Jump to content

    

косяк ethernet в коде куба?

Recommended Posts

jeka

При работе езернета натолкнулся на интересный глюк - содержимое пакета во время обработки может перезаписаться (на другой пакет). Причем долго работало нормально, стало проявляться только когда много коротких пакетов после задержки в роутере пачкой выплёвываются. Стал копать.

Смотрю, куб из коробки генерирует такой вот код (специально обновил куб, свежий то же самое генерит):

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 буфер перезаписывается свежими пакетами.

Вот думаю, что разработчики имели в виду написав так... и как проще исправить.

Share this post


Link to post
Share on other sites

jeka

Еще не понял. HAL_ETH_GetRxDataLength(&heth, &framelength); - это размер всего пакета же, а не одного куска в дескрипторе? т.е. далее по тексту сброс DCACHE подразумевает целый пакет одним куском. Но... ведь пакет может быть в нескольких дескрипторах и не подряд идущих (переход по кругу от последнего буфера к первому), и получается хвост может потерять, если пакет не влезает в один дескриптор?

Share this post


Link to post
Share on other sites

dimka76
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 

 

Share this post


Link to post
Share on other sites

jeka
4 hours ago, dimka76 said:

Поэтому пакет в нескольких кусках быть не может.

Как раз хотелось бы дескрипторы поменьше, например 128 байт, но общий объем буферов около 20кб. Иначе при маленьких пакетах и больших буферах эффективность использования RAM низкая, а packet rate может быть весьма высокий. И получается, если система задумалась при обработке жирного пакета, на буферизацию мелких пакетов может памяти не хватить (что собственно и происходит на практике).

Сейчас смотрю реализацию езернета в порте микропитона под STM... Код явно в разы шустрей чем hal от stm. Видимо придется взять его за основу и немного доработать, ибо он на мелкие дескрипторы не заточен. Потрассировал HAL - честно разочаровывают программисты от ST. такие жирные функции крутить на каждом пакете, которые по факту ничего не делают.

 

4 hours ago, dimka76 said:

И в моих примерах от ST (именно взято из примеров, а не из Куба) код другой.

Вероятно, у Вас в поллинг режиме езернет работает. Мне же нужно с IRQ и RTOS.

Share this post


Link to post
Share on other sites

mantech
13 часов назад, jeka сказал:

Вероятно, у Вас в поллинг режиме езернет работает. Мне же нужно с IRQ и RTOS.

А если заDDOSят ваш эзернет на прерываниях, вся программа встанет "колом"...

Share this post


Link to post
Share on other sites

pyroman
19 minutes ago, mantech said:

А если заDDOSят ваш эзернет на прерываниях, вся программа встанет "колом"...

Да не должно, вроде.
64 байта (минимальный размер пакета) + 12 байт (минимальный интервал между пакетами) - это на скорости 100 Мб/сек будет ~6 мкс. Для контроллера с частотой, скажем, 200 МГц - 1200 циклов на обработку прерывания. Достаточно, по-моему, времени.

Share this post


Link to post
Share on other sites

mantech
2 часа назад, pyroman сказал:

Да не должно, вроде.

Дак примете пакет, его же еще через стек прогнать надо, а если там еще куча протоколов... А если стек в поллинге, то что вы выиграете?

Share this post


Link to post
Share on other sites

pyroman
1 hour ago, mantech said:

Дак примете пакет, его же еще через стек прогнать надо, а если там еще куча протоколов... А если стек в поллинге, то что вы выиграете?

Я только про то, что программа "колом" не встанет, остальные аспекты не затрагиваю. Другие задачи RTOS, не связанные с ethernet, будут выполняться штатно.

Edited by pyroman

Share this post


Link to post
Share on other sites

dimka76
2 hours ago, mantech said:

Дак примете пакет, его же еще через стек прогнать надо, а если там еще куча протоколов... А если стек в поллинге, то что вы выиграете?

А вы что бы предложили ?

Share this post


Link to post
Share on other sites

mantech
2 часа назад, dimka76 сказал:

А вы что бы предложили ?

Как вариант, прерывания только, чтобы выделить новый адрес для кольцевого буфера, обработчик стека протоколов в другой задаче или суперлупом (если без РТОС), поллингом...

Edited by mantech

Share this post


Link to post
Share on other sites

jeka
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.

 

Share this post


Link to post
Share on other sites

jeka

После патча косяки исчезли.

Вот подправленный варинат:

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 );
      }
  }
}

Share this post


Link to post
Share on other sites

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.