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 <nandor.han@vaisala.com>
Reviewed-by: Simon Glass <sjg@chromium.org>
This commit is contained in:
Nandor Han 2021-06-10 15:40:38 +03:00 committed by Heiko Schocher
parent 7282b4352e
commit c50b21b705
7 changed files with 256 additions and 3 deletions

View file

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

View file

@ -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

View file

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

View file

@ -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

View file

@ -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

View file

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

View file

@ -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@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);