From: Tseng-Hui (Frank) Lin <hidden> Date: 2011-03-02 17:20:58
Icswx is a PowerPC co-processor instruction to send data to a
co-processor. On Book-S processors the LPAR_ID and process ID (PID) of
the owning process are registered in the window context of the
co-processor at initial time. When the icswx instruction is executed,
the L2 generates a cop-reg transaction on PowerBus. The transaction has
no address and the processor does not perform an MMU access to
authenticate the transaction. The coprocessor compares the LPAR_ID and
the PID included in the transaction and the LPAR_ID and PID held in the
window context to determine if the process is authorized to generate the
transaction.
The OS needs to assign a 16-bit PID for the process. This cop-PID needs
to be updated during context switch. The cop-PID needs to be destroied
when the context is destroied.
Change log from V3:
- Rebase to linux/kernel/git/next/linux-next.git 2011-02-28
- Move the SPRN_PID changes into a separate patch.
- (Unchanged) Not to make icswx a cpu_user_feature as the icswx support
is to be used by coprocessor drivers only.
arch/powerpc/platforms/Kconfig.cputype:
- Add ICSWX_LAZY_SWITCH
arch/powerpc/include/asm/mmu_context.h:
- Call switch_cop() in switch_mm() only if prev or next uses a coprocessor.
arch/powerpc/include/asm/mmu-hash64.h:
- Add cop_lock to mm_context_t
- Rename HASH64_MAX_PID to COP_PID_MAX and move to mmu_context_hash64.c
arch/powerpc/mm/mmu_context_hash64.c:
- Add a comment block to describe the usage of the icswx support code
- Define COP_PID_NONE, COP_PID_MIN, COP_PID_MAX for id allocation
- Define mtspr_acop() to make lazy switching a config option.
- change EXPORT_SYMBOL to EXPORT_SYMBOL_GPL.
- Remove EXPORT_SYMBOL(switch_cop) as it is only used in switch_mm().
- use_cop(): make id allocation code into new new_cop_pid().
- use_cop/drop_cop(): Use cop_lock to guard acop and cop_pid accesses
between threads in the same process. Init sop_lock in init_new_context().
- Remove unnecessary cpu_has_feature() check from drop_cop() and switch_cop().
- Call drop_cop() instead of destroy_acop() in destroy_context().
- Remove unused destroy_acop() function.
Change log from v2:
- Make the code a CPU feature and return -NODEV if CPU doesn't have
icswx co-processor instruction.
- Change the goto loop in use_cop() into a do-while loop.
- Change context destroy code into a new destroy_context_acop() function
and #define it based on CONFIG_ICSWX.
- Remove mmput() from drop_cop().
- Fix some TAB/space problems.
Change log from V1:
- Change 2'nd parameter of use_cop() from struct task_struct *task
to struct mm_struct *mm.
- Check for mm == current->active_mm before calling mtspr().
- Add a lock to guard acop related operations.
- Check for POWER7 CPU.
- Move the definition of SPRN_PID from reg_booke.h to avoid
defining SPRN_PID in two different files.
- Rename pid to acop_pid.
- Change function name disuse_cop() to drop_cop().
- Add a comment to describe the two new fields in mm_context_t.
- Moving CONFIG_ICSWX from arch/powerpc/platforms/pseries/Kconfig
to arch/powerpc/platforms/Kconfig.cputype.
Signed-off-by: Sonny Rao <redacted>
Signed-off-by: Tseng-Hui (Frank) Lin <redacted>
---
arch/powerpc/include/asm/cputable.h | 4 +-
arch/powerpc/include/asm/mmu-hash64.h | 13 +++
arch/powerpc/include/asm/mmu_context.h | 12 ++
arch/powerpc/include/asm/reg.h | 1 +
arch/powerpc/mm/mmu_context_hash64.c | 177 ++++++++++++++++++++++++++++++++
arch/powerpc/platforms/Kconfig.cputype | 43 ++++++++
6 files changed, 249 insertions(+), 1 deletions(-)
@@ -55,6 +61,12 @@ static inline void switch_mm(struct mm_struct *prev, struct mm_struct *next,if(prev==next)return;+#ifdef CONFIG_ICSWX+/* Switch coprocessor context only if prev or next uses a coprocessor */+if(prev->context.acop||next->context.acop)+switch_cop(next);+#endif /* CONFIG_ICSWX */+/* We must stop all altivec streams before changing the HW*context*/
+/*+ * 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.
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.
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 ?
+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 ?
quoted hunk
+ 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);
Do we want a userspace visible feature as well ? Or some other way to
inform userspace that we support icswx ?
Does a user space program really need to know about icswx? Only
coprocessor drivers need to know about icswx. Shouldn't user space
programs talk to the coprocessor drivers instead?
+/*+ * 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.
Thought about that. However, multiple threads can call use_cop() at the
same time. Without the spinlock being setup in advance, how do I
guarantee allocating struct copro_data and modifying the pointer in the
mm_context to be atomic?
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.
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 ?
There is only one coprocessor, HFI, using icswx at this moment. The lazy
switching makes sense. However, in the future, if more types of
coprocessors are added, the lazy switching may actually be a bad idea.
This option allows users to turn off the lazy switching.
+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 ?
I'll take the first option.
Also do you ever call use_cop for a non-current mm ?
Hmm, no. OK, remove the second parameter, mm.
I wonder if I should change drop_cop() to __drop_cop() and make a new
drop_cop() that calls __drop_cop() with current mm.
quoted
+ 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);
No, do that on the first time a process tries to use it. No need to add
overhead to every task in the system.
Same concern as above. I need something initialized in advance to
guarantee allocating memory and updating the pointer are safe when it
happens in use_cop().
Do we want a userspace visible feature as well ? Or some other way to
inform userspace that we support icswx ?
Does a user space program really need to know about icswx? Only
coprocessor drivers need to know about icswx. Shouldn't user space
programs talk to the coprocessor drivers instead?
Well, I don't know how you use icswx on P7+, but on Prism it's
definitely issued directly by userspace.
Thought about that. However, multiple threads can call use_cop() at the
same time. Without the spinlock being setup in advance, how do I
guarantee allocating struct copro_data and modifying the pointer in the
mm_context to be atomic?
You don't need to. You allocate and initialize the structure, and you
compare & swap the pointer. If somebody beat you, you trash your copy.
quoted
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 ?
There is only one coprocessor, HFI, using icswx at this moment. The lazy
switching makes sense. However, in the future, if more types of
coprocessors are added, the lazy switching may actually be a bad idea.
This option allows users to turn off the lazy switching.
No user in real life plays with kernel config options. Care to explain
why the lazy switching would be a problem ?
Same concern as above. I need something initialized in advance to
guarantee allocating memory and updating the pointer are safe when it
happens in use_cop().
Do we want a userspace visible feature as well ? Or some other way to
inform userspace that we support icswx ?
Does a user space program really need to know about icswx? Only
coprocessor drivers need to know about icswx. Shouldn't user space
programs talk to the coprocessor drivers instead?
Well, I don't know how you use icswx on P7+, but on Prism it's
definitely issued directly by userspace.
OK. You've got a point. I wasn't aware of Prism. HFI device driver is
currently the only icswx user on P7. Could you point me to more
information about how Prism uses icswx from user space?
quoted
Thought about that. However, multiple threads can call use_cop() at the
same time. Without the spinlock being setup in advance, how do I
guarantee allocating struct copro_data and modifying the pointer in the
mm_context to be atomic?
You don't need to. You allocate and initialize the structure, and you
compare & swap the pointer. If somebody beat you, you trash your copy.
Is atomic_cmpxchg() the one to do the trick?
quoted
quoted
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 ?
There is only one coprocessor, HFI, using icswx at this moment. The lazy
switching makes sense. However, in the future, if more types of
coprocessors are added, the lazy switching may actually be a bad idea.
This option allows users to turn off the lazy switching.
No user in real life plays with kernel config options. Care to explain
why the lazy switching would be a problem ?
The lazy switching checks the shadow variable first before setting ACOP
register. This saves mtspr() only if the new value is the same as
current. If there are several coprocessors on the system, the ACOP
register may have to be changed frequently. In that case, the lazy
switching will not save time. In extreme case when the ACOP register
needs to be changed every time, it actually slows down the execution by
the additional shadow variable checking.
quoted
Same concern as above. I need something initialized in advance to
guarantee allocating memory and updating the pointer are safe when it
happens in use_cop().
No you don't, see above.
Cheers,
Ben.
_______________________________________________
Linuxppc-dev mailing list
Linuxppc-dev@lists.ozlabs.org
https://lists.ozlabs.org/listinfo/linuxppc-dev
From: Benjamin Herrenschmidt <hidden> Date: 2011-03-04 22:40:44
On Fri, 2011-03-04 at 16:22 -0600, Tseng-Hui (Frank) Lin wrote:
quoted
Well, I don't know how you use icswx on P7+, but on Prism it's
definitely issued directly by userspace.
OK. You've got a point. I wasn't aware of Prism. HFI device driver is
currently the only icswx user on P7. Could you point me to more
information about how Prism uses icswx from user space?
Let's ignore that for now, there's a whole infrastructure for it and it
hasn't been published yet, maybe later this year... We can look at
exposing the feature to userspace then.
quoted
You don't need to. You allocate and initialize the structure, and you
compare & swap the pointer. If somebody beat you, you trash your copy.
Is atomic_cmpxchg() the one to do the trick?
No, just cmpxchg()
The lazy switching checks the shadow variable first before setting ACOP
register. This saves mtspr() only if the new value is the same as
current. If there are several coprocessors on the system, the ACOP
register may have to be changed frequently. In that case, the lazy
switching will not save time. In extreme case when the ACOP register
needs to be changed every time, it actually slows down the execution by
the additional shadow variable checking.
By how much ? Is it even measurable ?
Cheers,
Ben.
From: Tseng-Hui (Frank) Lin <hidden> Date: 2011-03-04 23:07:37
On Sat, 2011-03-05 at 09:40 +1100, Benjamin Herrenschmidt wrote:
quoted
The lazy switching checks the shadow variable first before setting ACOP
register. This saves mtspr() only if the new value is the same as
current. If there are several coprocessors on the system, the ACOP
register may have to be changed frequently. In that case, the lazy
switching will not save time. In extreme case when the ACOP register
needs to be changed every time, it actually slows down the execution by
the additional shadow variable checking.
By how much ? Is it even measurable ?
I don't have any measurable numbers. That's why I made it an option in
case people wants to disable it. I do agree that the kernel has so many
options that we should refrain from adding more. If the chance that the
lazy switching slows down the execution is really low, we should just
take out the option. Is there any one has an idea how much the numbers
are?
From: Benjamin Herrenschmidt <hidden> Date: 2011-03-04 23:14:03
On Fri, 2011-03-04 at 17:07 -0600, Tseng-Hui (Frank) Lin wrote:
I don't have any measurable numbers. That's why I made it an option in
case people wants to disable it. I do agree that the kernel has so many
options that we should refrain from adding more. If the chance that the
lazy switching slows down the execution is really low, we should just
take out the option. Is there any one has an idea how much the numbers
are?
Well, per-cpu loads are notoriously inefficient due to nasty codegen,
but often cache hot as well, I wouldn't worry too much. Just remove the
option for now. We can always add it back if it ever becomes desirable,
but I very much doubt it.
Cheers,
Ben.