9、Linux驱动的杂项设备

    杂项设备,是字符设备中的特殊,它的主设备号,是 10,不同的杂项设备,通过次设备号进行区分。

1、注册与注销

int misc_register(struct miscdevice * misc)

    完成杂项设备的注册,

int misc_deregister(struct miscdevice *misc)

    可见,设备的注册和注销,都是设置到 struct miscdevice  结构体

2、struct miscdevice  结构体

struct miscdevice  { 
    int minor; 
    const char *name;      // 名字 
    const struct file_operations *fops;  // 文件操作结构体 
    struct list_head list; 
    struct device *parent; 
    struct device *this_device; 
    const char *nodename; 
    mode_t mode; 
};

    结构体中,name 是注册的名字,以后将会在 /dev 目录下,进行显示的 name,里面最主要的是 struct file_operations ,在注册杂项设备的时候,字符设备的结构体与杂项设备进行绑定,

    minor 为 MISC_DYNAMIC_MINOR 的时候,miscdevice 核心层会自动寻找一个空闲的次设备号,

3、杂项设备与字符设备驱动

    杂项设备,本质上就是字符设备驱动,只不过是一个特殊一点的。杂项设备的主设备号,被固定在 10,通过次设备号进行区分设备。杂项设备注册之后,在 /dev/ 目录下就有 name 设备节点,在 /sys/clas/misc 下面,也会自动生成的类信息,因此,一定程度上,比标准的的字符设备驱动简单了一丢丢。

4、流程

    struct const file_operations xxxx_fops = {

    .unlock_ioctl = xxx_ioctl,

     xxxx

};

    struct miscdevice xxx_dev = {

    .minor = MISC_DYNAMIC_MINOR,

    .name = “xxx”,

    .fops = xxxx_fops

};

static int __init xxx_init()

{

    return misc_register(&xxx_dev );

}

void __exit xxx_exit()

{

    return misc_deregister(&xxx_dev) ;

}

   当调用 misc_register(&xxx_dev ); 的时候,会在 misc_open 的帮助下,实现将 xxx_dev 成为 file 的private_data,也就是 系统会帮助我们实现 file->private_data = xxx_dev .

二、总线平、设备、驱动

    在实际的编程中,最希望的是,一套支持,可以支持一类所有的设备,比如 同一套 DM9000 驱动,可最好是满足在不同板子上运行,所以为了达到目标,就提出了总线、设备、驱动软件架构。

    驱动编写代码中,有一些信息是关于设备的相关信息,比如 IO 地址,内存资源地址,其余的是对这些设备的操作,所以,就将这些对应的设备的信息进行提取到一个文件,设备文件,它只做对不同设备的资源进行定义,不同的平台就单独设置不同的设备文件。

    驱动则只管对设备的操作。

    总线,主要是完成 设备 与 驱动的配合。总线、设备、驱动 的 软件架构,就可以使得驱动四海皆准了。

    在系统注册设备的时候,总线就会根据设备的信息去匹配对应的设备;同理,驱动注册的时候,总线也会根据驱动的信息进行运行匹配的设备,当找到对应的驱动或者设备的时候,就会调用驱动文件的 probe 函数。

2.1、platform_device 与 platform_driver

    因为提出了总线、设备、驱动这种软件架构,Linux就虚构了虚拟的总线,称之为 platform 总线,设备称之为 platform_device,驱动则成为 platform_driver。

    platform_device  有对应的结构体,

struct platform_device { 
    const char    * name;     // 设备名字 
    int        id; 
    struct device    dev;    // 设备 
    u32        num_resources; 
    struct resource    * resource;   // 资源

    const struct platform_device_id    *id_entry;

    /* MFD cell pointer */ 
    struct mfd_cell *mfd_cell;

    /* arch specific additions */ 
    struct pdev_archdata    archdata; 
};

    platform_driver  结构体,我们只需要注意的是 设备的名字,以及设备的资源。

struct platform_driver { 
    int (*probe)(struct platform_device *);     // probe 
    int (*remove)(struct platform_device *);   // 删除 
    void (*shutdown)(struct platform_device *); 
    int (*suspend)(struct platform_device *, pm_message_t state); 
    int (*resume)(struct platform_device *); 
    struct device_driver driver; 
    const struct platform_device_id *id_table;     // 平台设备的 ID, 
};

    platform_driver  结构体的地位 和 i2c_driver、spi_driver 、usb_driver, pci_driver 相等。所以说,在实现 I2C 驱动的时候,就可以使用 i2c_driver,当然可以是 platform_driver 。

    设备与驱动的匹配主要是通过设备名字的方式实现的:

struct platform_device  里面的注册一个 一个 name,指定的是 device 的名字;struct platform_driver 里面的const struct platform_device_id *id_table 表,则是注册了驱动支持的不同设备的名字,正是对两个名字进行了匹配,匹配成功之后,就会调用 driver 端 的probe 函数。一般是在里面是  probe 里面,完成新的字符设备驱动的操作。

2.2、device 的注册

    当 新建了platform_device  结构体(也可以是新建了一个指针, 再通过platform_device_alloc 接口实现 ),完成了资源和名字的设置之后,需要将这个 device 注册到内核。

设备注册进内核:

    

platform_device_add(struct platform_device *pdev)

设备从内核取消:

   

 platform_device_unregister(struct platform_device * pdev)

2.3、driver 的注册

    struct platform_driver  里面的 id_table 指定了设备的名字,当总线完成名字的匹配之后,就会调用 driver 端的 probe 函数,一般是在 probe 函数里面完成注册,注册的实现,一般也是注册为字符设备驱动,或者杂项设备。

3.、device 资源、driver 获取资源

struct resource { 
    resource_size_t start; 
    resource_size_t end; 
    const char *name; 
    unsigned long flags; 
    struct resource *parent, *sibling, *child; 
};

    device 结构体 里面,的resource 结构体实现了对设备资源的描述。一般上,只需要关心 start、end、flags。

start: 设备资源的开始值

end : 设备资源的结束值

flags : 设备资源类型的标志。IORESOURCE_IO   IORESOURCE_MEM IORESOURCE_IRQ IORESOURCE_DMA

    当 flag 是 IORESOURCE_MEM  的时候,start 就是内存的开始地址,end 就是内存资源的结束地址。

    flag 是 IORESOURCE_IRQ  的时候,start 和 end 就是中断号的开始值和结束值。如果只是一个中断的话,那么开始值和结束值就是相同的。

    device 定了硬件相关的板级资源,所以 driver 端,就应该在需要获取板级资源的时候,去获取相关的设备资源,driver 是通过这个接口去实现 相匹配的 device 端的资源:

   

 platform_get_resource(struct platform_device * dev,unsigned int type,unsigned int num)

    去 device 获取 type 类型指定的资源。

如果是获取 irq 的资源的话,也可以使用直接封装的接口:

  

  platform_get_irq(struct platform_device * dev,unsigned int num) 

4、代码

     借助宋宝华的 Linux设备驱动开发详解的代码,

4.1、device

static struct platform_device *globalfifo_pdev;     // 指针

static int __init globalfifodev_init(void) 
{ 
    int ret;

    globalfifo_pdev = platform_device_alloc("globalfifo", -1);   // 分配一个  device ,指定了名字 
    if (!globalfifo_pdev) 
        return -ENOMEM;

    ret = platform_device_add(globalfifo_pdev);   // 将 device 注册进 总线 
    if (ret) { 
        platform_device_put(globalfifo_pdev); 
        return ret; 
    }

    return 0;

} 
module_init(globalfifodev_init);

static void __exit globalfifodev_exit(void) 
{ 
    platform_device_unregister(globalfifo_pdev);      // 将 device 从总线进行注销

} 
module_exit(globalfifodev_exit);

4.3、driver

static int globalfifo_probe(struct platform_device *pdev) 
{

    ret = misc_register(miscdev);    // 杂项设备的注册 
    if (ret < 0) 
        goto err; 
}

static int globalfifo_remove(struct platform_device *pdev) 
{ 
    
    misc_deregister(miscdev);   // 注销

    return 0; 
}

static struct platform_driver globalfifo_driver = { 
    .driver = { 
        .name = "globalfifo",   // 名字 
        .owner = THIS_MODULE, 
    }, 
    .probe = globalfifo_probe,   // 名字匹配成功,就调用 probe 函数 
    .remove = globalfifo_remove, 
};

module_platform_driver(globalfifo_driver);   // 平台设备 driver 入口
原文地址:https://www.cnblogs.com/qxj511/p/5514271.html