Manage RTC offset through UEFI variables handled by a TEE application that
authorpatrick <patrick@openbsd.org>
Mon, 16 Jan 2023 20:12:38 +0000 (20:12 +0000)
committerpatrick <patrick@openbsd.org>
Mon, 16 Jan 2023 20:12:38 +0000 (20:12 +0000)
can be interacted with using SMC calls.

ok kettenis@

sys/arch/arm64/conf/GENERIC
sys/arch/arm64/conf/RAMDISK
sys/dev/fdt/files.fdt
sys/dev/fdt/qcrtc.c
sys/dev/fdt/qcscm.c [new file with mode: 0644]

index cbad3c8..99dcaea 100644 (file)
@@ -1,4 +1,4 @@
-# $OpenBSD: GENERIC,v 1.252 2022/12/24 10:51:27 patrick Exp $
+# $OpenBSD: GENERIC,v 1.253 2023/01/16 20:12:38 patrick Exp $
 #
 # GENERIC machine description file
 #
@@ -316,6 +316,7 @@ qciic*              at acpi?
 qciic*         at fdt?
 iic*           at qciic?
 qcpdc*         at fdt?
+qcscm*         at fdt?
 qcspmi*                at fdt?
 qcpmic*                at qcspmi?
 qcpmicgpio*    at qcpmic?
index 9a93b39..36dfcfc 100644 (file)
@@ -1,4 +1,4 @@
-# $OpenBSD: RAMDISK,v 1.186 2022/12/24 10:51:27 patrick Exp $
+# $OpenBSD: RAMDISK,v 1.187 2023/01/16 20:12:38 patrick Exp $
 
 machine                arm64
 maxusers       4
@@ -240,6 +240,7 @@ qciic*              at acpi?
 qciic*         at fdt?
 iic*           at qciic?
 qcpdc*         at fdt?
+qcscm*         at fdt?
 qcspmi*                at fdt?
 qcpmic*                at qcspmi?
 qcpmicgpio*    at qcpmic?
index d5e55d0..45a680b 100644 (file)
@@ -1,4 +1,4 @@
-#      $OpenBSD: files.fdt,v 1.174 2023/01/01 01:34:33 jsg Exp $
+#      $OpenBSD: files.fdt,v 1.175 2023/01/16 20:12:38 patrick Exp $
 #
 # Config file and device description for machine-independent FDT code.
 # Included by ports that need it.
@@ -643,6 +643,11 @@ file       dev/fdt/qcgpio_fdt.c            qcgpio
 attach qciic at fdt with qciic_fdt
 file   dev/fdt/qciic_fdt.c             qciic
 
+# Qualcomm SCM
+device qcscm
+attach qcscm at fdt
+file   dev/fdt/qcscm.c                 qcscm
+
 # Qualcomm SPMI controller
 device qcspmi: spmi
 attach qcspmi at fdt
index 830181e..bc1e78d 100644 (file)
@@ -1,4 +1,4 @@
-/*     $OpenBSD: qcrtc.c,v 1.1 2022/11/08 19:47:05 patrick Exp $       */
+/*     $OpenBSD: qcrtc.c,v 1.2 2023/01/16 20:12:38 patrick Exp $       */
 /*
  * Copyright (c) 2022 Patrick Wildt <patrick@blueri.se>
  *
@@ -61,6 +61,9 @@ int   qcrtc_settime(struct todr_chip_handle *, struct timeval *);
 
 void   qcrtc_tick(void *);
 
+extern int qcscm_uefi_rtc_get(uint32_t *);
+extern int qcscm_uefi_rtc_set(uint32_t);
+
 int
 qcrtc_match(struct device *parent, void *match, void *aux)
 {
@@ -100,9 +103,10 @@ int
 qcrtc_gettime(struct todr_chip_handle *handle, struct timeval *tv)
 {
        struct qcrtc_softc *sc = handle->cookie;
-       uint32_t reg;
+       uint32_t reg, off;
        int error;
 
+       /* Read current counting RTC value. */
        error = spmi_cmd_read(sc->sc_tag, sc->sc_sid, SPMI_CMD_EXT_READL,
            sc->sc_addr + RTC_READ, &reg, sizeof(reg));
        if (error) {
@@ -110,16 +114,33 @@ qcrtc_gettime(struct todr_chip_handle *handle, struct timeval *tv)
                return error;
        }
 
-       tv->tv_sec = reg;
-       tv->tv_usec = 0;
+       /* Retrieve RTC offset stored in UEFI. */
+       if (qcscm_uefi_rtc_get(&off) != 0)
+               return EIO;
 
-       /* We need to offset. */
-       return EIO;
+       /* Add RTC counter and 10y+1w to get seconds from epoch. */
+       tv->tv_sec = off + (reg + (10 * 365 * 86400 + 7 * 86400));
+       tv->tv_usec = 0;
+       return 0;
 }
 
 int
 qcrtc_settime(struct todr_chip_handle *handle, struct timeval *tv)
 {
-       /* XXX: We can't set the time for now, hardware reasons. */
-       return EPERM;
+       struct qcrtc_softc *sc = handle->cookie;
+       uint32_t reg, off;
+       int error;
+
+       error = spmi_cmd_read(sc->sc_tag, sc->sc_sid, SPMI_CMD_EXT_READL,
+           sc->sc_addr + RTC_READ, &reg, sizeof(reg));
+       if (error) {
+               printf("%s: error reading RTC\n", sc->sc_dev.dv_xname);
+               return error;
+       }
+
+       /* Subtract RTC counter and 10y+1w to get offset for UEFI. */
+       off = tv->tv_sec - (reg + (10 * 365 * 86400 + 7 * 86400));
+
+       /* Store offset in UEFI. */
+       return qcscm_uefi_rtc_set(off);
 }
diff --git a/sys/dev/fdt/qcscm.c b/sys/dev/fdt/qcscm.c
new file mode 100644 (file)
index 0000000..180a4b0
--- /dev/null
@@ -0,0 +1,783 @@
+/* $OpenBSD: qcscm.c,v 1.1 2023/01/16 20:12:38 patrick Exp $ */
+/*
+ * Copyright (c) 2022 Patrick Wildt <patrick@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/queue.h>
+#include <sys/malloc.h>
+#include <sys/sysctl.h>
+#include <sys/device.h>
+#include <sys/evcount.h>
+#include <sys/socket.h>
+#include <sys/timeout.h>
+#include <sys/atomic.h>
+
+#include <uvm/uvm_extern.h>
+
+#include <machine/intr.h>
+#include <machine/bus.h>
+#include <machine/fdt.h>
+
+#include <dev/efi/efi.h>
+
+#include <dev/ofw/openfirm.h>
+#include <dev/ofw/ofw_misc.h>
+#include <dev/ofw/fdt.h>
+
+/* #define QCSCM_DEBUG */
+
+#define ARM_SMCCC_STD_CALL                     (0U << 31)
+#define ARM_SMCCC_FAST_CALL                    (1U << 31)
+#define ARM_SMCCC_LP64                         (1U << 30)
+
+#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_INTERRUPTED              1
+
+#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
+
+struct qcscm_dmamem {
+       bus_dmamap_t            qdm_map;
+       bus_dma_segment_t       qdm_seg;
+       size_t                  qdm_size;
+       caddr_t                 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)    ((void *)(_qdm)->qdm_kva)
+
+EFI_GUID qcscm_uefi_rtcinfo_guid =
+  { 0x882f8c2b, 0x9646, 0x435f,
+    { 0x8d, 0xe5, 0xf2, 0x08, 0xff, 0x80, 0xc1, 0xbd } };
+
+struct qcscm_softc {
+       struct device           sc_dev;
+       int                     sc_node;
+       bus_dma_tag_t           sc_dmat;
+
+       struct qcscm_dmamem     *sc_extarg;
+       uint32_t                sc_uefi_id;
+};
+
+int    qcscm_match(struct device *, void *, void *);
+void   qcscm_attach(struct device *parent, struct device *self, void *args);
+
+const struct cfattach  qcscm_ca = {
+       sizeof (struct qcscm_softc), qcscm_match, qcscm_attach
+};
+
+struct cfdriver qcscm_cd = {
+       NULL, "qcscm", DV_DULL
+};
+
+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 *, CHAR16 *,
+           int, EFI_GUID *, uint32_t *, uint8_t *, int *);
+EFI_STATUS qcscm_uefi_set_variable(struct qcscm_softc *, CHAR16 *,
+           int, EFI_GUID *, uint32_t, uint8_t *, int);
+EFI_STATUS qcscm_uefi_get_next_variable(struct qcscm_softc *,
+           CHAR16 *, int *, EFI_GUID *);
+
+#ifdef QCSCM_DEBUG
+void   qcscm_uefi_dump_variables(struct qcscm_softc *);
+void   qcscm_uefi_dump_variable(struct qcscm_softc *, CHAR16 *, int,
+           EFI_GUID *);
+#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;
+
+int
+qcscm_match(struct device *parent, void *match, void *aux)
+{
+       struct fdt_attach_args *faa = aux;
+
+       return OF_is_compatible(faa->fa_node, "qcom,scm");
+}
+
+void
+qcscm_attach(struct device *parent, struct device *self, void *aux)
+{
+       struct qcscm_softc *sc = (struct qcscm_softc *)self;
+       struct fdt_attach_args *faa = aux;
+
+       sc->sc_node = faa->fa_node;
+       sc->sc_dmat = faa->fa_dmat;
+
+       sc->sc_extarg = qcscm_dmamem_alloc(sc, PAGE_SIZE, 8);
+       if (sc->sc_extarg == NULL) {
+               printf(": can't allocate memory for extended args\n");
+               return;
+       }
+
+       if (qcscm_tee_app_get_id(sc, "qcom.tz.uefisecapp", &sc->sc_uefi_id)) {
+               printf(": can't retrieve UEFI App\n");
+               return;
+       }
+
+       printf("\n");
+       qcscm_sc = sc;
+
+#ifdef QCSCM_DEBUG
+       qcscm_uefi_dump_variables(sc);
+       qcscm_uefi_dump_variable(sc, u"RTCInfo", sizeof(u"RTCInfo"),
+           &qcscm_uefi_rtcinfo_guid);
+#endif
+}
+
+/* Expects an uint64_t[8] */
+void
+qcscm_smc_exec(uint64_t *in, uint64_t *out)
+{
+       __asm(
+           "ldp x0, x1, [%0, %2]\n"
+           "ldp x2, x3, [%0, %3]\n"
+           "ldp x4, x5, [%0, %4]\n"
+           "ldp x6, x7, [%0, %5]\n"
+           "smc #0\n"
+           "stp x0, x1, [%1, %2]\n"
+           "stp x2, x3, [%1, %3]\n"
+           "stp x4, x5, [%1, %4]\n"
+           "stp x6, x7, [%1, %5]\n" ::
+           "r" (in), "r" (out),
+           "i"(0), "i"(16), "i"(32), "i"(48),
+           "m" (*in), "m" (*out) :
+           "x0", "x1", "x2", "x3",
+           "x4", "x5", "x6", "x7");
+}
+
+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[8] = { 0 }, smcres[8] = { 0 };
+       uint64_t *smcextreq;
+       int i;
+
+       /* 4 of our 10 possible args fit into x2-x5 */
+       smcreq[0] = ARM_SMCCC_STD_CALL | ARM_SMCCC_LP64 |
+           owner << 24 | svc << 8 | cmd;
+       smcreq[1] = arginfo;
+       for (i = 0; i < min(arglen, 4); i++)
+               smcreq[2 + i] = args[i];
+
+       /* In case we have more than 4, use x5 as ptr to extra args */
+       smcextreq = QCSCM_DMA_KVA(sc->sc_extarg);
+       if (arglen > 4) {
+               smcreq[5] = QCSCM_DMA_DVA(sc->sc_extarg);
+               smcextreq = QCSCM_DMA_KVA(sc->sc_extarg);
+               for (i = 0; i < min(arglen - 3, 7); i++) {
+                       smcextreq[i] = args[3 + i];
+               }
+       }
+
+       for (;;) {
+               qcscm_smc_exec(smcreq, smcres);
+               /* If the call gets interrupted, try again and re-pass x0/x6 */
+               if (smcres[0] == QCSCM_INTERRUPTED) {
+                       smcreq[0] = smcres[0];
+                       smcreq[6] = smcres[6];
+                       continue;
+               }
+               break;
+       }
+
+       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), name, strlen(name));
+
+       /* Pass address of name and length */
+       arginfo = QCSCM_ARGINFO_NUM(nitems(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);
+
+       /* Make call into TEE */
+       ret = qcscm_smc_call(sc, QCTEE_TZ_OWNER_QSEE_OS, QCTEE_TZ_SVC_APP_MGR,
+           0x03, arginfo, args, nitems(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(nitems(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;
+
+       membar_sync();
+
+       /* Make call into TEE */
+       ret = qcscm_smc_call(sc, QCTEE_TZ_OWNER_TZ_APPS,
+           QCTEE_TZ_SVC_APP_ID_PLACEHOLDER, 0x01,
+           arginfo, args, nitems(args), res);
+
+       membar_sync();
+
+       /* 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,
+    CHAR16 *name, int name_size, EFI_GUID *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;
+
+       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);
+
+       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_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)
+                       *data_size = resp->data_size;
+               if (attributes)
+                       *attributes = resp->attributes;
+               ret = resp->status;
+               qcscm_dmamem_free(sc, qdm);
+               return ret;
+       }
+
+       if (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,
+    CHAR16 *name, int name_size, EFI_GUID *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;
+
+       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,
+    CHAR16 *name, int *name_size, EFI_GUID *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;
+
+       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;
+}
+
+#ifdef QCSCM_DEBUG
+void
+qcscm_uefi_dump_variables(struct qcscm_softc *sc)
+{
+       CHAR16 name[128];
+       EFI_GUID 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: ", sc->sc_dev.dv_xname);
+                       for (i = 0; i < namesize / 2; i++)
+                               printf("%c", name[i]);
+                       printf(" { 0x%08x, 0x%04x, 0x%04x, { ",
+                          guid.Data1, guid.Data2, guid.Data3);
+                       for (i = 0; i < 8; i++) {
+                               printf(" 0x%02x,", guid.Data4[i]);
+                       }
+                       printf(" }");
+                       printf("\n");
+                       namesize = sizeof(name);
+                       continue;
+               }
+               break;
+       }
+}
+
+void
+qcscm_uefi_dump_variable(struct qcscm_softc *sc, CHAR16 *name, int namesize,
+    EFI_GUID *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 ", sc->sc_dev.dv_xname);
+               for (i = 0; i < namesize / 2; i++)
+                       printf("%c", name[i]);
+               printf("\n");
+               return;
+       }
+
+       printf("%s: ", sc->sc_dev.dv_xname);
+       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, u"RTCInfo", sizeof(u"RTCInfo"),
+           &qcscm_uefi_rtcinfo_guid, NULL, (uint8_t *)rtcinfo,
+           &rtcinfosize) != 0)
+               return EIO;
+
+       *off = rtcinfo[0];
+       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, u"RTCInfo", sizeof(u"RTCInfo"),
+           &qcscm_uefi_rtcinfo_guid, NULL, (uint8_t *)rtcinfo,
+           &rtcinfosize) != 0)
+               return EIO;
+
+       /* No need to set if we're not changing anything */
+       if (rtcinfo[0] == off)
+               return 0;
+
+       rtcinfo[0] = off;
+       rtcinfo[1] = 0x10000;
+       rtcinfo[2] = 0;
+
+       if (qcscm_uefi_set_variable(sc, u"RTCInfo", sizeof(u"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;
+}
+
+/* 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 = malloc(sizeof(*qdm), M_DEVBUF, M_WAITOK | M_ZERO);
+       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;
+
+       bzero(qdm->qdm_kva, 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:
+       free(qdm, M_DEVBUF, 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);
+       free(qdm, M_DEVBUF, sizeof(*qdm));
+}