[PATCH 0/6] powerpc sstep: Extend instruction emulation support

STALE2746d

16 messages, 4 authors, 2019-02-20 · open the first message on its own page

[PATCH 0/6] powerpc sstep: Extend instruction emulation support

From: Sandipan Das <hidden>
Date: 2018-09-03 15:19:50

This series adds emulation support for some additional ISA 3.0
instructions, most of which are now generated by a recent compiler
(e.g. gcc-8.x) when building the kernel with CONFIG_POWER9_CPU = y.

PrasannaKumar Muralidharan (1):
  powerpc sstep: Add modsw, moduw instruction emulation

Sandipan Das (5):
  powerpc sstep: Add maddhd, maddhdu, maddld instruction emulation
  powerpc sstep: Add darn instruction emulation
  powerpc sstep: Add cnttzw, cnttzd instruction emulation
  powerpc sstep: Add extswsli instruction emulation
  powerpc sstep: Add modsd, modud instruction emulation

 arch/powerpc/lib/sstep.c | 111 ++++++++++++++++++++++++++++++++++++++++++++++-
 1 file changed, 109 insertions(+), 2 deletions(-)

-- 
2.14.4

[PATCH 1/6] powerpc sstep: Add maddhd, maddhdu, maddld instruction emulation

From: Sandipan Das <hidden>
Date: 2018-09-03 15:19:51

This adds emulation support for the following integer instructions:
  * Multiply-Add High Doubleword (maddhd)
  * Multiply-Add High Doubleword Unsigned (maddhdu)
  * Multiply-Add Low Doubleword (maddld)

Signed-off-by: Sandipan Das <redacted>
---
 arch/powerpc/lib/sstep.c | 35 ++++++++++++++++++++++++++++++++++-
 1 file changed, 34 insertions(+), 1 deletion(-)
diff --git a/arch/powerpc/lib/sstep.c b/arch/powerpc/lib/sstep.c
index d81568f783e5..b40ec18515bd 100644
--- a/arch/powerpc/lib/sstep.c
+++ b/arch/powerpc/lib/sstep.c
@@ -1169,7 +1169,7 @@ static nokprobe_inline int trap_compare(long v1, long v2)
 int analyse_instr(struct instruction_op *op, const struct pt_regs *regs,
 		  unsigned int instr)
 {
-	unsigned int opcode, ra, rb, rd, spr, u;
+	unsigned int opcode, ra, rb, rc, rd, spr, u;
 	unsigned long int imm;
 	unsigned long int val, val2;
 	unsigned int mb, me, sh;
@@ -1292,6 +1292,7 @@ int analyse_instr(struct instruction_op *op, const struct pt_regs *regs,
 	rd = (instr >> 21) & 0x1f;
 	ra = (instr >> 16) & 0x1f;
 	rb = (instr >> 11) & 0x1f;
+	rc = (instr >> 6) & 0x1f;
 
 	switch (opcode) {
 #ifdef __powerpc64__
@@ -1305,6 +1306,38 @@ int analyse_instr(struct instruction_op *op, const struct pt_regs *regs,
 			goto trap;
 		return 1;
 
+#ifdef __powerpc64__
+	case 4:
+		if (!cpu_has_feature(CPU_FTR_ARCH_300))
+			return -1;
+
+		switch (instr & 0x3f) {
+		case 48:	/* maddhd */
+			asm("maddhd %0,%1,%2,%3" : "=r" (op->val) :
+			    "r" (regs->gpr[ra]), "r" (regs->gpr[rb]),
+			    "r" (regs->gpr[rc]));
+			goto compute_done;
+
+		case 49:	/* maddhdu */
+			asm("maddhdu %0,%1,%2,%3" : "=r" (op->val) :
+			    "r" (regs->gpr[ra]), "r" (regs->gpr[rb]),
+			    "r" (regs->gpr[rc]));
+			goto compute_done;
+
+		case 51:	/* maddld */
+			asm("maddld %0,%1,%2,%3" : "=r" (op->val) :
+			    "r" (regs->gpr[ra]), "r" (regs->gpr[rb]),
+			    "r" (regs->gpr[rc]));
+			goto compute_done;
+		}
+
+		/*
+		 * There are other instructions from ISA 3.0 with the same
+		 * primary opcode which do not have emulation support yet.
+		 */
+		return -1;
+#endif
+
 	case 7:		/* mulli */
 		op->val = regs->gpr[ra] * (short) instr;
 		goto compute_done;
-- 
2.14.4

[PATCH 2/6] powerpc sstep: Add darn instruction emulation

From: Sandipan Das <hidden>
Date: 2018-09-03 15:19:53

This adds emulation support for the following integer instructions:
  * Deliver A Random Number (darn)

Signed-off-by: Sandipan Das <redacted>
---
 arch/powerpc/lib/sstep.c | 19 +++++++++++++++++++
 1 file changed, 19 insertions(+)
diff --git a/arch/powerpc/lib/sstep.c b/arch/powerpc/lib/sstep.c
index b40ec18515bd..18ac0a26c4fc 100644
--- a/arch/powerpc/lib/sstep.c
+++ b/arch/powerpc/lib/sstep.c
@@ -1728,6 +1728,25 @@ int analyse_instr(struct instruction_op *op, const struct pt_regs *regs,
 				(int) regs->gpr[rb];
 			goto arith_done;
 
+		case 755:	/* darn */
+			if (!cpu_has_feature(CPU_FTR_ARCH_300))
+				return -1;
+			switch (ra & 0x3) {
+			case 0:
+				asm("darn %0,0" : "=r" (op->val));
+				goto compute_done;
+
+			case 1:
+				asm("darn %0,1" : "=r" (op->val));
+				goto compute_done;
+
+			case 2:
+				asm("darn %0,2" : "=r" (op->val));
+				goto compute_done;
+			}
+
+			return -1;
+
 
 /*
  * Logical instructions
-- 
2.14.4

[PATCH 4/6] powerpc sstep: Add extswsli instruction emulation

From: Sandipan Das <hidden>
Date: 2018-09-03 15:19:59

This adds emulation support for the following integer instructions:
  * Extend-Sign Word and Shift Left Immediate (extswsli[.])

Signed-off-by: Sandipan Das <redacted>
---
 arch/powerpc/lib/sstep.c | 14 ++++++++++++++
 1 file changed, 14 insertions(+)
diff --git a/arch/powerpc/lib/sstep.c b/arch/powerpc/lib/sstep.c
index d57cb4e28621..828677df47b2 100644
--- a/arch/powerpc/lib/sstep.c
+++ b/arch/powerpc/lib/sstep.c
@@ -1932,6 +1932,20 @@ int analyse_instr(struct instruction_op *op, const struct pt_regs *regs,
 				op->xerval &= ~XER_CA;
 			set_ca32(op, op->xerval & XER_CA);
 			goto logical_done;
+
+		case 890:	/* extswsli with sh_5 = 0 */
+		case 891:	/* extswsli with sh_5 = 1 */
+			if (!cpu_has_feature(CPU_FTR_ARCH_300))
+				return -1;
+			op->type = COMPUTE + SETREG;
+			sh = rb | ((instr & 2) << 4);
+			val = (signed int) regs->gpr[rd];
+			if (sh)
+				op->val = ROTATE(val, sh) & MASK64(0, 63 - sh);
+			else
+				op->val = val;
+			goto logical_done;
+
 #endif /* __powerpc64__ */
 
 /*
-- 
2.14.4

[PATCH 3/6] powerpc sstep: Add cnttzw, cnttzd instruction emulation

From: Sandipan Das <hidden>
Date: 2018-09-03 15:20:01

This adds emulation support for the following integer instructions:
  * Count Trailing Zeros Word (cnttzw[.])
  * Count Trailing Zeros Doubleword (cnttzd[.])

Signed-off-by: Sandipan Das <redacted>
---
 arch/powerpc/lib/sstep.c | 14 ++++++++++++++
 1 file changed, 14 insertions(+)
diff --git a/arch/powerpc/lib/sstep.c b/arch/powerpc/lib/sstep.c
index 18ac0a26c4fc..d57cb4e28621 100644
--- a/arch/powerpc/lib/sstep.c
+++ b/arch/powerpc/lib/sstep.c
@@ -1816,6 +1816,20 @@ int analyse_instr(struct instruction_op *op, const struct pt_regs *regs,
 		case 506:	/* popcntd */
 			do_popcnt(regs, op, regs->gpr[rd], 64);
 			goto logical_done_nocc;
+#endif
+		case 538:	/* cnttzw */
+			if (!cpu_has_feature(CPU_FTR_ARCH_300))
+				return -1;
+			val = (unsigned int) regs->gpr[rd];
+			op->val = ( val ? __builtin_ctz(val) : 32 );
+			goto logical_done;
+#ifdef __powerpc64__
+		case 570:	/* cnttzd */
+			if (!cpu_has_feature(CPU_FTR_ARCH_300))
+				return -1;
+			val = regs->gpr[rd];
+			op->val = ( val ? __builtin_ctzl(val) : 64 );
+			goto logical_done;
 #endif
 		case 922:	/* extsh */
 			op->val = (signed short) regs->gpr[rd];
-- 
2.14.4

[PATCH 5/6] powerpc sstep: Add modsw, moduw instruction emulation

From: Sandipan Das <hidden>
Date: 2018-09-03 15:20:02

From: PrasannaKumar Muralidharan <redacted>

This adds emulation support for the following integer instructions:
  * Modulo Signed Word (modsw)
  * Modulo Unsigned Word (moduw)

Signed-off-by: PrasannaKumar Muralidharan <redacted>
Signed-off-by: Sandipan Das <redacted>
---
 arch/powerpc/lib/sstep.c | 14 ++++++++++++++
 1 file changed, 14 insertions(+)
diff --git a/arch/powerpc/lib/sstep.c b/arch/powerpc/lib/sstep.c
index 828677df47b2..ddc2de28e00a 100644
--- a/arch/powerpc/lib/sstep.c
+++ b/arch/powerpc/lib/sstep.c
@@ -1708,6 +1708,13 @@ int analyse_instr(struct instruction_op *op, const struct pt_regs *regs,
 		case 266:	/* add */
 			op->val = regs->gpr[ra] + regs->gpr[rb];
 			goto arith_done;
+
+		case 267:	/* moduw */
+			if (!cpu_has_feature(CPU_FTR_ARCH_300))
+				return -1;
+			op->val = (unsigned int) regs->gpr[ra] %
+				(unsigned int) regs->gpr[rb];
+			goto compute_done;
 #ifdef __powerpc64__
 		case 457:	/* divdu */
 			op->val = regs->gpr[ra] / regs->gpr[rb];
@@ -1747,6 +1754,13 @@ int analyse_instr(struct instruction_op *op, const struct pt_regs *regs,
 
 			return -1;
 
+		case 779:	/* modsw */
+			if (!cpu_has_feature(CPU_FTR_ARCH_300))
+				return -1;
+			op->val = (int) regs->gpr[ra] %
+				(int) regs->gpr[rb];
+			goto compute_done;
+
 
 /*
  * Logical instructions
-- 
2.14.4

[PATCH 6/6] powerpc sstep: Add modsd, modud instruction emulation

From: Sandipan Das <hidden>
Date: 2018-09-03 15:20:04

This adds emulation support for the following integer instructions:
  * Modulo Signed Doubleword (modsd)
  * Modulo Unsigned Doubleword (modud)

Signed-off-by: Sandipan Das <redacted>
---
 arch/powerpc/lib/sstep.c | 17 +++++++++++++++--
 1 file changed, 15 insertions(+), 2 deletions(-)
diff --git a/arch/powerpc/lib/sstep.c b/arch/powerpc/lib/sstep.c
index ddc2de28e00a..b3b508d175fb 100644
--- a/arch/powerpc/lib/sstep.c
+++ b/arch/powerpc/lib/sstep.c
@@ -1704,7 +1704,13 @@ int analyse_instr(struct instruction_op *op, const struct pt_regs *regs,
 				(int) regs->gpr[rb];
 
 			goto arith_done;
-
+#ifdef __powerpc64__
+		case 265:	/* modud */
+			if (!cpu_has_feature(CPU_FTR_ARCH_300))
+				return -1;
+			op->val = regs->gpr[ra] % regs->gpr[rb];
+			goto compute_done;
+#endif
 		case 266:	/* add */
 			op->val = regs->gpr[ra] + regs->gpr[rb];
 			goto arith_done;
@@ -1753,7 +1759,14 @@ int analyse_instr(struct instruction_op *op, const struct pt_regs *regs,
 			}
 
 			return -1;
-
+#ifdef __powerpc64__
+		case 777:	/* modsd */
+			if (!cpu_has_feature(CPU_FTR_ARCH_300))
+				return -1;
+			op->val = (long int) regs->gpr[ra] %
+				(long int) regs->gpr[rb];
+			goto compute_done;
+#endif
 		case 779:	/* modsw */
 			if (!cpu_has_feature(CPU_FTR_ARCH_300))
 				return -1;
-- 
2.14.4

Re: [PATCH 6/6] powerpc sstep: Add modsd, modud instruction emulation

From: Segher Boessenkool <hidden>
Date: 2018-09-04 21:22:24

On Mon, Sep 03, 2018 at 08:49:38PM +0530, Sandipan Das wrote:
+#ifdef __powerpc64__
+		case 265:	/* modud */
+			if (!cpu_has_feature(CPU_FTR_ARCH_300))
+				return -1;
+			op->val = regs->gpr[ra] % regs->gpr[rb];
+			goto compute_done;
+#endif
The mod instruction has special cases that aren't handled by this C code,
too (divide by 0, or signed division of the most negative number by -1).
For the mod intruction the behaviour is undefined in those cases, but you
probably should force some specific behaviour.  You don't want the kernel
to execute a trap instruction, etc. :-)


Segher

Re: [PATCH 1/6] powerpc sstep: Add maddhd, maddhdu, maddld instruction emulation

From: Segher Boessenkool <hidden>
Date: 2018-09-04 22:18:58

On Mon, Sep 03, 2018 at 08:49:33PM +0530, Sandipan Das wrote:
+#ifdef __powerpc64__
+	case 4:
+		if (!cpu_has_feature(CPU_FTR_ARCH_300))
+			return -1;
+
+		switch (instr & 0x3f) {
+		case 48:	/* maddhd */
+			asm("maddhd %0,%1,%2,%3" : "=r" (op->val) :
+			    "r" (regs->gpr[ra]), "r" (regs->gpr[rb]),
+			    "r" (regs->gpr[rc]));
+			goto compute_done;
If running maddhd does not work, will running it in kernel mode work?

I think you should *actually* emulate it.

(Same for the next patch, "darn", but emulation of that is much more
interesting).


Segher

Re: [PATCH 3/6] powerpc sstep: Add cnttzw, cnttzd instruction emulation

From: Segher Boessenkool <hidden>
Date: 2018-09-05 00:44:31

On Mon, Sep 03, 2018 at 08:49:35PM +0530, Sandipan Das wrote:
+		case 538:	/* cnttzw */
+			if (!cpu_has_feature(CPU_FTR_ARCH_300))
+				return -1;
+			val = (unsigned int) regs->gpr[rd];
+			op->val = ( val ? __builtin_ctz(val) : 32 );
+			goto logical_done;
+#ifdef __powerpc64__
+		case 570:	/* cnttzd */
+			if (!cpu_has_feature(CPU_FTR_ARCH_300))
+				return -1;
+			val = regs->gpr[rd];
+			op->val = ( val ? __builtin_ctzl(val) : 64 );
+			goto logical_done;
__builtin_ctz(val) is undefined for val == 0.


Segher

Re: [PATCH 3/6] powerpc sstep: Add cnttzw, cnttzd instruction emulation

From: Paul Mackerras <hidden>
Date: 2018-09-05 06:36:57

On Tue, Sep 04, 2018 at 04:12:07PM -0500, Segher Boessenkool wrote:
On Mon, Sep 03, 2018 at 08:49:35PM +0530, Sandipan Das wrote:
quoted
+		case 538:	/* cnttzw */
+			if (!cpu_has_feature(CPU_FTR_ARCH_300))
+				return -1;
+			val = (unsigned int) regs->gpr[rd];
+			op->val = ( val ? __builtin_ctz(val) : 32 );
+			goto logical_done;
+#ifdef __powerpc64__
+		case 570:	/* cnttzd */
+			if (!cpu_has_feature(CPU_FTR_ARCH_300))
+				return -1;
+			val = regs->gpr[rd];
+			op->val = ( val ? __builtin_ctzl(val) : 64 );
+			goto logical_done;
__builtin_ctz(val) is undefined for val == 0.
Which would be why he only calls it when val != 0, presumably, and
uses 64 when val == 0.  Apart from idiosyncratic whitespace his code
looks correct to me.

Are you saying there is a bug in his code, or that his patch
description is incomplete, or what?

Paul.

Re: [PATCH 1/6] powerpc sstep: Add maddhd, maddhdu, maddld instruction emulation

From: Sandipan Das <hidden>
Date: 2018-09-05 11:33:49

Hi Segher,

On Wednesday 05 September 2018 03:42 AM, Segher Boessenkool wrote:
On Mon, Sep 03, 2018 at 08:49:33PM +0530, Sandipan Das wrote:
quoted
+#ifdef __powerpc64__
+	case 4:
+		if (!cpu_has_feature(CPU_FTR_ARCH_300))
+			return -1;
+
+		switch (instr & 0x3f) {
+		case 48:	/* maddhd */
+			asm("maddhd %0,%1,%2,%3" : "=r" (op->val) :
+			    "r" (regs->gpr[ra]), "r" (regs->gpr[rb]),
+			    "r" (regs->gpr[rc]));
+			goto compute_done;
If running maddhd does not work, will running it in kernel mode work?
Not sure what you meant here but one of the scenarios that I'm aware of
where this is will be used is if we place a probe at a location having
an maddhd instruction. The kernel would first attempt to emulate its
behaviour, which in this case is done by executing the same instruction
(similar to what is done for mulhd and mulhw) and if that fails, try to
execute the instruction natively.

- Sandipan

Re: [PATCH 6/6] powerpc sstep: Add modsd, modud instruction emulation

From: Sandipan Das <hidden>
Date: 2018-09-05 11:53:10

Hi Segher,

On Wednesday 05 September 2018 02:51 AM, Segher Boessenkool wrote:
On Mon, Sep 03, 2018 at 08:49:38PM +0530, Sandipan Das wrote:
quoted
+#ifdef __powerpc64__
+		case 265:	/* modud */
+			if (!cpu_has_feature(CPU_FTR_ARCH_300))
+				return -1;
+			op->val = regs->gpr[ra] % regs->gpr[rb];
+			goto compute_done;
+#endif
The mod instruction has special cases that aren't handled by this C code,
too (divide by 0, or signed division of the most negative number by -1).
For the mod intruction the behaviour is undefined in those cases, but you
probably should force some specific behaviour.  You don't want the kernel
to execute a trap instruction, etc. :-)
Agreed. In that case, the same would apply to the divw, divwu, divd and divdu
instructions as well, right? Cause I don't see these cases being handled for
them currently.

Also, if I execute a modulo or division instruction for any of these special
cases in a userspace binary, I don't see any exceptions being generated. It's
just that the result is undefined (usually same as one of the source operands,
I don't remember if it was the dividend or the divisor). So, I'm wondering if
this would be necessary.

- Sandipan

Re: [PATCH 2/6] powerpc sstep: Add darn instruction emulation

From: Michael Ellerman <mpe@ellerman.id.au>
Date: 2019-02-20 00:52:25

Sandipan Das [off-list ref] writes:
This adds emulation support for the following integer instructions:
  * Deliver A Random Number (darn)
This doesn't build with old binutils. We need to support old binutils.

    {standard input}:4343: Error: Unrecognized opcode: `darn'


You need to use PPC_DARN().

cheers
quoted hunk
diff --git a/arch/powerpc/lib/sstep.c b/arch/powerpc/lib/sstep.c
index b40ec18515bd..18ac0a26c4fc 100644
--- a/arch/powerpc/lib/sstep.c
+++ b/arch/powerpc/lib/sstep.c
@@ -1728,6 +1728,25 @@ int analyse_instr(struct instruction_op *op, const struct pt_regs *regs,
 				(int) regs->gpr[rb];
 			goto arith_done;
 
+		case 755:	/* darn */
+			if (!cpu_has_feature(CPU_FTR_ARCH_300))
+				return -1;
+			switch (ra & 0x3) {
+			case 0:
+				asm("darn %0,0" : "=r" (op->val));
+				goto compute_done;
+
+			case 1:
+				asm("darn %0,1" : "=r" (op->val));
+				goto compute_done;
+
+			case 2:
+				asm("darn %0,2" : "=r" (op->val));
+				goto compute_done;
+			}
+
+			return -1;
+
 
 /*
  * Logical instructions
-- 
2.14.4

Re: [PATCH 1/6] powerpc sstep: Add maddhd, maddhdu, maddld instruction emulation

From: Michael Ellerman <mpe@ellerman.id.au>
Date: 2019-02-20 00:53:56

Sandipan Das [off-list ref] writes:
This adds emulation support for the following integer instructions:
  * Multiply-Add High Doubleword (maddhd)
  * Multiply-Add High Doubleword Unsigned (maddhdu)
  * Multiply-Add Low Doubleword (maddld)
This doesn't build with old binutils.

    {standard input}:2089: Error: Unrecognized opcode: `maddld'
    {standard input}:2104: Error: Unrecognized opcode: `maddhdu'
    {standard input}:1141: Error: Unrecognized opcode: `maddhd'


You'll need to add hand built versions, see ppc-opcode.h for examples.

cheers
quoted hunk
diff --git a/arch/powerpc/lib/sstep.c b/arch/powerpc/lib/sstep.c
index d81568f783e5..b40ec18515bd 100644
--- a/arch/powerpc/lib/sstep.c
+++ b/arch/powerpc/lib/sstep.c
@@ -1169,7 +1169,7 @@ static nokprobe_inline int trap_compare(long v1, long v2)
 int analyse_instr(struct instruction_op *op, const struct pt_regs *regs,
 		  unsigned int instr)
 {
-	unsigned int opcode, ra, rb, rd, spr, u;
+	unsigned int opcode, ra, rb, rc, rd, spr, u;
 	unsigned long int imm;
 	unsigned long int val, val2;
 	unsigned int mb, me, sh;
@@ -1292,6 +1292,7 @@ int analyse_instr(struct instruction_op *op, const struct pt_regs *regs,
 	rd = (instr >> 21) & 0x1f;
 	ra = (instr >> 16) & 0x1f;
 	rb = (instr >> 11) & 0x1f;
+	rc = (instr >> 6) & 0x1f;
 
 	switch (opcode) {
 #ifdef __powerpc64__
@@ -1305,6 +1306,38 @@ int analyse_instr(struct instruction_op *op, const struct pt_regs *regs,
 			goto trap;
 		return 1;
 
+#ifdef __powerpc64__
+	case 4:
+		if (!cpu_has_feature(CPU_FTR_ARCH_300))
+			return -1;
+
+		switch (instr & 0x3f) {
+		case 48:	/* maddhd */
+			asm("maddhd %0,%1,%2,%3" : "=r" (op->val) :
+			    "r" (regs->gpr[ra]), "r" (regs->gpr[rb]),
+			    "r" (regs->gpr[rc]));
+			goto compute_done;
+
+		case 49:	/* maddhdu */
+			asm("maddhdu %0,%1,%2,%3" : "=r" (op->val) :
+			    "r" (regs->gpr[ra]), "r" (regs->gpr[rb]),
+			    "r" (regs->gpr[rc]));
+			goto compute_done;
+
+		case 51:	/* maddld */
+			asm("maddld %0,%1,%2,%3" : "=r" (op->val) :
+			    "r" (regs->gpr[ra]), "r" (regs->gpr[rb]),
+			    "r" (regs->gpr[rc]));
+			goto compute_done;
+		}
+
+		/*
+		 * There are other instructions from ISA 3.0 with the same
+		 * primary opcode which do not have emulation support yet.
+		 */
+		return -1;
+#endif
+
 	case 7:		/* mulli */
 		op->val = regs->gpr[ra] * (short) instr;
 		goto compute_done;
-- 
2.14.4

Re: [PATCH 2/6] powerpc sstep: Add darn instruction emulation

From: Sandipan Das <hidden>
Date: 2019-02-20 09:51:33

Hi Michael,

On 20/02/19 6:20 AM, Michael Ellerman wrote:
Sandipan Das [off-list ref] writes:
quoted
This adds emulation support for the following integer instructions:
  * Deliver A Random Number (darn)
This doesn't build with old binutils. We need to support old binutils.

    {standard input}:4343: Error: Unrecognized opcode: `darn'


You need to use PPC_DARN().

cheers

[...]
Thanks for pointing these out. Will post v2 with the required changes.

- Sandipan
Keyboard shortcuts
hback out one level
jnext message in thread
kprevious message in thread
ldrill in
Escclose help / fold thread tree
?toggle this help