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).

Device trees

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:

net@10010000 {
  compatible = "smsc,lan91c111";
  reg = <0x10010000 0x10000>;
  interrupts = <25>;
};

Platform data

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:

  • IORESOURCE_MEM: The physical address of a region of memory
  • IORESOURCE_IO: The physical address or port number of IO registers
  • IORESOURCE_IRQ: The interrupt number

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:

#define VERSATILE_ETH_BASE     0x10010000
#define IRQ_ETH                25
static struct resource smc91x_resources[] = {
  [0] = {
    .start          = VERSATILE_ETH_BASE,
    .end            = VERSATILE_ETH_BASE + SZ_64K - 1,
    .flags          = IORESOURCE_MEM,
  },
  [1] = {
    .start          = IRQ_ETH,
    .end            = IRQ_ETH,
    .flags          = IORESOURCE_IRQ,
  },
};
static struct platform_device smc91x_device = {
  .name           = "smc91x",
  .id             = 0,
  .num_resources  = ARRAY_SIZE(smc91x_resources),
  .resource       = smc91x_resources,
};

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:

void __init versatile_init(void)
{
  platform_device_register(&versatile_flash_device);
  platform_device_register(&versatile_i2c_device);
  platform_device_register(&smc91x_device);
  [ ...]

Linking hardware with device drivers

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:

static const struct of_device_id smc91x_match[] = {
  { .compatible = "smsc,lan91c94", },
  { .compatible = "smsc,lan91c111", },
  {},
};
MODULE_DEVICE_TABLE(of, smc91x_match);
static struct platform_driver smc_driver = {
  .probe          = smc_drv_probe,
  .remove         = smc_drv_remove,
  .driver         = {
    .name   = "smc91x",
    .of_match_table = of_match_ptr(smc91x_match),
  },
};
static int __init smc_driver_init(void)
{
  return platform_driver_register(&smc_driver);
}
static void __exit smc_driver_exit(void) 
{
  platform_driver_unregister(&smc_driver);
}
module_init(smc_driver_init);
module_exit(smc_driver_exit);

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:

static int smc_drv_probe(struct platform_device *pdev)
{
  struct smc91x_platdata *pd = dev_get_platdata(&pdev->dev);
  const struct of_device_id *match = NULL;
  struct resource *res, *ires;
  int irq;

  res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
  ires = platform_get_resource(pdev, IORESOURCE_IRQ, 0);
  [...]
  addr = ioremap(res->start, SMC_IO_EXTENT);
  irq = ires->start;
  [...]
}

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:

match = of_match_device(of_match_ptr(smc91x_match), &pdev->dev);
if (match) {
  struct device_node *np = pdev->dev.of_node;
  u32 val;
  [...]
  of_property_read_u32(np, "reg-io-width", &val);
  [...]
}

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.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.147.27.131