File mlxbf-bootctl-1.1.6.25.obscpio of Package mlxbf-bootctl
07070100000000000081A4000000000000000000000001660AD06C0000004E000000000000000000000000000000000000002200000000mlxbf-bootctl-1.1.6.25/.gitignoreRPMBUILD
git_dir_pack
*.src.rpm
mlxbf-bootctl
mlxbf-bootctl.d
mlxbf-bootctl.o
07070100000001000081A4000000000000000000000001660AD06C00000533000000000000000000000000000000000000001F00000000mlxbf-bootctl-1.1.6.25/LICENSEBSD 2-Clause License
Copyright (c) 2020, Mellanox Technologies
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
07070100000002000081A4000000000000000000000001660AD06C00000329000000000000000000000000000000000000002000000000mlxbf-bootctl-1.1.6.25/MakefilePROJECT_NAME:=mlxbf-bootctl
SBINDIR = /sbin
# Default target.
all:
include $(shell dirname $(realpath $(firstword $(MAKEFILE_LIST))))/package.mk
# By default, use the Makefile's directory as the vpath.
VPATH := $(dir $(lastword $(MAKEFILE_LIST)))
CFLAGS += -O2 -g -std=gnu99 -Werror \
-Wall -Wshadow -Wuninitialized -Wstrict-overflow -Wundef \
-Wold-style-definition -Wwrite-strings
LDFLAGS += -Wl,--fatal-warnings
SOURCES := \
mlxbf-bootctl.c \
# Grab dependencies, if they exist.
-include $(SOURCES:.c=.d)
%.o: %.c
$(CC) $(CFLAGS) -MD -MP -c -o $@ $<
mlxbf-bootctl: $(SOURCES:.c=.o)
$(CC) $(LDFLAGS) -o $@ $^
all: mlxbf-bootctl
install: mlxbf-bootctl
mkdir -p $(DESTDIR)$(SBINDIR)
cp -f mlxbf-bootctl $(DESTDIR)$(SBINDIR)
clean:
rm -f *.d *.o mlxbf-bootctl
.PHONY: all install clean
07070100000003000081A4000000000000000000000001660AD06C0000272B000000000000000000000000000000000000002100000000mlxbf-bootctl-1.1.6.25/README.mdmlxbf-bootctl
=============
Background
----------
The BlueField boot flow comprises 4 main phases:
- Hardware loads ARM Trusted Firmware (ATF)
- ATF loads Unified Extensible Firmware Interface (UEFI); together ATF
and UEFI make up the booter software
- UEFI loads the operating system, such as the Linux kernel
- The operating system loads applications and user data
When booting from eMMC, these stages make use of two different types
of storage within the eMMC part:
- ATF and UEFI come from a special area known as an eMMC boot
partition. Data from a boot partition is automatically streamed
from the eMMC device to the eMMC controller under hardware control
during the initial bootup. Each eMMC device has two boot
partitions, and the partition that is used to stream the boot data
is chosen by a non-volatile configuration register in the eMMC.
- The operating system, applications, and user data come from the
remainder of the chip, known as the user area. This area is
accessed via block-size reads and writes, done by a device driver or
similar software routine.
In most deployments, BlueField's ARM cores are expected to obtain
their software stack from an on-board Embedded Multi-Media Card (eMMC)
device. Even in environments where the final OS kernel is not kept on
eMMC -- for instance, systems which boot over a network -- the initial
booter code will still come from eMMC.
Most software stacks need to be modified or upgraded at some point in
their lifetime. Doing this on a fielded BlueField system involves
some risk; if there's a severe problem with the new software, system
operation may be impaired to the extent that it is impossible to do
further modifications to fix the problem. Ideally, one would
like to be able to install a new software version on a BlueField
system, try it out, and then fall back to a previous version if the
new one does not work. In some environments, it's important that this
fallback operation happen automatically, since there may be no
physical access to the system. In others, there may be an external
agent, like a service processor, which could manage the process.
Solution Overview
-----------------
In order to satisfy the requests listed above, we do the following:
- Provide two software partitions on the eMMC, 0 and 1. At any given
time, one area is designated the primary partition, and the
other the backup partition. The primary partition is the one we'll
boot from on the next reboot or reset.
- Allow software running on the ARM cores to declare that the primary
partition is now the backup partition, and vice versa. (For
brevity, in the remainder of this document, we'll term this
"swapping the partitions", although we're just modifying a pointer;
the data on the partitions does not move.)
- Allow an external agent, like a service processor, to swap the
primary and backup partitions.
- Allow software running on the ARM cores to reboot the system, while
starting an upgrade watchdog timer. If the upgrade watchdog
expires, the system will reboot, but the primary and backup
partitions will first be swapped.
The mlxbf-bootctl Program
-------------------------
Access to all the boot partition management is via a program shipped
with the BlueField software called "mlxbf-bootctl". The binary is
shipped as part of our Yocto image (in /sbin) and the sources are
shipped in the "src" directory in the BlueField Runtime Distribution.
A simple `make` command will build the utility.
The mlxbf-bootctl syntax is as follows:
```
syntax: mlxbf-bootctl [--help|-h] [--swap|-s] [--device|-d MMCFILE]
[--output|-o OUTPUT] [--read|-r INPUT]
[--bootstream|-b BFBFILE] [--overwrite-current]
[--watchdog-swap interval | --nowatchdog-swap]
--device: Use a device other than the default /dev/mmcblk0
--bootstream: Write the specified bootstream to the alternate
partition of the device. This queries the base device
(e.g. /dev/mmcblk0) for the alternate partition, and uses that
information to open the appropriate boot partition device,
e.g. /dev/mmcblk0boot0.
--overwrite-current: Used with "--bootstream" to overwrite the current
boot partition instead of the alternate one (not recommended)
--output: Used with "--bootstream" to specify a file to write the boot
partition data to (creating it if necessary), rather than using an
existing master device and deriving the boot partition device.
--read: Read a bootstream and convert it back to a BFBFILE specified
by --bootstream. For example use "mlxbf-bootctl --read /dev/mmcblk0boot0
--bootstream current.bfb" to read the current bfb installed on boot
partition zero.
--watchdog-swap: Arrange to start the ARM watchdog with a countdown
of the specified number of seconds until it fires; also, set the
boot software so that it will swap the primary and alternate
partitions at the next reset.
--nowatchdog-swap: Ensure that after the next reset, no watchdog
will be started, and no swapping of boot partitions will happen.
```
Updating the Boot Partition
---------------------------
To update the boot partition on the ARM cores, let us assume we have a
new bootstream file, e.g. "bootstream.new". We would like to install
and validate this new bootstream. To just update and hope for the
best, you can do:
```bash
mlxbf-bootctl --bootstream bootstream.new --swap
reboot
```
This will write the new bootstream to the alternate boot partition,
swap alternate and primary so that the new bootstream will be used on
the next reboot, then reboot to use it.
(You can also use `--overwrite-current` instead of `--swap`, which will
just overwrite your current boot partition, but this is not
recommended as there is no easy way to recover if the new booter code
does not bring the system up.)
Safely Updating with a BMC
--------------------------
With a BMC (board management processor) you can do better. The ARM
cores simply notify the BMC prior to the reboot that an upgrade is
about to happen. Software running on the BMC can then be implemented
that will watch the ARM cores after reboot. If after some time the
BMC has not seen the ARM cores come up properly (for whatever
definition is appropriate for the application in question), it can use
its USB debug connection to the ARM cores to properly reset the ARM
cores, first setting a suitable mode bit that the ARM booter will
respond to by switching the primary and alternate boot partitions as
part of resetting into its original state.
Safely Updating from the ARM Cores
----------------------------------
Without a BMC, you can use the ARM watchdog to achieve similar
results. If something goes wrong on the next reboot and the system
does not come up properly, it will reboot and return to the original
configuration. In this case, you might run:
```bash
mlxbf-bootctl --bootstream bootstream.new --swap --watchdog-swap 60
reboot
```
With these commands, you will reboot the system, and if it hangs for
60 seconds or more, the watchdog will fire and reset the chip, the
booter will swap the partitions back again to the way they were
before, and the system will reboot back with the original boot
partition data. Similarly, if the system comes up but panics and
resets, the booter will again swap the boot partition back to the way
it was before.
You must ensure that Linux after the reboot is configured to boot up
with the `sbsa_gwdt` driver enabled. This is the SBSA (Server Base
System Architecture) Generic WatchDog Timer. As soon as the driver is
loaded, it will start refreshing the watchdog and preventing it from
firing, which will allow your system to finish booting up safely. In
the example above, we allow 60 seconds from system reset until the
Linux watchdog kernel driver is loaded. At that point, your
application may open /dev/watchdog explicitly, and the application
would then become responsible for refreshing the watchdog frequently
enough to keep the system from rebooting.
For documentation on the Linux watchdog subsystem, see the Linux
watchdog documentation:
https://www.kernel.org/doc/Documentation/watchdog/watchdog-api.txt
For example, to disable the watchdog completely, you can run:
```bash
echo V > /dev/watchdog
```
You can incorporate other features of the ARM generic watchdog into
your application code using the programming API as well, if you wish.
Once the system has booted up, in addition to disabling or
reconfiguring the watchdog itself if you wish, you should also clear
the "swap on next reset" functionality from the booter by running:
```bash
mlxbf-bootctl --nowatchdog-swap
```
Otherwise, next time you reset the system (via reboot, external reset,
etc) it will assume a failure or watchdog reset occurred and swap the
eMMC boot partition automatically.
The above steps can be done manually, or can be done automatically by
software running in the newly-booted system.
Changing the Kernel or Userspace
--------------------------------
The above solutions simply update the boot partition to hold new
booter software (ATF and UEFI). If you want to also provide a new
kernel image and/or new userspace, you should partition your eMMC into
multiple partitions appropriately. For example, you might have a
single FAT partition that UEFI can read the kernel image file from,
but the new bootstream contains a UEFI bootpath pointing to an updated
kernel image. Similarly, you might have two Linux partitions, and
your upgrade procedure would write a new filesystem into the "idle"
Linux partition, then reboot with the bootstream holding kernel boot
arguments that direct it to boot from the previously idle partition.
The details on how exactly to do this depend on the specifics of how
and what need to be upgraded for the specific application, but in
principle any component of the system can be safely upgraded using
this type of approach.
07070100000004000041ED000000000000000000000002660AD06C00000000000000000000000000000000000000000000001E00000000mlxbf-bootctl-1.1.6.25/debian07070100000005000081A4000000000000000000000001660AD06C000000C5000000000000000000000000000000000000002800000000mlxbf-bootctl-1.1.6.25/debian/changelogmlxbf-bootctl (2.1) UNRELEASED; urgency=medium
* First debian package release
-- Alfonso Sanchez-Beato (email Canonical) <alfonso.sanchez-beato@canonical.com> Fri, 13 Mar 2020 09:27:39 +0100
07070100000006000081A4000000000000000000000001660AD06C00000003000000000000000000000000000000000000002500000000mlxbf-bootctl-1.1.6.25/debian/compat10
07070100000007000081A4000000000000000000000001660AD06C000001AC000000000000000000000000000000000000002600000000mlxbf-bootctl-1.1.6.25/debian/controlSource: mlxbf-bootctl
Section: net
Priority: optional
Maintainer: Vladimir Sokolovsky <vlad@mellanox.com>
Build-Depends: debhelper (>= 10)
Standards-Version: 4.1.2
Package: mlxbf-bootctl
Architecture: any
Depends: ${shlibs:Depends}, ${misc:Depends}
Description: Binary to manage Mellanox boot partitions
Access to all the boot partition management is via a program shipped
with the BlueField software called "mlxbf-bootctl".
07070100000008000081A4000000000000000000000001660AD06C00000674000000000000000000000000000000000000002800000000mlxbf-bootctl-1.1.6.25/debian/copyrightFormat: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
Upstream-Name: mlxbf-bootctl
Source: https://mellanox.com
Files: *
Copyright: 2018 Mellanox
License: GPL-2 or OpenIB.org
This software is available to you under a choice of one of two
licenses. You may choose to be licensed under the terms of the GNU
General Public License (GPL) Version 2, available from the file
COPYING in the main directory of this source tree, or the
OpenIB.org BSD license below:
.
Redistribution and use in source and binary forms, with or
without modification, are permitted provided that the following
conditions are met:
.
- Redistributions of source code must retain the above
copyright notice, this list of conditions and the following
disclaimer.
.
- Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the following
disclaimer in the documentation and/or other materials
provided with the distribution.
.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
.
On Debian systems, the full text of the GNU General Public
License version 2 can be found in the file
'/usr/share/common-licenses/GPL-2'.
07070100000009000081A4000000000000000000000001660AD06C0000000A000000000000000000000000000000000000003100000000mlxbf-bootctl-1.1.6.25/debian/mlxbf-bootctl.docsREADME.md
0707010000000A000081ED000000000000000000000001660AD06C0000006F000000000000000000000000000000000000002400000000mlxbf-bootctl-1.1.6.25/debian/rules#!/usr/bin/make -f
%:
dh $@
override_dh_auto_build:
dh_auto_build -- CFLAGS="$(CFLAGS) -Wno-unused-result"
0707010000000B000041ED000000000000000000000002660AD06C00000000000000000000000000000000000000000000002500000000mlxbf-bootctl-1.1.6.25/debian/source0707010000000C000081A4000000000000000000000001660AD06C0000000D000000000000000000000000000000000000002C00000000mlxbf-bootctl-1.1.6.25/debian/source/format3.0 (native)
0707010000000D000081A4000000000000000000000001660AD06C00000EEA000000000000000000000000000000000000002700000000mlxbf-bootctl-1.1.6.25/mlxbf-bootctl.8.TH MLXBF\-BOOTCTL 8 "September 3, 2020" "version 2.1" "System Administration"
.SH NAME
mlxbf-bootctl \- control Mellanox BlueField boot partitions
.SH SYNOPSIS
.B mlxbf\-bootctl
[\-h|\-\-help] [options]
.SH DESCRIPTION
.B mlxbf-bootctl
is used to control the two boot firmware partitions present on most Mellanox
BlueField devices.
.SS Boot Partitions
Most Bluefield devices obtain their boot firmware (ATF, UEFI, etc) from an
on-board Embedded Multi-Media Card (eMMC). The eMMC has two partitions that
are treated separately from the others: boot0 and boot1, one of which acts as
the primary, the other acting as a backup. In addition, there is a watchdog
timer present on the chip, which when activated, will automatically swap
which boot partition is active if it is not deactivated in time. These two
features, taken together, allow for safe BlueField firmware upgrades that may
rolled back in case of failure.
.B mlxbf-bootctl
controls these features.
If
.B mlxbf-bootctl
is run without options, it will print the current state of the boot partitions.
.SH OPTIONS
.TP
.B
\-\-device|\-d MMCFILE
Use a device other than the default
.I /dev/mmcblk0
.TP
.B
\-\-bootstream|\-b BFBFILE
Write the specified bootstream to the alternate partition of the device. This
queries the base device (e.g.
.I
/dev/mmcblk0
) for the alternate partition, and
uses that information to open the appropriate boot partition device, e.g. the
.I
/dev/mmcblk0boot0
file. BFB files that can be installed in the boot partitions are typically
named "default.bfb" in BlueField software distributions.
.TP
.B
\-\-overwrite\-current
Used with "\-\-bootstream" to overwrite the current boot partition instead of
the alternate one. This is not recommended as there is no easy way to recover
if the new boot code does not bring the system up.
.TP
.B
\-\-output|\-o OUTPUT
Used with "\-\-bootstream" to specify a file to write the boot partition data
to (creating it if necessary), rather than using an existing master device and
deriving the boot partition device.
.TP
.B
\-\-read|\-r INPUT
Read a bootstream and convert it back to a BFBFILE specified by
"\-\-bootstream". For example use
.RS 11
.B mlxbf-bootctl
\-\-read /dev/mmcblk0boot0
.sp 0
\-\-bootstream current.bfb
.RE
.IP
to read the current bfb installed on boot partition zero.
.TP
.B
\-\-watchdog\-swap SECONDS
Arrange to start the ARM watchdog with a countdown of the specified number of
seconds until it firms; also, set the boot software so that it will swap the
primary and alternate partitions at the next reset. Mutually exclusive with
"\-\-nowatchdog\-swap".
.TP
.B
\-\-nowatchdog-swap
Ensure that after the next reset, no watchdog will be started, and no swapping
of boot partitions will happen.
.TP
.B
\-\-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
.B mlxbf-bootctl
\-\-bootstream new.bfb \-\-watchdog\-swap 90
.PP
Once the new firmware is confirmed to be good, turn off watchdog swapping with
.IP
.B mlxbf-bootctl
\-\-nowatchdog-swap
.SH COMMON ISSUES
When the watchdog timer is activated through the "\-\-watchdog\-swap" option,
entering the UEFI menu for long enough will trigger a reboot and partition
swap. To avoid this, run "\-\-nowatchdog\-swap" before entering the UEFI menu.
0707010000000E000081A4000000000000000000000001660AD06C00009FCC000000000000000000000000000000000000002700000000mlxbf-bootctl-1.1.6.25/mlxbf-bootctl.c/*
* Copyright (c) 2017-2018, Mellanox Technologies Ltd.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following
* disclaimer in the documentation and/or other materials provided
* with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
* COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
* INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
* OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#define _GNU_SOURCE // asprintf
#include <errno.h>
#include <getopt.h>
#include <stdarg.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/fcntl.h>
#include <sys/ioctl.h>
#include <sys/stat.h>
#include <linux/limits.h>
#include <linux/major.h>
/* Boot FIFO constants */
#define BOOT_FIFO_ADDR 0x0408
#define SEGMENT_HEADER_LEN 8
#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
/* PSC APP image ID */
#define PSC_APP_IMG_ID 39
/* Record size of irot signature db. */
#define PSC_APP_IROT_SIG_REC_LEN 104
/* TLV based PSC APP sub-image header. */
typedef struct __attribute__((__packed__)) {
#define PSC_APP_TYPE_IROT_SIGNATURE_DB 0x1
uint8_t type; /* sub-image type */
uint32_t length; /* sub-image payload length */
uint8_t unused[3];
} psc_app_img_hdr_t;
/* Other constants */
#define MAX_VERSIONS_COUNT 256
void die(const char* fmt, ...)
{
va_list ap;
va_start(ap, fmt);
vfprintf(stderr, fmt, ap);
putc('\n', stderr);
va_end(ap);
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>
/*
* The Linux MMC driver doesn't export its ioctl command values, so we
* copy them from include/linux/mmc/mmc.h and include/linux/mmc/core.h.
*/
#define MMC_SWITCH 6 /* ac [31:0] See below R1b */
#define MMC_SEND_EXT_CSD 8 /* adtc R1 */
#define MMC_RSP_PRESENT (1 << 0)
#define MMC_RSP_CRC (1 << 2) /* expect valid crc */
#define MMC_RSP_BUSY (1 << 3) /* card may send busy */
#define MMC_RSP_OPCODE (1 << 4) /* response contains opcode */
#define MMC_RSP_SPI_S1 (1 << 7) /* one status byte */
#define MMC_RSP_SPI_BUSY (1 << 10) /* card may send busy */
#define MMC_RSP_SPI_R1 (MMC_RSP_SPI_S1)
#define MMC_RSP_R1 (MMC_RSP_PRESENT|MMC_RSP_CRC|MMC_RSP_OPCODE)
#define MMC_RSP_R1B (MMC_RSP_PRESENT|MMC_RSP_CRC|MMC_RSP_OPCODE|MMC_RSP_BUSY)
#define MMC_RSP_SPI_R1B (MMC_RSP_SPI_S1|MMC_RSP_SPI_BUSY)
#define MMC_CMD_AC (0 << 5)
#define MMC_CMD_ADTC (1 << 5)
#define MMC_SWITCH_MODE_WRITE_BYTE 0x03 /* Set target to value */
#define EXT_CSD_CMD_SET_NORMAL (1<<0)
/* EXT_CSD register offset. */
#define EXT_CSD_RST_N 162 /* R/W */
#define EXT_CSD_BOOT_BUS_WIDTH 177 /* R/W */
#define EXT_CSD_PART_CONFIG 179 /* R/W */
#define EXT_CSD_BOOT_SIZE_MULT 226 /* R/W */
/* BOOT_BUS_WIDTH register definition. */
#define EXT_CSD_BOOT_BUS_WIDTH_MASK_ALL 0x7
#define EXT_CSD_BOOT_BUS_WIDTH_MASK 0x3
#define EXT_CSD_BOOT_BUS_WIDTH_RESET_MASK 0x4
#define EXT_CSD_BOOT_BUS_WIDTH_X8 0x6
/* EXT_CSD_RST_N register definition. */
#define EXT_CSD_RST_N_MASK 0x3
#define EXT_CSD_RST_N_ENABLE 0x1
/* Program constants */
#define EMMC_MIN_BOOT_SIZE 0x20000
#define EMMC_BLOCK_SIZE 512
#define SYS_PATH1 "/sys/bus/platform/devices/MLNXBF04:00/driver"
#define SYS_PATH2 "/sys/bus/platform/devices/MLNXBF04:00"
#define SECOND_RESET_ACTION_PATH "second_reset_action"
#define POST_RESET_WDOG_PATH "post_reset_wdog"
#define LIFECYCLE_STATE_PATH "lifecycle_state"
#define SECURE_BOOT_FUSE_STATE_PATH "secure_boot_fuse_state"
/* Program variables */
const char *mmc_path = "/dev/mmcblk0";
/* PSC cert update filtering info */
static uint8_t psc_cert_update; /* whether cert need update. */
static uint64_t psc_cert_key; /* cert signature lookup key */
/* Run an MMC_IOC_CMD ioctl on mmc_path */
void mmc_command(struct mmc_ioc_cmd *idata)
{
static int mmc_fd = -1;
if (mmc_fd < 0)
{
mmc_fd = open(mmc_path, O_RDWR);
if (mmc_fd < 0)
die("%s: %m", mmc_path);
}
if (ioctl(mmc_fd, MMC_IOC_CMD, idata) < 0)
die("%s: mmc ioctl: %m", mmc_path);
}
uint8_t* get_ext_csd(void)
{
static uint8_t ext_csd[EMMC_BLOCK_SIZE]
__attribute__((aligned(EMMC_BLOCK_SIZE))) = { 0 };
struct mmc_ioc_cmd idata = {
.write_flag = 0,
.opcode = MMC_SEND_EXT_CSD,
.arg = 0,
.flags = MMC_RSP_SPI_R1 | MMC_RSP_R1 | MMC_CMD_ADTC,
.blksz = EMMC_BLOCK_SIZE,
.blocks = 1
};
mmc_ioc_cmd_set_data(idata, ext_csd);
mmc_command(&idata);
return ext_csd;
}
/* Return the current partition (0 or 1) that we will boot from. */
int get_boot_partition(void)
{
uint8_t *ext_csd = get_ext_csd();
int part = ((ext_csd[EXT_CSD_PART_CONFIG] >> 3) & 0x7) - 1;
/* Set part to 0 by default if it is -1 (boot disabled). */
if (part < 0 || part > 1)
part = 0;
return part;
}
/* Set which partition to boot from. */
void set_boot_partition(int part)
{
int value = ((part + 1) & 0x7) << 3; /* Adjust for 1-based numbering */
struct mmc_ioc_cmd idata = {
.write_flag = 1,
.opcode = MMC_SWITCH,
.arg = ((MMC_SWITCH_MODE_WRITE_BYTE << 24) |
(EXT_CSD_PART_CONFIG << 16) |
(value << 8) |
EXT_CSD_CMD_SET_NORMAL),
.flags = MMC_RSP_SPI_R1B | MMC_RSP_R1B | MMC_CMD_AC
};
mmc_command(&idata);
}
/* Get the boot partition size */
uint64_t get_boot_partition_size(void)
{
uint64_t part_size;
uint8_t *ext_csd = get_ext_csd();
part_size = (ext_csd[EXT_CSD_BOOT_SIZE_MULT]) *
EMMC_MIN_BOOT_SIZE;
return part_size;
}
/* Return the current boot bus width. */
int get_boot_bus_width(void)
{
uint8_t *ext_csd = get_ext_csd();
return ext_csd[EXT_CSD_BOOT_BUS_WIDTH] & EXT_CSD_BOOT_BUS_WIDTH_MASK_ALL;
}
/* Set the boot-bus-width to 8-bit mode in ECSD. */
void set_boot_bus_width(void)
{
uint8_t *ext_csd = get_ext_csd();
int value = (ext_csd[EXT_CSD_BOOT_BUS_WIDTH] &
~EXT_CSD_BOOT_BUS_WIDTH_MASK_ALL) | EXT_CSD_BOOT_BUS_WIDTH_X8;
struct mmc_ioc_cmd idata = {
.write_flag = 1,
.opcode = MMC_SWITCH,
.arg = ((MMC_SWITCH_MODE_WRITE_BYTE << 24) |
(EXT_CSD_BOOT_BUS_WIDTH << 16) |
(value << 8) |
EXT_CSD_CMD_SET_NORMAL),
.flags = MMC_RSP_SPI_R1B | MMC_RSP_R1B | MMC_CMD_AC
};
mmc_command(&idata);
}
void enable_rst_n(void)
{
uint8_t *ext_csd = get_ext_csd();
int value = (ext_csd[EXT_CSD_RST_N] & ~EXT_CSD_RST_N_MASK) |
EXT_CSD_RST_N_ENABLE;
struct mmc_ioc_cmd idata = {
.write_flag = 1,
.opcode = MMC_SWITCH,
.arg = ((MMC_SWITCH_MODE_WRITE_BYTE << 24) |
(EXT_CSD_RST_N << 16) |
(value << 8) |
EXT_CSD_CMD_SET_NORMAL),
.flags = MMC_RSP_SPI_R1B | MMC_RSP_R1B | MMC_CMD_AC
};
mmc_command(&idata);
}
FILE *open_sysfs(const char *name, const char *attr)
{
FILE *f;
char path[PATH_MAX];
sprintf(path, "%s/%s", SYS_PATH1, name);
f = fopen(path, attr);
if (f == NULL) {
sprintf(path, "%s/%s", SYS_PATH2, name);
f = fopen(path, attr);
}
if (f == NULL) {
if (errno == ENOENT) {
fprintf(stderr, "cannot find required sysfs path %s\n", path);
die("please load mlxbf_bootctl kernel driver");
}
die("%s: %m", name);
}
return f;
}
int get_watchdog(void)
{
FILE *f = open_sysfs(POST_RESET_WDOG_PATH, "r");
int watchdog;
if (fscanf(f, "%d", &watchdog) != 1)
die("%s: failed to read integer", POST_RESET_WDOG_PATH);
fclose(f);
return watchdog;
}
void set_watchdog(int interval)
{
FILE *f = open_sysfs(POST_RESET_WDOG_PATH, "w");
if (fprintf(f, "%d\n", interval) < 0)
die("%s: failed to set watchdog to '%d'", POST_RESET_WDOG_PATH, interval);
fclose(f);
}
void set_second_reset_action(const char *action)
{
FILE *f = open_sysfs(SECOND_RESET_ACTION_PATH, "w");
if (fprintf(f, "%s\n", action) < 0)
die("%s: failed to set action to '%s'", SECOND_RESET_ACTION_PATH, action);
fclose(f);
}
// Return the (malloced) string describing the lifecycle state (w line return)
char *get_lifecycle_state(void)
{
FILE *f = open_sysfs(LIFECYCLE_STATE_PATH, "r");
char *buf = NULL;
size_t len = 0;
if (getline(&buf, &len, f) == -1)
die("%s: failed to get lifecycle state", LIFECYCLE_STATE_PATH);
fclose(f);
return buf;
}
// Return number of remaining free secure boot fuse versions
int get_free_sbfuse_slots(void)
{
int free_slot = 0;
FILE *f = open_sysfs(SECURE_BOOT_FUSE_STATE_PATH, "r");
char *buf = NULL, *p;
size_t len = 0;
while (getline(&buf, &len, f) != -1)
{
p = buf;
while ((p = strstr(p, "Free")) != NULL) {
free_slot++;
p += strlen("Free");
}
}
free(buf);
fclose(f);
return free_slot;
}
void show_status(void)
{
// Display the default boot partition
int part = get_boot_partition();
printf("primary: %sboot%d\n", mmc_path, part);
printf("backup: %sboot%d\n", mmc_path, part ^ 1);
// Display the boot bus width setting
int boot_bus_width = get_boot_bus_width();
printf("boot-bus-width: x%d\n",
(boot_bus_width & EXT_CSD_BOOT_BUS_WIDTH_MASK) ?
(boot_bus_width & EXT_CSD_BOOT_BUS_WIDTH_MASK) * 4 : 1);
printf("reset to x1 after reboot: %s\n",
(boot_bus_width & EXT_CSD_BOOT_BUS_WIDTH_RESET_MASK)? "FALSE" : "TRUE");
// Display the watchdog value
int watchdog = get_watchdog();
printf("watchdog-swap: ");
if (watchdog == 0)
printf("disabled\n");
else
printf("%d\n", watchdog);
// Display the secure boot fuse states
char *lifecycle_state = get_lifecycle_state();
printf("lifecycle state: %s", lifecycle_state);
free(lifecycle_state);
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;
}
/* Find substring within a buffer. */
static char* find_str(char *buf, int buf_len, const char *str)
{
int len;
char *p = buf, *find;
while ((p - buf) < buf_len) {
len = strlen(p);
find = strstr(p, str);
if (find)
return find;
p += len + 1;
}
return NULL;
}
/*
* Handle the IROT signature DB and returns the offset of the record which
* matches the key 'psc_cert_key', or return value < 0 in case of error case
* or not found.
*/
static int handle_irot_sig_db(uint8_t *buf, uint32_t buf_len)
{
uint32_t idx0, idx1, idx;
uint64_t data64;
/* Ignore sig db if not patch is needed. */
if (!psc_cert_update)
return -1;
/* Sanity check. */
if (buf_len % PSC_APP_IROT_SIG_REC_LEN)
return -EINVAL;
/* Binary search on the sorted records within range [idx0, idx1]. */
idx0 = 0;
idx1 = buf_len / PSC_APP_IROT_SIG_REC_LEN - 1;
/* Binary search to find the record. */
for(;;) {
idx = (idx0 + idx1) / 2;
data64 = *(uint64_t *)(buf + idx * PSC_APP_IROT_SIG_REC_LEN);
if (data64 < psc_cert_key) {
if (idx0 == idx)
break;
else
idx0 = idx;
} else if (data64 == psc_cert_key) {
break;
} else {
if (idx1 == idx)
break;
else
idx1 = idx;
}
}
return (data64 == psc_cert_key) ? idx : -1;
}
/*
* Filter the PSC APP image.
* For the IROT signature DB, only up to one record is needed if matching the
* lookup key.
*/
size_t filter_psc_app_image(void *image, size_t img_size)
{
psc_app_img_hdr_t *hdr;
size_t adjust_len = 0, len = 0;
int idx;
while (len < img_size) {
hdr = (psc_app_img_hdr_t *)(image + len);
if (len + sizeof(*hdr) + hdr->length > img_size)
die("corrupted image\n");
if (len != adjust_len) {
memmove(image + adjust_len, image + len, sizeof(*hdr) + hdr->length);
hdr = (psc_app_img_hdr_t *)(image + adjust_len);
}
len += sizeof(*hdr) + hdr->length;
if (hdr->type != PSC_APP_TYPE_IROT_SIGNATURE_DB) {
adjust_len += sizeof(*hdr) + hdr->length;
continue;
}
idx = handle_irot_sig_db((uint8_t *)&hdr[1], hdr->length);
if (idx >= 0) {
memmove((uint8_t *)&hdr[1],
(uint8_t *)&hdr[1] + idx * PSC_APP_IROT_SIG_REC_LEN,
PSC_APP_IROT_SIG_REC_LEN);
hdr->length = PSC_APP_IROT_SIG_REC_LEN;
adjust_len += sizeof(*hdr) + PSC_APP_IROT_SIG_REC_LEN;
}
}
return adjust_len;
}
static uint32_t crc32_update(uint32_t crc, uint8_t *p, unsigned int len)
{
int i;
while (len--) {
crc ^= *p++;
for (i = 0; i < 8; i++)
crc = (crc >> 1) ^ ((crc & 1) ? 0xedb88320 : 0);
}
return crc;
}
#else
/* Dummy max partition size for OUTPUT_ONLY */
uint64_t get_boot_partition_size(void)
{
return INPUT_BFB_MAX_SIZE;
}
#endif // OUTPUT_ONLY
// Read as much as possible despite EINTR or partial reads, and die on error.
ssize_t read_or_die(const char* filename, int fd, void* buf, size_t count)
{
ssize_t n = 0;
while (count > 0)
{
ssize_t rc = read(fd, buf, count);
if (rc < 0)
{
if (errno == EINTR)
continue;
die("%s: can't read: %m", filename);
}
if (rc == 0)
break;
n += rc;
buf += rc;
count -= rc;
}
return n;
}
// Write everything passed in despite EINTR or partial reads, and die on error.
ssize_t write_or_die(const char* filename,
int fd, const void* buf, size_t count)
{
ssize_t n = count;
while (count > 0)
{
ssize_t rc = write(fd, buf, count);
if (rc < 0)
{
if (errno == EINTR)
continue;
die("%s: can't write: %m", filename);
}
if (rc == 0)
die("%s: write returned zero", filename);
buf += rc;
count -= rc;
}
return n;
}
/*
* Generate the boot stream segment header.
*
* is_end: 1 if this segment is the last segment of the boot code, else 0.
* channel: The channel number to write to.
* address: The register address to write to.
* length: The length of this segment in bytes; max is MAX_SEG_LEN.
*
* We ignore endian issues here since if the tool is built natively,
* this is likely correct anyway, and if built cross, we don't have a
* way to know the endianness of the arm cores anyway.
*/
uint64_t gen_seg_header(int is_end, int channel, int address, size_t length)
{
return (((is_end & 0x1UL) << 63) |
((channel & 0xfUL) << 45) |
((address & 0xfff8UL) << 29) |
(((length + SEGMENT_HEADER_LEN) >> 3) & 0x1ffffUL));
}
uint64_t get_segment_length(uint64_t segheader)
{
uint64_t length = (segheader & 0x1ffffUL) << 3;
if (length == 0)
length = MAX_SEG_LEN;
else
length -= SEGMENT_HEADER_LEN;
return length;
}
void read_bootstream(const char *bootstream, const char *bootfile,
uint64_t part_size)
{
// Copy the contents of the bootfile device to a bootstream
printf("Copy bootstream from %s to %s\n", bootfile, bootstream);
int ifd = open(bootfile, O_RDONLY);
if (ifd < 0)
die("%s: %m", bootfile);
int ofd = open(bootstream, O_WRONLY | O_CREAT | O_TRUNC, 0666);
if (ofd < 0)
die("%s: %m", bootstream);
char *buf = malloc(MAX_SEG_LEN);
if (buf == NULL)
die("out of memory");
uint64_t header;
// Read and discard the header word
read_or_die(bootfile, ifd, &header, sizeof(header));
uint64_t segheader = 0, total_size = 0;
while (!(segheader & SEGMENT_IS_END))
{
read_or_die(bootfile, ifd, &segheader, sizeof(segheader));
uint64_t seg_size = get_segment_length(segheader);
read_or_die(bootfile, ifd, buf, seg_size);
write_or_die(bootstream, ofd, buf, seg_size);
total_size += seg_size;
if (total_size > part_size)
die("No valid bfb present");
}
if (close(ifd) < 0)
die("%s: close: %m", bootstream);
if (close(ofd) < 0)
die("%s: close: %m", bootfile);
free(buf);
}
// 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, uint8_t *max_versions) {
bool install = false;
// version < 0 indicates filtering should be off. All
// images will be installed in this case.
if (version < 0)
return true;
// If version filtering is on, check max_versions is valid
if (max_versions == NULL)
die("internal: max_versions is NULL");
#ifndef OUTPUT_ONLY
if ((hdr->image_id == PSC_APP_IMG_ID) && !psc_cert_update)
return false;
#endif
// The logic to decide what to install and what not to install
// is based on BlueField 1, 2, 3, <X> chips each possibly having
// their own version of a specific image while at the same time
// also using "shared" images. A shared image is defined as an
// image with a version of 0 AND there are no other higher
// versions in the bootstream (i.e. images with a version > 0).
// If the version matches OR the image is a shared image, install it.
if ((hdr->cur_img_ver == version) || (max_versions[hdr->image_id] == 0)) {
install = true;
}
return install;
}
// Find the maximum version image for each image ID.
// max_versions must be a 256 element array. Each element of max_versions
// corresponds to a maximum version: max_versions[img_id] = max_img_ver
void find_max_versions(const char *bootstream, uint8_t *max_versions) {
if (max_versions == NULL)
die("%s: internal: max_versions is NULL", bootstream);
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;
// Initialize max_versions
memset (max_versions, 0, MAX_VERSIONS_COUNT * sizeof(uint8_t));
boot_image_header_t hdr;
size_t hdr_size;
size_t pad_size;
size_t img_size;
size_t n_bytes;
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;
// update max_versions if needed
if (hdr.cur_img_ver > max_versions[hdr.image_id])
max_versions[hdr.image_id] = hdr.cur_img_ver;
// Skip 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;
}
close (ifd);
}
// 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);
}
uint8_t max_versions[MAX_VERSIONS_COUNT];
find_max_versions(bootstream, max_versions);
void *idx = buf; // Index along buffer
size_t n_bytes = 0, n_bytes_adjust = 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, max_versions)) {
// 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);
n_bytes_adjust = n_bytes;
#ifndef OUTPUT_ONLY
if (hdr.image_id == PSC_APP_IMG_ID) {
// Update image len and CRC for trimmed image.
n_bytes_adjust = filter_psc_app_image(idx, img_size);
boot_image_header_t *cur_hdr =
(boot_image_header_t *)(idx - sizeof(hdr));
cur_hdr->image_len = n_bytes_adjust;
cur_hdr->image_crc = ~crc32_update(~0, idx, n_bytes_adjust);
}
#endif
idx += n_bytes_adjust;
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;
}
/*
* Get the filtering information from ACPI, which has the
* irot-cert-update: L4 IROT cert needs to be updated.
* irot-cert-key: L4 IROT cert signature lookup key.
*/
static void get_psc_app_filter_info(void)
{
#ifndef OUTPUT_ONLY
char path[] = "/sys/firmware/acpi/tables/SSDT1";
struct stat st;
int i, fd;
char *buf = NULL, *p;
/* Support SSDT[1~9] */
for (i = 1; i <= 9; i++) {
path[strlen(path) - 1] = '0' + i;
fd = open(path, O_RDONLY);
if (fd == -1)
break;
// Get size
if (fstat(fd, &st) < 0)
die("%s: stat: %m", path);
// Allocate memory and read the file.
if (buf)
free(buf);
buf = malloc(st.st_size + 1);
if (!buf)
die("fail to alloc buf: %m");
buf[st.st_size] = 0;
if (read_or_die(path, fd, buf, st.st_size) != st.st_size)
die("%s: unexpected read", path);
close(fd);
p = find_str(buf, st.st_size, "irot-cert-key");
if (!p)
continue;
memcpy(&psc_cert_key, p + strlen("irot-cert-key") + 2, sizeof(uint64_t));
p = find_str(buf, st.st_size, "irot-cert-update");
if (!p)
break;
memcpy(&psc_cert_update, p + strlen("irot-cert-update") + 2, sizeof(uint8_t));
break;
}
if (buf)
free(buf);
#endif
}
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;
get_psc_app_filter_info();
// Reset the force_ro setting if need be
if (strncmp(bootfile, "/dev/", 5) == 0)
{
if (asprintf(&sysname, "/sys/block/%s/force_ro", &bootfile[5]) <= 0)
die("unexpected failure in asprintf (%s/%d)", __FILE__, __LINE__);
sysfd = open(sysname, O_RDWR | O_SYNC);
if (sysfd >= 0)
{
char status;
if (read_or_die(sysname, sysfd, &status, 1) != 1)
die("%s: unexpected EOF on read", sysname);
if (status == '1')
{
char disabled = '0';
if (lseek(sysfd, 0, SEEK_SET) != 0)
die("%s: can't seek back to start: %m", sysname);
write_or_die(sysname, sysfd, &disabled, 1);
}
else
{
close(sysfd);
sysfd = -1;
free(sysname);
sysname = NULL;
}
}
else
{
if (errno != ENOENT)
die("%s: open: %m", sysname);
printf("WARNING: No matching %s for %s\n", sysname, bootfile);
free(sysname);
sysname = NULL;
}
// If writing to /dev/... assume it's an MMC partition
ibuf_maxsize = get_boot_partition_size();
}
// Copy the bootstream to the bootfile device
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);
// 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).
uint64_t header = 0;
write_or_die(bootfile, ofd, &header, sizeof(header));
while (bytes_left > 0)
{
size_t seg_size = (bytes_left <= MAX_SEG_LEN) ? bytes_left : MAX_SEG_LEN;
bytes_left -= seg_size;
// Generate the segment header.
size_t pad_size = seg_size % 8 ? (8 - seg_size % 8) : 0;
uint64_t segheader = gen_seg_header(bytes_left == 0, 1, BOOT_FIFO_ADDR,
seg_size + pad_size);
write_or_die(bootfile, ofd, &segheader, sizeof(segheader));
// Copy the segment plus any padding.
write_or_die(bootfile, ofd, inidx, seg_size);
inidx += seg_size;
write_or_die(bootfile, ofd, padbuf, pad_size);
}
if (close(ofd) < 0)
die("%s: close: %m", bootfile);
free(ibuf);
// Put back the force_ro setting if need be
if (sysfd >= 0)
{
if (lseek(sysfd, 0, SEEK_SET) != 0)
die("%s: can't seek back to start: %m", sysname);
char enabled = '1';
write_or_die(sysname, sysfd, &enabled, 1);
close(sysfd);
free(sysname);
}
}
#ifdef OUTPUT_ONLY
int main(int argc, char **argv)
{
static struct option long_options[] = {
{ "bootstream", required_argument, NULL, 'b' },
{ "output", required_argument, NULL, 'o' },
{ "help", no_argument, NULL, 'h' },
{ NULL, 0, NULL, 0 }
};
static const char short_options[] = "b:o:h";
static const char help_text[] =
"syntax: mlx-bootctl [--help|-h] --bootstream|-b BFBFILE --output|-o OUTPUT";
const char *bootstream = NULL;
const char *output_file = NULL;
int opt;
while ((opt = getopt_long(argc, argv, short_options, long_options, NULL))
!= -1)
{
switch (opt)
{
case 'b':
bootstream = optarg;
break;
case 'o':
output_file = optarg;
break;
case 'h':
default:
die(help_text);
break;
}
}
if (bootstream == NULL || output_file == NULL)
die("mlx-bootctl: Must specify --output and --bootstream");
write_bootstream(bootstream, output_file, O_CREAT | O_TRUNC, -1);
return 0;
}
#else
static void verify_bootstream(const char *bootfile)
{
int ifd, bytes_left, img_size;
boot_image_header_t hdr;
struct stat st;
uint64_t data;
uint32_t crc;
ifd = open(bootfile, O_RDONLY);
if (ifd < 0)
die("%s: %m", bootfile);
if (fstat(ifd, &st) < 0)
die("%s: stat: %m", bootfile);
bytes_left = st.st_size;
if (bytes_left % sizeof(uint64_t))
die("Invalid size %d", bytes_left);
if (bytes_left > INPUT_BFB_MAX_SIZE)
die("Input boot file size too big");
while (bytes_left > 0) {
bytes_left -= read_bootstream_header(bootfile, ifd, &hdr);
if (bytes_left < 0)
die("Invalid header length");
// Verify the image crc.
crc = ~0;
img_size = hdr.image_len;
while (img_size > 0) {
if (read_or_die(bootfile, ifd, &data, sizeof(data)) != sizeof(data))
die("Not enough data for image id %d, missing size %d", hdr.image_id, img_size);
crc = crc32_update(crc, (uint8_t *)&data, sizeof(data));
img_size -= sizeof(data);
bytes_left -= sizeof(data);
}
if (hdr.image_crc != ~crc)
die("Invalid CRC for image_id %d", hdr.image_id);
}
close(ifd);
}
int main(int argc, char **argv)
{
static struct option long_options[] = {
{ "swap", no_argument, NULL, 's' },
{ "watchdog-swap", required_argument, NULL, 'w' },
{ "nowatchdog-swap", no_argument, NULL, 'n' },
{ "bootstream", required_argument, NULL, 'b' },
{ "overwrite-current", no_argument, NULL, 'c' },
{ "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: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]\n"
" [--version|-v VERSION]";
const char *watchdog_swap = NULL;
const char *bootstream = NULL;
const char *output_file = NULL;
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;
while ((opt = getopt_long(argc, argv, short_options, long_options, NULL))
!= -1)
{
switch (opt)
{
case 's':
swap = true;
break;
case 'w':
watchdog_swap = optarg;
watchdog_disable = false;
break;
case 'n':
watchdog_swap = NULL;
watchdog_disable = true;
break;
case 'b':
bootstream = optarg;
break;
case 'c':
which_boot = 0; // overwrite current boot partition (dangerous)
break;
case 'd':
mmc_path = optarg;
break;
case 'o':
output_file = optarg;
break;
case 'r':
input_file = optarg;
break;
case 'e':
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);
break;
}
}
if (!bootstream && !swap && watchdog_swap == NULL && !watchdog_disable)
{
show_status();
return 0;
}
if (bootstream)
{
if (input_file)
{
uint64_t boot_part_size = get_boot_partition_size();
read_bootstream(bootstream, input_file, boot_part_size);
}
else if (output_file)
{
// Write the bootstream to the given file, creating it if needed
// Don't filter anything here, since we're not writing to EMMC.
write_bootstream(bootstream, output_file, O_CREAT | O_TRUNC, -1);
}
else
{
verify_bootstream(bootstream);
struct stat st;
int error;
error = stat(bootstream, &st);
if (error < 0)
die("%s: %m", bootstream);
// Get the active partition and write to the appropriate *bootN file
// Must save/restore boot partition, which I/O otherwise resets to zero.
int boot_part = get_boot_partition();
char *bootfile;
if (asprintf(&bootfile, "%sboot%d", mmc_path, boot_part ^ which_boot) <= 0)
die("unexpected failure in asprintf (%s/%d)", __FILE__, __LINE__);
int version;
if (auto_version) {
version = get_hw_version();
} else {
version = version_arg;
}
// Note: There is no check to see if the image will fit in the
// boot partition in main(). This is because write_bootstream()
// may filter the contents of the stream, meaning the size check
// must be done in write_bootstream().
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
// command cannot be guaranteed. The check for the status of the eMMC
// at user level is non-trival, thus a delay is added here instead.
// @TODO: Update the delay to check for the eMMC status.
sleep(1);
set_boot_partition(boot_part);
set_boot_bus_width();
enable_rst_n();
}
}
if (swap)
set_boot_partition(get_boot_partition() ^ 1);
if (watchdog_swap != NULL)
{
// Enable reset watchdog to swap eMMC on reset after watchdog interval
char *end;
int watchdog = strtol(watchdog_swap, &end, 0);
if (end == watchdog_swap || *end != '\0')
die("watchdog-swap argument ('%s') must be an integer", watchdog_swap);
set_watchdog(watchdog);
set_second_reset_action("swap_emmc");
enable_rst_n();
}
if (watchdog_disable)
{
// Disable reset watchdog and don't adjust reset actions at reset time
set_watchdog(0);
set_second_reset_action("none");
}
return 0;
}
#endif // OUTPUT_ONLY
0707010000000F000081A4000000000000000000000001660AD06C0000074D000000000000000000000000000000000000002A00000000mlxbf-bootctl-1.1.6.25/mlxbf-bootctl.specName: mlxbf-bootctl
Version: 2.1
Release: 1%{?dist}
Summary: Mellanox BlueField boot partition management utility
License: BSD
Url: https://github.com/Mellanox/mlxbf-bootctl
Source: mlxbf-bootctl-2.1.tar.gz
# mlxbf-bootctl will build for most architectures, however this program is
# designed only to control BlueField SoCs, which are all aarch64, so only
# build for that architecture.
ExclusiveArch: aarch64
BuildRequires: gcc
%description
mlxbf-bootctl is used to control the two boot firmware (ATF, UEFI, etc...)
partitions present on most Mellanox BlueField devices. This includes switching
active partitions, listing the state of boot registers, and installing new
firmware.
# this section is automatically generated by rpkg
%prep
%setup
%build
%{?set_build_flags}
%make_build
%install
# Note: make install normally installs to "/sbin"
%{__make} install DESTDIR=%{buildroot}%{_exec_prefix}
%{__install} -d %{buildroot}%{_mandir}/man8
%{__install} -m 0644 mlxbf-bootctl.8 %{buildroot}%{_mandir}/man8
%files
%{_sbindir}/mlxbf-bootctl
%{_mandir}/man8/mlxbf-bootctl.8.gz
%license LICENSE
%doc README.md
%changelog
* Thu Sep 3 2020 Spencer Lingard <spencer@nvidia.com> - 2.1-1
- Bump version to sync up with others
- Modify spec.rpkg to depend less on rpkg
* Mon Jun 22 2020 Spencer Lingard <spencer@nvidia.com> - 1.1-6
- Add set_build_flags macro to build step
- Warn user if mlxbf_bootctl kernel driver is not loaded
* Wed Jun 17 2020 Spencer Lingard <spencer@nvidia.com> - 1.1-5
- Simplify release tag
- Add comment explaining ExclusiveArch tag
- Improve description
- Remove unneeded build dependency
- Move mlxbf-bootctl from /sbin to /usr/sbin
- Pull in code coverage fixes
* Wed Jun 10 2020 Spencer Lingard <spencer@nvidia.com> - 1.1-4
- Add man page
- Add license
* Tue May 12 2020 Spencer Lingard <spencer@nvidia.com> - 1.1-3
Initial packaging.
07070100000010000081A4000000000000000000000001660AD06C000007D8000000000000000000000000000000000000002F00000000mlxbf-bootctl-1.1.6.25/mlxbf-bootctl.spec.rpkgName: mlxbf-bootctl
Version: 2.1
Release: 1%{?dist}
Summary: Mellanox BlueField boot partition management utility
License: BSD
Url: https://github.com/Mellanox/mlxbf-bootctl
Source: {{{ name=$(rpmspec -q --qf "%{name}-%{version}\n" mlxbf-bootctl.spec.rpkg); git_dir_pack dir_name="$name" source_name="$name".tar.gz }}}
# mlxbf-bootctl will build for most architectures, however this program is
# designed only to control BlueField SoCs, which are all aarch64, so only
# build for that architecture.
ExclusiveArch: aarch64
BuildRequires: gcc
%description
mlxbf-bootctl is used to control the two boot firmware (ATF, UEFI, etc...)
partitions present on most Mellanox BlueField devices. This includes switching
active partitions, listing the state of boot registers, and installing new
firmware.
# this section is automatically generated by rpkg
%prep
%setup
%build
%set_build_flags
%make_build
%install
# Note: make install normally installs to "/sbin"
%{__make} install DESTDIR=%{buildroot}%{_exec_prefix}
%{__install} -d %{buildroot}%{_mandir}/man8
%{__install} -m 0644 mlxbf-bootctl.8 %{buildroot}%{_mandir}/man8
%files
%{_sbindir}/mlxbf-bootctl
%{_mandir}/man8/mlxbf-bootctl.8.gz
%license LICENSE
%doc README.md
%changelog
* Thu Sep 3 2020 Spencer Lingard <spencer@nvidia.com> - 2.1-1
- Bump version to sync up with others
- Modify spec.rpkg to depend less on rpkg
- Add makefile to build SRPMs
* Mon Jun 22 2020 Spencer Lingard <spencer@nvidia.com> - 1.1-6
- Add set_build_flags macro to build step
- Warn user if mlxbf_bootctl kernel driver is not loaded
* Wed Jun 17 2020 Spencer Lingard <spencer@nvidia.com> - 1.1-5
- Simplify release tag
- Add comment explaining ExclusiveArch tag
- Improve description
- Remove unneeded build dependency
- Move mlxbf-bootctl from /sbin to /usr/sbin
- Pull in code coverage fixes
* Wed Jun 10 2020 Spencer Lingard <spencer@nvidia.com> - 1.1-4
- Add man page
- Add license
* Tue May 12 2020 Spencer Lingard <spencer@nvidia.com> - 1.1-3
Initial packaging.
07070100000011000081A4000000000000000000000001660AD06C000005D5000000000000000000000000000000000000002200000000mlxbf-bootctl-1.1.6.25/package.mk# Include this file from the main Makefile, and define PROJECT_NAME
# before the include statement.
SPEC_NAME:=$(PROJECT_NAME).spec
# Obtain version from RPM spec file
VERSION:=$(shell grep '^Version:' $(SPEC_NAME) | cut -d' ' -f2)
TAR_BASE:=$(PROJECT_NAME)-$(VERSION)
TAR_NAME:=$(TAR_BASE).tar.gz
GIT_FILES:=$(shell git ls-files -co --exclude-standard)
git_dir_pack/$(TAR_NAME): $(GIT_FILES)
rm -rf git_dir_pack
mkdir -p git_dir_pack/$(TAR_BASE)
rsync --relative $(GIT_FILES) git_dir_pack/$(TAR_BASE)
(cd git_dir_pack; tar -zcvf ../$@ $(TAR_BASE))
.PHONY: tarball
tarball: git_dir_pack/$(TAR_NAME)
@echo Tarball can be found at $<
.PHONY: pkgclean
pkgclean: srpmclean
rm -rf git_dir_pack
# RPM-SPECIFIC
RPMBUILD_LOC:=$(shell which rpmbuild)
ifneq ($(RPMBUILD_LOC),)
# Draw pkg name from spec file
RPM_PROJECT_NAME:=$(shell \
rpmspec -q --qf "%{name}\n" $(SPEC_NAME) | \
head -n1)
RPM_BASE:=$(shell \
rpmspec --query --qf "%{name}-%{version}-%{release}\n" $(SPEC_NAME) | \
head -n1)
SRPM_NAME:=$(RPM_BASE).src.rpm
srpmclean:
rm -rf RPMBUILD
rm -f $(RPM_PROJECT_NAME)*.src.rpm
srpm: $(SRPM_NAME)
RPMBUILD/SOURCES/$(TAR_NAME): git_dir_pack/$(TAR_NAME)
mkdir -p RPMBUILD/SOURCES
cp $< $@
$(SRPM_NAME): RPMBUILD/SOURCES/$(TAR_NAME) $(SPEC_NAME)
rpmbuild -bs --define "_topdir $(shell pwd)/RPMBUILD" $(SPEC_NAME)
cp RPMBUILD/SRPMS/$(SRPM_NAME) ./
else
srpm:
@echo "Cannot build SRPM, rpmbuild tools not installed!"
exit 1
srpmclean:
endif
.PHONY: srpmclean srpm
07070100000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000B00000000TRAILER!!!133 blocks