xref: /illumos-gate/usr/src/cmd/ucodeadm/ucodeadm.c (revision bf56214c0556fa6864189c826d39dbe156bb22a0)
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