Message ID | 20241007220128.3023169-2-yidong.zhang@amd.com (mailing list archive) |
---|---|
State | New |
Headers | show |
Series | Add new driver for AMD Versal PCIe Card | expand |
Hi David, kernel test robot noticed the following build warnings: [auto build test WARNING on linus/master] [also build test WARNING on v6.12-rc2 next-20241008] [If your patch is applied to the wrong git tree, kindly drop us a note. And when submitting patch, we suggest to use '--base' as documented in https://git-scm.com/docs/git-format-patch#_base_tree_information] url: https://github.com/intel-lab-lkp/linux/commits/David-Zhang/drivers-fpga-amd-Add-communication-with-firmware/20241008-060253 base: linus/master patch link: https://lore.kernel.org/r/20241007220128.3023169-2-yidong.zhang%40amd.com patch subject: [PATCH V1 2/3] drivers/fpga/amd: Add communication with firmware config: i386-randconfig-054-20241009 (https://download.01.org/0day-ci/archive/20241009/202410091338.c38eM1Hd-lkp@intel.com/config) compiler: clang version 18.1.8 (https://github.com/llvm/llvm-project 3b5b5c1ec4a3095ab096dd780e84d7ab81f3d7ff) If you fix the issue in a separate patch/commit (i.e. not just a new version of the same patch/commit), kindly add following tags | Reported-by: kernel test robot <lkp@intel.com> | Closes: https://lore.kernel.org/oe-kbuild-all/202410091338.c38eM1Hd-lkp@intel.com/ cocci warnings: (new ones prefixed by >>) >> drivers/fpga/amd/vmgmt-rm.c:183:2-3: Unneeded semicolon -- >> drivers/fpga/amd/vmgmt-rm.c:498:10-17: WARNING: vzalloc should be used for buffer, instead of vmalloc/memset vim +183 drivers/fpga/amd/vmgmt-rm.c 146 147 int rm_queue_create_cmd(struct rm_device *rdev, enum rm_queue_opcode opcode, 148 struct rm_cmd **cmd_ptr) 149 { 150 struct rm_cmd *cmd = NULL; 151 int ret, id; 152 u16 size; 153 154 if (rdev->firewall_tripped) 155 return -ENODEV; 156 157 cmd = kzalloc(sizeof(*cmd), GFP_KERNEL); 158 if (!cmd) 159 return -ENOMEM; 160 cmd->rdev = rdev; 161 162 switch (opcode) { 163 case RM_QUEUE_OP_LOAD_XCLBIN: 164 fallthrough; 165 case RM_QUEUE_OP_LOAD_FW: 166 fallthrough; 167 case RM_QUEUE_OP_LOAD_APU_FW: 168 size = sizeof(struct rm_cmd_sq_bin); 169 break; 170 case RM_QUEUE_OP_GET_LOG_PAGE: 171 size = sizeof(struct rm_cmd_sq_log_page); 172 break; 173 case RM_QUEUE_OP_IDENTIFY: 174 size = 0; 175 break; 176 case RM_QUEUE_OP_VMR_CONTROL: 177 size = sizeof(struct rm_cmd_sq_ctrl); 178 break; 179 default: 180 vmgmt_err(rdev->vdev, "Invalid cmd opcode %d", opcode); 181 ret = -EINVAL; 182 goto error; > 183 }; 184 185 cmd->opcode = opcode; 186 cmd->sq_msg.hdr.opcode = FIELD_PREP(RM_CMD_SQ_HDR_OPS_MSK, opcode); 187 cmd->sq_msg.hdr.msg_size = FIELD_PREP(RM_CMD_SQ_HDR_SIZE_MSK, size); 188 189 id = ida_alloc_range(&rm_cmd_ids, RM_CMD_ID_MIN, RM_CMD_ID_MAX, GFP_KERNEL); 190 if (id < 0) { 191 vmgmt_err(rdev->vdev, "Failed to alloc cmd ID: %d", id); 192 ret = id; 193 goto error; 194 } 195 cmd->sq_msg.hdr.id = id; 196 197 init_completion(&cmd->executed); 198 199 *cmd_ptr = cmd; 200 return 0; 201 error: 202 kfree(cmd); 203 return ret; 204 } 205
Hi David, kernel test robot noticed the following build warnings: [auto build test WARNING on linus/master] [also build test WARNING on v6.12-rc2 next-20241008] [If your patch is applied to the wrong git tree, kindly drop us a note. And when submitting patch, we suggest to use '--base' as documented in https://git-scm.com/docs/git-format-patch#_base_tree_information] url: https://github.com/intel-lab-lkp/linux/commits/David-Zhang/drivers-fpga-amd-Add-communication-with-firmware/20241008-060253 base: linus/master patch link: https://lore.kernel.org/r/20241007220128.3023169-2-yidong.zhang%40amd.com patch subject: [PATCH V1 2/3] drivers/fpga/amd: Add communication with firmware config: x86_64-randconfig-121-20241009 (https://download.01.org/0day-ci/archive/20241009/202410091855.yLTZGOfr-lkp@intel.com/config) compiler: gcc-12 (Debian 12.2.0-14) 12.2.0 reproduce (this is a W=1 build): (https://download.01.org/0day-ci/archive/20241009/202410091855.yLTZGOfr-lkp@intel.com/reproduce) If you fix the issue in a separate patch/commit (i.e. not just a new version of the same patch/commit), kindly add following tags | Reported-by: kernel test robot <lkp@intel.com> | Closes: https://lore.kernel.org/oe-kbuild-all/202410091855.yLTZGOfr-lkp@intel.com/ sparse warnings: (new ones prefixed by >>) drivers/fpga/amd/vmgmt.c:35:14: sparse: sparse: symbol 'vmgmt_class' was not declared. Should it be static? >> drivers/fpga/amd/vmgmt.c:272:45: sparse: sparse: incorrect type in argument 2 (different address spaces) @@ expected void const [noderef] __user *from @@ got void * @@ drivers/fpga/amd/vmgmt.c:272:45: sparse: expected void const [noderef] __user *from drivers/fpga/amd/vmgmt.c:272:45: sparse: got void * drivers/fpga/amd/vmgmt.c:301:45: sparse: sparse: incorrect type in argument 2 (different address spaces) @@ expected void const [noderef] __user *from @@ got void * @@ drivers/fpga/amd/vmgmt.c:301:45: sparse: expected void const [noderef] __user *from drivers/fpga/amd/vmgmt.c:301:45: sparse: got void * vim +272 drivers/fpga/amd/vmgmt.c 257 258 static long vmgmt_ioctl(struct file *filep, unsigned int cmd, unsigned long arg) 259 { 260 struct vmgmt_device *vdev = (struct vmgmt_device *)filep->private_data; 261 struct vmgmt_fpga_region reg = { 0 }; 262 struct fpga_region *region = NULL; 263 struct axlf *axlf = NULL; 264 void *data = NULL; 265 size_t size = 0; 266 int ret = 0; 267 268 axlf = vmalloc(sizeof(*axlf)); 269 if (!axlf) 270 return -ENOMEM; 271 > 272 ret = copy_from_user((void *)axlf, (void *)arg, sizeof(*axlf)); 273 if (ret) { 274 vmgmt_err(vdev, "Failed to copy axlf: %d", ret); 275 ret = -EFAULT; 276 goto exit; 277 } 278 279 ret = memcmp(axlf->magic, VERSAL_XCLBIN_MAGIC_ID, 280 sizeof(VERSAL_XCLBIN_MAGIC_ID)); 281 if (ret) { 282 vmgmt_err(vdev, "unknown axlf magic %s", axlf->magic); 283 ret = -EINVAL; 284 goto exit; 285 } 286 287 /* axlf should never be over 1G and less than size of struct axlf */ 288 size = axlf->header.length; 289 if (size < sizeof(struct axlf) || size > 1024 * 1024 * 1024) { 290 vmgmt_err(vdev, "axlf length %zu is invalid", size); 291 ret = -EINVAL; 292 goto exit; 293 } 294 295 data = vmalloc(size); 296 if (!data) { 297 ret = -ENOMEM; 298 goto exit; 299 } 300 301 ret = copy_from_user((void *)data, (void *)arg, size); 302 if (ret) { 303 vmgmt_err(vdev, "Failed to copy data: %d", ret); 304 ret = -EFAULT; 305 goto exit; 306 } 307 308 switch (cmd) { 309 case VERSAL_MGMT_LOAD_XCLBIN_IOCTL: 310 vdev->fdev->fw.opcode = RM_QUEUE_OP_LOAD_XCLBIN; 311 break; 312 default: 313 vmgmt_err(vdev, "Invalid IOCTL command: %d", cmd); 314 ret = -EINVAL; 315 goto exit; 316 } 317 318 reg.uuid = &axlf->header.rom_uuid; 319 reg.fdev = vdev->fdev; 320 321 region = fpga_region_class_find(NULL, ®, vmgmt_fpga_region_match); 322 if (!region) { 323 vmgmt_err(vdev, "Failed to find compatible region"); 324 ret = -ENOENT; 325 goto exit; 326 } 327 328 ret = vmgmt_region_program(region, data); 329 if (ret) { 330 vmgmt_err(vdev, "Failed to program region"); 331 goto exit; 332 } 333 334 vmgmt_info(vdev, "Downloaded axlf %pUb of size %zu Bytes", 335 &axlf->header.uuid, size); 336 uuid_copy(&vdev->xclbin_uuid, &axlf->header.uuid); 337 338 exit: 339 vfree(data); 340 vfree(axlf); 341 342 return ret; 343 } 344
On Mon, Oct 07, 2024 at 03:01:27PM -0700, David Zhang wrote: > From: Yidong Zhang <yidong.zhang@amd.com> > > Add queue based communication between host driver and firmware on the > card. The remote queue (rm) can send/receive messages and providing Abbrevate 'remote queue' to 'rm', or 'remote' to 'rm'. I see you use rm_queue in the code. > firmware downloading services. > > Co-developed-by: Nishad Saraf <nishads@amd.com> > Signed-off-by: Nishad Saraf <nishads@amd.com> > Co-developed-by: Prapul Krishnamurthy <prapulk@amd.com> > Signed-off-by: Prapul Krishnamurthy <prapulk@amd.com> > Signed-off-by: Yidong Zhang <yidong.zhang@amd.com> > --- > drivers/fpga/amd/Makefile | 6 +- > drivers/fpga/amd/vmgmt-rm-queue.c | 38 +++ > drivers/fpga/amd/vmgmt-rm-queue.h | 15 + > drivers/fpga/amd/vmgmt-rm.c | 543 ++++++++++++++++++++++++++++++ > drivers/fpga/amd/vmgmt-rm.h | 222 ++++++++++++ > drivers/fpga/amd/vmgmt.c | 305 ++++++++++++++++- > drivers/fpga/amd/vmgmt.h | 7 +- > 7 files changed, 1130 insertions(+), 6 deletions(-) > create mode 100644 drivers/fpga/amd/vmgmt-rm-queue.c > create mode 100644 drivers/fpga/amd/vmgmt-rm-queue.h > create mode 100644 drivers/fpga/amd/vmgmt-rm.c > create mode 100644 drivers/fpga/amd/vmgmt-rm.h > > diff --git a/drivers/fpga/amd/Makefile b/drivers/fpga/amd/Makefile > index 3e4c6dd3b787..97cfff6be204 100644 > --- a/drivers/fpga/amd/Makefile > +++ b/drivers/fpga/amd/Makefile > @@ -2,5 +2,7 @@ > > obj-$(CONFIG_AMD_VERSAL_MGMT) += amd-vmgmt.o > > -amd-vmgmt-$(CONFIG_AMD_VERSAL_MGMT) := vmgmt.o \ > - vmgmt-comms.o > +amd-vmgmt-$(CONFIG_AMD_VERSAL_MGMT) := vmgmt.o \ > + vmgmt-comms.o \ > + vmgmt-rm.o \ > + vmgmt-rm-queue.o > diff --git a/drivers/fpga/amd/vmgmt-rm-queue.c b/drivers/fpga/amd/vmgmt-rm-queue.c > new file mode 100644 > index 000000000000..fe805373ea32 > --- /dev/null > +++ b/drivers/fpga/amd/vmgmt-rm-queue.c > @@ -0,0 +1,38 @@ > +// 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/completion.h> > +#include <linux/err.h> > +#include <linux/firmware.h> > +#include <linux/idr.h> > +#include <linux/jiffies.h> > +#include <linux/list.h> > +#include <linux/mutex.h> > +#include <linux/pci.h> > +#include <linux/semaphore.h> > +#include <linux/timer.h> > +#include <linux/uuid.h> > +#include <linux/workqueue.h> > + > +#include "vmgmt.h" > +#include "vmgmt-rm.h" > +#include "vmgmt-rm-queue.h" > + > +int rm_queue_send_cmd(struct rm_cmd *cmd, unsigned long timeout) > +{ > + return 0; > +} > + > +void rm_queue_fini(struct rm_device *rdev) > +{ > +} > + > +int rm_queue_init(struct rm_device *rdev) > +{ > + return 0; > +} > diff --git a/drivers/fpga/amd/vmgmt-rm-queue.h b/drivers/fpga/amd/vmgmt-rm-queue.h > new file mode 100644 > index 000000000000..6fd0e0026a13 > --- /dev/null > +++ b/drivers/fpga/amd/vmgmt-rm-queue.h > @@ -0,0 +1,15 @@ > +/* SPDX-License-Identifier: GPL-2.0 */ > +/* > + * Driver for Versal PCIe device > + * > + * Copyright (C) 2024 Advanced Micro Devices, Inc. All rights reserved. > + */ > + > +#ifndef __VMGMT_RM_QUEUE_H > +#define __VMGMT_RM_QUEUE_H > + > +int rm_queue_init(struct rm_device *rdev); > +void rm_queue_fini(struct rm_device *rdev); > +int rm_queue_send_cmd(struct rm_cmd *cmd, unsigned long timeout); > + > +#endif /* __VMGMT_RM_QUEUE_H */ > diff --git a/drivers/fpga/amd/vmgmt-rm.c b/drivers/fpga/amd/vmgmt-rm.c > new file mode 100644 > index 000000000000..856d5af52c8d > --- /dev/null > +++ b/drivers/fpga/amd/vmgmt-rm.c > @@ -0,0 +1,543 @@ > +// 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/completion.h> > +#include <linux/err.h> > +#include <linux/firmware.h> > +#include <linux/idr.h> > +#include <linux/jiffies.h> > +#include <linux/list.h> > +#include <linux/mutex.h> > +#include <linux/pci.h> > +#include <linux/semaphore.h> > +#include <linux/timer.h> > +#include <linux/uuid.h> > +#include <linux/workqueue.h> > + > +#include "vmgmt.h" > +#include "vmgmt-rm.h" > +#include "vmgmt-rm-queue.h" > + > +static DEFINE_IDA(rm_cmd_ids); > + > +static const struct regmap_config rm_shmem_regmap_config = { > + .name = "rm_shmem_config", > + .reg_bits = 32, > + .reg_stride = 4, > + .val_bits = 32, > +}; > + > +static const struct regmap_config rm_io_regmap_config = { > + .name = "rm_io_config", > + .reg_bits = 32, > + .reg_stride = 4, > + .val_bits = 32, > +}; > + > +static void rm_uninstall_health_monitor(struct rm_device *rdev); > + > +static inline struct rm_device *to_rdev_health_monitor(struct work_struct *w) > +{ > + return container_of(w, struct rm_device, health_monitor); > +} > + > +static inline struct rm_device *to_rdev_health_timer(struct timer_list *t) > +{ > + return container_of(t, struct rm_device, health_timer); > +} > + > +static inline int rm_shmem_read(struct rm_device *rdev, u32 offset, u32 *value) > +{ > + return regmap_read(rdev->shmem_regmap, offset, value); > +} > + > +static inline int rm_shmem_bulk_read(struct rm_device *rdev, u32 offset, > + u32 *value, u32 size) > +{ > + return regmap_bulk_read(rdev->shmem_regmap, offset, value, > + DIV_ROUND_UP(size, 4)); > +} > + > +static inline int rm_shmem_bulk_write(struct rm_device *rdev, u32 offset, > + u32 *value, u32 size) > +{ > + return regmap_bulk_write(rdev->shmem_regmap, offset, value, > + DIV_ROUND_UP(size, 4)); > +} > + > +void rm_queue_destory_cmd(struct rm_cmd *cmd) > +{ > + ida_free(&rm_cmd_ids, cmd->sq_msg.hdr.id); > + kfree(cmd); > +} > + > +int rm_queue_copy_response(struct rm_cmd *cmd, void *buffer, ssize_t len) > +{ > + struct rm_cmd_cq_log_page *result = &cmd->cq_msg.data.page; > + u64 off = cmd->sq_msg.data.page.address; > + > + if (!result->len || len < result->len) { > + vmgmt_err(cmd->rdev->vdev, "Invalid response or buffer size"); > + return -EINVAL; > + } > + > + return rm_shmem_bulk_read(cmd->rdev, off, (u32 *)buffer, result->len); > +} > + > +static void rm_queue_payload_fini(struct rm_cmd *cmd) > +{ > + up(&cmd->rdev->cq.data_lock); > +} > + > +static int rm_queue_payload_init(struct rm_cmd *cmd, > + enum rm_cmd_log_page_type type) > +{ > + struct rm_device *rdev = cmd->rdev; > + int ret; > + > + ret = down_interruptible(&rdev->cq.data_lock); > + if (ret) > + return ret; > + > + cmd->sq_msg.data.page.address = rdev->cq.data_offset; > + cmd->sq_msg.data.page.size = rdev->cq.data_size; > + cmd->sq_msg.data.page.reserved1 = 0; > + cmd->sq_msg.data.page.type = FIELD_PREP(RM_CMD_LOG_PAGE_TYPE_MASK, > + type); > + return 0; > +} > + > +void rm_queue_data_fini(struct rm_cmd *cmd) > +{ > + up(&cmd->rdev->sq.data_lock); > +} > + > +int rm_queue_data_init(struct rm_cmd *cmd, const char *buffer, ssize_t size) > +{ > + struct rm_device *rdev = cmd->rdev; > + int ret; > + > + if (!size || size > rdev->sq.data_size) { > + vmgmt_err(rdev->vdev, "Unsupported file size"); > + return -ENOMEM; > + } > + > + ret = down_interruptible(&rdev->sq.data_lock); > + if (ret) > + return ret; > + > + ret = rm_shmem_bulk_write(cmd->rdev, rdev->sq.data_offset, > + (u32 *)buffer, size); > + if (ret) { > + vmgmt_err(rdev->vdev, "Failed to copy binary to SQ buffer"); > + up(&cmd->rdev->sq.data_lock); > + return ret; > + } > + > + cmd->sq_msg.data.bin.address = rdev->sq.data_offset; > + cmd->sq_msg.data.bin.size = size; > + return 0; > +} > + > +int rm_queue_create_cmd(struct rm_device *rdev, enum rm_queue_opcode opcode, > + struct rm_cmd **cmd_ptr) > +{ > + struct rm_cmd *cmd = NULL; > + int ret, id; > + u16 size; > + > + if (rdev->firewall_tripped) > + return -ENODEV; > + > + cmd = kzalloc(sizeof(*cmd), GFP_KERNEL); > + if (!cmd) > + return -ENOMEM; > + cmd->rdev = rdev; > + > + switch (opcode) { > + case RM_QUEUE_OP_LOAD_XCLBIN: > + fallthrough; > + case RM_QUEUE_OP_LOAD_FW: > + fallthrough; > + case RM_QUEUE_OP_LOAD_APU_FW: > + size = sizeof(struct rm_cmd_sq_bin); > + break; > + case RM_QUEUE_OP_GET_LOG_PAGE: > + size = sizeof(struct rm_cmd_sq_log_page); > + break; > + case RM_QUEUE_OP_IDENTIFY: > + size = 0; > + break; > + case RM_QUEUE_OP_VMR_CONTROL: > + size = sizeof(struct rm_cmd_sq_ctrl); > + break; > + default: > + vmgmt_err(rdev->vdev, "Invalid cmd opcode %d", opcode); > + ret = -EINVAL; > + goto error; > + }; > + > + cmd->opcode = opcode; > + cmd->sq_msg.hdr.opcode = FIELD_PREP(RM_CMD_SQ_HDR_OPS_MSK, opcode); > + cmd->sq_msg.hdr.msg_size = FIELD_PREP(RM_CMD_SQ_HDR_SIZE_MSK, size); > + > + id = ida_alloc_range(&rm_cmd_ids, RM_CMD_ID_MIN, RM_CMD_ID_MAX, GFP_KERNEL); > + if (id < 0) { > + vmgmt_err(rdev->vdev, "Failed to alloc cmd ID: %d", id); > + ret = id; > + goto error; > + } > + cmd->sq_msg.hdr.id = id; > + > + init_completion(&cmd->executed); > + > + *cmd_ptr = cmd; > + return 0; > +error: > + kfree(cmd); > + return ret; > +} > + > +static int rm_queue_verify(struct rm_device *rdev) > +{ > + struct vmgmt_device *vdev = rdev->vdev; > + struct rm_cmd_cq_identify *result; > + struct rm_cmd *cmd; > + u32 major, minor; > + int ret; > + > + ret = rm_queue_create_cmd(rdev, RM_QUEUE_OP_IDENTIFY, &cmd); > + if (ret) > + return ret; > + > + ret = rm_queue_send_cmd(cmd, RM_CMD_WAIT_CONFIG_TIMEOUT); > + if (ret) > + goto error; > + > + result = &cmd->cq_msg.data.identify; > + major = result->major; > + minor = result->minor; > + vmgmt_dbg(vdev, "VMR version %d.%d", major, minor); > + if (!major) { > + vmgmt_err(vdev, "VMR version is unsupported"); > + ret = -EOPNOTSUPP; > + } > + > +error: > + rm_queue_destory_cmd(cmd); > + return ret; > +} > + > +static int rm_check_apu_status(struct rm_device *rdev, bool *status) > +{ > + struct rm_cmd_cq_control *result; > + struct rm_cmd *cmd; > + int ret; > + > + ret = rm_queue_create_cmd(rdev, RM_QUEUE_OP_VMR_CONTROL, &cmd); > + if (ret) > + return ret; > + > + ret = rm_queue_send_cmd(cmd, RM_CMD_WAIT_CONFIG_TIMEOUT); > + if (ret) > + goto error; > + > + result = &cmd->cq_msg.data.ctrl; > + *status = FIELD_GET(RM_CMD_VMR_CONTROL_PS_MASK, result->status); > + > + rm_queue_destory_cmd(cmd); > + return 0; > + > +error: > + rm_queue_destory_cmd(cmd); > + return ret; > +} > + > +static int rm_download_apu_fw(struct rm_device *rdev, char *data, ssize_t size) > +{ > + struct rm_cmd *cmd; > + int ret; > + > + ret = rm_queue_create_cmd(rdev, RM_QUEUE_OP_LOAD_APU_FW, &cmd); > + if (ret) > + return ret; > + > + ret = rm_queue_data_init(cmd, data, size); > + if (ret) > + goto done; > + > + ret = rm_queue_send_cmd(cmd, RM_CMD_WAIT_DOWNLOAD_TIMEOUT); > + > +done: > + rm_queue_destory_cmd(cmd); > + return ret; > +} > + > +int rm_boot_apu(struct rm_device *rdev) > +{ > + char *bin = "xilinx/xrt-versal-apu.xsabin"; So apu fw never changes, is it? > + const struct firmware *fw = NULL; > + bool status; > + int ret; > + > + ret = rm_check_apu_status(rdev, &status); > + if (ret) { > + vmgmt_err(rdev->vdev, "Failed to get APU status"); > + return ret; > + } > + > + if (status) { > + vmgmt_dbg(rdev->vdev, "APU online. Skipping APU FW download"); > + return 0; > + } > + > + ret = request_firmware(&fw, bin, &rdev->vdev->pdev->dev); > + if (ret) { > + vmgmt_warn(rdev->vdev, "Request APU FW %s failed %d", bin, ret); > + return ret; > + } > + > + vmgmt_dbg(rdev->vdev, "Starting... APU FW download"); > + ret = rm_download_apu_fw(rdev, (char *)fw->data, fw->size); > + vmgmt_dbg(rdev->vdev, "Finished... APU FW download %d", ret); > + > + if (ret) > + vmgmt_err(rdev->vdev, "Failed to download APU FW, ret:%d", ret); > + > + release_firmware(fw); > + > + return ret; > +} > + > +static void rm_check_health(struct work_struct *w) > +{ > + struct rm_device *rdev = to_rdev_health_monitor(w); > + ssize_t len = PAGE_SIZE; > + char *buffer = NULL; > + struct rm_cmd *cmd; > + int ret; > + > + buffer = vzalloc(len); > + if (!buffer) > + return; > + > + ret = rm_queue_create_cmd(rdev, RM_QUEUE_OP_GET_LOG_PAGE, &cmd); > + if (ret) > + return; Memory Leak! > + > + ret = rm_queue_payload_init(cmd, RM_CMD_LOG_PAGE_AXI_TRIP_STATUS); > + if (ret) > + goto error; > + > + ret = rm_queue_send_cmd(cmd, RM_CMD_WAIT_CONFIG_TIMEOUT); > + if (ret == -ETIME || ret == -EINVAL) > + goto payload_fini; > + > + if (cmd->cq_msg.data.page.len) { If no data, the heath is good? > + ret = rm_queue_copy_response(cmd, buffer, len); > + if (ret) > + goto payload_fini; > + > + vmgmt_err(rdev->vdev, "%s", buffer); Any concern reading out of bound. If the buffer is only used in this block, put its allocation/free here. > + rdev->firewall_tripped = 1; > + } > + > + vfree(buffer); > + > + rm_queue_payload_fini(cmd); > + rm_queue_destory_cmd(cmd); > + > + return; > + > +payload_fini: > + rm_queue_payload_fini(cmd); > +error: > + rm_queue_destory_cmd(cmd); > + vfree(buffer); > +} > + > +static void rm_sched_health_check(struct timer_list *t) > +{ > + struct rm_device *rdev = to_rdev_health_timer(t); > + > + if (rdev->firewall_tripped) { > + vmgmt_err(rdev->vdev, "Firewall tripped, health check paused. Please reset card"); > + return; > + } > + /* Schedule a work in the general workqueue */ > + schedule_work(&rdev->health_monitor); > + /* Periodic timer */ > + mod_timer(&rdev->health_timer, jiffies + RM_HEALTH_CHECK_TIMER); Again, is it necessary we endless poll. Coundn't we check on cmd create? > +} > + > +static void rm_uninstall_health_monitor(struct rm_device *rdev) > +{ > + del_timer_sync(&rdev->health_timer); > + cancel_work_sync(&rdev->health_monitor); > +} > + > +static void rm_install_health_monitor(struct rm_device *rdev) > +{ > + INIT_WORK(&rdev->health_monitor, &rm_check_health); > + timer_setup(&rdev->health_timer, &rm_sched_health_check, 0); > + mod_timer(&rdev->health_timer, jiffies + RM_HEALTH_CHECK_TIMER); > +} > + > +void vmgmt_rm_fini(struct rm_device *rdev) > +{ > + rm_uninstall_health_monitor(rdev); > + rm_queue_fini(rdev); > +} > + > +struct rm_device *vmgmt_rm_init(struct vmgmt_device *vdev) > +{ > + struct rm_header *header; > + struct rm_device *rdev; > + u32 status; > + int ret; > + > + rdev = devm_kzalloc(&vdev->pdev->dev, sizeof(*rdev), GFP_KERNEL); > + if (!rdev) > + return ERR_PTR(-ENOMEM); > + > + rdev->vdev = vdev; > + header = &rdev->rm_metadata; > + > + rdev->shmem_regmap = devm_regmap_init_mmio(&vdev->pdev->dev, > + vdev->tbl + RM_PCI_SHMEM_BAR_OFF, > + &rm_shmem_regmap_config); > + if (IS_ERR(rdev->shmem_regmap)) { > + vmgmt_err(vdev, "Failed to init RM shared memory regmap"); > + return ERR_CAST(rdev->shmem_regmap); > + } > + > + ret = rm_shmem_bulk_read(rdev, RM_HDR_OFF, (u32 *)header, > + sizeof(*header)); > + if (ret) { > + vmgmt_err(vdev, "Failed to read RM shared mem, ret %d", ret); > + ret = -ENODEV; > + goto err; > + } > + > + if (header->magic != RM_HDR_MAGIC_NUM) { > + vmgmt_err(vdev, "Invalid RM header 0x%x", header->magic); > + ret = -ENODEV; > + goto err; > + } > + > + ret = rm_shmem_read(rdev, header->status_off, &status); > + if (ret) { > + vmgmt_err(vdev, "Failed to read RM shared mem, ret %d", ret); > + ret = -ENODEV; > + goto err; > + } > + > + if (!status) { > + vmgmt_err(vdev, "RM status %d is not ready", status); > + ret = -ENODEV; > + goto err; > + } > + > + rdev->queue_buffer_size = header->data_end - header->data_start + 1; > + rdev->queue_buffer_start = header->data_start; > + rdev->queue_base = header->queue_base; > + > + rdev->io_regmap = devm_regmap_init_mmio(&vdev->pdev->dev, > + vdev->tbl + RM_PCI_IO_BAR_OFF, > + &rm_io_regmap_config); > + if (IS_ERR(rdev->io_regmap)) { > + vmgmt_err(vdev, "Failed to init RM IO regmap"); > + ret = PTR_ERR(rdev->io_regmap); > + goto err; > + } > + > + ret = rm_queue_init(rdev); > + if (ret) { > + vmgmt_err(vdev, "Failed to init cmd queue, ret %d", ret); > + ret = -ENODEV; > + goto err; > + } > + > + ret = rm_queue_verify(rdev); > + if (ret) { > + vmgmt_err(vdev, "Failed to verify cmd queue, ret %d", ret); > + ret = -ENODEV; > + goto queue_fini; > + } > + > + ret = rm_boot_apu(rdev); > + if (ret) { > + vmgmt_err(vdev, "Failed to bringup APU, ret %d", ret); > + ret = -ENODEV; > + goto queue_fini; > + } > + > + rm_install_health_monitor(rdev); > + > + return rdev; > +queue_fini: > + rm_queue_fini(rdev); > +err: > + return ERR_PTR(ret); > +} > + > +int vmgmt_rm_get_fw_id(struct rm_device *rdev, uuid_t *uuid) > +{ > + char str[UUID_STRING_LEN]; > + ssize_t len = PAGE_SIZE; > + char *buffer = NULL; > + struct rm_cmd *cmd; > + u8 i, j; > + int ret; > + > + buffer = vmalloc(len); > + if (!buffer) > + return -ENOMEM; > + > + memset(buffer, 0, len); vzalloc()? > + > + ret = rm_queue_create_cmd(rdev, RM_QUEUE_OP_GET_LOG_PAGE, &cmd); > + if (ret) > + return ret; > + > + ret = rm_queue_payload_init(cmd, RM_CMD_LOG_PAGE_FW_ID); > + if (ret) > + goto error; > + > + ret = rm_queue_send_cmd(cmd, RM_CMD_WAIT_CONFIG_TIMEOUT); > + if (ret) > + goto payload; > + > + ret = rm_queue_copy_response(cmd, buffer, len); > + if (ret) > + goto payload; > + > + /* parse uuid into a valid uuid string format */ > + for (i = 0, j = 0; i < strlen(buffer); i++) { > + str[j++] = buffer[i]; > + if (j == 8 || j == 13 || j == 18 || j == 23) > + str[j++] = '-'; > + } > + > + uuid_parse(str, uuid); > + vmgmt_dbg(rdev->vdev, "Interface uuid %pU", uuid); > + > + vfree(buffer); > + > + rm_queue_payload_fini(cmd); > + rm_queue_destory_cmd(cmd); > + > + return 0; > + > +payload: > + rm_queue_payload_fini(cmd); > +error: > + rm_queue_destory_cmd(cmd); > + vfree(buffer); > + return ret; > +} > diff --git a/drivers/fpga/amd/vmgmt-rm.h b/drivers/fpga/amd/vmgmt-rm.h > new file mode 100644 > index 000000000000..a74f93cefbe8 > --- /dev/null > +++ b/drivers/fpga/amd/vmgmt-rm.h > @@ -0,0 +1,222 @@ > +/* SPDX-License-Identifier: GPL-2.0 */ > +/* > + * Driver for Versal PCIe device > + * > + * Copyright (C) 2024 Advanced Micro Devices, Inc. All rights reserved. > + */ > + > +#ifndef __VMGMT_RM_H > +#define __VMGMT_RM_H > + > +#define RM_HDR_OFF 0x0 > +#define RM_HDR_MAGIC_NUM 0x564D5230 > +#define RM_QUEUE_HDR_MAGIC_NUM 0x5847513F > +#define RM_PCI_IO_BAR_OFF 0x2010000 > +#define RM_PCI_IO_SIZE 0x1000 > +#define RM_PCI_SHMEM_BAR_OFF 0x8000000 > +#define RM_PCI_SHMEM_SIZE 0x8000000 /* 128 MB */ > +#define RM_PCI_SHMEM_HDR_SIZE 0x28 > + > +#define RM_QUEUE_HDR_MAGIC_NUM_OFF 0x0 > +#define RM_IO_SQ_PIDX_OFF 0x0 > +#define RM_IO_CQ_PIDX_OFF 0x100 > + > +#define RM_CMD_ID_MIN 1 > +#define RM_CMD_ID_MAX (BIT(17) - 1) > +#define RM_CMD_SQ_HDR_OPS_MSK GENMASK(15, 0) > +#define RM_CMD_SQ_HDR_SIZE_MSK GENMASK(14, 0) > +#define RM_CMD_SQ_SLOT_SIZE 512 > +#define RM_CMD_CQ_SLOT_SIZE 16 > +#define RM_CMD_CQ_BUFFER_SIZE (1024 * 1024) > +#define RM_CMD_CQ_BUFFER_OFFSET 0x0 > +#define RM_CMD_LOG_PAGE_TYPE_MASK GENMASK(15, 0) > +#define RM_CMD_VMR_CONTROL_MSK GENMASK(10, 8) > +#define RM_CMD_VMR_CONTROL_PS_MASK BIT(9) > + > +#define RM_CMD_WAIT_CONFIG_TIMEOUT msecs_to_jiffies(10 * 1000) > +#define RM_CMD_WAIT_DOWNLOAD_TIMEOUT msecs_to_jiffies(300 * 1000) > + > +#define RM_COMPLETION_TIMER (HZ / 10) > +#define RM_HEALTH_CHECK_TIMER (HZ) > + > +#define RM_INVALID_SLOT 0 > + > +enum rm_queue_opcode { > + RM_QUEUE_OP_LOAD_XCLBIN = 0x0, > + RM_QUEUE_OP_GET_LOG_PAGE = 0x8, > + RM_QUEUE_OP_LOAD_FW = 0xA, > + RM_QUEUE_OP_LOAD_APU_FW = 0xD, > + RM_QUEUE_OP_VMR_CONTROL = 0xE, > + RM_QUEUE_OP_IDENTIFY = 0x202, > +}; > + > +struct rm_cmd_sq_hdr { > + u16 opcode; > + u16 msg_size; > + u16 id; > + u16 reserved; > +} __packed; > + > +struct rm_cmd_cq_hdr { > + u16 id; > + u16 reserved; > +} __packed; > + > +struct rm_cmd_sq_bin { > + u64 address; > + u32 size; > + u32 reserved1; > + u32 reserved2; > + u32 reserved3; > + u64 reserved4; > +} __packed; > + > +struct rm_cmd_sq_log_page { > + u64 address; > + u32 size; > + u32 reserved1; > + u32 type; > + u32 reserved2; > +} __packed; > + > +struct rm_cmd_sq_ctrl { > + u32 status; > +} __packed; > + > +struct rm_cmd_sq_data { > + union { > + struct rm_cmd_sq_log_page page; > + struct rm_cmd_sq_bin bin; > + struct rm_cmd_sq_ctrl ctrl; > + }; > +} __packed; > + > +struct rm_cmd_cq_identify { > + u16 major; > + u16 minor; > + u32 reserved; > +} __packed; > + > +struct rm_cmd_cq_log_page { > + u32 len; > + u32 reserved; > +} __packed; > + > +struct rm_cmd_cq_control { > + u16 status; > + u16 reserved1; > + u32 reserved2; > +} __packed; > + > +struct rm_cmd_cq_data { > + union { > + struct rm_cmd_cq_identify identify; > + struct rm_cmd_cq_log_page page; > + struct rm_cmd_cq_control ctrl; > + u32 reserved[2]; > + }; > + u32 rcode; > +} __packed; > + > +struct rm_cmd_sq_msg { > + struct rm_cmd_sq_hdr hdr; > + struct rm_cmd_sq_data data; > +} __packed; > + > +struct rm_cmd_cq_msg { > + struct rm_cmd_cq_hdr hdr; > + struct rm_cmd_cq_data data; > +} __packed; > + > +struct rm_cmd { > + struct rm_device *rdev; > + struct list_head list; > + struct completion executed; > + struct rm_cmd_sq_msg sq_msg; > + struct rm_cmd_cq_msg cq_msg; > + enum rm_queue_opcode opcode; > + void *buffer; > + ssize_t size; > +}; > + > +enum rm_queue_type { > + RM_QUEUE_SQ, > + RM_QUEUE_CQ > +}; > + > +enum rm_cmd_log_page_type { > + RM_CMD_LOG_PAGE_AXI_TRIP_STATUS = 0x0, > + RM_CMD_LOG_PAGE_FW_ID = 0xA, > +}; > + > +struct rm_queue { > + enum rm_queue_type type; > + u32 pidx; > + u32 cidx; > + u32 offset; > + u32 data_offset; > + u32 data_size; > + struct semaphore data_lock; > +}; > + > +struct rm_queue_header { > + u32 magic; > + u32 version; > + u32 size; > + u32 sq_off; > + u32 sq_slot_size; > + u32 cq_off; > + u32 sq_cidx; > + u32 cq_cidx; > +}; > + > +struct rm_header { > + u32 magic; > + u32 queue_base; > + u32 queue_size; > + u32 status_off; > + u32 status_len; > + u32 log_index; > + u32 log_off; > + u32 log_size; > + u32 data_start; > + u32 data_end; > +}; > + > +struct rm_device { > + struct vmgmt_device *vdev; > + struct regmap *shmem_regmap; > + struct regmap *io_regmap; > + > + struct rm_header rm_metadata; > + u32 queue_buffer_start; > + u32 queue_buffer_size; > + u32 queue_base; > + > + /* Lock to queue access */ > + struct mutex queue; > + struct rm_queue sq; > + struct rm_queue cq; > + u32 queue_size; > + > + struct timer_list msg_timer; > + struct work_struct msg_monitor; > + struct timer_list health_timer; > + struct work_struct health_monitor; > + struct list_head submitted_cmds; > + > + int firewall_tripped; > +}; > + > +int rm_queue_create_cmd(struct rm_device *rdev, enum rm_queue_opcode opcode, > + struct rm_cmd **cmd_ptr); > +void rm_queue_destory_cmd(struct rm_cmd *cmd); > + > +int rm_queue_data_init(struct rm_cmd *cmd, const char *buffer, ssize_t size); > +void rm_queue_data_fini(struct rm_cmd *cmd); > + > +int rm_queue_copy_response(struct rm_cmd *cmd, void *buffer, ssize_t len); > + > +int rm_boot_apu(struct rm_device *rdev); > + > +#endif /* __VMGMT_RM_H */ > diff --git a/drivers/fpga/amd/vmgmt.c b/drivers/fpga/amd/vmgmt.c > index b72eff9e8bc0..198213a13c7d 100644 > --- a/drivers/fpga/amd/vmgmt.c > +++ b/drivers/fpga/amd/vmgmt.c > @@ -21,6 +21,8 @@ > > #include "vmgmt.h" > #include "vmgmt-comms.h" > +#include "vmgmt-rm.h" > +#include "vmgmt-rm-queue.h" > > #define DRV_NAME "amd-vmgmt" > #define CLASS_NAME DRV_NAME > @@ -43,6 +45,61 @@ static inline struct vmgmt_device *vmgmt_inode_to_vdev(struct inode *inode) > return (struct vmgmt_device *)container_of(inode->i_cdev, struct vmgmt_device, cdev); > } > > +static int vmgmt_fpga_write_init(struct fpga_manager *mgr, > + struct fpga_image_info *info, const char *buf, > + size_t count) > +{ > + struct fpga_device *fdev = mgr->priv; > + struct fw_tnx *tnx = &fdev->fw; > + int ret; > + > + ret = rm_queue_create_cmd(fdev->vdev->rdev, tnx->opcode, &tnx->cmd); > + if (ret) { > + fdev->state = FPGA_MGR_STATE_WRITE_INIT_ERR; > + return ret; > + } > + > + fdev->state = FPGA_MGR_STATE_WRITE_INIT; > + return ret; > +} > + > +static int vmgmt_fpga_write(struct fpga_manager *mgr, const char *buf, > + size_t count) > +{ > + struct fpga_device *fdev = mgr->priv; > + int ret; > + > + ret = rm_queue_data_init(fdev->fw.cmd, buf, count); > + if (ret) { > + fdev->state = FPGA_MGR_STATE_WRITE_ERR; > + rm_queue_destory_cmd(fdev->fw.cmd); > + return ret; > + } > + > + fdev->state = FPGA_MGR_STATE_WRITE; > + return ret; > +} > + > +static int vmgmt_fpga_write_complete(struct fpga_manager *mgr, > + struct fpga_image_info *info) > +{ > + struct fpga_device *fdev = mgr->priv; > + int ret; > + > + ret = rm_queue_send_cmd(fdev->fw.cmd, RM_CMD_WAIT_DOWNLOAD_TIMEOUT); > + if (ret) { > + fdev->state = FPGA_MGR_STATE_WRITE_COMPLETE_ERR; > + vmgmt_err(fdev->vdev, "Send cmd failed:%d, cid:%d", ret, fdev->fw.id); > + } else { > + fdev->state = FPGA_MGR_STATE_WRITE_COMPLETE; > + } > + > + rm_queue_data_fini(fdev->fw.cmd); > + rm_queue_destory_cmd(fdev->fw.cmd); > + memset(&fdev->fw, 0, sizeof(fdev->fw)); > + return ret; > +} > + > static enum fpga_mgr_states vmgmt_fpga_state(struct fpga_manager *mgr) > { > struct fpga_device *fdev = mgr->priv; > @@ -51,6 +108,9 @@ static enum fpga_mgr_states vmgmt_fpga_state(struct fpga_manager *mgr) > } > > static const struct fpga_manager_ops vmgmt_fpga_ops = { > + .write_init = vmgmt_fpga_write_init, > + .write = vmgmt_fpga_write, > + .write_complete = vmgmt_fpga_write_complete, > .state = vmgmt_fpga_state, > }; > > @@ -96,6 +156,13 @@ static struct fpga_device *vmgmt_fpga_init(struct vmgmt_device *vdev) > return ERR_PTR(ret); > } > > + ret = vmgmt_rm_get_fw_id(vdev->rdev, &vdev->intf_uuid); > + if (ret) { > + vmgmt_warn(vdev, "Failed to get interface uuid"); > + ret = -EINVAL; > + goto unregister_fpga_mgr; > + } > + > /* create fgpa bridge, region for the base shell */ > fdev->bridge = fpga_bridge_register(dev, "AMD Versal FPGA Bridge", > &vmgmt_br_ops, fdev); > @@ -132,6 +199,149 @@ static struct fpga_device *vmgmt_fpga_init(struct vmgmt_device *vdev) > return ERR_PTR(ret); > } > > +static int vmgmt_region_program(struct fpga_region *region, const void *data) > +{ > + struct fpga_device *fdev = region->priv; > + struct vmgmt_device *vdev = fdev->vdev; > + const struct axlf *xclbin = data; > + struct fpga_image_info *info; > + int ret; > + > + info = fpga_image_info_alloc(&vdev->pdev->dev); > + if (!info) > + return -ENOMEM; > + > + region->info = info; > + > + info->flags |= FPGA_MGR_PARTIAL_RECONFIG; > + info->count = xclbin->header.length; > + info->buf = (char *)xclbin; > + > + ret = fpga_region_program_fpga(region); > + if (ret) { > + vmgmt_err(vdev, "Programming xclbin failed: %d", ret); > + goto exit; > + } > + > + /* free bridges to allow reprogram */ > + if (region->get_bridges) > + fpga_bridges_put(®ion->bridge_list); > + > +exit: > + fpga_image_info_free(info); > + return ret; > +} > + > +static int vmgmt_fpga_region_match(struct device *dev, const void *data) > +{ > + const struct vmgmt_fpga_region *arg = data; > + const struct fpga_region *match_region; > + struct fpga_device *fdev = arg->fdev; > + uuid_t compat_uuid; > + > + if (dev->parent != &fdev->vdev->pdev->dev) > + return false; > + > + match_region = to_fpga_region(dev); > + > + import_uuid(&compat_uuid, (const char *)match_region->compat_id); > + if (uuid_equal(&compat_uuid, arg->uuid)) { > + vmgmt_dbg(fdev->vdev, "Region match found"); > + return true; > + } > + > + vmgmt_err(fdev->vdev, "download uuid %pUb is not the same as device uuid %pUb", > + arg->uuid, &compat_uuid); > + return false; > +} > + > +static long vmgmt_ioctl(struct file *filep, unsigned int cmd, unsigned long arg) > +{ > + struct vmgmt_device *vdev = (struct vmgmt_device *)filep->private_data; > + struct vmgmt_fpga_region reg = { 0 }; > + struct fpga_region *region = NULL; > + struct axlf *axlf = NULL; > + void *data = NULL; > + size_t size = 0; > + int ret = 0; > + > + axlf = vmalloc(sizeof(*axlf)); > + if (!axlf) > + return -ENOMEM; > + > + ret = copy_from_user((void *)axlf, (void *)arg, sizeof(*axlf)); > + if (ret) { > + vmgmt_err(vdev, "Failed to copy axlf: %d", ret); > + ret = -EFAULT; > + goto exit; > + } > + > + ret = memcmp(axlf->magic, VERSAL_XCLBIN_MAGIC_ID, > + sizeof(VERSAL_XCLBIN_MAGIC_ID)); > + if (ret) { > + vmgmt_err(vdev, "unknown axlf magic %s", axlf->magic); > + ret = -EINVAL; > + goto exit; > + } > + > + /* axlf should never be over 1G and less than size of struct axlf */ > + size = axlf->header.length; > + if (size < sizeof(struct axlf) || size > 1024 * 1024 * 1024) { > + vmgmt_err(vdev, "axlf length %zu is invalid", size); > + ret = -EINVAL; > + goto exit; > + } > + > + data = vmalloc(size); > + if (!data) { > + ret = -ENOMEM; > + goto exit; > + } > + > + ret = copy_from_user((void *)data, (void *)arg, size); > + if (ret) { > + vmgmt_err(vdev, "Failed to copy data: %d", ret); > + ret = -EFAULT; > + goto exit; > + } > + > + switch (cmd) { > + case VERSAL_MGMT_LOAD_XCLBIN_IOCTL: > + vdev->fdev->fw.opcode = RM_QUEUE_OP_LOAD_XCLBIN; > + break; > + default: > + vmgmt_err(vdev, "Invalid IOCTL command: %d", cmd); > + ret = -EINVAL; > + goto exit; > + } > + > + reg.uuid = &axlf->header.rom_uuid; > + reg.fdev = vdev->fdev; > + > + region = fpga_region_class_find(NULL, ®, vmgmt_fpga_region_match); > + if (!region) { > + vmgmt_err(vdev, "Failed to find compatible region"); > + ret = -ENOENT; > + goto exit; > + } > + > + ret = vmgmt_region_program(region, data); > + if (ret) { > + vmgmt_err(vdev, "Failed to program region"); > + goto exit; > + } > + > + vmgmt_info(vdev, "Downloaded axlf %pUb of size %zu Bytes", > + &axlf->header.uuid, size); > + uuid_copy(&vdev->xclbin_uuid, &axlf->header.uuid); > + > +exit: > + vfree(data); > + vfree(axlf); > + > + return ret; > +} > + > static int vmgmt_open(struct inode *inode, struct file *filep) > { > struct vmgmt_device *vdev = vmgmt_inode_to_vdev(inode); > @@ -155,6 +365,7 @@ static const struct file_operations vmgmt_fops = { > .owner = THIS_MODULE, > .open = vmgmt_open, > .release = vmgmt_release, > + .unlocked_ioctl = vmgmt_ioctl, > }; > > static void vmgmt_chrdev_destroy(struct vmgmt_device *vdev) > @@ -201,6 +412,69 @@ static int vmgmt_chrdev_create(struct vmgmt_device *vdev) > return 0; > } > > +static enum fw_upload_err vmgmt_fw_prepare(struct fw_upload *fw_upload, > + const u8 *data, u32 size) > +{ > + struct firmware_device *fwdev = fw_upload->dd_handle; > + struct axlf *xsabin = (struct axlf *)data; > + int ret; > + > + ret = memcmp(xsabin->magic, VERSAL_XCLBIN_MAGIC_ID, > + sizeof(VERSAL_XCLBIN_MAGIC_ID)); > + if (ret) { > + vmgmt_err(fwdev->vdev, "Invalid device firmware"); > + return FW_UPLOAD_ERR_INVALID_SIZE; > + } > + > + /* Firmware size should never be over 1G and less than size of struct axlf */ > + if (!size || size != xsabin->header.length || size < sizeof(*xsabin) || > + size > 1024 * 1024 * 1024) { > + vmgmt_err(fwdev->vdev, "Invalid device firmware size"); > + return FW_UPLOAD_ERR_INVALID_SIZE; > + } > + > + ret = rm_queue_create_cmd(fwdev->vdev->rdev, RM_QUEUE_OP_LOAD_FW, > + &fwdev->cmd); > + if (ret) > + return FW_UPLOAD_ERR_RW_ERROR; > + > + uuid_copy(&fwdev->uuid, &xsabin->header.uuid); > + return FW_UPLOAD_ERR_NONE; > +} > + > +static enum fw_upload_err vmgmt_fw_write(struct fw_upload *fw_upload, > + const u8 *data, u32 offset, u32 size, > + u32 *written) > +{ > + struct firmware_device *fwdev = fw_upload->dd_handle; > + int ret; > + > + ret = rm_queue_data_init(fwdev->cmd, data, size); > + if (ret) > + return FW_UPLOAD_ERR_RW_ERROR; > + > + *written = size; > + return FW_UPLOAD_ERR_NONE; > +} > + > +static enum fw_upload_err vmgmt_fw_poll_complete(struct fw_upload *fw_upload) > +{ > + struct firmware_device *fwdev = fw_upload->dd_handle; > + int ret; > + > + vmgmt_info(fwdev->vdev, "Programming device firmware: %pUb", &fwdev->uuid); > + > + ret = rm_queue_send_cmd(fwdev->cmd, RM_CMD_WAIT_DOWNLOAD_TIMEOUT); > + if (ret) { > + vmgmt_err(fwdev->vdev, "Send cmd failedi:%d, cid %d", ret, fwdev->id); > + return FW_UPLOAD_ERR_HW_ERROR; > + } > + Basically I didn't see any difference on HW operations between FPGA reprogram & firmware loading. So why use different SW interfaces? My idea is FPGA reprogramming is not just for image loading, but also device re-enumeration. As I mentioned before, programing the fpga region without notifying the impacted driver makes kernel crash. I see there is an effort to introduce a unified FPGA reprograming interface that should address the concerns. I think we should stop adding vendor interfaces and make effort on the unified one. https://lore.kernel.org/linux-fpga/20240726063819.2274324-1-nava.kishore.manne@amd.com/ > + vmgmt_info(fwdev->vdev, "Successfully programmed device firmware: %pUb", > + &fwdev->uuid); > + return FW_UPLOAD_ERR_NONE; > +} > + > static void vmgmt_fw_cancel(struct fw_upload *fw_upload) > { > struct firmware_device *fwdev = fw_upload->dd_handle; > @@ -208,8 +482,26 @@ static void vmgmt_fw_cancel(struct fw_upload *fw_upload) > vmgmt_warn(fwdev->vdev, "canceled"); Nothing to do? I'll stop here, please better organize the patches next time submitting to make reviewers easier, even if you know it is an RFC. Thanks, Yilun
[AMD Official Use Only - AMD Internal Distribution Only] Hi Yilun, Thanks for reviewing the patches for us. I will fix those with detailed info with next V2 patches. My cover latter was sent with the other patches, but the second patch failed due to "D M K, Karthik" is not a valid email address, I just changed his name to "DMK"
diff --git a/drivers/fpga/amd/Makefile b/drivers/fpga/amd/Makefile index 3e4c6dd3b787..97cfff6be204 100644 --- a/drivers/fpga/amd/Makefile +++ b/drivers/fpga/amd/Makefile @@ -2,5 +2,7 @@ obj-$(CONFIG_AMD_VERSAL_MGMT) += amd-vmgmt.o -amd-vmgmt-$(CONFIG_AMD_VERSAL_MGMT) := vmgmt.o \ - vmgmt-comms.o +amd-vmgmt-$(CONFIG_AMD_VERSAL_MGMT) := vmgmt.o \ + vmgmt-comms.o \ + vmgmt-rm.o \ + vmgmt-rm-queue.o diff --git a/drivers/fpga/amd/vmgmt-rm-queue.c b/drivers/fpga/amd/vmgmt-rm-queue.c new file mode 100644 index 000000000000..fe805373ea32 --- /dev/null +++ b/drivers/fpga/amd/vmgmt-rm-queue.c @@ -0,0 +1,38 @@ +// 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/completion.h> +#include <linux/err.h> +#include <linux/firmware.h> +#include <linux/idr.h> +#include <linux/jiffies.h> +#include <linux/list.h> +#include <linux/mutex.h> +#include <linux/pci.h> +#include <linux/semaphore.h> +#include <linux/timer.h> +#include <linux/uuid.h> +#include <linux/workqueue.h> + +#include "vmgmt.h" +#include "vmgmt-rm.h" +#include "vmgmt-rm-queue.h" + +int rm_queue_send_cmd(struct rm_cmd *cmd, unsigned long timeout) +{ + return 0; +} + +void rm_queue_fini(struct rm_device *rdev) +{ +} + +int rm_queue_init(struct rm_device *rdev) +{ + return 0; +} diff --git a/drivers/fpga/amd/vmgmt-rm-queue.h b/drivers/fpga/amd/vmgmt-rm-queue.h new file mode 100644 index 000000000000..6fd0e0026a13 --- /dev/null +++ b/drivers/fpga/amd/vmgmt-rm-queue.h @@ -0,0 +1,15 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Driver for Versal PCIe device + * + * Copyright (C) 2024 Advanced Micro Devices, Inc. All rights reserved. + */ + +#ifndef __VMGMT_RM_QUEUE_H +#define __VMGMT_RM_QUEUE_H + +int rm_queue_init(struct rm_device *rdev); +void rm_queue_fini(struct rm_device *rdev); +int rm_queue_send_cmd(struct rm_cmd *cmd, unsigned long timeout); + +#endif /* __VMGMT_RM_QUEUE_H */ diff --git a/drivers/fpga/amd/vmgmt-rm.c b/drivers/fpga/amd/vmgmt-rm.c new file mode 100644 index 000000000000..856d5af52c8d --- /dev/null +++ b/drivers/fpga/amd/vmgmt-rm.c @@ -0,0 +1,543 @@ +// 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/completion.h> +#include <linux/err.h> +#include <linux/firmware.h> +#include <linux/idr.h> +#include <linux/jiffies.h> +#include <linux/list.h> +#include <linux/mutex.h> +#include <linux/pci.h> +#include <linux/semaphore.h> +#include <linux/timer.h> +#include <linux/uuid.h> +#include <linux/workqueue.h> + +#include "vmgmt.h" +#include "vmgmt-rm.h" +#include "vmgmt-rm-queue.h" + +static DEFINE_IDA(rm_cmd_ids); + +static const struct regmap_config rm_shmem_regmap_config = { + .name = "rm_shmem_config", + .reg_bits = 32, + .reg_stride = 4, + .val_bits = 32, +}; + +static const struct regmap_config rm_io_regmap_config = { + .name = "rm_io_config", + .reg_bits = 32, + .reg_stride = 4, + .val_bits = 32, +}; + +static void rm_uninstall_health_monitor(struct rm_device *rdev); + +static inline struct rm_device *to_rdev_health_monitor(struct work_struct *w) +{ + return container_of(w, struct rm_device, health_monitor); +} + +static inline struct rm_device *to_rdev_health_timer(struct timer_list *t) +{ + return container_of(t, struct rm_device, health_timer); +} + +static inline int rm_shmem_read(struct rm_device *rdev, u32 offset, u32 *value) +{ + return regmap_read(rdev->shmem_regmap, offset, value); +} + +static inline int rm_shmem_bulk_read(struct rm_device *rdev, u32 offset, + u32 *value, u32 size) +{ + return regmap_bulk_read(rdev->shmem_regmap, offset, value, + DIV_ROUND_UP(size, 4)); +} + +static inline int rm_shmem_bulk_write(struct rm_device *rdev, u32 offset, + u32 *value, u32 size) +{ + return regmap_bulk_write(rdev->shmem_regmap, offset, value, + DIV_ROUND_UP(size, 4)); +} + +void rm_queue_destory_cmd(struct rm_cmd *cmd) +{ + ida_free(&rm_cmd_ids, cmd->sq_msg.hdr.id); + kfree(cmd); +} + +int rm_queue_copy_response(struct rm_cmd *cmd, void *buffer, ssize_t len) +{ + struct rm_cmd_cq_log_page *result = &cmd->cq_msg.data.page; + u64 off = cmd->sq_msg.data.page.address; + + if (!result->len || len < result->len) { + vmgmt_err(cmd->rdev->vdev, "Invalid response or buffer size"); + return -EINVAL; + } + + return rm_shmem_bulk_read(cmd->rdev, off, (u32 *)buffer, result->len); +} + +static void rm_queue_payload_fini(struct rm_cmd *cmd) +{ + up(&cmd->rdev->cq.data_lock); +} + +static int rm_queue_payload_init(struct rm_cmd *cmd, + enum rm_cmd_log_page_type type) +{ + struct rm_device *rdev = cmd->rdev; + int ret; + + ret = down_interruptible(&rdev->cq.data_lock); + if (ret) + return ret; + + cmd->sq_msg.data.page.address = rdev->cq.data_offset; + cmd->sq_msg.data.page.size = rdev->cq.data_size; + cmd->sq_msg.data.page.reserved1 = 0; + cmd->sq_msg.data.page.type = FIELD_PREP(RM_CMD_LOG_PAGE_TYPE_MASK, + type); + return 0; +} + +void rm_queue_data_fini(struct rm_cmd *cmd) +{ + up(&cmd->rdev->sq.data_lock); +} + +int rm_queue_data_init(struct rm_cmd *cmd, const char *buffer, ssize_t size) +{ + struct rm_device *rdev = cmd->rdev; + int ret; + + if (!size || size > rdev->sq.data_size) { + vmgmt_err(rdev->vdev, "Unsupported file size"); + return -ENOMEM; + } + + ret = down_interruptible(&rdev->sq.data_lock); + if (ret) + return ret; + + ret = rm_shmem_bulk_write(cmd->rdev, rdev->sq.data_offset, + (u32 *)buffer, size); + if (ret) { + vmgmt_err(rdev->vdev, "Failed to copy binary to SQ buffer"); + up(&cmd->rdev->sq.data_lock); + return ret; + } + + cmd->sq_msg.data.bin.address = rdev->sq.data_offset; + cmd->sq_msg.data.bin.size = size; + return 0; +} + +int rm_queue_create_cmd(struct rm_device *rdev, enum rm_queue_opcode opcode, + struct rm_cmd **cmd_ptr) +{ + struct rm_cmd *cmd = NULL; + int ret, id; + u16 size; + + if (rdev->firewall_tripped) + return -ENODEV; + + cmd = kzalloc(sizeof(*cmd), GFP_KERNEL); + if (!cmd) + return -ENOMEM; + cmd->rdev = rdev; + + switch (opcode) { + case RM_QUEUE_OP_LOAD_XCLBIN: + fallthrough; + case RM_QUEUE_OP_LOAD_FW: + fallthrough; + case RM_QUEUE_OP_LOAD_APU_FW: + size = sizeof(struct rm_cmd_sq_bin); + break; + case RM_QUEUE_OP_GET_LOG_PAGE: + size = sizeof(struct rm_cmd_sq_log_page); + break; + case RM_QUEUE_OP_IDENTIFY: + size = 0; + break; + case RM_QUEUE_OP_VMR_CONTROL: + size = sizeof(struct rm_cmd_sq_ctrl); + break; + default: + vmgmt_err(rdev->vdev, "Invalid cmd opcode %d", opcode); + ret = -EINVAL; + goto error; + }; + + cmd->opcode = opcode; + cmd->sq_msg.hdr.opcode = FIELD_PREP(RM_CMD_SQ_HDR_OPS_MSK, opcode); + cmd->sq_msg.hdr.msg_size = FIELD_PREP(RM_CMD_SQ_HDR_SIZE_MSK, size); + + id = ida_alloc_range(&rm_cmd_ids, RM_CMD_ID_MIN, RM_CMD_ID_MAX, GFP_KERNEL); + if (id < 0) { + vmgmt_err(rdev->vdev, "Failed to alloc cmd ID: %d", id); + ret = id; + goto error; + } + cmd->sq_msg.hdr.id = id; + + init_completion(&cmd->executed); + + *cmd_ptr = cmd; + return 0; +error: + kfree(cmd); + return ret; +} + +static int rm_queue_verify(struct rm_device *rdev) +{ + struct vmgmt_device *vdev = rdev->vdev; + struct rm_cmd_cq_identify *result; + struct rm_cmd *cmd; + u32 major, minor; + int ret; + + ret = rm_queue_create_cmd(rdev, RM_QUEUE_OP_IDENTIFY, &cmd); + if (ret) + return ret; + + ret = rm_queue_send_cmd(cmd, RM_CMD_WAIT_CONFIG_TIMEOUT); + if (ret) + goto error; + + result = &cmd->cq_msg.data.identify; + major = result->major; + minor = result->minor; + vmgmt_dbg(vdev, "VMR version %d.%d", major, minor); + if (!major) { + vmgmt_err(vdev, "VMR version is unsupported"); + ret = -EOPNOTSUPP; + } + +error: + rm_queue_destory_cmd(cmd); + return ret; +} + +static int rm_check_apu_status(struct rm_device *rdev, bool *status) +{ + struct rm_cmd_cq_control *result; + struct rm_cmd *cmd; + int ret; + + ret = rm_queue_create_cmd(rdev, RM_QUEUE_OP_VMR_CONTROL, &cmd); + if (ret) + return ret; + + ret = rm_queue_send_cmd(cmd, RM_CMD_WAIT_CONFIG_TIMEOUT); + if (ret) + goto error; + + result = &cmd->cq_msg.data.ctrl; + *status = FIELD_GET(RM_CMD_VMR_CONTROL_PS_MASK, result->status); + + rm_queue_destory_cmd(cmd); + return 0; + +error: + rm_queue_destory_cmd(cmd); + return ret; +} + +static int rm_download_apu_fw(struct rm_device *rdev, char *data, ssize_t size) +{ + struct rm_cmd *cmd; + int ret; + + ret = rm_queue_create_cmd(rdev, RM_QUEUE_OP_LOAD_APU_FW, &cmd); + if (ret) + return ret; + + ret = rm_queue_data_init(cmd, data, size); + if (ret) + goto done; + + ret = rm_queue_send_cmd(cmd, RM_CMD_WAIT_DOWNLOAD_TIMEOUT); + +done: + rm_queue_destory_cmd(cmd); + return ret; +} + +int rm_boot_apu(struct rm_device *rdev) +{ + char *bin = "xilinx/xrt-versal-apu.xsabin"; + const struct firmware *fw = NULL; + bool status; + int ret; + + ret = rm_check_apu_status(rdev, &status); + if (ret) { + vmgmt_err(rdev->vdev, "Failed to get APU status"); + return ret; + } + + if (status) { + vmgmt_dbg(rdev->vdev, "APU online. Skipping APU FW download"); + return 0; + } + + ret = request_firmware(&fw, bin, &rdev->vdev->pdev->dev); + if (ret) { + vmgmt_warn(rdev->vdev, "Request APU FW %s failed %d", bin, ret); + return ret; + } + + vmgmt_dbg(rdev->vdev, "Starting... APU FW download"); + ret = rm_download_apu_fw(rdev, (char *)fw->data, fw->size); + vmgmt_dbg(rdev->vdev, "Finished... APU FW download %d", ret); + + if (ret) + vmgmt_err(rdev->vdev, "Failed to download APU FW, ret:%d", ret); + + release_firmware(fw); + + return ret; +} + +static void rm_check_health(struct work_struct *w) +{ + struct rm_device *rdev = to_rdev_health_monitor(w); + ssize_t len = PAGE_SIZE; + char *buffer = NULL; + struct rm_cmd *cmd; + int ret; + + buffer = vzalloc(len); + if (!buffer) + return; + + ret = rm_queue_create_cmd(rdev, RM_QUEUE_OP_GET_LOG_PAGE, &cmd); + if (ret) + return; + + ret = rm_queue_payload_init(cmd, RM_CMD_LOG_PAGE_AXI_TRIP_STATUS); + if (ret) + goto error; + + ret = rm_queue_send_cmd(cmd, RM_CMD_WAIT_CONFIG_TIMEOUT); + if (ret == -ETIME || ret == -EINVAL) + goto payload_fini; + + if (cmd->cq_msg.data.page.len) { + ret = rm_queue_copy_response(cmd, buffer, len); + if (ret) + goto payload_fini; + + vmgmt_err(rdev->vdev, "%s", buffer); + rdev->firewall_tripped = 1; + } + + vfree(buffer); + + rm_queue_payload_fini(cmd); + rm_queue_destory_cmd(cmd); + + return; + +payload_fini: + rm_queue_payload_fini(cmd); +error: + rm_queue_destory_cmd(cmd); + vfree(buffer); +} + +static void rm_sched_health_check(struct timer_list *t) +{ + struct rm_device *rdev = to_rdev_health_timer(t); + + if (rdev->firewall_tripped) { + vmgmt_err(rdev->vdev, "Firewall tripped, health check paused. Please reset card"); + return; + } + /* Schedule a work in the general workqueue */ + schedule_work(&rdev->health_monitor); + /* Periodic timer */ + mod_timer(&rdev->health_timer, jiffies + RM_HEALTH_CHECK_TIMER); +} + +static void rm_uninstall_health_monitor(struct rm_device *rdev) +{ + del_timer_sync(&rdev->health_timer); + cancel_work_sync(&rdev->health_monitor); +} + +static void rm_install_health_monitor(struct rm_device *rdev) +{ + INIT_WORK(&rdev->health_monitor, &rm_check_health); + timer_setup(&rdev->health_timer, &rm_sched_health_check, 0); + mod_timer(&rdev->health_timer, jiffies + RM_HEALTH_CHECK_TIMER); +} + +void vmgmt_rm_fini(struct rm_device *rdev) +{ + rm_uninstall_health_monitor(rdev); + rm_queue_fini(rdev); +} + +struct rm_device *vmgmt_rm_init(struct vmgmt_device *vdev) +{ + struct rm_header *header; + struct rm_device *rdev; + u32 status; + int ret; + + rdev = devm_kzalloc(&vdev->pdev->dev, sizeof(*rdev), GFP_KERNEL); + if (!rdev) + return ERR_PTR(-ENOMEM); + + rdev->vdev = vdev; + header = &rdev->rm_metadata; + + rdev->shmem_regmap = devm_regmap_init_mmio(&vdev->pdev->dev, + vdev->tbl + RM_PCI_SHMEM_BAR_OFF, + &rm_shmem_regmap_config); + if (IS_ERR(rdev->shmem_regmap)) { + vmgmt_err(vdev, "Failed to init RM shared memory regmap"); + return ERR_CAST(rdev->shmem_regmap); + } + + ret = rm_shmem_bulk_read(rdev, RM_HDR_OFF, (u32 *)header, + sizeof(*header)); + if (ret) { + vmgmt_err(vdev, "Failed to read RM shared mem, ret %d", ret); + ret = -ENODEV; + goto err; + } + + if (header->magic != RM_HDR_MAGIC_NUM) { + vmgmt_err(vdev, "Invalid RM header 0x%x", header->magic); + ret = -ENODEV; + goto err; + } + + ret = rm_shmem_read(rdev, header->status_off, &status); + if (ret) { + vmgmt_err(vdev, "Failed to read RM shared mem, ret %d", ret); + ret = -ENODEV; + goto err; + } + + if (!status) { + vmgmt_err(vdev, "RM status %d is not ready", status); + ret = -ENODEV; + goto err; + } + + rdev->queue_buffer_size = header->data_end - header->data_start + 1; + rdev->queue_buffer_start = header->data_start; + rdev->queue_base = header->queue_base; + + rdev->io_regmap = devm_regmap_init_mmio(&vdev->pdev->dev, + vdev->tbl + RM_PCI_IO_BAR_OFF, + &rm_io_regmap_config); + if (IS_ERR(rdev->io_regmap)) { + vmgmt_err(vdev, "Failed to init RM IO regmap"); + ret = PTR_ERR(rdev->io_regmap); + goto err; + } + + ret = rm_queue_init(rdev); + if (ret) { + vmgmt_err(vdev, "Failed to init cmd queue, ret %d", ret); + ret = -ENODEV; + goto err; + } + + ret = rm_queue_verify(rdev); + if (ret) { + vmgmt_err(vdev, "Failed to verify cmd queue, ret %d", ret); + ret = -ENODEV; + goto queue_fini; + } + + ret = rm_boot_apu(rdev); + if (ret) { + vmgmt_err(vdev, "Failed to bringup APU, ret %d", ret); + ret = -ENODEV; + goto queue_fini; + } + + rm_install_health_monitor(rdev); + + return rdev; +queue_fini: + rm_queue_fini(rdev); +err: + return ERR_PTR(ret); +} + +int vmgmt_rm_get_fw_id(struct rm_device *rdev, uuid_t *uuid) +{ + char str[UUID_STRING_LEN]; + ssize_t len = PAGE_SIZE; + char *buffer = NULL; + struct rm_cmd *cmd; + u8 i, j; + int ret; + + buffer = vmalloc(len); + if (!buffer) + return -ENOMEM; + + memset(buffer, 0, len); + + ret = rm_queue_create_cmd(rdev, RM_QUEUE_OP_GET_LOG_PAGE, &cmd); + if (ret) + return ret; + + ret = rm_queue_payload_init(cmd, RM_CMD_LOG_PAGE_FW_ID); + if (ret) + goto error; + + ret = rm_queue_send_cmd(cmd, RM_CMD_WAIT_CONFIG_TIMEOUT); + if (ret) + goto payload; + + ret = rm_queue_copy_response(cmd, buffer, len); + if (ret) + goto payload; + + /* parse uuid into a valid uuid string format */ + for (i = 0, j = 0; i < strlen(buffer); i++) { + str[j++] = buffer[i]; + if (j == 8 || j == 13 || j == 18 || j == 23) + str[j++] = '-'; + } + + uuid_parse(str, uuid); + vmgmt_dbg(rdev->vdev, "Interface uuid %pU", uuid); + + vfree(buffer); + + rm_queue_payload_fini(cmd); + rm_queue_destory_cmd(cmd); + + return 0; + +payload: + rm_queue_payload_fini(cmd); +error: + rm_queue_destory_cmd(cmd); + vfree(buffer); + return ret; +} diff --git a/drivers/fpga/amd/vmgmt-rm.h b/drivers/fpga/amd/vmgmt-rm.h new file mode 100644 index 000000000000..a74f93cefbe8 --- /dev/null +++ b/drivers/fpga/amd/vmgmt-rm.h @@ -0,0 +1,222 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Driver for Versal PCIe device + * + * Copyright (C) 2024 Advanced Micro Devices, Inc. All rights reserved. + */ + +#ifndef __VMGMT_RM_H +#define __VMGMT_RM_H + +#define RM_HDR_OFF 0x0 +#define RM_HDR_MAGIC_NUM 0x564D5230 +#define RM_QUEUE_HDR_MAGIC_NUM 0x5847513F +#define RM_PCI_IO_BAR_OFF 0x2010000 +#define RM_PCI_IO_SIZE 0x1000 +#define RM_PCI_SHMEM_BAR_OFF 0x8000000 +#define RM_PCI_SHMEM_SIZE 0x8000000 /* 128 MB */ +#define RM_PCI_SHMEM_HDR_SIZE 0x28 + +#define RM_QUEUE_HDR_MAGIC_NUM_OFF 0x0 +#define RM_IO_SQ_PIDX_OFF 0x0 +#define RM_IO_CQ_PIDX_OFF 0x100 + +#define RM_CMD_ID_MIN 1 +#define RM_CMD_ID_MAX (BIT(17) - 1) +#define RM_CMD_SQ_HDR_OPS_MSK GENMASK(15, 0) +#define RM_CMD_SQ_HDR_SIZE_MSK GENMASK(14, 0) +#define RM_CMD_SQ_SLOT_SIZE 512 +#define RM_CMD_CQ_SLOT_SIZE 16 +#define RM_CMD_CQ_BUFFER_SIZE (1024 * 1024) +#define RM_CMD_CQ_BUFFER_OFFSET 0x0 +#define RM_CMD_LOG_PAGE_TYPE_MASK GENMASK(15, 0) +#define RM_CMD_VMR_CONTROL_MSK GENMASK(10, 8) +#define RM_CMD_VMR_CONTROL_PS_MASK BIT(9) + +#define RM_CMD_WAIT_CONFIG_TIMEOUT msecs_to_jiffies(10 * 1000) +#define RM_CMD_WAIT_DOWNLOAD_TIMEOUT msecs_to_jiffies(300 * 1000) + +#define RM_COMPLETION_TIMER (HZ / 10) +#define RM_HEALTH_CHECK_TIMER (HZ) + +#define RM_INVALID_SLOT 0 + +enum rm_queue_opcode { + RM_QUEUE_OP_LOAD_XCLBIN = 0x0, + RM_QUEUE_OP_GET_LOG_PAGE = 0x8, + RM_QUEUE_OP_LOAD_FW = 0xA, + RM_QUEUE_OP_LOAD_APU_FW = 0xD, + RM_QUEUE_OP_VMR_CONTROL = 0xE, + RM_QUEUE_OP_IDENTIFY = 0x202, +}; + +struct rm_cmd_sq_hdr { + u16 opcode; + u16 msg_size; + u16 id; + u16 reserved; +} __packed; + +struct rm_cmd_cq_hdr { + u16 id; + u16 reserved; +} __packed; + +struct rm_cmd_sq_bin { + u64 address; + u32 size; + u32 reserved1; + u32 reserved2; + u32 reserved3; + u64 reserved4; +} __packed; + +struct rm_cmd_sq_log_page { + u64 address; + u32 size; + u32 reserved1; + u32 type; + u32 reserved2; +} __packed; + +struct rm_cmd_sq_ctrl { + u32 status; +} __packed; + +struct rm_cmd_sq_data { + union { + struct rm_cmd_sq_log_page page; + struct rm_cmd_sq_bin bin; + struct rm_cmd_sq_ctrl ctrl; + }; +} __packed; + +struct rm_cmd_cq_identify { + u16 major; + u16 minor; + u32 reserved; +} __packed; + +struct rm_cmd_cq_log_page { + u32 len; + u32 reserved; +} __packed; + +struct rm_cmd_cq_control { + u16 status; + u16 reserved1; + u32 reserved2; +} __packed; + +struct rm_cmd_cq_data { + union { + struct rm_cmd_cq_identify identify; + struct rm_cmd_cq_log_page page; + struct rm_cmd_cq_control ctrl; + u32 reserved[2]; + }; + u32 rcode; +} __packed; + +struct rm_cmd_sq_msg { + struct rm_cmd_sq_hdr hdr; + struct rm_cmd_sq_data data; +} __packed; + +struct rm_cmd_cq_msg { + struct rm_cmd_cq_hdr hdr; + struct rm_cmd_cq_data data; +} __packed; + +struct rm_cmd { + struct rm_device *rdev; + struct list_head list; + struct completion executed; + struct rm_cmd_sq_msg sq_msg; + struct rm_cmd_cq_msg cq_msg; + enum rm_queue_opcode opcode; + void *buffer; + ssize_t size; +}; + +enum rm_queue_type { + RM_QUEUE_SQ, + RM_QUEUE_CQ +}; + +enum rm_cmd_log_page_type { + RM_CMD_LOG_PAGE_AXI_TRIP_STATUS = 0x0, + RM_CMD_LOG_PAGE_FW_ID = 0xA, +}; + +struct rm_queue { + enum rm_queue_type type; + u32 pidx; + u32 cidx; + u32 offset; + u32 data_offset; + u32 data_size; + struct semaphore data_lock; +}; + +struct rm_queue_header { + u32 magic; + u32 version; + u32 size; + u32 sq_off; + u32 sq_slot_size; + u32 cq_off; + u32 sq_cidx; + u32 cq_cidx; +}; + +struct rm_header { + u32 magic; + u32 queue_base; + u32 queue_size; + u32 status_off; + u32 status_len; + u32 log_index; + u32 log_off; + u32 log_size; + u32 data_start; + u32 data_end; +}; + +struct rm_device { + struct vmgmt_device *vdev; + struct regmap *shmem_regmap; + struct regmap *io_regmap; + + struct rm_header rm_metadata; + u32 queue_buffer_start; + u32 queue_buffer_size; + u32 queue_base; + + /* Lock to queue access */ + struct mutex queue; + struct rm_queue sq; + struct rm_queue cq; + u32 queue_size; + + struct timer_list msg_timer; + struct work_struct msg_monitor; + struct timer_list health_timer; + struct work_struct health_monitor; + struct list_head submitted_cmds; + + int firewall_tripped; +}; + +int rm_queue_create_cmd(struct rm_device *rdev, enum rm_queue_opcode opcode, + struct rm_cmd **cmd_ptr); +void rm_queue_destory_cmd(struct rm_cmd *cmd); + +int rm_queue_data_init(struct rm_cmd *cmd, const char *buffer, ssize_t size); +void rm_queue_data_fini(struct rm_cmd *cmd); + +int rm_queue_copy_response(struct rm_cmd *cmd, void *buffer, ssize_t len); + +int rm_boot_apu(struct rm_device *rdev); + +#endif /* __VMGMT_RM_H */ diff --git a/drivers/fpga/amd/vmgmt.c b/drivers/fpga/amd/vmgmt.c index b72eff9e8bc0..198213a13c7d 100644 --- a/drivers/fpga/amd/vmgmt.c +++ b/drivers/fpga/amd/vmgmt.c @@ -21,6 +21,8 @@ #include "vmgmt.h" #include "vmgmt-comms.h" +#include "vmgmt-rm.h" +#include "vmgmt-rm-queue.h" #define DRV_NAME "amd-vmgmt" #define CLASS_NAME DRV_NAME @@ -43,6 +45,61 @@ static inline struct vmgmt_device *vmgmt_inode_to_vdev(struct inode *inode) return (struct vmgmt_device *)container_of(inode->i_cdev, struct vmgmt_device, cdev); } +static int vmgmt_fpga_write_init(struct fpga_manager *mgr, + struct fpga_image_info *info, const char *buf, + size_t count) +{ + struct fpga_device *fdev = mgr->priv; + struct fw_tnx *tnx = &fdev->fw; + int ret; + + ret = rm_queue_create_cmd(fdev->vdev->rdev, tnx->opcode, &tnx->cmd); + if (ret) { + fdev->state = FPGA_MGR_STATE_WRITE_INIT_ERR; + return ret; + } + + fdev->state = FPGA_MGR_STATE_WRITE_INIT; + return ret; +} + +static int vmgmt_fpga_write(struct fpga_manager *mgr, const char *buf, + size_t count) +{ + struct fpga_device *fdev = mgr->priv; + int ret; + + ret = rm_queue_data_init(fdev->fw.cmd, buf, count); + if (ret) { + fdev->state = FPGA_MGR_STATE_WRITE_ERR; + rm_queue_destory_cmd(fdev->fw.cmd); + return ret; + } + + fdev->state = FPGA_MGR_STATE_WRITE; + return ret; +} + +static int vmgmt_fpga_write_complete(struct fpga_manager *mgr, + struct fpga_image_info *info) +{ + struct fpga_device *fdev = mgr->priv; + int ret; + + ret = rm_queue_send_cmd(fdev->fw.cmd, RM_CMD_WAIT_DOWNLOAD_TIMEOUT); + if (ret) { + fdev->state = FPGA_MGR_STATE_WRITE_COMPLETE_ERR; + vmgmt_err(fdev->vdev, "Send cmd failed:%d, cid:%d", ret, fdev->fw.id); + } else { + fdev->state = FPGA_MGR_STATE_WRITE_COMPLETE; + } + + rm_queue_data_fini(fdev->fw.cmd); + rm_queue_destory_cmd(fdev->fw.cmd); + memset(&fdev->fw, 0, sizeof(fdev->fw)); + return ret; +} + static enum fpga_mgr_states vmgmt_fpga_state(struct fpga_manager *mgr) { struct fpga_device *fdev = mgr->priv; @@ -51,6 +108,9 @@ static enum fpga_mgr_states vmgmt_fpga_state(struct fpga_manager *mgr) } static const struct fpga_manager_ops vmgmt_fpga_ops = { + .write_init = vmgmt_fpga_write_init, + .write = vmgmt_fpga_write, + .write_complete = vmgmt_fpga_write_complete, .state = vmgmt_fpga_state, }; @@ -96,6 +156,13 @@ static struct fpga_device *vmgmt_fpga_init(struct vmgmt_device *vdev) return ERR_PTR(ret); } + ret = vmgmt_rm_get_fw_id(vdev->rdev, &vdev->intf_uuid); + if (ret) { + vmgmt_warn(vdev, "Failed to get interface uuid"); + ret = -EINVAL; + goto unregister_fpga_mgr; + } + /* create fgpa bridge, region for the base shell */ fdev->bridge = fpga_bridge_register(dev, "AMD Versal FPGA Bridge", &vmgmt_br_ops, fdev); @@ -132,6 +199,149 @@ static struct fpga_device *vmgmt_fpga_init(struct vmgmt_device *vdev) return ERR_PTR(ret); } +static int vmgmt_region_program(struct fpga_region *region, const void *data) +{ + struct fpga_device *fdev = region->priv; + struct vmgmt_device *vdev = fdev->vdev; + const struct axlf *xclbin = data; + struct fpga_image_info *info; + int ret; + + info = fpga_image_info_alloc(&vdev->pdev->dev); + if (!info) + return -ENOMEM; + + region->info = info; + + info->flags |= FPGA_MGR_PARTIAL_RECONFIG; + info->count = xclbin->header.length; + info->buf = (char *)xclbin; + + ret = fpga_region_program_fpga(region); + if (ret) { + vmgmt_err(vdev, "Programming xclbin failed: %d", ret); + goto exit; + } + + /* free bridges to allow reprogram */ + if (region->get_bridges) + fpga_bridges_put(®ion->bridge_list); + +exit: + fpga_image_info_free(info); + return ret; +} + +static int vmgmt_fpga_region_match(struct device *dev, const void *data) +{ + const struct vmgmt_fpga_region *arg = data; + const struct fpga_region *match_region; + struct fpga_device *fdev = arg->fdev; + uuid_t compat_uuid; + + if (dev->parent != &fdev->vdev->pdev->dev) + return false; + + match_region = to_fpga_region(dev); + + import_uuid(&compat_uuid, (const char *)match_region->compat_id); + if (uuid_equal(&compat_uuid, arg->uuid)) { + vmgmt_dbg(fdev->vdev, "Region match found"); + return true; + } + + vmgmt_err(fdev->vdev, "download uuid %pUb is not the same as device uuid %pUb", + arg->uuid, &compat_uuid); + return false; +} + +static long vmgmt_ioctl(struct file *filep, unsigned int cmd, unsigned long arg) +{ + struct vmgmt_device *vdev = (struct vmgmt_device *)filep->private_data; + struct vmgmt_fpga_region reg = { 0 }; + struct fpga_region *region = NULL; + struct axlf *axlf = NULL; + void *data = NULL; + size_t size = 0; + int ret = 0; + + axlf = vmalloc(sizeof(*axlf)); + if (!axlf) + return -ENOMEM; + + ret = copy_from_user((void *)axlf, (void *)arg, sizeof(*axlf)); + if (ret) { + vmgmt_err(vdev, "Failed to copy axlf: %d", ret); + ret = -EFAULT; + goto exit; + } + + ret = memcmp(axlf->magic, VERSAL_XCLBIN_MAGIC_ID, + sizeof(VERSAL_XCLBIN_MAGIC_ID)); + if (ret) { + vmgmt_err(vdev, "unknown axlf magic %s", axlf->magic); + ret = -EINVAL; + goto exit; + } + + /* axlf should never be over 1G and less than size of struct axlf */ + size = axlf->header.length; + if (size < sizeof(struct axlf) || size > 1024 * 1024 * 1024) { + vmgmt_err(vdev, "axlf length %zu is invalid", size); + ret = -EINVAL; + goto exit; + } + + data = vmalloc(size); + if (!data) { + ret = -ENOMEM; + goto exit; + } + + ret = copy_from_user((void *)data, (void *)arg, size); + if (ret) { + vmgmt_err(vdev, "Failed to copy data: %d", ret); + ret = -EFAULT; + goto exit; + } + + switch (cmd) { + case VERSAL_MGMT_LOAD_XCLBIN_IOCTL: + vdev->fdev->fw.opcode = RM_QUEUE_OP_LOAD_XCLBIN; + break; + default: + vmgmt_err(vdev, "Invalid IOCTL command: %d", cmd); + ret = -EINVAL; + goto exit; + } + + reg.uuid = &axlf->header.rom_uuid; + reg.fdev = vdev->fdev; + + region = fpga_region_class_find(NULL, ®, vmgmt_fpga_region_match); + if (!region) { + vmgmt_err(vdev, "Failed to find compatible region"); + ret = -ENOENT; + goto exit; + } + + ret = vmgmt_region_program(region, data); + if (ret) { + vmgmt_err(vdev, "Failed to program region"); + goto exit; + } + + vmgmt_info(vdev, "Downloaded axlf %pUb of size %zu Bytes", + &axlf->header.uuid, size); + uuid_copy(&vdev->xclbin_uuid, &axlf->header.uuid); + +exit: + vfree(data); + vfree(axlf); + + return ret; +} + static int vmgmt_open(struct inode *inode, struct file *filep) { struct vmgmt_device *vdev = vmgmt_inode_to_vdev(inode); @@ -155,6 +365,7 @@ static const struct file_operations vmgmt_fops = { .owner = THIS_MODULE, .open = vmgmt_open, .release = vmgmt_release, + .unlocked_ioctl = vmgmt_ioctl, }; static void vmgmt_chrdev_destroy(struct vmgmt_device *vdev) @@ -201,6 +412,69 @@ static int vmgmt_chrdev_create(struct vmgmt_device *vdev) return 0; } +static enum fw_upload_err vmgmt_fw_prepare(struct fw_upload *fw_upload, + const u8 *data, u32 size) +{ + struct firmware_device *fwdev = fw_upload->dd_handle; + struct axlf *xsabin = (struct axlf *)data; + int ret; + + ret = memcmp(xsabin->magic, VERSAL_XCLBIN_MAGIC_ID, + sizeof(VERSAL_XCLBIN_MAGIC_ID)); + if (ret) { + vmgmt_err(fwdev->vdev, "Invalid device firmware"); + return FW_UPLOAD_ERR_INVALID_SIZE; + } + + /* Firmware size should never be over 1G and less than size of struct axlf */ + if (!size || size != xsabin->header.length || size < sizeof(*xsabin) || + size > 1024 * 1024 * 1024) { + vmgmt_err(fwdev->vdev, "Invalid device firmware size"); + return FW_UPLOAD_ERR_INVALID_SIZE; + } + + ret = rm_queue_create_cmd(fwdev->vdev->rdev, RM_QUEUE_OP_LOAD_FW, + &fwdev->cmd); + if (ret) + return FW_UPLOAD_ERR_RW_ERROR; + + uuid_copy(&fwdev->uuid, &xsabin->header.uuid); + return FW_UPLOAD_ERR_NONE; +} + +static enum fw_upload_err vmgmt_fw_write(struct fw_upload *fw_upload, + const u8 *data, u32 offset, u32 size, + u32 *written) +{ + struct firmware_device *fwdev = fw_upload->dd_handle; + int ret; + + ret = rm_queue_data_init(fwdev->cmd, data, size); + if (ret) + return FW_UPLOAD_ERR_RW_ERROR; + + *written = size; + return FW_UPLOAD_ERR_NONE; +} + +static enum fw_upload_err vmgmt_fw_poll_complete(struct fw_upload *fw_upload) +{ + struct firmware_device *fwdev = fw_upload->dd_handle; + int ret; + + vmgmt_info(fwdev->vdev, "Programming device firmware: %pUb", &fwdev->uuid); + + ret = rm_queue_send_cmd(fwdev->cmd, RM_CMD_WAIT_DOWNLOAD_TIMEOUT); + if (ret) { + vmgmt_err(fwdev->vdev, "Send cmd failedi:%d, cid %d", ret, fwdev->id); + return FW_UPLOAD_ERR_HW_ERROR; + } + + vmgmt_info(fwdev->vdev, "Successfully programmed device firmware: %pUb", + &fwdev->uuid); + return FW_UPLOAD_ERR_NONE; +} + static void vmgmt_fw_cancel(struct fw_upload *fw_upload) { struct firmware_device *fwdev = fw_upload->dd_handle; @@ -208,8 +482,26 @@ static void vmgmt_fw_cancel(struct fw_upload *fw_upload) vmgmt_warn(fwdev->vdev, "canceled"); } +static void vmgmt_fw_cleanup(struct fw_upload *fw_upload) +{ + struct firmware_device *fwdev = fw_upload->dd_handle; + + if (!fwdev->cmd) + return; + + rm_queue_data_fini(fwdev->cmd); + rm_queue_destory_cmd(fwdev->cmd); + + fwdev->cmd = NULL; + fwdev->id = 0; +} + static const struct fw_upload_ops vmgmt_fw_ops = { + .prepare = vmgmt_fw_prepare, + .write = vmgmt_fw_write, + .poll_complete = vmgmt_fw_poll_complete, .cancel = vmgmt_fw_cancel, + .cleanup = vmgmt_fw_cleanup, }; static void vmgmt_fw_upload_fini(struct firmware_device *fwdev) @@ -250,17 +542,25 @@ static void vmgmt_device_teardown(struct vmgmt_device *vdev) vmgmt_fpga_fini(vdev->fdev); vmgmt_fw_upload_fini(vdev->fwdev); vmgmtm_comms_fini(vdev->ccdev); + vmgmt_rm_fini(vdev->rdev); } static int vmgmt_device_setup(struct vmgmt_device *vdev) { int ret; + vdev->rdev = vmgmt_rm_init(vdev); + if (IS_ERR(vdev->rdev)) { + ret = PTR_ERR(vdev->rdev); + vmgmt_err(vdev, "Failed to init runtime manager, err %d", ret); + return ret; + } + vdev->fwdev = vmgmt_fw_upload_init(vdev); if (IS_ERR(vdev->fwdev)) { ret = PTR_ERR(vdev->fwdev); vmgmt_err(vdev, "Failed to init FW uploader, err %d", ret); - goto done; + goto rm_fini; } vdev->ccdev = vmgmtm_comms_init(vdev); @@ -282,7 +582,8 @@ static int vmgmt_device_setup(struct vmgmt_device *vdev) vmgmtm_comms_fini(vdev->ccdev); upload_fini: vmgmt_fw_upload_fini(vdev->fwdev); -done: +rm_fini: + vmgmt_rm_fini(vdev->rdev); return ret; } diff --git a/drivers/fpga/amd/vmgmt.h b/drivers/fpga/amd/vmgmt.h index 4dc8a43f825e..c767d1372881 100644 --- a/drivers/fpga/amd/vmgmt.h +++ b/drivers/fpga/amd/vmgmt.h @@ -19,6 +19,7 @@ #include <linux/pci.h> #include <linux/uuid.h> #include <linux/regmap.h> +#include <linux/vmalloc.h> #define AMD_VMGMT_BAR 0 #define AMD_VMGMT_BAR_MASK BIT(0) @@ -93,8 +94,10 @@ struct vmgmt_device { void __iomem *tbl; uuid_t xclbin_uuid; uuid_t intf_uuid; - - void *debugfs_root; }; +struct rm_device *vmgmt_rm_init(struct vmgmt_device *vdev); +void vmgmt_rm_fini(struct rm_device *rdev); +int vmgmt_rm_get_fw_id(struct rm_device *rdev, uuid_t *uuid); + #endif /* __VMGMT_H */