[U-Boot] [PATCH v2 09/10] MIPS: L2 cache support

Paul Burton paul.burton at imgtec.com
Sat Sep 10 19:05:20 CEST 2016


On 10/09/16 17:55, Daniel Schwierzeck wrote:
> Am 09.09.2016 um 15:44 schrieb Paul Burton:
>> This patch adds support for initialising & maintaining L2 caches on MIPS
>> systems. The L2 cache configuration may be advertised through either
>> coprocessor 0 or the MIPS Coherence Manager depending upon the system,
>> and support for both is included.
>>
>> If the L2 can be bypassed then we bypass it early in boot & initialise
>> the L1 caches first, such that we can start making use of the L1
>> instruction cache as early as possible. Otherwise we initialise the L2
>> first such that the L1s have no opportunity to generate access to the
>> uninitialised L2.
> 
> Some questions:
> 
> Does it make sense to bypass L2 in early boot and mips_cache_reset() and
> to initialize and enable L2 after board_init_f()? Then you could
> implement the L2 code completely in C and avoid adding a lot of new
> assembly code.

Hi Daniel,

I thought of that too, and it would be great but would rely upon always
being able to bypass the L2. That's possible for all vaguely recent
chips from Imagination but there's nothing in the arch spec enforcing
that it'll be possible & I'm not sure whether it's possible for all MIPS
implementations from architecture licensees.

> Can't you make all flush_cache() functions a no-op when CONFIG_MIPS_CM
> is enabled?

Sadly, no. That can be the case when an IOCU is included (which we can
detect presence of via CM registers), but not all systems include an
IOCU & sadly there are also both real systems & FPGA bitfiles which
include an IOCU but don't connect peripherals to it. So in some cases it
needs SoC or board-specific knowledge about whether I/O is actually
coherent. I have support for this in a (somewhat messy) version of
U-Boot that has been used internally for a while:

  https://git.linux-mips.org/cgit/paul/u-boot.git/log/?h=wip-mips

IOCU support is something I'd like to get upstream fairly shortly but it
relies upon L2 support first - partially because systems with IOCUs all
have L2 caches & so U-Boot doesn't work so well if it doesn't initialise
or bypass that, and partially because the IOCU coherency ties in at the
level of the L2 so doesn't work if you bypass it.

Thanks,
    Paul

>>
>> Signed-off-by: Paul Burton <paul.burton at imgtec.com>
>> ---
>>
>> Changes in v2: None
>>
>>  arch/mips/Kconfig                   |   6 ++
>>  arch/mips/include/asm/cm.h          |  38 ++++++++
>>  arch/mips/include/asm/global_data.h |   3 +
>>  arch/mips/include/asm/mipsregs.h    |   5 +
>>  arch/mips/lib/cache.c               |  62 ++++++++++++-
>>  arch/mips/lib/cache_init.S          | 180 +++++++++++++++++++++++++++++++++++-
>>  6 files changed, 288 insertions(+), 6 deletions(-)
>>
>> diff --git a/arch/mips/Kconfig b/arch/mips/Kconfig
>> index d1cd6f1..ff86ad2 100644
>> --- a/arch/mips/Kconfig
>> +++ b/arch/mips/Kconfig
>> @@ -300,6 +300,12 @@ config MIPS_L1_CACHE_SHIFT
>>  	default "4" if MIPS_L1_CACHE_SHIFT_4
>>  	default "5"
>>  
>> +config MIPS_L2_CACHE
>> +	bool
>> +	help
>> +	  Select this if your system includes an L2 cache and you want U-Boot
>> +	  to initialise & maintain it.
>> +
>>  config DYNAMIC_IO_PORT_BASE
>>  	bool
>>  
>> diff --git a/arch/mips/include/asm/cm.h b/arch/mips/include/asm/cm.h
>> index 0261733..62ecef2 100644
>> --- a/arch/mips/include/asm/cm.h
>> +++ b/arch/mips/include/asm/cm.h
>> @@ -12,8 +12,46 @@
>>  #define GCR_BASE			0x0008
>>  #define GCR_BASE_UPPER			0x000c
>>  #define GCR_REV				0x0030
>> +#define GCR_L2_CONFIG			0x0130
>> +#define GCR_L2_TAG_ADDR			0x0600
>> +#define GCR_L2_TAG_ADDR_UPPER		0x0604
>> +#define GCR_L2_TAG_STATE		0x0608
>> +#define GCR_L2_TAG_STATE_UPPER		0x060c
>> +#define GCR_L2_DATA			0x0610
>> +#define GCR_L2_DATA_UPPER		0x0614
>>  
>>  /* GCR_REV CM versions */
>>  #define GCR_REV_CM3			0x0800
>>  
>> +/* GCR_L2_CONFIG fields */
>> +#define GCR_L2_CONFIG_ASSOC_SHIFT	0
>> +#define GCR_L2_CONFIG_ASSOC_BITS	8
>> +#define GCR_L2_CONFIG_LINESZ_SHIFT	8
>> +#define GCR_L2_CONFIG_LINESZ_BITS	4
>> +#define GCR_L2_CONFIG_SETSZ_SHIFT	12
>> +#define GCR_L2_CONFIG_SETSZ_BITS	4
>> +#define GCR_L2_CONFIG_BYPASS		(1 << 20)
>> +
>> +#ifndef __ASSEMBLY__
>> +
>> +#include <asm/io.h>
>> +
>> +static inline void *mips_cm_base(void)
>> +{
>> +	return (void *)CKSEG1ADDR(CONFIG_MIPS_CM_BASE);
>> +}
>> +
>> +static inline unsigned long mips_cm_l2_line_size(void)
>> +{
>> +	unsigned long l2conf, line_sz;
>> +
>> +	l2conf = __raw_readl(mips_cm_base() + GCR_L2_CONFIG);
>> +
>> +	line_sz = l2conf >> GCR_L2_CONFIG_LINESZ_SHIFT;
>> +	line_sz &= GENMASK(GCR_L2_CONFIG_LINESZ_BITS - 1, 0);
>> +	return line_sz ? (2 << line_sz) : 0;
>> +}
>> +
>> +#endif /* !__ASSEMBLY__ */
>> +
>>  #endif /* __MIPS_ASM_CM_H__ */
>> diff --git a/arch/mips/include/asm/global_data.h b/arch/mips/include/asm/global_data.h
>> index 8533b69..0078bbe 100644
>> --- a/arch/mips/include/asm/global_data.h
>> +++ b/arch/mips/include/asm/global_data.h
>> @@ -25,6 +25,9 @@ struct arch_global_data {
>>  	unsigned short l1i_line_size;
>>  	unsigned short l1d_line_size;
>>  #endif
>> +#ifdef CONFIG_MIPS_L2_CACHE
>> +	unsigned short l2_line_size;
>> +#endif
>>  };
>>  
>>  #include <asm-generic/global_data.h>
>> diff --git a/arch/mips/include/asm/mipsregs.h b/arch/mips/include/asm/mipsregs.h
>> index cd4f952..b4c2dff 100644
>> --- a/arch/mips/include/asm/mipsregs.h
>> +++ b/arch/mips/include/asm/mipsregs.h
>> @@ -485,9 +485,13 @@
>>  #define MIPS_CONF1_TLBS_SIZE    (6)
>>  #define MIPS_CONF1_TLBS         (_ULCAST_(63) << MIPS_CONF1_TLBS_SHIFT)
>>  
>> +#define MIPS_CONF2_SA_SHF	0
>>  #define MIPS_CONF2_SA		(_ULCAST_(15) << 0)
>> +#define MIPS_CONF2_SL_SHF	4
>>  #define MIPS_CONF2_SL		(_ULCAST_(15) << 4)
>> +#define MIPS_CONF2_SS_SHF	8
>>  #define MIPS_CONF2_SS		(_ULCAST_(15) << 8)
>> +#define MIPS_CONF2_L2B		(_ULCAST_(1) << 12)
>>  #define MIPS_CONF2_SU		(_ULCAST_(15) << 12)
>>  #define MIPS_CONF2_TA		(_ULCAST_(15) << 16)
>>  #define MIPS_CONF2_TL		(_ULCAST_(15) << 20)
>> @@ -551,6 +555,7 @@
>>  #define MIPS_CONF5_MVH		(_ULCAST_(1) << 5)
>>  #define MIPS_CONF5_FRE		(_ULCAST_(1) << 8)
>>  #define MIPS_CONF5_UFE		(_ULCAST_(1) << 9)
>> +#define MIPS_CONF5_L2C		(_ULCAST_(1) << 10)
>>  #define MIPS_CONF5_MSAEN	(_ULCAST_(1) << 27)
>>  #define MIPS_CONF5_EVA		(_ULCAST_(1) << 28)
>>  #define MIPS_CONF5_CV		(_ULCAST_(1) << 29)
>> diff --git a/arch/mips/lib/cache.c b/arch/mips/lib/cache.c
>> index d8baf08..bd14ba6 100644
>> --- a/arch/mips/lib/cache.c
>> +++ b/arch/mips/lib/cache.c
>> @@ -7,10 +7,44 @@
>>  
>>  #include <common.h>
>>  #include <asm/cacheops.h>
>> +#include <asm/cm.h>
>>  #include <asm/mipsregs.h>
>>  
>>  DECLARE_GLOBAL_DATA_PTR;
>>  
>> +static void probe_l2(void)
>> +{
>> +#ifdef CONFIG_MIPS_L2_CACHE
>> +	unsigned long conf2, sl;
>> +	bool l2c = false;
>> +
>> +	if (!(read_c0_config1() & MIPS_CONF_M))
>> +		return;
>> +
>> +	conf2 = read_c0_config2();
>> +
>> +	if (__mips_isa_rev >= 6) {
>> +		l2c = conf2 & MIPS_CONF_M;
>> +		if (l2c)
>> +			l2c = read_c0_config3() & MIPS_CONF_M;
>> +		if (l2c)
>> +			l2c = read_c0_config4() & MIPS_CONF_M;
>> +		if (l2c)
>> +			l2c = read_c0_config5() & MIPS_CONF5_L2C;
>> +	}
>> +
>> +	if (l2c && config_enabled(CONFIG_MIPS_CM)) {
>> +		gd->arch.l2_line_size = mips_cm_l2_line_size();
>> +	} else if (l2c) {
>> +		/* We don't know how to retrieve L2 config on this system */
>> +		BUG();
>> +	} else {
>> +		sl = (conf2 & MIPS_CONF2_SL) >> MIPS_CONF2_SL_SHF;
>> +		gd->arch.l2_line_size = sl ? (2 << sl) : 0;
>> +	}
>> +#endif
>> +}
>> +
>>  void mips_cache_probe(void)
>>  {
>>  #ifdef CONFIG_SYS_CACHE_SIZE_AUTO
>> @@ -24,6 +58,7 @@ void mips_cache_probe(void)
>>  	gd->arch.l1i_line_size = il ? (2 << il) : 0;
>>  	gd->arch.l1d_line_size = dl ? (2 << dl) : 0;
>>  #endif
>> +	probe_l2();
>>  }
>>  
>>  static inline unsigned long icache_line_size(void)
>> @@ -44,6 +79,15 @@ static inline unsigned long dcache_line_size(void)
>>  #endif
>>  }
>>  
>> +static inline unsigned long scache_line_size(void)
>> +{
>> +#ifdef CONFIG_MIPS_L2_CACHE
>> +	return gd->arch.l2_line_size;
>> +#else
>> +	return 0;
>> +#endif
>> +}
>> +
>>  #define cache_loop(start, end, lsize, ops...) do {			\
>>  	const void *addr = (const void *)(start & ~(lsize - 1));	\
>>  	const void *aend = (const void *)((end - 1) & ~(lsize - 1));	\
>> @@ -60,12 +104,13 @@ void flush_cache(ulong start_addr, ulong size)
>>  {
>>  	unsigned long ilsize = icache_line_size();
>>  	unsigned long dlsize = dcache_line_size();
>> +	unsigned long slsize = scache_line_size();
>>  
>>  	/* aend will be miscalculated when size is zero, so we return here */
>>  	if (size == 0)
>>  		return;
>>  
>> -	if (ilsize == dlsize) {
>> +	if ((ilsize == dlsize) && !slsize) {
>>  		/* flush I-cache & D-cache simultaneously */
>>  		cache_loop(start_addr, start_addr + size, ilsize,
>>  			   HIT_WRITEBACK_INV_D, HIT_INVALIDATE_I);
>> @@ -75,6 +120,11 @@ void flush_cache(ulong start_addr, ulong size)
>>  	/* flush D-cache */
>>  	cache_loop(start_addr, start_addr + size, dlsize, HIT_WRITEBACK_INV_D);
>>  
>> +	/* flush L2 cache */
>> +	if (slsize)
>> +		cache_loop(start_addr, start_addr + size, slsize,
>> +			   HIT_WRITEBACK_INV_SD);
>> +
>>  	/* flush I-cache */
>>  	cache_loop(start_addr, start_addr + size, ilsize, HIT_INVALIDATE_I);
>>  }
>> @@ -82,21 +132,31 @@ void flush_cache(ulong start_addr, ulong size)
>>  void flush_dcache_range(ulong start_addr, ulong stop)
>>  {
>>  	unsigned long lsize = dcache_line_size();
>> +	unsigned long slsize = scache_line_size();
>>  
>>  	/* aend will be miscalculated when size is zero, so we return here */
>>  	if (start_addr == stop)
>>  		return;
>>  
>>  	cache_loop(start_addr, stop, lsize, HIT_WRITEBACK_INV_D);
>> +
>> +	/* flush L2 cache */
>> +	if (slsize)
>> +		cache_loop(start_addr, stop, slsize, HIT_WRITEBACK_INV_SD);
>>  }
>>  
>>  void invalidate_dcache_range(ulong start_addr, ulong stop)
>>  {
>>  	unsigned long lsize = dcache_line_size();
>> +	unsigned long slsize = scache_line_size();
>>  
>>  	/* aend will be miscalculated when size is zero, so we return here */
>>  	if (start_addr == stop)
>>  		return;
>>  
>> +	/* invalidate L2 cache */
>> +	if (slsize)
>> +		cache_loop(start_addr, stop, slsize, HIT_INVALIDATE_SD);
>> +
>>  	cache_loop(start_addr, stop, lsize, HIT_INVALIDATE_D);
>>  }
>> diff --git a/arch/mips/lib/cache_init.S b/arch/mips/lib/cache_init.S
>> index 2df3a82..599a85f 100644
>> --- a/arch/mips/lib/cache_init.S
>> +++ b/arch/mips/lib/cache_init.S
>> @@ -13,6 +13,7 @@
>>  #include <asm/mipsregs.h>
>>  #include <asm/addrspace.h>
>>  #include <asm/cacheops.h>
>> +#include <asm/cm.h>
>>  
>>  #ifndef CONFIG_SYS_MIPS_CACHE_MODE
>>  #define CONFIG_SYS_MIPS_CACHE_MODE CONF_CM_CACHABLE_NONCOHERENT
>> @@ -95,14 +96,135 @@
>>   * with good parity is available. This routine will initialise an area of
>>   * memory starting at location zero to be used as a source of parity.
>>   *
>> + * Note that this function does not follow the standard calling convention &
>> + * may clobber typically callee-saved registers.
>> + *
>>   * RETURNS: N/A
>>   *
>>   */
>> -#define R_IC_SIZE	t2
>> -#define R_IC_LINE	t8
>> -#define R_DC_SIZE	t3
>> -#define R_DC_LINE	t9
>> +#define R_RETURN	s0
>> +#define R_IC_SIZE	s1
>> +#define R_IC_LINE	s2
>> +#define R_DC_SIZE	s3
>> +#define R_DC_LINE	s4
>> +#define R_L2_SIZE	s5
>> +#define R_L2_LINE	s6
>> +#define R_L2_BYPASSED	s7
>> +#define R_L2_L2C	t8
>>  LEAF(mips_cache_reset)
>> +	move	R_RETURN, ra
>> +
>> +#ifdef CONFIG_MIPS_L2_CACHE
>> +	/*
>> +	 * For there to be an L2 present, Config2 must be present. If it isn't
>> +	 * then we proceed knowing there's no L2 cache.
>> +	 */
>> +	move	R_L2_SIZE, zero
>> +	move	R_L2_LINE, zero
>> +	move	R_L2_BYPASSED, zero
>> +	move	R_L2_L2C, zero
>> +	mfc0	t0, CP0_CONFIG, 1
>> +	bgez	t0, l2_probe_done
>> +
>> +	/*
>> +	 * From MIPSr6 onwards the L2 cache configuration might not be reported
>> +	 * by Config2. The Config5.L2C bit indicates whether this is the case,
>> +	 * and if it is then we need knowledge of where else to look. For cores
>> +	 * from Imagination Technologies this is a CM GCR.
>> +	 */
>> +# if __mips_isa_rev >= 6
>> +	/* Check that Config5 exists */
>> +	mfc0	t0, CP0_CONFIG, 2
>> +	bgez	t0, l2_probe_cop0
>> +	mfc0	t0, CP0_CONFIG, 3
>> +	bgez	t0, l2_probe_cop0
>> +	mfc0	t0, CP0_CONFIG, 4
>> +	bgez	t0, l2_probe_cop0
>> +
>> +	/* Check Config5.L2C is set */
>> +	mfc0	t0, CP0_CONFIG, 5
>> +	and	R_L2_L2C, t0, MIPS_CONF5_L2C
>> +	beqz	R_L2_L2C, l2_probe_cop0
>> +
>> +	/* Config5.L2C is set */
>> +#  ifdef CONFIG_MIPS_CM
>> +	/* The CM will provide L2 configuration */
>> +	PTR_LI	t0, CKSEG1ADDR(CONFIG_MIPS_CM_BASE)
>> +	lw	t1, GCR_L2_CONFIG(t0)
>> +	bgez	t1, l2_probe_done
>> +
>> +	ext	R_L2_LINE, t1, \
>> +		GCR_L2_CONFIG_LINESZ_SHIFT, GCR_L2_CONFIG_LINESZ_BITS
>> +	beqz	R_L2_LINE, l2_probe_done
>> +	li	t2, 2
>> +	sllv	R_L2_LINE, t2, R_L2_LINE
>> +
>> +	ext	t2, t1, GCR_L2_CONFIG_ASSOC_SHIFT, GCR_L2_CONFIG_ASSOC_BITS
>> +	addiu	t2, t2, 1
>> +	mul	R_L2_SIZE, R_L2_LINE, t2
>> +
>> +	ext	t2, t1, GCR_L2_CONFIG_SETSZ_SHIFT, GCR_L2_CONFIG_SETSZ_BITS
>> +	sllv	R_L2_SIZE, R_L2_SIZE, t2
>> +	li	t2, 64
>> +	mul	R_L2_SIZE, R_L2_SIZE, t2
>> +
>> +	/* Bypass the L2 cache so that we can init the L1s early */
>> +	or	t1, t1, GCR_L2_CONFIG_BYPASS
>> +	sw	t1, GCR_L2_CONFIG(t0)
>> +	sync
>> +	li	R_L2_BYPASSED, 1
>> +
>> +	/* Zero the L2 tag registers */
>> +	sw	zero, GCR_L2_TAG_ADDR(t0)
>> +	sw	zero, GCR_L2_TAG_ADDR_UPPER(t0)
>> +	sw	zero, GCR_L2_TAG_STATE(t0)
>> +	sw	zero, GCR_L2_TAG_STATE_UPPER(t0)
>> +	sw	zero, GCR_L2_DATA(t0)
>> +	sw	zero, GCR_L2_DATA_UPPER(t0)
>> +	sync
>> +#  else
>> +	/* We don't know how to retrieve L2 configuration on this system */
>> +#  endif
>> +	b	l2_probe_done
>> +# endif
>> +
>> +	/*
>> +	 * For pre-r6 systems, or r6 systems with Config5.L2C==0, probe the L2
>> +	 * cache configuration from the cop0 Config2 register.
>> +	 */
>> +l2_probe_cop0:
>> +	mfc0	t0, CP0_CONFIG, 2
>> +
>> +	srl	R_L2_LINE, t0, MIPS_CONF2_SL_SHF
>> +	andi	R_L2_LINE, R_L2_LINE, MIPS_CONF2_SL >> MIPS_CONF2_SL_SHF
>> +	beqz	R_L2_LINE, l2_probe_done
>> +	li	t1, 2
>> +	sllv	R_L2_LINE, t1, R_L2_LINE
>> +
>> +	srl	t1, t0, MIPS_CONF2_SA_SHF
>> +	andi	t1, t1, MIPS_CONF2_SA >> MIPS_CONF2_SA_SHF
>> +	addiu	t1, t1, 1
>> +	mul	R_L2_SIZE, R_L2_LINE, t1
>> +
>> +	srl	t1, t0, MIPS_CONF2_SS_SHF
>> +	andi	t1, t1, MIPS_CONF2_SS >> MIPS_CONF2_SS_SHF
>> +	sllv	R_L2_SIZE, R_L2_SIZE, t1
>> +	li	t1, 64
>> +	mul	R_L2_SIZE, R_L2_SIZE, t1
>> +
>> +	/* Attempt to bypass the L2 so that we can init the L1s early */
>> +	or	t0, t0, MIPS_CONF2_L2B
>> +	mtc0	t0, CP0_CONFIG, 2
>> +	ehb
>> +	mfc0	t0, CP0_CONFIG, 2
>> +	and	R_L2_BYPASSED, t0, MIPS_CONF2_L2B
>> +
>> +	/* Zero the L2 tag registers */
>> +	mtc0	zero, CP0_TAGLO, 4
>> +	ehb
>> +l2_probe_done:
>> +#endif
>> +
>>  #ifndef CONFIG_SYS_CACHE_SIZE_AUTO
>>  	li	R_IC_SIZE, CONFIG_SYS_ICACHE_SIZE
>>  	li	R_IC_LINE, CONFIG_SYS_ICACHE_LINE_SIZE
>> @@ -142,11 +264,33 @@ LEAF(mips_cache_reset)
>>  
>>  #endif /* CONFIG_SYS_MIPS_CACHE_INIT_RAM_LOAD */
>>  
>> +#ifdef CONFIG_MIPS_L2_CACHE
>> +	/*
>> +	 * If the L2 is bypassed, init the L1 first so that we can execute the
>> +	 * rest of the cache initialisation using the L1 instruction cache.
>> +	 */
>> +	bnez		R_L2_BYPASSED, l1_init
>> +
>> +l2_init:
>> +	PTR_LI		t0, INDEX_BASE
>> +	PTR_ADDU	t1, t0, R_L2_SIZE
>> +1:	cache		INDEX_STORE_TAG_SD, 0(t0)
>> +	PTR_ADDU	t0, t0, R_L2_LINE
>> +	bne		t0, t1, 1b
>> +
>> +	/*
>> +	 * If the L2 was bypassed then we already initialised the L1s before
>> +	 * the L2, so we are now done.
>> +	 */
>> +	bnez		R_L2_BYPASSED, return
>> +#endif
>> +
>>  	/*
>>  	 * The TagLo registers used depend upon the CPU implementation, but the
>>  	 * architecture requires that it is safe for software to write to both
>>  	 * TagLo selects 0 & 2 covering supported cases.
>>  	 */
>> +l1_init:
>>  	mtc0		zero, CP0_TAGLO
>>  	mtc0		zero, CP0_TAGLO, 2
>>  
>> @@ -206,8 +350,34 @@ LEAF(mips_cache_reset)
>>  	PTR_LI		t0, INDEX_BASE
>>  	cache_loop	t0, t1, R_DC_LINE, INDEX_STORE_TAG_D
>>  #endif
>> +3:
>> +
>> +#ifdef CONFIG_MIPS_L2_CACHE
>> +	/* If the L2 isn't bypassed then we're done */
>> +	beqz		R_L2_BYPASSED, return
>>  
>> -3:	jr	ra
>> +	/* The L2 is bypassed - un-bypass it then initialise it */
>> +# if __mips_isa_rev >= 6
>> +	beqz		R_L2_L2C, 1f
>> +
>> +	li		t0, CKSEG1ADDR(CONFIG_MIPS_CM_BASE)
>> +	lw		t1, GCR_L2_CONFIG(t0)
>> +	xor		t1, t1, GCR_L2_CONFIG_BYPASS
>> +	sw		t1, GCR_L2_CONFIG(t0)
>> +	sync
>> +	ehb
>> +	b		l2_init
>> +# endif
>> +1:
>> +	mfc0		t0, CP0_CONFIG, 2
>> +	xor		t0, t0, MIPS_CONF2_L2B
>> +	mtc0		t0, CP0_CONFIG, 2
>> +	ehb
>> +	b		l2_init
>> +#endif
>> +
>> +return:
>> +	jr	ra
>>  	END(mips_cache_reset)
>>  
>>  /*
>>
> 


More information about the U-Boot mailing list