tools/ocp_recovery/ocp_recovery.c (1,489 lines of code) (raw):
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
#include <errno.h>
#include <fcntl.h>
#include <linux/i2c-dev.h>
#include <linux/i2c.h>
#include <linux/types.h>
#include <stdarg.h>
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ioctl.h>
#include <sys/stat.h>
#include <time.h>
#include <unistd.h>
#include "i2c_platform.h"
#include "crypto/checksum.h"
#include "logging/debug_log.h"
/**
* The maximum amount of data that can be transmitted in a single SMBus block read or write.
*/
#define MAX_SMBUS_BLOCK_SIZE 255
/**
* The maximum amount of aligned data that can be read from or written to a CMS.
*/
#define MAX_CMS_BLOCK_SIZE 252
/**
* File descriptor for the I2C bus connected to the device.
*/
int i2c;
/**
* The 7-bit I2C address of the target device.
*/
uint8_t addr = 0x69;
/**
* The CMS to use for the operation.
*/
uint8_t cms_id = 0;
/**
* Offset within the CMS to access.
*/
uint32_t cms_offset = 0;
/**
* Maximum block size for CMS write.
*/
uint32_t cms_block_write = MAX_CMS_BLOCK_SIZE;
/**
* Flag to ignore validation errors when processing commands. Protocol and bus errors still
* trigger a failure.
*/
bool ignore_errors = false;
/**
* Enable and disable PEC on reads/writes.
*/
bool pec = true;
/**
* Force a PEC error on a raw write transaction. This is only applicable if a PEC byte is being
* generated for the command.
*/
bool force_pec_error = false;
/**
* Flag indicating a read or write operation.
*/
bool is_read = true;
/**
* Flag indicating that raw bytes of the command should be displayed.
*/
bool raw_bytes = false;
/**
* Log read for a vendor defined CMS uses the Cerberus log format.
*/
bool is_cerberus_log = false;
/**
* For reset commands, force the device into recovery mode.
*/
bool force_recovery = false;
/**
* The command to execute.
*/
const char *command = NULL;
/**
* The file name provided with the command.
*/
const char *file_name = NULL;
/**
* Indicate the specified file should be used to output messages.
*/
bool file_out = false;
/**
* Array of raw data provided to the command.
*/
uint32_t raw_data[MAX_SMBUS_BLOCK_SIZE];
/**
* Number of raw data entries provided.
*/
size_t raw_data_count = 0;
/**
* Add a delay after sending block write commands before issuing another command.
*/
bool use_write_delay = false;
/**
* The delay to add after a block write, in microseconds.
*/
uint32_t write_delay = 1000;
/**
* Output verbosity.
*/
int verbose = 0;
/**
* Get the current monotonic clock count value.
*
* @param current Output for the current system time.
*/
static void get_current_time (struct timespec *current)
{
int status;
status = clock_gettime (CLOCK_MONOTONIC, current);
if (status != 0) {
printf ("Failed to get the current time: %s\n", strerror (errno));
exit (1);
}
}
/**
* Get the duration between two time values.
*
* @param start The start time for the time duration.
* @param end The end time for the time duration.
*
* @return The elapsed time, in microseconds. If either clock is null, the elapsed time will be 0.
*/
uint32_t get_time_duration (const struct timespec *start, const struct timespec *end)
{
if ((end == NULL) || (start == NULL)) {
return 0;
}
if (start->tv_sec > end->tv_sec) {
return 0;
}
else if (start->tv_sec == end->tv_sec) {
if (start->tv_nsec > end->tv_nsec) {
return 0;
}
else {
return (end->tv_nsec - start->tv_nsec) / 1000ULL;
}
}
else {
uint32_t duration = end->tv_nsec / 1000ULL;
duration += (1000000000ULL - start->tv_nsec) / 1000ULL;
duration += (end->tv_sec - start->tv_sec) * 1000000;
return duration;
}
}
/**
* Write data to an open file.
*
* @param fd The file descriptor for the file.
* @param data Data to write.
* @param length Amount of data to write.
*/
void write_to_file (int fd, const char *data, int length)
{
if (write (fd, data, length) < 0) {
printf ("Failed to write to output file %s: %s\n", file_name, strerror (errno));
exit (1);
}
}
/**
* Print a single message either to the console or to a file.
*
* @param fd The file descriptor for an open file, if the output should be to a file. Ignored if
* not using file output (determined by file_out).
* @param fmt The formatting string to print.
* @param ... Formatting arguments.
*/
void print_message (int fd, const char *fmt, ...)
{
char line[256];
int line_len;
va_list args;
va_start (args, fmt);
line_len = vsnprintf (line, sizeof (line), fmt, args);
if (line_len > (int) sizeof (line)) {
line[255] = '\0';
line_len = 255;
}
if (!file_out) {
printf ("%s", line);
}
else {
write_to_file (fd, line, line_len);
}
va_end (args);
}
/**
* Print a binary array either to the console or to a file.
*
* @param fd The file descriptor for an open file, if the output should be to a file. Ignored if
* not using file output (determined by file_name).
* @param data The buffer that contains the data to print.
* @param start First index of the array to be printed.
* @param end Last index of the array to be printed.
* @param label Label to apply to the array.
* @param tabs The tab depth for the array.
*/
void output_array (int fd, const uint8_t *data, int start, int end, const char *label,
const char *tabs)
{
int i;
print_message (fd, "%s%s (%d):", tabs, label, (end - start) + 1);
for (i = start; i <= end; i++) {
if ((((i - start) % 16) == 0)) {
print_message (fd, "\n%s\t", tabs);
}
print_message (fd, "0x%02x ", data[i]);
}
print_message (fd, "\n");
}
/**
* Print an array of bytes.
*
* @param data The buffer that contains the data to print.
* @param start First index of the array to be printed.
* @param end Last index of the array to be printed.
* @param label Label to apply to the array.
* @param tabs The tab depth for the array.
*/
void print_byte_array (const uint8_t *data, int start, int end, const char *label, const char *tabs)
{
output_array (-1, data, start, end, label, tabs);
}
/**
* Print an a data buffer in hex with address markers.
*
* @param data The data to print.
* @param length Length of the data.
* @param start_offset Starting offset for the data.
*/
void hex_dump (const uint8_t *data, uint32_t length, uint32_t start_offset)
{
const uint8_t *pos = data;
uint32_t offset = start_offset;
uint8_t i;
if (start_offset & 0xf) {
/* Offset is not 16-byte aligned. */
printf ("\n%08x: ", (start_offset & ~0xf));
for (i = 0; i < (start_offset & 0xf); i++) {
printf ("-- ");
}
while ((i < 0x10) && (length > 0)) {
printf ("%02x ", *pos);
i++;
pos++;
offset++;
length--;
}
}
while (length > 0) {
if (!(offset & 0xf)) {
printf ("\n%08x: ", offset);
}
printf ("%02x ", *pos);
pos++;
offset++;
length--;
}
printf ("\n\n");
}
/**
* Execute and SMBus block read command against the target device.
*
* @param cmd The command code to send to the device.
* @param payload Output buffer to the command payload. No SMBus overhead will be returned.
* @param min_length The minimum amount of data that is required from the device. Less than this is
* considered a failure.
* @param length The amount of data to read from the device. It may not all be valid if the device
* doesn't have a full amount of data to send.
*
* @return The number of bytes returned by the device.
*/
uint8_t smbus_block_read (uint8_t cmd, uint8_t *payload, uint8_t min_length, uint8_t length)
{
int smbus_overhead = 1;
uint8_t *rx_smbus;
uint8_t crc;
struct timespec start;
struct timespec end;
if (pec) {
smbus_overhead++;
}
rx_smbus = malloc (length + smbus_overhead);
if (rx_smbus == NULL) {
printf ("Failed to allocate Rx buffer\n");
exit (1);
}
get_current_time (&start);
if (i2c_read (cmd, addr, rx_smbus, length + smbus_overhead) < 0) {
exit (1);
}
get_current_time (&end);
if (verbose >= 1) {
printf ("Read Cmd (%d us): %d\n", get_time_duration (&start, &end), cmd);
if (verbose >= 2) {
print_byte_array (rx_smbus, 0, length + smbus_overhead - 1, "SMBus Rx", "");
printf ("\n");
}
}
if (length < rx_smbus[0]) {
printf ("WARNING: Incomplete block read (%d < %d)\n", length, rx_smbus[0]);
}
else if (min_length > rx_smbus[0]) {
printf ("Invalid response length for command %d. Rx %d bytes.\n", cmd, rx_smbus[0]);
exit (1);
}
length = rx_smbus[0];
memcpy (payload, &rx_smbus[1], length);
if (pec) {
uint8_t crc_addr = addr << 1;
/* Write */
crc = checksum_init_smbus_crc8 (crc_addr);
crc = checksum_update_smbus_crc8 (crc, &cmd, 1);
/* Read */
crc_addr |= 1;
crc = checksum_update_smbus_crc8 (crc, &crc_addr, 1);
crc = checksum_update_smbus_crc8 (crc, rx_smbus, 1 + length);
if (crc != rx_smbus[length + 1]) {
printf ("PEC failed: CRC=0x%x, Rx=%x\n", crc, rx_smbus[length + 1]);
exit (1);
}
}
free (rx_smbus);
return length;
}
/**
* Execute an SMBus block write command against the target device.
*
* @param cmd The command code to send to the device.
* @param payload Payload to send in the command. This must not have any SMBus overhead included.
* @param length Length of the payload.
*/
void smbus_block_write (uint8_t cmd, uint8_t *payload, uint8_t length)
{
int smbus_overhead = 2;
uint8_t *tx_smbus;
uint8_t crc;
struct timespec start;
struct timespec end;
if (pec) {
smbus_overhead++;
}
tx_smbus = malloc (length + smbus_overhead);
if (tx_smbus == NULL) {
printf ("Failed to allocate Tx buffer.\n");
exit (1);
}
tx_smbus[0] = cmd;
tx_smbus[1] = length;
memcpy (&tx_smbus[2], payload, length);
if (pec) {
crc = checksum_init_smbus_crc8 (addr << 1);
crc = checksum_update_smbus_crc8 (crc, tx_smbus, length + 2);
tx_smbus[length + 2] = crc;
if (force_pec_error) {
tx_smbus[length + 2] ^= 0x55;
}
}
get_current_time (&start);
if (i2c_write (addr, tx_smbus, length + smbus_overhead) < 0) {
exit (1);
}
get_current_time (&end);
if (verbose >= 1) {
printf ("Write Cmd (%d us): %d, Length: %d\n", get_time_duration (&start, &end), cmd,
length);
if (verbose >= 2) {
print_byte_array (tx_smbus, 0, length + smbus_overhead - 1, "SMBus Tx", "");
printf ("\n");
}
}
free (tx_smbus);
if (use_write_delay) {
usleep (write_delay);
}
}
/**
* OCP recovery command codes.
*/
enum {
PROT_CAP = 34, /**< Recovery capabilities command */
DEVICE_ID = 35, /**< Device Identifier */
DEVICE_STATUS = 36, /**< Current device status */
RESET = 37, /**< Device reset control */
RECOVERY_CTRL = 38, /**< Recovery image control */
RECOVERY_STATUS = 39, /**< Recovery image status */
HW_STATUS = 40, /**< Hardware status information. */
INDIRECT_CTRL = 41, /**< Control indirect access to memory regions. */
INDIRECT_STATUS = 42, /**< Status of indirect memory access. */
INDIRECT_DATA = 43, /**< Data access to indirect memory regions. */
VENDOR = 44, /**< Vendor-defined command. */
};
/**
* Write a command to the device that has requires no special processing of the input data. The
* entire input stream will be converted to an array of bytes.
*
* raw_data and raw_data_count must be initialized with the required data.
*/
void write_command_byte_array (uint8_t cmd)
{
uint8_t data[MAX_SMBUS_BLOCK_SIZE];
size_t i;
/* Only the LSB of each input word is relevant for this command. */
for (i = 0; i < raw_data_count; i++) {
data[i] = raw_data[i];
}
smbus_block_write (cmd, data, raw_data_count);
}
/**
* Send the PROT_CAP command to the device and parse the response.
*
* @param raw Flag indicating the raw response data should be printed.
*/
void read_prot_cap (bool raw)
{
uint8_t data[15];
char magic[9];
smbus_block_read (PROT_CAP, data, sizeof (data), sizeof (data));
memcpy ((uint8_t*) magic, data, 8);
magic[8] = '\0';
printf ("PROT_CAP:\n");
printf ("\tMagic String: %s\n", magic);
printf ("\tVersion: %d.%d\n", data[8], data[9]);
printf ("\tCapabilities: 0x%04x\n", *((uint16_t*) &data[10]));
printf ("\t\tIdentification: %s\n", (data[10] & (1U << 0)) ? "Yes" : "No");
printf ("\t\tForced Recovery: %s\n", (data[10] & (1U << 1)) ? "Yes" : "No");
printf ("\t\tManagement Reset: %s\n", (data[10] & (1U << 2)) ? "Yes" : "No");
printf ("\t\tDevice Reset: %s\n", (data[10] & (1U << 3)) ? "Yes" : "No");
printf ("\t\tDevice Status: %s\n", (data[10] & (1U << 4)) ? "Yes" : "No");
printf ("\t\tRecovery Memory Access: %s\n", (data[10] & (1U << 5)) ? "Yes" : "No");
printf ("\t\tLocal C-Image: %s\n", (data[10] & (1U << 6)) ? "Yes" : "No");
printf ("\t\tPush C-Image: %s\n", (data[10] & (1U << 7)) ? "Yes" : "No");
printf ("\t\tInterface Isolation: %s\n", (data[11] & (1U << 0)) ? "Yes" : "No");
printf ("\t\tHardware Status: %s\n", (data[11] & (1U << 1)) ? "Yes" : "No");
printf ("\t\tVendor Command: %s\n", (data[11] & (1U << 2)) ? "Yes" : "No");
printf ("\tTotal CMS: %d\n", data[12]);
printf ("\tMax Response Time: %dus\n", 1U << data[13]);
printf ("\tHeartbeat: %dus\n", (data[14] == 0) ? 0 : (1U << data[14]));
printf ("\n");
if (raw) {
print_byte_array (data, 0, 14, "Raw Data", "\t");
printf ("\n");
}
}
/**
* Capability bits indicating support for different commands.
*/
enum {
SUPPORT_DEVICE_ID = (1U << 0), /**< The DEVICE_ID command is supported. */
SUPPORT_RESET = (1U << 1) | (1U << 2) | (1U << 3) | (1U << 8), /**< The RESET command is supported. */
SUPPORT_DEVICE_STATUS = (1U << 4), /**< The DEVICE_STATUS command is supported. */
SUPPORT_INDIRECT = (1U << 5) | (1U << 7), /**< The INDIRECT commands are supported. */
SUPPORT_HW_STATUS = (1U << 9), /**< The HW_STATUS command is supported. */
SUPPORT_VENDOR = (1U << 10), /**< The VENDOR command is supported. */
};
/**
* Retrieve the capabilites bitmask from the device.
*
* @return The device capabilities.
*/
uint16_t get_device_capabilities ()
{
uint8_t data[15];
smbus_block_read (PROT_CAP, data, sizeof (data), sizeof (data));
if (strncmp ("OCP RECV", (char*) data, 8) == 0) {
return (data[11] << 8) | data[10];
}
else {
return 0;
}
}
/**
* Write the PROT_CAP command to the device.
*
* raw_data and raw_data_count must be initialized with the required data.
*/
void write_prot_cap ()
{
/* This is a read-only command, so the contents don't really matter. */
write_command_byte_array (PROT_CAP);
}
/**
* Send the DEVICE_ID command to the device and parse the response.
*
* @param raw Flag indicating the raw response data should be printed.
*/
void read_device_id (bool raw)
{
uint8_t data[MAX_SMBUS_BLOCK_SIZE];
char vendor[232];
int bytes;
bytes = smbus_block_read (DEVICE_ID, data, 24, sizeof (data));
if (data[1] > 231) {
printf ("%s: Response malformed. Vendor string too long (%d)\n", __func__, data[1]);
exit (1);
}
else if (bytes != (24 + data[1])) {
printf ("%s: Invalid response length %d, with vender string length %d.\n", __func__, bytes,
data[1]);
exit (1);
}
memcpy ((uint8_t*) vendor, &data[24], data[1]);
vendor[data[1]] = '\0';
printf ("DEVICE_ID:\n");
switch (data[0]) {
case 0x0:
printf ("\tPCI Vendor:\n");
printf ("\t\tVendor ID: 0x%04x\n", *((uint16_t*) &data[2]));
printf ("\t\tDevice ID: 0x%04x\n", *((uint16_t*) &data[4]));
printf ("\t\tSubsystem Vendor ID: 0x%04x\n", *((uint16_t*) &data[6]));
printf ("\t\tSubsystem Device ID: 0x%04x\n", *((uint16_t*) &data[8]));
printf ("\t\tRevision ID: 0x%02x\n", data[10]);
print_byte_array (data, 11, 23, "Pad", "\t\t");
break;
case 0x1:
printf ("\tIANA:\n");
printf ("\t\tEnterprise ID: 0x%08x\n", *((uint32_t*) &data[2]));
print_byte_array (data, 6, 17, "Product Identifier", "\t\t");
print_byte_array (data, 18, 23, "Pad", "\t\t");
break;
case 0x2:
print_byte_array (data, 2, 17, "UUID", "\t");
print_byte_array (data, 18, 23, "Pad", "\t");
break;
case 0x3:
printf ("\tPnP Vendor:\n");
printf ("\t\tVendor Identifier: 0x%02x%02x%02x\n", data[4], data[3], data[2]);
printf ("\t\tProduct Identifier: 0x%08x\n", *((uint32_t*) &data[5]));
print_byte_array (data, 9, 23, "Pad", "\t\t");
break;
case 0x4:
printf ("\tACPI Vendor:\n");
printf ("\t\tVendor Identifier: 0x%08x\n", *((uint32_t*) &data[2]));
printf ("\t\tProduct Identifier: 0x%02x%02x%02x\n", data[8], data[7], data[6]);
print_byte_array (data, 9, 23, "Pad", "\t\t");
break;
case 0xf:
printf ("\tNVMe-MI:\n");
printf ("\t\tVendor ID: 0x%04x\n", *((uint16_t*) &data[2]));
print_byte_array (data, 4, 23, "Device Serial Number", "\t\t");
break;
default:
printf ("\tReserved: 0x%02x\n", data[0]);
print_byte_array (data, 2, 23, "Unknown ID", "\t\t");
break;
}
if (data[1] != 0) {
printf ("\tVendor String: %s\n", vendor);
}
printf ("\n");
if (raw) {
print_byte_array (data, 0, bytes - 1, "Raw Data", "\t");
printf ("\n");
}
}
/**
* Write the DEVICE_ID command to the device.
*
* raw_data and raw_data_count must be initialized with the required data.
*/
void write_device_id ()
{
/* This is a read-only command, so the contents don't really matter. */
write_command_byte_array (DEVICE_ID);
}
/**
* List of status messages for the device.
*/
const char *DEVICE_STATUS_STR[] = {
[0x0] = "Status Pending",
[0x1] = "Device Healthy",
[0x2] = "Device Error",
[0x3] = "Recovery Mode",
[0x4] = "Recovery Pending",
[0x5] = "Running Recovery Image",
[0x6] = "Reserved",
[0x7] = "Reserved",
[0x8] = "Reserved",
[0x9] = "Reserved",
[0xa] = "Reserved",
[0xb] = "Reserved",
[0xc] = "Reserved",
[0xd] = "Reserved",
[0xe] = "Boot Failure",
[0xf] = "Fatal Error"
};
/**
* List of protocol error messages for the device.
*/
const char *PROTOCOL_ERROR_STR[] = {
[0x0] = "No Error",
[0x1] = "Unsupported Command",
[0x2] = "Unsupported Paramater",
[0x3] = "Length Write Error",
[0x4] = "CRC Error",
[0x5] = "Reserved",
[0x6] = "Reserved",
[0x7] = "Reserved",
[0x8] = "Reserved",
[0x9] = "Reserved",
[0xa] = "Reserved",
[0xb] = "Reserved",
[0xc] = "Reserved",
[0xd] = "Reserved",
[0xe] = "Reserved",
[0xf] = "Reserved",
};
/**
* List of recovery reason codes.
*/
const char *RECOVERY_REASON_STR[] = {
[0x00] = "No boot failure",
[0x01] = "Generic hardware error",
[0x02] = "Generic hardware soft error",
[0x03] = "Self-test failure",
[0x04] = "Missing or corrupt critical data",
[0x05] = "Missing or corrupt key manifest",
[0x06] = "Authentication failure on key manifest",
[0x07] = "Anti-rollback failure on key manifest",
[0x08] = "Missing or corrupt boot loader firmware image",
[0x09] = "Authentication failure on boot loader firmware image",
[0x0a] = "Anti-Rollback failure on boot loader firmware image",
[0x0b] = "Missing or corrupt main firmware image",
[0x0c] = "Authentication failure on main firmware image",
[0x0d] = "Anti-Rollback failure on main firmware image",
[0x0e] = "Missing or corrupt recovery firmware",
[0x0f] = "Authentication failure on recovery firmware",
[0x10] = "Anti-rollback failure on recovery firmware",
[0x11] = "Forced recovery"
};
/**
* Send the DEVICE_STATUS command to the device and parse the response.
*
* @param raw Flag indicating the raw response data should be printed.
*/
void read_device_status (bool raw)
{
uint8_t data[MAX_SMBUS_BLOCK_SIZE];
int bytes;
uint16_t reason;
bytes = smbus_block_read (DEVICE_STATUS, data, 7, sizeof (data));
if (data[6] > 249) {
printf ("%s: Response malformed. Vendor data too long (%d)\n", __func__, data[6]);
exit (1);
}
else if (bytes != (7 + data[6])) {
printf ("%s: Invalid response length %d, with vender length %d.\n", __func__, bytes,
data[1]);
exit (1);
}
reason = *((uint16_t*) &data[2]);
printf ("DEVICE_STATUS:\n");
printf ("\tStatus: 0x%02x%s%s\n", data[0], (data[0] <= 0xf) ? " -> " : "",
(data[0] <= 0xf) ? DEVICE_STATUS_STR[data[0]] : "");
printf ("\tProtocol Error: 0x%02x%s%s\n", data[1], (data[1] <= 0xf) ? " -> " : "",
(data[1] <= 0xf) ? PROTOCOL_ERROR_STR[data[1]] : "");
printf ("\tRecovery Reason Code: 0x%04x%s%s\n", reason, (reason <= 0x11) ? " -> " : "",
(reason <= 0x11) ? RECOVERY_REASON_STR[reason] : "");
printf ("\tHeartbeat: 0x%04x\n", *((uint16_t*) &data[4]));
if (data[6] != 0) {
printf ("\tVendor:\n");
if (data[6] == 5) {
/* Assume a vender message formatted per the Cerberus code. */
printf ("\t\tFailure ID: 0x%02x\n", data[7]);
printf ("\t\tError Code: 0x%08x\n", *((uint32_t*) &data[8]));
}
else {
print_byte_array (data, 7, bytes - 1, "Status", "\t\t");
}
}
printf ("\n");
if (raw) {
print_byte_array (data, 0, bytes - 1, "Raw Data", "\t");
printf ("\n");
}
}
/**
* Write the DEVICE_STATUS command to the device.
*
* raw_data and raw_data_count must be initialized with the required data.
*/
void write_device_status ()
{
/* This is a read-only command, so the contents don't really matter. */
write_command_byte_array (DEVICE_STATUS);
}
/**
* Check the device for any protocol errors.
*/
void check_protocol_error ()
{
uint8_t data[MAX_SMBUS_BLOCK_SIZE];
smbus_block_read (DEVICE_STATUS, data, 7, sizeof (data));
if (data[1] != 0) {
printf ("Protocol Error: 0x%02x%s%s\n", data[1], (data[1] <= 0xf) ? " -> " : "",
(data[1] <= 0xf) ? PROTOCOL_ERROR_STR[data[1]] : "");
exit (1);
}
}
/**
* List of values for device reset control.
*/
const char *RESET_CONTROL_STR[] = {
[0x0] = "No Reset",
[0x1] = "Reset Device",
[0x2] = "Reset Management",
};
/**
* Read the RESET information and parse the response.
*
* @param raw Flag indicating the raw response data should be printed.
*/
void read_reset (bool raw)
{
uint8_t data[3];
smbus_block_read (RESET, data, sizeof (data), sizeof (data));
printf ("RESET:\n");
printf ("\tDevice Reset Control: 0x%02x%s%s\n", data[0], (data[0] <= 2) ? " -> " : "",
(data[0] <= 2) ? RESET_CONTROL_STR[data[0]] : "");
printf ("\tForced Recovery: %s\n",
(data[1] == 0) ? "No" : ((data[1] == 0xf) ? "Yes" : "Invalid"));
printf ("\tInterface Control: %s\n",
(data[2] == 0) ? "Disable Mastering" : ((data[2] == 1) ? "Enable Mastering" : "Invalid"));
printf ("\n");
if (raw) {
print_byte_array (data, 0, 2, "Raw Data", "\t");
printf ("\n");
}
}
/**
* Write the RESET command to the device.
*
* raw_data and raw_data_count must be initialized with the required data.
*/
void write_reset ()
{
/* There are no multi-byte fields, so just covert the entire buffer to bytes. */
write_command_byte_array (RESET);
}
/**
* Send the RESET command to the device.
*
* @param type The type of reset to perform.
*/
void send_reset (uint8_t type)
{
uint8_t data[3];
data[0] = type;
data[1] = (force_recovery) ? 0xf : 0x0;
data[2] = 0;
smbus_block_write (RESET, data, sizeof (data));
}
/**
* List of values for recovery image selection.
*/
const char *RECOVERY_IMAGE_STR[] = {
[0x0] = "No operation",
[0x1] = "Use recovery image from a memory window (CMS)",
[0x2] = "Use recovery image stored on the device (C-Image)",
};
/**
* Read the RECOVERY_CTRL information and parse the response.
*
* @param raw Flag indicating the raw response data should be printed.
*/
void read_recovery_ctrl (bool raw)
{
uint8_t data[3];
smbus_block_read (RECOVERY_CTRL, data, sizeof (data), sizeof (data));
printf ("RECOVERY_CTRL:\n");
printf ("\tComponent Memory Space: 0x%02x\n", data[0]);
printf ("\tRecovery Image Selection: 0x%02x%s%s\n", data[1], (data[1] <= 2) ? " -> " : "",
(data[1] <= 2) ? RECOVERY_IMAGE_STR[data[1]] : "");
printf ("\tActivate Recovery Image: %s\n",
(data[2] == 0) ? "No" : ((data[2] == 0xf) ? "Yes" : "Invalid"));
printf ("\n");
if (raw) {
print_byte_array (data, 0, 2, "Raw Data", "\t");
printf ("\n");
}
}
/**
* Write the RECOVERY_CTRL command to the device.
*
* raw_data and raw_data_count must be initialized with the required data.
*/
void write_recovery_ctrl ()
{
/* There are no multi-byte fields, so just covert the entire buffer to bytes. */
write_command_byte_array (RECOVERY_CTRL);
}
/**
* Send the RECOVERY_CTRL command to configure the recovery CMS.
*
* @param cms The CMS region to enable for recovery
* @param activate Flag indicating if the recovery image should be activated.
*/
void send_recovery_ctrl (uint8_t cms, bool activate)
{
uint8_t data[3];
data[0] = cms;
data[1] = 0x1;
data[2] = (activate) ? 0xf : 0x0;
smbus_block_write (RECOVERY_CTRL, data, sizeof (data));
}
/**
* List of recovery status messages for the device.
*/
const char *RECOVERY_STATUS_STR[] = {
[0x0] = "Not in recovery mode",
[0x1] = "Awaiting recovery image",
[0x2] = "Booting recovery image",
[0x3] = "Recovery successful",
[0x4] = "Reserved",
[0x5] = "Reserved",
[0x6] = "Reserved",
[0x7] = "Reserved",
[0x8] = "Reserved",
[0x9] = "Reserved",
[0xa] = "Reserved",
[0xb] = "Reserved",
[0xc] = "Recovery failed",
[0xd] = "Recovery image authentication error",
[0xe] = "Error entering recovery mode",
[0xf] = "Invalid component address space"
};
/**
* Send the RECOVERY_STATUS command to the device and parse the response.
*
* @param raw Flag indicating the raw response data should be printed.
*/
void read_recovery_status (bool raw)
{
uint8_t data[2];
smbus_block_read (RECOVERY_STATUS, data, sizeof (data), sizeof (data));
printf ("RECOVERY_STATUS:\n");
printf ("\tStatus: 0x%02x%s%s\n", data[0], (data[0] <= 0xf) ? " -> " : "",
(data[0] <= 0xf) ? RECOVERY_STATUS_STR[data[0]] : "");
printf ("\tVendor: 0x%02x\n", data[1]);
printf ("\n");
if (raw) {
print_byte_array (data, 0, 1, "Raw Data", "\t");
printf ("\n");
}
}
/**
* Write the RECOVERY_STATUS command to the device.
*
* raw_data and raw_data_count must be initialized with the required data.
*/
void write_recovery_status ()
{
/* This is a read-only command, so the contents don't really matter. */
write_command_byte_array (RECOVERY_STATUS);
}
/**
* Send an RECOVERY_STATUS command to check the device for any recovery image errors.
*/
void check_recovery_status ()
{
uint8_t data[2];
smbus_block_read (RECOVERY_STATUS, data, sizeof (data), sizeof (data));
if (data[0] == 0xf) {
printf ("Not a valid recovery image region\n");
if (!ignore_errors) {
exit (1);
}
}
}
/**
* Send the HW_STATUS command to the device and parse the response.
*
* @param raw Flag indicating the raw response data should be printed.
*/
void read_hw_status (bool raw)
{
uint8_t data[MAX_SMBUS_BLOCK_SIZE];
int bytes;
uint16_t temp_val;
int temperature;
bytes = smbus_block_read (HW_STATUS, data, 5, sizeof (data));
if (data[4] > 250) {
printf ("%s: Response malformed. Vendor data too long (%d)\n", __func__, data[4]);
exit (1);
}
else if (bytes != (5 + data[4])) {
printf ("%s: Invalid response length %d, with vender length %d.\n", __func__, bytes,
data[1]);
exit (1);
}
temp_val = *((uint16_t*) &data[2]);
if (temp_val < 0x7f) {
temperature = temp_val;
}
else if (temp_val > 0xc4) {
temperature = (int16_t) temp_val;
}
else if ((temp_val == 0x80) || (temp_val == 0x81)) {
temperature = temp_val << 8;
}
printf ("HW_STATUS:\n");
printf ("\tStatus: 0x%02x\n", data[0]);
printf ("\t\tDevice Temperature Is Critical: %s\n", (data[0] & (1U << 0)) ? "Yes" : "No");
printf ("\t\tHardware Soft Error: %s\n", (data[0] & (1U << 1)) ? "Yes" : "No");
printf ("\t\tHardware Fatal Error: %s\n", (data[0] & (1U << 2)) ? "Yes" : "No");
printf ("Vendor Bitmask: 0x%02x\n", data[1]);
if (temperature & 0x8000) {
printf ("Composite Temperature: %s\n",
(temp_val == 0x80) ? "No Data" : ((temp_val == 0x81) ? "Sensor Failure" : "Invalid"));
}
else {
printf ("Composite Temperature: %d\n", temperature);
}
if (data[4] != 0) {
print_byte_array (data, 5, bytes - 1, "Vendor", "\t");
}
printf ("\n");
if (raw) {
print_byte_array (data, 0, bytes - 1, "Raw Data", "\t");
printf ("\n");
}
}
/**
* Write the HM_STATUS command to the device.
*
* raw_data and raw_data_count must be initialized with the required data.
*/
void write_hw_status ()
{
/* This is a read-only command, so the contents don't really matter. */
write_command_byte_array (HW_STATUS);
}
/**
* Read the INDIRECT_CTRL information and parse the response.
*
* @param raw Flag indicating the raw response data should be printed.
*/
void read_indirect_ctrl (bool raw)
{
uint8_t data[6];
smbus_block_read (INDIRECT_CTRL, data, sizeof (data), sizeof (data));
printf ("INDIRECT_CTRL:\n");
printf ("\tComponent Memory Space: 0x%02x\n", data[0]);
printf ("\tReserved: 0x%02x\n", data[1]);
printf ("\tIndirect Memory Offset: 0x%08x\n", *((uint32_t*) &data[2]));
printf ("\n");
if (raw) {
print_byte_array (data, 0, 5, "Raw Data", "\t");
printf ("\n");
}
}
/**
* Write the INDIRECT_CTRL command to the device.
*
* raw_data and raw_data_count must be initialized with the required data.
*/
void write_indirect_ctrl ()
{
uint8_t data[MAX_SMBUS_BLOCK_SIZE];
size_t bytes = 0;
size_t remain = raw_data_count;
if (remain > 0) {
/* Component Memory Space */
data[bytes] = raw_data[bytes];
bytes++;
remain--;
}
if (remain > 0) {
/* Reserved */
data[bytes] = raw_data[bytes];
bytes++;
remain--;
}
if (remain > 0) {
/* Indirect Memory Offset */
*((uint32_t*) &data[bytes]) = raw_data[bytes];
bytes += 4;
remain--;
}
for (; (bytes < 256) && (bytes < remain); bytes++) {
/* Just convert any extra data as a byte array. */
data[bytes] = raw_data[bytes];
}
smbus_block_write (INDIRECT_CTRL, data, bytes);
}
/**
* Send the INDIRECT_CTRL command to configure the current CMS.
*
* @param cms The CMS region to enable.
* @param offset Offset within the region.
*/
void send_indirect_ctrl (uint8_t cms, uint32_t offset)
{
uint8_t data[6];
data[0] = cms;
data[1] = 0;
*((uint32_t*) &data[2]) = offset;
smbus_block_write (INDIRECT_CTRL, data, sizeof (data));
}
/**
* List of types for indirect memory regions.
*/
const char *REGION_TYPE_STR[] = {
[0x0] = "Code space for recovery (RW)",
[0x1] = "Log using the OCP specified format (RO)",
[0x2] = "Reserved",
[0x3] = "Reserved",
[0x4] = "Reserved",
[0x5] = "Vendor defined region (RW)",
[0x6] = "Vendor defined region (RO)",
[0x7] = "Unsupported region",
[0x8] = "Code space for recovery (RW), requires polling",
[0x9] = "Log using the OCP specified format (RO), requires polling",
[0xa] = "Reserved",
[0xb] = "Reserved",
[0xc] = "Reserved",
[0xd] = "Vendor defined region (RW), requires polling",
[0xe] = "Vendor defined region (RO), requires polling",
[0xf] = "Unsupported region"
};
/**
* Send the INDIRECT_STATUS command to the device and parse the response.
*
* @param raw Flag indicating the raw response data should be printed.
*/
void read_indirect_status (bool raw)
{
uint8_t data[6];
smbus_block_read (INDIRECT_STATUS, data, sizeof (data), sizeof (data));
printf ("INDIRECT_STATUS:\n");
printf ("\tStatus: 0x%02x\n", data[0]);
printf ("\t\tOverflow CMS, Wrapped: %s\n", (data[0] & (1U << 0)) ? "Yes" : "No");
printf ("\t\tRead Only Error: %s\n", (data[0] & (1U << 1)) ? "Yes" : "No");
printf ("\t\tPolling CMS ACK: %s\n", (data[0] & (1U << 2)) ? "Yes" : "No");
printf ("\tRegion Type: 0x%02x%s%s\n", data[1], (data[1] <= 0xf) ? " -> " : "",
(data[1] <= 0xf) ? REGION_TYPE_STR[data[1]] : "");
printf ("\tRegion Size: 0x%08x -> 0x%x bytes\n", *((uint32_t*) &data[2]),
*((uint32_t*) &data[2]) << 2);
printf ("\n");
if (raw) {
print_byte_array (data, 0, 5, "Raw Data", "\t");
printf ("\n");
}
}
/**
* Write the INDIRECT_STATUS command to the device.
*
* raw_data and raw_data_count must be initialized with the required data.
*/
void write_indirect_status ()
{
/* This is a read-only command, so the contents don't really matter. */
write_command_byte_array (INDIRECT_STATUS);
}
/**
* Send an INDIRECT_STATUS command to check the device for any indirect access errors and confirm
* the expected region type.
*
* @param region_type An option region type code to compare against the active CMS. If this is
* negative, type checking will be skipped, but an unsupported region will still trigger an error.
* @param fail_wrap Flag indicating the program should fail if a CMS wrap is detected.
*
* @return true if the region is the expected type or type checking was skipped.
*/
bool check_indirect_status (int region_type, bool fail_wrap)
{
uint8_t data[6];
smbus_block_read (INDIRECT_STATUS, data, sizeof (data), sizeof (data));
if (fail_wrap && (data[0] & (1U << 0))) {
printf ("Overflow memory region\n");
exit (1);
}
if (data[0] & (1U << 1)) {
printf ("Write to RO CMS\n");
exit (1);
}
if ((data[1] & 7) == 0x7) {
printf ("Unsupported CMS\n");
if (!ignore_errors) {
exit (1);
}
}
return ((data[1] & 0xf) == region_type);
}
/**
* Poll the INDIRECT_STATUS command waiting for the device to ACK.
*/
void wait_for_cms_ack ()
{
uint8_t data[6];
do {
smbus_block_read (INDIRECT_STATUS, data, sizeof (data), sizeof (data));
} while (!(data[0] & (1U << 2)));
}
/**
* Query INDIRECT_STATUS to get the total size of the active CMS.
*
* @return The region size, reported in 4-byte units.
*/
uint32_t get_indirect_size ()
{
uint8_t data[6];
smbus_block_read (INDIRECT_STATUS, data, sizeof (data), sizeof (data));
return *((uint32_t*) &data[2]);
}
/**
* Read INDIRECT_DATA and parse the response.
*/
void read_indirect_data ()
{
uint8_t data[MAX_SMBUS_BLOCK_SIZE];
int bytes;
bytes = smbus_block_read (INDIRECT_DATA, data, 0, sizeof (data));
print_byte_array (data, 0, bytes - 1, "INDIRECT_DATA", "");
printf ("\n");
}
/**
* Write the INDIRECT_DATA command to the device.
*
* raw_data and raw_data_count must be initialized with the required data.
*/
void write_indirect_data ()
{
/* This command just sends an array of bytes. */
write_command_byte_array (INDIRECT_DATA);
}
/**
* Read the entire contents of a single CMS.
*
* @param cms The CMS to should be read.
* @param offset An offset within the CMS to start reading.
* @param file_out Output file for the data. Null when using a memory buffer.
* @param data_out Output for a memory buffer with the data. Null when using a file.
*
* @return The number of bytes read from the CMS.
*/
uint32_t dump_cms_data (uint8_t cms, uint32_t offset, const char *file_out, uint8_t **data_out)
{
uint32_t cms_size;
uint32_t length;
uint8_t *data;
int fd;
uint32_t pos;
uint8_t cmd_data[MAX_CMS_BLOCK_SIZE];
send_indirect_ctrl (cms, offset);
check_protocol_error ();
check_indirect_status (-1, false);
cms_size = get_indirect_size ();
cms_size <<= 2;
if (offset < cms_size) {
length = cms_size - ((offset + 3) & ~0x3);
}
else {
printf ("WARNING: Offset beyond end of CMS %d with size %u\n", cms, cms_size);
length = cms_size;
}
if (!file_out) {
data = malloc (length);
if (data == NULL) {
printf ("Failed to allocate buffer for data\n");
exit (1);
}
}
else {
fd = open (file_out, O_RDWR | O_CREAT,
S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH);
if (fd < 0) {
printf ("Failed to open output file %s: %s\n", file_out, strerror (errno));
exit (1);
}
}
pos = 0;
while (pos != length) {
int bytes = smbus_block_read (INDIRECT_DATA, cmd_data, 0, sizeof (cmd_data));
if (bytes == 0) {
check_protocol_error ();
check_indirect_status (-1, true);
printf ("No data received from CMS %d at 0x%x\n", cms, offset + pos);
exit (1);
}
if (!file_out) {
memcpy (&data[pos], cmd_data, bytes);
}
else {
if (write (fd, cmd_data, bytes) < 0) {
printf ("Failed to write to output file %s: %s\n", file_out, strerror (errno));
exit (1);
}
}
pos += bytes;
}
if (file_out) {
close (fd);
}
else {
*data_out = data;
}
return length;
}
#pragma pack(push, 1)
/**
* OCP Recovery log header format.
*/
struct ocp_log_entry_header {
uint16_t log_magic; /**< Start of entry marker. This is 0xe5e5. */
uint16_t length; /**< Total length of the entry. */
uint32_t entry_id; /**< Unique entry identifier. */
uint16_t format; /**< Format of the message body. */
};
#pragma pack(pop)
/**
* Parse a device log formatted per the OCP Recovery spec.
*
* @param data The log data.
* @param length The amount of log data.
*/
void parse_ocp_log (uint8_t *data, uint32_t length)
{
struct ocp_log_entry_header *header;
int fd = -1;
if (file_name) {
fd = open (file_name, O_RDWR | O_CREAT,
S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH);
if (fd < 0) {
printf ("Failed to open output file %s: %s\n", file_name, strerror (errno));
exit (1);
}
}
while (length >= sizeof (struct ocp_log_entry_header)) {
header = (struct ocp_log_entry_header*) data;
data += sizeof (header);
length -= sizeof (header);
if (header->log_magic != 0xe5e5) {
print_message (fd, "Invalid log entry marker 0x%04x\n", header->log_magic);
exit (1);
}
if (length < header->length) {
print_message (fd, "Malformed message length 0x%04x, remaining 0x%04x\n",
header->length, length);
exit (1);
}
print_message (fd, "Entry: 0x%08x\n", header->entry_id);
print_message (fd, "\tFormat: 0x%02x\n", header->format);
output_array (fd, data, 0, header->length - 1, "Msg Body", "\t");
data += header->length;
length -= header->length;
}
if (length != 0) {
print_message (fd, "Extra %u bytes of data at end of log\n", length);
}
if (file_name) {
close (fd);
}
}
/**
* Parse a device log using Cerberus log formatting.
*
* @param data The log data.
* @param length The amount of log data.
*/
void parse_cerberus_log (uint8_t *data, uint32_t length)
{
struct debug_log_entry *msg;
int fd = -1;
if (file_name) {
fd = open (file_name, O_RDWR | O_CREAT,
S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH);
if (fd < 0) {
printf ("Failed to open output file %s: %s\n", file_name, strerror (errno));
exit (1);
}
}
while (length >= sizeof (struct debug_log_entry)) {
msg = (struct debug_log_entry*) data;
if (!LOGGING_IS_ENTRY_START (msg->header.log_magic)) {
print_message (fd, "Invalid log entry marker 0x%02x\n", msg->header.log_magic);
exit (1);
}
if (length < msg->header.length) {
print_message (fd, "Malformed message length 0x%04x, remaining 0x%04x\n",
msg->header.length, length);
exit (1);
}
print_message (fd, "Entry: 0x%08x\n", msg->header.entry_id);
print_message (fd, "\tHeader Format: 0x%02x\n", msg->header.log_magic);
print_message (fd, "\tEntry Format: 0x%02x\n", msg->entry.format);
print_message (fd, "\tSeverity: 0x%02x\n", msg->entry.severity);
print_message (fd, "\tComponent: 0x%02x\n", msg->entry.component);
print_message (fd, "\tMessage ID: 0x%02x\n", msg->entry.msg_index);
print_message (fd, "\tArg1: 0x%08x\n", msg->entry.arg1);
print_message (fd, "\tArg2: 0x%08x\n", msg->entry.arg2);
print_message (fd, "\tTimestamp: 0x%llx\n", msg->entry.time);
if (msg->header.length > sizeof (struct debug_log_entry)) {
output_array (fd, data, sizeof (struct debug_log_entry), msg->header.length - 1,
"Unknown Data", "\t");
}
data += msg->header.length;
length -= msg->header.length;
}
if (length != 0) {
print_message (fd, "Extra %u bytes of data at end of log\n", length);
}
if (file_name) {
close (fd);
}
}
/**
* Read the VENDOR information and parse the response.
*/
void read_vendor ()
{
uint8_t data[MAX_SMBUS_BLOCK_SIZE];
int bytes;
bytes = smbus_block_read (VENDOR, data, 0, sizeof (data));
print_byte_array (data, 0, bytes - 1, "VENDOR", "");
printf ("\n");
}
/**
* Write the VENDOR command to the device.
*
* raw_data and raw_data_count must be initialized with the required data.
*/
void write_vendor ()
{
/* The format of this command is unknown. Treat it as an array of bytes. */
write_command_byte_array (VENDOR);
}
/**
* Execute the 'load_img' command that sends a recovery image to the device.
*/
void command_load_image ()
{
struct stat stat;
int fd;
bool valid;
uint32_t max_length;
uint8_t data[MAX_CMS_BLOCK_SIZE];
int bytes;
fd = open (file_name, O_RDONLY);
if (fd < 0) {
printf ("Failed to open input file %s: %s\n", file_name, strerror (errno));
exit (1);
}
if (fstat (fd, &stat) < 0) {
printf ("Failed to check size of input file %s: %s\n", file_name, strerror (errno));
exit (1);
}
send_indirect_ctrl (cms_id, cms_offset);
check_protocol_error ();
valid = check_indirect_status (0, false);
if (!valid) {
printf ("CMS %d is not a code memory region\n", cms_id);
if (!ignore_errors) {
exit (1);
}
}
max_length = get_indirect_size ();
if ((uint32_t) ((stat.st_size + 3) / 4) > max_length) {
printf ("CMS %d is not large enough for the image: CMS=%u, file=%u\n", cms_id, max_length,
(uint32_t) ((stat.st_size + 3) / 4));
if (!ignore_errors) {
exit (1);
}
}
do {
bytes = read (fd, data, cms_block_write);
if (bytes > 0) {
smbus_block_write (INDIRECT_DATA, data, bytes);
}
} while (bytes > 0);
if (bytes < 0) {
printf ("Failed to read data from input file %s: %s\n", file_name, strerror (errno));
exit (1);
}
check_protocol_error ();
check_indirect_status (-1, false);
close (fd);
}
/**
* Execute the 'verify_img' command that confirms the expected data is in device memory.
*/
void command_verify_image ()
{
struct stat stat;
int fd;
uint32_t max_length;
uint8_t img_data[MAX_CMS_BLOCK_SIZE];
uint8_t device_data[MAX_CMS_BLOCK_SIZE];
int img_bytes;
int device_bytes;
fd = open (file_name, O_RDONLY);
if (fd < 0) {
printf ("Failed to open input file %s: %s\n", file_name, strerror (errno));
exit (1);
}
if (fstat (fd, &stat) < 0) {
printf ("Failed to check size of input file %s: %s\n", file_name, strerror (errno));
exit (1);
}
send_indirect_ctrl (cms_id, cms_offset);
check_protocol_error ();
check_indirect_status (-1, false);
max_length = get_indirect_size ();
if ((uint32_t) ((stat.st_size + 3) / 4) > max_length) {
printf ("CMS %d is not large enough for the image: CMS=%u, file=%u\n", cms_id, max_length,
(uint32_t) ((stat.st_size + 3) / 4));
if (!ignore_errors) {
exit (1);
}
}
do {
img_bytes = read (fd, img_data, MAX_CMS_BLOCK_SIZE);
if (img_bytes > 0) {
device_bytes = smbus_block_read (INDIRECT_DATA, device_data, 0, MAX_CMS_BLOCK_SIZE);
if (device_bytes < img_bytes) {
printf ("Data length mismatch\n");
exit (1);
}
if (memcmp (img_data, device_data, img_bytes) != 0) {
printf ("Device memory does not match file data\n");
exit (1);
}
}
} while (img_bytes > 0);
if (img_bytes < 0) {
printf ("Failed to read data from input file %s: %s\n", file_name, strerror (errno));
exit (1);
}
check_protocol_error ();
check_indirect_status (-1, true);
close (fd);
}
/**
* Execute the 'activate_img' command that instructs the device to activate a previously loaded
* recovery image.
*/
void command_activate_image ()
{
send_recovery_ctrl (cms_id, false);
check_protocol_error ();
check_recovery_status ();
send_recovery_ctrl (cms_id, true);
}
/**
* Execute the 'recover' command that sends a recovery image to the device and activates it.
*/
void command_recover ()
{
command_load_image ();
command_activate_image ();
}
/**
* Execute the 'reset_device' command that issues a device reset.
*/
void command_reset_device ()
{
send_reset (1);
}
/**
* Execute the 'reset_management' command that issues a reset of the device management subsystem.
*/
void command_reset_management ()
{
send_reset (2);
}
/**
* Execute the 'read_data' command that reads the entire contents of a single CMS.
*/
void command_read_data ()
{
uint8_t *data;
uint32_t length;
uint32_t offset = cms_offset;
if (offset & 0x3) {
printf ("NOTE: Aligning offset to a 4-byte boundary\n");
offset &= ~0x3;
}
length = dump_cms_data (cms_id, offset, file_name, &data);
if (!file_name) {
hex_dump (data, length, offset);
free (data);
}
}
/**
* Execute the 'read_log' command that reads and parses the contents of a log CMS. If the region
* type does not indicate the standard log format, it will be parsed as a Cerberus log if the
* command argument was provided to enable this action. Otherwise, the region will not be read.
*/
void command_read_log ()
{
uint8_t *data;
uint32_t length;
bool is_log = false;
bool is_cerberus = false;
send_indirect_ctrl (cms_id, cms_offset);
is_log = check_indirect_status (0x1, false);
if (!is_log && is_cerberus_log) {
is_cerberus = check_indirect_status (0x6, false);
}
if (!is_log && !is_cerberus) {
printf ("CMS %d does not contain log data\n", cms_id);
exit (1);
}
length = dump_cms_data (cms_id, cms_offset, NULL, &data);
if (is_log) {
parse_ocp_log (data, length);
}
else {
parse_cerberus_log (data, length);
}
free (data);
}
/**
* Execute the 'show_all' command to read all device commands and display the results.
*/
void command_show_all ()
{
uint16_t capabilities = get_device_capabilities ();
read_prot_cap (raw_bytes);
if (capabilities & SUPPORT_DEVICE_ID) {
read_device_id (raw_bytes);
}
if (capabilities & SUPPORT_DEVICE_STATUS) {
read_device_status (raw_bytes);
}
if (capabilities & SUPPORT_RESET) {
read_reset (raw_bytes);
}
read_recovery_ctrl (raw_bytes);
read_recovery_status (raw_bytes);
if (capabilities & SUPPORT_HW_STATUS) {
read_hw_status (raw_bytes);
}
if (capabilities & SUPPORT_INDIRECT) {
read_indirect_ctrl (raw_bytes);
read_indirect_status (raw_bytes);
read_indirect_data ();
}
if (capabilities & SUPPORT_VENDOR) {
read_vendor ();
}
}
/**
* Print the application usage.
*/
void print_usage ()
{
printf ("Usage: ocp_recovery -d <num> [OPTIONS] COMMAND [ARGS]\n");
}
/**
* Print the detailed help for the command.
*/
void print_help ()
{
printf ("This tool provides a way to communicate with and test devices that implement the\n");
printf ("firmware recovery protocol specified by the OCP Security workgroup. Details\n");
printf ("about the protocol can be found at the OCP Security wiki.\n");
printf ("\n");
printf ("https://www.opencompute.org/wiki/Security\n");
printf ("\n\n");
print_usage ();
printf ("\n");
printf ("OPTIONS\n");
printf (
" -a <hex> : The 7-bit I2C address, in hex. This follows the spec and defaults to 0x69.\n");
printf (" -b : Show raw response bytes in addition to parsed data.\n");
printf (" -c <num> : The CMS to use for the operation. Defaults to 0.\n");
printf (" -d <num> : The I2C device number. This is required.\n");
printf (" -e : Force a PEC error on a raw write command.\n");
printf (" -f : Ignore failed error checks during operation validation.\n");
printf (" -l : Indicate a vendor RO CMS region uses Cerberus logging format.\n");
printf (" -o <hex> : The offset in a CMS to start reading or writing. Defaults to 0.\n");
printf (" -p : Disable PEC bytes on block reads and writes.\n");
printf (" -r : Force the device into recovery mode during reset commands.\n");
printf (
" -R : Maximum number of bytes to read from a CMS in each command. Defaults to 252 bytes.\n");
printf (" -s : Add a delay after every write transaction.\n");
printf (
" -S : Specify the amount of time, in usec, to delay after write transactions. Defaults to 1000.\n");
printf (
" -v : Verbose output for command processing. Specify multiple times to increase.\n");
printf (" -w : Execute a raw write transaction. Default is to execute a read.\n");
printf (
" -W : Maximum number of bytes to write to a CMS in each command. Defaults to 252 bytes.\n");
printf (" -h : Displays the help menu.\n");
printf ("\n");
printf ("COMMANDS\n");
printf (" recover <file> : Load a binary image into the device and activate it.\n");
printf (" load_img <file> : Write a binary file to device memory.\n");
printf (" verify_img <file> : Read CMS data and compare it to a specified file.\n");
printf (" activate_img : Activate an image loaded into device memory.\n");
printf (
" read_log [file] : Read and parse contents of a CMS log. Optionally output to a file.\n");
printf (" read_data [file] : Raw CMS data read. Optionally output to a file.\n");
printf (" reset_device : Issue a device reset.\n");
printf (" reset_mgmt : Issue a management reset for the device.\n");
printf (
" show_all : Send a read request for every command supported by the device.\n");
printf ("\n");
printf ("RAW COMMANDS\n");
printf (" prot_cap : The PROT_CAP command.\n");
printf (" device_id : The DEVICE_ID command.\n");
printf (" device_status : The DEVICE_STATUS command.\n");
printf (" reset : The RESET command.\n");
printf (" recovery_ctrl : The RECOVERY_CTRL command.\n");
printf (" recovery_status : The RECOVERY_STATUS command.\n");
printf (" hw_status : The HW_STATUS command.\n");
printf (" indirect_ctrl : The INDIRECT_CTRL command.\n");
printf (" indirect_status : The INDIRECT_STATUS command.\n");
printf (" indirect_data : The INDIRECT_DATA command.\n");
printf (" vendor : The VENDOR command.\n");
printf ("\n");
printf (" Raw commands give direct access to the associated OCP command. On write\n");
printf (" requests, a series of hex values must be provided, one for each field of the\n");
printf (" command. For indirect_data, it would take a list of bytes to write.\n");
printf (" Examples:\n");
printf (" reset 0x02 0x0f 0x00\n");
printf (" indirect_ctrl 0x01 0x00 0x1234\n");
printf (" indirect_data 0x00 0x01 0x02 0x03\n");
printf ("\n");
printf (" Raw commands do not use the CMS arguments provided for the normal commands.\n");
printf (" This means that INDIRECT or RECOVERY commands will not recognize the CMS or\n");
printf (" offset arguments provided. There is also no checking or protection against\n");
printf (" executing unsupported actions, such as writing to read only commands and\n");
printf (" regions.\n");
}
/**
* Determine if the current command is a raw command.
*
* @return true if the command is a raw command.
*/
bool is_raw_command ()
{
if (strcmp ("prot_cap", command) == 0) {
return true;
}
else if (strcmp ("device_id", command) == 0) {
return true;
}
else if (strcmp ("device_status", command) == 0) {
return true;
}
else if (strcmp ("reset", command) == 0) {
return true;
}
else if (strcmp ("recovery_ctrl", command) == 0) {
return true;
}
else if (strcmp ("recovery_status", command) == 0) {
return true;
}
else if (strcmp ("hw_status", command) == 0) {
return true;
}
else if (strcmp ("indirect_ctrl", command) == 0) {
return true;
}
else if (strcmp ("indirect_status", command) == 0) {
return true;
}
else if (strcmp ("indirect_data", command) == 0) {
return true;
}
else if (strcmp ("vendor", command) == 0) {
return true;
}
return false;
}
/**
* Entry point for the OCP recovery test application.
*
* @param argc Number of arguments provided to the application.
* @param argv Argument list.
*
* @return 0 on success or 1 on failure.
*/
int main (int argc, char *argv[])
{
const char *opts = "a:bc:d:efhlo:prsS:vwW:";
int opt;
int device_num = -1;
while ((opt = getopt (argc, argv, opts)) != -1) {
switch (opt) {
case 'a':
addr = strtol (optarg, NULL, 16);
break;
case 'b':
raw_bytes = true;
break;
case 'c':
cms_id = strtoul (optarg, NULL, 10);
break;
case 'd':
device_num = strtol (optarg, NULL, 10);
break;
case 'e':
force_pec_error = true;
break;
case 'f':
ignore_errors = true;
break;
case 'l':
is_cerberus_log = true;
break;
case 'o':
cms_offset = strtoul (optarg, NULL, 16);
break;
case 'p':
pec = false;
break;
case 'r':
force_recovery = true;
break;
case 's':
use_write_delay = true;
break;
case 'S':
write_delay = strtoul (optarg, NULL, 10);
break;
case 'v':
verbose++;
break;
case 'w':
is_read = false;
break;
case 'W':
cms_block_write = strtoul (optarg, NULL, 10);
break;
case 'h':
print_help ();
return 0;
}
}
if (device_num < 0) {
printf ("No I2C device specified.\n\n");
print_usage ();
return 1;
}
if (cms_block_write > MAX_CMS_BLOCK_SIZE) {
printf ("Invalid CMS block write value.\n\n");
print_usage ();
return 1;
}
if (optind >= argc) {
print_usage ();
return 1;
}
command = argv[optind++];
if ((strcmp ("recover", command) == 0) || (strcmp ("load_img", command) == 0) ||
(strcmp ("verify_img", command) == 0)) {
if (optind >= argc) {
printf ("A file must be provided for this command.\n");
return 1;
}
file_name = argv[optind++];
}
else if ((strcmp ("read_log", command) == 0) || (strcmp ("read_data", command) == 0)) {
if (optind < argc) {
file_name = argv[optind++];
file_out = true;
}
}
else if (is_raw_command ()) {
if (optind < argc) {
size_t i;
raw_data_count = argc - optind;
for (i = 0; i < raw_data_count; i++, optind++) {
raw_data[i] = strtoul (argv[optind], NULL, 16);
}
}
else if (!is_read) {
printf ("No data provided for the command.\n");
return 1;
}
}
i2c = i2c_init (device_num);
if (i2c < 0) {
return 1;
}
if (strcmp ("recover", command) == 0) {
command_recover ();
}
else if (strcmp ("load_img", command) == 0) {
command_load_image ();
}
else if (strcmp ("verify_img", command) == 0) {
command_verify_image ();
}
else if (strcmp ("activate_img", command) == 0) {
command_activate_image ();
}
else if (strcmp ("read_log", command) == 0) {
command_read_log ();
}
else if (strcmp ("read_data", command) == 0) {
command_read_data ();
}
else if (strcmp ("reset_device", command) == 0) {
command_reset_device ();
}
else if (strcmp ("reset_mgmt", command) == 0) {
command_reset_management ();
}
else if (strcmp ("show_all", command) == 0) {
command_show_all ();
}
else if (strcmp ("prot_cap", command) == 0) {
if (is_read) {
read_prot_cap (raw_bytes);
}
else {
write_prot_cap ();
}
}
else if (strcmp ("device_id", command) == 0) {
if (is_read) {
read_device_id (raw_bytes);
}
else {
write_device_id ();
}
}
else if (strcmp ("device_status", command) == 0) {
if (is_read) {
read_device_status (raw_bytes);
}
else {
write_device_status ();
}
}
else if (strcmp ("reset", command) == 0) {
if (is_read) {
read_reset (raw_bytes);
}
else {
write_reset (raw_bytes);
}
}
else if (strcmp ("recovery_ctrl", command) == 0) {
if (is_read) {
read_recovery_ctrl (raw_bytes);
}
else {
write_recovery_ctrl ();
}
}
else if (strcmp ("recovery_status", command) == 0) {
if (is_read) {
read_recovery_status (raw_bytes);
}
else {
write_recovery_status ();
}
}
else if (strcmp ("hw_status", command) == 0) {
if (is_read) {
read_hw_status (raw_bytes);
}
else {
write_hw_status ();
}
}
else if (strcmp ("indirect_ctrl", command) == 0) {
if (is_read) {
read_indirect_ctrl (raw_bytes);
}
else {
write_indirect_ctrl ();
}
}
else if (strcmp ("indirect_status", command) == 0) {
if (is_read) {
read_indirect_status (raw_bytes);
}
else {
write_indirect_status ();
}
}
else if (strcmp ("indirect_data", command) == 0) {
if (is_read) {
read_indirect_data ();
}
else {
write_indirect_data ();
}
}
else if (strcmp ("vendor", command) == 0) {
if (is_read) {
read_vendor ();
}
else {
write_vendor ();
}
}
else {
printf ("Uknown command.\n");
return 1;
}
return 0;
}