1/*-
2 * Copyright (c) 2015 Ruslan Bukin <br@bsdpad.com>
3 * All rights reserved.
4 *
5 * This software was developed by SRI International and the University of
6 * Cambridge Computer Laboratory under DARPA/AFRL contract (FA8750-10-C-0237)
7 * ("CTSRD"), as part of the DARPA CRASH research programme.
8 *
9 * Redistribution and use in source and binary forms, with or without
10 * modification, are permitted provided that the following conditions
11 * are met:
12 * 1. Redistributions of source code must retain the above copyright
13 *    notice, this list of conditions and the following disclaimer.
14 * 2. Redistributions in binary form must reproduce the above copyright
15 *    notice, this list of conditions and the following disclaimer in the
16 *    documentation and/or other materials provided with the distribution.
17 *
18 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
19 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
20 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
21 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
22 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
23 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
24 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
25 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
26 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
27 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
28 * SUCH DAMAGE.
29 */
30
31#include <sys/param.h>
32#include <sys/systm.h>
33#include <sys/bus.h>
34#include <sys/kernel.h>
35#include <sys/module.h>
36#include <sys/rman.h>
37
38#include <dev/ofw/openfirm.h>
39#include <dev/ofw/ofw_bus.h>
40#include <dev/ofw/ofw_bus_subr.h>
41
42#include "pmu.h"
43
44static struct ofw_compat_data compat_data[] = {
45	{"arm,armv8-pmuv3",	1},
46	{"arm,cortex-a77-pmu",  1},
47	{"arm,cortex-a76-pmu",  1},
48	{"arm,cortex-a75-pmu",  1},
49	{"arm,cortex-a73-pmu",  1},
50	{"arm,cortex-a72-pmu",  1},
51	{"arm,cortex-a65-pmu",  1},
52	{"arm,cortex-a57-pmu",  1},
53	{"arm,cortex-a55-pmu",  1},
54	{"arm,cortex-a53-pmu",  1},
55	{"arm,cortex-a34-pmu",  1},
56
57	{"arm,cortex-a17-pmu",	1},
58	{"arm,cortex-a15-pmu",	1},
59	{"arm,cortex-a12-pmu",	1},
60	{"arm,cortex-a9-pmu",	1},
61	{"arm,cortex-a8-pmu",	1},
62	{"arm,cortex-a7-pmu",	1},
63	{"arm,cortex-a5-pmu",	1},
64	{"arm,arm11mpcore-pmu",	1},
65	{"arm,arm1176-pmu",	1},
66	{"arm,arm1136-pmu",	1},
67	{"qcom,krait-pmu",	1},
68	{NULL,			0}
69};
70
71static int
72pmu_fdt_probe(device_t dev)
73{
74
75	if (!ofw_bus_status_okay(dev))
76		return (ENXIO);
77
78	if (ofw_bus_search_compatible(dev, compat_data)->ocd_data != 0) {
79		device_set_desc(dev, "Performance Monitoring Unit");
80		return (BUS_PROBE_DEFAULT);
81	}
82
83	return (ENXIO);
84}
85
86static int
87pmu_parse_affinity(device_t dev, struct pmu_softc *sc, struct pmu_intr *irq,
88    phandle_t xref, uint32_t mpidr)
89{
90	struct pcpu *pcpu;
91	int i, err;
92
93
94	if (xref  != 0) {
95		err = OF_getencprop(OF_node_from_xref(xref), "reg", &mpidr,
96		    sizeof(mpidr));
97		if (err < 0) {
98			device_printf(dev, "missing 'reg' property\n");
99				return (ENXIO);
100		}
101	}
102
103	for (i = 0; i < MAXCPU; i++) {
104		pcpu = pcpu_find(i);
105		if (pcpu != NULL && PCPU_GET_MPIDR(pcpu) == mpidr) {
106			irq->cpuid = i;
107			return (0);
108		}
109	}
110
111	device_printf(dev, "Cannot find CPU with MPIDR: 0x%08X\n", mpidr);
112	return (ENXIO);
113}
114
115static int
116pmu_parse_intr(device_t dev, struct pmu_softc *sc)
117{
118	bool has_affinity;
119	phandle_t node, *cpus;
120	int rid, err, ncpus, i;
121
122
123	node = ofw_bus_get_node(dev);
124	has_affinity = OF_hasprop(node, "interrupt-affinity");
125
126	for (i = 0; i < MAX_RLEN; i++)
127		sc->irq[i].cpuid = -1;
128
129	cpus = NULL;
130	if (has_affinity) {
131		ncpus = OF_getencprop_alloc_multi(node, "interrupt-affinity",
132		    sizeof(*cpus), (void **)&cpus);
133		if (ncpus < 0) {
134			device_printf(dev,
135			    "Cannot read interrupt affinity property\n");
136			return (ENXIO);
137		}
138	}
139
140	/* Process first interrupt */
141	rid = 0;
142	sc->irq[0].res = bus_alloc_resource_any(dev, SYS_RES_IRQ, &rid,
143	    RF_ACTIVE | RF_SHAREABLE);
144
145	if (sc->irq[0].res == NULL) {
146		device_printf(dev, "Cannot get interrupt\n");
147		err = ENXIO;
148		goto done;
149	}
150
151	/* Check if PMU have one per-CPU interrupt */
152	if (intr_is_per_cpu(sc->irq[0].res)) {
153		if (has_affinity) {
154			device_printf(dev,
155			    "Per CPU interupt have declared affinity\n");
156			err = ENXIO;
157			goto done;
158		}
159		return (0);
160	}
161
162	/*
163	 * PMU with set of generic interrupts (one per core)
164	 * Each one must be binded to exact core.
165	 */
166	err = pmu_parse_affinity(dev, sc, sc->irq + 0,
167	    has_affinity ? cpus[0] : 0, 0);
168	if (err != 0) {
169		device_printf(dev, "Cannot parse affinity for CPUid: 0\n");
170		goto done;
171	}
172
173	for (i = 1; i < MAX_RLEN; i++) {
174		rid = i;
175		sc->irq[i].res = bus_alloc_resource_any(dev, SYS_RES_IRQ,
176		    &rid, RF_ACTIVE | RF_SHAREABLE);
177		if (sc->irq[i].res == NULL)
178			break;
179
180		if (intr_is_per_cpu(sc->irq[i].res))
181		{
182			device_printf(dev, "Unexpected per CPU interupt\n");
183			err = ENXIO;
184			goto done;
185		}
186
187		if (has_affinity && i >= ncpus) {
188			device_printf(dev, "Missing value in interrupt "
189			    "affinity property\n");
190			err = ENXIO;
191			goto done;
192		}
193
194		err = pmu_parse_affinity(dev, sc, sc->irq + i,
195		    has_affinity ? cpus[i] : 0, i);
196		if (err != 0) {
197			device_printf(dev,
198			   "Cannot parse affinity for CPUid: %d.\n", i);
199			goto done;
200		}
201	}
202	err = 0;
203done:
204	OF_prop_free(cpus);
205	return (err);
206}
207
208static int
209pmu_fdt_attach(device_t dev)
210{
211	struct pmu_softc *sc;
212	int err;
213
214	sc = device_get_softc(dev);
215	err = pmu_parse_intr(dev, sc);
216	if (err != 0)
217		return (err);
218
219	return (pmu_attach(dev));
220}
221
222static device_method_t pmu_fdt_methods[] = {
223	DEVMETHOD(device_probe,		pmu_fdt_probe),
224	DEVMETHOD(device_attach,	pmu_fdt_attach),
225	{ 0, 0 }
226};
227
228static driver_t pmu_fdt_driver = {
229	"pmu",
230	pmu_fdt_methods,
231	sizeof(struct pmu_softc),
232};
233
234DRIVER_MODULE(pmu, simplebus, pmu_fdt_driver, 0, 0);
235