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 * Copyright 2009 Sun Microsystems, Inc. All rights reserved. 23 * Use is subject to license terms. 24 */ 25 26 #include <sys/stat.h> 27 #include <sys/types.h> 28 #include <sys/param.h> 29 #include <sys/cred.h> 30 #include <sys/policy.h> 31 #include <sys/file.h> 32 #include <sys/errno.h> 33 #include <sys/modctl.h> 34 #include <sys/ddi.h> 35 #include <sys/sunddi.h> 36 #include <sys/conf.h> 37 #include <sys/debug.h> 38 #include <sys/systeminfo.h> 39 40 #include <sys/fm/protocol.h> 41 #include <sys/devfm.h> 42 43 extern int fm_get_paddr(nvlist_t *, uint64_t *); 44 #if defined(__x86) 45 extern int fm_ioctl_physcpu_info(int, nvlist_t *, nvlist_t **); 46 extern int fm_ioctl_cpu_retire(int, nvlist_t *, nvlist_t **); 47 extern int fm_ioctl_gentopo_legacy(int, nvlist_t *, nvlist_t **); 48 #endif /* __x86 */ 49 50 static int fm_ioctl_versions(int, nvlist_t *, nvlist_t **); 51 static int fm_ioctl_page_retire(int, nvlist_t *, nvlist_t **); 52 53 /* 54 * The driver's capabilities are strictly versioned, allowing userland patching 55 * without a reboot. The userland should start with a FM_VERSIONS ioctl to 56 * query the versions of the kernel interfaces, then it's all userland's 57 * responsibility to prepare arguments etc to match the current kenrel. 58 * The version of FM_VERSIONS itself is FM_DRV_VERSION. 59 */ 60 typedef struct fm_version { 61 char *interface; /* interface name */ 62 uint32_t version; /* interface version */ 63 } fm_vers_t; 64 65 typedef struct fm_subroutine { 66 int cmd; /* ioctl cmd */ 67 boolean_t priv; /* require privilege */ 68 char *version; /* version name */ 69 int (*func)(int, nvlist_t *, nvlist_t **); /* handler */ 70 } fm_subr_t; 71 72 static const fm_vers_t fm_versions[] = { 73 { FM_VERSIONS_VERSION, FM_DRV_VERSION }, 74 { FM_PAGE_OP_VERSION, 1 }, 75 { FM_CPU_OP_VERSION, 1 }, 76 { FM_CPU_INFO_VERSION, 1 }, 77 { FM_TOPO_LEGACY_VERSION, 1 }, 78 { NULL, 0 } 79 }; 80 81 static const fm_subr_t fm_subrs[] = { 82 { FM_IOC_VERSIONS, B_FALSE, FM_VERSIONS_VERSION, fm_ioctl_versions }, 83 { FM_IOC_PAGE_RETIRE, B_TRUE, FM_PAGE_OP_VERSION, 84 fm_ioctl_page_retire }, 85 { FM_IOC_PAGE_STATUS, B_FALSE, FM_PAGE_OP_VERSION, 86 fm_ioctl_page_retire }, 87 { FM_IOC_PAGE_UNRETIRE, B_TRUE, FM_PAGE_OP_VERSION, 88 fm_ioctl_page_retire }, 89 #if defined(__x86) 90 { FM_IOC_PHYSCPU_INFO, B_FALSE, FM_CPU_INFO_VERSION, 91 fm_ioctl_physcpu_info }, 92 { FM_IOC_CPU_RETIRE, B_TRUE, FM_CPU_OP_VERSION, 93 fm_ioctl_cpu_retire }, 94 { FM_IOC_CPU_STATUS, B_FALSE, FM_CPU_OP_VERSION, 95 fm_ioctl_cpu_retire }, 96 { FM_IOC_CPU_UNRETIRE, B_TRUE, FM_CPU_OP_VERSION, 97 fm_ioctl_cpu_retire }, 98 { FM_IOC_GENTOPO_LEGACY, B_FALSE, FM_TOPO_LEGACY_VERSION, 99 fm_ioctl_gentopo_legacy }, 100 #endif /* __x86 */ 101 { -1, B_FALSE, NULL, NULL }, 102 }; 103 104 static dev_info_t *fm_dip; 105 static boolean_t is_i86xpv; 106 static nvlist_t *fm_vers_nvl; 107 108 static int 109 fm_attach(dev_info_t *dip, ddi_attach_cmd_t cmd) 110 { 111 switch (cmd) { 112 case DDI_ATTACH: 113 if (ddi_create_minor_node(dip, ddi_get_name(dip), S_IFCHR, 114 ddi_get_instance(dip), DDI_PSEUDO, 0) != DDI_SUCCESS) { 115 ddi_remove_minor_node(dip, NULL); 116 return (DDI_FAILURE); 117 } 118 fm_dip = dip; 119 is_i86xpv = (strcmp(platform, "i86xpv") == 0); 120 break; 121 case DDI_RESUME: 122 break; 123 default: 124 return (DDI_FAILURE); 125 } 126 return (DDI_SUCCESS); 127 } 128 129 static int 130 fm_detach(dev_info_t *dip, ddi_detach_cmd_t cmd) 131 { 132 int ret = DDI_SUCCESS; 133 134 switch (cmd) { 135 case DDI_DETACH: 136 ddi_remove_minor_node(dip, NULL); 137 fm_dip = NULL; 138 break; 139 default: 140 ret = DDI_FAILURE; 141 } 142 return (ret); 143 } 144 145 /*ARGSUSED*/ 146 static int 147 fm_info(dev_info_t *dip, ddi_info_cmd_t infocmd, void *arg, void **result) 148 { 149 int error; 150 151 switch (infocmd) { 152 case DDI_INFO_DEVT2DEVINFO: 153 *result = fm_dip; 154 error = DDI_SUCCESS; 155 break; 156 case DDI_INFO_DEVT2INSTANCE: 157 *result = NULL; 158 error = DDI_SUCCESS; 159 break; 160 default: 161 error = DDI_FAILURE; 162 } 163 return (error); 164 } 165 166 /*ARGSUSED1*/ 167 static int 168 fm_open(dev_t *devp, int flag, int typ, struct cred *cred) 169 { 170 if (typ != OTYP_CHR) 171 return (EINVAL); 172 if (getminor(*devp) != 0) 173 return (ENXIO); 174 175 return (0); 176 } 177 178 /*ARGSUSED*/ 179 static int 180 fm_ioctl_versions(int cmd, nvlist_t *invl, nvlist_t **onvlp) 181 { 182 nvlist_t *nvl; 183 int err; 184 185 if ((err = nvlist_dup(fm_vers_nvl, &nvl, KM_SLEEP)) == 0) 186 *onvlp = nvl; 187 188 return (err); 189 } 190 191 /* 192 * Given a mem-scheme FMRI for a page, execute the given page retire 193 * command on it. 194 */ 195 /*ARGSUSED*/ 196 static int 197 fm_ioctl_page_retire(int cmd, nvlist_t *invl, nvlist_t **onvlp) 198 { 199 uint64_t pa; 200 nvlist_t *fmri; 201 int err; 202 203 if (is_i86xpv) 204 return (ENOTSUP); 205 206 if ((err = nvlist_lookup_nvlist(invl, FM_PAGE_RETIRE_FMRI, &fmri)) 207 != 0) 208 return (err); 209 210 if ((err = fm_get_paddr(fmri, &pa)) != 0) 211 return (err); 212 213 switch (cmd) { 214 case FM_IOC_PAGE_STATUS: 215 return (page_retire_check(pa, NULL)); 216 217 case FM_IOC_PAGE_RETIRE: 218 return (page_retire(pa, PR_FMA)); 219 220 case FM_IOC_PAGE_UNRETIRE: 221 return (page_unretire(pa)); 222 } 223 224 return (ENOTTY); 225 } 226 227 /*ARGSUSED*/ 228 static int 229 fm_ioctl(dev_t dev, int cmd, intptr_t data, int flag, cred_t *cred, int *rvalp) 230 { 231 char *buf; 232 int err; 233 uint_t model; 234 const fm_subr_t *subr; 235 uint32_t vers; 236 fm_ioc_data_t fid; 237 nvlist_t *invl = NULL, *onvl = NULL; 238 #ifdef _MULTI_DATAMODEL 239 fm_ioc_data32_t fid32; 240 #endif 241 242 if (getminor(dev) != 0) 243 return (ENXIO); 244 245 for (subr = fm_subrs; subr->cmd != cmd; subr++) 246 if (subr->cmd == -1) 247 return (ENOTTY); 248 249 if (subr->priv && (flag & FWRITE) == 0 && 250 secpolicy_sys_config(CRED(), 0) != 0) 251 return (EPERM); 252 253 model = ddi_model_convert_from(flag & FMODELS); 254 255 switch (model) { 256 #ifdef _MULTI_DATAMODEL 257 case DDI_MODEL_ILP32: 258 if (ddi_copyin((void *)data, &fid32, 259 sizeof (fm_ioc_data32_t), flag) != 0) 260 return (EFAULT); 261 fid.fid_version = fid32.fid_version; 262 fid.fid_insz = fid32.fid_insz; 263 fid.fid_inbuf = (caddr_t)(uintptr_t)fid32.fid_inbuf; 264 fid.fid_outsz = fid32.fid_outsz; 265 fid.fid_outbuf = (caddr_t)(uintptr_t)fid32.fid_outbuf; 266 break; 267 #endif /* _MULTI_DATAMODEL */ 268 case DDI_MODEL_NONE: 269 default: 270 if (ddi_copyin((void *)data, &fid, sizeof (fm_ioc_data_t), 271 flag) != 0) 272 return (EFAULT); 273 } 274 275 if (nvlist_lookup_uint32(fm_vers_nvl, subr->version, &vers) != 0 || 276 fid.fid_version != vers) 277 return (ENOTSUP); 278 279 if (fid.fid_insz > FM_IOC_MAXBUFSZ) 280 return (ENAMETOOLONG); 281 if (fid.fid_outsz > FM_IOC_OUT_MAXBUFSZ) 282 return (EINVAL); 283 284 /* 285 * Copy in and unpack the input nvlist. 286 */ 287 if (fid.fid_insz != 0 && fid.fid_inbuf != (caddr_t)0) { 288 buf = kmem_alloc(fid.fid_insz, KM_SLEEP); 289 if (ddi_copyin(fid.fid_inbuf, buf, fid.fid_insz, flag) != 0) { 290 kmem_free(buf, fid.fid_insz); 291 return (EFAULT); 292 } 293 err = nvlist_unpack(buf, fid.fid_insz, &invl, KM_SLEEP); 294 kmem_free(buf, fid.fid_insz); 295 if (err != 0) 296 return (err); 297 } 298 299 err = subr->func(cmd, invl, &onvl); 300 301 nvlist_free(invl); 302 303 if (err != 0) { 304 nvlist_free(onvl); 305 return (err); 306 } 307 308 /* 309 * If the output nvlist contains any data, pack it and copyout. 310 */ 311 if (onvl != NULL) { 312 size_t sz; 313 314 if ((err = nvlist_size(onvl, &sz, NV_ENCODE_NATIVE)) != 0) { 315 nvlist_free(onvl); 316 return (err); 317 } 318 if (sz > fid.fid_outsz) { 319 nvlist_free(onvl); 320 return (ENAMETOOLONG); 321 } 322 323 buf = kmem_alloc(sz, KM_SLEEP); 324 if ((err = nvlist_pack(onvl, &buf, &sz, NV_ENCODE_NATIVE, 325 KM_SLEEP)) != 0) { 326 kmem_free(buf, sz); 327 nvlist_free(onvl); 328 return (err); 329 } 330 nvlist_free(onvl); 331 if (ddi_copyout(buf, fid.fid_outbuf, sz, flag) != 0) { 332 kmem_free(buf, sz); 333 return (EFAULT); 334 } 335 kmem_free(buf, sz); 336 fid.fid_outsz = sz; 337 338 switch (model) { 339 #ifdef _MULTI_DATAMODEL 340 case DDI_MODEL_ILP32: 341 fid32.fid_outsz = (size32_t)fid.fid_outsz; 342 if (ddi_copyout(&fid32, (void *)data, 343 sizeof (fm_ioc_data32_t), flag) != 0) 344 return (EFAULT); 345 break; 346 #endif /* _MULTI_DATAMODEL */ 347 case DDI_MODEL_NONE: 348 default: 349 if (ddi_copyout(&fid, (void *)data, 350 sizeof (fm_ioc_data_t), flag) != 0) 351 return (EFAULT); 352 } 353 } 354 355 return (err); 356 } 357 358 static struct cb_ops fm_cb_ops = { 359 fm_open, /* open */ 360 nulldev, /* close */ 361 nodev, /* strategy */ 362 nodev, /* print */ 363 nodev, /* dump */ 364 nodev, /* read */ 365 nodev, /* write */ 366 fm_ioctl, /* ioctl */ 367 nodev, /* devmap */ 368 nodev, /* mmap */ 369 nodev, /* segmap */ 370 nochpoll, /* poll */ 371 ddi_prop_op, /* prop_op */ 372 NULL, /* streamtab */ 373 D_NEW | D_MP | D_64BIT | D_U64BIT 374 }; 375 376 static struct dev_ops fm_ops = { 377 DEVO_REV, /* devo_rev, */ 378 0, /* refcnt */ 379 fm_info, /* get_dev_info */ 380 nulldev, /* identify */ 381 nulldev, /* probe */ 382 fm_attach, /* attach */ 383 fm_detach, /* detach */ 384 nodev, /* reset */ 385 &fm_cb_ops, /* driver operations */ 386 (struct bus_ops *)0 /* bus operations */ 387 }; 388 389 static struct modldrv modldrv = { 390 &mod_driverops, "fault management driver", &fm_ops, 391 }; 392 393 static struct modlinkage modlinkage = { 394 MODREV_1, &modldrv, NULL 395 }; 396 397 int 398 _init(void) 399 { 400 const fm_vers_t *p; 401 int ret; 402 403 404 if ((ret = mod_install(&modlinkage)) == 0) { 405 (void) nvlist_alloc(&fm_vers_nvl, NV_UNIQUE_NAME, KM_SLEEP); 406 for (p = fm_versions; p->interface != NULL; p++) 407 (void) nvlist_add_uint32(fm_vers_nvl, p->interface, 408 p->version); 409 } 410 411 return (ret); 412 } 413 414 int 415 _info(struct modinfo *modinfop) 416 { 417 return (mod_info(&modlinkage, modinfop)); 418 } 419 420 int 421 _fini(void) 422 { 423 int ret; 424 425 if ((ret = mod_remove(&modlinkage)) == 0) { 426 nvlist_free(fm_vers_nvl); 427 } 428 429 return (ret); 430 }