在实验本节代码前,准备环境一个ubuntu物理机或者虚拟机,下载并使能交叉编译工具链,然后编译好内核源码,参考《编译内核镜像并在云实验室开发板运行》。
2. 开始第一个内核字符驱动编写和编译运行
在你要写代码的目录下用touch新建两个文件,一个源码文件,一个Makefile。如下:
~/linux_driver/ 02_virtchardev$ touch virtchardev.c
~/linux_driver/ 02_virtchardev$ touch Makefile
~/linux_driver/ 02_virtchardev$ vim virtchardev.c
将下面的代码复制到helloworld_drvmod.c, 如下:
/*
* Copyright 2024 NXP
*
* SPDX-License-Identifier: BSD-3-Clause
*/
#include <linux/module.h>
#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/cdev.h>
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Cloud Lab");
#define pr_dbginfo() printk(KERN_INFO "%s: %d\n", __FUNCTION__, __LINE__ )
#define VIRTCHARDEV_NAME "virtdev"
static unsigned count = 1;
struct virtchardev{
dev_t virtdev_id;
struct cdev virtdev_cdev;
struct class *class;
struct device *device;
int virtdev_major;
int virtdev_minor;
};
struct virtchardev virtdev;
static int virtdev_open(struct inode *inode, struct file *filp)
{
pr_dbginfo();
return 0;
}
static int virtdev_close(struct inode *inode, struct file *filp)
{
pr_dbginfo();
return 0;
}
static ssize_t virtdev_read(struct file *filp, char __user *buf, size_t size,
loff_t *offset)
{
pr_dbginfo();
return 0;
}
static ssize_t virtdev_write(struct file *filp, const char __user *buf, size_t size,
loff_t *offset)
{
pr_dbginfo();
return 0;
}
static struct file_operations virtdev_fops = {
.owner = THIS_MODULE,
.open = virtdev_open,
.release = virtdev_close,
.read = virtdev_read,
.write = virtdev_write,
};
static int __init virtdev_init(void)
{
alloc_chrdev_region(&virtdev.virtdev_id, 0, count,
VIRTCHARDEV_NAME );
virtdev.virtdev_major = MAJOR(virtdev.virtdev_id);
cdev_init(&virtdev.virtdev_cdev, &virtdev_fops);
cdev_add(&virtdev.virtdev_cdev, virtdev.virtdev_id, count);
virtdev.class = class_create(THIS_MODULE, VIRTCHARDEV_NAME);
device_create(virtdev.class, NULL, virtdev.virtdev_id,
NULL, VIRTCHARDEV_NAME);
printk("Register ok %s: %d\n", __FUNCTION__, __LINE__ );
pr_dbginfo();
return 0;
}
static void __exit virtdev_exit(void)
{
cdev_del(&virtdev.virtdev_cdev);
unregister_chrdev_region(virtdev.virtdev_id, count);
device_destroy(virtdev.class, virtdev.virtdev_id);
class_destroy(virtdev.class);
printk("unregister virtdev %s: %d\n", __FUNCTION__, __LINE__ );
pr_dbginfo();
}
module_init(virtdev_init);
module_exit(virtdev_exit);~
编写Makefile:
PWD := $(shell pwd)
KERDIR := /home/test/kernel_build/linux-imx
obj-m += helloworld_drvmod.o
all:
make -C $(KERDIR) M=$(PWD) modules
clean:
make -C $(KERDIR) M=$(PWD) clean
3. 编译:
~/linux_driver/ 02_virtchardev$ export CROSS_COMPILE=~/toolchain/arm-gnu-toolchain-13.3.rel1-x86_64-aarch64-none-linux-gnu/bin/aarch64-none-linux-gnu-
~/linux_driver/ 02_virtchardev$ export ARCH=arm64
~/linux_driver/ 02_virtchardev$ make
编译完成后当前目录会有如下文件:
~/linux_driver/ 02_virtchardev$ ls
Makefile modules.order Module.symvers virtchardev.c virtchardev.ko virtchardev.mod virtchardev.mod.c virtchardev.mod.o virtchardev.o
4. 替换云实验室开发板镜像并重新启动:
根据《编译内核镜像并在云实验室开发板运行》替换dtb和image,这里仍然需要这一步,因为内核模块在加载时需要和内核编译版本相匹配上传并替换云实验室板子的dtb和Image,依次点击用户网页端下面1,2,3的位置,在点击位置2时,选择TFTP,点击3后选择文件~/kernel_build/linux-imx/arch/arm64/boot/dts/freescale/imx8mp-evk.dtb和~/kernel_build/linux-imx/arch/arm64/boot/Image。
上传成功后,点击下方PowerReset EVK按钮,重启开发板。
至此,开发板开始运行自己修改编译的linux镜像。
5. 上传自己编译的字符驱动模块
在3中编译出了第一个内核驱动程序virtchardev.ko,通过一次点击下图中的1,2,3位置,然后切换到本地对应的文件目录上传.
6. 运行字符驱动模块
执行insmod加载模块,然后执行rmmod卸载模块,log如下,可以看到当加载时,打印出了virtdev_init里的log, 当卸载时打印出了virtdev_exit里的log,执行加载卸载可以多次执行,也没有问题。