The communication channel (comm_chan) service is between versal-pci and the user PF driver. When the user PF driver requests PL data download, the comm_chan service will handle the request by versal_pci_load_xclbin. Co-developed-by: Nishad Saraf <nishads@xxxxxxx> Signed-off-by: Nishad Saraf <nishads@xxxxxxx> Signed-off-by: Yidong Zhang <yidong.zhang@xxxxxxx> --- drivers/fpga/amd/Makefile | 3 +- drivers/fpga/amd/versal-pci-comm-chan.c | 271 ++++++++++++++++++++++++ drivers/fpga/amd/versal-pci-comm-chan.h | 14 ++ drivers/fpga/amd/versal-pci-main.c | 14 +- drivers/fpga/amd/versal-pci.h | 2 + 5 files changed, 301 insertions(+), 3 deletions(-) create mode 100644 drivers/fpga/amd/versal-pci-comm-chan.c create mode 100644 drivers/fpga/amd/versal-pci-comm-chan.h diff --git a/drivers/fpga/amd/Makefile b/drivers/fpga/amd/Makefile index 5d1ef04b5e80..7a604785e5f9 100644 --- a/drivers/fpga/amd/Makefile +++ b/drivers/fpga/amd/Makefile @@ -2,4 +2,5 @@ obj-$(CONFIG_AMD_VERSAL_PCI) += versal-pci.o -versal-pci-$(CONFIG_AMD_VERSAL_PCI) := versal-pci-main.o +versal-pci-$(CONFIG_AMD_VERSAL_PCI) := versal-pci-main.o \ + versal-pci-comm-chan.o diff --git a/drivers/fpga/amd/versal-pci-comm-chan.c b/drivers/fpga/amd/versal-pci-comm-chan.c new file mode 100644 index 000000000000..20ccb1ac7754 --- /dev/null +++ b/drivers/fpga/amd/versal-pci-comm-chan.c @@ -0,0 +1,271 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Driver for Versal PCIe device + * + * Copyright (C) 2024 Advanced Micro Devices, Inc. All rights reserved. + */ + +#include <linux/bitfield.h> +#include <linux/pci.h> + +#include "versal-pci.h" +#include "versal-pci-comm-chan.h" + +#define COMM_CHAN_PROTOCOL_VERSION 1 +#define COMM_CHAN_PCI_BAR_OFF 0x2000000 +#define COMM_CHAN_TIMER (HZ / 10) +#define COMM_CHAN_DATA_LEN 16 +#define COMM_CHAN_DATA_TYPE_MASK GENMASK(7, 0) +#define COMM_CHAN_DATA_EOM_MASK BIT(31) +#define COMM_CHAN_MSG_END BIT(31) + +#define COMM_CHAN_REG_WRDATA_OFF 0x0 +#define COMM_CHAN_REG_RDDATA_OFF 0x8 +#define COMM_CHAN_REG_STATUS_OFF 0x10 +#define COMM_CHAN_REG_ERROR_OFF 0x14 +#define COMM_CHAN_REG_RIT_OFF 0x1C +#define COMM_CHAN_REG_IS_OFF 0x20 +#define COMM_CHAN_REG_IE_OFF 0x24 +#define COMM_CHAN_REG_CTRL_OFF 0x2C +#define COMM_CHAN_REGS_SIZE SZ_4K + +#define COMM_CHAN_IRQ_DISABLE_ALL 0 +#define COMM_CHAN_IRQ_RECEIVE_ENABLE BIT(1) +#define COMM_CHAN_IRQ_CLEAR_ALL GENMASK(2, 0) +#define COMM_CHAN_CLEAR_FIFO GENMASK(1, 0) +#define COMM_CHAN_RECEIVE_THRESHOLD 15 + +enum comm_chan_req_ops { + COMM_CHAN_REQ_OPS_UNKNOWN = 0, + COMM_CHAN_REQ_OPS_HOT_RESET = 5, + COMM_CHAN_REQ_OPS_GET_PROTOCOL_VERSION = 19, + COMM_CHAN_REQ_OPS_LOAD_XCLBIN_UUID = 20, + COMM_CHAN_REQ_OPS_MAX, +}; + +enum comm_chan_msg_type { + COMM_CHAN_MSG_INVALID = 0, + COMM_CHAN_MSG_START = 2, + COMM_CHAN_MSG_BODY = 3, +}; + +enum comm_chan_msg_service_type { + COMM_CHAN_MSG_SRV_RESPONSE = BIT(0), + COMM_CHAN_MSG_SRV_REQUEST = BIT(1), +}; + +struct comm_chan_hw_msg { + struct { + __u32 type; + __u32 payload_size; + } header; + struct { + __u64 id; + __u32 flags; + __u32 size; + __u32 payload[COMM_CHAN_DATA_LEN - 6]; + } body; +} __packed; + +struct comm_chan_srv_req { + __u64 flags; + __u32 opcode; + __u32 data[]; +}; + +struct comm_chan_srv_ver_resp { + __u32 version; +}; + +struct comm_chan_srv_uuid_resp { + __u32 ret; +}; + +struct comm_chan_msg { + __u64 id; + __u32 flags; + __u32 len; + __u32 bytes_read; + __u32 data[10]; +}; + +struct comm_chan_device { + struct versal_pci_device *vdev; + struct timer_list timer; + struct work_struct work; +}; + +static inline struct comm_chan_device *to_ccdev_work(struct work_struct *w) +{ + return container_of(w, struct comm_chan_device, work); +} + +static inline struct comm_chan_device *to_ccdev_timer(struct timer_list *t) +{ + return container_of(t, struct comm_chan_device, timer); +} + +static inline u32 comm_chan_read(struct comm_chan_device *cdev, u32 offset) +{ + return readl(cdev->vdev->io_regs + COMM_CHAN_PCI_BAR_OFF + offset); +} + +static inline void comm_chan_write(struct comm_chan_device *cdev, u32 offset, const u32 value) +{ + writel(value, cdev->vdev->io_regs + COMM_CHAN_PCI_BAR_OFF + offset); +} + +static u32 comm_chan_set_uuid_resp(void *payload, int ret) +{ + struct comm_chan_srv_uuid_resp *resp = (struct comm_chan_srv_uuid_resp *)payload; + u32 resp_len = sizeof(*resp); + + resp->ret = (u32)ret; + + return resp_len; +} + +static u32 comm_chan_set_protocol_resp(void *payload) +{ + struct comm_chan_srv_ver_resp *resp = (struct comm_chan_srv_ver_resp *)payload; + u32 resp_len = sizeof(*resp); + + resp->version = COMM_CHAN_PROTOCOL_VERSION; + + return sizeof(resp_len); +} + +static void comm_chan_send_response(struct comm_chan_device *ccdev, u64 msg_id, void *payload) +{ + struct comm_chan_srv_req *req = (struct comm_chan_srv_req *)payload; + struct versal_pci_device *vdev = ccdev->vdev; + struct comm_chan_hw_msg response = {0}; + u32 size; + int ret; + u8 i; + + switch (req->opcode) { + case COMM_CHAN_REQ_OPS_GET_PROTOCOL_VERSION: + size = comm_chan_set_protocol_resp(response.body.payload); + break; + case COMM_CHAN_REQ_OPS_LOAD_XCLBIN_UUID: + ret = versal_pci_load_xclbin(vdev, (uuid_t *)req->data); + size = comm_chan_set_uuid_resp(response.body.payload, ret); + break; + default: + vdev_err(vdev, "Unsupported request opcode: %d", req->opcode); + *response.body.payload = -1; + size = sizeof(int); + } + + vdev_dbg(vdev, "Response opcode: %d", req->opcode); + + response.header.type = COMM_CHAN_MSG_START | COMM_CHAN_MSG_END; + response.header.payload_size = size; + + response.body.flags = COMM_CHAN_MSG_SRV_RESPONSE; + response.body.size = size; + response.body.id = msg_id; + + for (i = 0; i < COMM_CHAN_DATA_LEN; i++) + comm_chan_write(ccdev, COMM_CHAN_REG_WRDATA_OFF, ((u32 *)&response)[i]); +} + +#define STATUS_IS_READY(status) ((status) & BIT(1)) +#define STATUS_IS_ERROR(status) ((status) & BIT(2)) + +static void comm_chan_check_request(struct work_struct *w) +{ + struct comm_chan_device *ccdev = to_ccdev_work(w); + u32 status = 0, request[COMM_CHAN_DATA_LEN] = {0}; + struct comm_chan_hw_msg *hw_msg; + u8 type, eom; + int i; + + status = comm_chan_read(ccdev, COMM_CHAN_REG_IS_OFF); + if (!STATUS_IS_READY(status)) + return; + if (STATUS_IS_ERROR(status)) { + vdev_err(ccdev->vdev, "An error has occurred with comms"); + return; + } + + /* ACK status */ + comm_chan_write(ccdev, COMM_CHAN_REG_IS_OFF, status); + + for (i = 0; i < COMM_CHAN_DATA_LEN; i++) + request[i] = comm_chan_read(ccdev, COMM_CHAN_REG_RDDATA_OFF); + + hw_msg = (struct comm_chan_hw_msg *)request; + type = FIELD_GET(COMM_CHAN_DATA_TYPE_MASK, hw_msg->header.type); + eom = FIELD_GET(COMM_CHAN_DATA_EOM_MASK, hw_msg->header.type); + + /* Only support fixed size 64B messages */ + if (!eom || type != COMM_CHAN_MSG_START) { + vdev_err(ccdev->vdev, "Unsupported message format or length"); + return; + } + + if (hw_msg->body.flags != COMM_CHAN_MSG_SRV_REQUEST) { + vdev_err(ccdev->vdev, "Unsupported service request"); + return; + } + + if (hw_msg->body.size > sizeof(hw_msg->body.payload)) { + vdev_err(ccdev->vdev, "msg is too big: %d", hw_msg->body.size); + return; + } + + /* Now decode and respond appropriately */ + comm_chan_send_response(ccdev, hw_msg->body.id, hw_msg->body.payload); +} + +static void comm_chan_sched_work(struct timer_list *t) +{ + struct comm_chan_device *ccdev = to_ccdev_timer(t); + + /* Schedule a work in the general workqueue */ + schedule_work(&ccdev->work); + /* Periodic timer */ + mod_timer(&ccdev->timer, jiffies + COMM_CHAN_TIMER); +} + +static void comm_chan_config(struct comm_chan_device *ccdev) +{ + /* Disable interrupts */ + comm_chan_write(ccdev, COMM_CHAN_REG_IE_OFF, COMM_CHAN_IRQ_DISABLE_ALL); + /* Clear request and response FIFOs */ + comm_chan_write(ccdev, COMM_CHAN_REG_CTRL_OFF, COMM_CHAN_CLEAR_FIFO); + /* Clear interrupts */ + comm_chan_write(ccdev, COMM_CHAN_REG_IS_OFF, COMM_CHAN_IRQ_CLEAR_ALL); + /* Setup RIT reg */ + comm_chan_write(ccdev, COMM_CHAN_REG_RIT_OFF, COMM_CHAN_RECEIVE_THRESHOLD); + /* Enable RIT interrupt */ + comm_chan_write(ccdev, COMM_CHAN_REG_IE_OFF, COMM_CHAN_IRQ_RECEIVE_ENABLE); + + /* Create and schedule timer to do recurring work */ + INIT_WORK(&ccdev->work, &comm_chan_check_request); + timer_setup(&ccdev->timer, &comm_chan_sched_work, 0); + mod_timer(&ccdev->timer, jiffies + COMM_CHAN_TIMER); +} + +void versal_pci_comm_chan_fini(struct comm_chan_device *ccdev) +{ + /* First stop scheduling new work then cancel work */ + del_timer_sync(&ccdev->timer); + cancel_work_sync(&ccdev->work); +} + +struct comm_chan_device *versal_pci_comm_chan_init(struct versal_pci_device *vdev) +{ + struct comm_chan_device *ccdev; + + ccdev = devm_kzalloc(&vdev->pdev->dev, sizeof(*ccdev), GFP_KERNEL); + if (!ccdev) + return ERR_PTR(-ENOMEM); + + ccdev->vdev = vdev; + + comm_chan_config(ccdev); + return ccdev; +} diff --git a/drivers/fpga/amd/versal-pci-comm-chan.h b/drivers/fpga/amd/versal-pci-comm-chan.h new file mode 100644 index 000000000000..7605abc5527f --- /dev/null +++ b/drivers/fpga/amd/versal-pci-comm-chan.h @@ -0,0 +1,14 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Driver for Versal PCIe device + * + * Copyright (C) 2024 Advanced Micro Devices, Inc. All rights reserved. + */ + +#ifndef __VERSAL_PCI_COMM_CHAN_H +#define __VERSAL_PCI_COMM_CHAN_H + +struct comm_chan_device *versal_pci_comm_chan_init(struct versal_pci_device *vdev); +void versal_pci_comm_chan_fini(struct comm_chan_device *ccdev); + +#endif /* __VERSAL_PCI_COMM_CHAN_H */ diff --git a/drivers/fpga/amd/versal-pci-main.c b/drivers/fpga/amd/versal-pci-main.c index a10ccf86802b..a3b83197c6d5 100644 --- a/drivers/fpga/amd/versal-pci-main.c +++ b/drivers/fpga/amd/versal-pci-main.c @@ -8,6 +8,7 @@ #include <linux/pci.h> #include "versal-pci.h" +#include "versal-pci-comm-chan.h" #define DRV_NAME "amd-versal-pci" @@ -238,6 +239,7 @@ static void versal_pci_device_teardown(struct versal_pci_device *vdev) { versal_pci_fpga_fini(vdev->fdev); versal_pci_fw_upload_fini(vdev->fwdev); + versal_pci_comm_chan_fini(vdev->ccdev); } static int versal_pci_device_setup(struct versal_pci_device *vdev) @@ -251,15 +253,23 @@ static int versal_pci_device_setup(struct versal_pci_device *vdev) return ret; } + vdev->ccdev = versal_pci_comm_chan_init(vdev); + if (IS_ERR(vdev->ccdev)) { + ret = PTR_ERR(vdev->ccdev); + vdev_err(vdev, "Failed to init comms channel, err %d", ret); + goto upload_fini; + } + vdev->fdev = versal_pci_fpga_init(vdev); if (IS_ERR(vdev->fdev)) { ret = PTR_ERR(vdev->fdev); vdev_err(vdev, "Failed to init FPGA manager, err %d", ret); - goto upload_fini; + goto comm_chan_fini; } return 0; - +comm_chan_fini: + versal_pci_comm_chan_fini(vdev->ccdev); upload_fini: versal_pci_fw_upload_fini(vdev->fwdev); diff --git a/drivers/fpga/amd/versal-pci.h b/drivers/fpga/amd/versal-pci.h index 1509bd0532ea..6c1ca3ce505d 100644 --- a/drivers/fpga/amd/versal-pci.h +++ b/drivers/fpga/amd/versal-pci.h @@ -26,6 +26,7 @@ dev_dbg(&(vdev)->pdev->dev, fmt, ##args) struct versal_pci_device; +struct comm_chan_device; struct axlf_header { __u64 length; @@ -69,6 +70,7 @@ struct versal_pci_device { struct pci_dev *pdev; struct fpga_device *fdev; + struct comm_chan_device *ccdev; struct firmware_device *fwdev; struct device *device; -- 2.34.1