LV195-rangs与platrorm资源获取
一、platform_device 怎么获取资源?
1. 两种方式
前面我们知道,对于 platform_device 来说,我们有两种方式可以获取对应的节点,一是 of 操作函数,就正常的查找对应节点即可,二是 platform_device.dev.of_node。通过这两种方式我们可以获取到节点的地址,从而获取节点中各种资源。
由于设备树在系统启动的时候有部分节点会转化为 platform 设备,对于 platform_device 设备来说
(1)如果在设备树节点里使用 reg 属性,那么内核生成对应的 platform_device 时会用 reg 属性来设置 IORESOURCE_MEM 类型的资源。
(2)如果在设备树节点里使用 interrupts 属性 ,那么内核生成对应的 platform_device 时会用 reg 属性来设置 IORESOURCE_IRQ 类型的资源。(对于 interrupts 属性,内核会检查它的有效性,所以不建议在设备树里使用该属性来表示其他资源。 )
所以,实际上当设备树中的节点被转换为 platform_device 后,设备树中的 reg 属性、 interrupts 属性也会被转换为“ resource”,内核为我们提供了函数 platform_get_resource() 来获取这些不同的资源。接下来就来看一下吧。
2. platform_get_resource()
platform_get_resource() 函数定义如下:
struct resource *platform_get_resource(struct platform_device *dev,
unsigned int type, unsigned int num)
{
int i;
for (i = 0; i < dev->num_resources; i++) {
struct resource *r = &dev->resource[i];
if (type == resource_type(r) && num-- == 0)
return r;
}
return NULL;
}
EXPORT_SYMBOL_GPL(platform_get_resource);【参数说明】
- dev:struct platform_device 类型指针,包含了设备树节点的相关信息。
- type:资源的类型,IORESOURCE_MEM、 IORESOURCE_REG、IORESOURCE_IRQ 等
- num:这类资源中的哪一个。
二、一个报错
那么我们用上面的 platform_get_resource() 函数来尝试获取一下资源。
1. 设备树
/dts-v1/;
/ {
model = "Freescale i.MX6 UlltraLite ALPHA EMMC Board";
compatible = "fsl,imx6ull-alpha-emmc", "fsl,imx6ull";
alpha {
#address-cells = <1>;
#size-cells = <1>;
compatible = "simple-bus";
sdev_led {
#address-cells = <1>;
#size-cells = <1>;
compatible = "led";
status = "okay";
reg = < 0X020C406C 0X04 /* CCM_CCGR1_BASE */
0X020E0068 0X04 /* SW_MUX_GPIO1_IO03_BASE */
0X020E02F4 0X04 /* SW_PAD_GPIO1_IO03_BASE */
0X0209C000 0X04 /* GPIO1_DR_BASE */
0X0209C004 0X04 >; /* GPIO1_GDIR_BASE */
};
};
};2. demo 源码
源码可以看这里:11_device_tree/06_ranges_property。主要是添加一个 platform_get_resource() 函数获取资源的部分:
static int sdrv_probe(struct platform_device *pdev)
{
int ret = 0;
struct resource *p_resources;
PRT("probing platform device & driver!pdev->name=%s\n", pdev->name);
// 获取平台设备的资源
p_resources = platform_get_resource(pdev, IORESOURCE_MEM, 0);
if (p_resources == NULL)
{
// 如果获取资源失败, 打印 value_compatible 的值
PRTE("platform_get_resource is error\n");
ret = -1;
goto err_platform_get_resource;
}
PRT("%s start: 0x%x, end: 0x%x\n",
p_resources->name, p_resources->start, p_resources->end + 1);
return 0;
err_platform_get_resource:
return ret;
}3. 开发板测试
我们编译后,加载对应的模块,出现下面的报错:

可以看到使用 platform_get_resource() 函数获取 reg 资源的函数失败了,后面我们就来分析一下为什么。
三、获取资源失败分析
1. 返回错误值分析
platform_get_resource() 定义如下:
struct resource *platform_get_resource(struct platform_device *dev,
unsigned int type, unsigned int num)
{
int i;
for (i = 0; i < dev->num_resources; i++) {
struct resource *r = &dev->resource[i];
if (type == resource_type(r) && num-- == 0)
return r;
}
return NULL;
}该函数返回 NULL 符合上面报错的时候的情况, 这里返回 NULL 的情况有两种可能性, 一种是没进入上面的 for 循环直接返回了 NULL, 另外一种是进入了 for 循环, 但是类型匹配不正确, 跳出 for 循环之后再返回 NULL。
这里的类型一定是匹配的, 所以我们就来寻找为什么没有进入 for 循环,这里只有一种可能, 也就是 dev-> num_resources 为 0。所以现在的目标来到了寻找 dev-> num_resources 是在哪里进行的赋值, 前面已经知道过了由设备树转换为 platform 的过程, 而且在系统启动后, 在对应目录下也有了相应的节点:
ls /sys/bus/platform/devices/
证明转换是没问题的, 所以继续寻找中间转换过程中有关资源数量的相关函数, 定位到 of_platform_device_create_pdata():
2. of_platform_device_create_pdata()
static struct platform_device *of_platform_device_create_pdata(
struct device_node *np,
const char *bus_id,
void *platform_data,
struct device *parent)
{
struct platform_device *dev;
/* 检查设备节点是否可用或已填充 */
if (!of_device_is_available(np) ||
of_node_test_and_set_flag(np, OF_POPULATED))
return NULL;
/* 分配平台设备结构体 */
dev = of_device_alloc(np, bus_id, parent);
if (!dev)
goto err_clear_flag;
/* 设置平台设备的一些属性 */
dev->dev.coherent_dma_mask = DMA_BIT_MASK(32);
if (!dev->dev.dma_mask)
dev->dev.dma_mask = &dev->dev.coherent_dma_mask;
dev->dev.bus = &platform_bus_type;
dev->dev.platform_data = platform_data;
of_msi_configure(&dev->dev, dev->dev.of_node);
/* 将平台设备添加到设备模型中 */
if (of_device_add(dev) != 0) {
platform_device_put(dev);
goto err_clear_flag;
}
return dev;
err_clear_flag:
/* 清除设备节点的已填充标志 */
of_node_clear_flag(np, OF_POPULATED);
return NULL;
}第 13 行:函数调用 of_device_alloc() 分配一个平台设备结构体, 并将设备节点指针、 设备标识符和父设备指针传递给它, 正是该函数决定的 resource.num。
3. of_device_alloc()
of_device_alloc() 函数定义如下:
struct platform_device *of_device_alloc(struct device_node *np,
const char *bus_id,
struct device *parent)
{
struct platform_device *dev;
int rc, i, num_reg = 0, num_irq;
struct resource *res, temp_res;
dev = platform_device_alloc("", PLATFORM_DEVID_NONE);
if (!dev)
return NULL;
/* count the io and irq resources */
while (of_address_to_resource(np, num_reg, &temp_res) == 0)
num_reg++;
num_irq = of_irq_count(np);
/* Populate the resource table */
if (num_irq || num_reg) {
res = kcalloc(num_irq + num_reg, sizeof(*res), GFP_KERNEL);
if (!res) {
platform_device_put(dev);
return NULL;
}
dev->num_resources = num_reg + num_irq;
dev->resource = res;
for (i = 0; i < num_reg; i++, res++) {
rc = of_address_to_resource(np, i, res);
WARN_ON(rc);
}
if (of_irq_to_resource_table(np, res, num_irq) != num_irq)
pr_debug("not all legacy IRQ resources mapped for %pOFn\n",
np);
}
dev->dev.of_node = of_node_get(np);
dev->dev.fwnode = &np->fwnode;
dev->dev.parent = parent ? : &platform_bus;
if (bus_id)
dev_set_name(&dev->dev, "%s", bus_id);
else
of_device_make_bus_id(&dev->dev);
return dev;
}在第 28 行出现了 for 循环的 dev-> num_resources = num_reg + num_irq; reg 的 number 和 irq 的 number, 由于在设备树中并没有添加中断相关的属性 num_irq 为 0, 那这里的 num_reg 是哪里确定的呢。我们向上找到 14、 15 行, 具体内容如下所示:
/* count the io and irq resources */
while (of_address_to_resource(np, num_reg, &temp_res) == 0)
num_reg++;
num_irq = of_irq_count(np);所以这里我们需要去看一下 of_address_to_resource() 函数:
int of_address_to_resource(struct device_node *dev, int index,
struct resource *r)
{
const __be32 *addrp;
u64 size;
unsigned int flags;
const char *name = NULL;
addrp = of_get_address(dev, index, &size, &flags);
if (addrp == NULL)
return -EINVAL;
/* Get optional "reg-names" property to add a name to a resource */
of_property_read_string_index(dev, "reg-names", index, &name);
return __of_address_to_resource(dev, addrp, size, flags, name, r);
}第 9 行:获取 reg 属性的地址、 大小和类型, 在设备树中 reg 属性已经存在了, 所以这里会正确返回。
第 14 行: 读取 reg-names 属性, 由于设备树中没有定义这个属性, 所以该函数不会有影响.
第 16 行:最后具有决定性作用的函数就是返回的 __of_address_to_resource() 函数 :
static int __of_address_to_resource(struct device_node *dev,
const __be32 *addrp, u64 size, unsigned int flags,
const char *name, struct resource *r)
{
u64 taddr;
if (flags & IORESOURCE_MEM)
taddr = of_translate_address(dev, addrp);
else if (flags & IORESOURCE_IO)
taddr = of_translate_ioport(dev, addrp, size);
else
return -EINVAL;
if (taddr == OF_BAD_ADDR)
return -EINVAL;
memset(r, 0, sizeof(struct resource));
r->start = taddr;
r->end = taddr + size - 1;
r->flags = flags;
r->name = name ? name : dev->full_name;
return 0;
}reg 属性的 flags 为 IORESOURCE_MEM, 所以又会执行第 7 行的 of_translate_address() 函数,看一下这个函数:
u64 of_translate_address(struct device_node *dev, const __be32 *in_addr)
{
struct device_node *host;
u64 ret;
ret = __of_translate_address(dev, in_addr, "ranges", &host);
if (host) {
of_node_put(host);
return OF_BAD_ADDR;
}
return ret;
}该函数的重点在第 6 行, 上述函数实际上是 __of_translate_address() 函数的封装, 其中传入的第三个参数“ranges”是我们要关注的重点.
4. __of_translate_address()
继续跳转到__of_translate_address() 函数的定义:
static u64 __of_translate_address(struct device_node *dev,
const __be32 *in_addr, const char *rprop,
struct device_node **host)
{
//函数有点长,直接看在线的吧。
} /* Get parent & match bus type */
parent = of_get_parent(dev);
if (parent == NULL)
goto bail;
bus = of_match_bus(parent);第 593 - 599 行获取 address-cell 和 size-cells
c/* Count address cells & copy address locally */ bus->count_cells(dev, &na, &ns); if (!OF_CHECK_COUNTS(na, ns)) { pr_debug("Bad cell count for %pOF\n", dev); goto bail; } memcpy(addr, in_addr, na * 4);第 646 行
/* Translate */
for (;;) {
//......
/* Apply bus translation */
if (of_translate_one(dev, bus, pbus, addr, na, ns, pna, rprop))
break;
/* Complete the move up one level */
na = pna;
ns = pns;
bus = pbus;
of_dump_addr("one level translation:", addr, na);
}使用 of_translate_one() 函数进行转换, 其中 rprop 参数表示要转换的资源属性, 该参数的值为传入的“ranges”
5. of_translate_one()
of_translate_one() 函数定义如下:
static int of_translate_one(struct device_node *parent, struct of_bus *bus,
struct of_bus *pbus, __be32 *addr,
int na, int ns, int pna, const char *rprop)
{
const __be32 *ranges;
unsigned int rlen;
int rone;
u64 offset = OF_BAD_ADDR;
/*
* Normally, an absence of a "ranges" property means we are
* crossing a non-translatable boundary, and thus the addresses
* below the current cannot be converted to CPU physical ones.
* Unfortunately, while this is very clear in the spec, it's not
* what Apple understood, and they do have things like /uni-n or
* /ht nodes with no "ranges" property and a lot of perfectly
* useable mapped devices below them. Thus we treat the absence of
* "ranges" as equivalent to an empty "ranges" property which means
* a 1:1 translation at that level. It's up to the caller not to try
* to translate addresses that aren't supposed to be translated in
* the first place. --BenH.
*
* As far as we know, this damage only exists on Apple machines, so
* This code is only enabled on powerpc. --gcl
*/
ranges = of_get_property(parent, rprop, &rlen);
if (ranges == NULL && !of_empty_ranges_quirk(parent)) {
pr_debug("no ranges; cannot translate\n");
return 1;
}
if (ranges == NULL || rlen == 0) {
offset = of_read_number(addr, na);
memset(addr, 0, pna * 4);
pr_debug("empty ranges; 1:1 translation\n");
goto finish;
}
// ......
}在该函数的第 26 行使用 of_get_property() 函数获取“ranges”属性, 但由于在我们添加的设备树节点中并没有该属性, 所以这里的 ranges 值就为 NULL, 第 27 行的条件判断成立, 也就会返回 1。
6. 一步一步返回
of_get_property() 函数返回 NULL,所以 of_translate_one() 返回 1
u64 result = OF_BAD_ADDR;
//......
for (;;) {
/* Apply bus translation */
if (of_translate_one(dev, bus, pbus, addr, na, ns, pna, rprop))
break;
}这里直接跳出循环,最终会返回 OF_BAD_ADDR 。再上一级的 of_address_to_resource() 返回值也是 OF_ BAD _ADDR, 继续向上查找 __of_address_to_resource() 函数会返回 EINVAL, of_address_to_resource() 返回 EINVAL, 所以 num_reg 为 0;

7. 总结
到这里关于为什么 platform_get_resource() 函数获取资源失败的问题就找到了, 只是因为在设备树中并没有这个名为 ranges 这个属性, 所以只需要对设备树进行 ranges 属性的添加即可。
四、问题解决
1. 设备树
修改设备树:
/dts-v1/;
/ {
model = "Freescale i.MX6 UlltraLite ALPHA EMMC Board";
compatible = "fsl,imx6ull-alpha-emmc", "fsl,imx6ull";
alpha {
#address-cells = <1>;
#size-cells = <1>;
ranges;
compatible = "simple-bus";
sdev_led {
#address-cells = <1>;
#size-cells = <1>;
compatible = "led";
status = "okay";
reg = < 0X020C406C 0X04 /* CCM_CCGR1_BASE */
0X020E0068 0X04 /* SW_MUX_GPIO1_IO03_BASE */
0X020E02F4 0X04 /* SW_PAD_GPIO1_IO03_BASE */
0X0209C000 0X04 /* GPIO1_DR_BASE */
0X0209C004 0X04 >; /* GPIO1_GDIR_BASE */
};
};
};2. 开发板测试
我们更新开发板的设备树文件,然后重新加载驱动,就会发现,资源获取成功了。