精华内容
下载资源
问答
  • linux pcie 分析
    2022-05-05 14:57:21

    Linux内核用主设备号来定位对应的设备驱动程序,而次设备号则由驱动程序使用,用来标识它所管理的若干同类设备;
    字符设备是 Linux 驱动中最基本的一类设备驱动,字符设备就是一个一个字节,按照字节
    流进行读写操作的设备,例如LED、按键、IIC、SPI等。

    字符型驱动通常加载在/dev/xxx下形成一个文件,例如/dev/led是led的驱动文件。
    用户处于用户空间,驱动处于内核空间,用户通过系统调用的方法实现对驱动的操作。
    在linux内核文件include/linux/fs.h 中有 file_operations 的结构体,是内核驱动操作函数集合。
    

    设备或模块需要启动时,需要访问出口和入口,其中入口作为配置接口和状态读取;访问请求出口,又包括数据读取和写出两个方向,一般来说,设备内包含DMA引擎,访问请求出口基本涵盖了设备除配置外的所有流量。根据简单的系统结构模型来看,PCIe设备既属于广义DMA,也属于存储体。

    Linux读取寄存器地方法
    一.应用层读写内部寄存器
    步骤1:打开linux系统dev目录下的/dev/mem文件;
    dev_fd = open("/dev/mem", O_RDWR|O_SYNC);
    步骤2:将寄存器地址映射到用户空间;
    unsigned char *map_base=(unsigned char * )mmap(NULL, MAP_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, dev_fd, AUDIO_REG_BASE );
    步骤3:用户空间读写寄存器;
    *(volatile unsigned int *)(map_base+0x38);
    *(volatile unsigned int *)(map_base + 0x30) = 0x208121bc;
    步骤4:解除映射关系;
    
    munmap(map_base,MAP_SIZE);
    二.驱动层读写内部寄存器
    
    步骤1:将寄存器物理地址映射到内核空间虚拟地址上;
    our_card->regs = ioremap(S3C2410_PA_IIS, 0x100); 
    
    步骤2:驱动层读写寄存器
    readl(our_card->regs );
    writel(value, our_card->regs);
    
    外部寄存器
    
    一、统一编址-IO内存方式
    步骤1:向内核申请并分配一段内存
    request_mem_region(0x56000014,0x4,"led");
    
    步骤2:将IO外设的物理地址映射到内核的虚拟地址中
    ioremap(0x56000014,0x4);
    
    步骤3:对外部寄存器读写
    value = ioread32(led->base);
    iowrite32( value & ~(1<<led->offset), led->base);
    
    步骤4:取消映射
    iounmap(led[i].base);
    
    步骤5:注销释放该段区域内存
    release_mem_region(0x56000014,0x4);
    
    二、独立编址-IO端口方式
    
    步骤1:申请一段区域
    request_region(0x56000014,0x4,"led");
    
    步骤2:读写寄存器
    
    inb/outb/inw/outw/inl/outl
    
    value = inl((unsigned)(S3C2410_GPBDAT));
    
    outl(value | 1<<led->offset,(unsigned)(S3C2410_GPBDAT));
    
    步骤3:注销释放该区域
    release_region(0x56000014,0x4);
    函数使用说明
    
    1.mmap函数使用说明
    用法:#include <sys/mman.h>
    
    void *mmap(void *start,size_t length,int prot,int flags,int fd,off_t offset);
    
    int munmap(void *start, size_t length);
    参数:
    
    start:映射区的开始地址;length:映射区的长度;prot:期望的内存保护标志,不能与文件的打开模式冲突,PROT_EXEC //页内容可以被执行,PROT_READ //页内容可以被读取,PROT_WRITE //页可以被写入,PROT_NONE //页不可访问;flags:指定映射对象的类型,映射选项和映射页是否可以共享;fd:有效的文件描述词;offset:被映射对象内容的起点,内部寄存器物理地址。
    函数释义:
    
    将物理地址映射到用户空间的虚拟地址上,在用户空间完成对设备寄存器的操作。
    
    2.ioremap函数使用说明
    
    用法:
    
    #include <io.h>
    
    void *ioremap(unsigned long phys_addr, unsigned long size)
    
    参数:
    phys_addr:是要映射的物理地址;size:是要映射的长度,单位是字节。
    函数释义:将物理地址转换为内核虚拟地址,通常内核态设备驱动程序会使用这个虚拟地址访问寄存器。
    3.request_mem_region函数使用说明
    用法:从 start 开始,分配一个 len 字节的内存区域
    #ioport.h
    struct resource *request_mem_region(unsigned long start, unsigned long len, char*name)
    参数:start,I/O外设寄存器的物理地址;len,分配内存的大小。
    函数释义:请求分配指定的I/O内存资源,任务是检查你申请的资源是否可用,如果可用则申请成功,并标志为已经使用,其它driver再申请这资源时则不会再成功。
    4.request_region函数使用说明
    用法:
    #ioport.h
    void request_region(unsigned long from, unsigned long num, const char *name) 
    参数:from,io端口的基地址;num,io端口占用的范围;name,使用这段io地址的设备名。
    

    分类 字符设备驱动、块设备驱动、网络设备驱动; 分类原则是设备读写操作的特征差异;

    映射bar空间,程序中获取到的地址已经是cpu存储域的地址 ,而不是PCIe域地址。/dev/mem接口是物理地址的全部镜像,mmap可以通过该接口将指定的物理地址映射到虚拟地址空间上,这样进程就可以访问实际存在的物理地址空间。映射函数如下,phy_addr参数待映射的物理地址,size是待映射的物理地址大小。
    到目前为止程序能够顺利访问PCIe 4k配置空间,写配置空间时必须在root用户下,接下来实现访问bar空间,bar空间访问分为两部分,得到大小和映射。获取bar空间大小先给bar0寄存器全写1,查看位的变化情况得到大小。

    从应用层编写驱动主要是使用pcilib库和/dev/mem接口,下面开始分析代码。根据pcie设备的厂家ID和设备ID初始化设备,并且返回访问设备指针描述符,pci_dev指针指向我们需要访问的设备。

    更多相关内容
  • LeCroy的PCIE分析软件是一款免费的软件,可以查看当前系统的PCIE设备,还可以对寄存器进行读写操作,在Linux下使用方法如下:1.下载软件,下载地址:Teledyne LeCroy - PCI Express Analysis Software2.安装: ...

    LeCroy的PCIE分析软件是一款免费的软件,可以查看当前系统的PCIE设备,还可以对寄存器进行读写操作,在Linux下使用方法如下:

    1.下载软件,下载地址:

    TeleScanPE_linux安装包-Linux文档类资源-CSDN下载LeCroy的PCIE分析软件是一款免费的软件,可以查看当前系统的PCIE设备,还可以对寄存器进行读更多下载资源、学习资料请访问CSDN下载频道.https://download.csdn.net/download/mabin2005/85848241 或者:

    Teledyne LeCroy - PCI Express Analysis Software

    2.安装:

    sudo ./telescanpe_linux_x86_64_common_v1_51_2021_03_24__10_02.run

    需要注意的是安装选项需要手动增加PCIE的选项。

    3.在应用程序中找到TeleScanPE软件,运行后如下图,可以看到PCIE的设备,以及设备详细信息:

    4.如果更换了内核,则软件会无法使用,估计是因为软件使用了内核驱动的原因,所以需要卸载后重新安装,卸载方式:

    su
    cd /Teledyne\ LeCroy/
    ./maintenancetool

     5.卸载完成后使用步骤2的方式进行安装,安装后可以正常使用。

    展开全文
  • linux PCIE驱动开发

    2020-12-21 15:20:35
    linux下PCI驱动源码实例1,该源码缺少pci_fops的初始化#include #include #include #include #include #include #undef debug// ATTENTION copied from /uboot_for_mpc/arch/powerpc/include/asm/signal.h// Maybe ...

    linux下PCI驱动源码实例1,该源码缺少pci_fops的初始化

    #include

    #include

    #include

    #include

    #include

    #include

    #undef debug

    // ATTENTION copied from /uboot_for_mpc/arch/powerpc/include/asm/signal.h

    // Maybe it don't work with that

    //____________________________________________________________

    #define SA_INTERRUPT 0x20000000 /* dummy -- ignored */

    #define SA_SHIRQ 0x04000000

    //____________________________________________________________

    #define pci_module_init pci_register_driver // function is obsoleted

    // Hardware specific part

    #define MY_VENDOR_ID 0x5333

    #define MY_DEVICE_ID 0x8e40

    #define MAJOR_NR 240

    #define DRIVER_NAME "PCI-Driver"

    static unsigned long ioport=0L, iolen=0L, memstart=0L, memlen=0L,flag0,flag1,flag2,temp=0L;

    // private_data

    struct _instance_data {

    int counter; // just as a example (5-27)

    // other instance specific data

    };

    // Interrupt Service Routine

    static irqreturn_t pci_isr( int irq, void *dev_id, struct pt_regs *regs )

    {

    return IRQ_HANDLED;

    }

    // Check if this driver is for the new device

    static int device_init(struct pci_dev *dev,

    const struct pci_device_id *id)

    {

    int err=0; // temp variable

    #ifdef debug

    flag0=pci_resource_flags(dev, 0 );

    flag1=pci_resource_flags(dev, 1 );

    flag2=pci_resource_flags(dev, 2 );

    printk("DEBUG: FLAGS0 = %u\n",flag0);

    printk("DEBUG: FLAGS1 = %u\n",flag1);

    printk("DEBUG: FLAGS2 = %u\n",flag2);

    /*

    * The following sequence checks if the resource is in the

    * IO / Storage / Interrupt / DMA address space

    * and prints the result in the dmesg log

    */

    if(pci_resource_flags(dev,0) & IORESOURCE_IO)

    {

    // Ressource is in the IO address space

    printk("DEBUG: IORESOURCE_IO\n");

    }

    else if (pci_resource_flags(dev,0) & IORESOURCE_MEM)

    {

    // Resource is in the Storage address space

    printk("DEBUG: IORESOURCE_MEM\n");

    }

    else if (pci_resource_flags(dev,0) & IORESOURCE_IRQ)

    {

    // Resource is in the IRQ address space

    printk("DEBUG: IORESOURCE_IRQ\n");

    }

    else if (pci_resource_flags(dev,0) & IORESOURCE_DMA)

    {

    // Resource is in the DMA address space

    printk("DEBUG: IORESOURCE_DMA\n");

    }

    else

    {

    printk("DEBUG: NOTHING\n");

    }

    #endif /* debug */

    // allocate memory_region

    memstart = pci_resource_start( dev, 0 );

    memlen = pci_resource_len( dev, 0 );

    if( request_mem_region( memstart, memlen, dev->dev.kobj.name )==NULL ) {

    printk(KERN_ERR "Memory address conflict for device \"%s\"\n",

    dev->dev.kobj.name);

    return -EIO;

    }

    // allocate a interrupt

    if(request_irq(dev->irq,pci_isr,SA_INTERRUPT|SA_SHIRQ,

    "pci_drv",dev)) {

    printk( KERN_ERR "pci_drv: IRQ %d not free.\n", dev->irq );

    }

    else

    {

    err=pci_enable_device( dev );

    if(err==0) // enable device successful

    {

    return 0;

    }

    else // enable device not successful

    {

    return err;

    }

    }

    // cleanup_mem

    release_mem_region( memstart, memlen );

    return -EIO;

    }

    // Function for deinitialization of the device

    static void device_deinit( struct pci_dev *pdev )

    {

    free_irq( pdev->irq, pdev );

    if( memstart )

    release_mem_region( memstart, memlen );

    }

    static struct file_operations pci_fops;

    static struct pci_device_id pci_drv_tbl[] __devinitdata = {

    { MY_VENDOR_ID, // manufacturer identifier

    MY_DEVICE_ID, // device identifier

    PCI_ANY_ID, // subsystem manufacturer identifier

    PCI_ANY_ID, // subsystem device identifier

    0, // device class

    0, // mask for device class

    0 }, // driver specific data

    { 0, }

    };

    static int driver_open( struct inode *geraetedatei, struct file *instance )

    {

    struct _instance_data *iptr;

    iptr = (struct _instance_data *)kmalloc(sizeof(struct _instance_data),

    GFP_KERNEL);

    if( iptr==0 ) {

    printk("not enough kernel mem\n");

    return -ENOMEM;

    }

    /* replace the following line with your instructions */

    iptr->counter= strlen("Hello World\n")+1; // just as a example (5-27)

    instance->private_data = (void *)iptr;

    return 0;

    }

    static void driver_close( struct file *instance )

    {

    if( instance->private_data )

    kfree( instance->private_data );

    }

    static struct pci_driver pci_drv = {

    .name= "pci_drv",

    .id_table= pci_drv_tbl,

    .probe= device_init,

    .remove= device_deinit,

    };

    static int __init pci_drv_init(void)

    { // register the driver by the OS

    if(register_chrdev(MAJOR_NR, DRIVER_NAME, &pci_fops)==0) {

    if(pci_module_init(&pci_drv) == 0 ) // register by the subsystem

    return 0;

    unregister_chrdev(MAJOR_NR,DRIVER_NAME); // unregister if no subsystem support

    }

    return -EIO;

    }

    static void __exit pci_drv_exit(void)

    {

    pci_unregister_driver( &pci_drv );

    unregister_chrdev(MAJOR_NR,DRIVER_NAME);

    }

    module_init(pci_drv_init);

    module_exit(pci_drv_exit);

    MODULE_LICENSE("GPL");

    PCIE同PCI驱动的差异

    From a software standpoint, PCI and PCI Express devices are essentially the same. PCIe devices had the same configuration space, BARs, and (usually) support the same PCI INTx interrupts.一般情况下,两者基本保持一致

    Example #1: Windows XP has no special knowledge of PCIe, but runs fine on PCIe systems.

    Example #2: My company offers both PCI and PCIe versions of a peripheral board, and they use the same Windows/Linux driver package. The driver does not "know" the difference between the two boards.

    However: PCIe devices frequently take advantage of "advanced" features, like MSI, Hotplugging, extended configuration space, etc. Many of these feature existed on legacy PCI, but were unused. If this is a device you are designing, it is up to you whether or not you implement these advanced features.但是pcie在一些高级特性上有优势,比如MSI(Message Signaled Interrupts)、Hotplugging(热插拔)、配置空间扩展等。

    linux设备驱动程序框架

    Linux将所有外部设备看成是一类特殊文件,称之为“设备文件”,如果说系统调用是Linux内核和应用程序之间的接口,那么设备驱动程序则可以看成是Linux内核与外部设备之间的接口。设备驱动程序向应用程序屏蔽了硬件在实现上的细节,使得应用程序可以像操作普通文件一样来操作外部设备。

    1. 字符设备和块设备

    Linux抽象了对硬件的处理,所有的硬件设备都可以像普通文件一样来看待:它们可以使用和操作文件相同的、标准的系统调用接口来完成打开、关闭、读写和I/O控制操作,而驱动程序的主要任务也就是要实现这些系统调用函数。Linux系统中的所有硬件设备都使用一个特殊的设备文件来表示,例如,系统中的第一个IDE硬盘使用/dev/hda表示。每个设备文件对应有两个设备号:一个是主设备号,标识该设备的种类,也标识了该设备所使用的驱动程序;另一个是次设备号,标识使用同一设备驱动程序的不同硬件设备。设备文件的主设备号必须与设备驱动程序在登录该设备时申请的主设备号一致,否则用户进程将无法访问到设备驱动程序。

    在Linux操作系统下有两类主要的设备文件:一类是字符设备,另一类则是块设备。字符设备是以字节为单位逐个进行I/O操作的设备,在对字符设备发出读写请求时,实际的硬件I/O紧接着就发生了,一般来说字符设备中的缓存是可有可无的,而且也不支持随机访问。块设备则是利用一块系统内存作为缓冲区,当用户进程对设备进行读写请求时,驱动程序先查看缓冲区中的内容,如果缓冲区中的数据能满足用户的要求就返回相应的数据,否则就调用相应的请求函数来进行实际的I/O操作。块设备主要是针对磁盘等慢速设备设计的,其目的是避免耗费过多的CPU时间来等待操作的完成。一般说来,PCI卡通常都属于字符设备。

    所有已经注册(即已经加载了驱动程序)的硬件设备的主设备号可以从/proc/devices文件中得到。使用mknod命令可以创建指定类型的设备文件,同时为其分配相应的主设备号和次设备号。例如,下面的命令:

    [root@gary root]# mknod /dev/lp0 c 6 0

    将建立一个主设备号为6,次设备号为0的字符设备文件/dev/lp0。当应用程序对某个设备文件进行系统调用时,Linux内核会根据该设备文件的设备类型和主设备号调用相应的驱动程序,并从用户态进入到核心态,再由驱动程序判断该设备的次设备号,最终完成对相应硬件的操作。

    2. 设备驱动程序接口

    Linux中的I/O子系统向内核中的其他部分提供了一个统一的标准设备接口,这是通过include/linux/fs.h中的数据结构file_operations来完成的:

    struct file_operations {

    struct module *owner;

    loff_t (*llseek) (struct file *, loff_t, int);

    ssize_t (*read) (struct file *, char *, size_t, loff_t *);

    ssize_t (*write) (struct file *, const char *, size_t, loff_t *);

    int (*readdir) (struct file *, void *, filldir_t);

    unsigned int (*poll) (struct file *, struct poll_table_struct *);

    int (*ioctl) (struct inode *, struct file *, unsigned int, unsigned long);

    int (*mmap) (struct file *, struct vm_area_struct *);

    int (*open) (struct inode *, struct file *);

    int (*flush) (struct file *);

    int (*release) (struct inode *, struct file *);

    int (*fsync) (struct file *, struct dentry *, int datasync);

    int (*fasync) (int, struct file *, int);

    int (*lock) (struct file *, int, struct file_lock *);

    ssize_t (*readv) (struct file *, const struct iovec *, unsigned long, loff_t *);

    ssize_t (*writev) (struct file *, const struct iovec *, unsigned long, loff_t *);

    ssize_t (*sendpage) (struct file *, struct page *, int, size_t, loff_t *, int);

    unsigned long (*get_unmapped_area)(struct file *, unsigned long,

    unsigned long, unsigned long, unsigned long);

    };

    当应用程序对设备文件进行诸如open、close、read、write等操作时,Linux内核将通过file_operations结构访问驱动程序提供的函数。例如,当应用程序对设备文件执行读操作时,内核将调用file_operations结构中的read函数。

    3. 设备驱动程序模块

    Linux下的设备驱动程序可以按照两种方式进行编译,一种是直接静态编译成内核的一部分,另一种则是编译成可以动态加载的模块。如果编译进内核的话,会增加内核的大小,还要改动内核的源文件,而且不能动态地卸载,不利于调试,所有推荐使用模块方式。

    从本质上来讲,模块也是内核的一部分,它不同于普通的应用程序,不能调用位于用户态下的C或者C++库函数,而只能调用Linux内核提供的函数,在/proc/ksyms中可以查看到内核提供的所有函数。

    在以模块方式编写驱动程序时,要实现两个必不可少的函数init_module( )和cleanup_module( ),而且至少要包含和两个头文件。在用gcc编译内核模块时,需要加上-DMODULE -D__KERNEL__ -DLINUX这几个参数,编译生成的模块(一般为.o文件)可以使用命令insmod载入Linux内核,从而成为内核的一个组成部分,此时内核会调用模块中的函数init_module( )。当不需要该模块时,可以使用rmmod命令进行卸载,此进内核会调用模块中的函数cleanup_module( )。任何时候都可以使用命令来lsmod查看目前已经加载的模块以及正在使用该模块的用户数。

    4. 设备驱动程序结构

    了解设备驱动程序的基本结构(或者称为框架),对开发人员而言是非常重要的,Linux的设备驱动程序大致可以分为如下几个部分:驱动程序的注册与注销、设备的打开与释放、设备的读写操作、设备的控制操作、设备的中断和轮询处理。

    驱动程序的注册与注销

    向系统增加一个驱动程序意味着要赋予它一个主设备号,这可以通过在驱动程序的初始化过程中调用register_chrdev( )或者register_blkdev( )来完成。而在关闭字符设备或者块设备时,则需要通过调用unregister_chrdev( )或unregister_blkdev( )从内核中注销设备,同时释放占用的主设备号。

    设备的打开与释放

    打开设备是通过调用file_operations结构中的函数open( )来完成的,它是驱动程序用来为今后的操作完成初始化准备工作的。在大部分驱动程序中,open( )通常需要完成下列工作:

    检查设备相关错误,如设备尚未准备好等。

    如果是第一次打开,则初始化硬件设备。

    识别次设备号,如果有必要则更新读写操作的当前位置指针f_ops。

    分配和填写要放在file->private_data里的数据结构。

    使用计数增1。

    释放设备是通过调用file_operations结构中的函数release( )来完成的,这个设备方法有时也被称为close( ),它的作用正好与open( )相反,通常要完成下列工作:

    使用计数减1。

    释放在file->private_data中分配的内存。

    如果使用计算为0,则关闭设备。

    设备的读写操作

    字符设备的读写操作相对比较简单,直接使用函数read( )和write( )就可以了。但如果是块设备的话,则需要调用函数block_read( )和block_write( )来进行数据读写,这两个函数将向设备请求表中增加读写请求,以便Linux内核可以对请求顺序进行优化。由于是对内存缓冲区而不是直接对设备进行操作的,因此能很大程度上加快读写速度。如果内存缓冲区中没有所要读入的数据,或者需要执行写操作将数据写入设备,那么就要执行真正的数据传输,这是通过调用数据结构blk_dev_struct中的函数request_fn( )来完成的。

    设备的控制操作

    除了读写操作外,应用程序有时还需要对设备进行控制,这可以通过设备驱动程序中的函数ioctl( )来完成。ioctl( )的用法与具体设备密切关联,因此需要根据设备的实际情况进行具体分析。

    设备的中断和轮询处理

    对于不支持中断的硬件设备,读写时需要轮流查询设备状态,以便决定是否继续进行数据传输。如果设备支持中断,则可以按中断方式进行操作。

    三、PCI驱动程序实现

    1. 关键数据结构

    PCI设备上有三种地址空间:PCI的I/O空间、PCI的存储空间和PCI的配置空间。CPU可以访问PCI设备上的所有地址空间,其中I/O空间和存储空间提供给设备驱动程序使用,而配置空间则由Linux内核中的PCI初始化代码使用。内核在启动时负责对所有PCI设备进行初始化,配置好所有的PCI设备,包括中断号以及I/O基址,并在文件/proc/pci中列出所有找到的PCI设备,以及这些设备的参数和属性。

    Linux驱动程序通常使用结构(struct)来表示一种设备,而结构体中的变量则代表某一具体设备,该变量存放了与该设备相关的所有信息。好的驱动程序都应该能驱动多个同种设备,每个设备之间用次设备号进行区分,如果采用结构数据来代表所有能由该驱动程序驱动的设备,那么就可以简单地使用数组下标来表示次设备号。

    在PCI驱动程序中,下面几个关键数据结构起着非常核心的作用:

    pci_driver

    这个数据结构在文件include/linux/pci.h里,这是Linux内核版本2.4之后为新型的PCI设备驱动程序所添加的,其中最主要的是用于识别设备的id_table结构,以及用于检测设备的函数probe( )和卸载设备的函数remove( ):

    struct pci_driver {

    struct list_head node;

    char *name;

    const struct pci_device_id *id_table;

    int (*probe) (struct pci_dev *dev, const struct pci_device_id *id);

    void (*remove) (struct pci_dev *dev);

    int (*save_state) (struct pci_dev *dev, u32 state);

    int (*suspend)(struct pci_dev *dev, u32 state);

    int (*resume) (struct pci_dev *dev);

    int (*enable_wake) (struct pci_dev *dev, u32 state, int enable);

    };

    pci_dev

    这个数据结构也在文件include/linux/pci.h里,它详细描述了一个PCI设备几乎所有的硬件信息,包括厂商ID、设备ID、各种资源等:

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

    };

    2. 基本框架

    在用模块方式实现PCI设备驱动程序时,通常至少要实现以下几个部分:初始化设备模块、设备打开模块、数据读写和控制模块、中断处理模块、设备释放模块、设备卸载模块。下面给出一个典型的PCI设备驱动程序的基本框架,从中不难体会到这几个关键模块是如何组织起来的。

    /* 指明该驱动程序适用于哪一些PCI设备 */

    static struct pci_device_id demo_pci_tbl [] __initdata = {

    {PCI_VENDOR_ID_DEMO, PCI_DEVICE_ID_DEMO,

    PCI_ANY_ID, PCI_ANY_ID, 0, 0, DEMO},

    {0,}

    };

    /* 对特定PCI设备进行描述的数据结构 */

    struct demo_card {

    unsigned int magic;

    /* 使用链表保存所有同类的PCI设备 */

    struct demo_card *next;

    /* ... */

    }

    /* 中断处理模块 */

    static void demo_interrupt(int irq, void *dev_id, struct pt_regs *regs)

    {

    /* ... */

    }

    /* 设备文件操作接口 */

    static struct file_operations demo_fops = {

    owner: THIS_MODULE, /* demo_fops所属的设备模块 */

    read: demo_read, /* 读设备操作*/

    write: demo_write, /* 写设备操作*/

    ioctl: demo_ioctl, /* 控制设备操作*/

    mmap: demo_mmap, /* 内存重映射操作*/

    open: demo_open, /* 打开设备操作*/

    release: demo_release /* 释放设备操作*/

    /* ... */

    };

    /* 设备模块信息 */

    static struct pci_driver demo_pci_driver = {

    name: demo_MODULE_NAME, /* 设备模块名称 */

    id_table: demo_pci_tbl, /* 能够驱动的设备列表 */

    probe: demo_probe, /* 查找并初始化设备 */

    remove: demo_remove /* 卸载设备模块 */

    /* ... */

    };

    static int __init demo_init_module (void)

    {

    /* ... */

    }

    static void __exit demo_cleanup_module (void)

    {

    pci_unregister_driver(&demo_pci_driver);

    }

    /* 加载驱动程序模块入口 */

    module_init(demo_init_module);

    /* 卸载驱动程序模块入口 */

    module_exit(demo_cleanup_module);

    上面这段代码给出了一个典型的PCI设备驱动程序的框架,是一种相对固定的模式。需要注意的是,同加载和卸载模块相关的函数或数据结构都要在前面加上__init、__exit等标志符,以使同普通函数区分开来。构造出这样一个框架之后,接下去的工作就是如何完成框架内的各个功能模块了。

    3. 初始化设备模块

    在Linux系统下,想要完成对一个PCI设备的初始化,需要完成以下工作:

    检查PCI总线是否被Linux内核支持;

    检查设备是否插在总线插槽上,如果在的话则保存它所占用的插槽的位置等信息。

    读出配置头中的信息提供给驱动程序使用。

    当Linux内核启动并完成对所有PCI设备进行扫描、登录和分配资源等初始化操作的同时,会建立起系统中所有PCI设备的拓扑结构,此后当PCI驱动程序需要对设备进行初始化时,一般都会调用如下的代码:

    static int __init demo_init_module (void)

    {

    /* 检查系统是否支持PCI总线 */

    if (!pci_present())

    return -ENODEV;

    /* 注册硬件驱动程序 */

    if (!pci_register_driver(&demo_pci_driver)) {

    pci_unregister_driver(&demo_pci_driver);

    return -ENODEV;

    }

    /* ... */

    return 0;

    }

    驱动程序首先调用函数pci_present( )检查PCI总线是否已经被Linux内核支持,如果系统支持PCI总线结构,这个函数的返回值为0,如果驱动程序在调用这个函数时得到了一个非0的返回值,那么驱动程序就必须得中止自己的任务了。在2.4以前的内核中,需要手工调用pci_find_device( )函数来查找PCI设备,但在2.4以后更好的办法是调用pci_register_driver( )函数来注册PCI设备的驱动程序,此时需要提供一个pci_driver结构,在该结构中给出的probe探测例程将负责完成对硬件的检测工作。

    static int __init demo_probe(struct pci_dev *pci_dev, const struct pci_device_id *pci_id)

    {

    struct demo_card *card;

    /* 启动PCI设备 */

    if (pci_enable_device(pci_dev))

    return -EIO;

    /* 设备DMA标识 */

    if (pci_set_dma_mask(pci_dev, DEMO_DMA_MASK)) {

    return -ENODEV;

    }

    /* 在内核空间中动态申请内存 */

    if ((card = kmalloc(sizeof(struct demo_card), GFP_KERNEL)) == NULL) {

    printk(KERN_ERR "pci_demo: 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 = DEMO_CARD_MAGIC;

    /* 设置成总线主DMA模式 */

    pci_set_master(pci_dev);

    /* 申请I/O资源 */

    request_region(card->iobase, 64, card_names[pci_id->driver_data]);

    return 0;

    }

    4. 打开设备模块

    在这个模块里主要实现申请中断、检查读写模式以及申请对设备的控制权等。在申请控制权的时候,非阻塞方式遇忙返回,否则进程主动接受调度,进入睡眠状态,等待其它进程释放对设备的控制权。

    static int demo_open(struct inode *inode, struct file *file)

    {

    /* 申请中断,注册中断处理程序 */

    request_irq(card->irq, &demo_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;

    /* ... */

    }

    }

    }

    5. 数据读写和控制信息模块

    PCI设备驱动程序可以通过demo_fops 结构中的函数demo_ioctl( ),向应用程序提供对硬件进行控制的接口。例如,通过它可以从I/O寄存器里读取一个数据,并传送到用户空间里:

    static int demo_ioctl(struct inode *inode, struct file *file,

    unsigned int cmd, unsigned long arg)

    {

    /* ... */

    switch(cmd) {

    case DEMO_RDATA:

    /* 从I/O端口读取4字节的数据 */

    val = inl(card->iobae + 0x10);

    /* 将读取的数据传输到用户空间 */

    return 0;

    }

    /* ... */

    }

    事实上,在demo_fops里还可以实现诸如demo_read( )、demo_mmap( )等操作,Linux内核源码中的driver目录里提供了许多设备驱动程序的源代码,找那里可以找到类似的例子。在对资源的访问方式上,除了有I/O指令以外,还有对外设I/O内存的访问。对这些内存的操作一方面可以通过把I/O内存重新映射后作为普通内存进行操作,另一方面也可以通过总线主DMA(Bus Master DMA)的方式让设备把数据通过DMA传送到系统内存中。

    6. 中断处理模块

    PC的中断资源比较有限,只有0~15的中断号,因此大部分外部设备都是以共享的形式申请中断号的。当中断发生的时候,中断处理程序首先负责对中断进行识别,然后再做进一步的处理。

    static void demo_interrupt(int irq, void *dev_id, struct pt_regs *regs)

    {

    struct demo_card *card = (struct demo_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缓冲区指针等 */

    }

    7. 释放设备模块

    释放设备模块主要负责释放对设备的控制权,释放占用的内存和中断等,所做的事情正好与打开设备模块相反:

    static int demo_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;

    /* ... */

    }

    8. 卸载设备模块

    卸载设备模块与初始化设备模块是相对应的,实现起来相对比较简单,主要是调用函数pci_unregister_driver( )从Linux内核中注销设备驱动程序:

    static void __exit demo_cleanup_module (void)

    {

    pci_unregister_driver(&demo_pci_driver);

    }

    如何将驱动程序编译后加载进内核

    (1)编写Makefile文件

    makefile文件实例

    ifneq ($(KERNELRELEASE),)

    obj-m:=hello.o

    else

    #generate the path

    CURRENT_PATH:=$(shell pwd)

    #the absolute path

    LINUX_KERNEL_PATH:=/lib/modules/$(shell uname -r)/build

    #complie object

    default:

    make -C $(LINUX_KERNEL_PATH) M=$(CURRENT_PATH) modules

    clean:

    make -C $(LINUX_KERNEL_PATH) M=$(CURRENT_PATH) clean

    endif

    obj-m 表示该文件要作为模块编译  obj-y则表示该文件要编译进内核

    正常情况下只需修改hello.o即可

    (2)执行make命令生成 *.ko 文件

    (3)sudo insmod *.ko加载驱动模块

    (4)sudo rmmod *.ko卸载驱动模块

    (5)使用dmesg | tail -10来查看内核输出的最后十条信息

    (6)使用modinfo *.ko来查看模块信息

    PCI IO/内存地址区域

    一个PCI设备可实现多达6个I/O地址区域,每个区域既可以使内存也可以是I/O地址。在内核中PCI设备的I/O区域已经被集成到通用资源管理器。因此,我们无需访问配置变量来了解设备被映射到内存或者I/O空间的何处。获得区域信息的首选接口是下面的宏定义:

    #define pci_resource_start(dev, bar)((dev)->resource[(bar)].start)

    该宏返回六个PCI I/O区域之一的首地址(内存地址或者I/O端口号).该区域由整数的bar(base address register,基地址寄存器)指定,bar取值为0到5。

    #define pci_resource_end(dev, bar)((dev)->resource[(bar)].end)

    该宏返回第bar个I/O区域的首地址。注意这是最后一个可用的地址,而不是该区域之后的第一个地址。

    #define pci_resource_flags(dev, bar)((dev)->resource[(bar)].flags)

    该宏返回和该资源相关联的标志。资源标志用来定义单个资源的特性,对与PCI I/O区域相关的PCI资源,该信息从基地址寄存器中获得,但对于和PCI无关的资源,它可能来自其他地方。所有资源标志定义在。

    PCIE驱动下的DMA及中断

    (1)DMA循环缓冲区的分配与实现:

    对于高速数据信号的采集处理,需要在驱动程序的初始化模块(probe)中申请大量的DMA循环缓冲区,申请的大小直接关系着能否实时对高速数据处理的成败。直接内存访问(DMA)是一种硬件机制,允许外围设备和主内存直接直接传输I/O数据,避免了大量的计算开销。

    (2) Linux内核的内存分区段:

    三个区段,可用于DMA的内存,常规内存以及高端内存。

    ·        通常的内存分配发生在常规内存区,但是通过设置内存标识也可以请求在其他区段中分配。可用于DMA的内存指存在于特别地址范围内的内存,外设可以利用这些内存执行DMA访问,进行数据通信传输。

    ·        DMA循环缓冲区的分配要求:物理连续,DMA可以访问,足够大。

    (3)Linux内存分配函数:

    ·        Linux系统使用虚拟地址,内存分配函数提供的都是虚拟地址,通过virt_to_bus转换才能得到物理地址。

    ·        分配内核内存空间的函数:kmalloc实现小于128KB的内核内存申请,申请空间物理连续;__get_free_pages实现最大4MB的内存申请,以页为单位,所申请空间物理连续;vmalloc分配的虚拟地址空间连续,但在物理上可能不连续。

    ·        Linux内核中专门提供了用于PCI设备申请内核内存的函数pci_alloc_consistent,支持按字节长度申请,该函数调用__get_free_pages,故一次最大为4MB。

    (4) DMA数据传输的方式:

    ·        一种是软件发起的数据请求(例如通过read函数调用),另一种是硬件异步将数据传给系统。对于数据采集设备,即便没有进程去读取数据,也要不断写入,随时等待进程调用,因此驱动程序应该维护一个环形缓冲区,当read调用时可以随时返回给用户空间需要的数据。

    (5)PCIe中向CPU发起中断请求的方式:

    ·        消息信号中断(MSI),INTx中断。

    ·        在MSI中断方式下,设备通过向OS预先分配的主存空间写入特定数据的方式请求CPU的中断服务,为PCIe系统首选的中断信号机制,对于PCIe到PCI/PCI-X的桥接设备和不能使用MSI机制的传统端点设备,采用INTx虚拟中断机制。

    ·        PCIe设备注册中断时使用共享中断方式,Linux系统通过request_irq实现中断处理程序的注册,调用位置在设备第一次打开,硬件产生中断之前;同样,free_irq时机在最后一次关闭设备,硬件不用中断处理器之后。

    ·        中断处理函数的功能是将有关中断接收的信息反馈给设备,并对数据进行相应读写。中断信号到来,系统调用相应的中断处理函数,函数判断中断号是否匹配,若是,则清除中断寄存器相应的位,即在驱动程序发起新的DMA之前设备不会产生其他中断,然后进行相应处理。

    (6)数据读写和ioctl控制:

    ·        数据读写:应用进程不需要数据时,驱动程序动态维护DMA环形缓冲区,当应用进程请求数据,驱动程序通过Linux内核提供copy_from_user()/copy_to_user()实现内核态和用户态之间的数据拷贝。

    ·        硬件控制:用户空间经常回去请求设备锁门,报告错误信息,设置寄存器等,这些操作都通过ioctl支持,可以对PCIe卡给定的寄存器空间进行配置。

    (7)中断处理程序的注册:

    ·        中断号在BIOS初始化阶段分配并写入设备配置空间,然后Linux在建立pci_dev时从配置空间中读出该中断号并写入pci_dev的irq成员中,所以注册中断程序时直接从pci_dev中读取就行。

    ·        当设备发生中断,8259A将中断号发给CPU,CPU根据中断号找到中断处理程序,执行。

    (8)DMA数据传输机制的产生:

    ·        传统经典过程:数据到达网卡 -> 网卡产生一个中断给内核 -> 内核使用 I/O 指令,从网卡I/O区域中去读取数据。这种方式,当大流量数据到来时,网卡会产生大量中断,内核在中断上下文中,会浪费大量资源处理中断本身。

    ·        改进:NAPI,即轮询,即内核屏蔽中断,隔一定时间去问网卡,是否有数据。则在数据量小的情况下,这种方式会浪费大量资源。

    ·        另一个问题,CPU到网卡的I/O区域,包括I/O寄存器和I/O内存中读取,再放到系统物理内存,都占用大量CPU资源,做改进,即有了DMA,让网卡直接从主内存之间读写自己的I/O数据。

    ·        首先,内核在主内存中为收发数据建立一个环形的缓冲队列(DMA环形缓冲区),内核将这个缓冲区通过DMA映射,将这个队列交给网卡;网卡收到数据,直接放进环形缓冲区,即直接放到主内存,然后向系统产生中断;

    ·        内核收到中断,取消DMA映射,可以直接从主内存中读取数据。

    展开全文
  • linux启动过程中进入驱动加载阶段时首先通过函数platform_drc_probe()进行设备树信息提取

    一、架构分析

    PCI的驱动和设备匹配方式较为特殊,一般设备可以通过设备名来匹配,但是PCI比较特殊,它是通过设备的 vendor 、subvendor 、device 、subdevice 来匹配

    1.1 pci数据结构

    在内核中与PCI相关的结构体大概有pci_driver 、pci_bus_type 、pci_dev 、pci_bus、pci_slot、pci_host_bridge等

    1. pci_host_bridge
      Host Bridge连接CPU和PCI系统
    2. pci_bus
      pci_bus包括节点信息、父总线pci_bus、设备链表、设备操作函数等信息
    3. pci_bus_type
      与pci_bus不同,该结构体是设备总线驱动模型里的总线
    4. pci_driver
      pci设备驱动
    5. pci_dev
      描述PCI设备,比如PCI-to-PCI桥设备等
    6. pci_slot
      用于描述总线上的物理插槽

    详细参考链接:LoyenWang博客
    PCI体系结构的拓扑关系如下图所示,图中的不同数据结构用于来描述对应的模块:
    在这里插入图片描述

    为PCIe的mem访问空间配置 region outbound寄存器组在这里插入图片描述

    • 顶层的结构为pci_host_bridge,这个结构一般由Host驱动负责来初始化创建;
    • pci_host_bridge指向root bus,也就是编号为0的总线,在该总线下,可以挂接各种外设或物理slot,也可以通过PCI桥去扩展总线;

    二、代码分析

    linux启动过程中进入驱动加载阶段时首先通过函数platform_drv_probe()进行设备树信息提取,该函数会进一步调用probe(dev) —> cdns_pcie_host_probe(),即指向dev的驱动文件中的*_probe函数,例如lcadence开发的pcie控制器的驱动就指向cdns_pcie_host_probe(),从而执行该函数体里的内容。

    该函数的主要任务是分配并初始化一个pci_host_bridge结构,最终通过这个bridge去枚举PCI总线上的所有设备;

    2.1 devm_pci_alloc_host_bridge()

    分配并初始化一个基础的pci_hsot_bridge结构;

    2.2 解析设备树信息

    调用pci_host_bridge_priv()
    将设备树中寄存器信息及中断信息存储到rc结构体中

    2.3 cdns_pcie_init_phy()

    初始化pcie phy模块

    2.4 cdns_pcie_host_init()

    初始化pcie host controller模块

    2.4.1 pci_parse_request_of_pci_ranges()

    解析设备树中的总线范围和地址空间范围,包括reg、reg-names、range等信息,并且将硬件信息注册到操作系统中,

    2.4.2 cdns_pcie_host_init_root_port()

    初始化RC设备,配置rc bar configuration寄存器,配置成pcie桥模式

    2.4.3 cdns_pcie_host_init_address_translation()

    为PCIe的配置空间配置region0 outbound寄存器组
    为PCIe的mem访问空间配置 region1 outbound寄存器组
    为PCIe的io访问空间配置 region2 outbound寄存器组
    配置inbound寄存器组

    2.4 注册PCIe信息

    将设备信息、地址信息、pci操作函数注册到bridge结构体中

    2.5 pci_host_probe()

    2.5.1 pci_scan_root_bus_bridge()

    Pcie总线枚举。

    • 调用pci_register_host_bridge()注册host bridge数据结构

    • 调用pci_scan_child_bus()由bridge开始对PCI总线扫描并添加设备。该函数调用pci_scan_child_bus_extend(),即扫描host桥下面设备的主体函数,具体执行如下

    2.5.1.1 pci_scan_slot()

    循环扫描一个PCI slot下的所有设备,进而进行响应配置

    2.5.1.2 pci_scan_single_device() —— “probe.c”

    调用pci_get_slot(),即调用get_device(),增加device对象的计数器值;然后调用pci_scan_device(),扫描PCIe总线树;继而调用pci_device_add(),将扫描出来的设备添加到BUS数据结构中。
    其中最重要的是==pci_scan_device()==函数,具体功能包括调用pci_bus_read_dev_vendor_id()
    查询vendor id和device id(注:函数调用了pci_bus_read_config_dword()函数,在access.c中采用宏定义来实现,详细参考链接:pci_bus_read_config_dword);调用pci_alloc_dev(bus)给创建pci_dev并分配内存;调用pci_set_of_node(dev)建立pcie设备树节点;调用pci_setup_device(dev)配置和初始化设备,具体实现如下

    2.5.1.2.1 pci_hdr_type()

    读取配置空间的BIST, Header Type, Latency Timer and Cache Line Size Registers寄存器中header type字段(hdr),判断该设备类型。若是RC设备则该该值为1,若是EP设备该值为0

    2.5.1.2.2 set_pcie_port_type()
    1. 调用pcie_find_capability,进而调用__pci_bus_find_cap_start读取0x06寄存器确认是否为0x10,根据hdr返回PCI_CAPABILITY_LIST;
    2. 调用pci_find_next_cap_ttl查询下一个capability pointer的地址
    2.5.1.2.3 pci_dev_assign_slot()
    2.5.1.2.4 pci_class()
    1. 读0x8寄存器,读取class code
    2.5.1.2.5 early_dump_cpi_device()

    若设置pci_dump_pci_device,在boot阶段将会打印devic的配置空间

    2.5.1.2.6 pci_cfg_space_size()
    1. 如果class code>>8为06,即为桥设备,则调用pci_cfg_space_size_ext,读0x100寄存器判断是否是pcie设备,若是,则返回4KB的配置空间大小参数
    2.5.1.2.7 set_pcie_thunderbolt()
    1. 查询该设备的capability是否是Intel thunderbolt控制器
    2.5.1.2.8 pci_fixup_device()
    2.5.1.2.9 配置command寄存器,测试是否INTX disable
    2.5.1.2.10 根据header type值进行相应设备的处理
    • 如果是EP设备,则进行如下处理:
    1. pci_read_irq()
    2. pci_read_bases()
    • 如果是BRIDGE设备,则进行如下处理:
    1. pci_read_irq(),读中断相关寄存器
    2. pci_read_bases(),配置bar寄存器的值
    3. pci_read_bridge_windows(),配置io/memory访问属性
    4. set_pcie_hotplug_bridge()
    5. pci_find_capability()

    2.5.1.3 pci_iov_bus_range()

    2.5.1.4 pcibios_fixup_bus()

    2.5.1.5 pci_scan_bridge_extend()

    2.5.2 将PCI资源载入到iomem和ioport resource中

    如果定义了CONFIG PCI且如果PCI_PROBE_ONLY在pci_flag中已设置,则运行以下函数
    pci_bus_claim_resources()
    若未定义CONFIG PCI且pci_flag中未设置PCI_PROBE_ONLY位,则运行以下函数
    pci_bus_size_bridges()
    pci_bus_assign_resources()

    2.5.3 pci_bus_add_devices()

    全部设备扫描完毕,注册设备

    三、与PCIe相关的option

    展开全文
  • 要弄清楚Linux PCI设备驱动,首先要明白,所谓的Linux PCI设备驱动实际包括Linux PCI设备驱动和设备本身驱动两部分。不知道读者理不理解这句话,本人觉得这句话很重要,对于PCI、USB这样的驱动来说,必须要理解这个...
  • RK3568/RK3588 PCIE控制器介绍,常见问题分析
  • linux下PCI驱动源码实例1,该源码缺少pci_fops的初始化 #include <linux/fs.h> #include <linux/module.h> #include <linux/init.h> #include <linux/pci.h> #include <linux/interrupt...
  • Freesclae i.MX6 Linux PCIE驱动源码分析

    千次阅读 2018-10-30 17:14:13
    Freesclae i.MX6 Linux PCIE驱动源码分析   转自: http://www.lai18.com/content/2232856.html     最近需要做一个工具来测试PCIE的link是否成功,但是由于PCIE的驱动都是在内核空间中,因此需要首先分析...
  • 前几天实验了一下XILINX官方的提供的在UBUNTU平台下的PCIE驱动,以下文字做个简单记录和总结。时间有限简单写写,可能以后有补充。 1,在LINUX下安装驱动后在/dev/目录里面多了十多个xdma0_开头的设备。 设备一...
  • 包含枚举流程的原理和linux 枚举简要分析
  • 基于linux 的 PCI & PCIe 总线分析总结

    千次阅读 多人点赞 2019-02-02 19:10:41
    PCIe 总线分析总结 前言 讲解PCI &amp; PCIe 的书有很多,我手上就拿了一本《PCI Express 体系结构导读》的书,据说这本书基本是翻译了外文,书上虽然内容比较全面,但是书那么厚,想达到快速掌握的目的还有...
  • 标准linux休眠和唤醒机制分析 说明 1. Based on linux2.6.32, only for mem(SDR) 2. 有兴趣请先参考阅读 电源管理方案APM和ACPI比较.doc Linux系统的休眠与唤醒简介.doc 3. 本文先研究标准linux的休眠与唤醒android...
  • Linux平台PCIe驱动编写

    万次阅读 2019-03-21 10:27:58
    以前文章分析PCIe整个系统知识,包括如何扫描PCIE树,这篇文章讲解一下当拿到一个PCIe设备时如何编写驱动程序。编写驱动程序在应用程序中编写,同样可以在内核层编写驱动。 从应用层编写驱动主要是使用pcilib库和...
  • 分析所述PCIE错误信息的错误事件类型及属性信息,根据所述错误事件类型及属性信息,将所述PCIE错误信息整合为预定文件格式的PCIE错误事件,并存储至数据库。2.根据权利要求1所述的收集方法,其特征在于,所述初始化....
  • 1.采用第三方固件如PCIE WIFI 模块,这一类的驱动往往不需要自己写,直接采用内核自由的开源模块或者厂商提供三方模块源码即可 2.需要自己实现EP端的设备驱动,本文主要介绍此类驱动的实现原理 EP端的设备驱动,主要...
  • linux驱动】pcie驱动入门

    千次阅读 2021-01-22 16:51:28
    pcie总线向下兼容pci总线,文中所述pci默认等于pcie pcie拓扑结构 pcie拓扑主要由总线、桥和设备卡组成,桥将父总线与子总线连接在一起形成树型结构。桥主要分为一下三类: Host/PCI桥:用于连接CPU与PCI根总线...
  • linuxPCIE控制器设备树 学习

    千次阅读 2017-07-12 20:15:29
    本系列全部内容基于Synopsys DesignWare进行学习与分析 在内核3.x之后开始使用设备树,所有和硬件相关部分均放在设备树中,在进行PCIE控制器学习时,对官方文档中各个变量的使用存在很大的迷惑,根据自己了解...
  • 我现在设了一个结构体指针数组用来放每次不同设备申请的地址,但是设备节点出不来,出来的话需要再open那里打开设备数量的const_of,但是open函数参数不是应用层打开节点传...我不确定是否思路有问题,能否帮忙分析分析
  • pcie总线向下兼容pci总线,文中所述pci默认等于pcie pcie拓扑结构 pcie拓扑主要由总线、桥和设备卡组成,桥将父总线与子总线连接在一起形成树型结构。桥主要分为一下三类: Host/PCI桥:用于连接CPU与PCI根总线,...
  • Xilinx PCIe DMA Linux驱动代码分析

    千次阅读 多人点赞 2018-11-02 14:00:24
  • 前言 本文基于Linux kernel 4.19.0, 体系结构为aarch64. PCIe hotplug
  • pci-endpoint-test设备驱动 这个驱动是TI写的,用于TI芯片进行PCIE级联使用,其他的平台可以参考此驱动进行移植调试 该驱动对大家写PCIE EP的设备驱动提供了很好的帮助,它提供了一种EP框架思路,可以加快PCIE的调试...
  • 本文选取Xilinx的nwl-pcie进行分析。 驱动整体编写简单,足以覆盖现有框架,不会占用太多笔墨; 2.过程分析 说到驾驶的分析,离不开驾驶模型的引入,而驾驶模型的实现使得具体的驾驶开发更加容易; 所以我们来回顾...
  • 1. 所购买的PCIE转串口卡芯片为MCS9922,在官方网站上下载驱动MCS9900_Linux.tar.gz。2. 解压,阅读readme,发现是一般的安装方法,遂直接操作之。3. make,报错,分析后可知是由于驱动针对的内核版本太低,需要对源...
  • 背 景Read the fucking source code! --By 鲁迅A picture is worth a thousand words. --By 高尔基说明:Kernel版本:... 概述先回顾一下PCIe的架构图:本文将讲PCIe Host的驱动,对应为Root Complex部分,相当于PCI...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 3,375
精华内容 1,350
关键字:

linux pcie 分析