jeka 0 1 мая, 2021 Опубликовано 1 мая, 2021 · Жалоба При работе езернета натолкнулся на интересный глюк - содержимое пакета во время обработки может перезаписаться (на другой пакет). Причем долго работало нормально, стало проявляться только когда много коротких пакетов после задержки в роутере пачкой выплёвываются. Стал копать. Смотрю, куб из коробки генерирует такой вот код (специально обновил куб, свежий то же самое генерит): static struct pbuf * low_level_input(struct netif *netif) { struct pbuf *p = NULL; uint32_t framelength = 0, i = 0; struct pbuf_custom* custom_pbuf; ETH_BufferTypeDef RxBuff[ETH_RX_DESC_CNT]; memset(RxBuff, 0 , ETH_RX_DESC_CNT*sizeof(ETH_BufferTypeDef)); for(i = 0; i < ETH_RX_DESC_CNT -1; i++) { RxBuff[i].next=&RxBuff[i+1]; } if (HAL_ETH_GetRxDataBuffer(&heth, RxBuff) == HAL_OK) { HAL_ETH_GetRxDataLength(&heth, &framelength); HAL_ETH_BuildRxDescriptors(&heth); #if !defined(DUAL_CORE) || defined(CORE_CM7) /* Invalidate data cache for ETH Rx Buffers */ SCB_InvalidateDCache_by_Addr((uint32_t *)RxBuff->buffer, framelength); #endif lan_stats.rx_frames++; custom_pbuf = (struct pbuf_custom*)LWIP_MEMPOOL_ALLOC(RX_POOL); if(custom_pbuf != NULL) { custom_pbuf->custom_free_function = pbuf_free_custom; p = pbuf_alloced_custom(PBUF_RAW, framelength, PBUF_REF, custom_pbuf, RxBuff->buffer, framelength); } } return p; } забавно, что HAL_ETH_BuildRxDescriptors (как я понял это функция, которая помечает буфера приема как "свободно") вызывается не дожидаясь обработки пакета (которая в традиционно в процессе RTOS крутится). Подебажил - действительно, после вызова HAL_ETH_BuildRxDescriptors буфер перезаписывается свежими пакетами. Вот думаю, что разработчики имели в виду написав так... и как проще исправить. Цитата Поделиться сообщением Ссылка на сообщение Поделиться на другие сайты Поделиться
jeka 0 1 мая, 2021 Опубликовано 1 мая, 2021 · Жалоба Еще не понял. HAL_ETH_GetRxDataLength(&heth, &framelength); - это размер всего пакета же, а не одного куска в дескрипторе? т.е. далее по тексту сброс DCACHE подразумевает целый пакет одним куском. Но... ведь пакет может быть в нескольких дескрипторах и не подряд идущих (переход по кругу от последнего буфера к первому), и получается хвост может потерять, если пакет не влезает в один дескриптор? Цитата Поделиться сообщением Ссылка на сообщение Поделиться на другие сайты Поделиться
dimka76 63 1 мая, 2021 Опубликовано 1 мая, 2021 · Жалоба 26 minutes ago, jeka said: Еще не понял. HAL_ETH_GetRxDataLength(&heth, &framelength); - это размер всего пакета же, а не одного куска в дескрипторе? т.е. далее по тексту сброс DCACHE подразумевает целый пакет одним куском. Но... ведь пакет может быть в нескольких дескрипторах и не подряд идущих (переход по кругу от последнего буфера к первому), и получается хвост может потерять, если пакет не влезает в один дескриптор? Обычно размер Ethernet фрейма 1536 байт. В примерах STM размеры приемных буферов такими и выбираются. #define ETH_RX_BUFFER_SIZE (1536UL) Поэтому пакет в нескольких кусках быть не может. И в моих примерах от ST (именно взято из примеров, а не из Куба) код другой. static struct pbuf * low_level_input(struct netif *netif) { struct pbuf *p = NULL; ETH_BufferTypeDef RxBuff; uint32_t framelength = 0; struct pbuf_custom* custom_pbuf; if (HAL_ETH_IsRxDataAvailable(&EthHandle)) { HAL_ETH_GetRxDataBuffer(&EthHandle, &RxBuff); HAL_ETH_GetRxDataLength(&EthHandle, &framelength); /* Invalidate data cache for ETH Rx Buffers */ SCB_InvalidateDCache_by_Addr((uint32_t *)Rx_Buff, (ETH_RX_DESC_CNT*ETH_RX_BUFFER_SIZE)); custom_pbuf = (struct pbuf_custom*)LWIP_MEMPOOL_ALLOC(RX_POOL); custom_pbuf->custom_free_function = pbuf_free_custom; p = pbuf_alloced_custom(PBUF_RAW, framelength, PBUF_REF, custom_pbuf, RxBuff.buffer, ETH_RX_BUFFER_SIZE); return p; } else { return NULL; } } void ethernetif_input(struct netif *netif) { err_t err; struct pbuf *p; /* move received packet into a new pbuf */ p = low_level_input(netif); /* no packet could be read, silently ignore this */ if (p == NULL) return; /* entry point to the LwIP stack */ err = netif->input(p, netif); if (err != ERR_OK) { LWIP_DEBUGF(NETIF_DEBUG, ("ethernetif_input: IP input error\n")); pbuf_free(p); p = NULL; } HAL_ETH_BuildRxDescriptors(&EthHandle); } Т.е. сначала идет обработка пакета, а потом HAL_ETH_BuildRxDescriptors Цитата Поделиться сообщением Ссылка на сообщение Поделиться на другие сайты Поделиться
jeka 0 1 мая, 2021 Опубликовано 1 мая, 2021 · Жалоба 4 hours ago, dimka76 said: Поэтому пакет в нескольких кусках быть не может. Как раз хотелось бы дескрипторы поменьше, например 128 байт, но общий объем буферов около 20кб. Иначе при маленьких пакетах и больших буферах эффективность использования RAM низкая, а packet rate может быть весьма высокий. И получается, если система задумалась при обработке жирного пакета, на буферизацию мелких пакетов может памяти не хватить (что собственно и происходит на практике). Сейчас смотрю реализацию езернета в порте микропитона под STM... Код явно в разы шустрей чем hal от stm. Видимо придется взять его за основу и немного доработать, ибо он на мелкие дескрипторы не заточен. Потрассировал HAL - честно разочаровывают программисты от ST. такие жирные функции крутить на каждом пакете, которые по факту ничего не делают. 4 hours ago, dimka76 said: И в моих примерах от ST (именно взято из примеров, а не из Куба) код другой. Вероятно, у Вас в поллинг режиме езернет работает. Мне же нужно с IRQ и RTOS. Цитата Поделиться сообщением Ссылка на сообщение Поделиться на другие сайты Поделиться
mantech 53 2 мая, 2021 Опубликовано 2 мая, 2021 · Жалоба 13 часов назад, jeka сказал: Вероятно, у Вас в поллинг режиме езернет работает. Мне же нужно с IRQ и RTOS. А если заDDOSят ваш эзернет на прерываниях, вся программа встанет "колом"... Цитата Поделиться сообщением Ссылка на сообщение Поделиться на другие сайты Поделиться
pyroman 2 2 мая, 2021 Опубликовано 2 мая, 2021 · Жалоба 19 minutes ago, mantech said: А если заDDOSят ваш эзернет на прерываниях, вся программа встанет "колом"... Да не должно, вроде. 64 байта (минимальный размер пакета) + 12 байт (минимальный интервал между пакетами) - это на скорости 100 Мб/сек будет ~6 мкс. Для контроллера с частотой, скажем, 200 МГц - 1200 циклов на обработку прерывания. Достаточно, по-моему, времени. Цитата Поделиться сообщением Ссылка на сообщение Поделиться на другие сайты Поделиться
mantech 53 2 мая, 2021 Опубликовано 2 мая, 2021 · Жалоба 2 часа назад, pyroman сказал: Да не должно, вроде. Дак примете пакет, его же еще через стек прогнать надо, а если там еще куча протоколов... А если стек в поллинге, то что вы выиграете? Цитата Поделиться сообщением Ссылка на сообщение Поделиться на другие сайты Поделиться
pyroman 2 2 мая, 2021 Опубликовано 2 мая, 2021 (изменено) · Жалоба 1 hour ago, mantech said: Дак примете пакет, его же еще через стек прогнать надо, а если там еще куча протоколов... А если стек в поллинге, то что вы выиграете? Я только про то, что программа "колом" не встанет, остальные аспекты не затрагиваю. Другие задачи RTOS, не связанные с ethernet, будут выполняться штатно. Изменено 2 мая, 2021 пользователем pyroman Цитата Поделиться сообщением Ссылка на сообщение Поделиться на другие сайты Поделиться
dimka76 63 2 мая, 2021 Опубликовано 2 мая, 2021 · Жалоба 2 hours ago, mantech said: Дак примете пакет, его же еще через стек прогнать надо, а если там еще куча протоколов... А если стек в поллинге, то что вы выиграете? А вы что бы предложили ? Цитата Поделиться сообщением Ссылка на сообщение Поделиться на другие сайты Поделиться
mantech 53 2 мая, 2021 Опубликовано 2 мая, 2021 (изменено) · Жалоба 2 часа назад, dimka76 сказал: А вы что бы предложили ? Как вариант, прерывания только, чтобы выделить новый адрес для кольцевого буфера, обработчик стека протоколов в другой задаче или суперлупом (если без РТОС), поллингом... Изменено 2 мая, 2021 пользователем mantech Цитата Поделиться сообщением Ссылка на сообщение Поделиться на другие сайты Поделиться
jeka 0 3 мая, 2021 Опубликовано 3 мая, 2021 · Жалоба On 5/1/2021 at 8:40 PM, dimka76 said: И в моих примерах от ST (именно взято из примеров, а не из Куба) код другой. ... Т.е. сначала идет обработка пакета, а потом HAL_ETH_BuildRxDescriptors Поразбирался. Самое веселое, что куда ни ставь HAL_ETH_BuildRxDescriptors, глюк не исчезает по банальной причине - пакет обрабатывается в другом потоке... Пришел пакет -> вызвалось Ethernet IRQ -> RxPktSemaphore -> завершение IRQ далее отрабатывает Ethif task: ethernetif_input -> tcpip_inpkt -> sys_mbox_trypost -> osMessageQueuePut -> xQueueSendToBack -> и в конце вызывает HAL_ETH_BuildRxDescriptors. Причем если вызов HAL_ETH_BuildRxDescriptors перенести в tcpip_thread, HAL перестанет сдвигать буфера и будет бесконечно один и тот же пакет пытаться отправить. далее, всё это принимает поток tcpip_thread. И в ней уже обрабатывается пакет, который был освобожден предыдущим потоком, со всеми вытекающими: tcpip_thread -> ethernet_input Вообщем, сделали крайне бестолково. Думаю, весь Ethif task надо убирать ибо он не нужен, а пакет из ethernetif_input сразу отдавать на обработку в ethernet_input. Цитата Поделиться сообщением Ссылка на сообщение Поделиться на другие сайты Поделиться
jeka 0 4 мая, 2021 Опубликовано 4 мая, 2021 · Жалоба После патча косяки исчезли. Вот подправленный варинат: static ETH_BufferTypeDef RxBuff[ETH_RX_DESC_CNT];// вынес в статику, чтоб при каждом пакете не инициализировать их заново. static struct pbuf * low_level_input(struct netif *netif) { struct pbuf *p = NULL; uint32_t framelength = 0; struct pbuf_custom* custom_pbuf; if (HAL_ETH_GetRxDataBuffer(&heth, RxBuff) == HAL_OK) { HAL_ETH_GetRxDataLength(&heth, &framelength); #if !defined(DUAL_CORE) || defined(CORE_CM7) // SCB_InvalidateDCache_by_Addr((uint32_t *)RxBuff->buffer, framelength); __DSB();// если кеш принимаемых пакетов отключен #endif lan_stats.rx_frames++; custom_pbuf = (struct pbuf_custom*)LWIP_MEMPOOL_ALLOC(RX_POOL); if(custom_pbuf != NULL) { custom_pbuf->custom_free_function = pbuf_free_custom; p = pbuf_alloced_custom(PBUF_RAW, framelength, PBUF_REF, custom_pbuf, RxBuff->buffer, framelength); } } return p; } extern SemaphoreHandle_t ipSemaphore; void ethernetif_input(void* argument) { struct pbuf *p; struct netif *netif = (struct netif *) argument; memset(RxBuff, 0 , ETH_RX_DESC_CNT*sizeof(ETH_BufferTypeDef)); for(int i = 0; i < ETH_RX_DESC_CNT -1; i++) RxBuff[i].next=&RxBuff[i+1]; while (ipSemaphore==NULL) osDelay(1); for( ;; ) { if (osSemaphoreAcquire(RxPktSemaphore, TIME_WAITING_FOR_INPUT) == osOK) { do { p = low_level_input( netif ); if (p != NULL) { // if (netif->input( p, netif) != ERR_OK ) if( xSemaphoreTake( ipSemaphore, ( TickType_t ) 0 ) ) { ethernet_input(p, netif); xSemaphoreGive( ipSemaphore ); } else pbuf_free(p); /* Build Rx descriptor to be ready for next data reception */ HAL_ETH_BuildRxDescriptors(&heth); } } while(p!=NULL); } } } И в tcpip.c семафор вставил: SemaphoreHandle_t ipSemaphore = NULL; static void tcpip_thread(void *arg) { struct tcpip_msg *msg; LWIP_UNUSED_ARG(arg); ipSemaphore = xSemaphoreCreateMutex(); xSemaphoreGive( ipSemaphore ); LWIP_MARK_TCPIP_THREAD(); LOCK_TCPIP_CORE(); if (tcpip_init_done != NULL) { tcpip_init_done(tcpip_init_done_arg); } while (1) { /* MAIN Loop */ LWIP_TCPIP_THREAD_ALIVE(); /* wait for a message, timeouts are processed while waiting */ TCPIP_MBOX_FETCH(&tcpip_mbox, (void **)&msg); if (msg == NULL) { LWIP_DEBUGF(TCPIP_DEBUG, ("tcpip_thread: invalid message: NULL\n")); LWIP_ASSERT("tcpip_thread: invalid message", 0); continue; } if( xSemaphoreTake( ipSemaphore, ( TickType_t ) 0 ) ) { tcpip_thread_handle_msg(msg); xSemaphoreGive( ipSemaphore ); } } } Цитата Поделиться сообщением Ссылка на сообщение Поделиться на другие сайты Поделиться