xref: /illumos-gate/usr/src/uts/i86pc/cpu/amd_opteron/ao_cpu.c (revision ef8846857fcf954444cdc77e72249afef48377d2)
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 (the "License").
6  * You may not use this file except in compliance with the License.
7  *
8  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
9  * or http://www.opensolaris.org/os/licensing.
10  * See the License for the specific language governing permissions
11  * and limitations under the License.
12  *
13  * When distributing Covered Code, include this CDDL HEADER in each
14  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
15  * If applicable, add the following below this CDDL HEADER, with the
16  * fields enclosed by brackets "[]" replaced with your own identifying
17  * information: Portions Copyright [yyyy] [name of copyright owner]
18  *
19  * CDDL HEADER END
20  */
21 
22 /*
23  * Copyright 2007 Sun Microsystems, Inc.  All rights reserved.
24  * Use is subject to license terms.
25  */
26 
27 #pragma ident	"%Z%%M%	%I%	%E% SMI"
28 
29 #include <sys/types.h>
30 #include <sys/pghw.h>
31 #include <sys/cmn_err.h>
32 #include <sys/sysmacros.h>
33 #include <sys/fm/protocol.h>
34 #include <sys/x86_archext.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_BIOSDEFAULT,		/* 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", pg_plat_hw_instance_id(ao->ao_cpu, PGHW_CHIP),
76 	    "cpu", cpuid_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  * Where chip-select sparing is available the DRAM scrub address registers
107  * must not be modified while a swap is in-progress.  This can't happen
108  * because we (the amd cpu module) take control of the online spare
109  * away from the BIOS when we perform NB configuration and we complete
110  * that operation before the memory controller driver loads.
111  */
112 int
113 ao_scrubber_enable(void *data, uint64_t base, uint64_t ilen, int csdiscontig)
114 {
115 	ao_data_t *ao = data;
116 	chipid_t chipid = pg_plat_hw_instance_id(ao->ao_cpu, PGHW_CHIP);
117 	uint32_t rev = cpuid_getchiprev(ao->ao_cpu);
118 	uint32_t scrubctl, lo, hi;
119 	int rv = 1;
120 
121 	/*
122 	 * Read the initial scrubber configuration and save it for debugging.
123 	 * If ao_scrub_policy is DEFAULT, return immediately.  Otherwise we
124 	 * disable scrubbing activity while we fiddle with the configuration.
125 	 */
126 	scrubctl = ao_pcicfg_read(chipid, AMD_NB_FUNC, AMD_NB_REG_SCRUBCTL);
127 	cas32(&ao_scrub_bios, 0, scrubctl);
128 
129 	if (ao_scrub_policy == AO_SCRUB_BIOSDEFAULT)
130 		return ((scrubctl & AMD_NB_SCRUBCTL_DRAM_MASK) != 0);
131 
132 	scrubctl &= ~AMD_NB_SCRUBCTL_DRAM_MASK;
133 	scrubctl &= ~AMD_NB_SCRUBCTL_L2_MASK;
134 	scrubctl &= ~AMD_NB_SCRUBCTL_DC_MASK;
135 
136 	ao_pcicfg_write(chipid, AMD_NB_FUNC, AMD_NB_REG_SCRUBCTL, scrubctl);
137 
138 	/*
139 	 * Read the DRAM Scrub Address Low and High registers, clear their
140 	 * address fields, enable sequential-redirect mode, and update the
141 	 * address fields using the specified DRAM Base Address.
142 	 */
143 	lo = ao_pcicfg_read(chipid, AMD_NB_FUNC, AMD_NB_REG_SCRUBADDR_LO);
144 	hi = ao_pcicfg_read(chipid, AMD_NB_FUNC, AMD_NB_REG_SCRUBADDR_HI);
145 
146 	lo &= ~AMD_NB_SCRUBADDR_LO_MASK;
147 	hi &= ~AMD_NB_SCRUBADDR_HI_MASK;
148 
149 	lo |= AMD_NB_SCRUBADDR_MKLO(base) | AMD_NB_SCRUBADDR_LO_SCRUBREDIREN;
150 	hi |= AMD_NB_SCRUBADDR_MKHI(base);
151 
152 	ao_scrub_lo = lo;
153 	ao_scrub_hi = hi;
154 
155 	ao_pcicfg_write(chipid, AMD_NB_FUNC, AMD_NB_REG_SCRUBADDR_LO, lo);
156 	ao_pcicfg_write(chipid, AMD_NB_FUNC, AMD_NB_REG_SCRUBADDR_HI, hi);
157 
158 	if (ao_scrub_rate_dcache > AMD_NB_SCRUBCTL_RATE_MAX) {
159 		cmn_err(CE_WARN, "ao_scrub_rate_dcache is too large; "
160 		    "resetting to 0x%x\n", AMD_NB_SCRUBCTL_RATE_MAX);
161 		ao_scrub_rate_dcache = AMD_NB_SCRUBCTL_RATE_MAX;
162 	}
163 
164 	if (ao_scrub_rate_l2cache > AMD_NB_SCRUBCTL_RATE_MAX) {
165 		cmn_err(CE_WARN, "ao_scrub_rate_l2cache is too large; "
166 		    "resetting to 0x%x\n", AMD_NB_SCRUBCTL_RATE_MAX);
167 		ao_scrub_rate_l2cache = AMD_NB_SCRUBCTL_RATE_MAX;
168 	}
169 
170 	if (ao_scrub_rate_dram > AMD_NB_SCRUBCTL_RATE_MAX) {
171 		cmn_err(CE_WARN, "ao_scrub_rate_dram is too large; "
172 		    "resetting to 0x%x\n", AMD_NB_SCRUBCTL_RATE_MAX);
173 		ao_scrub_rate_dram = AMD_NB_SCRUBCTL_RATE_MAX;
174 	}
175 
176 	switch (ao_scrub_policy) {
177 	case AO_SCRUB_FIXED:
178 		/* Use the system values checked above */
179 		break;
180 
181 	default:
182 		cmn_err(CE_WARN, "Unknown ao_scrub_policy value %d - "
183 		    "using default policy of AO_SCRUB_MAX", ao_scrub_policy);
184 		/*FALLTHRU*/
185 
186 	case AO_SCRUB_MAX:
187 		ao_scrub_rate_dcache =
188 		    ao_scrubber_max(ao_scrub_rate_dcache, ao_scrub_bios,
189 		    AMD_NB_SCRUBCTL_DC_MASK, AMD_NB_SCRUBCTL_DC_SHIFT);
190 
191 		ao_scrub_rate_l2cache =
192 		    ao_scrubber_max(ao_scrub_rate_l2cache, ao_scrub_bios,
193 		    AMD_NB_SCRUBCTL_L2_MASK, AMD_NB_SCRUBCTL_L2_SHIFT);
194 
195 		ao_scrub_rate_dram =
196 		    ao_scrubber_max(ao_scrub_rate_dram, ao_scrub_bios,
197 		    AMD_NB_SCRUBCTL_DRAM_MASK, AMD_NB_SCRUBCTL_DRAM_SHIFT);
198 		break;
199 	}
200 
201 #ifdef	OPTERON_ERRATUM_99
202 	/*
203 	 * This erratum applies on revisions D and earlier.
204 	 *
205 	 * Do not enable the dram scrubber is the chip-select ranges
206 	 * for the node are not contiguous.
207 	 */
208 	if (csdiscontig && !X86_CHIPREV_ATLEAST(rev, X86_CHIPREV_AMD_F_REV_E)) {
209 		cmn_err(CE_CONT, "?Opteron DRAM scrubber disabled on revision "
210 		    "%s chip because DRAM hole is present on this node",
211 		    cpuid_getchiprevstr(ao->ao_cpu));
212 		ao_scrub_rate_dram = 0;
213 		rv = 0;
214 	}
215 #endif
216 
217 #ifdef OPTERON_ERRATUM_101
218 	/*
219 	 * This erratum applies on revisions D and earlier.
220 	 *
221 	 * If the DRAM Base Address register's IntlvEn field indicates that
222 	 * node interleaving is enabled, we must disable the DRAM scrubber
223 	 * and return zero to indicate that Solaris should use s/w instead.
224 	 */
225 	if (ilen != 0 && ao_scrub_rate_dram != 0 &&
226 	    !X86_CHIPREV_ATLEAST(rev, X86_CHIPREV_AMD_F_REV_E)) {
227 		cmn_err(CE_CONT, "?Opteron DRAM scrubber disabled on revision "
228 		    "%s chip because DRAM memory is node-interleaved",
229 		    cpuid_getchiprevstr(ao->ao_cpu));
230 		ao_scrub_rate_dram = 0;
231 		rv = 0;
232 	}
233 #endif
234 	scrubctl |= AMD_NB_MKSCRUBCTL(ao_scrub_rate_dcache,
235 	    ao_scrub_rate_l2cache, ao_scrub_rate_dram);
236 
237 	ao_scrub_system = scrubctl;
238 	ao_pcicfg_write(chipid, AMD_NB_FUNC, AMD_NB_REG_SCRUBCTL, scrubctl);
239 
240 	return (rv);
241 }
242