git: 861fdb46300b - stable/13 - jedec_dimm(4): Add manufacturing year and week.

From: Ravi Pokala <rpokala_at_FreeBSD.org>
Date: Fri, 05 May 2023 00:30:01 UTC
The branch stable/13 has been updated by rpokala:

URL: https://cgit.FreeBSD.org/src/commit/?id=861fdb46300bd16808ca443ecc222f10db02d53d

commit 861fdb46300bd16808ca443ecc222f10db02d53d
Author:     Ravi Pokala <rpokala@FreeBSD.org>
AuthorDate: 2023-04-25 06:07:39 +0000
Commit:     Ravi Pokala <rpokala@FreeBSD.org>
CommitDate: 2023-05-05 00:27:55 +0000

    jedec_dimm(4): Add manufacturing year and week.
    
    DDR3 and DDR4 encode the week and year that the DIMM was manufactured,
    as a pair of two-digit binary-coded decimal values. Read the values, and
    report them as (uint8_t)s.
    
    Reviewed by:    imp, jhb
    MFC after:      1 week
    Sponsored by:   Panasas
    Differential Revision:  https://reviews.freebsd.org/D39795
    
    (cherry picked from commit de57e0ef5a15c75231191e643d4d1949ddbca92d)
---
 share/man/man4/jedec_dimm.4     |  10 ++-
 sys/dev/jedec_dimm/jedec_dimm.c | 131 +++++++++++++++++++++++++++++++++++++++-
 sys/dev/jedec_dimm/jedec_dimm.h |   6 +-
 3 files changed, 144 insertions(+), 3 deletions(-)

diff --git a/share/man/man4/jedec_dimm.4 b/share/man/man4/jedec_dimm.4
index ea4183fafc1a..6c13da1a450e 100644
--- a/share/man/man4/jedec_dimm.4
+++ b/share/man/man4/jedec_dimm.4
@@ -26,7 +26,7 @@
 .\"
 .\" $FreeBSD$
 .\"
-.Dd July 31, 2018
+.Dd April 25, 2023
 .Dt JEDEC_DIMM 4
 .Os
 .Sh NAME
@@ -76,6 +76,10 @@ interface; all values are read-only:
 a string description of the DIMM, including TSOD and slotid info if present.
 .It Va dev.jedec_dimm.X.capacity
 the DIMM's memory capacity, in megabytes
+.It Va dev.jedec_dimm.X.mfg_week
+the week within the year in which the DIMM was manufactured
+.It Va dev.jedec_dimm.X.mfg_year
+the year in which the DIMM was manufactured
 .It Va dev.jedec_dimm.X.part
 the manufacturer's part number of the DIMM
 .It Va dev.jedec_dimm.X.serial
@@ -144,6 +148,8 @@ dev.jedec_dimm.0.%location: addr=0xa0
 dev.jedec_dimm.0.%parent: smbus0
 dev.jedec_dimm.0.%pnpinfo:
 dev.jedec_dimm.0.capacity: 16384
+dev.jedec_dimm.0.mfg_week: 30
+dev.jedec_dimm.0.mfg_year: 17
 dev.jedec_dimm.0.part: 36ASF2G72PZ-2G1A2
 dev.jedec_dimm.0.serial: 0ea815de
 dev.jedec_dimm.0.slotid: A1
@@ -156,6 +162,8 @@ dev.jedec_dimm.6.%location: addr=0xa8
 dev.jedec_dimm.6.%parent: smbus1
 dev.jedec_dimm.6.%pnpinfo:
 dev.jedec_dimm.6.capacity: 8192
+dev.jedec_dimm.6.mfg_week: 13
+dev.jedec_dimm.6.mfg_year: 19
 dev.jedec_dimm.6.part: VRA9MR8B2H1603
 dev.jedec_dimm.6.serial: 0c4c46ad
 dev.jedec_dimm.6.temp: 43.1C
diff --git a/sys/dev/jedec_dimm/jedec_dimm.c b/sys/dev/jedec_dimm/jedec_dimm.c
index 884da8b51061..73ea10c5c5e1 100644
--- a/sys/dev/jedec_dimm/jedec_dimm.c
+++ b/sys/dev/jedec_dimm/jedec_dimm.c
@@ -4,7 +4,7 @@
  * Authors: Ravi Pokala (rpokala@freebsd.org), 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
  * modification, are permitted provided that the following conditions
@@ -56,6 +56,8 @@ struct jedec_dimm_softc {
 	device_t smbus;
 	uint8_t spd_addr;	/* SMBus address of the SPD EEPROM. */
 	uint8_t tsod_addr;	/* Address of the Thermal Sensor On DIMM */
+	uint8_t mfg_year;
+	uint8_t mfg_week;
 	uint32_t capacity_mb;
 	char type_str[5];
 	char part_str[21]; /* 18 (DDR3) or 20 (DDR4) chars, plus terminator */
@@ -154,6 +156,9 @@ 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,
     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_readw_be(struct jedec_dimm_softc *sc, uint8_t reg,
@@ -257,6 +262,11 @@ jedec_dimm_attach(device_t dev)
 		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),
 	    partnum_offset, partnum_len, true);
 	if (rc != 0) {
@@ -336,6 +346,14 @@ no_tsod:
 	    CTLFLAG_RD | CTLFLAG_MPSAFE, sc->serial_str, 0,
 	    "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 */
 	if (tsod_present && (tsod_match != NULL)) {
 		SYSCTL_ADD_PROC(ctx, children, OID_AUTO, "temp",
@@ -822,6 +840,117 @@ out:
 	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);
+		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);
+}
+
 /**
  * device_probe() method. Validate the address that was given as a hint, and
  * display an error if it's bogus. Make sure that we're dealing with one of the
diff --git a/sys/dev/jedec_dimm/jedec_dimm.h b/sys/dev/jedec_dimm/jedec_dimm.h
index 3b330251efc5..00a9b3d521b6 100644
--- a/sys/dev/jedec_dimm/jedec_dimm.h
+++ b/sys/dev/jedec_dimm/jedec_dimm.h
@@ -3,7 +3,7 @@
  *
  * Authors: Ravi Pokala (rpokala@freebsd.org)
  *
- * Copyright (c) 2018 Panasas
+ * Copyright (c) 2018-2023 Panasas
  *
  * Redistribution and use in source and binary forms, with or without
  * modification, are permitted provided that the following conditions
@@ -90,6 +90,8 @@
 #define SPD_OFFSET_DDR3_SDRAM_WIDTH	7
 #define SPD_OFFSET_DDR3_BUS_WIDTH	8
 #define SPD_OFFSET_DDR3_TSOD_PRESENT	32
+#define SPD_OFFSET_DDR3_MOD_MFG_YEAR	120
+#define SPD_OFFSET_DDR3_MOD_MFG_WEEK	121
 #define SPD_OFFSET_DDR3_SERIAL		122
 #define SPD_LEN_DDR3_SERIAL		4
 #define SPD_OFFSET_DDR3_PARTNUM		128
@@ -100,6 +102,8 @@
 #define SPD_OFFSET_DDR4_SDRAM_WIDTH	12
 #define SPD_OFFSET_DDR4_BUS_WIDTH	13
 #define SPD_OFFSET_DDR4_TSOD_PRESENT	14
+#define SPD_OFFSET_DDR4_MOD_MFG_YEAR	323
+#define SPD_OFFSET_DDR4_MOD_MFG_WEEK	324
 #define SPD_OFFSET_DDR4_SERIAL		325
 #define SPD_LEN_DDR4_SERIAL		4
 #define SPD_OFFSET_DDR4_PARTNUM		329