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 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 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:
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 TxRqst state
The driver cannot only think about the software queue.
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 one 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 queue slot,
clears the software ownership of the Message Object,
allows another queued frame to use the free TX buffer.
This work created the foundation for priority 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 was:
A software FIFO alone does not automatically guarantee transmission order when several hardware TX buffers are active.
This became important when I started implementing priority scheduling.
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 is not good for priority scheduling.
The mentor pointed me to the SJA1000 RTEMS driver. The SJA1000 driver checks whether the currently active hardware transmission has lower priority than a frame waiting in the software queue. If a higher-priority frame is waiting, it can abort the current TX request and return the old frame to the software queue.
The important idea is:
higher-priority frame waiting + lower-priority frame still pending in hardware | v abort the lower-priority transmission | v return it to software queue | v send the higher-priority frame first
Initial DCAN Preemption Design
Because DCAN has several TX Message Objects, my first implementation selected one lower-priority Message Object as the victim.
For example:
MO9 = low MO10 = low MO11 = low MO12 = low
software queue: high
The driver selected a victim, usually the lowest-priority or highest-numbered Message Object:
victim = MO12
Then:
abort MO12 return old MO12 frame to queue put high-priority frame into MO12
This was the first working version of TX preemption.
Debugging DCAN TX Abort
The most important low-level bug during this work was related to the DCAN Interface Command Register.
At first, the abort function tried to clear TxRqst in the IF Message Control Register:
IF1MCTL.TxRqst = 0
but the command also included:
IF1CMD.TxRqst/NewDat = 1
The DCAN documentation explains that during a write operation, setting the TxRqst/NewDat command bit directly sets the Message Object TxRqst bit to one, independent of the value in the IF Message Control Register.
So my code was effectively doing:
clear TxRqst | v command sets TxRqst again
The result was:
TXRQ before abort = 0x00000f00 TXRQ after abort = 0x00000f00
The abort never completed.
The fix was to clear TxRqst through the Message Control field and write it back using the CONTROL command, without setting the command's TxRqst/NewDat bit.
After the fix:
ABORT BEFORE MO12 TXRQ=0x00000f00 ABORT AFTER MO12 TXRQ=0x00000700
The bit for MO12 was successfully cleared.
Successful First Preemption Test
A test was created where four low-priority frames occupied MO9–MO12 and a higher-priority frame was queued later.
The driver successfully:
found the victim -> aborted the victim -> returned the old frame to the software queue -> submitted the higher-priority frame
The test showed:
PREEMPT VICTIM MO12 PREEMPT ABORTED MO12 PREEMPT REQUEUE MO12
This proved that hardware TX abort and software requeue were working.
Limitation of the Week 11 Design
The first design still had an important problem.
Suppose the hardware contains CAN IDs:
MO9 = 54 MO10 = 55 MO11 = 56 MO12 = 57
and a new higher-priority CAN ID 49 arrives.
The Week 11 design could do:
MO9 = 54 MO10 = 55 MO11 = 56 MO12 = 49
57 -> queue
The high-priority frame entered hardware, but it was placed in MO12.
This did not fully match the CAN priority model.
The mentor clarified that the correct result should be:
MO9 = 49 MO10 = 54 MO11 = 55 MO12 = 56
57 -> queue
This led to the Week 12 redesign.
GSoC 2026 RTEMS DCAN Driver Development — Week 12
Week 12 — CAN-ID Based TX Priority Window
New Design Goal
The 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 be treated as a fixed-size ordered hardware 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 ID 49 arrives:
49 < 54 < 55 < 56 < 57
The best four frames should stay in hardware:
MO9 = 49 MO10 = 54 MO11 = 55 MO12 = 56
and the lowest-priority frame should return to the software queue:
57 -> RTEMS TX queue
This is different from simply replacing one victim.
The driver now maintains a CAN-ID ordered TX hardware window.
Finding the Insertion Position
I added a helper that extracts the standard CAN identifier:
static uint32_t dcan_tx_frame_id( const struct can_frame *frame ) { return frame->header.can_id & 0x7ffu; }
Then the driver scans the active TX window and finds where the new frame belongs.
Conceptually:
hardware: 54 55 57 60
new: 56
Comparison:
56 < 54 ? no 56 < 55 ? no 56 < 57 ? yes
Therefore:
first_index = 2
which corresponds to MO11.
The driver only needs to reorder the affected suffix:
MO9 = 54 unchanged MO10 = 55 unchanged
MO11 and MO12 must be reordered
Using strict < also keeps equal CAN IDs behind already queued frames, which helps preserve FIFO behavior for frames with the same 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.
- PREEMPT
This happens when a new higher-priority CAN frame needs to enter a full hardware TX window.
Example:
54 55 57 60
56
Result:
54 55 56 57
60 -> queue
- COMPACT
This happens after an earlier Message Object finishes transmission and leaves a hole.
Example:
MO9 = free MO10 = 54 MO11 = 55 MO12 = 56
queue: 57
If the driver simply fills MO9 with ID57, it would create:
57 54 55 56
which breaks the hardware priority order.
Instead, the driver compacts the existing frames:
54 -> MO9 55 -> MO10 56 -> MO11
and then fills the tail:
57 -> MO12
Result:
54 55 56 57
This keeps the hardware window ordered after every TX completion.
Preemption State
The reorder process cannot happen in one instruction.
The driver must:
detect reorder | v request hardware abort | v wait until TxRqst becomes 0 | v rewrite 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 stores:
whether a reorder is active,
whether it is PREEMPT or COMPACT,
where the affected range starts,
the new queued frame that is waiting to enter hardware.
Starting Priority Preemption
When all TX Message Objects are occupied, the driver checks the next software TX frame.
The logic is approximately:
Is another reorder active? | yes -> wait
Are all four hardware TX buffers occupied? | no -> normal filling
Get next frame from RTEMS TX queue | v Find insertion index by CAN ID
If the new frame does not belong in the hardware window:
first_index == 4
it is simply returned to the software queue.
For example:
hardware: 49 54 55 56
new: 57
ID57 has lower priority than every active hardware frame:
first_index = 4
so no preemption is required.
Aborting Only the Affected Suffix
The new design does not always abort all four Message Objects.
For:
54 56 57 60 + 55
the insertion point is:
first_index = 1
so:
MO9 stays unchanged MO10-MO12 are affected
For:
54 55 57 60 + 56
the insertion point is:
first_index = 2
so only:
MO11-MO12
need to be stopped and rewritten.
This reduces unnecessary hardware operations.
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, it checks:
TxRqst == 0
Only after all requested aborts are complete does the driver mark the reorder 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 protects the driver from rewriting a Message Object while the controller still considers its transmission pending.
Completing the Ordered Insertion
Consider this test:
MO9 = 54 MO10 = 55 MO11 = 57 MO12 = 60
new = 56
The driver saves the frame that will leave the window:
evicted = ID60
Then it 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
and:
60 -> software queue
The shift is done from right to left because doing it from left to right could overwrite data that still needs to be moved.
Another important detail is that I move only the software ownership information:
slot edge priority
I do not copy the complete dcan_txb_info structure because mobj represents a physical hardware position:
txb_info[0].mobj = 9 txb_info[1].mobj = 10 txb_info[2].mobj = 11 txb_info[3].mobj = 12
These hardware numbers must never move.
TX Window Compaction
After a frame finishes, the TX completion handler releases its queue slot.
For example:
before: 49 54 55 56
49 finishes
after completion: free 54 55 56
Before 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 lower-numbered Message Objects:
free 54 55 56 | v 54 55 56 free
Then the next software frame can safely fill the tail:
54 55 56 57
This solves the ordering problem I observed during earlier tests.
Updated TX Processing Order
The TX worker now conceptually processes TX work in this order:
- Finish an active abort/reorder
- Compact a hardware window that contains a hole
- Fill normal free TX Message Objects
- Check whether a new queued frame should preempt the full window
This order is important.
If normal filling happened before compaction, a low-priority frame could enter an earlier Message Object and break CAN-ID ordering again.
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 I can independently check:
CAN ID -> priority behavior sequence -> frame identity, loss, or duplication
Test 1 — Front Insertion
Initial hardware:
54 55 56 57
New frame:
49
Expected:
49 54 55 56
57 -> queue
Driver result:
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
Test 2 — Middle Insertion
Initial hardware:
54 56 57 60
New frame:
55
Expected:
54 55 56 57
60 -> queue
Driver result:
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 and keep MO9 unchanged.
Test 3 — Tail-Range Insertion
Initial hardware:
54 55 57 60
New frame:
56
Expected:
54 55 56 57
60 -> queue
Driver result:
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 confirms that the reorder operation did not lose or duplicate any frame.
What I Learned
These three weeks helped me understand that TX scheduling is not only about putting frames into hardware.
There are several layers that must stay 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 logic much 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 hardware window consistent with CAN arbitration priority.
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 TxRqst semantics,
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.
Important remaining tests include:
first_index = 3
Initial: 54 55 56 60
New: 57
Expected: 54 55 56 57
60 -> queue
I also want to test:
equal CAN IDs and FIFO behavior,
a new frame that has 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 support toward more robust scheduling behavior suitable for upstream review.