diff --git a/MAINTAINERS b/MAINTAINERS index 1ba36b62cc7..5912980614d 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -293,6 +293,11 @@ F: drivers/spi/spi-qup.c F: drivers/net/mdio-ipq4019.c F: drivers/rng/msm_rng.c +ARM LAYERSCAPE SFP +M: Sean Anderson +S: Maintained +F: drivers/misc/ls2_sfp.c + ARM MARVELL KIRKWOOD ARMADA-XP ARMADA-38X ARMADA-37XX ARMADA-7K/8K M: Stefan Roese S: Maintained diff --git a/drivers/misc/Kconfig b/drivers/misc/Kconfig index f368d52c561..31b10f989c1 100644 --- a/drivers/misc/Kconfig +++ b/drivers/misc/Kconfig @@ -289,6 +289,20 @@ config JZ4780_EFUSE help This selects support for the eFUSE on Ingenic JZ4780 SoCs. +config LS2_SFP + bool "Layerscape Security Fuse Processor" + depends on FSL_LSCH2 || ARCH_LS1021A + depends on MISC + imply DM_REGULATOR + help + This adds support for the Security Fuse Processor found on Layerscape + SoCs. It contains various fuses related to secure boot, including the + Super Root Key hash, One-Time-Programmable Master Key, Debug + Challenge/Response values, and others. Fuses are numbered according + to their four-byte offset from the start of the bank. + + If you don't need to read/program fuses, say 'n'. + config MXC_OCOTP bool "Enable MXC OCOTP Driver" depends on ARCH_IMX8M || ARCH_MX6 || ARCH_MX7 || ARCH_MX7ULP || ARCH_VF610 diff --git a/drivers/misc/Makefile b/drivers/misc/Makefile index 6c790308937..7d15e9f1f62 100644 --- a/drivers/misc/Makefile +++ b/drivers/misc/Makefile @@ -52,6 +52,7 @@ obj-$(CONFIG_IMX8ULP) += imx8ulp/ obj-$(CONFIG_LED_STATUS) += status_led.o obj-$(CONFIG_LED_STATUS_GPIO) += gpio_led.o obj-$(CONFIG_MPC83XX_SERDES) += mpc83xx_serdes.o +obj-$(CONFIG_$(SPL_TPL_)LS2_SFP) += ls2_sfp.o obj-$(CONFIG_$(SPL_)MXC_OCOTP) += mxc_ocotp.o obj-$(CONFIG_MXS_OCOTP) += mxs_ocotp.o obj-$(CONFIG_NUVOTON_NCT6102D) += nuvoton_nct6102d.o diff --git a/drivers/misc/ls2_sfp.c b/drivers/misc/ls2_sfp.c new file mode 100644 index 00000000000..dd104962c28 --- /dev/null +++ b/drivers/misc/ls2_sfp.c @@ -0,0 +1,350 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (C) 2022 Sean Anderson + * + * This driver supports the Security Fuse Processor device found on some + * Layerscape processors. At the moment, we only support a few processors. + * This driver was written with reference to the Layerscape SDK User + * Guide [1] and the ATF SFP driver [2]. + * + * [1] https://docs.nxp.com/bundle/GUID-487B2E69-BB19-42CB-AC38-7EF18C0FE3AE/page/GUID-27FC40AD-3321-4A82-B29E-7BB49EE94F23.html + * [2] https://source.codeaurora.org/external/qoriq/qoriq-components/atf/tree/drivers/nxp/sfp?h=github.com/master + */ + +#define LOG_CATEGORY UCLASS_MISC +#include +#include +#include +#include +#include +#include +#include +#include +#include + +DECLARE_GLOBAL_DATA_PTR; + +#define SFP_INGR 0x20 +#define SFP_SVHESR 0x24 +#define SFP_SFPCR 0x28 + +#define SFP_START 0x200 +#define SFP_END 0x284 +#define SFP_SIZE (SFP_END - SFP_START + 4) + +#define SFP_INGR_ERR BIT(8) +#define SFP_INGR_INST GENMASK(7, 0) + +#define SFP_INGR_READFB 0x01 +#define SFP_INGR_PROGFB 0x02 + +#define SFP_SFPCR_PPW GENMASK(15, 0) + +enum ls2_sfp_ioctl { + LS2_SFP_IOCTL_READ, + LS2_SFP_IOCTL_PROG, +}; + +/** + * struct ls2_sfp_priv - private data for LS2 SFP + * @base: Base address of SFP + * @supply: The (optional) supply for TA_PROG_SFP + * @programmed: Whether we've already programmed the fuses since the last + * reset. The SFP has a *very* limited amount of programming + * cycles (two to six, depending on the model), so we try and + * prevent accidentally performing additional programming + * cycles. + * @dirty: Whether the mirror registers have been written to (overridden) + * since we've last read the fuses (either as part of the reset + * process or using a READFB instruction). There is a much larger, + * but still finite, limit on the number of SFP read cycles (around + * 300,000), so we try and minimize reads as well. + */ +struct ls2_sfp_priv { + void __iomem *base; + struct udevice *supply; + bool programmed, dirty; +}; + +static u32 ls2_sfp_readl(struct ls2_sfp_priv *priv, ulong off) +{ + u32 val = be32_to_cpu(readl(priv->base + off)); + + log_debug("%08x = readl(%p)\n", val, priv->base + off); + return val; +} + +static void ls2_sfp_writel(struct ls2_sfp_priv *priv, ulong val, ulong off) +{ + log_debug("writel(%08lx, %p)\n", val, priv->base + off); + writel(cpu_to_be32(val), priv->base + off); +} + +static bool ls2_sfp_validate(struct udevice *dev, int offset, int size) +{ + if (offset < 0 || size < 0) { + dev_notice(dev, "size and offset must be positive\n"); + return false; + } + + if (offset & 3 || size & 3) { + dev_notice(dev, "size and offset must be multiples of 4\n"); + return false; + } + + if (offset + size > SFP_SIZE) { + dev_notice(dev, "size + offset must be <= %#x\n", SFP_SIZE); + return false; + } + + return true; +} + +static int ls2_sfp_read(struct udevice *dev, int offset, void *buf_bytes, + int size) +{ + int i; + struct ls2_sfp_priv *priv = dev_get_priv(dev); + u32 *buf = buf_bytes; + + if (!ls2_sfp_validate(dev, offset, size)) + return -EINVAL; + + for (i = 0; i < size; i += 4) + buf[i >> 2] = ls2_sfp_readl(priv, SFP_START + offset + i); + + return size; +} + +static int ls2_sfp_write(struct udevice *dev, int offset, + const void *buf_bytes, int size) +{ + int i; + struct ls2_sfp_priv *priv = dev_get_priv(dev); + const u32 *buf = buf_bytes; + + if (!ls2_sfp_validate(dev, offset, size)) + return -EINVAL; + + for (i = 0; i < size; i += 4) + ls2_sfp_writel(priv, buf[i >> 2], SFP_START + offset + i); + + priv->dirty = true; + return size; +} + +static int ls2_sfp_check_secret(struct udevice *dev) +{ + struct ls2_sfp_priv *priv = dev_get_priv(dev); + u32 svhesr = ls2_sfp_readl(priv, SFP_SVHESR); + + if (svhesr) { + dev_warn(dev, "secret value hamming error not zero: %08x\n", + svhesr); + return -EIO; + } + return 0; +} + +static int ls2_sfp_transaction(struct ls2_sfp_priv *priv, ulong inst) +{ + u32 ingr; + + ls2_sfp_writel(priv, inst, SFP_INGR); + + do { + ingr = ls2_sfp_readl(priv, SFP_INGR); + } while (FIELD_GET(SFP_INGR_INST, ingr)); + + return FIELD_GET(SFP_INGR_ERR, ingr) ? -EIO : 0; +} + +static int ls2_sfp_ioctl(struct udevice *dev, unsigned long request, void *buf) +{ + int ret; + struct ls2_sfp_priv *priv = dev_get_priv(dev); + + switch (request) { + case LS2_SFP_IOCTL_READ: + if (!priv->dirty) { + dev_dbg(dev, "ignoring read request, since fuses are not dirty\n"); + return 0; + } + + ret = ls2_sfp_transaction(priv, SFP_INGR_READFB); + if (ret) { + dev_err(dev, "error reading fuses\n"); + return ret; + } + + ls2_sfp_check_secret(dev); + priv->dirty = false; + return 0; + case LS2_SFP_IOCTL_PROG: + if (priv->programmed) { + dev_warn(dev, "fuses already programmed\n"); + return -EPERM; + } + + ret = ls2_sfp_check_secret(dev); + if (ret) + return ret; + + if (priv->supply) { + ret = regulator_set_enable(priv->supply, true); + if (ret) + return ret; + } + + ret = ls2_sfp_transaction(priv, SFP_INGR_PROGFB); + priv->programmed = true; + if (priv->supply) + regulator_set_enable(priv->supply, false); + + if (ret) + dev_err(dev, "error programming fuses\n"); + return ret; + default: + dev_dbg(dev, "unknown ioctl %lu\n", request); + return -EINVAL; + } +} + +static const struct misc_ops ls2_sfp_ops = { + .read = ls2_sfp_read, + .write = ls2_sfp_write, + .ioctl = ls2_sfp_ioctl, +}; + +static int ls2_sfp_probe(struct udevice *dev) +{ + int ret; + struct clk clk; + struct ls2_sfp_priv *priv = dev_get_priv(dev); + ulong rate; + + priv->base = dev_read_addr_ptr(dev); + if (!priv->base) { + dev_dbg(dev, "could not read register base\n"); + return -EINVAL; + } + + ret = device_get_supply_regulator(dev, "ta-sfp-prog", &priv->supply); + if (ret && ret != -ENODEV && ret != -ENOSYS) { + dev_dbg(dev, "problem getting supply (err %d)\n", ret); + return ret; + } + + ret = clk_get_by_name(dev, "sfp", &clk); + if (ret == -ENOSYS) { + rate = gd->bus_clk / 4; + } else if (ret) { + dev_dbg(dev, "could not get clock (err %d)\n", ret); + return ret; + } else { + ret = clk_enable(&clk); + if (ret) { + dev_dbg(dev, "could not enable clock (err %d)\n", ret); + return ret; + } + + rate = clk_get_rate(&clk); + clk_free(&clk); + if (!rate || IS_ERR_VALUE(rate)) { + ret = rate ? rate : -ENOENT; + dev_dbg(dev, "could not get clock rate (err %d)\n", + ret); + return ret; + } + } + + /* sfp clock in MHz * 12 */ + ls2_sfp_writel(priv, FIELD_PREP(SFP_SFPCR_PPW, rate * 12 / 1000000), + SFP_SFPCR); + + ls2_sfp_check_secret(dev); + return 0; +} + +static const struct udevice_id ls2_sfp_ids[] = { + { .compatible = "fsl,ls1021a-sfp" }, + { } +}; + +U_BOOT_DRIVER(ls2_sfp) = { + .name = "ls2_sfp", + .id = UCLASS_MISC, + .of_match = ls2_sfp_ids, + .probe = ls2_sfp_probe, + .ops = &ls2_sfp_ops, + .priv_auto = sizeof(struct ls2_sfp_priv), +}; + +static int ls2_sfp_device(struct udevice **dev) +{ + int ret = uclass_get_device_by_driver(UCLASS_MISC, + DM_DRIVER_GET(ls2_sfp), dev); + + if (ret) + log_debug("device not found (err %d)\n", ret); + return ret; +} + +int fuse_read(u32 bank, u32 word, u32 *val) +{ + int ret; + struct udevice *dev; + + ret = ls2_sfp_device(&dev); + if (ret) + return ret; + + ret = misc_ioctl(dev, LS2_SFP_IOCTL_READ, NULL); + if (ret) + return ret; + + ret = misc_read(dev, word << 2, val, sizeof(*val)); + return ret < 0 ? ret : 0; +} + +int fuse_sense(u32 bank, u32 word, u32 *val) +{ + int ret; + struct udevice *dev; + + ret = ls2_sfp_device(&dev); + if (ret) + return ret; + + ret = misc_read(dev, word << 2, val, sizeof(*val)); + return ret < 0 ? ret : 0; +} + +int fuse_prog(u32 bank, u32 word, u32 val) +{ + int ret; + struct udevice *dev; + + ret = ls2_sfp_device(&dev); + if (ret) + return ret; + + ret = misc_write(dev, word << 2, &val, sizeof(val)); + if (ret < 0) + return ret; + + return misc_ioctl(dev, LS2_SFP_IOCTL_PROG, NULL); +} + +int fuse_override(u32 bank, u32 word, u32 val) +{ + int ret; + struct udevice *dev; + + ret = ls2_sfp_device(&dev); + if (ret) + return ret; + + ret = misc_write(dev, word << 2, &val, sizeof(val)); + return ret < 0 ? ret : 0; +}