1 |
/*- |
2 |
* SPDX-License-Identifier: BSD-2-Clause-FreeBSD |
3 |
* |
4 |
* Authors: Ravi Pokala (rpokala@freebsd.org), Andriy Gapon (avg@FreeBSD.org) |
5 |
* |
6 |
* Copyright (c) 2016 Andriy Gapon <avg@FreeBSD.org> |
7 |
* Copyright (c) 2018 Panasas |
8 |
* All rights reserved. |
9 |
* |
10 |
* Redistribution and use in source and binary forms, with or without |
11 |
* modification, are permitted provided that the following conditions |
12 |
* are met: |
13 |
* 1. Redistributions of source code must retain the above copyright |
14 |
* notice, this list of conditions and the following disclaimer. |
15 |
* 2. Redistributions in binary form must reproduce the above copyright |
16 |
* notice, this list of conditions and the following disclaimer in the |
17 |
* documentation and/or other materials provided with the distribution. |
18 |
* |
19 |
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND |
20 |
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE |
21 |
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE |
22 |
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE |
23 |
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL |
24 |
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS |
25 |
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) |
26 |
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT |
27 |
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY |
28 |
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF |
29 |
* SUCH DAMAGE. |
30 |
* |
31 |
* $FreeBSD: stable/10/sys/dev/jedec_dimm/jedec_dimm.c 345838 2019-04-03 06:37:25Z rpokala $ |
32 |
* $MidnightBSD$ |
33 |
*/ |
34 |
|
35 |
/* |
36 |
* This driver is a super-set of jedec_ts(4), and most of the code for reading |
37 |
* and reporting the temperature is either based on that driver, or copied |
38 |
* from it verbatim. |
39 |
*/ |
40 |
|
41 |
#include <sys/param.h> |
42 |
#include <sys/kernel.h> |
43 |
#include <sys/bus.h> |
44 |
#include <sys/endian.h> |
45 |
#include <sys/malloc.h> |
46 |
#include <sys/module.h> |
47 |
#include <sys/sysctl.h> |
48 |
#include <sys/systm.h> |
49 |
|
50 |
#include <dev/jedec_dimm/jedec_dimm.h> |
51 |
#include <dev/smbus/smbconf.h> |
52 |
#include <dev/smbus/smbus.h> |
53 |
|
54 |
#include "smbus_if.h" |
55 |
|
56 |
struct jedec_dimm_softc { |
57 |
device_t dev; |
58 |
device_t smbus; |
59 |
uint8_t spd_addr; /* SMBus address of the SPD EEPROM. */ |
60 |
uint8_t tsod_addr; /* Address of the Thermal Sensor On DIMM */ |
61 |
uint32_t capacity_mb; |
62 |
char type_str[5]; |
63 |
char part_str[21]; /* 18 (DDR3) or 20 (DDR4) chars, plus terminator */ |
64 |
char serial_str[9]; /* 4 bytes = 8 nybble characters, plus terminator */ |
65 |
char *slotid_str; /* Optional DIMM slot identifier (silkscreen) */ |
66 |
}; |
67 |
|
68 |
/* General Thermal Sensor on DIMM (TSOD) identification notes. |
69 |
* |
70 |
* The JEDEC TSE2004av specification defines the device ID that all compliant |
71 |
* devices should use, but very few do in practice. Maybe that's because the |
72 |
* earlier TSE2002av specification was rather vague about that. |
73 |
* Rare examples are IDT TSE2004GB2B0 and Atmel AT30TSE004A, not sure if |
74 |
* they are TSE2004av compliant by design or by accident. |
75 |
* Also, the specification mandates that PCI SIG manufacturer IDs are to be |
76 |
* used, but in practice the JEDEC manufacturer IDs are often used. |
77 |
*/ |
78 |
const struct jedec_dimm_tsod_dev { |
79 |
uint16_t vendor_id; |
80 |
uint8_t device_id; |
81 |
const char *description; |
82 |
} known_tsod_devices[] = { |
83 |
/* Analog Devices ADT7408. |
84 |
* http://www.analog.com/media/en/technical-documentation/data-sheets/ADT7408.pdf |
85 |
*/ |
86 |
{ 0x11d4, 0x08, "Analog Devices TSOD" }, |
87 |
|
88 |
/* Atmel AT30TSE002B, AT30TSE004A. |
89 |
* http://www.atmel.com/images/doc8711.pdf |
90 |
* http://www.atmel.com/images/atmel-8868-dts-at30tse004a-datasheet.pdf |
91 |
* Note how one chip uses the JEDEC Manufacturer ID while the other |
92 |
* uses the PCI SIG one. |
93 |
*/ |
94 |
{ 0x001f, 0x82, "Atmel TSOD" }, |
95 |
{ 0x1114, 0x22, "Atmel TSOD" }, |
96 |
|
97 |
/* Integrated Device Technology (IDT) TS3000B3A, TSE2002B3C, |
98 |
* TSE2004GB2B0 chips and their variants. |
99 |
* http://www.idt.com/sites/default/files/documents/IDT_TSE2002B3C_DST_20100512_120303152056.pdf |
100 |
* http://www.idt.com/sites/default/files/documents/IDT_TS3000B3A_DST_20101129_120303152013.pdf |
101 |
* https://www.idt.com/document/dst/tse2004gb2b0-datasheet |
102 |
*/ |
103 |
{ 0x00b3, 0x29, "IDT TSOD" }, |
104 |
{ 0x00b3, 0x22, "IDT TSOD" }, |
105 |
|
106 |
/* Maxim Integrated MAX6604. |
107 |
* Different document revisions specify different Device IDs. |
108 |
* Document 19-3837; Rev 0; 10/05 has 0x3e00 while |
109 |
* 19-3837; Rev 3; 10/11 has 0x5400. |
110 |
* http://datasheets.maximintegrated.com/en/ds/MAX6604.pdf |
111 |
*/ |
112 |
{ 0x004d, 0x3e, "Maxim Integrated TSOD" }, |
113 |
{ 0x004d, 0x54, "Maxim Integrated TSOD" }, |
114 |
|
115 |
/* Microchip Technology MCP9805, MCP9843, MCP98242, MCP98243 |
116 |
* and their variants. |
117 |
* http://ww1.microchip.com/downloads/en/DeviceDoc/21977b.pdf |
118 |
* Microchip Technology EMC1501. |
119 |
* http://ww1.microchip.com/downloads/en/DeviceDoc/00001605A.pdf |
120 |
*/ |
121 |
{ 0x0054, 0x00, "Microchip TSOD" }, |
122 |
{ 0x0054, 0x20, "Microchip TSOD" }, |
123 |
{ 0x0054, 0x21, "Microchip TSOD" }, |
124 |
{ 0x1055, 0x08, "Microchip TSOD" }, |
125 |
|
126 |
/* NXP Semiconductors SE97 and SE98. |
127 |
* http://www.nxp.com/docs/en/data-sheet/SE97B.pdf |
128 |
*/ |
129 |
{ 0x1131, 0xa1, "NXP TSOD" }, |
130 |
{ 0x1131, 0xa2, "NXP TSOD" }, |
131 |
|
132 |
/* ON Semiconductor CAT34TS02 revisions B and C, CAT6095 and compatible. |
133 |
* https://www.onsemi.com/pub/Collateral/CAT34TS02-D.PDF |
134 |
* http://www.onsemi.com/pub/Collateral/CAT6095-D.PDF |
135 |
*/ |
136 |
{ 0x1b09, 0x08, "ON Semiconductor TSOD" }, |
137 |
{ 0x1b09, 0x0a, "ON Semiconductor TSOD" }, |
138 |
|
139 |
/* ST[Microelectronics] STTS424E02, STTS2002 and others. |
140 |
* http://www.st.com/resource/en/datasheet/cd00157558.pdf |
141 |
* http://www.st.com/resource/en/datasheet/stts2002.pdf |
142 |
*/ |
143 |
{ 0x104a, 0x00, "ST Microelectronics TSOD" }, |
144 |
{ 0x104a, 0x03, "ST Microelectronics TSOD" }, |
145 |
}; |
146 |
|
147 |
static int jedec_dimm_attach(device_t dev); |
148 |
|
149 |
static int jedec_dimm_capacity(struct jedec_dimm_softc *sc, enum dram_type type, |
150 |
uint32_t *capacity_mb); |
151 |
|
152 |
static int jedec_dimm_detach(device_t dev); |
153 |
|
154 |
static int jedec_dimm_dump(struct jedec_dimm_softc *sc, enum dram_type type); |
155 |
|
156 |
static int jedec_dimm_field_to_str(struct jedec_dimm_softc *sc, char *dst, |
157 |
size_t dstsz, uint16_t offset, uint16_t len, bool ascii); |
158 |
|
159 |
static int jedec_dimm_probe(device_t dev); |
160 |
|
161 |
static int jedec_dimm_readw_be(struct jedec_dimm_softc *sc, uint8_t reg, |
162 |
uint16_t *val); |
163 |
|
164 |
static int jedec_dimm_temp_sysctl(SYSCTL_HANDLER_ARGS); |
165 |
|
166 |
static const char *jedec_dimm_tsod_match(uint16_t vid, uint16_t did); |
167 |
|
168 |
|
169 |
/** |
170 |
* device_attach() method. Read the DRAM type, use that to determine the offsets |
171 |
* and lengths of the asset string fields. Calculate the capacity. If a TSOD is |
172 |
* present, figure out exactly what it is, and update the device description. |
173 |
* If all of that was successful, create the sysctls for the DIMM. If an |
174 |
* optional slotid has been hinted, create a sysctl for that too. |
175 |
* |
176 |
* @author rpokala |
177 |
* |
178 |
* @param[in,out] dev |
179 |
* Device being attached. |
180 |
*/ |
181 |
static int |
182 |
jedec_dimm_attach(device_t dev) |
183 |
{ |
184 |
uint8_t byte; |
185 |
uint16_t devid; |
186 |
uint16_t partnum_len; |
187 |
uint16_t partnum_offset; |
188 |
uint16_t serial_len; |
189 |
uint16_t serial_offset; |
190 |
uint16_t tsod_present_offset; |
191 |
uint16_t vendorid; |
192 |
bool tsod_present; |
193 |
int rc; |
194 |
int new_desc_len; |
195 |
enum dram_type type; |
196 |
struct jedec_dimm_softc *sc; |
197 |
struct sysctl_ctx_list *ctx; |
198 |
struct sysctl_oid *oid; |
199 |
struct sysctl_oid_list *children; |
200 |
const char *tsod_match; |
201 |
const char *slotid_str; |
202 |
char *new_desc; |
203 |
|
204 |
sc = device_get_softc(dev); |
205 |
ctx = device_get_sysctl_ctx(dev); |
206 |
oid = device_get_sysctl_tree(dev); |
207 |
children = SYSCTL_CHILDREN(oid); |
208 |
|
209 |
bzero(sc, sizeof(*sc)); |
210 |
sc->dev = dev; |
211 |
sc->smbus = device_get_parent(dev); |
212 |
sc->spd_addr = smbus_get_addr(dev); |
213 |
|
214 |
/* The TSOD address has a different DTI from the SPD address, but shares |
215 |
* the LSA bits. |
216 |
*/ |
217 |
sc->tsod_addr = JEDEC_DTI_TSOD | (sc->spd_addr & 0x0f); |
218 |
|
219 |
/* Read the DRAM type, and set the various offsets and lengths. */ |
220 |
rc = smbus_readb(sc->smbus, sc->spd_addr, SPD_OFFSET_DRAM_TYPE, &byte); |
221 |
if (rc != 0) { |
222 |
device_printf(dev, "failed to read dram_type: %d\n", rc); |
223 |
goto out; |
224 |
} |
225 |
type = (enum dram_type) byte; |
226 |
switch (type) { |
227 |
case DRAM_TYPE_DDR3_SDRAM: |
228 |
(void) snprintf(sc->type_str, sizeof(sc->type_str), "DDR3"); |
229 |
partnum_len = SPD_LEN_DDR3_PARTNUM; |
230 |
partnum_offset = SPD_OFFSET_DDR3_PARTNUM; |
231 |
serial_len = SPD_LEN_DDR3_SERIAL; |
232 |
serial_offset = SPD_OFFSET_DDR3_SERIAL; |
233 |
tsod_present_offset = SPD_OFFSET_DDR3_TSOD_PRESENT; |
234 |
break; |
235 |
case DRAM_TYPE_DDR4_SDRAM: |
236 |
(void) snprintf(sc->type_str, sizeof(sc->type_str), "DDR4"); |
237 |
partnum_len = SPD_LEN_DDR4_PARTNUM; |
238 |
partnum_offset = SPD_OFFSET_DDR4_PARTNUM; |
239 |
serial_len = SPD_LEN_DDR4_SERIAL; |
240 |
serial_offset = SPD_OFFSET_DDR4_SERIAL; |
241 |
tsod_present_offset = SPD_OFFSET_DDR4_TSOD_PRESENT; |
242 |
break; |
243 |
default: |
244 |
device_printf(dev, "unsupported dram_type 0x%02x\n", type); |
245 |
rc = EINVAL; |
246 |
goto out; |
247 |
} |
248 |
|
249 |
if (bootverbose) { |
250 |
/* bootverbose debuggery is best-effort, so ignore the rc. */ |
251 |
(void) jedec_dimm_dump(sc, type); |
252 |
} |
253 |
|
254 |
/* Read all the required info from the SPD. If any of it fails, error |
255 |
* out without creating the sysctls. |
256 |
*/ |
257 |
rc = jedec_dimm_capacity(sc, type, &sc->capacity_mb); |
258 |
if (rc != 0) { |
259 |
goto out; |
260 |
} |
261 |
|
262 |
rc = jedec_dimm_field_to_str(sc, sc->part_str, sizeof(sc->part_str), |
263 |
partnum_offset, partnum_len, true); |
264 |
if (rc != 0) { |
265 |
goto out; |
266 |
} |
267 |
|
268 |
rc = jedec_dimm_field_to_str(sc, sc->serial_str, sizeof(sc->serial_str), |
269 |
serial_offset, serial_len, false); |
270 |
if (rc != 0) { |
271 |
goto out; |
272 |
} |
273 |
|
274 |
/* The MSBit of the TSOD-presence byte reports whether or not the TSOD |
275 |
* is in fact present. (While DDR3 and DDR4 don't explicitly require a |
276 |
* TSOD, essentially all DDR3 and DDR4 DIMMs include one.) But, as |
277 |
* discussed in [PR 235944], it turns out that some DIMMs claim to have |
278 |
* a TSOD when they actually don't. (Or maybe the firmware blocks it?) |
279 |
* <sigh> |
280 |
* If the SPD data says the TSOD is present, try to read manufacturer |
281 |
* and device info from it to confirm that it's a valid TSOD device. |
282 |
* If the data is unreadable, just continue as if the TSOD isn't there. |
283 |
* If the data was read successfully, see if it is a known TSOD device; |
284 |
* it's okay if it isn't (tsod_match == NULL). |
285 |
*/ |
286 |
rc = smbus_readb(sc->smbus, sc->spd_addr, tsod_present_offset, &byte); |
287 |
if (rc != 0) { |
288 |
device_printf(dev, "failed to read TSOD-present byte: %d\n", |
289 |
rc); |
290 |
goto out; |
291 |
} |
292 |
if (byte & 0x80) { |
293 |
tsod_present = true; |
294 |
rc = jedec_dimm_readw_be(sc, TSOD_REG_MANUFACTURER, &vendorid); |
295 |
if (rc != 0) { |
296 |
device_printf(dev, |
297 |
"failed to read TSOD Manufacturer ID\n"); |
298 |
rc = 0; |
299 |
goto no_tsod; |
300 |
} |
301 |
rc = jedec_dimm_readw_be(sc, TSOD_REG_DEV_REV, &devid); |
302 |
if (rc != 0) { |
303 |
device_printf(dev, "failed to read TSOD Device ID\n"); |
304 |
rc = 0; |
305 |
goto no_tsod; |
306 |
} |
307 |
|
308 |
tsod_match = jedec_dimm_tsod_match(vendorid, devid); |
309 |
if (bootverbose) { |
310 |
if (tsod_match == NULL) { |
311 |
device_printf(dev, |
312 |
"Unknown TSOD Manufacturer and Device IDs," |
313 |
" 0x%x and 0x%x\n", vendorid, devid); |
314 |
} else { |
315 |
device_printf(dev, |
316 |
"TSOD: %s\n", tsod_match); |
317 |
} |
318 |
} |
319 |
} else { |
320 |
no_tsod: |
321 |
tsod_match = NULL; |
322 |
tsod_present = false; |
323 |
} |
324 |
|
325 |
SYSCTL_ADD_STRING(ctx, children, OID_AUTO, "type", |
326 |
CTLFLAG_RD | CTLFLAG_MPSAFE, sc->type_str, 0, |
327 |
"DIMM type"); |
328 |
|
329 |
SYSCTL_ADD_UINT(ctx, children, OID_AUTO, "capacity", |
330 |
CTLFLAG_RD | CTLFLAG_MPSAFE, NULL, sc->capacity_mb, |
331 |
"DIMM capacity (MB)"); |
332 |
|
333 |
SYSCTL_ADD_STRING(ctx, children, OID_AUTO, "part", |
334 |
CTLFLAG_RD | CTLFLAG_MPSAFE, sc->part_str, 0, |
335 |
"DIMM Part Number"); |
336 |
|
337 |
SYSCTL_ADD_STRING(ctx, children, OID_AUTO, "serial", |
338 |
CTLFLAG_RD | CTLFLAG_MPSAFE, sc->serial_str, 0, |
339 |
"DIMM Serial Number"); |
340 |
|
341 |
/* Create the temperature sysctl IFF the TSOD is present and valid */ |
342 |
if (tsod_present && (tsod_match != NULL)) { |
343 |
SYSCTL_ADD_PROC(ctx, children, OID_AUTO, "temp", |
344 |
CTLTYPE_INT | CTLFLAG_RD | CTLFLAG_MPSAFE, dev, 0, |
345 |
jedec_dimm_temp_sysctl, "IK", "DIMM temperature (deg C)"); |
346 |
} |
347 |
|
348 |
/* If a "slotid" was hinted, add the sysctl for it. */ |
349 |
if (resource_string_value(device_get_name(dev), device_get_unit(dev), |
350 |
"slotid", &slotid_str) == 0) { |
351 |
if (slotid_str != NULL) { |
352 |
sc->slotid_str = strdup(slotid_str, M_DEVBUF); |
353 |
SYSCTL_ADD_STRING(ctx, children, OID_AUTO, "slotid", |
354 |
CTLFLAG_RD | CTLFLAG_MPSAFE, sc->slotid_str, 0, |
355 |
"DIMM Slot Identifier"); |
356 |
} |
357 |
} |
358 |
|
359 |
/* If a TSOD type string or a slotid are present, add them to the |
360 |
* device description. |
361 |
*/ |
362 |
if ((tsod_match != NULL) || (sc->slotid_str != NULL)) { |
363 |
new_desc_len = strlen(device_get_desc(dev)); |
364 |
if (tsod_match != NULL) { |
365 |
new_desc_len += strlen(tsod_match); |
366 |
new_desc_len += 4; /* " w/ " */ |
367 |
} |
368 |
if (sc->slotid_str != NULL) { |
369 |
new_desc_len += strlen(sc->slotid_str); |
370 |
new_desc_len += 3; /* space + parens */ |
371 |
} |
372 |
new_desc_len++; /* terminator */ |
373 |
new_desc = malloc(new_desc_len, M_TEMP, (M_WAITOK | M_ZERO)); |
374 |
(void) snprintf(new_desc, new_desc_len, "%s%s%s%s%s%s", |
375 |
device_get_desc(dev), |
376 |
(tsod_match ? " w/ " : ""), |
377 |
(tsod_match ? tsod_match : ""), |
378 |
(sc->slotid_str ? " (" : ""), |
379 |
(sc->slotid_str ? sc->slotid_str : ""), |
380 |
(sc->slotid_str ? ")" : "")); |
381 |
device_set_desc_copy(dev, new_desc); |
382 |
free(new_desc, M_TEMP); |
383 |
} |
384 |
|
385 |
out: |
386 |
return (rc); |
387 |
} |
388 |
|
389 |
/** |
390 |
* Calculate the capacity of a DIMM. Both DDR3 and DDR4 encode "geometry" |
391 |
* information in various SPD bytes. The standards documents codify everything |
392 |
* in look-up tables, but it's trivial to reverse-engineer the the formulas for |
393 |
* most of them. Unless otherwise noted, the same formulas apply for both DDR3 |
394 |
* and DDR4. The SPD offsets of where the data comes from are different between |
395 |
* the two types, because having them be the same would be too easy. |
396 |
* |
397 |
* @author rpokala |
398 |
* |
399 |
* @param[in] sc |
400 |
* Instance-specific context data |
401 |
* |
402 |
* @param[in] dram_type |
403 |
* The locations of the data used to calculate the capacity depends on the |
404 |
* type of the DIMM. |
405 |
* |
406 |
* @param[out] capacity_mb |
407 |
* The calculated capacity, in MB |
408 |
*/ |
409 |
static int |
410 |
jedec_dimm_capacity(struct jedec_dimm_softc *sc, enum dram_type type, |
411 |
uint32_t *capacity_mb) |
412 |
{ |
413 |
uint8_t bus_width_byte; |
414 |
uint8_t bus_width_offset; |
415 |
uint8_t dimm_ranks_byte; |
416 |
uint8_t dimm_ranks_offset; |
417 |
uint8_t sdram_capacity_byte; |
418 |
uint8_t sdram_capacity_offset; |
419 |
uint8_t sdram_pkg_type_byte; |
420 |
uint8_t sdram_pkg_type_offset; |
421 |
uint8_t sdram_width_byte; |
422 |
uint8_t sdram_width_offset; |
423 |
uint32_t bus_width; |
424 |
uint32_t dimm_ranks; |
425 |
uint32_t sdram_capacity; |
426 |
uint32_t sdram_pkg_type; |
427 |
uint32_t sdram_width; |
428 |
int rc; |
429 |
|
430 |
switch (type) { |
431 |
case DRAM_TYPE_DDR3_SDRAM: |
432 |
bus_width_offset = SPD_OFFSET_DDR3_BUS_WIDTH; |
433 |
dimm_ranks_offset = SPD_OFFSET_DDR3_DIMM_RANKS; |
434 |
sdram_capacity_offset = SPD_OFFSET_DDR3_SDRAM_CAPACITY; |
435 |
sdram_width_offset = SPD_OFFSET_DDR3_SDRAM_WIDTH; |
436 |
break; |
437 |
case DRAM_TYPE_DDR4_SDRAM: |
438 |
bus_width_offset = SPD_OFFSET_DDR4_BUS_WIDTH; |
439 |
dimm_ranks_offset = SPD_OFFSET_DDR4_DIMM_RANKS; |
440 |
sdram_capacity_offset = SPD_OFFSET_DDR4_SDRAM_CAPACITY; |
441 |
sdram_pkg_type_offset = SPD_OFFSET_DDR4_SDRAM_PKG_TYPE; |
442 |
sdram_width_offset = SPD_OFFSET_DDR4_SDRAM_WIDTH; |
443 |
break; |
444 |
default: |
445 |
device_printf(sc->dev, "unsupported dram_type 0x%02x\n", type); |
446 |
rc = EINVAL; |
447 |
goto out; |
448 |
} |
449 |
|
450 |
rc = smbus_readb(sc->smbus, sc->spd_addr, bus_width_offset, |
451 |
&bus_width_byte); |
452 |
if (rc != 0) { |
453 |
device_printf(sc->dev, "failed to read bus_width: %d\n", rc); |
454 |
goto out; |
455 |
} |
456 |
|
457 |
rc = smbus_readb(sc->smbus, sc->spd_addr, dimm_ranks_offset, |
458 |
&dimm_ranks_byte); |
459 |
if (rc != 0) { |
460 |
device_printf(sc->dev, "failed to read dimm_ranks: %d\n", rc); |
461 |
goto out; |
462 |
} |
463 |
|
464 |
rc = smbus_readb(sc->smbus, sc->spd_addr, sdram_capacity_offset, |
465 |
&sdram_capacity_byte); |
466 |
if (rc != 0) { |
467 |
device_printf(sc->dev, "failed to read sdram_capacity: %d\n", |
468 |
rc); |
469 |
goto out; |
470 |
} |
471 |
|
472 |
rc = smbus_readb(sc->smbus, sc->spd_addr, sdram_width_offset, |
473 |
&sdram_width_byte); |
474 |
if (rc != 0) { |
475 |
device_printf(sc->dev, "failed to read sdram_width: %d\n", rc); |
476 |
goto out; |
477 |
} |
478 |
|
479 |
/* The "SDRAM Package Type" is only needed for DDR4 DIMMs. */ |
480 |
if (type == DRAM_TYPE_DDR4_SDRAM) { |
481 |
rc = smbus_readb(sc->smbus, sc->spd_addr, sdram_pkg_type_offset, |
482 |
&sdram_pkg_type_byte); |
483 |
if (rc != 0) { |
484 |
device_printf(sc->dev, |
485 |
"failed to read sdram_pkg_type: %d\n", rc); |
486 |
goto out; |
487 |
} |
488 |
} |
489 |
|
490 |
/* "Primary bus width, in bits" is in bits [2:0]. */ |
491 |
bus_width_byte &= 0x07; |
492 |
if (bus_width_byte <= 3) { |
493 |
bus_width = 1 << bus_width_byte; |
494 |
bus_width *= 8; |
495 |
} else { |
496 |
device_printf(sc->dev, "invalid bus width info\n"); |
497 |
rc = EINVAL; |
498 |
goto out; |
499 |
} |
500 |
|
501 |
/* "Number of ranks per DIMM" is in bits [5:3]. Values 4-7 are only |
502 |
* valid for DDR4. |
503 |
*/ |
504 |
dimm_ranks_byte >>= 3; |
505 |
dimm_ranks_byte &= 0x07; |
506 |
if (dimm_ranks_byte <= 7) { |
507 |
dimm_ranks = dimm_ranks_byte + 1; |
508 |
} else { |
509 |
device_printf(sc->dev, "invalid DIMM Rank info\n"); |
510 |
rc = EINVAL; |
511 |
goto out; |
512 |
} |
513 |
if ((dimm_ranks_byte >= 4) && (type != DRAM_TYPE_DDR4_SDRAM)) { |
514 |
device_printf(sc->dev, "invalid DIMM Rank info\n"); |
515 |
rc = EINVAL; |
516 |
goto out; |
517 |
} |
518 |
|
519 |
/* "Total SDRAM capacity per die, in Mb" is in bits [3:0]. There are two |
520 |
* different formulas, for values 0-7 and for values 8-9. Also, values |
521 |
* 7-9 are only valid for DDR4. |
522 |
*/ |
523 |
sdram_capacity_byte &= 0x0f; |
524 |
if (sdram_capacity_byte <= 7) { |
525 |
sdram_capacity = 1 << sdram_capacity_byte; |
526 |
sdram_capacity *= 256; |
527 |
} else if (sdram_capacity_byte <= 9) { |
528 |
sdram_capacity = 12 << (sdram_capacity_byte - 8); |
529 |
sdram_capacity *= 1024; |
530 |
} else { |
531 |
device_printf(sc->dev, "invalid SDRAM capacity info\n"); |
532 |
rc = EINVAL; |
533 |
goto out; |
534 |
} |
535 |
if ((sdram_capacity_byte >= 7) && (type != DRAM_TYPE_DDR4_SDRAM)) { |
536 |
device_printf(sc->dev, "invalid SDRAM capacity info\n"); |
537 |
rc = EINVAL; |
538 |
goto out; |
539 |
} |
540 |
|
541 |
/* "SDRAM device width" is in bits [2:0]. */ |
542 |
sdram_width_byte &= 0x7; |
543 |
if (sdram_width_byte <= 3) { |
544 |
sdram_width = 1 << sdram_width_byte; |
545 |
sdram_width *= 4; |
546 |
} else { |
547 |
device_printf(sc->dev, "invalid SDRAM width info\n"); |
548 |
rc = EINVAL; |
549 |
goto out; |
550 |
} |
551 |
|
552 |
/* DDR4 has something called "3DS", which is indicated by [1:0] = 2; |
553 |
* when that is the case, the die count is encoded in [6:4], and |
554 |
* dimm_ranks is multiplied by it. |
555 |
*/ |
556 |
if ((type == DRAM_TYPE_DDR4_SDRAM) && |
557 |
((sdram_pkg_type_byte & 0x3) == 2)) { |
558 |
sdram_pkg_type_byte >>= 4; |
559 |
sdram_pkg_type_byte &= 0x07; |
560 |
sdram_pkg_type = sdram_pkg_type_byte + 1; |
561 |
dimm_ranks *= sdram_pkg_type; |
562 |
} |
563 |
|
564 |
/* Finally, assemble the actual capacity. The formula is the same for |
565 |
* both DDR3 and DDR4. |
566 |
*/ |
567 |
*capacity_mb = sdram_capacity / 8 * bus_width / sdram_width * |
568 |
dimm_ranks; |
569 |
|
570 |
out: |
571 |
return (rc); |
572 |
} |
573 |
|
574 |
/** |
575 |
* device_detach() method. If we allocated sc->slotid_str, free it. Even if we |
576 |
* didn't allocate, free it anyway; free(NULL) is safe. |
577 |
* |
578 |
* @author rpokala |
579 |
* |
580 |
* @param[in,out] dev |
581 |
* Device being detached. |
582 |
*/ |
583 |
static int |
584 |
jedec_dimm_detach(device_t dev) |
585 |
{ |
586 |
struct jedec_dimm_softc *sc; |
587 |
|
588 |
sc = device_get_softc(dev); |
589 |
free(sc->slotid_str, M_DEVBUF); |
590 |
|
591 |
return (0); |
592 |
} |
593 |
|
594 |
/** |
595 |
* Read and dump the entire SPD contents. |
596 |
* |
597 |
* @author rpokala |
598 |
* |
599 |
* @param[in] sc |
600 |
* Instance-specific context data |
601 |
* |
602 |
* @param[in] dram_type |
603 |
* The length of data which needs to be read and dumped differs based on |
604 |
* the type of the DIMM. |
605 |
*/ |
606 |
static int |
607 |
jedec_dimm_dump(struct jedec_dimm_softc *sc, enum dram_type type) |
608 |
{ |
609 |
int i; |
610 |
int rc; |
611 |
bool page_changed; |
612 |
uint8_t bytes[512]; |
613 |
|
614 |
page_changed = false; |
615 |
|
616 |
for (i = 0; i < 256; i++) { |
617 |
rc = smbus_readb(sc->smbus, sc->spd_addr, i, &bytes[i]); |
618 |
if (rc != 0) { |
619 |
device_printf(sc->dev, |
620 |
"unable to read page0:0x%02x: %d\n", i, rc); |
621 |
goto out; |
622 |
} |
623 |
} |
624 |
|
625 |
/* The DDR4 SPD is 512 bytes, but SMBus only allows for 8-bit offsets. |
626 |
* JEDEC gets around this by defining the "PAGE" DTI and LSAs. |
627 |
*/ |
628 |
if (type == DRAM_TYPE_DDR4_SDRAM) { |
629 |
page_changed = true; |
630 |
rc = smbus_writeb(sc->smbus, |
631 |
(JEDEC_DTI_PAGE | JEDEC_LSA_PAGE_SET1), 0, 0); |
632 |
if (rc != 0) { |
633 |
/* Some SPD devices (or SMBus controllers?) claim the |
634 |
* page-change command failed when it actually |
635 |
* succeeded. Log a message but soldier on. |
636 |
*/ |
637 |
device_printf(sc->dev, "unable to change page: %d\n", |
638 |
rc); |
639 |
} |
640 |
/* Add 256 to the store location, because we're in the second |
641 |
* page. |
642 |
*/ |
643 |
for (i = 0; i < 256; i++) { |
644 |
rc = smbus_readb(sc->smbus, sc->spd_addr, i, |
645 |
&bytes[256 + i]); |
646 |
if (rc != 0) { |
647 |
device_printf(sc->dev, |
648 |
"unable to read page1:0x%02x: %d\n", i, rc); |
649 |
goto out; |
650 |
} |
651 |
} |
652 |
} |
653 |
|
654 |
/* Display the data in a nice hexdump format, with byte offsets. */ |
655 |
hexdump(bytes, (page_changed ? 512 : 256), NULL, 0); |
656 |
|
657 |
out: |
658 |
if (page_changed) { |
659 |
int rc2; |
660 |
/* Switch back to page0 before returning. */ |
661 |
rc2 = smbus_writeb(sc->smbus, |
662 |
(JEDEC_DTI_PAGE | JEDEC_LSA_PAGE_SET0), 0, 0); |
663 |
if (rc2 != 0) { |
664 |
device_printf(sc->dev, "unable to restore page: %d\n", |
665 |
rc2); |
666 |
} |
667 |
} |
668 |
return (rc); |
669 |
} |
670 |
|
671 |
/** |
672 |
* Read a specified range of bytes from the SPD, convert them to a string, and |
673 |
* store them in the provided buffer. Some SPD fields are space-padded ASCII, |
674 |
* and some are just a string of bits that we want to convert to a hex string. |
675 |
* |
676 |
* @author rpokala |
677 |
* |
678 |
* @param[in] sc |
679 |
* Instance-specific context data |
680 |
* |
681 |
* @param[out] dst |
682 |
* The output buffer to populate |
683 |
* |
684 |
* @param[in] dstsz |
685 |
* The size of the output buffer |
686 |
* |
687 |
* @param[in] offset |
688 |
* The starting offset of the field within the SPD |
689 |
* |
690 |
* @param[in] len |
691 |
* The length in bytes of the field within the SPD |
692 |
* |
693 |
* @param[in] ascii |
694 |
* Is the field a sequence of ASCII characters? If not, it is binary data |
695 |
* which should be converted to characters. |
696 |
*/ |
697 |
static int |
698 |
jedec_dimm_field_to_str(struct jedec_dimm_softc *sc, char *dst, size_t dstsz, |
699 |
uint16_t offset, uint16_t len, bool ascii) |
700 |
{ |
701 |
uint8_t byte; |
702 |
int i; |
703 |
int rc; |
704 |
bool page_changed; |
705 |
|
706 |
/* Change to the proper page. Offsets [0, 255] are in page0; offsets |
707 |
* [256, 512] are in page1. |
708 |
* |
709 |
* *The page must be reset to page0 before returning.* |
710 |
* |
711 |
* For the page-change operation, only the DTI and LSA matter; the |
712 |
* offset and write-value are ignored, so use just 0. |
713 |
* |
714 |
* Mercifully, JEDEC defined the fields such that none of them cross |
715 |
* pages, so we don't need to worry about that complication. |
716 |
*/ |
717 |
if (offset < JEDEC_SPD_PAGE_SIZE) { |
718 |
page_changed = false; |
719 |
} else if (offset < (2 * JEDEC_SPD_PAGE_SIZE)) { |
720 |
page_changed = true; |
721 |
rc = smbus_writeb(sc->smbus, |
722 |
(JEDEC_DTI_PAGE | JEDEC_LSA_PAGE_SET1), 0, 0); |
723 |
if (rc != 0) { |
724 |
device_printf(sc->dev, |
725 |
"unable to change page for offset 0x%04x: %d\n", |
726 |
offset, rc); |
727 |
} |
728 |
/* Adjust the offset to account for the page change. */ |
729 |
offset -= JEDEC_SPD_PAGE_SIZE; |
730 |
} else { |
731 |
page_changed = false; |
732 |
rc = EINVAL; |
733 |
device_printf(sc->dev, "invalid offset 0x%04x\n", offset); |
734 |
goto out; |
735 |
} |
736 |
|
737 |
/* Sanity-check (adjusted) offset and length; everything must be within |
738 |
* the same page. |
739 |
*/ |
740 |
if (offset >= JEDEC_SPD_PAGE_SIZE) { |
741 |
rc = EINVAL; |
742 |
device_printf(sc->dev, "invalid offset 0x%04x\n", offset); |
743 |
goto out; |
744 |
} |
745 |
if ((offset + len) >= JEDEC_SPD_PAGE_SIZE) { |
746 |
rc = EINVAL; |
747 |
device_printf(sc->dev, |
748 |
"(offset + len) would cross page (0x%04x + 0x%04x)\n", |
749 |
offset, len); |
750 |
goto out; |
751 |
} |
752 |
|
753 |
/* Sanity-check the destination string length. If we're dealing with |
754 |
* ASCII chars, then the destination must be at least the same length; |
755 |
* otherwise, it must be *twice* the length, because each byte must |
756 |
* be converted into two nybble characters. |
757 |
* |
758 |
* And, of course, there needs to be an extra byte for the terminator. |
759 |
*/ |
760 |
if (ascii) { |
761 |
if (dstsz < (len + 1)) { |
762 |
rc = EINVAL; |
763 |
device_printf(sc->dev, |
764 |
"destination too short (%u < %u)\n", |
765 |
(uint16_t) dstsz, (len + 1)); |
766 |
goto out; |
767 |
} |
768 |
} else { |
769 |
if (dstsz < ((2 * len) + 1)) { |
770 |
rc = EINVAL; |
771 |
device_printf(sc->dev, |
772 |
"destination too short (%u < %u)\n", |
773 |
(uint16_t) dstsz, ((2 * len) + 1)); |
774 |
goto out; |
775 |
} |
776 |
} |
777 |
|
778 |
/* Read a byte at a time. */ |
779 |
for (i = 0; i < len; i++) { |
780 |
rc = smbus_readb(sc->smbus, sc->spd_addr, (offset + i), &byte); |
781 |
if (rc != 0) { |
782 |
device_printf(sc->dev, |
783 |
"failed to read byte at 0x%02x: %d\n", |
784 |
(offset + i), rc); |
785 |
goto out; |
786 |
} |
787 |
if (ascii) { |
788 |
/* chars can be copied directly. */ |
789 |
dst[i] = byte; |
790 |
} else { |
791 |
/* Raw bytes need to be converted to a two-byte hex |
792 |
* string, plus the terminator. |
793 |
*/ |
794 |
(void) snprintf(&dst[(2 * i)], 3, "%02x", byte); |
795 |
} |
796 |
} |
797 |
|
798 |
/* If we're dealing with ASCII, convert trailing spaces to NULs. */ |
799 |
if (ascii) { |
800 |
for (i = dstsz; i > 0; i--) { |
801 |
if (dst[i] == ' ') { |
802 |
dst[i] = 0; |
803 |
} else if (dst[i] == 0) { |
804 |
continue; |
805 |
} else { |
806 |
break; |
807 |
} |
808 |
} |
809 |
} |
810 |
|
811 |
out: |
812 |
if (page_changed) { |
813 |
int rc2; |
814 |
/* Switch back to page0 before returning. */ |
815 |
rc2 = smbus_writeb(sc->smbus, |
816 |
(JEDEC_DTI_PAGE | JEDEC_LSA_PAGE_SET0), 0, 0); |
817 |
if (rc2 != 0) { |
818 |
device_printf(sc->dev, |
819 |
"unable to restore page for offset 0x%04x: %d\n", |
820 |
offset, rc2); |
821 |
} |
822 |
} |
823 |
|
824 |
return (rc); |
825 |
} |
826 |
|
827 |
/** |
828 |
* device_probe() method. Validate the address that was given as a hint, and |
829 |
* display an error if it's bogus. Make sure that we're dealing with one of the |
830 |
* SPD versions that we can handle. |
831 |
* |
832 |
* @author rpokala |
833 |
* |
834 |
* @param[in] dev |
835 |
* Device being probed. |
836 |
*/ |
837 |
static int |
838 |
jedec_dimm_probe(device_t dev) |
839 |
{ |
840 |
uint8_t addr; |
841 |
uint8_t byte; |
842 |
int rc; |
843 |
enum dram_type type; |
844 |
device_t smbus; |
845 |
|
846 |
smbus = device_get_parent(dev); |
847 |
addr = smbus_get_addr(dev); |
848 |
|
849 |
/* Don't bother if this isn't an SPD address, or if the LSBit is set. */ |
850 |
if (((addr & 0xf0) != JEDEC_DTI_SPD) || |
851 |
((addr & 0x01) != 0)) { |
852 |
device_printf(dev, |
853 |
"invalid \"addr\" hint; address must start with \"0x%x\"," |
854 |
" and the least-significant bit must be 0\n", |
855 |
JEDEC_DTI_SPD); |
856 |
rc = ENXIO; |
857 |
goto out; |
858 |
} |
859 |
|
860 |
/* Try to read the DRAM_TYPE from the SPD. */ |
861 |
rc = smbus_readb(smbus, addr, SPD_OFFSET_DRAM_TYPE, &byte); |
862 |
if (rc != 0) { |
863 |
device_printf(dev, "failed to read dram_type\n"); |
864 |
goto out; |
865 |
} |
866 |
|
867 |
/* This driver currently only supports DDR3 and DDR4 SPDs. */ |
868 |
type = (enum dram_type) byte; |
869 |
switch (type) { |
870 |
case DRAM_TYPE_DDR3_SDRAM: |
871 |
rc = BUS_PROBE_DEFAULT; |
872 |
device_set_desc(dev, "DDR3 DIMM"); |
873 |
break; |
874 |
case DRAM_TYPE_DDR4_SDRAM: |
875 |
rc = BUS_PROBE_DEFAULT; |
876 |
device_set_desc(dev, "DDR4 DIMM"); |
877 |
break; |
878 |
default: |
879 |
rc = ENXIO; |
880 |
break; |
881 |
} |
882 |
|
883 |
out: |
884 |
return (rc); |
885 |
} |
886 |
|
887 |
/** |
888 |
* SMBus specifies little-endian byte order, but it looks like the TSODs use |
889 |
* big-endian. Read and convert. |
890 |
* |
891 |
* @author avg |
892 |
* |
893 |
* @param[in] sc |
894 |
* Instance-specific context data |
895 |
* |
896 |
* @param[in] reg |
897 |
* The register number to read. |
898 |
* |
899 |
* @param[out] val |
900 |
* Pointer to populate with the value read. |
901 |
*/ |
902 |
static int |
903 |
jedec_dimm_readw_be(struct jedec_dimm_softc *sc, uint8_t reg, uint16_t *val) |
904 |
{ |
905 |
int rc; |
906 |
|
907 |
rc = smbus_readw(sc->smbus, sc->tsod_addr, reg, val); |
908 |
if (rc != 0) { |
909 |
goto out; |
910 |
} |
911 |
*val = be16toh(*val); |
912 |
|
913 |
out: |
914 |
return (rc); |
915 |
} |
916 |
|
917 |
/** |
918 |
* Read the temperature data from the TSOD and convert it to the deciKelvin |
919 |
* value that the sysctl expects. |
920 |
* |
921 |
* @author avg |
922 |
*/ |
923 |
static int |
924 |
jedec_dimm_temp_sysctl(SYSCTL_HANDLER_ARGS) |
925 |
{ |
926 |
uint16_t val; |
927 |
int rc; |
928 |
int temp; |
929 |
device_t dev = arg1; |
930 |
struct jedec_dimm_softc *sc; |
931 |
|
932 |
sc = device_get_softc(dev); |
933 |
|
934 |
rc = jedec_dimm_readw_be(sc, TSOD_REG_TEMPERATURE, &val); |
935 |
if (rc != 0) { |
936 |
goto out; |
937 |
} |
938 |
|
939 |
/* The three MSBits are flags, and the next bit is a sign bit. */ |
940 |
temp = val & 0xfff; |
941 |
if ((val & 0x1000) != 0) |
942 |
temp = -temp; |
943 |
/* Each step is 0.0625 degrees, so convert to 1000ths of a degree C. */ |
944 |
temp *= 625; |
945 |
/* ... and then convert to 1000ths of a Kelvin */ |
946 |
temp += 2731500; |
947 |
/* As a practical matter, few (if any) TSODs are more accurate than |
948 |
* about a tenth of a degree, so round accordingly. This correlates with |
949 |
* the "IK" formatting used for this sysctl. |
950 |
*/ |
951 |
temp = (temp + 500) / 1000; |
952 |
|
953 |
rc = sysctl_handle_int(oidp, &temp, 0, req); |
954 |
|
955 |
out: |
956 |
return (rc); |
957 |
} |
958 |
|
959 |
/** |
960 |
* Check the TSOD's Vendor ID and Device ID against the list of known TSOD |
961 |
* devices. Return the description, or NULL if this doesn't look like a valid |
962 |
* TSOD. |
963 |
* |
964 |
* @author avg |
965 |
* |
966 |
* @param[in] vid |
967 |
* The Vendor ID of the TSOD device |
968 |
* |
969 |
* @param[in] did |
970 |
* The Device ID of the TSOD device |
971 |
* |
972 |
* @return |
973 |
* The description string, or NULL for a failure to match. |
974 |
*/ |
975 |
static const char * |
976 |
jedec_dimm_tsod_match(uint16_t vid, uint16_t did) |
977 |
{ |
978 |
const struct jedec_dimm_tsod_dev *d; |
979 |
int i; |
980 |
|
981 |
for (i = 0; i < nitems(known_tsod_devices); i++) { |
982 |
d = &known_tsod_devices[i]; |
983 |
if ((vid == d->vendor_id) && ((did >> 8) == d->device_id)) { |
984 |
return (d->description); |
985 |
} |
986 |
} |
987 |
|
988 |
/* If no matches for a specific device, then check for a generic |
989 |
* TSE2004av-compliant device. |
990 |
*/ |
991 |
if ((did >> 8) == 0x22) { |
992 |
return ("TSE2004av compliant TSOD"); |
993 |
} |
994 |
|
995 |
return (NULL); |
996 |
} |
997 |
|
998 |
static device_method_t jedec_dimm_methods[] = { |
999 |
/* Methods from the device interface */ |
1000 |
DEVMETHOD(device_probe, jedec_dimm_probe), |
1001 |
DEVMETHOD(device_attach, jedec_dimm_attach), |
1002 |
DEVMETHOD(device_detach, jedec_dimm_detach), |
1003 |
DEVMETHOD_END |
1004 |
}; |
1005 |
|
1006 |
static driver_t jedec_dimm_driver = { |
1007 |
.name = "jedec_dimm", |
1008 |
.methods = jedec_dimm_methods, |
1009 |
.size = sizeof(struct jedec_dimm_softc), |
1010 |
}; |
1011 |
|
1012 |
static devclass_t jedec_dimm_devclass; |
1013 |
|
1014 |
DRIVER_MODULE(jedec_dimm, smbus, jedec_dimm_driver, jedec_dimm_devclass, 0, 0); |
1015 |
MODULE_DEPEND(jedec_dimm, smbus, SMBUS_MINVER, SMBUS_PREFVER, SMBUS_MAXVER); |
1016 |
MODULE_VERSION(jedec_dimm, 1); |
1017 |
|
1018 |
/* vi: set ts=8 sw=4 sts=8 noet: */ |