/* * VMApple Backdoor Interface * * Copyright © 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * This work is licensed under the terms of the GNU GPL, version 2 or later. * See the COPYING file in the top-level directory. * * SPDX-License-Identifier: GPL-2.0-or-later */ #include "qemu/osdep.h" #include "qemu/units.h" #include "qemu/log.h" #include "qemu/module.h" #include "trace.h" #include "hw/vmapple/vmapple.h" #include "hw/sysbus.h" #include "hw/block/block.h" #include "qapi/error.h" #include "system/block-backend.h" #include "system/dma.h" OBJECT_DECLARE_SIMPLE_TYPE(VMAppleBdifState, VMAPPLE_BDIF) struct VMAppleBdifState { SysBusDevice parent_obj; BlockBackend *aux; BlockBackend *root; MemoryRegion mmio; }; #define VMAPPLE_BDIF_SIZE 0x00200000 #define REG_DEVID_MASK 0xffff0000 #define DEVID_ROOT 0x00000000 #define DEVID_AUX 0x00010000 #define DEVID_USB 0x00100000 #define REG_STATUS 0x0 #define REG_STATUS_ACTIVE BIT(0) #define REG_CFG 0x4 #define REG_CFG_ACTIVE BIT(1) #define REG_UNK1 0x8 #define REG_BUSY 0x10 #define REG_BUSY_READY BIT(0) #define REG_UNK2 0x400 #define REG_CMD 0x408 #define REG_NEXT_DEVICE 0x420 #define REG_UNK3 0x434 typedef struct VblkSector { uint32_t pad; uint32_t pad2; uint32_t sector; uint32_t pad3; } VblkSector; typedef struct VblkReqCmd { uint64_t addr; uint32_t len; uint32_t flags; } VblkReqCmd; typedef struct VblkReq { VblkReqCmd sector; VblkReqCmd data; VblkReqCmd retval; } VblkReq; #define VBLK_DATA_FLAGS_READ 0x00030001 #define VBLK_DATA_FLAGS_WRITE 0x00010001 #define VBLK_RET_SUCCESS 0 #define VBLK_RET_FAILED 1 static uint64_t bdif_read(void *opaque, hwaddr offset, unsigned size) { uint64_t ret = -1; uint64_t devid = offset & REG_DEVID_MASK; switch (offset & ~REG_DEVID_MASK) { case REG_STATUS: ret = REG_STATUS_ACTIVE; break; case REG_CFG: ret = REG_CFG_ACTIVE; break; case REG_UNK1: ret = 0x420; break; case REG_BUSY: ret = REG_BUSY_READY; break; case REG_UNK2: ret = 0x1; break; case REG_UNK3: ret = 0x0; break; case REG_NEXT_DEVICE: switch (devid) { case DEVID_ROOT: ret = 0x8000000; break; case DEVID_AUX: ret = 0x10000; break; } break; } trace_bdif_read(offset, size, ret); return ret; } static void le2cpu_sector(VblkSector *sector) { sector->sector = le32_to_cpu(sector->sector); } static void le2cpu_reqcmd(VblkReqCmd *cmd) { cmd->addr = le64_to_cpu(cmd->addr); cmd->len = le32_to_cpu(cmd->len); cmd->flags = le32_to_cpu(cmd->flags); } static void le2cpu_req(VblkReq *req) { le2cpu_reqcmd(&req->sector); le2cpu_reqcmd(&req->data); le2cpu_reqcmd(&req->retval); } static void vblk_cmd(uint64_t devid, BlockBackend *blk, uint64_t gp_addr, uint64_t static_off) { VblkReq req; VblkSector sector; uint64_t off = 0; g_autofree char *buf = NULL; uint8_t ret = VBLK_RET_FAILED; int r; MemTxResult dma_result; dma_result = dma_memory_read(&address_space_memory, gp_addr, &req, sizeof(req), MEMTXATTRS_UNSPECIFIED); if (dma_result != MEMTX_OK) { goto out; } le2cpu_req(&req); if (req.sector.len != sizeof(sector)) { goto out; } /* Read the vblk command */ dma_result = dma_memory_read(&address_space_memory, req.sector.addr, §or, sizeof(sector), MEMTXATTRS_UNSPECIFIED); if (dma_result != MEMTX_OK) { goto out; } le2cpu_sector(§or); off = sector.sector * 512ULL + static_off; /* Sanity check that we're not allocating bogus sizes */ if (req.data.len > 128 * MiB) { goto out; } buf = g_malloc0(req.data.len); switch (req.data.flags) { case VBLK_DATA_FLAGS_READ: r = blk_pread(blk, off, req.data.len, buf, 0); trace_bdif_vblk_read(devid == DEVID_AUX ? "aux" : "root", req.data.addr, off, req.data.len, r); if (r < 0) { goto out; } dma_result = dma_memory_write(&address_space_memory, req.data.addr, buf, req.data.len, MEMTXATTRS_UNSPECIFIED); if (dma_result == MEMTX_OK) { ret = VBLK_RET_SUCCESS; } break; case VBLK_DATA_FLAGS_WRITE: /* Not needed, iBoot only reads */ break; default: break; } out: dma_memory_write(&address_space_memory, req.retval.addr, &ret, 1, MEMTXATTRS_UNSPECIFIED); } static void bdif_write(void *opaque, hwaddr offset, uint64_t value, unsigned size) { VMAppleBdifState *s = opaque; uint64_t devid = (offset & REG_DEVID_MASK); trace_bdif_write(offset, size, value); switch (offset & ~REG_DEVID_MASK) { case REG_CMD: switch (devid) { case DEVID_ROOT: vblk_cmd(devid, s->root, value, 0x0); break; case DEVID_AUX: vblk_cmd(devid, s->aux, value, 0x0); break; } break; } } static const MemoryRegionOps bdif_ops = { .read = bdif_read, .write = bdif_write, .endianness = DEVICE_NATIVE_ENDIAN, .valid = { .min_access_size = 1, .max_access_size = 8, }, .impl = { .min_access_size = 1, .max_access_size = 8, }, }; static void bdif_init(Object *obj) { VMAppleBdifState *s = VMAPPLE_BDIF(obj); memory_region_init_io(&s->mmio, obj, &bdif_ops, obj, "VMApple Backdoor Interface", VMAPPLE_BDIF_SIZE); sysbus_init_mmio(SYS_BUS_DEVICE(obj), &s->mmio); } static const Property bdif_properties[] = { DEFINE_PROP_DRIVE("aux", VMAppleBdifState, aux), DEFINE_PROP_DRIVE("root", VMAppleBdifState, root), }; static void bdif_class_init(ObjectClass *klass, const void *data) { DeviceClass *dc = DEVICE_CLASS(klass); dc->desc = "VMApple Backdoor Interface"; device_class_set_props(dc, bdif_properties); } static const TypeInfo bdif_info = { .name = TYPE_VMAPPLE_BDIF, .parent = TYPE_SYS_BUS_DEVICE, .instance_size = sizeof(VMAppleBdifState), .instance_init = bdif_init, .class_init = bdif_class_init, }; static void bdif_register_types(void) { type_register_static(&bdif_info); } type_init(bdif_register_types)