8b6f4cb290
Change-Id: Ic64b501b7166dd718aaf12412833f912e23bc6bf Reviewed-on: https://review.haiku-os.org/c/967 Reviewed-by: waddlesplash <waddlesplash@gmail.com>
316 lines
14 KiB
HTML
316 lines
14 KiB
HTML
<html>
|
|
<body bgcolor=white>
|
|
|
|
<h1>Introduction to Haiku's Device Driver Architecture</h1>
|
|
|
|
<p>This document tries to give you a short introduction into the new device
|
|
manager, and how to write drivers for it. Haiku still supports the legacy
|
|
device driver architecture introduced with BeOS.</p>
|
|
|
|
<p>The new device driver architecture of Haiku is still a moving target,
|
|
although most of its details are already specificed.</p>
|
|
|
|
|
|
<h2>1. The Basics</h2>
|
|
|
|
<p>The device manager functionality builds upon <i>device_node</i> objects.
|
|
Every driver in the system publishes one or more of such nodes, building a
|
|
tree of device nodes. This tree is in theory a dynamic representation of the
|
|
current hardware devices in the system, but in practice will also contain
|
|
implementation specific details; since every node comes with an API specific
|
|
to that node, you'll find device nodes that only come with a number of support
|
|
functions for a certain class of drivers.</p>
|
|
|
|
<p>Structurally, a <i>device_node</i> is a set of a module, attributes,
|
|
and resources, as well as a parent and children. At a minimum, a node must
|
|
have a module, all other components are optional.</p>
|
|
|
|
TODO: picture of the device node tree
|
|
|
|
<p>When the system starts, there is only a root node registered. Only primary
|
|
hardware busses register with the root node, such as PCI, and ISA on x86.
|
|
Since the PCI bus is an intelligent bus, it knows what hardware is installed,
|
|
and registers a child node for each device on the bus.</p>
|
|
|
|
<p>Every driver can also publish a device in <i>/dev</i> for communication with
|
|
userland applications. All drivers and devices are kernel modules.</p>
|
|
|
|
|
|
<h2>2. Exploring the Device Tree</h2>
|
|
|
|
<p>So how does it all work? When building the initial device tree, the system only
|
|
explores a minimum of device drivers only, resulting in a tree that basically
|
|
only shows the hardware found in the computer.</p>
|
|
|
|
<p>Now, if the system requires disk access, it will scan the device file system
|
|
for a driver that provides such functionality, in this case, it will look for
|
|
drivers under "/dev/disk/". The device manager has a set of built-in rules for
|
|
how to translate a device path into a device node, and vice versa: every node
|
|
representing a device of an intelligent bus (such as PCI) will also contain
|
|
device type information following the PCI definitions. In this case, the "disk"
|
|
sub-path will translate into the <i>PCI_mass_storage</i> type, and hence, the
|
|
device manager will then completely explore all device nodes of that type.</p>
|
|
|
|
<p>It will also use that path information to only ask drivers that actually
|
|
are in a matching module directory. In the above example of a disk driver, this
|
|
would be either in "busses/scsi", "busses/ide", "drivers/disk", ...</p>
|
|
|
|
<p>For untyped or generic busses, it will use the context information gained
|
|
from the devfs query directly, and will search for drivers in that sub directory
|
|
only. The only exception to this rule are the devfs directories "disk", "ports",
|
|
and "bus", which will also allow to search matching drivers in "busses". While
|
|
this is relatively limited, it is a good way to cut down the number of drivers
|
|
to be loaded.</p>
|
|
|
|
|
|
<h2>3. Writing a Driver</h2>
|
|
|
|
<p>The device manager assumes the following API from a driver module:</p>
|
|
<ul>
|
|
<li><b>supports_device()</b><br>
|
|
Determines wether or not the driver supports a given parent device node,
|
|
that is the hardware device it represents (if any), and the API the node
|
|
exports.</li>
|
|
<li><b>register_device()</b><br>
|
|
The driver should register its device node here. The parent driver is
|
|
always initialized at this point. When registering the node, the driver
|
|
can also attach certain I/O resources (like I/O ports, or memory ranges)
|
|
to the node -- the device manager will make sure that only one node can
|
|
claim these resources.</li>
|
|
<li><b>init_driver()</b><br>
|
|
Any initialization necessary to get the driver going. For most drivers,
|
|
this will be reduced to the creation of a private data structure that is
|
|
going to be used for all of the following functions.</li>
|
|
<li><b>uninit_driver()</b><br>
|
|
Uninitializes resources acquired by <b>init_driver()</b>.</li>
|
|
<li><b>register_child_devices()</b><br>
|
|
If the driver wants to register any child device nodes or to publish
|
|
any devices, it should do so here. This function is called only during
|
|
the initial registration process of the device node.</li>
|
|
<li><b>rescan_child_devices()</b><br>
|
|
Is called whenever a manual rescan is triggered.</li>
|
|
<li><b>device_removed()</b></br>
|
|
Is called when the device node is about to be unregistered when its
|
|
device is gone, for example when a USB device is unplugged.</li>
|
|
<li><b>suspend()</b><br>
|
|
Enters different sleep modes.</li>
|
|
<li><b>resume()</b><br>
|
|
Resumes a device from a previous sleep mode.</li>
|
|
</ul>
|
|
|
|
<p>To ensure that a module exports this API, it <b>must</b> end its module name
|
|
with "driver_v1" to denote the version of the API it supports. Note that
|
|
<b>suspend()</b> and <b>resume()</b> are currently never called, as Haiku has
|
|
no power management implemented yet.</p>
|
|
|
|
<p>If your driver can give the device it is attached to a nice name that can be
|
|
presented to the user, it should add the <b>B_DEVICE_PRETTY_NAME</b> attribute
|
|
to the device node.
|
|
|
|
<p>The <b>B_DEVICE_UNIQUE_ID</b> should be used in case the device has a unique
|
|
ID that can be used to identify it, and also differentiate it from other devices
|
|
of the same model and vendor. This information will be added to the file system
|
|
attributes of all devices published by your driver, so that user applications
|
|
can identify, say, a USB printer no matter what USB slot it is attached to, and
|
|
assign it additional data, like paper configuration, or recognize it as the
|
|
default printer.</p>
|
|
|
|
<p>If your driver implements an API that is used by a support or bus module, you
|
|
will usually use the <b>B_DEVICE_FIXED_CHILD</b> attribute to specify exactly
|
|
which child device node you will be talking to. If you support several child
|
|
nodes, you may want to have a closer look at the section explaining
|
|
<a href="#bus_driver">how to write a bus driver</a>.</p>
|
|
|
|
<p>In addition to the child nodes a driver registers itself, a driver can either
|
|
have dynamic children or fixed children, never both. Also, fixed children are
|
|
registered before <b>register_child_devices()</b> is called, while dynamic
|
|
children are registered afterwards.</p>
|
|
|
|
|
|
<h2>4. Publishing a Device</h2>
|
|
|
|
To publish a device entry in the device file system under <i>/dev</i>, all your
|
|
driver has to do is to call the
|
|
<pre>
|
|
publish_device(device_node *node, const char *path,
|
|
const char *deviceModuleName);
|
|
</pre>
|
|
function the device manager module exports. The <i>path</i> is the path
|
|
component that follows "/dev", for example "net/ipro1000/0". The
|
|
<i>deviceModuleName</i> is the module exporting the device functionality.
|
|
It should end with "device_v1" to show the device manager which protocol it
|
|
supports. If the device node your device belongs to is removed, your device
|
|
is removed automatically with it. On the other hand, you are allowed to
|
|
unpublish the device at any point using the <b>unpublish_device()</b> function
|
|
the device manager delivers for this.</p>
|
|
|
|
<p>A device module must export the following API:</p>
|
|
<ul>
|
|
<li><b>init_device()</b><br>
|
|
This is called when the open() is called on this device for the first
|
|
time. You may want to create a private data structure that is passed on
|
|
to all subsequent calls of the <b>open()</b> function that your device
|
|
exports.</li>
|
|
<li><b>uninit_device()</b><br>
|
|
Is called when the last file descriptor to the device had been closed.</li>
|
|
<li><b>device_removed()</b><br>
|
|
When the device node your device belongs to is going to be removed,
|
|
you're notified about this in this function.</li>
|
|
<li><b>open()</b><br>
|
|
Called whenever your device is opened.</li>
|
|
<li><b>close()</b><br>
|
|
</li>
|
|
<li><b>free()</b><br>
|
|
Free the private data structure you allocated in <b>open()</b>.</li>
|
|
<li><b>read()</b><br>
|
|
</li>
|
|
<li><b>write()</b><br>
|
|
</li>
|
|
<li><b>io()</b><br>
|
|
This is a replacement for the <b>read()</b>, and <b>write()</b> calls,
|
|
and allows, among other things, for asynchronous I/O. This functionality
|
|
has not yet been implemented, though (see below).</li>
|
|
<li><b>control()</b><br>
|
|
</li>
|
|
<li><b>select()</b><br>
|
|
</li>
|
|
<li><b>deselect()</b><br>
|
|
</li>
|
|
</ul>
|
|
|
|
|
|
<h2>5. <a name="bus_driver">Writing a Bus Driver</a></h2>
|
|
|
|
<p>A bus driver is a driver that represents a bus where one or more arbitrary
|
|
devices can be attached to.</p>
|
|
|
|
<p>There are two basic types of busses: intelligent busses like PCI or USB that
|
|
know a lot about the devices attached to it, like a generic device type, as
|
|
well as device and vendor ID information, and simple untyped/generic busses that
|
|
either have not all the information (like device type) or don't even know what
|
|
and if any devices are attached. The device manager has been written in such a
|
|
way that device exploration makes use of additional information the bus can
|
|
provide in order to find a responsible device driver faster, and with less
|
|
overhead.</p>
|
|
|
|
<h4>5.1. Writing an Intelligent Bus Driver</h4>
|
|
|
|
<p>If your bus knows what type of device is attached to, and also has vendor and
|
|
device ID information about that device, it is considered to be an intelligent
|
|
bus. The bus driver is supposed to have one parent node representing the bus,
|
|
and to create a child node for each device attached to the bus.</p>
|
|
|
|
<p>The additional information you have about the devices are attached to the
|
|
device node in the following attributes:</p>
|
|
<ul>
|
|
<li><b>B_DEVICE_VENDOR_ID</b><br>
|
|
The vendor ID - this ID has only to be valid in the namespace of your
|
|
bus.</li>
|
|
<li><b>B_DEVICE_ID</b><br>
|
|
The device ID.</li>
|
|
<li><b>B_DEVICE_TYPE</b><br>
|
|
The device type as defined by the PCI class base information.</li>
|
|
<li><b>B_DEVICE_SUB_TYPE</b><br>
|
|
The device sub type as defined by the PCI sub class information.</li>
|
|
<li><b>B_DEVICE_INTERFACE</b><br>
|
|
The device interface type as defined by the PCI class API information.</li>
|
|
</ul>
|
|
|
|
<p>You can use the <b>B_DEVICE_FLAGS</b> attribute to define how the device
|
|
manager finds the children of the devices you exported. For this kind of bus
|
|
drivers, you will usually only want to specify <b>B_FIND_CHILD_ON_DEMAND</b>
|
|
here, which causes the driver only to be searched when the system asks for it.
|
|
</p>
|
|
|
|
<h4>5.2. Writing a Simple Bus Driver</h4>
|
|
|
|
<p>A bus can be simple in a number of ways:</p>
|
|
<ol>
|
|
<li>It may not know how many or if any devices are attached to it</li>
|
|
<li>It cannot retrieve any type information about the devices it has, but
|
|
knows all devices that are attached to it</li>
|
|
</ol>
|
|
|
|
<p>An example of the latter would be the Zorro bus of the Amiga - it only has
|
|
information about the vendor and device ID, but no type information. It should
|
|
be implemented like an intelligent bus, though, with the type information simply
|
|
omitted.</p>
|
|
|
|
<p>Therefore, this section is about the former case, that is, a simple bus like
|
|
the ISA bus. Since it doesn't know anything about its children, it does not
|
|
publish any child nodes, instead, it will just specify the
|
|
B_FIND_MULTIPLE_CHILDREN and B_FIND_CHILD_ON_DEMAND flags for its device node.
|
|
Since there is no additional information about this bus, the device manager
|
|
will assume a simple bus, and will try to find drivers on demand only.</p>
|
|
|
|
<h2>The generic bus</h2>
|
|
|
|
Some devices are not tied to a specific bus. This is the case for all drivers
|
|
that do not relate to a physical device: /dev/null, /dev/zero, /dev/random,
|
|
etc.
|
|
|
|
A "generic" bus has been added, and these drivers can attach to it.
|
|
|
|
<h2>6. Open Issues</h2>
|
|
|
|
While most of the new device manager is fledged out, there are some areas that
|
|
could use improvements or are problematic under certain requirements. Also, some
|
|
parts just haven't been written yet.
|
|
|
|
<h4>6.1. generic/simple busses</h4>
|
|
|
|
<h4>6.2. Unpublishing</h4>
|
|
|
|
<h4>6.4. Versioning</h4>
|
|
|
|
<p>The way the device manager works, it makes versioning of modules (which are
|
|
supposed to be one of the strong points of the module system) much harder or
|
|
even impossible. While the device manager could introduce a new API and could
|
|
translate between a "driver_v1", and a "driver_v2" API on the fly, it's not
|
|
yet possible for a PCI sub module to do the same thing.</p>
|
|
|
|
<p><b>Proposed Solution:</b> Add attribute <b>B_DEVICE_ALTERNATE_VERSION</b>
|
|
that specifies alternate versions of the module API this device node supports.
|
|
We would then need a <b>request_version()</b> or <b>set_version()</b> function
|
|
(to be called from <b>supports_device()</b>) that allows to specify the version
|
|
of the parent node this device node wants to talk to.</p>
|
|
|
|
<h4>6.5. Unregistering Nodes</h4>
|
|
|
|
<h4>6.6. Support for generic drivers is missing</h4>
|
|
|
|
<p>This should probably be done by simply adding a simple bus driver named
|
|
"generic" that generic drivers need to ask for.</p>
|
|
|
|
<h4>6.7. Mappings, And Other Optimizations</h4>
|
|
|
|
<p>Due to the way the device tree is built, the device manager could remember
|
|
which driver served a given device node. That way, it wouldn't need to search
|
|
for a driver anymore, but could just pick it up. Practically, the device manager
|
|
should cache the type (and/or vendor/device) information of a node, and assign
|
|
one or more drivers (via module name) to this information. It should also
|
|
remember negative outcome, that is if there is no driver supporting the
|
|
hardware.</p>
|
|
|
|
<p>This way, only the first boot would require an actual search for drivers, as
|
|
subsequent boots would reuse the type-driver assignments. If a new driver is
|
|
installed, the cached assignments would need to be updated immediately. If a
|
|
driver has been installed outside of the running system, the device manager
|
|
might want to create a hash per module directory to see if anything changed to
|
|
flush the cache. Alternatively or additionally, the boot loader could have a
|
|
menu causing the cache to be ignored.</p>
|
|
|
|
<p>It would be nice to find a way for generic and simple busses to reduce the
|
|
amount of searching necessary for them. One way would be to remember which
|
|
driver supports which bus - but this information is currently only accessible
|
|
derived from what the driver does, and is therefore not reliable or complete.
|
|
A separately exported information would be necessary for this.</p>
|
|
|
|
<p>Also, when looking for a generic or simple bus driver, actual directories
|
|
could be omitted; currently, driver search is always recursive, as that's how
|
|
the module mechanism is working. Eventually, we might want to extend the
|
|
open_module_list_etc() call a bit more to accomplish that.</p>
|
|
|
|
</body>
|
|
</html>
|