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
orflexcan
. 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
orcan1
. - 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_NAME
orCYGPKG_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
orcyg_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_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, andCYG_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; }
2024-03-18 | eCosPro Non-Commercial Public License |