diff options
Diffstat (limited to 'drivers/peci')
-rw-r--r-- | drivers/peci/Kconfig | 37 | ||||
-rw-r--r-- | drivers/peci/Makefile | 11 | ||||
-rw-r--r-- | drivers/peci/busses/Kconfig | 34 | ||||
-rw-r--r-- | drivers/peci/busses/Makefile | 7 | ||||
-rw-r--r-- | drivers/peci/busses/peci-aspeed.c | 484 | ||||
-rw-r--r-- | drivers/peci/busses/peci-npcm.c | 406 | ||||
-rw-r--r-- | drivers/peci/peci-core.c | 2089 | ||||
-rw-r--r-- | drivers/peci/peci-dev.c | 348 |
8 files changed, 3416 insertions, 0 deletions
diff --git a/drivers/peci/Kconfig b/drivers/peci/Kconfig new file mode 100644 index 000000000000..a64fed7bb367 --- /dev/null +++ b/drivers/peci/Kconfig @@ -0,0 +1,37 @@ +# +# Platform Environment Control Interface (PECI) subsystem configuration +# + +menu "PECI support" + +config PECI + tristate "PECI support" + select CRC8 + help + The Platform Environment Control Interface (PECI) is a one-wire bus + interface that provides a communication channel from Intel processors + and chipset components to external monitoring or control devices. + + If you want PECI support, you should say Y here and also to the + specific driver for your bus adapter(s) below. + + This support is also available as a module. If so, the module + will be called peci-core. + +if PECI + +config PECI_CHARDEV + tristate "PECI device interface" + help + Say Y here to use peci-* device files, usually found in the /dev + directory on your system. They make it possible to have user-space + programs use the PECI bus. + + This support is also available as a module. If so, the module + will be called peci-dev. + +source "drivers/peci/busses/Kconfig" + +endif # PECI + +endmenu diff --git a/drivers/peci/Makefile b/drivers/peci/Makefile new file mode 100644 index 000000000000..da8b0a33fa42 --- /dev/null +++ b/drivers/peci/Makefile @@ -0,0 +1,11 @@ +# SPDX-License-Identifier: GPL-2.0 +# +# Makefile for the PECI core drivers. +# + +# Core functionality +obj-$(CONFIG_PECI) += peci-core.o +obj-$(CONFIG_PECI_CHARDEV) += peci-dev.o + +# Hardware specific bus drivers +obj-y += busses/ diff --git a/drivers/peci/busses/Kconfig b/drivers/peci/busses/Kconfig new file mode 100644 index 000000000000..4316234db67c --- /dev/null +++ b/drivers/peci/busses/Kconfig @@ -0,0 +1,34 @@ +# +# PECI hardware bus configuration +# + +menu "PECI Hardware Bus support" + +config PECI_ASPEED + tristate "ASPEED PECI support" + depends on ARCH_ASPEED || COMPILE_TEST + depends on OF + depends on HAS_IOMEM + depends on PECI + help + Say Y here if you want support for the Platform Environment Control + Interface (PECI) bus adapter driver on the ASPEED SoCs. + + This support is also available as a module. If so, the module + will be called peci-aspeed. + +config PECI_NPCM + tristate "Nuvoton NPCM PECI support" + select REGMAP_MMIO + depends on OF + depends on HAS_IOMEM + depends on ARCH_NPCM || COMPILE_TEST + depends on PECI + help + Say Y here if you want support for the Platform Environment Control + Interface (PECI) bus adapter driver on the Nuvoton NPCM SoCs. + + This support is also available as a module. If so, the module + will be called peci-npcm. + +endmenu diff --git a/drivers/peci/busses/Makefile b/drivers/peci/busses/Makefile new file mode 100644 index 000000000000..aa8ce3ae5947 --- /dev/null +++ b/drivers/peci/busses/Makefile @@ -0,0 +1,7 @@ +# SPDX-License-Identifier: GPL-2.0 +# +# Makefile for the PECI hardware bus drivers. +# + +obj-$(CONFIG_PECI_ASPEED) += peci-aspeed.o +obj-$(CONFIG_PECI_NPCM) += peci-npcm.o diff --git a/drivers/peci/busses/peci-aspeed.c b/drivers/peci/busses/peci-aspeed.c new file mode 100644 index 000000000000..2673d4c4dcf9 --- /dev/null +++ b/drivers/peci/busses/peci-aspeed.c @@ -0,0 +1,484 @@ +// SPDX-License-Identifier: GPL-2.0 +// Copyright (C) 2012-2017 ASPEED Technology Inc. +// Copyright (c) 2018-2019 Intel Corporation + +#include <linux/bitfield.h> +#include <linux/clk.h> +#include <linux/delay.h> +#include <linux/interrupt.h> +#include <linux/io.h> +#include <linux/iopoll.h> +#include <linux/jiffies.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/peci.h> +#include <linux/platform_device.h> +#include <linux/reset.h> + +/* ASPEED PECI Registers */ +/* Control Register */ +#define ASPEED_PECI_CTRL 0x00 +#define ASPEED_PECI_CTRL_SAMPLING_MASK GENMASK(19, 16) +#define ASPEED_PECI_CTRL_READ_MODE_MASK GENMASK(13, 12) +#define ASPEED_PECI_CTRL_READ_MODE_COUNT BIT(12) +#define ASPEED_PECI_CTRL_READ_MODE_DBG BIT(13) +#define ASPEED_PECI_CTRL_CLK_SOURCE_MASK BIT(11) +#define ASPEED_PECI_CTRL_CLK_DIV_MASK GENMASK(10, 8) +#define ASPEED_PECI_CTRL_INVERT_OUT BIT(7) +#define ASPEED_PECI_CTRL_INVERT_IN BIT(6) +#define ASPEED_PECI_CTRL_BUS_CONTENT_EN BIT(5) +#define ASPEED_PECI_CTRL_PECI_EN BIT(4) +#define ASPEED_PECI_CTRL_PECI_CLK_EN BIT(0) + +/* Timing Negotiation Register */ +#define ASPEED_PECI_TIMING_NEGOTIATION 0x04 +#define ASPEED_PECI_TIMING_MESSAGE_MASK GENMASK(15, 8) +#define ASPEED_PECI_TIMING_ADDRESS_MASK GENMASK(7, 0) + +/* Command Register */ +#define ASPEED_PECI_CMD 0x08 +#define ASPEED_PECI_CMD_PIN_MON BIT(31) +#define ASPEED_PECI_CMD_STS_MASK GENMASK(27, 24) +#define ASPEED_PECI_CMD_IDLE_MASK \ + (ASPEED_PECI_CMD_STS_MASK | ASPEED_PECI_CMD_PIN_MON) +#define ASPEED_PECI_CMD_FIRE BIT(0) + +/* Read/Write Length Register */ +#define ASPEED_PECI_RW_LENGTH 0x0c +#define ASPEED_PECI_AW_FCS_EN BIT(31) +#define ASPEED_PECI_READ_LEN_MASK GENMASK(23, 16) +#define ASPEED_PECI_WRITE_LEN_MASK GENMASK(15, 8) +#define ASPEED_PECI_TAGET_ADDR_MASK GENMASK(7, 0) + +/* Expected FCS Data Register */ +#define ASPEED_PECI_EXP_FCS 0x10 +#define ASPEED_PECI_EXP_READ_FCS_MASK GENMASK(23, 16) +#define ASPEED_PECI_EXP_AW_FCS_AUTO_MASK GENMASK(15, 8) +#define ASPEED_PECI_EXP_WRITE_FCS_MASK GENMASK(7, 0) + +/* Captured FCS Data Register */ +#define ASPEED_PECI_CAP_FCS 0x14 +#define ASPEED_PECI_CAP_READ_FCS_MASK GENMASK(23, 16) +#define ASPEED_PECI_CAP_WRITE_FCS_MASK GENMASK(7, 0) + +/* Interrupt Register */ +#define ASPEED_PECI_INT_CTRL 0x18 +#define ASPEED_PECI_TIMING_NEGO_SEL_MASK GENMASK(31, 30) +#define ASPEED_PECI_1ST_BIT_OF_ADDR_NEGO 0 +#define ASPEED_PECI_2ND_BIT_OF_ADDR_NEGO 1 +#define ASPEED_PECI_MESSAGE_NEGO 2 +#define ASPEED_PECI_INT_MASK GENMASK(4, 0) +#define ASPEED_PECI_INT_BUS_TIMEOUT BIT(4) +#define ASPEED_PECI_INT_BUS_CONNECT BIT(3) +#define ASPEED_PECI_INT_W_FCS_BAD BIT(2) +#define ASPEED_PECI_INT_W_FCS_ABORT BIT(1) +#define ASPEED_PECI_INT_CMD_DONE BIT(0) + +/* Interrupt Status Register */ +#define ASPEED_PECI_INT_STS 0x1c +#define ASPEED_PECI_INT_TIMING_RESULT_MASK GENMASK(29, 16) + /* bits[4..0]: Same bit fields in the 'Interrupt Register' */ + +/* Rx/Tx Data Buffer Registers */ +#define ASPEED_PECI_W_DATA0 0x20 +#define ASPEED_PECI_W_DATA1 0x24 +#define ASPEED_PECI_W_DATA2 0x28 +#define ASPEED_PECI_W_DATA3 0x2c +#define ASPEED_PECI_R_DATA0 0x30 +#define ASPEED_PECI_R_DATA1 0x34 +#define ASPEED_PECI_R_DATA2 0x38 +#define ASPEED_PECI_R_DATA3 0x3c +#define ASPEED_PECI_W_DATA4 0x40 +#define ASPEED_PECI_W_DATA5 0x44 +#define ASPEED_PECI_W_DATA6 0x48 +#define ASPEED_PECI_W_DATA7 0x4c +#define ASPEED_PECI_R_DATA4 0x50 +#define ASPEED_PECI_R_DATA5 0x54 +#define ASPEED_PECI_R_DATA6 0x58 +#define ASPEED_PECI_R_DATA7 0x5c +#define ASPEED_PECI_DATA_BUF_SIZE_MAX 32 + +/* Timing Negotiation */ +#define ASPEED_PECI_RD_SAMPLING_POINT_DEFAULT 8 +#define ASPEED_PECI_RD_SAMPLING_POINT_MAX 15 +#define ASPEED_PECI_CLK_DIV_DEFAULT 0 +#define ASPEED_PECI_CLK_DIV_MAX 7 +#define ASPEED_PECI_MSG_TIMING_DEFAULT 1 +#define ASPEED_PECI_MSG_TIMING_MAX 255 +#define ASPEED_PECI_ADDR_TIMING_DEFAULT 1 +#define ASPEED_PECI_ADDR_TIMING_MAX 255 + +/* Timeout */ +#define ASPEED_PECI_IDLE_CHECK_TIMEOUT_USEC 50000 +#define ASPEED_PECI_IDLE_CHECK_INTERVAL_USEC 10000 +#define ASPEED_PECI_CMD_TIMEOUT_MS_DEFAULT 1000 +#define ASPEED_PECI_CMD_TIMEOUT_MS_MAX 60000 + +struct aspeed_peci { + struct peci_adapter *adapter; + struct device *dev; + void __iomem *base; + struct clk *clk; + struct reset_control *rst; + int irq; + spinlock_t lock; /* to sync completion status handling */ + struct completion xfer_complete; + u32 status; + u32 cmd_timeout_ms; +}; + +static inline int aspeed_peci_check_idle(struct aspeed_peci *priv) +{ + u32 cmd_sts; + + return readl_poll_timeout(priv->base + ASPEED_PECI_CMD, + cmd_sts, + !(cmd_sts & ASPEED_PECI_CMD_IDLE_MASK), + ASPEED_PECI_IDLE_CHECK_INTERVAL_USEC, + ASPEED_PECI_IDLE_CHECK_TIMEOUT_USEC); +} + +static int aspeed_peci_xfer(struct peci_adapter *adapter, + struct peci_xfer_msg *msg) +{ + struct aspeed_peci *priv = peci_get_adapdata(adapter); + long err, timeout = msecs_to_jiffies(priv->cmd_timeout_ms); + u32 peci_head, peci_state, rx_data = 0; + ulong flags; + int i, ret; + uint reg; + + if (msg->tx_len > ASPEED_PECI_DATA_BUF_SIZE_MAX || + msg->rx_len > ASPEED_PECI_DATA_BUF_SIZE_MAX) + return -EINVAL; + + /* Check command sts and bus idle state */ + ret = aspeed_peci_check_idle(priv); + if (ret) + return ret; /* -ETIMEDOUT */ + + spin_lock_irqsave(&priv->lock, flags); + reinit_completion(&priv->xfer_complete); + + peci_head = FIELD_PREP(ASPEED_PECI_TAGET_ADDR_MASK, msg->addr) | + FIELD_PREP(ASPEED_PECI_WRITE_LEN_MASK, msg->tx_len) | + FIELD_PREP(ASPEED_PECI_READ_LEN_MASK, msg->rx_len); + + writel(peci_head, priv->base + ASPEED_PECI_RW_LENGTH); + + for (i = 0; i < msg->tx_len; i += 4) { + reg = i < 16 ? ASPEED_PECI_W_DATA0 + i % 16 : + ASPEED_PECI_W_DATA4 + i % 16; + writel(le32_to_cpup((__le32 *)&msg->tx_buf[i]), + priv->base + reg); + } + + dev_dbg(priv->dev, "HEAD : 0x%08x\n", peci_head); + print_hex_dump_debug("TX : ", DUMP_PREFIX_NONE, 16, 1, + msg->tx_buf, msg->tx_len, true); + + priv->status = 0; + writel(ASPEED_PECI_CMD_FIRE, priv->base + ASPEED_PECI_CMD); + spin_unlock_irqrestore(&priv->lock, flags); + + err = wait_for_completion_interruptible_timeout(&priv->xfer_complete, + timeout); + + spin_lock_irqsave(&priv->lock, flags); + dev_dbg(priv->dev, "INT_STS : 0x%08x\n", priv->status); + peci_state = readl(priv->base + ASPEED_PECI_CMD); + dev_dbg(priv->dev, "PECI_STATE : 0x%lx\n", + FIELD_GET(ASPEED_PECI_CMD_STS_MASK, peci_state)); + + writel(0, priv->base + ASPEED_PECI_CMD); + + if (err <= 0 || priv->status != ASPEED_PECI_INT_CMD_DONE) { + if (err < 0) { /* -ERESTARTSYS */ + ret = (int)err; + goto err_irqrestore; + } else if (err == 0) { + dev_dbg(priv->dev, "Timeout waiting for a response!\n"); + ret = -ETIMEDOUT; + goto err_irqrestore; + } + + dev_dbg(priv->dev, "No valid response!\n"); + ret = -EIO; + goto err_irqrestore; + } + + /* + * Note that rx_len and rx_buf size can be an odd number. + * Byte handling is more efficient. + */ + for (i = 0; i < msg->rx_len; i++) { + u8 byte_offset = i % 4; + + if (byte_offset == 0) { + reg = i < 16 ? ASPEED_PECI_R_DATA0 + i % 16 : + ASPEED_PECI_R_DATA4 + i % 16; + rx_data = readl(priv->base + reg); + } + + msg->rx_buf[i] = (u8)(rx_data >> (byte_offset << 3)); + } + + print_hex_dump_debug("RX : ", DUMP_PREFIX_NONE, 16, 1, + msg->rx_buf, msg->rx_len, true); + + peci_state = readl(priv->base + ASPEED_PECI_CMD); + dev_dbg(priv->dev, "PECI_STATE : 0x%lx\n", + FIELD_GET(ASPEED_PECI_CMD_STS_MASK, peci_state)); + dev_dbg(priv->dev, "------------------------\n"); + +err_irqrestore: + spin_unlock_irqrestore(&priv->lock, flags); + + return ret; +} + +static irqreturn_t aspeed_peci_irq_handler(int irq, void *arg) +{ + struct aspeed_peci *priv = arg; + u32 status; + + spin_lock(&priv->lock); + status = readl(priv->base + ASPEED_PECI_INT_STS); + writel(status, priv->base + ASPEED_PECI_INT_STS); + priv->status |= (status & ASPEED_PECI_INT_MASK); + + /* + * In most cases, interrupt bits will be set one by one but also note + * that multiple interrupt bits could be set at the same time. + */ + if (status & ASPEED_PECI_INT_BUS_TIMEOUT) + dev_dbg(priv->dev, "ASPEED_PECI_INT_BUS_TIMEOUT\n"); + + if (status & ASPEED_PECI_INT_BUS_CONNECT) + dev_dbg(priv->dev, "ASPEED_PECI_INT_BUS_CONNECT\n"); + + if (status & ASPEED_PECI_INT_W_FCS_BAD) + dev_dbg(priv->dev, "ASPEED_PECI_INT_W_FCS_BAD\n"); + + if (status & ASPEED_PECI_INT_W_FCS_ABORT) + dev_dbg(priv->dev, "ASPEED_PECI_INT_W_FCS_ABORT\n"); + + /* + * All commands should be ended up with a ASPEED_PECI_INT_CMD_DONE bit + * set even in an error case. + */ + if (status & ASPEED_PECI_INT_CMD_DONE) { + dev_dbg(priv->dev, "ASPEED_PECI_INT_CMD_DONE\n"); + complete(&priv->xfer_complete); + } + + spin_unlock(&priv->lock); + + return IRQ_HANDLED; +} + +static int aspeed_peci_init_ctrl(struct aspeed_peci *priv) +{ + u32 msg_timing, addr_timing, rd_sampling_point; + u32 clk_freq, clk_divisor, clk_div_val = 0; + int ret; + + priv->clk = devm_clk_get(priv->dev, NULL); + if (IS_ERR(priv->clk)) { + dev_err(priv->dev, "Failed to get clk source.\n"); + return PTR_ERR(priv->clk); + } + + ret = clk_prepare_enable(priv->clk); + if (ret) { + dev_err(priv->dev, "Failed to enable clock.\n"); + return ret; + } + + ret = device_property_read_u32(priv->dev, "clock-frequency", &clk_freq); + if (ret) { + dev_err(priv->dev, + "Could not read clock-frequency property.\n"); + clk_disable_unprepare(priv->clk); + return ret; + } + + clk_divisor = clk_get_rate(priv->clk) / clk_freq; + + while ((clk_divisor >> 1) && (clk_div_val < ASPEED_PECI_CLK_DIV_MAX)) + clk_div_val++; + + ret = device_property_read_u32(priv->dev, "msg-timing", &msg_timing); + if (ret || msg_timing > ASPEED_PECI_MSG_TIMING_MAX) { + if (!ret) + dev_warn(priv->dev, + "Invalid msg-timing : %u, Use default : %u\n", + msg_timing, ASPEED_PECI_MSG_TIMING_DEFAULT); + msg_timing = ASPEED_PECI_MSG_TIMING_DEFAULT; + } + + ret = device_property_read_u32(priv->dev, "addr-timing", &addr_timing); + if (ret || addr_timing > ASPEED_PECI_ADDR_TIMING_MAX) { + if (!ret) + dev_warn(priv->dev, + "Invalid addr-timing : %u, Use default : %u\n", + addr_timing, ASPEED_PECI_ADDR_TIMING_DEFAULT); + addr_timing = ASPEED_PECI_ADDR_TIMING_DEFAULT; + } + + ret = device_property_read_u32(priv->dev, "rd-sampling-point", + &rd_sampling_point); + if (ret || rd_sampling_point > ASPEED_PECI_RD_SAMPLING_POINT_MAX) { + if (!ret) + dev_warn(priv->dev, + "Invalid rd-sampling-point : %u. Use default : %u\n", + rd_sampling_point, + ASPEED_PECI_RD_SAMPLING_POINT_DEFAULT); + rd_sampling_point = ASPEED_PECI_RD_SAMPLING_POINT_DEFAULT; + } + + ret = device_property_read_u32(priv->dev, "cmd-timeout-ms", + &priv->cmd_timeout_ms); + if (ret || priv->cmd_timeout_ms > ASPEED_PECI_CMD_TIMEOUT_MS_MAX || + priv->cmd_timeout_ms == 0) { + if (!ret) + dev_warn(priv->dev, + "Invalid cmd-timeout-ms : %u. Use default : %u\n", + priv->cmd_timeout_ms, + ASPEED_PECI_CMD_TIMEOUT_MS_DEFAULT); + priv->cmd_timeout_ms = ASPEED_PECI_CMD_TIMEOUT_MS_DEFAULT; + } + + writel(FIELD_PREP(ASPEED_PECI_CTRL_CLK_DIV_MASK, + ASPEED_PECI_CLK_DIV_DEFAULT) | + ASPEED_PECI_CTRL_PECI_CLK_EN, priv->base + ASPEED_PECI_CTRL); + + /* + * Timing negotiation period setting. + * The unit of the programmed value is 4 times of PECI clock period. + */ + writel(FIELD_PREP(ASPEED_PECI_TIMING_MESSAGE_MASK, msg_timing) | + FIELD_PREP(ASPEED_PECI_TIMING_ADDRESS_MASK, addr_timing), + priv->base + ASPEED_PECI_TIMING_NEGOTIATION); + + /* Clear interrupts */ + writel(readl(priv->base + ASPEED_PECI_INT_STS) | ASPEED_PECI_INT_MASK, + priv->base + ASPEED_PECI_INT_STS); + + /* Set timing negotiation mode and enable interrupts */ + writel(FIELD_PREP(ASPEED_PECI_TIMING_NEGO_SEL_MASK, + ASPEED_PECI_1ST_BIT_OF_ADDR_NEGO) | + ASPEED_PECI_INT_MASK, priv->base + ASPEED_PECI_INT_CTRL); + + /* Read sampling point and clock speed setting */ + writel(FIELD_PREP(ASPEED_PECI_CTRL_SAMPLING_MASK, rd_sampling_point) | + FIELD_PREP(ASPEED_PECI_CTRL_CLK_DIV_MASK, clk_div_val) | + ASPEED_PECI_CTRL_PECI_EN | ASPEED_PECI_CTRL_PECI_CLK_EN, + priv->base + ASPEED_PECI_CTRL); + + return 0; +} + +static int aspeed_peci_probe(struct platform_device *pdev) +{ + struct peci_adapter *adapter; + struct aspeed_peci *priv; + int ret; + + adapter = peci_alloc_adapter(&pdev->dev, sizeof(*priv)); + if (!adapter) + return -ENOMEM; + + priv = peci_get_adapdata(adapter); + priv->adapter = adapter; + priv->dev = &pdev->dev; + dev_set_drvdata(&pdev->dev, priv); + + priv->base = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(priv->base)) { + ret = PTR_ERR(priv->base); + goto err_put_adapter_dev; + } + + priv->irq = platform_get_irq(pdev, 0); + if (!priv->irq) { + ret = -ENODEV; + goto err_put_adapter_dev; + } + + ret = devm_request_irq(&pdev->dev, priv->irq, aspeed_peci_irq_handler, + 0, "peci-aspeed-irq", priv); + if (ret) + goto err_put_adapter_dev; + + init_completion(&priv->xfer_complete); + spin_lock_init(&priv->lock); + + priv->adapter->owner = THIS_MODULE; + priv->adapter->dev.of_node = of_node_get(dev_of_node(priv->dev)); + strlcpy(priv->adapter->name, pdev->name, sizeof(priv->adapter->name)); + priv->adapter->xfer = aspeed_peci_xfer; + priv->adapter->use_dma = false; + + priv->rst = devm_reset_control_get(&pdev->dev, NULL); + if (IS_ERR(priv->rst)) { + dev_err(&pdev->dev, + "missing or invalid reset controller entry\n"); + ret = PTR_ERR(priv->rst); + goto err_put_adapter_dev; + } + reset_control_deassert(priv->rst); + + ret = aspeed_peci_init_ctrl(priv); + if (ret) + goto err_put_adapter_dev; + + ret = peci_add_adapter(priv->adapter); + if (ret) + goto err_put_adapter_dev; + + dev_info(&pdev->dev, "peci bus %d registered, irq %d\n", + priv->adapter->nr, priv->irq); + + return 0; + +err_put_adapter_dev: + put_device(&adapter->dev); + + return ret; +} + +static int aspeed_peci_remove(struct platform_device *pdev) +{ + struct aspeed_peci *priv = dev_get_drvdata(&pdev->dev); + + clk_disable_unprepare(priv->clk); + reset_control_assert(priv->rst); + peci_del_adapter(priv->adapter); + of_node_put(priv->adapter->dev.of_node); + + return 0; +} + +static const struct of_device_id aspeed_peci_of_table[] = { + { .compatible = "aspeed,ast2400-peci", }, + { .compatible = "aspeed,ast2500-peci", }, + { .compatible = "aspeed,ast2600-peci", }, + { } +}; +MODULE_DEVICE_TABLE(of, aspeed_peci_of_table); + +static struct platform_driver aspeed_peci_driver = { + .probe = aspeed_peci_probe, + .remove = aspeed_peci_remove, + .driver = { + .name = KBUILD_MODNAME, + .of_match_table = of_match_ptr(aspeed_peci_of_table), + }, +}; +module_platform_driver(aspeed_peci_driver); + +MODULE_AUTHOR("Ryan Chen <ryan_chen@aspeedtech.com>"); +MODULE_AUTHOR("Jae Hyun Yoo <jae.hyun.yoo@linux.intel.com>"); +MODULE_DESCRIPTION("ASPEED PECI driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/peci/busses/peci-npcm.c b/drivers/peci/busses/peci-npcm.c new file mode 100644 index 000000000000..bdebbf1ec7f1 --- /dev/null +++ b/drivers/peci/busses/peci-npcm.c @@ -0,0 +1,406 @@ +// SPDX-License-Identifier: GPL-2.0 +// Copyright (c) 2019 Nuvoton Technology corporation. + +#include <linux/bitfield.h> +#include <linux/clk.h> +#include <linux/interrupt.h> +#include <linux/jiffies.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/peci.h> +#include <linux/platform_device.h> +#include <linux/regmap.h> +#include <linux/mfd/syscon.h> +#include <linux/reset.h> + +/* NPCM7xx GCR module */ +#define NPCM7XX_INTCR3_OFFSET 0x9C +#define NPCM7XX_INTCR3_PECIVSEL BIT(19) + +/* NPCM PECI Registers */ +#define NPCM_PECI_CTL_STS 0x00 +#define NPCM_PECI_RD_LENGTH 0x04 +#define NPCM_PECI_ADDR 0x08 +#define NPCM_PECI_CMD 0x0C +#define NPCM_PECI_CTL2 0x10 +#define NPCM_PECI_WR_LENGTH 0x1C +#define NPCM_PECI_PDDR 0x2C +#define NPCM_PECI_DAT_INOUT(n) (0x100 + ((n) * 4)) + +#define NPCM_PECI_MAX_REG 0x200 + +/* NPCM_PECI_CTL_STS - 0x00 : Control Register */ +#define NPCM_PECI_CTRL_DONE_INT_EN BIT(6) +#define NPCM_PECI_CTRL_ABRT_ERR BIT(4) +#define NPCM_PECI_CTRL_CRC_ERR BIT(3) +#define NPCM_PECI_CTRL_DONE BIT(1) +#define NPCM_PECI_CTRL_START_BUSY BIT(0) + +/* NPCM_PECI_RD_LENGTH - 0x04 : Command Register */ +#define NPCM_PECI_RD_LEN_MASK GENMASK(6, 0) + +/* NPCM_PECI_CMD - 0x10 : Command Register */ +#define NPCM_PECI_CTL2_MASK GENMASK(7, 6) + +/* NPCM_PECI_WR_LENGTH - 0x1C : Command Register */ +#define NPCM_PECI_WR_LEN_MASK GENMASK(6, 0) + +/* NPCM_PECI_PDDR - 0x2C : Command Register */ +#define NPCM_PECI_PDDR_MASK GENMASK(4, 0) + +#define NPCM_PECI_INT_MASK \ + (NPCM_PECI_CTRL_ABRT_ERR | NPCM_PECI_CTRL_CRC_ERR | NPCM_PECI_CTRL_DONE) + +#define NPCM_PECI_IDLE_CHECK_TIMEOUT_USEC 50000 +#define NPCM_PECI_IDLE_CHECK_INTERVAL_USEC 10000 +#define NPCM_PECI_CMD_TIMEOUT_MS_DEFAULT 1000 +#define NPCM_PECI_CMD_TIMEOUT_MS_MAX 60000 +#define NPCM_PECI_HOST_NEG_BIT_RATE_MAX 31 +#define NPCM_PECI_HOST_NEG_BIT_RATE_MIN 7 +#define NPCM_PECI_HOST_NEG_BIT_RATE_DEFAULT 15 +#define NPCM_PECI_PULL_DOWN_DEFAULT 0 +#define NPCM_PECI_PULL_DOWN_MAX 2 + +struct npcm_peci { + u32 cmd_timeout_ms; + u32 host_bit_rate; + struct completion xfer_complete; + struct regmap *gcr_regmap; + struct peci_adapter *adapter; + struct regmap *regmap; + u32 status; + spinlock_t lock; /* to sync completion status handling */ + struct device *dev; + struct clk *clk; + int irq; +}; + +static int npcm_peci_xfer_native(struct npcm_peci *priv, + struct peci_xfer_msg *msg) +{ + long err, timeout = msecs_to_jiffies(priv->cmd_timeout_ms); + unsigned long flags; + unsigned int msg_rd; + u32 cmd_sts; + int i, rc; + + /* Check command sts and bus idle state */ + rc = regmap_read_poll_timeout(priv->regmap, NPCM_PECI_CTL_STS, cmd_sts, + !(cmd_sts & NPCM_PECI_CTRL_START_BUSY), + NPCM_PECI_IDLE_CHECK_INTERVAL_USEC, + NPCM_PECI_IDLE_CHECK_TIMEOUT_USEC); + if (rc) + return rc; /* -ETIMEDOUT */ + + spin_lock_irqsave(&priv->lock, flags); + reinit_completion(&priv->xfer_complete); + + regmap_write(priv->regmap, NPCM_PECI_ADDR, msg->addr); + regmap_write(priv->regmap, NPCM_PECI_RD_LENGTH, + NPCM_PECI_WR_LEN_MASK & msg->rx_len); + regmap_write(priv->regmap, NPCM_PECI_WR_LENGTH, + NPCM_PECI_WR_LEN_MASK & msg->tx_len); + + if (msg->tx_len) { + regmap_write(priv->regmap, NPCM_PECI_CMD, msg->tx_buf[0]); + + for (i = 0; i < (msg->tx_len - 1); i++) + regmap_write(priv->regmap, NPCM_PECI_DAT_INOUT(i), + msg->tx_buf[i + 1]); + } + + priv->status = 0; + regmap_update_bits(priv->regmap, NPCM_PECI_CTL_STS, + NPCM_PECI_CTRL_START_BUSY, + NPCM_PECI_CTRL_START_BUSY); + + spin_unlock_irqrestore(&priv->lock, flags); + + err = wait_for_completion_interruptible_timeout(&priv->xfer_complete, + timeout); + + spin_lock_irqsave(&priv->lock, flags); + + regmap_write(priv->regmap, NPCM_PECI_CMD, 0); + + if (err <= 0 || priv->status != NPCM_PECI_CTRL_DONE) { + if (err < 0) { /* -ERESTARTSYS */ + rc = (int)err; + goto err_irqrestore; + } else if (err == 0) { + dev_dbg(priv->dev, "Timeout waiting for a response!\n"); + rc = -ETIMEDOUT; + goto err_irqrestore; + } + + dev_dbg(priv->dev, "No valid response!\n"); + rc = -EIO; + goto err_irqrestore; + } + + for (i = 0; i < msg->rx_len; i++) { + regmap_read(priv->regmap, NPCM_PECI_DAT_INOUT(i), &msg_rd); + msg->rx_buf[i] = (u8)msg_rd; + } + +err_irqrestore: + spin_unlock_irqrestore(&priv->lock, flags); + return rc; +} + +static irqreturn_t npcm_peci_irq_handler(int irq, void *arg) +{ + struct npcm_peci *priv = arg; + u32 status_ack = 0; + u32 status; + + spin_lock(&priv->lock); + regmap_read(priv->regmap, NPCM_PECI_CTL_STS, &status); + priv->status |= (status & NPCM_PECI_INT_MASK); + + if (status & NPCM_PECI_CTRL_CRC_ERR) { + dev_dbg(priv->dev, "PECI_INT_W_FCS_BAD\n"); + status_ack |= NPCM_PECI_CTRL_CRC_ERR; + } + + if (status & NPCM_PECI_CTRL_ABRT_ERR) { + dev_dbg(priv->dev, "NPCM_PECI_CTRL_ABRT_ERR\n"); + status_ack |= NPCM_PECI_CTRL_ABRT_ERR; + } + + /* + * All commands should be ended up with a NPCM_PECI_CTRL_DONE + * bit set even in an error case. + */ + if (status & NPCM_PECI_CTRL_DONE) { + dev_dbg(priv->dev, "NPCM_PECI_CTRL_DONE\n"); + status_ack |= NPCM_PECI_CTRL_DONE; + complete(&priv->xfer_complete); + } + + regmap_write_bits(priv->regmap, NPCM_PECI_CTL_STS, + NPCM_PECI_INT_MASK, status_ack); + + spin_unlock(&priv->lock); + return IRQ_HANDLED; +} + +static int npcm_peci_init_ctrl(struct npcm_peci *priv) +{ + u32 cmd_sts, host_neg_bit_rate = 0, pull_down = 0; + int ret; + + priv->clk = devm_clk_get(priv->dev, NULL); + if (IS_ERR(priv->clk)) { + dev_err(priv->dev, "Failed to get clk source.\n"); + return PTR_ERR(priv->clk); + } + + ret = clk_prepare_enable(priv->clk); + if (ret) { + dev_err(priv->dev, "Failed to enable clock.\n"); + return ret; + } + + ret = of_property_read_u32(priv->dev->of_node, "cmd-timeout-ms", + &priv->cmd_timeout_ms); + if (ret || priv->cmd_timeout_ms > NPCM_PECI_CMD_TIMEOUT_MS_MAX || + priv->cmd_timeout_ms == 0) { + if (ret) + dev_warn(priv->dev, + "cmd-timeout-ms not found, use default : %u\n", + NPCM_PECI_CMD_TIMEOUT_MS_DEFAULT); + else + dev_warn(priv->dev, + "Invalid cmd-timeout-ms : %u. Use default : %u\n", + priv->cmd_timeout_ms, + NPCM_PECI_CMD_TIMEOUT_MS_DEFAULT); + + priv->cmd_timeout_ms = NPCM_PECI_CMD_TIMEOUT_MS_DEFAULT; + } + + if (of_device_is_compatible(priv->dev->of_node, + "nuvoton,npcm750-peci")) { + priv->gcr_regmap = syscon_regmap_lookup_by_compatible + ("nuvoton,npcm750-gcr"); + if (!IS_ERR(priv->gcr_regmap)) { + bool volt = of_property_read_bool(priv->dev->of_node, + "high-volt-range"); + if (volt) + regmap_update_bits(priv->gcr_regmap, + NPCM7XX_INTCR3_OFFSET, + NPCM7XX_INTCR3_PECIVSEL, + NPCM7XX_INTCR3_PECIVSEL); + else + regmap_update_bits(priv->gcr_regmap, + NPCM7XX_INTCR3_OFFSET, + NPCM7XX_INTCR3_PECIVSEL, 0); + } + } + + ret = of_property_read_u32(priv->dev->of_node, "pull-down", + &pull_down); + if (ret || pull_down > NPCM_PECI_PULL_DOWN_MAX) { + if (ret) + dev_warn(priv->dev, + "pull-down not found, use default : %u\n", + NPCM_PECI_PULL_DOWN_DEFAULT); + else + dev_warn(priv->dev, + "Invalid pull-down : %u. Use default : %u\n", + pull_down, + NPCM_PECI_PULL_DOWN_DEFAULT); + pull_down = NPCM_PECI_PULL_DOWN_DEFAULT; + } + + regmap_update_bits(priv->regmap, NPCM_PECI_CTL2, NPCM_PECI_CTL2_MASK, + pull_down << 6); + + ret = of_property_read_u32(priv->dev->of_node, "host-neg-bit-rate", + &host_neg_bit_rate); + if (ret || host_neg_bit_rate > NPCM_PECI_HOST_NEG_BIT_RATE_MAX || + host_neg_bit_rate < NPCM_PECI_HOST_NEG_BIT_RATE_MIN) { + if (ret) + dev_warn(priv->dev, + "host-neg-bit-rate not found, use default : %u\n", + NPCM_PECI_HOST_NEG_BIT_RATE_DEFAULT); + else + dev_warn(priv->dev, + "Invalid host-neg-bit-rate : %u. Use default : %u\n", + host_neg_bit_rate, + NPCM_PECI_HOST_NEG_BIT_RATE_DEFAULT); + host_neg_bit_rate = NPCM_PECI_HOST_NEG_BIT_RATE_DEFAULT; + } + + regmap_update_bits(priv->regmap, NPCM_PECI_PDDR, NPCM_PECI_PDDR_MASK, + host_neg_bit_rate); + + priv->host_bit_rate = clk_get_rate(priv->clk) / + (4 * (host_neg_bit_rate + 1)); + + ret = regmap_read_poll_timeout(priv->regmap, NPCM_PECI_CTL_STS, cmd_sts, + !(cmd_sts & NPCM_PECI_CTRL_START_BUSY), + NPCM_PECI_IDLE_CHECK_INTERVAL_USEC, + NPCM_PECI_IDLE_CHECK_TIMEOUT_USEC); + if (ret) + return ret; /* -ETIMEDOUT */ + + /* PECI interrupt enable */ + regmap_update_bits(priv->regmap, NPCM_PECI_CTL_STS, + NPCM_PECI_CTRL_DONE_INT_EN, + NPCM_PECI_CTRL_DONE_INT_EN); + + return 0; +} + +static const struct regmap_config npcm_peci_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + .max_register = NPCM_PECI_MAX_REG, + .fast_io = true, +}; + +static int npcm_peci_xfer(struct peci_adapter *adapter, + struct peci_xfer_msg *msg) +{ + struct npcm_peci *priv = peci_get_adapdata(adapter); + + return npcm_peci_xfer_native(priv, msg); +} + +static int npcm_peci_probe(struct platform_device *pdev) +{ + struct peci_adapter *adapter; + struct npcm_peci *priv; + void __iomem *base; + int ret; + + adapter = peci_alloc_adapter(&pdev->dev, sizeof(*priv)); + if (!adapter) + return -ENOMEM; + + priv = peci_get_adapdata(adapter); + priv->adapter = adapter; + priv->dev = &pdev->dev; + dev_set_drvdata(&pdev->dev, priv); + + base = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(base)) { + ret = PTR_ERR(base); + goto err_put_adapter_dev; + } + + priv->regmap = devm_regmap_init_mmio(&pdev->dev, base, + &npcm_peci_regmap_config); + if (IS_ERR(priv->regmap)) { + ret = PTR_ERR(priv->regmap); + goto err_put_adapter_dev; + } + + priv->irq = platform_get_irq(pdev, 0); + if (!priv->irq) { + ret = -ENODEV; + goto err_put_adapter_dev; + } + + ret = devm_request_irq(&pdev->dev, priv->irq, npcm_peci_irq_handler, + 0, "peci-npcm-irq", priv); + if (ret) + goto err_put_adapter_dev; + + init_completion(&priv->xfer_complete); + spin_lock_init(&priv->lock); + + priv->adapter->owner = THIS_MODULE; + priv->adapter->dev.of_node = of_node_get(dev_of_node(priv->dev)); + strlcpy(priv->adapter->name, pdev->name, sizeof(priv->adapter->name)); + priv->adapter->xfer = npcm_peci_xfer; + + ret = npcm_peci_init_ctrl(priv); + if (ret) + goto err_put_adapter_dev; + + ret = peci_add_adapter(priv->adapter); + if (ret) + goto err_put_adapter_dev; + + dev_info(&pdev->dev, "peci bus %d registered, host negotiation bit rate %dHz", + priv->adapter->nr, priv->host_bit_rate); + + return 0; + +err_put_adapter_dev: + put_device(&adapter->dev); + return ret; +} + +static int npcm_peci_remove(struct platform_device *pdev) +{ + struct npcm_peci *priv = dev_get_drvdata(&pdev->dev); + + clk_disable_unprepare(priv->clk); + peci_del_adapter(priv->adapter); + of_node_put(priv->adapter->dev.of_node); + + return 0; +} + +static const struct of_device_id npcm_peci_of_table[] = { + { .compatible = "nuvoton,npcm750-peci", }, + { } +}; +MODULE_DEVICE_TABLE(of, npcm_peci_of_table); + +static struct platform_driver npcm_peci_driver = { + .probe = npcm_peci_probe, + .remove = npcm_peci_remove, + .driver = { + .name = KBUILD_MODNAME, + .of_match_table = of_match_ptr(npcm_peci_of_table), + }, +}; +module_platform_driver(npcm_peci_driver); + +MODULE_AUTHOR("Tomer Maimon <tomer.maimon@nuvoton.com>"); +MODULE_DESCRIPTION("NPCM Platform Environment Control Interface (PECI) driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/peci/peci-core.c b/drivers/peci/peci-core.c new file mode 100644 index 000000000000..9aedb74710e6 --- /dev/null +++ b/drivers/peci/peci-core.c @@ -0,0 +1,2089 @@ +// SPDX-License-Identifier: GPL-2.0 +// Copyright (c) 2018-2019 Intel Corporation + +#include <linux/bitfield.h> +#include <linux/crc8.h> +#include <linux/mm.h> +#include <linux/module.h> +#include <linux/of_device.h> +#include <linux/peci.h> +#include <linux/pm_domain.h> +#include <linux/pm_runtime.h> +#include <linux/sched/task_stack.h> +#include <linux/slab.h> + +/* Mask for getting minor revision number from DIB */ +#define REVISION_NUM_MASK GENMASK(15, 8) + +/* CRC8 table for Assured Write Frame Check */ +#define PECI_CRC8_POLYNOMIAL 0x07 +DECLARE_CRC8_TABLE(peci_crc8_table); + +static bool is_registered; + +static DEFINE_MUTEX(core_lock); +static DEFINE_IDR(peci_adapter_idr); + +struct peci_adapter *peci_get_adapter(int nr) +{ + struct peci_adapter *adapter; + + mutex_lock(&core_lock); + adapter = idr_find(&peci_adapter_idr, nr); + if (!adapter) + goto out_unlock; + + if (try_module_get(adapter->owner)) + get_device(&adapter->dev); + else + adapter = NULL; + +out_unlock: + mutex_unlock(&core_lock); + + return adapter; +} +EXPORT_SYMBOL_GPL(peci_get_adapter); + +void peci_put_adapter(struct peci_adapter *adapter) +{ + if (!adapter) + return; + + put_device(&adapter->dev); + module_put(adapter->owner); +} +EXPORT_SYMBOL_GPL(peci_put_adapter); + +static ssize_t name_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + return sprintf(buf, "%s\n", dev->type == &peci_client_type ? + to_peci_client(dev)->name : to_peci_adapter(dev)->name); +} +static DEVICE_ATTR_RO(name); + +static void peci_client_dev_release(struct device *dev) +{ + struct peci_client *client = to_peci_client(dev); + + dev_dbg(dev, "%s: %s\n", __func__, client->name); + peci_put_adapter(client->adapter); + kfree(client); +} + +static struct attribute *peci_device_attrs[] = { + &dev_attr_name.attr, + NULL +}; +ATTRIBUTE_GROUPS(peci_device); + +struct device_type peci_client_type = { + .groups = peci_device_groups, + .release = peci_client_dev_release, +}; +EXPORT_SYMBOL_GPL(peci_client_type); + +/** + * peci_verify_client - return parameter as peci_client, or NULL + * @dev: device, probably from some driver model iterator + * + * Return: pointer to peci_client on success, else NULL. + */ +struct peci_client *peci_verify_client(struct device *dev) +{ + return (dev->type == &peci_client_type) + ? to_peci_client(dev) + : NULL; +} +EXPORT_SYMBOL_GPL(peci_verify_client); + +/** + * peci_get_xfer_msg() - get a DMA safe peci_xfer_msg for the given tx and rx + * length + * @tx_len: the length of tx_buf. May be 0 if tx_buf isn't needed. + * @rx_len: the length of rx_buf. May be 0 if rx_buf isn't needed. + * + * Return: NULL if a DMA safe buffer was not obtained. + * Or a valid pointer to be used with DMA. After use, release it by + * calling peci_put_xfer_msg(). + * + * This function must only be called from process context! + */ +struct peci_xfer_msg *peci_get_xfer_msg(u8 tx_len, u8 rx_len) +{ + struct peci_xfer_msg *msg; + u8 *tx_buf, *rx_buf; + + if (tx_len) { + tx_buf = kzalloc(tx_len, GFP_KERNEL); + if (!tx_buf) + return NULL; + } else { + tx_buf = NULL; + } + + if (rx_len) { + rx_buf = kzalloc(rx_len, GFP_KERNEL); + if (!rx_buf) + goto err_free_tx_buf; + } else { + rx_buf = NULL; + } + + msg = kzalloc(sizeof(*msg), GFP_KERNEL); + if (!msg) + goto err_free_tx_rx_buf; + + msg->tx_len = tx_len; + msg->tx_buf = tx_buf; + msg->rx_len = rx_len; + msg->rx_buf = rx_buf; + + return msg; + +err_free_tx_rx_buf: + kfree(rx_buf); +err_free_tx_buf: + kfree(tx_buf); + + return NULL; +} +EXPORT_SYMBOL_GPL(peci_get_xfer_msg); + +/** + * peci_put_xfer_msg - release a DMA safe peci_xfer_msg + * @msg: the message obtained from peci_get_xfer_msg(). May be NULL. + */ +void peci_put_xfer_msg(struct peci_xfer_msg *msg) +{ + if (!msg) + return; + + kfree(msg->rx_buf); + kfree(msg->tx_buf); + kfree(msg); +} +EXPORT_SYMBOL_GPL(peci_put_xfer_msg); + +/* Calculate an Assured Write Frame Check Sequence byte */ +static int peci_aw_fcs(struct peci_xfer_msg *msg, int len, u8 *aw_fcs) +{ + u8 *tmp_buf; + + /* Allocate a temporary buffer to use a contiguous byte array */ + tmp_buf = kmalloc(len, GFP_KERNEL); + if (!tmp_buf) + return -ENOMEM; + + tmp_buf[0] = msg->addr; + tmp_buf[1] = msg->tx_len; + tmp_buf[2] = msg->rx_len; + memcpy(&tmp_buf[3], msg->tx_buf, len - 3); + + *aw_fcs = crc8(peci_crc8_table, tmp_buf, (size_t)len, 0); + + kfree(tmp_buf); + + return 0; +} + +static int __peci_xfer(struct peci_adapter *adapter, struct peci_xfer_msg *msg, + bool do_retry, bool has_aw_fcs) +{ + uint interval_ms = PECI_DEV_RETRY_INTERVAL_MIN_MSEC; + ulong timeout = jiffies; + u8 aw_fcs; + int ret; + + /* + * In case if adapter uses DMA, check at here whether tx and rx buffers + * are DMA capable or not. + */ + if (IS_ENABLED(CONFIG_HAS_DMA) && adapter->use_dma) { + if (is_vmalloc_addr(msg->tx_buf) || + is_vmalloc_addr(msg->rx_buf)) { + WARN_ONCE(1, "xfer msg is not dma capable\n"); + return -EAGAIN; + } else if (object_is_on_stack(msg->tx_buf) || + object_is_on_stack(msg->rx_buf)) { + WARN_ONCE(1, "xfer msg is on stack\n"); + return -EAGAIN; + } + } + + /* + * For some commands, the PECI originator may need to retry a command if + * the processor PECI client responds with a 0x8x completion code. In + * each instance, the processor PECI client may have started the + * operation but not completed it yet. When the 'retry' bit is set, the + * PECI client will ignore a new request if it exactly matches a + * previous valid request. For better performance and for reducing + * retry traffic, the interval time will be increased exponentially. + */ + + if (do_retry) + timeout += PECI_DEV_RETRY_TIMEOUT; + + for (;;) { + ret = adapter->xfer(adapter, msg); + + if (!do_retry || ret || !msg->rx_buf) + break; + + /* Retry is needed when completion code is 0x8x */ + if ((msg->rx_buf[0] & PECI_DEV_CC_RETRY_CHECK_MASK) != + PECI_DEV_CC_NEED_RETRY) + break; + + /* Set the retry bit to indicate a retry attempt */ + msg->tx_buf[1] |= PECI_DEV_RETRY_BIT; + + /* Recalculate the AW FCS if it has one */ + if (has_aw_fcs) { + ret = peci_aw_fcs(msg, 2 + msg->tx_len, &aw_fcs); + if (ret) + break; + + msg->tx_buf[msg->tx_len - 1] = 0x80 ^ aw_fcs; + } + + /* Retry it for 'timeout' before returning an error. */ + if (time_after(jiffies, timeout)) { + dev_dbg(&adapter->dev, "Timeout retrying xfer!\n"); + ret = -ETIMEDOUT; + break; + } + + set_current_state(TASK_INTERRUPTIBLE); + if (schedule_timeout(msecs_to_jiffies(interval_ms))) { + ret = -EINTR; + break; + } + + interval_ms *= 2; + if (interval_ms > PECI_DEV_RETRY_INTERVAL_MAX_MSEC) + interval_ms = PECI_DEV_RETRY_INTERVAL_MAX_MSEC; + } + + if (ret) + dev_dbg(&adapter->dev, "xfer error: %d\n", ret); + + return ret; +} + +static int peci_xfer(struct peci_adapter *adapter, struct peci_xfer_msg *msg) +{ + return __peci_xfer(adapter, msg, false, false); +} + +static int peci_xfer_with_retries(struct peci_adapter *adapter, + struct peci_xfer_msg *msg, + bool has_aw_fcs) +{ + return __peci_xfer(adapter, msg, true, has_aw_fcs); +} + +static int peci_scan_cmd_mask(struct peci_adapter *adapter) +{ + struct peci_xfer_msg *msg; + u8 revision; + int ret; + u64 dib; + + /* Update command mask just once */ + if (adapter->cmd_mask & BIT(PECI_CMD_XFER)) + return 0; + + msg = peci_get_xfer_msg(PECI_GET_DIB_WR_LEN, PECI_GET_DIB_RD_LEN); + if (!msg) + return -ENOMEM; + + msg->addr = PECI_BASE_ADDR; + msg->tx_buf[0] = PECI_GET_DIB_CMD; + + ret = peci_xfer(adapter, msg); + if (ret) + return ret; + + dib = le64_to_cpup((__le64 *)msg->rx_buf); + + /* Check special case for Get DIB command */ + if (dib == 0) { + dev_dbg(&adapter->dev, "DIB read as 0\n"); + ret = -EIO; + goto out; + } + + /* + * Setting up the supporting commands based on revision number. + * See PECI Spec Table 3-1. + */ + revision = FIELD_GET(REVISION_NUM_MASK, dib); + if (revision >= 0x40) { /* Rev. 4.0 */ + adapter->cmd_mask |= BIT(PECI_CMD_RD_IA_MSREX); + adapter->cmd_mask |= BIT(PECI_CMD_RD_END_PT_CFG); + adapter->cmd_mask |= BIT(PECI_CMD_WR_END_PT_CFG); + adapter->cmd_mask |= BIT(PECI_CMD_CRASHDUMP_DISC); + adapter->cmd_mask |= BIT(PECI_CMD_CRASHDUMP_GET_FRAME); + } + if (revision >= 0x36) /* Rev. 3.6 */ + adapter->cmd_mask |= BIT(PECI_CMD_WR_IA_MSR); + if (revision >= 0x35) /* Rev. 3.5 */ + adapter->cmd_mask |= BIT(PECI_CMD_WR_PCI_CFG); + if (revision >= 0x34) /* Rev. 3.4 */ + adapter->cmd_mask |= BIT(PECI_CMD_RD_PCI_CFG); + if (revision >= 0x33) { /* Rev. 3.3 */ + adapter->cmd_mask |= BIT(PECI_CMD_RD_PCI_CFG_LOCAL); + adapter->cmd_mask |= BIT(PECI_CMD_WR_PCI_CFG_LOCAL); + } + if (revision >= 0x32) /* Rev. 3.2 */ + adapter->cmd_mask |= BIT(PECI_CMD_RD_IA_MSR); + if (revision >= 0x31) { /* Rev. 3.1 */ + adapter->cmd_mask |= BIT(PECI_CMD_RD_PKG_CFG); + adapter->cmd_mask |= BIT(PECI_CMD_WR_PKG_CFG); + } + + adapter->cmd_mask |= BIT(PECI_CMD_XFER); + adapter->cmd_mask |= BIT(PECI_CMD_GET_TEMP); + adapter->cmd_mask |= BIT(PECI_CMD_GET_DIB); + adapter->cmd_mask |= BIT(PECI_CMD_PING); + +out: + peci_put_xfer_msg(msg); + + return ret; +} + +static int peci_check_cmd_support(struct peci_adapter *adapter, + enum peci_cmd cmd) +{ + if (!(adapter->cmd_mask & BIT(PECI_CMD_PING)) && + peci_scan_cmd_mask(adapter) < 0) { + dev_dbg(&adapter->dev, "Failed to scan command mask\n"); + return -EIO; + } + + if (!(adapter->cmd_mask & BIT(cmd))) { + dev_dbg(&adapter->dev, "Command %d is not supported\n", cmd); + return -EINVAL; + } + + return 0; +} + +static int peci_cmd_xfer(struct peci_adapter *adapter, void *vmsg) +{ + struct peci_xfer_msg *msg = vmsg; + u8 aw_fcs; + int ret; + + if (!msg->tx_len) { + ret = peci_xfer(adapter, msg); + } else { + switch (msg->tx_buf[0]) { + case PECI_RDPKGCFG_CMD: + case PECI_RDIAMSR_CMD: + case PECI_RDIAMSREX_CMD: + case PECI_RDPCICFG_CMD: + case PECI_RDPCICFGLOCAL_CMD: + case PECI_RDENDPTCFG_CMD: + case PECI_CRASHDUMP_CMD: + ret = peci_xfer_with_retries(adapter, msg, false); + break; + case PECI_WRPKGCFG_CMD: + case PECI_WRIAMSR_CMD: + case PECI_WRPCICFG_CMD: + case PECI_WRPCICFGLOCAL_CMD: + case PECI_WRENDPTCFG_CMD: + /* Check if the AW FCS byte is already provided */ + ret = peci_aw_fcs(msg, 2 + msg->tx_len, &aw_fcs); + if (ret) + break; + + if (msg->tx_buf[msg->tx_len - 1] != (0x80 ^ aw_fcs)) { + /* + * Add an Assured Write Frame Check Sequence + * byte and increment the tx_len to include + * the new byte. + */ + msg->tx_len++; + ret = peci_aw_fcs(msg, 2 + msg->tx_len, + &aw_fcs); + if (ret) + break; + + msg->tx_buf[msg->tx_len - 1] = 0x80 ^ aw_fcs; + } + + ret = peci_xfer_with_retries(adapter, msg, true); + break; + case PECI_GET_DIB_CMD: + case PECI_GET_TEMP_CMD: + default: + ret = peci_xfer(adapter, msg); + break; + } + } + + return ret; +} + +static int peci_cmd_ping(struct peci_adapter *adapter, void *vmsg) +{ + struct peci_ping_msg *umsg = vmsg; + struct peci_xfer_msg *msg; + int ret; + + msg = peci_get_xfer_msg(0, 0); + if (!msg) + return -ENOMEM; + + msg->addr = umsg->addr; + + ret = peci_xfer(adapter, msg); + + peci_put_xfer_msg(msg); + + return ret; +} + +static int peci_cmd_get_dib(struct peci_adapter *adapter, void *vmsg) +{ + struct peci_get_dib_msg *umsg = vmsg; + struct peci_xfer_msg *msg; + int ret; + + msg = peci_get_xfer_msg(PECI_GET_DIB_WR_LEN, PECI_GET_DIB_RD_LEN); + if (!msg) + return -ENOMEM; + + msg->addr = umsg->addr; + msg->tx_buf[0] = PECI_GET_DIB_CMD; + + ret = peci_xfer(adapter, msg); + if (ret) + goto out; + + umsg->dib = le64_to_cpup((__le64 *)msg->rx_buf); + +out: + peci_put_xfer_msg(msg); + + return ret; +} + +static int peci_cmd_get_temp(struct peci_adapter *adapter, void *vmsg) +{ + struct peci_get_temp_msg *umsg = vmsg; + struct peci_xfer_msg *msg; + int ret; + + msg = peci_get_xfer_msg(PECI_GET_TEMP_WR_LEN, PECI_GET_TEMP_RD_LEN); + if (!msg) + return -ENOMEM; + + msg->addr = umsg->addr; + msg->tx_buf[0] = PECI_GET_TEMP_CMD; + + ret = peci_xfer(adapter, msg); + if (ret) + goto out; + + umsg->temp_raw = le16_to_cpup((__le16 *)msg->rx_buf); + +out: + peci_put_xfer_msg(msg); + + return ret; +} + +static int peci_cmd_rd_pkg_cfg(struct peci_adapter *adapter, void *vmsg) +{ + struct peci_rd_pkg_cfg_msg *umsg = vmsg; + struct peci_xfer_msg *msg; + int ret; + + /* Per the PECI spec, the read length must be a byte, word, or dword */ + if (umsg->rx_len != 1 && umsg->rx_len != 2 && umsg->rx_len != 4) { + dev_dbg(&adapter->dev, "Invalid read length, rx_len: %d\n", + umsg->rx_len); + return -EINVAL; + } + + msg = peci_get_xfer_msg(PECI_RDPKGCFG_WRITE_LEN, + PECI_RDPKGCFG_READ_LEN_BASE + umsg->rx_len); + if (!msg) + return -ENOMEM; + + msg->addr = umsg->addr; + msg->tx_buf[0] = PECI_RDPKGCFG_CMD; + msg->tx_buf[1] = 0; /* request byte for Host ID | Retry bit */ + /* Host ID is 0 for PECI 3.0 */ + msg->tx_buf[2] = umsg->index; /* RdPkgConfig index */ + msg->tx_buf[3] = (u8)umsg->param; /* LSB - Config parameter */ + msg->tx_buf[4] = (u8)(umsg->param >> 8); /* MSB - Config parameter */ + + ret = peci_xfer_with_retries(adapter, msg, false); + if (!ret) + memcpy(umsg->pkg_config, &msg->rx_buf[1], umsg->rx_len); + + umsg->cc = msg->rx_buf[0]; + peci_put_xfer_msg(msg); + + return ret; +} + +static int peci_cmd_wr_pkg_cfg(struct peci_adapter *adapter, void *vmsg) +{ + struct peci_wr_pkg_cfg_msg *umsg = vmsg; + struct peci_xfer_msg *msg; + int ret, i; + u8 aw_fcs; + + /* Per the PECI spec, the write length must be a dword */ + if (umsg->tx_len != 4) { + dev_dbg(&adapter->dev, "Invalid write length, tx_len: %d\n", + umsg->tx_len); + return -EINVAL; + } + + msg = peci_get_xfer_msg(PECI_WRPKGCFG_WRITE_LEN_BASE + umsg->tx_len, + PECI_WRPKGCFG_READ_LEN); + if (!msg) + return -ENOMEM; + + msg->addr = umsg->addr; + msg->tx_buf[0] = PECI_WRPKGCFG_CMD; + msg->tx_buf[1] = 0; /* request byte for Host ID | Retry bit */ + /* Host ID is 0 for PECI 3.0 */ + msg->tx_buf[2] = umsg->index; /* RdPkgConfig index */ + msg->tx_buf[3] = (u8)umsg->param; /* LSB - Config parameter */ + msg->tx_buf[4] = (u8)(umsg->param >> 8); /* MSB - Config parameter */ + for (i = 0; i < umsg->tx_len; i++) + msg->tx_buf[5 + i] = (u8)(umsg->value >> (i << 3)); + + /* Add an Assured Write Frame Check Sequence byte */ + ret = peci_aw_fcs(msg, 8 + umsg->tx_len, &aw_fcs); + if (ret) + goto out; + + msg->tx_buf[5 + i] = 0x80 ^ aw_fcs; + + ret = peci_xfer_with_retries(adapter, msg, true); + +out: + umsg->cc = msg->rx_buf[0]; + peci_put_xfer_msg(msg); + + return ret; +} + +static int peci_cmd_rd_ia_msr(struct peci_adapter *adapter, void *vmsg) +{ + struct peci_rd_ia_msr_msg *umsg = vmsg; + struct peci_xfer_msg *msg; + int ret; + + msg = peci_get_xfer_msg(PECI_RDIAMSR_WRITE_LEN, PECI_RDIAMSR_READ_LEN); + if (!msg) + return -ENOMEM; + + msg->addr = umsg->addr; + msg->tx_buf[0] = PECI_RDIAMSR_CMD; + msg->tx_buf[1] = 0; + msg->tx_buf[2] = umsg->thread_id; + msg->tx_buf[3] = (u8)umsg->address; + msg->tx_buf[4] = (u8)(umsg->address >> 8); + + ret = peci_xfer_with_retries(adapter, msg, false); + if (!ret) + memcpy(&umsg->value, &msg->rx_buf[1], sizeof(uint64_t)); + + umsg->cc = msg->rx_buf[0]; + peci_put_xfer_msg(msg); + + return ret; +} + +static int peci_cmd_rd_ia_msrex(struct peci_adapter *adapter, void *vmsg) +{ + struct peci_rd_ia_msrex_msg *umsg = vmsg; + struct peci_xfer_msg *msg; + int ret; + + msg = peci_get_xfer_msg(PECI_RDIAMSREX_WRITE_LEN, + PECI_RDIAMSREX_READ_LEN); + if (!msg) + return -ENOMEM; + + msg->addr = umsg->addr; + msg->tx_buf[0] = PECI_RDIAMSREX_CMD; + msg->tx_buf[1] = 0; + msg->tx_buf[2] = (u8)umsg->thread_id; + msg->tx_buf[3] = (u8)(umsg->thread_id >> 8); + msg->tx_buf[4] = (u8)umsg->address; + msg->tx_buf[5] = (u8)(umsg->address >> 8); + + ret = peci_xfer_with_retries(adapter, msg, false); + if (!ret) + memcpy(&umsg->value, &msg->rx_buf[1], sizeof(uint64_t)); + + umsg->cc = msg->rx_buf[0]; + peci_put_xfer_msg(msg); + + return ret; +} + +static int peci_cmd_wr_ia_msr(struct peci_adapter *adapter, void *vmsg) +{ + return -ENOSYS; /* Not implemented yet */ +} + +static int peci_cmd_rd_pci_cfg(struct peci_adapter *adapter, void *vmsg) +{ + struct peci_rd_pci_cfg_msg *umsg = vmsg; + struct peci_xfer_msg *msg; + u32 address; + int ret; + + msg = peci_get_xfer_msg(PECI_RDPCICFG_WRITE_LEN, + PECI_RDPCICFG_READ_LEN); + if (!msg) + return -ENOMEM; + + address = umsg->reg; /* [11:0] - Register */ + address |= (u32)umsg->function << 12; /* [14:12] - Function */ + address |= (u32)umsg->device << 15; /* [19:15] - Device */ + address |= (u32)umsg->bus << 20; /* [27:20] - Bus */ + /* [31:28] - Reserved */ + msg->addr = umsg->addr; + msg->tx_buf[0] = PECI_RDPCICFG_CMD; + msg->tx_buf[1] = 0; /* request byte for Host ID | Retry bit */ + /* Host ID is 0 for PECI 3.0 */ + msg->tx_buf[2] = (u8)address; /* LSB - PCI Config Address */ + msg->tx_buf[3] = (u8)(address >> 8); /* PCI Config Address */ + msg->tx_buf[4] = (u8)(address >> 16); /* PCI Config Address */ + msg->tx_buf[5] = (u8)(address >> 24); /* MSB - PCI Config Address */ + + ret = peci_xfer_with_retries(adapter, msg, false); + if (!ret) + memcpy(umsg->pci_config, &msg->rx_buf[1], 4); + + umsg->cc = msg->rx_buf[0]; + peci_put_xfer_msg(msg); + + return ret; +} + +static int peci_cmd_wr_pci_cfg(struct peci_adapter *adapter, void *vmsg) +{ + return -ENOSYS; /* Not implemented yet */ +} + +static int peci_cmd_rd_pci_cfg_local(struct peci_adapter *adapter, void *vmsg) +{ + struct peci_rd_pci_cfg_local_msg *umsg = vmsg; + struct peci_xfer_msg *msg; + u32 address; + int ret; + + /* Per the PECI spec, the read length must be a byte, word, or dword */ + if (umsg->rx_len != 1 && umsg->rx_len != 2 && umsg->rx_len != 4) { + dev_dbg(&adapter->dev, "Invalid read length, rx_len: %d\n", + umsg->rx_len); + return -EINVAL; + } + + msg = peci_get_xfer_msg(PECI_RDPCICFGLOCAL_WRITE_LEN, + PECI_RDPCICFGLOCAL_READ_LEN_BASE + + umsg->rx_len); + if (!msg) + return -ENOMEM; + + address = umsg->reg; /* [11:0] - Register */ + address |= (u32)umsg->function << 12; /* [14:12] - Function */ + address |= (u32)umsg->device << 15; /* [19:15] - Device */ + address |= (u32)umsg->bus << 20; /* [23:20] - Bus */ + + msg->addr = umsg->addr; + msg->tx_buf[0] = PECI_RDPCICFGLOCAL_CMD; + msg->tx_buf[1] = 0; /* request byte for Host ID | Retry bit */ + /* Host ID is 0 for PECI 3.0 */ + msg->tx_buf[2] = (u8)address; /* LSB - PCI Configuration Address */ + msg->tx_buf[3] = (u8)(address >> 8); /* PCI Configuration Address */ + msg->tx_buf[4] = (u8)(address >> 16); /* PCI Configuration Address */ + + ret = peci_xfer_with_retries(adapter, msg, false); + if (!ret) + memcpy(umsg->pci_config, &msg->rx_buf[1], umsg->rx_len); + + umsg->cc = msg->rx_buf[0]; + peci_put_xfer_msg(msg); + + return ret; +} + +static int peci_cmd_wr_pci_cfg_local(struct peci_adapter *adapter, void *vmsg) +{ + struct peci_wr_pci_cfg_local_msg *umsg = vmsg; + struct peci_xfer_msg *msg; + u32 address; + int ret, i; + u8 aw_fcs; + + /* Per the PECI spec, the write length must be a byte, word, or dword */ + if (umsg->tx_len != 1 && umsg->tx_len != 2 && umsg->tx_len != 4) { + dev_dbg(&adapter->dev, "Invalid write length, tx_len: %d\n", + umsg->tx_len); + return -EINVAL; + } + + msg = peci_get_xfer_msg(PECI_WRPCICFGLOCAL_WRITE_LEN_BASE + + umsg->tx_len, PECI_WRPCICFGLOCAL_READ_LEN); + if (!msg) + return -ENOMEM; + + address = umsg->reg; /* [11:0] - Register */ + address |= (u32)umsg->function << 12; /* [14:12] - Function */ + address |= (u32)umsg->device << 15; /* [19:15] - Device */ + address |= (u32)umsg->bus << 20; /* [23:20] - Bus */ + + msg->addr = umsg->addr; + msg->tx_buf[0] = PECI_WRPCICFGLOCAL_CMD; + msg->tx_buf[1] = 0; /* request byte for Host ID | Retry bit */ + /* Host ID is 0 for PECI 3.0 */ + msg->tx_buf[2] = (u8)address; /* LSB - PCI Configuration Address */ + msg->tx_buf[3] = (u8)(address >> 8); /* PCI Configuration Address */ + msg->tx_buf[4] = (u8)(address >> 16); /* PCI Configuration Address */ + for (i = 0; i < umsg->tx_len; i++) + msg->tx_buf[5 + i] = (u8)(umsg->value >> (i << 3)); + + /* Add an Assured Write Frame Check Sequence byte */ + ret = peci_aw_fcs(msg, 8 + umsg->tx_len, &aw_fcs); + if (ret) + goto out; + + msg->tx_buf[5 + i] = 0x80 ^ aw_fcs; + + ret = peci_xfer_with_retries(adapter, msg, true); + +out: + umsg->cc = msg->rx_buf[0]; + peci_put_xfer_msg(msg); + + return ret; +} + +static int peci_cmd_rd_end_pt_cfg(struct peci_adapter *adapter, void *vmsg) +{ + struct peci_rd_end_pt_cfg_msg *umsg = vmsg; + struct peci_xfer_msg *msg = NULL; + u32 address; + u8 tx_size; + int ret; + + switch (umsg->msg_type) { + case PECI_ENDPTCFG_TYPE_LOCAL_PCI: + case PECI_ENDPTCFG_TYPE_PCI: + /* + * Per the PECI spec, the read length must be a byte, word, + * or dword + */ + if (umsg->rx_len != 1 && umsg->rx_len != 2 && + umsg->rx_len != 4) { + dev_dbg(&adapter->dev, + "Invalid read length, rx_len: %d\n", + umsg->rx_len); + return -EINVAL; + } + + msg = peci_get_xfer_msg(PECI_RDENDPTCFG_PCI_WRITE_LEN, + PECI_RDENDPTCFG_READ_LEN_BASE + + umsg->rx_len); + if (!msg) + return -ENOMEM; + + address = umsg->params.pci_cfg.reg; /* [11:0] - Register */ + address |= (u32)umsg->params.pci_cfg.function + << 12; /* [14:12] - Function */ + address |= (u32)umsg->params.pci_cfg.device + << 15; /* [19:15] - Device */ + address |= (u32)umsg->params.pci_cfg.bus + << 20; /* [27:20] - Bus */ + /* [31:28] - Reserved */ + msg->addr = umsg->addr; + msg->tx_buf[0] = PECI_RDENDPTCFG_CMD; + msg->tx_buf[1] = 0x00; /* request byte for Host ID|Retry bit */ + msg->tx_buf[2] = umsg->msg_type; /* Message Type */ + msg->tx_buf[3] = 0x00; /* Endpoint ID */ + msg->tx_buf[4] = 0x00; /* Reserved */ + msg->tx_buf[5] = 0x00; /* Reserved */ + msg->tx_buf[6] = PECI_ENDPTCFG_ADDR_TYPE_PCI; /* Addr Type */ + msg->tx_buf[7] = umsg->params.pci_cfg.seg; /* PCI Segment */ + msg->tx_buf[8] = (u8)address; /* LSB - PCI Config Address */ + msg->tx_buf[9] = (u8)(address >> 8); /* PCI Config Address */ + msg->tx_buf[10] = (u8)(address >> 16); /* PCI Config Address */ + msg->tx_buf[11] = + (u8)(address >> 24); /* MSB - PCI Config Address */ + break; + + case PECI_ENDPTCFG_TYPE_MMIO: + /* + * Per the PECI spec, the read length must be a byte, word, + * dword, or qword + */ + if (umsg->rx_len != 1 && umsg->rx_len != 2 && + umsg->rx_len != 4 && umsg->rx_len != 8) { + dev_dbg(&adapter->dev, + "Invalid read length, rx_len: %d\n", + umsg->rx_len); + return -EINVAL; + } + /* + * Per the PECI spec, the address type must specify either DWORD + * or QWORD + */ + if (umsg->params.mmio.addr_type != + PECI_ENDPTCFG_ADDR_TYPE_MMIO_D && + umsg->params.mmio.addr_type != + PECI_ENDPTCFG_ADDR_TYPE_MMIO_Q) { + dev_dbg(&adapter->dev, + "Invalid address type, addr_type: %d\n", + umsg->params.mmio.addr_type); + return -EINVAL; + } + + if (umsg->params.mmio.addr_type == + PECI_ENDPTCFG_ADDR_TYPE_MMIO_D) + tx_size = PECI_RDENDPTCFG_MMIO_D_WRITE_LEN; + else + tx_size = PECI_RDENDPTCFG_MMIO_Q_WRITE_LEN; + msg = peci_get_xfer_msg(tx_size, + PECI_RDENDPTCFG_READ_LEN_BASE + + umsg->rx_len); + if (!msg) + return -ENOMEM; + + address = umsg->params.mmio.function; /* [2:0] - Function */ + address |= (u32)umsg->params.mmio.device + << 3; /* [7:3] - Device */ + + msg->addr = umsg->addr; + msg->tx_buf[0] = PECI_RDENDPTCFG_CMD; + msg->tx_buf[1] = 0x00; /* request byte for Host ID|Retry bit */ + msg->tx_buf[2] = umsg->msg_type; /* Message Type */ + msg->tx_buf[3] = 0x00; /* Endpoint ID */ + msg->tx_buf[4] = 0x00; /* Reserved */ + msg->tx_buf[5] = umsg->params.mmio.bar; /* BAR # */ + msg->tx_buf[6] = umsg->params.mmio.addr_type; /* Address Type */ + msg->tx_buf[7] = umsg->params.mmio.seg; /* PCI Segment */ + msg->tx_buf[8] = (u8)address; /* Function/Device */ + msg->tx_buf[9] = umsg->params.mmio.bus; /* PCI Bus */ + msg->tx_buf[10] = (u8)umsg->params.mmio + .offset; /* LSB - Register Offset */ + msg->tx_buf[11] = (u8)(umsg->params.mmio.offset + >> 8); /* Register Offset */ + msg->tx_buf[12] = (u8)(umsg->params.mmio.offset + >> 16); /* Register Offset */ + msg->tx_buf[13] = (u8)(umsg->params.mmio.offset + >> 24); /* MSB - DWORD Register Offset */ + if (umsg->params.mmio.addr_type == + PECI_ENDPTCFG_ADDR_TYPE_MMIO_Q) { + msg->tx_buf[14] = (u8)(umsg->params.mmio.offset + >> 32); /* Register Offset */ + msg->tx_buf[15] = (u8)(umsg->params.mmio.offset + >> 40); /* Register Offset */ + msg->tx_buf[16] = (u8)(umsg->params.mmio.offset + >> 48); /* Register Offset */ + msg->tx_buf[17] = + (u8)(umsg->params.mmio.offset + >> 56); /* MSB - QWORD Register Offset */ + } + break; + + default: + return -EINVAL; + } + + ret = peci_xfer_with_retries(adapter, msg, false); + if (!ret) + memcpy(umsg->data, &msg->rx_buf[1], umsg->rx_len); + + umsg->cc = msg->rx_buf[0]; + peci_put_xfer_msg(msg); + + return ret; +} + +static int peci_cmd_wr_end_pt_cfg(struct peci_adapter *adapter, void *vmsg) +{ + struct peci_wr_end_pt_cfg_msg *umsg = vmsg; + struct peci_xfer_msg *msg = NULL; + u8 tx_size, aw_fcs; + int ret, i, idx; + u32 address; + + switch (umsg->msg_type) { + case PECI_ENDPTCFG_TYPE_LOCAL_PCI: + case PECI_ENDPTCFG_TYPE_PCI: + /* + * Per the PECI spec, the write length must be a byte, word, + * or dword + */ + if (umsg->tx_len != 1 && umsg->tx_len != 2 && + umsg->tx_len != 4) { + dev_dbg(&adapter->dev, + "Invalid write length, tx_len: %d\n", + umsg->tx_len); + return -EINVAL; + } + + msg = peci_get_xfer_msg(PECI_WRENDPTCFG_PCI_WRITE_LEN_BASE + + umsg->tx_len, PECI_WRENDPTCFG_READ_LEN); + if (!msg) + return -ENOMEM; + + address = umsg->params.pci_cfg.reg; /* [11:0] - Register */ + address |= (u32)umsg->params.pci_cfg.function + << 12; /* [14:12] - Function */ + address |= (u32)umsg->params.pci_cfg.device + << 15; /* [19:15] - Device */ + address |= (u32)umsg->params.pci_cfg.bus + << 20; /* [27:20] - Bus */ + /* [31:28] - Reserved */ + msg->addr = umsg->addr; + msg->tx_buf[0] = PECI_WRENDPTCFG_CMD; + msg->tx_buf[1] = 0x00; /* request byte for Host ID|Retry bit */ + msg->tx_buf[2] = umsg->msg_type; /* Message Type */ + msg->tx_buf[3] = 0x00; /* Endpoint ID */ + msg->tx_buf[4] = 0x00; /* Reserved */ + msg->tx_buf[5] = 0x00; /* Reserved */ + msg->tx_buf[6] = PECI_ENDPTCFG_ADDR_TYPE_PCI; /* Addr Type */ + msg->tx_buf[7] = umsg->params.pci_cfg.seg; /* PCI Segment */ + msg->tx_buf[8] = (u8)address; /* LSB - PCI Config Address */ + msg->tx_buf[9] = (u8)(address >> 8); /* PCI Config Address */ + msg->tx_buf[10] = (u8)(address >> 16); /* PCI Config Address */ + msg->tx_buf[11] = + (u8)(address >> 24); /* MSB - PCI Config Address */ + for (i = 0; i < umsg->tx_len; i++) + msg->tx_buf[12 + i] = (u8)(umsg->value >> (i << 3)); + + /* Add an Assured Write Frame Check Sequence byte */ + ret = peci_aw_fcs(msg, 15 + umsg->tx_len, &aw_fcs); + if (ret) + goto out; + + msg->tx_buf[12 + i] = 0x80 ^ aw_fcs; + break; + + case PECI_ENDPTCFG_TYPE_MMIO: + /* + * Per the PECI spec, the write length must be a byte, word, + * dword, or qword + */ + if (umsg->tx_len != 1 && umsg->tx_len != 2 && + umsg->tx_len != 4 && umsg->tx_len != 8) { + dev_dbg(&adapter->dev, + "Invalid write length, tx_len: %d\n", + umsg->tx_len); + return -EINVAL; + } + /* + * Per the PECI spec, the address type must specify either DWORD + * or QWORD + */ + if (umsg->params.mmio.addr_type != + PECI_ENDPTCFG_ADDR_TYPE_MMIO_D && + umsg->params.mmio.addr_type != + PECI_ENDPTCFG_ADDR_TYPE_MMIO_Q) { + dev_dbg(&adapter->dev, + "Invalid address type, addr_type: %d\n", + umsg->params.mmio.addr_type); + return -EINVAL; + } + + if (umsg->params.mmio.addr_type == + PECI_ENDPTCFG_ADDR_TYPE_MMIO_D) + tx_size = PECI_WRENDPTCFG_MMIO_D_WRITE_LEN_BASE + + umsg->tx_len; + else + tx_size = PECI_WRENDPTCFG_MMIO_Q_WRITE_LEN_BASE + + umsg->tx_len; + msg = peci_get_xfer_msg(tx_size, PECI_WRENDPTCFG_READ_LEN); + if (!msg) + return -ENOMEM; + + address = umsg->params.mmio.function; /* [2:0] - Function */ + address |= (u32)umsg->params.mmio.device + << 3; /* [7:3] - Device */ + + msg->addr = umsg->addr; + msg->tx_buf[0] = PECI_WRENDPTCFG_CMD; + msg->tx_buf[1] = 0x00; /* request byte for Host ID|Retry bit */ + msg->tx_buf[2] = umsg->msg_type; /* Message Type */ + msg->tx_buf[3] = 0x00; /* Endpoint ID */ + msg->tx_buf[4] = 0x00; /* Reserved */ + msg->tx_buf[5] = umsg->params.mmio.bar; /* BAR # */ + msg->tx_buf[6] = umsg->params.mmio.addr_type; /* Address Type */ + msg->tx_buf[7] = umsg->params.mmio.seg; /* PCI Segment */ + msg->tx_buf[8] = (u8)address; /* Function/Device */ + msg->tx_buf[9] = umsg->params.mmio.bus; /* PCI Bus */ + msg->tx_buf[10] = (u8)umsg->params.mmio + .offset; /* LSB - Register Offset */ + msg->tx_buf[11] = (u8)(umsg->params.mmio.offset + >> 8); /* Register Offset */ + msg->tx_buf[12] = (u8)(umsg->params.mmio.offset + >> 16); /* Register Offset */ + msg->tx_buf[13] = (u8)(umsg->params.mmio.offset + >> 24); /* MSB - DWORD Register Offset */ + if (umsg->params.mmio.addr_type == + PECI_ENDPTCFG_ADDR_TYPE_MMIO_Q) { + msg->tx_len = PECI_WRENDPTCFG_MMIO_Q_WRITE_LEN_BASE; + msg->tx_buf[14] = (u8)(umsg->params.mmio.offset + >> 32); /* Register Offset */ + msg->tx_buf[15] = (u8)(umsg->params.mmio.offset + >> 40); /* Register Offset */ + msg->tx_buf[16] = (u8)(umsg->params.mmio.offset + >> 48); /* Register Offset */ + msg->tx_buf[17] = + (u8)(umsg->params.mmio.offset + >> 56); /* MSB - QWORD Register Offset */ + idx = 18; + } else { + idx = 14; + } + for (i = 0; i < umsg->tx_len; i++) + msg->tx_buf[idx + i] = (u8)(umsg->value >> (i << 3)); + + /* Add an Assured Write Frame Check Sequence byte */ + ret = peci_aw_fcs(msg, idx + 3 + umsg->tx_len, &aw_fcs); + if (ret) + goto out; + + msg->tx_buf[idx + i] = 0x80 ^ aw_fcs; + break; + + default: + return -EINVAL; + } + + ret = peci_xfer_with_retries(adapter, msg, false); + +out: + umsg->cc = msg->rx_buf[0]; + peci_put_xfer_msg(msg); + + return ret; +} + +static int peci_cmd_crashdump_disc(struct peci_adapter *adapter, void *vmsg) +{ + struct peci_crashdump_disc_msg *umsg = vmsg; + struct peci_xfer_msg *msg; + int ret; + + /* Per the EDS, the read length must be a byte, word, or qword */ + if (umsg->rx_len != 1 && umsg->rx_len != 2 && umsg->rx_len != 8) { + dev_dbg(&adapter->dev, "Invalid read length, rx_len: %d\n", + umsg->rx_len); + return -EINVAL; + } + + msg = peci_get_xfer_msg(PECI_CRASHDUMP_DISC_WRITE_LEN, + PECI_CRASHDUMP_DISC_READ_LEN_BASE + + umsg->rx_len); + if (!msg) + return -ENOMEM; + + msg->addr = umsg->addr; + msg->tx_buf[0] = PECI_CRASHDUMP_CMD; + msg->tx_buf[1] = 0x00; /* request byte for Host ID | Retry bit */ + /* Host ID is 0 for PECI 3.0 */ + msg->tx_buf[2] = PECI_CRASHDUMP_DISC_VERSION; + msg->tx_buf[3] = PECI_CRASHDUMP_DISC_OPCODE; + msg->tx_buf[4] = umsg->subopcode; + msg->tx_buf[5] = umsg->param0; + msg->tx_buf[6] = (u8)umsg->param1; + msg->tx_buf[7] = (u8)(umsg->param1 >> 8); + msg->tx_buf[8] = umsg->param2; + + ret = peci_xfer_with_retries(adapter, msg, false); + if (!ret) + memcpy(umsg->data, &msg->rx_buf[1], umsg->rx_len); + + umsg->cc = msg->rx_buf[0]; + peci_put_xfer_msg(msg); + + return ret; +} + +static int peci_cmd_crashdump_get_frame(struct peci_adapter *adapter, + void *vmsg) +{ + struct peci_crashdump_get_frame_msg *umsg = vmsg; + struct peci_xfer_msg *msg; + int ret; + + /* Per the EDS, the read length must be a qword or dqword */ + if (umsg->rx_len != 8 && umsg->rx_len != 16) { + dev_dbg(&adapter->dev, "Invalid read length, rx_len: %d\n", + umsg->rx_len); + return -EINVAL; + } + + msg = peci_get_xfer_msg(PECI_CRASHDUMP_GET_FRAME_WRITE_LEN, + PECI_CRASHDUMP_GET_FRAME_READ_LEN_BASE + + umsg->rx_len); + if (!msg) + return -ENOMEM; + + msg->addr = umsg->addr; + msg->tx_buf[0] = PECI_CRASHDUMP_CMD; + msg->tx_buf[1] = 0x00; /* request byte for Host ID | Retry bit */ + /* Host ID is 0 for PECI 3.0 */ + msg->tx_buf[2] = PECI_CRASHDUMP_GET_FRAME_VERSION; + msg->tx_buf[3] = PECI_CRASHDUMP_GET_FRAME_OPCODE; + msg->tx_buf[4] = (u8)umsg->param0; + msg->tx_buf[5] = (u8)(umsg->param0 >> 8); + msg->tx_buf[6] = (u8)umsg->param1; + msg->tx_buf[7] = (u8)(umsg->param1 >> 8); + msg->tx_buf[8] = (u8)umsg->param2; + msg->tx_buf[9] = (u8)(umsg->param2 >> 8); + + ret = peci_xfer_with_retries(adapter, msg, false); + if (!ret) + memcpy(umsg->data, &msg->rx_buf[1], umsg->rx_len); + + umsg->cc = msg->rx_buf[0]; + peci_put_xfer_msg(msg); + + return ret; +} + +typedef int (*peci_cmd_fn_type)(struct peci_adapter *, void *); + +static const peci_cmd_fn_type peci_cmd_fn[PECI_CMD_MAX] = { + peci_cmd_xfer, + peci_cmd_ping, + peci_cmd_get_dib, + peci_cmd_get_temp, + peci_cmd_rd_pkg_cfg, + peci_cmd_wr_pkg_cfg, + peci_cmd_rd_ia_msr, + peci_cmd_wr_ia_msr, + peci_cmd_rd_ia_msrex, + peci_cmd_rd_pci_cfg, + peci_cmd_wr_pci_cfg, + peci_cmd_rd_pci_cfg_local, + peci_cmd_wr_pci_cfg_local, + peci_cmd_rd_end_pt_cfg, + peci_cmd_wr_end_pt_cfg, + peci_cmd_crashdump_disc, + peci_cmd_crashdump_get_frame, +}; + +/** + * peci_command - transfer function of a PECI command + * @adapter: pointer to peci_adapter + * @vmsg: pointer to PECI messages + * Context: can sleep + * + * This performs a transfer of a PECI command using PECI messages parameter + * which has various formats on each command. + * + * Return: zero on success, else a negative error code. + */ +int peci_command(struct peci_adapter *adapter, enum peci_cmd cmd, void *vmsg) +{ + int ret; + + if (cmd >= PECI_CMD_MAX || cmd < PECI_CMD_XFER) + return -ENOTTY; + + dev_dbg(&adapter->dev, "%s, cmd=0x%02x\n", __func__, cmd); + + if (!peci_cmd_fn[cmd]) + return -EINVAL; + + mutex_lock(&adapter->bus_lock); + + ret = peci_check_cmd_support(adapter, cmd); + if (!ret) + ret = peci_cmd_fn[cmd](adapter, vmsg); + + mutex_unlock(&adapter->bus_lock); + + return ret; +} +EXPORT_SYMBOL_GPL(peci_command); + +static int peci_detect(struct peci_adapter *adapter, u8 addr) +{ + struct peci_ping_msg msg; + + msg.addr = addr; + + return peci_command(adapter, PECI_CMD_PING, &msg); +} + +static const struct of_device_id * +peci_of_match_device(const struct of_device_id *matches, + struct peci_client *client) +{ +#if IS_ENABLED(CONFIG_OF) + if (!(client && matches)) + return NULL; + + return of_match_device(matches, &client->dev); +#else /* CONFIG_OF */ + return NULL; +#endif /* CONFIG_OF */ +} + +static const struct peci_device_id * +peci_match_id(const struct peci_device_id *id, struct peci_client *client) +{ + if (!(id && client)) + return NULL; + + while (id->name[0]) { + if (!strncmp(client->name, id->name, PECI_NAME_SIZE)) + return id; + id++; + } + + return NULL; +} + +static int peci_device_match(struct device *dev, struct device_driver *drv) +{ + struct peci_client *client = peci_verify_client(dev); + struct peci_driver *driver; + + /* Attempt an OF style match */ + if (peci_of_match_device(drv->of_match_table, client)) + return 1; + + driver = to_peci_driver(drv); + + /* Finally an ID match */ + if (peci_match_id(driver->id_table, client)) + return 1; + + return 0; +} + +static int peci_device_probe(struct device *dev) +{ + struct peci_client *client = peci_verify_client(dev); + struct peci_driver *driver; + int status = -EINVAL; + + if (!client) + return 0; + + driver = to_peci_driver(dev->driver); + + if (!driver->id_table && + !peci_of_match_device(dev->driver->of_match_table, client)) + return -ENODEV; + + dev_dbg(dev, "%s: name:%s\n", __func__, client->name); + + status = dev_pm_domain_attach(&client->dev, true); + if (status == -EPROBE_DEFER) + return status; + + if (driver->probe) + status = driver->probe(client); + else + status = -EINVAL; + + if (status) + goto err_detach_pm_domain; + + return 0; + +err_detach_pm_domain: + dev_pm_domain_detach(&client->dev, true); + + return status; +} + +static int peci_device_remove(struct device *dev) +{ + struct peci_client *client = peci_verify_client(dev); + struct peci_driver *driver; + int status = 0; + + if (!client || !dev->driver) + return 0; + + driver = to_peci_driver(dev->driver); + if (driver->remove) { + dev_dbg(dev, "%s: name:%s\n", __func__, client->name); + status = driver->remove(client); + } + + dev_pm_domain_detach(&client->dev, true); + + return status; +} + +static void peci_device_shutdown(struct device *dev) +{ + struct peci_client *client = peci_verify_client(dev); + struct peci_driver *driver; + + if (!client || !dev->driver) + return; + + dev_dbg(dev, "%s: name:%s\n", __func__, client->name); + + driver = to_peci_driver(dev->driver); + if (driver->shutdown) + driver->shutdown(client); +} + +struct bus_type peci_bus_type = { + .name = "peci", + .match = peci_device_match, + .probe = peci_device_probe, + .remove = peci_device_remove, + .shutdown = peci_device_shutdown, +}; +EXPORT_SYMBOL_GPL(peci_bus_type); + +static int peci_check_addr_validity(u8 addr) +{ + if (addr < PECI_BASE_ADDR && addr > PECI_BASE_ADDR + PECI_OFFSET_MAX) + return -EINVAL; + + return 0; +} + +static int peci_check_client_busy(struct device *dev, void *client_new_p) +{ + struct peci_client *client = peci_verify_client(dev); + struct peci_client *client_new = client_new_p; + + if (client && client->addr == client_new->addr) + return -EBUSY; + + return 0; +} + +/** + * peci_get_cpu_id - read CPU ID from the Package Configuration Space of CPU + * @adapter: pointer to peci_adapter + * @addr: address of the PECI client CPU + * @cpu_id: where the CPU ID will be stored + * Context: can sleep + * + * Return: zero on success, else a negative error code. + */ +int peci_get_cpu_id(struct peci_adapter *adapter, u8 addr, u32 *cpu_id) +{ + struct peci_rd_pkg_cfg_msg msg; + int ret; + + msg.addr = addr; + msg.index = PECI_MBX_INDEX_CPU_ID; + msg.param = PECI_PKG_ID_CPU_ID; + msg.rx_len = 4; + + ret = peci_command(adapter, PECI_CMD_RD_PKG_CFG, &msg); + if (msg.cc != PECI_DEV_CC_SUCCESS) + ret = -EAGAIN; + if (ret) + return ret; + + *cpu_id = le32_to_cpup((__le32 *)msg.pkg_config); + + return 0; +} +EXPORT_SYMBOL_GPL(peci_get_cpu_id); + +static struct peci_client *peci_new_device(struct peci_adapter *adapter, + struct peci_board_info const *info) +{ + struct peci_client *client; + int ret; + + /* Increase reference count for the adapter assigned */ + if (!peci_get_adapter(adapter->nr)) + return NULL; + + client = kzalloc(sizeof(*client), GFP_KERNEL); + if (!client) + goto err_put_adapter; + + client->adapter = adapter; + client->addr = info->addr; + strlcpy(client->name, info->type, sizeof(client->name)); + + ret = peci_check_addr_validity(client->addr); + if (ret) { + dev_err(&adapter->dev, "Invalid PECI CPU address 0x%02hx\n", + client->addr); + goto err_free_client_silent; + } + + /* Check online status of client */ + ret = peci_detect(adapter, client->addr); + if (ret) + goto err_free_client; + + ret = device_for_each_child(&adapter->dev, client, + peci_check_client_busy); + if (ret) + goto err_free_client; + + client->dev.parent = &client->adapter->dev; + client->dev.bus = &peci_bus_type; + client->dev.type = &peci_client_type; + client->dev.of_node = of_node_get(info->of_node); + dev_set_name(&client->dev, "%d-%02x", adapter->nr, client->addr); + + ret = device_register(&client->dev); + if (ret) + goto err_put_of_node; + + dev_dbg(&adapter->dev, "client [%s] registered with bus id %s\n", + client->name, dev_name(&client->dev)); + + return client; + +err_put_of_node: + of_node_put(info->of_node); +err_free_client: + dev_err(&adapter->dev, + "Failed to register peci client %s at 0x%02x (%d)\n", + client->name, client->addr, ret); +err_free_client_silent: + kfree(client); +err_put_adapter: + peci_put_adapter(adapter); + + return NULL; +} + +static void peci_unregister_device(struct peci_client *client) +{ + if (!client) + return; + + if (client->dev.of_node) { + of_node_clear_flag(client->dev.of_node, OF_POPULATED); + of_node_put(client->dev.of_node); + } + + device_unregister(&client->dev); +} + +static int peci_unregister_client(struct device *dev, void *dummy) +{ + struct peci_client *client = peci_verify_client(dev); + + peci_unregister_device(client); + + return 0; +} + +static void peci_adapter_dev_release(struct device *dev) +{ + struct peci_adapter *adapter = to_peci_adapter(dev); + + dev_dbg(dev, "%s: %s\n", __func__, adapter->name); + mutex_destroy(&adapter->userspace_clients_lock); + mutex_destroy(&adapter->bus_lock); + kfree(adapter); +} + +static ssize_t peci_sysfs_new_device(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct peci_adapter *adapter = to_peci_adapter(dev); + struct peci_board_info info = {}; + struct peci_client *client; + char *blank, end; + short addr; + int ret; + + /* Parse device type */ + blank = strchr(buf, ' '); + if (!blank) { + dev_err(dev, "%s: Missing parameters\n", "new_device"); + return -EINVAL; + } + if (blank - buf > PECI_NAME_SIZE - 1) { + dev_err(dev, "%s: Invalid device type\n", "new_device"); + return -EINVAL; + } + memcpy(info.type, buf, blank - buf); + + /* Parse remaining parameters, reject extra parameters */ + ret = sscanf(++blank, "%hi%c", &addr, &end); + if (ret < 1) { + dev_err(dev, "%s: Can't parse client address\n", "new_device"); + return -EINVAL; + } + if (ret > 1 && end != '\n') { + dev_err(dev, "%s: Extra parameters\n", "new_device"); + return -EINVAL; + } + + info.addr = (u8)addr; + client = peci_new_device(adapter, &info); + if (!client) + return -EINVAL; + + /* Keep track of the added device */ + mutex_lock(&adapter->userspace_clients_lock); + list_add_tail(&client->detected, &adapter->userspace_clients); + mutex_unlock(&adapter->userspace_clients_lock); + dev_dbg(dev, "%s: Instantiated device %s at 0x%02hx\n", "new_device", + info.type, info.addr); + + return count; +} +static DEVICE_ATTR(new_device, 0200, NULL, peci_sysfs_new_device); + +static ssize_t peci_sysfs_delete_device(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct peci_adapter *adapter = to_peci_adapter(dev); + struct peci_client *client, *next; + struct peci_board_info info = {}; + char *blank, end; + short addr; + int ret; + + /* Parse device type */ + blank = strchr(buf, ' '); + if (!blank) { + dev_err(dev, "%s: Missing parameters\n", "delete_device"); + return -EINVAL; + } + if (blank - buf > PECI_NAME_SIZE - 1) { + dev_err(dev, "%s: Invalid device type\n", "delete_device"); + return -EINVAL; + } + memcpy(info.type, buf, blank - buf); + + /* Parse remaining parameters, reject extra parameters */ + ret = sscanf(++blank, "%hi%c", &addr, &end); + if (ret < 1) { + dev_err(dev, "%s: Can't parse client address\n", + "delete_device"); + return -EINVAL; + } + if (ret > 1 && end != '\n') { + dev_err(dev, "%s: Extra parameters\n", "delete_device"); + return -EINVAL; + } + + info.addr = (u8)addr; + + /* Make sure the device was added through sysfs */ + ret = -ENOENT; + mutex_lock(&adapter->userspace_clients_lock); + list_for_each_entry_safe(client, next, &adapter->userspace_clients, + detected) { + if (client->addr == info.addr && + !strncmp(client->name, info.type, PECI_NAME_SIZE)) { + dev_dbg(dev, "%s: Deleting device %s at 0x%02hx\n", + "delete_device", client->name, client->addr); + list_del(&client->detected); + peci_unregister_device(client); + ret = count; + break; + } + } + mutex_unlock(&adapter->userspace_clients_lock); + + if (ret < 0) + dev_dbg(dev, "%s: Can't find device in list\n", + "delete_device"); + + return ret; +} +static DEVICE_ATTR_IGNORE_LOCKDEP(delete_device, 0200, NULL, + peci_sysfs_delete_device); + +static struct attribute *peci_adapter_attrs[] = { + &dev_attr_name.attr, + &dev_attr_new_device.attr, + &dev_attr_delete_device.attr, + NULL +}; +ATTRIBUTE_GROUPS(peci_adapter); + +struct device_type peci_adapter_type = { + .groups = peci_adapter_groups, + .release = peci_adapter_dev_release, +}; +EXPORT_SYMBOL_GPL(peci_adapter_type); + +/** + * peci_verify_adapter - return parameter as peci_adapter, or NULL + * @dev: device, probably from some driver model iterator + * + * Return: pointer to peci_adapter on success, else NULL. + */ +struct peci_adapter *peci_verify_adapter(struct device *dev) +{ + return (dev->type == &peci_adapter_type) + ? to_peci_adapter(dev) + : NULL; +} +EXPORT_SYMBOL_GPL(peci_verify_adapter); + +#if IS_ENABLED(CONFIG_OF) +static struct peci_client *peci_of_register_device(struct peci_adapter *adapter, + struct device_node *node) +{ + struct peci_board_info info = {}; + struct peci_client *client; + u32 addr; + int ret; + + dev_dbg(&adapter->dev, "register %pOF\n", node); + + ret = of_property_read_u32(node, "reg", &addr); + if (ret) { + dev_err(&adapter->dev, "invalid reg on %pOF\n", node); + return ERR_PTR(ret); + } + + info.addr = addr; + info.of_node = node; + + client = peci_new_device(adapter, &info); + if (!client) + client = ERR_PTR(-EINVAL); + + return client; +} + +static void peci_of_register_devices(struct peci_adapter *adapter) +{ + struct device_node *bus, *node; + struct peci_client *client; + + /* Only register child devices if the adapter has a node pointer set */ + if (!adapter->dev.of_node) + return; + + bus = of_get_child_by_name(adapter->dev.of_node, "peci-bus"); + if (!bus) + bus = of_node_get(adapter->dev.of_node); + + for_each_available_child_of_node(bus, node) { + if (of_node_test_and_set_flag(node, OF_POPULATED)) + continue; + + client = peci_of_register_device(adapter, node); + if (IS_ERR(client)) { + dev_warn(&adapter->dev, + "Failed to create PECI device for %pOF\n", + node); + of_node_clear_flag(node, OF_POPULATED); + } + } + + of_node_put(bus); +} +#else /* CONFIG_OF */ +static void peci_of_register_devices(struct peci_adapter *adapter) { } +#endif /* CONFIG_OF */ + +#if IS_ENABLED(CONFIG_OF_DYNAMIC) +static int peci_of_match_node(struct device *dev, const void *data) +{ + return dev->of_node == data; +} + +/* must call put_device() when done with returned peci_client device */ +static struct peci_client *peci_of_find_device(struct device_node *node) +{ + struct peci_client *client; + struct device *dev; + + dev = bus_find_device(&peci_bus_type, NULL, node, peci_of_match_node); + if (!dev) + return NULL; + + client = peci_verify_client(dev); + if (!client) + put_device(dev); + + return client; +} + +/* must call put_device() when done with returned peci_adapter device */ +static struct peci_adapter *peci_of_find_adapter(struct device_node *node) +{ + struct peci_adapter *adapter; + struct device *dev; + + dev = bus_find_device(&peci_bus_type, NULL, node, peci_of_match_node); + if (!dev) + return NULL; + + adapter = peci_verify_adapter(dev); + if (!adapter) + put_device(dev); + + return adapter; +} + +static int peci_of_notify(struct notifier_block *nb, ulong action, void *arg) +{ + struct of_reconfig_data *rd = arg; + struct peci_adapter *adapter; + struct peci_client *client; + + switch (of_reconfig_get_state_change(action, rd)) { + case OF_RECONFIG_CHANGE_ADD: + adapter = peci_of_find_adapter(rd->dn->parent); + if (!adapter) + return NOTIFY_OK; /* not for us */ + + if (of_node_test_and_set_flag(rd->dn, OF_POPULATED)) { + put_device(&adapter->dev); + return NOTIFY_OK; + } + + client = peci_of_register_device(adapter, rd->dn); + put_device(&adapter->dev); + + if (IS_ERR(client)) { + dev_err(&adapter->dev, + "failed to create client for '%pOF'\n", rd->dn); + of_node_clear_flag(rd->dn, OF_POPULATED); + return notifier_from_errno(PTR_ERR(client)); + } + break; + case OF_RECONFIG_CHANGE_REMOVE: + /* already depopulated? */ + if (!of_node_check_flag(rd->dn, OF_POPULATED)) + return NOTIFY_OK; + + /* find our device by node */ + client = peci_of_find_device(rd->dn); + if (!client) + return NOTIFY_OK; /* no? not meant for us */ + + /* unregister takes one ref away */ + peci_unregister_device(client); + + /* and put the reference of the find */ + put_device(&client->dev); + break; + } + + return NOTIFY_OK; +} + +static struct notifier_block peci_of_notifier = { + .notifier_call = peci_of_notify, +}; +#else /* CONFIG_OF_DYNAMIC */ +extern struct notifier_block peci_of_notifier; +#endif /* CONFIG_OF_DYNAMIC */ + +/** + * peci_alloc_adapter - allocate a PECI adapter + * @dev: the adapter, possibly using the platform_bus + * @size: how much zeroed driver-private data to allocate; the pointer to this + * memory is in the driver_data field of the returned device, + * accessible with peci_get_adapdata(). + * Context: can sleep + * + * This call is used only by PECI adapter drivers, which are the only ones + * directly touching chip registers. It's how they allocate a peci_adapter + * structure, prior to calling peci_add_adapter(). + * + * This must be called from context that can sleep. + * + * The caller is responsible for initializing the adapter's methods before + * calling peci_add_adapter(); and (after errors while adding the device) + * calling put_device() to prevent a memory leak. + * + * Return: the peci_adapter structure on success, else NULL. + */ +struct peci_adapter *peci_alloc_adapter(struct device *dev, uint size) +{ + struct peci_adapter *adapter; + + if (!dev) + return NULL; + + adapter = kzalloc(size + sizeof(*adapter), GFP_KERNEL); + if (!adapter) + return NULL; + + device_initialize(&adapter->dev); + adapter->dev.parent = dev; + adapter->dev.bus = &peci_bus_type; + adapter->dev.type = &peci_adapter_type; + peci_set_adapdata(adapter, &adapter[1]); + + return adapter; +} +EXPORT_SYMBOL_GPL(peci_alloc_adapter); + +static int peci_register_adapter(struct peci_adapter *adapter) +{ + int ret = -EINVAL; + + /* Can't register until after driver model init */ + if (WARN_ON(!is_registered)) + goto err_free_idr; + + if (WARN(!adapter->name[0], "peci adapter has no name")) + goto err_free_idr; + + if (WARN(!adapter->xfer, "peci adapter has no xfer function\n")) + goto err_free_idr; + + mutex_init(&adapter->bus_lock); + mutex_init(&adapter->userspace_clients_lock); + INIT_LIST_HEAD(&adapter->userspace_clients); + + dev_set_name(&adapter->dev, "peci-%d", adapter->nr); + + ret = device_add(&adapter->dev); + if (ret) { + pr_err("adapter '%s': can't add device (%d)\n", + adapter->name, ret); + goto err_free_idr; + } + + dev_dbg(&adapter->dev, "adapter [%s] registered\n", adapter->name); + + pm_runtime_no_callbacks(&adapter->dev); + pm_suspend_ignore_children(&adapter->dev, true); + pm_runtime_enable(&adapter->dev); + + /* create pre-declared device nodes */ + peci_of_register_devices(adapter); + + return 0; + +err_free_idr: + mutex_lock(&core_lock); + idr_remove(&peci_adapter_idr, adapter->nr); + mutex_unlock(&core_lock); + return ret; +} + +static int peci_add_numbered_adapter(struct peci_adapter *adapter) +{ + int id; + + mutex_lock(&core_lock); + id = idr_alloc(&peci_adapter_idr, adapter, + adapter->nr, adapter->nr + 1, GFP_KERNEL); + mutex_unlock(&core_lock); + if (WARN(id < 0, "couldn't get idr")) + return id == -ENOSPC ? -EBUSY : id; + + return peci_register_adapter(adapter); +} + +/** + * peci_add_adapter - add a PECI adapter + * @adapter: initialized adapter, originally from peci_alloc_adapter() + * Context: can sleep + * + * PECI adapters connect to their drivers using some non-PECI bus, + * such as the platform bus. The final stage of probe() in that code + * includes calling peci_add_adapter() to hook up to this PECI bus glue. + * + * This must be called from context that can sleep. + * + * It returns zero on success, else a negative error code (dropping the + * adapter's refcount). After a successful return, the caller is responsible + * for calling peci_del_adapter(). + * + * Return: zero on success, else a negative error code. + */ +int peci_add_adapter(struct peci_adapter *adapter) +{ + struct device *dev = &adapter->dev; + int id; + + id = of_alias_get_id(dev->of_node, "peci"); + if (id >= 0) { + adapter->nr = id; + return peci_add_numbered_adapter(adapter); + } + + mutex_lock(&core_lock); + id = idr_alloc(&peci_adapter_idr, adapter, 0, 0, GFP_KERNEL); + mutex_unlock(&core_lock); + if (WARN(id < 0, "couldn't get idr")) + return id; + + adapter->nr = id; + + return peci_register_adapter(adapter); +} +EXPORT_SYMBOL_GPL(peci_add_adapter); + +/** + * peci_del_adapter - delete a PECI adapter + * @adapter: the adpater being deleted + * Context: can sleep + * + * This call is used only by PECI adpater drivers, which are the only ones + * directly touching chip registers. + * + * This must be called from context that can sleep. + * + * Note that this function also drops a reference to the adapter. + */ +void peci_del_adapter(struct peci_adapter *adapter) +{ + struct peci_client *client, *next; + struct peci_adapter *found; + int nr; + + /* First make sure that this adapter was ever added */ + mutex_lock(&core_lock); + found = idr_find(&peci_adapter_idr, adapter->nr); + mutex_unlock(&core_lock); + + if (found != adapter) + return; + + /* Remove devices instantiated from sysfs */ + mutex_lock(&adapter->userspace_clients_lock); + list_for_each_entry_safe(client, next, &adapter->userspace_clients, + detected) { + dev_dbg(&adapter->dev, "Removing %s at 0x%x\n", client->name, + client->addr); + list_del(&client->detected); + peci_unregister_device(client); + } + mutex_unlock(&adapter->userspace_clients_lock); + + /* + * Detach any active clients. This can't fail, thus we do not + * check the returned value. + */ + device_for_each_child(&adapter->dev, NULL, peci_unregister_client); + + /* device name is gone after device_unregister */ + dev_dbg(&adapter->dev, "adapter [%s] unregistered\n", adapter->name); + + pm_runtime_disable(&adapter->dev); + nr = adapter->nr; + device_unregister(&adapter->dev); + + /* free bus id */ + mutex_lock(&core_lock); + idr_remove(&peci_adapter_idr, nr); + mutex_unlock(&core_lock); +} +EXPORT_SYMBOL_GPL(peci_del_adapter); + +int peci_for_each_dev(void *data, int (*fn)(struct device *, void *)) +{ + int ret; + + mutex_lock(&core_lock); + ret = bus_for_each_dev(&peci_bus_type, NULL, data, fn); + mutex_unlock(&core_lock); + + return ret; +} +EXPORT_SYMBOL_GPL(peci_for_each_dev); + +/** + * peci_register_driver - register a PECI driver + * @owner: owner module of the driver being registered + * @driver: the driver being registered + * Context: can sleep + * + * Return: zero on success, else a negative error code. + */ +int peci_register_driver(struct module *owner, struct peci_driver *driver) +{ + int ret; + + /* Can't register until after driver model init */ + if (WARN_ON(!is_registered)) + return -EAGAIN; + + /* add the driver to the list of peci drivers in the driver core */ + driver->driver.owner = owner; + driver->driver.bus = &peci_bus_type; + + /* + * When registration returns, the driver core + * will have called probe() for all matching-but-unbound devices. + */ + ret = driver_register(&driver->driver); + if (ret) + return ret; + + pr_debug("driver [%s] registered\n", driver->driver.name); + + return 0; +} +EXPORT_SYMBOL_GPL(peci_register_driver); + +/** + * peci_del_driver - unregister a PECI driver + * @driver: the driver being unregistered + * Context: can sleep + */ +void peci_del_driver(struct peci_driver *driver) +{ + driver_unregister(&driver->driver); + pr_debug("driver [%s] unregistered\n", driver->driver.name); +} +EXPORT_SYMBOL_GPL(peci_del_driver); + +static int __init peci_init(void) +{ + int ret; + + ret = bus_register(&peci_bus_type); + if (ret < 0) { + pr_err("peci: Failed to register PECI bus type!\n"); + return ret; + } + + crc8_populate_msb(peci_crc8_table, PECI_CRC8_POLYNOMIAL); + + if (IS_ENABLED(CONFIG_OF_DYNAMIC)) + WARN_ON(of_reconfig_notifier_register(&peci_of_notifier)); + + is_registered = true; + + return 0; +} + +static void __exit peci_exit(void) +{ + if (IS_ENABLED(CONFIG_OF_DYNAMIC)) + WARN_ON(of_reconfig_notifier_unregister(&peci_of_notifier)); + + bus_unregister(&peci_bus_type); +} + +subsys_initcall(peci_init); +module_exit(peci_exit); + +MODULE_AUTHOR("Jason M Biils <jason.m.bills@linux.intel.com>"); +MODULE_AUTHOR("Jae Hyun Yoo <jae.hyun.yoo@linux.intel.com>"); +MODULE_DESCRIPTION("PECI bus core module"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/peci/peci-dev.c b/drivers/peci/peci-dev.c new file mode 100644 index 000000000000..e0fe09467a80 --- /dev/null +++ b/drivers/peci/peci-dev.c @@ -0,0 +1,348 @@ +// SPDX-License-Identifier: GPL-2.0 +// Copyright (c) 2018-2019 Intel Corporation + +#include <linux/cdev.h> +#include <linux/fs.h> +#include <linux/list.h> +#include <linux/module.h> +#include <linux/notifier.h> +#include <linux/peci.h> +#include <linux/slab.h> +#include <linux/uaccess.h> + +/* + * A peci_dev represents an peci_adapter ... an PECI or SMBus master, not a + * slave (peci_client) with which messages will be exchanged. It's coupled + * with a character special file which is accessed by user mode drivers. + * + * The list of peci_dev structures is parallel to the peci_adapter lists + * maintained by the driver model, and is updated using bus notifications. + */ +struct peci_dev { + struct list_head list; + struct peci_adapter *adapter; + struct device *dev; + struct cdev cdev; +}; + +#define PECI_MINORS MINORMASK + +static dev_t peci_devt; +static LIST_HEAD(peci_dev_list); +static DEFINE_SPINLOCK(peci_dev_list_lock); + +static struct peci_dev *peci_dev_get_by_minor(uint index) +{ + struct peci_dev *peci_dev; + + spin_lock(&peci_dev_list_lock); + list_for_each_entry(peci_dev, &peci_dev_list, list) { + if (peci_dev->adapter->nr == index) + goto found; + } + peci_dev = NULL; +found: + spin_unlock(&peci_dev_list_lock); + + return peci_dev; +} + +static struct peci_dev *peci_dev_alloc(struct peci_adapter *adapter) +{ + struct peci_dev *peci_dev; + + if (adapter->nr >= PECI_MINORS) { + dev_err(&adapter->dev, "Out of device minors (%d)\n", + adapter->nr); + return ERR_PTR(-ENODEV); + } + + peci_dev = kzalloc(sizeof(*peci_dev), GFP_KERNEL); + if (!peci_dev) + return ERR_PTR(-ENOMEM); + peci_dev->adapter = adapter; + + spin_lock(&peci_dev_list_lock); + list_add_tail(&peci_dev->list, &peci_dev_list); + spin_unlock(&peci_dev_list_lock); + + return peci_dev; +} + +static void peci_dev_put(struct peci_dev *peci_dev) +{ + spin_lock(&peci_dev_list_lock); + list_del(&peci_dev->list); + spin_unlock(&peci_dev_list_lock); + kfree(peci_dev); +} + +static ssize_t name_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct peci_dev *peci_dev = peci_dev_get_by_minor(MINOR(dev->devt)); + + if (!peci_dev) + return -ENODEV; + + return sprintf(buf, "%s\n", peci_dev->adapter->name); +} +static DEVICE_ATTR_RO(name); + +static struct attribute *peci_dev_attrs[] = { + &dev_attr_name.attr, + NULL, +}; +ATTRIBUTE_GROUPS(peci_dev); + +static long peci_dev_ioctl(struct file *file, uint iocmd, ulong arg) +{ + struct peci_dev *peci_dev = file->private_data; + void __user *umsg = (void __user *)arg; + struct peci_xfer_msg *xmsg = NULL; + struct peci_xfer_msg uxmsg; + enum peci_cmd cmd; + u8 *msg = NULL; + uint msg_len; + int ret; + + cmd = _IOC_NR(iocmd); + msg_len = _IOC_SIZE(iocmd); + + switch (cmd) { + case PECI_CMD_XFER: + if (msg_len != sizeof(struct peci_xfer_msg)) { + ret = -EFAULT; + break; + } + + if (copy_from_user(&uxmsg, umsg, msg_len)) { + ret = -EFAULT; + break; + } + + xmsg = peci_get_xfer_msg(uxmsg.tx_len, uxmsg.rx_len); + if (IS_ERR(xmsg)) { + ret = PTR_ERR(xmsg); + break; + } + + if (uxmsg.tx_len && + copy_from_user(xmsg->tx_buf, (__u8 __user *)uxmsg.tx_buf, + uxmsg.tx_len)) { + ret = -EFAULT; + break; + } + + xmsg->addr = uxmsg.addr; + xmsg->tx_len = uxmsg.tx_len; + xmsg->rx_len = uxmsg.rx_len; + + ret = peci_command(peci_dev->adapter, cmd, xmsg); + if (!ret && xmsg->rx_len && + copy_to_user((__u8 __user *)uxmsg.rx_buf, xmsg->rx_buf, + xmsg->rx_len)) + ret = -EFAULT; + + break; + + default: + msg = memdup_user(umsg, msg_len); + if (IS_ERR(msg)) { + ret = PTR_ERR(msg); + break; + } + + ret = peci_command(peci_dev->adapter, cmd, msg); + if ((!ret || ret == -ETIMEDOUT) && + copy_to_user(umsg, msg, msg_len)) + ret = -EFAULT; + + break; + } + + peci_put_xfer_msg(xmsg); + kfree(msg); + + return (long)ret; +} + +static int peci_dev_open(struct inode *inode, struct file *file) +{ + struct peci_adapter *adapter; + struct peci_dev *peci_dev; + + peci_dev = peci_dev_get_by_minor(iminor(inode)); + if (!peci_dev) + return -ENODEV; + + adapter = peci_get_adapter(peci_dev->adapter->nr); + if (!adapter) + return -ENODEV; + + file->private_data = peci_dev; + + return 0; +} + +static int peci_dev_release(struct inode *inode, struct file *file) +{ + struct peci_dev *peci_dev = file->private_data; + + peci_put_adapter(peci_dev->adapter); + file->private_data = NULL; + + return 0; +} + +static const struct file_operations peci_dev_fops = { + .owner = THIS_MODULE, + .unlocked_ioctl = peci_dev_ioctl, + .open = peci_dev_open, + .release = peci_dev_release, + .llseek = no_llseek, +}; + +static struct class *peci_dev_class; + +static int peci_dev_attach_adapter(struct device *dev, void *dummy) +{ + struct peci_adapter *adapter; + struct peci_dev *peci_dev; + dev_t devt; + int ret; + + if (dev->type != &peci_adapter_type) + return 0; + + adapter = to_peci_adapter(dev); + peci_dev = peci_dev_alloc(adapter); + if (IS_ERR(peci_dev)) + return PTR_ERR(peci_dev); + + cdev_init(&peci_dev->cdev, &peci_dev_fops); + peci_dev->cdev.owner = THIS_MODULE; + devt = MKDEV(MAJOR(peci_devt), adapter->nr); + + ret = cdev_add(&peci_dev->cdev, devt, 1); + if (ret) + goto err_put_dev; + + /* register this peci device with the driver core */ + peci_dev->dev = device_create(peci_dev_class, &adapter->dev, devt, NULL, + "peci-%d", adapter->nr); + if (IS_ERR(peci_dev->dev)) { + ret = PTR_ERR(peci_dev->dev); + goto err_del_cdev; + } + + dev_info(dev, "cdev of adapter [%s] registered as minor %d\n", + adapter->name, adapter->nr); + + return 0; + +err_del_cdev: + cdev_del(&peci_dev->cdev); +err_put_dev: + peci_dev_put(peci_dev); + + return ret; +} + +static int peci_dev_detach_adapter(struct device *dev, void *dummy) +{ + struct peci_adapter *adapter; + struct peci_dev *peci_dev; + dev_t devt; + + if (dev->type != &peci_adapter_type) + return 0; + + adapter = to_peci_adapter(dev); + peci_dev = peci_dev_get_by_minor(adapter->nr); + if (!peci_dev) + return 0; + + cdev_del(&peci_dev->cdev); + devt = peci_dev->dev->devt; + peci_dev_put(peci_dev); + device_destroy(peci_dev_class, devt); + + dev_info(dev, "cdev of adapter [%s] unregistered\n", adapter->name); + + return 0; +} + +static int peci_dev_notifier_call(struct notifier_block *nb, ulong action, + void *data) +{ + struct device *dev = data; + + switch (action) { + case BUS_NOTIFY_ADD_DEVICE: + return peci_dev_attach_adapter(dev, NULL); + case BUS_NOTIFY_DEL_DEVICE: + return peci_dev_detach_adapter(dev, NULL); + } + + return 0; +} + +static struct notifier_block peci_dev_notifier = { + .notifier_call = peci_dev_notifier_call, +}; + +static int __init peci_dev_init(void) +{ + int ret; + + pr_debug("peci /dev entries driver\n"); + + ret = alloc_chrdev_region(&peci_devt, 0, PECI_MINORS, "peci"); + if (ret < 0) { + pr_err("peci: Failed to allocate chr dev region!\n"); + bus_unregister(&peci_bus_type); + goto err; + } + + peci_dev_class = class_create(THIS_MODULE, KBUILD_MODNAME); + if (IS_ERR(peci_dev_class)) { + ret = PTR_ERR(peci_dev_class); + goto err_unreg_chrdev; + } + peci_dev_class->dev_groups = peci_dev_groups; + + /* Keep track of adapters which will be added or removed later */ + ret = bus_register_notifier(&peci_bus_type, &peci_dev_notifier); + if (ret) + goto err_destroy_class; + + /* Bind to already existing adapters right away */ + peci_for_each_dev(NULL, peci_dev_attach_adapter); + + return 0; + +err_destroy_class: + class_destroy(peci_dev_class); +err_unreg_chrdev: + unregister_chrdev_region(peci_devt, PECI_MINORS); +err: + pr_err("%s: Driver Initialization failed\n", __FILE__); + + return ret; +} + +static void __exit peci_dev_exit(void) +{ + bus_unregister_notifier(&peci_bus_type, &peci_dev_notifier); + peci_for_each_dev(NULL, peci_dev_detach_adapter); + class_destroy(peci_dev_class); + unregister_chrdev_region(peci_devt, PECI_MINORS); +} + +module_init(peci_dev_init); +module_exit(peci_dev_exit); + +MODULE_AUTHOR("Jae Hyun Yoo <jae.hyun.yoo@linux.intel.com>"); +MODULE_DESCRIPTION("PECI /dev entries driver"); +MODULE_LICENSE("GPL v2"); |