summaryrefslogtreecommitdiff
path: root/usr/src/common
diff options
context:
space:
mode:
authorRobert Mustacchi <rm@fingolfin.org>2022-02-28 09:46:53 +0000
committerRobert Mustacchi <rm@fingolfin.org>2022-07-20 15:48:24 +0000
commit71815ce76261aa773c97600750fdce92334d1990 (patch)
tree3585ce8ffb6f3db3198f1d218191a3b36ee1918c /usr/src/common
parent1bcd6a1a4eeaf2fd7a90ce8b8cebd4f34baf049f (diff)
downloadillumos-joyent-71815ce76261aa773c97600750fdce92334d1990.tar.gz
14727 Want AMD Unified Memory Controller Driver
Reviewed by: Keith M Wesolowski <wesolows@oxide.computer> Reviewed by: Richard Lowe <richlowe@richlowe.net> Reviewed by: C Fraire <cfraire@me.com> Approved by: Garrett D'Amore <garrett@damore.org>
Diffstat (limited to 'usr/src/common')
-rw-r--r--usr/src/common/bitext/bitext.c171
-rw-r--r--usr/src/common/mc/zen_umc/zen_fabric_utils.c97
-rw-r--r--usr/src/common/mc/zen_umc/zen_umc_decode.c1619
-rw-r--r--usr/src/common/mc/zen_umc/zen_umc_dump.c717
4 files changed, 2604 insertions, 0 deletions
diff --git a/usr/src/common/bitext/bitext.c b/usr/src/common/bitext/bitext.c
new file mode 100644
index 0000000000..cfd74b6925
--- /dev/null
+++ b/usr/src/common/bitext/bitext.c
@@ -0,0 +1,171 @@
+/*
+ * This file and its contents are supplied under the terms of the
+ * Common Development and Distribution License ("CDDL"), version 1.0.
+ * You may only use this file in accordance with the terms of version
+ * 1.0 of the CDDL.
+ *
+ * A full copy of the text of the CDDL should have accompanied this
+ * source. A copy of the CDDL is also available via the Internet at
+ * http://www.illumos.org/license/CDDL.
+ */
+
+/*
+ * Copyright 2022 Oxide Computer Company
+ */
+
+/*
+ * Various functions for manipulating regions of bits in standard sized
+ * integers. Meant to be a replacement for the extant BITX macro and provide
+ * additional functionality. See bitx64(9F), bitdel64(9F), and bitset64(9f) for
+ * more information.
+ */
+
+#include <sys/debug.h>
+#include <sys/stdint.h>
+
+uint8_t
+bitx8(uint8_t reg, uint_t high, uint_t low)
+{
+ uint8_t mask;
+
+ ASSERT3U(high, >=, low);
+ ASSERT3U(high, <, 8);
+ ASSERT3U(low, <, 8);
+
+ mask = (1 << (high - low + 1)) - 1;
+ return ((reg >> low) & mask);
+}
+
+uint16_t
+bitx16(uint16_t reg, uint_t high, uint_t low)
+{
+ uint16_t mask;
+
+ ASSERT3U(high, >=, low);
+ ASSERT3U(high, <, 16);
+ ASSERT3U(low, <, 16);
+
+ mask = (1 << (high - low + 1)) - 1;
+ return ((reg >> low) & mask);
+}
+
+
+uint32_t
+bitx32(uint32_t reg, uint_t high, uint_t low)
+{
+ uint32_t mask;
+
+ ASSERT3U(high, >=, low);
+ ASSERT3U(high, <, 32);
+ ASSERT3U(low, <, 32);
+
+ mask = (1UL << (high - low + 1)) - 1;
+
+ return ((reg >> low) & mask);
+}
+
+uint64_t
+bitx64(uint64_t reg, uint_t high, uint_t low)
+{
+ uint64_t mask;
+
+ ASSERT3U(high, >=, low);
+ ASSERT3U(high, <, 64);
+ ASSERT3U(low, <, 64);
+
+ mask = (1ULL << (high - low + 1)) - 1ULL;
+ return ((reg >> low) & mask);
+}
+
+uint8_t
+bitset8(uint8_t reg, uint_t high, uint_t low, uint8_t val)
+{
+ uint8_t mask;
+
+ ASSERT3U(high, >=, low);
+ ASSERT3U(high, <, 8);
+ ASSERT3U(low, <, 8);
+
+ mask = (1 << (high - low + 1)) - 1;
+ ASSERT0(~mask & val);
+
+ reg &= ~(mask << low);
+ reg |= val << low;
+
+ return (reg);
+}
+
+uint16_t
+bitset16(uint16_t reg, uint_t high, uint_t low, uint16_t val)
+{
+ uint16_t mask;
+
+ ASSERT3U(high, >=, low);
+ ASSERT3U(high, <, 16);
+ ASSERT3U(low, <, 16);
+
+ mask = (1 << (high - low + 1)) - 1;
+ ASSERT0(~mask & val);
+
+ reg &= ~(mask << low);
+ reg |= val << low;
+
+ return (reg);
+}
+
+uint32_t
+bitset32(uint32_t reg, uint_t high, uint_t low, uint32_t val)
+{
+ uint32_t mask;
+
+ ASSERT3U(high, >=, low);
+ ASSERT3U(high, <, 32);
+ ASSERT3U(low, <, 32);
+
+ mask = (1UL << (high - low + 1)) - 1;
+ ASSERT0(~mask & val);
+
+ reg &= ~(mask << low);
+ reg |= val << low;
+
+ return (reg);
+}
+
+uint64_t
+bitset64(uint64_t reg, uint_t high, uint_t low, uint64_t val)
+{
+ uint64_t mask;
+
+ ASSERT3U(high, >=, low);
+ ASSERT3U(high, <, 64);
+ ASSERT3U(low, <, 64);
+
+ mask = (1ULL << (high - low + 1)) - 1ULL;
+ ASSERT0(~mask & val);
+
+ reg &= ~(mask << low);
+ reg |= val << low;
+
+ return (reg);
+}
+
+uint64_t
+bitdel64(uint64_t val, uint_t high, uint_t low)
+{
+ uint64_t high_val = 0;
+ uint64_t low_val = 0;
+
+ ASSERT3U(high, >=, low);
+ ASSERT3U(high, <, 64);
+ ASSERT3U(low, <, 64);
+
+ if (low != 0) {
+ low_val = bitx64(val, low - 1, 0);
+ }
+
+ if (high != 63) {
+ high_val = bitx64(val, 63, high + 1);
+ }
+
+ return ((high_val << low) | low_val);
+}
diff --git a/usr/src/common/mc/zen_umc/zen_fabric_utils.c b/usr/src/common/mc/zen_umc/zen_fabric_utils.c
new file mode 100644
index 0000000000..d4db7875f2
--- /dev/null
+++ b/usr/src/common/mc/zen_umc/zen_fabric_utils.c
@@ -0,0 +1,97 @@
+/*
+ * This file and its contents are supplied under the terms of the
+ * Common Development and Distribution License ("CDDL"), version 1.0.
+ * You may only use this file in accordance with the terms of version
+ * 1.0 of the CDDL.
+ *
+ * A full copy of the text of the CDDL should have accompanied this
+ * source. A copy of the CDDL is also available via the Internet at
+ * http://www.illumos.org/license/CDDL.
+ */
+
+/*
+ * Copyright 2022 Oxide Computer Company
+ */
+
+/*
+ * A collection of utility functions for interacting with fabric IDs.
+ */
+
+#include "zen_umc.h"
+
+/*
+ * Validate whether a fabric ID actually represents a valid ID for a given data
+ * fabric.
+ */
+boolean_t
+zen_fabric_id_valid_fabid(const df_fabric_decomp_t *decomp,
+ const uint32_t fabid)
+{
+ uint32_t mask = decomp->dfd_node_mask | decomp->dfd_comp_mask;
+ return ((fabid & ~mask) == 0);
+}
+
+/*
+ * Validate whether the parts of a fabric ID (e.g. the socket, die, and
+ * component) are in fact valid for a given data fabric.
+ */
+boolean_t
+zen_fabric_id_valid_parts(const df_fabric_decomp_t *decomp, const uint32_t sock,
+ const uint32_t die, const uint32_t comp)
+{
+ uint32_t node;
+
+ if (((sock << decomp->dfd_sock_shift) & ~decomp->dfd_sock_mask) != 0) {
+ return (B_FALSE);
+ }
+ if (((die << decomp->dfd_die_shift) & ~decomp->dfd_die_mask) != 0) {
+ return (B_FALSE);
+ }
+ if ((comp & ~decomp->dfd_comp_mask) != 0) {
+ return (B_FALSE);
+ }
+
+ node = die << decomp->dfd_die_shift;
+ node |= sock << decomp->dfd_sock_shift;
+
+ if (((node << decomp->dfd_node_shift) & ~decomp->dfd_node_mask) != 0) {
+ return (B_FALSE);
+ }
+
+ return (B_TRUE);
+}
+
+/*
+ * Take apart a fabric ID into its constituent parts. The decomposition
+ * information has the die and socket information relative to the node ID.
+ */
+void
+zen_fabric_id_decompose(const df_fabric_decomp_t *decomp, const uint32_t fabid,
+ uint32_t *sockp, uint32_t *diep, uint32_t *compp)
+{
+ uint32_t node;
+
+ ASSERT(zen_fabric_id_valid_fabid(decomp, fabid));
+
+ *compp = (fabid & decomp->dfd_comp_mask) >> decomp->dfd_comp_shift;
+ node = (fabid & decomp->dfd_node_mask) >> decomp->dfd_node_shift;
+ *diep = (node & decomp->dfd_die_mask) >> decomp->dfd_die_shift;
+ *sockp = (node & decomp->dfd_sock_mask) >> decomp->dfd_sock_shift;
+}
+
+/*
+ * Compose a fabric ID from its constituent parts: the socket, die, and fabric.
+ */
+void
+zen_fabric_id_compose(const df_fabric_decomp_t *decomp, const uint32_t sock,
+ const uint32_t die, const uint32_t comp, uint32_t *fabidp)
+{
+ uint32_t node;
+
+ ASSERT(zen_fabric_id_valid_parts(decomp, sock, die, comp));
+
+ node = die << decomp->dfd_die_shift;
+ node |= sock << decomp->dfd_sock_shift;
+ *fabidp = (node << decomp->dfd_node_shift) |
+ (comp << decomp->dfd_comp_shift);
+}
diff --git a/usr/src/common/mc/zen_umc/zen_umc_decode.c b/usr/src/common/mc/zen_umc/zen_umc_decode.c
new file mode 100644
index 0000000000..acf03868cd
--- /dev/null
+++ b/usr/src/common/mc/zen_umc/zen_umc_decode.c
@@ -0,0 +1,1619 @@
+/*
+ * This file and its contents are supplied under the terms of the
+ * Common Development and Distribution License ("CDDL"), version 1.0.
+ * You may only use this file in accordance with the terms of version
+ * 1.0 of the CDDL.
+ *
+ * A full copy of the text of the CDDL should have accompanied this
+ * source. A copy of the CDDL is also available via the Internet at
+ * http://www.illumos.org/license/CDDL.
+ */
+
+/*
+ * Copyright 2022 Oxide Computer Company
+ */
+
+/*
+ * Zen UMC Decoding logic. See zen_umc.c for an overview of everything. This
+ * implements shared userland/kernel decoding.
+ */
+
+#include "zen_umc.h"
+
+#ifndef _KERNEL
+#include <strings.h>
+#endif
+
+/*
+ * Address constants.
+ */
+#define ZEN_UMC_TOM2_START 0x100000000ULL
+#define ZEN_UMC_TOM2_RSVD_BEGIN 0xfd00000000ULL
+#define ZEN_UMC_TOM2_RSVD_END 0x10000000000ULL
+
+/*
+ * COD based hashing constants.
+ */
+#define ZEN_UMC_COD_NBITS 3
+#define ZEN_UMC_NPS_MOD_NBITS 3
+
+/*
+ * We want to apply some initial heuristics to determine if a physical address
+ * is DRAM before we proceed because of the MMIO hole and related. The DRAM
+ * ranges can overlap with these system reserved ranges so we have to manually
+ * check these. Effectively this means that we have a few valid ranges:
+ *
+ * o [ 0, TOM )
+ * o [ 4 GiB, TOM2 )
+ *
+ * However, the above 4 GiB runs into trouble depending on size. There is a 12
+ * GiB system reserved address region right below 1 TiB. So it really turns
+ * into the following when we have more than 1 TiB of DRAM:
+ *
+ * o [ 0, TOM )
+ * o [ 4 GiB, 1 TiB - 12 GiB )
+ * o [ 1 TiB, TOM2 )
+ *
+ * Note, this does not currently scan MTRRs or MMIO rules for what might be
+ * redirected to MMIO.
+ */
+static boolean_t
+zen_umc_decode_is_dram(const zen_umc_t *umc, zen_umc_decoder_t *dec)
+{
+ if (dec->dec_pa < umc->umc_tom) {
+ return (B_TRUE);
+ }
+
+ if (dec->dec_pa >= umc->umc_tom2) {
+ dec->dec_fail = ZEN_UMC_DECODE_F_OUTSIDE_DRAM;
+ return (B_FALSE);
+ }
+
+ /*
+ * If the address is in the reserved hole around 1 TiB, do not proceed.
+ */
+ if (dec->dec_pa >= ZEN_UMC_TOM2_RSVD_BEGIN &&
+ dec->dec_pa < ZEN_UMC_TOM2_RSVD_END) {
+ dec->dec_fail = ZEN_UMC_DECODE_F_OUTSIDE_DRAM;
+ return (B_FALSE);
+ }
+
+ /*
+ * Now that we've validated we're not in the hole, check to see if we're
+ * actually in a valid region for TOM2.
+ */
+ if (dec->dec_pa >= ZEN_UMC_TOM2_START &&
+ dec->dec_pa < umc->umc_tom2) {
+ return (B_TRUE);
+ }
+
+ /*
+ * At this point we have eliminated all known DRAM regions described by
+ * TOM and TOM2, so we have to conclude that whatever we're looking at
+ * is now not part of DRAM.
+ */
+ dec->dec_fail = ZEN_UMC_DECODE_F_OUTSIDE_DRAM;
+ return (B_FALSE);
+}
+
+/*
+ * In our first stop on decoding, we need to go through and take a physical
+ * address and figure out what the corresponding initial DF rule that applies
+ * is. This rule will then be used to figure out which target on the data fabric
+ * we should be going to and what interleaving rules apply.
+ *
+ * Our DRAM rule may reflect that the DRAM hole is active. In this case the
+ * specified range in the rule will be larger than the actual amount of DRAM
+ * present. MMIO accesses take priority over DRAM accesses in the core and
+ * therefore the MMIO portion of the rule is not actually decoded. When trying
+ * to match a rule we do not need to worry about that and can just look whether
+ * our physical address matches a rule. We will take into account whether
+ * hoisting should adjust the address when we translate from a system address to
+ * a normal address (e.g. an address in the channel) which will be done in a
+ * subsequent step. If an address is in the hole, that has already been
+ * accounted for.
+ *
+ * While gathering information, we have all the DRAM rules for a given CCM that
+ * corresponds to a CPU core. This allows us to review all DRAM rules in one
+ * place rather than walking through what's been assigned to each UMC instance,
+ * which only has the rules that are directed towards that particular channel
+ * and matter for determining channel offsets.
+ */
+static boolean_t
+zen_umc_decode_find_df_rule(const zen_umc_t *umc, zen_umc_decoder_t *dec)
+{
+ const zen_umc_df_t *df = &umc->umc_dfs[0];
+
+ for (uint_t i = 0; i < df->zud_dram_nrules; i++) {
+ const df_dram_rule_t *rule = &df->zud_rules[i];
+
+ /*
+ * If this rule is not enabled, skip it.
+ */
+ if ((rule->ddr_flags & DF_DRAM_F_VALID) == 0)
+ continue;
+
+ if (dec->dec_pa >= rule->ddr_base &&
+ dec->dec_pa < rule->ddr_limit) {
+ dec->dec_df_ruleno = i;
+ dec->dec_df_rule = rule;
+ dec->dec_df_rulesrc = df;
+ return (B_TRUE);
+ }
+ }
+
+ dec->dec_fail = ZEN_UMC_DECODE_F_NO_DF_RULE;
+ return (B_FALSE);
+}
+
+/*
+ * This function takes care of the common logic of adjusting an address by the
+ * base value in the rule and determining if we need to apply the DRAM hole or
+ * not. This function is used in two different places:
+ *
+ * o As part of adjusting the system address to construct the interleave
+ * address for DFv4 and Zen 3 based 6-channel hashing (see
+ * zen_umc_determine_ileave_addr() below).
+ * o As part of adjusting the system address at the beginning of normalization
+ * to a channel address.
+ *
+ * One thing to highlight is that the same adjustment we make in the first case
+ * applies to a subset of things for interleaving; however, it applies to
+ * everything when normalizing.
+ */
+static boolean_t
+zen_umc_adjust_dram_addr(const zen_umc_t *umc, zen_umc_decoder_t *dec,
+ uint64_t *addrp, zen_umc_decode_failure_t errno)
+{
+ const uint64_t init_addr = *addrp;
+ const df_dram_rule_t *rule = dec->dec_df_rule;
+ const zen_umc_df_t *df = dec->dec_df_rulesrc;
+ uint64_t mod_addr = init_addr;
+
+ ASSERT3U(init_addr, >=, rule->ddr_base);
+ ASSERT3U(init_addr, <, rule->ddr_limit);
+ mod_addr -= rule->ddr_base;
+
+ /*
+ * Determine if the hole applies to this rule.
+ */
+ if ((rule->ddr_flags & DF_DRAM_F_HOLE) != 0 &&
+ (df->zud_flags & ZEN_UMC_DF_F_HOLE_VALID) != 0 &&
+ init_addr >= ZEN_UMC_TOM2_START) {
+ uint64_t hole_size;
+ hole_size = ZEN_UMC_TOM2_START -
+ umc->umc_dfs[0].zud_hole_base;
+ if (mod_addr < hole_size) {
+ dec->dec_fail = errno;
+ dec->dec_fail_data = dec->dec_df_ruleno;
+ return (B_FALSE);
+ }
+
+ mod_addr -= hole_size;
+ }
+
+ *addrp = mod_addr;
+ return (B_TRUE);
+}
+
+/*
+ * Take care of constructing the address we need to use for determining the
+ * interleaving target fabric id. See the big theory statement in zen_umc.c for
+ * more on this.
+ */
+static boolean_t
+zen_umc_determine_ileave_addr(const zen_umc_t *umc, zen_umc_decoder_t *dec)
+{
+ const df_dram_rule_t *rule = dec->dec_df_rule;
+
+ if (umc->umc_df_rev <= DF_REV_3 &&
+ rule->ddr_chan_ileave != DF_CHAN_ILEAVE_6CH) {
+ dec->dec_ilv_pa = dec->dec_pa;
+ return (B_TRUE);
+ }
+
+ dec->dec_ilv_pa = dec->dec_pa;
+ if (!zen_umc_adjust_dram_addr(umc, dec, &dec->dec_ilv_pa,
+ ZEN_UMC_DECODE_F_ILEAVE_UNDERFLOW)) {
+ return (B_FALSE);
+ }
+
+ return (B_TRUE);
+}
+
+/*
+ * This is a simple interleaving case where we simply extract bits. No hashing
+ * required! Per zen_umc.c, from lowest to highest, we have channel, die, and
+ * then socket bits.
+ */
+static boolean_t
+zen_umc_decode_ileave_nohash(const zen_umc_t *umc, zen_umc_decoder_t *dec)
+{
+ uint32_t nchan_bit, ndie_bit, nsock_bit, addr_bit;
+ const df_dram_rule_t *rule = dec->dec_df_rule;
+
+ nsock_bit = rule->ddr_sock_ileave_bits;
+ ndie_bit = rule->ddr_die_ileave_bits;
+ switch (rule->ddr_chan_ileave) {
+ case DF_CHAN_ILEAVE_1CH:
+ nchan_bit = 0;
+ break;
+ case DF_CHAN_ILEAVE_2CH:
+ nchan_bit = 1;
+ break;
+ case DF_CHAN_ILEAVE_4CH:
+ nchan_bit = 2;
+ break;
+ case DF_CHAN_ILEAVE_8CH:
+ nchan_bit = 3;
+ break;
+ case DF_CHAN_ILEAVE_16CH:
+ nchan_bit = 4;
+ break;
+ case DF_CHAN_ILEAVE_32CH:
+ nchan_bit = 5;
+ break;
+ default:
+ dec->dec_fail = ZEN_UMC_DECODE_F_CHAN_ILEAVE_NOTSUP;
+ dec->dec_fail_data = rule->ddr_chan_ileave;
+ return (B_FALSE);
+ }
+
+ /*
+ * Zero all of these out in case no bits are dedicated to this purpose.
+ * In those cases, then the value for this is always zero.
+ */
+ dec->dec_ilv_sock = dec->dec_ilv_die = dec->dec_ilv_chan = 0;
+ addr_bit = rule->ddr_addr_start;
+ if (nchan_bit > 0) {
+ dec->dec_ilv_chan = bitx64(dec->dec_ilv_pa,
+ addr_bit + nchan_bit - 1, addr_bit);
+ addr_bit += nchan_bit;
+ }
+
+ if (ndie_bit > 0) {
+ dec->dec_ilv_die = bitx64(dec->dec_ilv_pa,
+ addr_bit + ndie_bit - 1, addr_bit);
+ addr_bit += ndie_bit;
+ }
+
+ if (nsock_bit > 0) {
+ dec->dec_ilv_sock = bitx64(dec->dec_ilv_pa,
+ addr_bit + nsock_bit - 1, addr_bit);
+ addr_bit += nsock_bit;
+ }
+
+ return (B_TRUE);
+}
+
+/*
+ * Perform the Zen 2/Zen 3 "COD" based hashing. See the zen_umc.c interleaving
+ * section of the big theory statement for an overview of how this works.
+ */
+static boolean_t
+zen_umc_decode_ileave_cod(const zen_umc_t *umc, zen_umc_decoder_t *dec)
+{
+ uint32_t nchan_bit;
+ const df_dram_rule_t *rule = dec->dec_df_rule;
+ /*
+ * The order of bits here is defined by AMD. Yes, we do use the rule's
+ * address bit first and then skip to bit 12 for the second hash bit.
+ */
+ const uint32_t addr_bits[3] = { rule->ddr_addr_start, 12, 13 };
+
+ if (rule->ddr_sock_ileave_bits != 0 || rule->ddr_die_ileave_bits != 0) {
+ dec->dec_fail = ZEN_UMC_DECODE_F_COD_BAD_ILEAVE;
+ dec->dec_fail_data = dec->dec_df_ruleno;
+ return (B_FALSE);
+ }
+
+ switch (rule->ddr_chan_ileave) {
+ case DF_CHAN_ILEAVE_COD4_2CH:
+ nchan_bit = 1;
+ break;
+ case DF_CHAN_ILEAVE_COD2_4CH:
+ nchan_bit = 2;
+ break;
+ case DF_CHAN_ILEAVE_COD1_8CH:
+ nchan_bit = 3;
+ break;
+ default:
+ dec->dec_fail = ZEN_UMC_DECODE_F_CHAN_ILEAVE_NOTSUP;
+ dec->dec_fail_data = rule->ddr_chan_ileave;
+ return (B_FALSE);
+ }
+
+ dec->dec_ilv_sock = dec->dec_ilv_die = dec->dec_ilv_chan = 0;
+
+ /*
+ * Proceed to calculate the address hash based on the number of bits
+ * that we have been told to use based on the DF rule. Use the flags in
+ * the rule to determine which additional address ranges to hash in.
+ */
+ for (uint_t i = 0; i < nchan_bit; i++) {
+ uint8_t hash = 0;
+
+ hash = bitx64(dec->dec_ilv_pa, addr_bits[i], addr_bits[i]);
+ if ((rule->ddr_flags & DF_DRAM_F_HASH_16_18) != 0) {
+ uint8_t val = bitx64(dec->dec_ilv_pa, 16 + i, 16 + i);
+ hash ^= val;
+ }
+
+ if ((rule->ddr_flags & DF_DRAM_F_HASH_21_23) != 0) {
+ uint8_t val = bitx64(dec->dec_ilv_pa, 21 + i, 21 + i);
+ hash ^= val;
+ }
+
+ if ((rule->ddr_flags & DF_DRAM_F_HASH_30_32) != 0) {
+ uint8_t val = bitx64(dec->dec_ilv_pa, 30 + i, 30 + i);
+ hash ^= val;
+ }
+
+ dec->dec_ilv_chan |= hash << i;
+ }
+
+ return (B_TRUE);
+}
+
+/*
+ * This implements the standard NPS hash for power of 2 based channel
+ * configurations that is found in DFv4. For more information, please see the
+ * interleaving portion of the zen_umc.c big theory statement.
+ */
+static boolean_t
+zen_umc_decode_ileave_nps(const zen_umc_t *umc, zen_umc_decoder_t *dec)
+{
+ uint32_t nchan_bit, nsock_bit;
+ const df_dram_rule_t *rule = dec->dec_df_rule;
+ /*
+ * The order of bits here is defined by AMD. Yes, this is start with the
+ * defined address bit and then skip to bit 12.
+ */
+ const uint32_t addr_bits[4] = { rule->ddr_addr_start, 12, 13, 14 };
+
+ if (rule->ddr_die_ileave_bits != 0) {
+ dec->dec_fail = ZEN_UMC_DECODE_F_NPS_BAD_ILEAVE;
+ dec->dec_fail_data = dec->dec_df_ruleno;
+ return (B_FALSE);
+ }
+
+ nsock_bit = rule->ddr_sock_ileave_bits;
+ switch (rule->ddr_chan_ileave) {
+ case DF_CHAN_ILEAVE_NPS4_2CH:
+ nchan_bit = 1;
+ break;
+ case DF_CHAN_ILEAVE_NPS2_4CH:
+ nchan_bit = 2;
+ break;
+ case DF_CHAN_ILEAVE_NPS1_8CH:
+ nchan_bit = 3;
+ break;
+ default:
+ dec->dec_fail = ZEN_UMC_DECODE_F_CHAN_ILEAVE_NOTSUP;
+ dec->dec_fail_data = rule->ddr_chan_ileave;
+ return (B_FALSE);
+ }
+
+ ASSERT3U(nchan_bit + nsock_bit, <=, 4);
+ dec->dec_ilv_sock = dec->dec_ilv_die = dec->dec_ilv_chan = 0;
+
+ for (uint_t i = 0; i < nchan_bit + nsock_bit; i++) {
+ uint8_t hash = 0;
+
+ hash = bitx64(dec->dec_ilv_pa, addr_bits[i], addr_bits[i]);
+ if ((rule->ddr_flags & DF_DRAM_F_HASH_16_18) != 0) {
+ uint8_t val = bitx64(dec->dec_ilv_pa, 16 + i, 16 + i);
+ hash ^= val;
+ }
+
+ if ((rule->ddr_flags & DF_DRAM_F_HASH_21_23) != 0) {
+ uint8_t val = bitx64(dec->dec_ilv_pa, 21 + i, 21 + i);
+ hash ^= val;
+ }
+
+ if ((rule->ddr_flags & DF_DRAM_F_HASH_30_32) != 0) {
+ uint8_t val = bitx64(dec->dec_ilv_pa, 30 + i, 30 + i);
+ hash ^= val;
+ }
+
+ /*
+ * If this is the first bit and we're not doing socket
+ * interleaving, then we need to add bit 14 to the running hash.
+ */
+ if (i == 0 && nsock_bit == 0) {
+ uint8_t val = bitx64(dec->dec_ilv_pa, 14, 14);
+ hash ^= val;
+ }
+
+ /*
+ * If socket interleaving is going on we need to store the first
+ * bit as the socket hash and then redirect the remaining bits
+ * to the channel, taking into account that the shift will be
+ * adjusted as a result.
+ */
+ if (nsock_bit > 0) {
+ if (i == 0) {
+ dec->dec_ilv_sock = hash;
+ } else {
+ dec->dec_ilv_chan |= hash << (i - 1);
+ }
+ } else {
+ dec->dec_ilv_chan |= hash << i;
+ }
+ }
+
+ return (B_TRUE);
+}
+
+/*
+ * This implements the logic to perform the Zen 3 6ch special hash. It's worth
+ * calling out that unlike all other hash functions, this does not support the
+ * use of the DF_DRAM_F_HASH_16_18 flag.
+ */
+static void
+zen_umc_decode_hash_zen3_6ch(const df_dram_rule_t *rule, uint64_t pa,
+ uint8_t hashes[3])
+{
+ uint32_t addr_bit = rule->ddr_addr_start;
+ /*
+ * Yes, we use these in a weird order. No, there is no 64K.
+ */
+ const uint32_t bits_2M[3] = { 23, 21, 22 };
+ const uint32_t bits_1G[3] = { 32, 30, 31 };
+
+ hashes[0] = hashes[1] = hashes[2] = 0;
+ for (uint_t i = 0; i < ZEN_UMC_COD_NBITS; i++) {
+ hashes[i] = bitx64(pa, addr_bit + i, addr_bit + i);
+ if (i == 0) {
+ uint8_t val = bitx64(pa, addr_bit + 3, addr_bit + 3);
+ hashes[i] ^= val;
+ }
+
+ if ((rule->ddr_flags & DF_DRAM_F_HASH_21_23) != 0) {
+ uint8_t val = bitx64(pa, bits_2M[i], bits_2M[i]);
+ hashes[i] ^= val;
+ }
+
+ if ((rule->ddr_flags & DF_DRAM_F_HASH_30_32) != 0) {
+ uint8_t val = bitx64(pa, bits_1G[i], bits_1G[i]);
+ hashes[i] ^= val;
+ }
+ }
+}
+
+/*
+ * Perform Zen 3 6-channel hashing. This is pretty weird compared to others. See
+ * the zen_umc.c big theory statement for the thorny details.
+ */
+static boolean_t
+zen_umc_decode_ileave_zen3_6ch(const zen_umc_t *umc, zen_umc_decoder_t *dec)
+{
+ uint8_t hashes[3] = { 0 };
+ const df_dram_rule_t *rule = dec->dec_df_rule;
+ uint32_t addr_bit = rule->ddr_addr_start;
+
+ if (rule->ddr_sock_ileave_bits != 0 || rule->ddr_die_ileave_bits != 0) {
+ dec->dec_fail = ZEN_UMC_DECODE_F_COD_BAD_ILEAVE;
+ dec->dec_fail_data = dec->dec_df_ruleno;
+ return (B_FALSE);
+ }
+
+ zen_umc_decode_hash_zen3_6ch(rule, dec->dec_ilv_pa, hashes);
+ dec->dec_ilv_sock = dec->dec_ilv_die = dec->dec_ilv_chan = 0;
+ dec->dec_ilv_chan = hashes[0];
+ if (hashes[1] == 1 && hashes[2] == 1) {
+ uint64_t mod_addr = dec->dec_ilv_pa >> (addr_bit + 3);
+ dec->dec_ilv_chan |= (mod_addr % 3) << 1;
+ } else {
+ dec->dec_ilv_chan |= hashes[1] << 1;
+ dec->dec_ilv_chan |= hashes[2] << 2;
+ }
+
+ return (B_TRUE);
+}
+
+/*
+ * This is the standard hash function for the non-power of two based NPS hashes.
+ * See the big theory statement for more information. Unlike the normal NPS hash
+ * which uses bit 14 conditionally based on socket interleaving, here it is
+ * always used.
+ */
+static void
+zen_umc_decode_hash_nps_mod(const df_dram_rule_t *rule, uint64_t pa,
+ uint8_t hashes[3])
+{
+ const uint32_t addr_bits[3] = { rule->ddr_addr_start, 12, 13 };
+
+ for (uint_t i = 0; i < ZEN_UMC_NPS_MOD_NBITS; i++) {
+ hashes[i] = bitx64(pa, addr_bits[i], addr_bits[i]);
+ if (i == 0) {
+ uint8_t val = bitx64(pa, 14, 14);
+ hashes[i] ^= val;
+ }
+
+ if ((rule->ddr_flags & DF_DRAM_F_HASH_16_18) != 0) {
+ uint8_t val = bitx64(pa, 16 + i, 16 + i);
+ hashes[i] ^= val;
+ }
+
+ if ((rule->ddr_flags & DF_DRAM_F_HASH_21_23) != 0) {
+ uint8_t val = bitx64(pa, 21 + i, 21 + i);
+ hashes[i] ^= val;
+ }
+
+ if ((rule->ddr_flags & DF_DRAM_F_HASH_30_32) != 0) {
+ uint8_t val = bitx64(pa, 30 + i, 30 + i);
+ hashes[i] ^= val;
+ }
+ }
+}
+
+/*
+ * See the big theory statement in zen_umc.c which describes the rules for this
+ * computation. This is a little less weird than the Zen 3 one, but still,
+ * unique.
+ */
+static boolean_t
+zen_umc_decode_ileave_nps_mod(const zen_umc_t *umc, zen_umc_decoder_t *dec)
+{
+ uint8_t hashes[3] = { 0 };
+ uint32_t nsock_bit, chan_mod;
+ const df_dram_rule_t *rule = dec->dec_df_rule;
+
+ if (rule->ddr_die_ileave_bits != 0) {
+ dec->dec_fail = ZEN_UMC_DECODE_F_NPS_BAD_ILEAVE;
+ dec->dec_fail_data = dec->dec_df_ruleno;
+ return (B_FALSE);
+ }
+
+ nsock_bit = rule->ddr_sock_ileave_bits;
+ switch (rule->ddr_chan_ileave) {
+ case DF_CHAN_ILEAVE_NPS4_3CH:
+ case DF_CHAN_ILEAVE_NPS2_6CH:
+ case DF_CHAN_ILEAVE_NPS1_12CH:
+ chan_mod = 3;
+ break;
+ case DF_CHAN_ILEAVE_NPS2_5CH:
+ case DF_CHAN_ILEAVE_NPS1_10CH:
+ chan_mod = 5;
+ break;
+ default:
+ dec->dec_fail = ZEN_UMC_DECODE_F_CHAN_ILEAVE_NOTSUP;
+ dec->dec_fail_data = rule->ddr_chan_ileave;
+ return (B_FALSE);
+ }
+
+ dec->dec_ilv_sock = dec->dec_ilv_die = dec->dec_ilv_chan = 0;
+ zen_umc_decode_hash_nps_mod(rule, dec->dec_ilv_pa, hashes);
+
+ if (nsock_bit > 0) {
+ ASSERT3U(nsock_bit, ==, 1);
+ dec->dec_ilv_sock = hashes[0];
+ }
+
+ dec->dec_ilv_chan = bitx64(dec->dec_ilv_pa, 63, 14) % chan_mod;
+ if (hashes[0] == 1) {
+ dec->dec_ilv_chan = (dec->dec_ilv_chan + 1) % chan_mod;
+ }
+
+ /*
+ * Use the remaining hash bits based on the number of channels. There is
+ * nothing else to do for 3/5 channel configs.
+ */
+ switch (rule->ddr_chan_ileave) {
+ case DF_CHAN_ILEAVE_NPS4_3CH:
+ case DF_CHAN_ILEAVE_NPS2_5CH:
+ break;
+ case DF_CHAN_ILEAVE_NPS2_6CH:
+ case DF_CHAN_ILEAVE_NPS1_10CH:
+ dec->dec_ilv_chan += hashes[2] * chan_mod;
+ break;
+ case DF_CHAN_ILEAVE_NPS1_12CH:
+ dec->dec_ilv_chan += ((hashes[2] << 1) | hashes[1]) * chan_mod;
+ break;
+ default:
+ dec->dec_fail = ZEN_UMC_DECODE_F_CHAN_ILEAVE_NOTSUP;
+ dec->dec_fail_data = rule->ddr_chan_ileave;
+ return (B_FALSE);
+ }
+
+ return (B_TRUE);
+}
+
+/*
+ * Our next task is to attempt to translate the PA and the DF rule from a system
+ * address into a normalized address and a particular DRAM channel that it's
+ * targeting. There are several things that we need to take into account here
+ * when performing interleaving and translation:
+ *
+ * o The DRAM Hole modifying our base address
+ * o The various interleave bits
+ * o Potentially hashing based on channel and global settings
+ * o Potential CS re-targeting registers (only on some systems)
+ * o Finally, the question of how to adjust for the DRAM hole and the base
+ * address changes based on the DF generation and channel configuration. This
+ * influences what address we start interleaving with.
+ *
+ * Note, this phase does not actually construct the normalized (e.g. channel)
+ * address. That's done in a subsequent step. For more background, please see
+ * the 'Data Fabric Interleaving' section of the zen_umc.c big theory statement.
+ */
+static boolean_t
+zen_umc_decode_sysaddr_to_csid(const zen_umc_t *umc, zen_umc_decoder_t *dec)
+{
+ uint32_t sock, die, chan, remap_ruleset;
+ const df_dram_rule_t *rule = dec->dec_df_rule;
+ const zen_umc_cs_remap_t *remap;
+
+ /*
+ * First, we must determine what the actual address used for
+ * interleaving is. This varies based on the interleaving and DF
+ * generation.
+ */
+ if (!zen_umc_determine_ileave_addr(umc, dec)) {
+ return (B_FALSE);
+ }
+
+ switch (rule->ddr_chan_ileave) {
+ case DF_CHAN_ILEAVE_1CH:
+ case DF_CHAN_ILEAVE_2CH:
+ case DF_CHAN_ILEAVE_4CH:
+ case DF_CHAN_ILEAVE_8CH:
+ case DF_CHAN_ILEAVE_16CH:
+ case DF_CHAN_ILEAVE_32CH:
+ if (!zen_umc_decode_ileave_nohash(umc, dec)) {
+ return (B_FALSE);
+ }
+ break;
+ case DF_CHAN_ILEAVE_COD4_2CH:
+ case DF_CHAN_ILEAVE_COD2_4CH:
+ case DF_CHAN_ILEAVE_COD1_8CH:
+ if (!zen_umc_decode_ileave_cod(umc, dec)) {
+ return (B_FALSE);
+ }
+ break;
+ case DF_CHAN_ILEAVE_NPS4_2CH:
+ case DF_CHAN_ILEAVE_NPS2_4CH:
+ case DF_CHAN_ILEAVE_NPS1_8CH:
+ if (!zen_umc_decode_ileave_nps(umc, dec)) {
+ return (B_FALSE);
+ }
+ break;
+ case DF_CHAN_ILEAVE_6CH:
+ if (!zen_umc_decode_ileave_zen3_6ch(umc, dec)) {
+ return (B_FALSE);
+ }
+ break;
+ case DF_CHAN_ILEAVE_NPS4_3CH:
+ case DF_CHAN_ILEAVE_NPS2_6CH:
+ case DF_CHAN_ILEAVE_NPS1_12CH:
+ case DF_CHAN_ILEAVE_NPS2_5CH:
+ case DF_CHAN_ILEAVE_NPS1_10CH:
+ if (!zen_umc_decode_ileave_nps_mod(umc, dec)) {
+ return (B_FALSE);
+ }
+ break;
+ default:
+ dec->dec_fail = ZEN_UMC_DECODE_F_CHAN_ILEAVE_NOTSUP;
+ dec->dec_fail_data = rule->ddr_chan_ileave;
+ return (B_FALSE);
+ }
+
+ /*
+ * At this point we have dealt with decoding the interleave into the
+ * logical elements that it contains. We need to transform that back
+ * into a fabric ID, so we can add it to the base fabric ID in our rule.
+ * After that, we need to see if there is any CS remapping going on. If
+ * there is, we will replace the component part of the decomposed fabric
+ * ID. With that done, we can then transform the components back into
+ * our target fabric ID, which indicates which UMC we're after.
+ */
+ zen_fabric_id_compose(&umc->umc_decomp, dec->dec_ilv_sock,
+ dec->dec_ilv_die, dec->dec_ilv_chan, &dec->dec_ilv_fabid);
+ dec->dec_log_fabid = dec->dec_ilv_fabid + rule->ddr_dest_fabid;
+
+ /*
+ * If there's no remapping to do, then we're done. Simply assign the
+ * logical ID as our target.
+ */
+ zen_fabric_id_decompose(&umc->umc_decomp, dec->dec_log_fabid, &sock,
+ &die, &chan);
+ if ((rule->ddr_flags & DF_DRAM_F_REMAP_EN) == 0) {
+ dec->dec_targ_fabid = dec->dec_log_fabid;
+ return (B_TRUE);
+ }
+
+ /*
+ * The DF contains multiple remapping tables. We must figure out which
+ * of these to actually use. There are two different ways that this can
+ * work. The first way is the one added in DFv4 and is used since then.
+ * In that case, the DRAM rule includes both that remapping was enabled
+ * and which of the multiple mapping tables to use.
+ *
+ * This feature also exists prior to DFv4, but only in Milan. In that
+ * world, indicated by the DF_DRAM_F_REMAP_SOCK flag, there is one table
+ * in each DF per-socket. Based on the destination socket from the data
+ * fabric ID, you pick the actual table to use.
+ *
+ * Once the table has been selected, we maintain the socket and die
+ * portions of the fabric ID as constants and replace the component with
+ * the one the remapping table indicates.
+ *
+ * Technically each DF has its own copy of the remapping tables. To make
+ * this work we rely on the following assumption: a given DF node has to
+ * be able to fully route all DRAM rules to a target. That is, a given
+ * DF node doesn't really forward a system address to the remote die for
+ * further interleave processing and therefore we must have enough
+ * information here to map it totally from the same DF that we got the
+ * CCM rules from in the first place, DF 0.
+ */
+ if ((rule->ddr_flags & DF_DRAM_F_REMAP_SOCK) != 0) {
+ remap_ruleset = sock;
+ } else {
+ remap_ruleset = rule->ddr_remap_ent;
+ }
+
+ if (remap_ruleset >= dec->dec_df_rulesrc->zud_cs_nremap) {
+ dec->dec_fail = ZEN_UMC_DECODE_F_BAD_REMAP_SET;
+ dec->dec_fail_data = remap_ruleset;
+ return (B_FALSE);
+ }
+
+ remap = &dec->dec_df_rulesrc->zud_remap[remap_ruleset];
+ if (chan >= remap->csr_nremaps) {
+ dec->dec_fail = ZEN_UMC_DECODE_F_BAD_REMAP_ENTRY;
+ dec->dec_fail_data = chan;
+ return (B_FALSE);
+ }
+
+ dec->dec_remap_comp = remap->csr_remaps[chan];
+ if ((dec->dec_remap_comp & ~umc->umc_decomp.dfd_comp_mask) != 0) {
+ dec->dec_fail = ZEN_UMC_DECODE_F_REMAP_HAS_BAD_COMP;
+ dec->dec_fail_data = dec->dec_remap_comp;
+ return (B_FALSE);
+ }
+
+ zen_fabric_id_compose(&umc->umc_decomp, sock, die, dec->dec_remap_comp,
+ &dec->dec_targ_fabid);
+
+ return (B_TRUE);
+}
+
+/*
+ * Our next step here is to actually take our target ID and find the
+ * corresponding DF, UMC, and actual rule that was used. Note, we don't
+ * decompose the ID and look things up that way for a few reasons. While each
+ * UMC should map linearly to its instance/component ID, there are suggestions
+ * that they can be renumbered. This makes it simplest to just walk over
+ * everything (and there aren't that many things to walk over either).
+ */
+static boolean_t
+zen_umc_decode_find_umc_rule(const zen_umc_t *umc, zen_umc_decoder_t *dec)
+{
+ for (uint_t dfno = 0; dfno < umc->umc_ndfs; dfno++) {
+ const zen_umc_df_t *df = &umc->umc_dfs[dfno];
+ for (uint_t umcno = 0; umcno < df->zud_nchan; umcno++) {
+ const zen_umc_chan_t *chan = &df->zud_chan[umcno];
+
+ if (chan->chan_fabid != dec->dec_targ_fabid) {
+ continue;
+ }
+
+ /*
+ * At this point we have found the UMC that we were
+ * looking for. Snapshot that and then figure out which
+ * rule index of it corresponds to our mapping so we can
+ * properly determine an offset. We will still use the
+ * primary CCM rule for all other calculations.
+ */
+ dec->dec_umc_chan = chan;
+ for (uint32_t ruleno = 0; ruleno < chan->chan_nrules;
+ ruleno++) {
+ const df_dram_rule_t *rule =
+ &chan->chan_rules[ruleno];
+ if ((rule->ddr_flags & DF_DRAM_F_VALID) == 0) {
+ continue;
+ }
+
+ if (dec->dec_pa >= rule->ddr_base &&
+ dec->dec_pa < rule->ddr_limit) {
+ dec->dec_umc_ruleno = ruleno;
+ return (B_TRUE);
+ }
+ }
+
+ dec->dec_fail = ZEN_UMC_DECODE_F_UMC_DOESNT_HAVE_PA;
+ return (B_FALSE);
+ }
+ }
+
+ dec->dec_fail = ZEN_UMC_DECODE_F_CANNOT_MAP_FABID;
+ return (B_FALSE);
+}
+
+/*
+ * Non-hashing interleave modes system address normalization logic. See the
+ * zen_umc.c big theory statement for more information.
+ */
+static boolean_t
+zen_umc_decode_normalize_nohash(const zen_umc_t *umc, zen_umc_decoder_t *dec)
+{
+ uint_t nbits = 0;
+ const df_dram_rule_t *rule = dec->dec_df_rule;
+
+ nbits += rule->ddr_sock_ileave_bits;
+ nbits += rule->ddr_die_ileave_bits;
+ switch (rule->ddr_chan_ileave) {
+ case DF_CHAN_ILEAVE_1CH:
+ break;
+ case DF_CHAN_ILEAVE_2CH:
+ nbits += 1;
+ break;
+ case DF_CHAN_ILEAVE_4CH:
+ nbits += 2;
+ break;
+ case DF_CHAN_ILEAVE_8CH:
+ nbits += 3;
+ break;
+ case DF_CHAN_ILEAVE_16CH:
+ nbits += 4;
+ break;
+ case DF_CHAN_ILEAVE_32CH:
+ nbits += 5;
+ break;
+ default:
+ dec->dec_fail = ZEN_UMC_DECODE_F_CHAN_ILEAVE_NOTSUP;
+ dec->dec_fail_data = rule->ddr_chan_ileave;
+ return (B_FALSE);
+ }
+
+ /*
+ * If we have a really simple configuration (e.g. no interleaving at
+ * all), then make sure that we do not actually do anything here.
+ */
+ if (nbits > 0) {
+ dec->dec_norm_addr = bitdel64(dec->dec_norm_addr,
+ rule->ddr_addr_start + nbits - 1, rule->ddr_addr_start);
+ }
+
+ return (B_TRUE);
+}
+
+/*
+ * COD/NPS system address normalization logic. See the zen_umc.c big theory
+ * statement for more information.
+ */
+static boolean_t
+zen_umc_decode_normalize_hash(const zen_umc_t *umc, zen_umc_decoder_t *dec)
+{
+ uint_t nbits = 0;
+ const df_dram_rule_t *rule = dec->dec_df_rule;
+
+ /*
+ * NPS hashes allow for socket interleaving, COD hashes do not. Add
+ * socket interleaving, skip die.
+ */
+ nbits += rule->ddr_sock_ileave_bits;
+ switch (rule->ddr_chan_ileave) {
+ case DF_CHAN_ILEAVE_COD4_2CH:
+ case DF_CHAN_ILEAVE_NPS4_2CH:
+ nbits += 1;
+ break;
+ case DF_CHAN_ILEAVE_COD2_4CH:
+ case DF_CHAN_ILEAVE_NPS2_4CH:
+ nbits += 2;
+ break;
+ case DF_CHAN_ILEAVE_COD1_8CH:
+ case DF_CHAN_ILEAVE_NPS1_8CH:
+ nbits += 3;
+ break;
+ default:
+ dec->dec_fail = ZEN_UMC_DECODE_F_CHAN_ILEAVE_NOTSUP;
+ dec->dec_fail_data = rule->ddr_chan_ileave;
+ }
+
+ /*
+ * Always remove high order bits before low order bits so we don't have
+ * to adjust the bits we need to remove.
+ */
+ if (nbits > 1) {
+ uint_t start = 12;
+ uint_t end = start + (nbits - 2);
+ dec->dec_norm_addr = bitdel64(dec->dec_norm_addr, end, start);
+ }
+
+ dec->dec_norm_addr = bitdel64(dec->dec_norm_addr, rule->ddr_addr_start,
+ rule->ddr_addr_start);
+ return (B_TRUE);
+}
+
+/*
+ * Now it's time to perform normalization of our favorite interleaving type.
+ * Please see the comments in zen_umc.c on this to understand what we're doing
+ * here and why.
+ */
+static boolean_t
+zen_umc_decode_normalize_zen3_6ch(const zen_umc_t *umc, zen_umc_decoder_t *dec)
+{
+ uint8_t hashes[3] = { 0 };
+ uint_t start, end;
+ const df_dram_rule_t *rule = dec->dec_df_rule;
+
+ /*
+ * As per the theory statement, we always remove the hash bits here from
+ * the starting address. Because this is a 6-channel config, that turns
+ * into 3. Perform the hash again first.
+ */
+ zen_umc_decode_hash_zen3_6ch(rule, dec->dec_norm_addr, hashes);
+ start = rule->ddr_addr_start;
+ end = rule->ddr_addr_start + ZEN_UMC_COD_NBITS - 1;
+ dec->dec_norm_addr = bitdel64(dec->dec_norm_addr, end, start);
+
+ /*
+ * This is the case the theory statement warned about. This gets
+ * normalized to the top of the DIMM's range (its two upper most bits
+ * are set).
+ */
+ if (hashes[1] == 1 && hashes[2] == 1) {
+ uint_t start = 14 - ZEN_UMC_COD_NBITS +
+ dec->dec_umc_chan->chan_np2_space0;
+ dec->dec_norm_addr = bitset64(dec->dec_norm_addr, start + 1,
+ start, 0x3);
+ }
+
+ return (B_TRUE);
+}
+
+/*
+ * Based on the algorithm of sorts described in zen_umc.c, we have a few
+ * different phases of extraction and combination. This isn't quite like the
+ * others where we simply delete bits.
+ */
+static boolean_t
+zen_umc_decode_normalize_nps_mod(const zen_umc_t *umc, zen_umc_decoder_t *dec)
+{
+ uint64_t low, high, mid;
+ uint_t nbits, chan_mod, sock_bits, nmid_bits;
+ uint_t mid_start, mid_end;
+ uint8_t hashes[3] = { 0 };
+ const df_dram_rule_t *rule = dec->dec_df_rule;
+
+ sock_bits = rule->ddr_sock_ileave_bits;
+ switch (rule->ddr_chan_ileave) {
+ case DF_CHAN_ILEAVE_NPS4_3CH:
+ chan_mod = 3;
+ nbits = 1;
+ break;
+ case DF_CHAN_ILEAVE_NPS2_5CH:
+ chan_mod = 5;
+ nbits = 1;
+ break;
+ case DF_CHAN_ILEAVE_NPS2_6CH:
+ chan_mod = 3;
+ nbits = 2;
+ break;
+ case DF_CHAN_ILEAVE_NPS1_10CH:
+ chan_mod = 5;
+ nbits = 2;
+ break;
+ case DF_CHAN_ILEAVE_NPS1_12CH:
+ chan_mod = 3;
+ nbits = 3;
+ break;
+ default:
+ dec->dec_fail = ZEN_UMC_DECODE_F_CHAN_ILEAVE_NOTSUP;
+ dec->dec_fail_data = rule->ddr_chan_ileave;
+ return (B_FALSE);
+ }
+
+ /*
+ * First extract the low bit range that we're using which is everything
+ * below the starting interleave address. We also always extract the
+ * high bits, which are always [63:14] and divide it by the modulus.
+ * Note, we apply the hash after any such division if needed. It becomes
+ * the new least significant bit.
+ */
+ low = bitx64(dec->dec_norm_addr, rule->ddr_addr_start - 1, 0);
+ high = bitx64(dec->dec_norm_addr, 63, 14) / chan_mod;
+ zen_umc_decode_hash_nps_mod(rule, dec->dec_norm_addr, hashes);
+ if (sock_bits == 0) {
+ high = (high << 1) | hashes[0];
+ }
+
+ /*
+ * Now for the weirdest bit here, extracting the middle bits. Recall
+ * this hash uses bit 8, then 13, then 12 (the hash order is still 8,
+ * 12, 13, but it uses the hashes[2] before hashes[1] in
+ * zen_umc_decode_ileave_nps_mod()). So if we're only using 1 interleave
+ * bit, we just remove bit 8 (assuming that is our starting address) and
+ * our range is [13:9]. If we're using two, our range becomes [12:9],
+ * and if three, [11:9]. The 6 - nbits below comes from the fact that in
+ * a 1 bit interleave we have 5 bits. Because our mid_start/mid_end
+ * range is inclusive, we subtract one at the end from mid_end.
+ */
+ nmid_bits = 6 - nbits;
+ mid_start = rule->ddr_addr_start + 1;
+ mid_end = mid_start + nmid_bits - 1;
+ mid = bitx64(dec->dec_norm_addr, mid_end, mid_start);
+
+ /*
+ * Because we've been removing bits, we don't use any of the start and
+ * ending ranges we calculated above for shifts, as that was what we
+ * needed from the original address.
+ */
+ dec->dec_norm_addr = low | (mid << rule->ddr_addr_start) | (high <<
+ (rule->ddr_addr_start + nmid_bits));
+
+ return (B_TRUE);
+}
+
+/*
+ * Now we need to go through and try to construct a normalized address using all
+ * the information that we've gathered to date. To do this we need to take into
+ * account all of the following transformations on the address that need to
+ * occur. We apply modifications to the address in the following order:
+ *
+ * o The base address of the rule
+ * o DRAM hole changes
+ * o Normalization of the address due to interleaving (more fun)
+ * o The DRAM offset register of the rule
+ */
+static boolean_t
+zen_umc_decode_sysaddr_to_norm(const zen_umc_t *umc, zen_umc_decoder_t *dec)
+{
+ const zen_umc_chan_t *chan = dec->dec_umc_chan;
+ const df_dram_rule_t *rule = dec->dec_df_rule;
+
+ dec->dec_norm_addr = dec->dec_pa;
+ if (!zen_umc_adjust_dram_addr(umc, dec, &dec->dec_norm_addr,
+ ZEN_UMC_DECODE_F_CALC_NORM_UNDERFLOW)) {
+ return (B_FALSE);
+ }
+
+ /*
+ * Now for the most annoying part of this whole thing, normalizing based
+ * on our actual interleave format. The reason for this is that when
+ * interleaving is going on, it actually is removing bits that are just
+ * being used to direct it somewhere; however, it's actually generally
+ * speaking the same value in each location. See the big theory
+ * statement in zen_umc.c for more information.
+ */
+ switch (rule->ddr_chan_ileave) {
+ case DF_CHAN_ILEAVE_1CH:
+ case DF_CHAN_ILEAVE_2CH:
+ case DF_CHAN_ILEAVE_4CH:
+ case DF_CHAN_ILEAVE_8CH:
+ case DF_CHAN_ILEAVE_16CH:
+ case DF_CHAN_ILEAVE_32CH:
+ if (!zen_umc_decode_normalize_nohash(umc, dec)) {
+ return (B_FALSE);
+ }
+ break;
+ case DF_CHAN_ILEAVE_COD4_2CH:
+ case DF_CHAN_ILEAVE_COD2_4CH:
+ case DF_CHAN_ILEAVE_COD1_8CH:
+ case DF_CHAN_ILEAVE_NPS4_2CH:
+ case DF_CHAN_ILEAVE_NPS2_4CH:
+ case DF_CHAN_ILEAVE_NPS1_8CH:
+ if (!zen_umc_decode_normalize_hash(umc, dec)) {
+ return (B_FALSE);
+ }
+ break;
+ case DF_CHAN_ILEAVE_6CH:
+ if (!zen_umc_decode_normalize_zen3_6ch(umc, dec)) {
+ return (B_FALSE);
+ }
+ break;
+ case DF_CHAN_ILEAVE_NPS4_3CH:
+ case DF_CHAN_ILEAVE_NPS2_6CH:
+ case DF_CHAN_ILEAVE_NPS1_12CH:
+ case DF_CHAN_ILEAVE_NPS2_5CH:
+ case DF_CHAN_ILEAVE_NPS1_10CH:
+ if (!zen_umc_decode_normalize_nps_mod(umc, dec)) {
+ return (B_FALSE);
+ }
+ break;
+ default:
+ dec->dec_fail = ZEN_UMC_DECODE_F_CHAN_ILEAVE_NOTSUP;
+ dec->dec_fail_data = rule->ddr_chan_ileave;
+ return (B_FALSE);
+ }
+
+ /*
+ * Determine if this rule has an offset to apply. Note, there is never
+ * an offset for rule 0, hence the index into this is one less than the
+ * actual rule number. Unlike other transformations these offsets
+ * describe the start of a normalized range. Therefore we need to
+ * actually add this value instead of subtract.
+ */
+ if (dec->dec_umc_ruleno > 0) {
+ uint32_t offno = dec->dec_umc_ruleno - 1;
+ const chan_offset_t *offset = &chan->chan_offsets[offno];
+
+ if (offset->cho_valid) {
+ dec->dec_norm_addr += offset->cho_offset;
+ }
+ }
+
+ return (B_TRUE);
+}
+
+/*
+ * This applies the formula that determines a chip-select actually matches which
+ * is defined as (address & ~mask) == (base & ~mask) in the PPR. There is both a
+ * primary and secondary mask here. We need to pay attention to which is used
+ * (if any) for later on.
+ */
+static boolean_t
+zen_umc_decoder_cs_matches(const umc_cs_t *cs, const uint64_t norm,
+ boolean_t *matched_sec)
+{
+ if (cs->ucs_base.udb_valid != 0) {
+ uint64_t imask = ~cs->ucs_base_mask;
+ if ((norm & imask) == (cs->ucs_base.udb_base & imask)) {
+ *matched_sec = B_FALSE;
+ return (B_TRUE);
+ }
+ }
+
+ if (cs->ucs_sec.udb_valid != 0) {
+ uint64_t imask = ~cs->ucs_sec_mask;
+ if ((norm & imask) == (cs->ucs_sec.udb_base & imask)) {
+ *matched_sec = B_TRUE;
+ return (B_TRUE);
+ }
+ }
+
+ return (B_FALSE);
+}
+
+/*
+ * Go through with our normalized address and map it to a given chip-select.
+ * This as a side effect indicates which DIMM we're going out on as well. Note,
+ * the final DIMM can change due to chip-select hashing; however, we use this
+ * DIMM for determining all of the actual address translations.
+ */
+static boolean_t
+zen_umc_decode_find_cs(const zen_umc_t *umc, zen_umc_decoder_t *dec)
+{
+ const zen_umc_chan_t *chan = dec->dec_umc_chan;
+
+ for (uint_t dimmno = 0; dimmno < ZEN_UMC_MAX_DIMMS; dimmno++) {
+ const umc_dimm_t *dimm = &chan->chan_dimms[dimmno];
+
+ if ((dimm->ud_flags & UMC_DIMM_F_VALID) == 0)
+ continue;
+
+ for (uint_t csno = 0; csno < ZEN_UMC_MAX_CS_PER_DIMM; csno++) {
+ const umc_cs_t *cs = &dimm->ud_cs[csno];
+ boolean_t is_sec = B_FALSE;
+
+ if (zen_umc_decoder_cs_matches(cs, dec->dec_norm_addr,
+ &is_sec)) {
+ dec->dec_dimm = dimm;
+ dec->dec_cs = cs;
+ dec->dec_log_csno = dimmno * ZEN_UMC_MAX_DIMMS +
+ csno;
+ dec->dec_cs_sec = is_sec;
+ return (B_TRUE);
+ }
+ }
+ }
+
+ dec->dec_fail = ZEN_UMC_DECODE_F_NO_CS_BASE_MATCH;
+ return (B_FALSE);
+}
+
+/*
+ * Extract the column from the address. For once, something that is almost
+ * straightforward.
+ */
+static boolean_t
+zen_umc_decode_cols(const zen_umc_t *umc, zen_umc_decoder_t *dec)
+{
+ uint32_t cols = 0;
+ const umc_cs_t *cs = dec->dec_cs;
+
+ for (uint_t i = 0; i < cs->ucs_ncol; i++) {
+ uint32_t index;
+
+ index = cs->ucs_col_bits[i];
+ cols |= bitx64(dec->dec_norm_addr, index, index) << i;
+ }
+
+ dec->dec_dimm_col = cols;
+ return (B_TRUE);
+}
+
+/*
+ * The row is split into two different regions. There's a low and high value,
+ * though the high value is only present in DDR4. Unlike the column, where each
+ * bit is spelled out, each set of row bits are contiguous (low and high are
+ * independent).
+ */
+static boolean_t
+zen_umc_decode_rows(const zen_umc_t *umc, zen_umc_decoder_t *dec)
+{
+ uint32_t row = 0;
+ uint8_t inv;
+ const umc_cs_t *cs = dec->dec_cs;
+ const uint_t total_bits = cs->ucs_nrow_lo + cs->ucs_nrow_hi;
+ const uint_t lo_end = cs->ucs_nrow_lo + cs->ucs_row_low_bit - 1;
+
+ row = bitx64(dec->dec_norm_addr, lo_end, cs->ucs_row_low_bit);
+ if (cs->ucs_nrow_hi > 0) {
+ const uint_t hi_end = cs->ucs_nrow_hi + cs->ucs_row_hi_bit - 1;
+ const uint32_t hi = bitx64(dec->dec_norm_addr, hi_end,
+ cs->ucs_row_hi_bit);
+
+ row |= hi << cs->ucs_nrow_lo;
+ }
+
+ if (dec->dec_cs_sec) {
+ inv = cs->ucs_inv_msbs_sec;
+ } else {
+ inv = cs->ucs_inv_msbs;
+ }
+
+ /*
+ * We need to potentially invert the top two bits of the row address
+ * based on the low two bits of the inverted register below. Note, inv
+ * only has two valid bits below. So we shift them into place to perform
+ * the XOR. See the big theory statement in zen_umc.c for more on why
+ * this works.
+ */
+ inv = inv << (total_bits - 2);
+ row = row ^ inv;
+
+ dec->dec_dimm_row = row;
+ return (B_TRUE);
+}
+
+/*
+ * Several of the hash schemes ask us to go through and xor all the bits that
+ * are in an address to transform it into a single bit. This implements that for
+ * a uint32_t. This is basically a bitwise XOR reduce.
+ */
+static uint8_t
+zen_umc_running_xor32(const uint32_t in)
+{
+ uint8_t run = 0;
+
+ for (uint_t i = 0; i < sizeof (in) * NBBY; i++) {
+ run ^= bitx32(in, i, i);
+ }
+
+ return (run);
+}
+
+static uint8_t
+zen_umc_running_xor64(const uint64_t in)
+{
+ uint8_t run = 0;
+
+ for (uint_t i = 0; i < sizeof (in) * NBBY; i++) {
+ run ^= bitx64(in, i, i);
+ }
+
+ return (run);
+}
+
+/*
+ * Our goal here is to extract the number of banks and bank groups that are
+ * used, if any.
+ */
+static boolean_t
+zen_umc_decode_banks(const zen_umc_t *umc, zen_umc_decoder_t *dec)
+{
+ uint8_t bank = 0;
+ const umc_cs_t *cs = dec->dec_cs;
+ const umc_chan_hash_t *hash = &dec->dec_umc_chan->chan_hash;
+
+ /*
+ * Get an initial bank address bit and then perform any hashing if
+ * bank hashing is enabled. Note, the memory controller's nbanks is the
+ * total number of bank and bank group bits, hence why it's used for
+ * the loop counter.
+ */
+ for (uint_t i = 0; i < cs->ucs_nbanks; i++) {
+ uint32_t row_hash, col_hash;
+ uint8_t row_xor, col_xor;
+ uint_t targ = cs->ucs_bank_bits[i];
+ uint8_t val = bitx64(dec->dec_norm_addr, targ, targ);
+ const umc_bank_hash_t *bank_hash = &hash->uch_bank_hashes[i];
+
+ if ((hash->uch_flags & UMC_CHAN_HASH_F_BANK) == 0 ||
+ !hash->uch_bank_hashes[i].ubh_en) {
+ bank |= val << i;
+ continue;
+ }
+
+ /*
+ * See the big theory statement for more on this. Short form,
+ * bit-wise AND the row and column, then XOR shenanigans.
+ */
+ row_hash = dec->dec_dimm_row & bank_hash->ubh_row_xor;
+ col_hash = dec->dec_dimm_col & bank_hash->ubh_col_xor;
+ row_xor = zen_umc_running_xor32(row_hash);
+ col_xor = zen_umc_running_xor32(col_hash);
+ bank |= (row_xor ^ col_xor ^ val) << i;
+ }
+
+ /*
+ * The bank and bank group are conjoined in the register and bit
+ * definitions. Once we've calculated that, extract it.
+ */
+ dec->dec_dimm_bank_group = bitx8(bank, cs->ucs_nbank_groups - 1, 0);
+ dec->dec_dimm_bank = bitx8(bank, cs->ucs_nbanks, cs->ucs_nbank_groups);
+ return (B_TRUE);
+}
+
+/*
+ * Extract the sub-channel. If not a DDR5 based device, simply set it to zero
+ * and return. We can't forget to hash this if required.
+ */
+static boolean_t
+zen_umc_decode_subchan(const zen_umc_t *umc, zen_umc_decoder_t *dec)
+{
+ uint8_t subchan;
+ uint32_t row_hash, col_hash, bank_hash;
+ uint8_t row_xor, col_xor, bank_xor;
+ const umc_cs_t *cs = dec->dec_cs;
+ const umc_chan_hash_t *hash = &dec->dec_umc_chan->chan_hash;
+
+ switch (dec->dec_dimm->ud_type) {
+ case UMC_DIMM_T_DDR5:
+ case UMC_DIMM_T_LPDDR5:
+ break;
+ default:
+ dec->dec_dimm_subchan = 0;
+ return (B_TRUE);
+ }
+
+ subchan = bitx64(dec->dec_norm_addr, cs->ucs_subchan, cs->ucs_subchan);
+ if ((hash->uch_flags & UMC_CHAN_HASH_F_PC) == 0 ||
+ !hash->uch_pc_hash.uph_en) {
+ dec->dec_dimm_subchan = subchan;
+ return (B_TRUE);
+ }
+
+ row_hash = dec->dec_dimm_row & hash->uch_pc_hash.uph_row_xor;
+ col_hash = dec->dec_dimm_col & hash->uch_pc_hash.uph_col_xor;
+ bank_hash = dec->dec_dimm_bank & hash->uch_pc_hash.uph_bank_xor;
+ row_xor = zen_umc_running_xor32(row_hash);
+ col_xor = zen_umc_running_xor32(col_hash);
+ bank_xor = zen_umc_running_xor32(bank_hash);
+
+ dec->dec_dimm_subchan = subchan ^ row_xor ^ col_xor ^ bank_xor;
+ return (B_TRUE);
+}
+
+/*
+ * Note that we have normalized the RM bits between the primary and secondary
+ * base/mask registers so that way even though the DDR5 controller always uses
+ * the same RM selection bits, it works in a uniform way for both DDR4 and DDR5.
+ */
+static boolean_t
+zen_umc_decode_rank_mul(const zen_umc_t *umc, zen_umc_decoder_t *dec)
+{
+ uint8_t rm = 0;
+ const umc_cs_t *cs = dec->dec_cs;
+ const umc_chan_hash_t *hash = &dec->dec_umc_chan->chan_hash;
+
+ for (uint_t i = 0; i < cs->ucs_nrm; i++) {
+ uint8_t index = cs->ucs_rm_bits[i];
+ uint8_t bit = bitx64(dec->dec_norm_addr, index, index);
+
+ if ((hash->uch_flags & UMC_CHAN_HASH_F_RM) != 0 &&
+ hash->uch_rm_hashes[i].uah_en) {
+ uint64_t norm_mask = dec->dec_norm_addr &
+ hash->uch_rm_hashes[i].uah_addr_xor;
+ uint8_t norm_hash = zen_umc_running_xor64(norm_mask);
+ bit = bit ^ norm_hash;
+ }
+
+ rm |= bit << i;
+ }
+
+ dec->dec_dimm_rm = rm;
+ return (B_TRUE);
+}
+
+/*
+ * Go through and determine the actual chip-select activated. This is subject to
+ * hashing. Note, we first constructed a logical chip-select value based on
+ * which of the four base/mask registers in the UMC we activated for the
+ * channel. That basically seeded the two bit value we start with.
+ */
+static boolean_t
+zen_umc_decode_chipsel(const zen_umc_t *umc, zen_umc_decoder_t *dec)
+{
+ uint8_t csno = 0;
+ const umc_cs_t *cs = dec->dec_cs;
+ const umc_chan_hash_t *hash = &dec->dec_umc_chan->chan_hash;
+
+ for (uint_t i = 0; i < ZEN_UMC_MAX_CS_BITS; i++) {
+ uint8_t bit = bitx8(dec->dec_log_csno, i, i);
+ if ((hash->uch_flags & UMC_CHAN_HASH_F_CS) != 0 &&
+ hash->uch_cs_hashes[i].uah_en) {
+ uint64_t mask = dec->dec_norm_addr &
+ hash->uch_cs_hashes[i].uah_addr_xor;
+ uint8_t rxor = zen_umc_running_xor64(mask);
+ bit = bit ^ rxor;
+ }
+ csno |= bit << i;
+ }
+
+ /*
+ * It is not entirely clear what the circumstances are that we need to
+ * apply the chip-select xor. Right now we always apply it. This only
+ * exists on a few DDR5 SoCs, it seems, and we zero out other cases to
+ * try and have a uniform and reasonable path. This tells us what the
+ * absolute chip-select is in the channel. We record this for debugging
+ * purposes and to derive the DIMM and CS.
+ */
+ dec->dec_chan_csno = (csno ^ cs->ucs_cs_xor) & 0x3;
+
+ /*
+ * Now that we actually know which chip-select we're targeting, go back
+ * and actual indicate which DIMM we'll go out to and what chip-select
+ * it is relative to the DIMM. This may have changed out due to CS
+ * hashing. As such we have to now snapshot our final DIMM and
+ * chip-select.
+ */
+ dec->dec_dimm_no = dec->dec_chan_csno >> 1;
+ dec->dec_dimm_csno = dec->dec_chan_csno % 2;
+ return (B_TRUE);
+}
+
+/*
+ * Initialize the decoder state. We do this by first zeroing it all and then
+ * setting various result addresses to the UINTXX_MAX that is appropriate. These
+ * work as better sentinel values than zero; however, we always zero the
+ * structure to be defensive, cover pointers, etc.
+ */
+static void
+zen_umc_decoder_init(zen_umc_decoder_t *dec)
+{
+ bzero(dec, sizeof (*dec));
+
+ dec->dec_pa = dec->dec_ilv_pa = UINT64_MAX;
+ dec->dec_df_ruleno = UINT32_MAX;
+ dec->dec_ilv_sock = dec->dec_ilv_die = dec->dec_ilv_chan =
+ dec->dec_ilv_fabid = dec->dec_log_fabid = dec->dec_remap_comp =
+ dec->dec_targ_fabid = UINT32_MAX;
+ dec->dec_umc_ruleno = UINT32_MAX;
+ dec->dec_norm_addr = UINT64_MAX;
+ dec->dec_dimm_col = dec->dec_dimm_row = UINT32_MAX;
+ dec->dec_log_csno = dec->dec_dimm_bank = dec->dec_dimm_bank_group =
+ dec->dec_dimm_subchan = dec->dec_dimm_rm = dec->dec_chan_csno =
+ dec->dec_dimm_no = dec->dec_dimm_csno = UINT8_MAX;
+}
+
+boolean_t
+zen_umc_decode_pa(const zen_umc_t *umc, const uint64_t pa,
+ zen_umc_decoder_t *dec)
+{
+ zen_umc_decoder_init(dec);
+ dec->dec_pa = pa;
+
+ /*
+ * Before we proceed through decoding, the first thing we should try to
+ * do is verify that this is even something that could be DRAM.
+ */
+ if (!zen_umc_decode_is_dram(umc, dec)) {
+ ASSERT3U(dec->dec_fail, !=, ZEN_UMC_DECODE_F_NONE);
+ return (B_FALSE);
+ }
+
+ /*
+ * The very first thing that we need to do is find a data fabric rule
+ * that corresponds to this memory address. This will be used to
+ * determine which set of rules for interleave and related we actually
+ * should then use.
+ */
+ if (!zen_umc_decode_find_df_rule(umc, dec)) {
+ ASSERT3U(dec->dec_fail, !=, ZEN_UMC_DECODE_F_NONE);
+ return (B_FALSE);
+ }
+
+ /*
+ * Now that we have a DF rule, we must take a more involved step of
+ * mapping to a given CS, e.g. a specific UMC channel. This will tell us
+ * the socket and die as well. This takes care of all the interleaving
+ * and remapping and produces a target fabric ID.
+ */
+ if (!zen_umc_decode_sysaddr_to_csid(umc, dec)) {
+ ASSERT3U(dec->dec_fail, !=, ZEN_UMC_DECODE_F_NONE);
+ return (B_FALSE);
+ }
+
+ /*
+ * With that target ID known, now actually map this to a corresponding
+ * UMC.
+ */
+ if (!zen_umc_decode_find_umc_rule(umc, dec)) {
+ ASSERT3U(dec->dec_fail, !=, ZEN_UMC_DECODE_F_NONE);
+ return (B_FALSE);
+ }
+
+ /*
+ * With the target and corresponding rules and offset information,
+ * actually perform normalization.
+ */
+ if (!zen_umc_decode_sysaddr_to_norm(umc, dec)) {
+ ASSERT3U(dec->dec_fail, !=, ZEN_UMC_DECODE_F_NONE);
+ return (B_FALSE);
+ }
+
+ /*
+ * Finally, we somehow managed to actually construct a normalized
+ * address. Now we must begin the act of transforming this channel
+ * address into something that makes sense to address a DIMM. To start
+ * with determine which logical chip-select, which determines where we
+ * source all our data to use.
+ */
+ if (!zen_umc_decode_find_cs(umc, dec)) {
+ ASSERT3U(dec->dec_fail, !=, ZEN_UMC_DECODE_F_NONE);
+ return (B_FALSE);
+ }
+
+ /*
+ * Now that we have the logical chip-select matched that we're sourcing
+ * our data from, the next this is a bit more involved: we need to
+ * extract the row, column, rank/rank multiplication, bank, and bank
+ * group out of all this, while taking into account all of our hashes.
+ *
+ * To do this, we begin by first calculating the row and column as those
+ * will be needed to determine some of our other values here.
+ */
+ if (!zen_umc_decode_rows(umc, dec)) {
+ ASSERT3U(dec->dec_fail, !=, ZEN_UMC_DECODE_F_NONE);
+ return (B_FALSE);
+ }
+
+ if (!zen_umc_decode_cols(umc, dec)) {
+ ASSERT3U(dec->dec_fail, !=, ZEN_UMC_DECODE_F_NONE);
+ return (B_FALSE);
+ }
+
+ /*
+ * Now that we have the rows and columns we can go through and determine
+ * the bank and bank group. This depends on the above.
+ */
+ if (!zen_umc_decode_banks(umc, dec)) {
+ ASSERT3U(dec->dec_fail, !=, ZEN_UMC_DECODE_F_NONE);
+ return (B_FALSE);
+ }
+
+ /*
+ * If we have a DDR5 generation DIMM then we need to consider the
+ * subchannel. This doesn't exist in DDR4 systems (the function handles
+ * this reality). Because of potential hashing, this needs to come after
+ * the row, column, and bank have all been determined.
+ */
+ if (!zen_umc_decode_subchan(umc, dec)) {
+ ASSERT3U(dec->dec_fail, !=, ZEN_UMC_DECODE_F_NONE);
+ return (B_FALSE);
+ }
+
+ /*
+ * Time for the last two pieces here: the actual chip select used and
+ * then figuring out which rank, taking into account rank
+ * multiplication. Don't worry, these both have hashing opportunities.
+ */
+ if (!zen_umc_decode_rank_mul(umc, dec)) {
+ ASSERT3U(dec->dec_fail, !=, ZEN_UMC_DECODE_F_NONE);
+ return (B_FALSE);
+ }
+
+ if (!zen_umc_decode_chipsel(umc, dec)) {
+ ASSERT3U(dec->dec_fail, !=, ZEN_UMC_DECODE_F_NONE);
+ return (B_FALSE);
+ }
+
+ /*
+ * Somehow, that's it.
+ */
+ return (B_TRUE);
+}
diff --git a/usr/src/common/mc/zen_umc/zen_umc_dump.c b/usr/src/common/mc/zen_umc/zen_umc_dump.c
new file mode 100644
index 0000000000..da3c2cc095
--- /dev/null
+++ b/usr/src/common/mc/zen_umc/zen_umc_dump.c
@@ -0,0 +1,717 @@
+/*
+ * This file and its contents are supplied under the terms of the
+ * Common Development and Distribution License ("CDDL"), version 1.0.
+ * You may only use this file in accordance with the terms of version
+ * 1.0 of the CDDL.
+ *
+ * A full copy of the text of the CDDL should have accompanied this
+ * source. A copy of the CDDL is also available via the Internet at
+ * http://www.illumos.org/license/CDDL.
+ */
+
+/*
+ * Copyright 2022 Oxide Computer Company
+ */
+
+/*
+ * Dump and restore logic for external processing. Dump generally runs in kernel
+ * context from a well formed structure created by the driver. Restore is used
+ * in userland as part of testing and related.
+ *
+ * Note, there are a lot of fields in these structures that are not serialized
+ * because they are not used as part of the decoder (e.g. the various raw values
+ * which are captured to aid future debugging).
+ */
+
+#include "zen_umc.h"
+#ifndef _KERNEL
+#include <string.h>
+#include <strings.h>
+#include <libnvpair.h>
+#endif
+
+static nvlist_t *
+zen_umc_dump_dram_rule(df_dram_rule_t *rule)
+{
+ nvlist_t *nvl;
+
+ nvl = fnvlist_alloc();
+ fnvlist_add_uint32(nvl, "ddr_flags", rule->ddr_flags);
+ fnvlist_add_uint64(nvl, "ddr_base", rule->ddr_base);
+ fnvlist_add_uint64(nvl, "ddr_limit", rule->ddr_limit);
+ fnvlist_add_uint16(nvl, "ddr_dest_fabid", rule->ddr_dest_fabid);
+ fnvlist_add_uint8(nvl, "ddr_sock_ileave_bits",
+ rule->ddr_sock_ileave_bits);
+ fnvlist_add_uint8(nvl, "ddr_die_ileave_bits",
+ rule->ddr_die_ileave_bits);
+ fnvlist_add_uint8(nvl, "ddr_addr_start", rule->ddr_addr_start);
+ fnvlist_add_uint8(nvl, "ddr_remap_ent", rule->ddr_remap_ent);
+ fnvlist_add_uint32(nvl, "ddr_chan_ileave", rule->ddr_chan_ileave);
+
+ return (nvl);
+}
+
+static nvlist_t *
+zen_umc_dump_cs(umc_cs_t *cs)
+{
+ nvlist_t *nvl = fnvlist_alloc();
+ nvlist_t *base = fnvlist_alloc();
+ nvlist_t *sec = fnvlist_alloc();
+
+ fnvlist_add_uint64(base, "udb_base", cs->ucs_base.udb_base);
+ fnvlist_add_uint8(base, "udb_valid", cs->ucs_base.udb_valid);
+ fnvlist_add_nvlist(nvl, "ucs_base", base);
+ nvlist_free(base);
+ fnvlist_add_uint64(sec, "udb_base", cs->ucs_sec.udb_base);
+ fnvlist_add_uint8(sec, "udb_valid", cs->ucs_sec.udb_valid);
+ fnvlist_add_nvlist(nvl, "ucs_sec", sec);
+ nvlist_free(sec);
+ fnvlist_add_uint64(nvl, "ucs_base_mask", cs->ucs_base_mask);
+ fnvlist_add_uint64(nvl, "ucs_sec_mask", cs->ucs_sec_mask);
+ fnvlist_add_uint8(nvl, "ucs_nrow_lo", cs->ucs_nrow_lo);
+ fnvlist_add_uint8(nvl, "ucs_nrow_hi", cs->ucs_nrow_hi);
+ fnvlist_add_uint8(nvl, "ucs_nbank_groups", cs->ucs_nbank_groups);
+ fnvlist_add_uint8(nvl, "ucs_cs_xor", cs->ucs_cs_xor);
+ fnvlist_add_uint8(nvl, "ucs_row_hi_bit", cs->ucs_row_hi_bit);
+ fnvlist_add_uint8(nvl, "ucs_row_low_bit", cs->ucs_row_low_bit);
+ fnvlist_add_uint8_array(nvl, "ucs_bank_bits", cs->ucs_bank_bits,
+ cs->ucs_nbanks);
+ fnvlist_add_uint8_array(nvl, "ucs_col_bits", cs->ucs_col_bits,
+ cs->ucs_ncol);
+ fnvlist_add_uint8(nvl, "ucs_inv_msbs", cs->ucs_inv_msbs);
+ fnvlist_add_uint8_array(nvl, "ucs_rm_bits", cs->ucs_rm_bits,
+ cs->ucs_nrm);
+ fnvlist_add_uint8(nvl, "ucs_inv_msbs_sec", cs->ucs_inv_msbs_sec);
+ fnvlist_add_uint8_array(nvl, "ucs_rm_bits_sec", cs->ucs_rm_bits_sec,
+ cs->ucs_nrm);
+ fnvlist_add_uint8(nvl, "ucs_subchan", cs->ucs_subchan);
+
+ return (nvl);
+}
+
+static nvlist_t *
+zen_umc_dump_dimm(umc_dimm_t *dimm)
+{
+ nvlist_t *nvl = fnvlist_alloc();
+ nvlist_t *cs[ZEN_UMC_MAX_CS_PER_DIMM];
+
+ fnvlist_add_uint32(nvl, "ud_flags", dimm->ud_flags);
+ fnvlist_add_uint32(nvl, "ud_width", dimm->ud_width);
+ fnvlist_add_uint32(nvl, "ud_type", dimm->ud_type);
+ fnvlist_add_uint32(nvl, "ud_kind", dimm->ud_kind);
+ fnvlist_add_uint32(nvl, "ud_dimmno", dimm->ud_dimmno);
+
+ for (uint_t i = 0; i < ZEN_UMC_MAX_CS_PER_DIMM; i++) {
+ cs[i] = zen_umc_dump_cs(&dimm->ud_cs[i]);
+ }
+ fnvlist_add_nvlist_array(nvl, "ud_cs", cs, ZEN_UMC_MAX_CS_PER_DIMM);
+ for (uint_t i = 0; i < ZEN_UMC_MAX_CS_PER_DIMM; i++) {
+ nvlist_free(cs[i]);
+ }
+
+ return (nvl);
+}
+
+static nvlist_t *
+zen_umc_dump_chan_hash(umc_chan_hash_t *hash)
+{
+ nvlist_t *nvl = fnvlist_alloc();
+
+ fnvlist_add_uint32(nvl, "uch_flags", hash->uch_flags);
+
+ if (hash->uch_flags & UMC_CHAN_HASH_F_BANK) {
+ nvlist_t *banks[ZEN_UMC_MAX_CHAN_BANK_HASH];
+ for (uint_t i = 0; i < ZEN_UMC_MAX_CHAN_BANK_HASH; i++) {
+ banks[i] = fnvlist_alloc();
+
+ fnvlist_add_uint32(banks[i], "ubh_row_xor",
+ hash->uch_bank_hashes[i].ubh_row_xor);
+ fnvlist_add_uint32(banks[i], "ubh_col_xor",
+ hash->uch_bank_hashes[i].ubh_col_xor);
+ fnvlist_add_boolean_value(banks[i], "ubh_en",
+ hash->uch_bank_hashes[i].ubh_en);
+ }
+ fnvlist_add_nvlist_array(nvl, "uch_bank_hashes", banks,
+ ZEN_UMC_MAX_CHAN_BANK_HASH);
+
+ for (uint_t i = 0; i < ZEN_UMC_MAX_CHAN_BANK_HASH; i++) {
+ nvlist_free(banks[i]);
+ }
+ }
+
+ if (hash->uch_flags & UMC_CHAN_HASH_F_RM) {
+ nvlist_t *rm[ZEN_UMC_MAX_CHAN_RM_HASH];
+ for (uint_t i = 0; i < ZEN_UMC_MAX_CHAN_RM_HASH; i++) {
+ rm[i] = fnvlist_alloc();
+
+ fnvlist_add_uint64(rm[i], "uah_addr_xor",
+ hash->uch_rm_hashes[i].uah_addr_xor);
+ fnvlist_add_boolean_value(rm[i], "uah_en",
+ hash->uch_rm_hashes[i].uah_en);
+ }
+ fnvlist_add_nvlist_array(nvl, "uch_rm_hashes", rm,
+ ZEN_UMC_MAX_CHAN_RM_HASH);
+
+ for (uint_t i = 0; i < ZEN_UMC_MAX_CHAN_RM_HASH; i++) {
+ nvlist_free(rm[i]);
+ }
+ }
+
+ if (hash->uch_flags & UMC_CHAN_HASH_F_CS) {
+ nvlist_t *cs[ZEN_UMC_MAX_CHAN_CS_HASH];
+ for (uint_t i = 0; i < ZEN_UMC_MAX_CHAN_CS_HASH; i++) {
+ cs[i] = fnvlist_alloc();
+
+ fnvlist_add_uint64(cs[i], "uah_addr_xor",
+ hash->uch_rm_hashes[i].uah_addr_xor);
+ fnvlist_add_boolean_value(cs[i], "uah_en",
+ hash->uch_rm_hashes[i].uah_en);
+ }
+ fnvlist_add_nvlist_array(nvl, "uch_cs_hashes", cs,
+ ZEN_UMC_MAX_CHAN_CS_HASH);
+
+ for (uint_t i = 0; i < ZEN_UMC_MAX_CHAN_CS_HASH; i++) {
+ nvlist_free(cs[i]);
+ }
+ }
+
+ if (hash->uch_flags & UMC_CHAN_HASH_F_PC) {
+ nvlist_t *pc = fnvlist_alloc();
+
+ fnvlist_add_uint32(pc, "uph_row_xor",
+ hash->uch_pc_hash.uph_row_xor);
+ fnvlist_add_uint32(pc, "uph_col_xor",
+ hash->uch_pc_hash.uph_col_xor);
+ fnvlist_add_uint8(pc, "uph_bank_xor",
+ hash->uch_pc_hash.uph_bank_xor);
+ fnvlist_add_boolean_value(pc, "uph_en",
+ hash->uch_pc_hash.uph_en);
+
+ fnvlist_add_nvlist(nvl, "uch_pch_hash", pc);
+ fnvlist_free(pc);
+
+ }
+
+ return (nvl);
+}
+
+static nvlist_t *
+zen_umc_dump_chan(zen_umc_chan_t *chan)
+{
+ nvlist_t *nvl, *hash;
+ nvlist_t *rules[ZEN_UMC_MAX_CS_RULES];
+ nvlist_t *offsets[ZEN_UMC_MAX_DRAM_OFFSET];
+ nvlist_t *dimms[ZEN_UMC_MAX_DIMMS];
+
+ nvl = fnvlist_alloc();
+ fnvlist_add_uint32(nvl, "chan_flags", chan->chan_flags);
+ fnvlist_add_uint32(nvl, "chan_fabid", chan->chan_fabid);
+ fnvlist_add_uint32(nvl, "chan_instid", chan->chan_instid);
+ fnvlist_add_uint32(nvl, "chan_logid", chan->chan_logid);
+ fnvlist_add_uint32(nvl, "chan_np2_space0", chan->chan_np2_space0);
+
+ for (uint_t i = 0; i < chan->chan_nrules; i++) {
+ rules[i] = zen_umc_dump_dram_rule(&chan->chan_rules[i]);
+ }
+
+ for (uint_t i = 0; i < chan->chan_nrules - 1; i++) {
+ offsets[i] = fnvlist_alloc();
+ fnvlist_add_boolean_value(offsets[i], "cho_valid",
+ chan->chan_offsets[i].cho_valid);
+ fnvlist_add_uint64(offsets[i], "cho_offset",
+ chan->chan_offsets[i].cho_offset);
+ }
+
+ for (uint_t i = 0; i < ZEN_UMC_MAX_DIMMS; i++) {
+ dimms[i] = zen_umc_dump_dimm(&chan->chan_dimms[i]);
+ }
+
+ fnvlist_add_nvlist_array(nvl, "chan_rules", rules, chan->chan_nrules);
+ fnvlist_add_nvlist_array(nvl, "chan_offsets", offsets,
+ chan->chan_nrules - 1);
+ fnvlist_add_nvlist_array(nvl, "chan_dimms", dimms, ZEN_UMC_MAX_DIMMS);
+ hash = zen_umc_dump_chan_hash(&chan->chan_hash);
+ fnvlist_add_nvlist(nvl, "chan_hash", hash);
+
+ for (uint_t i = 0; i < chan->chan_nrules; i++) {
+ nvlist_free(rules[i]);
+ }
+
+ for (uint_t i = 0; i < chan->chan_nrules - 1; i++) {
+ nvlist_free(offsets[i]);
+ }
+
+ for (uint_t i = 0; i < ZEN_UMC_MAX_DIMMS; i++) {
+ nvlist_free(dimms[i]);
+ }
+
+ nvlist_free(hash);
+
+ return (nvl);
+}
+
+static nvlist_t *
+zen_umc_dump_df(zen_umc_df_t *df)
+{
+ nvlist_t *nvl;
+ nvlist_t *rules[ZEN_UMC_MAX_DRAM_RULES];
+ nvlist_t *remap[ZEN_UMC_MAX_CS_REMAPS];
+ nvlist_t *chan[ZEN_UMC_MAX_UMCS];
+
+ nvl = fnvlist_alloc();
+ fnvlist_add_uint32(nvl, "zud_flags", df->zud_flags);
+ fnvlist_add_uint32(nvl, "zud_dfno", df->zud_dfno);
+ fnvlist_add_uint32(nvl, "zud_ccm_inst", df->zud_ccm_inst);
+ fnvlist_add_uint64(nvl, "zud_hole_base", df->zud_hole_base);
+
+ for (uint_t i = 0; i < df->zud_dram_nrules; i++) {
+ rules[i] = zen_umc_dump_dram_rule(&df->zud_rules[i]);
+ }
+
+ for (uint_t i = 0; i < df->zud_cs_nremap; i++) {
+ remap[i] = fnvlist_alloc();
+ fnvlist_add_uint16_array(remap[i], "csr_remaps",
+ df->zud_remap[i].csr_remaps, df->zud_remap[i].csr_nremaps);
+ }
+
+ for (uint_t i = 0; i < df->zud_nchan; i++) {
+ chan[i] = zen_umc_dump_chan(&df->zud_chan[i]);
+ }
+
+ fnvlist_add_nvlist_array(nvl, "zud_rules", rules, df->zud_dram_nrules);
+ fnvlist_add_nvlist_array(nvl, "zud_remap", remap, df->zud_cs_nremap);
+ fnvlist_add_nvlist_array(nvl, "zud_chan", chan, df->zud_nchan);
+
+ for (uint_t i = 0; i < df->zud_dram_nrules; i++) {
+ nvlist_free(rules[i]);
+ }
+
+ for (uint_t i = 0; i < df->zud_cs_nremap; i++) {
+ nvlist_free(remap[i]);
+ }
+
+ for (uint_t i = 0; i < df->zud_nchan; i++) {
+ nvlist_free(chan[i]);
+ }
+
+ return (nvl);
+}
+
+nvlist_t *
+zen_umc_dump_decoder(zen_umc_t *umc)
+{
+ nvlist_t *nvl, *umc_nvl, *decomp;
+ nvlist_t *dfs[ZEN_UMC_MAX_DFS];
+
+ nvl = fnvlist_alloc();
+ fnvlist_add_uint32(nvl, "mc_dump_version", 0);
+ fnvlist_add_string(nvl, "mc_dump_driver", "zen_umc");
+
+ umc_nvl = fnvlist_alloc();
+ fnvlist_add_uint64(umc_nvl, "umc_tom", umc->umc_tom);
+ fnvlist_add_uint64(umc_nvl, "umc_tom2", umc->umc_tom2);
+ fnvlist_add_uint32(umc_nvl, "umc_family", umc->umc_family);
+ fnvlist_add_uint32(umc_nvl, "umc_df_rev", umc->umc_df_rev);
+
+ decomp = fnvlist_alloc();
+ fnvlist_add_uint32(decomp, "dfd_sock_mask",
+ umc->umc_decomp.dfd_sock_mask);
+ fnvlist_add_uint32(decomp, "dfd_die_mask",
+ umc->umc_decomp.dfd_die_mask);
+ fnvlist_add_uint32(decomp, "dfd_node_mask",
+ umc->umc_decomp.dfd_node_mask);
+ fnvlist_add_uint32(decomp, "dfd_comp_mask",
+ umc->umc_decomp.dfd_comp_mask);
+ fnvlist_add_uint8(decomp, "dfd_sock_shift",
+ umc->umc_decomp.dfd_sock_shift);
+ fnvlist_add_uint8(decomp, "dfd_die_shift",
+ umc->umc_decomp.dfd_die_shift);
+ fnvlist_add_uint8(decomp, "dfd_node_shift",
+ umc->umc_decomp.dfd_node_shift);
+ fnvlist_add_uint8(decomp, "dfd_comp_shift",
+ umc->umc_decomp.dfd_comp_shift);
+ fnvlist_add_nvlist(umc_nvl, "umc_decomp", decomp);
+ nvlist_free(decomp);
+
+ for (uint_t i = 0; i < umc->umc_ndfs; i++) {
+ dfs[i] = zen_umc_dump_df(&umc->umc_dfs[i]);
+ }
+
+ fnvlist_add_nvlist_array(umc_nvl, "umc_dfs", dfs, umc->umc_ndfs);
+ fnvlist_add_nvlist(nvl, "zen_umc", umc_nvl);
+ for (uint_t i = 0; i < umc->umc_ndfs; i++) {
+ nvlist_free(dfs[i]);
+ }
+
+ return (nvl);
+}
+
+static boolean_t
+zen_umc_restore_dram_rule(nvlist_t *nvl, df_dram_rule_t *rule)
+{
+ return (nvlist_lookup_pairs(nvl, 0,
+ "ddr_flags", DATA_TYPE_UINT32, &rule->ddr_flags,
+ "ddr_base", DATA_TYPE_UINT64, &rule->ddr_base,
+ "ddr_limit", DATA_TYPE_UINT64, &rule->ddr_limit,
+ "ddr_dest_fabid", DATA_TYPE_UINT16, &rule->ddr_dest_fabid,
+ "ddr_sock_ileave_bits", DATA_TYPE_UINT8,
+ &rule->ddr_sock_ileave_bits,
+ "ddr_die_ileave_bits", DATA_TYPE_UINT8, &rule->ddr_die_ileave_bits,
+ "ddr_addr_start", DATA_TYPE_UINT8, &rule->ddr_addr_start,
+ "ddr_remap_ent", DATA_TYPE_UINT8, &rule->ddr_remap_ent,
+ "ddr_chan_ileave", DATA_TYPE_UINT32, &rule->ddr_chan_ileave,
+ NULL) == 0);
+}
+
+static boolean_t
+zen_umc_restore_cs(nvlist_t *nvl, umc_cs_t *cs)
+{
+ nvlist_t *base, *sec;
+ uint8_t *bank_bits, *col_bits, *rm_bits, *rm_bits_sec;
+ uint_t nbanks, ncols, nrm, nrm_sec;
+
+ if (nvlist_lookup_pairs(nvl, 0,
+ "ucs_base", DATA_TYPE_NVLIST, &base,
+ "ucs_sec", DATA_TYPE_NVLIST, &sec,
+ "ucs_base_mask", DATA_TYPE_UINT64, &cs->ucs_base_mask,
+ "ucs_sec_mask", DATA_TYPE_UINT64, &cs->ucs_sec_mask,
+ "ucs_nrow_lo", DATA_TYPE_UINT8, &cs->ucs_nrow_lo,
+ "ucs_nrow_hi", DATA_TYPE_UINT8, &cs->ucs_nrow_hi,
+ "ucs_nbank_groups", DATA_TYPE_UINT8, &cs->ucs_nbank_groups,
+ "ucs_cs_xor", DATA_TYPE_UINT8, &cs->ucs_cs_xor,
+ "ucs_row_hi_bit", DATA_TYPE_UINT8, &cs->ucs_row_hi_bit,
+ "ucs_row_low_bit", DATA_TYPE_UINT8, &cs->ucs_row_low_bit,
+ "ucs_bank_bits", DATA_TYPE_UINT8_ARRAY, &bank_bits, &nbanks,
+ "ucs_col_bits", DATA_TYPE_UINT8_ARRAY, &col_bits, &ncols,
+ "ucs_inv_msbs", DATA_TYPE_UINT8, &cs->ucs_inv_msbs,
+ "ucs_rm_bits", DATA_TYPE_UINT8_ARRAY, &rm_bits, &nrm,
+ "ucs_inv_msbs_sec", DATA_TYPE_UINT8, &cs->ucs_inv_msbs_sec,
+ "ucs_rm_bits_sec", DATA_TYPE_UINT8_ARRAY, &rm_bits_sec, &nrm_sec,
+ "ucs_subchan", DATA_TYPE_UINT8, &cs->ucs_subchan,
+ NULL) != 0) {
+ return (B_FALSE);
+ }
+
+ if (nbanks > ZEN_UMC_MAX_BANK_BITS ||
+ ncols > ZEN_UMC_MAX_COL_BITS ||
+ nrm > ZEN_UMC_MAX_RM_BITS ||
+ nrm != nrm_sec) {
+ return (B_FALSE);
+ }
+
+ cs->ucs_nbanks = nbanks;
+ cs->ucs_ncol = ncols;
+ cs->ucs_nrm = nrm;
+
+ bcopy(bank_bits, cs->ucs_bank_bits, cs->ucs_nbanks *
+ sizeof (uint8_t));
+ bcopy(col_bits, cs->ucs_col_bits, cs->ucs_ncol * sizeof (uint8_t));
+ bcopy(rm_bits, cs->ucs_rm_bits, cs->ucs_nrm * sizeof (uint8_t));
+ bcopy(rm_bits_sec, cs->ucs_rm_bits_sec, cs->ucs_nrm *
+ sizeof (uint8_t));
+
+ if (nvlist_lookup_pairs(base, 0,
+ "udb_base", DATA_TYPE_UINT64, &cs->ucs_base.udb_base,
+ "udb_valid", DATA_TYPE_UINT8, &cs->ucs_base.udb_valid,
+ NULL) != 0) {
+ return (B_FALSE);
+ }
+
+ if (nvlist_lookup_pairs(sec, 0,
+ "udb_base", DATA_TYPE_UINT64, &cs->ucs_sec.udb_base,
+ "udb_valid", DATA_TYPE_UINT8, &cs->ucs_sec.udb_valid,
+ NULL) != 0) {
+ return (B_FALSE);
+ }
+
+ return (B_TRUE);
+}
+
+static boolean_t
+zen_umc_restore_dimm(nvlist_t *nvl, umc_dimm_t *dimm)
+{
+ nvlist_t **cs;
+ uint_t ncs;
+
+ if (nvlist_lookup_pairs(nvl, 0,
+ "ud_flags", DATA_TYPE_UINT32, &dimm->ud_flags,
+ "ud_width", DATA_TYPE_UINT32, &dimm->ud_width,
+ "ud_type", DATA_TYPE_UINT32, &dimm->ud_type,
+ "ud_kind", DATA_TYPE_UINT32, &dimm->ud_kind,
+ "ud_dimmno", DATA_TYPE_UINT32, &dimm->ud_dimmno,
+ "ud_cs", DATA_TYPE_NVLIST_ARRAY, &cs, &ncs,
+ NULL) != 0) {
+ return (B_FALSE);
+ }
+
+ if (ncs != ZEN_UMC_MAX_CS_PER_DIMM) {
+ return (B_FALSE);
+ }
+
+ for (uint_t i = 0; i < ZEN_UMC_MAX_CS_PER_DIMM; i++) {
+ if (!zen_umc_restore_cs(cs[i], &dimm->ud_cs[i])) {
+ return (B_FALSE);
+ }
+ }
+
+ return (B_TRUE);
+}
+
+static boolean_t
+zen_umc_restore_hash(nvlist_t *nvl, umc_chan_hash_t *hash)
+{
+ if (nvlist_lookup_uint32(nvl, "uch_flags", &hash->uch_flags) != 0) {
+ return (B_FALSE);
+ }
+
+ if (hash->uch_flags & UMC_CHAN_HASH_F_BANK) {
+ nvlist_t **banks;
+ uint_t nbanks;
+
+ if (nvlist_lookup_nvlist_array(nvl, "uch_bank_hashes", &banks,
+ &nbanks) != 0) {
+ return (B_FALSE);
+ }
+
+ if (nbanks != ZEN_UMC_MAX_CHAN_BANK_HASH) {
+ return (B_FALSE);
+ }
+
+ for (uint_t i = 0; i < nbanks; i++) {
+ if (nvlist_lookup_pairs(banks[i], 0,
+ "ubh_row_xor", DATA_TYPE_UINT32,
+ &hash->uch_bank_hashes[i].ubh_row_xor,
+ "ubh_col_xor", DATA_TYPE_UINT32,
+ &hash->uch_bank_hashes[i].ubh_col_xor,
+ "ubh_en", DATA_TYPE_BOOLEAN_VALUE,
+ &hash->uch_bank_hashes[i].ubh_en,
+ NULL) != 0) {
+ return (B_FALSE);
+ }
+ }
+ }
+
+ if (hash->uch_flags & UMC_CHAN_HASH_F_RM) {
+ nvlist_t **rm;
+ uint_t nrm;
+
+ if (nvlist_lookup_nvlist_array(nvl, "uch_rm_hashes", &rm,
+ &nrm) != 0) {
+ return (B_FALSE);
+ }
+
+ if (nrm != ZEN_UMC_MAX_CHAN_RM_HASH) {
+ return (B_FALSE);
+ }
+
+ for (uint_t i = 0; i < nrm; i++) {
+ if (nvlist_lookup_pairs(rm[i], 0,
+ "uah_addr_xor", DATA_TYPE_UINT64,
+ &hash->uch_rm_hashes[i].uah_addr_xor,
+ "uah_en", DATA_TYPE_BOOLEAN_VALUE,
+ &hash->uch_rm_hashes[i].uah_en,
+ NULL) != 0) {
+ return (B_FALSE);
+ }
+ }
+ }
+
+ if (hash->uch_flags & UMC_CHAN_HASH_F_CS) {
+ nvlist_t **cs;
+ uint_t ncs;
+
+ if (nvlist_lookup_nvlist_array(nvl, "uch_cs_hashes", &cs,
+ &ncs) != 0) {
+ return (B_FALSE);
+ }
+
+ if (ncs != ZEN_UMC_MAX_CHAN_CS_HASH) {
+ return (B_FALSE);
+ }
+
+ for (uint_t i = 0; i < ncs; i++) {
+ if (nvlist_lookup_pairs(cs[i], 0,
+ "uah_addr_xor", DATA_TYPE_UINT64,
+ &hash->uch_cs_hashes[i].uah_addr_xor,
+ "uah_en", DATA_TYPE_BOOLEAN_VALUE,
+ &hash->uch_cs_hashes[i].uah_en,
+ NULL) != 0) {
+ return (B_FALSE);
+ }
+ }
+ }
+
+ if (hash->uch_flags & UMC_CHAN_HASH_F_PC) {
+ nvlist_t *pc;
+
+ if (nvlist_lookup_nvlist(nvl, "uch_pch_hash", &pc) != 0) {
+ return (B_FALSE);
+ }
+
+ if (nvlist_lookup_pairs(pc, 0,
+ "uph_row_xor", DATA_TYPE_UINT32,
+ &hash->uch_pc_hash.uph_row_xor,
+ "uph_col_xor", DATA_TYPE_UINT32,
+ &hash->uch_pc_hash.uph_col_xor,
+ "uph_bank_xor", DATA_TYPE_UINT32,
+ &hash->uch_pc_hash.uph_bank_xor,
+ "uph_en", DATA_TYPE_BOOLEAN_VALUE,
+ &hash->uch_pc_hash.uph_en,
+ NULL) != 0) {
+ return (B_FALSE);
+ }
+ }
+ return (B_TRUE);
+}
+
+static boolean_t
+zen_umc_restore_chan(nvlist_t *nvl, zen_umc_chan_t *chan)
+{
+ uint_t noffsets, ndimms;
+ nvlist_t **rules, **offsets, **dimms, *hash;
+
+ if (nvlist_lookup_pairs(nvl, 0,
+ "chan_flags", DATA_TYPE_UINT32, &chan->chan_flags,
+ "chan_fabid", DATA_TYPE_UINT32, &chan->chan_fabid,
+ "chan_instid", DATA_TYPE_UINT32, &chan->chan_instid,
+ "chan_logid", DATA_TYPE_UINT32, &chan->chan_logid,
+ "chan_rules", DATA_TYPE_NVLIST_ARRAY, &rules, &chan->chan_nrules,
+ "chan_np2_space0", DATA_TYPE_UINT32, &chan->chan_np2_space0,
+ "chan_offsets", DATA_TYPE_NVLIST_ARRAY, &offsets, &noffsets,
+ "chan_dimms", DATA_TYPE_NVLIST_ARRAY, &dimms, &ndimms,
+ "chan_hash", DATA_TYPE_NVLIST, &hash,
+ NULL) != 0) {
+ return (B_FALSE);
+ }
+
+ if (chan->chan_nrules > ZEN_UMC_MAX_CS_RULES ||
+ noffsets != chan->chan_nrules - 1 || ndimms != ZEN_UMC_MAX_DIMMS) {
+ return (B_FALSE);
+ }
+
+ for (uint_t i = 0; i < chan->chan_nrules; i++) {
+ if (!zen_umc_restore_dram_rule(rules[i],
+ &chan->chan_rules[i])) {
+ return (B_FALSE);
+ }
+ }
+
+ for (uint_t i = 0; i < chan->chan_nrules - 1; i++) {
+ chan_offset_t *coff = &chan->chan_offsets[i];
+
+ if (nvlist_lookup_pairs(offsets[i], 0,
+ "cho_valid", DATA_TYPE_BOOLEAN_VALUE, &coff->cho_valid,
+ "cho_offset", DATA_TYPE_UINT64, &coff->cho_offset,
+ NULL) != 0) {
+ return (B_FALSE);
+ }
+ }
+
+ for (uint_t i = 0; i < ZEN_UMC_MAX_DIMMS; i++) {
+ if (!zen_umc_restore_dimm(dimms[i], &chan->chan_dimms[i])) {
+ return (B_FALSE);
+ }
+ }
+
+ if (!zen_umc_restore_hash(hash, &chan->chan_hash)) {
+ return (B_FALSE);
+ }
+
+ return (B_TRUE);
+}
+
+static boolean_t
+zen_umc_restore_df(nvlist_t *nvl, zen_umc_df_t *df)
+{
+ nvlist_t **rules, **chan, **remap;
+
+ if (nvlist_lookup_pairs(nvl, 0,
+ "zud_flags", DATA_TYPE_UINT32, &df->zud_flags,
+ "zud_dfno", DATA_TYPE_UINT32, &df->zud_dfno,
+ "zud_ccm_inst", DATA_TYPE_UINT32, &df->zud_ccm_inst,
+ "zud_hole_base", DATA_TYPE_UINT64, &df->zud_hole_base,
+ "zud_rules", DATA_TYPE_NVLIST_ARRAY, &rules, &df->zud_dram_nrules,
+ "zud_remap", DATA_TYPE_NVLIST_ARRAY, &remap, &df->zud_cs_nremap,
+ "zud_chan", DATA_TYPE_NVLIST_ARRAY, &chan, &df->zud_nchan,
+ NULL != 0) ||
+ df->zud_dram_nrules > ZEN_UMC_MAX_DRAM_RULES ||
+ df->zud_cs_nremap > ZEN_UMC_MAX_CS_REMAPS ||
+ df->zud_nchan > ZEN_UMC_MAX_UMCS) {
+ return (B_FALSE);
+ }
+
+ for (uint_t i = 0; i < df->zud_dram_nrules; i++) {
+ if (!zen_umc_restore_dram_rule(rules[i], &df->zud_rules[i])) {
+ return (B_FALSE);
+ }
+ }
+
+ for (uint_t i = 0; i < df->zud_cs_nremap; i++) {
+ uint16_t *u16p;
+ if (nvlist_lookup_uint16_array(remap[i], "csr_remaps", &u16p,
+ &df->zud_remap[i].csr_nremaps) != 0 ||
+ df->zud_remap[i].csr_nremaps > ZEN_UMC_MAX_REMAP_ENTS) {
+ return (B_FALSE);
+ }
+ bcopy(u16p, df->zud_remap[i].csr_remaps,
+ df->zud_remap[i].csr_nremaps);
+ }
+
+ for (uint_t i = 0; i < df->zud_nchan; i++) {
+ if (!zen_umc_restore_chan(chan[i], &df->zud_chan[i])) {
+ return (B_FALSE);
+ }
+ }
+
+ return (B_TRUE);
+}
+
+boolean_t
+zen_umc_restore_decoder(nvlist_t *nvl, zen_umc_t *umc)
+{
+ uint32_t vers;
+ char *driver;
+ nvlist_t *umc_nvl, *decomp, **dfs;
+ bzero(umc, sizeof (zen_umc_t));
+
+ if (nvlist_lookup_pairs(nvl, 0,
+ "mc_dump_version", DATA_TYPE_UINT32, &vers,
+ "mc_dump_driver", DATA_TYPE_STRING, &driver,
+ NULL) != 0 || vers != 0 || strcmp(driver, "zen_umc") != 0 ||
+ nvlist_lookup_nvlist(nvl, "zen_umc", &umc_nvl) != 0) {
+ return (B_FALSE);
+ }
+
+ if (nvlist_lookup_pairs(umc_nvl, 0,
+ "umc_tom", DATA_TYPE_UINT64, &umc->umc_tom,
+ "umc_tom2", DATA_TYPE_UINT64, &umc->umc_tom2,
+ "umc_family", DATA_TYPE_UINT32, &umc->umc_family,
+ "umc_df_rev", DATA_TYPE_UINT32, &umc->umc_df_rev,
+ "umc_decomp", DATA_TYPE_NVLIST, &decomp,
+ "umc_dfs", DATA_TYPE_NVLIST_ARRAY, &dfs, &umc->umc_ndfs,
+ NULL) != 0 || umc->umc_ndfs > ZEN_UMC_MAX_DFS) {
+ return (B_FALSE);
+ }
+
+
+ if (nvlist_lookup_pairs(decomp, 0,
+ "dfd_sock_mask", DATA_TYPE_UINT32, &umc->umc_decomp.dfd_sock_mask,
+ "dfd_die_mask", DATA_TYPE_UINT32, &umc->umc_decomp.dfd_die_mask,
+ "dfd_node_mask", DATA_TYPE_UINT32, &umc->umc_decomp.dfd_node_mask,
+ "dfd_comp_mask", DATA_TYPE_UINT32, &umc->umc_decomp.dfd_comp_mask,
+ "dfd_sock_shift", DATA_TYPE_UINT8, &umc->umc_decomp.dfd_sock_shift,
+ "dfd_die_shift", DATA_TYPE_UINT8, &umc->umc_decomp.dfd_die_shift,
+ "dfd_node_shift", DATA_TYPE_UINT8, &umc->umc_decomp.dfd_node_shift,
+ "dfd_comp_shift", DATA_TYPE_UINT8, &umc->umc_decomp.dfd_comp_shift,
+ NULL) != 0) {
+ return (B_FALSE);
+ }
+
+ for (uint_t i = 0; i < umc->umc_ndfs; i++) {
+ if (!zen_umc_restore_df(dfs[i], &umc->umc_dfs[i])) {
+ return (B_FALSE);
+ }
+ }
+
+ return (B_TRUE);
+}