summaryrefslogtreecommitdiff
path: root/drivers/dma/idxd/submit.c
blob: 19afb62abaffdc274123db07ba2edef258801c4a (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
// SPDX-License-Identifier: GPL-2.0
/* Copyright(c) 2019 Intel Corporation. All rights rsvd. */
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/pci.h>
#include <uapi/linux/idxd.h>
#include "idxd.h"
#include "registers.h"

static struct idxd_desc *__get_desc(struct idxd_wq *wq, int idx, int cpu)
{
	struct idxd_desc *desc;
	struct idxd_device *idxd = wq->idxd;

	desc = wq->descs[idx];
	memset(desc->hw, 0, sizeof(struct dsa_hw_desc));
	memset(desc->completion, 0, idxd->data->compl_size);
	desc->cpu = cpu;

	if (device_pasid_enabled(idxd))
		desc->hw->pasid = idxd->pasid;

	/*
	 * Descriptor completion vectors are 1...N for MSIX. We will round
	 * robin through the N vectors.
	 */
	wq->vec_ptr = (wq->vec_ptr % idxd->num_wq_irqs) + 1;
	if (!idxd->int_handles) {
		desc->hw->int_handle = wq->vec_ptr;
	} else {
		desc->vector = wq->vec_ptr;
		/*
		 * int_handles are only for descriptor completion. However for device
		 * MSIX enumeration, vec 0 is used for misc interrupts. Therefore even
		 * though we are rotating through 1...N for descriptor interrupts, we
		 * need to acqurie the int_handles from 0..N-1.
		 */
		desc->hw->int_handle = idxd->int_handles[desc->vector - 1];
	}

	return desc;
}

struct idxd_desc *idxd_alloc_desc(struct idxd_wq *wq, enum idxd_op_type optype)
{
	int cpu, idx;
	struct idxd_device *idxd = wq->idxd;
	DEFINE_SBQ_WAIT(wait);
	struct sbq_wait_state *ws;
	struct sbitmap_queue *sbq;

	if (idxd->state != IDXD_DEV_ENABLED)
		return ERR_PTR(-EIO);

	sbq = &wq->sbq;
	idx = sbitmap_queue_get(sbq, &cpu);
	if (idx < 0) {
		if (optype == IDXD_OP_NONBLOCK)
			return ERR_PTR(-EAGAIN);
	} else {
		return __get_desc(wq, idx, cpu);
	}

	ws = &sbq->ws[0];
	for (;;) {
		sbitmap_prepare_to_wait(sbq, ws, &wait, TASK_INTERRUPTIBLE);
		if (signal_pending_state(TASK_INTERRUPTIBLE, current))
			break;
		idx = sbitmap_queue_get(sbq, &cpu);
		if (idx > 0)
			break;
		schedule();
	}

	sbitmap_finish_wait(sbq, ws, &wait);
	if (idx < 0)
		return ERR_PTR(-EAGAIN);

	return __get_desc(wq, idx, cpu);
}

void idxd_free_desc(struct idxd_wq *wq, struct idxd_desc *desc)
{
	int cpu = desc->cpu;

	desc->cpu = -1;
	sbitmap_queue_clear(&wq->sbq, desc->id, cpu);
}

int idxd_submit_desc(struct idxd_wq *wq, struct idxd_desc *desc)
{
	struct idxd_device *idxd = wq->idxd;
	void __iomem *portal;
	int rc;

	if (idxd->state != IDXD_DEV_ENABLED)
		return -EIO;

	if (!percpu_ref_tryget_live(&wq->wq_active))
		return -ENXIO;

	portal = wq->portal;

	/*
	 * The wmb() flushes writes to coherent DMA data before
	 * possibly triggering a DMA read. The wmb() is necessary
	 * even on UP because the recipient is a device.
	 */
	wmb();
	if (wq_dedicated(wq)) {
		iosubmit_cmds512(portal, desc->hw, 1);
	} else {
		/*
		 * It's not likely that we would receive queue full rejection
		 * since the descriptor allocation gates at wq size. If we
		 * receive a -EAGAIN, that means something went wrong such as the
		 * device is not accepting descriptor at all.
		 */
		rc = enqcmds(portal, desc->hw);
		if (rc < 0)
			return rc;
	}

	percpu_ref_put(&wq->wq_active);

	/*
	 * Pending the descriptor to the lockless list for the irq_entry
	 * that we designated the descriptor to.
	 */
	if (desc->hw->flags & IDXD_OP_FLAG_RCI) {
		int vec;

		/*
		 * If the driver is on host kernel, it would be the value
		 * assigned to interrupt handle, which is index for MSIX
		 * vector. If it's guest then can't use the int_handle since
		 * that is the index to IMS for the entire device. The guest
		 * device local index will be used.
		 */
		vec = !idxd->int_handles ? desc->hw->int_handle : desc->vector;
		llist_add(&desc->llnode, &idxd->irq_entries[vec].pending_llist);
	}

	return 0;
}