Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What is the difference between a Linux platform driver and normal device driver?

Earlier I had assumed that :

  • Platform driver is for those devices that are on chip.
  • Normal device driver are for those that are interfaced to the processor chip.

Before coming across one i2c driver... But here, I am reading through multi function i2c driver defined as platform driver. I had gone through https://www.kernel.org/doc/Documentation/driver-model/platform.txt. But still could not get clear idea to come to an conclusion on how to define drivers, like for both onchip as well interfaced devices.

Please somebody explain.

like image 672
kzs Avatar asked Mar 25 '13 08:03

kzs


People also ask

What is a platform device driver?

Platform drivers are dedicated to devices not based on conventional buses. I2C devices or SPI devices are platform devices, but respectively rely on I2C or SPI buses not on the platform bus. Everything needs to be done manually with the platform driver.

What are the two types of drivers in Linux?

There are various types of drivers present in GNU/Linux such as Character, Block, Network and USB drivers. In this column, we will explore only character drivers. Character drivers are the most common drivers.

What is device driver in Linux?

The software that handles or manages a hardware controller is known as a device driver. The Linux kernel device drivers are, essentially, a shared library of privileged, memory resident, low level hardware handling routines. It is Linux's device drivers that handle the peculiarities of the devices they are managing.

Are Linux and Windows drivers the same?

Windows device driver architecture is different from the one used in Linux drivers, with either of them having their own pros and cons. Differences are mainly influenced by the fact that Windows is a closed-source OS while Linux is open-source.


2 Answers

Your references are good but lack a definition of what is a platform device. There is one on LWN. What we can learn from this page:

  1. Platform devices are inherently not discoverable, i.e. the hardware cannot say "Hey! I'm present!" to the software. Typical examples are i2c devices, kernel/Documentation/i2c/instantiating-devices states:

    Unlike PCI or USB devices, I2C devices are not enumerated at the hardware level (at run time). Instead, the software must know (at compile time) which devices are connected on each I2C bus segment. So USB and PCI are not platform devices.

  2. Platform devices are bound to drivers by matching names,

  3. Platform devices should be registered very early during system boot. Because they are often critical to the rest of the system (platform) and its drivers.

So basically, the question "is it a platform device or a standard device?" is more a question of which bus it uses. To work with a particular platform device, you have to:

  1. register a platform driver that will manage this device. It should define a unique name,
  2. register your platform device, defining the same name as the driver.

Platform driver is for those devices that are on chip.

Not true (in theory, but true in practice). i2c devices are not onChip, but are platform devices because they are not discoverable. Also we can think of onChip devices which are normal devices. Example: an integrated PCI GPU chip on a modern x86 processor. It is discoverable, thus not a platform device.

Normal device driver are for those that are interfaced to the processor chip. before coming across one i2c driver.

Not true. Many normal devices are interfaced to the processor, but not through an i2c bus. Example: a USB mouse.

[EDIT] In your case, have a look to drivers/usb/host/ohci-pnx4008.c, which is a USB host controller platform device (Here the USB host controller is not discoverable, whereas USB devices, which will connect to it, are). It is a platform device registered by the board file (arch/arm/mach-pnx4008/core.c:pnx4008_init). And within its probe function, it registers its i2c device to the bus with i2c_register_driver. We can infer that the USB Host controller chipset talks to the CPU through an i2c bus.

Why that architecture? Because on one hand, this device can be considered a bare i2c device providing some functionalities to the system. On the other hand, it is a USB Host capable device. It needs to register to the USB stack (usb_create_hcd). So probing only i2c will be insufficient. Have a look to Documentation/i2c/instantiating-devices.

like image 196
m-ric Avatar answered Oct 20 '22 07:10

m-ric


Minimal module code examples

Maybe the difference will also become clearer with some concrete examples.

Platform device example

Code:

  • driver upstream
  • minimal QEMU virtual device driven.
  • DTS entry modifications on Linux kernel

Further integration notes at: https://stackoverflow.com/a/44612957/895245

See how:

  • register and interrupt addresses are hardcoded in the device tree and match the QEMU -M versatilepb machine description, which represents the SoC
  • there is no way to remove the device hardware (since it is part of the SoC)
  • the correct driver is selected by the compatible device tree property which matches platform_driver.name in the driver
  • platform_driver_register is the main register interface
#include <linux/init.h> #include <linux/interrupt.h> #include <linux/io.h> #include <linux/kernel.h> #include <linux/module.h> #include <linux/of_address.h> #include <linux/of_device.h> #include <linux/of_irq.h> #include <linux/platform_device.h>  MODULE_LICENSE("GPL");  static struct resource res; static unsigned int irq; static void __iomem *map;  static irqreturn_t lkmc_irq_handler(int irq, void *dev) {     /* TODO this 34 and not 18 as in the DTS, likely the interrupt controller moves it around.      * Understand precisely. 34 = 18 + 16. */     pr_info("lkmc_irq_handler irq = %d dev = %llx\n", irq, *(unsigned long long *)dev);     /* ACK the IRQ. */     iowrite32(0x9ABCDEF0, map + 4);     return IRQ_HANDLED; }  static int lkmc_platform_device_probe(struct platform_device *pdev) {     int asdf;     struct device *dev = &pdev->dev;     struct device_node *np = dev->of_node;      dev_info(dev, "probe\n");      /* Play with our custom poperty. */     if (of_property_read_u32(np, "lkmc-asdf", &asdf) ) {         dev_err(dev, "of_property_read_u32\n");         return -EINVAL;     }     if (asdf != 0x12345678) {         dev_err(dev, "asdf = %llx\n", (unsigned long long)asdf);         return -EINVAL;     }      /* IRQ. */     irq = irq_of_parse_and_map(dev->of_node, 0);     if (request_irq(irq, lkmc_irq_handler, 0, "lkmc_platform_device", dev) < 0) {         dev_err(dev, "request_irq");         return -EINVAL;     }     dev_info(dev, "irq = %u\n", irq);      /* MMIO. */     if (of_address_to_resource(pdev->dev.of_node, 0, &res)) {         dev_err(dev, "of_address_to_resource");         return -EINVAL;     }     if  (!request_mem_region(res.start, resource_size(&res), "lkmc_platform_device")) {         dev_err(dev, "request_mem_region");         return -EINVAL;     }     map = of_iomap(pdev->dev.of_node, 0);     if (!map) {         dev_err(dev, "of_iomap");         return -EINVAL;     }     dev_info(dev, "res.start = %llx resource_size = %llx\n",             (unsigned long long)res.start, (unsigned long long)resource_size(&res));      /* Test MMIO and IRQ. */     iowrite32(0x12345678, map);      return 0; }  static int lkmc_platform_device_remove(struct platform_device *pdev) {     dev_info(&pdev->dev, "remove\n");     free_irq(irq, &pdev->dev);     iounmap(map);     release_mem_region(res.start, resource_size(&res));     return 0; }  static const struct of_device_id of_lkmc_platform_device_match[] = {     { .compatible = "lkmc_platform_device", },     {}, };  MODULE_DEVICE_TABLE(of, of_lkmc_platform_device_match);  static struct platform_driver lkmc_plaform_driver = {     .probe      = lkmc_platform_device_probe,     .remove     = lkmc_platform_device_remove,     .driver     = {         .name   = "lkmc_platform_device",         .of_match_table = of_lkmc_platform_device_match,         .owner = THIS_MODULE,     }, };  static int lkmc_platform_device_init(void) {     pr_info("lkmc_platform_device_init\n");     return platform_driver_register(&lkmc_plaform_driver); }  static void lkmc_platform_device_exit(void) {     pr_info("lkmc_platform_device_exit\n");     platform_driver_unregister(&lkmc_plaform_driver); }  module_init(lkmc_platform_device_init) module_exit(lkmc_platform_device_exit) 

PCI non-platform device example

  • driver upstream
  • minimal QEMU virtual device driven

See how:

  • register and interrupt addresses are dynamically allocated by the PCI system, no device tree is used
  • the correct driver is selected by the PCI vendor:device ID (QEMU_VENDOR_ID, EDU_DEVICE_ID on example). This is baked into every device, and vendors must ensure uniqueness.
  • we can insert and remove the PCI device with device_add edu and device_del edu as we can in real life. Probing is not automatic, but can be done after boot with echo 1 > /sys/bus/pci/rescan. See also: Why is the probe method needed in Linux device drivers in addition to init?
#include <asm/uaccess.h> #include <linux/cdev.h> #include <linux/fs.h> #include <linux/init.h> #include <linux/interrupt.h> #include <linux/kernel.h> #include <linux/module.h> #include <linux/pci.h>  #define BAR 0 #define CDEV_NAME "lkmc_hw_pci_min" #define EDU_DEVICE_ID 0x11e9 #define QEMU_VENDOR_ID 0x1234  MODULE_LICENSE("GPL");  static struct pci_device_id id_table[] = {     { PCI_DEVICE(QEMU_VENDOR_ID, EDU_DEVICE_ID), },     { 0, } }; MODULE_DEVICE_TABLE(pci, id_table); static int major; static struct pci_dev *pdev; static void __iomem *mmio; static struct file_operations fops = {     .owner   = THIS_MODULE, };  static irqreturn_t irq_handler(int irq, void *dev) {     pr_info("irq_handler irq = %d dev = %d\n", irq, *(int *)dev);     iowrite32(0, mmio + 4);     return IRQ_HANDLED; }  static int probe(struct pci_dev *dev, const struct pci_device_id *id) {     pr_info("probe\n");     major = register_chrdev(0, CDEV_NAME, &fops);     pdev = dev;     if (pci_enable_device(dev) < 0) {         dev_err(&(pdev->dev), "pci_enable_device\n");         goto error;     }     if (pci_request_region(dev, BAR, "myregion0")) {         dev_err(&(pdev->dev), "pci_request_region\n");         goto error;     }     mmio = pci_iomap(pdev, BAR, pci_resource_len(pdev, BAR));     pr_info("dev->irq = %u\n", dev->irq);     if (request_irq(dev->irq, irq_handler, IRQF_SHARED, "pci_irq_handler0", &major) < 0) {         dev_err(&(dev->dev), "request_irq\n");         goto error;     }     iowrite32(0x12345678, mmio);     return 0; error:     return 1; }  static void remove(struct pci_dev *dev) {     pr_info("remove\n");     free_irq(dev->irq, &major);     pci_release_region(dev, BAR);     unregister_chrdev(major, CDEV_NAME); }  static struct pci_driver pci_driver = {     .name     = CDEV_NAME,     .id_table = id_table,     .probe    = probe,     .remove   = remove, };  static int myinit(void) {     if (pci_register_driver(&pci_driver) < 0) {         return 1;     }     return 0; }  static void myexit(void) {     pci_unregister_driver(&pci_driver); }  module_init(myinit); module_exit(myexit);