Character Device Driver¶
Device Driver là một kernel module, có chức năng giao tiếp trực tiếp với phần cứng và cung cấp các giao diện để các module khác của kernel cũng như user có thể truy cập và sử dụng các tài nguyên liên quan đến các phần cứng tương ứng.
Tùy thuộc vào loại phần cứng tương ứng mà device driver được chia ra làm các loại khác nhau. Trong tài liệu LDD3, device driver được chia ra làm 3 loại chính: char module, block module và network module.
Trong đó character device sẽ được tập trung nghiên cứu trong tài liệu này.
Như vậy, một device driver nói chung cần thực hiện hai nhiệm vụ chính:
-
Cung cấp các giao diện để có thể giao tiếp được với User Space, qua đó các ứng dụng của người dùng có thể giao tiếp được với phần cứng thông qua các giao diện này.
-
Truy cập đến phần cứng. Device driver được sinh ra là để giao tiếp trực tiếp với phần cứng.
Trong tài liệu này sẽ tìm hiểu từng nhiệm vụ trên của Device Driver.
Device File¶
Device file hay special file là một giao diện để người dùng có thể giao tiếp được với device driver. Thông thường các device file xuất hiện trong /dev/ và được quản lý bởi virtual file system.
Người dùng thông qua các System Call để tương tác với device driver thông qua device file. Các System Call liên quan đến tương tác file được xử lý bởi hệ thống VFS (Virtual File System) như: open(), close(), read(), write(), mmpa()... Quá trình hoạt động của các system call này được mô tả rõ trong Chapter 12: The Virtual File System của sách Understanding Linux Kernel.
Vậy với một normal file, các người dùng có thể tác động gì được? Thông tin này có thể tìm hiểu qua từ khóa "Working with file in C".
Major & Minor¶
Mỗi Device File trong /dev/ được đặc trưng bởi hai tham số: <Major, Minor>.
- Major được sử dụng để xác định xem device đó tương ứng với driver nào.
- Minor mang thông tin của từng thiết bị, về cơ bản nhiều thiết bị có thể điều khiển bằng một driver, do đó trong một số hệ thống, driver sử dụng Minor để phân biệt giữa các thiết bị với nhau.
Major và Minor tạo thành một cặp số unsigned để đặc trưng cho một
device, gọi là Device Number. Kernel sử dụng kiểu dữ liệu dev_t để mô tả
hai tham số này.
Dev_t được định nghĩa trong header linux/types dưới dạng một số 32 bit.
Trong đó, 20 bit thấp được sử dụng cho Minor và các bit cao còn lại được
sử dụng cho Major. Các macro sử dụng cho dev_t như MKDEV, MAJOR, MINOR
được khai báo trong linux/k_dev_t.h.
Có thể tạo một Device File đơn giản bằng Command:
mknod /dev/coffee c 12 2.
Khai báo một Character Device File¶
Cấp phát Device Number¶
Hàm register_chrdev_region() được sử dụng để đăng ký device number.
int register_chrdev_region(dev_t from, unsigned count, const char
*name);
Quá trình này là cấp phát tĩnh dev_t, trong một số trường hợp yêu cầu
cấp phát này không thành công do yêu cầu của driver không phù hợp với
yêu cầu hiện tại của hệ thống. Trong trường hợp đó có thể sử dụng hàm
alloc_chrdev_region(). Nguyên mẫu của hàm đó như sau:
int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned
count,*const char *name);
Kết quả của quá trình cấp phát device number là Major và một dải Minor mà driver có thể sử dụng.
Đăng ký Character Device File¶
Kernel sử dụng một cấu trúc dữ liệu để lưu giữ thông tin của một
character device: struct cdev. Struct cdev được định nghĩa trong
linux/cdev.h bao gồm các trường thông tin ở bảng sau.
structcdev{
struct kobject kobj;
struct module *owner;
const struct file_operations *ops;
struct list_head list;
dev_t dev;
unsigned int count;
} __randomize_layout;
Mỗi char device tương ứng với một device number dev_t và có các
file_operations tướng ứng. Các file_operations (định nghĩa trong
linux/fs.h) được mô tả trong bảng sau.
struct file_operations {
ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);
ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);
int (*mmap) (struct file *, struct vm_area_struct *);
long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);
[some feilds obmitted]
} __randomize_layout;
cdev_init & cdev_add¶
Trước khi khởi tạo và đăng ký một cdev với kernel, một vùng nhớ sử dụng cho biến cdev này phải được khởi tạo. Phương pháp đơn giản nhất là khai báo một biến toàn cục trong driver.
Quá trình cdev_init bản chất là trang bị file_openratons cho cdev và
thêm nó vào danh sách các thiết bị đã được khai báo trong hệ thống. Hàm
cdev_add tiến hành thêm cdev vào hệ thống.
Lưu ý nguyên mẫu hàm cdev_add như sau:
int cdev_add(struct cdev *p, dev_t dev, unsigned count);
Trong đó con trỏ p được trỏ đến biến cdev đã được khai báo, và dev_t
dev là device number đầu tiên trong dãy các device number liên quan đến
cdev này, count là số lượng các minor number liên tiếp sau MINOR(dev_t
dev) liên quan đến device này.
Giả sử ta tiến hành thêm một cdev như sau:
Và sau đó khởi tạo thêm 4 files.
mknod /dev/scull0 c 246 0
mknod /dev/scull1 c 246 1
mknod /dev/scull2 c 246 2
mknod /dev/scull3 c 246 3
/dev/file này đểu trả về một dev_t duy nhất
<246, 0>.
Mutex¶
http://derekmolloy.ie/writing-a-linux-kernel-module-part-2-a-character-device/
MMAP¶
https://linux-kernel-labs.github.io/master/labs/memory_mapping.html
IOCTL¶
https://ufal.mff.cuni.cz/~jernej/2018/docs/predavanja06.pdf
Ioctl - Input Output Control được sinh ra để hỗ trợ người dùng khai báo thêm các câu lệnh mới phù hợp với từng dòng thiết bị.
Trong ví dụ liên quan đến bài đào tạo, led_controller_core có hai tham số có thể cấu hình được trong quá trình chạy:
-
SPEED: Tốc độ LED.
-
POSITION: Vị trí LED nháy.
-> Mục tiêu cần phải định nghĩa được một số câu lệnh để làm ví dụ trong quá trình phát triển.

