From 7b379fc9c40979d2c4c57c682e76b7eb7b5d306e Mon Sep 17 00:00:00 2001 From: Thom Nichols <95562+thom-nic@users.noreply.github.com> Date: Tue, 7 Oct 2025 09:57:19 -0400 Subject: [PATCH 1/3] dm_eeprom: look up i2c eeprom bootcount device in the device tree instead of using compile-time constants for bus/address/offset ref: https://github.com/u-boot/u-boot/blob/master/drivers/bootcount/bootcount_dm_i2c.c --- src/Makefile.am | 2 +- src/bootcount.c | 6 + src/constants.h | 4 + src/dm_eeprom.c | 324 ++++++++++++++++++++++++++++++++++++++++++++++++ src/dm_eeprom.h | 7 ++ src/dt.h | 4 + 6 files changed, 346 insertions(+), 1 deletion(-) create mode 100644 src/dm_eeprom.c create mode 100644 src/dm_eeprom.h diff --git a/src/Makefile.am b/src/Makefile.am index 17c212b..fc7122a 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -1,6 +1,6 @@ AM_CFLAGS = -std=c99 -pedantic -W -Wall -Wextra -Wno-unused-parameter -Wshadow sbin_PROGRAMS = bootcount -bootcount_SOURCES = bootcount.c am33xx.c stm32mp1.c i2c_eeprom.c memory.c \ +bootcount_SOURCES = bootcount.c am33xx.c stm32mp1.c i2c_eeprom.c dm_eeprom.c memory.c \ dt.c imx8m.c imx93.c diff --git a/src/bootcount.c b/src/bootcount.c index 7023fe0..ded4bfc 100644 --- a/src/bootcount.c +++ b/src/bootcount.c @@ -34,6 +34,7 @@ #include "imx93.h" #include "stm32mp1.h" #include "i2c_eeprom.h" +#include "dm_eeprom.h" struct platform { const char *name; @@ -65,6 +66,11 @@ static const struct platform platforms[] = { .read_bootcount = stm32mp1_read_bootcount, .write_bootcount = stm32mp1_write_bootcount }, + {.name = DM_EEPROM_NAME, + .detect = dm_eeprom_exists, + .read_bootcount = dm_eeprom_read_bootcount, + .write_bootcount = dm_eeprom_write_bootcount + }, {.name = EEPROM_NAME, .detect = eeprom_exists, .read_bootcount = eeprom_read_bootcount, diff --git a/src/constants.h b/src/constants.h index b72b5e5..8aed52f 100644 --- a/src/constants.h +++ b/src/constants.h @@ -4,6 +4,10 @@ #include #include +#ifndef PATH_MAX +#define PATH_MAX 4096 +#endif + #define DEBUG false #define BOOTCOUNT_MAGIC 0xB001C041ul // from u-boot include/common.h diff --git a/src/dm_eeprom.c b/src/dm_eeprom.c new file mode 100644 index 0000000..af2f261 --- /dev/null +++ b/src/dm_eeprom.c @@ -0,0 +1,324 @@ + +/** + * dm-eeprom bootcount implementation for linux userspace. + * Ref: https://github.com/u-boot/u-boot/blob/master/drivers/bootcount/bootcount_dm_i2c.c + * + * This file is part of the uboot-bootcount (https://github.com/VoltServer/uboot-bootcount). + * Copyright (c) 2018 VoltServer. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "dt.h" +#include "constants.h" + +#define DM_I2C_MAGIC 0xbc + +/* + * Runtime discovery of the EEPROM used for bootcount via the flattened + * device tree exported at /proc/device-tree. + * + * The definition looks like this: + * + * chosen { + * // see: u-boot/drivers/bootcount/bootcount-uclass.c + * u-boot,bootcount-device = &bootcount_i2c_eeprom; + * }; + * bootcount_i2c_eeprom: bc_i2c_eeprom { + * // see: u-boot/drivers/bootcount/i2c-eeprom.c + * compatible = "u-boot,bootcount-i2c-eeprom"; + * i2c-eeprom = <&eeprom0>; + * offset = <0x30>; + * }; + * + * see: https://github.com/u-boot/u-boot/blob/master/drivers/bootcount/i2c-eeprom.c + * + * Read the phandle /sys/firmware/devicetree/base//i2c-eeprom + * Look for the device at /sys/bus/i2c/devices/-/eeprom + * + */ + +static bool dt_root_available(void) +{ + struct stat sb; + return (stat(DT_ROOT, &sb) == 0 && S_ISDIR(sb.st_mode)); +} + +/* Utility: read a big-endian 32-bit value from a property file */ +static bool read_u32(const char *path, uint32_t *val) +{ + FILE *f = fopen(path, "rb"); + if (!f) + return false; + unsigned char b[4]; + size_t r = fread(b, 1, 4, f); + fclose(f); + if (r != 4) + return false; + *val = (b[0] << 24) | (b[1] << 16) | (b[2] << 8) | b[3]; + return true; +} + +/* directory traversal to locate a node's path by phandle. */ +static bool scan_dir_for_phandle(const char *dir, uint32_t target, char *out, size_t outlen, int depth) +{ + if (depth > 8) /* arbitrary safety 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 . and .. and hidden */ + 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 */ + + /* Each node is a directory containing (possibly) phandle files */ + struct stat st; + if (lstat(path, &st) != 0) + continue; + if (!S_ISDIR(st.st_mode)) + continue; + /* Check for phandle or linux,phandle inside */ + char ph_path[PATH_MAX]; + uint32_t val; + plen = snprintf(ph_path, sizeof(ph_path), "%s/phandle", path); + if (plen >= 0 && plen < (int)sizeof(ph_path)) { + if (read_u32(ph_path, &val) && val == target) { + strncpy(out, path, outlen - 1); + out[outlen - 1] = 0; + found = true; + break; + } + } + /* legacy linux,phandle */ + plen = snprintf(ph_path, sizeof(ph_path), "%s/linux,phandle", path); + if (plen >= 0 && plen < (int)sizeof(ph_path)) { + if (read_u32(ph_path, &val) && val == target) { + strncpy(out, path, outlen - 1); + out[outlen - 1] = 0; + found = true; + break; + } + } + /* Recurse only if not found locally */ + if (scan_dir_for_phandle(path, target, out, outlen, depth + 1)) { + found = true; + break; + } + } + closedir(d); + return found; +} + +/* Given a phandle value, find the corresponding node directory path */ +static bool find_phandle_node(uint32_t phandle, char *out, size_t outlen) +{ + return scan_dir_for_phandle(DT_ROOT, phandle, out, outlen, 0); +} + +/* Read a u32 property inside a node given its directory path */ +static bool node_read_u32(const char *node_dir, const char *prop, uint32_t *val) +{ + char path[PATH_MAX]; + int n = snprintf(path, sizeof(path), "%s/%s", node_dir, prop); + if (n < 0 || n >= (int)sizeof(path)) + return false; + return read_u32(path, val); +} + +/* Extract the reg property (EEPROM I2C slave address) */ +static bool get_eeprom_address(const char *eeprom_node, uint8_t *addr) +{ + uint32_t reg; + if (!node_read_u32(eeprom_node, "reg", ®)) + return false; + /* 'reg' may encode address and size; typical simple case it's just addr */ + *addr = (uint8_t)(reg & 0xFF); + return true; +} + +/* Global cached discovered path and offset */ +static bool g_inited = false; +static char g_eeprom_sysfs_path[128]; +static off_t g_offset = 0; + +static bool discover_dm_eeprom(void) +{ + if (g_inited) + return true; + DEBUG_PRINTF("Discovering DM I2C EEPROM bootcount device...\n"); + + /* Verify required sysfs roots exist */ + struct stat sys_sb; + if (!dt_root_available() || stat("/sys/bus/i2c/devices", &sys_sb) != 0) { + DEBUG_PRINTF(" Required sysfs paths missing; DM EEPROM unsupported.\n"); + return false; + } + char chosen_bc_path[PATH_MAX]; + if (snprintf(chosen_bc_path, sizeof(chosen_bc_path), DT_ROOT "/chosen/u-boot,bootcount-device") >= (int)sizeof(chosen_bc_path)) + return false; + uint32_t bc_phandle; + if (!read_u32(chosen_bc_path, &bc_phandle)) { + return false; /* No chosen bootcount device */ + } + DEBUG_PRINTF(" Found bootcount-device phandle %u\n", bc_phandle); + char bc_node[PATH_MAX]; + if (!find_phandle_node(bc_phandle, bc_node, sizeof(bc_node))) + return false; + DEBUG_PRINTF(" Found bootcount node %s\n", bc_node); + + /* Read offset (optional) */ + uint32_t offset = 0; + node_read_u32(bc_node, "offset", &offset); /* ignore failure => 0 */ + g_offset = (off_t)offset; + DEBUG_PRINTF(" Using offset 0x%lx\n", (unsigned long)g_offset); + + /* Read i2c-eeprom phandle */ + uint32_t eeprom_phandle; + if (!dt_node_read_u32(bc_node, "i2c-eeprom", &eeprom_phandle)) + return false; + DEBUG_PRINTF(" Found i2c-eeprom phandle %u\n", eeprom_phandle); + + char eeprom_node[PATH_MAX]; + if (!find_phandle_node(eeprom_phandle, eeprom_node, sizeof(eeprom_node))) + return false; + DEBUG_PRINTF(" Found eeprom node %s\n", eeprom_node); + uint8_t slave_addr; + if (!get_eeprom_address(eeprom_node, &slave_addr)) + return false; + DEBUG_PRINTF(" EEPROM I2C address 0x%02x\n", slave_addr); + + /* Iterate /sys/bus/i2c/devices// and compare their + * of_node symlink target to the exact eeprom DT node path we resolved. + */ + bool matched = false; + DIR *dev_dir = opendir("/sys/bus/i2c/devices"); + if (!dev_dir) + return false; + struct dirent *de2; + while ((de2 = readdir(dev_dir))) { + int bus; unsigned int addr4; + if (sscanf(de2->d_name, "%d-%04x", &bus, &addr4) != 2) + continue; /* skip non device entries */ + /* Fast reject: address mismatch */ + if ((addr4 & 0xFF) != slave_addr) + continue; + DEBUG_PRINTF(" Checking device %s ...\n", de2->d_name); + char link_path[PATH_MAX]; + if (snprintf(link_path, sizeof(link_path), "/sys/bus/i2c/devices/%s/of_node", de2->d_name) >= (int)sizeof(link_path)) + continue; /* path truncated */ + char *resolved = realpath(link_path, NULL); /* let libc allocate */ + if (!resolved) + continue; + bool same = (strcmp(resolved, eeprom_node) == 0); + free(resolved); + if (same) { + snprintf(g_eeprom_sysfs_path, sizeof(g_eeprom_sysfs_path), + "/sys/bus/i2c/devices/%s/eeprom", de2->d_name); + matched = true; + DEBUG_PRINTF(" Chose EEPROM device %s\n", g_eeprom_sysfs_path); + break; + } + } + closedir(dev_dir); + if (!matched) + return false; + + /* Validate file exists */ + struct stat sb; + if (stat(g_eeprom_sysfs_path, &sb) != 0) + return false; + + g_inited = true; + return true; +} + +static int dm_eeprom_open(void) +{ + if (!discover_dm_eeprom()) + return E_DEVICE; + int fd = open(g_eeprom_sysfs_path, O_RDWR); + if (fd < 0) + return E_DEVICE; + if (lseek(fd, g_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 fd = dm_eeprom_open(); + if (fd < 0) + return fd; + + unsigned char bytes[2]; + if (read(fd, bytes, sizeof(bytes)) != (ssize_t)sizeof(bytes)) { + close(fd); + return E_DEVICE; + } + close(fd); + + if (bytes[1] != DM_I2C_MAGIC) { + /* Upstream DM driver resets counter to 0 on invalid magic. + We have a reset command, so do not write on a read operation + */ + return E_BADMAGIC; + } + *val = bytes[0]; + return 0; +} + +int dm_eeprom_write_bootcount(uint16_t val) +{ + int fd = dm_eeprom_open(); + if (fd < 0) + return fd; + unsigned char bytes[2]; + bytes[0] = (unsigned char)(val & 0xff); + bytes[1] = DM_I2C_MAGIC; + ssize_t written = write(fd, bytes, sizeof(bytes)); + if (written != (ssize_t)sizeof(bytes)) { + close(fd); + return E_DEVICE; + } + close(fd); + return 0; +} + + diff --git a/src/dm_eeprom.h b/src/dm_eeprom.h new file mode 100644 index 0000000..969722c --- /dev/null +++ b/src/dm_eeprom.h @@ -0,0 +1,7 @@ +#pragma once + +#define DM_EEPROM_NAME "DM I2C EEPROM" + +bool dm_eeprom_exists(void); +int dm_eeprom_read_bootcount(uint16_t *val); +int dm_eeprom_write_bootcount(uint16_t val); diff --git a/src/dt.h b/src/dt.h index af29e85..678de3d 100644 --- a/src/dt.h +++ b/src/dt.h @@ -22,4 +22,8 @@ #include +/* Root of flattened DT in sysfs (preferred for runtime property access) */ +#define DT_ROOT "/sys/firmware/devicetree/base" + +/* /proc helper: check if SoC compatible string is present */ bool is_compatible_soc(const char* compat); From 43f8a7e2b6cfb18b1294ffe88c832cdecf2761b1 Mon Sep 17 00:00:00 2001 From: Thom Nichols <95562+thom-nic@users.noreply.github.com> Date: Wed, 8 Oct 2025 14:10:41 -0400 Subject: [PATCH 2/3] dm_eeprom,dt: refactor device tree operations into dm so they can be used for rtc driver logic. --- src/dm_eeprom.c | 105 +++--------------------------------------------- src/dt.c | 103 +++++++++++++++++++++++++++++++++++++++++++++++ src/dt.h | 15 +++++++ 3 files changed, 123 insertions(+), 100 deletions(-) diff --git a/src/dm_eeprom.c b/src/dm_eeprom.c index af2f261..9a7d26c 100644 --- a/src/dm_eeprom.c +++ b/src/dm_eeprom.c @@ -61,107 +61,12 @@ * */ -static bool dt_root_available(void) -{ - struct stat sb; - return (stat(DT_ROOT, &sb) == 0 && S_ISDIR(sb.st_mode)); -} - -/* Utility: read a big-endian 32-bit value from a property file */ -static bool read_u32(const char *path, uint32_t *val) -{ - FILE *f = fopen(path, "rb"); - if (!f) - return false; - unsigned char b[4]; - size_t r = fread(b, 1, 4, f); - fclose(f); - if (r != 4) - return false; - *val = (b[0] << 24) | (b[1] << 16) | (b[2] << 8) | b[3]; - return true; -} - -/* directory traversal to locate a node's path by phandle. */ -static bool scan_dir_for_phandle(const char *dir, uint32_t target, char *out, size_t outlen, int depth) -{ - if (depth > 8) /* arbitrary safety 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 . and .. and hidden */ - 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 */ - - /* Each node is a directory containing (possibly) phandle files */ - struct stat st; - if (lstat(path, &st) != 0) - continue; - if (!S_ISDIR(st.st_mode)) - continue; - /* Check for phandle or linux,phandle inside */ - char ph_path[PATH_MAX]; - uint32_t val; - plen = snprintf(ph_path, sizeof(ph_path), "%s/phandle", path); - if (plen >= 0 && plen < (int)sizeof(ph_path)) { - if (read_u32(ph_path, &val) && val == target) { - strncpy(out, path, outlen - 1); - out[outlen - 1] = 0; - found = true; - break; - } - } - /* legacy linux,phandle */ - plen = snprintf(ph_path, sizeof(ph_path), "%s/linux,phandle", path); - if (plen >= 0 && plen < (int)sizeof(ph_path)) { - if (read_u32(ph_path, &val) && val == target) { - strncpy(out, path, outlen - 1); - out[outlen - 1] = 0; - found = true; - break; - } - } - /* Recurse only if not found locally */ - if (scan_dir_for_phandle(path, target, out, outlen, depth + 1)) { - found = true; - break; - } - } - closedir(d); - return found; -} - -/* Given a phandle value, find the corresponding node directory path */ -static bool find_phandle_node(uint32_t phandle, char *out, size_t outlen) -{ - return scan_dir_for_phandle(DT_ROOT, phandle, out, outlen, 0); -} - -/* Read a u32 property inside a node given its directory path */ -static bool node_read_u32(const char *node_dir, const char *prop, uint32_t *val) -{ - char path[PATH_MAX]; - int n = snprintf(path, sizeof(path), "%s/%s", node_dir, prop); - if (n < 0 || n >= (int)sizeof(path)) - return false; - return read_u32(path, val); -} - /* Extract the reg property (EEPROM I2C slave address) */ static bool get_eeprom_address(const char *eeprom_node, uint8_t *addr) { uint32_t reg; - if (!node_read_u32(eeprom_node, "reg", ®)) + if (!dt_node_read_u32(eeprom_node, "reg", ®)) return false; - /* 'reg' may encode address and size; typical simple case it's just addr */ *addr = (uint8_t)(reg & 0xFF); return true; } @@ -187,18 +92,18 @@ static bool discover_dm_eeprom(void) if (snprintf(chosen_bc_path, sizeof(chosen_bc_path), DT_ROOT "/chosen/u-boot,bootcount-device") >= (int)sizeof(chosen_bc_path)) return false; uint32_t bc_phandle; - if (!read_u32(chosen_bc_path, &bc_phandle)) { + if (!dt_read_u32(chosen_bc_path, &bc_phandle)) { return false; /* No chosen bootcount device */ } DEBUG_PRINTF(" Found bootcount-device phandle %u\n", bc_phandle); char bc_node[PATH_MAX]; - if (!find_phandle_node(bc_phandle, bc_node, sizeof(bc_node))) + if (!dt_find_phandle_node(bc_phandle, bc_node, sizeof(bc_node))) return false; DEBUG_PRINTF(" Found bootcount node %s\n", bc_node); /* Read offset (optional) */ uint32_t offset = 0; - node_read_u32(bc_node, "offset", &offset); /* ignore failure => 0 */ + dt_node_read_u32(bc_node, "offset", &offset); /* ignore failure => 0 */ g_offset = (off_t)offset; DEBUG_PRINTF(" Using offset 0x%lx\n", (unsigned long)g_offset); @@ -209,7 +114,7 @@ static bool discover_dm_eeprom(void) DEBUG_PRINTF(" Found i2c-eeprom phandle %u\n", eeprom_phandle); char eeprom_node[PATH_MAX]; - if (!find_phandle_node(eeprom_phandle, eeprom_node, sizeof(eeprom_node))) + if (!dt_find_phandle_node(eeprom_phandle, eeprom_node, sizeof(eeprom_node))) return false; DEBUG_PRINTF(" Found eeprom node %s\n", eeprom_node); uint8_t slave_addr; diff --git a/src/dt.c b/src/dt.c index 72eb026..3daf96e 100644 --- a/src/dt.c +++ b/src/dt.c @@ -21,6 +21,10 @@ #include #include #include +#include +#include +#include +#include #include "constants.h" #include "dt.h" @@ -70,3 +74,102 @@ bool is_compatible_soc(const char* compat_str) { } return false; } + +bool dt_root_available(void) +{ + struct stat sb; + return (stat(DT_ROOT, &sb) == 0 && S_ISDIR(sb.st_mode)); +} + +bool dt_read_u32(const char *path, uint32_t *val) +{ + FILE *f = fopen(path, "rb"); + if (!f) + return false; + + unsigned char b[4]; + size_t r = fread(b, 1, 4, f); + fclose(f); + if (r != 4) + return false; + + *val = (b[0] << 24) | (b[1] << 16) | (b[2] << 8) | b[3]; + return true; +} + +/* Recursive directory traversal to locate a node directory that owns a given phandle */ +static bool dt_scan_dir_for_phandle(const char *dir, uint32_t target, + 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 */ + + char ph_path[PATH_MAX]; + uint32_t val; + + /* Try primary 'phandle' */ + plen = snprintf(ph_path, sizeof(ph_path), "%s/phandle", 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; + found = true; + break; + } + } + + /* 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 (dt_read_u32(ph_path, &val) && val == target) { + strncpy(out, path, outlen - 1); + out[outlen - 1] = 0; + found = true; + break; + } + } + + /* Recurse if not matched here */ + if (!found && dt_scan_dir_for_phandle(path, target, out, outlen, depth + 1)) { + found = true; + break; + } + } + + closedir(d); + return found; +} + +bool dt_find_phandle_node(uint32_t phandle, char *out, size_t outlen) +{ + return dt_scan_dir_for_phandle(DT_ROOT, phandle, out, outlen, 0); +} + +bool dt_node_read_u32(const char *node_dir, const char *prop, uint32_t *val) +{ + char path[PATH_MAX]; + int n = snprintf(path, sizeof(path), "%s/%s", node_dir, prop); + if (n < 0 || n >= (int)sizeof(path)) + return false; + return dt_read_u32(path, val); +} + diff --git a/src/dt.h b/src/dt.h index 678de3d..1811143 100644 --- a/src/dt.h +++ b/src/dt.h @@ -21,9 +21,24 @@ #pragma once #include +#include +#include /* Root of flattened DT in sysfs (preferred for runtime property access) */ #define DT_ROOT "/sys/firmware/devicetree/base" /* /proc helper: check if SoC compatible string is present */ bool is_compatible_soc(const char* compat); + +/* Returns true if DT_ROOT exists */ +bool dt_root_available(void); + +/* Read big-endian u32 from a property file path. Returns true on success. */ +bool dt_read_u32(const char *path, uint32_t *val); + +/* Find node directory (full path) for a given phandle. Returns true on success. */ +bool dt_find_phandle_node(uint32_t phandle, char *out, size_t outlen); + +/* Read a u32 property (big-endian) from inside a node directory. Returns true on success. */ +bool dt_node_read_u32(const char *node_dir, const char *prop, uint32_t *val); + From ce0ebafd87143edccc93f6d4301d15794195bc3f Mon Sep 17 00:00:00 2001 From: Thom Nichols <95562+thom-nic@users.noreply.github.com> Date: Thu, 9 Oct 2025 17:44:22 -0400 Subject: [PATCH 3/3] dm_eeprom: refactor discover_dm_eeprom() Makefile.am: add error and warning CFLAGS --- src/Makefile.am | 3 +- src/dm_eeprom.c | 90 +++++++++++++++++++------------------------------ src/dt.c | 67 ++++++++++++++++++++++++++++++++++++ src/dt.h | 6 ++++ 4 files changed, 110 insertions(+), 56 deletions(-) diff --git a/src/Makefile.am b/src/Makefile.am index fc7122a..803cd21 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -1,4 +1,5 @@ -AM_CFLAGS = -std=c99 -pedantic -W -Wall -Wextra -Wno-unused-parameter -Wshadow +AM_CFLAGS = -std=c99 -pedantic -W -Wall -Wextra -Wno-unused-parameter -Wshadow -Wundef +AM_CFLAGS += -Werror sbin_PROGRAMS = bootcount bootcount_SOURCES = bootcount.c am33xx.c stm32mp1.c i2c_eeprom.c dm_eeprom.c memory.c \ diff --git a/src/dm_eeprom.c b/src/dm_eeprom.c index 9a7d26c..73a800e 100644 --- a/src/dm_eeprom.c +++ b/src/dm_eeprom.c @@ -36,6 +36,7 @@ #include "constants.h" #define DM_I2C_MAGIC 0xbc +#define I2C_SYSFS_DEVICES "/sys/bus/i2c/devices" /* * Runtime discovery of the EEPROM used for bootcount via the flattened @@ -61,19 +62,9 @@ * */ -/* Extract the reg property (EEPROM I2C slave address) */ -static bool get_eeprom_address(const char *eeprom_node, uint8_t *addr) -{ - uint32_t reg; - if (!dt_node_read_u32(eeprom_node, "reg", ®)) - return false; - *addr = (uint8_t)(reg & 0xFF); - return true; -} - /* Global cached discovered path and offset */ static bool g_inited = false; -static char g_eeprom_sysfs_path[128]; +static char g_eeprom_sysfs_path[PATH_MAX]; static off_t g_offset = 0; static bool discover_dm_eeprom(void) @@ -82,23 +73,10 @@ static bool discover_dm_eeprom(void) return true; DEBUG_PRINTF("Discovering DM I2C EEPROM bootcount device...\n"); - /* Verify required sysfs roots exist */ - struct stat sys_sb; - if (!dt_root_available() || stat("/sys/bus/i2c/devices", &sys_sb) != 0) { - DEBUG_PRINTF(" Required sysfs paths missing; DM EEPROM unsupported.\n"); - return false; - } - char chosen_bc_path[PATH_MAX]; - if (snprintf(chosen_bc_path, sizeof(chosen_bc_path), DT_ROOT "/chosen/u-boot,bootcount-device") >= (int)sizeof(chosen_bc_path)) - return false; - uint32_t bc_phandle; - if (!dt_read_u32(chosen_bc_path, &bc_phandle)) { - return false; /* No chosen bootcount device */ - } - DEBUG_PRINTF(" Found bootcount-device phandle %u\n", bc_phandle); char bc_node[PATH_MAX]; - if (!dt_find_phandle_node(bc_phandle, bc_node, sizeof(bc_node))) + if(!dt_get_chosen_bootcount_node("u-boot,bootcount-i2c-eeprom", bc_node, sizeof(bc_node))) { return false; + } DEBUG_PRINTF(" Found bootcount node %s\n", bc_node); /* Read offset (optional) */ @@ -113,46 +91,48 @@ static bool discover_dm_eeprom(void) return false; DEBUG_PRINTF(" Found i2c-eeprom phandle %u\n", eeprom_phandle); - char eeprom_node[PATH_MAX]; - if (!dt_find_phandle_node(eeprom_phandle, eeprom_node, sizeof(eeprom_node))) + char eeprom_device_path[PATH_MAX]; + if (!dt_find_phandle_node(eeprom_phandle, eeprom_device_path, sizeof(eeprom_device_path))) return false; - DEBUG_PRINTF(" Found eeprom node %s\n", eeprom_node); - uint8_t slave_addr; - if (!get_eeprom_address(eeprom_node, &slave_addr)) + DEBUG_PRINTF(" Found eeprom node %s\n", eeprom_device_path); + struct stat target_st; + if (stat(eeprom_device_path, &target_st) != 0) { + DEBUG_PRINTF(" stat() failed on target node %s\n", eeprom_device_path); return false; - DEBUG_PRINTF(" EEPROM I2C address 0x%02x\n", slave_addr); - - /* Iterate /sys/bus/i2c/devices// and compare their - * of_node symlink target to the exact eeprom DT node path we resolved. + } + /* Iterate /sys/bus/i2c/devices//of_node and compare the + * symlink target to the eeprom DT node path we resolved above */ bool matched = false; - DIR *dev_dir = opendir("/sys/bus/i2c/devices"); + DIR *dev_dir = opendir(I2C_SYSFS_DEVICES); if (!dev_dir) return false; + DEBUG_PRINTF(" Scanning " I2C_SYSFS_DEVICES " for matching device ...\n"); + // iterate all devices under /sys/bus/i2c/devices/ and find a matching of_node struct dirent *de2; while ((de2 = readdir(dev_dir))) { - int bus; unsigned int addr4; - if (sscanf(de2->d_name, "%d-%04x", &bus, &addr4) != 2) - continue; /* skip non device entries */ - /* Fast reject: address mismatch */ - if ((addr4 & 0xFF) != slave_addr) - continue; - DEBUG_PRINTF(" Checking device %s ...\n", de2->d_name); + if (de2->d_name[0] == '.') + continue; /* skip dot entries */ char link_path[PATH_MAX]; - if (snprintf(link_path, sizeof(link_path), "/sys/bus/i2c/devices/%s/of_node", de2->d_name) >= (int)sizeof(link_path)) - continue; /* path truncated */ - char *resolved = realpath(link_path, NULL); /* let libc allocate */ - if (!resolved) + if (snprintf(link_path, sizeof(link_path), I2C_SYSFS_DEVICES "/%s/of_node", de2->d_name) >= (int)sizeof(link_path)) { + DEBUG_PRINTF(" ERROR Path truncated constructing of_node path\n"); + continue; + } + if (!same_fs_node(link_path, eeprom_device_path)) { + continue; + } + DEBUG_PRINTF(" Matched device %s\n", link_path); + snprintf(g_eeprom_sysfs_path, sizeof(g_eeprom_sysfs_path), I2C_SYSFS_DEVICES "/%s/eeprom", de2->d_name); + + /* verify the eeprom node exists: */ + struct stat sb; + if (stat(g_eeprom_sysfs_path, &sb) != 0) { + DEBUG_PRINTF(" WARN EEPROM sysfs path %s does not exist, continuing...\n", g_eeprom_sysfs_path); continue; - bool same = (strcmp(resolved, eeprom_node) == 0); - free(resolved); - if (same) { - snprintf(g_eeprom_sysfs_path, sizeof(g_eeprom_sysfs_path), - "/sys/bus/i2c/devices/%s/eeprom", de2->d_name); - matched = true; - DEBUG_PRINTF(" Chose EEPROM device %s\n", g_eeprom_sysfs_path); - break; } + matched = true; + DEBUG_PRINTF(" Chose EEPROM device %s\n", g_eeprom_sysfs_path); + break; } closedir(dev_dir); if (!matched) diff --git a/src/dt.c b/src/dt.c index 3daf96e..8a794c5 100644 --- a/src/dt.c +++ b/src/dt.c @@ -173,3 +173,70 @@ bool dt_node_read_u32(const char *node_dir, const char *prop, uint32_t *val) return dt_read_u32(path, val); } +/* + * dt_node_read_str + * Returns: number of bytes read (excluding terminator) on success. + * -E_DEVICE (re-using existing error codes) on I/O failure or empty. + * NOTE: The string is always null-terminated on success (and on partial reads). + */ +int dt_node_read_str(const char *node_dir, const char *prop, char *out, size_t outlen) +{ + if (!out || outlen == 0) + return E_DEVICE; + + char path[PATH_MAX]; + int n = snprintf(path, sizeof(path), "%s/%s", node_dir, prop); + if (n < 0 || n >= (int)sizeof(path)) + return E_DEVICE; + + FILE *f = fopen(path, "rb"); + if (!f) + return E_DEVICE; + + size_t r = fread(out, 1, outlen - 1, f); + fclose(f); + if (r == 0) { + out[0] = 0; + return E_DEVICE; + } + out[r] = 0; + return (int)r; +} + +/* Compare two filesystem objects for identity (same underlying node). */ +bool same_fs_node(const char *a, const char *b) +{ + struct stat sa, sb; + if (stat(a, &sa) != 0) + return false; + if (stat(b, &sb) != 0) + return false; + return (sa.st_dev == sb.st_dev) && (sa.st_ino == sb.st_ino); +} + +bool dt_get_chosen_bootcount_node(const char *compat_str, char* bc_node, size_t bc_node_len) +{ + if (!dt_root_available()) + 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 (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; + } + + /* if the bc_node/compatible does not match "u-boot,bootcount-rtc" + then this is not the correct driver: */ + char compatible[100]; + 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); + return false; + } + return true; +} diff --git a/src/dt.h b/src/dt.h index 1811143..ca6879e 100644 --- a/src/dt.h +++ b/src/dt.h @@ -42,3 +42,9 @@ bool dt_find_phandle_node(uint32_t phandle, char *out, size_t outlen); /* Read a u32 property (big-endian) from inside a node directory. Returns true on success. */ bool dt_node_read_u32(const char *node_dir, const char *prop, uint32_t *val); +int dt_node_read_str(const char *node_dir, const char *prop, char *out, size_t outlen); + +/* Compare two filesystem objects for identity (same underlying node). */ +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);