• Linux驱动之PCI


    <背景>

    PCI设备有许多地址配置的寄存器,初始化时这寄存器来配置设备的总线地址,配置好后CPU就可以访问该设备的各项资源了。(提炼:配置总线地址)
     
    <配置寄存器>
     
    (1)256字节的PCI配置空间分为64字节的头标区和192字节的设备相关区两部分。头标区的各个寄存器用来唯一地识别设备;设备相关区则保存一些与设备相关的数据。
    (2)配置空间的头标区又分为两部分:前16个字节的定义在各种类型的PCI设备中都是一样的;剩余的字节随设备类型不同而有所不同。位于偏移地址0EH处的头标类型字段规定了头标区的布局结构。目前,规范定义了三种头标类型。
                                     
    a:设备的识别
    (1)  供应商代码:该寄存器用于识别PCI设备的制造商,具体代码由PCI SIG(http://www.pcisig.com)分配。0FFFFH是无效的供应商代码。
    (2)       设备代码。该寄存器用来标识某供应商生产的具体设备,代码由各供应商定义。供应商代码和设备代码,读者可以到网站http://www.pcidatabase.com/查阅。
    (3)       版本号。该寄存器用来定义指定设备的版本信息。
    (4)       头标类型。该字段的第7位为“1”标识该设备是多功能设备,为“0”标识为单功能设备;该字段的0~6位就是上文表中所述的头标类型。
    (5)       设备分类代码。用来标识设备的总体功能和特定的寄存器级编程接口。
    上面5个字段均为只读类型,所有的PCI设备都必须实现其功能。
    b:设备控制和设备状态
    (1)       命令寄存器为一个设备发出和响应PCI总线命令提供粗略的控制。图4就是命令寄存器格式。
     
    位0(I/O空间控制):控制对I/O空间访问的响应。该位为0时,禁止设备响应对I/O空间的访问;该位为1时,允许设备响应I/O空间的访问。缺省设置为0。
    位1(存储器空间控制):控制一个设备对存储器空间访问的响应。该位为0时,禁止响应;该位为1时,允许设备响应对存储器空间的访问。缺省设置为0。
     
    状态寄存器用来记录PCI总线有关的状态信息。
    c:基址寄存器
    PCI设备中,除了配置空间外,还有两个物理空间:内存空间和I/O空间。为了访问这两个地址空间,就必须使用基址寄存器。头标类型0中涉及3种基址寄存器:内存空间基址寄存器、I/O空间基址寄存器和扩展ROM基址寄存器。
    d:其他寄存器
    其他寄存器包括一些本文不涉及到的寄存器,如中断引脚、中断线等等。 
    <PCI配置空间的访问>
        a:PCI规范使用从0CF8H~0CFFH 这8个I/O地址来访问所有设备的PCI配置空间。这8个字节实际上构成了两个32位寄存器:0CF8H寄存器叫做“配置地址寄存器”;0CFCH叫做“配置数据寄存器”。当要访问配置空间的寄存器时,先向地址寄存器写上目标地址,然后就可以从数据寄存器中读写数据了。
            PCI配置空间对应于一个PCI逻辑设备,所以要访问一个配置空间的某个寄存器,必须要指定:PCI总线号、PCI设备号、PCI设备功能号和寄存器号。配置地址寄存器的格式如下:
                         
           第0、1位上的“0”是用来要求你只能按双字(4字节)来读写配置空间寄存器。第31位“使能位”用来决定是否允许访问配置空间:为“1”时表示可以访问;为“0”时表示不可以访问。
            从上面的配置地址寄存器的格式我们可以看出:总线号从0~255、设备号从0~31、功能号从0~7。根据配置空间的第0个寄存器是否返回0FFFFH值来判断是否存在该PCI设备(这里可以看出PCI为什支持32个设备)
     
    <PCI驱动开发概总>
    对于驱动开发人员来说,pci具有如下吸引人的优势:
    a:设备自动配置系统,与旧的ISA驱动程序不一样,pci驱动不需要实现复杂的检测逻辑。
    b:系统启动时,BIOS(如果是嵌入式系统内核本身会完成该任务)会遍历pci总线并分配资源(比如中断优先级,I/O基地址) 
    c: 设备去驱动程序会查询叫做"PCI配置空间"的内存来找到资源分配情况
    d:PCI设备总共具有256B的配置空间内存。配置空间 顶部64B空间的含义是标准的,所有设备的配置在这段区域都是相似的。该空间被分成状态,I/O基地址,中断线。
     
    <访问PCI>
    a:内核函数
    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)
    形参分析:
    pdev:指向PCI设备的结构体
    offset:配置空间的偏移地址
    value:需要写入或读出数据的存放位置
    举例:
    unsigned char irq;
    pci_read_config_byte(pdev,PCI_INTERRUPT_LINE,&irq);
    注意:在配置空间中的中断号的偏移是60,这为什么不使用60,是因为在Linux内核中/include/linux/pci_regs.h进行了定义。
     
    <I/O和内存>
    I/O访问
    a:要访问一个PCI设备的I/O空间或内存空间在内存或I/O区域的映射。需要读取配置空间的相应基地址寄存器里得到I/O区域的基地址。
    (1)从配置区域相应基址寄存器得到I/O区域的基地址
    unsigned long io_base = pci_resource_start(pdev,bar)
    注意:该函数还有相应的变形
    unsigned long pci_resource_[start|lenght|flags](struct pci_dev*pdev, int bar)
    (2)调用内核函数request_region()获得这个IO区域,标明这片区域对应的设备
    request_region(io_base,length,"mydriver")
    (3)这样就可以使用I/O操作函数访问这些寄存器了,
    inl();
    outl();
     内存访问
    (1)调用该函数获得内存基地址
    unsigned long pci_resource_[start|lenght|flags](struct pci_dev*pdev, int bar)
    (2)调用内核函数request_mem_region()获得这个内存区域,标明这片区域对应的设备
    request_region(mmio_base,mmio_length,"mydriver")
    (3)将获得内存地址转换成虚拟地址
    buffer = pci_iomap(pdev, bar,mmio_length)
     
    <驱动实例>
    a当PCI热插拔检测到新插入的设备的ID属性和驱动程序中的pci_device_id表里的ID信息一致的时候。该层将激发该驱动程序的probe()函数被调用。进一步注册相应的设备驱动。可以看出首先得要注册pci_driver程序:
    调用函数pci_register_drivet()
    b:
  • 相关阅读:
    thinkphp目录解析
    开发规范
    form
    命名空间
    类与对象
    OS知识点汇总
    C++每日一记!
    语言哲学和语言逻辑
    形式语言与自动机
    C#脚本
  • 原文地址:https://www.cnblogs.com/big-devil/p/8589485.html
Copyright © 2020-2023  润新知