在arch/arm64/mm/dma-mapping.c 中定义了三种dma的ops,分别是:dummy_dma_ops/swiotlb_dma_ops/iommu_dma_ops 。那kernel是如何选择iommu的呢?
如果acpi表中定义了IORT 子表的话,flow如下:
acpi_iort_init->iort_init_platform_devices
static void __init iort_init_platform_devices(void)
{
if ((iort_node->type == ACPI_IORT_NODE_SMMU) ||
(iort_node->type == ACPI_IORT_NODE_SMMU_V3)) {
fwnode = acpi_alloc_fwnode_static();
if (!fwnode)
return;
iort_set_fwnode(iort_node, fwnode);
ret = iort_add_smmu_platform_device(iort_node);
if (ret) {
iort_delete_fwnode(iort_node);
acpi_free_fwnode_static(fwnode);
return;
}
}
}
从iort_init_platform_devices 中可以知道当前kernel同时support SMMU 和SMMUV3
iort_add_smmu_platform_device->acpi_dma_configure->arch_setup_dma_ops->acpi_dma_configure
在acpi_dma_configure 中会调用arch_setup_dma_ops 这里需要注意attr == DEV_DMA_COHERENT
arch_setup_dma_ops(dev, 0, dev->coherent_dma_mask + 1, iommu,
attr == DEV_DMA_COHERENT);
在arch_setup_dma_ops 函数中dev->archdata.dma_ops == NULL
void arch_setup_dma_ops(struct device *dev, u64 dma_base, u64 size,
const struct iommu_ops *iommu, bool coherent)
{
if (!dev->archdata.dma_ops)
dev->archdata.dma_ops = &swiotlb_dma_ops;
dev->archdata.dma_coherent = coherent;
__iommu_setup_dma_ops(dev, dma_base, size, iommu);
}
我们这边暂时给dev->archdata.dma_ops = &swiotlb_dma_ops 以防后面iommu赋值失败。这里继续调用__iommu_setup_dma_ops
static void __iommu_setup_dma_ops(struct device *dev, u64 dma_base, u64 size,
const struct iommu_ops *ops)
{
struct iommu_group *group;
if (!ops)
return;
/*
* TODO: As a concession to the future, we're ready to handle being
* called both early and late (i.e. after bus_add_device). Once all
* the platform bus code is reworked to call us late and the notifier
* junk above goes away, move the body of do_iommu_attach here.
*/
group = iommu_group_get(dev);
if (group) {
do_iommu_attach(dev, ops, dma_base, size);
iommu_group_put(group);
} else {
queue_iommu_attach(dev, ops, dma_base, size);
}
}
在__iommu_setup_dma_ops 中ops显然部位NULL,且group也不为null,因此调用do_iommu_attach
static bool do_iommu_attach(struct device *dev, const struct iommu_ops *ops,
u64 dma_base, u64 size)
{
struct iommu_domain *domain = iommu_get_domain_for_dev(dev);
/*
* If the IOMMU driver has the DMA domain support that we require,
* then the IOMMU core will have already configured a group for this
* device, and allocated the default domain for that group.
*/
if (!domain || iommu_dma_init_domain(domain, dma_base, size, dev)) {
pr_warn("Failed to set up IOMMU for device %s; retaining platform DMA ops\n",
dev_name(dev));
return false;
}
dev->archdata.dma_ops = &iommu_dma_ops;
return true;
}
哈哈哈,在do_iommu_attach 中终于赋值了dev->archdata.dma_ops = &iommu_dma_ops;