// SPDX-License-Identifier: GPL-2.0 /* Copyright(c) 2023 Advanced Micro Devices, Inc */ #include #include #include #include "core.h" int pdsc_err_to_errno(enum pds_core_status_code code) { switch (code) { case PDS_RC_SUCCESS: return 0; case PDS_RC_EVERSION: case PDS_RC_EQTYPE: case PDS_RC_EQID: case PDS_RC_EINVAL: case PDS_RC_ENOSUPP: return -EINVAL; case PDS_RC_EPERM: return -EPERM; case PDS_RC_ENOENT: return -ENOENT; case PDS_RC_EAGAIN: return -EAGAIN; case PDS_RC_ENOMEM: return -ENOMEM; case PDS_RC_EFAULT: return -EFAULT; case PDS_RC_EBUSY: return -EBUSY; case PDS_RC_EEXIST: return -EEXIST; case PDS_RC_EVFID: return -ENODEV; case PDS_RC_ECLIENT: return -ECHILD; case PDS_RC_ENOSPC: return -ENOSPC; case PDS_RC_ERANGE: return -ERANGE; case PDS_RC_BAD_ADDR: return -EFAULT; case PDS_RC_BAD_PCI: return -ENXIO; case PDS_RC_EOPCODE: case PDS_RC_EINTR: case PDS_RC_DEV_CMD: case PDS_RC_ERROR: case PDS_RC_ERDMA: case PDS_RC_EIO: default: return -EIO; } } bool pdsc_is_fw_running(struct pdsc *pdsc) { if (!pdsc->info_regs) return false; pdsc->fw_status = ioread8(&pdsc->info_regs->fw_status); pdsc->last_fw_time = jiffies; pdsc->last_hb = ioread32(&pdsc->info_regs->fw_heartbeat); /* Firmware is useful only if the running bit is set and * fw_status != 0xff (bad PCI read) */ return (pdsc->fw_status != PDS_RC_BAD_PCI) && (pdsc->fw_status & PDS_CORE_FW_STS_F_RUNNING); } bool pdsc_is_fw_good(struct pdsc *pdsc) { bool fw_running = pdsc_is_fw_running(pdsc); u8 gen; /* Make sure to update the cached fw_status by calling * pdsc_is_fw_running() before getting the generation */ gen = pdsc->fw_status & PDS_CORE_FW_STS_F_GENERATION; return fw_running && gen == pdsc->fw_generation; } static u8 pdsc_devcmd_status(struct pdsc *pdsc) { return ioread8(&pdsc->cmd_regs->comp.status); } static bool pdsc_devcmd_done(struct pdsc *pdsc) { return ioread32(&pdsc->cmd_regs->done) & PDS_CORE_DEV_CMD_DONE; } static void pdsc_devcmd_dbell(struct pdsc *pdsc) { iowrite32(0, &pdsc->cmd_regs->done); iowrite32(1, &pdsc->cmd_regs->doorbell); } static void pdsc_devcmd_clean(struct pdsc *pdsc) { iowrite32(0, &pdsc->cmd_regs->doorbell); memset_io(&pdsc->cmd_regs->cmd, 0, sizeof(pdsc->cmd_regs->cmd)); } static const char *pdsc_devcmd_str(int opcode) { switch (opcode) { case PDS_CORE_CMD_NOP: return "PDS_CORE_CMD_NOP"; case PDS_CORE_CMD_IDENTIFY: return "PDS_CORE_CMD_IDENTIFY"; case PDS_CORE_CMD_RESET: return "PDS_CORE_CMD_RESET"; case PDS_CORE_CMD_INIT: return "PDS_CORE_CMD_INIT"; case PDS_CORE_CMD_FW_DOWNLOAD: return "PDS_CORE_CMD_FW_DOWNLOAD"; case PDS_CORE_CMD_FW_CONTROL: return "PDS_CORE_CMD_FW_CONTROL"; default: return "PDS_CORE_CMD_UNKNOWN"; } } static int pdsc_devcmd_wait(struct pdsc *pdsc, u8 opcode, int max_seconds) { struct device *dev = pdsc->dev; unsigned long start_time; unsigned long max_wait; unsigned long duration; int timeout = 0; bool running; int done = 0; int err = 0; int status; start_time = jiffies; max_wait = start_time + (max_seconds * HZ); while (!done && !timeout) { running = pdsc_is_fw_running(pdsc); if (!running) break; done = pdsc_devcmd_done(pdsc); if (done) break; timeout = time_after(jiffies, max_wait); if (timeout) break; usleep_range(100, 200); } duration = jiffies - start_time; if (done && duration > HZ) dev_dbg(dev, "DEVCMD %d %s after %ld secs\n", opcode, pdsc_devcmd_str(opcode), duration / HZ); if ((!done || timeout) && running) { dev_err(dev, "DEVCMD %d %s timeout, done %d timeout %d max_seconds=%d\n", opcode, pdsc_devcmd_str(opcode), done, timeout, max_seconds); err = -ETIMEDOUT; pdsc_devcmd_clean(pdsc); } status = pdsc_devcmd_status(pdsc); err = pdsc_err_to_errno(status); if (err && err != -EAGAIN) dev_err(dev, "DEVCMD %d %s failed, status=%d err %d %pe\n", opcode, pdsc_devcmd_str(opcode), status, err, ERR_PTR(err)); return err; } int pdsc_devcmd_locked(struct pdsc *pdsc, union pds_core_dev_cmd *cmd, union pds_core_dev_comp *comp, int max_seconds) { int err; if (!pdsc->cmd_regs) return -ENXIO; memcpy_toio(&pdsc->cmd_regs->cmd, cmd, sizeof(*cmd)); pdsc_devcmd_dbell(pdsc); err = pdsc_devcmd_wait(pdsc, cmd->opcode, max_seconds); if ((err == -ENXIO || err == -ETIMEDOUT) && pdsc->wq) queue_work(pdsc->wq, &pdsc->health_work); else memcpy_fromio(comp, &pdsc->cmd_regs->comp, sizeof(*comp)); return err; } int pdsc_devcmd(struct pdsc *pdsc, union pds_core_dev_cmd *cmd, union pds_core_dev_comp *comp, int max_seconds) { int err; mutex_lock(&pdsc->devcmd_lock); err = pdsc_devcmd_locked(pdsc, cmd, comp, max_seconds); mutex_unlock(&pdsc->devcmd_lock); return err; } int pdsc_devcmd_init(struct pdsc *pdsc) { union pds_core_dev_comp comp = {}; union pds_core_dev_cmd cmd = { .opcode = PDS_CORE_CMD_INIT, }; return pdsc_devcmd(pdsc, &cmd, &comp, pdsc->devcmd_timeout); } int pdsc_devcmd_reset(struct pdsc *pdsc) { union pds_core_dev_comp comp = {}; union pds_core_dev_cmd cmd = { .reset.opcode = PDS_CORE_CMD_RESET, }; if (!pdsc_is_fw_running(pdsc)) return 0; return pdsc_devcmd(pdsc, &cmd, &comp, pdsc->devcmd_timeout); } static int pdsc_devcmd_identify_locked(struct pdsc *pdsc) { union pds_core_dev_comp comp = {}; union pds_core_dev_cmd cmd = { .identify.opcode = PDS_CORE_CMD_IDENTIFY, .identify.ver = PDS_CORE_IDENTITY_VERSION_1, }; return pdsc_devcmd_locked(pdsc, &cmd, &comp, pdsc->devcmd_timeout); } static void pdsc_init_devinfo(struct pdsc *pdsc) { pdsc->dev_info.asic_type = ioread8(&pdsc->info_regs->asic_type); pdsc->dev_info.asic_rev = ioread8(&pdsc->info_regs->asic_rev); pdsc->fw_generation = PDS_CORE_FW_STS_F_GENERATION & ioread8(&pdsc->info_regs->fw_status); memcpy_fromio(pdsc->dev_info.fw_version, pdsc->info_regs->fw_version, PDS_CORE_DEVINFO_FWVERS_BUFLEN); pdsc->dev_info.fw_version[PDS_CORE_DEVINFO_FWVERS_BUFLEN] = 0; memcpy_fromio(pdsc->dev_info.serial_num, pdsc->info_regs->serial_num, PDS_CORE_DEVINFO_SERIAL_BUFLEN); pdsc->dev_info.serial_num[PDS_CORE_DEVINFO_SERIAL_BUFLEN] = 0; dev_dbg(pdsc->dev, "fw_version %s\n", pdsc->dev_info.fw_version); } static int pdsc_identify(struct pdsc *pdsc) { struct pds_core_drv_identity drv = {}; size_t sz; int err; int n; drv.drv_type = cpu_to_le32(PDS_DRIVER_LINUX); /* Catching the return quiets a Wformat-truncation complaint */ n = snprintf(drv.driver_ver_str, sizeof(drv.driver_ver_str), "%s %s", PDS_CORE_DRV_NAME, utsname()->release); if (n > sizeof(drv.driver_ver_str)) dev_dbg(pdsc->dev, "release name truncated, don't care\n"); /* Next let's get some info about the device * We use the devcmd_lock at this level in order to * get safe access to the cmd_regs->data before anyone * else can mess it up */ mutex_lock(&pdsc->devcmd_lock); sz = min_t(size_t, sizeof(drv), sizeof(pdsc->cmd_regs->data)); memcpy_toio(&pdsc->cmd_regs->data, &drv, sz); err = pdsc_devcmd_identify_locked(pdsc); if (!err) { sz = min_t(size_t, sizeof(pdsc->dev_ident), sizeof(pdsc->cmd_regs->data)); memcpy_fromio(&pdsc->dev_ident, &pdsc->cmd_regs->data, sz); } mutex_unlock(&pdsc->devcmd_lock); if (err) { dev_err(pdsc->dev, "Cannot identify device: %pe\n", ERR_PTR(err)); return err; } if (isprint(pdsc->dev_info.fw_version[0]) && isascii(pdsc->dev_info.fw_version[0])) dev_info(pdsc->dev, "FW: %.*s\n", (int)(sizeof(pdsc->dev_info.fw_version) - 1), pdsc->dev_info.fw_version); else dev_info(pdsc->dev, "FW: (invalid string) 0x%02x 0x%02x 0x%02x 0x%02x ...\n", (u8)pdsc->dev_info.fw_version[0], (u8)pdsc->dev_info.fw_version[1], (u8)pdsc->dev_info.fw_version[2], (u8)pdsc->dev_info.fw_version[3]); return 0; } void pdsc_dev_uninit(struct pdsc *pdsc) { if (pdsc->intr_info) { int i; for (i = 0; i < pdsc->nintrs; i++) pdsc_intr_free(pdsc, i); kfree(pdsc->intr_info); pdsc->intr_info = NULL; pdsc->nintrs = 0; } pci_free_irq_vectors(pdsc->pdev); } int pdsc_dev_init(struct pdsc *pdsc) { unsigned int nintrs; int err; /* Initial init and reset of device */ pdsc_init_devinfo(pdsc); pdsc->devcmd_timeout = PDS_CORE_DEVCMD_TIMEOUT; err = pdsc_devcmd_reset(pdsc); if (err) return err; err = pdsc_identify(pdsc); if (err) return err; pdsc_debugfs_add_ident(pdsc); /* Now we can reserve interrupts */ nintrs = le32_to_cpu(pdsc->dev_ident.nintrs); nintrs = min_t(unsigned int, num_online_cpus(), nintrs); /* Get intr_info struct array for tracking */ pdsc->intr_info = kcalloc(nintrs, sizeof(*pdsc->intr_info), GFP_KERNEL); if (!pdsc->intr_info) return -ENOMEM; err = pci_alloc_irq_vectors(pdsc->pdev, nintrs, nintrs, PCI_IRQ_MSIX); if (err != nintrs) { dev_err(pdsc->dev, "Can't get %d intrs from OS: %pe\n", nintrs, ERR_PTR(err)); err = -ENOSPC; goto err_out; } pdsc->nintrs = nintrs; return 0; err_out: kfree(pdsc->intr_info); pdsc->intr_info = NULL; return err; }