# Linux platform device driver and design

> 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

**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;
}
```