1 /* $OpenBSD: qcsmptp.c,v 1.2 2023/07/04 14:32:21 patrick Exp $ */
2 /*
3 * Copyright (c) 2023 Patrick Wildt <patrick@blueri.se>
4 *
5 * Permission to use, copy, modify, and distribute this software for any
6 * purpose with or without fee is hereby granted, provided that the above
7 * copyright notice and this permission notice appear in all copies.
8 *
9 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
10 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
11 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
12 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
13 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
14 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
15 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
16 */
17
18 #include <sys/param.h>
19 #include <sys/systm.h>
20 #include <sys/device.h>
21 #include <sys/malloc.h>
22 #include <sys/atomic.h>
23
24 #include <machine/bus.h>
25 #include <machine/fdt.h>
26
27 #include <dev/ofw/openfirm.h>
28 #include <dev/ofw/ofw_misc.h>
29 #include <dev/ofw/fdt.h>
30
31 #define SMP2P_MAX_ENTRY 16
32 #define SMP2P_MAX_ENTRY_NAME 16
33
34 struct qcsmptp_smem_item {
35 uint32_t magic;
36 #define SMP2P_MAGIC 0x504d5324
37 uint8_t version;
38 #define SMP2P_VERSION 1
39 unsigned features:24;
40 #define SMP2P_FEATURE_SSR_ACK (1 << 0)
41 uint16_t local_pid;
42 uint16_t remote_pid;
43 uint16_t total_entries;
44 uint16_t valid_entries;
45 uint32_t flags;
46 #define SMP2P_FLAGS_RESTART_DONE (1 << 0)
47 #define SMP2P_FLAGS_RESTART_ACK (1 << 1)
48
49 struct {
50 uint8_t name[SMP2P_MAX_ENTRY_NAME];
51 uint32_t value;
52 } entries[SMP2P_MAX_ENTRY];
53 } __packed;
54
55 struct qcsmptp_intrhand {
56 TAILQ_ENTRY(qcsmptp_intrhand) ih_q;
57 int (*ih_func)(void *);
58 void *ih_arg;
59 void *ih_ic;
60 int ih_pin;
61 int ih_enabled;
62 };
63
64 struct qcsmptp_interrupt_controller {
65 TAILQ_HEAD(,qcsmptp_intrhand) ic_intrq;
66 struct qcsmptp_softc *ic_sc;
67 struct interrupt_controller ic_ic;
68 };
69
70 struct qcsmptp_entry {
71 TAILQ_ENTRY(qcsmptp_entry) e_q;
72 char *e_name;
73 int e_namelen;
74 uint32_t *e_value;
75 uint32_t e_last_value;
76 struct qcsmptp_interrupt_controller *e_ic;
77 };
78
79 struct qcsmptp_softc {
80 struct device sc_dev;
81 int sc_node;
82 void *sc_ih;
83
84 uint16_t sc_local_pid;
85 uint16_t sc_remote_pid;
86 uint32_t sc_smem_id[2];
87
88 struct qcsmptp_smem_item *sc_in;
89 struct qcsmptp_smem_item *sc_out;
90
91 TAILQ_HEAD(,qcsmptp_entry) sc_inboundq;
92 TAILQ_HEAD(,qcsmptp_entry) sc_outboundq;
93
94 int sc_negotiated;
95 int sc_ssr_ack_enabled;
96 int sc_ssr_ack;
97
98 uint16_t sc_valid_entries;
99
100 struct mbox_channel *sc_mc;
101 };
102
103 int qcsmptp_match(struct device *, void *, void *);
104 void qcsmptp_attach(struct device *, struct device *, void *);
105 void qcsmptp_deferred(struct device *);
106
107 const struct cfattach qcsmptp_ca = {
108 sizeof (struct qcsmptp_softc), qcsmptp_match, qcsmptp_attach
109 };
110
111 struct cfdriver qcsmptp_cd = {
112 NULL, "qcsmptp", DV_DULL
113 };
114
115 int qcsmptp_intr(void *);
116 void *qcsmptp_intr_establish(void *, int *, int, struct cpu_info *,
117 int (*)(void *), void *, char *);
118 void qcsmptp_intr_disestablish(void *);
119 void qcsmptp_intr_enable(void *);
120 void qcsmptp_intr_disable(void *);
121 void qcsmptp_intr_barrier(void *);
122
123 extern int qcsmem_alloc(int, int, int);
124 extern void *qcsmem_get(int, int, int *);
125
126 int
qcsmptp_match(struct device * parent,void * match,void * aux)127 qcsmptp_match(struct device *parent, void *match, void *aux)
128 {
129 struct fdt_attach_args *faa = aux;
130
131 return OF_is_compatible(faa->fa_node, "qcom,smp2p");
132 }
133
134 void
qcsmptp_attach(struct device * parent,struct device * self,void * aux)135 qcsmptp_attach(struct device *parent, struct device *self, void *aux)
136 {
137 struct qcsmptp_softc *sc = (struct qcsmptp_softc *)self;
138 struct fdt_attach_args *faa = aux;
139
140 sc->sc_node = faa->fa_node;
141
142 TAILQ_INIT(&sc->sc_inboundq);
143 TAILQ_INIT(&sc->sc_outboundq);
144
145 sc->sc_ih = fdt_intr_establish(faa->fa_node, IPL_BIO,
146 qcsmptp_intr, sc, sc->sc_dev.dv_xname);
147 if (sc->sc_ih == NULL) {
148 printf(": can't establish interrupt\n");
149 return;
150 }
151
152 sc->sc_local_pid = OF_getpropint(faa->fa_node,
153 "qcom,local-pid", 0xffff);
154 sc->sc_remote_pid = OF_getpropint(faa->fa_node,
155 "qcom,remote-pid", 0xffff);
156 if (sc->sc_local_pid == 0xffff || sc->sc_remote_pid == 0xffff) {
157 printf(": can't get pids\n");
158 return;
159 }
160
161 if (OF_getpropintarray(faa->fa_node, "qcom,smem", sc->sc_smem_id,
162 sizeof(sc->sc_smem_id)) != sizeof(sc->sc_smem_id)) {
163 printf(": can't get smem property \n");
164 return;
165 }
166
167 printf("\n");
168
169 config_defer(self, qcsmptp_deferred);
170 }
171
172 void
qcsmptp_deferred(struct device * self)173 qcsmptp_deferred(struct device *self)
174 {
175 struct qcsmptp_softc *sc = (struct qcsmptp_softc *)self;
176 struct qcsmptp_entry *e;
177 int node, size;
178
179 sc->sc_mc = mbox_channel_idx(sc->sc_node, 0, NULL);
180 if (sc->sc_mc == NULL) {
181 printf(": can't get mailbox\n");
182 return;
183 }
184
185 if (qcsmem_alloc(sc->sc_remote_pid, sc->sc_smem_id[0],
186 sizeof(*sc->sc_in)) != 0) {
187 printf(": can't alloc smp2p item\n");
188 return;
189 }
190
191 sc->sc_in = qcsmem_get(sc->sc_remote_pid, sc->sc_smem_id[0], NULL);
192 if (sc->sc_in == NULL) {
193 printf(": can't get smp2p item\n");
194 return;
195 }
196
197 if (qcsmem_alloc(sc->sc_remote_pid, sc->sc_smem_id[1],
198 sizeof(*sc->sc_out)) != 0) {
199 printf(": can't alloc smp2p item\n");
200 return;
201 }
202
203 sc->sc_out = qcsmem_get(sc->sc_remote_pid, sc->sc_smem_id[1], NULL);
204 if (sc->sc_out == NULL) {
205 printf(": can't get smp2p item\n");
206 return;
207 }
208
209 memset(sc->sc_out, 0, sizeof(*sc->sc_out));
210 sc->sc_out->magic = SMP2P_MAGIC;
211 sc->sc_out->local_pid = sc->sc_local_pid;
212 sc->sc_out->remote_pid = sc->sc_remote_pid;
213 sc->sc_out->total_entries = SMP2P_MAX_ENTRY;
214 sc->sc_out->features = SMP2P_FEATURE_SSR_ACK;
215 membar_sync();
216 sc->sc_out->version = SMP2P_VERSION;
217 mbox_send(sc->sc_mc, NULL, 0);
218
219 for (node = OF_child(sc->sc_node); node; node = OF_peer(node)) {
220 size = OF_getproplen(node, "qcom,entry-name");
221 if (size <= 0)
222 continue;
223 e = malloc(sizeof(*e), M_DEVBUF, M_WAITOK | M_ZERO);
224 e->e_namelen = size;
225 e->e_name = malloc(e->e_namelen, M_DEVBUF, M_WAITOK);
226 OF_getprop(node, "qcom,entry-name", e->e_name,
227 e->e_namelen);
228 e->e_name[e->e_namelen - 1] = '\0';
229
230 if (OF_getproplen(node, "interrupt-controller") == 0) {
231 struct qcsmptp_interrupt_controller *ic;
232 ic = malloc(sizeof(*ic), M_DEVBUF, M_WAITOK | M_ZERO);
233 TAILQ_INIT(&ic->ic_intrq);
234 ic->ic_sc = sc;
235 ic->ic_ic.ic_node = node;
236 ic->ic_ic.ic_cookie = ic;
237 ic->ic_ic.ic_establish = qcsmptp_intr_establish;
238 ic->ic_ic.ic_disestablish = qcsmptp_intr_disestablish;
239 ic->ic_ic.ic_enable = qcsmptp_intr_enable;
240 ic->ic_ic.ic_disable = qcsmptp_intr_disable;
241 ic->ic_ic.ic_barrier = qcsmptp_intr_barrier;
242 fdt_intr_register(&ic->ic_ic);
243 e->e_ic = ic;
244 TAILQ_INSERT_TAIL(&sc->sc_inboundq, e, e_q);
245 } else {
246 if (sc->sc_out->valid_entries >= SMP2P_MAX_ENTRY)
247 continue;
248
249 strlcpy(sc->sc_out->entries[sc->sc_out->valid_entries].name,
250 e->e_name, SMP2P_MAX_ENTRY_NAME);
251 e->e_value = &sc->sc_out->entries[sc->sc_out->valid_entries].value;
252 sc->sc_out->valid_entries++;
253 TAILQ_INSERT_TAIL(&sc->sc_outboundq, e, e_q);
254
255 /* TODO: provide as smem state */
256 }
257 }
258 }
259
260 int
qcsmptp_intr(void * arg)261 qcsmptp_intr(void *arg)
262 {
263 struct qcsmptp_softc *sc = arg;
264 struct qcsmptp_entry *e;
265 struct qcsmptp_intrhand *ih;
266 uint32_t changed, val;
267 int do_ack = 0, i;
268
269 /* Do initial feature negotiation if inbound is new. */
270 if (!sc->sc_negotiated) {
271 if (sc->sc_in->version != sc->sc_out->version)
272 return 1;
273 sc->sc_out->features &= sc->sc_in->features;
274 if (sc->sc_out->features & SMP2P_FEATURE_SSR_ACK)
275 sc->sc_ssr_ack_enabled = 1;
276 sc->sc_negotiated = 1;
277 }
278 if (!sc->sc_negotiated)
279 return 1;
280
281 /* Use ACK mechanism if negotiated. */
282 if (sc->sc_ssr_ack_enabled &&
283 !!(sc->sc_in->flags & SMP2P_FLAGS_RESTART_DONE) != sc->sc_ssr_ack)
284 do_ack = 1;
285
286 /* Catch up on new inbound entries that got added in the meantime. */
287 for (i = sc->sc_valid_entries; i < sc->sc_in->valid_entries; i++) {
288 TAILQ_FOREACH(e, &sc->sc_inboundq, e_q) {
289 if (strncmp(sc->sc_in->entries[i].name, e->e_name,
290 SMP2P_MAX_ENTRY_NAME) != 0)
291 continue;
292 e->e_value = &sc->sc_in->entries[i].value;
293 }
294 }
295 sc->sc_valid_entries = i;
296
297 /* For each inbound "interrupt controller". */
298 TAILQ_FOREACH(e, &sc->sc_inboundq, e_q) {
299 if (e->e_value == NULL)
300 continue;
301 val = *e->e_value;
302 if (val == e->e_last_value)
303 continue;
304 changed = val ^ e->e_last_value;
305 e->e_last_value = val;
306 TAILQ_FOREACH(ih, &e->e_ic->ic_intrq, ih_q) {
307 if (!ih->ih_enabled)
308 continue;
309 if ((changed & (1 << ih->ih_pin)) == 0)
310 continue;
311 ih->ih_func(ih->ih_arg);
312 }
313 }
314
315 if (do_ack) {
316 sc->sc_ssr_ack = !sc->sc_ssr_ack;
317 if (sc->sc_ssr_ack)
318 sc->sc_out->flags |= SMP2P_FLAGS_RESTART_ACK;
319 else
320 sc->sc_out->flags &= ~SMP2P_FLAGS_RESTART_ACK;
321 membar_sync();
322 mbox_send(sc->sc_mc, NULL, 0);
323 }
324
325 return 1;
326 }
327
328 void *
qcsmptp_intr_establish(void * cookie,int * cells,int ipl,struct cpu_info * ci,int (* func)(void *),void * arg,char * name)329 qcsmptp_intr_establish(void *cookie, int *cells, int ipl,
330 struct cpu_info *ci, int (*func)(void *), void *arg, char *name)
331 {
332 struct qcsmptp_interrupt_controller *ic = cookie;
333 struct qcsmptp_softc *sc = ic->ic_sc;
334 struct qcsmptp_intrhand *ih;
335
336 ih = malloc(sizeof(*ih), M_DEVBUF, M_WAITOK | M_ZERO);
337 ih->ih_func = func;
338 ih->ih_arg = arg;
339 ih->ih_ic = ic;
340 ih->ih_pin = cells[0];
341 TAILQ_INSERT_TAIL(&ic->ic_intrq, ih, ih_q);
342
343 qcsmptp_intr_enable(ih);
344
345 if (ipl & IPL_WAKEUP)
346 intr_set_wakeup(sc->sc_ih);
347
348 return ih;
349 }
350
351 void
qcsmptp_intr_disestablish(void * cookie)352 qcsmptp_intr_disestablish(void *cookie)
353 {
354 struct qcsmptp_intrhand *ih = cookie;
355 struct qcsmptp_interrupt_controller *ic = ih->ih_ic;
356
357 qcsmptp_intr_disable(ih);
358
359 TAILQ_REMOVE(&ic->ic_intrq, ih, ih_q);
360 free(ih, M_DEVBUF, sizeof(*ih));
361 }
362
363 void
qcsmptp_intr_enable(void * cookie)364 qcsmptp_intr_enable(void *cookie)
365 {
366 struct qcsmptp_intrhand *ih = cookie;
367
368 ih->ih_enabled = 1;
369 }
370
371 void
qcsmptp_intr_disable(void * cookie)372 qcsmptp_intr_disable(void *cookie)
373 {
374 struct qcsmptp_intrhand *ih = cookie;
375
376 ih->ih_enabled = 0;
377 }
378
379 void
qcsmptp_intr_barrier(void * cookie)380 qcsmptp_intr_barrier(void *cookie)
381 {
382 struct qcsmptp_intrhand *ih = cookie;
383 struct qcsmptp_interrupt_controller *ic = ih->ih_ic;
384 struct qcsmptp_softc *sc = ic->ic_sc;
385
386 intr_barrier(sc->sc_ih);
387 }
388