Skip to content

Commit 4811815

Browse files
Jean-Philippe Bruckerwilldeacon
authored andcommitted
arm64: mm: Pin down ASIDs for sharing mm with devices
To enable address space sharing with the IOMMU, introduce arm64_mm_context_get() and arm64_mm_context_put(), that pin down a context and ensure that it will keep its ASID after a rollover. Export the symbols to let the modular SMMUv3 driver use them. Pinning is necessary because a device constantly needs a valid ASID, unlike tasks that only require one when running. Without pinning, we would need to notify the IOMMU when we're about to use a new ASID for a task, and it would get complicated when a new task is assigned a shared ASID. Consider the following scenario with no ASID pinned: 1. Task t1 is running on CPUx with shared ASID (gen=1, asid=1) 2. Task t2 is scheduled on CPUx, gets ASID (1, 2) 3. Task tn is scheduled on CPUy, a rollover occurs, tn gets ASID (2, 1) We would now have to immediately generate a new ASID for t1, notify the IOMMU, and finally enable task tn. We are holding the lock during all that time, since we can't afford having another CPU trigger a rollover. The IOMMU issues invalidation commands that can take tens of milliseconds. It gets needlessly complicated. All we wanted to do was schedule task tn, that has no business with the IOMMU. By letting the IOMMU pin tasks when needed, we avoid stalling the slow path, and let the pinning fail when we're out of shareable ASIDs. After a rollover, the allocator expects at least one ASID to be available in addition to the reserved ones (one per CPU). So (NR_ASIDS - NR_CPUS - 1) is the maximum number of ASIDs that can be shared with the IOMMU. Signed-off-by: Jean-Philippe Brucker <jean-philippe@linaro.org> Reviewed-by: Jonathan Cameron <Jonathan.Cameron@huawei.com> Link: https://lore.kernel.org/r/20200918101852.582559-5-jean-philippe@linaro.org Signed-off-by: Will Deacon <will@kernel.org>
1 parent f75aef3 commit 4811815

3 files changed

Lines changed: 112 additions & 7 deletions

File tree

arch/arm64/include/asm/mmu.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,11 +17,14 @@
1717

1818
#ifndef __ASSEMBLY__
1919

20+
#include <linux/refcount.h>
21+
2022
typedef struct {
2123
atomic64_t id;
2224
#ifdef CONFIG_COMPAT
2325
void *sigpage;
2426
#endif
27+
refcount_t pinned;
2528
void *vdso;
2629
unsigned long flags;
2730
} mm_context_t;

arch/arm64/include/asm/mmu_context.h

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -177,7 +177,13 @@ static inline void cpu_replace_ttbr1(pgd_t *pgdp)
177177
#define destroy_context(mm) do { } while(0)
178178
void check_and_switch_context(struct mm_struct *mm);
179179

180-
#define init_new_context(tsk,mm) ({ atomic64_set(&(mm)->context.id, 0); 0; })
180+
static inline int
181+
init_new_context(struct task_struct *tsk, struct mm_struct *mm)
182+
{
183+
atomic64_set(&mm->context.id, 0);
184+
refcount_set(&mm->context.pinned, 0);
185+
return 0;
186+
}
181187

182188
#ifdef CONFIG_ARM64_SW_TTBR0_PAN
183189
static inline void update_saved_ttbr0(struct task_struct *tsk,
@@ -248,6 +254,9 @@ switch_mm(struct mm_struct *prev, struct mm_struct *next,
248254
void verify_cpu_asid_bits(void);
249255
void post_ttbr_update_workaround(void);
250256

257+
unsigned long arm64_mm_context_get(struct mm_struct *mm);
258+
void arm64_mm_context_put(struct mm_struct *mm);
259+
251260
#endif /* !__ASSEMBLY__ */
252261

253262
#endif /* !__ASM_MMU_CONTEXT_H */

arch/arm64/mm/context.c

Lines changed: 99 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,10 @@ static DEFINE_PER_CPU(atomic64_t, active_asids);
2727
static DEFINE_PER_CPU(u64, reserved_asids);
2828
static cpumask_t tlb_flush_pending;
2929

30+
static unsigned long max_pinned_asids;
31+
static unsigned long nr_pinned_asids;
32+
static unsigned long *pinned_asid_map;
33+
3034
#define ASID_MASK (~GENMASK(asid_bits - 1, 0))
3135
#define ASID_FIRST_VERSION (1UL << asid_bits)
3236

@@ -72,7 +76,7 @@ void verify_cpu_asid_bits(void)
7276
}
7377
}
7478

75-
static void set_kpti_asid_bits(void)
79+
static void set_kpti_asid_bits(unsigned long *map)
7680
{
7781
unsigned int len = BITS_TO_LONGS(NUM_USER_ASIDS) * sizeof(unsigned long);
7882
/*
@@ -81,13 +85,15 @@ static void set_kpti_asid_bits(void)
8185
* is set, then the ASID will map only userspace. Thus
8286
* mark even as reserved for kernel.
8387
*/
84-
memset(asid_map, 0xaa, len);
88+
memset(map, 0xaa, len);
8589
}
8690

8791
static void set_reserved_asid_bits(void)
8892
{
89-
if (arm64_kernel_unmapped_at_el0())
90-
set_kpti_asid_bits();
93+
if (pinned_asid_map)
94+
bitmap_copy(asid_map, pinned_asid_map, NUM_USER_ASIDS);
95+
else if (arm64_kernel_unmapped_at_el0())
96+
set_kpti_asid_bits(asid_map);
9197
else
9298
bitmap_clear(asid_map, 0, NUM_USER_ASIDS);
9399
}
@@ -165,6 +171,14 @@ static u64 new_context(struct mm_struct *mm)
165171
if (check_update_reserved_asid(asid, newasid))
166172
return newasid;
167173

174+
/*
175+
* If it is pinned, we can keep using it. Note that reserved
176+
* takes priority, because even if it is also pinned, we need to
177+
* update the generation into the reserved_asids.
178+
*/
179+
if (refcount_read(&mm->context.pinned))
180+
return newasid;
181+
168182
/*
169183
* We had a valid ASID in a previous life, so try to re-use
170184
* it if possible.
@@ -256,6 +270,71 @@ void check_and_switch_context(struct mm_struct *mm)
256270
cpu_switch_mm(mm->pgd, mm);
257271
}
258272

273+
unsigned long arm64_mm_context_get(struct mm_struct *mm)
274+
{
275+
unsigned long flags;
276+
u64 asid;
277+
278+
if (!pinned_asid_map)
279+
return 0;
280+
281+
raw_spin_lock_irqsave(&cpu_asid_lock, flags);
282+
283+
asid = atomic64_read(&mm->context.id);
284+
285+
if (refcount_inc_not_zero(&mm->context.pinned))
286+
goto out_unlock;
287+
288+
if (nr_pinned_asids >= max_pinned_asids) {
289+
asid = 0;
290+
goto out_unlock;
291+
}
292+
293+
if (!asid_gen_match(asid)) {
294+
/*
295+
* We went through one or more rollover since that ASID was
296+
* used. Ensure that it is still valid, or generate a new one.
297+
*/
298+
asid = new_context(mm);
299+
atomic64_set(&mm->context.id, asid);
300+
}
301+
302+
nr_pinned_asids++;
303+
__set_bit(asid2idx(asid), pinned_asid_map);
304+
refcount_set(&mm->context.pinned, 1);
305+
306+
out_unlock:
307+
raw_spin_unlock_irqrestore(&cpu_asid_lock, flags);
308+
309+
asid &= ~ASID_MASK;
310+
311+
/* Set the equivalent of USER_ASID_BIT */
312+
if (asid && arm64_kernel_unmapped_at_el0())
313+
asid |= 1;
314+
315+
return asid;
316+
}
317+
EXPORT_SYMBOL_GPL(arm64_mm_context_get);
318+
319+
void arm64_mm_context_put(struct mm_struct *mm)
320+
{
321+
unsigned long flags;
322+
u64 asid = atomic64_read(&mm->context.id);
323+
324+
if (!pinned_asid_map)
325+
return;
326+
327+
raw_spin_lock_irqsave(&cpu_asid_lock, flags);
328+
329+
if (refcount_dec_and_test(&mm->context.pinned)) {
330+
__clear_bit(asid2idx(asid), pinned_asid_map);
331+
nr_pinned_asids--;
332+
}
333+
334+
raw_spin_unlock_irqrestore(&cpu_asid_lock, flags);
335+
}
336+
EXPORT_SYMBOL_GPL(arm64_mm_context_put);
337+
259338
/* Errata workaround post TTBRx_EL1 update. */
260339
asmlinkage void post_ttbr_update_workaround(void)
261340
{
@@ -296,15 +375,25 @@ static int asids_update_limit(void)
296375
{
297376
unsigned long num_available_asids = NUM_USER_ASIDS;
298377

299-
if (arm64_kernel_unmapped_at_el0())
378+
if (arm64_kernel_unmapped_at_el0()) {
300379
num_available_asids /= 2;
380+
if (pinned_asid_map)
381+
set_kpti_asid_bits(pinned_asid_map);
382+
}
301383
/*
302384
* Expect allocation after rollover to fail if we don't have at least
303385
* one more ASID than CPUs. ASID #0 is reserved for init_mm.
304386
*/
305387
WARN_ON(num_available_asids - 1 <= num_possible_cpus());
306388
pr_info("ASID allocator initialised with %lu entries\n",
307389
num_available_asids);
390+
391+
/*
392+
* There must always be an ASID available after rollover. Ensure that,
393+
* even if all CPUs have a reserved ASID and the maximum number of ASIDs
394+
* are pinned, there still is at least one empty slot in the ASID map.
395+
*/
396+
max_pinned_asids = num_available_asids - num_possible_cpus() - 2;
308397
return 0;
309398
}
310399
arch_initcall(asids_update_limit);
@@ -319,13 +408,17 @@ static int asids_init(void)
319408
panic("Failed to allocate bitmap for %lu ASIDs\n",
320409
NUM_USER_ASIDS);
321410

411+
pinned_asid_map = kcalloc(BITS_TO_LONGS(NUM_USER_ASIDS),
412+
sizeof(*pinned_asid_map), GFP_KERNEL);
413+
nr_pinned_asids = 0;
414+
322415
/*
323416
* We cannot call set_reserved_asid_bits() here because CPU
324417
* caps are not finalized yet, so it is safer to assume KPTI
325418
* and reserve kernel ASID's from beginning.
326419
*/
327420
if (IS_ENABLED(CONFIG_UNMAP_KERNEL_AT_EL0))
328-
set_kpti_asid_bits();
421+
set_kpti_asid_bits(asid_map);
329422
return 0;
330423
}
331424
early_initcall(asids_init);

0 commit comments

Comments
 (0)