Jump to content

    

STM32F207+FreeRTOS+LwIP замирает задача ethernetif_input

Продолжаю разбираться с FreeRTOS+LwIP. Камень STM32F207, физика KSZ8041.

Делаю простой UDP сервер, netconn, на основе примера ST.

 

Симптом следующий.

Посылаю с ПК запросы, устройство отвечает. Примерно через полчаса такой работы ответы приходить перестают. Просмотр состояния задач, очередей показал, что задача ethernetif_input висит в состоянии READY, но sheduler ее не запускает. Cемафор s_xSemaphore заполняется полностью. При этом другие задачи с меньшим приоритетом (мигание лампочки) выполняться продолжают.

 

Прочитал топик, внес изменения в ethernetif.c, это ни к чему не привело.

static void ethernet_watchdog(void) {
   /* When Rx Buffer unavailable flag is set: clear it and resume reception */
   if ((ETH->DMASR & ETH_DMASR_RBUS) != (u32)RESET)  
   {
       /* Clear RBUS ETHERNET DMA flag */
       ETH->DMASR = ETH_DMASR_RBUS;   

       /* Resume DMA reception. The register doesn't care what you write to it. */
       ETH->DMARPDR = 0;
   }
}

unsigned int thread_cnt = 0;
void ethernetif_input( void * pvParameters )
{
 struct pbuf *p;

 for( ;; )
 {
   if (xSemaphoreTake( s_xSemaphore, emacBLOCK_TIME_WAITING_FOR_INPUT)==pdTRUE)
   {
     while ((p = low_level_input( s_pxNetIf )) != 0) 
//      p = low_level_input( s_pxNetIf );
     {
       thread_cnt++;
       if (p != 0) {
           if (ERR_OK != s_pxNetIf->input( p, s_pxNetIf))
           {
             pbuf_free(p);
             p=NULL;
           }
       }
     }
   }
   ethernet_watchdog();
 }
}

 

Настройки FreeRTOS:

#define configUSE_PREEMPTION		1
#define configUSE_IDLE_HOOK	        0
#define configUSE_TICK_HOOK	        0
#define configCPU_CLOCK_HZ              ( ( unsigned long ) 120000000 )	
#define configTICK_RATE_HZ	        ( ( portTickType ) 1000 )
#define configMAX_PRIORITIES		( ( unsigned portBASE_TYPE ) 7 )
#define configMINIMAL_STACK_SIZE	( ( unsigned short ) 128 )
#define configTOTAL_HEAP_SIZE		( ( size_t ) ( 15 * 1024 ) )
#define configMAX_TASK_NAME_LEN		( 16 )
#define configUSE_TRACE_FACILITY	0
#define configUSE_16_BIT_TICKS		0
#define configIDLE_SHOULD_YIELD		1
#define configUSE_MUTEXES               1
#define configUSE_COUNTING_SEMAPHORES   1
#define configUSE_MALLOC_FAILED_HOOK    0

//For queue trace
#define configQUEUE_REGISTRY_SIZE       5

#define configCHECK_FOR_STACK_OVERFLOW  0

/* Co-routine definitions. */
#define configUSE_CO_ROUTINES 		0
#define configMAX_CO_ROUTINE_PRIORITIES ( 2 )

/* Set the following definitions to 1 to include the API function, or zero
to exclude the API function. */

#define INCLUDE_vTaskPrioritySet		0
#define INCLUDE_uxTaskPriorityGet		0
#define INCLUDE_vTaskDelete			0
#define INCLUDE_vTaskCleanUpResources	        0
#define INCLUDE_vTaskSuspend			0
#define INCLUDE_vTaskDelayUntil			0
#define INCLUDE_vTaskDelay		        1

/* This is the raw value as per the Cortex-M3 NVIC.  Values can be 255
(lowest) to 0 (1?) (highest). */
#define configKERNEL_INTERRUPT_PRIORITY         255
#define configMAX_SYSCALL_INTERRUPT_PRIORITY 	191 /* equivalent to 0xb0, or priority 11. */


/* This is the value being used as per the ST library which permits 16
priority values, 0 to 15.  This must correspond to the
configKERNEL_INTERRUPT_PRIORITY setting.  Here 15 corresponds to the lowest
NVIC value of 255. */
#define configLIBRARY_KERNEL_INTERRUPT_PRIORITY	15

 

Настройки LwIP:

#define SYS_LIGHTWEIGHT_PROT    1

#define ETHARP_TRUST_IP_MAC     0
#define IP_REASSEMBLY           0
#define IP_FRAG                 0
#define ARP_QUEUEING            0

#define LWIP_TCPIP_CORE_LOCKING 0

#define NO_SYS                  0

#define MEM_ALIGNMENT           4

#define MEM_SIZE                (20*1024)

#define MEMP_NUM_PBUF           200
#define MEMP_NUM_UDP_PCB        6
#define MEMP_NUM_TCP_PCB        10
#define MEMP_NUM_TCP_PCB_LISTEN 5
#define MEMP_NUM_TCP_SEG        20
#define MEMP_NUM_SYS_TIMEOUT    10

#define PBUF_POOL_SIZE          20

#define PBUF_POOL_BUFSIZE       500

#define LWIP_TCP                0
#define TCP_TTL                 255

#define LWIP_ICMP                       1

#define LWIP_DHCP               0

#define LWIP_UDP                1
#define UDP_TTL                 255

/* ---------- Statistics options ---------- */
#define LWIP_STATS 1
#define ETHARP_STATS 1
#define UDP_STATS 1
#define LWIP_PROVIDE_ERRNO 1
#define MEM_STATS 1


#define CHECKSUM_BY_HARDWARE 

#define LWIP_NETCONN                    1

#define LWIP_SOCKET                     0

#define LWIP_DEBUG                      0

#define TCPIP_THREAD_STACKSIZE          1000
#define TCPIP_MBOX_SIZE                 50
#define DEFAULT_UDP_RECVMBOX_SIZE       2
#define DEFAULT_TCP_RECVMBOX_SIZE       20
#define DEFAULT_ACCEPTMBOX_SIZE         20
#define DEFAULT_THREAD_STACKSIZE        500
#define TCPIP_THREAD_PRIO               (configMAX_PRIORITIES - 2)

 

 

 

Share this post


Link to post
Share on other sites

На работе сталкивались с подобной проблемой, но только на STMF417, могу посмотреть как там решили проблему(честно говоря не помню решили ли её вообще).

Share this post


Link to post
Share on other sites

V_M_Luck, а в ethernetif.c в low_level_input() тоже внесли изменения?

static struct pbuf * low_level_input(struct netif *netif)
{
 struct pbuf *p, *q;
 u16_t len;
 uint32_t l=0,i =0;
 FrameTypeDef frame;
 u8 *buffer;
 __IO ETH_DMADESCTypeDef *DMARxNextDesc;

 p = NULL;

 /* Get received frame */
 frame = ETH_Get_Received_Frame_interrupt();

 if (frame.descriptor && frame.buffer) {
  /* check that frame has no error */
  if ((frame.descriptor->Status & ETH_DMARxDesc_ES) == (uint32_t)RESET)
  {

	/* Obtain the size of the packet and put it into the "len" variable. */
	len = frame.length;
	buffer = (u8 *)frame.buffer;

	/* We allocate a pbuf chain of pbufs from the pool. */
	p = pbuf_alloc(PBUF_RAW, len, PBUF_POOL);

	/* Copy received frame from ethernet driver buffer to stack buffer */
	if (p != NULL)
	{
	  for (q = p; q != NULL; q = q->next)
	  {
		memcpy((u8_t*)q->payload, (u8_t*)&buffer[l], q->len);
		l = l + q->len;
	  }
	}
  }

  /* Release descriptors to DMA */
  /* Check if received frame with multiple DMA buffer segments */
  if (DMA_RX_FRAME_infos->Seg_Count > 1)
  {
	DMARxNextDesc = DMA_RX_FRAME_infos->FS_Rx_Desc;
  }
  else
  {
	DMARxNextDesc = frame.descriptor;
  }

  /* Set Own bit in Rx descriptors: gives the buffers back to DMA */
  for (i=0; i<DMA_RX_FRAME_infos->Seg_Count; i++)
  { 
	DMARxNextDesc->Status = ETH_DMARxDesc_OWN;
	DMARxNextDesc = (ETH_DMADESCTypeDef *)(DMARxNextDesc->Buffer2NextDescAddr);
  }

  /* Clear Segment_Count */
  DMA_RX_FRAME_infos->Seg_Count =0;
 }
 return p;
}

Edited by IXUS666

Share this post


Link to post
Share on other sites
IXUS666, да эти изменения я внес. Извините, я это сделал сразу в начале разборок и подзабыл где и что именно.

Share this post


Link to post
Share on other sites

Если отвлечься от LwIP, как во FreeRTOS можно завесить задачу с наивысшим приоритетом в состоянии READY? Что ее может блокировать?

Share this post


Link to post
Share on other sites
IXUS666, да эти изменения я внес. Извините, я это сделал сразу в начале разборок и подзабыл где и что именно.

Попробуйте запустить сервер на не блокирующих сокетах..

Похожая ситуация выплыла с HTTP сервером, когда размер HTTP запроса был больше чем PBUF_POOL_BUFSIZE-(~40-60байт) и netconn_thread блокировалась на совсем. Пока толком не разбирался с этим, но если запустить входное соединения на не блокирующих сокетах то работает (хотя я полагаю что это неправильно, т.к. должно работать и так).

Для этого нужно в определить в lwipopts.h #define LWIP_SO_RCVTIMEO 1 и после newconn = netconn_accept(conn), определить newconn->recv_timeout = XX, XX в ms. Хотя у вас механизм UDP, как с ним работать я не знаю.

 

 

Если отвлечься от LwIP, как во FreeRTOS можно завесить задачу с наивысшим приоритетом в состоянии READY? Что ее может блокировать?

Так если используете плагин AVIX-RT для RTOS, то в окошке Tasks можно посмотреть Event Object. Если задача заблокирована то в Event Object отображается адресс обьекта (очередь/мьютекс) котораый заблокировал задачу.

Share this post


Link to post
Share on other sites
Попробуйте запустить сервер на не блокирующих сокетах..

Для UDP вродебы нет смысла, там нет установления соединений, ожидания подтверждений и т.п. Да и пакеты я шлю небольшие. От компа 160 байт, в обратную сторону - 60 байт.

Так если используете плагин AVIX-RT для RTOS, то в окошке Tasks можно посмотреть Event Object. Если задача заблокирована то в Event Object отображается адресс обьекта (очередь/мьютекс) котораый заблокировал задачу.

Я и смотрю. Состояние задачи READY! Ничего не блокирует. А шедулер на эту задачу не переключает.

 

Вот что еще накопал.

Во время сбоя стал бряком в vTaskSwitchContext( ). Вижу, что в pxReadyTasksLists на списке уровня приоритета 6 (задача ethernetif_input) задача есть. А в переменной uxTopReadyPriority то 0, то 1. Т.е. шедулер так и не узнал о существовании готовой высокоприоритетной задачи. Или что-то ему помешало переключиться на эту задачу при ее размещении в очереди готовых. Если руками здесь подправить uxTopReadyPriority на 6, все начинает крутиться дальше, правда до следующего сбоя.

Справедливости ради, необходимо отметить, что иногда, гораздо реже, чем возникновение проблемы, описанной в топике, я сваливаюсь в HardFault. Видимо разваливается стек. Что из этих проблем первично - не совсем понятно, но проблема с зависанием задачи возникает чаще, пока разбираюсь с ней.

Share this post


Link to post
Share on other sites

Кажется разобрался. Дело было в приоритетах прерываний.

Я назначил для прерывания Ethernet приоритет выше, чем configMAX_SYSCALL_INTERRUPT_PRIORITY, что совсем неправильно.

1,5 часа - полет нормальный.

Share this post


Link to post
Share on other sites

На самом деле проблема в том, что вот в этом куске кода:

            // Освобождаем дескрипторы DMA

            // Проверяем, фрейм растянут на несколько дескрипторов или нет
            if (DMA_RX_FRAME_infos->Seg_Count > 1)
            {
                DMARxNextDesc = DMA_RX_FRAME_infos->FS_Rx_Desc;
            }
            else
            {
                DMARxNextDesc = frame.descriptor;
            }


            // Взводим бит OWN в Rx дескрипторах - отдаём буферы назад DMA
            for (i = 0; i < DMA_RX_FRAME_infos->Seg_Count; i++)
            {  
                DMARxNextDesc->Status = ETH_DMARxDesc_OWN;
                DMARxNextDesc = (ETH_DMADESCTypeDef *)(DMARxNextDesc->Buffer2NextDescAddr);
            }
  
            // Очищаем счётчик сегментов
            DMA_RX_FRAME_infos->Seg_Count = 0;
  
  
            // Если стоял флаг о том, что Rx буфер недоступен, то снимаем его и опять включаем приём
            if ((ETH->DMASR & ETH_DMASR_RBUS) != (u32)RESET)  
            {
                // Снимаем флаг Receive buffer unavailable
                ETH->DMASR = ETH_DMASR_RBUS;
      
                // Включаем приём
                ETH->DMARPDR = 0;
            }

Может возникнуть такая ситуация, что мы работаем с дескриптором, но внутренняя периферия Ethernet принимает ещё один фрейм в это время. И при переключении на следующий дескриптор там уже нет флага OWN и работать с ним DMA не может. Соответственно и в прерывание мы не попадём. Лечится очень просто. В этом куске кода:

            // Взводим бит OWN в Rx дескрипторах - отдаём буферы назад DMA
            for (i = 0; i < DMA_RX_FRAME_infos->Seg_Count; i++)
            {  
                DMARxNextDesc->Status = ETH_DMARxDesc_OWN;
                DMARxNextDesc = (ETH_DMADESCTypeDef *)(DMARxNextDesc->Buffer2NextDescAddr);
            }

Добавляем, чтобы стало так:

            // Взводим бит OWN в Rx дескрипторах - отдаём буферы назад DMA
            for (i = 0; i < DMA_RX_FRAME_infos->Seg_Count; i++)
            {  
                DMARxNextDesc->Status = ETH_DMARxDesc_OWN;
                DMARxNextDesc = (ETH_DMADESCTypeDef *)(DMARxNextDesc->Buffer2NextDescAddr);

                // Если после смены дескриптора на следующий он не свободен, то нужно обработать его тоже
                // фактически ещё раз попасть в это прерывание
                if ((DMARxNextDesc->Status & ETH_DMARxDesc_OWN) == 0) {
                    again = true;
                }
            }

А там, где снимаем флаги прерываний (далее по коду) поменять на:

        // Снимаем флаги прерываний
        if (!again)
        {
            ETH_DMAClearITPendingBit(ETH_DMA_IT_R);
            ETH_DMAClearITPendingBit(ETH_DMA_IT_NIS);
        }
        else
        {
            // Сюда можно добавить счётчик и убедиться, что иногда такое бывает
        }

Ну и где-нибудь объявить переменную bool again = false;

Мне такой танец с бубном помог. Оставил блок на всю ночь, вот утром проверил - полёт нормальный. А до этого через 2 часа работы зависал стабильно.

Share this post


Link to post
Share on other sites

Добрый день уважаемый V_M_Luck. Работаю с той же физикой KSZ8041.

Вопрос следующий: как вы изменили файл stm32_eth.h, а конкретно мне интересно

та часть кода где указывается адрес PHY_SR, регистра который у каждой физики разный

 

/** 
  * @brief  For LAN8700  
  */ 
//#define PHY_SR                           31         /*!< Tranceiver Status Register */
/** 
  * @brief  For DP83848  
  */ 
#define PHY_SR                           16     /*!< Tranceiver Status Register */

/* The Speed and Duplex mask values change from a PHY to another so the user have to update
   this value depending on the used external PHY */
/** 
  * @brief  For LAN8700  
  */ 
//#define PHY_Speed_Status            ((u16)0x0004)       /*!< Configured information of Speed: 10Mbps */
//#define PHY_Duplex_Status           ((u16)0x0010)       /*!< Configured information of Duplex: Full-duplex */

/** 
  * @brief  For DP83848  
  */ 
#define PHY_Speed_Status            ((u16)0x0002)    /*!< Configured information of Speed: 10Mbps */
#define PHY_Duplex_Status           ((u16)0x0004)    /*!< Configured information of Duplex: Full-duplex */
#define IS_ETH_PHY_ADDRESS(ADDRESS) ((ADDRESS) <= 0x20)
#define IS_ETH_PHY_REG(REG) (((REG) == PHY_BCR) || \
                             ((REG) == PHY_BSR) || \
                             ((REG) == PHY_SR))

 

за ответ зараннее благодарен

Share this post


Link to post
Share on other sites
Добрый день уважаемый V_M_Luck. Работаю с той же физикой KSZ8041.

Вопрос следующий: как вы изменили файл stm32_eth.h, а конкретно мне интересно

та часть кода где указывается адрес PHY_SR, регистра который у каждой физики разный

Извините, что сразу не ответил. Если еще актуально...

Все адреса регистров и биты описаны в даташите на KSZ8041.

Фактически, если работаете с библиотеками STM, вам нужно оперделить адреса регистров и биты и слегка подправить ETH_Init в файле stm32fxxx_eth.c.

У меня в этой функции от настройки физики оставлено только это:

  /*-------------------- PHY initialization and configuration ----------------*/
  /* Put the PHY in reset mode */
  if(!(ETH_WritePHYRegister(PHYAddress, PHY_BCR, PHY_Reset)))
  {
    /* Return ERROR in case of write timeout */
    return ETH_ERROR;
  }
  
  while(ETH_ReadPHYRegister(PHYAddress, PHY_BCR) & PHY_Reset);

  /* Enable Auto-Negotiation */
  if(!(ETH_WritePHYRegister(PHYAddress, PHY_ANAR, PHY_ANAR_100BTX_FD | PHY_ANAR_100BTX_HD | PHY_ANAR_10BT_FD | PHY_ANAR_10BT_HD | PHY_ANAR_SELECT)))
  {
    /* Return ERROR in case of write timeout */
    return ETH_ERROR;
  }
  /* Start Auto-Negotiation */
  if(!(ETH_WritePHYRegister(PHYAddress, PHY_BCR, PHY_AutoNegotiation | PHY_Restart_AutoNegotiation)))
  {
    /* Return ERROR in case of write timeout */
    return ETH_ERROR;
  }

И, далее, в какой-нибудь низкоуровневой задаче (или просто в main) проверять состояние подключения (идею и куски кода позаимствовал у кого-то на форуме - большое ему спасибо):

  if (ETH_ReadPHYRegister(PHY_ADDRESS, PHY_BSR) & PHY_Linked_Status)
  {
    //Соединение установлено
    switch(prev_link_state)
    {
    case 0:
      RegValue = ETH_ReadPHYRegister(PHY_ADDRESS, PHY_CNTR2) & PHY_CNTR2_OP_MODE;
      
      switch(RegValue)
      {
      case PHY_CNTR2_OP_MODE_10HD:  // 10 BASE T Half-duplex
        prev_link_state=1;
        RegValue = PHY_HALFDUPLEX_10M;
        ETH_WritePHYRegister(PHY_ADDRESS, PHY_BCR, RegValue);
        RegValue = ETH->MACCR;
        RegValue &= ~(ETH_MACCR_FES | ETH_MACCR_DM);
        ETH->MACCR = RegValue;
        break;
      case PHY_CNTR2_OP_MODE_100HD:  // 100 BASE TX Half-duplex
        prev_link_state=1;
        RegValue = PHY_HALFDUPLEX_100M;
        ETH_WritePHYRegister(PHY_ADDRESS, PHY_BCR, RegValue);
        RegValue = ETH->MACCR;
        RegValue &= ~(ETH_MACCR_FES | ETH_MACCR_DM);
        RegValue |= ETH_MACCR_FES;
        ETH->MACCR = RegValue;
        break;
      case PHY_CNTR2_OP_MODE_10FD: // 10 BASE T Full-duplex
        prev_link_state=2;
        RegValue = PHY_FULLDUPLEX_10M;
        ETH_WritePHYRegister(PHY_ADDRESS, PHY_BCR, RegValue);
        RegValue = ETH->MACCR;
        RegValue &= ~(ETH_MACCR_FES | ETH_MACCR_DM);
        RegValue |= ETH_MACCR_DM;
        ETH->MACCR = RegValue;
        break;
      case PHY_CNTR2_OP_MODE_100FD: // 100 BASE TX Full-duplex
        prev_link_state=2;
        RegValue = PHY_FULLDUPLEX_100M;
        ETH_WritePHYRegister(PHY_ADDRESS, PHY_BCR, RegValue);
        RegValue = ETH->MACCR;
        RegValue |= (ETH_MACCR_FES | ETH_MACCR_DM);
        ETH->MACCR = RegValue;
        break;
      default:
        break;
      }
    case 1:
      
      break;
    }
  }
  else
  {
    //Нет соединения
    if (prev_link_state) 
    {
      //Соединение было потеряно
      prev_link_state=0;
      // Enable and start Auto-Negotiation
      ETH_WritePHYRegister(PHY_ADDRESS, PHY_BCR, PHY_AutoNegotiation | PHY_Restart_AutoNegotiation);
    }
  }

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
Sign in to follow this