arm: layerscape: Add sfp driver
authorSean Anderson <sean.anderson@seco.com>
Fri, 22 Apr 2022 18:34:18 +0000 (14:34 -0400)
committerPeng Fan <peng.fan@nxp.com>
Mon, 20 Jun 2022 01:18:25 +0000 (09:18 +0800)
This adds a driver for the Security Fuse Processor (SFP) present on
LS1012A, LS1021A, LS1043A, and LS1046A processors. It holds the
Super-Root Key (SRK), One-Time-Programmable Master Key (OTPMK), and
other "security" related fuses. Similar devices (sharing the same name)
are present on other processors, but for the moment this just supports
the LS2 variants.

The mirror registers are loaded during power-on reset. All mirror
registers must be programmed or read at once. Because of this, `fuse
prog` will program all fuses, even though only one might be specified.
To prevent accidentally burning through all your fuse programming cycles
with something like `fuse prog 0 0 A B C D`, we limit ourselves to one
programming cycle per reset. Fuses are numbered based on their address.
The fuse at 0x1e80200 is 0, the fuse at 0x1e80204 is 1, etc.

The TA_PROG_SFP supply must be enabled when programming fuses, but must
be disabled when reading them. Typically this supply is enabled by
inserting a jumper or by setting a register in the board's FPGA. I've
also added support for using a regulator. This could be helpful for
automatically issuing the FPGA write, or for toggling a GPIO controlling
the supply.

I suggest using the following procedure for programming:

1. Override the fuses you wish to program
   => fuse override 0 2 A B C D
2. Inspect the values and ensure that they are what you expect
   => fuse sense 0 2 4
3. Enable TA_PROG_SFP
4. Issue a program command using OSPR0 as a dummy. Since it contains the
   write-protect bit you will usually want to write it last anyway.
   => fuse prog 0 0 0
5. Disable TA_PROG_SFP
6. Read back the fuses and ensure they are correct
   => fuse read 0 2 4

Signed-off-by: Sean Anderson <sean.anderson@seco.com>
MAINTAINERS
drivers/misc/Kconfig
drivers/misc/Makefile
drivers/misc/ls2_sfp.c [new file with mode: 0644]

index 1ba36b6..5912980 100644 (file)
@@ -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 <sean.anderson@seco.com>
+S:     Maintained
+F:     drivers/misc/ls2_sfp.c
+
 ARM MARVELL KIRKWOOD ARMADA-XP ARMADA-38X ARMADA-37XX ARMADA-7K/8K
 M:     Stefan Roese <sr@denx.de>
 S:     Maintained
index f368d52..31b10f9 100644 (file)
@@ -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
index 6c79030..7d15e9f 100644 (file)
@@ -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 (file)
index 0000000..dd10496
--- /dev/null
@@ -0,0 +1,350 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Copyright (C) 2022 Sean Anderson <sean.anderson@seco.com>
+ *
+ * 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 <common.h>
+#include <clk.h>
+#include <fuse.h>
+#include <misc.h>
+#include <asm/io.h>
+#include <dm/device_compat.h>
+#include <dm/read.h>
+#include <linux/bitfield.h>
+#include <power/regulator.h>
+
+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;
+}