我叫老中医 发表于 2023-11-11 16:52:23

深入浅出 Linux 中的 ARM IOMMU SMMU I

Linux 系统下的 SMMU 介绍

在计算机系统架构中,与传统的用于 CPU 访问内存的管理的 MMU 类似,IOMMU (Input Output Memory Management Unit) 将来自系统 I/O 设备的 DMA 请求传递到系统互连之前,它会先转换请求的地址,并对系统 I/O 设备的内存访问事务进行管理和限制。IOMMU 将设备可见的虚拟地址 (IOVA) 映射到物理内存地址。不同的硬件体系结构有不同的 IOMMU 实现,ARM 平台的 IOMMU 是 SMMU (System Memory Management)。
SMMU 只为来自系统 I/O 设备的内存访问事务提供转换服务,而不为到系统 I/O 设备的事务提供转换服务。从系统或 CPU 到系统 I/O 设备的事务由其它方式管理,例如 MMU。下图展示了 SMMU 在系统中的角色。
https://upload-images.jianshu.io/upload_images/1315506-a1a65726e5f2e118.png
来自系统 I/O 设备的内存访问事务指系统 I/O 设备对内存的读写,到系统 I/O 设备的事务通常指 CPU 访问系统 I/O 设备内部映射到物理内存地址空间的存储器或寄存器。关于 SMMU 更详细的介绍,可以参考 IOMMU和Arm SMMU介绍 及 SMMU 软件指南。关于 SMMU 的寄存器、数据结构和行为的详细描述,可以参考 ARM 系统内存管理单元架构规范版本 3。关于 SMMU 的具体实现,可以参考相关实现的文档,如 MMU-600 的 Arm CoreLink MMU-600 系统内存管理单元技术参考手册 和 MMU-700 的 Arm® CoreLink™ MMU-700 系统内存管理单元技术参考手册。
SMMU 通过 StreamID 等区分不同的系统 I/O 设备,系统 I/O 设备在通过 SMMU 访问内存时,需要将 StreamID 等信息带给 SMMU。从系统 I/O 设备的角度来看,包含 SMMU 的系统更精细的结构如下图:
https://upload-images.jianshu.io/upload_images/1315506-2a45b1e6b5a52941.png
系统 I/O 设备通过 DMA 访问内存,DMA 请求发出后,在送到 SMMU 和系统互联之前,先要经过一个称为 DAA (对于其它实现,可能是其它设备) 的设备,DAA 做第一次地址转换,之后将内存访问请求信息,包括配置的 StreamID 等送进 SMMU,以做进一步的处理。
在 Linux 系统中,要为某个系统 I/O 设备开启 SMMU,一般要经过如下步骤:

[*]SMMU 驱动程序的初始化。这主要包括读取 dts 文件中的,SMMU 设备节点,探测 SMMU 的硬件设备特性,初始化全局资源及数据结构,如命令队列、事件队列、中断,和流表等,并将 SMMU 设备注册进 Linux 内核的 IOMMU 子系统。
[*]系统 I/O 设备探测、发现,并和驱动程序绑定初始化的过程中,设备和 IOMMU 的绑定。对于使用 DMA 来访问内存的设备,这一般通过调用 of_dma_configure()/of_dma_configure_id() 函数完成。设备探测、发现,并和驱动程序绑定初始化的过程,需要访问设备树 dts 文件里,设备节点定义中与 IOMMU 配置相关的字段。如在 arch/arm64/boot/dts/renesas/r8a77961.dtsi 文件中:
                        iommus = <&ipmmu_vc0 19>;
[*]系统 I/O 设备驱动程序关于 IOMMU 的配置。这部分通常因具体的硬件系统实现而异。这主要包括调用 dma_coerce_mask_and_coherent()/dma_set_mask_and_coherent() 函数将 DMA 掩码和一致性 DMA 掩码设置为相同的值,以及配置类似前面提到的 DAA 之类的设备。
[*]系统 I/O 设备驱动程序分配内存。系统 I/O 设备驱动程序通过 dma_alloc_coherent() 等接口分配内存,这个过程除了分配内存外,还会通过 SMMU 设备驱动程序的操作函数,创建地址转换表,并完成 SMMU CD 等数据结构的设置。在 Linux 内核中,不同的子系统实际调用的分配 DMA 内存的方法不同,但最终都需要调用 dma_alloc_coherent() 函数,这样分配的内存,在通过 DMA 访问时,才会经过 SMMU。
[*]访问分配的内存。通过 dma_alloc_coherent() 函数分配到的内存,其地址可以提供给系统 I/O 设备的 DMA 配置相关逻辑,后续系统 I/O 设备通过 DMA 访问内存,将经过 SMMU 完成地址转换。通过 DMA 访问内存时,将经过 SMMU 的地址转换。
SMMU 的地址转换借助于相关的数据结构完成,这主要包括流表及其流表项 STE,上下文描述符表及其表项 CD,和地址转换表及其表项。STE 存储流的上下文信息,每个 STE 64 字节。CD 存储了与第 1 阶段转换有关的所有设置,每个 CD 64 字节。地址转换表用于描述虚拟地址和物理内存地址之间的映射关系。流表的结构可以分为 线性流表 和 2 级流表 两种。线性流表结构如下图:
https://upload-images.jianshu.io/upload_images/1315506-df0cc7a06e452575.png
2 级流表示例结构如下图:
https://upload-images.jianshu.io/upload_images/1315506-3861c2c7ebd79a8e.png
上下文描述符表的结构可以分为 单个 CD,单级 CD 表 和 2 级 CD 表 三种情况。单个 CD 示例结构如下图:
https://upload-images.jianshu.io/upload_images/1315506-b6dc4d5cb00d8242.png
单级 CD 表示例结构如下图:
https://upload-images.jianshu.io/upload_images/1315506-0876894aa8a348cb.png
2 级 CD 表示例结构如下图:
https://upload-images.jianshu.io/upload_images/1315506-ad4d486bdff68dcb.png
SMMU 在做地址转换时,根据 SMMU 的流表基址寄存器找到流表,通过 StreamID 在流表中找到 STE。之后根据 STE 的配置和 SubstreamID/PASID,找到上下文描述符表及对应的 CD。再根据 CD 中的信息找到地址转换表,并通过地址转换表完成最终的地址转换。
Linux 内核的 IOMMU 子系统相关源码位于 drivers/iommu,ARM SMMU 驱动实现位于 drivers/iommu/arm/arm-smmu-v3。在 Linux 内核的 SMMU 驱动实现中,做地址转换所用到的数据结构,在上面提到的不同步骤中创建:

[*]流表在 SMMU 驱动程序的初始化过程中创建。如果流表的结构为线性流表,则线性流表中所有的 STE 都被配置为旁路 SMMU,即对应的流不做 SMMU 地址转换;如果流表的结构为 2 级流表,则流表中为无效的 L1 流表描述符。
[*]系统 I/O 设备发现、探测,并和驱动程序绑定初始化的过程中,设备和 IOMMU 绑定时,创建上下文描述符表。如果流表的结构为 2 级流表,这个过程会先创建第 2 级流表,第 2 级流表中的 STE 都被配置为旁路 SMMU。创建上下文描述符表时,同样分是否需要 2 级上下文描述符表来执行。上下文描述符表创建之后,其地址被写入 STE。
[*]系统 I/O 设备驱动程序分配内存的过程中创建地址转换表。这个过程中,SMMU 驱动程序的回调会被调用,以将地址转换表的地址放进 CD 中。
Linux 内核中 SMMU 的数据结构

Linux 内核的 IOMMU 子系统用 struct iommu_device 结构体表示一个 IOMMU 硬件设备实例,并用 struct iommu_ops 结构体描述 IOMMU 硬件设备实例支持的操作和能力,这两个结构体定义 (位于 include/linux/iommu.h 文件中) 如下:
/**
* struct iommu_ops - iommu ops and capabilities
* @capable: check capability
* @domain_alloc: allocate iommu domain
* @domain_free: free iommu domain
* @attach_dev: attach device to an iommu domain
* @detach_dev: detach device from an iommu domain
* @map: map a physically contiguous memory region to an iommu domain
* @unmap: unmap a physically contiguous memory region from an iommu domain
* @flush_iotlb_all: Synchronously flush all hardware TLBs for this domain
* @iotlb_sync_map: Sync mappings created recently using @map to the hardware
* @iotlb_sync: Flush all queued ranges from the hardware TLBs and empty flush
*            queue
* @iova_to_phys: translate iova to physical address
* @probe_device: Add device to iommu driver handling
* @release_device: Remove device from iommu driver handling
* @probe_finalize: Do final setup work after the device is added to an IOMMU
*                  group and attached to the groups domain
* @device_group: find iommu group for a particular device
* @domain_get_attr: Query domain attributes
* @domain_set_attr: Change domain attributes
* @support_dirty_log: Check whether domain supports dirty log tracking
* @switch_dirty_log: Perform actions to start|stop dirty log tracking
* @sync_dirty_log: Sync dirty log from IOMMU into a dirty bitmap
* @clear_dirty_log: Clear dirty log of IOMMU by a mask bitmap
* @get_resv_regions: Request list of reserved regions for a device
* @put_resv_regions: Free list of reserved regions for a device
* @apply_resv_region: Temporary helper call-back for iova reserved ranges
* @domain_window_enable: Configure and enable a particular window for a domain
* @domain_window_disable: Disable a particular window for a domain
* @of_xlate: add OF master IDs to iommu grouping
* @is_attach_deferred: Check if domain attach should be deferred from iommu
*                      driver init to device driver init (default no)
* @dev_has/enable/disable_feat: per device entries to check/enable/disable
*                               iommu specific features.
* @dev_feat_enabled: check enabled feature
* @aux_attach/detach_dev: aux-domain specific attach/detach entries.
* @aux_get_pasid: get the pasid given an aux-domain
* @sva_bind: Bind process address space to device
* @sva_unbind: Unbind process address space from device
* @sva_get_pasid: Get PASID associated to a SVA handle
* @page_response: handle page request response
* @cache_invalidate: invalidate translation caches
* @sva_bind_gpasid: bind guest pasid and mm
* @sva_unbind_gpasid: unbind guest pasid and mm
* @def_domain_type: device default domain type, return value:
*                - IOMMU_DOMAIN_IDENTITY: must use an identity domain
*                - IOMMU_DOMAIN_DMA: must use a dma domain
*                - 0: use the default setting
* @attach_pasid_table: attach a pasid table
* @detach_pasid_table: detach the pasid table
* @pgsize_bitmap: bitmap of all possible supported page sizes
* @owner: Driver module providing these ops
*/
struct iommu_ops {
        bool (*capable)(enum iommu_cap);

        /* Domain allocation and freeing by the iommu driver */
        struct iommu_domain *(*domain_alloc)(unsigned iommu_domain_type);
        void (*domain_free)(struct iommu_domain *);

        int (*attach_dev)(struct iommu_domain *domain, struct device *dev);
        void (*detach_dev)(struct iommu_domain *domain, struct device *dev);
        int (*map)(struct iommu_domain *domain, unsigned long iova,
                   phys_addr_t paddr, size_t size, int prot, gfp_t gfp);
        size_t (*unmap)(struct iommu_domain *domain, unsigned long iova,
                     size_t size, struct iommu_iotlb_gather *iotlb_gather);
        void (*flush_iotlb_all)(struct iommu_domain *domain);
        void (*iotlb_sync_map)(struct iommu_domain *domain, unsigned long iova,
                             size_t size);
        void (*iotlb_sync)(struct iommu_domain *domain,
                           struct iommu_iotlb_gather *iotlb_gather);
        phys_addr_t (*iova_to_phys)(struct iommu_domain *domain, dma_addr_t iova);
        struct iommu_device *(*probe_device)(struct device *dev);
        void (*release_device)(struct device *dev);
        void (*probe_finalize)(struct device *dev);
        struct iommu_group *(*device_group)(struct device *dev);
        int (*domain_get_attr)(struct iommu_domain *domain,
                             enum iommu_attr attr, void *data);
        int (*domain_set_attr)(struct iommu_domain *domain,
                             enum iommu_attr attr, void *data);

        /*
       * Track dirty log. Note: Don't concurrently call these interfaces with
       * other ops that access underlying page table.
       */
        bool (*support_dirty_log)(struct iommu_domain *domain);
        int (*switch_dirty_log)(struct iommu_domain *domain, bool enable,
                                unsigned long iova, size_t size, int prot);
        int (*sync_dirty_log)(struct iommu_domain *domain,
                              unsigned long iova, size_t size,
                              unsigned long *bitmap, unsigned long base_iova,
                              unsigned long bitmap_pgshift);
        int (*clear_dirty_log)(struct iommu_domain *domain,
                             unsigned long iova, size_t size,
                             unsigned long *bitmap, unsigned long base_iova,
                             unsigned long bitmap_pgshift);

        /* Request/Free a list of reserved regions for a device */
        void (*get_resv_regions)(struct device *dev, struct list_head *list);
        void (*put_resv_regions)(struct device *dev, struct list_head *list);
        void (*apply_resv_region)(struct device *dev,
                                  struct iommu_domain *domain,
                                  struct iommu_resv_region *region);

        /* Window handling functions */
        int (*domain_window_enable)(struct iommu_domain *domain, u32 wnd_nr,
                                  phys_addr_t paddr, u64 size, int prot);
        void (*domain_window_disable)(struct iommu_domain *domain, u32 wnd_nr);

        int (*of_xlate)(struct device *dev, struct of_phandle_args *args);
        bool (*is_attach_deferred)(struct iommu_domain *domain, struct device *dev);

        /* Per device IOMMU features */
        bool (*dev_has_feat)(struct device *dev, enum iommu_dev_features f);
        bool (*dev_feat_enabled)(struct device *dev, enum iommu_dev_features f);
        int (*dev_enable_feat)(struct device *dev, enum iommu_dev_features f);
        int (*dev_disable_feat)(struct device *dev, enum iommu_dev_features f);

        /* Aux-domain specific attach/detach entries */
        int (*aux_attach_dev)(struct iommu_domain *domain, struct device *dev);
        void (*aux_detach_dev)(struct iommu_domain *domain, struct device *dev);
        int (*aux_get_pasid)(struct iommu_domain *domain, struct device *dev);

        struct iommu_sva *(*sva_bind)(struct device *dev, struct mm_struct *mm,
                                      void *drvdata);
        void (*sva_unbind)(struct iommu_sva *handle);
        u32 (*sva_get_pasid)(struct iommu_sva *handle);

        int (*page_response)(struct device *dev,
                             struct iommu_fault_event *evt,
                             struct iommu_page_response *msg);
        int (*cache_invalidate)(struct iommu_domain *domain, struct device *dev,
                                struct iommu_cache_invalidate_info *inv_info);
        int (*sva_bind_gpasid)(struct iommu_domain *domain,
                        struct device *dev, struct iommu_gpasid_bind_data *data);

        int (*sva_unbind_gpasid)(struct device *dev, u32 pasid);
        int (*attach_pasid_table)(struct iommu_domain *domain,
                                  struct iommu_pasid_table_config *cfg);
        void (*detach_pasid_table)(struct iommu_domain *domain);

        int (*def_domain_type)(struct device *dev);
        int (*dev_get_config)(struct device *dev, int type, void *data);
        int (*dev_set_config)(struct device *dev, int type, void *data);

        unsigned long pgsize_bitmap;
        struct module *owner;
};

/**
* struct iommu_device - IOMMU core representation of one IOMMU hardware
*                       instance
* @list: Used by the iommu-core to keep a list of registered iommus
* @ops: iommu-ops for talking to this iommu
* @dev: struct device for sysfs handling
*/
struct iommu_device {
        struct list_head list;
        const struct iommu_ops *ops;
        struct fwnode_handle *fwnode;
        struct device *dev;
};SMMU 驱动程序创建 struct iommu_device 和 struct iommu_ops 结构体的实例并注册进 IOMMU 子系统中。
Linux 内核的 IOMMU 子系统用 struct dev_iommu 结构体表示一个连接到 IOMMU 的系统 I/O 设备,用 struct iommu_fwspec 表示系统 I/O 设备连接的 IOMMU 设备,这几个结构体定义 (位于 include/linux/iommu.h 文件中) 如下:
struct fwnode_handle {
        struct fwnode_handle *secondary;
        const struct fwnode_operations *ops;
        struct device *dev;
};
. . . . . .
/**
* struct dev_iommu - Collection of per-device IOMMU data
*
* @fault_param: IOMMU detected device fault reporting data
* @iopf_param:       I/O Page Fault queue and data
* @fwspec:       IOMMU fwspec data
* @iommu_dev:       IOMMU device this device is linked to
* @priv:       IOMMU Driver private data
*
* TODO: migrate other per device data pointers under iommu_dev_data, e.g.
*        struct iommu_group        *iommu_group;
*/
struct dev_iommu {
        struct mutex lock;
        struct iommu_fault_param        *fault_param;
        struct iopf_device_param        *iopf_param;
        struct iommu_fwspec                *fwspec;
        struct iommu_device                *iommu_dev;
        void                                *priv;
};
. . . . . .
/**
* struct iommu_fwspec - per-device IOMMU instance data
* @ops: ops for this device's IOMMU
* @iommu_fwnode: firmware handle for this device's IOMMU
* @iommu_priv: IOMMU driver private data for this device
* @num_ids: number of associated device IDs
* @ids: IDs which this device may present to the IOMMU
*/
struct iommu_fwspec {
        const struct iommu_ops        *ops;
        struct fwnode_handle        *iommu_fwnode;
        u32                        flags;
        unsigned int                num_ids;
        u32                        ids[];
};在 IOMMU 中,每一个 domain 即代表一个 IOMMU 映射地址空间,即一个 page table。一个 group 逻辑上是需要与 domain 进行绑定的,即一个 group 中的所有设备都位于一个 domain 中。在 Linux 内核的 IOMMU 子系统中,domain 由 struct iommu_domain 结构体表示,这个结构体定义 (位于 include/linux/iommu.h 文件中) 如下:
struct iommu_domain {
        unsigned type;
        const struct iommu_ops *ops;
        unsigned long pgsize_bitmap;        /* Bitmap of page sizes in use */
        iommu_fault_handler_t handler;
        void *handler_token;
        struct iommu_domain_geometry geometry;
        void *iova_cookie;
        struct mutex switch_log_lock;
};Linux 内核的 IOMMU 子系统用 struct iommu_group 结构体表示位于同一个 domain 的设备组,并用 struct group_device 结构体表示设备组中的一个设备。这两个结构体定义 (位于 drivers/iommu/iommu.c 文件中) 如下:
struct iommu_group {
        struct kobject kobj;
        struct kobject *devices_kobj;
        struct list_head devices;
        struct mutex mutex;
        struct blocking_notifier_head notifier;
        void *iommu_data;
        void (*iommu_data_release)(void *iommu_data);
        char *name;
        int id;
        struct iommu_domain *default_domain;
        struct iommu_domain *domain;
        struct list_head entry;
};

struct group_device {
        struct list_head list;
        struct device *dev;
        char *name;
};以面向对象的编程方法来看,可以认为在 ARM SMMUv3 驱动程序中,struct iommu_device 和 struct iommu_domain 结构体有其特定的实现,即 struct arm_smmu_device 和 struct arm_smmu_domain 结构体继承了 struct iommu_device 和 struct iommu_domain 结构体,这两个结构体定义 (位于 drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h 文件中) 如下:
/* An SMMUv3 instance */
struct arm_smmu_device {
        struct device                        *dev;
        void __iomem                        *base;
        void __iomem                        *page1;

#define ARM_SMMU_FEAT_2_LVL_STRTAB        (1 << 0)
#define ARM_SMMU_FEAT_2_LVL_CDTAB        (1 << 1)
#define ARM_SMMU_FEAT_TT_LE                (1 << 2)
#define ARM_SMMU_FEAT_TT_BE                (1 << 3)
#define ARM_SMMU_FEAT_PRI                (1 << 4)
#define ARM_SMMU_FEAT_ATS                (1 << 5)
#define ARM_SMMU_FEAT_SEV                (1 << 6)
#define ARM_SMMU_FEAT_MSI                (1 << 7)
#define ARM_SMMU_FEAT_COHERENCY                (1 << 8)
#define ARM_SMMU_FEAT_TRANS_S1                (1 << 9)
#define ARM_SMMU_FEAT_TRANS_S2                (1 << 10)
#define ARM_SMMU_FEAT_STALLS                (1 << 11)
#define ARM_SMMU_FEAT_HYP                (1 << 12)
#define ARM_SMMU_FEAT_STALL_FORCE        (1 << 13)
#define ARM_SMMU_FEAT_VAX                (1 << 14)
#define ARM_SMMU_FEAT_RANGE_INV                (1 << 15)
#define ARM_SMMU_FEAT_BTM                (1 << 16)
#define ARM_SMMU_FEAT_SVA                (1 << 17)
#define ARM_SMMU_FEAT_E2H                (1 << 18)
#define ARM_SMMU_FEAT_HA                (1 << 19)
#define ARM_SMMU_FEAT_HD                (1 << 20)
#define ARM_SMMU_FEAT_BBML1                (1 << 21)
#define ARM_SMMU_FEAT_BBML2                (1 << 22)
#define ARM_SMMU_FEAT_ECMDQ                (1 << 23)
#define ARM_SMMU_FEAT_MPAM                (1 << 24)
        u32                                features;

#define ARM_SMMU_OPT_SKIP_PREFETCH        (1 << 0)
#define ARM_SMMU_OPT_PAGE0_REGS_ONLY        (1 << 1)
#define ARM_SMMU_OPT_MSIPOLL                (1 << 2)
        u32                                options;

        union {
                u32                        nr_ecmdq;
                u32                        ecmdq_enabled;
        };
        struct arm_smmu_ecmdq *__percpu        *ecmdq;

        struct arm_smmu_cmdq                cmdq;
        struct arm_smmu_evtq                evtq;
        struct arm_smmu_priq                priq;

        int                                gerr_irq;
        int                                combined_irq;

        unsigned long                        ias; /* IPA */
        unsigned long                        oas; /* PA */
        unsigned long                        pgsize_bitmap;

#define ARM_SMMU_MAX_ASIDS                (1 << 16)
        unsigned int                        asid_bits;

#define ARM_SMMU_MAX_VMIDS                (1 << 16)
        unsigned int                        vmid_bits;
        DECLARE_BITMAP(vmid_map, ARM_SMMU_MAX_VMIDS);

        unsigned int                        ssid_bits;
        unsigned int                        sid_bits;

        struct arm_smmu_strtab_cfg        strtab_cfg;

        /* IOMMU core code handle */
        struct iommu_device                iommu;

        struct rb_root                        streams;
        struct mutex                        streams_mutex;

        unsigned int                        mpam_partid_max;
        unsigned int                        mpam_pmg_max;

        bool                                bypass;
};
. . . . . .
struct arm_smmu_domain {
        struct arm_smmu_device                *smmu;
        struct mutex                        init_mutex; /* Protects smmu pointer */

        struct io_pgtable_ops                *pgtbl_ops;
        bool                                stall_enabled;
        bool                                non_strict;
        atomic_t                        nr_ats_masters;

        enum arm_smmu_domain_stage        stage;
        union {
                struct arm_smmu_s1_cfg        s1_cfg;
                struct arm_smmu_s2_cfg        s2_cfg;
        };

        struct iommu_domain                domain;

        /* Unused in aux domains */
        struct list_head                devices;
        spinlock_t                        devices_lock;

        struct list_head                mmu_notifiers;

        /* Auxiliary domain stuff */
        struct arm_smmu_domain                *parent;
        ioasid_t                        ssid;
        unsigned long                        aux_nr_devs;
};arm_smmu_device_probe() 函数主要做了如下几件事情:

[*]分配 struct arm_smmu_device 对象,这个对象用来在 IOMMU 子系统中描述 SMMUv3 设备。
[*]获取设备树文件 dts/dtsi 中的 SMMUv3 设备节点中包含的信息,和引用的资源,这主要包括:

[*]关于 SMMUv3 设备的信息,如 iommu-cells,其值必须为 1;options,如是否只有寄存器页 0 等;SMMU 是否支持 coherent,这主要由设备树文件中的设备节点的 dma-coherent 属性表示;
[*]SMMUv3 设备的寄存器映射,arm_smmu_device_probe() 函数会根据 options 的值检查寄存器映射的范围大小是否与预期匹配,并重映射 SMMUv3 设备的寄存器映射;
[*]SMMUv3 设备引用的中断资源,包括用于命令队列、事件队列和全局错误的中断资源。

[*]探测 SMMUv3 设备的硬件特性,这主要按照 ARM 系统内存管理单元架构规范版本 3 中定义的寄存器 SMMU_IDR0、SMMU_IDR1、SMMU_IDR3、和 SMMU_IDR5 (另外一些用于提供信息的只读寄存器包含的信息和 SMMUv3 硬件设备的特性关系不大,SMMU_IDR2 包含 SMMU 为非安全编程接口实现的特性相关的信息,SMMU_IDR4 是一个 SMMU 实现定义的寄存器,SMMU_IIDR 寄存器包含 SMMU 的实现和实现者的信息,以及由实现定义的支持的架构版本信息,SMMU_AIDR 寄存器包含 SMMU 实现遵从的 SMMU 架构版本号信息) 的各个字段,确认实际的 SMMUv3 硬件设备支持的特性,这主要通过调用 arm_smmu_device_hw_probe() 函数完成。
[*]初始化数据结构,这主要包括几个队列和流表,队列包括命令队列、事件队列和 PRIQ 队列。对于流表的初始化,分两种情况,如果流表的结构为线性流表,则线性流表中所有的 STE 都被配置为旁路 SMMU;如果流表的结构为 2 级流表,则流表中为无效的 L1 流表描述符,这主要通过调用 arm_smmu_init_structures() 函数完成。
[*]在设备结构 struct platform_device 对象的私有字段中记录 struct arm_smmu_device 对象。
[*]复位 SMMUv3 设备,这主要包括通过 SMMU_CR0 等寄存器复位硬件设备,设置流表基址寄存器等;以及设置中断,包括向系统请求中断及注册中断处理程序;初始化数据结构在内存中建立各个数据结构,复位 SMMUv3 设备则将各个数据结构的基地址和各种配置写进对应的设备寄存器中,这主要通过调用 arm_smmu_device_reset() 函数完成。
[*]将 SMMUv3 设备注册到 IOMMU 子系统,这包括为 struct iommu_device 设置 struct iommu_ops 和 struct fwnode_handle,并将 struct iommu_device 对象注册进 IOMMU 子系统。struct fwnode_handle 用于匹配 SMMUv3 设备和系统 I/O 设备,这主要通过调用 iommu_device_register() 函数完成。
[*]为各个总线类型设置 struct iommu_ops,SMMUv3 设备驱动程序和要使用 IOMMU 的系统 I/O 设备的加载顺序可能是不确定的;正常情况下,应该是 SMMUv3 设备驱动程序先加载,要使用 IOMMU 的系统 I/O 设备后加载;这里会处理使用 IOMMU 的系统 I/O 设备先于 SMMUv3 设备驱动程序加载的情况,这主要通过调用 arm_smmu_set_bus_ops() 函数完成。
探测 SMMUv3 设备的硬件特性

arm_smmu_device_probe() 函数调用 arm_smmu_device_hw_probe() 函数探测 SMMUv3 设备的硬件特性,后者定义 (位于 drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c 文件中) 如下:
static int arm_smmu_ecmdq_probe(struct arm_smmu_device *smmu){        int ret, cpu;        u32 i, nump, numq, gap;        u32 reg, shift_increment;        u64 addr, smmu_dma_base;        void __iomem *cp_regs, *cp_base;        /* IDR6 */        reg = readl_relaxed(smmu->base + ARM_SMMU_IDR6);        smmu_reg_dump(smmu);        nump = 1 > FIELD_GET(IDR6_LOG2NUMQ, reg);        smmu_dma_base = (vmalloc_to_pfn(smmu->base) dev, "ecmdq control page %u is memory mode\n", i);                        return -EFAULT;                }                if (i && ((val & ECMDQ_CP_ADDR) != (pre_addr + ECMDQ_CP_RRESET_SIZE))) {                        iounmap(cp_regs);                        dev_err(smmu->dev, "ecmdq_cp memory region is not contiguous\n");                        return -EFAULT;                }                pre_addr = val & ECMDQ_CP_ADDR;        }        addr = readl_relaxed(cp_regs) & ECMDQ_CP_ADDR;        iounmap(cp_regs);        cp_base = devm_ioremap(smmu->dev, smmu_dma_base + addr, ECMDQ_CP_RRESET_SIZE * nump);        if (!cp_base)                return -ENOMEM;        smmu->ecmdq = devm_alloc_percpu(smmu->dev, struct arm_smmu_ecmdq *);        if (!smmu->ecmdq)                return -ENOMEM;        ret = arm_smmu_ecmdq_layout(smmu);        if (ret)                return ret;        shift_increment = order_base_2(num_possible_cpus() / smmu->nr_ecmdq);        addr = 0;        for_each_possible_cpu(cpu) {                struct arm_smmu_ecmdq *ecmdq;                struct arm_smmu_queue *q;                ecmdq = *per_cpu_ptr(smmu->ecmdq, cpu);                q = &ecmdq->cmdq.q;                /*               * The boot option "maxcpus=" can limit the number of online               * CPUs. The CPUs that are not selected are not showed in               * cpumask_of_node(node), their 'ecmdq' may be NULL.               *               * (q->ecmdq_prod & ECMDQ_PROD_EN) indicates that the ECMDQ is               * shared by multiple cores and has been initialized.               */                if (!ecmdq || (q->ecmdq_prod & ECMDQ_PROD_EN))                        continue;                ecmdq->base = cp_base + addr;                q->llq.max_n_shift = ECMDQ_MAX_SZ_SHIFT + shift_increment;                ret = arm_smmu_init_one_queue(smmu, q, ecmdq->base, ARM_SMMU_ECMDQ_PROD,                                ARM_SMMU_ECMDQ_CONS, CMDQ_ENT_DWORDS, "ecmdq");                if (ret)                        return ret;                q->ecmdq_prod = ECMDQ_PROD_EN;                rwlock_init(&q->ecmdq_lock);                ret = arm_smmu_ecmdq_init(&ecmdq->cmdq);                if (ret) {                        dev_err(smmu->dev, "ecmdq[%d] init failed\n", i);                        return ret;                }                addr += gap;        }        return 0;}static void arm_smmu_get_httu(struct arm_smmu_device *smmu, u32 reg){        u32 fw_features = smmu->features & (ARM_SMMU_FEAT_HA | ARM_SMMU_FEAT_HD);        u32 features = 0;        switch (FIELD_GET(IDR0_HTTU, reg)) {        case IDR0_HTTU_ACCESS_DIRTY:                features |= ARM_SMMU_FEAT_HD;                fallthrough;        case IDR0_HTTU_ACCESS:                features |= ARM_SMMU_FEAT_HA;        }        if (smmu->dev->of_node)                smmu->features |= features;        else if (features != fw_features)                /* ACPI IORT sets the HTTU bits */                dev_warn(smmu->dev,                       "IDR0.HTTU overridden by FW configuration (0x%x)\n",                       fw_features);}static int arm_smmu_device_hw_probe(struct arm_smmu_device *smmu){        u32 reg;        bool coherent = smmu->features & ARM_SMMU_FEAT_COHERENCY;        bool vhe = cpus_have_cap(ARM64_HAS_VIRT_HOST_EXTN);        /* IDR0 */        reg = readl_relaxed(smmu->base + ARM_SMMU_IDR0);        /* 2-level structures */        if (FIELD_GET(IDR0_ST_LVL, reg) == IDR0_ST_LVL_2LVL)                smmu->features |= ARM_SMMU_FEAT_2_LVL_STRTAB;        if (reg & IDR0_CD2L)                smmu->features |= ARM_SMMU_FEAT_2_LVL_CDTAB;        /*       * Translation table endianness.       * We currently require the same endianness as the CPU, but this       * could be changed later by adding a new IO_PGTABLE_QUIRK.       */        switch (FIELD_GET(IDR0_TTENDIAN, reg)) {        case IDR0_TTENDIAN_MIXED:                smmu->features |= ARM_SMMU_FEAT_TT_LE | ARM_SMMU_FEAT_TT_BE;                break;#ifdef __BIG_ENDIAN        case IDR0_TTENDIAN_BE:                smmu->features |= ARM_SMMU_FEAT_TT_BE;                break;#else        case IDR0_TTENDIAN_LE:                smmu->features |= ARM_SMMU_FEAT_TT_LE;                break;#endif        default:                dev_err(smmu->dev, "unknown/unsupported TT endianness!\n");                return -ENXIO;        }        /* Boolean feature flags */        if (IS_ENABLED(CONFIG_PCI_PRI) && reg & IDR0_PRI)                smmu->features |= ARM_SMMU_FEAT_PRI;        if (IS_ENABLED(CONFIG_PCI_ATS) && reg & IDR0_ATS)                smmu->features |= ARM_SMMU_FEAT_ATS;        if (reg & IDR0_SEV)                smmu->features |= ARM_SMMU_FEAT_SEV;        if (reg & IDR0_MSI) {                smmu->features |= ARM_SMMU_FEAT_MSI;                if (coherent && !disable_msipolling)                        smmu->options |= ARM_SMMU_OPT_MSIPOLL;        }        if (reg & IDR0_HYP) {                smmu->features |= ARM_SMMU_FEAT_HYP;                if (vhe)                        smmu->features |= ARM_SMMU_FEAT_E2H;        }        arm_smmu_get_httu(smmu, reg);        /*       * If the CPU is using VHE, but the SMMU doesn't support it, the SMMU       * will create TLB entries for NH-EL1 world and will miss the       * broadcasted TLB invalidations that target EL2-E2H world. Don't enable       * BTM in that case.       */        if (reg & IDR0_BTM && (!vhe || reg & IDR0_HYP))                smmu->features |= ARM_SMMU_FEAT_BTM;        /*       * The coherency feature as set by FW is used in preference to the ID       * register, but warn on mismatch.       */        if (!!(reg & IDR0_COHACC) != coherent)                dev_warn(smmu->dev, "IDR0.COHACC overridden by FW configuration (%s)\n",                       coherent ? "true" : "false");        switch (FIELD_GET(IDR0_STALL_MODEL, reg)) {        case IDR0_STALL_MODEL_FORCE:                smmu->features |= ARM_SMMU_FEAT_STALL_FORCE;                fallthrough;        case IDR0_STALL_MODEL_STALL:                smmu->features |= ARM_SMMU_FEAT_STALLS;        }        if (reg & IDR0_S1P)                smmu->features |= ARM_SMMU_FEAT_TRANS_S1;        if (reg & IDR0_S2P)                smmu->features |= ARM_SMMU_FEAT_TRANS_S2;        if (!(reg & (IDR0_S1P | IDR0_S2P))) {                dev_err(smmu->dev, "no translation support!\n");                return -ENXIO;        }        /* We only support the AArch64 table format at present */        switch (FIELD_GET(IDR0_TTF, reg)) {        case IDR0_TTF_AARCH32_64:                smmu->ias = 40;                fallthrough;        case IDR0_TTF_AARCH64:                break;        default:                dev_err(smmu->dev, "AArch64 table format not supported!\n");                return -ENXIO;        }        /* ASID/VMID sizes */        smmu->asid_bits = reg & IDR0_ASID16 ? 16 : 8;        smmu->vmid_bits = reg & IDR0_VMID16 ? 16 : 8;        /* IDR1 */        reg = readl_relaxed(smmu->base + ARM_SMMU_IDR1);        if (reg & (IDR1_TABLES_PRESET | IDR1_QUEUES_PRESET | IDR1_REL)) {                dev_err(smmu->dev, "embedded implementation not supported\n");                return -ENXIO;        }        if (reg & IDR1_ECMDQ)                smmu->features |= ARM_SMMU_FEAT_ECMDQ;        /* Queue sizes, capped to ensure natural alignment */        smmu->cmdq.q.llq.max_n_shift = min_t(u32, CMDQ_MAX_SZ_SHIFT,                                             FIELD_GET(IDR1_CMDQS, reg));        if (smmu->cmdq.q.llq.max_n_shift dev, "command queue size
页: [1]
查看完整版本: 深入浅出 Linux 中的 ARM IOMMU SMMU I