diff mbox series

rtc: rtc-twl: add NVRAM support

Message ID ZDf7qZTiml0ijD2g@lenoch (mailing list archive)
State New, archived
Headers show
Series rtc: rtc-twl: add NVRAM support | expand

Commit Message

Ladislav Michl April 13, 2023, 12:55 p.m. UTC
From: Ladislav Michl <ladis@linux-mips.org>
Date: Sat, 28 May 2022 19:36:13 +0200

Export SRAM using nvmem.

Signed-off-by: Ladislav Michl <ladis@linux-mips.org>
---
 drivers/rtc/rtc-twl.c | 37 +++++++++++++++++++++++++++++++++++++
 1 file changed, 37 insertions(+)

Comments

Tony Lindgren April 18, 2023, 9:43 a.m. UTC | #1
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
Ladislav Michl April 18, 2023, 12:03 p.m. UTC | #2
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
Tony Lindgren April 18, 2023, 12:46 p.m. UTC | #3
* 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
Tony Lindgren May 31, 2023, 4:17 a.m. UTC | #4
* 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>
Ladislav Michl Aug. 14, 2023, 8 p.m. UTC | #5
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.
Alexandre Belloni Aug. 27, 2023, 10:10 p.m. UTC | #6
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 mbox series

Patch

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;
 }