Generic Driver Model using hardware abstraction and standard APIs
Abstract
In most cases, device drivers are written with both higher level logic and hardware specific code within the same functions. This means that each time a given physical device is used on multiple boards or platforms, the device driver code has to be re-written specific to the environment in which it is being used.
This paper describes a unique approach for developing drivers using hardware abstraction and standard APIs for hardware and software interfaces. Having standard interfaces encourages design methodology which enables development of common software for controlling and managing physical devices. The operating system and platform specific driver interface software can be separated from device specific software. This promotes sharing and reuse of software IP. For example, common device driver code can be used across multiple platforms where the same physical device is being used.
At Cisco Systems, a team of engineers developed such an approach called the Device Object Model and developed software APIs based on hardware abstraction of common functions and interfaces. This approach simplifies porting, integration and testing of device driver software across different platforms and OS environments. This model has been used on several Cisco platforms. The cost of software development and full life cycle support has been significantly reduced (nearly 30%) due to the adoption of this model.
Generic Driver Model
A generic driver for a card using the Device Object Model can be divided into four components:
1. Interface driver
2. Plugin driver
3. Interface-device mapper
4. Device drivers
The interface driver implements the hardware API and network interface related support. For example, in the case of Packet Over SONET (POS) interface related support will include Automatic Protection Switching (APS), command line interface (CLI), etc. The interface driver architecture is structured in such a way that it is independent of platform and device specifics. For example an Ethernet interface driver API may be shared among all Ethernet card drivers across all platforms. To achieve this, each interface driver specifies a function table that must be provided for this interface type. Within the function table, the function vectors should be grouped by usage such as command handling, event reporting and interface attribute retrieval. There is one interface driver structure for each interface type.
The plugin driver abstracts a card or the platform. The goal is to add a new card type without touching generic code - that is, platform support code and generic OS code should not contain any card specific knowledge. It provides a set of function vectors which are called by the on-line insertion & removal (OIR) state machines to analyze, initialize, start, stop, and remove a card. It also provides function vectors to handle card specific interrupts, and to provide a management interface.
The interface-device mapper acts as glue between the interface driver and the device drivers. It should be a small card specific layer that adapts the API of the interface driver to the API of one or more device drivers.
The device drivers implement software to control devices on the card. Framers, Ethernet PHY chips, pluggable optics (SFPs) and FPGAs are examples of devices. Each device driver controls a single type of device. The device driver utilizes a device object data structure which defines a set of standard APIs. Each device driver must provide valid functions for each standard API. Optionally, a driver may provide a device specific extended API through device specific call-in and call-out vectors. Each device is controlled by a separate instance of the device driver object. There will be one instance of a device driver object for each device on the card.
Figure 1: Generic Driver Model
Figure 2: Generic Driver Model
Figure 2 shows various layers of abstraction for the Generic Driver Model. The bottom layer includes hardware devices or components, such as ASICs, vendor ICs, e.g. Ethernet PHY, HDLC controller, T3/E3 or SONET framer etc. The low level software which communicates to these physical devices is called the device specific code. This component of the software can be made completely independent of operating system and the platform software. If a particular physical device is used on other platform or card, then this component of the driver software can be completely re-used. The component on top of this layer is called plugin driver, which is platform specific. There is only one plugin driver required for a card and it supports multiple device drivers, one for each physical device type. The top layer is called interface layer and it is operating system specific. It provides CLI, management related functions and also handles communication to various processes using IPC mechanism.
Device Driver Architecture
The device driver architecture described here divides the driver into an OS/platform dependent layer and an OS/platform independent layer. Standard APIs defined the interface between these layers. This driver architecture is referred to as the “Device Object Model”.
Figure 3: Device Driver Architecture
OS/Platform Dependent Layer
The OS/Platform dependent layer code is responsible for providing the platform and OS specific interface to the driver. This layer is fully integrated with the host platform and OS and as such it has full access to any services provided by the OS. This layer must be replicated on each OS which supports this driver. Separate platforms running the same OS may be able to reuse significant portions if not all of this layer. The upper layer system interfaces with the low level device through this layer.
OS/Platform Independent “Dev Object” Layer
The OS/Platform independent layer (also called the “Dev Object” layer) code is responsible for implementing low level hardware access routines used to read and write device registers, collect statistics, maintain control structures and so on. At this layer, a limited set of data types and APIs are used. Keeping this layer relatively lightweight makes it easier to reuse code at this layer across multiple platforms and operating systems. Note that although this layer is shown as logically separate from the OS, it will in fact be compiled into the OS which runs on the host platform.
Drivers which adopt this model are structured such that the majority of the code resides in the OS/Platform independent layer. This approach maximizes the amount of code that can be reused.
The low level “Dev Object” layer can be further sub-divided into three components:
1. Base Device Objects
2. Common Device Class Extensions
3. Device Specific Extensions
Figure 4 shows the components which make up a device driver using Device Object Model approach. The arrows in the diagram show the APIs between various layers.
Figure 4: Device Object Components
Base Device Object
The base device object defines standard high level functions which apply to all devices. Some examples of these functions are device: creation, destruction, initialization, interrupt enabling and disabling, interrupt handler, generic information display, etc. Functions at this level which do not apply to a given device can be attached to a “do nothing” function.
Since this base object is present and standardized for every device, it can be invoked from common code which is entirely independent and unaware of the specific device type. A typical example of where this would be used is in Online Insertion and Removal (OIR) state machine handling code.
The base object also defines a common base device data structure that contains standard debugging flags and other fields which are standardized across all devices.
Device Classes
Since the base Device Object defines only very high level APIs (attach, init, etc.) to generically control any type of device, those APIs do not implement any device type specific functionality. For this task, it is useful to define a ‘device class’ to specify the API for controlling a particular type of device.
Common device class extensions may be defined for devices which implement a particular application. Examples of common device classes can include applications such as: an ATM SAR, an Ethernet MAC, a SONET/SDH framer, etc. The device class defines an interface (APIs, control structures, etc.) used to configure, collect statistics, and monitor a given type of device. The device class typically provides a superset of functionality required for supporting any particular device of a given type. In other words, it is not limited to operating any particular vendor’s implementation of the device.
Defining a device class is not required, but is useful in that it defines a consistent interface to the upper layer code that controls a given type of device. It also defines the essential set of functions which must be supported by the low level driver for the given application. For example, an ATM SAR device class may define APIs for tasks such as setting up and tearing down ATM virtual circuits. Another benefit of the use of common device classes is that the upper layer code which controls them need not be aware of which specific device it happens to be controlling.
Device classes extend, rather than replace, device functionality beyond that of the base Device Object. The base object continues to be used for high level functions such as device creation, destruction, and initialization and so on. Those APIs are generic to any device and as such do not provide access to more granular device functionality.
A well designed of device class provide an interface that is reusable on any vendor’s implementation of a given device type. Since the device class is defined within Device Object scope, it allows that interface to be fully reused on other operating systems and hardware platforms.
Device Specific Extensions
Device specific extensions define an interface for invoking functions specific to a particular device or vendor part. For example, one vendor’s Ethernet PHY chip may maintain a set of proprietary counters beyond those defined in the common class. These vendor or device specific functions can be addressed in this class. Use of device specific extensions is generally minimized since this approach is the least reusable. These extensions do make sense for functions which are not applicable to the common device class.
OS/Driver Isolation
In order for device drivers to be portable across multiple operating systems, a restricted set of data types and APIs are used. Keeping this interface lightweight minimizes the burden of supporting this model across multiple operating systems. Data types are limited to well known POSIX types. These can be used to form whatever complex data structures may be required within the context of each driver.
A well defined set of APIs are provided to drivers using this model to provide access to essential OS provided functionality such as malloc, free, printf and so on. Any OS that will support drivers based on this model must implement this set of APIs. These APIs allow the low level drivers to access OS provided services while remaining completely independent of the particular OS within which the driver happens to be running.
Device Object Scope
To maintain modularity and portability requirements, Device Object based drivers are restricted to use of data types and APIs only defined within “Dev Object Scope”. These drivers must never access native OS provided data types or APIs outside the scope of those defined by Device Object Model header files.
From the perspective of a Dev Object based driver, it is completely isolated from the operating system in which it is running. It is structured such that it is independent of the underlying OS. This allows Dev Object based drivers to be compiled into any operating system that supports the Device Object Model.
Applicability of the Model
This model is well suited for device drivers for hardware devices (ASICs, FPGAs, etc) that will be reused across multiple platforms or operating systems. For example, if a given SONET/SDH framer chip is used on several different boards, using this model allows the same code to be compiled into the target specific driver for each board. In cases where the same board is supported in multiple operating systems, use of this model again allows the low level device driver to be shared among operating systems. Highly complex ASICs require significant software drivers to control them. Using this model in such cases has the benefit of keeping those large drivers entirely platform and operating system independent.
Figure 6: Physical Device Reuse
Another common use of this model is to enable device driver testing in a simulation environment before hardware has been completed. Since the same driver runs in both the simulation environment and in the target platform environment, it allows more thorough testing of the device drivers prior to board bringup. Generally, the driver can be converted for use in a simulation environment by simply redefining hardware access read/write macros.
This model is also useful for sharing device driver code between diagnostics and shippable application code. By sharing code between those environments, significant duplication of effort can be avoided. This approach also gives more complete coverage of code testing before the software is ready to go to market.
Conclusion
The use generic driver model has provided a modular and well structured approach to device driver developers and has resulted in increase reuse of software and hardware IP across multiple development teams. The key benefits realized are improvement in time to market (TTM) of new products and reduction of product development and software lifecycle support cost.
On the other hand, new changes in organization structure, processes and tools are required to develop and adopt standard based generic driver model across multiple product development teams. Some key challenges are: 1) need to develop common infrastructure and tools to facilitate collaboration and sharing of common device driver code development among multiple design teams, 2) need a central engineering team to support updates, enhancements, bug fixing, and testing and certifying common device driver code and finally 3) defining common objects and interfaces with well thought through APIs to increase their potential reuse across multiple platforms and product lines.