Supporting Linux Device Drivers

Structure

Each driver will have:

A portion of the address space will be reserved for thread stacks, each in a fixed address range. For reliability, each stack is bounded by an unmapped page, to detect stack overflow. A thread can find out which stack it is using by arithmetic on its stack pointer. A table relates stacks to threads.

One thread (the "server thread") will receive requests from a start key.

One thread (the "sync thread") will perform certain synchronization between threads.

There may be one or more threads to service interrupts (see below). There may be one or more threads used to sleep (see below). There may be one or more stacks (but probably not threads) used in conjunction with wait queues (see below).

Configuration

File base/lib/linux-headers/linux/autoconf.h is taken from a configured Linux kernel. It should have the Preemptible Kernel option selected, as that most closely approximates the emulation environment, which has independent processes.

The SMP option might also be useful, but there doesn't seem to be a way to select it for ARM EP93xx using menuconfig.

Support for Linux Features

The Linux kernel makes a number of features available to device drivers. Here is how we plan to support these features in CapROS.

Semaphores

Semaphores and read-write semaphores will be supported using a shared structure with a count that is updated atomically. If a process needs to wait or wake up another process, it will make a CALL invocation to the Sync process to do the work. In the (hopefully common) case where there is no contention, no context switches are required.

Mutexes

Mutexes will be implemented as semaphores. A more efficient implementation may be done later.

Spinlocks

Spinlocks differ from semaphores in that they can be used at interrupt level, where suspending the current process is not an option. In CapROS, interrupt processing for a driver is done in a user-mode process, so this restriction does not apply. In CapROS it would be possible to implement spinlocks as mutexes or semaphores, and read-write spinlocks as read-write semaphores.

However, Linux drivers might assume that while under a spin_lock_irq() or spin_lock_irqsave(), interrupts cannot occur and therefore execution timing is predictable. (I know of no actual instances of this at the moment.) Therefore we implement spinlocks by disabling interrupts. This also has the advantage that the speed of spinlocks in CapROS is similar to that in Linux.

Some Linux operations that can be done under a spinlock (for example, reading jiffies) require an invocation of a kernel object in CapROS. To support this, the kernel runs with interrupts disabled if it was entered from a process that had interrupts disabled. Of course, if a context switch occurs, interrupts will be enabled according to the new process.

We need to be aware that page faults might still occur while a driver is under a spin_lock_irq() or spin_lock_irqsave(). In particular, a page fault could occur if a driver writes to a pinned persistent object, because, despite the object being pinned, it could be temporarily read-only just after a checkpoint.

Wait Queues

Wait queues can be handled in a general way that preserves the stack at the point of waiting, in a manner similar to sleeping (see below). However, wait queues are often used to have the client-requested operation wait for some event. Interactions with the driver client require CapROS-specific code, so in many cases it may be feasible to handle client waiting differently. We can enqueue a continuation procedure on the queue, with access to the client's resume key.

Sleep

The general way to support sleep (that is, without knowledge of the driver code) is as follows. If the thread sleeping is the server thread, create a new thread. The new thread uses the original thread's stack; it will be the sleeping thread. The old thread gets a new stack, remains the server thread, and receives new requests. The current resume key moves to the new thread.

Then, the sleeping thread sleeps and returns to the caller of msleep(). Eventually it will reply to the resume key. Then, it will notice that it is not the server thread, and terminate the thread.

In the specific case of the serial port driver, sleeping is used for only two things. One is a delay after close(), which will probably be handled outside the driver. The other is in a loop polling to see if the transmitter is empty. For reliability we may create a single timer thread that just does that.

Supported procedures are:

msleep
msleep_interruptible Since there are no signals in CapROS, this is the same as msleep.
ssleep
jiffies This will expand to a procedure call to get the time using capros_Sleep_getTimeMonotonic().

Interrupts

The procedure request_irq registers a handler for an interrupt. This will create a thread that loops waiting for an interrupt and executing the handler. free_irq deregisters the handler.

Memory Allocation

We look critically at dynamic allocation in drivers, because allocation failures are a potential source of unreliability. Still, it seems we will have to suppport it.

So far we only support the GFP_KERNEL flag, and we don't guarantee that the memory occupies contigous physical addresses. So far we haven't seen other requirements.

We use the C library procedures malloc() and free(), and supply an sbrk() that allocates space following BSS. For convenience we will use a VCSK to allocate pages in memory; in the future it might be better to do something more lightweight.

Supported procedures are:

kmalloc
kfree amba_pl010.c only calls kfree at remove time.
kzalloc amba_pl010.c only calls kzalloc to allocate a structure at probe time.

__get_free_pages and free_pages can be supported by allocating addresses in the same range used to map device registers, and getting pages from the space bank. They will not be contiguous in physical memory.

Device Registers

Following POLA, I would like to grant access to only the page(s) needed. If the big bang grants access to only one process, request/release_mem_region can be nops.

For ioremap() and iounmap(), there is a region of the address space reserved for mapping device registers. The driver gets access to the pages it needs via a node containing resource keys including physical pages.

Clock Control

The Linux files clk.h and clock.c provide support for enabling and disabling various hardware clocks. A clock is enabled if there is any driver that requires it.

Because this functionality spans different drivers, for POLA it will be implemented in a separate object. There will be one object for each type of clock. Each driver will have a key to manipulate only the type(s) of clock that it needs.

The UART clock may be a special case because a UART can be used as a console during boot and for kprintf().

Supported procedures are:

clk_disable This procedure counts the caller as no longer a user of the clock. When there are no users, the clock is disabled.
clk_enable This procedure counts the caller as a user of the clock, and enables the clock.
clk_get This procedure accesses a clock by looking up its name. amba-pl010.c uses the "UARTCLK".
clk_get_rate On ep93xx, returns the UART clock rate of 14,745,600 Hz.
clk_put This procedure destroys the clock obtained by clk_get(). However, since clock objects are permanent, this does nothing.

Diagnostic Output

printk will be implemented using kprintf. Drivers will have a key register KR_OSTREAM for console output.

Performance issues

Spinlocks are frequently used. In Linux on a uniprocessor this simply disables interrupts. On CapROS it does an atomic operation, which, on ARM architectures prior to 6, traps to the kernel.

Serial Port Driver

The Linux serial port driver has a number of interrelated parts, including the low-level hardware driver, various line disciplines or protocols, tty devices, and consoles. As you might expect with a monolithic kernel, in Linux there seems to have been little incentive to structure these relationships with modularity in mind. In addition, some of this code is immodular simply for historical reasons.

The code to support tty devices, consoles, and file I/O is quite Linux-specific, and we do not plan to port that code. Code to handle the 17 line disciplines (which include input-line editing, SLIP, PP, X25, IrDa, HDLC, etc.) may be ported as needed. We do not expect frequent changes here, as the protocols are governed by standards, so we are willing to tolerate a high degree of customization for CapROS.

The low-level hardware handler for specific serial port (UART) hardware will be ported with minimal change by emulating the Linux environment. In the case of our ARM hardware, this consists of the single file drivers/serial/amba-pl010.c.

This driver uses many of the facilities described above, and also the following that are more specific to serial ports.

Procedures in drivers/serial/serial_core.c and include/linux/serial_core.h

uart_add_one_port
uart_get_baud_rate
uart_get_divisor
uart_register_driver
uart_remove_one_port
uart_resume_port
uart_suspend_port
uart_unregister_driver
uart_update_timeout
uart_write_wakeup
uart_handle_break Secure attention will be handled at a higher level.
uart_handle_dcd_change This procedure wakes up a wait queue.

Tty flip buffer

tty_flip_buffer_push Ensure the buffered input is sent to the consumer (the line discipline), either immediately or delayed.
tty_insert_flip_string_flags Part of inline procedure tty_insert_flip_char, part of uart_insert_char, which inserts an input character and flag and possibly overrun into the receive buffer. The character and flag are buffered in parallel.

Wait Queues

wake_up_interruptible() is used in pl010_modem_status.

Other

amba_driver_register From drivers/built-in.o.
amba_driver_unregister

Other References from drivers/serial/built-in.o

add_wait_queue From kernel/built-in.o.
alloc_tty_driver From drivers/built-in.o.
__bug From arch/arm/kernel/built-in.o(traps.o). Prints a bug message.
capable From kernel/built-in.o.
__copy_from_user
__copy_to_user
default_wake_function
dump_stack
free_pages
get_zeroed_page
init_waitqueue_head
jiffies
jiffies_to_msecs
__memzero From arch/arm/lib/lib.a(memzero.o).
msecs_to_jiffies
msleep
msleep_interruptible
__mutex_init
mutex_lock
mutex_lock_interruptible
mutex_unlock
put_tty_driver
__put_user_4
register_console
remove_wait_queue
schedule
strlcpy
tasklet_init
tasklet_kill
__tasklet_schedule
tty_hung_up_p
tty_ldisc_flush
tty_name
tty_register_device
tty_register_driver
tty_set_operations
tty_std_termios
tty_termios_baud_rate
tty_unregister_device
tty_unregister_driver
tty_vhangup
tty_wait_until_sent
tty_wakeup
__udivsi3 From arch/arm/lib/lib.a(lib1funcs.o).

SourceForge.net Logo Copyright 2007 by Strawberry Development Group. All rights reserved. For terms of redistribution, see the GNU General Public License This material is based upon work supported by the US Defense Advanced Research Projects Agency under Contract No. W31P4Q-07-C-0070. Approved for public release, distribution unlimited.