// // Created by epagris on 2022.10.20.. // #include "eth_interface.h" #include "dynmem.h" #include "etherlib/prefab/conn_blocks/ethernet_connblock.h" #include "etherlib/prefab/packet_parsers/ipv4_types.h" #include "etherlib_options.h" #include "utils.h" typedef enum { ETH_IIE_RECV_NOTIFY = 0, ETH_IIE_LINK_CHG_NOTIFY, ETH_IIE_TRANSMIT_NOTIFY, ETH_IIE_UNKNOWN = 0xFFFF } EthIntfInternalEvent; static int ethintf_llrecv(EthIODef *io, const RawPckt *pckt) { ethinf_receive((EthInterface *)io->tag, pckt); return 0; } static int ethintf_llrxnotify(EthIODef *io) { ethinf_push_notification((EthInterface *)io->tag, ETH_IIE_RECV_NOTIFY, 0); return 0; } static int ethinf_lllinkchg(EthIODef *io, int ls, uint16_t speed, bool duplex) { // TAG: speed [11:2] | duplex [1] | ls [0] uint16_t tag = ((speed & 0x3FF) << 2) | ((duplex & 0b1) << 1) | (ls & 0b1); ethinf_push_notification((EthInterface *)io->tag, ETH_IIE_LINK_CHG_NOTIFY, tag); return 0; } static void ethintf_register(EthInterface *intf) { intf->ioDef->tag = intf; intf->ioDef->llRxStore = ethintf_llrecv; intf->ioDef->llRxNotify = ethintf_llrxnotify; intf->ioDef->llLinkChg = ethinf_lllinkchg; } // interface processing thread static ThreadReturnType task_ethintf(ThreadParamType param); EthInterface *ethintf_new(EthIODef *io) { EthInterface *ethIntf = (EthInterface *)dynmem_alloc(sizeof(EthInterface)); ASSERT_NULL(ethIntf); memset(ðIntf->sieve.layer0, 0, sizeof(PcktSieveLayer)); ethIntf->up = false; // the interface is down for now ethIntf->sieve.intf = ethIntf; ethIntf->sieve.layer0.connBReportFn = ethernet_print_report; ethIntf->ioDef = io; ethIntf->ip = 0; ethIntf->arpc = arpc_new(ethIntf, ETHLIB_ARPCACHE_SIZE); ASSERT_NULL(ethIntf->arpc); ethIntf->txQ = mq_create(ETHLIB_DEF_MQ_SIZE); ethIntf->rxQ = mq_create(ETHLIB_DEF_MQ_SIZE); ethIntf->eventQ = ETHLIB_OS_QUEUE_CREATE(ETHLIB_DEF_MQ_SIZE, uint32_t); // ETHLIB_OS_SEM_CREATE(ðIntf->eventSem, ETHLIB_DEF_MQ_SIZE); ETHLIB_OS_THREAD_CREATE(task_ethintf, "eth", ethIntf, osPriorityRealtime, 2048); ethIntf->ipra = ipra_new(ethIntf); ethIntf->capabilities = 0; ethIntf->dhcp = NULL; ethIntf->manageDhcp = true; // by default, enable automatic DHCP management memset(ðIntf->evtCbTable, 0, sizeof(EthIntfEventCbTable)); ethIntf->interceptTxCb = NULL; ethIntf->interceptRxCb = NULL; ethIntf->ip = 0; ethIntf->router = 0; ethIntf->dns = 0; ethIntf->netmask = IPv4_ANY_ADDR; ethIntf->linkState = false; ethintf_register(ethIntf); return ethIntf; } static ThreadReturnType task_ethintf(ThreadParamType param) { EthInterface *intf = (EthInterface *)param; while (true) { // fetch notification uint16_t event_code = ETH_IIE_UNKNOWN; uint16_t event_data; ethinf_pull_notification(intf, &event_code, &event_data); // act accordingly... switch (event_code) { case ETH_IIE_RECV_NOTIFY: { intf->ioDef->llRxRead(intf->ioDef); // read packets, will invoke RxStore while (mq_avail(intf->rxQ) > 0) { // get the raw packet RawPckt rawPckt = mq_top(intf->rxQ); mq_pop(intf->rxQ); // packet interception, if defined if (intf->interceptRxCb != NULL) { intf->interceptRxCb(intf, &rawPckt); } // packet processing packsieve_input(&intf->sieve, &rawPckt); } } break; case ETH_IIE_LINK_CHG_NOTIFY: { bool ls = event_data & 0b1; bool duplex = (event_data >> 1) & 0b1; uint16_t speed = (event_data >> 2); bool prev_ls = ethinf_get_link_state(intf); // get previous link state ethinf_set_link_state(intf, ls); // set new link state if ((intf->manageDhcp) && (intf->dhcp != NULL) && (ls != prev_ls)) { // no double start! if (ls) { // if link is on dhcp_start(intf->dhcp); } else { // if link is off dhcp_stop(intf->dhcp); } // print generic message MSG("ETH LINK: %s%s", (ls ? (ANSI_COLOR_BGREEN "UP ") : (ANSI_COLOR_BRED "DOWN\n")), ANSI_COLOR_RESET); // print link properties if (ls) { MSG("(%u Mbps, %s duplex)\n", speed, duplex ? "FULL" : "HALF"); } } } break; case ETH_IIE_TRANSMIT_NOTIFY: { intf->ioDef->llTxTrigger(intf->ioDef, intf->txQ); } break; default: MSG("UNKNOWN event!\n"); break; } } } void ethinf_receive(EthInterface *intf, const RawPckt *rawPckt) { bool pushOK = mq_push(intf->rxQ, rawPckt); if (!pushOK) { dynmem_free(rawPckt->payload); ERROR("Interface RECEIVE queue full, packet dropped!\n"); } } void ethinf_transmit(EthInterface *intf, const RawPckt *rawPckt) { // if link is down, discard packets if (!ethinf_get_link_state(intf)) { dynmem_free(rawPckt->payload); return; } // packet interception, if defined if (intf->interceptTxCb != NULL) { intf->interceptTxCb(intf, rawPckt); } // push packet onto the message queue if (mq_push(intf->txQ, rawPckt)) { ethinf_push_notification(intf, ETH_IIE_TRANSMIT_NOTIFY, 0); // notify processing thread of the peding transmit } else { ERROR("Interface TRANSMIT queue full, packet dropped!\n"); } } void ethinf_push_notification(EthInterface *intf, uint16_t event_code, uint16_t event_data) { if (!intf->up) { // disabled interfaces cannot receive notifications return; } uint32_t cpd = (event_data << 16) | event_code; // create event compound ETHLIB_OS_QUEUE_PUSH(intf->eventQ, &cpd); } void ethinf_pull_notification(EthInterface *intf, uint16_t *event_code, uint16_t *event_data) { uint32_t cpd; ETHLIB_OS_QUEUE_POP(intf->eventQ, &cpd); // wait for event semaphore // retrieve event code and data *event_code = cpd & 0xFFFF; *event_data = (cpd >> 16) & 0xFFFF; // MSG("EC: %d\n", *event_code); } void ethinf_set_capabilities(EthInterface *intf, uint32_t cap) { intf->capabilities = cap; } void ethinf_set_event_callback(EthInterface *intf, EthIntfEvIndex idx, EthIntfEvtCb cb) { ((EthIntfEvtCb *)((&(intf->evtCbTable))))[idx] = cb; } void ethinf_trigger_event(EthInterface *intf, EthIntfEvIndex idx) { EthIntfEvtCb cb = ((EthIntfEvtCb *)((&(intf->evtCbTable))))[idx]; if (cb != NULL) { cb(intf); } } void ethinf_set_link_state(EthInterface *intf, bool ls) { if (intf->linkState != ls) { // trigger event only if state has actually changed intf->linkState = ls; ethinf_trigger_event(intf, ETH_EVT_LINK_CHANGE); } } bool ethinf_get_link_state(EthInterface *intf) { return intf->linkState; } void ethinf_up(EthInterface *intf) { intf->up = true; // trigger a link change interrupt to query link state if (intf->ioDef->llTrigLinkChg != NULL) { intf->ioDef->llTrigLinkChg(intf->ioDef); } } void ethinf_down(EthInterface *intf) { intf->up = false; } void ethinf_set_intercept_callback(EthInterface *intf, EthIntfInterceptCb cb, EthIntfInterceptDir dir) { if (dir & ETH_INTERCEPT_TX) { intf->interceptTxCb = cb; } if (dir & ETH_INTERCEPT_RX) { intf->interceptRxCb = cb; } } void ethinf_set_automatic_dhcp_management(EthInterface *intf, bool automg) { intf->manageDhcp = automg; } void ethinf_set_config(EthInterface *intf, const EthInterfaceNetworkConfig *config) { intf->ip = config->ip; intf->router = config->router; intf->netmask = config->netmask; intf->dns = config->dns; intf->manageDhcp = config->manageDhcp; } void ethinf_get_config(EthInterface *intf, EthInterfaceNetworkConfig *config) { config->ip = intf->ip; config->router = intf->router; config->netmask = intf->netmask; config->dns = intf->dns; config->manageDhcp = intf->manageDhcp; } void ethinf_print_info(const EthInterface * intf) { MSG("IP: " ANSI_COLOR_BYELLOW); PRINT_IPv4(intf->ip); MSG(ANSI_COLOR_RESET "\nRouter: "); PRINT_IPv4(intf->router); MSG("\nNetmask: "); PRINT_IPv4(intf->netmask); MSG("\nDNS: "); PRINT_IPv4(intf->dns); MSG("\n\n"); if (intf->dhcp != NULL) { bool on = dhcp_get_fsm_state(intf->dhcp) != DHCP_STOPPED; MSG("DHCP is %s" ANSI_COLOR_RESET " %s.", on ? (ANSI_COLOR_BGREEN "RUNNING") : (ANSI_COLOR_BRED "STOPPED"), intf->manageDhcp ? "[automanaged]" : ""); } else { MSG ("DHCP is " ANSI_COLOR_RED "ABSENT" ANSI_COLOR_RESET "."); } MSG("\n"); }