設備: 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
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
kernel module 相比應用程序,必須告訴 kernel 我在哪,我該被載卸,因此必須設定 __init
、__exit
#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");
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
另開一個終端來顯示輸出內容
$ sudo dmesg -W
[ 2075.106107] Hello world!!!
[ 2080.365640] Goodbye, world
dmesg
: 顯示 kernel 環境的訊息。-W
: 監控模式驅動程式類型 Block device 與 character device
#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
copy_to_user(void __user *to, const void *from, unsigned long n)
copy_from_user(void *to, const void __user *from, unsigned long n)
下圖說明了讀取操作以及資料如何在用戶空間和驅動程式之間傳輸:
alloc_chrdev_region
: 向 linux kernel 申請設備號給定次設備號和名字,告訴linux要申請幾個設備號,讓linux直接幫你尋找主設備號
register_chrdev_region
: 自定義初始設備編號
unregister_chrdev_region
: 註銷設備號
註冊完會出現在 /proc/devices
、 sysfs
中,若註冊成功返回 0 ,失敗則為負值。
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)
file_operations
結構體負責實現系統調用
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
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()
。
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 可以省略大量複雜的程式。
/* 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 語句在失敗情況下使用, 在事情變壞之前只對先前已成功註冊的設施進行註銷。
注意: 按照註冊時得順序相反的註銷
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!
#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 初始化:
/* 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 輸出/輸入模式
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);
}
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!
以下為查詢結果(省略部分內容,請自行查閱)
$ 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 )
...
修正
+#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 設備檔中讀取第一行內容。
#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 對照表
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
Learn More →
參考教學 : 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 的鮑率
步驟:
定義 ioctl
指令
#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
#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
函式
int ioctl(struct inode *inode,struct file *file,unsigned int cmd,unsigned long arg)
inode
:是正在處理的檔案的索引節點號
file
:是指向應用程式傳遞的檔案的檔案指標
cmd
:是從 user space 呼叫的 ioctl 指令
arg
:是從 user space 傳遞的參數
範例:
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>
long ioctl( "file descriptor","ioctl command","Arguments");
file descriptor
:需要執行ioctl指令的開啟文件,一般是設備文件。
ioctl command
:為實現所需功能而實現的 ioctl 指令
arguments
:需要將參數傳遞給 ioctl 指令。
user space 範例:
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
等
#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);
}
/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
創建在 /proc/*
底下的資料夾 API
struct proc_dir_entry *proc_mkdir(const char *name, struct proc_dir_entry *parent)
/proc
底下資料夾名稱創建 proc 底下的 entry
struct proc_dir_entry *proc_create ( const char *name, umode_t mode, struct proc_dir_entry *parent, const struct file_operations *proc_fops )
0666
=> 所有人、群組、其他人皆可讀寫, 開頭的 0 表示八進制註冊該 proc entry filesystem
static struct proc_ops proc_fops = {
.proc_read = procfs_read,
.proc_write = procfs_write,
};
需要注意在這結構體只適用 Linux v5.6+ ,詳情可以看 lkmpg_the-procops-structure
sysfs 是針對特定裝置將系統資訊從核心空間匯出到使用者空間的常用方法
$ ls /sys/
block bus class dev devices firmware fs kernel module power
sysfs 與 procfs 差異在於,sysfs 專注於設備和驅動程序的屬性和狀態,procfs 提供系統運行時信息和執行緒資訊
kobject
#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
函式
struct kobject * kobject_create_and_add ( const char * name, struct kobject * parent);
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
範例:
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
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 結構體定義名稱與權限
struct attribute {
const char *name; // 名稱
umode_t mode; // 權限(八進制表示,如 0444, 0666 等)
};
建立 sysfs 檔案
int sysfs_create_file ( struct kobject * kobj, const struct attribute * attr);
完成 sysfs 檔案後,您應該使用以下命令刪除該文件 sysfs_remove_file
void sysfs_remove_file ( struct kobject * kobj, const struct attribute * attr);
範例:
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
當中使用 __ATTR
macro 以下為 linux/kobject.h
中的定義
#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 passfirmware_kobj
to the second argument, it will create the directory under/sys/firmware/
. If you passfs_kobj
to the second argument, it will create the directory under/sys/fs
/. If you passNULL
to the second argument, it will create the directory under/sys/
.
kobj_ref = kobject_create_and_add("test_dir", kernel_kobj);
參考:
查詢 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
啟用 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 設備
參考 : How to update your Raspberry Pi Kernel and install Kernel Headers
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
其中的問題點在於 gpio_request_array()
& gpio_free_array()
在 kernel 版本 6.10.rc1 中已被移除 Commit dbcedec
改成使用 gpio_request()
來啟用 GPIO 即可
在 unregister 使用 gpio_free()
來取代 gpio_free_array()
/**
* 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;
};
修改後程式碼
/*
* 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'
先將你新的修改加到暫存區 (stage):
git add .
變更上次 commit 可以用
git commit --amend
這會把目前暫存的檔案變更,融合進「上一次 commit」,並讓你可以編輯該 commit 訊息。
如果只想替換內容但不改訊息,可加上 –no-edit。
如果這個分支已經 push 到遠端,要用 force push:
git push --force