xref: /linux/arch/x86/kernel/cpu/mtrr/amd.c (revision 570f7e331f5febb30f1384817463c7e42b65ca7d)
1 // SPDX-License-Identifier: GPL-2.0
2 #include <linux/init.h>
3 #include <linux/mm.h>
4 #include <asm/mtrr.h>
5 #include <asm/msr.h>
6 
7 #include "mtrr.h"
8 
9 static void
10 amd_get_mtrr(unsigned int reg, unsigned long *base,
11 	     unsigned long *size, mtrr_type *type)
12 {
13 	unsigned long val;
14 	struct msr msr;
15 
16 	rdmsrq(MSR_K6_UWCCR, msr.q);
17 	/* Upper dword is region 1, lower is region 0 */
18 	if (reg == 1)
19 		val = msr.h;
20 	else
21 		val = msr.l;
22 	/* The base masks off on the right alignment */
23 	*base = (val & 0xFFFE0000) >> PAGE_SHIFT;
24 	*type = 0;
25 	if (val & 1)
26 		*type = MTRR_TYPE_UNCACHABLE;
27 	if (val & 2)
28 		*type = MTRR_TYPE_WRCOMB;
29 	if (!(val & 3)) {
30 		*size = 0;
31 		return;
32 	}
33 	/*
34 	 * This needs a little explaining. The size is stored as an
35 	 * inverted mask of bits of 128K granularity 15 bits long offset
36 	 * 2 bits.
37 	 *
38 	 * So to get a size we do invert the mask and add 1 to the lowest
39 	 * mask bit (4 as its 2 bits in). This gives us a size we then shift
40 	 * to turn into 128K blocks.
41 	 *
42 	 * eg              111 1111 1111 1100      is 512K
43 	 *
44 	 * invert          000 0000 0000 0011
45 	 * +1              000 0000 0000 0100
46 	 * *128K   ...
47 	 */
48 	val = (~val) & 0x1FFFC;
49 	*size = (val + 4) << (15 - PAGE_SHIFT);
50 }
51 
52 /**
53  * amd_set_mtrr - Set variable MTRR register on the local CPU.
54  *
55  * @reg The register to set.
56  * @base The base address of the region.
57  * @size The size of the region. If this is 0 the region is disabled.
58  * @type The type of the region.
59  *
60  * Returns nothing.
61  */
62 static void
63 amd_set_mtrr(unsigned int reg, unsigned long base, unsigned long size, mtrr_type type)
64 {
65 	struct msr msr;
66 	u32 regs[2];
67 
68 	/*
69 	 * Low is MTRR0, High MTRR 1
70 	 */
71 	rdmsrq(MSR_K6_UWCCR, msr.q);
72 	regs[0] = msr.l;
73 	regs[1] = msr.h;
74 
75 	/*
76 	 * Blank to disable
77 	 */
78 	if (size == 0) {
79 		regs[reg] = 0;
80 	} else {
81 		/*
82 		 * Set the register to the base, the type (off by one) and an
83 		 * inverted bitmask of the size The size is the only odd
84 		 * bit. We are fed say 512K We invert this and we get 111 1111
85 		 * 1111 1011 but if you subtract one and invert you get the
86 		 * desired 111 1111 1111 1100 mask
87 		 *
88 		 *  But ~(x - 1) == ~x + 1 == -x. Two's complement rocks!
89 		 */
90 		regs[reg] = (-size >> (15 - PAGE_SHIFT) & 0x0001FFFC)
91 		    | (base << PAGE_SHIFT) | (type + 1);
92 	}
93 
94 	/*
95 	 * The writeback rule is quite specific. See the manual. Its
96 	 * disable local interrupts, write back the cache, set the mtrr
97 	 */
98 	wbinvd();
99 	msr.l = regs[0];
100 	msr.h = regs[1];
101 	wrmsrq(MSR_K6_UWCCR, msr.q);
102 }
103 
104 static int
105 amd_validate_add_page(unsigned long base, unsigned long size, unsigned int type)
106 {
107 	/*
108 	 * Apply the K6 block alignment and size rules
109 	 * In order
110 	 * o Uncached or gathering only
111 	 * o 128K or bigger block
112 	 * o Power of 2 block
113 	 * o base suitably aligned to the power
114 	 */
115 	if (type > MTRR_TYPE_WRCOMB || size < (1 << (17 - PAGE_SHIFT))
116 	    || (size & ~(size - 1)) - size || (base & (size - 1)))
117 		return -EINVAL;
118 	return 0;
119 }
120 
121 const struct mtrr_ops amd_mtrr_ops = {
122 	.var_regs          = 2,
123 	.set               = amd_set_mtrr,
124 	.get               = amd_get_mtrr,
125 	.get_free_region   = generic_get_free_region,
126 	.validate_add_page = amd_validate_add_page,
127 	.have_wrcomb       = positive_have_wrcomb,
128 };
129