1
linux/drivers/mfd/intel-m10-bmc-core.c
Ilpo Järvinen 867cae44f8 mfd: intel-m10-bmc: Manage access to MAX 10 fw handshake registers
On some MAX 10 cards, the BMC firmware is not available to service
handshake registers during secure update erase and write phases at
normal speeds. This problem affects at least hwmon driver. When the MAX
10 hwmon driver tries to read the sensor values during a secure update,
the reads are slowed down (e.g., reading all D5005 sensors takes ~24s
which is magnitudes worse than the normal <0.02s).

Manage access to the handshake registers using a rw semaphore and a FW
state variable to prevent accesses during those secure update phases
and return -EBUSY instead.

If handshake_sys_reg_nranges == 0, don't update bwcfw_state as it is not
used. This avoids the locking cost.

Co-developed-by: Russ Weight <russell.h.weight@intel.com>
Signed-off-by: Russ Weight <russell.h.weight@intel.com>
Co-developed-by: Xu Yilun <yilun.xu@intel.com>
Signed-off-by: Xu Yilun <yilun.xu@intel.com>
Signed-off-by: Ilpo Järvinen <ilpo.jarvinen@linux.intel.com>
Signed-off-by: Lee Jones <lee@kernel.org>
Link: https://lore.kernel.org/r/20230417092653.16487-5-ilpo.jarvinen@linux.intel.com
2023-06-15 09:19:36 +01:00

209 lines
5.9 KiB
C

// SPDX-License-Identifier: GPL-2.0
/*
* Intel MAX 10 Board Management Controller chip - common code
*
* Copyright (C) 2018-2020 Intel Corporation. All rights reserved.
*/
#include <linux/bitfield.h>
#include <linux/device.h>
#include <linux/dev_printk.h>
#include <linux/mfd/core.h>
#include <linux/mfd/intel-m10-bmc.h>
#include <linux/module.h>
void m10bmc_fw_state_set(struct intel_m10bmc *m10bmc, enum m10bmc_fw_state new_state)
{
/* bmcfw_state is only needed if handshake_sys_reg_nranges > 0 */
if (!m10bmc->info->handshake_sys_reg_nranges)
return;
down_write(&m10bmc->bmcfw_lock);
m10bmc->bmcfw_state = new_state;
up_write(&m10bmc->bmcfw_lock);
}
EXPORT_SYMBOL_NS_GPL(m10bmc_fw_state_set, INTEL_M10_BMC_CORE);
/*
* For some Intel FPGA devices, the BMC firmware is not available to service
* handshake registers during a secure update.
*/
static bool m10bmc_reg_always_available(struct intel_m10bmc *m10bmc, unsigned int offset)
{
if (!m10bmc->info->handshake_sys_reg_nranges)
return true;
return !regmap_reg_in_ranges(offset, m10bmc->info->handshake_sys_reg_ranges,
m10bmc->info->handshake_sys_reg_nranges);
}
/*
* m10bmc_handshake_reg_unavailable - Checks if reg access collides with secure update state
* @m10bmc: M10 BMC structure
*
* For some Intel FPGA devices, the BMC firmware is not available to service
* handshake registers during a secure update erase and write phases.
*
* Context: @m10bmc->bmcfw_lock must be held.
*/
static bool m10bmc_handshake_reg_unavailable(struct intel_m10bmc *m10bmc)
{
return m10bmc->bmcfw_state == M10BMC_FW_STATE_SEC_UPDATE_PREPARE ||
m10bmc->bmcfw_state == M10BMC_FW_STATE_SEC_UPDATE_WRITE;
}
/*
* This function helps to simplify the accessing of the system registers.
*
* The base of the system registers is configured through the struct
* csr_map.
*/
int m10bmc_sys_read(struct intel_m10bmc *m10bmc, unsigned int offset, unsigned int *val)
{
const struct m10bmc_csr_map *csr_map = m10bmc->info->csr_map;
int ret;
if (m10bmc_reg_always_available(m10bmc, offset))
return m10bmc_raw_read(m10bmc, csr_map->base + offset, val);
down_read(&m10bmc->bmcfw_lock);
if (m10bmc_handshake_reg_unavailable(m10bmc))
ret = -EBUSY; /* Reg not available during secure update */
else
ret = m10bmc_raw_read(m10bmc, csr_map->base + offset, val);
up_read(&m10bmc->bmcfw_lock);
return ret;
}
EXPORT_SYMBOL_NS_GPL(m10bmc_sys_read, INTEL_M10_BMC_CORE);
int m10bmc_sys_update_bits(struct intel_m10bmc *m10bmc, unsigned int offset,
unsigned int msk, unsigned int val)
{
const struct m10bmc_csr_map *csr_map = m10bmc->info->csr_map;
int ret;
if (m10bmc_reg_always_available(m10bmc, offset))
return regmap_update_bits(m10bmc->regmap, csr_map->base + offset, msk, val);
down_read(&m10bmc->bmcfw_lock);
if (m10bmc_handshake_reg_unavailable(m10bmc))
ret = -EBUSY; /* Reg not available during secure update */
else
ret = regmap_update_bits(m10bmc->regmap, csr_map->base + offset, msk, val);
up_read(&m10bmc->bmcfw_lock);
return ret;
}
EXPORT_SYMBOL_NS_GPL(m10bmc_sys_update_bits, INTEL_M10_BMC_CORE);
static ssize_t bmc_version_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct intel_m10bmc *ddata = dev_get_drvdata(dev);
unsigned int val;
int ret;
ret = m10bmc_sys_read(ddata, ddata->info->csr_map->build_version, &val);
if (ret)
return ret;
return sprintf(buf, "0x%x\n", val);
}
static DEVICE_ATTR_RO(bmc_version);
static ssize_t bmcfw_version_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct intel_m10bmc *ddata = dev_get_drvdata(dev);
unsigned int val;
int ret;
ret = m10bmc_sys_read(ddata, ddata->info->csr_map->fw_version, &val);
if (ret)
return ret;
return sprintf(buf, "0x%x\n", val);
}
static DEVICE_ATTR_RO(bmcfw_version);
static ssize_t mac_address_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct intel_m10bmc *ddata = dev_get_drvdata(dev);
unsigned int macaddr_low, macaddr_high;
int ret;
ret = m10bmc_sys_read(ddata, ddata->info->csr_map->mac_low, &macaddr_low);
if (ret)
return ret;
ret = m10bmc_sys_read(ddata, ddata->info->csr_map->mac_high, &macaddr_high);
if (ret)
return ret;
return sysfs_emit(buf, "%02x:%02x:%02x:%02x:%02x:%02x\n",
(u8)FIELD_GET(M10BMC_N3000_MAC_BYTE1, macaddr_low),
(u8)FIELD_GET(M10BMC_N3000_MAC_BYTE2, macaddr_low),
(u8)FIELD_GET(M10BMC_N3000_MAC_BYTE3, macaddr_low),
(u8)FIELD_GET(M10BMC_N3000_MAC_BYTE4, macaddr_low),
(u8)FIELD_GET(M10BMC_N3000_MAC_BYTE5, macaddr_high),
(u8)FIELD_GET(M10BMC_N3000_MAC_BYTE6, macaddr_high));
}
static DEVICE_ATTR_RO(mac_address);
static ssize_t mac_count_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct intel_m10bmc *ddata = dev_get_drvdata(dev);
unsigned int macaddr_high;
int ret;
ret = m10bmc_sys_read(ddata, ddata->info->csr_map->mac_high, &macaddr_high);
if (ret)
return ret;
return sysfs_emit(buf, "%u\n", (u8)FIELD_GET(M10BMC_N3000_MAC_COUNT, macaddr_high));
}
static DEVICE_ATTR_RO(mac_count);
static struct attribute *m10bmc_attrs[] = {
&dev_attr_bmc_version.attr,
&dev_attr_bmcfw_version.attr,
&dev_attr_mac_address.attr,
&dev_attr_mac_count.attr,
NULL,
};
static const struct attribute_group m10bmc_group = {
.attrs = m10bmc_attrs,
};
const struct attribute_group *m10bmc_dev_groups[] = {
&m10bmc_group,
NULL,
};
EXPORT_SYMBOL_NS_GPL(m10bmc_dev_groups, INTEL_M10_BMC_CORE);
int m10bmc_dev_init(struct intel_m10bmc *m10bmc, const struct intel_m10bmc_platform_info *info)
{
int ret;
m10bmc->info = info;
dev_set_drvdata(m10bmc->dev, m10bmc);
init_rwsem(&m10bmc->bmcfw_lock);
ret = devm_mfd_add_devices(m10bmc->dev, PLATFORM_DEVID_AUTO,
info->cells, info->n_cells,
NULL, 0, NULL);
if (ret)
dev_err(m10bmc->dev, "Failed to register sub-devices: %d\n", ret);
return ret;
}
EXPORT_SYMBOL_NS_GPL(m10bmc_dev_init, INTEL_M10_BMC_CORE);
MODULE_DESCRIPTION("Intel MAX 10 BMC core driver");
MODULE_AUTHOR("Intel Corporation");
MODULE_LICENSE("GPL v2");