Chinaunix首页 | 论坛 | 博客
  • 博客访问: 1056350
  • 博文数量: 573
  • 博客积分: 0
  • 博客等级: 民兵
  • 技术积分: 66
  • 用 户 组: 普通用户
  • 注册时间: 2016-06-28 16:21
文章分类

全部博文(573)

文章存档

2018年(3)

2016年(48)

2015年(522)

分类: LINUX

2016-04-12 15:04:06

PCI小结

几个基本的问题:

1)  注册函数的问题:因为编写的PCI设备驱动,是为某一具体设备服务,而这一具体设备,虽为PCI设备,但其本质仍可划分为,字符设备,网络设备等。因此在驱动程序初始化的时候,仍应该调用相应类型设备的驱动注册函数,对驱动进行注册,如,register_chrdev().register_sound_mixer,register_sound_midi,register_netdev等。一定要区别pci_register_driver()函数。

参考:《嵌入式设计及linux驱动开发指南》及中国linux论谈-linux高级应用-linux内核技术版中的文章《PCI Device Driver编写》

2)  关于pci_register_driver()的问题:原以为这是一个注册PCI设备的函数,但实际上,这是一个用于探测设备的函数。以下为引用一文章中关于这一问题的描述:


探测设备
顾名思义探测设备就是要找到驱动需要控制的设备随着内核的不断升级目前主要有两种风格的设备探测方式两种方式的区别在于老的风格是手工探测而新的方式则把探测的任务交给系统中的PCI层由它来自动完成这样一来可以支持设备的热插拔因此非常的方便和灵活一般在内核中开发驱动程序建议使用新的也就是自动探测方式如果出于向后兼容的需要则仍然需要采用老的手工方式进行探测下面分别对这两种方式进行介绍
(1) 新的探测方式:在驱动初始化代码中通过调用内核提供的一个函数
pci_register_driver完成探测任务该函数的原型如下: 
int pci_register_driver(struct pci_driver *)
struct pci_driver结构是个关键通过填写该结构的成员我们通知了PCI层需要被探测的设备的相关信息该结构包含如下成员:
name:描述该驱动程序的名字
id_table:指向设备驱动程序感兴趣的设备ID的一个列表
Probe:指向一个函数对于每一个与id_table中的项匹配的且未被其他驱动程序处理的设备在执行pci_register_driver时候调用此函数或者如果是以后插入的一个新设备的话,只要满足上述条件也调用此函数
Remove: 指向一个函数当该驱动程序卸载或者被该驱动程序管理的设备被卸下的时候将调用此函数
Save_state:用于在设备被挂起之前保存设备的相关状态
Suspend:挂起设备使之处于节能状态
Resume:唤醒处于挂起态的设备
enable_wake:使设备能够从挂起态产生唤醒事件
(2) 老的探测设备方式相对来说这种方式已经应用比较广泛了主要是利用
pcibios_xxx类型的函数来实现手工探测可以利用厂商id和设备id,或者设备分类代码,或者是厂商id设备id系统厂商id子系统设备id四者联合这三种方式进行探测相应的函数分别使用
pci_find_device(VENDOR_ID, DEVICE_ID, dev)
pci_find_class(CLASS_ID, dev)
pci_find_subsys(VENDOR_ID, DEVICE_ID, SUBSYS_VENDOR_ID, SUBSYS_DEVICE_ID, dev)


可见,只是两种探测方法。在《嵌入式设计及linux驱动开发指南》中的PCI例程中,使用的是第二种手动调用pci_find_device(VENDOR_ID, DEVICE_ID, dev)方法探测设备,而在多数网路文章中关于pci设备驱动开发模板中多是调用第一种nt pci_register_driver(struct
pci_driver *)方法进行。


3)  PCI I/O 资源

引用网路文章。
在 Linux 2.4 中,PCI 设备的 I/O 区域已经集成到了一般的资源管理。出于该原因,我们无需访问配置变量来获得设备内存和 I/O 空间的映射情况。获得区域信息的首选接口,由如下函数组成: 
unsigned long pci_resource_start(struct pci_dev *dev, int bar); 
该函数返回六个 PCI I/O 区域之一的第一个地址(内存地址或 I/O 端口编号)。该区域由整数的 bar(base address register,基地址寄存器)决定,可取 0 到 5 的值。
unsigned long pci_resource_end(struct pci_dev *dev, int bar); 
该函数返回第 bar 个 I/O 区域的后一个地址。注意这是最后一个可用的地址,而不是该区域之后的第一个地址。 
unsigned long pci_resource_flags(struct pci_dev *dev, int bar); 
该函数返回资源关联的标志。 
资源标志用来定义单个资源的某些特性。对与 PCI I/O 区域关联的 PCI 资源,该信息从基地址寄存器中获得,但对其它与 PCI 设备无关的资源,它可能来自任何地方。 
所有的资源标志定义在 中,下面列出其中最重要的几个: 
IORESOURCE_IO 
IORESOURCE_MEM 
如果对应的 I/O 区域存在,将设置上面标志中的一个,而且只有一个。 
IORESOURCE_PREFETCH 
IORESOURCE_READONLY 
上述标志定义内存区域是可预取的,或者是写保护的。对 PCI 资源来讲,从来不会设置后面的那个标志。 
通过使用 pci_resource_ 函数,设备驱动程序可完全忽略底层的 PCI 寄存器,因为系统已经使用这些寄存器构建了资源信息。

基地址寄存器 
通过避免对 PCI 寄存器的访问,我们可以获得更好的硬件抽象,以及向前的兼容性,但却不能获得向后兼容性。如果希望自己的设备驱动程序能够在 2.4 之前的内核上工作,就不能使用上述这些漂亮的资源接口,而必须直接访问 PCI 寄存器。 
在这个小节,我们将讨论基地址寄存器的工作方式,以及访问这些寄存器的方法。如果读者可以直接利用先前讲述过的资源管理函数,这里的内容则有点多余。
我们不会在这里详细地介绍基地址寄存器,这是因为,如果我们正准备编写一个 PCI 驱动程序,则无论如何都会有设备的硬件手册可作参考。尤其是,我们不会使用寄存器的可预取位,以及两个“类型”位,而且我们的讨论会限制在 32 位外设上。但是,这些东西的实现方法,以及 Linux 驱动程序处理 PCI 内存的方法,仍然值得一看。 
PCI 规范指出,制造商必须将每个有效区域映射到可配置地址。这意味着,设备必须为每个区域装备一个可编程的 32 位地址解码器,利用了 64 位 PCI 扩展的任何 PCI 板,都应该有 64 位的可编程解码器。 
实际的实现以及可编程解码器的使用,因如下事实而简化:一个区域中的字节数,通常是 2 的幂,例如 32 字节、4 KB 或者 2MB 等等。另外,也无需考虑将区域映射到不对齐地址的情况,1MB 的区域会对齐在 1MB 倍数的地址处,而 32 字节的区域会在 32 的倍数处对齐。PCI 规范利用了这种对齐,它指出地址解码器只需看到地址总线上的高位,而且只有高位是可编程的。这种约定也意味着任意区域的大小都必须是 2 的幂。 
将一个 PCI 区域映射到物理地址空间的工作,通过在配置寄存器高位中设置适当的值而实现。例如,对 1 MB 区域,它有 20 位的地址空间,通过设置寄存器的高 12 位来进行重新映射,这样,为了让 PCI 板具有 64 MB 到 65MB 的地址范围,可向寄存器写入 0x040xxxxx 范围的任意一个地址。实际情况下,只有很高的地址才用来映射 PCI 区域。 
“部分解码”技术还有一个附加的有点,软件可以检查配置寄存器中的非可编程位数来判断 PCI 区域的大小。为此,PCI 标准指出不用的位必须在读取时始终为 0。标准还要求 I/O 区域的最小大小是 8 字节,而内存区域的最小范围是 16 字节,这样,就可以在基地址寄存器的低位中保存一些额外的信息:

位 0 位“空间(space)”位。如果区域映射到内存地址空间,则设置为 0;如果映射到 I/O 地址空间,则设置为 1。 
位 1 和 2 是“类型(type)”位:内存区域可标记为 32 位区域、64 位区域或者“必须映射到 1 MB 以下的 32 位区域”(已废弃的 x86 特有类型,现在已不再使用)。 
位 3 是“可预取(prefetchable)”位,用于内存区域。

读到这里,读者应该知道资源标志的来源了。

检测 PCI 区域的大小,可利用 中定义的若干位掩码来简化:如果是内存区域,则 PCI_BASE_ADDRESS_SPACE 位掩码被设置为 PCI_BASE_ADDRESS_SPACE_MEMORY,是 I/O 区域时,设置为 PCI_BASE_ADDRESS_IO。若要知道映射后内存区域的实际地址,可将 PCI 寄存器和 PCI_BASE_ADDRESS_MEM_MASK 进行“与”操作,以便丢弃前面那些低位值。对 I/O 寄存器,应使用 PCI_BASE_ADDRESS_IO_MASK。需要注意的是,设备制造商可能以任意顺序使用 PCI 区域。使用第一个和第三个区域,却留下第二个区域不用的设备,还是很常见的。

下面将给出报告 PCI 区域当前位置和大小的典型代码。这段代码是 pciregions 模块的一个部分,和 pcidata 在同一目录中发布。该模块建立一个 /proc/pciregions 文件,并使用先前给出的代码生成数据。该程序将一个全 1 的值写入配置寄存器,然后读取这些值,以便了解寄存器中哪些位可被编程。注意当这个程序探测配置寄存器时,设备实际被映射到了物理地址空间的顶端,这就是探测过程中禁止中断报告的原因(这样可在将区域映射到错误的地点时,避免驱动程序访问该区域)。

尽管 PCI 规范指出 I/O 地址空间是 32 位宽,但某些制造商明显倾向于 x86 平台,假设它为 64 KB,而不会实现基地址寄存器的所有 32 位。这就是下面的代码(以及内核)忽略 I/O 区域地址掩码高位的原因。

static u32 addresses[] = { 
   PCI_BASE_ADDRESS_0, 
   PCI_BASE_ADDRESS_1, 
   PCI_BASE_ADDRESS_2, 
   PCI_BASE_ADDRESS_3, 
   PCI_BASE_ADDRESS_4, 
   PCI_BASE_ADDRESS_5, 
   0 
};

int pciregions_read_proc(char *buf, char **start, off_t offset, 
                  int len, int *eof, void *data) 

   /* this macro helps in keeping the following lines short */ 
#define PRINTF(fmt, args...) sprintf(buf+len, fmt, ## args) 
   len=0;

   /* Loop through the devices (code not printed in the book) */

       /* Print the address regions of this device */ 
       for (i=0; addresses[ i]; i++) { 
           u32 curr, mask, size; 
           char *type;

           pci_read_config_dword(dev, addresses[ i],&curr); 
           cli(); 
           pci_write_config_dword(dev, addresses[ i],~0); 
           pci_read_config_dword(dev, addresses[ i],&mask); 
           pci_write_config_dword(dev, addresses[ i],curr); 
           sti();

           if (!mask) 
               continue; /* there may be other regions */

           /* 
            * apply the I/O or memory mask to current position. 
            * note that I/O is limited to 0xffff, and 64-bit is not 
            * supported by this simple implementation 
            */ 
           if (curr & PCI_BASE_ADDRESS_SPACE_IO) { 
               curr &= PCI_BASE_ADDRESS_IO_MASK; 
           } else { 
               curr &= PCI_BASE_ADDRESS_MEM_MASK; 
           }

           len += PRINTF("\tregion %i: mask 0x%08lx, now at 0x%08lx\n", 
                       i, (unsigned long)mask, 
                          (unsigned long)curr); 
           /* extract the type, and the programmable bits */ 
           if (mask & PCI_BASE_ADDRESS_SPACE_IO) { 
               type = "I/O"; mask &= PCI_BASE_ADDRESS_IO_MASK; 
               size = (~mask + 1) & 0xffff; /* Bleah */ 
           } else { 
               type = "mem"; mask &= PCI_BASE_ADDRESS_MEM_MASK; 
               size = ~mask + 1; 
           } 
           len += PRINTF("\tregion %i: type %s, size %i (%i%s)\n", i, 
                         type, size, 
                         (size & 0xfffff) == 0 ? size >> 20 : 
                           (size & 0x3ff) == 0 ? size >> 10 : size, 
                         (size & 0xfffff) == 0 ? "MB" : 
                           (size & 0x3ff) == 0 ? "KB" : "B");


           if (len > PAGE_SIZE / 2) { 
               len += PRINTF("... more info skipped ...\n"); 
               *eof = 1; return len; 
           } 
       } 
   return len; 
}


下面是 /proc/pciregions 给出的帧捕获器的区域报告:


Bus 0, device 13, fun  0 (id 8086-1223) 
       region 0: mask 0xfffff000, now at 0xf1000000 
       region 0: type mem, size 4096 (4KB) 
 
值得注意的是,该程序所报告的内存大小有时会有点夸大事实。例如,/proc/pciregions 报告某个显卡有 16 MB 的内存,但实际上只有 1 MB。这是可接受的,因为这个大小信息仅仅被固件用来分配地址范围。对了解设备细节的驱动程序编写者来说,超过实际情况的大小并不是问题,他们能够正确处理固件赋于的地址范围。在这种情况下,无需修改 PCI 寄存器,就可在设备上添加更多的 RAM。

如果存在这种夸大的情况,也会反映在资源接口中,这时,pci_resource_size 将报告夸大后的大小。


《嵌入式设计及linux驱动开发指南》中使用的是第一种方法,即用pci_resource_start()来获得,用pci_resource_flags()&IORESOURCE_IO来判断资源类型。


其它,还需参加pci设备开发基本框架,及《嵌入式设计及linux驱动开发指南》

阅读(794) | 评论(0) | 转发(0) |
0

上一篇:Kobject结构体分析

下一篇: PCI register driver

给主人留下些什么吧!~~