一、? ? ? ? 概述
????????
? ? ? ? ? ? ? ? 像這樣的分層設計在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模型,而只需處理與具體硬件相關的訪問。
? ? ? ? 這種分層有時候還不是兩層,可以有更多層,在軟件上呈現為面向對象里類繼承和多態的狀態。上面介紹的終端設備驅動類似下圖一樣的結果。
?????????
??
?????????
?????????
????????