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

后端 未结 2 1941
闹比i
闹比i 2020-11-28 02:07

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 proc
2条回答
  •  抹茶落季
    2020-11-28 02:25

    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 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    
    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 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    
    #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);
    

提交回复
热议问题