MIPS: Add defs & probing of extended CP0_EBase
authorJames Hogan <james.hogan@imgtec.com>
Wed, 11 May 2016 12:50:50 +0000 (13:50 +0100)
committerRalf Baechle <ralf@linux-mips.org>
Fri, 13 May 2016 13:30:25 +0000 (15:30 +0200)
The CP0_EBase register may optionally have a write gate (WG) bit to
allow the upper bits to be written, i.e. bits 31:30 on MIPS32 since r3
(to allow for an exception base outside of KSeg0/KSeg1 when segmentation
control is in use) and bits 63:30 on MIPS64 (which also implies the
extension of CP0_EBase to 64 bits long).

The presence of this feature will need to be known about for VZ support
in order to correctly save and restore all the bits of the guest
CP0_EBase register, so add CPU feature definition and probing for this
feature.

Probing the WG bit on MIPS64 can be a bit fiddly, since 64-bit COP0
register access instructions were UNDEFINED for 32-bit registers prior
to MIPS r6, and it'd be nice to be able to probe without clobbering the
existing state, so there are 3 potential paths:

- If we do a 32-bit read of CP0_EBase and the WG bit is already set, the
  register must be 64-bit.

- On MIPS r6 we can do a 64-bit read-modify-write to set CP0_EBase.WG,
  since the upper bits will read 0 and be ignored on write if the
  register is 32-bit.

- On pre-r6 cores, we do a 32-bit read-modify-write of CP0_EBase. This
  avoids the potentially UNDEFINED behaviour, but will clobber the upper
  32-bits of CP0_EBase if it isn't a simple sign extension (which also
  requires us to ensure BEV=1 or modifying the exception base would be
  UNDEFINED too). It is hopefully unlikely a bootloader would set up
  CP0_EBase to a 64-bit segment and leave WG=0.

[ralf@linux-mips.org: Resolved merge conflict.]

Signed-off-by: James Hogan <james.hogan@imgtec.com>
Tested-by: Matt Redfearn <matt.redfearn@imgtec.com>
Cc: linux-mips@linux-mips.org
Patchwork: https://patchwork.linux-mips.org/patch/13223/
Signed-off-by: Ralf Baechle <ralf@linux-mips.org>
arch/mips/include/asm/cpu-features.h
arch/mips/include/asm/cpu.h
arch/mips/include/asm/mipsregs.h
arch/mips/kernel/cpu-probe.c

index bff73c52b273befce673219475a82085d9250d28..676a37b9c37ec8280906260e272df9f397076d88 100644 (file)
 #define cpu_has_nan_2008       (cpu_data[0].options & MIPS_CPU_NAN_2008)
 #endif
 
+#ifndef cpu_has_ebase_wg
+# define cpu_has_ebase_wg      (cpu_data[0].options & MIPS_CPU_EBASE_WG)
+#endif
+
 #endif /* __ASM_CPU_FEATURES_H */
index f6cecc52a8c658a103a203157827684bae2b6ae6..3108d9b35bf1d3cea9a2973e364aa7668219f44a 100644 (file)
@@ -404,6 +404,7 @@ enum cpu_type_enum {
 #define MIPS_CPU_VP            MBIT_ULL(40)    /* MIPSr6 Virtual Processors (multi-threading) */
 #define MIPS_CPU_LDPTE         MBIT_ULL(41)    /* CPU has ldpte/lddir instructions */
 #define MIPS_CPU_MVH           MBIT_ULL(42)    /* CPU supports MFHC0/MTHC0 */
+#define MIPS_CPU_EBASE_WG      MBIT_ULL(43)    /* CPU has EBase.WG */
 
 /*
  * CPU ASE encodings
index 57d72f2bf7450f054b4e3d3ccdbb1247a67e14bb..4e8ad9d6038a680459747a8918f1bc999467537d 100644 (file)
@@ -1458,6 +1458,9 @@ do {                                                                      \
 #define read_c0_ebase()                __read_32bit_c0_register($15, 1)
 #define write_c0_ebase(val)    __write_32bit_c0_register($15, 1, val)
 
+#define read_c0_ebase_64()     __read_64bit_c0_register($15, 1)
+#define write_c0_ebase_64(val) __write_64bit_c0_register($15, 1, val)
+
 #define read_c0_cdmmbase()     __read_ulong_c0_register($15, 2)
 #define write_c0_cdmmbase(val) __write_ulong_c0_register($15, 2, val)
 
index 46a24729fbba539797adddc1e79d06b004fbae55..6a1a7da4b2d2a4cd2baaf4473961a1f6e33abe09 100644 (file)
@@ -860,6 +860,41 @@ static void decode_configs(struct cpuinfo_mips *c)
        if (ok)
                ok = decode_config5(c);
 
+       /* Probe the EBase.WG bit */
+       if (cpu_has_mips_r2_r6) {
+               u64 ebase;
+               unsigned int status;
+
+               /* {read,write}_c0_ebase_64() may be UNDEFINED prior to r6 */
+               ebase = cpu_has_mips64r6 ? read_c0_ebase_64()
+                                        : (s32)read_c0_ebase();
+               if (ebase & MIPS_EBASE_WG) {
+                       /* WG bit already set, we can avoid the clumsy probe */
+                       c->options |= MIPS_CPU_EBASE_WG;
+               } else {
+                       /* Its UNDEFINED to change EBase while BEV=0 */
+                       status = read_c0_status();
+                       write_c0_status(status | ST0_BEV);
+                       irq_enable_hazard();
+                       /*
+                        * On pre-r6 cores, this may well clobber the upper bits
+                        * of EBase. This is hard to avoid without potentially
+                        * hitting UNDEFINED dm*c0 behaviour if EBase is 32-bit.
+                        */
+                       if (cpu_has_mips64r6)
+                               write_c0_ebase_64(ebase | MIPS_EBASE_WG);
+                       else
+                               write_c0_ebase(ebase | MIPS_EBASE_WG);
+                       back_to_back_c0_hazard();
+                       /* Restore BEV */
+                       write_c0_status(status);
+                       if (read_c0_ebase() & MIPS_EBASE_WG) {
+                               c->options |= MIPS_CPU_EBASE_WG;
+                               write_c0_ebase(ebase);
+                       }
+               }
+       }
+
        mips_probe_watch_registers(c);
 
 #ifndef CONFIG_MIPS_CPS