\chapter{Device Drivers} This section written by Peter De Schrijver and Daniel Wagner. \section{Requirements} \begin{itemize} \item Performance: Speed is important! \item Portability: Framework should work on different architectures. Also: Useable in a not hurdisch environment with only small changes. \item Flexibility \item Convenient interfaces \item Consistency \item Safety: driver failure should have as minimal system impact as possible. \end{itemize} \section{Overview} The framework consists of: \begin{itemize} \item Bus drivers \item Device drivers \item Service servers (plugin managers, $\omega_0$, deva) \end{itemize} \subsection{Layer of the drivers} The device driver framework consists only of the lower level drivers and doesn't need to have a complicated scheme for access control. This is because it should be possible to share devices, e.g. for neighbour Hurd. The authentication is done by installing a virtual driver in each OS/neighour Hurd. The driver framework trusts these virtual drivers. So it's possible for a non Hurdish system to use the driver framework just by implementing these virtual drivers. Only threads which have registered as trusted are allowed to access device drivers. The check is simply done by checking the senders ID against a table of known threads. \subsection{Address spaces} Drivers always reside in their own AS. The overhead for cross AS IPC is small enough to do so. \subsection{Zero copying and DMA} It is assumed that there are no differences between physical memory pages. For example each physical memory page can be used for DMA transfers. Of course, older hardware like ISA devices can so not be supported. Still some support for ISA devices like serial ports and PS/2 for keyboard is needed. With this assumption, the device driver framework can be given any physical memory page for DMA operation. This physical memory page must be pinned down. If an application wants to send or receive data to/from a device driver it has to tell the virtual driver the page on which the operation has to be executed. Since the application doesn't know the virtual-real memory mapping, it has to ask the physical memory manager for the real memory address of the page in question. If the page is not directly mapped from the physical memory manager the application asks the mapper (another application which has mapped this memory region to the first application) to resolve the mapping. This can be done recursively. Normally, this resolving of a mapping can be sped up using a cache services, since a small number of pages are reused very often. With the scheme, the drivers do not have to take special care of zero copying if there is only one virtual driver. When there is more than one virtual driver pages have to be copied for all other virtual drivers. \subsection{Physical versus logical device view} The device driver framework will only offer a physical device view. Ie. it will be a tree with devices as the leaves connected by various bus technologies. Any logical view and naming persistence will have to be build on top of this (translator). \subsection{Things for the future} \begin{itemize} \item Interaction with the task server (e.g. listings driver threads with ps,etc.) \item Powermanagement \end{itemize} \section{Bus Drivers} A bus driver is responsible to manage the bus and provide access to devices connected to it. In practice it means a bus driver has to perform the following tasks: \begin{itemize} \item Handle hotplug events Busses which do not support hotplugging, will treated as if there is 1 insertion event for every device connected to it when the bus driver is started. Drivers which don't support autoprobing of devices will probably have to read some configuration data from a file\footnote{It might be a good idea, if the device driver has no notion how the configuraiton is stored. It just asks the bus driver which should know how to get the configuration.} or if the driver is needed for bootstrapping configuration can be given as argument on its stack. In some cases the bus doesn't generate insertion/removal events, but can still support some form of hotplug functionality if the user tells the driver when a change to the bus configuration has happened (eg. SCSI). \item Configure client device drivers The bus driver should start the appropriate client device driver translator when an insertion event is detected. It should also provide the client device driver with all necessary configuration info, so it can access the device it needs. This configuration data typically consists of the bus addresses of the device and possibly IRQ numbers or DMA channel ID's. The device driver is loaded by the associated plugin manager. \item Provide access to devices This means the bus driver should be able to perform a bus transaction on behalf of a client device driver. In some cases this involves sending a message and waiting for reply (eg. SCSI, USB, IEEE 1394, Fibre Channel,...). The driver should provide send/receive message primitives in this case. In other cases devices on the bus can be accessed by memory accesses or by using special I/O instructions. In this case the driver should provide mapping and unmapping primitives so a client device driver can get access to the memory range or is allowed to access the I/O addresses. The client device driver should use a library, which is bus dependant, to access the device on the bus. This library hides the platform specific details of accessing the bus. \item Rescans Furthermore the bus driver must also support rescans for hardware. It might be that not all drivers are found during bootstrapping and hence later on drivers could be loaded. This is done by generating new attach notification, which are sent to the bus's plugin manager. The plugin manager then loads a new driver, if possible. A probe funtion is not needed since all supported hardware can be identified by vendor/device identification (unlike ISA hardware). For hardware busses which don't support such identification only static configuration is possible (configuration scripts etc.) \end{itemize} \subsection{Root bus driver} The root bus is the entrypoint to look up devices. \subsection{Generic Bus Driver} Operations: \begin{itemize} \item notify (attach, detach) \item string enumerate \end{itemize} \subsection{ISA Bus Driver} Inherits from: \begin{itemize} \item Generic Bus Driver \end{itemize} Operations: \begin{itemize} \item (none) \end{itemize} \subsection{PCI Bus Driver} Inherits from: \begin{itemize} \item Generic Bus Driver \end{itemize} Operations: \begin{itemize} \item map\_mmio: map a PCI BAR for MMIO \item map\_io: map a PCI BAR for I/O \item map\_mem: map a PCI BAR for memory \item read\_mmio\_{8,16,32,64}: read from a MMIO register \item write\_mmio\_{8,16,32,64}: write to a MMIO register \item read\_io\_{8,16,32,64}: read from an IO register \item write\_io\_{8,16,32,64}: write to an IO register \item read\_config\_{8,16,32,?}: read from a PCI config register \item write\_config\_{8,16,32,?}: write to a PCI config register \item alloc\_dma\_mem(for non zero copying): allocate main memory useable for DMA \item free\_dma\_mem (for non zero copying): free main memory useable for DMA \item prepare\_dma\_read: write back CPU cachelines for DMAable memory area \item sync\_dma\_write: discard CPU cachelines for DMAable memory area \item alloc\_consistent\_mem: allocate memory which is consistent between CPU and device \item free\_consistent\_mem: free memory which is consistent between CPU and device \item get\_irq\_mapping (A,B,C,D): get the IRQ matching the INT(A,B,C,D) line \end{itemize} \section{Device Drivers} \subsection{Classes} \begin{itemize} \item character: This the standard tty as known in the Unix environment. \item block \item human input: Keyboard, mouse, ... \item packet switched network \item circuit switched network \item framebuffer \item streaming audio \item streaming video \item solid state storage: flash memory \end{itemize} \subsection{Human input devices (HID) and the console} The HIDs and the console are critical for user interaction with the system. Furthmore, the console should be working as soons as possible to give feedback. Log messages which are send to the console before the hardware has been initialized should be buffered. \subsection{Generic Device Driver} Operations: \begin{itemize} \item init : prepare hardware for use \item start : start normal operation \item stop : stop normal operation \item deinit : shutdown hardware \item change\_irq\_peer : change peer thread to propagate irq message to. \end{itemize} \subsection{ISA Devices} Inherits from: \begin{itemize} \item Generic Device Driver \end{itemize} Supported devices \begin{itemize} \item Keyboard (ps2) \item Serial port (mainly for debugging purposses) \end{itemize} \subsection{PCI Devices} Inherits from: \begin{itemize} \item Generic Device Driver \end{itemize} Supported devices: \begin{itemize} \item block devices \end{itemize} \section{Service Servers} \subsection{Plugin Manager} Each bus driver has a handle/reference to which insert/remove events are send. The owner of the handle/refence must then take appropriate action like loading the drivers. These actors are called plugin managers. The plugin manager is also the pager for the loaded driver. \begin{comment} Obviously, the plugin manager needs some sort of exec format support. Maybe it's own ELF loader. \end{comment} \subsection{Deva} Deva stands for \emph{Device Access Server}. This server implements basic services for the device driver framework like thread creation, thread deletion, etc. The device driver framework itself doesn't depend on any Hurd code. The interaction with the Hurd system will be abstracted by deva. Which services must deva provide: \begin{itemize} \item task/thread manipulation (create, deletion) \item memory (de)allocation (virtual, physical) \item io ports \item driver (un)loading \item bootstrapping \end{itemize} %% Deva is also the pager for the first plugin manager. \subsection{$\omega_0$} $\omega_0$ is a system-central IRQ-logic server. It runs in the privileged AS space in order to be allowed rerouting IRQ IPC. If an IRQ is shared between several devices, the drivers are daisy chained and have to notify their peers if an IRQ IPC has arrived. For more details see http://os.inf.tu-dresden.de/\~hohmuth/prj/omega0.ps.gz Operations: \begin{itemize} \item attach\_irq : attach an ISR thread to the IRQ \item detach\_irq : detach an ISR thread from the IRQ \end{itemize} \section{Resource Management} \subsection{IRQ handling} \subsubsection{IRQ based interrupt vectors} Some CPU architectures (eg 68k, IA32) can directly jump to an interrupt vector depending on the IRQ number. This is typically the case on CISC CPU's. In this case there is some priorization scheme. On IA32 for example, the lowest IRQ number has the highest priority. Sometimes the priorities are programmable. Most RISC CPU's have only a few interrupt vectors which are connected external IRQs. (typically 1 or 2). This means the IRQ handler should read a register in the interrupt controller to determine which IRQ handler has to be executed. Sometimes the hardware assists here by providing a register which indicates the highest priority interrupt according to some (programmable) scheme. \subsubsection{IRQ acknowlegdement} The IRQ acknowledgement is done in two steps. First inform the hardware about the successful IRQ acceptance. Then inform the ISRs about the IRQ event. \subsubsection{Edge versus level triggered IRQs} Edge triggered IRQs typically don't need explicit acknowledgment by the CPU at the device level. You can just acknowledge them at the interrupt controller level. Level triggered IRQs typically need to explicitly acknowledged by the CPU at the device level. The CPU has to read or write a register from the IRQ generating peripheral to make the IRQ go away. If this is not done, the IRQ handler will be reentered immediatly after it ended, effectively creating an endless loop. Another way of preventing this would be to mask the IRQ. \subsubsection{Multiple interrupt controllers} Some systems have multiple interrupt controllers in cascade. This is for example the case on a PC, where you have 2 8259 interrupt controllers. The second controller is connected to the IRQ 2 pin of the first controller. It is also common in non PC systems which still use some standard PC components such as a Super IO controller. In this case the 2 8259's are connected to 1 pin of the primary interrupt controller. Important for the software here is that you need to acknowledge IRQ's at each controller. So to acknowledge an IRQ from the second 8259 connected to the first 8259 connected to another interrupt controller, you have to give an ACK command to each of those controllers. Another import fact is that on the PC architecture the order of the ACKs is important. \subsubsection{Shared IRQs} Some systems have shared IRQs. In this case the IRQ handler has to look at all devices using the same IRQ... \subsubsection{IRQ priorities} All IRQs on L4 have priorities, so if an IRQ occurs any IRQ lower then the first IRQ will be blocked until the first IRQ has been acknowlegded. ISR priorities must much the hardware priority (danger of priority inversion). Furthermore the IRQ acknowledgment order is important. The 8259 also supports a specific IRQ acknowledge iirc. But, this scheme does not work in most level triggered IRQ environments. In these environments you must acknowledge (or mask) the IRQ before leaving the IRQ handler, otherwise the CPU will immediately reenter the IRQ handler, effectively creating an endless loop. In this case L4 would have to mask the IRQ. The IRQ thread would have to unmask it after acknowledgement and processing. \subsubsection{IRQ handling by L4/x86} The L4 kernel does handle IRQ acknowlegdment. \subsection{Memory} If no physical memory pages are provided by the OS the device driver framework alloces pages from the physical memory manager. The device driver framework has at no point of time to handle any virtual to physical page mapping. \section{Bootstrapping} The device driver framework will be started by deva, which is started by wortel. All drivers and servers (e.g. the plugin manager) are stored in a archive which will be extracted by deva. \subsection{deva} For bootstrapping deva will only have a subset of drivers ready. As soon the filesystem runs deva can ask for drivers from the harddisk. If new drivers are available it has to inform the plugin manager to ask for unresolved drivers again. Deva starts as first task a plugin server. The plugin server does then the rest of the bootstrapping process. \subsection{Plugin Manager} A Plugin manager handles driver loading for devices. It asks deva for drivers. The first plugin server does also some bootstrapping. First, it starts the root bus driver. \section{Order of implementation} \begin{enumerate} \item deva, plugin manager \item root bus server \item pci bus \item isa bus \item serial port (isa bus) \item console \end{enumerate} \section{Scenarios} \subsection{Insert Event} \begin{figure} \begin{center} \leavevmode \includegraphics[scale=0.8]{ddf_insert_event.eps} \end{center} \caption[New hardware detected]{A new hardware device is detected (a network card) by the PCI root bus driver. The PCI root bus driver initiates the loading of the correct driver for the new hardware device.} \label{fig:ddf_insert_event} \end{figure} If a simple hardware device is found the ddf will load a driver for the new hardware device as follows (see Figure~\ref{fig:ddf_insert_event}): \begin{enumerate} \item The PCI Bus Driver detects a hardware device for which no driver has been loaded yet. It generates an insert event which it sends to one (all?) registered entity. The interface for the event handler has not been decided yet. \item The Root Bus Driver receives the event signal. Note it is not necessary that the Root Bus Driver handles the insert signal for all drivers. It forwards the signal to the/a Plugin Manager (PLM). \item The/a Plugin Manager (PLM) asks Deva to load the driver binary for the new device. \item Deva forwards the loading request to the ext2 filesystem process. During bootstrapping Deva will handle the request by itself. Deva has an archive of drivers loaded by grub. \item The ext2 process decides where it finds the device driver binary (block address) \item The ddwrapper (device driver wrapper) forwards the read call from the ext2 process to the IDE Driver. \item After checking if the caller is allowed start a read command, the IDE Driver reads the device driver from the disk. \item The IDE Driver returns the data. \item ddwrapper returns the data. XXX This might be wrong. IFRC, the data is returned in a container and only the handle of the container is transfered. \item Ext2 returns the device driver (data). \item Deva returns the device driver (data). \item Ask Deva to create a new address space. \item Deva asks wortel to create new address space. \item wortel returns ``a new address space''. \item Deva returns ``a new address space''. \item PLM is registered as pagefault handler for the new driver address space. The bootstrap thread starts to run and generates a page fault. \item PLM asks Deva for memory. \item Deva asks physmem for memory. \item physmem returns memory pages. \item Deva returns memory pages. \item PLM maps the device driver binary into the address space of the new driver. \end{enumerate} \subsection{Several Plugin Managers} \begin{figure} \begin{center} \leavevmode \includegraphics{ddf_several_plms.eps} \end{center} \caption[Several plugin managers]{For the new NIC driver a specialised plugin manager is loaded first.} \label{fig:ddf_several_plms} \end{figure} For certain drivers it makes sense to have specialised plugin managers. The default plugin manger (dPLM) has to be asked to create a new plugin manager. It is loaded like a normal driver. The default plugin manager will also act as pager for the new plugin manager. When the new plugin manager is activated it registers itself to the Deva as new plugin manager. Deva will send all signals/messages from outside of the ddf to all registered plugin managers.