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