74c6317df0
The secure update driver does a sanity-check of the image size in comparison to the size of the staging area in FLASH. Instead of hard-wiring M10BMC_STAGING_SIZE, move the staging size to the m10bmc_csr_map structure to make the size assignment more flexible. Co-developed-by: Russ Weight <russell.h.weight@intel.com> Signed-off-by: Russ Weight <russell.h.weight@intel.com> Signed-off-by: Ilpo Järvinen <ilpo.jarvinen@linux.intel.com> Signed-off-by: Peter Colberg <peter.colberg@intel.com> Reviewed-by: Xu Yilun <yilun.xu@intel.com> Link: https://lore.kernel.org/r/20240402184925.1065932-1-peter.colberg@intel.com Signed-off-by: Lee Jones <lee@kernel.org>
458 lines
12 KiB
C
458 lines
12 KiB
C
// SPDX-License-Identifier: GPL-2.0
|
|
/*
|
|
* MAX10 BMC Platform Management Component Interface (PMCI) based
|
|
* interface.
|
|
*
|
|
* Copyright (C) 2020-2023 Intel Corporation.
|
|
*/
|
|
|
|
#include <linux/bitfield.h>
|
|
#include <linux/device.h>
|
|
#include <linux/dfl.h>
|
|
#include <linux/mfd/core.h>
|
|
#include <linux/mfd/intel-m10-bmc.h>
|
|
#include <linux/minmax.h>
|
|
#include <linux/module.h>
|
|
#include <linux/regmap.h>
|
|
|
|
struct m10bmc_pmci_device {
|
|
void __iomem *base;
|
|
struct intel_m10bmc m10bmc;
|
|
struct mutex flash_mutex; /* protects flash_busy and serializes flash read/read */
|
|
bool flash_busy;
|
|
};
|
|
|
|
/*
|
|
* Intel FGPA indirect register access via hardware controller/bridge.
|
|
*/
|
|
#define INDIRECT_CMD_OFF 0
|
|
#define INDIRECT_CMD_CLR 0
|
|
#define INDIRECT_CMD_RD BIT(0)
|
|
#define INDIRECT_CMD_WR BIT(1)
|
|
#define INDIRECT_CMD_ACK BIT(2)
|
|
|
|
#define INDIRECT_ADDR_OFF 0x4
|
|
#define INDIRECT_RD_OFF 0x8
|
|
#define INDIRECT_WR_OFF 0xc
|
|
|
|
#define INDIRECT_INT_US 1
|
|
#define INDIRECT_TIMEOUT_US 10000
|
|
|
|
struct indirect_ctx {
|
|
void __iomem *base;
|
|
struct device *dev;
|
|
};
|
|
|
|
static int indirect_clear_cmd(struct indirect_ctx *ctx)
|
|
{
|
|
unsigned int cmd;
|
|
int ret;
|
|
|
|
writel(INDIRECT_CMD_CLR, ctx->base + INDIRECT_CMD_OFF);
|
|
|
|
ret = readl_poll_timeout(ctx->base + INDIRECT_CMD_OFF, cmd,
|
|
cmd == INDIRECT_CMD_CLR,
|
|
INDIRECT_INT_US, INDIRECT_TIMEOUT_US);
|
|
if (ret)
|
|
dev_err(ctx->dev, "timed out waiting clear cmd (residual cmd=0x%x)\n", cmd);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int indirect_reg_read(void *context, unsigned int reg, unsigned int *val)
|
|
{
|
|
struct indirect_ctx *ctx = context;
|
|
unsigned int cmd, ack, tmpval;
|
|
int ret, ret2;
|
|
|
|
cmd = readl(ctx->base + INDIRECT_CMD_OFF);
|
|
if (cmd != INDIRECT_CMD_CLR)
|
|
dev_warn(ctx->dev, "residual cmd 0x%x on read entry\n", cmd);
|
|
|
|
writel(reg, ctx->base + INDIRECT_ADDR_OFF);
|
|
writel(INDIRECT_CMD_RD, ctx->base + INDIRECT_CMD_OFF);
|
|
|
|
ret = readl_poll_timeout(ctx->base + INDIRECT_CMD_OFF, ack,
|
|
(ack & INDIRECT_CMD_ACK) == INDIRECT_CMD_ACK,
|
|
INDIRECT_INT_US, INDIRECT_TIMEOUT_US);
|
|
if (ret)
|
|
dev_err(ctx->dev, "read timed out on reg 0x%x ack 0x%x\n", reg, ack);
|
|
else
|
|
tmpval = readl(ctx->base + INDIRECT_RD_OFF);
|
|
|
|
ret2 = indirect_clear_cmd(ctx);
|
|
|
|
if (ret)
|
|
return ret;
|
|
if (ret2)
|
|
return ret2;
|
|
|
|
*val = tmpval;
|
|
return 0;
|
|
}
|
|
|
|
static int indirect_reg_write(void *context, unsigned int reg, unsigned int val)
|
|
{
|
|
struct indirect_ctx *ctx = context;
|
|
unsigned int cmd, ack;
|
|
int ret, ret2;
|
|
|
|
cmd = readl(ctx->base + INDIRECT_CMD_OFF);
|
|
if (cmd != INDIRECT_CMD_CLR)
|
|
dev_warn(ctx->dev, "residual cmd 0x%x on write entry\n", cmd);
|
|
|
|
writel(val, ctx->base + INDIRECT_WR_OFF);
|
|
writel(reg, ctx->base + INDIRECT_ADDR_OFF);
|
|
writel(INDIRECT_CMD_WR, ctx->base + INDIRECT_CMD_OFF);
|
|
|
|
ret = readl_poll_timeout(ctx->base + INDIRECT_CMD_OFF, ack,
|
|
(ack & INDIRECT_CMD_ACK) == INDIRECT_CMD_ACK,
|
|
INDIRECT_INT_US, INDIRECT_TIMEOUT_US);
|
|
if (ret)
|
|
dev_err(ctx->dev, "write timed out on reg 0x%x ack 0x%x\n", reg, ack);
|
|
|
|
ret2 = indirect_clear_cmd(ctx);
|
|
|
|
if (ret)
|
|
return ret;
|
|
return ret2;
|
|
}
|
|
|
|
static void pmci_write_fifo(void __iomem *base, const u32 *buf, size_t count)
|
|
{
|
|
while (count--)
|
|
writel(*buf++, base);
|
|
}
|
|
|
|
static void pmci_read_fifo(void __iomem *base, u32 *buf, size_t count)
|
|
{
|
|
while (count--)
|
|
*buf++ = readl(base);
|
|
}
|
|
|
|
static u32 pmci_get_write_space(struct m10bmc_pmci_device *pmci)
|
|
{
|
|
u32 val;
|
|
int ret;
|
|
|
|
ret = read_poll_timeout(readl, val,
|
|
FIELD_GET(M10BMC_N6000_FLASH_FIFO_SPACE, val) ==
|
|
M10BMC_N6000_FIFO_MAX_WORDS,
|
|
M10BMC_FLASH_INT_US, M10BMC_FLASH_TIMEOUT_US,
|
|
false, pmci->base + M10BMC_N6000_FLASH_CTRL);
|
|
if (ret == -ETIMEDOUT)
|
|
return 0;
|
|
|
|
return FIELD_GET(M10BMC_N6000_FLASH_FIFO_SPACE, val) * M10BMC_N6000_FIFO_WORD_SIZE;
|
|
}
|
|
|
|
static int pmci_flash_bulk_write(struct intel_m10bmc *m10bmc, const u8 *buf, u32 size)
|
|
{
|
|
struct m10bmc_pmci_device *pmci = container_of(m10bmc, struct m10bmc_pmci_device, m10bmc);
|
|
u32 blk_size, offset = 0, write_count;
|
|
|
|
while (size) {
|
|
blk_size = min(pmci_get_write_space(pmci), size);
|
|
if (blk_size == 0) {
|
|
dev_err(m10bmc->dev, "get FIFO available size fail\n");
|
|
return -EIO;
|
|
}
|
|
|
|
if (size < M10BMC_N6000_FIFO_WORD_SIZE)
|
|
break;
|
|
|
|
write_count = blk_size / M10BMC_N6000_FIFO_WORD_SIZE;
|
|
pmci_write_fifo(pmci->base + M10BMC_N6000_FLASH_FIFO,
|
|
(u32 *)(buf + offset), write_count);
|
|
|
|
size -= blk_size;
|
|
offset += blk_size;
|
|
}
|
|
|
|
/* Handle remainder (less than M10BMC_N6000_FIFO_WORD_SIZE bytes) */
|
|
if (size) {
|
|
u32 tmp = 0;
|
|
|
|
memcpy(&tmp, buf + offset, size);
|
|
pmci_write_fifo(pmci->base + M10BMC_N6000_FLASH_FIFO, &tmp, 1);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int pmci_flash_bulk_read(struct intel_m10bmc *m10bmc, u8 *buf, u32 addr, u32 size)
|
|
{
|
|
struct m10bmc_pmci_device *pmci = container_of(m10bmc, struct m10bmc_pmci_device, m10bmc);
|
|
u32 blk_size, offset = 0, val, full_read_count, read_count;
|
|
int ret;
|
|
|
|
while (size) {
|
|
blk_size = min_t(u32, size, M10BMC_N6000_READ_BLOCK_SIZE);
|
|
full_read_count = blk_size / M10BMC_N6000_FIFO_WORD_SIZE;
|
|
|
|
read_count = full_read_count;
|
|
if (full_read_count * M10BMC_N6000_FIFO_WORD_SIZE < blk_size)
|
|
read_count++;
|
|
|
|
writel(addr + offset, pmci->base + M10BMC_N6000_FLASH_ADDR);
|
|
writel(FIELD_PREP(M10BMC_N6000_FLASH_READ_COUNT, read_count) |
|
|
M10BMC_N6000_FLASH_RD_MODE,
|
|
pmci->base + M10BMC_N6000_FLASH_CTRL);
|
|
|
|
ret = readl_poll_timeout((pmci->base + M10BMC_N6000_FLASH_CTRL), val,
|
|
!(val & M10BMC_N6000_FLASH_BUSY),
|
|
M10BMC_FLASH_INT_US, M10BMC_FLASH_TIMEOUT_US);
|
|
if (ret) {
|
|
dev_err(m10bmc->dev, "read timed out on reading flash 0x%xn", val);
|
|
return ret;
|
|
}
|
|
|
|
pmci_read_fifo(pmci->base + M10BMC_N6000_FLASH_FIFO,
|
|
(u32 *)(buf + offset), full_read_count);
|
|
|
|
size -= blk_size;
|
|
offset += blk_size;
|
|
|
|
if (full_read_count < read_count)
|
|
break;
|
|
|
|
writel(0, pmci->base + M10BMC_N6000_FLASH_CTRL);
|
|
}
|
|
|
|
/* Handle remainder (less than M10BMC_N6000_FIFO_WORD_SIZE bytes) */
|
|
if (size) {
|
|
u32 tmp;
|
|
|
|
pmci_read_fifo(pmci->base + M10BMC_N6000_FLASH_FIFO, &tmp, 1);
|
|
memcpy(buf + offset, &tmp, size);
|
|
|
|
writel(0, pmci->base + M10BMC_N6000_FLASH_CTRL);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int m10bmc_pmci_set_flash_host_mux(struct intel_m10bmc *m10bmc, bool request)
|
|
{
|
|
u32 ctrl;
|
|
int ret;
|
|
|
|
ret = regmap_update_bits(m10bmc->regmap, M10BMC_N6000_FLASH_MUX_CTRL,
|
|
M10BMC_N6000_FLASH_HOST_REQUEST,
|
|
FIELD_PREP(M10BMC_N6000_FLASH_HOST_REQUEST, request));
|
|
if (ret)
|
|
return ret;
|
|
|
|
return regmap_read_poll_timeout(m10bmc->regmap,
|
|
M10BMC_N6000_FLASH_MUX_CTRL, ctrl,
|
|
request ?
|
|
(get_flash_mux(ctrl) == M10BMC_N6000_FLASH_MUX_HOST) :
|
|
(get_flash_mux(ctrl) != M10BMC_N6000_FLASH_MUX_HOST),
|
|
M10BMC_FLASH_INT_US, M10BMC_FLASH_TIMEOUT_US);
|
|
}
|
|
|
|
static int m10bmc_pmci_flash_read(struct intel_m10bmc *m10bmc, u8 *buf, u32 addr, u32 size)
|
|
{
|
|
struct m10bmc_pmci_device *pmci = container_of(m10bmc, struct m10bmc_pmci_device, m10bmc);
|
|
int ret, ret2;
|
|
|
|
mutex_lock(&pmci->flash_mutex);
|
|
if (pmci->flash_busy) {
|
|
ret = -EBUSY;
|
|
goto unlock;
|
|
}
|
|
|
|
ret = m10bmc_pmci_set_flash_host_mux(m10bmc, true);
|
|
if (ret)
|
|
goto mux_fail;
|
|
|
|
ret = pmci_flash_bulk_read(m10bmc, buf, addr, size);
|
|
|
|
mux_fail:
|
|
ret2 = m10bmc_pmci_set_flash_host_mux(m10bmc, false);
|
|
|
|
unlock:
|
|
mutex_unlock(&pmci->flash_mutex);
|
|
if (ret)
|
|
return ret;
|
|
return ret2;
|
|
}
|
|
|
|
static int m10bmc_pmci_flash_write(struct intel_m10bmc *m10bmc, const u8 *buf, u32 offset, u32 size)
|
|
{
|
|
struct m10bmc_pmci_device *pmci = container_of(m10bmc, struct m10bmc_pmci_device, m10bmc);
|
|
int ret;
|
|
|
|
mutex_lock(&pmci->flash_mutex);
|
|
WARN_ON_ONCE(!pmci->flash_busy);
|
|
/* On write, firmware manages flash MUX */
|
|
ret = pmci_flash_bulk_write(m10bmc, buf + offset, size);
|
|
mutex_unlock(&pmci->flash_mutex);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int m10bmc_pmci_flash_lock(struct intel_m10bmc *m10bmc)
|
|
{
|
|
struct m10bmc_pmci_device *pmci = container_of(m10bmc, struct m10bmc_pmci_device, m10bmc);
|
|
int ret = 0;
|
|
|
|
mutex_lock(&pmci->flash_mutex);
|
|
if (pmci->flash_busy) {
|
|
ret = -EBUSY;
|
|
goto unlock;
|
|
}
|
|
|
|
pmci->flash_busy = true;
|
|
|
|
unlock:
|
|
mutex_unlock(&pmci->flash_mutex);
|
|
return ret;
|
|
}
|
|
|
|
static void m10bmc_pmci_flash_unlock(struct intel_m10bmc *m10bmc)
|
|
{
|
|
struct m10bmc_pmci_device *pmci = container_of(m10bmc, struct m10bmc_pmci_device, m10bmc);
|
|
|
|
mutex_lock(&pmci->flash_mutex);
|
|
WARN_ON_ONCE(!pmci->flash_busy);
|
|
pmci->flash_busy = false;
|
|
mutex_unlock(&pmci->flash_mutex);
|
|
}
|
|
|
|
static const struct intel_m10bmc_flash_bulk_ops m10bmc_pmci_flash_bulk_ops = {
|
|
.read = m10bmc_pmci_flash_read,
|
|
.write = m10bmc_pmci_flash_write,
|
|
.lock_write = m10bmc_pmci_flash_lock,
|
|
.unlock_write = m10bmc_pmci_flash_unlock,
|
|
};
|
|
|
|
static const struct regmap_range m10bmc_pmci_regmap_range[] = {
|
|
regmap_reg_range(M10BMC_N6000_SYS_BASE, M10BMC_N6000_SYS_END),
|
|
};
|
|
|
|
static const struct regmap_access_table m10bmc_pmci_access_table = {
|
|
.yes_ranges = m10bmc_pmci_regmap_range,
|
|
.n_yes_ranges = ARRAY_SIZE(m10bmc_pmci_regmap_range),
|
|
};
|
|
|
|
static struct regmap_config m10bmc_pmci_regmap_config = {
|
|
.reg_bits = 32,
|
|
.reg_stride = 4,
|
|
.val_bits = 32,
|
|
.wr_table = &m10bmc_pmci_access_table,
|
|
.rd_table = &m10bmc_pmci_access_table,
|
|
.reg_read = &indirect_reg_read,
|
|
.reg_write = &indirect_reg_write,
|
|
.max_register = M10BMC_N6000_SYS_END,
|
|
};
|
|
|
|
static struct mfd_cell m10bmc_pmci_n6000_bmc_subdevs[] = {
|
|
{ .name = "n6000bmc-hwmon" },
|
|
{ .name = "n6000bmc-sec-update" },
|
|
};
|
|
|
|
static const struct m10bmc_csr_map m10bmc_n6000_csr_map = {
|
|
.base = M10BMC_N6000_SYS_BASE,
|
|
.build_version = M10BMC_N6000_BUILD_VER,
|
|
.fw_version = NIOS2_N6000_FW_VERSION,
|
|
.mac_low = M10BMC_N6000_MAC_LOW,
|
|
.mac_high = M10BMC_N6000_MAC_HIGH,
|
|
.doorbell = M10BMC_N6000_DOORBELL,
|
|
.auth_result = M10BMC_N6000_AUTH_RESULT,
|
|
.bmc_prog_addr = M10BMC_N6000_BMC_PROG_ADDR,
|
|
.bmc_reh_addr = M10BMC_N6000_BMC_REH_ADDR,
|
|
.bmc_magic = M10BMC_N6000_BMC_PROG_MAGIC,
|
|
.sr_prog_addr = M10BMC_N6000_SR_PROG_ADDR,
|
|
.sr_reh_addr = M10BMC_N6000_SR_REH_ADDR,
|
|
.sr_magic = M10BMC_N6000_SR_PROG_MAGIC,
|
|
.pr_prog_addr = M10BMC_N6000_PR_PROG_ADDR,
|
|
.pr_reh_addr = M10BMC_N6000_PR_REH_ADDR,
|
|
.pr_magic = M10BMC_N6000_PR_PROG_MAGIC,
|
|
.rsu_update_counter = M10BMC_N6000_STAGING_FLASH_COUNT,
|
|
.staging_size = M10BMC_STAGING_SIZE,
|
|
};
|
|
|
|
static const struct intel_m10bmc_platform_info m10bmc_pmci_n6000 = {
|
|
.cells = m10bmc_pmci_n6000_bmc_subdevs,
|
|
.n_cells = ARRAY_SIZE(m10bmc_pmci_n6000_bmc_subdevs),
|
|
.csr_map = &m10bmc_n6000_csr_map,
|
|
};
|
|
|
|
static int m10bmc_pmci_probe(struct dfl_device *ddev)
|
|
{
|
|
struct device *dev = &ddev->dev;
|
|
struct m10bmc_pmci_device *pmci;
|
|
struct indirect_ctx *ctx;
|
|
int ret;
|
|
|
|
pmci = devm_kzalloc(dev, sizeof(*pmci), GFP_KERNEL);
|
|
if (!pmci)
|
|
return -ENOMEM;
|
|
|
|
pmci->m10bmc.flash_bulk_ops = &m10bmc_pmci_flash_bulk_ops;
|
|
pmci->m10bmc.dev = dev;
|
|
|
|
pmci->base = devm_ioremap_resource(dev, &ddev->mmio_res);
|
|
if (IS_ERR(pmci->base))
|
|
return PTR_ERR(pmci->base);
|
|
|
|
ctx = devm_kzalloc(dev, sizeof(*ctx), GFP_KERNEL);
|
|
if (!ctx)
|
|
return -ENOMEM;
|
|
|
|
mutex_init(&pmci->flash_mutex);
|
|
|
|
ctx->base = pmci->base + M10BMC_N6000_INDIRECT_BASE;
|
|
ctx->dev = dev;
|
|
indirect_clear_cmd(ctx);
|
|
pmci->m10bmc.regmap = devm_regmap_init(dev, NULL, ctx, &m10bmc_pmci_regmap_config);
|
|
|
|
if (IS_ERR(pmci->m10bmc.regmap)) {
|
|
ret = PTR_ERR(pmci->m10bmc.regmap);
|
|
goto destroy_mutex;
|
|
}
|
|
|
|
ret = m10bmc_dev_init(&pmci->m10bmc, &m10bmc_pmci_n6000);
|
|
if (ret)
|
|
goto destroy_mutex;
|
|
return 0;
|
|
|
|
destroy_mutex:
|
|
mutex_destroy(&pmci->flash_mutex);
|
|
return ret;
|
|
}
|
|
|
|
static void m10bmc_pmci_remove(struct dfl_device *ddev)
|
|
{
|
|
struct intel_m10bmc *m10bmc = dev_get_drvdata(&ddev->dev);
|
|
struct m10bmc_pmci_device *pmci = container_of(m10bmc, struct m10bmc_pmci_device, m10bmc);
|
|
|
|
mutex_destroy(&pmci->flash_mutex);
|
|
}
|
|
|
|
#define FME_FEATURE_ID_M10BMC_PMCI 0x12
|
|
|
|
static const struct dfl_device_id m10bmc_pmci_ids[] = {
|
|
{ FME_ID, FME_FEATURE_ID_M10BMC_PMCI },
|
|
{ }
|
|
};
|
|
MODULE_DEVICE_TABLE(dfl, m10bmc_pmci_ids);
|
|
|
|
static struct dfl_driver m10bmc_pmci_driver = {
|
|
.drv = {
|
|
.name = "intel-m10-bmc",
|
|
.dev_groups = m10bmc_dev_groups,
|
|
},
|
|
.id_table = m10bmc_pmci_ids,
|
|
.probe = m10bmc_pmci_probe,
|
|
.remove = m10bmc_pmci_remove,
|
|
};
|
|
|
|
module_dfl_driver(m10bmc_pmci_driver);
|
|
|
|
MODULE_DESCRIPTION("MAX10 BMC PMCI-based interface");
|
|
MODULE_AUTHOR("Intel Corporation");
|
|
MODULE_LICENSE("GPL");
|
|
MODULE_IMPORT_NS(INTEL_M10_BMC_CORE);
|