xref: /linux/arch/x86/kernel/cpu/mtrr/amd.c (revision 2d7eaeb05ae265bd64270376e6a2c0d80244771f)
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  * @reg: The register to set.
55  * @base: The base address of the region.
56  * @size: The size of the region. If this is 0 the region is disabled.
57  * @type: The type of the region.
58  *
59  * Returns nothing.
60  */
61 static void
62 amd_set_mtrr(unsigned int reg, unsigned long base, unsigned long size, mtrr_type type)
63 {
64 	struct msr msr;
65 	u32 regs[2];
66 
67 	/*
68 	 * Low is MTRR0, High MTRR 1
69 	 */
70 	rdmsrq(MSR_K6_UWCCR, msr.q);
71 	regs[0] = msr.l;
72 	regs[1] = msr.h;
73 
74 	/*
75 	 * Blank to disable
76 	 */
77 	if (size == 0) {
78 		regs[reg] = 0;
79 	} else {
80 		/*
81 		 * Set the register to the base, the type (off by one) and an
82 		 * inverted bitmask of the size The size is the only odd
83 		 * bit. We are fed say 512K We invert this and we get 111 1111
84 		 * 1111 1011 but if you subtract one and invert you get the
85 		 * desired 111 1111 1111 1100 mask
86 		 *
87 		 *  But ~(x - 1) == ~x + 1 == -x. Two's complement rocks!
88 		 */
89 		regs[reg] = (-size >> (15 - PAGE_SHIFT) & 0x0001FFFC)
90 		    | (base << PAGE_SHIFT) | (type + 1);
91 	}
92 
93 	/*
94 	 * The writeback rule is quite specific. See the manual. Its
95 	 * disable local interrupts, write back the cache, set the mtrr
96 	 */
97 	wbinvd();
98 	msr.l = regs[0];
99 	msr.h = regs[1];
100 	wrmsrq(MSR_K6_UWCCR, msr.q);
101 }
102 
103 static int
104 amd_validate_add_page(unsigned long base, unsigned long size, unsigned int type)
105 {
106 	/*
107 	 * Apply the K6 block alignment and size rules
108 	 * In order
109 	 * o Uncached or gathering only
110 	 * o 128K or bigger block
111 	 * o Power of 2 block
112 	 * o base suitably aligned to the power
113 	 */
114 	if (type > MTRR_TYPE_WRCOMB || size < (1 << (17 - PAGE_SHIFT))
115 	    || (size & ~(size - 1)) - size || (base & (size - 1)))
116 		return -EINVAL;
117 	return 0;
118 }
119 
120 const struct mtrr_ops amd_mtrr_ops = {
121 	.var_regs          = 2,
122 	.set               = amd_set_mtrr,
123 	.get               = amd_get_mtrr,
124 	.get_free_region   = generic_get_free_region,
125 	.validate_add_page = amd_validate_add_page,
126 	.have_wrcomb       = positive_have_wrcomb,
127 };
128