最近在使用树莓派4B来进行linux驱动的学习,发现一系列的配置还是很繁琐的,特此记录一下
前置条件
因为树莓派4B是arm架构,而我们常见的主机是x86架构,所以大多数情况下需要进行交叉编译,所以在进行驱动学习之前,需要你的主机有完整的交叉编译工具,并且编译过一次树莓派的linux内核。具体安装和编译过程可以参考从0开始移植linux系统到树莓派4b,这个里面的编译linux内核一节。
VSCode配置
首先安装一些插件,我装的有
Arm Assembly、C/C++、C/C++ Extension Pack、Chinese (Simplified) (简体中文)、DeviceTree、GBKtoUTF8、Include Autocomplete、One Dark Pro、TONGYI Lingma
安装完成后,在项目的目录下创建.vscode
文件夹,在.vscode
文件夹中创建c_cpp_properties.json
和settings.json
文件。
c_cpp_properties.json
// 需要将/home/pjw6/Documents/kernel/linux/替换为你编译的内核目录
{
"configurations": [
{
"name": "Linux",
"includePath": [
"${workspaceFolder}/**",
"/home/pjw6/Documents/kernel/linux/include",
"/home/pjw6/Documents/kernel/linux/arch/arm64/include",
"/home/pjw6/Documents/kernel/linux/drivers",
"/home/pjw6/Documents/kernel/linux/arch/arm64/include/asm/",
// "/home/pjw6/Documents/kernel/linux/arch/arm64/include/generated/asm/",
// "/home/pjw6/Documents/kernel/linux/arch/arm64/include/generated/uapi/asm/",
// "/home/pjw6/Documents/kernel/linux/arch/arm64/include/uapi/asm/",
"/home/pjw6/Documents/kernel/linux/arch/arm64/include/generated",
"/home/pjw6/Documents/kernel/linux/arch/arm64/include/uapi"
],
"defines": [],
"compilerPath": "/usr/bin/aarch64-linux-gnu-gcc",
"cStandard": "c11",
"cppStandard": "c++11",
"intelliSenseMode": "linux-gcc-arm64"
}
],
"version": 4
}
settings.json
{
"search.exclude": {
"**/node_modules": true,
"**/bower_components": true,
"**/*.code-search": true,
"**/*.o": true,
"**/*.su": true,
"**/*.cmd": true,
"Documentation":true,
"README":true
},
"files.exclude": {
"**/.git": true,
"**/.svn": true,
"**/.hg": true,
"**/CVS": true,
"**/.DS_Store": true,
"**/Thumbs.db": true,
"**/*.o": true,
"**/*.su": true,
"**/*.cmd": true,
"Documentation":true
},
"C_Cpp.intelliSenseEngine": "default",
"files.associations": {
"module.h": "c",
"fs.h": "c",
"uaccess.h": "c",
"init.h": "c",
"cdev.h": "c",
"io.h": "c"
},
"C_Cpp.errorSquiggles": "disabled"
}
字符驱动源文件模板
temp.c
#include <linux/module.h>
#include <linux/init.h>
#include <linux/cdev.h>
#include <linux/fs.h>
#include <linux/uaccess.h>
#include <asm/io.h>
MODULE_LICENSE("GPL");
MODULE_AUTHOR("pjw6");
#define DEV_NAME "my_char_device"
#define DEV_COUNT 1
struct my_dev
{
struct cdev cdev;
struct class *my_class;
struct device *my_device;
dev_t devid;
int major;
int minor;
};
struct my_dev mydev;
static int drv_open(struct inode *inode, struct file *file)
{
file->private_data = &mydev;
printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
return 0;
}
static int drv_release(struct inode *inode, struct file *file)
{
printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
file->private_data = NULL;
return 0;
}
static ssize_t drv_read(struct file *file, char __user *buffer, size_t size, loff_t *pos)
{
struct my_dev *dev = (struct my_dev *)file->private_data;
printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
return 0;
}
static ssize_t drv_write(struct file *file, const char __user *buffer, size_t size, loff_t *ppos)
{
struct my_dev *dev = (struct my_dev *)file->private_data;
printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
return 0;
}
static const struct file_operations fops = {
.owner = THIS_MODULE,
.read = drv_read,
.write = drv_write,
.open = drv_open,
.release = drv_release,
};
static int __init modinit(void)
{
int ret = 0;
printk("hello,kernel!\n");
/*注册字符设备号*/
// mydev.major = 0;
if (mydev.major)
{
mydev.devid = MKDEV(mydev.major, 0);
ret = register_chrdev_region(mydev.devid, DEV_COUNT, DEV_NAME);
}
else
{
ret = alloc_chrdev_region(&mydev.devid, mydev.major, DEV_COUNT, DEV_NAME);
mydev.major = MAJOR(mydev.devid);
mydev.minor = MINOR(mydev.devid);
}
if (ret < 0)
{
printk("my chrdev_region err!\n");
goto DevidError;
}
printk("my_chrdev_major = %d, my_chrdev_minor = %d\n", mydev.major, mydev.minor);
/*创建设备类*/
mydev.my_class = class_create(DEV_NAME);
if (IS_ERR(mydev.my_class))
{
printk("device class can not be created\n");
goto ClassError;
}
/*自动创建设备节点*/
mydev.my_device = device_create(mydev.my_class, NULL, mydev.devid, NULL, DEV_NAME);
if (IS_ERR(mydev.my_device))
{
printk("can not create device file!\n");
goto DeviceError;
}
/*注册字符设备*/
cdev_init(&mydev.cdev, &fops);
ret = cdev_add(&mydev.cdev, mydev.devid, DEV_COUNT);
if (ret < 0)
{
printk("registering of device to kernel failed!\n");
goto AddError;
}
return 0;
AddError:
device_destroy(mydev.my_class, mydev.devid);
DeviceError:
class_destroy(mydev.my_class);
ClassError:
unregister_chrdev_region(mydev.devid, DEV_COUNT);
DevidError:
return -1;
}
static void __exit modexit(void)
{
cdev_del(&mydev.cdev);
device_destroy(mydev.my_class, mydev.devid);
class_destroy(mydev.my_class);
unregister_chrdev_region(mydev.devid, DEV_COUNT);
printk("goodbye,kernel!\n");
}
module_init(modinit);
module_exit(modexit);
编译
Makefile
obj-m += temp.o # 修改为你的驱动文件的名字,即将temp.c替换为temp.o,xxx.c替换为xxx.o
CROSS_COMPILE := aarch64-linux-gnu-
CC := $(CROSS_COMPILE)gcc
LD := $(CROSS_COMPILE)ld
KERNELDIR := /home/pjw6/Documents/kernel/linux # 替换为你编译内核的目录
CURRENTPATH := $(shell pwd)
ARCH := arm64
all:
$(MAKE) -C $(KERNELDIR) M=$(CURRENTPATH) modules ARCH=$(ARCH) CROSS_COMPILE=$(CROSS_COMPILE)
clean:
$(MAKE) -C $(KERNELDIR) M=$(CURRENTPATH) clean ARCH=$(ARCH) CROSS_COMPILE=$(CROSS_COMPILE)
编译、加载及卸载过程
# 编译
make
# 如果需要清除编译生成的文件,可以使用
make clean
# 加载驱动
# 先将生成的.ko文件转移到树莓派上,可以使用ftp、nfs、scp等
# 通过ssh或者串口连接树莓派,进入到你转移的.ko目录下
insmod ./xxx.ko # 将xxx替换为你的驱动文件
# 卸载驱动
remod xxx # xxx为你安装的驱动名
# 查看输出信息
dmesg | tail -n 10
# 如果有类似输出,则测试成功
# [ 3448.835077] Hello kernel!
# [ 3635.604023] Goodbye kernel!
评论区