ao_cpu.c revision 3434:5142e1d7d0bc
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 */
53uint32_t ao_scrub_rate_dcache = 8;	/* 64B every 5.12 us */
54uint32_t ao_scrub_rate_l2cache = 9;	/* 64B every 10.2 us */
55uint32_t ao_scrub_rate_dram = 0xd;	/* 64B every 163.8 us */
56
57uint32_t ao_scrub_system;		/* debug stash for system's value */
58uint32_t ao_scrub_bios;			/* debug stash for bios's value */
59uint32_t ao_scrub_lo;			/* debug stash for system low addr */
60uint32_t ao_scrub_hi;			/* debug stash for system high addr */
61
62enum {
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
68nvlist_t *
69ao_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 */
87static uint32_t
88ao_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 */
112int
113ao_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