From b07c0adb429bd4719158488d216d9be8cd83d527 Mon Sep 17 00:00:00 2001 From: martamomotko Date: Thu, 9 Apr 2026 17:39:51 +0100 Subject: [PATCH] rpierrorlog: Add rpierrorlog to utils to manage firmware error logging --- rpierrorlog/CMakeLists.txt | 21 ++++ rpierrorlog/README.md | 25 +++++ rpierrorlog/main.c | 91 +++++++++++++++ rpierrorlog/rpierrorlog.c | 220 +++++++++++++++++++++++++++++++++++++ rpierrorlog/rpierrorlog.h | 50 +++++++++ 5 files changed, 407 insertions(+) create mode 100644 rpierrorlog/CMakeLists.txt create mode 100644 rpierrorlog/README.md create mode 100644 rpierrorlog/main.c create mode 100644 rpierrorlog/rpierrorlog.c create mode 100644 rpierrorlog/rpierrorlog.h diff --git a/rpierrorlog/CMakeLists.txt b/rpierrorlog/CMakeLists.txt new file mode 100644 index 0000000..6c05ce2 --- /dev/null +++ b/rpierrorlog/CMakeLists.txt @@ -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}) diff --git a/rpierrorlog/README.md b/rpierrorlog/README.md new file mode 100644 index 0000000..2f98805 --- /dev/null +++ b/rpierrorlog/README.md @@ -0,0 +1,25 @@ + +# rpierrorlog + +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) diff --git a/rpierrorlog/main.c b/rpierrorlog/main.c new file mode 100644 index 0000000..3d77ff1 --- /dev/null +++ b/rpierrorlog/main.c @@ -0,0 +1,91 @@ +#include +#include +#include "rpierrorlog.h" + +static void usage(const char *progname) +{ + fprintf(stderr, + "Usage: %s \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; +} diff --git a/rpierrorlog/rpierrorlog.c b/rpierrorlog/rpierrorlog.c new file mode 100644 index 0000000..7ed15f9 --- /dev/null +++ b/rpierrorlog/rpierrorlog.c @@ -0,0 +1,220 @@ +#include +#include +#include +#include +#include +#include +#include +#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) || + !(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; +} diff --git a/rpierrorlog/rpierrorlog.h b/rpierrorlog/rpierrorlog.h new file mode 100644 index 0000000..0b164d7 --- /dev/null +++ b/rpierrorlog/rpierrorlog.h @@ -0,0 +1,50 @@ +#ifndef RPI_ERROR_LOG_H +#define RPI_ERROR_LOG_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include + +#define RPI_ERROR_LOG_ENTRY_SIZE 8 /* error_code (u32 LE) + ~error_code (u32 LE) */ +#define RPI_ERROR_LOG_MAX_SIZE 4096 + +typedef struct { + uint32_t error_code; +} rpi_error_log_t; + +/** + * Read all error log entries from the EEPROM via the firmware mailbox. + * + * Sends the maximum buffer size and uses the firmware-returned length + * to determine how many entries were actually read. + * + * @param entries Output buffer for parsed entries. + * @param max_entries Size of the entries buffer. + * @param out_count Output: number of entries returned by firmware. + * @return 0 on success, -1 on firmware/ioctl error. + */ +int rpi_error_log_get(rpi_error_log_t *entries, size_t max_entries, size_t *out_count); + +/** + * Clear the EEPROM error log via the firmware mailbox (SET_EEPROM_ERROR_LOGS). + * + * @return 0 on success, -1 on firmware/ioctl error. + */ +int rpi_error_log_clear(void); + +/** + * Translate a bootrom error code to a human-readable string. + * + * @param error_code The error_code field from rpi_error_log_t. + * @return A constant string describing the error. + */ +const char *rpi_error_log_strerror(uint32_t error_code); + +#ifdef __cplusplus +} +#endif + +#endif /* RPI_ERROR_LOG_H */