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