diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index ff5b989..c5d4260 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -21,7 +21,9 @@ jobs: with: submodules: recursive - - run: sudo apt install -y gcc-${{ matrix.target}} build-essential + - run: | + sudo apt update + sudo apt install -y gcc-${{ matrix.target}} build-essential - run: ./autogen.sh - run: ./configure --host=${{ matrix.target }} --prefix=/usr - run: make binary-dist diff --git a/src/Makefile.am b/src/Makefile.am index 803cd21..1119f46 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -2,6 +2,6 @@ AM_CFLAGS = -std=c99 -pedantic -W -Wall -Wextra -Wno-unused-parame AM_CFLAGS += -Werror sbin_PROGRAMS = bootcount -bootcount_SOURCES = bootcount.c am33xx.c stm32mp1.c i2c_eeprom.c dm_eeprom.c memory.c \ +bootcount_SOURCES = bootcount.c am33xx.c stm32mp1.c i2c_eeprom.c dm_eeprom.c dm_rtc.c memory.c \ dt.c imx8m.c imx93.c diff --git a/src/bootcount.c b/src/bootcount.c index ded4bfc..8ddc799 100644 --- a/src/bootcount.c +++ b/src/bootcount.c @@ -35,6 +35,7 @@ #include "stm32mp1.h" #include "i2c_eeprom.h" #include "dm_eeprom.h" +#include "dm_rtc.h" struct platform { const char *name; @@ -71,6 +72,11 @@ static const struct platform platforms[] = { .read_bootcount = dm_eeprom_read_bootcount, .write_bootcount = dm_eeprom_write_bootcount }, + {.name = DM_RTC_NAME, + .detect = dm_rtc_exists, + .read_bootcount = dm_rtc_read_bootcount, + .write_bootcount = dm_rtc_write_bootcount + }, {.name = EEPROM_NAME, .detect = eeprom_exists, .read_bootcount = eeprom_read_bootcount, diff --git a/src/dm_eeprom.c b/src/dm_eeprom.c index 73a800e..ecfd9eb 100644 --- a/src/dm_eeprom.c +++ b/src/dm_eeprom.c @@ -147,28 +147,23 @@ static bool discover_dm_eeprom(void) return true; } -static int dm_eeprom_open(void) +int dm_eeprom_open_path(const char *path, off_t offset) { - if (!discover_dm_eeprom()) + if (path == NULL) return E_DEVICE; - int fd = open(g_eeprom_sysfs_path, O_RDWR); + int fd = open(path, O_RDWR); if (fd < 0) return E_DEVICE; - if (lseek(fd, g_offset, SEEK_SET) == -1) { + if (lseek(fd, offset, SEEK_SET) == -1) { close(fd); return E_DEVICE; } return fd; } -bool dm_eeprom_exists(void) -{ - return discover_dm_eeprom(); -} - -int dm_eeprom_read_bootcount(uint16_t *val) +int dm_eeprom_read_path(const char *path, off_t offset, uint8_t magic, uint16_t *val) { - int fd = dm_eeprom_open(); + int fd = dm_eeprom_open_path(path, offset); if (fd < 0) return fd; @@ -179,7 +174,7 @@ int dm_eeprom_read_bootcount(uint16_t *val) } close(fd); - if (bytes[1] != DM_I2C_MAGIC) { + if (bytes[1] != magic) { /* Upstream DM driver resets counter to 0 on invalid magic. We have a reset command, so do not write on a read operation */ @@ -189,14 +184,14 @@ int dm_eeprom_read_bootcount(uint16_t *val) return 0; } -int dm_eeprom_write_bootcount(uint16_t val) +int dm_eeprom_write_path(const char *path, off_t offset, uint8_t magic, uint16_t val) { - int fd = dm_eeprom_open(); + int fd = dm_eeprom_open_path(path, offset); if (fd < 0) return fd; unsigned char bytes[2]; bytes[0] = (unsigned char)(val & 0xff); - bytes[1] = DM_I2C_MAGIC; + bytes[1] = magic; ssize_t written = write(fd, bytes, sizeof(bytes)); if (written != (ssize_t)sizeof(bytes)) { close(fd); @@ -206,4 +201,18 @@ int dm_eeprom_write_bootcount(uint16_t val) return 0; } +bool dm_eeprom_exists(void) +{ + return discover_dm_eeprom(); +} + +int dm_eeprom_read_bootcount(uint16_t *val) +{ + return dm_eeprom_read_path(g_eeprom_sysfs_path, g_offset, DM_I2C_MAGIC, val); +} + +int dm_eeprom_write_bootcount(uint16_t val) +{ + return dm_eeprom_write_path(g_eeprom_sysfs_path, g_offset, DM_I2C_MAGIC, val); +} diff --git a/src/dm_eeprom.h b/src/dm_eeprom.h index 969722c..fbfd656 100644 --- a/src/dm_eeprom.h +++ b/src/dm_eeprom.h @@ -5,3 +5,6 @@ bool dm_eeprom_exists(void); int dm_eeprom_read_bootcount(uint16_t *val); int dm_eeprom_write_bootcount(uint16_t val); + +int dm_eeprom_read_path(const char *path, off_t offset, uint8_t magic, uint16_t *val); +int dm_eeprom_write_path(const char *path, off_t offset, uint8_t magic, uint16_t val); diff --git a/src/dm_rtc.c b/src/dm_rtc.c new file mode 100644 index 0000000..eb8e3b5 --- /dev/null +++ b/src/dm_rtc.c @@ -0,0 +1,193 @@ +/* Support u-boot rtc bootcount devices via DM RTC. + * ref: u-boot/drivers/bootcount/rtc.c + * + * Example device tree fragment: + * + * chosen { + * // see: u-boot/drivers/bootcount/bootcount-uclass.c + * u-boot,bootcount-device = &bootcount_rv3028; + * }; + * + * // Phycore contains an RV-3028-C7 RTC + * // ensure CONFIG_RV3028 is enabled in U-Boot config + * bootcount_rv3028: bc_rv3028 { + * // see: u-boot/drivers/bootcount/rtc.c + * compatible = "u-boot,bootcount-rtc"; + * rtc = <&i2c_som_rtc>; + * offset = <0x1F>; // registers 0x1F-0x20 are "User RAM" + * // In linux, the rtc-rv3028 driver creates a two-byte nvmem. So in linux the offset is not the same + * // as the I2C register offset. So we use another property to specify the linux,nvmem-offset: + * linux,nvmem-offset = <0x00>; + * // The rtc-rv3028 driver creates two nvmem devices, one for "User RAM" with type "Battery backed" + * // and one for "EEPROM" with type "EEPROM". We want the "Battery backed" one because the rv3028 + * // driver for u-boot does not support the EEPROM. Use this nvmem-type property to select the correct + * // nvmem device: + * linux,nvmem-type = "Battery backed" + * }; + * + * The underlying RTC device should expose an nvmem provider in Linux + * which results in a sysfs file: + * /sys/bus/nvmem/devices//nvmem + * We store the bootcount (magic + value) at the specified offset. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "constants.h" +#include "dm_eeprom.h" +#include "dt.h" + +#define NVMEM_SYSFS_DEVICES "/sys/bus/nvmem/devices" +#define RTC_MAGIC 0xbc + +/* Cached discovery state */ +static bool g_inited = false; +static char g_rtc_nvmem_path[128]; +static off_t g_offset = 0; + +static bool discover_dm_rtc(void) +{ + if (g_inited) + return true; + DEBUG_PRINTF("Discovering DM RTC bootcount device...\n"); + + char bc_node[PATH_MAX]; + if(!dt_get_chosen_bootcount_node("u-boot,bootcount-rtc", bc_node, sizeof(bc_node))) { + return false; + } + DEBUG_PRINTF(" Found bootcount node %s\n", bc_node); + + // if there is a `linux,nvmem-type` property, use it later to select the correct nvmem device: + char nvmem_type[64]; + int nvmem_type_len = dt_node_read_str(bc_node, "linux,nvmem-type", nvmem_type, sizeof(nvmem_type)); + if (nvmem_type_len > 0) { + DEBUG_PRINTF(" found linux,nvmem-type '%s'\n", nvmem_type); + } + + uint32_t offset = 0; + dt_node_read_u32(bc_node, "offset", &offset); /* ignore failure => 0 */ + g_offset = (off_t)offset; + + // if there is a `linux,nvmem-offset` property, use it instead of `offset`: + uint32_t linux_nvram_offset = 0; + if (dt_node_read_u32(bc_node, "linux,nvmem-offset", &linux_nvram_offset)) { + g_offset = (off_t)linux_nvram_offset; + DEBUG_PRINTF(" found linux,nvmem-offset 0x%lx\n", (unsigned long)g_offset); + } + DEBUG_PRINTF(" using offset 0x%lx\n", (unsigned long)g_offset); + + /* rtc phandle */ + uint32_t rtc_phandle; + if (!dt_node_read_u32(bc_node, "rtc", &rtc_phandle)) + return false; + DEBUG_PRINTF(" rtc phandle %u\n", rtc_phandle); + + char rtc_device_path[PATH_MAX]; + if (!dt_find_phandle_node(rtc_phandle, rtc_device_path, sizeof(rtc_device_path))) + return false; + DEBUG_PRINTF(" rtc node %s\n", rtc_device_path); + + /* Iterate nvmem devices to find matching of_node */ + DIR *dev_dir = opendir(NVMEM_SYSFS_DEVICES); + if (!dev_dir) + return false; + struct dirent *de; + bool matched = false; + while ((de = readdir(dev_dir))) { + if (de->d_name[0] == '.') + continue; /* skip dot entries */ + + // construct the base path to the nvmem device: /sys/bus/nvmem/devices/ + char nvmem_path[PATH_MAX]; + if (snprintf(nvmem_path, sizeof(nvmem_path), NVMEM_SYSFS_DEVICES "/%s", de->d_name) >= (int)sizeof(nvmem_path)) { + DEBUG_PRINTF(" ERROR Path truncated constructing nvmem path\n"); + continue; + } + + // find the device under /sys/bus/nvmem/devices whose of_node symlink + // matches the rtc_device_path we resolved above: + char link_path[PATH_MAX]; + if (snprintf(link_path, sizeof(link_path), "%s/of_node", nvmem_path) >= (int)sizeof(link_path)) { + DEBUG_PRINTF(" ERROR Path truncated constructing of_node path\n"); + continue; + } + if (!same_fs_node(link_path, rtc_device_path)) { + continue; + } + DEBUG_PRINTF(" Matched device %s\n", link_path); + + // if the dt definition included a `linux,nvmem-type` property, check it matches the nvmem "type" + // example: /sys/bus/nvmem/devices/rv3028_nvram0/type -> "Battery backed" + if (nvmem_type_len > 0) { + char dev_nvmem_type[64]; + int dev_nvmem_type_len = dt_node_read_str(nvmem_path, "type", dev_nvmem_type, sizeof(dev_nvmem_type)); + // trim the trailing newline if present: + if (dev_nvmem_type_len > 0 && dev_nvmem_type[dev_nvmem_type_len - 1] == '\n') { + dev_nvmem_type[--dev_nvmem_type_len] = '\0'; + } + if (dev_nvmem_type_len <= 0 || strcmp(dev_nvmem_type, nvmem_type) != 0) { + DEBUG_PRINTF(" %s/type '%s' does not match expected '%s', continuing...\n", nvmem_path, dev_nvmem_type, nvmem_type); + continue; + } + DEBUG_PRINTF(" matched nvmem-type '%s'\n", dev_nvmem_type); + } + + // ensure the device has an nvmem file: + if (snprintf(g_rtc_nvmem_path, sizeof(g_rtc_nvmem_path), "%s/nvmem", nvmem_path) >= (int)sizeof(g_rtc_nvmem_path)) { + DEBUG_PRINTF(" ERROR path too long: %s/nvmem\n" , nvmem_path); + continue; + } + struct stat sb; + if (stat(g_rtc_nvmem_path, &sb) != 0) { + DEBUG_PRINTF(" WARN nvmem path %s does not exist, continuing...\n", g_rtc_nvmem_path); + continue; + } + /* Ensure the nvmem file has sufficient size for the requested offset: + * we need 2 bytes at offset (magic + bootcount). + */ + if (sb.st_size < (off_t)(g_offset + 2)) { + DEBUG_PRINTF(" ERROR nvmem size %ld too small for offset 0x%lx\n", (long)sb.st_size, (unsigned long)g_offset); + continue; + } + + matched = true; + DEBUG_PRINTF(" Chose RTC nvmem %s\n", g_rtc_nvmem_path); + break; + } + closedir(dev_dir); + if (!matched) + return false; + + g_inited = true; + return true; +} + +bool dm_rtc_exists(void) +{ + return discover_dm_rtc(); +} + +int dm_rtc_read_bootcount(uint16_t *val) +{ + if (!dm_rtc_exists()) + return E_DEVICE; + return dm_eeprom_read_path(g_rtc_nvmem_path, g_offset, RTC_MAGIC, val); +} + +int dm_rtc_write_bootcount(uint16_t val) +{ + if (!dm_rtc_exists()) + return E_DEVICE; + return dm_eeprom_write_path(g_rtc_nvmem_path, g_offset, RTC_MAGIC, val); +} + diff --git a/src/dm_rtc.h b/src/dm_rtc.h new file mode 100644 index 0000000..bc02002 --- /dev/null +++ b/src/dm_rtc.h @@ -0,0 +1,7 @@ +#pragma once + +#define DM_RTC_NAME "DM RTC NVMEM" + +bool dm_rtc_exists(void); +int dm_rtc_read_bootcount(uint16_t *val); +int dm_rtc_write_bootcount(uint16_t val); diff --git a/src/dt.c b/src/dt.c index 8a794c5..f877b84 100644 --- a/src/dt.c +++ b/src/dt.c @@ -139,7 +139,7 @@ static bool dt_scan_dir_for_phandle(const char *dir, uint32_t target, /* Try legacy 'linux,phandle' */ plen = snprintf(ph_path, sizeof(ph_path), "%s/linux,phandle", path); - if (!found && plen >= 0 && plen < (int)sizeof(ph_path)) { + if (plen >= 0 && plen < (int)sizeof(ph_path)) { if (dt_read_u32(ph_path, &val) && val == target) { strncpy(out, path, outlen - 1); out[outlen - 1] = 0; @@ -149,7 +149,7 @@ static bool dt_scan_dir_for_phandle(const char *dir, uint32_t target, } /* Recurse if not matched here */ - if (!found && dt_scan_dir_for_phandle(path, target, out, outlen, depth + 1)) { + if (dt_scan_dir_for_phandle(path, target, out, outlen, depth + 1)) { found = true; break; } @@ -220,23 +220,81 @@ bool dt_get_chosen_bootcount_node(const char *compat_str, char* bc_node, size_t return false; char bc_path[128]; - if (!dt_node_read_str(DT_ROOT "/chosen", "u-boot,bootcount-device", bc_path, sizeof(bc_path))) { - DEBUG_PRINTF(" No chosen/u-boot,bootcount-device in device tree\n"); - return false; /* No chosen bootcount device */ - // TODO find the first bootcount node with compatible = compat_str and use it + if (dt_node_read_str(DT_ROOT "/chosen", "u-boot,bootcount-device", bc_path, sizeof(bc_path))) { + DEBUG_PRINTF(" Found chosen/u-boot,bootcount-device %s\n", bc_path); + if (snprintf(bc_node, bc_node_len, DT_ROOT "%s", bc_path) >= (int)bc_node_len) { + DEBUG_PRINTF(" ERROR Path truncated building device node path for %s\n", bc_path); + return false; + } + // else bc_node is set to the full path of the chosen bootcount device node } - if (snprintf(bc_node, bc_node_len, DT_ROOT "%s", bc_path) >= (int)bc_node_len) { - DEBUG_PRINTF(" ERROR Path truncated building device node path for %s\n", bc_path); - return false; + else { + // find the first device with compatible = 'u-boot,bootcount*' and see if it matches our compat_str + if (!dt_find_compatible_node("u-boot,bootcount", bc_node, sizeof(bc_node))) { + DEBUG_PRINTF(" No compatible node found for bootcount driver '%s'\n", compat_str); + return false; + } + // else bc_node is set to the full path of the first matching bootcount device node } - /* if the bc_node/compatible does not match "u-boot,bootcount-rtc" - then this is not the correct driver: */ - char compatible[100]; + /* if the bc_node/compatible does not match `compat_str`, then this is not the correct driver: */ + char compatible[128]; int bc_compat_len = dt_node_read_str(bc_node, "compatible", compatible, sizeof(compatible)); if (bc_compat_len <= 0 || strstr(compatible, compat_str) == NULL) { - DEBUG_PRINTF(" Chosen bootcount node is not compatible: '%s'\n", compatible); + DEBUG_PRINTF(" Found bootcount node is not compatible: '%s'\n", compatible); return false; } return true; } + +/* Recursive directory traversal to locate a device node with a /compatible property that matches compat_str */ +static bool dt_scan_dir_for_compatible(const char *dir, const char *compat_str, char *out, size_t outlen, int depth) +{ + if (depth > 8) /* safety recursion limit */ + return false; + + DIR *d = opendir(dir); + if (!d) + return false; + + struct dirent *e; + bool found = false; + while (!found && (e = readdir(d))) { + if (e->d_name[0] == '.') + continue; /* skip dot entries */ + + char path[PATH_MAX]; + int plen = snprintf(path, sizeof(path), "%s/%s", dir, e->d_name); + if (plen < 0 || plen >= (int)sizeof(path)) + continue; /* truncated */ + + struct stat st; + if (stat(path, &st) != 0 || !S_ISDIR(st.st_mode)) + continue; /* not a DT node directory */ + + /* Try to read a 'compatible' property */ + char compat_val[255]; + int dt_compat_len = dt_node_read_str(path, "compatible", compat_val, sizeof(compat_val)); + // match if compat_str is a substring of compat_val: + if (dt_compat_len > 0 && strncmp(compat_val, compat_str, strlen(compat_str)) == 0) { + strncpy(out, path, outlen - 1); + out[outlen - 1] = 0; + found = true; + break; + } + + /* Recurse if not matched here */ + if (dt_scan_dir_for_compatible(path, compat_str, out, outlen, depth + 1)) { + found = true; + break; + } + } + + closedir(d); + return found; +} + +bool dt_find_compatible_node(const char * compat_str, char *out, size_t outlen) +{ + return dt_scan_dir_for_compatible(DT_ROOT, compat_str, out, outlen, 0); +} diff --git a/src/dt.h b/src/dt.h index ca6879e..d2105c0 100644 --- a/src/dt.h +++ b/src/dt.h @@ -48,3 +48,5 @@ int dt_node_read_str(const char *node_dir, const char *prop, char *out, size_t o bool same_fs_node(const char *a, const char *b); bool dt_get_chosen_bootcount_node(const char *compat_str, char* bc_node, size_t bc_node_len); + +bool dt_find_compatible_node(const char * compat_str, char *out, size_t outlen);