Discovering hardware configuration

The dummy driver demonstrates the structure of a device driver, but it lacks interaction with real hardware since it only manipulates memory structures. Device drivers are usually written to interact with hardware and part of that is being able to discover the hardware in the first place, bearing in mind that it may be at different addresses in different configurations.

In some cases, the hardware provides the information itself. Devices on a discoverable bus such as PCI or USB have a query mode which returns resource requirements and a unique identifier. The kernel matches the identifier and possibly other characteristics with the device drivers, and marries them up.

However, most of the hardware blocks on an SoC do not have such identifiers. You have to provide the information yourself in the form of a device tree or as C structures known as platform data.

In the standard driver model for Linux, device drivers register themselves with the appropriate subsystem: PCI, USB, open firmware (device tree), platform device, and so on. The registration includes an identifier and a callback function called a probe function that is called if there is a match between the ID of the hardware and the ID of the driver. For PCI and USB, the ID is based on the vendor and the product IDs of the devices, for device tree and platform devices, it is a name (an ASCII string).

I gave you an introduction to device trees in Chapter 3, All About Bootloaders. Here, I want to show you how the Linux device drivers hook up with that information.

As an example, I will use the ARM Versatile board, arch/arm/boot/dts/versatile-ab.dts, for which the Ethernet adapter is defined here:

In the absence of device tree support, there is a fallback method of describing hardware using C structures, known as platform data.

Each piece of hardware is described by struct platform_device, which has a name and a pointer to an array of resources. The type of the resource is determined by flags, which include the following:

Here is an example of the platform data for an Ethernet controller taken from arch/arm/mach-versatile/core.c, which has been edited for clarity:

It has a memory area of 64 KiB and an interrupt. The platform data has to be registered with the kernel, usually when the board is initialized:

You have seen in the preceding section how an Ethernet adapter is described using a device tree and using platform data. The corresponding driver code is in drivers/net/ethernet/smsc/smc91x.c and it works with both the device tree and platform data. Here is the initialization code, once again edited for clarity:

When the driver is initialized, it calls platform_driver_register(), pointing to struct platform_driver, in which there is a callback to a probe function, a driver name, smc91x, and a pointer to struct of_device_id.

If this driver has been configured by the device tree, the kernel will look for a match between the compatible property in the device tree node and the string pointed to by the compatible structure element. For each match, it calls the probe function.

On the other hand, if it was configured through platform data, the probe function will be called for each match on the string pointed to by driver.name.

The probe function extracts information about the interface:

The calls to platform_get_resource() extract the memory and irq information from either the device tree or the platform data. It is up to the driver to map the memory and install the interrupt handler. The third parameter, which is zero in both of the previous cases, comes into play if there is more than one resource of that particular type.

Device trees allow you to configure more than just basic memory ranges and interrupts, however. There is a section of code in the probe function that extracts optional parameters from the device tree. In this snippet, it gets the register-io-width property:

For most drivers, specific bindings are documented in Documentation/devicetree/bindings. For this particular driver, the information is in Documentation/devicetree/bindings/net/smsc911x.txt.

The main thing to remember here is that drivers should register a probe function and enough information for the kernel to call the probe as it finds matches with the hardware it knows about. The linkage between the hardware described by the device tree and the device driver is through the compatible property. The linkage between platform data and a driver is through the name.