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 /* 28 * Copyright 2023 Oxide Computer Company 29 */ 30 31 /* 32 * libfmd_agent contains the low-level operations that needed by the fmd 33 * agents, such as page operations (status/retire/unretire), cpu operations 34 * (status/online/offline), etc. 35 * 36 * Some operations are implemented by /dev/fm ioctls. Those ioctls are 37 * heavily versioned to allow userland patching without requiring a reboot 38 * to get a matched /dev/fm. All the ioctls use packed nvlist to interact 39 * between userland and kernel. (see fmd_agent_nvl_ioctl()). 40 */ 41 42 #include <fcntl.h> 43 #include <errno.h> 44 #include <unistd.h> 45 #include <strings.h> 46 #include <libnvpair.h> 47 #include <string.h> 48 #include <sys/types.h> 49 #include <sys/devfm.h> 50 #include <fmd_agent_impl.h> 51 52 int 53 fmd_agent_errno(fmd_agent_hdl_t *hdl) 54 { 55 return (hdl->agent_errno); 56 } 57 58 int 59 fmd_agent_seterrno(fmd_agent_hdl_t *hdl, int err) 60 { 61 hdl->agent_errno = err; 62 return (-1); 63 } 64 65 const char * 66 fmd_agent_strerr(int err) 67 { 68 return (strerror(err)); 69 } 70 71 const char * 72 fmd_agent_errmsg(fmd_agent_hdl_t *hdl) 73 { 74 return (fmd_agent_strerr(hdl->agent_errno)); 75 } 76 77 static int 78 cleanup_set_errno(fmd_agent_hdl_t *hdl, nvlist_t *innvl, nvlist_t *outnvl, 79 int err) 80 { 81 nvlist_free(innvl); 82 nvlist_free(outnvl); 83 return (fmd_agent_seterrno(hdl, err)); 84 } 85 86 /* 87 * Perform /dev/fm ioctl. The input and output data are represented by 88 * name-value lists (nvlists). 89 */ 90 int 91 fmd_agent_nvl_ioctl(fmd_agent_hdl_t *hdl, int cmd, uint32_t ver, 92 nvlist_t *innvl, nvlist_t **outnvlp) 93 { 94 fm_ioc_data_t fid; 95 int err = 0; 96 char *inbuf = NULL, *outbuf = NULL; 97 size_t insz = 0, outsz = 0; 98 99 if (innvl != NULL) { 100 if ((err = nvlist_size(innvl, &insz, NV_ENCODE_NATIVE)) != 0) 101 return (err); 102 if (insz > FM_IOC_MAXBUFSZ) 103 return (ENAMETOOLONG); 104 if ((inbuf = umem_alloc(insz, UMEM_DEFAULT)) == NULL) 105 return (errno); 106 107 if ((err = nvlist_pack(innvl, &inbuf, &insz, 108 NV_ENCODE_NATIVE, 0)) != 0) { 109 umem_free(inbuf, insz); 110 return (err); 111 } 112 } 113 114 if (outnvlp != NULL) { 115 outsz = FM_IOC_OUT_BUFSZ; 116 } 117 for (;;) { 118 if (outnvlp != NULL) { 119 outbuf = umem_alloc(outsz, UMEM_DEFAULT); 120 if (outbuf == NULL) { 121 err = errno; 122 break; 123 } 124 } 125 126 fid.fid_version = ver; 127 fid.fid_insz = insz; 128 fid.fid_inbuf = inbuf; 129 fid.fid_outsz = outsz; 130 fid.fid_outbuf = outbuf; 131 132 if (ioctl(hdl->agent_devfd, cmd, &fid) < 0) { 133 if (errno == ENAMETOOLONG && outsz != 0 && 134 outsz <= (FM_IOC_OUT_MAXBUFSZ / 2)) { 135 umem_free(outbuf, outsz); 136 outbuf = NULL; 137 outsz *= 2; 138 } else { 139 err = errno; 140 break; 141 } 142 } else if (outnvlp != NULL) { 143 err = nvlist_unpack(fid.fid_outbuf, fid.fid_outsz, 144 outnvlp, 0); 145 break; 146 } else { 147 break; 148 } 149 } 150 151 if (inbuf != NULL) 152 umem_free(inbuf, insz); 153 if (outbuf != NULL) 154 umem_free(outbuf, outsz); 155 156 return (err); 157 } 158 159 /* 160 * Open /dev/fm and return a handle. ver is the overall interface version. 161 */ 162 static fmd_agent_hdl_t * 163 fmd_agent_open_dev(int ver, int mode) 164 { 165 fmd_agent_hdl_t *hdl; 166 int fd, err; 167 nvlist_t *nvl; 168 169 if ((fd = open("/dev/fm", mode)) < 0) 170 return (NULL); /* errno is set for us */ 171 172 if ((hdl = umem_alloc(sizeof (fmd_agent_hdl_t), 173 UMEM_DEFAULT)) == NULL) { 174 err = errno; 175 (void) close(fd); 176 errno = err; 177 return (NULL); 178 } 179 180 hdl->agent_devfd = fd; 181 hdl->agent_version = ver; 182 183 /* 184 * Get the individual interface versions. 185 */ 186 if ((err = fmd_agent_nvl_ioctl(hdl, FM_IOC_VERSIONS, ver, NULL, &nvl)) 187 < 0) { 188 (void) close(fd); 189 umem_free(hdl, sizeof (fmd_agent_hdl_t)); 190 errno = err; 191 return (NULL); 192 } 193 194 hdl->agent_ioc_versions = nvl; 195 return (hdl); 196 } 197 198 fmd_agent_hdl_t * 199 fmd_agent_open(int ver) 200 { 201 if (ver > FMD_AGENT_VERSION) { 202 errno = ENOTSUP; 203 return (NULL); 204 } 205 return (fmd_agent_open_dev(ver, O_RDONLY)); 206 } 207 208 void 209 fmd_agent_close(fmd_agent_hdl_t *hdl) 210 { 211 (void) close(hdl->agent_devfd); 212 nvlist_free(hdl->agent_ioc_versions); 213 umem_free(hdl, sizeof (fmd_agent_hdl_t)); 214 } 215 216 /* 217 * Given a interface name, return the kernel interface version. 218 */ 219 int 220 fmd_agent_version(fmd_agent_hdl_t *hdl, const char *op, uint32_t *verp) 221 { 222 int err; 223 224 err = nvlist_lookup_uint32(hdl->agent_ioc_versions, 225 op, verp); 226 227 if (err != 0) { 228 errno = err; 229 return (-1); 230 } 231 return (0); 232 } 233 234 static int 235 fmd_agent_pageop_v1(fmd_agent_hdl_t *hdl, int cmd, nvlist_t *fmri) 236 { 237 int err; 238 nvlist_t *nvl = NULL; 239 240 if ((err = nvlist_alloc(&nvl, NV_UNIQUE_NAME_TYPE, 0)) != 0 || 241 (err = nvlist_add_nvlist(nvl, FM_PAGE_RETIRE_FMRI, fmri)) != 0 || 242 (err = fmd_agent_nvl_ioctl(hdl, cmd, 1, nvl, NULL)) != 0) 243 return (cleanup_set_errno(hdl, nvl, NULL, err)); 244 245 nvlist_free(nvl); 246 return (0); 247 } 248 249 static int 250 fmd_agent_pageop(fmd_agent_hdl_t *hdl, int cmd, nvlist_t *fmri) 251 { 252 uint32_t ver; 253 254 if (fmd_agent_version(hdl, FM_PAGE_OP_VERSION, &ver) == -1) 255 return (fmd_agent_seterrno(hdl, errno)); 256 257 switch (ver) { 258 case 1: 259 return (fmd_agent_pageop_v1(hdl, cmd, fmri)); 260 261 default: 262 return (fmd_agent_seterrno(hdl, ENOTSUP)); 263 } 264 } 265 266 int 267 fmd_agent_page_retire(fmd_agent_hdl_t *hdl, nvlist_t *fmri) 268 { 269 int rc = fmd_agent_pageop(hdl, FM_IOC_PAGE_RETIRE, fmri); 270 int err = fmd_agent_errno(hdl); 271 272 /* 273 * FM_IOC_PAGE_RETIRE ioctl returns: 274 * 0 - success in retiring page 275 * -1, errno = EIO - page is already retired 276 * -1, errno = EAGAIN - page is scheduled for retirement 277 * -1, errno = EINVAL - page fmri is invalid 278 * -1, errno = any else - error 279 */ 280 if (rc == 0 || err == EIO || err == EINVAL) { 281 if (rc == 0) 282 (void) fmd_agent_seterrno(hdl, 0); 283 return (FMD_AGENT_RETIRE_DONE); 284 } 285 if (err == EAGAIN) 286 return (FMD_AGENT_RETIRE_ASYNC); 287 288 return (FMD_AGENT_RETIRE_FAIL); 289 } 290 291 int 292 fmd_agent_page_unretire(fmd_agent_hdl_t *hdl, nvlist_t *fmri) 293 { 294 int rc = fmd_agent_pageop(hdl, FM_IOC_PAGE_UNRETIRE, fmri); 295 int err = fmd_agent_errno(hdl); 296 297 /* 298 * FM_IOC_PAGE_UNRETIRE ioctl returns: 299 * 0 - success in unretiring page 300 * -1, errno = EIO - page is already unretired 301 * -1, errno = EAGAIN - page couldn't be locked, still retired 302 * -1, errno = EINVAL - page fmri is invalid 303 * -1, errno = any else - error 304 */ 305 if (rc == 0 || err == EIO || err == EINVAL) { 306 if (rc == 0) 307 (void) fmd_agent_seterrno(hdl, 0); 308 return (FMD_AGENT_RETIRE_DONE); 309 } 310 311 return (FMD_AGENT_RETIRE_FAIL); 312 } 313 314 int 315 fmd_agent_page_isretired(fmd_agent_hdl_t *hdl, nvlist_t *fmri) 316 { 317 int rc = fmd_agent_pageop(hdl, FM_IOC_PAGE_STATUS, fmri); 318 int err = fmd_agent_errno(hdl); 319 320 /* 321 * FM_IOC_PAGE_STATUS returns: 322 * 0 - page is retired 323 * -1, errno = EAGAIN - page is scheduled for retirement 324 * -1, errno = EIO - page not scheduled for retirement 325 * -1, errno = EINVAL - page fmri is invalid 326 * -1, errno = any else - error 327 */ 328 if (rc == 0 || err == EINVAL) { 329 if (rc == 0) 330 (void) fmd_agent_seterrno(hdl, 0); 331 return (FMD_AGENT_RETIRE_DONE); 332 } 333 if (err == EAGAIN) 334 return (FMD_AGENT_RETIRE_ASYNC); 335 336 return (FMD_AGENT_RETIRE_FAIL); 337 } 338 339 void 340 fmd_agent_cache_info_free(fmd_agent_hdl_t *hdl __unused, 341 fmd_agent_cpu_cache_list_t *cache) 342 { 343 for (uint_t cpuno = 0; cpuno < cache->fmc_ncpus; cpuno++) { 344 fmd_agent_cpu_cache_t *cpu_cache = &cache->fmc_cpus[cpuno]; 345 346 if (cpu_cache->fmcc_caches == NULL) 347 continue; 348 349 for (uint_t cacheno = 0; cacheno < cpu_cache->fmcc_ncaches; 350 cacheno++) { 351 nvlist_free(cpu_cache->fmcc_caches[cacheno]); 352 } 353 354 umem_free(cpu_cache->fmcc_caches, sizeof (nvlist_t *) * 355 cpu_cache->fmcc_ncaches); 356 cpu_cache->fmcc_caches = NULL; 357 } 358 359 if (cache->fmc_cpus != NULL) { 360 umem_free(cache->fmc_cpus, sizeof (fmd_agent_cpu_cache_t) * 361 cache->fmc_ncpus); 362 cache->fmc_cpus = NULL; 363 cache->fmc_ncpus = 0; 364 } 365 } 366 367 static int 368 fmd_agent_cache_info_pop_cpu(fmd_agent_cpu_cache_list_t *cache, 369 nvlist_t *cpu_nvl, uint_t cpuno) 370 { 371 int ret; 372 char cpustr[32]; 373 nvlist_t **cache_nvls; 374 uint_t ncache_nvls; 375 fmd_agent_cpu_cache_t *cpu = &cache->fmc_cpus[cpuno]; 376 377 (void) snprintf(cpustr, sizeof (cpustr), "%u", cpuno); 378 if ((ret = nvlist_lookup_nvlist_array(cpu_nvl, cpustr, &cache_nvls, 379 &ncache_nvls)) != 0) { 380 return (ret); 381 } 382 383 if (ncache_nvls == 0) { 384 cpu->fmcc_ncaches = 0; 385 cpu->fmcc_caches = NULL; 386 return (0); 387 } 388 389 cpu->fmcc_caches = umem_zalloc(sizeof (nvlist_t *) * ncache_nvls, 390 UMEM_DEFAULT); 391 if (cpu->fmcc_caches == NULL) { 392 return (errno); 393 } 394 cpu->fmcc_ncaches = ncache_nvls; 395 for (uint_t i = 0; i < cpu->fmcc_ncaches; i++) { 396 ret = nvlist_dup(cache_nvls[i], &cpu->fmcc_caches[i], 0); 397 if (ret != 0) { 398 return (ret); 399 } 400 } 401 402 return (0); 403 } 404 405 int 406 fmd_agent_cache_info(fmd_agent_hdl_t *hdl, fmd_agent_cpu_cache_list_t *cache) 407 { 408 int err, ret = 0; 409 uint32_t ncpus; 410 nvlist_t *nvl = NULL; 411 412 bzero(cache, sizeof (fmd_agent_cpu_cache_list_t)); 413 if ((err = fmd_agent_nvl_ioctl(hdl, FM_IOC_CACHE_INFO, 1, NULL, 414 &nvl)) != 0) { 415 ret = fmd_agent_seterrno(hdl, err); 416 goto out; 417 } 418 419 if ((err = nvlist_lookup_uint32(nvl, FM_CACHE_INFO_NCPUS, &ncpus)) != 420 0) { 421 ret = fmd_agent_seterrno(hdl, err); 422 goto out; 423 } 424 425 cache->fmc_cpus = umem_zalloc(sizeof (fmd_agent_cpu_cache_t) * ncpus, 426 UMEM_DEFAULT); 427 if (cache->fmc_cpus == NULL) { 428 ret = fmd_agent_seterrno(hdl, errno); 429 goto out; 430 } 431 cache->fmc_ncpus = ncpus; 432 for (uint_t i = 0; i < cache->fmc_ncpus; i++) { 433 if ((err = fmd_agent_cache_info_pop_cpu(cache, nvl, i)) != 0) { 434 ret = fmd_agent_seterrno(hdl, errno); 435 goto out; 436 } 437 438 } 439 out: 440 if (ret != 0) { 441 fmd_agent_cache_info_free(hdl, cache); 442 } 443 nvlist_free(nvl); 444 return (ret); 445 } 446