#include <sys/sysmacros.h>
#ifndef _KERNEL
#include <stdint.h>
#include <strings.h>
#define BITX(u, h, l) (((u) >> (l)) & ((1LU << ((h) - (l) + 1LU)) - 1LU))
#endif
#include "imc.h"
#define IMC_DECODE_CONV_BASE 0UL
#define IMC_DECODE_CONV_MAX 0x00009ffffULL
#define IMC_DECODE_LOW_BASE 0x000100000ULL
#define IMC_DECODE_HIGH_BASE 0x100000000ULL
typedef struct imc_legacy_range {
uint64_t ilr_base;
size_t ilr_len;
const char *ilr_desc;
} imc_legacy_range_t;
static imc_legacy_range_t imc_legacy_ranges[] = {
{ 0x00000A0000ULL, 128 * 1024, "VGA" },
{ 0x00000C0000ULL, 256 * 1024, "PAM" },
{ 0x0000F00000ULL, 1024 * 1024, "Reserved" },
{ 0x00FE000000ULL, 32 * 1024 * 1024, "Unknown" },
{ 0x00FF000000ULL, 16 * 1024 * 1024, "Firmware" },
{ 0x00FED20000ULL, 384 * 1024, "TXT" },
{ 0x00FED00000ULL, 1024 * 1024, "PCH" },
{ 0x00FEC00000ULL, 1024 * 1024, "IOAPIC" },
{ 0x00FEB80000ULL, 512 * 1024, "Reserved" },
{ 0x00FEB00000ULL, 64 * 1024, "Reserved" }
};
static boolean_t
imc_decode_addr_resvd(const imc_t *imc, imc_decode_state_t *dec)
{
uint_t i;
const imc_sad_t *sad;
for (i = 0; i < ARRAY_SIZE(imc_legacy_ranges); i++) {
uint64_t end = imc_legacy_ranges[i].ilr_base +
imc_legacy_ranges[i].ilr_len;
if (dec->ids_pa >= imc_legacy_ranges[i].ilr_base &&
dec->ids_pa < end) {
dec->ids_fail = IMC_DECODE_F_LEGACY_RANGE;
dec->ids_fail_data = i;
return (B_TRUE);
}
}
sad = &imc->imc_sockets[0].isock_sad;
if (sad->isad_valid != IMC_SAD_V_VALID) {
dec->ids_fail = IMC_DECODE_F_BAD_SAD;
return (B_TRUE);
}
if (dec->ids_pa <= IMC_DECODE_CONV_MAX) {
return (B_FALSE);
}
if (dec->ids_pa >= IMC_DECODE_LOW_BASE &&
dec->ids_pa < sad->isad_tolm) {
return (B_FALSE);
}
if (dec->ids_pa >= IMC_DECODE_HIGH_BASE &&
dec->ids_pa < sad->isad_tohm) {
return (B_FALSE);
}
dec->ids_fail = IMC_DECODE_F_OUTSIDE_DRAM;
return (B_TRUE);
}
static uint_t
imc_decode_sad_interleave(const imc_sad_rule_t *rule, uint64_t pa)
{
uint_t itgt = 0;
switch (rule->isr_imode) {
case IMC_SAD_IMODE_8t6:
if (rule->isr_a7mode) {
itgt = BITX(pa, 9, 9);
itgt |= (BITX(pa, 8, 7) << 1);
} else {
itgt = BITX(pa, 8, 6);
}
break;
case IMC_SAD_IMODE_8t6XOR:
if (rule->isr_a7mode) {
itgt = BITX(pa, 9, 9);
itgt |= (BITX(pa, 8, 7) << 1);
} else {
itgt = BITX(pa, 8, 6);
}
itgt ^= BITX(pa, 18, 16);
break;
case IMC_SAD_IMODE_10t8:
itgt = BITX(pa, 10, 8);
break;
case IMC_SAD_IMODE_14t12:
itgt = BITX(pa, 14, 12);
break;
case IMC_SAD_IMODE_32t30:
itgt = BITX(pa, 32, 30);
break;
}
return (itgt);
}
static boolean_t
imc_decode_sad(const imc_t *imc, imc_decode_state_t *dec)
{
uint_t i, ileaveidx;
uint8_t ileavetgt;
uint32_t nodeid, tadid, channelid;
uint64_t base;
const imc_socket_t *socket = &imc->imc_sockets[0];
const imc_sad_t *sad = &socket->isock_sad;
const imc_sad_rule_t *rule;
boolean_t loop = B_FALSE;
start:
for (rule = NULL, i = 0, base = 0; i < sad->isad_nrules; i++) {
rule = &sad->isad_rules[i];
if (rule->isr_enable && dec->ids_pa >= base &&
dec->ids_pa < rule->isr_limit) {
break;
}
base = rule->isr_limit;
}
if (rule == NULL || i == sad->isad_nrules) {
dec->ids_fail = IMC_DECODE_F_NO_SAD_RULE;
return (B_FALSE);
}
dec->ids_sad = sad;
dec->ids_sad_rule = rule;
ileaveidx = imc_decode_sad_interleave(rule, dec->ids_pa);
if (ileaveidx >= rule->isr_ntargets) {
dec->ids_fail = IMC_DECODE_F_BAD_SAD_INTERLEAVE;
dec->ids_fail_data = ileaveidx;
return (B_FALSE);
}
ileavetgt = rule->isr_targets[ileaveidx];
if (imc->imc_gen >= IMC_GEN_SKYLAKE &&
IMC_SAD_ILEAVE_SKX_LOCAL(ileavetgt) == 0) {
nodeid = IMC_SAD_ILEAVE_SKX_TARGET(ileavetgt);
if (loop) {
dec->ids_fail = IMC_DECODE_F_SAD_SEARCH_LOOP;
return (B_FALSE);
}
for (i = 0; i < imc->imc_nsockets; i++) {
if (imc->imc_sockets[i].isock_valid ==
IMC_SOCKET_V_VALID &&
imc->imc_sockets[i].isock_nodeid == nodeid) {
socket = &imc->imc_sockets[i];
sad = &imc->imc_sockets[i].isock_sad;
loop = B_TRUE;
goto start;
}
}
dec->ids_fail = IMC_DECODE_F_BAD_REMOTE_MC_ROUTE;
dec->ids_fail_data = nodeid;
return (B_FALSE);
}
if (rule->isr_need_mod3) {
uint64_t addr;
uint8_t channel;
switch (rule->isr_mod_mode) {
case IMC_SAD_MOD_MODE_45t6:
addr = dec->ids_pa >> 6;
break;
case IMC_SAD_MOD_MODE_45t8:
addr = dec->ids_pa >> 8;
break;
case IMC_SAD_MOD_MODE_45t12:
addr = dec->ids_pa >> 12;
break;
default:
dec->ids_fail = IMC_DECODE_F_SAD_BAD_MOD;
return (B_FALSE);
}
switch (rule->isr_mod_type) {
case IMC_SAD_MOD_TYPE_MOD3:
channel = (addr % 3) << 1;
channel |= ileavetgt & 1;
break;
case IMC_SAD_MOD_TYPE_MOD2_01:
channel = (addr % 2) << 1;
channel |= ileavetgt & 1;
break;
case IMC_SAD_MOD_TYPE_MOD2_12:
channel = (addr % 2) << 2;
channel |= (~addr % 2) << 1;
channel |= ileavetgt & 1;
break;
case IMC_SAD_MOD_TYPE_MOD2_02:
channel = (addr % 2) << 2;
channel |= ileavetgt & 1;
break;
default:
dec->ids_fail = IMC_DECODE_F_SAD_BAD_MOD;
return (B_FALSE);
}
ileavetgt = channel;
}
switch (imc->imc_gen) {
case IMC_GEN_SANDY:
nodeid = ileavetgt;
tadid = 0;
channelid = UINT32_MAX;
break;
case IMC_GEN_IVY:
case IMC_GEN_HASWELL:
case IMC_GEN_BROADWELL:
nodeid = IMC_NODEID_IVY_BRD_UPPER(ileavetgt) |
IMC_NODEID_IVY_BRD_LOWER(ileavetgt);
tadid = IMC_NODEID_IVY_BRD_HA(ileavetgt);
channelid = UINT32_MAX;
break;
case IMC_GEN_SKYLAKE:
nodeid = socket->isock_nodeid;
if (ileavetgt > IMC_SAD_ILEAVE_SKX_MAX) {
dec->ids_fail = IMC_DECODE_F_BAD_SAD_INTERLEAVE;
dec->ids_fail_data = ileavetgt;
return (B_FALSE);
}
ileavetgt = IMC_SAD_ILEAVE_SKX_TARGET(ileavetgt);
if (ileavetgt > sad->isad_mcroute.ismc_nroutes) {
dec->ids_fail = IMC_DECODE_F_BAD_SAD_INTERLEAVE;
dec->ids_fail_data = ileavetgt;
return (B_FALSE);
}
tadid = sad->isad_mcroute.ismc_mcroutes[ileavetgt].ismce_imc;
channelid =
sad->isad_mcroute.ismc_mcroutes[ileavetgt].ismce_pchannel;
break;
default:
nodeid = tadid = channelid = UINT32_MAX;
break;
}
dec->ids_socket = NULL;
for (i = 0; i < imc->imc_nsockets; i++) {
if (imc->imc_sockets[i].isock_nodeid == nodeid) {
dec->ids_socket = &imc->imc_sockets[i];
break;
}
}
if (dec->ids_socket == NULL) {
dec->ids_fail = IMC_DECODE_F_SAD_BAD_SOCKET;
dec->ids_fail_data = nodeid;
return (B_FALSE);
}
if (tadid >= dec->ids_socket->isock_ntad) {
dec->ids_fail = IMC_DECODE_F_SAD_BAD_TAD;
dec->ids_fail_data = tadid;
return (B_FALSE);
}
dec->ids_nodeid = nodeid;
dec->ids_tadid = tadid;
dec->ids_channelid = channelid;
dec->ids_tad = &dec->ids_socket->isock_tad[tadid];
dec->ids_mc = &dec->ids_socket->isock_imcs[tadid];
return (B_TRUE);
}
static boolean_t
imc_decode_tad_channel(const imc_t *imc, imc_decode_state_t *dec)
{
uint64_t index;
const imc_tad_rule_t *rule = dec->ids_tad_rule;
index = dec->ids_pa >> 6;
if ((dec->ids_tad->itad_flags & IMC_TAD_FLAG_CHANSHIFT) != 0) {
index = index >> 1;
}
index = index / rule->itr_sock_way;
if ((dec->ids_tad->itad_flags & IMC_TAD_FLAG_CHANHASH) != 0) {
uint_t i;
for (i = 12; i < 28; i += 2) {
uint64_t shift = (dec->ids_pa >> i) & 0x3;
index ^= shift;
}
}
index %= rule->itr_chan_way;
if (index >= rule->itr_ntargets) {
dec->ids_fail = IMC_DECODE_F_TAD_BAD_TARGET_INDEX;
dec->ids_fail_data = index;
return (B_FALSE);
}
dec->ids_channelid = rule->itr_targets[index];
return (B_TRUE);
}
static uint_t
imc_tad_gran_to_shift(const imc_tad_t *tad, imc_tad_gran_t gran)
{
uint_t shift = 0;
switch (gran) {
case IMC_TAD_GRAN_64B:
shift = 6;
if ((tad->itad_flags & IMC_TAD_FLAG_CHANSHIFT) != 0) {
shift++;
}
break;
case IMC_TAD_GRAN_256B:
shift = 8;
break;
case IMC_TAD_GRAN_4KB:
shift = 12;
break;
case IMC_TAD_GRAN_1GB:
shift = 30;
break;
}
return (shift);
}
static boolean_t
imc_decode_tad(const imc_t *imc, imc_decode_state_t *dec)
{
uint_t i, tadruleno;
uint_t sockshift, chanshift, sockmask, chanmask;
uint64_t off, chanaddr;
const imc_tad_t *tad = dec->ids_tad;
const imc_mc_t *mc = dec->ids_mc;
const imc_tad_rule_t *rule = NULL;
const imc_channel_t *chan;
for (i = 0; i < tad->itad_nrules; i++) {
rule = &tad->itad_rules[i];
if (dec->ids_pa >= rule->itr_base &&
dec->ids_pa < rule->itr_limit) {
break;
}
}
if (rule == NULL || i == tad->itad_nrules) {
dec->ids_fail = IMC_DECODE_F_NO_TAD_RULE;
return (B_FALSE);
}
tadruleno = i;
dec->ids_tad_rule = rule;
if (rule->itr_chan_way == 3) {
dec->ids_fail = IMC_DECODE_F_TAD_3_ILEAVE;
return (B_FALSE);
}
switch (imc->imc_gen) {
case IMC_GEN_SANDY:
case IMC_GEN_IVY:
case IMC_GEN_HASWELL:
case IMC_GEN_BROADWELL:
if (!imc_decode_tad_channel(imc, dec)) {
return (B_FALSE);
}
break;
default:
break;
}
if (dec->ids_channelid >= mc->icn_nchannels) {
dec->ids_fail = IMC_DECODE_F_BAD_CHANNEL_ID;
dec->ids_fail_data = dec->ids_channelid;
return (B_FALSE);
}
chan = &mc->icn_channels[dec->ids_channelid];
dec->ids_chan = chan;
if (tadruleno >= chan->ich_ntad_offsets) {
dec->ids_fail = IMC_DECODE_F_BAD_CHANNEL_TAD_OFFSET;
dec->ids_fail_data = tadruleno;
return (B_FALSE);
}
off = chan->ich_tad_offsets[tadruleno];
if (off > dec->ids_pa) {
dec->ids_fail = IMC_DECODE_F_CHANOFF_UNDERFLOW;
return (B_FALSE);
}
chanshift = imc_tad_gran_to_shift(tad, rule->itr_chan_gran);
sockshift = imc_tad_gran_to_shift(tad, rule->itr_sock_gran);
chanmask = (1 << chanshift) - 1;
sockmask = (1 << sockshift) - 1;
chanaddr = dec->ids_pa - off;
chanaddr >>= sockshift;
chanaddr /= rule->itr_sock_way;
chanaddr <<= sockshift;
chanaddr |= dec->ids_pa & sockmask;
chanaddr >>= chanshift;
chanaddr /= rule->itr_chan_way;
chanaddr <<= chanshift;
chanaddr |= dec->ids_pa & chanmask;
dec->ids_chanaddr = chanaddr;
return (B_TRUE);
}
static boolean_t
imc_decode_rir(const imc_t *imc, imc_decode_state_t *dec)
{
const imc_mc_t *mc = dec->ids_mc;
const imc_channel_t *chan = dec->ids_chan;
const imc_rank_ileave_t *rir = NULL;
const imc_rank_ileave_entry_t *rirtarg;
const imc_dimm_t *dimm;
uint32_t shift, index;
uint_t i, dimmid, rankid;
uint64_t mask, base, rankaddr;
if (mc->icn_closed) {
shift = IMC_PAGE_BITS_CLOSED;
} else {
shift = IMC_PAGE_BITS_OPEN;
}
mask = (1UL << shift) - 1;
for (i = 0, base = 0; i < chan->ich_nrankileaves; i++) {
rir = &chan->ich_rankileaves[i];
if (rir->irle_enabled && dec->ids_chanaddr >= base &&
dec->ids_chanaddr < rir->irle_limit) {
break;
}
base = rir->irle_limit;
}
if (rir == NULL || i == chan->ich_nrankileaves) {
dec->ids_fail = IMC_DECODE_F_NO_RIR_RULE;
return (B_FALSE);
}
dec->ids_rir = rir;
index = (dec->ids_chanaddr >> shift) % rir->irle_nways;
if (index >= rir->irle_nentries) {
dec->ids_fail = IMC_DECODE_F_BAD_RIR_ILEAVE_TARGET;
dec->ids_fail_data = index;
return (B_FALSE);
}
rirtarg = &rir->irle_entries[index];
dec->ids_physrankid = rirtarg->irle_target;
dimmid = dec->ids_physrankid / 4;
rankid = dec->ids_physrankid % 4;
if (dimmid >= chan->ich_ndimms) {
dec->ids_fail = IMC_DECODE_F_BAD_DIMM_INDEX;
dec->ids_fail_data = dimmid;
return (B_FALSE);
}
dimm = &chan->ich_dimms[dimmid];
if (!dimm->idimm_present) {
dec->ids_fail = IMC_DECODE_F_DIMM_NOT_PRESENT;
return (B_FALSE);
}
dec->ids_dimmid = dimmid;
dec->ids_dimm = dimm;
if (rankid >= dimm->idimm_nranks) {
dec->ids_fail = IMC_DECODE_F_BAD_DIMM_RANK;
dec->ids_fail_data = rankid;
return (B_FALSE);
}
dec->ids_rankid = rankid;
rankaddr = dec->ids_chanaddr;
rankaddr >>= shift;
rankaddr /= rir->irle_nways;
rankaddr <<= shift;
rankaddr |= dec->ids_chanaddr & mask;
if (rirtarg->irle_offset > rankaddr) {
dec->ids_fail = IMC_DECODE_F_RANKOFF_UNDERFLOW;
return (B_FALSE);
}
rankaddr -= rirtarg->irle_offset;
dec->ids_rankaddr = rankaddr;
return (B_TRUE);
}
boolean_t
imc_decode_pa(const imc_t *imc, uint64_t pa, imc_decode_state_t *dec)
{
bzero(dec, sizeof (*dec));
dec->ids_pa = pa;
dec->ids_nodeid = dec->ids_tadid = dec->ids_channelid = UINT32_MAX;
if (imc->imc_nsockets < 1 ||
imc->imc_sockets[0].isock_valid != IMC_SOCKET_V_VALID) {
dec->ids_fail = IMC_DECODE_F_BAD_SOCKET;
dec->ids_fail_data = 0;
return (B_FALSE);
}
if (imc_decode_addr_resvd(imc, dec)) {
return (B_FALSE);
}
if (!imc_decode_sad(imc, dec)) {
return (B_FALSE);
}
if (!imc_decode_tad(imc, dec)) {
return (B_FALSE);
}
if (!imc_decode_rir(imc, dec)) {
return (B_FALSE);
}
return (B_TRUE);
}