Перейти к содержанию
    

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

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

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

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

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

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

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

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

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 

 

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

4 hours ago, dimka76 said:

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

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

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

 

4 hours ago, dimka76 said:

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

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

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

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

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

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

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

19 minutes ago, mantech said:

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

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

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

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

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

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

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

1 hour ago, mantech said:

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

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

Изменено пользователем pyroman

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

2 hours ago, mantech said:

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

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

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

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

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

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

Изменено пользователем mantech

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

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.

 

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

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

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

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

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

Присоединяйтесь к обсуждению

Вы можете написать сейчас и зарегистрироваться позже. Если у вас есть аккаунт, авторизуйтесь, чтобы опубликовать от имени своего аккаунта.

Гость
Ответить в этой теме...

×   Вставлено с форматированием.   Вставить как обычный текст

  Разрешено использовать не более 75 эмодзи.

×   Ваша ссылка была автоматически встроена.   Отображать как обычную ссылку

×   Ваш предыдущий контент был восстановлен.   Очистить редактор

×   Вы не можете вставлять изображения напрямую. Загружайте или вставляйте изображения по ссылке.

×
×
  • Создать...