summaryrefslogtreecommitdiff
path: root/usr/src/cmd/ucodeadm/ucodeadm.c
diff options
context:
space:
mode:
Diffstat (limited to 'usr/src/cmd/ucodeadm/ucodeadm.c')
-rw-r--r--usr/src/cmd/ucodeadm/ucodeadm.c662
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);
+}