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 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 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 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 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 * 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 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 364 qcsmptp_intr_enable(void *cookie) 365 { 366 struct qcsmptp_intrhand *ih = cookie; 367 368 ih->ih_enabled = 1; 369 } 370 371 void 372 qcsmptp_intr_disable(void *cookie) 373 { 374 struct qcsmptp_intrhand *ih = cookie; 375 376 ih->ih_enabled = 0; 377 } 378 379 void 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