summaryrefslogtreecommitdiff
path: root/usr/src/uts/common/io/ena/ena_intr.c
blob: 2650609cfabb71813d7766fd550773af66feed2b (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
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
/*
 * This file and its contents are supplied under the terms of the
 * Common Development and Distribution License ("CDDL"), version 1.0.
 * You may only use this file in accordance with the terms of version
 * 1.0 of the CDDL.
 *
 * A full copy of the text of the CDDL should have accompanied this
 * source.  A copy of the CDDL is also available via the Internet at
 * http://www.illumos.org/license/CDDL.
 */

/*
 * Copyright 2021 Oxide Computer Company
 */
#include "ena.h"

/*
 * We currently limit the number of Tx/Rx queues to the number of
 * available interrupts (minus one for the admin queue).
 */
static uint_t
ena_io_intr(caddr_t arg1, caddr_t arg2)
{
	ena_t *ena = (ena_t *)arg1;
	uint16_t vector = (uintptr_t)(void *)arg2;
	ASSERT3U(vector, >, 0);
	ASSERT3U(vector, <, ena->ena_num_intrs);
	ena_txq_t *txq = &ena->ena_txqs[vector - 1];
	ena_rxq_t *rxq = &ena->ena_rxqs[vector - 1];
	uint32_t intr_ctrl;

	ASSERT3P(txq, !=, NULL);
	ASSERT3P(rxq, !=, NULL);
	ena_tx_intr_work(txq);
	ena_rx_intr_work(rxq);

	/*
	 * The Rx/Tx queue share the same interrupt, only need to
	 * unmask interrupts for one of them.
	 */
	intr_ctrl = ena_hw_abs_read32(ena, txq->et_cq_unmask_addr);
	ENAHW_REG_INTR_UNMASK(intr_ctrl);
	ena_hw_abs_write32(ena, txq->et_cq_unmask_addr, intr_ctrl);
	return (DDI_INTR_CLAIMED);
}

static uint_t
ena_admin_intr(caddr_t arg1, caddr_t arg2)
{
	ena_t *ena = (ena_t *)arg1;

	ena_aenq_work(ena);
	return (DDI_INTR_CLAIMED);
}

void
ena_intr_remove_handlers(ena_t *ena)
{
	for (int i = 0; i < ena->ena_num_intrs; i++) {
		int ret = ddi_intr_remove_handler(ena->ena_intr_handles[i]);

		/* Nothing we can really do except log. */
		if (ret != DDI_SUCCESS) {
			ena_err(ena, "failed to remove interrupt handler for "
			    "vector %d: %d", i, ret);
		}
	}
}

/*
 * The ena driver uses separate interrupt handlers for the admin queue
 * and I/O queues.
 */
boolean_t
ena_intr_add_handlers(ena_t *ena)
{
	ASSERT3S(ena->ena_num_intrs, >=, 2);
	if (ddi_intr_add_handler(ena->ena_intr_handles[0], ena_admin_intr, ena,
	    (void *)(uintptr_t)0) != DDI_SUCCESS) {
		ena_err(ena, "failed to add admin interrupt handler");
		return (B_FALSE);
	}

	for (int i = 1; i < ena->ena_num_intrs; i++) {
		caddr_t vector = (void *)(uintptr_t)(i);
		int ret = ddi_intr_add_handler(ena->ena_intr_handles[i],
		    ena_io_intr, ena, vector);

		if (ret != DDI_SUCCESS) {
			ena_err(ena, "failed to add I/O interrupt handler "
			    "for vector %u", i);

			/*
			 * If we fail to add any I/O handler, then all
			 * successfully added handlers are removed,
			 * including the admin handler. For example,
			 * when i=2 we remove handler 1 (the first I/O
			 * handler), and when i=1 we remove handler 0
			 * (the admin handler).
			 */
			while (i >= 1) {
				i--;
				(void) ddi_intr_remove_handler(
				    ena->ena_intr_handles[i]);
			}

			return (B_FALSE);
		}
	}

	return (B_TRUE);
}

boolean_t
ena_intrs_disable(ena_t *ena)
{
	int ret;

	if (ena->ena_intr_caps & DDI_INTR_FLAG_BLOCK) {
		if ((ret = ddi_intr_block_disable(ena->ena_intr_handles,
		    ena->ena_num_intrs)) != DDI_SUCCESS) {
			ena_err(ena, "failed to block disable interrupts: %d",
			    ret);
			return (B_FALSE);
		}
	} else {
		for (int i = 0; i < ena->ena_num_intrs; i++) {
			ret = ddi_intr_disable(ena->ena_intr_handles[i]);
			if (ret != DDI_SUCCESS) {
				ena_err(ena, "failed to disable interrupt "
				    "%d: %d", i, ret);
				return (B_FALSE);
			}
		}
	}

	return (B_TRUE);
}

boolean_t
ena_intrs_enable(ena_t *ena)
{
	int ret;

	if (ena->ena_intr_caps & DDI_INTR_FLAG_BLOCK) {
		if ((ret = ddi_intr_block_enable(ena->ena_intr_handles,
		    ena->ena_num_intrs)) != DDI_SUCCESS) {
			ena_err(ena, "failed to block enable interrupts: %d",
			    ret);
			return (B_FALSE);
		}
	} else {
		for (int i = 0; i < ena->ena_num_intrs; i++) {
			if ((ret = ddi_intr_enable(ena->ena_intr_handles[i])) !=
			    DDI_SUCCESS) {
				ena_err(ena, "failed to enable interrupt "
				    "%d: %d", i, ret);

				/*
				 * If we fail to enable any interrupt,
				 * then all interrupts are disabled.
				 */
				while (i >= 1) {
					i--;
					(void) ddi_intr_disable(
					    ena->ena_intr_handles[i]);
				}

				return (B_FALSE);
			}
		}
	}

	return (B_TRUE);
}