<s id="mvh2b"><strike id="mvh2b"><u id="mvh2b"></u></strike></s>
    1. <rp id="mvh2b"></rp>

      当前位置:首页 > IT教程

      pci驱动开发详解

      时间:2021-08-10 09:51:04来源:金橙教程网 作者:admin8 阅读:53次 [手机版]
       

      pci驱动

      一、在了解pic启动开发前,作为开发人员需了解以下内核结构体:

      struct pci_device_id {

      ? ? __u32 vendor, device;/* Vendor and device ID or PCI_ANY_ID*/

      ? ? __u32 subvendor, subdevice;/* SubSystem ID's or PCI_ANY_ID */

      ? ? __u32 class, class_mask;/* (class,subclass,Prog-if) triplet */

      ? ? kernel_ulong_t driver_DATa;/* Data private to the driver */

      ? ? };

      vendor:标识硬件制造商,是一个16位的寄存器。

      device:设备ID,由制造商选择,也是一个16位的寄存器。一般与厂商ID配对生成一个唯一的32位硬件设备标识符。

      class:每个外部设备属于某个类(class),也是一个16位的寄存器。当某个驱动程序可支持多个相似的设备,

      每个具有不同的签名,但都属于同一个类,这时,就可以用class类对它们的外设进行识别。

      该结构用来区分各个设备。(即该驱动程序控制的设备)

      每个驱动程序可控制多个设备,这时用数组

      ? ? staticstruct pci_device_id example_pci_tbl [] __initdata ={

      ? ? {PCI_VENDOR_ID_EXAMPLE, PCI_DEVICE_ID_EXAMPLE, PCI_ANY_ID, PCI_ANY_ID,0,0, EXAMPLE},

      ? ? {0,}

      ? ? };

      注意:不管你这里匹配了多少设备,记得最后一个都是{0,}。

      介绍下面两个宏

      PCI_DEVICE(vendor, device)

      ? ? ? ? 创建一个仅和特定厂商及设备ID相匹配的struct pci_device_id。它把结构体的subvendor和subdevice设为PCI_ANY_ID。PCI_ANY_ID定义如下:

      ? ? #define PCI_ANY_ID (~0)

      PCI_DEVICE_CLASS(device_class, device_class_mask)

      ? ? ? ? 创建一个和特定PCI类相匹配的struct pci_device_id。

      struct pci_driver {

      ? ? struct List_head node;

      ? ? char*name;

      ? ? conststruct pci_device_id *id_table;/* must be non-NULL for probe to be called */

      ? ? int(*probe)(struct pci_dev *dev,conststruct pci_device_id *id);/* New device inserted */

      ? ? void(*remove)(struct pci_dev *dev);/* Device removed (NULL if not a hot-plug capable driver) */

      ? ? int(*suspend)(struct pci_dev *dev,pm_message_t state);/* Device suspended */

      ? ? int(*suspend_late)(struct pci_dev *dev,pm_message_t state);

      ? ? int(*resume_early)(struct pci_dev *dev);

      ? ? int(*resume)(struct pci_dev *dev);/* Device woken up */

      ? ? void(*shutdown)(struct pci_dev *dev);

      ? ? struct pci_ERROR_handlers *err_handler;

      ? ? struct device_driver driver;

      ? ? struct pci_dynids dynids;

      };

      可以认为pci_driver结构体对象就表示一个驱动,重点说明的成员如下:

      id_table: 表示该驱动程序控制的所有设备

      probe: 当驱动程序与设备匹配后,就会调用probe指向的函数。(通常在函数里激活设备,占用I/O端口和内存)

      remove: 指向卸载设备函数

      在pci_driver结构体里有包含struct device_driver,下面有说明。

      在驱动程序开发里,device_driver里包含总线信息

      struct pci_dev {

      ? ? struct list_head bus_list;/* node in per-bus list */

      ? ? struct pci_bus *bus;/* bus this device is on */

      ? ? struct pci_bus *subordinate;/* bus this device bridges to */

      ? ? ?

      ? ? void*sysdata;/* hook for sys-specific extension */

      ? ? struct proc_dir_entry *procent;/* device entry in /proc/bus/pci */

      ? ? struct pci_slot *slot;/* Physical slot this device is in */

      ? ? ?

      ? ? unsignedint devfn;/* encoded device & function index */

      ? ? unsignedshort vendor;

      ? ? unsignedshort device;

      ? ? unsignedshort subsystem_vendor;

      ? ? unsignedshort subsystem_device;

      ? ? unsignedintclass;/* 3 bytes: (base,sub,prog-if) */

      ? ? u8 revision;/* PCI revision, low byte of class word */

      ? ? u8 hdr_type;/* PCI header type (`multi' flag masked out) */

      ? ? u8 pcie_cap;/* PCI-E capability offset */

      ? ? u8 pcie_type;/* PCI-E device/port type */

      ? ? u8 ROM_base_reg;/* which config register controls the ROM */

      ? ? u8 pin;/* which interrupt pin this device uses */

      ? ? ?

      ? ? struct pci_driver *driver;/* which driver has allocated this device */

      ? ? u64 dma_mask;/* Mask of the bits of bus address this

      ? ? device implements. Normally this is

      ? ? 0xffffffff. You only need to change

      ? ? this if your device has broken DMA

      ? ? or supports 64-bit transfers. */

      ? ? ?

      ? ? struct device_dma_parameters dma_parms;

      ? ? ?

      ? ? pci_power_t current_state;/* Current operating state. In ACPI-speak,

      ? ? this is D0-D3, D0 being fully functional,

      ? ? and D3 being off. */

      ? ? int pm_cap;/* PM capability offset in the

      ? ? configuration space */

      ? ? unsignedint pme_support:5;/* Bitmask of states from which PME#

      ? ? can be generated */

      ? ? unsignedint pme_interrupt:1;

      ? ? unsignedint d1_support:1;/* Low power state D1 is supported */

      ? ? unsignedint d2_support:1;/* Low power state D2 is supported */

      ? ? unsignedint no_d1d2:1;/* Only allow D0 and D3 */

      ? ? unsignedint mmio_always_on:1;/* disallow turning off io/mem

      ? ? decoding during bar sizing */

      ? ? unsignedint wakeup_prepared:1;

      ? ? unsignedint d3_delay;/* D3->D0 transition time in ms */

      ? ? ?

      ? ? #ifdef CONFIG_PCIEaspM

      ? ? struct pcie_link_state *link_state;/* ASPM link state. */

      ? ? #endif

      ? ? ?

      ? ? pci_channel_state_t error_state;/* current connectivity state */

      ? ? struct device dev;/* Generic device interface */

      ? ? ?

      ? ? int cfg_size;/* Size of configuration space */

      ? ? ?

      ? ? /*

      ? ? * Instead of touching interrupt line and base address registers

      ? ? * directly, use the values stored here. They might be different!

      ? ? */

      ? ? unsignedint irq;

      ? ? struct resource resource[DEVICE_COUNT_RESOURCE];/* I/O and memory regions + expansion ROMs */

      ? ? resource_size_t fw_addr[DEVICE_COUNT_RESOURCE];/* FW-assigned addr */

      ? ? ?

      ? ? /* These fields are used by common fixups */

      ? ? unsignedint transparent:1;/* Transparent PCI bridge */

      ? ? unsignedint multifunction:1;/* Part of multi-function device */

      ? ? /* keep track of device state */

      ? ? unsignedint is_added:1;

      ? ? unsignedint is_busmaster:1;/* device is busmaster */

      ? ? unsignedint no_msi:1;/* device may not use msi */

      ? ? unsignedint block_ucfg_Access:1;/* userspace config space access is blocked */

      ? ? unsignedint broken_parity_Status:1;/* Device generates false positive parity */

      ? ? unsignedint irq_reroute_variant:2;/* device needs IRQ rerouting variant */

      ? ? unsignedint msi_enabled:1;

      ? ? unsignedint msix_enabled:1;

      ? ? unsignedint ari_enabled:1;/* ARI forwarding */

      ? ? unsignedint is_managed:1;

      ? ? unsignedint is_pcie:1;/* Obsolete. Will be removed.

      ? ? Use pci_is_pcie() instead */

      ? ? unsignedint needs_freset:1;/* Dev requires fundamental reset */

      ? ? unsignedint state_saved:1;

      ? ? unsignedint is_physfn:1;

      ? ? unsignedint is_virtfn:1;

      ? ? unsignedint reset_fn:1;

      ? ? unsignedint is_hotplug_bridge:1;

      ? ? unsignedint __aer_firmware_first_valid:1;

      ? ? unsignedint __aer_firmware_first:1;

      ? ? pci_dev_flags_t dev_flags;

      ? ? atomic_t enable_cnt;/* pci_enable_device has been called */

      ? ? ?

      ? ? u32 saved_config_space[16];/* config space saved at suspend time */

      ? ? struct hlist_head saved_cap_space;

      ? ? struct bin_attribute *rom_attr;/* attribute descriptor for sysfs ROM entry */

      ? ? int rom_attr_enabled;/* has display of the rom attribute been enabled? */

      ? ? struct bin_attribute *res_attr[DEVICE_COUNT_RESOURCE];/* sysfs file for resources */

      ? ? struct bin_attribute *res_attr_wc[DEVICE_COUNT_RESOURCE];/* sysfs file for WC Mapping of resources */

      ? ? #ifdef CONFIG_PCI_MSI

      ? ? struct list_head msi_list;

      ? ? #endif

      ? ? struct pci_vpd *vpd;

      ? ? #ifdef CONFIG_PCI_IOV

      ? ? union{

      ? ? struct pci_sriov *sriov;/* SR-IOV capability related */

      ? ? struct pci_dev *physfn;/* the PF this VF is associated with */

      ? ? };

      ? ? struct pci_ats *ats;/* Address Translation service */

      ? ? #endif

      };

      可以看到该结构体包含总线、驱动、device和一个PCI设备几乎所有的硬件信息,包括厂商ID、设备ID、各种资源等:

      struct device 就不多说了:它用于描述设备相关的信息设备之间的层次关系,以及设备与总线、驱动的关系。

      二、理解设备和驱动加载先后顺序

      Linux驱动先注册总线,总线上可以先挂device,也可以先挂driver,那么究竟怎么控制先后的顺序呢。

      Linux关于总线、设备、驱动的注册顺序

      ? ? ? ? 设备挂接到总线上时,与总线上的所有驱动进行匹配(用bus_type.match进行匹配),

      ? ? ? ? ?如果匹配成功,则调用bus_type.probe或者driver.probe初始化该设备,挂接到总线上

      ? ? ? ? ?如果匹配失败,则只是将该设备挂接到总线上。?

      ? ? ? ? ?驱动挂接到总线上时,与总线上的所有设备进行匹配(用bus_type.match进行匹配),

      ? ? ? ? ?如果匹配成功,则调用bus_type.probe或者driver.probe初始化该设备;挂接到总线上

      ? ? ? ? ?如果匹配失败,则只是将该驱动挂接到总线上。

      需要重点关注的是总线的匹配函数match(),驱动的初始化函数probe()

      1. platform_bus_type--总线先被kenrel注册。

      2. 系统初始化过程中调用platform_add_devices或者platform_device_register,将平台设备(platform devices)注册到平台总线中(platform_bus_type)

      3. 平台驱动(platform driver)与平台设备(platform device)的关联是在platform_driver_register或者driver_register中实现,一般这个函数在驱动的初始化过程调用。

      通过这三步,就将平台总线,设备,驱动关联起来。

      先看看pci_register_driver注册驱动过程

      1.首先会调用int _pci_register_driver()函数,在这个函数里面,首先会把pci_driver存储的信息复制到device_driver中,

      因为内核最终要注册的是一个device的driver信息,而不单单指pci设备的driver信息。在复制完成之后,内核会调用driver_register()函数注册device_driver的信息。

      2.driver_register()函数,driver_register首先会检查struct kset drivers链表中有没有对应名称的dirver: driver_find(drv->name, drv->bus); 如果已经有了,

      则重新把对应的driver加载到struct kset drivers中,如果没有,则会执行bus_add_drivers函数,把当前的驱动加载到struct kset drivers中。

      3.bus_add_drivers函数,这个函数中重要的一个操作就是driver_attach操作,在driver_attach函数中会调用dirver_match_deivce函数来判断总线上是否有设备与当前

      driver相匹配,实现的方式应该就是比较vendor_id和device_id。如果当前bus中有设备和当前driver相匹配,那么就会执行driver_probe_device函数,对当前deivce进行

      初始化,这个probe函数里最终就是指向的驱动程序中编写的probe函数。driver和deivce就绑定完毕,而且device也已经初始化完毕。

      Linux中总线、设备、驱动是如何关联的?

      总线、设备、驱动,也就是bus、device、driver,在内核里都会有它们自己专属的结构,在include/linux/device.h 里定义。

      首先是总线,bus_type.

      struct bus_type {

      const char ?* name;

      struct subsystem subsys;//代表自身

      struct kset ?drivers; ? //当前总线的设备驱动集合

      struct kset ?devices; //所有设备集合

      struct klist ?klist_devices;

      struct klist ?klist_drivers;

      struct bus_attribute * bus_attrs;//总线属性

      struct device_attribute * dev_attrs;//设备属性

      struct driver_attribute * drv_attrs;

      int ?(*match)(struct device * dev, struct device_driver * drv);//设备驱动匹配函数

      int ?(*uevent)(struct device *dev, char **envp, ??

      ? ? ? int num_envp, char *buffer, int buffer_size);//热拔插事件

      int ?(*probe)(struct device * dev);

      int ?(*remove)(struct device * dev);

      void ?(*shutdown)(struct device * dev);

      int ?(*suspend)(struct device * dev, pm_message_t state);

      int ?(*resume)(struct device * dev);

      };

      下面是设备device的定义:

      struct device {

      struct device ?* parent; //父设备,一般一个bus也对应一个设备。

      struct kobject kobj;//代表自身

      char bus_id[BUS_ID_SIZE];?

      struct bus_type * bus; ?/* 所属的总线 */

      struct device_driver *driver; /* 匹配的驱动*/

      void ?*driver_data; /* data private to the driver 指向驱动 */

      void ?*platform_data; /* Platform specific data,由驱动定义并使用*/

      ///更多字段忽略了

      };

      下面是设备驱动定义:

      struct device_driver {

      const char ?* name; //该driver的名称

      struct bus_type ?* bus;//所属总线

      struct completion unloaded;

      struct kobject ?kobj;//代表自身

      struct klist ?klist_devices;//设备列表

      struct klist_node knode_bus;

      struct module ?* owner;

      int (*probe) (struct device * dev);

      int (*remove) (struct device * dev);

      void (*shutdown) (struct device * dev);

      int (*suspend) (struct device * dev, pm_message_t state);

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

      const struct dev_pm_ops *pm;

      struct driver_private *p;

      };

      我们会发现,struct bus_type中有成员struct kset drivers和struct kset devices,同时struct device中有两个成员struct bus_type *bus和struct device_driver *driver

      ?, struct device_driver中有两个成员struct bus_type *bus和struct klist klist_devices。struct device中的bus表示这个设备连到哪个总线上,driver表示这个设备的驱动

      ?是什么,struct device_driver中的bus表示这个驱动属于哪个总线,klist_devices表示这个驱动都支持哪些设备,因为这里device是复数,又是list,更因为一个驱动可以支持多个设备,

      ?而一个设备只能绑定一个驱动。当然,struct bus_type中的drivers和devices分别表示了这个总线拥有哪些设备和哪些驱动。

      还有上面device 和driver结构里出现的kobject 结构是什么?kobject 和kset 都是Linux 设备模型中最基本的元素。一般来说应该这么理解,整个Linux 的设备模型是一个OO

      ?的体系结构,总线、设备和驱动都是其中鲜活存在的对象,kobject 是它们的基类,所实现的只是一些公共的接口,kset 是同种类型kobject 对象的集合,也可以说是对象的容器。

      那么总线、设备和驱动之间是如何关联的呢?

      先说说总线中的那两条链表是怎么形成的。内核要求每次出现一个设备就要向总线汇报,或者说注册,每次出现一个驱动,也要向总线汇报,或者说注册。比如系统初始化的

      时候,会扫描连接了哪些设备,并为每一个设备建立起一个struct device 的变量,每一次有一个驱动程序,就要准备一个truct device_driver 结构的变量。把这些变量统

      统加入相应的链表,device插入devices链表,driver 插入drivers 链表。这样通过总线就能找到每一个设备,每一个驱动。

      设备和驱动又是如何联系?

      原来是把每一个要用的设备在计算机启动之前就已经插好了,插放在它应该在的位置上,然后计算机启动,然后操作系统开始初始化,总线开始扫描设备,每找到一个设备,

      就为其申请一个struct device 结构,并且挂入总线中的devices 链表中来,然后每一个驱动程序开始初始化,开始注册其struct device_driver 结构,然后它去总线的

      devices 链表中去寻找(遍历),去寻找每一个还没有绑定驱动的设备,struct device 中的struct device_driver 指针仍为空的设备,然后它会去观察这种设备的特征,

      看是否是他所支持的设备,如果是,那么调用一个叫做device_bind_driver 的函数,然后他们就结为了秦晋之好。换句话说,把struct device 中的

      struct device_driver driver指向这个驱动,而struct device_driver driver 把struct device 加入他的那struct klist klist_devices链表中来。就这样,bus、device?

      和driver,这三者之间或者说他们中的两两之间,就给联系上了。知道其中之一,就能找到另外两个。

      但现在情况变了,出现了一种新的名词,叫热插拔。设备可以在计算机启动以后在插入或者拔出计算机了。设备可以在任何时刻出现,而驱动也可以在任何时刻被加载,所以,

      出现的情况就是,每当一个struct device 诞生,它就会去bus 的drivers链表中寻找自己的另一半,反之,每当一个struct device_driver 诞生,它就去bus的devices?

      链表中寻找它的那些设备。如果找到了合适的,那么OK,和之前那种情况一下,调device_bind_driver 绑定好。如果找不到,没有关系,等待吧!

      三、开始pic驱动开发基本步骤

      // 在这里指定PCI设备ID,PCI_VDEVICE会组装厂家ID和PCI设备ID

      static const struct pci_device_id misc_pci_tbl[] = { ?

      ? ? { PCI_VDEVICE(INTEL, 0x0f1c), 0 }, ?

      ? ? { PCI_VDEVICE(INTEL, 0x0f12), 1 }, // SMBus ??

      ? ? {0,}, ?

      }; ?

      MODULE_DEVICE_TABLE(pci, misc_pci_tbl);?

      static struct pci_driver haMAChi_driver = { ?

      ? ? .name ? ? ? = DRV_NAME, ?

      ? ? .id_table ? = hamachi_pci_tbl, ?

      ? ? .probe ? ? ?= hamachi_init_one, ?

      ? ? .remove ? ? = __devexit_p(hamachi_remove_one), ?

      }; ?

      ??

      ? static int hamachi_init_one(struct pci_dev *dev, const struct pci_device_id *id)

      ? {

      ? /*

      ? 此函数里的dev,在PCI总线里已经被初始化了。接着我们该实现如何对该设备进行控制。

      ? 无非,就是读写操作

      ? */

      ?

      ? 1. pci_enable_device(dev);//启用设备I/O,内存

      ?

      ? 2.分配I/O端口空间或内存空间(如果是分配I/O内存,则需要把外设备寄存器地址映射到内存中才能访问设备),供驱动程序对设备进行访问。

      ?

      ? 3.注册主设备号(内核提供了三个函数来注册一组字符设备编号,这三个函数分别是 register_chrdev_region()、alloc_chrdev_region() 和 register_chrdev())

      ?

      ? 4.cdev_init和cdev_add初始化和注册字符设备文件

      ? 在cdev_init时会提供访问设备文件的方法(函数)

      ? 说明:struct cdev { ??

      ? ? struct kobject kobj; ? ? ? ? ? ? ? ? ?//内嵌的内核对象. ?

      ? ? struct module *owner; ? ? ? ? ? ? ? ? //该字符设备所在的内核模块的对象指针. ?

      ? ? const struct file_operations *ops; ? ?//该结构描述了字符设备所能实现的方法,是极为关键的一个结构体. ?

      ? ? struct list_head list; ? ? ? ? ? ? ? ?//用来将已经向内核注册的所有字符设备形成链表. ?

      ? ? dev_t dev; ? ? ? ? ? ? ? ? ? ? ? ? ? ?//字符设备的设备号,由主设备号和次设备号构成. ?

      ? ? unsigned int count; ? ? ? ? ? ? ? ? ? //隶属于同一主设备号的次设备号的个数. ?

      };

      打开文件设备,读写就可以访问设备了。

      ?

      ?

      ? }

      ??

      static int __init hamachi_init (void) ?

      { ??

      ? ? return pci_register_driver(&hamachi_driver); ?

      } ?

      ??

      static void __exit hamachi_exit (void) ?

      { ?

      ? ? pci_unregister_driver(&hamachi_driver); ?

      } ?

      module_init(hamachi_init); ?

      module_exit(hamachi_exit);

      说明:在加载模块时使用宏module_init将hamachi_init函数注册到内核,hamachi_init函数里注册驱动程序,并挂在PCI总线上,当匹配到PCI总线上的device时,

      就会调用probe函数.device在总线扫描到设备时就申请device结构,初始化挂在总线上。

      probe函数里一般实现,1.激活设备 2.分配I/O端口或I/O内存 3.分配设备号;2.提供操作设备文件的方法。

      每个PCI设备包括三个寻址空间:配置空间,I/O端口空间,内存空间,其中I/0和内存空间用来与外设备进行通讯。

      1. 获取物理地址或I/O端口

      函数pci_resource_start(struct pci_dev *dev, ?int bar)

      说明:用来获取设备的物理地址或设备的I/O端口(每个PCI设备有0-5一共6个地址空间,我们通常只使用前两个bar的范围为0-5)

      pio_start = pci_resource_start (pdev, 0);

      ?pio_end = pci_resource_end (pdev, 0);

      ?pio_flags = pci_resource_flags (pdev, 0);

      ?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提供内存映射。

      pci_resource_start 取得的物理地址是PCI设备上内存的地址(I/O端口).

      IOREMAP可以转换这个地址.因为转换的过程就是给这个物理地址建立一个页表,分配一个相应的虚拟地址

      得到内存长度

      pci_resource_length(struct pci_dev *dev,? int bar)??? Bar值的范围为0-5。

      从配置区相应寄存器得到I/O区域的内存的相关标志:

      pci_resource_flags(struct pci_dev *dev,? int bar)??? Bar值的范围为0-5。

      2. 分配I/O端口空间

      void Request_region(unsigned long from,?

            unsigned long num, const char *name)?

      参数1:io端口的基地址。?

      参数2:io端口占用的范围。?

      参数3:使用这段io地址的设备名。

      这个函数分配的是I/O端口空间,可以直接使用inb(),outb()等I/O端口函数直接访问(或ioport_map端口映射,方便访问)。告诉内核from开始num个数端口被占用

      分配I/O内存空间

      void request_mem_region(unsigned long from,?

            unsigned long num, const char *name)?

      参数1:io端口的基地址。?

      参数2:io端口占用的范围。?

      参数3:使用这段io地址的设备名。

      request_mem_region函数并没有做实际性的映射工作,只是告诉内核要使用一块内存地址,标志占有。重要的还是ioremap函数,ioremap()则是映射地址了。

      pci_request_regions (pdev, DRV_NAME);

      通知内核该设备对应的IO端口和内存资源已经使用,其他的PCI设备不要再使用这个区域

      1.使用Linux内核 提供了如下一些访问I/O端口的内联函数:?

      unsigned inb(unsigned port);?

      void outb(unsigned char byte, unsigned port);?

      unsigned inw(unsigned port);?

      void outw(unsigned short word, unsigned port);?

      unsigned inl(unsigned port);?

      void outl(unsigned longword, unsigned port);?

      2.void *ioport_map( unsigned long port, unsigned int count );?

      通过这个函数,可以把port开始的count个连续端口重映射为一段“内存空间”。然后就可以在其返回的地址上象访问I/O内存一样访问这几个I/O端口。

      当不需要这种映射时,需要调用撤消函数: void iport_unmap(void *addr);?

      3.I/O内存映射,确保I/O内存对内核是可以访问的。使用函数void *ioremap(unsigned long phys_addr, unsigned long size);?

      phys_addr:要映射的内存地址

      size:内存大小

      pci驱动为什么要ioremap?

      根据计算机体系和总线不同,I/O 内存可分为可以或者不可以通过页表来存取。若通过页表存取,内核必须先重新编排物理地址,使其对驱动程序可见,

      这就意味着在进行任何I/O操作之前,你必须调用ioremap;如果不需要页表,I/O内存区域就类似于I/O端口,你可以直接使用适当的I/O函数读写它们。

      由于边际效应的缘故,不管是否需要 ioremap,都不鼓励直接使用I/O内存指针,而应使用专门的I/O内存操作函数。这些I/O内存操作函数不仅在所有平台上是安全,

      而且对直接使用指针操作 I/O 内存的情况进行了优化.

      那什么叫边际效应呢? 我查了查 这个答案比较靠谱

      (个人理解就是副作用):读取某个地址时可能导致这个地址的内容发生变化,比如很多中断寄存器的值一经读取,便自动清零

      附加:copy_to_user和copy_from_user就是在进行驱动相关程序设计的时候,要经常遇到的两个函数。由于内核空间与用户空间的内存不能直接互访,因此借助函数copy_to_user()完成内核空间到用户空间的复制,函数copy_from_user()完成用户空间到内核空间的复制。

      unsigned long copy_to_user(void __user *to, const void *from, unsigned long n)

      第一个参数to是用户空间的数据源地址指针,

      第二个参数from是内核空间的数据目标地址指针,

      第三个参数n是数据的长度。

      unsigned long copy_from_user(void * to, const void __user * from, unsigned long n)

      第一个参数to是内核空间的数据目标地址指针,

      第二个参数from是用户空间的数据源地址指针,

      第三个参数n是数据的长度。

      内核提供了三个函数来注册一组字符设备编号,这三个函数分别是 register_chrdev_region()、alloc_chrdev_region() 和 register_chrdev()。

      (1)register_chrdev ?比较老的内核注册的形式 ? 早期的驱动

      (2)register_chrdev_region/alloc_chrdev_region + cdev ?新的驱动形式

      区别:register_chrdev()函数是老版本里面的设备号注册函数,可以实现静态和动态注册两种方法,主要是通过给定的主设备号是否为0来进行区别,为0的时候为动态注册。register_chrdev_region以及alloc_chrdev_region就是将上述函数的静态和动态注册设备号进行了拆分的强化。

      register_chrdev_region(dev_t first,unsigned int count,char *name)

      First :要分配的设备编号范围的初始值,?这组连续设备号的起始设备号, 相当于register_chrdev()中主设备号

      Count:连续编号范围. ??是这组设备号的大小(也是次设备号的个数)

      Name:编号相关联的设备名称. (/proc/devices);?本组设备的驱动名称

      alloc_chrdev_region函数,来让内核自动给我们分配设备号

      (1)register_chrdev_region是在事先知道要使用的主、次设备号时使用的;要先查看cat /proc/devices去查看没有使用的。

      (2)更简便、更智能的方法是让内核给我们自动分配一个主设备号,使用alloc_chrdev_region就可以自动分配了。

      (3)自动分配的设备号,我们必须去知道他的主次设备号,否则后面没法去mknod创建他对应的设备文件。

      int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count, const char *name)

      1:这个函数的第一个参数,是输出型参数,获得一个分配到的设备号。可以用MAJOR宏和MINOR宏,将主设备号和次设备号,提取打印出来,看是自动分配的是多少,方便我们在mknod创建设备文件时用到主设备号和次设备号。 mknod /dev/xxx c 主设备号 次设备号

      2:第二个参数:次设备号的基准,从第几个次设备号开始分配。

      3:第三个参数:次设备号的个数。

      4:第四个参数:驱动的名字。

      5:返回值:小于0,则错误,自动分配设备号错误。否则分配得到的设备号就被第一个参数带出来。

      cdev介绍

      cdev是一个结构体,里面的成员来共同帮助我们注册驱动到内核中,表达字符设备的,将这个struct cdev结构体进行填充,主要填充的内容就是

      复制代码

      struct cdev {
      
      struct kobject kobj;
      
      struct module *owner;//填充时,值要为 THIS_MODULE,表示模块
      
      const struct file_operations *ops;//这个file_operations结构体,注册驱动的关键,要填充成这个结构体变量
      
      struct list_head list;
      
      dev_t dev;//设备号,主设备号+次设备号
      
      unsigned int count;//次设备号个数
      
      };
      
      

      复制代码

      file_operations这个结构体变量,让cdev中的ops成员的值为file_operations结构体变量的值。这个结构体会被cdev_add函数想内核注册

      cdev结构体,可以用很多函数来操作他。

      如:

      cdev_alloc:让内核为这个结构体分配内存的

      cdev_init:将struct cdev类型的结构体变量和file_operations结构体进行绑定的

      void cdev_init(struct cdev *, const struct file_operations *);

      cdev_add:向内核里面添加一个驱动,注册驱动

      int cdev_add(struct cdev *p, dev_t dev, unsigned count);

      该函数向内核注册一个struct cdev结构,即正式通知内核由struct cdev *p代表的字符设备已经可以使用了。

      当然这里还需提供两个参数:

      (1)第一个设备号 dev,

      (2)和该设备关联的设备编号的数量。

      这两个参数直接赋值给struct cdev 的dev成员和count成员

      ?

      cdev_del:从内核中注销掉一个驱动。注销驱动

      设备号

      (1)dev_t类型(包括了主设备号和次设备号 ?不同的内核中定义不一样有的是16位次设备号和16位主设备号构成 ?有的是20为次设备号12位主设备号 )

      (2)MKDEV、MAJOR、MINOR三个宏

      MKDEV: ?是用来将主设备号和次设备号,转换成一个主次设备号的。(设备号)

      MAJOR: ? 从设备号里面提取出来主设备号的。

      MINOR宏:从设备号中提取出来次设备号的。

      相关阅读

      PCI Bus

      PCI Bus pinout for both 32 bit and 64 bit cards is shown below;Signal Pins 63-94 are only used on 64 bit PCI bus cards.

      dvd驱动器不见了怎么办 dvd驱动器恢复方法步骤介绍

        很多网友都在问dvd驱动器不见了怎么办?不少网友们根本没动过dvd,在设备管理器中找不到dvd驱动器,安装驱动精灵也不管用,或者重装安

      开发一个系统软件的流程是什么呢?软件系统开发分为哪几

      开发一个系统软件的流程是什么呢&#xff1f;软件系统开发分为哪几个步骤 软件开发过程的6个阶段 计划 对所要解决的问题进行总体

      阿里巴巴java开发手册(2020版)

      2020版 链接:https://pan.baidu.com/s/1Zls_FUBKfGksU1GR

      华为主题包hwt下载_hwtTool-hwtTool(华为主题开发工具

      hwtTool华为主题开发工具是华为自主研发的基于PC端的华为主题编辑工具&#xff0c;主题设计师在完成主题相关切图后可使用该工具快速

      分享到:

      IT相关

      程序相关

      推荐文章

      热门文章

      东北老女人嫖老头视频_无遮挡H肉动漫视频在线观看_欧美牲交a欧美牲交aⅴ另类_狼人乱码无限2021芒果