Implementing prvEMACDeferredInterruptHandlerTask

orifai01 wrote on Tuesday, January 03, 2017:

Hello, I’m trying to pass received data to the TCP/IP in a deferred interrupt handler task.
I’ve implemented an ISR called xEthernetHandler as described in xTimerPendFunctionCallFromISR web page:

void xEthernetHandler( void )
{
BaseType_t xHigherPriorityTaskWoken = pdFALSE;

NVIC_ClearPendingIRQ(ETHERNET_IRQn);

if(smsc9220_RxStatusFifoLevelIrq())
{
	xTimerPendFunctionCallFromISR( prvEMACDeferredInterruptHandlerTask,
								   NULL,
								   1,
								   &xHigherPriorityTaskWoken );

	portYIELD_FROM_ISR( xHigherPriorityTaskWoken );
}

}

void prvEMACDeferredInterruptHandlerTask( void * pvParameter1, uint32_t ulParameter2 )
{
NetworkBufferDescriptor_t *pxBufferDescriptor;
size_t xBytesReceived;
unsigned int index = 0;

/* Used to indicate that xSendEventStructToIPTask() is being called because
of an Ethernet receive event. */
IPStackEvent_t xRxEvent;

for( ;; )
{
	/* Wait for the Ethernet MAC interrupt to indicate that another packet
	has been received.  The task notification is used in a similar way to a
	counting semaphore to count Rx events, but is a lot more efficient than
	a semaphore. */
	ulTaskNotifyTake( pdFALSE, portMAX_DELAY );

    /* See how much data was received.  Here it is assumed ReceiveSize() is
    a peripheral driver function that returns the number of bytes in the
    received Ethernet frame. */
    xBytesReceived = smsc9220_recv_size();

    if( xBytesReceived > 0 )
    {
        /* Allocate a network buffer descriptor that points to a buffer
        large enough to hold the received frame.  As this is the simple
        rather than efficient example the received data will just be copied
        into this buffer. */
        pxBufferDescriptor = pxGetNetworkBufferWithDescriptor( xBytesReceived, 0 );

        if( pxBufferDescriptor != NULL )
        {
            if(smsc9220_recv_packet((unsigned int *)pxBufferDescriptor->pucEthernetBuffer, &index))
            {
                printf("Packet receive failed.\n");
                vReleaseNetworkBufferAndDescriptor( pxBufferDescriptor );
            }
            else
            {
                pxBufferDescriptor->xDataLength = xBytesReceived;

                /* See if the data contained in the received Ethernet frame needs
                to be processed.  NOTE! It is preferable to do this in
                the interrupt service routine itself, which would remove the need
                to unblock this task for packets that don't need processing. */
                if( eConsiderFrameForProcessing( pxBufferDescriptor->pucEthernetBuffer )
                                                                      == eProcessBuffer )
                {
                    /* The event about to be sent to the TCP/IP is an Rx event. */
                    xRxEvent.eEventType = eNetworkRxEvent;

                    /* pvData is used to point to the network buffer descriptor that
                    now references the received data. */
                    xRxEvent.pvData = ( void * ) pxBufferDescriptor;

                    /* Send the data to the TCP/IP stack. */
                    if( xSendEventStructToIPTask( &xRxEvent, 0 ) == pdFALSE )
                    {
                        /* The buffer could not be sent to the IP task so the buffer
                        must be released. */
                        vReleaseNetworkBufferAndDescriptor( pxBufferDescriptor );

                        /* Make a call to the standard trace macro to log the
                        occurrence. */
                        iptraceETHERNET_RX_EVENT_LOST();
                    }
                    else
                    {
                        /* The message was successfully sent to the TCP/IP stack.
                        Call the standard trace macro to log the occurrence. */
                        iptraceNETWORK_INTERFACE_RECEIVE();
                    }
                }
                else
                {
                    /* The Ethernet frame can be dropped, but the Ethernet buffer
                    must be released. */
                    vReleaseNetworkBufferAndDescriptor( pxBufferDescriptor );
                }
            }
        }
        else
        {
            iptraceETHERNET_RX_EVENT_LOST();
        }
    }
}

}

I’m using BufferAllocation_2.c. ipconfigIP_TASK_PRIORITY is set to (configMAX_PRIORITIES - 1).

Two questions:

  1. I don’t understand why ulTaskNotifyTake() is necessary, as we already got an Ethernet MAC interrupt (that’s the reason we entered handllerTask function).
    2.Althugh xTimerPendFunctionCallFromISR changes xHigherPriorityTaskWoken to be pdTRUE, prvEMACDeferredInterruptHandlerTask is not being called and all task seem to be stuck.

Thank you for your help

heinbali01 wrote on Tuesday, January 03, 2017:

The library FreeRTOS+TCP does not make use of FreeRTOS’ timer functions.

In all published versions of NetworkInterface.c, the TaskNotify mechanism is used to wake-up the prvEMACDeferredInterruptHandlerTask.

void xEthernetHandler( void )
{
BaseType_t xHigherPriorityTaskWoken = pdFALSE;

    NVIC_ClearPendingIRQ(ETHERNET_IRQn);

    if(smsc9220_RxStatusFifoLevelIrq())
    {
        TaskNotifyGiveFromISR( xDeferredInterruptTaskHandle, ( BaseType_t * ) &xHigherPriorityTaskWoken );
        portYIELD_FROM_ISR( xHigherPriorityTaskWoken );
    }
}
  1. I don’t understand why ulTaskNotifyTake() is necessary

The function will go sleeping until an interrupt has occurred, which is soon after TaskNotifyGiveFromISR(). Note the for(;;) loop.

Normally I would let it sleep for a limited amount of time: the PHY’s Link Status (LS) must be checked. When the LS goes low, transmission of packets is impossible and should be inhibited.

2.Although xTimerPendFunctionCallFromISR changes xHigherPriorityTaskWoken
to be pdTRUE, prvEMACDeferredInterruptHandlerTask is not being called and
all task seem to be stuck.

I’m not sure what the reason is for that, but I would use the ‘lighter’ method of TaskNotify.

Regards.

orifai01 wrote on Tuesday, January 03, 2017:

Hi Hein,
Thank you for your answer.

  1. But the Ethernet interrupt has already occurred. That’s the reason you’ve entered xEthernetHandler. At that point I would like to read the received bytes but ulTaskNotifyTake() forces me to wait for additional interrupt. Shouldn’t it be at the end of the loop?

I have two additional questions:
2. Shall I disable & clean the interrupt at the begging of xEthernetHandler and enable it somewhere?
3. Is this task running from scheduler startup or only from the first time Ethernet interrupt occures? What happens after receiving the second interrupt?

Thank you,
Orit

heinbali01 wrote on Tuesday, January 03, 2017:

Hi Orit,

But the Ethernet interrupt has already occurred.
That’s the reason you’ve entered xEthernetHandler.
At that point I would like to read the received bytes
but ulTaskNotifyTake() forces me to wait for additional
interrupt. Shouldn’t it be at the end of the loop?

It doesn’t matter if the ulTaskNotifyTake() is placed at the beginning or at the end. The function will be sleeping 99.9% of the time.

The following will be the order of events, it all happens within micro seconds:

● The interrupt handler xEthernetHandler() is called
● It will call TaskNotifyGiveFromISR() to make prvEMACDeferredInterruptHandlerTask() runnable again
● xHigherPriorityTaskWoken will be pdTRUE, and the YIELD will succeed
● The task running prvEMACDeferredInterruptHandlerTask() is unblocked immediately ( unless there are other runnable tasks with a higher priority ).

  1. Shall I disable & clean the interrupt at the beginning of xEthernetHandler and enable it somewhere?

I would think that it doesn’t make any difference.

  1. Is this task running from scheduler startup or only
    from the first time Ethernet interrupt occures?
    What happens after receiving the second interrupt?

Please have a good look at any of the other versions of NetworkInterface.c: you will see that prvEMACDeferredInterruptHandlerTask() will be started up once and that will will run for ever.

orifai01 wrote on Wednesday, January 04, 2017:

Hi Hein,
I changed the xTimerPendFunctionCallFromISR() to TaskNotifyGiveFromISR() in the irq and rewrote xNetworkInterfaceInitialise so now it creates the deferred interrupt handler task.
I’ve encountered a different problem. Context switch occurs but I keep receiving interrupts that preventing me from continuing.

TaskHandle_t xDeferredInterruptTaskHandle = NULL;

portBASE_TYPE xNetworkInterfaceInitialise(void)
{
portBASE_TYPE xreturn;
xreturn = xTaskCreate( xDeferredInterruptHandlerTask,
“RCV”,
configMINIMAL_STACK_SIZE * 3,
NULL,
mainDEFERRED_IRQ_TASK_PRIORITY,
&xDeferredInterruptTaskHandle );

Common_EnableIrq(ETHERNET_IRQn, 223);
return xreturn;

}

void xEthernetHandler( void )
{
BaseType_t xHigherPriorityTaskWoken = pdFALSE;

Common_DisableIrq(ETHERNET_IRQn);
NVIC_ClearPendingIRQ(ETHERNET_IRQn);

if(smsc9220_RxStatusFifoLevelIrq())
{
    vTaskNotifyGiveFromISR( xDeferredInterruptTaskHandle, ( BaseType_t * ) &xHigherPriorityTaskWoken );
    portYIELD_FROM_ISR( xHigherPriorityTaskWoken );
}

Common_EnableIrq(ETHERNET_IRQn, configMAX_SYSCALL_INTERRUPT_PRIORITY);

}

What shall be the ETHERNET_IRQn irq priority if configKERNEL_INTERRUPT_PRIORITY equals to 255 and configMAX_SYSCALL_INTERRUPT_PRIORITY equals to 191.

Thank you,
Orit

rtel wrote on Wednesday, January 04, 2017:

Interrupt priorities on Cortex-M cores is often the cause of some
confusion, so we have tried to explain it on the following page:
http://www.freertos.org/RTOS-Cortex-M3-M4.html

heinbali01 wrote on Thursday, January 05, 2017:

Hi Orit,

I would be curious to see smsc9220_RxStatusFifoLevelIrq().

In general: there are many possible triggers for ETHERNET_IRQn. Only the triggers that you need (RX, error?) should be enabled (the others are said to be “masked off”). Now if your interrupt-handler is called, the cause of the interrupt must be reset / cleared. How it is to be cleared depends on the peripheral.
In some cases it suffices to read the STATUS bits, sometimes a logical 0 or 1 must be written to the status-bit in order to reset it. Yet in other cases, nothing has to be done.

So if you observe that the interrupt keeps on being called, try to make clear what the reason is and how it can be cleared.

I do not think it is necessary to call Common_DisableIrq() and Common_EnableIrq() from within the interrupt handler.

orifai01 wrote on Thursday, January 05, 2017:

Hi Hein,
The Trigger I need is “RX FIFO Status Interrupt Level”. smsc9220_RxStatusFifoLevelIrq() simply checks the “RX FIFO Status Interrupt Level” bit in interrupt status register:

int smsc9220_RxStatusFifoLevelIrq(void)
{
return CHECK_BIT(SMSC9220->INT_STS, 3);
}

I would like the task function to read received bytes everytime interrupt occurs. I do not want the interrupt to occur during the reading process so I disable it, and enable it after reading a packet.
The problem is that the interrupt keeps on being called because I receive a lot of packets, and no other task can run. Is it better to implement this listener as a simple reading function (without a loop) and call xTimerPendFunctionCallFromISR() instead of a task?

heinbali01 wrote on Friday, January 06, 2017:

Hi Orit,

int smsc9220_RxStatusFifoLevelIrq(void)
{
/* HT : if CHECK_BIT() is only testing the value of a bit,
  this action will probably not reset the cause of the interrupt.
  It is documented as a R/WC bit: Read/Write Clear:
  A register bit with this attribute can be read and written.
  However, a write of a 1 clears (sets to 0) the corresponding
  bit and a write of a 0 has no effect
*/
	return CHECK_BIT(SMSC9220->INT_STS, 3);
}

I would like the task function to read received bytes every time
interrupt occurs. I do not want the interrupt to occur during the
reading process so I disable it, and enable it after reading a packet.

You better leave the interrupt enabled all the time, no problem. All it does is make your task runnable. The next call to ulTaskNotifyTake() will succeed immediately without blocking.
If you disable the RX interrupt, you might miss data or receive them too late.

The problem is that the interrupt keeps on being called because I
receive a lot of packets, and no other task can run.

I dare to doubt this theory :slight_smile:

Please check which interrupt bits are enabled in INT_EN and make sure that during the interrupt, the corresponding bits in INT_STS will be cleared.
That will stop the heavy load of interrupts.

If you still see too many RX data, make sure that the MAC filtering is OK: do not use promiscuous mode but set certain MAC addresses that are used by your device.

Is it better to implement this listener as a simple reading function
(without a loop) and call xTimerPendFunctionCallFromISR() instead of a task?

I don’t think so.

The proposal is as follows:

    for( ;; )
    {
        ulTaskNotifyTake( pdFALSE, ulMaxBlockTime );
        for( ;; )
        {
            xBytesReceived = smsc9220_recv_size();
           if( xBytesReceived <= 0 )
           {
               break;
           }
           /* send the received packet to the IP-task. */
        }
    }

The ETH interrupt remains active (enabled) all the time. If an interrupt occurs while reading RX packets, that is no problem. Only vTaskNotifyGiveFromISR() will be called, causing the next ulTaskNotifyTake() to “fall through”.

Please have a look of one of the other ports, such as:

FreeRTOS-Plus-TCP/portable/NetworkInterface/ATSAM4E/NetworkInterface.c

Its interrupt handlers will also set a (volatile) bit to indicate what type of interrupt has occurred. This is tested later in the task.

orifai01 wrote on Sunday, January 08, 2017:

First of all, thank you very much for your time and efforts!

  1. I’ve check INT_EN. only RX Fifo level interrupt is enabled (INT_EN = 0b1000).
  2. I tried to clean the RX interrupt by setting the third bit in INT_STS. For some reason it remains 1. I even tried to clear all interrupts but it didn’t help. What could be the reason for that?
  3. In order to set the perfect filtering mode, the following bits in MAC_CR were cleared at smsc initialization: MCPAS (19), PRMS (18), INVFILT (17), HO (15), HPFILT (13). MAC_CR = 0x1000000C.
  4. In two cases I stop receiving too many RX data: When setting BCAST in MAC_CR or when setting RX level to FF (i.e. setting FIFO_INT to 0xFFFFFFFF).
  5. I’ve checked MAC_ADDRH and MAC_ADDRL and they seem to be correct.
    If you have additional ideas of what could be wrong, I’ll be very glad to hear.

heinbali01 wrote on Sunday, January 08, 2017:

Hi Orit,

I’ve check INT_EN. only RX Fifo level interrupt is enabled (INT_EN = 0x1000).

Setting 0x1000 would set bit 12, which is marked as reserved and RO. Did you mean INT_EN = b'00001000' or 0x08 ?

I tried to clean the RX interrupt by setting the third bit in INT_STS.
For some reason it remains 1. I even tried to clear all interrupts but
it didn’t help. What could be the reason for that?

That is unexpected.
What happens if within the interrupt you clear INT_EN bit 3, and re-enable the same bit from within the task, after emptying the RX FIFO?

In order to set the perfect filtering mode, the following bits in MAC_CR were
cleared at smsc initialisation: MCPAS (19), PRMS (18), INVFILT (17), HO (15),
HPFILT (13). MAC_CR = 0x10 00 00 0C.

Did you set bit 28 your self? It is described as reserved.

I would set bit 23: “Disable Receive Own (RCVOWN)”, if not you will receive your own packets as well.

Later on, you might want to set Pass All Multicast (MCPAS), in case you want to enable LLMNR protocol.

In two cases I stop receiving too many RX data: When setting BCAST
in MAC_CR or when setting RX level to FF (i.e. setting FIFO_INT to 0xFFFFFFFF).

With 0xFFFFFFFF you would be writing 1’s to reserved bits, which is normally ‘not done’.
Wouldn’t you set it to e.g. 0x48000030 ?
‘0x30’ or 48 bytes is assumed to be a minimum frame length.

What I can not find is: how do you know when a received frame is complete?

If you have additional ideas of what could be wrong, I’ll be very glad to hear.

BCAST disables broadcast. Can you check the following: run WireShark on your laptop, connected to the same LAN, and see if there is an abundant amount of broadcast messages. These messages have the MAC address ff:ff:ff:ff:ff:ff as a target.
UDP broadcast messages can be quite disturbing for small embedded applications. Solution: filter them as soon as possible, preferably in the ‘deferred interrupt handler task’. The function xPortHasUDPSocket() tells whether a UDP port number is bound to a socket.

orifai01 wrote on Monday, January 09, 2017:

Hi Hein,

  1. Thanks. I ment 0b1000.
  2. Apparently this behavior caused by packets accumulation in TX FIFO. I let it run + changed FIFO_INT to 0x4800000C ( 12 dwords, 48 bytes, as the length is specify in dwords).
  3. I’ve changed MAC_CR to 0x90000C (disable receive own, disable heartbeat, enable xmit and enable receiver) and I’ve added xPortHasUDPSocket() to task function as you advised. Thank you very much!

As you mentioned, interrupt shall occur when a full packet is received. I thought maybe “Magic Packet Detection” would fit but it detects reception of a specific pattern. I continue to investigate…

orifai01 wrote on Tuesday, January 10, 2017:

I found the solution. RX Status level represents the number of words in RX status (not in RX data), so it is actually represents the number of received packets. Changed RX Status level to 0.