|
|
|
A device driver is a bolt-on module for the Symbian OS Kernel. It lives on the Kernel side, has the same access rights as the Kernel, and also uses the Kernel heap.
User side code accesses a device driver through a specific API provided by the Kernel. This is the class RBusLogicalChannel.
This provides functions that are used to open a channel to a device driver and make synchronous and asynchronous requests. These functions are protected, and the device driver author provides a header file containing a class derived from RBusLogicalChannel that implements functions that are specific to the device driver.
The following diagram shows the location of device driver code.

Device drivers can be very powerful, which means that they can very easily cause the machine to crash, or leak memory, if they are not carefully designed and implemented.
The interface to a device driver is often the same for a number of devices but the implementation at the hardware level may be different. The LDD-PDD model for Symbian OS device drivers reflects this split.
In some situations a device driver does a unique job. In other situations, a device driver performs a generic task which may be the same for a number of different devices.
Where a device driver performs a generic task, it is desirable to make as much code as possible common to all the device driver variants and to use the same user side API. For this reason, a device driver is usually split into two distinct parts:
the logical device driver - the LDD
the physical device driver - the PDD.
The idea behind this is that some types of hardware device may have different register sets (depending on manufacturer and model) but perform the same function. The classic example is a UART - different makes and types of UART have different register layouts but the basic task of transferring data through a UART works the same way for all UARTs.
The main part of the device driver is in the LDD, this is the logical part because it is not hardware specific. The lowest layer that accesses the hardware registers is in a separate PDD. This makes it easy to port to new hardware because only the PDD needs to be changed.
Typically, the PDD is kept as thin as possible.
LDDs should be regarded as a general purpose method of providing user code with access to functions that require Kernel privilege or to provide custom extensions to the Kernel API.
Occasionally, there is no need for this split between the LDD and the PDD; the device driver can then simply consist of an LDD alone. In this case, the LDD still has Kernel side privileges and can access hardware registers if it needs to. In this sense, there is nothing “unphysical” about an LDD.
If an LDD requires a PDD, the Kernel tries to find an appropriate PDD from the list of loaded PDDs. The decision is based on the name of the LDD and PDD. Note that this is not the filename, but the name that the LDD and PDD register with the Kernel. So, for example, an LDD that registers its name with the Kernel as "Foo" can be associated with any PDD that registers as "Foo.*". There are limitations to this LDD-PDD model; an LDD can only use one PDD, it has no choice over which PDD to use, and PDDs cannot be shared by LDDs.
Both LDDs and PDDs are DLLs.
Each DLL contains an exported function at ordinal 1 whose purpose is to create what is called a "factory" object, and return a pointer to it.
For an LDD, the factory object is an instance of a class derived from DLogicalDevice.
The exported function at ordinal 1 is, typically, called CreateLogicalDevice() and has the form:
EXPORT_C DLogicalDevice* CreateLogicalDevice()
{
return new DerivedLogicalDevice;
}
This factory object is created on the Kernel heap. Its purpose is to create the logical channel, an instance of a class derived from DLogicalChannel.
For a PDD, the factory object is an instance of a class derived from DPhysicalDevice.
The exported function at ordinal 1 is, typically, called CreatePhysicalDevice() and has the form
EXPORT_C DPhysicalDevice* CreatePhysicalDevice()
{
return new DerivedPhysicalDevice;
}
This factory object is created on the Kernel heap. Its purpose is to create the physical channel, if needed, an instance of a class derived from CBase.
The file extensions of both the LDD DLL and the PDD DLL can be any permitted Symbian OS name but, by convention, the LDD DLL has extension .LDD and the PDD DLL has extension .PDD.
Device driver DLLs are polymorphic interface DLLs.
When building LDDs and PDDs, specify a target type of ldd and pdd, respectively, in the .mmp file.
The communication between user code and a driver is through a logical channel, represented on the user side by an instance of a RBusLogicalChannel derived class, and on the Kernel side by an instance of a DLogicalChannel derived class, which is constructed by the DLogicalDevice factory object.
An LDD is loaded by calling User::LoadLogicalDevice(). This loads the DLL, and calls the exported function at ordinal 1, which creates the factory object, the DLogicalDevice derived object.
The channel links a user thread with a device and is used by the user side to execute Kernel side device driver functions as the following diagram shows.

Each thread communicates with a device through a logical channel. The following diagram shows how a device is accessed by more than one thread; each thread accesses the device through its own channel.

The DLogicalChannel derived class defines the Kernel side interface to the logical device
In the same way that a logical device is partnered by a logical channel, a physical device is partnered by a CBase derived object. This defines the interface between the logical device and the physical device. Typically, this interface is different for each device family and, therefore, cannot be derived from any class other than CBase. For the sake of clarity, this will be referred to as the physical channel.
A PDD is loaded by calling User::LoadPhysicalDevice(). This loads the DLL, and calls the exported function at ordinal 1, which creates the factory object, the DPhysicalDevice derived object.
The CBase derived class defines the Kernel side interface to the physical device.
The following diagram shows a typical situation:

In addition to implementing the function at ordinal 1 that constructs the LDD factory object, the DLL also contains, as a minimum, the implementation of:
a DLogicalDevice derived class (the LDD factory class)
a DLogicalChannel derived class.
If the device needs power management and this is to be handled by the logical channel, then a DPowerHandler derived class would also be included here.
In addition to implementing the function at ordinal 1 that constructs the PDD factory object, the DLL also contains, as a minimum, the implementation of:
a DPhysicalDevice derived class (the PDD factory class)
a CBase derived class representing the physical channel.
If the device needs power management and this is to be handled by the physical channel, then a DPowerHandler derived class would also be included here.
The user side API consists of a class derived from RBusLogicalChannel.
The derived class has functions which form a "thin layer" over the functions defined in RBusLogicalChannel and are commonly inline and published in a header file. However, if the API functions need to do more complex tasks, then they can be published in their own DLL.
The user side API also includes the class RDevice which enables user side code to fetch information about a device.