You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
qemu/hw/vmapple/bdif.c

275 lines
6.6 KiB
C

/*
* 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,
&sector, sizeof(sector),
MEMTXATTRS_UNSPECIFIED);
if (dma_result != MEMTX_OK) {
goto out;
}
le2cpu_sector(&sector);
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)