Linux内核:Pci设备驱动——设备枚举

Pci,是Peripheral Component Interconnect的缩写,翻译成中文即为外部设备互联.与传统的总线相比.它的传输速率较高.能为用户提供动态查询pci deivce.和局部总线信息的方法,此外,它还能自动为总线提供仲裁.在近几年的发展过程中,被广泛应用于多种平台.pci协议比较复杂,关于它的详细说明,请查阅有关pci规范的资料,本文不会重复这些部份.对于驱动工程师来说,Pci设备的枚举是pci设备驱动编写最复杂的操作.分析和理解这部份,是进行深入分析pci设备驱动架构的基础.
摘要由CSDN通过智能技术生成

有了设备模型基础及usb设备驱动的基础知识,来了解PCI设备驱动,就相对简单了,因为PCI设备驱动仍然套用了设备驱动模型的方式,用到的仍然是设备模型的相应函数,只是把相应的pci设备挂载到PCI总线的device队列,而凭此驱动则挂载到pci总线的driver队列,对应的匹配函数,探测函数,都是pci总线提供的函数。

因为pci设备驱动的安装跟usb设备驱动的安装模式相似,因此,其繁杂的地方则是如何发现设备并把设备添加到pci设备队列中去这个步骤了。

一:前言

Pci,是Peripheral Component Interconnect的缩写,翻译成中文即为外部设备互联.与传统的总线相比.它的传输速率较高.能为用户提供动态查询pci deivce.和局部总线信息的方法,此外,它还能自动为总线提供仲裁.在近几年的发展过程中,被广泛应用于多种平台.

pci协议比较复杂,关于它的详细说明,请查阅有关pci规范的资料,本文不会重复这些部份.

对于驱动工程师来说,Pci设备的枚举是pci设备驱动编写最复杂的操作.分析和理解这部份,是进行深入分析pci设备驱动架构的基础.

我们也顺便来研究一下,linux是怎么对这个庞然大物进行封装的.

二:pci架构概貌

上图展现了pci驱动架构中,pci_bus.pci_dev之间的关系.

如上图所示:所有的根总线都链接在pci_root_buses链表中. Pci_bus ->device链表链接着该总线下的所有设备.而pci_bus->children链表链接着它的下层总线.

对于pci_dev来说,pci_dev->bus指向它所属的pci_bus. Pci_dev->bus_list链接在它所属bus的device链表上.此外,所有pci设备都链接在pci_device链表中.

三:pci设备的配置空间

每个pci设备都有最多256个连续的配置空间.配置空间中包含了设备的厂商ID,设备ID,IRQ,设备存储区信息等.摘下LDD3中的一副说明图,如下:

要注意了,上图是以字节为单位的,而不是以位为单位.

那怎么去读取每个设备的配置空间呢?我们在开篇的时候提到过,pci总线为用户提供了动态查询pci设备信息的方法.

在x86上,保留了0xCF8~0xCFF的8个寄存器.实际上就是对应地址为0xCF8的32位寄存器和地址为0xCFC的32位寄存器.

在0xCF8寄存中写入要访问设备对应的总线号, 设备号、功能号和寄存器号组成的一个32位数写入0xCF8.然后从0xCFC上就可以取出对应pci设备的信息.

写入到0xCF8寄存器的格式如下:

低八位(0~7): (寄存器地址)&0xFC.低二位为零

8~10:功能位. 有时候,一个pci设备对应多个功能.将每个功能单元分离出来,对应一个独立的pci device

11~15位:设备号 对应该pci总线上的设备序号

16~23位:总线号 根总线的总线号为0.每遍历到下层总线,总线号+1

31:有效位 如果该位为1.则说明写入的数据有效,否则无效

例如:要读取n总线号m设备号f功能号对应设备的vendor id和Device id.过程如下:

要写入到0xCF8中的数为: l = 0x80<<23 | n<<16 | m<<11 | f<<8 | 0x00

即:outl(l,0xCF8)

从0xCFC中读相关信息:

L = Inw(0xCFC) (从上图中看到,vendor id和device id总共占四个字节.因此用inw)

所以:device id = L&0xFF

Vendor id = (L>>8)&0xFF

四:总线枚举入口分析

Pci的代码分为两个部份.一个部份是与平台相关的部份.存放在linux-2.6.25\arch\XXX\pci.在x86,对应为linux-2.6.25\arch\x86\pci\ 另一个部份是平台无关的代码,存放在linux-2.6.25\driver\pci\下面.

大致浏览一下这两个地方的init函数.发现可能枚举pci设备是由函数pcibios_scan_root()完成的.不过搜索源代码后,发现有两个地方会调用这个调数.一个是在linux-2.6.25\arch\x86\pci\numa.c 另一个是linux-2.6.25\arch\x86\pci\Legacy.c

这两个地方都是封装在一个subsys_initcall()所引用的初始化函数呢? 到底哪一个文件才是我们要分析的呢?

分析一下linux-2.6.25\arch\x86\pci\下的Makefile_32.内容如下:

obj-y                             := i386.o init.o
 
obj-$(CONFIG_PCI_BIOS)                 += pcbios.o
obj-$(CONFIG_PCI_MMCONFIG)     += mmconfig_32.o direct.o mmconfig-shared.o
obj-$(CONFIG_PCI_DIRECT)  += direct.o
 
pci-y                             := fixup.o
pci-$(CONFIG_ACPI)                  += acpi.o
pci-y                             += legacy.o irq.o
 
pci-$(CONFIG_X86_VISWS)              := visws.o fixup.o
pci-$(CONFIG_X86_NUMAQ)            := numa.o irq.o
 
obj-y                             += $(pci-y) common.o early.o

从这个makefile中可以看出:legacy.c是一定会编译到了.而numa.c只有在编译选择了CONFIG_X86_NUMAQ的时候才起效.所以,我们可以毫不犹豫的将眼光放到了legacy.c中.

该文件中的初始化函数如下:

static int __init pci_legacy_init(void)
{
         if (!raw_pci_ops) {
                   printk("PCI: System does not support PCI\n");
                   return 0;
         }
 
         if (pcibios_scanned++)
                   return 0;
 
         printk("PCI: Probing PCI hardware\n");
         pci_root_bus = pcibios_scan_root(0);
         if (pci_root_bus)
                   pci_bus_add_devices(pci_root_bus);
 
         pcibios_fixup_peer_bridges();
 
         return 0;
}

subsys_initcall(pci_legacy_init);

由subsys_initcall()引用的函数都会放在init区域,这里面的函数是kernel启动的时候会自己执行的函数.首先我们碰到的问题是raw_pci_ops是在什么地方被赋值的.搜索整个代码树,发现是在pci_access_init()中初始化的.如下:

static __init int pci_access_init(void)
{
         int type __maybe_unused = 0;
 
#ifdef CONFIG_PCI_DIRECT
         type = pci_direct_probe();
#endif
#ifdef CONFIG_PCI_MMCONFIG
         pci_mmcfg_init(type);
#endif
         if (raw_pci_ops)
                   return 0;
#ifdef CONFIG_PCI_BIOS
         pci_pcbios_init();
#endif
         /*
          * don't check for raw_pci_ops here because we want pcbios as last
          * fallback, yet it's needed to run first to set pcibios_last_bus
          * in case legacy PCI probing is used. otherwise detecting peer busses
          * fails.
          */
#ifdef CONFIG_PCI_DIRECT
         pci_direct_init(type);
#endif
         if (!raw_pci_ops)
                   printk(KERN_ERR
                   "PCI: Fatal: No config space access function found\n");
 
         return 0;
}

arch_initcall(pci_access_init);

由于arch_initcall()的优先级比subsys_initcall要高.因此,会先运行完pci_access_init之后,才会执行pci_legacy_init.

上面的代码看起来很复杂,没关系,去掉几个我们没有用到的编译代码就简单了. 在x86中,bios其实提供了pci设备的枚举功能.这也是CONFIG_PCI_BIOS的作用,如果对它进行了定义,那么就用bios的pci枚举功能.如果没有定义,说明不采用bios的功能,而是自己手动去枚举,这就是CONFIG_PCI_DIRECT的作用.为了一般性,我们分析CONFIG_PCI_DIRECT的过程.把其它不相关的代码略掉.剩余的就简单了.

在pci规范中,定义了两种操作配置空间的方法,即type1 和type2.在新的设计中,type2的配置机制不会被采用,通常会使用type1.因此,在代码中pci_direct_probe()一般会返回1,即使用type1.

pci_direct_init()的代码如下:

void __init pci_direct_init(int type)
{
         if (type == 0)
                   return;
         printk(KERN_INFO "PCI: Using configuration type %d\n", type);
         if (type == 1)
                   raw_pci_ops = &pci_direct_conf1;
         else
                   raw_pci_ops = &pci_direct_conf2;
}
在这里看到,ram_pci_ops最终会指向pci_direct_conf1.顺便看下这个结构:
struct pci_raw_ops pci_direct_conf1 = {
         .read =               pci_conf1_read,
         .write =      pci_conf1_write,
};void __init pci_direct_init(int type)
{
         if (type == 0)
                   return;
         printk(KERN_INFO "PCI: Using configuration type %d\n", type);
         if (type == 1)
                   raw_pci_ops = &pci_direct_conf1;
         else
                   raw_pci_ops = &pci_direct_conf2;
}
在这里看到,ram_pci_ops最终会指向pci_direct_conf1.顺便看下这个结构:
struct pci_raw_ops pci_direct_conf1 = {
         .read =               pci_conf1_read,
         .write =      pci_conf1_write,
};

这个结构其实就是pci设备配置空间操作的接口.

五:pci设备的枚举过程

返回到pci_legacy_init()中

static int __init pci_legacy_init(void)
{
         ……
         printk("PCI: Probing PCI hardware\n");
         pci_root_bus = pcibios_scan_root(0);
         if (pci_root_bus)
                   pci_bus_add_devices(pci_root_bus);
         ……
}

Pci设备的枚举过程是由pcibios_scan_root()完成的.在这里调用是以0为参数.说明是从根总线起开始枚举.

pcibios_scan_root()代码如下:

struct pci_bus * __devinit pcibios_scan_root(int busnum)
{
         struct pci_bus *bus = NULL;
         struct pci_sysdata *sd;
 
         dmi_check_system(pciprobe_dmi_table);
         while ((bus = pci_find_next_bus(bus)) != NULL
  • 1
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值