|  | /* | 
|  | * 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, 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) |