Module Name: src Committed By: jmcneill Date: Mon Dec 30 12:31:10 UTC 2024
Modified Files: src/sys/arch/evbarm/conf: GENERIC64 src/sys/dev/acpi: files.acpi Added Files: src/sys/dev/acpi: qcomipcc.c qcomipcc.h qcompas.c qcompep.c qcompep.h qcomscm.c qcomscm.h qcomsmem.c qcomsmem.h qcomsmptp.c qcomsmptp.h qcomspmi.c Log Message: arm64: Add support for Snapdragon X1E battery and charger sensors. This is a port of a set of drivers from OpenBSD, adapted to use ACPI instead of FDT bindings. These drivers are required to boot firmware on the application DSP which is required to read sensors. This is an impressive collection of work on the OpenBSD side -- big thank you to Patrick for writing this code and his help with this porting effort. Firmware is signed and as a result may be board specific. The qcompas(4) driver looks for firmware files in qcompas/<subsystem> where <subsystem> is the string returned by the _SUB method on the ADSP device node. For example, on Lenovo ThinkPad T14s Gen 6 the subsystem ID is "233817AA". The files "qcadsp8380.mbn" and "adsp_dtbs.elf" must be placed in this directory to read sensor data. These files can be obtained by downloading the appropriate OEM drivers for Windows. When everything is online, the sensors are reported by sysmon_envsys and the charger creates a sysmon_pswitch of type PSWITCH_TYPE_ACADAPTER. The goal is to match the acpiacad(4) and acpibat(4) behaviour as much as possible. XXX Firmware loading sometimes fails, still looking into this. When this happens, sensors are not registered. [battery] design voltage: 0.000 V voltage: 12.980 V design cap: 58.000 Wh last full cap: 59.810 Wh charge: 59.740 2.000% 8.998% Wh (99.88%) charge rate: N/A discharge rate: N/A charging: FALSE charge state: NORMAL discharge cycles: 4 0 0 0 0 none temperature: 30.230 degC [charger] connected: TRUE To generate a diff of this commit: cvs rdiff -u -r1.221 -r1.222 src/sys/arch/evbarm/conf/GENERIC64 cvs rdiff -u -r1.135 -r1.136 src/sys/dev/acpi/files.acpi cvs rdiff -u -r0 -r1.1 src/sys/dev/acpi/qcomipcc.c \ src/sys/dev/acpi/qcomipcc.h src/sys/dev/acpi/qcompas.c \ src/sys/dev/acpi/qcompep.c src/sys/dev/acpi/qcompep.h \ src/sys/dev/acpi/qcomscm.c src/sys/dev/acpi/qcomscm.h \ src/sys/dev/acpi/qcomsmem.c src/sys/dev/acpi/qcomsmem.h \ src/sys/dev/acpi/qcomsmptp.c src/sys/dev/acpi/qcomsmptp.h \ src/sys/dev/acpi/qcomspmi.c Please note that diffs are not public domain; they are subject to the copyright notices on the relevant files.
Modified files: Index: src/sys/arch/evbarm/conf/GENERIC64 diff -u src/sys/arch/evbarm/conf/GENERIC64:1.221 src/sys/arch/evbarm/conf/GENERIC64:1.222 --- src/sys/arch/evbarm/conf/GENERIC64:1.221 Mon Dec 9 22:17:31 2024 +++ src/sys/arch/evbarm/conf/GENERIC64 Mon Dec 30 12:31:09 2024 @@ -1,5 +1,5 @@ # -# $NetBSD: GENERIC64,v 1.221 2024/12/09 22:17:31 jmcneill Exp $ +# $NetBSD: GENERIC64,v 1.222 2024/12/30 12:31:09 jmcneill Exp $ # # GENERIC ARM (aarch64) kernel # @@ -158,6 +158,13 @@ gpioleds* at fdt? # System Controller syscon* at fdt? pass 1 # Generic System Controller sunxisramc* at fdt? pass 1 # Allwinner System Control +qcomipcc* at acpi? # Qualcomm IPCC +qcompas* at acpi? # Qualcomm ADSP PAS +qcompep* at acpi? # Qualcomm PEP / AOSS +qcomscm* at acpi? # Qualcomm Secure Channel Manager +qcomsmem* at acpi? # Qualcomm Shared Memory +qcomsmptp* at acpi? # Qualcomm Shared Memory P2P +qcomspmi* at acpi? # Qualcomm SPMI # Timer gtmr* at fdt? pass 2 # ARM Generic Timer Index: src/sys/dev/acpi/files.acpi diff -u src/sys/dev/acpi/files.acpi:1.135 src/sys/dev/acpi/files.acpi:1.136 --- src/sys/dev/acpi/files.acpi:1.135 Fri Dec 13 13:30:10 2024 +++ src/sys/dev/acpi/files.acpi Mon Dec 30 12:31:10 2024 @@ -1,4 +1,4 @@ -# $NetBSD: files.acpi,v 1.135 2024/12/13 13:30:10 martin Exp $ +# $NetBSD: files.acpi,v 1.136 2024/12/30 12:31:10 jmcneill Exp $ defflag opt_acpi.h ACPIVERBOSE ACPI_DEBUG ACPI_ACTIVATE_DEV ACPI_DSDT_OVERRIDE ACPI_SCANPCI ACPI_BREAKPOINT @@ -347,4 +347,39 @@ device qcomiic: i2cbus attach qcomiic at acpinodebus file dev/acpi/qcomiic.c qcomiic +# Qualcomm SPMI controller +device qcomspmi +attach qcomspmi at acpinodebus +file dev/acpi/qcomspmi.c qcomspmi + +# Qualcomm Secure Channel Manager +device qcomscm +attach qcomscm at acpinodebus +file dev/acpi/qcomscm.c qcomscm + +# Qualcomm IPCC driver +device qcomipcc +attach qcomipcc at acpinodebus +file dev/acpi/qcomipcc.c qcomipcc + +# Qualcomm PEP / AOSS driver +device qcompep +attach qcompep at acpinodebus +file dev/acpi/qcompep.c qcompep + +# Qualcomm Shared Memory driver +device qcomsmem +attach qcomsmem at acpinodebus +file dev/acpi/qcomsmem.c qcomsmem + +# Qualcomm SMP2P driver +device qcomsmptp +attach qcomsmptp at acpinodebus +file dev/acpi/qcomsmptp.c qcomsmptp + +# Qualcomm PAS driver +device qcompas +attach qcompas at acpinodebus +file dev/acpi/qcompas.c qcompas + include "dev/acpi/wmi/files.wmi" Added files: Index: src/sys/dev/acpi/qcomipcc.c diff -u /dev/null src/sys/dev/acpi/qcomipcc.c:1.1 --- /dev/null Mon Dec 30 12:31:10 2024 +++ src/sys/dev/acpi/qcomipcc.c Mon Dec 30 12:31:10 2024 @@ -0,0 +1,262 @@ +/* $NetBS$ */ +/* $OpenBSD: qcipcc.c,v 1.2 2023/05/19 20:54:55 patrick Exp $ */ +/* + * Copyright (c) 2023 Patrick Wildt <patr...@blueri.se> + * + * 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 <sys/param.h> +#include <sys/systm.h> +#include <sys/device.h> +#include <sys/kmem.h> + +#include <dev/acpi/acpivar.h> +#include <dev/acpi/acpi_intr.h> +#include <dev/acpi/qcomipcc.h> + +#define IPCC_SEND_ID 0x0c +#define IPCC_RECV_ID 0x10 +#define IPCC_RECV_SIGNAL_ENABLE 0x14 +#define IPCC_RECV_SIGNAL_DISABLE 0x18 +#define IPCC_RECV_SIGNAL_CLEAR 0x1c + +#define IPCC_SIGNAL_ID_MASK __BITS(15,0) +#define IPCC_CLIENT_ID_MASK __BITS(31,16) + +#define HREAD4(sc, reg) \ + bus_space_read_4((sc)->sc_iot, (sc)->sc_ioh, (reg)) +#define HWRITE4(sc, reg, val) \ + bus_space_write_4((sc)->sc_iot, (sc)->sc_ioh, (reg), (val)) + +struct qcipcc_intrhand { + TAILQ_ENTRY(qcipcc_intrhand) ih_q; + int (*ih_func)(void *); + void *ih_arg; + void *ih_sc; + uint32_t ih_client_id; + uint32_t ih_signal_id; +}; + +struct qcipcc_softc { + device_t sc_dev; + bus_space_tag_t sc_iot; + bus_space_handle_t sc_ioh; + + void *sc_ih; + + TAILQ_HEAD(,qcipcc_intrhand) sc_intrq; +}; + +static struct qcipcc_softc *qcipcc = NULL; + +struct qcipcc_channel { + struct qcipcc_softc *ch_sc; + uint32_t ch_client_id; + uint32_t ch_signal_id; +}; + +#define QCIPCC_8380_BASE 0x00408000 +#define QCIPCC_SIZE 0x1000 + +static const struct device_compatible_entry compat_data[] = { + { .compat = "QCOM06C2", .value = QCIPCC_8380_BASE }, + DEVICE_COMPAT_EOL +}; + +static int qcipcc_match(device_t, cfdata_t, void *); +static void qcipcc_attach(device_t, device_t, void *); +static int qcipcc_intr(void *); + +CFATTACH_DECL_NEW(qcomipcc, sizeof(struct qcipcc_softc), + qcipcc_match, qcipcc_attach, NULL, NULL); + +static int +qcipcc_match(device_t parent, cfdata_t match, void *aux) +{ + struct acpi_attach_args *aa = aux; + + return acpi_compatible_match(aa, compat_data); +} + +static void +qcipcc_attach(device_t parent, device_t self, void *aux) +{ + struct qcipcc_softc *sc = device_private(self); + struct acpi_attach_args *aa = aux; + ACPI_HANDLE hdl; + struct acpi_resources res; + struct acpi_irq *irq; + bus_addr_t base_addr; + ACPI_STATUS rv; + + if (qcipcc != NULL) { + aprint_error(": already attached!\n"); + return; + } + + hdl = aa->aa_node->ad_handle; + rv = acpi_resource_parse(self, hdl, "_CRS", &res, + &acpi_resource_parse_ops_default); + if (ACPI_FAILURE(rv)) { + return; + } + + irq = acpi_res_irq(&res, 0); + if (irq == NULL) { + aprint_error_dev(self, "couldn't find irq resource\n"); + goto done; + } + + base_addr = acpi_compatible_lookup(aa, compat_data)->value; + + sc->sc_dev = self; + sc->sc_iot = aa->aa_memt; + if (bus_space_map(sc->sc_iot, base_addr, QCIPCC_SIZE, + BUS_SPACE_MAP_NONPOSTED, &sc->sc_ioh) != 0) { + aprint_error_dev(self, "couldn't map registers\n"); + goto done; + } + + TAILQ_INIT(&sc->sc_intrq); + + sc->sc_ih = acpi_intr_establish(self, + (uint64_t)(uintptr_t)hdl, + IPL_VM, false, qcipcc_intr, sc, device_xname(self)); + if (sc->sc_ih == NULL) { + aprint_error_dev(self, + "couldn't establish interrupt\n"); + goto done; + } + + qcipcc = sc; + +done: + acpi_resource_cleanup(&res); +} + +static int +qcipcc_intr(void *arg) +{ + struct qcipcc_softc *sc = arg; + struct qcipcc_intrhand *ih; + uint16_t client_id, signal_id; + uint32_t reg; + int handled = 0; + + while ((reg = HREAD4(sc, IPCC_RECV_ID)) != ~0) { + HWRITE4(sc, IPCC_RECV_SIGNAL_CLEAR, reg); + + client_id = __SHIFTOUT(reg, IPCC_CLIENT_ID_MASK); + signal_id = __SHIFTOUT(reg, IPCC_SIGNAL_ID_MASK); + + TAILQ_FOREACH(ih, &sc->sc_intrq, ih_q) { + if (ih->ih_client_id != client_id || + ih->ih_signal_id != signal_id) + continue; + ih->ih_func(ih->ih_arg); + handled = 1; + } + } + + return handled; +} + +void * +qcipcc_intr_establish(uint16_t client_id, uint16_t signal_id, int ipl, + int (*func)(void *), void *arg) +{ + struct qcipcc_softc *sc = qcipcc; + struct qcipcc_intrhand *ih; + + if (sc == NULL) { + return NULL; + } + + ih = kmem_zalloc(sizeof(*ih), KM_SLEEP); + ih->ih_func = func; + ih->ih_arg = arg; + ih->ih_sc = sc; + ih->ih_client_id = client_id; + ih->ih_signal_id = signal_id; + TAILQ_INSERT_TAIL(&sc->sc_intrq, ih, ih_q); + + qcipcc_intr_enable(ih); + + return ih; +} + +void +qcipcc_intr_disestablish(void *cookie) +{ + struct qcipcc_intrhand *ih = cookie; + struct qcipcc_softc *sc = ih->ih_sc; + + qcipcc_intr_disable(ih); + + TAILQ_REMOVE(&sc->sc_intrq, ih, ih_q); + kmem_free(ih, sizeof(*ih)); +} + +void +qcipcc_intr_enable(void *cookie) +{ + struct qcipcc_intrhand *ih = cookie; + struct qcipcc_softc *sc = ih->ih_sc; + + HWRITE4(sc, IPCC_RECV_SIGNAL_ENABLE, + __SHIFTIN(ih->ih_client_id, IPCC_CLIENT_ID_MASK) | + __SHIFTIN(ih->ih_signal_id, IPCC_SIGNAL_ID_MASK)); +} + +void +qcipcc_intr_disable(void *cookie) +{ + struct qcipcc_intrhand *ih = cookie; + struct qcipcc_softc *sc = ih->ih_sc; + + HWRITE4(sc, IPCC_RECV_SIGNAL_DISABLE, + __SHIFTIN(ih->ih_client_id, IPCC_CLIENT_ID_MASK) | + __SHIFTIN(ih->ih_signal_id, IPCC_SIGNAL_ID_MASK)); +} + +void * +qcipcc_channel(uint16_t client_id, uint16_t signal_id) +{ + struct qcipcc_softc *sc = qcipcc; + struct qcipcc_channel *ch; + + if (qcipcc == NULL) { + return NULL; + } + + ch = kmem_zalloc(sizeof(*ch), KM_SLEEP); + ch->ch_sc = sc; + ch->ch_client_id = client_id; + ch->ch_signal_id = signal_id; + + return ch; +} + +int +qcipcc_send(void *cookie) +{ + struct qcipcc_channel *ch = cookie; + struct qcipcc_softc *sc = ch->ch_sc; + + HWRITE4(sc, IPCC_SEND_ID, + __SHIFTIN(ch->ch_client_id, IPCC_CLIENT_ID_MASK) | + __SHIFTIN(ch->ch_signal_id, IPCC_SIGNAL_ID_MASK)); + + return 0; +} Index: src/sys/dev/acpi/qcomipcc.h diff -u /dev/null src/sys/dev/acpi/qcomipcc.h:1.1 --- /dev/null Mon Dec 30 12:31:10 2024 +++ src/sys/dev/acpi/qcomipcc.h Mon Dec 30 12:31:10 2024 @@ -0,0 +1,28 @@ +/* $NetBS$ */ +/* $OpenBSD: qcipcc.c,v 1.2 2023/05/19 20:54:55 patrick Exp $ */ +/* + * Copyright (c) 2023 Patrick Wildt <patr...@blueri.se> + * + * 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. + */ + +#pragma once + +void *qcipcc_intr_establish(uint16_t, uint16_t, int, + int (*)(void *), void *); +void qcipcc_intr_disestablish(void *); +void qcipcc_intr_enable(void *); +void qcipcc_intr_disable(void *); + +void *qcipcc_channel(uint16_t, uint16_t); +int qcipcc_send(void *); Index: src/sys/dev/acpi/qcompas.c diff -u /dev/null src/sys/dev/acpi/qcompas.c:1.1 --- /dev/null Mon Dec 30 12:31:10 2024 +++ src/sys/dev/acpi/qcompas.c Mon Dec 30 12:31:10 2024 @@ -0,0 +1,1610 @@ +/* $NetBSD: qcompas.c,v 1.1 2024/12/30 12:31:10 jmcneill Exp $ */ +/* $OpenBSD: qcpas.c,v 1.8 2024/11/08 21:13:34 landry Exp $ */ +/* + * Copyright (c) 2023 Patrick Wildt <patr...@blueri.se> + * + * 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 <sys/param.h> +#include <sys/systm.h> +#include <sys/device.h> +#include <sys/kmem.h> +#include <sys/mutex.h> +#include <sys/condvar.h> +#include <sys/callout.h> +#include <sys/exec_elf.h> + +#include <dev/firmload.h> +#include <dev/sysmon/sysmonvar.h> +#include <dev/sysmon/sysmon_taskq.h> + +#include <dev/acpi/acpivar.h> +#include <dev/acpi/acpi_intr.h> +#include <dev/acpi/qcomipcc.h> +#include <dev/acpi/qcompep.h> +#include <dev/acpi/qcomscm.h> +#include <dev/acpi/qcomsmem.h> +#include <dev/acpi/qcomsmptp.h> + +#define DRIVER_NAME "qcompas" + +#define MDT_TYPE_MASK (7 << 24) +#define MDT_TYPE_HASH (2 << 24) +#define MDT_RELOCATABLE (1 << 27) + +extern struct arm32_bus_dma_tag arm_generic_dma_tag; + +enum qcpas_batt_sensor { + /* Battery sensors (must be first) */ + QCPAS_DVOLTAGE, + QCPAS_VOLTAGE, + QCPAS_DCAPACITY, + QCPAS_LFCCAPACITY, + QCPAS_CAPACITY, + QCPAS_CHARGERATE, + QCPAS_DISCHARGERATE, + QCPAS_CHARGING, + QCPAS_CHARGE_STATE, + QCPAS_DCYCLES, + QCPAS_TEMPERATURE, + /* AC adapter sensors */ + QCPAS_ACADAPTER, + /* Total number of sensors */ + QCPAS_NUM_SENSORS +}; + +struct qcpas_dmamem { + bus_dmamap_t tdm_map; + bus_dma_segment_t tdm_seg; + size_t tdm_size; + void *tdm_kva; +}; +#define QCPAS_DMA_MAP(_tdm) ((_tdm)->tdm_map) +#define QCPAS_DMA_LEN(_tdm) ((_tdm)->tdm_size) +#define QCPAS_DMA_DVA(_tdm) ((_tdm)->tdm_map->dm_segs[0].ds_addr) +#define QCPAS_DMA_KVA(_tdm) ((_tdm)->tdm_kva) + +struct qcpas_softc { + device_t sc_dev; + bus_dma_tag_t sc_dmat; + + char *sc_sub; + + void *sc_ih[5]; + + kmutex_t sc_ready_lock; + kcondvar_t sc_ready_cv; + bool sc_ready; + + paddr_t sc_mem_phys[2]; + size_t sc_mem_size[2]; + uint8_t *sc_mem_region[2]; + vaddr_t sc_mem_reloc[2]; + + const char *sc_fwname; + const char *sc_dtb_fwname; + uint32_t sc_pas_id; + uint32_t sc_dtb_pas_id; + uint32_t sc_lite_pas_id; + const char *sc_load_state; + uint32_t sc_glink_remote_pid; + uint32_t sc_crash_reason; + + struct qcpas_dmamem *sc_metadata[2]; + + /* GLINK */ + volatile uint32_t *sc_tx_tail; + volatile uint32_t *sc_tx_head; + volatile uint32_t *sc_rx_tail; + volatile uint32_t *sc_rx_head; + + uint32_t sc_tx_off; + uint32_t sc_rx_off; + + uint8_t *sc_tx_fifo; + int sc_tx_fifolen; + uint8_t *sc_rx_fifo; + int sc_rx_fifolen; + void *sc_glink_ih; + + void *sc_ipcc; + + uint32_t sc_glink_max_channel; + TAILQ_HEAD(,qcpas_glink_channel) sc_glink_channels; + + uint32_t sc_warning_capacity; + uint32_t sc_low_capacity; + uint32_t sc_power_state; + struct sysmon_envsys *sc_sme; + envsys_data_t sc_sens[QCPAS_NUM_SENSORS]; + struct sysmon_envsys *sc_sme_acadapter; + struct sysmon_pswitch sc_smpsw_acadapter; + callout_t sc_rtr_refresh; +}; + +static int qcpas_match(device_t, cfdata_t, void *); +static void qcpas_attach(device_t, device_t, void *); + +CFATTACH_DECL_NEW(qcompas, sizeof(struct qcpas_softc), + qcpas_match, qcpas_attach, NULL, NULL); + +static void qcpas_mountroot(device_t); +static void qcpas_firmload(void *); +static int qcpas_map_memory(struct qcpas_softc *); +static int qcpas_mdt_init(struct qcpas_softc *, int, u_char *, size_t); +static void qcpas_glink_attach(struct qcpas_softc *); +static void qcpas_glink_recv(void *); +static void qcpas_get_limits(struct sysmon_envsys *, envsys_data_t *, + sysmon_envsys_lim_t *, uint32_t *); + +static struct qcpas_dmamem * + qcpas_dmamem_alloc(struct qcpas_softc *, bus_size_t, bus_size_t); +static void qcpas_dmamem_free(struct qcpas_softc *, struct qcpas_dmamem *); + +static int qcpas_intr_wdog(void *); +static int qcpas_intr_fatal(void *); +static int qcpas_intr_ready(void *); +static int qcpas_intr_handover(void *); +static int qcpas_intr_stop_ack(void *); + +struct qcpas_mem_region { + bus_addr_t start; + bus_size_t size; +}; + +struct qcpas_data { + bus_addr_t reg_addr; + bus_size_t reg_size; + uint32_t pas_id; + uint32_t dtb_pas_id; + uint32_t lite_pas_id; + const char *load_state; + uint32_t glink_remote_pid; + struct qcpas_mem_region mem_region[2]; + const char *fwname; + const char *dtb_fwname; + uint32_t crash_reason; +}; + +static struct qcpas_data qcpas_x1e_data = { + .reg_addr = 0x30000000, + .reg_size = 0x100, + .pas_id = 1, + .dtb_pas_id = 36, + .lite_pas_id = 31, + .load_state = "adsp", + .glink_remote_pid = 2, + .mem_region = { + [0] = { .start = 0x87e00000, .size = 0x3a00000 }, + [1] = { .start = 0x8b800000, .size = 0x80000 }, + }, + .fwname = "qcadsp8380.mbn", + .dtb_fwname = "adsp_dtbs.elf", + .crash_reason = 423, +}; + +#define IPCC_CLIENT_LPASS 3 +#define IPCC_MPROC_SIGNAL_GLINK_QMP 0 + +static const struct device_compatible_entry compat_data[] = { + { .compat = "QCOM0C1B", .data = &qcpas_x1e_data }, + DEVICE_COMPAT_EOL +}; + +static int +qcpas_match(device_t parent, cfdata_t match, void *aux) +{ + struct acpi_attach_args *aa = aux; + + return acpi_compatible_match(aa, compat_data); +} + +static void +qcpas_attach(device_t parent, device_t self, void *aux) +{ + struct qcpas_softc *sc = device_private(self); + struct acpi_attach_args *aa = aux; + const struct qcpas_data *data; + struct acpi_resources res; + ACPI_STATUS rv; + int i; + + rv = acpi_resource_parse(self, aa->aa_node->ad_handle, "_CRS", &res, + &acpi_resource_parse_ops_default); + if (ACPI_FAILURE(rv)) { + return; + } + acpi_resource_cleanup(&res); + + data = acpi_compatible_lookup(aa, compat_data)->data; + + sc->sc_dev = self; + sc->sc_dmat = &arm_generic_dma_tag; + mutex_init(&sc->sc_ready_lock, MUTEX_DEFAULT, IPL_VM); + cv_init(&sc->sc_ready_cv, "qcpasrdy"); + + sc->sc_fwname = data->fwname; + sc->sc_dtb_fwname = data->dtb_fwname; + sc->sc_pas_id = data->pas_id; + sc->sc_dtb_pas_id = data->dtb_pas_id; + sc->sc_lite_pas_id = data->lite_pas_id; + sc->sc_load_state = data->load_state; + sc->sc_glink_remote_pid = data->glink_remote_pid; + sc->sc_crash_reason = data->crash_reason; + for (i = 0; i < __arraycount(sc->sc_mem_phys); i++) { + sc->sc_mem_phys[i] = data->mem_region[i].start; + KASSERT((sc->sc_mem_phys[i] & PAGE_MASK) == 0); + sc->sc_mem_size[i] = data->mem_region[i].size; + KASSERT((sc->sc_mem_size[i] & PAGE_MASK) == 0); + } + + rv = acpi_eval_string(aa->aa_node->ad_handle, "_SUB", &sc->sc_sub); + if (ACPI_FAILURE(rv)) { + aprint_error_dev(self, "failed to evaluate _SUB: %s\n", + AcpiFormatException(rv)); + return; + } + aprint_verbose_dev(self, "subsystem ID %s\n", sc->sc_sub); + + sc->sc_ih[0] = acpi_intr_establish(self, + (uint64_t)(uintptr_t)aa->aa_node->ad_handle, + IPL_VM, false, qcpas_intr_wdog, sc, device_xname(self)); + sc->sc_ih[1] = + qcsmptp_intr_establish(0, qcpas_intr_fatal, sc); + sc->sc_ih[2] = + qcsmptp_intr_establish(1, qcpas_intr_ready, sc); + sc->sc_ih[3] = + qcsmptp_intr_establish(2, qcpas_intr_handover, sc); + sc->sc_ih[4] = + qcsmptp_intr_establish(3, qcpas_intr_stop_ack, sc); + + if (qcpas_map_memory(sc) != 0) + return; + + config_mountroot(self, qcpas_mountroot); +} + +static void +qcpas_firmload(void *arg) +{ + struct qcpas_softc *sc = arg; + firmware_handle_t fwh = NULL, dtb_fwh = NULL; + char fwname[128]; + size_t fwlen = 0, dtb_fwlen = 0; + u_char *fw = NULL, *dtb_fw = NULL; + int ret, error; + + snprintf(fwname, sizeof(fwname), "%s/%s", sc->sc_sub, sc->sc_fwname); + error = firmware_open(DRIVER_NAME, fwname, &fwh); + if (error == 0) { + fwlen = firmware_get_size(fwh); + fw = fwlen ? firmware_malloc(fwlen) : NULL; + error = fw == NULL ? ENOMEM : + firmware_read(fwh, 0, fw, fwlen); + } + if (error) { + device_printf(sc->sc_dev, "failed to load %s/%s: %d\n", + DRIVER_NAME, fwname, error); + goto cleanup; + } + aprint_normal_dev(sc->sc_dev, "loading %s/%s\n", DRIVER_NAME, fwname); + + if (sc->sc_lite_pas_id) { + if (qcscm_pas_shutdown(sc->sc_lite_pas_id)) { + device_printf(sc->sc_dev, + "failed to shutdown lite firmware\n"); + } + } + + if (sc->sc_dtb_pas_id) { + snprintf(fwname, sizeof(fwname), "%s/%s", sc->sc_sub, + sc->sc_dtb_fwname); + error = firmware_open(DRIVER_NAME, fwname, &dtb_fwh); + if (error == 0) { + dtb_fwlen = firmware_get_size(dtb_fwh); + dtb_fw = dtb_fwlen ? firmware_malloc(dtb_fwlen) : NULL; + error = dtb_fw == NULL ? ENOMEM : + firmware_read(dtb_fwh, 0, dtb_fw, dtb_fwlen); + } + if (error) { + device_printf(sc->sc_dev, "failed to load %s/%s: %d\n", + DRIVER_NAME, fwname, error); + goto cleanup; + } + aprint_normal_dev(sc->sc_dev, "loading %s/%s\n", DRIVER_NAME, fwname); + } + + if (sc->sc_load_state) { + char buf[64]; + snprintf(buf, sizeof(buf), + "{class: image, res: load_state, name: %s, val: on}", + sc->sc_load_state); + ret = qcaoss_send(buf, sizeof(buf)); + if (ret != 0) { + device_printf(sc->sc_dev, "failed to toggle load state\n"); + goto cleanup; + } + } + + if (sc->sc_dtb_pas_id) { + qcpas_mdt_init(sc, sc->sc_dtb_pas_id, dtb_fw, dtb_fwlen); + } + + ret = qcpas_mdt_init(sc, sc->sc_pas_id, fw, fwlen); + if (ret != 0) { + device_printf(sc->sc_dev, "failed to boot coprocessor\n"); + goto cleanup; + } + + qcpas_glink_attach(sc); + + /* Battery sensors */ + sc->sc_sme = sysmon_envsys_create(); + sc->sc_sme->sme_name = "battery"; + sc->sc_sme->sme_cookie = sc; + sc->sc_sme->sme_flags = SME_DISABLE_REFRESH; + sc->sc_sme->sme_class = SME_CLASS_BATTERY; + sc->sc_sme->sme_get_limits = qcpas_get_limits; + + /* AC adapter sensors */ + sc->sc_sme_acadapter = sysmon_envsys_create(); + sc->sc_sme_acadapter->sme_name = "charger"; + sc->sc_sme_acadapter->sme_cookie = sc; + sc->sc_sme_acadapter->sme_flags = SME_DISABLE_REFRESH; + sc->sc_sme_acadapter->sme_class = SME_CLASS_ACADAPTER; + +#define INIT_SENSOR(sme, idx, unit, str) \ + do { \ + strlcpy(sc->sc_sens[idx].desc, str, \ + sizeof(sc->sc_sens[0].desc)); \ + sc->sc_sens[idx].units = unit; \ + sc->sc_sens[idx].state = ENVSYS_SINVALID; \ + sysmon_envsys_sensor_attach(sme, \ + &sc->sc_sens[idx]); \ + } while (0) + + INIT_SENSOR(sc->sc_sme, QCPAS_DVOLTAGE, ENVSYS_SVOLTS_DC, "design voltage"); + INIT_SENSOR(sc->sc_sme, QCPAS_VOLTAGE, ENVSYS_SVOLTS_DC, "voltage"); + INIT_SENSOR(sc->sc_sme, QCPAS_DCAPACITY, ENVSYS_SWATTHOUR, "design cap"); + INIT_SENSOR(sc->sc_sme, QCPAS_LFCCAPACITY, ENVSYS_SWATTHOUR, "last full cap"); + INIT_SENSOR(sc->sc_sme, QCPAS_CAPACITY, ENVSYS_SWATTHOUR, "charge"); + INIT_SENSOR(sc->sc_sme, QCPAS_CHARGERATE, ENVSYS_SWATTS, "charge rate"); + INIT_SENSOR(sc->sc_sme, QCPAS_DISCHARGERATE, ENVSYS_SWATTS, "discharge rate"); + INIT_SENSOR(sc->sc_sme, QCPAS_CHARGING, ENVSYS_BATTERY_CHARGE, "charging"); + INIT_SENSOR(sc->sc_sme, QCPAS_CHARGE_STATE, ENVSYS_BATTERY_CAPACITY, "charge state"); + INIT_SENSOR(sc->sc_sme, QCPAS_DCYCLES, ENVSYS_INTEGER, "discharge cycles"); + INIT_SENSOR(sc->sc_sme, QCPAS_TEMPERATURE, ENVSYS_STEMP, "temperature"); + INIT_SENSOR(sc->sc_sme_acadapter, QCPAS_ACADAPTER, ENVSYS_INDICATOR, "connected"); + +#undef INIT_SENSOR + + sc->sc_sens[QCPAS_CHARGE_STATE].value_cur = + ENVSYS_BATTERY_CAPACITY_NORMAL; + sc->sc_sens[QCPAS_CAPACITY].flags |= + ENVSYS_FPERCENT | ENVSYS_FVALID_MAX | ENVSYS_FMONLIMITS; + sc->sc_sens[QCPAS_CHARGE_STATE].flags |= + ENVSYS_FMONSTCHANGED; + + sc->sc_sens[QCPAS_VOLTAGE].flags = ENVSYS_FMONNOTSUPP; + sc->sc_sens[QCPAS_CHARGERATE].flags = ENVSYS_FMONNOTSUPP; + sc->sc_sens[QCPAS_DISCHARGERATE].flags = ENVSYS_FMONNOTSUPP; + sc->sc_sens[QCPAS_DCAPACITY].flags = ENVSYS_FMONNOTSUPP; + sc->sc_sens[QCPAS_LFCCAPACITY].flags = ENVSYS_FMONNOTSUPP; + sc->sc_sens[QCPAS_DVOLTAGE].flags = ENVSYS_FMONNOTSUPP; + + sc->sc_sens[QCPAS_CHARGERATE].flags |= ENVSYS_FHAS_ENTROPY; + sc->sc_sens[QCPAS_DISCHARGERATE].flags |= ENVSYS_FHAS_ENTROPY; + + sysmon_envsys_register(sc->sc_sme); + sysmon_envsys_register(sc->sc_sme_acadapter); + + sc->sc_smpsw_acadapter.smpsw_name = "acpiacad0"; + sc->sc_smpsw_acadapter.smpsw_type = PSWITCH_TYPE_ACADAPTER; + sysmon_pswitch_register(&sc->sc_smpsw_acadapter); + +cleanup: + if (dtb_fw != NULL) { + firmware_free(dtb_fw, dtb_fwlen); + } + if (fw != NULL) { + firmware_free(fw, fwlen); + } + if (dtb_fwh != NULL) { + firmware_close(dtb_fwh); + } + if (fwh != NULL) { + firmware_close(fwh); + } +} + +static void +qcpas_mountroot(device_t self) +{ + struct qcpas_softc *sc = device_private(self); + + sysmon_task_queue_sched(0, qcpas_firmload, sc); +} + +static int +qcpas_map_memory(struct qcpas_softc *sc) +{ + int i; + + for (i = 0; i < __arraycount(sc->sc_mem_phys); i++) { + paddr_t pa, epa; + vaddr_t va; + + if (sc->sc_mem_size[i] == 0) + break; + + va = uvm_km_alloc(kernel_map, sc->sc_mem_size[i], 0, UVM_KMF_VAONLY); + KASSERT(va != 0); + sc->sc_mem_region[i] = (void *)va; + + for (pa = sc->sc_mem_phys[i], epa = sc->sc_mem_phys[i] + sc->sc_mem_size[i]; + pa < epa; + pa += PAGE_SIZE, va += PAGE_SIZE) { + pmap_kenter_pa(va, pa, VM_PROT_READ|VM_PROT_WRITE, PMAP_WRITE_COMBINE); + } + pmap_update(pmap_kernel()); + } + + return 0; +} + +static int +qcpas_mdt_init(struct qcpas_softc *sc, int pas_id, u_char *fw, size_t fwlen) +{ + Elf32_Ehdr *ehdr; + Elf32_Phdr *phdr; + paddr_t minpa = -1, maxpa = 0; + int i, hashseg = 0, relocate = 0; + uint8_t *metadata; + int error; + ssize_t off; + int idx; + + if (pas_id == sc->sc_dtb_pas_id) + idx = 1; + else + idx = 0; + + ehdr = (Elf32_Ehdr *)fw; + phdr = (Elf32_Phdr *)&ehdr[1]; + + if (ehdr->e_phnum < 2 || phdr[0].p_type == PT_LOAD) + return EINVAL; + + for (i = 0; i < ehdr->e_phnum; i++) { + if ((phdr[i].p_flags & MDT_TYPE_MASK) == MDT_TYPE_HASH) { + if (i > 0 && !hashseg) + hashseg = i; + continue; + } + if (phdr[i].p_type != PT_LOAD || phdr[i].p_memsz == 0) + continue; + if (phdr[i].p_flags & MDT_RELOCATABLE) + relocate = 1; + if (phdr[i].p_paddr < minpa) + minpa = phdr[i].p_paddr; + if (phdr[i].p_paddr + phdr[i].p_memsz > maxpa) + maxpa = + roundup(phdr[i].p_paddr + phdr[i].p_memsz, + PAGE_SIZE); + } + + if (!hashseg) + return EINVAL; + + if (sc->sc_metadata[idx] == NULL) { + sc->sc_metadata[idx] = qcpas_dmamem_alloc(sc, phdr[0].p_filesz + + phdr[hashseg].p_filesz, PAGE_SIZE); + if (sc->sc_metadata[idx] == NULL) { + return EINVAL; + } + } + + metadata = QCPAS_DMA_KVA(sc->sc_metadata[idx]); + + memcpy(metadata, fw, phdr[0].p_filesz); + if (phdr[0].p_filesz + phdr[hashseg].p_filesz == fwlen) { + memcpy(metadata + phdr[0].p_filesz, + fw + phdr[0].p_filesz, phdr[hashseg].p_filesz); + } else if (phdr[hashseg].p_offset + phdr[hashseg].p_filesz <= fwlen) { + memcpy(metadata + phdr[0].p_filesz, + fw + phdr[hashseg].p_offset, phdr[hashseg].p_filesz); + } else { + device_printf(sc->sc_dev, "metadata split segment not supported\n"); + return EINVAL; + } + + cpu_drain_writebuf(); + + error = qcscm_pas_init_image(pas_id, + QCPAS_DMA_DVA(sc->sc_metadata[idx])); + if (error != 0) { + device_printf(sc->sc_dev, "init image failed: %d\n", error); + qcpas_dmamem_free(sc, sc->sc_metadata[idx]); + return error; + } + + if (relocate) { + if (qcscm_pas_mem_setup(pas_id, + sc->sc_mem_phys[idx], maxpa - minpa) != 0) { + device_printf(sc->sc_dev, "mem setup failed\n"); + qcpas_dmamem_free(sc, sc->sc_metadata[idx]); + return EINVAL; + } + } + + sc->sc_mem_reloc[idx] = relocate ? minpa : sc->sc_mem_phys[idx]; + + for (i = 0; i < ehdr->e_phnum; i++) { + if ((phdr[i].p_flags & MDT_TYPE_MASK) == MDT_TYPE_HASH || + phdr[i].p_type != PT_LOAD || phdr[i].p_memsz == 0) + continue; + off = phdr[i].p_paddr - sc->sc_mem_reloc[idx]; + if (off < 0 || off + phdr[i].p_memsz > sc->sc_mem_size[0]) + return EINVAL; + if (phdr[i].p_filesz > phdr[i].p_memsz) + return EINVAL; + + if (phdr[i].p_filesz && phdr[i].p_offset < fwlen && + phdr[i].p_offset + phdr[i].p_filesz <= fwlen) { + memcpy(sc->sc_mem_region[idx] + off, + fw + phdr[i].p_offset, phdr[i].p_filesz); + } else if (phdr[i].p_filesz) { + device_printf(sc->sc_dev, "firmware split segment not supported\n"); + return EINVAL; + } + + if (phdr[i].p_memsz > phdr[i].p_filesz) + memset(sc->sc_mem_region[idx] + off + phdr[i].p_filesz, + 0, phdr[i].p_memsz - phdr[i].p_filesz); + } + + cpu_drain_writebuf(); + + if (qcscm_pas_auth_and_reset(pas_id) != 0) { + device_printf(sc->sc_dev, "auth and reset failed\n"); + qcpas_dmamem_free(sc, sc->sc_metadata[idx]); + return EINVAL; + } + + if (pas_id == sc->sc_dtb_pas_id) + return 0; + + mutex_enter(&sc->sc_ready_lock); + while (!sc->sc_ready) { + error = cv_timedwait(&sc->sc_ready_cv, &sc->sc_ready_lock, + hz * 5); + if (error == EWOULDBLOCK) { + break; + } + } + mutex_exit(&sc->sc_ready_lock); + if (!sc->sc_ready) { + device_printf(sc->sc_dev, "timeout waiting for ready signal\n"); + return ETIMEDOUT; + } + + /* XXX: free metadata ? */ + + return 0; +} + +static struct qcpas_dmamem * +qcpas_dmamem_alloc(struct qcpas_softc *sc, bus_size_t size, bus_size_t align) +{ + struct qcpas_dmamem *tdm; + int nsegs; + + tdm = kmem_zalloc(sizeof(*tdm), KM_SLEEP); + tdm->tdm_size = size; + + if (bus_dmamap_create(sc->sc_dmat, size, 1, size, 0, + BUS_DMA_WAITOK | BUS_DMA_ALLOCNOW, &tdm->tdm_map) != 0) + goto tdmfree; + + if (bus_dmamem_alloc(sc->sc_dmat, size, align, 0, + &tdm->tdm_seg, 1, &nsegs, BUS_DMA_WAITOK) != 0) + goto destroy; + + if (bus_dmamem_map(sc->sc_dmat, &tdm->tdm_seg, nsegs, size, + &tdm->tdm_kva, BUS_DMA_WAITOK | BUS_DMA_PREFETCHABLE) != 0) + goto free; + + if (bus_dmamap_load(sc->sc_dmat, tdm->tdm_map, tdm->tdm_kva, size, + NULL, BUS_DMA_WAITOK) != 0) + goto unmap; + + memset(tdm->tdm_kva, 0, size); + + return (tdm); + +unmap: + bus_dmamem_unmap(sc->sc_dmat, tdm->tdm_kva, size); +free: + bus_dmamem_free(sc->sc_dmat, &tdm->tdm_seg, 1); +destroy: + bus_dmamap_destroy(sc->sc_dmat, tdm->tdm_map); +tdmfree: + kmem_free(tdm, sizeof(*tdm)); + + return (NULL); +} + +static void +qcpas_dmamem_free(struct qcpas_softc *sc, struct qcpas_dmamem *tdm) +{ + bus_dmamem_unmap(sc->sc_dmat, tdm->tdm_kva, tdm->tdm_size); + bus_dmamem_free(sc->sc_dmat, &tdm->tdm_seg, 1); + bus_dmamap_destroy(sc->sc_dmat, tdm->tdm_map); + kmem_free(tdm, sizeof(*tdm)); +} + +static void +qcpas_report_crash(struct qcpas_softc *sc, const char *source) +{ + char *msg; + int size; + + msg = qcsmem_get(-1, sc->sc_crash_reason, &size); + if (msg == NULL || size <= 0) { + device_printf(sc->sc_dev, "%s\n", source); + } else { + device_printf(sc->sc_dev, "%s: \"%s\"\n", source, msg); + } +} + +static int +qcpas_intr_wdog(void *cookie) +{ + struct qcpas_softc *sc = cookie; + + qcpas_report_crash(sc, "watchdog"); + + return 0; +} + +static int +qcpas_intr_fatal(void *cookie) +{ + struct qcpas_softc *sc = cookie; + + qcpas_report_crash(sc, "fatal error"); + + return 0; +} + +static int +qcpas_intr_ready(void *cookie) +{ + struct qcpas_softc *sc = cookie; + + aprint_debug_dev(sc->sc_dev, "%s\n", __func__); + + mutex_enter(&sc->sc_ready_lock); + sc->sc_ready = true; + cv_broadcast(&sc->sc_ready_cv); + mutex_exit(&sc->sc_ready_lock); + + return 0; +} + +static int +qcpas_intr_handover(void *cookie) +{ + struct qcpas_softc *sc = cookie; + + aprint_debug_dev(sc->sc_dev, "%s\n", __func__); + + return 0; +} + +static int +qcpas_intr_stop_ack(void *cookie) +{ + struct qcpas_softc *sc = cookie; + + aprint_debug_dev(sc->sc_dev, "%s\n", __func__); + + return 0; +} + +/* GLINK */ + +#define SMEM_GLINK_NATIVE_XPRT_DESCRIPTOR 478 +#define SMEM_GLINK_NATIVE_XPRT_FIFO_0 479 +#define SMEM_GLINK_NATIVE_XPRT_FIFO_1 480 + +struct glink_msg { + uint16_t cmd; + uint16_t param1; + uint32_t param2; + uint8_t data[]; +} __packed; + +struct qcpas_glink_intent_pair { + uint32_t size; + uint32_t iid; +} __packed; + +struct qcpas_glink_intent { + TAILQ_ENTRY(qcpas_glink_intent) it_q; + uint32_t it_id; + uint32_t it_size; + int it_inuse; +}; + +struct qcpas_glink_channel { + TAILQ_ENTRY(qcpas_glink_channel) ch_q; + struct qcpas_softc *ch_sc; + struct qcpas_glink_protocol *ch_proto; + uint32_t ch_rcid; + uint32_t ch_lcid; + uint32_t ch_max_intent; + TAILQ_HEAD(,qcpas_glink_intent) ch_l_intents; + TAILQ_HEAD(,qcpas_glink_intent) ch_r_intents; +}; + +#define GLINK_CMD_VERSION 0 +#define GLINK_CMD_VERSION_ACK 1 +#define GLINK_VERSION 1 +#define GLINK_FEATURE_INTENT_REUSE (1 << 0) +#define GLINK_CMD_OPEN 2 +#define GLINK_CMD_CLOSE 3 +#define GLINK_CMD_OPEN_ACK 4 +#define GLINK_CMD_INTENT 5 +#define GLINK_CMD_RX_DONE 6 +#define GLINK_CMD_RX_INTENT_REQ 7 +#define GLINK_CMD_RX_INTENT_REQ_ACK 8 +#define GLINK_CMD_TX_DATA 9 +#define GLINK_CMD_CLOSE_ACK 11 +#define GLINK_CMD_TX_DATA_CONT 12 +#define GLINK_CMD_READ_NOTIF 13 +#define GLINK_CMD_RX_DONE_W_REUSE 14 + +static int qcpas_glink_intr(void *); + +static void qcpas_glink_tx(struct qcpas_softc *, uint8_t *, int); +static void qcpas_glink_tx_commit(struct qcpas_softc *); +static void qcpas_glink_rx(struct qcpas_softc *, uint8_t *, int); +static void qcpas_glink_rx_commit(struct qcpas_softc *); + +static void qcpas_glink_send(void *, void *, int); + +static int qcpas_pmic_rtr_init(void *); +static int qcpas_pmic_rtr_recv(void *, uint8_t *, int); + +struct qcpas_glink_protocol { + const char *name; + int (*init)(void *cookie); + int (*recv)(void *cookie, uint8_t *buf, int len); +} qcpas_glink_protocols[] = { + { "PMIC_RTR_ADSP_APPS", qcpas_pmic_rtr_init , qcpas_pmic_rtr_recv }, +}; + +static void +qcpas_glink_attach(struct qcpas_softc *sc) +{ + uint32_t remote = sc->sc_glink_remote_pid; + uint32_t *descs; + int size; + + if (qcsmem_alloc(remote, SMEM_GLINK_NATIVE_XPRT_DESCRIPTOR, 32) != 0 || + qcsmem_alloc(remote, SMEM_GLINK_NATIVE_XPRT_FIFO_0, 16384) != 0) + return; + + descs = qcsmem_get(remote, SMEM_GLINK_NATIVE_XPRT_DESCRIPTOR, &size); + if (descs == NULL || size != 32) + return; + + sc->sc_tx_tail = &descs[0]; + sc->sc_tx_head = &descs[1]; + sc->sc_rx_tail = &descs[2]; + sc->sc_rx_head = &descs[3]; + + sc->sc_tx_fifo = qcsmem_get(remote, SMEM_GLINK_NATIVE_XPRT_FIFO_0, + &sc->sc_tx_fifolen); + if (sc->sc_tx_fifo == NULL) + return; + sc->sc_rx_fifo = qcsmem_get(remote, SMEM_GLINK_NATIVE_XPRT_FIFO_1, + &sc->sc_rx_fifolen); + if (sc->sc_rx_fifo == NULL) + return; + + sc->sc_ipcc = qcipcc_channel(IPCC_CLIENT_LPASS, + IPCC_MPROC_SIGNAL_GLINK_QMP); + if (sc->sc_ipcc == NULL) + return; + + TAILQ_INIT(&sc->sc_glink_channels); + + sc->sc_glink_ih = qcipcc_intr_establish(IPCC_CLIENT_LPASS, + IPCC_MPROC_SIGNAL_GLINK_QMP, IPL_VM, qcpas_glink_intr, sc); + if (sc->sc_glink_ih == NULL) + return; + + /* Expect peer to send initial message */ +} + +static void +qcpas_glink_rx(struct qcpas_softc *sc, uint8_t *buf, int len) +{ + uint32_t head, tail; + int avail; + + head = *sc->sc_rx_head; + tail = *sc->sc_rx_tail + sc->sc_rx_off; + if (tail >= sc->sc_rx_fifolen) + tail -= sc->sc_rx_fifolen; + + /* Checked by caller */ + KASSERT(head != tail); + + if (head >= tail) + avail = head - tail; + else + avail = (sc->sc_rx_fifolen - tail) + head; + + /* Dumb, but should do. */ + KASSERT(avail >= len); + + while (len > 0) { + *buf = sc->sc_rx_fifo[tail]; + tail++; + if (tail >= sc->sc_rx_fifolen) + tail -= sc->sc_rx_fifolen; + buf++; + sc->sc_rx_off++; + len--; + } +} + +static void +qcpas_glink_rx_commit(struct qcpas_softc *sc) +{ + uint32_t tail; + + tail = *sc->sc_rx_tail + roundup(sc->sc_rx_off, 8); + if (tail >= sc->sc_rx_fifolen) + tail -= sc->sc_rx_fifolen; + + membar_producer(); + *sc->sc_rx_tail = tail; + sc->sc_rx_off = 0; +} + +static void +qcpas_glink_tx(struct qcpas_softc *sc, uint8_t *buf, int len) +{ + uint32_t head, tail; + int avail; + + head = *sc->sc_tx_head + sc->sc_tx_off; + if (head >= sc->sc_tx_fifolen) + head -= sc->sc_tx_fifolen; + tail = *sc->sc_tx_tail; + + if (head < tail) + avail = tail - head; + else + avail = (sc->sc_rx_fifolen - head) + tail; + + /* Dumb, but should do. */ + KASSERT(avail >= len); + + while (len > 0) { + sc->sc_tx_fifo[head] = *buf; + head++; + if (head >= sc->sc_tx_fifolen) + head -= sc->sc_tx_fifolen; + buf++; + sc->sc_tx_off++; + len--; + } +} + +static void +qcpas_glink_tx_commit(struct qcpas_softc *sc) +{ + uint32_t head; + + head = *sc->sc_tx_head + roundup(sc->sc_tx_off, 8); + if (head >= sc->sc_tx_fifolen) + head -= sc->sc_tx_fifolen; + + membar_producer(); + *sc->sc_tx_head = head; + sc->sc_tx_off = 0; + qcipcc_send(sc->sc_ipcc); +} + +static void +qcpas_glink_send(void *cookie, void *buf, int len) +{ + struct qcpas_glink_channel *ch = cookie; + struct qcpas_softc *sc = ch->ch_sc; + struct qcpas_glink_intent *it; + struct glink_msg msg; + uint32_t chunk_size, left_size; + + TAILQ_FOREACH(it, &ch->ch_r_intents, it_q) { + if (!it->it_inuse) + break; + if (it->it_size < len) + continue; + } + if (it == NULL) { + device_printf(sc->sc_dev, "all intents in use\n"); + return; + } + it->it_inuse = 1; + + msg.cmd = GLINK_CMD_TX_DATA; + msg.param1 = ch->ch_lcid; + msg.param2 = it->it_id; + + chunk_size = len; + left_size = 0; + + qcpas_glink_tx(sc, (char *)&msg, sizeof(msg)); + qcpas_glink_tx(sc, (char *)&chunk_size, sizeof(chunk_size)); + qcpas_glink_tx(sc, (char *)&left_size, sizeof(left_size)); + qcpas_glink_tx(sc, buf, len); + qcpas_glink_tx_commit(sc); +} + +static void +qcpas_glink_recv_version(struct qcpas_softc *sc, uint32_t ver, + uint32_t features) +{ + struct glink_msg msg; + + if (ver != GLINK_VERSION) { + device_printf(sc->sc_dev, + "unsupported glink version %u\n", ver); + return; + } + + msg.cmd = GLINK_CMD_VERSION_ACK; + msg.param1 = GLINK_VERSION; + msg.param2 = features & GLINK_FEATURE_INTENT_REUSE; + + qcpas_glink_tx(sc, (char *)&msg, sizeof(msg)); + qcpas_glink_tx_commit(sc); +} + +static void +qcpas_glink_recv_open(struct qcpas_softc *sc, uint32_t rcid, uint32_t namelen) +{ + struct qcpas_glink_protocol *proto = NULL; + struct qcpas_glink_channel *ch; + struct glink_msg msg; + char *name; + int i, err; + + name = kmem_zalloc(namelen, KM_SLEEP); + qcpas_glink_rx(sc, name, namelen); + qcpas_glink_rx_commit(sc); + + TAILQ_FOREACH(ch, &sc->sc_glink_channels, ch_q) { + if (ch->ch_rcid == rcid) { + device_printf(sc->sc_dev, "duplicate open for %s\n", + name); + kmem_free(name, namelen); + return; + } + } + + for (i = 0; i < __arraycount(qcpas_glink_protocols); i++) { + if (strcmp(qcpas_glink_protocols[i].name, name) != 0) + continue; + proto = &qcpas_glink_protocols[i]; + break; + } + if (proto == NULL) { + kmem_free(name, namelen); + return; + } + + ch = kmem_zalloc(sizeof(*ch), KM_SLEEP); + ch->ch_sc = sc; + ch->ch_proto = proto; + ch->ch_rcid = rcid; + ch->ch_lcid = ++sc->sc_glink_max_channel; + TAILQ_INIT(&ch->ch_l_intents); + TAILQ_INIT(&ch->ch_r_intents); + TAILQ_INSERT_TAIL(&sc->sc_glink_channels, ch, ch_q); + + /* Assume we can leave HW dangling if proto init fails */ + err = proto->init(ch); + if (err) { + TAILQ_REMOVE(&sc->sc_glink_channels, ch, ch_q); + kmem_free(ch, sizeof(*ch)); + kmem_free(name, namelen); + return; + } + + msg.cmd = GLINK_CMD_OPEN_ACK; + msg.param1 = ch->ch_rcid; + msg.param2 = 0; + + qcpas_glink_tx(sc, (char *)&msg, sizeof(msg)); + qcpas_glink_tx_commit(sc); + + msg.cmd = GLINK_CMD_OPEN; + msg.param1 = ch->ch_lcid; + msg.param2 = strlen(name) + 1; + + qcpas_glink_tx(sc, (char *)&msg, sizeof(msg)); + qcpas_glink_tx(sc, name, strlen(name) + 1); + qcpas_glink_tx_commit(sc); + + kmem_free(name, namelen); +} + +static void +qcpas_glink_recv_open_ack(struct qcpas_softc *sc, uint32_t lcid) +{ + struct qcpas_glink_channel *ch; + struct glink_msg msg; + struct qcpas_glink_intent_pair intent; + int i; + + TAILQ_FOREACH(ch, &sc->sc_glink_channels, ch_q) { + if (ch->ch_lcid == lcid) + break; + } + if (ch == NULL) { + device_printf(sc->sc_dev, "unknown channel %u for OPEN_ACK\n", + lcid); + return; + } + + /* Respond with default intent now that channel is open */ + for (i = 0; i < 5; i++) { + struct qcpas_glink_intent *it; + + it = kmem_zalloc(sizeof(*it), KM_SLEEP); + it->it_id = ++ch->ch_max_intent; + it->it_size = 1024; + TAILQ_INSERT_TAIL(&ch->ch_l_intents, it, it_q); + + msg.cmd = GLINK_CMD_INTENT; + msg.param1 = ch->ch_lcid; + msg.param2 = 1; + intent.size = it->it_size; + intent.iid = it->it_id; + } + + qcpas_glink_tx(sc, (char *)&msg, sizeof(msg)); + qcpas_glink_tx(sc, (char *)&intent, sizeof(intent)); + qcpas_glink_tx_commit(sc); +} + +static void +qcpas_glink_recv_intent(struct qcpas_softc *sc, uint32_t rcid, uint32_t count) +{ + struct qcpas_glink_intent_pair *intents; + struct qcpas_glink_channel *ch; + struct qcpas_glink_intent *it; + int i; + + intents = kmem_zalloc(sizeof(*intents) * count, KM_SLEEP); + qcpas_glink_rx(sc, (char *)intents, sizeof(*intents) * count); + qcpas_glink_rx_commit(sc); + + TAILQ_FOREACH(ch, &sc->sc_glink_channels, ch_q) { + if (ch->ch_rcid == rcid) + break; + } + if (ch == NULL) { + device_printf(sc->sc_dev, "unknown channel %u for INTENT\n", + rcid); + kmem_free(intents, sizeof(*intents) * count); + return; + } + + for (i = 0; i < count; i++) { + it = kmem_zalloc(sizeof(*it), KM_SLEEP); + it->it_id = intents[i].iid; + it->it_size = intents[i].size; + TAILQ_INSERT_TAIL(&ch->ch_r_intents, it, it_q); + } + + kmem_free(intents, sizeof(*intents) * count); +} + +static void +qcpas_glink_recv_tx_data(struct qcpas_softc *sc, uint32_t rcid, uint32_t liid) +{ + struct qcpas_glink_channel *ch; + struct qcpas_glink_intent *it; + struct glink_msg msg; + uint32_t chunk_size, left_size; + char *buf; + + qcpas_glink_rx(sc, (char *)&chunk_size, sizeof(chunk_size)); + qcpas_glink_rx(sc, (char *)&left_size, sizeof(left_size)); + qcpas_glink_rx_commit(sc); + + buf = kmem_zalloc(chunk_size, KM_SLEEP); + qcpas_glink_rx(sc, buf, chunk_size); + qcpas_glink_rx_commit(sc); + + TAILQ_FOREACH(ch, &sc->sc_glink_channels, ch_q) { + if (ch->ch_rcid == rcid) + break; + } + if (ch == NULL) { + device_printf(sc->sc_dev, "unknown channel %u for TX_DATA\n", + rcid); + kmem_free(buf, chunk_size); + return; + } + + TAILQ_FOREACH(it, &ch->ch_l_intents, it_q) { + if (it->it_id == liid) + break; + } + if (it == NULL) { + device_printf(sc->sc_dev, "unknown intent %u for TX_DATA\n", + liid); + kmem_free(buf, chunk_size); + return; + } + + /* FIXME: handle message chunking */ + KASSERT(left_size == 0); + + ch->ch_proto->recv(ch, buf, chunk_size); + kmem_free(buf, chunk_size); + + if (!left_size) { + msg.cmd = GLINK_CMD_RX_DONE_W_REUSE; + msg.param1 = ch->ch_lcid; + msg.param2 = it->it_id; + + qcpas_glink_tx(sc, (char *)&msg, sizeof(msg)); + qcpas_glink_tx_commit(sc); + } +} + +static void +qcpas_glink_recv_rx_done(struct qcpas_softc *sc, uint32_t rcid, uint32_t riid, + int reuse) +{ + struct qcpas_glink_channel *ch; + struct qcpas_glink_intent *it; + + TAILQ_FOREACH(ch, &sc->sc_glink_channels, ch_q) { + if (ch->ch_rcid == rcid) + break; + } + if (ch == NULL) { + device_printf(sc->sc_dev, "unknown channel %u for RX_DONE\n", + rcid); + return; + } + + TAILQ_FOREACH(it, &ch->ch_r_intents, it_q) { + if (it->it_id == riid) + break; + } + if (it == NULL) { + device_printf(sc->sc_dev, "unknown intent %u for RX_DONE\n", + riid); + return; + } + + /* FIXME: handle non-reuse */ + KASSERT(reuse); + + KASSERT(it->it_inuse); + it->it_inuse = 0; +} + +static void +qcpas_glink_recv(void *arg) +{ + struct qcpas_softc *sc = arg; + struct glink_msg msg; + + while (*sc->sc_rx_tail != *sc->sc_rx_head) { + membar_consumer(); + qcpas_glink_rx(sc, (uint8_t *)&msg, sizeof(msg)); + qcpas_glink_rx_commit(sc); + + switch (msg.cmd) { + case GLINK_CMD_VERSION: + qcpas_glink_recv_version(sc, msg.param1, msg.param2); + break; + case GLINK_CMD_OPEN: + qcpas_glink_recv_open(sc, msg.param1, msg.param2); + break; + case GLINK_CMD_OPEN_ACK: + qcpas_glink_recv_open_ack(sc, msg.param1); + break; + case GLINK_CMD_INTENT: + qcpas_glink_recv_intent(sc, msg.param1, msg.param2); + break; + case GLINK_CMD_RX_INTENT_REQ: + /* Nothing to do so far */ + break; + case GLINK_CMD_TX_DATA: + qcpas_glink_recv_tx_data(sc, msg.param1, msg.param2); + break; + case GLINK_CMD_RX_DONE: + qcpas_glink_recv_rx_done(sc, msg.param1, msg.param2, 0); + break; + case GLINK_CMD_RX_DONE_W_REUSE: + qcpas_glink_recv_rx_done(sc, msg.param1, msg.param2, 1); + break; + default: + device_printf(sc->sc_dev, "unknown cmd %u\n", msg.cmd); + return; + } + } +} + +static int +qcpas_glink_intr(void *cookie) +{ + struct qcpas_softc *sc = cookie; + + sysmon_task_queue_sched(0, qcpas_glink_recv, sc); + + return 1; +} + +/* GLINK PMIC Router */ + +struct pmic_glink_hdr { + uint32_t owner; +#define PMIC_GLINK_OWNER_BATTMGR 32778 +#define PMIC_GLINK_OWNER_USBC 32779 +#define PMIC_GLINK_OWNER_USBC_PAN 32780 + uint32_t type; +#define PMIC_GLINK_TYPE_REQ_RESP 1 +#define PMIC_GLINK_TYPE_NOTIFY 2 + uint32_t opcode; +}; + +#define BATTMGR_OPCODE_BAT_STATUS 0x1 +#define BATTMGR_OPCODR_REQUEST_NOTIFICATION 0x4 +#define BATTMGR_OPCODE_NOTIF 0x7 +#define BATTMGR_OPCODE_BAT_INFO 0x9 +#define BATTMGR_OPCODE_BAT_DISCHARGE_TIME 0xc +#define BATTMGR_OPCODE_BAT_CHARGE_TIME 0xd + +#define BATTMGR_NOTIF_BAT_PROPERTY 0x30 +#define BATTMGR_NOTIF_USB_PROPERTY 0x32 +#define BATTMGR_NOTIF_WLS_PROPERTY 0x34 +#define BATTMGR_NOTIF_BAT_STATUS 0x80 +#define BATTMGR_NOTIF_BAT_INFO 0x81 + +#define BATTMGR_CHEMISTRY_LEN 4 +#define BATTMGR_STRING_LEN 128 + +struct battmgr_bat_info { + uint32_t power_unit; + uint32_t design_capacity; + uint32_t last_full_capacity; + uint32_t battery_tech; + uint32_t design_voltage; + uint32_t capacity_low; + uint32_t capacity_warning; + uint32_t cycle_count; + uint32_t accuracy; + uint32_t max_sample_time_ms; + uint32_t min_sample_time_ms; + uint32_t max_average_interval_ms; + uint32_t min_average_interval_ms; + uint32_t capacity_granularity1; + uint32_t capacity_granularity2; + uint32_t swappable; + uint32_t capabilities; + char model_number[BATTMGR_STRING_LEN]; + char serial_number[BATTMGR_STRING_LEN]; + char battery_type[BATTMGR_STRING_LEN]; + char oem_info[BATTMGR_STRING_LEN]; + char battery_chemistry[BATTMGR_CHEMISTRY_LEN]; + char uid[BATTMGR_STRING_LEN]; + uint32_t critical_bias; + uint8_t day; + uint8_t month; + uint16_t year; + uint32_t battery_id; +}; + +struct battmgr_bat_status { + uint32_t battery_state; +#define BATTMGR_BAT_STATE_DISCHARGE (1 << 0) +#define BATTMGR_BAT_STATE_CHARGING (1 << 1) +#define BATTMGR_BAT_STATE_CRITICAL_LOW (1 << 2) + uint32_t capacity; + int32_t rate; + uint32_t battery_voltage; + uint32_t power_state; +#define BATTMGR_PWR_STATE_AC_ON (1 << 0) + uint32_t charging_source; +#define BATTMGR_CHARGING_SOURCE_AC 1 +#define BATTMGR_CHARGING_SOURCE_USB 2 +#define BATTMGR_CHARGING_SOURCE_WIRELESS 3 + uint32_t temperature; +}; + +static void qcpas_pmic_rtr_refresh(void *); +static void qcpas_pmic_rtr_bat_info(struct qcpas_softc *, + struct battmgr_bat_info *); +static void qcpas_pmic_rtr_bat_status(struct qcpas_softc *, + struct battmgr_bat_status *); + +static void +qcpas_pmic_rtr_battmgr_req_info(void *cookie) +{ + struct { + struct pmic_glink_hdr hdr; + uint32_t battery_id; + } msg; + + msg.hdr.owner = PMIC_GLINK_OWNER_BATTMGR; + msg.hdr.type = PMIC_GLINK_TYPE_REQ_RESP; + msg.hdr.opcode = BATTMGR_OPCODE_BAT_INFO; + msg.battery_id = 0; + qcpas_glink_send(cookie, &msg, sizeof(msg)); +} + +static void +qcpas_pmic_rtr_battmgr_req_status(void *cookie) +{ + struct { + struct pmic_glink_hdr hdr; + uint32_t battery_id; + } msg; + + msg.hdr.owner = PMIC_GLINK_OWNER_BATTMGR; + msg.hdr.type = PMIC_GLINK_TYPE_REQ_RESP; + msg.hdr.opcode = BATTMGR_OPCODE_BAT_STATUS; + msg.battery_id = 0; + qcpas_glink_send(cookie, &msg, sizeof(msg)); +} + +static int +qcpas_pmic_rtr_init(void *cookie) +{ + struct qcpas_glink_channel *ch = cookie; + struct qcpas_softc *sc = ch->ch_sc; + + callout_init(&sc->sc_rtr_refresh, 0); + callout_setfunc(&sc->sc_rtr_refresh, qcpas_pmic_rtr_refresh, ch); + + callout_schedule(&sc->sc_rtr_refresh, hz * 5); + + return 0; +} + +static int +qcpas_pmic_rtr_recv(void *cookie, uint8_t *buf, int len) +{ + struct qcpas_glink_channel *ch = cookie; + struct qcpas_softc *sc = ch->ch_sc; + struct pmic_glink_hdr hdr; + uint32_t notification; + + if (len < sizeof(hdr)) { + device_printf(sc->sc_dev, "pmic glink message too small\n"); + return 0; + } + + memcpy(&hdr, buf, sizeof(hdr)); + + switch (hdr.owner) { + case PMIC_GLINK_OWNER_BATTMGR: + switch (hdr.opcode) { + case BATTMGR_OPCODE_NOTIF: + if (len - sizeof(hdr) != sizeof(uint32_t)) { + device_printf(sc->sc_dev, + "invalid battgmr notification\n"); + return 0; + } + memcpy(¬ification, buf + sizeof(hdr), + sizeof(uint32_t)); + switch (notification) { + case BATTMGR_NOTIF_BAT_INFO: + qcpas_pmic_rtr_battmgr_req_info(cookie); + /* FALLTHROUGH */ + case BATTMGR_NOTIF_BAT_STATUS: + case BATTMGR_NOTIF_BAT_PROPERTY: + qcpas_pmic_rtr_battmgr_req_status(cookie); + break; + default: + aprint_debug_dev(sc->sc_dev, + "unknown battmgr notification 0x%02x\n", + notification); + break; + } + break; + case BATTMGR_OPCODE_BAT_INFO: { + struct battmgr_bat_info *bat; + if (len - sizeof(hdr) < sizeof(*bat)) { + device_printf(sc->sc_dev, + "invalid battgmr bat info\n"); + return 0; + } + bat = kmem_alloc(sizeof(*bat), KM_SLEEP); + memcpy(bat, buf + sizeof(hdr), sizeof(*bat)); + qcpas_pmic_rtr_bat_info(sc, bat); + kmem_free(bat, sizeof(*bat)); + break; + } + case BATTMGR_OPCODE_BAT_STATUS: { + struct battmgr_bat_status *bat; + if (len - sizeof(hdr) != sizeof(*bat)) { + device_printf(sc->sc_dev, + "invalid battgmr bat status\n"); + return 0; + } + bat = kmem_alloc(sizeof(*bat), KM_SLEEP); + memcpy(bat, buf + sizeof(hdr), sizeof(*bat)); + qcpas_pmic_rtr_bat_status(sc, bat); + kmem_free(bat, sizeof(*bat)); + break; + } + default: + device_printf(sc->sc_dev, + "unknown battmgr opcode 0x%02x\n", + hdr.opcode); + break; + } + break; + default: + device_printf(sc->sc_dev, + "unknown pmic glink owner 0x%04x\n", + hdr.owner); + break; + } + + return 0; +} + +static void +qcpas_pmic_rtr_refresh(void *arg) +{ + struct qcpas_glink_channel *ch = arg; + struct qcpas_softc *sc = ch->ch_sc; + + qcpas_pmic_rtr_battmgr_req_status(ch); + + callout_schedule(&sc->sc_rtr_refresh, hz * 5); +} + +static void +qcpas_pmic_rtr_bat_info(struct qcpas_softc *sc, struct battmgr_bat_info *bat) +{ + sc->sc_warning_capacity = bat->capacity_warning; + sc->sc_low_capacity = bat->capacity_low; + + sc->sc_sens[QCPAS_DCAPACITY].value_cur = + bat->design_capacity * 1000; + sc->sc_sens[QCPAS_DCAPACITY].state = ENVSYS_SVALID; + + sc->sc_sens[QCPAS_LFCCAPACITY].value_cur = + bat->last_full_capacity * 1000; + sc->sc_sens[QCPAS_LFCCAPACITY].state = ENVSYS_SVALID; + + sc->sc_sens[QCPAS_DVOLTAGE].value_cur = + bat->design_voltage * 1000; + sc->sc_sens[QCPAS_DVOLTAGE].state = ENVSYS_SVALID; + + sc->sc_sens[QCPAS_DCYCLES].value_cur = + bat->cycle_count; + sc->sc_sens[QCPAS_DCYCLES].state = ENVSYS_SVALID; + + sc->sc_sens[QCPAS_CAPACITY].value_max = + bat->last_full_capacity * 1000; + sysmon_envsys_update_limits(sc->sc_sme, + &sc->sc_sens[QCPAS_CAPACITY]); +} + +void +qcpas_pmic_rtr_bat_status(struct qcpas_softc *sc, + struct battmgr_bat_status *bat) +{ + sc->sc_sens[QCPAS_CHARGING].value_cur = + (bat->battery_state & BATTMGR_BAT_STATE_CHARGING) != 0; + sc->sc_sens[QCPAS_CHARGING].state = ENVSYS_SVALID; + if ((bat->battery_state & BATTMGR_BAT_STATE_CHARGING) != 0) { + sc->sc_sens[QCPAS_CHARGERATE].value_cur = + abs(bat->rate) * 1000; + sc->sc_sens[QCPAS_CHARGERATE].state = ENVSYS_SVALID; + sc->sc_sens[QCPAS_DISCHARGERATE].state = ENVSYS_SINVALID; + } else if ((bat->battery_state & BATTMGR_BAT_STATE_DISCHARGE) != 0) { + sc->sc_sens[QCPAS_CHARGERATE].state = ENVSYS_SINVALID; + sc->sc_sens[QCPAS_DISCHARGERATE].value_cur = + abs(bat->rate) * 1000; + sc->sc_sens[QCPAS_DISCHARGERATE].state = ENVSYS_SVALID; + } else { + sc->sc_sens[QCPAS_DISCHARGERATE].state = ENVSYS_SINVALID; + sc->sc_sens[QCPAS_CHARGERATE].state = ENVSYS_SINVALID; + } + + sc->sc_sens[QCPAS_VOLTAGE].value_cur = + bat->battery_voltage * 1000; + sc->sc_sens[QCPAS_VOLTAGE].state = ENVSYS_SVALID; + + sc->sc_sens[QCPAS_TEMPERATURE].value_cur = + (bat->temperature * 10000) + 273150000; + sc->sc_sens[QCPAS_TEMPERATURE].state = ENVSYS_SVALID; + + sc->sc_sens[QCPAS_CAPACITY].value_cur = + bat->capacity * 1000; + sc->sc_sens[QCPAS_CAPACITY].state = ENVSYS_SVALID; + + sc->sc_sens[QCPAS_CHARGE_STATE].value_cur = + ENVSYS_BATTERY_CAPACITY_NORMAL; + sc->sc_sens[QCPAS_CHARGE_STATE].state = ENVSYS_SVALID; + + if (bat->capacity < sc->sc_warning_capacity) { + sc->sc_sens[QCPAS_CAPACITY].state = ENVSYS_SWARNUNDER; + sc->sc_sens[QCPAS_CHARGE_STATE].value_cur = + ENVSYS_BATTERY_CAPACITY_WARNING; + } + + if (bat->capacity < sc->sc_low_capacity) { + sc->sc_sens[QCPAS_CAPACITY].state = ENVSYS_SCRITUNDER; + sc->sc_sens[QCPAS_CHARGE_STATE].value_cur = + ENVSYS_BATTERY_CAPACITY_LOW; + } + + if ((bat->battery_state & BATTMGR_BAT_STATE_CRITICAL_LOW) != 0) { + sc->sc_sens[QCPAS_CAPACITY].state = ENVSYS_SCRITICAL; + sc->sc_sens[QCPAS_CHARGE_STATE].value_cur = + ENVSYS_BATTERY_CAPACITY_CRITICAL; + } + + if ((bat->power_state & BATTMGR_PWR_STATE_AC_ON) != + (sc->sc_power_state & BATTMGR_PWR_STATE_AC_ON)) { + sysmon_pswitch_event(&sc->sc_smpsw_acadapter, + (bat->power_state & BATTMGR_PWR_STATE_AC_ON) != 0 ? + PSWITCH_EVENT_PRESSED : PSWITCH_EVENT_RELEASED); + + aprint_debug_dev(sc->sc_dev, "AC adapter %sconnected\n", + (bat->power_state & BATTMGR_PWR_STATE_AC_ON) == 0 ? + "not " : ""); + } + + sc->sc_power_state = bat->power_state; + sc->sc_sens[QCPAS_ACADAPTER].value_cur = + (bat->power_state & BATTMGR_PWR_STATE_AC_ON) != 0; + sc->sc_sens[QCPAS_ACADAPTER].state = ENVSYS_SVALID; +} + +static void +qcpas_get_limits(struct sysmon_envsys *sme, envsys_data_t *edata, + sysmon_envsys_lim_t *limits, uint32_t *props) +{ + struct qcpas_softc *sc = sme->sme_cookie; + + if (edata->sensor != QCPAS_CAPACITY) { + return; + } + + limits->sel_critmin = sc->sc_low_capacity * 1000; + limits->sel_warnmin = sc->sc_warning_capacity * 1000; + + *props |= PROP_BATTCAP | PROP_BATTWARN | PROP_DRIVER_LIMITS; +} Index: src/sys/dev/acpi/qcompep.c diff -u /dev/null src/sys/dev/acpi/qcompep.c:1.1 --- /dev/null Mon Dec 30 12:31:10 2024 +++ src/sys/dev/acpi/qcompep.c Mon Dec 30 12:31:10 2024 @@ -0,0 +1,235 @@ +/* $NetBSD: qcompep.c,v 1.1 2024/12/30 12:31:10 jmcneill Exp $ */ +/* $OpenBSD: qcaoss.c,v 1.1 2023/05/23 14:10:27 patrick Exp $ */ +/* + * Copyright (c) 2023 Patrick Wildt <patr...@blueri.se> + * + * 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 <sys/param.h> +#include <sys/systm.h> +#include <sys/device.h> +#include <sys/kmem.h> + +#include <dev/acpi/acpivar.h> +#include <dev/acpi/qcompep.h> +#include <dev/acpi/qcomipcc.h> + +#define AOSS_DESC_MAGIC 0x0 +#define AOSS_DESC_VERSION 0x4 +#define AOSS_DESC_FEATURES 0x8 +#define AOSS_DESC_UCORE_LINK_STATE 0xc +#define AOSS_DESC_UCORE_LINK_STATE_ACK 0x10 +#define AOSS_DESC_UCORE_CH_STATE 0x14 +#define AOSS_DESC_UCORE_CH_STATE_ACK 0x18 +#define AOSS_DESC_UCORE_MBOX_SIZE 0x1c +#define AOSS_DESC_UCORE_MBOX_OFFSET 0x20 +#define AOSS_DESC_MCORE_LINK_STATE 0x24 +#define AOSS_DESC_MCORE_LINK_STATE_ACK 0x28 +#define AOSS_DESC_MCORE_CH_STATE 0x2c +#define AOSS_DESC_MCORE_CH_STATE_ACK 0x30 +#define AOSS_DESC_MCORE_MBOX_SIZE 0x34 +#define AOSS_DESC_MCORE_MBOX_OFFSET 0x38 + +#define AOSS_MAGIC 0x4d41494c +#define AOSS_VERSION 1 + +#define AOSS_STATE_UP (0xffffU << 0) +#define AOSS_STATE_DOWN (0xffffU << 16) + +#define AOSSREAD4(sc, reg) \ + bus_space_read_4((sc)->sc_iot, (sc)->sc_aoss_ioh, (reg)) +#define AOSSWRITE4(sc, reg, val) \ + bus_space_write_4((sc)->sc_iot, (sc)->sc_aoss_ioh, (reg), (val)) + +struct qcpep_data { + bus_addr_t aoss_base; + bus_size_t aoss_size; + uint32_t aoss_client_id; + uint32_t aoss_signal_id; +}; + +struct qcpep_softc { + device_t sc_dev; + bus_space_tag_t sc_iot; + + const struct qcpep_data *sc_data; + + bus_space_handle_t sc_aoss_ioh; + size_t sc_aoss_offset; + size_t sc_aoss_size; + void * sc_aoss_ipcc; +}; + +struct qcpep_softc *qcpep_sc; + +static const struct qcpep_data qcpep_x1e_data = { + .aoss_base = 0x0c300000, + .aoss_size = 0x400, + .aoss_client_id = 0, /* IPCC_CLIENT_AOP */ + .aoss_signal_id = 0, /* IPCC_MPROC_SIGNAL_GLINK_QMP */ +}; + +static const struct device_compatible_entry compat_data[] = { + { .compat = "QCOM0C17", .data = &qcpep_x1e_data }, + DEVICE_COMPAT_EOL +}; + +static int qcpep_match(device_t, cfdata_t, void *); +static void qcpep_attach(device_t, device_t, void *); + +CFATTACH_DECL_NEW(qcompep, sizeof(struct qcpep_softc), + qcpep_match, qcpep_attach, NULL, NULL); + +static int +qcpep_match(device_t parent, cfdata_t match, void *aux) +{ + struct acpi_attach_args *aa = aux; + + return acpi_compatible_match(aa, compat_data); +} + +static void +qcpep_attach(device_t parent, device_t self, void *aux) +{ + struct qcpep_softc *sc = device_private(self); + struct acpi_attach_args *aa = aux; + struct acpi_resources res; + ACPI_STATUS rv; + int i; + + rv = acpi_resource_parse(self, aa->aa_node->ad_handle, + "_CRS", &res, &acpi_resource_parse_ops_default); + if (ACPI_FAILURE(rv)) { + return; + } + acpi_resource_cleanup(&res); + + sc->sc_dev = self; + sc->sc_iot = aa->aa_memt; + sc->sc_data = acpi_compatible_lookup(aa, compat_data)->data; + + if (bus_space_map(sc->sc_iot, sc->sc_data->aoss_base, + sc->sc_data->aoss_size, BUS_SPACE_MAP_NONPOSTED, &sc->sc_aoss_ioh)) { + aprint_error_dev(self, "couldn't map registers\n"); + return; + } + + sc->sc_aoss_ipcc = qcipcc_channel(sc->sc_data->aoss_client_id, + sc->sc_data->aoss_signal_id); + if (sc->sc_aoss_ipcc == NULL) { + aprint_error_dev(self, "couldn't find ipcc mailbox\n"); + return; + } + + if (AOSSREAD4(sc, AOSS_DESC_MAGIC) != AOSS_MAGIC || + AOSSREAD4(sc, AOSS_DESC_VERSION) != AOSS_VERSION) { + aprint_error_dev(self, "invalid QMP info\n"); + return; + } + + sc->sc_aoss_offset = AOSSREAD4(sc, AOSS_DESC_MCORE_MBOX_OFFSET); + sc->sc_aoss_size = AOSSREAD4(sc, AOSS_DESC_MCORE_MBOX_SIZE); + if (sc->sc_aoss_size == 0) { + aprint_error_dev(self, "invalid AOSS mailbox size\n"); + return; + } + + AOSSWRITE4(sc, AOSS_DESC_UCORE_LINK_STATE_ACK, + AOSSREAD4(sc, AOSS_DESC_UCORE_LINK_STATE)); + + AOSSWRITE4(sc, AOSS_DESC_MCORE_LINK_STATE, AOSS_STATE_UP); + qcipcc_send(sc->sc_aoss_ipcc); + + for (i = 1000; i > 0; i--) { + if (AOSSREAD4(sc, AOSS_DESC_MCORE_LINK_STATE_ACK) == AOSS_STATE_UP) + break; + delay(1000); + } + if (i == 0) { + aprint_error_dev(self, "didn't get link state ack\n"); + return; + } + + AOSSWRITE4(sc, AOSS_DESC_MCORE_CH_STATE, AOSS_STATE_UP); + qcipcc_send(sc->sc_aoss_ipcc); + + for (i = 1000; i > 0; i--) { + if (AOSSREAD4(sc, AOSS_DESC_UCORE_CH_STATE) == AOSS_STATE_UP) + break; + delay(1000); + } + if (i == 0) { + aprint_error_dev(self, "didn't get open channel\n"); + return; + } + + AOSSWRITE4(sc, AOSS_DESC_UCORE_CH_STATE_ACK, AOSS_STATE_UP); + qcipcc_send(sc->sc_aoss_ipcc); + + for (i = 1000; i > 0; i--) { + if (AOSSREAD4(sc, AOSS_DESC_MCORE_CH_STATE_ACK) == AOSS_STATE_UP) + break; + delay(1000); + } + if (i == 0) { + aprint_error_dev(self, "didn't get channel ack\n"); + return; + } + + qcpep_sc = sc; +} + +int +qcaoss_send(char *data, size_t len) +{ + struct qcpep_softc *sc = qcpep_sc; + uint32_t reg; + int i; + + if (sc == NULL) + return ENXIO; + + if (data == NULL || sizeof(uint32_t) + len > sc->sc_aoss_size || + (len % sizeof(uint32_t)) != 0) + return EINVAL; + + /* Write data first, needs to be 32-bit access. */ + for (i = 0; i < len; i += 4) { + memcpy(®, data + i, sizeof(reg)); + AOSSWRITE4(sc, sc->sc_aoss_offset + sizeof(uint32_t) + i, reg); + } + + /* Commit transaction by writing length. */ + AOSSWRITE4(sc, sc->sc_aoss_offset, len); + + /* Assert it's stored and inform peer. */ + if (AOSSREAD4(sc, sc->sc_aoss_offset) != len) { + device_printf(sc->sc_dev, + "aoss message readback failed\n"); + } + qcipcc_send(sc->sc_aoss_ipcc); + + for (i = 1000; i > 0; i--) { + if (AOSSREAD4(sc, sc->sc_aoss_offset) == 0) + break; + delay(1000); + } + if (i == 0) { + device_printf(sc->sc_dev, "timeout sending message\n"); + AOSSWRITE4(sc, sc->sc_aoss_offset, 0); + return ETIMEDOUT; + } + + return 0; +} Index: src/sys/dev/acpi/qcompep.h diff -u /dev/null src/sys/dev/acpi/qcompep.h:1.1 --- /dev/null Mon Dec 30 12:31:10 2024 +++ src/sys/dev/acpi/qcompep.h Mon Dec 30 12:31:10 2024 @@ -0,0 +1,20 @@ +/* $NetBS$ */ +/* + * Copyright (c) 2023 Patrick Wildt <patr...@blueri.se> + * + * 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. + */ + +#pragma once + +int qcaoss_send(char *, size_t); Index: src/sys/dev/acpi/qcomscm.c diff -u /dev/null src/sys/dev/acpi/qcomscm.c:1.1 --- /dev/null Mon Dec 30 12:31:10 2024 +++ src/sys/dev/acpi/qcomscm.c Mon Dec 30 12:31:10 2024 @@ -0,0 +1,1038 @@ +/* $NetBSD: qcomscm.c,v 1.1 2024/12/30 12:31:10 jmcneill Exp $ */ +/* $OpenBSD: qcscm.c,v 1.9 2024/08/04 15:30:08 kettenis Exp $ */ +/* + * Copyright (c) 2022 Patrick Wildt <patr...@blueri.se> + * + * 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 <sys/param.h> +#include <sys/systm.h> +#include <sys/device.h> +#include <sys/kmem.h> +#include <sys/bus.h> +#include <sys/uuid.h> + +#include <dev/efi/efi.h> +#include <dev/acpi/acpivar.h> +#include <dev/acpi/qcomscm.h> + +#define ARM_SMCCC_STD_CALL (0U << 31) +#define ARM_SMCCC_FAST_CALL (1U << 31) +#define ARM_SMCCC_LP64 (1U << 30) +#define ARM_SMCCC_OWNER_SIP 2 + +#define QCTEE_TZ_OWNER_TZ_APPS 48 +#define QCTEE_TZ_OWNER_QSEE_OS 50 + +#define QCTEE_TZ_SVC_APP_ID_PLACEHOLDER 0 +#define QCTEE_TZ_SVC_APP_MGR 1 + +#define QCTEE_OS_RESULT_SUCCESS 0 +#define QCTEE_OS_RESULT_INCOMPLETE 1 +#define QCTEE_OS_RESULT_BLOCKED_ON_LISTENER 2 +#define QCTEE_OS_RESULT_FAILURE 0xffffffff + +#define QCTEE_OS_SCM_RES_APP_ID 0xee01 +#define QCTEE_OS_SCM_RES_QSEOS_LISTENER_ID 0xee02 + +#define QCTEE_UEFI_GET_VARIABLE 0x8000 +#define QCTEE_UEFI_SET_VARIABLE 0x8001 +#define QCTEE_UEFI_GET_NEXT_VARIABLE 0x8002 +#define QCTEE_UEFI_QUERY_VARIABLE_INFO 0x8003 + +#define QCTEE_UEFI_SUCCESS 0 +#define QCTEE_UEFI_BUFFER_TOO_SMALL 0x80000005 +#define QCTEE_UEFI_DEVICE_ERROR 0x80000007 +#define QCTEE_UEFI_NOT_FOUND 0x8000000e + +#define QCSCM_SVC_PIL 0x02 +#define QCSCM_PIL_PAS_INIT_IMAGE 0x01 +#define QCSCM_PIL_PAS_MEM_SETUP 0x02 +#define QCSCM_PIL_PAS_AUTH_AND_RESET 0x05 +#define QCSCM_PIL_PAS_SHUTDOWN 0x06 +#define QCSCM_PIL_PAS_IS_SUPPORTED 0x07 +#define QCSCM_PIL_PAS_MSS_RESET 0x0a + +#define QCSCM_INTERRUPTED 1 +#define QCSCM_EBUSY -22 + +#define QCSCM_ARGINFO_NUM(x) (((x) & 0xf) << 0) +#define QCSCM_ARGINFO_TYPE(x, y) (((y) & 0x3) << (4 + 2 * (x))) +#define QCSCM_ARGINFO_TYPE_VAL 0 +#define QCSCM_ARGINFO_TYPE_RO 1 +#define QCSCM_ARGINFO_TYPE_RW 2 +#define QCSCM_ARGINFO_TYPE_BUFVAL 3 + +#define EFI_VARIABLE_NON_VOLATILE 0x00000001 +#define EFI_VARIABLE_BOOTSERVICE_ACCESS 0x00000002 +#define EFI_VARIABLE_RUNTIME_ACCESS 0x00000004 + +#define UNIX_GPS_EPOCH_OFFSET 315964800 + +#define EFI_VAR_RTCINFO __UNCONST(u"RTCInfo") + +extern struct arm32_bus_dma_tag arm_generic_dma_tag; + +struct qcscm_dmamem { + bus_dmamap_t qdm_map; + bus_dma_segment_t qdm_seg; + size_t qdm_size; + void *qdm_kva; +}; + +#define QCSCM_DMA_MAP(_qdm) ((_qdm)->qdm_map) +#define QCSCM_DMA_LEN(_qdm) ((_qdm)->qdm_size) +#define QCSCM_DMA_DVA(_qdm) ((uint64_t)(_qdm)->qdm_map->dm_segs[0].ds_addr) +#define QCSCM_DMA_KVA(_qdm, off) ((void *)((uintptr_t)(_qdm)->qdm_kva + (off))) + +static struct uuid qcscm_uefi_rtcinfo_guid = + { 0x882f8c2b, 0x9646, 0x435f, + 0x8d, 0xe5, { 0xf2, 0x08, 0xff, 0x80, 0xc1, 0xbd } }; + +struct qcscm_softc { + device_t sc_dev; + bus_dma_tag_t sc_dmat; + + struct qcscm_dmamem *sc_extarg; + uint32_t sc_uefi_id; +}; + +static int qcscm_match(device_t, cfdata_t, void *); +static void qcscm_attach(device_t, device_t, void *); + +CFATTACH_DECL_NEW(qcomscm, sizeof(struct qcscm_softc), + qcscm_match, qcscm_attach, NULL, NULL); + +static inline void qcscm_smc_exec(uint64_t *, uint64_t *); +int qcscm_smc_call(struct qcscm_softc *, uint8_t, uint8_t, uint8_t, + uint32_t, uint64_t *, int, uint64_t *); +int qcscm_tee_app_get_id(struct qcscm_softc *, const char *, uint32_t *); +int qcscm_tee_app_send(struct qcscm_softc *, uint32_t, uint64_t, uint64_t, + uint64_t, uint64_t); + +efi_status qcscm_uefi_get_variable(struct qcscm_softc *, efi_char *, + int, struct uuid *, uint32_t *, uint8_t *, int *); +efi_status qcscm_uefi_set_variable(struct qcscm_softc *, efi_char *, + int, struct uuid *, uint32_t, uint8_t *, int); +efi_status qcscm_uefi_get_next_variable(struct qcscm_softc *, + efi_char *, int *, struct uuid *); + +efi_status qcscm_efi_get_variable(efi_char *, struct uuid *, uint32_t *, + u_long *, void *); +efi_status qcscm_efi_set_variable(efi_char *, struct uuid *, uint32_t, + u_long, void *); +efi_status qcscm_efi_get_next_variable_name(u_long *, efi_char *, struct uuid *); + +#ifdef QCSCM_DEBUG +void qcscm_uefi_dump_variables(struct qcscm_softc *); +void qcscm_uefi_dump_variable(struct qcscm_softc *, efi_char *, int, + struct uuid *); +#endif + +int qcscm_uefi_rtc_get(uint32_t *); +int qcscm_uefi_rtc_set(uint32_t); + +struct qcscm_dmamem * + qcscm_dmamem_alloc(struct qcscm_softc *, bus_size_t, bus_size_t); +void qcscm_dmamem_free(struct qcscm_softc *, struct qcscm_dmamem *); + +struct qcscm_softc *qcscm_sc; + +static const struct device_compatible_entry compat_data[] = { + { .compat = "QCOM04DD" }, + DEVICE_COMPAT_EOL +}; + +static int +qcscm_match(device_t parent, cfdata_t cf, void *aux) +{ + struct acpi_attach_args *aa = aux; + + return acpi_compatible_match(aa, compat_data); +} + +static void +qcscm_attach(device_t parent, device_t self, void *aux) +{ + struct qcscm_softc *sc = device_private(self); + int error; + + sc->sc_dev = self; + error = bus_dmatag_subregion(&arm_generic_dma_tag, + 0, __MASK(32), &sc->sc_dmat, 0); + KASSERT(error == 0); + + sc->sc_extarg = qcscm_dmamem_alloc(sc, PAGE_SIZE, 8); + if (sc->sc_extarg == NULL) { + aprint_error(": can't allocate memory for extended args\n"); + return; + } + + aprint_naive("\n"); + aprint_normal("\n"); + +#if notyet + error = qcscm_tee_app_get_id(sc, "qcom.tz.uefisecapp", &sc->sc_uefi_id); + if (error != 0) { + aprint_error_dev(self, "can't retrieve UEFI App: %d\n", error); + sc->sc_uefi_id = UINT32_MAX; + } +#else + sc->sc_uefi_id = UINT32_MAX; +#endif + + qcscm_sc = sc; + +#ifdef QCSCM_DEBUG + qcscm_uefi_dump_variables(sc); + qcscm_uefi_dump_variable(sc, EFI_VAR_RTCINFO, sizeof(EFI_VAR_RTCINFO), + &qcscm_uefi_rtcinfo_guid); +#endif +} + +/* Expects an uint64_t[18] */ +static inline void +qcscm_smc_exec(uint64_t *in, uint64_t *out) +{ + asm volatile( + "ldp x0, x1, [%[in], #0]\n" + "ldp x2, x3, [%[in], #16]\n" + "ldp x4, x5, [%[in], #32]\n" + "ldp x6, x7, [%[in], #48]\n" + "ldp x8, x9, [%[in], #64]\n" + "ldp x10, x11, [%[in], #80]\n" + "ldp x12, x13, [%[in], #96]\n" + "ldp x14, x15, [%[in], #112]\n" + "ldp x16, x17, [%[in], #128]\n" + "smc #0\n" + "stp x0, x1, [%[out], #0]\n" + "stp x2, x3, [%[out], #16]\n" + "stp x4, x5, [%[out], #32]\n" + "stp x6, x7, [%[out], #48]\n" + "stp x8, x9, [%[out], #64]\n" + "stp x10, x11, [%[out], #80]\n" + "stp x12, x13, [%[out], #96]\n" + "stp x14, x15, [%[out], #112]\n" + "stp x16, x17, [%[out], #128]\n" + : + : [in] "r" (in), [out] "r" (out) + : "x0", "x1", "x2", "x3", "x4", "x5", + "x6", "x7", "x8", "x9", "x10", "x11", + "x12", "x13", "x14", "x15", "x16", "x17", + "memory"); +} + +int +qcscm_smc_call(struct qcscm_softc *sc, uint8_t owner, uint8_t svc, uint8_t cmd, + uint32_t arginfo, uint64_t *args, int arglen, uint64_t *res) +{ + uint64_t smcreq[18] = { 0 }, smcres[18] = { 0 }; + uint64_t *smcextreq; + int i, busy_retry; + + /* 4 of our 10 possible args fit into x2-x5 */ + smcreq[0] = ARM_SMCCC_STD_CALL | ARM_SMCCC_LP64 | + owner << 24 | svc << 8 | cmd; +#ifdef QCSCM_DEBUG_SMC + device_printf(sc->sc_dev, "owner %#x svc %#x cmd %#x req %#lx\n", + owner, svc, cmd, smcreq[0]); +#endif + smcreq[1] = arginfo; + for (i = 0; i < uimin(arglen, 4); i++) + smcreq[2 + i] = args[i]; + + /* In case we have more than 4, use x5 as ptr to extra args */ + if (arglen > 4) { + smcreq[5] = QCSCM_DMA_DVA(sc->sc_extarg); + smcextreq = QCSCM_DMA_KVA(sc->sc_extarg, 0); + for (i = 0; i < uimin(arglen - 3, 7); i++) { + smcextreq[i] = args[3 + i]; + } + cpu_drain_writebuf(); + } + +#ifdef QCSCM_DEBUG_SMC + device_printf(sc->sc_dev, "smcreq[before]:"); + for (i = 0; i < __arraycount(smcreq); i++) { + printf(" %#lx", smcreq[i]); + } + printf("\n"); +#endif + for (busy_retry = 20; busy_retry > 0; busy_retry--) { + int intr_retry = 1000000; + for (;;) { + qcscm_smc_exec(smcreq, smcres); + /* If the call gets interrupted, try again and re-pass x0/x6 */ + if (smcres[0] == QCSCM_INTERRUPTED) { + if (--intr_retry == 0) { + break; + } + smcreq[0] = smcres[0]; + smcreq[6] = smcres[6]; + continue; + } + break; + } + + if (smcres[0] != QCSCM_EBUSY) { + break; + } + delay(30000); + } + +#ifdef QCSCM_DEBUG_SMC + device_printf(sc->sc_dev, "smcreq[after]:"); + for (i = 0; i < __arraycount(smcreq); i++) { + printf(" %#lx", smcreq[i]); + } + printf("\n"); + device_printf(sc->sc_dev, "smcres[after]:"); + for (i = 0; i < __arraycount(smcres); i++) { + printf(" %#lx", smcres[i]); + } + printf("\n"); +#endif + + if (res) { + res[0] = smcres[1]; + res[1] = smcres[2]; + res[2] = smcres[3]; + } + + return smcres[0]; +} + +/* Retrieve id of app running in TEE by name */ +int +qcscm_tee_app_get_id(struct qcscm_softc *sc, const char *name, uint32_t *id) +{ + struct qcscm_dmamem *qdm; + uint64_t res[3]; + uint64_t args[2]; + uint32_t arginfo; + int ret; + + /* Max name length is 64 */ + if (strlen(name) > 64) + return EINVAL; + + /* Alloc some phys mem to hold the name */ + qdm = qcscm_dmamem_alloc(sc, PAGE_SIZE, 8); + if (qdm == NULL) + return ENOMEM; + + /* Copy name of app we want to get an id for to page */ + memcpy(QCSCM_DMA_KVA(qdm, 0), name, strlen(name)); + + /* Pass address of name and length */ + arginfo = QCSCM_ARGINFO_NUM(__arraycount(args)); + arginfo |= QCSCM_ARGINFO_TYPE(0, QCSCM_ARGINFO_TYPE_RW); + arginfo |= QCSCM_ARGINFO_TYPE(1, QCSCM_ARGINFO_TYPE_VAL); + args[0] = QCSCM_DMA_DVA(qdm); + args[1] = strlen(name); + + cpu_drain_writebuf(); + + /* Make call into TEE */ + ret = qcscm_smc_call(sc, QCTEE_TZ_OWNER_QSEE_OS, QCTEE_TZ_SVC_APP_MGR, + 0x03, arginfo, args, __arraycount(args), res); + + /* If the call succeeded, check the response status */ + if (ret == 0) + ret = res[0]; + + /* If the response status is successful as well, retrieve data */ + if (ret == 0) + *id = res[2]; + + qcscm_dmamem_free(sc, qdm); + return ret; +} + +/* Message interface to app running in TEE */ +int +qcscm_tee_app_send(struct qcscm_softc *sc, uint32_t id, uint64_t req_phys, + uint64_t req_len, uint64_t rsp_phys, uint64_t rsp_len) +{ + uint64_t res[3]; + uint64_t args[5]; + uint32_t arginfo; + int ret; + + /* Pass id of app we target, plus request and response buffers */ + arginfo = QCSCM_ARGINFO_NUM(__arraycount(args)); + arginfo |= QCSCM_ARGINFO_TYPE(0, QCSCM_ARGINFO_TYPE_VAL); + arginfo |= QCSCM_ARGINFO_TYPE(1, QCSCM_ARGINFO_TYPE_RW); + arginfo |= QCSCM_ARGINFO_TYPE(2, QCSCM_ARGINFO_TYPE_VAL); + arginfo |= QCSCM_ARGINFO_TYPE(3, QCSCM_ARGINFO_TYPE_RW); + arginfo |= QCSCM_ARGINFO_TYPE(4, QCSCM_ARGINFO_TYPE_VAL); + args[0] = id; + args[1] = req_phys; + args[2] = req_len; + args[3] = rsp_phys; + args[4] = rsp_len; + + /* Make call into TEE */ + ret = qcscm_smc_call(sc, QCTEE_TZ_OWNER_TZ_APPS, + QCTEE_TZ_SVC_APP_ID_PLACEHOLDER, 0x01, + arginfo, args, __arraycount(args), res); + + /* If the call succeeded, check the response status */ + if (ret == 0) + ret = res[0]; + + return ret; +} + +struct qcscm_req_uefi_get_variable { + uint32_t command_id; + uint32_t length; + uint32_t name_offset; + uint32_t name_size; + uint32_t guid_offset; + uint32_t guid_size; + uint32_t data_size; +}; + +struct qcscm_rsp_uefi_get_variable { + uint32_t command_id; + uint32_t length; + uint32_t status; + uint32_t attributes; + uint32_t data_offset; + uint32_t data_size; +}; + +efi_status +qcscm_uefi_get_variable(struct qcscm_softc *sc, + efi_char *name, int name_size, struct uuid *guid, + uint32_t *attributes, uint8_t *data, int *data_size) +{ + struct qcscm_req_uefi_get_variable *req; + struct qcscm_rsp_uefi_get_variable *resp; + struct qcscm_dmamem *qdm; + size_t reqsize, respsize; + off_t reqoff, respoff; + int ret; + + if (sc->sc_uefi_id == UINT32_MAX) + return QCTEE_UEFI_DEVICE_ERROR; + + reqsize = ALIGN(sizeof(*req)) + ALIGN(name_size) + ALIGN(sizeof(*guid)); + respsize = ALIGN(sizeof(*resp)) + ALIGN(*data_size); + + reqoff = 0; + respoff = reqsize; + + qdm = qcscm_dmamem_alloc(sc, round_page(reqsize + respsize), 8); + if (qdm == NULL) + return QCTEE_UEFI_DEVICE_ERROR; + + req = QCSCM_DMA_KVA(qdm, reqoff); + req->command_id = QCTEE_UEFI_GET_VARIABLE; + req->data_size = *data_size; + req->name_offset = ALIGN(sizeof(*req)); + req->name_size = name_size; + req->guid_offset = ALIGN(req->name_offset + req->name_size); + req->guid_size = sizeof(*guid); + req->length = req->guid_offset + req->guid_size; + + memcpy((char *)req + req->guid_offset, guid, sizeof(*guid)); + memcpy((char *)req + req->name_offset, name, name_size); + + cpu_drain_writebuf(); + + ret = qcscm_tee_app_send(sc, sc->sc_uefi_id, + QCSCM_DMA_DVA(qdm) + reqoff, reqsize, + QCSCM_DMA_DVA(qdm) + respoff, respsize); + if (ret) { + qcscm_dmamem_free(sc, qdm); + return QCTEE_UEFI_DEVICE_ERROR; + } + + cpu_drain_writebuf(); + + resp = QCSCM_DMA_KVA(qdm, respoff); + if (resp->command_id != QCTEE_UEFI_GET_VARIABLE || + resp->length < sizeof(*resp)) { + qcscm_dmamem_free(sc, qdm); + return QCTEE_UEFI_DEVICE_ERROR; + } + + if (resp->status) { + if (resp->status == QCTEE_UEFI_BUFFER_TOO_SMALL) + *data_size = resp->data_size; + if (attributes) + *attributes = resp->attributes; + ret = resp->status; + qcscm_dmamem_free(sc, qdm); + return ret; + } + + if (resp->length > respsize || + resp->data_offset + resp->data_size > resp->length) { + qcscm_dmamem_free(sc, qdm); + return QCTEE_UEFI_DEVICE_ERROR; + } + + if (attributes) + *attributes = resp->attributes; + + if (*data_size == 0) { + *data_size = resp->data_size; + qcscm_dmamem_free(sc, qdm); + return QCTEE_UEFI_SUCCESS; + } + + if (resp->data_size > *data_size) { + *data_size = resp->data_size; + qcscm_dmamem_free(sc, qdm); + return QCTEE_UEFI_BUFFER_TOO_SMALL; + } + + memcpy(data, (char *)resp + resp->data_offset, resp->data_size); + *data_size = resp->data_size; + + qcscm_dmamem_free(sc, qdm); + return EFI_SUCCESS; +} + +struct qcscm_req_uefi_set_variable { + uint32_t command_id; + uint32_t length; + uint32_t name_offset; + uint32_t name_size; + uint32_t guid_offset; + uint32_t guid_size; + uint32_t attributes; + uint32_t data_offset; + uint32_t data_size; +}; + +struct qcscm_rsp_uefi_set_variable { + uint32_t command_id; + uint32_t length; + uint32_t status; + uint32_t unknown[2]; +}; + +efi_status +qcscm_uefi_set_variable(struct qcscm_softc *sc, + efi_char *name, int name_size, struct uuid *guid, + uint32_t attributes, uint8_t *data, int data_size) +{ + struct qcscm_req_uefi_set_variable *req; + struct qcscm_rsp_uefi_set_variable *resp; + struct qcscm_dmamem *qdm; + size_t reqsize, respsize; + off_t reqoff, respoff; + int ret; + + if (sc->sc_uefi_id == UINT32_MAX) + return QCTEE_UEFI_DEVICE_ERROR; + + reqsize = ALIGN(sizeof(*req)) + ALIGN(name_size) + ALIGN(sizeof(*guid)) + + ALIGN(data_size); + respsize = ALIGN(sizeof(*resp)); + + reqoff = 0; + respoff = reqsize; + + qdm = qcscm_dmamem_alloc(sc, round_page(reqsize + respsize), 8); + if (qdm == NULL) + return QCTEE_UEFI_DEVICE_ERROR; + + req = QCSCM_DMA_KVA(qdm, reqoff); + req->command_id = QCTEE_UEFI_SET_VARIABLE; + req->attributes = attributes; + req->name_offset = ALIGN(sizeof(*req)); + req->name_size = name_size; + req->guid_offset = ALIGN(req->name_offset + req->name_size); + req->guid_size = sizeof(*guid); + req->data_offset = ALIGN(req->guid_offset + req->guid_size); + req->data_size = data_size; + req->length = req->data_offset + req->data_size; + + memcpy((char *)req + req->name_offset, name, name_size); + memcpy((char *)req + req->guid_offset, guid, sizeof(*guid)); + memcpy((char *)req + req->data_offset, data, data_size); + + ret = qcscm_tee_app_send(sc, sc->sc_uefi_id, + QCSCM_DMA_DVA(qdm) + reqoff, reqsize, + QCSCM_DMA_DVA(qdm) + respoff, respsize); + if (ret) { + qcscm_dmamem_free(sc, qdm); + return QCTEE_UEFI_DEVICE_ERROR; + } + + resp = QCSCM_DMA_KVA(qdm, respoff); + if (resp->command_id != QCTEE_UEFI_SET_VARIABLE || + resp->length < sizeof(*resp) || resp->length > respsize) { + qcscm_dmamem_free(sc, qdm); + return QCTEE_UEFI_DEVICE_ERROR; + } + + if (resp->status) { + ret = resp->status; + qcscm_dmamem_free(sc, qdm); + return ret; + } + + qcscm_dmamem_free(sc, qdm); + return QCTEE_UEFI_SUCCESS; +} + +struct qcscm_req_uefi_get_next_variable { + uint32_t command_id; + uint32_t length; + uint32_t guid_offset; + uint32_t guid_size; + uint32_t name_offset; + uint32_t name_size; +}; + +struct qcscm_rsp_uefi_get_next_variable { + uint32_t command_id; + uint32_t length; + uint32_t status; + uint32_t guid_offset; + uint32_t guid_size; + uint32_t name_offset; + uint32_t name_size; +}; + +efi_status +qcscm_uefi_get_next_variable(struct qcscm_softc *sc, + efi_char *name, int *name_size, struct uuid *guid) +{ + struct qcscm_req_uefi_get_next_variable *req; + struct qcscm_rsp_uefi_get_next_variable *resp; + struct qcscm_dmamem *qdm; + size_t reqsize, respsize; + off_t reqoff, respoff; + int ret; + + if (sc->sc_uefi_id == UINT32_MAX) + return QCTEE_UEFI_DEVICE_ERROR; + + reqsize = ALIGN(sizeof(*req)) + ALIGN(sizeof(*guid)) + ALIGN(*name_size); + respsize = ALIGN(sizeof(*resp)) + ALIGN(sizeof(*guid)) + ALIGN(*name_size); + + reqoff = 0; + respoff = reqsize; + + qdm = qcscm_dmamem_alloc(sc, round_page(reqsize + respsize), 8); + if (qdm == NULL) + return QCTEE_UEFI_DEVICE_ERROR; + + req = QCSCM_DMA_KVA(qdm, reqoff); + req->command_id = QCTEE_UEFI_GET_NEXT_VARIABLE; + req->guid_offset = ALIGN(sizeof(*req)); + req->guid_size = sizeof(*guid); + req->name_offset = ALIGN(req->guid_offset + req->guid_size); + req->name_size = *name_size; + req->length = req->name_offset + req->name_size; + + memcpy((char *)req + req->guid_offset, guid, sizeof(*guid)); + memcpy((char *)req + req->name_offset, name, *name_size); + + ret = qcscm_tee_app_send(sc, sc->sc_uefi_id, + QCSCM_DMA_DVA(qdm) + reqoff, reqsize, + QCSCM_DMA_DVA(qdm) + respoff, respsize); + if (ret) { + qcscm_dmamem_free(sc, qdm); + return QCTEE_UEFI_DEVICE_ERROR; + } + + resp = QCSCM_DMA_KVA(qdm, respoff); + if (resp->command_id != QCTEE_UEFI_GET_NEXT_VARIABLE || + resp->length < sizeof(*resp) || resp->length > respsize) { + qcscm_dmamem_free(sc, qdm); + return QCTEE_UEFI_DEVICE_ERROR; + } + + if (resp->status) { + if (resp->status == QCTEE_UEFI_BUFFER_TOO_SMALL) + *name_size = resp->name_size; + ret = resp->status; + qcscm_dmamem_free(sc, qdm); + return ret; + } + + if (resp->guid_offset + resp->guid_size > resp->length || + resp->name_offset + resp->name_size > resp->length) { + qcscm_dmamem_free(sc, qdm); + return QCTEE_UEFI_DEVICE_ERROR; + } + + if (resp->guid_size != sizeof(*guid)) { + qcscm_dmamem_free(sc, qdm); + return QCTEE_UEFI_DEVICE_ERROR; + } + + if (resp->name_size > *name_size) { + *name_size = resp->name_size; + qcscm_dmamem_free(sc, qdm); + return QCTEE_UEFI_BUFFER_TOO_SMALL; + } + + memcpy(guid, (char *)resp + resp->guid_offset, sizeof(*guid)); + memcpy(name, (char *)resp + resp->name_offset, resp->name_size); + *name_size = resp->name_size; + + qcscm_dmamem_free(sc, qdm); + return QCTEE_UEFI_SUCCESS; +} + +efi_status +qcscm_efi_get_variable(efi_char *name, struct uuid *guid, uint32_t *attributes, + u_long *data_size, void *data) +{ + struct qcscm_softc *sc = qcscm_sc; + efi_status status; + int name_size; + int size; + + name_size = 0; + while (name[name_size]) + name_size++; + name_size++; + + size = *data_size; + status = qcscm_uefi_get_variable(sc, name, name_size * 2, guid, + attributes, data, &size); + *data_size = size; + + /* Convert 32-bit status code to 64-bit. */ + return ((status & 0xf0000000) << 32 | (status & 0x0fffffff)); +} + +efi_status +qcscm_efi_set_variable(efi_char *name, struct uuid *guid, uint32_t attributes, + u_long data_size, void *data) +{ + struct qcscm_softc *sc = qcscm_sc; + efi_status status; + int name_size; + + name_size = 0; + while (name[name_size]) + name_size++; + name_size++; + + status = qcscm_uefi_set_variable(sc, name, name_size * 2, guid, + attributes, data, data_size); + + /* Convert 32-bit status code to 64-bit. */ + return ((status & 0xf0000000) << 32 | (status & 0x0fffffff)); +} + +efi_status +qcscm_efi_get_next_variable_name(u_long *name_size, efi_char *name, + struct uuid *guid) +{ + struct qcscm_softc *sc = qcscm_sc; + efi_status status; + int size; + + size = *name_size; + status = qcscm_uefi_get_next_variable(sc, name, &size, guid); + *name_size = size; + + /* Convert 32-bit status code to 64-bit. */ + return ((status & 0xf0000000) << 32 | (status & 0x0fffffff)); +} + +#ifdef QCSCM_DEBUG + +void +qcscm_uefi_dump_variables(struct qcscm_softc *sc) +{ + efi_char name[128]; + struct uuid guid; + int namesize = sizeof(name); + int i, ret; + + memset(name, 0, sizeof(name)); + memset(&guid, 0, sizeof(guid)); + + for (;;) { + ret = qcscm_uefi_get_next_variable(sc, name, &namesize, &guid); + if (ret == 0) { + printf("%s: ", device_xname(sc->sc_dev)); + for (i = 0; i < namesize / 2; i++) + printf("%c", name[i]); + printf(" { 0x%08x, 0x%04x, 0x%04x, { ", + guid.time_low, guid.time_mid, guid.time_hi_and_version); + printf(" 0x%02x, 0x%02x,", + guid.clock_seq_hi_and_reserved, + guid.clock_seq_low); + for (i = 0; i < 6; i++) { + printf(" 0x%02x,", guid.node[i]); + } + printf(" }"); + printf("\n"); + namesize = sizeof(name); + continue; + } + break; + } +} + +void +qcscm_uefi_dump_variable(struct qcscm_softc *sc, efi_char *name, int namesize, + struct uuid *guid) +{ + uint8_t data[512]; + int datasize = sizeof(data); + int i, ret; + + ret = qcscm_uefi_get_variable(sc, name, namesize, guid, + NULL, data, &datasize); + if (ret != QCTEE_UEFI_SUCCESS) { + printf("%s: error reading ", device_xname(sc->sc_dev)); + for (i = 0; i < namesize / 2; i++) + printf("%c", name[i]); + printf("\n"); + return; + } + + printf("%s: ", device_xname(sc->sc_dev)); + for (i = 0; i < namesize / 2; i++) + printf("%c", name[i]); + printf(" = "); + for (i = 0; i < datasize; i++) + printf("%02x", data[i]); + printf("\n"); +} + +#endif + +int +qcscm_uefi_rtc_get(uint32_t *off) +{ + struct qcscm_softc *sc = qcscm_sc; + uint32_t rtcinfo[3]; + int rtcinfosize = sizeof(rtcinfo); + + if (sc == NULL) + return ENXIO; + + if (qcscm_uefi_get_variable(sc, EFI_VAR_RTCINFO, sizeof(EFI_VAR_RTCINFO), + &qcscm_uefi_rtcinfo_guid, NULL, (uint8_t *)rtcinfo, + &rtcinfosize) != 0) + return EIO; + + /* UEFI stores the offset based on GPS epoch */ + *off = rtcinfo[0] + UNIX_GPS_EPOCH_OFFSET; + return 0; +} + +int +qcscm_uefi_rtc_set(uint32_t off) +{ + struct qcscm_softc *sc = qcscm_sc; + uint32_t rtcinfo[3]; + int rtcinfosize = sizeof(rtcinfo); + + if (sc == NULL) + return ENXIO; + + if (qcscm_uefi_get_variable(sc, EFI_VAR_RTCINFO, sizeof(EFI_VAR_RTCINFO), + &qcscm_uefi_rtcinfo_guid, NULL, (uint8_t *)rtcinfo, + &rtcinfosize) != 0) + return EIO; + + /* UEFI stores the offset based on GPS epoch */ + off -= UNIX_GPS_EPOCH_OFFSET; + + /* No need to set if we're not changing anything */ + if (rtcinfo[0] == off) + return 0; + + rtcinfo[0] = off; + + if (qcscm_uefi_set_variable(sc, EFI_VAR_RTCINFO, sizeof(EFI_VAR_RTCINFO), + &qcscm_uefi_rtcinfo_guid, EFI_VARIABLE_NON_VOLATILE | + EFI_VARIABLE_BOOTSERVICE_ACCESS | EFI_VARIABLE_RUNTIME_ACCESS, + (uint8_t *)rtcinfo, sizeof(rtcinfo)) != 0) + return EIO; + + return 0; +} + +int +qcscm_pas_init_image(uint32_t peripheral, paddr_t metadata) +{ + struct qcscm_softc *sc = qcscm_sc; + uint64_t res[3]; + uint64_t args[2]; + uint32_t arginfo; + int ret; + + if (sc == NULL) + return ENXIO; + + arginfo = QCSCM_ARGINFO_NUM(__arraycount(args)); + arginfo |= QCSCM_ARGINFO_TYPE(0, QCSCM_ARGINFO_TYPE_VAL); + arginfo |= QCSCM_ARGINFO_TYPE(1, QCSCM_ARGINFO_TYPE_RW); + args[0] = peripheral; + args[1] = metadata; + + /* Make call into TEE */ + ret = qcscm_smc_call(sc, ARM_SMCCC_OWNER_SIP, QCSCM_SVC_PIL, + QCSCM_PIL_PAS_INIT_IMAGE, arginfo, args, __arraycount(args), res); + + /* If the call succeeded, check the response status */ + if (ret == 0) + ret = res[0]; + + return ret; +} + +int +qcscm_pas_mem_setup(uint32_t peripheral, paddr_t addr, size_t size) +{ + struct qcscm_softc *sc = qcscm_sc; + uint64_t res[3]; + uint64_t args[3]; + uint32_t arginfo; + int ret; + + if (sc == NULL) + return ENXIO; + + arginfo = QCSCM_ARGINFO_NUM(__arraycount(args)); + arginfo |= QCSCM_ARGINFO_TYPE(0, QCSCM_ARGINFO_TYPE_VAL); + arginfo |= QCSCM_ARGINFO_TYPE(1, QCSCM_ARGINFO_TYPE_VAL); + arginfo |= QCSCM_ARGINFO_TYPE(2, QCSCM_ARGINFO_TYPE_VAL); + args[0] = peripheral; + args[1] = addr; + args[2] = size; + + /* Make call into TEE */ + ret = qcscm_smc_call(sc, ARM_SMCCC_OWNER_SIP, QCSCM_SVC_PIL, + QCSCM_PIL_PAS_MEM_SETUP, arginfo, args, __arraycount(args), res); + + /* If the call succeeded, check the response status */ + if (ret == 0) + ret = res[0]; + + return ret; +} + +int +qcscm_pas_auth_and_reset(uint32_t peripheral) +{ + struct qcscm_softc *sc = qcscm_sc; + uint64_t res[3]; + uint64_t args[1]; + uint32_t arginfo; + int ret; + + if (sc == NULL) + return ENXIO; + + arginfo = QCSCM_ARGINFO_NUM(__arraycount(args)); + arginfo |= QCSCM_ARGINFO_TYPE(0, QCSCM_ARGINFO_TYPE_VAL); + args[0] = peripheral; + + /* Make call into TEE */ + ret = qcscm_smc_call(sc, ARM_SMCCC_OWNER_SIP, QCSCM_SVC_PIL, + QCSCM_PIL_PAS_AUTH_AND_RESET, arginfo, args, __arraycount(args), res); + + /* If the call succeeded, check the response status */ + if (ret == 0) + ret = res[0]; + + return ret; +} + +int +qcscm_pas_shutdown(uint32_t peripheral) +{ + struct qcscm_softc *sc = qcscm_sc; + uint64_t res[3]; + uint64_t args[1]; + uint32_t arginfo; + int ret; + + if (sc == NULL) + return ENXIO; + + arginfo = QCSCM_ARGINFO_NUM(__arraycount(args)); + arginfo |= QCSCM_ARGINFO_TYPE(0, QCSCM_ARGINFO_TYPE_VAL); + args[0] = peripheral; + + /* Make call into TEE */ + ret = qcscm_smc_call(sc, ARM_SMCCC_OWNER_SIP, QCSCM_SVC_PIL, + QCSCM_PIL_PAS_SHUTDOWN, arginfo, args, __arraycount(args), res); + + /* If the call succeeded, check the response status */ + if (ret == 0) + ret = res[0]; + + return ret; +} + +/* DMA code */ +struct qcscm_dmamem * +qcscm_dmamem_alloc(struct qcscm_softc *sc, bus_size_t size, bus_size_t align) +{ + struct qcscm_dmamem *qdm; + int nsegs; + + qdm = kmem_zalloc(sizeof(*qdm), KM_SLEEP); + qdm->qdm_size = size; + + if (bus_dmamap_create(sc->sc_dmat, size, 1, size, 0, + BUS_DMA_WAITOK | BUS_DMA_ALLOCNOW, &qdm->qdm_map) != 0) + goto qdmfree; + + if (bus_dmamem_alloc(sc->sc_dmat, size, align, 0, + &qdm->qdm_seg, 1, &nsegs, BUS_DMA_WAITOK) != 0) + goto destroy; + + if (bus_dmamem_map(sc->sc_dmat, &qdm->qdm_seg, nsegs, size, + &qdm->qdm_kva, BUS_DMA_WAITOK | BUS_DMA_COHERENT) != 0) + goto free; + + if (bus_dmamap_load(sc->sc_dmat, qdm->qdm_map, qdm->qdm_kva, size, + NULL, BUS_DMA_WAITOK) != 0) + goto unmap; + + memset(qdm->qdm_kva, 0, size); + + return (qdm); + +unmap: + bus_dmamem_unmap(sc->sc_dmat, qdm->qdm_kva, size); +free: + bus_dmamem_free(sc->sc_dmat, &qdm->qdm_seg, 1); +destroy: + bus_dmamap_destroy(sc->sc_dmat, qdm->qdm_map); +qdmfree: + kmem_free(qdm, sizeof(*qdm)); + + return (NULL); +} + +void +qcscm_dmamem_free(struct qcscm_softc *sc, struct qcscm_dmamem *qdm) +{ + bus_dmamem_unmap(sc->sc_dmat, qdm->qdm_kva, qdm->qdm_size); + bus_dmamem_free(sc->sc_dmat, &qdm->qdm_seg, 1); + bus_dmamap_destroy(sc->sc_dmat, qdm->qdm_map); + kmem_free(qdm, sizeof(*qdm)); +} Index: src/sys/dev/acpi/qcomscm.h diff -u /dev/null src/sys/dev/acpi/qcomscm.h:1.1 --- /dev/null Mon Dec 30 12:31:10 2024 +++ src/sys/dev/acpi/qcomscm.h Mon Dec 30 12:31:10 2024 @@ -0,0 +1,23 @@ +/* $NetBSD: qcomscm.h,v 1.1 2024/12/30 12:31:10 jmcneill Exp $ */ +/* + * Copyright (c) 2022 Patrick Wildt <patr...@blueri.se> + * + * 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. + */ + +#pragma once + +int qcscm_pas_init_image(uint32_t, paddr_t); +int qcscm_pas_mem_setup(uint32_t, paddr_t, size_t); +int qcscm_pas_auth_and_reset(uint32_t); +int qcscm_pas_shutdown(uint32_t); Index: src/sys/dev/acpi/qcomsmem.c diff -u /dev/null src/sys/dev/acpi/qcomsmem.c:1.1 --- /dev/null Mon Dec 30 12:31:10 2024 +++ src/sys/dev/acpi/qcomsmem.c Mon Dec 30 12:31:10 2024 @@ -0,0 +1,573 @@ +/* $NetBSD: qcomsmem.c,v 1.1 2024/12/30 12:31:10 jmcneill Exp $ */ +/* $OpenBSD: qcsmem.c,v 1.1 2023/05/19 21:13:49 patrick Exp $ */ +/* + * Copyright (c) 2023 Patrick Wildt <patr...@blueri.se> + * + * 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 <sys/param.h> +#include <sys/systm.h> +#include <sys/device.h> +#include <sys/kmem.h> + +#include <dev/acpi/acpivar.h> +#include <dev/acpi/qcomsmem.h> + +#define QCSMEM_ITEM_FIXED 8 +#define QCSMEM_ITEM_COUNT 512 +#define QCSMEM_HOST_COUNT 15 + +struct qcsmem_proc_comm { + uint32_t command; + uint32_t status; + uint32_t params[2]; +}; + +struct qcsmem_global_entry { + uint32_t allocated; + uint32_t offset; + uint32_t size; + uint32_t aux_base; +#define QCSMEM_GLOBAL_ENTRY_AUX_BASE_MASK 0xfffffffc +}; + +struct qcsmem_header { + struct qcsmem_proc_comm proc_comm[4]; + uint32_t version[32]; +#define QCSMEM_HEADER_VERSION_MASTER_SBL_IDX 7 +#define QCSMEM_HEADER_VERSION_GLOBAL_HEAP 11 +#define QCSMEM_HEADER_VERSION_GLOBAL_PART 12 + uint32_t initialized; + uint32_t free_offset; + uint32_t available; + uint32_t reserved; + struct qcsmem_global_entry toc[QCSMEM_ITEM_COUNT]; +}; + +struct qcsmem_ptable_entry { + uint32_t offset; + uint32_t size; + uint32_t flags; + uint16_t host[2]; +#define QCSMEM_LOCAL_HOST 0 +#define QCSMEM_GLOBAL_HOST 0xfffe + uint32_t cacheline; + uint32_t reserved[7]; +}; + +struct qcsmem_ptable { + uint32_t magic; +#define QCSMEM_PTABLE_MAGIC 0x434f5424 + uint32_t version; +#define QCSMEM_PTABLE_VERSION 1 + uint32_t num_entries; + uint32_t reserved[5]; + struct qcsmem_ptable_entry entry[]; +}; + +struct qcsmem_partition_header { + uint32_t magic; +#define QCSMEM_PART_HDR_MAGIC 0x54525024 + uint16_t host[2]; + uint32_t size; + uint32_t offset_free_uncached; + uint32_t offset_free_cached; + uint32_t reserved[3]; +}; + +struct qcsmem_partition { + struct qcsmem_partition_header *phdr; + size_t cacheline; + size_t size; +}; + +struct qcsmem_private_entry { + uint16_t canary; +#define QCSMEM_PRIV_ENTRY_CANARY 0xa5a5 + uint16_t item; + uint32_t size; + uint16_t padding_data; + uint16_t padding_hdr; + uint32_t reserved; +}; + +struct qcsmem_info { + uint32_t magic; +#define QCSMEM_INFO_MAGIC 0x49494953 + uint32_t size; + uint32_t base_addr; + uint32_t reserved; + uint32_t num_items; +}; + +struct qcsmem_softc { + device_t sc_dev; + bus_space_tag_t sc_iot; + void *sc_smem; + bus_space_handle_t sc_mtx_ioh; + + bus_addr_t sc_aux_base; + bus_size_t sc_aux_size; + + int sc_item_count; + struct qcsmem_partition sc_global_partition; + struct qcsmem_partition sc_partitions[QCSMEM_HOST_COUNT]; +}; + +#define QCMTX_OFF(idx) ((idx) * 0x1000) +#define QCMTX_NUM_LOCKS 32 +#define QCMTX_APPS_PROC_ID 1 + +#define MTXREAD4(sc, reg) \ + bus_space_read_4((sc)->sc_iot, (sc)->sc_mtx_ioh, (reg)) +#define MTXWRITE4(sc, reg, val) \ + bus_space_write_4((sc)->sc_iot, (sc)->sc_mtx_ioh, (reg), (val)) + +struct qcsmem_softc *qcsmem_sc; + +#define QCSMEM_X1E_BASE 0xffe00000 +#define QCSMEM_X1E_SIZE 0x200000 + +#define QCMTX_X1E_BASE 0x01f40000 +#define QCMTX_X1E_SIZE 0x20000 + +#define QCSMEM_X1E_LOCK_IDX 3 + +static const struct device_compatible_entry compat_data[] = { + { .compat = "QCOM0C84" }, + DEVICE_COMPAT_EOL +}; + +static int qcsmem_match(device_t, cfdata_t, void *); +static void qcsmem_attach(device_t, device_t, void *); +static int qcmtx_lock(struct qcsmem_softc *, u_int, u_int); +static void qcmtx_unlock(struct qcsmem_softc *, u_int); + +CFATTACH_DECL_NEW(qcomsmem, sizeof(struct qcsmem_softc), + qcsmem_match, qcsmem_attach, NULL, NULL); + +static int +qcsmem_match(device_t parent, cfdata_t match, void *aux) +{ + struct acpi_attach_args *aa = aux; + + return acpi_compatible_match(aa, compat_data); +} + +static void +qcsmem_attach(device_t parent, device_t self, void *aux) +{ + struct qcsmem_softc *sc = device_private(self); + struct acpi_attach_args *aa = aux; + struct qcsmem_header *header; + struct qcsmem_ptable *ptable; + struct qcsmem_ptable_entry *pte; + struct qcsmem_info *info; + struct qcsmem_partition *part; + struct qcsmem_partition_header *phdr; + uintptr_t smem_va; + uint32_t hdr_version; + int i; + + sc->sc_dev = self; + sc->sc_iot = aa->aa_memt; + sc->sc_smem = AcpiOsMapMemory(QCSMEM_X1E_BASE, QCSMEM_X1E_SIZE); + KASSERT(sc->sc_smem != NULL); + + sc->sc_aux_base = QCSMEM_X1E_BASE; + sc->sc_aux_size = QCSMEM_X1E_SIZE; + + if (bus_space_map(sc->sc_iot, QCMTX_X1E_BASE, + QCMTX_X1E_SIZE, 0, &sc->sc_mtx_ioh)) { + aprint_error(": can't map mutex registers\n"); + return; + } + + smem_va = (uintptr_t)sc->sc_smem; + + ptable = (void *)(smem_va + sc->sc_aux_size - PAGE_SIZE); + if (ptable->magic != QCSMEM_PTABLE_MAGIC || + ptable->version != QCSMEM_PTABLE_VERSION) { + aprint_error(": unsupported ptable 0x%x/0x%x\n", + ptable->magic, ptable->version); + return; + } + + header = (void *)smem_va; + hdr_version = header->version[QCSMEM_HEADER_VERSION_MASTER_SBL_IDX] >> 16; + if (hdr_version != QCSMEM_HEADER_VERSION_GLOBAL_PART) { + aprint_error(": unsupported header 0x%x\n", hdr_version); + return; + } + + for (i = 0; i < ptable->num_entries; i++) { + pte = &ptable->entry[i]; + if (!pte->offset || !pte->size) + continue; + if (pte->host[0] == QCSMEM_GLOBAL_HOST && + pte->host[1] == QCSMEM_GLOBAL_HOST) + part = &sc->sc_global_partition; + else if (pte->host[0] == QCSMEM_LOCAL_HOST && + pte->host[1] < QCSMEM_HOST_COUNT) + part = &sc->sc_partitions[pte->host[1]]; + else if (pte->host[1] == QCSMEM_LOCAL_HOST && + pte->host[0] < QCSMEM_HOST_COUNT) + part = &sc->sc_partitions[pte->host[0]]; + else + continue; + if (part->phdr != NULL) + continue; + phdr = (void *)(smem_va + pte->offset); + if (phdr->magic != QCSMEM_PART_HDR_MAGIC) { + aprint_error(": unsupported partition 0x%x\n", + phdr->magic); + return; + } + if (pte->host[0] != phdr->host[0] || + pte->host[1] != phdr->host[1]) { + aprint_error(": bad hosts 0x%x/0x%x+0x%x/0x%x\n", + pte->host[0], phdr->host[0], + pte->host[1], phdr->host[1]); + return; + } + if (pte->size != phdr->size) { + aprint_error(": bad size 0x%x/0x%x\n", + pte->size, phdr->size); + return; + } + if (phdr->offset_free_uncached > phdr->size) { + aprint_error(": bad size 0x%x > 0x%x\n", + phdr->offset_free_uncached, phdr->size); + return; + } + part->phdr = phdr; + part->size = pte->size; + part->cacheline = pte->cacheline; + } + if (sc->sc_global_partition.phdr == NULL) { + aprint_error(": could not find global partition\n"); + return; + } + + sc->sc_item_count = QCSMEM_ITEM_COUNT; + info = (struct qcsmem_info *)&ptable->entry[ptable->num_entries]; + if (info->magic == QCSMEM_INFO_MAGIC) + sc->sc_item_count = info->num_items; + + aprint_naive("\n"); + aprint_normal("\n"); + + qcsmem_sc = sc; +} + +static int +qcsmem_alloc_private(struct qcsmem_softc *sc, struct qcsmem_partition *part, + int item, int size) +{ + struct qcsmem_private_entry *entry, *last; + struct qcsmem_partition_header *phdr = part->phdr; + uintptr_t phdr_va = (uintptr_t)phdr; + + entry = (void *)&phdr[1]; + last = (void *)(phdr_va + phdr->offset_free_uncached); + + if ((void *)last > (void *)(phdr_va + part->size)) + return EINVAL; + + while (entry < last) { + if (entry->canary != QCSMEM_PRIV_ENTRY_CANARY) { + device_printf(sc->sc_dev, "invalid canary\n"); + return EINVAL; + } + + if (entry->item == item) + return 0; + + entry = (void *)((uintptr_t)&entry[1] + entry->padding_hdr + + entry->size); + } + + if ((void *)entry > (void *)(phdr_va + part->size)) + return EINVAL; + + if ((uintptr_t)&entry[1] + roundup(size, 8) > + phdr_va + phdr->offset_free_cached) + return EINVAL; + + entry->canary = QCSMEM_PRIV_ENTRY_CANARY; + entry->item = item; + entry->size = roundup(size, 8); + entry->padding_data = entry->size - size; + entry->padding_hdr = 0; + membar_producer(); + + phdr->offset_free_uncached += sizeof(*entry) + entry->size; + + return 0; +} + +static int +qcsmem_alloc_global(struct qcsmem_softc *sc, int item, int size) +{ + struct qcsmem_header *header; + struct qcsmem_global_entry *entry; + + header = (void *)sc->sc_smem; + entry = &header->toc[item]; + if (entry->allocated) + return 0; + + size = roundup(size, 8); + if (size > header->available) + return EINVAL; + + entry->offset = header->free_offset; + entry->size = size; + membar_producer(); + entry->allocated = 1; + + header->free_offset += size; + header->available -= size; + + return 0; +} + +int +qcsmem_alloc(int host, int item, int size) +{ + struct qcsmem_softc *sc = qcsmem_sc; + struct qcsmem_partition *part; + int ret; + + if (sc == NULL) + return ENXIO; + + if (item < QCSMEM_ITEM_FIXED) + return EPERM; + + if (item >= sc->sc_item_count) + return ENXIO; + + ret = qcmtx_lock(sc, QCSMEM_X1E_LOCK_IDX, 1000); + if (ret) + return ret; + + if (host < QCSMEM_HOST_COUNT && + sc->sc_partitions[host].phdr != NULL) { + part = &sc->sc_partitions[host]; + ret = qcsmem_alloc_private(sc, part, item, size); + } else if (sc->sc_global_partition.phdr != NULL) { + part = &sc->sc_global_partition; + ret = qcsmem_alloc_private(sc, part, item, size); + } else { + ret = qcsmem_alloc_global(sc, item, size); + } + + qcmtx_unlock(sc, QCSMEM_X1E_LOCK_IDX); + + return ret; +} + +static void * +qcsmem_get_private(struct qcsmem_softc *sc, struct qcsmem_partition *part, + int item, int *size) +{ + struct qcsmem_private_entry *entry, *last; + struct qcsmem_partition_header *phdr = part->phdr; + uintptr_t phdr_va = (uintptr_t)phdr; + + entry = (void *)&phdr[1]; + last = (void *)(phdr_va + phdr->offset_free_uncached); + + while (entry < last) { + if (entry->canary != QCSMEM_PRIV_ENTRY_CANARY) { + device_printf(sc->sc_dev, "invalid canary\n"); + return NULL; + } + + if (entry->item == item) { + if (size != NULL) { + if (entry->size > part->size || + entry->padding_data > entry->size) + return NULL; + *size = entry->size - entry->padding_data; + } + + return (void *)((uintptr_t)&entry[1] + entry->padding_hdr); + } + + entry = (void *)((uintptr_t)&entry[1] + entry->padding_hdr + + entry->size); + } + + if ((uintptr_t)entry > phdr_va + part->size) + return NULL; + + entry = (void *)(phdr_va + phdr->size - + roundup(sizeof(*entry), part->cacheline)); + last = (void *)(phdr_va + phdr->offset_free_cached); + + if ((uintptr_t)entry < phdr_va || + (uintptr_t)last > phdr_va + part->size) + return NULL; + + while (entry > last) { + if (entry->canary != QCSMEM_PRIV_ENTRY_CANARY) { + device_printf(sc->sc_dev, "invalid canary\n"); + return NULL; + } + + if (entry->item == item) { + if (size != NULL) { + if (entry->size > part->size || + entry->padding_data > entry->size) + return NULL; + *size = entry->size - entry->padding_data; + } + + return (void *)((uintptr_t)entry - entry->size); + } + + entry = (void *)((uintptr_t)entry - entry->size - + roundup(sizeof(*entry), part->cacheline)); + } + + if ((uintptr_t)entry < phdr_va) + return NULL; + + return NULL; +} + +static void * +qcsmem_get_global(struct qcsmem_softc *sc, int item, int *size) +{ + struct qcsmem_header *header; + struct qcsmem_global_entry *entry; + uint32_t aux_base; + + header = (void *)sc->sc_smem; + entry = &header->toc[item]; + if (!entry->allocated) + return NULL; + + aux_base = entry->aux_base & QCSMEM_GLOBAL_ENTRY_AUX_BASE_MASK; + if (aux_base != 0 && aux_base != sc->sc_aux_base) + return NULL; + + if (entry->size + entry->offset > sc->sc_aux_size) + return NULL; + + if (size != NULL) + *size = entry->size; + + return (void *)((uintptr_t)sc->sc_smem + + entry->offset); +} + +void * +qcsmem_get(int host, int item, int *size) +{ + struct qcsmem_softc *sc = qcsmem_sc; + struct qcsmem_partition *part; + void *p = NULL; + int ret; + + if (sc == NULL) + return NULL; + + if (item >= sc->sc_item_count) + return NULL; + + ret = qcmtx_lock(sc, QCSMEM_X1E_LOCK_IDX, 1000); + if (ret) + return NULL; + + if (host >= 0 && + host < QCSMEM_HOST_COUNT && + sc->sc_partitions[host].phdr != NULL) { + part = &sc->sc_partitions[host]; + p = qcsmem_get_private(sc, part, item, size); + } else if (sc->sc_global_partition.phdr != NULL) { + part = &sc->sc_global_partition; + p = qcsmem_get_private(sc, part, item, size); + } else { + p = qcsmem_get_global(sc, item, size); + } + + qcmtx_unlock(sc, QCSMEM_X1E_LOCK_IDX); + return p; +} + +void +qcsmem_memset(void *ptr, uint8_t val, size_t len) +{ + if (len % 8 == 0 && val == 0) { + volatile uint64_t *p = ptr; + size_t n; + + for (n = 0; n < len; n += 8) { + p[n] = val; + } + } else { + volatile uint8_t *p = ptr; + size_t n; + + for (n = 0; n < len; n++) { + p[n] = val; + } + } +} + +static int +qcmtx_dolockunlock(struct qcsmem_softc *sc, u_int idx, int lock) +{ + if (idx >= QCMTX_NUM_LOCKS) + return ENXIO; + + if (lock) { + MTXWRITE4(sc, QCMTX_OFF(idx), QCMTX_APPS_PROC_ID); + if (MTXREAD4(sc, QCMTX_OFF(idx)) != + QCMTX_APPS_PROC_ID) + return EAGAIN; + KASSERT(MTXREAD4(sc, QCMTX_OFF(idx)) == QCMTX_APPS_PROC_ID); + } else { + KASSERT(MTXREAD4(sc, QCMTX_OFF(idx)) == QCMTX_APPS_PROC_ID); + MTXWRITE4(sc, QCMTX_OFF(idx), 0); + } + + return 0; +} + +static int +qcmtx_lock(struct qcsmem_softc *sc, u_int idx, u_int timeout_ms) +{ + int rv = EINVAL; + u_int n; + + for (n = 0; n < timeout_ms; n++) { + rv = qcmtx_dolockunlock(sc, idx, 1); + if (rv != EAGAIN) { + break; + } + delay(1000); + } + + return rv; +} + +static void +qcmtx_unlock(struct qcsmem_softc *sc, u_int idx) +{ + qcmtx_dolockunlock(sc, idx, 0); +} Index: src/sys/dev/acpi/qcomsmem.h diff -u /dev/null src/sys/dev/acpi/qcomsmem.h:1.1 --- /dev/null Mon Dec 30 12:31:10 2024 +++ src/sys/dev/acpi/qcomsmem.h Mon Dec 30 12:31:10 2024 @@ -0,0 +1,22 @@ +/* $NetBSD: qcomsmem.h,v 1.1 2024/12/30 12:31:10 jmcneill Exp $ */ +/* + * Copyright (c) 2023 Patrick Wildt <patr...@blueri.se> + * + * 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. + */ + +#pragma once + +int qcsmem_alloc(int, int, int); +void * qcsmem_get(int, int, int *); +void qcsmem_memset(void *, uint8_t, size_t); Index: src/sys/dev/acpi/qcomsmptp.c diff -u /dev/null src/sys/dev/acpi/qcomsmptp.c:1.1 --- /dev/null Mon Dec 30 12:31:10 2024 +++ src/sys/dev/acpi/qcomsmptp.c Mon Dec 30 12:31:10 2024 @@ -0,0 +1,332 @@ +/* $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 <patr...@blueri.se> + * + * 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 <sys/param.h> +#include <sys/systm.h> +#include <sys/device.h> +#include <sys/kmem.h> + +#include <dev/acpi/acpivar.h> +#include <dev/acpi/qcomipcc.h> +#include <dev/acpi/qcomsmem.h> +#include <dev/acpi/qcomsmptp.h> + +#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; +} Index: src/sys/dev/acpi/qcomsmptp.h diff -u /dev/null src/sys/dev/acpi/qcomsmptp.h:1.1 --- /dev/null Mon Dec 30 12:31:10 2024 +++ src/sys/dev/acpi/qcomsmptp.h Mon Dec 30 12:31:10 2024 @@ -0,0 +1,23 @@ +/* $NetBSD: qcomsmptp.h,v 1.1 2024/12/30 12:31:10 jmcneill Exp $ */ +/* + * Copyright (c) 2023 Patrick Wildt <patr...@blueri.se> + * + * 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. + */ + +#pragma once + +void *qcsmptp_intr_establish(u_int, int (*)(void *), void *); +void qcsmptp_intr_disestablish(void *); +void qcsmptp_intr_enable(void *); +void qcsmptp_intr_disable(void *); Index: src/sys/dev/acpi/qcomspmi.c diff -u /dev/null src/sys/dev/acpi/qcomspmi.c:1.1 --- /dev/null Mon Dec 30 12:31:10 2024 +++ src/sys/dev/acpi/qcomspmi.c Mon Dec 30 12:31:10 2024 @@ -0,0 +1,399 @@ +/* $NetBSD: qcomspmi.c,v 1.1 2024/12/30 12:31:10 jmcneill Exp $ */ +/* $OpenBSD: qcspmi.c,v 1.6 2024/08/14 10:54:58 mglocker Exp $ */ +/* + * Copyright (c) 2022 Patrick Wildt <patr...@blueri.se> + * + * 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 <sys/param.h> +#include <sys/kmem.h> +#include <sys/systm.h> +#include <sys/bus.h> +#include <sys/device.h> + +#include <dev/acpi/acpivar.h> + +/* SPMI commands */ +#define SPMI_CMD_EXT_WRITEL 0x30 +#define SPMI_CMD_EXT_READL 0x38 + +/* Core registers. */ +#define SPMI_VERSION 0x00 +#define SPMI_VERSION_V2_MIN 0x20010000 +#define SPMI_VERSION_V3_MIN 0x30000000 +#define SPMI_VERSION_V5_MIN 0x50000000 +#define SPMI_VERSION_V7_MIN 0x70000000 +#define SPMI_ARB_APID_MAP(sc, x) ((sc)->sc_arb_apid_map + (x) * 0x4) +#define SPMI_ARB_APID_MAP_PPID_MASK 0xfff +#define SPMI_ARB_APID_MAP_PPID_SHIFT 8 +#define SPMI_ARB_APID_MAP_IRQ_OWNER (1 << 14) + +/* Channel registers. */ +#define SPMI_CHAN_OFF(sc, x) ((sc)->sc_chan_stride * (x)) +#define SPMI_OBSV_OFF(sc, x, y) \ + ((sc)->sc_obsv_ee_stride * (x) + (sc)->sc_obsv_apid_stride * (y)) +#define SPMI_COMMAND 0x00 +#define SPMI_COMMAND_OP_EXT_WRITEL (0 << 27) +#define SPMI_COMMAND_OP_EXT_READL (1 << 27) +#define SPMI_COMMAND_OP_EXT_WRITE (2 << 27) +#define SPMI_COMMAND_OP_RESET (3 << 27) +#define SPMI_COMMAND_OP_SLEEP (4 << 27) +#define SPMI_COMMAND_OP_SHUTDOWN (5 << 27) +#define SPMI_COMMAND_OP_WAKEUP (6 << 27) +#define SPMI_COMMAND_OP_AUTHENTICATE (7 << 27) +#define SPMI_COMMAND_OP_MSTR_READ (8 << 27) +#define SPMI_COMMAND_OP_MSTR_WRITE (9 << 27) +#define SPMI_COMMAND_OP_EXT_READ (13 << 27) +#define SPMI_COMMAND_OP_WRITE (14 << 27) +#define SPMI_COMMAND_OP_READ (15 << 27) +#define SPMI_COMMAND_OP_ZERO_WRITE (16 << 27) +#define SPMI_COMMAND_ADDR(x) (((x) & 0xff) << 4) +#define SPMI_COMMAND_LEN(x) (((x) & 0x7) << 0) +#define SPMI_CONFIG 0x04 +#define SPMI_STATUS 0x08 +#define SPMI_STATUS_DONE (1 << 0) +#define SPMI_STATUS_FAILURE (1 << 1) +#define SPMI_STATUS_DENIED (1 << 2) +#define SPMI_STATUS_DROPPED (1 << 3) +#define SPMI_WDATA0 0x10 +#define SPMI_WDATA1 0x14 +#define SPMI_RDATA0 0x18 +#define SPMI_RDATA1 0x1c +#define SPMI_ACC_ENABLE 0x100 +#define SPMI_ACC_ENABLE_BIT (1 << 0) +#define SPMI_IRQ_STATUS 0x104 +#define SPMI_IRQ_CLEAR 0x108 + +/* Intr registers */ +#define SPMI_OWNER_ACC_STATUS(sc, x, y) \ + ((sc)->sc_chan_stride * (x) + 0x4 * (y)) + +/* Config registers */ +#define SPMI_OWNERSHIP_TABLE(sc, x) ((sc)->sc_ownership_table + (x) * 0x4) +#define SPMI_OWNERSHIP_TABLE_OWNER(x) ((x) & 0x7) + +/* Misc */ +#define SPMI_MAX_PERIPH 1024 +#define SPMI_MAX_PPID 4096 +#define SPMI_PPID_TO_APID_VALID (1U << 15) +#define SPMI_PPID_TO_APID_MASK (0x7fff) + +/* Intr commands */ +#define INTR_RT_STS 0x10 +#define INTR_SET_TYPE 0x11 +#define INTR_POLARITY_HIGH 0x12 +#define INTR_POLARITY_LOW 0x13 +#define INTR_LATCHED_CLR 0x14 +#define INTR_EN_SET 0x15 +#define INTR_EN_CLR 0x16 +#define INTR_LATCHED_STS 0x18 + +#define HREAD4(sc, obj, reg) \ + bus_space_read_4((sc)->sc_iot, (sc)->sc_ioh, \ + (sc)->sc_data->regs[obj] + (reg)) +#define HWRITE4(sc, obj, reg, val) \ + bus_space_write_4((sc)->sc_iot, (sc)->sc_ioh, \ + (sc)->sc_data->regs[obj] + (reg), (val)) + +#define QCSPMI_REG_CORE 0 +#define QCSPMI_REG_CHNLS 1 +#define QCSPMI_REG_OBSRVR 2 +#define QCSPMI_REG_INTR 3 +#define QCSPMI_REG_CNFG 4 +#define QCSPMI_REG_MAX 5 + +struct qcspmi_apid { + uint16_t ppid; + uint8_t write_ee; + uint8_t irq_ee; +}; + +struct qcspmi_data { + bus_size_t regs[QCSPMI_REG_MAX]; + int ee; +}; + +struct qcspmi_softc { + device_t sc_dev; + + bus_space_tag_t sc_iot; + bus_space_handle_t sc_ioh; + + const struct qcspmi_data *sc_data; + + int sc_ee; + + struct qcspmi_apid sc_apid[SPMI_MAX_PERIPH]; + uint16_t sc_ppid_to_apid[SPMI_MAX_PPID]; + uint16_t sc_max_periph; + bus_size_t sc_chan_stride; + bus_size_t sc_obsv_ee_stride; + bus_size_t sc_obsv_apid_stride; + bus_size_t sc_arb_apid_map; + bus_size_t sc_ownership_table; +}; + +static int qcspmi_match(device_t, cfdata_t, void *); +static void qcspmi_attach(device_t, device_t, void *); + +int qcspmi_cmd_read(struct qcspmi_softc *, uint8_t, uint8_t, + uint16_t, void *, size_t); +int qcspmi_cmd_write(struct qcspmi_softc *, uint8_t, uint8_t, uint16_t, + const void *, size_t); + +CFATTACH_DECL_NEW(qcomspmi, sizeof(struct qcspmi_softc), + qcspmi_match, qcspmi_attach, NULL, NULL); + +static const struct qcspmi_data qcspmi_x1e_data = { + .ee = 0, + .regs = { + [QCSPMI_REG_CORE] = 0x0, + [QCSPMI_REG_CHNLS] = 0x100000, + [QCSPMI_REG_OBSRVR] = 0x40000, + [QCSPMI_REG_INTR] = 0xc0000, + [QCSPMI_REG_CNFG] = 0x2d000, + }, +}; + +static const struct device_compatible_entry compat_data[] = { + { .compat = "QCOM0C0B", .data = &qcspmi_x1e_data }, + DEVICE_COMPAT_EOL +}; + +static int +qcspmi_match(device_t parent, cfdata_t match, void *aux) +{ + struct acpi_attach_args *aa = aux; + + return acpi_compatible_match(aa, compat_data); +} + +void +qcspmi_attach(device_t parent, device_t self, void *aux) +{ + struct acpi_attach_args *aa = aux; + struct qcspmi_softc *sc = device_private(self); + struct qcspmi_apid *apid, *last_apid; + struct acpi_resources res; + struct acpi_mem *mem; + uint32_t val, ppid, irq_own; + ACPI_STATUS rv; + int error, i; + + rv = acpi_resource_parse(sc->sc_dev, aa->aa_node->ad_handle, "_CRS", + &res, &acpi_resource_parse_ops_default); + if (ACPI_FAILURE(rv)) { + return; + } + + mem = acpi_res_mem(&res, 0); + if (mem == NULL) { + aprint_error_dev(self, "couldn't find mem resource\n"); + goto done; + } + + sc->sc_dev = self; + sc->sc_data = acpi_compatible_lookup(aa, compat_data)->data; + sc->sc_iot = aa->aa_memt; + error = bus_space_map(sc->sc_iot, mem->ar_base, mem->ar_length, 0, + &sc->sc_ioh); + if (error != 0) { + aprint_error_dev(self, "couldn't map registers\n"); + goto done; + } + + /* Support only version 5 and 7 for now */ + val = HREAD4(sc, QCSPMI_REG_CORE, SPMI_VERSION); + if (val < SPMI_VERSION_V5_MIN) { + printf(": unsupported version 0x%08x\n", val); + return; + } + + if (val < SPMI_VERSION_V7_MIN) { + sc->sc_max_periph = 512; + sc->sc_chan_stride = 0x10000; + sc->sc_obsv_ee_stride = 0x10000; + sc->sc_obsv_apid_stride = 0x00080; + sc->sc_arb_apid_map = 0x00900; + sc->sc_ownership_table = 0x00700; + } else { + sc->sc_max_periph = 1024; + sc->sc_chan_stride = 0x01000; + sc->sc_obsv_ee_stride = 0x08000; + sc->sc_obsv_apid_stride = 0x00020; + sc->sc_arb_apid_map = 0x02000; + sc->sc_ownership_table = 0x00000; + } + + KASSERT(sc->sc_max_periph <= SPMI_MAX_PERIPH); + + sc->sc_ee = sc->sc_data->ee; + + for (i = 0; i < sc->sc_max_periph; i++) { + val = HREAD4(sc, QCSPMI_REG_CORE, SPMI_ARB_APID_MAP(sc, i)); + if (!val) + continue; + ppid = (val >> SPMI_ARB_APID_MAP_PPID_SHIFT) & + SPMI_ARB_APID_MAP_PPID_MASK; + irq_own = val & SPMI_ARB_APID_MAP_IRQ_OWNER; + val = HREAD4(sc, QCSPMI_REG_CNFG, SPMI_OWNERSHIP_TABLE(sc, i)); + apid = &sc->sc_apid[i]; + apid->write_ee = SPMI_OWNERSHIP_TABLE_OWNER(val); + apid->irq_ee = 0xff; + if (irq_own) + apid->irq_ee = apid->write_ee; + last_apid = &sc->sc_apid[sc->sc_ppid_to_apid[ppid] & + SPMI_PPID_TO_APID_MASK]; + if (!(sc->sc_ppid_to_apid[ppid] & SPMI_PPID_TO_APID_VALID) || + apid->write_ee == sc->sc_ee) { + sc->sc_ppid_to_apid[ppid] = SPMI_PPID_TO_APID_VALID | i; + } else if ((sc->sc_ppid_to_apid[ppid] & + SPMI_PPID_TO_APID_VALID) && irq_own && + last_apid->write_ee == sc->sc_ee) { + last_apid->irq_ee = apid->irq_ee; + } + } + +done: + acpi_resource_cleanup(&res); +} + +int +qcspmi_cmd_read(struct qcspmi_softc *sc, uint8_t sid, uint8_t cmd, + uint16_t addr, void *buf, size_t len) +{ + uint8_t *cbuf = buf; + uint32_t reg; + uint16_t apid, ppid; + int bc = len - 1; + int i; + + if (len == 0 || len > 8) + return EINVAL; + + /* TODO: support more types */ + if (cmd != SPMI_CMD_EXT_READL) + return EINVAL; + + ppid = (sid << 8) | (addr >> 8); + if (!(sc->sc_ppid_to_apid[ppid] & SPMI_PPID_TO_APID_VALID)) + return ENXIO; + apid = sc->sc_ppid_to_apid[ppid] & SPMI_PPID_TO_APID_MASK; + + HWRITE4(sc, QCSPMI_REG_OBSRVR, + SPMI_OBSV_OFF(sc, sc->sc_ee, apid) + SPMI_COMMAND, + SPMI_COMMAND_OP_EXT_READL | SPMI_COMMAND_ADDR(addr) | + SPMI_COMMAND_LEN(bc)); + + for (i = 1000; i > 0; i--) { + reg = HREAD4(sc, QCSPMI_REG_OBSRVR, + SPMI_OBSV_OFF(sc, sc->sc_ee, apid) + SPMI_STATUS); + if (reg & SPMI_STATUS_DONE) + break; + if (reg & SPMI_STATUS_FAILURE) { + printf(": transaction failed\n"); + return EIO; + } + if (reg & SPMI_STATUS_DENIED) { + printf(": transaction denied\n"); + return EIO; + } + if (reg & SPMI_STATUS_DROPPED) { + printf(": transaction dropped\n"); + return EIO; + } + } + if (i == 0) { + printf("\n"); + return ETIMEDOUT; + } + + if (len > 0) { + reg = HREAD4(sc, QCSPMI_REG_OBSRVR, + SPMI_OBSV_OFF(sc, sc->sc_ee, apid) + SPMI_RDATA0); + memcpy(cbuf, ®, MIN(len, 4)); + cbuf += MIN(len, 4); + len -= MIN(len, 4); + } + if (len > 0) { + reg = HREAD4(sc, QCSPMI_REG_OBSRVR, + SPMI_OBSV_OFF(sc, sc->sc_ee, apid) + SPMI_RDATA1); + memcpy(cbuf, ®, MIN(len, 4)); + cbuf += MIN(len, 4); + len -= MIN(len, 4); + } + + return 0; +} + +int +qcspmi_cmd_write(struct qcspmi_softc *sc, uint8_t sid, uint8_t cmd, + uint16_t addr, const void *buf, size_t len) +{ + const uint8_t *cbuf = buf; + uint32_t reg; + uint16_t apid, ppid; + int bc = len - 1; + int i; + + if (len == 0 || len > 8) + return EINVAL; + + /* TODO: support more types */ + if (cmd != SPMI_CMD_EXT_WRITEL) + return EINVAL; + + ppid = (sid << 8) | (addr >> 8); + if (!(sc->sc_ppid_to_apid[ppid] & SPMI_PPID_TO_APID_VALID)) + return ENXIO; + apid = sc->sc_ppid_to_apid[ppid] & SPMI_PPID_TO_APID_MASK; + + if (sc->sc_apid[apid].write_ee != sc->sc_ee) + return EPERM; + + if (len > 0) { + memcpy(®, cbuf, MIN(len, 4)); + HWRITE4(sc, QCSPMI_REG_CHNLS, SPMI_CHAN_OFF(sc, apid) + + SPMI_WDATA0, reg); + cbuf += MIN(len, 4); + len -= MIN(len, 4); + } + if (len > 0) { + memcpy(®, cbuf, MIN(len, 4)); + HWRITE4(sc, QCSPMI_REG_CHNLS, SPMI_CHAN_OFF(sc, apid) + + SPMI_WDATA1, reg); + cbuf += MIN(len, 4); + len -= MIN(len, 4); + } + + HWRITE4(sc, QCSPMI_REG_CHNLS, SPMI_CHAN_OFF(sc, apid) + SPMI_COMMAND, + SPMI_COMMAND_OP_EXT_WRITEL | SPMI_COMMAND_ADDR(addr) | + SPMI_COMMAND_LEN(bc)); + + for (i = 1000; i > 0; i--) { + reg = HREAD4(sc, QCSPMI_REG_CHNLS, SPMI_CHAN_OFF(sc, apid) + + SPMI_STATUS); + if (reg & SPMI_STATUS_DONE) + break; + } + if (i == 0) + return ETIMEDOUT; + + if (reg & SPMI_STATUS_FAILURE || + reg & SPMI_STATUS_DENIED || + reg & SPMI_STATUS_DROPPED) + return EIO; + + return 0; +}