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