【IMX6ULL驱动开发学习】07.驱动程序分离的思想之平台总线设备驱动模型和设备树

news2024/12/22 23:06:42

一、驱动程序分离的思想

【IMX6ULL驱动开发学习】05.字符设备驱动开发模板(包括读写函数、poll机制、异步通知、定时器、中断、自动创建设备节点和环形缓冲区)_阿龙还在写代码的博客-CSDN博客

之前编写驱动程序的代码存在不少弊端:移植性差,驱动程序移植到别的板子上时,开发者需要修改引脚。,并且还要重新编译驱动程序或内核。为提高移植和开发效率,驱动程序分离编程的思想尤为重要。

首先我们要知道: 内核里有个结构体platform_bus_type(虚拟的总线),总线上抽象出两个链表:设备链表驱动链表

我们在写驱动程序时,可以构造platfrom_device结构体,然后把它添加进内核里(platform_device_register函数),就是放入platform_bus_type结构体的设备链表中。

驱动程序会调用两个函数,注册platform_device结构体和platform_driver结构体。platform_device结构体里含有硬件资源,包括寄存器地址、内存地址、中断号;platform_driver结构体里有通用的代码。 以前写驱动程序时,只写成一个.c文件,在入口函数里注册字符设备驱动程序;现在需要故意拆分成两个文件gpio_drv.c和gpio_dev.c。

在gpio_drv.c的入口函数里注册platform_driver结构体(用到platform_driver_register函数),在gpio_dev.c的入口函数里注册platform_device结构体(用到platform_device_register函数),该函数会把要注册的platform_device结构体放入内核中platform_bus_type结构体(虚拟总线)的设备链表,并且会遍历platform_bus_type结构体(虚拟总线)的驱动链表,将platform_device结构体和每一个platform_driver结构体进行比较(为硬件设备找驱动程序),匹配成功后就不会往后比较了。

如果匹配成功,会调用platform_driver结构体中的probe函数。在probe函数中完成:①从platform_device结构体中得到引脚编号 ②注册字符设备驱动程序。

如果事先添加了设备(platform_device结构体),但并没找到与之匹配的驱动程序(platform_driver结构体)。之后添加驱动时,会遍历设备链表,若匹配成功则调用驱动的probe函数。且一个驱动可能支持多个设备。

二、设备树

gpio_dev.c和设备树的目的都是为了构造platform_device结构体。如果用gpio_dev.c时需要每次都修改引脚,重新编译和安装,导致内核里有很多冗余的gpio_dev.c文件和platform_device结构体。 使用设备树:在设备树文件中添加节点信息,根据节点信息,内核会构造出platform_device结构体

板子启动时有个uboot,uboot会做两件事:①板子上如果有SD卡,SD卡中存放有设备树dtb文件,uboot会把设备树文件读入到内存中;②SD卡中还有内核,uboot会把内核读入内存;③启动内核,uboot会把设备树的地址传入内核,内核会这个地址上把设备树文件解析成各种platform_device结构体。

 

以后产品修改了引脚,我们只需要修改设备树dtb文件就可以了。内核不变,变设备树文件。

2.1 使用设备树

  • 修改设备树:arch/arm/boot/dts/100ask_imx6ull-14x14.dts

 在设备树文件中添加节点信息,注意compatible与驱动的compatible匹配

motor {
    compatible = "100ask,gpiodemo";
    gpios = <&gpio4 19 GPIO_ACTIVE_HIGH>, 
            <&gpio4 20 GPIO_ACTIVE_HIGH>,
            <&gpio4 21 GPIO_ACTIVE_HIGH>,
			<&gpio4 22 GPIO_ACTIVE_HIGH>;
};
  • 编译:make dtbs
  • 复制到板子上 
PC:
cp arch/arm/boot/dts/100ask_imx6ull-14x14.dtb ~/nfs_rootfs/

开发板:
mount -t nfs -o nolock,vers=3 192.168.5.11:/home/book/nfs_rootfs /mnt
cp /mnt/100ask_imx6ull-14x14.dtb  /boot
reboot
  •  测试
insmod gpio_drv.ko
./button_test /dev/gpio ...

三、平台总线设备驱动模板

支持platfrom_device来自自己写的.c文件和更改的设备树文件,包括中断、定时器、读写、poll机制、异步通知。驱动程序如下:

#include <linux/module.h>
#include <linux/poll.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 <linux/gpio/consumer.h>
#include <linux/platform_device.h>
#include <linux/of_gpio.h>
#include <linux/of_irq.h>
#include <linux/interrupt.h>
#include <linux/irq.h>
#include <linux/slab.h>
#include <linux/fcntl.h>
#include <linux/timer.h>

struct gpio_desc{
	int gpio;
	int irq;
    char name[128];
    int key;
	struct timer_list key_timer;
} ;

static struct gpio_desc *gpios;
static int count;

/* 主设备号                                                                 */
static int major = 0;
static struct class *gpio_class;

/* 环形缓冲区 */
#define BUF_LEN 128
static int g_keys[BUF_LEN];
static int r, w;

struct fasync_struct *button_fasync;

#define NEXT_POS(x) ((x+1) % BUF_LEN)

static int is_key_buf_empty(void)
{
	return (r == w);
}

static int is_key_buf_full(void)
{
	return (r == NEXT_POS(w));
}

static void put_key(int key)
{
	if (!is_key_buf_full())
	{
		g_keys[w] = key;
		w = NEXT_POS(w);
	}
}

static int get_key(void)
{
	int key = 0;
	if (!is_key_buf_empty())
	{
		key = g_keys[r];
		r = NEXT_POS(r);
	}
	return key;
}


static DECLARE_WAIT_QUEUE_HEAD(gpio_wait);

// static void key_timer_expire(struct timer_list *t)
static void key_timer_expire(unsigned long data)
{
	/* data ==> gpio */
	// struct gpio_desc *gpio_desc = from_timer(gpio_desc, t, key_timer);
	struct gpio_desc *gpio_desc = (struct gpio_desc *)data;
	int val;
	int key;

	val = gpio_get_value(gpio_desc->gpio);


	//printk("key_timer_expire key %d %d\n", gpio_desc->gpio, val);
	key = (gpio_desc->key) | (val<<8);
	put_key(key);
	wake_up_interruptible(&gpio_wait);
	kill_fasync(&button_fasync, SIGIO, POLL_IN);
}


/* 实现对应的open/read/write等函数,填入file_operations结构体                   */
static ssize_t gpio_drv_read (struct file *file, char __user *buf, size_t size, loff_t *offset)
{
	//printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
	int err;
	int key;

	if (is_key_buf_empty() && (file->f_flags & O_NONBLOCK))
		return -EAGAIN;
	
	wait_event_interruptible(gpio_wait, !is_key_buf_empty());
	key = get_key();
	err = copy_to_user(buf, &key, 4);
	
	return 4;
}

static ssize_t gpio_drv_write(struct file *file, const char __user *buf, size_t size, loff_t *offset)
{
    unsigned char ker_buf[2];
    int err;

    if (size != 2)
        return -EINVAL;

    err = copy_from_user(ker_buf, buf, size);
    
    if (ker_buf[0] >= sizeof(gpios)/sizeof(gpios[0]))
        return -EINVAL;

    gpio_set_value(gpios[ker_buf[0]].gpio, ker_buf[1]);
    return 2;    
}


static unsigned int gpio_drv_poll(struct file *fp, poll_table * wait)
{
	//printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
	poll_wait(fp, &gpio_wait, wait);
	return is_key_buf_empty() ? 0 : POLLIN | POLLRDNORM;
}

static int gpio_drv_fasync(int fd, struct file *file, int on)
{
	if (fasync_helper(fd, file, on, &button_fasync) >= 0)
		return 0;
	else
		return -EIO;
}


/* 定义自己的file_operations结构体                                              */
static struct file_operations gpio_key_drv = {
	.owner	 = THIS_MODULE,
	.read    = gpio_drv_read,
	.write   = gpio_drv_write,
	.poll    = gpio_drv_poll,
	.fasync  = gpio_drv_fasync,
};


static irqreturn_t gpio_key_isr(int irq, void *dev_id)
{
	struct gpio_desc *gpio_desc = dev_id;
	printk("gpio_key_isr key %d irq happened\n", gpio_desc->gpio);
	mod_timer(&gpio_desc->key_timer, jiffies + HZ/5);
	return IRQ_HANDLED;
}


/* 在入口函数 */
static int gpio_drv_probe(struct platform_device *pdev)
{
    int err = 0;
    int i;
	//平台设备里面有设备树节点信息
	//如果平台设备platform_device来自设备树的话,np就不是NULL
	struct device_node *np = pdev->dev.of_node;
	//资源指针 
	struct resource *res;
    
	printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
	

	/* 从platfrom_device获得引脚信息 
	 * 1. pdev来自自己写的c文件
     * 2. pdev来自设备树(在设备树文件中添加硬件的节点信息)
	 */
	
	if (np)
	{
		/* pdev来自设备树 
		设备树节点信息示例:
        reg_usb_ltemodule: regulator@1 {
            compatible = "100ask,gpiodemo";
            gpios = <&gpio5 5 GPIO_ACTIVE_HIGH>, <&gpio5 3 GPIO_ACTIVE_HIGH>;
        };
		*/
		count = of_gpio_count(np);//获得这个设备信息:多少个引脚
		if (!count)
			return -EINVAL;

		gpios = kmalloc(count * sizeof(struct gpio_desc), GFP_KERNEL);
		for (i = 0; i < count; i++)
		{
			gpios[i].gpio = of_get_gpio(np, i);//取出这个设备的第i个引脚的引脚编号
			sprintf(gpios[i].name, "%s_pin_%d", np->name, i);//给对应引脚取名字 申请gpio时需要用到名字
		}
	}
	else
	{
		/* pdev来自c文件 
		static struct resource omap16xx_gpio3_resources[] = {
			{
					.start  = 115,
					.end    = 115,
					.flags  = IORESOURCE_IRQ,
			},
			{
					.start  = 118,
					.end    = 118,
					.flags  = IORESOURCE_IRQ,
			},		};		
		*/
		count = 0;
		while (1)
		{	//获得平台设备里面的,这种IORESOURCE_IRQ类型的资源
			//@dev:platform_device  @type:resource type @num:resource index
			res = platform_get_resource(pdev, IORESOURCE_IRQ, count);
			if (res)
			{
				count++;
			}
			else
			{
				break;
			}
		}

		if (!count)
			return -EINVAL;

		gpios = kmalloc(count * sizeof(struct gpio_desc), GFP_KERNEL);
		for (i = 0; i < count; i++)
		{
			res = platform_get_resource(pdev, IORESOURCE_IRQ, i);
			gpios[i].gpio = res->start;//取出这个设备的第i个引脚的引脚编号
			sprintf(gpios[i].name, "%s_pin_%d", pdev->name, i);//给对应引脚取名字 申请gpio时需要用到名字
		}

	}

	for (i = 0; i < count; i++)
	{		
		gpios[i].irq  = gpio_to_irq(gpios[i].gpio);

		setup_timer(&gpios[i].key_timer, key_timer_expire, (unsigned long)&gpios[i]);
	 	//timer_setup(&gpios[i].key_timer, key_timer_expire, 0);
		gpios[i].key_timer.expires = ~0;
		add_timer(&gpios[i].key_timer);
		err = request_irq(gpios[i].irq, gpio_key_isr, IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING, "100ask_gpio_key", &gpios[i]);
	}

	/* 注册file_operations 	*/
	major = register_chrdev(0, "100ask_gpio_key", &gpio_key_drv);  /* /dev/gpio_desc */

	gpio_class = class_create(THIS_MODULE, "100ask_gpio_key_class");
	if (IS_ERR(gpio_class)) {
		printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
		unregister_chrdev(major, "100ask_gpio_key");
		return PTR_ERR(gpio_class);
	}

	device_create(gpio_class, NULL, MKDEV(major, 0), NULL, "100ask_gpio"); /* /dev/100ask_gpio */
	
	return err;
}

/* 有入口函数就应该有出口函数:卸载驱动程序时,就会去调用这个出口函数
 */
//这里应该free gpio这个数组 但是没加上不知道为啥
static int gpio_drv_remove(struct platform_device *pdev)
{
    int i;
    
	printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);

	device_destroy(gpio_class, MKDEV(major, 0));
	class_destroy(gpio_class);
	unregister_chrdev(major, "100ask_gpio_key");

	for (i = 0; i < count; i++)
	{
		free_irq(gpios[i].irq, &gpios[i]);
		del_timer(&gpios[i].key_timer);
	}

	return 0;
}

//支持的设备
//只要设备树节点的信息它的compatible与下面的compatible相同,
//即platfrom_device和platfrom_driver匹配成功,成功后probe函数就被调用
static const struct of_device_id gpio_dt_ids[] = {
        { .compatible = "100ask,gpiodemo", },
        { /* sentinel */ }
};

static struct platform_driver gpio_platform_driver = {
	.driver		= {
		.name	= "100ask_gpio_plat_drv",
		.of_match_table = gpio_dt_ids,
	},
	.probe		= gpio_drv_probe,
	.remove		= gpio_drv_remove,
};

static int __init gpio_drv_init(void)
{
	/* 注册platform_driver */
	return platform_driver_register(&gpio_platform_driver);
}

static void __exit gpio_drv_exit(void)
{
	/* 反注册platform_driver */
	platform_driver_unregister(&gpio_platform_driver);
}

/* 7. 其他完善:提供设备信息,自动创建设备节点                                     */

module_init(gpio_drv_init);
module_exit(gpio_drv_exit);

MODULE_LICENSE("GPL");


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

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

相关文章

治疗偏头痛等亚疼痛的远程电神经调控(REN)设备

原文链接&#xff1a; NERIVIO CE标志适应症扩展到青少年和成人偏头痛的预防和急性治疗 (prnewswire.com) 公司官网&#xff1a; Homepage - Theranica APP下载链接&#xff1a; Migraine Headache Treatment - Nerivio 使用过程问题&#xff1a; 常见问题 - 无药物偏头痛两…

解决Oracle中XML插入数据时的空格问题

&#x1f337;&#x1f341; 博主猫头虎 带您 Go to New World.✨&#x1f341; &#x1f984; 博客首页——猫头虎的博客&#x1f390; &#x1f433;《面试题大全专栏》 文章图文并茂&#x1f995;生动形象&#x1f996;简单易学&#xff01;欢迎大家来踩踩~&#x1f33a; &a…

05- 定时器

定时器 定时器5.1 独立看门狗定时器独立看门狗的配置步骤&#xff1a;看门狗计时时间的计算&#xff1a; 5.2 窗口看门狗看门狗有什么作用&#xff1f;窗口看门狗超时时间计算&#xff1a;窗口看门狗的配置过程&#xff1a; 5.3 通用定时器三种定时器&#xff1a; 计数器的时钟…

使用Jmeter进行压力测试你所不知道内幕

在网络很多网友反馈Jmeter的测试结果不准确&#xff0c;下面我们主要聊一下不准确的原因。 Jmeter 是ASF的一款开源免费软件 &#xff0c;在国内被很多中小公司当作性能测试工具广泛使用。Apache软件基金会&#xff08;ASF&#xff09;是一家总部位于美国的非营利性慈善组织。…

Binder机制原理解析

前言 我们都知道Android应用程序由Activity、Service、Broadcast Receiver、Content Provider四大组件构成的,他们可能运行在不同进程中&#xff0c;还有Android系统中还有各种服务&#xff0c;例如Actiivty管理服务ActivityManagerService、电源管理服务PowerManagerService、…

半导体退火那些事(3)

4.半导体退火设备 双腔全自动兼容6-8寸快速退火炉RTP 产地:中国 型号: S803 特点: 室温到1250C&#xff0c;应用于SiC&#xff0c;GaN等第三代半导体领域 简介 (Description) S803系列自动快速退火炉&#xff0c;内置Robot可以自动取放片&#xff0c;适用于最大8英寸 (单片200m…

【MySQL系列】-回表、覆盖索引真的懂吗

【MySQL系列】-回表、覆盖索引真的懂吗 文章目录 【MySQL系列】-回表、覆盖索引真的懂吗一、MYSQL索引结构1.1 索引的概念1.2 索引的特点1.3 索引的优点1.4 索引的缺点 二、B-Tree与BTree2.1 B-Tree2.2 BTree2.3 B-Tree 与BTree树的区别2.4 那么为什么InnoDB的主键最好要搞成有…

【数据结构OJ题】链表的回文结构

原题链接&#xff1a;https://www.nowcoder.com/practice/d281619e4b3e4a60a2cc66ea32855bfa?tpId49&&tqId29370&rp1&ru/activity/oj&qru/ta/2016test/question-ranking 目录 1. 题目描述 2. 思路分析 3. 代码实现 1. 题目描述 2. 思路分析 在做这道…

EMO实战:使用EMO实现图像分类任务(一)

文章目录 摘要安装包安装timm安装 grad-cam安装einops 数据增强Cutout和MixupEMA项目结构计算mean和std生成数据集 摘要 论文翻译&#xff1a;https://blog.csdn.net/m0_47867638/article/details/132034098?spm1001.2014.3001.5501 官方源码&#xff1a;https://github.com/…

uniapp项目APP云打包步骤

1.打开hbuilder工具&#xff0c;选择uniapp项目&#xff0c;从上方工具中选择发行 2.证书的生成&#xff0c;点击上面的如何生成证书的网址&#xff0c;按照步骤进行生成&#xff1a; 3.结果&#xff0c;点击打开所在目录&#xff0c;点击直接安装到手机&#xff0c;前提是手机…

excel日期函数篇1

1、DAY(serial_number)&#xff1a;返回序列数表示的某月的天数 在括号内给出一个时间对象或引用一个时间对象&#xff08;年月日&#xff09;&#xff0c;返回多少日 下面结果都为20 2、MONTH(serial_number)&#xff1a;返回序列数表示的某年的月份 在括号内给出一个时间对…

重新认识小米

被镁光灯聚焦的企业&#xff0c;总是会被贴上各种标签。 8月14日&#xff0c;小米科技创始人雷军以“成长”为主题的年度演讲&#xff0c;刷遍社交网络。提到小米&#xff0c;你首先想到什么&#xff1f;手机发烧友、极致性价比&#xff0c;还是最年轻的500强&#xff1f; 这…

手撕单链表

目录 链表的概念和结构 单链表的实现 申请新结点 打印 尾插 头插 尾删 头删 ​编辑 查找 在pos位置前插入元素 在pos位置后插入元素 删除pos位置的元素 删除pos位置之后的位置的元素​编辑 完整代码 SListNode.h SListNode.c 链表的概念和结构 链表是一种物理存储…

VS2022如何查看类成员都在哪里被调用了(VS如何打开Call Hierarchy视图)

文章目录 打开Call Hierarchy视图查看成员的调用 打开Call Hierarchy视图 单击菜单栏的“视图” > “调用层次结构”&#xff0c;即可打卡Call Hierarchy视图。 查看成员的调用 在代码编辑窗口&#xff0c;右键单击想要查看的类成员&#xff0c;然后选择“查看调用层次结…

2003-2021年全国30省市数字关注度数据

1、时间&#xff1a;2003-2021年 2、来源&#xff1a;政府工作报告 3、范围&#xff1a;30省市 不含西藏、其中2004年吉林缺失 4、指标&#xff1a;省份、年份、一级指标、二级指标、关键词、关键词词频、全文词频 5、词频&#xff1a;选取大数据、云计算、区块链、人工智能…

解决一直提示No module named “Crypto” 解决方案

今天跑脚本发现一直提示装Crypto包 已经装好了情况下还是这样&#xff1a; 解决方法&#xff1a; pip uninstall crypto pycryptodome pip install pycryptodome pycrypto和crypto是同一个库&#xff0c;crypto在 python 中又被称为pycrypto&#xff0c;它是一个第三方库&…

归并排序(C++ mpi 并行实现)

文章目录 主要思路1. 串行归并排序2. 进程的分发3. 对接收到的子数组进行排序4. 合并数组5.输出排序后的数组6.进程分发部分的优化7.完整代码 主要思路 我们首先实现串行的归并排序&#xff1b;实现进程的分发&#xff1b;排序其中的每个子部分&#xff1b;进程的合并通信&…

宝藏级画图工具-drawio

今天推荐一款非常好用的免费开源画图工具drawio. Drawio即可以下载安装到本地&#xff0c;也可以在线编辑&#xff0c;在线编辑网址为 https://app.diagrams.net/。 本地版下载地址为https://github.com/jgraph/drawio-desktop/releases 1、支持各类图形 Drawio可以非常便捷…

Docker服务编排Docker Compose介绍

1.服务编排概念 2.Docker Compose介绍 3.Docker Compose安装及使用

【【STM32----I2C通信协议】】

STM32----I2C通信协议 我们会发现I2C有两根通信线&#xff1a; SCL和SDA 同步 半双工 带数据应答 支持总线挂载多设备&#xff08;一主多从&#xff0c;多主多从&#xff09; 硬件电路 所有I2C设备的SCL连在一起&#xff0c;SDA连在一起 设备的SCL和SDA均要配置成开漏输出模式 …