12ec1df41SThomas Gleixner /* This only handles 32bit MTRR on 32bit hosts. This is strictly wrong 22ec1df41SThomas Gleixner because MTRRs can span upto 40 bits (36bits on most modern x86) */ 32ec1df41SThomas Gleixner #include <linux/init.h> 42ec1df41SThomas Gleixner #include <linux/slab.h> 52ec1df41SThomas Gleixner #include <linux/mm.h> 62ec1df41SThomas Gleixner #include <linux/module.h> 72ec1df41SThomas Gleixner #include <asm/io.h> 82ec1df41SThomas Gleixner #include <asm/mtrr.h> 92ec1df41SThomas Gleixner #include <asm/msr.h> 102ec1df41SThomas Gleixner #include <asm/system.h> 112ec1df41SThomas Gleixner #include <asm/cpufeature.h> 127ebad705SDave Jones #include <asm/processor-flags.h> 132ec1df41SThomas Gleixner #include <asm/tlbflush.h> 14*2e5d9c85Svenkatesh.pallipadi@intel.com #include <asm/pat.h> 152ec1df41SThomas Gleixner #include "mtrr.h" 162ec1df41SThomas Gleixner 172ec1df41SThomas Gleixner struct mtrr_state { 1899fc8d42SJesse Barnes struct mtrr_var_range var_ranges[MAX_VAR_RANGES]; 192ec1df41SThomas Gleixner mtrr_type fixed_ranges[NUM_FIXED_RANGES]; 202ec1df41SThomas Gleixner unsigned char enabled; 212ec1df41SThomas Gleixner unsigned char have_fixed; 222ec1df41SThomas Gleixner mtrr_type def_type; 232ec1df41SThomas Gleixner }; 242ec1df41SThomas Gleixner 252ec1df41SThomas Gleixner struct fixed_range_block { 262ec1df41SThomas Gleixner int base_msr; /* start address of an MTRR block */ 272ec1df41SThomas Gleixner int ranges; /* number of MTRRs in this block */ 282ec1df41SThomas Gleixner }; 292ec1df41SThomas Gleixner 302ec1df41SThomas Gleixner static struct fixed_range_block fixed_range_blocks[] = { 312ec1df41SThomas Gleixner { MTRRfix64K_00000_MSR, 1 }, /* one 64k MTRR */ 322ec1df41SThomas Gleixner { MTRRfix16K_80000_MSR, 2 }, /* two 16k MTRRs */ 332ec1df41SThomas Gleixner { MTRRfix4K_C0000_MSR, 8 }, /* eight 4k MTRRs */ 342ec1df41SThomas Gleixner {} 352ec1df41SThomas Gleixner }; 362ec1df41SThomas Gleixner 372ec1df41SThomas Gleixner static unsigned long smp_changes_mask; 382ec1df41SThomas Gleixner static struct mtrr_state mtrr_state = {}; 39*2e5d9c85Svenkatesh.pallipadi@intel.com static int mtrr_state_set; 402ec1df41SThomas Gleixner 412ec1df41SThomas Gleixner #undef MODULE_PARAM_PREFIX 422ec1df41SThomas Gleixner #define MODULE_PARAM_PREFIX "mtrr." 432ec1df41SThomas Gleixner 442ec1df41SThomas Gleixner static int mtrr_show; 452ec1df41SThomas Gleixner module_param_named(show, mtrr_show, bool, 0); 462ec1df41SThomas Gleixner 47*2e5d9c85Svenkatesh.pallipadi@intel.com /* 48*2e5d9c85Svenkatesh.pallipadi@intel.com * Returns the effective MTRR type for the region 49*2e5d9c85Svenkatesh.pallipadi@intel.com * Error returns: 50*2e5d9c85Svenkatesh.pallipadi@intel.com * - 0xFE - when the range is "not entirely covered" by _any_ var range MTRR 51*2e5d9c85Svenkatesh.pallipadi@intel.com * - 0xFF - when MTRR is not enabled 52*2e5d9c85Svenkatesh.pallipadi@intel.com */ 53*2e5d9c85Svenkatesh.pallipadi@intel.com u8 mtrr_type_lookup(u64 start, u64 end) 54*2e5d9c85Svenkatesh.pallipadi@intel.com { 55*2e5d9c85Svenkatesh.pallipadi@intel.com int i; 56*2e5d9c85Svenkatesh.pallipadi@intel.com u64 base, mask; 57*2e5d9c85Svenkatesh.pallipadi@intel.com u8 prev_match, curr_match; 58*2e5d9c85Svenkatesh.pallipadi@intel.com 59*2e5d9c85Svenkatesh.pallipadi@intel.com if (!mtrr_state_set) 60*2e5d9c85Svenkatesh.pallipadi@intel.com return 0xFF; 61*2e5d9c85Svenkatesh.pallipadi@intel.com 62*2e5d9c85Svenkatesh.pallipadi@intel.com if (!mtrr_state.enabled) 63*2e5d9c85Svenkatesh.pallipadi@intel.com return 0xFF; 64*2e5d9c85Svenkatesh.pallipadi@intel.com 65*2e5d9c85Svenkatesh.pallipadi@intel.com /* Make end inclusive end, instead of exclusive */ 66*2e5d9c85Svenkatesh.pallipadi@intel.com end--; 67*2e5d9c85Svenkatesh.pallipadi@intel.com 68*2e5d9c85Svenkatesh.pallipadi@intel.com /* Look in fixed ranges. Just return the type as per start */ 69*2e5d9c85Svenkatesh.pallipadi@intel.com if (mtrr_state.have_fixed && (start < 0x100000)) { 70*2e5d9c85Svenkatesh.pallipadi@intel.com int idx; 71*2e5d9c85Svenkatesh.pallipadi@intel.com 72*2e5d9c85Svenkatesh.pallipadi@intel.com if (start < 0x80000) { 73*2e5d9c85Svenkatesh.pallipadi@intel.com idx = 0; 74*2e5d9c85Svenkatesh.pallipadi@intel.com idx += (start >> 16); 75*2e5d9c85Svenkatesh.pallipadi@intel.com return mtrr_state.fixed_ranges[idx]; 76*2e5d9c85Svenkatesh.pallipadi@intel.com } else if (start < 0xC0000) { 77*2e5d9c85Svenkatesh.pallipadi@intel.com idx = 1 * 8; 78*2e5d9c85Svenkatesh.pallipadi@intel.com idx += ((start - 0x80000) >> 14); 79*2e5d9c85Svenkatesh.pallipadi@intel.com return mtrr_state.fixed_ranges[idx]; 80*2e5d9c85Svenkatesh.pallipadi@intel.com } else if (start < 0x1000000) { 81*2e5d9c85Svenkatesh.pallipadi@intel.com idx = 3 * 8; 82*2e5d9c85Svenkatesh.pallipadi@intel.com idx += ((start - 0xC0000) >> 12); 83*2e5d9c85Svenkatesh.pallipadi@intel.com return mtrr_state.fixed_ranges[idx]; 84*2e5d9c85Svenkatesh.pallipadi@intel.com } 85*2e5d9c85Svenkatesh.pallipadi@intel.com } 86*2e5d9c85Svenkatesh.pallipadi@intel.com 87*2e5d9c85Svenkatesh.pallipadi@intel.com /* 88*2e5d9c85Svenkatesh.pallipadi@intel.com * Look in variable ranges 89*2e5d9c85Svenkatesh.pallipadi@intel.com * Look of multiple ranges matching this address and pick type 90*2e5d9c85Svenkatesh.pallipadi@intel.com * as per MTRR precedence 91*2e5d9c85Svenkatesh.pallipadi@intel.com */ 92*2e5d9c85Svenkatesh.pallipadi@intel.com if (!mtrr_state.enabled & 2) { 93*2e5d9c85Svenkatesh.pallipadi@intel.com return mtrr_state.def_type; 94*2e5d9c85Svenkatesh.pallipadi@intel.com } 95*2e5d9c85Svenkatesh.pallipadi@intel.com 96*2e5d9c85Svenkatesh.pallipadi@intel.com prev_match = 0xFF; 97*2e5d9c85Svenkatesh.pallipadi@intel.com for (i = 0; i < num_var_ranges; ++i) { 98*2e5d9c85Svenkatesh.pallipadi@intel.com unsigned short start_state, end_state; 99*2e5d9c85Svenkatesh.pallipadi@intel.com 100*2e5d9c85Svenkatesh.pallipadi@intel.com if (!(mtrr_state.var_ranges[i].mask_lo & (1 << 11))) 101*2e5d9c85Svenkatesh.pallipadi@intel.com continue; 102*2e5d9c85Svenkatesh.pallipadi@intel.com 103*2e5d9c85Svenkatesh.pallipadi@intel.com base = (((u64)mtrr_state.var_ranges[i].base_hi) << 32) + 104*2e5d9c85Svenkatesh.pallipadi@intel.com (mtrr_state.var_ranges[i].base_lo & PAGE_MASK); 105*2e5d9c85Svenkatesh.pallipadi@intel.com mask = (((u64)mtrr_state.var_ranges[i].mask_hi) << 32) + 106*2e5d9c85Svenkatesh.pallipadi@intel.com (mtrr_state.var_ranges[i].mask_lo & PAGE_MASK); 107*2e5d9c85Svenkatesh.pallipadi@intel.com 108*2e5d9c85Svenkatesh.pallipadi@intel.com start_state = ((start & mask) == (base & mask)); 109*2e5d9c85Svenkatesh.pallipadi@intel.com end_state = ((end & mask) == (base & mask)); 110*2e5d9c85Svenkatesh.pallipadi@intel.com if (start_state != end_state) 111*2e5d9c85Svenkatesh.pallipadi@intel.com return 0xFE; 112*2e5d9c85Svenkatesh.pallipadi@intel.com 113*2e5d9c85Svenkatesh.pallipadi@intel.com if ((start & mask) != (base & mask)) { 114*2e5d9c85Svenkatesh.pallipadi@intel.com continue; 115*2e5d9c85Svenkatesh.pallipadi@intel.com } 116*2e5d9c85Svenkatesh.pallipadi@intel.com 117*2e5d9c85Svenkatesh.pallipadi@intel.com curr_match = mtrr_state.var_ranges[i].base_lo & 0xff; 118*2e5d9c85Svenkatesh.pallipadi@intel.com if (prev_match == 0xFF) { 119*2e5d9c85Svenkatesh.pallipadi@intel.com prev_match = curr_match; 120*2e5d9c85Svenkatesh.pallipadi@intel.com continue; 121*2e5d9c85Svenkatesh.pallipadi@intel.com } 122*2e5d9c85Svenkatesh.pallipadi@intel.com 123*2e5d9c85Svenkatesh.pallipadi@intel.com if (prev_match == MTRR_TYPE_UNCACHABLE || 124*2e5d9c85Svenkatesh.pallipadi@intel.com curr_match == MTRR_TYPE_UNCACHABLE) { 125*2e5d9c85Svenkatesh.pallipadi@intel.com return MTRR_TYPE_UNCACHABLE; 126*2e5d9c85Svenkatesh.pallipadi@intel.com } 127*2e5d9c85Svenkatesh.pallipadi@intel.com 128*2e5d9c85Svenkatesh.pallipadi@intel.com if ((prev_match == MTRR_TYPE_WRBACK && 129*2e5d9c85Svenkatesh.pallipadi@intel.com curr_match == MTRR_TYPE_WRTHROUGH) || 130*2e5d9c85Svenkatesh.pallipadi@intel.com (prev_match == MTRR_TYPE_WRTHROUGH && 131*2e5d9c85Svenkatesh.pallipadi@intel.com curr_match == MTRR_TYPE_WRBACK)) { 132*2e5d9c85Svenkatesh.pallipadi@intel.com prev_match = MTRR_TYPE_WRTHROUGH; 133*2e5d9c85Svenkatesh.pallipadi@intel.com curr_match = MTRR_TYPE_WRTHROUGH; 134*2e5d9c85Svenkatesh.pallipadi@intel.com } 135*2e5d9c85Svenkatesh.pallipadi@intel.com 136*2e5d9c85Svenkatesh.pallipadi@intel.com if (prev_match != curr_match) { 137*2e5d9c85Svenkatesh.pallipadi@intel.com return MTRR_TYPE_UNCACHABLE; 138*2e5d9c85Svenkatesh.pallipadi@intel.com } 139*2e5d9c85Svenkatesh.pallipadi@intel.com } 140*2e5d9c85Svenkatesh.pallipadi@intel.com 141*2e5d9c85Svenkatesh.pallipadi@intel.com if (prev_match != 0xFF) 142*2e5d9c85Svenkatesh.pallipadi@intel.com return prev_match; 143*2e5d9c85Svenkatesh.pallipadi@intel.com 144*2e5d9c85Svenkatesh.pallipadi@intel.com return mtrr_state.def_type; 145*2e5d9c85Svenkatesh.pallipadi@intel.com } 146*2e5d9c85Svenkatesh.pallipadi@intel.com 1472ec1df41SThomas Gleixner /* Get the MSR pair relating to a var range */ 1482ec1df41SThomas Gleixner static void 1492ec1df41SThomas Gleixner get_mtrr_var_range(unsigned int index, struct mtrr_var_range *vr) 1502ec1df41SThomas Gleixner { 1512ec1df41SThomas Gleixner rdmsr(MTRRphysBase_MSR(index), vr->base_lo, vr->base_hi); 1522ec1df41SThomas Gleixner rdmsr(MTRRphysMask_MSR(index), vr->mask_lo, vr->mask_hi); 1532ec1df41SThomas Gleixner } 1542ec1df41SThomas Gleixner 1552ec1df41SThomas Gleixner static void 1562ec1df41SThomas Gleixner get_fixed_ranges(mtrr_type * frs) 1572ec1df41SThomas Gleixner { 1582ec1df41SThomas Gleixner unsigned int *p = (unsigned int *) frs; 1592ec1df41SThomas Gleixner int i; 1602ec1df41SThomas Gleixner 1612ec1df41SThomas Gleixner rdmsr(MTRRfix64K_00000_MSR, p[0], p[1]); 1622ec1df41SThomas Gleixner 1632ec1df41SThomas Gleixner for (i = 0; i < 2; i++) 1642ec1df41SThomas Gleixner rdmsr(MTRRfix16K_80000_MSR + i, p[2 + i * 2], p[3 + i * 2]); 1652ec1df41SThomas Gleixner for (i = 0; i < 8; i++) 1662ec1df41SThomas Gleixner rdmsr(MTRRfix4K_C0000_MSR + i, p[6 + i * 2], p[7 + i * 2]); 1672ec1df41SThomas Gleixner } 1682ec1df41SThomas Gleixner 1692ec1df41SThomas Gleixner void mtrr_save_fixed_ranges(void *info) 1702ec1df41SThomas Gleixner { 1712ec1df41SThomas Gleixner if (cpu_has_mtrr) 1722ec1df41SThomas Gleixner get_fixed_ranges(mtrr_state.fixed_ranges); 1732ec1df41SThomas Gleixner } 1742ec1df41SThomas Gleixner 1752ec1df41SThomas Gleixner static void print_fixed(unsigned base, unsigned step, const mtrr_type*types) 1762ec1df41SThomas Gleixner { 1772ec1df41SThomas Gleixner unsigned i; 1782ec1df41SThomas Gleixner 1792ec1df41SThomas Gleixner for (i = 0; i < 8; ++i, ++types, base += step) 1802ec1df41SThomas Gleixner printk(KERN_INFO "MTRR %05X-%05X %s\n", 1812ec1df41SThomas Gleixner base, base + step - 1, mtrr_attrib_to_str(*types)); 1822ec1df41SThomas Gleixner } 1832ec1df41SThomas Gleixner 184*2e5d9c85Svenkatesh.pallipadi@intel.com static void prepare_set(void); 185*2e5d9c85Svenkatesh.pallipadi@intel.com static void post_set(void); 186*2e5d9c85Svenkatesh.pallipadi@intel.com 1872ec1df41SThomas Gleixner /* Grab all of the MTRR state for this CPU into *state */ 1882ec1df41SThomas Gleixner void __init get_mtrr_state(void) 1892ec1df41SThomas Gleixner { 1902ec1df41SThomas Gleixner unsigned int i; 1912ec1df41SThomas Gleixner struct mtrr_var_range *vrs; 1922ec1df41SThomas Gleixner unsigned lo, dummy; 193*2e5d9c85Svenkatesh.pallipadi@intel.com unsigned long flags; 1942ec1df41SThomas Gleixner 1952ec1df41SThomas Gleixner vrs = mtrr_state.var_ranges; 1962ec1df41SThomas Gleixner 1972ec1df41SThomas Gleixner rdmsr(MTRRcap_MSR, lo, dummy); 1982ec1df41SThomas Gleixner mtrr_state.have_fixed = (lo >> 8) & 1; 1992ec1df41SThomas Gleixner 2002ec1df41SThomas Gleixner for (i = 0; i < num_var_ranges; i++) 2012ec1df41SThomas Gleixner get_mtrr_var_range(i, &vrs[i]); 2022ec1df41SThomas Gleixner if (mtrr_state.have_fixed) 2032ec1df41SThomas Gleixner get_fixed_ranges(mtrr_state.fixed_ranges); 2042ec1df41SThomas Gleixner 2052ec1df41SThomas Gleixner rdmsr(MTRRdefType_MSR, lo, dummy); 2062ec1df41SThomas Gleixner mtrr_state.def_type = (lo & 0xff); 2072ec1df41SThomas Gleixner mtrr_state.enabled = (lo & 0xc00) >> 10; 2082ec1df41SThomas Gleixner 2092ec1df41SThomas Gleixner if (mtrr_show) { 2102ec1df41SThomas Gleixner int high_width; 2112ec1df41SThomas Gleixner 2122ec1df41SThomas Gleixner printk(KERN_INFO "MTRR default type: %s\n", mtrr_attrib_to_str(mtrr_state.def_type)); 2132ec1df41SThomas Gleixner if (mtrr_state.have_fixed) { 2142ec1df41SThomas Gleixner printk(KERN_INFO "MTRR fixed ranges %sabled:\n", 2152ec1df41SThomas Gleixner mtrr_state.enabled & 1 ? "en" : "dis"); 2162ec1df41SThomas Gleixner print_fixed(0x00000, 0x10000, mtrr_state.fixed_ranges + 0); 2172ec1df41SThomas Gleixner for (i = 0; i < 2; ++i) 2182ec1df41SThomas Gleixner print_fixed(0x80000 + i * 0x20000, 0x04000, mtrr_state.fixed_ranges + (i + 1) * 8); 2192ec1df41SThomas Gleixner for (i = 0; i < 8; ++i) 2202ec1df41SThomas Gleixner print_fixed(0xC0000 + i * 0x08000, 0x01000, mtrr_state.fixed_ranges + (i + 3) * 8); 2212ec1df41SThomas Gleixner } 2222ec1df41SThomas Gleixner printk(KERN_INFO "MTRR variable ranges %sabled:\n", 2232ec1df41SThomas Gleixner mtrr_state.enabled & 2 ? "en" : "dis"); 2242ec1df41SThomas Gleixner high_width = ((size_or_mask ? ffs(size_or_mask) - 1 : 32) - (32 - PAGE_SHIFT) + 3) / 4; 2252ec1df41SThomas Gleixner for (i = 0; i < num_var_ranges; ++i) { 2262ec1df41SThomas Gleixner if (mtrr_state.var_ranges[i].mask_lo & (1 << 11)) 2272ec1df41SThomas Gleixner printk(KERN_INFO "MTRR %u base %0*X%05X000 mask %0*X%05X000 %s\n", 2282ec1df41SThomas Gleixner i, 2292ec1df41SThomas Gleixner high_width, 2302ec1df41SThomas Gleixner mtrr_state.var_ranges[i].base_hi, 2312ec1df41SThomas Gleixner mtrr_state.var_ranges[i].base_lo >> 12, 2322ec1df41SThomas Gleixner high_width, 2332ec1df41SThomas Gleixner mtrr_state.var_ranges[i].mask_hi, 2342ec1df41SThomas Gleixner mtrr_state.var_ranges[i].mask_lo >> 12, 2352ec1df41SThomas Gleixner mtrr_attrib_to_str(mtrr_state.var_ranges[i].base_lo & 0xff)); 2362ec1df41SThomas Gleixner else 2372ec1df41SThomas Gleixner printk(KERN_INFO "MTRR %u disabled\n", i); 2382ec1df41SThomas Gleixner } 2392ec1df41SThomas Gleixner } 240*2e5d9c85Svenkatesh.pallipadi@intel.com mtrr_state_set = 1; 241*2e5d9c85Svenkatesh.pallipadi@intel.com 242*2e5d9c85Svenkatesh.pallipadi@intel.com /* PAT setup for BP. We need to go through sync steps here */ 243*2e5d9c85Svenkatesh.pallipadi@intel.com local_irq_save(flags); 244*2e5d9c85Svenkatesh.pallipadi@intel.com prepare_set(); 245*2e5d9c85Svenkatesh.pallipadi@intel.com 246*2e5d9c85Svenkatesh.pallipadi@intel.com pat_init(); 247*2e5d9c85Svenkatesh.pallipadi@intel.com 248*2e5d9c85Svenkatesh.pallipadi@intel.com post_set(); 249*2e5d9c85Svenkatesh.pallipadi@intel.com local_irq_restore(flags); 250*2e5d9c85Svenkatesh.pallipadi@intel.com 2512ec1df41SThomas Gleixner } 2522ec1df41SThomas Gleixner 2532ec1df41SThomas Gleixner /* Some BIOS's are fucked and don't set all MTRRs the same! */ 2542ec1df41SThomas Gleixner void __init mtrr_state_warn(void) 2552ec1df41SThomas Gleixner { 2562ec1df41SThomas Gleixner unsigned long mask = smp_changes_mask; 2572ec1df41SThomas Gleixner 2582ec1df41SThomas Gleixner if (!mask) 2592ec1df41SThomas Gleixner return; 2602ec1df41SThomas Gleixner if (mask & MTRR_CHANGE_MASK_FIXED) 2612ec1df41SThomas Gleixner printk(KERN_WARNING "mtrr: your CPUs had inconsistent fixed MTRR settings\n"); 2622ec1df41SThomas Gleixner if (mask & MTRR_CHANGE_MASK_VARIABLE) 2632ec1df41SThomas Gleixner printk(KERN_WARNING "mtrr: your CPUs had inconsistent variable MTRR settings\n"); 2642ec1df41SThomas Gleixner if (mask & MTRR_CHANGE_MASK_DEFTYPE) 2652ec1df41SThomas Gleixner printk(KERN_WARNING "mtrr: your CPUs had inconsistent MTRRdefType settings\n"); 2662ec1df41SThomas Gleixner printk(KERN_INFO "mtrr: probably your BIOS does not setup all CPUs.\n"); 2672ec1df41SThomas Gleixner printk(KERN_INFO "mtrr: corrected configuration.\n"); 2682ec1df41SThomas Gleixner } 2692ec1df41SThomas Gleixner 2702ec1df41SThomas Gleixner /* Doesn't attempt to pass an error out to MTRR users 2712ec1df41SThomas Gleixner because it's quite complicated in some cases and probably not 2722ec1df41SThomas Gleixner worth it because the best error handling is to ignore it. */ 2732ec1df41SThomas Gleixner void mtrr_wrmsr(unsigned msr, unsigned a, unsigned b) 2742ec1df41SThomas Gleixner { 2752ec1df41SThomas Gleixner if (wrmsr_safe(msr, a, b) < 0) 2762ec1df41SThomas Gleixner printk(KERN_ERR 2772ec1df41SThomas Gleixner "MTRR: CPU %u: Writing MSR %x to %x:%x failed\n", 2782ec1df41SThomas Gleixner smp_processor_id(), msr, a, b); 2792ec1df41SThomas Gleixner } 2802ec1df41SThomas Gleixner 2812ec1df41SThomas Gleixner /** 2822ec1df41SThomas Gleixner * Enable and allow read/write of extended fixed-range MTRR bits on K8 CPUs 2832ec1df41SThomas Gleixner * see AMD publication no. 24593, chapter 3.2.1 for more information 2842ec1df41SThomas Gleixner */ 2852ec1df41SThomas Gleixner static inline void k8_enable_fixed_iorrs(void) 2862ec1df41SThomas Gleixner { 2872ec1df41SThomas Gleixner unsigned lo, hi; 2882ec1df41SThomas Gleixner 2892ec1df41SThomas Gleixner rdmsr(MSR_K8_SYSCFG, lo, hi); 2902ec1df41SThomas Gleixner mtrr_wrmsr(MSR_K8_SYSCFG, lo 2912ec1df41SThomas Gleixner | K8_MTRRFIXRANGE_DRAM_ENABLE 2922ec1df41SThomas Gleixner | K8_MTRRFIXRANGE_DRAM_MODIFY, hi); 2932ec1df41SThomas Gleixner } 2942ec1df41SThomas Gleixner 2952ec1df41SThomas Gleixner /** 2961d3381ebSRandy Dunlap * set_fixed_range - checks & updates a fixed-range MTRR if it differs from the value it should have 2971d3381ebSRandy Dunlap * @msr: MSR address of the MTTR which should be checked and updated 2981d3381ebSRandy Dunlap * @changed: pointer which indicates whether the MTRR needed to be changed 2991d3381ebSRandy Dunlap * @msrwords: pointer to the MSR values which the MSR should have 3001d3381ebSRandy Dunlap * 3011d3381ebSRandy Dunlap * If K8 extentions are wanted, update the K8 SYSCFG MSR also. 3021d3381ebSRandy Dunlap * See AMD publication no. 24593, chapter 7.8.1, page 233 for more information. 3032ec1df41SThomas Gleixner */ 3042d2ee8deSPaul Jimenez static void set_fixed_range(int msr, bool *changed, unsigned int *msrwords) 3052ec1df41SThomas Gleixner { 3062ec1df41SThomas Gleixner unsigned lo, hi; 3072ec1df41SThomas Gleixner 3082ec1df41SThomas Gleixner rdmsr(msr, lo, hi); 3092ec1df41SThomas Gleixner 3102ec1df41SThomas Gleixner if (lo != msrwords[0] || hi != msrwords[1]) { 3112ec1df41SThomas Gleixner if (boot_cpu_data.x86_vendor == X86_VENDOR_AMD && 3122ec1df41SThomas Gleixner boot_cpu_data.x86 == 15 && 3132ec1df41SThomas Gleixner ((msrwords[0] | msrwords[1]) & K8_MTRR_RDMEM_WRMEM_MASK)) 3142ec1df41SThomas Gleixner k8_enable_fixed_iorrs(); 3152ec1df41SThomas Gleixner mtrr_wrmsr(msr, msrwords[0], msrwords[1]); 3162d2ee8deSPaul Jimenez *changed = true; 3172ec1df41SThomas Gleixner } 3182ec1df41SThomas Gleixner } 3192ec1df41SThomas Gleixner 3201d3381ebSRandy Dunlap /** 3211d3381ebSRandy Dunlap * generic_get_free_region - Get a free MTRR. 3221d3381ebSRandy Dunlap * @base: The starting (base) address of the region. 3231d3381ebSRandy Dunlap * @size: The size (in bytes) of the region. 3241d3381ebSRandy Dunlap * @replace_reg: mtrr index to be replaced; set to invalid value if none. 3251d3381ebSRandy Dunlap * 3261d3381ebSRandy Dunlap * Returns: The index of the region on success, else negative on error. 3272ec1df41SThomas Gleixner */ 3281d3381ebSRandy Dunlap int generic_get_free_region(unsigned long base, unsigned long size, int replace_reg) 3292ec1df41SThomas Gleixner { 3302ec1df41SThomas Gleixner int i, max; 3312ec1df41SThomas Gleixner mtrr_type ltype; 3322ec1df41SThomas Gleixner unsigned long lbase, lsize; 3332ec1df41SThomas Gleixner 3342ec1df41SThomas Gleixner max = num_var_ranges; 3352ec1df41SThomas Gleixner if (replace_reg >= 0 && replace_reg < max) 3362ec1df41SThomas Gleixner return replace_reg; 3372ec1df41SThomas Gleixner for (i = 0; i < max; ++i) { 3382ec1df41SThomas Gleixner mtrr_if->get(i, &lbase, &lsize, <ype); 3392ec1df41SThomas Gleixner if (lsize == 0) 3402ec1df41SThomas Gleixner return i; 3412ec1df41SThomas Gleixner } 3422ec1df41SThomas Gleixner return -ENOSPC; 3432ec1df41SThomas Gleixner } 3442ec1df41SThomas Gleixner 3452ec1df41SThomas Gleixner static void generic_get_mtrr(unsigned int reg, unsigned long *base, 3462ec1df41SThomas Gleixner unsigned long *size, mtrr_type *type) 3472ec1df41SThomas Gleixner { 3482ec1df41SThomas Gleixner unsigned int mask_lo, mask_hi, base_lo, base_hi; 3492ec1df41SThomas Gleixner 3502ec1df41SThomas Gleixner rdmsr(MTRRphysMask_MSR(reg), mask_lo, mask_hi); 3512ec1df41SThomas Gleixner if ((mask_lo & 0x800) == 0) { 3522ec1df41SThomas Gleixner /* Invalid (i.e. free) range */ 3532ec1df41SThomas Gleixner *base = 0; 3542ec1df41SThomas Gleixner *size = 0; 3552ec1df41SThomas Gleixner *type = 0; 3562ec1df41SThomas Gleixner return; 3572ec1df41SThomas Gleixner } 3582ec1df41SThomas Gleixner 3592ec1df41SThomas Gleixner rdmsr(MTRRphysBase_MSR(reg), base_lo, base_hi); 3602ec1df41SThomas Gleixner 3612ec1df41SThomas Gleixner /* Work out the shifted address mask. */ 3622ec1df41SThomas Gleixner mask_lo = size_or_mask | mask_hi << (32 - PAGE_SHIFT) 3632ec1df41SThomas Gleixner | mask_lo >> PAGE_SHIFT; 3642ec1df41SThomas Gleixner 3652ec1df41SThomas Gleixner /* This works correctly if size is a power of two, i.e. a 3662ec1df41SThomas Gleixner contiguous range. */ 3672ec1df41SThomas Gleixner *size = -mask_lo; 3682ec1df41SThomas Gleixner *base = base_hi << (32 - PAGE_SHIFT) | base_lo >> PAGE_SHIFT; 3692ec1df41SThomas Gleixner *type = base_lo & 0xff; 3702ec1df41SThomas Gleixner } 3712ec1df41SThomas Gleixner 3722ec1df41SThomas Gleixner /** 3731d3381ebSRandy Dunlap * set_fixed_ranges - checks & updates the fixed-range MTRRs if they differ from the saved set 3741d3381ebSRandy Dunlap * @frs: pointer to fixed-range MTRR values, saved by get_fixed_ranges() 3752ec1df41SThomas Gleixner */ 3762ec1df41SThomas Gleixner static int set_fixed_ranges(mtrr_type * frs) 3772ec1df41SThomas Gleixner { 3782ec1df41SThomas Gleixner unsigned long long *saved = (unsigned long long *) frs; 3792d2ee8deSPaul Jimenez bool changed = false; 3802ec1df41SThomas Gleixner int block=-1, range; 3812ec1df41SThomas Gleixner 3822ec1df41SThomas Gleixner while (fixed_range_blocks[++block].ranges) 3832ec1df41SThomas Gleixner for (range=0; range < fixed_range_blocks[block].ranges; range++) 3842ec1df41SThomas Gleixner set_fixed_range(fixed_range_blocks[block].base_msr + range, 3852ec1df41SThomas Gleixner &changed, (unsigned int *) saved++); 3862ec1df41SThomas Gleixner 3872ec1df41SThomas Gleixner return changed; 3882ec1df41SThomas Gleixner } 3892ec1df41SThomas Gleixner 3902ec1df41SThomas Gleixner /* Set the MSR pair relating to a var range. Returns TRUE if 3912ec1df41SThomas Gleixner changes are made */ 3922d2ee8deSPaul Jimenez static bool set_mtrr_var_ranges(unsigned int index, struct mtrr_var_range *vr) 3932ec1df41SThomas Gleixner { 3942ec1df41SThomas Gleixner unsigned int lo, hi; 3952d2ee8deSPaul Jimenez bool changed = false; 3962ec1df41SThomas Gleixner 3972ec1df41SThomas Gleixner rdmsr(MTRRphysBase_MSR(index), lo, hi); 3982ec1df41SThomas Gleixner if ((vr->base_lo & 0xfffff0ffUL) != (lo & 0xfffff0ffUL) 3992ec1df41SThomas Gleixner || (vr->base_hi & (size_and_mask >> (32 - PAGE_SHIFT))) != 4002ec1df41SThomas Gleixner (hi & (size_and_mask >> (32 - PAGE_SHIFT)))) { 4012ec1df41SThomas Gleixner mtrr_wrmsr(MTRRphysBase_MSR(index), vr->base_lo, vr->base_hi); 4022d2ee8deSPaul Jimenez changed = true; 4032ec1df41SThomas Gleixner } 4042ec1df41SThomas Gleixner 4052ec1df41SThomas Gleixner rdmsr(MTRRphysMask_MSR(index), lo, hi); 4062ec1df41SThomas Gleixner 4072ec1df41SThomas Gleixner if ((vr->mask_lo & 0xfffff800UL) != (lo & 0xfffff800UL) 4082ec1df41SThomas Gleixner || (vr->mask_hi & (size_and_mask >> (32 - PAGE_SHIFT))) != 4092ec1df41SThomas Gleixner (hi & (size_and_mask >> (32 - PAGE_SHIFT)))) { 4102ec1df41SThomas Gleixner mtrr_wrmsr(MTRRphysMask_MSR(index), vr->mask_lo, vr->mask_hi); 4112d2ee8deSPaul Jimenez changed = true; 4122ec1df41SThomas Gleixner } 4132ec1df41SThomas Gleixner return changed; 4142ec1df41SThomas Gleixner } 4152ec1df41SThomas Gleixner 4162ec1df41SThomas Gleixner static u32 deftype_lo, deftype_hi; 4172ec1df41SThomas Gleixner 4181d3381ebSRandy Dunlap /** 4191d3381ebSRandy Dunlap * set_mtrr_state - Set the MTRR state for this CPU. 4201d3381ebSRandy Dunlap * 4211d3381ebSRandy Dunlap * NOTE: The CPU must already be in a safe state for MTRR changes. 4221d3381ebSRandy Dunlap * RETURNS: 0 if no changes made, else a mask indicating what was changed. 4232ec1df41SThomas Gleixner */ 4241d3381ebSRandy Dunlap static unsigned long set_mtrr_state(void) 4252ec1df41SThomas Gleixner { 4262ec1df41SThomas Gleixner unsigned int i; 4272ec1df41SThomas Gleixner unsigned long change_mask = 0; 4282ec1df41SThomas Gleixner 4292ec1df41SThomas Gleixner for (i = 0; i < num_var_ranges; i++) 4302ec1df41SThomas Gleixner if (set_mtrr_var_ranges(i, &mtrr_state.var_ranges[i])) 4312ec1df41SThomas Gleixner change_mask |= MTRR_CHANGE_MASK_VARIABLE; 4322ec1df41SThomas Gleixner 4332ec1df41SThomas Gleixner if (mtrr_state.have_fixed && set_fixed_ranges(mtrr_state.fixed_ranges)) 4342ec1df41SThomas Gleixner change_mask |= MTRR_CHANGE_MASK_FIXED; 4352ec1df41SThomas Gleixner 4362ec1df41SThomas Gleixner /* Set_mtrr_restore restores the old value of MTRRdefType, 4372ec1df41SThomas Gleixner so to set it we fiddle with the saved value */ 4382ec1df41SThomas Gleixner if ((deftype_lo & 0xff) != mtrr_state.def_type 4392ec1df41SThomas Gleixner || ((deftype_lo & 0xc00) >> 10) != mtrr_state.enabled) { 4402ec1df41SThomas Gleixner deftype_lo = (deftype_lo & ~0xcff) | mtrr_state.def_type | (mtrr_state.enabled << 10); 4412ec1df41SThomas Gleixner change_mask |= MTRR_CHANGE_MASK_DEFTYPE; 4422ec1df41SThomas Gleixner } 4432ec1df41SThomas Gleixner 4442ec1df41SThomas Gleixner return change_mask; 4452ec1df41SThomas Gleixner } 4462ec1df41SThomas Gleixner 4472ec1df41SThomas Gleixner 4482ec1df41SThomas Gleixner static unsigned long cr4 = 0; 4492ec1df41SThomas Gleixner static DEFINE_SPINLOCK(set_atomicity_lock); 4502ec1df41SThomas Gleixner 4512ec1df41SThomas Gleixner /* 4522ec1df41SThomas Gleixner * Since we are disabling the cache don't allow any interrupts - they 4532ec1df41SThomas Gleixner * would run extremely slow and would only increase the pain. The caller must 4542ec1df41SThomas Gleixner * ensure that local interrupts are disabled and are reenabled after post_set() 4552ec1df41SThomas Gleixner * has been called. 4562ec1df41SThomas Gleixner */ 4572ec1df41SThomas Gleixner 4582ec1df41SThomas Gleixner static void prepare_set(void) __acquires(set_atomicity_lock) 4592ec1df41SThomas Gleixner { 4602ec1df41SThomas Gleixner unsigned long cr0; 4612ec1df41SThomas Gleixner 4622ec1df41SThomas Gleixner /* Note that this is not ideal, since the cache is only flushed/disabled 4632ec1df41SThomas Gleixner for this CPU while the MTRRs are changed, but changing this requires 4642ec1df41SThomas Gleixner more invasive changes to the way the kernel boots */ 4652ec1df41SThomas Gleixner 4662ec1df41SThomas Gleixner spin_lock(&set_atomicity_lock); 4672ec1df41SThomas Gleixner 4682ec1df41SThomas Gleixner /* Enter the no-fill (CD=1, NW=0) cache mode and flush caches. */ 4697ebad705SDave Jones cr0 = read_cr0() | X86_CR0_CD; 4702ec1df41SThomas Gleixner write_cr0(cr0); 4712ec1df41SThomas Gleixner wbinvd(); 4722ec1df41SThomas Gleixner 4732ec1df41SThomas Gleixner /* Save value of CR4 and clear Page Global Enable (bit 7) */ 4742ec1df41SThomas Gleixner if ( cpu_has_pge ) { 4752ec1df41SThomas Gleixner cr4 = read_cr4(); 4762ec1df41SThomas Gleixner write_cr4(cr4 & ~X86_CR4_PGE); 4772ec1df41SThomas Gleixner } 4782ec1df41SThomas Gleixner 4792ec1df41SThomas Gleixner /* Flush all TLBs via a mov %cr3, %reg; mov %reg, %cr3 */ 4802ec1df41SThomas Gleixner __flush_tlb(); 4812ec1df41SThomas Gleixner 4822ec1df41SThomas Gleixner /* Save MTRR state */ 4832ec1df41SThomas Gleixner rdmsr(MTRRdefType_MSR, deftype_lo, deftype_hi); 4842ec1df41SThomas Gleixner 4852ec1df41SThomas Gleixner /* Disable MTRRs, and set the default type to uncached */ 4862ec1df41SThomas Gleixner mtrr_wrmsr(MTRRdefType_MSR, deftype_lo & ~0xcff, deftype_hi); 4872ec1df41SThomas Gleixner } 4882ec1df41SThomas Gleixner 4892ec1df41SThomas Gleixner static void post_set(void) __releases(set_atomicity_lock) 4902ec1df41SThomas Gleixner { 4912ec1df41SThomas Gleixner /* Flush TLBs (no need to flush caches - they are disabled) */ 4922ec1df41SThomas Gleixner __flush_tlb(); 4932ec1df41SThomas Gleixner 4942ec1df41SThomas Gleixner /* Intel (P6) standard MTRRs */ 4952ec1df41SThomas Gleixner mtrr_wrmsr(MTRRdefType_MSR, deftype_lo, deftype_hi); 4962ec1df41SThomas Gleixner 4972ec1df41SThomas Gleixner /* Enable caches */ 4982ec1df41SThomas Gleixner write_cr0(read_cr0() & 0xbfffffff); 4992ec1df41SThomas Gleixner 5002ec1df41SThomas Gleixner /* Restore value of CR4 */ 5012ec1df41SThomas Gleixner if ( cpu_has_pge ) 5022ec1df41SThomas Gleixner write_cr4(cr4); 5032ec1df41SThomas Gleixner spin_unlock(&set_atomicity_lock); 5042ec1df41SThomas Gleixner } 5052ec1df41SThomas Gleixner 5062ec1df41SThomas Gleixner static void generic_set_all(void) 5072ec1df41SThomas Gleixner { 5082ec1df41SThomas Gleixner unsigned long mask, count; 5092ec1df41SThomas Gleixner unsigned long flags; 5102ec1df41SThomas Gleixner 5112ec1df41SThomas Gleixner local_irq_save(flags); 5122ec1df41SThomas Gleixner prepare_set(); 5132ec1df41SThomas Gleixner 5142ec1df41SThomas Gleixner /* Actually set the state */ 5152ec1df41SThomas Gleixner mask = set_mtrr_state(); 5162ec1df41SThomas Gleixner 517*2e5d9c85Svenkatesh.pallipadi@intel.com /* also set PAT */ 518*2e5d9c85Svenkatesh.pallipadi@intel.com pat_init(); 519*2e5d9c85Svenkatesh.pallipadi@intel.com 5202ec1df41SThomas Gleixner post_set(); 5212ec1df41SThomas Gleixner local_irq_restore(flags); 5222ec1df41SThomas Gleixner 5232ec1df41SThomas Gleixner /* Use the atomic bitops to update the global mask */ 5242ec1df41SThomas Gleixner for (count = 0; count < sizeof mask * 8; ++count) { 5252ec1df41SThomas Gleixner if (mask & 0x01) 5262ec1df41SThomas Gleixner set_bit(count, &smp_changes_mask); 5272ec1df41SThomas Gleixner mask >>= 1; 5282ec1df41SThomas Gleixner } 5292ec1df41SThomas Gleixner 5302ec1df41SThomas Gleixner } 5312ec1df41SThomas Gleixner 5322ec1df41SThomas Gleixner static void generic_set_mtrr(unsigned int reg, unsigned long base, 5332ec1df41SThomas Gleixner unsigned long size, mtrr_type type) 5342ec1df41SThomas Gleixner /* [SUMMARY] Set variable MTRR register on the local CPU. 5352ec1df41SThomas Gleixner <reg> The register to set. 5362ec1df41SThomas Gleixner <base> The base address of the region. 5372ec1df41SThomas Gleixner <size> The size of the region. If this is 0 the region is disabled. 5382ec1df41SThomas Gleixner <type> The type of the region. 5392ec1df41SThomas Gleixner [RETURNS] Nothing. 5402ec1df41SThomas Gleixner */ 5412ec1df41SThomas Gleixner { 5422ec1df41SThomas Gleixner unsigned long flags; 5432ec1df41SThomas Gleixner struct mtrr_var_range *vr; 5442ec1df41SThomas Gleixner 5452ec1df41SThomas Gleixner vr = &mtrr_state.var_ranges[reg]; 5462ec1df41SThomas Gleixner 5472ec1df41SThomas Gleixner local_irq_save(flags); 5482ec1df41SThomas Gleixner prepare_set(); 5492ec1df41SThomas Gleixner 5502ec1df41SThomas Gleixner if (size == 0) { 5512ec1df41SThomas Gleixner /* The invalid bit is kept in the mask, so we simply clear the 5522ec1df41SThomas Gleixner relevant mask register to disable a range. */ 5532ec1df41SThomas Gleixner mtrr_wrmsr(MTRRphysMask_MSR(reg), 0, 0); 5542ec1df41SThomas Gleixner memset(vr, 0, sizeof(struct mtrr_var_range)); 5552ec1df41SThomas Gleixner } else { 5562ec1df41SThomas Gleixner vr->base_lo = base << PAGE_SHIFT | type; 5572ec1df41SThomas Gleixner vr->base_hi = (base & size_and_mask) >> (32 - PAGE_SHIFT); 5582ec1df41SThomas Gleixner vr->mask_lo = -size << PAGE_SHIFT | 0x800; 5592ec1df41SThomas Gleixner vr->mask_hi = (-size & size_and_mask) >> (32 - PAGE_SHIFT); 5602ec1df41SThomas Gleixner 5612ec1df41SThomas Gleixner mtrr_wrmsr(MTRRphysBase_MSR(reg), vr->base_lo, vr->base_hi); 5622ec1df41SThomas Gleixner mtrr_wrmsr(MTRRphysMask_MSR(reg), vr->mask_lo, vr->mask_hi); 5632ec1df41SThomas Gleixner } 5642ec1df41SThomas Gleixner 5652ec1df41SThomas Gleixner post_set(); 5662ec1df41SThomas Gleixner local_irq_restore(flags); 5672ec1df41SThomas Gleixner } 5682ec1df41SThomas Gleixner 5692ec1df41SThomas Gleixner int generic_validate_add_page(unsigned long base, unsigned long size, unsigned int type) 5702ec1df41SThomas Gleixner { 5712ec1df41SThomas Gleixner unsigned long lbase, last; 5722ec1df41SThomas Gleixner 5732ec1df41SThomas Gleixner /* For Intel PPro stepping <= 7, must be 4 MiB aligned 5742ec1df41SThomas Gleixner and not touch 0x70000000->0x7003FFFF */ 5752ec1df41SThomas Gleixner if (is_cpu(INTEL) && boot_cpu_data.x86 == 6 && 5762ec1df41SThomas Gleixner boot_cpu_data.x86_model == 1 && 5772ec1df41SThomas Gleixner boot_cpu_data.x86_mask <= 7) { 5782ec1df41SThomas Gleixner if (base & ((1 << (22 - PAGE_SHIFT)) - 1)) { 5792ec1df41SThomas Gleixner printk(KERN_WARNING "mtrr: base(0x%lx000) is not 4 MiB aligned\n", base); 5802ec1df41SThomas Gleixner return -EINVAL; 5812ec1df41SThomas Gleixner } 5822ec1df41SThomas Gleixner if (!(base + size < 0x70000 || base > 0x7003F) && 5832ec1df41SThomas Gleixner (type == MTRR_TYPE_WRCOMB 5842ec1df41SThomas Gleixner || type == MTRR_TYPE_WRBACK)) { 5852ec1df41SThomas Gleixner printk(KERN_WARNING "mtrr: writable mtrr between 0x70000000 and 0x7003FFFF may hang the CPU.\n"); 5862ec1df41SThomas Gleixner return -EINVAL; 5872ec1df41SThomas Gleixner } 5882ec1df41SThomas Gleixner } 5892ec1df41SThomas Gleixner 5902ec1df41SThomas Gleixner /* Check upper bits of base and last are equal and lower bits are 0 5912ec1df41SThomas Gleixner for base and 1 for last */ 5922ec1df41SThomas Gleixner last = base + size - 1; 5932ec1df41SThomas Gleixner for (lbase = base; !(lbase & 1) && (last & 1); 5942ec1df41SThomas Gleixner lbase = lbase >> 1, last = last >> 1) ; 5952ec1df41SThomas Gleixner if (lbase != last) { 5962ec1df41SThomas Gleixner printk(KERN_WARNING "mtrr: base(0x%lx000) is not aligned on a size(0x%lx000) boundary\n", 5972ec1df41SThomas Gleixner base, size); 5982ec1df41SThomas Gleixner return -EINVAL; 5992ec1df41SThomas Gleixner } 6002ec1df41SThomas Gleixner return 0; 6012ec1df41SThomas Gleixner } 6022ec1df41SThomas Gleixner 6032ec1df41SThomas Gleixner 6042ec1df41SThomas Gleixner static int generic_have_wrcomb(void) 6052ec1df41SThomas Gleixner { 6062ec1df41SThomas Gleixner unsigned long config, dummy; 6072ec1df41SThomas Gleixner rdmsr(MTRRcap_MSR, config, dummy); 6082ec1df41SThomas Gleixner return (config & (1 << 10)); 6092ec1df41SThomas Gleixner } 6102ec1df41SThomas Gleixner 6112ec1df41SThomas Gleixner int positive_have_wrcomb(void) 6122ec1df41SThomas Gleixner { 6132ec1df41SThomas Gleixner return 1; 6142ec1df41SThomas Gleixner } 6152ec1df41SThomas Gleixner 6162ec1df41SThomas Gleixner /* generic structure... 6172ec1df41SThomas Gleixner */ 6182ec1df41SThomas Gleixner struct mtrr_ops generic_mtrr_ops = { 6192ec1df41SThomas Gleixner .use_intel_if = 1, 6202ec1df41SThomas Gleixner .set_all = generic_set_all, 6212ec1df41SThomas Gleixner .get = generic_get_mtrr, 6222ec1df41SThomas Gleixner .get_free_region = generic_get_free_region, 6232ec1df41SThomas Gleixner .set = generic_set_mtrr, 6242ec1df41SThomas Gleixner .validate_add_page = generic_validate_add_page, 6252ec1df41SThomas Gleixner .have_wrcomb = generic_have_wrcomb, 6262ec1df41SThomas Gleixner }; 627