# Character Device Driver ###### tags: `device driver` `rp3` * major and minor * Allocating and registering a character device * device file operations * Writing file operations * Exchanging data between kernel space and user space * get_user * put_user * copy_from_user * copy_to_user * The open method * The release method * The write method * The read method * The llseek method * The poll method * The ioctl method * Remove a Characteer Device Driver() * class_destroy(gpio_class); // 取消class * cdev_del(gpio_cdev); // 移除cdev * unregister_chrdev_region(driverno, ARRAY_SIZE(leds)); // 解登錄driverno ## Expected Achievemet Goal e.g: * 完成一個+1的character device driver * 控制LED的character device driver * user space status diagmram 的character device driver * 一個可以執行中斷.pooling 的character device driver ## 概念解析 字符設備是3大類設備(字符設備、塊設備和網絡設備)中的一類,其驅動程序完成的主要工作是初始化添加和刪除cdev結構體,申請和釋放設備號,以及填充`file_operations`結構體中的操作函數,來實現file_operations結構體中的`read()`、`write()`和`ioctl()`等函數是驅動設計的主體工作。 而註冊character deivce driver主要分成兩類 #### (a) Static Regist-[lxr code](https://elixir.bootlin.com/linux/latest/source/fs/char_dev.c#L235) ```clike= int register_chrdev_region(dev_t first, \ unsigned int count, \ char *name); ``` #### (b) Dynamic Regist-[lxr code](https://elixir.bootlin.com/linux/latest/source/fs/char_dev.c#L235)-RECOMMEND * flow of character device driver dynamic register * Suggestion let the Kernel do this Job ```clike= int alloc_chrdev_region(dev_t *dev, \ unsigned int firstminor, \ unsigned int count, \ char *name); ``` ## Major && Minor you may have a minor and a major, and need to build a dev_t . The macro you should use is `MKDEV(int major, int minor)` ## ## Allocating and registering a character device 1. Reserve a major and a range of minors with `alloc_chrdev_region()`. 2. Create a class for your devices with `class_create()`, visible in `/sys/class/` . 3. Set up a struct file_operation (to be given to cdev_init ), and for each device you need to create, call `cdev_init()` and `cdev_add()` to register the device. 4. Then, create a `device_create()` for each device with a proper name. It will result in your device being created in the `/dev` directory: ### alloc_chrdev_region() register a range of char device numbers ```c= int alloc_chrdev_region(dev_t *dev, \ unsigned int firstminor, \ unsigned int count, \ char *name); ``` * dev: output parameter for first assigned number * baseminor: first of the requested range of minor numbers * count: the number of minor numbers required (how many device) * name: the name of the associated device or driver ### `cdev_init` ```c= /* visible /sys/class @cdev: the structure to initialize @fops: the file_operations for this device Initializes @cdev, remembering @fops, making it ready to add to the system with cdev_add(). */ void cdev_init ( struct cdev * cdev,const struct file_operations * fops); ``` ### `cdev_add()` ```c= int cdev_add(struct cdev *dev, dev_t num, unsigned int count); ``` ### `device_create()` ```c= device_create(struct class *cls, struct device *parent, dev_t devt, void *drvdata, const char *fmt, ...); ``` ### `class_create()` ```c= ``` ## File Operations of Character drivers Linux一切皆文件,那麼作為一個設備文件,它的操作方法介面封裝在struct file_operations,當我們寫一個驅動的時候,一定要實現相應的介面,這樣才能使這個驅動可用,Linux的內核中大量使用"註冊+回調"機制進行驅動程式的編寫,所謂註冊回調,簡單的理解,就是當我們open一個設備文件的時候,其實是通過VFS找到相應的inode,並執行此前創建這個設備文件時註冊在inode中的open函數,其他函數也是如此,所以,為了讓我們寫的驅動能夠正常的被應用程式操作,首先要做的就是實現相應的方法,然後再創建相應的設備文件。 字元裝置,所以對該檔案的操作通過open,write,ioctl等函式,所以要把這個函式和底層的操作函式對應起來,這就要用到file_operation這個結構體,來宣告: ```cpp= static const struct file_operations dynamic_fops = { .owner = THIS_MODULE, .open = dev_open, .release = dev_release, .unlocked_ioctl = dev_ioctl, }; ``` * open * read * write * release * The llseek method * The poll method * The ioctl method * #### lock(Kernel 2.6) * unlocked_ioctl * compatible_ioctl ### Dummy Node #### [290312.423090] dummy_char major number = 240 #### [290312.424638] dummy char module loaded ## Tracing Code 在Linux Kernel中character device driver 使用cdev 這個結構(structure)進行並且登記系統呼叫handler(fops)。 我們可以從hello world device diver來延伸 #### Module Intialization * applied device number (申請設備號) * register `cdev` * * alloc_chrdev_region * device_create ```clike= device_create(struct class *cls, \ struct device *parent, \ dev_t devt, \ void *drvdata, \ const char *fmt, ...); ``` * e.g: ```clike= device_create(dynamic_class, NULL, MKDEV(dev_major, 0), NULL,"dyn_ch_dev"); ``` #### Module Exit * release device number (釋放設備號) * unregister `cdev` * major number: 每一個Device file都有自己一個獨一無二的major number。 * minor number: 驅動程式自己管理的編號,kernel完全不過問,如果驅動程式同時控制多個裝置的話,需要用到minor number來區分不同的裝置。 * e.g: chdev1, chdev2, chdev3 * Cdev structure * 需要註冊 ## LAB * `echo` * `cat` ## Code Function 主要分成三個部份 ### Intialize * #### alloc_chrdev_region ```clike= alloc_chrdev_region() ``` * #### cdev_init * #### cdev add * a pointer point to * a parameter with "dev_t" * Unsigned ```clike= int cdev_add(struct cdev *, dev_t, unsigned); ``` * #### cdev initialization * (a) struct cdev * * (b) const struct file_operations * ### Exit module exit(Unload Module) * 使用 marco `MKDEV`合成 ```clike= #define MKDEV(ma,mi) (((ma) << MINORBITS) | (mi)) ``` * 使用 marco `MAJOR`獲得主設備號(major device number) ```clike= #define MAJOR(dev) ((unsigned int) ((dev) >> MINORBITS)) ``` * delete the device driver -> chdev_del * unregister chr devregion->unregister_chrdev_region ```clike= dev_t chrdev = MKDEV(chrdev_major, cdev_del(&chrdev_cdev); unregister_chrdev_region(chrdev, ``` ## Reference * [ch0 && ch1 device driver](https://embetronicx.com/tutorials/linux/device-drivers/cdev-structure-and-file-operations-of-character-drivers/) * echo * head * cat * head * [most_cdev.c](https://elixir.bootlin.com/linux/latest/source/drivers/most/most_cdev.c#L501) * [Dummy Device Driver](https://embetronicx.com/tutorials/linux/device-drivers/cdev-structure-and-file-operations-of-character-drivers/) * [Linux Kernel Character device driver](https://linux-kernel-labs.github.io/master/labs/device_drivers.html) * [用樹莓派選寫驅動程式](https://jasonblog.github.io/note/raspberry_pi/yong_raspberry_pi_xie_qu_dong_cheng_shi_--_fan_li_.html) * [字元裝置驅動程式](https://www.itread01.com/content/1548639364.html) * [Pi GPIO Kenel Module](https://github.com/frobino/kernel-modules-raspi)