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