Issues when running USB with interrupts

Hello,
I’m having some troubles that I’m failing to debug. I’m using libopencm3 on a STM32F103 CPU. The goal is to run the USB from the interrupts. It works fine when I poll the USB in a task but when I enable interrupts I’m ending up in this routine

void vPortEnterCritical( void )
{
    portDISABLE_INTERRUPTS();
    uxCriticalNesting++;

    /* This is not the interrupt safe version of the enter critical function so
     * assert() if it is being called from an interrupt context.  Only API
     * functions that end in "FromISR" can be used in an interrupt.  Only assert if
     * the critical nesting count is 1 to protect against recursive calls if the
     * assert function also uses a critical section. */
    if( uxCriticalNesting == 1 )
    {
        configASSERT( ( portNVIC_INT_CTRL_REG & portVECTACTIVE_MASK ) == 0 );
    }
}

It gets there only if the usbd_poll, from libopencm3, calls user_callback_ctr. Otherwise it spins fine.

		if (dev->user_callback_ctr[ep][type]) {
			dev->user_callback_ctr[ep][type] (dev, ep);  // if I comment out this line, RTOS spins fine
		} else {
			USB_CLR_EP_RX_CTR(ep);
		}

The user_callback_ctr is defined as

static void cdcacm_data_rx_cb(usbd_device *usbd_dev, uint8_t ep)
{
	(void)ep;
	(void)usbd_dev;

	char buf[EP_PACKET_SIZE_BULK];
	// usbd_ep_read_packet runs USB_CLR_EP_RX_CTR, which clears the interrupt
	const int len = usbd_ep_read_packet(usbd_dev, EP_ADDR_BULK_OUT, buf, EP_PACKET_SIZE_BULK);

	(void)len;
	// This is basically an echo.
	if (len) {
		usbd_ep_write_packet(usbd_dev, EP_ADDR_BULK_IN, buf, len);
	}
}

Even if I comment out the actual code from this function, the code ends up in the vPortEnterCritical. I’m a bit puzzled what’s happening here. Oh and here is how I init the interrupts

	nvic_enable_irq(NVIC_USB_HP_CAN_TX_IRQ);
	nvic_enable_irq(NVIC_USB_LP_CAN_RX0_IRQ);

	nvic_set_priority(NVIC_USB_HP_CAN_TX_IRQ, configLIBRARY_LOWEST_INTERRUPT_PRIORITY);
	nvic_set_priority(NVIC_USB_LP_CAN_RX0_IRQ, configLIBRARY_LOWEST_INTERRUPT_PRIORITY);

First thing is when you get at that point, stop the processor with the debugger and look at the call stack. See what function used in an ISR is using a non-FromISR function that is ending up in a critical section.

Well the only thing the USB interrupt does is clearing some USB interrupt register bits and calling cdcacm_data_rx_cb - calling this function causes issues. I have pasted the function in the first post and it’s quite simple. That function doesn’t use any FreeRTOS functions.

Might it be that I should disable the interrupts when entering the USB interrupt? Or should FreeRTOS handle that by itself? Let’s say SysTick or any other FreeRTOS interrupts are called when the USB interrupts is being handled. I’ve set the lowest prio on the USB interrupt so everything should be fine there… right?

As I asked, what called the function that call vPortEnterCritical for it to hit the assert?

Note, that function is calling usbd_ep_read_packet (and write_packet) could those be calling something that makes the call?

Yes, sorry. I actually thought it’s interrupt or some fault that gets there and I wouldn’t get that information. I tried checking the call stack when hitting that place.

#0  vPortEnterCritical () at ../FreeRTOS/portable/GCC/ARM_CM3/port.c:383
#1  0x080031ea in xTaskResumeAll () at ../FreeRTOS/tasks.c:2188
#2  0x08003348 in xTaskResumeAll () at ../FreeRTOS/tasks.c:2181
#3  0x08002288 in xEventGroupSetBits (uxBitsToSet=536872052, xEventGroup=0x20000468 <ucHeap+612>) at ../FreeRTOS/event_groups.c:623
#4  xEventGroupSetBits (xEventGroup=0x20000468 <ucHeap+612>, uxBitsToSet=uxBitsToSet@entry=1) at ../FreeRTOS/event_groups.c:533
#5  0x08001a42 in cdcacm_control_request (dev=<optimized out>, req=<optimized out>, buf=<optimized out>, len=0x20001240 <st_usbfs_dev+60>, complete=0x20001244 <st_usbfs_dev+64>)
    at main.c:267
#6  cdcacm_control_request (dev=<optimized out>, req=<optimized out>, buf=<optimized out>, len=0x20001240 <st_usbfs_dev+60>, complete=0x20001244 <st_usbfs_dev+64>) at main.c:230
#7  0x0800684c in usb_control_request_dispatch (usbd_dev=usbd_dev@entry=0x20001204 <st_usbfs_dev>, req=req@entry=0x20001234 <st_usbfs_dev+48>) at ../../usb/usb_control.c:155
#8  0x080069dc in _usbd_control_out (usbd_dev=0x20001204 <st_usbfs_dev>, ea=<optimized out>) at ../../usb/usb_control.c:263
#9  0x080075e6 in st_usbfs_poll (dev=0x20001204 <st_usbfs_dev>) at ../common/st_usbfs_core.c:265
#10 0x08001b32 in usb_lp_can_rx0_isr () at ../libopencm3/include/libopencm3/cm3/cortex.h:128
#11 <signal handler called>
#12 0x08001d68 in prvPortStartFirstTask () at ../FreeRTOS/portable/GCC/ARM_CM3/port.c:247
#13 0x08001fce in xPortStartScheduler () at ../FreeRTOS/portable/GCC/ARM_CM3/port.c:347
#14 0x00000000 in ?? ()

Looks like it’s actually cdcacm_control_request and failing on setting the EventGroup bits… I guess I should handle those with the FromISR variant of the function.

using the FromISR variants is critical to correct interrupt operation. Please let us know how that works out.

One big thing to double check is if that same function is used in non-ISR contexts, that can make things a bit more complicated.

Yes, it was basically the issue of me thinking that it errors out in a different interrupt handler - the one that didn’t call any FreeRTOS functions. It was failing on the control messages handler, which was called from an ISR and called the non ISR FreeRTOS function. Another mistake I did was assuming that I can’t just print the stacktrace from the gdb. Thanks to @richard-damon I did that, which showed me that I’m looking in the wrong direction. After that it was just obvious that it’s the lack of the ISR function variant.

Good lesson. Thank you for your help.

Good point. I’ll keep that in mind.