#include <sys/modctl.h>
#include <sys/conf.h>
#include <sys/devops.h>
#include <sys/ddi.h>
#include <sys/sunddi.h>
#include <sys/types.h>
#include <sys/file.h>
#include <sys/errno.h>
#include <sys/open.h>
#include <sys/cred.h>
#include <sys/pci.h>
#include <sys/sysmacros.h>
#include <sys/avl.h>
#include <sys/stat.h>
#include <sys/policy.h>
#include <sys/cpu_module.h>
#include <sys/mc.h>
#include <sys/mc_intel.h>
#include "imc.h"
static const imc_gen_data_t imc_gen_data_snb = {
.igd_max_sockets = 4,
.igd_max_imcs = 2,
.igd_max_channels = 4,
.igd_max_dimms = 3,
.igd_max_ranks = IMC_MTR_DDR_RANKS_MAX,
.igd_mtr_offsets = { IMC_REG_MC_MTR0, IMC_REG_MC_MTR1,
IMC_REG_MC_MTR2 },
.igd_mcmtr_offset = 0x7c,
.igd_tolm_offset = 0x80,
.igd_tohm_low_offset = 0x84,
.igd_sad_dram_offset = 0x80,
.igd_sad_ndram_rules = 10,
.igd_sad_nodeid_offset = 0x40,
.igd_tad_nrules = 12,
.igd_tad_rule_offset = 0x40,
.igd_tad_chan_offset = 0x90,
.igd_tad_sysdef = 0x80,
.igd_tad_sysdef2 = 0x84,
.igd_mc_mirror = 0xac,
.igd_rir_nways = 5,
.igd_rir_way_offset = 0x108,
.igd_rir_nileaves = 8,
.igd_rir_ileave_offset = 0x120,
.igd_ubox_cpubusno_offset = 0xd0,
};
static const imc_gen_data_t imc_gen_data_ivb = {
.igd_max_sockets = 4,
.igd_max_imcs = 2,
.igd_max_channels = 4,
.igd_max_dimms = 3,
.igd_max_ranks = IMC_MTR_DDR_RANKS_MAX,
.igd_mtr_offsets = { IMC_REG_MC_MTR0, IMC_REG_MC_MTR1,
IMC_REG_MC_MTR2 },
.igd_mcmtr_offset = 0x7c,
.igd_tolm_offset = 0x80,
.igd_tohm_low_offset = 0x84,
.igd_sad_dram_offset = 0x60,
.igd_sad_ndram_rules = 20,
.igd_sad_nodeid_offset = 0x40,
.igd_tad_nrules = 12,
.igd_tad_rule_offset = 0x40,
.igd_tad_chan_offset = 0x90,
.igd_tad_sysdef = 0x80,
.igd_tad_sysdef2 = 0x84,
.igd_mc_mirror = 0xac,
.igd_rir_nways = 5,
.igd_rir_way_offset = 0x108,
.igd_rir_nileaves = 8,
.igd_rir_ileave_offset = 0x120,
.igd_ubox_cpubusno_offset = 0xd0,
};
static const imc_gen_data_t imc_gen_data_has_brd = {
.igd_max_sockets = 4,
.igd_max_imcs = 2,
.igd_max_channels = 4,
.igd_max_dimms = 3,
.igd_max_ranks = IMC_MTR_DDR_RANKS_MAX_HAS_SKX,
.igd_mtr_offsets = { IMC_REG_MC_MTR0, IMC_REG_MC_MTR1,
IMC_REG_MC_MTR2 },
.igd_mcmtr_offset = 0x7c,
.igd_tolm_offset = 0xd0,
.igd_tohm_low_offset = 0xd4,
.igd_tohm_hi_offset = 0xd8,
.igd_sad_dram_offset = 0x60,
.igd_sad_ndram_rules = 20,
.igd_sad_nodeid_offset = 0x40,
.igd_tad_nrules = 12,
.igd_tad_rule_offset = 0x40,
.igd_tad_chan_offset = 0x90,
.igd_tad_sysdef = 0x80,
.igd_tad_sysdef2 = 0x84,
.igd_mc_mirror = 0xac,
.igd_rir_nways = 5,
.igd_rir_way_offset = 0x108,
.igd_rir_nileaves = 8,
.igd_rir_ileave_offset = 0x120,
.igd_ubox_cpubusno_offset = 0xd0,
};
static const imc_gen_data_t imc_gen_data_skx = {
.igd_max_sockets = 8,
.igd_max_imcs = 2,
.igd_max_channels = 3,
.igd_max_dimms = 2,
.igd_max_ranks = IMC_MTR_DDR_RANKS_MAX,
.igd_mtr_offsets = { IMC_REG_MC_MTR0, IMC_REG_MC_MTR1 },
.igd_mcmtr_offset = 0x87c,
.igd_topo_offset = 0x88,
.igd_tolm_offset = 0xd0,
.igd_tohm_low_offset = 0xd4,
.igd_tohm_hi_offset = 0xd8,
.igd_sad_dram_offset = 0x60,
.igd_sad_ndram_rules = 24,
.igd_sad_nodeid_offset = 0xc0,
.igd_tad_nrules = 8,
.igd_tad_rule_offset = 0x850,
.igd_tad_chan_offset = 0x90,
.igd_rir_nways = 4,
.igd_rir_way_offset = 0x108,
.igd_rir_nileaves = 4,
.igd_rir_ileave_offset = 0x120,
.igd_ubox_cpubusno_offset = 0xcc,
};
static const imc_stub_table_t imc_stub_table[] = {
{ IMC_GEN_SANDY, IMC_TYPE_MC0_MAIN0, 0x3ca8, 15, 0, "IMC 0 Main 0" },
{ IMC_GEN_SANDY, IMC_TYPE_MC0_MAIN1, 0x3c71, 15, 1, "IMC 0 Main 0" },
{ IMC_GEN_SANDY, IMC_TYPE_MC0_CHANNEL0, 0x3caa, 15, 2, "IMC 0 Channel 0 Info" },
{ IMC_GEN_SANDY, IMC_TYPE_MC0_CHANNEL1, 0x3cab, 15, 3, "IMC 0 Channel 1 Info" },
{ IMC_GEN_SANDY, IMC_TYPE_MC0_CHANNEL2, 0x3cac, 15, 4, "IMC 0 Channel 2 Info" },
{ IMC_GEN_SANDY, IMC_TYPE_MC0_CHANNEL3, 0x3cad, 15, 5, "IMC 0 Channel 3 Info" },
{ IMC_GEN_SANDY, IMC_TYPE_SAD_DRAM, 0x3cf4, 12, 6, "SAD DRAM Rules" },
{ IMC_GEN_SANDY, IMC_TYPE_SAD_MMIO, 0x3cf5, 13, 6, "SAD MMIO Rules" },
{ IMC_GEN_SANDY, IMC_TYPE_SAD_MISC, 0x3cf6, 12, 7, "SAD Memory Map" },
{ IMC_GEN_SANDY, IMC_TYPE_UBOX, 0x3ce0, 11, 0, "UBox" },
{ IMC_GEN_SANDY, IMC_TYPE_UBOX_CPUBUSNO, 0x3ce3, 11, 3, "UBox Scratch" },
{ IMC_GEN_SANDY, IMC_TYPE_HA0, 0x3ca0, 14, 0, "Home Agent" },
{ IMC_GEN_IVY, IMC_TYPE_MC0_MAIN0, 0x0ea8, 15, 0, "IMC 0 Main 0" },
{ IMC_GEN_IVY, IMC_TYPE_MC0_MAIN1, 0x0e71, 15, 1, "IMC 0 Main 1" },
{ IMC_GEN_IVY, IMC_TYPE_MC0_CHANNEL0, 0x0eaa, 15, 2, "IMC 0 Channel 0 Info" },
{ IMC_GEN_IVY, IMC_TYPE_MC0_CHANNEL1, 0x0eab, 15, 3, "IMC 0 Channel 1 Info" },
{ IMC_GEN_IVY, IMC_TYPE_MC0_CHANNEL2, 0x0eac, 15, 4, "IMC 0 Channel 2 Info" },
{ IMC_GEN_IVY, IMC_TYPE_MC0_CHANNEL3, 0x0ead, 15, 5, "IMC 0 Channel 3 Info" },
{ IMC_GEN_IVY, IMC_TYPE_MC1_MAIN0, 0x0e68, 29, 0, "IMC 1 Main 0" },
{ IMC_GEN_IVY, IMC_TYPE_MC1_MAIN1, 0x0e79, 29, 1, "IMC 1 Main 1" },
{ IMC_GEN_IVY, IMC_TYPE_MC1_CHANNEL0, 0x0e6a, 15, 2, "IMC 1 Channel 0 Info" },
{ IMC_GEN_IVY, IMC_TYPE_MC1_CHANNEL1, 0x0e6b, 15, 3, "IMC 1 Channel 1 Info" },
{ IMC_GEN_IVY, IMC_TYPE_MC1_CHANNEL2, 0x0e6c, 15, 4, "IMC 1 Channel 2 Info" },
{ IMC_GEN_IVY, IMC_TYPE_MC1_CHANNEL3, 0x0e6d, 15, 5, "IMC 1 Channel 3 Info" },
{ IMC_GEN_IVY, IMC_TYPE_SAD_DRAM, 0x0ec8, 22, 0, "SAD DRAM Rules" },
{ IMC_GEN_IVY, IMC_TYPE_SAD_MMIO, 0x0ec9, 22, 1, "SAD MMIO Rules" },
{ IMC_GEN_IVY, IMC_TYPE_SAD_MISC, 0x0eca, 22, 2, "SAD Memory Map" },
{ IMC_GEN_IVY, IMC_TYPE_UBOX, 0x0e1e, 11, 0, "UBox" },
{ IMC_GEN_IVY, IMC_TYPE_UBOX_CPUBUSNO, 0x0e1f, 11, 3, "UBox Scratch" },
{ IMC_GEN_IVY, IMC_TYPE_HA0, 0x0ea0, 14, 0, "Home Agent 0" },
{ IMC_GEN_IVY, IMC_TYPE_HA1, 0x0e60, 28, 0, "Home Agent 1" },
{ IMC_GEN_HASWELL, IMC_TYPE_MC0_MAIN0, 0x2fa8, 19, 0, "IMC 0 Main 0" },
{ IMC_GEN_HASWELL, IMC_TYPE_MC0_MAIN1, 0x2f71, 19, 1, "IMC 0 Main 1" },
{ IMC_GEN_HASWELL, IMC_TYPE_MC0_CHANNEL0, 0x2faa, 19, 2, "IMC 0 Channel 0 Info" },
{ IMC_GEN_HASWELL, IMC_TYPE_MC0_CHANNEL1, 0x2fab, 19, 3, "IMC 0 Channel 1 Info" },
{ IMC_GEN_HASWELL, IMC_TYPE_MC0_CHANNEL2, 0x2fac, 19, 4, "IMC 0 Channel 2 Info" },
{ IMC_GEN_HASWELL, IMC_TYPE_MC0_CHANNEL3, 0x2fad, 19, 5, "IMC 0 Channel 3 Info" },
{ IMC_GEN_HASWELL, IMC_TYPE_MC1_MAIN0, 0x2f68, 22, 0, "IMC 1 Main 0" },
{ IMC_GEN_HASWELL, IMC_TYPE_MC1_MAIN1, 0x2f79, 22, 1, "IMC 1 Main 1" },
{ IMC_GEN_HASWELL, IMC_TYPE_MC1_CHANNEL0, 0x2f6a, 22, 2, "IMC 1 Channel 0 Info" },
{ IMC_GEN_HASWELL, IMC_TYPE_MC1_CHANNEL1, 0x2f6b, 22, 3, "IMC 1 Channel 1 Info" },
{ IMC_GEN_HASWELL, IMC_TYPE_MC1_CHANNEL2, 0x2f6c, 22, 4, "IMC 1 Channel 2 Info" },
{ IMC_GEN_HASWELL, IMC_TYPE_MC1_CHANNEL3, 0x2f6d, 22, 5, "IMC 1 Channel 3 Info" },
{ IMC_GEN_HASWELL, IMC_TYPE_SAD_DRAM, 0x2ffc, 15, 4, "SAD DRAM Rules" },
{ IMC_GEN_HASWELL, IMC_TYPE_SAD_MMIO, 0x2ffd, 15, 5, "SAD MMIO Rules" },
{ IMC_GEN_HASWELL, IMC_TYPE_VTD_MISC, 0x2f28, 5, 0, "Misc. Vritualization" },
{ IMC_GEN_HASWELL, IMC_TYPE_UBOX, 0x2f1e, 16, 5, "UBox" },
{ IMC_GEN_HASWELL, IMC_TYPE_UBOX_CPUBUSNO, 0x2f1f, 16, 7, "UBox Scratch" },
{ IMC_GEN_HASWELL, IMC_TYPE_HA0, 0x2fa0, 18, 0, "Home Agent 0" },
{ IMC_GEN_HASWELL, IMC_TYPE_HA1, 0x2f60, 18, 4, "Home Agent 1" },
{ IMC_GEN_BROADWELL, IMC_TYPE_MC0_MAIN0, 0x6fa8, 19, 0, "IMC 0 Main 0" },
{ IMC_GEN_BROADWELL, IMC_TYPE_MC0_MAIN1, 0x6f71, 19, 1, "IMC 0 Main 1" },
{ IMC_GEN_BROADWELL, IMC_TYPE_MC0_CHANNEL0, 0x6faa, 19, 2, "IMC 0 Channel 0 Info" },
{ IMC_GEN_BROADWELL, IMC_TYPE_MC0_CHANNEL1, 0x6fab, 19, 3, "IMC 0 Channel 1 Info" },
{ IMC_GEN_BROADWELL, IMC_TYPE_MC0_CHANNEL2, 0x6fac, 19, 4, "IMC 0 Channel 2 Info" },
{ IMC_GEN_BROADWELL, IMC_TYPE_MC0_CHANNEL3, 0x6fad, 19, 5, "IMC 0 Channel 3 Info" },
{ IMC_GEN_BROADWELL, IMC_TYPE_MC1_MAIN0, 0x6f68, 22, 0, "IMC 1 Main 0" },
{ IMC_GEN_BROADWELL, IMC_TYPE_MC1_MAIN1, 0x6f79, 22, 1, "IMC 1 Main 1" },
{ IMC_GEN_BROADWELL, IMC_TYPE_MC1_CHANNEL0, 0x6f6a, 22, 2, "IMC 1 Channel 0 Info" },
{ IMC_GEN_BROADWELL, IMC_TYPE_MC1_CHANNEL1, 0x6f6b, 22, 3, "IMC 1 Channel 1 Info" },
{ IMC_GEN_BROADWELL, IMC_TYPE_MC1_CHANNEL2, 0x6f6c, 22, 4, "IMC 1 Channel 2 Info" },
{ IMC_GEN_BROADWELL, IMC_TYPE_MC1_CHANNEL3, 0x6f6d, 22, 5, "IMC 1 Channel 3 Info" },
{ IMC_GEN_BROADWELL, IMC_TYPE_SAD_DRAM, 0x6ffc, 15, 4, "SAD DRAM Rules" },
{ IMC_GEN_BROADWELL, IMC_TYPE_SAD_MMIO, 0x6ffd, 15, 5, "SAD MMIO Rules" },
{ IMC_GEN_BROADWELL, IMC_TYPE_VTD_MISC, 0x6f28, 5, 0, "Misc. Vritualization" },
{ IMC_GEN_BROADWELL, IMC_TYPE_UBOX, 0x6f1e, 16, 5, "UBox" },
{ IMC_GEN_BROADWELL, IMC_TYPE_UBOX_CPUBUSNO, 0x6f1f, 16, 7, "UBox Scratch" },
{ IMC_GEN_BROADWELL, IMC_TYPE_HA0, 0x6fa0, 18, 0, "Home Agent 0" },
{ IMC_GEN_BROADWELL, IMC_TYPE_HA1, 0x6f60, 18, 4, "Home Agent 1" },
{ IMC_GEN_SKYLAKE, IMC_TYPE_MC0_M2M, 0x2066, 8, 0, "IMC 0 M2M" },
{ IMC_GEN_SKYLAKE, IMC_TYPE_MC1_M2M, 0x2066, 9, 0, "IMC 0 M2M" },
{ IMC_GEN_SKYLAKE, IMC_TYPE_MC0_MAIN0, 0x2040, 10, 0, "IMC 0 Main / Channel 0" },
{ IMC_GEN_SKYLAKE, IMC_TYPE_MC1_MAIN0, 0x2040, 12, 0, "IMC 0 Main / Channel 0" },
{ IMC_GEN_SKYLAKE, IMC_TYPE_MC0_CHANNEL1, 0x2044, 10, 4, "IMC 0 Channel 1" },
{ IMC_GEN_SKYLAKE, IMC_TYPE_MC0_CHANNEL2, 0x2048, 11, 0, "IMC 0 Channel 2" },
{ IMC_GEN_SKYLAKE, IMC_TYPE_MC1_CHANNEL1, 0x2044, 12, 4, "IMC 1 Channel 1" },
{ IMC_GEN_SKYLAKE, IMC_TYPE_MC1_CHANNEL2, 0x2048, 13, 0, "IMC 1 Channel 2" },
{ IMC_GEN_SKYLAKE, IMC_TYPE_SAD_DRAM, 0x2054, 29, 0, "SAD DRAM Rules" },
{ IMC_GEN_SKYLAKE, IMC_TYPE_SAD_MMIO, 0x2055, 29, 1, "SAD MMIO Rules" },
{ IMC_GEN_SKYLAKE, IMC_TYPE_VTD_MISC, 0x2024, 5, 0, "Misc. Virtualization" },
{ IMC_GEN_SKYLAKE, IMC_TYPE_SAD_MCROUTE, 0x208e, 14, 0, "Per-Core SAD" },
{ IMC_GEN_SKYLAKE, IMC_TYPE_SAD_MCROUTE, 0x208e, 14, 1, "Per-Core SAD" },
{ IMC_GEN_SKYLAKE, IMC_TYPE_SAD_MCROUTE, 0x208e, 14, 2, "Per-Core SAD" },
{ IMC_GEN_SKYLAKE, IMC_TYPE_SAD_MCROUTE, 0x208e, 14, 3, "Per-Core SAD" },
{ IMC_GEN_SKYLAKE, IMC_TYPE_SAD_MCROUTE, 0x208e, 14, 4, "Per-Core SAD" },
{ IMC_GEN_SKYLAKE, IMC_TYPE_SAD_MCROUTE, 0x208e, 14, 5, "Per-Core SAD" },
{ IMC_GEN_SKYLAKE, IMC_TYPE_SAD_MCROUTE, 0x208e, 14, 6, "Per-Core SAD" },
{ IMC_GEN_SKYLAKE, IMC_TYPE_SAD_MCROUTE, 0x208e, 14, 7, "Per-Core SAD" },
{ IMC_GEN_SKYLAKE, IMC_TYPE_SAD_MCROUTE, 0x208e, 15, 0, "Per-Core SAD" },
{ IMC_GEN_SKYLAKE, IMC_TYPE_SAD_MCROUTE, 0x208e, 15, 1, "Per-Core SAD" },
{ IMC_GEN_SKYLAKE, IMC_TYPE_SAD_MCROUTE, 0x208e, 15, 2, "Per-Core SAD" },
{ IMC_GEN_SKYLAKE, IMC_TYPE_SAD_MCROUTE, 0x208e, 15, 3, "Per-Core SAD" },
{ IMC_GEN_SKYLAKE, IMC_TYPE_SAD_MCROUTE, 0x208e, 15, 4, "Per-Core SAD" },
{ IMC_GEN_SKYLAKE, IMC_TYPE_SAD_MCROUTE, 0x208e, 15, 5, "Per-Core SAD" },
{ IMC_GEN_SKYLAKE, IMC_TYPE_SAD_MCROUTE, 0x208e, 15, 6, "Per-Core SAD" },
{ IMC_GEN_SKYLAKE, IMC_TYPE_SAD_MCROUTE, 0x208e, 15, 7, "Per-Core SAD" },
{ IMC_GEN_SKYLAKE, IMC_TYPE_SAD_MCROUTE, 0x208e, 16, 0, "Per-Core SAD" },
{ IMC_GEN_SKYLAKE, IMC_TYPE_SAD_MCROUTE, 0x208e, 16, 1, "Per-Core SAD" },
{ IMC_GEN_SKYLAKE, IMC_TYPE_SAD_MCROUTE, 0x208e, 16, 2, "Per-Core SAD" },
{ IMC_GEN_SKYLAKE, IMC_TYPE_SAD_MCROUTE, 0x208e, 16, 3, "Per-Core SAD" },
{ IMC_GEN_SKYLAKE, IMC_TYPE_SAD_MCROUTE, 0x208e, 16, 4, "Per-Core SAD" },
{ IMC_GEN_SKYLAKE, IMC_TYPE_SAD_MCROUTE, 0x208e, 16, 5, "Per-Core SAD" },
{ IMC_GEN_SKYLAKE, IMC_TYPE_SAD_MCROUTE, 0x208e, 16, 6, "Per-Core SAD" },
{ IMC_GEN_SKYLAKE, IMC_TYPE_SAD_MCROUTE, 0x208e, 16, 7, "Per-Core SAD" },
{ IMC_GEN_SKYLAKE, IMC_TYPE_SAD_MCROUTE, 0x208e, 17, 0, "Per-Core SAD" },
{ IMC_GEN_SKYLAKE, IMC_TYPE_SAD_MCROUTE, 0x208e, 17, 1, "Per-Core SAD" },
{ IMC_GEN_SKYLAKE, IMC_TYPE_SAD_MCROUTE, 0x208e, 17, 2, "Per-Core SAD" },
{ IMC_GEN_SKYLAKE, IMC_TYPE_SAD_MCROUTE, 0x208e, 17, 3, "Per-Core SAD" },
{ IMC_GEN_SKYLAKE, IMC_TYPE_SAD_MCROUTE, 0x208e, 17, 4, "Per-Core SAD" },
{ IMC_GEN_SKYLAKE, IMC_TYPE_SAD_MCROUTE, 0x208e, 17, 5, "Per-Core SAD" },
{ IMC_GEN_SKYLAKE, IMC_TYPE_SAD_MCROUTE, 0x208e, 17, 6, "Per-Core SAD" },
{ IMC_GEN_SKYLAKE, IMC_TYPE_SAD_MCROUTE, 0x208e, 17, 7, "Per-Core SAD" },
{ IMC_GEN_SKYLAKE, IMC_TYPE_UBOX, 0x2014, 8, 0, "UBox" },
{ IMC_GEN_SKYLAKE, IMC_TYPE_UBOX_CPUBUSNO, 0x2016, 8, 2, "DECS" },
};
#define IMC_PCI_VENDOR_INTC 0x8086
static imc_t *imc_data = NULL;
int imc_allow_detach = 0;
static void
imc_set_gen_data(imc_t *imc)
{
switch (imc->imc_gen) {
case IMC_GEN_SANDY:
imc->imc_gen_data = &imc_gen_data_snb;
break;
case IMC_GEN_IVY:
imc->imc_gen_data = &imc_gen_data_ivb;
break;
case IMC_GEN_HASWELL:
case IMC_GEN_BROADWELL:
imc->imc_gen_data = &imc_gen_data_has_brd;
break;
case IMC_GEN_SKYLAKE:
imc->imc_gen_data = &imc_gen_data_skx;
break;
default:
dev_err(imc->imc_dip, CE_PANIC, "imc driver programmer error: "
"set to unknown generation: %u", imc->imc_gen);
}
}
static boolean_t
imc_create_minors(imc_t *imc)
{
uint_t i;
ddi_set_name_addr(imc->imc_dip, "1");
for (i = 0; i < imc->imc_nsockets; i++) {
char buf[MAXNAMELEN];
if (snprintf(buf, sizeof (buf), "mc-imc-%u", i) >=
sizeof (buf)) {
goto fail;
}
if (ddi_create_minor_node(imc->imc_dip, buf, S_IFCHR, i,
"ddi_mem_ctrl", 0) != DDI_SUCCESS) {
dev_err(imc->imc_dip, CE_WARN, "failed to create "
"minor node %u: %s", i, buf);
goto fail;
}
}
return (B_TRUE);
fail:
ddi_remove_minor_node(imc->imc_dip, NULL);
return (B_FALSE);
}
static void
imc_mcroute_check(imc_t *imc, imc_sad_t *sad, imc_stub_t *stub)
{
uint32_t val;
val = pci_config_get32(stub->istub_cfgspace,
IMC_REG_SKX_SAD_MC_ROUTE_TABLE);
if (val == PCI_EINVAL32) {
sad->isad_valid |= IMC_SAD_V_BAD_PCI_READ;
return;
}
if ((sad->isad_flags & IMC_SAD_MCROUTE_VALID) == 0 && val != 0) {
sad->isad_flags |= IMC_SAD_MCROUTE_VALID;
sad->isad_mcroute.ismc_raw_mcroute = val;
return;
}
if (val != sad->isad_mcroute.ismc_raw_mcroute && val != 0) {
dev_err(imc->imc_dip, CE_WARN, "SAD MC_ROUTE_TABLE mismatch "
"with socket. SAD has val 0x%x, system has %x\n",
val, sad->isad_mcroute.ismc_raw_mcroute);
sad->isad_valid |= IMC_SAD_V_BAD_MCROUTE;
}
}
static boolean_t
imc_map_buses(imc_t *imc)
{
imc_stub_t *stub;
uint_t nsock;
for (nsock = 0, stub = avl_first(&imc->imc_stubs); stub != NULL;
stub = AVL_NEXT(&imc->imc_stubs, stub)) {
uint32_t busno;
if (stub->istub_table->imcs_type != IMC_TYPE_UBOX_CPUBUSNO) {
continue;
}
busno = pci_config_get32(stub->istub_cfgspace,
imc->imc_gen_data->igd_ubox_cpubusno_offset);
if (busno == PCI_EINVAL32) {
dev_err(imc->imc_dip, CE_WARN, "failed to read "
"UBOX_DECS CPUBUSNO0: invalid PCI read");
return (B_FALSE);
}
if (imc->imc_gen >= IMC_GEN_SKYLAKE) {
imc->imc_sockets[nsock].isock_nbus = 3;
imc->imc_sockets[nsock].isock_bus[0] =
IMC_UBOX_CPUBUSNO_0(busno);
imc->imc_sockets[nsock].isock_bus[1] =
IMC_UBOX_CPUBUSNO_1(busno);
imc->imc_sockets[nsock].isock_bus[2] =
IMC_UBOX_CPUBUSNO_2(busno);
} else {
imc->imc_sockets[nsock].isock_bus[0] =
IMC_UBOX_CPUBUSNO_0(busno);
imc->imc_sockets[nsock].isock_bus[1] =
IMC_UBOX_CPUBUSNO_1(busno);
imc->imc_sockets[nsock].isock_nbus = 2;
}
nsock++;
}
imc->imc_nsockets = nsock;
return (B_TRUE);
}
static imc_socket_t *
imc_map_find_socket(imc_t *imc, imc_stub_t *stub)
{
uint_t i;
for (i = 0; i < imc->imc_nsockets; i++) {
uint_t bus;
for (bus = 0; bus < imc->imc_sockets[i].isock_nbus; bus++) {
if (imc->imc_sockets[i].isock_bus[bus] ==
stub->istub_bus) {
return (&imc->imc_sockets[i]);
}
}
}
return (NULL);
}
static boolean_t
imc_map_stubs(imc_t *imc)
{
imc_stub_t *stub;
if (!imc_map_buses(imc)) {
return (B_FALSE);
}
stub = avl_first(&imc->imc_stubs);
for (stub = avl_first(&imc->imc_stubs); stub != NULL;
stub = AVL_NEXT(&imc->imc_stubs, stub)) {
imc_socket_t *sock = imc_map_find_socket(imc, stub);
if (sock == NULL) {
dev_err(imc->imc_dip, CE_WARN, "found stub type %u "
"PCI%x,%x with bdf %u/%u/%u that does not match a "
"known PCI bus for any of %u sockets",
stub->istub_table->imcs_type, stub->istub_vid,
stub->istub_did, stub->istub_bus, stub->istub_dev,
stub->istub_func, imc->imc_nsockets);
continue;
}
switch (stub->istub_table->imcs_type) {
case IMC_TYPE_MC0_M2M:
sock->isock_imcs[0].icn_m2m = stub;
break;
case IMC_TYPE_MC1_M2M:
sock->isock_imcs[1].icn_m2m = stub;
break;
case IMC_TYPE_MC0_MAIN0:
sock->isock_nimc++;
sock->isock_imcs[0].icn_main0 = stub;
if (imc->imc_gen >= IMC_GEN_SKYLAKE) {
sock->isock_imcs[0].icn_nchannels++;
sock->isock_imcs[0].icn_channels[0].ich_desc =
stub;
sock->isock_tad[0].itad_stub = stub;
sock->isock_ntad++;
}
break;
case IMC_TYPE_MC0_MAIN1:
sock->isock_imcs[0].icn_main1 = stub;
break;
case IMC_TYPE_MC1_MAIN0:
sock->isock_nimc++;
sock->isock_imcs[1].icn_main0 = stub;
if (imc->imc_gen >= IMC_GEN_SKYLAKE) {
sock->isock_imcs[1].icn_nchannels++;
sock->isock_imcs[1].icn_channels[0].ich_desc =
stub;
sock->isock_tad[1].itad_stub = stub;
sock->isock_ntad++;
}
break;
case IMC_TYPE_MC1_MAIN1:
sock->isock_imcs[1].icn_main1 = stub;
break;
case IMC_TYPE_MC0_CHANNEL0:
sock->isock_imcs[0].icn_nchannels++;
sock->isock_imcs[0].icn_channels[0].ich_desc = stub;
break;
case IMC_TYPE_MC0_CHANNEL1:
sock->isock_imcs[0].icn_nchannels++;
sock->isock_imcs[0].icn_channels[1].ich_desc = stub;
break;
case IMC_TYPE_MC0_CHANNEL2:
sock->isock_imcs[0].icn_nchannels++;
sock->isock_imcs[0].icn_channels[2].ich_desc = stub;
break;
case IMC_TYPE_MC0_CHANNEL3:
sock->isock_imcs[0].icn_nchannels++;
sock->isock_imcs[0].icn_channels[3].ich_desc = stub;
break;
case IMC_TYPE_MC1_CHANNEL0:
sock->isock_imcs[1].icn_nchannels++;
sock->isock_imcs[1].icn_channels[0].ich_desc = stub;
break;
case IMC_TYPE_MC1_CHANNEL1:
sock->isock_imcs[1].icn_nchannels++;
sock->isock_imcs[1].icn_channels[1].ich_desc = stub;
break;
case IMC_TYPE_MC1_CHANNEL2:
sock->isock_imcs[1].icn_nchannels++;
sock->isock_imcs[1].icn_channels[2].ich_desc = stub;
break;
case IMC_TYPE_MC1_CHANNEL3:
sock->isock_imcs[1].icn_nchannels++;
sock->isock_imcs[1].icn_channels[3].ich_desc = stub;
break;
case IMC_TYPE_SAD_DRAM:
sock->isock_sad.isad_dram = stub;
break;
case IMC_TYPE_SAD_MMIO:
sock->isock_sad.isad_mmio = stub;
break;
case IMC_TYPE_SAD_MISC:
sock->isock_sad.isad_tolh = stub;
break;
case IMC_TYPE_VTD_MISC:
if (imc->imc_gvtd_misc == NULL) {
imc->imc_gvtd_misc = stub;
}
break;
case IMC_TYPE_SAD_MCROUTE:
ASSERT3U(imc->imc_gen, >=, IMC_GEN_SKYLAKE);
imc_mcroute_check(imc, &sock->isock_sad, stub);
break;
case IMC_TYPE_UBOX:
sock->isock_ubox = stub;
break;
case IMC_TYPE_HA0:
sock->isock_ntad++;
sock->isock_tad[0].itad_stub = stub;
break;
case IMC_TYPE_HA1:
sock->isock_ntad++;
sock->isock_tad[1].itad_stub = stub;
break;
case IMC_TYPE_UBOX_CPUBUSNO:
sock->isock_cpubusno = stub;
break;
default:
dev_err(imc->imc_dip, CE_WARN, "Encountered unknown "
"IMC type (%u) on PCI %x,%x",
stub->istub_table->imcs_type,
stub->istub_vid, stub->istub_did);
break;
}
}
return (B_TRUE);
}
static void
imc_fixup_stubs(imc_t *imc)
{
if (imc->imc_gen >= IMC_GEN_HASWELL) {
uint_t i;
for (i = 0; i < imc->imc_nsockets; i++) {
ASSERT3P(imc->imc_sockets[i].isock_sad.isad_tolh,
==, NULL);
imc->imc_sockets[i].isock_sad.isad_tolh =
imc->imc_gvtd_misc;
}
}
}
static boolean_t
imc_validate_stubs(imc_t *imc)
{
for (uint_t sock = 0; sock < imc->imc_nsockets; sock++) {
imc_socket_t *socket = &imc->imc_sockets[sock];
for (uint_t mc = 0; mc < socket->isock_nimc; mc++) {
imc_mc_t *mcp = &socket->isock_imcs[mc];
for (uint_t chan = 0; chan < mcp->icn_nchannels;
chan++) {
if (mcp->icn_channels[chan].ich_desc == NULL) {
dev_err(imc->imc_dip, CE_WARN,
"!missing device for socket %u/"
"imc %u/channel %u", sock, mc,
chan);
return (B_FALSE);
}
}
}
}
return (B_TRUE);
}
static void
imc_map_sockets(imc_t *imc)
{
uint_t i;
for (i = 0; i < imc->imc_nsockets; i++) {
uint32_t nodeid;
ddi_acc_handle_t h;
h = imc->imc_sockets[i].isock_ubox->istub_cfgspace;
nodeid = pci_config_get32(h,
imc->imc_gen_data->igd_sad_nodeid_offset);
if (nodeid == PCI_EINVAL32) {
imc->imc_sockets[i].isock_valid |=
IMC_SOCKET_V_BAD_NODEID;
continue;
}
imc->imc_sockets[i].isock_nodeid = IMC_NODEID_UBOX_MASK(nodeid);
imc->imc_spointers[nodeid] = &imc->imc_sockets[i];
}
}
static void
imc_decode_mtr(imc_t *imc, imc_mc_t *icn, imc_dimm_t *dimm, uint32_t mtr)
{
uint8_t disable;
if (imc->imc_gen < IMC_GEN_SKYLAKE &&
IMC_MTR_PRESENT_SNB_BRD(mtr) == 0) {
dimm->idimm_present = B_FALSE;
return;
} else if (imc->imc_gen >= IMC_GEN_SKYLAKE &&
IMC_MTR_PRESENT_SKYLAKE(mtr) == 0) {
dimm->idimm_present = B_FALSE;
return;
}
dimm->idimm_present = B_TRUE;
dimm->idimm_ncolumns = IMC_MTR_CA_WIDTH(mtr) + IMC_MTR_CA_BASE;
if (dimm->idimm_ncolumns < IMC_MTR_CA_MIN ||
dimm->idimm_ncolumns > IMC_MTR_CA_MAX) {
dimm->idimm_valid |= IMC_DIMM_V_BAD_COLUMNS;
}
dimm->idimm_nrows = IMC_MTR_RA_WIDTH(mtr) + IMC_MTR_RA_BASE;
if (dimm->idimm_nrows < IMC_MTR_RA_MIN ||
dimm->idimm_nrows > IMC_MTR_RA_MAX) {
dimm->idimm_valid |= IMC_DIMM_V_BAD_ROWS;
}
switch (imc->imc_gen) {
case IMC_GEN_IVY:
dimm->idimm_density = 1U << IMC_MTR_DENSITY_IVY_BRD(mtr);
break;
case IMC_GEN_HASWELL:
case IMC_GEN_BROADWELL:
switch (IMC_MTR_DENSITY_IVY_BRD(mtr)) {
case 0:
default:
dimm->idimm_density = 0;
dimm->idimm_valid |= IMC_DIMM_V_BAD_DENSITY;
break;
case 1:
dimm->idimm_density = 2;
break;
case 2:
dimm->idimm_density = 4;
break;
case 3:
dimm->idimm_density = 8;
break;
}
break;
case IMC_GEN_SKYLAKE:
switch (IMC_MTR_DENSITY_SKX(mtr)) {
case 0:
default:
dimm->idimm_density = 0;
dimm->idimm_valid |= IMC_DIMM_V_BAD_DENSITY;
break;
case 1:
dimm->idimm_density = 2;
break;
case 2:
dimm->idimm_density = 4;
break;
case 3:
dimm->idimm_density = 8;
break;
case 4:
dimm->idimm_density = 16;
break;
case 5:
dimm->idimm_density = 12;
break;
}
break;
case IMC_GEN_UNKNOWN:
case IMC_GEN_SANDY:
dimm->idimm_density = 0;
break;
}
if (imc->imc_gen > IMC_GEN_SANDY) {
uint8_t width;
if (imc->imc_gen >= IMC_GEN_BROADWELL) {
width = IMC_MTR_WIDTH_BRD_SKX(mtr);
} else {
width = IMC_MTR_WIDTH_IVB_HAS(mtr);
}
switch (width) {
case 0:
dimm->idimm_width = 4;
break;
case 1:
dimm->idimm_width = 8;
break;
case 2:
dimm->idimm_width = 16;
break;
default:
dimm->idimm_width = 0;
dimm->idimm_valid |= IMC_DIMM_V_BAD_WIDTH;
break;
}
} else {
dimm->idimm_width = 0;
}
dimm->idimm_nranks = 1 << IMC_MTR_DDR_RANKS(mtr);
switch (imc->imc_gen) {
case IMC_GEN_HASWELL:
case IMC_GEN_BROADWELL:
case IMC_GEN_SKYLAKE:
if (dimm->idimm_nranks > IMC_MTR_DDR_RANKS_MAX_HAS_SKX) {
dimm->idimm_nranks = 0;
dimm->idimm_valid |= IMC_DIMM_V_BAD_RANKS;
}
break;
default:
if (dimm->idimm_nranks > IMC_MTR_DDR_RANKS_MAX) {
dimm->idimm_nranks = 0;
dimm->idimm_valid |= IMC_DIMM_V_BAD_RANKS;
}
}
disable = IMC_MTR_RANK_DISABLE(mtr);
dimm->idimm_ranks_disabled[0] = (disable & 0x1) != 0;
dimm->idimm_ranks_disabled[1] = (disable & 0x2) != 0;
dimm->idimm_ranks_disabled[2] = (disable & 0x4) != 0;
dimm->idimm_ranks_disabled[3] = (disable & 0x8) != 0;
if (imc->imc_gen >= IMC_GEN_HASWELL) {
dimm->idimm_hdrl = IMC_MTR_HDRL_HAS_SKX(mtr) != 0;
dimm->idimm_hdrl_parity = IMC_MTR_HDRL_PARITY_HAS_SKX(mtr) != 0;
dimm->idimm_3dsranks = IMC_MTR_3DSRANKS_HAS_SKX(mtr);
if (dimm->idimm_3dsranks != 0) {
dimm->idimm_3dsranks = 1 << dimm->idimm_3dsranks;
}
}
if (icn->icn_dimm_type == IMC_DIMM_DDR4) {
dimm->idimm_nbanks = 16;
} else {
dimm->idimm_nbanks = 8;
}
dimm->idimm_size = dimm->idimm_nbanks * dimm->idimm_nranks * 8 *
(1ULL << (dimm->idimm_ncolumns + dimm->idimm_nrows));
}
static void
imc_fill_dimms(imc_t *imc, imc_mc_t *icn, imc_channel_t *chan)
{
uint_t i;
chan->ich_ndimms = imc->imc_gen_data->igd_max_dimms;
for (i = 0; i < imc->imc_gen_data->igd_max_dimms; i++) {
uint32_t mtr;
imc_dimm_t *dimm = &chan->ich_dimms[i];
bzero(dimm, sizeof (imc_dimm_t));
mtr = pci_config_get32(chan->ich_desc->istub_cfgspace,
imc->imc_gen_data->igd_mtr_offsets[i]);
dimm->idimm_mtr = mtr;
if (mtr == PCI_EINVAL32) {
dimm->idimm_valid |= IMC_DIMM_V_BAD_PCI_READ;
continue;
}
imc_decode_mtr(imc, icn, dimm, mtr);
}
}
static boolean_t
imc_fill_controller(imc_t *imc, imc_mc_t *icn)
{
uint32_t mcmtr;
mcmtr = pci_config_get32(icn->icn_main0->istub_cfgspace,
imc->imc_gen_data->igd_mcmtr_offset);
if (mcmtr == PCI_EINVAL32) {
icn->icn_invalid = B_TRUE;
return (B_FALSE);
}
icn->icn_closed = IMC_MCMTR_CLOSED_PAGE(mcmtr) != 0;
if (imc->imc_gen < IMC_GEN_SKYLAKE) {
icn->icn_lockstep = IMC_MCMTR_LOCKSTEP(mcmtr) != 0;
} else {
icn->icn_lockstep = B_FALSE;
}
icn->icn_ecc = IMC_MCMTR_ECC_ENABLED(mcmtr) != 0;
switch (imc->imc_gen) {
case IMC_GEN_SANDY:
case IMC_GEN_IVY:
icn->icn_dimm_type = IMC_DIMM_DDR3;
break;
case IMC_GEN_HASWELL:
case IMC_GEN_BROADWELL:
if (IMC_MCMTR_DDR4_HAS_BRD(mcmtr)) {
icn->icn_dimm_type = IMC_DIMM_DDR4;
} else {
icn->icn_dimm_type = IMC_DIMM_DDR3;
}
break;
default:
icn->icn_dimm_type = IMC_DIMM_DDR4;
break;
}
if (imc->imc_gen >= IMC_GEN_SKYLAKE && icn->icn_m2m != NULL) {
icn->icn_topo = pci_config_get32(icn->icn_m2m->istub_cfgspace,
imc->imc_gen_data->igd_topo_offset);
}
return (B_TRUE);
}
static void
imc_fill_data(imc_t *imc)
{
uint_t csock, cmc, cchan;
for (csock = 0; csock < imc->imc_nsockets; csock++) {
imc_socket_t *sock = &imc->imc_sockets[csock];
for (cmc = 0; cmc < sock->isock_nimc; cmc++) {
imc_mc_t *icn = &sock->isock_imcs[cmc];
if (!imc_fill_controller(imc, icn))
continue;
for (cchan = 0; cchan < icn->icn_nchannels; cchan++) {
imc_fill_dimms(imc, icn,
&icn->icn_channels[cchan]);
}
}
}
}
static nvlist_t *
imc_nvl_create_dimm(imc_t *imc, imc_dimm_t *dimm)
{
nvlist_t *nvl;
nvl = fnvlist_alloc();
fnvlist_add_boolean_value(nvl, MCINTEL_NVLIST_V1_DIMM_PRESENT,
dimm->idimm_present);
if (!dimm->idimm_present) {
return (nvl);
}
fnvlist_add_uint64(nvl, MCINTEL_NVLIST_V1_DIMM_SIZE, dimm->idimm_size);
fnvlist_add_uint32(nvl, MCINTEL_NVLIST_V1_DIMM_NCOLS,
dimm->idimm_ncolumns);
fnvlist_add_uint32(nvl, MCINTEL_NVLIST_V1_DIMM_NROWS,
dimm->idimm_nrows);
if (imc->imc_gen > IMC_GEN_SANDY) {
fnvlist_add_uint64(nvl, MCINTEL_NVLIST_V1_DIMM_DENSITY,
dimm->idimm_density * (1ULL << 30));
fnvlist_add_uint32(nvl, MCINTEL_NVLIST_V1_DIMM_WIDTH,
dimm->idimm_width);
}
fnvlist_add_uint32(nvl, MCINTEL_NVLIST_V1_DIMM_RANKS,
dimm->idimm_nranks);
fnvlist_add_uint32(nvl, MCINTEL_NVLIST_V1_DIMM_BANKS,
dimm->idimm_nbanks);
fnvlist_add_boolean_array(nvl, MCINTEL_NVLIST_V1_DIMM_RDIS,
dimm->idimm_ranks_disabled, IMC_MAX_RANK_DISABLE);
if (imc->imc_gen >= IMC_GEN_HASWELL) {
fnvlist_add_boolean_value(nvl, MCINTEL_NVLIST_V1_DIMM_HDRL,
dimm->idimm_hdrl);
fnvlist_add_boolean_value(nvl, MCINTEL_NVLIST_V1_DIMM_HDRLP,
dimm->idimm_hdrl_parity);
if (dimm->idimm_3dsranks > 0) {
fnvlist_add_uint32(nvl, MCINTEL_NVLIST_V1_DIMM_3DRANK,
dimm->idimm_3dsranks);
}
}
return (nvl);
}
static nvlist_t *
imc_nvl_create_channel(imc_t *imc, imc_channel_t *chan)
{
nvlist_t *nvl;
nvlist_t *dimms[IMC_MAX_DIMMPERCHAN];
uint_t i;
nvl = fnvlist_alloc();
fnvlist_add_uint32(nvl, MCINTEL_NVLIST_V1_CHAN_NDPC,
imc->imc_gen_data->igd_max_dimms);
for (i = 0; i < imc->imc_gen_data->igd_max_dimms; i++) {
dimms[i] = imc_nvl_create_dimm(imc, &chan->ich_dimms[i]);
}
fnvlist_add_nvlist_array(nvl, MCINTEL_NVLIST_V1_CHAN_DIMMS,
dimms, i);
for (; i > 0; i--) {
nvlist_free(dimms[i-1]);
}
return (nvl);
}
static nvlist_t *
imc_nvl_create_mc(imc_t *imc, imc_mc_t *icn)
{
nvlist_t *nvl;
nvlist_t *channels[IMC_MAX_CHANPERMC];
uint_t i;
nvl = fnvlist_alloc();
fnvlist_add_uint32(nvl, MCINTEL_NVLIST_V1_MC_NCHAN, icn->icn_nchannels);
fnvlist_add_boolean_value(nvl, MCINTEL_NVLIST_V1_MC_ECC,
icn->icn_ecc);
if (icn->icn_lockstep) {
fnvlist_add_string(nvl, MCINTEL_NVLIST_V1_MC_CHAN_MODE,
MCINTEL_NVLIST_V1_MC_CHAN_MODE_LOCK);
} else {
fnvlist_add_string(nvl, MCINTEL_NVLIST_V1_MC_CHAN_MODE,
MCINTEL_NVLIST_V1_MC_CHAN_MODE_INDEP);
}
if (icn->icn_closed) {
fnvlist_add_string(nvl, MCINTEL_NVLIST_V1_MC_POLICY,
MCINTEL_NVLIST_V1_MC_POLICY_CLOSED);
} else {
fnvlist_add_string(nvl, MCINTEL_NVLIST_V1_MC_POLICY,
MCINTEL_NVLIST_V1_MC_POLICY_OPEN);
}
for (i = 0; i < icn->icn_nchannels; i++) {
channels[i] = imc_nvl_create_channel(imc,
&icn->icn_channels[i]);
}
fnvlist_add_nvlist_array(nvl, MCINTEL_NVLIST_V1_MC_CHANNELS,
channels, icn->icn_nchannels);
for (i = 0; i < icn->icn_nchannels; i++) {
nvlist_free(channels[i]);
}
return (nvl);
}
static void
imc_nvl_pack(imc_socket_t *sock, boolean_t sleep)
{
char *buf = NULL;
size_t len = 0;
int kmflag;
if (sock->isock_nvl == NULL)
return;
if (sock->isock_buf != NULL)
return;
if (sleep) {
kmflag = KM_SLEEP;
} else {
kmflag = KM_NOSLEEP_LAZY;
}
if (nvlist_pack(sock->isock_nvl, &buf, &len, NV_ENCODE_XDR,
kmflag) != 0) {
return;
}
sock->isock_buf = buf;
sock->isock_buflen = len;
sock->isock_gen++;
}
static void
imc_decoder_pack(imc_t *imc)
{
char *buf = NULL;
size_t len = 0;
if (imc->imc_decoder_buf != NULL)
return;
if (imc->imc_decoder_dump == NULL) {
imc->imc_decoder_dump = imc_dump_decoder(imc);
}
if (nvlist_pack(imc->imc_decoder_dump, &buf, &len, NV_ENCODE_XDR,
KM_NOSLEEP_LAZY) != 0) {
return;
}
imc->imc_decoder_buf = buf;
imc->imc_decoder_len = len;
}
static void
imc_nvl_create(imc_t *imc)
{
uint_t csock;
for (csock = 0; csock < imc->imc_nsockets; csock++) {
uint_t i;
nvlist_t *nvl;
nvlist_t *mcs[IMC_MAX_IMCPERSOCK];
imc_socket_t *sock = &imc->imc_sockets[csock];
nvl = fnvlist_alloc();
fnvlist_add_uint8(nvl, MCINTEL_NVLIST_VERSTR,
MCINTEL_NVLIST_VERS1);
fnvlist_add_uint8(nvl, MCINTEL_NVLIST_V1_NMC,
sock->isock_nimc);
for (i = 0; i < sock->isock_nimc; i++) {
mcs[i] = imc_nvl_create_mc(imc, &sock->isock_imcs[i]);
}
fnvlist_add_nvlist_array(nvl, MCINTEL_NVLIST_V1_MCS,
mcs, sock->isock_nimc);
for (i = 0; i < sock->isock_nimc; i++) {
nvlist_free(mcs[i]);
}
sock->isock_nvl = nvl;
imc_nvl_pack(sock, B_TRUE);
}
}
static void
imc_sad_read_tohm(imc_t *imc, imc_sad_t *sad)
{
uint32_t tolm, tohm_low, tohm_hi;
tolm = pci_config_get32(sad->isad_tolh->istub_cfgspace,
imc->imc_gen_data->igd_tolm_offset);
tohm_low = pci_config_get32(sad->isad_tolh->istub_cfgspace,
imc->imc_gen_data->igd_tohm_low_offset);
if (imc->imc_gen_data->igd_tohm_hi_offset != 0) {
tohm_hi = pci_config_get32(sad->isad_tolh->istub_cfgspace,
imc->imc_gen_data->igd_tohm_hi_offset);
} else {
tohm_hi = 0;
}
if (tolm == PCI_EINVAL32 || tohm_low == PCI_EINVAL32 ||
tohm_hi == PCI_EINVAL32) {
sad->isad_valid |= IMC_SAD_V_BAD_PCI_READ;
return;
}
switch (imc->imc_gen) {
case IMC_GEN_SANDY:
case IMC_GEN_IVY:
sad->isad_tolm = ((uint64_t)tolm & IMC_TOLM_SNB_IVY_MASK) <<
IMC_TOLM_SNB_IVY_SHIFT;
sad->isad_tohm = ((uint64_t)tohm_low & IMC_TOHM_SNB_IVY_MASK) <<
IMC_TOLM_SNB_IVY_SHIFT;
break;
case IMC_GEN_HASWELL:
case IMC_GEN_BROADWELL:
case IMC_GEN_SKYLAKE:
sad->isad_tolm = (uint64_t)tolm & IMC_TOLM_HAS_SKX_MASK;
sad->isad_tohm = ((uint64_t)tohm_low &
IMC_TOHM_LOW_HAS_SKX_MASK) | ((uint64_t)tohm_hi << 32);
sad->isad_tolm += IMC_TOLM_HAS_SKY_EXCL;
sad->isad_tohm += IMC_TOHM_HAS_SKY_EXCL;
break;
default:
dev_err(imc->imc_dip, CE_PANIC, "imc driver programmer error: "
"set to unknown generation: %u", imc->imc_gen);
return;
}
}
static void
imc_sad_fill_rule(imc_t *imc, imc_sad_t *sad, imc_sad_rule_t *rule,
uint32_t raw)
{
uint_t attr;
uint64_t limit;
bzero(rule, sizeof (imc_sad_rule_t));
rule->isr_raw_dram = raw;
rule->isr_enable = IMC_SAD_DRAM_RULE_ENABLE(raw) != 0;
if (imc->imc_gen < IMC_GEN_SKYLAKE) {
switch (IMC_SAD_DRAM_INTERLEAVE_SNB_BRD(raw)) {
case IMC_SAD_DRAM_INTERLEAVE_SNB_BRD_8t6:
rule->isr_imode = IMC_SAD_IMODE_8t6;
break;
case IMC_SAD_DRAM_INTERLEAVE_SNB_BRD_8t6XOR:
rule->isr_imode = IMC_SAD_IMODE_8t6XOR;
break;
}
} else {
switch (IMC_SAD_DRAM_INTERLEAVE_SKX(raw)) {
case IMC_SAD_DRAM_INTERLEAVE_SKX_8t6:
rule->isr_imode = IMC_SAD_IMODE_8t6;
break;
case IMC_SAD_DRAM_INTERLEAVE_SKX_10t8:
rule->isr_imode = IMC_SAD_IMODE_10t8;
break;
case IMC_SAD_DRAM_INTERLEAVE_SKX_14t12:
rule->isr_imode = IMC_SAD_IMODE_14t12;
break;
case IMC_SAD_DRAM_INTERLEAVE_SKX_32t30:
rule->isr_imode = IMC_SAD_IMODE_32t30;
break;
}
}
if (imc->imc_gen >= IMC_GEN_SKYLAKE) {
attr = IMC_SAD_DRAM_ATTR_SKX(raw);
} else {
attr = IMC_SAD_DRAM_ATTR_SNB_BRD(raw);
}
switch (attr) {
case IMC_SAD_DRAM_ATTR_DRAM:
rule->isr_type = IMC_SAD_TYPE_DRAM;
break;
case IMC_SAD_DRAM_ATTR_MMCFG:
rule->isr_type = IMC_SAD_TYPE_MMCFG;
break;
case IMC_SAD_DRAM_ATTR_NXM:
if (imc->imc_gen < IMC_GEN_SKYLAKE) {
sad->isad_valid |= IMC_SAD_V_BAD_DRAM_ATTR;
}
rule->isr_type = IMC_SAD_TYPE_NXM;
break;
default:
sad->isad_valid |= IMC_SAD_V_BAD_DRAM_ATTR;
break;
}
if (imc->imc_gen >= IMC_GEN_SKYLAKE) {
limit = IMC_SAD_DRAM_LIMIT_SKX(raw);
} else {
limit = IMC_SAD_DRAM_LIMIT_SNB_BRD(raw);
}
rule->isr_limit = (limit << IMC_SAD_DRAM_LIMIT_SHIFT) +
IMC_SAD_DRAM_LIMIT_EXCLUSIVE;
if (imc->imc_gen == IMC_GEN_SANDY)
return;
if (imc->imc_gen >= IMC_GEN_IVY && imc->imc_gen < IMC_GEN_SKYLAKE) {
rule->isr_a7mode = IMC_SAD_DRAM_A7_IVB_BRD(raw) != 0;
return;
}
switch (IMC_SAD_DRAM_MOD23_SKX(raw)) {
case IMC_SAD_DRAM_MOD23_MOD3:
rule->isr_mod_type = IMC_SAD_MOD_TYPE_MOD3;
break;
case IMC_SAD_DRAM_MOD23_MOD2_C01:
rule->isr_mod_type = IMC_SAD_MOD_TYPE_MOD2_01;
break;
case IMC_SAD_DRAM_MOD23_MOD2_C12:
rule->isr_mod_type = IMC_SAD_MOD_TYPE_MOD2_12;
break;
case IMC_SAD_DRAM_MOD23_MOD2_C02:
rule->isr_mod_type = IMC_SAD_MOD_TYPE_MOD2_02;
break;
}
rule->isr_need_mod3 = IMC_SAD_DRAM_MOD3_SKX(raw) != 0;
switch (IMC_SAD_DRAM_MOD3_SKX(raw)) {
case IMC_SAD_DRAM_MOD3_MODE_45t6:
rule->isr_mod_mode = IMC_SAD_MOD_MODE_45t6;
break;
case IMC_SAD_DRAM_MOD3_MODE_45t8:
rule->isr_mod_mode = IMC_SAD_MOD_MODE_45t8;
break;
case IMC_SAD_DRAM_MOD3_MODE_45t12:
rule->isr_mod_mode = IMC_SAD_MOD_MODE_45t12;
break;
default:
sad->isad_valid |= IMC_SAD_V_BAD_MOD3;
break;
}
}
static void
imc_sad_fill_rule_interleave(imc_t *imc, imc_sad_rule_t *rule, uint32_t raw)
{
uint_t i;
uint32_t mlen, mbase, skipbits, skipafter;
rule->isr_raw_interleave = raw;
rule->isr_ntargets = IMC_MAX_SAD_INTERLEAVE;
if (imc->imc_gen > IMC_GEN_SANDY) {
mlen = IMC_SAD_ILEAVE_IVB_SKX_LEN;
mbase = IMC_SAD_ILEAVE_IVB_SKX_MASK;
skipbits = skipafter = 0;
} else {
mlen = IMC_SAD_ILEAVE_SNB_LEN;
mbase = IMC_SAD_ILEAVE_SNB_MASK;
skipbits = 2;
skipafter = 4;
}
for (i = 0; i < rule->isr_ntargets; i++) {
uint32_t mask, shift;
shift = i * mlen;
if (i >= skipafter)
shift += skipbits;
mask = mbase << shift;
rule->isr_targets[i] = (raw & mask) >> shift;
}
}
static void
imc_sad_read_dram_rules(imc_t *imc, imc_sad_t *sad)
{
uint_t i;
off_t off;
sad->isad_nrules = imc->imc_gen_data->igd_sad_ndram_rules;
for (i = 0, off = imc->imc_gen_data->igd_sad_dram_offset;
i < sad->isad_nrules; i++, off += sizeof (uint64_t)) {
uint32_t dram, interleave;
imc_sad_rule_t *rule = &sad->isad_rules[i];
dram = pci_config_get32(sad->isad_dram->istub_cfgspace, off);
interleave = pci_config_get32(sad->isad_dram->istub_cfgspace,
off + 4);
if (dram == PCI_EINVAL32 || interleave == PCI_EINVAL32) {
sad->isad_valid |= IMC_SAD_V_BAD_PCI_READ;
return;
}
imc_sad_fill_rule(imc, sad, rule, dram);
imc_sad_fill_rule_interleave(imc, rule, interleave);
}
}
static void
imc_sad_decode_mcroute(imc_t *imc, imc_sad_t *sad)
{
uint_t i;
imc_sad_mcroute_table_t *mc = &sad->isad_mcroute;
if (imc->imc_gen < IMC_GEN_SKYLAKE)
return;
if (sad->isad_valid != 0)
return;
mc->ismc_nroutes = IMC_MAX_SAD_MCROUTES;
for (i = 0; i < IMC_MAX_SAD_MCROUTES; i++) {
uint_t chanoff, ringoff;
ringoff = i * IMC_MC_ROUTE_RING_BITS;
chanoff = i * IMC_MC_ROUTE_CHAN_BITS + IMC_MC_ROUTE_CHAN_OFFSET;
mc->ismc_mcroutes[i].ismce_imc = (mc->ismc_raw_mcroute >>
ringoff) & IMC_MC_ROUTE_RING_MASK;
mc->ismc_mcroutes[i].ismce_pchannel = (mc->ismc_raw_mcroute >>
chanoff) & IMC_MC_ROUTE_CHAN_MASK;
}
}
static void
imc_decoder_init_sad(imc_t *imc)
{
uint_t i;
for (i = 0; i < imc->imc_nsockets; i++) {
imc_sad_read_tohm(imc, &imc->imc_sockets[i].isock_sad);
imc_sad_read_dram_rules(imc, &imc->imc_sockets[i].isock_sad);
imc_sad_decode_mcroute(imc, &imc->imc_sockets[i].isock_sad);
}
}
static void
imc_tad_fill_rule(imc_t *imc, imc_tad_t *tad, imc_tad_rule_t *prev,
imc_tad_rule_t *rule, uint32_t val)
{
uint64_t limit;
limit = IMC_TAD_LIMIT(val);
rule->itr_limit = (limit << IMC_TAD_LIMIT_SHIFT) +
IMC_TAD_LIMIT_EXCLUSIVE;
rule->itr_raw = val;
switch (IMC_TAD_SOCK_WAY(val)) {
case IMC_TAD_SOCK_WAY_1:
rule->itr_sock_way = 1;
break;
case IMC_TAD_SOCK_WAY_2:
rule->itr_sock_way = 2;
break;
case IMC_TAD_SOCK_WAY_4:
rule->itr_sock_way = 4;
break;
case IMC_TAD_SOCK_WAY_8:
rule->itr_sock_way = 8;
break;
}
rule->itr_chan_way = IMC_TAD_CHAN_WAY(val) + 1;
rule->itr_sock_gran = IMC_TAD_GRAN_64B;
rule->itr_chan_gran = IMC_TAD_GRAN_64B;
if (imc->imc_gen >= IMC_GEN_SKYLAKE) {
rule->itr_ntargets = 0;
return;
}
rule->itr_ntargets = IMC_TAD_SNB_BRD_NTARGETS;
rule->itr_targets[0] = IMC_TAD_TARG0(val);
rule->itr_targets[1] = IMC_TAD_TARG1(val);
rule->itr_targets[2] = IMC_TAD_TARG2(val);
rule->itr_targets[3] = IMC_TAD_TARG3(val);
if (prev == NULL) {
rule->itr_base = 0;
} else {
rule->itr_base = prev->itr_limit + 1;
}
}
static void
imc_tad_fill_skx(imc_t *imc, imc_tad_t *tad, imc_tad_rule_t *rule,
uint32_t val)
{
uint64_t base;
rule->itr_raw_gran = val;
base = IMC_TAD_BASE_BASE(val);
rule->itr_base = base << IMC_TAD_BASE_SHIFT;
switch (IMC_TAD_BASE_CHAN_GRAN(val)) {
case IMC_TAD_BASE_CHAN_GRAN_64B:
rule->itr_sock_gran = IMC_TAD_GRAN_64B;
break;
case IMC_TAD_BASE_CHAN_GRAN_256B:
rule->itr_sock_gran = IMC_TAD_GRAN_256B;
break;
case IMC_TAD_BASE_CHAN_GRAN_4KB:
rule->itr_sock_gran = IMC_TAD_GRAN_4KB;
break;
default:
tad->itad_valid |= IMC_TAD_V_BAD_CHAN_GRAN;
return;
}
switch (IMC_TAD_BASE_SOCK_GRAN(val)) {
case IMC_TAD_BASE_SOCK_GRAN_64B:
rule->itr_sock_gran = IMC_TAD_GRAN_64B;
break;
case IMC_TAD_BASE_SOCK_GRAN_256B:
rule->itr_sock_gran = IMC_TAD_GRAN_256B;
break;
case IMC_TAD_BASE_SOCK_GRAN_4KB:
rule->itr_sock_gran = IMC_TAD_GRAN_4KB;
break;
case IMC_TAD_BASE_SOCK_GRAN_1GB:
rule->itr_sock_gran = IMC_TAD_GRAN_1GB;
break;
}
}
static void
imc_tad_read_rules(imc_t *imc, imc_tad_t *tad)
{
uint_t i;
off_t baseoff;
imc_tad_rule_t *prev;
tad->itad_nrules = imc->imc_gen_data->igd_tad_nrules;
for (i = 0, baseoff = imc->imc_gen_data->igd_tad_rule_offset,
prev = NULL; i < tad->itad_nrules;
i++, baseoff += sizeof (uint32_t)) {
uint32_t val;
off_t off;
imc_tad_rule_t *rule = &tad->itad_rules[i];
if (imc->imc_gen >= IMC_GEN_SKYLAKE) {
off = baseoff + IMC_SKX_WAYNESS_OFFSET;
} else {
off = baseoff;
}
val = pci_config_get32(tad->itad_stub->istub_cfgspace, off);
if (val == PCI_EINVAL32) {
tad->itad_valid |= IMC_TAD_V_BAD_PCI_READ;
return;
}
imc_tad_fill_rule(imc, tad, prev, rule, val);
prev = rule;
if (imc->imc_gen < IMC_GEN_SKYLAKE)
continue;
val = pci_config_get32(tad->itad_stub->istub_cfgspace, baseoff);
if (val == PCI_EINVAL32) {
tad->itad_valid |= IMC_TAD_V_BAD_PCI_READ;
return;
}
imc_tad_fill_skx(imc, tad, rule, val);
}
}
static void
imc_tad_read_features(imc_t *imc, imc_tad_t *tad, imc_mc_t *mc)
{
uint32_t val;
switch (imc->imc_gen) {
case IMC_GEN_SANDY:
case IMC_GEN_IVY:
case IMC_GEN_HASWELL:
case IMC_GEN_BROADWELL:
val = pci_config_get32(tad->itad_stub->istub_cfgspace,
imc->imc_gen_data->igd_tad_sysdef);
if (val == PCI_EINVAL32) {
tad->itad_valid |= IMC_TAD_V_BAD_PCI_READ;
return;
}
if (IMC_TAD_SYSDEF_LOCKSTEP(val)) {
tad->itad_flags |= IMC_TAD_FLAG_LOCKSTEP;
}
val = pci_config_get32(mc->icn_main1->istub_cfgspace,
imc->imc_gen_data->igd_mc_mirror);
if (val == PCI_EINVAL32) {
tad->itad_valid |= IMC_TAD_V_BAD_PCI_READ;
return;
}
if (IMC_MC_MIRROR_SNB_BRD(val)) {
tad->itad_flags |= IMC_TAD_FLAG_MIRROR;
}
break;
default:
break;
}
switch (imc->imc_gen) {
case IMC_GEN_IVY:
case IMC_GEN_HASWELL:
case IMC_GEN_BROADWELL:
val = pci_config_get32(tad->itad_stub->istub_cfgspace,
imc->imc_gen_data->igd_tad_sysdef2);
if (val == PCI_EINVAL32) {
tad->itad_valid |= IMC_TAD_V_BAD_PCI_READ;
return;
}
if (IMC_TAD_SYSDEF2_SHIFTUP(val)) {
tad->itad_flags |= IMC_TAD_FLAG_CHANSHIFT;
}
if (IMC_TAD_SYSDEF2_SHIFTUP(val)) {
tad->itad_flags |= IMC_TAD_FLAG_CHANHASH;
}
break;
default:
break;
}
}
static void
imc_tad_read_interleave(imc_t *imc, imc_channel_t *chan)
{
uint_t i;
off_t off;
chan->ich_ntad_offsets = imc->imc_gen_data->igd_tad_nrules;
for (i = 0, off = imc->imc_gen_data->igd_tad_chan_offset;
i < chan->ich_ntad_offsets; i++, off += sizeof (uint32_t)) {
uint32_t val;
uint64_t offset;
val = pci_config_get32(chan->ich_desc->istub_cfgspace,
off);
if (val == PCI_EINVAL32) {
chan->ich_valid |= IMC_CHANNEL_V_BAD_PCI_READ;
return;
}
if (imc->imc_gen >= IMC_GEN_SKYLAKE) {
offset = IMC_TADCHAN_OFFSET_SKX(val);
} else {
offset = IMC_TADCHAN_OFFSET_SNB_BRD(val);
}
chan->ich_tad_offsets[i] = offset << IMC_TADCHAN_OFFSET_SHIFT;
chan->ich_tad_offsets_raw[i] = val;
}
}
static void
imc_decoder_init_tad(imc_t *imc)
{
uint_t i;
for (i = 0; i < imc->imc_nsockets; i++) {
uint_t j;
for (j = 0; j < imc->imc_sockets[i].isock_ntad; j++) {
imc_tad_read_features(imc,
&imc->imc_sockets[i].isock_tad[j],
&imc->imc_sockets[i].isock_imcs[j]);
imc_tad_read_rules(imc,
&imc->imc_sockets[i].isock_tad[j]);
}
}
for (i = 0; i < imc->imc_nsockets; i++) {
uint_t j;
imc_socket_t *sock = &imc->imc_sockets[i];
for (j = 0; j < imc->imc_sockets[i].isock_nimc; j++) {
uint_t k;
imc_mc_t *mc = &sock->isock_imcs[j];
for (k = 0; k < mc->icn_nchannels; k++) {
imc_channel_t *chan = &mc->icn_channels[k];
imc_tad_read_interleave(imc, chan);
}
}
}
}
static void
imc_rir_read_ileave_offsets(imc_t *imc, imc_channel_t *chan,
imc_rank_ileave_t *rank, uint_t rirno, boolean_t contig)
{
uint_t i;
off_t off, incr;
if (contig) {
off = imc->imc_gen_data->igd_rir_ileave_offset +
(rirno * imc->imc_gen_data->igd_rir_nileaves *
sizeof (uint32_t));
incr = sizeof (uint32_t);
} else {
off = imc->imc_gen_data->igd_rir_ileave_offset +
(rirno * sizeof (uint32_t));
incr = imc->imc_gen_data->igd_rir_nileaves * sizeof (uint32_t);
}
for (i = 0; i < rank->irle_nentries; i++, off += incr) {
uint32_t val;
uint64_t offset;
imc_rank_ileave_entry_t *ent = &rank->irle_entries[i];
val = pci_config_get32(chan->ich_desc->istub_cfgspace, off);
if (val == PCI_EINVAL32) {
chan->ich_valid |= IMC_CHANNEL_V_BAD_PCI_READ;
return;
}
switch (imc->imc_gen) {
case IMC_GEN_BROADWELL:
ent->irle_target = IMC_RIR_OFFSET_TARGET_BRD(val);
break;
default:
ent->irle_target = IMC_RIR_OFFSET_TARGET(val);
break;
}
if (imc->imc_gen >= IMC_GEN_HASWELL) {
offset = IMC_RIR_OFFSET_OFFSET_HAS_SKX(val);
} else {
offset = IMC_RIR_OFFSET_OFFSET_SNB_IVB(val);
}
ent->irle_offset = offset << IMC_RIR_OFFSET_SHIFT;
}
}
static void
imc_rir_read_wayness(imc_t *imc, imc_channel_t *chan)
{
uint_t i;
off_t off;
chan->ich_nrankileaves = imc->imc_gen_data->igd_rir_nways;
for (i = 0, off = imc->imc_gen_data->igd_rir_way_offset;
i < chan->ich_nrankileaves; i++, off += sizeof (uint32_t)) {
uint32_t val;
uint64_t lim;
imc_rank_ileave_t *ent = &chan->ich_rankileaves[i];
val = pci_config_get32(chan->ich_desc->istub_cfgspace, off);
if (val == PCI_EINVAL32) {
chan->ich_valid |= IMC_CHANNEL_V_BAD_PCI_READ;
return;
}
ent->irle_raw = val;
ent->irle_enabled = IMC_RIR_WAYNESS_ENABLED(val) != 0;
ent->irle_nways = 1 << IMC_RIR_WAYNESS_WAY(val);
ent->irle_nwaysbits = IMC_RIR_WAYNESS_WAY(val);
if (imc->imc_gen >= IMC_GEN_HASWELL) {
lim = IMC_RIR_LIMIT_HAS_SKX(val);
} else {
lim = IMC_RIR_LIMIT_SNB_IVB(val);
}
ent->irle_limit = (lim << IMC_RIR_LIMIT_SHIFT) +
IMC_RIR_LIMIT_EXCLUSIVE;
ent->irle_nentries = imc->imc_gen_data->igd_rir_nileaves;
if (imc->imc_gen >= IMC_GEN_SKYLAKE) {
imc_rir_read_ileave_offsets(imc, chan, ent, i, B_FALSE);
} else {
imc_rir_read_ileave_offsets(imc, chan, ent, i, B_TRUE);
}
}
}
static void
imc_decoder_init_rir(imc_t *imc)
{
uint_t i;
for (i = 0; i < imc->imc_nsockets; i++) {
uint_t j;
imc_socket_t *sock = &imc->imc_sockets[i];
for (j = 0; j < imc->imc_sockets[i].isock_nimc; j++) {
uint_t k;
imc_mc_t *mc = &sock->isock_imcs[j];
for (k = 0; k < mc->icn_nchannels; k++) {
imc_channel_t *chan = &mc->icn_channels[k];
imc_rir_read_wayness(imc, chan);
}
}
}
}
static cmi_errno_t
imc_mc_patounum(void *arg, uint64_t pa, uint8_t valid_hi, uint8_t valid_lo,
uint32_t synd, int syndtype, mc_unum_t *unump)
{
imc_t *imc = arg;
uint_t i;
imc_decode_state_t dec;
bzero(&dec, sizeof (dec));
if (!imc_decode_pa(imc, pa, &dec)) {
switch (dec.ids_fail) {
case IMC_DECODE_F_LEGACY_RANGE:
case IMC_DECODE_F_OUTSIDE_DRAM:
return (CMIERR_MC_NOTDIMMADDR);
default:
return (CMIERR_MC_BADSTATE);
}
}
unump->unum_board = 0;
for (i = 0; i < imc->imc_nsockets; i++) {
if (imc->imc_spointers[i] == dec.ids_socket)
break;
}
if (i == imc->imc_nsockets) {
return (CMIERR_MC_BADSTATE);
}
unump->unum_chip = i;
unump->unum_mc = dec.ids_tadid;
unump->unum_chan = dec.ids_channelid;
unump->unum_cs = dec.ids_dimmid;
unump->unum_rank = dec.ids_rankid;
unump->unum_offset = dec.ids_rankaddr;
for (i = 0; i < MC_UNUM_NDIMM; i++) {
unump->unum_dimms[i] = MC_INVALNUM;
}
return (CMI_SUCCESS);
}
static cmi_errno_t
imc_mc_unumtopa(void *arg, mc_unum_t *unum, nvlist_t *nvl, uint64_t *pa)
{
return (CMIERR_UNKNOWN);
}
static const cmi_mc_ops_t imc_mc_ops = {
.cmi_mc_patounum = imc_mc_patounum,
.cmi_mc_unumtopa = imc_mc_unumtopa
};
static void
imc_attach_complete(void *arg)
{
imc_t *imc = arg;
cmi_errno_t err;
imc_set_gen_data(imc);
if (!imc_map_stubs(imc)) {
goto done;
}
if (!imc_validate_stubs(imc)) {
imc->imc_flags |= IMC_F_VALIDATE_FAILED;
goto done;
}
imc_fixup_stubs(imc);
imc_map_sockets(imc);
if (!imc_create_minors(imc)) {
goto done;
}
imc_fill_data(imc);
imc_nvl_create(imc);
imc_decoder_init_sad(imc);
imc_decoder_init_tad(imc);
imc_decoder_init_rir(imc);
if ((err = cmi_mc_register_global(&imc_mc_ops, imc)) != CMI_SUCCESS) {
imc->imc_flags |= IMC_F_MCREG_FAILED;
dev_err(imc->imc_dip, CE_WARN, "failed to register memory "
"decoding operations: 0x%x", err);
}
done:
mutex_enter(&imc->imc_lock);
imc->imc_flags &= IMC_F_ATTACH_DISPATCHED;
imc->imc_flags |= IMC_F_ATTACH_COMPLETE;
mutex_exit(&imc->imc_lock);
}
static int
imc_stub_comparator(const void *l, const void *r)
{
const imc_stub_t *sl = l, *sr = r;
if (sl->istub_bus > sr->istub_bus)
return (1);
if (sl->istub_bus < sr->istub_bus)
return (-1);
if (sl->istub_dev > sr->istub_dev)
return (1);
if (sl->istub_dev < sr->istub_dev)
return (-1);
if (sl->istub_func > sr->istub_func)
return (1);
if (sl->istub_func < sr->istub_func)
return (-1);
return (0);
}
static int
imc_stub_scan_cb(dev_info_t *dip, void *arg)
{
int vid, did;
const imc_stub_table_t *table;
imc_t *imc = arg;
int *regs;
uint_t i, nregs;
if (dip == ddi_root_node()) {
return (DDI_WALK_CONTINUE);
}
if (strncmp("pci", ddi_get_name(dip), 3) != 0) {
return (DDI_WALK_PRUNECHILD);
}
vid = ddi_prop_get_int(DDI_DEV_T_ANY, dip, DDI_PROP_DONTPASS,
"vendor-id", PCI_EINVAL16);
did = ddi_prop_get_int(DDI_DEV_T_ANY, dip, DDI_PROP_DONTPASS,
"device-id", PCI_EINVAL16);
if (vid == PCI_EINVAL16 || did == PCI_EINVAL16) {
return (DDI_WALK_CONTINUE);
}
if (vid != IMC_PCI_VENDOR_INTC) {
return (DDI_WALK_PRUNECHILD);
}
if (ddi_prop_lookup_int_array(DDI_DEV_T_ANY, dip, DDI_PROP_DONTPASS,
"reg", ®s, &nregs) != DDI_PROP_SUCCESS) {
return (DDI_WALK_CONTINUE);
}
if (nregs == 0) {
ddi_prop_free(regs);
return (DDI_WALK_CONTINUE);
}
table = NULL;
for (i = 0; i < ARRAY_SIZE(imc_stub_table); i++) {
if (imc_stub_table[i].imcs_devid == did &&
imc_stub_table[i].imcs_pcidev == PCI_REG_DEV_G(regs[0]) &&
imc_stub_table[i].imcs_pcifunc == PCI_REG_FUNC_G(regs[0])) {
table = &imc_stub_table[i];
break;
}
}
ddi_prop_free(regs);
if (table == NULL) {
return (DDI_WALK_CONTINUE);
}
mutex_enter(&imc->imc_lock);
imc->imc_nscanned++;
mutex_exit(&imc->imc_lock);
return (DDI_WALK_CONTINUE);
}
static void
imc_stub_scan(void *arg)
{
imc_t *imc = arg;
boolean_t dispatch = B_FALSE;
mutex_enter(&imc->imc_lock);
imc->imc_nscanned = 0;
mutex_exit(&imc->imc_lock);
ddi_walk_devs(ddi_root_node(), imc_stub_scan_cb, imc);
mutex_enter(&imc->imc_lock);
imc->imc_flags |= IMC_F_SCAN_COMPLETE;
imc->imc_flags &= ~IMC_F_SCAN_DISPATCHED;
if (imc->imc_nscanned == 0) {
imc->imc_flags |= IMC_F_UNSUP_PLATFORM;
mutex_exit(&imc->imc_lock);
return;
}
if (avl_numnodes(&imc->imc_stubs) == imc->imc_nscanned) {
imc->imc_flags |= IMC_F_ATTACH_DISPATCHED;
dispatch = B_TRUE;
}
mutex_exit(&imc->imc_lock);
if (dispatch) {
(void) ddi_taskq_dispatch(imc->imc_taskq, imc_attach_complete,
imc, DDI_SLEEP);
}
}
int
imc_detach_stub(dev_info_t *dip, ddi_detach_cmd_t cmd)
{
imc_stub_t *stub;
imc_t *imc = imc_data;
mutex_enter(&imc->imc_lock);
if ((imc->imc_flags & IMC_F_UNSUP_PLATFORM) == 0 &&
imc_allow_detach == 0) {
mutex_exit(&imc->imc_lock);
return (DDI_FAILURE);
}
for (stub = avl_first(&imc->imc_stubs); stub != NULL;
stub = AVL_NEXT(&imc->imc_stubs, stub)) {
if (stub->istub_dip == dip) {
break;
}
}
if (stub == NULL) {
mutex_exit(&imc->imc_lock);
return (DDI_SUCCESS);
}
pci_config_teardown(&stub->istub_cfgspace);
avl_remove(&imc->imc_stubs, stub);
kmem_free(stub, sizeof (imc_stub_t));
mutex_exit(&imc->imc_lock);
return (DDI_SUCCESS);
}
int
imc_attach_stub(dev_info_t *dip, ddi_attach_cmd_t cmd)
{
imc_stub_t *stub, *lookup;
int did, vid, *regs;
uint_t i, nregs;
const imc_stub_table_t *table;
avl_index_t idx;
boolean_t dispatch = B_FALSE;
imc_t *imc = imc_data;
if (cmd != DDI_ATTACH) {
return (DDI_FAILURE);
}
if (strncmp("pci", ddi_get_name(dip), 3) != 0) {
return (DDI_FAILURE);
}
vid = ddi_prop_get_int(DDI_DEV_T_ANY, dip, DDI_PROP_DONTPASS,
"vendor-id", PCI_EINVAL16);
did = ddi_prop_get_int(DDI_DEV_T_ANY, dip, DDI_PROP_DONTPASS,
"device-id", PCI_EINVAL16);
if (vid == PCI_EINVAL16 || did == PCI_EINVAL16) {
return (DDI_FAILURE);
}
if (vid != IMC_PCI_VENDOR_INTC) {
return (DDI_FAILURE);
}
if (ddi_prop_lookup_int_array(DDI_DEV_T_ANY, dip, DDI_PROP_DONTPASS,
"reg", ®s, &nregs) != DDI_PROP_SUCCESS) {
return (DDI_FAILURE);
}
if (nregs == 0) {
ddi_prop_free(regs);
return (DDI_FAILURE);
}
table = NULL;
for (i = 0; i < ARRAY_SIZE(imc_stub_table); i++) {
if (imc_stub_table[i].imcs_devid == did &&
imc_stub_table[i].imcs_pcidev == PCI_REG_DEV_G(regs[0]) &&
imc_stub_table[i].imcs_pcifunc == PCI_REG_FUNC_G(regs[0])) {
table = &imc_stub_table[i];
break;
}
}
if (i == ARRAY_SIZE(imc_stub_table)) {
ddi_prop_free(regs);
return (DDI_FAILURE);
}
mutex_enter(&imc->imc_lock);
if (imc->imc_gen != IMC_GEN_UNKNOWN && imc->imc_gen !=
table->imcs_gen) {
mutex_exit(&imc->imc_lock);
ddi_prop_free(regs);
dev_err(dip, CE_WARN, "Encountered IMC stub device (%u/%u) "
"that has different hardware generation (%u) from current "
"generation (%u)", vid, did, table->imcs_gen, imc->imc_gen);
return (DDI_FAILURE);
} else {
imc->imc_gen = table->imcs_gen;
}
mutex_exit(&imc->imc_lock);
stub = kmem_zalloc(sizeof (imc_stub_t), KM_SLEEP);
stub->istub_dip = dip;
stub->istub_vid = vid;
stub->istub_did = did;
stub->istub_bus = PCI_REG_BUS_G(regs[0]);
stub->istub_dev = PCI_REG_DEV_G(regs[0]);
stub->istub_func = PCI_REG_FUNC_G(regs[0]);
ddi_prop_free(regs);
stub->istub_table = table;
if (pci_config_setup(dip, &stub->istub_cfgspace) != DDI_SUCCESS) {
kmem_free(stub, sizeof (stub));
dev_err(dip, CE_WARN, "Failed to set up PCI config space "
"for IMC stub device %s (%u/%u)", ddi_node_name(dip),
vid, did);
return (DDI_FAILURE);
}
mutex_enter(&imc->imc_lock);
if ((lookup = avl_find(&imc->imc_stubs, stub, &idx)) != NULL) {
dev_err(dip, CE_WARN, "IMC stub %s (%u/%u) has duplicate "
"bdf %u/%u/%u with %s (%u/%u), not attaching",
ddi_node_name(imc->imc_dip), vid, did,
stub->istub_bus, stub->istub_dev, stub->istub_func,
ddi_node_name(lookup->istub_dip), lookup->istub_vid,
lookup->istub_did);
mutex_exit(&imc->imc_lock);
pci_config_teardown(&stub->istub_cfgspace);
kmem_free(stub, sizeof (stub));
return (DDI_FAILURE);
}
avl_insert(&imc->imc_stubs, stub, idx);
if ((imc->imc_flags & IMC_F_ALL_FLAGS) == IMC_F_SCAN_COMPLETE &&
avl_numnodes(&imc->imc_stubs) == imc->imc_nscanned) {
imc->imc_flags |= IMC_F_ATTACH_DISPATCHED;
dispatch = B_TRUE;
}
mutex_exit(&imc->imc_lock);
if (dispatch) {
(void) ddi_taskq_dispatch(imc->imc_taskq, imc_attach_complete,
imc, DDI_SLEEP);
}
return (DDI_SUCCESS);
}
static int
imc_open(dev_t *devp, int flag, int otyp, cred_t *credp)
{
imc_t *imc = imc_data;
if ((flag & (FEXCL | FNDELAY)) != 0)
return (EINVAL);
if (otyp != OTYP_CHR)
return (EINVAL);
mutex_enter(&imc->imc_lock);
if ((imc->imc_flags & IMC_F_UNSUP_PLATFORM) != 0) {
mutex_exit(&imc->imc_lock);
return (ENOTSUP);
}
if ((imc->imc_flags & IMC_F_ATTACH_COMPLETE) == 0) {
mutex_exit(&imc->imc_lock);
return (EAGAIN);
}
if (getminor(*devp) >= imc->imc_nsockets) {
mutex_exit(&imc->imc_lock);
return (EINVAL);
}
if (imc->imc_spointers[getminor(*devp)] == NULL) {
mutex_exit(&imc->imc_lock);
return (EINVAL);
}
mutex_exit(&imc->imc_lock);
return (0);
}
static void
imc_ioctl_decode(imc_t *imc, mc_encode_ioc_t *encode)
{
imc_decode_state_t dec;
uint_t i;
bzero(&dec, sizeof (dec));
if (!imc_decode_pa(imc, encode->mcei_pa, &dec)) {
encode->mcei_err = (uint32_t)dec.ids_fail;
encode->mcei_errdata = dec.ids_fail_data;
return;
}
encode->mcei_errdata = 0;
encode->mcei_err = 0;
encode->mcei_board = 0;
for (i = 0; i < imc->imc_nsockets; i++) {
if (imc->imc_spointers[i] == dec.ids_socket)
break;
}
encode->mcei_chip = i;
encode->mcei_die = 0;
encode->mcei_mc = dec.ids_tadid;
encode->mcei_chan_addr = dec.ids_chanaddr;
encode->mcei_chan = dec.ids_channelid;
encode->mcei_dimm = dec.ids_dimmid;
encode->mcei_rank_addr = dec.ids_rankaddr;
encode->mcei_rank = dec.ids_rankid;
encode->mcei_row = UINT32_MAX;
encode->mcei_column = UINT32_MAX;
encode->mcei_cs = encode->mcei_rm = UINT8_MAX;
encode->mcei_bank = encode->mcei_bank_group = UINT8_MAX;
encode->mcei_subchan = UINT8_MAX;
}
static int
imc_ioctl(dev_t dev, int cmd, intptr_t arg, int mode, cred_t *credp,
int *rvalp)
{
int ret;
minor_t m;
mc_snapshot_info_t info;
mc_encode_ioc_t encode;
imc_t *imc = imc_data;
imc_socket_t *sock;
mutex_enter(&imc->imc_lock);
m = getminor(dev);
if (m >= imc->imc_nsockets) {
ret = EINVAL;
goto done;
}
sock = imc->imc_spointers[m];
if (sock == NULL) {
ret = EINVAL;
goto done;
}
switch (cmd) {
case MC_IOC_SNAPSHOT_INFO:
imc_nvl_pack(sock, B_FALSE);
if (sock->isock_buf == NULL) {
ret = EIO;
break;
}
info.mcs_size = sock->isock_buflen;
info.mcs_gen = sock->isock_gen;
if (ddi_copyout(&info, (void *)arg, sizeof (info), mode) != 0) {
ret = EFAULT;
break;
}
ret = 0;
break;
case MC_IOC_SNAPSHOT:
imc_nvl_pack(sock, B_FALSE);
if (sock->isock_buf == NULL) {
ret = EIO;
break;
}
if (ddi_copyout(sock->isock_buf, (void *)arg,
sock->isock_buflen, mode) != 0) {
ret = EFAULT;
break;
}
ret = 0;
break;
case MC_IOC_DECODE_SNAPSHOT_INFO:
imc_decoder_pack(imc);
if (imc->imc_decoder_buf == NULL) {
ret = EIO;
break;
}
info.mcs_size = imc->imc_decoder_len;
info.mcs_gen = imc->imc_spointers[0]->isock_gen;
if (ddi_copyout(&info, (void *)arg, sizeof (info), mode) != 0) {
ret = EFAULT;
break;
}
ret = 0;
break;
case MC_IOC_DECODE_SNAPSHOT:
imc_decoder_pack(imc);
if (imc->imc_decoder_buf == NULL) {
ret = EIO;
break;
}
if (ddi_copyout(imc->imc_decoder_buf, (void *)arg,
imc->imc_decoder_len, mode) != 0) {
ret = EFAULT;
break;
}
ret = 0;
break;
case MC_IOC_DECODE_PA:
if (crgetzoneid(credp) != GLOBAL_ZONEID ||
drv_priv(credp) != 0) {
ret = EPERM;
break;
}
if (ddi_copyin((void *)arg, &encode, sizeof (encode),
mode & FKIOCTL) != 0) {
ret = EPERM;
break;
}
imc_ioctl_decode(imc, &encode);
ret = 0;
if (ddi_copyout(&encode, (void *)arg, sizeof (encode),
mode & FKIOCTL) != 0) {
ret = EPERM;
break;
}
break;
default:
ret = EINVAL;
goto done;
}
done:
mutex_exit(&imc->imc_lock);
return (ret);
}
static int
imc_close(dev_t dev, int flag, int otyp, cred_t *credp)
{
return (0);
}
static int
imc_attach(dev_info_t *dip, ddi_attach_cmd_t cmd)
{
if (cmd != DDI_ATTACH) {
return (DDI_FAILURE);
}
if (imc_data == NULL || imc_data->imc_dip != NULL) {
return (DDI_FAILURE);
}
mutex_enter(&imc_data->imc_lock);
if ((imc_data->imc_taskq = ddi_taskq_create(dip, "imc", 1,
TASKQ_DEFAULTPRI, 0)) == NULL) {
mutex_exit(&imc_data->imc_lock);
return (DDI_FAILURE);
}
imc_data->imc_dip = dip;
imc_data->imc_flags |= IMC_F_SCAN_DISPATCHED;
mutex_exit(&imc_data->imc_lock);
(void) ddi_taskq_dispatch(imc_data->imc_taskq, imc_stub_scan, imc_data,
DDI_SLEEP);
return (DDI_SUCCESS);
}
static int
imc_getinfo(dev_info_t *dip, ddi_info_cmd_t infocmd, void *arg, void **resultp)
{
if (imc_data == NULL || imc_data->imc_dip == NULL) {
return (DDI_FAILURE);
}
switch (infocmd) {
case DDI_INFO_DEVT2DEVINFO:
*resultp = imc_data->imc_dip;
break;
case DDI_INFO_DEVT2INSTANCE:
*resultp = (void *)0;
break;
default:
return (DDI_FAILURE);
}
return (DDI_SUCCESS);
}
static int
imc_detach(dev_info_t *dip, ddi_detach_cmd_t cmd)
{
if (cmd != DDI_DETACH) {
return (DDI_FAILURE);
}
if (imc_data == NULL || imc_data->imc_dip) {
return (DDI_FAILURE);
}
mutex_enter(&imc_data->imc_lock);
if ((imc_data->imc_flags &
(IMC_F_SCAN_DISPATCHED | IMC_F_ATTACH_DISPATCHED)) != 0) {
mutex_exit(&imc_data->imc_lock);
return (DDI_FAILURE);
}
if (!avl_is_empty(&imc_data->imc_stubs)) {
mutex_exit(&imc_data->imc_lock);
return (DDI_FAILURE);
}
nvlist_free(imc_data->imc_decoder_dump);
imc_data->imc_decoder_dump = NULL;
if (imc_data->imc_decoder_buf != NULL) {
kmem_free(imc_data->imc_decoder_buf, imc_data->imc_decoder_len);
imc_data->imc_decoder_buf = NULL;
imc_data->imc_decoder_len = 0;
}
ddi_remove_minor_node(imc_data->imc_dip, NULL);
imc_data->imc_dip = NULL;
mutex_exit(&imc_data->imc_lock);
ddi_taskq_wait(imc_data->imc_taskq);
ddi_taskq_destroy(imc_data->imc_taskq);
imc_data->imc_taskq = NULL;
return (DDI_SUCCESS);
}
static void
imc_free(void)
{
if (imc_data == NULL) {
return;
}
VERIFY(avl_is_empty(&imc_data->imc_stubs));
avl_destroy(&imc_data->imc_stubs);
mutex_destroy(&imc_data->imc_lock);
kmem_free(imc_data, sizeof (imc_t));
imc_data = NULL;
}
static void
imc_alloc(void)
{
imc_data = kmem_zalloc(sizeof (imc_t), KM_SLEEP);
mutex_init(&imc_data->imc_lock, NULL, MUTEX_DRIVER, NULL);
avl_create(&imc_data->imc_stubs, imc_stub_comparator,
sizeof (imc_stub_t), offsetof(imc_stub_t, istub_link));
}
static struct cb_ops imc_cb_ops = {
.cb_open = imc_open,
.cb_close = imc_close,
.cb_strategy = nodev,
.cb_print = nodev,
.cb_dump = nodev,
.cb_read = nodev,
.cb_write = nodev,
.cb_ioctl = imc_ioctl,
.cb_devmap = nodev,
.cb_mmap = nodev,
.cb_segmap = nodev,
.cb_chpoll = nochpoll,
.cb_prop_op = ddi_prop_op,
.cb_flag = D_MP,
.cb_rev = CB_REV,
.cb_aread = nodev,
.cb_awrite = nodev
};
static struct dev_ops imc_dev_ops = {
.devo_rev = DEVO_REV,
.devo_refcnt = 0,
.devo_getinfo = imc_getinfo,
.devo_identify = nulldev,
.devo_probe = nulldev,
.devo_attach = imc_attach,
.devo_detach = imc_detach,
.devo_reset = nodev,
.devo_cb_ops = &imc_cb_ops,
.devo_quiesce = ddi_quiesce_not_needed
};
static struct modldrv imc_modldrv = {
.drv_modops = &mod_driverops,
.drv_linkinfo = "Intel Integrated Memory Controller Driver",
.drv_dev_ops = &imc_dev_ops
};
static struct modlinkage imc_modlinkage = {
.ml_rev = MODREV_1,
.ml_linkage = { &imc_modldrv, NULL }
};
int
_init(void)
{
int ret;
if ((ret = mod_install(&imc_modlinkage)) == 0) {
imc_alloc();
}
return (ret);
}
int
_info(struct modinfo *modinfop)
{
return (mod_info(&imc_modlinkage, modinfop));
}
int
_fini(void)
{
int ret;
if ((ret = mod_remove(&imc_modlinkage)) == 0) {
imc_free();
}
return (ret);
}