1. CAN Driver

RTEMS provides fully featured CAN/CAN FD stack. The API to the driver is provided in the form of the POSIX character driver with each CAN controller (chip) registered as node into “/dev” namespace by name (i.e. “can0”, “can1”, … ). The stack utilizes FIFO queues (also called edges) organized into oriented edges between controller side and application side. Edges, responsible for message transfers from application to controller and vice versa, can have different priorities and function as priority classes.

Controller takes frames from FIFOs according their priority class and transmits them to the network. Successfully sent frames are echoed through queues back to open file instances except the sending one (that filtering is configurable). Received frames a filtered to all queues to applications ends of the queues which filter matches the CAN identifier and frame type.

The stack provides run time configuration options to create new queues with desired priority, direction and filter, making it suitable for various applications requirements. There is also a possibility to configure controller’s characteristics (bit rate, mode, chip specific ioctl calls).

The device can be opened in both blocking and nonblocking mode and one device can be opened from multiple applications. Read and write operations wait on binary semaphore indefinitely if blocking mode is used and frame can not be passed to the framework immediately (full FIFO queue for example) or there is no received message to process.

1.1. RTEMS CAN API

Application Interface is provided with standard POSIX calls open(), write(), read(). close() and ioctl(). Functions write() and read() are used with can_frame structure representing one CAN frame (message).

1.1.1. Opening Device and Configuration

Device is registered as a node into “/dev” namespace and can be opened with POSIX open() call. A single chip can be opened multiple times with each instance creating its own queues between controller and application.

CALLING SEQUENCE:
int open( const char* pathname, int flags );
DESCRIPTION:

Opens CAN device at path “pathname” with mode defined in “flags”. Modes are defined according to POSIX standard

Both infrastructure resources and controller itself can be configured once the device is opened. The configuration is provided via ioctl calls.

1.1.1.1. Managing Queues

One RX and one TX queue is created by default during open() operation. These queues have the lowest priority and default filter and size settings. If needed, more queues can be created by RTEMS_CAN_CREATE_QUEUE call.

CALLING SEQUENCE:
ssize_t ioctl( fd, RTEMS_CAN_CREATE_QUEUE, &queue );
DESCRIPTION:

Creates new queue with characteristics defined in “queue” field provided as can_queue structure.

struct can_queue {
  uint8_t direction;
  uint8_t priority;
  uint8_t dlen_max;
  uint8_t buffer_size;
  struct can_filter filter;
};

Queues can be removed (discarded) with RTEMS_CAN_DISCARD_QUEUES command. It is not possible to discard one specific queue, just all RX or/and all TX queues for given opened instance (file descriptor) at once. Direction can be defined by RTEMS_CAN_QUEUE_TX and RTEMS_CAN_QUEUE_RX defines. Terms TX and RX are used from application’s point of view: TX meaning queues transferring messages from application to controller, RX from controller to application.

CALLING SEQUENCE:
ssize_t ioctl( fd, RTEMS_CAN_DISCARD_QUEUES, type );
DESCRIPTION:

Discard TX and/or RX queues based on integer “type” argument. Defines RTEMS_CAN_QUEUE_TX and RTEMS_CAN_QUEUE_RX can be used to specify queues for deletion.

Queues can also be flushed with RTEMS_CAN_FLUSH_QUEUES command.

CALLING SEQUENCE:
ssize_t ioctl( fd, RTEMS_CAN_FLUSH_QUEUES, type );
DESCRIPTION:

Flushes TX and/or RX queues based on integer “type” argument. Defines RTEMS_CAN_QUEUE_TX and RTEMS_CAN_QUEUE_RX can be used to specify queues for deletion. The operation flushes all RX or/and all TX queues even if multiple queues are used.

1.1.1.2. Setting Bit Timing

There are two ways to set CAN bit timing. Either the user can pass desired bit rate value and let the infrastructure calculate bit timing, or precomputed bit timing values can be passed directly. IOCTL call RTEMS_CAN_SET_BITRATE is used for this purpose.

CALLING SEQUENCE:
ssize_t ioctl( fd, RTEMS_CAN_SET_BITRATE, &set_bittiming );
DESCRIPTION:

Sets bit timing based on “set_bittiming” parameter passed as a pointer to can_set_bittiming structure.

Field “type” determines bit timing to be set (CAN_BITTIME_TYPE_NOMINAL or CAN_BITTIME_TYPE_DATA), field “from” determines source of bit timing values (CAN_BITTIME_FROM_BITRATE or CAN_BITTIME_FROM_PRECOMPUTED).

Actual bit timing values and controller’s bit timing constants can be retrieved with RTEMS_CAN_GET_BITTIMING.

CALLING SEQUENCE:
ssize_t ioctl( fd, RTEMS_CAN_GET_BITTIMING, &get_bittiming );
DESCRIPTION:

Retrieves currently set bit timing values and controller’s bit timing constants.

Field “type” determines bit timing to be set (CAN_BITTIME_TYPE_NOMINAL or CAN_BITTIME_TYPE_DATA).

1.1.1.3. Setting Mode

Different mode of the chip can be enabled/disabled. IOCTL call RTEMS_CAN_CHIP_SET_MODE is used to set mode as 32-bit large unsigned integer mask.

CALLING SEQUENCE:
ssize_t ioctl( fd, RTEMS_CAN_CHIP_SET_MODE, mode );
DESCRIPTION:

Argument “mode” is a 32-bit large unsigned integer with modes to be set. Available modes are

  • CAN_CTRLMODE_LOOPBACK,

  • CAN_CTRLMODE_LISTENONLY,

  • CAN_CTRLMODE_3_SAMPLES,

  • CAN_CTRLMODE_ONE_SHOT,

  • CAN_CTRLMODE_BERR_REPORTING,

  • CAN_CTRLMODE_FD,

  • CAN_CTRLMODE_PRESUME_ACK,

  • CAN_CTRLMODE_FD_NON_ISO,

  • CAN_CTRLMODE_CC_LEN8_DLC,

  • CAN_CTRLMODE_TDC_AUTO, and

  • CAN_CTRLMODE_TDC_MANUAL.

The modes are selected to be compatible with GNU/Linux’s SocketCAN. It is possible to set multiple modes during one IOCTL call. The controller should be implemented in such a way that not setting particular mode in this IOCTL call disables this mode.

Every controller holds its supported mode. An attempt to set a mode not supported by the controller leads to ioctl call returning error. It is also possible to set mode only if the controller is stopped, otherwise error is returned.

1.1.1.4. Starting Chip

Opening the device does not start the chip, this operation has to be handled by specific ioctl call RTEMS_CAN_CHIP_START

CALLING SEQUENCE:
ssize_t ioctl( fd, RTEMS_CAN_CHIP_START );
DESCRIPTION:

Starts the chip (enables write/read). Repeated calls on already started chip do not have any effect.

1.1.1.5. Stopping Chip

Similarly to chip start operation, chip stop is performed with RTEMS_CAN_CHIP_STOP ioctl call.

CALLING SEQUENCE:
ssize_t ioctl( fd, RTEMS_CAN_CHIP_STOP );
DESCRIPTION:

Stops the chip (disables write/read). Repeated calls on already stopped chip do not have any effect.

NOTES.

It is important to check the number of users (applications) using the chip before turning it off as there can be more than one user per chip. The infrastructure allows turning off the controller even if there are other users on it. Read and write calls from other applications return error in that case.

1.1.1.7. Controller Statistics

The controller can keep track of its statistics as number of received/transmitted frames, number of received/transmitted bytes, number of errors and so on. These statistics are represented in can_stats structure and can be obtained with RTEMS_CAN_CHIP_STATISTICS ioctl call.

CALLING SEQUENCE:
ssize_t ioctl( fd, RTEMS_CAN_CHIP_STATISTICS, &statistics );
DESCRIPTION:

Obtains controller’s statistics provided in with argument statistics as a pointer to can_stats structure.

1.1.2. CAN Frame Representation

CAN frame header is statically defined by a separate can_frame_header. It has an 8 bytes long timestamp, 4 bytes long CAN identifier, 2 bytes long flag field and 2 bytes long field with information about data length. Data field itself is a 64 byte long array with byte access.

struct can_frame_header {
  uint64_t timestamp;
  uint32_t can_id;
  uint16_t flags;
  uint16_t len;
};

struct can_frame {
    struct can_frame_header header;
    uint8_t data[CAN_FRAME_MAX_DLEN];
};

Flags are used to distinguish frame formats (extended identifier, CAN FD format, remote request and so on). Following defines can be used.

  • CAN_FRAME_IDE,

  • CAN_FRAME_RTR,

  • CAN_FRAME_FDF,

  • CAN_FRAME_BRS,

  • CAN_FRAME_ESI,

  • CAN_FRAME_ECHO,

  • CAN_FRAME_LOCAL and

  • CAN_FRAME_ERR.

Extended frame format (CAN_FRAME_IDE) is forced automatically if identifier exceeds 11 bits.

1.1.3. Frame Transmission

Frame is transmitted to the CAN framework by calling write() function.

CALLING SEQUENCE:
ssize_t write( int fd, struct can_frame *frame, size_t count );
DESCRIPTION:

Passes CAN frame represented by can_frame structure to the network. Return values comply with POSIX standard. Write size can be calculated by can_framelen() function. It is possible to write just one frame with a single call. Passing incorrect frame length (less than header size or larger than maximum CAN frame size) results in write error.

User can check whether the messages were transferred from RTEMS framework to CAN network by calling ioctl RTEMS_CAN_WAIT_TX_DONE.

CALLING SEQUENCE:
ssize_t ioctl( fd, RTEMS_CAN_WAIT_TX_DONE, &timeout );
DESCRIPTION:

Waits with timeout until all frames are transferred to the network. The timeout is defined as a pointer to timespec structure. Returns 0 on success and “-ETIME” on timeout.

Polling in nonblocking mode can be done with RTEMS_CAN_POLL_TX_READY ioctl call.

CALLING SEQUENCE:
ssize_t ioctl( fd, RTEMS_CAN_POLL_TX_READY, &timeout );
DESCRIPTION:

Implements polling function on outgoing edges. Timeout is defined with timespec structure. It waits until there is an available frame in any of the input FIFOs or until timeout.

1.1.4. Frame Reception

Frame is received from the CAN framework by calling read() function.

CALLING SEQUENCE:
ssize_t read( int fd, struct can_frame *frame, size_t count );
DESCRIPTION:

Reads CAN frame represented by can_frame from the network. Return values comply with POSIX standard. The call returns error if read size specified by count is less than the length of the frame header. It is possible to read only a single frame with a read call.

Polling in nonblocking mode can be done with RTEMS_CAN_POLL_RX_AVAIL ioctl call.

CALLING SEQUENCE:
ssize_t ioctl( fd, RTEMS_CAN_POLL_RX_AVAIL, &timeout );
DESCRIPTION:

Implements polling function on incoming edges. Timeout is defined with timespec structure. It waits until there is an available frame in any of the input FIFOs or until timeout.

1.2. Driver Interface

The infrastructure provides several functions providing interface between FIFO queue side of the stack and controller specific implementation. Controller’s driver should use these functions to access the FIFO.

For examples of CAN controller’s driver see:

1.2.1. Chip Initialization

Chip initialization function should allocate can_chip structure. This structure holds controller’s ends of FIFO edges as well as chip private structure. This structure is chip specific and is used to store chip specific data. Chip’s ends are initialized with canqueue_ends_init_chip().

CALLING SEQUENCE:
int canqueue_ends_init_chip (
  struct canque_ends_dev_t *qends_dev,
  struct can_chip *chip,
  rtems_binary_semaphore worker_sem
);
DESCRIPTION:

Initializes controller’s side ends defined in “chip->qends_dev” and connects them to the edges. Also assigns worker binary semaphore “worker_sem” used by the framework to inform controller’s side about new message. Controller can also use this semaphore to trigger its worker thread in case of interrupt.

Driver should also register several functions used by IOCTL calls (start chip, stop chip, set bitrate for example). These functions are assigned throught can_chip_ops structure.

1.2.2. Frame Transmission

Controller is informed about new message to be transferred by frameworking posting “worker_sem” semaphore. Function canque_test_outslot() can then be used to obtain the highest priority ready slot from the framework.

CALLING SEQUENCE:
int canque_test_outslot(
  struct canque_ends_t *qends,
  struct canque_edge_t **qedgep,
  struct canque_slot_t **slotp
);
DESCRIPTION:

Tests and retrieves oldest ready slot from the highest priority active edge (priority class).

The slot can be subsequently put into controller’s HW buffer and sent to the network. Function canque_test_outslot() does not free space in FIFO queue but still holds the space for the slot until confirmed the transmission was done. The controller should inform the framework by calling canque_free_outslot() function.

CALLING SEQUENCE:
int canque_free_outslot(
  struct canque_ends_t *qends,
  struct canque_edge_t *qedge,
  struct canque_slot_t *slot
);
DESCRIPTION:

Releases processed slot previously acquired by canque_test_outslot() call.

The framework also provides a unique feature to pushback slots back to the correct FIFO and schedule the slot later processing. This is useful in case of transmission abort. The abort might be used when some later scheduled low-priority frame occupies the hardware TX buffer, which is urgently demanded for a higher priority pending message from other FIFO for example.

CALLING SEQUENCE:
int canque_again_outslot(
  struct canque_ends_t *qends,
  struct canque_edge_t *qedge,
  struct canque_slot_t *slot
);
DESCRIPTION:

Reschedules slot previously acquired by canque_test_outslot() function call for second time processing.

Previously described function canque_test_outslot() already takes the slot from the FIFO. This is not convenient in case there is no free space in controller’s hardware TX buffers. We would rather just check whether there is some pending message from higher priority class compared to priority classes presented in buffers. This can be done with canque_pending_outslot_prio().

CALLING SEQUENCE:
int canque_pending_outslot_prio(
  struct canque_ends_t *qends,
  int prio_min
);
DESCRIPTION:

Tests whether there is ready slot for given ends and minimum priority to be considered. Negative value informs this is not a case, positive value informs about the available slot priority class.

1.2.3. Frame Reception

Upon successful frame reception, the controller has to pass the frame to FIFO edges. This is done with canque_filter_frame2edges()

CALLING SEQUENCE:
int canque_filter_frame2edges(
  struct canque_ends_t *qends,
  struct canque_edge_t *src_edge,
  struct can_frame *frame,
  unsigned int flags2add
);
DESCRIPTION:

Sends message (frame) defined by “frame” argument to all outgoing edges connected to given ends (“qends”) with additional flags “flags2add”. Argument “src_edge” defined optional source edge for echo detection.

1.3. Registering CAN bus

Once initialized (can_chip structure allocated and obtained), the device can be registered into /dev namespace by can_bus_register() function.

CALLING SEQUENCE:
int can_bus_register( struct can_bus *bus, const char *bus_path );
DESCRIPTION:

Registers CAN devices in structure can_bus to /dev namespace with path bus_path. The path may follow standard /dev/canX naming or it can be a different name selected by the user/BSP. Structure can_bus represents one CAN device and is defined as:

struct can_bus {
    struct can_chip *chip;
};

Device can be opened by open() function once registered into /dev namespace.