From c50b21b70523939c561d0455a2c423f63a9162ca Mon Sep 17 00:00:00 2001 From: Nandor Han Date: Thu, 10 Jun 2021 15:40:38 +0300 Subject: [PATCH] bootcount: add a new driver with syscon as backend 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 Reviewed-by: Simon Glass --- arch/sandbox/dts/test.dts | 14 ++ configs/sandbox_defconfig | 1 + doc/device-tree-bindings/bootcount-syscon.txt | 24 +++ drivers/bootcount/Kconfig | 12 ++ drivers/bootcount/Makefile | 1 + drivers/bootcount/bootcount_syscon.c | 159 ++++++++++++++++++ test/dm/bootcount.c | 48 +++++- 7 files changed, 256 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 d5976318d1..962bdbe556 100644 --- a/arch/sandbox/dts/test.dts +++ b/arch/sandbox/dts/test.dts @@ -731,6 +731,20 @@ i2c-eeprom = <&bootcount_i2c>; }; + bootcount_4@0 { + compatible = "u-boot,bootcount-syscon"; + syscon = <&syscon0>; + reg = <0x0 0x04>, <0x0 0x04>; + reg-names = "syscon_reg", "offset"; + }; + + bootcount_2@0 { + compatible = "u-boot,bootcount-syscon"; + syscon = <&syscon0>; + reg = <0x0 0x04>, <0x0 0x02> ; + reg-names = "syscon_reg", "offset"; + }; + adc: adc@0 { compatible = "sandbox,adc"; #io-channel-cells = <1>; diff --git a/configs/sandbox_defconfig b/configs/sandbox_defconfig index 952d430304..4658f18dfa 100644 --- a/configs/sandbox_defconfig +++ b/configs/sandbox_defconfig @@ -131,6 +131,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..e124f7b614 --- /dev/null +++ b/doc/device-tree-bindings/bootcount-syscon.txt @@ -0,0 +1,24 @@ +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@0 { + compatible = "sandbox,syscon0"; + reg = <0x10 16>; + }; + ... + bootcount@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 0de2b7bd78..607027c968 100644 --- a/drivers/bootcount/Kconfig +++ b/drivers/bootcount/Kconfig @@ -144,6 +144,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 12658ffdce..3a784bb0a6 100644 --- a/drivers/bootcount/Makefile +++ b/drivers/bootcount/Makefile @@ -14,3 +14,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 +#include +#include +#include +#include +#include +#include + +#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, ®val); + 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", ®_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 #include -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@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@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@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);