Linux AMBA 驱动:DMA 控制器 PL330 驱动简析

1. 前言

限于作者能力水平,本文可能存在谬误,因此而给读者带来的损失,作者不做任何承诺。

2. 背景

本文基于 ARMv8 架构Linux 5.10 进行分析,DMA 控制器(DMAC: DMA Controller)ARMPL330

3. PL330 简介

PL330ARM 设计的 DMA 控制器(DMAC: DMA Controller),支持 Scatter/GatherLLI(Linked-List Item) 特性。

在这里插入图片描述
上图是 PL330 的接口图,其中:

. AXI master interface,用于 DMA 传输。
. Non-secure/Secure APB slave interface,用于配置/控制 DMAC PL330。
. Peripheral request interface [x:0],外设通过它发起 DMA 传输请求。
. Interrupts[x:0],用于发送中断给 CPU。

PL330 的典型应用框图如下:

在这里插入图片描述

更详细的 PL330 框图如下:

在这里插入图片描述

本文对 PL330 的介绍,就到此为止,更多关于 PL330 的细节,可参考 ARM 官方文档 DDI0424A_dmac_pl330_r0p0_trm.pdf

4. PL330 驱动加载流程

章节 3. 简单的介绍了 PL330 的功能和接口,本节将着重介绍 PL330 驱动的加载过程。首先看一下 PL330DTS 配置:

4.1 PL330 设备注册流程

dmac0: dma-controller@ff2c0000 {
	compatible = "arm,pl330", "arm,primecell";
	reg = <0x0 0xff2c0000 0x0 0x4000>;
	interrupts = <GIC_SPI 0 IRQ_TYPE_LEVEL_HIGH>,
		     <GIC_SPI 1 IRQ_TYPE_LEVEL_HIGH>;
	arm,pl330-periph-burst;
	clocks = <&cru ACLK_DMAC0>;
	clock-names = "apb_pclk";
	#dma-cells = <1>;
};

系统启动过程中,解析 DMAC PL330DTS 配置:

kernel_init()
	kernel_init_freeable()
		...
		do_one_initcall()
			// arch_initcall_sync(of_platform_default_populate_init);
			of_platform_default_populate_init()
				of_platform_default_populate(NULL, NULL, NULL)
					of_platform_populate(root, of_default_bus_match_table, lookup, parent)
						rc = of_platform_bus_create(child, matches, lookup, parent, true)

/* drivers/of/platform.c */

static int of_platform_bus_create(struct device_node *bus,
				  const struct of_device_id *matches, 
				  const struct of_dev_auxdata *lookup, 
				  struct device *parent, bool strict)
{
	...

	if (of_device_is_compatible(bus, "arm,primecell")) {
		/*
		 * Don't return an error here to keep compatibility with older
		 * device tree files.
		 */
		of_amba_device_create(bus, bus_id, platform_data, parent);
		return 0;
	}
 
	...
}

#ifdef CONFIG_ARM_AMBA
static struct amba_device *of_amba_device_create(struct device_node *node,
						 const char *bus_id, 
						 void *platform_data,
						 struct device *parent)
{
	struct amba_device *dev;
	...

	/* 1. 创建 AMBA 设备: DMA PL330 */
	dev = amba_device_alloc(NULL, 0, 0);
	...

	/* 2. AMBA 设备初始化 */
	/* AMBA devices only support a single DMA mask */
	dev->dev.coherent_dma_mask = DMA_BIT_MASK(32);
	dev->dev.dma_mask = &dev->dev.coherent_dma_mask;

	/* setup generic device info */
	dev->dev.of_node = of_node_get(node);
	dev->dev.fwnode = &node->fwnode;
	dev->dev.parent = parent ? : &platform_bus;
	dev->dev.platform_data = platform_data;
	if (bus_id)
		dev_set_name(&dev->dev, "%s", bus_id);
	else
		of_device_make_bus_id(&dev->dev);
	
	...

	/* Decode the IRQs and address ranges */
	for (i = 0; i < AMBA_NR_IRQS; i++)
		dev->irq[i] = irq_of_parse_and_map(node, i);

	...

	/* 3. 注册 AMBA 设备到系统 */
	ret = amba_device_add(dev, &iomem_resource);

	...
}
#else /* CONFIG_ARM_AMBA */
static struct amba_device *of_amba_device_create(struct device_node *node,
						 const char *bus_id,
						 void *platform_data,
						 struct device *parent)
{
	return NULL;
}
#endif /* CONFIG_ARM_AMBA */

上面 amba_device_alloc() 创建设备过程中,绑定设备总线类型为 amba_bustype,是 PL330 驱动匹配加载的重要一环:

amba_device_alloc()
	struct amba_device *dev;

	dev = kzalloc(sizeof(*dev), GFP_KERNEL);
	if (dev) {
		amba_device_initialize(dev, name);
			...
			dev->dev.bus = &amba_bustype; /* 绑定 设备 的 总线类型 为 amba_bustype */
			...
		...
	}

	return dev;

amba_device_add() 在注册设备到系统前,扫描读取 AMBA(Advanced Microcontroller Bus Architecture) 设备的 {periphid, cid},是 PL330 驱动匹配加载的另一重要环节:

amba_device_add()
	amba_device_try_add()

static int amba_device_try_add(struct amba_device *dev, struct resource *parent)
{
	...
	/* 扫描读取 AMBA 设备的 {periph_id,cid} */
	ret = amba_get_enable_pclk(dev);
	if (ret == 0) {
		u32 pid, cid;
		...

		/*
		 * Read pid and cid based on size of resource
		 * they are located at end of region
		 */
		/* 读取 {periphid,cid} */
		for (pid = 0, i = 0; i < 4; i++)
			pid |= (readl(tmp + size - 0x20 + 4 * i) & 255) << (i * 8);
		for (cid = 0, i = 0; i < 4; i++)
			cid |= (readl(tmp + size - 0x10 + 4 * i) & 255) << (i * 8);
		
		...
		
		amba_put_disable_pclk(dev);

		/* 记录 {periphid, cid} 到设备对象 */
		if (cid == AMBA_CID || cid == CORESIGHT_CID) {
			dev->periphid = pid;
			dev->cid = cid;
		}

		if (!dev->periphid)
			ret = -ENODEV;
	}

	...
 skip_probe:
 	/* 注册 AMBA 设备到系统 */
 	ret = device_add(&dev->dev);
 	...
}

读取到的 PL330periphid0x00241330cid0xb105f00d

4.2 PL330 驱动加载流程

/* drivers/dma/pl330.c */

static const struct amba_id pl330_ids[] = {
	{
		.id = 0x00041330, /* periphid */
		.mask = 0x000fffff, /* periphid 掩码 */
	},
	{ 0, 0 },
};

MODULE_DEVICE_TABLE(amba, pl330_ids);

static struct amba_driver pl330_driver = {
	.drv = {
		.owner = THIS_MODULE,
		.name = "dma-pl330",
		.pm = &pl330_pm,
	},
	.id_table = pl330_ids,
	.probe = pl330_probe,
	.remove = pl330_remove,
};

module_amba_driver(pl330_driver);
/* include/linux/amba/bus.h */

#define module_amba_driver(__amba_drv) \
	module_driver(__amba_drv, amba_driver_register, amba_driver_unregister)
/* drivers/amba/bus.c */

int amba_driver_register(struct amba_driver *drv)
{
	if (!drv->probe)
		return -EINVAL;
	
	drv->drv.bus = &amba_bustype; /* 绑定 驱动 的 总线类型 为 amba_bustype */
	drv->drv.probe = amba_probe;
	drv->drv.remove = amba_remove;
	drv->drv.shutdown = amba_shutdown;

	return driver_register(&drv->drv);
}
driver_register()
	bus_add_driver()
		driver_attach()
			__driver_attach()
				...
				/* 匹配 驱动 和 设备 */
				driver_match_device(drv, dev);
					amba_match()
						amba_lookup()
				...
				/* 绑定 驱动 到 设备 */
				device_driver_attach(drv, dev);
					driver_probe_device(drv, dev);
						really_probe(dev, drv);
							drv->probe(dev); /* pl330_probe() */
				...

static const struct amba_id *
amba_lookup(const struct amba_id *table, struct amba_device *dev)
{
	while (table->mask) {
		if (((dev->periphid & table->mask) == table->id) &&
			((dev->cid != CORESIGHT_CID) ||
			(amba_cs_uci_id_match(table, dev))))
			return table;
		table++;
	}
	return NULL;
}

从上面的分析中,可以看到 AMBA 设备 (PL330) 的匹配是通过 {periphid, cid} 进行匹配的。前面读到的 正好匹配到 pl330_ids[0],驱动加载流程进入了 pl330_probe()

/* drivers/dma/pl330.c */

pl330_probe()
	...
	/* 注册 DMA 中断处理接口 */
	for (i = 0; i < AMBA_NR_IRQS; i++) {
		irq = adev->irq[i];
		if (irq) {
			ret = devm_request_irq(&adev->dev, irq,
						pl330_irq_handler, 0, 
						dev_name(&adev->dev), pl330);
			...
		} else {
			break;
		}
	}
	...
	/* 注册 DMA 控制器设备到 DMA 子系统 */
	ret = dma_async_device_register(pd);
	...

5. 小结

虽然本文分析的 DMAC(DMA Controller) 设备的设备驱动注册加载,但其主干流程也适用于其它 AMBA(Advanced Microcontroller Bus Architecture) 设备驱动。

6. 参考资料

[1] DDI0424A_dmac_pl330_r0p0_trm.pdf
[2] 102202_0100_01_Introduction_to_AMBA_AXI.pdf

  • 4
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值