1/*-
2 * Copyright (c) 2018 Microsemi Corporation.
3 * All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met:
8 * 1. Redistributions of source code must retain the above copyright
9 *    notice, this list of conditions and the following disclaimer.
10 * 2. Redistributions in binary form must reproduce the above copyright
11 *    notice, this list of conditions and the following disclaimer in the
12 *    documentation and/or other materials provided with the distribution.
13 *
14 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
15 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
17 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
18 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
19 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
20 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
21 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
22 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
23 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
24 * SUCH DAMAGE.
25 */
26
27/* $FreeBSD: stable/11/sys/dev/smartpqi/smartpqi_intr.c 333019 2018-04-26 16:59:06Z sbruno $ */
28
29#include "smartpqi_includes.h"
30
31
32/*
33 * Function to get processor count
34 */
35int os_get_processor_config(pqisrc_softstate_t *softs)
36{
37	DBG_FUNC("IN\n");
38	softs->num_cpus_online = mp_ncpus;
39	DBG_FUNC("OUT\n");
40
41	return PQI_STATUS_SUCCESS;
42}
43
44/*
45 * Function to get interrupt count and type supported
46 */
47int os_get_intr_config(pqisrc_softstate_t *softs)
48{
49	device_t dev;
50	int msi_count = 0;
51	int error = 0;
52	int ret = PQI_STATUS_SUCCESS;
53	dev = softs->os_specific.pqi_dev;
54
55	DBG_FUNC("IN\n");
56
57	msi_count = pci_msix_count(dev);
58
59	if (msi_count > softs->num_cpus_online)
60		msi_count = softs->num_cpus_online;
61	if (msi_count > PQI_MAX_MSIX)
62		msi_count = PQI_MAX_MSIX;
63	if (msi_count == 0 || (error = pci_alloc_msix(dev, &msi_count)) != 0) {
64		device_printf(dev, "alloc msix failed - msi_count=%d, err=%d; "
65                                   "will try MSI\n", msi_count, error);
66		pci_release_msi(dev);
67	} else {
68		softs->intr_count = msi_count;
69		softs->intr_type = INTR_TYPE_MSIX;
70		softs->os_specific.msi_enabled = TRUE;
71		device_printf(dev, "using MSI-X interrupts (%d vectors)\n",
72			msi_count);
73	}
74	if (!softs->intr_type) {
75		msi_count = 1;
76		if ((error = pci_alloc_msi(dev, &msi_count)) != 0) {
77			device_printf(dev, "alloc msi failed - err=%d; "
78				"will use INTx\n", error);
79			pci_release_msi(dev);
80		} else {
81			softs->os_specific.msi_enabled = TRUE;
82			softs->intr_count = msi_count;
83			softs->intr_type = INTR_TYPE_MSI;
84			device_printf(dev, "using MSI interrupts\n");
85		}
86	}
87
88	if (!softs->intr_type) {
89		device_printf(dev, "using legacy interrupts\n");
90		softs->intr_type = INTR_TYPE_FIXED;
91		softs->intr_count = 1;
92	}
93
94	if(!softs->intr_type) {
95		DBG_FUNC("OUT failed\n");
96		ret =  PQI_STATUS_FAILURE;
97		return ret;
98	}
99	DBG_FUNC("OUT\n");
100	return ret;
101}
102
103void os_eventtaskqueue_enqueue(pqisrc_softstate_t *sc)
104{
105	taskqueue_enqueue(taskqueue_swi, &sc->os_specific.event_task);
106}
107
108void pqisrc_event_worker(void *arg1, int arg2)
109{
110	pqisrc_ack_all_events(arg1);
111}
112
113/*
114 * ithread routine to handle uniprocessor systems
115 */
116static void shared_ithread_routine(void *arg)
117{
118	pqi_intr_ctx_t *intr_ctx = (pqi_intr_ctx_t *)arg;
119	pqisrc_softstate_t *softs = device_get_softc(intr_ctx->pqi_dev);
120	int oq_id  = intr_ctx->oq_id;
121
122	DBG_FUNC("IN\n");
123
124	pqisrc_process_response_queue(softs, oq_id);
125	pqisrc_process_event_intr_src(softs, oq_id - 1);
126
127	DBG_FUNC("OUT\n");
128}
129
130/*
131 * ithread routine to process non event response
132 */
133static void common_ithread_routine(void *arg)
134{
135	pqi_intr_ctx_t *intr_ctx = (pqi_intr_ctx_t *)arg;
136	pqisrc_softstate_t *softs = device_get_softc(intr_ctx->pqi_dev);
137	int oq_id  = intr_ctx->oq_id;
138
139	DBG_FUNC("IN\n");
140
141	pqisrc_process_response_queue(softs, oq_id);
142
143	DBG_FUNC("OUT\n");
144}
145
146static void event_ithread_routine(void *arg)
147{
148	pqi_intr_ctx_t *intr_ctx = (pqi_intr_ctx_t *)arg;
149	pqisrc_softstate_t *softs = device_get_softc(intr_ctx->pqi_dev);
150	int oq_id  = intr_ctx->oq_id;
151
152	DBG_FUNC("IN\n");
153
154	pqisrc_process_event_intr_src(softs, oq_id);
155
156	DBG_FUNC("OUT\n");
157}
158
159/*
160 * Registration of legacy interrupt in case MSI is unsupported
161 */
162int register_legacy_intr(pqisrc_softstate_t *softs)
163{
164	int error = 0;
165	device_t dev;
166
167	DBG_FUNC("IN\n");
168
169	dev = softs->os_specific.pqi_dev;
170
171	softs->os_specific.pqi_irq_rid[0] = 0;
172	softs->os_specific.pqi_irq[0] = bus_alloc_resource_any(dev, \
173		SYS_RES_IRQ, &softs->os_specific.pqi_irq_rid[0],
174		RF_ACTIVE | RF_SHAREABLE);
175	if (NULL == softs->os_specific.pqi_irq[0]) {
176		DBG_ERR("Failed to allocate resource for interrupt\n");
177		return PQI_STATUS_FAILURE;
178	}
179	if ((softs->os_specific.msi_ctx = os_mem_alloc(softs,sizeof(pqi_intr_ctx_t))) == NULL) {
180		DBG_ERR("Failed to allocate memory for msi_ctx\n");
181		return PQI_STATUS_FAILURE;
182	}
183	softs->os_specific.msi_ctx[0].pqi_dev = dev;
184	softs->os_specific.msi_ctx[0].oq_id = 0;
185
186	error = bus_setup_intr(dev, softs->os_specific.pqi_irq[0],
187				INTR_TYPE_CAM | INTR_MPSAFE, \
188				NULL, shared_ithread_routine,
189				&softs->os_specific.msi_ctx[0],
190				&softs->os_specific.intrcookie[0]);
191	if (error) {
192		DBG_ERR("Failed to setup legacy interrupt err = %d\n", error);
193		return error;
194	}
195	softs->os_specific.intr_registered[0] = TRUE;
196
197	DBG_FUNC("OUT error = %d\n", error);
198
199	return error;
200}
201
202/*
203 * Registration of MSIx
204 */
205int register_msix_intr(pqisrc_softstate_t *softs)
206{
207	int error = 0;
208	int i = 0;
209	device_t dev;
210	dev = softs->os_specific.pqi_dev;
211	int msix_count = softs->intr_count;
212
213	DBG_FUNC("IN\n");
214
215	softs->os_specific.msi_ctx = os_mem_alloc(softs, sizeof(pqi_intr_ctx_t) * msix_count);
216	/*Add shared handler */
217	if (softs->share_opq_and_eventq) {
218		softs->os_specific.pqi_irq_rid[i] = i+1;
219		softs->os_specific.pqi_irq[i] = bus_alloc_resource_any(dev, \
220						SYS_RES_IRQ,
221						&softs->os_specific.pqi_irq_rid[i],
222						RF_SHAREABLE |  RF_ACTIVE);
223		if (NULL == softs->os_specific.pqi_irq[i]) {
224			DBG_ERR("Failed to allocate \
225				event interrupt resource\n");
226			return PQI_STATUS_FAILURE;
227		}
228
229		softs->os_specific.msi_ctx[i].pqi_dev = dev;
230		softs->os_specific.msi_ctx[i].oq_id = i;
231
232		error = bus_setup_intr(dev,softs->os_specific.pqi_irq[i],
233					INTR_TYPE_CAM | INTR_MPSAFE,\
234					NULL,
235					shared_ithread_routine,
236					&softs->os_specific.msi_ctx[i],
237					&softs->os_specific.intrcookie[i]);
238
239		if (error) {
240			DBG_ERR("Failed to setup interrupt for events r=%d\n",
241				error);
242			return error;
243		}
244		softs->os_specific.intr_registered[i] = TRUE;
245	}
246	else {
247		/* Add event handler */
248		softs->os_specific.pqi_irq_rid[i] = i+1;
249		softs->os_specific.pqi_irq[i] = bus_alloc_resource_any(dev, \
250						SYS_RES_IRQ,
251						&softs->os_specific.pqi_irq_rid[i],
252						RF_SHAREABLE |  RF_ACTIVE);
253		if (NULL == softs->os_specific.pqi_irq[i]) {
254			DBG_ERR("ERR : Failed to allocate \
255				event interrupt resource\n");
256			return PQI_STATUS_FAILURE;
257		}
258
259
260		softs->os_specific.msi_ctx[i].pqi_dev = dev;
261		softs->os_specific.msi_ctx[i].oq_id = i;
262
263
264		error = bus_setup_intr(dev,softs->os_specific.pqi_irq[i],
265					INTR_TYPE_CAM | INTR_MPSAFE,\
266                       			NULL,
267					event_ithread_routine,
268					&softs->os_specific.msi_ctx[i],
269					&softs->os_specific.intrcookie[i]);
270		if (error) {
271			DBG_ERR("Failed to setup interrupt for events err=%d\n",
272				error);
273			return error;
274		}
275		softs->os_specific.intr_registered[i] = TRUE;
276		/* Add interrupt handlers*/
277		for (i = 1; i < msix_count; ++i) {
278			softs->os_specific.pqi_irq_rid[i] = i+1;
279			softs->os_specific.pqi_irq[i] = \
280					bus_alloc_resource_any(dev,
281					SYS_RES_IRQ,
282					&softs->os_specific.pqi_irq_rid[i],
283					RF_SHAREABLE | RF_ACTIVE);
284			if (NULL == softs->os_specific.pqi_irq[i]) {
285				DBG_ERR("Failed to allocate \
286					msi/x interrupt resource\n");
287				return PQI_STATUS_FAILURE;
288			}
289			softs->os_specific.msi_ctx[i].pqi_dev = dev;
290			softs->os_specific.msi_ctx[i].oq_id = i;
291			error = bus_setup_intr(dev,
292					softs->os_specific.pqi_irq[i],
293					INTR_TYPE_CAM | INTR_MPSAFE,\
294					NULL,
295					common_ithread_routine,
296					&softs->os_specific.msi_ctx[i],
297					&softs->os_specific.intrcookie[i]);
298			if (error) {
299				DBG_ERR("Failed to setup \
300					msi/x interrupt error = %d\n", error);
301				return error;
302			}
303			softs->os_specific.intr_registered[i] = TRUE;
304		}
305	}
306
307	DBG_FUNC("OUT error = %d\n", error);
308
309	return error;
310}
311
312/*
313 * Setup interrupt depending on the configuration
314 */
315int os_setup_intr(pqisrc_softstate_t *softs)
316{
317	int error = 0;
318
319	DBG_FUNC("IN\n");
320
321	if (softs->intr_type == INTR_TYPE_FIXED) {
322		error = register_legacy_intr(softs);
323	}
324	else {
325		error = register_msix_intr(softs);
326	}
327	if (error) {
328		DBG_FUNC("OUT failed error = %d\n", error);
329		return error;
330	}
331
332	DBG_FUNC("OUT error = %d\n", error);
333
334	return error;
335}
336
337/*
338 * Deregistration of legacy interrupt
339 */
340void deregister_pqi_intx(pqisrc_softstate_t *softs)
341{
342	device_t dev;
343
344	DBG_FUNC("IN\n");
345
346	dev = softs->os_specific.pqi_dev;
347	if (softs->os_specific.pqi_irq[0] != NULL) {
348		if (softs->os_specific.intr_registered[0]) {
349			bus_teardown_intr(dev, softs->os_specific.pqi_irq[0],
350					softs->os_specific.intrcookie[0]);
351			softs->os_specific.intr_registered[0] = FALSE;
352		}
353		bus_release_resource(dev, SYS_RES_IRQ,
354			softs->os_specific.pqi_irq_rid[0],
355			softs->os_specific.pqi_irq[0]);
356		softs->os_specific.pqi_irq[0] = NULL;
357		os_mem_free(softs, (char*)softs->os_specific.msi_ctx, sizeof(pqi_intr_ctx_t));
358	}
359
360	DBG_FUNC("OUT\n");
361}
362
363/*
364 * Deregistration of MSIx interrupt
365 */
366void deregister_pqi_msix(pqisrc_softstate_t *softs)
367{
368	device_t dev;
369	dev = softs->os_specific.pqi_dev;
370	int msix_count = softs->intr_count;
371	int i = 0;
372
373	DBG_FUNC("IN\n");
374
375	os_mem_free(softs, (char*)softs->os_specific.msi_ctx, sizeof(pqi_intr_ctx_t) * msix_count);
376	softs->os_specific.msi_ctx = NULL;
377
378	for (; i < msix_count; ++i) {
379		if (softs->os_specific.pqi_irq[i] != NULL) {
380			if (softs->os_specific.intr_registered[i]) {
381				bus_teardown_intr(dev,
382					softs->os_specific.pqi_irq[i],
383					softs->os_specific.intrcookie[i]);
384				softs->os_specific.intr_registered[i] = FALSE;
385			}
386			bus_release_resource(dev, SYS_RES_IRQ,
387				softs->os_specific.pqi_irq_rid[i],
388			softs->os_specific.pqi_irq[i]);
389			softs->os_specific.pqi_irq[i] = NULL;
390		}
391	}
392
393	DBG_FUNC("OUT\n");
394}
395
396/*
397 * Function to destroy interrupts registered
398 */
399int os_destroy_intr(pqisrc_softstate_t *softs)
400{
401	device_t dev;
402	dev = softs->os_specific.pqi_dev;
403
404	DBG_FUNC("IN\n");
405
406	if (softs->intr_type == INTR_TYPE_FIXED) {
407		deregister_pqi_intx(softs);
408	} else if (softs->intr_type == INTR_TYPE_MSIX) {
409		deregister_pqi_msix(softs);
410	}
411	if (softs->os_specific.msi_enabled) {
412		pci_release_msi(dev);
413		softs->os_specific.msi_enabled = FALSE;
414	}
415
416	DBG_FUNC("OUT\n");
417
418	return PQI_STATUS_SUCCESS;
419}
420
421/*
422 * Free interrupt related resources for the adapter
423 */
424void os_free_intr_config(pqisrc_softstate_t *softs)
425{
426	device_t dev;
427	dev = softs->os_specific.pqi_dev;
428
429	DBG_FUNC("IN\n");
430
431        if (softs->os_specific.msi_enabled) {
432                pci_release_msi(dev);
433                softs->os_specific.msi_enabled = FALSE;
434        }
435
436	DBG_FUNC("OUT\n");
437}
438