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;
}