Name

Device Drivers — Writing new CAN device drivers

Description

Adding CAN support for a new device to eCos involves a number of steps. First a new package for the driver must be created and added to ecos.db. This package must contain CDL, headers and sources for implementing the driver. If the driver can apply to devices on different platforms, then a further package that configures the generic device to each platform will also be needed.

Device Driver Data Structures

Each CAN device is represented by a cyg_can_device structure. Most of the fields of this structure are private to the CAN subsystem and it needs to be defined in such a way that it is included in a table of all the CAN devices. To make this simple for the driver writer a macro has been defined to create this structure for each channel.

CYG_CAN_DEVICE( tag, chan, name, priv );

The arguments to this macro are:

tag
This is a general name for the device driver as a whole, it should usually be the name of the device chip or interface type. Typical values might be sja1000 or flexcan. This tag is used to ensure that the data structures declared by this macro are unique to this driver.
chan
This distinguishes between separate channels supported by the driver. Typical values might be can0 or can1.
name
This is the name of the channel as used in the cyg_can_open() function. It is a string constant and is usually defined by the driver's CDL. Typical CAN channel names are "can0" and "can1". However this argument will usually be a CDL option such as CYGPKG_DEVS_CAN_CHANNEL0_NAME or CYGPKG_DEVS_CAN_CHANNEL1_NAME.
priv
This is a pointer to a private data structure that contains device specific information such as its base address and interrupt vector. The CAN subsystem does not interpret the contents of this in any way. Typical values for this might be cyg_can_flexcan_drv_0 or cyg_can_sja1000_drv[1].

The interface to each driver is via a table of function calls that is pointed to by the cyg_can_device structure. This structure has the following definition:

struct cyg_can_device_calls {
       int (*init)  (cyg_can_device *dev);
       int (*open)  (cyg_can_device *dev);
       int (*close) (cyg_can_device *dev);
       int (*send)  (cyg_can_device *dev, cyg_can_msg *msg);
       int (*poll)  (cyg_can_device *dev);
       int (*filter)(cyg_can_device *dev, cyg_bool ide, cyg_uint32 match, cyg_uint32 mask);
       int (*baud)  (cyg_can_device *dev, cyg_uint32 baud);
       int (*filter_ext)(cyg_can_device *dev, cyg_can_filter *filters, int len);
};
init()
This is called to initialize the channel when cyg_can_init() is called. It should locate the channel and initialize it ready for communication. It should also install any interrupts and initialize the fields of the private data structure.
open()
This is called if the name of this channel matches the device name passed to cyg_can_open(). There is no requirement for the driver to do anything here, but possible things it might do is to allocate per-client resources in the hardware, or just keep count of the number of users.
close()
This is called when cyg_can_close() is called. As with the open() function, there is no required behaviour here, but if open() allocated resources, then this is where they should be released.
send()

This function is called to transmit a message by cyg_can_send() and is passed a pointer to a message buffer. This function should return one of several return codes depending on the state of the channel:

CYG_CAN_BUSY
The channel is busy and cannot transmit the message at this point. The CAN subsystem will add the message to its pending queue until the channel is available, at which point the buffer will be passed back to the driver by the cyg_can_tx_done() function.
CYG_CAN_WAIT
The channel has started the transmission of the message, but it is not yet complete. The CAN subsystem will cause the sending thread to wait until the driver calls cyg_can_tx_done(). This is usually used by interrupt driven drivers to make the sender wait for the transmit completion interrupt.
CYG_CAN_NOERROR
The channel has transmitted the message immediately, and has also called cyg_can_tx_done() to complete the process. This is mainly used by polled drivers, which don't use interrupts.
CYG_CAN_*
Any other error code indicates a hardware error of some sort and will be passed back to the caller by the CAN subsystem.
poll()
This is usually called from the driver's DSR routine to handle any events that have occurred on the channel. It may also be called from the CAN subsystem at various times, and may be called from application code calling cyg_can_poll().
filter()
This function is called to set the hardware ID filter. Not all hardware supports a filter mechanism that matches the model implemented by the CAN subsystem. This function should return CYG_CAN_NOERROR whether it can set the hardware filter or not. The CAN subsystem will always apply the filter in software to all received messages, so setting the hardware filter is an optimization to reduce the number of messages received. There is no function to return the filter since the CAN subsystem records the filter itself.
baud()
This function is called to set the network baud rate. The driver is at liberty to support only a subset of the possible baud rates, and return CYG_CAN_INVALID for those it cannot. It is also possible for certain baud rates not be supported for certain system clock speeds due to limitations in the clock divider. It may not be possible for the driver to detect this.
filter_ext()
This function is called to set the extended hardware ID filters. Not all hardware supports the extended filter mechanism and this function pointer may be NULL, in which case the CAN subsystem will apply the filters in software. If not NULL then the driver should return CYG_CAN_NOERROR if all the extended filters can be handled, and CYG_CAN_INVALID if not. Partial implementation of the filters is not supported; either all the filters are installed into the hardware or none are.

The CAN subsystem defines a macro to create this function table:

CYG_CAN_DEVICE_CALLS( tag );

That macro creates a function table with filter_ext() set to NULL. If extended filters are supported, the driver should use the following macro:

CYG_CAN_DEVICE_CALLS_EXT( tag );

The tag argument should match the tag argument of the CYG_CAN_DEVICE() macro. This macro does two things. First it declares static function prototypes for all the driver functions of the form cyg_can_<tag>_<function> (e.g. cyg_can_sja1000_init() or cyg_can_flexcan_send()). Second, it defines a function table called cyg_can_<tag>_calls. The CYG_CAN_DEVICE() macro assumes that a function table of this name is defined.

Device Driver API Calls

In addition to the standard device driver API calls defined by the kernel, there are a number of additional CAN specific API calls that a device driver must use to interact with the CAN subsystem. These functions may only be called from thread level with the DSR lock claimed, or from a DSR. They cannot be called from an ISR.

cyg_can_msg *cyg_can_tx_done( cyg_can_device *can_dev, cyg_can_msg *msg );

This must be called when a transmission is completed. For polled drivers it should be called from the send() function while for interrupt driven drivers it should be called from the poll() routine invoked from the driver's DSR. The function is called with a pointer to the transmitted message buffer and following this call the message buffer will become the property of the CAN subsystem and may be returned to the free pool. The return value from this function will a pointer be another message buffer to transmit, or NULL. The driver should use the resources recently freed by the completion of the previous transmission to start transmission of this message.

cyg_can_msg *cyg_can_rx_buffer( void );

This is called by the driver to acquire a message buffer in to which a message will be received. The normal approach is to allocate a buffer during driver initialization and to keep at least one pending buffer available at all times. New buffers will usually be passed back to the running driver by the cyg_can_rx_done() function.

cyg_can_msg *cyg_can_rx_done( cyg_can_device *can_dev, cyg_can_msg *msg );

This must be called when a driver has a completed message buffer to return to the user. This buffer may either contain a received message, or may be reporting a channel event. This call is made with a pointer to the message buffer to be returned. Following this call the message buffer becomes the property of the CAN subsystem. The return value of this function will be a pointer to a message buffer to replace the one just passed back. Thus with one call the driver both gives up an old buffer and gets a new one to use in its place.

It is possible for cyg_can_rx_done() to return a NULL pointer if there are currently no more buffers available. The driver must therefore be able to handle this. The usual approach is to check, just before it is needed, for a current pending buffer. If no buffer is present then call cyg_can_rx_buffer() and if this returns NULL then take action to, for example, throw the message away.

typedef struct
{
    cyg_uint8           tseg1_min;      // Time segment 1 = prop+phase1
    cyg_uint8           tseg1_max;
    cyg_uint8           tseg2_min;      // Time segment 2 = phase2
    cyg_uint8           tseg2_max;
    cyg_uint16          divider_min;    // Clock quantum divider
    cyg_uint16          divider_max;
} cyg_can_bitrate_param;

typedef struct
{
    // Input parameters
    cyg_uint32          clock;          // System input clock in Hz

    // Input/Output parameters
    cyg_uint32          bitrate;        // Target bit rate in Hz
                                        // Must be set on input
                                        // Updated with actual rate set

    cyg_uint16          sample;         // Sample point in tenths of a percent
                                        // If zero, CIA recommended value used
                                        // Updated with actual sample point

    // Output calculated values
    cyg_uint8           prop;           // Propogation segment in quanta
    cyg_uint8           phase1;         // Phase segment 1 in quanta
    cyg_uint8           phase2;         // Phase segment 2 in quanta
    cyg_uint16          divider;        // Clock divider

} cyg_can_bitrate;

int cyg_can_calculate_bitrate(  const cyg_can_bitrate_param *param,
                                cyg_can_bitrate             *bitrate );

The function cyg_can_calculate_bitrate may be called from the driver to calculate the timing values for a given bit rate.

The param argument contains details of the bit timing hardware in the device, mainly derived from the field widths in the timing register(s). These details comprise minimum and maximum values for tseg1 (propogation segment plus phase segment one), tseg2 (phase segment two) and the quantum clock divider.

The bitrate argument must have the input clock and target bitrate set. The sample point must either be set, or zeroed for a CIA recommended value to be chosen. On return the fields prop, phase1 and phase2 will be set to the quantum counts calculated for each segment. The divider field will set to the calculated divider. The bitrate and sample fields will be updated with the actual bit rate and sample point.

This function will either return CYG_CAN_NOERROR if a valid set of timing values have been calculated, or CYG_CAN_INVALID if no values could be found. The routine will attempt to find values that give the closest match to the bitrate and sample point requested. If an exact match is found for both, that setting is returned. If no exact match is found, success is only reported if the calculated bitrate is within 5% of the requested rate.

Most drivers should be able to use the values returned by this routine directly. Most CAN devices have one or two registers that contain fields that are directly analogous to these values. All that is needed is for them to be shifted in to position. Quanta are divided more or less equally between prop and phase1. However, some hardware may combine these values into a single field or have different sized fields for each. In these cases then the driver may need to add them together, or move some quanta from one to another to match the hardware.

Configuration

The only direct configuration requirement on device drivers is that for each channel supported, the driver should have an "implements CYGINT_IO_CAN_DRIVER" statement to ensure that the correct number of message buffers are available. The name of the channels should also be defined in the CDL. A minimal CDL file for the XYZZY driver would be as follows:

cdl_package CYGPKG_DEVS_CAN_XYZZY {
    display       "XYZZY CAN driver"
    description   "XYZZY CAN driver."

    parent        CYGPKG_IO_CAN
    active_if     CYGPKG_IO_CAN

    include_dir   cyg/devs/can

    compile -library=libextras.a xyzzy.c

    cdl_component CYGPKG_DEVS_CAN_CHANNEL0 {
        display   "CAN channel 0 configuration"
        default_value 1
        implements    CYGINT_IO_CAN_DRIVER

       cdl_option CYGPKG_DEVS_CAN_CHANNEL0_NAME {
            display       "CAN channel 0 name"
            flavor        data
            default_value { "\"can0\"" }
            description   "Name of CAN channel 0"
        }
    }

    cdl_component CYGPKG_DEVS_CAN_CHANNEL1 {
        display   "CAN channel 1 configuration"
        default_value 1
        implements    CYGINT_IO_CAN_DRIVER

        cdl_option CYGPKG_DEVS_CAN_CHANNEL1_NAME {
            display       "CAN channel 1 name"
            flavor        data
            default_value { "\"can1\"" }
            description   "Name of CAN channel 1"
        }

    }

    # Further entries for extra channels would go here

}

If the driver is multi-platform, then the channel configurations should go into the second platform specific package which may also need to define suitable configuration options to customize the generic driver.

Driver Template

The following example show the general structure of a CAN device driver for a fictional XYZZY device.

The first thing we need to do is to define the data structures that interface the device to the CAN subsystem:

#include <pkgconf/hal.h>
#include <pkgconf/io_can.h>
#include <pkgconf/devs_can_xyzzy.h>

#include <cyg/io/can_dev.h>

//=============================================================================
// Define private data structure. At the very least this needs to
// contain the base address of the device and the interrupt vector.
// If this is an interrupt driven device, then it will also need to
// contain the data structures to manage the interrupt.

struct cyg_can_xyzzy_priv
{
    cyg_uint32                  devno;          // device number
    CYG_ADDRESS                 base;           // base address
    cyg_vector_t                vector;         // vector number

    cyg_can_msg                 *tx_msg;        // current tx message buffer
    cyg_can_msg                 *rx_msg;        // pending rx message buffer

    cyg_handle_t                interrupt_handle;
    cyg_interrupt               interrupt_object;

    // Further device fields here
};

//=============================================================================
// Define device function call table. This should be done before the
// CYG_CAN_DEVICE() macro is called.

CYG_CAN_DEVICE_CALLS( xyzzy );

//=============================================================================
// Define driver-private structures for each of the channels. For this
// example we define just two.

#ifdef CYGPKG_DEVS_CAN_CHANNEL0
struct cyg_can_xyzzy_priv cyg_can_xyzzy_drv_0 =
    { 0, CYGARC_HAL_XYZZY_BASE_CAN_0, CYGNUM_HAL_INTERRUPT_CAN_0 };
#endif
#ifdef CYGPKG_DEVS_CAN_CHANNEL1
struct cyg_can_xyzzy_priv cyg_can_xyzzy_drv_1 =
    { 1, CYGARC_HAL_XYZZY_BASE_CAN_1, CYGNUM_HAL_INTERRUPT_CAN_1 };
#endif


//=============================================================================
// Define CAN device table entries.

#ifdef CYGPKG_DEVS_CAN_CHANNEL0
CYG_CAN_DEVICE( xyzzy, can0, CYGPKG_DEVS_CAN_CHANNEL0_NAME, cyg_can_xyzzy_drv_0 );
#endif
#ifdef CYGPKG_DEVS_CAN_CHANNEL1
CYG_CAN_DEVICE( xyzzy, can1, CYGPKG_DEVS_CAN_CHANNEL1_NAME, cyg_can_xyzzy_drv_1 );
#endif

The first thing that needs writing is the initialization routine:

static int cyg_can_xyzzy_init(cyg_can_device *dev)
{
    int result = CYG_CAN_NOERROR;
    struct cyg_can_xyzzy_priv *priv = (struct cyg_can_xyzzy_priv *)dev->private;


    // Locate, validate and initialize the channel hardware. This
    // may include setting up the acceptance filter to accept all IDs
    // and setting the baud rate to a default (100kHz say).

    // Install interrupt handlers

    cyg_drv_interrupt_create( priv->vector,
                              0,
                              (CYG_ADDRWORD)dev,
                              cyg_can_xyzzy_isr,
                              cyg_can_xyzzy_dsr,
                              &priv->interrupt_handle,
                              &priv->interrupt_object );
    cyg_drv_interrupt_attach( priv->interrupt_handle );
    cyg_drv_interrupt_unmask( priv->vector );

    // Perform any final initialization, for example clearing and then
    // enabling interrupts in the channel.

    // Allocate a pending buffer for message receive.
    priv->rx_msg = cyg_can_rx_buffer();

    return result;
}

The open and close routines come next. Most drivers don't need to do much here so these examples are the minimum necessary:

static int cyg_can_xyzzy_open(cyg_can_device *dev)
{
    return CYG_CAN_NOERROR;
}

static int cyg_can_xyzzy_close(cyg_can_device *dev)
{
    return CYG_CAN_NOERROR;
}

The send routine is responsible for actually transmitting a message:

static int cyg_can_xyzzy_send(cyg_can_device *dev, cyg_can_msg *msg)
{
    struct cyg_can_xyzzy_priv *priv = (struct cyg_can_xyzzy_priv *)dev->private;

    // If there is still a current tx message or the transmit hardware
    // is still busy, return busy so that the upper levels will
    // queue this request.
    if( priv->tx_msg != NULL || xyzzy_tx_busy( priv ) )
        return CYG_CAN_BUSY;

    // Record current transmit packet
    priv->tx_msg = msg;

    // Write the message header and ID to be sent into the channel
    // transmit buffer, ensuring that the length and the IDE bit is
    // set correctly, and the ID is correct.

    // If the RTR flag is not set, install the data in the transmit
    // buffer. If the RTR flag is set, do not install the data and set
    // the RTR bit in the frame info.

    // Start the transmission.

    // Return CYG_CAN_WAIT to cause the sending thread to wait for completion.

    return CYG_CAN_WAIT;
}

The following routine is internal to the driver, it is called from the poll() routine to actually receive a message into a buffer:

static int cyg_can_xyzzy_recv(cyg_can_device *dev, cyg_can_msg *msg)
{
    int result = CYG_CAN_NOERROR;
    struct cyg_can_xyzzy_priv *priv = (struct cyg_can_xyzzy_priv *)dev->private;
    cyg_ucount8 ide, rtr, len;
    cyg_uint32 id = 0;

    // Get the message frame header and decode it into some locals:
    // ide, rtr, len and id.

    // If we have a message buffer, move the message out into it.

    if( msg != NULL )
    {
        msg->ide = ide;
        msg->rtr = rtr;
        msg->len = len;
        msg->id  = id;

        if( !rtr )
        {
            // Copy data from receive frame into message buffer.
        }
    }
    else
    {
            // Do whatever is needed to throw the message away since
            // there is no buffer available.
    }

    // Do whatever is needed to release the receive buffer and ready
    // it for a new message and/or cancel the interrupt.

    return result;
}

The poll() handles most of the asynchronous events:

static int cyg_can_xyzzy_poll(cyg_can_device *dev)
{
    int result = CYG_CAN_NOERROR;
    struct cyg_can_xyzzy_priv *priv = (struct cyg_can_xyzzy_priv *)dev->private;

    // If there is a pending transmission and the hardware channel
    // indicates that it is finished, then call cyg_can_tx_done().

    if( priv->tx_msg != NULL && xyzzy_tx_done( priv ) )
    {
        cyg_can_msg *msg = priv->tx_msg;

        priv->tx_msg = NULL;
        msg = cyg_can_tx_done( dev, msg );

        // If we have been passed a new message to transmit, send it.
        if( msg != NULL )
            cyg_can_xyzzy_send( dev, msg );
    }

    // While there are messages available in the receive buffer or
    // FIFO, pull them out and pass them back to the CAN subsystem.

    while( xyzzy_rx_done( priv ) )
    {
        // If there is no current rx buffer, try to allocate one here.
        if( priv->rx_msg == NULL )
            priv->rx_msg = cyg_can_rx_buffer();

        // Either receive the message, or clear the channel.
        cyg_can_xyzzy_recv( dev, priv->rx_msg );

        // If we have a buffer, pass it back.
        if( priv->rx_msg != NULL )
            priv->rx_msg = cyg_can_rx_done( dev, priv->rx_msg );
    }

    // See if any other CAN channel events have occurred.

    if( xyzzy_event( priv ) )
    {
        // Decode the event and set result to an appropriate error
        // code.

        // Return a message buffer recording this event. As above, we
        // may need to allocate a fresh buffer if none is available.

        if( result != CYG_CAN_NOERROR )
        {
            if( priv->rx_msg == NULL )
                priv->rx_msg = cyg_can_rx_buffer();

            if( priv->rx_msg != NULL )
            {
                priv->rx_msg->result = result;
                priv->rx_msg = cyg_can_rx_done( dev, priv->rx_msg );
            }
            result = CYG_CAN_NOERROR;
        }
    }

    return result;
}

The simplest form for the ISR is for it to just mask the channel's interrupt vector and cause the DSR to run. The DSR can then simply call cyg_can_xyzzy_poll() to handle the channel events. Alternatively, the ISR could handle the hardware, but the DSR still needs to be run to call cyg_can_tx_done(), cyg_can_rx_done() and cyg_can_rx_buffer().

static cyg_uint32 cyg_can_xyzzy_isr(cyg_vector_t    vector,
                                    cyg_addrword_t  data)
{
    cyg_can_device *dev = (cyg_can_device *)data;

    // Block interrupts from this device until the DSR is run
    cyg_drv_interrupt_mask( vector );

    // Ack the interrupt in the system interrupt controller
    cyg_drv_interrupt_acknowledge( vector );

    // Pass handling on to DSR
    return (CYG_ISR_HANDLED|CYG_ISR_CALL_DSR);
}

static void cyg_can_xyzzy_dsr(cyg_vector_t    vector,
                              cyg_ucount32    count,
                              cyg_addrword_t  data)
{
    cyg_can_device *dev = (cyg_can_device *)data;

    // Poll hardware for pending events
    cyg_can_xyzzy_poll( dev );

    // Re-allow device interrupts
    cyg_drv_interrupt_unmask( vector );
}

Finally, the filter and baud rate functions are very simple:

static int cyg_can_xyzzy_filter(cyg_can_device  *dev,
                                cyg_bool        ide,
                                cyg_uint32      match,
                                cyg_uint32 mask)
{
    int result = CYG_CAN_NOERROR;
    struct cyg_can_xyzzy_priv *priv = (struct cyg_can_xyzzy_priv *)dev->private;

    // Set the hardware filter to match the parameters. If the
    // hardware filter cannot be used, return CYG_CAN_NOERROR anyway,
    // since filtering will also be done in the CAN subsystem.

    return result;
}

static int cyg_can_xyzzy_filter_ext(cyg_can_device  *dev,
                                    cyg_can_filter  *filter,
                                    int             len)
{
    int result = CYG_CAN_NOERROR;
    struct cyg_can_xyzzy_priv *priv = (struct cyg_can_xyzzy_priv *)dev->private;

    // Set the extended filters to match the parameters. If the
    // hardware filter cannot be used, return CYG_CAN_INVALID, only
    // return CYG_CAN_NOERROR if all filters can be installed.

    return result;
}

static int cyg_can_xyzzy_baud(cyg_can_device  *dev,
                              cyg_uint32      baud)
{
    int result = CYG_CAN_NOERROR;
    struct cyg_can_xyzzy_priv *priv = (struct cyg_can_xyzzy_priv *)dev->private;

    // Set the baud rate, which may involve checking that the
    // requested rate is supported. If not the return
    // CYG_CAN_INVALID.

    result = cyg_can_xyzzy_set_baudrate( priv, baud );

    return result;
}