Device Drivers¶
Since KUDOS is a realistic operating system, it can use hardware devices to interact with the outside world. Hardware devices include things like consoles, disks and network interface adapters.
Device drivers provide an interface between the hardware devices and the operating system. Device drivers use two hardware-provided mechanisms intensively: they depend on hardware generated interrupts and command the hardware with memory mapped I/O.
Most hardware devices generate interrupts when they have completed the previous action or when some asynchronous event, such as user input, occurs. Device drivers implement handlers for these interrupts and react to events.
Memory mapped I/O is an interface to the hardware components. The underlying machine provides certain memory addresses which are actually ports in hardware. This makes it possible to send and receive data to and from hardware components. Certain components also support block data transfers with direct memory access (DMA). In DMA the data is copied between main memory and the device without going through the CPU. Completion of DMA transfer usually causes an interrupt.
Interrupt driven device drivers can be thought to have two halves, top and bottom. The top half is implemented as a set of functions which can be called from threads to get service from the device. The bottom half is the interrupt handler which is run asynchronously whenever an interrupt is generated by the device. It should be noted that the bottom half might be called also when the interrupt was actually generated by some other device which shares the same interrupt request channel (IRQ).
Top and bottom halves of a device driver typically share some data structures and require synchronized access to that data. The threads calling the service functions on the top half might also need to sleep and wait for the device. Resource waiting (also called blocking or sleeping) is implemented by using the sleep queue or semaphores. The syncronization on the data structures however needs to be done on a lower level since interrupt handlers cannot sleep and wait for access to the data. Thus the data structures need to be synchronized by disabling interrupts and acquiring a spinlock which protects the data. In interrupt handlers interrupts are already disabled and only spinlock acquiring is needed.
Interrupt Handlers¶
All device drivers include an interrupt handler. When an interrupt occurs the system needs to know which interrupt handlers need to be called. This mechanism is implemented with an interrupt handler registration scheme. When the device drivers are initialized, they will register their interrupt handler to be called whenever specified interrupts occur. When an interrupt occurs, the interrupt handling mechanism will then call all interrupt handlers which are registered with the occured interrupt. This means that the interrupt handler might be called although the device has not generated an interrupt.
The registered interrupt handlers are kept in the table interrupt handlers which holds elements of type interrupt entry t. The fields of this structure are described in the following table:
Type | Name | Explanation |
---|---|---|
device_t |
device |
The device for which this interrupt is registered. |
uint32_t |
irq |
The interrupt mask. Bits 8 through 15 indicate the interrupts that this handler is registered for. The interrupt handler is called whenever at least one of these interrupts has occured. |
void (*) (device_t *) |
handler |
The interrupt handler
function called when an
interrupt occurs. The
argument given to this
function is device . |
Fields in structure interruptentry_t
.
void interrupt_register (uint32_t irq, void (*handler)(device_t *), device_t device)
- Registers an interrupt handler for the
device
.irq
is an interrupt mask, which indicates the interrupts this device has registered. Bits 8 through 15 indicate the registered interrupts. handler is the interrupt handler called when at least one of the specified interrupts has occured. This function can only be called during bootup. - Implementation:
- Find the first unused entry in
interrupt_handlers
. - Insert the given parameters to the found table entry.
- Find the first unused entry in
void interrupt_handle (uint32_t cause)
- Called when an interrupt has occured. The argument
cause
contains the Cause register. Goes through the registered interrupt handlers and calls those interrupt handlers that have registered the occured interrupt. - Implementation:
- Clear software interrupts.
- Call the appropriate interrupt handlers.
- Call the scheduler if appropriate.
Device Abstraction Layers¶
The device driver interface in KUDOS contains several abstraction layers. All
device drivers must implement standard interface functions (initialization
function and possibly interrupt handler) and most will also additionally
implement functions for some generic device type. Three generic device types
are provided in KUDOS: generic character device (gcd
), generic block
device (gbd
) and generic network device (gnd
). These can be thought as
“superclasses” from which the actual device drivers are inherited.
Generic character device is a device which provides uni- or bidirectional bytestream. The only such device preimplemented in KUDOS is the console. Generic block device is a device which provides random read/write access to fixed sized blocks. The only such device implemented is the disk driver. These interfaces could also be used to implement stream based network protocol or network block device, for example. The interface for generic network device is also given.
All device drivers must have an initialization function. A pointer to this
function must be placed in the drivers_available
array in
drivers/$ARCH/drivers.c
, together with a designated name and a device
typecode identifier. Device typecodes which are defined in
drivers/device.h
. The system will initialize the device drivers on bootup
for each device in the system by calling these initialization functions. This
initialization is done by device_init()
, found in
drivers/$ARCH/device.c
.
Device Driver Initialization¶
Every device driver’s initialization function must return a pointer to the
device descriptor (device_t
) for this device, described in
kudos/drivers/device.h
.
Device driver initialization code is called from init()
on bootup. The
function called is:
void device_init(void)
Finds all devices connected to the system and attempts to initialize device drivers for them.
Implementation:
- Loop through the device descriptor area of YAMS.
- For each found device, try to find the driver by scanning through the list of available drivers (
drivers_available
inkudos/drivers/$ARCH/drivers.c
).- If a matching driver is found, call its initialization function and print the match to the console. Store the initialized driver instance to the device driver table
device_table
.- Otherwise print a warning about an unrecognized device.
After device drivers are initialized, we must have some mechanism to get a
handle of a specific device. This can be done with the device_get
function:
device_t *device_get(uint32_t typecode, uint32_t n)
Finds initialized device driver based on the type of the device and sequence
number. Returns nth initialized driver for device with type typecode
. The
sequencing begins from zero. If device driver matching the specifield type and
sequence number if not found, the function returns NULL
.
Generic Character Device¶
The generic character device (GCD) is an abstraction for any character-buffered (stream based) I/O device (e.g. a terminal). A GCD specifies read and write functions for the device, which have the same syntax for every GCD. Thus, when using GCD for all character device implementations, the code which reads or writes them does not have to care whether the device is a TTY or some other character device.
The generic character device is implemented as a structure with the fields
described in the gcd_t
structure in kudos/drivers/gcd.h
.
Generic Block Device¶
The generic block device (GBD) is an abstraction of a block-oriented device
(e.g. a disk). GBD consists of a function interface and a request
data
structure that abstracts the blocks to be handled. All functions are
implemented by the actual device driver.
The function interface is provided by the gbd_t
data structure in
kudos/drivers/gbd.h
. To use this interface, it is necessary to describe
requests in detail; for this, the gbd_request_t
data structure is used.
This structure includes all necessary information related to the reading or
writing of a block.
The GBD interface supports both synchronous and asynchronous calls (see the
gbd.h
file for the practical details).
In case of asynchronous calls, the gbd
interface functions will return
immediately. This means that the user must wait on an associated kernel
semaphore before continuing. Memory reserved for the request may not be
released until the semaphore is released. The thread using a GBD device must be
very careful especially with reserving memory from function stacks (ie. static
allocation). If the function is exited before the request is served, the memory
area of the request may corrupt.
In case of synchronous calls, the gbd
interface functions will block until
the request is handled. The memory of the request
data structure may be
released when control is returned.