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 sja1000orflexcan. 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 can0orcan1.
- 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 asCYGPKG_DEVS_CAN_CHANNEL0_NAMEorCYGPKG_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_0orcyg_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 theopen()function, there is no required behaviour here, but ifopen()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_NOERRORwhether 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_INVALIDfor 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_NOERRORif all the extended filters can be handled, andCYG_CAN_INVALIDif 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 );
#endifThe 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;
}| 2025-10-02 | eCosPro Non-Commercial Public License | 



