* [PATCH v1 0/1] fpga: m10bmc-sec: add image_load sysfs for N3000/D5005
@ 2026-07-15 7:38 Ionut Nechita (Wind River)
2026-07-15 7:38 ` [PATCH v1 1/1] fpga: m10bmc-sec: add image_load sysfs for N3000 and D5005 Ionut Nechita (Wind River)
0 siblings, 1 reply; 2+ messages in thread
From: Ionut Nechita (Wind River) @ 2026-07-15 7:38 UTC (permalink / raw)
To: Xu Yilun
Cc: Tom Rix, Moritz Fischer, Lee Jones, Ilpo Järvinen,
Ionut Nechita, linux-fpga, mfd, linux-kernel
From: Ionut Nechita <ionut.nechita@windriver.com>
The mainline Intel MAX10 BMC secure-update driver can stage and flash a
new image through the firmware-upload ABI, but it has no way to trigger a
reload of an image that is already resident in FLASH or EEPROM. OPAE
userspace needs that trigger to switch between the BMC factory/user
images and to reload the PKVL retimer firmware without running a full
firmware-upload cycle.
This series adds the control/available_images and control/image_load
sysfs attributes for the N3000 and D5005 cards, restoring image-reload
parity with linux-dfl for those two boards:
- N3000: bmc_factory, bmc_user, retimer_fw
- D5005: bmc_factory, bmc_user (config-select polarity inverted vs
N3000, hence a separate ops/handler table)
The control group is only visible when the device ops provide an
image_load table, so N6000 (.image_load = NULL) is unaffected. The N6000
PMCI and SDM image-load handlers are deliberately not included here; they
depend on the wider PMCI stack that has not been upstreamed yet.
The code is derived from the linux-dfl commits 604ad4f48cee and
e1b885714f68 by Russ Weight, Tianfei Zhang, Xu Yilun and Ilpo Järvinen,
adapted to the current mainline driver structure. New sysfs attributes
are documented under Documentation/ABI/testing/.
No functional change for N6000/PMCI/SDM devices. checkpatch --strict is
clean and the driver builds as a module on x86_64.
Ionut Nechita (1):
fpga: m10bmc-sec: add image_load sysfs for N3000 and D5005
.../sysfs-driver-intel-m10-bmc-sec-update | 23 ++
drivers/fpga/intel-m10-bmc-sec-update.c | 286 +++++++++++++++++-
include/linux/mfd/intel-m10-bmc.h | 31 ++
3 files changed, 339 insertions(+), 1 deletion(-)
base-commit: cc2b5f627e8ccbae1188ef2d8be3e451d7f933a5
--
2.55.0
^ permalink raw reply [flat|nested] 2+ messages in thread
* [PATCH v1 1/1] fpga: m10bmc-sec: add image_load sysfs for N3000 and D5005
2026-07-15 7:38 [PATCH v1 0/1] fpga: m10bmc-sec: add image_load sysfs for N3000/D5005 Ionut Nechita (Wind River)
@ 2026-07-15 7:38 ` Ionut Nechita (Wind River)
0 siblings, 0 replies; 2+ messages in thread
From: Ionut Nechita (Wind River) @ 2026-07-15 7:38 UTC (permalink / raw)
To: Xu Yilun
Cc: Tom Rix, Moritz Fischer, Lee Jones, Ilpo Järvinen,
Ionut Nechita, linux-fpga, mfd, linux-kernel, Russ Weight,
Tianfei Zhang
From: Ionut Nechita <ionut.nechita@windriver.com>
The Intel MAX10 BMC secure-update driver in mainline exposes only the
firmware-upload ABI (/sys/class/firmware/secure-updateN) for staging and
flashing a new image. It has no way to trigger a reload of an image that
is already present in FLASH or EEPROM, which OPAE userspace relies on to
switch between the BMC factory/user images or to reload the PKVL retimer
firmware without a full firmware-upload cycle.
Add the control/available_images and control/image_load sysfs attributes
for the N3000 and D5005 cards. available_images returns the space
separated list of image key words accepted by the device, and writing
one of those key words to image_load triggers the corresponding reload:
- bmc_factory / bmc_user reload a BMC image by writing DRBL_CONFIG_SEL
and DRBL_REBOOT_REQ to the doorbell register. The config-select
polarity is inverted between N3000 and D5005, so the two cards use
separate handler tables and a separate m10sec_d5005_ops.
- retimer_fw (N3000 only) reloads the PKVL retimer EEPROM by asserting
DRBL_PKVL_EEPROM_LOAD_SEC, waiting for RSU_PROG_PKVL_PROM_DONE (or a
RSU_STAT_PKVL_REJECT for a duplicate image), then polling
M10BMC_PKVL_POLL_CTRL for preload completion.
The control group is hidden via its is_visible() callback whenever the
device ops do not provide an image_load table, so N6000 (which keeps
.image_load = NULL) is unaffected.
The M10BMC_PKVL_POLL_CTRL register and its bit definitions are added to
intel-m10-bmc.h for the retimer path.
This is a subset of the functionality carried in linux-dfl, ported to
the mainline driver structure. It is derived from the linux-dfl
commit 604ad4f48cee ("fpga: m10bmc-sec: add sysfs to load bmc images")
and commit e1b885714f68 ("fpga: m10bmc-sec: m10bmc_sec_retimer_load
callback") by Russ Weight, Tianfei Zhang, Xu Yilun and Ilpo Järvinen.
The N6000 PMCI and SDM image-load handlers are intentionally left out;
they depend on the wider PMCI stack that has not been upstreamed.
Cc: Russ Weight <russell.h.weight@intel.com>
Cc: Tianfei Zhang <tianfei.zhang@intel.com>
Cc: Xu Yilun <yilun.xu@intel.com>
Cc: Ilpo Järvinen <ilpo.jarvinen@linux.intel.com>
Assisted-by: Claude:claude-opus-4-8 checkpatch
Signed-off-by: Ionut Nechita <ionut.nechita@windriver.com>
---
.../sysfs-driver-intel-m10-bmc-sec-update | 23 ++
drivers/fpga/intel-m10-bmc-sec-update.c | 286 +++++++++++++++++-
include/linux/mfd/intel-m10-bmc.h | 31 ++
3 files changed, 339 insertions(+), 1 deletion(-)
diff --git a/Documentation/ABI/testing/sysfs-driver-intel-m10-bmc-sec-update b/Documentation/ABI/testing/sysfs-driver-intel-m10-bmc-sec-update
index 3a6ca780c75c..978ed68ff23e 100644
--- a/Documentation/ABI/testing/sysfs-driver-intel-m10-bmc-sec-update
+++ b/Documentation/ABI/testing/sysfs-driver-intel-m10-bmc-sec-update
@@ -59,3 +59,26 @@ Contact: Matthew Gerlach <matthew.gerlach@altera.com>
Description: Read only. Returns number of times the secure update
staging area has been flashed.
Format: "%u".
+
+What: /sys/bus/platform/drivers/intel-m10bmc-sec-update/.../control/available_images
+Date: Jul 2026
+KernelVersion: 6.18
+Contact: Ionut Nechita <ionut.nechita@windriver.com>
+Description: Read only. Returns a space separated list of key words
+ that may be written into the image_load file described
+ below. These key words describe a BMC or retimer firmware
+ image in FLASH or EEPROM storage that may be reloaded.
+ This file is only visible if the underlying device
+ supports image reload.
+
+What: /sys/bus/platform/drivers/intel-m10bmc-sec-update/.../control/image_load
+Date: Jul 2026
+KernelVersion: 6.18
+Contact: Ionut Nechita <ionut.nechita@windriver.com>
+Description: Write only. A key word may be written to this file to
+ trigger a reload of a BMC or retimer firmware image from
+ FLASH or EEPROM. Refer to the available_images file for
+ the list of key words supported by the underlying device.
+ Writing an unsupported string to this file results in
+ -EINVAL being returned. This file is only visible if the
+ underlying device supports image reload.
diff --git a/drivers/fpga/intel-m10-bmc-sec-update.c b/drivers/fpga/intel-m10-bmc-sec-update.c
index 7d23d914df3f..7a390cd70516 100644
--- a/drivers/fpga/intel-m10-bmc-sec-update.c
+++ b/drivers/fpga/intel-m10-bmc-sec-update.c
@@ -15,8 +15,14 @@
struct m10bmc_sec;
+struct image_load {
+ const char *name;
+ int (*load_image)(struct m10bmc_sec *sec);
+};
+
struct m10bmc_sec_ops {
int (*rsu_status)(struct m10bmc_sec *sec);
+ struct image_load *image_load; /* terminated with { } member */
};
struct m10bmc_sec {
@@ -253,8 +259,280 @@ static struct attribute_group m10bmc_security_attr_group = {
.attrs = m10bmc_security_attrs,
};
+/* Image reload control (N3000 / D5005) */
+
+static int m10bmc_sec_bmc_image_load(struct m10bmc_sec *sec, unsigned int val)
+{
+ const struct m10bmc_csr_map *csr_map = sec->m10bmc->info->csr_map;
+ u32 doorbell;
+ int ret;
+
+ if (val > 1) {
+ dev_err(sec->dev, "%s invalid reload val = %d\n",
+ __func__, val);
+ return -EINVAL;
+ }
+
+ ret = m10bmc_sys_read(sec->m10bmc, csr_map->doorbell, &doorbell);
+ if (ret)
+ return ret;
+
+ if (doorbell & DRBL_REBOOT_DISABLED)
+ return -EBUSY;
+
+ return m10bmc_sys_update_bits(sec->m10bmc, csr_map->doorbell,
+ DRBL_CONFIG_SEL | DRBL_REBOOT_REQ,
+ FIELD_PREP(DRBL_CONFIG_SEL, val) |
+ DRBL_REBOOT_REQ);
+}
+
+static int m10bmc_sec_bmc_image_load_0(struct m10bmc_sec *sec)
+{
+ return m10bmc_sec_bmc_image_load(sec, 0);
+}
+
+static int m10bmc_sec_bmc_image_load_1(struct m10bmc_sec *sec)
+{
+ return m10bmc_sec_bmc_image_load(sec, 1);
+}
+
+static int retimer_check_idle(struct m10bmc_sec *sec)
+{
+ const struct m10bmc_csr_map *csr_map = sec->m10bmc->info->csr_map;
+ u32 doorbell;
+ int ret;
+
+ ret = m10bmc_sys_read(sec->m10bmc, csr_map->doorbell, &doorbell);
+ if (ret)
+ return -EIO;
+
+ if (rsu_prog(doorbell) != RSU_PROG_IDLE &&
+ rsu_prog(doorbell) != RSU_PROG_RSU_DONE &&
+ rsu_prog(doorbell) != RSU_PROG_PKVL_PROM_DONE) {
+ dev_err(sec->dev, "Doorbell not idle: 0x%08x\n", doorbell);
+ return -EBUSY;
+ }
+
+ return 0;
+}
+
+static int trigger_retimer_eeprom_load(struct m10bmc_sec *sec)
+{
+ const struct m10bmc_csr_map *csr_map = sec->m10bmc->info->csr_map;
+ struct intel_m10bmc *m10bmc = sec->m10bmc;
+ unsigned int val;
+ int ret;
+
+ ret = m10bmc_sys_update_bits(m10bmc, csr_map->doorbell,
+ DRBL_PKVL_EEPROM_LOAD_SEC,
+ DRBL_PKVL_EEPROM_LOAD_SEC);
+ if (ret)
+ return ret;
+
+ /*
+ * If the current NIOS FW is not bootloader, then the retimer load
+ * is expected to trigger a NIOS reboot, so the DRBL_PKVL_EEPROM_LOAD
+ * bit is cleared by the reboot. Otherwise wait for it to be cleared
+ * by the NIOS FW.
+ */
+ ret = regmap_read_poll_timeout(m10bmc->regmap,
+ csr_map->base + csr_map->doorbell,
+ val,
+ (!(val & DRBL_PKVL_EEPROM_LOAD_SEC)),
+ M10BMC_PKVL_LOAD_INTERVAL_US,
+ M10BMC_PKVL_LOAD_TIMEOUT_US);
+ if (ret == -ETIMEDOUT) {
+ dev_err(sec->dev, "PKVL_EEPROM_LOAD clear timedout\n");
+ m10bmc_sys_update_bits(m10bmc, csr_map->doorbell,
+ DRBL_PKVL_EEPROM_LOAD_SEC, 0);
+ ret = -ENODEV;
+ } else if (ret) {
+ dev_err(sec->dev, "poll EEPROM_LOAD error %d\n", ret);
+ }
+
+ return ret;
+}
+
+static int poll_retimer_eeprom_load_done(struct m10bmc_sec *sec)
+{
+ const struct m10bmc_csr_map *csr_map = sec->m10bmc->info->csr_map;
+ struct intel_m10bmc *m10bmc = sec->m10bmc;
+ unsigned int doorbell_reg;
+ int ret;
+
+ /*
+ * RSU_STAT_PKVL_REJECT indicates that the current image is
+ * already programmed. RSU_PROG_PKVL_PROM_DONE that the firmware
+ * update process has finished, but does not necessarily indicate
+ * a successful update.
+ */
+ ret = regmap_read_poll_timeout(m10bmc->regmap,
+ csr_map->base + csr_map->doorbell,
+ doorbell_reg,
+ (rsu_prog(doorbell_reg) ==
+ RSU_PROG_PKVL_PROM_DONE ||
+ FIELD_GET(DRBL_RSU_STATUS, doorbell_reg) ==
+ RSU_STAT_PKVL_REJECT),
+ M10BMC_PKVL_PRELOAD_INTERVAL_US,
+ M10BMC_PKVL_PRELOAD_TIMEOUT_US);
+ if (ret == -ETIMEDOUT) {
+ dev_err(sec->dev, "Doorbell check timedout: 0x%08x\n",
+ doorbell_reg);
+ return ret;
+ } else if (ret) {
+ dev_err(sec->dev, "poll Doorbell error %d\n", ret);
+ return ret;
+ }
+
+ if (FIELD_GET(DRBL_RSU_STATUS, doorbell_reg) == RSU_STAT_PKVL_REJECT) {
+ dev_err(sec->dev, "duplicate image rejected\n");
+ return -ECANCELED;
+ }
+
+ return 0;
+}
+
+static int poll_retimer_preload_done(struct m10bmc_sec *sec)
+{
+ const struct m10bmc_csr_map *csr_map = sec->m10bmc->info->csr_map;
+ struct intel_m10bmc *m10bmc = sec->m10bmc;
+ unsigned int val;
+ int ret;
+
+ /*
+ * Wait for the updated firmware to be loaded by the PKVL device
+ * and confirm that the applied version matches the expected value.
+ */
+ ret = regmap_read_poll_timeout(m10bmc->regmap,
+ csr_map->base + M10BMC_PKVL_POLL_CTRL,
+ val,
+ ((val & M10BMC_PKVL_PRELOAD) ==
+ M10BMC_PKVL_PRELOAD),
+ M10BMC_PKVL_PRELOAD_INTERVAL_US,
+ M10BMC_PKVL_PRELOAD_TIMEOUT_US);
+ if (ret) {
+ dev_err(sec->dev, "poll M10BMC_PKVL_PRELOAD error %d\n", ret);
+ return ret;
+ }
+
+ if ((val & M10BMC_PKVL_UPG_STATUS_MASK) != M10BMC_PKVL_UPG_STATUS_GOOD) {
+ dev_err(sec->dev, "error during M10BMC PKVL upgrade\n");
+ return -EIO;
+ }
+
+ return 0;
+}
+
+static int m10bmc_sec_retimer_eeprom_load(struct m10bmc_sec *sec)
+{
+ int ret;
+
+ ret = retimer_check_idle(sec);
+ if (ret)
+ return ret;
+
+ ret = trigger_retimer_eeprom_load(sec);
+ if (ret)
+ return ret;
+
+ ret = poll_retimer_eeprom_load_done(sec);
+ if (ret)
+ return ret;
+
+ return poll_retimer_preload_done(sec);
+}
+
+static struct image_load n3000_image_load_hndlrs[] = {
+ {
+ .name = "bmc_factory",
+ .load_image = m10bmc_sec_bmc_image_load_1,
+ },
+ {
+ .name = "bmc_user",
+ .load_image = m10bmc_sec_bmc_image_load_0,
+ },
+ {
+ .name = "retimer_fw",
+ .load_image = m10bmc_sec_retimer_eeprom_load,
+ },
+ {}
+};
+
+static struct image_load d5005_image_load_hndlrs[] = {
+ {
+ .name = "bmc_factory",
+ .load_image = m10bmc_sec_bmc_image_load_0,
+ },
+ {
+ .name = "bmc_user",
+ .load_image = m10bmc_sec_bmc_image_load_1,
+ },
+ {}
+};
+
+static ssize_t available_images_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct m10bmc_sec *sec = dev_get_drvdata(dev);
+ const struct image_load *hndlr;
+ ssize_t count = 0;
+
+ for (hndlr = sec->ops->image_load; hndlr->name; hndlr++)
+ count += scnprintf(buf + count, PAGE_SIZE - count,
+ "%s ", hndlr->name);
+
+ if (count)
+ buf[count - 1] = '\n';
+
+ return count;
+}
+static DEVICE_ATTR_RO(available_images);
+
+static ssize_t image_load_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct m10bmc_sec *sec = dev_get_drvdata(dev);
+ const struct image_load *hndlr;
+ int ret = -EINVAL;
+
+ for (hndlr = sec->ops->image_load; hndlr->name; hndlr++) {
+ if (sysfs_streq(buf, hndlr->name)) {
+ ret = hndlr->load_image(sec);
+ break;
+ }
+ }
+
+ return ret ? : count;
+}
+static DEVICE_ATTR_WO(image_load);
+
+static umode_t
+m10bmc_control_visible(struct kobject *kobj, struct attribute *attr, int n)
+{
+ struct m10bmc_sec *sec = dev_get_drvdata(kobj_to_dev(kobj));
+
+ if (!sec->ops->image_load)
+ return 0;
+
+ return attr->mode;
+}
+
+static struct attribute *m10bmc_control_attrs[] = {
+ &dev_attr_available_images.attr,
+ &dev_attr_image_load.attr,
+ NULL,
+};
+
+static struct attribute_group m10bmc_control_attr_group = {
+ .name = "control",
+ .attrs = m10bmc_control_attrs,
+ .is_visible = m10bmc_control_visible,
+};
+
static const struct attribute_group *m10bmc_sec_attr_groups[] = {
&m10bmc_security_attr_group,
+ &m10bmc_control_attr_group,
NULL,
};
@@ -675,6 +953,12 @@ static const struct fw_upload_ops m10bmc_ops = {
static const struct m10bmc_sec_ops m10sec_n3000_ops = {
.rsu_status = m10bmc_sec_n3000_rsu_status,
+ .image_load = n3000_image_load_hndlrs,
+};
+
+static const struct m10bmc_sec_ops m10sec_d5005_ops = {
+ .rsu_status = m10bmc_sec_n3000_rsu_status,
+ .image_load = d5005_image_load_hndlrs,
};
static const struct m10bmc_sec_ops m10sec_n6000_ops = {
@@ -746,7 +1030,7 @@ static const struct platform_device_id intel_m10bmc_sec_ids[] = {
},
{
.name = "d5005bmc-sec-update",
- .driver_data = (kernel_ulong_t)&m10sec_n3000_ops,
+ .driver_data = (kernel_ulong_t)&m10sec_d5005_ops,
},
{
.name = "n6000bmc-sec-update",
diff --git a/include/linux/mfd/intel-m10-bmc.h b/include/linux/mfd/intel-m10-bmc.h
index 988f1cd90032..bc9fafcbfe37 100644
--- a/include/linux/mfd/intel-m10-bmc.h
+++ b/include/linux/mfd/intel-m10-bmc.h
@@ -40,6 +40,37 @@
#define M10BMC_N3000_VER_PCB_INFO_MSK GENMASK(31, 24)
#define M10BMC_N3000_VER_LEGACY_INVALID 0xffffffff
+/* PKVL (retimer) preload poll control register, in system register region */
+#define M10BMC_PKVL_POLL_CTRL 0x80
+#define M10BMC_PKVL_A_PRELOAD BIT(16)
+#define M10BMC_PKVL_A_PRELOAD_TO BIT(17)
+#define M10BMC_PKVL_A_DATA_TOO_BIG BIT(18)
+#define M10BMC_PKVL_A_HDR_CKSUM BIT(20)
+#define M10BMC_PKVL_B_PRELOAD BIT(24)
+#define M10BMC_PKVL_B_PRELOAD_TO BIT(25)
+#define M10BMC_PKVL_B_DATA_TOO_BIG BIT(26)
+#define M10BMC_PKVL_B_HDR_CKSUM BIT(28)
+
+#define M10BMC_PKVL_PRELOAD (M10BMC_PKVL_A_PRELOAD | M10BMC_PKVL_B_PRELOAD)
+#define M10BMC_PKVL_PRELOAD_TIMEOUT (M10BMC_PKVL_A_PRELOAD_TO | \
+ M10BMC_PKVL_B_PRELOAD_TO)
+#define M10BMC_PKVL_DATA_TOO_BIG (M10BMC_PKVL_A_DATA_TOO_BIG | \
+ M10BMC_PKVL_B_DATA_TOO_BIG)
+#define M10BMC_PKVL_HDR_CHECKSUM (M10BMC_PKVL_A_HDR_CKSUM | \
+ M10BMC_PKVL_B_HDR_CKSUM)
+
+#define M10BMC_PKVL_UPG_STATUS_MASK (M10BMC_PKVL_PRELOAD | M10BMC_PKVL_PRELOAD_TIMEOUT | \
+ M10BMC_PKVL_DATA_TOO_BIG | M10BMC_PKVL_HDR_CHECKSUM)
+#define M10BMC_PKVL_UPG_STATUS_GOOD (M10BMC_PKVL_PRELOAD | M10BMC_PKVL_HDR_CHECKSUM)
+
+/* interval 100ms, timeout 2s to trigger the PKVL EEPROM load */
+#define M10BMC_PKVL_LOAD_INTERVAL_US (100 * 1000)
+#define M10BMC_PKVL_LOAD_TIMEOUT_US (2 * 1000 * 1000)
+
+/* interval 100ms, timeout 30s for the PKVL preload to complete */
+#define M10BMC_PKVL_PRELOAD_INTERVAL_US (100 * 1000)
+#define M10BMC_PKVL_PRELOAD_TIMEOUT_US (30 * 1000 * 1000)
+
/* Telemetry registers */
#define M10BMC_N3000_TELEM_START 0x100
#define M10BMC_N3000_TELEM_END 0x250
--
2.55.0
^ permalink raw reply [flat|nested] 2+ messages in thread
end of thread, other threads:[~2026-07-15 7:39 UTC | newest]
Thread overview: 2+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-07-15 7:38 [PATCH v1 0/1] fpga: m10bmc-sec: add image_load sysfs for N3000/D5005 Ionut Nechita (Wind River)
2026-07-15 7:38 ` [PATCH v1 1/1] fpga: m10bmc-sec: add image_load sysfs for N3000 and D5005 Ionut Nechita (Wind River)
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox