Message ID | ZDf7qZTiml0ijD2g@lenoch (mailing list archive) |
---|---|
State | New, archived |
Headers | show |
Series | rtc: rtc-twl: add NVRAM support | expand |
Hi,
* Ladislav Michl <oss-lists@triops.cz> [230413 12:56]:
> Export SRAM using nvmem.
Patch looks good to me, just wondering how this is used..
Are we just exporting the nvram via sysfs for userspace to use it
for whatever, or is there also some RTC core layer use for it?
Regards,
Tony
Hi Tony, On Tue, Apr 18, 2023 at 12:43:03PM +0300, Tony Lindgren wrote: > Hi, > > * Ladislav Michl <oss-lists@triops.cz> [230413 12:56]: > > Export SRAM using nvmem. > > Patch looks good to me, just wondering how this is used.. This has been sitting in my tree for over five years and it is used to store boot state. Patches for u-boot are pending here as well, but I'll do my best to upstream reasonable parts of them. All was done for failsafe device updating using A/B scheme. Device is also using U-Boot's Falcon mode to speed things up, so SPL loads kernel directly from UBI volume. To decide which one to load and boot, information from NVRAM is used - there is no other memory except NVRAM and NAND. I didn't want to use NAND for this purpose, see bellow how NVRAM failures are handled. > Are we just exporting the nvram via sysfs for userspace to use it > for whatever, or is there also some RTC core layer use for it? RTC itself is not using it. My board is using RAUC for updates: https://rauc.readthedocs.io/en/latest/examples.html#symmetric-a-b-setup Information in NVRAM is crc protected and once inconsistent, normal U-Boot is loaded instead and information from it's env is used. In worst case it attempts recovery. Although U-Boot is used, this scenarion is not handled by RAUC out of the box, so custom integration is used: https://rauc.readthedocs.io/en/latest/integration.html#custom And here's the code for it: #include <errno.h> #include <fcntl.h> #include <stdint.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <sys/stat.h> #include <sys/sysmacros.h> #include <sys/types.h> #include <libubi.h> #ifdef DEBUG #define debug(x...) fprintf(stderr, x) #else #define debug(x...) do {} while (0) #endif struct bootstate { uint8_t selected; /* 0xa0 | selected */ uint8_t a_left; uint8_t b_left; uint8_t crc; }; /* Keep in sync with U-Boot configuration! */ #define KERNEL_VOLID 3 #define DTB_VOLID 5 #define POLY (0x1070U << 3) static uint8_t crc8(uint8_t crc, uint8_t byte) { unsigned data = (crc ^ byte) << 8; int i = 8; while (i--) { if (data & 0x8000) data ^= POLY; data <<= 1; } return data >> 8; } static int bootstate_open(int rw) { return open("/sys/bus/nvmem/devices/twl-backup-0/nvmem", (rw ? O_RDWR : O_RDONLY) | O_SYNC); } static int bootstate_read(int fd, struct bootstate *bs) { uint8_t crc; switch (pread(fd, bs, sizeof(*bs), 0)) { case -1: return errno; case sizeof(*bs): break; default: return -1; } debug("bootstate <- %02x %02x %02x %02x\n", bs->selected, bs->a_left, bs->b_left, bs->crc); if ((bs->selected & 0xf0) != 0xa0) return -2; crc = crc8(0, bs->selected); crc = crc8(crc, bs->a_left); crc = crc8(crc, bs->b_left); if (crc != bs->crc) return -3; bs->selected &= 0xf; if ((bs->selected != 0) && (bs->selected != 1)) return -4; return 0; } static int bootstate_write(int fd, struct bootstate *bs) { bs->selected |= 0xa0; bs->crc = crc8(0, bs->selected); bs->crc = crc8(bs->crc, bs->a_left); bs->crc = crc8(bs->crc, bs->b_left); debug("bootstate -> %02x %02x %02x %02x\n", bs->selected, bs->a_left, bs->b_left, bs->crc); switch (pwrite(fd, bs, sizeof(*bs), 0)) { case -1: return errno; case sizeof(*bs): break; default: return -1; } return 0; } /* create temporary node to open ubi device */ static char* node_of_major_minor(unsigned int maj, unsigned int min) { static char n[128]; sprintf(n, "/dev/.ubi_id-%u-%u-%u", (unsigned int)getpid(), maj, min); unlink(n); if (mknod(n, (S_IFCHR | 0600), makedev(maj, min)) == 0) return n; debug("failed to create temporary UBI node: %d (%s)\n", errno, strerror(errno)); return NULL; } static int update_ubi_names(struct bootstate *bs) { static const char *kernel_names[] = { "kernel", "kernel_r" }; static const char *dtb_names[] = { "dtb", "dtb_r" }; int i, j, err, invalid; struct ubi_rnvol_req rnvol; struct ubi_info ubi_info; struct ubi_dev_info dev_info; struct ubi_vol_info vol_info; libubi_t libubi; char *node; libubi = libubi_open(); if (!libubi) return -1; err = ubi_get_info(libubi, &ubi_info); if (err) goto out_close; if (ubi_info.dev_count == 0) { err = -1; goto out_close; } for (i = ubi_info.lowest_dev_num; i <= ubi_info.highest_dev_num; i++) { if (!ubi_dev_present(libubi, i)) continue; err = ubi_get_dev_info1(libubi, i, &dev_info); if (err) goto out_close; if (dev_info.vol_count == 0) { err = -1; goto out_close; } /* We do expect one UBI device with properly named volumes */ for (j = dev_info.lowest_vol_id; j <= dev_info.highest_vol_id; j++) { err = ubi_get_vol_info1(libubi, dev_info.dev_num, j, &vol_info); if (err == -1) { if (errno == ENOENT) continue; goto out_close; } switch (vol_info.vol_id) { case KERNEL_VOLID: invalid = strcmp(vol_info.name, kernel_names[bs->selected]); break; case KERNEL_VOLID + 1: invalid = strcmp(vol_info.name, kernel_names[bs->selected ^ 1]); break; case DTB_VOLID: invalid = strcmp(vol_info.name, dtb_names[bs->selected]); break; case DTB_VOLID + 1: invalid = strcmp(vol_info.name, dtb_names[bs->selected ^ 1]); break; } /* Rename all relevant volumes once any with unxpected name found */ if (invalid) { node = node_of_major_minor(dev_info.major, dev_info.minor); if (!node) { err = -1; goto out_close; } rnvol.ents[0].vol_id = KERNEL_VOLID + bs->selected; rnvol.ents[0].name_len = strlen(kernel_names[0]); strcpy(rnvol.ents[0].name, kernel_names[0]); rnvol.ents[1].vol_id = KERNEL_VOLID + (bs->selected ^ 1); rnvol.ents[1].name_len = strlen(kernel_names[1]); strcpy(rnvol.ents[1].name, kernel_names[1]); rnvol.ents[2].vol_id = DTB_VOLID + bs->selected; rnvol.ents[2].name_len = strlen(dtb_names[0]); strcpy(rnvol.ents[2].name, dtb_names[0]); rnvol.ents[3].vol_id = DTB_VOLID + (bs->selected ^ 1); rnvol.ents[3].name_len = strlen(dtb_names[1]); strcpy(rnvol.ents[3].name, dtb_names[1]); rnvol.count = 4; debug("Renaming UBI volumes:\n%s: %d\n%s: %d\n%s: %d\n%s: %d\n", rnvol.ents[0].name, rnvol.ents[0].vol_id, rnvol.ents[1].name, rnvol.ents[1].vol_id, rnvol.ents[2].name, rnvol.ents[2].vol_id, rnvol.ents[3].name, rnvol.ents[3].vol_id); err = ubi_rnvols(libubi, node, &rnvol); unlink(node); goto out_close; } } debug("UBI volume names correct.\n"); err = 0; break; } out_close: libubi_close(libubi); return err; } static int get_slot(const char *slot) { if (strcmp(slot, "system0") == 0) return 0; if (strcmp(slot, "system1") == 0) return 1; return -1; } static int get_count(const char *state) { if (strcmp(state, "good") == 0) return 3; if (strcmp(state, "bad") == 0) return 0; return -1; } static int out_str(const char* str) { debug(">>> %s\n", str); return puts(str) == EOF ? EXIT_FAILURE : EXIT_SUCCESS; } static int out_state(int good) { return out_str(good ? "good" : "bad"); } int main(int argc, char *argv[]) { int count, fd, slot; struct bootstate bs; debug("%s %s %s\n", (argc > 1) ? argv[1] : "", (argc > 2) ? argv[2] : "", (argc > 3) ? argv[3] : ""); if (argc > 2 && strcmp(argv[1], "get-state") == 0) { slot = get_slot(argv[2]); if (slot >= 0) { fd = bootstate_open(0); if (fd != -1 && bootstate_read(fd, &bs) == 0) return out_state(0 < (slot == 0 ? bs.a_left : bs.b_left)); } } else if (argc > 3 && strcmp(argv[1], "set-state") == 0) { if ((count = get_count(argv[3])) >= 0) { fd = bootstate_open(1); if (fd != -1) { slot = get_slot(argv[2]); if (bootstate_read(fd, &bs)) { memset(&bs, 0, sizeof(bs)); if (count) bs.selected = slot; else bs.selected = !!slot; } switch (slot) { case 0: bs.a_left = count; break; case 1: bs.b_left = count; break; default: return EXIT_FAILURE; } update_ubi_names(&bs); if (bootstate_write(fd, &bs) == 0) return EXIT_SUCCESS; } } } else if (argc > 1 && strcmp(argv[1], "get-primary") == 0) { fd = bootstate_open(0); if (fd != -1 && bootstate_read(fd, &bs) == 0) { switch (bs.selected) { case 0: return out_str("system0"); case 1: return out_str("system1"); default: break; } } } else if (argc > 2 && strcmp(argv[1], "set-primary") == 0) { fd = bootstate_open(1); if (fd != -1 && bootstate_read(fd, &bs) == 0) { switch (get_slot(argv[2])) { case 0: bs.selected = 0; bs.a_left = 3; break; case 1: bs.selected = 1; bs.b_left = 3; break; default: return EXIT_FAILURE; } if (bootstate_write(fd, &bs) == 0) return EXIT_SUCCESS; } } else { debug("malformed command.\n"); } return EXIT_FAILURE; } Hopefully above clarifies use case a bit, ladis
* Ladislav Michl <oss-lists@triops.cz> [230418 12:03]: > Hi Tony, > > On Tue, Apr 18, 2023 at 12:43:03PM +0300, Tony Lindgren wrote: > > Hi, > > > > * Ladislav Michl <oss-lists@triops.cz> [230413 12:56]: > > > Export SRAM using nvmem. > > > > Patch looks good to me, just wondering how this is used.. > > This has been sitting in my tree for over five years and it is used > to store boot state. Patches for u-boot are pending here as well, > but I'll do my best to upstream reasonable parts of them. > > All was done for failsafe device updating using A/B scheme. Device > is also using U-Boot's Falcon mode to speed things up, so SPL > loads kernel directly from UBI volume. To decide which one to load > and boot, information from NVRAM is used - there is no other memory > except NVRAM and NAND. I didn't want to use NAND for this purpose, > see bellow how NVRAM failures are handled. > > > Are we just exporting the nvram via sysfs for userspace to use it > > for whatever, or is there also some RTC core layer use for it? > > RTC itself is not using it. My board is using RAUC for updates: > https://rauc.readthedocs.io/en/latest/examples.html#symmetric-a-b-setup OK thanks for the detailed information :) Regards, Tony
* Ladislav Michl <oss-lists@triops.cz> [230413 12:56]: > From: Ladislav Michl <ladis@linux-mips.org> > Date: Sat, 28 May 2022 19:36:13 +0200 > > Export SRAM using nvmem. Reviewed-by: Tony Lindgren <tony@atomide.com>
On Wed, May 31, 2023 at 07:17:01AM +0300, Tony Lindgren wrote: > * Ladislav Michl <oss-lists@triops.cz> [230413 12:56]: > > From: Ladislav Michl <ladis@linux-mips.org> > > Date: Sat, 28 May 2022 19:36:13 +0200 > > > > Export SRAM using nvmem. > > Reviewed-by: Tony Lindgren <tony@atomide.com> Gentle ping... Is anyone going to merge this patch? Thanks, l.
On Thu, 13 Apr 2023 14:55:05 +0200, Ladislav Michl wrote: > Export SRAM using nvmem. > > Applied, thanks! [1/1] rtc: rtc-twl: add NVRAM support commit: 7130856f56054a031c492d37896cbcbfd04a61b5 Best regards,
diff --git a/drivers/rtc/rtc-twl.c b/drivers/rtc/rtc-twl.c index c24d1e18f56c..31877522b379 100644 --- a/drivers/rtc/rtc-twl.c +++ b/drivers/rtc/rtc-twl.c @@ -487,11 +487,24 @@ static const struct rtc_class_ops twl_rtc_ops = { .alarm_irq_enable = twl_rtc_alarm_irq_enable, }; +static int twl_nvram_read(void *priv, unsigned int offset, void *val, + size_t bytes) +{ + return twl_i2c_read((long)priv, val, offset, bytes); +} + +static int twl_nvram_write(void *priv, unsigned int offset, void *val, + size_t bytes) +{ + return twl_i2c_write((long)priv, val, offset, bytes); +} + /*----------------------------------------------------------------------*/ static int twl_rtc_probe(struct platform_device *pdev) { struct twl_rtc *twl_rtc; + struct nvmem_config nvmem_cfg; struct device_node *np = pdev->dev.of_node; int ret = -EINVAL; int irq = platform_get_irq(pdev, 0); @@ -579,6 +592,30 @@ static int twl_rtc_probe(struct platform_device *pdev) return ret; } + memset(&nvmem_cfg, 0, sizeof(nvmem_cfg)); + nvmem_cfg.name = "twl-secured-"; + nvmem_cfg.type = NVMEM_TYPE_BATTERY_BACKED; + nvmem_cfg.reg_read = twl_nvram_read, + nvmem_cfg.reg_write = twl_nvram_write, + nvmem_cfg.word_size = 1; + nvmem_cfg.stride = 1; + if (twl_class_is_4030()) { + /* 20 bytes SECURED_REG area */ + nvmem_cfg.size = 20; + nvmem_cfg.priv = (void *)TWL_MODULE_SECURED_REG; + devm_rtc_nvmem_register(twl_rtc->rtc, &nvmem_cfg); + /* 8 bytes BACKUP area */ + nvmem_cfg.name = "twl-backup-"; + nvmem_cfg.size = 8; + nvmem_cfg.priv = (void *)TWL4030_MODULE_BACKUP; + devm_rtc_nvmem_register(twl_rtc->rtc, &nvmem_cfg); + } else { + /* 8 bytes SECURED_REG area */ + nvmem_cfg.size = 8; + nvmem_cfg.priv = (void *)TWL_MODULE_SECURED_REG; + devm_rtc_nvmem_register(twl_rtc->rtc, &nvmem_cfg); + } + return 0; }