Linux驱动(六):Linux2.6驱动编写之平台设备总线

news2024/9/24 7:15:52

目录

  • 前言
  • 一、平台设备总线
    • 1.是个啥?
    • 2.API函数
  • 二、设备端+驱动端
    • 1. 匹配机制
    • 2. 实现代码
  • 三、设备树+驱动端
    • 1.匹配机制
    • 2.代码实现


前言

本文主要介绍了一下两种驱动编写方法:
1.比较原始的设备端+驱动端编写方法。
2.效率较高的设备树+驱动端编写方法。
最后,使用LED闪烁实战验证了一下。


一、平台设备总线

1.是个啥?

在这里插入图片描述

  在Linux驱动开发中,平台设备总线(Platform Device Bus)是一个重要的概念,用于管理与特定平台紧密集成的设备。这些设备通常不是通过传统的总线(如PCI、USB)连接的,而是直接连接到系统的硬件平台上。
  说人话就是:平台设备总线(Platform Device Bus)是Linux内核虚拟出来的一条总线,不是真实的导线。它将底层驱动分为了设备端驱动端设备端主要就是负责提供稳定不变的东西。例如设备的信息等,大部分就是和硬件相关的(该部分可以选择用设备树写)。驱动端主要就是负责先从设备端获取稳定不变的信息,然后再去驱动一些需要变动的东西。
  平台设备总线也分为两种编写方式:1.设备端+驱动端。2.设备树+驱动端。本质上的区别就是硬件配置的管理方式不同,前者是要单独写一个驱动程序,里面包含了有关设备的所有配置信息。后者的硬件配置信息通过挂载到设备树上来管理。
注意:无论是设备端+驱动端还是设备树+驱动端他们在加载时都不用分先后。

2.API函数

注册平台设备函数:

int platform_device_register(struct platform_device *pdev);

函数参数:指向 platform_device 结构体的指针(核心结构体)

void xyd_release(struct device *dev)//虽然不用填东西,但必须要有
{
}

struct platform_device {
	const char *name;
	int id;
	struct device dev;
	u32 num_resources;
	struct resource *resource;
}

核心结构体里的参数有很多,需要填写的部分如上所示。
name:就是和驱动端所要匹配的名字。
id:也是匹配方式的一种,这里一般填-1即可
dev:存放设备信息的核心结构体(void (*release)(struct device *dev);
num_resources:提供资源的数量,一般用ARRAY_SIZE()计算。
resource:提供的具体资源

struct resource {
resource_size_t start;
resource_size_t end;
const char *name;
unsigned long flags;
};

start:资源设备的起始地址
end:资源设备的结束地址
name:给这个设备资源取的名字
flags:资源的类型 — 使用宏即可,内核已经帮你定义好了

注销平台设备函数:

void platform_device_unregister(struct platform_device *pdev)

函数参数:平台设备的核心结构体

接下来是驱动端的API函数:

platform_driver_register(struct platform_driver *prv)
struct platform_driver {
int (*probe)(struct platform_device *);
int (*remove)(struct platform_device *);
struct device_driver driver;
};

probe:探测函数。 设备端和驱动端匹配成功时就会进入到该函数中去执行,从而去获取资源以及注册相关的设备资源。
remove:移除函数,有一端被卸载时就会进入该移除函数中执行。一般存放卸载的API函数。
driver:设备资源的核心结构体

struct device_driver {
const char *name;
const struct of_device_id *of_match_table;
};

name:驱动端和设备端进行匹配的名字
of_match_table:他是用于驱动端和设备树进行匹配的参数

struct of_device_id {
char compatible[128];//他要和设备树里的 compatible 保持一致
};

如果你使用的是驱动端+设备树的编写方法,那么此时你需要填写如下结构体:

struct device_driver {
const char *name,
}

否则你去加载内核驱动时,内核会出问题。

二、设备端+驱动端

  这种方法,现在用的已经较少了,因为比较麻烦。设备信息通常在代码中直接定义,通常适合简单的工作场景。

1. 匹配机制

  因为平台设备总线将驱动分为设备端和驱动端,那么驱动运行时,驱动端是如何找到对应的设备端,去寻找设备信息的呢?
  关键就在于:名字,设备端+驱动端的匹配是由名字来匹配的,本质上就是利用strcmp函数进行字符串比较
  设备端和驱动端都有一个属于自己的核心结构体,而各自的核心结构体里都有一个名为name的成员变量,他们就是通过该变量进行匹配的。
  在编写时一定要注意两端的name变量要保持一致

2. 实现代码

设备端

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/of.h>
#include <linux/of_gpio.h>
#include <linux/cdev.h>
#include <linux/gpio.h>
#include <linux/device.h>
#include <linux/fs.h>
#include <linux/platform_device.h>

//定义结构体表明存放的设备信息
struct resource Led_s[2]={
	[0]={
	.start = 21,
	.end = 22,
	.name = "my_leds",
	.flags = IORESOURCE_MEM,
	},	
	[1]={
	.start = 0,
	.end = 1,
	.name = "my_leds",
	.flags = IORESOURCE_MEM,
	}	
};

void xyd_release(struct device *dev)
{
}

struct platform_device pdev={
	.name="my_leds",
	.id= -1,
	.dev={
		.release = xyd_release,
	},
	.num_resources = ARRAY_SIZE(Led_s),
	.resource = Led_s,
};

static int __init mydevice_init(void)
{
	int a=0;
	printk("设备端加载函数运行!\n");
	a=platform_device_register(&pdev);
	if(a < 0)
	{
		printk("platform_device_register error\n");
		return -1;
	}
	printk("设备端注册成功\n");	
	return 0;
}

static void __exit mydevice_exit(void)
{
	printk("设备端卸载函数运行!\n");
	platform_device_unregister(&pdev);
	printk("设备端卸载完成!\n");
}

module_init(mydevice_init);
module_exit(mydevice_exit);
MODULE_LICENSE("GPL");

驱动端

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/miscdevice.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include<linux/device.h>
#include <linux/gpio.h>
#include <linux/fs.h>
#include <linux/of_gpio.h>
#include <linux/of.h>
#include <linux/platform_device.h>

struct resource *led[2];
dev_t dev;
struct cdev mydev;
struct class *myclass = NULL;

int my_open (struct inode *inode, struct file *fp)
{
	int i;
	for(i=led[0]->start;i<=led[0]->end;i++)
		{
			gpio_set_value(i,led[1]->end);//21,1/22,1
		}
	printk("led灯:亮!\n");
	return 0;
}

int my_release (struct inode *inode, struct file *fp)
{
	int i;
	for(i=led[0]->start;i<=led[0]->end;i++)
		{
			gpio_set_value(i,led[1]->start);//21,0/22,0
		}
	printk("led灯:灭!\n");
	return 0;
}

struct file_operations my_file = {
	.open = my_open,
	.release = my_release,
};

int my_probe(struct platform_device *pdev)
{
	int i;
	for(i=0;i<=1;i++)
	{
		led[i] = platform_get_resource(pdev,IORESOURCE_MEM,i);
	}
	gpio_request(led[0]->start, "led1");
	gpio_request(led[0]->end, "led2");
	gpio_direction_output(led[0] -> start, 0);
	gpio_direction_output(led[0] -> end, 0);
	//动态申请设备号
	alloc_chrdev_region(&dev, 0, 1, "myled");
	//初始化设备核心结构体
	cdev_init(&mydev,&my_file);
	//向内核申请Linux2.6字符设备
	cdev_add(&mydev, dev, 1);
	myclass = class_create(THIS_MODULE,"LED_CLASS");
	device_create(myclass, NULL, dev, NULL, "myleds");
	printk("探测函数:设备端和驱动端匹配成功\n");
	return 0;
}

int myled_remove(struct platform_device *pdev)
{
	printk("移除函数运行\n");
	device_destroy(myclass,dev);//销毁设备节点
	class_destroy(myclass);//销毁设备类
	cdev_del(&mydev);//删除字符设备
	unregister_chrdev_region(dev,1);//释放设备号
	printk("设备注销成功\n");
	gpio_free(led[0]->start);
	gpio_free(led[1]->start);
	return 0;
}

struct platform_driver drv = {
	.probe = my_probe,
	.remove = myled_remove,
	.driver = {
		.name = "my_leds",
		},
};

static int __init mydriver_init(void)
{
	platform_driver_register(&drv);
	return 0;
}

static void __exit mydriver_exit(void)
{
	platform_driver_unregister(&drv);
}	

module_init(mydriver_init);
module_exit(mydriver_exit);
MODULE_LICENSE("GPL");

应用端

#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
int main(int argc,char *argv[])
{
	int fd =0;
	char buffer[100];
	const char *data = "Hello, this is a test write!";
	if(argc<2)
	{
		printf("请输入正确的参数\n");
		return -1;
	}
	fd = open(argv[1],O_RDWR);
	if(fd<0)
	{
		perror("open");
		return -1;
	}
	while(1)
	{
		fd = open(argv[1],O_RDWR); // --- 底层的open函数
		sleep(1);
		close(fd);//底层的close
		sleep(1);
	}	
	return 0;
}

三、设备树+驱动端

  使用设备树+驱动端的编写方法是当前最常用效率最高的方式。通过设备树,我们能够通过几行简洁的代码完成硬件信息的挂载,从而将主要精力集中在编写驱动端的代码上。

1.匹配机制

  和设备端+驱动端的匹配方法不同,设备树+驱动端的匹配机制主要是通过设备树里的 Compatible属性来匹配的。所以在编写时,驱动端代码要和设备树上的compatible(兼容性标识符)保持一致。

设备树
在这里插入图片描述

驱动端:
在这里插入图片描述

2.代码实现

设备树:

// SPDX-License-Identifier: (GPL-2.0+ OR MIT)
/*
 * Copyright (c) 2021 Rockchip Electronics Co., Ltd.
 *
 */

/dts-v1/;

#include "rk3588s-evb4-lp4x-yyt.dtsi"
#include "rk3588-linux.dtsi"

/ {
	model = "Rockchip RK3588S EVB4 LP4X V10 Board";
	compatible = "rockchip,rk3588s-evb4-lp4x-v10", "rockchip,rk3588";
	xydled:xyd_leds {
		compatible = "xyd_led";
		leds-gpios = <&gpio0 RK_PC5 GPIO_ACTIVE_HIGH>,
					 <&gpio0 RK_PC6 GPIO_ACTIVE_HIGH>,
					 <&gpio0 RK_PD0 GPIO_ACTIVE_HIGH>,
					 <&gpio1 RK_PD5 GPIO_ACTIVE_HIGH>;
		status = "okay";
		};
		
	xydkey:xyd_keys{
		compatible = "xyd_keys";
		keys-gpios = <&gpio1 RK_PA7 GPIO_ACTIVE_LOW>,
					 <&gpio1 RK_PB1 GPIO_ACTIVE_LOW>;
		interrupt-parent = <&gpio1>;
		interrupts = <RK_PA7 IRQ_TYPE_LEVEL_LOW>,
					 <RK_PB1 IRQ_TYPE_LEVEL_LOW>;		 
		status = "okay";
		};
		
	xydbeep:xyd_beep{
		compatible = "xyd-beep";
		beeps-gpios = <&gpio1 RK_PA4 GPIO_ACTIVE_HIGH>;
		status = "okay";	
		};
	xydinfo:xyd_device{
		compatible = "xyd-device";
		devices-gpios =  <&gpio0 RK_PC5 GPIO_ACTIVE_HIGH>,
 						 <&gpio0 RK_PC6 GPIO_ACTIVE_HIGH>,
 						 <&gpio1 RK_PA7 GPIO_ACTIVE_LOW>,
 						 <&gpio1 RK_PB1 GPIO_ACTIVE_LOW>,
 						 <&gpio1 RK_PA4 GPIO_ACTIVE_HIGH>;
	status = "okay";	
	};
	xydnod:xyd_devs{
		compatible = "xyd-devs";
		xydled1: xyd_leds {
		leds-gpios = <&gpio0 RK_PC5 GPIO_ACTIVE_HIGH>,								
 					 <&gpio0 RK_PC6 GPIO_ACTIVE_HIGH>;
						status = "okay";
					};
		xydkey1: xyd_keys {
		keys-gpios = <&gpio1 RK_PA7 GPIO_ACTIVE_LOW>,
 					 <&gpio1 RK_PB1 GPIO_ACTIVE_LOW>;
					status = "okay";
					};
		xydbeep1: xyd_beep {
		beeps-gpios = <&gpio1 RK_PA4 GPIO_ACTIVE_HIGH>;
					status = "okay";
		};
	};
};

驱动端:

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/miscdevice.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include<linux/device.h>
#include <linux/gpio.h>
#include <linux/fs.h>
#include <linux/of_gpio.h>
#include <linux/of.h>
#include <linux/platform_device.h>

int led[2]={0};
dev_t dev;
struct cdev mydev;
struct class *myclass = NULL;

int my_open (struct inode *inode, struct file *fp)
{
	int i;
	for(i=0;i<=1;i++)
		{
		gpio_set_value(led[i],1);//21,1/22,1
		}
	printk("led灯:亮!\n");
	return 0;
}

int my_release (struct inode *inode, struct file *fp)
{
	int i;
	for(i=0;i<=1;i++)
		{
			gpio_set_value(led[i],0);//21,0/22,0
		}
	printk("led灯:灭!\n");
	return 0;
}

struct file_operations my_file = {
	.open = my_open,
	.release = my_release,
};

int my_probe(struct platform_device *pdev)
{
	//获取编号
	led[0] = of_get_named_gpio(pdev->dev.of_node,"leds-gpios",0);
	led[1] = of_get_named_gpio(pdev->dev.of_node,"leds-gpios",1);
	gpio_request(led[0], "led1");
	gpio_request(led[1], "led2");
	gpio_direction_output(led[0], 0);
	gpio_direction_output(led[1], 0);
	//动态申请设备号
	alloc_chrdev_region(&dev, 0, 1, "myled");
	//初始化设备核心结构体
	cdev_init(&mydev,&my_file);
	//向内核申请Linux2.6字符设备
	cdev_add(&mydev, dev, 1);
	myclass = class_create(THIS_MODULE,"LED_CLASS");
	device_create(myclass, NULL, dev, NULL, "my_leds");
	printk("探测函数:设备端和驱动端匹配成功\n");
	return 0;
}
int myled_remove(struct platform_device *pdev)
{
	printk("移除函数运行\n");
	device_destroy(myclass,dev);//销毁设备节点
	class_destroy(myclass);//销毁设备类
	cdev_del(&mydev);//删除字符设备
	unregister_chrdev_region(dev,1);//释放设备号
	printk("设备注销成功\n");
	gpio_free(led[0]);
	gpio_free(led[1]);
	return 0;
}

const struct of_device_id mydev_node={
		.compatible = "xyd_led",
};


struct platform_driver drv = {
	.probe = my_probe,
	.remove = myled_remove,
	.driver = {
		.name = "my_leds",
		.of_match_table = &mydev_node	
		},
};

static int __init mydriver_init(void)
{
	platform_driver_register(&drv);
	return 0;
}

static void __exit mydriver_exit(void)
{
	platform_driver_unregister(&drv);
}	

module_init(mydriver_init);
module_exit(mydriver_exit);
MODULE_LICENSE("GPL");

应用端:

#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
int main(int argc,char *argv[])
{
	int fd =0;
	char buffer[100];
	const char *data = "Hello, this is a test write!";
	if(argc<2)
	{
		printf("请输入正确的参数\n");
		return -1;
	}
	fd = open(argv[1],O_RDWR);
	if(fd<0)
	{
		perror("open");
		return -1;
	}
	while(1)
	{
		fd = open(argv[1],O_RDWR); // --- 底层的open函数
		sleep(1);
		close(fd);//底层的close
		sleep(1);
	}	
	return 0;
}

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

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

相关文章

echarts 5.3.2 折线图 tooltip设置trigger为axis无效

在使用echarts5.3.2过程中&#xff0c;发生一个不应该发生的bug&#xff0c;希望效果如下 现实中如下 代码中设置了tooltip: {trigger: ‘axis’}不生效啊。查阅文档&#xff0c;应该是这样设置的啊&#xff0c;可是为什么无效呢。改成tooltip: {trigger: ‘item’}虽能显示弹…

合宙低功耗4G模组Air724UG ——产品规格书

Air724UG 是合宙通信推出的超小封装 LTE Cat.1 bis 模块&#xff1b;采用紫光展锐的UIS8910平台&#xff0c;支持 LTE 3GPP Rel.13 技术。是4G全网通模块&#xff0c;可适应不同的运营商和产品&#xff0c;确保产品设计的最大灵活性。 模块主要特点&#xff1a; 超小封装&…

‘“node“‘ �����ڲ����ⲿ���Ҳ���ǿ����еij��� ���������ļ���

错误信息 使用vscode提交前端代码到git时&#xff0c;报下面的错&#xff0c;一直不知道啥原因&#xff0c;后来找到了个临时解决方案。。。 vscode解决方案 package.json文件中&#xff0c;去掉hooks的配置。 Idea解决方案 网上有说idea的解决方案的&#xff1a;就是提…

【Qt网络编程基础】Tcp服务器和客户端(只支持一对一)

目录 一、编写思路 1、服务器 总体思路 详细思路 1. 构造函数 (Widget::Widget) 2. 启动监听 (Widget::on_btn_start_clicked) 3. 停止监听 (Widget::on_btn_cease_clicked) 4. 发送消息 (Widget::on_btn_info_clicked) 5. 接收消息 (Widget::receive_message) 6. 处…

顶象图标点选模型识别

注意,本文只提供学习的思路,严禁违反法律以及破坏信息系统等行为,本文只提供思路 如有侵犯,请联系作者下架 (水文一篇)使用resnet分类模型配合yolo训练的图标点选验证码,用于顶象图标点选,案例如下图:

类型参数传值问题

一、基本数据类型传参问题 public static void main(String[] args) throws Exception {Integer number null;method01(number);}private static void method01(int number){System.out.println("number " number);}Ps: 基于int基本数据类型传参的时候&#xff0c…

常用的Adobe XD插件就看这10款!

Adobe XD是为 UI/UX 设计量身定制的协作工具&#xff0c;它与 Adobe 其他产品的无缝集成使得设计工作更加高效。与专注于 MacOS 的 Sketch不同&#xff0c;Adobe XD支持多平台操作&#xff0c;这种灵活性让它在原型设计领域备受瞩目。为了更高效地使用 Adobe XD&#xff0c;插件…

【大模型专栏—入门篇】机器学习与深度学习基础测试

大模型专栏介绍 &#x1f60a;你好&#xff0c;我是小航&#xff0c;一个正在变秃、变强的文艺倾年。 &#x1f514;本文为大模型专栏子篇&#xff0c;大模型专栏将持续更新&#xff0c;主要讲解大模型从入门到实战打怪升级。如有兴趣&#xff0c;欢迎您的阅读。 &#x1f4…

开源项目低代码表单FormCreate中ElementPlus表单使用校验规则示例

在开源项目低代码表单FormCreate 中&#xff0c;可以通过 validate 配置项为表单组件设置验证规则。无论是内置的表单组件还是自定义的表单组件&#xff0c;都支持表单校验。本文将详细介绍验证规则的使用方法&#xff0c;并提供一些示例来帮助您更好地理解和应用这些功能。 源…

2024年最新微短剧系统重构版,短剧小程序源码开源源码搭建部署

技术栈 前端&#xff1a;vueuniapp 后端&#xff1a;php 数据库&#xff1a;MySQL <?php class Drama {private $title;private $description;private $cast;private $genre;public function __construct($title, $description, $cast, $genre) {$this->title $tit…

iPhone手机备忘录转移到Windows电脑上的方法

备忘录作为我们日常生活中常用的软件&#xff0c;帮助我们记录下重要事项、待办任务、灵感创意等&#xff0c;已成为许多人不可或缺的工具。然而&#xff0c;当我们需要在不同设备间转移备忘录内容时&#xff0c;常常会遇到一些困难。特别是从iPhone手机转移到Windows电脑上&am…

从0到1实现线程池(C语言版)

目录 &#x1f324;️1. 基础知识 ⛅1.1 线程概述 ⛅1.2 linux下线程相关函数 &#x1f325;️1.2.1 线程ID &#x1f325;️1.2.2 线程创建 &#x1f325;️1.2.3 线程回收 &#x1f325;️1.2.4 线程分离 &#x1f324;️2. 线程池概述 ⛅2.1 线程池的定义 ⛅2.2 为…

多种现货黄金的简介 第三种你要特别注意

近期黄金的价格全线上涨&#xff0c;虽然作为商品&#xff0c;一般人看到其价格上涨&#xff0c;购买它的欲望就下跌了。但是作为一种投资品种&#xff0c;在近期黄金价格的上涨以及未来美联储货币政策的影响下&#xff0c;他的投资潜力是巨大的。但市场中有那么多的黄金理财产…

JavaScript编程精粹:语法、函数与对象

JavaScript编程精粹&#xff1a;语法、函数与对象 一 . JavaScript 介绍1.1 应用场景1.2 JavaScript 介绍1.3 JavaScript 组成部分 二 . JavaScript 引入方式2.1 内部脚本2.2 外部脚本 三 . JavaScript 基础语法3.1 输出语句3.2 变量3.3 数据类型3.3.1 number 类型3.3.2 string…

W11电脑无法找到音频输出设备,所有声音设备都是空的的解决方法

搜索&#xff1a;设备管理器 然后下滑查看。 在查看的时候&#xff0c;发现OED上面有一个黄色的感叹号 右键更新之后即可&#xff0c;笔者是选择的本地更新。 之后右下角的声音设备就恢复正常了

局域网设备自动发现常用方法

文章目录 需求实现方法ARP (Address Resolution Protocol)Ping ip的流程抓包如下代码实现 mDNS 对比测试Avahi 介绍Avahi 安装Avahi 使用测试代码 需求 局域网设备自动发现是软件开发中的一个常见且重要的需求&#xff0c;它简化了设备间的协作机制&#xff0c;降低了软件各模…

自动化测试之Selenium的使用

06、Selenium的使用 Selenium 是一个自动化测试工具&#xff0c;利用它可以驱动浏览器执行特定的动作&#xff0c;如点击、下拉等操作&#xff0c;同时还可以获取浏览器当前呈现的页面的源代码&#xff0c;做到可见即可爬。对于一些 JavaScript 动态渲染的页面来说&#xff0c…

CleanMyMac X2024破解版mac电脑清理工具

今天&#xff0c;我要跟大家分享一个让我彻底告别电脑卡顿的秘密武器——CleanMyMac X。这不仅仅是一款普通的清理工具&#xff0c;它是你电脑的私人健身教练&#xff0c;让电脑焕发青春活力&#xff01; CleanMyMac直装官方版下载地址&#xff1a; http://wm.makeding.com/i…

公共英语三级考试时间安排

公共英语三级考试时间安排