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).
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.
The Linux kernel makes a number of features available to device drivers. Here is how we plan to support these features in CapROS.
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 will be implemented as semaphores. A more efficient implementation may be done later.
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 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.
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(). |
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.
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.
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.
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. |
printk
will be implemented using kprintf.
Drivers will have a key register KR_OSTREAM for console output.
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.
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.
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_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. |
wake_up_interruptible() is used in pl010_modem_status.
amba_driver_register | From drivers/built-in.o. |
amba_driver_unregister |
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). |
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. |