In computing, a device driver is a computer program that operates or controls a particular type of device that is attached to a computer or automaton. The main purpose of device drivers is to provide abstraction by acting as a translator between a hardware device and the applications or operating systems that use it.
A driver provides a software interface to hardware devices, enabling operating systems and other computer programs to access hardware functions without needing to know precise details about the hardware being used. For example, suppose an application needs to read some data from a device. The application calls a function implemented by the operating system, and the operating system calls a function implemented by the driver.
The driver, which was written by the same company that designed and manufactured the device, knows how to communicate with the device hardware to get the data. After the driver gets the data from the device, it returns the data to the operating system, which returns it to the application.
Programmers can write higher-level application code independently of whatever specific hardware the end-user is using. For example, a high-level application for interacting with a serial port may simply have two functions for “send data” and “receive data”. At a lower level, a device driver implementing these functions would communicate to the particular serial port controller installed on a user’s computer. The commands needed to control a 16550 UART are much different from the commands needed to control an FTDI serial port converter, but each hardware-specific device driver abstracts these details into the same (or similar) software interface.
A driver communicates with the device through the computer bus or communications subsystem to which the hardware connects. When a calling program invokes a routine in the driver, the driver issues commands to the device (drives it). Once the device sends data back to the driver, the driver may invoke routines in the original calling program.
Device Driver Types – Kernel & User Drivers
There are device drivers for almost every device associated with a computer – from BIOS to even virtual machines and more. Device drivers can be broadly be classified into two categories:
- Kernel Device Drivers
- User Device Drivers
Kernel Device Drivers are the generic device drivers that load with the operating system into the memory as part of the operating system; not the entire driver but a pointer to that effect so that the device driver can be invoked as soon as it is required. The drivers are pertaining to BIOS, motherboard, processor, and similar hardware form part of Kernel Software.
A problem with Kernel Device Drivers is that when one of them is invoked, it is loaded into the RAM and cannot be moved to a page file (virtual memory). Thus, a number of device drivers running at the same time can slow down machines. That is why there is a minimum system requirement for each operating system.
User Mode Device Drivers are the ones usually triggered by users during their session on a computer. It might be thought of devices that the user brought to the computer other than the kernel devices. Drivers for most of the Plug and Play devices fall into this category. User Device Drivers can be written to disk so that they don’t act tough on the resources.
The primary benefit of running a driver in user mode is improved stability, since a poorly written user-mode device driver cannot crash the system by overwriting kernel memory. On the other hand, user/kernel-mode transitions usually impose a considerable performance overhead, thus making kernel-mode drivers preferred for low-latency networking.
Kernel space can be accessed by user module only through the use of system calls. End user programs like the UNIX shell or other GUI-based applications are part of user space. These applications interact with hardware through kernel supported functions.
Block Drivers and Character Drivers
These two – the block and character device drivers – belong to the category of data reading and writing. Hard disks, CD ROMs, USB Drives, etc. – might be either Block Drivers or Character Drivers based on how they are used.
Character Drivers are used in serial buses. They write data one character at a time. One character means a byte in a generic sense. If a device is connected to a serial port, it is using a character driver. A mouse is a serial device and has a character device driver.
Block drivers refer to the writing and reading of more than one character at a time. Usually, block device drivers create a block and retrieve as much information as the block can contain. Hard disks, for example, use block device drivers. CD ROMs too, are Block device drivers, but the kernel needs to check that the device is still connected to the computer, each time the CD ROM is invoked by any application.
Device drivers and Operating systems
Drivers are hardware-dependent and operating-system-specific. They usually provide the interrupt handling required for any necessary asynchronous time-dependent hardware interface.
Microsoft has attempted to reduce system instability due to poorly written device drivers by creating a new framework for driver development, called Windows Driver Frameworks (WDF). This includes User-Mode Driver Framework (UMDF) that encourages development of certain types of drivers—primarily those that implement a message-based protocol for communicating with their devices—as user-mode drivers. If such drivers malfunction, they do not cause system instability.
The Kernel-Mode Driver Framework (KMDF) model continues to allow development of kernel-mode device drivers, but attempts to provide standard implementations of functions that are known to cause problems, including cancellation of I/O operations, power management, and plug and play device support.
Apple has an open-source framework for developing drivers on macOS, called I/O Kit.
Linux Device drivers
Linux is primarily divided into User Space & Kernel Space. These two components interact through a System Call Interface – which is a predefined and matured interface to Linux Kernel for Userspace applications. Makedev includes a list of the devices in Linux, including ttyS (terminal), lp (parallel port), hd (disk), loop, and sound (these include mixer, sequencer, dsp, and audio).
In Linux environments, programmers can build device drivers as parts of the kernel, separately as loadable modules, or as user-mode drivers (for certain types of devices where kernel interfaces exist, such as for USB devices). The basic way is to add the code to the kernel source tree and recompile the kernel.
A more efficient way is to do this is by adding code to the kernel while it is running. This process is called loading the module, where the module refers to the code that we want to add to the kernel.
Since we are loading these codes at runtime and they are not part of the official Linux kernel, these are called loadable kernel modules (LKM), which is different from the “base kernel”. The base kernel is located in /boot directory and is always loaded when we boot our machine whereas LKMs are loaded after the base kernel is already loaded. Nonetheless, this LKM is very much part of our kernel and they communicate with the base kernel to complete their functions.
Network Device: A network device is, so far as Linux’s network subsystem is concerned, an entity that sends and receives packets of data. This is normally a physical device such as an ethernet card. Some network devices though are software only such as the loopback device which is used for sending data to yourself.
Microsoft Windows .sys files and Linux .ko files can contain loadable device drivers. The advantage of loadable device drivers is that they can be loaded only when necessary and then unloaded, thus saving kernel memory.
Virtual Device Drivers
Drivers for virtual devices are called Virtual Device Drivers. Often, we use some software to emulate hardware and the software used to run such virtual hardware is a virtual device driver. For example, if you are using a VPN, it may create a virtual network card for connecting securely to the Internet. It is not a real physical card, but one set up by VPN software. Even that card needs a device driver, and the same VPN software will install the virtual device drivers
Writing Embedded system drivers
Within an embedded system, there a typically two types of drivers: microcontroller peripheral drivers and external device drivers that are connected through an interface like I2C, SPI, or UART.
A major advantage to using a microcontroller today is that embedded software developers typically don’t have to write their own drivers anymore. It’s very common for the microcontroller vendor to provide software frameworks that abstract the hardware and allow developers to make simple function calls to initialize, read and write to peripherals such as SPI, UART, analog to digital converters and so on. However, developers still often need to write drivers to interact with external integrated circuits that could be sensors, actuators, motor controllers and so forth.
It’s important to realize that there is more than one way to write a driver and the way that it is written can dramatically affect system performance, energy consumption and many other factors that we like to track as we develop a product.
A key aspect to writing any driver is to separate the implementation from the configuration. The separation helps to ensure that the driver will be reusable and flexible. For example, the driver could easily be compiled into an object file so that a developer can’t see the insides and so it could be used on multiple projects. The developer would still have access to a configuration module that they can use to configure the driver for their specific application needs. If a change is needed to the configuration, it won’t impact the driver design or force other projects using the driver to either be out of sync or forced to accept new changes and go through a validation cycle.
Separating the implementation from the configuration also allows the external hardware to be abstracted so that the developer doesn’t need to fully understand what is happening in the hardware, just like on the microcontroller.
The interface for the driver should contain a simple interface that includes:
- An initialization function
- A write function
- A read function
The device driver should consider potential errors and faults. For example, what happens if the bus goes down? Can the driver time out and provide an error? If a read operation is performed, can the function return whether the read was successful? What if a parity error occurs?
There are several different ways to provide error and fault detection in the driver. First, every function could return an error code. This error code would simply be true if the operation is successful or false if a problem occurred. Second, if a problem did occur, then there could be an addition to the device interface that would allow errors to be checked. I will sometimes include additional operations that:
- Return the driver error state
- Clear the driver error state
Again, this gives flexibility and fault detection capabilities to the driver and will allow the application code to carefully monitor whether driver operations were successful or not, writes ELE Times Bureau.
Types of Drivers:
The Polled Driver: The first technique, and the most fundamental, is to develop a driver that polls the peripheral (or external device) to see if it is ready to either send or receive information. Polling drivers are very easy to implement since they typically do nothing more than poll a flag. For example, an analog to digital converter (ADC) driver might start a conversion sequence and then simply block processor execution and constantly check the ADC complete flag.
Interrupt-Driven Drivers: Using interrupts in a driver is fantastic because it can dramatically improve the codes execution efficiency. Instead of checking constantly for whether it’s time to do something, an interrupt tells the processor that the driver is now ready and we jump to handle the interrupt. In general, we have two types of interrupt-driven driver mechanisms we can use: event driven and scheduled. An event-driven driver will fire an interrupt when an event occurs in the peripheral that needs to be handled. For example, we may have a UART driver who will fire an interrupt when a new character has been received in the buffer. On the other hand, we might have an ADC driver that uses a timer to schedule access for starting sampling or processing received data.
Using an interrupt-driven driver, while more efficient, can add additional implementation complexity to the design. First, the developer needs to enable the appropriate interrupts for use in the driver such as receive, transmit and buffer full.
DMA Driven Drivers: There are some drivers that move a large amount of data through the system such as I2S and SDIO. Managing the buffers on these types of interfaces can require constant action from the CPU. If the CPU falls behind or has to handle another system event, data could be missed or delayed, which can cause noticeable issues to the user such as an audio skip.
The advantage to using DMA is that the CPU can be off doing other things while the DMA channel is moving data around for the driver, essentially get two things done at one. DMA drivers are the most efficient implementation for a driver, most microcontrollers have a limited number of DMA channels available.
The definition of the driver can be expanded to include any software component that observes or participates in the communication between the operating system and a device. Because some drivers are not associated with any hardware device at all.
For example, suppose you need to write a tool that has access to core operating system data structures, which can be accessed only by code running in kernel mode. You can do that by splitting the tool into two components. The first component runs in user mode and presents the user interface. The second component runs in kernel mode and has access to the core operating system data. The component that runs in user mode is called an application, and the component that runs in kernel mode is called a software driver. A software driver is not associated with a hardware device.
Software drivers always run in kernel mode. The main reason for writing a software driver is to gain access to protected data that is available only in kernel mode. However device drivers do not always need access to kernel-mode data and resources. So some device drivers run in user mode.
References and Resources also include: