Skip to content

Commit 757d2b1

Browse files
ssuthiku-amdjoergroedel
authored andcommitted
iommu/amd: Introduce gDomID-to-hDomID Mapping and handle parent domain invalidation
Each nested domain is assigned guest domain ID (gDomID), which guest OS programs into guest Device Table Entry (gDTE). For each gDomID, the driver assigns a corresponding host domain ID (hDomID), which will be programmed into the host Device Table Entry (hDTE). The hDomID is allocated during amd_iommu_alloc_domain_nested(), and free during nested_domain_free(). The gDomID-to-hDomID mapping info (struct guest_domain_mapping_info) is stored in a per-viommu xarray (struct amd_iommu_viommu.gdomid_array), which is indexed by gDomID. Note also that parent domain can be shared among struct iommufd_viommu. Therefore, when hypervisor invalidates the nest parent domain, the AMD IOMMU command INVALIDATE_IOMMU_PAGES must be issued for each hDomID in the gdomid_array. This is handled by the iommu_flush_pages_v1_hdom_ids(), where it iterates through struct protection_domain.viommu_list. Suggested-by: Jason Gunthorpe <jgg@nvidia.com> Signed-off-by: Suravee Suthikulpanit <suravee.suthikulpanit@amd.com> Signed-off-by: Joerg Roedel <joerg.roedel@amd.com>
1 parent 774180a commit 757d2b1

4 files changed

Lines changed: 203 additions & 0 deletions

File tree

drivers/iommu/amd/amd_iommu_types.h

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -503,6 +503,22 @@ struct pdom_iommu_info {
503503
struct amd_iommu_viommu {
504504
struct iommufd_viommu core;
505505
struct protection_domain *parent; /* nest parent domain for this viommu */
506+
struct list_head pdom_list; /* For protection_domain->viommu_list */
507+
508+
/*
509+
* Per-vIOMMU guest domain ID to host domain ID mapping.
510+
* Indexed by guest domain ID.
511+
*/
512+
struct xarray gdomid_array;
513+
};
514+
515+
/*
516+
* Contains guest domain ID mapping info,
517+
* which is stored in the struct xarray gdomid_array.
518+
*/
519+
struct guest_domain_mapping_info {
520+
refcount_t users;
521+
u32 hdom_id; /* Host domain ID */
506522
};
507523

508524
/*
@@ -511,6 +527,7 @@ struct amd_iommu_viommu {
511527
struct nested_domain {
512528
struct iommu_domain domain; /* generic domain handle used by iommu core code */
513529
u16 gdom_id; /* domain ID from gDTE */
530+
struct guest_domain_mapping_info *gdom_info;
514531
struct iommu_hwpt_amd_guest gdte; /* Guest vIOMMU DTE */
515532
struct amd_iommu_viommu *viommu; /* AMD hw-viommu this nested domain belong to */
516533
};
@@ -535,6 +552,12 @@ struct protection_domain {
535552

536553
struct mmu_notifier mn; /* mmu notifier for the SVA domain */
537554
struct list_head dev_data_list; /* List of pdom_dev_data */
555+
556+
/*
557+
* Store reference to list of vIOMMUs, which use this protection domain.
558+
* This will be used to look up host domain ID when flushing this domain.
559+
*/
560+
struct list_head viommu_list;
538561
};
539562
PT_IOMMU_CHECK_DOMAIN(struct protection_domain, iommu, domain);
540563
PT_IOMMU_CHECK_DOMAIN(struct protection_domain, amdv1.iommu, domain);

drivers/iommu/amd/iommu.c

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1543,6 +1543,32 @@ static void amd_iommu_flush_tlb_domid(struct amd_iommu *iommu, u32 dom_id)
15431543
iommu_completion_wait(iommu);
15441544
}
15451545

1546+
static int iommu_flush_pages_v1_hdom_ids(struct protection_domain *pdom, u64 address, size_t size)
1547+
{
1548+
int ret = 0;
1549+
struct amd_iommu_viommu *aviommu;
1550+
1551+
list_for_each_entry(aviommu, &pdom->viommu_list, pdom_list) {
1552+
unsigned long i;
1553+
struct guest_domain_mapping_info *gdom_info;
1554+
struct amd_iommu *iommu = container_of(aviommu->core.iommu_dev,
1555+
struct amd_iommu, iommu);
1556+
1557+
xa_lock(&aviommu->gdomid_array);
1558+
xa_for_each(&aviommu->gdomid_array, i, gdom_info) {
1559+
struct iommu_cmd cmd;
1560+
1561+
pr_debug("%s: iommu=%#x, hdom_id=%#x\n", __func__,
1562+
iommu->devid, gdom_info->hdom_id);
1563+
build_inv_iommu_pages(&cmd, address, size, gdom_info->hdom_id,
1564+
IOMMU_NO_PASID, false);
1565+
ret |= iommu_queue_command(iommu, &cmd);
1566+
}
1567+
xa_unlock(&aviommu->gdomid_array);
1568+
}
1569+
return ret;
1570+
}
1571+
15461572
static void amd_iommu_flush_all(struct amd_iommu *iommu)
15471573
{
15481574
struct iommu_cmd cmd;
@@ -1691,6 +1717,17 @@ static int domain_flush_pages_v1(struct protection_domain *pdom,
16911717
ret |= iommu_queue_command(pdom_iommu_info->iommu, &cmd);
16921718
}
16931719

1720+
/*
1721+
* A domain w/ v1 table can be a nest parent, which can have
1722+
* multiple nested domains. Each nested domain has 1:1 mapping
1723+
* between gDomID and hDomID. Therefore, flush every hDomID
1724+
* associated to this nest parent domain.
1725+
*
1726+
* See drivers/iommu/amd/nested.c: amd_iommu_alloc_domain_nested()
1727+
*/
1728+
if (!list_empty(&pdom->viommu_list))
1729+
ret |= iommu_flush_pages_v1_hdom_ids(pdom, address, size);
1730+
16941731
return ret;
16951732
}
16961733

@@ -2508,6 +2545,7 @@ static void protection_domain_init(struct protection_domain *domain)
25082545
spin_lock_init(&domain->lock);
25092546
INIT_LIST_HEAD(&domain->dev_list);
25102547
INIT_LIST_HEAD(&domain->dev_data_list);
2548+
INIT_LIST_HEAD(&domain->viommu_list);
25112549
xa_init(&domain->iommu_array);
25122550
}
25132551

drivers/iommu/amd/iommufd.c

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@
99
#include "amd_iommu.h"
1010
#include "amd_iommu_types.h"
1111

12+
static const struct iommufd_viommu_ops amd_viommu_ops;
13+
1214
void *amd_iommufd_hw_info(struct device *dev, u32 *length, u32 *type)
1315
{
1416
struct iommu_hw_info_amd *hwinfo;
@@ -38,10 +40,39 @@ size_t amd_iommufd_get_viommu_size(struct device *dev, enum iommu_viommu_type vi
3840
int amd_iommufd_viommu_init(struct iommufd_viommu *viommu, struct iommu_domain *parent,
3941
const struct iommu_user_data *user_data)
4042
{
43+
unsigned long flags;
4144
struct protection_domain *pdom = to_pdomain(parent);
4245
struct amd_iommu_viommu *aviommu = container_of(viommu, struct amd_iommu_viommu, core);
4346

47+
xa_init_flags(&aviommu->gdomid_array, XA_FLAGS_ALLOC1);
4448
aviommu->parent = pdom;
4549

50+
viommu->ops = &amd_viommu_ops;
51+
52+
spin_lock_irqsave(&pdom->lock, flags);
53+
list_add(&aviommu->pdom_list, &pdom->viommu_list);
54+
spin_unlock_irqrestore(&pdom->lock, flags);
55+
4656
return 0;
4757
}
58+
59+
static void amd_iommufd_viommu_destroy(struct iommufd_viommu *viommu)
60+
{
61+
unsigned long flags;
62+
struct amd_iommu *iommu = container_of(viommu->iommu_dev, struct amd_iommu, iommu);
63+
struct amd_iommu_viommu *aviommu = container_of(viommu, struct amd_iommu_viommu, core);
64+
struct protection_domain *pdom = aviommu->parent;
65+
66+
spin_lock_irqsave(&pdom->lock, flags);
67+
list_del(&aviommu->pdom_list);
68+
spin_unlock_irqrestore(&pdom->lock, flags);
69+
xa_destroy(&aviommu->gdomid_array);
70+
}
71+
72+
/*
73+
* See include/linux/iommufd.h
74+
* struct iommufd_viommu_ops - vIOMMU specific operations
75+
*/
76+
static const struct iommufd_viommu_ops amd_viommu_ops = {
77+
.destroy = amd_iommufd_viommu_destroy,
78+
};

drivers/iommu/amd/nested.c

Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
#define dev_fmt(fmt) "AMD-Vi: " fmt
77

88
#include <linux/iommu.h>
9+
#include <linux/refcount.h>
910
#include <uapi/linux/iommufd.h>
1011

1112
#include "amd_iommu.h"
@@ -58,6 +59,33 @@ static int validate_gdte_nested(struct iommu_hwpt_amd_guest *gdte)
5859
return 0;
5960
}
6061

62+
static void *gdom_info_load_or_alloc_locked(struct xarray *xa, unsigned long index)
63+
{
64+
struct guest_domain_mapping_info *elm, *res;
65+
66+
elm = xa_load(xa, index);
67+
if (elm)
68+
return elm;
69+
70+
xa_unlock(xa);
71+
elm = kzalloc(sizeof(struct guest_domain_mapping_info), GFP_KERNEL);
72+
xa_lock(xa);
73+
if (!elm)
74+
return ERR_PTR(-ENOMEM);
75+
76+
res = __xa_cmpxchg(xa, index, NULL, elm, GFP_KERNEL);
77+
if (xa_is_err(res))
78+
res = ERR_PTR(xa_err(res));
79+
80+
if (res) {
81+
kfree(elm);
82+
return res;
83+
}
84+
85+
refcount_set(&elm->users, 0);
86+
return elm;
87+
}
88+
6189
/*
6290
* This function is assigned to struct iommufd_viommu_ops.alloc_domain_nested()
6391
* during the call to struct iommu_ops.viommu_init().
@@ -68,6 +96,7 @@ amd_iommu_alloc_domain_nested(struct iommufd_viommu *viommu, u32 flags,
6896
{
6997
int ret;
7098
struct nested_domain *ndom;
99+
struct guest_domain_mapping_info *gdom_info;
71100
struct amd_iommu_viommu *aviommu = container_of(viommu, struct amd_iommu_viommu, core);
72101

73102
if (user_data->type != IOMMU_HWPT_DATA_AMD_GUEST)
@@ -92,16 +121,98 @@ amd_iommu_alloc_domain_nested(struct iommufd_viommu *viommu, u32 flags,
92121
ndom->domain.type = IOMMU_DOMAIN_NESTED;
93122
ndom->viommu = aviommu;
94123

124+
/*
125+
* Normally, when a guest has multiple pass-through devices,
126+
* the IOMMU driver setup DTEs with the same stage-2 table and
127+
* use the same host domain ID (hDomId). In case of nested translation,
128+
* if the guest setup different stage-1 tables with same PASID,
129+
* IOMMU would use the same TLB tag. This will results in TLB
130+
* aliasing issue.
131+
*
132+
* The guest is assigning gDomIDs based on its own algorithm for managing
133+
* cache tags of (DomID, PASID). Within a single viommu, the nest parent domain
134+
* (w/ S2 table) is used by all DTEs. But we need to consistently map the gDomID
135+
* to a single hDomID. This is done using an xarray in the vIOMMU to
136+
* keep track of the gDomID mapping. When the S2 is changed, the INVALIDATE_IOMMU_PAGES
137+
* command must be issued for each hDomID in the xarray.
138+
*/
139+
xa_lock(&aviommu->gdomid_array);
140+
141+
gdom_info = gdom_info_load_or_alloc_locked(&aviommu->gdomid_array, ndom->gdom_id);
142+
if (IS_ERR(gdom_info)) {
143+
xa_unlock(&aviommu->gdomid_array);
144+
ret = PTR_ERR(gdom_info);
145+
goto out_err;
146+
}
147+
148+
/* Check if gDomID exist */
149+
if (refcount_inc_not_zero(&gdom_info->users)) {
150+
ndom->gdom_info = gdom_info;
151+
xa_unlock(&aviommu->gdomid_array);
152+
153+
pr_debug("%s: Found gdom_id=%#x, hdom_id=%#x\n",
154+
__func__, ndom->gdom_id, gdom_info->hdom_id);
155+
156+
return &ndom->domain;
157+
}
158+
159+
/* The gDomID does not exist. We allocate new hdom_id */
160+
gdom_info->hdom_id = amd_iommu_pdom_id_alloc();
161+
if (gdom_info->hdom_id <= 0) {
162+
__xa_cmpxchg(&aviommu->gdomid_array,
163+
ndom->gdom_id, gdom_info, NULL, GFP_ATOMIC);
164+
xa_unlock(&aviommu->gdomid_array);
165+
ret = -ENOSPC;
166+
goto out_err_gdom_info;
167+
}
168+
169+
ndom->gdom_info = gdom_info;
170+
refcount_set(&gdom_info->users, 1);
171+
172+
xa_unlock(&aviommu->gdomid_array);
173+
174+
pr_debug("%s: Allocate gdom_id=%#x, hdom_id=%#x\n",
175+
__func__, ndom->gdom_id, gdom_info->hdom_id);
176+
95177
return &ndom->domain;
178+
179+
out_err_gdom_info:
180+
kfree(gdom_info);
96181
out_err:
97182
kfree(ndom);
98183
return ERR_PTR(ret);
99184
}
100185

101186
static void nested_domain_free(struct iommu_domain *dom)
102187
{
188+
struct guest_domain_mapping_info *curr;
103189
struct nested_domain *ndom = to_ndomain(dom);
190+
struct amd_iommu_viommu *aviommu = ndom->viommu;
191+
192+
xa_lock(&aviommu->gdomid_array);
193+
194+
if (!refcount_dec_and_test(&ndom->gdom_info->users)) {
195+
xa_unlock(&aviommu->gdomid_array);
196+
return;
197+
}
198+
199+
/*
200+
* The refcount for the gdom_id to hdom_id mapping is zero.
201+
* It is now safe to remove the mapping.
202+
*/
203+
curr = __xa_cmpxchg(&aviommu->gdomid_array, ndom->gdom_id,
204+
ndom->gdom_info, NULL, GFP_ATOMIC);
205+
206+
xa_unlock(&aviommu->gdomid_array);
207+
if (WARN_ON(!curr || xa_err(curr)))
208+
return;
209+
210+
/* success */
211+
pr_debug("%s: Free gdom_id=%#x, hdom_id=%#x\n",
212+
__func__, ndom->gdom_id, curr->hdom_id);
104213

214+
amd_iommu_pdom_id_free(ndom->gdom_info->hdom_id);
215+
kfree(curr);
105216
kfree(ndom);
106217
}
107218

0 commit comments

Comments
 (0)