aos/doc/019-device-drivers/DeviceDriver.tex
Daniel Schwyn 6d444bf552 Main handout
Signed-off-by: Daniel Schwyn <daniel.schwyn@inf.ethz.ch>
2022-03-03 14:57:51 +01:00

629 lines
32 KiB
TeX

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% Copyright (c) 2013, ETH Zurich.
% All rights reserved.
%
% This file is distributed under the terms in the attached LICENSE file.
% If you do not find this file, copies can be found by writing to:
% ETH Zurich D-INFK, Universitaetstr 6, CH-8092 Zurich. Attn: Systems Group.
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
\documentclass[a4paper,11pt,twoside]{report}
\usepackage{bftn}
\usepackage{calc}
\usepackage{verbatim}
\usepackage{xspace}
\usepackage{pifont}
\usepackage{textcomp}
\usepackage{amsmath}
\usepackage{multirow}
\usepackage{listings}
\title{Device Drivers in Barrelfish}
\author{Barrelfish project}
% \date{\today} % Uncomment (if needed) - date is automatic
\tnnumber{19}
\tnkey{Drivers}
\begin{document}
\maketitle % Uncomment for final draft
\begin{versionhistory}
\vhEntry{0.1}{05.12.2013}{GZ}{Initial Version}
\vhEntry{0.1}{16.05.2017}{GZ}{Update with info about new driver structure}
\end{versionhistory}
% \intro{Abstract} % Insert abstract here
% \intro{Acknowledgements} % Uncomment (if needed) for acknowledgements
\tableofcontents % Uncomment (if needed) for final draft
% \listoffigures % Uncomment (if needed) for final draft
% \listoftables % Uncomment (if needed) for final draft
\cleardoublepage
\setcounter{secnumdepth}{2}
\newcommand{\fnname}[1]{\textit{\texttt{#1}}}%
\newcommand{\datatype}[1]{\textit{\texttt{#1}}}%
\newcommand{\varname}[1]{\texttt{#1}}%
\newcommand{\keywname}[1]{\textbf{\texttt{#1}}}%
\newcommand{\pathname}[1]{\texttt{#1}}%
\newcommand{\tabindent}{\hspace*{3ex}}%
\lstset{
language=C,
basicstyle=\ttfamily \small,
flexiblecolumns=false,
basewidth={0.5em,0.45em},
boxpos=t,
captionpos=b
}
\chapter{New device driver interface}
\label{chap:introduction}
This document describes how we write device drivers in Barrelfish. It will walk
you through the necessary steps to integrate your driver in the Barrelfish
infrastructure and gives an overview of available APIs, libraries and tools to
help you with the process.
\section{Overview}
\label{chap:overview}
There are three main entities when discussing drivers:
\begin{itemize}
\item[Driver Domain] Is a domain that executes one or more drivers. It is
special in that it communicates with Kaluga to act on requests to
spawn or destroy new driver instances.
\item[Driver Module] A Barrelfish driver module is a piece of code
(typically a library) that contains the logic for a device driver.
It follows a well defined structure that allows Kaluga to interact with an
instantiated driver (see Driver Instance) in order to control its
life-cycle.
\item[Driver Instance] A driver instance is the runtime object instantiated from a
given driver module. In practice, any number of instances can be created
from a driver module and executed within one or more driver domains.
\end{itemize}
\section{Driver Domain}
\label{sec:domain}
A driver domain is a regular domain (process) in Barrelfish. The template driver
domain can be found in \pathname{usr/drivers/domain/main.c}, with most of the
respective logic implemented in \pathname{lib/driverkit}. Driver domains are
typically started by Kaluga (device manager), and then continue to communicate
with Kaluga using the ddomain interface (\pathname{if/ddomain.if}).
The ddomain interface exposes two API calls:
\begin{itemize}
\item[\fnname{create}] Creates a driver instance from a driver module.
\item[\fnname{destroy}] Destroys a previously created driver instance.
\end{itemize}
A driver domain can ``dynamically'' instantiate driver instances if a create call
is made to spawn an instance of a specific module. The driver domain uses a
basic C-style module system (similar to Linux kernel or Tensorflow modules). The
module system uses a custom ELF section (called .bfdrivers) where structs with
module informations are stored. This is achieved using a special linker file
located in \pathname{lib/driverkit/bfdrivers.ld}.
\section{Driver Module}
\label{sec:module}
The driver module is written as a library. A template module can be found in
\pathname{usr/drivers/domain/drivertpl.c}. The driver is then statically linked
with the driver domain. Currently, there is no support to dynamically load
modules since everything is statically linked in Barrelfish anyways. It's
important that the module is not linked like a regular library with the domain,
but rather as a module (\fnname{addModules = ["drivermodule"]}). This makes sure
the linker will not throw away the (seemingly unused) symbols during linking.
For an example, take a look at \pathname{usr/drivers/domain/Hakefile}.
A driver must provide an implementation for the following five functions:
\begin{itemize}
\item[\fnname{init}] Initialize driver an device.
\item[\fnname{detach}] Release ownership of the device temporarily.
\item[\fnname{attach}] Regain ownership of the device (after \fnname{detach}).
\item[\fnname{set\_sleep\_level}] Change power state of the device.
\item[\fnname{destroy}] Destruct the driver instance, release ownership of device.
\end{itemize}
A driver implementation can register itself as a module using the
\fnname{DEFINE\_MODULE} macro:
\begin{lstlisting}[caption={Registering an uart driver module}, label={lst:module}]
DEFINE_MODULE(uart, init, attach, detach, set_sleep_level, destroy);
\end{lstlisting}
\section{Driver Instance}
\label{sec:instance}
At runtime, the driver domain creates instances of a given module. This means a
having a specific, per-instance state coupled with the functions provided in
module along with the dcontrol interface (\pathname{if/dcontrol.if}) that is
exported for every driver instance.
Kaluga connects to the dcontrol interface of a driver instance to have more fine
grained control over the life-cycle of the instance. It exports the following
messages which basically act as wrappers for the driver functions described in
Section~\ref{sec:module}:
\begin{itemize}
\item[\fnname{attach}] Calls attach on the driver instance.
\item[\fnname{detach}] Calls detach on the driver instance.
\item[\fnname{set\_sleep\_level}] Calls \fnname{set\_sleep\_level} on the driver instance.
\end{itemize}
\chapter{Legacy device drivers}
\label{chap:legacy}
Legacy drivers live within a 9single) domain and the structure, as well as the control
interface of the driver is completely up to the programmer. No one was really
happy with this so we introduced some more rules of how a device driver is
written as described in the previous chapter. Unfortunately, most of our current
device drivers are still legacy drivers. So in order to understand what is going
on in such a driver, this chapter is kept around for now.
In a first step, we will look at the necessary bits and pieces to write a new
driver in Barrelfish on the ARM platform, by using an existing, very simple
driver as a walk-through from the Barrelfish code base -- the FDIF driver, a
device used for face detection typically found on OMAP chips.
The relevant code for the driver resides in the tree at
\pathname{usr/drivers/omap44xx/fdif/}. In order to get an idea of what the
driver entails, we will look at its Hakefile. Hakefiles are the declarative
description of a how a program is built in Barrelfish and what its
dependencies are. If you need more informations on our build system, you
should have a look at the Hake technote~\cite{btn003-hake}, which provides a
detailed description of Hake, the Barrelfish build system. We will look at the
Hakefile for the FDIF driver in Listing~\ref{lst:hakefile} (a Haskell program,
really) and explain what it means for the driver program. \varname{cFiles} is
a list of C source files that are compiled and linked for this driver. In our
case, we use hake to just search for every file that has a "c" file extension
in the current directory and return this as a list of files for
\varname{cFiles}. If you go and look at \pathname{usr/drivers/omap44xx/fdif}
you will find that this will encompass only two files, \pathname{fdif.c} and
\pathname{picture.c}.
The next attribute, \varname{mackerelDevices}, contains a list Mackerl files,
these are devices that we have specified in our domain specific language
called Mackerel, and we would now like to use in our driver. If you do not
know what Mackerel is, let me explain it to you: Mackerel is a DSL for
describing device registers. You can find an abundance of Mackerel files in
the \pathname{devices} directory inside the source tree. For example, the
\keywname{omap44xx\_fdif} device mentioned in the Hakefile at the third
position is found in \pathname{devices/omap/omap44xx\_fdif.dev}. They all,
describe a particular device we use in Barrelfish to varying degrees of
detail, ranging from ethernet cards to xAPIC or even descriptions of the file-
allocation-table for the FAT file-system. Now, you might think, well, why
can't I just use a regular C struct or even integer types with bit operations
for that? And in general you could. However, the Mackerel compiler gives you a
lot of nice things on top of this description. For one thing, it will generate
for you all the code you need to program the register with certain values,
that means it takes care of all the bit-shifting and masking operations based
on your description. On the other hand, it will generate functions that let
you print the contents of the register in a human readable way, which is a
very useful thing for debugging. For more information on Mackerel you should
read the Mackerel Technote~\cite{btn002-mackerel}.
The \varname{addLibraries} entry specifies the libraries your application
needs in order to run. In this example, we add driverkit, a helper library for
finding, and mapping our device registers in the virtual memory space of the
application.
The last argument specifies the architectures we want to build this driver
for. Since we are currently using this device on ARM/OMAP4 platforms, this is
set to ARMv7 and ARMv7-M architectures.
\begin{lstlisting}[caption={A Hakefile for a simple device driver}, label={lst:hakefile}]
[ build application {
target = "fdif",
cFiles = (find withSuffices [".c"]),
mackerelDevices = [
"omap/omap44xx_cam_prm",
"omap/omap44xx_cam_cm2",
"omap/omap44xx_fdif",
"omap/omap44xx_device_prm" ],
addLibraries = ["driverkit"],
architectures = ["armv7", "armv7-m"]
}]
\end{lstlisting}
Now let's have a look at \pathname{usr/drivers/omap44xx/fdif/fdif.c}, the
device driver source code. If you open the file, aside from the comment
header, you will see a bunch of included header files that are of interest to
us (Listing~\ref{lst:mackerel}).
\begin{lstlisting}[caption={Mackerel includes in driver source code.}, label={lst:mackerel}]
#include <dev/omap/omap44xx_cam_prm_dev.h>
#include <dev/omap/omap44xx_cam_cm2_dev.h>
#include <dev/omap/omap44xx_fdif_dev.h>
\end{lstlisting}
These look very familiar to our specified \varname{mackerelDevices} in the
Hakefile, and in fact, they are the generated header files based on our
mackerel files. If you want to have a look at them, you can find these header
files in your build directory in \pathname{<arch>/include/dev/omap}. In our
code, we use various functions (the ones starting with \keywname{omap44xx\_})
that are defined in these header files and use them to access device
registers.
Another interesting part is early on in the main function (Listing~\ref{lst:mapping}).
\begin{lstlisting}[caption={Mapping device registers in virtual memory.}, label={lst:mapping}]
err = map_device_register(0x4A10A000, 4096, &vbase);
\end{lstlisting}
\fnname{map\_device\_register} is a function provided by the driverkit
library. We will talk more about it later, but for now, here is what it does:
It takes the physical address of a device register (\varname{0x4A10A000}) the
size of the register (\varname{4096}) and will map this at a random virtual
address in your address space (given back to you by \varname{vbase}). Since
our device drivers all run in user-space this function ensures that you can
access the device in your address space. There is also the issue of how, and
which programs we allow access to what device registers. We will discuss this
in the next chapter.
For the the FDIF device, we can receive an interrupt from the device in case
the face processing is done. Since we run in user space, we have to invoke a
system call to register for the interrupt in the kernel. Once the interrupt
arrives, the kernel will use the message passing infrastructure of Barrelfish
to forward the IRQ to us. Fortunately, libbarrelfish provides us with a high-
level interface to do just that. In the function \fnname{enable\_irq\_mode},
we register an interrupt for the device by using
\fnname{inthandler\_setup\_arm} (Listing~\ref{lst:irqregister}). It takes as
arguments a handler function (\fnname{irq\_handler}) that is executed in case
the interrupt arrives, an additional state argument that is passed to that
function for this particular interrupt, in our case NULL, and the interrupt
number or vector we are interested in.
\begin{lstlisting}[caption={Register to receive an Interrupt.}, label={lst:irqregister}]
err = inthandler_setup_arm(irq_handler, NULL, FDIF_IRQ);
\end{lstlisting}
This concludes our walkthrough on the FDIF driver. So far, you have seen a
glimpse of the user-level side on writing device drivers for ARM. It consists
of an interplay of the build system, mackerel device descriptions, mapping
device registers, interrupt registrations and your actual driver code. In the
next chapters we will have a closer look on what is actually happening behind
the scenes and how to adapt the infrastructure for new architcures or boards.
Note that the FDIF driver is a very minimal example of a driver. We use it to
teach students about the basic concepts of device drivers. However, if you
would want to write a real driver, you also need to export a interface for
clients. In Barrelfish, the typical way is to export a message passing
interface for the driver, so that applications can connect and communicate
with the driver using messages. There are many source code examples in the
tree on how to do this, as a starting point, have a look at
\pathname{usr/examples/xmpl-call-response} in the source tree and the tech-
note on inter-dispatcher communication~\cite{btn011-idc} to get started.
\section{ARM and the simple SKB}
\label{sec:simpleskb}
If you look in the source tree of the SKB (\pathname{usr/skb/}) you will find
that there are currently two different versions of the SKB built. One is the
SKB based on the ECLiPSe runtime engine for x86 systems, the other is the SKB
simple for ARM. This is due to portability issues of the ECLiPSe runtime for
ARM. So, what is the simple SKB? It is an implementation of the Octopus API.
It is important to have at least a minimal a implementation of Octopus for all
architectures we run on. Because it provides essential system features such as
the name-service which is used most for of the service look-ups.
Not having the constraint logic programming interface unfortunately means we
can currently not use the APIs in \pathname{lib/skb} on ARM. We are currently
investigating alternatives for a constraint solver that will run on both
platforms.
\section{Kaluga -- The device manager}
Kaluga is the device manager in Barrelfish. Its responsibility is to manage
the periperhals of a system. That encompasses starting the correct drivers,
once a device is discovered, in the right order and making sure that each driver
has the permissions (capabilities) to access the device' memory areas
or I/O ports. In this chapter wewill learn how Kaluga interacts with the
rest of the system for device discovery and driver start-up.
For reasons stated in Section~\ref{sec:simpleskb} we currently do not have the
full system knowledge base on non-x86 platforms. Also, the ARM platforms we
support right now, do not have an infrastructure like PCI that brings
automatic device discovery -- there are device trees, but we do not
have support for them yet.
This means that right now, the way Kaluga finds the available devices differs
quite a bit based on the platform we are running on. This ranges from
automatic discovery using PCI, Octopus and the SKB on x86, to hardcoded
information in Kaluga for the OMAP4 SoC. However, the general way of
discovering what drivers are available and how we start them remains the same.
We will briefly look at what operations Kaluga provides for finding binaries
and how you can program it to start drivers the right way in your system.
If you look inside of the main function in Kaluga, you can see a call to the
\fnname{init\_boot\_modules} function. We usually rely on multiboot to provide
us with a set of ELF files at start-up. The \fnname{init\_boot\_modules}
function parses the information provided by multiboot (your menu.lst file) to
find a list of available binaries. It then looks at the arguments that are
hardcoded next to those binaries and follows a simple policy for these, if a
binary has the argument `'auto`' next to it, it considers this binary as a
driver and will start it, if it finds a suitable device. How Kaluga finds a
suitable device is explained in the following sections. Drivers are started in
different ways, ranging from just starting one driver binary to a number of
binaries or sending notifications to other subsystems and starting a driver.
Kaluga supports custom start-up policies for different binaries in your
system, you can set a start-up policy per driver binary using the
\fnname{set\_start\_function}. The default start function, the one that is
chosen if no special start-up function, is set for a binary, is defined in
\pathname{usr/kaluga/driver\_startup.c}. For example, on x86, this function
will just spawn the binary and provide as arguments the PCI device identifiers
(bus, class, function etc.) to the driver program.
As we mentioned before, a driver usually needs a special set of permissions to
gain access to the device registers. For historical reasons, the way we
provide this permissions currently differs between x86 and ARM. Unifying this
interface is part of future work.
\section{Starting PCI drivers on x86}
\label{sec:pcidriverstart}
On x86, peripherals are usually in the form of PCI or PCI express cards. PCI
supports automatic discovery of periperhals using PCI bus enumeration. In
Barrelfish, the PCI related code lives in \pathname{usr/pci}. PCI is
structured as a hierarchical tree with it's leaves being devices. The root
node, called PCI root bridge, forms the entry point to such a tree. PCI root
bridges are found by reading the ACPI tables. ACPI, short for Advanced
Configuration and Power Interface, is an open standard for device
configuration and power management in operating systems. ACPI related code in
Barrelfish lives in \pathname{usr/acpi}.
The bootstrapping of an x86 machine in Barrelfish works as follows: After
parsing the boot script, Kaluga starts ACPI. ACPI will then add specific
Octopus records for every PCI root bridge it finds. Meanwhile, Kaluga will
receive notification for all the root bridges added to Octopus. If a root
bridge is found, Kaluga will start the PCI domain which in turn will do a PCI
bus enumeration. Devices found during PCI bus enumeration are again added to
Octopus and propagated to Kaluga which will start individual device drivers to
handle the peripherals. How does Kaluga know what driver to start for each
device record? We already discussed how Kaluga uses different start functions
for different types of devices. But how do we choose the right binary? Kaluga
uses the SKB that stores a mapping from PCI identifiers to driver binaries. This
mapping is retrieved from the SKB once Kaluga receives a Octopus record for a
new device. You will find the mapping database in
\pathname{usr/skb/programs/device\_db.pl}. If you want to start your PCI driver
with Kaluga, you will need to add it there and provide at least the
corresponding device and vendor id.
Barrelfish has a number of drivers for PCI cards. Mostly for network
interfaces. Barrelfish drivers, including the ones for PCI, are located in the
source tree in \pathname{usr/drivers/}.
\section{Writing PCI drivers}
\label{sec:pcidriverwriting}
In order to write a PCI driver, one has to communicate with the PCI domain.
There is a client library that provides a helpful API in \pathname{lib/pci} that
helps doing that. One of the first steps is to initialize the client library
by connecting to the PCI domain:
\begin{lstlisting}[caption={A client connects to the PCI subsystem.}, label={lst:pciconenct}]
err = pci_client_connect();
\end{lstlisting}
After that, you are able to invoke the library functions in
Listing~\ref{lst:pciapi} to initialize devices. The functions allow to gain
control for a specific PCI device. The device is identified by using the
numerous PCI identifiers (subclass, prog\_if, vendor et. al.). The caller
provides a callback function (\fnname{init\_func}) that gets called by the
library once it has registered the device with the PCI domain.
\fnname{init\_func} takes as an argument an array of \keywname{struct
device\_mem}. A description of the basic address registers (BAR) for this PCI
device and also permissions (capabilities) to map these address registers in
the drivers address space. You can use the defined in helper functions in
\pathname{include/pci/mem.h} to map these BARs into the address space of the
client. For legacy devices (such as a serial driver for example) that live in
the I/O address space and do not use memory mapped registers you can use the
\fnname{pci\_register\_legacy\_driver\_irq} function.
\begin{lstlisting}[caption={A driver uses one of the following functions to register for PCI devices.}, label={lst:pciapi}]
errval_t pci_register_driver_noirq(pci_driver_init_fn init_func, uint32_t class,
uint32_t subclass, uint32_t prog_if,
uint32_t vendor, uint32_t device,
uint32_t bus, uint32_t dev, uint32_t fun);
errval_t pci_register_driver_irq(pci_driver_init_fn init_func, uint32_t class,
uint32_t subclass, uint32_t prog_if,
uint32_t vendor, uint32_t device,
uint32_t bus, uint32_t dev, uint32_t fun,
interrupt_handler_fn handler, void *handler_arg);
errval_t pci_register_legacy_driver_irq(legacy_driver_init_fn init_func,
uint16_t iomin, uint16_t iomax, int irq,
interrupt_handler_fn handler,
void *handler_arg);
\end{lstlisting}
Note that the discussed PCI API is rather low-level and provides a lot of
freedom in who can register for PCI devices. In the future the plan is for x86
to push more of that complexity in Kaluga. The device registration with PCI
should be done by Kaluga before the driver is started, the driver then only
receives a list of capabilities for a particular device which it can map in
its address space. That means a driver no longer has the need to call these
functions.
\section{Writing drivers for ARM and System-on-Chip platforms}
\label{sec:armdriverwriting}
You have already encountered most of the provided functionality for ARM
drivers in the overview in Chapter~\ref{chap:overview}. This section
will focus on how we currently support the OMAP platform to start
drivers in Kaluga.
On ARM the situation differs compared to x86. There is currently no
established standard like PCI for x86. That means that the way we have to
integrate ARM differs from platform to platform. We also have no support for
device trees at the moment. Therefore, if you look at the \fnname{main}
function in Kaluga, you will find that we currently look-up the binaries using
the \fnname{find\_module} function and hardwire the start-up of these drivers
for the pandaboard platform. In \pathname{omap\_startup.c} we define the start
function for these binaries. If you look at the code in the file you'll also
see that we use the function \fnname{spawn\_program\_with\_caps} to start the
driver and pass the driver a list of memory capabilities to access the device
memory. This is the service part of the driverkit library we have seen in
Chapter~\ref{chap:overview}, it makes sure the capabilities are actually given
to the driver in a way that driverkit can map them. What capabilities we give
to a driver for the OMAP chip is also hardcoded at the moment, you can find a
series of \varname{struct allowed\_registers} in the same file that defines for a
given driver, what memory ranges it is allowed to access. The situation is not
solved sufficiently right now, in the future, we would like to store this
information in a SKB like system that also runs on ARM and lets us query for
information about various platforms.
If you go on and read the capability technote \cite{btn013-capabilities}
you'll learn that capabilities can only be created in the kernel, and the
representation we have in user-space, are references to capabilities. So, a
valid question here is how Kaluga gets the capabilities for these devices in
the first place. For that we have to look at the \fnname{device\_caps.c} file
inside Kaluga. The file contains the capability manager or memory manager for
Kaluga, it is an instance of the memory management library found in
\pathname{lib/mm}. The memory manager (\keywname{libmm}) manages capabilities
for you, in reality it is a B+-tree structure that will manage a certain range
of memory, in our case device memory. It allows you to request a smaller range
from this usually very large range that we initalize our memory manager with
and, \keywname{libmm} will split up the inital capability we gave to the
instance at the beginning into smaller pieces and hand them out to you, giving
you a way to have fine grained, page level access control on memory. In
practice, because capabilities can only be created and split in half in the
kernel, it has to invoke system calls to do that.
As a note aside, there are three important memory managers in the system. The
one found in memserv (\pathname{usr/memserv}), it manages all physical memory,
the one in ACPI (\pathname{usr/acpi}), it handles all device memory on x86 and
should really be merged with the third one in Kaluga that we use for ARM.
If you look at the function \fnname{init\_cap\_manager} in \pathname{usr/kaluga/device\_caps.c}
you will find a call to the monitor to request the I/O capability:
\begin{lstlisting}[caption={RPC call to receive the I/O capability from
the monitor.}, label={lst:getio}]
err = cl->vtbl.get_io_cap(cl, &requested_cap, &error_code);
\end{lstlisting}
In the case of the ARM Pandaboard, the requested capability allows one to
access the whole space of the device memory. We pass this capability on to the
device manager in the \fnname{mm\_add} call further down. Now, we are free to
use the \fnname{get\_device\_cap} function, also defined in this file to
create fine grained capabilities for this entire memory range. If you go back
and look at code in \pathname{usr/kaluga/omap\_startup.c} you will find it
actually uses \fnname{get\_device\_cap} to create the capabilities it needs to
pass on to the device drivers.
Now you should understand how the user-space side works if you want to write
user-space drivers for your own platform. We have not covered yet how we
actually create a capability in the kernel and how it ends up in the monitor,
but we will cover that shortly in Section~\ref{sec:kernelmemory}.
\section{Kernel support for user-space drivers}
\label{chap:kernel}
In this chapter, we will look at the necessary support in the kernel, if we
want to write user-level device drivers on a new, unsupported platform. We
cover the main parts that are needed in this case: How do we forward
interrupts to user-space and how we create capabilities for device memory.
\section{Interrupts}
\label{sec:kernelirq}
In Chapter~\ref{chap:overview} we have already seen how we can register to
receive interrupts using the message passing architecture in Barrelfish. In
this section we will look at what the kernel does in order to forward the
interrupt to you. It all starts with having a driver for your interrupt
controller. We have support for a number of interrupt controllers already in
Barrelfish, like the xAPIC on x86 (\pathname{kernel/x86/apic.c}) or the GIC in
ARMv7 (\pathname{kernel/arch/armv7/gic.c}). If there is currently no interrupt
controller for your architecture, you'll have to write one yourself. In any
case, if you want to forward interrupts to user-space, you can rely on the
\fnname{send\_user\_interrupt} function provided by the architecture
independent part of the CPU driver. It allows you to forward interrupts
from the kernel to a domain running on its core using the message passing
infrastructure of Barrelfish \cite{btn011-idc}.
\section{Device Memory}
\label{sec:kernelmemory}
In Section~\ref{sec:armdriverwriting} we talked about how Kaluga constructs a
series of smaller capabilities for device drivers from an initial, huge
capability it receives from the monitor. We also mention that capabilities are
created in the kernel. In this Section we look at what is necessary to
create capabilities for device memory and how we can pass it on to user-space.
First you need to know what memory areas your devices are in. On x86 we
usually ask the BIOS to get a list of memory regions for RAM and device
memory. In Barrelfish, we construct capabilities for these regions and we hand
the device regions to ACPI which is the domain that initializes the ACPI
subsystem and does the memory book keeping for PCI drivers. On an ARM
platform, the device memory usually lives in a statically pre-defined range.
In \pathname{kernel/arch/omap44xx/startup\_arch.c} in
\fnname{spawn\_init\_common} we can see how we construct a capability for the
device memory range of the the OMAP4 platform. The relevant parts are
given in Listing~\ref{lst:capcreate}.
\begin{lstlisting}[caption={Creating a cabaility in the kernel and placing
it in the I/O slot in a task cnode.}, label={lst:capcreate}]
struct cte *iocap = caps_locate_slot(CNODE(spawn_state.taskcn), TASKCN_SLOT_IO);
errval_t err = caps_create_new(ObjType_DevFrame, 0x40000000, 30, 30, iocap);
\end{lstlisting}
\fnname{spawn\_init\_common} is setting up a new dispatcher control block, for
the first user-space program called init, in the system. Similar to a UNIX
based OS, all subsequent programs are children of init. The call to
\fnname{caps\_create\_new} creates a new capability of type
\varname{ObjType\_DevFrame}, a special type for device memory that makes sure
the pages are not zeroed before mapping it for the first time. The next two
arguments are the physical base of the address range and the size (in bits) of
the range. This particular capability covers a memory range of $2^{30}$ bytes,
or one GiB, starting from address \varname{0x40000000} -- the device memory
region of the OMAP4 chip. The last argument specifies where this new
capability is stored. The location is defined by the preceding
\fnname{caps\_locate\_slot} function call. You can think of the
\fnname{caps\_locate\_slot} function as an array look-up. We use the task
CNode (a table of capabilities) of \varname{spawn\_state}, a struct
representing the kernel state for the init domain. We use
\varname{TASKCN\_SLOT\_IO} as an index to the cnode table. Once init is
started, it can refers to this capability by using the \varname{TASKCN\_SLOT\_IO}
offset to find it. If you look inside \pathname{usr/init/spawn.c} you will
find the code (Listing~\ref{lst:slotio}) doing just that to propagate the capability on to the monitors task cnode. Notice that \fnname{cap\_copy} is
now a system call. The monitor then can use the I/O capability in
his task cnode if somebody requests it (for example by using \fnname{get\_io\_cap},
seen in Listing~\ref{lst:getio}).
\begin{lstlisting}[caption={Copy of the I/O capability from
\varname{src} to \varname{dest}.}, label={lst:slotio}]
/* Give monitor IO */
dest.cnode = si->taskcn;
dest.slot = TASKCN_SLOT_IO;
src.cnode = cnode_task;
src.slot = TASKCN_SLOT_IO;
err = cap_copy(dest, src);
if (err_is_fail(err)) {
return err_push(err, INIT_ERR_COPY_IO_CAP);
}
\end{lstlisting}
\section{Limitations \& Work in Progress}
Altough we currently have the necessary support for user-space drivers
on both major platforms Barrelfish runs on we do not yet have an
unified interface between ARM and x86 architectures. In this technote
we have seen both approaches explained to varying levels of details and
we mentioned briefly where the two approaches differ. In the future we will
most likely unify both platforms under a standardized API which will have
the best of both worlds.
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
\bibliographystyle{abbrv}
\bibliography{barrelfish}
\end{document}