2019-06-03 13:44:50 +08:00
|
|
|
// SPDX-License-Identifier: GPL-2.0-only
|
2012-03-05 19:49:28 +08:00
|
|
|
/*
|
|
|
|
* Based on arch/arm/mm/context.c
|
|
|
|
*
|
|
|
|
* Copyright (C) 2002-2003 Deep Blue Solutions Ltd, all rights reserved.
|
|
|
|
* Copyright (C) 2012 ARM Ltd.
|
|
|
|
*/
|
|
|
|
|
2020-02-13 20:14:52 +08:00
|
|
|
#include <linux/bitfield.h>
|
2015-10-07 01:46:24 +08:00
|
|
|
#include <linux/bitops.h>
|
2012-03-05 19:49:28 +08:00
|
|
|
#include <linux/sched.h>
|
2015-10-07 01:46:24 +08:00
|
|
|
#include <linux/slab.h>
|
2012-03-05 19:49:28 +08:00
|
|
|
#include <linux/mm.h>
|
|
|
|
|
2015-10-07 01:46:24 +08:00
|
|
|
#include <asm/cpufeature.h>
|
2012-03-05 19:49:28 +08:00
|
|
|
#include <asm/mmu_context.h>
|
2016-02-23 18:31:45 +08:00
|
|
|
#include <asm/smp.h>
|
2012-03-05 19:49:28 +08:00
|
|
|
#include <asm/tlbflush.h>
|
|
|
|
|
2015-10-07 01:46:24 +08:00
|
|
|
static u32 asid_bits;
|
|
|
|
static DEFINE_RAW_SPINLOCK(cpu_asid_lock);
|
2012-03-05 19:49:28 +08:00
|
|
|
|
2015-10-07 01:46:24 +08:00
|
|
|
static atomic64_t asid_generation;
|
|
|
|
static unsigned long *asid_map;
|
2012-03-05 19:49:28 +08:00
|
|
|
|
2015-10-07 01:46:24 +08:00
|
|
|
static DEFINE_PER_CPU(atomic64_t, active_asids);
|
|
|
|
static DEFINE_PER_CPU(u64, reserved_asids);
|
|
|
|
static cpumask_t tlb_flush_pending;
|
2012-03-05 19:49:28 +08:00
|
|
|
|
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>
2020-09-18 18:18:44 +08:00
|
|
|
static unsigned long max_pinned_asids;
|
|
|
|
static unsigned long nr_pinned_asids;
|
|
|
|
static unsigned long *pinned_asid_map;
|
|
|
|
|
2015-10-07 01:46:24 +08:00
|
|
|
#define ASID_MASK (~GENMASK(asid_bits - 1, 0))
|
|
|
|
#define ASID_FIRST_VERSION (1UL << asid_bits)
|
2017-08-10 21:10:28 +08:00
|
|
|
|
2020-01-07 18:28:03 +08:00
|
|
|
#define NUM_USER_ASIDS ASID_FIRST_VERSION
|
2021-12-09 09:42:25 +08:00
|
|
|
#define ctxid2asid(asid) ((asid) & ~ASID_MASK)
|
|
|
|
#define asid2ctxid(asid, genid) ((asid) | (genid))
|
2015-10-07 01:46:24 +08:00
|
|
|
|
2016-02-23 18:31:44 +08:00
|
|
|
/* Get the ASIDBits supported by the current CPU */
|
|
|
|
static u32 get_cpu_asid_bits(void)
|
|
|
|
{
|
|
|
|
u32 asid;
|
2016-03-04 20:54:05 +08:00
|
|
|
int fld = cpuid_feature_extract_unsigned_field(read_cpuid(ID_AA64MMFR0_EL1),
|
2022-09-06 06:54:06 +08:00
|
|
|
ID_AA64MMFR0_EL1_ASIDBITS_SHIFT);
|
2016-02-23 18:31:44 +08:00
|
|
|
|
|
|
|
switch (fld) {
|
|
|
|
default:
|
|
|
|
pr_warn("CPU%d: Unknown ASID size (%d); assuming 8-bit\n",
|
|
|
|
smp_processor_id(), fld);
|
2020-08-24 06:36:59 +08:00
|
|
|
fallthrough;
|
2022-09-06 06:54:06 +08:00
|
|
|
case ID_AA64MMFR0_EL1_ASIDBITS_8:
|
2016-02-23 18:31:44 +08:00
|
|
|
asid = 8;
|
|
|
|
break;
|
2022-09-06 06:54:06 +08:00
|
|
|
case ID_AA64MMFR0_EL1_ASIDBITS_16:
|
2016-02-23 18:31:44 +08:00
|
|
|
asid = 16;
|
|
|
|
}
|
|
|
|
|
|
|
|
return asid;
|
|
|
|
}
|
|
|
|
|
2016-02-23 18:31:45 +08:00
|
|
|
/* Check if the current cpu's ASIDBits is compatible with asid_bits */
|
|
|
|
void verify_cpu_asid_bits(void)
|
|
|
|
{
|
|
|
|
u32 asid = get_cpu_asid_bits();
|
|
|
|
|
|
|
|
if (asid < asid_bits) {
|
|
|
|
/*
|
|
|
|
* We cannot decrease the ASID size at runtime, so panic if we support
|
|
|
|
* fewer ASID bits than the boot CPU.
|
|
|
|
*/
|
|
|
|
pr_crit("CPU%d: smaller ASID size(%u) than boot CPU (%u)\n",
|
|
|
|
smp_processor_id(), asid, asid_bits);
|
2016-04-12 22:46:00 +08:00
|
|
|
cpu_panic_kernel();
|
2016-02-23 18:31:45 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
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>
2020-09-18 18:18:44 +08:00
|
|
|
static void set_kpti_asid_bits(unsigned long *map)
|
2020-01-07 18:28:03 +08:00
|
|
|
{
|
|
|
|
unsigned int len = BITS_TO_LONGS(NUM_USER_ASIDS) * sizeof(unsigned long);
|
|
|
|
/*
|
|
|
|
* In case of KPTI kernel/user ASIDs are allocated in
|
|
|
|
* pairs, the bottom bit distinguishes the two: if it
|
|
|
|
* is set, then the ASID will map only userspace. Thus
|
|
|
|
* mark even as reserved for kernel.
|
|
|
|
*/
|
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>
2020-09-18 18:18:44 +08:00
|
|
|
memset(map, 0xaa, len);
|
2020-01-07 18:28:03 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
static void set_reserved_asid_bits(void)
|
|
|
|
{
|
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>
2020-09-18 18:18:44 +08:00
|
|
|
if (pinned_asid_map)
|
|
|
|
bitmap_copy(asid_map, pinned_asid_map, NUM_USER_ASIDS);
|
|
|
|
else if (arm64_kernel_unmapped_at_el0())
|
|
|
|
set_kpti_asid_bits(asid_map);
|
2020-01-07 18:28:03 +08:00
|
|
|
else
|
|
|
|
bitmap_clear(asid_map, 0, NUM_USER_ASIDS);
|
|
|
|
}
|
|
|
|
|
2020-05-20 01:54:43 +08:00
|
|
|
#define asid_gen_match(asid) \
|
|
|
|
(!(((asid) ^ atomic64_read(&asid_generation)) >> asid_bits))
|
|
|
|
|
2018-10-06 16:49:04 +08:00
|
|
|
static void flush_context(void)
|
2012-03-05 19:49:28 +08:00
|
|
|
{
|
2015-10-07 01:46:24 +08:00
|
|
|
int i;
|
|
|
|
u64 asid;
|
|
|
|
|
|
|
|
/* Update the list of reserved ASIDs and the ASID bitmap. */
|
2020-01-07 18:28:03 +08:00
|
|
|
set_reserved_asid_bits();
|
2015-10-07 01:46:24 +08:00
|
|
|
|
|
|
|
for_each_possible_cpu(i) {
|
|
|
|
asid = atomic64_xchg_relaxed(&per_cpu(active_asids, i), 0);
|
|
|
|
/*
|
|
|
|
* If this CPU has already been through a
|
|
|
|
* rollover, but hasn't run another task in
|
|
|
|
* the meantime, we must preserve its reserved
|
|
|
|
* ASID, as this is the only trace we have of
|
|
|
|
* the process it is still running.
|
|
|
|
*/
|
|
|
|
if (asid == 0)
|
|
|
|
asid = per_cpu(reserved_asids, i);
|
2021-12-09 09:42:25 +08:00
|
|
|
__set_bit(ctxid2asid(asid), asid_map);
|
2015-10-07 01:46:24 +08:00
|
|
|
per_cpu(reserved_asids, i) = asid;
|
|
|
|
}
|
|
|
|
|
2017-11-21 19:59:13 +08:00
|
|
|
/*
|
|
|
|
* Queue a TLB invalidation for each CPU to perform on next
|
|
|
|
* context-switch
|
|
|
|
*/
|
2015-10-07 01:46:24 +08:00
|
|
|
cpumask_setall(&tlb_flush_pending);
|
2012-03-05 19:49:28 +08:00
|
|
|
}
|
|
|
|
|
arm64: mm: keep reserved ASIDs in sync with mm after multiple rollovers
Under some unusual context-switching patterns, it is possible to end up
with multiple threads from the same mm running concurrently with
different ASIDs:
1. CPU x schedules task t with mm p containing ASID a and generation g
This task doesn't block and the CPU doesn't context switch.
So:
* per_cpu(active_asid, x) = {g,a}
* p->context.id = {g,a}
2. Some other CPU generates an ASID rollover. The global generation is
now (g + 1). CPU x is still running t, with no context switch and
so per_cpu(reserved_asid, x) = {g,a}
3. CPU y schedules task t', which shares mm p with t. The generation
mismatches, so we take the slowpath and hit the reserved ASID from
CPU x. p is then updated so that p->context.id = {g + 1,a}
4. CPU y schedules some other task u, which has an mm != p.
5. Some other CPU generates *another* CPU rollover. The global
generation is now (g + 2). CPU x is still running t, with no context
switch and so per_cpu(reserved_asid, x) = {g,a}.
6. CPU y once again schedules task t', but now *fails* to hit the
reserved ASID from CPU x because of the generation mismatch. This
results in a new ASID being allocated, despite the fact that t is
still running on CPU x with the same mm.
Consequently, TLBIs (e.g. as a result of CoW) will not be synchronised
between the two threads.
This patch fixes the problem by updating all of the matching reserved
ASIDs when we hit on the slowpath (i.e. in step 3 above). This keeps
the reserved ASIDs in-sync with the mm and avoids the problem.
Reported-by: Tony Thompson <anthony.thompson@arm.com>
Reviewed-by: Catalin Marinas <catalin.marinas@arm.com>
Signed-off-by: Will Deacon <will.deacon@arm.com>
Signed-off-by: Catalin Marinas <catalin.marinas@arm.com>
2015-11-26 21:49:39 +08:00
|
|
|
static bool check_update_reserved_asid(u64 asid, u64 newasid)
|
2012-03-05 19:49:28 +08:00
|
|
|
{
|
2015-10-07 01:46:24 +08:00
|
|
|
int cpu;
|
arm64: mm: keep reserved ASIDs in sync with mm after multiple rollovers
Under some unusual context-switching patterns, it is possible to end up
with multiple threads from the same mm running concurrently with
different ASIDs:
1. CPU x schedules task t with mm p containing ASID a and generation g
This task doesn't block and the CPU doesn't context switch.
So:
* per_cpu(active_asid, x) = {g,a}
* p->context.id = {g,a}
2. Some other CPU generates an ASID rollover. The global generation is
now (g + 1). CPU x is still running t, with no context switch and
so per_cpu(reserved_asid, x) = {g,a}
3. CPU y schedules task t', which shares mm p with t. The generation
mismatches, so we take the slowpath and hit the reserved ASID from
CPU x. p is then updated so that p->context.id = {g + 1,a}
4. CPU y schedules some other task u, which has an mm != p.
5. Some other CPU generates *another* CPU rollover. The global
generation is now (g + 2). CPU x is still running t, with no context
switch and so per_cpu(reserved_asid, x) = {g,a}.
6. CPU y once again schedules task t', but now *fails* to hit the
reserved ASID from CPU x because of the generation mismatch. This
results in a new ASID being allocated, despite the fact that t is
still running on CPU x with the same mm.
Consequently, TLBIs (e.g. as a result of CoW) will not be synchronised
between the two threads.
This patch fixes the problem by updating all of the matching reserved
ASIDs when we hit on the slowpath (i.e. in step 3 above). This keeps
the reserved ASIDs in-sync with the mm and avoids the problem.
Reported-by: Tony Thompson <anthony.thompson@arm.com>
Reviewed-by: Catalin Marinas <catalin.marinas@arm.com>
Signed-off-by: Will Deacon <will.deacon@arm.com>
Signed-off-by: Catalin Marinas <catalin.marinas@arm.com>
2015-11-26 21:49:39 +08:00
|
|
|
bool hit = false;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Iterate over the set of reserved ASIDs looking for a match.
|
|
|
|
* If we find one, then we can update our mm to use newasid
|
|
|
|
* (i.e. the same ASID in the current generation) but we can't
|
|
|
|
* exit the loop early, since we need to ensure that all copies
|
|
|
|
* of the old ASID are updated to reflect the mm. Failure to do
|
|
|
|
* so could result in us missing the reserved ASID in a future
|
|
|
|
* generation.
|
|
|
|
*/
|
|
|
|
for_each_possible_cpu(cpu) {
|
|
|
|
if (per_cpu(reserved_asids, cpu) == asid) {
|
|
|
|
hit = true;
|
|
|
|
per_cpu(reserved_asids, cpu) = newasid;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return hit;
|
2012-03-05 19:49:28 +08:00
|
|
|
}
|
|
|
|
|
2018-10-06 16:49:04 +08:00
|
|
|
static u64 new_context(struct mm_struct *mm)
|
2012-03-05 19:49:28 +08:00
|
|
|
{
|
2015-10-07 01:46:24 +08:00
|
|
|
static u32 cur_idx = 1;
|
|
|
|
u64 asid = atomic64_read(&mm->context.id);
|
|
|
|
u64 generation = atomic64_read(&asid_generation);
|
2012-03-05 19:49:28 +08:00
|
|
|
|
2015-10-07 01:46:24 +08:00
|
|
|
if (asid != 0) {
|
2021-12-09 09:42:25 +08:00
|
|
|
u64 newasid = asid2ctxid(ctxid2asid(asid), generation);
|
arm64: mm: keep reserved ASIDs in sync with mm after multiple rollovers
Under some unusual context-switching patterns, it is possible to end up
with multiple threads from the same mm running concurrently with
different ASIDs:
1. CPU x schedules task t with mm p containing ASID a and generation g
This task doesn't block and the CPU doesn't context switch.
So:
* per_cpu(active_asid, x) = {g,a}
* p->context.id = {g,a}
2. Some other CPU generates an ASID rollover. The global generation is
now (g + 1). CPU x is still running t, with no context switch and
so per_cpu(reserved_asid, x) = {g,a}
3. CPU y schedules task t', which shares mm p with t. The generation
mismatches, so we take the slowpath and hit the reserved ASID from
CPU x. p is then updated so that p->context.id = {g + 1,a}
4. CPU y schedules some other task u, which has an mm != p.
5. Some other CPU generates *another* CPU rollover. The global
generation is now (g + 2). CPU x is still running t, with no context
switch and so per_cpu(reserved_asid, x) = {g,a}.
6. CPU y once again schedules task t', but now *fails* to hit the
reserved ASID from CPU x because of the generation mismatch. This
results in a new ASID being allocated, despite the fact that t is
still running on CPU x with the same mm.
Consequently, TLBIs (e.g. as a result of CoW) will not be synchronised
between the two threads.
This patch fixes the problem by updating all of the matching reserved
ASIDs when we hit on the slowpath (i.e. in step 3 above). This keeps
the reserved ASIDs in-sync with the mm and avoids the problem.
Reported-by: Tony Thompson <anthony.thompson@arm.com>
Reviewed-by: Catalin Marinas <catalin.marinas@arm.com>
Signed-off-by: Will Deacon <will.deacon@arm.com>
Signed-off-by: Catalin Marinas <catalin.marinas@arm.com>
2015-11-26 21:49:39 +08:00
|
|
|
|
2012-03-05 19:49:28 +08:00
|
|
|
/*
|
2015-10-07 01:46:24 +08:00
|
|
|
* If our current ASID was active during a rollover, we
|
|
|
|
* can continue to use it and this was just a false alarm.
|
2012-03-05 19:49:28 +08:00
|
|
|
*/
|
arm64: mm: keep reserved ASIDs in sync with mm after multiple rollovers
Under some unusual context-switching patterns, it is possible to end up
with multiple threads from the same mm running concurrently with
different ASIDs:
1. CPU x schedules task t with mm p containing ASID a and generation g
This task doesn't block and the CPU doesn't context switch.
So:
* per_cpu(active_asid, x) = {g,a}
* p->context.id = {g,a}
2. Some other CPU generates an ASID rollover. The global generation is
now (g + 1). CPU x is still running t, with no context switch and
so per_cpu(reserved_asid, x) = {g,a}
3. CPU y schedules task t', which shares mm p with t. The generation
mismatches, so we take the slowpath and hit the reserved ASID from
CPU x. p is then updated so that p->context.id = {g + 1,a}
4. CPU y schedules some other task u, which has an mm != p.
5. Some other CPU generates *another* CPU rollover. The global
generation is now (g + 2). CPU x is still running t, with no context
switch and so per_cpu(reserved_asid, x) = {g,a}.
6. CPU y once again schedules task t', but now *fails* to hit the
reserved ASID from CPU x because of the generation mismatch. This
results in a new ASID being allocated, despite the fact that t is
still running on CPU x with the same mm.
Consequently, TLBIs (e.g. as a result of CoW) will not be synchronised
between the two threads.
This patch fixes the problem by updating all of the matching reserved
ASIDs when we hit on the slowpath (i.e. in step 3 above). This keeps
the reserved ASIDs in-sync with the mm and avoids the problem.
Reported-by: Tony Thompson <anthony.thompson@arm.com>
Reviewed-by: Catalin Marinas <catalin.marinas@arm.com>
Signed-off-by: Will Deacon <will.deacon@arm.com>
Signed-off-by: Catalin Marinas <catalin.marinas@arm.com>
2015-11-26 21:49:39 +08:00
|
|
|
if (check_update_reserved_asid(asid, newasid))
|
|
|
|
return newasid;
|
2015-10-07 01:46:24 +08:00
|
|
|
|
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>
2020-09-18 18:18:44 +08:00
|
|
|
/*
|
|
|
|
* If it is pinned, we can keep using it. Note that reserved
|
|
|
|
* takes priority, because even if it is also pinned, we need to
|
|
|
|
* update the generation into the reserved_asids.
|
|
|
|
*/
|
|
|
|
if (refcount_read(&mm->context.pinned))
|
|
|
|
return newasid;
|
|
|
|
|
2015-10-07 01:46:24 +08:00
|
|
|
/*
|
|
|
|
* We had a valid ASID in a previous life, so try to re-use
|
|
|
|
* it if possible.
|
|
|
|
*/
|
2021-12-09 09:42:25 +08:00
|
|
|
if (!__test_and_set_bit(ctxid2asid(asid), asid_map))
|
arm64: mm: keep reserved ASIDs in sync with mm after multiple rollovers
Under some unusual context-switching patterns, it is possible to end up
with multiple threads from the same mm running concurrently with
different ASIDs:
1. CPU x schedules task t with mm p containing ASID a and generation g
This task doesn't block and the CPU doesn't context switch.
So:
* per_cpu(active_asid, x) = {g,a}
* p->context.id = {g,a}
2. Some other CPU generates an ASID rollover. The global generation is
now (g + 1). CPU x is still running t, with no context switch and
so per_cpu(reserved_asid, x) = {g,a}
3. CPU y schedules task t', which shares mm p with t. The generation
mismatches, so we take the slowpath and hit the reserved ASID from
CPU x. p is then updated so that p->context.id = {g + 1,a}
4. CPU y schedules some other task u, which has an mm != p.
5. Some other CPU generates *another* CPU rollover. The global
generation is now (g + 2). CPU x is still running t, with no context
switch and so per_cpu(reserved_asid, x) = {g,a}.
6. CPU y once again schedules task t', but now *fails* to hit the
reserved ASID from CPU x because of the generation mismatch. This
results in a new ASID being allocated, despite the fact that t is
still running on CPU x with the same mm.
Consequently, TLBIs (e.g. as a result of CoW) will not be synchronised
between the two threads.
This patch fixes the problem by updating all of the matching reserved
ASIDs when we hit on the slowpath (i.e. in step 3 above). This keeps
the reserved ASIDs in-sync with the mm and avoids the problem.
Reported-by: Tony Thompson <anthony.thompson@arm.com>
Reviewed-by: Catalin Marinas <catalin.marinas@arm.com>
Signed-off-by: Will Deacon <will.deacon@arm.com>
Signed-off-by: Catalin Marinas <catalin.marinas@arm.com>
2015-11-26 21:49:39 +08:00
|
|
|
return newasid;
|
2012-03-05 19:49:28 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
2015-10-07 01:46:24 +08:00
|
|
|
* Allocate a free ASID. If we can't find one, take a note of the
|
2017-08-10 21:10:28 +08:00
|
|
|
* currently active ASIDs and mark the TLBs as requiring flushes. We
|
|
|
|
* always count from ASID #2 (index 1), as we use ASID #0 when setting
|
|
|
|
* a reserved TTBR0 for the init_mm and we allocate ASIDs in even/odd
|
|
|
|
* pairs.
|
2012-03-05 19:49:28 +08:00
|
|
|
*/
|
2015-10-07 01:46:24 +08:00
|
|
|
asid = find_next_zero_bit(asid_map, NUM_USER_ASIDS, cur_idx);
|
|
|
|
if (asid != NUM_USER_ASIDS)
|
|
|
|
goto set_asid;
|
|
|
|
|
|
|
|
/* We're out of ASIDs, so increment the global generation count */
|
|
|
|
generation = atomic64_add_return_relaxed(ASID_FIRST_VERSION,
|
|
|
|
&asid_generation);
|
2018-10-06 16:49:04 +08:00
|
|
|
flush_context();
|
2015-10-07 01:46:24 +08:00
|
|
|
|
2016-06-18 01:33:00 +08:00
|
|
|
/* We have more ASIDs than CPUs, so this will always succeed */
|
2015-10-07 01:46:24 +08:00
|
|
|
asid = find_next_zero_bit(asid_map, NUM_USER_ASIDS, 1);
|
|
|
|
|
|
|
|
set_asid:
|
|
|
|
__set_bit(asid, asid_map);
|
|
|
|
cur_idx = asid;
|
2021-12-09 09:42:25 +08:00
|
|
|
return asid2ctxid(asid, generation);
|
2012-03-05 19:49:28 +08:00
|
|
|
}
|
|
|
|
|
2020-07-10 22:04:12 +08:00
|
|
|
void check_and_switch_context(struct mm_struct *mm)
|
2012-03-05 19:49:28 +08:00
|
|
|
{
|
2015-10-07 01:46:24 +08:00
|
|
|
unsigned long flags;
|
2020-07-10 22:04:12 +08:00
|
|
|
unsigned int cpu;
|
arm64: asid: Do not replace active_asids if already 0
Under some uncommon timing conditions, a generation check and
xchg(active_asids, A1) in check_and_switch_context() on P1 can race with
an ASID roll-over on P2. If P2 has not seen the update to
active_asids[P1], it can re-allocate A1 to a new task T2 on P2. P1 ends
up waiting on the spinlock since the xchg() returned 0 while P2 can go
through a second ASID roll-over with (T2,A1,G2) active on P2. This
roll-over copies active_asids[P1] == A1,G1 into reserved_asids[P1] and
active_asids[P2] == A1,G2 into reserved_asids[P2]. A subsequent
scheduling of T1 on P1 and T2 on P2 would match reserved_asids and get
their generation bumped to G3:
P1 P2
-- --
TTBR0.BADDR = T0
TTBR0.ASID = A0
asid_generation = G1
check_and_switch_context(T1,A1,G1)
generation match
check_and_switch_context(T2,A0,G0)
new_context()
ASID roll-over
asid_generation = G2
flush_context()
active_asids[P1] = 0
asid_map[A1] = 0
reserved_asids[P1] = A0,G0
xchg(active_asids, A1)
active_asids[P1] = A1,G1
xchg returns 0
spin_lock_irqsave()
allocated ASID (T2,A1,G2)
asid_map[A1] = 1
active_asids[P2] = A1,G2
...
check_and_switch_context(T3,A0,G0)
new_context()
ASID roll-over
asid_generation = G3
flush_context()
active_asids[P1] = 0
asid_map[A1] = 1
reserved_asids[P1] = A1,G1
reserved_asids[P2] = A1,G2
allocated ASID (T3,A2,G3)
asid_map[A2] = 1
active_asids[P2] = A2,G3
new_context()
check_update_reserved_asid(A1,G1)
matches reserved_asid[P1]
reserved_asid[P1] = A1,G3
updated T1 ASID to (T1,A1,G3)
check_and_switch_context(T2,A1,G2)
new_context()
check_and_switch_context(A1,G2)
matches reserved_asids[P2]
reserved_asids[P2] = A1,G3
updated T2 ASID to (T2,A1,G3)
At this point, we have two tasks, T1 and T2 both using ASID A1 with the
latest generation G3. Any of them is allowed to be scheduled on the
other CPU leading to two different tasks with the same ASID on the same
CPU.
This patch changes the xchg to cmpxchg so that the active_asids is only
updated if non-zero to avoid a race with an ASID roll-over on a
different CPU.
The ASID allocation algorithm has been formally verified using the TLA+
model checker (see
https://git.kernel.org/pub/scm/linux/kernel/git/cmarinas/kernel-tla.git/tree/asidalloc.tla
for the spec).
Reviewed-by: Will Deacon <will.deacon@arm.com>
Signed-off-by: Catalin Marinas <catalin.marinas@arm.com>
2017-12-27 23:12:56 +08:00
|
|
|
u64 asid, old_active_asid;
|
2015-10-07 01:46:24 +08:00
|
|
|
|
2018-07-31 21:08:56 +08:00
|
|
|
if (system_supports_cnp())
|
|
|
|
cpu_set_reserved_ttbr0();
|
|
|
|
|
2015-10-07 01:46:24 +08:00
|
|
|
asid = atomic64_read(&mm->context.id);
|
2012-03-05 19:49:28 +08:00
|
|
|
|
2015-06-12 18:24:41 +08:00
|
|
|
/*
|
2017-12-01 02:25:17 +08:00
|
|
|
* The memory ordering here is subtle.
|
arm64: asid: Do not replace active_asids if already 0
Under some uncommon timing conditions, a generation check and
xchg(active_asids, A1) in check_and_switch_context() on P1 can race with
an ASID roll-over on P2. If P2 has not seen the update to
active_asids[P1], it can re-allocate A1 to a new task T2 on P2. P1 ends
up waiting on the spinlock since the xchg() returned 0 while P2 can go
through a second ASID roll-over with (T2,A1,G2) active on P2. This
roll-over copies active_asids[P1] == A1,G1 into reserved_asids[P1] and
active_asids[P2] == A1,G2 into reserved_asids[P2]. A subsequent
scheduling of T1 on P1 and T2 on P2 would match reserved_asids and get
their generation bumped to G3:
P1 P2
-- --
TTBR0.BADDR = T0
TTBR0.ASID = A0
asid_generation = G1
check_and_switch_context(T1,A1,G1)
generation match
check_and_switch_context(T2,A0,G0)
new_context()
ASID roll-over
asid_generation = G2
flush_context()
active_asids[P1] = 0
asid_map[A1] = 0
reserved_asids[P1] = A0,G0
xchg(active_asids, A1)
active_asids[P1] = A1,G1
xchg returns 0
spin_lock_irqsave()
allocated ASID (T2,A1,G2)
asid_map[A1] = 1
active_asids[P2] = A1,G2
...
check_and_switch_context(T3,A0,G0)
new_context()
ASID roll-over
asid_generation = G3
flush_context()
active_asids[P1] = 0
asid_map[A1] = 1
reserved_asids[P1] = A1,G1
reserved_asids[P2] = A1,G2
allocated ASID (T3,A2,G3)
asid_map[A2] = 1
active_asids[P2] = A2,G3
new_context()
check_update_reserved_asid(A1,G1)
matches reserved_asid[P1]
reserved_asid[P1] = A1,G3
updated T1 ASID to (T1,A1,G3)
check_and_switch_context(T2,A1,G2)
new_context()
check_and_switch_context(A1,G2)
matches reserved_asids[P2]
reserved_asids[P2] = A1,G3
updated T2 ASID to (T2,A1,G3)
At this point, we have two tasks, T1 and T2 both using ASID A1 with the
latest generation G3. Any of them is allowed to be scheduled on the
other CPU leading to two different tasks with the same ASID on the same
CPU.
This patch changes the xchg to cmpxchg so that the active_asids is only
updated if non-zero to avoid a race with an ASID roll-over on a
different CPU.
The ASID allocation algorithm has been formally verified using the TLA+
model checker (see
https://git.kernel.org/pub/scm/linux/kernel/git/cmarinas/kernel-tla.git/tree/asidalloc.tla
for the spec).
Reviewed-by: Will Deacon <will.deacon@arm.com>
Signed-off-by: Catalin Marinas <catalin.marinas@arm.com>
2017-12-27 23:12:56 +08:00
|
|
|
* If our active_asids is non-zero and the ASID matches the current
|
|
|
|
* generation, then we update the active_asids entry with a relaxed
|
|
|
|
* cmpxchg. Racing with a concurrent rollover means that either:
|
2017-12-01 02:25:17 +08:00
|
|
|
*
|
arm64: asid: Do not replace active_asids if already 0
Under some uncommon timing conditions, a generation check and
xchg(active_asids, A1) in check_and_switch_context() on P1 can race with
an ASID roll-over on P2. If P2 has not seen the update to
active_asids[P1], it can re-allocate A1 to a new task T2 on P2. P1 ends
up waiting on the spinlock since the xchg() returned 0 while P2 can go
through a second ASID roll-over with (T2,A1,G2) active on P2. This
roll-over copies active_asids[P1] == A1,G1 into reserved_asids[P1] and
active_asids[P2] == A1,G2 into reserved_asids[P2]. A subsequent
scheduling of T1 on P1 and T2 on P2 would match reserved_asids and get
their generation bumped to G3:
P1 P2
-- --
TTBR0.BADDR = T0
TTBR0.ASID = A0
asid_generation = G1
check_and_switch_context(T1,A1,G1)
generation match
check_and_switch_context(T2,A0,G0)
new_context()
ASID roll-over
asid_generation = G2
flush_context()
active_asids[P1] = 0
asid_map[A1] = 0
reserved_asids[P1] = A0,G0
xchg(active_asids, A1)
active_asids[P1] = A1,G1
xchg returns 0
spin_lock_irqsave()
allocated ASID (T2,A1,G2)
asid_map[A1] = 1
active_asids[P2] = A1,G2
...
check_and_switch_context(T3,A0,G0)
new_context()
ASID roll-over
asid_generation = G3
flush_context()
active_asids[P1] = 0
asid_map[A1] = 1
reserved_asids[P1] = A1,G1
reserved_asids[P2] = A1,G2
allocated ASID (T3,A2,G3)
asid_map[A2] = 1
active_asids[P2] = A2,G3
new_context()
check_update_reserved_asid(A1,G1)
matches reserved_asid[P1]
reserved_asid[P1] = A1,G3
updated T1 ASID to (T1,A1,G3)
check_and_switch_context(T2,A1,G2)
new_context()
check_and_switch_context(A1,G2)
matches reserved_asids[P2]
reserved_asids[P2] = A1,G3
updated T2 ASID to (T2,A1,G3)
At this point, we have two tasks, T1 and T2 both using ASID A1 with the
latest generation G3. Any of them is allowed to be scheduled on the
other CPU leading to two different tasks with the same ASID on the same
CPU.
This patch changes the xchg to cmpxchg so that the active_asids is only
updated if non-zero to avoid a race with an ASID roll-over on a
different CPU.
The ASID allocation algorithm has been formally verified using the TLA+
model checker (see
https://git.kernel.org/pub/scm/linux/kernel/git/cmarinas/kernel-tla.git/tree/asidalloc.tla
for the spec).
Reviewed-by: Will Deacon <will.deacon@arm.com>
Signed-off-by: Catalin Marinas <catalin.marinas@arm.com>
2017-12-27 23:12:56 +08:00
|
|
|
* - We get a zero back from the cmpxchg and end up waiting on the
|
2017-12-01 02:25:17 +08:00
|
|
|
* lock. Taking the lock synchronises with the rollover and so
|
|
|
|
* we are forced to see the updated generation.
|
|
|
|
*
|
arm64: asid: Do not replace active_asids if already 0
Under some uncommon timing conditions, a generation check and
xchg(active_asids, A1) in check_and_switch_context() on P1 can race with
an ASID roll-over on P2. If P2 has not seen the update to
active_asids[P1], it can re-allocate A1 to a new task T2 on P2. P1 ends
up waiting on the spinlock since the xchg() returned 0 while P2 can go
through a second ASID roll-over with (T2,A1,G2) active on P2. This
roll-over copies active_asids[P1] == A1,G1 into reserved_asids[P1] and
active_asids[P2] == A1,G2 into reserved_asids[P2]. A subsequent
scheduling of T1 on P1 and T2 on P2 would match reserved_asids and get
their generation bumped to G3:
P1 P2
-- --
TTBR0.BADDR = T0
TTBR0.ASID = A0
asid_generation = G1
check_and_switch_context(T1,A1,G1)
generation match
check_and_switch_context(T2,A0,G0)
new_context()
ASID roll-over
asid_generation = G2
flush_context()
active_asids[P1] = 0
asid_map[A1] = 0
reserved_asids[P1] = A0,G0
xchg(active_asids, A1)
active_asids[P1] = A1,G1
xchg returns 0
spin_lock_irqsave()
allocated ASID (T2,A1,G2)
asid_map[A1] = 1
active_asids[P2] = A1,G2
...
check_and_switch_context(T3,A0,G0)
new_context()
ASID roll-over
asid_generation = G3
flush_context()
active_asids[P1] = 0
asid_map[A1] = 1
reserved_asids[P1] = A1,G1
reserved_asids[P2] = A1,G2
allocated ASID (T3,A2,G3)
asid_map[A2] = 1
active_asids[P2] = A2,G3
new_context()
check_update_reserved_asid(A1,G1)
matches reserved_asid[P1]
reserved_asid[P1] = A1,G3
updated T1 ASID to (T1,A1,G3)
check_and_switch_context(T2,A1,G2)
new_context()
check_and_switch_context(A1,G2)
matches reserved_asids[P2]
reserved_asids[P2] = A1,G3
updated T2 ASID to (T2,A1,G3)
At this point, we have two tasks, T1 and T2 both using ASID A1 with the
latest generation G3. Any of them is allowed to be scheduled on the
other CPU leading to two different tasks with the same ASID on the same
CPU.
This patch changes the xchg to cmpxchg so that the active_asids is only
updated if non-zero to avoid a race with an ASID roll-over on a
different CPU.
The ASID allocation algorithm has been formally verified using the TLA+
model checker (see
https://git.kernel.org/pub/scm/linux/kernel/git/cmarinas/kernel-tla.git/tree/asidalloc.tla
for the spec).
Reviewed-by: Will Deacon <will.deacon@arm.com>
Signed-off-by: Catalin Marinas <catalin.marinas@arm.com>
2017-12-27 23:12:56 +08:00
|
|
|
* - We get a valid ASID back from the cmpxchg, which means the
|
2017-12-01 02:25:17 +08:00
|
|
|
* relaxed xchg in flush_context will treat us as reserved
|
|
|
|
* because atomic RmWs are totally ordered for a given location.
|
2015-06-12 18:24:41 +08:00
|
|
|
*/
|
2020-07-10 22:04:12 +08:00
|
|
|
old_active_asid = atomic64_read(this_cpu_ptr(&active_asids));
|
2020-05-20 01:54:43 +08:00
|
|
|
if (old_active_asid && asid_gen_match(asid) &&
|
2020-07-10 22:04:12 +08:00
|
|
|
atomic64_cmpxchg_relaxed(this_cpu_ptr(&active_asids),
|
arm64: asid: Do not replace active_asids if already 0
Under some uncommon timing conditions, a generation check and
xchg(active_asids, A1) in check_and_switch_context() on P1 can race with
an ASID roll-over on P2. If P2 has not seen the update to
active_asids[P1], it can re-allocate A1 to a new task T2 on P2. P1 ends
up waiting on the spinlock since the xchg() returned 0 while P2 can go
through a second ASID roll-over with (T2,A1,G2) active on P2. This
roll-over copies active_asids[P1] == A1,G1 into reserved_asids[P1] and
active_asids[P2] == A1,G2 into reserved_asids[P2]. A subsequent
scheduling of T1 on P1 and T2 on P2 would match reserved_asids and get
their generation bumped to G3:
P1 P2
-- --
TTBR0.BADDR = T0
TTBR0.ASID = A0
asid_generation = G1
check_and_switch_context(T1,A1,G1)
generation match
check_and_switch_context(T2,A0,G0)
new_context()
ASID roll-over
asid_generation = G2
flush_context()
active_asids[P1] = 0
asid_map[A1] = 0
reserved_asids[P1] = A0,G0
xchg(active_asids, A1)
active_asids[P1] = A1,G1
xchg returns 0
spin_lock_irqsave()
allocated ASID (T2,A1,G2)
asid_map[A1] = 1
active_asids[P2] = A1,G2
...
check_and_switch_context(T3,A0,G0)
new_context()
ASID roll-over
asid_generation = G3
flush_context()
active_asids[P1] = 0
asid_map[A1] = 1
reserved_asids[P1] = A1,G1
reserved_asids[P2] = A1,G2
allocated ASID (T3,A2,G3)
asid_map[A2] = 1
active_asids[P2] = A2,G3
new_context()
check_update_reserved_asid(A1,G1)
matches reserved_asid[P1]
reserved_asid[P1] = A1,G3
updated T1 ASID to (T1,A1,G3)
check_and_switch_context(T2,A1,G2)
new_context()
check_and_switch_context(A1,G2)
matches reserved_asids[P2]
reserved_asids[P2] = A1,G3
updated T2 ASID to (T2,A1,G3)
At this point, we have two tasks, T1 and T2 both using ASID A1 with the
latest generation G3. Any of them is allowed to be scheduled on the
other CPU leading to two different tasks with the same ASID on the same
CPU.
This patch changes the xchg to cmpxchg so that the active_asids is only
updated if non-zero to avoid a race with an ASID roll-over on a
different CPU.
The ASID allocation algorithm has been formally verified using the TLA+
model checker (see
https://git.kernel.org/pub/scm/linux/kernel/git/cmarinas/kernel-tla.git/tree/asidalloc.tla
for the spec).
Reviewed-by: Will Deacon <will.deacon@arm.com>
Signed-off-by: Catalin Marinas <catalin.marinas@arm.com>
2017-12-27 23:12:56 +08:00
|
|
|
old_active_asid, asid))
|
2015-10-07 01:46:24 +08:00
|
|
|
goto switch_mm_fastpath;
|
|
|
|
|
|
|
|
raw_spin_lock_irqsave(&cpu_asid_lock, flags);
|
|
|
|
/* Check that our ASID belongs to the current generation. */
|
|
|
|
asid = atomic64_read(&mm->context.id);
|
2020-05-20 01:54:43 +08:00
|
|
|
if (!asid_gen_match(asid)) {
|
2018-10-06 16:49:04 +08:00
|
|
|
asid = new_context(mm);
|
2015-10-07 01:46:24 +08:00
|
|
|
atomic64_set(&mm->context.id, asid);
|
|
|
|
}
|
2015-06-12 18:24:41 +08:00
|
|
|
|
2020-07-10 22:04:12 +08:00
|
|
|
cpu = smp_processor_id();
|
2015-10-07 01:46:24 +08:00
|
|
|
if (cpumask_test_and_clear_cpu(cpu, &tlb_flush_pending))
|
|
|
|
local_flush_tlb_all();
|
2012-03-05 19:49:28 +08:00
|
|
|
|
2020-07-10 22:04:12 +08:00
|
|
|
atomic64_set(this_cpu_ptr(&active_asids), asid);
|
2015-10-07 01:46:24 +08:00
|
|
|
raw_spin_unlock_irqrestore(&cpu_asid_lock, flags);
|
2012-03-05 19:49:28 +08:00
|
|
|
|
2015-10-07 01:46:24 +08:00
|
|
|
switch_mm_fastpath:
|
2018-01-19 23:42:09 +08:00
|
|
|
|
|
|
|
arm64_apply_bp_hardening();
|
|
|
|
|
2016-09-02 21:54:03 +08:00
|
|
|
/*
|
|
|
|
* Defer TTBR0_EL1 setting for user threads to uaccess_enable() when
|
|
|
|
* emulating PAN.
|
|
|
|
*/
|
|
|
|
if (!system_uses_ttbr0_pan())
|
|
|
|
cpu_switch_mm(mm->pgd, mm);
|
2012-03-05 19:49:28 +08:00
|
|
|
}
|
|
|
|
|
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>
2020-09-18 18:18:44 +08:00
|
|
|
unsigned long arm64_mm_context_get(struct mm_struct *mm)
|
|
|
|
{
|
|
|
|
unsigned long flags;
|
|
|
|
u64 asid;
|
|
|
|
|
|
|
|
if (!pinned_asid_map)
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
raw_spin_lock_irqsave(&cpu_asid_lock, flags);
|
|
|
|
|
|
|
|
asid = atomic64_read(&mm->context.id);
|
|
|
|
|
|
|
|
if (refcount_inc_not_zero(&mm->context.pinned))
|
|
|
|
goto out_unlock;
|
|
|
|
|
|
|
|
if (nr_pinned_asids >= max_pinned_asids) {
|
|
|
|
asid = 0;
|
|
|
|
goto out_unlock;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!asid_gen_match(asid)) {
|
|
|
|
/*
|
|
|
|
* We went through one or more rollover since that ASID was
|
|
|
|
* used. Ensure that it is still valid, or generate a new one.
|
|
|
|
*/
|
|
|
|
asid = new_context(mm);
|
|
|
|
atomic64_set(&mm->context.id, asid);
|
|
|
|
}
|
|
|
|
|
|
|
|
nr_pinned_asids++;
|
2021-12-09 09:42:25 +08:00
|
|
|
__set_bit(ctxid2asid(asid), pinned_asid_map);
|
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>
2020-09-18 18:18:44 +08:00
|
|
|
refcount_set(&mm->context.pinned, 1);
|
|
|
|
|
|
|
|
out_unlock:
|
|
|
|
raw_spin_unlock_irqrestore(&cpu_asid_lock, flags);
|
|
|
|
|
2021-12-09 09:42:25 +08:00
|
|
|
asid = ctxid2asid(asid);
|
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>
2020-09-18 18:18:44 +08:00
|
|
|
|
|
|
|
/* Set the equivalent of USER_ASID_BIT */
|
|
|
|
if (asid && arm64_kernel_unmapped_at_el0())
|
|
|
|
asid |= 1;
|
|
|
|
|
|
|
|
return asid;
|
|
|
|
}
|
|
|
|
EXPORT_SYMBOL_GPL(arm64_mm_context_get);
|
|
|
|
|
|
|
|
void arm64_mm_context_put(struct mm_struct *mm)
|
|
|
|
{
|
|
|
|
unsigned long flags;
|
|
|
|
u64 asid = atomic64_read(&mm->context.id);
|
|
|
|
|
|
|
|
if (!pinned_asid_map)
|
|
|
|
return;
|
|
|
|
|
|
|
|
raw_spin_lock_irqsave(&cpu_asid_lock, flags);
|
|
|
|
|
|
|
|
if (refcount_dec_and_test(&mm->context.pinned)) {
|
2021-12-09 09:42:25 +08:00
|
|
|
__clear_bit(ctxid2asid(asid), pinned_asid_map);
|
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>
2020-09-18 18:18:44 +08:00
|
|
|
nr_pinned_asids--;
|
|
|
|
}
|
|
|
|
|
|
|
|
raw_spin_unlock_irqrestore(&cpu_asid_lock, flags);
|
|
|
|
}
|
|
|
|
EXPORT_SYMBOL_GPL(arm64_mm_context_put);
|
|
|
|
|
2018-01-03 02:19:39 +08:00
|
|
|
/* Errata workaround post TTBRx_EL1 update. */
|
|
|
|
asmlinkage void post_ttbr_update_workaround(void)
|
|
|
|
{
|
2020-02-13 20:14:52 +08:00
|
|
|
if (!IS_ENABLED(CONFIG_CAVIUM_ERRATUM_27456))
|
|
|
|
return;
|
|
|
|
|
2018-01-03 02:19:39 +08:00
|
|
|
asm(ALTERNATIVE("nop; nop; nop",
|
|
|
|
"ic iallu; dsb nsh; isb",
|
2020-02-13 20:14:52 +08:00
|
|
|
ARM64_WORKAROUND_CAVIUM_27456));
|
|
|
|
}
|
|
|
|
|
|
|
|
void cpu_do_switch_mm(phys_addr_t pgd_phys, struct mm_struct *mm)
|
|
|
|
{
|
|
|
|
unsigned long ttbr1 = read_sysreg(ttbr1_el1);
|
|
|
|
unsigned long asid = ASID(mm);
|
|
|
|
unsigned long ttbr0 = phys_to_ttbr(pgd_phys);
|
|
|
|
|
|
|
|
/* Skip CNP for the reserved ASID */
|
|
|
|
if (system_supports_cnp() && asid)
|
|
|
|
ttbr0 |= TTBR_CNP_BIT;
|
|
|
|
|
|
|
|
/* SW PAN needs a copy of the ASID in TTBR0 for entry */
|
|
|
|
if (IS_ENABLED(CONFIG_ARM64_SW_TTBR0_PAN))
|
|
|
|
ttbr0 |= FIELD_PREP(TTBR_ASID_MASK, asid);
|
|
|
|
|
|
|
|
/* Set ASID in TTBR1 since TCR.A1 is set */
|
|
|
|
ttbr1 &= ~TTBR_ASID_MASK;
|
|
|
|
ttbr1 |= FIELD_PREP(TTBR_ASID_MASK, asid);
|
|
|
|
|
|
|
|
write_sysreg(ttbr1, ttbr1_el1);
|
|
|
|
isb();
|
|
|
|
write_sysreg(ttbr0, ttbr0_el1);
|
|
|
|
isb();
|
|
|
|
post_ttbr_update_workaround();
|
2018-01-03 02:19:39 +08:00
|
|
|
}
|
|
|
|
|
2020-02-27 16:34:47 +08:00
|
|
|
static int asids_update_limit(void)
|
2012-03-05 19:49:28 +08:00
|
|
|
{
|
2020-02-27 16:34:47 +08:00
|
|
|
unsigned long num_available_asids = NUM_USER_ASIDS;
|
|
|
|
|
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>
2020-09-18 18:18:44 +08:00
|
|
|
if (arm64_kernel_unmapped_at_el0()) {
|
2020-02-27 16:34:47 +08:00
|
|
|
num_available_asids /= 2;
|
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>
2020-09-18 18:18:44 +08:00
|
|
|
if (pinned_asid_map)
|
|
|
|
set_kpti_asid_bits(pinned_asid_map);
|
|
|
|
}
|
2016-06-18 01:33:00 +08:00
|
|
|
/*
|
|
|
|
* Expect allocation after rollover to fail if we don't have at least
|
|
|
|
* one more ASID than CPUs. ASID #0 is reserved for init_mm.
|
|
|
|
*/
|
2020-02-27 16:34:47 +08:00
|
|
|
WARN_ON(num_available_asids - 1 <= num_possible_cpus());
|
|
|
|
pr_info("ASID allocator initialised with %lu entries\n",
|
|
|
|
num_available_asids);
|
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>
2020-09-18 18:18:44 +08:00
|
|
|
|
|
|
|
/*
|
|
|
|
* There must always be an ASID available after rollover. Ensure that,
|
|
|
|
* even if all CPUs have a reserved ASID and the maximum number of ASIDs
|
|
|
|
* are pinned, there still is at least one empty slot in the ASID map.
|
|
|
|
*/
|
|
|
|
max_pinned_asids = num_available_asids - num_possible_cpus() - 2;
|
2020-02-27 16:34:47 +08:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
arch_initcall(asids_update_limit);
|
|
|
|
|
|
|
|
static int asids_init(void)
|
|
|
|
{
|
|
|
|
asid_bits = get_cpu_asid_bits();
|
2015-10-07 01:46:24 +08:00
|
|
|
atomic64_set(&asid_generation, ASID_FIRST_VERSION);
|
2021-05-29 19:15:10 +08:00
|
|
|
asid_map = bitmap_zalloc(NUM_USER_ASIDS, GFP_KERNEL);
|
2015-10-07 01:46:24 +08:00
|
|
|
if (!asid_map)
|
|
|
|
panic("Failed to allocate bitmap for %lu ASIDs\n",
|
|
|
|
NUM_USER_ASIDS);
|
|
|
|
|
2021-05-29 19:15:10 +08:00
|
|
|
pinned_asid_map = bitmap_zalloc(NUM_USER_ASIDS, GFP_KERNEL);
|
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>
2020-09-18 18:18:44 +08:00
|
|
|
nr_pinned_asids = 0;
|
|
|
|
|
2020-01-07 18:28:03 +08:00
|
|
|
/*
|
|
|
|
* We cannot call set_reserved_asid_bits() here because CPU
|
|
|
|
* caps are not finalized yet, so it is safer to assume KPTI
|
|
|
|
* and reserve kernel ASID's from beginning.
|
|
|
|
*/
|
|
|
|
if (IS_ENABLED(CONFIG_UNMAP_KERNEL_AT_EL0))
|
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>
2020-09-18 18:18:44 +08:00
|
|
|
set_kpti_asid_bits(asid_map);
|
2015-10-07 01:46:24 +08:00
|
|
|
return 0;
|
2012-03-05 19:49:28 +08:00
|
|
|
}
|
2015-10-07 01:46:24 +08:00
|
|
|
early_initcall(asids_init);
|