Skip to content

Commit 6b9391b

Browse files
charlie-rivospalmer-dabbelt
authored andcommitted
riscv: Include riscv_set_icache_flush_ctx prctl
Support new prctl with key PR_RISCV_SET_ICACHE_FLUSH_CTX to enable optimization of cross modifying code. This prctl enables userspace code to use icache flushing instructions such as fence.i with the guarantee that the icache will continue to be clean after thread migration. Signed-off-by: Charlie Jenkins <[email protected]> Reviewed-by: Atish Patra <[email protected]> Reviewed-by: Alexandre Ghiti <[email protected]> Reviewed-by: Samuel Holland <[email protected]> Link: https://lore.kernel.org/r/[email protected] Signed-off-by: Palmer Dabbelt <[email protected]>
1 parent bebc345 commit 6b9391b

File tree

7 files changed

+171
-8
lines changed

7 files changed

+171
-8
lines changed

arch/riscv/include/asm/mmu.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@ typedef struct {
1919
#ifdef CONFIG_SMP
2020
/* A local icache flush is needed before user execution can resume. */
2121
cpumask_t icache_stale_mask;
22+
/* Force local icache flush on all migrations. */
23+
bool force_icache_flush;
2224
#endif
2325
#ifdef CONFIG_BINFMT_ELF_FDPIC
2426
unsigned long exec_fdpic_loadmap;

arch/riscv/include/asm/processor.h

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@
6969
#endif
7070

7171
#ifndef __ASSEMBLY__
72+
#include <linux/cpumask.h>
7273

7374
struct task_struct;
7475
struct pt_regs;
@@ -123,6 +124,12 @@ struct thread_struct {
123124
struct __riscv_v_ext_state vstate;
124125
unsigned long align_ctl;
125126
struct __riscv_v_ext_state kernel_vstate;
127+
#ifdef CONFIG_SMP
128+
/* Flush the icache on migration */
129+
bool force_icache_flush;
130+
/* A forced icache flush is not needed if migrating to the previous cpu. */
131+
unsigned int prev_cpu;
132+
#endif
126133
};
127134

128135
/* Whitelist the fstate from the task_struct for hardened usercopy */
@@ -184,6 +191,9 @@ extern int set_unalign_ctl(struct task_struct *tsk, unsigned int val);
184191
#define GET_UNALIGN_CTL(tsk, addr) get_unalign_ctl((tsk), (addr))
185192
#define SET_UNALIGN_CTL(tsk, val) set_unalign_ctl((tsk), (val))
186193

194+
#define RISCV_SET_ICACHE_FLUSH_CTX(arg1, arg2) riscv_set_icache_flush_ctx(arg1, arg2)
195+
extern int riscv_set_icache_flush_ctx(unsigned long ctx, unsigned long per_thread);
196+
187197
#endif /* __ASSEMBLY__ */
188198

189199
#endif /* _ASM_RISCV_PROCESSOR_H */

arch/riscv/include/asm/switch_to.h

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88

99
#include <linux/jump_label.h>
1010
#include <linux/sched/task_stack.h>
11+
#include <linux/mm_types.h>
1112
#include <asm/vector.h>
1213
#include <asm/cpufeature.h>
1314
#include <asm/processor.h>
@@ -72,14 +73,36 @@ static __always_inline bool has_fpu(void) { return false; }
7273
extern struct task_struct *__switch_to(struct task_struct *,
7374
struct task_struct *);
7475

76+
static inline bool switch_to_should_flush_icache(struct task_struct *task)
77+
{
78+
#ifdef CONFIG_SMP
79+
bool stale_mm = task->mm && task->mm->context.force_icache_flush;
80+
bool stale_thread = task->thread.force_icache_flush;
81+
bool thread_migrated = smp_processor_id() != task->thread.prev_cpu;
82+
83+
return thread_migrated && (stale_mm || stale_thread);
84+
#else
85+
return false;
86+
#endif
87+
}
88+
89+
#ifdef CONFIG_SMP
90+
#define __set_prev_cpu(thread) ((thread).prev_cpu = smp_processor_id())
91+
#else
92+
#define __set_prev_cpu(thread)
93+
#endif
94+
7595
#define switch_to(prev, next, last) \
7696
do { \
7797
struct task_struct *__prev = (prev); \
7898
struct task_struct *__next = (next); \
99+
__set_prev_cpu(__prev->thread); \
79100
if (has_fpu()) \
80101
__switch_to_fpu(__prev, __next); \
81102
if (has_vector()) \
82103
__switch_to_vector(__prev, __next); \
104+
if (switch_to_should_flush_icache(__next)) \
105+
local_flush_icache_all(); \
83106
((last) = __switch_to(__prev, __next)); \
84107
} while (0)
85108

arch/riscv/mm/cacheflush.c

Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55

66
#include <linux/acpi.h>
77
#include <linux/of.h>
8+
#include <linux/prctl.h>
89
#include <asm/acpi.h>
910
#include <asm/cacheflush.h>
1011

@@ -152,3 +153,115 @@ void __init riscv_init_cbo_blocksizes(void)
152153
if (cboz_block_size)
153154
riscv_cboz_block_size = cboz_block_size;
154155
}
156+
157+
#ifdef CONFIG_SMP
158+
static void set_icache_stale_mask(void)
159+
{
160+
cpumask_t *mask;
161+
bool stale_cpu;
162+
163+
/*
164+
* Mark every other hart's icache as needing a flush for
165+
* this MM. Maintain the previous value of the current
166+
* cpu to handle the case when this function is called
167+
* concurrently on different harts.
168+
*/
169+
mask = &current->mm->context.icache_stale_mask;
170+
stale_cpu = cpumask_test_cpu(smp_processor_id(), mask);
171+
172+
cpumask_setall(mask);
173+
assign_bit(cpumask_check(smp_processor_id()), cpumask_bits(mask), stale_cpu);
174+
}
175+
#endif
176+
177+
/**
178+
* riscv_set_icache_flush_ctx() - Enable/disable icache flushing instructions in
179+
* userspace.
180+
* @ctx: Set the type of icache flushing instructions permitted/prohibited in
181+
* userspace. Supported values described below.
182+
*
183+
* Supported values for ctx:
184+
*
185+
* * %PR_RISCV_CTX_SW_FENCEI_ON: Allow fence.i in user space.
186+
*
187+
* * %PR_RISCV_CTX_SW_FENCEI_OFF: Disallow fence.i in user space. All threads in
188+
* a process will be affected when ``scope == PR_RISCV_SCOPE_PER_PROCESS``.
189+
* Therefore, caution must be taken; use this flag only when you can guarantee
190+
* that no thread in the process will emit fence.i from this point onward.
191+
*
192+
* @scope: Set scope of where icache flushing instructions are allowed to be
193+
* emitted. Supported values described below.
194+
*
195+
* Supported values for scope:
196+
*
197+
* * %PR_RISCV_SCOPE_PER_PROCESS: Ensure the icache of any thread in this process
198+
* is coherent with instruction storage upon
199+
* migration.
200+
*
201+
* * %PR_RISCV_SCOPE_PER_THREAD: Ensure the icache of the current thread is
202+
* coherent with instruction storage upon
203+
* migration.
204+
*
205+
* When ``scope == PR_RISCV_SCOPE_PER_PROCESS``, all threads in the process are
206+
* permitted to emit icache flushing instructions. Whenever any thread in the
207+
* process is migrated, the corresponding hart's icache will be guaranteed to be
208+
* consistent with instruction storage. This does not enforce any guarantees
209+
* outside of migration. If a thread modifies an instruction that another thread
210+
* may attempt to execute, the other thread must still emit an icache flushing
211+
* instruction before attempting to execute the potentially modified
212+
* instruction. This must be performed by the user-space program.
213+
*
214+
* In per-thread context (eg. ``scope == PR_RISCV_SCOPE_PER_THREAD``) only the
215+
* thread calling this function is permitted to emit icache flushing
216+
* instructions. When the thread is migrated, the corresponding hart's icache
217+
* will be guaranteed to be consistent with instruction storage.
218+
*
219+
* On kernels configured without SMP, this function is a nop as migrations
220+
* across harts will not occur.
221+
*/
222+
int riscv_set_icache_flush_ctx(unsigned long ctx, unsigned long scope)
223+
{
224+
#ifdef CONFIG_SMP
225+
switch (ctx) {
226+
case PR_RISCV_CTX_SW_FENCEI_ON:
227+
switch (scope) {
228+
case PR_RISCV_SCOPE_PER_PROCESS:
229+
current->mm->context.force_icache_flush = true;
230+
break;
231+
case PR_RISCV_SCOPE_PER_THREAD:
232+
current->thread.force_icache_flush = true;
233+
break;
234+
default:
235+
return -EINVAL;
236+
}
237+
break;
238+
case PR_RISCV_CTX_SW_FENCEI_OFF:
239+
switch (scope) {
240+
case PR_RISCV_SCOPE_PER_PROCESS:
241+
current->mm->context.force_icache_flush = false;
242+
243+
set_icache_stale_mask();
244+
break;
245+
case PR_RISCV_SCOPE_PER_THREAD:
246+
current->thread.force_icache_flush = false;
247+
248+
set_icache_stale_mask();
249+
break;
250+
default:
251+
return -EINVAL;
252+
}
253+
break;
254+
default:
255+
return -EINVAL;
256+
}
257+
return 0;
258+
#else
259+
switch (ctx) {
260+
case PR_RISCV_CTX_SW_FENCEI_ON:
261+
case PR_RISCV_CTX_SW_FENCEI_OFF:
262+
return 0;
263+
default:
264+
return -EINVAL;
265+
}
266+
#endif
267+
}

arch/riscv/mm/context.c

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
#include <asm/tlbflush.h>
1616
#include <asm/cacheflush.h>
1717
#include <asm/mmu_context.h>
18+
#include <asm/switch_to.h>
1819

1920
#ifdef CONFIG_MMU
2021

@@ -297,21 +298,23 @@ static inline void set_mm(struct mm_struct *prev,
297298
*
298299
* The "cpu" argument must be the current local CPU number.
299300
*/
300-
static inline void flush_icache_deferred(struct mm_struct *mm, unsigned int cpu)
301+
static inline void flush_icache_deferred(struct mm_struct *mm, unsigned int cpu,
302+
struct task_struct *task)
301303
{
302304
#ifdef CONFIG_SMP
303-
cpumask_t *mask = &mm->context.icache_stale_mask;
304-
305-
if (cpumask_test_cpu(cpu, mask)) {
306-
cpumask_clear_cpu(cpu, mask);
305+
if (cpumask_test_and_clear_cpu(cpu, &mm->context.icache_stale_mask)) {
307306
/*
308307
* Ensure the remote hart's writes are visible to this hart.
309308
* This pairs with a barrier in flush_icache_mm.
310309
*/
311310
smp_mb();
312-
local_flush_icache_all();
313-
}
314311

312+
/*
313+
* If cache will be flushed in switch_to, no need to flush here.
314+
*/
315+
if (!(task && switch_to_should_flush_icache(task)))
316+
local_flush_icache_all();
317+
}
315318
#endif
316319
}
317320

@@ -332,5 +335,5 @@ void switch_mm(struct mm_struct *prev, struct mm_struct *next,
332335

333336
set_mm(prev, next, cpu);
334337

335-
flush_icache_deferred(next, cpu);
338+
flush_icache_deferred(next, cpu, task);
336339
}

include/uapi/linux/prctl.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -306,4 +306,10 @@ struct prctl_mm_map {
306306
# define PR_RISCV_V_VSTATE_CTRL_NEXT_MASK 0xc
307307
# define PR_RISCV_V_VSTATE_CTRL_MASK 0x1f
308308

309+
#define PR_RISCV_SET_ICACHE_FLUSH_CTX 71
310+
# define PR_RISCV_CTX_SW_FENCEI_ON 0
311+
# define PR_RISCV_CTX_SW_FENCEI_OFF 1
312+
# define PR_RISCV_SCOPE_PER_PROCESS 0
313+
# define PR_RISCV_SCOPE_PER_THREAD 1
314+
309315
#endif /* _LINUX_PRCTL_H */

kernel/sys.c

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,9 @@
146146
#ifndef RISCV_V_GET_CONTROL
147147
# define RISCV_V_GET_CONTROL() (-EINVAL)
148148
#endif
149+
#ifndef RISCV_SET_ICACHE_FLUSH_CTX
150+
# define RISCV_SET_ICACHE_FLUSH_CTX(a, b) (-EINVAL)
151+
#endif
149152

150153
/*
151154
* this is where the system-wide overflow UID and GID are defined, for
@@ -2743,6 +2746,9 @@ SYSCALL_DEFINE5(prctl, int, option, unsigned long, arg2, unsigned long, arg3,
27432746
case PR_RISCV_V_GET_CONTROL:
27442747
error = RISCV_V_GET_CONTROL();
27452748
break;
2749+
case PR_RISCV_SET_ICACHE_FLUSH_CTX:
2750+
error = RISCV_SET_ICACHE_FLUSH_CTX(arg2, arg3);
2751+
break;
27462752
default:
27472753
error = -EINVAL;
27482754
break;

0 commit comments

Comments
 (0)