Task Crash While Waiting for Semaphore

paulromero wrote on Wednesday, July 27, 2016:

Dear Group:

I am having a FreeRTOS problem which I believe is due to incorrect
interrupt priority configuration.

The generic hardware is the Cortex M4 and the LPC4088 processor running on
on an Embedded Artist’s LPC4088-32 Developer’s Kit in particular. (i.e. The FreeRTOS
version is V8.0.1.)

Interrupts generated by LPC4088 UART2 are associated with NVIC interrupt
UART2_IRQn and handled by the UART2_IRQHandler() interrupt handler. It signals
the Modem_UART_Event_Handler() task with the BT_UART_Data_Signal semaphore.

The symptoms of the problem are as follow:

  1. A UART2 interrupt is generated, UART2_IRQHandler() signals MODEM_UART_Event_Handler()
    of the event via the BT_UART_Data_Signal semaphore and the event is successfully
    handled. At this point MODEM_UART_Event_Handler() task has called xSemaphoreTake()
    once.

  2. The MODEM_UART_Event_Handler() call xSemaphoreTake() again and crashes as follows:

    In the FreeRTOS ListInsert() routine the crash occurs when the following line
    of code executes:

    pxNewListItem->pxPrevious = pXIterator;

    The sequence of FreeRTOS calls is as follows:

     SemaphoreTake();
     QueueGenericReceive();
     TaskPlaceOnEventList();
     ListInsert();
    

The definitions and code I consider relevant follow below.

The relevant UART definitions are as follows:

#define		BT_UART		LPC_UART2
#define		BT_IRQ		UART2_IRQn

The relevant definitions from FreeRTOSConfig.h are as follows.

#define configMAX_PRIORITIES		( 5 )
#define configPRIO_BITS       		4        /* 15 priority levels */

#define configLIBRARY_LOWEST_INTERRUPT_PRIORITY			0xf
#define configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY	5

/* Interrupt priorities used by the kernel port layer itself.  These are generic
to all Cortex-M ports, and do not rely on any particular library functions. */
#define configKERNEL_INTERRUPT_PRIORITY ( configLIBRARY_LOWEST_INTERRUPT_PRIORITY << (8 - configPRIO_BITS) )
/* !!!! configMAX_SYSCALL_INTERRUPT_PRIORITY must not be set to zero !!!!
See http://www.FreeRTOS.org/RTOS-Cortex-M3-M4.html. */
#define configMAX_SYSCALL_INTERRUPT_PRIORITY 	( configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY << (8 - configPRIO_BITS) )

The priority initialization for the interrupt handler for UART2 is as follows and
the interrupt handler follows below:

 unsigned int pri;
	//
	// Finally enable the IRQ interrupt.
	// The priority of SysTickIRQn is 30, and that of TIMER1_IRQn is 29
	// Set the UART1_IRQn to 28 as it is the highest priority less than
	// that of TIME1_IRQn.
	//
	// The LPC4088 interrupt priority range is as follows:
	//
	//  HIGH: 0 - 15
	//  LOW: 16 - 31
	//
	pri = ((1u << __NVIC_PRIO_BITS) - 2u) - 2u;	// SysTickIRQn
	pri -= 3;
	NVIC_SetPriority(BT_IRQ, pri);

This is the relevant task and semaphore initialization:

	BT_UART_Data_Signal = xSemaphoreCreateBinary();
	//
	// BT_MODEM_HANDLER_TASK - Highest Priority - Almost
	//
	xTaskCreate(Modem_UART_Event_Handler, (signed char *) "MODEM/UART",
				configMINIMAL_STACK_SIZE, NULL, (tskIDLE_PRIORITY + 3UL),
				&task_id);

This is the relevant portion of the task:

TASK void Modem_UART_Event_Handler(void *parm)
{
	INTERTASK_MSG  msg;
	UINT ecause, n;

	vTaskDelay(configTICK_RATE_HZ * 3);

	for(;;)
	  {

		//
		// Wait until the UART #2 interrupt handler signals that data is available.
		//
		xSemaphoreTake(BT_UART_Data_Signal, portMAX_DELAY);

		switch(MU_Event.type)
		{
		case MU_XE_TXFIFO_EMPTY:
			if(MU_Data->mode == FRAME_MODE)
			  {
				msg.event = IEV_BT_FIFO_EMPTY;
				msg.data = NULL;
				msg.length = 0;
				X_SEND_MSG(LINK_HANDLER_TASK, &msg, X_HI_PRIO);
			  }
			else
				atd_tx_stored(TRUE);
			break;
		case MU_XE_RXDATA:
			n = Chip_UART_ReadRB(BT_UART, &MU_Data->rx_ring,
		 	                      (void *) MU_Data->rx_data_buf, BT_MAX_RING_BUF);

			if(MU_Data->mode == FRAME_MODE)
				link_handle_data(MU_Data->rx_data_buf, n);
			else
				atd_rx_data(MU_Data->rx_data_buf, n);

			break;
		case MU_XE_PHY_ERROR:
			ecause = MU_Event.cause;
			bt_rx_phy_error(ecause);
			break;
		default:
			//
			// BUG
			//
			abort();
		}

	  }

}

Finally, this is the interrupt handler:

ROUTINE void UART2_IRQHandler(void)
{
	BaseType_t hi_prio_alert;
	UINT cause;

	MU_Event.type = MU_XE_INVALID;
	MU_Event.cause = 0;

	//
	// Examine te IIR register to determine the interrupt cause.
	//
	cause = Chip_UART_ReadIntIDReg(BT_UART);
	cause &= 0xFF;

	//
	// Ignore a spurious interrupt.
	//
	if( !(cause & UART_IIR_INTSTAT_PEND) )
	  {
		if( (cause & (UART_IIR_INTID_THRE | UART_IIR_INTID_RDA)) ||
		  ( (cause & UART_IIR_INTID_CTI) == UART_IIR_INTID_CTI ) )
		  {
			//
			// In the case of a RBR or THR interrupt, this the following routine
			// manages Ring Buffer and FIFO data transfer transactions.
			//
			// After the data transfer, notify the BT_MODEM_HANLDER_TASK that
			// data is available for reading, or that a data transmission operation
			// is complete.
			//
			Chip_UART_IRQRBHandler(BT_UART, &MU_Data->rx_ring, &MU_Data->tx_ring);
	
			if( cause &  UART_IIR_INTID_THRE )
				MU_Event.type = MU_XE_TXFIFO_EMPTY;
			else
				MU_Event.type = MU_XE_RXDATA;
		  }
		else
		if( (cause & UART_IIR_INTID_RLS) == UART_IIR_INTID_RLS )
		  {
			MU_Event.type = MU_XE_PHY_ERROR;
			MU_Event.cause = Chip_UART_ReadLineStatus(BT_UART);
			MU_Event.cause &= 0xFF;
		  }
		else
		  {
			//
			// BUG: Invalid cause.
			//
			abort();
		  }
	
		//
		// Signal the BT_MODEM_HANDLER_TASK of an event.
		//
		xSemaphoreGiveFromISR(BT_UART_Data_Signal, &hi_prio_alert);
	  }
	else
	  {
		//
		// Spurious interrupt
		//
		;
	  }
	//
	// The BT_MODEM_HANDLER_TASK has a higher priority than almost
	// all other tasks which could be executing. If that is the case,
 	// force a context switch to the BT_MODEM_HANDLER_TASK.
	//
	portEND_SWITCHING_ISR(hi_prio_alert);

}

rtel wrote on Thursday, July 28, 2016:

First, tripple check the value of

__NVICPRIOBITS
,

as this has been the cause of some confusion over the years with the NXP headers and documentation mismatching. For example, a quick web search finds it set to 3 in this file: http://www.keil.com/dd/docs/arm/nxp/lpc43xx/lpc43xx.h (you have it set to 4) and a comment that it has been corrected in this change history: http://cdn.rowleydownload.co.uk/arm/packages/LPC4300.htm

Do you have configASSERT() defined? http://www.freertos.org/a00110.html#configASSERT That should assist in finding a priority assignment error - it might be worth updating to a newer version of FreeRTOS to as newer versions have additional assert() points for better error detection.

paulromero wrote on Thursday, July 28, 2016:

There is a conflict between the number of NVIC interrupt priority bits of the LPC4088 Cortex M4
processor as defined by NXP versus CMIS3. NXP defines 5 bits and 32 priority levels split
into two groups–the maskable group is appropriate for use with FreeRTOS. CMIS3 defines 4
bits and 16 priority levels by necessity.

(i.e. The target environment is an LPC4088 processor on the Embedded Artist’s LPC4088-32
Developer’s Kit board.)

I am fairly certain that the NXP 5 bit definition is correct.

When __NVIC_PRIO_BITS is set to 5 bits in my environment and xSemaporeGiveFromISR(), called from the interrupt handler,
correctly signals the Modem_UART_Handler() task with
BT_UART_Data_Signal semaphore. In that case the priority
of SysTickIRQn = 30 and can be computed as follows:

(1 << NVIC_PRIO_BITS)  - 2;

The important FreeRTOSConfig.h definitions should be as follows:

#define configPRIO_BITS   5      /* 32 priority levels */
#define configLIBRARY_LOWEST_INTERRUPT_PRIORITY	0x1f

It isn’t clear if configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY should
be set but it is set to 5 as in the current configuration and any clues
would be appreciated. If the value is 5, the encoded value,
configMAX_SYSCALL_INTERRUPT_PRIORITY, is 40.

In LPC407x_8x_177x_8x.h the value is as follows:

#define __NVIC_PRIO_BITS   5    /* !< Number of Bits used for Priority Levels */

This seems reasonable since a great deal of Cortex M4 documentation indicates
there are 32 rather than 15 priority levels. Chapter 5 section 3.5
of the LPC17xx edition of “Using the FreeRTOS Real Time Kernel” indicates there
are 32 priority levels. Section 5.5 of the UM10562–The LPC408x/LPC407x User Manual,
indicates there are 5 interrupt bits and 32 levels as well.

In CMSIS/Include/core_cm3.h the value is as follows:

#define __NVIC_PRIO_BITS          4

Best Regards,

Paul R.

rtel wrote on Friday, July 29, 2016:

It isn’t so clear how configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY should
be set but it is set to 5 in the current configuration. This is important
because the priority of an interrupt that uses xSemaphoreGiveFromISR() must
be numerically greater than configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY
if I
understand the documentation correctly. Is this right ?

Here is an example for 5 bits:

/* Use the system definition, if there is one */
#ifdef __NVIC_PRIO_BITS
  #define configPRIO_BITS  __NVIC_PRIO_BITS
#else
  #define configPRIO_BITS  5
#endif

#define configLIBRARY_LOWEST_INTERRUPT_PRIORITY	 0x1f
#define configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY 5

/* The lowest priority. */
#define configKERNEL_INTERRUPT_PRIORITY ( 
configLIBRARY_LOWEST_INTERRUPT_PRIORITY << (8 - configPRIO_BITS) )
#define configMAX_SYSCALL_INTERRUPT_PRIORITY ( 
configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY << (8 - configPRIO_BITS) )

See RTOS for ARM Cortex-M.

Yes it must be lower than configMAX_SYSCALL_INTERRUPT_PRIORITY - which
means a higher numerical value. See the link above.

NVIC_SetPriority(irq, (1 << (__NVIC_PRIO_BITS - 1)) + priority);

Why not just

NVIC_SetPriority(irq, configMAX_LIBRARY_INTERRUPT_PRIORITY + whatever );

paulromero wrote on Friday, July 29, 2016:

Please clarify the recommendation about NVIC_SetPriority(). Do you
really mean to use configMAX_LIBRARY_INTERRUPT_PRIORITY = 5 or
configMAX_SYSCALL_INTERRUPT_PRIORITY = 40 ?

rtel wrote on Saturday, July 30, 2016:

I mean you really have to set the parameter to whatever the processor
actually needs - but you will find, as others have, that different
documents and source files say different things about what the processor
actually needs, so you will first have to work that out (which can be
done by experimentation - you can write 0xff into one of the priority
registers, then read the value back, and see how many of the bits
actually got set).