Script 'mail_helper' called by obssrc Hello community, here is the log from the commit of package mlxbf-bootctl for openSUSE:Factory checked in at 2022-09-19 16:04:18 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/mlxbf-bootctl (Old) and /work/SRC/openSUSE:Factory/.mlxbf-bootctl.new.2083 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "mlxbf-bootctl" Mon Sep 19 16:04:18 2022 rev:2 rq:1004721 version:1.1.6.14 Changes: -------- --- /work/SRC/openSUSE:Factory/mlxbf-bootctl/mlxbf-bootctl.changes 2021-01-27 18:58:06.844422175 +0100 +++ /work/SRC/openSUSE:Factory/.mlxbf-bootctl.new.2083/mlxbf-bootctl.changes 2022-09-19 16:04:27.238329431 +0200 @@ -1,0 +2,11 @@ +Thu Sep 15 11:18:42 UTC 2022 - Matthias Brugger <mbrug...@suse.com> + +- update to 1.1.6.14 + * check image version to not install unsupported images + +------------------------------------------------------------------- +Thu Jan 28 09:34:07 UTC 2021 - Matthias Brugger <mbrug...@suse.com> + +- Fix spec file comments + +------------------------------------------------------------------- Old: ---- mlxbf-bootctl-1.1.6.11.obscpio mlxbf-bootctl-1.1.6.11.tar New: ---- mlxbf-bootctl-1.1.6.14.obscpio mlxbf-bootctl-1.1.6.14.tar ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ mlxbf-bootctl.spec ++++++ --- /var/tmp/diff_new_pack.3ZbqUr/_old 2022-09-19 16:04:27.978331408 +0200 +++ /var/tmp/diff_new_pack.3ZbqUr/_new 2022-09-19 16:04:27.986331429 +0200 @@ -1,8 +1,7 @@ # -# spec file for package rshim +# spec file for package mlxbf-bootctl # -# Copyright (c) 2020 SUSE LLC -# Copyright (c) 2019 Mellanox Technologies. All Rights Reserved. +# Copyright (c) 2022 SUSE LLC # # All modifications and additions to the file contributed by third parties # remain the property of their copyright owners, unless otherwise agreed @@ -18,7 +17,7 @@ Name: mlxbf-bootctl -Version: 1.1.6.11 +Version: 1.1.6.14 Release: 0 Summary: User-space driver for Mellanox BlueField SoC License: BSD-2-Clause ++++++ mlxbf-bootctl-1.1.6.11.obscpio -> mlxbf-bootctl-1.1.6.14.obscpio ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/mlxbf-bootctl-1.1.6.11/.gitignore new/mlxbf-bootctl-1.1.6.14/.gitignore --- old/mlxbf-bootctl-1.1.6.11/.gitignore 1970-01-01 01:00:00.000000000 +0100 +++ new/mlxbf-bootctl-1.1.6.14/.gitignore 2022-06-14 20:41:49.000000000 +0200 @@ -0,0 +1,6 @@ +RPMBUILD +git_dir_pack +*.src.rpm +mlxbf-bootctl +mlxbf-bootctl.d +mlxbf-bootctl.o diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/mlxbf-bootctl-1.1.6.11/mlxbf-bootctl.8 new/mlxbf-bootctl-1.1.6.14/mlxbf-bootctl.8 --- old/mlxbf-bootctl-1.1.6.11/mlxbf-bootctl.8 2020-10-04 22:04:14.000000000 +0200 +++ new/mlxbf-bootctl-1.1.6.14/mlxbf-bootctl.8 2022-06-14 20:41:49.000000000 +0200 @@ -86,6 +86,18 @@ \-\-swap|\-s Set the boot software so that will swap the primary and alternate partitions at the next reset. +.TP +.B +\-\-version|\-v +Override automatic image version filtering. By default, when +.B \-v +is not specifed, image versions contained in the bootstream will not be +installed to the boot partition if they are incompatible with the current +BlueField platform. +.B \-v +allows you to manually specify the target version. Version 0 corresponds to BF-1, +version 1 to BF-2, and so on. Version -1, or any version less than zero, +will turn off image filtering entirely. .SH EXAMPLES To update to new firmware as safely as possible: .IP diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/mlxbf-bootctl-1.1.6.11/mlxbf-bootctl.c new/mlxbf-bootctl-1.1.6.14/mlxbf-bootctl.c --- old/mlxbf-bootctl-1.1.6.11/mlxbf-bootctl.c 2020-10-04 22:04:14.000000000 +0200 +++ new/mlxbf-bootctl-1.1.6.14/mlxbf-bootctl.c 2022-06-14 20:41:49.000000000 +0200 @@ -50,6 +50,15 @@ #define MAX_SEG_LEN ((1 << 20) - SEGMENT_HEADER_LEN) #define SEGMENT_IS_END (1UL << 63) +/* File size limits */ +#define INPUT_BFB_MAX_SIZE (20 * 1024 * 1024) +#define MMC_BOOT_PARTITION_MAX_SIZE (4 * 1024 * 1024) + +/* DMI constants */ +#define DMI_TABLE_PATH "/sys/firmware/dmi/tables/DMI" +#define DMI_PROCESSOR_INFO_TYPE 4 +#define DMI_PROCESSOR_VERSION_STR_MAX_SIZE 100 + void die(const char* fmt, ...) { va_list ap; @@ -60,6 +69,29 @@ exit(1); } +/* + * BFB image header. + * + * This definition is extracted from file + * atf/plat/mellanox/common/include/drivers/io/bluefield_boot.h + */ +#define BFB_IMGHDR_MAGIC 0x13026642 /* "Bf^B^S" */ +typedef struct { + unsigned long magic:32; + unsigned long major:4; + unsigned long minor:4; + unsigned long reserved:4; + unsigned long next_img_ver:4; + unsigned long cur_img_ver:4; + unsigned long hdr_len:4; + unsigned long image_id:8; + unsigned long image_len:32; + unsigned long image_crc:32; + unsigned long following_images:64; +} boot_image_header_t; + +ssize_t read_or_die(const char* filename, int fd, void* buf, size_t count); + #ifndef OUTPUT_ONLY #include <linux/mmc/ioctl.h> @@ -112,29 +144,6 @@ #define LIFECYCLE_STATE_PATH "lifecycle_state" #define SECURE_BOOT_FUSE_STATE_PATH "secure_boot_fuse_state" -#define MMC_BOOT_PARTITION_MAX_SIZE (4 * 1024 * 1024) - -/* - * BFB image header. - * - * This definition is extracted from file - * atf/plat/mellanox/common/include/drivers/io/bluefield_boot.h - */ -#define BFB_IMGHDR_MAGIC 0x13026642 /* "Bf^B^S" */ -typedef struct { - unsigned long magic:32; - unsigned long major:4; - unsigned long minor:4; - unsigned long reserved:4; - unsigned long next_img_ver:4; - unsigned long cur_img_ver:4; - unsigned long hdr_len:4; - unsigned long image_id:8; - unsigned long image_len:32; - unsigned long image_crc:32; - unsigned long following_images:64; -} boot_image_header_t; - /* Program variables */ const char *mmc_path = "/dev/mmcblk0"; @@ -372,6 +381,142 @@ printf("secure boot key free slots: %d\n", get_free_sbfuse_slots()); } +// Get the version of hardware we're currently running on. +// If it can't be found, this will return -1. +int get_hw_version(void) { + const char *dmi_file = DMI_TABLE_PATH; + + int dmi_fd = open(dmi_file, O_RDONLY); + if (dmi_fd < 0) { + fprintf(stderr, "warn: %s: %m, disable version filtering\n", dmi_file); + return -1; + } + + struct stat st; + if (fstat(dmi_fd, &st) < 0) + die("%s: stat: %m", dmi_file); + + void *dmi_buf = malloc(st.st_size); + if (dmi_buf == NULL) + die("out of memory"); + + int n_bytes = 0; + n_bytes = read_or_die(dmi_file, dmi_fd, dmi_buf, st.st_size); + if (n_bytes != st.st_size) + die("%s: could not read DMI table", dmi_file); + + if (close(dmi_fd) < 0) + die("%s: close: %m", dmi_file); + + // Now, skip along table until we reach the processor info + int bytes_left = st.st_size; + void *idx = dmi_buf; + uint8_t table_size = 0; + + while (bytes_left > 0) { + uint8_t table_type = *((uint8_t*)idx); + table_size = *((uint8_t*)idx + 1); + + // Break if we hit the processor table. + if (table_type == DMI_PROCESSOR_INFO_TYPE) + break; + + // Otherwise, skip to next table. + idx += table_size; + bytes_left -= table_size; + + bool first_str = true; + + // We might need to skip over strings, too. + while (bytes_left > 0) { + // If idx is over two 0 bytes, we're at the end, so + // advance to the next table. + if (*(uint16_t*)idx == 0) { + idx += sizeof(uint16_t); + bytes_left -= sizeof(uint16_t); + break; + } else { + // Skip a string. Note the first iteration will + // place the index at the beginning of the string, + // whereas all further iterations place the index at + // the NULL terminator. + if (!first_str) { + idx++; + bytes_left--; + } + first_str = false; + while (*(char*)idx && bytes_left > 0) { + idx++; + bytes_left--; + } + } + } + } + + if (bytes_left <= 0) { + fprintf(stderr, "warning: could not find BlueField SoC revision\n"); + free(dmi_buf); + return -1; + } + + // Now idx is at processor version table - we can read the + // string now. SMBIOS tables append all strings to the end + // of the structure, and refer to them by their order. + // First string is 1, second is 2, etc. + uint8_t soc_ver_snum = *(uint8_t*)(idx + 0x10); + if (soc_ver_snum == 0) { + fprintf(stderr, "warning: found DMI processor ver field, but it's NULL\n"); + free(dmi_buf); + return -1; + } + + idx += table_size; + bytes_left -= table_size; + + // Skip strings until we hit the desired one. + char version_string[DMI_PROCESSOR_VERSION_STR_MAX_SIZE] = {0}; + bool first_str = true; + while (soc_ver_snum-- > 1 && bytes_left > 0) { + if (*(uint16_t*)idx == 0) { + fprintf(stderr, "warning: DMI processor ver specifies string that does not exist.\n"); + free(dmi_buf); + return -1; + } else { + if (!first_str) { + idx++; + bytes_left--; + } + first_str = false; + // Skip a string. + while (*(char*)idx && bytes_left > 0) { + idx++; + bytes_left--; + } + } + } + + idx++; + bytes_left--; + + // Finally, actually copy the string. + strncpy( + version_string, + idx, + bytes_left < DMI_PROCESSOR_VERSION_STR_MAX_SIZE - 1 ? bytes_left : DMI_PROCESSOR_VERSION_STR_MAX_SIZE - 1 + ); + + // Extract version + int version = -1; + if (1 != sscanf(version_string, "Mellanox BlueField-%d", &version)) + fprintf(stderr, "warning: Unknown SoC revision"); + + // BlueField 1 is v0, BF2 is v1, etc. + version--; + + free(dmi_buf); + return version; +} + #endif // OUTPUT_ONLY // Read as much as possible despite EINTR or partial reads, and die on error. @@ -496,10 +641,198 @@ free(buf); } -void write_bootstream(const char *bootstream, const char *bootfile, int flags) +// Read a header from a boot stream. This will consume the +// entire header, verify it, and return the header +// structure. +size_t read_bootstream_header(const char *bootstream, int ifd, boot_image_header_t *hdr) { + int n; + uint64_t data; + + // Read and verify the image header. + n = read_or_die(bootstream, ifd, hdr, sizeof(*hdr)); + if (n != sizeof(*hdr)) + die("Unable to read next header, n=%d", n); + n = hdr->hdr_len - sizeof(*hdr) / sizeof(uint64_t); + if (n < 0) + die("Invalid header length %d, n=%d", hdr->hdr_len, n); + // Drain the rest of header. + while (n--) { + if (read_or_die(bootstream, ifd, &data, sizeof(data)) != sizeof(data)) + die("Not enough data for header"); + } + + return hdr->hdr_len * sizeof(uint64_t); +} + +// Correct the following_images and next_img_ver fields in a BFB buffer. +// Note this assumes a well-formed BFB stream in the buffer. +void correct_bootstream_headers(void *buf, int num_images) { + uint64_t fi_map = 0; + void *idx = buf; + boot_image_header_t *hdr; + int img_size = 0; + int pad_size = 0; + + if (num_images <= 0) + die("%s: num_images must be at least 1", __FUNCTION__); + + // Markers to find images next time + void **images = malloc(num_images * sizeof(void*)); + if (images == NULL) + die("out of memory"); + + // Iterate once to build fi_map and mark image starts + for (int i = 0; i < num_images; i++) { + hdr = (boot_image_header_t*) idx; + + // Build image maps + fi_map |= 1UL << hdr->image_id; + images[i] = idx; + + // Skip to next image + img_size = hdr->image_len; + pad_size = (img_size % 8) ? (8 - img_size % 8) : 0; + idx += img_size + pad_size + hdr->hdr_len * sizeof(uint64_t); + } + + boot_image_header_t *prev_hdr; + + // Now we can iterate over the images directly and fixup + // the fields that need it + for (int i = 0; i < num_images; i++) { + hdr = (boot_image_header_t*) images[i]; + + // Update image bitmap. Note that on the last + // image of a series of versions, this will be wrong, + // and will be corrected next iteration. + hdr->following_images = fi_map; + + if (i > 0) { + prev_hdr = (boot_image_header_t*) images[i-1]; + + if (prev_hdr->image_id == hdr->image_id) { + // Update next_img_ver as long as we haven't gone to + // a new image ID. + prev_hdr->next_img_ver = hdr->cur_img_ver; + } else { + // If next image ID is different, then update + // fi_map, and correct the previous image. + fi_map &= ~(1UL << prev_hdr->image_id); + prev_hdr->following_images = fi_map; + + // We also set prev_hdr's next_img_ver to 0 since + // there are no more images of that version. + prev_hdr->next_img_ver = 0; + } + } + } + + // Edge case: Handle last image. + hdr = (boot_image_header_t*)images[num_images-1]; + hdr->following_images = 0; + hdr->next_img_ver = 0; + + free(images); +} + +// Tell whether we should write an image to the boot +// partition, based on a version number. +// We do this conservatively, so we don't accidentally +// filter out something common. +bool should_install_image(boot_image_header_t *hdr, int version) { + // version < 0 indicates filtering should be off. All + // images will be installed in this case. + if (version < 0) + return true; + + // For now, do not install anything that was introduced + // after the given version. + return hdr->cur_img_ver <= version; +} + +// Read a bootstream to an internal buffer, optionally filtering out images +// of a given version. Supply -1 to version to turn this behavior off. +size_t read_bootstream_to_buffer(const char *bootstream, void *buf, int buf_size, int version) +{ + int ifd = open(bootstream, O_RDONLY); + if (ifd < 0) + die("%s: %m", bootstream); + + struct stat st; + if (fstat(ifd, &st) < 0) + die("%s: stat: %m", bootstream); + + int bytes_left = st.st_size; + + // If no version filtering requested and file is too large for the + // buffer, error out. + if (version < 0 && bytes_left > buf_size) { + die("%s: Boot stream file is too large", bootstream); + } + + void *idx = buf; // Index along buffer + size_t n_bytes = 0; + boot_image_header_t hdr; + size_t hdr_size; // Size of the image header, including reserved words + size_t pad_size; + size_t img_size; + + int num_images = 0; + + // Otherwise, we'll need to read the whole file and filter it to tell + // whether stream will fit. + while (bytes_left > 0) { + hdr_size = read_bootstream_header(bootstream, ifd, &hdr); + bytes_left -= hdr_size; + img_size = hdr.image_len; + + pad_size = (img_size % 8) ? (8 - img_size % 8) : 0; + + // Check whether the version should be included. + if (should_install_image(&hdr, version)) { + // If so, copy the header and img into the buffer. + if ((hdr_size + idx) - buf > buf_size) + die("Boot stream file is too large"); + + memcpy(idx, &hdr, sizeof(hdr)); + idx += hdr_size; + + // Copy image into buffer. + if ((hdr.image_len + idx) - buf > buf_size) + die("Boot stream file is too large"); + + n_bytes = read_or_die(bootstream, ifd, idx, img_size + pad_size); + if (n_bytes != img_size + pad_size) + die("Unable to read next image, n=%d", n_bytes); + + idx += n_bytes; + bytes_left -= n_bytes; + + // Finally, count the image. We reuse this later for + // fixing up the headers. + num_images++; + } else { + // Otherwise, skip over the entire image. + n_bytes = lseek(ifd, img_size + pad_size, SEEK_CUR); + if (n_bytes < 0) + die("%s: Could not skip filtered image: %m", bootstream); + bytes_left -= img_size + pad_size; + } + } + + if (close(ifd) < 0) + die("%s: close: %m", bootstream); + + correct_bootstream_headers(buf, num_images); + + return idx - buf; +} + +void write_bootstream(const char *bootstream, const char *bootfile, int flags, int version) { int sysfd = -1; char *sysname; + size_t ibuf_maxsize = INPUT_BFB_MAX_SIZE; // Reset the force_ro setting if need be if (strncmp(bootfile, "/dev/", 5) == 0) @@ -536,23 +869,21 @@ free(sysname); sysname = NULL; } + + // If writing to /dev/... assume it's an MMC partition + ibuf_maxsize = MMC_BOOT_PARTITION_MAX_SIZE; } // Copy the bootstream to the bootfile device - int ifd = open(bootstream, O_RDONLY); - if (ifd < 0) - die("%s: %m", bootstream); + void *ibuf = malloc(ibuf_maxsize); + void *inidx = ibuf; + uint8_t padbuf[8] = {0}; // There are at most 8 pad bytes. + if (ibuf == NULL) + die("out of memory"); + size_t bytes_left = read_bootstream_to_buffer(bootstream, ibuf, ibuf_maxsize, version); int ofd = open(bootfile, O_WRONLY | flags, 0666); if (ofd < 0) die("%s: %m", bootfile); - struct stat st; - if (fstat(ifd, &st) < 0) - die("%s: stat: %m", bootstream); - size_t bytes_left = st.st_size; - - char *buf = malloc(MAX_SEG_LEN); - if (buf == NULL) - die("out of memory"); // Write the bootstream header word first. This has the byte to // be displayed in the rev_id register as the low 8 bits (zero for now). @@ -571,16 +902,14 @@ write_or_die(bootfile, ofd, &segheader, sizeof(segheader)); // Copy the segment plus any padding. - read_or_die(bootstream, ifd, buf, seg_size); - memset(buf + seg_size, 0, pad_size); - write_or_die(bootfile, ofd, buf, seg_size + pad_size); + write_or_die(bootfile, ofd, inidx, seg_size); + inidx += seg_size; + write_or_die(bootfile, ofd, padbuf, pad_size); } - if (close(ifd) < 0) - die("%s: close: %m", bootstream); if (close(ofd) < 0) die("%s: close: %m", bootfile); - free(buf); + free(ibuf); // Put back the force_ro setting if need be if (sysfd >= 0) @@ -635,7 +964,7 @@ if (bootstream == NULL || output_file == NULL) die("mlx-bootctl: Must specify --output and --bootstream"); - write_bootstream(bootstream, output_file, O_CREAT | O_TRUNC); + write_bootstream(bootstream, output_file, O_CREAT | O_TRUNC, -1); return 0; } @@ -656,7 +985,7 @@ static void verify_bootstream(const char *bootfile) { - int ifd, n, bytes_left, img_size; + int ifd, bytes_left, img_size; boot_image_header_t hdr; struct stat st; uint64_t data; @@ -671,23 +1000,13 @@ bytes_left = st.st_size; if (bytes_left % sizeof(uint64_t)) die("Invalid size %d", bytes_left); - if (bytes_left > MMC_BOOT_PARTITION_MAX_SIZE) - die("Boot file size too big"); + if (bytes_left > INPUT_BFB_MAX_SIZE) + die("Input boot file size too big"); while (bytes_left > 0) { - // Read and verify the image header. - n = read_or_die(bootfile, ifd, &hdr, sizeof(hdr)); - if (n != sizeof(hdr)) - die("Unable to read next header, n=%d", n); - n = hdr.hdr_len - sizeof(hdr) / sizeof(uint64_t); - bytes_left -= hdr.hdr_len * sizeof(uint64_t); - if (n < 0 || bytes_left < 0) + bytes_left -= read_bootstream_header(bootfile, ifd, &hdr); + if (bytes_left < 0) die("Invalid header length"); - // Drain the rest of header. - while (n--) { - if (read_or_die(bootfile, ifd, &data, sizeof(data)) != sizeof(data)) - die("Not enough data for header"); - } // Verify the image crc. crc = ~0; @@ -717,15 +1036,17 @@ { "device", required_argument, NULL, 'd' }, { "output", required_argument, NULL, 'o' }, { "read", required_argument, NULL, 'r' }, + { "version", required_argument, NULL, 'v' }, { "help", no_argument, NULL, 'h' }, { NULL, 0, NULL, 0 } }; - static const char short_options[] = "sb:d:o:r:he"; + static const char short_options[] = "sb:d:o:r:hev:"; static const char help_text[] = "syntax: mlxbf-bootctl [--help|-h] [--swap|-s] [--device|-d MMCFILE]\n" " [--output|-o OUTPUT] [--read|-r INPUT]\n" " [--bootstream|-b BFBFILE] [--overwrite-current]\n" - " [--watchdog-swap interval | --nowatchdog-swap]"; + " [--watchdog-swap interval | --nowatchdog-swap]\n" + " [--version|-v VERSION]"; const char *watchdog_swap = NULL; const char *bootstream = NULL; @@ -733,6 +1054,8 @@ const char *input_file = NULL; bool watchdog_disable = false; bool swap = false; + bool auto_version = true; + int version_arg = -1; int which_boot = 1; // alternate boot partition by default int opt; @@ -779,6 +1102,14 @@ enable_rst_n(); break; + case 'v': + auto_version = false; + char *end; + version_arg = strtol(optarg, &end, 0); + if (end == optarg || *end != '\0') + die("version argument ('%s') must be an integer", optarg); + break; + case 'h': default: die(help_text); @@ -802,7 +1133,8 @@ else if (output_file) { // Write the bootstream to the given file, creating it if needed - write_bootstream(bootstream, output_file, O_CREAT | O_TRUNC); + // Don't filter anything here, since we're not writing to EMMC. + write_bootstream(bootstream, output_file, O_CREAT | O_TRUNC, -1); } else { @@ -827,7 +1159,13 @@ if (asprintf(&bootfile, "%sboot%d", mmc_path, boot_part ^ which_boot) <= 0) die("unexpected failure in asprintf (%s/%d)", __FILE__, __LINE__); - write_bootstream(bootstream, bootfile, O_SYNC); + int version; + if (auto_version) { + version = get_hw_version(); + } else { + version = version_arg; + } + write_bootstream(bootstream, bootfile, O_SYNC, version); // The eMMC driver works in an asynchronous way, thus any commands sent // should occur after the write to the bootstream has retired. Otherwise // a blk_update_request I/O error would be raised and the success of the ++++++ mlxbf-bootctl-1.1.6.11.tar -> mlxbf-bootctl-1.1.6.14.tar ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/mlxbf-bootctl-1.1.6.11/.gitignore new/mlxbf-bootctl-1.1.6.14/.gitignore --- old/mlxbf-bootctl-1.1.6.11/.gitignore 1970-01-01 01:00:00.000000000 +0100 +++ new/mlxbf-bootctl-1.1.6.14/.gitignore 2022-06-14 20:41:49.000000000 +0200 @@ -0,0 +1,6 @@ +RPMBUILD +git_dir_pack +*.src.rpm +mlxbf-bootctl +mlxbf-bootctl.d +mlxbf-bootctl.o diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/mlxbf-bootctl-1.1.6.11/mlxbf-bootctl.8 new/mlxbf-bootctl-1.1.6.14/mlxbf-bootctl.8 --- old/mlxbf-bootctl-1.1.6.11/mlxbf-bootctl.8 2020-10-04 22:04:14.000000000 +0200 +++ new/mlxbf-bootctl-1.1.6.14/mlxbf-bootctl.8 2022-06-14 20:41:49.000000000 +0200 @@ -86,6 +86,18 @@ \-\-swap|\-s Set the boot software so that will swap the primary and alternate partitions at the next reset. +.TP +.B +\-\-version|\-v +Override automatic image version filtering. By default, when +.B \-v +is not specifed, image versions contained in the bootstream will not be +installed to the boot partition if they are incompatible with the current +BlueField platform. +.B \-v +allows you to manually specify the target version. Version 0 corresponds to BF-1, +version 1 to BF-2, and so on. Version -1, or any version less than zero, +will turn off image filtering entirely. .SH EXAMPLES To update to new firmware as safely as possible: .IP diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/mlxbf-bootctl-1.1.6.11/mlxbf-bootctl.c new/mlxbf-bootctl-1.1.6.14/mlxbf-bootctl.c --- old/mlxbf-bootctl-1.1.6.11/mlxbf-bootctl.c 2020-10-04 22:04:14.000000000 +0200 +++ new/mlxbf-bootctl-1.1.6.14/mlxbf-bootctl.c 2022-06-14 20:41:49.000000000 +0200 @@ -50,6 +50,15 @@ #define MAX_SEG_LEN ((1 << 20) - SEGMENT_HEADER_LEN) #define SEGMENT_IS_END (1UL << 63) +/* File size limits */ +#define INPUT_BFB_MAX_SIZE (20 * 1024 * 1024) +#define MMC_BOOT_PARTITION_MAX_SIZE (4 * 1024 * 1024) + +/* DMI constants */ +#define DMI_TABLE_PATH "/sys/firmware/dmi/tables/DMI" +#define DMI_PROCESSOR_INFO_TYPE 4 +#define DMI_PROCESSOR_VERSION_STR_MAX_SIZE 100 + void die(const char* fmt, ...) { va_list ap; @@ -60,6 +69,29 @@ exit(1); } +/* + * BFB image header. + * + * This definition is extracted from file + * atf/plat/mellanox/common/include/drivers/io/bluefield_boot.h + */ +#define BFB_IMGHDR_MAGIC 0x13026642 /* "Bf^B^S" */ +typedef struct { + unsigned long magic:32; + unsigned long major:4; + unsigned long minor:4; + unsigned long reserved:4; + unsigned long next_img_ver:4; + unsigned long cur_img_ver:4; + unsigned long hdr_len:4; + unsigned long image_id:8; + unsigned long image_len:32; + unsigned long image_crc:32; + unsigned long following_images:64; +} boot_image_header_t; + +ssize_t read_or_die(const char* filename, int fd, void* buf, size_t count); + #ifndef OUTPUT_ONLY #include <linux/mmc/ioctl.h> @@ -112,29 +144,6 @@ #define LIFECYCLE_STATE_PATH "lifecycle_state" #define SECURE_BOOT_FUSE_STATE_PATH "secure_boot_fuse_state" -#define MMC_BOOT_PARTITION_MAX_SIZE (4 * 1024 * 1024) - -/* - * BFB image header. - * - * This definition is extracted from file - * atf/plat/mellanox/common/include/drivers/io/bluefield_boot.h - */ -#define BFB_IMGHDR_MAGIC 0x13026642 /* "Bf^B^S" */ -typedef struct { - unsigned long magic:32; - unsigned long major:4; - unsigned long minor:4; - unsigned long reserved:4; - unsigned long next_img_ver:4; - unsigned long cur_img_ver:4; - unsigned long hdr_len:4; - unsigned long image_id:8; - unsigned long image_len:32; - unsigned long image_crc:32; - unsigned long following_images:64; -} boot_image_header_t; - /* Program variables */ const char *mmc_path = "/dev/mmcblk0"; @@ -372,6 +381,142 @@ printf("secure boot key free slots: %d\n", get_free_sbfuse_slots()); } +// Get the version of hardware we're currently running on. +// If it can't be found, this will return -1. +int get_hw_version(void) { + const char *dmi_file = DMI_TABLE_PATH; + + int dmi_fd = open(dmi_file, O_RDONLY); + if (dmi_fd < 0) { + fprintf(stderr, "warn: %s: %m, disable version filtering\n", dmi_file); + return -1; + } + + struct stat st; + if (fstat(dmi_fd, &st) < 0) + die("%s: stat: %m", dmi_file); + + void *dmi_buf = malloc(st.st_size); + if (dmi_buf == NULL) + die("out of memory"); + + int n_bytes = 0; + n_bytes = read_or_die(dmi_file, dmi_fd, dmi_buf, st.st_size); + if (n_bytes != st.st_size) + die("%s: could not read DMI table", dmi_file); + + if (close(dmi_fd) < 0) + die("%s: close: %m", dmi_file); + + // Now, skip along table until we reach the processor info + int bytes_left = st.st_size; + void *idx = dmi_buf; + uint8_t table_size = 0; + + while (bytes_left > 0) { + uint8_t table_type = *((uint8_t*)idx); + table_size = *((uint8_t*)idx + 1); + + // Break if we hit the processor table. + if (table_type == DMI_PROCESSOR_INFO_TYPE) + break; + + // Otherwise, skip to next table. + idx += table_size; + bytes_left -= table_size; + + bool first_str = true; + + // We might need to skip over strings, too. + while (bytes_left > 0) { + // If idx is over two 0 bytes, we're at the end, so + // advance to the next table. + if (*(uint16_t*)idx == 0) { + idx += sizeof(uint16_t); + bytes_left -= sizeof(uint16_t); + break; + } else { + // Skip a string. Note the first iteration will + // place the index at the beginning of the string, + // whereas all further iterations place the index at + // the NULL terminator. + if (!first_str) { + idx++; + bytes_left--; + } + first_str = false; + while (*(char*)idx && bytes_left > 0) { + idx++; + bytes_left--; + } + } + } + } + + if (bytes_left <= 0) { + fprintf(stderr, "warning: could not find BlueField SoC revision\n"); + free(dmi_buf); + return -1; + } + + // Now idx is at processor version table - we can read the + // string now. SMBIOS tables append all strings to the end + // of the structure, and refer to them by their order. + // First string is 1, second is 2, etc. + uint8_t soc_ver_snum = *(uint8_t*)(idx + 0x10); + if (soc_ver_snum == 0) { + fprintf(stderr, "warning: found DMI processor ver field, but it's NULL\n"); + free(dmi_buf); + return -1; + } + + idx += table_size; + bytes_left -= table_size; + + // Skip strings until we hit the desired one. + char version_string[DMI_PROCESSOR_VERSION_STR_MAX_SIZE] = {0}; + bool first_str = true; + while (soc_ver_snum-- > 1 && bytes_left > 0) { + if (*(uint16_t*)idx == 0) { + fprintf(stderr, "warning: DMI processor ver specifies string that does not exist.\n"); + free(dmi_buf); + return -1; + } else { + if (!first_str) { + idx++; + bytes_left--; + } + first_str = false; + // Skip a string. + while (*(char*)idx && bytes_left > 0) { + idx++; + bytes_left--; + } + } + } + + idx++; + bytes_left--; + + // Finally, actually copy the string. + strncpy( + version_string, + idx, + bytes_left < DMI_PROCESSOR_VERSION_STR_MAX_SIZE - 1 ? bytes_left : DMI_PROCESSOR_VERSION_STR_MAX_SIZE - 1 + ); + + // Extract version + int version = -1; + if (1 != sscanf(version_string, "Mellanox BlueField-%d", &version)) + fprintf(stderr, "warning: Unknown SoC revision"); + + // BlueField 1 is v0, BF2 is v1, etc. + version--; + + free(dmi_buf); + return version; +} + #endif // OUTPUT_ONLY // Read as much as possible despite EINTR or partial reads, and die on error. @@ -496,10 +641,198 @@ free(buf); } -void write_bootstream(const char *bootstream, const char *bootfile, int flags) +// Read a header from a boot stream. This will consume the +// entire header, verify it, and return the header +// structure. +size_t read_bootstream_header(const char *bootstream, int ifd, boot_image_header_t *hdr) { + int n; + uint64_t data; + + // Read and verify the image header. + n = read_or_die(bootstream, ifd, hdr, sizeof(*hdr)); + if (n != sizeof(*hdr)) + die("Unable to read next header, n=%d", n); + n = hdr->hdr_len - sizeof(*hdr) / sizeof(uint64_t); + if (n < 0) + die("Invalid header length %d, n=%d", hdr->hdr_len, n); + // Drain the rest of header. + while (n--) { + if (read_or_die(bootstream, ifd, &data, sizeof(data)) != sizeof(data)) + die("Not enough data for header"); + } + + return hdr->hdr_len * sizeof(uint64_t); +} + +// Correct the following_images and next_img_ver fields in a BFB buffer. +// Note this assumes a well-formed BFB stream in the buffer. +void correct_bootstream_headers(void *buf, int num_images) { + uint64_t fi_map = 0; + void *idx = buf; + boot_image_header_t *hdr; + int img_size = 0; + int pad_size = 0; + + if (num_images <= 0) + die("%s: num_images must be at least 1", __FUNCTION__); + + // Markers to find images next time + void **images = malloc(num_images * sizeof(void*)); + if (images == NULL) + die("out of memory"); + + // Iterate once to build fi_map and mark image starts + for (int i = 0; i < num_images; i++) { + hdr = (boot_image_header_t*) idx; + + // Build image maps + fi_map |= 1UL << hdr->image_id; + images[i] = idx; + + // Skip to next image + img_size = hdr->image_len; + pad_size = (img_size % 8) ? (8 - img_size % 8) : 0; + idx += img_size + pad_size + hdr->hdr_len * sizeof(uint64_t); + } + + boot_image_header_t *prev_hdr; + + // Now we can iterate over the images directly and fixup + // the fields that need it + for (int i = 0; i < num_images; i++) { + hdr = (boot_image_header_t*) images[i]; + + // Update image bitmap. Note that on the last + // image of a series of versions, this will be wrong, + // and will be corrected next iteration. + hdr->following_images = fi_map; + + if (i > 0) { + prev_hdr = (boot_image_header_t*) images[i-1]; + + if (prev_hdr->image_id == hdr->image_id) { + // Update next_img_ver as long as we haven't gone to + // a new image ID. + prev_hdr->next_img_ver = hdr->cur_img_ver; + } else { + // If next image ID is different, then update + // fi_map, and correct the previous image. + fi_map &= ~(1UL << prev_hdr->image_id); + prev_hdr->following_images = fi_map; + + // We also set prev_hdr's next_img_ver to 0 since + // there are no more images of that version. + prev_hdr->next_img_ver = 0; + } + } + } + + // Edge case: Handle last image. + hdr = (boot_image_header_t*)images[num_images-1]; + hdr->following_images = 0; + hdr->next_img_ver = 0; + + free(images); +} + +// Tell whether we should write an image to the boot +// partition, based on a version number. +// We do this conservatively, so we don't accidentally +// filter out something common. +bool should_install_image(boot_image_header_t *hdr, int version) { + // version < 0 indicates filtering should be off. All + // images will be installed in this case. + if (version < 0) + return true; + + // For now, do not install anything that was introduced + // after the given version. + return hdr->cur_img_ver <= version; +} + +// Read a bootstream to an internal buffer, optionally filtering out images +// of a given version. Supply -1 to version to turn this behavior off. +size_t read_bootstream_to_buffer(const char *bootstream, void *buf, int buf_size, int version) +{ + int ifd = open(bootstream, O_RDONLY); + if (ifd < 0) + die("%s: %m", bootstream); + + struct stat st; + if (fstat(ifd, &st) < 0) + die("%s: stat: %m", bootstream); + + int bytes_left = st.st_size; + + // If no version filtering requested and file is too large for the + // buffer, error out. + if (version < 0 && bytes_left > buf_size) { + die("%s: Boot stream file is too large", bootstream); + } + + void *idx = buf; // Index along buffer + size_t n_bytes = 0; + boot_image_header_t hdr; + size_t hdr_size; // Size of the image header, including reserved words + size_t pad_size; + size_t img_size; + + int num_images = 0; + + // Otherwise, we'll need to read the whole file and filter it to tell + // whether stream will fit. + while (bytes_left > 0) { + hdr_size = read_bootstream_header(bootstream, ifd, &hdr); + bytes_left -= hdr_size; + img_size = hdr.image_len; + + pad_size = (img_size % 8) ? (8 - img_size % 8) : 0; + + // Check whether the version should be included. + if (should_install_image(&hdr, version)) { + // If so, copy the header and img into the buffer. + if ((hdr_size + idx) - buf > buf_size) + die("Boot stream file is too large"); + + memcpy(idx, &hdr, sizeof(hdr)); + idx += hdr_size; + + // Copy image into buffer. + if ((hdr.image_len + idx) - buf > buf_size) + die("Boot stream file is too large"); + + n_bytes = read_or_die(bootstream, ifd, idx, img_size + pad_size); + if (n_bytes != img_size + pad_size) + die("Unable to read next image, n=%d", n_bytes); + + idx += n_bytes; + bytes_left -= n_bytes; + + // Finally, count the image. We reuse this later for + // fixing up the headers. + num_images++; + } else { + // Otherwise, skip over the entire image. + n_bytes = lseek(ifd, img_size + pad_size, SEEK_CUR); + if (n_bytes < 0) + die("%s: Could not skip filtered image: %m", bootstream); + bytes_left -= img_size + pad_size; + } + } + + if (close(ifd) < 0) + die("%s: close: %m", bootstream); + + correct_bootstream_headers(buf, num_images); + + return idx - buf; +} + +void write_bootstream(const char *bootstream, const char *bootfile, int flags, int version) { int sysfd = -1; char *sysname; + size_t ibuf_maxsize = INPUT_BFB_MAX_SIZE; // Reset the force_ro setting if need be if (strncmp(bootfile, "/dev/", 5) == 0) @@ -536,23 +869,21 @@ free(sysname); sysname = NULL; } + + // If writing to /dev/... assume it's an MMC partition + ibuf_maxsize = MMC_BOOT_PARTITION_MAX_SIZE; } // Copy the bootstream to the bootfile device - int ifd = open(bootstream, O_RDONLY); - if (ifd < 0) - die("%s: %m", bootstream); + void *ibuf = malloc(ibuf_maxsize); + void *inidx = ibuf; + uint8_t padbuf[8] = {0}; // There are at most 8 pad bytes. + if (ibuf == NULL) + die("out of memory"); + size_t bytes_left = read_bootstream_to_buffer(bootstream, ibuf, ibuf_maxsize, version); int ofd = open(bootfile, O_WRONLY | flags, 0666); if (ofd < 0) die("%s: %m", bootfile); - struct stat st; - if (fstat(ifd, &st) < 0) - die("%s: stat: %m", bootstream); - size_t bytes_left = st.st_size; - - char *buf = malloc(MAX_SEG_LEN); - if (buf == NULL) - die("out of memory"); // Write the bootstream header word first. This has the byte to // be displayed in the rev_id register as the low 8 bits (zero for now). @@ -571,16 +902,14 @@ write_or_die(bootfile, ofd, &segheader, sizeof(segheader)); // Copy the segment plus any padding. - read_or_die(bootstream, ifd, buf, seg_size); - memset(buf + seg_size, 0, pad_size); - write_or_die(bootfile, ofd, buf, seg_size + pad_size); + write_or_die(bootfile, ofd, inidx, seg_size); + inidx += seg_size; + write_or_die(bootfile, ofd, padbuf, pad_size); } - if (close(ifd) < 0) - die("%s: close: %m", bootstream); if (close(ofd) < 0) die("%s: close: %m", bootfile); - free(buf); + free(ibuf); // Put back the force_ro setting if need be if (sysfd >= 0) @@ -635,7 +964,7 @@ if (bootstream == NULL || output_file == NULL) die("mlx-bootctl: Must specify --output and --bootstream"); - write_bootstream(bootstream, output_file, O_CREAT | O_TRUNC); + write_bootstream(bootstream, output_file, O_CREAT | O_TRUNC, -1); return 0; } @@ -656,7 +985,7 @@ static void verify_bootstream(const char *bootfile) { - int ifd, n, bytes_left, img_size; + int ifd, bytes_left, img_size; boot_image_header_t hdr; struct stat st; uint64_t data; @@ -671,23 +1000,13 @@ bytes_left = st.st_size; if (bytes_left % sizeof(uint64_t)) die("Invalid size %d", bytes_left); - if (bytes_left > MMC_BOOT_PARTITION_MAX_SIZE) - die("Boot file size too big"); + if (bytes_left > INPUT_BFB_MAX_SIZE) + die("Input boot file size too big"); while (bytes_left > 0) { - // Read and verify the image header. - n = read_or_die(bootfile, ifd, &hdr, sizeof(hdr)); - if (n != sizeof(hdr)) - die("Unable to read next header, n=%d", n); - n = hdr.hdr_len - sizeof(hdr) / sizeof(uint64_t); - bytes_left -= hdr.hdr_len * sizeof(uint64_t); - if (n < 0 || bytes_left < 0) + bytes_left -= read_bootstream_header(bootfile, ifd, &hdr); + if (bytes_left < 0) die("Invalid header length"); - // Drain the rest of header. - while (n--) { - if (read_or_die(bootfile, ifd, &data, sizeof(data)) != sizeof(data)) - die("Not enough data for header"); - } // Verify the image crc. crc = ~0; @@ -717,15 +1036,17 @@ { "device", required_argument, NULL, 'd' }, { "output", required_argument, NULL, 'o' }, { "read", required_argument, NULL, 'r' }, + { "version", required_argument, NULL, 'v' }, { "help", no_argument, NULL, 'h' }, { NULL, 0, NULL, 0 } }; - static const char short_options[] = "sb:d:o:r:he"; + static const char short_options[] = "sb:d:o:r:hev:"; static const char help_text[] = "syntax: mlxbf-bootctl [--help|-h] [--swap|-s] [--device|-d MMCFILE]\n" " [--output|-o OUTPUT] [--read|-r INPUT]\n" " [--bootstream|-b BFBFILE] [--overwrite-current]\n" - " [--watchdog-swap interval | --nowatchdog-swap]"; + " [--watchdog-swap interval | --nowatchdog-swap]\n" + " [--version|-v VERSION]"; const char *watchdog_swap = NULL; const char *bootstream = NULL; @@ -733,6 +1054,8 @@ const char *input_file = NULL; bool watchdog_disable = false; bool swap = false; + bool auto_version = true; + int version_arg = -1; int which_boot = 1; // alternate boot partition by default int opt; @@ -779,6 +1102,14 @@ enable_rst_n(); break; + case 'v': + auto_version = false; + char *end; + version_arg = strtol(optarg, &end, 0); + if (end == optarg || *end != '\0') + die("version argument ('%s') must be an integer", optarg); + break; + case 'h': default: die(help_text); @@ -802,7 +1133,8 @@ else if (output_file) { // Write the bootstream to the given file, creating it if needed - write_bootstream(bootstream, output_file, O_CREAT | O_TRUNC); + // Don't filter anything here, since we're not writing to EMMC. + write_bootstream(bootstream, output_file, O_CREAT | O_TRUNC, -1); } else { @@ -827,7 +1159,13 @@ if (asprintf(&bootfile, "%sboot%d", mmc_path, boot_part ^ which_boot) <= 0) die("unexpected failure in asprintf (%s/%d)", __FILE__, __LINE__); - write_bootstream(bootstream, bootfile, O_SYNC); + int version; + if (auto_version) { + version = get_hw_version(); + } else { + version = version_arg; + } + write_bootstream(bootstream, bootfile, O_SYNC, version); // The eMMC driver works in an asynchronous way, thus any commands sent // should occur after the write to the bootstream has retired. Otherwise // a blk_update_request I/O error would be raised and the success of the ++++++ mlxbf-bootctl.obsinfo ++++++ --- /var/tmp/diff_new_pack.3ZbqUr/_old 2022-09-19 16:04:28.182331953 +0200 +++ /var/tmp/diff_new_pack.3ZbqUr/_new 2022-09-19 16:04:28.186331964 +0200 @@ -1,6 +1,5 @@ name: mlxbf-bootctl -version: 1.1.6.11 -mtime: 1601841854 -commit: b6dd3501668c45020f1e81690cd53aa6e21289b0 - +version: 1.1.6.14 +mtime: 1655232109 +commit: 15a7d0e759292ae54b34e881cd0e1a4235c3f957