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