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;elseled.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;elseled.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 faultpcon->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;
}

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

  1. 内核自带的基于GPIO的LED驱动学习(一)

    为什么学习内核自带的LED驱动? 前面已经学习过了基于纯字符设备的LED驱动,也学习过了基于平台驱动的LED驱动,但是感觉都是按照教程在生搬硬套,到底我们写出来的驱动能不能拿得上台面,是否能在实际的生 ...

  2. 内核自带的基于GPIO的LED驱动学习(三)

    上篇文章讲到了gpio_leds_create函数(),其定义位于drivers/leds/leds-gpio.c,如下: static struct gpio_leds_priv *gpio_led ...

  3. 不写一行代码(一):实现安卓基于GPIO的LED设备驱动

    文章目录 系列文章 一.前言 二.准备工作 2.1 内核版本 2.2 内核文档:bindings->leds 2.3 文档解析: leds-gpio.txt 三.编写DTS 3.1 查原理图,挑 ...

  4. 基于MTD的NAND驱动开发(二)

    基于MTD的NAND驱动开发(二) 基于MTD的NAND驱动开发(三) http://blog.csdn.net/leibniz_zsu/article/details/4977853 http:// ...

  5. 基于触摸屏的LED驱动电路设计

    本系统是利用触摸屏控制的可调颜色的照明灯具.灯源为大功率超高亮三基色发光二极管组成.本设计最大的亮点是触摸屏上所指示的颜色与实际灯照出的颜色一致.到通过c语言程序对单片机的PWM 功能进行控制实现相应 ...

  6. 基于exynos4412的led驱动编程

    本文基于华清4412开发板,讲解如何从零开始编写led驱动程序和测试程序.首先介绍一下该4412开发板的led硬件原理图. 从原理图上我们可以看出,让led点亮的条件是往对应端口送高电平,熄灭的条件是 ...

  7. 基于RK3399的LED驱动开发

    1.添加设备树 在设备树 arch/arm64/boot/dts/rockchip/rk3399-firefly-linux.dts 中添加 gpio-led{status = "okay& ...

  8. 字符设备驱动之Led驱动学习记录

    一.概述 Linux内核就是由各种驱动组成的,内核源码中大约有85%的各种渠道程序的代码.一般来说,编写Linux设备驱动大致流程如下: 1.查看原理图,数据手册,了解设备的操作方法. 2.在内核中找 ...

  9. 基于RK3399PRo的串口驱动学习-XR21V1414IM48

    目录 原理图 XR21V1414IM48简介 重点代码 函数入口初始化 tty串口操作集 USB转串初始化 设备ID 测试代码 头文件 宏定义 输入参数提示 打开设备 设置波特率 配置数据位.停止位. ...

最新文章

  1. IT小小鸟VS.小小小鸟:展翅,我们一起翱翔!
  2. C Implement a string class with basic functionality
  3. css 引用otf文件,CSS-如何将OTF / TTF文件转换为EOT格式?
  4. C语言经典例82-八进制转换为十进制
  5. 一些常规形几何形状的绘制和效果填充(一)
  6. css3动画应用-音乐唱片旋转播放特效
  7. h5专题应该兼容那些浏览器?
  8. ubuntu下对Atom配置python的虚拟环境
  9. 10、存储过程、while语句
  10. pandas read_sql
  11. 【LOJ】 #2521. 「FJOI2018」领导集团问题
  12. 小试ImageMagik——使用篇
  13. Qt翻译文件(.ts)的使用
  14. C语言小程序:找出100以内素数
  15. android10新特性 视频解码,Android万能视频播放器10-OpenGL ESMediaCodec解码数据t
  16. MyBatisPlus 查询selectOne方法
  17. 《数字图像处理》-(3)-2频率域滤波
  18. curly怎么读(curly怎么读音发音英语怎么说)
  19. 解决微信emjoy特殊符号插入数据库出错
  20. 蓝牙鼠标windows linux,Windows+Linux+MacOS三大系统共用蓝牙鼠标

热门文章

  1. python代码学习1
  2. IOS跟ANDROID的区别
  3. 【每日早报】2019/12/17
  4. 进入新公司一个月的一些感想
  5. Android BLE(低功耗蓝牙)在Android不同版本的适配问题,华为Mate30扫描不到蓝牙模块
  6. 学计算机的前后对比,2020计算机考研(408)大纲前后对比分析!
  7. Aleo大使公告将在本周发布,所有大使都必须通过 KYC
  8. 《勋伯格和声学》读书笔记(十三):在调性的边缘(对减七和弦和增七和弦及其转位和弦的进一步观察)
  9. 中国集装箱涂料行业调研与投资前景研究报告(2022版)
  10. 压力眼这一Part,久等啦!