= Writing Device Drivers for HelenOS = == What is a device driver? == The hardware an OS runs on can be modelled as a hierarchy of (peripheral) interconnects (buses) to which devices are attached. Each bus provides connectivity to devices or other buses. This hiearchy can be often represented as a tree (or directed acyclic graph), where inner (nexus) nodes correspond to buses and leaf nodes correspond to devices. Needless to say this is just a model, meaning nothing is given ''a priori'', the model can look differently depending on our choice. It is often not clear what is a device and what is not, where are the boundaries of a particular device, etc. Consequently, there is also no clear line between what still is a device driver and what is not. Usually devices (or their drivers) have one or more of the following traits. When a system is coming up, it attempts to transitively discover all buses and devices connected to it. For each bus or device it selects the appropriate driver. The driver takes (complete) control of the bus or device and makes its services available to the system (to other drivers in case of a bus, to applications in case of a device). The driver abstracts away the details of a particular device model and provides an interface common to a class of devices (e.g. Ethernet adapter). The system often also virtualizes the device, providing concurrent access to multiple clients, but this can be done at a higher level, rather than in the driver itself. == Overview of Drivers in HelenOS == Device drivers in HelenOS come in two flavors, ''plain drivers'' and ''DDF drivers''. Plain drivers originated before the Device Driver Framework (DDF) was created. They are simple servers which are started manually (from command line or from ''init'' task) and they reside in {{{/srv}}}. DDF drivers make use of the Device Driver Framework which takes care of starting them, attaching them to devices, etc. They reside in {{{/drv}}}. Both kinds of drivers export their services to clients in the same way, as services registered with the ''Location Service''. Each service has a unique name and it can be added to one or more categories. For a client it does not matter how a service is implemented, whether in a plain driver or in a DDF driver. This is an important design point. DDF drivers are most useful for drivers that reside on busses that support discovery and hotplug (PCI, USB). Plain drivers are useful for implementing pseudo-devices. ''file_bd'' is an example of a plain driver. It implements a file-backed block device and it is started from the command line. == DDF (Device Driver Framework) == The Device Driver Framework (DDF) implements common functionality which is useful for most device drivers. It manages the device topology graph (device tree), coordinates enumeration, automatically starts drivers and allows communication between drivers of parent and child devices. DDF imposes certain structure of the driver and defines calls and entry points by which DDF and the driver communicate. Internally DDF consists of a server, the Device Manager (''devman'') and the Device Driver Library (''libdrv''). Every driver is linked against ''libdrv'', which internally communicates with the Device Manager via IPC -- this is hidden from the driver. Device Manager holds information about drivers and device topology and coordinates operation of the drivers. There is an administration tool associated with the Device Manager, ''devctl''. ''devctl'' can be used to control operation of the Device Manager. It can display the device tree, offline and online devices (i.e. perform anticipated unplug operations). === Device Graph === ==== Devices and Functions ==== The traditional view of the device graph, which models the system topology, is a tree which has a single type of node (device node). There are two interested parties to each device node, the bus driver (parent) and the device driver (child). Each views the same node from a different perspective. From the point of view of the bus driver, the node identifies a device on the bus that the bus driver can provide access to, or, more broadly, a service/function provided by the bus driver. From the point of view of the child/device driver, the same node represents a device that the driver is handling (that usually corresponds to what is considered an ''instance'' of the driver). The device driver accesses the device via the parent (bus) driver and identifies the device using the device node. In HelenOS DDF this traditional model is slightly modified. We split the single type of node -- having two roles -- into two node types, ''device node'' and ''function node''. Having done that, each driver consumes device nodes and provides function nodes. From the driver perspective, these are represented by distinct C types, ''ddf_dev_t'' and ''ddf_fun_t''. ==== Inner and Exposed Functions ==== There is no formal distinction between a bus (nexus) driver and a leaf (device) driver. All drivers consume device nodes and produce function nodes. There are two type of functions (function nodes), ''inner functions'' and ''exposed functions''. Inner functions are functions internal to the device graph and DDF will attempt to attach child devices/drivers under these functions. Exposed functions are meant to be consumed by clients external to the DDF (such as applications or non-DDF servers) and the DDF will expose them as services via the Location Service. == DDF Driver Structure == When HelenOS runs the driver is an executable stored in {{{/drv}}} (e.g. {{{/drv/foo}}}). The driver is normally started automatically by the Device Manager. When the driver is needed, the Device Manager will start it and the driver connects to the Device Manager. In HelenOS source tree drivers are located under {{{uspace/drv}}}. To add a new driver ''foo'', you need to: * create a directory {{{uspace/drv/a/b/foo}}} * create a makefile {{{uspace/drv/a/b/foo/Makefile}}} * create at least one source file {{{uspace/drv/a/b/foo/foo.c}}} * add directory {{{uspace/drv/a/b/foo}}} to the DIRS definition in {{{uspace/Makefile}}} You can use an existing driver as a starting point, for example {{{uspace/drv/test/test1}}}. A driver is a C program similar to a HelenOS server or application. It is linked with {{{libdrv}}} (and the makefile should add libdrv's include path to the header search paths). An example driver makefile (with license stripped): {{{ USPACE_PREFIX = ../../.. LIBS = $(LIBDRV_PREFIX)/libdrv.a EXTRA_CFLAGS += -I$(LIBDRV_PREFIX)/include BINARY = foo SOURCES = \ foo.c include $(USPACE_PREFIX)/Makefile.common }}} The most basic DDF interfaces are defined in the header {{{ddf/driver.h}}}. * #include * driver_t * driver_ops_t * add_device (rename to dev_add) * dev_remove * dev_gone * fun_online * fun_offline * ddf_dev_ops_t == Device and Function Life Cycle == * ddf_fun_create() * ddf_fun_destroy() * ddf_fun_add_match_id() * ddf_fun_add_to_category() * ddf_fun_bind() * ddf_fun_unbind() * ddf_fun_online() * ddf_fun_offline() == Soft State Management == A driver can associate driver-specific data with its devices, functions or both. The framework provides a way to allocate a block of memory (soft state) associated with a device or function and allows the driver to obtain pointer to this memory block. The framework frees the soft state automatically (and in such a way that it does not happen while the driver is still accessing it). The driver is expected to synchronize access to this soft-state structure on its own in order to achieve mutual exclusion. Since the framework ensures that soft-state is not deallocated while the driver is accessing it, the synchronization can be as simple as a mutex embedded in the structure. The soft-state management functions are: * void *ddf_dev_data_alloc(ddf_dev_t *dev, size_t size) - allocate driver-specific device data * void *ddf_fun_data_alloc(ddf_fun_t *fun, size_t size) - allocate driver-specific function data The function ''ddf_dev_data_alloc'' allocates a driver-specific data strutcure ''size'' bytes large, associated with the device ''dev''. The structure will not be deallocated by the framework until the ''dev_remove'' or ''dev_gone'' entry point returns '''and''' control exits all driver entry points that are invoked with ''dev'' as a parameter. (Internally, this is achieved using reference counting). The function ''ddf_fun_data_alloc'' allocates a driver-specific data structure ''size'' bytes large, associated with the function ''fun''. The structure will not be deallocated by the framework until ''ddf_fun_destroy()'' is called '''and''' control exits all driver entry points that are invoked with ''fun'' as parameter. 'ddf_dev_data_alloc'' is normally called from within the ''add_device'' emtry point. In practice, as long as you don't copy the pointer to device or function soft-state to a global/heap structure or pass it to another thread, it can never become dangling. As long as you have the reference, it is valid (i.e. points to an allocated block). ''ddf_fun_data_alloc'' is called on an unbound function (after creating, before binding). == Exposing Driver Services to Clients == All driver services are provided to clients (applications) via the location service. For each bound function node the device framework creates a service whose name is based on the physical path of that function. The services are created in the ''devices'' namespace. This is mapped to the {{{/loc/devices}}} directory via ''locfs''. For example: {{{ /# ls /loc/devices \hw\pci0\00:01.0\com1\a \hw\pci0\00:01.0\ctl \hw\pci0\ctl \virt\lo\port0 }}} Often the driver will want to add the function service to one or more service categories. Service categories imply the protocol spoken by a service (and possibly also the intended use). This can be done using the DDF function: {{{ int ddf_fun_add_to_category(ddf_fun_t *fun, const char *cat_name) }}} For example, the input server automatically picks up and opens any device in the categories ''keyboard'' and ''mouse''. == Traditional I/O Device Drivers == === Programmed I/O === === Interrupts === === DMA === == USB Device Drivers ==