韦东山嵌入式linux系列-实现读LED状态的功能

news2024/12/26 21:39:00

这是第五篇第5章的课后作业,尝试实现

实现读 LED 状态的功能:涉及 APP 和驱动。

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

参考分层思想

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

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

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

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

2 写代码

驱动程序分为上下两层: led_drv.c、 board_demo.c。

led_drv.c 负责注册file_operations 结构体,它的 open/write 成员会调用 board_demo.c 中提供的硬件 led_opr 中的对应函数。

1 把 LED 的操作抽象出一个 led_operations 结构体
首先看看 led_opr.h,它定义了一个 led_operations 结构体,把LED的操作抽象为这个结构体:

分别是写操作和读操作

#ifndef LED_OPERATIONS_H
#define LED_OPERATIONS_H

struct led_operations {
    int (*init) (int which);                 	   // 初始化LED,which是哪一个LED
    int (*ctl_write) (int which, char status);     // 控制LED,which-哪一个LED,status-1亮,0-灭
    int (*ctl_read) (int which);				   // 控制LED,which-哪一个LED,返回值1-亮,0-灭
};

// 返回结构体指针
struct led_operations* get_board_led_operations(void);


#endif

2 驱动程序的上层: file_operations 结构体

上层是 leddrv.c,它的核心是 file_operations 结构体,首先看看入口函数:

// 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 */
	// 创建class
	led_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;
}

(1)通过register_chrdev函数向内核注册一个 file_operations 结构体

(2)通过get_board_led_operations函数从底层硬件相关的代码 board_demo.c 中获得 led_operaions结构体

和之前的一样

再来看看 leddrv.c 中 file_operations 结构体的成员函数:

因为是读操作,所以要在read函数中做文章,write函数、open函数不变

// 3 实现对应的 drv_open/drv_read/drv_write 等函数,填入 file_operations 结构体
// res = read(fd, val, 1)
static ssize_t led_drv_read (struct file *file, char __user *buf, size_t size, loff_t *offset)
{
	int err;
	struct inode* node;
	int minor;
	printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
	// 将kernel_buf区的数据拷贝到用户区数据status中,即从内核kernel_buf中读数据
	err = copy_to_user(buf, status, MIN(1024, size));
	// 根据次设备号和status控制LED
	node = file_inode(file);
	minor = iminor(node);
	p_led_operations->ctl_read(minor);
	return MIN(1024, size);
}

// 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;
	struct inode* node;
	int minor;
	printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
	// 把用户区的数据buf拷贝到内核区status,即向写到内核status中写数据
	err = copy_from_user(status, buf, MIN(1024, size));
	// 根据次设备号和status控制LED
	node = file_inode(file);
	minor = iminor(node);
	p_led_operations->ctl_write(minor, status);
	return MIN(1024, size);
}

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);
	
	// 根据次设备号初始化LED
	p_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,
};

说白了以前是一个drv.c文件,在里面定义了file_operation结构体、并实现了对应的read/open等函数,再在init函数中注册file_operaions结构体。

现在是将drv.c抽象抽象成两层:

(1)底层抽象为led_operations结构体,这里面主要有init初始化属性(函数指针)和ctl控制属性(函数指针),然后针对不同的板子,去实现对应的初始化函数和控制函数;

(2)上层中依旧保留原来大的框架,在初始化函数(open函数)中利用led_operations结构体指针调用init初始化属性,在控制函数(read/write函数)中利用led_operations结构体指针调用ctl控制属性。

从而实现具体板子的功能业务和框架的分离

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;
char status[1024];




#define MIN(a, b) (a < b ? a : b)

// 3 实现对应的 drv_open/drv_read/drv_write 等函数,填入 file_operations 结构体
// res = read(fd, val, 1)
static ssize_t led_drv_read (struct file *file, char __user *buf, size_t size, loff_t *offset)
{
	int err;
	struct inode* node;
	int minor;
	printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
	// 将kernel_buf区的数据拷贝到用户区数据status中,即从内核kernel_buf中读数据
	err = copy_to_user(buf, status, MIN(1024, size));
	// 根据次设备号和status控制LED
	node = file_inode(file);
	minor = iminor(node);
	p_led_operations->ctl_read(minor);
	return MIN(1024, size);
}

// 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;
	struct inode* node;
	int minor;
	printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
	// 把用户区的数据buf拷贝到内核区status,即向写到内核status中写数据
	err = copy_from_user(status, buf, MIN(1024, size));
	// 根据次设备号和status控制LED
	node = file_inode(file);
	minor = iminor(node);
	p_led_operations->ctl_write(minor, status);
	return MIN(1024, size);
}

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);
	
	// 根据次设备号初始化LED
	p_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 */
	// 创建class
	led_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_write函数
static int board_demo_led_ctl_write(int which, char status[1024])
{
	char* str = status;
	printk("%s %s line %d, led %d, %s\n", 
		__FILE__, __FUNCTION__, __LINE__, which, strcmp(str, "on") == 0 ? "on" : "off");
	return 0;
}

// ctl_write函数
static int board_demo_led_ctl_read(int which)
{
	printk("%s %s line %d, led %d\n", __FILE__, __FUNCTION__, __LINE__, which);
	return 0;
}


static struct led_operations board_demo_led_operations = {
	.init = board_demo_led_init,
	.ctl_write = board_demo_led_ctl_write,
	.ctl_read = board_demo_led_ctl_read,
};

// 返回结构体
struct led_operations* get_board_led_operations(void)
{
	return &board_demo_led_operations;
}

led_operations.h

#ifndef LED_OPERATIONS_H
#define LED_OPERATIONS_H

struct led_operations {
    int (*init) (int which);                 	   // 初始化LED,which是哪一个LED
    int (*ctl_write) (int which, char status);     // 控制LED,which-哪一个LED,status-1亮,0-灭
    int (*ctl_read) (int which);				   // 控制LED,which-哪一个LED,返回值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 -w on
 * ./led_drv  /dev/winter_led0 -w off
 * ./led_drv  /dev/winter_led0 -r
 */
int main(int argc, char **argv)
{
	int fd;
	int len;
	char buf[1024];

	
	/* 1. 判断参数 */
	if (argc < 3) 
	{
		printf("Usage: %s <dev> -w <on | off>\n", argv[0]);
		printf("       %s <dev> -r\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], "-w")) && (0 == strcmp(argv[3], "on")))
	{
		write(fd, argv[3], strlen(argv[3]) + 1);
	}
	else if ((0 == strcmp(argv[2], "-w")) && (0 == strcmp(argv[3], "off")))
	{
		write(fd, argv[3], strlen(argv[3]) + 1);
	}
	else
	{
		len = read(fd, buf, 1024);
		buf[1023] = '\0';
		printf("APP read : %s\n", buf);
	}
	
	
	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.4
 
all:
	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 clean
	rm -rf modules.order
	rm -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

3 测试

在开发板挂载 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 -w on
./led_drv_test /dev/winter_led0 -r

./led_drv_test /dev/winter_led0 -w off
./led_drv_test /dev/winter_led0 -r

./led_drv_test /dev/winter_led1 -w on
./led_drv_test /dev/winter_led1 -r

./led_drv_test /dev/winter_led1 -w off
./led_drv_test /dev/winter_led1 -r

结果

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

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

相关文章

Vue3 引入Vanta.js使用

能搜到这篇文章 想必一定看过demo效果图了吧 示例 Vanta.js - Animated 3D Backgrounds For Your Website (vantajs.com) 1. 引入 在根目录 index.html中引入依赖 <script src"https://cdnjs.cloudflare.com/ajax/libs/three.js/r134/three.min.js"></sc…

jenkins系列-02.配置jenkins

首先&#xff1a;我们要给jenkins配备jdkmaven: 从上一节我们知道 ~/dockerV/jenkins/jenkins/data目录 就是 容器中jenkins的home目录 所以把jdkmaven 放在当前宿主机上的 ~/dockerV/jenkins/jenkins/data目录下即可 容器内&#xff1a; 开始配置jenkins: 注意是在jenkins…

CSIP-FTE考试专业题

靶场下载链接&#xff1a; https://pan.baidu.com/s/1ce1Kk0hSYlxrUoRTnNsiKA?pwdha1x pte-2003密码&#xff1a;admin123 centos:root admin123 解压密码&#xff1a; PTE考试专用 下载好后直接用vmware打开&#xff0c;有两个靶机&#xff0c;一个是基础题&#x…

树莓派PICO使用INA226测量电流和总线电压(2)

上一篇文章里&#xff0c;我们讲了如何设置配置寄存器&#xff08;0x01&#xff09;&#xff0c;在测量电流之前&#xff0c;还需要设置校准寄存器&#xff08;0x05&#xff09;&#xff0c;校准寄存器非常关键&#xff0c;如果不设置这个寄存器&#xff0c;INA226是不会工作的…

同时用到,网页,java程序,数据库的web小应用

具体实现功能&#xff1a;通过网页传输添加用户的请求&#xff0c;需要通过JDBC来向 MySql 添加一个用户数据 第一步&#xff0c;部署所有需要用到的工具 IDEA(2021.1),Tomcat(9),谷歌浏览器&#xff0c;MySql,jdk(17) 第二步&#xff0c;创建java项目&#xff0c;提前部署数…

keil无法读取jlink的一个原因——使用jlink的Vout引脚给芯片供电

keil无法读取jlink的一个原因——使用jlink的Vout引脚给芯片供电 问题背景问题排查 问题背景 在使用 J-Link 对 GD32F470VGT6 进行程序烧录时&#xff0c;遇到下载失败且 J-Link 未能识别设备的问题。 通过检查设备管理器确认 J-Link 驱动已正确安装。 问题排查 对照jil…

免开steam 脱离steam 进行游戏的小工具

链接&#xff1a;https://pan.baidu.com/s/1k2C8b4jEqKIGLtLZp8YCgA?pwd6666 提取码&#xff1a;6666 我们只需选择游戏根目录 然后输入AppID 点击底部按钮 进行就可以了 关于AppID在&#xff1a;

OceanBase:引领下一代分布式数据库技术的前沿

OceanBase的基本概念 定义和特点 OceanBase是一款由蚂蚁金服开发的分布式关系数据库系统&#xff0c;旨在提供高性能、高可用性和强一致性的数据库服务。它结合了关系数据库和分布式系统的优势&#xff0c;适用于大规模数据处理和高并发业务场景。其核心特点包括&#xff1a; …

3分钟搞定Kali Linux安装,超详细教程(附安装包)

**今天写一写Kali渗透中的第一个知识点&#xff1a;Kali安装配置。 俗话说得好&#xff1a;kali学得好&#xff0c;牢饭吃到饱&#xff01;** 相信很多同学在刚接触网络安全的时候&#xff0c;都听过kali linux的大名&#xff0c;那到底什么是kali&#xff0c;初学者用kali能做…

请编写程序,利用malloc函数开辟动态存储单元,存放输入的三个整数,然后按从小到大的顺序输出这三个数

int main() {int* nums;nums (int*)malloc(3 * sizeof(int));if (nums NULL){perror("error:");exit(1);}printf("请输入三个整数\n");int i 0;for (i 0; i < 3; i){scanf("%d", &nums[i]);}printf("请输入的三个整数为\n"…

控制单/多用户权限

多用户权限控制 Unix/类Unix是一个多用户的操作系统&#xff0c;拥有众多的发行版系统。单一用户可以使用chmod命令修改可读可写可执行权限。多用户使用chmod就显得力不从心了。多用户操作权限则使用ACL规则(Access Control List)&#xff0c;即访问控制列表&#xff0c;ACL规则…

数据库的学习(6)

题目&#xff1a; 数据准备创建两张表:部门(dept)和员工(emp)&#xff0c;并插入数据&#xff0c;代码如下create table dept(dept_id int primary key auto_increment comment 部门编号,dept_name char(20)comment部门名称 ); insert into dept (dept_name) values(销售部),(财…

新手-前端生态

文章目录 新手的前端生态一、概念的理解1、脚手架2、组件 二、基础知识1、HTML2、css3、JavaScript 三、主流框架vue3框架 四、 工具&#xff08;特定框架&#xff09;1、uinapp 五、组件库&#xff08;&#xff09;1、uView如何在哪项目中导入uView 六、应用&#xff08;各种应…

Wavlink 路由器攻击链

本文仅用于技术研究学习&#xff0c;请遵守相关法律&#xff0c;禁止使用本文所提及的相关技术开展非法攻击行为&#xff0c;由于传播、利用本文所提供的信息而造成任何不良后果及损失&#xff0c;与本账号及作者无关。 本文来源无问社区&#xff0c;更多实战内容&#xff0c;…

手把手教你打数学建模国赛!!!第一天软件准备篇

第一天软件准备 MATLAB MATLAB&#xff08;Matrix Laboratory&#xff09;是一种强大的数值计算和科学编程软件。它提供了丰富的数学函数和工具&#xff0c;用于数据分析、算法开发、信号处理、图像处理、控制系统设计、仿真等应用领域。 MATLAB具有直观的语法&#xff0c;使…

SAP HCM 定额扣减不生效问题,从定位错误到玩转配置

导读 INTRODUCTION 定额扣减:今天遇到一个很奇怪的问题,就是年假不会扣减年假定额的问题,认真去查看相关配置,但是一直没找到为什么不触发扣减规则,这次出现的问题还是触发规则的问题,触发规则主要这么几类、星期、假期类、日类型、期间工作日程表的技术类、日工作计划类…

用友NC Cloud blobRefClassSearch FastJson反序列化RCE漏洞复现

0x01 产品简介 用友 NC Cloud 是一种商业级的企业资源规划云平台,为企业提供全面的管理解决方案,包括财务管理、采购管理、销售管理、人力资源管理等功能,实现企业的数字化转型和业务流程优化。 0x02 漏洞概述 用友 NC Cloud blobRefClassSearch 接口处存在FastJson反序列…

Apache AGE 聚合函数

简介 一般来说&#xff0c;聚合函数 aggr(expr) 会处理每个聚合键在传入记录中找到的所有匹配行&#xff08;键使用等价性进行比较&#xff09;。 在常规聚合&#xff08;即形式为 aggr(expr) 的情况下&#xff09;&#xff0c;聚合值列表是候选值列表&#xff0c;其中所有空…

学生护眼用什么样的台灯比较好?推荐学生护眼台灯十大排名

台灯成为每家每户不可缺少的家具产品&#xff0c;更是成为学生认可的学习搭子。而设计师设计出多功能的台灯&#xff0c;既能营造适宜的环境&#xff0c;也在为眼睛这个器官提供一个优质舒适的环境。对于学生而言&#xff0c; 学生护眼用什么样的台灯比较好&#xff1f;我们处于…

【经典面试题】是否形成有环链表

1.环形链表oj 2. oj解法 利用快慢指针&#xff1a; /*** Definition for singly-linked list.* struct ListNode {* int val;* struct ListNode *next;* };*/typedef struct ListNode ListNode; bool hasCycle(struct ListNode *head) {ListNode* slow head, *fast…