侧边栏壁纸
博主头像
Into The Abyss 博主等级

My Life is a Death Race

  • 累计撰写 34 篇文章
  • 累计创建 7 个标签
  • 累计收到 4 条评论

目 录CONTENT

文章目录

树莓派linux字符设备驱动模板

Administrator
2024-03-18 / 0 评论 / 0 点赞 / 368 阅读 / 0 字

最近在使用树莓派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.jsonsettings.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!
0

评论区