linux PCI设备驱动

  • 1.PCI 简介

    • 1.1 PCI 引脚
      pci-pins

    为处理数据、寻址、接口控制、仲裁以及系统功能,PC接口作为目标设备的设备至少有47条引脚。作为总线主设备的设备至少有49条引脚。必要的引脚在左边,任选的引脚在右边

    • 1.2 传输速率

    Transparent upgrade from 32-bit data path at 33 MHz
    (132 MB/s peak) to 64-bit data path at 33 MHz
    (264 MB/s peak) and from 32-bit data path at 66 MHz
    (264 MB/s peak) to 64-bit data path at 66 MHz
    (528 MB/s peak).

    • 1.3 PCI 的配置空间
      所有的PCI都设备必须实现PCI协议规定必需的配置寄存器,以便系统上电时利用这些寄存器的信息来进行系统配置。
      配置空间头区域及功能:
    1. 设备识别
    2. 设备控制
    3. 设备状态
    4. 基址寄存器。系统初始化代码在引导操作系统之前,必须建立一个统一的地址映射关系,以确定系统中有多少存储器和IO控制器,它们需要占用多少地址空间,当确定这些信息后,系统初始化代码便可以吧IO控制器映射到合理的地址空间并引导系统。为了做到这种映射与相应设备无关,在配置空间头区域中安排了一组供映射时使用的基址寄存器。
      pci配置空间头区域及功能
      基地址设置过程:
      PCI基地址设置过程
      查看pci 配置寄存器内容:
      # hexdump /sys/bus/pci/devices/xxx/xxx/config
      0000000 8086 2e30 0006 2090 0003 0600 0000 0000
      0000010 0000 0000 0000 0000 0000 0000 0000 0000
      0000020 0000 0000 0000 0000 0000 0000 1458 5000
      0000030 0000 0000 00e0 0000 0000 0000 0000 0000
      0000040 9001 fed1 0000 0000 4001 fed1 0000 0000
      0000050 0000 0350 0009 0000 0000 0000 0000 0000
      0000060 0005 e000 0000 0000 8001 fed1 0000 0000
      0000070 0000 0000 0000 0000 0001 0000 0000 0000
      0000080 0000 0000 0000 0000 0000 0000 0000 0000
      0000090 1110 0011 0000 0000 03ff 0000 0a00 0079
      00000a0 0020 0800 0000 7e00 0000 7de0 0000 7dd0
      00000b0 8000 0000 0000 0000 0000 0000 0000 0000
      00000c0 0000 0000 0000 0000 0000 0000 0000 0000
      00000d0 0000 0000 0303 0000 6660 1366 aa55 aa55
      00000e0 0009 610c 0b24 cfa7 9de2 812f 0000 0000
      00000f0 0000 0000 0000 0000 0fa6 0005 0000 0000
      0000100 0000 0000 0000 0000 0000 0000 0000 0000
      *
      0001000
      
  • 2.PCI 驱动

    • 2.1 注册一个PCI设备驱动
      为了被正确注册到内核, 所有的 PCI 驱动必须创建的主结构是 struct pci_driver 结构.
      这个结构包含许多函数回调和变量, 来描述 PCI 驱动给 PCI 核心. 这里是这个结构的一
      个 PCI 驱动需要知道的成员:
    struct pci_driver{
    const char *name;
          /*驱动的名子. 它必须是唯一的, 在内核中所有 PCI 驱动里面. 通常被设置为和驱动模块名子相同的名子. 
          它显示在 sysfs 中在 /sys/bus/pci/drivers/ 下.*/
    const struct pci_device_id *id_table;
          /*指向 struct pci_device_id 表的指针,用于macth函数*/
    int (*probe) (struct pci_dev *dev, const struct pci_device_id *id);
          /*指向 PCI 驱动中 probe 函数的指针*/
    void (*remove) (struct pci_dev *dev);
          /*指向 PCI 核心在 struct pci_dev 被从系统中去除时调用的函数的指针*/     
          ...
    } 
    

    总之, 为创建一个正确的 struct pci_driver 结构, 只有 4 个字段需要被初始化,一个PCI设备驱动的骨架如下:

    static int probe(struct pci_dev *dev, const struct pci_device_id *id)
    {
        //...
    }
    static void remove(struct pci_dev *dev)
    {
        //...     
    }
    static struct pci_driver pci_driver = {
    .name = "pci_skel",
    .id_table = ids,
    .probe = probe,
    .remove = remove,
    };
    static int __init pci_skel_init(void)
    {
       return pci_register_driver(&pci_driver);
    }
    static void __exit pci_skel_exit(void)
    {
       pci_unregister_driver(&pci_driver);
    }
    module_init(pci_skel_init);
    module_exit(pci_skel_exit);
    
    • 2.2 probe 函数
      pci_driver 的 probe()函数要完成 PCI 设备的初始化及其设备本身身份(字符、TTY、
      网络等)驱动的注册。
    //pci_driver probe()函数 的骨架
    int probe(struct pci_dev *dev, const struct pci_device_id *id)
    {
        if (pci_enable_device(pdev))
        {
             return -EIO;
     	}
     	iobase = pci_resource_start(pdev, 0); //获取io基地址 ,0 ~5 根据具体设备来定
     	...
     	request_region(iobase , size, "name"); // 申请io空间,声明这块地址空间已经被占用
     	...
     	dev_init();   // 注册字符设备,网络设备等,以及注册中断
    }
    
    • 2.3 常用的接口
     申请/释放 I/O 或内存资源。
    int pci_request_regions(struct pci_dev *pdev, const char *res_name);
    void pci_release_regions(struct pci_dev *pdev);
    获取/设置驱动私有数据。
    void *pci_get_drvdata(struct pci_dev *pdev);
    void pci_set_drvdata(struct pci_dev *pdev, void *data);
    使能/禁止 PCI 设备。
    int pci_enable_device(struct pci_dev *dev);
    void pci_disable_device(struct pci_dev *dev);
    设置为总线主 DMA。
    void pci_set_master(struct pci_dev *dev);
    寻找指定总线指定槽位的 PCI 设备。
    struct pci_dev *pci_find_slot(unsigned int bus, unsigned int devfn);
    设置 PCI 能量管理状态(0=D0 ... 3=D3)。
    int pci_set_power_state(struct pci_dev *dev, pci_power_t state);
    在设备的能力表中找出指定的能力。
    int pci_find_capability(struct pci_dev *dev, int cap);
    启用设备内存写无效事务。
    int pci_set_mwi(struct pci_dev *dev);
    禁用设备内存写无效事务。
    void pci_clear_mwi(struct pci_dev *dev); 
    
  • 3.小结
    就跟USB、I2C、SPI、UART等常用接口一样,PCI 也是一种通信接口,其最终的目的是实现数据交换,如果一个SPI接口的外设模块要接在PCI接口上实现数据通信,那么中间需要加一个转换芯片,如TIGER320.
    PCI 设备的启动分为两个部分,第一部分是系统引导时,读取配置寄存器信息(也就是常说的的配置空间)并确认好基址映射地址回写到PCI设备的基址寄存器。第二部分是系统启动后加载驱动,探测到该设备后,进行的初始化操作,如启用PCI设备,申请IO空间,具体设备注册,注册中断等。

参考资料:
1.《微机原理与接口技术》[刘立康]
2.《linux设备驱动程序》

发布了63 篇原创文章 · 获赞 20 · 访问量 7万+

猜你喜欢

转载自blog.csdn.net/agave7/article/details/85613857