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#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
52static const char ucode_dev[] = "/dev/" UCODE_DRIVER_NAME;
53
54static char	*cmdname;
55
56static char	ucode_vendor_str[UCODE_MAX_VENDORS_NAME_LEN];
57static char	ucode_install_path[] = UCODE_INSTALL_PATH;
58
59static int	ucode_debug = 0;
60
61static int ucode_convert_amd(const char *, uint8_t *, size_t);
62static int ucode_convert_intel(const char *, uint8_t *, size_t);
63
64static ucode_errno_t ucode_gen_files_amd(uint8_t *, int, char *);
65static ucode_errno_t ucode_gen_files_intel(uint8_t *, int, char *);
66
67static 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
72const struct ucode_ops *ucode;
73
74static void
75dprintf(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
85static void
86usage(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
111static void
112ucode_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 */
125static int
126ucode_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
143static int
144ucode_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 */
203static int
204ucode_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 */
235static ucode_errno_t
236ucode_gen_files_amd(uint8_t *buf, int size, char *path)
237{
238	/* LINTED: pointer alignment */
239	uint32_t *ptr = (uint32_t *)buf;
240	char common_path[PATH_MAX];
241	int fd, count, counter;
242	ucode_header_amd_t *uh;
243	int last_cpu_rev = 0;
244
245
246	/* write container file */
247	(void) snprintf(common_path, PATH_MAX, "%s/%s", path, "container");
248
249	dprintf("path = %s\n", common_path);
250	fd = open(common_path, O_WRONLY | O_CREAT | O_TRUNC,
251	    S_IRUSR | S_IRGRP | S_IROTH);
252
253	if (fd == -1) {
254		ucode_perror(common_path, EM_SYS);
255		return (EM_SYS);
256	}
257
258	if (write(fd, buf, size) != size) {
259		(void) close(fd);
260		ucode_perror(common_path, EM_SYS);
261		return (EM_SYS);
262	}
263
264	(void) close(fd);
265
266	/* skip over magic number & equivalence table header */
267	ptr += 2; size -= 8;
268
269	count = *ptr++; size -= 4;
270
271	/* equivalence table uses special name */
272	(void) snprintf(common_path, PATH_MAX, "%s/%s", path,
273	    "equivalence-table");
274
275	for (;;) {
276		dprintf("path = %s\n", common_path);
277		fd = open(common_path, O_WRONLY | O_CREAT | O_TRUNC,
278		    S_IRUSR | S_IRGRP | S_IROTH);
279
280		if (fd == -1) {
281			ucode_perror(common_path, EM_SYS);
282			return (EM_SYS);
283		}
284
285		if (write(fd, ptr, count) != count) {
286			(void) close(fd);
287			ucode_perror(common_path, EM_SYS);
288			return (EM_SYS);
289		}
290
291		(void) close(fd);
292		ptr += count >> 2; size -= count;
293
294		if (!size)
295			return (EM_OK);
296
297		ptr++; size -= 4;
298		count = *ptr++; size -= 4;
299
300		/* construct name from header information */
301		uh = (ucode_header_amd_t *)ptr;
302
303		if (uh->uh_cpu_rev != last_cpu_rev) {
304			last_cpu_rev = uh->uh_cpu_rev;
305			counter = 0;
306		}
307
308		(void) snprintf(common_path, PATH_MAX, "%s/%04X-%02X", path,
309		    uh->uh_cpu_rev, counter++);
310	}
311}
312
313static ucode_errno_t
314ucode_gen_files_intel(uint8_t *buf, int size, char *path)
315{
316	int	remaining;
317	char	common_path[PATH_MAX];
318	DIR	*dirp;
319	struct dirent *dp;
320
321	(void) snprintf(common_path, PATH_MAX, "%s/%s", path,
322	    UCODE_INSTALL_COMMON_PATH);
323
324	if (mkdirp(common_path, 0755) == -1 && errno != EEXIST) {
325		ucode_perror(common_path, EM_SYS);
326		return (EM_SYS);
327	}
328
329	for (remaining = size; remaining > 0; ) {
330		uint32_t	total_size, body_size, offset;
331		char		firstname[PATH_MAX];
332		char		name[PATH_MAX];
333		int		i;
334		uint8_t		*curbuf = &buf[size - remaining];
335		ucode_header_intel_t	*uhp;
336		ucode_ext_table_intel_t *extp;
337
338		uhp = (ucode_header_intel_t *)(intptr_t)curbuf;
339
340		total_size = UCODE_TOTAL_SIZE_INTEL(uhp->uh_total_size);
341		body_size = UCODE_BODY_SIZE_INTEL(uhp->uh_body_size);
342
343		remaining -= total_size;
344
345		(void) snprintf(firstname, PATH_MAX, "%s/%08X-%02X",
346		    common_path, uhp->uh_signature, uhp->uh_proc_flags);
347		dprintf("firstname = %s\n", firstname);
348
349		if (ucode_should_update_intel(firstname, uhp->uh_rev) != 0) {
350			int fd;
351
352			/* Remove the existing one first */
353			(void) unlink(firstname);
354
355			if ((fd = open(firstname, O_WRONLY | O_CREAT | O_TRUNC,
356			    S_IRUSR | S_IRGRP | S_IROTH)) == -1) {
357				ucode_perror(firstname, EM_SYS);
358				return (EM_SYS);
359			}
360
361			if (write(fd, curbuf, total_size) != total_size) {
362				(void) close(fd);
363				ucode_perror(firstname, EM_SYS);
364				return (EM_SYS);
365			}
366
367			(void) close(fd);
368		}
369
370		/*
371		 * Only 1 byte of the proc_flags field is used, therefore
372		 * we only need to match 8 potential platform ids.
373		 */
374		for (i = 0; i < 8; i++) {
375			uint32_t platid = uhp->uh_proc_flags & (1 << i);
376
377			if (platid == 0 && uhp->uh_proc_flags != 0)
378				continue;
379
380			(void) snprintf(name, PATH_MAX,
381			    "%s/%08X-%02X", path, uhp->uh_signature, platid);
382
383			dprintf("proc_flags = %x, platid = %x, name = %s\n",
384			    uhp->uh_proc_flags, platid, name);
385
386			if (ucode_should_update_intel(name, uhp->uh_rev) != 0) {
387
388				/* Remove the existing one first */
389				(void) unlink(name);
390
391				if (link(firstname, name) == -1) {
392					ucode_perror(name, EM_SYS);
393					return (EM_SYS);
394				}
395			}
396
397			if (uhp->uh_proc_flags == 0)
398				break;
399		}
400
401		offset = UCODE_HEADER_SIZE_INTEL + body_size;
402
403		/* Check to see if there is extended signature table */
404		if (total_size == offset)
405			continue;
406
407		/* There is extended signature table.  More processing. */
408		extp = (ucode_ext_table_intel_t *)(uintptr_t)&curbuf[offset];
409
410		for (i = 0; i < extp->uet_count; i++) {
411			ucode_ext_sig_intel_t *uesp = &extp->uet_ext_sig[i];
412			int j;
413
414			for (j = 0; j < 8; j++) {
415				uint32_t id = uesp->ues_proc_flags & (1 << j);
416
417				if (id == 0 && uesp->ues_proc_flags)
418					continue;
419
420				(void) snprintf(name, PATH_MAX,
421				    "%s/%08X-%02X", path, extp->uet_ext_sig[i],
422				    id);
423
424				if (ucode_should_update_intel(name, uhp->uh_rev)
425				    != 0) {
426
427					/* Remove the existing one first */
428					(void) unlink(name);
429					if (link(firstname, name) == -1) {
430						ucode_perror(name, EM_SYS);
431						return (EM_SYS);
432					}
433				}
434
435				if (uesp->ues_proc_flags == 0)
436					break;
437			}
438		}
439
440	}
441
442	/*
443	 * Remove files with no links to them.  These are probably
444	 * obsolete microcode files.
445	 */
446	if ((dirp = opendir(common_path)) == NULL) {
447		ucode_perror(common_path, EM_SYS);
448		return (EM_SYS);
449	}
450
451	while ((dp = readdir(dirp)) != NULL) {
452		char filename[PATH_MAX];
453		struct stat statbuf;
454
455		(void) snprintf(filename, PATH_MAX,
456		    "%s/%s", common_path, dp->d_name);
457		if (stat(filename, &statbuf) == -1)
458			continue;
459
460		if ((statbuf.st_mode & S_IFMT) == S_IFREG) {
461			if (statbuf.st_nlink == 1)
462				(void) unlink(filename);
463		}
464	}
465
466	(void) closedir(dirp);
467
468	return (EM_OK);
469}
470
471/*
472 * Returns 0 on success, 2 on usage error, and 3 on operation error.
473 */
474int
475main(int argc, char *argv[])
476{
477	int	c;
478	int	action = 0;
479	int	actcount = 0;
480	char	*path = NULL;
481	char	*filename = NULL;
482	int	errflg = 0;
483	int	dev_fd = -1;
484	int	fd = -1;
485	int	verbose = 0;
486	uint8_t	*buf = NULL;
487	ucode_errno_t	rc = EM_OK;
488	processorid_t	cpuid_max;
489	struct stat filestat;
490	uint32_t ucode_size;
491
492	(void) setlocale(LC_ALL, "");
493
494#if !defined(TEXT_DOMAIN)
495#define	TEXT_DOMAIN "SYS_TEST"
496#endif
497	(void) textdomain(TEXT_DOMAIN);
498
499	cmdname = basename(argv[0]);
500
501	while ((c = getopt(argc, argv, "idhuvVR:")) != EOF) {
502		switch (c) {
503
504		case 'i':
505			action |= UCODE_OPT_INSTALL;
506			actcount++;
507			break;
508
509		case 'u':
510			action |= UCODE_OPT_UPDATE;
511			actcount++;
512			break;
513
514		case 'v':
515			action |= UCODE_OPT_VERSION;
516			actcount++;
517			break;
518
519		case 'd':
520			ucode_debug = 1;
521			break;
522
523		case 'R':
524			if (optarg[0] == '-')
525				errflg++;
526			else if (strlen(optarg) > UCODE_MAX_PATH_LEN) {
527				(void) fprintf(stderr,
528				    gettext("Alternate path too long\n"));
529				errflg++;
530			} else if ((path = strdup(optarg)) == NULL) {
531				errflg++;
532			}
533
534			break;
535
536		case 'V':
537			verbose = 1;
538			break;
539
540		case 'h':
541			usage(1);
542			return (0);
543
544		default:
545			usage(verbose);
546			return (2);
547		}
548	}
549
550	if (actcount != 1) {
551		(void) fprintf(stderr, gettext("%s: options -v, -i and -u "
552		    "are mutually exclusive.\n"), cmdname);
553		usage(verbose);
554		return (2);
555	}
556
557	if (optind <= argc - 1)
558		filename = argv[optind];
559	else if (!(action & UCODE_OPT_VERSION))
560		errflg++;
561
562	if (errflg || action == 0) {
563		usage(verbose);
564		return (2);
565	}
566
567	/*
568	 * Convert from text format to binary format
569	 */
570	if ((action & UCODE_OPT_INSTALL) || (action & UCODE_OPT_UPDATE)) {
571		int i;
572		UCODE_VENDORS;
573
574		for (i = 0; ucode_vendors[i].filestr != NULL; i++) {
575			dprintf("i = %d, filestr = %s, filename = %s\n",
576			    i, ucode_vendors[i].filestr, filename);
577			if (strncasecmp(ucode_vendors[i].filestr,
578			    basename(filename),
579			    strlen(ucode_vendors[i].filestr)) == 0) {
580				ucode = &ucode_ops[i];
581				(void) strncpy(ucode_vendor_str,
582				    ucode_vendors[i].vendorstr,
583				    sizeof (ucode_vendor_str));
584				break;
585			}
586		}
587
588		if (ucode_vendors[i].filestr == NULL) {
589			rc = EM_NOVENDOR;
590			ucode_perror(basename(filename), rc);
591			goto err_out;
592		}
593
594		if ((stat(filename, &filestat)) < 0) {
595			rc = EM_SYS;
596			ucode_perror(filename, rc);
597			goto err_out;
598		}
599
600		if ((filestat.st_mode & S_IFMT) != S_IFREG &&
601		    (filestat.st_mode & S_IFMT) != S_IFLNK) {
602			rc = EM_FILEFORMAT;
603			ucode_perror(filename, rc);
604			goto err_out;
605		}
606
607		if ((buf = malloc(filestat.st_size)) == NULL) {
608			rc = EM_SYS;
609			ucode_perror(filename, rc);
610			goto err_out;
611		}
612
613		ucode_size = ucode->convert(filename, buf, filestat.st_size);
614
615		dprintf("ucode_size = %d\n", ucode_size);
616
617		if (ucode_size == 0) {
618			rc = EM_FILEFORMAT;
619			ucode_perror(filename, rc);
620			goto err_out;
621		}
622
623		if ((rc = ucode->validate(buf, ucode_size)) != EM_OK) {
624			ucode_perror(filename, rc);
625			goto err_out;
626		}
627	}
628
629	/*
630	 * For the install option, the microcode file must start with
631	 * "intel" for Intel microcode, and "amd" for AMD microcode.
632	 */
633	if (action & UCODE_OPT_INSTALL) {
634		/*
635		 * If no path is provided by the -R option, put the files in
636		 * /ucode_install_path/ucode_vendor_str/.
637		 */
638		if (path == NULL) {
639			if ((path = malloc(PATH_MAX)) == NULL) {
640				rc = EM_SYS;
641				ucode_perror("malloc", rc);
642				goto err_out;
643			}
644
645			(void) snprintf(path, PATH_MAX, "/%s/%s",
646			    ucode_install_path, ucode_vendor_str);
647		}
648
649		if (mkdirp(path, 0755) == -1 && errno != EEXIST) {
650			rc = EM_SYS;
651			ucode_perror(path, rc);
652			goto err_out;
653		}
654
655		rc = ucode->gen_files(buf, ucode_size, path);
656
657		goto err_out;
658	}
659
660	if ((dev_fd = open(ucode_dev, O_RDONLY)) == -1) {
661		rc = EM_SYS;
662		ucode_perror(ucode_dev, rc);
663		goto err_out;
664	}
665
666	if (action & UCODE_OPT_VERSION) {
667		int tmprc;
668		uint32_t *revp = NULL;
669		int i;
670#if defined(_SYSCALL32_IMPL)
671	struct ucode_get_rev_struct32 inf32;
672#else
673	struct ucode_get_rev_struct info;
674#endif
675
676		cpuid_max = (processorid_t)sysconf(_SC_CPUID_MAX);
677
678		if ((revp = (uint32_t *)
679		    malloc(cpuid_max * sizeof (uint32_t))) == NULL) {
680			rc = EM_SYS;
681			ucode_perror("malloc", rc);
682			goto err_out;
683		}
684
685		for (i = 0; i < cpuid_max; i++)
686			revp[i] = (uint32_t)-1;
687
688#if defined(_SYSCALL32_IMPL)
689		info32.ugv_rev = (caddr32_t)revp;
690		info32.ugv_size = cpuid_max;
691		info32.ugv_errno = EM_OK;
692		tmprc = ioctl(dev_fd, UCODE_GET_VERSION, &info32);
693		rc = info32.ugv_errno;
694#else
695		info.ugv_rev = revp;
696		info.ugv_size = cpuid_max;
697		info.ugv_errno = EM_OK;
698		tmprc = ioctl(dev_fd, UCODE_GET_VERSION, &info);
699		rc = info.ugv_errno;
700#endif
701
702		if (tmprc && rc == EM_OK) {
703			rc = EM_SYS;
704		}
705
706		if (rc == EM_OK) {
707			(void) printf(gettext("CPU\tMicrocode Version\n"));
708			for (i = 0; i < cpuid_max; i++) {
709				if (info.ugv_rev[i] == (uint32_t)-1)
710					continue;
711				(void) printf("%d\t0x%x\n", i, info.ugv_rev[i]);
712			}
713		} else {
714			ucode_perror(gettext("get microcode version"), rc);
715		}
716
717		if (revp)
718			free(revp);
719	}
720
721	if (action & UCODE_OPT_UPDATE) {
722		int tmprc;
723#if defined(_SYSCALL32_IMPL)
724	struct ucode_write_struct32 uw_struct32;
725#else
726	struct ucode_write_struct uw_struct;
727#endif
728
729#if defined(_SYSCALL32_IMPL)
730		uw_struct32.uw_size = ucode_size;
731		uw_struct32.uw_ucode = (caddr32_t)buf;
732		uw_struct32.uw_errno = EM_OK;
733		tmprc = ioctl(dev_fd, UCODE_UPDATE, &uw_struct32);
734		rc = uw_struct32.uw_errno;
735
736#else
737		uw_struct.uw_size = ucode_size;
738		uw_struct.uw_ucode = buf;
739		uw_struct.uw_errno = EM_OK;
740		tmprc = ioctl(dev_fd, UCODE_UPDATE, &uw_struct);
741		rc = uw_struct.uw_errno;
742#endif
743
744		if (rc == EM_OK) {
745			if (tmprc) {
746				rc = EM_SYS;
747				ucode_perror(ucode_dev, rc);
748			}
749		} else if (rc == EM_NOMATCH || rc == EM_HIGHERREV) {
750			ucode_perror(filename, rc);
751		} else {
752			ucode_perror(gettext("microcode update"), rc);
753		}
754	}
755
756err_out:
757	if (dev_fd != -1)
758		(void) close(dev_fd);
759
760	if (fd != -1)
761		(void) close(fd);
762
763	free(buf);
764	free(path);
765
766	if (rc != EM_OK)
767		return (3);
768
769	return (0);
770}
771