Built-in Drivers¶
KUDOS ships with several built-in working drivers. The drivers for the MIPS target are all designed to work with YAMS hardware. The purpose of this section is to show how one can create actual drivers for for the KUDOS device driver interface.
TTY¶
TTY roughly stands for “teletype”, and in this context refers to a class of drivers that provide a low-level interface for terminal-style user interaction. KUDOS comes with both a polling, and interrupt-driven TTY driver built-in. The former works by repeatedly polling the keyboard device, which can waste precious clock-cycles, while the latter relies on an interrupt handler to wake up the kernel thread when input from the keyboard actually becomes available.
Historically, TTY drivers also implement a line discipline, and the KUDOS TTY drivers are no exception.
Polling TTY Driver¶
Two separate drivers are provided for the TTY (the terminal). The first one is implemented by polling and the other with interrupt handlers.
Polling means that the kernel again and again asks the (virtual, in this case) hardware if anything new has come up. Depending on interrupt handlers means that the hardware signals the kernel when a change has occurred.
The polling driver is needed when booting up, since interrupts are disabled. It is also useful in kernel panic situations, because interrupt handlers might not be relied on in such error cases.
Perhaps the easiest way to use the polling TTY driver is using the built-in
functions kwrite
and kprintf
(defined in kudos/lib/libc.h
). See
kudos/drivers/polltty.h
and kudos/drivers/$ARCH/polltty.c
for the
implementation and documentation of the driver itself.
Interrupt-driven TTY Driver¶
The interrupt driven (i.e. the asynchronous) TTY driver is the terminal device
driver used most of the kernel terminal I/O-routines. The terminal driver has
two functions to provide output to the terminal and input to the kernel. Both
of these happen asynchronously. i.e. the input handling is triggered when the
user presses a key on the keyboard. The output handler is invoked when some
part of the kernel requests a write. The asynchronous TTY driver is implemented
in drivers/$ARCH/tty.c
and implements the generic character device
interface.
The following functions implement the TTY driver:
device_t *tty_init(io_descriptor_t *desc)
¶
Initialize a driver for the TTY defined by desc
. This function is called
once for each TTY driver present in the YAMS virtual machine.
Implementation:
- Allocate memory for one
device_t
.- Allocate memory for one
gcd_t
and setgeneric_device
to point to it.- Set
gcd->device
to point to the allocateddevice_t
,gcd->write
totty_write
andgcd->read
totty_read
.- Register the interrupt handler (
tty_interrupt_handle
).- Allocate a structure that has (small) read and write buffers and head and count variables for them, and a spinlock to synchronize access to the structure and
real_device
to point to it. The first tty driver’s spinlock is shared withkprintf()
(i.e. the first tty device is shared with polling TTY driver).- Return a pointer to the allocated
device_t
.
void tty_interrupt_handle(device_t *device)
¶
Handle interrupts concerning device
. This function is never called
directly from kernel code, instead it is invoked from interrupt handler.
Implementation if WIRQ (write interrupt request) is set:
- Acquire the driver spinlock.
- Issue the WIRQD into COMMAND (inhibits write interrupts).
- Issue the Reset WIRQ into COMMAND.
- While WBUSY is not set and there is data in the write buffer, Reset WIRQ and write a byte from the write buffer to DATA.
- Issue the WIRQE into COMMAND (enables write interrupts).
- If the buffer is empty, wake up the threads sleeping on the write buffer.
- Release the driver spinlock.
Implementation if RIRQ (read interrupt request) is set:
- Acquire the driver spinlock.
- Issue the Reset RIRQ command to COMMAND. If this caused an error, panic (serious hardware failure).
- Read from DATA to the read buffer while RAVAIL is set. Read all available data, even if the read buffer becomes filled (because the driver expects us to do this).
- Release the driver spinlock.
- Wake up all threads sleeping on the read buffer.
static int tty_write(gcd_t *gcd, void *buf, int len)
¶
Write len
bytes from buf
to the TTY specified by gcd
.
Implementation:
- Disable interrupts and acquire driver spinlock.
- As long as write buffer is not empty, sleep on it (release-reacquire for the spinlock).
- Fill the write buffer from
buf
.- If WBUSY is not set, write
one
byte to the DATA port. (This is needed so that the write IRQ is raised. The interrupt handler will write the rest of the buffer.)- If there is more than one byte of data to be written, release the spinlock and sleep on the write buffer.
- If there is more data in
buf
, repeat from step 3.- Release spinlock and restore interrupt state.
- Return the number of bytes written.
static int tty_read(gcd_t *gcd, void *buf, int len)
¶
Read at least one and at most len
bytes into buf
from the TTY specified
by gcd
.
Implementation:
- Disable interrupts and acquire driver spinlock.
- While there is no data in the read buffer, sleep on it (release-reacquire for the spinlock).
- Read
MIN(len, data-in-readbuf)
bytes intobuf
from the read buffer.- Release spinlock and restore interrupt state.
- Return the number of bytes read.
Disk Driver¶
The disk driver implements the Generic Block Device (GBD) interface. The driver is interrupt-driven and provides both synchronous (blocking) and asynchronous (non-blocking) operating modes for request. The driver has three main parts:
- An initialization function, which is called in startup when a disk is found.
- An interrupt handler.
- Functions which implement the GBD interface (read, write and information inquiring).
The disk driver maintains a queue of pending requests. The queue insertion is handled in disk scheduler, which currently just inserts new requests at the end of the queue. This queue, as well as access to the disk device, is protected by a spinlock. The spinlock and queue are stored in driver’s internal data. The internal data also contains a pointer to the currently served disk request.
The disk driver is implemented and documented in kudos/drivers/$ARCH/disk.c
.
Note how the fields modified by both the inquiring and interrupt-ready parts of
the driver are marked as volatile
, so that the compiler won’t optimize
access to them (store them in registers and assume that value is valid later,
which would be a flawed approach because of interrupts, which can change the
values of the variables asynchronously).
Timer Driver¶
The Timer driver allows to set timer interrupts at certain intervals. The
timer_set_ticks()
C function works as a front-end for the
_timer_set_ticks
assembler function. The C function takes anumber of
processor clock cycles after the timer interrupt is wanted to happen, and it
passes it to the assembler function that does all work.
A timer interrupt is caused by using CP0
registers Count
and
Compare
. The Count
register contains the current cycle count, and the
Compare
register contains the cycle number where the timer interrupt is to
happen. The assembler function simply adds the number of cycles to the current
cycle count and writes it to the Compare
register.
The timer driver is implemented and documented in kudos/drivers/timer.c
and
kudos/drivers/$ARCH/_timer.S
.
Metadevice Drivers¶
“Metadevices” is a name for those devices documented in the YAMS documentation
as non-peripheral devices (the 0x100
series). They don’t really interface
to any specific device but rather to the system itself (the motherboard main
chipset, firmware or similar). The metadevices and their drivers are very
simple, and they are as follows.
See kudos/drivers/metadev.h
and kudos/drivers/$ARCH/metadev.c
for the
implementation and description of the following metadevices.
Meminfo¶
The system memory information device provides information about the amount of memory present in the system.
RTC¶
The Real Time Clock (RTC) device provides simulated real time data, such as system uptime and clock speed. It is a wrapper to the RTC device I/O ports.
Shutdown¶
The (software) shutdown device is used to either halt the system by dropping to the YAMS console (firmware console) or “poweroff” the system by exiting YAMS completely.
CPU Status¶
Each processor has its own status device. These devices can be used to count the number of CPUs on the system or to generate interrupts on any CPU.
Exercises¶
- Both
kwrite
andkprintf
use the polling TTY driver. Why?