在树莓派平台进行 Linux kernel 驱动编程指引。
1. 前置环境
1.1. 开发环境
- raspberry pi 4
- kernel version: 4.19.71-rt24-v7l+
- wireless connect: ssh pi@192.168.31.25
1.2. 安装编译依赖
编译驱动需要依赖内核树,有两种方法:
- 安装内核头文件,编译时引用该头文件目录
sudo apt-get install raspberrypi-kernel-headers
- 下载对应内核源码,编译时引用该内核文件目录。(下载内核源码版本一定要与目标平台一致)
1.3. 编译方式
均在 raspberry pi 本地编译,为使用交叉编译。如要交叉编译开发可在 raspberry 处下载交叉编译工具。但是不支持 MAC。
2. Hello World
直接贴代码,
- hello.c
#include <linux/init.h>
#include <linux/module.h>
MODULE_LICENSE("Dual BSD/GPL");
static int hello_init(void)
{
printk(KERN_ALERT"Hello World enter\n");
return 0;
}
static void hello_exit(void)
{
printk(KERN_ALERT"Hello World exit\n");
}
module_init(hello_init);
module_exit(hello_exit);
MODULE_AUTHOR("Song YanNa");
MODULE_DESCRIPTION("A Sample Hello World Module");
MODULE_ALIAS("A Sample module");
注意 kernel 里面不支持 libc 库,那么意味着在用户空间里的 libc 的头文件,接口调用都无效。
- Makefile
obj-m := hello.o
CURRENT_PATH := $(shell pwd)
# LINUX_KERNEL := $(shell uname -r)
# LINUX_KERNEL_PATH := /usr/src/linux-headers-$(LINUX_KERNEL)
LINUX_KERNEL_PATH := /home/pi/linux-rpi-4.19.y-rt
all:
$(MAKE) -C $(LINUX_KERNEL_PATH) M=$(CURRENT_PATH) modules
clean:
$(MAKE) -C $(LINUX_KERNEL_PATH) M=$(CURRENT_PATH) clean
注意 编码 module 引用的内核树依赖必须与运行环境中一致。
3. 应用层与驱动层如何关联
- 驱动代码 dev_nr.c
#include <linux/module.h>
#include <linux/init.h>
#include <linux/fs.h>
MODULE_LICENSE("Dual BSD/GPL");
MODULE_AUTHOR("tango");
MODULE_ALIAS("A Sample module");
MODULE_DESCRIPTION("Register a device nr, and implement some callback function");
static int dev_nr_open(struct inode* device_file, struct file* inst)
{
printk("dev_nr, open was callback \n");
return 0;
}
static int dev_nr_close(struct inode* device_file, struct file* inst)
{
printk("dev_nr, close was callback \n");
return 0;
}
static struct file_operations fops = {
.owner = THIS_MODULE,
.open = dev_nr_open,
.release = dev_nr_close,
};
#define MYMAJOR 90
static int __init dev_nr_init(void)
{
int ret;
printk("kernel enter\n");
// register device nr
ret = register_chrdev(MYMAJOR, "tango_nr", &fops);
if (ret == 0) printk("dev_nr, register major: %d, minor: %d \n", MYMAJOR, 0);
else if (ret > 0) printk("dev_nr, register major: %d, minor: %d \n", ret>>20, ret&0xFFFFF);
else
{
printk("register failed \n");
return -1;
}
return 0;
}
static void __exit dev_nr_exit(void)
{
unregister_chrdev(MYMAJOR, "tango_nr");
printk("exit kernel\n");
}
module_init(dev_nr_init);
module_exit(dev_nr_exit);
驱动程序会注册有唯一的 Major-Minor 号(示例程序是 90 0,且要保证该号没有被系统中其他驱动占用,cat /proc/devices
可以查询)。然后再创建节点并关联此 Major-Minor 号。sudo mknod /dev/tango_nr c 90 0
。最后应用程序打开该设备节点就可以关联上驱动了。这是手动模式,当然也可以在驱动初始化时自动分配 Major-Minor 号和创建设备节点。
应用程序代码 app_nr.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
int main (void)
{
int dev = open("/dev/tango_nr", O_RDONLY);
if (dev == -1)
{
printf("open failed \n");
return -1;
}
printf("open success\n");
close(dev);
}