Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions rpierrorlog/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
cmake_minimum_required(VERSION 3.10...3.27)
include(GNUInstallDirs)

set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall -Wextra -Werror")

project(rpierrorlog)

option(BUILD_SHARED_LIBS "Build using shared libraries" ON)

add_library(rpierrorlog rpierrorlog.c)
target_sources(rpierrorlog PUBLIC rpierrorlog.h)
set_target_properties(rpierrorlog PROPERTIES PUBLIC_HEADER rpierrorlog.h)
set_target_properties(rpierrorlog PROPERTIES SOVERSION 0)

add_executable(rpi-error-log main.c)
target_link_libraries(rpi-error-log rpierrorlog)

install(TARGETS rpi-error-log RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR})
install(TARGETS rpierrorlog
ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}
PUBLIC_HEADER DESTINATION ${CMAKE_INSTALL_INCLUDEDIR})
25 changes: 25 additions & 0 deletions rpierrorlog/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@

# rpierrorlog
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please can you also update the top level readme with a suitable description


The Raspberry Pi Error Log service is a mailbox based API for reading
and clearing error logs stored in SPI EEPROM. These logs are written
by the firmware when a fatal failure occurs and persist across reboots.

Although this service can be used via raw vcmailbox commands the
recommended API is either the command line rpi-error-log application
or the librpierrorlog.so shared library.

**Build Instructions**
Install prerequisites with "sudo apt install cmake" - you need at least version 3.10.

- *mkdir build*
- *cd build*
- *cmake ..*
- *make*
- *sudo make install*

**Usage**

* rpi-error-log (Shows usage information)
* rpi-error-log get (Read and print all EEPROM error log entries - Pi 4 and Pi 5 family boards only)
* rpi-error-log clear (Clear the EEPROM error log and verify it is empty - Pi 5 family boards only)
91 changes: 91 additions & 0 deletions rpierrorlog/main.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
#include <stdio.h>
#include <string.h>
#include "rpierrorlog.h"

static void usage(const char *progname)
{
fprintf(stderr,
"Usage: %s <command>\n"
"\n"
"Commands:\n"
" get Read and print all EEPROM error log entries\n"
" (Pi 4 and Pi 5 family boards only)\n"
" clear Clear the EEPROM error log and verify it is empty\n"
" (Pi 5 family boards only)\n",
progname);
}

static int cmd_get(void)
{
rpi_error_log_t entries[RPI_ERROR_LOG_MAX_SIZE / RPI_ERROR_LOG_ENTRY_SIZE];
size_t count;

if (rpi_error_log_get(entries, sizeof(entries) / sizeof(entries[0]), &count) < 0) {
fprintf(stderr, "Failed to read error log\n");
return -1;
}

if (count == 0) {
printf("No error log entries\n");
return 0;
}

for (size_t i = 0; i < count; i++)
printf("Entry %zu: 0x%08x - %s\n", i, entries[i].error_code, rpi_error_log_strerror(entries[i].error_code));

return 0;
}

static int cmd_clear(void)
{
rpi_error_log_t entries[RPI_ERROR_LOG_MAX_SIZE / RPI_ERROR_LOG_ENTRY_SIZE];
size_t count;

if (rpi_error_log_clear() < 0) {
fprintf(stderr, "Failed to clear error log\n");
return -1;
}
printf("Error log cleared\n");

if (rpi_error_log_get(entries, sizeof(entries) / sizeof(entries[0]), &count) < 0) {
fprintf(stderr, "Verification read failed\n");
return -1;
}
if (count != 0) {
fprintf(stderr, "Verification failed: %zu entries still present after clear\n", count);
return -1;
}
printf("Verification OK: error log is empty\n");
return 0;
}

int main(int argc, char *argv[])
{
int rc = -1;

if (argc != 2) {
usage(argv[0]);
return 1;
}

if (strcmp(argv[1], "get") == 0) {
rc = cmd_get();
if (rc < 0)
goto error;
return 0;
}

if (strcmp(argv[1], "clear") == 0) {
rc = cmd_clear();
if (rc < 0)
goto error;
return 0;
}

usage(argv[0]);
return 1;

error:
fprintf(stderr, "Command '%s' failed\n", argv[1]);
return rc;
}
220 changes: 220 additions & 0 deletions rpierrorlog/rpierrorlog.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,220 @@
#include <stdio.h>
#include <stdint.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <errno.h>
#include "rpierrorlog.h"

#define DEVICE_FILE_NAME "/dev/vcio"
#define MAJOR_NUM 100
#define IOCTL_MBOX_PROPERTY _IOWR(MAJOR_NUM, 0, char *)

#if 0
#define LOG_DEBUG(...) do { \
fprintf(stderr, __VA_ARGS__); \
} while (0)
#else
#define LOG_DEBUG(...) do { } while (0)
#endif

/* VideoCore mailbox error flag */
#define VC_MAILBOX_ERROR 0x80000000u

/* Error log mailbox tags */
#define TAG_GET_EEPROM_ERROR_LOGS 0x0003009au /* Read error log entries from EEPROM */
#define TAG_SET_EEPROM_ERROR_LOGS 0x0003809au /* Clear error log in EEPROM */

/* Common header for all firmware mailbox messages */
struct firmware_msg_header {
uint32_t buf_size;
uint32_t code;
uint32_t tag;
uint32_t tag_buf_size;
uint32_t tag_req_resp_size;
};

/* Mailbox message for TAG_GET_EEPROM_ERROR_LOGS.
*
* Request: reserved (ignored, 0) + length (max bytes to receive)
* Response: status (VC_MAILBOX_ERROR on failure) + length (bytes written) + data
*/
struct firmware_error_log_msg {
struct firmware_msg_header hdr;
union {
struct {
uint32_t reserved;
uint32_t length;
} req;
struct {
uint32_t status;
uint32_t length;
uint8_t data[RPI_ERROR_LOG_MAX_SIZE];
} resp;
};
uint32_t end_tag;
};

/* Mailbox message for TAG_SET_EEPROM_ERROR_LOGS.
*
* Request: reserved[2] (ignored)
* Response: status (VC_MAILBOX_ERROR on failure) + reserved
*/
struct firmware_error_log_clear_msg {
struct firmware_msg_header hdr;
union {
struct {
uint32_t reserved[2];
} req;
struct {
uint32_t status;
uint32_t reserved;
} resp;
};
uint32_t end_tag;
};

static int mbox_open(void)
{
int fd = open(DEVICE_FILE_NAME, O_RDWR);
if (fd < 0)
fprintf(stderr, "Failed to open %s: %s\n", DEVICE_FILE_NAME, strerror(errno));
return fd;
}

static void mbox_close(int fd)
{
close(fd);
}

static int mbox_property(int fd, void *msg)
{
struct firmware_msg_header *hdr = (struct firmware_msg_header *)msg;
int rc = ioctl(fd, IOCTL_MBOX_PROPERTY, msg);
if (rc < 0)
fprintf(stderr, "ioctl_mbox_property failed: %d\n", rc);

LOG_DEBUG("hdr.code=0x%08x tag=0x%08x tag_buf_size=%u tag_req_resp_size=0x%08x\n",
hdr->code, hdr->tag, hdr->tag_buf_size, hdr->tag_req_resp_size);

if (!(hdr->code & VC_MAILBOX_ERROR) ||
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've updated rpi-fw-crypto to make this less confusing because bit31 doesn't mean ERROR error. It means tag completed.

!(hdr->tag_req_resp_size & VC_MAILBOX_ERROR))
return -1;

return 0;
}

const char *rpi_error_log_strerror(uint32_t error_code)
{
switch (error_code) {
case 0x03: return "Generic boot failure";
case 0x04: return "Firmware (start*.elf) not found";
case 0x07: return "Kernel or device-tree not found or is not compatible";
case 0x08: return "SDRAM failure";
case 0x09: return "SDRAM mismatch";
case 0x0a: return "Halting";
case 0x11: return "Operation requires USB high current limit";
case 0x12: return "SD card overcurrent";
case 0x21: return "Partition is not FAT";
case 0x22: return "Failed to read from partition";
case 0x23: return "Extended partition not FAT";
case 0x24: return "File signature or hash mismatch";
case 0x31: return "SPI EEPROM error";
case 0x32: return "EEPROM is write protected";
case 0x33: return "I2C error";
case 0x34: return "Configuration error";
case 0x43: return "RP1 not found";
case 0x44: return "Unsupported board type";
case 0x45: return "Fatal firmware error";
case 0x46: return "Power failure type A";
case 0x47: return "Power failure type B";
default: return "Unknown";
}
}

int rpi_error_log_get(rpi_error_log_t *entries, size_t max_entries, size_t *out_count)
{
struct firmware_error_log_msg msg = {0};
size_t num_entries, i;
int mb, rc;

if (!entries || !out_count || max_entries == 0)
return -1;

mb = mbox_open();
if (mb < 0)
return -1;

msg.hdr.buf_size = sizeof(msg);
msg.hdr.tag = TAG_GET_EEPROM_ERROR_LOGS;
msg.hdr.tag_buf_size = sizeof(msg.resp);
msg.req.reserved = 0;
msg.req.length = RPI_ERROR_LOG_MAX_SIZE;

rc = mbox_property(mb, &msg);
mbox_close(mb);

if (rc < 0)
return -1;

if (msg.resp.status & VC_MAILBOX_ERROR) {
/* Firmware error means no error log file exists in EEPROM */
*out_count = 0;
return 0;
}

num_entries = msg.resp.length / RPI_ERROR_LOG_ENTRY_SIZE;
if (num_entries > max_entries)
num_entries = max_entries;

for (i = 0; i < num_entries; i++) {
uint32_t ec, ec_inv;
size_t offset = i * RPI_ERROR_LOG_ENTRY_SIZE;

memcpy(&ec, msg.resp.data + offset, sizeof(ec));
memcpy(&ec_inv, msg.resp.data + offset + 4, sizeof(ec_inv));

LOG_DEBUG("entry[%zu] ec=0x%08x ec_inv=0x%08x\n", i, ec, ec_inv);

if (ec == 0xffffffffu)
break;

if (ec_inv != (~ec & 0xffffffffu)) {
fprintf(stderr, "Entry %zu checksum invalid\n", i);
return -1;
}

entries[i].error_code = ec;
}

*out_count = i;
return 0;
}

int rpi_error_log_clear(void)
{
struct firmware_error_log_clear_msg msg = {0};
int mb, rc;

mb = mbox_open();
if (mb < 0)
return -1;

msg.hdr.buf_size = sizeof(msg);
msg.hdr.tag = TAG_SET_EEPROM_ERROR_LOGS;
msg.hdr.tag_buf_size = sizeof(msg.resp);

rc = mbox_property(mb, &msg);
mbox_close(mb);

if (rc < 0)
return -1;

if (msg.resp.status & VC_MAILBOX_ERROR) {
fprintf(stderr, "Firmware returned error for SET_EEPROM_ERROR_LOGS\n");
return -1;
}

return 0;
}
Loading