Hook up the serial port on your development board to your host development platform, then start up a serial communication program on your host development platform to communicate with your target.
tty driver
An interrupt driven serial driver can be used to create a terminal device. A terminal device can be created by registering the serial driver with tty. A variety of serial drivers are available in the $(TOPDIR)/drivers/char directory. The driver that matches closest to the serial port hardware being used should be picked up and modified. The interfaces to an interrupt-driven character driver under Linux have been explained in Linux Device Drivers by Rubini (see References).
CONFIG_SERIAL (serial support) has to be defined as Y in "make config." To test, hook up the interrupt-driven serial port to the host development platform and run a serial communication program to communicate with your target (terminal device).
Bootloader
Although LILO (the Linux loader) should be available for your architecture, it may be quicker to use your own bootloader to load the Linux kernel. 4 LILO passes some information to the kernel in a way similar to how an Intel PC BIOS passes information to the kernel. LILO then calls the "kernel_entry" function inside the kernel, giving up control to the kernel. If you're using your own bootloader, you need to pass parameters to the kernel by adding them to the "command_line" string, which is parsed by the kernel. In my case, I had to add "root=/dev/ram" to the command_line string to tell the kernel that I wanted the ramdisk to be mounted as the root file system. You could add other kernel parameters to this string, if needed. Load the image at the specified load address using your bootloader. Start executing from the address of the "kernel_entry" symbol in the kernel image.
It will be easier to debug if the bootloader had its own "print" function, because the printk function inside the kernel buffers all the output to the console until the console is initialized (console_init() in $(TOPDIR)/init/main.c).
If everything goes well, you should get something like the following message on your kernel debug terminal:
Detected 32MB of memory.
Loading R4000/MIPS32 MMU routines.
CPU revision is: 000028a0
Primary instruction cache 32 kb, linesize 32 bytes
Primary data cache 32 kb, linesize 32 bytes
Linux version 2.2.12 (rpalani@rplinux)
(gcc version egcs-2.90.29 980515 (egcs-10))
CPU frequency 200.00 MHz
Calibrating delay loop: 199.88 BogoMIPS
Memory: 14612k/16380k available
(472k kernel code, 908k data)
Checking for wait' instruction... available.
POSIX conformance testing by UNIFIX
Linux NET4.0 for Linux 2.2
Based upon Swansea University Computer Society NET3.039
Starting kswapd v1.1.1.1
No keyboard driver installed
RAMDISK driver initialized: 16 RAM disks of 4096K size 1024 blocksize
RAMDISK: Compressed image found at block 0
VFS: Mounted root (ext2 filesystem) readonly.
Freeing unused kernel memory: 32k freed
The kernel tries to open a console and find and execute "init" from one of the following places in the root file system, in sequence: /sbin/init, /etc/init, /bin/init. If all the above fail, it tries to create an interactive shell (/bin/sh as happens in my case). If even this fails, then the kernel "panics," as would you. I hope that this does not happen in your case. If it doesn't a shell prompt will appear on the console. Applications can be run on the system by dropping them inside the ramdisk image and executing from there.
Adding new drivers
New drivers for your target hardware can be added by picking up the driver that matches most closely to your hardware (a vast number are available) and modifying it. If you are dealing with a proprietary piece of hardware that is specific to your system, use the standard driver interfaces to implement a driver for the same. These drivers can be implemented as kernel modules in order to load and unload them using insmod and rmmod.
Useful tips
Sprinkle printk() statements liberally throughout your code to aid debugging. This may be an obvious suggestion, but it is worth mentioning.
Remote GDB5 may also be useful for debugging, though in my experience printk's are more than enough for debugging kernel code. In remote GDB, the host development system runs gdb and talks to the kernel running on the target platform via a serial line. You need to setup CONFIG_REMOTE_DEBUG = Y in the kernel configuration. putDebugChar(char ch) and getDebugChar() are the two functions that need to be implemented over the serial port for remote debugging using gdb.
If you are forced to use a common port for console and debug, the GDB output can be multiplexed with the debug output by setting the high bit in putDebugChar(). GDB forwards output without the high bit set to the user session.
To start with, implement only the basic minimum functions for the tty driver as specified in $(TOPDIR)/include/linux/tty_ldisc.h.
Real-time requirements
The subject of embedded systems is not complete without a mention of real-time requirements. The standard Linux kernel provides soft real-time support. There are currently two major approaches to achieve hard real-time with Linux. These are RTLinux and RTAI. Both approaches have their own real-time kernel running Linux as the lowest priority task.
When dealing with proprietary hardware, as it often happens in embedded systems, the issue of proprietary software crops up as well. In Linux, proprietary modules can be handled with the GNU Lesser General Public License, which permits linking with non-free modules. It is compatible with the GNU General Public License, which is a free software license, and a copyleft license. 6
With a good knowledge of the processor architecture and the hardware devices being used, porting Linux to an embedded system can be accomplished in a short time frame, which is of vital importance in the fast paced embedded systems market. In my case, where I have been using UNIX for quite some time, it took me around two months to complete the port of the minimum kernel functionality to our platform. Porting Linux to a different platform should not take that long when doing it for a second time.
Rajesh Palani works as a senior software engineer at Philips Semiconductors. He has been designing and developing embedded software since 1993. He has worked on the design and development of software (ranging from firmware to applications) for set-top boxes, digital still cameras, TVs (Teletext), and antilock braking systems. Contact him at rajesh.palani@philips.com.
Endnotes
1. Stands for "GNU's Not Unix," a project launched in 1984 to develop a complete Unix-like operating system which is free software: the GNU system.
2. The topmost directory in the Linux source tree (/usr/src/linux, by default).
3. Translation Lookaside Buffer-hardware used for virtual to physical address
mapping in a processor.
4. The subject of developing a bootloader for your processor is outside the scope of this article.
5. GNU Debugger-helps you to start your program, make it stop on specified conditions, examine what has happened (when your program has stopped), and change things in your program.
6. Copyleft says that anyone who redistributes the software, with or without changes, must pass along the freedom to further copy and change it.
References
A Web site containing a wealth of information on Linux in general:
Beck, M. et al.
New York: Addison-Wesley, 1998. This book is a good source of information on the kernel internals.
Rubini, Alesandro. Linux Device Drivers. Sebastopol, CA: O Reilly & Associates, 1998.
This book delves into kernel internals and talks in detail about all types of device drivers under Linux.