<kernel v5.0>
DMA -5- (DMA-IOMMU)
디바이스가 DMA(Direct Memory Access)로 IOMMU를 통과하여 메모리에 접근하기 위해 커널에서 제공하는 두 가지의 방법이 있다.
- IOMMU API
- drivers/iommu/iommu.c 파일에 IOMMU core API들이 제공된다.
- IOMMU API들 대부분이 iommu prefix로 이루어진 함수이다.
- drivers/iommu 디렉토리에 iommu 칩 드라이버들이 구현되어 있다.
- drivers/iommu/arm-smmu.c
- drivers/iommu/arm-smmu-v3.c
- drivers/iommu/intel-iommu.c
- drivers/iommu/amd_iommu.c
- …
- DMA-IOMMU API
- drivers/iommu/dma-iommu.c 파일에 DMA-IOMMU core API들이 제공된다.
- DMA-IOMMU API들 대부분이 iommu_dma prefix로 이루어진 함수이다.
- drivers/iommu 디렉토리에 iommu 칩 드라이버들이 구현되어 있다.
- arch/arm/mm/dma-mapping.c (2가지 구현)
- arch/arm64/mm/dma-mapping.c
- arch/x86/kernel/amd-gart_64.c
- …
다음 그림은 IOMMU에 연결된 사용자 디바이스가 IOMMU 매핑을 위해 IOMMU API 또는 DMA-IOMMU API 를 통해 접근할 수 있음을 보여준다.
DMA-IOMMU
IOMMU를 통해 시스템 메모리의 물리 주소에 접근할 수 있는 디바이스가 dma coherent 메모리를 할당하는 방법은 각 시스템의 IOMMU 장치마다 다른 구현을 가진다. 이 글에서는 DMA-IOMMU에 대한 core API를 위주로 분석하고, 조금씩 이에 대응하는 ARM64 아키텍처에 코드를 추가로 분석한다.
- DMA-IOMMU API가 아닌 IOMMU API와 관련된 문서는 다음을 참고한다.
ARM, ARM64 – CoreLink MMU-500 System
다음 그림은 ARM, ARM64에서 사용하는 SMMU를 보여준다.
다음 그림은 SMMU가 관리하는 두 개의 공간 모습을 보여준다.
다음 그림은 SMMU를 사용한 매핑들을 보여준다.
초기화
ARM64 IOMMU-DMA 초기화
arch/arm64/mm/dma-mapping.c
static int __init __iommu_dma_init(void)
{
return iommu_dma_init();
}
arch_initcall(__iommu_dma_init);
iommu dma를 초기화한다.
drivers/iommu/dma-iommu.c
int iommu_dma_init(void)
{
return iova_cache_get();
}
iommu dma를 위해 iova 캐시를 생성한다.
iova_cache_get()
drivers/iommu/iova.c
int iova_cache_get(void)
{
mutex_lock(&iova_cache_mutex);
if (!iova_cache_users) {
iova_cache = kmem_cache_create(
"iommu_iova", sizeof(struct iova), 0,
SLAB_HWCACHE_ALIGN, NULL);
if (!iova_cache) {
mutex_unlock(&iova_cache_mutex);
printk(KERN_ERR "Couldn't create iova cache\n");
return -ENOMEM;
}
}
iova_cache_users++;
mutex_unlock(&iova_cache_mutex);
return 0;
}
EXPORT_SYMBOL_GPL(iova_cache_get);
iova 캐시를 생성한다.
- 코드 라인 4~13에서 처음 iova 캐시를 사용할 때 iova 구조체를 공급할 수 있도록 kmem 캐시를 생성한다.
- 코드 라인 15에서 한 번만 kmem 캐시가 생성되도록 카운트를 증가시킨다.
디바이스 트리를 통한 DMA 준비
of_dma_configure()
drivers/of/device.c
/**
* of_dma_configure - Setup DMA configuration
* @dev: Device to apply DMA configuration
* @np: Pointer to OF node having DMA configuration
* @force_dma: Whether device is to be set up by of_dma_configure() even if
* DMA capability is not explicitly described by firmware.
*
* Try to get devices's DMA configuration from DT and update it
* accordingly.
*
* If platform code needs to use its own special DMA configuration, it
* can use a platform bus notifier and handle BUS_NOTIFY_ADD_DEVICE events
* to fix up DMA configuration.
*/
int of_dma_configure(struct device *dev, struct device_node *np, bool force_dma)
{
u64 dma_addr, paddr, size = 0;
int ret;
bool coherent;
unsigned long offset;
const struct iommu_ops *iommu;
u64 mask;
ret = of_dma_get_range(np, &dma_addr, &paddr, &size);
if (ret < 0) {
/*
* For legacy reasons, we have to assume some devices need
* DMA configuration regardless of whether "dma-ranges" is
* correctly specified or not.
*/
if (!force_dma)
return ret == -ENODEV ? 0 : ret;
dma_addr = offset = 0;
} else {
offset = PFN_DOWN(paddr - dma_addr);
/*
* Add a work around to treat the size as mask + 1 in case
* it is defined in DT as a mask.
*/
if (size & 1) {
dev_warn(dev, "Invalid size 0x%llx for dma-range\n",
size);
size = size + 1;
}
if (!size) {
dev_err(dev, "Adjusted size 0x%llx invalid\n", size);
return -EINVAL;
}
dev_dbg(dev, "dma_pfn_offset(%#08lx)\n", offset);
}
- 코드 라인 10~20에서 “dma-ranges” 속성을 읽어 dma 주소와, 물리 주소, 사이즈를 알아온다. 만일 속성이 없이 사용된 legacy를 위해 1:1 direct 매핑으로 판단하여 dma 주소와 offset 을 모두 0으로 한다.
- 코드 라인 21~39에서 물리 주소에서 dma 주소를 뺀 값을 offset으로 한다. 워크어라운드 이슈로 size는 짝수로 round up한다.
/*
* If @dev is expected to be DMA-capable then the bus code that created
* it should have initialised its dma_mask pointer by this point. For
* now, we'll continue the legacy behaviour of coercing it to the
* coherent mask if not, but we'll no longer do so quietly.
*/
if (!dev->dma_mask) {
dev_warn(dev, "DMA mask not set\n");
dev->dma_mask = &dev->coherent_dma_mask;
}
if (!size && dev->coherent_dma_mask)
size = max(dev->coherent_dma_mask, dev->coherent_dma_mask + 1);
else if (!size)
size = 1ULL << 32;
dev->dma_pfn_offset = offset;
/*
* Limit coherent and dma mask based on size and default mask
* set by the driver.
*/
mask = DMA_BIT_MASK(ilog2(dma_addr + size - 1) + 1);
dev->coherent_dma_mask &= mask;
*dev->dma_mask &= mask;
/* ...but only set bus mask if we found valid dma-ranges earlier */
if (!ret)
dev->bus_dma_mask = mask;
coherent = of_dma_is_coherent(np);
dev_dbg(dev, "device is%sdma coherent\n",
coherent ? " " : " not ");
iommu = of_iommu_configure(dev, np);
if (IS_ERR(iommu) && PTR_ERR(iommu) == -EPROBE_DEFER)
return -EPROBE_DEFER;
dev_dbg(dev, "device is%sbehind an iommu\n",
iommu ? " " : " not ");
arch_setup_dma_ops(dev, dma_addr, size, iommu, coherent);
return 0;
}
EXPORT_SYMBOL_GPL(of_dma_configure);
디바이스 트리로부터 dma 매핑 값을 읽어 디바이스의 dma 매핑 지원을 위해 설정한다. 1:1 direct 매핑이 아닌 경우 iommu 설정도 수행한다.
- 코드 라인 7~10에서 디바이스에 dma 주소제한이 설정되어 있지 않은 경우 경고 메시지를 출력한 후 디바이스의 coherent 주소 제한 값을 사용한다.
- 코드 라인 12~15에서 size가 0(“dma-ranges”를 사용하지 않은 legacy)일 때 다음과 같이 size를 설정한다.
- coherent 주소 제한이 설정된 경우 size 값은 coherent 주소 제한 값+1로 한다. 주소가 경계를 넘어가지 않게 제한한다.
- 예) coherent_dma_mask=0xffff_ffff 인 경우
- 32비트 시스템에서 size=0xffff_ffff
- 64비트 시스템에서 size=0x1_0000_0000
- coherent 주소 제한이 설정되지 않은 경우 size 값은 0x1_0000_0000 (4G)로 한다.
- 코드 라인 17에서 디바이스의 dma_pfn_offset 값에 offset 값을 대입한다. (paddr – dma_addr)
- 코드 라인 23~25에서 dma 주소 + size 를 적용한 마스크 값으로 디바이스의 coherent 및 dma 주소 제한을 한다.
- 코드 라인 27~28에서 “dma-ranges” 속성이 발견된 경우에 한해서 디바이스의 버스 주소 제한값에 dma 주소 + size를 적용한 마스크 값을 사용한다.
- 코드 라인 30~32에서 “dma-coherent” 속성이 있는지 찾아 coherent 디바이스인지 여부를 디버그 메시지로 출력한다.
- 코드 라인 34~36에서 디바이스를 위해 iommu를 설정한다.
- 코드 라인 38~39에서 iommu 아래에 디바이스가 동작하는지 여부를 디버그 메시지로 출력한다.
- 코드 라인 41에서 디바이스에 대한 dma opeation을 셋업한다.
of_dma_get_range()
drivers/of/address.c
/**
* of_dma_get_range - Get DMA range info
* @np: device node to get DMA range info
* @dma_addr: pointer to store initial DMA address of DMA range
* @paddr: pointer to store initial CPU address of DMA range
* @size: pointer to store size of DMA range
*
* Look in bottom up direction for the first "dma-ranges" property
* and parse it.
* dma-ranges format:
* DMA addr (dma_addr) : naddr cells
* CPU addr (phys_addr_t) : pna cells
* size : nsize cells
*
* It returns -ENODEV if "dma-ranges" property was not found
* for this device in DT.
*/
int of_dma_get_range(struct device_node *np, u64 *dma_addr, u64 *paddr, u64 *size)
{
struct device_node *node = of_node_get(np);
const __be32 *ranges = NULL;
int len, naddr, nsize, pna;
int ret = 0;
u64 dmaaddr;
if (!node)
return -EINVAL;
while (1) {
naddr = of_n_addr_cells(node);
nsize = of_n_size_cells(node);
node = of_get_next_parent(node);
if (!node)
break;
ranges = of_get_property(node, "dma-ranges", &len);
/* Ignore empty ranges, they imply no translation required */
if (ranges && len > 0)
break;
/*
* At least empty ranges has to be defined for parent node if
* DMA is supported
*/
if (!ranges)
break;
}
if (!ranges) {
pr_debug("no dma-ranges found for node(%pOF)\n", np);
ret = -ENODEV;
goto out;
}
len /= sizeof(u32);
pna = of_n_addr_cells(node);
/* dma-ranges format:
* DMA addr : naddr cells
* CPU addr : pna cells
* size : nsize cells
*/
dmaaddr = of_read_number(ranges, naddr);
*paddr = of_translate_dma_address(np, ranges);
if (*paddr == OF_BAD_ADDR) {
pr_err("translation of DMA address(%pad) to CPU address failed node(%pOF)\n",
dma_addr, np);
ret = -EINVAL;
goto out;
}
*dma_addr = dmaaddr;
*size = of_read_number(ranges + naddr + pna, nsize);
pr_debug("dma_addr(%llx) cpu_addr(%llx) size(%llx)\n",
*dma_addr, *paddr, *size);
out:
of_node_put(node);
return ret;
}
디바이스 트리의 @np 노드에서 “dma-ranges” 속성을 읽어 dma 주소와, 물리 주소, 사이즈를 알아온다.
- 코드 라인 13에서 상위 노드에서 “#address-cells” 값을 알아온다.
- “dma-ranges” 속성의 주소에 32비트 주소 값을 사용하려면 1, 64비트 주소 값을 사용하려면 2를 사용한다.
- 코드 라인 14에서 상위 노드에서 “#size-cells” 값을 알아온다.
- 코드 라인 15~17에서 부모 노드가 없으면 루프를 빠져나간다.
- 코드 라인 19에서 “dma-ranges” 속성을 읽어온다. len에는 읽은 속성의 길이가 담긴다.
- 코드 라인 22~23에서 정상적으로 읽어온 경우 루프를 빠져나간다.
- 코드 라인 29~30에서 정상적으로 읽어오지 못한 경우 루프를 빠져나간다.
- 코드 라인 33~37에서 “dma-ranges” 속성을 못찾은 경우 디버그 메시지를 출력하고 -ENODEV 에러로 함수를 빠져나간다.
- 코드 라인 49~56에서 dma 주소와 물리 주소를 얻어 @dma_addr 및 @paddr에 대입한다.
- 코드 라인 58에서 size를 얻어 @size에 대입한다.
- 코드 라인 60~61에서 dma range에 대한 디버그 정보를 출력한다.
예) ARM64 juno 보드
DMA 주소 0x0 ~ 0x100_0000_0000 128G를 동일한 cpu 물리주소에 매핑한다. (inbound 매핑)
arch/arm64/boot/dts/arm/juno-base.dtsi
{
dma-ranges = <0 0 0 0 0x100 0>;
...
- <dma_addr:msb> <dma_addr:lsb> <cpu_addr:msb> <cpu_addr:lsb> <len:msb> <len:lsb>
예) ARM rpi2 & rpi3 보드
DMA 주소 0xc000_0000 ~ 0xff00_0000 1008M를 cpu 물리주소 0x0 ~ 0x3f00_0000에 매핑한다. (inbound 매핑)
boot/dts/bcm2836.dtsi & bcm2837.dtsi
soc {
ranges = <0x7e000000 0x3f000000 0x1000000>,
<0x40000000 0x40000000 0x00001000>;
dma-ranges = <0xc0000000 0x00000000 0x3f000000>;
...
- <dma_addr> <cpu_addr> <len>
of_dma_is_coherent()
drivers/of/address.c
/**
* of_dma_is_coherent - Check if device is coherent
* @np: device node
*
* It returns true if "dma-coherent" property was found
* for this device in DT.
*/
bool of_dma_is_coherent(struct device_node *np)
{
struct device_node *node = of_node_get(np);
while (node) {
if (of_property_read_bool(node, "dma-coherent")) {
of_node_put(node);
return true;
}
node = of_get_next_parent(node);
}
of_node_put(node);
return false;
}
EXPORT_SYMBOL_GPL(of_dma_is_coherent);
디바이스 트리의 @np 노드 또는 상위노드에서 “dma-coherent” 속성 발견 여부로 dma coherent 디바이스 여부를 반환한다.
of_iommu_configure()
drivers/iommu/of_iommu.c
const struct iommu_ops *of_iommu_configure(struct device *dev,
struct device_node *master_np)
{
const struct iommu_ops *ops = NULL;
struct iommu_fwspec *fwspec = dev_iommu_fwspec_get(dev);
int err = NO_IOMMU;
if (!master_np)
return NULL;
if (fwspec) {
if (fwspec->ops)
return fwspec->ops;
/* In the deferred case, start again from scratch */
iommu_fwspec_free(dev);
}
/*
* We don't currently walk up the tree looking for a parent IOMMU.
* See the `Notes:' section of
* Documentation/devicetree/bindings/iommu/iommu.txt
*/
if (dev_is_pci(dev)) {
struct of_pci_iommu_alias_info info = {
.dev = dev,
.np = master_np,
};
err = pci_for_each_dma_alias(to_pci_dev(dev),
of_pci_iommu_init, &info);
} else if (dev_is_fsl_mc(dev)) {
err = of_fsl_mc_iommu_init(to_fsl_mc_device(dev), master_np);
} else {
struct of_phandle_args iommu_spec;
int idx = 0;
while (!of_parse_phandle_with_args(master_np, "iommus",
"#iommu-cells",
idx, &iommu_spec)) {
err = of_iommu_xlate(dev, &iommu_spec);
of_node_put(iommu_spec.np);
idx++;
if (err)
break;
}
}
/*
* Two success conditions can be represented by non-negative err here:
* >0 : there is no IOMMU, or one was unavailable for non-fatal reasons
* 0 : we found an IOMMU, and dev->fwspec is initialised appropriately
* <0 : any actual error
*/
if (!err) {
/* The fwspec pointer changed, read it again */
fwspec = dev_iommu_fwspec_get(dev);
ops = fwspec->ops;
}
/*
* If we have reason to believe the IOMMU driver missed the initial
* probe for dev, replay it to get things in order.
*/
if (!err && dev->bus && !device_iommu_mapped(dev))
err = iommu_probe_device(dev);
/* Ignore all other errors apart from EPROBE_DEFER */
if (err == -EPROBE_DEFER) {
ops = ERR_PTR(err);
} else if (err < 0) {
dev_dbg(dev, "Adding to IOMMU failed: %d\n", err);
ops = NULL;
}
return ops;
}
디바이스 트리에서 디바이스가 연결된 iommu를 찾아 fwspec을 전달한 후 해당 iommu 드라이버를 probe 한다. (fwspec은 iommu 드라이버에 전달할 id 등의 값)
- 코드 라인 8~9에서 노드가 지정되지 않은 경우 null을 반환한다.
- 코드 라인 11~17에서 디바이스에 iommu configure가 이미 완료된 경우 디바이스가 사용하고 있는 iommu_ops를 반환한다.
- 코드 라인 24~31에서 pci 디바이스인 경우 “iommu-map” 속성 값을 읽어들여 of_iommu_xlate() 함수를 호출한다.
- 코드 라인 32~33에서 NXP의 QoriQ DPAA2 “fsl-mc” 버스를 사용하는 디바이스인 경우에 “iommu-map” 속성 값을 읽어들여 of_iommu_xlate() 함수를 호출한다.
- 코드 라인 34~47에서 그 외의 일반 iommu인 경우 “iommus” 속성에 담긴 값을 읽고, 해당 iommu가 원하는 셀 수만큼 인자를 읽어들여 of_iommu_xlate() 함수를 호출한다.
- 코드 라인 55~59에서 iommu를 찾은 경우 디바이스에 지정된 iommu ops를 알아온다.
- 코드 라인 64~73에서 디바이스가 아직 iommu에 probe되지 않은 경우 probe를 시도한다. probe 시도 시 나중에 probe 해달라는 -EPROBE_DEFER 에러를 받을 수도 있다.
of_iommu_xlate()
drivers/iommu/of_iommu.c
static int of_iommu_xlate(struct device *dev,
struct of_phandle_args *iommu_spec)
{
const struct iommu_ops *ops;
struct fwnode_handle *fwnode = &iommu_spec->np->fwnode;
int err;
ops = iommu_ops_from_fwnode(fwnode);
if ((ops && !ops->of_xlate) ||
!of_device_is_available(iommu_spec->np))
return NO_IOMMU;
err = iommu_fwspec_init(dev, &iommu_spec->np->fwnode, ops);
if (err)
return err;
/*
* The otherwise-empty fwspec handily serves to indicate the specific
* IOMMU device we're waiting for, which will be useful if we ever get
* a proper probe-ordering dependency mechanism in future.
*/
if (!ops)
return driver_deferred_probe_check_state(dev);
return ops->of_xlate(dev, iommu_spec);
}
요청한 @iommu_spec phandle 인자를 iommu 드라이버의 (*of_xlate) 후크 함수를 통해 전달한다.
smmu에 연결된 디바이스 예)
smmu에 연결된 sdhci 디바이스가 0x6002, 0x0000 인자를 전달한다. (두 개 값을 조합하여 0x6002_0000라는 id를 지정한다.)
sdio0: sdhci@3f1000 {
compatible = "brcm,sdhci-iproc";
reg = <0x003f1000 0x100>;
interrupts = <GIC_SPI 204 IRQ_TYPE_LEVEL_HIGH>;
bus-width = <8>;
clocks = <&sdio0_clk>;
iommus = <&smmu 0x6002 0x0000>;
status = "disabled";
};
smmu 디바이스 예)
#iommu-cells 속성 값과 같이 2개의 인자를 전달받는 것을 알 수 있다.
smmu: mmu@3000000 {
compatible = "arm,mmu-500";
reg = <0x03000000 0x80000>;
...
#iommu-cells = <2>;
}
iommu_fwspec_init()
drivers/iommu/iommu.c
int iommu_fwspec_init(struct device *dev, struct fwnode_handle *iommu_fwnode,
const struct iommu_ops *ops)
{
struct iommu_fwspec *fwspec = dev_iommu_fwspec_get(dev);
if (fwspec)
return ops == fwspec->ops ? 0 : -EINVAL;
fwspec = kzalloc(sizeof(*fwspec), GFP_KERNEL);
if (!fwspec)
return -ENOMEM;
of_node_get(to_of_node(iommu_fwnode));
fwspec->iommu_fwnode = iommu_fwnode;
fwspec->ops = ops;
dev_iommu_fwspec_set(dev, fwspec);
return 0;
}
EXPORT_SYMBOL_GPL(iommu_fwspec_init);
iommu용 fwspec이 처음 초기화되는 경우 fwspec을 할당한 후 @iommu_fwnode 및 @ops를 설정한다. 성공 시 0을 반환한다.
arm_smmu_of_xlate() – ARM, ARM64
drivers/iommu/arm-smmu.c
static int arm_smmu_of_xlate(struct device *dev, struct of_phandle_args *args)
{
u32 mask, fwid = 0;
if (args->args_count > 0)
fwid |= (u16)args->args[0];
if (args->args_count > 1)
fwid |= (u16)args->args[1] << SMR_MASK_SHIFT;
else if (!of_property_read_u32(args->np, "stream-match-mask", &mask))
fwid |= (u16)mask << SMR_MASK_SHIFT;
return iommu_fwspec_add_ids(dev, &fwid, 1);
}
smmu에 phandle argument인 @args를 전달받아 디바이스에 설정한다.
- smmu의 경우 fwspec에 id 값으로 지정한다.
- @args의 첫 번째 인자는 low 16bit 값이고, 두 번째 인자는 high 16bit 값으로 이를 더한 id를 추가 할당한다.
- “stream-match-mask” 속성이 지정된 경우 id 값에 이 마스크 값을 16비트 좌측 shift 한 후 id 값에 or 한다.
smmu에 연결된 디바이스
“iommu-map” 속성에 지정된 id 값 0(u-boot에 의해 수정)을 iommu 드라이버에 전달
. fsl_mc: fsl-mc@80c000000 {
compatible = "fsl,qoriq-mc";
reg = <0x00000008 0x0c000000 0 0x40>, /* MC portal base */
<0x00000000 0x08340000 0 0x40000>; /* MC control reg */
msi-parent = <&its>;
iommu-map = <0 &smmu 0 0>; /* This is fixed-up by u-boot */
dma-coherent;
...
}
smmu 디바이스
전달받은 id 값에 “stream-match-mask” 속성 값 0x7c00 를 smmu 드라이버가 16bit 좌로 쉬프트한 id 값을 or 연산하여 사용한다.
. smmu: iommu@5000000 {
compatible = "arm,mmu-500";
reg = <0 0x5000000 0 0x800000>;
#global-interrupts = <12>;
#iommu-cells = <1>;
stream-match-mask = <0x7C00>;
dma-coherent;
...
}
iommu_fwspec_add_ids()
drivers/iommu/iommu.c
int iommu_fwspec_add_ids(struct device *dev, u32 *ids, int num_ids)
{
struct iommu_fwspec *fwspec = dev_iommu_fwspec_get(dev);
size_t size;
int i;
if (!fwspec)
return -EINVAL;
size = offsetof(struct iommu_fwspec, ids[fwspec->num_ids + num_ids]);
if (size > sizeof(*fwspec)) {
fwspec = krealloc(fwspec, size, GFP_KERNEL);
if (!fwspec)
return -ENOMEM;
dev_iommu_fwspec_set(dev, fwspec);
}
for (i = 0; i < num_ids; i++)
fwspec->ids[fwspec->num_ids + i] = ids[i];
fwspec->num_ids += num_ids;
return 0;
}
EXPORT_SYMBOL_GPL(iommu_fwspec_add_ids);
디바이스에 요청한 ids[] 배열에서 @num_ids 수 만큼 디바이스의 fwspc의 id 공간을 확보하고 전달받은 ids를 복사한다.
ARM64 IOMMU Operations 준비
arch_setup_dma_ops()
arch/arm64/mm/dma-mapping.c
void arch_setup_dma_ops(struct device *dev, u64 dma_base, u64 size,
const struct iommu_ops *iommu, bool coherent)
{
dev->dma_coherent = coherent;
__iommu_setup_dma_ops(dev, dma_base, size, iommu);
#ifdef CONFIG_XEN
if (xen_initial_domain())
dev->dma_ops = xen_dma_ops;
#endif
}
arm64용 dma operation를 준비한다. @coherent는 cohrent 디바이스 여부를 나타낸다.
- of_dma_configure() 함수에서 “dma-ranges” 속성을 읽어들인 후 dma_base 및 size 등을 인자로 주고 이 함수에 진입하였다.
__iommu_setup_dma_ops()
arch/arm64/mm/dma-mapping.c
static void __iommu_setup_dma_ops(struct device *dev, u64 dma_base, u64 size,
const struct iommu_ops *ops)
{
struct iommu_domain *domain;
if (!ops)
return;
/*
* The IOMMU core code allocates the default DMA domain, which the
* underlying IOMMU driver needs to support via the dma-iommu layer.
*/
domain = iommu_get_domain_for_dev(dev);
if (!domain)
goto out_err;
if (domain->type == IOMMU_DOMAIN_DMA) {
if (iommu_dma_init_domain(domain, dma_base, size, dev))
goto out_err;
dev->dma_ops = &iommu_dma_ops;
}
return;
out_err:
pr_warn("Failed to set up IOMMU for device %s; retaining platform DMA ops\n",
dev_name(dev));
}
iommu를 사용하는 dma 디바이스인 경우 dma 매핑 도메인을 초기화하고 디바이스에 generic iommu_dma_ops를 지정한다.
도메인 초기화
iommu_dma_init_domain()
drivers/iommu/dma-iommu.c
/**
* iommu_dma_init_domain - Initialise a DMA mapping domain
* @domain: IOMMU domain previously prepared by iommu_get_dma_cookie()
* @base: IOVA at which the mappable address space starts
* @size: Size of IOVA space
* @dev: Device the domain is being initialised for
*
* @base and @size should be exact multiples of IOMMU page granularity to
* avoid rounding surprises. If necessary, we reserve the page at address 0
* to ensure it is an invalid IOVA. It is safe to reinitialise a domain, but
* any change which could make prior IOVAs invalid will fail.
*/
int iommu_dma_init_domain(struct iommu_domain *domain, dma_addr_t base,
u64 size, struct device *dev)
{
struct iommu_dma_cookie *cookie = domain->iova_cookie;
struct iova_domain *iovad = &cookie->iovad;
unsigned long order, base_pfn, end_pfn;
int attr;
if (!cookie || cookie->type != IOMMU_DMA_IOVA_COOKIE)
return -EINVAL;
/* Use the smallest supported page size for IOVA granularity */
order = __ffs(domain->pgsize_bitmap);
base_pfn = max_t(unsigned long, 1, base >> order);
end_pfn = (base + size - 1) >> order;
/* Check the domain allows at least some access to the device... */
if (domain->geometry.force_aperture) {
if (base > domain->geometry.aperture_end ||
base + size <= domain->geometry.aperture_start) {
pr_warn("specified DMA range outside IOMMU capability\n");
return -EFAULT;
}
/* ...then finally give it a kicking to make sure it fits */
base_pfn = max_t(unsigned long, base_pfn,
domain->geometry.aperture_start >> order);
}
/* start_pfn is always nonzero for an already-initialised domain */
if (iovad->start_pfn) {
if (1UL << order != iovad->granule ||
base_pfn != iovad->start_pfn) {
pr_warn("Incompatible range for DMA domain\n");
return -EFAULT;
}
return 0;
}
init_iova_domain(iovad, 1UL << order, base_pfn);
if (!cookie->fq_domain && !iommu_domain_get_attr(domain,
DOMAIN_ATTR_DMA_USE_FLUSH_QUEUE, &attr) && attr) {
cookie->fq_domain = domain;
init_iova_flush_queue(iovad, iommu_dma_flush_iotlb_all, NULL);
}
if (!dev)
return 0;
return iova_reserve_iommu_regions(dev, domain);
}
EXPORT_SYMBOL(iommu_dma_init_domain);
dma 매핑 도메인을 초기호한다.
- 코드 라인 9~10에서 도메인에 지정된 iova 쿠키가 없거나 IOMMU_DMA_IOVA_COOKIE 타입이 아닌 경우 -EINVAL 에러를 반환한다.
- IOMMU_DMA_MSI_COOKIE 타입인 경우 에러
- 코드 라인 13~15에서 도메인이 지원하는 페이지 사이지 중 가장 작은 페이지 단위의 order 값을 사용한 후 dma 주소인 @base와 @size로 base_pfn과 end_pfn을 산출한다.
- 예) 4K 페이지, @base=0x1000_0000, @size=0x2000
- base_pfn=0x1_0000, end_pfn=0x1_0001과 같이 2 개의 페이지 범위가 산출된다.
- 코드 라인 18~27에서 주소 범위가 도메인 주소 경계를 초과하는 경우 경고 메시지를 출력하고 -EFAULT 에러를 반환한다.
- 코드 라인 30~38에서 iova 도메인이 초기화되어 이미 시작 pfn이 지정된 경우에는 아무것도 하지 않고 성공(0)을 반환한다.
- 코드 라인 40에서 iova 도메인을 초기화한다.
- 코드 라인 42~46에서 flush queue 콜백을 사용하지 않으면서 flush queue 속성 요청이 있으면 이 도메인을 fq_domain에 지정하고, flush queue를 초기화한다.
- 코드 라인 51에서 도메인 영역을 reserve 한다.
DMA 준비
다음 그림은 DMA coherent 메모리 할당 및 매핑 관련 operation을 설정하는 과정을 보여준다.
- AMBA 버스, Platform 버스 또는 PCI 버스에 연결된 디바이스가 probe될 때각 버스의 dma configure 함수들이 호출되고, 아래에서는 ACPI는 제외하고 디바이스 트리를 통해 셋업하는 과정만을 보여준다.
- 또한 디바이스 트리의 reserve-memory가 지정되는 경우에도 of_dma_configure() 함수가 호출되는 것을 알 수 있다.
- 자세한 과정의 소스는 여기에서 분석하지 않고, (*alloc) 후크에 연결된 함수만을 보면 아래 붉은 글씨로 __iommu_alloc_attrs() 함수가 연결된 것을 확인할 수 있다. 이 소스만 잠깐 아래에서 분석해본다.
__iommu_alloc_attrs()
arch/arm64/mm/dma-mapping.c – 1/2-
static void *__iommu_alloc_attrs(struct device *dev, size_t size,
dma_addr_t *handle, gfp_t gfp,
unsigned long attrs)
{
bool coherent = dev_is_dma_coherent(dev);
int ioprot = dma_info_to_prot(DMA_BIDIRECTIONAL, coherent, attrs);
size_t iosize = size;
void *addr;
if (WARN(!dev, "cannot create IOMMU mapping for unknown device\n"))
return NULL;
size = PAGE_ALIGN(size);
/*
* Some drivers rely on this, and we probably don't want the
* possibility of stale kernel data being read by devices anyway.
*/
gfp |= __GFP_ZERO;
if (!gfpflags_allow_blocking(gfp)) {
struct page *page;
/*
* In atomic context we can't remap anything, so we'll only
* get the virtually contiguous buffer we need by way of a
* physically contiguous allocation.
*/
if (coherent) {
page = alloc_pages(gfp, get_order(size));
addr = page ? page_address(page) : NULL;
} else {
addr = dma_alloc_from_pool(size, &page, gfp);
}
if (!addr)
return NULL;
*handle = iommu_dma_map_page(dev, page, 0, iosize, ioprot);
if (*handle == DMA_MAPPING_ERROR) {
if (coherent)
__free_pages(page, get_order(size));
else
dma_free_from_pool(addr, size);
addr = NULL;
}
DMA를 위해 coherent 메모리를 할당한 후 매핑한다.
- 코드 라인 6에서 dma 방향, coherent 지원 여부, 속성을 통해 iommu 페이지 프로텍션 플래그를 구해온다.
- 코드 라인 13에서 매핑 사이즈는 페이지 단위로 정렬한다.
- 코드 라인 19에서 할당할 메모리는 0으로 초기화하도록 __GFP_ZERO 플래그를 추가한다.
- 코드 라인 21~35에서 interrupt context 등의 atomic 할당 요청인 경우이다. coherent 지원 디바이스인 경우 시스템 메모리에서 할당 받고, 아닌 경우 atomic pool에서 할당 받는다.
- 코드 라인 37~44에서 할당한 페이지를 iommu 매핑한다. 매핑이 실패한 경우 할당한 메모리를 할당 해제한다.
iommu 페이지 프로텍션 플래그
- IOMMU_READ
- IOMMU_WRITE
- IOMMU_CACHE
- IOMMU_NOEXEC
- IOMMU_MMIO
- IOMMU_PRIV
- privilege level (커널 레벨) 액세스
arch/arm64/mm/dma-mapping.c – 2/2-
} else if (attrs & DMA_ATTR_FORCE_CONTIGUOUS) {
pgprot_t prot = arch_dma_mmap_pgprot(dev, PAGE_KERNEL, attrs);
struct page *page;
page = dma_alloc_from_contiguous(dev, size >> PAGE_SHIFT,
get_order(size), gfp & __GFP_NOWARN);
if (!page)
return NULL;
*handle = iommu_dma_map_page(dev, page, 0, iosize, ioprot);
if (*handle == DMA_MAPPING_ERROR) {
dma_release_from_contiguous(dev, page,
size >> PAGE_SHIFT);
return NULL;
}
addr = dma_common_contiguous_remap(page, size, VM_USERMAP,
prot,
__builtin_return_address(0));
if (addr) {
if (!coherent)
__dma_flush_area(page_to_virt(page), iosize);
memset(addr, 0, size);
} else {
iommu_dma_unmap_page(dev, *handle, iosize, 0, attrs);
dma_release_from_contiguous(dev, page,
size >> PAGE_SHIFT);
}
} else {
pgprot_t prot = arch_dma_mmap_pgprot(dev, PAGE_KERNEL, attrs);
struct page **pages;
pages = iommu_dma_alloc(dev, iosize, gfp, attrs, ioprot,
handle, flush_page);
if (!pages)
return NULL;
addr = dma_common_pages_remap(pages, size, VM_USERMAP, prot,
__builtin_return_address(0));
if (!addr)
iommu_dma_free(dev, pages, iosize, handle);
}
return addr;
}
- 코드 라인 1~27에서 contiguous 메모리를 사용하라는 속성 요청이 있는 경우 contiguous 메모리 페이지를 할당 받은 후 iommu 매핑을 한다. 그런 후 아키텍처가 지원하는 페이지 속성으로 다시 리매핑한다.
- 코드 라인 28~41에서 iommu 디바이스를 위해 dma 버퍼 메모리를 할당하고 iova 영역에 매핑한다. 그 후 아키텍처가 지원하는 페이지 속성으로 다시 리매핑한다.
IOMMU-DMA 메모리 할당
iommu_dma_alloc()
drivers/iommu/dma-iommu.c
/**
* iommu_dma_alloc - Allocate and map a buffer contiguous in IOVA space
* @dev: Device to allocate memory for. Must be a real device
* attached to an iommu_dma_domain
* @size: Size of buffer in bytes
* @gfp: Allocation flags
* @attrs: DMA attributes for this allocation
* @prot: IOMMU mapping flags
* @handle: Out argument for allocated DMA handle
* @flush_page: Arch callback which must ensure PAGE_SIZE bytes from the
* given VA/PA are visible to the given non-coherent device.
*
* If @size is less than PAGE_SIZE, then a full CPU page will be allocated,
* but an IOMMU which supports smaller pages might not map the whole thing.
*
* Return: Array of struct page pointers describing the buffer,
* or NULL on failure.
*/
struct page **iommu_dma_alloc(struct device *dev, size_t size, gfp_t gfp,
unsigned long attrs, int prot, dma_addr_t *handle,
void (*flush_page)(struct device *, const void *, phys_addr_t))
{
struct iommu_domain *domain = iommu_get_dma_domain(dev);
struct iommu_dma_cookie *cookie = domain->iova_cookie;
struct iova_domain *iovad = &cookie->iovad;
struct page **pages;
struct sg_table sgt;
dma_addr_t iova;
unsigned int count, min_size, alloc_sizes = domain->pgsize_bitmap;
*handle = DMA_MAPPING_ERROR;
min_size = alloc_sizes & -alloc_sizes;
if (min_size < PAGE_SIZE) {
min_size = PAGE_SIZE;
alloc_sizes |= PAGE_SIZE;
} else {
size = ALIGN(size, min_size);
}
if (attrs & DMA_ATTR_ALLOC_SINGLE_PAGES)
alloc_sizes = min_size;
count = PAGE_ALIGN(size) >> PAGE_SHIFT;
pages = __iommu_dma_alloc_pages(dev, count, alloc_sizes >> PAGE_SHIFT,
gfp);
if (!pages)
return NULL;
size = iova_align(iovad, size);
iova = iommu_dma_alloc_iova(domain, size, dev->coherent_dma_mask, dev);
if (!iova)
goto out_free_pages;
if (sg_alloc_table_from_pages(&sgt, pages, count, 0, size, GFP_KERNEL))
goto out_free_iova;
if (!(prot & IOMMU_CACHE)) {
struct sg_mapping_iter miter;
/*
* The CPU-centric flushing implied by SG_MITER_TO_SG isn't
* sufficient here, so skip it by using the "wrong" direction.
*/
sg_miter_start(&miter, sgt.sgl, sgt.orig_nents, SG_MITER_FROM_SG);
while (sg_miter_next(&miter))
flush_page(dev, miter.addr, page_to_phys(miter.page));
sg_miter_stop(&miter);
}
if (iommu_map_sg(domain, iova, sgt.sgl, sgt.orig_nents, prot)
< size)
goto out_free_sg;
*handle = iova;
sg_free_table(&sgt);
return pages;
out_free_sg:
sg_free_table(&sgt);
out_free_iova:
iommu_dma_free_iova(cookie, iova, size);
out_free_pages:
__iommu_dma_free_pages(pages, count);
return NULL;
}
iommu 디바이스를 위해 dma 버퍼 메모리를 할당하고 iova 영역에 매핑한다.
- 코드 라인 15에서 지원하는 페이지들 중 가장 작은 페이지의 크기를 구한다.
- 예) 4K, 16K를 동시 지원하는 경우, pgsize_bitmap=0x5000(16K+4K)
- 코드 라인 16~21에서 최소 사이즈가 페이지 사이즈보다 작은 경우 최소 사이즈를 페이지 사이즈로 하고, 할당 사이즈는 페이지 사이즈를 포함한다. 요청한 @size는 최소 사이즈 단위로 정렬해야 한다.
- 코드 라인 22~23에서 만일 싱글 페이지 요청이 있었던 경우 할당 사이즈는 최소 사이즈가 된다.
- 코드 라인 25~29에서 iommu 디바이스를 위해 dma 버퍼 메모리를 할당한다. 분산된 물리 페이지를 가리키는 페이지 디스크립터 배열을 반환한다.
- 코드 라인 31에서 요청한 @size를 iova 공간이 지원하는 granule 단위로 정렬한다.
- 코드 라인 32~34에서 iommu 디바이스를 위해 iova 영역을 할당한다.
- 코드 라인 36~37에서 페이지 배열 정보로 이 함수에서 임시로 사용하기 위해 sg 테이블을 할당하고 구성한다.
- 코드 라인 39~49에서 iommu가 캐시를 지원하지 않는 경우 페이지들을 플러시한다.
- 코드 라인 51~53에서 해당 iova 영역을 sg 매핑한다.
- 코드 라인 55~57에서 @handle에 dma 물리 주소를 대입하고, 임시로 할당받은 sg 테이블을 할당 해제한 후 페이지를 반환한다.
__iommu_dma_alloc_pages()
drivers/iommu/dma-iommu.c
static struct page **__iommu_dma_alloc_pages(struct device *dev,
unsigned int count, unsigned long order_mask, gfp_t gfp)
{
struct page **pages;
unsigned int i = 0, nid = dev_to_node(dev);
order_mask &= (2U << MAX_ORDER) - 1;
if (!order_mask)
return NULL;
pages = kvzalloc(count * sizeof(*pages), GFP_KERNEL);
if (!pages)
return NULL;
/* IOMMU can map any pages, so himem can also be used here */
gfp |= __GFP_NOWARN | __GFP_HIGHMEM;
while (count) {
struct page *page = NULL;
unsigned int order_size;
/*
* Higher-order allocations are a convenience rather
* than a necessity, hence using __GFP_NORETRY until
* falling back to minimum-order allocations.
*/
for (order_mask &= (2U << __fls(count)) - 1;
order_mask; order_mask &= ~order_size) {
unsigned int order = __fls(order_mask);
gfp_t alloc_flags = gfp;
order_size = 1U << order;
if (order_mask > order_size)
alloc_flags |= __GFP_NORETRY;
page = alloc_pages_node(nid, alloc_flags, order);
if (!page)
continue;
if (!order)
break;
if (!PageCompound(page)) {
split_page(page, order);
break;
} else if (!split_huge_page(page)) {
break;
}
__free_pages(page, order);
}
if (!page) {
__iommu_dma_free_pages(pages, i);
return NULL;
}
count -= order_size;
while (order_size--)
pages[i++] = page++;
}
return pages;
}
iommu dma 디바이스를 위해 @count 페이지 수 만큼 페이지를 할당한 후 page 구조체 배열로 반환한다. 물리 페이지들은 sg 방식을 사용할 계획이므로 order 페이지 단위로 분산될 수 있다. @order_mask에는 할당에 사용할 모든 order 비트들을 지정한다.
- 코드 라인 7~9에서 @order_mask를 버디 시스템에서 사용할 수 있는 모든 order 범위로 제한한다.
- 예) 4K, MAX_ORDER=11, @order_mask=0xf0f
- @order_mask=0x70f (11개 비트로 제한)
- 코드 라인 11~13에서 @count 수 만큼 page 디스크립터 배열을 할당한다.
- 코드 라인 16에서 할당 가능한 영역으로 highmem 메모리 영역을 포함한다. 또한 높은 order 할당부터 내부 루프를 돌며 반복 시도를 할 예정이므로 nowarn 플래그를 추가한다.
- 코드 라인 18~29에서 할당 단위 @order_mask의 비트들 중 큰 order 부터 먼저 시도한다. 그 후 낮은 오더로 줄여나간다.
- 예) size=8M+56K, count=0x40e, order_mask=0x40e
- order = 10, 3, 2, 1 순으로 내려간다.
- 코드 라인 32~34에서 할당할 order size가 order_mask보다 작은 경우 noretry 플래그를 추가한다.
- 코드 라인 35~37에서 2^order 만큼 페이지를 할당한다.
- 코드 라인 38~39에서 최소 order 0인 경우 for 루프를 중단한다.
- 코드 라인 40~46에서 compound 페이지 또는 huge 페이지인 경우 split한다음 버디로 돌려보내고 계속 루프를 진행한다.
- 코드 라인 47~50에서 할당한 페이지가 없는 경우 그 동안 할당한 모든 페이지들을 할당 해제한 후 null을 반환한다.
- 코드 라인 51~53에서 할당한 페이지 수 만큼 @count를 감소시킨다. 할당에 사용한 order_size만큼 pages[] 배열의 인덱스 i와 page 포인터를 증가시킨다.
다음 그림은 dma 용도로 사용할 메모리를 분산된 물리 메모리로 부터 order 단위로 할당한 페이지들에 대한 page 디스크립터 배열로 반환하는 모습을 보여준다. (예: 이해를 돕기 위해 4M+56K 할당)
iommu_dma_alloc_iova()
drivers/iommu/dma-iommu.c
static dma_addr_t iommu_dma_alloc_iova(struct iommu_domain *domain,
size_t size, dma_addr_t dma_limit, struct device *dev)
{
struct iommu_dma_cookie *cookie = domain->iova_cookie;
struct iova_domain *iovad = &cookie->iovad;
unsigned long shift, iova_len, iova = 0;
if (cookie->type == IOMMU_DMA_MSI_COOKIE) {
cookie->msi_iova += size;
return cookie->msi_iova - size;
}
shift = iova_shift(iovad);
iova_len = size >> shift;
/*
* Freeing non-power-of-two-sized allocations back into the IOVA caches
* will come back to bite us badly, so we have to waste a bit of space
* rounding up anything cacheable to make sure that can't happen. The
* order of the unadjusted size will still match upon freeing.
*/
if (iova_len < (1 << (IOVA_RANGE_CACHE_MAX_SIZE - 1)))
iova_len = roundup_pow_of_two(iova_len);
if (dev->bus_dma_mask)
dma_limit &= dev->bus_dma_mask;
if (domain->geometry.force_aperture)
dma_limit = min(dma_limit, domain->geometry.aperture_end);
/* Try to get PCI devices a SAC address */
if (dma_limit > DMA_BIT_MASK(32) && dev_is_pci(dev))
iova = alloc_iova_fast(iovad, iova_len,
DMA_BIT_MASK(32) >> shift, false);
if (!iova)
iova = alloc_iova_fast(iovad, iova_len, dma_limit >> shift,
true);
return (dma_addr_t)iova << shift;
}
iommu 디바이스를 위해 iova 영역을 할당하고, iova 주소를 반환한다.
- 코드 라인 8~11에서 pci msi 인터럽트를 사용하는 dma 디바이스인 경우 iova 쿠키의 msi_iova에서 @size만큼 더한다. 반환 값은 @size를 더하기 직전의 값을 반환한다.
- 코드 라인 13~14에서 @size에 맞게 iova 매핑할 개수를 iova_len에 담는다.
- 코드 라인 21~22에서 iova 매핑할 개수가 캐시 최대 사이즈의 절반인 16보다 작은 경우 iova_len 개수를 2의 제곱승 단위로 올림 처리한다.
- IOVA_RANGE_CACHE_MAX_SIZE(6)
- 코드 라인 24~25에서 버스가 지원하는 영역 범위 이내로 디바이스의 dma 주소 제한을 한다.
- 코드 라인 27~28에서 도메인이 지원하는 영역 범위 이내로 디바이스의 dma 주소 제한을 한다.
- 코드 라인 31~37에서 매핑할 페이지 수 @iova_len 만큼 iova 공간을 할당한다.
- 코드 라인 39에서 iova 주소를 반환한다.
IOMMU-DMA 메모리 페이지 매핑
iommu_dma_map_page()
drivers/iommu/dma-iommu.c
dma_addr_t iommu_dma_map_page(struct device *dev, struct page *page,
unsigned long offset, size_t size, int prot)
{
return __iommu_dma_map(dev, page_to_phys(page) + offset, size, prot,
iommu_get_dma_domain(dev));
}
dma 디바이스에서 물리 페이지 + @offset을 한 iova 공간에 @size 만큼 @prot 속성으로 매핑한다.
__iommu_dma_map()
drivers/iommu/dma-iommu.c
static dma_addr_t __iommu_dma_map(struct device *dev, phys_addr_t phys,
size_t size, int prot, struct iommu_domain *domain)
{
struct iommu_dma_cookie *cookie = domain->iova_cookie;
size_t iova_off = 0;
dma_addr_t iova;
if (cookie->type == IOMMU_DMA_IOVA_COOKIE) {
iova_off = iova_offset(&cookie->iovad, phys);
size = iova_align(&cookie->iovad, size + iova_off);
}
iova = iommu_dma_alloc_iova(domain, size, dma_get_mask(dev), dev);
if (!iova)
return DMA_MAPPING_ERROR;
if (iommu_map(domain, iova, phys - iova_off, size, prot)) {
iommu_dma_free_iova(cookie, iova, size);
return DMA_MAPPING_ERROR;
}
return iova + iova_off;
}
dma 디바이스에서 물리 주소 @phys @size 만큼 @prot 속성으로 매핑한다.
- 코드 라인 8~11에서 full 할당자인 iova 도메인을 사용하는 경우 iova offset 값과 size를 정렬한다.(smmu에서사용)
- 그 외의 쿠키 타입은 리니어 페이지 할당자를 사용하는 msi 방식이다. (vfio에서 사용)
- 코드 라인 13~15에서 @size 만큼 iova 공간을 할당한다.
- 코드 라인 17~20에서 할당한 공간을 물리 메모리가 있는 주소 공간에 매핑한다.
- iova (디바이스가 보는 io 주소) <—–> phys(메모리가 있는 물리 주소)
IOMMU-DMA 메모리 페이지 매핑 해제
iommu_dma_unmap()
drivers/iommu/dma-iommu.c
void iommu_dma_unmap_page(struct device *dev, dma_addr_t handle, size_t size,
enum dma_data_direction dir, unsigned long attrs)
{
__iommu_dma_unmap(iommu_get_dma_domain(dev), handle, size);
}
dma 디바이스가 사용한 dma용 물리 페이지 및 iova 공간을 size 만큼 매핑 해제한다.
__iommu_dma_unmap()
drivers/iommu/dma-iommu.c
static void __iommu_dma_unmap(struct iommu_domain *domain, dma_addr_t dma_addr,
size_t size)
{
struct iommu_dma_cookie *cookie = domain->iova_cookie;
struct iova_domain *iovad = &cookie->iovad;
size_t iova_off = iova_offset(iovad, dma_addr);
dma_addr -= iova_off;
size = iova_align(iovad, size + iova_off);
WARN_ON(iommu_unmap_fast(domain, dma_addr, size) != size);
if (!cookie->fq_domain)
iommu_tlb_sync(domain);
iommu_dma_free_iova(cookie, dma_addr, size);
}
IOVA 공간 관리
iova 영역 관리는 RB 트리와 rcache로 관리된다.
init_iova_domain()
drivers/iommu/iova.c
void
init_iova_domain(struct iova_domain *iovad, unsigned long granule,
unsigned long start_pfn)
{
/*
* IOVA granularity will normally be equal to the smallest
* supported IOMMU page size; both *must* be capable of
* representing individual CPU pages exactly.
*/
BUG_ON((granule > PAGE_SIZE) || !is_power_of_2(granule));
spin_lock_init(&iovad->iova_rbtree_lock);
iovad->rbroot = RB_ROOT;
iovad->cached_node = &iovad->anchor.node;
iovad->cached32_node = &iovad->anchor.node;
iovad->granule = granule;
iovad->start_pfn = start_pfn;
iovad->dma_32bit_pfn = 1UL << (32 - iova_shift(iovad));
iovad->max32_alloc_size = iovad->dma_32bit_pfn;
iovad->flush_cb = NULL;
iovad->fq = NULL;
iovad->anchor.pfn_lo = iovad->anchor.pfn_hi = IOVA_ANCHOR;
rb_link_node(&iovad->anchor.node, NULL, &iovad->rbroot.rb_node);
rb_insert_color(&iovad->anchor.node, &iovad->rbroot);
init_iova_rcaches(iovad);
}
EXPORT_SYMBOL_GPL(init_iova_domain);
iova 도메인을 초기화한다.
IOVA reserve 영역 관리
iova_reserve_iommu_regions()
drivers/iommu/dma-iommu.c
static int iova_reserve_iommu_regions(struct device *dev,
struct iommu_domain *domain)
{
struct iommu_dma_cookie *cookie = domain->iova_cookie;
struct iova_domain *iovad = &cookie->iovad;
struct iommu_resv_region *region;
LIST_HEAD(resv_regions);
int ret = 0;
if (dev_is_pci(dev))
iova_reserve_pci_windows(to_pci_dev(dev), iovad);
iommu_get_resv_regions(dev, &resv_regions);
list_for_each_entry(region, &resv_regions, list) {
unsigned long lo, hi;
/* We ARE the software that manages these! */
if (region->type == IOMMU_RESV_SW_MSI)
continue;
lo = iova_pfn(iovad, region->start);
hi = iova_pfn(iovad, region->start + region->length - 1);
reserve_iova(iovad, lo, hi);
if (region->type == IOMMU_RESV_MSI)
ret = cookie_init_hw_msi_region(cookie, region->start,
region->start + region->length);
if (ret)
break;
}
iommu_put_resv_regions(dev, &resv_regions);
return ret;
}
reserved iommu 영역을 iova 도메인에 등록한다.
- 코드 라인 10~11에서 pci 디바이스인 경우 호스트 브리지의 window 영역들을 iova 도메인에 reserve 한다.
- 코드 라인 13에서 임시로 resv_regions 리스트에 디바이스가 사용하는 iommu의 reserve 영역 정보를 사용하기 위해 추가한다.
- 코드 라인 14~19에서 4 가지 reserve 영역을 순회하며 reserve 타입 중 sw_msi 타입은 skip 한다.
- 코드 라인 21~23에서 시작과 끝 영역의 pfn 값을 사요하여 iova 도메인에 등록한다.
- pfn 영역은 iova 도메인의 rbroot 멤버에서 RB Tree로 운영한다.
- 코드 라인 25~29에서 reserve 타입이 msi 타입인 경우 쿠키 초기화를 한다.
- 코드 라인 31에서 임시로 사용한 resv_regions 리스트로부터 reserve 영역 정보를 제거하고 해제한다.
iommu_get_resv_regions()
drivers/iommu/iommu.c
void iommu_get_resv_regions(struct device *dev, struct list_head *list)
{
const struct iommu_ops *ops = dev->bus->iommu_ops;
if (ops && ops->get_resv_regions)
ops->get_resv_regions(dev, list);
}
iommu 디바이스가 사용하는 iommu 드라이버에서 reserve 영역을 준비하여 @list에 추가한다.
arm_smmu_get_resv_regions() – ARM, ARM64
drivers/iommu/arm-smmu.c
static void arm_smmu_get_resv_regions(struct device *dev,
struct list_head *head)
{
struct iommu_resv_region *region;
int prot = IOMMU_WRITE | IOMMU_NOEXEC | IOMMU_MMIO;
region = iommu_alloc_resv_region(MSI_IOVA_BASE, MSI_IOVA_LENGTH,
prot, IOMMU_RESV_SW_MSI);
if (!region)
return;
list_add_tail(®ion->list, head);
iommu_dma_get_resv_regions(dev, head);
}
smmu에 reserved된 영역을 할당하여 추가한다.
- 코드 라인 5에서 write 가능, 실행 금지, mmio 매핑 플래그를 지정한다.
- 코드 라인 7~10에서 0x800_0000 ~ 0x810_0000 까지 msi iova 영역을 reserve 한다.
- MSI_IOVA_BASE(0x800_0000)
- MSI_IOVA_LENGTH(0x10_0000)
- 코드 라인 12에서 인자로 전달받은 @head 리스트에 이 영역을 추가한다.
- 코드 라인 14에서 디바이스 트리가 아닌 서버에서 acpi를 통해 reserve 영역을 추가한다.
IOVA Rcaches
iova 도메인의 영역을 관리할 때 RB 트리를 이용하여 관리하지만, 더 빠른 fast-path 할당 관리를 지원하기 위해 rcache를 사용한다.
init_iova_rcaches()
drivers/iommu/iova.c
static void init_iova_rcaches(struct iova_domain *iovad)
{
struct iova_cpu_rcache *cpu_rcache;
struct iova_rcache *rcache;
unsigned int cpu;
int i;
for (i = 0; i < IOVA_RANGE_CACHE_MAX_SIZE; ++i) {
rcache = &iovad->rcaches[i];
spin_lock_init(&rcache->lock);
rcache->depot_size = 0;
rcache->cpu_rcaches = __alloc_percpu(sizeof(*cpu_rcache), cache_line_size());
if (WARN_ON(!rcache->cpu_rcaches))
continue;
for_each_possible_cpu(cpu) {
cpu_rcache = per_cpu_ptr(rcache->cpu_rcaches, cpu);
spin_lock_init(&cpu_rcache->lock);
cpu_rcache->loaded = iova_magazine_alloc(GFP_KERNEL);
cpu_rcache->prev = iova_magazine_alloc(GFP_KERNEL);
}
}
}
iova 영역을 Fast-Path로 할당 관리할 수 있도록 rcaches를 초기화한다.
다음 그림은 rcaches의 초기화된 모습을 보여준다.
alloc_iova_fast()
drivers/iommu/iova.c
/**
* alloc_iova_fast - allocates an iova from rcache
* @iovad: - iova domain in question
* @size: - size of page frames to allocate
* @limit_pfn: - max limit address
* @flush_rcache: - set to flush rcache on regular allocation failure
* This function tries to satisfy an iova allocation from the rcache,
* and falls back to regular allocation on failure. If regular allocation
* fails too and the flush_rcache flag is set then the rcache will be flushed.
*/
unsigned long
alloc_iova_fast(struct iova_domain *iovad, unsigned long size,
unsigned long limit_pfn, bool flush_rcache)
{
unsigned long iova_pfn;
struct iova *new_iova;
iova_pfn = iova_rcache_get(iovad, size, limit_pfn + 1);
if (iova_pfn)
return iova_pfn;
retry:
new_iova = alloc_iova(iovad, size, limit_pfn, true);
if (!new_iova) {
unsigned int cpu;
if (!flush_rcache)
return 0;
/* Try replenishing IOVAs by flushing rcache. */
flush_rcache = false;
for_each_online_cpu(cpu)
free_cpu_cached_iovas(cpu, iovad);
goto retry;
}
return new_iova->pfn_lo;
}
EXPORT_SYMBOL_GPL(alloc_iova_fast);
iova 공간 할당을 한 후 iova pfn을 반환한다. fast-path인 rcache를 먼저 이용해보고, 안되면 slow-path인 RB 트리를 사용한다.
- 코드 라인 8~10에서 per-cpu rcache를 구해와서 존재하는 경우 이를 반환한다. (Fast-path)
- 코드 라인 13에서 iova 도메인의 limit_pfn 범위 이내에서 size 만큼의 영역을 할당한다.
- 코드 라인 14~25에서 만일 할당이 실패한 경우 0을 반환한다. 다만 flush_rcache 옵션이 지정된 경우 cpu 캐시를 해제한 후 재시도 해본다.
다음 그림은 Fast-Path인 rcache로 먼저 iova 영역을 할당 시도한 후 실패 시 Slow-Path인 RB-Tree를 사용한 정규 방법으로 시도한다.
iova_rcache_insert()
drivers/iommu/iova.c
static bool iova_rcache_insert(struct iova_domain *iovad, unsigned long pfn,
unsigned long size)
{
unsigned int log_size = order_base_2(size);
if (log_size >= IOVA_RANGE_CACHE_MAX_SIZE)
return false;
return __iova_rcache_insert(iovad, &iovad->rcaches[log_size], pfn);
}
요청한 iova 도메인에서 @size에 해당하는 order용 rcache를 선택하고, iova용 @pfn을 추가한다.
- rcache는 order 별로 관리되며, order는 0 ~ IOVA_RANGE_CACHE_MAX_SIZE(6) – 1까지 사용한다.
- 1, 2, 4, 8, 16, 32 페이지만 가능하다.
다음 그림은 order 별로 관리하는 rcache를 @size로 rcache를 선택한 후 iova pfn을 추가하는 모습을 보여준다.
- cpu rcache의 loaded에서 먼저 할당해준다.
- 만일 cpu rcache의 loaded가 full 상태이면 prev와 교체한 후 loaded에서 할당한다.
- 만일 prev 마저도 full 상태이면 loaded를 iova 영역으로 free한 후 새로운 magazine을 할당해서 loaded에 지정하고 여기서 다시 할당해준다.
__iova_rcache_insert()
drivers/iommu/iova.c
/*
* Try inserting IOVA range starting with 'iova_pfn' into 'rcache', and
* return true on success. Can fail if rcache is full and we can't free
* space, and free_iova() (our only caller) will then return the IOVA
* range to the rbtree instead.
*/
static bool __iova_rcache_insert(struct iova_domain *iovad,
struct iova_rcache *rcache,
unsigned long iova_pfn)
{
struct iova_magazine *mag_to_free = NULL;
struct iova_cpu_rcache *cpu_rcache;
bool can_insert = false;
unsigned long flags;
cpu_rcache = raw_cpu_ptr(rcache->cpu_rcaches);
spin_lock_irqsave(&cpu_rcache->lock, flags);
if (!iova_magazine_full(cpu_rcache->loaded)) {
can_insert = true;
} else if (!iova_magazine_full(cpu_rcache->prev)) {
swap(cpu_rcache->prev, cpu_rcache->loaded);
can_insert = true;
} else {
struct iova_magazine *new_mag = iova_magazine_alloc(GFP_ATOMIC);
if (new_mag) {
spin_lock(&rcache->lock);
if (rcache->depot_size < MAX_GLOBAL_MAGS) {
rcache->depot[rcache->depot_size++] =
cpu_rcache->loaded;
} else {
mag_to_free = cpu_rcache->loaded;
}
spin_unlock(&rcache->lock);
cpu_rcache->loaded = new_mag;
can_insert = true;
}
}
if (can_insert)
iova_magazine_push(cpu_rcache->loaded, iova_pfn);
spin_unlock_irqrestore(&cpu_rcache->lock, flags);
if (mag_to_free) {
iova_magazine_free_pfns(mag_to_free, iovad);
iova_magazine_free(mag_to_free);
}
return can_insert;
}
요청한 iova 도메인의 rcache에 iova용 @pfn을 추가한다.
- 코드 라인 10~14에서 per-cpu rcache의 loaded에 있는 magazine이 full이 아니면 can_insert를 true로 한다.
- 코드 라인 15~17에서 per-cpu rcache의 prev에 있는 magazine이 full이 아니면 loaded로 옮기고 can_insert를 true로 한다.
- 코드 라인 18~32에서 depot이 충분한 경우 기존 loaded의 magazine을 push 한다. depot이 충분하지 않은 경우 loaded를 할당해제 하기 위해 임시 mag_to_free 변수에 대입한다. 할당한 magazine은 loaded에 대입한 후 can_insert를 true로 한다.
- 코드 라인 34~35에서 can_insert가 ture인 경우 magazine을 push한다.
- 코드 라인 39~42에서 depot이 full 된 경우 loaded를 모두 할당 해제한다.
iova_rcache_get()
drivers/iommu/iova.c
/*
* Try to satisfy IOVA allocation range from rcache. Fail if requested
* size is too big or the DMA limit we are given isn't satisfied by the
* top element in the magazine.
*/
static unsigned long iova_rcache_get(struct iova_domain *iovad,
unsigned long size,
unsigned long limit_pfn)
{
unsigned int log_size = order_base_2(size);
if (log_size >= IOVA_RANGE_CACHE_MAX_SIZE)
return 0;
return __iova_rcache_get(&iovad->rcaches[log_size], limit_pfn - size);
}
요청한 iova 도메인의 size order를 관리하는 rcache의 @limit_pfn 범위이내에서 iova 공간을 할당하고, 이에 해당하는 iova pfn을 반환한다.
- rcache는 order 별로 관리되며, order는 0 ~ 5까지 사용한다.
__iova_rcache_get()
drivers/iommu/iova.c
/*
* Caller wants to allocate a new IOVA range from 'rcache'. If we can
* satisfy the request, return a matching non-NULL range and remove
* it from the 'rcache'.
*/
static unsigned long __iova_rcache_get(struct iova_rcache *rcache,
unsigned long limit_pfn)
{
struct iova_cpu_rcache *cpu_rcache;
unsigned long iova_pfn = 0;
bool has_pfn = false;
unsigned long flags;
cpu_rcache = raw_cpu_ptr(rcache->cpu_rcaches);
spin_lock_irqsave(&cpu_rcache->lock, flags);
if (!iova_magazine_empty(cpu_rcache->loaded)) {
has_pfn = true;
} else if (!iova_magazine_empty(cpu_rcache->prev)) {
swap(cpu_rcache->prev, cpu_rcache->loaded);
has_pfn = true;
} else {
spin_lock(&rcache->lock);
if (rcache->depot_size > 0) {
iova_magazine_free(cpu_rcache->loaded);
cpu_rcache->loaded = rcache->depot[--rcache->depot_size];
has_pfn = true;
}
spin_unlock(&rcache->lock);
}
if (has_pfn)
iova_pfn = iova_magazine_pop(cpu_rcache->loaded, limit_pfn);
spin_unlock_irqrestore(&cpu_rcache->lock, flags);
return iova_pfn;
}
요청한 rcache의 @limit_pfn 범위이내에서 iova 공간을 할당하고, 이에 해당하는 iova pfn을 반환한다.
- 코드 라인 9~13에서 per-cpu rcache의 loaded에 있는 magazine이 있는 경우 has_pfn을 true로 한다.
- 코드 라인 14~16에서 prev magazie이 있는 경우 prev magazine을 load로 옮긴 후 has_pfn을 true로 한다.
- 코드 라인 17~25에서 그 외의 경우 depot_size가 있는 경우 rcache를 lock으로 보호한 후 혹시 lock 이전에 끼어든 loaded magazine이 있으면 할당 해제한다. 그런 후 depot[]을 하나 연결하고 has_pfn을 true로 한다.
- 코드 라인 27~28에서 has_pfn이 true인 경우 loaded magazine pop을 하여 iova_pfn을 알아온다.
참고
- ARM® CoreLink™ MMU-500 System Memory Management Unit – Technical Reference Manual – 다운로드 pdf