1 /*
   2  * CDDL HEADER START
   3  *
   4  * The contents of this file are subject to the terms of the
   5  * Common Development and Distribution License (the "License").
   6  * You may not use this file except in compliance with the License.
   7  *
   8  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
   9  * or http://www.opensolaris.org/os/licensing.
  10  * See the License for the specific language governing permissions
  11  * and limitations under the License.
  12  *
  13  * When distributing Covered Code, include this CDDL HEADER in each
  14  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
  15  * If applicable, add the following below this CDDL HEADER, with the
  16  * fields enclosed by brackets "[]" replaced with your own identifying
  17  * information: Portions Copyright [yyyy] [name of copyright owner]
  18  *
  19  * CDDL HEADER END
  20  */
  21 
  22 /*
  23  * Copyright 2009 Sun Microsystems, Inc.  All rights reserved.
  24  * Use is subject to license terms.
  25  */
  26 
  27 #include <sys/types.h>
  28 #include <sys/time.h>
  29 #include <sys/nvpair.h>
  30 #include <sys/cmn_err.h>
  31 #include <sys/cred.h>
  32 #include <sys/open.h>
  33 #include <sys/ddi.h>
  34 #include <sys/sunddi.h>
  35 #include <sys/conf.h>
  36 #include <sys/modctl.h>
  37 #include <sys/cyclic.h>
  38 #include <sys/errorq.h>
  39 #include <sys/stat.h>
  40 #include <sys/cpuvar.h>
  41 #include <sys/mc_intel.h>
  42 #include <sys/mc.h>
  43 #include <sys/fm/protocol.h>
  44 #include "nhm_log.h"
  45 #include "intel_nhm.h"
  46 
  47 int max_bus_number = 0xff;
  48 
  49 nvlist_t *inhm_mc_nvl[MAX_CPU_NODES];
  50 krwlock_t inhm_mc_lock;
  51 
  52 char *inhm_mc_snapshot[MAX_CPU_NODES];
  53 uint_t nhm_config_gen;
  54 uint_t inhm_mc_snapshotgen;
  55 size_t inhm_mc_snapshotsz[MAX_CPU_NODES];
  56 static dev_info_t *inhm_dip;
  57 int nhm_allow_detach = 0;
  58 
  59 extern int nhm_patrol_scrub;
  60 extern int nhm_demand_scrub;
  61 extern int nhm_no_smbios;
  62 extern int nhm_smbios_serial;
  63 extern int nhm_smbios_manufacturer;
  64 extern int nhm_smbios_part_number;
  65 extern int nhm_smbios_version;
  66 extern int nhm_smbios_label;
  67 
  68 extern void inhm_create_nvl(int);
  69 extern char *inhm_mc_name(void);
  70 extern void init_dimms(void);
  71 extern void nhm_smbios();
  72 
  73 static void
  74 inhm_mc_snapshot_destroy()
  75 {
  76         int i;
  77 
  78         ASSERT(RW_LOCK_HELD(&inhm_mc_lock));
  79 
  80         for (i = 0; i < MAX_CPU_NODES; i++) {
  81                 if (inhm_mc_snapshot[i] == NULL)
  82                         continue;
  83 
  84                 kmem_free(inhm_mc_snapshot[i], inhm_mc_snapshotsz[i]);
  85                         inhm_mc_snapshot[i] = NULL;
  86                         inhm_mc_snapshotsz[i] = 0;
  87         }
  88         inhm_mc_snapshotgen++;
  89 }
  90 
  91 static int
  92 inhm_mc_snapshot_update()
  93 {
  94         int i;
  95         int rt = 0;
  96 
  97         ASSERT(RW_LOCK_HELD(&inhm_mc_lock));
  98 
  99         for (i = 0; i < MAX_CPU_NODES; i++) {
 100                 if (inhm_mc_snapshot[i] != NULL)
 101                         continue;
 102 
 103                 if (nvlist_pack(inhm_mc_nvl[i], &inhm_mc_snapshot[i],
 104                     &inhm_mc_snapshotsz[i], NV_ENCODE_XDR, KM_SLEEP) != 0)
 105                         rt = -1;
 106         }
 107 
 108         return (rt);
 109 }
 110 
 111 /*ARGSUSED*/
 112 static int
 113 inhm_mc_ioctl(dev_t dev, int cmd, intptr_t arg, int mode, cred_t *credp,
 114     int *rvalp)
 115 {
 116         int rc = 0;
 117         int chip;
 118         mc_snapshot_info_t mcs;
 119 
 120         if (cmd != MC_IOC_SNAPSHOT_INFO && cmd != MC_IOC_SNAPSHOT)
 121                 return (EINVAL);
 122 
 123         rw_enter(&inhm_mc_lock, RW_READER);
 124         chip = getminor(dev) % MAX_CPU_NODES;
 125         if (inhm_mc_nvl[chip] == NULL ||
 126             inhm_mc_snapshotgen != nhm_config_gen) {
 127                 if (!rw_tryupgrade(&inhm_mc_lock)) {
 128                         rw_exit(&inhm_mc_lock);
 129                         return (EAGAIN);
 130                 }
 131                 if (inhm_mc_nvl[chip])
 132                         inhm_mc_snapshot_destroy();
 133                 inhm_create_nvl(chip);
 134                 nhm_config_gen = inhm_mc_snapshotgen;
 135                 (void) inhm_mc_snapshot_update();
 136         }
 137         switch (cmd) {
 138         case MC_IOC_SNAPSHOT_INFO:
 139                 mcs.mcs_size = (uint32_t)inhm_mc_snapshotsz[chip];
 140                 mcs.mcs_gen = inhm_mc_snapshotgen;
 141 
 142                 if (ddi_copyout(&mcs, (void *)arg, sizeof (mc_snapshot_info_t),
 143                     mode) < 0)
 144                         rc = EFAULT;
 145                 break;
 146         case MC_IOC_SNAPSHOT:
 147                 if (ddi_copyout(inhm_mc_snapshot[chip], (void *)arg,
 148                     inhm_mc_snapshotsz[chip], mode) < 0)
 149                         rc = EFAULT;
 150                 break;
 151         }
 152         rw_exit(&inhm_mc_lock);
 153         return (rc);
 154 }
 155 
 156 /*ARGSUSED*/
 157 static int
 158 inhm_mc_getinfo(dev_info_t *dip, ddi_info_cmd_t infocmd, void *arg,
 159     void **result)
 160 {
 161         if ((infocmd != DDI_INFO_DEVT2DEVINFO &&
 162             infocmd != DDI_INFO_DEVT2INSTANCE) || inhm_dip == NULL) {
 163                 *result = NULL;
 164                 return (DDI_FAILURE);
 165         }
 166         if (infocmd == DDI_INFO_DEVT2DEVINFO)
 167                 *result = inhm_dip;
 168         else
 169                 *result = (void *)(uintptr_t)ddi_get_instance(inhm_dip);
 170         return (0);
 171 }
 172 
 173 static int
 174 inhm_mc_attach(dev_info_t *dip, ddi_attach_cmd_t cmd)
 175 {
 176         int i;
 177         char buf[64];
 178 
 179         if (cmd == DDI_RESUME) {
 180                 nhm_dev_reinit();
 181                 nhm_scrubber_enable();
 182                 nhm_smbios();
 183                 return (DDI_SUCCESS);
 184         }
 185         if (cmd != DDI_ATTACH)
 186                 return (DDI_FAILURE);
 187         if (inhm_dip == NULL) {
 188                 inhm_dip = dip;
 189                 nhm_pci_cfg_setup(dip);
 190                 (void) ddi_prop_update_string(DDI_DEV_T_NONE, dip, "model",
 191                     inhm_mc_name());
 192                 if (nhm_dev_init()) {
 193                         nhm_pci_cfg_free();
 194                         inhm_dip = NULL;
 195                         return (DDI_FAILURE);
 196                 }
 197                 ddi_set_name_addr(dip, "1");
 198                 for (i = 0; i < MAX_CPU_NODES; i++) {
 199                         (void) snprintf(buf, sizeof (buf), "mc-intel-%d", i);
 200                         if (ddi_create_minor_node(dip, buf, S_IFCHR,
 201                             i, "ddi_mem_ctrl", 0) != DDI_SUCCESS) {
 202                                 cmn_err(CE_WARN, "failed to create minor node"
 203                                     " for memory controller %d\n", i);
 204                         }
 205                 }
 206                 cmi_hdl_walk(inhm_mc_register, NULL, NULL, NULL);
 207                 nhm_patrol_scrub = ddi_prop_get_int(DDI_DEV_T_ANY, dip,
 208                     DDI_PROP_DONTPASS, "patrol-scrub", 0);
 209                 nhm_demand_scrub = ddi_prop_get_int(DDI_DEV_T_ANY, dip,
 210                     DDI_PROP_DONTPASS, "demand-scrub", 0);
 211                 nhm_no_smbios = ddi_prop_get_int(DDI_DEV_T_ANY, dip,
 212                     DDI_PROP_DONTPASS, "no-smbios", 0);
 213                 nhm_smbios_serial = ddi_prop_get_int(DDI_DEV_T_ANY, dip,
 214                     DDI_PROP_DONTPASS, "smbios-dimm-serial", 1);
 215                 nhm_smbios_manufacturer = ddi_prop_get_int(DDI_DEV_T_ANY, dip,
 216                     DDI_PROP_DONTPASS, "smbios-dimm-manufacturer", 1);
 217                 nhm_smbios_part_number = ddi_prop_get_int(DDI_DEV_T_ANY, dip,
 218                     DDI_PROP_DONTPASS, "smbios-dimm-part-number", 1);
 219                 nhm_smbios_version = ddi_prop_get_int(DDI_DEV_T_ANY, dip,
 220                     DDI_PROP_DONTPASS, "smbios-dimme-version", 1);
 221                 nhm_smbios_label = ddi_prop_get_int(DDI_DEV_T_ANY, dip,
 222                     DDI_PROP_DONTPASS, "smbios-dimm-label", 1);
 223                 nhm_scrubber_enable();
 224                 nhm_smbios();
 225         }
 226 
 227         return (DDI_SUCCESS);
 228 }
 229 
 230 /*ARGSUSED*/
 231 static int
 232 inhm_mc_detach(dev_info_t *dip, ddi_detach_cmd_t cmd)
 233 {
 234         if (nhm_allow_detach && cmd == DDI_DETACH && dip == inhm_dip) {
 235                 rw_enter(&inhm_mc_lock, RW_WRITER);
 236                 inhm_mc_snapshot_destroy();
 237                 rw_exit(&inhm_mc_lock);
 238                 inhm_dip = NULL;
 239                 return (DDI_SUCCESS);
 240         } else if (cmd == DDI_SUSPEND || cmd == DDI_PM_SUSPEND) {
 241                 return (DDI_SUCCESS);
 242         } else {
 243                 return (DDI_FAILURE);
 244         }
 245 }
 246 
 247 /*ARGSUSED*/
 248 static int
 249 inhm_mc_open(dev_t *devp, int flag, int otyp, cred_t *credp)
 250 {
 251         if (otyp != OTYP_CHR)
 252                 return (EINVAL);
 253 
 254         rw_enter(&inhm_mc_lock, RW_READER);
 255         if (getminor(*devp) >= MAX_CPU_NODES) {
 256                 rw_exit(&inhm_mc_lock);
 257                 return (EINVAL);
 258         }
 259         rw_exit(&inhm_mc_lock);
 260 
 261         return (0);
 262 }
 263 
 264 /*ARGSUSED*/
 265 static int
 266 inhm_mc_close(dev_t dev, int flag, int otyp, cred_t *credp)
 267 {
 268         return (0);
 269 }
 270 
 271 
 272 static struct cb_ops inhm_mc_cb_ops = {
 273         inhm_mc_open,
 274         inhm_mc_close,
 275         nodev,          /* not a block driver */
 276         nodev,          /* no print routine */
 277         nodev,          /* no dump routine */
 278         nodev,          /* no read routine */
 279         nodev,          /* no write routine */
 280         inhm_mc_ioctl,
 281         nodev,          /* no devmap routine */
 282         nodev,          /* no mmap routine */
 283         nodev,          /* no segmap routine */
 284         nochpoll,       /* no chpoll routine */
 285         ddi_prop_op,
 286         0,              /* not a STREAMS driver */
 287         D_NEW | D_MP,   /* safe for multi-thread/multi-processor */
 288 };
 289 
 290 static struct dev_ops inhm_mc_ops = {
 291         DEVO_REV,               /* devo_rev */
 292         0,                      /* devo_refcnt */
 293         inhm_mc_getinfo,                /* devo_getinfo */
 294         nulldev,                /* devo_identify */
 295         nulldev,                /* devo_probe */
 296         inhm_mc_attach,         /* devo_attach */
 297         inhm_mc_detach,         /* devo_detach */
 298         nodev,                  /* devo_reset */
 299         &inhm_mc_cb_ops,            /* devo_cb_ops */
 300         NULL,                   /* devo_bus_ops */
 301         NULL,                   /* devo_power */
 302         ddi_quiesce_not_needed, /* devo_quiesce */
 303 };
 304 
 305 static struct modldrv modldrv = {
 306         &mod_driverops,
 307         "Intel QuickPath Memory Controller Hub Module",
 308         &inhm_mc_ops
 309 };
 310 
 311 static struct modlinkage modlinkage = {
 312         MODREV_1,
 313         (void *)&modldrv,
 314         NULL
 315 };
 316 
 317 int
 318 _init(void)
 319 {
 320         int err;
 321 
 322         err = nhm_init();
 323         if (err == 0 && (err = mod_install(&modlinkage)) == 0) {
 324                 rw_init(&inhm_mc_lock, NULL, RW_DRIVER, NULL);
 325                 init_dimms();
 326         }
 327 
 328         return (err);
 329 }
 330 
 331 int
 332 _info(struct modinfo *modinfop)
 333 {
 334         return (mod_info(&modlinkage, modinfop));
 335 }
 336 
 337 int
 338 _fini(void)
 339 {
 340         int err;
 341 
 342         if ((err = mod_remove(&modlinkage)) == 0) {
 343                 nhm_unload();
 344                 rw_destroy(&inhm_mc_lock);
 345         }
 346 
 347         return (err);
 348 }