Introduction
This is the blog for GSoC 2026.
Contents would be the process of RTEMS Controller Area Network (CAN) Stack Improvement: BeagleBone Black D-CAN Driver Implementation.
Project Abstract
This project develops a D-CAN driver for the AM335x controller on the BeagleBone Black and integrates it into the RTEMS CAN stack, enabling reliable CAN communication through standard interfaces such as /dev/canX. The driver will support FIFO-based reception, priority-aware transmission, and real-time behavior.
By adding CAN support to a widely used embedded platform, this project extends RTEMS usability in applications such as robotics, automotive systems, and industrial control. The implementation will be validated on real hardware and follow RTEMS coding standards, while also serving as a reference for future CAN driver development.
Project Description
This project focuses on implementing support for the AM335x D-CAN controller on the BeagleBone Black within RTEMS. While RTEMS provides a generic CAN/CAN FD stack and user-facing device interface, it currently lacks a driver for this hardware. This project will enable RTEMS applications to access CAN functionality through standard device nodes such as /dev/canX.
The D-CAN controller uses a mailbox-based hardware architecture, where each message object is configured individually for transmission or reception. In contrast, the RTEMS CAN stack follows a queue-based abstraction. Bridging this mismatch is a key design challenge.
To address this, the driver will introduce a mapping layer between hardware and software:
- RX Path: A subset of hardware message objects will be configured to emulate FIFO behavior. The driver will extract messages and push them into RTEMS CAN queues while preserving ordering.
- TX Path: A software-managed queue will group messages by priority. Hardware mailboxes will be dynamically assigned based on availability, allowing higher-priority messages to preempt lower-priority ones while maintaining FIFO order within the same priority class.
- Synchronization: Register access via IFx interfaces and interrupt handling will be carefully managed to ensure consistency and avoid race conditions.
The implementation will follow an incremental approach. The initial stage will establish a minimal working driver with initialization, polling-based transmission, and FIFO-based reception. After validating correctness, the driver will be extended to support interrupt-driven operation, improved mailbox utilization, and priority-aware scheduling.
Existing implementations such as the Linux c_can driver will be studied for design reference, but not directly reused. Validation will be performed on real hardware using loopback and dual-board communication tests (RTEMS ↔ Linux).
The final outcome will be a robust, well-structured, and maintainable D-CAN driver integrated into the RTEMS CAN stack, providing a strong foundation for future enhancements such as performance optimization and optional DMA support.
Blog setup
This blog uses mdBook + github.io.
I installed the mdBook using homebrew, which I used before, so it is just easy to continue to use that. Other resources as below:
About me
This is Ning. I just got my master degree in Electrical Engineering in the Internet of Things from University at Buffalo, SUNY.
And I will pursue my PhD degree in the cross area of embedded system and cybersecurity in 2026 Fall in University of Colorado Colorado Springs.
The Internet of Things is naturally close to embedded system applications and I have built some embedded applications before and it was interesting. I like the process of solving problems. The difficulties will be the great opportunities for learning and development. I am looking forward this summer to be challenged and learn and grow.
I found the documentation in RTEMS website is very helpful, which helps me understand how to start at the very beginning. And through the responses and resources with the community helps know the next steps. Really thankful to the mentors, who reply promptly and give their valuable insights.
Application
Preparation
This is the Timeline of Google Summer of Code.
I started to look at GSoC around mid-January. This is the time to find out interested projects. I was looking into one and then found a more suitable one for me with the help from the community.
1. Discord and Discourse
Join the Discord server.
Post on the Discourse in the Programs category.
2. Finish helloworld in GitLab
Follow the GSoC Getting Started
Learn the basic of RTEMS stack.
3. Post a tracking page in GitLab
Learn from previous year contributors. It is okay to leave some part empty and fill them later.
Write Proposal
Then it is the time to focus to understand the project, prepare the hardware, software and knowledge. During this time, keep in mind to get involved with the community. Like post your interests in Discourse and connect with the community.
This is very important. Continue to improve your proposal.
Community bonding
After selected for GSoC, connect with mentors.
Initial Setup
This is a hardware project, it would be necessary to show the hardware works in your proposal.
Application Period, March 19 - March 31 Preliminary Work (Completed)
- Set up the RTEMS development environment, SPARC based BSP and hello world.
- Set up hardware for CAN implementation using the BBB debian system.
- Booted RTEMS on the BeagleBone Black using an SD card with hello.c application.
- Update proposal link to Gitlab tracking page.
- Draft the proposal, focusing on deliverables.
Application Review Period, April 1 - April 31
- Finish a courser of Microcontrollers: Basic Architecture and Design.
RTEMS BBB D-CAN Development Quick Reference
quick commands
Convert it into a bootable U-Boot image:
arm-rtems7-objcopy hello_dcan.exe \
-O binary app.bin
gzip -9 app.bin
mkimage -A arm -O linux -T kernel -a 0x80000000 -e 0x80000000 -n RTEMS -d app.bin.gz rtems-app.img
In U-Boot:
BBB U-Boot configuration:
setenv ipaddr 192.168.5.4
setenv serverip 192.168.5.1
Transfer files:
tftpboot 0x80800000 rtems-app.img
tftpboot 0x88000000 am335x-boneblack.dtb
Run:
bootm 0x80800000 - 0x88000000
1. Build Workflow
Build RTEMS application
Inside repo:
cd ~/development/app/rtems-bbb-dcan
Build:
./waf
2. When to Run Configure
First time only
Run configure when:
- first build
- BSP changes
- RTEMS toolchain changes
- build directory deleted
- configure options changed
./waf configure \
--rtems=$HOME/development/rtems/7 \
--rtems-tools=$HOME/development/rtems/7 \
--rtems-bsp=arm/beagleboneblack
Normally
After source code changes:
./waf
No need to reconfigure.
3. Generated Files
After build:
build/arm-rtems7-beagleboneblack/hello_dcan.exe
4. Generate Bootable RTEMS Image
objcopy
arm-rtems7-objcopy \
build/arm-rtems7-beagleboneblack/hello_dcan.exe \
-O binary \
app.bin
gzip
gzip -9 -f app.bin
Generates:
app.bin.gz
mkimage
mkimage \
-A arm \
-O linux \
-T kernel \
-a 0x80000000 \
-e 0x80000000 \
-n RTEMS \
-d app.bin.gz \
rtems-app-dcantest.img
5. TFTP Workflow
TFTP working directory
mkdir ~/tftpboot
macOS TFTP export directory
/private/tftpboot
symbolic links
sudo ln -sf ~/tftpboot/rtems-app-dcantest.img \
/private/tftpboot/rtems-app-dcantest.img
sudo ln -sf ~/tftpboot/am335x-boneblack.dtb \
/private/tftpboot/am335x-boneblack.dtb
Only needed once.
Copy updated image
After rebuild:
cp rtems-app-dcantest.img ~/tftpboot/
6. Start TFTP Server on macOS
Start
sudo launchctl load -F \
/System/Library/LaunchDaemons/tftp.plist
Stop
sudo launchctl unload \
/System/Library/LaunchDaemons/tftp.plist
Check status
sudo launchctl list | grep tftp
7. BBB U-Boot Network Setup
Configure BBB IP
setenv ethact ethernet@4a100000
setenv ipaddr 192.168.5.4
setenv serverip 192.168.5.1
Test connection
ping 192.168.5.1
Expected:
host 192.168.5.1 is alive
8. TFTP Download on BBB
Download RTEMS image
tftpboot 0x80800000 rtems-app-dcantest.img
Download DTB
tftpboot 0x88000000 am335x-boneblack.dtb
9. Boot RTEMS
bootm 0x80800000 - 0x88000000
10. Serial Console
Find serial device
ls /dev/cu.*
screen without logging
screen /dev/cu.usbserial-XXXXX 115200
screen with logging
TERM=xterm screen -L /dev/cu.usbserial-XXXXX 115200
Creates:
screenlog.0
11. Exit screen Properly
Ctrl-A
then k
then y
12. Kill Stuck screen Sessions
Find process
lsof /dev/cu.usbserial-XXXXX
Kill process
kill -9 <PID>
or:
pkill screen
13. Useful Git Commands
Check changes
git status
Add files
git add .
Commit
git commit -m "message"
Push
git push
14. .gitignore Recommended
/build
/build-*
doc
/*.ini
.lock*
*.pyc
.waf*
*.o
*.a
*.exe
*.bin
*.img
*.map
*.gz
15. Current Development Flow
Modify source code
↓
./waf
↓
objcopy
↓
gzip
↓
mkimage
↓
copy image to ~/tftpboot
↓
BBB tftpboot
↓
bootm
↓
observe UART logs
16. Current Driver Development Stages
Stage 1
RTEMS boot + D-CAN base address test
[Completed]
Stage 2
Enable DCAN clock
Stage 3
Read DCAN_CTL / DCAN_ES safely
Stage 4
Initialize D-CAN controller
Stage 5
Transmit CAN frame
Stage 6
Linux candump receives frame
Stage 7
Receive CAN frame on BBB
Stage 8
Interrupt handling
Stage 9
RTEMS CAN framework integration
/dev/can0
Coding Start
Coding Coding officially begins at May 25th!
week_1
Overall Conclusion
This week marked my first successful interaction with real hardware through RTEMS on the BeagleBone Black.
At the beginning of the project, my understanding of embedded systems was largely theoretical. I knew that RTEMS applications could be compiled into executable images, but I did not have a clear mental model of how source code actually became a running program on hardware.
Through building, deploying, and testing a minimal RTEMS application, I established a complete understanding of the development workflow:
Source Code
↓
RTEMS Build
↓
rtems-app.img
↓
TFTP Transfer
↓
U-Boot
↓
BeagleBone Black
↓
ARM Cortex-A8 Execution
↓
Memory-Mapped Registers
↓
DCAN Peripheral
This realization significantly reduced the gap between software and hardware in my understanding of embedded systems.
Technically, the primary accomplishment of the week was successfully bringing up the AM335x D-CAN controller and verifying the complete configuration path from the CPU to the controller's Message RAM.
The following milestones were completed:
✓ RTEMS application execution on BBB
✓ DCAN base address verification
✓ DCAN clock enable
✓ Controller register access
✓ INIT / CCE configuration sequence
✓ Bit Timing Register access
✓ TX Message Object configuration
✓ IF1 → Message RAM transfer
✓ Message RAM → IF1 readback transfer
✓ Message Object verification
In addition to DCAN-specific knowledge, I gained a deeper understanding of several important embedded systems concepts:
Memory-mapped I/O
Register addressing
Clock gating
Synchronous digital logic
D Flip-Flops
CAN bit timing
Time Quanta (TQ)
Message RAM architecture
Interface Registers (IF1/IF2)
Acceptance masks
One of the most valuable lessons was learning that CAN controllers are much more than simple transmit and receive registers. The AM335x D-CAN architecture uses Interface Registers and Message Objects as an intermediate layer between software and hardware, which introduced me to a more sophisticated peripheral design than I had previously encountered.
By the end of the week, I successfully verified the complete data path:
CPU
↓
IF1 Registers
↓
Message RAM
↓
Message Object 1
↓
IF1 Registers
↓
CPU
The readback results matched the original configuration exactly, confirming that the controller configuration mechanism is functioning correctly.
Overall, Week 1 established a solid foundation for future driver development. The next phase of the project will focus on actual CAN communication by requesting frame transmission, connecting external CAN hardware. And eventually integrating the implementation into the RTEMS CAN framework.
May 25
- Read the Technical Reference Manual to understand the D-CAN.
- Build a repo in Gitlab. Refer to the RTEMS style, and CTU CAN FD structure.
- Build hello app to test
- Convert ELF Executable and Linkable Format to U-Boot image
May 26
RTEMS BBB TFTP Boot Workflow Simplified Main Flow version and details can be seen in this blog. Unfold week_1 tab will see details.
May 27
- DCAN initial test, try and learn the registers in DCAN. I was reading the Technicial Reference Mannual for several days and feel confused.Until the mentor said try it and commit it every day in Gitlab. That is a good way to learn. Do it and learn from doing. Mere reading helps little. Avoid Paralysis by analysis Unfold week_1 tab will see details.
May 28,29
- Bringing Up the AM335x D-CAN Controller on RTEMS.
- Register Access, Clock Enable, and Message Object Configuration.
- From Register Access to Message Object Configuration on BeagleBone Black.
- Combine with Technical Reference Manual
RTEMS BeagleBone Black Hello World Application Workflow
Development Directory Layout
/Users/ningzhang/development
├── rtems
│ └── 7
│ ├── bin
│ ├── arm-rtems7
│ └── share
│
├── src
│ └── rtems
│ ├── waf
│ ├── bsps
│ ├── cpukit
│ └── testsuites
│
├── build-bbb
│ ├── config.log
│ ├── arm-rtems7
│ └── cache
│
└── app
└── hello
├── init.c
├── hello.c
├── wscript
├── waf
├── build
├── app.bin
├── app.bin.gz
└── rtems-app.img
1. Install RTEMS 7 Toolchain
Install the RTEMS 7 toolchain using RTEMS Source Builder (RSB).
2. Clone RTEMS Source
Clone the RTEMS source tree into:
~/development/src/rtems
3. Create Application Workspace
mkdir -p $HOME/development/app/hello
cd $HOME/development/app/hello
4. Download Waf Build System
curl https://waf.io/waf-2.0.19 > waf
chmod +x waf
5. Initialize Git Repository
git init
6. Add RTEMS Waf Support
git submodule add \
https://gitlab.rtems.org/rtems/tools/rtems_waf.git \
rtems_waf
7. Create Application Source Files
Create:
init.chello.cwscript
8. Configure for BBB BSP
./waf configure \
--rtems=$HOME/development/rtems/7 \
--rtems-tools=$HOME/development/rtems/7 \
--rtems-bsp=arm/beagleboneblack
9. Build the Application
./waf
10. Generate RTEMS Executable
Output:
build/arm/beagleboneblack/hello.exe
11. Convert ELF to Binary
arm-rtems7-objcopy hello.exe -O binary app.bin
12. Compress the Binary
gzip -9 app.bin
13. Create U-Boot Image
mkimage -A arm -O linux -T kernel \
-a 0x80000000 -e 0x80000000 \
-n RTEMS \
-d app.bin.gz rtems-app.img
14. Prepare the Boot Method
Option A: SD Card Boot
Prepare a FAT32 SD card and copy:
rtems-app.imgam335x-boneblack.dtbuEnv.txt
Option B: TFTP Boot
Configure the host machine as a TFTP server.
Connect the BBB and host through Ethernet.
Configure U-Boot network settings:
ipaddr
serverip
Load the RTEMS image:
tftp 0x80800000 rtems-app.img
tftp 0x88000000 am335x-boneblack.dtb
15. Connect UART Console
Connect the USB-to-TTL serial adapter to the BBB UART pins.
Open the serial console:
screen /dev/cu.usbserial-XXXX 115200
16. Load RTEMS in U-Boot
SD Card Boot
fatload mmc 0 0x80800000 rtems-app.img
fatload mmc 0 0x88000000 am335x-boneblack.dtb
TFTP Boot
tftp 0x80800000 rtems-app.img
tftp 0x88000000 am335x-boneblack.dtb
17. Boot RTEMS
bootm 0x80800000 - 0x88000000
18. Observe Output
Observe RTEMS boot messages and the Hello World output through the UART serial console.
May 26
RTEMS BBB TFTP Boot Workflow
Goal
Establish a fast RTEMS development workflow:
Mac
→ TFTP Server
→ Ethernet
→ BBB U-Boot
→ RAM boot
→ RTEMS
without repeatedly reflashing SD cards.
1. UART Serial Console
Connect the BBB using a USB-to-TTL adapter:
TX
RX
GND
Find the serial device on macOS:
ls /dev/cu.*
Connect using:
screen /dev/cu.usbserial-XXXXX 115200
This is used for:
- accessing U-Boot
- viewing RTEMS boot logs
2. Enter U-Boot
Press the space bar during BBB boot to interrupt autoboot and enter:
=>
the U-Boot shell.
3. Configure Ethernet
Mac IP:
192.168.5.1
BBB U-Boot configuration:
setenv ethact ethernet@4a100000
setenv ipaddr 192.168.5.4
setenv serverip 192.168.5.1
Verify connectivity:
ping 192.168.5.1
This confirms BBB ↔ Mac Ethernet communication.
4. Start the TFTP Server on macOS
macOS exports TFTP files from:
/private/tftpboot
Create a working directory:
mkdir ~/tftpboot
Create symbolic links:
sudo ln -sf ~/tftpboot/rtems-app.img \
/private/tftpboot/rtems-app.img
sudo ln -sf ~/tftpboot/am335x-boneblack.dtb \
/private/tftpboot/am335x-boneblack.dtb
Start the TFTP server:
sudo launchctl load -F \
/System/Library/LaunchDaemons/tftp.plist
5. Build the RTEMS Application
Generate:
hello_dcan.exe
Convert it into a bootable U-Boot image:
arm-rtems7-objcopy hello_dcan.exe \
-O binary app.bin
gzip -9 app.bin
mkimage -A arm -O linux -T kernel -a 0x80000000 -e 0x80000000 -n RTEMS -d app.bin.gz rtems-app.img
6. Update the TFTP File
Copy the updated image:
cp rtems-app.img ~/tftpboot/
No need to recreate the symbolic links.
7. Download RTEMS via TFTP on BBB
In U-Boot:
tftpboot 0x80800000 rtems-app.img
tftpboot 0x88000000 am335x-boneblack.dtb
Memory layout:
| Address | Content |
|---|---|
| 0x80800000 | RTEMS image |
| 0x88000000 | DTB |
8. Boot RTEMS
Run:
bootm 0x80800000 - 0x88000000
Boot flow:
U-Boot
→ RAM
→ RTEMS
RTEMS successfully boots from RAM over Ethernet.
Final Fast Development Loop
Modify code
↓
./waf
↓
Generate hello_dcan.exe
↓
mkimage → rtems-app.img
↓
Copy to ~/tftpboot
↓
BBB tftpboot
↓
bootm
↓
Test RTEMS
Main Advantage
This workflow enables very fast iteration for:
- RTEMS CAN driver development
- BBB BSP work
- RX/TX testing
- interrupt debugging
- embedded bring-up
without constantly removing and reflashing SD cards.
Details during the process
The goal of this work was to establish a professional RTEMS development workflow on the BeagleBone Black (BBB) using U-Boot, Ethernet, and TFTP, instead of repeatedly reflashing SD cards for every RTEMS build iteration. The final objective was to successfully boot RTEMS over the network directly from RAM using U-Boot.
The complete workflow that was achieved is:
Mac host
→ TFTP server
→ Ethernet connection
→ BBB U-Boot
→ TFTP transfer of RTEMS image into RAM
→ TFTP transfer of Device Tree Blob (DTB)
→ bootm execution
→ RTEMS boot successfully
The final successful RTEMS output was:
RTEMS Beagleboard: am335x-based
Hello from RTEMS on BeagleBone Black!
The first stage was establishing UART serial communication with the BBB. A USB-to-TTL FTDI adapter was connected using TX, RX, and GND lines. On macOS, the serial console device appeared as:
/dev/tty.usbserial-A5069RR4
The serial console was accessed using screen:
screen /dev/tty.usbserial-A5069RR4 115200
Initially, boot logs were difficult to analyze because screen only displayed a limited amount of scrollback history. To solve this, screen logging was enabled:
TERM=xterm screen -L /dev/tty.usbserial-A5069RR4 115200
This generated a log file named:
screenlog.0
in the current working directory, which preserved the complete UART output, including U-Boot logs, RTEMS boot messages, Linux kernel logs, and debugging information.
It was also learned that screen should not be exited using Ctrl-C, because this can leave the serial device busy. The proper exit method is:
Ctrl-A
then k
then y
If the serial port became locked or busy, the following commands were used:
lsof /dev/tty.usbserial-A5069RR4
to determine which process owned the serial port, and:
kill -9 <PID>
or:
pkill screen
to terminate stale screen sessions.
The next stage was entering the U-Boot shell. Initially this was difficult because bootdelay was set to zero, which caused autoboot to proceed immediately into Debian Linux before any user interaction could occur. By continuously pressing the space key during reset, U-Boot autoboot was interrupted and the prompt:
=>
was reached successfully.
Once inside U-Boot, environment variables and bootloader configuration concepts were explored. Important commands included:
printenv
setenv
bootm
Key environment variables included:
ipaddr
serverip
ethact
bootdelay
bootcmd
The next major task was bringing up Ethernet networking between the Mac and the BBB. A USB Ethernet adapter on the Mac appeared as interface:
en7
A static IP configuration was created:
Mac:
192.168.5.1
BBB:
192.168.5.4
The BBB side network settings in U-Boot were configured with:
setenv ethact ethernet@4a100000
setenv ipaddr 192.168.5.4
setenv serverip 192.168.5.1
Network connectivity was verified using:
ping 192.168.5.1
One important discovery was that U-Boot sometimes automatically switched to the USB gadget Ethernet interface instead of the physical RJ45 Ethernet interface. This produced messages such as:
using musb-hdrc
RNDIS ready
CDC Ethernet
To ensure the correct Ethernet interface was used, the following environment variable was explicitly set:
setenv ethact ethernet@4a100000
During Ethernet debugging, several real hardware-level networking issues were encountered. The RJ45 LEDs on the BBB were observed carefully. When the LEDs turned off, it indicated loss of physical Ethernet carrier or PHY negotiation failure. Reconnecting the Ethernet cable restored link negotiation and the LEDs became active again.
On the Mac side, network status was monitored using:
ifconfig en7
A working Ethernet link required:
status: active
and:
media: autoselect (100baseTX ...)
When the link failed, the interface showed:
status: inactive
media: autoselect (none)
This demonstrated that the problem was not IP configuration but rather physical Ethernet link establishment.
The next major stage was setting up a TFTP server on macOS. macOS includes a built-in TFTP server daemon (tftpd), but it only exports files from:
/private/tftpboot
A working directory was created:
mkdir ~/tftpboot
and files were connected into the TFTP export directory using symbolic links:
sudo ln -sf ~/tftpboot/test.txt /private/tftpboot/test.txt
The TFTP daemon was started using:
sudo launchctl load -F /System/Library/LaunchDaemons/tftp.plist
A self-test was first performed locally on the Mac using the TFTP client:
tftp localhost
This confirmed the TFTP server was functioning correctly.
The first successful BBB-side TFTP transfer was:
tftpboot 0x80800000 test.txt
This demonstrated that U-Boot Ethernet and TFTP communication were working correctly.
A major conceptual realization during this stage was understanding that:
tftpboot does not load files into a filesystem
Instead, it loads raw bytes directly into RAM at a specified memory address. For example:
tftpboot 0x80800000 test.txt
loads the file contents into RAM beginning at address:
0x80800000
This reflects the fundamental bootloader execution model used in embedded systems.
The next stage involved transferring the actual RTEMS image:
tftpboot 0x80800000 rtems-app.img
The image was verified using:
iminfo 0x80800000
which confirmed that the image was a valid U-Boot legacy image with a correct checksum, load address, and entry point.
Initially, booting failed using:
bootm 0x80800000
The boot log showed:
FDT and ATAGS support not compiled in
followed by a reset and automatic fallback to Debian Linux boot.
At this point, it became clear that the RTEMS image required a Device Tree Blob (DTB) for successful boot.
Reference was made to the previously working SD-card boot configuration:
setenv bootdelay 5
uenvcmd=echo loading RTEMS from SD...; run boot
boot=fatload mmc 0 0x80800000 rtems-app.img; fatload mmc 0 0x88000000 am335x-boneblack.dtb; bootm 0x80800000 - 0x88000000
This revealed that the correct boot process required:
RTEMS image at 0x80800000
DTB at 0x88000000
bootm with DTB parameter
The DTB file was then added to the TFTP workflow:
tftpboot 0x88000000 am335x-boneblack.dtb
However, the TFTP server initially returned:
TFTP error: 'Access violation' (2)
This issue was traced to Unix file permissions. The DTB file permissions were:
-rwx------
meaning only the file owner could access the file. Since the macOS TFTP daemon runs as a different system user, it could not read the DTB.
This was fixed using:
chmod 644 ~/tftpboot/am335x-boneblack.dtb
which changed the permissions to:
-rw-r--r--
allowing the TFTP daemon to read the file successfully.
After this, the DTB transfer succeeded.
Finally, the complete successful RTEMS network boot sequence became:
setenv ethact ethernet@4a100000
setenv ipaddr 192.168.5.4
setenv serverip 192.168.5.1
tftpboot 0x80800000 rtems-app.img
tftpboot 0x88000000 am335x-boneblack.dtb
bootm 0x80800000 - 0x88000000
This successfully booted RTEMS directly from RAM over Ethernet.
Through this work, the following concepts and technologies were explored and successfully applied:
UART serial debugging
U-Boot shell interaction
U-Boot environment variables
ARM boot flow
bootm
TFTP networking
Ethernet PHY negotiation
RJ45 link debugging
macOS TFTP server configuration
Unix symbolic links
Unix file permissions and chmod
RAM-based executable loading
Device Tree Blob handling
RTEMS BSP boot workflow
Network-based embedded system bring-up
This workflow now provides a fast iterative RTEMS development environment suitable for future RTEMS CAN driver and BSP development work.
May 26, 27
Initial D-CAN Address Read
Goal
The goal of this experiment was to verify that an RTEMS application running on the BeagleBone Black could successfully access the AM335x D-CAN peripheral base addresses through MMIO-based register access.
Background
The AM335x SoC maps hardware peripherals into memory address space.
The following D-CAN base addresses were used:
DCAN0: 0x481CC000
DCAN1: 0x481D0000
These addresses were obtained from:
soc_AM335x.h
and verified against the AM335x TRM memory map.
Implementation
A simple RTEMS application was created which:
- booted through U-Boot and TFTP
- entered the RTEMS Init task
- called
dcan_reg_test() - printed the D-CAN peripheral base addresses
Relevant code:
printf("DCAN0 base: 0x%08lx\n",
(unsigned long) SOC_DCAN_0_REGS);
printf("DCAN1 base: 0x%08lx\n",
(unsigned long) SOC_DCAN_1_REGS);
Result
The UART output showed:
DCAN0 base: 0x481cc000
DCAN1 base: 0x481d0000
This confirmed:
- RTEMS boot succeeded
- the Init task executed correctly
soc_AM335x.hwas included correctly- D-CAN peripheral addresses matched the TRM
Significance
This experiment established the first successful interaction between the RTEMS application and the AM335x D-CAN hardware address space.
This forms the foundation for future:
- clock enable
- register access
- D-CAN initialization
- message object configuration
- CAN TX/RX testing
DCAN1 Clock Enable Test
Goal
The goal of this experiment was to enable the DCAN1 module clock through the AM335x PRCM subsystem and verify successful access to D-CAN control and status registers.
Background
Modern SoCs use clock gating to reduce power consumption.
Peripheral modules such as:
- UART
- SPI
- I2C
- CAN
are often disabled by default until their clocks are explicitly enabled.
Without enabling the peripheral clock, MMIO register access may fail and generate bus exceptions.
The DCAN1 clock control register is:
CM_PER_DCAN1_CLKCTRL
located inside the PRCM clock module.
Implementation
The following register write was used to enable the DCAN1 module clock:
REG32(SOC_CM_PER_REGS + CM_PER_DCAN1_CLKCTRL) = 0x2;
where:
0x2 = MODULEMODE_ENABLE
After enabling the clock, the application read:
- DCAN1 CTL register
- DCAN1 ES register
through MMIO access.
Relevant code:
printf("DCAN1 CTL: 0x%08lx\n",
(unsigned long) REG32(SOC_DCAN_1_REGS + DCAN_CTL));
printf("DCAN1 ES: 0x%08lx\n",
(unsigned long) REG32(SOC_DCAN_1_REGS + DCAN_ES));
Result
The UART output showed:
CM_PER_DCAN1_CLKCTRL: 0x00030002
DCAN1 CTL: 0x00001401
DCAN1 ES: 0x00000207
This confirmed:
- DCAN1 clock enable succeeded
- PRCM register access worked correctly
- DCAN1 MMIO registers could be read successfully
- no RTEMS exception occurred
TRM References
Relevant AM335x TRM sections:
- Memory Map
- PRCM / Clock Management
- CM_PER_DCAN1_CLKCTRL
- DCAN Control Register (CTL)
- DCAN Error and Status Register (ES)
Significance
This experiment established successful interaction between:
RTEMS
→ PRCM clock subsystem
→ DCAN1 hardware
→ MMIO register interface
This forms the basis for future:
- DCAN initialization mode control
- bit timing configuration
- interface register usage
- message object configuration
- CAN frame TX/RX
Overview
This phase of the project focused on bringing up the AM335x D-CAN controller on the BeagleBone Black running RTEMS.
The primary objective was not yet to transmit CAN frames, but rather to establish and verify the complete software-to-hardware interaction path required for future driver development.
Starting from a minimal RTEMS application, I progressively verified:
RTEMS Application
↓
ARM CPU Execution
↓
Memory-Mapped Register Access
↓
DCAN Controller Configuration
↓
Interface Registers (IF1)
↓
Message RAM
↓
Message Object Configuration
During this process, I gained practical experience with several fundamental embedded systems concepts, including:
Memory-mapped I/O
Address spaces and register offsets
Clock gating
Synchronous digital logic
D Flip-Flops
CAN bit timing
Interface registers
Message RAM architecture
RTEMS application deployment
One of the most important realizations was developing an intuitive understanding of how software actually reaches and executes on embedded hardware.
Before this project, I understood that RTEMS applications could be compiled into executable images, but I did not have a concrete mental picture of how software traveled from source code to a running application on the BeagleBone Black.
Through repeatedly building, deploying, and testing the application, I established the following mental model:
Source Code
↓
RTEMS Build System
↓
rtems-app.img
↓
TFTP Transfer
↓
U-Boot Bootloader
↓
BeagleBone Black
↓
ARM Cortex-A8 Processor
↓
Memory-Mapped Registers
↓
Hardware Peripheral (DCAN)
This understanding significantly reduced the abstraction barrier between software and hardware and provided a strong foundation for future RTEMS driver development.
The work completed in this phase successfully verified:
✓ RTEMS application execution on BBB
✓ DCAN base address access
✓ DCAN clock enable
✓ Controller register access
✓ Initialization/configuration mode
✓ Bit Timing Register (BTR) access
✓ TX Message Object configuration
✓ IF1 → Message RAM transfer
✓ Message RAM → IF1 readback transfer
✓ Message Object verification
The next stage of development will focus on actual CAN communication by requesting frame transmission, connecting external CAN hardware, and integrating the implementation into the RTEMS CAN driver framework.
A Key Realization
One of my biggest realizations was that RTEMS development is not just reading source code.
At the beginning of this project, I understood that RTEMS applications could be compiled into executable images, but I did not have a concrete mental picture of how software actually reached the BeagleBone Black and interacted with hardware.
Through this workflow:
Source Code
↓
RTEMS Build
↓
RTEMS Application Image
↓
TFTP Transfer
↓
U-Boot
↓
BeagleBone Black
↓
ARM CPU Execution
↓
Hardware Registers
I finally developed an intuitive understanding that:
The code I write on my Mac can actually run on the ARM processor inside the BeagleBone Black and directly control hardware peripherals.
This was an important mental breakthrough.
Understanding Memory-Mapped Hardware
The first step was learning that hardware peripherals appear as memory addresses.
For example:
DCAN0 = 0x481CC000
DCAN1 = 0x481D0000
These are not ordinary variables.
Instead:
Specific memory addresses
↔
Specific hardware registers
Reading:
REG32(SOC_DCAN_1_REGS + DCAN_CTL)
means:
Read the DCAN Control Register directly from hardware.
This was my first successful hardware register access on the BBB.
Understanding Addressing
Another important concept was understanding memory addresses.
Initially I confused:
Address increments
with:
Bit-width increments
I learned that:
Addresses are byte-addressable.
For example:
0x1000
0x1001
0x1002
0x1003
represent:
4 consecutive bytes
not:
4 bits
A 32-bit register occupies:
4 bytes
therefore:
Register N
→ address + 4
→ Register N+1
This explained why many DCAN registers appear at offsets:
0x0
0x4
0x8
0xC
inside the TRM.
A related realization was that:
Hexadecimal digits describe numbers.
Addresses describe locations.
Address changes are not related to the number of bits displayed in hexadecimal notation.
Understanding Clock Enable
Another important concept was understanding why peripherals require clocks.
Initially I thought:
Registers always exist.
which is true physically.
However:
The internal logic cannot operate without a clock.
The clock acts like a heartbeat for digital logic.
Most internal state updates occur on clock edges.
Without a clock:
State machines stop
Counters stop
Register updates stop
Peripheral logic stops
Therefore the first requirement before accessing DCAN was:
Enable DCAN1 clock
through:
CM_PER_DCAN1_CLKCTRL
After enabling the clock, register access became reliable.
Understanding Synchronous Digital Logic
While studying clock behavior, I also learned a fundamental digital design principle:
Most digital systems are synchronous.
A D Flip-Flop behaves as:
Clock Rising Edge
↓
Sample Input D
↓
Update Output Q
This means the entire hardware system updates state in a coordinated way.
This concept helped me better understand:
Registers
State Machines
Microcontrollers
Peripherals
at the hardware level.
Reading Controller Registers
After enabling the clock, I successfully accessed:
DCAN_CTL
DCAN_ES
and verified:
RTEMS
→ Memory Mapped I/O
→ DCAN Hardware
was working correctly.
Observed values:
DCAN1 CTL = 0x00001401
DCAN1 ES = 0x00000207
This was the first successful interaction with live DCAN hardware registers.
Understanding CAN Bit Timing
One concept I learned was that a CAN bit is not simply:
0
or
1
Instead, each CAN bit is divided into multiple:
Time Quanta (TQ)
A CAN bit consists of:
SYNC_SEG
PROP_SEG
PHASE_SEG1
PHASE_SEG2
These segments allow CAN controllers on different nodes to synchronize themselves and tolerate clock differences.
This timing is configured through:
BTR
(Bit Timing Register)
which controls:
BRP
SJW
TSEG1
TSEG2
and ultimately determines the CAN baud rate.
This was my first exposure to the fact that communication timing is configured at the hardware level rather than being handled automatically by software.
Controller Initialization
Following the AM335x TRM procedure:
- Set INIT
- Set CCE
- Configure BTR
- Clear INIT and CCE
I successfully moved the controller into and out of configuration mode.
Observed values:
Before:
CTL = 0x00001401
After entering config mode:
CTL = 0x00001441
After leaving config mode:
CTL = 0x00001400
This verified:
Controller configuration path works.
Understanding Message Objects
The most important new concept was learning the Message RAM architecture.
Unlike normal memory:
CPU cannot directly access Message RAM.
Instead, DCAN uses:
IF1
IF2
Interface Registers.
The data flow is:
CPU
↓
IF1
↓
Message RAM
↓
Message Object
This was a completely new hardware architecture for me.
Understanding Acceptance Masks
I also learned the purpose of message masks.
A mask determines:
Which CAN identifiers should match.
For example:
Mask = all ones
means:
Exact identifier matching.
While:
Mask = some zeros
allows a range of identifiers to match.
For the initial TX object experiment:
Mask = 0
because transmit objects do not require acceptance filtering.
Configuring a TX Message Object
I configured:
Message Object 1
with:
ID = 0x123
DLC = 8
Data:
11 22 33 44
55 66 77 88
using:
IF1MSK
IF1ARB
IF1MCTL
IF1DATA
IF1DATB
IF1CMD
The IF1 command register then transferred these values into Message Object 1.
This established the path:
CPU
→ IF1
→ Message RAM
→ Message Object 1
Reading Back Message Object Data
To verify the configuration, I performed a readback operation.
The path was:
Message Object 1
↓
IF1
↓
CPU
The readback values matched the original values exactly:
IF1ARB = 0xA48C0000
IF1MCTL = 0x00000088
IF1DATA = 0x44332211
IF1DATB = 0x88776655
Decoded ID = 0x123
Decoded DLC = 8
This confirmed:
CPU ↔ IF1 ↔ Message RAM
communication was functioning correctly.
Current Status
The following functionality has been verified:
✓ RTEMS boot on BBB
✓ TFTP deployment workflow
✓ DCAN base address access
✓ Clock enable
✓ Controller register access
✓ INIT / CCE configuration
✓ BTR access
✓ Message Object configuration
✓ IF1 write transfer
✓ IF1 readback transfer
✓ Message RAM verification
Lessons Learned
Through this work I gained a much clearer understanding of:
Memory-mapped I/O
Hardware registers
Clock gating
Synchronous digital logic
D Flip-Flops
CAN bit timing
Message RAM architecture
Interface registers
Embedded software deployment
Most importantly, I now have a concrete understanding that:
RTEMS application
↓
ARM processor
↓
Memory mapped registers
↓
Real hardware behavior
This bridge between software and hardware was one of the most valuable lessons of the project so far.
Next Steps
The next milestone is:
Set TXRQST
↓
Request frame transmission
↓
Connect CAN transceiver
↓
Receive frame using Linux SocketCAN
↓
Implement RTEMS DCAN driver
This will move the project from controller configuration into actual CAN communication.
Week 2: CAN Transmission Bring-up and Hardware Validation
Period: June 1 – June 5, 2026
Overview
Last week I focused on learning Message Object configuration, this week will toward test CAN transmission bring-up on the AM335x D-CAN controller.
I configuring and reading back a transmit Message Object, I began testing the transmission path. Although transmission requests could be issued, the TXRQ register still return 0. Maybe the register address is wrong. To move forware, this week will focus on hardware test. To isolate the problem, I validated the hardware path under Linux, determined the correct controller clock frequency, and updated the CAN bit timing configuration.
Request Transmission for Configured Message Object
After successfully configuring and reading back Message Object 1, I added a transmit request through the IF1 command interface.
The test:
- Left DCAN initialization mode
- Issued a TX request
- Read TXRQ and ES status registers
Although no CAN frame was observed on the bus, this step exercised the transmission request path and provided useful debug information.
This established that Message Object configuration alone is not sufficient for successful CAN communication and that further investigation of the controller and hardware setup was required.
Validate CAN Hardware Path
To determine whether the issue was caused by hardware or software, I validated the complete CAN hardware path using Linux.
Test setup:
BBB Linux
↓
DCAN1
↓
CAN Transceiver
↓
CAN Bus
↓
CANable Adapter
↓
Linux SocketCAN
Read System Clock Frequency
To correctly configure CAN bit timing, I first needed to determine the controller input clock frequency.
I read the AM335x CONTROL_STATUS register and decoded the SYSBOOT configuration bits.
Result:
CONTROL_STATUS = 0x0040033c
System Clock = 24 MHz
This information provided the basis for calculating the correct CAN bitrate configuration.
Update Bit Timing
Using the confirmed 24 MHz clock, I calculated a new BTR value for 125 kbps operation.
Configuration:
BRP = 24
TSEG1 = 6
TSEG2 = 1
Result:
Bitrate = 125000 bit/s
BTR = 0x00000517
This configuration matches the bitrate used during the Linux SocketCAN hardware validation.
CAN Reception Validation
Implemented a receive Message Object and configured it to accept standard CAN frames.
Receive path:
UTM Linux
↓
CANable USB Adapter
↓
CAN Bus
↓
CAN Transceiver
↓
AM335x DCAN1
↓
Message Object 2
↓
RTEMS
After sending
cansend can0 123#1122334455667788
RTEMS successfully decoded:
ID: 0x123
DLC: 8
Data:
11 22 33 44 55 66 77 88
This verified that the complete RX data path is functional.
Receive Path Debugging
During debugging, an interesting behavior was observed.
Initially, polling the NWDAT register never indicated that new data had arrived:
NWDAT = 0
However, the controller status register showed:
RXOK = 1
Using RXOK as the trigger to force a Message Object read successfully retrieved the received frame.
This demonstrated that:
- CAN controller successfully received the frame
- Message Object 2 correctly stored the received data
- IF register transfer and frame decoding work correctly
The remaining investigation is understanding why NWDAT is not behaving as expected in the current implementation.
Driver Analysis
Compared the current implementation with the previous D-CAN driver.
Studied:
dcan_init_rxobj()dcan_read_obj()dcan_save_msg()dcan_isr()
Key observations:
- Previous driver uses IF2 for receive operations.
- Receive events are primarily detected through
DCAN_INTand interrupt handling rather than pollingNWDAT. - Received frames are converted into
struct can_msgand passed to the RTEMS CAN stack.
This provides a clear direction for future driver integration.
Hardware and Environment Notes
During testing, an environment issue was identified.
Stable communication requires:
Start UTM Linux
↓
Attach CANable USB device
↓
Bring up SocketCAN interface
↓
Boot RTEMS BBB application
Following this sequence ensures reliable CAN communication between RTEMS and Linux.
Summary
This week achieved a major milestone:
- Validated the complete CAN hardware path using Linux SocketCAN
- CAN transmit path verified
- CAN receive path verified
- Message Object 2 successfully stores and returns CAN frames
- End-to-end communication between RTEMS BBB and UTM Linux validated
- Began studying the previous D-CAN driver architecture for RTEMS CAN stack integration
The next step is to understand the interrupt-driven receive flow (DCAN_INT, INTPND, and NWDAT) and gradually refactor the register-level implementation into a clean RTEMS CAN driver.
Linux SocketCAN Hardware Validation for AM335x DCAN1 (BeagleBone Black Linux → UTM Linux via CANable)
Objective
Before continuing RTEMS D-CAN driver bring-up, validate that the external CAN hardware path is functioning correctly.
The goal is to determine whether communication issues originate from hardware or from the RTEMS software stack.
Test Environment
Transmitter
- BeagleBone Black
- Debian Linux
- AM335x DCAN1 controller
- External CAN transceiver
Receiver
- UTM Virtual Machine
- Debian Linux
- CANable USB-to-CAN adapter
- Linux SocketCAN tools
Hardware Setup
BeagleBone Black
DCAN1 interface:
P9.24 -> DCAN1_TX
P9.26 -> DCAN1_RX
Connected to an external CAN transceiver.
CANable USB-to-CAN Adapter
Connections:
CANH -> CANH
CANL -> CANL
GND -> GND
Bus termination:
120Ω resistor at one end
120Ω resistor at the other end
CANable termination switch:
OFF
Validation Topology
BBB Linux
↓
AM335x DCAN1
↓
CAN Transceiver
↓
CAN Bus
↓
CANable USB-to-CAN Adapter
↓
UTM Linux VM
↓
Linux SocketCAN
↓
candump
Connecting CANable to UTM Linux
Initially, the CANable device did not appear inside the Linux virtual machine.
Checking available interfaces:
ip link
No CAN interface was present.
Root Cause
The USB device was attached to macOS instead of the UTM virtual machine.
Solution
In UTM:
UTM
└─ USB Devices
└─ Connect
└─ canable gs_usb
After attaching the device to the VM:
ip link
Result:
can0
The CANable adapter was successfully detected by Linux.
Configure BBB Linux
Configure pin multiplexing:
config-pin p9.24 can
config-pin p9.26 can
Enable CAN interface:
sudo ip link set can1 up type can bitrate 125000
Verify:
ip -details link show can1
Configure UTM Linux SocketCAN
Bring up the CANable interface:
sudo ip link set can0 up type can bitrate 125000
Verify:
ip -details link show can0
Monitor CAN Traffic
Start monitoring CAN frames:
candump can0
The terminal waits for incoming frames.
Transmit Test Frame
From BBB Linux:
cansend can1 123#1122334455667788
Frame contents:
ID = 0x123
DLC = 8
11 22 33 44 55 66 77 88
Observed Result
UTM Linux successfully received:
can0 123 [8] 11 22 33 44 55 66 77 88
Validation Outcome
The successful transmission confirms:
- AM335x DCAN1 controller is operational
- External CAN transceiver is operational
- CAN bus wiring is correct
- Bus termination is correct
- CANable adapter is operational
- UTM USB passthrough is functioning correctly
- Linux SocketCAN configuration is correct
- 125 kbps communication is working
Engineering Conclusion
Because Linux successfully transmitted and received CAN frames through the same hardware path, the physical CAN network was verified to be working correctly.
This allowed subsequent debugging efforts to focus exclusively on the RTEMS software bring-up path rather than hardware issues.
The validation significantly reduced the debugging scope and ultimately helped identify a missing DCAN1 pin multiplexing configuration in the RTEMS test application.
Key Lesson
When performing embedded driver bring-up, validating the hardware path independently can save significant debugging time.
By proving that the CAN controller, transceiver, bus, CANable adapter, UTM USB passthrough, and SocketCAN tools all work correctly under Linux, remaining failures can be isolated to software configuration issues rather than hardware faults.
Functional Driver Bring-up: From Register Access to CAN Frame Communication on AM335x D-CAN
Overview
Driver bring-up is often an iterative process.
Instead of implementing a complete driver immediately, I gradually verified every layer of the hardware and software stack until a complete CAN communication path was established.
This post summarizes the bring-up methodology used for the AM335x D-CAN controller on BeagleBone Black.
Step 1. Verify Register Access
The first goal is not sending CAN frames.
It is simply verifying that the CPU can access the controller registers.
Example:
printf("DCAN1 base: 0x%08lx\n", SOC_DCAN_1_REGS);
printf("CTL = 0x%08lx\n", REG32(SOC_DCAN_1_REGS + DCAN_CTL));
If register values can be read consistently, the MMIO mapping is working.
Step 2. Enable Peripheral Clock
Many peripherals are inaccessible until their clocks are enabled.
Configure:
CM_PER_DCAN1_CLKCTRL
Verify:
MODULEMODE = ENABLE
IDLEST = Functional
Only after this step can the controller operate normally.
Step 3. Configure Pin Multiplexing
Even if the controller is initialized correctly, communication will fail if the pins are still configured as UART or GPIO.
Configure:
P9.24 -> DCAN1_TX
P9.26 -> DCAN1_RX
Verify pad configuration registers.
Step 4. Enter Initialization Mode
Before modifying controller configuration:
INIT = 1
CCE = 1
This allows:
- Bit timing configuration
- Message Object configuration
without active CAN communication.
Step 5. Configure Bit Timing
Determine:
- Controller input clock
- Desired bitrate
- BRP
- TSEG1
- TSEG2
Verify:
24 MHz
↓
125 kbps
↓
BTR = 0x00000517
Step 6. Configure TX Message Object
Configure:
- Message ID
- DLC
- Data
- MSGVAL
- DIR
Transfer configuration through IF registers into Message Object memory.
Issue:
TX Request
Step 7. Validate Hardware Independently
Before debugging RTEMS software, validate the physical CAN path using Linux.
Topology:
BBB Linux
↓
DCAN1
↓
CAN Transceiver
↓
CAN Bus
↓
CANable
↓
UTM Linux SocketCAN
Successfully receiving frames with candump confirms:
- Controller
- Transceiver
- Wiring
- Termination
- USB adapter
- SocketCAN
are all functioning correctly.
Step 8. Validate RTEMS Transmission
Switch to RTEMS.
Topology:
RTEMS BBB
↓
DCAN1
↓
CAN Bus
↓
CANable
↓
UTM Linux
Verify:
candump can0
Frame received:
123 [8] 11 22 33 44 55 66 77 88
This proves the TX path is functional.
Step 9. Configure RX Message Object
Create a receive Message Object.
Configure:
- MSGVAL
- RXIE
- UMASK
- ID filtering
Transfer configuration into Message Object memory.
Verify configuration by reading it back.
Step 10. Validate RTEMS Reception
Send from Linux:
cansend can0 123#1122334455667788
Initially:
NWDAT = 0
but
RXOK = 1
Using RXOK as the trigger to force a Message Object read successfully returned:
ID = 0x123
DLC = 8
Data:
11 22 33 44 55 66 77 88
This confirmed that:
- CAN controller received the frame
- Message Object stored the frame correctly
- IF register transfer works
- Frame decoding works
Step 11. Compare with Previous Driver
Study the previous implementation:
dcan_init_rxobj()
dcan_read_obj()
dcan_save_msg()
dcan_isr()
instead of immediately integrating into the RTEMS CAN stack.
Understanding the receive flow first makes future integration much easier.
Bring-up Philosophy
The bring-up process can be summarized as:
Register Access
↓
Clock
↓
Pinmux
↓
Bit Timing
↓
TX Message Object
↓
Hardware Validation
↓
TX Functional
↓
RX Message Object
↓
RX Functional
↓
Interrupt Handling
↓
RTEMS CAN Stack Integration
Each layer builds upon the previous one.
If one layer is verified independently, debugging becomes significantly easier.
Lessons Learned
A functional driver should not be developed by implementing everything at once.
Instead:
- verify hardware first,
- validate each register operation,
- prove TX independently,
- prove RX independently,
- then integrate with the operating system framework.
This incremental bring-up methodology greatly reduces debugging complexity and provides clear checkpoints throughout driver development.
What Is a Driver? A Beginner's Journey Through an RTEMS BeagleBone Black D-CAN Driver
Overview
I got my master's degree in EE but I had never written a driver and the "driver" was a mysterious word to me. Thanks to the RTEMS project and mentors' guidance, I brought up a minimum driver that can successfully send and receive can frames between BBB and Linux. This blog is not a tutorial from an expert. It is simply a beginner's notes, recording how I gradually understood what a driver is and how to write one. Hopefully it can help another beginner get started more easily.
About project. RTEMS has a state-of-art CAN stack, but it doesn't support the hardware BeagleBone Black(BBB) yet. My goal is to write a driver that enables RTEMS to use the DCAN controller in BBB.
What is a driver?
I use a analogy to help me understand "driver". Driver is a bridge between operating system and hardware. With a driver, the commands from operating system can be delivered to hardware and be executed. If the driver works, software and hardware can communicate. Then next question is how to write a driver?
How to write a driver?
Since a driver is the bridge of software and hardware, it needs to communicate with both sftware--RTESM, and hardware--BeagleBone Black DCAN controller.
That said the driver needs to have RTESM API and the register address.
For the software side, with RTEMS, we need to know how to use the CAN API, RTEMS CAN Driver link.
This is the part I am going to work on next and will update later.
For the hardware side, we need to understand how the CAN controller works, which means to read the Technical Reference Manual, to understand the logic of the hardware. In the driver, we will map the address of the register and writing codes to read or write values to these registers, and then achieve the function of driver.
Mindset: Don't Prepare Forever
At the beginning, I wanted to read the entire Technical Reference Manual (TRM). I wanted to understand every register, every CAN protocol detail, every RTEMS interface...and then start writing code. But it took several days and I was still at the same place and felt pressure.
Fortunately, my mentors kept encouraging me to write code first. Looking back, that was absolutely the right decision. I learned much more by making one register work than by reading one hundred pages.
My experience became:
Read a little - Write a little - Debug a little - Read again
instead of
Read everything - Try to understand everything - Finally start coding
Driver development is highly practical. Theory and practice should move together.
Before Writing a Driver, Make Hardware Work
One lesson saved me a huge amount of debugging time. Before blaming the software, verify the hardware first, and used candump cansend to verify Linux-to-Linux communication.
For my project, I connected
BBB Linux - AM335x DCAN1 - CAN Transceiver - CAN Bus - USB-to-CAN Adapter - UTM Linux SocketCAN
If Linux can successfully send and receive CAN frames, then I know:
- CAN controller works
- CAN transceiver works
- CAN bus wiring is correct
- Bus termination is correct
- USB CAN adapter works
- SocketCAN configuration works
At that point, the hardware becomes a pair of swimming floats. I can learn and grow with a solid hardware foundation. The debugging scope becomes much smaller.
BBB TFTP Ethernet Boot
I first tried to flash SD card to boot on BBB. It works, but it is just very slow. Then learned from mentors that TFTP boot is a more efficient way.
I built a TFTP connection between BBB and my computer through a Ethernet cable. I used TFTP to transfer the image file from my computer to BBB to test.Booting via Network
UART serial
The results on BBB needs to be seen. One way is connecting a monitor. Another is use a UART serial device.
I used a UART serial to connect with BBB and then my computer. On my mac computer in a terminal I used the following command
screen /dev/tty.usbserial-A5069RR4 115200
to see the output on BBB.
CAN transceiver
BBB doesn't have a transceiver, so we need a external transceiver.
USB CAN adapter
The adaptor allows the CAN frame to be sent to computer. Then UTM Linux in my Mac can receive the CAN frame.
CAN Bus
I used a bread board as CAN Bus. Make sure there are 2 120Ω-ish resistors at both ends of the CAN Bus.
Driver Bring-up Process
My bring-up process looks like this:
Register access - Clock enable - Pinmux configuration - Bit timing configuration -
TX Message Object - Hardware validation - debug - TX success -
RX Message Object - debug - RX success -
Interrupt pending - RTEMS CAN stack integration
At first I thought I was writing a driver. Actually I was solving one very small problem after another.
Registers Became Less Scary
At first, seeing names like these felt overwhelming.
DCAN_CTL
DCAN_ES
DCAN_BTR
DCAN_IFARB
DCAN_IFMCTL
After debugging for a while, I realized: Registers are simply variables inside hardware.
Reading DCAN_ES is similar to reading a C variable.
Writing DCAN_BTR = value is similar to assigning a value.
The only difference is that these variables live inside hardware instead of RAM.
And when I don't know what is the meaning of the register, it is the time to go back to learn TRM. All registers information are in TRM.
TX and RX Are Independent Milestones
TX
The first successful transmission was exciting.
RTEMS BBB -> CAN Bus -> UTM Linux
Linux successfully received
123 [8] 11 22 33 44 55 66 77 88
RX
UTM Linux -> CAN Bus -> RTEMS BBB
RX debugging takes longer time. I found NWDAT register value never changed, but during the test, RXOK worked, which means a new frame come to the CAN Bus. So I use RXOK as a flag to read the value from message object RAM and in polling mode it worked. Then I tried to read the value of messagae object based on interrupt, which is a normal way in a driver.
Finally I switched to polling the DCAN_INT register and successfully detected:
DCAN_INT = 0x00000002
which means Message Object 2 generated an interrupt.
Reading Message Object 2 produced
ID: 0x123
DLC: 8
Data:
11 22 33 44 55 66 77 88
Every debugging step improved my understanding of how the controller actually works.
Ask mentors for resources
For the project of writing a driver, usually it is not a new thing in this world. Maybe just only new to beginner. So one thing I learned is, there are many resources related the work I am going to do. Do not think you are all alone and build all the things from scratch. There are lots of resources and support. If you cannot find the information, ask your mentors. I really appreciate my mentors for providing me many useful resources that helped me have a good start.
Learn by Building
One thing surprised me. Many concepts that looked difficult on paper became very simple after implementation.
For example, before:
Message Object
Interrupt
RXOK
DCAN_INT
looked complicated. After implementation knowledge became much more intuitive.
CAN frame arrives -
Message Object stores frame -
DCAN_INT points to Message Object 2 -
Driver reads Message Object -
Software gets CAN frame
My Biggest Lesson
I used to think writing a driver meant writing thousands of lines of complicated code. Now I think writing a driver is mostly about building step by step. Read one register, enable one clock, configure one Message Object, send one CAN frame, receive one CAN frame. One small success leads to another. Eventually, the minimum driver starts working.
Next Steps
The current driver can already transmit and receive CAN frames. The next goals are:
- understand interrupt flow,
- implement ISR and worker thread,
- convert hardware frames into software structures,
- integrate with the RTEMS CAN stack.
The driver is still under development. But the biggest change is through step by step development, the word "driver" is no longer mysterious.
Final Thoughts
As beginners, we often feel we need to understand everything before starting. My experience is exactly the opposite.
Start building. Read when necessary. Debug patiently. Repeat.
Driver development is not about knowing everything. It is about solving one small problem after another until the hardware finally responds. And when the first CAN frame appears on the screen, all those registers suddenly become much friendlier.
week_3
RTEMS AM335x D-CAN Driver Development (Weekly Progress)
Overview
This week, the focus shifted from register-level bring-up to driver architecture refactoring. Instead of simply verifying that the AM335x D-CAN controller could transmit and receive CAN frames, I gradually reorganized the implementation to follow the RTEMS CAN Framework design by studying the SJA1000 driver.
The primary goal was:
Transform a standalone D-CAN register test into a reusable RTEMS-style CAN driver skeleton.
1. Refactoring Register Test into Driver Structure
Originally, all initialization, register access, polling, and frame processing were implemented inside a single test routine.
The code was reorganized into multiple driver-oriented modules:
am335x_dcan_init_125k()
↓
am335x_dcan_process_interrupts()
↓
am335x_dcan_get_rx_frame()
↓
am335x_dcan_worker_like()
↓
dcan1_process_received_frame()
This modular structure is much closer to the organization of the official RTEMS CAN drivers.
2. Migration to RTEMS struct can_frame
The original implementation used a custom frame structure for received CAN messages.
During this week, the implementation was migrated to:
struct can_frame
which is the standard frame representation used by the RTEMS CAN framework.
The receive path now becomes:
Linux CAN Frame
↓
DCAN Message Object
↓
struct can_frame
↓
Driver Internal State
↓
Worker-like Processing
↓
Upper Layer
Using the RTEMS data structure simplifies future framework integration and aligns the driver with the existing RTEMS CAN stack.
3. Driver Internal State
A dedicated driver private structure was introduced:
struct am335x_dcan_internal
which currently stores:
- received CAN frame
- frame_to_pass flag
- RX callback
- callback argument
- worker semaphore
- RTEMS CAN chip pointer
instead of keeping these variables scattered throughout the implementation.
This organization follows the design philosophy of
struct sja1000_internal
from the official RTEMS SJA1000 driver.
4. Worker-like Processing Model
A polling-based worker was implemented:
worker_like()
↓
process_interrupts()
↓
get_rx_frame()
↓
process_received_frame()
instead of directly processing received frames inside the test application.
The worker continuously polls the controller interrupt register and dispatches received frames after they are converted into RTEMS CAN frames.
Although still polling-based, this design closely resembles the worker model used by the official RTEMS CAN drivers.
5. Callback Dispatch Layer
A simple callback mechanism was introduced:
am335x_dcan_set_rx_callback()
allowing upper-layer code to register a receive handler.
Current receive flow:
worker_like()
↓
process_interrupts()
↓
internal frame
↓
callback(frame)
↓
test application prints frame
instead of
driver
↓
direct printf()
This callback layer serves as a temporary upper-layer interface and will eventually be replaced by the RTEMS CAN queue system.
6. Initial RTEMS Driver Skeleton
An initial
rtems_can_am335x_dcan_initialize()
function was implemented.
Current responsibilities include:
allocate internal structure
↓
allocate rtems_can_chip
↓
allocate qends_dev
↓
chip->internal = internal
↓
internal->chip = chip
↓
return initialized chip
This implementation is modeled after
rtems_can_sja1000_initialize()
and represents the first step toward integrating the AM335x D-CAN driver into the RTEMS CAN framework.
7. Hardware Validation
The complete receive path has been successfully verified:
Linux
↓
cansend can0 123#1122334455667788
↓
CAN Bus
↓
AM335x DCAN Message Object 2
↓
worker_like()
↓
process_interrupts()
↓
struct can_frame
↓
callback()
↓
RTEMS prints:
RX ID=0x123
DLC=8
DATA=11 22 33 44 55 66 77 88
This confirms that the refactoring preserved the original hardware functionality while introducing a cleaner driver architecture.
8. Comparison with SJA1000 Driver
The development this week focused on gradually aligning the AM335x D-CAN driver with the architecture of the official RTEMS SJA1000 driver.
Current mapping:
| SJA1000 Driver | AM335x D-CAN Driver |
|---|---|
struct sja1000_internal | struct am335x_dcan_internal |
struct can_frame | struct can_frame |
worker() | worker_like() |
process_interrupts() | am335x_dcan_process_interrupts() |
initialize() | rtems_can_am335x_dcan_initialize() |
| queue dispatch | callback dispatch (temporary) |
Rather than focusing on hardware registers, the implementation is now evolving toward a framework-oriented RTEMS CAN driver.
Current Progress
Completed:
- ✅ DCAN clock and pinmux configuration
- ✅ Bit timing configuration (125 kbps)
- ✅ TX Message Object configuration
- ✅ RX Message Object configuration
- ✅ Linux → RTEMS CAN communication
- ✅ Migration to RTEMS
struct can_frame - ✅ Driver internal state organization
- ✅ Worker-like processing model
- ✅ Callback dispatch layer
- ✅ Initial RTEMS CAN driver skeleton
Next Steps
The next development stage will continue following the SJA1000 driver architecture.
Planned tasks include:
Register Wrapper
REG32(...)
↓
dcan_read_reg()
dcan_write_reg()
↓
internal->base
↓
RTEMS Queue Integration
↓
queue_filter_frame_to_edges()
↓
Application read()
↓
Full RTEMS CAN Driver
The overall development is gradually transitioning from a hardware validation project into a framework-compliant RTEMS CAN driver implementation.
week_4 from bringup to RTEMS Driver framework
Overview
This week, I continued developing the AM335x D-CAN driver on BeagleBone Black. The focus was to move from a simple register-level test toward a cleaner RTEMS CAN framework driver. Significant progress was made in initialization, bit timing support, register abstraction, interrupt handling, and worker task design.
Driver Initialization Improvements
Several improvements were made to the initialization flow:
-
Created and initialized the basic RTEMS CAN structures:
chipinternalqends_dev
-
Added worker task creation during initialization.
-
Installed the DCAN interrupt handler.
-
Cleaned up the startup sequence.
-
Improved the overall driver structure to better follow existing RTEMS CAN drivers.
RTEMS CAN Bit Timing Support
Previously, the bit timing parameters were hard-coded.
This week, the driver was converted to use the RTEMS CAN framework:
bitrate
↓
rtems_can_bitrate2bittiming()
↓
bittiming structure
↓
dcan_set_bittiming()
↓
DCAN BTR register
Completed
- Added
dcan_calc_and_set_bittiming(). - Used
rtems_can_bitrate2bittiming()to calculate CAN timing parameters. - Programmed the BTR register based on the calculated values.
- Removed hard-coded timing values.
This makes the driver more flexible and consistent with other RTEMS CAN drivers.
Register Access Cleanup
Previously, many registers were accessed directly through:
REG32(...)
These accesses were replaced with:
dcan_read_reg()
dcan_write_reg()
Benefits
- Cleaner code.
- Better encapsulation.
- Easier debugging.
- Less hardware-specific code scattered throughout the driver.
Removing Global Variables
Private driver information is now stored inside:
chip->internal
instead of using global variables.
Benefits
- Better modularity.
- Easier future expansion.
- Similar structure to SJA1000 and CTU CAN FD drivers.
Header and Register File Cleanup
Several code organization improvements were made:
- Reorganized register definitions.
- Cleaned up
dcan_regs.h. - Reduced dependency on legacy TI header files.
- Removed unnecessary include files.
- Began moving AM335x-specific definitions into local driver files.
- Simplified the code structure.
Interrupt Handling
Following the mentor's recommendation, I studied the CTU CAN FD driver instead of SJA1000 because its interrupt architecture is simpler and closer to D-CAN.
Interrupt Flow
Hardware Interrupt
↓
ISR
↓
Disable interrupt
↓
Post worker semaphore
↓
Return immediately
The interrupt service routine remains short and avoids heavy processing.
Worker Flow
Worker wakes up
↓
Process interrupt source
↓
Read message object
↓
Deliver frame
↓
Re-enable interrupt
This design separates interrupt handling from frame processing and follows the CTU CAN FD style.
Worker Task Skeleton
A real RTEMS worker task was implemented.
Worker Responsibilities
- Wait on a binary semaphore.
- Process pending interrupts.
- Read received message objects.
- Deliver frames to the RTEMS CAN queue.
- Re-enable interrupts.
This replaces the earlier polling-based "worker-like" implementation.
Receive Path Validation
Successfully verified the complete receive path:
Linux SocketCAN
↓
CAN bus
↓
BBB DCAN1
↓
Hardware interrupt
↓
ISR
↓
Worker task
↓
Read RX message object
↓
RTEMS CAN queue
↓
RX callback
Multiple CAN frames were successfully received.
Example:
RX ID=0x123 DLC=8 DATA=00 99 88 77 66 55 44 33
This confirms that the interrupt-driven receive mechanism is functioning correctly.
Current Status
Completed
- Driver initialization skeleton
- RTEMS CAN bit timing support
- Register read/write abstraction
- Remove global variables
- Register file cleanup
- Worker task skeleton
- Interrupt handler installation
- Interrupt-driven receive mechanism
- Multiple-frame reception
- Frame delivery to RTEMS CAN queue
- Debug callback verification
Architecture After This Week
Linux SocketCAN
↓
CAN bus
↓
DCAN Controller
↓
Hardware Interrupt
↓
ISR
(disable interrupt + post semaphore)
↓
Worker Task
↓
dcan_process_interrupts()
↓
Read RX message object
↓
RTEMS CAN Queue
↓
Application / Callback
Next Steps
Code Cleanup
- Remove the old polling-based
worker_like()implementation. - Continue separating AM335x-specific definitions.
- Further simplify interrupt helper functions.
RTEMS CAN Queue Study
- Study
can-queue.c. - Understand queue edges and queue slots.
- Learn how applications read frames from the queue.
Application-Level Integration
Implement a queue-based application read path:
Hardware
↓
Interrupt
↓
Worker
↓
RTEMS CAN Queue
↓
Application read()
Transmit Path
Begin TX queue integration:
Application
↓
RTEMS CAN Queue
↓
TX buffer
↓
DCAN Message Object
↓
CAN bus
Long-Term Goal
Continue refining the driver toward full RTEMS CAN framework support and eventual upstream submission.
week_5
Overview
This week I mainly worked on two parts of the D-CAN driver:
- Debugging the IF3 receive path and understanding how it works.
- Integrating the TX path into the RTEMS CAN framework worker, so transmission follows the standard RTEMS CAN architecture.
I also spent time cleaning the driver source code to prepare for the first merge request.
IF3 Debugging
Goal
My goal was to understand how the IF3 register works on the AM335x D-CAN controller.
Unlike IF1 and IF2, IF3 is designed as an automatic update interface. According to the AM335x Technical Reference Manual, IF3 can automatically copy the content of a selected Message Object into the IF3 registers.
I wanted to verify whether IF3 could become another receive path in the driver.
Initial Design
I configured two receive Message Objects.
-
Message Object 2
- ID = 0x123
- Normal interrupt receive using IF1
-
Message Object 64
- ID = 0x153
- IF3 automatic update
Then I enabled the IF3UPD register and periodically read:
- IF3ARB
- IF3MCTL
- IF3DATA
- IF3DATB
The first test was successful. IF3 could receive CAN frames correctly when tested alone.
Problem
The problem appeared after I enabled interrupts.
The IF1 interrupt receive path continued working correctly, but IF3 stopped updating.
At first, I thought the problem was related to:
- IF3 configuration
- IF3UPD register
- IF3OBS register
- DE3 bit
I spent a long time checking these registers one by one, but none of them solved the problem.
Debug Process
To better understand the hardware behavior, I added many debug messages.
I printed several registers before and after reading IF3:
- DCAN_INT
- ES
- INTPND1
- INTPND2
- NWDAT1
- NWDAT2
These debug messages helped me understand exactly what happened after each received CAN frame.
Root Cause
Finally, I found that the problem was not IF3 itself.
The real reason was that Message Object 64 was generating an interrupt.
When the interrupt occurred:
- The worker thread woke up.
- The worker processed the interrupt.
- IF1 read the Message Object.
- The interrupt pending bit and NewDat bit were cleared.
After that, IF3 no longer had a new frame to update.
In other words, IF1 and IF3 were trying to access the same Message Object, causing them to interfere with each other.
Final Solution
The solution was surprisingly simple.
I removed the RXIE bit from Message Object 64.
The final design became:
Message Object 2
- RX interrupt enabled
- Used by the RTEMS receive queue
Message Object 64
- RX interrupt disabled
- Used only for IF3 automatic update
Now the two receive paths are completely independent.
Both paths work correctly at the same time.
Another Problem
At first, IF3 could only receive one CAN frame.
The second CAN frame never appeared.
I first suspected the NewDat bit and tried clearing it manually.
However, after adding more debug messages, I realized that NewDat was not the problem.
The real issue was my polling logic.
IF3 automatically keeps the latest received frame in its registers.
My polling loop kept reading the same data repeatedly, so it looked like no new frames were arriving.
I changed the polling logic so that only newly updated data is processed.
After this change, IF3 successfully received multiple CAN frames.
What I Learned
This debugging process helped me understand the D-CAN receive architecture much better.
The most important conclusion is:
IF3 is not designed to use interrupts.
Instead, IF3 is designed for automatic register updates and is intended to work with polling or DMA.
Normal interrupt-driven receive should continue using IF1.
TX Path Integration into the RTEMS CAN Framework
Previous Implementation
Previously, the driver transmitted CAN frames directly.
The driver configured Message Object 1 and immediately requested transmission.
Although this verified the hardware, it did not follow the RTEMS CAN framework design.
New Goal
The goal was to make transmission follow the standard RTEMS CAN architecture.
The new flow is:
Application
↓
write("/dev/can0")
↓
RTEMS CAN TX Queue
↓
Worker Thread
↓
DCAN Driver
↓
Message Object 1
↓
CAN Bus
Instead of sending frames directly, the application simply writes a CAN frame into /dev/can0.
The RTEMS CAN framework stores the frame inside the TX queue.
The worker thread later retrieves one frame from the queue and sends it through the hardware.
Implementation
To implement this feature, I studied the SJA1000 driver because it is the reference CAN driver inside RTEMS.
I followed the same design.
The worker now performs the following steps:
- Check whether the hardware TX Message Object is available.
- Get one transmit slot from the RTEMS TX queue.
- Copy the CAN frame into Message Object 1.
- Request hardware transmission.
- Notify the RTEMS CAN framework after transmission is completed.
This makes the D-CAN driver consistent with the RTEMS CAN framework.
Testing
I created a simple application writer task.
The application opens:
/dev/can0
and sends a CAN frame using:
write(fd, &tx, sizeof(tx));
instead of calling hardware functions directly.
Linux SocketCAN successfully received:
ID = 0x333
DATA = 11 22 33 44 55 88 88 88
This verified that the complete software stack works correctly.
What I Learned
This work helped me understand the architecture of the RTEMS CAN framework.
The application never communicates directly with the hardware.
Instead, it only sends CAN frames into the framework queue.
The worker thread is responsible for retrieving frames from the queue and transmitting them through the hardware.
This design separates the application layer from the hardware layer and makes the driver much cleaner.
Code Cleanup
Besides implementing new features, I also started cleaning the driver source code for the first merge request.
The cleanup included:
- Removing unnecessary forward function declarations.
- Reordering functions so that most forward declarations are no longer needed.
- Deleting unused helper functions.
- Removing duplicated code.
- Cleaning old debugging comments.
- Organizing the file structure to make it easier to read.
This cleanup makes the driver closer to the coding style used in other RTEMS drivers.
Plans for Next Week
Next week I plan to:
- Continue cleaning the driver for the first merge request.
- Follow the RTEMS coding style more closely.
- Remove temporary debugging code.
- Improve TX and RX error handling.
- Continue studying whether IF3 can eventually deliver received frames into the RTEMS CAN queue using automatic update or DMA instead of interrupts.
Overall, this week I made good progress in understanding both the AM335x D-CAN hardware and the RTEMS CAN framework. The driver now supports a complete TX path through the RTEMS CAN queue, and I also gained a much deeper understanding of how IF3 works and why it should not be treated as a normal interrupt-driven receive path.
Week 6 Progress Report
1. RTEMS Main-Tree Build Integration
This week, I added the DCAN driver into the RTEMS source tree and tested it with the RTEMS build system. This is an important step for preparing the first merge request.
I added the following DCAN files:
cpukit/dev/can/dcan/dcan.c
cpukit/dev/can/dcan/dcan_regs.h
cpukit/include/dev/can/dcan.h
I also added the DCAN source and header paths into:
spec/build/cpukit/librtemscpu.yml
First, I configured RTEMS for the BeagleBone Black BSP:
./waf configure \
--prefix=$HOME/development/rtems/7 \
--rtems-tools=$HOME/development/rtems/7 \
--rtems-bsps=arm/beagleboneblack
Then, I built the RTEMS source tree and saved the build output into a log file:
./waf build 2>&1 | tee build.log
I used the following command to check whether the DCAN driver was compiled and whether it had warnings:
grep -n "dcan" build.log
During the build process, I fixed several issues, including:
- incorrect header include paths
- unused variables
- unused functions
- missing function prototypes
- unnecessary internal variables
After these modifications, the final RTEMS build completed successfully. All 1,520 build tasks completed, and the DCAN driver compiled without DCAN-specific warnings.
The final check only showed:
[1097/1520] Compiling cpukit/dev/can/dcan/dcan.c
This confirmed that the DCAN driver was successfully included in the RTEMS build system.
2. Install the Updated RTEMS Build
After the RTEMS build succeeded, I installed the updated RTEMS libraries and headers into the configured RTEMS prefix:
./waf install
The configured installation prefix was:
$HOME/development/rtems/7
This step made the newly integrated DCAN driver available to external RTEMS applications.
The flow is:
RTEMS source tree
↓
./waf build
↓
DCAN driver compiled into RTEMS
↓
./waf install
↓
Updated RTEMS headers and libraries installed
3. Test the Installed DCAN Driver from an External Application
Previously, my test application directly compiled a local copy of the DCAN driver:
source = [
'init.c',
'dcan/dcan_reg_test.c',
'dcan/dcan.c',
]
This meant that the test application was using the local dcan.c file.
To verify the DCAN driver integrated into RTEMS, I removed the local driver source from the test application build:
source = [
'init.c',
'dcan/dcan_reg_test.c',
# 'dcan/dcan.c',
]
The test application now only contains the application and test code. The DCAN driver implementation comes from the installed RTEMS libraries.
I also used the RTEMS public header:
#include <dev/can/dcan.h>
The new test flow is:
dcan_reg_test.c
↓
#include <dev/can/dcan.h>
↓
call rtems_can_dcan_initialize()
↓
link with installed RTEMS libraries
↓
use the DCAN driver integrated into RTEMS
4. Clean and Rebuild the Test Application
Because the previous application build included the local dcan.c, I cleaned the old build files first:
./waf clean
Then I rebuilt the test application:
./waf build
This generated a new RTEMS test application image using the installed RTEMS DCAN driver instead of the local driver source.
5. BeagleBone Black Hardware Test
Finally, I booted the newly built RTEMS image on the BeagleBone Black and tested both TX and RX communication.
For TX testing:
RTEMS application
↓
write("/dev/can0")
↓
RTEMS CAN TX queue
↓
DCAN worker
↓
DCAN Message Object
↓
CAN bus
↓
Linux candump
The Linux side successfully received the CAN frame sent from RTEMS.
For RX testing:
Linux cansend
↓
CAN bus
↓
DCAN RX Message Object
↓
DCAN interrupt
↓
DCAN worker
↓
RTEMS CAN RX queue
↓
read("/dev/can0")
The RTEMS application successfully received the CAN frame sent from Linux.
Final Result
The complete integration and test flow was successful:
Add DCAN driver to RTEMS source tree
↓
Add files to RTEMS build specification
↓
Configure RTEMS for BeagleBone Black
↓
Build complete RTEMS source tree
↓
Fix all DCAN-specific compiler warnings
↓
Install updated RTEMS libraries and headers
↓
Remove local dcan.c from test application
↓
Clean and rebuild test application
↓
Build new RTEMS image
↓
Boot image on BeagleBone Black
↓
Test RTEMS → Linux TX
↓
Test Linux → RTEMS RX
↓
Both TX and RX tests successful
This confirms that the DCAN driver is not only working as a standalone driver in my local test project, but is also successfully integrated into the RTEMS build system and can be used by an external RTEMS application through the RTEMS CAN framework.
RTEMS Merge Request Preparation Notes
1. Repositories
I have three repositories:
upstream = Official RTEMS repository
origin = My fork on GitLab
local = My local repository on my computer
Important idea:
Most Git work happens in local repository.
GitLab fork is mainly a remote backup and a place to create Merge Requests.
2. Relationship Between Repositories
To update my main branch:
Official RTEMS upstream/main
↓ git pull upstream main
Local main
↓ git push origin main
My fork origin/main
So local is the middle step.
This is different from my first feeling. I thought the fork was the middle step, but actually Git works mainly from the local repository.
3. Branch Purpose
main
main should stay clean.
It is only used to synchronize with official RTEMS.
Do not develop DCAN code on main.
bbb-dcan-driver
This is my development branch.
All DCAN driver code should be added here.
The Merge Request will be created from this branch.
4. Updating Main
Before starting serious work or before creating an MR:
git checkout main
git pull upstream main
git push origin main
Meaning:
Official RTEMS main
↓
Local main
↓
My fork main
After this:
upstream/main = local main = origin/main
5. Check Development Branch
Switch to the branch:
git checkout bbb-dcan-driver
Check history:
git log --oneline --decorate --graph --all --max-count=20
There are two situations.
6. Case 1 — Branch Has No Development History
Example:
main
A --- B --- C
bbb-dcan-driver
↑
C
or I see something like:
HEAD -> bbb-dcan-driver, main, origin/main, upstream/main
This means:
The branch exists,
but it is the same as main.
There are no DCAN commits yet.
What to do
No rebase is needed.
Just copy the tested DCAN driver code from my testing repository into this official fork repository.
Then:
git status
git add .
git commit -m "can/dcan: Add AM335x DCAN driver"
git push origin bbb-dcan-driver
This sends my local development branch to my fork:
Local bbb-dcan-driver
↓ git push origin bbb-dcan-driver
Fork bbb-dcan-driver
7. Case 2 — Branch Already Has Development History
Example:
main
A --- B --- C
bbb-dcan-driver
\
D --- E --- F
D, E, and F are my DCAN commits.
If official RTEMS adds new commits:
Official main
A --- B --- C --- G --- H
Then I should update main first:
git checkout main
git pull upstream main
git push origin main
Then rebase my development branch:
git checkout bbb-dcan-driver
git rebase main
After rebase:
A --- B --- C --- G --- H --- D --- E --- F
If there are conflicts:
git status
# fix conflict files manually
git add <fixed-files>
git rebase --continue
After rebase, push the branch again:
git push origin bbb-dcan-driver --force-with-lease
--force-with-lease is needed because rebase rewrites branch history.
8. Why Rebase?
Rebase moves my DCAN commits onto the newest RTEMS main.
This keeps the Merge Request clean.
The reviewers should only see my DCAN changes, not unrelated old main differences.
9. Copy Driver From Testing Repository
My current DCAN driver was developed in another testing repository.
The official RTEMS fork is clean.
Next step:
1. Switch to bbb-dcan-driver
2. Copy DCAN code into the correct RTEMS path
3. Clean the code
4. Build and test
5. Commit
6. Push branch to fork
7. Create MR
Mentor suggested the new CAN controller location should be:
bsps/shared/dev/can/dcan/
Example files:
bsps/shared/dev/can/dcan/dcan.c
bsps/shared/dev/can/dcan/dcan_regs.h
The exact header location should follow the new SJA1000 layout.
10. Push Branch to Fork
After I commit locally:
git push origin bbb-dcan-driver
Meaning:
Local bbb-dcan-driver
↓
Fork bbb-dcan-driver
Now GitLab can create a Merge Request from my fork branch.
11. Create Merge Request
The MR direction is:
Fork bbb-dcan-driver
↓ Create Merge Request
Official RTEMS main
It is NOT:
Fork bbb-dcan-driver
↓
Fork main
The source branch is my fork branch:
River/rtems:bbb-dcan-driver
The target branch is official RTEMS main:
rtems/rtos/rtems:main
So the contribution flow is:
Local branch
↓ push
Fork branch
↓ Merge Request
Official RTEMS main
12. Full Development Cycle
Official RTEMS main
↓ pull
Local main
↓ push
Fork main
Local bbb-dcan-driver
↓ commit
Local bbb-dcan-driver
↓ push
Fork bbb-dcan-driver
↓ Create Merge Request
Official RTEMS main
13. Code Cleanup Before MR
Before submitting the MR:
Remove temporary debug code
Remove unnecessary printf()
Remove commented-out code
Remove duplicated functions
Remove old fixed TX test functions
Remove old callback path if queue path works
Use timeout instead of infinite busy wait
Follow RTEMS/SJA1000 coding style
Add useful Doxygen comments
Organize function order
Keep only necessary comments
The first MR should focus on the basic DCAN driver:
clock / pinmux
bit timing
start / stop
RX interrupt path
TX queue path
worker
driver initialization
IF3 polling/debug code should probably be left for a later MR.
14. Simple Summary
main
↓
Only sync with official RTEMS.
bbb-dcan-driver
↓
Develop DCAN driver here.
If branch has no commits
↓
Copy code, commit, push. No rebase.
If branch has commits
↓
Update main, rebase branch, push with --force-with-lease.
MR direction
↓
Fork branch → Official RTEMS main.
Week 7 Report: RTEMS Integration, Installation, and First Merge Request
1. Overview
This week, I prepared the initial AM335x DCAN driver for my first RTEMS merge request.
The main goal was to move the driver from my standalone test project into the RTEMS source tree, build it as part of RTEMS, install the updated RTEMS libraries, and verify that my external BeagleBone Black test application could use the installed driver successfully.
The complete workflow was:
Develop DCAN driver
↓
Add driver to RTEMS source tree
↓
Update RTEMS build specification
↓
Build RTEMS
↓
Fix compiler warnings
↓
Check RTEMS coding style
↓
Install updated RTEMS libraries
↓
Remove local dcan.c from test application
↓
Rebuild BBB test image
↓
Run hardware TX/RX tests
↓
Commit and create Merge Request
2. Add the DCAN Driver to the RTEMS Source Tree
The driver files were added to the RTEMS source tree:
cpukit/dev/can/dcan/dcan.c
cpukit/dev/can/dcan/dcan_regs.h
cpukit/include/dev/can/dcan.h
The RTEMS build specification was updated in:
spec/build/cpukit/librtemscpu.yml
The new DCAN source and header files were added to this build specification so that the driver could be compiled as part of RTEMS.
I verified the entries with:
grep -n "dcan" spec/build/cpukit/librtemscpu.yml
3. Configure RTEMS for BeagleBone Black
RTEMS was configured for the BeagleBone Black BSP:
./waf configure \
--prefix=$HOME/development/rtems/7 \
--rtems-tools=$HOME/development/rtems/7 \
--rtems-bsps=arm/beagleboneblack
This configuration selected:
BSP: arm/beagleboneblack
Compiler: arm-rtems7-gcc
Installation prefix: $HOME/development/rtems/7
The configuration completed successfully.
4. Build the Driver as Part of RTEMS
The complete RTEMS tree was built with:
./waf build
To save the build output and search specifically for DCAN-related messages, I also used:
./waf build 2>&1 | tee build.log
grep -n "dcan" build.log
The command:
tee build.log
saved the complete terminal output into build.log.
The command:
grep -n "dcan" build.log
searched the build log for lines related to the DCAN driver.
During the first builds, several warnings were found, including:
unused variable
unused function
missing prototype
These warnings were fixed by removing unused debugging code, removing unnecessary variables, making internal functions static, and cleaning up old test functions.
After the fixes, the driver compiled successfully as part of RTEMS:
Compiling cpukit/dev/can/dcan/dcan.c
All RTEMS sample applications also linked successfully.
5. Build with -Werror
To make sure the driver did not introduce new compiler warnings, I tested the build with:
-Werror
This converts compiler warnings into errors.
The final build completed successfully:
build_arm/beagleboneblack finished successfully
This was important because the RTEMS contribution requirements state that each patch should not introduce new compiler warnings.
6. Check RTEMS Coding Style
I checked for whitespace problems with:
git diff --check
No output was produced, which means Git did not detect common whitespace problems such as:
trailing whitespace
space before tab
whitespace errors in modified lines
I also checked the RTEMS 79-character line limit:
awk 'length($0) > 79 { print FNR ":" length($0) ":" $0 }' \
cpukit/dev/can/dcan/dcan.c
The same check was used for the public header:
awk 'length($0) > 79 { print FNR ":" length($0) ":" $0 }' \
cpukit/include/dev/can/dcan.h
Long lines were reformatted according to the RTEMS coding conventions.
After the changes, the line-length check produced no output.
7. Install the Updated RTEMS Build
After the RTEMS build completed successfully, I installed the updated RTEMS libraries and headers:
./waf install
This step was important because my external BBB test application uses the installed RTEMS environment under:
$HOME/development/rtems/7
Before installation, the test application could still link against an older installed RTEMS library.
After:
./waf install
the updated RTEMS library included the new DCAN driver.
The workflow was:
RTEMS source tree
↓
./waf build
↓
new librtemscpu.a
↓
./waf install
↓
updated RTEMS installation
↓
external test application links against updated RTEMS
8. Remove the Local Driver Copy from the Test Application
Previously, my standalone BBB test application directly compiled its own local DCAN driver:
source = [
'init.c',
'dcan/dcan_reg_test.c',
'dcan/dcan.c',
]
After installing the driver through RTEMS, I removed the local dcan.c from the test application build:
source = [
'init.c',
'dcan/dcan_reg_test.c',
]
This was an important verification step.
The test application now used:
Application
↓
Installed RTEMS library
↓
DCAN driver from RTEMS source tree
↓
AM335x DCAN1 hardware
instead of:
Application
↓
Local dcan.c
↓
AM335x DCAN1 hardware
This confirmed that the driver was truly integrated into RTEMS.
9. Rebuild the External BBB Test Application
After removing the local dcan.c, I cleaned and rebuilt the external test application.
The exact commands depend on the test project configuration, but the workflow was:
./waf clean
./waf
The application linked successfully against the installed RTEMS libraries.
A new BeagleBone Black test image was generated.
10. Hardware TX/RX Verification
The new image was loaded onto the BeagleBone Black and tested with Linux SocketCAN.
The hardware setup was:
Linux SocketCAN
↕
CANable USB-CAN adapter
↕
CAN bus
↕
BeagleBone Black DCAN1
↕
RTEMS DCAN driver
The CAN bitrate was:
125 kbit/s
TX Test
The RTEMS application transmitted a CAN frame through the RTEMS CAN TX queue.
The path was:
RTEMS application
↓
RTEMS CAN TX queue
↓
DCAN driver
↓
TX Message Object
↓
DCAN1
↓
CAN bus
↓
Linux SocketCAN
Linux successfully received the RTEMS CAN frame.
RX Test
Linux sent a CAN frame using SocketCAN:
cansend can0 123#1122334455667799
The RTEMS side successfully received the frame.
The RX path was:
Linux SocketCAN
↓
CAN bus
↓
DCAN1
↓
RX Message Object
↓
Message Object interrupt
↓
RTEMS ISR
↓
worker task
↓
struct can_frame
↓
RTEMS CAN RX queue
↓
application read()
The RTEMS application printed the received frame correctly:
APP READ RX ID=0x123 DLC=8
DATA=11 22 33 44 55 66 77 99
This test confirmed that the driver still worked after being integrated into and installed from the RTEMS source tree.
11. Git Checks and Commit
Before creating the merge request, I checked the repository status:
git status --short
I also reviewed the changes:
git diff --stat
git diff --check
The driver files were staged:
git add cpukit/dev/can/dcan/dcan.c \
cpukit/dev/can/dcan/dcan_regs.h \
cpukit/include/dev/can/dcan.h \
spec/build/cpukit/librtemscpu.yml
The signed-off commit was created with:
git commit -s -m "can/dcan: Add AM335x DCAN controller driver"
The commit was checked with:
git log --oneline -1
git show --stat --oneline HEAD
12. First Merge Request
After the local commit was verified, the branch was pushed and the first RTEMS merge request was created.
The merge request was associated with RTEMS issue:
#5440
The MR description included:
Update #5440.
The merge request was initially kept as a Draft so that the implementation could receive early review and further improvements.
The first review provided several useful comments, including code changes and a possible source location change because the RTEMS CAN drivers are being reorganized.
The next step is to address the mentor and reviewer comments, test the changes in my standalone BBB environment first, and then update the same MR branch.
13. Main Achievement of Week 7
The main achievement this week was completing the full contribution workflow:
Standalone DCAN development
↓
RTEMS source integration
↓
RTEMS build
↓
warning cleanup
↓
coding style checks
↓
waf install
↓
external application linking
↓
BBB hardware TX/RX verification
↓
signed-off Git commit
↓
first RTEMS Merge Request
↓
mentor/reviewer feedback received
This was an important milestone because the DCAN driver was no longer only a standalone experimental driver. It was successfully compiled as part of RTEMS, installed into the RTEMS development environment, linked by an external application, and verified on real BeagleBone Black hardware.
Safely Testing RTEMS DCAN Driver Changes After Code Review
Introduction
After submitting my first RTEMS merge request for the AM335x DCAN driver, I received several useful review comments from my mentor.
The comments included:
- code structure changes
- coding style improvements
- removal of unnecessary test code
- changes to the driver implementation
- possible movement of the driver to a new RTEMS source location
- comparison with the Linux C_CAN/D_CAN driver architecture
At this stage, I did not want to directly modify the formal RTEMS merge request version and test every experimental change there.
Instead, I decided to first test the review changes in my standalone BeagleBone Black environment.
However, this introduced an important question:
If the DCAN driver has already been installed into RTEMS, and my standalone test project also contains a local
dcan.c, how can I make sure the correct driver version is being compiled and tested?
This blog describes the workflow I use to separate these two environments.
1. Two DCAN Driver Versions Exist
After the previous RTEMS integration work, I had two possible DCAN driver sources.
The first one was the driver integrated into the RTEMS source tree:
RTEMS source tree
└── cpukit/dev/can/dcan/dcan.c
After running:
./waf build
./waf install
this driver became part of the installed RTEMS development environment.
The second one was the local experimental driver in my standalone BBB test project:
rtems-bbb-dcan/
└── dcan/
└── dcan.c
Therefore, I needed to clearly distinguish between:
Installed RTEMS DCAN driver
and:
Local experimental DCAN driver
2. Why This Can Be Confusing
My standalone application uses RTEMS libraries.
At the same time, its wscript can also directly compile a local source file:
source = [
'init.c',
'dcan/dcan_reg_test.c',
'dcan/dcan.c',
]
This means the application build explicitly compiles:
dcan/dcan.c
from the local project.
However, the installed RTEMS libraries may also contain the previously installed DCAN implementation.
Conceptually, the environment becomes:
External BBB application
↓
local dcan/dcan.c
+
installed RTEMS libraries
↓
possible duplicate DCAN implementation
This is something that should not be ignored.
If the same global function symbols exist in both versions, the build may produce duplicate symbol errors, or the final linking behavior may depend on which object files are pulled from a static library.
Even when the build succeeds, I should not simply assume which implementation is actually being tested.
Therefore, the test workflow needs to make the selected driver source explicit.
3. Formal RTEMS Integration Mode
When I want to test the driver integrated into the RTEMS source tree, the workflow is:
Modify RTEMS source driver
↓
./waf build
↓
./waf install
↓
external application does not compile local dcan.c
↓
rebuild application
↓
test on BBB
The application wscript should not include the local driver:
source = [
'init.c',
'dcan/dcan_reg_test.c',
]
In this mode:
Application
↓
installed RTEMS libraries
↓
RTEMS-integrated DCAN driver
↓
AM335x DCAN hardware
This is the correct mode for final merge request verification.
4. Standalone Experimental Mode
When I want to quickly test mentor review changes, I prefer to modify the standalone local driver first.
The workflow is:
Copy or implement review changes
↓
modify local dcan/dcan.c
↓
compile local dcan.c explicitly
↓
build BBB image
↓
run hardware TX/RX tests
The wscript includes:
source = [
'init.c',
'dcan/dcan_reg_test.c',
'dcan/dcan.c',
]
In this mode, the local driver is explicitly compiled into the application.
Conceptually:
Application source
↓
local dcan/dcan.c
↓
application executable
↓
BBB hardware test
This environment is useful because I can make experimental changes without immediately changing the formal MR version.
5. Do I Need to Delete the Previous RTEMS Installation?
No.
I do not need to delete the previous RTEMS installation every time I switch to standalone testing.
The installed RTEMS environment can remain under:
$HOME/development/rtems/7
The important point is not simply whether an old driver exists in the installation.
The important point is:
Which implementation is compiled and linked into the final executable?
Therefore, instead of deleting the whole RTEMS installation, I should inspect the build and linking process.
6. Verify That the Local Driver Is Actually Compiled
The first check is to build with verbose output.
For example:
./waf -v
I should look for a compile command containing:
dcan/dcan.c
This confirms that the standalone project is compiling the local driver source.
I can also save the output:
./waf -v 2>&1 | tee build.log
Then search:
grep -n "dcan.c" build.log
If I see the local project path, I know the local driver is being compiled.
7. Check for Duplicate Symbols
Because an older DCAN driver may already exist in the installed RTEMS static library, I should also check whether the same symbols exist in multiple places.
For example:
arm-rtems7-nm path/to/hello_dcan.exe | grep dcan
This shows DCAN-related symbols in the final executable.
I can also inspect the installed RTEMS library:
arm-rtems7-nm \
$HOME/development/rtems/7/arm-rtems7/beagleboneblack/lib/librtemscpu.a \
| grep dcan
The exact library path may depend on the RTEMS installation layout.
This check helps answer:
Does the installed RTEMS library contain DCAN symbols?
and:
Does the final application executable contain the expected symbols?
8. A Stronger Verification Method: Link Map File
A very useful method is generating a linker map file.
A map file can show which object file or library provides each symbol.
Conceptually, I want to answer:
dcan_start_chip
↓
provided by which object?
For example:
local dcan.c object?
or:
installed librtemscpu.a?
This is stronger than assuming that the local source file is used just because the build succeeds.
The exact linker flag depends on the application build configuration, but the goal is to generate something like:
hello_dcan.map
Then search:
grep -n "dcan_start_chip" hello_dcan.map
This provides direct evidence about symbol resolution.
9. Temporary Experimental Marker
For short-term debugging, another simple method is adding a temporary marker only to the local experimental driver.
For example:
printk( "LOCAL EXPERIMENTAL DCAN DRIVER\n" );
Then boot the BBB image.
If the output appears:
LOCAL EXPERIMENTAL DCAN DRIVER
I know that the local experimental version is running.
This marker should only be used temporarily and should never be included in the final merge request.
A cleaner alternative is a temporary unique symbol or version string.
10. My Review Testing Workflow
For mentor review comments, I plan to use the following process:
Receive mentor comment
↓
understand the technical reason
↓
modify standalone local driver
↓
clean application build
↓
verify local dcan.c compilation
↓
build BBB image
↓
run TX test
↓
run RX test
↓
check interrupts and queue behavior
↓
repeat for next review comment
After all major review comments are tested:
stable experimental version
↓
apply changes to RTEMS MR branch
↓
build complete RTEMS tree
↓
check warnings
↓
git diff --check
↓
./waf install
↓
remove local dcan.c from test application
↓
rebuild external application
↓
run final BBB TX/RX tests
↓
update MR branch
11. Clean Builds Are Important
When switching between driver versions, I should avoid relying only on incremental builds.
For the standalone project:
./waf clean
./waf
For more significant build configuration changes, a full reconfiguration may be useful depending on the project.
The reason is simple:
old object files
+
changed source list
+
changed driver location
can make debugging confusing.
A clean build reduces uncertainty.
12. Review Changes Should Be Tested in Small Steps
I received many mentor comments, and it can be tempting to modify everything at once.
A safer process is:
Change 1
↓
build
↓
test
Change 2
↓
build
↓
test
Change 3
↓
build
↓
test
For example:
coding style cleanup
↓
build
remove old debug function
↓
build
change RX Message Object logic
↓
build + RX test
change TX handling
↓
build + TX test
change interrupt processing
↓
build + TX/RX test
This makes regression debugging much easier.
If ten changes are made at once and RX stops working, it is difficult to know which change caused the problem.
13. Final MR Verification
After the standalone version is stable, I will apply the final changes to the existing MR branch.
I do not need to delete the MR branch.
The workflow is:
standalone tested changes
↓
existing MR branch
↓
apply final code changes
↓
move files if required by new RTEMS layout
↓
update build specification
↓
RTEMS full build
↓
RTEMS install
↓
external app without local dcan.c
↓
BBB hardware verification
↓
update commit
↓
push branch
↓
MR automatically updates
The final verification should again use:
git diff --check
and:
./waf build
followed by:
./waf install
Then the external test application should remove the local driver source and link against the installed RTEMS version.
Conclusion
After receiving code review comments, I now use two clearly separated modes:
Standalone experimental mode
→ local dcan.c
→ fast review changes
→ BBB hardware testing
and:
Formal RTEMS integration mode
→ RTEMS source tree driver
→ waf build
→ waf install
→ external application without local dcan.c
→ final BBB verification
The most important lesson is that a successful build alone is not always enough to prove which driver implementation is being tested.
When the same driver exists both locally and inside installed RTEMS libraries, I should verify the actual build and link path using:
verbose build output
symbol inspection
linker map files
temporary test markers
clean builds
This workflow allows me to test mentor feedback quickly while keeping the formal RTEMS merge request version controlled and reproducible.
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
Week 9 Progress Report
BSP-specific Support, Git History Cleanup, and Installed Driver Testing
Overview
This week, I mainly focused on preparing the DCAN driver for upstream review. Separation between the generic driver and the AM335x board-specific support.
I also reorganized the Git history, rebuilt RTEMS, installed the new build into a separate directory, and used the installed RTEMS environment to build my external DCAN test program.
The major work this week included:
- moving AM335x-specific initialization into the Beagle BSP
- keeping the generic DCAN driver independent from the board
- splitting one large commit into two logical commits
- creating a backup branch before changing Git history
- recovering tracked and untracked files from Git stash
- rebuilding and installing RTEMS
- verifying the installed public header and Beagle BSP files
- building the external DCAN register test program with the installed RTEMS
- testing CAN transmit and receive on the BeagleBone Black
1. Separating the Generic Driver and BSP Support
One important task this week was moving AM335x-specific initialization out of the generic DCAN driver.
Before this change, the generic driver directly handled the BeagleBone Black clock and pin configuration.
For example, the driver startup included operations similar to:
dcan1_clock_enable();
dcan1_pinmux();
These functions are specific to the AM335x processor and should not be part of a reusable DCAN controller driver.
After the refactoring, the responsibilities became:
Generic DCAN driver
- controller initialization
- message object access
- interrupt processing
- transmit and receive
- RTEMS CAN framework integration
Beagle BSP support
- AM335x DCAN1 clock setup
- AM335x pin multiplexing
- board startup initialization
The new startup flow is:
RTEMS startup
↓
bbb_drivers_initialize()
↓
rtems_am335x_dcan_initialize()
↓
dcan1_clock_enable()
↓
dcan1_pinmux()
The application still creates the generic DCAN controller later.
chip = rtems_can_dcan_initialize(
SOC_DCAN_1_REGS,
AM335X_INT_DCAN1_INT0,
24000000
);
This design keeps the generic driver independent from the AM335x platform.
2. Files Added and Modified
Generic DCAN Driver
The generic driver files are located under:
bsps/shared/dev/can/dcan/
Main implementation:
bsps/shared/dev/can/dcan/dcan.c
Register definitions:
bsps/shared/dev/can/dcan/dcan_regs.h
Public driver header:
bsps/include/dev/can/dcan.h
AM335x BSP Support
The AM335x-specific implementation was added under the Beagle BSP.
Source file:
bsps/arm/beagle/can/dcan_am335x.c
Header file:
bsps/arm/beagle/include/bsp/dcan_am335x.h
BSP Startup
The Beagle BSP startup file was modified:
bsps/arm/beagle/start/bspstart.c
The BSP now calls:
rtems_am335x_dcan_initialize();
This performs the board-specific DCAN setup during RTEMS startup.
Build System
The generic RTEMS build file was modified:
spec/build/bsps/obj.yml
The Beagle BSP build file was also modified:
spec/build/bsps/arm/beagle/obj.yml
3. Reorganizing the Merge Request
The original merge request contained one large commit:
f2d1931ce9
can/dcan: Add AM335x DCAN controller driver
This commit included both:
- generic DCAN driver code
- AM335x Beagle BSP support
Based on mentor feedback, I split the work into two logical commits.
Commit 1
83df456537
can/dcan: Add generic DCAN controller driver
This commit contains:
- generic driver source
- public driver header
- register definitions
- generic RTEMS build system changes
Files in this commit:
bsps/include/dev/can/dcan.h
bsps/shared/dev/can/dcan/dcan.c
bsps/shared/dev/can/dcan/dcan_regs.h
spec/build/bsps/obj.yml
Commit 2
ca6e1b52a3
bsps/arm/beagle: Add AM335x DCAN board support
This commit contains:
- AM335x clock setup
- pin multiplexing
- Beagle BSP startup integration
- Beagle BSP build configuration
Files in this commit:
bsps/arm/beagle/can/dcan_am335x.c
bsps/arm/beagle/include/bsp/dcan_am335x.h
bsps/arm/beagle/start/bspstart.c
spec/build/bsps/arm/beagle/obj.yml
This structure makes the merge request easier to review because the generic code and board-specific code are clearly separated.
4. Creating a Backup Branch Before Reorganizing the Commits
Before changing the Git history, I created a backup branch.
I checked out the original development branch.
git switch 5440-am335x-dcan
Then I created a new branch for the history cleanup.
git switch -c 5440-am335x-dcan-rebase
The new branch initially pointed to exactly the same commit as the original branch.
This was important because reset and rebase operations can rewrite commit history. If I made a mistake, the backup branch would still point to the original working version of the driver.
Step 1: Check the Current Branch
git branch --show-current
The development branch was:
5440-am335x-dcan-rebase
I also checked the repository status. git status. Before changing the history, I confirmed that the important work was committed or saved.
Step 2: Check the Current Commit
Checked the latest commits.
git log --oneline --decorate -5
The original driver implementation was contained in:
f2d1931ce9 can/dcan: Add AM335x DCAN controller driver
The backup branch needed to point to this commit before I changed the development branch.
Step 3: Create the Backup Branch
While staying on the development branch, I created the backup branch.
git branch backup/5440-am335x-dcan-before-rebase
This command created a new branch without switching to it.
At this point, both branch names pointed to the same commit.
5440-am335x-dcan-rebase
backup/5440-am335x-dcan-before-rebase
↓
f2d1931ce9
This allowed to safely reset and rewrite the development branch.
Step 4: Verify the Backup Branch
I checked the branch list.
git branch -vv
I also checked the latest commit of the backup branch.
git log \
--oneline \
--decorate \
backup/5440-am335x-dcan-before-rebase \
-1
The expected output was similar to:
f2d1931ce9 can/dcan: Add AM335x DCAN controller driver
Step 5: Continue on the Development Branch
The backup branch was only used as a safety reference.
I confirmed that I was still on the development branch.
git switch 5440-am335x-dcan-rebase
Then I continued with:
git stash push -u
and:
git reset --mixed upstream/main
The backup branch remained unchanged.
Why the Backup Branch Was Useful
A Git branch is a name that points to a commit.
The command:
git branch backup/5440-am335x-dcan-before-rebase
did not copy the complete repository. It created another reference to the current commit.
Before the history cleanup:
5440-am335x-dcan-rebase
backup/5440-am335x-dcan-before-rebase
│
└── f2d1931ce9
After the cleanup:
5440-am335x-dcan-rebase
│
└── ca6e1b52a3
│
└── 83df456537
│
└── upstream/main
backup/5440-am335x-dcan-before-rebase
│
└── f2d1931ce9
If the history cleanup failed, I could return to the original code with:
git switch backup/5440-am335x-dcan-before-rebase
I could also restore one file from the backup branch.
git restore \
--source=backup/5440-am335x-dcan-before-rebase \
path/to/file
I kept the backup branch after the cleanup as an additional safety copy.
5. Resetting and Recovering the Changes
After creating the backup branch, I saved the working tree.
git stash push -u
The -u option included untracked files.
Then I reset the development branch to the current upstream main branch.
git reset --mixed upstream/main
The purpose was to remove the original large commit while keeping the driver changes available as local modifications.
However, when I used:
git stash pop
Git reported conflicts because some files restored by the reset were also stored as untracked files in the stash.
To safely recover the work, I first created a temporary commit.
git add -A
git commit -m "WIP: recover DCAN changes"
Then I applied the stash again.
git stash pop
Some untracked files were stored in the third parent of the stash commit. I restored them using:
git restore \
--source='stash@{0}^3' \
--worktree \
bsps/arm/beagle/can/dcan_am335x.c \
bsps/arm/beagle/include/bsp/dcan_am335x.h
I verified the recovered files with:
git diff --exit-code 'stash@{0}^3'
After confirming that the code was recovered, I removed the temporary WIP commit.
git reset --mixed upstream/main
At this point, all driver changes were available as uncommitted changes, and I could split them into two clean commits.
6. Creating the Two New Commits
Generic Driver Commit
I added only the generic files.
git add \
bsps/include/dev/can/dcan.h \
bsps/shared/dev/can/dcan/dcan.c \
bsps/shared/dev/can/dcan/dcan_regs.h \
spec/build/bsps/obj.yml
Then I created the first commit.
git commit -s \
-m "can/dcan: Add generic DCAN controller driver"
The -s option added my Signed-off-by line.
AM335x BSP Commit
Next, I added the board-specific files.
git add \
bsps/arm/beagle/can/dcan_am335x.c \
bsps/arm/beagle/include/bsp/dcan_am335x.h \
bsps/arm/beagle/start/bspstart.c \
spec/build/bsps/arm/beagle/obj.yml
Then I created the second commit.
git commit -s \
-m "bsps/arm/beagle: Add AM335x DCAN board support"
Verify the Commit History
I checked the commits that were ahead of upstream main.
git log --oneline upstream/main..HEAD
The output showed only:
ca6e1b52a3 bsps/arm/beagle: Add AM335x DCAN board support
83df456537 can/dcan: Add generic DCAN controller driver
I also checked the working tree.
git status
The expected result was:
nothing to commit, working tree clean
7. Updating the Existing Merge Request
Because the commit history changed, I needed to update the remote branch with a force push.
I used:
git push --force-with-lease \
origin \
5440-am335x-dcan-rebase:5440-am335x-dcan
I used --force-with-lease instead of --force because it is safer.
It checks whether the remote branch changed unexpectedly before replacing its history.
After pushing, I fetched the remote branch again.
git fetch origin
Then I checked the remote merge request branch.
git log --oneline \
upstream/main..origin/5440-am335x-dcan
The output showed only the two new commits.
I also counted them.
git rev-list --count \
upstream/main..origin/5440-am335x-dcan
The result was:
2
GitLab showed additional upstream commits in the activity log because the branch had been rebased. However, the merge request itself contained only my two driver commits.
8. Building and Installing RTEMS
After finishing the refactoring and commit cleanup, I rebuilt RTEMS to verify that the new driver was correctly integrated into the build system.
First, I configured a clean RTEMS build:
./waf configure \
--prefix=$HOME/development/rtems/7-new \
--rtems-tools=$HOME/development/rtems/7-new \
--rtems-bsps=arm/beagleboneblack
Then I built and installed RTEMS.
./waf build
./waf install
The installation was placed in a separate directory (7-new) so it would not overwrite my existing RTEMS installation.
After installation, I verified that:
- the generic DCAN driver was compiled successfully
- the AM335x BSP support was included
- the public header
dcan.hwas installed - the BeagleBone Black BSP was generated correctly
Finally, I rebuilt my external DCAN test application using the installed RTEMS instead of compiling the driver source files directly. The application linked successfully and the driver worked correctly on the BeagleBone Black.
9. Keeping the DCAN Register Test Program
I kept my original DCAN register test program after the driver refactoring.
The register test is useful because it can test the hardware at a lower level before testing the complete RTEMS CAN framework.
The test program can help verify:
- DCAN clock setup
- pin multiplexing
- controller register access
- initialization mode
- bit timing configuration
- message object configuration
- TX request state
- RX new-data state
- interrupt register values
This test program is especially useful when the full CAN application does not work because it helps separate hardware problems from CAN framework problems.
10. Building the Test Program with the Installed RTEMS
After installing RTEMS under:
$HOME/development/rtems/7-new
I rebuilt the external DCAN test program using the new installation.
The important difference was that the test program no longer compiled the driver source files directly.
It did not compile:
bsps/shared/dev/can/dcan/dcan.c
or:
bsps/arm/beagle/can/dcan_am335x.c
Instead, the application used the driver that was already built into the installed RTEMS BSP libraries.
The test application included the public header:
#include <dev/can/dcan.h>
It also used the Beagle BSP definitions.
#include <bsp.h>
#include <bsp/irq.h>
The controller was created with:
chip = rtems_can_dcan_initialize(
SOC_DCAN_1_REGS,
AM335X_INT_DCAN1_INT0,
24000000
);
The application then started and registered the controller.
chip->chip_ops.start_chip(chip);
rtems_can_bus_register(chip, "dcan1");
The exact application build command depends on the test program build system.
The important point was that the application build used the installed Beagle BSP configuration from:
$HOME/development/rtems/7-new
This verified that:
- the driver was included in the installed BSP libraries
- the public header was installed
- the application could link without adding the driver source files
- the BSP-specific initialization was included automatically
11. Hardware Test Setup
The hardware test setup was:
Linux system
↓
SocketCAN
↓
CANable USB adapter
↓
CAN bus with termination
↓
BeagleBone Black DCAN1
↓
RTEMS DCAN driver
The BeagleBone Black DCAN1 pins were:
P9.24: DCAN1_TX
P9.26: DCAN1_RX
The CAN bus used two 120-ohm termination resistors.
The bitrate was:
125000 bit/s
12. Configuring Linux SocketCAN
Before testing, I configured the Linux CAN interface.
First, I disabled the interface.
sudo ip link set can0 down
Then I enabled it with the correct bitrate.
sudo ip link set can0 up type can bitrate 125000
I checked the interface configuration.
ip -details link show can0
This confirmed:
- CAN state
- bitrate
- sample point
- error counters
- controller status
13. Testing Linux-to-RTEMS Communication
To monitor CAN traffic on Linux, I used:
candump can0
Then I sent a CAN frame from Linux to the BeagleBone Black.
cansend can0 123#1122334455667788
The RTEMS test application received the frame and printed the CAN identifier, DLC, and data.
This verified:
- CANable transmission
- physical CAN bus connection
- DCAN1 receive
- RX message object configuration
- interrupt handling
- RTEMS CAN receive path
14. Testing RTEMS-to-Linux Communication
I kept candump running on Linux.
candump can0
The RTEMS test program transmitted frames through the installed DCAN driver.
Linux displayed the received frame in a format similar to:
can0 123 [8] 11 22 33 44 55 66 77 88
This verified:
- application write path
- RTEMS CAN queue
- DCAN message object write
- TX request
- TX completion interrupt
- physical transmission
- Linux SocketCAN reception
15. Testing the Register Test Program
The register test program was also used to inspect DCAN register values.
The test checked values such as:
DCAN1 base address
clock control register
pin multiplexing registers
control register
bit timing register
error and status register
interrupt register
new data register
message object control registers
The register test allowed me to confirm that BSP initialization happened before the application used the controller.
It was also useful for checking whether:
- the module clock was enabled
- the controller entered initialization mode
- the controller left initialization mode
- the selected message object was valid
- TX and RX state changed as expected
16. Complete Testing Workflow
The complete workflow was:
Modify driver source
↓
Build RTEMS
↓
Install RTEMS to 7-new
↓
Verify installed header and BSP files
↓
Build external DCAN register test
↓
Build external CAN framework test
↓
Copy executable to the BeagleBone Black boot environment
↓
Boot RTEMS
↓
Configure Linux SocketCAN
↓
Test Linux-to-RTEMS CAN frames
↓
Test RTEMS-to-Linux CAN frames
This process verified both the source-tree integration and the installed-driver usage.
17. What I Learned
This week helped me understand that upstream driver development includes more than making the hardware work.
I learned how to:
- separate generic driver code from board-specific support
- organize source files according to RTEMS structure
- update RTEMS build system files
- create a backup branch before changing Git history
- use stash and reset safely
- recover untracked files from a Git stash
- split one large commit into two reviewable commits
- update a merge request using
--force-with-lease - rebuild and install RTEMS into a separate prefix
- verify installed public headers and BSP files
- build an external application using the installed driver
- keep a low-level register test for hardware debugging
The most important result was that the DCAN driver could now be built as part of RTEMS and used by an external application through the installed RTEMS environment.
18. Current Project Status
The current merge request contains two commits:
83df456537
can/dcan: Add generic DCAN controller driver
ca6e1b52a3
bsps/arm/beagle: Add AM335x DCAN board support
The working tree is clean.
git status
Expected output:
nothing to commit, working tree clean
The remote merge request branch contains only these two commits ahead of upstream/main.
git rev-list --count \
upstream/main..origin/5440-am335x-dcan
Expected result:
2
The driver has also been tested through:
- RTEMS source build
- RTEMS installation
- installed public header
- external register test program
- external CAN application
- Linux-to-RTEMS communication
- RTEMS-to-Linux communication
19. Next Week
Next week, I will:
- think of FIFO
GSoC 2026 RTEMS DCAN Driver Development — Week 10
Week 10 — Multi-Buffer TX and FIFO Behavior
Background
The AM335x DCAN controller provides multiple Message Objects. In my driver, I use Message Objects 9 through 12 as TX buffers:
- MO9
- MO10
- MO11
- MO12
Using several TX Message Objects improves throughput because the driver can prepare multiple CAN frames in hardware instead of waiting for one frame to finish before submitting the next one.
The basic TX flow is:
RTEMS TX queue
|
v
Find a free DCAN TX Message Object
|
v
Write CAN frame into the Message Object
|
v
Set TxRqst
|
v
DCAN transmits the frame
|
v
TX completion interrupt
|
v
Release the RTEMS queue slot
At first, the driver could successfully transmit frames using several Message Objects, but I needed to verify that the transmission order was still correct.
FIFO Problem
Suppose four frames are submitted in this order:
Frame A
Frame B
Frame C
Frame D
They may be loaded into the hardware TX buffers as:
MO9 = A
MO10 = B
MO11 = C
MO12 = D
A simple expectation is:
A -> B -> C -> D
However, using multiple hardware Message Objects makes the situation more complicated.
The software queue may be FIFO, but the hardware Message Object positions can also affect which pending transmission is selected.
During testing, I observed results where frames could be received in a different order, for example:
100 101 103 102
instead of:
100 101 102 103
This showed that simply filling every free Message Object was not enough to guarantee the desired ordering.
Understanding the Cause
The important lesson was that there are two different layers involved in TX ordering:
Software TX queue ordering
+
DCAN hardware Message Object ordering
The software queue may return frames in the correct order, but once several frames are already pending inside DCAN, the hardware decides which pending Message Object is transmitted next.
Therefore, the driver has to carefully manage the relationship between:
- RTEMS queue slot
- RTEMS queue edge
- DCAN Message Object
TxRqststate
The driver cannot only consider the software queue. It also has to account for the state of the hardware TX buffers.
TX Completion Tracking
For each hardware TX buffer, I keep information such as:
struct dcan_txb_info {
struct rtems_can_queue_slot *slot;
struct rtems_can_queue_edge *edge;
unsigned int mobj;
int prio;
bool abort_requested;
};
This structure connects each DCAN Message Object with the RTEMS frame currently using it.
When a transmission finishes, the driver:
- Identifies the Message Object that generated the interrupt.
- Checks the TX result.
- Releases the corresponding RTEMS queue slot.
- Clears the software ownership of the Message Object.
- Allows another queued frame to use the free TX buffer.
The TX buffer state can therefore be viewed as:
RTEMS queue
|
v
Queue slot + queue edge
|
v
dcan_txb_info
|
v
DCAN Message Object
|
v
Hardware transmission
|
v
TX completion interrupt
|
v
Release queue ownership
Keeping this relationship explicit became increasingly important as the TX implementation became more complex.
It also created the foundation for implementing priority scheduling and TX preemption in the following weeks.
Week 10 Result
By the end of Week 10, the driver could use multiple TX Message Objects and correctly track their queue ownership and TX completion.
The main lesson from this week was:
A software FIFO alone does not automatically guarantee transmission order when several hardware TX buffers are active.
The driver must consider both the ordering of the RTEMS software queues and the state of the DCAN hardware Message Objects.
This became particularly important when I started implementing priority scheduling and runtime TX preemption in the following stage of the project.
GSoC 2026 RTEMS DCAN Driver Development — Week 11
Week 11 — First Version of TX Priority Preemption
Why TX Preemption Is Needed
Originally, the driver only filled free TX Message Objects.
For example:
MO9 = low-priority frame
MO10 = low-priority frame
MO11 = low-priority frame
MO12 = low-priority frame
If a high-priority frame arrived while all four Message Objects were occupied, it had to remain in the RTEMS software queue.
That means:
Low-priority frames already in hardware
|
v
must all finish first
|
v
High-priority frame can finally enter hardware
This behavior limits the effectiveness of priority scheduling. Even if the RTEMS software queue correctly identifies a high-priority frame, the frame cannot be transmitted promptly if all hardware TX Message Objects are occupied by lower-priority traffic.
Learning from the SJA1000 Driver
My mentor pointed me to the RTEMS SJA1000 CAN driver as a reference.
The SJA1000 driver checks whether the currently active hardware transmission has a lower priority than a frame waiting in the software queue.
If a higher-priority frame is waiting, the driver can abort the lower-priority TX request and return that frame to the software queue.
The basic idea is:
Higher-priority frame waiting
+
Lower-priority frame pending in hardware
|
v
Abort the lower-priority transmission
|
v
Return the old frame to the software queue
|
v
Send the higher-priority frame first
I used this idea as the starting point for implementing TX preemption in the DCAN driver.
However, DCAN is more complicated because the driver uses multiple hardware TX Message Objects instead of a single active TX buffer.
Initial DCAN Preemption Design
Because DCAN uses several TX Message Objects, my first implementation selected one lower-priority Message Object as the preemption victim.
For example:
Hardware:
MO9 = low
MO10 = low
MO11 = low
MO12 = low
Software queue:
high
When the higher-priority frame arrived, the driver selected a lower-priority Message Object as the victim.
For example:
victim = MO12
The preemption sequence was:
Abort MO12
|
v
Return the old MO12 frame to the software queue
|
v
Load the high-priority frame into MO12
|
v
Request transmission
This became the first working version of DCAN TX priority preemption.
Debugging DCAN TX Abort
The most important low-level bug during this work was related to the DCAN Interface Command Register.
To preempt a frame, the driver first needs to cancel the pending transmission by clearing the Message Object's TxRqst state.
Initially, the abort function attempted to clear TxRqst in the Interface Message Control Register:
IF1MCTL.TxRqst = 0
However, the Interface Command Register also included:
IF1CMD.TxRqst/NewDat = 1
According to the DCAN hardware behavior, during a write operation the TxRqst/NewDat command bit can directly set the Message Object's TxRqst state.
Therefore, my original abort sequence was effectively doing:
Clear TxRqst in IF1MCTL
|
v
Execute IF1 write command
|
v
TxRqst/NewDat command bit sets TxRqst again
As a result, the transmission request was never actually cleared.
The debugging output showed:
TXRQ before abort = 0x00000f00
TXRQ after abort = 0x00000f00
The TX request remained active.
Fixing the Abort Operation
The fix was to clear TxRqst through the Message Control field and write the updated control state back to the Message Object using the CONTROL transfer command, without setting the command's TxRqst/NewDat bit.
After this change, the debugging output became:
ABORT BEFORE MO12 TXRQ=0x00000f00
ABORT AFTER MO12 TXRQ=0x00000700
Before the abort:
0x00000f00
indicated that MO9 through MO12 had pending TX requests.
After aborting MO12:
0x00000700
the MO12 TX request bit was cleared successfully.
This confirmed that the driver could now cancel a pending DCAN hardware transmission.
Successful First Preemption Test
I then created a test where four low-priority frames occupied MO9 through MO12 and a higher-priority frame was queued afterward.
The expected behavior was:
Four low-priority frames
|
v
MO9–MO12 occupied
|
v
Higher-priority frame arrives
|
v
Select lower-priority victim
|
v
Abort victim
|
v
Return victim frame to RTEMS queue
|
v
Submit higher-priority frame
The driver successfully performed the complete sequence:
PREEMPT VICTIM MO12
PREEMPT ABORTED MO12
PREEMPT REQUEUE MO12
This was an important milestone because it demonstrated that two key mechanisms were working together:
- Hardware TX abort
- Software queue requeue
The lower-priority frame was not simply discarded when preempted. Instead, ownership was returned to the software queue so that the frame could be transmitted later.
Limitation of the Week 11 Design
Although the first preemption mechanism worked, it still had an important limitation.
Suppose the hardware currently contains frames with the following CAN IDs:
MO9 = 54
MO10 = 55
MO11 = 56
MO12 = 57
Then a new higher-priority frame with CAN ID 49 arrives.
The Week 11 implementation could produce:
MO9 = 54
MO10 = 55
MO11 = 56
MO12 = 49
Software queue:
57
The higher-priority frame successfully entered hardware, so the basic preemption mechanism worked.
However, it was simply placed into the Message Object that had been selected as the victim:
MO12 = 49
This did not provide the hardware layout expected for the intended priority behavior.
My mentor clarified that the desired result should instead be:
MO9 = 49
MO10 = 54
MO11 = 55
MO12 = 56
Software queue:
57
Conceptually, the TX window should change from:
Before:
MO9 MO10 MO11 MO12
54 55 56 57
+
new 49
to:
After:
MO9 MO10 MO11 MO12
49 54 55 56
Queue:
57
The new high-priority frame should enter at the front of the active hardware TX window, while the existing frames are shifted and the lowest-priority frame is returned to the software queue.
Why This Matters
Week 11 showed that preemption is more than simply aborting one Message Object and replacing its frame.
There are two separate problems:
1. Which frame should be removed from hardware?
2. Where should the new high-priority frame be placed?
The first implementation solved the first problem:
A lower-priority pending transmission could be aborted and safely returned to the RTEMS software queue.
But it did not yet fully solve the second problem:
The remaining hardware Message Objects needed to be reorganized so that the active TX window reflected the desired priority ordering.
This distinction became the main focus of the next stage of development.
Week 11 Result
By the end of Week 11, the driver could:
- Detect when a higher-priority frame was waiting.
- Identify a lower-priority hardware TX Message Object as a preemption victim.
- Correctly abort a pending DCAN transmission.
- Clear the corresponding
TxRqststate. - Return the preempted frame to the RTEMS software queue.
- Load the higher-priority frame into the available hardware TX buffer.
- Preserve the preempted frame instead of losing it.
The most important low-level lesson was:
Clearing
TxRqstin the Message Control field is not sufficient if the Interface Command itself setsTxRqst/NewDatagain.
The larger scheduling lesson was:
Successful TX preemption requires both software queue management and careful management of the active hardware Message Object window.
The Week 11 implementation proved that DCAN TX abort and requeue were possible, but it also exposed the limitation of replacing only a single victim Message Object.
That limitation led directly to the Week 12 redesign, where I began reorganizing the entire active TX Message Object window to preserve the intended priority order.
GSoC 2026 RTEMS DCAN Driver Development — Week 12
Week 12 — CAN-ID Based TX Priority Window
New Design Goal
After the first TX preemption implementation in Week 11, my mentor clarified that TX priority should follow the CAN arbitration identifier:
A lower CAN ID has higher transmission priority.
Therefore, the four DCAN TX Message Objects should no longer be treated simply as independent TX buffers.
Instead, they should be treated as a fixed-size ordered hardware TX window.
The important invariant is:
CAN_ID(MO9)
<=
CAN_ID(MO10)
<=
CAN_ID(MO11)
<=
CAN_ID(MO12)
For example:
MO9 = 54
MO10 = 55
MO11 = 56
MO12 = 57
If a new frame with CAN ID 49 arrives:
49 < 54 < 55 < 56 < 57
the best four frames should remain in hardware:
MO9 = 49
MO10 = 54
MO11 = 55
MO12 = 56
while the lowest-priority frame should return to the software queue:
57 -> RTEMS TX queue
This is different from the Week 11 design, which simply selected and replaced one victim Message Object.
The new design maintains a CAN-ID ordered TX hardware window.
Finding the Insertion Position
I added a helper function that extracts the standard CAN identifier from a frame:
static uint32_t dcan_tx_frame_id(
const struct can_frame *frame
)
{
return frame->header.can_id & 0x7ffu;
}
The driver then scans the active TX window and determines where the new frame belongs.
For example:
Hardware:
54 55 57 60
New:
56
The comparison becomes:
56 < 54 ? no
56 < 55 ? no
56 < 57 ? yes
Therefore:
first_index = 2
which corresponds to MO11.
Only the affected suffix needs to be reorganized:
MO9 = 54 unchanged
MO10 = 55 unchanged
MO11 and MO12 must be reordered
Using a strict < comparison also keeps frames with equal CAN IDs behind frames that are already queued.
This helps preserve FIFO behavior for frames with the same CAN identifier.
Two Reorder Operations
I introduced two types of TX window reorganization:
enum dcan_tx_reorder_type {
DCAN_TX_REORDER_NONE = 0,
DCAN_TX_REORDER_PREEMPT,
DCAN_TX_REORDER_COMPACT
};
They solve two different problems.
1. PREEMPT
DCAN_TX_REORDER_PREEMPT occurs when a new higher-priority CAN frame needs to enter a full hardware TX window.
For example:
Hardware:
54 55 57 60
New:
56
The desired result is:
Hardware:
54 55 56 57
Software queue:
60
The new frame is inserted into the correct position, the affected frames are shifted, and the lowest-priority frame is returned to the RTEMS TX queue.
2. COMPACT
DCAN_TX_REORDER_COMPACT occurs when an earlier Message Object finishes transmission and leaves a hole in the hardware window.
For example:
MO9 = free
MO10 = 54
MO11 = 55
MO12 = 56
Software queue:
57
If the driver simply filled MO9 with ID 57, the result would be:
57 54 55 56
which breaks the hardware priority order.
Instead, the driver first compacts the existing frames:
54 -> MO9
55 -> MO10
56 -> MO11
leaving:
MO9 = 54
MO10 = 55
MO11 = 56
MO12 = free
The next queued frame can then safely fill the tail:
57 -> MO12
The final hardware window becomes:
54 55 56 57
This keeps the hardware window ordered after TX completion.
Reorder State
A reorder operation cannot be completed in a single instruction.
The driver must perform several steps:
Detect reorder
|
v
Request hardware abort
|
v
Wait until TxRqst becomes 0
|
v
Rewrite affected Message Objects
Therefore, I added state to remember the active operation:
struct dcan_tx_reorder {
bool active;
enum dcan_tx_reorder_type type;
unsigned int first_index;
struct rtems_can_queue_slot *new_slot;
struct rtems_can_queue_edge *new_edge;
};
This structure records:
- whether a reorder operation is active,
- whether the operation is
PREEMPTorCOMPACT, - where the affected Message Object range begins,
- the new queued frame waiting to enter the hardware window.
This allows the operation to continue safely across multiple TX worker iterations.
Starting Priority Preemption
When all TX Message Objects are occupied, the driver checks the next frame waiting in the RTEMS software TX queue.
Conceptually, the logic is:
Is another reorder active?
|
yes -> wait
|
no
v
Are all four hardware TX buffers occupied?
|
no -> normal filling
|
yes
v
Get next frame from RTEMS TX queue
|
v
Find insertion index by CAN ID
If the new frame does not belong in the active hardware window:
first_index == 4
it is simply returned to the software queue.
For example:
Hardware:
49 54 55 56
New:
57
ID 57 has lower priority than every frame already in hardware.
Therefore:
first_index = 4
and no preemption is required.
Aborting Only the Affected Suffix
The new design does not always abort all four Message Objects.
For example:
Hardware:
54 56 57 60
New:
55
The insertion point is:
first_index = 1
Therefore:
MO9 stays unchanged
MO10
MO11
MO12
are the only affected Message Objects.
Similarly:
Hardware:
54 55 57 60
New:
56
produces:
first_index = 2
so only:
MO11
MO12
need to be stopped and rewritten.
This avoids unnecessary abort and rewrite operations on Message Objects that are already in the correct position.
Waiting for Hardware Abort Completion
The driver does not assume that calling the abort function means the Message Object is immediately safe to rewrite.
For every affected Message Object, the driver checks:
TxRqst == 0
Only after all requested aborts have completed does the driver mark the reorder operation as ready.
The debug flow looks like:
PREEMPT CHECK new_id=56 first_index=2
PREEMPT START first_index=2
REORDER READY type=1 first_index=2
This prevents the driver from rewriting a Message Object while the DCAN controller still considers its transmission pending.
Completing the Ordered Insertion
Consider the following test:
MO9 = 54
MO10 = 55
MO11 = 57
MO12 = 60
New = 56
The driver first saves the frame that will leave the hardware window:
evicted = ID60
It then shifts the affected frames from right to left:
MO12 <- old MO11 = 57
MO11 <- new frame = 56
The final hardware window becomes:
54 55 56 57
while:
60 -> software queue
The shift is performed from right to left.
This is important because shifting from left to right could overwrite information that is still needed for the next move.
Preserving Physical Message Object Identity
Another important implementation detail is that I move only the software ownership information associated with a frame:
slot
edge
priority
I do not copy the complete dcan_txb_info structure.
The reason is that mobj represents a physical DCAN hardware position:
txb_info[0].mobj = 9
txb_info[1].mobj = 10
txb_info[2].mobj = 11
txb_info[3].mobj = 12
These hardware Message Object numbers must remain fixed.
Therefore, the driver moves the frame ownership information between TX buffer records while preserving the physical mobj identity of each buffer.
TX Window Compaction
After a frame finishes transmission, the TX completion handler releases its RTEMS queue slot.
For example:
Before:
49 54 55 56
After ID 49 finishes:
free 54 55 56
Before performing a normal queue refill, the driver checks whether there is a hole before another active TX frame.
If there is, it starts:
DCAN_TX_REORDER_COMPACT
The remaining frames are moved toward the lower-numbered Message Objects:
free 54 55 56
|
v
54 55 56 free
The next software frame can then safely fill the tail:
54 55 56 57
This solves the ordering problem observed during the earlier multi-buffer TX tests.
Updated TX Processing Order
The TX worker now conceptually processes TX work in the following order:
1. Finish an active abort/reorder
2. Compact a hardware window that contains a hole
3. Fill normal free TX Message Objects
4. Check whether a new queued frame should preempt the full window
This ordering is important.
If normal filling occurred before compaction, a lower-priority frame could enter an earlier Message Object and break the CAN-ID ordering again.
The driver therefore restores the hardware-window invariant before accepting additional frames.
Week 12 Testing
I changed the local test so that it tests actual CAN IDs instead of only RTEMS queue priorities.
All test software queues use the same RTEMS queue priority.
This isolates:
CAN-ID scheduling
from:
RTEMS software queue priority
The payload also contains a sequence number so that I can independently verify:
CAN ID -> priority behavior
sequence -> frame identity, loss, or duplication
Test 1 — Front Insertion
Initial hardware:
54 55 56 57
New frame:
49
Expected result:
49 54 55 56
57 -> software queue
The driver reported:
PREEMPT CHECK new_id=49 first_index=0
PREEMPT START first_index=0
REORDER READY type=1 first_index=0
TX REORDER: MO9=49 MO10=54 MO11=55 MO12=56
TX REQUEUE ID=57
Final receive order:
49 54 55 56 57
Result: PASS
This test verified insertion at the front of the hardware TX window.
Test 2 — Middle Insertion
Initial hardware:
54 56 57 60
New frame:
55
Expected result:
54 55 56 57
60 -> software queue
The driver reported:
PREEMPT CHECK new_id=55 first_index=1
PREEMPT START first_index=1
REORDER READY type=1 first_index=1
TX REORDER: MO9=54 MO10=55 MO11=56 MO12=57
TX REQUEUE ID=60
Final receive order:
54 55 56 57 60
Result: PASS
This test proved that the driver can perform a partial-range reorder while keeping MO9 unchanged.
Test 3 — Tail-Range Insertion
Initial hardware:
54 55 57 60
New frame:
56
Expected result:
54 55 56 57
60 -> software queue
The driver reported:
PREEMPT CHECK new_id=56 first_index=2
PREEMPT START first_index=2
REORDER READY type=1 first_index=2
TX REORDER: MO9=54 MO10=55 MO11=56 MO12=57
TX REQUEUE ID=60
Final receive order:
54 55 56 57 60
Sequence order:
100 101 1 102 103
Result: PASS
The test also reported:
unique original frames: 4/4
every expected sequence once: yes
invalid frames: 0
error frames: 0
This confirmed that the reorder operation did not lose or duplicate any frame.
What I Learned
The work from Weeks 10–12 helped me understand that TX scheduling is not only about putting frames into hardware.
Several layers must remain consistent:
RTEMS software TX queue
|
v
Queue slot / edge ownership
|
v
DCAN Message Object assignment
|
v
TxRqst hardware state
|
v
CAN arbitration priority
The biggest change in my understanding was moving from:
"Find one low-priority victim."
to:
"Maintain an ordered hardware TX priority window."
This model makes the driver behavior easier to reason about.
The main invariant is now:
Occupied TX Message Objects should remain ordered from lower CAN ID to higher CAN ID.
When a higher-priority frame arrives, the driver performs an ordered insertion.
When a frame finishes and leaves a hole, the driver performs compaction.
Together, these two operations keep the active hardware TX window consistent with the intended CAN-ID priority model.
Current Status
At the end of Week 12, the DCAN driver supports and has locally tested:
- Multiple TX Message Objects
- TX completion handling
- TX abort using the correct DCAN
TxRqstsemantics - CAN-ID based priority comparison
- Priority insertion into a full TX hardware window
- Partial-range Message Object reordering
- Evicted frame return to the RTEMS software queue
- Post-completion TX window compaction
- Front insertion (
first_index = 0) - Middle insertion (
first_index = 1) - Tail-range insertion (
first_index = 2) - Frame-loss and duplicate-frame checking
The main tested example now behaves as expected:
Initial:
MO9 = 54
MO10 = 55
MO11 = 56
MO12 = 57
New high-priority frame:
49
After preemption:
MO9 = 49
MO10 = 54
MO11 = 55
MO12 = 56
Software queue:
57
This implementation is much closer to the intended DCAN TX priority scheduling behavior and addresses the main mentor feedback about TX priority preemption.
Next Steps
The next step is to continue testing edge cases before cleaning the code for the merge request.
An important remaining case is:
first_index = 3
Initial:
54 55 56 60
New:
57
Expected result:
54 55 56 57
60 -> software queue
I also want to test:
- Equal CAN IDs and FIFO behavior
- A new frame with lower priority than every hardware frame
- Repeated preemptions
- Preemption while TX completion interrupts are occurring
- Heavier CAN bus load
- Cleanup of temporary debug output
- Final review of comments and error paths before upstream submission
The driver is now moving from basic functional TX support toward more robust scheduling behavior suitable for upstream review.
DCAN TX Priority and Preemption Experiments
Overview
After the basic DCAN TX and RX paths were working, I started to improve the TX side of the driver.
The first working version used only one TX Message Object. This design was simple because one RTEMS CAN queue slot was connected to one hardware TX Message Object.
Later, I extended the driver to use multiple TX Message Objects. I used:
- MO1-MO8 for RX
- MO9-MO12 for TX
The goal was to improve TX capability and also study how TX priority should work when several CAN frames are already waiting inside the DCAN hardware.
This became more complicated than I expected, but the experiments helped me understand the relationship between the RTEMS CAN queue, DCAN Message Objects, and CAN bus arbitration.
1. Multi-TX Message Objects
With four TX Message Objects, the basic idea was:
RTEMS CAN TX queue
|
v
DCAN worker
|
v
+-------+-------+-------+-------+
| MO9 | MO10 | MO11 | MO12 |
+-------+-------+-------+-------+
Each active Message Object keeps one RTEMS queue slot:
MO9 <-> slot A
MO10 <-> slot B
MO11 <-> slot C
MO12 <-> slot D
This gives an important ownership rule:
One RTEMS CAN queue slot should be owned by only one TX Message Object at one time.
When a Message Object finishes transmission, the driver returns a local echo, updates the TX statistics, and releases the related queue slot.
This ownership model became one of the most important parts of the TX implementation.
2. Why TX Priority Is More Complicated
CAN already has hardware arbitration.
For normal CAN frames:
A lower CAN identifier has higher arbitration priority.
For example:
CAN ID 0x020 -> high CAN arbitration priority
CAN ID 0x500 -> medium CAN arbitration priority
CAN ID 0x700 -> low CAN arbitration priority
However, RTEMS also has software TX queues with their own priorities.
This means there are several scheduling levels:
Application
|
v
RTEMS software TX queue priority
|
v
DCAN TX Message Objects
|
v
CAN ID hardware arbitration
|
v
CAN bus
A higher-priority frame may enter the RTEMS software queue after several lower-priority frames have already been copied into hardware Message Objects.
This was the reason I started experimenting with TX preemption.
3. First Preemption Idea
Suppose all four TX Message Objects are busy:
MO9 -> low-priority frame
MO10 -> low-priority frame
MO11 -> low-priority frame
MO12 -> low-priority frame
Then a new high-priority frame arrives.
The idea was:
high-priority frame enters software queue
|
v
all TX MOs are busy
|
v
find a low-priority TX frame
|
v
cancel its TxRqst
|
v
return old frame to software queue
|
v
send high-priority frame first
This idea was partly inspired by the SJA1000 driver, which can abort a pending hardware transmission when a higher-priority software frame is waiting.
The basic idea is reasonable, but DCAN makes the implementation more complicated because several hardware Message Objects may be active at the same time.
4. CAN-ID Based Hardware Reordering
I later tested a more complete design.
The idea was to keep the active DCAN TX Message Objects ordered by CAN ID.
For example:
MO9 -> ID 54
MO10 -> ID 55
MO11 -> ID 57
MO12 -> ID 60
Now a new frame with ID 56 arrives.
Because ID 56 has higher CAN arbitration priority than ID 57 and ID 60, the desired hardware order becomes:
MO9 -> ID 54
MO10 -> ID 55
MO11 -> ID 56
MO12 -> ID 57
and ID 60 is returned to the software queue.
The driver therefore needs to:
- Find the correct insertion position.
- Cancel the affected hardware TX requests.
- Wait for
TxRqstto clear. - Move the software ownership information.
- Insert the new frame.
- Return the displaced frame to the RTEMS queue.
- Rewrite the affected DCAN Message Objects.
I created several controlled tests for front, middle, and tail insertion.
These tests showed that the basic CAN-ID ordering idea could work in small deterministic cases.
However, the implementation also introduced much more state movement between RTEMS queue slots and hardware Message Objects.
5. TX Compaction
Another problem happens when Message Objects finish independently.
For example:
MO9 -> free
MO10 -> ID 54
MO11 -> ID 55
MO12 -> ID 56
Now there is a hole in the hardware TX window:
[ free ][ 54 ][ 55 ][ 56 ]
I experimented with a compaction operation:
before:
[ NULL, 54, 55, 56 ]
after:
[ 54, 55, 56, NULL ]
Then the driver can take another frame from the RTEMS TX queue and put it into the last free Message Object.
This made the TX scheduling design more complete, but also much more complex.
A compaction operation must update both:
software ownership state
+
hardware Message Object state
while interrupts and TX completion may happen asynchronously.
6. A Timing-Sensitive Bug
During these experiments, I found an interesting problem.
When the driver contained many printf() debug messages, the preemption
code sometimes appeared to work correctly.
After removing the debug prints, the driver became much faster, but some failures appeared.
I observed errors such as:
DUPLICATE FROM QUEUE
queue returned owned slot
SLOT HAS MULTIPLE OWNERS
FIFO self-loop risk
This was an important clue.
The printf() calls were not fixing the driver. They were changing the
timing of the system.
The debug output slowed down:
- the worker task
- TX completion processing
- queue operations
- hardware Message Object updates
Because of this, some timing-sensitive problems became harder to reproduce.
This is similar to a Heisenbug: adding debug code changes the timing and therefore changes the behavior of the bug.
This taught me that a driver should always be tested again after debug output is removed.
7. Queue Slot Ownership Became the Main Debugging Question
At first, I thought the main problem was the preemption algorithm.
Later, I realized that the more important question was:
Who owns each RTEMS CAN queue slot at each point in time?
The expected ownership lifecycle is:
RTEMS queue owns slot
|
| rtems_can_queue_test_outslot()
v
DCAN driver owns slot
|
| hardware transmission
v
TX completion
|
| rtems_can_queue_free_outslot()
v
RTEMS queue owns slot again
A slot should never be owned by two TX Message Objects at the same time.
It should also not be returned by the RTEMS queue while the driver still uses it.
I added ownership checks and a small in-memory trace to record operations such as:
GET
MOVE
REQUEUE
FREE
This helped show that some failures were related to queue-slot lifetime, not only to CAN-ID preemption.
The most important invariant became:
One queue slot must have exactly one owner while it is assigned to hardware.
8. A/B Test
To understand the problem better, I disabled runtime priority preemption but kept the multi-TX-MO implementation.
The duplicate ownership problem could still appear.
This was an important result because it showed:
The problem was not caused only by the preemption algorithm.
Preemption and compaction made the TX path more complicated and made the problem easier to reproduce, but the deeper issue involved multiple outstanding RTEMS TX queue slots and their ownership lifetime.
This experiment also showed that there were two different questions:
1. Is queue-slot ownership correct?
2. Is the externally visible TX order correct?
Both must be correct before a multi-TX-MO implementation is suitable for upstream use.
9. Same-Priority Ordering With Multiple TX Message Objects
After removing the complex preemption logic, I returned to a simpler multi-TX-MO implementation.
The worker simply took frames from the RTEMS CAN queue and assigned them to free TX Message Objects:
RTEMS TX queue
|
v
DCAN worker
|
+--> MO9
+--> MO10
+--> MO11
+--> MO12
There was no runtime preemption, no Message Object sorting, and no compaction.
Each Message Object independently owned one queue slot until transmission completion.
This design was much more stable than the earlier preemption version.
However, another important requirement remained:
Frames from the same RTEMS priority class should preserve the software queue order.
To test this, I created a same-priority FIFO test.
All frames used:
same RTEMS TX queue
same queue priority
same CAN ID
The only difference was the sequence number in the CAN payload:
0 1 2 3 4 5 6 7 ...
With four hardware TX Message Objects active, the received order showed a very regular reordering pattern:
0 2 1 3
4 6 5 7
8 10 9 11
12 14 13 15
...
This was not random packet loss.
All frames were still received, but their transmission order did not always match the RTEMS software queue order.
This showed that:
Simply filling several DCAN TX Message Objects from the RTEMS FIFO does not automatically preserve the externally visible FIFO transmission order.
The software queue may give frames to the driver in order, but once several transmissions are simultaneously outstanding in different hardware Message Objects, hardware scheduling becomes another part of the ordering problem.
This result changed the final upstream design decision.
10. Returning to a Simpler Upstream Baseline
Because same-priority FIFO ordering is an important correctness property, I decided to use a more conservative TX baseline for the current upstream driver.
The current configuration uses only one hardware TX Message Object:
RTEMS CAN TX queue
|
v
MO9
|
v
CAN bus
The current Message Object allocation is:
RX:
MO1-MO8
TX:
MO9
This intentionally limits the number of outstanding hardware transmissions to one.
The driver still keeps the generic TX ownership structure:
txb_info[]
and the TX code is still written around DCAN_TX_MO_COUNT.
Therefore the architecture can be extended to multiple TX Message Objects later, but the current configuration enables only one TX Message Object.
This gives the current driver a simple property:
RTEMS queue order
|
v
single hardware TX request
|
v
CAN transmission order
It avoids having several same-priority frames concurrently active in different hardware TX Message Objects.
The TX and RX interface registers are also separated:
IF1 -> RX and deferred Message Object / interrupt processing
IF2 -> TX
This separation was kept because it makes TX/RX register interaction simpler and avoids unnecessary contention between the two paths.
11. Same-Priority FIFO Verification
I tested the single-TX-MO baseline with 64 frames.
All frames used:
CAN ID = 0x123
RTEMS priority = 0
sequence = 0..63
The expected order was:
0 1 2 3 4 5 ... 63
The result was:
Expected frames: 64
Received frames: 64
Order errors: 0
Invalid frames: 0
CAN error frames: 0
Verified order: 0..63
The received sequence was exactly:
0 1 2 3 4 5 6 7 8 9 ...
...
60 61 62 63
The test passed.
This confirms that the current single-TX-MO baseline preserves FIFO ordering for frames from the same RTEMS priority class.
It also repeatedly exercises:
RTEMS queue
|
v
test_outslot()
|
v
MO9 transmission
|
v
TX completion
|
v
local echo
|
v
free_outslot()
|
v
next frame
so the test verifies more than only the first hardware transmission.
12. Current Upstream TX Design
The current upstream design is intentionally simple.
The TX path does:
RTEMS software TX queue
|
| rtems_can_queue_test_outslot()
v
queue slot
|
v
bind slot / edge / priority
to TX ownership structure
|
v
write frame to MO9 using IF2
|
v
hardware transmission
|
v
TX completion interrupt
|
v
local echo + statistics
|
v
clear driver ownership
|
v
rtems_can_queue_free_outslot()
The current TX code still contains the future scheduling direction:
/*
* TODO: Call rtems_can_queue_pending_outslot_prio() and rearrange
* TX message objects when a higher-priority class would be blocked
* by a lower-priority one.
*/
This means the current upstream implementation focuses on correctness and FIFO ordering first.
Advanced multi-TX-MO scheduling can be added later without changing the basic queue-slot ownership model.
13. Current Latency Test Topology
During the TX experiments, I also created a latency test using both AM335x DCAN controllers.
The topology was:
DCAN0 (/dev/can0)
|
+-- high-priority sender
| CAN ID = 0x020
|
+-- low-priority sender
CAN ID = 0x700
DCAN1 (/dev/can1)
|
+-- medium-priority sender
| CAN ID = 0x500
|
+-- receiver
All controllers were connected to the same physical CAN bus.
The receiver was mainly used to measure the high-priority ID 0x020
traffic.
The test also used paced traffic instead of unlimited write loops.
For example:
send 4 frames
|
v
wait 10 ms
|
v
send next 4 frames
This made the workload more balanced and prevented one RTEMS task from completely dominating the others.
One long-running test successfully reached:
high-priority sent: 1000
high-priority received: 1000
low-priority sent: 1000
medium-priority sent: 1000
This test was useful for checking long-running driver behavior and ownership stability.
However, the later same-priority FIFO test showed that long-running stability alone is not enough to prove software queue ordering when multiple hardware TX Message Objects are active.
That distinction became important for the final upstream design.
14. What the Failed Experiments Taught Me
The preemption experiments did not become part of the final upstream baseline, but they were still very useful.
They revealed several important problems.
14.1 Priority is not only sorting
At first, TX priority looked like a sorting problem:
high priority first
low priority later
But in a real driver, the problem also includes:
RTEMS software queues
+
queue-slot ownership
+
DCAN hardware Message Objects
+
asynchronous TX completion
+
interrupt processing
+
worker scheduling
+
hardware abort
+
CAN arbitration
14.2 Hardware parallelism creates software complexity
Multiple hardware TX buffers can improve flexibility, but they also mean that several software queue slots may be outstanding at the same time.
This makes ordering and ownership more difficult.
14.3 A successful small test is not enough
Several controlled preemption tests worked correctly.
However, larger tests exposed:
duplicate frames
reordered frames
ownership problems
timing-sensitive behavior
This showed why stress testing is important before putting a scheduling feature into an upstream driver.
14.4 Debug output can hide bugs
The behavior difference with and without printf() was a useful lesson.
A driver should not depend on debug output timing.
14.5 A smaller correct baseline is better than a larger unstable one
The final upstream baseline intentionally supports fewer simultaneous TX Message Objects.
This is a tradeoff:
less hardware parallelism
|
v
simpler ownership
+
deterministic same-priority FIFO
+
easier upstream review
For the current stage of the driver, this is the better engineering choice.
15. Possible Future Work
The multi-TX-MO and preemption experiments still show an interesting future development problem:
How can an RTOS CAN driver use multiple hardware TX Message Objects while preserving software queue priority, FIFO ordering, and correct queue ownership?
A future implementation can investigate:
- RTEMS software queue priority.
- FIFO ordering within the same priority class.
- DCAN hardware Message Object scheduling.
- CAN identifier arbitration.
- Runtime TX preemption.
- Hardware abort and rescheduling.
- Queue-slot ownership during asynchronous completion.
The planned upstream direction is to use:
rtems_can_queue_pending_outslot_prio()
to determine whether a higher-priority software queue is waiting.
If a higher-priority class would otherwise be blocked by lower-priority frames already assigned to hardware Message Objects, the driver could rearrange or abort selected hardware TX requests.
However, a future implementation must preserve:
same-priority FIFO
no frame loss
no frame duplication
correct queue-slot ownership
stable behavior without debug timing
Possible measurements include:
- high-priority frame latency
- average latency
- worst-case latency
- p95 latency
- p99 latency
- TX throughput
- number of preemptions
- number of hardware aborts
- low-priority starvation
- CPU overhead
- queue occupancy
- frame loss
- frame duplication
- ordering errors
- ownership errors
This may also be useful for future research on priority-aware CAN scheduling in RTEMS.
Conclusion
The TX priority work started as an attempt to improve the DCAN multi-buffer implementation.
The experiments included:
- multiple TX Message Objects
- RTEMS software queue priority
- CAN-ID priority
- hardware TX abort
- priority preemption
- Message Object reordering
- TX-window compaction
- queue-slot ownership tracing
- latency testing
- same-priority FIFO testing
The most important lesson was that TX priority is not only a sorting problem.
It is also an ownership and synchronization problem between:
RTEMS software queues
+
DCAN hardware Message Objects
+
asynchronous interrupts
+
worker scheduling
+
CAN bus arbitration
The experiments also showed that simply using multiple independent TX Message Objects does not automatically preserve the RTEMS software queue order.
For the current upstream driver, I therefore chose a conservative single-TX-MO baseline.
The current configuration uses:
RX: MO1-MO8
TX: MO9
A 64-frame same-priority test verified:
64 frames sent
64 frames received
0 ordering errors
0 invalid frames
0 CAN error frames
FIFO sequence 0..63 preserved
The code still keeps a structure that can support multiple TX Message Objects in the future, but only one TX Message Object is currently enabled.
This provides a simple and deterministic upstream baseline while the more advanced multi-TX-MO priority-aware scheduler remains future work.
The preemption experiments were therefore still valuable even though the experimental implementation is not included in the upstream driver.
They helped identify the scheduling, synchronization, ordering, and queue-ownership problems that a future multi-TX-MO implementation will need to solve.
DCAN TX Priority Ring Investigation and Validation
1. Background
TX scheduling was one of the most difficult parts of the DCAN driver project.
The RTEMS CAN stack can provide multiple software TX queues with different priority classes. The driver retrieves the oldest frame from the highest-priority active queue by calling:
rtems_can_queue_test_outslot(qends, &qedge, &slot);
The returned qedge->edge_prio tells the driver which RTEMS CAN
queue priority class the frame belongs to.
For the current experiment, three RTEMS CAN priority classes are used:
priority 2: high priority 1: middle priority 0: low
The DCAN TX Message Objects are divided into three fixed groups:
RTEMS priority 2 -> MO9 / MO10 RTEMS priority 1 -> MO11 /
MO12 RTEMS priority 0 -> MO15 / MO16 / MO17 / MO18
Each priority class is managed as an independent TX ring. The current experimental configuration does not require all rings to have the same size: the high-priority ring remains small, while the low-priority ring has been expanded to four Message Objects.
2. Why the Original Multi-MO Approach Was Difficult
The first multi-MO implementation treated several TX Message Objects as independent free hardware buffers.
For example:
hardware: MO9 = 0 MO10 = 1 MO11 = 2 MO12 = 3
All four Message Objects could have TxRqst set at the same time.
After the frames were placed into independent hardware Message Objects,
the original software FIFO order was no longer automatically represented
by the hardware.
Testing showed receive patterns such as:
Therefore, simply filling multiple Message Objects was not enough to preserve RTEMS queue order.
Another problem appeared when low-priority frames occupied all available hardware TX Message Objects. If a high-priority frame arrived later, the driver would need to decide whether to abort a low-priority MO, move a frame to another MO, push a queue slot back, or rearrange the hardware window.
This also made RTEMS queue-slot ownership difficult to manage correctly.
3. Queue Slot Ownership
The current driver keeps both the RTEMS queue slot and its queue edge in each hardware TX buffer record:
struct rtems_can_queue_edge *edge; int prio; unsigned int mobj;
bool active; bool cached; bool done; };
The ownership model is:
retrieves slot | v DCAN Message Object owns slot | v hardware
transmission completes | v ring retires the frame | v
rtems_can_queue_free_outslot() | v slot is returned to RTEMS
This is important because the slot is not free immediately after it is copied into a hardware Message Object. The driver must keep the slot until the hardware transmission has completed and the frame has been retired in ring order.
The new design avoids moving the same slot between different Message Objects.
4. Priority-Specific Hardware Rings
Instead of sharing one hardware TX pool among all priority classes, the new design gives each RTEMS priority class its own fixed hardware resources:
p2 p1 p0
\| | |
v v v
ring[2] ring[1] ring[0]
\| | |
MO9/10 MO11/12 MO13/14
This has two important advantages.
4.1 Same-Priority Ordering
Each priority class has a small ordered software view over its fixed hardware Message Objects.
struct dcan_tx_ring { unsigned int first_index; unsigned int
obj_num; unsigned int head; unsigned int tail; };
The number of outstanding frames is:
used = head - tail;
The physical Message Object position is calculated only when needed:
position = logical_position % obj_num;
For a two-object ring:
logical position: 0 1 2 3 4 5 ... physical object: A B A B A B ...
head is the next producer position.
tail is the oldest frame that has not yet been retired.
4.2 High-Priority Frames Have Reserved Hardware Resources
A low-priority backlog can fill MO13/MO14, but it cannot use MO9/MO10.
Therefore:
p0 backlog | +-> MO13 +-> MO14 +-> remaining frames stay
in RTEMS software queue
later p2 frame arrives | +-> MO9 or MO10
The high-priority frame does not require the driver to abort, move, or rearrange the low-priority Message Objects.
5. Ring Retirement
Hardware completion and software retirement are different events.
When a TX completion is detected, the driver marks the corresponding Message Object:
active = false done = true
The driver then calls the ring retirement logic.
The retirement function always starts from tail.
For example:
FIFO order:
A -> MO13 B -> MO14
head = 2 tail = 0
If completion of B is observed first:
A.done = false B.done = true
the driver does not retire B first.
The current consumer is still:
tail -> A
Since A is not complete, retirement stops.
After A is complete:
retire A tail = 1
retire B tail = 2
This preserves software ownership order even if completion observation is not perfectly ordered.
A frame is removed from ownership only when:
1. the ring is not empty;
2. the Message Object at tail owns a valid slot;
3. that frame is marked done;
4. no other Message Object owns the same RTEMS queue slot.
After these checks, the driver updates TX statistics, generates local
echo, clears Message Object ownership, calls
rtems_can_queue_free_outslot(), and increments tail.
6. Same-Priority FIFO Validation
A focused test used only RTEMS priority 2 and the MO9/MO10 ring.
The test transmitted 64 frames with sequence numbers:
200..263
Both Message Objects were used:
MO9 -> 200, 202, 204, ...
MO10 -> 201, 203, 205, ...
The final result was:
Expected frames: 64 Received frames: 64 FIFO errors: 0
Invalid frames: 0 CAN error frames: 0
This showed that the current two-object ring management can preserve same-priority FIFO order under the tested workload.
7. Three-Priority Validation
The driver was then tested with three RTEMS CAN priority classes:
p2 -> MO9/MO10 p1 -> MO11/MO12 p0 -> MO13/MO14
Three sender tasks used the same RTEMS task priority so that the RTEMS task scheduler would not become the main experimental variable.
Only the CAN queue priorities were different.
The test sent 256 frames for each priority class.
The result was:
p2: received=256, FIFO errors=0 p1: received=256, FIFO
errors=0 p0: received=256, FIFO errors=0
invalid frames: 0 CAN error frames: 0
This demonstrated that all three fixed priority rings could operate together while preserving FIFO order inside each priority class.
8. High-Priority Bypass Validation
The most important targeted test asked:
Can a high-priority frame arriving later bypass an existing low-priority backlog without rearranging the low-priority Message Objects?
DCAN0 was the sender and DCAN1 was the receiver.
DCAN1 remained stopped so that DCAN0 could not receive acknowledgements.
Sixteen low-priority frames were queued:
p0 CAN ID = 0x600 sequence = 100..115
The driver loaded the first two low-priority frames into the dedicated low-priority ring:
MO13 = p0 seq100 ACTIVE MO14 = p0 seq101 ACTIVE
The remaining low-priority frames stayed in RTEMS software queue.
A high-priority frame was then queued later:
p2 CAN ID = 0x020 sequence = 1
The driver immediately placed it into the high-priority ring:
MO9 = p2 seq1 ACTIVE
The hardware state before restoring ACK was:
MO9 = HIGH seq1 MO13 = LOW seq100 MO14 = LOW seq101
software p0 backlog: 102..115
No low-priority Message Object was aborted or rearranged.
After DCAN1 was started, the receive order started with:
HIGH seq1 LOW seq100 LOW seq101 LOW seq102 ... LOW seq115
The final result was:
expected total: 17 received total: 17 low received: 16/16
high received: 1/1 low FIFO errors: 0 high receive position: 0 low
frames before high: 0 high-priority bypass: PASS invalid frames: 0 CAN
error frames: 0
This directly demonstrated that dedicated priority-specific Message Object groups provide an independent hardware path for a later arriving high-priority frame.
This should be described as priority bypass, not frame preemption. CAN transmission itself is non-preemptive. A frame already transmitting on the bus cannot be interrupted in the middle.
9. Why the Current Design Is Simpler
The old approach used a shared hardware TX pool:
MO9 MO10 MO11 MO12
If low-priority traffic occupied the whole pool, inserting a high-priority frame required complex decisions about aborting, moving, and reassigning ownership.
The current design changes the problem:
p2 owns MO9/MO10 p1 owns MO11/MO12 p0 owns MO13/MO14
Therefore, low-priority traffic cannot consume the hardware resources reserved for higher-priority traffic.
The design avoids:
moving RTEMS queue slots between Message Objects aborting
low-priority frames only to create space rebuilding the complete
hardware TX window complex slot-ownership reassignment
10. RTEMS CAN Queue Priority
RTEMS CAN queues, also called edges, can belong to different priority classes.
The official RTEMS CAN driver documentation states that
rtems_can_queue_test_outslot() retrieves the oldest ready slot from
the highest-priority active queue.
This priority is the software CAN queue priority, not CAN identifier arbitration priority and not RTEMS task priority.
For the current experiments:
2 = high 1 = middle 0 = low
Therefore:
queue priority 2 | v qedge->edge_prio = 2 | v tx_ring[2]
| v MO9/MO10
Do not confuse this with RTEMS task priority, where smaller numeric values normally represent higher task priority.
11. Testing Without TX Ring Trace
During development:
#define DCAN_TX_RING_TRACE 1
enables output such as:
DCAN TX SUBMIT ... DCAN TX DONE ... DCAN TX RETIRE ...
The next validation step is:
#define DCAN_TX_RING_TRACE 0
and then rebuild and repeat the tests.
The purpose is to verify that the driver still behaves correctly without
frequent printf() calls changing worker timing.
Recommended regression tests:
Test 1: Same-Priority FIFO
priority: 2 ring: MO9/MO10 frames: 64 expected: FIFO errors = 0
Test 2: Three-Priority Stress
p2 -> MO9/MO10 p1 -> MO11/MO12 p0 -> MO13/MO14
expected: all frames received FIFO errors = 0 for all classes
Test 3: Low-Backlog / High-Arrival Bypass
p0 backlog first p2 arrives later
expected: high bypasses remaining p0 software backlog low FIFO remains
correct
If these tests continue to pass with trace disabled, it provides stronger evidence that the behavior comes from the scheduling design itself rather than timing changes caused by debug printing.
12. Expanded Low-Priority Ring and Trace-Free Validation
After the two-object priority-ring design was working, the low-priority ring was expanded from two Message Objects to four:
p2 high -> MO9 / MO10
p0 low -> MO15 / MO16 / MO17 / MO18
The important design rule was kept unchanged:
Increasing the ring size does not increase the number of simultaneously ACTIVE Message Objects inside one priority class.
Instead, the larger ring is used as a hardware-side prefetch/cache window.
For the four-object p0 ring, the intended state before acknowledgements are restored is:
MO15 = seq100 ACTIVE
MO16 = seq101 CACHED
MO17 = seq102 CACHED
MO18 = seq103 CACHED
RTEMS software queue:
seq104 .. seq115
Therefore, for a ring with obj_num = 4, there is still only one ACTIVE transmission candidate from that priority class, while up to three additional frames may already be copied into DCAN Message RAM as CACHED frames.
The rotation is conceptually:
MO15: 100 ACTIVE ---- DONE
MO16: 101 CACHED ---- ACTIVE ---- DONE
MO17: 102 CACHED ---------------- ACTIVE ---- DONE
MO18: 103 CACHED ---------------------------- ACTIVE ---- DONE
MO15: 104 CACHED ---------------------------------------- ACTIVE ...
This keeps same-priority hardware arbitration from reordering several ACTIVE frames while allowing the driver to pre-stage future frames.
12.1 Trace-Free Test
The validation was repeated with:
#define DCAN_TX_RING_TRACE 0
This was important because earlier experiments showed that frequent debug printf() calls could change worker timing and hide ordering problems.
The test configuration was:
sender: DCAN0 /dev/can0
receiver: DCAN1 /dev/can1
LOW:
RTEMS queue priority = 0
CAN ID = 0x600
ring = MO15-MO18
sequence = 100..115
HIGH:
RTEMS queue priority = 2
CAN ID = 0x020
ring = MO9/MO10
sequence = 1
DCAN1 remained stopped while the low-priority backlog and the later high-priority frame were prepared.
Before restoring acknowledgements, the expected state was:
MO9 = HIGH seq1 ACTIVE
MO15 = LOW seq100 ACTIVE
MO16 = LOW seq101 CACHED
MO17 = LOW seq102 CACHED
MO18 = LOW seq103 CACHED
remaining LOW software backlog:
seq104..115
After DCAN1 was started, the observed receive order was:
HIGH seq1
LOW seq100
LOW seq101
LOW seq102
LOW seq103
...
LOW seq115
The result was:
expected total: 17
received total: 17
low received: 16/16
high received: 1/1
low FIFO errors: 0
high receive position: 0
low frames before high: 0
high-priority bypass: PASS
invalid frames: 0
CAN error frames: 0
This test is stronger than the earlier trace-enabled result because the driver preserved both priority bypass and low-priority FIFO ordering without TX-ring debug printing affecting the timing.
12.2 What the Larger Ring Means
The larger ring should not be interpreted as:
four p0 Message Objects competing to transmit simultaneously
The design is instead:
one ACTIVE
+
up to obj_num - 1 CACHED
For the current p0 ring:
obj_num = 4
1 ACTIVE
+
up to 3 CACHED
This separates two concerns:
ACTIVE -> hardware transmission eligibility
CACHED -> hardware-side prefetch / ownership storage
The main purpose of increasing ring size is therefore not to create more same-priority hardware transmission candidates. It is to increase the number of frames that can be prefetched from the RTEMS software queue into DCAN Message RAM while still preserving explicit FIFO activation order.
12.3 Potential Performance Benefit
A larger cache window may improve TX pipeline efficiency, but it does not increase the configured CAN bitrate.
Without prefetching, after a completion the driver may need to perform the complete path:
TX completion
|
v
worker processing
|
v
retrieve next RTEMS queue slot
|
v
copy frame through DCAN IF registers
|
v
load Message Object
|
v
set TxRqst
With cached Message Objects, the next frame has already been copied into DCAN Message RAM:
TX completion
|
v
retire old tail
|
v
next tail is CACHED
|
v
activate cached Message Object
The possible benefit is therefore reduced dependence on immediate queue retrieval and Message Object loading after every completion.
Potential metrics for a future benchmark include:
1. total time to transmit N frames
2. inter-frame gap
3. TX-completion-to-next-activation latency
4. worker scheduling sensitivity / jitter
5. CPU processing overhead
Useful configurations to compare are:
ring size = 1
ring size = 2
ring size = 4
ring size = 8
The expectation should not be stated as "larger is always faster." The current one-ACTIVE policy still requires software to activate the next cached frame after completion. The performance benefit therefore needs to be measured experimentally.
A more precise description of the architecture is:
The priority ring maintains an ordered software view over a fixed set of DCAN TX Message Objects. One Message Object is transmission-eligible at a time for each priority class, while additional occupied Message Objects can serve as prefetched cached frames.
13. Long-Run Same-Priority FIFO Stress Validation
After the targeted FIFO and priority-bypass tests passed, a longer stress test was added using the mentor test's core idea: transmit a large sequence of frames, encode a sequence number into the payload, and verify the receive order explicitly.
The test configuration was:
sender: DCAN0 /dev/can0
receiver: DCAN1 /dev/can1
CAN bitrate: 125000 bit/s
fixed CAN ID: 0x600
frames per test: 2000
expected sequence: 0..1999
burst size: 100
burst delay: 1 ms
TX ring trace: disabled
The sequence number was stored in data[0..3]. The receiver
reconstructed the sequence and compared every frame with the expected
value. A fixed CAN ID was used deliberately so this experiment focused
on driver ordering rather than CAN-ID arbitration.
The long run repeatedly exercised head, tail, physical Message
Object wrap-around, ACTIVE -> DONE -> RETIRE, CACHED -> ACTIVE, and
refill from the RTEMS software queue.
13.1 Priority 0: MO15-MO18
RTEMS queue priority: 0
TX ring: MO15-MO18
ring size: 4
sent frames: 2000
received frames: 2000
order errors: 0
invalid frames: 0
CAN error frames: 0
timeout: no
verified sequence: 0..1999
elapsed time: 1899282708 ns
Result: PASS
13.2 Priority 1: MO11-MO14
RTEMS queue priority: 1
TX ring: MO11-MO14
ring size: 4
sent frames: 2000
received frames: 2000
order errors: 0
invalid frames: 0
CAN error frames: 0
timeout: no
verified sequence: 0..1999
elapsed time: 1899274708 ns
Result: PASS
13.3 Priority 2: MO9-MO10
RTEMS queue priority: 2
TX ring: MO9-MO10
ring size: 2
sent frames: 2000
received frames: 2000
order errors: 0
invalid frames: 0
CAN error frames: 0
timeout: no
verified sequence: 0..1999
elapsed time: 1899186625 ns
Result: PASS
13.4 Combined Result
RTEMS TX Message Ring size Frames Order errors Result priority Objects
p2 MO9-MO10 2 2000 0 PASS
p1 MO11-MO14 4 2000 0 PASS
p0 MO15-MO18 4 2000 0 PASS
The current variable-size priority-ring implementation therefore preserved same-priority FIFO ordering for all three RTEMS CAN queue priority classes under the tested long-run workload.
This supports the current invariant:
Within one priority ring:
at most one Message Object is ACTIVE
+
remaining occupied Message Objects may be CACHED
+
activation follows logical FIFO order
+
retirement follows tail order
14. Interpretation of the Timing Results
The total times were nearly identical:
Priority Ring size Elapsed time
p0 4 1.899282708 s p1 4 1.899274708 s p2 2 1.899186625 s
These measurements do not show a meaningful throughput improvement from ring size 4 over ring size 2. That is consistent with the architecture because increasing ring size does not create more same-priority transmission candidates.
ring size 2:
1 ACTIVE + up to 1 CACHED
ring size 4:
1 ACTIVE + up to 3 CACHED
The larger ring increases hardware-side prefetch/cache depth. Any performance benefit is therefore more likely to appear in completion-to-next-activation latency, inter-frame gap, worker scheduling sensitivity, CPU overhead, or burst absorption. The 2000-frame experiment should primarily be treated as a correctness and stability test.
15. Current Validated TX Architecture
RTEMS CAN TX queues
|
+-- priority 2 (high)
| -> ring[2] -> MO9-MO10
|
+-- priority 1 (middle)
| -> ring[1] -> MO11-MO14
|
+-- priority 0 (low)
-> ring[0] -> MO15-MO18
The scheduling path is:
RTEMS queue priority
|
v
select priority ring
|
v
prefetch frames into that ring
|
v
only the oldest eligible frame becomes ACTIVE
|
v
later frames remain CACHED
|
v
TX completion
|
v
tail-ordered retirement
|
v
activate next cached frame
The architecture has now been validated in two complementary ways:
- Within-priority ordering: p0, p1, and p2 each transmitted 2000 frames with zero ordering errors.
- Cross-priority bypass: a later high-priority frame used its reserved hardware ring and bypassed a low-priority backlog while the low-priority stream remained FIFO-correct.
The second property should be described as priority bypass, not frame preemption. CAN transmission itself remains non-preemptive.
16. Why the Current Design Solves the Earlier Problems
The earlier multi-MO design exposed several same-priority Message
Objects as independent hardware transmission candidates. Once frames
were distributed among them, software FIFO order was no longer
sufficient to determine hardware transmission order. Observed patterns
such as 0 2 1 3 demonstrated this problem.
The current design instead preserves an explicit logical order:
head = next producer position
tail = oldest frame not yet retired
It also separates Message Object state:
ACTIVE = eligible for hardware transmission
CACHED = frame and RTEMS ownership are staged, but TxRqst is not yet eligible
Therefore, expanding a ring from two to four Message Objects increases staged frames without increasing same-priority hardware candidates.
Resource partitioning also simplifies priority handling. Low-priority traffic cannot consume the Message Objects reserved for p1 or p2, so a later high-priority frame does not require moving, aborting, or reassigning low-priority RTEMS queue slots.
17. Current Conclusions
What did not work well
- Treating multiple DCAN TX Message Objects as unrelated free buffers did not automatically preserve RTEMS software FIFO order.
- A shared hardware TX pool complicated later high-priority insertion because lower-priority traffic could occupy the available hardware resources.
What worked better
The successful experimental architecture combines:
- fixed Message Object groups for each RTEMS software priority;
- an independent TX ring for each priority;
- explicit RTEMS queue-slot ownership;
- logical
head/tailproducer-consumer management; - one
ACTIVEMessage Object per priority ring; - additional occupied Message Objects as
CACHEDframes; - tail-ordered retirement;
- reserved hardware resources for higher-priority traffic.
The current variable-size mapping is:
p2 high -> MO9-MO10 ring size 2
p1 middle -> MO11-MO14 ring size 4
p0 low -> MO15-MO18 ring size 4
The current experiments have demonstrated:
- same-priority FIFO preservation for p0, p1, and p2;
- 2000/2000 frames received for each long-run priority test;
- zero ordering errors in all three long-run tests;
- repeated ring wrap and refill under the tested workload;
- low-priority FIFO preservation with a later high-priority arrival;
- high-priority bypass using reserved hardware resources;
- no need to rearrange low-priority Message Objects in the tested bypass case;
- correct behavior with
DCAN_TX_RING_TRACEdisabled.
A concise description of the architecture is:
The driver maintains an ordered software view over fixed DCAN TX Message Objects for each RTEMS CAN queue priority. Only one Message Object per priority ring is transmission-eligible at a time, while additional occupied Message Objects may cache prefetched frames. This preserves same-priority FIFO order while reserving an independent hardware path for higher-priority traffic.
18. Remaining Work
- Repeat the 2000-frame p0, p1, and p2 tests multiple times to check repeatability.
- Run a simultaneous three-priority long-duration stress test using
the new
2/4/4ring sizes. - Test a p0 backlog followed by a p1 arrival.
- Test a p1 backlog followed by a p2 arrival.
- Consider a changing-CAN-ID test to study interaction between driver priority and CAN bus arbitration.
- Benchmark ring sizes 1, 2, 4, and 8 using more sensitive metrics than total TX time.
- Measure completion-to-next-activation latency and inter-frame gap.
- Keep the one-
ACTIVE-per-priority invariant while evaluating larger cache windows. - Remove experimental trace and validation code before upstream submission.
- Discuss with mentors whether per-priority Message Object ranges and ring sizes should remain fixed, become configurable, or be simplified for the first upstream version.
The current results provide strong functional evidence for the priority-specific TX-ring architecture. They should still be described as experimental validation rather than as a final upstream scheduling policy.
GSoC 2026 Midterm Evaluation
Project
Organization: RTEMS
Project: Bosch DCAN Driver for RTEMS on BeagleBone Black
Contributor: Ning Zhang
Project Progress at Midterm
At the midterm, I had completed the basic bring-up and hardware validation of the Bosch DCAN controller on the BeagleBone Black (AM335x).
The main work completed at this stage included:
- DCAN clock and pin configuration on the BeagleBone Black.
- Basic DCAN register configuration.
- CAN transmission from RTEMS to Linux.
- CAN reception from Linux to RTEMS.
- Interrupt and
DCAN_INTtesting. - Initial integration with the RTEMS CAN framework.
- Building the driver with RTEMS and checking the code with warnings treated as errors.
- Testing the driver on real BeagleBone Black hardware.
My hardware test setup used RTEMS 7 on the BeagleBone Black and Linux SocketCAN with a CANable adapter. The CAN bus was tested at 125 kbit/s. Basic TX and RX communication worked successfully.
I also opened and continued working on the RTEMS merge request. Based on mentor feedback, I started improving the code structure. An important change was separating the generic Bosch DCAN driver code from AM335x-specific clock and pinmux code.
Communication With My Mentor
I communicated with my mentor regularly through the RTEMS development and review process. The merge request was kept as a draft while the implementation was still being developed.
The mentor feedback helped me understand not only how to make the hardware work, but also how a driver should be designed for upstream RTEMS.
Most Challenging Part So Far
The most challenging part was understanding the Bosch DCAN hardware and connecting its Message Object model with the RTEMS CAN framework.
At first, much of the work was low-level hardware bring-up: clocks, pinmux, registers, Message Objects, and interrupts. After basic communication worked, the challenge became designing the driver in a way that fits the RTEMS CAN stack.
I also learned that hardware functionality alone is not enough for an upstream driver. Code organization, generic interfaces, error handling, synchronization, and maintainability are also important.
What I Planned to Work on After Midterm
- Improve RTEMS CAN stack integration.
- Use the RTEMS
struct can_framethroughout the driver. - Improve the interrupt and worker-task design.
- Implement RX FIFO handling with multiple Message Objects.
- Improve TX completion handling.
- Add CAN error reporting and statistics.
- Improve controller start/stop behavior.
- Continue hardware testing.
- Respond to merge-request review comments and prepare the driver for upstream review.
Midterm Reflection
I was happy that the basic DCAN communication was working on real hardware by the midterm. The project also gave me a much better understanding of RTEMS driver development and CAN controller hardware.
The next stage was more about making the implementation robust and suitable for the RTEMS CAN framework.
GSoC 2026 Final Evaluation
Project
Organization: RTEMS
Project: Bosch DCAN Driver for RTEMS on BeagleBone Black
Contributor: Ning Zhang
Link to My GSoC Work
Final work product: Local Repo
RTEMS merge request: RTEMS GitLab merge request
What I Completed
During GSoC 2026, I developed and tested a Bosch DCAN driver for RTEMS 7 using the BeagleBone Black AM335x platform.
The main completed work includes:
- Bosch DCAN controller support for RTEMS.
- AM335x BeagleBone Black clock and pinmux support.
- DCAN0 and DCAN1 registration and configuration.
- Integration with the RTEMS CAN framework.
- CAN frame transmission and reception.
- Interrupt-driven receive handling.
- Worker-task processing outside the interrupt handler.
- RX FIFO handling using multiple DCAN Message Objects.
- Detection of the DCAN
MsgLstcondition and RX overflow reporting. - TX queue integration and queue-slot ownership handling.
- TX completion handling and local echo.
- CAN TX/RX statistics.
- CAN controller error-state and protocol-error reporting.
- Controller start and stop synchronization.
- Cleanup of pending TX frames when stopping the controller.
- Hardware testing with Linux SocketCAN and a CANable adapter.
The main hardware tests used a CAN bitrate of 125 kbit/s.
TX Ordering
TX scheduling was one of the most challenging parts of the project.
I first extended the TX path to use multiple hardware TX Message Objects. However, testing showed that simply filling multiple Message Objects did not guarantee that the transmission order would match the RTEMS software queue order.
I also experimented with priority-aware TX scheduling and Message Object rearrangement. Some small tests showed the expected ordering, but larger tests exposed stability problems, including reordered or duplicated frames.
Because this experimental implementation was not stable enough for upstream submission, it is not included in the current upstream driver.
For the current baseline, I use one hardware TX Message Object. This keeps the TX path simple and preserves FIFO ordering within the same RTEMS CAN queue priority class.
I verified this with a 64-frame same-priority test. All frames used the same CAN ID and queue priority, with sequence numbers from 0 to 63.
The result was:
- 64 frames sent and received.
- 0 ordering errors.
- 0 invalid frames.
- 0 CAN error frames.
- Received sequence:
0..63.
The test passed.
Support for multiple TX Message Objects with priority-aware scheduling remains future work.
Most Challenging Parts
The most challenging parts were understanding the DCAN Message Object architecture, connecting DCAN hardware behavior with the RTEMS CAN queue model, designing interrupt and worker processing, handling RX FIFO overflow and CAN errors, managing TX queue-slot ownership, and preserving TX ordering.
These problems helped me understand that driver development is not only register programming. Synchronization, queue ownership, error paths, hardware behavior, and maintainable architecture are equally important.
Current State and Remaining Work
The driver can communicate on real BeagleBone Black hardware and is integrated with the RTEMS CAN framework.
The current upstream baseline intentionally uses one TX Message Object. This provides a simple and stable TX path and preserves FIFO ordering within the same priority class.
The main remaining TX improvement is support for multiple TX Message
Objects with priority-aware scheduling. The planned direction is to use
rtems_can_queue_pending_outslot_prio() and rearrange TX Message Objects
when a higher-priority class would otherwise be blocked by a
lower-priority one.
I experimented with this design during GSoC, but the experimental version was not stable enough to include in the upstream driver. Therefore, this work is left as a TODO instead of adding unstable code to the final baseline.
Additional changes may also come from the normal RTEMS upstream review process and further hardware testing.
GSoC Experience
My favorite part of GSoC was seeing a low-level driver progress from register-level experiments to real CAN communication and then to integration with the RTEMS CAN framework.
The project improved my skills in embedded systems, RTOS driver development, CAN bus hardware, interrupt handling, debugging on real hardware, Git, code review, and open-source development.
I plan to continue contributing to RTEMS and improving the driver based on upstream feedback.
GSoC 2026 Final Report: Bosch DCAN Driver for RTEMS
Organization: RTEMS
Contributor: Ning Zhang
Project: Bosch DCAN Driver for RTEMS on BeagleBone Black
Platform: BeagleBone Black (TI AM335x)
RTOS: RTEMS 7
1. Project Overview
The goal of this Google Summer of Code project was to develop Bosch DCAN controller support for RTEMS and validate it on the BeagleBone Black.
The TI AM335x processor contains DCAN controllers based on the Bosch CAN architecture. The project required both low-level hardware support and integration with the RTEMS CAN framework.
The work started with register-level bring-up and gradually moved toward an RTEMS driver architecture with interrupts, queues, error handling, RX FIFO support, TX completion, and controller lifecycle management.
2. Development and Test Environment
- RTEMS 7.
- BeagleBone Black with TI AM335x.
- Bosch DCAN controller.
- Linux SocketCAN.
- CANable USB-to-CAN adapter.
- Physical CAN bus with proper termination.
- 125 kbit/s CAN bitrate for the main driver tests.
Linux cansend and candump were useful during early hardware
validation. Later tests also used both DCAN controllers on the
BeagleBone Black.
3. Work Completed
3.1 Basic DCAN Bring-up
The first stage included enabling the DCAN clock, configuring AM335x pinmux, accessing DCAN registers, configuring bit timing and Message Objects, and testing CAN TX and RX.
Successful bidirectional communication confirmed the basic hardware configuration.
3.2 RTEMS CAN Framework Integration
The driver was integrated with the RTEMS CAN framework instead of keeping an application-specific frame path.
This work included RTEMS CAN frame structures, CAN device registration, TX/RX queues, TX queue-slot ownership, local echo, and CAN statistics.
3.3 Interrupt and Worker Design
The driver uses a short interrupt handler and performs most processing in a worker task. The worker processes controller status, RX events, TX completion, and pending TX work before interrupts are re-enabled.
3.4 RX FIFO
Multiple DCAN Message Objects are used for receive processing. The
implementation checks hardware state including NewDat, MsgLst, and
end-of-buffer information.
The driver reports MsgLst as an RX overflow through the RTEMS CAN
framework and statistics.
3.5 Error Handling
The driver handles controller states including error active, error warning, error passive, and bus off. Protocol errors such as ACK, bit, stuff, form, and CRC errors are also reported where appropriate.
3.6 TX Queue and Completion Handling
The TX path obtains frames from RTEMS CAN queues and keeps ownership of a queue slot until transmission completes or the controller is stopped.
After completion, the driver updates statistics, provides local echo when required, releases the queue slot, and continues pending TX work.
The controller-stop path also cleans up pending transmissions.
3.7 DCAN0 and DCAN1
Support was extended so that DCAN0 and DCAN1 could both be registered and tested.
4. TX Ordering Investigation
TX scheduling was one of the most challenging parts of the project.
The initial driver design did not implement TX preemption or Message Object rearrangement. When I extended the TX path to use multiple hardware TX Message Objects, I found that simply filling several free Message Objects from the RTEMS software queue did not guarantee that the transmission order would match the software queue order.
For example, in a same-priority test, 64 frames were queued in the order:
'''text 0 1 2 3 4 5 6 7 ... '''
With multiple TX Message Objects active, the received order included patterns such as:
'''text 0 2 1 3 4 6 5 7 8 10 9 11 '''
This showed that assigning frames to multiple hardware Message Objects was not enough to preserve FIFO ordering.
I then experimented with a more advanced TX scheduling design. The idea was to track the RTEMS queue priority associated with each hardware TX Message Object and rearrange pending Message Objects when a higher-priority frame arrived.
Some small tests showed the expected ordering, but larger tests exposed stability problems, including reordered or duplicated frames. This indicated that aborting, reassigning, and restoring active TX Message Objects requires more careful synchronization and ownership handling.
Because this experimental preemption code was not stable enough for upstream submission, it is not part of the current upstream driver.
For the current baseline, the driver intentionally uses one hardware TX Message Object:
'''text RTEMS TX queue | v MO9 | v CAN bus '''
This keeps the TX path simple and guarantees that frames from the same priority class are transmitted in FIFO order.
I verified this behavior with a same-priority test containing 64 frames. All frames used the same CAN ID and queue priority, with sequence numbers from 0 to 63.
'''text Expected frames: 64 Received frames: 64 Order errors: 0 Invalid frames: 0 CAN error frames: 0 Verified order: 0..63 '''
The test passed.
Support for multiple TX Message Objects with priority-aware scheduling
remains future work. The planned direction is to use
rtems_can_queue_pending_outslot_prio() and rearrange TX Message Objects
when a higher-priority class would otherwise be blocked by a
lower-priority one.
5. Code Organization
Generic Bosch DCAN controller logic is separated from AM335x-specific support such as peripheral clock and pinmux configuration. This organization was improved based on mentor and merge-request feedback.
6. Upstream Work
The driver has been developed through the RTEMS GitLab review process.
- RTEMS merge request: RTEMS GitLab merge request
7. Challenges and Lessons Learned
Understanding DCAN
DCAN uses hardware Message Objects and interface registers. Understanding how these parts interact was a major part of the project.
Hardware Testing
Real hardware testing was essential. Testing with the BeagleBone Black, CANable, and Linux SocketCAN helped make controller behavior visible.
RTEMS Integration
A working hardware prototype is different from an upstream-quality RTEMS driver. Queue ownership, synchronization, error handling, code organization, and lifecycle management are important.
TX Scheduling
The multi-TX-Message-Object experiments showed that more hardware parallelism also creates more scheduling complexity. The driver must consider both RTEMS software queues and DCAN hardware behavior.
Open-Source Development
Mentor review helped improve the architecture and code quality. I learned to keep changes reviewable, test changes on hardware, manage Git history, and separate generic code from board-specific code.
8. Remaining Work
The main future improvement is priority-aware multi-Message-Object TX scheduling.
The planned direction is:
- Support multiple TX Message Objects.
- Use
rtems_can_queue_pending_outslot_prio()to inspect pending queue priority. - Prevent a higher-priority class from being blocked by lower-priority frames already assigned to hardware TX Message Objects.
- Rearrange TX Message Objects while preserving FIFO ordering within the same priority class.
- Add focused tests for priority ordering and TX Message Object rearrangement.
Additional cleanup may also be required through normal RTEMS upstream review.
9. Final Status
At the end of GSoC, the project has a functional Bosch DCAN driver running on RTEMS 7 and tested on BeagleBone Black hardware.
The project progressed from basic register-level CAN communication to RTEMS CAN framework integration, including interrupt-driven receive, RX FIFO handling, TX queue integration, statistics, error reporting, controller lifecycle handling, and DCAN0/DCAN1 support.
The current TX baseline uses one hardware TX Message Object and has verified same-priority FIFO ordering. More advanced priority-aware multi-Message-Object TX scheduling is left as future work.
I plan to continue contributing to RTEMS and improving the driver based on upstream feedback.
10. Links
- GSoC project: GSoC Project Introduction
- RTEMS merge request: RTEMS GitLab merge request
- Local Repo: Local Repo
- Test notes / blog posts: Blog Posts
11. Acknowledgements
Thank you to my RTEMS mentor and the RTEMS community for the technical guidance, code review, and support throughout Google Summer of Code 2026.