Files
QEMU-S5L8950X/hw/usb/s5l8950x-usb-otg.c
T
Yaya48 5d9a60a926 hw/arm: add authenticated A6 IMG3 boot lab
Model the A6 crypto, interrupt, USB, and platform blocks needed to boot SecureROM through iBSS into iBEC Recovery.

Add local lab identity, IMG3, and APTicket tooling, patched macOS recovery utilities, UART and GDB access, and English end-user documentation.
2026-09-01 09:51:49 -07:00

2378 lines
87 KiB
C

/*
* Apple S5L8950X Synopsys USB OTG core (minimal SecureROM-facing model)
*
* This starts with the reset handshake used by SecureROM. More endpoint and
* interrupt behavior can be added as the device-mode driver reaches it.
*/
#include "qemu/osdep.h"
#include "qemu/log.h"
#include "qemu/timer.h"
#include "hw/arm/s5l8950x.h"
#include "hw/core/cpu.h"
#include "hw/core/irq.h"
#include "hw/core/sysbus.h"
#include "hw/core/qdev-properties.h"
#include "hw/core/qdev-properties-system.h"
#include "chardev/char-fe.h"
#include "qapi/error.h"
#include "qom/object.h"
#include "exec/tb-flush.h"
#include "exec/translation-block.h"
#include "system/address-spaces.h"
#include "system/dma.h"
#include "target/arm/cpu.h"
#include "../../windows/include/qemu_a6_usb_protocol.h"
#define S5L8950X_USB_OTG_REGION_SIZE 0x10000
#define S5L8950X_USB_OTG_NUM_REGS \
(S5L8950X_USB_OTG_REGION_SIZE / sizeof(uint32_t))
#define USB_OTG_GRSTCTL 0x10
#define USB_OTG_GRSTCTL_CSFTRST BIT(0)
#define USB_OTG_GRSTCTL_RXFFLSH BIT(4)
#define USB_OTG_GRSTCTL_TXFFLSH BIT(5)
#define USB_OTG_GRSTCTL_AHBIDLE BIT(31)
#define USB_OTG_GAHBCFG 0x08
#define USB_OTG_GAHBCFG_GLBLINTRMSK BIT(0)
#define USB_OTG_GINTSTS 0x14
#define USB_OTG_GINTMSK 0x18
#define USB_OTG_GINT_USBRST BIT(12)
#define USB_OTG_GINT_ENUMDONE BIT(13)
#define USB_OTG_GINT_IEPINT BIT(18)
#define USB_OTG_GINT_OEPINT BIT(19)
#define USB_OTG_DCTL 0x804
#define USB_OTG_DCTL_SFTDISCON BIT(1)
#define USB_OTG_DIEPMSK 0x810
#define USB_OTG_DOEPMSK 0x814
#define USB_OTG_DAINT 0x818
#define USB_OTG_DAINTMSK 0x81c
#define USB_OTG_DIEPCTL0 0x900
#define USB_OTG_DIEPINT0 0x908
#define USB_OTG_DIEPTSIZ0 0x910
#define USB_OTG_DIEPDMA0 0x914
#define USB_OTG_DOEPCTL0 0xb00
#define USB_OTG_DOEPINT0 0xb08
#define USB_OTG_DOEPTSIZ0 0xb10
#define USB_OTG_DOEPDMA0 0xb14
#define USB_OTG_EP_STRIDE 0x20
#define USB_OTG_EP_COUNT 8
#define USB_OTG_EPCTL_EPENA BIT(31)
#define USB_OTG_EPCTL_STALL BIT(21)
#define USB_OTG_EPINT_SETUP BIT(3)
#define USB_OTG_EPINT_XFERCOMPL BIT(0)
#define USB_OTG_EPTSIZ_XFERSIZE_MASK 0x0007ffffu
#define USB_OTG_EPTSIZ_PKTCNT_MASK 0x1ff80000u
#define USB_DFU_TRANSFER_SIZE 0x800
#define USB_DFU_STATUS_SIZE 6
#define USB_DFU_STATE_IDLE 2
#define USB_DFU_STATE_DNLOAD_IDLE 5
#define USB_DFU_STATE_MANIFEST_SYNC 6
#define USB_DFU_STATE_MANIFEST 7
#define USB_DFU_STATE_WAIT_RESET 8
#define USB_DFU_STATE_ERROR 10
#define USB_DFU_MAX_STATUS_POLLS 20
#define A6_SRAM_BASE 0x10000000u
#define A6_SRAM_SIZE 0x00080000u
#define A6_DRAM_BASE 0x80000000u
#define A6_IBEC_BASE 0xbff00000u
#define A6_AIC_MASK_CLR 0x3f204180u
#define A6_USB_IRQ 11
#define A6_DFU_CAPTURE_MAX (16u * 1024u * 1024u)
#define IMG3_MAGIC 0x496d6733u
#define IMG3_TAG_TYPE 0x54595045u
#define IMG3_TAG_DATA 0x44415441u
#define IMG3_TYPE_IBSS 0x69627373u
#define IMG3_TYPE_IBEC 0x69626563u
#define IMG3_HEADER_SIZE 20u
#define IMG3_ELEMENT_HEADER_SIZE 12u
#define IMG3_SEARCH_SIZE 0x00010000u
OBJECT_DECLARE_SIMPLE_TYPE(S5L8950XUSBOTGState, S5L8950X_USB_OTG)
typedef enum S5L8950XUSBHostStage {
USB_HOST_WAIT_DEVICE_SETUP,
USB_HOST_WAIT_DEVICE_IN,
USB_HOST_WAIT_DEVICE_STATUS_OUT,
USB_HOST_WAIT_SET_ADDRESS_SETUP,
USB_HOST_WAIT_SET_ADDRESS_STATUS_IN,
USB_HOST_ADDRESS_ASSIGNED,
USB_HOST_WAIT_SERIAL_IN,
USB_HOST_WAIT_SERIAL_STATUS_OUT,
USB_HOST_WAIT_CONFIG_HEADER_SETUP,
USB_HOST_WAIT_CONFIG_HEADER_IN,
USB_HOST_WAIT_CONFIG_HEADER_STATUS_OUT,
USB_HOST_WAIT_CONFIG_FULL_SETUP,
USB_HOST_WAIT_CONFIG_FULL_IN,
USB_HOST_WAIT_CONFIG_FULL_STATUS_OUT,
USB_HOST_WAIT_SET_CONFIGURATION_SETUP,
USB_HOST_WAIT_SET_CONFIGURATION_STATUS_IN,
USB_HOST_DFU_READY,
USB_HOST_WAIT_DFU_GETSTATE_IN,
USB_HOST_WAIT_DFU_GETSTATE_STATUS_OUT,
USB_HOST_DFU_DNLOAD_READY,
USB_HOST_WAIT_DFU_DNLOAD_DATA_OUT,
USB_HOST_WAIT_DFU_DNLOAD_STATUS_IN,
USB_HOST_WAIT_DFU_GETSTATUS_SETUP,
USB_HOST_WAIT_DFU_GETSTATUS_IN,
USB_HOST_WAIT_DFU_GETSTATUS_STATUS_OUT,
USB_HOST_DFU_DONE,
USB_HOST_DFU_ERROR,
USB_HOST_BRIDGE_WAIT_DATA_IN,
USB_HOST_BRIDGE_WAIT_STATUS_OUT,
USB_HOST_BRIDGE_WAIT_DATA_OUT,
USB_HOST_BRIDGE_WAIT_STATUS_IN,
} S5L8950XUSBHostStage;
typedef enum S5L8950XDFUPacketKind {
USB_DFU_PACKET_PAYLOAD,
USB_DFU_PACKET_SUFFIX,
USB_DFU_PACKET_NOTIFY,
} S5L8950XDFUPacketKind;
struct S5L8950XUSBOTGState {
SysBusDevice parent_obj;
MemoryRegion iomem;
qemu_irq irq;
uint32_t regs[S5L8950X_USB_OTG_NUM_REGS];
bool usb_reset_sent;
bool enum_done_sent;
bool setup_sent;
bool descriptor_received;
uint8_t device_descriptor[18];
uint32_t device_descriptor_len;
uint8_t serial_descriptor[256];
uint32_t serial_descriptor_len;
uint8_t config_descriptor[256];
uint32_t config_descriptor_len;
uint32_t config_total_length;
uint32_t enumeration_generation;
bool current_device_is_dfu;
S5L8950XUSBHostStage host_stage;
char *dfu_image_path;
uint8_t *dfu_image;
size_t dfu_image_size;
size_t dfu_image_offset;
uint8_t dfu_packet[USB_DFU_TRANSFER_SIZE];
uint32_t dfu_packet_len;
uint32_t dfu_packet_offset;
uint16_t dfu_packet_block;
uint16_t dfu_next_block;
uint16_t dfu_notify_block;
S5L8950XDFUPacketKind dfu_packet_kind;
uint8_t dfu_suffix[16];
bool dfu_suffix_pending;
uint8_t dfu_status[USB_DFU_STATUS_SIZE];
uint8_t dfu_state;
uint8_t dfu_status_polls;
uint8_t dfu_finish_statuses;
bool dfu_injection_complete;
CharFrontend bridge_chr;
bool bridge_open;
bool bridge_request_active;
Qa6UsbFrameHeader bridge_request;
uint8_t bridge_rx[QA6_USB_MAX_FRAME_SIZE];
uint32_t bridge_rx_len;
uint8_t bridge_out_data[QA6_USB_MAX_PAYLOAD];
uint32_t bridge_out_offset;
uint8_t bridge_in_data[QA6_USB_MAX_PAYLOAD];
uint32_t bridge_in_len;
uint8_t bridge_dfu_status;
uint8_t bridge_dfu_state;
GByteArray *bridge_dfu_image;
uint16_t bridge_dfu_next_block;
bool bridge_dfu_capture_valid;
QEMUTimer *recovery_reset_timer;
QEMUTimer *bridge_bulk_timer;
bool recovery_reset_wait_for_halt;
bool force_debug_uarts;
bool native_img3_handoff;
};
static void s5l8950x_usb_otg_update_ep_irqs(S5L8950XUSBOTGState *s)
{
uint32_t daint = 0;
int ep;
for (ep = 0; ep < USB_OTG_EP_COUNT; ep++) {
uint32_t in_int = s->regs[(USB_OTG_DIEPINT0 +
ep * USB_OTG_EP_STRIDE) / 4];
uint32_t out_int = s->regs[(USB_OTG_DOEPINT0 +
ep * USB_OTG_EP_STRIDE) / 4];
if (in_int & s->regs[USB_OTG_DIEPMSK / 4]) {
daint |= BIT(ep);
}
if (out_int & s->regs[USB_OTG_DOEPMSK / 4]) {
daint |= BIT(ep + 16);
}
}
s->regs[USB_OTG_DAINT / 4] = daint;
s->regs[USB_OTG_GINTSTS / 4] &=
~(USB_OTG_GINT_IEPINT | USB_OTG_GINT_OEPINT);
if (daint & s->regs[USB_OTG_DAINTMSK / 4] & 0xffff) {
s->regs[USB_OTG_GINTSTS / 4] |= USB_OTG_GINT_IEPINT;
}
if (daint & s->regs[USB_OTG_DAINTMSK / 4] & 0xffff0000) {
s->regs[USB_OTG_GINTSTS / 4] |= USB_OTG_GINT_OEPINT;
}
}
static void s5l8950x_usb_otg_update_irq(S5L8950XUSBOTGState *s)
{
s5l8950x_usb_otg_update_ep_irqs(s);
bool level = (s->regs[USB_OTG_GAHBCFG / 4] &
USB_OTG_GAHBCFG_GLBLINTRMSK) &&
(s->regs[USB_OTG_GINTSTS / 4] &
s->regs[USB_OTG_GINTMSK / 4]);
qemu_set_irq(s->irq, level);
}
static void s5l8950x_usb_otg_recovery_reset(void *opaque)
{
S5L8950XUSBOTGState *s = opaque;
CPUState *cs = first_cpu;
int ep;
if (s->host_stage != USB_HOST_WAIT_DEVICE_SETUP ||
s->device_descriptor_len) {
return;
}
/*
* A real host cannot reset a device until its reconnect has reached the
* bus. Waiting for the bootloader's WFE idle point models that ordering
* and, importantly, prevents the reset IRQ from preempting iBSS before it
* has installed the USB handler. SecureROM's first enumeration does not
* use this delayed path.
*/
if (s->recovery_reset_wait_for_halt && cs && !cs->halted) {
timer_mod(s->recovery_reset_timer,
qemu_clock_get_ms(QEMU_CLOCK_VIRTUAL) + 1);
return;
}
/* A USB port reset disables active endpoints but preserves core setup. */
for (ep = 0; ep < USB_OTG_EP_COUNT; ep++) {
s->regs[(USB_OTG_DIEPCTL0 + ep * USB_OTG_EP_STRIDE) / 4] &=
~USB_OTG_EPCTL_EPENA;
s->regs[(USB_OTG_DOEPCTL0 + ep * USB_OTG_EP_STRIDE) / 4] &=
~USB_OTG_EPCTL_EPENA;
s->regs[(USB_OTG_DIEPINT0 + ep * USB_OTG_EP_STRIDE) / 4] = 0;
s->regs[(USB_OTG_DOEPINT0 + ep * USB_OTG_EP_STRIDE) / 4] = 0;
}
s->regs[USB_OTG_DAINT / 4] = 0;
/* The last iBSS EP0 completion can leave AIC's auto-mask asserted. */
address_space_stl(&address_space_memory, A6_AIC_MASK_CLR,
BIT(A6_USB_IRQ), MEMTXATTRS_UNSPECIFIED, NULL);
s->regs[USB_OTG_GINTSTS / 4] |= USB_OTG_GINT_USBRST;
s->usb_reset_sent = true;
s->recovery_reset_wait_for_halt = false;
s5l8950x_usb_otg_update_irq(s);
qemu_log_mask(LOG_UNIMP,
"s5l8950x.usb-host: delayed bootloader port reset "
"delivered\n");
}
static bool s5l8950x_usb_otg_is_ep_int(hwaddr offset, hwaddr base)
{
return offset >= base &&
offset < base + USB_OTG_EP_COUNT * USB_OTG_EP_STRIDE &&
(offset - base) % USB_OTG_EP_STRIDE == 0;
}
static void s5l8950x_usb_otg_send_device_descriptor_setup(
S5L8950XUSBOTGState *s)
{
static const uint8_t setup[] = {
0x80, 0x06, 0x00, 0x01, 0x00, 0x00, 0x12, 0x00,
};
hwaddr dma = s->regs[USB_OTG_DOEPDMA0 / 4];
if (dma_memory_write(&address_space_memory, dma, setup, sizeof(setup),
MEMTXATTRS_UNSPECIFIED) != MEMTX_OK) {
qemu_log_mask(LOG_GUEST_ERROR,
"s5l8950x.usb: EP0 SETUP DMA write failed at 0x%08"
HWADDR_PRIx "\n", dma);
return;
}
s->regs[USB_OTG_DOEPINT0 / 4] |= USB_OTG_EPINT_SETUP;
s->regs[USB_OTG_DOEPCTL0 / 4] &= ~USB_OTG_EPCTL_EPENA;
s->setup_sent = true;
s->host_stage = USB_HOST_WAIT_DEVICE_IN;
qemu_log_mask(LOG_UNIMP,
"s5l8950x.usb-host: GET_DESCRIPTOR(Device) -> EP0 DMA "
"0x%08" HWADDR_PRIx "\n", dma);
s5l8950x_usb_otg_update_irq(s);
}
static void s5l8950x_usb_otg_send_set_address_setup(
S5L8950XUSBOTGState *s)
{
static const uint8_t setup[] = {
0x00, 0x05, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00,
};
hwaddr dma = s->regs[USB_OTG_DOEPDMA0 / 4];
if (dma_memory_write(&address_space_memory, dma, setup, sizeof(setup),
MEMTXATTRS_UNSPECIFIED) != MEMTX_OK) {
qemu_log_mask(LOG_GUEST_ERROR,
"s5l8950x.usb: SET_ADDRESS DMA write failed at 0x%08"
HWADDR_PRIx "\n", dma);
return;
}
s->regs[USB_OTG_DOEPINT0 / 4] |= USB_OTG_EPINT_SETUP;
s->regs[USB_OTG_DOEPCTL0 / 4] &= ~USB_OTG_EPCTL_EPENA;
s->host_stage = USB_HOST_WAIT_SET_ADDRESS_STATUS_IN;
qemu_log_mask(LOG_UNIMP,
"s5l8950x.usb-host: SET_ADDRESS(1) -> EP0 DMA 0x%08"
HWADDR_PRIx "\n", dma);
s5l8950x_usb_otg_update_irq(s);
}
static bool s5l8950x_usb_otg_send_setup(S5L8950XUSBOTGState *s,
const uint8_t setup[8],
const char *name,
S5L8950XUSBHostStage next_stage)
{
hwaddr dma = s->regs[USB_OTG_DOEPDMA0 / 4];
if (dma_memory_write(&address_space_memory, dma, setup, 8,
MEMTXATTRS_UNSPECIFIED) != MEMTX_OK) {
qemu_log_mask(LOG_GUEST_ERROR,
"s5l8950x.usb: %s DMA write failed at 0x%08"
HWADDR_PRIx "\n", name, dma);
return false;
}
s->regs[USB_OTG_DOEPINT0 / 4] |= USB_OTG_EPINT_SETUP;
s->regs[USB_OTG_DOEPCTL0 / 4] &= ~USB_OTG_EPCTL_EPENA;
s->host_stage = next_stage;
qemu_log_mask(LOG_UNIMP,
"s5l8950x.usb-host: %s -> EP0 DMA 0x%08"
HWADDR_PRIx "\n", name, dma);
s5l8950x_usb_otg_update_irq(s);
return true;
}
static void s5l8950x_usb_bridge_write_response(
S5L8950XUSBOTGState *s, const Qa6UsbFrameHeader *request,
int status, uint32_t transfer_length, const uint8_t *payload,
uint32_t payload_length)
{
Qa6UsbFrameHeader response = { 0 };
g_autofree uint8_t *frame = NULL;
uint32_t frame_length;
int written;
if (payload_length > QA6_USB_MAX_PAYLOAD) {
status = QA6_USB_STATUS_PROTOCOL;
payload_length = 0;
transfer_length = 0;
}
response.magic = QA6_USB_MAGIC;
response.version = QA6_USB_PROTOCOL_VERSION;
switch (request->type) {
case QA6_USB_MESSAGE_INFO_REQUEST:
response.type = QA6_USB_MESSAGE_INFO_RESPONSE;
break;
case QA6_USB_MESSAGE_BULK_REQUEST:
response.type = QA6_USB_MESSAGE_BULK_RESPONSE;
break;
case QA6_USB_MESSAGE_RESET_REQUEST:
response.type = QA6_USB_MESSAGE_RESET_RESPONSE;
break;
default:
response.type = QA6_USB_MESSAGE_RESPONSE;
break;
}
response.request_id = request->request_id;
response.status = status;
response.payload_length = payload_length;
response.transfer_length = transfer_length;
memcpy(response.setup, request->setup, sizeof(response.setup));
frame_length = QA6_USB_HEADER_SIZE + payload_length;
frame = g_malloc(frame_length);
memcpy(frame, &response, QA6_USB_HEADER_SIZE);
if (payload_length) {
memcpy(frame + QA6_USB_HEADER_SIZE, payload, payload_length);
}
written = qemu_chr_fe_write_all(&s->bridge_chr, frame, frame_length);
if (written != frame_length) {
qemu_log_mask(LOG_GUEST_ERROR,
"s5l8950x.usb-bridge: response write failed "
"(%d/%u bytes)\n", written, frame_length);
}
}
static void s5l8950x_usb_bridge_write_info_response(
S5L8950XUSBOTGState *s, const Qa6UsbFrameHeader *request)
{
Qa6UsbDeviceInfo info = { 0 };
uint32_t input_offset;
uint32_t output_offset = 0;
if (!s->enumeration_generation || s->device_descriptor_len < 12 ||
s->serial_descriptor_len < 2) {
s5l8950x_usb_bridge_write_response(
s, request, QA6_USB_STATUS_DISCONNECTED, 0, NULL, 0);
return;
}
info.generation = s->enumeration_generation;
info.vendor_id = s->device_descriptor[8] |
(s->device_descriptor[9] << 8);
info.product_id = s->device_descriptor[10] |
(s->device_descriptor[11] << 8);
info.device_class = s->device_descriptor[4];
info.device_subclass = s->device_descriptor[5];
info.device_protocol = s->device_descriptor[6];
info.is_dfu = s->current_device_is_dfu;
/* libirecovery recognizes Apple's public DFU PID. */
if (info.is_dfu && info.vendor_id == 0x05ac) {
info.product_id = 0x1227;
}
for (input_offset = 2;
input_offset + 1 < s->serial_descriptor_len &&
output_offset + 1 < sizeof(info.serial);
input_offset += 2) {
info.serial[output_offset++] =
s->serial_descriptor[input_offset + 1] ? '?' :
s->serial_descriptor[input_offset];
}
s5l8950x_usb_bridge_write_response(
s, request, QA6_USB_STATUS_SUCCESS, sizeof(info),
(const uint8_t *)&info, sizeof(info));
}
static void s5l8950x_usb_bridge_finish(S5L8950XUSBOTGState *s, int status)
{
bool direction_in = (s->bridge_request.setup[0] & 0x80) != 0;
uint32_t transfer_length = direction_in ? s->bridge_in_len :
s->bridge_out_offset;
timer_del(s->bridge_bulk_timer);
if (status == QA6_USB_STATUS_SUCCESS && !direction_in &&
s->bridge_request.setup[0] == 0x21 &&
s->bridge_request.setup[1] == 1 && transfer_length) {
uint16_t block = s->bridge_request.setup[2] |
(s->bridge_request.setup[3] << 8);
if (block == 0) {
g_byte_array_set_size(s->bridge_dfu_image, 0);
s->bridge_dfu_next_block = 0;
s->bridge_dfu_capture_valid = true;
}
if (!s->bridge_dfu_capture_valid ||
block != s->bridge_dfu_next_block ||
s->bridge_dfu_image->len >
A6_DFU_CAPTURE_MAX - transfer_length) {
s->bridge_dfu_capture_valid = false;
qemu_log_mask(LOG_GUEST_ERROR,
"s5l8950x.usb-bridge: invalid DFU block sequence "
"(%u, expected %u)\n",
block, s->bridge_dfu_next_block);
} else {
g_byte_array_append(s->bridge_dfu_image, s->bridge_out_data,
transfer_length);
s->bridge_dfu_next_block++;
}
}
s5l8950x_usb_bridge_write_response(
s, &s->bridge_request, status, transfer_length,
direction_in ? s->bridge_in_data : NULL,
direction_in && status == QA6_USB_STATUS_SUCCESS ?
s->bridge_in_len : 0);
if (status == QA6_USB_STATUS_SUCCESS && direction_in &&
s->bridge_request.setup[0] == 0xa1 &&
s->bridge_request.setup[1] == 3 && s->bridge_in_len == 6) {
s->bridge_dfu_status = s->bridge_in_data[0];
s->bridge_dfu_state = s->bridge_in_data[4];
qemu_log_mask(LOG_UNIMP,
"s5l8950x.usb-bridge: DFU GETSTATUS status=%u "
"state=%u poll=%u ms\n",
s->bridge_in_data[0], s->bridge_in_data[4],
s->bridge_in_data[1] |
(s->bridge_in_data[2] << 8) |
(s->bridge_in_data[3] << 16));
}
qemu_log_mask(LOG_UNIMP,
"s5l8950x.usb-bridge: %s request %u complete "
"(status=%d, bytes=%u)\n",
s->bridge_request.type == QA6_USB_MESSAGE_BULK_REQUEST ?
"bulk" : "EP0", s->bridge_request.request_id, status,
transfer_length);
s->bridge_request_active = false;
s->bridge_out_offset = 0;
s->bridge_in_len = 0;
s->host_stage = USB_HOST_DFU_READY;
}
static void s5l8950x_usb_bridge_bulk_timeout(void *opaque)
{
S5L8950XUSBOTGState *s = opaque;
if (!s->bridge_request_active ||
s->bridge_request.type != QA6_USB_MESSAGE_BULK_REQUEST ||
!(s->bridge_request.setup[0] & 0x80)) {
return;
}
qemu_log_mask(LOG_UNIMP,
"s5l8950x.usb-bridge: bulk IN timeout, returning %u "
"bytes\n", s->bridge_in_len);
s5l8950x_usb_bridge_finish(s, QA6_USB_STATUS_SUCCESS);
}
static void s5l8950x_usb_bridge_try_bulk(S5L8950XUSBOTGState *s)
{
bool direction_in;
unsigned ep;
hwaddr ctl_offset;
hwaddr int_offset;
hwaddr size_offset;
hwaddr dma_offset;
hwaddr dma;
uint32_t capacity;
uint32_t remaining;
uint32_t length;
if (!s->bridge_request_active ||
s->bridge_request.type != QA6_USB_MESSAGE_BULK_REQUEST) {
return;
}
direction_in = (s->bridge_request.setup[0] & 0x80) != 0;
ep = s->bridge_request.setup[0] & 0x7f;
if (!ep || ep >= USB_OTG_EP_COUNT) {
s5l8950x_usb_bridge_finish(s, QA6_USB_STATUS_PROTOCOL);
return;
}
if (direction_in) {
ctl_offset = USB_OTG_DIEPCTL0 + ep * USB_OTG_EP_STRIDE;
int_offset = USB_OTG_DIEPINT0 + ep * USB_OTG_EP_STRIDE;
size_offset = USB_OTG_DIEPTSIZ0 + ep * USB_OTG_EP_STRIDE;
dma_offset = USB_OTG_DIEPDMA0 + ep * USB_OTG_EP_STRIDE;
} else {
ctl_offset = USB_OTG_DOEPCTL0 + ep * USB_OTG_EP_STRIDE;
int_offset = USB_OTG_DOEPINT0 + ep * USB_OTG_EP_STRIDE;
size_offset = USB_OTG_DOEPTSIZ0 + ep * USB_OTG_EP_STRIDE;
dma_offset = USB_OTG_DOEPDMA0 + ep * USB_OTG_EP_STRIDE;
}
if (!(s->regs[ctl_offset / 4] & USB_OTG_EPCTL_EPENA)) {
return;
}
capacity = s->regs[size_offset / 4] &
USB_OTG_EPTSIZ_XFERSIZE_MASK;
dma = s->regs[dma_offset / 4];
if (direction_in) {
remaining = s->bridge_request.transfer_length - s->bridge_in_len;
length = MIN(capacity, remaining);
if (length &&
dma_memory_read(&address_space_memory, dma,
s->bridge_in_data + s->bridge_in_len, length,
MEMTXATTRS_UNSPECIFIED) != MEMTX_OK) {
qemu_log_mask(LOG_GUEST_ERROR,
"s5l8950x.usb-bridge: bulk IN%u DMA read failed "
"at 0x%08" HWADDR_PRIx "\n", ep, dma);
s5l8950x_usb_bridge_finish(
s, QA6_USB_STATUS_DISCONNECTED);
return;
}
s->bridge_in_len += length;
} else {
remaining = s->bridge_request.payload_length -
s->bridge_out_offset;
length = MIN(capacity, remaining);
if (length &&
dma_memory_write(&address_space_memory, dma,
s->bridge_out_data + s->bridge_out_offset,
length,
MEMTXATTRS_UNSPECIFIED) != MEMTX_OK) {
qemu_log_mask(LOG_GUEST_ERROR,
"s5l8950x.usb-bridge: bulk OUT%u DMA write failed "
"at 0x%08" HWADDR_PRIx "\n", ep, dma);
s5l8950x_usb_bridge_finish(
s, QA6_USB_STATUS_DISCONNECTED);
return;
}
s->bridge_out_offset += length;
}
s->regs[dma_offset / 4] = dma + length;
s->regs[size_offset / 4] &=
~(USB_OTG_EPTSIZ_XFERSIZE_MASK | USB_OTG_EPTSIZ_PKTCNT_MASK);
if (!direction_in && capacity > length) {
s->regs[size_offset / 4] |= capacity - length;
}
s->regs[ctl_offset / 4] &= ~USB_OTG_EPCTL_EPENA;
s->regs[int_offset / 4] |= USB_OTG_EPINT_XFERCOMPL;
qemu_log_mask(LOG_UNIMP,
"s5l8950x.usb-bridge: bulk %s EP%u DMA=0x%08"
HWADDR_PRIx " bytes=%u\n",
direction_in ? "IN" : "OUT", ep, dma, length);
if (direction_in ||
s->bridge_out_offset >= s->bridge_request.payload_length) {
s5l8950x_usb_bridge_finish(s, QA6_USB_STATUS_SUCCESS);
}
s5l8950x_usb_otg_update_irq(s);
}
static void s5l8950x_usb_otg_rearm(S5L8950XUSBOTGState *s, bool cold);
typedef struct S5L8950XBootloaderHandoff {
S5L8950XUSBOTGState *usb;
const uint8_t *payload;
uint32_t data_length;
uint32_t image_type;
hwaddr load_base;
bool success;
} S5L8950XBootloaderHandoff;
/*
* RELEASE iBSS does not read NVRAM, and iBEC only reads debug-uarts after its
* early platform initialization. On research devices the equivalent early
* setting is 3, which enables both UART directions. Locate iBoot's small
* debug_enable_uarts() helper by its stable Thumb body and replace only the
* ORRS with MOVS r0, #3. The authenticated IMG3 remains untouched until the
* real bootloader has accepted it; this is an optional emulator-side debug
* aid applied to the copied executable image immediately before handoff.
*/
static bool s5l8950x_usb_enable_bootloader_uarts(
const S5L8950XBootloaderHandoff *handoff)
{
const uint8_t *image = handoff->payload;
static const uint16_t helper_tail[] = {
0x680a, /* ldr r2, [r1] */
0x4310, /* orrs r0, r2 */
0x6008, /* str r0, [r1] */
0x4770, /* bx lr */
};
static const uint8_t movs_r0_3[] = { 0x03, 0x20 };
uint32_t offset;
for (offset = 0; offset + 10 <= handoff->data_length; offset += 2) {
uint16_t ldr_literal = lduw_le_p(image + offset);
unsigned int i;
/* Thumb LDR (literal), with Rt == r1. */
if ((ldr_literal & 0xff00) != 0x4900) {
continue;
}
for (i = 0; i < ARRAY_SIZE(helper_tail); i++) {
if (lduw_le_p(image + offset + 2 + i * 2) != helper_tail[i]) {
break;
}
}
if (i != ARRAY_SIZE(helper_tail)) {
continue;
}
if (address_space_write_rom(&address_space_memory,
handoff->load_base + offset + 4,
MEMTXATTRS_UNSPECIFIED, movs_r0_3,
sizeof(movs_r0_3)) != MEMTX_OK) {
return false;
}
qemu_log_mask(LOG_UNIMP,
"s5l8950x.uart: forced debug-uarts=3 for %s at "
"0x%08" HWADDR_PRIx "\n",
handoff->image_type == IMG3_TYPE_IBSS ? "iBSS" :
"iBEC",
handoff->load_base + offset + 4);
return true;
}
return false;
}
static void s5l8950x_usb_bridge_handoff_on_cpu(CPUState *cpu,
run_on_cpu_data data)
{
S5L8950XBootloaderHandoff *handoff = data.host_ptr;
S5L8950XUSBOTGState *s = handoff->usb;
if (address_space_write_rom(&address_space_memory, handoff->load_base,
MEMTXATTRS_UNSPECIFIED, handoff->payload,
handoff->data_length) != MEMTX_OK) {
return;
}
if (s->force_debug_uarts &&
!s5l8950x_usb_enable_bootloader_uarts(handoff)) {
qemu_log_mask(LOG_GUEST_ERROR,
"s5l8950x.uart: debug_enable_uarts helper not found "
"in authenticated bootloader\n");
}
/*
* iBSS and iBEC occupy the same SRAM window. This callback runs at a
* vCPU safe point, so no old translated block can execute concurrently
* with the replacement and reset below.
*/
{
RCU_READ_LOCK_GUARD();
hwaddr xlat;
hwaddr length = handoff->data_length;
MemoryRegion *mr = address_space_translate(
&address_space_memory, handoff->load_base, &xlat, &length, false,
MEMTXATTRS_UNSPECIFIED);
ram_addr_t ram_addr = memory_region_get_ram_addr(mr);
if (ram_addr != RAM_ADDR_INVALID) {
tb_invalidate_phys_range(cpu, ram_addr + xlat,
ram_addr + xlat + length - 1);
tcg_flush_jmp_cache(cpu);
qemu_log_mask(LOG_UNIMP,
"s5l8950x.usb-bridge: invalidated code RAM="
RAM_ADDR_FMT " offset=0x%" HWADDR_PRIx
" length=%" HWADDR_PRIu "\n",
ram_addr, xlat, length);
}
}
if (handoff->image_type == IMG3_TYPE_IBSS) {
s5l8950x_usb_otg_rearm(s, false);
s->recovery_reset_wait_for_halt = true;
timer_mod(s->recovery_reset_timer,
qemu_clock_get_ms(QEMU_CLOCK_VIRTUAL) + 100);
qemu_log_mask(LOG_UNIMP,
"s5l8950x.usb-host: SecureROM disconnected; "
"waiting for iBSS DFU reinitialization\n");
} else {
/*
* Invalidate iBSS's DFU identity immediately, but preserve the DWC2
* register image inherited by iBEC. iBEC relocates before resetting
* the core, so its delayed host reset must not race that relocation.
*/
s->usb_reset_sent = false;
s->enum_done_sent = false;
s->setup_sent = false;
s->descriptor_received = false;
s->device_descriptor_len = 0;
s->serial_descriptor_len = 0;
s->config_descriptor_len = 0;
s->config_total_length = 0;
s->current_device_is_dfu = false;
s->host_stage = USB_HOST_WAIT_DEVICE_SETUP;
s->bridge_dfu_status = 0;
s->bridge_dfu_state = 0;
s->recovery_reset_wait_for_halt = false;
memset(s->device_descriptor, 0, sizeof(s->device_descriptor));
memset(s->serial_descriptor, 0, sizeof(s->serial_descriptor));
memset(s->config_descriptor, 0, sizeof(s->config_descriptor));
/*
* iBEC resets and reconnects the DWC core after relocation. Do not
* inject a port reset merely because control was transferred: the
* DCTL soft-reconnect path below is the point at which a real host
* can observe the device and arm the delayed reset.
*/
timer_del(s->recovery_reset_timer);
qemu_log_mask(LOG_UNIMP,
"s5l8950x.usb-host: iBSS disconnected; waiting for "
"iBEC Recovery reinitialization\n");
}
cpu_reset(cpu);
ARM_CPU(cpu)->env.regs[0] = 1;
cpu->halted = 0;
cpu_set_pc(cpu, handoff->load_base);
cpu_exit(cpu);
handoff->success = true;
}
static bool s5l8950x_usb_bridge_handoff_captured(
S5L8950XUSBOTGState *s)
{
CPUState *cpu = first_cpu;
const uint8_t *image = s->bridge_dfu_image->data;
size_t image_length = s->bridge_dfu_image->len;
size_t img3_offset;
uint32_t full_size = 0;
uint32_t offset;
uint32_t data_offset = 0;
uint32_t data_length = 0;
uint32_t image_type = 0;
hwaddr load_base;
g_autofree uint8_t *payload = NULL;
S5L8950XBootloaderHandoff handoff = { 0 };
if (!cpu || !s->bridge_dfu_capture_valid) {
return false;
}
/* A personalized bootloader is the TSS ticket followed by its IMG3. */
for (img3_offset = 0;
img3_offset + IMG3_HEADER_SIZE <= image_length;
img3_offset += 4) {
if (ldl_le_p(image + img3_offset) == IMG3_MAGIC) {
uint32_t candidate_size = ldl_le_p(image + img3_offset + 4);
if (candidate_size >= IMG3_HEADER_SIZE &&
candidate_size <= image_length - img3_offset) {
full_size = candidate_size;
break;
}
}
}
if (!full_size) {
return false;
}
offset = IMG3_HEADER_SIZE;
while (offset <= full_size - IMG3_ELEMENT_HEADER_SIZE) {
const uint8_t *element = image + img3_offset + offset;
uint32_t tag = ldl_le_p(element);
uint32_t total_length = ldl_le_p(element + 4);
uint32_t element_data_length = ldl_le_p(element + 8);
if (total_length < IMG3_ELEMENT_HEADER_SIZE ||
total_length > full_size - offset ||
element_data_length > total_length - IMG3_ELEMENT_HEADER_SIZE) {
return false;
}
if (tag == IMG3_TAG_TYPE && element_data_length >= 4) {
image_type = ldl_le_p(element + IMG3_ELEMENT_HEADER_SIZE);
} else if (tag == IMG3_TAG_DATA) {
data_offset = offset + IMG3_ELEMENT_HEADER_SIZE;
data_length = element_data_length;
}
offset += total_length;
}
if ((image_type != IMG3_TYPE_IBSS && image_type != IMG3_TYPE_IBEC) ||
!data_offset || !data_length ||
data_offset > full_size || data_length > full_size - data_offset ||
data_length > A6_SRAM_SIZE) {
return false;
}
load_base = image_type == IMG3_TYPE_IBSS ? A6_SRAM_BASE : A6_IBEC_BASE;
payload = g_memdup2(image + img3_offset + data_offset, data_length);
handoff.usb = s;
handoff.payload = payload;
handoff.data_length = data_length;
handoff.image_type = image_type;
handoff.load_base = load_base;
run_on_cpu(cpu, s5l8950x_usb_bridge_handoff_on_cpu,
RUN_ON_CPU_HOST_PTR(&handoff));
if (!handoff.success) {
return false;
}
qemu_log_mask(LOG_UNIMP,
"s5l8950x.usb-bridge: authenticated %s handoff; "
"DATA=%u bytes DFU_IMG3=0x%zx PC=0x%08" HWADDR_PRIx
"\n",
image_type == IMG3_TYPE_IBSS ? "iBSS" : "iBEC",
data_length, img3_offset, load_base);
s->bridge_dfu_capture_valid = false;
g_byte_array_set_size(s->bridge_dfu_image, 0);
return true;
}
/*
* The real host-side reset completes SecureROM's verified IMG3 handoff.
* Our minimal DWC2 model has no physical port-reset object, so its USBRST
* interrupt takes the generic re-enumeration path instead. Preserve all ROM
* parsing and signature checks, then reproduce only the successful final
* hardware handoff once the ROM itself reports DFU-MANIFEST-WAIT-RESET.
*/
static bool s5l8950x_usb_bridge_handoff_bootloader(
S5L8950XUSBOTGState *s)
{
CPUState *cpu = first_cpu;
uint8_t img3_header[IMG3_HEADER_SIZE];
uint8_t element_header[IMG3_ELEMENT_HEADER_SIZE];
g_autofree uint8_t *payload = NULL;
uint32_t full_size;
uint32_t offset = IMG3_HEADER_SIZE;
uint32_t data_offset = 0;
uint32_t data_length = 0;
uint32_t image_type = 0;
hwaddr image_base = 0;
hwaddr load_base = 0;
S5L8950XBootloaderHandoff handoff = { 0 };
static const hwaddr load_bases[] = { A6_SRAM_BASE, A6_DRAM_BASE };
unsigned int region;
if (!cpu) {
return false;
}
for (region = 0; region < ARRAY_SIZE(load_bases); region++) {
uint32_t search_offset;
for (search_offset = 0;
search_offset <= IMG3_SEARCH_SIZE - IMG3_HEADER_SIZE;
search_offset += 4) {
hwaddr candidate = load_bases[region] + search_offset;
if (dma_memory_read(&address_space_memory, candidate, img3_header,
sizeof(img3_header),
MEMTXATTRS_UNSPECIFIED) == MEMTX_OK &&
ldl_le_p(img3_header) == IMG3_MAGIC) {
uint32_t candidate_size = ldl_le_p(img3_header + 4);
if (candidate_size >= IMG3_HEADER_SIZE &&
candidate_size <= A6_SRAM_SIZE - search_offset) {
image_base = candidate;
load_base = load_bases[region];
break;
}
}
}
if (image_base) {
break;
}
}
if (!image_base) {
return false;
}
full_size = ldl_le_p(img3_header + 4);
if (full_size < IMG3_HEADER_SIZE || full_size > A6_SRAM_SIZE) {
return false;
}
while (offset <= full_size - IMG3_ELEMENT_HEADER_SIZE) {
uint32_t tag;
uint32_t total_length;
uint32_t element_data_length;
if (dma_memory_read(&address_space_memory, image_base + offset,
element_header, sizeof(element_header),
MEMTXATTRS_UNSPECIFIED) != MEMTX_OK) {
return false;
}
tag = ldl_le_p(element_header);
total_length = ldl_le_p(element_header + 4);
element_data_length = ldl_le_p(element_header + 8);
if (total_length < IMG3_ELEMENT_HEADER_SIZE ||
total_length > full_size - offset ||
element_data_length > total_length - IMG3_ELEMENT_HEADER_SIZE) {
return false;
}
if (tag == IMG3_TAG_TYPE && element_data_length >= 4) {
uint8_t type_data[4];
if (dma_memory_read(&address_space_memory,
image_base + offset +
IMG3_ELEMENT_HEADER_SIZE,
type_data, sizeof(type_data),
MEMTXATTRS_UNSPECIFIED) != MEMTX_OK) {
return false;
}
image_type = ldl_le_p(type_data);
} else if (tag == IMG3_TAG_DATA) {
data_offset = offset + IMG3_ELEMENT_HEADER_SIZE;
data_length = element_data_length;
}
offset += total_length;
}
if ((image_type != IMG3_TYPE_IBSS && image_type != IMG3_TYPE_IBEC) ||
!data_offset || !data_length ||
data_offset > full_size || data_length > full_size - data_offset ||
data_length > A6_SRAM_SIZE) {
return false;
}
payload = g_malloc(data_length);
if (dma_memory_read(&address_space_memory, image_base + data_offset,
payload, data_length,
MEMTXATTRS_UNSPECIFIED) != MEMTX_OK) {
return false;
}
handoff.usb = s;
handoff.payload = payload;
handoff.data_length = data_length;
handoff.image_type = image_type;
handoff.load_base = load_base;
run_on_cpu(cpu, s5l8950x_usb_bridge_handoff_on_cpu,
RUN_ON_CPU_HOST_PTR(&handoff));
if (!handoff.success) {
return false;
}
qemu_log_mask(LOG_UNIMP,
"s5l8950x.usb-bridge: authenticated %s handoff; "
"DATA=%u bytes IMG3=0x%08" HWADDR_PRIx
" PC=0x%08" HWADDR_PRIx "\n",
image_type == IMG3_TYPE_IBSS ? "iBSS" : "iBEC",
data_length, image_base, load_base);
return true;
}
static void s5l8950x_usb_bridge_start_request(S5L8950XUSBOTGState *s)
{
bool direction_in;
uint16_t length;
S5L8950XUSBHostStage next_stage;
if (!s->bridge_request_active || s->host_stage != USB_HOST_DFU_READY) {
return;
}
direction_in = (s->bridge_request.setup[0] & 0x80) != 0;
length = s->bridge_request.setup[6] |
(s->bridge_request.setup[7] << 8);
if (!length) {
next_stage = USB_HOST_BRIDGE_WAIT_STATUS_IN;
} else if (direction_in) {
next_stage = USB_HOST_BRIDGE_WAIT_DATA_IN;
} else {
next_stage = USB_HOST_BRIDGE_WAIT_DATA_OUT;
}
/*
* USB control endpoints recover from a halt when the host delivers a new
* SETUP packet. Without this, one unsupported iBoot command leaves the
* emulated EP0 STALL bit set and poisons every later irecovery request.
*/
s->regs[USB_OTG_DIEPCTL0 / 4] &= ~USB_OTG_EPCTL_STALL;
s->regs[USB_OTG_DOEPCTL0 / 4] &= ~USB_OTG_EPCTL_STALL;
s5l8950x_usb_otg_send_setup(s, s->bridge_request.setup,
"external EP0 request", next_stage);
}
static void s5l8950x_usb_bridge_complete_data_out(
S5L8950XUSBOTGState *s)
{
hwaddr dma = s->regs[USB_OTG_DOEPDMA0 / 4];
uint32_t remaining = s->bridge_request.payload_length -
s->bridge_out_offset;
uint32_t capacity = s->regs[USB_OTG_DOEPTSIZ0 / 4] & 0x7f;
uint32_t length;
if (!capacity) {
capacity = 64;
}
length = MIN(remaining, capacity);
if (length &&
dma_memory_write(&address_space_memory, dma,
s->bridge_out_data + s->bridge_out_offset, length,
MEMTXATTRS_UNSPECIFIED) != MEMTX_OK) {
qemu_log_mask(LOG_GUEST_ERROR,
"s5l8950x.usb-bridge: OUT DMA write failed at "
"0x%08" HWADDR_PRIx "\n", dma);
s5l8950x_usb_bridge_finish(s, QA6_USB_STATUS_DISCONNECTED);
return;
}
s->bridge_out_offset += length;
/* DWC2 leaves the unreceived byte count in XFRSIZ on a short packet. */
s->regs[USB_OTG_DOEPTSIZ0 / 4] =
(s->regs[USB_OTG_DOEPTSIZ0 / 4] & ~0x7f) |
(capacity - length);
s->regs[USB_OTG_DOEPCTL0 / 4] &= ~USB_OTG_EPCTL_EPENA;
s->regs[USB_OTG_DOEPINT0 / 4] |= USB_OTG_EPINT_XFERCOMPL;
if (s->bridge_out_offset >= s->bridge_request.payload_length) {
s->host_stage = USB_HOST_BRIDGE_WAIT_STATUS_IN;
}
s5l8950x_usb_otg_update_irq(s);
}
static void s5l8950x_usb_bridge_complete_data_in(
S5L8950XUSBOTGState *s)
{
hwaddr dma = s->regs[USB_OTG_DIEPDMA0 / 4];
uint32_t requested = s->bridge_request.transfer_length;
uint32_t remaining = requested - s->bridge_in_len;
uint32_t length = s->regs[USB_OTG_DIEPTSIZ0 / 4] & 0x7f;
length = MIN(length, remaining);
if (length &&
dma_memory_read(&address_space_memory, dma,
s->bridge_in_data + s->bridge_in_len, length,
MEMTXATTRS_UNSPECIFIED) != MEMTX_OK) {
qemu_log_mask(LOG_GUEST_ERROR,
"s5l8950x.usb-bridge: IN DMA read failed at "
"0x%08" HWADDR_PRIx "\n", dma);
s5l8950x_usb_bridge_finish(s, QA6_USB_STATUS_DISCONNECTED);
return;
}
s->bridge_in_len += length;
s->regs[USB_OTG_DIEPTSIZ0 / 4] &= ~0x7f;
s->regs[USB_OTG_DIEPCTL0 / 4] &= ~USB_OTG_EPCTL_EPENA;
s->regs[USB_OTG_DIEPINT0 / 4] |= USB_OTG_EPINT_XFERCOMPL;
if (length < 64 || s->bridge_in_len >= requested) {
s->host_stage = USB_HOST_BRIDGE_WAIT_STATUS_OUT;
}
s5l8950x_usb_otg_update_irq(s);
}
static void s5l8950x_usb_bridge_complete_status_out(
S5L8950XUSBOTGState *s)
{
s->regs[USB_OTG_DOEPINT0 / 4] |= USB_OTG_EPINT_XFERCOMPL;
s->regs[USB_OTG_DOEPCTL0 / 4] &= ~USB_OTG_EPCTL_EPENA;
s5l8950x_usb_bridge_finish(s, QA6_USB_STATUS_SUCCESS);
s5l8950x_usb_otg_update_irq(s);
}
static void s5l8950x_usb_bridge_complete_status_in(
S5L8950XUSBOTGState *s)
{
s->regs[USB_OTG_DIEPTSIZ0 / 4] &= ~0x7f;
s->regs[USB_OTG_DIEPCTL0 / 4] &= ~USB_OTG_EPCTL_EPENA;
s->regs[USB_OTG_DIEPINT0 / 4] |= USB_OTG_EPINT_XFERCOMPL;
s5l8950x_usb_bridge_finish(s, QA6_USB_STATUS_SUCCESS);
s5l8950x_usb_otg_update_irq(s);
}
static void s5l8950x_usb_bridge_process_frame(S5L8950XUSBOTGState *s)
{
Qa6UsbFrameHeader request;
uint32_t expected_payload;
uint16_t setup_length;
bool direction_in;
memcpy(&request, s->bridge_rx, sizeof(request));
expected_payload = QA6_USB_HEADER_SIZE + request.payload_length;
direction_in = (request.setup[0] & 0x80) != 0;
setup_length = request.setup[6] | (request.setup[7] << 8);
if (request.magic != QA6_USB_MAGIC ||
request.version != QA6_USB_PROTOCOL_VERSION ||
request.payload_length > QA6_USB_MAX_PAYLOAD ||
expected_payload != s->bridge_rx_len) {
qemu_log_mask(LOG_GUEST_ERROR,
"s5l8950x.usb-bridge: invalid frame header\n");
return;
}
if (request.type == QA6_USB_MESSAGE_INFO_REQUEST) {
if (request.payload_length || request.transfer_length) {
s5l8950x_usb_bridge_write_response(
s, &request, QA6_USB_STATUS_PROTOCOL, 0, NULL, 0);
} else {
s5l8950x_usb_bridge_write_info_response(s, &request);
}
return;
}
if (request.type == QA6_USB_MESSAGE_RESET_REQUEST) {
if (request.payload_length || request.transfer_length) {
s5l8950x_usb_bridge_write_response(
s, &request, QA6_USB_STATUS_PROTOCOL, 0, NULL, 0);
return;
}
if (s->bridge_dfu_status == 0 &&
s->bridge_dfu_state == USB_DFU_STATE_WAIT_RESET) {
if (s->native_img3_handoff) {
s5l8950x_usb_bridge_write_response(
s, &request, QA6_USB_STATUS_SUCCESS, 0, NULL, 0);
s->regs[USB_OTG_GINTSTS / 4] |= USB_OTG_GINT_USBRST;
s->usb_reset_sent = true;
s5l8950x_usb_otg_update_irq(s);
qemu_log_mask(LOG_UNIMP,
"s5l8950x.usb-bridge: native DFU "
"manifestation reset delivered to guest\n");
return;
}
if (!s5l8950x_usb_bridge_handoff_captured(s) &&
!s5l8950x_usb_bridge_handoff_bootloader(s)) {
qemu_log_mask(LOG_GUEST_ERROR,
"s5l8950x.usb-bridge: authenticated bootloader "
"handoff failed\n");
s5l8950x_usb_bridge_write_response(
s, &request, QA6_USB_STATUS_PROTOCOL, 0, NULL, 0);
return;
}
s5l8950x_usb_bridge_write_response(
s, &request, QA6_USB_STATUS_SUCCESS, 0, NULL, 0);
return;
}
s5l8950x_usb_bridge_write_response(
s, &request, QA6_USB_STATUS_SUCCESS, 0, NULL, 0);
qemu_log_mask(LOG_UNIMP,
"s5l8950x.usb-bridge: host USB reset request %u\n",
request.request_id);
s->regs[USB_OTG_GINTSTS / 4] |= USB_OTG_GINT_USBRST;
s5l8950x_usb_otg_update_irq(s);
return;
}
if (request.type == QA6_USB_MESSAGE_BULK_REQUEST) {
unsigned ep = request.setup[0] & 0x7f;
uint32_t timeout_ms = request.setup[1] |
(request.setup[2] << 8) |
(request.setup[3] << 16) |
((uint32_t)request.setup[4] << 24);
if (!ep || ep >= USB_OTG_EP_COUNT ||
request.transfer_length > QA6_USB_MAX_PAYLOAD ||
(direction_in && request.payload_length != 0) ||
(!direction_in &&
request.payload_length != request.transfer_length)) {
s5l8950x_usb_bridge_write_response(
s, &request, QA6_USB_STATUS_PROTOCOL, 0, NULL, 0);
return;
}
if (s->bridge_request_active ||
s->host_stage != USB_HOST_DFU_READY ||
!s->enumeration_generation || !s->device_descriptor_len) {
s5l8950x_usb_bridge_write_response(
s, &request, QA6_USB_STATUS_PROTOCOL, 0, NULL, 0);
return;
}
s->bridge_request = request;
if (request.payload_length) {
memcpy(s->bridge_out_data,
s->bridge_rx + QA6_USB_HEADER_SIZE,
request.payload_length);
}
s->bridge_out_offset = 0;
s->bridge_in_len = 0;
s->bridge_request_active = true;
if (direction_in && timeout_ms) {
timer_mod(s->bridge_bulk_timer,
qemu_clock_get_ms(QEMU_CLOCK_VIRTUAL) + timeout_ms);
}
qemu_log_mask(LOG_UNIMP,
"s5l8950x.usb-bridge: bulk request %u EP=%02x "
"len=%u\n", request.request_id, request.setup[0],
request.transfer_length);
s5l8950x_usb_bridge_try_bulk(s);
return;
}
if (request.type != QA6_USB_MESSAGE_REQUEST ||
request.transfer_length != setup_length ||
(direction_in && request.payload_length != 0) ||
(!direction_in && request.payload_length != setup_length)) {
qemu_log_mask(LOG_GUEST_ERROR,
"s5l8950x.usb-bridge: invalid request frame\n");
s5l8950x_usb_bridge_write_response(
s, &request, QA6_USB_STATUS_PROTOCOL, 0, NULL, 0);
return;
}
if (s->bridge_request_active || s->host_stage != USB_HOST_DFU_READY) {
qemu_log_mask(LOG_GUEST_ERROR,
"s5l8950x.usb-bridge: device is busy (stage=%d)\n",
s->host_stage);
s5l8950x_usb_bridge_write_response(
s, &request, QA6_USB_STATUS_PROTOCOL, 0, NULL, 0);
return;
}
s->bridge_request = request;
if (request.payload_length) {
memcpy(s->bridge_out_data, s->bridge_rx + QA6_USB_HEADER_SIZE,
request.payload_length);
}
s->bridge_out_offset = 0;
s->bridge_in_len = 0;
s->bridge_dfu_status = 0;
s->bridge_dfu_state = 0;
s->bridge_request_active = true;
qemu_log_mask(LOG_UNIMP,
"s5l8950x.usb-bridge: EP0 request %u "
"bm=%02x req=%02x len=%u\n",
request.request_id, request.setup[0], request.setup[1],
setup_length);
if (s->regs[USB_OTG_DOEPCTL0 / 4] & USB_OTG_EPCTL_EPENA) {
s5l8950x_usb_bridge_start_request(s);
}
}
static int s5l8950x_usb_bridge_can_receive(void *opaque)
{
S5L8950XUSBOTGState *s = opaque;
return QA6_USB_MAX_FRAME_SIZE - s->bridge_rx_len;
}
static void s5l8950x_usb_bridge_receive(void *opaque,
const uint8_t *buffer, int size)
{
S5L8950XUSBOTGState *s = opaque;
while (size > 0) {
uint32_t target = QA6_USB_HEADER_SIZE;
uint32_t amount;
if (s->bridge_rx_len >= QA6_USB_HEADER_SIZE) {
Qa6UsbFrameHeader header;
memcpy(&header, s->bridge_rx, sizeof(header));
if (header.payload_length > QA6_USB_MAX_PAYLOAD) {
qemu_log_mask(LOG_GUEST_ERROR,
"s5l8950x.usb-bridge: oversized frame\n");
s->bridge_rx_len = 0;
return;
}
target += header.payload_length;
}
amount = MIN((uint32_t)size, target - s->bridge_rx_len);
memcpy(s->bridge_rx + s->bridge_rx_len, buffer, amount);
s->bridge_rx_len += amount;
buffer += amount;
size -= amount;
if (s->bridge_rx_len >= QA6_USB_HEADER_SIZE) {
Qa6UsbFrameHeader header;
memcpy(&header, s->bridge_rx, sizeof(header));
if (header.payload_length <= QA6_USB_MAX_PAYLOAD &&
s->bridge_rx_len ==
QA6_USB_HEADER_SIZE + header.payload_length) {
s5l8950x_usb_bridge_process_frame(s);
s->bridge_rx_len = 0;
}
}
}
}
static void s5l8950x_usb_bridge_event(void *opaque, QEMUChrEvent event)
{
S5L8950XUSBOTGState *s = opaque;
if (event == CHR_EVENT_OPENED) {
s->bridge_open = true;
qemu_log_mask(LOG_UNIMP,
"s5l8950x.usb-bridge: Windows bridge connected\n");
} else if (event == CHR_EVENT_CLOSED) {
s->bridge_open = false;
s->bridge_rx_len = 0;
s->bridge_request_active = false;
timer_del(s->bridge_bulk_timer);
if (s->host_stage >= USB_HOST_BRIDGE_WAIT_DATA_IN &&
s->host_stage <= USB_HOST_BRIDGE_WAIT_STATUS_IN) {
s->host_stage = USB_HOST_DFU_READY;
}
qemu_log_mask(LOG_UNIMP,
"s5l8950x.usb-bridge: Windows bridge disconnected\n");
}
}
/*
* Reset the guest-visible DWC2 state and re-arm the small host state machine.
* SecureROM and iBSS each initialize this core, so a core reset must allow a
* fresh descriptor sequence without replaying an already-consumed DFU image.
*/
static void s5l8950x_usb_otg_rearm(S5L8950XUSBOTGState *s, bool cold)
{
timer_del(s->recovery_reset_timer);
timer_del(s->bridge_bulk_timer);
if (!cold && s->bridge_request_active) {
s5l8950x_usb_bridge_finish(s, QA6_USB_STATUS_DISCONNECTED);
}
memset(s->regs, 0, sizeof(s->regs));
s->regs[USB_OTG_GRSTCTL / sizeof(uint32_t)] =
USB_OTG_GRSTCTL_AHBIDLE;
s->usb_reset_sent = false;
s->enum_done_sent = false;
s->setup_sent = false;
s->descriptor_received = false;
s->device_descriptor_len = 0;
s->serial_descriptor_len = 0;
s->config_descriptor_len = 0;
s->config_total_length = 0;
s->current_device_is_dfu = false;
s->host_stage = USB_HOST_WAIT_DEVICE_SETUP;
s->bridge_request_active = false;
s->bridge_rx_len = 0;
s->bridge_out_offset = 0;
s->bridge_in_len = 0;
memset(s->device_descriptor, 0, sizeof(s->device_descriptor));
memset(s->serial_descriptor, 0, sizeof(s->serial_descriptor));
memset(s->config_descriptor, 0, sizeof(s->config_descriptor));
memset(&s->bridge_request, 0, sizeof(s->bridge_request));
memset(s->bridge_out_data, 0, sizeof(s->bridge_out_data));
memset(s->bridge_in_data, 0, sizeof(s->bridge_in_data));
if (cold) {
s->enumeration_generation = 0;
s->recovery_reset_wait_for_halt = false;
g_byte_array_set_size(s->bridge_dfu_image, 0);
s->bridge_dfu_next_block = 0;
s->bridge_dfu_capture_valid = false;
s->dfu_image_offset = 0;
s->dfu_packet_len = 0;
s->dfu_packet_offset = 0;
s->dfu_packet_block = 0;
s->dfu_next_block = 0;
s->dfu_notify_block = DIV_ROUND_UP(s->dfu_image_size,
USB_DFU_TRANSFER_SIZE);
s->dfu_packet_kind = USB_DFU_PACKET_PAYLOAD;
s->dfu_suffix_pending = false;
s->dfu_state = 0;
s->dfu_status_polls = 0;
s->dfu_finish_statuses = 0;
s->dfu_injection_complete = false;
memset(s->dfu_packet, 0, sizeof(s->dfu_packet));
memset(s->dfu_status, 0, sizeof(s->dfu_status));
}
s5l8950x_usb_otg_update_irq(s);
}
static uint32_t s5l8950x_usb_dfu_crc32(uint32_t crc, const uint8_t *data,
size_t length)
{
size_t i;
for (i = 0; i < length; i++) {
int bit;
crc ^= data[i];
for (bit = 0; bit < 8; bit++) {
crc = (crc >> 1) ^ (0xedb88320U & -(crc & 1));
}
}
return crc;
}
static void s5l8950x_usb_dfu_build_suffix(S5L8950XUSBOTGState *s)
{
static const uint8_t suffix_header[12] = {
0xff, 0xff, 0xff, 0xff, 0xac, 0x05,
0x00, 0x01, 0x55, 0x46, 0x44, 0x10,
};
uint32_t crc = 0xffffffffU;
crc = s5l8950x_usb_dfu_crc32(crc, s->dfu_image, s->dfu_image_size);
crc = s5l8950x_usb_dfu_crc32(crc, suffix_header,
sizeof(suffix_header));
memcpy(s->dfu_suffix, suffix_header, sizeof(suffix_header));
s->dfu_suffix[12] = crc;
s->dfu_suffix[13] = crc >> 8;
s->dfu_suffix[14] = crc >> 16;
s->dfu_suffix[15] = crc >> 24;
}
static void s5l8950x_usb_dfu_prepare_packet(S5L8950XUSBOTGState *s)
{
size_t remaining;
s->dfu_packet_offset = 0;
if (s->dfu_image_offset < s->dfu_image_size) {
remaining = s->dfu_image_size - s->dfu_image_offset;
s->dfu_packet_len = MIN(remaining, (size_t)USB_DFU_TRANSFER_SIZE);
memcpy(s->dfu_packet, s->dfu_image + s->dfu_image_offset,
s->dfu_packet_len);
s->dfu_image_offset += s->dfu_packet_len;
s->dfu_packet_block = s->dfu_next_block++;
s->dfu_packet_kind = USB_DFU_PACKET_PAYLOAD;
if (s->dfu_image_offset == s->dfu_image_size) {
if (s->dfu_packet_len + sizeof(s->dfu_suffix) <=
USB_DFU_TRANSFER_SIZE) {
memcpy(s->dfu_packet + s->dfu_packet_len, s->dfu_suffix,
sizeof(s->dfu_suffix));
s->dfu_packet_len += sizeof(s->dfu_suffix);
} else {
s->dfu_suffix_pending = true;
}
}
return;
}
if (s->dfu_suffix_pending) {
memcpy(s->dfu_packet, s->dfu_suffix, sizeof(s->dfu_suffix));
s->dfu_packet_len = sizeof(s->dfu_suffix);
s->dfu_packet_block = s->dfu_next_block - 1;
s->dfu_packet_kind = USB_DFU_PACKET_SUFFIX;
s->dfu_suffix_pending = false;
return;
}
s->dfu_packet_len = 0;
s->dfu_packet_block = s->dfu_notify_block;
s->dfu_packet_kind = USB_DFU_PACKET_NOTIFY;
}
static void s5l8950x_usb_dfu_send_getstate(S5L8950XUSBOTGState *s)
{
static const uint8_t setup[] = {
0xa1, 0x05, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00,
};
s5l8950x_usb_otg_send_setup(s, setup, "DFU_GETSTATE",
USB_HOST_WAIT_DFU_GETSTATE_IN);
}
static void s5l8950x_usb_dfu_send_dnload(S5L8950XUSBOTGState *s)
{
uint8_t setup[] = {
0x21, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
};
s5l8950x_usb_dfu_prepare_packet(s);
setup[2] = s->dfu_packet_block;
setup[3] = s->dfu_packet_block >> 8;
setup[6] = s->dfu_packet_len;
setup[7] = s->dfu_packet_len >> 8;
s5l8950x_usb_otg_send_setup(
s, setup,
s->dfu_packet_kind == USB_DFU_PACKET_NOTIFY ?
"DFU_DNLOAD(finish)" : "DFU_DNLOAD",
s->dfu_packet_len ? USB_HOST_WAIT_DFU_DNLOAD_DATA_OUT :
USB_HOST_WAIT_DFU_DNLOAD_STATUS_IN);
}
static void s5l8950x_usb_dfu_send_getstatus(S5L8950XUSBOTGState *s)
{
static const uint8_t setup[] = {
0xa1, 0x03, 0x00, 0x00, 0x00, 0x00,
USB_DFU_STATUS_SIZE, 0x00,
};
s5l8950x_usb_otg_send_setup(s, setup, "DFU_GETSTATUS",
USB_HOST_WAIT_DFU_GETSTATUS_IN);
}
static void s5l8950x_usb_otg_send_config_header_setup(
S5L8950XUSBOTGState *s)
{
static const uint8_t setup[] = {
0x80, 0x06, 0x00, 0x02, 0x00, 0x00, 0x09, 0x00,
};
s5l8950x_usb_otg_send_setup(s, setup, "GET_DESCRIPTOR(Config, 9)",
USB_HOST_WAIT_CONFIG_HEADER_IN);
}
static void s5l8950x_usb_otg_send_serial_descriptor_setup(
S5L8950XUSBOTGState *s)
{
static const uint8_t setup[] = {
0x80, 0x06, 0x04, 0x03, 0x09, 0x04, 0xff, 0x00,
};
s->serial_descriptor_len = 0;
memset(s->serial_descriptor, 0, sizeof(s->serial_descriptor));
s5l8950x_usb_otg_send_setup(s, setup, "GET_DESCRIPTOR(String 4)",
USB_HOST_WAIT_SERIAL_IN);
}
static void s5l8950x_usb_otg_send_config_full_setup(
S5L8950XUSBOTGState *s)
{
uint8_t setup[] = {
0x80, 0x06, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00,
};
setup[6] = s->config_total_length;
setup[7] = s->config_total_length >> 8;
s5l8950x_usb_otg_send_setup(s, setup, "GET_DESCRIPTOR(Config, full)",
USB_HOST_WAIT_CONFIG_FULL_IN);
}
static void s5l8950x_usb_otg_send_set_configuration_setup(
S5L8950XUSBOTGState *s)
{
static const uint8_t setup[] = {
0x00, 0x09, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00,
};
s5l8950x_usb_otg_send_setup(s, setup, "SET_CONFIGURATION(1)",
USB_HOST_WAIT_SET_CONFIGURATION_STATUS_IN);
}
static void s5l8950x_usb_otg_complete_out_status(
S5L8950XUSBOTGState *s, S5L8950XUSBHostStage next_stage)
{
s->regs[USB_OTG_DOEPINT0 / 4] |= USB_OTG_EPINT_XFERCOMPL;
s->regs[USB_OTG_DOEPCTL0 / 4] &= ~USB_OTG_EPCTL_EPENA;
s->host_stage = next_stage;
qemu_log_mask(LOG_UNIMP,
"s5l8950x.usb-host: EP0 OUT status complete\n");
s5l8950x_usb_otg_update_irq(s);
}
static void s5l8950x_usb_otg_complete_in_status(S5L8950XUSBOTGState *s)
{
s->regs[USB_OTG_DIEPTSIZ0 / 4] &= ~0x7f;
s->regs[USB_OTG_DIEPCTL0 / 4] &= ~USB_OTG_EPCTL_EPENA;
s->regs[USB_OTG_DIEPINT0 / 4] |= USB_OTG_EPINT_XFERCOMPL;
s->host_stage = USB_HOST_ADDRESS_ASSIGNED;
qemu_log_mask(LOG_UNIMP,
"s5l8950x.usb-host: SET_ADDRESS status complete, DCFG="
"0x%08x\n", s->regs[0x800 / 4]);
s5l8950x_usb_otg_update_irq(s);
}
static void s5l8950x_usb_otg_complete_configuration_status_in(
S5L8950XUSBOTGState *s)
{
uint16_t vendor = 0;
uint16_t product = 0;
uint32_t offset = 0;
bool has_dfu_interface = false;
bool has_recovery_interface = false;
s->regs[USB_OTG_DIEPTSIZ0 / 4] &= ~0x7f;
s->regs[USB_OTG_DIEPCTL0 / 4] &= ~USB_OTG_EPCTL_EPENA;
s->regs[USB_OTG_DIEPINT0 / 4] |= USB_OTG_EPINT_XFERCOMPL;
s->host_stage = USB_HOST_DFU_READY;
if (s->device_descriptor_len >= 12) {
vendor = s->device_descriptor[8] | (s->device_descriptor[9] << 8);
product = s->device_descriptor[10] |
(s->device_descriptor[11] << 8);
}
while (offset + 2 <= s->config_descriptor_len) {
uint8_t length = s->config_descriptor[offset];
uint8_t type = s->config_descriptor[offset + 1];
if (length < 2 || offset + length > s->config_descriptor_len) {
break;
}
if (type == 4 && length >= 9) {
uint8_t interface_class = s->config_descriptor[offset + 5];
uint8_t interface_subclass = s->config_descriptor[offset + 6];
if (interface_class == 0xfe && interface_subclass == 0x01) {
has_dfu_interface = true;
} else if (interface_class == 0xff) {
has_recovery_interface = true;
}
}
offset += length;
}
/*
* SecureROM uses an internal PID but a DFU-only configuration, which the
* bridge maps to Apple's public 0x1227 PID. Recovery PID 0x1281 keeps a
* DFU fallback interface alongside its vendor command/bulk interface and
* must retain its real identity.
*/
s->current_device_is_dfu = has_dfu_interface &&
!has_recovery_interface;
s->enumeration_generation++;
qemu_log_mask(LOG_UNIMP,
"s5l8950x.usb-host: enumeration complete; "
"%04x:%04x, %s interface\n", vendor, product,
s->current_device_is_dfu ? "DFU" : "non-DFU");
s5l8950x_usb_otg_update_irq(s);
}
static void s5l8950x_usb_dfu_complete_dnload_data_out(
S5L8950XUSBOTGState *s)
{
hwaddr dma = s->regs[USB_OTG_DOEPDMA0 / 4];
uint32_t remaining = s->dfu_packet_len - s->dfu_packet_offset;
uint32_t capacity = s->regs[USB_OTG_DOEPTSIZ0 / 4] & 0x7f;
uint32_t length;
if (!capacity) {
capacity = 64;
}
length = MIN(remaining, capacity);
if (dma_memory_write(&address_space_memory, dma,
s->dfu_packet + s->dfu_packet_offset, length,
MEMTXATTRS_UNSPECIFIED) != MEMTX_OK) {
qemu_log_mask(LOG_GUEST_ERROR,
"s5l8950x.usb: DFU data DMA write failed at 0x%08"
HWADDR_PRIx "\n", dma);
s->host_stage = USB_HOST_DFU_ERROR;
return;
}
s->dfu_packet_offset += length;
/* Preserve the residual XFRSIZ so SecureROM sees the short packet. */
s->regs[USB_OTG_DOEPTSIZ0 / 4] =
(s->regs[USB_OTG_DOEPTSIZ0 / 4] & ~0x7f) |
(capacity - length);
s->regs[USB_OTG_DOEPCTL0 / 4] &= ~USB_OTG_EPCTL_EPENA;
s->regs[USB_OTG_DOEPINT0 / 4] |= USB_OTG_EPINT_XFERCOMPL;
if (s->dfu_packet_offset == s->dfu_packet_len) {
s->host_stage = USB_HOST_WAIT_DFU_DNLOAD_STATUS_IN;
qemu_log_mask(LOG_UNIMP,
"s5l8950x.usb-host: DFU block %u data complete "
"(%u bytes)\n", s->dfu_packet_block,
s->dfu_packet_len);
}
s5l8950x_usb_otg_update_irq(s);
}
static void s5l8950x_usb_dfu_complete_dnload_status_in(
S5L8950XUSBOTGState *s)
{
s->regs[USB_OTG_DIEPTSIZ0 / 4] &= ~0x7f;
s->regs[USB_OTG_DIEPCTL0 / 4] &= ~USB_OTG_EPCTL_EPENA;
s->regs[USB_OTG_DIEPINT0 / 4] |= USB_OTG_EPINT_XFERCOMPL;
s->host_stage = USB_HOST_WAIT_DFU_GETSTATUS_SETUP;
s5l8950x_usb_otg_update_irq(s);
}
static void s5l8950x_usb_dfu_complete_getstate_in(
S5L8950XUSBOTGState *s)
{
hwaddr dma = s->regs[USB_OTG_DIEPDMA0 / 4];
if (dma_memory_read(&address_space_memory, dma, &s->dfu_state, 1,
MEMTXATTRS_UNSPECIFIED) != MEMTX_OK) {
qemu_log_mask(LOG_GUEST_ERROR,
"s5l8950x.usb: DFU_GETSTATE DMA read failed at 0x%08"
HWADDR_PRIx "\n", dma);
s->host_stage = USB_HOST_DFU_ERROR;
return;
}
s->regs[USB_OTG_DIEPTSIZ0 / 4] &= ~0x7f;
s->regs[USB_OTG_DIEPCTL0 / 4] &= ~USB_OTG_EPCTL_EPENA;
s->regs[USB_OTG_DIEPINT0 / 4] |= USB_OTG_EPINT_XFERCOMPL;
s->host_stage = USB_HOST_WAIT_DFU_GETSTATE_STATUS_OUT;
qemu_log_mask(LOG_UNIMP,
"s5l8950x.usb-host: DFU initial state=%u\n",
s->dfu_state);
s5l8950x_usb_otg_update_irq(s);
}
static void s5l8950x_usb_dfu_complete_getstatus_in(
S5L8950XUSBOTGState *s)
{
hwaddr dma = s->regs[USB_OTG_DIEPDMA0 / 4];
if (dma_memory_read(&address_space_memory, dma, s->dfu_status,
sizeof(s->dfu_status),
MEMTXATTRS_UNSPECIFIED) != MEMTX_OK) {
qemu_log_mask(LOG_GUEST_ERROR,
"s5l8950x.usb: DFU_GETSTATUS DMA read failed at "
"0x%08" HWADDR_PRIx "\n", dma);
s->host_stage = USB_HOST_DFU_ERROR;
return;
}
s->dfu_state = s->dfu_status[4];
s->regs[USB_OTG_DIEPTSIZ0 / 4] &= ~0x7f;
s->regs[USB_OTG_DIEPCTL0 / 4] &= ~USB_OTG_EPCTL_EPENA;
s->regs[USB_OTG_DIEPINT0 / 4] |= USB_OTG_EPINT_XFERCOMPL;
s->host_stage = USB_HOST_WAIT_DFU_GETSTATUS_STATUS_OUT;
qemu_log_mask(LOG_UNIMP,
"s5l8950x.usb-host: DFU status=%u state=%u "
"poll=%u ms\n", s->dfu_status[0], s->dfu_state,
s->dfu_status[1] | (s->dfu_status[2] << 8) |
(s->dfu_status[3] << 16));
s5l8950x_usb_otg_update_irq(s);
}
static S5L8950XUSBHostStage s5l8950x_usb_dfu_stage_after_status(
S5L8950XUSBOTGState *s)
{
if (s->dfu_status[0] || s->dfu_state == USB_DFU_STATE_ERROR) {
qemu_log_mask(LOG_GUEST_ERROR,
"s5l8950x.usb-host: DFU rejected block %u "
"(status=%u state=%u)\n", s->dfu_packet_block,
s->dfu_status[0], s->dfu_state);
return USB_HOST_DFU_ERROR;
}
if (s->dfu_packet_kind == USB_DFU_PACKET_NOTIFY) {
s->dfu_finish_statuses++;
if (s->dfu_state == USB_DFU_STATE_MANIFEST_SYNC ||
s->dfu_state == USB_DFU_STATE_MANIFEST) {
if (s->dfu_finish_statuses >= USB_DFU_MAX_STATUS_POLLS) {
qemu_log_mask(LOG_GUEST_ERROR,
"s5l8950x.usb-host: DFU manifestation did "
"not reach MANIFEST-WAIT-RESET\n");
return USB_HOST_DFU_ERROR;
}
return USB_HOST_WAIT_DFU_GETSTATUS_SETUP;
}
if (s->dfu_state != USB_DFU_STATE_WAIT_RESET) {
qemu_log_mask(LOG_GUEST_ERROR,
"s5l8950x.usb-host: unexpected final DFU state "
"%u\n", s->dfu_state);
return USB_HOST_DFU_ERROR;
}
qemu_log_mask(LOG_UNIMP,
"s5l8950x.usb-host: SecureROM accepted DFU image "
"(%zu bytes, final state=%u)\n",
s->dfu_image_size, s->dfu_state);
s->dfu_injection_complete = true;
return USB_HOST_DFU_DONE;
}
if (s->dfu_state == USB_DFU_STATE_DNLOAD_IDLE) {
s->dfu_status_polls = 0;
return USB_HOST_DFU_DNLOAD_READY;
}
if (++s->dfu_status_polls >= USB_DFU_MAX_STATUS_POLLS) {
qemu_log_mask(LOG_GUEST_ERROR,
"s5l8950x.usb-host: DFU state %u did not become "
"DNLOAD-IDLE\n", s->dfu_state);
return USB_HOST_DFU_ERROR;
}
return USB_HOST_WAIT_DFU_GETSTATUS_SETUP;
}
static void s5l8950x_usb_otg_complete_descriptor_in(
S5L8950XUSBOTGState *s)
{
hwaddr dma = s->regs[USB_OTG_DIEPDMA0 / 4];
uint32_t length = s->regs[USB_OTG_DIEPTSIZ0 / 4] & 0x7f;
uint32_t i;
length = MIN(length, (uint32_t)sizeof(s->device_descriptor));
if (dma_memory_read(&address_space_memory, dma, s->device_descriptor,
length, MEMTXATTRS_UNSPECIFIED) != MEMTX_OK) {
qemu_log_mask(LOG_GUEST_ERROR,
"s5l8950x.usb: EP0 IN DMA read failed at 0x%08"
HWADDR_PRIx "\n", dma);
return;
}
s->device_descriptor_len = length;
s->descriptor_received = true;
s->host_stage = USB_HOST_WAIT_DEVICE_STATUS_OUT;
s->regs[USB_OTG_DIEPTSIZ0 / 4] &= ~0x7f;
s->regs[USB_OTG_DIEPCTL0 / 4] &= ~USB_OTG_EPCTL_EPENA;
s->regs[USB_OTG_DIEPINT0 / 4] |= USB_OTG_EPINT_XFERCOMPL;
qemu_log_mask(LOG_UNIMP,
"s5l8950x.usb-host: device descriptor (%u bytes):",
length);
for (i = 0; i < length; i++) {
qemu_log(" %02x", s->device_descriptor[i]);
}
qemu_log("\n");
s5l8950x_usb_otg_update_irq(s);
}
static void s5l8950x_usb_otg_complete_serial_in(S5L8950XUSBOTGState *s)
{
hwaddr dma = s->regs[USB_OTG_DIEPDMA0 / 4];
uint32_t length = s->regs[USB_OTG_DIEPTSIZ0 / 4] & 0x7f;
uint32_t available = sizeof(s->serial_descriptor) -
s->serial_descriptor_len;
uint32_t descriptor_length;
bool complete;
length = MIN(length, available);
if (dma_memory_read(&address_space_memory, dma,
s->serial_descriptor + s->serial_descriptor_len,
length, MEMTXATTRS_UNSPECIFIED) != MEMTX_OK) {
qemu_log_mask(LOG_GUEST_ERROR,
"s5l8950x.usb: serial descriptor DMA read failed at "
"0x%08" HWADDR_PRIx "\n", dma);
return;
}
s->serial_descriptor_len += length;
descriptor_length = s->serial_descriptor_len ?
s->serial_descriptor[0] : 0;
complete = length < 64 ||
(descriptor_length &&
s->serial_descriptor_len >= descriptor_length) ||
s->serial_descriptor_len == sizeof(s->serial_descriptor);
s->regs[USB_OTG_DIEPTSIZ0 / 4] &= ~0x7f;
s->regs[USB_OTG_DIEPCTL0 / 4] &= ~USB_OTG_EPCTL_EPENA;
s->regs[USB_OTG_DIEPINT0 / 4] |= USB_OTG_EPINT_XFERCOMPL;
if (complete) {
char serial[128];
uint32_t i;
uint32_t j = 0;
for (i = 2; i + 1 < s->serial_descriptor_len &&
j + 1 < sizeof(serial); i += 2) {
serial[j++] = s->serial_descriptor[i + 1] ? '?' :
s->serial_descriptor[i];
}
serial[j] = '\0';
s->host_stage = USB_HOST_WAIT_SERIAL_STATUS_OUT;
qemu_log_mask(LOG_UNIMP,
"s5l8950x.usb-host: DFU serial: %s\n", serial);
}
s5l8950x_usb_otg_update_irq(s);
}
static void s5l8950x_usb_otg_complete_config_in(S5L8950XUSBOTGState *s,
bool header_only)
{
hwaddr dma = s->regs[USB_OTG_DIEPDMA0 / 4];
uint32_t length = s->regs[USB_OTG_DIEPTSIZ0 / 4] & 0x7f;
uint32_t i;
length = MIN(length, (uint32_t)sizeof(s->config_descriptor));
if (dma_memory_read(&address_space_memory, dma, s->config_descriptor,
length, MEMTXATTRS_UNSPECIFIED) != MEMTX_OK) {
qemu_log_mask(LOG_GUEST_ERROR,
"s5l8950x.usb: config descriptor DMA read failed at "
"0x%08" HWADDR_PRIx "\n", dma);
return;
}
s->config_descriptor_len = length;
if (header_only && length >= 4) {
s->config_total_length = s->config_descriptor[2] |
(s->config_descriptor[3] << 8);
if (s->config_total_length < 9 ||
s->config_total_length > sizeof(s->config_descriptor)) {
s->config_total_length = 9;
}
s->host_stage = USB_HOST_WAIT_CONFIG_HEADER_STATUS_OUT;
} else {
s->host_stage = USB_HOST_WAIT_CONFIG_FULL_STATUS_OUT;
}
s->regs[USB_OTG_DIEPTSIZ0 / 4] &= ~0x7f;
s->regs[USB_OTG_DIEPCTL0 / 4] &= ~USB_OTG_EPCTL_EPENA;
s->regs[USB_OTG_DIEPINT0 / 4] |= USB_OTG_EPINT_XFERCOMPL;
qemu_log_mask(LOG_UNIMP,
"s5l8950x.usb-host: config descriptor (%u bytes):",
length);
for (i = 0; i < length; i++) {
qemu_log(" %02x", s->config_descriptor[i]);
}
qemu_log("\n");
s5l8950x_usb_otg_update_irq(s);
}
static uint64_t s5l8950x_usb_otg_read(void *opaque, hwaddr offset,
unsigned size)
{
S5L8950XUSBOTGState *s = opaque;
uint32_t value;
if (offset == USB_OTG_GRSTCTL) {
value = s->regs[offset / sizeof(uint32_t)] |
USB_OTG_GRSTCTL_AHBIDLE;
} else {
value = s->regs[offset / sizeof(uint32_t)];
}
return value;
}
static void s5l8950x_usb_otg_write(void *opaque, hwaddr offset,
uint64_t value, unsigned size)
{
S5L8950XUSBOTGState *s = opaque;
if (s->enumeration_generation && !s->device_descriptor_len &&
(offset == USB_OTG_GRSTCTL || offset == USB_OTG_GAHBCFG ||
offset == USB_OTG_GINTSTS || offset == USB_OTG_GINTMSK ||
offset == USB_OTG_DCTL || offset == USB_OTG_DIEPMSK ||
offset == USB_OTG_DOEPMSK || offset == USB_OTG_DAINTMSK ||
offset == USB_OTG_DIEPCTL0 || offset == USB_OTG_DIEPTSIZ0 ||
offset == USB_OTG_DIEPDMA0 || offset == USB_OTG_DOEPCTL0 ||
offset == USB_OTG_DOEPTSIZ0 || offset == USB_OTG_DOEPDMA0)) {
vaddr pc = current_cpu ? ARM_CPU(current_cpu)->env.regs[15] : 0;
qemu_log_mask(LOG_UNIMP,
"s5l8950x.usb-trace: PC=0x%08" VADDR_PRIx
" write[0x%04" HWADDR_PRIx "]=0x%08" PRIx64
" stage=%u\n",
pc, offset, value, s->host_stage);
}
if (offset == USB_OTG_GRSTCTL &&
(value & USB_OTG_GRSTCTL_CSFTRST)) {
/* iBSS initializes the core again after SecureROM jumps to it. */
s5l8950x_usb_otg_rearm(s, false);
qemu_log_mask(LOG_UNIMP,
"s5l8950x.usb-host: core soft reset; "
"waiting for device re-enumeration\n");
return;
}
if (offset == USB_OTG_DCTL) {
uint32_t old_value = s->regs[USB_OTG_DCTL / 4];
if ((old_value & USB_OTG_DCTL_SFTDISCON) &&
!(value & USB_OTG_DCTL_SFTDISCON)) {
/*
* DCTL soft reconnect changes only the bus attachment. The DWC
* register image programmed by iBSS must survive it; the host
* reset is delivered later, once the interrupt handler is live.
*/
s->regs[USB_OTG_GINTSTS / 4] &=
~(USB_OTG_GINT_USBRST | USB_OTG_GINT_ENUMDONE);
s->usb_reset_sent = false;
s->enum_done_sent = false;
s->setup_sent = false;
s->descriptor_received = false;
s->device_descriptor_len = 0;
s->serial_descriptor_len = 0;
s->config_descriptor_len = 0;
s->config_total_length = 0;
s->current_device_is_dfu = false;
s->host_stage = USB_HOST_WAIT_DEVICE_SETUP;
timer_mod(s->recovery_reset_timer,
qemu_clock_get_ms(QEMU_CLOCK_VIRTUAL) + 1);
qemu_log_mask(LOG_UNIMP,
"s5l8950x.usb-host: soft reconnect; "
"waiting for device re-enumeration\n");
}
s->regs[USB_OTG_DCTL / 4] = value;
s5l8950x_usb_otg_update_irq(s);
return;
}
if (offset == USB_OTG_GINTSTS) {
uint32_t old_status = s->regs[offset / 4];
s->regs[offset / 4] &= ~value;
if ((old_status & value & USB_OTG_GINT_USBRST) &&
!s->enum_done_sent) {
s->regs[offset / 4] |= USB_OTG_GINT_ENUMDONE;
s->enum_done_sent = true;
}
s5l8950x_usb_otg_update_irq(s);
return;
}
if (s5l8950x_usb_otg_is_ep_int(offset, USB_OTG_DIEPINT0) ||
s5l8950x_usb_otg_is_ep_int(offset, USB_OTG_DOEPINT0)) {
s->regs[offset / 4] &= ~value;
s5l8950x_usb_otg_update_irq(s);
return;
}
if (offset == USB_OTG_GRSTCTL) {
/* Reset/FIFO flush requests complete synchronously in this model. */
value &= ~(USB_OTG_GRSTCTL_CSFTRST |
USB_OTG_GRSTCTL_RXFFLSH |
USB_OTG_GRSTCTL_TXFFLSH);
value |= USB_OTG_GRSTCTL_AHBIDLE;
}
s->regs[offset / sizeof(uint32_t)] = value;
if ((value & USB_OTG_EPCTL_EPENA) &&
(s5l8950x_usb_otg_is_ep_int(offset, USB_OTG_DIEPCTL0) ||
s5l8950x_usb_otg_is_ep_int(offset, USB_OTG_DOEPCTL0))) {
s5l8950x_usb_bridge_try_bulk(s);
}
if ((offset == USB_OTG_DIEPCTL0 || offset == USB_OTG_DOEPCTL0) &&
(value & USB_OTG_EPCTL_STALL) && s->bridge_request_active) {
qemu_log_mask(LOG_GUEST_ERROR,
"s5l8950x.usb-bridge: guest armed EP0 STALL; "
"CPU state follows\n");
if (current_cpu && qemu_loglevel_mask(LOG_GUEST_ERROR)) {
FILE *log_file = qemu_log_trylock();
if (log_file) {
cpu_dump_state(current_cpu, log_file, 0);
qemu_log_unlock(log_file);
}
}
s5l8950x_usb_bridge_finish(s, QA6_USB_STATUS_STALL);
s5l8950x_usb_otg_update_irq(s);
return;
}
if (offset == USB_OTG_DIEPCTL0 &&
(value & USB_OTG_EPCTL_STALL) &&
s->host_stage == USB_HOST_WAIT_DFU_DNLOAD_STATUS_IN) {
s->host_stage = USB_HOST_DFU_ERROR;
qemu_log_mask(LOG_GUEST_ERROR,
"s5l8950x.usb-host: firmware stalled EP0; "
"DFU image rejected after block %u\n",
s->dfu_packet_block);
s5l8950x_usb_otg_update_irq(s);
return;
}
if (offset == USB_OTG_GINTMSK &&
(value & USB_OTG_GINT_USBRST) && !s->usb_reset_sent) {
if (s->enumeration_generation) {
/*
* Enabling USBRST while DCTL still holds the soft-disconnect
* bit does not expose the device to a physical host. UART
* logging makes iBEC's early platform setup long enough for an
* incorrectly armed timer to fire here, before its USB handler
* is installed. The DCTL reconnect path above will arm the
* reset once the guest is actually visible on the bus.
*/
if (!(s->regs[USB_OTG_DCTL / 4] &
USB_OTG_DCTL_SFTDISCON)) {
timer_mod(s->recovery_reset_timer,
qemu_clock_get_ms(QEMU_CLOCK_VIRTUAL) + 1);
}
} else {
s->regs[USB_OTG_GINTSTS / 4] |= USB_OTG_GINT_USBRST;
s->usb_reset_sent = true;
}
}
if (offset == USB_OTG_DOEPCTL0 &&
(value & USB_OTG_EPCTL_EPENA) && s->enum_done_sent) {
if (s->host_stage == USB_HOST_DFU_READY &&
s->bridge_request_active) {
s5l8950x_usb_bridge_start_request(s);
} else if (s->host_stage == USB_HOST_BRIDGE_WAIT_DATA_OUT) {
s5l8950x_usb_bridge_complete_data_out(s);
} else if (s->host_stage == USB_HOST_BRIDGE_WAIT_STATUS_OUT) {
s5l8950x_usb_bridge_complete_status_out(s);
} else if (s->host_stage == USB_HOST_WAIT_DEVICE_SETUP) {
s5l8950x_usb_otg_send_device_descriptor_setup(s);
} else if (s->host_stage == USB_HOST_WAIT_DEVICE_STATUS_OUT) {
s5l8950x_usb_otg_complete_out_status(
s, USB_HOST_WAIT_SET_ADDRESS_SETUP);
} else if (s->host_stage == USB_HOST_WAIT_SET_ADDRESS_SETUP) {
s5l8950x_usb_otg_send_set_address_setup(s);
} else if (s->host_stage == USB_HOST_ADDRESS_ASSIGNED) {
s5l8950x_usb_otg_send_serial_descriptor_setup(s);
} else if (s->host_stage == USB_HOST_WAIT_SERIAL_STATUS_OUT) {
s5l8950x_usb_otg_complete_out_status(
s, USB_HOST_WAIT_CONFIG_HEADER_SETUP);
} else if (s->host_stage == USB_HOST_WAIT_CONFIG_HEADER_SETUP) {
s5l8950x_usb_otg_send_config_header_setup(s);
} else if (s->host_stage ==
USB_HOST_WAIT_CONFIG_HEADER_STATUS_OUT) {
s5l8950x_usb_otg_complete_out_status(
s, USB_HOST_WAIT_CONFIG_FULL_SETUP);
} else if (s->host_stage == USB_HOST_WAIT_CONFIG_FULL_SETUP) {
s5l8950x_usb_otg_send_config_full_setup(s);
} else if (s->host_stage ==
USB_HOST_WAIT_CONFIG_FULL_STATUS_OUT) {
s5l8950x_usb_otg_complete_out_status(
s, USB_HOST_WAIT_SET_CONFIGURATION_SETUP);
} else if (s->host_stage ==
USB_HOST_WAIT_SET_CONFIGURATION_SETUP) {
s5l8950x_usb_otg_send_set_configuration_setup(s);
} else if (s->host_stage == USB_HOST_DFU_READY && s->dfu_image &&
s->current_device_is_dfu &&
!s->dfu_injection_complete) {
s5l8950x_usb_dfu_send_getstate(s);
} else if (s->host_stage ==
USB_HOST_WAIT_DFU_GETSTATE_STATUS_OUT) {
s5l8950x_usb_otg_complete_out_status(
s, s->dfu_state == USB_DFU_STATE_IDLE ?
USB_HOST_DFU_DNLOAD_READY : USB_HOST_DFU_ERROR);
if (s->dfu_state != USB_DFU_STATE_IDLE) {
qemu_log_mask(LOG_GUEST_ERROR,
"s5l8950x.usb-host: expected DFU-IDLE, got "
"state %u\n", s->dfu_state);
}
} else if (s->host_stage == USB_HOST_DFU_DNLOAD_READY) {
s5l8950x_usb_dfu_send_dnload(s);
} else if (s->host_stage ==
USB_HOST_WAIT_DFU_DNLOAD_DATA_OUT) {
s5l8950x_usb_dfu_complete_dnload_data_out(s);
} else if (s->host_stage ==
USB_HOST_WAIT_DFU_GETSTATUS_SETUP) {
s5l8950x_usb_dfu_send_getstatus(s);
} else if (s->host_stage ==
USB_HOST_WAIT_DFU_GETSTATUS_STATUS_OUT) {
s5l8950x_usb_otg_complete_out_status(
s, s5l8950x_usb_dfu_stage_after_status(s));
}
/* Device firmware may arm the status IN stage before SETUP/OUT ends. */
if (s->regs[USB_OTG_DIEPCTL0 / 4] & USB_OTG_EPCTL_EPENA) {
if (s->host_stage == USB_HOST_BRIDGE_WAIT_STATUS_IN) {
s5l8950x_usb_bridge_complete_status_in(s);
} else if (s->host_stage ==
USB_HOST_WAIT_SET_ADDRESS_STATUS_IN) {
s5l8950x_usb_otg_complete_in_status(s);
} else if (s->host_stage ==
USB_HOST_WAIT_SET_CONFIGURATION_STATUS_IN) {
s5l8950x_usb_otg_complete_configuration_status_in(s);
} else if (s->host_stage ==
USB_HOST_WAIT_DFU_DNLOAD_STATUS_IN) {
s5l8950x_usb_dfu_complete_dnload_status_in(s);
}
}
}
if (offset == USB_OTG_DIEPCTL0 &&
(value & USB_OTG_EPCTL_EPENA)) {
if (s->host_stage == USB_HOST_BRIDGE_WAIT_DATA_IN) {
s5l8950x_usb_bridge_complete_data_in(s);
} else if (s->host_stage == USB_HOST_BRIDGE_WAIT_STATUS_IN) {
s5l8950x_usb_bridge_complete_status_in(s);
} else if (s->host_stage == USB_HOST_WAIT_DEVICE_IN) {
s5l8950x_usb_otg_complete_descriptor_in(s);
} else if (s->host_stage == USB_HOST_WAIT_SET_ADDRESS_STATUS_IN) {
s5l8950x_usb_otg_complete_in_status(s);
} else if (s->host_stage == USB_HOST_WAIT_SERIAL_IN) {
s5l8950x_usb_otg_complete_serial_in(s);
} else if (s->host_stage == USB_HOST_WAIT_CONFIG_HEADER_IN) {
s5l8950x_usb_otg_complete_config_in(s, true);
} else if (s->host_stage == USB_HOST_WAIT_CONFIG_FULL_IN) {
s5l8950x_usb_otg_complete_config_in(s, false);
} else if (s->host_stage ==
USB_HOST_WAIT_SET_CONFIGURATION_STATUS_IN) {
s5l8950x_usb_otg_complete_configuration_status_in(s);
} else if (s->host_stage == USB_HOST_WAIT_DFU_GETSTATE_IN) {
s5l8950x_usb_dfu_complete_getstate_in(s);
} else if (s->host_stage ==
USB_HOST_WAIT_DFU_DNLOAD_STATUS_IN) {
s5l8950x_usb_dfu_complete_dnload_status_in(s);
} else if (s->host_stage == USB_HOST_WAIT_DFU_GETSTATUS_IN) {
s5l8950x_usb_dfu_complete_getstatus_in(s);
}
/* Device firmware may arm status OUT before the final IN packet. */
if (s->regs[USB_OTG_DOEPCTL0 / 4] & USB_OTG_EPCTL_EPENA) {
if (s->host_stage == USB_HOST_BRIDGE_WAIT_STATUS_OUT) {
s5l8950x_usb_bridge_complete_status_out(s);
} else if (s->host_stage == USB_HOST_WAIT_DEVICE_STATUS_OUT) {
s5l8950x_usb_otg_complete_out_status(
s, USB_HOST_WAIT_SET_ADDRESS_SETUP);
} else if (s->host_stage == USB_HOST_WAIT_SERIAL_STATUS_OUT) {
s5l8950x_usb_otg_complete_out_status(
s, USB_HOST_WAIT_CONFIG_HEADER_SETUP);
} else if (s->host_stage ==
USB_HOST_WAIT_CONFIG_HEADER_STATUS_OUT) {
s5l8950x_usb_otg_complete_out_status(
s, USB_HOST_WAIT_CONFIG_FULL_SETUP);
} else if (s->host_stage ==
USB_HOST_WAIT_CONFIG_FULL_STATUS_OUT) {
s5l8950x_usb_otg_complete_out_status(
s, USB_HOST_WAIT_SET_CONFIGURATION_SETUP);
} else if (s->host_stage ==
USB_HOST_WAIT_DFU_GETSTATE_STATUS_OUT) {
s5l8950x_usb_otg_complete_out_status(
s, s->dfu_state == USB_DFU_STATE_IDLE ?
USB_HOST_DFU_DNLOAD_READY : USB_HOST_DFU_ERROR);
} else if (s->host_stage ==
USB_HOST_WAIT_DFU_GETSTATUS_STATUS_OUT) {
s5l8950x_usb_otg_complete_out_status(
s, s5l8950x_usb_dfu_stage_after_status(s));
}
}
}
s5l8950x_usb_otg_update_irq(s);
}
static const MemoryRegionOps s5l8950x_usb_otg_ops = {
.read = s5l8950x_usb_otg_read,
.write = s5l8950x_usb_otg_write,
.endianness = DEVICE_LITTLE_ENDIAN,
.valid = {
.min_access_size = 4,
.max_access_size = 4,
},
.impl = {
.min_access_size = 4,
.max_access_size = 4,
},
};
static void s5l8950x_usb_otg_reset(DeviceState *dev)
{
S5L8950XUSBOTGState *s = S5L8950X_USB_OTG(dev);
QEMU_BUILD_BUG_ON(sizeof(Qa6UsbFrameHeader) != 32);
QEMU_BUILD_BUG_ON(sizeof(Qa6UsbDeviceInfo) != 268);
s5l8950x_usb_otg_rearm(s, true);
}
static void s5l8950x_usb_otg_realize(DeviceState *dev, Error **errp)
{
S5L8950XUSBOTGState *s = S5L8950X_USB_OTG(dev);
g_autoptr(GError) gerr = NULL;
if (qemu_chr_fe_backend_connected(&s->bridge_chr)) {
qemu_chr_fe_set_handlers(&s->bridge_chr,
s5l8950x_usb_bridge_can_receive,
s5l8950x_usb_bridge_receive,
s5l8950x_usb_bridge_event,
NULL, s, NULL, true);
qemu_log_mask(LOG_UNIMP,
"s5l8950x.usb-bridge: external USB bridge enabled\n");
}
if (!s->dfu_image_path) {
return;
}
if (!g_file_get_contents(s->dfu_image_path, (gchar **)&s->dfu_image,
&s->dfu_image_size, &gerr)) {
error_setg(errp, "could not load DFU image '%s': %s",
s->dfu_image_path, gerr->message);
return;
}
if (!s->dfu_image_size ||
s->dfu_image_size > (size_t)UINT16_MAX * USB_DFU_TRANSFER_SIZE) {
error_setg(errp, "DFU image '%s' has unsupported size %zu",
s->dfu_image_path, s->dfu_image_size);
g_clear_pointer(&s->dfu_image, g_free);
s->dfu_image_size = 0;
return;
}
s5l8950x_usb_dfu_build_suffix(s);
s->dfu_notify_block = DIV_ROUND_UP(s->dfu_image_size,
USB_DFU_TRANSFER_SIZE);
qemu_log_mask(LOG_UNIMP,
"s5l8950x.usb-host: loaded DFU image '%s' (%zu bytes)\n",
s->dfu_image_path, s->dfu_image_size);
}
static void s5l8950x_usb_otg_unrealize(DeviceState *dev)
{
S5L8950XUSBOTGState *s = S5L8950X_USB_OTG(dev);
qemu_chr_fe_set_handlers(&s->bridge_chr, NULL, NULL, NULL, NULL,
NULL, NULL, false);
timer_free(s->recovery_reset_timer);
s->recovery_reset_timer = NULL;
timer_free(s->bridge_bulk_timer);
s->bridge_bulk_timer = NULL;
g_clear_pointer(&s->bridge_dfu_image, g_byte_array_unref);
g_clear_pointer(&s->dfu_image, g_free);
s->dfu_image_size = 0;
}
static void s5l8950x_usb_otg_init(Object *obj)
{
S5L8950XUSBOTGState *s = S5L8950X_USB_OTG(obj);
SysBusDevice *sbd = SYS_BUS_DEVICE(obj);
s->recovery_reset_timer = timer_new_ms(
QEMU_CLOCK_VIRTUAL, s5l8950x_usb_otg_recovery_reset, s);
s->bridge_bulk_timer = timer_new_ms(
QEMU_CLOCK_VIRTUAL, s5l8950x_usb_bridge_bulk_timeout, s);
s->bridge_dfu_image = g_byte_array_new();
memory_region_init_io(&s->iomem, obj, &s5l8950x_usb_otg_ops, s,
TYPE_S5L8950X_USB_OTG,
S5L8950X_USB_OTG_REGION_SIZE);
sysbus_init_mmio(sbd, &s->iomem);
sysbus_init_irq(sbd, &s->irq);
}
static const Property s5l8950x_usb_otg_properties[] = {
DEFINE_PROP_STRING("dfu-image", S5L8950XUSBOTGState, dfu_image_path),
DEFINE_PROP_CHR("usb-bridge", S5L8950XUSBOTGState, bridge_chr),
DEFINE_PROP_BOOL("force-debug-uarts", S5L8950XUSBOTGState,
force_debug_uarts, false),
DEFINE_PROP_BOOL("native-img3-handoff", S5L8950XUSBOTGState,
native_img3_handoff, false),
};
static void s5l8950x_usb_otg_class_init(ObjectClass *klass,
const void *data)
{
DeviceClass *dc = DEVICE_CLASS(klass);
dc->realize = s5l8950x_usb_otg_realize;
dc->unrealize = s5l8950x_usb_otg_unrealize;
device_class_set_legacy_reset(dc, s5l8950x_usb_otg_reset);
device_class_set_props(dc, s5l8950x_usb_otg_properties);
}
static const TypeInfo s5l8950x_usb_otg_info = {
.name = TYPE_S5L8950X_USB_OTG,
.parent = TYPE_SYS_BUS_DEVICE,
.instance_size = sizeof(S5L8950XUSBOTGState),
.instance_init = s5l8950x_usb_otg_init,
.class_init = s5l8950x_usb_otg_class_init,
};
static void s5l8950x_usb_otg_register_types(void)
{
type_register_static(&s5l8950x_usb_otg_info);
}
type_init(s5l8950x_usb_otg_register_types)