<s id="mvh2b"><strike id="mvh2b"><u id="mvh2b"></u></strike></s>
    1. <rp id="mvh2b"></rp>

      当前位置:首页 > IT教程

      pcie驱动介绍

      时间:2021-08-07 16:54:21来源:金橙教程网 作者:admin8 阅读:76次 [手机版]
       

      pci驱动

      PCIE设备的地址由总线号、设备号和功能号组成,分别称为厂家ID、设备ID和设备类代码

      我们可以利用lspci工具了解这些概念。PCI工具集的一部分,下载地址为http://mj.ucw.cz/sw/pciutils/

      下面这个照片是在xx.xx.xx.xx下面的shell终端下运行lspci,运行lspci

      上面输出代码每行开头的逻辑地址(xx:yy.z).XX代表PCI的总线号。一个PCI域能够容纳256个总线。上图中有3个PCI桥,引出来4条pci总线

      YY是PCI设备编号。每个PCI总线可以支持32个PCI设备。Z表示PCI功能,每个PCI设备可以容纳8个PCI功能。向上图中的Advanced Micro devices设备就有两个功能

      在我们T1040插上Intel? Ethernet Controller I350 T4之后

      输入lspci可以看到如下输出信息

      由于是4个网口,所以这里对应的功能号也是4个。回到我们的服务器上去,在服务器上面输入lspci -t可以看到如下信息

      对这个信息的理解你可以参考下面的体系架构来画出相应的框图,便于理解

      从上面的输出结果来看,我们的Advanced Micro devices,先从PCI总线开始(标号是0000),经过PCI-PCI桥(00:01.0)。

      PCI设备拥有256B的空间,用于存放配置寄存器。改空间是辨别PCI卡型号和性能的关键。我们可以查看配置去的详细情况

      在shell下面输入lspci -x

      如下图

      PCI寄存器是小端字节序格式,可以通过sysfs来查看PCI的配置,如下命令,查看命令和结果如下

      对上图进行解释,前面两字节是厂家ID,表示生产厂家。PCI厂家ID在全球都是统一分配和管理的,在www.pciDATabase.com可以查看。PCI配置空间含义如下

      对应上上图的输出信息,翻译起来就是

      厂家ID是0x1002,设备ID是6778,设备类型代码是0300,基址寄存器是0x000c-0000

      子厂家ID是174b,子设备ID是d145,

      配置空间中包含10字节用于表示设备类型的编码。表示PCI桥设备类编码的第一个字节是0x06,网络设备类编码的第一个字节是0x02,类编码类型的定义见include/Linux/pci_ids.h

      PCI驱动程序向PCI子系统注册其支持的厂家ID、设备ID和设备类编码。使用插入的卡通过配置空间被识别后,PCI子系统把插入的卡和对应的驱动绑定

      访问PCI

      PCI设备包括3个寻址空间:配置空间、IO端口和设备内存。

      配置区

      内核为驱动程序提供6个可以调用的函数来访问PCI配置区:

      pci_read_config_[byte|word|dword](struct pci_dev *pdev,int offset,int *value);

      pci_write_config_[byte|word|dword](struct pci_dev *pdev,int offset,int *value);

      在参数列表中,strutc pci_dev是PCI设备结构体,offset是想访问的配置空间中的字节位置。对读函数来讲,value是数据缓冲区的指针;对写函数来讲,value放的是准备写的数据。

      看看下面的例子。

      要得到分配给某卡功能的中断号,进行如下操作

      unsigend char_irq;

      pci_read_config_byte(pdev,PCI_INTERRUPT_LINE,&irq);

      按照PCI规定说明,PCI配置空间偏移量地址60处存放卡的IRQ号,配置空间的偏移地址都在文件include/linux/pci_regs.h中有详细定义,所以一般用到PCI_INTERRUPT_LINE 表示偏移地址。于此类似,要想读PCI状态寄存器(配置空间偏移6处),进行如下操作:

      unsigned short Status; ?pci_read_config_word(pdev,PCI_STATUS,&status);

      配置去开头的64B是规范了的。其他地方由设备生产厂家按照自己的意思定义。

      比如说我们使用的Xircom卡,在64B偏移处的4个字节用于电源管理的目的,为了禁止电灌管理功能,Xircom CardBus驱动程序在实现文件drivers/net/tulip/xircom_cb.c里进行如下操作:

      #define PCI_POWERMGMT 0x40

      pci_write_config_dword(pdev,PCI_POWERMGMT,0x0000);

      IO和内存

      PCI卡有6个IO或内存区域,I/O区域包括寄存器,内存区域存放数据。例如:视频卡的I/0区域包含控制寄存器,内存区域映射缓冲。但并不是所有的卡都有可寻址的内存区域。

      I/O和内存区域的功能和硬件有关系,在设备手册中可以查到。

      像配置区一样,内核提供一系列的辅助函数,可以用来操作PCI设备的/O和内存区域

      Unsigned long pci_resource_[start|len|end|flag](struct pci_dev *pdev,int bar);为操作I/O区域。如PCI视频卡设备控制寄存器,驱动程序要完成以下事件

      (1)从配置区相应基址寄存器里得到I/O区域的基址:

      unsigned long io_base = pci_resource_start(pdev,bar);

      这里假定设备控制寄存器被映射到由变量bar关联的I/O区域,bar值的变化范围是0-5。

      (2)用内核的Request_region()常规机制获得这个I/O区域,并标明它对应的设备:

      request_region(io_base,length,”my_driver”);

      通过调用request_region()申请I/O地址后,设备驱动程序在申请的I/O地址中操作运行。此机制确保其他程序不能申请此区域,直至用过调用release_region()释放占用的区域。

      lenth用于控制寄存器空间的大小,my_driver表示这个区域的使用者。在文件/Proc/ioports中的my_driver对应的文本行里可以看到这片I/O区域。也可以使用drivers/pci/pci.c文件中定义的封装函数pci_request_region()申请I/O端口

      (3)用寄存器手册上的偏移地址加上(1)得到的基址,然后用inb()和outb()函数来访问这些寄存器

      Read函数

      Register_data = inl(io_base+REGISTER_OFFSET);

      write函数

      Outl(register_data,io_base+REGISTER_OFFSET)

      为了能够操作PCI设备的内存区域,按照下面步骤进行

      (1)获得基址、内存区域长度以及内存相关标志

      Unsigned long mmio_base=pci_resource_start(pdev,bar)

      Unsigned long mmio_length=pci_resource_length(pdev,bar)

      Unsigned long mmio_flags=pci_resource_flags(pdev,bar)

      这里假定设备内存区域被映射到基址寄存器bar

      (2)用内核的request_men_region()常规机制标记这片内存区的拥有者

      Request_men_region(mmio_base,mmio_length,”my_driver”);

      用前面提到的封装函数pci_request_region()也可以

      (3)让cpu访问第(1)步获得的设备内存。为防止发生意外,某些内存空间(如包含寄存器的地方)设置成不让CPU可预取或不可启用高速缓存。对于其他设备(如本例中提到的区域),CPU 可以启用高速缓存。根据内存区域的访问标志,使用合适的函数可以得到与映射区域相对应的内核虚拟地址

      Viod __iomen *buffer;

      If(flages & IORESOURCE_CACHEABLE)

      {

      Buffer=ioreMap(mmio_base,mmio_length);

      }

      Else

      {

      Buffer = ioremap_nocache(mmio_base,mmio_length);

      }

      为了安全起见并避免执行前述的检查,使用lib/iomap.c定义的函数pci_iomap()代替

      Buffer=pci_iomap(pdev,bar,mmio_length)

      DMA缓冲区是DMA传送时用做源端地址或者目的地址的内存区。如果总线接口能访问的地址受限,将会影响DMA缓冲区的大小。因此,用于24位总线(如ISA)的DMA缓冲区只占系统内存底部的16MB。称为ZONE_DMA。PCI总线默认为32位,所以在32的平台下一般不会碰到这种限制。可以用下面的函数告诉内核系统中可用作DMA缓冲区的地址的特殊要求:

      dma_set_mask(struct device *dev,u64 mask);

      如果这个函数返回成功,就可以在mask指定的地址范围内进行DMA操作。如e1000PCI-X吉位以太网驱动程序(drivers/net/e1000/e1000-main.c)里:

      If(!(err = pci_set_dma_mask(pdev,DMA_64)))

      {

      /*System supports 64-bit DMA*/

      Pci_using_dac =1

      }

      Else

      {

      /*see if 32-bit DMA is supported */

      If((err =pci_set_dma_mask(pdev,DMA_32BIT_MASK)))

      {

      /*no,let’s abort*/

      E1000_ERR(“No usable DMA configuration,aborting\n”);

      Return err;

      }

      /*32-bit DMA*/

      Pci_using_dac=0;

      }

      I/O设备从总线控制器或IOMMU(I/O Memory Unit,I/O内存管理单元)的角度看待DMA缓冲区。所以,I/O设备需要的是DMA缓冲区的总线地址,而不是物理地址或者内核虚拟地址。如果你想告诉PCI卡DMA缓冲区的地址信息,必须让PCI卡知道DMA缓冲区的总线地址。总线地址的类型是dma_addr_t,在文件include/asm-your-arch/types.h里有定义

      DMA还有几个概念要了解,一个是回弹缓冲区。回弹缓冲区驻留在可作为DMA缓冲区的内存区域里,在DMA请求的源或目的地址没有DMA功能的内存区域时,它可以作为临时内存区存放数据。例如,用DMA方式从32位PCI外围设备向地址高于4GB的目标传送数据时,如果没有IOMMU单元,就需要用到回弹缓冲区了。数据先暂时存放在回弹缓冲区,再复制到目标地址。第二个概念颇具DMA的特色:分散、聚集。当要传输的数据分布在不连续的内存上时,分散/聚集能对这些不连续缓冲区的数据进行一次性发送,反过来,DMA也能把数据从卡正确地传送到地址不连续的缓冲区中。分散/聚集通过减少重复的DMA传送请求来提高效率。

      内核提供有用的api函数来屏蔽配置DMA的细节。如果你是在给支持总线管理的PCI卡(大多数PCI卡都支持)写驱动程序,这些API会更简单。PCI DMA函数基本上就是封装DMA的通用服务函数,在include/asm/-genetic/pci-dma-compat.h文件中有定义。本章中我们只用PCI的DMA API。

      内核为PCI驱动程序提供了两类DMA服务

      (1)一致性DMA访问方法。这些程序保证DMA传送数据的一致性。如果PCI设备和CPU都有可能干预DMA缓冲区,保证数据的一致性是很重要的,这时要用一致性API函数。它是性能上的一个折中。要得到能保证数据一致性的DMA缓冲区,用下面的API函数

      Void *pci_alloc_consistent(strcuct pci_dev *pdev,size_t ?size,dma_addr_t *dma_handle);

      这个函数分配一个DMA缓冲区,生成它的总线地址,并返回相关的内核虚拟地址。前面两个参数是PCI设备结构体和DMA缓冲区的字节大小,第三个参数(dma-handle)是指向总线地址的指针,由函数调用产生。下面的代码片段分配和释放一个一致性DMA缓冲区

      /*ALLOCATE*/

      Void *vaddr = pci_alloc_consistent(pdev,size,&dma_handle)

      /*free*/

      Pci_free_consistent(pdev,size,vaddr,dma_handle);

      (2)流式DMA访问函数。这些API不保证数据的一致性,所以速度更快。它们在不太需要CPU和I/O设备共享DMA缓冲区的时候比较有用。当设备存储的流式缓冲区被映射以便被设备访问后,驱动程序要明确得去映射(或同步)流式缓冲区,之后CPU才可以可靠地操作该缓冲区。流式访问有两类函数:pci_[map|unmap|dma_sync]_single()和pci_[map |unmap|dma_sync]_sg().

      第一组函数可以映射、去映射以及同步一个预先分配的单一DMA缓冲区。Pci_map_single()的原型如下

      Dma_addr_t pci_map_single(struct pci_dev *pdev,void *ptr,size_t size,int direction);

      前三个参数分别表示PCI设备结构体,预先分配的DMA缓冲区虚拟内核地址和缓冲区的字节数。第四个参数取下面几个值:PCI_DMA_BIDIRECTION、PCI_DMA_TODEVICE、PCI_DMA_FROMDEVICE和PCI_DMA_NONE,从名字上就很容易看出这些变量的含义。但是使用第一个选项(PCI_DMA_BIDIRECTION)的代价比较大,最后一个选项是在调试的时候使用。

      第二组函数映射、去映射并且同步一个分散/聚集的DMA缓冲区链。Pci_map_sg()的原型如下:

      Int pci_map_sg(struct pci_dev *pdev,struct scatterList *sgl,int num_entries,int direction);

      第二个参数是sruct scatterlist表示分散内存的链表,在include/asm-your-arch/scatterlist.h

      里面定义。Num_entries变量表示分散内存的链表入口函数。第一和最后一个参数的意思跟pci_map_single()函数里的参数是一样的。

      相关阅读

      Linux 删除文件夹和文件的命令

      Linux删除目录很简单&#xff0c;使用rm -rf命令即可。使用规则&#xff1a;?? rm -rf 目录名字?? ?? -r 向下递归&#xff0c;不管有多

      linux启动出现reboot and select proper boot device

      问题描述&#xff1a; 台式机原来是固态硬盘安装的Windows&#xff0c;有一块空闲的硬盘&#xff0c;想在硬盘上安装一个linux系统&#xff0c;

      PCI Bus

      PCI Bus pinout for both 32 bit and 64 bit cards is shown below;Signal Pins 63-94 are only used on 64 bit PCI bus cards.

      dvd驱动器不见了怎么办 dvd驱动器恢复方法步骤介绍

        很多网友都在问dvd驱动器不见了怎么办?不少网友们根本没动过dvd,在设备管理器中找不到dvd驱动器,安装驱动精灵也不管用,或者重装安

      linux系统日志以及分析

      Linux系统拥有非常灵活和强大的日志功能&#xff0c;可以保存几乎所有的操作记录&#xff0c;并可以从中检索出我们需要的信息。 大部分L

      分享到:

      IT相关

      程序相关

      推荐文章

      热门文章

      东北老女人嫖老头视频_无遮挡H肉动漫视频在线观看_欧美牲交a欧美牲交aⅴ另类_狼人乱码无限2021芒果