Changeset View
Changeset View
Standalone View
Standalone View
sys/dev/jedec_dimm/jedec_dimm.c
/*- | /*- | ||||
* SPDX-License-Identifier: BSD-2-Clause-FreeBSD | * SPDX-License-Identifier: BSD-2-Clause-FreeBSD | ||||
* | * | ||||
* Authors: Ravi Pokala (rpokala@freebsd.org), Andriy Gapon (avg@FreeBSD.org) | * Authors: Ravi Pokala (rpokala@freebsd.org), Andriy Gapon (avg@FreeBSD.org) | ||||
* | * | ||||
* Copyright (c) 2016 Andriy Gapon <avg@FreeBSD.org> | * Copyright (c) 2016 Andriy Gapon <avg@FreeBSD.org> | ||||
* Copyright (c) 2018 Panasas | * Copyright (c) 2018,2023 Panasas | ||||
* | * | ||||
* Redistribution and use in source and binary forms, with or without | * Redistribution and use in source and binary forms, with or without | ||||
* modification, are permitted provided that the following conditions | * modification, are permitted provided that the following conditions | ||||
* are met: | * are met: | ||||
* 1. Redistributions of source code must retain the above copyright | * 1. Redistributions of source code must retain the above copyright | ||||
* notice, this list of conditions and the following disclaimer. | * notice, this list of conditions and the following disclaimer. | ||||
* 2. Redistributions in binary form must reproduce the above copyright | * 2. Redistributions in binary form must reproduce the above copyright | ||||
* notice, this list of conditions and the following disclaimer in the | * notice, this list of conditions and the following disclaimer in the | ||||
Show All 35 Lines | |||||
#include "smbus_if.h" | #include "smbus_if.h" | ||||
struct jedec_dimm_softc { | struct jedec_dimm_softc { | ||||
device_t dev; | device_t dev; | ||||
device_t smbus; | device_t smbus; | ||||
uint8_t spd_addr; /* SMBus address of the SPD EEPROM. */ | uint8_t spd_addr; /* SMBus address of the SPD EEPROM. */ | ||||
uint8_t tsod_addr; /* Address of the Thermal Sensor On DIMM */ | uint8_t tsod_addr; /* Address of the Thermal Sensor On DIMM */ | ||||
uint8_t mfg_year; | |||||
uint8_t mfg_week; | |||||
uint32_t capacity_mb; | uint32_t capacity_mb; | ||||
char type_str[5]; | char type_str[5]; | ||||
char part_str[21]; /* 18 (DDR3) or 20 (DDR4) chars, plus terminator */ | char part_str[21]; /* 18 (DDR3) or 20 (DDR4) chars, plus terminator */ | ||||
char serial_str[9]; /* 4 bytes = 8 nybble characters, plus terminator */ | char serial_str[9]; /* 4 bytes = 8 nybble characters, plus terminator */ | ||||
char *slotid_str; /* Optional DIMM slot identifier (silkscreen) */ | char *slotid_str; /* Optional DIMM slot identifier (silkscreen) */ | ||||
}; | }; | ||||
/* General Thermal Sensor on DIMM (TSOD) identification notes. | /* General Thermal Sensor on DIMM (TSOD) identification notes. | ||||
▲ Show 20 Lines • Show All 82 Lines • ▼ Show 20 Lines | |||||
static int jedec_dimm_detach(device_t dev); | static int jedec_dimm_detach(device_t dev); | ||||
static int jedec_dimm_dump(struct jedec_dimm_softc *sc, enum dram_type type); | static int jedec_dimm_dump(struct jedec_dimm_softc *sc, enum dram_type type); | ||||
static int jedec_dimm_field_to_str(struct jedec_dimm_softc *sc, char *dst, | static int jedec_dimm_field_to_str(struct jedec_dimm_softc *sc, char *dst, | ||||
size_t dstsz, uint16_t offset, uint16_t len, bool ascii); | size_t dstsz, uint16_t offset, uint16_t len, bool ascii); | ||||
static int jedec_dimm_mfg_date(struct jedec_dimm_softc *sc, enum dram_type type, | |||||
uint8_t *year, uint8_t *week); | |||||
static int jedec_dimm_probe(device_t dev); | static int jedec_dimm_probe(device_t dev); | ||||
static int jedec_dimm_readw_be(struct jedec_dimm_softc *sc, uint8_t reg, | static int jedec_dimm_readw_be(struct jedec_dimm_softc *sc, uint8_t reg, | ||||
uint16_t *val); | uint16_t *val); | ||||
static int jedec_dimm_temp_sysctl(SYSCTL_HANDLER_ARGS); | static int jedec_dimm_temp_sysctl(SYSCTL_HANDLER_ARGS); | ||||
static const char *jedec_dimm_tsod_match(uint16_t vid, uint16_t did); | static const char *jedec_dimm_tsod_match(uint16_t vid, uint16_t did); | ||||
▲ Show 20 Lines • Show All 87 Lines • ▼ Show 20 Lines | jedec_dimm_attach(device_t dev) | ||||
/* Read all the required info from the SPD. If any of it fails, error | /* Read all the required info from the SPD. If any of it fails, error | ||||
* out without creating the sysctls. | * out without creating the sysctls. | ||||
*/ | */ | ||||
rc = jedec_dimm_capacity(sc, type, &sc->capacity_mb); | rc = jedec_dimm_capacity(sc, type, &sc->capacity_mb); | ||||
if (rc != 0) { | if (rc != 0) { | ||||
goto out; | goto out; | ||||
} | } | ||||
rc = jedec_dimm_mfg_date(sc, type, &sc->mfg_year, &sc->mfg_week); | |||||
if (rc != 0) { | |||||
goto out; | |||||
} | |||||
rc = jedec_dimm_field_to_str(sc, sc->part_str, sizeof(sc->part_str), | rc = jedec_dimm_field_to_str(sc, sc->part_str, sizeof(sc->part_str), | ||||
partnum_offset, partnum_len, true); | partnum_offset, partnum_len, true); | ||||
if (rc != 0) { | if (rc != 0) { | ||||
goto out; | goto out; | ||||
} | } | ||||
rc = jedec_dimm_field_to_str(sc, sc->serial_str, sizeof(sc->serial_str), | rc = jedec_dimm_field_to_str(sc, sc->serial_str, sizeof(sc->serial_str), | ||||
serial_offset, serial_len, false); | serial_offset, serial_len, false); | ||||
▲ Show 20 Lines • Show All 63 Lines • ▼ Show 20 Lines | no_tsod: | ||||
SYSCTL_ADD_STRING(ctx, children, OID_AUTO, "part", | SYSCTL_ADD_STRING(ctx, children, OID_AUTO, "part", | ||||
CTLFLAG_RD | CTLFLAG_MPSAFE, sc->part_str, 0, | CTLFLAG_RD | CTLFLAG_MPSAFE, sc->part_str, 0, | ||||
"DIMM Part Number"); | "DIMM Part Number"); | ||||
SYSCTL_ADD_STRING(ctx, children, OID_AUTO, "serial", | SYSCTL_ADD_STRING(ctx, children, OID_AUTO, "serial", | ||||
CTLFLAG_RD | CTLFLAG_MPSAFE, sc->serial_str, 0, | CTLFLAG_RD | CTLFLAG_MPSAFE, sc->serial_str, 0, | ||||
"DIMM Serial Number"); | "DIMM Serial Number"); | ||||
SYSCTL_ADD_U8(ctx, children, OID_AUTO, "mfg_year", | |||||
CTLFLAG_RD | CTLFLAG_MPSAFE, NULL, sc->mfg_year, | |||||
"DIMM manufacturing year (20xx)"); | |||||
SYSCTL_ADD_U8(ctx, children, OID_AUTO, "mfg_week", | |||||
CTLFLAG_RD | CTLFLAG_MPSAFE, NULL, sc->mfg_week, | |||||
"DIMM manufacturing week"); | |||||
/* Create the temperature sysctl IFF the TSOD is present and valid */ | /* Create the temperature sysctl IFF the TSOD is present and valid */ | ||||
if (tsod_present && (tsod_match != NULL)) { | if (tsod_present && (tsod_match != NULL)) { | ||||
SYSCTL_ADD_PROC(ctx, children, OID_AUTO, "temp", | SYSCTL_ADD_PROC(ctx, children, OID_AUTO, "temp", | ||||
CTLTYPE_INT | CTLFLAG_RD | CTLFLAG_MPSAFE, dev, 0, | CTLTYPE_INT | CTLFLAG_RD | CTLFLAG_MPSAFE, dev, 0, | ||||
jedec_dimm_temp_sysctl, "IK", "DIMM temperature (deg C)"); | jedec_dimm_temp_sysctl, "IK", "DIMM temperature (deg C)"); | ||||
} | } | ||||
/* If a "slotid" was hinted, add the sysctl for it. */ | /* If a "slotid" was hinted, add the sysctl for it. */ | ||||
▲ Show 20 Lines • Show All 464 Lines • ▼ Show 20 Lines | if (page_changed) { | ||||
int rc2; | int rc2; | ||||
/* Switch back to page0 before returning. */ | /* Switch back to page0 before returning. */ | ||||
rc2 = smbus_writeb(sc->smbus, | rc2 = smbus_writeb(sc->smbus, | ||||
(JEDEC_DTI_PAGE | JEDEC_LSA_PAGE_SET0), 0, 0); | (JEDEC_DTI_PAGE | JEDEC_LSA_PAGE_SET0), 0, 0); | ||||
if (rc2 != 0) { | if (rc2 != 0) { | ||||
device_printf(sc->dev, | device_printf(sc->dev, | ||||
"unable to restore page for offset 0x%04x: %d\n", | "unable to restore page for offset 0x%04x: %d\n", | ||||
offset, rc2); | offset, rc2); | ||||
} | |||||
} | |||||
return (rc); | |||||
} | |||||
/** | |||||
* Both DDR3 and DDR4 encode manufacturing date as a one-byte BCD-encoded | |||||
* year (offset from 2000) and a one-byte BCD-encoded week within that year. | |||||
* The SPD offsets are different between the two types. | |||||
* | |||||
* @author rpokala | |||||
* | |||||
* @param[in] sc | |||||
* Instance-specific context data | |||||
* | |||||
* @param[in] dram_type | |||||
* The locations of the manufacturing date depends on the type of the DIMM. | |||||
* | |||||
* @param[out] year | |||||
* The manufacturing year, offset from 2000 | |||||
* | |||||
* @param[out] week | |||||
* The manufacturing week within the year | |||||
*/ | |||||
static int | |||||
jedec_dimm_mfg_date(struct jedec_dimm_softc *sc, enum dram_type type, | |||||
uint8_t *year, uint8_t *week) | |||||
{ | |||||
uint8_t year_bcd; | |||||
uint8_t week_bcd; | |||||
uint16_t year_offset; | |||||
uint16_t week_offset; | |||||
bool page_changed; | |||||
int rc; | |||||
switch (type) { | |||||
case DRAM_TYPE_DDR3_SDRAM: | |||||
year_offset = SPD_OFFSET_DDR3_MOD_MFG_YEAR; | |||||
week_offset = SPD_OFFSET_DDR3_MOD_MFG_WEEK; | |||||
break; | |||||
case DRAM_TYPE_DDR4_SDRAM: | |||||
year_offset = SPD_OFFSET_DDR4_MOD_MFG_YEAR; | |||||
week_offset = SPD_OFFSET_DDR4_MOD_MFG_WEEK; | |||||
break; | |||||
default: | |||||
device_printf(sc->dev, "unsupported dram_type 0x%02x\n", type); | |||||
jhb: Maybe use __assert_unreachable() here since the caller has already checked this? | |||||
Not Done Inline ActionsThere are a few other bits of cleanup/refactoring that I'm going to do a bit later; I'll add this to the list. rpokala: There are a few other bits of cleanup/refactoring that I'm going to do a bit later; I'll add… | |||||
Done Inline ActionsCleanup is out for review in https://reviews.freebsd.org/D39842 rpokala: Cleanup is out for review in https://reviews.freebsd.org/D39842 | |||||
rc = EINVAL; | |||||
page_changed = false; | |||||
goto out; | |||||
} | |||||
/* Change to the proper page. Offsets [0, 255] are in page0; offsets | |||||
* [256, 512] are in page1. | |||||
* | |||||
* *The page must be reset to page0 before returning.* | |||||
* | |||||
* For the page-change operation, only the DTI and LSA matter; the | |||||
* offset and write-value are ignored, so use just 0. | |||||
* | |||||
* Mercifully, JEDEC defined the fields such that all of the | |||||
* manufacturing-related ones are on the same page, so we don't need to | |||||
* worry about that complication. | |||||
*/ | |||||
if (year_offset < JEDEC_SPD_PAGE_SIZE) { | |||||
page_changed = false; | |||||
} else if (year_offset < (2 * JEDEC_SPD_PAGE_SIZE)) { | |||||
page_changed = true; | |||||
rc = smbus_writeb(sc->smbus, | |||||
(JEDEC_DTI_PAGE | JEDEC_LSA_PAGE_SET1), 0, 0); | |||||
if (rc != 0) { | |||||
device_printf(sc->dev, | |||||
"unable to change page for offset 0x%04x: %d\n", | |||||
year_offset, rc); | |||||
} | |||||
/* Adjust the offset to account for the page change. */ | |||||
year_offset -= JEDEC_SPD_PAGE_SIZE; | |||||
week_offset -= JEDEC_SPD_PAGE_SIZE; | |||||
} else { | |||||
device_printf(sc->dev, "invalid offset 0x%04x\n", year_offset); | |||||
rc = EINVAL; | |||||
page_changed = false; | |||||
goto out; | |||||
} | |||||
rc = smbus_readb(sc->smbus, sc->spd_addr, year_offset, &year_bcd); | |||||
if (rc != 0) { | |||||
device_printf(sc->dev, "failed to read mfg year: %d\n", rc); | |||||
goto out; | |||||
} | |||||
rc = smbus_readb(sc->smbus, sc->spd_addr, week_offset, &week_bcd); | |||||
if (rc != 0) { | |||||
device_printf(sc->dev, "failed to read mfg week: %d\n", rc); | |||||
goto out; | |||||
} | |||||
/* Convert from one-byte BCD to one-byte integer. */ | |||||
*year = (((year_bcd & 0xf0) >> 4) * 10) + (year_bcd & 0x0f); | |||||
*week = (((week_bcd & 0xf0) >> 4) * 10) + (week_bcd & 0x0f); | |||||
out: | |||||
if (page_changed) { | |||||
int rc2; | |||||
/* Switch back to page0 before returning. */ | |||||
rc2 = smbus_writeb(sc->smbus, | |||||
(JEDEC_DTI_PAGE | JEDEC_LSA_PAGE_SET0), 0, 0); | |||||
if (rc2 != 0) { | |||||
device_printf(sc->dev, | |||||
"unable to restore page for offset 0x%04x: %d\n", | |||||
year_offset, rc2); | |||||
} | } | ||||
} | } | ||||
return (rc); | return (rc); | ||||
} | } | ||||
/** | /** | ||||
* device_probe() method. Validate the address that was given as a hint, and | * device_probe() method. Validate the address that was given as a hint, and | ||||
▲ Show 20 Lines • Show All 188 Lines • Show Last 20 Lines |
Maybe use __assert_unreachable() here since the caller has already checked this?