# 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)