IOMMU

<kernel v5.0>

IOMMU(Input Output Memory Management Unit)

IOMMU는 다음과 같은 일들을 할 수 있다.

  • Transalation
    • 디바이스(IO 또는 버스) 주소를 물리 주소로 변환할 수 있도록 매핑을 제공한다.
    • DMA에 사용하는 버퍼는 연속된 물리 주소이어야 하는데, IOMMU를 사용하는 경우 그러한 제한이 없어진다.
      • IOMMU가 MMU와는 별도의 매핑 테이블을 사용하므로 시스템 메모리에 페이지가 반드시 연속되지 않아도 된다.
  • Isolation
    • 메모리에 대한 디바이스의 접근 제어를 제공한다.
  • IO Virtualization
    • 가상화를 지원하며 디바이스가 별개의 DMA 가상 주소 공간을 사용할 수 있다.
    • cpu에 있는 MMU도 가상화를 위해 별개의 MMU가 있는 것처럼 IOMMU도 유사하다.

 

아래 그림에서 좌측은 디바이스와 CPU가 메모리에 접근하는 것에 대한 논리적인 다이어그램이고, 우측은 ARM, ARM64 아키텍처에서 사용되는 ARM SMMU가 구성된 위치를 보여준다.

 

메모리 할당의 제한과 성능

시스템 메모리에 접근하는 다음 두 가지에 방법에 대한 성능을 생각해보자.

  • Direct DMA
    • 시스템이 DMA를 위해 메모리 영역의 일부문만을 접근하는 몇 가지 case를 고려해보자.
      • 32비트 시스템에서 제한된 ZONE_DMA 또는 ZONE_NORMAL 영역만을 사용할 수 있는 경우
      • 물리 메모리의 주소 영역이 4G를 초과하는 64비트 시스템에서 제한된 ZONE_DMA32(4G) 영역만을 사용할 수 있는 경우
    • DMA를 위해 극히 제한된 영역만을 사용할 수 있는 경우 DMA 버퍼만을 제한된 영역에 두고, DMA 전송 후에 시스템은 사용자의 버퍼 공간으로 복사하는 방법을 사용해야한다. 이러한 바운스 버퍼를 사용하는 swiotlb 구현을 사용하면 성능이 저하된다.
    • 연속된 물리 메모리의 할당만을 요구하므로 대규모 또는 큰 버퍼가 요구되는 경우 처리가 지연될 수 있다.
  • IOMMU
    • IOMMU를 사용하면 디바이스가 물리 메모리의 전체 영역을 사용할 수 있으므로 대용량 버퍼를 구성할 수 있다.
    • IOMMU의 매핑 테이블을 사용하여 디바이스가 분산된 물리 메모리에 접근할 수 있다.
      • DMA 버퍼 용도로 분산된 물리 메모리를 할당받고, IOMMU의 매핑 테이블을 이용하여 디바이스가 하나의 연속된 가상 주소에 액세스한다.
    • 특수한 제한: 물리 메모리의 주소 영역이 4G를 초과하는 64비트 시스템에서 IOMMU를 32비트 모드를 사용하는 경우 시스템 메모리의 모든 영역에 버퍼를 만들 수가 없다. 이러한 경우에도 swiotlb 방법을 사용하므로 이 때에도 성능이 저하된다. 그러나 이러한 사용사례 마저도 점점 64비트 IOMMU 모드를 사용하는 방법으로 migration 하므로 점점 찾아보기 힘들것이다.

 

대표적인 IOMMU

  • 인텔
    • North Bridge에 VT-D(Virtualization Technology for Directed I/O)를 채용하여 IO 허브를 제공하고 가상화도 지원한다.
  • AMD
    • dual IOMMU를 채용하여 IO 허브를 제공하고 가상화도 지원한다.
  • ARM 및 ARM64
    • 여러 버전의 SMMU가 제공되며 역시 가상화를 지원한다.
  • PCI-SIG
    • 내부에 IOMMU가 있고, I/O 가상화 (IOV)와 Address Translation Services (ATS) 기능을 제공한다.
  • Nvidia 그래픽 카드
    • 내부에 GARTGraphics Address Remapping Table 라는 IOMMU가 있고, 가상화를 지원한다.

 

IOMMU-API vs DMA-IOMMU API

IOMMU를 사용하기 위해  IOMMU API를 직접 사용하는 경우는 몇 개의 드라이버를 제외하곤 드물다. 대부분 DMA-IOMMU API를 통해 IOMMU와 연동하여 사용한다.

 

IOMMU 코어 API들과 이에 대응하는 드라이버로 ARM의 SMMU-500(compatible=”arm,mmu-500″) 코드를 위주로 분석한다.

  • 참고한 드라이버는 ARM smmu v1 및 v2를 지원하는 드라이버이다.
    • ARM smmu v3 드라이버는 별도의 코드를 사용한다.

 

Default Domain

  • 디바이스들의 각 그룹을 위해 하나의 디폴트 도메인을 할당한다.
  • 초기 모든 디바이스들은 디폴트 도메인에 속한다.
  • 디폴트 도메인은 공통 DMA-API 구현을 사용한다.

 

디바이스 트리

smmu v1 사용법

iommu 드라이버
.       /* SMMU with stream matching or stream indexing */
        smmu1: iommu {
                compatible = "arm,smmu-v1";
                reg = <0xba5e0000 0x10000>;
                #global-interrupts = <2>;
                interrupts = <0 32 4>,
                             <0 33 4>,
                             <0 34 4>, /* This is the first context interrupt */
                             <0 35 4>,
                             <0 36 4>,
                             <0 37 4>;
                #iommu-cells = <1>;
        };

 

iommu 디바이스
.       /* device with two stream IDs, 0 and 7 */
        master1 {
                iommus = <&smmu1 0>,
                         <&smmu1 7>;
        };

 

smmu v1 사용 사례)

smmu 드라이버

./arm/juno-base.dtsi

        smmu_pcie: iommu@2b500000 {
                compatible = "arm,mmu-401", "arm,smmu-v1";
                reg = <0x0 0x2b500000 0x0 0x10000>;
                interrupts = <GIC_SPI 40 IRQ_TYPE_LEVEL_HIGH>,
                             <GIC_SPI 40 IRQ_TYPE_LEVEL_HIGH>;
                #iommu-cells = <1>;
                #global-interrupts = <1>;
                dma-coherent;
                status = "disabled";
        };
smmu 디바이스

./arm/juno-base.dtsi

.       pcie_ctlr: pcie@40000000 {
                compatible = "arm,juno-r1-pcie", "plda,xpressrich3-axi", "pci-host-ecam-generic";
                device_type = "pci";
                reg = <0 0x40000000 0 0x10000000>;      /* ECAM config space */
                bus-range = <0 255>;
                linux,pci-domain = <0>;
                #address-cells = <3>;
                #size-cells = <2>;
                dma-coherent;
                ranges = <0x01000000 0x00 0x00000000 0x00 0x5f800000 0x0 0x00800000>,
                         <0x02000000 0x00 0x50000000 0x00 0x50000000 0x0 0x08000000>,
                         <0x42000000 0x40 0x00000000 0x40 0x00000000 0x1 0x00000000>;
                #interrupt-cells = <1>;
                interrupt-map-mask = <0 0 0 7>;
                interrupt-map = <0 0 0 1 &gic 0 0 0 136 4>,
                                <0 0 0 2 &gic 0 0 0 137 4>,
                                <0 0 0 3 &gic 0 0 0 138 4>,
                                <0 0 0 4 &gic 0 0 0 139 4>;
                msi-parent = <&v2m_0>;
                status = "disabled";
                iommu-map-mask = <0x0>; /* RC has no means to output PCI RID */
                iommu-map = <0x0 &smmu_pcie 0x0 0x1>;
        };

 

IOMMU 코어 초기화

iommu_init()

static int __init iommu_init(void)
{
        iommu_group_kset = kset_create_and_add("iommu_groups",
                                               NULL, kernel_kobj);
        BUG_ON(!iommu_group_kset);

        iommu_debugfs_setup();

        return 0;
}
core_initcall(iommu_init);

“/sys/kernel/iommu_groups” 디렉토리를 생성한다.

  • iommu 도메인에 iommu 그룹이 추가되는 경우 위의 디렉토리 아래에 추가된 iommu 그룹의 id 번호로 디렉토리가 생성된다.

 

IOMMU Core API

기본이 되는 파란색 함수들에 한해 소스를 자세히 분석하였다.

 

IOMMU 디바이스
  • iommu_device_register()
    • iommu 디바이스를 시스템에 등록한다.
  • iommu_device_unregister()
    • iommu 디바이스를 시스템에서 등록 해제한다.

 

IOMMU 도메인
  • iommu_domain_alloc()
    • 새 iommu 도메인을 시스템에 추가한다.
  • iommu_domain_free()
    • iommu 도메인을 시스템에서 삭제한다.
  • iommu_domain_window_enable()
  • iommu_domain_window_disable()
  • iommu_domain_set_attr()
    • 도메인 속성에 따른 데이터를 지정한다.
      • DOMAIN_ATTR_GEOMETRY
      • DOMAIN_ATTR_PAGING
      • DOMAIN_ATTR_WINDOWS
      • DOMAIN_ATTR_FSL_PAMU_STASH
      • DOMAIN_ATTR_FSL_PAMU_ENABLE
      • DOMAIN_ATTR_FSL_PAMUV1
      • DOMAIN_ATTR_NESTING
      • DOMAIN_ATTR_DMA_USE_FLUSH_QUEUE
  • iommu_domain_get_attr()
    • 도메인 속성에 따른 데이터를 알아온다.

 

IOMMU 그룹
  • iommu_group_alloc()
    • iommu 드라이버에서 새로운 iommu 그룹을 할당하기 위해 호출된다.
  • iommu_group_release()
    • 새로운 iommu 그룹을 할당 해제한다.
  • iommu_group_get_by_id()
    • id 번호로 iommu 그룹을 알아온다.
  • iommu_group_get_iommudata()
    • iommu 그룹에서 저장된 데이터를 가져온다.
  • iommu_group_set_iommudata()
    • iommu 그룹에 데이터를 저장한다.
  • iommu_group_set_name()
    • iommu 그룹 이름을 지정한다.
  • iommu_group_get()
    • 디바이스를 위해 그룹을 알아온다. 그리고 참조 카운터를 증가시킨다.
  • iommu_group_put()
    • 디바이스가 iommu 그룹의 사용이 완료되었다. 따라서 참조 카운터를 감소시킨다.
  • iommu_group_register_notifier()
    • iommu 그룹에 디바이스의 추가 및 삭제 시마다 호출될 notifier call-back을 등록한다.
    • blocking_notifier_call_chain() 함수 호출시 위의 call-back이 동작된다.
  • iommu_group_unregister_notifier()
    • iommu 그룹에 디바이스의 추가 및 삭제 시마다 호출되던 notifier call-back을 등록 해제한다.
  • iommu_group_id()
    • iommu 그룹 id를 반환한다.
  • iommu_get_group_resv_regions()
    • /sys/kernel/iommu_groups/reserved_regions 파일을 통해 등록된 그룹을 확인할 수 있다.

 

IOMMU 디바이스
  • iommu_group_add_device()
    • iommu 그룹에 디바이스를 추가한다.
  • iommu_group_remove_device()
    • iommu 그룹에서 디바이스를 제거한다.
  • iommu_group_for_each_dev()
    • iommu 그룹내에서의 디바이스 iteration
  • iommu_attach_device()
    • 디바이스가 포함된 그룹을 도메인에 attach 한다.
  • iommu_detach_device()
    • 디바이스가 포함된 그룹을 도메인에서 detach 한다.
  • iommu_get_domain_for_dev()
    • 디바이스가 소속한 그룹의 도메인을 알아온다.
  • iommu_attach_group()
    • iommu 그룹을 iommu 도메인에 attach 한다.
  • iommu_detach_group()
    • iommu 그룹을 iommu 도메인에서 detach 한다.

 

IOMMU 매핑
  • iommu_map()
    • 페이지들을 매핑한다.
  • iommu_map_sg()
    • scatter/gather 분산된 메모리를 매핑한다.
  • iommu_unmap()
    • 페이지들을 매핑 해제한다.
  • iommu_unmap_fast()
    • iommu_unmap()과 동일하나 iotlb sync를 하지 않는다.

 

디바이스 트리

  • iommu_fwspec_init()
  • iommu_fwspec_add_ids()
  • iommu_fwspec_free()

 

기타

  • iommu_set_fault_handler()
  • report_iommu_fault()
  • iommu_iova_to_phys()

 

IOMMU 디바이스 등록/해제

iommu_device_register()

drivers/iommu/iommu.c

int iommu_device_register(struct iommu_device *iommu)
{
        spin_lock(&iommu_device_lock);
        list_add_tail(&iommu->list, &iommu_device_list);
        spin_unlock(&iommu_device_lock);

        return 0;
}

iommu 디바이스를 시스템에 등록한다.

  • iommu 디바이스를 전역 리스트인 iommu_device_list에 추가한다.

 

iommu_device_unregister()

drivers/iommu/iommu.c

void iommu_device_unregister(struct iommu_device *iommu)
{
        spin_lock(&iommu_device_lock);
        list_del(&iommu->list);
        spin_unlock(&iommu_device_lock);
}

iommu 디바이스를 시스템에서 등록 해제한다.

  • iommu 디바이스를 전역 리스트인 iommu_device_list에서 제거한다.

 

IOMMU Domain

게스트 VM을 지원(보호)하기 위해 도메인 기능을 제공한다.

  • 디바이스는 호스트 및 VM들 간에 이동할 수 있다.

 

 

iommu domain 타입
  • IOMMU_DOMAIN_BLOCKED
    • 모든 DMA가 차단되었으므로 디바이스를 분리할 수 있다.
  • IOMMU_DOMAIN_IDENTITY
    • DMA 주소와 시스템의 물리 주소가 같다. (1:1 identity 매핑)
  • IOMMU_DOMAIN_UNMANAGED
    • 가상 머신에서 사용되며, DMA 매핑이 IOMMU-API에 의해 관리된다.
  • IOMMU_DOMAIN_DMA
    • 내부적으로 DMA-API 구현을 위해 사용되는 타입이다.
    • 이 플래그를 사용하면 IOMMU 드라이버가 이 도메인에 대해 특정 최적화를 구현할 수 있다.

 

iommu_domain_alloc()

drivers/iommu/iommu.c

struct iommu_domain *iommu_domain_alloc(struct bus_type *bus)
{
        return __iommu_domain_alloc(bus, IOMMU_DOMAIN_UNMANAGED);
}
EXPORT_SYMBOL_GPL(iommu_domain_alloc);

버스의 iommu 도메인을 할당하여 지정한다.

  • 이렇게 생성한 도메인은 IOMMU-API를 사용할 수 있다.
  • IOMMU-API들은 몇 개의 함수를 제외하고는 대부분 iommu로 시작한다.
    • iommu_*()

 

__iommu_domain_alloc()

drivers/iommu/iommu.c

static struct iommu_domain *__iommu_domain_alloc(struct bus_type *bus,
                                                 unsigned type)
{
        struct iommu_domain *domain;

        if (bus == NULL || bus->iommu_ops == NULL)
                return NULL;

        domain = bus->iommu_ops->domain_alloc(type);
        if (!domain)
                return NULL;

        domain->ops  = bus->iommu_ops;
        domain->type = type;
        /* Assume all sizes by default; the driver may override this later */
        domain->pgsize_bitmap  = bus->iommu_ops->pgsize_bitmap;

        return domain;
}

요청한 버스를 위해 iommu 도메인을 새로 할당한다.

  • 코드 라인 6~7에서 버스에 지정된 iommu ops가 구현되지 않은 경우 함수를 빠져나간다.
  • 코드 라인 9~11에서 iommu 드라이버에서 domain을 할당한다.
  • 코드 라인 13~16에서 할당한 도메인에 버스의 ops, pgsize_bitmap 및 인자로 요청한 타입을 지정한다.

 

arm_smmu_domain_alloc() – compatible=”arm,mmu-500″

drivers/iommu/arm-smmu.c

static struct iommu_domain *arm_smmu_domain_alloc(unsigned type)
{
        struct arm_smmu_domain *smmu_domain;

        if (type != IOMMU_DOMAIN_UNMANAGED &&
            type != IOMMU_DOMAIN_DMA &&
            type != IOMMU_DOMAIN_IDENTITY)
                return NULL;
        /*
         * Allocate the domain and initialise some of its data structures.
         * We can't really do anything meaningful until we've added a
         * master.
         */
        smmu_domain = kzalloc(sizeof(*smmu_domain), GFP_KERNEL);
        if (!smmu_domain)
                return NULL;

        if (type == IOMMU_DOMAIN_DMA && (using_legacy_binding ||
            iommu_get_dma_cookie(&smmu_domain->domain))) {
                kfree(smmu_domain);
                return NULL;
        }

        mutex_init(&smmu_domain->init_mutex);
        spin_lock_init(&smmu_domain->cb_lock);

        return &smmu_domain->domain;
}

ARM smmu의 도메인을 할당한다.

 

iommu_domain_free()

drivers/iommu/iommu.c

void iommu_domain_free(struct iommu_domain *domain)
{
        domain->ops->domain_free(domain);
}
EXPORT_SYMBOL_GPL(iommu_domain_free);

버스의 iommu 도메인을 할당 해제한다.

 

arm_smmu_domain_free() – compatible=”arm,mmu-500″

drivers/iommu/arm-smmu.c

static void arm_smmu_domain_free(struct iommu_domain *domain)
{
        struct arm_smmu_domain *smmu_domain = to_smmu_domain(domain);

        /*
         * Free the domain resources. We assume that all devices have
         * already been detached.
         */
        iommu_put_dma_cookie(domain);
        arm_smmu_destroy_domain_context(domain);
        kfree(smmu_domain);
}

ARM smmu 도메인을 할당 해제한다.

 

IOMMU 그룹

디바이스 개별로 isolation이 안될 수도 있다. 즉 IOMMU 그룹에 묶인 디바이스들을 대상으로 접근 제어를 제공할 수도 있다.

 

그룹명

그룹 생성 시 그룹 마다 부여되는 group_id는 0부터 시작하는 숫자이고, 곧바로 그룹 명으로 사용된다.

 

다음은 0번 iommu 그룹에 연결된 디바이스를 보여준다.

$ ls /sys/kernel/iommu_groups/0/devices/
ff650000.vpu_service

 

Grouping 구현

그루핑에 관계된 디바이스 구현 함수는 다음과 같이 3가지로 나뉜다.

  • PCI 디바이스 그루핑 함수
  • FSL-MC 디바이스 그루핑 함수
  • Generic 디바이스 그루핑 함수

 

그룹 할당

iommu_group_alloc()

drivers/iommu/iommu.c

/**
 * iommu_group_alloc - Allocate a new group
 *
 * This function is called by an iommu driver to allocate a new iommu
 * group.  The iommu group represents the minimum granularity of the iommu.
 * Upon successful return, the caller holds a reference to the supplied
 * group in order to hold the group until devices are added.  Use
 * iommu_group_put() to release this extra reference count, allowing the
 * group to be automatically reclaimed once it has no devices or external
 * references.
 */
struct iommu_group *iommu_group_alloc(void)
{
        struct iommu_group *group;
        int ret;

        group = kzalloc(sizeof(*group), GFP_KERNEL);
        if (!group)
                return ERR_PTR(-ENOMEM);

        group->kobj.kset = iommu_group_kset;
        mutex_init(&group->mutex);
        INIT_LIST_HEAD(&group->devices);
        BLOCKING_INIT_NOTIFIER_HEAD(&group->notifier);

        ret = ida_simple_get(&iommu_group_ida, 0, 0, GFP_KERNEL);
        if (ret < 0) {
                kfree(group);
                return ERR_PTR(ret);
        }
        group->id = ret;

        ret = kobject_init_and_add(&group->kobj, &iommu_group_ktype,
                                   NULL, "%d", group->id);
        if (ret) {
                ida_simple_remove(&iommu_group_ida, group->id);
                kfree(group);
                return ERR_PTR(ret);
        }

        group->devices_kobj = kobject_create_and_add("devices", &group->kobj);
        if (!group->devices_kobj) {
                kobject_put(&group->kobj); /* triggers .release & free */
                return ERR_PTR(-ENOMEM);
        }

        /*
         * The devices_kobj holds a reference on the group kobject, so
         * as long as that exists so will the group.  We can therefore
         * use the devices_kobj for reference counting.
         */
        kobject_put(&group->kobj);

        ret = iommu_group_create_file(group,
                                      &iommu_group_attr_reserved_regions);
        if (ret)
                return ERR_PTR(ret);

        ret = iommu_group_create_file(group, &iommu_group_attr_type);
        if (ret)
                return ERR_PTR(ret);

        pr_debug("Allocated group %d\n", group->id);

        return group;
}
EXPORT_SYMBOL_GPL(iommu_group_alloc);

iommu 그룹을 새로 할당한다.

  • 코드 라인 6~8에서 iommu_group 구조체를 할당한다.
  • 코드 라인 10~13에서 할당 받은 그룹을 초기화한다.
  • 코드 라인 15~20에서 전역 iommu_group_ida를 통해 그룹 id를 부여받는다.
  • 코드 라인 22~34에서 “/sys/kernel/iommu_groups/”뒤에 부여 받은 그룹 id 번호로 디렉토리를 생성하고, 그 아래로 “devices” 디렉토리도 생성한다.
  • 코드 라인 43~46에서 “/sys/kernel/iommu_groups/reserved_regions” 속성 파일을 생성하여 등록된 영역을 볼 수 있도록 한다.
  • 코드 라인   48~50에서 “/sys/kernel/iommu_groups/reserved_regions”속성 파일을 생성하여 그룹이 등록된 디폴트 도메인의 타입을 볼 수 있도록 한다.
  • 코드 라인 52~54에서 “allocated group %d” 메시지를 출력하고 할당한 그룹을 반환한다.

 

그룹 삭제

그룹 할당 해제는 그룹에 해당하는 속성 디렉토리를 삭제하면 iommu_group_release() 함수가 호출되어 제거된다.

iommu_group_release()

drivers/iommu/iommu.c

static void iommu_group_release(struct kobject *kobj)
{
        struct iommu_group *group = to_iommu_group(kobj);

        pr_debug("Releasing group %d\n", group->id);

        if (group->iommu_data_release)
                group->iommu_data_release(group->iommu_data);

        ida_simple_remove(&iommu_group_ida, group->id);

        if (group->default_domain)
                iommu_domain_free(group->default_domain);

        kfree(group->name);
        kfree(group);
}

iommu 그룹에 embed된 kboject를 지정하여 그룹을 삭제한다.

 

그룹에 디바이스 추가 및 제거

iommu_group_add_device()

drivers/iommu/iommu.c

/**
 * iommu_group_add_device - add a device to an iommu group
 * @group: the group into which to add the device (reference should be held)
 * @dev: the device
 *
 * This function is called by an iommu driver to add a device into a
 * group.  Adding a device increments the group reference count.
 */
int iommu_group_add_device(struct iommu_group *group, struct device *dev)
{
        int ret, i = 0;
        struct group_device *device;

        device = kzalloc(sizeof(*device), GFP_KERNEL);
        if (!device)
                return -ENOMEM;

        device->dev = dev;

        ret = sysfs_create_link(&dev->kobj, &group->kobj, "iommu_group");
        if (ret)
                goto err_free_device;

        device->name = kasprintf(GFP_KERNEL, "%s", kobject_name(&dev->kobj));
rename:
        if (!device->name) {
                ret = -ENOMEM;
                goto err_remove_link;
        }

        ret = sysfs_create_link_nowarn(group->devices_kobj,
                                       &dev->kobj, device->name);
        if (ret) {
                if (ret == -EEXIST && i >= 0) {
                        /*ㅑㅐㅡ
                         * Account for the slim chance of collision
                         * and append an instance to the name.
                         */
                        kfree(device->name);
                        device->name = kasprintf(GFP_KERNEL, "%s.%d",
                                                 kobject_name(&dev->kobj), i++);
                        goto rename;
                }
                goto err_free_name;
        }

        kobject_get(group->devices_kobj);

        dev->iommu_group = group;

        iommu_group_create_direct_mappings(group, dev);

        mutex_lock(&group->mutex);
        list_add_tail(&device->list, &group->devices);
        if (group->domain)
                ret = __iommu_attach_device(group->domain, dev);
        mutex_unlock(&group->mutex);
        if (ret)
                goto err_put_group;

        /* Notify any listeners about change to group. */
        blocking_notifier_call_chain(&group->notifier,
                                     IOMMU_GROUP_NOTIFY_ADD_DEVICE, dev);

        trace_add_device_to_group(group->id, dev);

        pr_info("Adding device %s to group %d\n", dev_name(dev), group->id);

        return 0;

err_put_group:
        mutex_lock(&group->mutex);
        list_del(&device->list);
        mutex_unlock(&group->mutex);
        dev->iommu_group = NULL;
        kobject_put(group->devices_kobj);
err_free_name:
        kfree(device->name);
err_remove_link:
        sysfs_remove_link(&dev->kobj, "iommu_group");
err_free_device:
        kfree(device);
        pr_err("Failed to add device %s to group %d: %d\n", dev_name(dev), group->id, ret);
        return ret;
}

디바이스를 iommu 그룹에 추가한다. 정상적으로 추가되는 경우 0을 반환한다.

  • 코드 라인 6~8에서 그룹을 만들기 위해 group_device 구조체를 할당받는다.
  • 코드 라인 12~14에서 “iommu_group” 이라는 이름의 링크를 생성하여 그룹에 연결한다.
    • 예) “/sys/devices/platform/ff650000.vpu_service/iommu_group”
      • “/sys/kernel/iommu_groups/0” 그룹을 가리킨다.
  • 코드 라인 16~37에서 디바이스명으로 링크를 생성하여 디바이스에 연결한다.
    • 예) “/sys/kernel/iommu_groups/0/devices/ff650000.vpu_service”
      • “/sys/devices/platform/ff650000.vpu_service”를 가리킨다.
    • 이름이 중복되는 경우가 생기면 “디바이스명.0″과 같이 숫자를 붙여서 재시도한다. 숫자는 계속 증가될 수 있다.
  • 코드 라인 41에서 디바이스에 소속 그룹을 연결한다.
  • 코드 라인 43에서 그룹에 디바이스를 Direct 매핑한다.
  • 코드 라인 45~51에서 그룹 락을 획득한 채로 디바이스를 그룹의 도메인에 attach한다.
  • 코드 라인 54~55에서 그룹에 디바이스가 추가되었음을 알리도록 notifier 콜 체인에 등록한 함수들을 호출한다.
  • 코드 라인 59~61에서 디바이스가 그룹에 추가되었음을 알리는 메시지를 출력하고 정상적으로 함수를 종료한다.

 

iommu_group_remove_device()

drivers/iommu/iommu.c

/**
 * iommu_group_remove_device - remove a device from it's current group
 * @dev: device to be removed
 *
 * This function is called by an iommu driver to remove the device from
 * it's current group.  This decrements the iommu group reference count.
 */
void iommu_group_remove_device(struct device *dev)
{
        struct iommu_group *group = dev->iommu_group;
        struct group_device *tmp_device, *device = NULL;

        pr_info("Removing device %s from group %d\n", dev_name(dev), group->id);

        /* Pre-notify listeners that a device is being removed. */
        blocking_notifier_call_chain(&group->notifier,
                                     IOMMU_GROUP_NOTIFY_DEL_DEVICE, dev);

        mutex_lock(&group->mutex);
        list_for_each_entry(tmp_device, &group->devices, list) {
                if (tmp_device->dev == dev) {
                        device = tmp_device;
                        list_del(&device->list);
                        break;
                }
        }
        mutex_unlock(&group->mutex);

        if (!device)
                return;

        sysfs_remove_link(group->devices_kobj, device->name);
        sysfs_remove_link(&dev->kobj, "iommu_group");

        trace_remove_device_from_group(group->id, dev);

        kfree(device->name);
        kfree(device);
        dev->iommu_group = NULL;
        kobject_put(group->devices_kobj);
}
EXPORT_SYMBOL_GPL(iommu_group_remove_device);

디바이스를 iommu 그룹에서 제거한다.

  • 코드 라인 6에서 디바이스가 그룹에서 제거되었음을 알리는 메시지를 출력한다.
  • 코드 라인 9~10에서 iommu 그룹에서 디바이스가 제거되었음을 알리도록 notifier 콜 체인에 등록한 함수들을 호출한다.
  • 코드 라인 12~23에서 그룹에 등록된 디바이스를 제거한다.
  • 코드 라인 25에서 iommu 그룹에서 디바이스로 연결된 링크를 제거한다.
    • 예) “/sys/kernel/iommu_groups/0/devices/ff650000.vpu_service”
  • 코드 라인 26에서 디바이스 디렉토리에서 “iommu_group” 링크를 제거한다.
    • 예) “/sys/devices/platform/ff650000.vpu_service/iommu_group”
  • 코드 라인 30~34에서 group_device를 할당해제한다.

 

그룹내 디바이스들의 attach/detach

iommu_attach_group()

drivers/iommu/iommu.c

int iommu_attach_group(struct iommu_domain *domain, struct iommu_group *group)
{
        int ret;

        mutex_lock(&group->mutex);
        ret = __iommu_attach_group(domain, group);
        mutex_unlock(&group->mutex);

        return ret;
}
EXPORT_SYMBOL_GPL(iommu_attach_group);

iommu 그룹 락을 소유한 채로 iommu 그룹에 포함된 디바이스들을 요청한 iommu 도메인에 attach 한다.

 

iommu_attach_device()

drivers/iommu/iommu.c

int iommu_attach_device(struct iommu_domain *domain, struct device *dev)
{
        struct iommu_group *group;
        int ret;

        group = iommu_group_get(dev);
        if (!group)
                return -ENODEV;

        /*
         * Lock the group to make sure the device-count doesn't
         * change while we are attaching
         */
        mutex_lock(&group->mutex);
        ret = -EINVAL;
        if (iommu_group_device_count(group) != 1)
                goto out_unlock;

        ret = __iommu_attach_group(domain, group);

out_unlock:
        mutex_unlock(&group->mutex);
        iommu_group_put(group);

        return ret;
}
EXPORT_SYMBOL_GPL(iommu_attach_device);

iommu 그룹 락을 소유한 채로 iommu 그룹에 포함된 디바이스들을 요청한 iommu 도메인에 attach 한다.

  • 코드 라인 16~17에서 iommu 그룹에 포함된 디바이스가 하나도 없는 경우 실패 결과로 함수를 빠져나간다.
  • 코드 라인 19에서 iommu 그룹에 포함된 디바이스들을 요청한 iommu 도메인에 attach 한다.

 

__iommu_attach_group()

drivers/iommu/iommu.c

static int __iommu_attach_group(struct iommu_domain *domain,
                                struct iommu_group *group)
{
        int ret;

        if (group->default_domain && group->domain != group->default_domain)
                return -EBUSY;

        ret = __iommu_group_for_each_dev(group, domain,
                                         iommu_group_do_attach_device);
        if (ret == 0)
                group->domain = domain;

        return ret;
}

요청한 iommu 그룹(@group)에 포함된 디바이스들을 요청한 iommu 도메인(@domain)에 attach 한다.

 

__iommu_group_for_each_dev()

drivers/iommu/iommu.c

/**
 * iommu_group_for_each_dev - iterate over each device in the group
 * @group: the group
 * @data: caller opaque data to be passed to callback function
 * @fn: caller supplied callback function
 *
 * This function is called by group users to iterate over group devices.
 * Callers should hold a reference count to the group during callback.
 * The group->mutex is held across callbacks, which will block calls to
 * iommu_group_add/remove_device.
 */
static int __iommu_group_for_each_dev(struct iommu_group *group, void *data,
                                      int (*fn)(struct device *, void *))
{
        struct group_device *device;
        int ret = 0;

        list_for_each_entry(device, &group->devices, list) {
                ret = fn(device->dev, data);
                if (ret)
                        break;
        }
        return ret;
}

요청한 iommu 그룹(@group)에 포함된 디바이스들을 대상으로 함수(fn)을 호출한다. 호출 할 때 인자 @data를 전달한다.

 

iommu_group_do_attach_device()

drivers/iommu/iommu.c

/*
 * IOMMU groups are really the natural working unit of the IOMMU, but
 * the IOMMU API works on domains and devices.  Bridge that gap by
 * iterating over the devices in a group.  Ideally we'd have a single
 * device which represents the requestor ID of the group, but we also
 * allow IOMMU drivers to create policy defined minimum sets, where
 * the physical hardware may be able to distiguish members, but we
 * wish to group them at a higher level (ex. untrusted multi-function
 * PCI devices).  Thus we attach each device.
 */
static int iommu_group_do_attach_device(struct device *dev, void *data)
{
        struct iommu_domain *domain = data;

        return __iommu_attach_device(domain, dev);
}

디바이스를 도메인에 attach 한다.

 

__iommu_attach_device()

drivers/iommu/iommu.c

static int __iommu_attach_device(struct iommu_domain *domain,
                                 struct device *dev)
{
        int ret;
        if ((domain->ops->is_attach_deferred != NULL) &&
            domain->ops->is_attach_deferred(domain, dev))
                return 0;

        if (unlikely(domain->ops->attach_dev == NULL))
                return -ENODEV;

        ret = domain->ops->attach_dev(domain, dev);
        if (!ret)
                trace_attach_device_to_domain(dev);
        return ret;
}

디바이스를 도메인에 attach 하기 위해 iommu 도메인에 등록된 (*attach_dev)  후크 함수를 호출한다.

  • amd iommu의 경우 iommu 드라이버의 (*is_attach_deferred) 함수를 호출하여 유예되는 case에서는 함수를 그냥 정상 종료시킨다.

 

arm_smmu_attach_dev() – compatible=”arm,mmu-500″

drivers/iommu/arm-smmu.c

static int arm_smmu_attach_dev(struct iommu_domain *domain, struct device *dev)
{
        int ret;
        struct iommu_fwspec *fwspec = dev_iommu_fwspec_get(dev);
        struct arm_smmu_device *smmu;
        struct arm_smmu_domain *smmu_domain = to_smmu_domain(domain);

        if (!fwspec || fwspec->ops != &arm_smmu_ops) {
                dev_err(dev, "cannot attach to SMMU, is it on the same bus?\n");
                return -ENXIO;
        }

        /*
         * FIXME: The arch/arm DMA API code tries to attach devices to its own
         * domains between of_xlate() and add_device() - we have no way to cope
         * with that, so until ARM gets converted to rely on groups and default
         * domains, just say no (but more politely than by dereferencing NULL).
         * This should be at least a WARN_ON once that's sorted.
         */
        if (!fwspec->iommu_priv)
                return -ENODEV;

        smmu = fwspec_smmu(fwspec);

        ret = arm_smmu_rpm_get(smmu);
        if (ret < 0)
                return ret;

        /* Ensure that the domain is finalised */
        ret = arm_smmu_init_domain_context(domain, smmu);
        if (ret < 0)
                goto rpm_put;

        /*
         * Sanity check the domain. We don't support domains across
         * different SMMUs.
         */
        if (smmu_domain->smmu != smmu) {
                dev_err(dev,
                        "cannot attach to SMMU %s whilst already attached to domain on SMMU %s\n",
                        dev_name(smmu_domain->smmu->dev), dev_name(smmu->dev));
                ret = -EINVAL;
                goto rpm_put;
        }

        /* Looks ok, so add the device to the domain */
        ret = arm_smmu_domain_add_master(smmu_domain, fwspec);

rpm_put:
        arm_smmu_rpm_put(smmu);
        return ret;
}

smmu 도메인에 디바이스를 attach 한다.

  • 코드 라인 4에서 디바이스가 디바이스 트리를 통해 iommu(smmu) 드라이버에 전달할 가변 인자값들을 알아온다.
  • 코드 라인 8~11에서 fwspec이 SMMU 드라이버와 attach하지 않은 경우 에러를 반환한다.
  • 코드 라인 20~21에서 smmu 마스터 정보가 없는 경우 에러를 반환한다.
  • 코드 라인 23에서 smmu가 절전 상태인 경우 깨운다.
  • 코드 라인 26~28에서 smmu의 페이지 테이블을 구성하고 매핑을 동작시킨다.
  • 코드 라인 34~40에서 도메인에 smmu 디바이스를 가리키지 않는 경우 에러 메시지를 출력하고 함수를 빠져나간다.
  • 코드 라인 43에서 디바이스를 도메인에 추가한다.

 

다음은 iommu  각 그룹에 attach된 디바이스들을 보여준다.

find /sys/kernel/iommu_groups/ -type l
/sys/kernel/iommu_groups/0/devices/ff650000.vpu_service
/sys/kernel/iommu_groups/1/devices/ff660000.rkvdec
/sys/kernel/iommu_groups/2/devices/ff8f0000.vop
/sys/kernel/iommu_groups/3/devices/ff900000.vop
/sys/kernel/iommu_groups/4/devices/ff910000.cif_isp

 

IOMMU 매핑

iommu_map()

drivers/iommu/iommu.c

int iommu_map(struct iommu_domain *domain, unsigned long iova,
              phys_addr_t paddr, size_t size, int prot)
{
        unsigned long orig_iova = iova;
        unsigned int min_pagesz;
        size_t orig_size = size;
        phys_addr_t orig_paddr = paddr;
        int ret = 0;

        if (unlikely(domain->ops->map == NULL ||
                     domain->pgsize_bitmap == 0UL))
                return -ENODEV;

        if (unlikely(!(domain->type & __IOMMU_DOMAIN_PAGING)))
                return -EINVAL;

        /* find out the minimum page size supported */
        min_pagesz = 1 << __ffs(domain->pgsize_bitmap);

        /*
         * both the virtual address and the physical one, as well as
         * the size of the mapping, must be aligned (at least) to the
         * size of the smallest page supported by the hardware
         */
        if (!IS_ALIGNED(iova | paddr | size, min_pagesz)) {
                pr_err("unaligned: iova 0x%lx pa %pa size 0x%zx min_pagesz 0x%x\n",
                       iova, &paddr, size, min_pagesz);
                return -EINVAL;
        }

        pr_debug("map: iova 0x%lx pa %pa size 0x%zx\n", iova, &paddr, size);

        while (size) {
                size_t pgsize = iommu_pgsize(domain, iova | paddr, size);

                pr_debug("mapping: iova 0x%lx pa %pa pgsize 0x%zx\n",
                         iova, &paddr, pgsize);

                ret = domain->ops->map(domain, iova, paddr, pgsize, prot);
                if (ret)
                        break;

                iova += pgsize;
                paddr += pgsize;
                size -= pgsize;
        }

        /* unroll mapping in case something went wrong */
        if (ret)
                iommu_unmap(domain, orig_iova, orig_size - size);
        else
                trace_map(orig_iova, orig_paddr, orig_size);

        return ret;
}
EXPORT_SYMBOL_GPL(iommu_map);

가상 주소 @iova를 물리 주소 @paddr로 @size 만큼 변환하도록 매핑을 요청한다.  정상 매핑된 경우 0을 반환한다.

  • 코드 라인 10~12에서 (*map) 후크 함수가 없거나 사이즈가 지정되지 않은 경우 에러 결과로 함수를 빠져나간다.
  • 코드 라인 14~15에서 매핑 변환 플래그가 없는 요청인 경우 에러 결과로 함수를 빠져나간다.
  • 코드 라인 18에서 지원하는 페이지 사이즈 중 가장 작은 단위의 페이지 사이즈를 알아온다.
    • 예) pgsize_bitmap=0x10_1000
      • 1M 페이지와 4K 페이지를 지원
  • 코드 라인 25~29에서 가상 주소와 물리 주소 및 사이즈 가 최소 페이지 단위로 정렬되지 않은 경우 에러 결과로 함수를 빠져나간다.
  • 코드 라인 31에서 iommu 매핑 정보를 디버그 레벨로 출력한다.
  • 코드 라인 33~46에서 @size 만큼 매핑을 하기 위해 페이지 사이즈 단위로 매핑을 하며 반복한다.
  • 코드 라인 39~44에서 매핑이 실패한 경우 언맵을 수행하고, 정상 처리가 된 경우 0을 반환한다.

 

drivers/iommu/arm-smmu.c – ARM & ARM64

static int arm_smmu_map(struct iommu_domain *domain, unsigned long iova,
                        phys_addr_t paddr, size_t size, int prot)
{
        struct io_pgtable_ops *ops = to_smmu_domain(domain)->pgtbl_ops;
        struct arm_smmu_device *smmu = to_smmu_domain(domain)->smmu;
        int ret;

        if (!ops)
                return -ENODEV;

        arm_smmu_rpm_get(smmu);
        ret = ops->map(ops, iova, paddr, size, prot);
        arm_smmu_rpm_put(smmu);

        return ret;
}

smmu용 페이지 테이블 드라이버를 통해 가상 주소 @iova 를 물리 주소 @paddr로 @size 만큼 매핑한다.

 

arm_lpae_map() – ARM & ARM64

drivers/iommu/io-pgtable-arm.c

static int arm_lpae_map(struct io_pgtable_ops *ops, unsigned long iova,
                        phys_addr_t paddr, size_t size, int iommu_prot)
{
        struct arm_lpae_io_pgtable *data = io_pgtable_ops_to_data(ops);
        arm_lpae_iopte *ptep = data->pgd;
        int ret, lvl = ARM_LPAE_START_LVL(data);
        arm_lpae_iopte prot;

        /* If no access, then nothing to do */
        if (!(iommu_prot & (IOMMU_READ | IOMMU_WRITE)))
                return 0;

        if (WARN_ON(iova >= (1ULL << data->iop.cfg.ias) ||
                    paddr >= (1ULL << data->iop.cfg.oas)))
                return -ERANGE;

        prot = arm_lpae_prot_to_pte(data, iommu_prot);
        ret = __arm_lpae_map(data, iova, paddr, size, prot, lvl, ptep);
        /*
         * Synchronise all PTE updates for the new mapping before there's
         * a chance for anything to kick off a table walk for the new iova.
         */
        wmb();

        return ret;
}

smmu용 페이지 테이블 드라이버를 통해 가상 주소 @iova 를 물리 주소 @paddr로 @size 만큼 매핑한다.

  • 코드 라인 10~11에서 페이지 속성 @iommu_prot에 read 또는 write 속성이 지정되지 않은 경우 아무 일도 하지 않고 성공(0)을 반환한다.
  • 코드 라인 13~15에서 @iova 및 @paddr이 smmu가 처리할 수 있는 영역을 벗어난 경우 -ERANGE 에러를 반환한다.
  • 코드 라인 17에서 @iommu_prot를 smmu의 pte 엔트리에 기록할 속성으로 변환한다.
  • 코드 라인 18에서 시작 페이지 테이블 레벨부터 smmu를 위해 매핑 테이블을 구성한다.

 

__arm_lpae_map() – ARM & ARM64

drivers/iommu/io-pgtable-arm.c

static int __arm_lpae_map(struct arm_lpae_io_pgtable *data, unsigned long iova,
                          phys_addr_t paddr, size_t size, arm_lpae_iopte prot,
                          int lvl, arm_lpae_iopte *ptep)
{
        arm_lpae_iopte *cptep, pte;
        size_t block_size = ARM_LPAE_BLOCK_SIZE(lvl, data);
        size_t tblsz = ARM_LPAE_GRANULE(data);
        struct io_pgtable_cfg *cfg = &data->iop.cfg;

        /* Find our entry at the current level */
        ptep += ARM_LPAE_LVL_IDX(iova, lvl, data);

        /* If we can install a leaf entry at this level, then do so */
        if (size == block_size && (size & cfg->pgsize_bitmap))
                return arm_lpae_init_pte(data, iova, paddr, prot, lvl, ptep);

        /* We can't allocate tables at the final level */
        if (WARN_ON(lvl >= ARM_LPAE_MAX_LEVELS - 1))
                return -EINVAL;

        /* Grab a pointer to the next level */
        pte = READ_ONCE(*ptep);
        if (!pte) {
                cptep = __arm_lpae_alloc_pages(tblsz, GFP_ATOMIC, cfg);
                if (!cptep)
                        return -ENOMEM;

                pte = arm_lpae_install_table(cptep, ptep, 0, cfg);
                if (pte)
                        __arm_lpae_free_pages(cptep, tblsz, cfg);
        } else if (!(cfg->quirks & IO_PGTABLE_QUIRK_NO_DMA) &&
                   !(pte & ARM_LPAE_PTE_SW_SYNC)) {
                __arm_lpae_sync_pte(ptep, cfg);
        }

        if (pte && !iopte_leaf(pte, lvl)) {
                cptep = iopte_deref(pte, data);
        } else if (pte) {
                /* We require an unmap first */
                WARN_ON(!selftest_running);
                return -EEXIST;
        }

        /* Rinse, repeat */
        return __arm_lpae_map(data, iova, paddr, size, prot, lvl + 1, cptep);
}

smmu용 페이지 테이블을 첫 레벨부터 마지막 레벨의 엔트리까지  드라이버를 통해 가상 주소 @iova 를 물리 주소 @paddr로 @size 만큼 매핑한다.

  • 코드 라인 6에서 @lvl에 해당하는 블럭 사이즈를 구해온다.
    • 4K 페이지 테이블을 사용하는 경우 가장 마지막 레벨에서는 블럭 크기는 4K이다.
  • 코드 라인 7에서 할당 준비할 테이블 크기를 구한다.
  • 코드 라인 11에서 @lvl에 해당하는 페이지 테이블의 @iova에 해당하는 엔트리 주소를  알아온다.
  • 코드 라인 14~15에서 만일 매핑 범위가 최종 페이지 테이블의 pte 엔트리인 경우 해당 pte를 갱신한다.
  • 코드 라인 18~19에서 이 함수는 마지막 페이지 테이블 레벨까지 재귀 호출된다. 마지막 테이블 레벨까지 이미 처리한 경우 -EINVAL 결과로 함수를 빠져나간다.
  • 코드 라인 22에서 연결된 페이지 테이블을 과 연결된 엔트리 값을 읽어온다.
  • 코드 라인 23~30에서 엔트리가 구성되지 않은 상태면 다음 단계용 페이지 테이블을 할당하고 할당한 테이블을 가리키도록 엔트리를 수정한다.코드 라인 31~34에서 만일 NO_DMA 요청을 사용하지 않으면서 pte에 대한 SW 싱크 플래그가 없으면 누군가 수정 중이므로 coherency sync를 수행한다.
  • 코드 라인 36~42에서 leaf 레벨의 pte가 아닌 경우 pte 엔트리가 가리키는 가상 주소 값을 알아온다. 만일 pte 엔트리가 이미 매핑된 경우 -EEXIST 에러를 반환한다.
  • 코드 라인 45에서 다음 레벨로 바꾸고 이 함수를 다시 재귀호출한다.

 

arm_lpae_install_table() – ARM & ARM64

drivers/iommu/io-pgtable-arm.c

static arm_lpae_iopte arm_lpae_install_table(arm_lpae_iopte *table,
                                             arm_lpae_iopte *ptep,
                                             arm_lpae_iopte curr,
                                             struct io_pgtable_cfg *cfg)
{
        arm_lpae_iopte old, new;

        new = __pa(table) | ARM_LPAE_PTE_TYPE_TABLE;
        if (cfg->quirks & IO_PGTABLE_QUIRK_ARM_NS)
                new |= ARM_LPAE_PTE_NSTABLE;

        /*
         * Ensure the table itself is visible before its PTE can be.
         * Whilst we could get away with cmpxchg64_release below, this
         * doesn't have any ordering semantics when !CONFIG_SMP.
         */
        dma_wmb();

        old = cmpxchg64_relaxed(ptep, curr, new);

        if ((cfg->quirks & IO_PGTABLE_QUIRK_NO_DMA) ||
            (old & ARM_LPAE_PTE_SW_SYNC))
                return old;

        /* Even if it's not ours, there's no point waiting; just kick it */
        __arm_lpae_sync_pte(ptep, cfg);
        if (old == curr)
                WRITE_ONCE(*ptep, new | ARM_LPAE_PTE_SW_SYNC);

        return old;
}

smmu용 페이지 테이블 엔트리 @ptep와 요청한 페이지 테이블 @table을 연결하여 기록한다.

  • 코드 라인 8에서 엔트리에 기록하고자 하는 값으로 새로 할당받은 테이블의 물리 주소와 테이블 플래그를 추가한다.
  • 코드 라인 9~10에서 non-secure quirk 플래그로 요청된 경우 기록할 값에도 non-secure 테이블 플래그를 추가한다.
  • 코드 라인 17~19에서 베리어를 먼저 수행한 후 엔트리를 교체한다.
  • 코드 라인 21~23에서 no dma 요청이었거나 기존 값에 sw sync가 설정된 경우 기존 엔트리를 반환한다.
    • ARM_LPAE_PTE_SW_SYNC
      • coherency race를 방지하기 위해 기록한다.
        • 기존 엔트리 값에 sw sync 플래그가 기록된 경우 엔트리 변경이 완벽히 완료된 상태이다.
        • 기존 엔트리값에 sw sync 플래그가 없는 경우 누군가 이 엔트리를 변경 중인 상태이다.
  • 코드 라인 26에서 아직 sync 되지 않은 상태이다. 따라서 다른 modifier를 위해 coherency sync를 수행한다.
  • 코드 라인 27~28에서 cmpxchg64 명령에서 경쟁 상황 없이 변경이 이루어진 경우 sw sync 플래그를 추가로 기록한다.

 

iommu_map_sg()

drivers/iommu/iommu.c

size_t iommu_map_sg(struct iommu_domain *domain, unsigned long iova,
                    struct scatterlist *sg, unsigned int nents, int prot)
{
        size_t len = 0, mapped = 0;
        phys_addr_t start;
        unsigned int i = 0;
        int ret;

        while (i <= nents) {
                phys_addr_t s_phys = sg_phys(sg);

                if (len && s_phys != start + len) {
                        ret = iommu_map(domain, iova + mapped, start, len, prot);
                        if (ret)
                                goto out_err;

                        mapped += len;
                        len = 0;
                }

                if (len) {
                        len += sg->length;
                } else {
                        len = sg->length;
                        start = s_phys;
                }

                if (++i < nents)
                        sg = sg_next(sg);
        }

        return mapped;

out_err:
        /* undo mappings already done */
        iommu_unmap(domain, iova, mapped);

        return 0;

}
EXPORT_SYMBOL_GPL(iommu_map_sg);

 

참고

 

2 thoughts to “IOMMU”

  1. 안녕하세요 Domain attribute 중 DOMAIN_ATTR_FAST 관련하여 질문이 있어 댓글 남겨봅니다.
    DOMAIN_ATTR_FAST 을 설정하고, map_page를 요청하였을 경우엔 설정하지 않았을 때와 다르게
    smmu용 페이지 테이블에 해당 영역를 맵핑시키기 위한 arm_smmu_map 함수가 호출되지 않는 것으로 보입니다.
    그렇다면 map_page에서 리턴해주는 영역은 smmu에 언제 어떻게 맵핑이 되는 것인지 궁금합니다.

    스터디하는데 항상 많은 도움을 받고 있습니다. 감사합니다.

    1. 안녕하세요?

      DOMAIN_ATTR_FAST 속성은 아직 표준 커널에 존재하지 않네요?
      검색해보니 스마트폰 관련 커널에 추가된 속성으로 보입니다.

      1) iommu 장치 등록 관련

      smmu 드라이버가 정상적으로 등록되는지 다음들을 체크해보세요. (smmu-v1 기준)
      – 디바이스 트리 지정 여부 – compatible = “arm,mmu-401”, “arm,smmu-v1”;
      – smmu 드라이버 호출 여부 – arm_smmu_device_probe()
      – iommu 서브시스템에 등록 – iommu_device_register() 호출 여부
      – iommu_map()이 호출된 후 arm_smmu_map()으로 향하는지 디버깅해보시기 바랍니다.

      ———————-

      2) iommu를 사용하는 dma 장치 관련

      iommu를 통해 dma를 사용하는 장치들은 디바이스 트리에 iommus = 속성이 지정되어야 합니다.
      dma-iommu API를 통해 iommu 장치에 매핑을 시도할 때 iommu_map()이 호출될 때까지의 함수 호출 관계를 살펴보세요.

      감사합니다.

문영일에게 댓글 남기기 댓글 취소

이메일은 공개되지 않습니다. 필수 입력창은 * 로 표시되어 있습니다