xref: /illumos-gate/usr/src/uts/i86pc/cpu/amd_opteron/ao_cpu.c (revision 1a220b56b93ff1dc80855691548503117af4cc10)
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