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 (c) 2018, Joyent, Inc. 28 * Copyright 2021 OmniOS Community Edition (OmniOSce) Association. 29 */ 30 31 #include <sys/types.h> 32 #include <sys/processor.h> 33 #include <sys/ucode.h> 34 #include <sys/ioctl.h> 35 #include <sys/stat.h> 36 #include <unistd.h> 37 #include <dirent.h> 38 #include <fcntl.h> 39 #include <errno.h> 40 #include <stdio.h> 41 #include <stdlib.h> 42 #include <stdarg.h> 43 #include <string.h> 44 #include <errno.h> 45 #include <syslog.h> 46 #include <time.h> 47 #include <ctype.h> 48 #include <assert.h> 49 #include <libgen.h> 50 #include <locale.h> 51 #include <libintl.h> 52 53 #define UCODE_OPT_INSTALL 0x0001 54 #define UCODE_OPT_UPDATE 0x0002 55 #define UCODE_OPT_VERSION 0x0004 56 57 static const char ucode_dev[] = "/dev/" UCODE_DRIVER_NAME; 58 59 static char *cmdname; 60 61 static char ucode_vendor_str[UCODE_MAX_VENDORS_NAME_LEN]; 62 static char ucode_install_path[] = UCODE_INSTALL_PATH; 63 64 static int ucode_debug = 0; 65 66 static int ucode_convert_amd(const char *, uint8_t *, size_t); 67 static int ucode_convert_intel(const char *, uint8_t *, size_t); 68 69 static ucode_errno_t ucode_gen_files_amd(uint8_t *, int, char *); 70 static ucode_errno_t ucode_gen_files_intel(uint8_t *, int, char *); 71 72 static const struct ucode_ops ucode_ops[] = { 73 { ucode_convert_intel, ucode_gen_files_intel, ucode_validate_intel }, 74 { ucode_convert_amd, ucode_gen_files_amd, ucode_validate_amd }, 75 }; 76 77 const struct ucode_ops *ucode; 78 79 static void 80 dprintf(const char *format, ...) 81 { 82 if (ucode_debug) { 83 va_list alist; 84 va_start(alist, format); 85 (void) vfprintf(stderr, format, alist); 86 va_end(alist); 87 } 88 } 89 90 static void 91 usage(int verbose) 92 { 93 (void) fprintf(stderr, gettext("usage:\n")); 94 (void) fprintf(stderr, "\t%s -v\n", cmdname); 95 if (verbose) { 96 (void) fprintf(stderr, 97 gettext("\t\t Shows running microcode version.\n\n")); 98 } 99 100 (void) fprintf(stderr, "\t%s -u microcode-file\n", cmdname); 101 if (verbose) { 102 (void) fprintf(stderr, gettext("\t\t Updates microcode to the " 103 "latest matching version found in\n" 104 "\t\t microcode-file.\n\n")); 105 } 106 107 (void) fprintf(stderr, "\t%s -i [-R path] microcode-file\n", cmdname); 108 if (verbose) { 109 (void) fprintf(stderr, gettext("\t\t Installs microcode to be " 110 "used for subsequent boots.\n\n")); 111 (void) fprintf(stderr, gettext("Microcode file name must start " 112 "with vendor name, such as \"intel\" or \"amd\".\n\n")); 113 } 114 } 115 116 static void 117 ucode_perror(const char *str, ucode_errno_t rc) 118 { 119 (void) fprintf(stderr, "%s: %s: %s\n", cmdname, str, 120 errno == 0 ? ucode_strerror(rc) : strerror(errno)); 121 errno = 0; 122 } 123 124 #define LINESIZE 120 /* copyright line sometimes is longer than 80 */ 125 126 /* 127 * Convert text format microcode release into binary format. 128 * Return the number of characters read. 129 */ 130 static int 131 ucode_convert_amd(const char *infile, uint8_t *buf, size_t size) 132 { 133 int fd; 134 135 if (infile == NULL || buf == NULL || size == 0) 136 return (0); 137 138 if ((fd = open(infile, O_RDONLY)) < 0) 139 return (0); 140 141 size = read(fd, buf, size); 142 143 (void) close(fd); 144 145 return (size); 146 } 147 148 static int 149 ucode_convert_intel(const char *infile, uint8_t *buf, size_t size) 150 { 151 char linebuf[LINESIZE]; 152 FILE *infd = NULL; 153 int count = 0, firstline = 1; 154 uint32_t *intbuf = (uint32_t *)(intptr_t)buf; 155 156 if (infile == NULL || buf == NULL || size == 0) 157 return (0); 158 159 if ((infd = fopen(infile, "r")) == NULL) 160 return (0); 161 162 while (fgets(linebuf, LINESIZE, infd)) { 163 164 /* Check to see if we are processing a binary file */ 165 if (firstline && !isprint(linebuf[0])) { 166 if (fseek(infd, 0, SEEK_SET) == 0) 167 count = fread(buf, 1, size, infd); 168 169 (void) fclose(infd); 170 return (count); 171 } 172 173 firstline = 0; 174 175 /* Skip blank lines */ 176 if (strlen(linebuf) == 1) 177 continue; 178 179 /* Skip lines with all spaces or tabs */ 180 if (strcspn(linebuf, " \t") == 0) 181 continue; 182 183 /* Text file. Skip comments. */ 184 if (linebuf[0] == '/') 185 continue; 186 187 if (sscanf(linebuf, "%x, %x, %x, %x", 188 &intbuf[count], &intbuf[count+1], 189 &intbuf[count+2], &intbuf[count+3]) != 4) 190 break; 191 192 count += 4; 193 } 194 195 (void) fclose(infd); 196 197 /* 198 * If we get here, we are processing a text format file 199 * where "count" is used to count the number of integers 200 * read. Convert it to number of characters read. 201 */ 202 return (count * sizeof (int)); 203 } 204 205 /* 206 * Returns 0 if no need to update the link; -1 otherwise 207 */ 208 static int 209 ucode_should_update_intel(char *filename, uint32_t new_rev) 210 { 211 int fd; 212 struct stat statbuf; 213 ucode_header_intel_t header; 214 215 /* 216 * If the file or link already exists, check to see if 217 * it is necessary to update it. 218 */ 219 if (stat(filename, &statbuf) == 0) { 220 if ((fd = open(filename, O_RDONLY)) == -1) 221 return (-1); 222 223 if (read(fd, &header, sizeof (header)) == -1) { 224 (void) close(fd); 225 return (-1); 226 } 227 228 (void) close(fd); 229 230 if (header.uh_rev >= new_rev) 231 return (0); 232 } 233 234 return (-1); 235 } 236 237 /* 238 * Generate microcode binary files. Must be called after ucode_validate(). 239 */ 240 static ucode_errno_t 241 ucode_gen_files_amd(uint8_t *buf, int size, char *path) 242 { 243 uint32_t *ptr = (uint32_t *)buf; 244 char common_path[PATH_MAX]; 245 int fd, count, counter; 246 ucode_header_amd_t *uh; 247 int last_cpu_rev = 0; 248 249 250 /* write container file */ 251 (void) snprintf(common_path, PATH_MAX, "%s/%s", path, "container"); 252 253 dprintf("path = %s\n", common_path); 254 fd = open(common_path, O_WRONLY | O_CREAT | O_TRUNC, 255 S_IRUSR | S_IRGRP | S_IROTH); 256 257 if (fd == -1) { 258 ucode_perror(common_path, EM_SYS); 259 return (EM_SYS); 260 } 261 262 if (write(fd, buf, size) != size) { 263 (void) close(fd); 264 ucode_perror(common_path, EM_SYS); 265 return (EM_SYS); 266 } 267 268 (void) close(fd); 269 270 /* skip over magic number & equivalence table header */ 271 ptr += 2; size -= 8; 272 273 count = *ptr++; size -= 4; 274 275 /* equivalence table uses special name */ 276 (void) snprintf(common_path, PATH_MAX, "%s/%s", path, 277 "equivalence-table"); 278 279 for (;;) { 280 dprintf("path = %s\n", common_path); 281 fd = open(common_path, O_WRONLY | O_CREAT | O_TRUNC, 282 S_IRUSR | S_IRGRP | S_IROTH); 283 284 if (fd == -1) { 285 ucode_perror(common_path, EM_SYS); 286 return (EM_SYS); 287 } 288 289 if (write(fd, ptr, count) != count) { 290 (void) close(fd); 291 ucode_perror(common_path, EM_SYS); 292 return (EM_SYS); 293 } 294 295 (void) close(fd); 296 ptr += count >> 2; size -= count; 297 298 if (!size) 299 return (EM_OK); 300 301 ptr++; size -= 4; 302 count = *ptr++; size -= 4; 303 304 /* construct name from header information */ 305 uh = (ucode_header_amd_t *)ptr; 306 307 if (uh->uh_cpu_rev != last_cpu_rev) { 308 last_cpu_rev = uh->uh_cpu_rev; 309 counter = 0; 310 } 311 312 (void) snprintf(common_path, PATH_MAX, "%s/%04X-%02X", path, 313 uh->uh_cpu_rev, counter++); 314 } 315 } 316 317 static ucode_errno_t 318 ucode_gen_files_intel(uint8_t *buf, int size, char *path) 319 { 320 int remaining; 321 char common_path[PATH_MAX]; 322 DIR *dirp; 323 struct dirent *dp; 324 325 (void) snprintf(common_path, PATH_MAX, "%s/%s", path, 326 UCODE_INSTALL_COMMON_PATH); 327 328 if (mkdirp(common_path, 0755) == -1 && errno != EEXIST) { 329 ucode_perror(common_path, EM_SYS); 330 return (EM_SYS); 331 } 332 333 for (remaining = size; remaining > 0; ) { 334 uint32_t total_size, body_size, offset; 335 char firstname[PATH_MAX]; 336 char name[PATH_MAX]; 337 int i; 338 uint8_t *curbuf = &buf[size - remaining]; 339 ucode_header_intel_t *uhp; 340 ucode_ext_table_intel_t *extp; 341 342 uhp = (ucode_header_intel_t *)(intptr_t)curbuf; 343 344 total_size = UCODE_TOTAL_SIZE_INTEL(uhp->uh_total_size); 345 body_size = UCODE_BODY_SIZE_INTEL(uhp->uh_body_size); 346 347 remaining -= total_size; 348 349 (void) snprintf(firstname, PATH_MAX, "%s/%08X-%02X", 350 common_path, uhp->uh_signature, uhp->uh_proc_flags); 351 dprintf("firstname = %s\n", firstname); 352 353 if (ucode_should_update_intel(firstname, uhp->uh_rev) != 0) { 354 int fd; 355 356 /* Remove the existing one first */ 357 (void) unlink(firstname); 358 359 if ((fd = open(firstname, O_WRONLY | O_CREAT | O_TRUNC, 360 S_IRUSR | S_IRGRP | S_IROTH)) == -1) { 361 ucode_perror(firstname, EM_SYS); 362 return (EM_SYS); 363 } 364 365 if (write(fd, curbuf, total_size) != total_size) { 366 (void) close(fd); 367 ucode_perror(firstname, EM_SYS); 368 return (EM_SYS); 369 } 370 371 (void) close(fd); 372 } 373 374 /* 375 * Only 1 byte of the proc_flags field is used, therefore 376 * we only need to match 8 potential platform ids. 377 */ 378 for (i = 0; i < 8; i++) { 379 uint32_t platid = uhp->uh_proc_flags & (1 << i); 380 381 if (platid == 0 && uhp->uh_proc_flags != 0) 382 continue; 383 384 (void) snprintf(name, PATH_MAX, 385 "%s/%08X-%02X", path, uhp->uh_signature, platid); 386 387 dprintf("proc_flags = %x, platid = %x, name = %s\n", 388 uhp->uh_proc_flags, platid, name); 389 390 if (ucode_should_update_intel(name, 391 uhp->uh_rev) != 0) { 392 /* Remove the existing one first */ 393 (void) unlink(name); 394 if (link(firstname, name) == -1) { 395 ucode_perror(name, EM_SYS); 396 return (EM_SYS); 397 } 398 } 399 400 if (uhp->uh_proc_flags == 0) 401 break; 402 } 403 404 offset = UCODE_HEADER_SIZE_INTEL + body_size; 405 406 /* Check to see if there is extended signature table */ 407 if (total_size == offset) 408 continue; 409 410 /* There is extended signature table. More processing. */ 411 extp = (ucode_ext_table_intel_t *)(uintptr_t)&curbuf[offset]; 412 413 for (i = 0; i < extp->uet_count; i++) { 414 ucode_ext_sig_intel_t *uesp = &extp->uet_ext_sig[i]; 415 int j; 416 417 for (j = 0; j < 8; j++) { 418 uint32_t id = uesp->ues_proc_flags & (1 << j); 419 420 if (id == 0 && uesp->ues_proc_flags) 421 continue; 422 423 (void) snprintf(name, PATH_MAX, 424 "%s/%08X-%02X", path, 425 uesp->ues_signature, id); 426 427 dprintf("extsig: proc_flags = %x, " 428 "platid = %x, name = %s\n", 429 uesp->ues_proc_flags, id, name); 430 431 if (ucode_should_update_intel(name, 432 uhp->uh_rev) != 0) { 433 /* Remove the existing one first */ 434 (void) unlink(name); 435 if (link(firstname, name) == -1) { 436 ucode_perror(name, EM_SYS); 437 return (EM_SYS); 438 } 439 } 440 441 if (uesp->ues_proc_flags == 0) 442 break; 443 } 444 } 445 446 } 447 448 /* 449 * Remove files with no links to them. These are probably 450 * obsolete microcode files. 451 */ 452 if ((dirp = opendir(common_path)) == NULL) { 453 ucode_perror(common_path, EM_SYS); 454 return (EM_SYS); 455 } 456 457 while ((dp = readdir(dirp)) != NULL) { 458 char filename[PATH_MAX]; 459 struct stat statbuf; 460 461 (void) snprintf(filename, PATH_MAX, 462 "%s/%s", common_path, dp->d_name); 463 if (stat(filename, &statbuf) == -1) 464 continue; 465 466 if ((statbuf.st_mode & S_IFMT) == S_IFREG) { 467 if (statbuf.st_nlink == 1) 468 (void) unlink(filename); 469 } 470 } 471 472 (void) closedir(dirp); 473 474 return (EM_OK); 475 } 476 477 /* 478 * Returns 0 on success, 2 on usage error, and 3 on operation error. 479 */ 480 int 481 main(int argc, char *argv[]) 482 { 483 int c; 484 int action = 0; 485 int actcount = 0; 486 char *path = NULL; 487 char *filename = NULL; 488 int errflg = 0; 489 int dev_fd = -1; 490 int fd = -1; 491 int verbose = 0; 492 uint8_t *buf = NULL; 493 ucode_errno_t rc = EM_OK; 494 processorid_t cpuid_max; 495 struct stat filestat; 496 uint32_t ucode_size; 497 498 (void) setlocale(LC_ALL, ""); 499 500 #if !defined(TEXT_DOMAIN) 501 #define TEXT_DOMAIN "SYS_TEST" 502 #endif 503 (void) textdomain(TEXT_DOMAIN); 504 505 cmdname = basename(argv[0]); 506 507 while ((c = getopt(argc, argv, "idhuvVR:")) != EOF) { 508 switch (c) { 509 510 case 'i': 511 action |= UCODE_OPT_INSTALL; 512 actcount++; 513 break; 514 515 case 'u': 516 action |= UCODE_OPT_UPDATE; 517 actcount++; 518 break; 519 520 case 'v': 521 action |= UCODE_OPT_VERSION; 522 actcount++; 523 break; 524 525 case 'd': 526 ucode_debug = 1; 527 break; 528 529 case 'R': 530 if (optarg[0] == '-') 531 errflg++; 532 else if (strlen(optarg) > UCODE_MAX_PATH_LEN) { 533 (void) fprintf(stderr, 534 gettext("Alternate path too long\n")); 535 errflg++; 536 } else if ((path = strdup(optarg)) == NULL) { 537 errflg++; 538 } 539 540 break; 541 542 case 'V': 543 verbose = 1; 544 break; 545 546 case 'h': 547 usage(1); 548 return (0); 549 550 default: 551 usage(verbose); 552 return (2); 553 } 554 } 555 556 if (actcount != 1) { 557 (void) fprintf(stderr, gettext("%s: options -v, -i and -u " 558 "are mutually exclusive.\n"), cmdname); 559 usage(verbose); 560 return (2); 561 } 562 563 if (optind <= argc - 1) 564 filename = argv[optind]; 565 else if (!(action & UCODE_OPT_VERSION)) 566 errflg++; 567 568 if (errflg || action == 0) { 569 usage(verbose); 570 return (2); 571 } 572 573 /* 574 * Convert from text format to binary format 575 */ 576 if ((action & UCODE_OPT_INSTALL) || (action & UCODE_OPT_UPDATE)) { 577 int i; 578 UCODE_VENDORS; 579 580 for (i = 0; ucode_vendors[i].filestr != NULL; i++) { 581 dprintf("i = %d, filestr = %s, filename = %s\n", 582 i, ucode_vendors[i].filestr, filename); 583 if (strncasecmp(ucode_vendors[i].filestr, 584 basename(filename), 585 strlen(ucode_vendors[i].filestr)) == 0) { 586 ucode = &ucode_ops[i]; 587 (void) strncpy(ucode_vendor_str, 588 ucode_vendors[i].vendorstr, 589 sizeof (ucode_vendor_str)); 590 break; 591 } 592 } 593 594 if (ucode_vendors[i].filestr == NULL) { 595 rc = EM_NOVENDOR; 596 ucode_perror(basename(filename), rc); 597 goto err_out; 598 } 599 600 if ((stat(filename, &filestat)) < 0) { 601 rc = EM_SYS; 602 ucode_perror(filename, rc); 603 goto err_out; 604 } 605 606 if ((filestat.st_mode & S_IFMT) != S_IFREG && 607 (filestat.st_mode & S_IFMT) != S_IFLNK) { 608 rc = EM_FILEFORMAT; 609 ucode_perror(filename, rc); 610 goto err_out; 611 } 612 613 if ((buf = malloc(filestat.st_size)) == NULL) { 614 rc = EM_SYS; 615 ucode_perror(filename, rc); 616 goto err_out; 617 } 618 619 ucode_size = ucode->convert(filename, buf, filestat.st_size); 620 621 dprintf("ucode_size = %d\n", ucode_size); 622 623 if (ucode_size == 0) { 624 rc = EM_FILEFORMAT; 625 ucode_perror(filename, rc); 626 goto err_out; 627 } 628 629 if ((rc = ucode->validate(buf, ucode_size)) != EM_OK) { 630 ucode_perror(filename, rc); 631 goto err_out; 632 } 633 } 634 635 /* 636 * For the install option, the microcode file must start with 637 * "intel" for Intel microcode, and "amd" for AMD microcode. 638 */ 639 if (action & UCODE_OPT_INSTALL) { 640 /* 641 * If no path is provided by the -R option, put the files in 642 * /ucode_install_path/ucode_vendor_str/. 643 */ 644 if (path == NULL) { 645 if ((path = malloc(PATH_MAX)) == NULL) { 646 rc = EM_SYS; 647 ucode_perror("malloc", rc); 648 goto err_out; 649 } 650 651 (void) snprintf(path, PATH_MAX, "/%s/%s", 652 ucode_install_path, ucode_vendor_str); 653 } 654 655 if (mkdirp(path, 0755) == -1 && errno != EEXIST) { 656 rc = EM_SYS; 657 ucode_perror(path, rc); 658 goto err_out; 659 } 660 661 rc = ucode->gen_files(buf, ucode_size, path); 662 663 goto err_out; 664 } 665 666 if ((dev_fd = open(ucode_dev, O_RDONLY)) == -1) { 667 rc = EM_SYS; 668 ucode_perror(ucode_dev, rc); 669 goto err_out; 670 } 671 672 if (action & UCODE_OPT_VERSION) { 673 int tmprc; 674 uint32_t *revp = NULL; 675 int i; 676 #if defined(_SYSCALL32_IMPL) 677 struct ucode_get_rev_struct32 inf32; 678 #else 679 struct ucode_get_rev_struct info; 680 #endif 681 682 cpuid_max = (processorid_t)sysconf(_SC_CPUID_MAX); 683 684 if ((revp = (uint32_t *) 685 malloc(cpuid_max * sizeof (uint32_t))) == NULL) { 686 rc = EM_SYS; 687 ucode_perror("malloc", rc); 688 goto err_out; 689 } 690 691 for (i = 0; i < cpuid_max; i++) 692 revp[i] = (uint32_t)-1; 693 694 #if defined(_SYSCALL32_IMPL) 695 info32.ugv_rev = (caddr32_t)revp; 696 info32.ugv_size = cpuid_max; 697 info32.ugv_errno = EM_OK; 698 tmprc = ioctl(dev_fd, UCODE_GET_VERSION, &info32); 699 rc = info32.ugv_errno; 700 #else 701 info.ugv_rev = revp; 702 info.ugv_size = cpuid_max; 703 info.ugv_errno = EM_OK; 704 tmprc = ioctl(dev_fd, UCODE_GET_VERSION, &info); 705 rc = info.ugv_errno; 706 #endif 707 708 if (tmprc && rc == EM_OK) { 709 rc = EM_SYS; 710 } 711 712 if (rc == EM_OK) { 713 (void) printf(gettext("CPU\tMicrocode Version\n")); 714 for (i = 0; i < cpuid_max; i++) { 715 if (info.ugv_rev[i] == (uint32_t)-1) 716 continue; 717 (void) printf("%d\t0x%x\n", i, info.ugv_rev[i]); 718 } 719 } else { 720 ucode_perror(gettext("get microcode version"), rc); 721 } 722 723 if (revp) 724 free(revp); 725 } 726 727 if (action & UCODE_OPT_UPDATE) { 728 int tmprc; 729 #if defined(_SYSCALL32_IMPL) 730 struct ucode_write_struct32 uw_struct32; 731 #else 732 struct ucode_write_struct uw_struct; 733 #endif 734 735 #if defined(_SYSCALL32_IMPL) 736 uw_struct32.uw_size = ucode_size; 737 uw_struct32.uw_ucode = (caddr32_t)buf; 738 uw_struct32.uw_errno = EM_OK; 739 tmprc = ioctl(dev_fd, UCODE_UPDATE, &uw_struct32); 740 rc = uw_struct32.uw_errno; 741 742 #else 743 uw_struct.uw_size = ucode_size; 744 uw_struct.uw_ucode = buf; 745 uw_struct.uw_errno = EM_OK; 746 tmprc = ioctl(dev_fd, UCODE_UPDATE, &uw_struct); 747 rc = uw_struct.uw_errno; 748 #endif 749 750 if (rc == EM_OK) { 751 if (tmprc) { 752 rc = EM_SYS; 753 ucode_perror(ucode_dev, rc); 754 } 755 } else if (rc == EM_NOMATCH || rc == EM_HIGHERREV) { 756 ucode_perror(filename, rc); 757 } else { 758 ucode_perror(gettext("microcode update"), rc); 759 } 760 } 761 762 err_out: 763 if (dev_fd != -1) 764 (void) close(dev_fd); 765 766 if (fd != -1) 767 (void) close(fd); 768 769 free(buf); 770 free(path); 771 772 if (rc != EM_OK) 773 return (3); 774 775 return (0); 776 } 777