If your embedded system doesn't need networking and storage, porting Linux to your hardware may not be worth the effort.
I was originally skeptical about how much Linux had to offer the embedded community. I warmed to it after a positive experience on one project, but further consideration has left me somewhat skeptical once again. In this column, I share what I learned during that project. My hope is that when you're done reading, you'll have learned something about how Linux can be put to best use in embedded systems.
Any port in a storm?
Because so many RTOSes are written with portability in minddue to the wide variety of hardware platforms used in embedded systemsperforming a port to a new board sometimes takes only a few hours. And porting an RTOS is often just a matter of porting to one particular microcontroller. A microcontroller with a number of memory models may require a port for each memory model, and the port may be tied to a particular compiler, but that's the extent of the variation.
Porting Linux is a more complicated process. Linux expects mass storage and an array of device drivers for peripherals, such as network cards, to be considered a full implementation. Such a Linux implementation would be more powerful than an easier-to-port RTOS, but the extra power is mostly in the area of storage and networking. If your system doesn't require those capabilities, you gain little from the effort you put into the port.
Some embedded applications require a general purpose machine with storage and networking. In such cases, the effort of porting Linux should yield good results, especially if users are going to access the system over a network. For these sorts of applications there is little temptation to vary much from a standard PC architecture, except maybe to install a flash disk instead of a hard disk to minimize moving parts. Thus, the port is also easier.
When you opt for a ready-made single-board computer (SBC) in your embedded application, you want to take advantage of the software that has already been written for that board. If the board has storage and a network connection, then Linux is a great match because someone else has probably done the porting, and the result is an environment in which a proficient Linux programmer can work with limited embedded knowledge and limited access to the hardware.
Recently, I used a board from SSV (www.ssv-embedded.de) called the TRM/816. It's a single-board PC with a few extra peripherals: some I/O lines, a CAN controller, and so on. The TRM/816 can run DOS or Linux, and its user interface consists of a monochrome display and a numeric keypad. The user interface is designed to be controlled by means of a custom application rather than by means of a Linux shell or a Linux application. The Linux root console can be connected to the serial port. In this case, I was building a device that would connect to the CAN network running in a vehicle and display some status information for service engineers. The device also needed modem connectivity to allow remote monitoring and remote download of software upgrades.
Controlling a modem is straightforward from Linux because the mgetty software provides a process that listens for incoming calls, responds to rings, and hangs up when appropriate. Some configuration was required to allow for the variations in the command set between modem manufacturers. The point-to-point protocol (PPP) could then turn the modem connection into a TCP/IP connection. (If so, additional configuration will be necessary to set the IP address.)
Once these components are in place, any ordinary PC can dial in to the Linux-based device using a standard PPP connection. Once connected, users can manage the system remotely via telnet or FTP. For our diagnostic device, we only needed FTP and telnet during development; such access served no purpose in the finished system. The more important communication happened using a socket connection between a custom application on the PC and a process running on the Linux box that was dedicated to communicating with the PC. I called this program the remote-control process.
Linux's inet daemon allows you to choose which applications run in response to certain kinds of traffic. We set up our device so that the remote-control process would only launch when a remote PC tried to open a connection to the device. We were also able to open one copy of the remote control process for each incoming connection if required.
The root of it all
The TRM/816 runs Linux entirely from a RAM disk. When the system starts up, the disk image is copied from flash to RAM and the kernel boots using the RAM disk as the root partition. Because you start from the same state every time you power up the system, issues such as log files growing too large or accidental deletion of a vital file are avoided. The disadvantage is that if you download an updated program via FTP, that version will reside in the RAM disk and disappear on the next reboot.
To permanently change the filesystem, you have to change an image of the filesystem that you maintain on a more conventional Linux box. You can mount the TRM/816 root file system to make it look like a normal partition and then alter the files as required. When you're happy with the setup, you can compress the filesystem and copy it to the device's flash memory via a serial connection.
To a conventional Linux developer this would seem unwieldy, but to the embedded systems developer, who is used to flashing his entire application onto a device after every software change, it's not an unreasonable way to do business. What might seem more surprising to the embedded developer is that this RAM disk occupies about 2MB of RAM. On my project, the code was only a few hundred lines long, so the proportion of space occupied by this code is only a tiny fraction of the overall memory requirement. This would not be typical if I were using no operating system or a small kernel.
If you're faced with the necessity of running from RAM in this fashion, you'll have to decide whether you want to be able to telnet into the device and make updates that will persist through a reset. Being able to modify the system throughout its life is normal in the desktop world, but it may or may not be desirable on your embedded application.
Who drives the hardware?
I've shown you that running Linux gives us nice ways to communicate with the outside world. But what happens when we try to communicate with custom hardwarethe bread and butter of most embedded systems? This is where Linux development diverges from conventional embedded systems programming.
The elegant solution is to provide a device driver so that all hardware access from user processes passes through that driver. For example, the device driver might make the CAN controller visible as a file, /dev/CAN , and this file can then be opened to read and write data onto the CAN bus. The device's ioctl() function configures the controller for items such as bus speed and error thresholds. (The ioctl() , short for I/O control, system call is a standard Unix function for configuring a device and provides the only access to the device that's not a stream of data into or out of the device. The exact type of data passed to an ioctl() call varies for each device driver.)
The advantage of a device driver is that even if you were to switch to another CAN controller, the interface to the device via /dev/CAN would hold. There's a strict division between control of the hardware, which handles building a packet, and the application-level control, which decides what the data means.
The device driver solution provides access management by only allowing one process at a time to have special file /dev/CAN open. We can also set the ownership and permissions for this special file to manage which users have access to the CAN bus.
But do we really want that flexibility? This is a closed system, so the concept of a user requesting “cat /dev/CAN ” is nonsensical. The extra control and protection we get from a Linux device driver buy us capabilities that are great in a multiuser system, but don't have much upside on an embedded system. In a closed system, designers can restrict the kind of activity that is allowed.
An alternative to a device driver is to access the hardware directly from any user process. This will concern many Linux purists. However, as a developer, this is a far simpler approach. This is a closed system, so I can be certain that no other process is trying to use the same hardware at the same time as I am. You don't have to learn the intricacies of writing and installing Linux device drivers.
One major loss is that a user process has little control over the real-time behavior of the code. On my CAN project, I knew that there was sufficient buffering in the CAN controller to minimize the chance of missing a message while waiting for a context switch. The other disadvantage of accessing the hardware directly from user space is that there's no easy way to intercept interrupts there. In this project, since I had already given up the possibility of hard real-time control, the loss of interrupts wasn't a big issue, though I can see it would be in many other cases. Fortunately, RT Linux has a mechanism for mapping real interrupts that are caught by the kernel to “soft interrupts,” which can then be handled in user space.
A question of scale
The Linux philosophy is to write general solutions that scale well and to separate user activities from kernel or device-driver activities. But adding scalability takes effort. If many developers are going to use the resulting drivers, then the effort is well spent, since these developers won't have to access the hardware directly. If the target user for the device driver is one developeras is often the casethen the return on investment is not good.
Optimization often depends on crossing the boundaries, or layers, that separate different parts of our code. If I separate my application from my device driver, I lose opportunities to optimize. In particular, I've seen projects where the device drivers had to be rewritten to be less general, and faster, than standard Linux drivers.
The separation of application and device driver is an alien concept to many embedded programmers, which makes it a significant obstacle to using embedded Linux for custom hardware.
In Linux, each device is seen as a file. The serial port is /dev/serial , a terminal connection is /dev/term1 . Like files, devices are opened, closed, read, and written. This makes sense for what Linux does best, which is manage storage and network connections. It doesn't map as well to the control of the A/D converters, general-purpose I/O ports, and custom hardware found in many embedded systems. Directly accessing memory locations that map to the registers of the chip being controlled provides the kind of control an embedded developer wants. Doing it this way also makes it easier to see a correlation between the values written and the description in the data sheet.
Some would argue that having a device driver allows porting to another CAN controller or a different system. But porting the device driver is no easier than porting an application that accesses the hardware directly. The Linux approach works well when there are lots of developers using the same hardware, but not on one custom board.
I see great advantages to Linux when starting with an existing SBC. Ideally, all of the peripherals on the SBC would have Linux drivers already, since there should be a large family of developers using the SBC. It is much harder to justify that approach for a custom board where there is only one team of developers writing software.
Unfortunately the TRM/816 board does not provide drivers for all of its peripherals. Drivers are provided for standard peripherals, such as the serial port, but extras like the CAN controller, serial EEPROM, and Compact Flash interface, come only with sample code, not a complete driver.
Still, for this particular project, embedded Linux was a good match. Only service technicians within the company were going to use the device, so volumes were low. We could afford to spend extra money on the hardware in order to reduce development time. Using embedded Linux on an SBC provided that balance. If the same product had to be mass produced, we would likely have arrived at a very different hardware and software solution.
Ask yourself . . .
The most important measure of Linux's success is whether projects that use Linux manage to ship the resulting product.
As a developer, you need to search for a good fit. In the case of Linux, it's most appropriate to consider first your networking and storage needs.
Niall Murphy has been writing software for user interfaces and medical systems for 10 years. He is the author of Front Panel: Designing Software for Embedded User Interfaces. Murphy's training and consulting business is based in Galway, Ireland. He welcomes feedback and can be reached at . Reader feedback to this column can be found at .