xref: /kernel/linux/linux-5.10/drivers/dma/idxd/irq.c (revision 8c2ecf20)
18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0
28c2ecf20Sopenharmony_ci/* Copyright(c) 2019 Intel Corporation. All rights rsvd. */
38c2ecf20Sopenharmony_ci#include <linux/init.h>
48c2ecf20Sopenharmony_ci#include <linux/kernel.h>
58c2ecf20Sopenharmony_ci#include <linux/module.h>
68c2ecf20Sopenharmony_ci#include <linux/pci.h>
78c2ecf20Sopenharmony_ci#include <linux/io-64-nonatomic-lo-hi.h>
88c2ecf20Sopenharmony_ci#include <linux/dmaengine.h>
98c2ecf20Sopenharmony_ci#include <uapi/linux/idxd.h>
108c2ecf20Sopenharmony_ci#include "../dmaengine.h"
118c2ecf20Sopenharmony_ci#include "idxd.h"
128c2ecf20Sopenharmony_ci#include "registers.h"
138c2ecf20Sopenharmony_ci
148c2ecf20Sopenharmony_cistatic void idxd_device_reinit(struct work_struct *work)
158c2ecf20Sopenharmony_ci{
168c2ecf20Sopenharmony_ci	struct idxd_device *idxd = container_of(work, struct idxd_device, work);
178c2ecf20Sopenharmony_ci	struct device *dev = &idxd->pdev->dev;
188c2ecf20Sopenharmony_ci	int rc, i;
198c2ecf20Sopenharmony_ci
208c2ecf20Sopenharmony_ci	idxd_device_reset(idxd);
218c2ecf20Sopenharmony_ci	rc = idxd_device_config(idxd);
228c2ecf20Sopenharmony_ci	if (rc < 0)
238c2ecf20Sopenharmony_ci		goto out;
248c2ecf20Sopenharmony_ci
258c2ecf20Sopenharmony_ci	rc = idxd_device_enable(idxd);
268c2ecf20Sopenharmony_ci	if (rc < 0)
278c2ecf20Sopenharmony_ci		goto out;
288c2ecf20Sopenharmony_ci
298c2ecf20Sopenharmony_ci	for (i = 0; i < idxd->max_wqs; i++) {
308c2ecf20Sopenharmony_ci		struct idxd_wq *wq = &idxd->wqs[i];
318c2ecf20Sopenharmony_ci
328c2ecf20Sopenharmony_ci		if (wq->state == IDXD_WQ_ENABLED) {
338c2ecf20Sopenharmony_ci			rc = idxd_wq_enable(wq);
348c2ecf20Sopenharmony_ci			if (rc < 0) {
358c2ecf20Sopenharmony_ci				dev_warn(dev, "Unable to re-enable wq %s\n",
368c2ecf20Sopenharmony_ci					 dev_name(&wq->conf_dev));
378c2ecf20Sopenharmony_ci			}
388c2ecf20Sopenharmony_ci		}
398c2ecf20Sopenharmony_ci	}
408c2ecf20Sopenharmony_ci
418c2ecf20Sopenharmony_ci	return;
428c2ecf20Sopenharmony_ci
438c2ecf20Sopenharmony_ci out:
448c2ecf20Sopenharmony_ci	idxd_device_wqs_clear_state(idxd);
458c2ecf20Sopenharmony_ci}
468c2ecf20Sopenharmony_ci
478c2ecf20Sopenharmony_ciirqreturn_t idxd_irq_handler(int vec, void *data)
488c2ecf20Sopenharmony_ci{
498c2ecf20Sopenharmony_ci	struct idxd_irq_entry *irq_entry = data;
508c2ecf20Sopenharmony_ci	struct idxd_device *idxd = irq_entry->idxd;
518c2ecf20Sopenharmony_ci
528c2ecf20Sopenharmony_ci	idxd_mask_msix_vector(idxd, irq_entry->id);
538c2ecf20Sopenharmony_ci	return IRQ_WAKE_THREAD;
548c2ecf20Sopenharmony_ci}
558c2ecf20Sopenharmony_ci
568c2ecf20Sopenharmony_cistatic int process_misc_interrupts(struct idxd_device *idxd, u32 cause)
578c2ecf20Sopenharmony_ci{
588c2ecf20Sopenharmony_ci	struct device *dev = &idxd->pdev->dev;
598c2ecf20Sopenharmony_ci	union gensts_reg gensts;
608c2ecf20Sopenharmony_ci	u32 val = 0;
618c2ecf20Sopenharmony_ci	int i;
628c2ecf20Sopenharmony_ci	bool err = false;
638c2ecf20Sopenharmony_ci
648c2ecf20Sopenharmony_ci	if (cause & IDXD_INTC_ERR) {
658c2ecf20Sopenharmony_ci		spin_lock_bh(&idxd->dev_lock);
668c2ecf20Sopenharmony_ci		for (i = 0; i < 4; i++)
678c2ecf20Sopenharmony_ci			idxd->sw_err.bits[i] = ioread64(idxd->reg_base +
688c2ecf20Sopenharmony_ci					IDXD_SWERR_OFFSET + i * sizeof(u64));
698c2ecf20Sopenharmony_ci
708c2ecf20Sopenharmony_ci		iowrite64(idxd->sw_err.bits[0] & IDXD_SWERR_ACK,
718c2ecf20Sopenharmony_ci			  idxd->reg_base + IDXD_SWERR_OFFSET);
728c2ecf20Sopenharmony_ci
738c2ecf20Sopenharmony_ci		if (idxd->sw_err.valid && idxd->sw_err.wq_idx_valid) {
748c2ecf20Sopenharmony_ci			int id = idxd->sw_err.wq_idx;
758c2ecf20Sopenharmony_ci			struct idxd_wq *wq = &idxd->wqs[id];
768c2ecf20Sopenharmony_ci
778c2ecf20Sopenharmony_ci			if (wq->type == IDXD_WQT_USER)
788c2ecf20Sopenharmony_ci				wake_up_interruptible(&wq->err_queue);
798c2ecf20Sopenharmony_ci		} else {
808c2ecf20Sopenharmony_ci			int i;
818c2ecf20Sopenharmony_ci
828c2ecf20Sopenharmony_ci			for (i = 0; i < idxd->max_wqs; i++) {
838c2ecf20Sopenharmony_ci				struct idxd_wq *wq = &idxd->wqs[i];
848c2ecf20Sopenharmony_ci
858c2ecf20Sopenharmony_ci				if (wq->type == IDXD_WQT_USER)
868c2ecf20Sopenharmony_ci					wake_up_interruptible(&wq->err_queue);
878c2ecf20Sopenharmony_ci			}
888c2ecf20Sopenharmony_ci		}
898c2ecf20Sopenharmony_ci
908c2ecf20Sopenharmony_ci		spin_unlock_bh(&idxd->dev_lock);
918c2ecf20Sopenharmony_ci		val |= IDXD_INTC_ERR;
928c2ecf20Sopenharmony_ci
938c2ecf20Sopenharmony_ci		for (i = 0; i < 4; i++)
948c2ecf20Sopenharmony_ci			dev_warn(dev, "err[%d]: %#16.16llx\n",
958c2ecf20Sopenharmony_ci				 i, idxd->sw_err.bits[i]);
968c2ecf20Sopenharmony_ci		err = true;
978c2ecf20Sopenharmony_ci	}
988c2ecf20Sopenharmony_ci
998c2ecf20Sopenharmony_ci	if (cause & IDXD_INTC_CMD) {
1008c2ecf20Sopenharmony_ci		val |= IDXD_INTC_CMD;
1018c2ecf20Sopenharmony_ci		complete(idxd->cmd_done);
1028c2ecf20Sopenharmony_ci	}
1038c2ecf20Sopenharmony_ci
1048c2ecf20Sopenharmony_ci	if (cause & IDXD_INTC_OCCUPY) {
1058c2ecf20Sopenharmony_ci		/* Driver does not utilize occupancy interrupt */
1068c2ecf20Sopenharmony_ci		val |= IDXD_INTC_OCCUPY;
1078c2ecf20Sopenharmony_ci	}
1088c2ecf20Sopenharmony_ci
1098c2ecf20Sopenharmony_ci	if (cause & IDXD_INTC_PERFMON_OVFL) {
1108c2ecf20Sopenharmony_ci		/*
1118c2ecf20Sopenharmony_ci		 * Driver does not utilize perfmon counter overflow interrupt
1128c2ecf20Sopenharmony_ci		 * yet.
1138c2ecf20Sopenharmony_ci		 */
1148c2ecf20Sopenharmony_ci		val |= IDXD_INTC_PERFMON_OVFL;
1158c2ecf20Sopenharmony_ci	}
1168c2ecf20Sopenharmony_ci
1178c2ecf20Sopenharmony_ci	val ^= cause;
1188c2ecf20Sopenharmony_ci	if (val)
1198c2ecf20Sopenharmony_ci		dev_warn_once(dev, "Unexpected interrupt cause bits set: %#x\n",
1208c2ecf20Sopenharmony_ci			      val);
1218c2ecf20Sopenharmony_ci
1228c2ecf20Sopenharmony_ci	if (!err)
1238c2ecf20Sopenharmony_ci		return 0;
1248c2ecf20Sopenharmony_ci
1258c2ecf20Sopenharmony_ci	gensts.bits = ioread32(idxd->reg_base + IDXD_GENSTATS_OFFSET);
1268c2ecf20Sopenharmony_ci	if (gensts.state == IDXD_DEVICE_STATE_HALT) {
1278c2ecf20Sopenharmony_ci		idxd->state = IDXD_DEV_HALTED;
1288c2ecf20Sopenharmony_ci		if (gensts.reset_type == IDXD_DEVICE_RESET_SOFTWARE) {
1298c2ecf20Sopenharmony_ci			/*
1308c2ecf20Sopenharmony_ci			 * If we need a software reset, we will throw the work
1318c2ecf20Sopenharmony_ci			 * on a system workqueue in order to allow interrupts
1328c2ecf20Sopenharmony_ci			 * for the device command completions.
1338c2ecf20Sopenharmony_ci			 */
1348c2ecf20Sopenharmony_ci			INIT_WORK(&idxd->work, idxd_device_reinit);
1358c2ecf20Sopenharmony_ci			queue_work(idxd->wq, &idxd->work);
1368c2ecf20Sopenharmony_ci		} else {
1378c2ecf20Sopenharmony_ci			spin_lock_bh(&idxd->dev_lock);
1388c2ecf20Sopenharmony_ci			idxd_device_wqs_clear_state(idxd);
1398c2ecf20Sopenharmony_ci			dev_err(&idxd->pdev->dev,
1408c2ecf20Sopenharmony_ci				"idxd halted, need %s.\n",
1418c2ecf20Sopenharmony_ci				gensts.reset_type == IDXD_DEVICE_RESET_FLR ?
1428c2ecf20Sopenharmony_ci				"FLR" : "system reset");
1438c2ecf20Sopenharmony_ci			spin_unlock_bh(&idxd->dev_lock);
1448c2ecf20Sopenharmony_ci			return -ENXIO;
1458c2ecf20Sopenharmony_ci		}
1468c2ecf20Sopenharmony_ci	}
1478c2ecf20Sopenharmony_ci
1488c2ecf20Sopenharmony_ci	return 0;
1498c2ecf20Sopenharmony_ci}
1508c2ecf20Sopenharmony_ci
1518c2ecf20Sopenharmony_ciirqreturn_t idxd_misc_thread(int vec, void *data)
1528c2ecf20Sopenharmony_ci{
1538c2ecf20Sopenharmony_ci	struct idxd_irq_entry *irq_entry = data;
1548c2ecf20Sopenharmony_ci	struct idxd_device *idxd = irq_entry->idxd;
1558c2ecf20Sopenharmony_ci	int rc;
1568c2ecf20Sopenharmony_ci	u32 cause;
1578c2ecf20Sopenharmony_ci
1588c2ecf20Sopenharmony_ci	cause = ioread32(idxd->reg_base + IDXD_INTCAUSE_OFFSET);
1598c2ecf20Sopenharmony_ci	if (cause)
1608c2ecf20Sopenharmony_ci		iowrite32(cause, idxd->reg_base + IDXD_INTCAUSE_OFFSET);
1618c2ecf20Sopenharmony_ci
1628c2ecf20Sopenharmony_ci	while (cause) {
1638c2ecf20Sopenharmony_ci		rc = process_misc_interrupts(idxd, cause);
1648c2ecf20Sopenharmony_ci		if (rc < 0)
1658c2ecf20Sopenharmony_ci			break;
1668c2ecf20Sopenharmony_ci		cause = ioread32(idxd->reg_base + IDXD_INTCAUSE_OFFSET);
1678c2ecf20Sopenharmony_ci		if (cause)
1688c2ecf20Sopenharmony_ci			iowrite32(cause, idxd->reg_base + IDXD_INTCAUSE_OFFSET);
1698c2ecf20Sopenharmony_ci	}
1708c2ecf20Sopenharmony_ci
1718c2ecf20Sopenharmony_ci	idxd_unmask_msix_vector(idxd, irq_entry->id);
1728c2ecf20Sopenharmony_ci	return IRQ_HANDLED;
1738c2ecf20Sopenharmony_ci}
1748c2ecf20Sopenharmony_ci
1758c2ecf20Sopenharmony_cistatic int irq_process_pending_llist(struct idxd_irq_entry *irq_entry,
1768c2ecf20Sopenharmony_ci				     int *processed)
1778c2ecf20Sopenharmony_ci{
1788c2ecf20Sopenharmony_ci	struct idxd_desc *desc, *t;
1798c2ecf20Sopenharmony_ci	struct llist_node *head;
1808c2ecf20Sopenharmony_ci	int queued = 0;
1818c2ecf20Sopenharmony_ci
1828c2ecf20Sopenharmony_ci	*processed = 0;
1838c2ecf20Sopenharmony_ci	head = llist_del_all(&irq_entry->pending_llist);
1848c2ecf20Sopenharmony_ci	if (!head)
1858c2ecf20Sopenharmony_ci		return 0;
1868c2ecf20Sopenharmony_ci
1878c2ecf20Sopenharmony_ci	llist_for_each_entry_safe(desc, t, head, llnode) {
1888c2ecf20Sopenharmony_ci		if (desc->completion->status) {
1898c2ecf20Sopenharmony_ci			idxd_dma_complete_txd(desc, IDXD_COMPLETE_NORMAL);
1908c2ecf20Sopenharmony_ci			idxd_free_desc(desc->wq, desc);
1918c2ecf20Sopenharmony_ci			(*processed)++;
1928c2ecf20Sopenharmony_ci		} else {
1938c2ecf20Sopenharmony_ci			list_add_tail(&desc->list, &irq_entry->work_list);
1948c2ecf20Sopenharmony_ci			queued++;
1958c2ecf20Sopenharmony_ci		}
1968c2ecf20Sopenharmony_ci	}
1978c2ecf20Sopenharmony_ci
1988c2ecf20Sopenharmony_ci	return queued;
1998c2ecf20Sopenharmony_ci}
2008c2ecf20Sopenharmony_ci
2018c2ecf20Sopenharmony_cistatic int irq_process_work_list(struct idxd_irq_entry *irq_entry,
2028c2ecf20Sopenharmony_ci				 int *processed)
2038c2ecf20Sopenharmony_ci{
2048c2ecf20Sopenharmony_ci	struct list_head *node, *next;
2058c2ecf20Sopenharmony_ci	int queued = 0;
2068c2ecf20Sopenharmony_ci
2078c2ecf20Sopenharmony_ci	*processed = 0;
2088c2ecf20Sopenharmony_ci	if (list_empty(&irq_entry->work_list))
2098c2ecf20Sopenharmony_ci		return 0;
2108c2ecf20Sopenharmony_ci
2118c2ecf20Sopenharmony_ci	list_for_each_safe(node, next, &irq_entry->work_list) {
2128c2ecf20Sopenharmony_ci		struct idxd_desc *desc =
2138c2ecf20Sopenharmony_ci			container_of(node, struct idxd_desc, list);
2148c2ecf20Sopenharmony_ci
2158c2ecf20Sopenharmony_ci		if (desc->completion->status) {
2168c2ecf20Sopenharmony_ci			list_del(&desc->list);
2178c2ecf20Sopenharmony_ci			/* process and callback */
2188c2ecf20Sopenharmony_ci			idxd_dma_complete_txd(desc, IDXD_COMPLETE_NORMAL);
2198c2ecf20Sopenharmony_ci			idxd_free_desc(desc->wq, desc);
2208c2ecf20Sopenharmony_ci			(*processed)++;
2218c2ecf20Sopenharmony_ci		} else {
2228c2ecf20Sopenharmony_ci			queued++;
2238c2ecf20Sopenharmony_ci		}
2248c2ecf20Sopenharmony_ci	}
2258c2ecf20Sopenharmony_ci
2268c2ecf20Sopenharmony_ci	return queued;
2278c2ecf20Sopenharmony_ci}
2288c2ecf20Sopenharmony_ci
2298c2ecf20Sopenharmony_cistatic int idxd_desc_process(struct idxd_irq_entry *irq_entry)
2308c2ecf20Sopenharmony_ci{
2318c2ecf20Sopenharmony_ci	int rc, processed, total = 0;
2328c2ecf20Sopenharmony_ci
2338c2ecf20Sopenharmony_ci	/*
2348c2ecf20Sopenharmony_ci	 * There are two lists we are processing. The pending_llist is where
2358c2ecf20Sopenharmony_ci	 * submmiter adds all the submitted descriptor after sending it to
2368c2ecf20Sopenharmony_ci	 * the workqueue. It's a lockless singly linked list. The work_list
2378c2ecf20Sopenharmony_ci	 * is the common linux double linked list. We are in a scenario of
2388c2ecf20Sopenharmony_ci	 * multiple producers and a single consumer. The producers are all
2398c2ecf20Sopenharmony_ci	 * the kernel submitters of descriptors, and the consumer is the
2408c2ecf20Sopenharmony_ci	 * kernel irq handler thread for the msix vector when using threaded
2418c2ecf20Sopenharmony_ci	 * irq. To work with the restrictions of llist to remain lockless,
2428c2ecf20Sopenharmony_ci	 * we are doing the following steps:
2438c2ecf20Sopenharmony_ci	 * 1. Iterate through the work_list and process any completed
2448c2ecf20Sopenharmony_ci	 *    descriptor. Delete the completed entries during iteration.
2458c2ecf20Sopenharmony_ci	 * 2. llist_del_all() from the pending list.
2468c2ecf20Sopenharmony_ci	 * 3. Iterate through the llist that was deleted from the pending list
2478c2ecf20Sopenharmony_ci	 *    and process the completed entries.
2488c2ecf20Sopenharmony_ci	 * 4. If the entry is still waiting on hardware, list_add_tail() to
2498c2ecf20Sopenharmony_ci	 *    the work_list.
2508c2ecf20Sopenharmony_ci	 * 5. Repeat until no more descriptors.
2518c2ecf20Sopenharmony_ci	 */
2528c2ecf20Sopenharmony_ci	do {
2538c2ecf20Sopenharmony_ci		rc = irq_process_work_list(irq_entry, &processed);
2548c2ecf20Sopenharmony_ci		total += processed;
2558c2ecf20Sopenharmony_ci		if (rc != 0)
2568c2ecf20Sopenharmony_ci			continue;
2578c2ecf20Sopenharmony_ci
2588c2ecf20Sopenharmony_ci		rc = irq_process_pending_llist(irq_entry, &processed);
2598c2ecf20Sopenharmony_ci		total += processed;
2608c2ecf20Sopenharmony_ci	} while (rc != 0);
2618c2ecf20Sopenharmony_ci
2628c2ecf20Sopenharmony_ci	return total;
2638c2ecf20Sopenharmony_ci}
2648c2ecf20Sopenharmony_ci
2658c2ecf20Sopenharmony_ciirqreturn_t idxd_wq_thread(int irq, void *data)
2668c2ecf20Sopenharmony_ci{
2678c2ecf20Sopenharmony_ci	struct idxd_irq_entry *irq_entry = data;
2688c2ecf20Sopenharmony_ci	int processed;
2698c2ecf20Sopenharmony_ci
2708c2ecf20Sopenharmony_ci	processed = idxd_desc_process(irq_entry);
2718c2ecf20Sopenharmony_ci	idxd_unmask_msix_vector(irq_entry->idxd, irq_entry->id);
2728c2ecf20Sopenharmony_ci
2738c2ecf20Sopenharmony_ci	if (processed == 0)
2748c2ecf20Sopenharmony_ci		return IRQ_NONE;
2758c2ecf20Sopenharmony_ci
2768c2ecf20Sopenharmony_ci	return IRQ_HANDLED;
2778c2ecf20Sopenharmony_ci}
278