韦东山嵌入式linux系列-LED 驱动程序框架

1 回顾字符设备驱动程序框架

图中驱动层访问硬件外设寄存器依靠的是 ioremap 函数去映射到寄存器地址,然后开始控制寄存器。

那么该如何编写驱动程序?

① 确定主设备号,也可以让内核分配;
② 定义自己的 file_operations 结构体, 这是核心;
③ 实现对应的 drv_open/drv_read/drv_write 等函数,填入 file_operations 结构体;
④ 把 file_operations 结构体告诉内核:通过 register_chrdev 函数;
⑤ 谁来注册驱动程序?需要一个入口函数:安装驱动程序时,就会去调用这个入口函数;
⑥ 有入口函数就应该有出口函数:卸载驱动程序是,出口函数调用unregister_chrdev;
⑦ 其它完善:提供设备信息,自动创建设备节点: class_create,device_create;

应用程序调用open等函数最简单的方法是驱动层也提供对应的drv_open,应用程序调用read,驱动层也提供对应的drv_read等等。需要写出驱动层的函数,为了便于管理,将这些函数放到file_operations结构体中,即第一步定义对应的file_operations结构体,并实现对应的open等程序(第一步);实现完成之后,将file_operations结构体通过register_chrdev注册到内核(第二步);然后通过入口函数调用注册函数(chrdev),安装驱动程序的时候,内核会调用入口函数,完成file_operations结构体的注册(第三步);有入口函数就有出口函数(第四步)。对于字符设备驱动程序而言,file_operations结构体是核心,每一个驱动程序都对应file_operations结构体,内核中有众多file_operations结构体,怎么才能找到对应的结构体呢?

应用程序要访问驱动程序,需要打开一个设备结点,里面有主设备号,根据设备结点的主设备号在内核中找到对应的file_operations结构体。注册结构体时足以提供主设备号,可以让内核分配;最后就是完善信息,创建类,创建设备。

2 对于 LED 驱动,我们想要什么样的接口

在open函数中配置LED的引脚为输出,在write函数中确定LED和引脚电平。

3 LED 驱动能支持多个板子的基础: 分层思想

①把驱动拆分为通用的框架(leddrv.c)、具体的硬件操作(board_X.c):

②以面向对象的思想,改进代码, 抽象出一个结构体

struct led_operations {int (*init) (int which);                // 初始化LED,which是哪一个LEDint (*ctl) (int which, int status);     // 控制LED,which-哪一个LED,status-1亮,0灭
}

有初始化函数、有控制函数,每个单板相关的 board_X.c 实现自己的 led_operations 结构体,供上层的 leddrv.c 调用

4 写代码

led.drv.c

/*************************************************************************> File Name: led.drv.c> Author: Winter> Created Time: Sun 07 Jul 2024 12:35:19 AM EDT************************************************************************/#include <linux/module.h>#include <linux/fs.h>
#include <linux/errno.h>
#include <linux/miscdevice.h>
#include <linux/kernel.h>
#include <linux/major.h>
#include <linux/mutex.h>
#include <linux/proc_fs.h>
#include <linux/seq_file.h>
#include <linux/stat.h>
#include <linux/init.h>
#include <linux/device.h>
#include <linux/tty.h>
#include <linux/kmod.h>
#include <linux/gfp.h>
#include "led_operations.h"#define LED_NUM 2// 1确定主设备号,也可以让内核分配
static int major = 0;				// 让内核分配
static struct class *led_class;
struct led_operations* p_led_operations;#define MIN(a, b) (a < b ? a : b)// 3 实现对应的 drv_open/drv_read/drv_write 等函数,填入 file_operations 结构体
static ssize_t led_drv_read (struct file *file, char __user *buf, size_t size, loff_t *offset)
{printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);return 0;
}// write(fd, &val, 1);
static ssize_t led_drv_write (struct file *file, const char __user *buf, size_t size, loff_t *offset)
{int err;char status;struct inode* node;int minor;printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);// 把用户区的数据buf拷贝到内核区status,即向写到内核status中写数据err = copy_from_user(&status, buf, 1);// 根据次设备号和status控制LEDnode = file_inode(file);minor = iminor(node);p_led_operations->ctl(minor, status);return 1;
}static int led_drv_open (struct inode *node, struct file *file)
{int minor;printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);// 得到次设备号minor = iminor(node);// 根据次设备号初始化LEDp_led_operations->init(minor);return 0;
}static int led_drv_close (struct inode *node, struct file *file)
{printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);return 0;
}// 2定义自己的 file_operations 结构体
static struct file_operations led_drv = {.owner = THIS_MODULE,.open = led_drv_open,.read = led_drv_read,.write = led_drv_write,.release = led_drv_close,
};// 4把 file_operations 结构体告诉内核: register_chrdev
// 5谁来注册驱动程序啊?得有一个入口函数:安装驱动程序时,就会去调用这个入口函数
static int __init led_init(void)
{int err, i;	printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);// 注册led_drv,返回主设备号major = register_chrdev(0, "winter_led", &led_drv);  /* /dev/led */// 创建classled_class = class_create(THIS_MODULE, "led_class");err = PTR_ERR(led_class);if (IS_ERR(led_class)) {printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);unregister_chrdev(major, "led_class");return -1;}// 创建device// 根据次设备号访问多个LED
//	device_create(led_class, NULL, MKDEV(major, 0), NULL, "winter_led0"); /* /dev/winter_led0 */
//	device_create(led_class, NULL, MKDEV(major, 1), NULL, "winter_led1"); /* /dev/winter_led1 */for (i = 0; i < LED_NUM; i++){device_create(led_class, NULL, MKDEV(major, i), NULL, "winter_led%d", i);}// 入口函数获得结构体指针p_led_operations = get_board_led_operations();return 0;
}// 6有入口函数就应该有出口函数:卸载驱动程序时,出口函数调用unregister_chrdev
static void __exit led_exit(void)
{int i;printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);for (i = 0; i < LED_NUM; i++){device_destroy(led_class, MKDEV(major, i));}class_destroy(led_class);// 卸载unregister_chrdev(major, "winter_led");
}// 7其他完善:提供设备信息,自动创建设备节点: class_create,device_create
module_init(led_init);
module_exit(led_exit);MODULE_LICENSE("GPL");

board_demo.c

#include <linux/gfp.h>
#include "led_operations.h"// init函数
static int board_demo_led_init(int which)
{printk("%s %s line %d, led %d\n", __FILE__, __FUNCTION__, __LINE__, which);return 0;
}// ctl函数
static int board_demo_led_ctl(int which, char status)
{printk("%s %s line %d, led %d, %s\n", __FILE__, __FUNCTION__, __LINE__, which, status ? "on" : "off");return 0;
}static struct led_operations board_demo_led_operations = {.init = board_demo_led_init,.ctl = board_demo_led_ctl,
};// 返回结构体
struct led_operations* get_board_led_operations(void)
{return &board_demo_led_operations;
}

led_operations.h

#ifndef LED_OPERATIONS_H
#define LED_OPERATIONS_Hstruct led_operations {int (*init) (int which);                // 初始化LED,which是哪一个LEDint (*ctl) (int which, char status);     // 控制LED,which-哪一个LED,status-1亮,0灭
};// 返回结构体指针
struct led_operations* get_board_led_operations(void);#endif

led_drv_test.c

/*************************************************************************> File Name: hello_test.c> Author: Winter> Created Time: Sun 07 Jul 2024 01:39:39 AM EDT************************************************************************/#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>/** ./led_drv  /dev/winter_led0 on* ./led_drv  /dev/winter_led0 off*/
int main(int argc, char **argv)
{int fd;char status;/* 1. 判断参数 */if (argc < 2) {printf("Usage: %s <dev> <on | off>\n", argv[0]);return -1;}/* 2. 打开文件 */fd = open(argv[1], O_RDWR);if (fd == -1){printf("can not open file %s\n", argv[1]);return -1;}/* 3. 写文件 */if (0 == strcmp(argv[2], "on")){status = 1;}else{status = 0;}write(fd, &status, 1);close(fd);return 0;
}

Makefile

# 1. 使用不同的开发板内核时, 一定要修改KERN_DIR
# 2. KERN_DIR中的内核要事先配置、编译, 为了能编译内核, 要先设置下列环境变量:
# 2.1 ARCH,          比如: export ARCH=arm64
# 2.2 CROSS_COMPILE, 比如: export CROSS_COMPILE=aarch64-linux-gnu-
# 2.3 PATH,          比如: export PATH=$PATH:/home/book/100ask_roc-rk3399-pc/ToolChain-6.3.1/gcc-linaro-6.3.1-2017.05-x86_64_aarch64-linux-gnu/bin 
# 注意: 不同的开发板不同的编译器上述3个环境变量不一定相同,
#       请参考各开发板的高级用户使用手册KERN_DIR = /home/book/100ask_stm32mp157_pro-sdk/Linux-5.4all:make -C $(KERN_DIR) M=`pwd` modules $(CROSS_COMPILE)gcc -o led_drv_test led_drv_test.c clean:make -C $(KERN_DIR) M=`pwd` modules cleanrm -rf modules.orderrm -f led_drv_test# 参考内核源码drivers/char/ipmi/Makefile
# 要想把a.c, b.c编译成ab.ko, 可以这样指定:
# ab-y := a.o b.o
# obj-m += ab.o# leddrv.c board_demo.c 编译成 100ask.ko
winter_led-y := led_drv.o board_demo.o
obj-m	+= winter_led.o

执行

make

5 测试

在开发板挂载 Ubuntu 的NFS目录

mount -t nfs -o nolock,vers=3 192.168.5.11:/home/book/nfs_rootfs/ /mnt

安装驱动

insmod winter_led.ko

执行打开灯

./led_drv_test /dev/winter_led0 on
./led_drv_test /dev/winter_led0 off

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/870467.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【LeetCode:1071. 字符串的最大公因子 + 模拟 + 最大公约数】

&#x1f680; 算法题 &#x1f680; &#x1f332; 算法刷题专栏 | 面试必备算法 | 面试高频算法 &#x1f340; &#x1f332; 越难的东西,越要努力坚持&#xff0c;因为它具有很高的价值&#xff0c;算法就是这样✨ &#x1f332; 作者简介&#xff1a;硕风和炜&#xff0c;…

水库大坝安全监测险情应对措施

汛期暴雨洪涝灾害发生后&#xff0c;为保证大坝及下游人民生命财产安全&#xff0c;应及时进行大坝安全现场检查和快速评估。评估内容包括大坝沉降和水平变形、裂缝、坝坡是否塌滑、下游坡是否存在集中渗漏或大面积渗水、溢洪道启闭设备能否正常运行、近坝库岸是否有大的滑坡体…

react学习——26react-redux实现求和案例(完整版)

1、目录结构 2、components/count/index.js import React, {Component} from "react"; export default class Count extends Component {//加法increment()>{const {value} this.selectNumthis.props.jia(Number(value))}//减法decrement()>{const {value} …

一场夏测杀出个“双冠王”,极越01成为纯电SUV标杆

文 | AUTO芯球 作者 | 雷慢 万万没想到&#xff0c;懂车帝夏测运动会杀出一匹最大的黑马&#xff0c;竟然是极越01。 当前正在进行的懂车帝夏测运动会&#xff0c;在“纯电SUV/MPV续航达成率”赛事中&#xff0c;极越01以85.8%的续航达成率获得第一名。并且由于赛制规则限制…

应力 (Stress) 是指单位面积上所承受的力

应力 (Stress) 是指单位面积上所承受的力 flyfish 轴向力 轴向力 (Axial Force) 是指沿着物体的纵轴施加的力。对于一根杆或柱子&#xff0c;轴向力可以是拉力或压力&#xff0c;具体取决于力的方向。 拉力 (Tensile Force)&#xff1a;使物体拉长的力。 压力 (Compressive…

Vue中实现在线画流程图实现

概述 最近在调研一些在线文档的实现&#xff0c;包括文档编辑器、在线思维导图、在线流程图等&#xff0c;前面的文章基于语雀编辑器的在线文档编辑与查看实现了文档编辑器。在本文&#xff0c;分享在Vue框架下基于metaeditor-mxgraph实现在线流程图。 实现效果 实现 1. 添加…

LabVIEW开发CAN总线多传感器液位检测系统

设计并实现了一个基于CAN总线和LabVIEW的多传感器液位检测系统。该系统利用STM32F107单片机进行模拟信号与数字信号的转换&#xff0c;通过TJA1050实现CAN总线通信&#xff0c;并使用USB-CAN分析仪连接PC。LabVIEW用于数据采集、人机交互界面的设计、数据分析和仪器标定。系统能…

Paimon下载使用和基础操作说明

简介 Apache Paimon 是一种湖格式&#xff0c;支持使用 Flink 和 Spark 构建实时湖仓一体架构 用于流式处理和批处理操作。Paimon创新性地将湖格式与LSM&#xff08;Log-structured merge-tree&#xff09;相结合 结构&#xff0c;将实时流式更新引入 Lake 架构。 Paimon提供以…

05_TypeScript 中的数据类型

TypeScript 中的数据类型 一、概述二、详解布尔类型&#xff08;boolean&#xff09; true / false数字类型&#xff08;number&#xff09;字符串类型&#xff08;string&#xff09;数组类型&#xff08;array&#xff09;元组类型&#xff08;tuple&#xff09; 属于数组的一…

linux高级编程(网络)

数据的封包和拆包 封包&#xff1a; 应用层数据&#xff08;例如HTTP请求&#xff09;被传递给传输层。传输层&#xff08;TCP&#xff09;在数据前添加TCP头部&#xff08;包含端口号、序列号等&#xff09;。网络层&#xff08;IP&#xff09;在TCP段前添加IP头部&#xff…

C#Winform窗体中嵌入exe文件

1&#xff0c;效果以嵌入Modbus Slave为例&#xff1a; 2&#xff0c;代码&#xff1a; public partial class Form1 : Form{//设置嵌入exe的常量private const int nIndex -16;private const int dwNewLong 0x10000000;Process m_AppProcess;public Form1(){InitializeCompo…

VIM模式之间的切换

命令行界面下&#xff0c;常用的文本编辑器是 VI / VIM(VI增强版)&#xff0c;VI 是 Linux 最通用的文本编辑器&#xff0c;VIM相较于VI&#xff0c;提供了代码高亮等功能&#xff0c;两者用法完全兼容&#xff1b; 1. 进入 VIM 工作界面 vim 文件名 2. 进入编辑模式 三种方…

ENSP中OSPF配置

题目 划分网段&#xff0c;配置ip OSPF配置按照区域划分&#xff0c;这个网段也要按照区域个数划分&#xff0c;如这一题&#xff0c;分成两个区域&#xff0c;所以将192.168.1.0/24划分先为两个网段&#xff0c;然后在具体的划分区域中的网段。 以交换机为中心的三条线属于一…

[Qt] Qt Creator中,新建QT文件时选择界面模版下的各选项

在Qt Creator中&#xff0c;新建文件时选择界面模版下的各选项具有特定的意义&#xff0c;这些选项主要帮助开发者根据项目需求快速生成不同类型的文件。以下是对这些选项的详细解释&#xff1a; 0. Qt Item Model 意义&#xff1a;列表模型是Qt中用于表示和操作数据的强大抽…

Android 使用 Debug.startMethodTracing 分析方法耗时

参考 Generate Trace Logs by Instrumenting Your App 官网提供了 trace 工具来分析方法耗时。 生成 trace 文件 package com.test.luodemo.trace;import androidx.appcompat.app.AppCompatActivity;import android.os.Bundle; import android.os.Debug; import android.uti…

js vue table单元格合并

实现效果 关键代码 <table classtable table-bordered><thead><tr><th>检测项目</th><th>详细说明</th><th>检测结果</th><th>检测说明</th></tr></thead><tbody><tr ng-repeatrow in…

【car】深入浅出学习机械燃油车知识、结构、原理、维修、保养、改装、编程

汽车的五大总成通常是指发动机、变速器、前后桥、车架和悬挂系统。 发动机&#xff1a;是汽车的动力来源&#xff0c;负责将燃料的化学能转化为机械能&#xff0c;驱动汽车行驶。常见的发动机类型有内燃机&#xff08;如汽油发动机、柴油发动机&#xff09;和电动机&#xff0…

ant design pro多页签功能

效果&#xff1a; 原理&#xff1a; 1、所有需要页签页面&#xff0c;都需要一个共同父组件 2、如何缓存&#xff0c;用的是ant的Tabs组件&#xff0c;在共同父组件中&#xff0c;实际是展示的Tabs组件 3、右键&#xff0c;用的是ant的Dropdown组件&#xff0c;当点击时&…

在linux中查找 / 目录下的以.jar结尾的文件(find / -name *.jar)

文章目录 1、查找 / 目录下的以.jar结尾的文件 1、查找 / 目录下的以.jar结尾的文件 [rootiZuf6332h890vozldoxcprZ ~]# find / -name *.jar /etc/java/java-1.8.0-openjdk/java-1.8.0-openjdk-1.8.0.342.b07-1.el9_0.x86_64/lib/security/policy/limited/US_export_policy.ja…