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_usercopy_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宏:从设备号中提取出来次设备号的。

 

你可能感兴趣的:(c++)