diff options
Diffstat (limited to 'usr/src/cmd/ucodeadm/ucodeadm.c')
-rw-r--r-- | usr/src/cmd/ucodeadm/ucodeadm.c | 662 |
1 files changed, 662 insertions, 0 deletions
diff --git a/usr/src/cmd/ucodeadm/ucodeadm.c b/usr/src/cmd/ucodeadm/ucodeadm.c new file mode 100644 index 0000000000..0100bd15d7 --- /dev/null +++ b/usr/src/cmd/ucodeadm/ucodeadm.c @@ -0,0 +1,662 @@ +/* + * CDDL HEADER START + * + * The contents of this file are subject to the terms of the + * Common Development and Distribution License (the "License"). + * You may not use this file except in compliance with the License. + * + * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE + * or http://www.opensolaris.org/os/licensing. + * See the License for the specific language governing permissions + * and limitations under the License. + * + * When distributing Covered Code, include this CDDL HEADER in each + * file and include the License file at usr/src/OPENSOLARIS.LICENSE. + * If applicable, add the following below this CDDL HEADER, with the + * fields enclosed by brackets "[]" replaced with your own identifying + * information: Portions Copyright [yyyy] [name of copyright owner] + * + * CDDL HEADER END + */ +/* + * Copyright 2007 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ + +#pragma ident "%Z%%M% %I% %E% SMI" + +#include <sys/types.h> +#include <sys/processor.h> +#include <sys/ucode.h> +#include <sys/ioctl.h> +#include <sys/stat.h> +#include <unistd.h> +#include <dirent.h> +#include <fcntl.h> +#include <errno.h> +#include <stdio.h> +#include <stdlib.h> +#include <stdarg.h> +#include <string.h> +#include <errno.h> +#include <syslog.h> +#include <time.h> +#include <ctype.h> +#include <assert.h> +#include <libgen.h> +#include <locale.h> +#include <libintl.h> + +#define UCODE_OPT_INSTALL 0x0001 +#define UCODE_OPT_UPDATE 0x0002 +#define UCODE_OPT_VERSION 0x0004 + +static const char ucode_dev[] = "/dev/" UCODE_DRIVER_NAME; + +static char *cmdname; + +static char ucode_vendor_str[UCODE_MAX_VENDORS_NAME_LEN]; +static char ucode_install_path[] = UCODE_INSTALL_PATH; + +static int ucode_debug = 0; + +static void +dprintf(const char *format, ...) +{ + if (ucode_debug) { + va_list alist; + va_start(alist, format); + (void) vfprintf(stderr, format, alist); + va_end(alist); + } +} + +static void +usage(int verbose) +{ + (void) fprintf(stderr, gettext("usage:\n")); + (void) fprintf(stderr, "\t%s -v\n", cmdname); + if (verbose) { + (void) fprintf(stderr, + gettext("\t\t Shows running microcode version.\n\n")); + } + + (void) fprintf(stderr, "\t%s -u microcode-text-file\n", cmdname); + if (verbose) { + (void) fprintf(stderr, gettext("\t\t Updates microcode to the " + "latest matching version found in\n" + "\t\t microcode-text-file.\n\n")); + } + + (void) fprintf(stderr, "\t%s -i [-R path] microcode-text-file\n", + cmdname); + if (verbose) { + (void) fprintf(stderr, gettext("\t\t Installs microcode to be " + "used for subsequent boots. Microcode\n" + "\t\t text file name must start with vendor name, " + "such as \"intel\".\n\n")); + } +} + +static void +ucode_perror(const char *str, ucode_errno_t rc) +{ + (void) fprintf(stderr, "%s: %s: %s\n", cmdname, str, + errno == 0 ? ucode_strerror(rc) : strerror(errno)); + errno = 0; +} + +#define LINESIZE 120 /* copyright line sometimes is longer than 80 */ + +/* + * Convert text format microcode release into binary format. + * Return the number of characters read. + */ +static int +ucode_convert(const char *infile, uint8_t *buf, size_t size) +{ + char linebuf[LINESIZE]; + FILE *infd = NULL; + int count = 0, firstline = 1; + uint32_t *intbuf = (uint32_t *)(intptr_t)buf; + + if (infile == NULL || buf == NULL || size == 0) + return (0); + + if ((infd = fopen(infile, "r")) == NULL) + return (0); + + while (fgets(linebuf, LINESIZE, infd)) { + + /* Check to see if we are processing a binary file */ + if (firstline && !isprint(linebuf[0])) { + if (fseek(infd, 0, SEEK_SET) == 0) + count = fread(buf, 1, size, infd); + + (void) fclose(infd); + return (count); + } + + firstline = 0; + + /* Skip blank lines */ + if (strlen(linebuf) == 1) + continue; + + /* Skip lines with all spaces or tabs */ + if (strcspn(linebuf, " \t") == 0) + continue; + + /* Text file. Skip comments. */ + if (linebuf[0] == '/') + continue; + + if (sscanf(linebuf, "%x, %x, %x, %x", + &intbuf[count], &intbuf[count+1], + &intbuf[count+2], &intbuf[count+3]) != 4) + break; + + count += 4; + } + + (void) fclose(infd); + + /* + * If we get here, we are processing a text format file + * where "count" is used to count the number of integers + * read. Convert it to number of characters read. + */ + return (count * sizeof (int)); +} + +/* + * Returns 0 if no need to update the link; -1 otherwise + */ +static int +ucode_should_update(char *filename, uint32_t new_rev) +{ + int fd; + struct stat statbuf; + ucode_header_t header; + + /* + * If the file or link already exists, check to see if + * it is necessary to update it. + */ + if (stat(filename, &statbuf) == 0) { + if ((fd = open(filename, O_RDONLY)) == -1) + return (-1); + + if (read(fd, &header, sizeof (header)) == -1) { + (void) close(fd); + return (-1); + } + + (void) close(fd); + + if (header.uh_rev >= new_rev) + return (0); + } + + return (-1); +} + +/* + * Generate microcode binary files. Must be called after ucode_validate(). + */ +static ucode_errno_t +ucode_gen_files(uint8_t *buf, int size, char *path) +{ + int remaining; + char common_path[PATH_MAX]; + DIR *dirp; + struct dirent *dp; + + (void) snprintf(common_path, PATH_MAX, "%s/%s", path, + UCODE_INSTALL_COMMON_PATH); + + if (mkdirp(common_path, 0755) == -1 && errno != EEXIST) { + ucode_perror(common_path, EM_SYS); + return (EM_SYS); + } + + for (remaining = size; remaining > 0; ) { + uint32_t total_size, body_size, offset; + char firstname[PATH_MAX]; + char name[PATH_MAX]; + int i; + uint8_t *curbuf = &buf[size - remaining]; + ucode_header_t *uhp = (ucode_header_t *)(intptr_t)curbuf; + ucode_ext_table_t *extp; + + total_size = UCODE_TOTAL_SIZE(uhp->uh_total_size); + body_size = UCODE_BODY_SIZE(uhp->uh_body_size); + + remaining -= total_size; + + (void) snprintf(firstname, PATH_MAX, "%s/%08X-%02X", + common_path, uhp->uh_signature, uhp->uh_proc_flags); + dprintf("firstname = %s\n", firstname); + + if (ucode_should_update(firstname, uhp->uh_rev) != 0) { + int fd; + + /* Remove the existing one first */ + (void) unlink(firstname); + + if ((fd = open(firstname, O_WRONLY | O_CREAT | O_TRUNC, + S_IRUSR | S_IRGRP | S_IROTH)) == -1) { + ucode_perror(firstname, EM_SYS); + return (EM_SYS); + } + + if (write(fd, curbuf, total_size) != total_size) { + (void) close(fd); + ucode_perror(firstname, EM_SYS); + return (EM_SYS); + } + + (void) close(fd); + } + + /* + * Only 1 byte of the proc_flags field is used, therefore + * we only need to match 8 potential platform ids. + */ + for (i = 0; i < 8; i++) { + uint32_t platid = uhp->uh_proc_flags & (1 << i); + + if (platid == 0 && uhp->uh_proc_flags != 0) + continue; + + (void) snprintf(name, PATH_MAX, + "%s/%08X-%02X", path, uhp->uh_signature, platid); + + dprintf("proc_flags = %x, platid = %x, name = %s\n", + uhp->uh_proc_flags, platid, name); + + if (ucode_should_update(name, uhp->uh_rev) != 0) { + + /* Remove the existing one first */ + (void) unlink(name); + + if (link(firstname, name) == -1) { + ucode_perror(name, EM_SYS); + return (EM_SYS); + } + } + + if (uhp->uh_proc_flags == 0) + break; + } + + offset = UCODE_HEADER_SIZE + body_size; + + /* Check to see if there is extended signature table */ + if (total_size == offset) + continue; + + /* There is extended signature table. More processing. */ + extp = (ucode_ext_table_t *)(uintptr_t)&curbuf[offset]; + + for (i = 0; i < extp->uet_count; i++) { + ucode_ext_sig_t *uesp = &extp->uet_ext_sig[i]; + int j; + + for (j = 0; j < 8; j++) { + uint32_t id = uesp->ues_proc_flags & (1 << j); + + if (id == 0 && uesp->ues_proc_flags) + continue; + + (void) snprintf(name, PATH_MAX, + "%s/%08X-%02X", path, extp->uet_ext_sig[i], + id); + + if (ucode_should_update(name, uhp->uh_rev) != + 0) { + + /* Remove the existing one first */ + (void) unlink(name); + if (link(firstname, name) == -1) { + ucode_perror(name, EM_SYS); + return (EM_SYS); + } + } + + if (uesp->ues_proc_flags == 0) + break; + } + } + + } + + /* + * Remove files with no links to them. These are probably + * obsolete microcode files. + */ + if ((dirp = opendir(common_path)) == NULL) { + ucode_perror(common_path, EM_SYS); + return (EM_SYS); + } + + while ((dp = readdir(dirp)) != NULL) { + char filename[PATH_MAX]; + struct stat statbuf; + + (void) snprintf(filename, PATH_MAX, + "%s/%s", common_path, dp->d_name); + if (stat(filename, &statbuf) == -1) + continue; + + if ((statbuf.st_mode & S_IFMT) == S_IFREG) { + if (statbuf.st_nlink == 1) + (void) unlink(filename); + } + } + + (void) closedir(dirp); + + return (EM_OK); +} + +/* + * Returns 0 on success, 2 on usage error, and 3 on operation error. + */ +int +main(int argc, char *argv[]) +{ + int c; + int action = 0; + int actcount = 0; + char *path = NULL; + char *filename = NULL; + int errflg = 0; + int dev_fd = -1; + int fd = -1; + int verbose = 0; + uint8_t *buf = NULL; + ucode_errno_t rc = EM_OK; + processorid_t cpuid_max; + struct stat filestat; + uint32_t ucode_size; + + (void) setlocale(LC_ALL, ""); + +#if !defined(TEXT_DOMAIN) +#define TEXT_DOMAIN "SYS_TEST" +#endif + (void) textdomain(TEXT_DOMAIN); + + cmdname = basename(argv[0]); + + while ((c = getopt(argc, argv, "idhuvVR:")) != EOF) { + switch (c) { + + case 'i': + action |= UCODE_OPT_INSTALL; + actcount++; + break; + + case 'u': + action |= UCODE_OPT_UPDATE; + actcount++; + break; + + case 'v': + action |= UCODE_OPT_VERSION; + actcount++; + break; + + case 'd': + ucode_debug = 1; + break; + + case 'R': + if (optarg[0] == '-') + errflg++; + else if (strlen(optarg) > UCODE_MAX_PATH_LEN) { + (void) fprintf(stderr, + gettext("Alternate path too long\n")); + errflg++; + } else if ((path = strdup(optarg)) == NULL) { + errflg++; + } + + break; + + case 'V': + verbose = 1; + break; + + case 'h': + usage(1); + return (0); + + default: + usage(verbose); + return (2); + } + } + + if (actcount != 1) { + (void) fprintf(stderr, gettext("%s: options -v, -i and -u " + "are mutually exclusive.\n"), cmdname); + usage(verbose); + return (2); + } + + if (optind <= argc - 1) + filename = argv[optind]; + else if (!(action & UCODE_OPT_VERSION)) + errflg++; + + if (errflg || action == 0) { + usage(verbose); + return (2); + } + + /* + * Convert from text format to binary format + */ + if ((action & UCODE_OPT_INSTALL) || (action & UCODE_OPT_UPDATE)) { + if ((stat(filename, &filestat)) < 0) { + rc = EM_SYS; + ucode_perror(filename, rc); + goto err_out; + } + + if ((filestat.st_mode & S_IFMT) != S_IFREG && + (filestat.st_mode & S_IFMT) != S_IFLNK) { + rc = EM_FILEFORMAT; + ucode_perror(filename, rc); + goto err_out; + } + + if ((buf = malloc(filestat.st_size)) == NULL) { + rc = EM_SYS; + ucode_perror(filename, rc); + goto err_out; + } + + ucode_size = ucode_convert(filename, buf, filestat.st_size); + + dprintf("ucode_size = %d\n", ucode_size); + + if (ucode_size == 0) { + rc = EM_FILEFORMAT; + ucode_perror(filename, rc); + goto err_out; + } + + if ((rc = ucode_validate(buf, ucode_size)) != EM_OK) { + ucode_perror(filename, rc); + goto err_out; + } + } + + /* + * For the install option, the microcode file must start with + * "intel" for Intel microcode, and "amd" for AMD microcode. + */ + if (action & UCODE_OPT_INSTALL) { + int i; + UCODE_VENDORS; + + for (i = 0; ucode_vendors[i].filestr != NULL; i++) { + dprintf("i = %d, filestr = %s, filename = %s\n", + i, ucode_vendors[i].filestr, filename); + if (strncasecmp(ucode_vendors[i].filestr, + basename(filename), + strlen(ucode_vendors[i].filestr)) == 0) { + + (void) strncpy(ucode_vendor_str, + ucode_vendors[i].vendorstr, + sizeof (ucode_vendor_str)); + break; + } + } + + if (ucode_vendors[i].filestr == NULL) { + rc = EM_NOVENDOR; + ucode_perror(basename(filename), rc); + goto err_out; + } + + /* + * If no path is provided by the -R option, put the files in + * /ucode_install_path/ucode_vendor_str/. + */ + if (path == NULL) { + if ((path = malloc(PATH_MAX)) == NULL) { + rc = EM_SYS; + ucode_perror("malloc", rc); + goto err_out; + } + + (void) snprintf(path, PATH_MAX, "/%s/%s", + ucode_install_path, ucode_vendor_str); + } + + if (mkdirp(path, 0755) == -1 && errno != EEXIST) { + rc = EM_SYS; + ucode_perror(path, rc); + goto err_out; + } + + rc = ucode_gen_files(buf, ucode_size, path); + + goto err_out; + } + + if ((dev_fd = open(ucode_dev, O_RDONLY)) == -1) { + rc = EM_SYS; + ucode_perror(ucode_dev, rc); + goto err_out; + } + + if (action & UCODE_OPT_VERSION) { + int tmprc; + uint32_t *revp = NULL; + int i; +#if defined(_SYSCALL32_IMPL) + struct ucode_get_rev_struct32 inf32; +#else + struct ucode_get_rev_struct info; +#endif + + cpuid_max = (processorid_t)sysconf(_SC_CPUID_MAX); + + if ((revp = (uint32_t *) + malloc(cpuid_max * sizeof (uint32_t))) == NULL) { + rc = EM_SYS; + ucode_perror("malloc", rc); + goto err_out; + } + + for (i = 0; i < cpuid_max; i++) + revp[i] = (uint32_t)-1; + +#if defined(_SYSCALL32_IMPL) + info32.ugv_rev = (caddr32_t)revp; + info32.ugv_size = cpuid_max; + info32.ugv_errno = EM_OK; + tmprc = ioctl(dev_fd, UCODE_GET_VERSION, &info32); + rc = info32.ugv_errno; +#else + info.ugv_rev = revp; + info.ugv_size = cpuid_max; + info.ugv_errno = EM_OK; + tmprc = ioctl(dev_fd, UCODE_GET_VERSION, &info); + rc = info.ugv_errno; +#endif + + if (tmprc && rc == EM_OK) { + rc = EM_SYS; + } + + if (rc == EM_OK) { + (void) printf(gettext("CPU\tMicrocode Version\n")); + for (i = 0; i < cpuid_max; i++) { + if (info.ugv_rev[i] == (uint32_t)-1) + continue; + (void) printf("%d\t0x%x\n", i, info.ugv_rev[i]); + } + } else { + ucode_perror(gettext("get microcode version"), rc); + } + + if (revp) + free(revp); + } + + if (action & UCODE_OPT_UPDATE) { + int tmprc; +#if defined(_SYSCALL32_IMPL) + struct ucode_write_struct32 uw_struct32; +#else + struct ucode_write_struct uw_struct; +#endif + +#if defined(_SYSCALL32_IMPL) + uw_struct32.uw_size = ucode_size; + uw_struct32.uw_ucode = (caddr32_t)buf; + uw_struct32.uw_errno = EM_OK; + tmprc = ioctl(dev_fd, UCODE_UPDATE, &uw_struct32); + rc = uw_struct32.uw_errno; + +#else + uw_struct.uw_size = ucode_size; + uw_struct.uw_ucode = buf; + uw_struct.uw_errno = EM_OK; + tmprc = ioctl(dev_fd, UCODE_UPDATE, &uw_struct); + rc = uw_struct.uw_errno; +#endif + + if (rc == EM_OK) { + if (tmprc) { + rc = EM_SYS; + ucode_perror(ucode_dev, rc); + } + } else if (rc == EM_NOMATCH || rc == EM_HIGHERREV) { + ucode_perror(filename, rc); + } else { + ucode_perror(gettext("microcode update"), rc); + } + } + +err_out: + if (dev_fd != -1) + (void) close(dev_fd); + + if (fd != -1) + (void) close(fd); + + free(buf); + free(path); + + if (rc != EM_OK) + return (3); + + return (0); +} |