STM32MP157驱动开发——字符设备驱动

2023-05-16

一、简介

        字符设备是 Linux 驱动中最基本的一类设备驱动,字符设备就是一个一个字节,按照字节
流进行读写操作的设备,读写数据是分先后顺序的。比如我们最常见的点灯、按键、 IIC、 SPI,
LCD 等等都是字符设备,这些设备的驱动就叫做字符设备驱动。

        驱动加载成功以后会在“/dev”目录下生成一个相应的文件,应用程序通过对这个名为“/dev/xxx” (xxx 是具体的驱动文件名字)的文件进行相应的操作即可实现对硬件的操作。

        Linux 驱动有两种运行方式,第一种就是将驱动编译进 Linux 内核中,这样当 Linux 内核启动的时候就会自动运行驱动程序。第二种就是将驱动编译成模块(Linux 下模块扩展名为.ko),在Linux 内核启动以后使用“modprobe”或者“insmod”命令加载驱动模块。

        在原子哥教程中,统一采用模块式开发,使用modprobe命令挂载(因为insmod不能解决模块的依赖关系),好处是开发方便,修改完驱动代码后只需要编译模块即可,不用编译整个Linux代码。当驱动开发完毕确认无误后再编译进Linux内核中。

二、设备驱动组成

1.一个简单的字符设备驱动模板: 

/* 打开设备 */
static int chrtest_open(struct inode *inode, struct file *filp)
{
    /* 用户实现具体功能 */
    return 0;
}

/* 从设备读取 */
static ssize_t chrtest_read(struct file *filp, char __user *buf, size_t cnt, loff_t *offt)
{
    /* 用户实现具体功能 */
    return 0;
}

/* 向设备写数据 */
static ssize_t chrtest_write(struct file *filp, const char __user *buf, size_t cnt, loff_t *offt)
{
 /* 用户实现具体功能 */
    return 0;
}

/* 关闭/释放设备 */
static int chrtest_release(struct inode *inode, struct file *filp)
{
    /* 用户实现具体功能 */
    return 0;
}

static struct file_operations test_fops = {
    .owner = THIS_MODULE,
    .open = chrtest_open,
    .read = chrtest_read,
    .write = chrtest_write,
    .release = chrtest_release,
};

/* 驱动入口函数 */
static int __init xxx_init(void)
{
    /* 入口函数具体内容 */
    int retvalue = 0;

    /* 注册字符设备驱动 */
    retvalue = register_chrdev(200, "chrtest", &test_fops);
    if(retvalue < 0){
        /* 字符设备注册失败,自行处理 */
        //.....
    }
    return 0;
}

/* 驱动出口函数 */
static void __exit xxx_exit(void)
{
    /* 注销字符设备驱动 */
    unregister_chrdev(200, "chrtest");
}

/* 将上面两个函数指定为驱动的入口和出口函数 */
module_init(xxx_init);
module_exit(xxx_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("name");

        里面定义了一个file_operations结构体,用于描述设备的操作函数,包括open、read、write、release等。__init和__exit为设备的挂载和卸载函数,隐式声明并在模块的启动时调用。最后还要注入使用的协议和作者信息。

2.设备号

        Linux 中每个设备都有一个设备号,设备号由主设备号和次设备号两部分组成,主设备号表示某一个具体的驱动,次设备号表示使用这个驱动的各个设备。Linux使用一个dev_t变量表示设备号,dev_t 其实就是 unsigned int 类型,是一个 32 位的数据类型。这 32 位的数据构成了主设备号和次设备号两部分,其中高 12 位为主设备号,低 20 位为次设备号。因此 Linux系统中主设备号范围为 0~4095。

        设备号的分配有静态分配和动态分配两种,Linux社区推荐使用动态分配。使用alloc_chrdev_region()和unregister_chrdev_region()申请和释放。

int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count, const char *name)
/*
dev:保存申请到的设备号。
baseminor: 次设备号起始地址, alloc_chrdev_region 可以申请一段连续的多个设备号,这
    些设备号的主设备号一样,但是次设备号不同,次设备号以 baseminor 为起始地址地址开始递
    增。一般 baseminor 为 0,也就是说次设备号从 0 开始。
count: 要申请的设备号数量。
name:设备名字。
*/

void unregister_chrdev_region(dev_t from, unsigned count)
/*
from:要释放的设备号。
count:表示从 from 开始,要释放的设备号数量
*/

三、chrdevbase字符设备开发实验

        按照原子哥教程配置VSCode开发环境,有兴趣的可以在Windows下配置远程开发环境连接到虚拟机下进行开发,配置较繁琐但使用起来更方便。此外根据个人喜好修改代码颜色。

        接下来就是编写实验程序,一开始学习建议自己手敲,并理解代码的构成方式。

#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/ide.h>
#include <linux/init.h>
#include <linux/module.h>

#define CHRDEVBASE_MAJOR 200                /* 主设备号 */
#define CHRDEVBASE_NAME  "chrdevbase"       /*设备名*/

static char readbuf[100];
static char writebuf[100];
static char kerneldata[] = {"kernel data!"};


/*
* @description : 打开设备
* @param – inode : 传递给驱动的 inode
* @param - filp : 设备文件, file 结构体有个叫做 private_data 的成员变量
* 一般在 open 的时候将 private_data 指向设备结构体。
* @return : 0 成功;其他 失败
*/
static int chrdevbase_open(struct inode *inode, struct file *filp)
{
    printk("chrdevbase open!\r\n");
    return 0;
}

/*
* @description : 从设备读取数据
* @param - filp : 要打开的设备文件(文件描述符)
* @param - buf : 返回给用户空间的数据缓冲区
* @param - cnt : 要读取的数据长度
* @param - offt : 相对于文件首地址的偏移
* @return : 读取的字节数,如果为负值,表示读取失败
*/
static ssize_t chrdevbase_read(struct file *filp, char __user *buf, size_t cnt, loff_t *offt)
{
    int retvalue=0;

    /*向用户空间发送数据*/
    memcpy(readbuf, kerneldata, sizeof(kerneldata));
    retvalue = copy_to_user(buf, readbuf, cnt);
    if(retvalue == 0){
        printk("kerneldata senddata ok!\r\n");
    } else {
        printk("kerneldata senddata failed!\r\n");
    }

    printk("chrdevbase read!\r\n");
    return 0;
}

/*
* @description : 向设备写数据
* @param - filp : 设备文件,表示打开的文件描述符
* @param - buf : 要写给设备写入的数据
* @param - cnt : 要写入的数据长度
* @param - offt : 相对于文件首地址的偏移
* @return : 写入的字节数,如果为负值,表示写入失败
*/
static ssize_t chrdevbase_write(struct file *filp, const char __user *buf, size_t cnt, loff_t *offt)
{
    int retvalue = 0;

    /*接收用户空间传递给内核的数据并打印*/
    retvalue = copy_from_user(writebuf, buf, cnt);
    if(retvalue == 0){
        printk("kernel recevdata:%s\r\n", writebuf);
    } else {
        printk("kernel recevdata failed!\r\n");
    }

    return 0;
}

/*
* @description : 关闭/释放设备
* @param - filp : 要关闭的设备文件(文件描述符)
* @return : 0 成功;其他 失败
*/
static int chrdevbase_release(struct inode *inode, struct file *filp)
{
    printk("chrdevbase release!\r\n");
    return 0;
}

/*
*设备操作函数结构体
*/
 static struct file_operations chrdevbase_fops = {
    .owner = THIS_MODULE,
    .open = chrdevbase_open,
    .read = chrdevbase_read,
    .write = chrdevbase_write,
    .release = chrdevbase_release,
};

/*
* @description : 驱动入口函数
* @param : 无
* @return : 0 成功;其他 失败
*/
static int __init chrdevbase_init(void)
{
    int retvalue = 0;

    /*注册字符设备驱动*/
    retvalue = register_chrdev(CHRDEVBASE_MAJOR, CHRDEVBASE_NAME, &chrdevbase_fops);

    if(retvalue < 0){
        printk("chrdevbase driver register failed!\r\n");
    }
    printk("chrdevbase_init()\r\n");
    return 0;
}

/*
* @description : 驱动出口函数
* @param : 无
* @return : 无
*/
static void __exit chrdevbase_exit(void)
{
    /*注销字符设备驱动*/
    unregister_chrdev(CHRDEVBASE_MAJOR, CHRDEVBASE_NAME);
    printk("chrdevbase_exit()\r\n");
}

/*将入口和出口函数指定为驱动的入口和出口*/
module_init(chrdevbase_init);
module_exit(chrdevbase_exit);

/*LISENCE和作者信息*/
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Amonter");
MODULE_INFO(intree, "Y");

与模板中的格式及总体信息基本相同。其中printk()为内核态的printf(),有8个消息输出级别,0的级别最高,7的级别最低,默认级别为7。在Linux Kernel的图形化配置界面中可以在Kernel hacking->printk and dmesg options路径下,设置Default console loglevel的值来设置默认终端消息级别,设置Default message loglevel来设置默认消息级别。

#define KERN_SOH "\001"
#define KERN_EMERG KERN_SOH "0" /* 紧急事件,一般是内核崩溃 */
#define KERN_ALERT KERN_SOH "1" /* 必须立即采取行动 */
#define KERN_CRIT KERN_SOH "2" /* 临界条件,比如严重的软件或硬件错误*/
#define KERN_ERR KERN_SOH "3" /* 错误状态,一般设备驱动程序中使用KERN_ERR报告硬件错误 */
#define KERN_WARNING KERN_SOH "4" /* 警告信息,不会对系统造成严重影响 */
#define KERN_NOTICE KERN_SOH "5" /* 有必要进行提示的一些信息 */
#define KERN_INFO KERN_SOH "6" /* 提示性的信息 */
#define KERN_DEBUG KERN_SOH "7" /* 调试信息

四、编写测试APP

#include <stdio.h>
#include "unistd.h"
#include "sys/types.h"
#include "sys/stat.h"
#include "fcntl.h"
#include "string.h"
#include "stdlib.h"

static char userdata[] = {"user data!"};

int main(int argc, char *argv[])
{
    int fd, retvalue;
    char *filename;
    char readbuf[100], writebuf[100];

    if(argc != 3){
        printf("Error Usage!\r\n");
        return -1;
    }

    filename = argv[1];

    /*打开驱动文件*/
    fd = open(filename, O_RDWR);
    if(fd < 0){
        printf("Can't open file %s\r\n", filename);
        return -1;
    }

    if(atoi(argv[2]) == 1){
        /*从驱动文件读取数据*/
        retvalue = read(fd, readbuf, 50);
        if(retvalue < 0){
            printf("read file %s failed!\r\n", filename);
        } else {
            /*读取成功,打印出读取结果*/
            printf("read data %s\r\n", readbuf);
        }
    }

    if(atoi(argv[2]) == 2){
        /*向设备驱动写数据*/
        memcpy(writebuf, userdata, sizeof(userdata));
        retvalue = write(fd, writebuf, 50);
        if(retvalue < 0){
            printf("write file %s failed!\r\n", filename);
        }
    }

    /*关闭设备*/
    retvalue = close(fd);
    if(retvalue < 0){
        printf("Can't close file %s\r\n", filename);
        return -1;
    }
    return 0;
}

        在运行时可以输入以下的命令示例来对设备进行操作。命令一共有三个参数“./chrdevbaseApp”、“/dev/chrdevbase”和“1”,这三个参数分别对应 argv[0]、 argv[1]和 argv[2]。第一个参数表示运行 chrdevbaseAPP 这个软件,第二个参数表示测试APP要打开/dev/chrdevbase这个设备。第三个参数就是要执行的操作, 1表示从chrdevbase中读取数据, 2 表示向 chrdevbase 写数据。

/chrdevbaseApp /dev/chrdevbase 1

五、编译驱动程序和测试App

1.首先编译设备驱动程序

        需要将chedevbase.c文件编译成.ko模块。在同级目录下创建Makefile,内容如下。

KERNELDIR := ~/my_linux/linux-5.5.15    #注意修改路径,建议用绝对路径
CURRENT_PATH := $(shell pwd)

obj-m := chrdevbase.o

build: kernel_modules

kernel_modules:
$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) modules
clean:
$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) clean

        在这里遇到一个编译错误,使用Linux5.15内核,编译过程中会找不到头文件<linux/ide.h>,查找Linux5.15版本的内核资料,发现原因是5.15版本的内核对设备块部分做出了较大修改。鉴于目前没有相关的教程,直接手动添加ide.h文件也不太现实。在此建议使用V2.4版本-Linux5.10以前的STM32固件包,就是V2.0或V2.2固件版本,变动不大,使用起来更方便。这里就先使用原子哥资料中的01、程序源码-->教程Linux系统源码中的Linux5.4版本内核。

        更换内核后,修改VSCode环境中的头文件路径,更改Makefile中的路径,然后编译出chrdevbase.ko,测试App程序只有一个源文件,使用交叉编译工具单独编译:

arm-none-linux-gnueabihf-gcc chrdevbaseApp.c -o chrdevbaseApp

编译出的文件如下:

 六、运行测试

        ①设置开发板U-Boot中的启动参数:

将bootcmd修改为从tftp服务器加载镜像和设备树启动:

#设置内核启动命令
setenv bootcmd 'tftp c2000000 uImage;tftp c4000000 stm32mp157d-atk.dtb;bootm c2000000 - c4000000'
#如果只想在本次生效可以不保存
saveenv


将启动参数bootargs修改为挂载网络文件系统:

#设置bootargs,其中格式nfsroot=serverip:nfs_pwd,ip=board_ip:server_ip:gatewayip....
setenv bootargs 'console=ttySTM0,115200 root=/dev/nfs nfsroot=192.168.68.221:/home/amonter/linux/nfs/rootfs,proto=tcp rw ip=192.168.68.226:192.168.68.221:192.168.68.1:255.255.255.0::eth0:off'

检查根文件系统中是否有/lib/modules/5.4.31目录,没有的话需要手动创建,然后将编译出的.ko文件和App文件复制进去。然后使用如下命令进行测试。

#挂载驱动模块(不加.ko后缀)
modprobe chrdevbase
#查看系统已有模块
lsmod
#查看系统中有哪些设备
cat /proc/devices
#创建设备节点文件
mknod /dev/chrdevbase c 200 0
#App测试
./chrdevbaseApp /dev/chrdevbase 1
#卸载驱动模块
rmmod chrdevbase

        其中“mknod”是创建节点命令,“/dev/chrdevbase”是要创建的节点文件,“c”表示这是个
字符设备,“ 200”是设备的主设备号,“ 0”是设备的次设备号。主设备可以在驱动代码中指定,也可以使用动态分配生成,在cat /proc/devices命令中可以查询到。App测试命令,参数1为读取,2为写入。

 

 

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

STM32MP157驱动开发——字符设备驱动 的相关文章

随机推荐

  • ubuntu在更新软件时出现E: Release file for http://security.ubuntu.com/ubuntu/dists/bionic-security/InRelease

    问题 E Release file for http security ubuntu com ubuntu dists bionic security InRelease is not valid yet invalid for anoth
  • LeetCode之二分查找实战2之第一个错误的版本(278)、猜数字大小(374)

    二分查找2 1 第一个错误的版本 278 2 猜数字大小 xff08 374 xff09 1 第一个错误的版本 278 题目描述 xff1a 简单题 你是产品经理 xff0c 目前正在带领一个团队开发新的产品 不幸的是 xff0c 你的产品
  • python之逻辑回归项目实战——信用卡欺诈检测

    信用卡欺诈检测 1 项目介绍2 项目背景3 分析项目4 数据读取与分析4 1 加载数据4 2 查看数据的标签分布 5 数据预处理5 1 特征标准化5 2 使用下采样解决样本数据不均衡 6 训练数据即划分数据集7 模型建立7 1 sklear
  • C++ 全局变量的跨文件使用

    文章目录 前言一 extern的使用二 容易犯的错误 前言 在写C 43 43 工程文件的时候 xff0c 往往会用到一些所有类都使用的数据 xff0c 比如数据文件等 xff0c 一种写法是写成静态类 xff0c 调用数据时使用类名加属性
  • VS2019使用C++创建winform界面

    用C 43 43 实现winform界面 算是对上一篇文章的补充吧 xff0c 实际上不需要那么繁琐也可以做到 事先准备 打开VS xff0c 新建一个CLR项目 如果在选项中没有发现CLR项目 xff1a 1 找到Visual Studi
  • c++面试题(亲测常问)

    注意 xff1a 此题为我自己面试被问到的 xff0c 及一些摘抄的 xff0c 如有侵权请联系我马上删除 xff01 1 2 32位指针地址所占字节数 为四 举例说明 xff1a char p char test 10 p 61 test
  • torchvision与torch的对应关系及下载链接

    https github com pytorch vision 另外 xff1a Ubuntu18下编译安装torchvision C 43 43 API的详细过程
  • Logisim计算机组成原理实验16位无符号比较器设计

    Logisim用4位无符号比较器构建16位无符号比较器 4位无符号比较器设计思路表达式构建 16位无符号比较器构建思路构建 4位无符号比较器设计 思路 不同位之间进行比较 xff0c 高位优先 真值表太麻烦 xff0c 可以利用表达式进行构
  • React+hooks+TS练习

    一 初始化项目 通过create react app命令创建项目 xff0c template表示使用typescript xff08 node版本高于14才能使用npx xff09 npx create span class token
  • 基于Python的信用卡欺诈检测机器学习案例报告

    本报告借助Python语言探究了在机器学习中 面对一个大型的人与人之间交易的数据集 如何尽快处理大量数据并区分某交易记录是正常的用户行为还是潜在的信用卡欺诈行为 最终通过构建分类模型来对欺诈交易进行分类区分 并通过恰当的方式对构建的模型进行
  • 一个既有趣又简单的整人代码——关机代码

    这一篇博客来的比我的预计时间要长啊 xff0c 在这一周多的时间里 xff0c 我几乎很少有休息和出去玩耍的时间 说实话 xff0c 这样忙碌的生活给我的感觉还是蛮好的 xff0c 让我有一种很充实的感觉 xff0c 有种自己在与时间赛跑的
  • 【CMake】CMakeList编写整理

    什么是CMake 如果软件想跨平台 xff0c 必须要保证能够在不同平台编译 而如果使用 Make 工具 xff0c 就得为每一种标准写一次 Makefile CMake 就是针对上面问题所设计的工具 xff1a 它首先允许开发者编写一种平
  • 解决 WARN util.NativeCodeLoader: Unable to load native-hadoop library for your platform...警告

    解决 WARN util NativeCodeLoader Unable to load native hadoop library for your platform using builtin java classes where ap
  • Vue获取数组的数组数据

    Q xff1a 如何在vue获取数组的数组 xff1f A xff1a 用到js的map对象方法 一 data里要先定义好有两个数组 二 主要代码 这样就可以获取到数组的子数组数据
  • Ubuntu18.04 GAAS学习笔记

    GAAS学习笔记 1 环境构建1 1 依赖项安装1 2 ros安装1 3 MAVROS安装1 4 PX4 Firmware安装 全程参考官方文档 xff0c 总结遇见的错误 xff1a https gaas gitbook io guide
  • ArUco标定板生成与打印

    链接如下 xff1a https span class token punctuation span span class token operator span chev span class token punctuation span
  • ROS工作空间与功能包

    工作空间 工作空间 xff08 workspace xff09 是一个存放工程开发相关文件的文件夹 xff0c 其目录下有 xff1a src xff1a 代码空间 xff08 Source Space xff09 build xff1a
  • Ubuntu20.04安装UHD及GUN Radio3.9

    目录 1 安装UHD依赖库及UHD 2 安装GNU Radio3 9 3 1 安装UHD依赖库及UHD 总结自 xff1a USRP Hardware Driver and USRP Manual Building and Installi
  • ros安装的依赖问题

    问题描述 xff1a ros kinetic desktop full 依赖 ros kinetic desktop 但是它将不会被安装 依赖 ros kinetic perception 但是它将不会被安装 依赖 ros kinetic
  • STM32MP157驱动开发——字符设备驱动

    一 简介 字符设备是 Linux 驱动中最基本的一类设备驱动 xff0c 字符设备就是一个一个字节 xff0c 按照字节 流进行读写操作的设备 xff0c 读写数据是分先后顺序的 比如我们最常见的点灯 按键 IIC SPI xff0c LC