Skip to content

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() 函数定义如下:

c
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. 设备树

c
/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() 函数获取资源的部分:

c
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. 开发板测试

我们编译后,加载对应的模块,出现下面的报错:

image-20250316104023581

可以看到使用 platform_get_resource() 函数获取 reg 资源的函数失败了,后面我们就来分析一下为什么。

三、获取资源失败分析

1. 返回错误值分析

platform_get_resource() 定义如下:

c
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 的过程, 而且在系统启动后, 在对应目录下也有了相应的节点:

shell
ls /sys/bus/platform/devices/
image-20250316104118501

证明转换是没问题的, 所以继续寻找中间转换过程中有关资源数量的相关函数, 定位到 of_platform_device_create_pdata()

2. of_platform_device_create_pdata()

c
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() 函数定义如下:

c
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 行, 具体内容如下所示:

c
	/* 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() 函数:

c
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() 函数 :

c
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() 函数,看一下这个函数:

c
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() 函数的定义:

c
static u64 __of_translate_address(struct device_node *dev,
				  const __be32 *in_addr, const char *rprop,
				  struct device_node **host)
{
	//函数有点长,直接看在线的吧。
}
  • 588 - 591 行:获取父节点和匹配的总线类型
c
	/* 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

c
	/* 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() 函数定义如下:

c
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

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

image-20260120201128371

7. 总结

到这里关于为什么 platform_get_resource() 函数获取资源失败的问题就找到了, 只是因为在设备树中并没有这个名为 ranges 这个属性, 所以只需要对设备树进行 ranges 属性的添加即可。

四、问题解决

1. 设备树

修改设备树:

c
/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. 开发板测试

我们更新开发板的设备树文件,然后重新加载驱动,就会发现,资源获取成功了。