37.【linux驱动】pcie驱动入门

pcie总线向下兼容pci总线,文中所述pci默认等于pcie

pcie拓扑结构

37.【linux驱动】pcie驱动入门_第1张图片
pcie拓扑主要由总线、桥和设备卡组成,桥将父总线与子总线连接在一起形成树型结构。桥主要分为一下三类:

  • Host/PCI桥:用于连接CPU与PCI根总线,在PC中,内存控制器也通常被集成到Host/PCI桥设备芯片,Host/PCI桥通常被称 为“北桥芯片组”。
  • PCI/ISA桥:用于连接旧的ISA总线。PCI/ISA桥也被称为“南桥芯片组”。
  • PCI-to-PCI桥:用于连接PCI主总线与次总线。
    37.【linux驱动】pcie驱动入门_第2张图片
    pci总线编号采用深度优先(DFS)遍历顺序
    37.【linux驱动】pcie驱动入门_第3张图片

pcie地址空间

pcie由三种地址空间,分别是:PCI配置空间PCI/IO空间PCI内存地址空间

PCI配置空间 可以理解为pcie设备卡的配置寄存器,这个配置寄存器有一定的规范,pci协议中大小为256字节,pcie协议中可以扩展到4K。其中前64字节是固化在设备中的,不可读写,其他部分可以读写。访问配置寄存器的方法是借助北桥,北桥将0xCF8 ~ 0xCFF的地址用作pci卡配置用。其中向0xCF8 ~ 0xCFC写入要访问的PCI的卡id,id由pic总线编号+设备编号+功能编号+寄存器号组成,具体格式如下图。上一步选中卡和寄存器之后读写0xCFC ~ 0xCFF,北桥会发送配置事务完成寄存器的配置。
37.【linux驱动】pcie驱动入门_第4张图片

PCI内存地址空间 可以理解为pci设备的数据寄存器,设备设计的时候定好的。这段空间记录在了PCI配置空间的前64k信息里面。操作系统只需要吧这段地址映射到内存就可以访问了。

PCI/IO空间 可以理解为pcie设备的可寻址空间,32为的pci总线可寻址空间为4GB,所以PCI内存地址空间不可以大于4GB

pcie配置空间

详细看一下pcie配置寄存器的格式如下图:
37.【linux驱动】pcie驱动入门_第5张图片

37.【linux驱动】pcie驱动入门_第6张图片

  1. Device ID 和 Verdor ID. 厂商分配的,只读。
  2. Revision ID. 记录了PCI设备的版本号,只读
  3. Class Code. 记录了PCI设备的分类,只读。分为base class code(把设备分为显卡、网卡、PCI桥等设备)、sub class code(进行细分)、interface(定义编程接口) 三个字段。这个寄存器可供系统软件识别当前PCI设备的分类。按类型写驱动就靠这个区分。
  4. Header Type. 表示当前配置空间类型,只读。
  5. Cache Line Size. HOST处理器使用的Cache行长度,由系统软件设置。(对PCIe无意义
  6. Subsystem ID 和 subSystem Vendor ID. 当使用Device ID 和 Vendor ID 无法区分的情况
  7. Expansion ROM base address. 记录了ROM程序的基地址。有些PCI设备在处理器还没有运行操作系统之前,就需要完成基本的初始化设置,PCI设备提供了一段ROM程序,处理器在初始化过程中将运行这段ROM程序,初始化这些PCI设备。
  8. Capabilities Pointer. 在PCI设备中,寄存器是可选的,在PCI-X和PCIe设备中必须支持。
  9. Interrupt Line. 系统软件对PCI设备进行配置的时候写入的,记录当前PCI设备使用的中断向量号,如果不适用8259A中断控制器,这个寄存器没有意义
  10. Interrupt Pin.使用那个引脚作为中断,pci一共有4个中断引脚,可以是任意一个
  11. Base Address Register 0 ~ 5. 保存PCI设备使用的地址空间的基地址,保存的是该设备在PCI总线域中的地址。

其实配置寄存器分为三种:

  • PCI Agent 使用的配置空间
  • PCI桥使用的配置空间
  • Cardbus桥片使用的配置空间
    上面是Agent的寄存器,下面看一下桥的:
    37.【linux驱动】pcie驱动入门_第7张图片
    PCI桥有两组BAR寄存器,如果PCI桥本身不存在私有寄存器,那么BAR寄存器可以不使用(透明桥),初始化为0.

PCI Bridge 的配置空间相比较 PCI Agent 的配置空间,多了 Bus Number 寄存器

  1. Subordinate Bus Number 寄存器存放当前PCI子树中编号最大的PCI总线号
  2. Secondary Bus Number 存放当前PCI桥Secondary Bus使用的总线号,也是该子树中编号最小的总线号
  3. Primary Bus Number 存放该PCI桥上游的PCI总线号

Linux内核API

读写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);

获取配置寄存器单中的base addr起始地址,bar为第几个地址,一共六个

pci_resource_start(dev, bar) /*Bar值的范围为0-5*/

获取配置寄存器单中的base addr结束地址

pci_resource_end(dev, bar) /*Bar值的范围为0-5*/

获取配置寄存器单中的base addr状态,状态还是比较多的,详见ioport.h。

pci_resource_flags(dev, bar) /*Bar值的范围为0-5*/

获取配置寄存器单中的base addr地址长度,也等于pci_resource_end((dev), (bar))-pci_resource_start((dev), (bar)) + 1)

pci_resource_len(dev,bar) /*Bar值的范围为0-5*/

申请或者释放资源

int pci_request_regions(struct pci_dev *pdev, const char *res_name);
void pci_release_regions(struct pci_dev *pdev);

使能或禁用设备

int pci_enable_device(struct pci_dev *pdev);
int pci_disable_device(struct pci_dev *pdev);

设置为主设备

void pci_set_master(struct pci_dev *pdev);

查找设备

struct pci_dev *pci_find_slot (unsigned int bus,unsigned int devfn);

设置电源状态

int pci_set_power_state(struct pci_dev *pdev, pci_power_t state);

查找功能

int pci_find_capability(struct pci_dev *pdev, int cap);

启用禁用禁止写入

int pci_set_mwi(struct pci_dev *pdev);
void pci_clear_mwi(struct pci_dev *pdev);

helloworld

pci设备的框架跟bus差不多,也都万变不离其踪,大致是下面的步骤:

1.驱动的注册:

  • 检测硬件是否支持pic

2.驱动适配:

  • 打开pci设备
  • 配置寄存器
  • 读取映射地址
  • 初始化字符设备、网络设备、USB等

3.pci设备的读写

pci_dev结构体基本对应配置寄存器里面的内容

struct pci_dev {
    struct list_head global_list;
    struct list_head bus_list;
    struct pci_bus  *bus;
    struct pci_bus  *subordinate;
    void        *sysdata;
    struct proc_dir_entry *procent;
    unsigned int    devfn;
    unsigned short  vendor;
    unsigned short  device;
    unsigned short  subsystem_vendor;
    unsigned short  subsystem_device;
    unsigned int    class;
    u8      hdr_type;
    u8      rom_base_reg;
    struct pci_driver *driver;
    void        *driver_data;
    u64     dma_mask;
    u32             current_state;
    unsigned short vendor_compatible[DEVICE_COUNT_COMPATIBLE];
    unsigned short device_compatible[DEVICE_COUNT_COMPATIBLE];
    unsigned int    irq;
    struct resource resource[DEVICE_COUNT_RESOURCE];
    struct resource dma_resource[DEVICE_COUNT_DMA];
    struct resource irq_resource[DEVICE_COUNT_IRQ];
    char        name[80];
    char        slot_name[8];
    int     active;
    int     ro;
    unsigned short  regs;
    int (*prepare)(struct pci_dev *dev);
    int (*activate)(struct pci_dev *dev);
    int (*deactivate)(struct pci_dev *dev);
};

整体演示

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

MODULE_LICENSE("GPL");


/* 对特定PCI设备进行描述的数据结构 */
struct hello_card {
    unsigned int magic;
    /* 使用链表保存所有同类的PCI设备 */
    struct hello_card *next;
}

static int hello_ioctl(struct inode *inode, struct file *file,
      unsigned int cmd, unsigned long arg)
{
    switch(cmd) {
        case hello_RDATA:
            /* 从I/O端口读取4字节的数据 */
            val = inl(card->iobae + 0x10);
/* 将读取的数据传输到用户空间 */
    }
    return 0;
}

static int hello_open(struct inode *inode, struct file *file)
{
    /* 申请中断,注册中断处理程序 */
    request_irq(card->irq, &hello_interrupt, SA_SHIRQ,
        card_names[pci_id->driver_data], card)) {
    /* 检查读写模式 */
    if(file->f_mode & FMODE_READ) {
        /* ... */
    }
    if(file->f_mode & FMODE_WRITE) {
       /* ... */
    }
     
    /* 申请对设备的控制权 */
    down(&card->open_sem);
    while(card->open_mode & file->f_mode) {
        if (file->f_flags & O_NONBLOCK) {
            /* NONBLOCK模式,返回-EBUSY */
            up(&card->open_sem);
            return -EBUSY;
        } else {
            /* 等待调度,获得控制权 */
            card->open_mode |= f_mode & (FMODE_READ | FMODE_WRITE);
            up(&card->open_sem);
            /* 设备打开计数增1 */
            MOD_INC_USE_COUNT;
            /* ... */
        }
    }
}

static void hello_interrupt(int irq, void *dev_id, struct pt_regs *regs)
{
    struct hello_card *card = (struct hello_card *)dev_id;
    u32 status;
    spin_lock(&card->lock);
    /* 识别中断 */
    status = inl(card->iobase + GLOB_STA);
    if(!(status & INT_MASK)) 
    {
        spin_unlock(&card->lock);
        return;  /* not for us */
    }
    /* 告诉设备已经收到中断 */
    outl(status & INT_MASK, card->iobase + GLOB_STA);
    spin_unlock(&card->lock);
     
    /* 其它进一步的处理,如更新DMA缓冲区指针等 */
}

static int hello_release(struct inode *inode, struct file *file)
{
    /* 释放对设备的控制权 */
    card->open_mode &= (FMODE_READ | FMODE_WRITE);
     
    /* 唤醒其它等待获取控制权的进程 */
    wake_up(&card->open_wait);
    up(&card->open_sem);
     
    /* 释放中断 */
    free_irq(card->irq, card);
     
    /* 设备打开计数增1 */
    MOD_DEC_USE_COUNT;
}

static struct file_operations hello_fops = {
    owner:      THIS_MODULE,
    read:       hello_read,
    write:      hello_write,
    ioctl:      hello_ioctl,
    open:       hello_open,
    release:    hello_release,
};


static int __init hello_probe(struct pci_dev *pci_dev, const struct pci_device_id *pci_id)
{
    struct hello_card *card;
    /* 启动PCI设备 */
    if (pci_enable_device(pci_dev))
        return -EIO;
    /* 设备DMA标识 */
    if (pci_set_dma_mask(pci_dev, hello_DMA_MASK)) {
        return -ENODEV;
    }
    /* 在内核空间中动态申请内存 */
    if ((card = kmalloc(sizeof(struct hello_card), GFP_KERNEL)) == NULL) {
        printk(KERN_ERR "pci_hello: out of memory\n");
        return -ENOMEM;
    }
    memset(card, 0, sizeof(*card));
    /* 读取PCI配置信息 */
    card->iobase = pci_resource_start(pci_dev, 1);
    card->pci_dev = pci_dev;
    card->pci_id = pci_id->device;
    card->irq = pci_dev->irq;
    card->next = devs;
    card->magic = hello_CARD_MAGIC;
    /* 设置成总线主DMA模式 */
    pci_set_master(pci_dev);
    /* 申请I/O资源 */
    request_region(card->iobase, 64, card_names[pci_id->driver_data]);
    return 0;
}

static struct pci_device_id hello_pci_tbl [] __initdata = {
    {PCI_VENDOR_ID_hello, PCI_DEVICE_ID_hello,
     PCI_ANY_ID, PCI_ANY_ID, 0, 0, hello},
    {0,}
};

static struct pci_driver hello_pci_driver = {
    name:       hello_MODULE_NAME,
    id_table:   hello_pci_tbl,
    probe:      hello_probe,
    remove:     hello_remove,
};

static int __init hello_init_module (void)
{
    //检查系统是否支持PCI总线
    if (!pci_present())
        return -ENODEV;

    if (!pci_register_driver(&hello_pci_driver)) {
        pci_unregister_driver(&hello_pci_driver);
        return -ENODEV;
    }
    return 0;
}

static void __exit hello_cleanup_module (void)
{
    pci_unregister_driver(&hello_pci_driver);
}

module_init(hello_init_module);
module_exit(hello_cleanup_module);

你可能感兴趣的:(#,linux驱动,arm,linux内核,pci,pcie,驱动)