diff mbox series

[v2,6/6] MIPS: cm: Probe GCR address from DeviceTree

Message ID 20240612-cm_probe-v2-6-a5b55440563c@flygoat.com (mailing list archive)
State New
Headers show
Series MIPS: cm: Probe GCR address from devicetree | expand

Commit Message

Jiaxun Yang June 12, 2024, 10:08 a.m. UTC
Traditionally, CM GCR address can be probed from CP0_CMGCRBase.

However there are chips in wild that do have CM GCR but CP0_CMGCRBase
is not available from CPU point of view. Thus we need to be able to
probe GCR address from DeviceTree.

It is implemented as:
- If only CP0_CMGCRBase present, trust CP0_CMGCRBase
- If only mti,mips-cm node present, trust mti,mips-cm reg prop
- If both present, remap address space to address specified in dt

Signed-off-by: Jiaxun Yang <jiaxun.yang@flygoat.com>
---
v2: Fix build warning (test bot)
---
 arch/mips/kernel/mips-cm.c | 61 +++++++++++++++++++++++++++++++++++++++++-----
 1 file changed, 55 insertions(+), 6 deletions(-)

Comments

Serge Semin Sept. 10, 2024, 12:36 p.m. UTC | #1
Hi Jiaxun, Thomas

On Wed, Jun 12, 2024 at 11:08:58AM +0100, Jiaxun Yang wrote:
> Traditionally, CM GCR address can be probed from CP0_CMGCRBase.
> 
> However there are chips in wild that do have CM GCR but CP0_CMGCRBase
> is not available from CPU point of view. Thus we need to be able to
> probe GCR address from DeviceTree.
> 
> It is implemented as:
> - If only CP0_CMGCRBase present, trust CP0_CMGCRBase
> - If only mti,mips-cm node present, trust mti,mips-cm reg prop
> - If both present, remap address space to address specified in dt
> 
> Signed-off-by: Jiaxun Yang <jiaxun.yang@flygoat.com>
> ---
> v2: Fix build warning (test bot)
> ---
>  arch/mips/kernel/mips-cm.c | 61 +++++++++++++++++++++++++++++++++++++++++-----
>  1 file changed, 55 insertions(+), 6 deletions(-)
> 
> diff --git a/arch/mips/kernel/mips-cm.c b/arch/mips/kernel/mips-cm.c
> index dddc9428fe58..02afc795ba8a 100644
> --- a/arch/mips/kernel/mips-cm.c
> +++ b/arch/mips/kernel/mips-cm.c
> @@ -5,6 +5,8 @@
>   */
>  
>  #include <linux/errno.h>
> +#include <linux/of_address.h>
> +#include <linux/of_fdt.h>
>  #include <linux/percpu.h>
>  #include <linux/spinlock.h>
>  
> @@ -179,23 +181,70 @@ static char *cm3_causes[32] = {
>  static DEFINE_PER_CPU_ALIGNED(spinlock_t, cm_core_lock);
>  static DEFINE_PER_CPU_ALIGNED(unsigned long, cm_core_lock_flags);
>  
> +static int __init mips_cm_fdt_scan(unsigned long node, const char *uname,
> +		int depth, void *data)
> +{
> +	u64 addr;
> +	unsigned long *cmgcr = data;
> +
> +	if (!of_flat_dt_is_compatible(node, "mti,mips-cm"))
> +		return 0;
> +
> +	addr = of_flat_dt_translate_address(node);
> +	if (addr == OF_BAD_ADDR || addr >= ULONG_MAX)
> +		*cmgcr = 0;
> +	else
> +		*cmgcr = addr;
> +
> +	return 0;
> +}
> +
>  phys_addr_t __init __weak mips_cm_phys_base(void)
>  {
> -	unsigned long cmgcr;
> +	unsigned long gcr_reg = 0, gcr_dt = 0;
> +
> +	if (of_have_populated_dt()) {
> +		int err;
> +		struct resource res;
> +		struct device_node *cm_node;
> +
> +		cm_node = of_find_compatible_node(of_root, NULL, "mti,mips-cm");
> +		if (cm_node) {
> +			err = of_address_to_resource(cm_node, 0, &res);
> +			of_node_put(cm_node);
> +			if (!err)
> +				gcr_dt = res.start;
> +		}
> +	} else {
> +		of_scan_flat_dt(mips_cm_fdt_scan, &gcr_dt);
> +	}
>  
>  	/* Check the CMGCRBase register is implemented */
>  	if (!(read_c0_config() & MIPS_CONF_M))
> -		return 0;
> +		return gcr_dt;
>  
>  	if (!(read_c0_config2() & MIPS_CONF_M))
> -		return 0;
> +		return gcr_dt;
>  
>  	if (!(read_c0_config3() & MIPS_CONF3_CMGCR))
> -		return 0;
> +		return gcr_dt;
>  
>  	/* Read the address from CMGCRBase */
> -	cmgcr = read_c0_cmgcrbase();
> -	return (cmgcr & MIPS_CMGCRF_BASE) << (36 - 32);
> +	gcr_reg = read_c0_cmgcrbase();
> +	gcr_reg = (gcr_reg & MIPS_CMGCRF_BASE) << (36 - 32);
> +
> +	/* If no of node, return straight away */
> +	if (!gcr_dt)
> +		return gcr_reg;
> +
> +	/* If the CMGCRBase mismatches with dt, remap it */
> +	if (gcr_reg != gcr_dt) {
> +		pr_info("Remapping CMGCRBase from 0x%08lx to 0x%08lx\n",
> +			gcr_reg, gcr_dt);

> +		change_gcr_base(CM_GCR_BASE_GCRBASE, gcr_dt);

This causes the kernel boot-up procedure to crash/hang-up because the
CM GCR base address is supposed to be redefined by means of the
already mapped CM GCR address space by accessing the
CM_GCR_BASE_GCRBASE register:
change_gcr_base()
+-> read_gcr_base()
    +-> addr_gcr_base()
        +-> return mips_gcr_base + CM_GCR_BASE_GCRBASE

By the time of the change_gcr_base() call in mips_cm_phys_base(), the
mips_gcr_base variable hasn't been defined. So the IO operations
performed in the change_gcr_base() method would be accessing the
NULL-based memory space. That's why the kernel crash/hanging-up.

In order to fix this we have to first map the CM GCR block at the
default base-address, then update the CM GCR-base CSR and after that
remap the CM GCR-space.

Please also note, the GCR_BASE field might be RO. It depends on the
IP-core configuration. So it's possible that the CM_GCR_BASE_GCRBASE
field update won't work. Although that will be detected a bit later in
the mips_cm_probe() method by comparing the address returned from
mips_cm_phys_base() and retrieved from the CM GCR-base CSR.

-Serge(y)

> +	}
> +
> +	return gcr_dt;
>  }
>  
>  phys_addr_t __init __weak mips_cm_l2sync_phys_base(void)
> 
> -- 
> 2.43.0
>
Jiaxun Yang Sept. 10, 2024, 7:23 p.m. UTC | #2
在2024年9月10日九月 下午1:36,Serge Semin写道:
[...]
>
> This causes the kernel boot-up procedure to crash/hang-up because the
> CM GCR base address is supposed to be redefined by means of the
> already mapped CM GCR address space by accessing the
> CM_GCR_BASE_GCRBASE register:
> change_gcr_base()
> +-> read_gcr_base()
>     +-> addr_gcr_base()
>         +-> return mips_gcr_base + CM_GCR_BASE_GCRBASE
>
> By the time of the change_gcr_base() call in mips_cm_phys_base(), the
> mips_gcr_base variable hasn't been defined. So the IO operations
> performed in the change_gcr_base() method would be accessing the
> NULL-based memory space. That's why the kernel crash/hanging-up.

Thanks for the analysis!
This path was not taken on my audience hardware, so I didn't catch this,
will fix in next version.

>
> In order to fix this we have to first map the CM GCR block at the
> default base-address, then update the CM GCR-base CSR and after that
> remap the CM GCR-space.
>
> Please also note, the GCR_BASE field might be RO. It depends on the
> IP-core configuration. So it's possible that the CM_GCR_BASE_GCRBASE
> field update won't work. Although that will be detected a bit later in
> the mips_cm_probe() method by comparing the address returned from
> mips_cm_phys_base() and retrieved from the CM GCR-base CSR.

Hmm, I just checked RTL and RDL for CM2 and CM3 and I didn't see it as a
configurable option. It's possible to change hardware reset value but not make it RO.

Maybe it was possible on earlier IP release, in this case it's always
user's responsibility to write correct address in DeviceTree :-)

Thanks

>
> -Serge(y)
Serge Semin Sept. 10, 2024, 8:07 p.m. UTC | #3
On Tue, Sep 10, 2024 at 08:23:25PM +0100, Jiaxun Yang wrote:
> 
> 
> 在2024年9月10日九月 下午1:36,Serge Semin写道:
> [...]
> >
> > This causes the kernel boot-up procedure to crash/hang-up because the
> > CM GCR base address is supposed to be redefined by means of the
> > already mapped CM GCR address space by accessing the
> > CM_GCR_BASE_GCRBASE register:
> > change_gcr_base()
> > +-> read_gcr_base()
> >     +-> addr_gcr_base()
> >         +-> return mips_gcr_base + CM_GCR_BASE_GCRBASE
> >
> > By the time of the change_gcr_base() call in mips_cm_phys_base(), the
> > mips_gcr_base variable hasn't been defined. So the IO operations
> > performed in the change_gcr_base() method would be accessing the
> > NULL-based memory space. That's why the kernel crash/hanging-up.
> 
> Thanks for the analysis!
> This path was not taken on my audience hardware, so I didn't catch this,
> will fix in next version.
> 
> >
> > In order to fix this we have to first map the CM GCR block at the
> > default base-address, then update the CM GCR-base CSR and after that
> > remap the CM GCR-space.
> >
> > Please also note, the GCR_BASE field might be RO. It depends on the
> > IP-core configuration. So it's possible that the CM_GCR_BASE_GCRBASE
> > field update won't work. Although that will be detected a bit later in
> > the mips_cm_probe() method by comparing the address returned from
> > mips_cm_phys_base() and retrieved from the CM GCR-base CSR.
> 

> Hmm, I just checked RTL and RDL for CM2 and CM3 and I didn't see it as a
> configurable option. It's possible to change hardware reset value but not make it RO.

Both MIPS P5600 and P6600 databooks define the GCR_BASE field as
optionally R/W:

GCR_BASE 31:15 This field sets the base address of the 32KB          R or R/W
               GCR block of the P5600 MPS.                           (IP Config-
               This register has a fixed value after reset if         uration)
               configured as Read-Only (an IP Configuration Option).

> 
> Maybe it was possible on earlier IP release,

I found the text above in the latest MIPS Warrior P-class software
manuals downloaded from the ImagTech site. Not sure why your RTL code
doesn't have such configs.

> in this case it's always
> user's responsibility to write correct address in DeviceTree :-)

Right. The system just won't work if the CM GCR base address couldn't
be updated.

-Serge(y)

> 
> Thanks
> 
> >
> > -Serge(y)
> 
> -- 
> - Jiaxun
Jiaxun Yang Sept. 10, 2024, 8:48 p.m. UTC | #4
在2024年9月10日九月 下午9:07,Serge Semin写道:
[...]
> Both MIPS P5600 and P6600 databooks define the GCR_BASE field as
> optionally R/W:
>
> GCR_BASE 31:15 This field sets the base address of the 32KB          R or R/W
>                GCR block of the P5600 MPS.                           (IP Config-
>                This register has a fixed value after reset if         uration)
>                configured as Read-Only (an IP Configuration Option).
>

Thanks for the pointer, I traced code history and it seems like MIPS decided
to not expose this functionality at some point, but documents were not updated.

Maybe I should add a read back check here.

Thanks
Serge Semin Sept. 10, 2024, 10:16 p.m. UTC | #5
On Tue, Sep 10, 2024 at 09:48:10PM +0100, Jiaxun Yang wrote:
> 
> 
> 在2024年9月10日九月 下午9:07,Serge Semin写道:
> [...]
> > Both MIPS P5600 and P6600 databooks define the GCR_BASE field as
> > optionally R/W:
> >
> > GCR_BASE 31:15 This field sets the base address of the 32KB          R or R/W
> >                GCR block of the P5600 MPS.                           (IP Config-
> >                This register has a fixed value after reset if         uration)
> >                configured as Read-Only (an IP Configuration Option).
> >
> 

> Thanks for the pointer, I traced code history and it seems like MIPS decided
> to not expose this functionality at some point, but documents were not updated.

Got it. Thanks for clarification.

> 
> Maybe I should add a read back check here.

The check is already implemented in the mips_cm_probe() method. Just
10 lines below the mips_cm_phys_base() method call.

-Serge(y)

> 
> Thanks
> -- 
> - Jiaxun
diff mbox series

Patch

diff --git a/arch/mips/kernel/mips-cm.c b/arch/mips/kernel/mips-cm.c
index dddc9428fe58..02afc795ba8a 100644
--- a/arch/mips/kernel/mips-cm.c
+++ b/arch/mips/kernel/mips-cm.c
@@ -5,6 +5,8 @@ 
  */
 
 #include <linux/errno.h>
+#include <linux/of_address.h>
+#include <linux/of_fdt.h>
 #include <linux/percpu.h>
 #include <linux/spinlock.h>
 
@@ -179,23 +181,70 @@  static char *cm3_causes[32] = {
 static DEFINE_PER_CPU_ALIGNED(spinlock_t, cm_core_lock);
 static DEFINE_PER_CPU_ALIGNED(unsigned long, cm_core_lock_flags);
 
+static int __init mips_cm_fdt_scan(unsigned long node, const char *uname,
+		int depth, void *data)
+{
+	u64 addr;
+	unsigned long *cmgcr = data;
+
+	if (!of_flat_dt_is_compatible(node, "mti,mips-cm"))
+		return 0;
+
+	addr = of_flat_dt_translate_address(node);
+	if (addr == OF_BAD_ADDR || addr >= ULONG_MAX)
+		*cmgcr = 0;
+	else
+		*cmgcr = addr;
+
+	return 0;
+}
+
 phys_addr_t __init __weak mips_cm_phys_base(void)
 {
-	unsigned long cmgcr;
+	unsigned long gcr_reg = 0, gcr_dt = 0;
+
+	if (of_have_populated_dt()) {
+		int err;
+		struct resource res;
+		struct device_node *cm_node;
+
+		cm_node = of_find_compatible_node(of_root, NULL, "mti,mips-cm");
+		if (cm_node) {
+			err = of_address_to_resource(cm_node, 0, &res);
+			of_node_put(cm_node);
+			if (!err)
+				gcr_dt = res.start;
+		}
+	} else {
+		of_scan_flat_dt(mips_cm_fdt_scan, &gcr_dt);
+	}
 
 	/* Check the CMGCRBase register is implemented */
 	if (!(read_c0_config() & MIPS_CONF_M))
-		return 0;
+		return gcr_dt;
 
 	if (!(read_c0_config2() & MIPS_CONF_M))
-		return 0;
+		return gcr_dt;
 
 	if (!(read_c0_config3() & MIPS_CONF3_CMGCR))
-		return 0;
+		return gcr_dt;
 
 	/* Read the address from CMGCRBase */
-	cmgcr = read_c0_cmgcrbase();
-	return (cmgcr & MIPS_CMGCRF_BASE) << (36 - 32);
+	gcr_reg = read_c0_cmgcrbase();
+	gcr_reg = (gcr_reg & MIPS_CMGCRF_BASE) << (36 - 32);
+
+	/* If no of node, return straight away */
+	if (!gcr_dt)
+		return gcr_reg;
+
+	/* If the CMGCRBase mismatches with dt, remap it */
+	if (gcr_reg != gcr_dt) {
+		pr_info("Remapping CMGCRBase from 0x%08lx to 0x%08lx\n",
+			gcr_reg, gcr_dt);
+		change_gcr_base(CM_GCR_BASE_GCRBASE, gcr_dt);
+	}
+
+	return gcr_dt;
 }
 
 phys_addr_t __init __weak mips_cm_l2sync_phys_base(void)