// SPDX-License-Identifier: GPL-2.0 /* Copyright(c) 2023 Advanced Micro Devices, Inc */ #include #include #include "core.h" static BLOCKING_NOTIFIER_HEAD(pds_notify_chain); int pdsc_register_notify(struct notifier_block *nb) { return blocking_notifier_chain_register(&pds_notify_chain, nb); } EXPORT_SYMBOL_GPL(pdsc_register_notify); void pdsc_unregister_notify(struct notifier_block *nb) { blocking_notifier_chain_unregister(&pds_notify_chain, nb); } EXPORT_SYMBOL_GPL(pdsc_unregister_notify); void pdsc_notify(unsigned long event, void *data) { blocking_notifier_call_chain(&pds_notify_chain, event, data); } void pdsc_intr_free(struct pdsc *pdsc, int index) { struct pdsc_intr_info *intr_info; if (index >= pdsc->nintrs || index < 0) { WARN(true, "bad intr index %d\n", index); return; } intr_info = &pdsc->intr_info[index]; if (!intr_info->vector) return; dev_dbg(pdsc->dev, "%s: idx %d vec %d name %s\n", __func__, index, intr_info->vector, intr_info->name); pds_core_intr_mask(&pdsc->intr_ctrl[index], PDS_CORE_INTR_MASK_SET); pds_core_intr_clean(&pdsc->intr_ctrl[index]); free_irq(intr_info->vector, intr_info->data); memset(intr_info, 0, sizeof(*intr_info)); } int pdsc_intr_alloc(struct pdsc *pdsc, char *name, irq_handler_t handler, void *data) { struct pdsc_intr_info *intr_info; unsigned int index; int err; /* Find the first available interrupt */ for (index = 0; index < pdsc->nintrs; index++) if (!pdsc->intr_info[index].vector) break; if (index >= pdsc->nintrs) { dev_warn(pdsc->dev, "%s: no intr, index=%d nintrs=%d\n", __func__, index, pdsc->nintrs); return -ENOSPC; } pds_core_intr_clean_flags(&pdsc->intr_ctrl[index], PDS_CORE_INTR_CRED_RESET_COALESCE); intr_info = &pdsc->intr_info[index]; intr_info->index = index; intr_info->data = data; strscpy(intr_info->name, name, sizeof(intr_info->name)); /* Get the OS vector number for the interrupt */ err = pci_irq_vector(pdsc->pdev, index); if (err < 0) { dev_err(pdsc->dev, "failed to get intr vector index %d: %pe\n", index, ERR_PTR(err)); goto err_out_free_intr; } intr_info->vector = err; /* Init the device's intr mask */ pds_core_intr_clean(&pdsc->intr_ctrl[index]); pds_core_intr_mask_assert(&pdsc->intr_ctrl[index], 1); pds_core_intr_mask(&pdsc->intr_ctrl[index], PDS_CORE_INTR_MASK_SET); /* Register the isr with a name */ err = request_irq(intr_info->vector, handler, 0, intr_info->name, data); if (err) { dev_err(pdsc->dev, "failed to get intr irq vector %d: %pe\n", intr_info->vector, ERR_PTR(err)); goto err_out_free_intr; } return index; err_out_free_intr: pdsc_intr_free(pdsc, index); return err; } static void pdsc_qcq_intr_free(struct pdsc *pdsc, struct pdsc_qcq *qcq) { if (!(qcq->flags & PDS_CORE_QCQ_F_INTR) || qcq->intx == PDS_CORE_INTR_INDEX_NOT_ASSIGNED) return; pdsc_intr_free(pdsc, qcq->intx); qcq->intx = PDS_CORE_INTR_INDEX_NOT_ASSIGNED; } static int pdsc_qcq_intr_alloc(struct pdsc *pdsc, struct pdsc_qcq *qcq) { char name[PDSC_INTR_NAME_MAX_SZ]; int index; if (!(qcq->flags & PDS_CORE_QCQ_F_INTR)) { qcq->intx = PDS_CORE_INTR_INDEX_NOT_ASSIGNED; return 0; } snprintf(name, sizeof(name), "%s-%d-%s", PDS_CORE_DRV_NAME, pdsc->pdev->bus->number, qcq->q.name); index = pdsc_intr_alloc(pdsc, name, pdsc_adminq_isr, pdsc); if (index < 0) return index; qcq->intx = index; qcq->cq.bound_intr = &pdsc->intr_info[index]; return 0; } void pdsc_qcq_free(struct pdsc *pdsc, struct pdsc_qcq *qcq) { struct device *dev = pdsc->dev; if (!(qcq && qcq->pdsc)) return; pdsc_debugfs_del_qcq(qcq); pdsc_qcq_intr_free(pdsc, qcq); if (qcq->q_base) dma_free_coherent(dev, qcq->q_size, qcq->q_base, qcq->q_base_pa); if (qcq->cq_base) dma_free_coherent(dev, qcq->cq_size, qcq->cq_base, qcq->cq_base_pa); vfree(qcq->cq.info); vfree(qcq->q.info); memset(qcq, 0, sizeof(*qcq)); } static void pdsc_q_map(struct pdsc_queue *q, void *base, dma_addr_t base_pa) { struct pdsc_q_info *cur; unsigned int i; q->base = base; q->base_pa = base_pa; for (i = 0, cur = q->info; i < q->num_descs; i++, cur++) cur->desc = base + (i * q->desc_size); } static void pdsc_cq_map(struct pdsc_cq *cq, void *base, dma_addr_t base_pa) { struct pdsc_cq_info *cur; unsigned int i; cq->base = base; cq->base_pa = base_pa; for (i = 0, cur = cq->info; i < cq->num_descs; i++, cur++) cur->comp = base + (i * cq->desc_size); } int pdsc_qcq_alloc(struct pdsc *pdsc, unsigned int type, unsigned int index, const char *name, unsigned int flags, unsigned int num_descs, unsigned int desc_size, unsigned int cq_desc_size, unsigned int pid, struct pdsc_qcq *qcq) { struct device *dev = pdsc->dev; void *q_base, *cq_base; dma_addr_t cq_base_pa; dma_addr_t q_base_pa; int err; qcq->q.info = vcalloc(num_descs, sizeof(*qcq->q.info)); if (!qcq->q.info) { err = -ENOMEM; goto err_out; } qcq->pdsc = pdsc; qcq->flags = flags; INIT_WORK(&qcq->work, pdsc_work_thread); qcq->q.type = type; qcq->q.index = index; qcq->q.num_descs = num_descs; qcq->q.desc_size = desc_size; qcq->q.tail_idx = 0; qcq->q.head_idx = 0; qcq->q.pid = pid; snprintf(qcq->q.name, sizeof(qcq->q.name), "%s%u", name, index); err = pdsc_qcq_intr_alloc(pdsc, qcq); if (err) goto err_out_free_q_info; qcq->cq.info = vcalloc(num_descs, sizeof(*qcq->cq.info)); if (!qcq->cq.info) { err = -ENOMEM; goto err_out_free_irq; } qcq->cq.num_descs = num_descs; qcq->cq.desc_size = cq_desc_size; qcq->cq.tail_idx = 0; qcq->cq.done_color = 1; if (flags & PDS_CORE_QCQ_F_NOTIFYQ) { /* q & cq need to be contiguous in case of notifyq */ qcq->q_size = PDS_PAGE_SIZE + ALIGN(num_descs * desc_size, PDS_PAGE_SIZE) + ALIGN(num_descs * cq_desc_size, PDS_PAGE_SIZE); qcq->q_base = dma_alloc_coherent(dev, qcq->q_size + qcq->cq_size, &qcq->q_base_pa, GFP_KERNEL); if (!qcq->q_base) { err = -ENOMEM; goto err_out_free_cq_info; } q_base = PTR_ALIGN(qcq->q_base, PDS_PAGE_SIZE); q_base_pa = ALIGN(qcq->q_base_pa, PDS_PAGE_SIZE); pdsc_q_map(&qcq->q, q_base, q_base_pa); cq_base = PTR_ALIGN(q_base + ALIGN(num_descs * desc_size, PDS_PAGE_SIZE), PDS_PAGE_SIZE); cq_base_pa = ALIGN(qcq->q_base_pa + ALIGN(num_descs * desc_size, PDS_PAGE_SIZE), PDS_PAGE_SIZE); } else { /* q DMA descriptors */ qcq->q_size = PDS_PAGE_SIZE + (num_descs * desc_size); qcq->q_base = dma_alloc_coherent(dev, qcq->q_size, &qcq->q_base_pa, GFP_KERNEL); if (!qcq->q_base) { err = -ENOMEM; goto err_out_free_cq_info; } q_base = PTR_ALIGN(qcq->q_base, PDS_PAGE_SIZE); q_base_pa = ALIGN(qcq->q_base_pa, PDS_PAGE_SIZE); pdsc_q_map(&qcq->q, q_base, q_base_pa); /* cq DMA descriptors */ qcq->cq_size = PDS_PAGE_SIZE + (num_descs * cq_desc_size); qcq->cq_base = dma_alloc_coherent(dev, qcq->cq_size, &qcq->cq_base_pa, GFP_KERNEL); if (!qcq->cq_base) { err = -ENOMEM; goto err_out_free_q; } cq_base = PTR_ALIGN(qcq->cq_base, PDS_PAGE_SIZE); cq_base_pa = ALIGN(qcq->cq_base_pa, PDS_PAGE_SIZE); } pdsc_cq_map(&qcq->cq, cq_base, cq_base_pa); qcq->cq.bound_q = &qcq->q; pdsc_debugfs_add_qcq(pdsc, qcq); return 0; err_out_free_q: dma_free_coherent(dev, qcq->q_size, qcq->q_base, qcq->q_base_pa); err_out_free_cq_info: vfree(qcq->cq.info); err_out_free_irq: pdsc_qcq_intr_free(pdsc, qcq); err_out_free_q_info: vfree(qcq->q.info); memset(qcq, 0, sizeof(*qcq)); err_out: dev_err(dev, "qcq alloc of %s%d failed %d\n", name, index, err); return err; } static void pdsc_core_uninit(struct pdsc *pdsc) { pdsc_qcq_free(pdsc, &pdsc->notifyqcq); pdsc_qcq_free(pdsc, &pdsc->adminqcq); if (pdsc->kern_dbpage) { iounmap(pdsc->kern_dbpage); pdsc->kern_dbpage = NULL; } } static int pdsc_core_init(struct pdsc *pdsc) { union pds_core_dev_comp comp = {}; union pds_core_dev_cmd cmd = { .init.opcode = PDS_CORE_CMD_INIT, }; struct pds_core_dev_init_data_out cido; struct pds_core_dev_init_data_in cidi; u32 dbid_count; u32 dbpage_num; int numdescs; size_t sz; int err; /* Scale the descriptor ring length based on number of CPUs and VFs */ numdescs = max_t(int, PDSC_ADMINQ_MIN_LENGTH, num_online_cpus()); numdescs += 2 * pci_sriov_get_totalvfs(pdsc->pdev); numdescs = roundup_pow_of_two(numdescs); err = pdsc_qcq_alloc(pdsc, PDS_CORE_QTYPE_ADMINQ, 0, "adminq", PDS_CORE_QCQ_F_CORE | PDS_CORE_QCQ_F_INTR, numdescs, sizeof(union pds_core_adminq_cmd), sizeof(union pds_core_adminq_comp), 0, &pdsc->adminqcq); if (err) return err; err = pdsc_qcq_alloc(pdsc, PDS_CORE_QTYPE_NOTIFYQ, 0, "notifyq", PDS_CORE_QCQ_F_NOTIFYQ, PDSC_NOTIFYQ_LENGTH, sizeof(struct pds_core_notifyq_cmd), sizeof(union pds_core_notifyq_comp), 0, &pdsc->notifyqcq); if (err) goto err_out_uninit; cidi.adminq_q_base = cpu_to_le64(pdsc->adminqcq.q_base_pa); cidi.adminq_cq_base = cpu_to_le64(pdsc->adminqcq.cq_base_pa); cidi.notifyq_cq_base = cpu_to_le64(pdsc->notifyqcq.cq.base_pa); cidi.flags = cpu_to_le32(PDS_CORE_QINIT_F_IRQ | PDS_CORE_QINIT_F_ENA); cidi.intr_index = cpu_to_le16(pdsc->adminqcq.intx); cidi.adminq_ring_size = ilog2(pdsc->adminqcq.q.num_descs); cidi.notifyq_ring_size = ilog2(pdsc->notifyqcq.q.num_descs); mutex_lock(&pdsc->devcmd_lock); sz = min_t(size_t, sizeof(cidi), sizeof(pdsc->cmd_regs->data)); memcpy_toio(&pdsc->cmd_regs->data, &cidi, sz); err = pdsc_devcmd_locked(pdsc, &cmd, &comp, pdsc->devcmd_timeout); if (!err) { sz = min_t(size_t, sizeof(cido), sizeof(pdsc->cmd_regs->data)); memcpy_fromio(&cido, &pdsc->cmd_regs->data, sz); } mutex_unlock(&pdsc->devcmd_lock); if (err) { dev_err(pdsc->dev, "Device init command failed: %pe\n", ERR_PTR(err)); goto err_out_uninit; } pdsc->hw_index = le32_to_cpu(cido.core_hw_index); dbid_count = le32_to_cpu(pdsc->dev_ident.ndbpgs_per_lif); dbpage_num = pdsc->hw_index * dbid_count; pdsc->kern_dbpage = pdsc_map_dbpage(pdsc, dbpage_num); if (!pdsc->kern_dbpage) { dev_err(pdsc->dev, "Cannot map dbpage, aborting\n"); err = -ENOMEM; goto err_out_uninit; } pdsc->adminqcq.q.hw_type = cido.adminq_hw_type; pdsc->adminqcq.q.hw_index = le32_to_cpu(cido.adminq_hw_index); pdsc->adminqcq.q.dbval = PDS_CORE_DBELL_QID(pdsc->adminqcq.q.hw_index); pdsc->notifyqcq.q.hw_type = cido.notifyq_hw_type; pdsc->notifyqcq.q.hw_index = le32_to_cpu(cido.notifyq_hw_index); pdsc->notifyqcq.q.dbval = PDS_CORE_DBELL_QID(pdsc->notifyqcq.q.hw_index); pdsc->last_eid = 0; return 0; err_out_uninit: pdsc_core_uninit(pdsc); return err; } static struct pdsc_viftype pdsc_viftype_defaults[] = { [PDS_DEV_TYPE_VDPA] = { .name = PDS_DEV_TYPE_VDPA_STR, .vif_id = PDS_DEV_TYPE_VDPA, .dl_id = DEVLINK_PARAM_GENERIC_ID_ENABLE_VNET }, [PDS_DEV_TYPE_MAX] = {} }; static int pdsc_viftypes_init(struct pdsc *pdsc) { enum pds_core_vif_types vt; pdsc->viftype_status = kzalloc(sizeof(pdsc_viftype_defaults), GFP_KERNEL); if (!pdsc->viftype_status) return -ENOMEM; for (vt = 0; vt < PDS_DEV_TYPE_MAX; vt++) { bool vt_support; if (!pdsc_viftype_defaults[vt].name) continue; /* Grab the defaults */ pdsc->viftype_status[vt] = pdsc_viftype_defaults[vt]; /* See what the Core device has for support */ vt_support = !!le16_to_cpu(pdsc->dev_ident.vif_types[vt]); dev_dbg(pdsc->dev, "VIF %s is %ssupported\n", pdsc->viftype_status[vt].name, vt_support ? "" : "not "); pdsc->viftype_status[vt].supported = vt_support; } return 0; } int pdsc_setup(struct pdsc *pdsc, bool init) { int err; err = pdsc_dev_init(pdsc); if (err) return err; /* Set up the Core with the AdminQ and NotifyQ info */ err = pdsc_core_init(pdsc); if (err) goto err_out_teardown; /* Set up the VIFs */ if (init) { err = pdsc_viftypes_init(pdsc); if (err) goto err_out_teardown; pdsc_debugfs_add_viftype(pdsc); } refcount_set(&pdsc->adminq_refcnt, 1); clear_bit(PDSC_S_FW_DEAD, &pdsc->state); return 0; err_out_teardown: pdsc_teardown(pdsc, init); return err; } void pdsc_teardown(struct pdsc *pdsc, bool removing) { if (!pdsc->pdev->is_virtfn) pdsc_devcmd_reset(pdsc); if (pdsc->adminqcq.work.func) cancel_work_sync(&pdsc->adminqcq.work); pdsc_core_uninit(pdsc); if (removing) { kfree(pdsc->viftype_status); pdsc->viftype_status = NULL; } pdsc_dev_uninit(pdsc); set_bit(PDSC_S_FW_DEAD, &pdsc->state); } int pdsc_start(struct pdsc *pdsc) { pds_core_intr_mask(&pdsc->intr_ctrl[pdsc->adminqcq.intx], PDS_CORE_INTR_MASK_CLEAR); return 0; } void pdsc_stop(struct pdsc *pdsc) { int i; if (!pdsc->intr_info) return; /* Mask interrupts that are in use */ for (i = 0; i < pdsc->nintrs; i++) if (pdsc->intr_info[i].vector) pds_core_intr_mask(&pdsc->intr_ctrl[i], PDS_CORE_INTR_MASK_SET); } static void pdsc_adminq_wait_and_dec_once_unused(struct pdsc *pdsc) { /* The driver initializes the adminq_refcnt to 1 when the adminq is * allocated and ready for use. Other users/requesters will increment * the refcnt while in use. If the refcnt is down to 1 then the adminq * is not in use and the refcnt can be cleared and adminq freed. Before * calling this function the driver will set PDSC_S_FW_DEAD, which * prevent subsequent attempts to use the adminq and increment the * refcnt to fail. This guarantees that this function will eventually * exit. */ while (!refcount_dec_if_one(&pdsc->adminq_refcnt)) { dev_dbg_ratelimited(pdsc->dev, "%s: adminq in use\n", __func__); cpu_relax(); } } void pdsc_fw_down(struct pdsc *pdsc) { union pds_core_notifyq_comp reset_event = { .reset.ecode = cpu_to_le16(PDS_EVENT_RESET), .reset.state = 0, }; if (test_and_set_bit(PDSC_S_FW_DEAD, &pdsc->state)) { dev_warn(pdsc->dev, "%s: already happening\n", __func__); return; } if (pdsc->pdev->is_virtfn) return; pdsc_adminq_wait_and_dec_once_unused(pdsc); /* Notify clients of fw_down */ if (pdsc->fw_reporter) devlink_health_report(pdsc->fw_reporter, "FW down reported", pdsc); pdsc_notify(PDS_EVENT_RESET, &reset_event); pdsc_stop(pdsc); pdsc_teardown(pdsc, PDSC_TEARDOWN_RECOVERY); } void pdsc_fw_up(struct pdsc *pdsc) { union pds_core_notifyq_comp reset_event = { .reset.ecode = cpu_to_le16(PDS_EVENT_RESET), .reset.state = 1, }; int err; if (!test_bit(PDSC_S_FW_DEAD, &pdsc->state)) { dev_err(pdsc->dev, "%s: fw not dead\n", __func__); return; } if (pdsc->pdev->is_virtfn) { clear_bit(PDSC_S_FW_DEAD, &pdsc->state); return; } err = pdsc_setup(pdsc, PDSC_SETUP_RECOVERY); if (err) goto err_out; err = pdsc_start(pdsc); if (err) goto err_out; /* Notify clients of fw_up */ pdsc->fw_recoveries++; if (pdsc->fw_reporter) devlink_health_reporter_state_update(pdsc->fw_reporter, DEVLINK_HEALTH_REPORTER_STATE_HEALTHY); pdsc_notify(PDS_EVENT_RESET, &reset_event); return; err_out: pdsc_teardown(pdsc, PDSC_TEARDOWN_RECOVERY); } void pdsc_pci_reset_thread(struct work_struct *work) { struct pdsc *pdsc = container_of(work, struct pdsc, pci_reset_work); struct pci_dev *pdev = pdsc->pdev; pci_dev_get(pdev); pci_reset_function(pdev); pci_dev_put(pdev); } static void pdsc_check_pci_health(struct pdsc *pdsc) { u8 fw_status; /* some sort of teardown already in progress */ if (!pdsc->info_regs) return; fw_status = ioread8(&pdsc->info_regs->fw_status); /* is PCI broken? */ if (fw_status != PDS_RC_BAD_PCI) return; /* prevent deadlock between pdsc_reset_prepare and pdsc_health_thread */ queue_work(pdsc->wq, &pdsc->pci_reset_work); } void pdsc_health_thread(struct work_struct *work) { struct pdsc *pdsc = container_of(work, struct pdsc, health_work); unsigned long mask; bool healthy; mutex_lock(&pdsc->config_lock); /* Don't do a check when in a transition state */ mask = BIT_ULL(PDSC_S_INITING_DRIVER) | BIT_ULL(PDSC_S_STOPPING_DRIVER); if (pdsc->state & mask) goto out_unlock; healthy = pdsc_is_fw_good(pdsc); dev_dbg(pdsc->dev, "%s: health %d fw_status %#02x fw_heartbeat %d\n", __func__, healthy, pdsc->fw_status, pdsc->last_hb); if (test_bit(PDSC_S_FW_DEAD, &pdsc->state)) { if (healthy) pdsc_fw_up(pdsc); } else { if (!healthy) pdsc_fw_down(pdsc); } pdsc_check_pci_health(pdsc); pdsc->fw_generation = pdsc->fw_status & PDS_CORE_FW_STS_F_GENERATION; out_unlock: mutex_unlock(&pdsc->config_lock); }