Rk3568驱动开发_驱动实现流程以及本质_3

news/2025/2/25 11:36:47

1设备号:

在这里插入图片描述
cat /proc/devices

编写驱动模块需要要想加载到内核并与设备正常通信,那就需要申请一个设备号,用cat /proc/devices可以查看已经被占用的设备号

设备号有什么用?不同设备其驱动实现不同用设备号去区分,例如字符驱动键盘,块设备驱动固态硬盘,这两种设备不同驱动实现方式不同所以用不同主设备号区分,什么会是从设备号?例如串口,一台电脑接入两根串口,这俩串口用的驱动是一样的,但是要区分不同的串口,就用从设备号区分

2.注册节点:

为什么要注册节点?Linux上一切皆文件,例如你写了串口驱动,你要调用接到你电脑上的串口,串口接到你的电脑上电脑识别后就会创建这样一个节点,供open函数打开串口做后续操作

在这里插入图片描述

目前刚学习是手动创建,后面估计会用一些方法自动创建

3.实现流程:

驱动代码:

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/fs.h>


#define CHREDEVBASE_MAJOR 200  // 主设备号
#define CHRDEVNAME  "chrdevbase"  // 名字



static int chrdevbase_open(struct inode* inode, struct file* filp){
    printk("chrdevbase_open\r\n");
    return 0;
}

static int chrdevbase_release(struct inode* inode, struct file* filp){
    printk("chrdevbase_release\r\n");
    return 0;
}

static ssize_t chrdevbase_read(struct file* filp, __user char* buf, size_t count, 
                                loff_t* ppos){
    printk("hrdevbase_read\r\n");
    return 0;                
}

static ssize_t chrdevbase_write(struct file* filp, const char __user *buf, 
                        size_t count, loff_t* ppos){
    printk("chrdevbase_write\r\n");
    return 0;
}


// 字符设备操作集合
static struct file_operations chrdevbase_fops={
    .owner = THIS_MODULE,       // 属于这个驱动模块
    .open = chrdevbase_open,
    .release = chrdevbase_release,
    .read = chrdevbase_read,
    .write = chrdevbase_write,
};



static int __init chrdevbase_init(void){   

    int ret = 0;

    printk("chrdevbase_init\r\n");
    // 注册字符设备
    ret = register_chrdev(CHREDEVBASE_MAJOR, CHRDEVNAME, &chrdevbase_fops);
    
    if(ret < 0){
        printk("chrdevbase init failed\r\n");
        unregister_chrdev(CHREDEVBASE_MAJOR, CHRDEVNAME);
    }

    return 0;
}

static void __exit chrdevbase_exit(void){
    printk("chrdevbase_exit\r\n");
}

// 核心修正点:添加以下声明
MODULE_LICENSE("GPL");          // 必须声明许可证(例如GPL)
MODULE_AUTHOR("Narnat");      // 可选:作者信息
MODULE_DESCRIPTION("chrdevbase"); // 可选:模块描述

// 模块入口与出口
module_init(chrdevbase_init);
module_exit(chrdevbase_exit);

makefile:

# 设置绝对路径(避免相对路径引发的错误)
KERNELDIR := /home/saisi/RK3568/SDK/linux/rk3568_linux_sdk/kernel
CURRENT_PATH := $(shell pwd)

# 必须明确指定架构和交叉编译器!!!
ARCH := arm64
# 关键修正点:使用ARM官方工具链的正确前缀和路径
TOOLCHAIN_PATH := /usr/local/arm-gnu-toolchain-12.3.rel1-x86_64-aarch64-none-linux-gnu/bin
CROSS_COMPILE := $(TOOLCHAIN_PATH)/aarch64-none-linux-gnu-

# 模块名称
obj-m := chrdevbase.o

build: kernel_modules

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

clean:
	$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) ARCH=$(ARCH) clean
	rm -rf app
app:
	/usr/local/arm-gnu-toolchain-12.3.rel1-x86_64-aarch64-none-linux-gnu/bin/aarch64-none-linux-gnu-gcc chrdevbaseApp.c -o app

app代码:

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


int main(int argc, char* argv[]){

    int ret = 0;
    int fd;
    char readbuf[10], writebuf[50];
    
    char* filename;
    filename = argv[1];

    fd = open(filename, O_RDWR);

    if(fd < 0){
      printf("cant open file %s\r\n", filename);
      return -1;
    }

    ret =  read(fd, readbuf, 10);
    if(ret < 0){
      printf("read file %s failed\r\n", filename);
      return -1;
    }

    ret = write(fd, writebuf, 50);
    if(ret < 0){
      printf("write file %s failed!\r\n", filename);
    }

    close(fd);

    return 0;
}

驱动代码中register_chrdev和unregister_chrdev是用于注册字符驱动和注销字符驱动的函数,也就是指明你的驱动类型其中要指定你的主设备号和名字把这些写好后挂载模块后能在系统里找到你注册的信息

在这里插入图片描述
lsmod显示的只是你的这个模块名字

在这里插入图片描述

注意:注册完节点后,我们的这个驱动会被当做一个文件,就像linux上的摄像头设备一样/dev/video0也是一个文件

在这里插入图片描述
用我们写的程序在应用层去打开、读、写的时候本质会调用到我们自己写的驱动

在这里插入图片描述

这张图片的含金量还在增加

在这里插入图片描述

4.最后总结:

Linux中为何一切皆文件?open函数打开txt文件调用的是读取txt文件的函数,open函数打开串口是调用串口的驱动函数,打开chrdevbase文件就是调用我们自己写的驱动函数,也就是说每一个设备都有其对应的实现驱动模块,open打开他的时候就会去调用这个接口,我们要做的就是实现这些驱动的接口,创建一个节点/dev节点也就是所谓文件,open调用这个文件的时候能正确的调用我们的驱动模块,实现我们的目的

最开始没有学驱动的时候我摸不着头脑,觉得驱动它遥不可及,当我真正下定决心去学的时候当我迈出第一步的时候我发现它并非不可能,无论是学习还是人生,最难的应该是迈出第一步,试试呗


http://www.niftyadmin.cn/n/5865443.html

相关文章

(六)趣学设计模式 之 代理模式!

目录 一、啥是代理模式&#xff1f;二、为什么要用代理模式&#xff1f;三、代理模式的实现方式1. 静态代理2. JDK动态代理3. CGLIB动态代理 四、三种代理的对比五、代理模式的优缺点六、代理模式的应用场景七、总结 &#x1f31f;我的其他文章也讲解的比较有趣&#x1f601;&a…

Directed acyclic graph [DAG]有向无环图 应用场景汇总与知名开源库实现细节说明

文章大纲 1. 任务调度与依赖管理Spark 中的 DAG2. 编译器优化3. 数据流分析 -- Dagre 中的DAG 待查4. 版本控制系统5. 区块链与加密货币6. 拓扑排序7. 网络路由8. 机器学习与深度学习 :TensorFlow中,DAG(有向无环图)被广泛用于表示计算图(Computation Graph)**TensorFlow…

请谈谈 Vue 中的 key 属性的重要性,如何确保列表项的唯一标识?

1. Key属性的核心作用&#xff08;附代码对比&#xff09; // 错误示例&#xff1a;未使用key的列表渲染 <template><ul><li v-for"item in items">{{ item.text }}</li></ul> </template>// 正确示例&#xff1a;使用唯一key的…

3.18 ReAct 理论实战:构建动态推理-行动循环的企业级 Agent

ReAct 理论实战:构建动态推理-行动循环的企业级 Agent 关键词:ReAct 理论实践, 动态工具调用, 反思迭代机制, 企业级 Agent 架构, LangChain 集成 1. ReAct 理论核心要素解析 1.1 传统 Agent vs ReAct Agent 架构对比 #mermaid-svg-t2TFPvWG94jJjpRG {font-family:"tr…

硅基流动---deepseek 部署

方式1&#xff1a;APIDeepSeek服务器 创建自己的应用的方式或者使用一些客户端访问&#xff08;官方推荐&#xff09; 通过下面连接可以查看什么客户端。 awesome-deepseek-integration/README_cn.md at main deepseek-ai/awesome-deepseek-integration GitHubhttps://git…

加油小程序实战教程01需求分析

目录 1. 产品概述2. 产品目标3 用户角色分析4. 功能模块分析4.1 前台小程序功能4.1.1 导航条4.1.2 加油模块4.1.3 网点列表4.1.4 e享加油流程4.1.5 钱包模块4.1.6 充值功能4.1.7 电子券与积分管理4.1.8 订单管理4.1.9 “我的”模块 4.2 后台管理系统4.2.1 用户及权限管理4.2.2 …

w~视觉~合集13

我自己的原文哦~ https://blog.51cto.com/whaosoft/13384038 #xxx w视觉合集13~17没了.... #ViTAR 作者提出了一种新颖的架构&#xff1a;任意分辨率的视觉 Transformer &#xff08;ViTAR&#xff09;。ViTAR中的自适应标记合并功能使模型能够自适应地处理可变分辨率图像…

kafka小白基础知识

一、Kafka 入门 &#xff08;一&#xff09;Kafka 简介 Kafka 是一个开源的分布式流处理平台&#xff0c;最初由 LinkedIn 开发&#xff0c;后来贡献给了 Apache 软件基金会。它被设计用于处理实时数据流&#xff0c;具有高吞吐量、可扩展性、持久性和容错性等特点。Kafka 主要…