前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Linux驱动之PCI子系统剖析

Linux驱动之PCI子系统剖析

作者头像
菜菜cc
发布2022-11-15 21:28:11
3.3K1
发布2022-11-15 21:28:11
举报
文章被收录于专栏:菜菜的技术博客

PCI是外围设备互连(Peripheral Component Interconnect)的简称,作为一种通用的总线接口标准,它已经普遍使用在了计算机中。PCI总线常见于x86体系,本文默认面向的体系为x86,注意x86架构下IO与内存是独立编址的

附: 本文默认读者熟悉Linux设备驱动模型,不熟悉的可以先阅读这两篇blog。 Linux驱动之I2C子系统剖析 Linux驱动之SPI子系统剖析

PCI寻址

PCI系统总体布局组织为树状,从CPU连接的Host Bridge引出PCI主桥,主桥连接的是PCI总线0,可以直接连接PCI设备,或者再挂上PCI桥引出下一级PCI总线。

每个PCI设备由一个总线号设备号功能号确定。PCI规范允许一个系统最多拥有256条总线,每条总线最多带有32个设备,每个设备可以是最多8个功能的多功能板,但是对于大型系统而言总线数不够,故还支持PCI域,每个PCI域可最多支持256个总线。

  • PCI域: 16位
  • 总线号: 8位
  • 设备号: 5位
  • 功能号: 3位

在PC机上可以使用lspci查看计算机上PCI设备信息,笔者在自己电脑上执行该命令后输出如下

每一行表示一个PCI设备或者PCI桥,而每行的开头即表示总线号设备号功能号

PCI配置寄存器

所有的PCI设备都有至少256字节的地址空间,其中前64字节是标准化的,被称为PCI配置寄存器,剩下的字节是设备相关的 (取决于具体的厂商,需要查看datasheet得知)。

PCI配置寄存器如下图所示。

  • Vendor ID: 标识硬件厂商,需要向特定组织进行注册。
  • Device ID: 由硬件厂商来分配的设备ID,无需对ID进行注册。
  • Subsystem IDSubsystem Vendor ID: 用来进一步标识设备。

硬件标识信息在硬件出厂时就写入相应设备中了。

当BIOS启动时,会为每个PCI设备分配内存、IO空间以及irq号,并写入相应PCI设备的配置寄存器中。Linux内核启动时会从PCI设备的配置寄存器里读取内存/IO起始地址以及irq,并把这些信息赋值给struct pci_dev的相应成员来生成软件描述的PCI设备。

从上图的寄存器分布中可以看到中间有一段地址空间描述BARS(Base Address Register),这些寄存器组用来存储备PCI设备工作时的io地址、irq号和mem地址起始地址以及长度。这些信息存储的具体位置需要查阅相应PCI设备的datasheet方可得知,在内核中提供了以下几个接口来获取这些资源。

代码语言:javascript
复制
/*
 * dev为PCI设备的软件抽象,bar的取值为0 ~ 5
 * 这三个函数分别返回第bar个区域的首地址、尾地址和长度
*/
unsigned long pci_resource_start(struct pci_dev *dev, int bar);
unsigned long pci_resource_end(struct pci_dev *dev, int bar);
unsigned long pci_resource_len(struct pci_dev *dev, int bar);
/*
 * 返回和这个bar相关联资源的标识
 * IORESOURCE_IO:io端口
 * IORESOURCE_MEM:内存
*/
unsigned long pci_resource_flags(struct pci_dev *dev, int bar);

内核提供了一组接口来访问配置空间。

代码语言:javascript
复制
int pci_read_config_byte(struct pci_dev *dev, int where, u8 *val)
int pci_read_config_word(struct pci_dev *dev, int where, u16 *val)
int pci_read_config_dword(struct pci_dev *dev, int where, u32 *val)
int pci_write_config_byte(struct pci_dev *dev, int where, u8 val)
int pci_write_config_word(struct pci_dev *dev, int where, u16 val)
int pci_write_config_dword(struct pci_dev *dev, int where,  u32 val)

PCI驱动的注册及匹配

BIOS在启动时,会为每个PCI设备分配地址和irq等信息,并写入各个PCI设备的配置寄存器中,所以PCI设备无需像其他总线那样去注册设备。

内核中使用struct pci_dev来描述PCI设备的抽象。当linux系统启动时,会探测系统中的所有PCI设备,并为探测到的每个PCI设备做如下操作:

1.分配一个struct pci_dev结构体,用来表示相应的PCI设备

2.为这个结构体填充设备vendor id、device id、subvendor id、subdevice id以及地址和irq信息(通过读取PIC配置寄存器得到)

3.最后把这个struct pci_dev结构体挂接到pci_bus

内核中使用struct pci_driver来描述PCI驱动的抽象

代码语言:javascript
复制
struct pci_driver {
    struct list_head node;
    char *name;
    const struct pci_device_id *id_table;   /* must be non-NULL for probe to be called */
    int  (*probe)  (struct pci_dev *dev, const struct pci_device_id *id);   /* New device inserted */
    void (*remove) (struct pci_dev *dev);   /* Device removed (NULL if not a hot-plug capable driver) */
    int  (*suspend) (struct pci_dev *dev, pm_message_t state);  /* Device suspended */
    int  (*suspend_late) (struct pci_dev *dev, pm_message_t state);
    int  (*resume_early) (struct pci_dev *dev);
    int  (*resume) (struct pci_dev *dev);                   /* Device woken up */
    void (*shutdown) (struct pci_dev *dev);
    struct pci_error_handlers *err_handler;
    struct device_driver    driver;
    struct pci_dynids dynids;
};

其中id_table用来匹配设备

代码语言:javascript
复制
struct pci_device_id {
    __u32 vendor, device;       /* Vendor and device ID or PCI_ANY_ID*/
    __u32 subvendor, subdevice; /* Subsystem ID's or PCI_ANY_ID */
    __u32 class, class_mask;    /* (class,subclass,prog-if) triplet */
    kernel_ulong_t driver_data; /* Data private to the driver */
};

PCI驱动的注册接口为pci_register_driver(struct pci_driver *drv),当调用该接口后,会调用PCI总线下的match方法来进行匹配

代码语言:javascript
复制
static int pci_bus_match(struct device *dev, struct device_driver *drv)
{
    struct pci_dev *pci_dev = to_pci_dev(dev);
    struct pci_driver *pci_drv = to_pci_driver(drv);
    const struct pci_device_id *found_id;

    found_id = pci_match_device(pci_drv, pci_dev);
    if (found_id)
        return 1;

    return 0;
}

可以看到pci_bus_match调用的是pci_match_device函数

代码语言:javascript
复制
static const struct pci_device_id *pci_match_device(struct pci_driver *drv,
                            struct pci_dev *dev)
{
    struct pci_dynid *dynid;

    /* Look at the dynamic ids first, before the static ones */
    spin_lock(&drv->dynids.lock);
    list_for_each_entry(dynid, &drv->dynids.list, node) {
        if (pci_match_one_device(&dynid->id, dev)) {
            spin_unlock(&drv->dynids.lock);
            return &dynid->id;
        }
    }
    spin_unlock(&drv->dynids.lock);

    return pci_match_id(drv->id_table, dev);
}

最终调用的是pci_match_id匹配

代码语言:javascript
复制
const struct pci_device_id *pci_match_id(const struct pci_device_id *ids,
                     struct pci_dev *dev)
{
    if (ids) {
        while (ids->vendor || ids->subvendor || ids->class_mask) {
            if (pci_match_one_device(ids, dev))
                return ids;
            ids++;
        }
    }
    return NULL;
}

遍历id_table,调用pci_match_one_device进行严格匹配

代码语言:javascript
复制
static inline const struct pci_device_id *
pci_match_one_device(const struct pci_device_id *id, const struct pci_dev *dev)
{
    if ((id->vendor == PCI_ANY_ID || id->vendor == dev->vendor) &&
        (id->device == PCI_ANY_ID || id->device == dev->device) &&
        (id->subvendor == PCI_ANY_ID || id->subvendor == dev->subsystem_vendor) &&
        (id->subdevice == PCI_ANY_ID || id->subdevice == dev->subsystem_device) &&
        !((id->class ^ dev->class) & id->class_mask))
        return id;
    return NULL;
}

分别对vendor、device、subvendor、subdevice和class进行匹配,除非某一项配置为PCI_ANY_ID,否则都要进行严格匹配,只要有一项匹配不上则直接匹配失败。

本文作者: Ifan Tsai  (菜菜)

本文链接: https://cloud.tencent.com/developer/article/2164590

版权声明: 本文采用 知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议 进行许可。转载请注明出处!

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2019-08-10,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • PCI寻址
  • PCI配置寄存器
  • PCI驱动的注册及匹配
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档