文章目录
- 1. 前言
- 2. 背景
- 3. PL330 简介
- 4. PL330 驱动加载流程
- 4.1 PL330 设备注册流程
- 4.2 PL330 驱动加载流程
- 5. 小结
- 6. 参考资料
1. 前言
限于作者能力水平,本文可能存在谬误,因此而给读者带来的损失,作者不做任何承诺。
2. 背景
本文基于 ARMv8 架构
、Linux 5.10
进行分析,DMA 控制器(DMAC: DMA Controller)
为 ARM
的 PL330。
3. PL330 简介
PL330
是 ARM
设计的 DMA 控制器(DMAC: DMA Controller)
,支持 Scatter/Gather 和 LLI(Linked-List Item) 特性。
上图是 PL330
的接口图,其中:
. AXI master 接口,用于 DMA 传输。
. APB slave 接口,用于配置/控制 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 驱动的加载过程
。首先看一下 PL330
的 DTS
配置:
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 PL330
的 DTS
配置:
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);elseof_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*//* 读取 {periph_id,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);...
}
读取到的 PL330
的 periphid
为 0x00241330
,cid
为 0xb105f00d
。
4.2 PL330 驱动加载流程
/* drivers/dma/pl330.c */static const struct amba_id pl330_ids[] = {{.id = 0x00041330,.mask = 0x000fffff,},{ 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().../* 驱动匹配 */ret = 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()
。
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