xref: /openbmc/linux/arch/x86/kernel/cpu/mtrr/amd.c (revision 03409069)
1b2441318SGreg Kroah-Hartman // SPDX-License-Identifier: GPL-2.0
22ec1df41SThomas Gleixner #include <linux/init.h>
32ec1df41SThomas Gleixner #include <linux/mm.h>
42ec1df41SThomas Gleixner #include <asm/mtrr.h>
52ec1df41SThomas Gleixner #include <asm/msr.h>
62ec1df41SThomas Gleixner 
72ec1df41SThomas Gleixner #include "mtrr.h"
82ec1df41SThomas Gleixner 
92ec1df41SThomas Gleixner static void
amd_get_mtrr(unsigned int reg,unsigned long * base,unsigned long * size,mtrr_type * type)102ec1df41SThomas Gleixner amd_get_mtrr(unsigned int reg, unsigned long *base,
112ec1df41SThomas Gleixner 	     unsigned long *size, mtrr_type *type)
122ec1df41SThomas Gleixner {
132ec1df41SThomas Gleixner 	unsigned long low, high;
142ec1df41SThomas Gleixner 
152ec1df41SThomas Gleixner 	rdmsr(MSR_K6_UWCCR, low, high);
162ec1df41SThomas Gleixner 	/* Upper dword is region 1, lower is region 0 */
172ec1df41SThomas Gleixner 	if (reg == 1)
182ec1df41SThomas Gleixner 		low = high;
192ec1df41SThomas Gleixner 	/* The base masks off on the right alignment */
202ec1df41SThomas Gleixner 	*base = (low & 0xFFFE0000) >> PAGE_SHIFT;
212ec1df41SThomas Gleixner 	*type = 0;
222ec1df41SThomas Gleixner 	if (low & 1)
232ec1df41SThomas Gleixner 		*type = MTRR_TYPE_UNCACHABLE;
242ec1df41SThomas Gleixner 	if (low & 2)
252ec1df41SThomas Gleixner 		*type = MTRR_TYPE_WRCOMB;
262ec1df41SThomas Gleixner 	if (!(low & 3)) {
272ec1df41SThomas Gleixner 		*size = 0;
282ec1df41SThomas Gleixner 		return;
292ec1df41SThomas Gleixner 	}
302ec1df41SThomas Gleixner 	/*
312ec1df41SThomas Gleixner 	 * This needs a little explaining. The size is stored as an
322ec1df41SThomas Gleixner 	 * inverted mask of bits of 128K granularity 15 bits long offset
3342204455SJaswinder Singh Rajput 	 * 2 bits.
342ec1df41SThomas Gleixner 	 *
352ec1df41SThomas Gleixner 	 * So to get a size we do invert the mask and add 1 to the lowest
362ec1df41SThomas Gleixner 	 * mask bit (4 as its 2 bits in). This gives us a size we then shift
3742204455SJaswinder Singh Rajput 	 * to turn into 128K blocks.
382ec1df41SThomas Gleixner 	 *
392ec1df41SThomas Gleixner 	 * eg              111 1111 1111 1100      is 512K
402ec1df41SThomas Gleixner 	 *
412ec1df41SThomas Gleixner 	 * invert          000 0000 0000 0011
422ec1df41SThomas Gleixner 	 * +1              000 0000 0000 0100
432ec1df41SThomas Gleixner 	 * *128K   ...
442ec1df41SThomas Gleixner 	 */
452ec1df41SThomas Gleixner 	low = (~low) & 0x1FFFC;
462ec1df41SThomas Gleixner 	*size = (low + 4) << (15 - PAGE_SHIFT);
472ec1df41SThomas Gleixner }
482ec1df41SThomas Gleixner 
4942204455SJaswinder Singh Rajput /**
5042204455SJaswinder Singh Rajput  * amd_set_mtrr - Set variable MTRR register on the local CPU.
5142204455SJaswinder Singh Rajput  *
5242204455SJaswinder Singh Rajput  * @reg The register to set.
5342204455SJaswinder Singh Rajput  * @base The base address of the region.
5442204455SJaswinder Singh Rajput  * @size The size of the region. If this is 0 the region is disabled.
5542204455SJaswinder Singh Rajput  * @type The type of the region.
5642204455SJaswinder Singh Rajput  *
5742204455SJaswinder Singh Rajput  * Returns nothing.
582ec1df41SThomas Gleixner  */
5942204455SJaswinder Singh Rajput static void
amd_set_mtrr(unsigned int reg,unsigned long base,unsigned long size,mtrr_type type)6042204455SJaswinder Singh Rajput amd_set_mtrr(unsigned int reg, unsigned long base, unsigned long size, mtrr_type type)
612ec1df41SThomas Gleixner {
622ec1df41SThomas Gleixner 	u32 regs[2];
632ec1df41SThomas Gleixner 
642ec1df41SThomas Gleixner 	/*
652ec1df41SThomas Gleixner 	 * Low is MTRR0, High MTRR 1
662ec1df41SThomas Gleixner 	 */
672ec1df41SThomas Gleixner 	rdmsr(MSR_K6_UWCCR, regs[0], regs[1]);
682ec1df41SThomas Gleixner 	/*
692ec1df41SThomas Gleixner 	 * Blank to disable
702ec1df41SThomas Gleixner 	 */
7142204455SJaswinder Singh Rajput 	if (size == 0) {
722ec1df41SThomas Gleixner 		regs[reg] = 0;
7342204455SJaswinder Singh Rajput 	} else {
7442204455SJaswinder Singh Rajput 		/*
7542204455SJaswinder Singh Rajput 		 * Set the register to the base, the type (off by one) and an
7642204455SJaswinder Singh Rajput 		 * inverted bitmask of the size The size is the only odd
7742204455SJaswinder Singh Rajput 		 * bit. We are fed say 512K We invert this and we get 111 1111
7842204455SJaswinder Singh Rajput 		 * 1111 1011 but if you subtract one and invert you get the
7942204455SJaswinder Singh Rajput 		 * desired 111 1111 1111 1100 mask
8042204455SJaswinder Singh Rajput 		 *
8142204455SJaswinder Singh Rajput 		 *  But ~(x - 1) == ~x + 1 == -x. Two's complement rocks!
8242204455SJaswinder Singh Rajput 		 */
832ec1df41SThomas Gleixner 		regs[reg] = (-size >> (15 - PAGE_SHIFT) & 0x0001FFFC)
842ec1df41SThomas Gleixner 		    | (base << PAGE_SHIFT) | (type + 1);
8542204455SJaswinder Singh Rajput 	}
862ec1df41SThomas Gleixner 
872ec1df41SThomas Gleixner 	/*
882ec1df41SThomas Gleixner 	 * The writeback rule is quite specific. See the manual. Its
892ec1df41SThomas Gleixner 	 * disable local interrupts, write back the cache, set the mtrr
902ec1df41SThomas Gleixner 	 */
912ec1df41SThomas Gleixner 	wbinvd();
922ec1df41SThomas Gleixner 	wrmsr(MSR_K6_UWCCR, regs[0], regs[1]);
932ec1df41SThomas Gleixner }
942ec1df41SThomas Gleixner 
9542204455SJaswinder Singh Rajput static int
amd_validate_add_page(unsigned long base,unsigned long size,unsigned int type)9642204455SJaswinder Singh Rajput amd_validate_add_page(unsigned long base, unsigned long size, unsigned int type)
972ec1df41SThomas Gleixner {
9842204455SJaswinder Singh Rajput 	/*
9942204455SJaswinder Singh Rajput 	 * Apply the K6 block alignment and size rules
10042204455SJaswinder Singh Rajput 	 * In order
10142204455SJaswinder Singh Rajput 	 * o Uncached or gathering only
10242204455SJaswinder Singh Rajput 	 * o 128K or bigger block
10342204455SJaswinder Singh Rajput 	 * o Power of 2 block
10442204455SJaswinder Singh Rajput 	 * o base suitably aligned to the power
1052ec1df41SThomas Gleixner 	 */
1062ec1df41SThomas Gleixner 	if (type > MTRR_TYPE_WRCOMB || size < (1 << (17 - PAGE_SHIFT))
1072ec1df41SThomas Gleixner 	    || (size & ~(size - 1)) - size || (base & (size - 1)))
1082ec1df41SThomas Gleixner 		return -EINVAL;
1092ec1df41SThomas Gleixner 	return 0;
1102ec1df41SThomas Gleixner }
1112ec1df41SThomas Gleixner 
112f8bd9f25SJuergen Gross const struct mtrr_ops amd_mtrr_ops = {
113*03409069SJuergen Gross 	.var_regs          = 2,
1142ec1df41SThomas Gleixner 	.set               = amd_set_mtrr,
1152ec1df41SThomas Gleixner 	.get               = amd_get_mtrr,
1162ec1df41SThomas Gleixner 	.get_free_region   = generic_get_free_region,
1172ec1df41SThomas Gleixner 	.validate_add_page = amd_validate_add_page,
1182ec1df41SThomas Gleixner 	.have_wrcomb       = positive_have_wrcomb,
1192ec1df41SThomas Gleixner };
120