Linux平台驱动程序和普通设备驱动程序的区别是什么?

时间:2021-09-16 15:16:05

I previously had a thought about the platform driver as well as normal device driver like :

我之前想过平台驱动程序和普通设备驱动程序,比如:

  • Platform driver is for those devices that are on chip. and ,
  • 平台驱动程序适用于芯片上的设备。而且,
  • Normal device driver are for those that are interfaced to the proccesor chip. before coming across one i2c driver.

    通常的设备驱动程序是为那些与proccesor芯片交互的驱动程序。在遇到一个i2c驱动之前。

    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. I went through this link too.. http://meld.org/discussion/general-discussion/platform-driver-vs-ordinary-device-drivers

    但是在这里,我正在阅读定义为平台驱动的多功能i2c驱动程序。我已经通过了https://www.kernel.org/doc/Documentation/driver-model/platform.txt。但对于如何定义驱动程序,仍然没有明确的概念,比如对于单片机和接口设备。我也通过这个链接。http://meld.org/discussion/general-discussion/platform-driver-vs-ordinary-device-drivers

Please somebody explain.

请一个人解释。

2 个解决方案

#1


69  

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:

    平台设备本质上是不可发现的,即硬件不能说“嘿!我在场!典型的例子是i2c设备、内核/文档/i2c/实例化设备状态:

    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.

    与PCI或USB设备不同,I2C设备不在硬件级别(在运行时)枚举。相反,软件必须知道(在编译时)在每个I2C总线段上连接哪些设备。所以USB和PCI不是平台设备。

  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.
  4. 在系统引导过程中,应该尽早注册平台设备。因为它们通常对系统的其他部分(平台)及其驱动程序至关重要。

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. 注册一个平台驱动程序来管理这个设备。它应该定义一个唯一的名字,
  3. register your platform device, defining the same name as the driver.
  4. 注册您的平台设备,定义与驱动程序相同的名称。

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.

不正确(在理论上,但在实践中是正确的)。i2c设备不是芯片设备,而是平台设备,因为它们无法被发现。我们也可以想到芯片设备,这是正常的设备。一个现代的x86处理器集成的PCI GPU芯片。它是可发现的,因此不是一个平台设备。

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

普通设备驱动程序是针对那些与处理器芯片接口的。在遇到一个i2c驱动之前。

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

不正确的。许多普通设备都与处理器交互,但不是通过i2c总线。例子:一个USB鼠标。

[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.

[编辑]在你的情况下,看看驱动程序/usb/主机/ohci-pnx4008。c是一个USB主机控制器平台设备(这里的USB主机控制器无法发现,而要连接到它的USB设备是)。它是一个由董事会文件(arch/arm/mach-pnx4008/core.c:pnx4008_init)注册的平台设备。在它的探测函数中,它用i2c_register_driver向总线注册它的i2c设备。我们可以推断USB主机控制器芯片组通过i2c总线与CPU通信。

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.

为什么架构?因为一方面,这个设备可以被认为是一个为系统提供一些功能的裸i2c设备。另一方面,它是一个USB主机的设备。它需要注册到USB堆栈(usb_create_hcd)。因此,仅仅探测i2c是不够的。查看文档/i2c/实例化设备。

#2


1  

Minimal module code examples

最小的模块代码示例

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

也许,通过一些具体的例子,差异也会变得更清晰。

Platform device example

平台设备的例子

Code:

代码:

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

进一步的集成说明在:https://*.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
  • 寄存器和中断地址在设备树中硬编码,并匹配QEMU -M多用途的lepb机器描述,它表示SoC
  • there is no way to remove the device hardware (since it is part of the SoC)
  • 没有办法删除设备硬件(因为它是SoC的一部分)
  • the correct driver is selected by the compatible device tree property which matches platform_driver.name in the driver
  • 正确的驱动程序由兼容的设备树属性选择,该属性匹配驱动程序中的platform_driver.name
  • platform_driver_register is the main register interface
  • platform_driver_register是主要的寄存器接口
#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

PCI non-platform设备的例子

See how:

看看:

  • register and interrupt addresses are dynamically allocated by the PCI system, no device tree is used
  • PCI系统动态分配寄存器和中断地址,不使用设备树
  • 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.
  • PCI供应商选择正确的驱动程序:设备ID(例如,QEMU_VENDOR_ID, EDU_DEVICE_ID)。这是每个设备都要考虑的,供应商必须确保唯一性。
  • 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?
  • 我们可以使用device_add edu和device_del edu插入和移除PCI设备,就像我们在现实生活中所做的那样。探测不是自动的,但是可以在使用echo 1 > /sys/bus/pci/rescan引导后完成。请参见:为什么在Linux设备驱动程序中除了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);

#1


69  

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:

    平台设备本质上是不可发现的,即硬件不能说“嘿!我在场!典型的例子是i2c设备、内核/文档/i2c/实例化设备状态:

    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.

    与PCI或USB设备不同,I2C设备不在硬件级别(在运行时)枚举。相反,软件必须知道(在编译时)在每个I2C总线段上连接哪些设备。所以USB和PCI不是平台设备。

  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.
  4. 在系统引导过程中,应该尽早注册平台设备。因为它们通常对系统的其他部分(平台)及其驱动程序至关重要。

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. 注册一个平台驱动程序来管理这个设备。它应该定义一个唯一的名字,
  3. register your platform device, defining the same name as the driver.
  4. 注册您的平台设备,定义与驱动程序相同的名称。

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.

不正确(在理论上,但在实践中是正确的)。i2c设备不是芯片设备,而是平台设备,因为它们无法被发现。我们也可以想到芯片设备,这是正常的设备。一个现代的x86处理器集成的PCI GPU芯片。它是可发现的,因此不是一个平台设备。

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

普通设备驱动程序是针对那些与处理器芯片接口的。在遇到一个i2c驱动之前。

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

不正确的。许多普通设备都与处理器交互,但不是通过i2c总线。例子:一个USB鼠标。

[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.

[编辑]在你的情况下,看看驱动程序/usb/主机/ohci-pnx4008。c是一个USB主机控制器平台设备(这里的USB主机控制器无法发现,而要连接到它的USB设备是)。它是一个由董事会文件(arch/arm/mach-pnx4008/core.c:pnx4008_init)注册的平台设备。在它的探测函数中,它用i2c_register_driver向总线注册它的i2c设备。我们可以推断USB主机控制器芯片组通过i2c总线与CPU通信。

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.

为什么架构?因为一方面,这个设备可以被认为是一个为系统提供一些功能的裸i2c设备。另一方面,它是一个USB主机的设备。它需要注册到USB堆栈(usb_create_hcd)。因此,仅仅探测i2c是不够的。查看文档/i2c/实例化设备。

#2


1  

Minimal module code examples

最小的模块代码示例

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

也许,通过一些具体的例子,差异也会变得更清晰。

Platform device example

平台设备的例子

Code:

代码:

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

进一步的集成说明在:https://*.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
  • 寄存器和中断地址在设备树中硬编码,并匹配QEMU -M多用途的lepb机器描述,它表示SoC
  • there is no way to remove the device hardware (since it is part of the SoC)
  • 没有办法删除设备硬件(因为它是SoC的一部分)
  • the correct driver is selected by the compatible device tree property which matches platform_driver.name in the driver
  • 正确的驱动程序由兼容的设备树属性选择,该属性匹配驱动程序中的platform_driver.name
  • platform_driver_register is the main register interface
  • platform_driver_register是主要的寄存器接口
#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

PCI non-platform设备的例子

See how:

看看:

  • register and interrupt addresses are dynamically allocated by the PCI system, no device tree is used
  • PCI系统动态分配寄存器和中断地址,不使用设备树
  • 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.
  • PCI供应商选择正确的驱动程序:设备ID(例如,QEMU_VENDOR_ID, EDU_DEVICE_ID)。这是每个设备都要考虑的,供应商必须确保唯一性。
  • 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?
  • 我们可以使用device_add edu和device_del edu插入和移除PCI设备,就像我们在现实生活中所做的那样。探测不是自动的,但是可以在使用echo 1 > /sys/bus/pci/rescan引导后完成。请参见:为什么在Linux设备驱动程序中除了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);