Try   HackMD

Linux platform device driver and design

Image Not Showing Possible Reasons
  • The image file may be corrupted
  • The server hosting the image is unavailable
  • The image path is incorrect
  • The image format is not supported
Learn More →

The discussion below is based on the assumption that we are talking about after Linux 2.6

Platform Devices and Drivers

platform bus line, device and driver

In the model of device driver, there are three instances we have to take care of:

  • Bus line

  • Device

  • Driver

    The bus line always bind devices with drivers. Everytime we register a device in the system, the system search a driver matching with the device; Plus, everytime we register a driver, system searches the mathcing deivce through bus line

    Normally, a real linux device and a driver hung on a bus line: PCI, USB, I2C, SPI
    However, in an embedded system, SoC integrates: independent peripheral controllers
    The devices hung in the SoC memory space are not covered

    Therefore, Linux comes up with a virtual bus line called platform bus line, the structure of device and driver come up correspondly.

struct platform_device { const char *name; int id; bool id_auto; struct device dev; u64 platform_dma_mask; struct device_dma_parameters dma_parms; u32 num_resources; struct resource *resource; const struct platform_device_id *id_entry; char *driver_override; /* Driver name to force a match */ /* MFD cell pointer */ struct mfd_cell *mfd_cell; /* arch specific additions */ struct pdev_archdata archdata; };
  • name: platform_match would use name to find a match driver.
  • id: the instance of the device. Platform device IDs are assumed to be encoded like this: "<name><instance>", where <name> is a short description of the type of device, like "pci" or "floppy", and <instance> is the enumerated instance of the device, like '0' or '42'. eg. pci42
  • resource: the resouce of the device, eg. IO space base address, regiester base address, irq number
struct platform_driver { int (*probe)(struct platform_device *); int (*remove)(struct platform_device *); void (*shutdown)(struct platform_device *); int (*suspend)(struct platform_device *, pm_message_t state); int (*resume)(struct platform_device *); struct device_driver driver; const struct platform_device_id *id_table; bool prevent_deferred_probe; };
  • probe: triggered when a device is installed

  • remove: triggered when a device is removed

  • suspend and resume: use to do the power management but are out of date. Now, people use struct dev_pm_ops in the struct device_driver driver

  • struct device_driver

struct device_driver { const char *name; struct bus_type *bus; struct module *owner; const char *mod_name; /* used for built-in modules */ bool suppress_bind_attrs; /* disables bind/unbind via sysfs */ enum probe_type probe_type; const struct of_device_id *of_match_table; const struct acpi_device_id *acpi_match_table; int (*probe) (struct device *dev); void (*sync_state)(struct device *dev); int (*remove) (struct device *dev); void (*shutdown) (struct device *dev); int (*suspend) (struct device *dev, pm_message_t state); int (*resume) (struct device *dev); const struct attribute_group **groups; const struct attribute_group **dev_groups; const struct dev_pm_ops *pm; void (*coredump) (struct device *dev); struct driver_private *p; };

The struct device_driver is included not only in platform_driver, but also i2c_driver spi_drvier usb_driver pci_driver (platform_driver has the same level to i2c_driver spi_driver )

See how matching goes from the platform_match function

/** * platform_match - bind platform device to platform driver. * @dev: device. * @drv: driver. * * Platform device IDs are assumed to be encoded like this: * "<name><instance>", where <name> is a short description of the type of * device, like "pci" or "floppy", and <instance> is the enumerated * instance of the device, like '0' or '42'. Driver IDs are simply * "<name>". So, extract the <name> from the platform_device structure, * and compare it against the name of the driver. Return whether they match * or not. */ static int platform_match(struct device *dev, struct device_driver *drv) { struct platform_device *pdev = to_platform_device(dev); struct platform_driver *pdrv = to_platform_driver(drv); /* When driver_override is set, only bind to the matching driver */ if (pdev->driver_override) return !strcmp(pdev->driver_override, drv->name); /* Attempt an OF style match first */ if (of_driver_match_device(dev, drv)) return 1; /* Then try ACPI style match */ if (acpi_driver_match_device(dev, drv)) return 1; /* Then try to match against the id table */ if (pdrv->id_table) return platform_match_id(pdrv->id_table, pdev) != NULL; /* fall-back to driver name match */ return (strcmp(pdev->name, drv->name) == 0); }

We can see that there are 4 possiblilities to find a match.

Case Study: NIC DM9000

Driver: drivers/net/ethernet/davicom/dm9000.c

Firstly, let's see the platform_driver structure

static struct platform_driver dm9000_driver = { .driver = { .name = "dm9000", .pm = &dm9000_drv_pm_ops, .of_match_table = of_match_ptr(dm9000_of_matches), }, .probe = dm9000_probe, .remove = dm9000_drv_remove, };

Here we can see that the name of the driver is dm9000 and of_match_table is specified. These are the ways that this driver can match with corresponded platform_device through the function platform_match.

pm stands for power management
prob and remove are specified.

  • dm9000_probe(struct platform_device *pdev)
/* * Search DM9000 board, allocate space and register it */ static int dm9000_probe(struct platform_device *pdev) { struct dm9000_plat_data *pdata = dev_get_platdata(&pdev->dev); struct board_info *db; /* Point a board information structure */ struct net_device *ndev; struct device *dev = &pdev->dev; const unsigned char *mac_src; int ret = 0; int iosize; int i; u32 id_val; int reset_gpios; enum of_gpio_flags flags; struct regulator *power; bool inv_mac_addr = false; ... db->addr_res = platform_get_resource(pdev, IORESOURCE_MEM, 0); db->data_res = platform_get_resource(pdev, IORESOURCE_MEM, 1); if (!db->addr_res || !db->data_res) { dev_err(db->dev, "insufficient resources addr=%p data=%p\n", db->addr_res, db->data_res); ret = -ENOENT; goto out; } ndev->irq = platform_get_irq(pdev, 0); if (ndev->irq < 0) { ret = ndev->irq; goto out; } ... db->io_addr = ioremap(db->addr_res->start, iosize); ... iosize = resource_size(db->data_res); db->data_req = request_mem_region(db->data_res->start, iosize, pdev->name); if (db->data_req == NULL) { dev_err(db->dev, "cannot claim data reg area\n"); ret = -EIO; goto out; } db->io_data = ioremap(db->data_res->start, iosize); ... /* try multiple times, DM9000 sometimes gets the read wrong */ for (i = 0; i < 8; i++) { id_val = ior(db, DM9000_VIDL); id_val |= (u32)ior(db, DM9000_VIDH) << 8; id_val |= (u32)ior(db, DM9000_PIDL) << 16; id_val |= (u32)ior(db, DM9000_PIDH) << 24; if (id_val == DM9000_ID) break; dev_err(db->dev, "read wrong id 0x%08x\n", id_val); } ... }

How do platform_get_resource platform_get_irq work?
Ans: by extruct struct resource *resource from the platform_device.
After that we can access the io space by APIs like ioremap

Device: arch/arm/mach-davinci/board-dm355-evm.c

Before we start, The naming rule of a board is like: /arch/<arch>/mach<soc>/<board name>.c
So, next time if we want to add a description of a borad, that is how we do.

The first thing we can take a look is the platform_device structure

static struct platform_device dm355evm_dm9000 = { .name = "dm9000", .id = -1, .resource = dm355evm_dm9000_rsrc, .num_resources = ARRAY_SIZE(dm355evm_dm9000_rsrc), .dev = { .platform_data = &dm335evm_dm9000_platdata, }, };

Here we can see that the name is dm9000 this would help system to find the match driver we regiter above.

The platform_data is a self-defined data, for dm9000 this stores MAC address, Bus line width, is there EEPROM on the board , which driver can get this through dev_get_platdata (see dm9000_probe above)

Then we can see how is resource dm355evm_dm9000_rsrc defined.

static struct resource dm355evm_dm9000_rsrc[] = { { /* addr */ .start = 0x04014000, .end = 0x04014001, .flags = IORESOURCE_MEM, }, { /* data */ .start = 0x04014002, .end = 0x04014003, .flags = IORESOURCE_MEM, }, { .flags = IORESOURCE_IRQ | IORESOURCE_IRQ_HIGHEDGE /* rising (active high) */, }, };

Here we got three resouces the lastes one is arising (active high) IRQ.

And the

static __init void dm355_evm_init(void) { ... gpio_request(1, "dm9000"); gpio_direction_input(1); dm355evm_dm9000_rsrc[2].start = gpio_to_irq(1); ... platform_add_devices(davinci_evm_devices, ARRAY_SIZE(davinci_evm_devices)); ... gpio_request(2, "usb_id_toggle"); gpio_direction_output(2, USB_ID_VALUE); /* irlml6401 switches over 1A in under 8 msec */ davinci_setup_usb(1000, 8); ... }

handle the gpio irq and assign to resource

So when dm9000_probe calls:

db->addr_res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
db->addr_res->start; // will be `0x04014000`

Device tree

The case above is not the only way to enumerated a dm9000 driver.
After Linux 3.x dm9000 driver can be enumerated through the device tree.

So, to add a dm9000 card on a board can be easily done by revising the dts file.

For example: /arch/arm/boot/dts/s3c6410-mini6410.dts

srom-cs1-bus@18000000 { compatible = "simple-bus"; #address-cells = <1>; #size-cells = <1>; reg = <0x18000000 0x8000000>; ranges; ethernet@18000000 { compatible = "davicom,dm9000"; reg = <0x18000000 0x2 0x18000004 0x2>; interrupt-parent = <&gpn>; interrupts = <7 IRQ_TYPE_LEVEL_HIGH>; davicom,no-eeprom; }; };

And the device tree would be parsed here: /arch/arm/kernel/setup.c
also: [PATCH 5/7] arm-dt: unflatten device tree

input_dev, evdev and its model

Case Study: keyboard gpio_keys

See /drivers/input/keyboard/gpio_keys.c

static int gpio_keys_probe(struct platform_device *pdev)

static int gpio_keys_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; const struct gpio_keys_platform_data *pdata = dev_get_platdata(dev); struct fwnode_handle *child = NULL; struct gpio_keys_drvdata *ddata; struct input_dev *input; ... input = devm_input_allocate_device(dev); ... input->name = pdata->name ? : pdev->name; input->phys = "gpio-keys/input0"; input->dev.parent = dev; input->open = gpio_keys_open; input->close = gpio_keys_close; input->id.bustype = BUS_HOST; input->id.vendor = 0x0001; input->id.product = 0x0001; input->id.version = 0x0100; input->keycode = ddata->keymap; input->keycodesize = sizeof(ddata->keymap[0]); input->keycodemax = pdata->nbuttons; ... error = input_register_device(input); ... device_init_wakeup(dev, wakeup); return 0; }

see /drivers/input/input.c

  • struct input_dev *devm_input_allocate_device(struct device *dev): allocate managed input device, @dev: device owning the input device being created
  • struct input_dev *devm_input_allocate_device(struct device *dev): allocate managed input device, @dev is a device owning the input device being created

static irqreturn_t gpio_keys_irq_isr(int irq, void *dev_id)

static irqreturn_t gpio_keys_irq_isr(int irq, void *dev_id) { struct gpio_button_data *bdata = dev_id; struct input_dev *input = bdata->input; unsigned long flags; BUG_ON(irq != bdata->irq); spin_lock_irqsave(&bdata->lock, flags); if (!bdata->key_pressed) { ... input_event(input, EV_KEY, *bdata->code, 1); input_sync(input); ... } if (bdata->release_delay) hrtimer_start(&bdata->release_timer, ms_to_ktime(bdata->release_delay), HRTIMER_MODE_REL_HARD); out: spin_unlock_irqrestore(&bdata->lock, flags); return IRQ_HANDLED; }

When we see the ISR we can find that it uses input_event and input_sync API to sync the data and event.
And file_operations and IO operations are not involved in this.
In fact, the Linux VFS interface related part is in drives/input/evdev.c

  • void input_event(struct input_dev *dev, unsigned int type, unsigned int code, int value): report new input event.
* @dev: device that generated the event * @type: type of the event * @code: event code * @value: value of the event
  • input_sync is equal to input_event(dev, EV_SYN, SYN_REPORT, 0);

if we go deeper we can found that it call handler in the dev

/drivers/input/evdev.c

Here comes the VFS file_operations part.

evdev_read

static ssize_t evdev_read(struct file *file, char __user *buffer, size_t count, loff_t *ppos) { struct evdev_client *client = file->private_data; struct evdev *evdev = client->evdev; struct input_event event; size_t read = 0; int error; if (count != 0 && count < input_event_size()) return -EINVAL; for (;;) { if (!evdev->exist || client->revoked) return -ENODEV; if (client->packet_head == client->tail && (file->f_flags & O_NONBLOCK)) return -EAGAIN; /* * count == 0 is special - no IO is done but we check * for error conditions (see above). */ if (count == 0) break; while (read + input_event_size() <= count && evdev_fetch_next_event(client, &event)) { if (input_event_to_user(buffer + read, &event)) return -EFAULT; read += input_event_size(); } if (read) break; if (!(file->f_flags & O_NONBLOCK)) { error = wait_event_interruptible(client->wait, client->packet_head != client->tail || !evdev->exist || client->revoked); if (error) return error; } } return read; }

struct evdev *evdev = client->evdev; gets the evdev from file's private data.

evdev_fetch_next_event: get the buffer from evdev_client

static int evdev_fetch_next_event(struct evdev_client *client, struct input_event *event) { int have_event; spin_lock_irq(&client->buffer_lock); have_event = client->packet_head != client->tail; if (have_event) { *event = client->buffer[client->tail++]; client->tail &= client->bufsize - 1; } spin_unlock_irq(&client->buffer_lock); return have_event; }