summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorOleksandr Shulzhenko <oleksandr.shulzhenko.viktorovych@intel.com>2022-05-20 15:53:59 +0300
committerOleksandr Shulzhenko <oleksandr.shulzhenko.viktorovych@intel.com>2022-06-01 11:46:09 +0300
commitf097c09e94e4d3d63ccb7d9a670e9ffe095807a2 (patch)
treeff9f83646c11922071eee8f632c68b49c88037b8
parente0e3e2a1c66c03c5cf77afedbac829d31ca06ec0 (diff)
downloadlinux-f097c09e94e4d3d63ccb7d9a670e9ffe095807a2.tar.xz
peci: busses: add PECI o. MCTP o. I3C platform driver
Add PECI adapter that allows to send MCTP PCI VDM packets via i3c-mctp driver. Signed-off-by: Oleksandr Shulzhenko <oleksandr.shulzhenko.viktorovych@intel.com>
-rw-r--r--drivers/peci/busses/Kconfig13
-rw-r--r--drivers/peci/busses/Makefile1
-rw-r--r--drivers/peci/busses/peci-i3c.c254
3 files changed, 268 insertions, 0 deletions
diff --git a/drivers/peci/busses/Kconfig b/drivers/peci/busses/Kconfig
index 20a1a7472d96..8e6a602c879d 100644
--- a/drivers/peci/busses/Kconfig
+++ b/drivers/peci/busses/Kconfig
@@ -44,4 +44,17 @@ config PECI_MCTP
This support is also available as a module. If so, the module
will be called peci-mctp.
+config PECI_I3C
+ tristate "PECI over MCTP over I3C support"
+ depends on I3C
+ depends on PECI
+ depends on I3C_MCTP
+
+ help
+ Say Y here if you want support for the Platform Environment Control
+ Interface (PECI) over MCTP over I3C bus adapter driver.
+
+ This support is also available as a module. If so, the module
+ will be called peci-i3c
+
endmenu
diff --git a/drivers/peci/busses/Makefile b/drivers/peci/busses/Makefile
index ebf3fda9bedc..948578ff6bd1 100644
--- a/drivers/peci/busses/Makefile
+++ b/drivers/peci/busses/Makefile
@@ -6,3 +6,4 @@
obj-$(CONFIG_PECI_ASPEED) += peci-aspeed.o
obj-$(CONFIG_PECI_NPCM) += peci-npcm.o
obj-$(CONFIG_PECI_MCTP) += peci-mctp.o
+obj-$(CONFIG_PECI_I3C) += peci-i3c.o
diff --git a/drivers/peci/busses/peci-i3c.c b/drivers/peci/busses/peci-i3c.c
new file mode 100644
index 000000000000..aaf427ffe35a
--- /dev/null
+++ b/drivers/peci/busses/peci-i3c.c
@@ -0,0 +1,254 @@
+// SPDX-License-Identifier: GPL-2.0
+/* Copyright (C) 2022 Intel Corporation.*/
+
+#include <linux/err.h>
+#include <linux/peci.h>
+#include <linux/platform_device.h>
+#include <linux/printk.h>
+#include <linux/slab.h>
+
+#include <linux/i3c/device.h>
+
+#include <linux/i3c/mctp/i3c-mctp.h>
+
+#define MSG_TAG_MASK GENMASK(2, 0)
+#define MCTP_SET_MSG_TAG(x, val) ((x)->flags_seq_tag |= ((val) & MSG_TAG_MASK))
+#define MCTP_GET_MSG_TAG(x) ((x)->flags_seq_tag & MSG_TAG_MASK)
+#define MCTP_HDR_VERSION 1
+#define REQUEST_FLAGS 0xc8
+#define RESPONSE_FLAGS 0xc0
+#define PECI_REQUEST 0x80
+#define PECI_RESPONSE 0
+#define PECI_PAYLOAD_SIZE 59
+
+#define I3C_PECI_MCTP_TIMEOUT_VALUE_MS 800
+
+struct mctp_peci_vdm_hdr {
+ u8 type;
+ __be16 vendor_id;
+ u8 instance_req_d;
+ u8 vendor_code;
+} __packed;
+
+static const struct mctp_protocol_hdr mctp_protocol_hdr_template = {
+ .ver = MCTP_HDR_VERSION,
+ .flags_seq_tag = REQUEST_FLAGS
+};
+
+static const struct mctp_peci_vdm_hdr mctp_peci_vdm_hdr_template = {
+ .type = MCTP_MSG_TYPE_VDM_PCI,
+ .instance_req_d = PECI_REQUEST,
+ .vendor_code = MCTP_VDM_PCI_INTEL_PECI
+};
+
+struct i3c_peci {
+ struct peci_adapter *adapter;
+ struct device *dev;
+ struct i3c_device *i3cdev;
+ struct i3c_mctp_client *client;
+ u8 tag;
+};
+
+static void
+prepare_tx_packet(struct i3c_mctp_packet *tx_packet,
+ u8 tx_len, u8 rx_len, u8 *tx_buf, u8 dest, u8 tag)
+{
+ struct mctp_protocol_hdr *mctp_protocol_hdr;
+ struct mctp_peci_vdm_hdr *mctp_peci_vdm_hdr;
+ u8 *peci_payload;
+
+ mctp_protocol_hdr = (struct mctp_protocol_hdr *)&tx_packet->data.protocol_hdr;
+ *mctp_protocol_hdr = mctp_protocol_hdr_template;
+ mctp_protocol_hdr->dest = dest;
+ MCTP_SET_MSG_TAG(mctp_protocol_hdr, tag);
+
+ mctp_peci_vdm_hdr = (struct mctp_peci_vdm_hdr *)&tx_packet->data.payload;
+ *mctp_peci_vdm_hdr = mctp_peci_vdm_hdr_template;
+ mctp_peci_vdm_hdr->vendor_id = cpu_to_be16(MCTP_VDM_PCI_INTEL_VENDOR_ID);
+
+ peci_payload = (u8 *)(tx_packet->data.payload) + sizeof(struct mctp_peci_vdm_hdr);
+
+ peci_payload[0] = tx_len;
+ peci_payload[1] = rx_len;
+ memcpy(&peci_payload[2], tx_buf, tx_len);
+
+ tx_packet->size = I3C_MCTP_PACKET_SIZE;
+}
+
+static int
+verify_rx_packet(struct peci_adapter *adapter, struct i3c_mctp_packet *rx_packet, u8 tag)
+{
+ struct i3c_peci *priv = peci_get_adapdata(adapter);
+ bool invalid_packet = false;
+ struct mctp_protocol_hdr *mctp_protocol_hdr;
+ struct mctp_peci_vdm_hdr *mctp_message_hdr;
+ u8 expected_flags;
+
+ expected_flags = (RESPONSE_FLAGS | (tag & MSG_TAG_MASK));
+
+ mctp_protocol_hdr = (struct mctp_protocol_hdr *)&rx_packet->data.protocol_hdr;
+ mctp_message_hdr = (struct mctp_peci_vdm_hdr *)&rx_packet->data.payload;
+
+ if (mctp_protocol_hdr->flags_seq_tag != expected_flags) {
+ dev_dbg(priv->dev,
+ "mismatch in mctp flags: expected: 0x%.2x, got: 0x%.2x",
+ expected_flags, mctp_protocol_hdr->flags_seq_tag);
+ invalid_packet = true;
+ }
+
+ if (mctp_message_hdr->instance_req_d != PECI_RESPONSE) {
+ dev_dbg(priv->dev,
+ "mismatch in PECI response code: expected: 0x%.2x, got: 0x%.2x",
+ PECI_RESPONSE, mctp_message_hdr->instance_req_d);
+ invalid_packet = true;
+ }
+
+ if (invalid_packet) {
+ dev_warn_ratelimited(priv->dev, "unexpected peci response found\n");
+ return -EIO;
+ }
+
+ return 0;
+}
+
+static struct i3c_mctp_packet *i3c_peci_send_receive(struct peci_adapter *adapter,
+ struct i3c_device *i3cdev,
+ u8 tx_len, u8 rx_len, u8 *tx_buf, u8 dest_eid)
+{
+ unsigned long timeout = msecs_to_jiffies(I3C_PECI_MCTP_TIMEOUT_VALUE_MS);
+ struct i3c_peci *priv = peci_get_adapdata(adapter);
+ struct i3c_mctp_packet *tx_packet;
+ struct i3c_mctp_packet *rx_packet;
+ u8 tag = priv->tag;
+ int ret;
+
+ tx_packet = i3c_mctp_packet_alloc(GFP_KERNEL);
+ if (!tx_packet)
+ return ERR_PTR(-ENOMEM);
+
+ prepare_tx_packet(tx_packet, tx_len, rx_len, tx_buf, dest_eid, tag);
+
+ print_hex_dump_bytes("TX : ", DUMP_PREFIX_NONE, &tx_packet->data, tx_packet->size);
+
+ ret = i3c_mctp_send_packet(i3cdev, tx_packet);
+ if (ret) {
+ i3c_mctp_packet_free(tx_packet);
+ return ERR_PTR(ret);
+ }
+
+ i3c_mctp_packet_free(tx_packet);
+ priv->tag++;
+ rx_packet = i3c_mctp_receive_packet(priv->client, timeout);
+ if (IS_ERR(rx_packet))
+ return rx_packet;
+
+ ret = verify_rx_packet(adapter, rx_packet, tag);
+ if (ret) {
+ i3c_mctp_packet_free(rx_packet);
+ return ERR_PTR(ret);
+ }
+
+ print_hex_dump_bytes("RX : ", DUMP_PREFIX_NONE, &rx_packet->data, rx_packet->size);
+
+ return rx_packet;
+}
+
+static int
+i3c_peci_xfer(struct peci_adapter *adapter, struct peci_xfer_msg *msg)
+{
+ struct i3c_peci *priv = peci_get_adapdata(adapter);
+ struct i3c_mctp_packet *rx_packet;
+ u8 domain_id = 0;
+ u8 dest_eid;
+ int ret;
+
+ if (msg->tx_len > PECI_PAYLOAD_SIZE || msg->rx_len > PECI_PAYLOAD_SIZE)
+ return -EINVAL;
+
+ if (msg->tx_len > 2)
+ domain_id = msg->tx_buf[1] >> 1;
+
+ ret = i3c_mctp_get_eid(priv->client, domain_id, &dest_eid);
+ if (ret)
+ return -ENODEV;
+
+ rx_packet = i3c_peci_send_receive(adapter, priv->i3cdev,
+ msg->tx_len, msg->rx_len, msg->tx_buf, dest_eid);
+ if (IS_ERR(rx_packet))
+ return PTR_ERR(rx_packet);
+
+ memcpy(msg->rx_buf, (u8 *)(rx_packet->data.payload) + sizeof(struct mctp_peci_vdm_hdr),
+ msg->rx_len);
+
+ i3c_mctp_packet_free(rx_packet);
+
+ return 0;
+}
+
+static int i3c_peci_probe(struct platform_device *pdev)
+{
+ struct peci_adapter *adapter;
+ struct i3c_peci *priv;
+ int ret;
+
+ adapter = peci_alloc_adapter(&pdev->dev, sizeof(*priv));
+ if (!adapter)
+ return -ENOMEM;
+
+ priv = peci_get_adapdata(adapter);
+
+ platform_set_drvdata(pdev, priv);
+
+ priv->i3cdev = dev_to_i3cdev(pdev->dev.parent);
+ priv->dev = &pdev->dev;
+
+ adapter->owner = THIS_MODULE;
+ strscpy(adapter->name, pdev->name, sizeof(adapter->name));
+
+ adapter->xfer = i3c_peci_xfer;
+ adapter->peci_revision = 0x41;
+
+ priv->adapter = adapter;
+
+ priv->client = i3c_mctp_add_peci_client(priv->i3cdev);
+ if (IS_ERR(priv->client)) {
+ ret = -ENOMEM;
+ goto out_put_device;
+ }
+
+ ret = peci_add_adapter(adapter);
+ if (ret)
+ goto out_del_client;
+
+ return 0;
+
+out_del_client:
+ i3c_mctp_remove_peci_client(priv->client);
+out_put_device:
+ put_device(&adapter->dev);
+ return ret;
+}
+
+static int i3c_peci_remove(struct platform_device *pdev)
+{
+ struct i3c_peci *priv = platform_get_drvdata(pdev);
+
+ peci_del_adapter(priv->adapter);
+
+ i3c_mctp_remove_peci_client(priv->client);
+
+ return 0;
+}
+
+static struct platform_driver i3c_peci_driver = {
+ .probe = i3c_peci_probe,
+ .remove = i3c_peci_remove,
+ .driver = {
+ .name = "peci-i3c",
+ },
+};
+module_platform_driver(i3c_peci_driver);
+
+MODULE_AUTHOR("Oleksandr Shulzhenko <oleksandr.shulzhenko.viktorovych@intel.com>");
+MODULE_DESCRIPTION("I3C PECI driver");
+MODULE_LICENSE("GPL");