[PATCH v4 2/2] add icswx support
Benjamin Herrenschmidt
benh at kernel.crashing.org
Fri Mar 4 12:02:40 EST 2011
On Wed, 2011-03-02 at 11:20 -0600, Tseng-Hui (Frank) Lin wrote:
> +#define CPU_FTR_ICSWX LONG_ASM_CONST(0x1000000000000000)
Do we want a userspace visible feature as well ? Or some other way to
inform userspace that we support icswx ?
> index acac35d..b0c2549 100644
> --- a/arch/powerpc/include/asm/mmu-hash64.h
> +++ b/arch/powerpc/include/asm/mmu-hash64.h
> @@ -409,6 +409,14 @@ static inline void subpage_prot_init_new_context(struct mm_struct *mm) { }
>
> typedef unsigned long mm_context_id_t;
>
> +#ifdef CONFIG_ICSWX
CONFIG_PPC_ICSWX please.
> +/*
> + * Use forward declearation to avoid including linux/spinlock_types.h
> + * which breaks kernel build.
> + */
> +struct spinlock;
> +#endif /* CONFIG_ICSWX */
> +
Yuck. Instead put all your fields into a structure called something
like struct copro_data, make that a fwd declaration and stick a pointer
to it in the mm_context.
It then only need to be allocated for processes that try to use copros,
and the definition of that structure can remain local to whatever header
you have dedicated for that.
> typedef struct {
> mm_context_id_t id;
> u16 user_psize; /* page size index */
> @@ -423,6 +431,11 @@ typedef struct {
> #ifdef CONFIG_PPC_SUBPAGE_PROT
> struct subpage_prot_table spt;
> #endif /* CONFIG_PPC_SUBPAGE_PROT */
> +#ifdef CONFIG_ICSWX
> + struct spinlock *cop_lockp; /* guard acop and cop_pid */
> + unsigned long acop; /* mask of enabled coprocessor types */
> + unsigned int cop_pid; /* pid value used with coprocessors */
> +#endif /* CONFIG_ICSWX */
> } mm_context_t;
>
>
> diff --git a/arch/powerpc/include/asm/mmu_context.h b/arch/powerpc/include/asm/mmu_context.h
> index 81fb412..fe0a09a 100644
> --- a/arch/powerpc/include/asm/mmu_context.h
> +++ b/arch/powerpc/include/asm/mmu_context.h
> @@ -32,6 +32,12 @@ extern void __destroy_context(unsigned long context_id);
> extern void mmu_context_init(void);
> #endif
>
> +#ifdef CONFIG_ICSWX
> +extern void switch_cop(struct mm_struct *next);
> +extern int use_cop(unsigned long acop, struct mm_struct *mm);
> +extern void drop_cop(unsigned long acop, struct mm_struct *mm);
> +#endif /* CONFIG_ICSWX */
No need to ifdef declarations.
.../...
> +
> +#ifdef CONFIG_ICSWX_LAZY_SWITCH
> +static DEFINE_PER_CPU(unsigned long, acop_reg);
> +#define mtspr_acop(val) \
> + if (__get_cpu_var(acop_reg) != val) { \
> + get_cpu_var(acop_reg) = val; \
> + mtspr(SPRN_ACOP, val); \
> + put_cpu_var(acop_reg); \
> + }
Why get/put games here since you did a __get in the first place ?
Doesn't make much sense. This is all inside context switch anyways so
you just do __ always and don't bother with put.
> +#else
> +#define mtspr_acop(val) mtspr(SPRN_ACOP, val)
> +#endif /* CONFIG_ICSWX_LAZY_SWITCH */
I'm not sure I totally get the point of having an ifdef here. Can't you
make it unconditional ? Or do you expect distros to turn that off in
which case what's the point ?
> +#define COP_PID_NONE 0
> +#define COP_PID_MIN (COP_PID_NONE + 1)
> +#define COP_PID_MAX (0xFFFF)
> +
> +static DEFINE_SPINLOCK(mmu_context_acop_lock);
> +static DEFINE_IDA(cop_ida);
> +
> +void switch_cop(struct mm_struct *next)
> +{
> + mtspr(SPRN_PID, next->context.cop_pid);
> + mtspr_acop(next->context.acop);
> +}
s/mtspr_acop/set_cpu_acop() instead.
> +static int new_cop_pid(struct ida *ida, int min_id, int max_id,
> + spinlock_t *lock)
> +{
> + int index;
> + int err;
> +
> +again:
> + if (!ida_pre_get(ida, GFP_KERNEL))
> + return -ENOMEM;
> +
> + spin_lock(lock);
> + err = ida_get_new_above(ida, min_id, &index);
> + spin_unlock(lock);
> +
> + if (err == -EAGAIN)
> + goto again;
> + else if (err)
> + return err;
> +
> + if (index > max_id) {
> + spin_lock(lock);
> + ida_remove(ida, index);
> + spin_unlock(lock);
> + return -ENOMEM;
> + }
> +
> + return index;
> +}
> +
> +/*
> + * Start using a coprocessor.
> + * @acop: mask of coprocessor to be used.
> + * @mm: The mm the coprocessor to associate with. Most likely current mm.
> + *
> + * Return a positive PID if successful. Negative errno otherwise.
> + * The returned PID will be fed to the coprocessor to determine if an
> + * icswx transaction is authenticated.
> + */
> +int use_cop(unsigned long acop, struct mm_struct *mm)
> +{
> + int cop_pid;
> +
> + if (!cpu_has_feature(CPU_FTR_ICSWX))
> + return -ENODEV;
> +
> + if (!mm || !acop)
> + return -EINVAL;
> +
> + spin_lock(mm->context.cop_lockp);
> + if (mm->context.cop_pid == COP_PID_NONE) {
new_cop_pid goes GFP_KERNEL allocs no ? It even goes at great length to
drop the mmu_context_acop_lock while doing ide_pre_get() ... but you
call the whole thing with the mm->context.cop_lockp held. So that's all
wrong. You need to drop the lock, allocate a PID, take the lock again,
check if somebody came in and gave you a PID already, if yes, release
the PID you allocated.
Another option is to use a mutex since none of that is in the context
switch path right ?
Also do you ever call use_cop for a non-current mm ?
> + cop_pid = new_cop_pid(&cop_ida, COP_PID_MIN, COP_PID_MAX,
> + &mmu_context_acop_lock);
> + if (cop_pid < 0) {
> + spin_unlock(mm->context.cop_lockp);
> + return cop_pid;
> + }
> + mm->context.cop_pid = cop_pid;
> + if (mm == current->active_mm)
> + mtspr(SPRN_PID, mm->context.cop_pid);
> + }
> + mm->context.acop |= acop;
> + if (mm == current->active_mm)
> + mtspr_acop(mm->context.acop);
> + spin_unlock(mm->context.cop_lockp);
> + return mm->context.cop_pid;
> +}
> +EXPORT_SYMBOL_GPL(use_cop);
> +
> +/*
> + * Stop using a coprocessor.
> + * @acop: mask of coprocessor to be stopped.
> + * @mm: The mm the coprocessor associated with.
> + */
> +void drop_cop(unsigned long acop, struct mm_struct *mm)
> +{
> + if (WARN_ON(!mm))
> + return;
> +
> + spin_lock(mm->context.cop_lockp);
> + mm->context.acop &= ~acop;
> + if (mm == current->active_mm)
> + mtspr_acop(mm->context.acop);
> + if ((!mm->context.acop) && (mm->context.cop_pid != COP_PID_NONE)) {
> + spin_lock(&mmu_context_acop_lock);
> + ida_remove(&cop_ida, mm->context.cop_pid);
> + spin_unlock(&mmu_context_acop_lock);
> + mm->context.cop_pid = COP_PID_NONE;
> + if (mm == current->active_mm)
> + mtspr(SPRN_PID, mm->context.cop_pid);
> + }
> + spin_unlock(mm->context.cop_lockp);
> +}
> +EXPORT_SYMBOL_GPL(drop_cop);
> +
> +#endif /* CONFIG_ICSWX */
> +
> static DEFINE_SPINLOCK(mmu_context_lock);
> static DEFINE_IDA(mmu_context_ida);
>
> @@ -79,6 +245,12 @@ int init_new_context(struct task_struct *tsk, struct mm_struct *mm)
> slice_set_user_psize(mm, mmu_virtual_psize);
> subpage_prot_init_new_context(mm);
> mm->context.id = index;
> +#ifdef CONFIG_ICSWX
> + mm->context.cop_lockp = kmalloc(sizeof(spinlock_t), GFP_KERNEL);
> + if (! mm->context.cop_lockp)
> + return -ENOMEM;
> + spin_lock_init(mm->context.cop_lockp);
> +#endif /* CONFIG_ICSWX */
>
> return 0;
> }
No, do that on the first time a process tries to use it. No need to add
overhead to every task in the system.
> @@ -93,6 +265,11 @@ EXPORT_SYMBOL_GPL(__destroy_context);
>
> void destroy_context(struct mm_struct *mm)
> {
> +#ifdef CONFIG_ICSWX
> + drop_cop(mm->context.acop, mm);
> + kfree(mm->context.cop_lockp);
> + mm->context.cop_lockp = NULL;
> +#endif /* CONFIG_ICSWX */
> __destroy_context(mm->context.id);
> subpage_prot_free(mm);
> mm->context.id = NO_CONTEXT;
> diff --git a/arch/powerpc/platforms/Kconfig.cputype b/arch/powerpc/platforms/Kconfig.cputype
> index 111138c..0007b66 100644
> --- a/arch/powerpc/platforms/Kconfig.cputype
> +++ b/arch/powerpc/platforms/Kconfig.cputype
> @@ -226,6 +226,49 @@ config VSX
>
> If in doubt, say Y here.
>
> +config ICSWX
> + bool "Support for PowerPC icswx coprocessor instruction"
> + depends on POWER4
> + default n
> + ---help---
> +
> + Enabling this option to turn on the PowerPC Initiate Coprocessor
> + Store Word (icswx) coprocessor instruction support for POWER7
> + or newer processors.
> +
> + This option is only useful if you have a processor that supports
> + icswx coprocessor instruction. It does not have any effect on
> + processors without icswx coprocessor instruction.
> +
> + This option slightly increases kernel memory usage.
> +
> + Say N if you do not have a PowerPC processor supporting icswx
> + instruction and a PowerPC coprocessor.
> +
> +config ICSWX_LAZY_SWITCH
> + bool "Lazy switching coprocessor type registers at context switching"
> + depends on ICSWX
> + default y
> + ---help---
> +
> + Coprocessor type register is part of process context. It needs
> + to be switched at context switching.
> +
> + As most machines have a very small number (most likely <= 1)
> + of coprocessors, there is a good chance that the value of the
> + coprocessor type register is the same between many processes.
> + We do not need to change coprocessor type register at context
> + switching unless the task to switch to has a different value.
> +
> + Accessing CPU special purpose registers is far more expensive
> + than accessing memory. This option uses a per-CPU variable
> + to track the value of coprocessor type register. The variable
> + is checked before updating coprocessor type register. The saving
> + for one access is small but could turn big with the high
> + frequency of context switching.
> +
> + Say Y unless you have multiple coprocessors.
> +
> config SPE
> bool "SPE Support"
> depends on E200 || (E500 && !PPC_E500MC)
Ben.
More information about the Linuxppc-dev
mailing list