linux 设备驱动的分层思想
一、 概述
像这样的分层设计在linux的input、RTC、MTD、I2c、SPI、tty、USB等诸多类型设备驱动中屡见不鲜,下面对这些驱动进行详细的分析。
二、 输入设备驱动
输入设备(如按键、键盘、触摸屏、鼠标等)是典型的字符设备,其一般的工作机理是底层在按键、触摸等动作发送时产生一个中断(或驱动通过Timer定时查询),然后通过CPU通过SPI、I2c或外部存储器读取键值、坐标等数据,并将它们放入一个缓冲区,字符设备驱动管理该缓冲区,而驱动的read()接口让用户可以读取键值、坐标等数据。
显然,在这些工作中,只是中断、读键值/坐标值是与设备相关的,而输入事件的缓冲区管理以及字符设备驱动的file_operations接口则对输入设备是通用的。基于此,内核设计了输入子系统,由核心层处理公共的工作。Linux内核输入子系统如图所示:
输入核心提供了底层输入设备驱动程序所需要的API,如分配/释放一个输入设备:
input_allocate_device()返回的是1个input_dev的结构体,此结构体用于表征1个输入设备。注册/注销输入设备用的接口如下:
报告输入事件用的接口如下:
而对于所有的输入事件,内核都用统一都数据结构来描述,这个数据结构是input_event,如代码:
drivers/input/keyboard/gpio_keys.c基于input架构实现了通用的GPIO按键驱动。该驱动是基于platform_driver架构的,名为”gpio-keys“它将硬件相关的信息(如使用GPIO号,按下和抬起时的电平等)屏蔽在板文件platform_device的platform_data中,因此该驱动应用于各个处理器,具有良好的跨平台性。以下是该驱动的probe()函数。
851 static int gpio_keys_probe(struct platform_device *pdev)852 {853 struct device *dev = &pdev->dev;854 const struct gpio_keys_platform_data *pdata = dev_get_platdata(dev);855 struct fwnode_handle *child = NULL;856 struct gpio_keys_drvdata *ddata;857 struct input_dev *input;858 int i, error;859 int wakeup = 0;860 861 if (!pdata) {862 pdata = gpio_keys_get_devtree_pdata(dev);863 if (IS_ERR(pdata))864 return PTR_ERR(pdata);865 }866 867 ddata = devm_kzalloc(dev, struct_size(ddata, data, pdata->nbuttons),868 GFP_KERNEL);869 if (!ddata) {870 dev_err(dev, "failed to allocate state\n");871 return -ENOMEM;872 }873 874 ddata->keymap = devm_kcalloc(dev,875 pdata->nbuttons, sizeof(ddata->keymap[0]),876 GFP_KERNEL);877 if (!ddata->keymap)878 return -ENOMEM;879 880 input = devm_input_allocate_device(dev);881 if (!input) {882 dev_err(dev, "failed to allocate input device\n");883 return -ENOMEM;884 }885 886 ddata->pdata = pdata;887 ddata->input = input;888 mutex_init(&ddata->disable_lock);889 890 platform_set_drvdata(pdev, ddata);891 input_set_drvdata(input, ddata);892 893 input->name = pdata->name ? : pdev->name;894 input->phys = "gpio-keys/input0";895 input->dev.parent = dev;896 input->open = gpio_keys_open;897 input->close = gpio_keys_close;898 899 input->id.bustype = BUS_HOST;900 input->id.vendor = 0x0001;901 input->id.product = 0x0001;902 input->id.version = 0x0100;903 904 input->keycode = ddata->keymap;905 input->keycodesize = sizeof(ddata->keymap[0]);906 input->keycodemax = pdata->nbuttons;907 908 /* Enable auto repeat feature of Linux input subsystem */909 if (pdata->rep)910 __set_bit(EV_REP, input->evbit);911 912 for (i = 0; i < pdata->nbuttons; i++) {913 const struct gpio_keys_button *button = &pdata->buttons[i];914 915 if (!dev_get_platdata(dev)) {916 child = device_get_next_child_node(dev, child);917 if (!child) {918 dev_err(dev,919 "missing child device node for entry %d\n",920 i);921 return -EINVAL;922 }923 }924 925 error = gpio_keys_setup_key(pdev, input, ddata,926 button, i, child);927 if (error) {928 fwnode_handle_put(child);929 return error;930 }931 932 if (button->wakeup)933 wakeup = 1;934 }935 936 fwnode_handle_put(child);937 938 error = input_register_device(input);939 if (error) {940 dev_err(dev, "Unable to register input device, error: %d\n",941 error);942 return error;943 }944 945 device_init_wakeup(dev, wakeup);946 947 return 0;948 }
880 input = devm_input_allocate_device(dev);886 ddata->pdata = pdata;887 ddata->input = input;888 mutex_init(&ddata->disable_lock);889 890 platform_set_drvdata(pdev, ddata);891 input_set_drvdata(input, ddata);892 893 input->name = pdata->name ? : pdev->name;894 input->phys = "gpio-keys/input0";895 input->dev.parent = dev;896 input->open = gpio_keys_open;897 input->close = gpio_keys_close;898 899 input->id.bustype = BUS_HOST;900 input->id.vendor = 0x0001;901 input->id.product = 0x0001;902 input->id.version = 0x0100;903 904 input->keycode = ddata->keymap;905 input->keycodesize = sizeof(ddata->keymap[0]);906 input->keycodemax = pdata->nbuttons;907 925 error = gpio_keys_setup_key(pdev, input, ddata,926 button, i, child);912 for (i = 0; i < pdata->nbuttons; i++) {913 const struct gpio_keys_button *button = &pdata->buttons[i];914 915 if (!dev_get_platdata(dev)) {916 child = device_get_next_child_node(dev, child);917 if (!child) {918 dev_err(dev,919 "missing child device node for entry %d\n",920 i);921 return -EINVAL;922 }923 }924 925 error = gpio_keys_setup_key(pdev, input, ddata,926 button, i, child);927 if (error) {928 fwnode_handle_put(child);929 return error;930 }931 932 if (button->wakeup)933 wakeup = 1;934 }938 error = input_register_device(input);
上诉代码的第880行分配了1个输入设备,第886~907行初始化了该input_dev的一些属性,第925行注册了这个输入设备。第912~934行则初始化了所用到的GPIO,第23行完成了这个输入设备的注册 。
在注册输入设备后,底层输入设备驱动的核心工作只剩下在按键、触摸等人为动作发送时报告事件。下方展示了GPIO按键中断发送时的事件报告代码。
GPIO
指通用输入输出接口(General-purpose input/output)Keys
表示按键设备IRQ
是中断请求(Interrupt Request)的缩写ISR
即中断服务程序(Interrupt Service Routine)
469 static irqreturn_t gpio_keys_irq_isr(int irq, void *dev_id)470 {471 struct gpio_button_data *bdata = dev_id;472 struct input_dev *input = bdata->input;473 unsigned long flags;474 475 BUG_ON(irq != bdata->irq);476 477 spin_lock_irqsave(&bdata->lock, flags);478 479 if (!bdata->key_pressed) {480 if (bdata->button->wakeup)481 pm_wakeup_event(bdata->input->dev.parent, 0);482 483 input_report_key(input, *bdata->code, 1);484 input_sync(input);485 486 if (!bdata->release_delay) {487 input_report_key(input, *bdata->code, 0);488 input_sync(input);489 goto out;490 }491 492 bdata->key_pressed = true;493 }494 495 if (bdata->release_delay)496 hrtimer_start(&bdata->release_timer,497 ms_to_ktime(bdata->release_delay),498 HRTIMER_MODE_REL_HARD);499 out:500 spin_unlock_irqrestore(&bdata->lock, flags);501 return IRQ_HANDLED;502 }
483 input_report_key(input, *bdata->code, 1);484 input_sync(input);
GPIO按键驱动通过input_report_key()、input_sync()这样的函数来汇报按键事件以及同步事件。从底层的GPIO按键驱动可以看出,该驱动没有任何file_operations的动作,也没有各种I/O模式,注册进入系统也用的是input_register_device()这样与input相关的API。这是由于与linux VFS接口的这一部分代码全部都在drivers/input/evdev.c中实现了。
558 static ssize_t evdev_read(struct file *file, char __user *buffer,559 size_t count, loff_t *ppos)560 {561 struct evdev_client *client = file->private_data;562 struct evdev *evdev = client->evdev;563 struct input_event event;564 size_t read = 0;565 int error;566 567 if (count != 0 && count < input_event_size())568 return -EINVAL;569 570 for (;;) {571 if (!evdev->exist || client->revoked)572 return -ENODEV;573 574 if (client->packet_head == client->tail &&575 (file->f_flags & O_NONBLOCK))576 return -EAGAIN;577 578 /*579 * count == 0 is special - no IO is done but we check580 * for error conditions (see above).581 */582 if (count == 0)583 break;584 585 while (read + input_event_size() <= count &&586 evdev_fetch_next_event(client, &event)) {587 588 if (input_event_to_user(buffer + read, &event))589 return -EFAULT;590 591 read += input_event_size();592 }593 594 if (read)595 break;596 597 if (!(file->f_flags & O_NONBLOCK)) {598 error = wait_event_interruptible(client->wait,599 client->packet_head != client->tail ||600 !evdev->exist || client->revoked);601 if (error)602 return error;603 }604 }605 606 return read;607 }1292 static const struct file_operations evdev_fops = {
1293 .owner = THIS_MODULE,
1294 .read = evdev_read,
1295 .write = evdev_write,
1296 .poll = evdev_poll,
1297 .open = evdev_open,
1298 .release = evdev_release,
1299 .unlocked_ioctl = evdev_ioctl,
1300 #ifdef CONFIG_COMPAT
1301 .compat_ioctl = evdev_ioctl_compat,
1302 #endif
1303 .fasync = evdev_fasync,
1304 .llseek = no_llseek,
1305 };
上诉代码574-576行在检查出是非阻塞访问后,立即返回EAGAIM错误, 而586和598~600行都代码则处理了阻塞的睡眠情况。回过头来想,其实gpio_keys驱动里面调用的input_event()、input_sync()有间接唤醒这个等待队列evdev->wait的功能,只不过这些代码都隐藏在其内部实现里了。
三、 RTC设备驱动
RTC(实时钟)借助电池供电,在系统掉电都情况下依然可以正常计时。它通常还具有产生周期性中断以及闹钟(Alarm)中断的能力,是一种典型的字符设备。作为一种字符设备驱动,RTC需要有file_operations中接口函数的实现,如open()、release()、read()、poll()、ioctl()等,而典型的IOCTL包括RTC_SET_TIME、RTC_ALM_READ、RTC_ALM_SET、RTC_IRQP_SET、RTC_IRQP_READ等,这些对于所有的RTC是通用的,只用底层的具体实现是与设备相关的。
四、 Framebuffer设备驱动
Framebuff(帧缓冲)是Linux系统为显示设备提供设备的一个接口,它将显示缓冲器抽象,屏蔽图像硬件的底层差异,允许上层应用程序在图形模式下直接对显示缓冲区进行读写操作。对于帧缓冲设备而言,只要在显示缓冲区中与显示点对应的区域内写入区域颜色值,对应的颜色会自动在屏幕上显示。
下图为Linux帧缓冲设备驱动的主要结构,帧缓冲设备提供给用户空间file_operations结构体由
drivers/video/fbdev/core/fbmem.c中的file_operations提供,而特定帧缓冲设备fb_info结构的注册、注销以及其中成员的维护,尤其是fb_ops中成员函数的实现则由对应的xxxfb.c文件实现,fb_ops中的函数最终会操作LCD控制其硬件寄存器。
多数显存的操作方法都是规范的,可以按照像素点格式的要求顺序写帧缓冲区。但是有少量LCD的显存写法可能比较特殊,这时候,在核心层drivers/video/fbdev/core/fb_chrdev.c实现的fb_write()中,实际上可以给底层提供一个重写自己的机会,如下:
46 static ssize_t fb_write(struct file *file, const char __user *buf, size_t count, loff_t *ppos)47 {48 struct fb_info *info = file_fb_info(file);49 50 if (!info)51 return -ENODEV;52 53 if (info->state != FBINFO_STATE_RUNNING)54 return -EPERM;55 56 if (info->fbops->fb_write)57 return info->fbops->fb_write(info, buf, count, ppos);58 59 return fb_io_write(info, buf, count, ppos);60 }
第56~57行是一个检查底层LCD有没有实现自己特殊显存写法的代码,如果有,直接调底层的;如果没有,用中间层标准的显存写法就搞定了底层的那个不特殊的LCD。
五、 终端设备驱动
在linux系统中,终端是一种字符型设备,它有多种类型,通常使用tty来简称各种类型的终端设备。对于嵌入式系统而言,最普通采用的是UART(Univer Asynchronous Receiver/Transmitter)串行端口,日常生活中简称串口。
Linux内核中tty的层次结构如图所示,它包含tty核心tty_io.c、tty线路规程n_tty.c(实现N_TTY线路规程)和tty驱动实例,tty线路规程的工作是以特殊的方式格式化从一个用户或者硬件收到的数据,这个格式化常常采用一个协议转换的形势。
tty_io.c本身是一个标准的字符设备驱动,它对上字符设备的职责,实现file_operations成员函数。但是tty核心层对下又定义了tty_driver的架构,这样tty设备驱动的主体工作就变成了填充tty_driver结构体中的成员,实现其中的tty_operations的成员函数,而不再是去实现file_operations这一级的工作。tty_driver结构体和tty_operations的定义分别如下:
433 struct tty_driver {
434 struct kref kref;
435 struct cdev **cdevs;
436 struct module *owner;
437 const char *driver_name;
438 const char *name;
439 int name_base;
440 int major;
441 int minor_start;
442 unsigned int num;
443 short type;
444 short subtype;
445 struct ktermios init_termios;
446 unsigned long flags;
447 struct proc_dir_entry *proc_entry;
448 struct tty_driver *other;
449
450 /*
451 * Pointer to the tty data structures
452 */
453 struct tty_struct **ttys;
454 struct tty_port **ports;
455 struct ktermios **termios;
456 void *driver_state;
457
458 /*
459 * Driver methods
460 */
461
462 const struct tty_operations *ops;
463 struct list_head tty_drivers;
464 } __randomize_layout;
350 struct tty_operations {
351 struct tty_struct * (*lookup)(struct tty_driver *driver,
352 struct file *filp, int idx);
353 int (*install)(struct tty_driver *driver, struct tty_struct *tty);
354 void (*remove)(struct tty_driver *driver, struct tty_struct *tty);
355 int (*open)(struct tty_struct * tty, struct file * filp);
356 void (*close)(struct tty_struct * tty, struct file * filp);
357 void (*shutdown)(struct tty_struct *tty);
358 void (*cleanup)(struct tty_struct *tty);
359 ssize_t (*write)(struct tty_struct *tty, const u8 *buf, size_t count);
360 int (*put_char)(struct tty_struct *tty, u8 ch);
361 void (*flush_chars)(struct tty_struct *tty);
362 unsigned int (*write_room)(struct tty_struct *tty);
363 unsigned int (*chars_in_buffer)(struct tty_struct *tty);
364 int (*ioctl)(struct tty_struct *tty,
365 unsigned int cmd, unsigned long arg);
366 long (*compat_ioctl)(struct tty_struct *tty,
367 unsigned int cmd, unsigned long arg);
368 void (*set_termios)(struct tty_struct *tty, const struct ktermios *old);
369 void (*throttle)(struct tty_struct * tty);
370 void (*unthrottle)(struct tty_struct * tty);
371 void (*stop)(struct tty_struct *tty);
372 void (*start)(struct tty_struct *tty);
373 void (*hangup)(struct tty_struct *tty);
374 int (*break_ctl)(struct tty_struct *tty, int state);
375 void (*flush_buffer)(struct tty_struct *tty);
376 void (*set_ldisc)(struct tty_struct *tty);
377 void (*wait_until_sent)(struct tty_struct *tty, int timeout);
378 void (*send_xchar)(struct tty_struct *tty, char ch);
379 int (*tiocmget)(struct tty_struct *tty);
380 int (*tiocmset)(struct tty_struct *tty,
381 unsigned int set, unsigned int clear);
382 int (*resize)(struct tty_struct *tty, struct winsize *ws);
383 int (*get_icount)(struct tty_struct *tty,
384 struct serial_icounter_struct *icount);
385 int (*get_serial)(struct tty_struct *tty, struct serial_struct *p);
386 int (*set_serial)(struct tty_struct *tty, struct serial_struct *p);
387 void (*show_fdinfo)(struct tty_struct *tty, struct seq_file *m);
388 #ifdef CONFIG_CONSOLE_POLL
389 int (*poll_init)(struct tty_driver *driver, int line, char *options);
390 int (*poll_get_char)(struct tty_driver *driver, int line);
391 void (*poll_put_char)(struct tty_driver *driver, int line, char ch);
392 #endif
393 int (*proc_show)(struct seq_file *m, void *driver);
394 } __randomize_layout;
tty设备发送数据的流程为:tty核心从一个用户获取将要发送给一个tty设备的数据,tty核心将数据传递给tty线路规程驱动,接着数据被传递到tty驱动,tty驱动将数据转化为可以发送给硬件的格式。接收数据的流程为:从tty硬件接收到的数据向上交给tty驱动,接着进入tty线程驱动,再进入tty核心,在这里它被一个用户获取。
尽管一个特定的底层UART设备驱动完全可以遵循上述tty_driver的方法来设计,即定义tty_driver并实现tty_operations中的成员函数,但是鉴于串口之间的共性,Linux考虑在文件drivers/tty/serial/serial_core.c中实现了UART设备的通用tty驱动层(我们也可以称其为串口核心层)。这样,UART驱动的主要任务就进一步演变成了serial-core.c中定义的一组uart_xxx接口而不是tty_xxx接口,如下图所示,按照面向对象的思想,可以认为tty_driver是字符设备的泛华、serial-core是tty_driver的泛化,而具体的串口驱动又是serial-core的泛化。
串口核心层又定义了新的uart_driver结构体和其操作集uart_ops。一个底层的UART驱动需要创建和通过uart_register_driver注册一个uart_driver而不是tty_driver,代码给出uart_driver的定义。
732 struct uart_driver {733 struct module *owner;734 const char *driver_name;735 const char *dev_name;736 int major;737 int minor;738 int nr;739 struct console *cons;740 741 /*742 * these are private; the low level driver should not743 * touch these; they should be initialised to NULL744 */745 struct uart_state *state;746 struct tty_driver *tty_driver;747 };
uart_driver结构体在本质上是派生uart_driver结构体,因此,它的第746行也包含了一个tty_driver的结构体成员。tty_operations在UART这个层面也被进一步泛化为了uart_ops,其定义如代码如下:
374 struct uart_ops {375 unsigned int (*tx_empty)(struct uart_port *);376 void (*set_mctrl)(struct uart_port *, unsigned int mctrl);377 unsigned int (*get_mctrl)(struct uart_port *);378 void (*stop_tx)(struct uart_port *);379 void (*start_tx)(struct uart_port *);380 void (*throttle)(struct uart_port *);381 void (*unthrottle)(struct uart_port *);382 void (*send_xchar)(struct uart_port *, char ch);383 void (*stop_rx)(struct uart_port *);384 void (*start_rx)(struct uart_port *);385 void (*enable_ms)(struct uart_port *);386 void (*break_ctl)(struct uart_port *, int ctl);387 int (*startup)(struct uart_port *);388 void (*shutdown)(struct uart_port *);389 void (*flush_buffer)(struct uart_port *);390 void (*set_termios)(struct uart_port *, struct ktermios *new,391 const struct ktermios *old);392 void (*set_ldisc)(struct uart_port *, struct ktermios *);393 void (*pm)(struct uart_port *, unsigned int state,394 unsigned int oldstate);395 const char *(*type)(struct uart_port *);396 void (*release_port)(struct uart_port *);397 int (*request_port)(struct uart_port *);398 void (*config_port)(struct uart_port *, int);399 int (*verify_port)(struct uart_port *, struct serial_struct *);400 int (*ioctl)(struct uart_port *, unsigned int, unsigned long);401 #ifdef CONFIG_CONSOLE_POLL402 int (*poll_init)(struct uart_port *);403 void (*poll_put_char)(struct uart_port *, unsigned char);404 int (*poll_get_char)(struct uart_port *);405 #endif406 };
由于drivers/tty/serial/serial_core.c是一个tty_driver,因此在serial_core.c中,存在一个tty_operations的实例,这个实例的成员函数会进一步调用struct uart_ops的成员函数,这样就把file_operations里的成员函数、tty_operations的成员函数和uart_ops的成员函数串起来。
六、 misc设备驱动
由于Linux驱动倾向与分层设计,所有各个具体的设备都可以找到它归属都类型,从而套到它相应的架构里面去,并且只需要实现最底层的那 一部分。但是,也有部分类似globalmem、globalfifo的字符设备,确实不知道它属于什么类型,一般推荐大家采用miscdevice框架结构。miscdevice本质上也是字符设备,只是在miscdevice核心层的misc_init()函数中,通过register_chrdev(MISC_MAJOR,"misc",&misc_fops)注册了字符设备,而具体miscdevice实例调用misc_register()的时候又自动完成了device_create()、获取动态次设备号的动作。
miscdevice的主设备号是固定的,MISC_MAJOR定义为10,在linux内核中,大概可以找到200多处使用miscdevice框架结构的驱动。
miscdevice的结构体定义如下,第82行,指向了一个file_operations的结构体。miscdevice结构体内file_operations中的成员函数实际上是由dirvers/char/misc.c中的misc驱动的核心层的misc_ops成员函数间接调用的,比如misc_open()就会间接调用底层注册的miscdevice的fops->open。
79 struct miscdevice {80 int minor;81 const char *name;82 const struct file_operations *fops;83 struct list_head list;84 struct device *parent;85 struct device *this_device;86 const struct attribute_group **groups;87 const char *nodename;88 umode_t mode;89 };
如果上诉代码第80行的minor为MISC_DYNAMIC_MINOR,miscdevice核心层会自动找一个空闲的次设备号,否则用minor指定的次设备号。第81行的name是设备的名称。
miscdevice驱动的注册和注销分别用下面两个API:
因此miscdevice驱动的一般结构形如:
在调用misc_register(&xxx_dev)时,该函数内部会自动调用device_create(),而device_create会以xxx_dev作为drvdata参数。其次,在miscdevice核心层misc_open()函数的帮助下,在file_operations的成员函数中,xxx_dev会自动生成为file的private_data(misc_open会完成file->private_data的赋值操作)。
如果我们用面向对象的封装思想把一个设备的属性、自旋锁、互斥体、等待队列、miscdevice等封装在一个结构体里面:
在file_operations的成员函数中,就可以通过container_of()和file->private_data反推出xxx_dev的实例。
七、 驱动核心层
分析了上诉多个实例,我们可以归纳出核心层肩负的3大职责:
1)对上提供接口。file_operations的读、写、ioctl都被中间层搞定,各种I/O模型也被处理掉了。
2)中间层实现通用逻辑。可以被底层各种实例共享都代码都被中间层搞定,避免底层重复实现。
3)对下定义框架。底层的驱动不再需要关心linux内核VFS的接口和各种可能的I/O模型,而只需处理与具体硬件相关的访问。
这种分层有时候还不是两层,可以有更多层,在软件上呈现为面向对象里类继承和多态的状态。上面介绍的终端设备驱动类似下图一样的结果。