Real-time operating systems (RTOS) are designed for real-time applications such as embedded systems, industrial robots, scientific research equipment and others. Read on to discover more about the Xenomai project, which emulates a traditional RTOS, but is ported to GNU/Linux.
Today, Linux is used a lot in embedded products and industrial applications. But sometimes, even Linux is not competent enough for embedded systems as they need some specific implementation practices. So this need has given birth to the real-time operating system (RTOS). Mostly, RTOS applications are embedded so they are used in embedded products/systems. They typically have a small footprint.
Xenomai history
The Xenomai project was started in 2001, with the aim of emulating a traditional RTOS and facilitating its porting to GNU/Linux. Xenomai was initially related to RTAI (Real-Time Application Interface) but now it is independent.
The Xenomai core running in the kernel space is offered under GPL 2. The user space interface libraries are released under the LGPL 2.1. Xenomai supports many architectures like PowerPC, Blackfin, ARM, x86, x86_64, etc.
Xenomai architecture
So to give a real-time guarantee to the applications, Xenomai real-time sub-system is patched with the Linux kernel. Its basically a dual kernel approach in which both kernels work side by side without one kernel being aware that the other kernel is working in parallel see Figure 1. Both can communicate with each other via a virtual interrupt controller or I-pipe.
So there are two kernels:
1) A Linux kernel: For non-real-time tasks, and
2) The Xenomai kernel: For real-time tasks.
The Xenomai kernel gives predicted results hence it is also known as the primary mode while the Linux kernel does not give predicted results and hence is known as the secondary mode. Real-time applications are expected to work in primary mode while other applications are expected to work in secondary mode. Switching between modes takes place automatically.
This article talks about a real-time implementation of Linux known as the Xenomai project.
The main goal of the Xenomai project is to facilitate the migration of industrial applications from the proprietary world to a GNU/Linux based environment, while keeping real-time guarantees.
The interrupt pipeline
This is software between the hardware, Linux and Xenomai that acts as a virtual programmable interrupt controller
Interrupt pipeline(I-pipe) is a software layer which maintains the polices for handing interrupts and masking them. We can say that both the Linux and Xenomai domains are connected to each other. Domains are usually kernel modules that call some I-pipe service.
Hardware abstraction layer (HAL)
Xenomai has its own HAL which is needed to port Xenomai to a particular processor architecture. All skins and core services are on top of the HAL.
The Xenomai core and nucleus
The Xenomai core is responsible for supplying operating system service to skins which mimic traditional RTOS APIs. It is because of the Xenomai core that developers get predictable latencies. You can imagine the Xenomai core as an abstract RTOS. These building blocks are gathered into a single, loadable module called the Xenomai nucleus.
Xenomai skins
The various types of skins supported by Xenomai are listed below:
- POSIX
- VxWorks
- pSOS+
- VRTX
- uITRON
- RTAI 3.x
- Native API
- RTDM (Real-Time Driver Model)
The real-time driver model
Developing a real-time driver for a dual kernel architecture would be a difficult task if there is no common framework available for it. Hence the RTDM (real-time driver model) skin gives that common framework to easily port the real-time driver see Figure 2. There are two classes of RTDM:
1) The protocol driver, which connects to a socket interface and hence is well suited to managing message -oriented communications with a real-time device, and…
2) A named device, which is like a character driver in the Linux device driver.
Implementation
For the purposes of implementation, I am using a I2C driver developed by me for Beagle-Bone Black with Linux-3.8.13 and Xenomai 2.6.3. The full I2C driver code is available at https://github.com/JayKothari/i2c_rtdm. The driver was tested with an application made for a I2C sensor (BMP085 pressure sensor).
Warning: The driver is still under testing so if any bug is found, please inform me.
The I2C driver is a platform driver and hence some setting is required in the device tree of the Cortex-A8 processor which resides in Beagle-Bone Black. Various real-time driver development APIs are available like, device registration services, task services, clock services, timer services, synchronisation services, interrupt management, etc.
Registering the RTDM device API
Before registering the RTDM driver, we need to define its structure struct rtdm_device. The following is struct rtdm_device initialisation for the RTDM driver for I2C:
static struct rtdm_device i2c_device= { .struct_version= RTDM_DEVICE_STRUCT_VER, .device_flags= RTDM_NAMED_DEVICE, .context_size= sizeof(MY_DEV), //size of local structure variable .device_name= omap-i2c, .proc_name= omap-i2c, .open_nrt= i2c_open_nrt, .ops={ .close_nrt = i2c_close_nrt, .read_rt= i2c_rd_rt, .write_rt= i2c_wr_rt, .ioctl_rt=i2c_ioctl_rt, }, .device_class=RTDM_CLASS_SERIAL, .device_sub_class=2015, .profile_version=1, .driver_name=DRIVER_NAME, .driver_version=RTDM_DRIVER_VER(0,0,1), .peripheral_name=RTDM I2C MASTER, .provider_name=JAY KOTHARI, };
Now we need to register this information using API int rtdm_dev_register (struct rtdm_device * device).
Example:
struct rtdm_device *rdev; rtdm_dev_register(rdev);
Interrupt management API
Now we need to register the interrupt number which we got from the platform_get_irq() API, as follows:
int rtdm_irq_request ( rtdm_irq_t * irq_handle, unsigned int irq_no, rtdm_irq_handler_t handler, unsigned long flags, const char * device_name, void * arg )
Example:
err=rtdm_irq_request (&dev->irq_handle,dev->irq,rtdm_my_isr_2,0,pdev->name,dev);
With the above API, when the hardware interrupt occurs, the Xenomai co-kernel checks the interrupt table and calls the ISR which is registered with that interrupt.
Synchronisation management API
A locking mechanism can be initialised with the following API:
rtdm_lock_init(rtdm_lock_t lock )
Example:
rtdm_lock_t lock; rtdm_lock_init(lock); rtdm_lock_irqrestore(rtdm_lockctx_t context ) : Restores preemption state rtdm_lock_irqsave(rtdm_lockctx_t context) : disable preemption state rtdm_lock_put(rtdm_lock_t lock) : release lock without preemption restoration rtdm_lock_put_irqrestore(rtdm_lock_t lock,rtdm_lockctx_t context) : release lock and restore preemption state. rtdm_event_init(rtdm_event_t *event,unsigned long pending): Initialize an event void rtdm_signal_event(rtdm_event_t *event) : Signal an event occurrence. Int rtdm_event_wait(rtdm_event_t *event) : wait on event occurrence
Clock services
Sometimes we need to get to know the uptime of the system, which the following API helps us do. The API returns the clock in nanoseconds. The resolution of this service depends on the system timer.
nanosecs_abs_t rtdm_clock_read(void) : Gets system time
Timer services
These are services for timer management which are provided by Xenomai project. There would APIs for a timer handler with which we start or stop.
int rtdm_timer_init(rtdm_timer_t *timer,rtdm_timer_handler_t handler handler,const char *name) : Initialize the timer int rtdm_timer_start(rtdm_timer_t *timer,nanosecs_abs_t expiry , nanosecs_abs_t interval , enum rtdm_timer_mode mode) : start timer
User API in Xenomai
I am assuming readers are familiar with the device driver of Linux, as well as with open, read, write, ioctl and other system calls. Xenomai also supports its own system call so that the application calls the Xenomai kernel module. Some system calls supported by Linux are:
Open a device
int rt_dev_open(const char *path,int oflag,....)
Example:
int handle=rt_dev_open(omap-i2c,0);
Xenomai has an active device concept which means that rt_dev_open() calls the device by the name it was registered in the RTDM driver.
Read from device
ssize_t rt_dev_read(int fd,void *buf,size_t nbyte)
Example:
rt_dev_read(handle,(void *)buf,length)
Write to the device
ssize_t rt_dev_write ( int fd , const void * buf , size_t nbyte )
Example:
rt_dev_write(handle,(void *)buf, size)
Issue an IOCTL
Example 1:
int rt_dev_ioctl(int fd,int request,....)
Example 2:
rt_dev_ioctl(device,command,value_to_pass)
Close device or socket
int rt_dev_close(int fd)
Example:
rt_dev_close(handle);
These are just a few APIs and examples of real-time driver development, shown for introductory purposes only. If you are keen, you will find more documentation on the Xenomai project site.
Some results
The I2C RTDM driver for AM335x Cortex-A8 developed by me was tested by finding the interrupt latency and scheduling latency. The interrupt and scheduling latency were less than 15 micro-seconds. Various load conditions, which were much less than the interrupt latency and scheduling latency in the Linux I2C driver, were found to be in milliseconds sometimes. Testing was done with the help of the ftrace tool.
References
[1] www.xenomai.org
[2] Building Embedded Linux Systems, by Karim Yaghmour, Jon Master, Gilad Ben-Yossef and Philippe Gerum (second edition), OReilly