# 環境
設備: Raspberry pi 5
OS : `$ uname -a`
```
Linux pi 6.6.31+rpt-rpi-2712 #1 SMP PREEMPT Debian 1:6.6.31-1+rpt1 (2024-05-29) aarch64 GNU/Linux
```
# 參考資料
[The Linux Kernel Module Programming Guide](https://sysprog21.github.io/lkmpg/#headers)
# 事前作業
Raspberry Pi 5 Pinout

更新 Packages
```
sudo apt update
```
```
sudo apt upgrade -y
```
安裝 Kernel Header
```
sudo apt install -y raspberrypi-kernel-headers
```
安裝工具包
```
sudo apt install -y build-essential
```
重啟載入新 Kernel
```
sudo reboot
```
`$ls /dev` 可以查看設備編號,透過給予這些編號將其與 kernel 連接。
顯示設備編號分別有 character dev 與 block dev
```
$ cat /proc/devices
```
# 基本操作
當前有的 moduel
```
sudo lsmod
```
也可以直接從指定路徑尋找
```
sudo cat /proc/modules
```
```
$ sudo lsmod | grep i2c_i801
```

載入 module
```
sudo insmod [module_name].ko
```
載卸 module
```
sudo rmmod [module_name].ko
```
# 實作
## Hellow World
kernel module 相比應用程序,必須告訴 kernel 我在哪,我該被載卸,因此必須設定 `__init`、`__exit`

### hello.c
```c
#include <linux/init.h> /* Needed for the macros */
#include <linux/module.h> /* Needed by all modules */
static int __init hello_init(void)
{
printk("Hello world!!!\n");
return 0;
}
static void __exit hello_exit(void)
{
printk("Goodbye, world\n");
}
module_init(hello_init);
module_exit(hello_exit);
MODULE_LICENSE("GPL");
```
### Makefile
```make
obj-m += hello.o
all:
make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules
clean:
make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean
```
`-C` 到達此目錄後在編譯,編譯 `hello.c` 來生成 `hello.o`,然後進一步生成 `hello.ko`。
`M=` 使 Makefile 在試圖建立模塊前,回到你的 module 源碼目錄。
`make` 後
```
$ make
make -C /lib/modules/6.6.51+rpt-rpi-2712/build M=/home/pi/LKM/01_HelloWorld modules
make[1]: Entering directory '/usr/src/linux-headers-6.6.51+rpt-rpi-2712'
CC [M] /home/pi/LKM/01_HelloWorld/hello.o
MODPOST /home/pi/LKM/01_HelloWorld/Module.symvers
CC [M] /home/pi/LKM/01_HelloWorld/hello.mod.o
LD [M] /home/pi/LKM/01_HelloWorld/hello.ko
make[1]: Leaving directory '/usr/src/linux-headers-6.6.51+rpt-rpi-2712'
```
```
$ ls
hello.c hello.mod hello.mod.o Makefile Module.symvers
hello.ko hello.mod.c hello.o modules.order
```
**結果**
```
$ sudo insmod hello.ko
$ sudo rmmod hello.ko
```
另開一個終端來顯示輸出內容
```c
$ sudo dmesg -W
[ 2075.106107] Hello world!!!
[ 2080.365640] Goodbye, world
```
- `dmesg` : 顯示 kernel 環境的訊息。
- `-W` : 監控模式
## Read and Write
**驅動程式類型 Block device 與 character device**
- Block device
Block devices 可透過 /dev 中的 Filesystem nodes 來 Accessed。一個 Block 只能處理一到多個完整 Block 的 I/O 操作,大小為 512 bytes (或是更大的2的次方)。Linux 可以讓 User Space 讀取 Block device 如同操作File 一樣,而不用一次讀一整個 Block,在 User Space 中使用與 Char 基本上無差別。有差別的是在 Kernel & Driver 這一層完全不同。
- Character devices
Character devices 可以被當作 A stream of bytes 來被 Accessed,就像 File 一樣。因此 Char dirver 就要負責至少 open, close, read, and write 的 system calls 操作,有的還有提供 ioctl 的操作方式。常見的例子有 Text Console (/dev/console) and the Serial Ports (/dev/ttyS0),都是 Streaming 結構。Char devices 由 Filesystem nodes 來 accessed, such as /dev/tty1 and /dev/lp0。但跟一般 File 不同的是,一般 File 可以把指標往前往後來操作檔案,但是多數 Char device 只是 Data Channels,只能依序 Access。當然也有例外,而該例外多出現在 Frame Grabbers,就會像 Data Areas 一樣,可以在之中隨意前後操作,多用 mmap or lseek 來配合使用。
- Block 與 Character 的差異
Block devices 是以固定大小長度來傳送轉移資料,而 Character devices 是以不定長度的字元傳送資料,而其操作方式在 User Spcae 則大同小異。
> 取自 [Driver (驅動) - 從零開始的開源地下城](https://hackmd.io/@combo-tw/Linux-%E8%AE%80%E6%9B%B8%E6%9C%83/%2F%40combo-tw%2FryRp--nQS)
### read_write.c
```c
#include <linux/init.h> /* Needed for the macros */
#include <linux/module.h> /* Needed by all modules */
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/uaccess.h>
/* buffer for data */
static char buffer[255];
static size_t buffer_pointer = 0;
/* variables for device and device class */
static dev_t my_dev_num;
static struct class *my_class;
static struct cdev my_dev;
#define DRIVER_NAME "dummydriver"
#define DRIVER_CLASS "MyModuleClass"
/* read data out of the buffer */
static ssize_t driver_read(struct file *file, char *usr_buffer, size_t count, loff_t *offs){
int to_copy, not_copied, delta;
/* get amount of data to cp*/
to_copy = min(count, buffer_pointer);
/* copy data to user*/
not_copied = copy_to_user(usr_buffer, buffer, to_copy);
/* calcaulate data */
delta = to_copy - not_copied;
return delta;
}
/* write data to buffer */
static ssize_t driver_write(struct file *file, const char *usr_buffer, size_t count, loff_t *offs){
int to_copy, not_copied, delta;
/* get amount of data to cp*/
to_copy = min(count, sizeof(buffer));
/* copy data to user*/
not_copied = copy_from_user(buffer, usr_buffer, to_copy);
buffer_pointer = to_copy;
/* calcaulate data */
delta = to_copy - not_copied;
return delta;
}
static int driver_open(struct inode *driver_file, struct file *instance){
printk("dev_num - open was called!\n");
return 0;
}
static int driver_close(struct inode *driver_file, struct file *instance){
printk("dev_num - close was called!\n");
return 0;
}
static struct file_operations fops = {
.owner = THIS_MODULE,
.open = driver_open,
.release = driver_close,
.read = driver_read,
.write = driver_write
};
static int __init hello_init(void)
{
printk("Hello world!!!\n");
/* allocate a dev num*/
if(alloc_chrdev_region(&my_dev_num, 0, 1, DRIVER_NAME) < 0){
printk("device number cant be allocated!\n");
return -1;
}
printk("read_write - device number Major: %d, Minor: %d was registered!\n", my_dev_num >> 20, my_dev_num && 0xfffff);
/* create device class*/
if((my_class = class_create(DRIVER_CLASS)) == NULL){
printk("device class cant be created!\n");
goto ClassError;
}
/* create device file */
if(device_create(my_class, NULL, my_dev_num, NULL, DRIVER_NAME) == NULL){
printk("cant create device file!\n");
goto FileError;
}
/* initalize device file */
cdev_init(&my_dev, &fops);
/* register device to kernel */
if(cdev_add(&my_dev, my_dev_num, 1) == -1){
printk("register of device to kernel failed!\n");
goto AddError;
}
return 0;
AddError:
device_destroy(my_class, my_dev_num);
FileError:
class_destroy(my_class);
ClassError:
unregister_chrdev_region(my_dev_num, 1);
return -1;
}
static void __exit hello_exit(void)
{
cdev_del(&my_dev);
device_destroy(my_class, my_dev_num);
class_destroy(my_class);
unregister_chrdev_region(my_dev_num, 1);
printk("Goodbye, world\n");
}
module_init(hello_init);
module_exit(hello_exit);
MODULE_LICENSE("GPL");
```
`uaccess.h` 用作 user 與 kernel 之間的數據傳輸,例如 `copy_to_user`、`copy_from_user`
> [/include/linux/uaccess.h](https://elixir.bootlin.com/linux/v6.6.31/source/include/linux/uaccess.h)
```c
copy_to_user(void __user *to, const void *from, unsigned long n)
copy_from_user(void *to, const void __user *from, unsigned long n)
```
下圖說明了讀取操作以及資料如何在用戶空間和驅動程式之間傳輸:
1. when the driver has enough data available (starting with the OFFSET position) to accurately transfer the required size (SIZE) to the user.
2. when a smaller amount is transferred than required.


#### 設備編號
`alloc_chrdev_region` : 向 linux kernel 申請設備號給定次設備號和名字,告訴linux要申請幾個設備號,讓linux直接幫你尋找主設備號
`register_chrdev_region` : 自定義初始設備編號
`unregister_chrdev_region` : 註銷設備號
註冊完會出現在 `/proc/devices` 、 `sysfs` 中,若註冊成功返回 0 ,失敗則為負值。
```c
int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count,
const char *name);
int register_chrdev_region(dev_t from, unsigned count, const char *name);
void unregister_chrdev_region(dev_t from, unsigned count)
```
#### 文件操作
> [/include/linux /fs.h](https://elixir.bootlin.com/linux/v6.6.31/source/include/linux/fs.h)
`file_operations` 結構體負責實現系統調用
```c
struct file_operations {
struct module *owner;
loff_t (*llseek) (struct file *, loff_t, int);
ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);
ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);
ssize_t (*read_iter) (struct kiocb *, struct iov_iter *);
ssize_t (*write_iter) (struct kiocb *, struct iov_iter *);
int (*iopoll)(struct kiocb *kiocb, struct io_comp_batch *,
unsigned int flags);
int (*iterate_shared) (struct file *, struct dir_context *);
__poll_t (*poll) (struct file *, struct poll_table_struct *);
long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);
long (*compat_ioctl) (struct file *, unsigned int, unsigned long);
int (*mmap) (struct file *, struct vm_area_struct *);
unsigned long mmap_supported_flags;
int (*open) (struct inode *, struct file *);
int (*flush) (struct file *, fl_owner_t id);
int (*release) (struct inode *, struct file *);
int (*fsync) (struct file *, loff_t, loff_t, int datasync);
int (*fasync) (int, struct file *, int);
int (*lock) (struct file *, int, struct file_lock *);
unsigned long (*get_unmapped_area)(struct file *, unsigned long, unsigned long, unsigned long, unsigned long);
int (*check_flags)(int);
int (*flock) (struct file *, int, struct file_lock *);
ssize_t (*splice_write)(struct pipe_inode_info *, struct file *, loff_t *, size_t, unsigned int);
ssize_t (*splice_read)(struct file *, loff_t *, struct pipe_inode_info *, size_t, unsigned int);
void (*splice_eof)(struct file *file);
int (*setlease)(struct file *, int, struct file_lock **, void **);
long (*fallocate)(struct file *file, int mode, loff_t offset,
loff_t len);
void (*show_fdinfo)(struct seq_file *m, struct file *f);
#ifndef CONFIG_MMU
unsigned (*mmap_capabilities)(struct file *);
#endif
ssize_t (*copy_file_range)(struct file *, loff_t, struct file *,
loff_t, size_t, unsigned int);
loff_t (*remap_file_range)(struct file *file_in, loff_t pos_in,
struct file *file_out, loff_t pos_out,
loff_t len, unsigned int remap_flags);
int (*fadvise)(struct file *, loff_t, loff_t, int);
int (*uring_cmd)(struct io_uring_cmd *ioucmd, unsigned int issue_flags);
int (*uring_cmd_iopoll)(struct io_uring_cmd *, struct io_comp_batch *,
unsigned int poll_flags);
} __randomize_layout;
```
當中 `struct module *owner;` 並非一個操作,他指向擁有這個結構的 module 指針。用來阻止 module 被卸載。
新字元設備註冊方式 `cdev.h`
> [/include/linux/cdev.h](https://elixir.bootlin.com/linux/v6.6.31/source/include/linux/cdev.h)
```c
struct cdev {
struct kobject kobj;
struct module *owner;
const struct file_operations *ops;
struct list_head list;
dev_t dev;
unsigned int count;
} __randomize_layout;
```
使用 `cdev` 表示 `char device`,然後使用 `cdev_init()` 初始化結構體,然後透過 `cdev_add()` 進行添加到linux kernel中,最後,註銷時使用 `cdev_del()`。
```c
void cdev_init(struct cdev *cdev, const struct file_operations *fops);
int cdev_add(struct cdev *p, dev_t dev, unsigned count);
void cdev_del(struct cdev *p);
```
#### 錯誤處理
在註冊時發生任何錯誤,第一件事是決定 module 是否能夠無論如何繼續初始化他自己,常常,在一個註冊失敗後模組可以繼續操作, 如果需要可以功能降級. 在任何可能的時候, 你的模組應盡力向前, 並提供事情失敗後具備的能力。
若確定註冊失敗將導致整個 module 無法加載,使用 goto 可以省略大量複雜的程式。
```c
/* create device class*/
if((my_class = class_create(DRIVER_CLASS)) == NULL){
printk("device class cant be created!\n");
goto ClassError;
}
/* create device file */
if(device_create(my_class, NULL, my_dev_num, NULL, DRIVER_NAME) == NULL){
printk("cant create device file!\n");
goto FileError;
}
/* initalize device file */
cdev_init(&my_dev, &fops);
/* register device to kernel */
if(cdev_add(&my_dev, my_dev_num, 1) == -1){
printk("register of device to kernel failed!\n");
goto AddError;
}
return 0;
AddError:
device_destroy(my_class, my_dev_num);
FileError:
class_destroy(my_class);
ClassError:
unregister_chrdev_region(my_dev_num, 1);
return -1;
```
goto 語句在失敗情況下使用, 在事情變壞之前只對先前已成功註冊的設施進行註銷。
注意: 按照註冊時得順序相反的註銷
### Makefile
```make
obj-m += read_write.o
all:
make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules
clean:
make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean
```
掛載 module
```
$ sudo insmod read_write.ko
$ sudo rmmod read_write.ko
$ dmesg | tail
```
```
[11959.284419] Hello world!!!
[11959.284428] read_write - device number Major: 510, Minor: 1 was registered!
[11961.040756] Goodbye, world
```
執行結果
```
$ sudo insmod read_write.ko
$ ls /dev/dummydriver
/dev/dummydriver
$ sudo chmod 666 /dev/dummydriver
$ echo "Hello World!" > /dev/dummydriver
$ head -n 1 /dev/dummydriver
Hello World!
```
## GPIO driver
### gpio_driver.c
```c
#include <linux/init.h> /* Needed for the macros */
#include <linux/module.h> /* Needed by all modules */
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/uaccess.h>
#include <linux/gpio.h>
/* variables for device and device class */
static dev_t my_dev_num;
static struct class *my_class;
static struct cdev my_dev;
#define DRIVER_NAME "LED_gpio_driver"
#define DRIVER_CLASS "MyModuleClass" // After class_create, the device exports to /sys/class/
#define GPIO_LED 575 // Corresponds to gpio4
#define GPIO_BUTTON 588 // Corresponds to gpio17
/* read data out of the buffer */
static ssize_t driver_read(struct file *file, char *usr_buffer, size_t count, loff_t *offs){
int to_copy, not_copied, delta;
char tmp[3] = " \n";
/* get amount of data to cp*/
to_copy = min(count, sizeof(tmp));
/* Read value of button */
printk("gpio 17 value: %d\n", gpio_get_value(GPIO_BUTTON));
tmp[0] = gpio_get_value(GPIO_BUTTON) + '0'; // store to tmp[0]
/* copy data to user*/
not_copied = copy_to_user(usr_buffer, &tmp, to_copy);
/* calcaulate data */
delta = to_copy - not_copied;
return delta;
}
/* write data to buffer */
static ssize_t driver_write(struct file *file, const char *usr_buffer, size_t count, loff_t *offs){
int to_copy, not_copied, delta;
char value;
/* get amount of data to cp*/
to_copy = min(count, sizeof(value));
/* copy data from user*/
not_copied = copy_from_user(&value, usr_buffer, to_copy);
switch(value){
case '0':
gpio_set_value(GPIO_LED, 0);
break;
case '1':
gpio_set_value(GPIO_LED, 1);
break;
default:
printk("Invalid value!\n");
break;
}
/* calcaulate data */
delta = to_copy - not_copied;
return delta;
}
static int driver_open(struct inode *driver_file, struct file *instance){
printk("dev_num - open was called!\n");
return 0;
}
static int driver_close(struct inode *driver_file, struct file *instance){
printk("dev_num - close was called!\n");
return 0;
}
static struct file_operations fops = {
.owner = THIS_MODULE,
.open = driver_open,
.release = driver_close,
.read = driver_read,
.write = driver_write
};
static int __init hello_init(void)
{
printk("Hello world!!!\n");
/* allocate a dev num*/
if(alloc_chrdev_region(&my_dev_num, 0, 1, DRIVER_NAME) < 0){
printk("device number cant be allocated!\n");
return -1;
}
printk("read_write - device number Major: %d, Minor: %d was registered!\n", my_dev_num >> 20, my_dev_num && 0xfffff);
/* create device class*/
if((my_class = class_create(DRIVER_CLASS)) == NULL){
printk("device class cant be created!\n");
goto ClassError;
}
/* create device file */
if(device_create(my_class, NULL, my_dev_num, NULL, DRIVER_NAME) == NULL){
printk("cant create device file!\n");
goto FileError;
}
/* initalize device file */
cdev_init(&my_dev, &fops);
/* register device to kernel */
if(cdev_add(&my_dev, my_dev_num, 1) == -1){
printk("register of device to kernel failed!\n");
goto AddError;
}
/* set gpio 4 init */
if(gpio_request(GPIO_LED, "rpi-gpio-4")){
printk("gpio 4 can't be allocated!\n");
goto AddError;
}
/* set gpio 4 direction */
if(gpio_direction_output(GPIO_LED, 0)){
printk("can't set gpio 4 to output!\n");
goto Gpio4Error;
}
/* set gpio 17 init */
if(gpio_request(GPIO_BUTTON, "rpi-gpio-17")){
printk("gpio 17 can't be allocated!\n");
goto AddError;
}
/* set gpio 17 direction */
if(gpio_direction_input(GPIO_BUTTON)){
printk("cant set gpio 17 to input!\n");
goto Gpio17Error;
}
return 0;
Gpio17Error:
gpio_free(GPIO_BUTTON);
Gpio4Error:
gpio_free(GPIO_LED);
AddError:
device_destroy(my_class, my_dev_num);
FileError:
class_destroy(my_class);
ClassError:
unregister_chrdev_region(my_dev_num, 1);
return -1;
}
static void __exit hello_exit(void)
{
gpio_set_value(GPIO_LED, 0);
gpio_free(GPIO_BUTTON);
gpio_free(GPIO_LED);
cdev_del(&my_dev);
device_destroy(my_class, my_dev_num);
class_destroy(my_class);
unregister_chrdev_region(my_dev_num, 1);
printk("Goodbye, world\n");
}
module_init(hello_init);
module_exit(hello_exit);
/* Meta Information */
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Tsai Zhong-Han");
MODULE_DESCRIPTION("A simple gpio driver for setting a LED and reading a button");
```
GPIO 初始化:
```c
/* set gpio 4 init */
if(gpio_request(GPIO_LED, "rpi-gpio-4")){
printk("gpio 4 can't be allocated!\n");
goto AddError;
}
/* set gpio 4 direction */
if(gpio_direction_output(GPIO_LED, 0)){
printk("can't set gpio 4 to output!\n");
goto Gpio4Error;
}
/* set gpio 17 init */
if(gpio_request(GPIO_BUTTON, "rpi-gpio-17")){
printk("gpio 17 can't be allocated!\n");
goto AddError;
}
/* set gpio 17 direction */
if(gpio_direction_input(GPIO_BUTTON)){
printk("cant set gpio 17 to input!\n");
goto Gpio17Error;
}
```
以下為 GPIO 相關函式
`gpio_request` : 根據 GPIO 編號註冊該 GPIO 並指定 label
`gpio_direction_input` 、 `gpio_direction_output` : 設置 GPIO 輸出/輸入模式
```c
static inline int gpio_request(unsigned gpio, const char *label)
{
return -ENOSYS;
}
static inline int gpio_direction_input(unsigned gpio)
{
return gpiod_direction_input(gpio_to_desc(gpio));
}
static inline int gpio_direction_output(unsigned gpio, int value)
{
return gpiod_direction_output_raw(gpio_to_desc(gpio), value);
}
```
### Makefile
```make
obj-m += gpio_driver.o
all:
make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules
clean:
make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean
```
問題: 載入 module 時無法判別出 4 就是 GPIO4 ,因此查了 `$ sudo cat /sys/kernel/debug/gpio` 後才知道 GPIO4 對應到的是 `gpio-575`
錯誤結果
```
[ 9162.264345] Hello world!!!
[ 9162.264352] read_write - device number Major: 510, Minor: 1 was registered!
[ 9162.264530] gpio 4 can't be allocated!
```
以下為查詢結果(省略部分內容,請自行查閱)
```c
$ sudo cat /sys/kernel/debug/gpio
gpiochip10: GPIOs 512-543, parent: platform/107d508500.gpio, gpio-brcmstb@107d508500:
gpio-512 (- )
gpio-513 (2712_BOOT_CS_N |spi10 CS0 ) out hi ACTIVE LOW
gpio-514 (2712_BOOT_MISO )
gpio-515 (2712_BOOT_MOSI )
gpio-516 (2712_BOOT_SCLK )
...
gpiochip0: GPIOs 571-624, parent: platform/1f000d0000.gpio, pinctrl-rp1:
gpio-571 (ID_SDA )
gpio-572 (ID_SCL )
gpio-573 (GPIO2 )
gpio-574 (GPIO3 )
gpio-575 (GPIO4 )
gpio-576 (GPIO5 )
gpio-577 (GPIO6 )
gpio-578 (GPIO7 )
gpio-579 (GPIO8 )
gpio-580 (GPIO9 )
gpio-581 (GPIO10 )
gpio-582 (GPIO11 )
gpio-583 (GPIO12 )
gpio-584 (GPIO13 )
gpio-585 (GPIO14 )
gpio-586 (GPIO15 )
gpio-587 (GPIO16 )
gpio-588 (GPIO17 )
gpio-589 (GPIO18 )
gpio-590 (GPIO19 )
gpio-591 (GPIO20 )
gpio-592 (GPIO21 )
gpio-593 (GPIO22 )
gpio-594 (GPIO23 )
gpio-595 (GPIO24 )
gpio-596 (GPIO25 )
gpio-597 (GPIO26 )
gpio-598 (GPIO27 )
...
```
修正
```diff
+#define GPIO_LED 575
/* set gpio 4 init */
- if(gpio_request(4, "rpi-gpio-4")){
+ if(gpio_request(GPIO_LED, "rpi-gpio-4")){
printk("gpio 4 can't be allocated!\n");
goto AddError;
}
/* set gpio 4 direction */
- if(gpio_direction_output(4, 0)){
+ if(gpio_direction_output(GPIO_LED, 0)){
printk("can't set gpio 4 to output!\n");
goto Gpio4Error;
}
```
### 輸出結果
```
$ ls /dev/LED_gpio_driver
/dev/LED_gpio_driver
$ ls /dev/LED_gpio_driver -al
crw------- 1 root root 510, 0 Nov 7 02:12 /dev/LED_gpio_driver
$ sudo chmod 666 /dev/LED_gpio_driver
$ echo 1 > /dev/LED_gpio_driver
$ echo 0 > /dev/LED_gpio_driver
$ head -n 1 /dev/LED_gpio_driver
0
$ head -n 1 /dev/LED_gpio_driver
1
```
`head -n 1 /dev/LED_gpio_driver` 命令嘗試從 /dev/LED_gpio_driver 設備檔中讀取第一行內容。
1. LED 開關
{%youtube AQSmF-lCzuw %}
2. Button 開關
{%youtube FON64_ACAX8 %}
## LCD Driver
### lcd_driver.c
```c
#include <linux/init.h> /* Needed for the macros */
#include <linux/module.h> /* Needed by all modules */
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/uaccess.h>
#include <linux/gpio.h>
#include <linux/delay.h>
/* variables for device and device class */
static dev_t my_dev_num;
static struct class *my_class;
static struct cdev my_dev;
#define DRIVER_NAME "lcd"
#define DRIVER_CLASS "MyModuleClass" // After class_create, the device exports to /sys/class/
#define ENABLE_PIN 573 // corresponds to GPIO 2
#define REGISTER_SELECT_PIN 574 // corresponds to GPIO 3
#define LCD_DATA_PIN_0 575 // corresponds to GPIO 4
#define LCD_DATA_PIN_1 588 // corresponds to GPIO 17
#define LCD_DATA_PIN_2 598 // corresponds to GPIO 27
#define LCD_DATA_PIN_3 593 // corresponds to GPIO 22
#define LCD_DATA_PIN_4 581 // corresponds to GPIO 10
#define LCD_DATA_PIN_5 580 // corresponds to GPIO 9
#define LCD_DATA_PIN_6 582 // corresponds to GPIO 11
#define LCD_DATA_PIN_7 576 // corresponds to GPIO 5
/*lcd char buffer */
static char lcd_buffer[17];
unsigned int gpios[] = {
ENABLE_PIN,
REGISTER_SELECT_PIN,
LCD_DATA_PIN_0,
LCD_DATA_PIN_1,
LCD_DATA_PIN_2,
LCD_DATA_PIN_3,
LCD_DATA_PIN_4,
LCD_DATA_PIN_5,
LCD_DATA_PIN_6,
LCD_DATA_PIN_7
};
#define ENABLE gpios[0]
#define REGISTER_SELECT gpios[1]
/* generate a pulse on the enable signal */
void lcd_enable(void){
gpio_set_value(gpios[0], 1);
msleep(20);
gpio_set_value(gpios[0], 0);
}
/* set 8 bit data bus */
void lcd_send_byte(char data){
int i;
for(i = 0; i < 8; i++){
gpio_set_value(gpios[i+2], ((data) & (1<<i)) >> i);
}
lcd_enable();
msleep(20);
}
/* send command to the lcd */
void lcd_command(uint8_t data){
gpio_set_value(REGISTER_SELECT, 0); // RS to instruction
lcd_send_byte(data);
}
void lcd_data(uint8_t data){
gpio_set_value(REGISTER_SELECT, 1); // RS to data
lcd_send_byte(data);
}
/* write data to buffer */
static ssize_t driver_write(struct file *file, const char *usr_buffer, size_t count, loff_t *offs){
int to_copy, not_copied, delta;
/* get amount of data to cp*/
to_copy = min(count, sizeof(lcd_buffer));
/* copy data from user*/
not_copied = copy_from_user(lcd_buffer, usr_buffer, to_copy);
/* calcaulate data */
delta = to_copy - not_copied;
/* set the new data to the display */
lcd_command(0x1); // clear display
for(int i = 0; i < to_copy; i++){
lcd_data(lcd_buffer[i]);
}
return delta;
}
static int driver_open(struct inode *driver_file, struct file *instance){
printk("dev_num - open was called!\n");
return 0;
}
static int driver_close(struct inode *driver_file, struct file *instance){
printk("dev_num - close was called!\n");
return 0;
}
static struct file_operations fops = {
.owner = THIS_MODULE,
.open = driver_open,
.release = driver_close,
.write = driver_write
};
static int __init ModuleInit(void)
{
printk("Hello kernel!!!\n");
char *names[] = {"ENABLE_PIN", "REGISTER_SELECT", "DATA_PIN0",
"DATA_PIN1", "DATA_PIN2", "DATA_PIN3", "DATA_PIN4",
"DATA_PIN5", "DATA_PIN6", "DATA_PIN7"};
int i;
/* allocate a dev num*/
if(alloc_chrdev_region(&my_dev_num, 0, 1, DRIVER_NAME) < 0){
printk("device number cant be allocated!\n");
return -1;
}
printk("read_write - device number Major: %d, Minor: %d was registered!\n", my_dev_num >> 20, my_dev_num && 0xfffff);
/* create device class*/
if((my_class = class_create(DRIVER_CLASS)) == NULL){
printk("device class cant be created!\n");
goto ClassError;
}
/* create device file */
if(device_create(my_class, NULL, my_dev_num, NULL, DRIVER_NAME) == NULL){
printk("cant create device file!\n");
goto FileError;
}
/* initalize device file */
cdev_init(&my_dev, &fops);
/* register device to kernel */
if(cdev_add(&my_dev, my_dev_num, 1) == -1){
printk("register of device to kernel failed!\n");
goto AddError;
}
/* initialize GPIOs */
printk("initializing GPIOs\n");
for(i = 0; i < 10; i++){
if(gpio_request(gpios[i], names[i])){
printk("gpio %d can't be allocated!\n", gpios[i]);
goto GPIOInitError;
}
}
printk("setting GPIOs to output\n");
for(i = 0; i < 10; i++){
if(gpio_direction_output(gpios[i], 0)){
printk("gpio %d can't be set to output!\n", gpios[i]);
goto GPIODirectionError;
}
}
/* init the display */
lcd_command(0x30); // 8 bit mode
lcd_command(0xf); // display on, cursor on, blinking on
lcd_command(0x1); // clear display
char text[] = "Hello!";
for(i = 0; i < sizeof(text)-1; i++){
lcd_data(text[i]);
}
return 0;
GPIODirectionError:
i = 9; // set i to 9 to free all the GPIOs
GPIOInitError:
for(;i>=0; i--)
gpio_free(gpios[i]);
AddError:
device_destroy(my_class, my_dev_num);
FileError:
class_destroy(my_class);
ClassError:
unregister_chrdev_region(my_dev_num, 1);
return -1;
}
static void __exit ModuleExit(void)
{
lcd_command(0x1); // clear display
for(int i = 0; i < 10; i++){
gpio_set_value(gpios[i], 0);
gpio_free(gpios[i]);
}
cdev_del(&my_dev);
device_destroy(my_class, my_dev_num);
class_destroy(my_class);
unregister_chrdev_region(my_dev_num, 1);
printk("Goodbye, kernel\n");
}
module_init(ModuleInit);
module_exit(ModuleExit);
/* Meta Information */
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Tsai Zhong-Han");
MODULE_DESCRIPTION("A simple gpio driver for setting a LED and reading a button");
```

Command 對照表

### Makefile
```
obj-m += lcd_driver.o
all:
make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules
clean:
make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean
```
### 輸出結果
```
$ sudo insmod lcd_driver.ko
$ ls /dev/lcd -al
crw------- 1 root root 510, 0 Nov 10 01:08 /dev/lcd
$ sudo chmod 666 /dev/lcd
$ echo "Tsai Zhong-Han " > /dev/lcd
```
{%youtube Cuo0WwNcKRs %}
## IOCtl
### 說明
參考教學 : [Linux/Device_Driver/IOCTL](https://github.com/Embetronicx/Tutorials/tree/master/Linux/Device_Driver/IOCTL)
IOCtl 說明 :
IOCTL is referred to as Input and Output Control, which is used to talk to device drivers. This system call is available in most driver categories. The major use of this is in case of handling some specific operations of a device for which the kernel does not have a system call by default
> 處理 kernel 預設沒有系統呼叫的情境 => 讀寫設備 register 、修改 Serial port 的鮑率
步驟:
1. 在 Driver 建立 IOCtl 命令
2. 在 Driver 編寫 IOCtl 函式
3. 在 user space 建立 IOCtl 命令
4. 在 user space 使用 IOCtl 系統呼叫
**定義 `ioctl` 指令**
```c
#define "ioctl name" __IOX("magic number","command number","argument type")
```
其中 `__IOX` 可以是:
`IO`:不含參數的 ioctl
`IOW`:具有寫入參數的 ioctl (`copy_from_user`)
`IOR`:具有讀取參數的 ioctl (`copy_to_user`)
`IOWR`:具有寫入和讀取參數的 ioctl
- Magic Number 是唯一的數字,用來區分所有 ioctl 呼叫。有時設置為主設備編號。
- 指派給 ioctl 的編號,用來區分命令編號
- Data 類型
範例:
```c
#define IOC_MAGIC 100
/* Set the message of the device */
#define WR_VALUE _IOW(IOC_MAGIC, 0, int32_t *)
/* Get the message of the device */
#define RD_VALUE _IOR(IOC_MAGIC, 1, int32_t *)
```
**定義 `ioctl` 函式**
```c
int ioctl(struct inode *inode,struct file *file,unsigned int cmd,unsigned long arg)
```
`inode`:是正在處理的檔案的索引節點號
`file`:是指向應用程式傳遞的檔案的檔案指標
`cmd`:是從 user space 呼叫的 ioctl 指令
`arg`:是從 user space 傳遞的參數
範例:
```c
static long int my_ioctl(struct file *file, unsigned int cmd, unsigned long arg){
switch(cmd){
case WR_VALUE:
if(copy_from_user(&value, (int32_t *)arg, sizeof(value))){
printk("Data Write : Error\n");
}
printk("Value = %d", value);
break;
case RD_VALUE:
if(copy_to_user((int32_t *)arg, &value, sizeof(value))){
printk("Data Read : Error\n");
}
break;
default:
printk("Default\n");
break;
}
return 0;
}
static struct file_operations fops = {
.owner = THIS_MODULE,
.open = driver_open,
.release = driver_close,
.unlocked_ioctl = my_ioctl
};
```
**在 user space 使用 `IOCTL` 系統呼叫**
引入標頭檔 `#include <sys/ioctl.h>`
```c
long ioctl( "file descriptor","ioctl command","Arguments");
```
`file descriptor`:需要執行ioctl指令的開啟文件,一般是設備文件。
`ioctl command`:為實現所需功能而實現的 ioctl 指令
`arguments`:需要將參數傳遞給 ioctl 指令。
user space 範例:
```c
int dev = open("/dev/ioctl_ex", O_RDWR);
printf("Writing Value to Driver\n");
ioctl(dev, WR_VALUE, (int32_t*) &value);
printf("Reading Value from Driver\n");
ioctl(dev, RD_VALUE, (int32_t*) &value);
```
其中 `O_RDWR` 開啟讀寫功能,還有像是 `O_RDONLY`、`O_WRONLY` 等
### 程式碼
[ioctl_ex.c](https://github.com/jeremy90307/LKM/blob/main/05_IOCtl/ioctl_ex.c)
[Makefile](https://github.com/jeremy90307/LKM/blob/main/05_IOCtl/Makefile)
[test.c](https://github.com/jeremy90307/LKM/blob/main/05_IOCtl/test.c)
```c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include "ioctl_ex.h"
int main()
{
int32_t value;
printf("\nOpening Driver\n");
int dev = open("/dev/ioctl_ex", O_RDWR);
if(dev == -1) {
printf("Cannot open device file...\n");
return 0;
}
ioctl(dev, RD_VALUE, (int32_t*) &value);
printf("Initial value is %d\n", value);
printf("Enter the Value to send\n");
scanf("%d",&value);
printf("Writing Value to Driver\n");
ioctl(dev, WR_VALUE, (int32_t*) &value);
printf("Reading Value from Driver\n");
ioctl(dev, RD_VALUE, (int32_t*) &value);
printf("Value is %d\n", value);
printf("Closing Driver\n");
close(dev);
}
```
### 實際結果

## procfs
### 說明
`/proc` 並非存儲在硬碟上的實體文件系統,而是由內核在記憶體中動態生成的虛擬文件系統。這意味著其內容反映了系統的即時狀態,並且隨著系統的運行而變化。
`/procfs` 可以充當連接用戶空間和核心空間的橋樑。user space 程式可以使用 `/proc` 中的檔案來讀取 kernel 導出的資訊。
`/proc` 檔案系統中的每個條目都提供了一些來自核心的資訊,例如:
`/proc/meminfo` 給出了系統中正在使用的記憶體的詳細資訊,若要讀取此項目的資訊只需
```
$cat /proc/meminfo
```
更多項目=>
```
/proc/devices — 註冊字元和區塊主編號
/proc/iomem — 系統上實體 RAM 和匯流排設備位址
/proc/ioports — 系統上的 I/O 連接埠位址(特別是對於 x86 系統)
/proc/interrupts — 註冊的中斷請求號碼
/proc/filesystems — 目前活動的檔案系統驅動程式
/proc/cpuinfo — 有關係統上 CPU 的信息
/proc/[pid]:每個運行中的進程都有一個對應的目錄,[pid] 是進程的 ID,該目錄包含有關該進程的詳細資訊
```
當我們想要調試核心模組時,`/proc` 檔案系統也非常有用。在偵錯時,我們可能想知道模組中各種變數的值或模組正在處理的資料。在這種情況下,我們可以為自己建立一個 `/proc` 條目,並轉儲我們想要在該條目中查看的任何資料。
`/proc` 也可以用於透過寫入核心的方式向核心傳遞數據,因此可以有兩種 `/proc`
1. 僅從核心空間讀取資料的條目
2. 從核心空間讀取資料以及將資料寫入核心空間的條目
### 程式碼
[procfs_ex.c]()
**創建在 `/proc/*` 底下的資料夾 API**
```c
struct proc_dir_entry *proc_mkdir(const char *name, struct proc_dir_entry *parent)
```
- name : 創建 `/proc` 底下資料夾名稱
- parent : 指向該資料的指標
**創建 proc 底下的 entry**
```c
struct proc_dir_entry *proc_create ( const char *name, umode_t mode, struct proc_dir_entry *parent, const struct file_operations *proc_fops )
```
- name : entry 名稱
- mode : 存取模式, `0666` => 所有人、群組、其他人皆可讀寫, 開頭的 0 表示八進制
- parent 指向該 entry 的指標
- proc_fops : 將在其中建立 proc entry 的文件操作的結構
**註冊該 proc entry filesystem**
```c
static struct proc_ops proc_fops = {
.proc_read = procfs_read,
.proc_write = procfs_write,
};
```
需要注意在這結構體只適用 Linux v5.6+ ,詳情可以看 [lkmpg_the-procops-structure](https://sysprog21.github.io/lkmpg/#the-procops-structure)
[Makefile]()
### 實際運行

## sysfs
### 說明
sysfs 是針對特定裝置將系統資訊從核心空間匯出到使用者空間的常用方法
```
$ ls /sys/
block bus class dev devices firmware fs kernel module power
```
sysfs 與 procfs 差異在於,sysfs 專注於設備和驅動程序的屬性和狀態,procfs 提供系統運行時信息和執行緒資訊
**kobject**
- kobject 是核心(kernel)與 sysfs 文件系統之間的橋樑,定義在 <linux/kobject.h> 檔案中
- sysfs 是 Linux 中用來展示內核對象(如設備和驅動)的虛擬文件系統
- 一個 kobject 通常代表 kernel 中的某個物件,例如設備(device)或其他資源
```c
#define KOBJ_NAME_LEN 20
struct kobject {
char *k_name;
char name[KOBJ_NAME_LEN];
struct kref kref;
struct list_head entry;
struct kobject *parent;
struct kset *kset;
struct kobj_type *ktype;
struct dentry *dentry;
};
```
**那該如何在 `/sys` 底下建立子目錄,使用 `kobject_create_and_add` 函式**
```c
struct kobject * kobject_create_and_add ( const char * name, struct kobject * parent);
```
- name : 名稱
- parent : 說明參考如下
If you pass `kernel_kobj` to the second argument, it will create the directory under `/sys/kernel/`. If you pass `firmware_kobj` to the second argument, it will create the directory under `/sys/firmware/`. If you pass `fs_kobj` to the second argument, it will create the directory under `/sys/fs/`. If you pass `NULL` to the second argument, it will create the directory under `/sys/`.
若 `kobject` 結構體註冊失敗將回傳 `NULL`
> This function creates a kobject structure dynamically and registers it with sysfs. If the kobject was not able to be created, NULL will be returned.
當結束此結構,使用 `kobject_put` 函式,該結構體將在不再被使用時被動態釋放
> When you are finished with this structure, call `kobject_put` and the structure will be dynamically freed when it is no longer being used
範例:
```c
struct kobject *kobj_ref;
/*Creating a directory in /sys/kernel/ */
kobj_ref = kobject_create_and_add("etx_sysfs",kernel_kobj); //sys/kernel/etx_sysfs
/*Freeing Kobj*/
kobject_put(kobj_ref);
```
**創建 sysfs file**
在上面函式中我們建立 `/sys` 底下的子目錄,現在我們需要建立一個 sysfs 文件,該文件用於透過sysfs來實現 user space 與 kernel space 的互動。所以我們可以使用 sysfs 屬性來建立 sysfs 檔案
定義屬性: `Kobj_attribute`
```c
struct kobj_attribute {
struct attribute attr;
ssize_t (*show)(struct kobject *kobj, struct kobj_attribute *attr, char *buf);
ssize_t (*store)(struct kobject *kobj, struct kobj_attribute *attr, const char *buf, size_t count);
};
```
其中 attribute 結構體定義名稱與權限
```c
struct attribute {
const char *name; // 名稱
umode_t mode; // 權限(八進制表示,如 0444, 0666 等)
};
```
- attr : 定義屬性
- show : 指向在 sysfs 中讀取檔案時將呼叫的函數的指標,當我們向 sysfs 屬性寫入內容時,會呼叫store 函數
- store : 指向將檔案寫入 sysfs 時將被呼叫的函數的指標,當我們讀取 sysfs 屬性時,就會呼叫show 函數
建立 sysfs 檔案
```c
int sysfs_create_file ( struct kobject * kobj, const struct attribute * attr);
```
- kobj : 正在為其建立的物件
- attr : 屬性描述
完成 sysfs 檔案後,您應該使用以下命令刪除該文件 `sysfs_remove_file`
```c
void sysfs_remove_file ( struct kobject * kobj, const struct attribute * attr);
```
範例:
```c
struct kobj_attribute etx_attr = __ATTR(etx_value, 0660, sysfs_show, sysfs_store);
static ssize_t sysfs_show(struct kobject *kobj,
struct kobj_attribute *attr, char *buf)
{
return sprintf(buf, "%d", etx_value);
}
static ssize_t sysfs_store(struct kobject *kobj,
struct kobj_attribute *attr,const char *buf, size_t count)
{
sscanf(buf,"%d",&etx_value);
return count;
}
//This Function will be called from Init function
/*Creating a directory in /sys/kernel/ */
kobj_ref = kobject_create_and_add("etx_sysfs",kernel_kobj);
/*Creating sysfs file for etx_value*/
if(sysfs_create_file(kobj_ref,&etx_attr.attr)){
printk(KERN_INFO"Cannot create sysfs file......\n");
goto r_sysfs;
}
//This should be called from exit function
kobject_put(kobj_ref);
sysfs_remove_file(kernel_kobj, &etx_attr.attr);
```
### 程式碼
[sysfs_test.c](https://github.com/jeremy90307/LKM/blob/main/07_sysfs/sysfs_test.c)
當中使用 `__ATTR` macro 以下為 `linux/kobject.h` 中的定義
> [/ include / linux / sysfs.h](https://elixir.bootlin.com/linux/v6.11/source/include/linux/sysfs.h#L219)
```c
#define __ATTR(_name, _mode, _show, _store) { \
.attr = {.name = __stringify(_name), \
.mode = VERIFY_OCTAL_PERMISSIONS(_mode) }, \
.show = _show, \
.store = _store, \
}
```
在 `kobject_create_and_add` 函式的第二個參數中傳遞 `kernel_kobj` ,表示它將在 /sys/kernel/ 下建立目錄
> If you pass `kernel_kobj` to the second argument, it will create the directory under `/sys/kernel/`. If you pass `firmware_kobj` to the second argument, it will create the directory under `/sys/firmware/`. If you pass `fs_kobj` to the second argument, it will create the directory under `/sys/fs`/. If you pass `NULL` to the second argument, it will create the directory under `/sys/`.
```c
kobj_ref = kobject_create_and_add("test_dir", kernel_kobj);
```
[Makefile](https://github.com/jeremy90307/LKM/blob/main/07_sysfs/Makefile)
### 實際結果

## Device tree
參考:
- [Linux DTS (Device Tree Source)设备树源码](https://blog.csdn.net/MyArrow/article/details/123837118)
### 說明
### 程式碼
### 實際結果
# 待更新
## PWM Driver
查詢 Raspberry pi 5 上 pwm pin 對應在 kernel 上的編號
```
$ sudo cat /sys/kernel/debug/pwm
platform/107d517a80.pwm, 2 PWM devices
pwm-0 ((null) ): period: 0 ns duty: 0 ns polarity: normal
pwm-1 ((null) ): period: 0 ns duty: 0 ns polarity: normal
```
## 0.96 OLED Driver (i2c)
啟用 Raspberry pi5 的 i2c
```
sudo raspi-config
```
選擇 Interfacing Options
找到 I2C,按下 Enable
```
$ sudo i2cdetect -y 1
0 1 2 3 4 5 6 7 8 9 a b c d e f
00: -- -- -- -- -- -- -- --
10: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
20: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
30: -- -- -- -- -- -- -- -- -- -- -- -- 3c -- -- --
40: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
50: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
60: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
70: -- -- -- -- -- -- -- --
```
確認是否有接正確並且顯示偵測到 i2c 設備
# 番外篇 : GPIO examples failed to compile on Kernel 6.10+ #285
## 更新 kernel 至 6.12.1
> 參考 : [How to update your Raspberry Pi Kernel and install Kernel Headers](https://pineboards.io/blogs/tutorials/how-to-update-your-raspberry-pi-kernel-and-install-kernel-headers?srsltid=AfmBOorCkpJK0gFAlsv39j9PC2j_1RpmOo8fU5lLDhfIBTJ2RKspLYOi)
```
sudo apt update
sudo apt full-upgrade
sudo apt install rpi-update
sudo rpi-update next
sudo reboot
```
安裝 rpi-source 以取得核心標頭
```
sudo apt install git bc bison flex libssl-dev make libncurses5-dev
sudo wget https://raw.githubusercontent.com/jgartrel/rpi-source/master/rpi-source -O /usr/bin/rpi-source
sudo chmod +x /usr/bin/rpi-source
rpi-source --tag-update
```
```
rpi-source --default-config
```
此命令會取得核心原始碼並確保核心標頭與目前正在執行的核心正確對齊。
結果:
```
$ uname -r
6.12.1-v8-16k+
```
## 問題
lkmpg 的 issue [GPIO examples failed to compile on Kernel 6.10+ #285](https://github.com/sysprog21/lkmpg/issues/285)
其中的問題點在於 `gpio_request_array()` & `gpio_free_array()` 在 kernel 版本 6.10.rc1 中已被移除 Commit [dbcedec](https://github.com/torvalds/linux/commit/dbcedec3a31119d7594baacc743300d127c99c56#diff-f99ec00ad1ef2e30305b9cf54d994aef3486f92e9569002d8d50642111e335ecL229)
改成使用 `gpio_request()` 來啟用 GPIO 即可
在 unregister 使用 `gpio_free()` 來取代 `gpio_free_array()`
[linux/gpio.h](https://elixir.bootlin.com/linux/v6.12.1/source/include/linux/gpio.h)
```c
/**
* struct gpio - a structure describing a GPIO with configuration
* @gpio: the GPIO number
* @flags: GPIO configuration as specified by GPIOF_*
* @label: a literal description string of this GPIO
*/
struct gpio {
unsigned gpio;
unsigned long flags;
const char *label;
};
```
修改後程式碼
```c
/*
* intrpt.c - Handling GPIO with interrupts
*
* Based upon the RPi example by Stefan Wendler (devnull@kaltpost.de)
* from:
* https://github.com/wendlers/rpi-kmod-samples
*
* Press one button to turn on a LED and another to turn it off.
*/
#include <linux/gpio.h>
#include <linux/interrupt.h>
#include <linux/kernel.h> /* for ARRAY_SIZE() */
#include <linux/module.h>
#include <linux/printk.h>
static int button_irqs[] = { -1, -1 };
/* Define GPIOs for LEDs.
* TODO: Change the numbers for the GPIO on your board.
*/
static struct gpio leds[] = { { 4, GPIOF_OUT_INIT_LOW, "LED 1" } };
/* Define GPIOs for BUTTONS
* TODO: Change the numbers for the GPIO on your board.
*/
static struct gpio buttons[] = { { 17, GPIOF_IN, "LED 1 ON BUTTON" },
{ 18, GPIOF_IN, "LED 1 OFF BUTTON" } };
/* interrupt function triggered when a button is pressed. */
static irqreturn_t button_isr(int irq, void *data)
{
/* first button */
if (irq == button_irqs[0] && !gpio_get_value(leds[0].gpio))
gpio_set_value(leds[0].gpio, 1);
/* second button */
else if (irq == button_irqs[1] && gpio_get_value(leds[0].gpio))
gpio_set_value(leds[0].gpio, 0);
return IRQ_HANDLED;
}
static int __init intrpt_init(void)
{
int ret = 0;
pr_info("%s\n", __func__);
/* register LED gpios */
ret = gpio_request(leds[0].gpio, leds[0].label);
if (ret) {
pr_err("Unable to request GPIOs for LEDs: %d\n", ret);
return ret;
}
/* register BUTTON gpios */
ret = gpio_request(buttons[0].gpio, buttons[0].label);
if (ret) {
pr_err("Unable to request GPIOs for BUTTONs: %d\n", ret);
goto fail1;
}
ret = gpio_request(buttons[1].gpio, buttons[1].label);
if (ret) {
pr_err("Unable to request GPIOs for BUTTONs: %d\n", ret);
goto fail2;
}
pr_info("Current button1 value: %d\n", gpio_get_value(buttons[0].gpio));
ret = gpio_to_irq(buttons[0].gpio);
if (ret < 0) {
pr_err("Unable to request IRQ: %d\n", ret);
goto fail3;
}
button_irqs[0] = ret;
pr_info("Successfully requested BUTTON1 IRQ # %d\n", button_irqs[0]);
ret = request_irq(button_irqs[0], button_isr,
IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING,
"gpiomod#button1", NULL);
if (ret) {
pr_err("Unable to request IRQ: %d\n", ret);
goto fail3;
}
ret = gpio_to_irq(buttons[1].gpio);
if (ret < 0) {
pr_err("Unable to request IRQ: %d\n", ret);
goto fail3;
}
button_irqs[1] = ret;
pr_info("Successfully requested BUTTON2 IRQ # %d\n", button_irqs[1]);
ret = request_irq(button_irqs[1], button_isr,
IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING,
"gpiomod#button2", NULL);
if (ret) {
pr_err("Unable to request IRQ: %d\n", ret);
goto fail4;
}
return 0;
/* cleanup what has been setup so far */
fail4:
free_irq(button_irqs[0], NULL);
fail3:
gpio_free(buttons[1].gpio);
fail2:
gpio_free(buttons[0].gpio);
fail1:
gpio_free(leds[0].gpio);
return ret;
}
static void __exit intrpt_exit(void)
{
pr_info("%s\n", __func__);
/* free irqs */
free_irq(button_irqs[0], NULL);
free_irq(button_irqs[1], NULL);
/* turn all LEDs off */
gpio_set_value(leds[0].gpio, 0);
/* unregister */
gpio_free(leds[0].gpio);
gpio_free(buttons[0].gpio);
gpio_free(buttons[1].gpio);
}
module_init(intrpt_init);
module_exit(intrpt_exit);
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("Handle some GPIO interrupts");
```
編譯結果成功
```
$ make
make -C /lib/modules/6.12.1-v8-16k+/build M=/home/jeremypi/LKM/intrpt modules
make[1]: Entering directory '/home/jeremypi/linux-fcadf633eb574f99c13b8dcd127a3934343ea404'
CC [M] /home/jeremypi/LKM/intrpt/intrpt.o
MODPOST /home/jeremypi/LKM/intrpt/Module.symvers
CC [M] /home/jeremypi/LKM/intrpt/intrpt.mod.o
CC [M] /home/jeremypi/LKM/intrpt/.module-common.o
LD [M] /home/jeremypi/LKM/intrpt/intrpt.ko
make[1]: Leaving directory '/home/jeremypi/linux-fcadf633eb574f99c13b8dcd127a3934343ea404'
```
# dht11
```c
/*
* dht11.c - Linux kernel module for DHT11 sensor (no device tree)
*/
#include <linux/cdev.h>
#include <linux/delay.h>
#include <linux/device.h>
#include <linux/fs.h>
#include <linux/gpio.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/printk.h>
#include <linux/types.h>
#include <linux/uaccess.h>
#include <linux/version.h>
#include <asm/errno.h>
#define DEVICE_NAME "dht11"
#define DEVICE_CNT 1
// 暫存濕度與溫度
static uint16_t sensor_data[4] = { 0 }; // [0]=humidity, [1]=temperature
struct dht11_dev {
dev_t dev_num;
int major_num, minor_num;
struct cdev cdev;
struct class *cls;
struct device *dev;
};
static struct dht11_dev dht11_device;
/* Define GPIOs for LEDs.
* TODO: According to the requirements, search /sys/kernel/debug/gpio to
* find the corresponding GPIO location.
*/
static struct gpio dht11[] = { { 575, GPIOF_OUT_INIT_HIGH, "Signal" } };
static int device_open(struct inode *inode, struct file *file)
{
return 0;
}
static int device_release(struct inode *inode, struct file *file)
{
return 0;
}
// 在此函式中完成整個 DHT11 時序與資料讀取
static int dht11_read_data(void)
{
int timeout;
uint8_t data[5] = { 0 };
uint8_t i, j;
// 1. 啟動訊號: 拉低 ≥20ms, 拉高 30µs
gpio_set_value(dht11[0].gpio, 0);
mdelay(20);
gpio_set_value(dht11[0].gpio, 1);
udelay(30);
gpio_direction_input(dht11[0].gpio);
udelay(2);
pr_info("start success\n");
// dht 回應接收到訊號
// 等待低電位到來
timeout = 500;
while (gpio_get_value(dht11[0].gpio) && timeout--)
udelay(1);
if (!timeout)
return -ETIMEDOUT;
// 等待高電位到來
timeout = 500;
while (!gpio_get_value(dht11[0].gpio) && timeout--)
udelay(1);
if (!timeout)
return -ETIMEDOUT;
// 等待高電位結束
timeout = 500;
while (gpio_get_value(dht11[0].gpio) && timeout--)
udelay(1);
if (!timeout)
return -ETIMEDOUT;
pr_info("dht11 responed success\n");
// 讀取 5 字節資料
for (j = 0; j < 5; j++) {
uint8_t byte = 0;
for (i = 0; i < 8; i++) {
// 等待變成低電位
timeout = 500;
while (gpio_get_value(dht11[0].gpio) && timeout--)
udelay(1);
if (!timeout)
return -ETIMEDOUT;
// 等待變成高電位
timeout = 500;
while (!gpio_get_value(dht11[0].gpio) && timeout--)
udelay(1);
if (!timeout)
return -ETIMEDOUT;
// 等待 40us 後看高低電位決定這個 bit 的值
udelay(40);
byte <<= 1;
if (gpio_get_value(dht11[0].gpio))
byte |= 0x01;
}
data[j] = byte;
}
// 檢查
if (data[4] != (uint8_t)(data[0] + data[1] + data[2] + data[3]))
return -EIO;
// 更新緩衝
sensor_data[0] = data[0]; // 濕度
sensor_data[1] = data[1]; // 小數
sensor_data[2] = data[2]; // 溫度
sensor_data[3] = data[3]; // 小數
return 0;
}
static ssize_t device_read(struct file *filp, char __user *buf, size_t cnt,
loff_t *off)
{
int ret;
size_t to_copy = min(cnt, sizeof(sensor_data));
// 呼叫 dht11_read_data 完成所有時序與 bit 讀取
ret = dht11_read_data();
if (ret) {
gpio_direction_output(dht11[0].gpio, 1);
return ret;
}
gpio_direction_output(dht11[0].gpio, 1);
if (copy_to_user(buf, sensor_data, to_copy))
return -EFAULT;
return to_copy;
}
static struct file_operations fops = {
#if LINUX_VERSION_CODE < KERNEL_VERSION(6, 4, 0)
.owner = THIS_MODULE,
#endif
.read = device_read,
.open = device_open,
.release = device_release,
};
/* Initialize the module - Register the character device */
static int __init dht11_init(void)
{
int ret = 0;
/* Determine whether dynamic allocation of the device number is needed. */
if (dht11_device.major_num) {
dht11_device.dev_num =
MKDEV(dht11_device.major_num, dht11_device.minor_num);
ret = register_chrdev_region(dht11_device.dev_num, DEVICE_CNT,
DEVICE_NAME);
} else {
ret = alloc_chrdev_region(&dht11_device.dev_num, 0, DEVICE_CNT,
DEVICE_NAME);
}
/* Negative values signify an error */
if (ret < 0) {
pr_alert("Failed to register character device, error: %d\n", ret);
return ret;
}
pr_info("Major = %d, Minor = %d\n", MAJOR(dht11_device.dev_num),
MINOR(dht11_device.dev_num));
/* Prevents module unloading while operations are in use */
#if LINUX_VERSION_CODE < KERNEL_VERSION(6, 4, 0)
dht11_device.cdev.owner = THIS_MODULE;
#endif
cdev_init(&dht11_device.cdev, &fops);
ret = cdev_add(&dht11_device.cdev, dht11_device.dev_num, 1);
if (ret) {
pr_err("Failed to add the device to the system\n");
goto fail1;
}
#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 4, 0)
dht11_device.cls = class_create(DEVICE_NAME);
#else
dht11_device.cls = class_create(THIS_MODULE, DEVICE_NAME);
#endif
if (IS_ERR(dht11_device.cls)) {
pr_err("Failed to create class for device\n");
ret = PTR_ERR(dht11_device.cls);
goto fail2;
}
dht11_device.dev = device_create(dht11_device.cls, NULL,
dht11_device.dev_num, NULL, DEVICE_NAME);
if (IS_ERR(dht11_device.dev)) {
pr_err("Failed to create the device file\n");
ret = PTR_ERR(dht11_device.dev);
goto fail3;
}
pr_info("Device created on /dev/%s\n", DEVICE_NAME);
ret = gpio_request(dht11[0].gpio, dht11[0].label);
if (ret) {
pr_err("Unable to request GPIOs for dht11: %d\n", ret);
goto fail4;
}
ret = gpio_direction_output(dht11[0].gpio, dht11[0].flags);
if (ret) {
pr_err("Failed to set GPIO %d direction\n", dht11[0].gpio);
goto fail5;
}
return 0;
fail5:
gpio_free(dht11[0].gpio);
fail4:
device_destroy(dht11_device.cls, dht11_device.dev_num);
fail3:
class_destroy(dht11_device.cls);
fail2:
cdev_del(&dht11_device.cdev);
fail1:
unregister_chrdev_region(dht11_device.dev_num, DEVICE_CNT);
return ret;
}
static void __exit dht11_exit(void)
{
gpio_set_value(dht11[0].gpio, 0);
gpio_free(dht11[0].gpio);
device_destroy(dht11_device.cls, dht11_device.dev_num);
class_destroy(dht11_device.cls);
cdev_del(&dht11_device.cdev);
unregister_chrdev_region(dht11_device.dev_num, DEVICE_CNT);
}
module_init(dht11_init);
module_exit(dht11_exit);
MODULE_LICENSE("GPL");
```
## pull request 可能會用到 git 知識
先將你新的修改加到暫存區 (stage):
```
git add .
```
變更上次 commit 可以用
```
git commit --amend
```
這會把目前暫存的檔案變更,融合進「上一次 commit」,並讓你可以編輯該 commit 訊息。
如果只想替換內容但不改訊息,可加上 --no-edit。
如果這個分支已經 push 到遠端,要用 force push:
```
git push --force
```