[PATCH 1/1] fpga: intel-m10-bmc-log: Create MAX 10 BMC log driver

From: Ilpo Järvinen
Date: Thu Feb 09 2023 - 06:14:15 EST


Add MAX 10 BMC log driver for accessing BMC event log, FPGA image
directory, and bill-of-materials (BOM) info partitions on FPGA card's
flash. Use the nvmem API to expose the event MAX 10 BMC event logs to
userspace.

The PMCI MAX 10 BMC contains high and low timestamp registers that are
periodically written by the host driver to facilitate BMC event logs.
Add a kernel worker thread to update the BMC timestamp registers. The
frequency of timestamp updates is controlled through sysfs file (the
default frequency is once per minute).

Co-developed-by: Russ Weight <russell.h.weight@xxxxxxxxx>
Signed-off-by: Russ Weight <russell.h.weight@xxxxxxxxx>
Co-developed-by: Tianfei Zhang <tianfei.zhang@xxxxxxxxx>
Signed-off-by: Tianfei Zhang <tianfei.zhang@xxxxxxxxx>
Co-developed-by: Matthew Gerlach <matthew.gerlach@xxxxxxxxxxxxxxx>
Signed-off-by: Matthew Gerlach <matthew.gerlach@xxxxxxxxxxxxxxx>
Signed-off-by: Ilpo Järvinen <ilpo.jarvinen@xxxxxxxxxxxxxxx>
---

This requires commit 4f43a6e80ba9 ("fpga: m10bmc-sec: Add support for
N6000") from ib-mfd-fpga-hwmon-6.3-1 (the 11th patch of the M10 BMC split
series that wasn't picked up at first and still isn't in for-fpga-v6.3-rc1).

.../testing/sysfs-driver-intel-m10-bmc-log | 37 +++
MAINTAINERS | 4 +-
drivers/fpga/Kconfig | 9 +
drivers/fpga/Makefile | 3 +-
drivers/fpga/intel-m10-bmc-log.c | 264 ++++++++++++++++++
drivers/mfd/intel-m10-bmc-pmci.c | 1 +
include/linux/mfd/intel-m10-bmc.h | 13 +
7 files changed, 329 insertions(+), 2 deletions(-)
create mode 100644 Documentation/ABI/testing/sysfs-driver-intel-m10-bmc-log
create mode 100644 drivers/fpga/intel-m10-bmc-log.c

diff --git a/Documentation/ABI/testing/sysfs-driver-intel-m10-bmc-log b/Documentation/ABI/testing/sysfs-driver-intel-m10-bmc-log
new file mode 100644
index 000000000000..a170e675d11e
--- /dev/null
+++ b/Documentation/ABI/testing/sysfs-driver-intel-m10-bmc-log
@@ -0,0 +1,37 @@
+What: /sys/bus/platform/devices/intel-m10-bmc-log.*.auto/time_sync_frequency
+Date: Apr 2023
+KernelVersion: 6.3
+Contact: Russ Weight <russell.h.weight@xxxxxxxxx>
+Description: Read/write. This sysfs node controls the frequency (in
+ seconds) that the host writes to the MAX10 BMC registers
+ to synchronize the timestamp registers used for the BMC
+ event log. Write zero to stop the timestamp synchronization.
+ Write a non-zero integer value to restart or modify the
+ update frequency. Reading from this file will return the
+ same integer value.
+ Format: %u
+
+What: /sys/bus/platform/devices/intel-m10-bmc-log.*.auto/bmc_event_log*/nvmem
+Date: Apr 2023
+KernelVersion: 6.3
+Contact: Tianfei zhang <tianfei.zhang@xxxxxxxxx>
+Description: Read-only. This file returns the contents of the "evemt log"
+ partition in flash. This partition includes the event and
+ error info for the BMC.
+
+What: /sys/bus/platform/devices/intel-m10-bmc-log.*.auto/fpga_image_directory*/nvmem
+Date: Apr 2023
+KernelVersion: 6.3
+Contact: Tianfei zhang <tianfei.zhang@xxxxxxxxx>
+Description: Read-only. This file returns the contents of the "FPGA image
+ directory" partition in flash. This partition includes
+ information like the FPGA Image versions and state.
+
+What: /sys/bus/platform/devices/intel-m10-bmc-log.*.auto/bom_info*/nvmem
+Date: Apr 2023
+KernelVersion: 6.3
+Contact: Tianfei zhang <tianfei.zhang@xxxxxxxxx>
+Description: Read-only. This file returns the contents of the "BOM info"
+ partition in flash. This partition includes information such
+ as the bill of materials (BOM) critical components like
+ PBA#, MMID.
diff --git a/MAINTAINERS b/MAINTAINERS
index ddfa4f8b3c80..1b2427ba6729 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -8075,11 +8075,13 @@ F: Documentation/fpga/
F: drivers/fpga/
F: include/linux/fpga/

-INTEL MAX10 BMC SECURE UPDATES
+INTEL MAX10 BMC SUBDRIVERS
M: Russ Weight <russell.h.weight@xxxxxxxxx>
L: linux-fpga@xxxxxxxxxxxxxxx
S: Maintained
+F: Documentation/ABI/testing/sysfs-driver-intel-m10-bmc-log
F: Documentation/ABI/testing/sysfs-driver-intel-m10-bmc-sec-update
+F: drivers/fpga/intel-m10-bmc-log.c
F: drivers/fpga/intel-m10-bmc-sec-update.c

MICROCHIP POLARFIRE FPGA DRIVERS
diff --git a/drivers/fpga/Kconfig b/drivers/fpga/Kconfig
index 0a00763b9f28..773667e3b027 100644
--- a/drivers/fpga/Kconfig
+++ b/drivers/fpga/Kconfig
@@ -244,6 +244,15 @@ config FPGA_MGR_VERSAL_FPGA

To compile this as a module, choose M here.

+config FPGA_M10_BMC_LOG
+ tristate "Intel MAX 10 Board Management Controller Log Driver"
+ depends on MFD_INTEL_M10_BMC_CORE
+ help
+ Support for the Intel MAX 10 Board Management Controller (BMC)
+ event log, event log timestamp synchronization, and other
+ information on flash partitions (available images and
+ bill-of-materials critical information).
+
config FPGA_M10_BMC_SEC_UPDATE
tristate "Intel MAX10 BMC Secure Update driver"
depends on MFD_INTEL_M10_BMC_CORE
diff --git a/drivers/fpga/Makefile b/drivers/fpga/Makefile
index 72e554b4d2f7..1db25ad3d76a 100644
--- a/drivers/fpga/Makefile
+++ b/drivers/fpga/Makefile
@@ -25,7 +25,8 @@ obj-$(CONFIG_FPGA_MGR_LATTICE_SYSCONFIG_SPI) += lattice-sysconfig-spi.o
obj-$(CONFIG_ALTERA_PR_IP_CORE) += altera-pr-ip-core.o
obj-$(CONFIG_ALTERA_PR_IP_CORE_PLAT) += altera-pr-ip-core-plat.o

-# FPGA Secure Update Drivers
+# MAX10 subdrivers
+obj-$(CONFIG_FPGA_M10_BMC_LOG) += intel-m10-bmc-log.o
obj-$(CONFIG_FPGA_M10_BMC_SEC_UPDATE) += intel-m10-bmc-sec-update.o

# FPGA Bridge Drivers
diff --git a/drivers/fpga/intel-m10-bmc-log.c b/drivers/fpga/intel-m10-bmc-log.c
new file mode 100644
index 000000000000..4e7a54afe786
--- /dev/null
+++ b/drivers/fpga/intel-m10-bmc-log.c
@@ -0,0 +1,264 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Intel Max10 Board Management Controller Log Driver
+ *
+ * Copyright (C) 2021-2023 Intel Corporation.
+ */
+
+#include <linux/bitfield.h>
+#include <linux/dev_printk.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/nvmem-provider.h>
+#include <linux/mod_devicetable.h>
+#include <linux/types.h>
+
+#include <linux/mfd/intel-m10-bmc.h>
+
+#define M10BMC_TIMESTAMP_FREQ 60 /* 60 secs between updates */
+
+struct m10bmc_log_cfg {
+ int el_size;
+ unsigned long el_off;
+
+ int id_size;
+ unsigned long id_off;
+
+ int bi_size;
+ unsigned long bi_off;
+};
+
+struct m10bmc_log {
+ struct device *dev;
+ struct intel_m10bmc *m10bmc;
+ unsigned int freq_s; /* update frequency in seconds */
+ struct delayed_work dwork;
+ const struct m10bmc_log_cfg *log_cfg;
+ struct nvmem_device *bmc_event_log_nvmem;
+ struct nvmem_device *fpga_image_dir_nvmem;
+ struct nvmem_device *bom_info_nvmem;
+};
+
+static void m10bmc_log_time_sync(struct work_struct *work)
+{
+ struct delayed_work *dwork = to_delayed_work(work);
+ const struct m10bmc_csr_map *csr_map;
+ struct m10bmc_log *log;
+ s64 time_ms;
+ int ret;
+
+ log = container_of(dwork, struct m10bmc_log, dwork);
+ csr_map = log->m10bmc->info->csr_map;
+
+ time_ms = ktime_to_ms(ktime_get_real());
+ ret = regmap_write(log->m10bmc->regmap, csr_map->base + M10BMC_N6000_TIME_HIGH,
+ upper_32_bits(time_ms));
+ if (!ret) {
+ ret = regmap_write(log->m10bmc->regmap, csr_map->base + M10BMC_N6000_TIME_LOW,
+ lower_32_bits(time_ms));
+ }
+ if (ret)
+ dev_err_once(log->dev, "Failed to update BMC timestamp: %d\n", ret);
+
+ schedule_delayed_work(&log->dwork, log->freq_s * HZ);
+}
+
+static ssize_t time_sync_frequency_store(struct device *dev, struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct m10bmc_log *ddata = dev_get_drvdata(dev);
+ unsigned int old_freq = ddata->freq_s;
+ int ret;
+
+ ret = kstrtouint(buf, 0, &ddata->freq_s);
+ if (ret)
+ return ret;
+
+ if (old_freq)
+ cancel_delayed_work_sync(&ddata->dwork);
+
+ if (ddata->freq_s)
+ m10bmc_log_time_sync(&ddata->dwork.work);
+
+ return count;
+}
+
+static ssize_t time_sync_frequency_show(struct device *dev, struct device_attribute *attr,
+ char *buf)
+{
+ struct m10bmc_log *ddata = dev_get_drvdata(dev);
+
+ return sysfs_emit(buf, "%u\n", ddata->freq_s);
+}
+static DEVICE_ATTR_RW(time_sync_frequency);
+
+static struct attribute *m10bmc_log_attrs[] = {
+ &dev_attr_time_sync_frequency.attr,
+ NULL,
+};
+ATTRIBUTE_GROUPS(m10bmc_log);
+
+static int bmc_nvmem_read(struct m10bmc_log *ddata, unsigned int addr,
+ unsigned int off, void *val, size_t count)
+{
+ struct intel_m10bmc *m10bmc = ddata->m10bmc;
+ int ret;
+
+ ret = m10bmc->flash_bulk_ops->read(m10bmc, val, addr + off, count);
+ if (ret) {
+ if (ret != -EBUSY)
+ dev_err(ddata->dev, "failed to read flash %x (%d)\n", addr, ret);
+ return ret;
+ }
+
+ return 0;
+}
+
+static int bmc_event_log_nvmem_read(void *priv, unsigned int off, void *val, size_t count)
+{
+ struct m10bmc_log *ddata = priv;
+
+ return bmc_nvmem_read(ddata, ddata->log_cfg->el_off, off, val, count);
+}
+
+static int fpga_image_dir_nvmem_read(void *priv, unsigned int off, void *val, size_t count)
+{
+ struct m10bmc_log *ddata = priv;
+
+ return bmc_nvmem_read(ddata, ddata->log_cfg->id_off, off, val, count);
+}
+
+static int bom_info_nvmem_read(void *priv, unsigned int off, void *val, size_t count)
+{
+ struct m10bmc_log *ddata = priv;
+
+ return bmc_nvmem_read(ddata, ddata->log_cfg->bi_off, off, val, count);
+}
+
+static struct nvmem_config bmc_event_log_nvmem_config = {
+ .name = "bmc_event_log",
+ .stride = 4,
+ .word_size = 1,
+ .reg_read = bmc_event_log_nvmem_read,
+ .id = NVMEM_DEVID_AUTO,
+};
+
+static struct nvmem_config fpga_image_dir_nvmem_config = {
+ .name = "fpga_image_directory",
+ .stride = 4,
+ .word_size = 1,
+ .reg_read = fpga_image_dir_nvmem_read,
+ .id = NVMEM_DEVID_AUTO,
+};
+
+static struct nvmem_config bom_info_nvmem_config = {
+ .name = "bom_info",
+ .stride = 4,
+ .word_size = 1,
+ .reg_read = bom_info_nvmem_read,
+ .id = NVMEM_DEVID_AUTO,
+};
+
+static int m10bmc_log_probe(struct platform_device *pdev)
+{
+ const struct platform_device_id *id = platform_get_device_id(pdev);
+ struct intel_m10bmc *m10bmc = dev_get_drvdata(pdev->dev.parent);
+ struct nvmem_config nvconfig;
+ struct m10bmc_log *ddata;
+
+ if (WARN_ON_ONCE(!m10bmc->flash_bulk_ops))
+ return -ENODEV;
+
+ ddata = devm_kzalloc(&pdev->dev, sizeof(*ddata), GFP_KERNEL);
+ if (!ddata)
+ return -ENOMEM;
+
+ ddata->dev = &pdev->dev;
+ ddata->m10bmc = m10bmc;
+ ddata->freq_s = M10BMC_TIMESTAMP_FREQ;
+ INIT_DELAYED_WORK(&ddata->dwork, m10bmc_log_time_sync);
+ ddata->log_cfg = (struct m10bmc_log_cfg *)id->driver_data;
+ dev_set_drvdata(&pdev->dev, ddata);
+
+ if (ddata->log_cfg->el_size > 0) {
+ m10bmc_log_time_sync(&ddata->dwork.work);
+
+ memcpy(&nvconfig, &bmc_event_log_nvmem_config, sizeof(bmc_event_log_nvmem_config));
+ nvconfig.dev = ddata->dev;
+ nvconfig.priv = ddata;
+ nvconfig.size = ddata->log_cfg->el_size;
+
+ ddata->bmc_event_log_nvmem = devm_nvmem_register(ddata->dev, &nvconfig);
+ if (IS_ERR(ddata->bmc_event_log_nvmem))
+ return PTR_ERR(ddata->bmc_event_log_nvmem);
+ }
+
+ if (ddata->log_cfg->id_size > 0) {
+ memcpy(&nvconfig, &fpga_image_dir_nvmem_config, sizeof(fpga_image_dir_nvmem_config));
+ nvconfig.dev = ddata->dev;
+ nvconfig.priv = ddata;
+ nvconfig.size = ddata->log_cfg->id_size;
+
+ ddata->fpga_image_dir_nvmem = devm_nvmem_register(ddata->dev, &nvconfig);
+ if (IS_ERR(ddata->fpga_image_dir_nvmem))
+ return PTR_ERR(ddata->fpga_image_dir_nvmem);
+ }
+
+ if (ddata->log_cfg->bi_size > 0) {
+ memcpy(&nvconfig, &bom_info_nvmem_config, sizeof(bom_info_nvmem_config));
+ nvconfig.dev = ddata->dev;
+ nvconfig.priv = ddata;
+ nvconfig.size = ddata->log_cfg->bi_size;
+
+ ddata->bom_info_nvmem = devm_nvmem_register(ddata->dev, &nvconfig);
+ if (IS_ERR(ddata->bom_info_nvmem))
+ return PTR_ERR(ddata->bom_info_nvmem);
+ }
+
+ return 0;
+}
+
+static int m10bmc_log_remove(struct platform_device *pdev)
+{
+ struct m10bmc_log *ddata = dev_get_drvdata(&pdev->dev);
+
+ cancel_delayed_work_sync(&ddata->dwork);
+
+ return 0;
+}
+
+static const struct m10bmc_log_cfg m10bmc_log_n6000_cfg = {
+ .el_size = M10BMC_N6000_ERROR_LOG_SIZE,
+ .el_off = M10BMC_N6000_ERROR_LOG_ADDR,
+
+ .id_size = M10BMC_N6000_FPGA_IMAGE_DIR_SIZE,
+ .id_off = M10BMC_N6000_FPGA_IMAGE_DIR_ADDR,
+
+ .bi_size = M10BMC_N6000_BOM_INFO_SIZE,
+ .bi_off = M10BMC_N6000_BOM_INFO_ADDR,
+};
+
+static const struct platform_device_id intel_m10bmc_log_ids[] = {
+ {
+ .name = "n6000bmc-log",
+ .driver_data = (unsigned long)&m10bmc_log_n6000_cfg,
+ },
+ { }
+};
+
+static struct platform_driver intel_m10bmc_log_driver = {
+ .probe = m10bmc_log_probe,
+ .remove = m10bmc_log_remove,
+ .driver = {
+ .name = "intel-m10-bmc-log",
+ .dev_groups = m10bmc_log_groups,
+ },
+ .id_table = intel_m10bmc_log_ids,
+};
+module_platform_driver(intel_m10bmc_log_driver);
+
+MODULE_DEVICE_TABLE(platform, intel_m10bmc_log_ids);
+MODULE_AUTHOR("Intel Corporation");
+MODULE_DESCRIPTION("Intel MAX 10 BMC log driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/mfd/intel-m10-bmc-pmci.c b/drivers/mfd/intel-m10-bmc-pmci.c
index 8821f1876dd6..f8803b7bb98e 100644
--- a/drivers/mfd/intel-m10-bmc-pmci.c
+++ b/drivers/mfd/intel-m10-bmc-pmci.c
@@ -350,6 +350,7 @@ static struct regmap_config m10bmc_pmci_regmap_config = {
static struct mfd_cell m10bmc_pmci_n6000_bmc_subdevs[] = {
{ .name = "n6000bmc-hwmon" },
{ .name = "n6000bmc-sec-update" },
+ { .name = "n6000bmc-log" },
};

static const struct m10bmc_csr_map m10bmc_n6000_csr_map = {
diff --git a/include/linux/mfd/intel-m10-bmc.h b/include/linux/mfd/intel-m10-bmc.h
index 1812ebfa11a8..1079e580e9e6 100644
--- a/include/linux/mfd/intel-m10-bmc.h
+++ b/include/linux/mfd/intel-m10-bmc.h
@@ -125,6 +125,9 @@
#define M10BMC_N6000_SYS_BASE 0x0
#define M10BMC_N6000_SYS_END 0xfff

+#define M10BMC_N6000_TIME_LOW 0x178
+#define M10BMC_N6000_TIME_HIGH 0x17c
+
#define M10BMC_N6000_DOORBELL 0x1c0
#define M10BMC_N6000_AUTH_RESULT 0x1c4
#define AUTH_RESULT_RSU_STATUS GENMASK(23, 16)
@@ -134,6 +137,16 @@
#define M10BMC_N6000_MAC_LOW 0x20
#define M10BMC_N6000_MAC_HIGH (M10BMC_N6000_MAC_LOW + 4)

+/* Addresses for BMC log data in FLASH */
+#define M10BMC_N6000_ERROR_LOG_ADDR 0x7fb0000
+#define M10BMC_N6000_ERROR_LOG_SIZE 0x40000
+
+#define M10BMC_N6000_FPGA_IMAGE_DIR_ADDR 0x7ff6000
+#define M10BMC_N6000_FPGA_IMAGE_DIR_SIZE 0x3000
+
+#define M10BMC_N6000_BOM_INFO_ADDR 0x7ff0000
+#define M10BMC_N6000_BOM_INFO_SIZE 0x2000
+
/* Addresses for security related data in FLASH */
#define M10BMC_N6000_BMC_REH_ADDR 0x7ffc004
#define M10BMC_N6000_BMC_PROG_ADDR 0x7ffc000
--
2.30.2