1 /* 2 * CDDL HEADER START 3 * 4 * The contents of this file are subject to the terms of the 5 * Common Development and Distribution License, Version 1.0 only 6 * (the "License"). You may not use this file except in compliance 7 * with the License. 8 * 9 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE 10 * or http://www.opensolaris.org/os/licensing. 11 * See the License for the specific language governing permissions 12 * and limitations under the License. 13 * 14 * When distributing Covered Code, include this CDDL HEADER in each 15 * file and include the License file at usr/src/OPENSOLARIS.LICENSE. 16 * If applicable, add the following below this CDDL HEADER, with the 17 * fields enclosed by brackets "[]" replaced with your own identifying 18 * information: Portions Copyright [yyyy] [name of copyright owner] 19 * 20 * CDDL HEADER END 21 */ 22 23 /* 24 * Copyright 2006 Sun Microsystems, Inc. All rights reserved. 25 * Use is subject to license terms. 26 */ 27 28 #pragma ident "%Z%%M% %I% %E% SMI" 29 30 #include <sys/types.h> 31 #include <sys/chip.h> 32 #include <sys/cmn_err.h> 33 #include <sys/sysmacros.h> 34 #include <sys/fm/protocol.h> 35 36 #include "ao.h" 37 38 /* 39 * AMD Opteron CPU Subroutines 40 * 41 * The following three tunables are used to determine the scrubbing rates for 42 * the D$, L2$, and DRAM hardware scrubbers. The values range from 0x00-0x16 43 * as described in BKDG 3.6.6 Scrub Control Register. A value of zero disables 44 * the scrubber. Values above zero indicate rates in descending order. 45 * 46 * The current default values are used on several Sun systems. In the future 47 * this code should assign values dynamically based on memory sizing. If you 48 * tune these values manually be aware of the following architectural issue: 49 * At present, Opteron can only survive certain kinds of multi-bit errors if 50 * they are detected by the scrubbers. Therefore in general we want these 51 * values tuned as high as possible without impacting workload performance. 52 */ 53 uint32_t ao_scrub_rate_dcache = 8; /* 64B every 5.12 us */ 54 uint32_t ao_scrub_rate_l2cache = 9; /* 64B every 10.2 us */ 55 uint32_t ao_scrub_rate_dram = 0xd; /* 64B every 163.8 us */ 56 57 uint32_t ao_scrub_system; /* debug stash for system's value */ 58 uint32_t ao_scrub_bios; /* debug stash for bios's value */ 59 uint32_t ao_scrub_lo; /* debug stash for system low addr */ 60 uint32_t ao_scrub_hi; /* debug stash for system high addr */ 61 62 enum { 63 AO_SCRUB_DEFAULT, /* retain system default values */ 64 AO_SCRUB_FIXED, /* assign ao_scrub_rate_* values */ 65 AO_SCRUB_MAX /* assign max of system and tunables */ 66 } ao_scrub_policy = AO_SCRUB_MAX; 67 68 nvlist_t * 69 ao_fmri_create(ao_data_t *ao, nv_alloc_t *nva) 70 { 71 nvlist_t *nvl = fm_nvlist_create(nva); 72 73 fm_fmri_hc_set(nvl, FM_HC_SCHEME_VERSION, NULL, NULL, 3, 74 "motherboard", 0, 75 "chip", ao->ao_cpu->cpu_chip->chip_id, 76 "cpu", chip_plat_get_clogid(ao->ao_cpu)); 77 78 return (nvl); 79 } 80 81 /* 82 * Return the maximum scrubbing rate between r1 and r2, where r2 is extracted 83 * from the specified 'cfg' register value using 'mask' and 'shift'. If a 84 * value is zero, scrubbing is off so return the opposite value. Otherwise 85 * the maximum rate is the smallest non-zero value of the two values. 86 */ 87 static uint32_t 88 ao_scrubber_max(uint32_t r1, uint32_t cfg, uint32_t mask, uint32_t shift) 89 { 90 uint32_t r2 = (cfg & mask) >> shift; 91 92 if (r1 != 0 && r2 != 0) 93 return (MIN(r1, r2)); 94 95 return (r1 ? r1 : r2); 96 } 97 98 /* 99 * Enable the chip-specific hardware scrubbers for the D$, L2$, and DRAM, and 100 * return a boolean value indicating if we enabled the DRAM scrubber. We set 101 * the scrubber rate based on a set of tunables defined at the top of the file. 102 * The 'base' parameter is the DRAM Base Address for this chip and is used to 103 * determine where the scrubber starts. The 'ilen' value is the IntvlEn field 104 * from the DRAM configuration indicating the node-interleaving configuration. 105 */ 106 int 107 ao_scrubber_enable(void *data, uint64_t base, uint64_t ilen) 108 { 109 ao_data_t *ao = data; 110 chipid_t chipid = chip_plat_get_chipid(ao->ao_cpu); 111 uint32_t scrubctl, lo, hi; 112 int rv = 1; 113 114 /* 115 * Read the initial scrubber configuration and save it for debugging. 116 * If ao_scrub_policy is DEFAULT, return immediately. Otherwise we 117 * disable scrubbing activity while we fiddle with the configuration. 118 */ 119 scrubctl = ao_pcicfg_read(chipid, AMD_NB_FUNC, AMD_NB_REG_SCRUBCTL); 120 cas32(&ao_scrub_bios, 0, scrubctl); 121 122 if (ao_scrub_policy == AO_SCRUB_DEFAULT) 123 return ((scrubctl & AMD_NB_SCRUBCTL_DRAM_MASK) != 0); 124 125 scrubctl &= ~AMD_NB_SCRUBCTL_DRAM_MASK; 126 scrubctl &= ~AMD_NB_SCRUBCTL_L2_MASK; 127 scrubctl &= ~AMD_NB_SCRUBCTL_DC_MASK; 128 129 ao_pcicfg_write(chipid, AMD_NB_FUNC, AMD_NB_REG_SCRUBCTL, scrubctl); 130 131 /* 132 * Read the DRAM Scrub Address Low and High registers, clear their 133 * address fields, enable sequential-redirect mode, and update the 134 * address fields using the specified DRAM Base Address. 135 */ 136 lo = ao_pcicfg_read(chipid, AMD_NB_FUNC, AMD_NB_REG_SCRUBADDR_LO); 137 hi = ao_pcicfg_read(chipid, AMD_NB_FUNC, AMD_NB_REG_SCRUBADDR_HI); 138 139 lo &= ~AMD_NB_SCRUBADDR_LO_MASK; 140 hi &= ~AMD_NB_SCRUBADDR_HI_MASK; 141 142 lo |= AMD_NB_SCRUBADDR_MKLO(base) | AMD_NB_SCRUBADDR_LO_SCRUBREDIREN; 143 hi |= AMD_NB_SCRUBADDR_MKHI(base); 144 145 ao_scrub_lo = lo; 146 ao_scrub_hi = hi; 147 148 ao_pcicfg_write(chipid, AMD_NB_FUNC, AMD_NB_REG_SCRUBADDR_LO, lo); 149 ao_pcicfg_write(chipid, AMD_NB_FUNC, AMD_NB_REG_SCRUBADDR_HI, hi); 150 151 if (ao_scrub_rate_dcache > AMD_NB_SCRUBCTL_RATE_MAX) { 152 cmn_err(CE_WARN, "ao_scrub_rate_dcache is too large; " 153 "resetting to 0x%x\n", AMD_NB_SCRUBCTL_RATE_MAX); 154 ao_scrub_rate_dcache = AMD_NB_SCRUBCTL_RATE_MAX; 155 } 156 157 if (ao_scrub_rate_l2cache > AMD_NB_SCRUBCTL_RATE_MAX) { 158 cmn_err(CE_WARN, "ao_scrub_rate_l2cache is too large; " 159 "resetting to 0x%x\n", AMD_NB_SCRUBCTL_RATE_MAX); 160 ao_scrub_rate_l2cache = AMD_NB_SCRUBCTL_RATE_MAX; 161 } 162 163 if (ao_scrub_rate_dram > AMD_NB_SCRUBCTL_RATE_MAX) { 164 cmn_err(CE_WARN, "ao_scrub_rate_dram is too large; " 165 "resetting to 0x%x\n", AMD_NB_SCRUBCTL_RATE_MAX); 166 ao_scrub_rate_dram = AMD_NB_SCRUBCTL_RATE_MAX; 167 } 168 169 if (ao_scrub_policy == AO_SCRUB_MAX) { 170 ao_scrub_rate_dcache = 171 ao_scrubber_max(ao_scrub_rate_dcache, ao_scrub_bios, 172 AMD_NB_SCRUBCTL_DC_MASK, AMD_NB_SCRUBCTL_DC_SHIFT); 173 174 ao_scrub_rate_l2cache = 175 ao_scrubber_max(ao_scrub_rate_l2cache, ao_scrub_bios, 176 AMD_NB_SCRUBCTL_L2_MASK, AMD_NB_SCRUBCTL_L2_SHIFT); 177 178 ao_scrub_rate_dram = 179 ao_scrubber_max(ao_scrub_rate_dram, ao_scrub_bios, 180 AMD_NB_SCRUBCTL_DRAM_MASK, AMD_NB_SCRUBCTL_DRAM_SHIFT); 181 } 182 183 #ifdef OPTERON_ERRATUM_101 184 /* 185 * If the DRAM Base Address register's IntlvEn field indicates that 186 * node interleaving is enabled, we must disable the DRAM scrubber 187 * and return zero to indicate that Solaris should use s/w instead. 188 */ 189 if (ilen != 0) { 190 cmn_err(CE_CONT, "?Opteron DRAM scrubber disabled because " 191 "DRAM memory is node-interleaved"); 192 ao_scrub_rate_dram = 0; 193 rv = 0; 194 } 195 #endif 196 scrubctl |= AMD_NB_MKSCRUBCTL(ao_scrub_rate_dcache, 197 ao_scrub_rate_l2cache, ao_scrub_rate_dram); 198 199 ao_scrub_system = scrubctl; 200 ao_pcicfg_write(chipid, AMD_NB_FUNC, AMD_NB_REG_SCRUBCTL, scrubctl); 201 202 return (rv); 203 } 204