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 入口