Week 8 Progress Report
Theme: Improving the Generic DCAN Driver Based on Mentor Feedback, Driver Synchronization, TX Safety, and Generic Message Object Support
Overview
This week, I mainly focused on improving the generic DCAN driver according to my mentors' review comments on the first merge request.
I also rebuilt my RTEMS toolchain, solved several build warnings, and verified that the driver worked correctly with the new environment.
The major work included:
- adding mutual exclusion for controller state changes
- improving the start and stop synchronization
- aborting pending transmissions when the controller stops
- waiting for the worker task to finish before completing the stop operation
- checking TX completion status
- reporting failed transmissions through the RTEMS CAN framework
- generalizing TX and RX functions to support selectable message objects
- removing the temporary received-frame interface
- rebuilding the RTEMS toolchain and solving compiler warnings
These changes were important because the driver already supported basic CAN transmission and reception, but it still needed stronger synchronization and error handling before upstream integration.
1. Responding to Mentor Review Comments
I reviewed the mentor comments on the merge request and updated the driver step by step.
The main feedback was related to:
- controller state protection
- synchronization between the application and worker task
- stopping the controller safely
- handling a pending TX request
- avoiding fixed Message Object 1 implementations
- checking whether a transmission completed successfully
- removing temporary test interfaces from the driver
The work this week focused more on driver correctness than adding new visible features.
2. Adding Mutual Exclusion
One of the most important changes was protecting controller state changes with mutual exclusion.
The driver can be accessed from several execution contexts:
- application task
- interrupt service routine
- worker task
- start operation
- stop operation
- transmit queue processing
Without synchronization, two contexts may modify the controller state at the same time.
For example, the application could stop the controller while the worker task is processing a TX completion interrupt.
This may cause:
- invalid register access
- incorrect controller state
- a frame being transmitted after the controller starts stopping
- the worker task accessing data that is being changed
- inconsistent statistics
Using the RTEMS CAN Chip Lock
The driver uses the lock provided by the RTEMS CAN chip structure.
The main idea is:
rtems_mutex_lock(&chip->lock);
/*
* Modify shared controller state.
*/
rtems_mutex_unlock(&chip->lock);
The lock protects operations such as:
- checking whether the controller is running
- setting or clearing the running state
- changing internal driver state
- updating statistics
- beginning the stop process
A simplified start operation is:
static int dcan_start_chip(struct rtems_can_chip *chip)
{
struct dcan_internal *internal = chip->internal;
int error;
rtems_mutex_lock(&chip->lock);
if (internal->running) {
rtems_mutex_unlock(&chip->lock);
return 0;
}
error = dcan_start_controller(chip);
if (error == 0) {
internal->running = true;
}
rtems_mutex_unlock(&chip->lock);
return error;
}
The actual implementation follows the RTEMS CAN framework state and locking requirements, but the important principle is that the controller state cannot be changed by two tasks at the same time.
3. Synchronizing the Stop Operation
Stopping the controller is more complicated than only disabling interrupts.
When the stop function is called, the worker task may still be:
- processing an RX interrupt
- processing TX completion
- accessing a message object
- waiting on its worker semaphore
- updating the CAN queues
Therefore, the stop function must coordinate with the worker task.
The stop process becomes:
Application calls stop
↓
Acquire chip lock
↓
Mark controller as stopping
↓
Disable controller interrupts
↓
Abort pending TX request
↓
Wake the worker task
↓
Wait for worker stop confirmation
↓
Enter DCAN initialization mode
↓
Clear running state
Stop Semaphore
A stop semaphore is used so that the stop function can wait until the worker has finished its current work.
A simplified structure is:
struct dcan_internal {
rtems_binary_semaphore worker_sem;
rtems_binary_semaphore stop_sem;
bool running;
bool stopping;
};
The stop function requests the worker to stop and then waits:
internal->stopping = true;
rtems_binary_semaphore_post(&internal->worker_sem);
rtems_binary_semaphore_wait(&internal->stop_sem);
The worker checks the stop state:
if (internal->stopping) {
rtems_binary_semaphore_post(&internal->stop_sem);
break;
}
This ensures that the controller is not fully stopped while the worker is still using it.
4. Improving the Worker Wait Logic
Previously, the worker used a timeout while waiting for an interrupt event.
The mentor suggested using a blocking wait without a timeout.
The updated worker logic uses:
rtems_binary_semaphore_wait(&internal->worker_sem);
The worker is now activated by:
- the interrupt service routine
- the stop operation
- another driver event that needs worker processing
This is better because the worker does not wake up periodically without work.
Benefits include:
- lower CPU usage
- simpler logic
- fewer unnecessary wakeups
- clearer synchronization
5. Aborting a Pending Transmission
Another major improvement was safely handling a TX request when the controller is stopped.
A transmission may already be pending in a DCAN message object when the application calls the stop function.
If the driver enters initialization mode without handling the request, several problems may happen:
- the frame may remain pending
- the TX message object may not become idle
- the CAN queue may wait for a completion that never happens
- the frame may be transmitted unexpectedly after restarting
- driver state and hardware state may become inconsistent
DCAN TX Request State
A DCAN TX message object uses the TxRqst state to indicate that a frame is waiting for transmission.
The driver first checks whether the TX message object is idle.
static bool dcan_tx_mo_is_idle(
struct rtems_can_chip *chip,
uint32_t mobj
);
Conceptually:
if (!dcan_tx_mo_is_idle(chip, tx_mobj)) {
dcan_abort_tx(chip, tx_mobj);
}
Clearing the Pending TX Request
The driver uses the DCAN interface registers to update the selected message object and clear its pending TX request.
A simplified process is:
Select TX message object
↓
Wait until interface registers are available
↓
Clear TxRqst
↓
Transfer control data to message RAM
↓
Wait for interface operation to complete
This prevents the hardware from keeping an unfinished transmission request after the controller stops.
Reporting the Aborted Frame
A pending frame should not silently disappear.
The driver reports the failed transmission to the RTEMS CAN framework as a TX error frame.
The queue helper is used conceptually as follows:
rtems_can_queue_filter_frame_to_edges(
chip->base,
&frame,
CAN_FRAME_TXERR
);
This allows the application and CAN framework to know that the frame was not transmitted successfully.
6. Checking TX Completion Status
Previously, the driver processed the TX interrupt but did not fully check whether the transmission completed successfully.
A TX interrupt does not always mean that the frame was transmitted correctly.
The updated implementation checks the DCAN status register.
A helper function was added:
static bool dcan_tx_completed_ok(
struct rtems_can_chip *chip
);
The TX completion process becomes:
TX interrupt occurs
↓
Worker reads interrupt source
↓
Clear message object interrupt pending state
↓
Check TX completion status
↓
Success: complete normal TX processing
↓
Failure: generate CAN_FRAME_TXERR
A simplified implementation is:
if (dcan_tx_completed_ok(chip)) {
dcan_process_successful_tx(chip);
} else {
dcan_process_failed_tx(chip);
}
This improves error handling and makes the driver behavior closer to other RTEMS CAN drivers.
7. Clearing the TX Interrupt Pending State
The DCAN message object interrupt must be cleared after the worker processes the TX completion.
The driver uses the message object control operation with CLRINTPND.
Conceptually:
dcan_clear_message_object_interrupt(chip, tx_mobj);
The process is:
Read interrupt identifier
↓
Identify TX message object
↓
Check TX result
↓
Clear IntPnd for the message object
↓
Allow the next TX operation
Without clearing IntPnd, the controller could continue reporting the same interrupt or fail to generate the next expected interrupt correctly.
8. Supporting Selectable TX Message Objects
The original TX function was designed only for Message Object 1.
Before:
dcan1_write_frame_mo1(chip, frame);
This implementation was too specific because DCAN controllers usually provide many message objects.
The function was changed to accept a message object number.
After:
dcan_mo_write_frame(chip, mobj, frame);
Example:
dcan_mo_write_frame(chip, 1, frame);
In the future, another message object can be selected:
dcan_mo_write_frame(chip, 5, frame);
The generic function performs:
- message object validation
- CAN identifier conversion
- DLC configuration
- frame data copy
- arbitration register configuration
- TX request activation
This removes the assumption that Message Object 1 is always used for transmission.
9. Generalizing RX Message Object Access
The RX path was also changed to support a selectable message object.
Before, the implementation was tied to a specific RX message object.
The generalized helper became:
dcan_mo_read_rx_message(chip, mobj, frame);
The function reads:
- arbitration information
- CAN identifier
- standard or extended frame format
- DLC
- data bytes
- message loss state
- new data state
Example:
struct can_frame frame;
dcan_mo_read_rx_message(
chip,
rx_mobj,
&frame
);
This makes the RX implementation reusable for multiple filters and multiple receive message objects.
10. Adding Message Object Idle Checking
Before writing a new frame, the driver must make sure that the selected TX message object is available.
A helper function was added:
static bool dcan_tx_mo_is_idle(
struct rtems_can_chip *chip,
uint32_t mobj
);
The TX processing logic becomes:
if (!dcan_tx_mo_is_idle(chip, tx_mobj)) {
return;
}
dcan_mo_write_frame(chip, tx_mobj, frame);
This prevents overwriting a message object that still contains a pending frame.
It also prepares the driver for using multiple TX message objects in the future.
11. Removing the Temporary Received-Frame Interface
During the early bring-up stage, the driver stored the latest received frame internally.
The temporary function was:
struct can_frame *dcan_get_rx_frame(
struct rtems_can_chip *chip
);
The internal structure also contained:
bool frame_to_pass;
struct can_frame frame;
This was useful during early testing, but it was no longer needed after the RX path was integrated with the RTEMS CAN queues.
The normal flow is now:
DCAN receives frame
↓
Interrupt wakes worker
↓
Worker reads message object
↓
Frame enters RTEMS CAN queue
↓
Application reads from CAN bus
The temporary frame_to_pass mechanism was removed because it duplicated the CAN framework and only stored one frame.
Removing it made the driver cleaner and avoided possible frame loss when multiple frames arrived quickly.
12. Improving the Start and Stop State
The controller state is now updated more carefully.
The running state is set only after the controller starts successfully.
error = dcan_start_controller(chip);
if (error == 0) {
internal->running = true;
}
When stopping, the state is not cleared until:
- interrupts are disabled
- pending TX is handled
- the worker finishes
- the controller enters the correct hardware state
This avoids a situation where software reports that the controller has stopped while the hardware or worker task is still active.
13. Interrupt and Worker Processing
The interrupt service routine remains small.
Its main responsibilities are:
Read interrupt source
↓
Disable or mask the interrupt when necessary
↓
Wake the worker task
A simplified ISR is:
static void dcan_interrupt_handler(void *arg)
{
struct rtems_can_chip *chip = arg;
struct dcan_internal *internal = chip->internal;
dcan_disable_interrupts(chip);
rtems_binary_semaphore_post(&internal->worker_sem);
}
The worker performs the larger operations:
- RX message reading
- TX completion processing
- error checking
- queue interaction
- interrupt pending clearing
- interrupt re-enabling
- stop synchronization
This design keeps the interrupt handler short and moves more complex work into task context.
14. Building the Updated RTEMS Toolchain
After the code changes, I rebuilt RTEMS to make sure the driver worked with the current source tree.
First, I configured the Beagle BSP build.
./waf configure \
--prefix=$HOME/development/rtems/7 \
--rtems-bsps=arm/beagle
Then I built RTEMS.
./waf build
After a successful build, I installed it.
./waf install
The updated installation was then used to build the external DCAN test application.
15. Solving Compiler Warnings
The new toolchain reported warnings that were not visible in the previous environment.
The warnings helped identify several code quality problems, such as:
- missing declarations
- unused functions
- unused variables
- type differences
- incorrect function visibility
- header dependency problems
- style issues
My workflow was:
Build RTEMS
↓
Read the first warning
↓
Find the related function or header
↓
Modify the code
↓
Build again
↓
Repeat until clean
I avoided hiding warnings and instead fixed their causes.
This was useful because compiler warnings often show problems that may later become runtime bugs.
16. Hardware Testing
After rebuilding RTEMS, I rebuilt the test application and tested the driver on the BeagleBone Black.
The hardware setup was:
Linux SocketCAN
↓
CANable Adapter
↓
CAN Bus
↓
BeagleBone Black DCAN1
↓
RTEMS DCAN Driver
The CAN bitrate was:
125000 bit/s
Linux CAN interface configuration:
sudo ip link set can0 down
sudo ip link set can0 up type can bitrate 125000
Receive frames:
candump can0
Send a frame to RTEMS:
cansend can0 123#1122334455667788
I verified:
- controller start
- controller stop
- stop during TX processing
- TX completion interrupt
- TX success checking
- TX error reporting
- RX interrupt processing
- selectable message object access
- restarting the controller after stopping
- Linux-to-RTEMS communication
- RTEMS-to-Linux communication
17. Major Functions Completed This Week
The main functions and mechanisms completed or improved this week were:
dcan_start_chip()
- protects controller state
- starts the controller safely
- updates running state only after success
dcan_stop_chip()
- prevents new operations
- disables interrupts
- aborts pending transmission
- wakes and synchronizes with the worker
- waits for stop completion
dcan_tx_mo_is_idle()
- checks whether a selected TX message object can accept a new frame
dcan_mo_write_frame()
- writes a CAN frame to a selectable message object
dcan_mo_read_rx_message()
- reads a CAN frame from a selectable RX message object
dcan_tx_completed_ok()
- checks whether the hardware completed transmission successfully
dcan_process_tx()
- gets frames from the RTEMS CAN queue
- checks message object availability
- starts transmission
- handles completion and error reporting
What I Learned
This week helped me understand that concurrency control is a very important part of device driver development.
A driver may work during simple testing but still fail when:
- an interrupt occurs during a state change
- the controller stops during transmission
- the worker and application access shared state together
- a hardware request remains pending
- an error interrupt is treated as a successful operation
I learned how mutual exclusion, semaphores, hardware state, and the RTEMS CAN queue must work together.
I also learned that making functions generic is not only changing their names. A generic function should avoid fixed assumptions and receive the required controller information through parameters.
Current Status
Completed this week:
- Mutual exclusion for controller state
- Start and stop synchronization
- Worker stop semaphore
- Blocking worker wait
- Pending TX abort during stop
- TX completion status checking
- TX error reporting
- TX interrupt pending clearing
- Selectable TX message object
- Selectable RX message object
- TX message object idle checking
- Removal of the temporary
frame_to_passinterface - RTEMS toolchain rebuild
- Compiler warning fixes
- BeagleBone Black hardware testing
Next Week
Next week, I will:
- separate AM335x-specific initialization from the generic driver
- move clock and pin multiplexing code into the Beagle BSP
- update the RTEMS build files
- reorganize the merge request into two logical commits
- rebase the work onto the latest RTEMS main branch
- rebuild and test the installed driver