内核自带的基于GPIO的LED驱动学习(二)

2023-11-10

2)分析平台驱动的probe函数
好,既然这个LED驱动使用的是平台驱动框架,当设备和驱动匹配上之后,就会执行指定的probe函数,那接下来的工作就转移到分析对应的probe函数了。为了直观,我把probe函数也粘贴上来。

static int gpio_led_probe(struct platform_device *pdev)
{
	struct gpio_led_platform_data *pdata = dev_get_platdata(&pdev->dev);
	struct gpio_leds_priv *priv;
	int i, ret = 0;

	if (pdata && pdata->num_leds) {
		priv = devm_kzalloc(&pdev->dev,
				sizeof_gpio_leds_priv(pdata->num_leds),
					GFP_KERNEL);
		if (!priv)
			return -ENOMEM;

		priv->num_leds = pdata->num_leds;
		for (i = 0; i < priv->num_leds; i++) {
			ret = create_gpio_led(&pdata->leds[i],
					      &priv->leds[i],
					      &pdev->dev, pdata->gpio_blink_set);
			if (ret < 0) {
				/* On failure: unwind the led creations */
				for (i = i - 1; i >= 0; i--)
					delete_gpio_led(&priv->leds[i]);
				return ret;
			}
		}
	} else {
		priv = gpio_leds_create(pdev);
		if (IS_ERR(priv))
			return PTR_ERR(priv);
	}

	platform_set_drvdata(pdev, priv);

	return 0;
}

拿到一个函数的时候,一般不要急于直接一行行地去分析代码细节,可以大概浏览一遍,这个函数大概是干嘛的,看看根据自己目前已有的认知,能看懂多少,看不懂的地方在哪里,这些看不懂的地方,是否会影响自己对这个函数功能的理解。总之,如果没有必要,千万不要一头扎进庞大的源码里面去。

对于这个probe函数,我们目前知道它是LED驱动的probe函数,所以里面应该要配置LED对应的gpio引脚,还应该要为每个LED分配一个私有结构体来保存必要的信息,还要做以前纯字符设备驱动里面做的一些工作,比如创建class和device,在/dev下生成一个应用可访问的设备节点。这是我目前能够想到的,可能它还会做其它的工作,但是我觉得应该八九不离十。
函数的主体是如下的一个选择结构

if (pdata && pdata->num_leds)
{
	...
} else {
	...
}

我想,我们要分析下去,必须的知道 if(pdata && pdata->num_leds) 这个判断是什么意思。pdata是一个指针,定义如下:

struct gpio_led_platform_data *pdata = dev_get_platdata(&pdev->dev);

它什么时候为空?什么时候非空?从定义来看,pdata是通过dev_get_platdata()获取到的该平台设备的私有数据。

static inline void *dev_get_platdata(const struct device *dev)
{
	return dev->platform_data;
}

私有数据是跟平台设备相关,不同的平台设备,私有数据是不同的,需要由驱动开发人员来定义。那内核就不可能知道该用什么类型来表示,所以是一个void *。当我们还没有给它分配内存的时候,这个指针肯定是空的(因为内核不知道具体类型,不会给它分配内存),也就是说,当一个平台设备与驱动匹配上的时候,执行probe函数

struct gpio_led_platform_data *pdata = dev_get_platdata(&pdev->dev);

这个时候,由于还没有分配私有数据,pdata就是NULL,会走else分支。那么,会存在匹配上,pdata却不为空的情况吗?这个我暂时还想不到,所以这里就先不考虑了。

当平台设备与驱动匹配上的时候,由于私有数据为空,应该走else分支,调用gpio_leds_create()函数。一起来看看gpio_leds_create()做了些什么事情,我把函数的定义贴出来:

static struct gpio_leds_priv *gpio_leds_create(struct platform_device *pdev)
{
	struct device *dev = &pdev->dev;
	struct fwnode_handle *child;
	struct gpio_leds_priv *priv;
	int count, ret;
	struct device_node *np;

	count = device_get_child_node_count(dev);
	if (!count)
		return ERR_PTR(-ENODEV);

	priv = devm_kzalloc(dev, sizeof_gpio_leds_priv(count), GFP_KERNEL);
	if (!priv)
		return ERR_PTR(-ENOMEM);

	device_for_each_child_node(dev, child) {
		struct gpio_led led = {};
		const char *state = NULL;

		led.gpiod = devm_get_gpiod_from_child(dev, NULL, child);
		if (IS_ERR(led.gpiod)) {
			fwnode_handle_put(child);
			ret = PTR_ERR(led.gpiod);
			goto err;
		}

		np = of_node(child);

		if (fwnode_property_present(child, "label")) {
			fwnode_property_read_string(child, "label", &led.name);
		} else {
			if (IS_ENABLED(CONFIG_OF) && !led.name && np)
				led.name = np->name;
			if (!led.name)
				return ERR_PTR(-EINVAL);
		}
		fwnode_property_read_string(child, "linux,default-trigger",
					    &led.default_trigger);

		if (!fwnode_property_read_string(child, "default-state",
						 &state)) {
			if (!strcmp(state, "keep"))
				led.default_state = LEDS_GPIO_DEFSTATE_KEEP;
			else if (!strcmp(state, "on"))
				led.default_state = LEDS_GPIO_DEFSTATE_ON;
			else
				led.default_state = LEDS_GPIO_DEFSTATE_OFF;
		}

		if (fwnode_property_present(child, "retain-state-suspended"))
			led.retain_state_suspended = 1;

		ret = create_gpio_led(&led, &priv->leds[priv->num_leds++],
				      dev, NULL);
		if (ret < 0) {
			fwnode_handle_put(child);
			goto err;
		}
	}

	return priv;

err:
	for (count = priv->num_leds - 2; count >= 0; count--)
		delete_gpio_led(&priv->leds[count]);
	return ERR_PTR(ret);
}

首先,这个函数的返回值是指向struct gpio_leds_priv结构体的指针,也就是平台设备的私有数据结构体的首地址,用来保存LED平台设备信息的,其定义如下:

struct gpio_leds_priv {
	int num_leds;
	struct gpio_led_data leds[];
};

诶,一看这个结构体,它居然有一个变量num_leds,应该是用来保存具体的LED设备的数量。然后有一个struct gpio_led_data类型的数组,应该是用来保存该平台设备下面各个LED设备的信息。进入到函数gpio_leds_create(),我们来看看函数里面做了什么。

1)获取LED设备的数量

count = device_get_child_node_count(dev);

其中,参数dev就是匹配到的平台设备。从这里可以看出来,内核自带的这个LED驱动是将LED设备进行分组匹配的,也就是说dev代表的平台设备其实是设备树里面的一个LED分组,并不是一个真正的LED设备。所以在这里, LED分组 = LED平台设备,后面的内容就会混用这两个概念了。真正的LED设备是作为LED分组下面的子节点存在的,也就是说,这个LED驱动所对应的设备树结构应该是类似下面的模型:

LED-GRP1 {
	compatible = "leds,kernel"

	led-red {
		...
	};
	led-green {
		...
	};
};

LED-GRP2 {
	compatible = "leds,kernel"

	led-yellow {
		...
	};
	led-blue {
		...
	};
};

匹配到一个LED分组时,就会执行probe函数,然后通过device_get_child_node_count(dev)获取分组下面的LED设备数量。

2)为LED分组分配一个结构体struct gpio_leds_priv,作为该平台设备的私有数据。devm_kzalloc()是内核的内存分配函数,其原型位于include/linux/device.h中(关于devm_kzalloc,请参考《深入分析linux内核的内存分配函数devm_kzalloc》)

priv = devm_kzalloc(dev, sizeof_gpio_leds_priv(count), GFP_KERNEL);

dev表示这片私有数据内存需要关联到的平台设备。使用devm_kzalloc()申请的内存,不需要我们手动释放,因为我们将其关联到平台设备dev,当平台设备与驱动分离时,这片内存会自动释放。这里我们要申请的内存大小为sizeof_gpio_leds_priv(count)。sizeof_gpio_leds_priv是一个内联函数,其定义如下:

static inline int sizeof_gpio_leds_priv(int num_leds)
{
	return sizeof(struct gpio_leds_priv) +
		(sizeof(struct gpio_led_data) * num_leds);
}

这个函数的功能是计算当LED分组里面有num_leds个LED设备的时候,结构体struct gpio_leds_priv的大小。我们把struct gpio_leds_priv的定义重新贴出来:

struct gpio_leds_priv {
	int num_leds;
	struct gpio_led_data leds[];
};

再次看这个结构体的定义,里面leds这个结构体数组,它并没有指明大小,是不是很奇怪?不指明数组大小,编译不会出错吗?大家平时可没有这样用过吧,但是人家内核代码就这样用了。为什么它不在定义时指明数组的大小?因为它根本不知道该设置为多大,内核开发人员可不能提前预测你会在一个分组下挂多少个LED,对吧。像这样的情况,我们平时都是定义一个指针,然后根据需要来分配对应大小的内存。人家这里不是定义指针,而是直接定义了一个没有指定大小的数组(经过测试,其效果相当于定义了一个已经有指向的指针,其指向的内存位置相当于直接定义一个struct gpio_led_data的led成员时,led的地址)。实际上,它是利用了数组的存储空间在内存上是连续的特点,通过使用类似leds[i]的引用来将devm_kzalloc()分配的内存的尾部当做struct gpio_led_data来访问(关于这点的用法,参考文末的参考代码1)。所以我们使用devm_kzalloc()来分配内存的时候,应该分配sizeof(struct gpio_leds_priv) + (sizeof(struct gpio_led_data) * num_leds)大小的内存空间。

3)遍历LED分组下面的所有LED设备
好,我们通过devm_kzalloc(),为匹配到的LED分组分配了一个私有结构体(关于devm_kzalloc,请参考《深入分析linux内核的内存分配函数devm_kzalloc》),里面可以保存num_leds个LED设备的信息,接下来就应该遍历这个LED分组下面的所有LED设备,提取它们的信息,保存到各自对应的struct gpio_led_data里面去。代码里面使用了一个for循环来做这件事:

device_for_each_child_node(dev, child) {
	...
}

这个for循环里面大概做了以下几件事情:

3-1)定义一个struct gpio_led类型的变量led,来临时保存该LED设备的信息

struct gpio_led led = {};

struct gpio_led的定义如下:

struct gpio_led {
	const char *name;
	const char *default_trigger;
	unsigned 	gpio;
	unsigned	active_low : 1;
	unsigned	retain_state_suspended : 1;
	unsigned	default_state : 2;
	/* default_state should be one of LEDS_GPIO_DEFSTATE_(ON|OFF|KEEP) */
	struct gpio_desc *gpiod;
};

3-2)获取LED设备使用的GPIO引脚信息

led.gpiod = devm_get_gpiod_from_child(dev, NULL, child);

3-3)获取LED设备的设备树节点

np = of_node(child);

3-4)解析LED设备的设备树节点内容,保存到led变量里面

if (fwnode_property_present(child, "label")) {
			fwnode_property_read_string(child, "label", &led.name);
		} else {
			if (IS_ENABLED(CONFIG_OF) && !led.name && np)
				led.name = np->name;
			if (!led.name)
				return ERR_PTR(-EINVAL);
		}
		fwnode_property_read_string(child, "linux,default-trigger",
					    &led.default_trigger);

		if (!fwnode_property_read_string(child, "default-state",
						 &state)) {
			if (!strcmp(state, "keep"))
				led.default_state = LEDS_GPIO_DEFSTATE_KEEP;
			else if (!strcmp(state, "on"))
				led.default_state = LEDS_GPIO_DEFSTATE_ON;
			else
				led.default_state = LEDS_GPIO_DEFSTATE_OFF;
		}

		if (fwnode_property_present(child, "retain-state-suspended"))
			led.retain_state_suspended = 1;

3-5)使用led变量作为模板,创建一个LED字符设备

ret = create_gpio_led(&led, &priv->leds[priv->num_leds++],  dev, NULL);

这5个步骤执行完毕后,就成功创建了一个LED字符设备。这个for循环遍历结束之后,这个LED分组下面的所有LED设备都被创建为LED字符设备。这里只是大概讲了下这个for循环里面所做的工作,下一篇将详细讲解各个步骤里面具体的知识点。

参考代码1(Ubuntu16.04下编译测试)

#include <stdio.h>

struct pair
{
	const char *name;
	int value;
};

struct container
{
	int number;
	struct pair parameter[];
};

int main(int argc, char *argv)
{
	int i;
	unsigned char buf[512];
	struct container *pcon = (struct container *)buf;

	//应保证buf的内存空间足够大,否则会引起Segmentation fault
	pcon->number = 1;
	pcon->parameter[0].name = "first";
	pcon->parameter[0].value = 1;

	pcon->number++;
	pcon->parameter[1].name = "second";
	pcon->parameter[1].value = 2;

	pcon->number++;
	pcon->parameter[2].name = "third";
	pcon->parameter[2].value = 3;

	for(i = 0; i < pcon->number; i++)
	{
		printf("%dth pair:\n", i+1);
		printf("name:%s, value:%d\n", pcon->parameter[i].name, pcon->parameter[i].value);
	}

	return 0;
}
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

内核自带的基于GPIO的LED驱动学习(二) 的相关文章

  • 使用命令把类打成jar包

    测试用类 public class Hello public static void main String args System out println hello world 一般的Jar包 生成class文件 在命令行中输入下面代码
  • windows桌面应用自动化测试

    1 AutoIt3 原理 使用spy抓应用的hwnd 根据hwnd获取窗口信息 模拟发送鼠标按键 移动窗口实现自动化操作 缺点 获取到的信息少 编程实现复杂 2 UIAutomation msdn介绍 Microsoft UI Automa
  • STM32 USB DFU功能

    STM32 USB DFU功能 工具的安装配置 CubeMX上配置 完善接口 工具使用 HEX固件转为DFU文件 更新固件 DFU特点 工程代码 DFU的全称为 DownLoad Firmware Update即固件升级 以下配置以STM3
  • Junit5单元测试

    配置 Maven配置 我用的spring版本是2 2 2 其实引入一个就行
  • 解读开源 Go HTTP 框架 Hertz

    前言 在参与 Hertz 框架的开发迭代过程中 对 Hertz 的主库也越来越熟悉 接下来的几篇文章我将分别解析 Hertz 的服务注册 服务发现和负载均衡拓展 最后会使用适配于 Hertz 的 etcd 拓展进行实战 欢迎大家关注 Her
  • DSP之TMS320F28335学习总结与笔记(二)————ADC模块

    F28335 ADC模块 ADC转换模块 A D转换器 ADC 将模拟量转换为数字量通常要经过四个步骤 采样 保持 量化和编码 采样 将一个时间上连续变化的模拟量转化为时间上离散变化的模拟量 保持 将采样结果存储起来 直到下次采样 这个过程

随机推荐

  • 高精度斐波那契

    1 为何会有高精度斐波那契一说 0 0 1 1 2 1 3 2 4 3 5 5 6 8 7 13 8 21 9 34 10 55 11 89 12 144 13 233 14 377 15 610 16 987 17 1597 18 258
  • CentOS6/7 日常管理--启动服务及日志查看

    CentOS6 chkconfig chkconfig list显示开机启动服务列表 chkconfig level 3 服务名 on off chkconfig 服务名 on off chkconfig del 服务名 删除 关闭 服务
  • 程序员搜索引擎比较

    正如大家口中所说的一样 百度搜索引擎吃相太难看了 如果谷歌搜索还在的话 百度搜索的日子可能并不会太好过 其他搜索引擎 1 360搜索 比较少用甚至没有用过的搜索引擎 360的全家桶太恶心了 简直是流氓 所以很多时候360安全管家 杀毒什么的
  • 开源框架 WebFirst 一键生成项目,在线建表

    1 WebFirst框架描述 WebFirst 是果糖大数据团队开发的新一代 高性能 代码生成器 数据库设计工具 由 net core sqlsugar 开发 导入1000个表只要1 2秒 用法简单 功能强大 支持多种数据库 具体功能如下
  • Bundle Adjustment 光束平差法

    https www cnblogs com Jessica jie p 7739775 html 感觉这个链接讲的比较好理解 看slam的书完全一脸懵
  • linux启动redis失败,解决redis服务启动失败的问题

    最近学redis 就遇到了各种坑 在这里分享一下 我是将redis做成后台 安装 配置环境变量统统省略掉了 做成后台服务呢 首先 cd到redis的安装目录下 再cd到util 接着执行 install server sh 然后修改服务名称
  • windows批处理文件删除n天前的文件

    author skate time 2010 12 23 windows批处理文件删除n天前的文件 用批处理文件删除n天前的文件如果操作系统是 Windows Server 2003 那就好办了 因为它有一个forfiles命令能够查找满足
  • BI大数据名词术语

    大数据的出现带来了许多新的术语 但这些术语往往比较难以理解 因此 我们通过本文给出一个常用的大数据术语表 抛砖引玉 供大家深入了解 部分定义参考了相应的博客文章 A 聚合 Aggregation 搜索 合并 显示数据的过程 算法 Algor
  • Postman之全局变量、环境变量

    1 什么是环境变量 环境变量 postman可以自定义环境参数值 这样就不用每次请求都去输入某些值 直接引用设置的值 使我们的测试更方便 例如 通过变换环境变量来快速变换环境地址 可将本地 测试环境的IP添加至不同的环境变量 调用时使用 x
  • 'react-scripts' 不是内部或外部命令 、propTypes is not defined 解决办法

    在手动安装了React 提供的第三方库 prop types报错的 我的React 就报错运行不了了 报错如下 react scripts 不是内部或外部命令 也不是可运行的程序 或批处理文件 这是一个在学习React 踩的坑 百度了一下
  • webpack打包vue

    在Windows10系统下 自定义打包整个Vue文件夹项目的相关配置项涉及以下内容 安装Node js和npm Node js官网 https nodejs org en download npm是Node js的包管理工具 在Node j
  • JDK8 Date 日期常用使用方法

    JDBC日期的转换 最新JDBC映射将把数据库的日期类型和Java 8的新类型关联起来 SQL gt Java date gt LocalDate time gt LocalTime timestamp gt LocalDateTime L
  • X11相关参数设置

    X11相关参数设置 X11 环境变量 DISPLAY 用来设置将图形显示到何处 变量格式 Xlib connection to 0 0 refused by server 开关闭屏幕显示 连接实际屏幕 确认实际屏幕的name of disp
  • 模式识别和计算机应用(转载)

    导读 只是覆盖了很小的范围 但总结的不错 1 数学方面 1 矩阵的各种分解 比如 LU QR Cholesky SVD Polar 2 广义逆与子空间 3 最小二乘法 特别齐性方程Ax b的各种解法及其几何意义 4 凸分析与凸优化的基本知识
  • 8种专坑同事的 SQL 写法,性能降低100倍,你学会了吗?

    程序员的成长之路 互联网 程序员 技术 资料共享 关注 阅读本文大概需要 14 分钟 来自 juejin cn post 6844903998974099470 1 LIMIT 语句 分页查询是最常用的场景之一 但也通常也是最容易出问题的地
  • R语言Logistic回归模型深度验证以及Nomogram绘制

    R语言Logistic回归模型深度验证以及Nomogram绘制 小易学统计 互联网医疗统计师 自立 爱家人 15 人 赞同了该文章 01 研究背景
  • openwrt解除软件包安装限制

    Openwrt解除验证限制 sed i s tdetect package t detect package g koolshare scripts ks tar install sh
  • 安卓开发笔记——关于java.lang.RuntimeException: Unable to start activity ComponentInfo{......}问题的解决方案

    笔者在实现一个消息上下轮播时 遇到APP闪退 将日志打印出来 Process com ghl intelligence PID 6557 java lang RuntimeException Unable to start activity
  • 公众号如何快速接入查题功能

    最近很多同学私信小睿 我不会编程 写代码 我怎么能做一个自己的查题公众号呢 既然这么多同学最近在问 那小睿今天就来告诉大家 调用量瞩目 首先你需要一个查题的接口 然后配置到公众号内就可以了 那么接口从哪儿来呢 当然是找小睿啦 小睿为了使大家
  • 内核自带的基于GPIO的LED驱动学习(二)

    2 分析平台驱动的probe函数 好 既然这个LED驱动使用的是平台驱动框架 当设备和驱动匹配上之后 就会执行指定的probe函数 那接下来的工作就转移到分析对应的probe函数了 为了直观 我把probe函数也粘贴上来 static in