linux驱动 pcie 框架_【linux驱动】pcie驱动入门

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

pcie拓扑结构

pcie拓扑主要由总线、桥和设备卡组成,桥将父总线与子总线连接在一起形成树型结构。桥主要分为一下三类:

Host/PCI桥:用于连接CPU与PCI根总线,在PC中,内存控制器也通常被集成到Host/PCI桥设备芯片,Host/PCI桥通常被称 为“北桥芯片组”。PCI/ISA桥:用于连接旧的ISA总线。PCI/ISA桥也被称为“南桥芯片组”。PCI-to-PCI桥:用于连接PCI主总线与次总线。 pci总线编号采用深度优先(DFS)遍历顺序

pcie地址空间

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

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

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

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

pcie配置空间

详细看一下pcie配置寄存器的格式如下图:

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

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

PCI Agent 使用的配置空间PCI桥使用的配置空间Cardbus桥片使用的配置空间 上面是Agent的寄存器,下面看一下桥的: PCI桥有两组BAR寄存器,如果PCI桥本身不存在私有寄存器,那么BAR寄存器可以不使用(透明桥),初始化为0.

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

Subordinate Bus Number 寄存器存放当前PCI子树中编号最大的PCI总线号Secondary Bus Number 存放当前PCI桥Secondary Bus使用的总线号,也是该子树中编号最小的总线号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);

12

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

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

1

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

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

1

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

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

1

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

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

1

申请或者释放资源

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

void pci_release_regions(struct pci_dev *pdev);

12

使能或禁用设备

int pci_enable_device(struct pci_dev *pdev);

int pci_disable_device(struct pci_dev *pdev);

12

设置为主设备

void pci_set_master(struct pci_dev *pdev);

1

查找设备

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

1

设置电源状态

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

1

查找功能

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

1

启用禁用禁止写入

int pci_set_mwi(struct pci_dev *pdev);

void pci_clear_mwi(struct pci_dev *pdev);

12

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

};

12345678910111213141516171819202122232425262728293031323334

整体演示

#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)


版权声明:本文为weixin_29091445原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。