1/*-
2 * Copyright (c) 2015,2016 Annapurna Labs Ltd. and affiliates
3 * All rights reserved.
4 *
5 * Developed by Semihalf.
6 *
7 * Redistribution and use in source and binary forms, with or without
8 * modification, are permitted provided that the following conditions
9 * are met:
10 * 1. Redistributions of source code must retain the above copyright
11 *    notice, this list of conditions and the following disclaimer.
12 * 2. Redistributions in binary form must reproduce the above copyright
13 *    notice, this list of conditions and the following disclaimer in the
14 *    documentation and/or other materials provided with the distribution.
15 *
16 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
17 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
18 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
19 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
20 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
21 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
22 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
23 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
24 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
25 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
26 * SUCH DAMAGE.
27 */
28
29#include <sys/cdefs.h>
30__FBSDID("$FreeBSD$");
31
32#include <sys/param.h>
33#include <sys/systm.h>
34#include <sys/kernel.h>
35#include <sys/lock.h>
36#include <sys/malloc.h>
37#include <sys/module.h>
38#include <sys/mutex.h>
39#include <sys/bus.h>
40#include <sys/rman.h>
41#include <sys/vmem.h>
42
43#include <dev/ofw/ofw_bus.h>
44#include <dev/ofw/ofw_bus_subr.h>
45
46#include "msi_if.h"
47#include "pic_if.h"
48
49#define	AL_SPI_INTR		0
50#define	AL_EDGE_HIGH		1
51#define	ERR_NOT_IN_MAP		-1
52#define	IRQ_OFFSET		1
53#define	GIC_INTR_CELL_CNT	3
54#define	INTR_RANGE_COUNT	2
55#define	MAX_MSIX_COUNT		160
56
57static int al_msix_attach(device_t);
58static int al_msix_probe(device_t);
59
60static msi_alloc_msi_t al_msix_alloc_msi;
61static msi_release_msi_t al_msix_release_msi;
62static msi_alloc_msix_t al_msix_alloc_msix;
63static msi_release_msix_t al_msix_release_msix;
64static msi_map_msi_t al_msix_map_msi;
65
66static int al_find_intr_pos_in_map(device_t, struct intr_irqsrc *);
67
68static struct ofw_compat_data compat_data[] = {
69	{"annapurna-labs,al-msix",	true},
70	{"annapurna-labs,alpine-msix",	true},
71	{NULL,				false}
72};
73
74/*
75 * Bus interface definitions.
76 */
77static device_method_t al_msix_methods[] = {
78	DEVMETHOD(device_probe,		al_msix_probe),
79	DEVMETHOD(device_attach,	al_msix_attach),
80
81	/* Interrupt controller interface */
82	DEVMETHOD(msi_alloc_msi,	al_msix_alloc_msi),
83	DEVMETHOD(msi_release_msi,	al_msix_release_msi),
84	DEVMETHOD(msi_alloc_msix,	al_msix_alloc_msix),
85	DEVMETHOD(msi_release_msix,	al_msix_release_msix),
86	DEVMETHOD(msi_map_msi,		al_msix_map_msi),
87
88	DEVMETHOD_END
89};
90
91struct al_msix_softc {
92	bus_addr_t	base_addr;
93	struct resource	*res;
94	uint32_t	irq_min;
95	uint32_t	irq_max;
96	uint32_t	irq_count;
97	struct mtx	msi_mtx;
98	vmem_t		*irq_alloc;
99	device_t	gic_dev;
100	/* Table of isrcs maps isrc pointer to vmem_alloc'd irq number */
101	struct intr_irqsrc	*isrcs[MAX_MSIX_COUNT];
102};
103
104static driver_t al_msix_driver = {
105	"al_msix",
106	al_msix_methods,
107	sizeof(struct al_msix_softc),
108};
109
110devclass_t al_msix_devclass;
111
112DRIVER_MODULE(al_msix, ofwbus, al_msix_driver, al_msix_devclass, 0, 0);
113DRIVER_MODULE(al_msix, simplebus, al_msix_driver, al_msix_devclass, 0, 0);
114
115MALLOC_DECLARE(M_AL_MSIX);
116MALLOC_DEFINE(M_AL_MSIX, "al_msix", "Alpine MSIX");
117
118static int
119al_msix_probe(device_t dev)
120{
121
122	if (!ofw_bus_status_okay(dev))
123		return (ENXIO);
124
125	if (!ofw_bus_search_compatible(dev, compat_data)->ocd_data)
126		return (ENXIO);
127
128	device_set_desc(dev, "Annapurna-Labs MSI-X Controller");
129	return (BUS_PROBE_DEFAULT);
130}
131
132static int
133al_msix_attach(device_t dev)
134{
135	struct al_msix_softc	*sc;
136	device_t		gic_dev;
137	phandle_t		iparent;
138	phandle_t		node;
139	intptr_t		xref;
140	int			interrupts[INTR_RANGE_COUNT];
141	int			nintr, i, rid;
142	uint32_t		icells, *intr;
143
144	sc = device_get_softc(dev);
145
146	node = ofw_bus_get_node(dev);
147	xref = OF_xref_from_node(node);
148	OF_device_register_xref(xref, dev);
149
150	rid = 0;
151	sc->res = bus_alloc_resource_any(dev, SYS_RES_MEMORY, &rid, RF_ACTIVE);
152	if (sc->res == NULL) {
153		device_printf(dev, "Failed to allocate resource\n");
154		return (ENXIO);
155	}
156
157	sc->base_addr = (bus_addr_t)rman_get_start(sc->res);
158
159	/* Register this device to handle MSI interrupts */
160	if (intr_msi_register(dev, xref) != 0) {
161		device_printf(dev, "could not register MSI-X controller\n");
162		return (ENXIO);
163	}
164	else
165		device_printf(dev, "MSI-X controller registered\n");
166
167	/* Find root interrupt controller */
168	iparent = ofw_bus_find_iparent(node);
169	if (iparent == 0) {
170		device_printf(dev, "No interrupt-parrent found. "
171				"Error in DTB\n");
172		return (ENXIO);
173	} else {
174		/* While at parent - store interrupt cells prop */
175		if (OF_searchencprop(OF_node_from_xref(iparent),
176		    "#interrupt-cells", &icells, sizeof(icells)) == -1) {
177			device_printf(dev, "DTB: Missing #interrupt-cells "
178			    "property in GIC node\n");
179			return (ENXIO);
180		}
181	}
182
183	gic_dev = OF_device_from_xref(iparent);
184	if (gic_dev == NULL) {
185		device_printf(dev, "Cannot find GIC device\n");
186		return (ENXIO);
187	}
188	sc->gic_dev = gic_dev;
189
190	/* Manually read range of interrupts from DTB */
191	nintr = OF_getencprop_alloc_multi(node, "interrupts", sizeof(*intr),
192	    (void **)&intr);
193	if (nintr == 0) {
194		device_printf(dev, "Cannot read interrupts prop from DTB\n");
195		return (ENXIO);
196	} else if ((nintr / icells) != INTR_RANGE_COUNT) {
197		/* Supposed to have min and max value only */
198		device_printf(dev, "Unexpected count of interrupts "
199				"in DTB node\n");
200		return (EINVAL);
201	}
202
203	/* Read interrupt range values */
204	for (i = 0; i < INTR_RANGE_COUNT; i++)
205		interrupts[i] = intr[(i * icells) + IRQ_OFFSET];
206
207	sc->irq_min = interrupts[0];
208	sc->irq_max = interrupts[1];
209	sc->irq_count = (sc->irq_max - sc->irq_min + 1);
210
211	if (sc->irq_count > MAX_MSIX_COUNT) {
212		device_printf(dev, "Available MSI-X count exceeds buffer size."
213				" Capping to %d\n", MAX_MSIX_COUNT);
214		sc->irq_count = MAX_MSIX_COUNT;
215	}
216
217	mtx_init(&sc->msi_mtx, "msi_mtx", NULL, MTX_DEF);
218
219	sc->irq_alloc = vmem_create("Alpine MSI-X IRQs", 0, sc->irq_count,
220	    1, 0, M_FIRSTFIT | M_WAITOK);
221
222	device_printf(dev, "MSI-X SPI IRQ %d-%d\n", sc->irq_min, sc->irq_max);
223
224	return (bus_generic_attach(dev));
225}
226
227static int
228al_find_intr_pos_in_map(device_t dev, struct intr_irqsrc *isrc)
229{
230	struct al_msix_softc *sc;
231	int i;
232
233	sc = device_get_softc(dev);
234	for (i = 0; i < MAX_MSIX_COUNT; i++)
235		if (sc->isrcs[i] == isrc)
236			return (i);
237	return (ERR_NOT_IN_MAP);
238}
239
240static int
241al_msix_map_msi(device_t dev, device_t child, struct intr_irqsrc *isrc,
242    uint64_t *addr, uint32_t *data)
243{
244	struct al_msix_softc *sc;
245	int i, spi;
246
247	sc = device_get_softc(dev);
248
249	i = al_find_intr_pos_in_map(dev, isrc);
250	if (i == ERR_NOT_IN_MAP)
251		return (EINVAL);
252
253	spi = sc->irq_min + i;
254
255	/*
256	 * MSIX message address format:
257	 * [63:20] - MSIx TBAR
258	 *           Same value as the MSIx Translation Base  Address Register
259	 * [19]    - WFE_EXIT
260	 *           Once set by MSIx message, an EVENTI is signal to the CPUs
261	 *           cluster specified by ���Local GIC Target List���
262	 * [18:17] - Target GIC ID
263	 *           Specifies which IO-GIC (external shared GIC) is targeted
264	 *           0: Local GIC, as specified by the Local GIC Target List
265	 *           1: IO-GIC 0
266	 *           2: Reserved
267	 *           3: Reserved
268	 * [16:13] - Local GIC Target List
269	 *           Specifies the Local GICs list targeted by this MSIx
270	 *           message.
271	 *           [16]  If set, SPIn is set in Cluster 0 local GIC
272	 *           [15:13] Reserved
273	 *           [15]  If set, SPIn is set in Cluster 1 local GIC
274	 *           [14]  If set, SPIn is set in Cluster 2 local GIC
275	 *           [13]  If set, SPIn is set in Cluster 3 local GIC
276	 * [12:3]  - SPIn
277	 *           Specifies the SPI (Shared Peripheral Interrupt) index to
278	 *           be set in target GICs
279	 *           Notes:
280	 *           If targeting any local GIC than only SPI[249:0] are valid
281	 * [2]     - Function vector
282	 *           MSI Data vector extension hint
283	 * [1:0]   - Reserved
284	 *           Must be set to zero
285	 */
286	*addr = (uint64_t)sc->base_addr + (uint64_t)((1 << 16) + (spi << 3));
287	*data = 0;
288
289	if (bootverbose)
290		device_printf(dev, "MSI mapping: SPI: %d addr: %jx data: %x\n",
291		    spi, (uintmax_t)*addr, *data);
292	return (0);
293}
294
295static int
296al_msix_alloc_msi(device_t dev, device_t child, int count, int maxcount,
297    device_t *pic, struct intr_irqsrc **srcs)
298{
299	struct intr_map_data_fdt *fdt_data;
300	struct al_msix_softc *sc;
301	vmem_addr_t irq_base;
302	int error;
303	u_int i, j;
304
305	sc = device_get_softc(dev);
306
307	if ((powerof2(count) == 0) || (count > 8))
308		return (EINVAL);
309
310	if (vmem_alloc(sc->irq_alloc, count, M_FIRSTFIT | M_NOWAIT,
311	    &irq_base) != 0)
312		return (ENOMEM);
313
314	/* Fabricate OFW data to get ISRC from GIC and return it */
315	fdt_data = malloc(sizeof(*fdt_data) +
316	    GIC_INTR_CELL_CNT * sizeof(pcell_t), M_AL_MSIX, M_WAITOK);
317	fdt_data->hdr.type = INTR_MAP_DATA_FDT;
318	fdt_data->iparent = 0;
319	fdt_data->ncells = GIC_INTR_CELL_CNT;
320	fdt_data->cells[0] = AL_SPI_INTR;	/* code for SPI interrupt */
321	fdt_data->cells[1] = 0;			/* SPI number (uninitialized) */
322	fdt_data->cells[2] = AL_EDGE_HIGH;	/* trig = edge, pol = high */
323
324	mtx_lock(&sc->msi_mtx);
325
326	for (i = irq_base; i < irq_base + count; i++) {
327		fdt_data->cells[1] = sc->irq_min + i;
328		error = PIC_MAP_INTR(sc->gic_dev,
329		    (struct intr_map_data *)fdt_data, srcs);
330		if (error) {
331			for (j = irq_base; j < i; j++)
332				sc->isrcs[j] = NULL;
333			mtx_unlock(&sc->msi_mtx);
334			vmem_free(sc->irq_alloc, irq_base, count);
335			free(fdt_data, M_AL_MSIX);
336			return (error);
337		}
338
339		sc->isrcs[i] = *srcs;
340		srcs++;
341	}
342
343	mtx_unlock(&sc->msi_mtx);
344	free(fdt_data, M_AL_MSIX);
345
346	if (bootverbose)
347		device_printf(dev,
348		    "MSI-X allocation: start SPI %d, count %d\n",
349		    (int)irq_base + sc->irq_min, count);
350
351	*pic = sc->gic_dev;
352
353	return (0);
354}
355
356static int
357al_msix_release_msi(device_t dev, device_t child, int count,
358    struct intr_irqsrc **srcs)
359{
360	struct al_msix_softc *sc;
361	int i, pos;
362
363	sc = device_get_softc(dev);
364
365	mtx_lock(&sc->msi_mtx);
366
367	pos = al_find_intr_pos_in_map(dev, *srcs);
368	vmem_free(sc->irq_alloc, pos, count);
369	for (i = 0; i < count; i++) {
370		pos = al_find_intr_pos_in_map(dev, *srcs);
371		if (pos != ERR_NOT_IN_MAP)
372			sc->isrcs[pos] = NULL;
373		srcs++;
374	}
375
376	mtx_unlock(&sc->msi_mtx);
377
378	return (0);
379}
380
381static int
382al_msix_alloc_msix(device_t dev, device_t child, device_t *pic,
383    struct intr_irqsrc **isrcp)
384{
385
386	return (al_msix_alloc_msi(dev, child, 1, 1, pic, isrcp));
387}
388
389static int
390al_msix_release_msix(device_t dev, device_t child, struct intr_irqsrc *isrc)
391{
392
393	return (al_msix_release_msi(dev, child, 1, &isrc));
394}
395