fpga: m10bmc-sec: add max10 secure update functions
authorRuss Weight <russell.h.weight@intel.com>
Mon, 6 Jun 2022 16:00:38 +0000 (09:00 -0700)
committerXu Yilun <yilun.xu@intel.com>
Wed, 8 Jun 2022 09:04:39 +0000 (17:04 +0800)
Create firmware upload ops and call the Firmware Upload support of the
Firmware Loader subsystem to enable FPGA image uploads for secure
updates of BMC images, FPGA images, etc.

Tested-by: Tianfei Zhang <tianfei.zhang@intel.com>
Signed-off-by: Russ Weight <russell.h.weight@intel.com>
Link: https://lore.kernel.org/r/20220606160038.846236-6-russell.h.weight@intel.com
Signed-off-by: Xu Yilun <yilun.xu@intel.com>
drivers/fpga/intel-m10-bmc-sec-update.c

index 65fec2a7090166e043a00272cdd32edf71dde136..72c677c910de3bda260d18248b10695169860bd3 100644 (file)
 struct m10bmc_sec {
        struct device *dev;
        struct intel_m10bmc *m10bmc;
+       struct fw_upload *fwl;
+       char *fw_name;
+       u32 fw_name_id;
+       bool cancel_request;
 };
 
+static DEFINE_XARRAY_ALLOC(fw_upload_xa);
+
 /* Root Entry Hash (REH) support */
 #define REH_SHA256_SIZE                32
 #define REH_SHA384_SIZE                48
@@ -192,10 +198,365 @@ static const struct attribute_group *m10bmc_sec_attr_groups[] = {
        NULL,
 };
 
+static void log_error_regs(struct m10bmc_sec *sec, u32 doorbell)
+{
+       u32 auth_result;
+
+       dev_err(sec->dev, "RSU error status: 0x%08x\n", doorbell);
+
+       if (!m10bmc_sys_read(sec->m10bmc, M10BMC_AUTH_RESULT, &auth_result))
+               dev_err(sec->dev, "RSU auth result: 0x%08x\n", auth_result);
+}
+
+static enum fw_upload_err rsu_check_idle(struct m10bmc_sec *sec)
+{
+       u32 doorbell;
+       int ret;
+
+       ret = m10bmc_sys_read(sec->m10bmc, M10BMC_DOORBELL, &doorbell);
+       if (ret)
+               return FW_UPLOAD_ERR_RW_ERROR;
+
+       if (rsu_prog(doorbell) != RSU_PROG_IDLE &&
+           rsu_prog(doorbell) != RSU_PROG_RSU_DONE) {
+               log_error_regs(sec, doorbell);
+               return FW_UPLOAD_ERR_BUSY;
+       }
+
+       return FW_UPLOAD_ERR_NONE;
+}
+
+static inline bool rsu_start_done(u32 doorbell)
+{
+       u32 status, progress;
+
+       if (doorbell & DRBL_RSU_REQUEST)
+               return false;
+
+       status = rsu_stat(doorbell);
+       if (status == RSU_STAT_ERASE_FAIL || status == RSU_STAT_WEAROUT)
+               return true;
+
+       progress = rsu_prog(doorbell);
+       if (progress != RSU_PROG_IDLE && progress != RSU_PROG_RSU_DONE)
+               return true;
+
+       return false;
+}
+
+static enum fw_upload_err rsu_update_init(struct m10bmc_sec *sec)
+{
+       u32 doorbell, status;
+       int ret;
+
+       ret = regmap_update_bits(sec->m10bmc->regmap,
+                                M10BMC_SYS_BASE + M10BMC_DOORBELL,
+                                DRBL_RSU_REQUEST | DRBL_HOST_STATUS,
+                                DRBL_RSU_REQUEST |
+                                FIELD_PREP(DRBL_HOST_STATUS,
+                                           HOST_STATUS_IDLE));
+       if (ret)
+               return FW_UPLOAD_ERR_RW_ERROR;
+
+       ret = regmap_read_poll_timeout(sec->m10bmc->regmap,
+                                      M10BMC_SYS_BASE + M10BMC_DOORBELL,
+                                      doorbell,
+                                      rsu_start_done(doorbell),
+                                      NIOS_HANDSHAKE_INTERVAL_US,
+                                      NIOS_HANDSHAKE_TIMEOUT_US);
+
+       if (ret == -ETIMEDOUT) {
+               log_error_regs(sec, doorbell);
+               return FW_UPLOAD_ERR_TIMEOUT;
+       } else if (ret) {
+               return FW_UPLOAD_ERR_RW_ERROR;
+       }
+
+       status = rsu_stat(doorbell);
+       if (status == RSU_STAT_WEAROUT) {
+               dev_warn(sec->dev, "Excessive flash update count detected\n");
+               return FW_UPLOAD_ERR_WEAROUT;
+       } else if (status == RSU_STAT_ERASE_FAIL) {
+               log_error_regs(sec, doorbell);
+               return FW_UPLOAD_ERR_HW_ERROR;
+       }
+
+       return FW_UPLOAD_ERR_NONE;
+}
+
+static enum fw_upload_err rsu_prog_ready(struct m10bmc_sec *sec)
+{
+       unsigned long poll_timeout;
+       u32 doorbell, progress;
+       int ret;
+
+       ret = m10bmc_sys_read(sec->m10bmc, M10BMC_DOORBELL, &doorbell);
+       if (ret)
+               return FW_UPLOAD_ERR_RW_ERROR;
+
+       poll_timeout = jiffies + msecs_to_jiffies(RSU_PREP_TIMEOUT_MS);
+       while (rsu_prog(doorbell) == RSU_PROG_PREPARE) {
+               msleep(RSU_PREP_INTERVAL_MS);
+               if (time_after(jiffies, poll_timeout))
+                       break;
+
+               ret = m10bmc_sys_read(sec->m10bmc, M10BMC_DOORBELL, &doorbell);
+               if (ret)
+                       return FW_UPLOAD_ERR_RW_ERROR;
+       }
+
+       progress = rsu_prog(doorbell);
+       if (progress == RSU_PROG_PREPARE) {
+               log_error_regs(sec, doorbell);
+               return FW_UPLOAD_ERR_TIMEOUT;
+       } else if (progress != RSU_PROG_READY) {
+               log_error_regs(sec, doorbell);
+               return FW_UPLOAD_ERR_HW_ERROR;
+       }
+
+       return FW_UPLOAD_ERR_NONE;
+}
+
+static enum fw_upload_err rsu_send_data(struct m10bmc_sec *sec)
+{
+       u32 doorbell;
+       int ret;
+
+       ret = regmap_update_bits(sec->m10bmc->regmap,
+                                M10BMC_SYS_BASE + M10BMC_DOORBELL,
+                                DRBL_HOST_STATUS,
+                                FIELD_PREP(DRBL_HOST_STATUS,
+                                           HOST_STATUS_WRITE_DONE));
+       if (ret)
+               return FW_UPLOAD_ERR_RW_ERROR;
+
+       ret = regmap_read_poll_timeout(sec->m10bmc->regmap,
+                                      M10BMC_SYS_BASE + M10BMC_DOORBELL,
+                                      doorbell,
+                                      rsu_prog(doorbell) != RSU_PROG_READY,
+                                      NIOS_HANDSHAKE_INTERVAL_US,
+                                      NIOS_HANDSHAKE_TIMEOUT_US);
+
+       if (ret == -ETIMEDOUT) {
+               log_error_regs(sec, doorbell);
+               return FW_UPLOAD_ERR_TIMEOUT;
+       } else if (ret) {
+               return FW_UPLOAD_ERR_RW_ERROR;
+       }
+
+       switch (rsu_stat(doorbell)) {
+       case RSU_STAT_NORMAL:
+       case RSU_STAT_NIOS_OK:
+       case RSU_STAT_USER_OK:
+       case RSU_STAT_FACTORY_OK:
+               break;
+       default:
+               log_error_regs(sec, doorbell);
+               return FW_UPLOAD_ERR_HW_ERROR;
+       }
+
+       return FW_UPLOAD_ERR_NONE;
+}
+
+static int rsu_check_complete(struct m10bmc_sec *sec, u32 *doorbell)
+{
+       if (m10bmc_sys_read(sec->m10bmc, M10BMC_DOORBELL, doorbell))
+               return -EIO;
+
+       switch (rsu_stat(*doorbell)) {
+       case RSU_STAT_NORMAL:
+       case RSU_STAT_NIOS_OK:
+       case RSU_STAT_USER_OK:
+       case RSU_STAT_FACTORY_OK:
+               break;
+       default:
+               return -EINVAL;
+       }
+
+       switch (rsu_prog(*doorbell)) {
+       case RSU_PROG_IDLE:
+       case RSU_PROG_RSU_DONE:
+               return 0;
+       case RSU_PROG_AUTHENTICATING:
+       case RSU_PROG_COPYING:
+       case RSU_PROG_UPDATE_CANCEL:
+       case RSU_PROG_PROGRAM_KEY_HASH:
+               return -EAGAIN;
+       default:
+               return -EINVAL;
+       }
+}
+
+static enum fw_upload_err rsu_cancel(struct m10bmc_sec *sec)
+{
+       u32 doorbell;
+       int ret;
+
+       ret = m10bmc_sys_read(sec->m10bmc, M10BMC_DOORBELL, &doorbell);
+       if (ret)
+               return FW_UPLOAD_ERR_RW_ERROR;
+
+       if (rsu_prog(doorbell) != RSU_PROG_READY)
+               return FW_UPLOAD_ERR_BUSY;
+
+       ret = regmap_update_bits(sec->m10bmc->regmap,
+                                M10BMC_SYS_BASE + M10BMC_DOORBELL,
+                                DRBL_HOST_STATUS,
+                                FIELD_PREP(DRBL_HOST_STATUS,
+                                           HOST_STATUS_ABORT_RSU));
+       if (ret)
+               return FW_UPLOAD_ERR_RW_ERROR;
+
+       return FW_UPLOAD_ERR_CANCELED;
+}
+
+static enum fw_upload_err m10bmc_sec_prepare(struct fw_upload *fwl,
+                                            const u8 *data, u32 size)
+{
+       struct m10bmc_sec *sec = fwl->dd_handle;
+       u32 ret;
+
+       sec->cancel_request = false;
+
+       if (!size || size > M10BMC_STAGING_SIZE)
+               return FW_UPLOAD_ERR_INVALID_SIZE;
+
+       ret = rsu_check_idle(sec);
+       if (ret != FW_UPLOAD_ERR_NONE)
+               return ret;
+
+       ret = rsu_update_init(sec);
+       if (ret != FW_UPLOAD_ERR_NONE)
+               return ret;
+
+       ret = rsu_prog_ready(sec);
+       if (ret != FW_UPLOAD_ERR_NONE)
+               return ret;
+
+       if (sec->cancel_request)
+               return rsu_cancel(sec);
+
+       return FW_UPLOAD_ERR_NONE;
+}
+
+#define WRITE_BLOCK_SIZE 0x4000        /* Default write-block size is 0x4000 bytes */
+
+static enum fw_upload_err m10bmc_sec_write(struct fw_upload *fwl, const u8 *data,
+                                          u32 offset, u32 size, u32 *written)
+{
+       struct m10bmc_sec *sec = fwl->dd_handle;
+       u32 blk_size, doorbell, extra_offset;
+       unsigned int stride, extra = 0;
+       int ret;
+
+       stride = regmap_get_reg_stride(sec->m10bmc->regmap);
+       if (sec->cancel_request)
+               return rsu_cancel(sec);
+
+       ret = m10bmc_sys_read(sec->m10bmc, M10BMC_DOORBELL, &doorbell);
+       if (ret) {
+               return FW_UPLOAD_ERR_RW_ERROR;
+       } else if (rsu_prog(doorbell) != RSU_PROG_READY) {
+               log_error_regs(sec, doorbell);
+               return FW_UPLOAD_ERR_HW_ERROR;
+       }
+
+       WARN_ON_ONCE(WRITE_BLOCK_SIZE % stride);
+       blk_size = min_t(u32, WRITE_BLOCK_SIZE, size);
+       ret = regmap_bulk_write(sec->m10bmc->regmap,
+                               M10BMC_STAGING_BASE + offset,
+                               (void *)data + offset,
+                               blk_size / stride);
+       if (ret)
+               return FW_UPLOAD_ERR_RW_ERROR;
+
+       /*
+        * If blk_size is not aligned to stride, then handle the extra
+        * bytes with regmap_write.
+        */
+       if (blk_size % stride) {
+               extra_offset = offset + ALIGN_DOWN(blk_size, stride);
+               memcpy(&extra, (u8 *)(data + extra_offset), blk_size % stride);
+               ret = regmap_write(sec->m10bmc->regmap,
+                                  M10BMC_STAGING_BASE + extra_offset, extra);
+               if (ret)
+                       return FW_UPLOAD_ERR_RW_ERROR;
+       }
+
+       *written = blk_size;
+       return FW_UPLOAD_ERR_NONE;
+}
+
+static enum fw_upload_err m10bmc_sec_poll_complete(struct fw_upload *fwl)
+{
+       struct m10bmc_sec *sec = fwl->dd_handle;
+       unsigned long poll_timeout;
+       u32 doorbell, result;
+       int ret;
+
+       if (sec->cancel_request)
+               return rsu_cancel(sec);
+
+       result = rsu_send_data(sec);
+       if (result != FW_UPLOAD_ERR_NONE)
+               return result;
+
+       poll_timeout = jiffies + msecs_to_jiffies(RSU_COMPLETE_TIMEOUT_MS);
+       do {
+               msleep(RSU_COMPLETE_INTERVAL_MS);
+               ret = rsu_check_complete(sec, &doorbell);
+       } while (ret == -EAGAIN && !time_after(jiffies, poll_timeout));
+
+       if (ret == -EAGAIN) {
+               log_error_regs(sec, doorbell);
+               return FW_UPLOAD_ERR_TIMEOUT;
+       } else if (ret == -EIO) {
+               return FW_UPLOAD_ERR_RW_ERROR;
+       } else if (ret) {
+               log_error_regs(sec, doorbell);
+               return FW_UPLOAD_ERR_HW_ERROR;
+       }
+
+       return FW_UPLOAD_ERR_NONE;
+}
+
+/*
+ * m10bmc_sec_cancel() may be called asynchronously with an on-going update.
+ * All other functions are called sequentially in a single thread. To avoid
+ * contention on register accesses, m10bmc_sec_cancel() must only update
+ * the cancel_request flag. Other functions will check this flag and handle
+ * the cancel request synchronously.
+ */
+static void m10bmc_sec_cancel(struct fw_upload *fwl)
+{
+       struct m10bmc_sec *sec = fwl->dd_handle;
+
+       sec->cancel_request = true;
+}
+
+static void m10bmc_sec_cleanup(struct fw_upload *fwl)
+{
+       struct m10bmc_sec *sec = fwl->dd_handle;
+
+       (void)rsu_cancel(sec);
+}
+
+static const struct fw_upload_ops m10bmc_ops = {
+       .prepare = m10bmc_sec_prepare,
+       .write = m10bmc_sec_write,
+       .poll_complete = m10bmc_sec_poll_complete,
+       .cancel = m10bmc_sec_cancel,
+       .cleanup = m10bmc_sec_cleanup,
+};
+
 #define SEC_UPDATE_LEN_MAX 32
 static int m10bmc_sec_probe(struct platform_device *pdev)
 {
+       char buf[SEC_UPDATE_LEN_MAX];
        struct m10bmc_sec *sec;
+       struct fw_upload *fwl;
+       unsigned int len;
+       int  ret;
 
        sec = devm_kzalloc(&pdev->dev, sizeof(*sec), GFP_KERNEL);
        if (!sec)
@@ -205,6 +566,38 @@ static int m10bmc_sec_probe(struct platform_device *pdev)
        sec->m10bmc = dev_get_drvdata(pdev->dev.parent);
        dev_set_drvdata(&pdev->dev, sec);
 
+       ret = xa_alloc(&fw_upload_xa, &sec->fw_name_id, sec,
+                      xa_limit_32b, GFP_KERNEL);
+       if (ret)
+               return ret;
+
+       len = scnprintf(buf, SEC_UPDATE_LEN_MAX, "secure-update%d",
+                       sec->fw_name_id);
+       sec->fw_name = kmemdup_nul(buf, len, GFP_KERNEL);
+       if (!sec->fw_name)
+               return -ENOMEM;
+
+       fwl = firmware_upload_register(THIS_MODULE, sec->dev, sec->fw_name,
+                                      &m10bmc_ops, sec);
+       if (IS_ERR(fwl)) {
+               dev_err(sec->dev, "Firmware Upload driver failed to start\n");
+               kfree(sec->fw_name);
+               xa_erase(&fw_upload_xa, sec->fw_name_id);
+               return PTR_ERR(fwl);
+       }
+
+       sec->fwl = fwl;
+       return 0;
+}
+
+static int m10bmc_sec_remove(struct platform_device *pdev)
+{
+       struct m10bmc_sec *sec = dev_get_drvdata(&pdev->dev);
+
+       firmware_upload_unregister(sec->fwl);
+       kfree(sec->fw_name);
+       xa_erase(&fw_upload_xa, sec->fw_name_id);
+
        return 0;
 }
 
@@ -218,6 +611,7 @@ MODULE_DEVICE_TABLE(platform, intel_m10bmc_sec_ids);
 
 static struct platform_driver intel_m10bmc_sec_driver = {
        .probe = m10bmc_sec_probe,
+       .remove = m10bmc_sec_remove,
        .driver = {
                .name = "intel-m10bmc-sec-update",
                .dev_groups = m10bmc_sec_attr_groups,