linux设备驱动之pci设备的IO和内存
- 格式:doc
- 大小:157.50 KB
- 文档页数:21
用I/O命令访问PCI总线设备配置空间pio_len = pci_resource_len (pdev, 0);mmio_start = pci_resource_start (pdev, 1);mmio_end = pci_resource_end (pdev, 1);mmio_flags = pci_resource_flags (pdev, 1);mmio_len = pci_resource_len (pdev, 1);这样看来0号bar就是提供的io映射,1号bar提供内存映射。
所以我想如果自己写一个以太网驱动的话,关于这块的代码就可以精简一下,只使用内存映射的方法就可以了。
下面是网上有人写的几种不同方式测试代码,他这些代码应该是在x86上试的,我没有试过,先分析,周末搞到arm上去跑一下。
[第一种]unsigned long mmio_start, addr1, addr2;void __iomem *ioaddr;mmio_start = pci_resource_start( pdev, 1);ioaddr = pci_iomap(pdev, 1, 0);addr1 = ioread32( ioaddr );addr2 = ioread32( ioaddr + 4 );printk(KERN_INFO "mmio start: %lX\n", mmio_start);printk(KERN_INFO "ioaddr: %p\n", ioaddr);printk(KERN_INFO "%02lX.%02lX.%02lX.%02lX.%02lX.%02lX\n",(addr1) & 0xFF,(addr1 >> 8) & 0xFF,(addr1 >> 16 ) & 0xFF,(addr1 >> 24 ) & 0xFF,(addr2) & 0xFF,(addr2 >> 8) & 0xFF );运行结果:Mar 10 22:34:56 localhost kernel: mmio start: E0000800Mar 10 22:34:56 localhost kernel: ioaddr: f8aa6800Mar 10 22:34:56 localhost kernel: 00.02.3F.AC.41.9D------------------------------------------------------------------------------------------------这种方法采用内存映射的方法,得到bar1的物理地址,mmio_start然后由pci_ioremap函数把bar1的这段地址映射到内核虚拟地址空间。
⼀、如何编写LinuxPCI驱动程序PCI的世界是⼴阔的,充满了(⼤部分令⼈不快的)惊喜。
由于每个CPU体系结构实现不同的芯⽚集,并且PCI设备有不同的需求(“特性”),因此Linux内核中的PCI⽀持并不像⼈们希望的那么简单。
这篇简短的⽂章介绍⽤于PCI设备驱动程序的Linux APIs。
1.1 PCI驱动程序结构PCI驱动程序通过pci_register_driver()在系统中"发现"PCI设备。
事实上,恰恰相反。
当PCI通⽤代码发现⼀个新设备时,具有匹配“描述”的驱动程序将被通知。
详情如下。
pci_register_driver()将设备的⼤部分探测留给PCI层,并⽀持在线插⼊/删除设备[因此在单个驱动程序中⽀持热插拔PCI、CardBus和Express-Card]。
pci_register_driver()调⽤需要传⼊⼀个函数指针表,从⽽指⽰驱动程序的更⾼⼀级结构体。
⼀旦驱动程序知道了⼀个PCI设备并获得了所有权,驱动程序通常需要执⾏以下初始化:启⽤设备请求MMIO / IOP资源设置DMA掩码⼤⼩(⽤于⼀致性DMA和流式DMA)分配和初始化共享控制数据(pci_allocate_coherent())访问设备配置空间(如果需要)注册IRQ处理程序(request_irq())初始化non-PCI(即LAN/SCSI/等芯⽚部分)启⽤DMA /处理引擎当使⽤设备完成时,可能需要卸载模块,驱动程序需要采取以下步骤:禁⽌设备产⽣irq释放IRQ (free_irq())停⽌所有DMA活动释放DMA缓冲区(包括流式DMA和⼀致性DMA)从其他⼦系统注销(例如scsi或netdev)释放MMIO / IOP资源禁⽤该设备下⾯⼏节将介绍这些主题中的⼤部分。
其余部分请查看LDD3或<linux/pci.h>。
如果PCI⼦系统没有配置(没有设置CONFIG_PCI),下⾯描述的⼤多数PCI函数都被定义为内联函数,要么完全空,要么只是返回⼀个适当的错误代码,以避免在驱动程序中出现⼤量ifdefs。
Linux命令行中的硬件信息查看和驱动管理在Linux命令行中,我们可以通过一些命令来查看硬件信息和管理驱动,这对于系统维护和故障排除非常重要。
本文将介绍几个常用的命令及其用法,帮助您快速获取硬件信息和管理驱动。
1. 查看硬件信息1.1 lshw命令lshw(或者lswhw)是一个用于查看硬件信息的命令,可以列出系统中所有硬件的详细信息,包括处理器、内存、硬盘、网卡等。
使用示例:```$ sudo lshw```运行以上命令后,您将看到完整的硬件信息列表,可以通过滚动查看或者使用管道和grep命令过滤感兴趣的部分。
1.2 lspci命令lspci命令用于列出系统中所有PCI设备的信息,包括显卡、网卡、声卡等。
使用示例:```$ lspci```该命令会输出PCI设备的详细信息,可以通过管道和grep进行过滤。
1.3 lsusb命令lsusb命令用于列出系统中所有USB设备的信息。
使用示例:```$ lsusb```该命令会输出USB设备的详细信息,可以通过管道和grep进行过滤。
2. 管理驱动2.1 modprobe命令modprobe命令用于加载和卸载Linux内核模块,包括驱动程序。
使用示例:```$ sudo modprobe <module_name> // 加载模块$ sudo modprobe -r <module_name> // 卸载模块```其中,`<module_name>`为要加载或卸载的模块名称。
2.2 lsmod命令lsmod命令用于列出当前已加载的内核模块。
使用示例:```$ lsmod```该命令会输出已加载模块的列表,包括模块名称、使用次数等信息。
2.3 rmmod命令rmmod命令用于卸载已加载的内核模块。
使用示例:```$ sudo rmmod <module_name>```其中,`<module_name>`为要卸载的模块名称。
PCie驱动Pcie设备上有三种地址空间:PCI的I/O空间、PCI的存储空间和PCI的配置空间。
Pce的配置空间:PCI有三个相互独立的物理地址空间:设备存储器地址空间、I/O地址空间和配置空间。
配置空间是PCI 所特有的一个物理空间。
由于PCI支持设备即插即用,所以PCI设备不占用固定的内存地址空间或I/O地址空间,而是由操作系统决定其映射的基址。
系统加电时,BIOS检测PCI总线,确定所有连接在PCI总线上的设备以及它们的配置要求,并进行系统配置。
所以,所有的PCI设备必须实现配置空间,从而能够实现参数的自动配置,实现真正的即插即用。
PCI总线规范定义的配置空间总长度为256个字节,配置信息按一定的顺序和大小依次存放。
前64个字节的配置空间称为配置头,对于所有的设备都一样,配置头的主要功能是用来识别设备、定义主机访问PCI卡的方式(I/O访问或者存储器访问,还有中断信息)。
其余的192个字节称为本地配置空间,主要定义卡上局部总线的特性、本地空间基地址及范围等。
一般来说,基于pcie总线的驱动,需要涉及到pci_driverpci_devpci_device_id.pci_device_id:用于标识pcie设备,通过上图的厂商Id设备Id功能号等唯一确定一个pcie设备,内核通过这个结构体确认驱动与设备是否匹配。
pci_dev:一般pcie设备都具有热拔插功能,当内核检测到有pcie设备插入时,会与相应的Pci_driver:当有相应的设备匹配会调用驱动的相关方法,驱动中通常要做的是读出Base AdrressRegister1-6的值,这是pcies设备6个内存空间的基地址,然后通过ioremap方法映射成虚拟地址,至于6个内存空间的具体含义需要依赖于设备。
在用模块方式实现PCI设备驱动程序时,通常至少要实现以下几个部分:初始化设备模块、设备打开模块、数据读写和控制模块、中断处理模块、设备释放模块、设备卸载模块。
⏹ Linux 网络设备驱动结构Linux的加载和卸载设备的注册初始化和注销设备的打开和释放据包的发送和接收络连接状况数设置和统计数据此驱动所支持的网卡系列初始化网络设备注销网络设备设备挂起函数设备恢复函数打开网络设备关闭网络设备读取包的网卡收发包的状态,统计数据用户的ioctl 命令系统调用硬件处理数据包发送ISR 数据包发送和接收⏹ struct pci_driver如果网络设备(包括wireless )是PCI 规范的,则先是向内核注册该PCI 设备(pci_register_driver),然后由pci_driver 数据结构中的probe 函数指针所指向的侦测函数来初始化该PCI 设备,并且同时注册和初始化该网络设备。
如果网络设备(包括wireless )是PCMCIA 规范的,则先是向内核注册该PCMCIA 设备(register_pccard_driver),然后driver_info_t 数据结构中的attach 函数指针所指向的侦测函数来初始化该PCMCIA 设备,并且同时注册和初始化该网络设备。
1. 申明为PCI 设备:static struct pci_driver tg3_driver = {.name = DRV_MODULE_NAME,//此驱动所支持的网卡系列,vendor_id, device_id.id_table = tg3_pci_tbl,//初始化网络设备的回调函数.probe = tg3_init_one,//注销网络设备的回调函数.remove = __devexit_p(tg3_remove_one),//设备挂起函数.suspend = tg3_suspend,//设备恢复函数.resume = tg3_resume};2. 驱动模块的加载和卸载static int __init tg3_init(void){//先注册成PCI设备,并初始化,如果是其他的ESIA,PCMCIA,用其他函数return pci_module_init(&tg3_driver);}static void __exit tg3_cleanup(void){pci_unregister_driver(&tg3_driver);//注销PCI设备}module_init(tg3_init); //驱动模块的加载module_exit(tg3_cleanup); //驱动模块的卸载3. PCI设备探测函数probe,初始化网络设备主要工作:申请并设置pci资源(内存),申请并设置net_device网络设备结构,IO映射,注册网络设备static int __devinit tg3_init_one(struct pci_dev *pdev, const struct pci_device_id *ent){//初始化设备,使I/O,memory可用,唤醒设备pci_enable_device(pdev);//申请内存空间,配置网卡的I/O,memory资源pci_request_regions(pdev, DRV_MODULE_NAME);pci_set_master(pdev);//设置DMA属性pci_set_dma_mask(pdev, (u64) 0xffffffffffffffff);//网卡 I/O,memory资源的启始地址tg3reg_base = pci_resource_start(pdev, 0);//网卡I/O,memory资源的大小tg3reg_len = pci_resource_len(pdev, 0);//分配并设置网络设备dev = alloc_etherdev(sizeof(*tp));//申明为内核设备模块SET_MODULE_OWNER(dev);//初始化私有结构中的各成员值tp = dev->priv;tp->pdev = pdev;tp->dev = dev;……//锁的初始化spin_lock_init(&tp->lock);//映射I/O,memory地址到私有域中的寄存器结构tp->regs = (unsigned long) ioremap(tg3reg_base, tg3reg_len);dev->irq = pdev->irq;//网络设备回调函数赋值dev->open = tg3_open;dev->stop = tg3_close;dev->get_stats = tg3_get_stats;dev->set_multicast_list = tg3_set_rx_mode;dev->set_mac_aDDRess = tg3_set_mac_addr;dev->do_ioctl = tg3_ioctl;dev->tx_timeout = tg3_tx_timeout;dev->hard_start_xmit= tg3_start_xmit;//网卡的MAC地址赋值dev->addrtg3_get_device_address(tp);//注册网络设备register_netdev(dev);//把网络设备指针地址放入PCI设备中的设备指针中pci_set_drvdata(pdev, dev);}4. 注销网络设备主要工作:注销并释放网络设备,取消地址映射,释放PCI资源static void __devexit tg3_remove_one(struct pci_dev *pdev){struct net_device *dev = pci_get_drvdata(pdev);//注销网络设备unregister_netdev(dev);//取消地址映射iounmap((void *) ((struct tg3 *)(dev->priv))->regs);//释放网络设备kfree(dev);//释放PCI资源pci_release_regions(pdev);//停用PCI设备pci_disable_device(pdev);//PCI设备中的设备指针赋空pci_set_drvdata(pdev, NULL);}5. 网络设备挂起主要工作:停用网卡的中断寄存器,停止网卡收发包,停用网卡某些硬件,设置电源状态static int tg3_suspend(struct pci_dev *pdev, u32 state){//停用网卡的中断寄存器tg3_disable_ints(tp);//停止网卡收发包netif_device_detach(dev);//停止网卡某些硬件,fireware的一些功能tg3_halt(tp);//设置网卡的电源状态tg3_set_power_state(tp, state);}6. 网络设备恢复主要工作:恢复网卡电源,允许收发包,初始化收发包的缓冲区,初始化网卡硬件,打开网卡中断寄存器static int tg3_resume(struct pci_dev *pdev){//恢复网卡电源tg3_set_power_state(tp, 0);//允许网卡收发包netif_device_attach(dev);//初始化收发包的缓冲区tg3_init_rings(tp);//初始化网卡硬件tg3_init_hw(tp);//打开网卡中断寄存器tg3_enable_ints(tp);}struct net_device1. 打开网络设备主要工作:分配中断及注册中断处理函数,初始化硬件及收发缓冲区,初始化定时器及注册超时函数,允许网卡开始传送包static int tg3_open(struct net_device *dev){//分配一个中断request_irq(dev->irq, tg3_interrupt, SA_SHIRQ, dev->name, dev);/* int request_irq(unsigned int irq,void (*handler)(int irq, void *dev_id, struct pt_regs *regs),unsigned long irqflags,const char * devname,void *dev_id);irq是要申请的硬件中断号。
linux中pcie设备初始化流程PCIe(Peripheral Component Interconnect Express)是一种高速串行总线接口,用于连接计算机主板和外部设备。
在Linux系统中,PCIe设备的初始化是一个重要的过程,它确保设备能够正常工作并与系统进行通信。
本文将介绍Linux中PCIe设备初始化的流程。
1. 设备检测与识别在Linux系统启动时,内核会进行设备检测与识别的过程。
对于PCIe设备,内核会扫描PCIe总线,识别连接在总线上的设备。
这个过程是通过读取PCIe设备的配置空间来完成的。
配置空间是一块特殊的内存区域,包含了设备的各种信息,如设备ID、厂商ID、设备类别等。
2. 分配资源一旦设备被识别,内核会为其分配必要的资源,如内存空间、中断线等。
这些资源的分配是通过解析设备的配置空间来完成的。
内核会根据设备的需求和系统的可用资源进行分配,以确保设备能够正常工作。
3. 驱动加载设备的驱动程序是用来控制和管理设备的软件模块。
在Linux系统中,驱动程序是以内核模块的形式存在的。
一旦设备被识别并分配了资源,内核会加载与之对应的驱动程序。
驱动程序会与设备进行通信,配置设备的寄存器、中断等,并提供相应的接口供用户空间程序使用。
4. 设备初始化设备初始化是指对设备进行一系列的配置和初始化操作,以使其能够正常工作。
设备初始化的具体过程是由设备的驱动程序来完成的。
驱动程序会根据设备的特性和需求,对设备进行相应的配置和初始化。
这包括设置设备的工作模式、参数、中断处理等。
5. 设备注册设备注册是将设备与系统进行关联的过程。
在Linux系统中,设备注册是通过设备模型来完成的。
设备模型是一种用于描述和管理设备的框架,它提供了一套标准的接口和方法,用于设备的注册、管理和访问。
设备注册的过程包括将设备添加到设备模型中,并分配设备号等。
6. 设备启动设备启动是指设备开始正常工作的过程。
在Linux系统中,设备启动是由设备的驱动程序来完成的。
【原创】LinuxPCI驱动框架分析(一)背景•Read the fucking source code! --By 鲁迅•A picture is worth a thousand words. --By 高尔基说明:1.Kernel版本:4.142.ARM64处理器3.使用工具:Source Insight 3.5, Visio1. 概述从本文开始,将会针对PCIe专题来展开,涉及的内容包括:1.PCI/PCIe总线硬件;2.Linux PCI驱动核心框架;3.Linux PCI Host控制器驱动;不排除会包含PCIe外设驱动模块,一切随缘。
作为专题的第一篇,当然会先从硬件总线入手。
进入主题前,先讲点背景知识。
在PC时代,随着处理器的发展,经历了几代I/O总线的发展,解决的问题都是CPU主频提升与外部设备访问速度的问题:1.第一代总线包含ISA、EISA、VESA和Micro Channel等;2.第二代总线包含PCI、AGP、PCI-X等;3.第三代总线包含PCIe、mPCIe、m.2等;PCIe(PCI Express)是目前PC和嵌入式系统中最常用的高速总线,PCIe在PCI的基础上发展而来,在软件上PCIe与PCI是后向兼容的,PCI的系统软件可以用在PCIe系统中。
本文会分两部分展开,先介绍PCI总线,然后再介绍PCIe总线,方便在理解上的过渡,开始旅程吧。
2. PCI Local Bus2.1 PCI总线组成•PCI总线(Peripheral Component Interconnect,外部设备互联),由Intel公司提出,其主要功能是连接外部设备;•PCI Local Bus,PCI局部总线,局部总线技术是PC体系结构发展的一次变革,是在ISA总线和CPU总线之间增加的一级总线或管理层,可将一些高速外设,如图形卡、硬盘控制器等从ISA总线上卸下,而通过局部总线直接挂接在CPU总线上,使之与高速CPU总线相匹配。
linux设备驱动之pci设备的I/O和内存------------------------------------------Pci设备的I/O和内存是一个比较复杂的问题.如下的总线结构:在上图的总线结构中,ethernet设备和pci-pci bridge的同类型资源空间必须要是pci bus0的一个子集例如,pci bus 0的I/O端口资源是0x00CC~0x01CC. Ethernet设备的I/O范围的是0x00CC~0x0xE0.那么pci-pci bridge的I/O端口范围就必须要在0x0xE0~0x01CC之间. 同样,SCSI和VIDEO同类型资源必须要是pci_bus1的子集.pci bus1上有一个pci桥,对应的资源也就是它所连桥上的资源.即pci_bus->self.也就是说,下层总线的资源是它上层总线资源的子集。
上层总线资源是下层总线资源的父集。
其实,每个PCI设备的资源地始地址都是由操作系统设置的.在x86上,都由bios设置好了.假若没有bios的时候,我们应该怎么去设置设备的资源起始范围呢?可能在pci枚举完成之后:1:从根总线开始,设置根总线的资源范围是从0开始,到0xFFFF或者0xFFFFFFFF的最大范围. 2:对其它的设备,可往其资源寄存器全部写入1,就可以求得该资源项的类型和长度.3:设备从根总线的资源那里分得对应长度的资源.4:如果设备是pci-pci bridge,则递归配置它.可能有人会有这样迷惑,对应于上图,如果pci-pci bridge的资源大小是N.而SCSI和video资源范围超过了N怎么办呢?我们必须要注意一点,总线的区间是可以自已设定的,而设备资源的区间是在设计的时候就已经确定好了.也就是说,我们可以更改pci device区间的起始地址,但我们不能改变它的大小.因此,出现了上面所说的这种情况.可能是由bios在处理PCI的时候出现了BUG.我们需要调整总线的资源区间.其实对于pci_bus的资源范围就是它的过滤窗口.对于过滤窗口的作用,我们在枚举的时候分析的很清楚了.CPU访问PC过程是这样的(只有一个根总线和pci-pci bridge过滤窗口功能打开的情况): 1:cpu向pci发出一个I/O请求.首先经过根总线.它会判断是否在它的资源范围内.如果在它的范围,它就会丢向总线所在的每一个设备.包括pci bridge. 如果没有在根总线的资源范围,则不会处理这个请求.2:如果pci设备判断该地址属于它的资源范围,则处理后发出应答4:pci bridge接收到这个请求,它会判断I/O地址是否在它的资源范围内.如果在它的范围,它会把它丢到它的下层子线.5:下层总线经过经过相同的处理后,就会找到这个PCI设备了一个PCI设备访问其它PCI设备或者其它外设的过程:1:首先这个PCI发出一个请求,这个请求会在总线上广播2:如果要请求的设备是在同级总线,就会产生应答3:请求的设备不是在同层总线,就会进行pci bridge.pci桥判断该请求不在它的范围内(目的地不是它下层的设备),就会将它丢向上层.4:这样辗转之后,就能找到对应的设备了经过这样的分析过来,相信对pci bridge的过滤窗口有更深的理解了.Linux中使用struct resource的结构来表示I/O端口或者是设备内存。
定义如下:struct resource {resource_size_t start;resource_size_t end;const char *nam e;unsigned long flags;struct resource *parent, *sibling, *child;};Start: 表示它所占资源的起始地址。
End: 表示它所占资源的未尾地址Name: 所占资源的名字Flags: 资源的类型。
目前有I/O和内存两种Parent.sibling.child:用来表示资源的所属关系。
分别表示它的父结点,兄弟结点和子结点。
从前面的分析可以看到,有一些总线可能bios没有遍历到或许bios的处理有错误,所以需要对整个系统的PCI总线和PCI设备的资源占用情况遍历一次。
完整的建立上述的struct resource 结构(在之前枚举的时候,只是处理了start和end成员).。
这个过程是在pcibios_resource_survey( )完成的。
如下所示:subsys_initcall(pcibios_init);static int __init pcibios_init(void){………….pcibios_resource_survey();}pcibios_init这个函数是被fs_initcall()所描述的。
在kernel启动的时候,会调用宏所描述的函数。
在pcibios_init ()又会调用pcibios_assign_resources(),它的代码如下所示:void __init pcibios_resource_survey(void){DBG("PCI: Allocating resources\n");pcibios_allocate_bus_resources(&pci_root_buses);pcibios_allocate_resources(0);pcibios_allocate_resources(1);}它先对总线的资源进行处理。
然后再对PCI设备的资源进行处理。
我们先看pcibios_allocate_bus_resources()static void __init pcibios_allocate_bus_resources(struct list_head *bus_list){struct pci_bus *bus;struct pci_dev *dev;int idx;struct resource *r, *pr;/* Depth-First Search on bus tree */list_for_each_entry(bus, bus_list, node) {//pci-bridgeif ((dev = bus->self)) {for (idx = PCI_BRIDGE_RESOURCES;idx < PCI_NUM_RESOURCES; idx++) {r = &dev->resource[idx];if (!r->flags)continue;pr = pci_find_parent_resource(dev, r);if (!r->start || !pr ||request_resource(pr, r) < 0) {printk(KERN_ERR "PCI: Cannot allocate ""resource region %d ""of bridge %s\n",idx, pci_name(dev));/** Som ething is wrong with the region.* Invalidate the resource to prevent* child resource allocations in this* range.*/r->flags = 0;}}}pcibios_allocate_bus_resources(&bus->children);}}这个是一个深度优先搜索算法。
类似的算法在pci结构中用得很多。
它遍历pci_root_buses中的每一个根总线下面的所有总线。
如果该总线有对应的pci-pci bridge,就先处理这个pci桥的资源.PCI桥的资源范围是PCI_BRIDGE_RESOURCES~ PCI_NUM_RESOURCES.对于它的每个资源区间。
都要从它的上层总线中获得.代码中遍历PCI桥的每一个资源区间,然后找到它在上层总线的对应区间。
然后为它建立结构关系。
pci_find_parent_resource()就是为PCI的资源区间找到一个合适的父结点。
代码如下:struct resource *pci_find_parent_resource(const struct pci_dev *dev, struct resource *res){const struct pci_bus *bus = dev->bus;int i;struct resource *best = NULL;for(i = 0; i < PCI_BUS_NUM_RESOURCES; i++) {struct resource *r = bus->resource[i];if (!r)continue;if (res->start && !(res->start >= r->start && res->end <= r->end)) continue; /* Not contained */if ((res->flags ^ r->flags) & (IORESOURCE_IO | IORESOURCE_MEM)) continue; /* Wrong type */if (!((res->flags ^ r->flags) & IORESOURCE_PREFETCH))return r; /* Exact m atch */if ((res->flags & IORESOURCE_PREFETCH) && !(r->flags & IORESOURCE_PREFETCH))best = r; /* Approximating prefetchable by non-prefetchable */}return best;}首先从pci_dev ->bus就找到了它的上层总线,每条总线拥有PCI_BUS_NUM_RESOURCES 个资源区间. 所要寻找的父结点必须要满足以后几个条件:1:子结点的范围必须要落在父结点的区间范围内2:父子区间的基本类型应该一致。
(基本类型即IO或者内存)3:如果父子窗口都是可预读的,就完全匹配了4:如果子结点可预读,而父结点不可预读。
也将就着可以了.注意:父结点可预读而子结点不可预读是不允许的。
找到它所属的父结点之后,会调用request_resource()从父结点中请求资源。