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

编程入门 行业动态 更新时间:2024-10-25 10:25:59

37.【linux驱动】pcie驱动<a href=https://www.elefans.com/category/jswz/34/1770026.html style=入门"/>

37.【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配置寄存器的格式如下图:

  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的寄存器,下面看一下桥的:

    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 <linux/init.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/device.h>
#include <linux/cdev.h>
#include <linux/slab.h>
#include <asm/uaccess.h>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);

更多推荐

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

本文发布于:2024-02-12 23:14:12,感谢您对本站的认可!
本文链接:https://www.elefans.com/category/jswz/34/1689764.html
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。
本文标签:入门   linux   pcie

发布评论

评论列表 (有 0 条评论)
草根站长

>www.elefans.com

编程频道|电子爱好者 - 技术资讯及电子产品介绍!