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