本文主要介紹iommu的框架。基於4.19.204內核
IOMMU核心框架是管理IOMMU設備的一個通過框架,IOMMU設備通過實現特定的回調函數並將自身注冊到IOMMU核心框架中,以此通過IOMMU核心框架提供的API向整個內核提供IOMMU功能。
1、借用互聯網的圖:
該圖幾乎到處可見,大致表明了iommu在內核中的地位,但是需要注意的是,這個只表明了iommu的 dma 翻譯功能,沒有表明其 irq_remap 的功能。
2、iommu的驅動模塊抽象
不同的arch,不同的iommu硬件,怎么去抽象對應的公共部分呢?我們知道,iommu的主要作用就是為了給設備提供可控制的dma環境,沒有mmu的時候,
直接物理地址訪問,有了mmu,可以對內存地址訪問做控制了,同樣可以類比系統的dma和iommu。
主要的抽象實現在:
linux/drivers/iommu 目錄下,之前iommu目錄下混亂地集合了各個arch的iommu驅動,在新一點的內核中,將常見的intel、amd、arm三大架構的獨立的文件夾管理開來。
但是其他小芯片的還是散落在這個目錄下。
iommu.c:主要抽象的對象為:
iommu_device--- iommu 設備,指的是提供iommu功能的設備,所有的IOMMU設備都嵌入了一個 struct iommu_device
iommu_group---iommu 的組對象,多個dev 可以用同一個組,他是iommu管理的最小單元。
iommu_domain---iommu 的domain 對象,可以關聯一個 group,
iommu_resv_region ----保留區域,不需要iommu映射的區域。
主要的方法就是:
iommu_device_register--- iommu 設備注冊,簡單掛一下管理鏈表
iommu_device_unregister ---iommu 設備注銷,這個注銷主要就是注冊的逆操作,從管理鏈表中摘除
static int iommu_insert_resv_region(struct iommu_resv_region *new,
struct list_head *regions)//caq:在某個位置插入,相同類型則合並,不同類型不能合並
int iommu_get_group_resv_regions(struct iommu_group *group,
struct list_head *head)//caq:獲取一個iommu_group 的resv_regions
static ssize_t iommu_group_show_resv_regions(struct iommu_group *group,
char *buf)//caq:展示某個group的resv_regions
static ssize_t iommu_group_show_type(struct iommu_group *group,
char *buf)//caq:iommu_domain 類型對應的字符串
static void iommu_group_release(struct kobject *kobj)//caq:釋放一個iommu_group
struct iommu_group *iommu_group_alloc(void)//caq:申請內存,初始化一個iommu_group,它是iommu管理的最小單元
struct iommu_group *iommu_group_get_by_id(int id)//caq:根據id獲取到iommu_group
int iommu_group_set_name(struct iommu_group *group, const char *name)//caq:過來的一個name,設置一下,一般來說是 給group 分配一個名字,常見的就是1,2,3等,比如 /sys/kernel/iommu_groups/1,這個1就是一個iommu_group的name
int iommu_group_add_device(struct iommu_group *group, struct device *dev)//caq:將dev加入到iommu_group
void iommu_group_remove_device(struct device *dev)//caq:和加入group對應,從iommu_group中移除
static int iommu_group_device_count(struct iommu_group *group)//caq:統計多少個device在此group中
static int __iommu_group_for_each_dev(struct iommu_group *group, void *data,
int (*fn)(struct device *, void *))//caq:對group中device進行迭代並執行fn
struct iommu_group *generic_device_group(struct device *dev)//caq:申請一個iommu_group
3、iommu模塊,對業務驅動往上封裝 dma_map_ops 這個結構,它包含一系列常用的dma 函數指針。
比如intel硬件的iommu對業務驅動實現實例化如下:
const struct dma_map_ops intel_dma_ops = {//caq:dma_map_ops 注意與 iommu_ops 區別
.alloc = intel_alloc_coherent,//caq:創建一個一致性內存映射,要么nocache,要么soc保證cache一致性
.free = intel_free_coherent,//caq:釋放一段一致性內存映射,
.map_sg = intel_map_sg,//caq:分散聚集io的映射
.unmap_sg = intel_unmap_sg,
.map_page = intel_map_page,//caq:映射page
.unmap_page = intel_unmap_page,
.mapping_error = intel_mapping_error,
ifdef CONFIG_X86
.dma_supported = dma_direct_supported,
endif
};//caq:所有的iommu硬件,對外需要提供給驅動 一個dma_map_ops 的實現,對內需要按照 iommu 的抽象,來實例化 iommu_ops
這個dma_map_ops的實例,
**intel 下會賦值給 dma_ops 這個全局的變量來保存,**
**arm下 會賦值給 arm_smmu_ops 這個全局變量,**
**同時,他們會在 iommu_device 的ops 成員中保存,以便調用。**
比如查看對應的dma_ops,有的時候是:
crash> p dma_ops->alloc
$8 = (void ()(struct device *, size_t, dma_addr_t *, gfp_t, unsigned long)) 0xffffffffa92eefb0
crash> dis -l 0xffffffffa92eefb0
/build/linux-hwe-5.4-Cf7BMf/linux-hwe-5.4-5.4.0/drivers/iommu/intel-iommu.c: 3658
0xffffffffa92eefb0 <intel_alloc_coherent>: nopl 0x0(%rax,%rax,1) [FTRACE NOP]
0xffffffffa92eefb5 <intel_alloc_coherent+5>: push %rbp
有的時候又是這樣的,比如使用的是swiotlb作為iommu,
crash> p dma_ops.alloc
$3 = (void ()(struct device *, size_t, dma_addr_t *, gfp_t, unsigned long)) 0xffffffffacf23ab0 //caq:swiotlb_alloc
crash> dis -l 0xffffffffacf23ab0
/home/kernel/linux_config1/linux-4.19.40/kernel/dma/swiotlb.c: 1045
0xffffffffacf23ab0 <swiotlb_alloc>: nopl 0x0(%rax,%rax,1) [FTRACE NOP]
/home/kernel/linux_config1/linux-4.19.40/kernel/dma/swiotlb.c: 1049
還有的時候是這樣子的,比如arm smmuv3的:
crash> p arm_smmu_ops.domain_alloc
$2 = (struct iommu_domain ()(unsigned int)) 0xffff00001065df78 <arm_smmu_domain_alloc>
4、iommu模塊,對內需要實現 內核抽象的 iommu 框架,它需要實現 iommu_ops ,后續會有一篇文章描述設備驅動怎么調用 dma_map_ops 的接口(todo) :
struct iommu_ops {//caq:一個iommu硬件對內必須實現的ops
bool (*capable)(enum iommu_cap);//caq:該iommu 設備的能力,這里寫為設備而不是硬件,是因為完成iommu的可能是軟件,如swiotlb
/* Domain allocation and freeing by the iommu driver */
struct iommu_domain *(*domain_alloc)(unsigned iommu_domain_type);//caq:創建一個iommu_domain
void (*domain_free)(struct iommu_domain *);//caq:釋放一個iommu_domain
int (*attach_dev)(struct iommu_domain *domain, struct device *dev);//caq:關聯一個dev到一個iommu_domain,如 arm_smmu_attach_dev
void (*detach_dev)(struct iommu_domain *domain, struct device *dev);//caq:從iommu_domain中取消一個dev關聯
int (*map)(struct iommu_domain *domain, unsigned long iova,
phys_addr_t paddr, size_t size, int prot);//caq:將iova與phy的addr進行map,map后返回給驅動
size_t (*unmap)(struct iommu_domain *domain, unsigned long iova,
size_t size);
void (*flush_iotlb_all)(struct iommu_domain *domain);//caq:flush iotlb
void (*iotlb_range_add)(struct iommu_domain *domain,//caq:iotlb 增加一段區域
unsigned long iova, size_t size);
void (*iotlb_sync)(struct iommu_domain *domain);//caq:tlb同步
phys_addr_t (*iova_to_phys)(struct iommu_domain *domain, dma_addr_t iova);//caq:核心熱點函數
int (*add_device)(struct device *dev);//caq:add 是指add到iommu_group中
void (*remove_device)(struct device *dev);//caq:remove是從iommu_group中移除
struct iommu_group *(*device_group)(struct device *dev);//caq:從device獲取到對應的group
int (*domain_get_attr)(struct iommu_domain *domain,
enum iommu_attr attr, void *data);//caq:獲取attr屬性
int (*domain_set_attr)(struct iommu_domain *domain,
enum iommu_attr attr, void *data);//caq:設置attr屬性
/* Request/Free a list of reserved regions for a device */
void (*get_resv_regions)(struct device *dev, struct list_head *list);//caq:獲取dev的所有resv的regions
void (*put_resv_regions)(struct device *dev, struct list_head *list);//caq:從dev的地址空間釋放一段resv的regions
void (*apply_resv_region)(struct device *dev,
struct iommu_domain *domain,
struct iommu_resv_region *region);//caq:對dev地址空間中的保留空間做進一步apply
/* Window handling functions */
int (*domain_window_enable)(struct iommu_domain *domain, u32 wnd_nr,//caq:已廢棄,不用管
phys_addr_t paddr, u64 size, int prot);
void (*domain_window_disable)(struct iommu_domain *domain, u32 wnd_nr);//caq:已廢棄,不用管
/* Set the number of windows per domain */
int (*domain_set_windows)(struct iommu_domain *domain, u32 w_count);//caq:已廢棄,不用管
/* Get the number of windows per domain */
u32 (*domain_get_windows)(struct iommu_domain *domain);//caq:已廢棄,不用管
int (*of_xlate)(struct device *dev, struct of_phandle_args *args);
bool (*is_attach_deferred)(struct iommu_domain *domain, struct device *dev);
unsigned long pgsize_bitmap;//caq:該iommu支持的page size 的bitmap集合
};
而 intel 硬件對這個的實例化為:
const struct iommu_ops intel_iommu_ops = {//caq:和arm_smmu_ops 並列的一個iommu_ops實例
.capable = intel_iommu_capable,//caq:該iommu 硬件的能力
.domain_alloc = intel_iommu_domain_alloc,//caq:分配 dmar_domain,並返回 iommu_domain
.domain_free = intel_iommu_domain_free,//caq:釋放 dmar_domain
.attach_dev = intel_iommu_attach_device,//caq:將一個設備attach 到一個iommu_domain
.detach_dev = intel_iommu_detach_device,//caq:將一個設備 從一個iommu_domain 進行detach 掉
.map = intel_iommu_map,//caq:將iova 與phy addr 進行map
.unmap = intel_iommu_unmap,//caq:解除某段iova的map
.iova_to_phys = intel_iommu_iova_to_phys,//caq:獲取iova map的phyaddr
.add_device = intel_iommu_add_device,//caq:將一個 設備添加到 iommu_group中
.remove_device = intel_iommu_remove_device,//caq:將一個 設備從iommu_group中 移除
.get_resv_regions = intel_iommu_get_resv_regions,//caq:獲取 某個設備的 保存內存區域
.put_resv_regions = intel_iommu_put_resv_regions,//caq:從某個設備 的保留內存區域摘除
.device_group = pci_device_group,//caq:獲取一個dev的iommu_group
.pgsize_bitmap = INTEL_IOMMU_PGSIZES,//caq:固定4k
};
arm的 smmuv3將它實例化為:
static struct iommu_ops arm_smmu_ops = {//smmu-v3實現的instance,注意和 dma_map_ops 區別
.capable = arm_smmu_capable,//caq: 該iommu 設備的capability
.domain_alloc = arm_smmu_domain_alloc,//caq:分配iommu_domain
.domain_free = arm_smmu_domain_free,//caq:free 掉一個分配的iommu_domain
.attach_dev = arm_smmu_attach_dev,//caq:將設備歸屬到對應的iommu_domain
.map = arm_smmu_map,//caq:將iova 與 phy addr 進行map
.unmap = arm_smmu_unmap,//caq:將iova 與 對應的phy addr 解除map
.flush_iotlb_all = arm_smmu_iotlb_sync,//caq:注意:intel沒有直接在iommu_ops中實現flush,但是在最新內核是參照arm的
.iotlb_sync = arm_smmu_iotlb_sync,//caq:arm-v3實現的flush tlb的函數
.iova_to_phys = arm_smmu_iova_to_phys,//caq:根據iommu_domain 與iova 獲取映射過的phy addr
.add_device = arm_smmu_add_device,//caq:將設備添加到iommu_group
.remove_device = arm_smmu_remove_device,//caq:將設備從iommu_group中移除
.device_group = arm_smmu_device_group,//caq:獲取dev的歸屬iommu_group
.domain_get_attr = arm_smmu_domain_get_attr,//caq:獲取iommu_domain相關的屬性,其實大多數是 arm_smmu_domain的
.domain_set_attr = arm_smmu_domain_set_attr,//caq:設置,同上
.of_xlate = arm_smmu_of_xlate,
.get_resv_regions = arm_smmu_get_resv_regions,//caq:獲取dev的所有resv的regions
.put_resv_regions = arm_smmu_put_resv_regions,//caq:釋放上面的regions
.pgsize_bitmap = -1UL, /* Restricted during device attach */
};
5、iommu 硬件的注冊
iommu_device 是remap 框架對iommu硬件的一個抽象,框架會要求 **iommu_device_register **來注冊一個iommu設備,只要
走到了這一步,說明iommu硬件初始化完成,同時軟件功能上,也被iommu框架所接納。