From 57d878a6c35443e84e2b7ccbc69ef4e0d2968090 Mon Sep 17 00:00:00 2001 From: Andrea Gilardoni Date: Tue, 26 Sep 2023 13:53:40 +0200 Subject: [PATCH 1/7] Changing the logic of ethernet driver to avoid busy waiting --- libraries/Ethernet/src/EthernetDriver.cpp | 25 ++++++++++++++--------- libraries/Ethernet/src/EthernetDriver.h | 4 ++-- 2 files changed, 17 insertions(+), 12 deletions(-) diff --git a/libraries/Ethernet/src/EthernetDriver.cpp b/libraries/Ethernet/src/EthernetDriver.cpp index 3ef4fb7f4..70eaf43d1 100644 --- a/libraries/Ethernet/src/EthernetDriver.cpp +++ b/libraries/Ethernet/src/EthernetDriver.cpp @@ -64,7 +64,7 @@ class EthernetDriver { #define ETHER_FRAME_TRANSFER_COMPLETED (1UL << 21) #define ETHER_MAGIC_PACKET_DETECTED_MASK (1UL << 1) -static volatile bool frame_transmitted_flag = false; +static volatile bool frame_being_transmitted = false; static EthernetDriver eth_driver; static uint8_t eth_tx_buffer[ETH_BUFF_DIM]; @@ -230,7 +230,7 @@ void EthernetDriver::irq_callback(ether_callback_args_t * p_args) { if (ETHER_FRAME_TRANSFER_COMPLETED == (reg_eesr & ETHER_FRAME_TRANSFER_COMPLETED)) { - frame_transmitted_flag = true; + frame_being_transmitted = false; /* FRAME TRANSMISSION COMPLETED */ if(frame_transmitted != nullptr) { frame_transmitted(); @@ -341,18 +341,23 @@ void eth_release_rx_buffer() { bool eth_output(uint8_t *buf, uint16_t dim) { - frame_transmitted_flag = false; - fsp_err_t err = R_ETHER_Write ( eth_driver.get_ctrl(), buf, dim); - if(err == FSP_SUCCESS) { - - while(!frame_transmitted_flag) { + bool retval = true; - } - return true; + fsp_err_t err = R_ETHER_Write(eth_driver.get_ctrl(), buf, dim); + if(err == FSP_SUCCESS) { + frame_being_transmitted = true; + retval = true; } else { - return false; + retval = false; } + + return retval; +} + +// this function return true if the tx buffer is not being used for the transmission of another frame +bool eth_output_can_transimit() { + return !frame_being_transmitted; } uint8_t *eth_input(volatile uint32_t *dim) { diff --git a/libraries/Ethernet/src/EthernetDriver.h b/libraries/Ethernet/src/EthernetDriver.h index 6205ed1ec..e9b4a701c 100644 --- a/libraries/Ethernet/src/EthernetDriver.h +++ b/libraries/Ethernet/src/EthernetDriver.h @@ -23,6 +23,7 @@ bool eth_init(); void eth_execute_link_process(); uint8_t *eth_input(volatile uint32_t *dim); bool eth_output(uint8_t *buf, uint16_t dim); +bool eth_output_can_transimit(); void eth_release_rx_buffer(); uint8_t *eth_get_tx_buffer(uint16_t *size); void eth_set_rx_frame_cbk(EtherCallback_f fn); @@ -33,5 +34,4 @@ void eth_set_lan_wake_up_cbk(EtherCallback_f fn); void eth_set_magic_packet_cbk(EtherCallback_f fn); - -#endif \ No newline at end of file +#endif From b7c9e9b358a1d999cdfd30ae2ff44624c45c6593 Mon Sep 17 00:00:00 2001 From: Andrea Gilardoni Date: Tue, 26 Sep 2023 13:54:19 +0200 Subject: [PATCH 2/7] using std::function to allow passing object functions --- libraries/Ethernet/src/EthernetDriver.h | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/libraries/Ethernet/src/EthernetDriver.h b/libraries/Ethernet/src/EthernetDriver.h index e9b4a701c..18cf68f9f 100644 --- a/libraries/Ethernet/src/EthernetDriver.h +++ b/libraries/Ethernet/src/EthernetDriver.h @@ -5,8 +5,9 @@ #include "r_ether_phy.h" #include "r_ether_api.h" #include "r_ether.h" +#include -using EtherCallback_f = void (*)(void); +using EtherCallback_f = std::function; #define ETHERNET_IRQ_PRIORITY 10 From 6de156881053d905363501cdf9f1a04713d15651 Mon Sep 17 00:00:00 2001 From: Andrea Gilardoni Date: Tue, 26 Sep 2023 14:02:43 +0200 Subject: [PATCH 3/7] Fix on Ethernet driver and lwip wrapper This fix aims to solve issues on ethernet on portenta c33. This required some changes in the structure of the library, in order to avoid wasting memory on temporary queues. --- libraries/lwIpWrapper/src/CNetIf.cpp | 206 +++++++++++++-------------- libraries/lwIpWrapper/src/CNetIf.h | 57 ++++---- 2 files changed, 133 insertions(+), 130 deletions(-) diff --git a/libraries/lwIpWrapper/src/CNetIf.cpp b/libraries/lwIpWrapper/src/CNetIf.cpp index 75d2329c0..baeeba8f1 100644 --- a/libraries/lwIpWrapper/src/CNetIf.cpp +++ b/libraries/lwIpWrapper/src/CNetIf.cpp @@ -1,4 +1,5 @@ #include "CNetIf.h" +#include IPAddress CNetIf::default_ip("192.168.0.10"); IPAddress CNetIf::default_nm("255.255.255.0"); @@ -9,7 +10,6 @@ CNetIf* CLwipIf::net_ifs[] = { nullptr }; bool CLwipIf::wifi_hw_initialized = false; bool CLwipIf::connected_to_access_point = false; WifiStatus_t CLwipIf::wifi_status = WL_IDLE_STATUS; -std::queue CLwipIf::eth_queue; bool CLwipIf::pending_eth_rx = false; FspTimer CLwipIf::timer; @@ -69,7 +69,22 @@ CLwipIf::CLwipIf() ch = FspTimer::get_available_timer(type, true); } - timer.begin(TIMER_MODE_PERIODIC, type, ch, 10.0, 50.0, timer_cb); + /* + * NOTE Timer and buffer size + * The frequency for the timer highly influences the memory requirements for the desired transfer speed + * You can calculate the buffer size required to achieve that performance from the following formula: + * buffer_size[byte] = Speed[bit/s] * timer_frequency[Hz]^-1 / 8 + * + * In the case of portenta C33, the maximum speed achievable was measured with + * iperf2 tool (provided by lwip) and can reach up to 12Mbit/s. + * Further improvements can be made, but if we desire to reach that speed the buffer size + * and the timer frequency should be designed accordingly. + * buffer = 12 * 10^6 bit/s * (100Hz)^-1 / 8 = 15000 Byte = 15KB + * + * Since this is a constrained environment we could accept performance loss and + * delegate lwip to handle lost packets. + */ + timer.begin(TIMER_MODE_PERIODIC, type, ch, 100.0, 50.0, timer_cb); timer.setup_overflow_irq(); timer.open(); timer.start(); @@ -252,56 +267,48 @@ CNetIf* CLwipIf::get(NetIfType_t type, } /* -------------------------------------------------------------------------- */ -void CLwipIf::ethLinkUp() -{ - /* -------------------------------------------------------------------------- */ - if (net_ifs[NI_ETHERNET] != nullptr) { - net_ifs[NI_ETHERNET]->setLinkUp(); - } -} +void CEth::handleEthRx() +{ + /* + * This function is called by the ethernet driver, when a frame is receiverd, + * as a callback inside an interrupt context. + * It is required to be as fast as possible and not perform busy waits. + * + * The idea is the following: + * - take the rx buffer pointer + * - try to allocate a pbuf of the desired size + * - if it is possible copy the the buffer inside the pbuf and give it to lwip netif + * - release the buffer + * + * If the packet is discarded the upper TCP/IP layers should handle the retransmission of the lost packets. + * This should not happen really often if the buffers and timers are designed taking into account the + * desired performance + */ + __disable_irq(); -/* -------------------------------------------------------------------------- */ -void CLwipIf::ethLinkDown() -{ - /* -------------------------------------------------------------------------- */ - if (net_ifs[NI_ETHERNET] != nullptr) { - net_ifs[NI_ETHERNET]->setLinkDown(); - } -} + volatile uint32_t rx_frame_dim = 0; + volatile uint8_t* rx_frame_buf = eth_input(&rx_frame_dim); + if (rx_frame_dim > 0 && rx_frame_buf != nullptr) { + struct pbuf* p=nullptr; -/* -------------------------------------------------------------------------- */ -void CLwipIf::ethFrameRx() -{ - /* -------------------------------------------------------------------------- */ + p = pbuf_alloc(PBUF_RAW, rx_frame_dim, PBUF_RAM); - if (pending_eth_rx) { - pending_eth_rx = false; - volatile uint32_t rx_frame_dim = 0; - volatile uint8_t* rx_frame_buf = eth_input(&rx_frame_dim); - if (rx_frame_dim > 0 && rx_frame_buf != nullptr) { - while (rx_frame_dim % 4 != 0) { - rx_frame_dim++; - } - struct pbuf* p = pbuf_alloc(PBUF_RAW, rx_frame_dim, PBUF_RAM); - if (p != NULL) { - /* Copy ethernet frame into pbuf */ - pbuf_take((struct pbuf*)p, (uint8_t*)rx_frame_buf, (uint32_t)rx_frame_dim); - eth_release_rx_buffer(); - eth_queue.push((struct pbuf*)p); + if (p != NULL) { + /* Copy ethernet frame into pbuf */ + pbuf_take((struct pbuf*)p, (uint8_t*)rx_frame_buf, (uint32_t)rx_frame_dim); + + if (ni.input((struct pbuf*)p, &ni) != ERR_OK) { + pbuf_free((struct pbuf*)p); } } - } -} -/* -------------------------------------------------------------------------- */ -void CLwipIf::setPendingEthRx() -{ - /* -------------------------------------------------------------------------- */ - pending_eth_rx = true; + eth_release_rx_buffer(); + } + __enable_irq(); } /* -------------------------------------------------------------------------- */ -err_t CLwipIf::initEth(struct netif* _ni) +err_t CEth::init(struct netif* _ni) { /* -------------------------------------------------------------------------- */ #if LWIP_NETIF_HOSTNAME @@ -316,7 +323,7 @@ err_t CLwipIf::initEth(struct netif* _ni) * from it if you have to do some checks before sending (e.g. if link * is available...) */ _ni->output = etharp_output; - _ni->linkoutput = ouputEth; + _ni->linkoutput = CEth::output; /* set MAC hardware address */ _ni->hwaddr_len = eth_get_mac_address(_ni->hwaddr); @@ -328,36 +335,42 @@ err_t CLwipIf::initEth(struct netif* _ni) /* don't set NETIF_FLAG_ETHARP if this device is not an ethernet one */ _ni->flags |= NETIF_FLAG_BROADCAST | NETIF_FLAG_ETHARP; - /* set the callback function that is called when an ethernet frame is physically - received, it is important that the callbacks are set before the initializiation */ - eth_set_rx_frame_cbk(setPendingEthRx); - eth_set_link_on_cbk(ethLinkUp); - eth_set_link_off_cbk(ethLinkDown); - return ERR_OK; } /* -------------------------------------------------------------------------- */ -err_t CLwipIf::ouputEth(struct netif* _ni, struct pbuf *p) { -/* -------------------------------------------------------------------------- */ - (void)_ni; +err_t CEth::output(struct netif* _ni, struct pbuf *p) { +/* -------------------------------------------------------------------------- */ + /* + * This function is called inside the lwip timeout engine. Since we are working inside + * an environment without threads it is required to not lock. For this reason we should + * avoid busy waiting and instead discard the transmission. Lwip will handle the retransmission + * of the packet. + */ + (void)_ni; + + err_t errval = ERR_OK; + + if(eth_output_can_transimit()) { + uint16_t tx_buf_dim = 0; + + // TODO analyze the race conditions that may arise from sharing a non synchronized buffer + uint8_t *tx_buf = eth_get_tx_buffer(&tx_buf_dim); + assert (p->tot_len <= tx_buf_dim); - err_t errval = ERR_OK; - uint16_t tx_buf_dim = 0; - uint8_t *tx_buf = eth_get_tx_buffer(&tx_buf_dim); - assert (p->tot_len <= tx_buf_dim); + uint16_t bytes_actually_copied = pbuf_copy_partial(p, tx_buf, p->tot_len, 0); - uint16_t bytes_actually_copied = pbuf_copy_partial(p, tx_buf, p->tot_len, 0); - if (bytes_actually_copied > 0) { - if (!eth_output(tx_buf, bytes_actually_copied)) { + if (bytes_actually_copied > 0 && !eth_output(tx_buf, bytes_actually_copied)) { errval = ERR_IF; } + } else { + errval = ERR_INPROGRESS; } return errval; } /* -------------------------------------------------------------------------- */ -err_t CLwipIf::outputWifiStation(struct netif* _ni, struct pbuf *p) { +err_t CWifiStation::output(struct netif* _ni, struct pbuf *p) { /* -------------------------------------------------------------------------- */ (void)_ni; err_t errval = ERR_IF; @@ -366,8 +379,8 @@ err_t CLwipIf::outputWifiStation(struct netif* _ni, struct pbuf *p) { uint16_t bytes_actually_copied = pbuf_copy_partial(p, buf, p->tot_len, 0); if (bytes_actually_copied > 0) { int ifn = 0; - if (net_ifs[NI_WIFI_STATION] != nullptr) { - ifn = net_ifs[NI_WIFI_STATION]->getId(); + if (CLwipIf::net_ifs[NI_WIFI_STATION] != nullptr) { + ifn = CLwipIf::net_ifs[NI_WIFI_STATION]->getId(); } #ifdef DEBUG_OUTPUT_DISABLED @@ -391,7 +404,7 @@ err_t CLwipIf::outputWifiStation(struct netif* _ni, struct pbuf *p) { } /* -------------------------------------------------------------------------- */ -err_t CLwipIf::initWifiStation(struct netif* _ni) +err_t CWifiStation::init(struct netif* _ni) { /* -------------------------------------------------------------------------- */ #if LWIP_NETIF_HOSTNAME @@ -406,7 +419,7 @@ err_t CLwipIf::initWifiStation(struct netif* _ni) * from it if you have to do some checks before sending (e.g. if link * is available...) */ _ni->output = etharp_output; - _ni->linkoutput = outputWifiStation; + _ni->linkoutput = CWifiStation::output; /* maximum transfer unit */ _ni->mtu = 1500; @@ -422,7 +435,7 @@ err_t CLwipIf::initWifiStation(struct netif* _ni) } /* -------------------------------------------------------------------------- */ -err_t CLwipIf::outputWifiSoftAp(struct netif* _ni, struct pbuf* p) +err_t CWifiSoftAp::output(struct netif* _ni, struct pbuf* p) { /* -------------------------------------------------------------------------- */ (void)_ni; @@ -434,8 +447,8 @@ err_t CLwipIf::outputWifiSoftAp(struct netif* _ni, struct pbuf* p) uint16_t bytes_actually_copied = pbuf_copy_partial(p, buf, p->tot_len, 0); if (bytes_actually_copied > 0) { int ifn = 0; - if (net_ifs[NI_WIFI_SOFTAP] != nullptr) { - ifn = net_ifs[NI_WIFI_SOFTAP]->getId(); + if (CLwipIf::net_ifs[NI_WIFI_SOFTAP] != nullptr) { + ifn = CLwipIf::net_ifs[NI_WIFI_SOFTAP]->getId(); } if (CEspControl::getInstance().sendBuffer(ESP_AP_IF, ifn, buf, bytes_actually_copied) == ESP_CONTROL_OK) { @@ -449,7 +462,7 @@ err_t CLwipIf::outputWifiSoftAp(struct netif* _ni, struct pbuf* p) } /* -------------------------------------------------------------------------- */ -err_t CLwipIf::initWifiSoftAp(struct netif* _ni) +err_t CWifiSoftAp::init(struct netif* _ni) { /* -------------------------------------------------------------------------- */ #if LWIP_NETIF_HOSTNAME @@ -464,7 +477,7 @@ err_t CLwipIf::initWifiSoftAp(struct netif* _ni) * from it if you have to do some checks before sending (e.g. if link * is available...) */ _ni->output = etharp_output; - _ni->linkoutput = outputWifiSoftAp; + _ni->linkoutput = CWifiSoftAp::output; /* maximum transfer unit */ _ni->mtu = 1500; @@ -640,8 +653,8 @@ int CLwipIf::connectToAp(const char* ssid, const char* pwd) rv = ESP_CONTROL_OK; /* when we get the connection to access point we are sure we are STATION and we are connected */ - if (net_ifs[NI_WIFI_STATION] != nullptr) { - net_ifs[NI_WIFI_STATION]->setLinkUp(); + if (CLwipIf::net_ifs[NI_WIFI_STATION] != nullptr) { + CLwipIf::net_ifs[NI_WIFI_STATION]->setLinkUp(); } } @@ -762,28 +775,13 @@ int CLwipIf::resetLowPowerMode() return rv; } -/* -------------------------------------------------------------------------- */ -struct pbuf* CLwipIf::getEthFrame() -{ - /* -------------------------------------------------------------------------- */ - struct pbuf* rv = nullptr; - if (!CLwipIf::eth_queue.empty()) { - rv = CLwipIf::eth_queue.front(); - CLwipIf::eth_queue.pop(); - } - else { - CLwipIf::eth_queue = {}; - } - return rv; -} - #ifdef LWIP_USE_TIMER /* -------------------------------------------------------------------------- */ void CLwipIf::timer_cb(timer_callback_args_t *arg) { /* -------------------------------------------------------------------------- */ (void)arg; CLwipIf::getInstance().lwip_task(); -} +} #endif /* *************************************************************************** @@ -1288,7 +1286,7 @@ void CEth::begin(IPAddress _ip, IPAddress _gw, IPAddress _nm) IP_ADDR4(&nm, _nm[0], _nm[1], _nm[2], _nm[3]); IP_ADDR4(&gw, _gw[0], _gw[1], _gw[2], _gw[3]); - netif_add(&ni, &ip, &nm, &gw, NULL, CLwipIf::initEth, ethernet_input); + netif_add(&ni, &ip, &nm, &gw, NULL, CEth::init, ethernet_input); netif_set_default(&ni); if (netif_is_link_up(&ni)) { @@ -1303,32 +1301,27 @@ void CEth::begin(IPAddress _ip, IPAddress _gw, IPAddress _nm) /* Set the link callback function, this function is called on change of link status */ // netif_set_link_callback(ð0if, eht0if_link_toggle_cbk); #endif /* LWIP_NETIF_LINK_CALLBACK */ + /* + * set the callback function that is called when an ethernet frame is physically + * received, it is important that the callbacks are set before the initializiation + */ + eth_set_rx_frame_cbk(std::bind(&CEth::handleEthRx, this)); + eth_set_link_on_cbk(std::bind(&CEth::setLinkUp, this)); + eth_set_link_off_cbk(std::bind(&CEth::setLinkDown, this)); } /* -------------------------------------------------------------------------- */ void CEth::task() { /* -------------------------------------------------------------------------- */ - struct pbuf* p = nullptr; eth_execute_link_process(); - __disable_irq(); - CLwipIf::ethFrameRx(); - p = (struct pbuf*)CLwipIf::getInstance().getEthFrame(); - __enable_irq(); - if (p != nullptr) { - - if (ni.input((struct pbuf*)p, &ni) != ERR_OK) { - pbuf_free((struct pbuf*)p); - } - } - - #if LWIP_DHCP static unsigned long dhcp_last_time_call = 0; if (dhcp_last_time_call == 0 || millis() - dhcp_last_time_call > DHCP_FINE_TIMER_MSECS) { dhcp_task(); + dhcp_last_time_call = millis(); } #endif } @@ -1351,7 +1344,7 @@ void CWifiStation::begin(IPAddress _ip, IPAddress _gw, IPAddress _nm) IP_ADDR4(&nm, _nm[0], _nm[1], _nm[2], _nm[3]); IP_ADDR4(&gw, _gw[0], _gw[1], _gw[2], _gw[3]); - netif_add(&ni, &ip, &nm, &gw, NULL, CLwipIf::initWifiStation, ethernet_input); + netif_add(&ni, &ip, &nm, &gw, NULL, CWifiStation::init, ethernet_input); netif_set_default(&ni); if (netif_is_link_up(&ni)) { @@ -1402,6 +1395,7 @@ void CWifiStation::task() static unsigned long dhcp_last_time_call = 0; if (dhcp_last_time_call == 0 || millis() - dhcp_last_time_call > DHCP_FINE_TIMER_MSECS) { dhcp_task(); + dhcp_last_time_call = millis(); } #endif } @@ -1458,7 +1452,7 @@ void CWifiSoftAp::begin(IPAddress _ip, IPAddress _gw, IPAddress _nm) IP_ADDR4(&nm, _nm[0], _nm[1], _nm[2], _nm[3]); IP_ADDR4(&gw, _gw[0], _gw[1], _gw[2], _gw[3]); - netif_add(&ni, &ip, &nm, &gw, NULL, CLwipIf::initWifiSoftAp, ethernet_input); + netif_add(&ni, &ip, &nm, &gw, NULL, CWifiSoftAp::init, ethernet_input); netif_set_default(&ni); if (netif_is_link_up(&ni)) { /* When the netif is fully configured this function must be called */ @@ -1478,7 +1472,8 @@ void CWifiSoftAp::begin(IPAddress _ip, IPAddress _gw, IPAddress _nm) void CWifiSoftAp::task() { /* -------------------------------------------------------------------------- */ - /* get messages and process it */ + /* get messages and process it + * TODO change the algorithm and make it similar to WiFiStation */ uint8_t if_num; uint16_t dim; uint8_t* buf = nullptr; @@ -1505,6 +1500,7 @@ void CWifiSoftAp::task() static unsigned long dhcp_last_time_call = 0; if (dhcp_last_time_call == 0 || millis() - dhcp_last_time_call > DHCP_FINE_TIMER_MSECS) { dhcp_task(); + dhcp_last_time_call = millis(); } #endif } diff --git a/libraries/lwIpWrapper/src/CNetIf.h b/libraries/lwIpWrapper/src/CNetIf.h index ed4f8d855..557d63948 100644 --- a/libraries/lwIpWrapper/src/CNetIf.h +++ b/libraries/lwIpWrapper/src/CNetIf.h @@ -11,7 +11,6 @@ #include "CEspControl.h" #include "IPAddress.h" #include "EthernetDriver.h" -#include #include #ifdef USE_LWIP_AS_LIBRARY #include "lwip/include/lwip/dhcp.h" @@ -216,6 +215,15 @@ class CNetIf { class CEth : public CNetIf { /* -------------------------------------------------------------------------- */ protected: + /* + * this function is used to initialize the netif structure of lwip + */ + static err_t init(struct netif* ni); + + /* + * This function is passed to lwip and used to send a buffer to the driver in order to transmit it + */ + static err_t output(struct netif* ni, struct pbuf* p); public: CEth(); virtual ~CEth(); @@ -229,12 +237,23 @@ class CEth : public CNetIf { UNUSED(mac); return 1; } + + virtual void handleEthRx(); }; /* -------------------------------------------------------------------------- */ class CWifiStation : public CNetIf { /* -------------------------------------------------------------------------- */ protected: + /* + * this function is used to initialize the netif structure of lwip + */ + static err_t init(struct netif* ni); + + /* + * This function is passed to lwip and used to send a buffer to the driver in order to transmit it + */ + static err_t output(struct netif* ni, struct pbuf* p); public: CWifiStation(); virtual ~CWifiStation(); @@ -254,6 +273,16 @@ class CWifiStation : public CNetIf { /* -------------------------------------------------------------------------- */ class CWifiSoftAp : public CNetIf { /* -------------------------------------------------------------------------- */ +protected: + /* + * this function is used to initialize the netif structure of lwip + */ + static err_t init(struct netif* ni); + + /* + * This function is passed to lwip and used to send a buffer to the driver in order to transmit it + */ + static err_t output(struct netif* ni, struct pbuf* p); public: CWifiSoftAp(); virtual ~CWifiSoftAp(); @@ -274,14 +303,14 @@ class CWifiSoftAp : public CNetIf { class CLwipIf { /* -------------------------------------------------------------------------- */ private: - static std::queue eth_queue; - bool eth_initialized; int dns_num; bool willing_to_start_sync_req; bool async_requests_ongoing; + friend CWifiStation; + friend CWifiSoftAp; static CNetIf* net_ifs[NETWORK_INTERFACES_MAX_NUM]; static WifiStatus_t wifi_status; @@ -349,24 +378,6 @@ class CLwipIf { void addDns(IPAddress aDNSServer); IPAddress getDns(int _num = 0); - /* - * these functions are passed to netif_add function as 'init' function for - * that network interface - */ - - static err_t initEth(struct netif* ni); - static err_t initWifiStation(struct netif* ni); - static err_t initWifiSoftAp(struct netif* ni); - - /* - * these functions are passed to are set to netif->linkoutput function during - * the execution of the previous init function - */ - - static err_t ouputEth(struct netif* ni, struct pbuf* p); - static err_t outputWifiStation(struct netif* netif, struct pbuf* p); - static err_t outputWifiSoftAp(struct netif* netif, struct pbuf* p); - /* when you 'get' a network interface, you get a pointer to one of the pointers held by net_ifs array if the array element then an attempt to set up the network interface is made @@ -380,9 +391,7 @@ class CLwipIf { static void ethLinkUp(); static void ethLinkDown(); - static void ethFrameRx(); - static void setPendingEthRx(); /* this function set the mac address of the corresponding interface to mac and set this value for lwip */ bool setMacAddress(NetIfType_t type, uint8_t* mac = nullptr); @@ -415,8 +424,6 @@ class CLwipIf { int setWifiMode(WifiMode_t mode); - struct pbuf* getEthFrame(); - void lwip_task(); }; From f2f33c67eb694590942a2571a1850bee85f06851 Mon Sep 17 00:00:00 2001 From: Andrea Gilardoni Date: Tue, 26 Sep 2023 14:07:05 +0200 Subject: [PATCH 4/7] CLwipIf fix indentations --- libraries/lwIpWrapper/src/CNetIf.cpp | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/libraries/lwIpWrapper/src/CNetIf.cpp b/libraries/lwIpWrapper/src/CNetIf.cpp index baeeba8f1..0abee9c33 100644 --- a/libraries/lwIpWrapper/src/CNetIf.cpp +++ b/libraries/lwIpWrapper/src/CNetIf.cpp @@ -144,22 +144,22 @@ CLwipIf::~CLwipIf() /* -------------------------------------------------------------------------- */ int CLwipIf::disconnectEventcb(CCtrlMsgWrapper *resp) { - (void)resp; - if(CLwipIf::connected_to_access_point) { - wifi_status = WL_DISCONNECTED; - if(net_ifs[NI_WIFI_STATION] != nullptr) { - net_ifs[NI_WIFI_STATION]->setLinkDown(); - } - } - return ESP_CONTROL_OK; + (void)resp; + if(CLwipIf::connected_to_access_point) { + wifi_status = WL_DISCONNECTED; + if(net_ifs[NI_WIFI_STATION] != nullptr) { + net_ifs[NI_WIFI_STATION]->setLinkDown(); + } + } + return ESP_CONTROL_OK; } /* -------------------------------------------------------------------------- */ int CLwipIf::initEventCb(CCtrlMsgWrapper *resp) { - (void)resp; - CLwipIf::wifi_hw_initialized = true; - return ESP_CONTROL_OK; + (void)resp; + CLwipIf::wifi_hw_initialized = true; + return ESP_CONTROL_OK; } From 4df2dd20c31317b3708f0e60d86aa49f4aa30a29 Mon Sep 17 00:00:00 2001 From: Andrea Gilardoni Date: Tue, 26 Sep 2023 13:41:14 +0200 Subject: [PATCH 5/7] Increasing lwip buffer size to improve performances --- extras/net/lwipopts.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extras/net/lwipopts.h b/extras/net/lwipopts.h index 1eac55bef..bd520af91 100644 --- a/extras/net/lwipopts.h +++ b/extras/net/lwipopts.h @@ -137,7 +137,7 @@ * a lot of data that needs to be copied, this should be set high. */ #ifndef MEM_SIZE -#define MEM_SIZE (1522*4) +#define MEM_SIZE (15*1024) #endif From a34f20ac41330bbaa1d8ddea44366b30f7601123 Mon Sep 17 00:00:00 2001 From: Andrea Gilardoni Date: Tue, 26 Sep 2023 13:41:59 +0200 Subject: [PATCH 6/7] disabling TCP_QUEUE_OOSEQ to avoid wasting memory --- extras/net/lwipopts.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extras/net/lwipopts.h b/extras/net/lwipopts.h index bd520af91..87d0d9a5c 100644 --- a/extras/net/lwipopts.h +++ b/extras/net/lwipopts.h @@ -596,7 +596,7 @@ * Define to 0 if your device is low on memory. */ #ifndef TCP_QUEUE_OOSEQ -#define TCP_QUEUE_OOSEQ (LWIP_TCP) +#define TCP_QUEUE_OOSEQ 0 #endif /** From 23eccce7f0e755aac472ec05f0e3f2c43f66476a Mon Sep 17 00:00:00 2001 From: Andrea Gilardoni Date: Tue, 26 Sep 2023 13:43:58 +0200 Subject: [PATCH 7/7] Recompiling lwip --- .../lwIpWrapper/src/cortex-m33/liblwIP.a | Bin 277060 -> 274428 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/libraries/lwIpWrapper/src/cortex-m33/liblwIP.a b/libraries/lwIpWrapper/src/cortex-m33/liblwIP.a index 7c55e58add373c727e0cac32ff710db30ae8fe81..adaabfb1cd45db2e25e5800f1377a10dcf103d47 100644 GIT binary patch delta 17894 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 RQg!dP4UlXRo2F|P{~zWciyHs{