Skip to content
btsimonh edited this page Oct 28, 2022 · 2 revisions
Serial Interrupts

Some notes on serial interrupts on Beken 7231 T & N

The hardware TX fifo on these devices is 256 bytes deep, so writing bytes from firmware is cheap until the fifo is full.

From user code, you can use

uart_is_tx_fifo_full(UART_PORT)

to detect when the fifo is full.

There are also two TX interrupts which could be useful if implementing a low level protocol. The SDK has no examples of using these, but the below code snippets illustrate the use of one:

initialisation

To run interrupts on the uart, you must initialise. Beken have provided most of the required in the form of one of their device drivers.

Their driver creates a pair of software fifos (kfifo_) of 64 bytes for tx and 64 bytes for rx.

NOTE: for MOST applications you will NOT need to use interrupt controlled TX, because of the large h/w TX fifo.

TX

The below code uses the TX_FIFO_NEEDWR_CALLBACK to trigger filling of the HW fifo from the s/w fifo.

NOTE: for MOST applications you will NOT need to use interrupt controlled TX, because of the large h/w TX fifo which can be filled from user code...

RX

(untested by me)

Once initialised, the driver will be filling the s/w RX kfifo with data.

rx s/w fifo occupancy can be found using kfifo_data_size(uart[UART_PORT_INDEX].rx))

The kfifo can be peeked as well.

The driver can be used for these functions:

// get the rx available bytes in s/w fifo
uint32_t thing;
int len = ddev_control(uart_hdl, CMD_RX_COUNT, (void *)&thing);

// peek bytes iin the rx s/w fifo
uint8_t buff[16];
UART_PEEK_RX peek;
peek.sig = URX_PEEK_SIG;
peek.ptr = buff;
peek.len = 16;
uint32_t len = ddev_control(uart_hdl, CMD_RX_PEEK, (void *)&peek);

// read
uint32_t len = ddev_read(uart_hdl, char *user_buf, UINT32 count, UINT32 op_flag)

Code:

// this header gets us both uart_pub.h, AND the low level uart.h
#include "command_line.h"

extern UART_S uart[2];


#define UART_PORT UART2_PORT 
#define UART_DEV_NAME UART2_DEV_NAME
#define UART_PORT_INDEX 1 

// call whenever we add some data to the TX kfifo (s/w fifo) to start TX
// if not already running
void startISRTx(int index){
    // only if it is fifo empty, 
    // else interrupt will trigger.
    if (index == 0){
        if (UART1_TX_WRITE_READY){
            uart_set_tx_fifo_needwr_int(UART1_PORT, 1);
        }
    } else {
        if (UART2_TX_WRITE_READY){
            uart_set_tx_fifo_needwr_int(UART2_PORT, 1);
        }
    }
}

// read how much we can send
int getISRFifoSpace(){
    return kfifo_unused(uart[UART_PORT_INDEX].tx);
}

// send up to amount returned by getISRFifoSpace()
// and then trigger TX if needed.
void writeISRFifo(char *src, int count){
    kfifo_put(uart[UART_PORT_INDEX].tx, (UINT8 *)src, count);
    startISRTx(UART_PORT_INDEX);
}


// CALLED FROM ISR!!!
void interruptTxCallback(int uport, void *param){
    uint32_t p = (uint32_t)param;

    switch(p){
        case 0: //CMD_UART_SET_TX_CALLBACK
            // I don't think we need this.  this is TX complete? i.e. fifo empty.
            // this may be good for tight timeout protocols.
            break;
        case 1: {//CMD_UART_SET_TX_FIFO_NEEDWR_CALLBACK
            // assume this is when hardware fifo is past low water mark?.
            // so feed it more from kfifo...
            // note: the kfifo was created by the driver init.
            struct kfifo *fifo = uart[UART_PORT_INDEX].tx;
            tx_interrupt_count++;

            // while we can add data to the fifo...
            while (UART2_TX_WRITE_READY){
                // if any bytes
                if (fifo->out - fifo->in){
                    // read one
                    uint8_t c = *(fifo->buffer + (fifo->out & (fifo->size - 1)));
                    fifo->out++;
                    // write to uart fifo
                    UART_WRITE_BYTE(UART_PORT_INDEX,c);              
                    tx_interrupt_charcount++;
                } else {
                    // disable interrupt, as no more data
                    uart_set_tx_fifo_needwr_int(UART_PORT, 0);
                    break;
                }
            }
        } break;
        
    }
}


static DD_HANDLE uart_hdl;
void interruptinit(){
#ifdef PLATFORM_BEKEN
    UINT32 status;
    UINT32 ret;

    uart_config_t config;
    config.baud_rate = 115200;
    config.data_width = DATA_WIDTH_8BIT;
    config.parity = BK_PARITY_NO;
    config.stop_bits = BK_STOP_BITS_1;
    config.flow_control = FLOW_CTRL_DISABLED;
    config.flags = 0;

    uart_hdl = ddev_open(UART_DEV_NAME, &status, 0);
    ret = ddev_control(uart_hdl, CMD_UART_INIT, (void *)&config);

    struct uart_callback_des cb;
    cb.callback = interruptTxCallback;
    cb.param = (void *)0;
    ret = ddev_control(uart_hdl, CMD_UART_SET_TX_CALLBACK, (void *)&cb);

    cb.param = (void *)1;
    ret = ddev_control(uart_hdl, CMD_UART_SET_TX_FIFO_NEEDWR_CALLBACK, (void *)&cb);

    // enable write interrupt.  Note: this will immediately fire the interrupt;
    // so instead of doing it here, we do it when bytes we have bytes to send
    uint8_t set = 1;
    // we have no documentation about this interrupt - likely it fires when the H/W fifo reaches a low water mark.
    //ret = ddev_control(uart_hdl, CMD_SET_TX_FIFO_NEEDWR_INT, (void *)&set);

    interrupt_initialised = 1;
    //use_interrupts = 1;
#endif
}

Free Rtos - malloc and stack

malloc and friends

The memory allocation functions in freertos are in heap_4.c - pcPortMalloc, etc.

In the applicaiton.mk, the linker flags -Wl,-wrap,malloc and associated linker flags cause the linker itself to call __wrap_malloc wherever malloc is called, even from any libraries.

The __wrap_xxx functions for memory routines are in mem_arch.c

Note that realloc is implemented as malloc, copy, free. We think because the pvPortRealloc function does not free up memory if the size is reduced.

The heap area is the remaining ram after C memory has been given by the linker. It's defined as this memory range (~128kbytes):

    #define HEAP_START_ADDRESS    (void*)&_empty_ram
    #define HEAP_END_ADDRESS      (void*)(0x00400000 + 256 * 1024)

The heap itself is filled with malloc structures (one initially) like:

// from heap_4.c
typedef struct A_BLOCK_LINK
{
	struct A_BLOCK_LINK *pxNextFreeBlock;	/*<< The next free block in the list. */
	size_t xBlockSize;						/*<< The size of the free block. */
} BlockLink_t;

and the pvPortMalloc routines manipulate this area. Blocks are kept in a freelist. There is no used list.

However, as the area is 'filled' with blocks, the whole area can be scanned for consistency and statistics - see memtest.c in OBK for routines which can check the heap.

general use of -wrap

the linker option -wrap is a generally useful function. Once a function is wrapped, you can provide the new function as __wrap_fn_name, and the original function is available as __real_fn_name. An example of it's use is in memtest.c in OpenBeken, where some diagnostic wrapping of pvPortMalloc can be enabled though flags in the makefile, modifying the way malloc behaves to add guard bytes around allocs, and allow checking of caller.

freertos stacks & threads

The stacks for threads are allocated using pvPortMalloc directly at thread creation. So, stack overflow WILL corrupt the heap (malloc space).

FreeRtos tries to keep it's internal structures private. However, see memtest.c/.h where we have 'broken into' the freertos structures to provide some useful functionality.

NOTE: With different SDKs or FreeRtos changes - this copied structure definition may be invalid!!!!

e.g. using a line of assembler (see GETSTACK in memtest.h), we can extract the stack pointer, and by examining the current thread we can get the start of stack, and from this the size of the allocation, and so are able to know where we are in the stack (see stackCheck(percentMax) in memtest.c).

Freertos can (does in our case) check for stack overflow - but only on task switch, so it may not tell you every time, and won't avoid a crash. The Freertos stack check results in a serial log and hard stop. An uncaught stack overflow may or may not cause immediate death.

caller address

For deep debugging, in arm we can get the caller address from within a function. See GETCALLERADDR in memtest.c

e.g. when deeply diagnosing malloc, I modified __wrap_malloc to log and print the caller addr, which then could identify the function from the .map file.

So, combined with -wrap described above, this can be very useful in diagnosing a crash in a commonly used function to identify where it was called from, but unfortunately for malloc and friends, it requires a modification to the SDK.