当前位置: 首页 > news >正文

三种Linux字符设备驱动写法-2:总线设备驱动框架

这篇文章中介绍Linux总线设备驱动框架,重点是体会整个框架,特别是感受一下分层的思想和抽象的方法,很多结构体中的成员不必深究到底如何实现或者有何作用,我将在今后的文章中深入探讨。

三种Linux字符设备驱动写法-1:最简单的基本框架

1. 分层思想

回顾第一篇,最简单的驱动框架:

如下,在驱动程序中直接把资源(IO引脚)写死

static unsigned long gpio_va;
 
#define GPIO_OFT(x) ((x) - 0x56000000)
#define GPFCON  (*(volatile unsigned long *)(gpio_va + GPIO_OFT(0x56000050)))
#define GPFDAT  (*(volatile unsigned long *)(gpio_va + GPIO_OFT(0x56000054)))

有单片机基础,或者c程序写得多的朋友可能就发现这种写法的缺陷了:

当程序简单时还好,程序复杂了,又需要换硬件时,将会变得难以修改。所以需要将硬件相关部分的代码给抽离开,这就叫分层,硬件层一部分代码,驱动层一部分代码,在单片机中,常见的处理方法是利用单独的头文件定义引脚或者其他功能,比写在一个文件中好一些,但是如果有修改还是需要整个程序重新编译。

Linux系统中采用总线设备驱动模型,完美解决了这些问题。

引入platform_device/platform_driver,将“硬件资源”与“驱动”分离开

同样以led驱动为例,我们编写两个源文件,led_drv.c表示驱动,led_dev.c表示设备(资源),编译生成led_drv.ko和led_dev.ko两个文件,insmod后即可正常使用,当我们需要修改硬件时,只需要rmmod led_dev,然后重新加载即可,而不需要卸载修改led_drv。

2. platform_device

static int led_dev_init(void)
{
	platform_device_register(&led_dev);
	return 0;
}

static void led_dev_exit(void)
{
	platform_device_unregister(&led_dev);
}

module_init(led_dev_init);
module_exit(led_dev_exit);

先从入口看,和第一篇的基本驱动一样,只不过入口从register_chrdev变成了注册platform_device。

platform_device_register(&led_dev)函数中的led_dev是一个platform_device结构体

static struct platform_device led_dev = {
    .name         = "myled",
    .id       = -1,
    .num_resources    = ARRAY_SIZE(led_resource),
    .resource     = led_resource,
    .dev = { 
    	.release = led_release, 
	},
};

结构体原型:

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

第一项:name,取个名字

第二项:id,设备实例号,或者用“-1”表示只有一个实例。

第三项:dev,一个device结构体,这里实现一个空函数,不会报警告就行,其他成员用不到。之后会出文章细讲。

    .dev = { 
    	.release = led_release, 
	},
static void led_release(struct device * dev)
{
}

第四项:num_resources,资源的数目,ARRAY_SIZE(led_resource)返回资源数目

第五项:resource,资源数组

使用resource 结构体实现一个资源数组,数组内容就是我们的硬件资源

start为起始,end为结束,flags为资源类型,我的开发板GPF4、GPF5、GPF6为三个led灯

GPFCON为0x56000050,GPFDAT为0x56000054,定义如下资源,实际上有第一个就能控制所有灯,这里这样写是为了突出展示使用资源数组的作用和便捷性。

static struct resource led_resource[] = {
    {
        .start = 0x56000050,
        .end   = 0x56000050 + 8 - 1,
        .flags = IORESOURCE_MEM,
    },
    {
        .start = 4,
        .flags = IORESOURCE_IO,
    },
	{
        .start = 5,
        .flags = IORESOURCE_IO,
    },
	{
        .start = 6,
        .flags = IORESOURCE_IO,
    },

};

3. platform_driver

static int led_drv_init(void)
{
	platform_driver_register(&led_drv);
	return 0;
}

static void led_drv_exit(void)
{
	platform_driver_unregister(&led_drv);
}

module_init(led_drv_init);
module_exit(led_drv_exit);

一样从入口看,注册platform_driver。

platform_driver_register(&led_drv)函数中的led_drv是一个platform_driver结构体

struct platform_driver led_drv = {
	.probe		= led_probe,
	.remove		= led_remove,
	.driver		= {
		.name	= "myled",
	}
};

结构体原型:

struct platform_driver {
	int (*probe)(struct platform_device *);
	int (*remove)(struct platform_device *);
	void (*shutdown)(struct platform_device *);
	int (*suspend)(struct platform_device *, pm_message_t state);
	int (*suspend_late)(struct platform_device *, pm_message_t state);
	int (*resume_early)(struct platform_device *);
	int (*resume)(struct platform_device *);
	struct device_driver driver;
};

以后出文章细讲这个结构体,这里只需要几个基本成员:

1.probe,探针,在这个函数中要获取资源、注册字符设备,创建设备等

static int led_probe(struct platform_device *pdev)
{
	struct resource		*res;
	led_num=0;

	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
	gpio_con = ioremap(res->start, res->end - res->start + 1);
	gpio_dat = gpio_con + 1;

	major = register_chrdev(0, "myled", &led_fops);
	cls = class_create(THIS_MODULE, "myled");

	while(1)
	{
		res = platform_get_resource(pdev, IORESOURCE_IO, led_num);
		if(res==NULL)
			break;
		led_num++;
		pin[led_num] = res->start;

		printk("led_probe, found led%d\n",led_num);

		class_device_create(cls, NULL, MKDEV(major, led_num), NULL, "led%d",led_num); /* /dev/led */
	}
	return 0;
}

使用platform_get_resource获取资源,pin[]数组中保存了引脚的值,然后循环创建设备节点,有几个led资源就创建几个led节点。

static int led_open(struct inode *inode, struct file *file)
{
	int minor;
	minor=iminor(inode);
	
	led_init(minor);

	return 0;	
}

static ssize_t led_write(struct file *file, const char __user *buf, size_t count, loff_t * ppos)
{
	int val;
	
	int minor = iminor(file->f_path.dentry->d_inode);
	printk("write led%d\n",minor);

	copy_from_user(&val, buf, count); //	copy_to_user();

	led_ctl(minor,int);
	
	return 0;
}

file_operations结构体中的open和write和第一篇其实是差不多的,区别在于把初始化操作封装进了led_init()函数,把控制操作封装进了led_ctl()函数,并通过次设备号来区分对哪一个led进行操作。

从inode结构体中获取次设备号:

minor=iminor(inode);

从file结构体中获取次设备号:

int minor = iminor(file->f_path.dentry->d_inode);

2.remove,这个函数表示移除,销毁设备

static int led_remove(struct platform_device *pdev)
{
	int i;
	i=0;
	for(i=0;i<led_num;i++)
		{
			class_device_destroy(cls, MKDEV(major, i));
		}
	class_destroy(cls);
	unregister_chrdev(major, "myled");
	iounmap(gpio_con);
	
	return 0;
}

3.driver,这个结构体中的名字要与platform_device中的name对应,这样platform_dev与platform_drv才能对应起来。

insmod led_drv.ko没有反应很正常

insmod led_dev.ko后打印出了找到三个设备说明platform_dev和platform_drv对应上了

再通过命令看一下设备节点是否创建成功

接下来便可通过应用程序控制着三个led了。

./ledtest /dev/led1 on使led1亮,以此类推,应用程序源码和第一篇中的基本差不多。

这篇文章讲得不是很细致,因为重点其实就是感受一下分层思想,并且了解linux系统中一些抽象出来的结构体,实际上手工作,你在源码中搜索,便可以找到很多可以参考的代码,随着工作深入,自然会对这些结构体熟悉。今后我也将深入分析部分结构体。

源码https://download.csdn.net/download/freestep96/86731971

相关文章:

  • 力扣刷题篇之【数组篇】
  • 【opencv-c++】图像像素的逻辑操作
  • Jupyter Notebook 远程连接服务器 (mac os)
  • 中国电信5G技术承载网络
  • 【程序填空】表达式计算(栈应用)C++
  • 朴素贝叶斯关于naivebayes包核函数等
  • 泛型的详解
  • 数据填报系统可有效解决业务部门哪些问题?_光点科技
  • 【Web基础】FilterListener
  • 【数模】数模入门与常用算法汇总
  • 【深度学习100例】—— 使用pytorch实现LSTM自动AI作诗(藏头诗和首句续写)| 第6例
  • Linux的开发工具
  • Cadence Allegro Vertex功能介绍使用方法Vertex与Slide的区别图文教程
  • Qt5开发从入门到精通——第九篇六节( Qt5 文件及磁盘处理—— 监视文件和目录变化)
  • 接收节点无线广播发送的数据,并printf打印出来(含核心代码)_物联网挑战赛第四届第一题
  • .pyc 想到的一些问题
  • GDB 调试 Mysql 实战(三)优先队列排序算法中的行记录长度统计是怎么来的(上)...
  • golang 发送GET和POST示例
  • maven工程打包jar以及java jar命令的classpath使用
  • oldjun 检测网站的经验
  • 程序员最讨厌的9句话,你可有补充?
  • 大型网站性能监测、分析与优化常见问题QA
  • 多线程 start 和 run 方法到底有什么区别?
  • 工程优化暨babel升级小记
  • 海量大数据大屏分析展示一步到位:DataWorks数据服务+MaxCompute Lightning对接DataV最佳实践...
  • 一起来学SpringBoot | 第十篇:使用Spring Cache集成Redis
  • 以太坊客户端Geth命令参数详解
  • 追踪解析 FutureTask 源码
  • ​Distil-Whisper:比Whisper快6倍,体积小50%的语音识别模型
  • ​html.parser --- 简单的 HTML 和 XHTML 解析器​
  • #绘制圆心_R语言——绘制一个诚意满满的圆 祝你2021圆圆满满
  • (16)UiBot:智能化软件机器人(以头歌抓取课程数据为例)
  • (ZT) 理解系统底层的概念是多么重要(by趋势科技邹飞)
  • (二)基于wpr_simulation 的Ros机器人运动控制,gazebo仿真
  • (附源码)springboot优课在线教学系统 毕业设计 081251
  • (更新)A股上市公司华证ESG评级得分稳健性校验ESG得分年均值中位数(2009-2023年.12)
  • (机器学习的矩阵)(向量、矩阵与多元线性回归)
  • **PyTorch月学习计划 - 第一周;第6-7天: 自动梯度(Autograd)**
  • ./configure、make、make install 命令
  • .NET Compact Framework 3.5 支持 WCF 的子集
  • .net core 实现redis分片_基于 Redis 的分布式任务调度框架 earth-frost
  • .Net Core/.Net6/.Net8 ,启动配置/Program.cs 配置
  • .NET MVC第三章、三种传值方式
  • .NET中的Event与Delegates,从Publisher到Subscriber的衔接!
  • @Autowired和@Resource装配
  • @EnableAsync和@Async开始异步任务支持
  • @ModelAttribute注解使用
  • @RequestBody与@ModelAttribute
  • [ linux ] linux 命令英文全称及解释
  • [ vulhub漏洞复现篇 ] Grafana任意文件读取漏洞CVE-2021-43798
  • [【JSON2WEB】 13 基于REST2SQL 和 Amis 的 SQL 查询分析器
  • [android] 手机卫士黑名单功能(ListView优化)
  • [AutoSar]状态管理(五)Dcm与BswM、EcuM的复位实现
  • [BZOJ4566][HAOI2016]找相同字符(SAM)
  • [cocos2d-x]关于CC_CALLBACK