[PATCH v2 1/1] bootcount: add a new driver with syscon as backend

Nandor Han nandor.han at vaisala.com
Thu Jun 10 14:40:38 CEST 2021


The driver will use a syscon regmap as backend and supports both
16 and 32 size value. The value will be stored in the CPU's endianness.

Signed-off-by: Nandor Han <nandor.han at vaisala.com>
---

Notes:
    Description
    -----------
    Add a new driver for bootcount feature that supports `syscon` device as
    backend.

    Testing
    -------
    UnitTests:
    1. Verify that unit-tests run successfully: PASS
    ```
    ./test/py/test.py --bd sandbox --build -k ut_dm_bootcount -v

    test/py/tests/test_ut.py::test_ut[ut_dm_bootcount_rtc] PASSED                [ 33%]
    test/py/tests/test_ut.py::test_ut[ut_dm_bootcount_syscon_four_bytes] PASSED  [ 66%]
    test/py/tests/test_ut.py::test_ut[ut_dm_bootcount_syscon_two_bytes] PASSED   [100%]
    ```

    Using hardware:
    Board: i.MX6sx based dev board
    U-Boot version: 2020.01
    1. Use `bootcount` command to verity that value is updated
    correctly: PASS


Changes since v1:
 - rebased

 arch/sandbox/dts/test.dts                     |  14 ++
 configs/sandbox_defconfig                     |   1 +
 doc/device-tree-bindings/bootcount-syscon.txt |  25 +++
 drivers/bootcount/Kconfig                     |  12 ++
 drivers/bootcount/Makefile                    |   1 +
 drivers/bootcount/bootcount_syscon.c          | 159 ++++++++++++++++++
 test/dm/bootcount.c                           |  48 +++++-
 7 files changed, 257 insertions(+), 3 deletions(-)
 create mode 100644 doc/device-tree-bindings/bootcount-syscon.txt
 create mode 100644 drivers/bootcount/bootcount_syscon.c

diff --git a/arch/sandbox/dts/test.dts b/arch/sandbox/dts/test.dts
index 5ca3bc502a..32d16fbefc 100644
--- a/arch/sandbox/dts/test.dts
+++ b/arch/sandbox/dts/test.dts
@@ -707,6 +707,20 @@
 		i2c-eeprom = <&bootcount_i2c>;
 	};
 
+	bootcount_4 at 0 {
+		compatible = "u-boot,bootcount-syscon";
+		syscon = <&syscon0>;
+		reg = <0x0 0x04>, <0x0 0x04>;
+		reg-names = "syscon_reg", "offset";
+	};
+
+	bootcount_2 at 0 {
+		compatible = "u-boot,bootcount-syscon";
+		syscon = <&syscon0>;
+		reg = <0x0 0x04>, <0x0 0x02> ;
+		reg-names = "syscon_reg", "offset";
+	};
+
 	adc: adc at 0 {
 		compatible = "sandbox,adc";
 		#io-channel-cells = <1>;
diff --git a/configs/sandbox_defconfig b/configs/sandbox_defconfig
index bdbf714e2b..ee7a817de6 100644
--- a/configs/sandbox_defconfig
+++ b/configs/sandbox_defconfig
@@ -122,6 +122,7 @@ CONFIG_AXI=y
 CONFIG_AXI_SANDBOX=y
 CONFIG_BOOTCOUNT_LIMIT=y
 CONFIG_DM_BOOTCOUNT=y
+CONFIG_DM_BOOTCOUNT_SYSCON=y
 CONFIG_DM_BOOTCOUNT_RTC=y
 CONFIG_DM_BOOTCOUNT_I2C_EEPROM=y
 CONFIG_BUTTON=y
diff --git a/doc/device-tree-bindings/bootcount-syscon.txt b/doc/device-tree-bindings/bootcount-syscon.txt
new file mode 100644
index 0000000000..f2482b3c6f
--- /dev/null
+++ b/doc/device-tree-bindings/bootcount-syscon.txt
@@ -0,0 +1,25 @@
+Bootcount Configuration
+This is the implementation of the feature as described in
+https://www.denx.de/wiki/DULG/UBootBootCountLimit.
+
+Required Properties:
+- compatible: must be "u-boot,bootcount-syscon".
+- syscon: reference to the syscon device used.
+- reg: contains address and size of the register and the location and size of the bootcount value.
+	   The driver supports a 4 bytes register length and 2 and 4 bytes bootcount value length.
+- reg-names: must be "syscon_reg", "offset";
+
+Example:
+	...
+	syscon0: syscon at 0 {
+		compatible = "sandbox,syscon0";
+		reg = <0x10 16>;
+	};
+	...
+	bootcount at 0 {
+		compatible = "u-boot,bootcount-syscon";
+		syscon = <&syscon0>;
+		reg = <0x0 0x04>, <0x0 0x04>;
+		reg-names = "syscon_reg", "offset";
+	};
+
diff --git a/drivers/bootcount/Kconfig b/drivers/bootcount/Kconfig
index b5ccea0d9c..41a5c919d5 100644
--- a/drivers/bootcount/Kconfig
+++ b/drivers/bootcount/Kconfig
@@ -125,6 +125,18 @@ config BOOTCOUNT_MEM
 	  is not cleared on softreset.
 	  compatible = "u-boot,bootcount";
 
+config DM_BOOTCOUNT_SYSCON
+	bool "Support SYSCON devices as a backing store for bootcount"
+	select REGMAP
+	select SYSCON
+	help
+	  Enable reading/writing the bootcount value in a DM SYSCON device.
+	  The driver supports a fixed 32 bits size register using the native
+	  endianness. However, this can be controlled from the SYSCON DT node
+	  configuration.
+
+	  Accessing the backend is done using the regmap interface.
+
 endmenu
 
 endif
diff --git a/drivers/bootcount/Makefile b/drivers/bootcount/Makefile
index 51d860b00e..10fadf4fe9 100644
--- a/drivers/bootcount/Makefile
+++ b/drivers/bootcount/Makefile
@@ -13,3 +13,4 @@ obj-$(CONFIG_DM_BOOTCOUNT)      += bootcount-uclass.o
 obj-$(CONFIG_DM_BOOTCOUNT_RTC)  += rtc.o
 obj-$(CONFIG_DM_BOOTCOUNT_I2C_EEPROM)	+= i2c-eeprom.o
 obj-$(CONFIG_DM_BOOTCOUNT_SPI_FLASH)	+= spi-flash.o
+obj-$(CONFIG_DM_BOOTCOUNT_SYSCON) += bootcount_syscon.o
diff --git a/drivers/bootcount/bootcount_syscon.c b/drivers/bootcount/bootcount_syscon.c
new file mode 100644
index 0000000000..413fd5bb9d
--- /dev/null
+++ b/drivers/bootcount/bootcount_syscon.c
@@ -0,0 +1,159 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (c) Vaisala Oyj. All rights reserved.
+ */
+
+#include <common.h>
+#include <bootcount.h>
+#include <dm.h>
+#include <dm/device_compat.h>
+#include <linux/ioport.h>
+#include <regmap.h>
+#include <syscon.h>
+
+#define BYTES_TO_BITS(bytes) ((bytes) << 3)
+#define GEN_REG_MASK(val_size, val_addr)                                       \
+	(GENMASK(BYTES_TO_BITS(val_size) - 1, 0)                               \
+	 << (!!((val_addr) == 0x02) * BYTES_TO_BITS(2)))
+#define GET_DEFAULT_VALUE(val_size)                                            \
+	(CONFIG_SYS_BOOTCOUNT_MAGIC >>                                         \
+	 (BYTES_TO_BITS((sizeof(u32) - (val_size)))))
+
+/**
+ * struct bootcount_syscon_priv - driver's private data
+ *
+ * @regmap: syscon regmap
+ * @reg_addr: register address used to store the bootcount value
+ * @size: size of the bootcount value (2 or 4 bytes)
+ * @magic: magic used to validate/save the bootcount value
+ * @magic_mask: magic value bitmask
+ * @reg_mask: mask used to identify the location of the bootcount value
+ * in the register when 2 bytes length is used
+ * @shift: value used to extract the botcount value from the register
+ */
+struct bootcount_syscon_priv {
+	struct regmap *regmap;
+	fdt_addr_t reg_addr;
+	fdt_size_t size;
+	u32 magic;
+	u32 magic_mask;
+	u32 reg_mask;
+	int shift;
+};
+
+static int bootcount_syscon_set(struct udevice *dev, const u32 val)
+{
+	struct bootcount_syscon_priv *priv = dev_get_priv(dev);
+	u32 regval;
+
+	if ((val & priv->magic_mask) != 0)
+		return -EINVAL;
+
+	regval = (priv->magic & priv->magic_mask) | (val & ~priv->magic_mask);
+
+	if (priv->size == 2) {
+		regval &= 0xffff;
+		regval |= (regval & 0xffff) << BYTES_TO_BITS(priv->size);
+	}
+
+	debug("%s: Prepare to write reg value: 0x%08x with register mask: 0x%08x\n",
+	      __func__, regval, priv->reg_mask);
+
+	return regmap_update_bits(priv->regmap, priv->reg_addr, priv->reg_mask,
+				  regval);
+}
+
+static int bootcount_syscon_get(struct udevice *dev, u32 *val)
+{
+	struct bootcount_syscon_priv *priv = dev_get_priv(dev);
+	u32 regval;
+	int ret;
+
+	ret = regmap_read(priv->regmap, priv->reg_addr, &regval);
+	if (ret)
+		return ret;
+
+	regval &= priv->reg_mask;
+	regval >>= priv->shift;
+
+	if ((regval & priv->magic_mask) == (priv->magic & priv->magic_mask)) {
+		*val = regval & ~priv->magic_mask;
+	} else {
+		dev_err(dev, "%s: Invalid bootcount magic\n", __func__);
+		return -EINVAL;
+	}
+
+	debug("%s: Read bootcount value: 0x%08x from regval: 0x%08x\n",
+	      __func__, *val, regval);
+	return 0;
+}
+
+static int bootcount_syscon_of_to_plat(struct udevice *dev)
+{
+	struct bootcount_syscon_priv *priv = dev_get_priv(dev);
+	fdt_addr_t bootcount_offset;
+	fdt_size_t reg_size;
+
+	priv->regmap = syscon_regmap_lookup_by_phandle(dev, "syscon");
+	if (IS_ERR(priv->regmap)) {
+		dev_err(dev, "%s: Unable to find regmap (%ld)\n", __func__,
+			PTR_ERR(priv->regmap));
+		return PTR_ERR(priv->regmap);
+	}
+
+	priv->reg_addr = dev_read_addr_size_name(dev, "syscon_reg", &reg_size);
+	if (priv->reg_addr == FDT_ADDR_T_NONE) {
+		dev_err(dev, "%s: syscon_reg address not found\n", __func__);
+		return -EINVAL;
+	}
+	if (reg_size != 4) {
+		dev_err(dev, "%s: Unsupported register size: %d\n", __func__,
+			reg_size);
+		return -EINVAL;
+	}
+
+	bootcount_offset = dev_read_addr_size_name(dev, "offset", &priv->size);
+	if (bootcount_offset == FDT_ADDR_T_NONE) {
+		dev_err(dev, "%s: offset configuration not found\n", __func__);
+		return -EINVAL;
+	}
+	if (bootcount_offset + priv->size > reg_size) {
+		dev_err(dev,
+			"%s: Bootcount value doesn't fit in the reserved space\n",
+			__func__);
+		return -EINVAL;
+	}
+	if (priv->size != 2 && priv->size != 4) {
+		dev_err(dev,
+			"%s: Driver supports only 2 and 4 bytes bootcount size\n",
+			__func__);
+		return -EINVAL;
+	}
+
+	priv->magic = GET_DEFAULT_VALUE(priv->size);
+	priv->magic_mask = GENMASK(BYTES_TO_BITS(priv->size) - 1,
+				   BYTES_TO_BITS(priv->size >> 1));
+	priv->shift = !!(bootcount_offset == 0x02) * BYTES_TO_BITS(priv->size);
+	priv->reg_mask = GEN_REG_MASK(priv->size, bootcount_offset);
+
+	return 0;
+}
+
+static const struct bootcount_ops bootcount_syscon_ops = {
+	.get = bootcount_syscon_get,
+	.set = bootcount_syscon_set,
+};
+
+static const struct udevice_id bootcount_syscon_ids[] = {
+	{ .compatible = "u-boot,bootcount-syscon" },
+	{}
+};
+
+U_BOOT_DRIVER(bootcount_syscon) = {
+	.name = "bootcount-syscon",
+	.id = UCLASS_BOOTCOUNT,
+	.of_to_plat = bootcount_syscon_of_to_plat,
+	.priv_auto = sizeof(struct bootcount_syscon_priv),
+	.of_match = bootcount_syscon_ids,
+	.ops = &bootcount_syscon_ops,
+};
diff --git a/test/dm/bootcount.c b/test/dm/bootcount.c
index e0c47b5d7a..b77b472d1f 100644
--- a/test/dm/bootcount.c
+++ b/test/dm/bootcount.c
@@ -12,12 +12,13 @@
 #include <test/test.h>
 #include <test/ut.h>
 
-static int dm_test_bootcount(struct unit_test_state *uts)
+static int dm_test_bootcount_rtc(struct unit_test_state *uts)
 {
 	struct udevice *dev;
 	u32 val;
 
-	ut_assertok(uclass_get_device(UCLASS_BOOTCOUNT, 0, &dev));
+	ut_assertok(uclass_get_device_by_name(UCLASS_BOOTCOUNT, "bootcount at 0",
+					      &dev));
 	ut_assertok(dm_bootcount_set(dev, 0));
 	ut_assertok(dm_bootcount_get(dev, &val));
 	ut_assert(val == 0);
@@ -36,5 +37,46 @@ static int dm_test_bootcount(struct unit_test_state *uts)
 	return 0;
 }
 
-DM_TEST(dm_test_bootcount, UT_TESTF_SCAN_PDATA | UT_TESTF_SCAN_FDT);
+DM_TEST(dm_test_bootcount_rtc, UT_TESTF_SCAN_PDATA | UT_TESTF_SCAN_FDT);
 
+static int dm_test_bootcount_syscon_four_bytes(struct unit_test_state *uts)
+{
+	struct udevice *dev;
+	u32 val;
+
+	sandbox_set_enable_memio(true);
+	ut_assertok(uclass_get_device_by_name(UCLASS_BOOTCOUNT, "bootcount_4 at 0",
+					      &dev));
+	ut_assertok(dm_bootcount_set(dev, 0xab));
+	ut_assertok(dm_bootcount_get(dev, &val));
+	ut_assert(val == 0xab);
+	ut_assertok(dm_bootcount_set(dev, 0));
+	ut_assertok(dm_bootcount_get(dev, &val));
+	ut_assert(val == 0);
+
+	return 0;
+}
+
+DM_TEST(dm_test_bootcount_syscon_four_bytes,
+	UT_TESTF_SCAN_PDATA | UT_TESTF_SCAN_FDT);
+
+static int dm_test_bootcount_syscon_two_bytes(struct unit_test_state *uts)
+{
+	struct udevice *dev;
+	u32 val;
+
+	sandbox_set_enable_memio(true);
+	ut_assertok(uclass_get_device_by_name(UCLASS_BOOTCOUNT, "bootcount_2 at 0",
+					      &dev));
+	ut_assertok(dm_bootcount_set(dev, 0xab));
+	ut_assertok(dm_bootcount_get(dev, &val));
+	ut_assert(val == 0xab);
+	ut_assertok(dm_bootcount_set(dev, 0));
+	ut_assertok(dm_bootcount_get(dev, &val));
+	ut_assert(val == 0);
+
+	return 0;
+}
+
+DM_TEST(dm_test_bootcount_syscon_two_bytes,
+	UT_TESTF_SCAN_PDATA | UT_TESTF_SCAN_FDT);
-- 
2.26.3



More information about the U-Boot mailing list