1e3d60c9bSAdrian Frost /* 2e3d60c9bSAdrian Frost * CDDL HEADER START 3e3d60c9bSAdrian Frost * 4e3d60c9bSAdrian Frost * The contents of this file are subject to the terms of the 5e3d60c9bSAdrian Frost * Common Development and Distribution License (the "License"). 6e3d60c9bSAdrian Frost * You may not use this file except in compliance with the License. 7e3d60c9bSAdrian Frost * 8e3d60c9bSAdrian Frost * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE 9e3d60c9bSAdrian Frost * or http://www.opensolaris.org/os/licensing. 10e3d60c9bSAdrian Frost * See the License for the specific language governing permissions 11e3d60c9bSAdrian Frost * and limitations under the License. 12e3d60c9bSAdrian Frost * 13e3d60c9bSAdrian Frost * When distributing Covered Code, include this CDDL HEADER in each 14e3d60c9bSAdrian Frost * file and include the License file at usr/src/OPENSOLARIS.LICENSE. 15e3d60c9bSAdrian Frost * If applicable, add the following below this CDDL HEADER, with the 16e3d60c9bSAdrian Frost * fields enclosed by brackets "[]" replaced with your own identifying 17e3d60c9bSAdrian Frost * information: Portions Copyright [yyyy] [name of copyright owner] 18e3d60c9bSAdrian Frost * 19e3d60c9bSAdrian Frost * CDDL HEADER END 20e3d60c9bSAdrian Frost */ 21e3d60c9bSAdrian Frost 22e3d60c9bSAdrian Frost /* 23*f657cd55SCheng Sean Ye * Copyright 2009 Sun Microsystems, Inc. All rights reserved. 24e3d60c9bSAdrian Frost * Use is subject to license terms. 25e3d60c9bSAdrian Frost */ 26e3d60c9bSAdrian Frost 27e3d60c9bSAdrian Frost #include <sys/types.h> 28e3d60c9bSAdrian Frost #include <sys/cmn_err.h> 29e3d60c9bSAdrian Frost #include <sys/errno.h> 30e3d60c9bSAdrian Frost #include <sys/log.h> 31e3d60c9bSAdrian Frost #include <sys/systm.h> 32e3d60c9bSAdrian Frost #include <sys/modctl.h> 33e3d60c9bSAdrian Frost #include <sys/errorq.h> 34e3d60c9bSAdrian Frost #include <sys/controlregs.h> 35e3d60c9bSAdrian Frost #include <sys/fm/util.h> 36e3d60c9bSAdrian Frost #include <sys/fm/protocol.h> 37e3d60c9bSAdrian Frost #include <sys/sysevent.h> 38e3d60c9bSAdrian Frost #include <sys/pghw.h> 39e3d60c9bSAdrian Frost #include <sys/cyclic.h> 40e3d60c9bSAdrian Frost #include <sys/pci_cfgspace.h> 41e3d60c9bSAdrian Frost #include <sys/mc_intel.h> 42e3d60c9bSAdrian Frost #include <sys/cpu_module_impl.h> 43e3d60c9bSAdrian Frost #include <sys/smbios.h> 44e3d60c9bSAdrian Frost #include <sys/pci.h> 45e3d60c9bSAdrian Frost #include "intel_nhm.h" 46e3d60c9bSAdrian Frost #include "nhm_log.h" 47e3d60c9bSAdrian Frost 48e3d60c9bSAdrian Frost errorq_t *nhm_queue; 49e3d60c9bSAdrian Frost kmutex_t nhm_mutex; 50e3d60c9bSAdrian Frost uint32_t nhm_chipset; 51e3d60c9bSAdrian Frost 52e3d60c9bSAdrian Frost nhm_dimm_t **nhm_dimms; 53e3d60c9bSAdrian Frost 54e3d60c9bSAdrian Frost uint64_t nhm_memory_on_ctl[MAX_MEMORY_CONTROLLERS]; 55e3d60c9bSAdrian Frost int nhm_patrol_scrub; 56e3d60c9bSAdrian Frost int nhm_demand_scrub; 57e3d60c9bSAdrian Frost int nhm_no_smbios; 58e3d60c9bSAdrian Frost int nhm_smbios_serial; 59e3d60c9bSAdrian Frost int nhm_smbios_manufacturer; 60e3d60c9bSAdrian Frost int nhm_smbios_part_number; 61e3d60c9bSAdrian Frost int nhm_smbios_version; 62e3d60c9bSAdrian Frost int nhm_smbios_label; 63e3d60c9bSAdrian Frost 64e3d60c9bSAdrian Frost extern char ecc_enabled; 65e3d60c9bSAdrian Frost extern void mem_reg_init(void); 66e3d60c9bSAdrian Frost 67e3d60c9bSAdrian Frost static void 68e3d60c9bSAdrian Frost check_serial_number() 69e3d60c9bSAdrian Frost { 70e3d60c9bSAdrian Frost nhm_dimm_t *dimmp, *tp; 71e3d60c9bSAdrian Frost nhm_dimm_t **dimmpp, **tpp; 72e3d60c9bSAdrian Frost nhm_dimm_t **end; 73e3d60c9bSAdrian Frost int not_unique; 74e3d60c9bSAdrian Frost 75e3d60c9bSAdrian Frost end = &nhm_dimms[MAX_MEMORY_CONTROLLERS * 76e3d60c9bSAdrian Frost CHANNELS_PER_MEMORY_CONTROLLER * MAX_DIMMS_PER_CHANNEL]; 77e3d60c9bSAdrian Frost for (dimmpp = nhm_dimms; dimmpp < end; dimmpp++) { 78e3d60c9bSAdrian Frost dimmp = *dimmpp; 79e3d60c9bSAdrian Frost if (dimmp == NULL) 80e3d60c9bSAdrian Frost continue; 81e3d60c9bSAdrian Frost not_unique = 0; 82e3d60c9bSAdrian Frost for (tpp = dimmpp + 1; tpp < end; tpp++) { 83e3d60c9bSAdrian Frost tp = *tpp; 84e3d60c9bSAdrian Frost if (tp == NULL) 85e3d60c9bSAdrian Frost continue; 86e3d60c9bSAdrian Frost if (strncmp(dimmp->serial_number, tp->serial_number, 87e3d60c9bSAdrian Frost sizeof (dimmp->serial_number)) == 0) { 88e3d60c9bSAdrian Frost not_unique = 1; 89e3d60c9bSAdrian Frost tp->serial_number[0] = 0; 90e3d60c9bSAdrian Frost } 91e3d60c9bSAdrian Frost } 92e3d60c9bSAdrian Frost if (not_unique) 93e3d60c9bSAdrian Frost dimmp->serial_number[0] = 0; 94e3d60c9bSAdrian Frost } 95e3d60c9bSAdrian Frost } 96e3d60c9bSAdrian Frost 97e3d60c9bSAdrian Frost static void 98e3d60c9bSAdrian Frost dimm_manufacture_data(smbios_hdl_t *shp, id_t id, nhm_dimm_t *dimmp) 99e3d60c9bSAdrian Frost { 100e3d60c9bSAdrian Frost smbios_info_t cd; 101e3d60c9bSAdrian Frost 102e3d60c9bSAdrian Frost if (smbios_info_common(shp, id, &cd) == 0) { 103e3d60c9bSAdrian Frost if (cd.smbi_serial && nhm_smbios_serial) { 104e3d60c9bSAdrian Frost (void) strncpy(dimmp->serial_number, cd.smbi_serial, 105e3d60c9bSAdrian Frost sizeof (dimmp->serial_number)); 106e3d60c9bSAdrian Frost } 107e3d60c9bSAdrian Frost if (cd.smbi_manufacturer && nhm_smbios_manufacturer) { 108e3d60c9bSAdrian Frost (void) strncpy(dimmp->manufacturer, 109e3d60c9bSAdrian Frost cd.smbi_manufacturer, 110e3d60c9bSAdrian Frost sizeof (dimmp->manufacturer)); 111e3d60c9bSAdrian Frost } 112e3d60c9bSAdrian Frost if (cd.smbi_part && nhm_smbios_part_number) { 113e3d60c9bSAdrian Frost (void) strncpy(dimmp->part_number, cd.smbi_part, 114e3d60c9bSAdrian Frost sizeof (dimmp->part_number)); 115e3d60c9bSAdrian Frost } 116e3d60c9bSAdrian Frost if (cd.smbi_version && nhm_smbios_version) { 117e3d60c9bSAdrian Frost (void) strncpy(dimmp->revision, cd.smbi_version, 118e3d60c9bSAdrian Frost sizeof (dimmp->revision)); 119e3d60c9bSAdrian Frost } 120e3d60c9bSAdrian Frost } 121e3d60c9bSAdrian Frost } 122e3d60c9bSAdrian Frost 123e3d60c9bSAdrian Frost struct dimm_slot { 124e3d60c9bSAdrian Frost int controller; 125e3d60c9bSAdrian Frost int channel; 126e3d60c9bSAdrian Frost int dimm; 127e3d60c9bSAdrian Frost int max_dimm; 128e3d60c9bSAdrian Frost }; 129e3d60c9bSAdrian Frost 130e3d60c9bSAdrian Frost static int 131e3d60c9bSAdrian Frost dimm_label(smbios_hdl_t *shp, const smbios_struct_t *sp, void *arg) 132e3d60c9bSAdrian Frost { 133e3d60c9bSAdrian Frost nhm_dimm_t *dimmp; 134e3d60c9bSAdrian Frost smbios_memdevice_t md; 135e3d60c9bSAdrian Frost int slot; 136e3d60c9bSAdrian Frost int last_slot; 137e3d60c9bSAdrian Frost struct dimm_slot *dsp = (struct dimm_slot *)arg; 138e3d60c9bSAdrian Frost 139e3d60c9bSAdrian Frost slot = (dsp->controller * CHANNELS_PER_MEMORY_CONTROLLER * 140e3d60c9bSAdrian Frost MAX_DIMMS_PER_CHANNEL) + (dsp->channel * MAX_DIMMS_PER_CHANNEL) + 141e3d60c9bSAdrian Frost dsp->dimm; 142e3d60c9bSAdrian Frost last_slot = MAX_MEMORY_CONTROLLERS * CHANNELS_PER_MEMORY_CONTROLLER * 143e3d60c9bSAdrian Frost MAX_DIMMS_PER_CHANNEL; 144e3d60c9bSAdrian Frost if (slot >= last_slot) 145e3d60c9bSAdrian Frost return (0); 146e3d60c9bSAdrian Frost dimmp = nhm_dimms[slot]; 147e3d60c9bSAdrian Frost if (sp->smbstr_type == SMB_TYPE_MEMDEVICE) { 148e3d60c9bSAdrian Frost if (smbios_info_memdevice(shp, sp->smbstr_id, 149e3d60c9bSAdrian Frost &md) == 0 && md.smbmd_dloc != NULL) { 150e3d60c9bSAdrian Frost if (dimmp == NULL && md.smbmd_size) { 151e3d60c9bSAdrian Frost /* skip non existent slot */ 152e3d60c9bSAdrian Frost dsp->channel++; 153e3d60c9bSAdrian Frost if (dsp->dimm == 2) 154e3d60c9bSAdrian Frost dsp->max_dimm = 2; 155e3d60c9bSAdrian Frost dsp->dimm = 0; 156e3d60c9bSAdrian Frost slot = (dsp->controller * 157e3d60c9bSAdrian Frost CHANNELS_PER_MEMORY_CONTROLLER * 158e3d60c9bSAdrian Frost MAX_DIMMS_PER_CHANNEL) + 159e3d60c9bSAdrian Frost (dsp->channel * MAX_DIMMS_PER_CHANNEL); 160e3d60c9bSAdrian Frost if (slot >= last_slot) 161e3d60c9bSAdrian Frost return (0); 162e3d60c9bSAdrian Frost 163e3d60c9bSAdrian Frost dimmp = nhm_dimms[slot]; 164e3d60c9bSAdrian Frost 165e3d60c9bSAdrian Frost if (dimmp == NULL) { 166e3d60c9bSAdrian Frost dsp->channel++; 167e3d60c9bSAdrian Frost if (dsp->channel == 168e3d60c9bSAdrian Frost CHANNELS_PER_MEMORY_CONTROLLER) { 169e3d60c9bSAdrian Frost dsp->channel = 0; 170e3d60c9bSAdrian Frost dsp->controller++; 171e3d60c9bSAdrian Frost } 172e3d60c9bSAdrian Frost slot = (dsp->controller * 173e3d60c9bSAdrian Frost CHANNELS_PER_MEMORY_CONTROLLER * 174e3d60c9bSAdrian Frost MAX_DIMMS_PER_CHANNEL) + 175e3d60c9bSAdrian Frost (dsp->channel * 176e3d60c9bSAdrian Frost MAX_DIMMS_PER_CHANNEL); 177e3d60c9bSAdrian Frost if (slot >= last_slot) 178e3d60c9bSAdrian Frost return (0); 179e3d60c9bSAdrian Frost dimmp = nhm_dimms[slot]; 180e3d60c9bSAdrian Frost } 181e3d60c9bSAdrian Frost } 182e3d60c9bSAdrian Frost if (dimmp) { 183e3d60c9bSAdrian Frost if (nhm_smbios_label) 184e3d60c9bSAdrian Frost (void) snprintf(dimmp->label, 185e3d60c9bSAdrian Frost sizeof (dimmp->label), "%s", 186e3d60c9bSAdrian Frost md.smbmd_dloc); 187e3d60c9bSAdrian Frost dimm_manufacture_data(shp, sp->smbstr_id, 188e3d60c9bSAdrian Frost dimmp); 189e3d60c9bSAdrian Frost } 190e3d60c9bSAdrian Frost } 191e3d60c9bSAdrian Frost dsp->dimm++; 192e3d60c9bSAdrian Frost if (dsp->dimm == dsp->max_dimm) { 193e3d60c9bSAdrian Frost dsp->dimm = 0; 194e3d60c9bSAdrian Frost dsp->channel++; 195e3d60c9bSAdrian Frost if (dsp->channel == CHANNELS_PER_MEMORY_CONTROLLER) { 196e3d60c9bSAdrian Frost dsp->channel = 0; 197e3d60c9bSAdrian Frost dsp->controller++; 198e3d60c9bSAdrian Frost } 199e3d60c9bSAdrian Frost } 200e3d60c9bSAdrian Frost } 201e3d60c9bSAdrian Frost return (0); 202e3d60c9bSAdrian Frost } 203e3d60c9bSAdrian Frost 204e3d60c9bSAdrian Frost void 205e3d60c9bSAdrian Frost nhm_smbios() 206e3d60c9bSAdrian Frost { 207e3d60c9bSAdrian Frost struct dimm_slot ds; 208e3d60c9bSAdrian Frost 209e3d60c9bSAdrian Frost if (ksmbios != NULL && nhm_no_smbios == 0) { 210e3d60c9bSAdrian Frost ds.dimm = 0; 211e3d60c9bSAdrian Frost ds.channel = 0; 212e3d60c9bSAdrian Frost ds.controller = 0; 213e3d60c9bSAdrian Frost ds.max_dimm = MAX_DIMMS_PER_CHANNEL; 214e3d60c9bSAdrian Frost (void) smbios_iter(ksmbios, dimm_label, &ds); 215e3d60c9bSAdrian Frost check_serial_number(); 216e3d60c9bSAdrian Frost } 217e3d60c9bSAdrian Frost } 218e3d60c9bSAdrian Frost 219e3d60c9bSAdrian Frost static void 220e3d60c9bSAdrian Frost dimm_prop(nhm_dimm_t *dimmp, uint32_t dod) 221e3d60c9bSAdrian Frost { 222e3d60c9bSAdrian Frost dimmp->dimm_size = DIMMSIZE(dod); 223e3d60c9bSAdrian Frost dimmp->nranks = NUMRANK(dod); 224e3d60c9bSAdrian Frost dimmp->nbanks = NUMBANK(dod); 225e3d60c9bSAdrian Frost dimmp->ncolumn = NUMCOL(dod); 226e3d60c9bSAdrian Frost dimmp->nrow = NUMROW(dod); 227e3d60c9bSAdrian Frost dimmp->width = DIMMWIDTH; 228e3d60c9bSAdrian Frost } 229e3d60c9bSAdrian Frost 230e3d60c9bSAdrian Frost void 231e3d60c9bSAdrian Frost nhm_scrubber_enable() 232e3d60c9bSAdrian Frost { 233e3d60c9bSAdrian Frost uint32_t mc_ssrcontrol; 234e3d60c9bSAdrian Frost uint32_t mc_dimm_clk_ratio_status; 235e3d60c9bSAdrian Frost uint64_t cycle_time; 236e3d60c9bSAdrian Frost uint32_t interval; 237e3d60c9bSAdrian Frost int i; 238e3d60c9bSAdrian Frost int hw_scrub = 0; 239e3d60c9bSAdrian Frost 240e3d60c9bSAdrian Frost if (ecc_enabled && (nhm_patrol_scrub || nhm_demand_scrub)) { 241e3d60c9bSAdrian Frost for (i = 0; i < MAX_MEMORY_CONTROLLERS; i++) { 242e3d60c9bSAdrian Frost if (nhm_memory_on_ctl[i] == 0) 243e3d60c9bSAdrian Frost continue; 244e3d60c9bSAdrian Frost mc_ssrcontrol = MC_SSR_CONTROL_RD(i); 245e3d60c9bSAdrian Frost if (nhm_demand_scrub && 246e3d60c9bSAdrian Frost (mc_ssrcontrol & DEMAND_SCRUB_ENABLE) == 0) { 247e3d60c9bSAdrian Frost mc_ssrcontrol |= DEMAND_SCRUB_ENABLE; 248e3d60c9bSAdrian Frost MC_SSR_CONTROL_WR(i, mc_ssrcontrol); 249e3d60c9bSAdrian Frost } 250e3d60c9bSAdrian Frost if (nhm_patrol_scrub == 0) 251e3d60c9bSAdrian Frost continue; 252e3d60c9bSAdrian Frost if (SSR_MODE(mc_ssrcontrol) == SSR_IDLE) { 253e3d60c9bSAdrian Frost mc_dimm_clk_ratio_status = 254e3d60c9bSAdrian Frost MC_DIMM_CLK_RATIO_STATUS(i); 255e3d60c9bSAdrian Frost cycle_time = 256e3d60c9bSAdrian Frost MAX_DIMM_CLK_RATIO(mc_dimm_clk_ratio_status) 257e3d60c9bSAdrian Frost * 80000000; 258e3d60c9bSAdrian Frost interval = (uint32_t)((36400ULL * cycle_time) / 259e3d60c9bSAdrian Frost (nhm_memory_on_ctl[i]/64)); 260e3d60c9bSAdrian Frost MC_SCRUB_CONTROL_WR(i, STARTSCRUB | interval); 261e3d60c9bSAdrian Frost MC_SSR_CONTROL_WR(i, mc_ssrcontrol | SSR_SCRUB); 262e3d60c9bSAdrian Frost } else if (SSR_MODE(mc_ssrcontrol) == SSR_SPARE) { 263e3d60c9bSAdrian Frost hw_scrub = 0; 264e3d60c9bSAdrian Frost break; 265e3d60c9bSAdrian Frost } 266e3d60c9bSAdrian Frost hw_scrub = 1; 267e3d60c9bSAdrian Frost } 268e3d60c9bSAdrian Frost if (hw_scrub) 269*f657cd55SCheng Sean Ye cmi_mc_sw_memscrub_disable(); 270e3d60c9bSAdrian Frost } 271e3d60c9bSAdrian Frost } 272e3d60c9bSAdrian Frost 273e3d60c9bSAdrian Frost void 274e3d60c9bSAdrian Frost init_dimms() 275e3d60c9bSAdrian Frost { 276e3d60c9bSAdrian Frost int i, j, k; 277e3d60c9bSAdrian Frost nhm_dimm_t **dimmpp; 278e3d60c9bSAdrian Frost nhm_dimm_t *dimmp; 279e3d60c9bSAdrian Frost uint32_t dod; 280e3d60c9bSAdrian Frost 281e3d60c9bSAdrian Frost nhm_dimms = (nhm_dimm_t **)kmem_zalloc(sizeof (nhm_dimm_t *) * 282e3d60c9bSAdrian Frost MAX_MEMORY_CONTROLLERS * CHANNELS_PER_MEMORY_CONTROLLER * 283e3d60c9bSAdrian Frost MAX_DIMMS_PER_CHANNEL, KM_SLEEP); 284e3d60c9bSAdrian Frost dimmpp = nhm_dimms; 285e3d60c9bSAdrian Frost for (i = 0; i < MAX_MEMORY_CONTROLLERS; i++) { 286e3d60c9bSAdrian Frost if (CPU_ID_RD(i) != NHM_CPU) { 287e3d60c9bSAdrian Frost dimmpp += CHANNELS_PER_MEMORY_CONTROLLER * 288e3d60c9bSAdrian Frost MAX_DIMMS_PER_CHANNEL; 289e3d60c9bSAdrian Frost continue; 290e3d60c9bSAdrian Frost } 291e3d60c9bSAdrian Frost for (j = 0; j < CHANNELS_PER_MEMORY_CONTROLLER; j++) { 292e3d60c9bSAdrian Frost for (k = 0; k < MAX_DIMMS_PER_CHANNEL; k++) { 293e3d60c9bSAdrian Frost dod = MC_DOD_RD(i, j, k); 294e3d60c9bSAdrian Frost if (DIMMPRESENT(dod)) { 295e3d60c9bSAdrian Frost dimmp = (nhm_dimm_t *) 296e3d60c9bSAdrian Frost kmem_zalloc(sizeof (nhm_dimm_t), 297e3d60c9bSAdrian Frost KM_SLEEP); 298e3d60c9bSAdrian Frost dimm_prop(dimmp, dod); 299e3d60c9bSAdrian Frost (void) snprintf(dimmp->label, 300e3d60c9bSAdrian Frost sizeof (dimmp->label), 301e3d60c9bSAdrian Frost "Socket %d channel %d dimm %d", 302e3d60c9bSAdrian Frost i, j, k); 303e3d60c9bSAdrian Frost *dimmpp = dimmp; 304e3d60c9bSAdrian Frost nhm_memory_on_ctl[i] += 305e3d60c9bSAdrian Frost dimmp->dimm_size; 306e3d60c9bSAdrian Frost } 307e3d60c9bSAdrian Frost dimmpp++; 308e3d60c9bSAdrian Frost } 309e3d60c9bSAdrian Frost } 310e3d60c9bSAdrian Frost } 311e3d60c9bSAdrian Frost } 312e3d60c9bSAdrian Frost 313e3d60c9bSAdrian Frost 314e3d60c9bSAdrian Frost int 315e3d60c9bSAdrian Frost nhm_init(void) 316e3d60c9bSAdrian Frost { 317e3d60c9bSAdrian Frost int slot; 318e3d60c9bSAdrian Frost 319e3d60c9bSAdrian Frost /* return ENOTSUP if there is no PCI config space support. */ 320e3d60c9bSAdrian Frost if (pci_getl_func == NULL) 321e3d60c9bSAdrian Frost return (ENOTSUP); 322e3d60c9bSAdrian Frost for (slot = 0; slot < MAX_CPU_NODES; slot++) { 323e3d60c9bSAdrian Frost nhm_chipset = CPU_ID_RD(slot); 324e3d60c9bSAdrian Frost if (nhm_chipset == NHM_CPU) 325e3d60c9bSAdrian Frost break; 326e3d60c9bSAdrian Frost } 327e3d60c9bSAdrian Frost if (nhm_chipset != NHM_CPU) { 328e3d60c9bSAdrian Frost return (ENOTSUP); 329e3d60c9bSAdrian Frost } 330e3d60c9bSAdrian Frost mem_reg_init(); 331e3d60c9bSAdrian Frost return (0); 332e3d60c9bSAdrian Frost } 333e3d60c9bSAdrian Frost 334e3d60c9bSAdrian Frost int 335e3d60c9bSAdrian Frost nhm_reinit(void) 336e3d60c9bSAdrian Frost { 337e3d60c9bSAdrian Frost mem_reg_init(); 338e3d60c9bSAdrian Frost return (0); 339e3d60c9bSAdrian Frost } 340e3d60c9bSAdrian Frost 341e3d60c9bSAdrian Frost int 342e3d60c9bSAdrian Frost nhm_dev_init() 343e3d60c9bSAdrian Frost { 344e3d60c9bSAdrian Frost return (0); 345e3d60c9bSAdrian Frost } 346e3d60c9bSAdrian Frost 347e3d60c9bSAdrian Frost void 348e3d60c9bSAdrian Frost nhm_dev_reinit() 349e3d60c9bSAdrian Frost { 350e3d60c9bSAdrian Frost } 351e3d60c9bSAdrian Frost 352e3d60c9bSAdrian Frost void 353e3d60c9bSAdrian Frost nhm_unload() 354e3d60c9bSAdrian Frost { 355e3d60c9bSAdrian Frost } 356