/* $NetBSD: qcomsmptp.c,v 1.1 2024/12/30 12:31:10 jmcneill Exp $ */ /* $OpenBSD: qcsmptp.c,v 1.2 2023/07/04 14:32:21 patrick Exp $ */ /* * Copyright (c) 2023 Patrick Wildt * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include #include #include #include #include #include #define SMP2P_MAX_ENTRY 16 #define SMP2P_MAX_ENTRY_NAME 16 struct qcsmptp_smem_item { uint32_t magic; #define SMP2P_MAGIC 0x504d5324 uint8_t version; #define SMP2P_VERSION 1 unsigned features:24; #define SMP2P_FEATURE_SSR_ACK (1 << 0) uint16_t local_pid; uint16_t remote_pid; uint16_t total_entries; uint16_t valid_entries; uint32_t flags; #define SMP2P_FLAGS_RESTART_DONE (1 << 0) #define SMP2P_FLAGS_RESTART_ACK (1 << 1) struct { uint8_t name[SMP2P_MAX_ENTRY_NAME]; uint32_t value; } entries[SMP2P_MAX_ENTRY]; } __packed; struct qcsmptp_intrhand { TAILQ_ENTRY(qcsmptp_intrhand) ih_q; int (*ih_func)(void *); void *ih_arg; void *ih_ic; int ih_pin; int ih_enabled; }; struct qcsmptp_interrupt_controller { TAILQ_HEAD(,qcsmptp_intrhand) ic_intrq; struct qcsmptp_softc *ic_sc; }; struct qcsmptp_entry { TAILQ_ENTRY(qcsmptp_entry) e_q; const char *e_name; uint32_t *e_value; uint32_t e_last_value; struct qcsmptp_interrupt_controller *e_ic; }; struct qcsmptp_softc { device_t sc_dev; void *sc_ih; uint16_t sc_local_pid; uint16_t sc_remote_pid; uint32_t sc_smem_id[2]; struct qcsmptp_smem_item *sc_in; struct qcsmptp_smem_item *sc_out; TAILQ_HEAD(,qcsmptp_entry) sc_inboundq; TAILQ_HEAD(,qcsmptp_entry) sc_outboundq; int sc_negotiated; int sc_ssr_ack_enabled; int sc_ssr_ack; uint16_t sc_valid_entries; void *sc_ipcc; }; static struct qcsmptp_interrupt_controller *qcsmptp_ic = NULL; static int qcsmptp_match(device_t, cfdata_t, void *); static void qcsmptp_attach(device_t, device_t, void *); static int qcsmptp_intr(void *); CFATTACH_DECL_NEW(qcomsmptp, sizeof(struct qcsmptp_softc), qcsmptp_match, qcsmptp_attach, NULL, NULL); #define IPCC_CLIENT_LPASS 3 #define IPCC_MPROC_SIGNAL_SMP2P 2 #define QCSMPTP_X1E_LOCAL_PID 0 #define QCSMPTP_X1E_REMOTE_PID 2 #define QCSMPTP_X1E_SMEM_ID0 443 #define QCSMPTP_X1E_SMEM_ID1 429 static const struct device_compatible_entry compat_data[] = { { .compat = "QCOM0C5C" }, DEVICE_COMPAT_EOL }; static int qcsmptp_match(device_t parent, cfdata_t match, void *aux) { struct acpi_attach_args *aa = aux; return acpi_compatible_match(aa, compat_data); } static void qcsmptp_attach(device_t parent, device_t self, void *aux) { struct qcsmptp_softc *sc = device_private(self); struct qcsmptp_interrupt_controller *ic; struct qcsmptp_entry *e; sc->sc_dev = self; TAILQ_INIT(&sc->sc_inboundq); TAILQ_INIT(&sc->sc_outboundq); sc->sc_ih = qcipcc_intr_establish(IPCC_CLIENT_LPASS, IPCC_MPROC_SIGNAL_SMP2P, IPL_VM, qcsmptp_intr, sc); if (sc->sc_ih == NULL) { aprint_error(": can't establish interrupt\n"); return; } sc->sc_local_pid = QCSMPTP_X1E_LOCAL_PID; sc->sc_remote_pid = QCSMPTP_X1E_REMOTE_PID; sc->sc_smem_id[0] = QCSMPTP_X1E_SMEM_ID0; sc->sc_smem_id[1] = QCSMPTP_X1E_SMEM_ID1; aprint_naive("\n"); aprint_normal("\n"); sc->sc_ipcc = qcipcc_channel(IPCC_CLIENT_LPASS, IPCC_MPROC_SIGNAL_SMP2P); if (sc->sc_ipcc == NULL) { aprint_error_dev(self, "can't get mailbox\n"); return; } if (qcsmem_alloc(sc->sc_remote_pid, sc->sc_smem_id[0], sizeof(*sc->sc_in)) != 0) { aprint_error_dev(self, "can't alloc smp2p item\n"); return; } sc->sc_in = qcsmem_get(sc->sc_remote_pid, sc->sc_smem_id[0], NULL); if (sc->sc_in == NULL) { aprint_error_dev(self, "can't get smp2p item\n"); return; } if (qcsmem_alloc(sc->sc_remote_pid, sc->sc_smem_id[1], sizeof(*sc->sc_out)) != 0) { aprint_error_dev(self, "can't alloc smp2p item\n"); return; } sc->sc_out = qcsmem_get(sc->sc_remote_pid, sc->sc_smem_id[1], NULL); if (sc->sc_out == NULL) { aprint_error_dev(self, "can't get smp2p item\n"); return; } qcsmem_memset(sc->sc_out, 0, sizeof(*sc->sc_out)); sc->sc_out->magic = SMP2P_MAGIC; sc->sc_out->local_pid = sc->sc_local_pid; sc->sc_out->remote_pid = sc->sc_remote_pid; sc->sc_out->total_entries = SMP2P_MAX_ENTRY; sc->sc_out->features = SMP2P_FEATURE_SSR_ACK; membar_sync(); sc->sc_out->version = SMP2P_VERSION; qcipcc_send(sc->sc_ipcc); e = kmem_zalloc(sizeof(*e), KM_SLEEP); e->e_name = "master-kernel"; e->e_value = &sc->sc_out->entries[sc->sc_out->valid_entries].value; sc->sc_out->valid_entries++; TAILQ_INSERT_TAIL(&sc->sc_outboundq, e, e_q); /* TODO: provide as smem state */ e = kmem_zalloc(sizeof(*e), KM_SLEEP); e->e_name = "slave-kernel"; ic = kmem_zalloc(sizeof(*ic), KM_SLEEP); TAILQ_INIT(&ic->ic_intrq); ic->ic_sc = sc; e->e_ic = ic; TAILQ_INSERT_TAIL(&sc->sc_inboundq, e, e_q); qcsmptp_ic = ic; } static int qcsmptp_intr(void *arg) { struct qcsmptp_softc *sc = arg; struct qcsmptp_entry *e; struct qcsmptp_intrhand *ih; uint32_t changed, val; int do_ack = 0, i; /* Do initial feature negotiation if inbound is new. */ if (!sc->sc_negotiated) { if (sc->sc_in->version != sc->sc_out->version) return 1; sc->sc_out->features &= sc->sc_in->features; if (sc->sc_out->features & SMP2P_FEATURE_SSR_ACK) sc->sc_ssr_ack_enabled = 1; sc->sc_negotiated = 1; } if (!sc->sc_negotiated) { return 1; } /* Use ACK mechanism if negotiated. */ if (sc->sc_ssr_ack_enabled && !!(sc->sc_in->flags & SMP2P_FLAGS_RESTART_DONE) != sc->sc_ssr_ack) do_ack = 1; /* Catch up on new inbound entries that got added in the meantime. */ for (i = sc->sc_valid_entries; i < sc->sc_in->valid_entries; i++) { TAILQ_FOREACH(e, &sc->sc_inboundq, e_q) { if (strncmp(sc->sc_in->entries[i].name, e->e_name, SMP2P_MAX_ENTRY_NAME) != 0) continue; e->e_value = &sc->sc_in->entries[i].value; } } sc->sc_valid_entries = i; /* For each inbound "interrupt controller". */ TAILQ_FOREACH(e, &sc->sc_inboundq, e_q) { if (e->e_value == NULL) continue; val = *e->e_value; if (val == e->e_last_value) continue; changed = val ^ e->e_last_value; e->e_last_value = val; TAILQ_FOREACH(ih, &e->e_ic->ic_intrq, ih_q) { if (!ih->ih_enabled) continue; if ((changed & (1 << ih->ih_pin)) == 0) continue; ih->ih_func(ih->ih_arg); } } if (do_ack) { sc->sc_ssr_ack = !sc->sc_ssr_ack; if (sc->sc_ssr_ack) sc->sc_out->flags |= SMP2P_FLAGS_RESTART_ACK; else sc->sc_out->flags &= ~SMP2P_FLAGS_RESTART_ACK; membar_sync(); qcipcc_send(sc->sc_ipcc); } return 1; } void * qcsmptp_intr_establish(u_int pin, int (*func)(void *), void *arg) { struct qcsmptp_interrupt_controller *ic = qcsmptp_ic; struct qcsmptp_intrhand *ih; if (ic == NULL) { return NULL; } ih = kmem_zalloc(sizeof(*ih), KM_SLEEP); ih->ih_func = func; ih->ih_arg = arg; ih->ih_ic = ic; ih->ih_pin = pin; TAILQ_INSERT_TAIL(&ic->ic_intrq, ih, ih_q); qcsmptp_intr_enable(ih); return ih; } void qcsmptp_intr_disestablish(void *cookie) { struct qcsmptp_intrhand *ih = cookie; struct qcsmptp_interrupt_controller *ic = ih->ih_ic; qcsmptp_intr_disable(ih); TAILQ_REMOVE(&ic->ic_intrq, ih, ih_q); kmem_free(ih, sizeof(*ih)); } void qcsmptp_intr_enable(void *cookie) { struct qcsmptp_intrhand *ih = cookie; ih->ih_enabled = 1; } void qcsmptp_intr_disable(void *cookie) { struct qcsmptp_intrhand *ih = cookie; ih->ih_enabled = 0; }