# Linux platform device driver and design ![](https://i.imgur.com/8W27aIX.png) > The discussion below is based on the assumption that we are talking about after Linux 2.6 [Platform Devices and Drivers](https://www.kernel.org/doc/html/latest/driver-api/driver-model/platform.html) ## 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`](https://elixir.bootlin.com/linux/latest/source/include/linux/platform_device.h#L23) * [`struct platform_driver`](https://elixir.bootlin.com/linux/latest/source/include/linux/platform_device.h#L206) ```c= 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 ... ```c= 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`](https://elixir.bootlin.com/linux/latest/source/include/linux/device/driver.h#L95) ```c= 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`](https://elixir.bootlin.com/linux/latest/source/drivers/base/platform.c#L1345) function ```c= /** * 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. * Based on device tree style (`of_driver_match_device`): [How devices in device tree and platform drivers were connected](https://stackoverflow.com/questions/38493999/how-devices-in-device-tree-and-platform-drivers-were-connected) * Based on ACPI style * Based on ID table (see if the platform_device name shows up in the platform_driver ID table) * Match pdev->name, drv->name ## Case Study: NIC **DM9000** ### Driver: [`drivers/net/ethernet/davicom/dm9000.c`](https://elixir.bootlin.com/linux/latest/source/drivers/net/ethernet/davicom/dm9000.c#L804) Firstly, let's see the `platform_driver` structure ```c= 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)` ```c= /* * 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`](https://elixir.bootlin.com/linux/latest/source/arch/arm/mach-davinci/board-dm355-evm.c#L102) 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 ```c= 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. ```c= 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 ```c= 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`](https://elixir.bootlin.com/linux/latest/source/arch/arm/boot/dts/s3c6410-mini6410.dts) ```c= 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`](https://elixir.bootlin.com/linux/latest/source/arch/arm/kernel/setup.c#L1170) also: [`[PATCH 5/7] arm-dt: unflatten device tree`](https://lists.infradead.org/pipermail/linux-arm-kernel/2010-February/010115.html) ## input_dev, evdev and its model ![](https://i.imgur.com/mfT5zZc.png) **Case Study: keyboard gpio_keys** ### See [`/drivers/input/keyboard/gpio_keys.c`](https://elixir.bootlin.com/linux/latest/source/drivers/input/keyboard/gpio_keys.c) #### `static int gpio_keys_probe(struct platform_device *pdev)` ```c= 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`](https://elixir.bootlin.com/linux/latest/source/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)` ```c= 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. ```c= * @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`](https://elixir.bootlin.com/linux/latest/source/drivers/input/evdev.c) Here comes the VFS `file_operations` part. **evdev_read** ```c= 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` ```c= 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; } ```