Files
QEMU-S5L8950X/hw/usb/s5l8950x-usb-otg.c
T

1373 lines
50 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 "hw/arm/s5l8950x.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 "system/address-spaces.h"
#include "system/dma.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_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_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_ERROR 10
#define USB_DFU_MAX_STATUS_POLLS 20
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;
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;
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;
};
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 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;
response.type = QA6_USB_MESSAGE_RESPONSE;
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_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;
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);
qemu_log_mask(LOG_UNIMP,
"s5l8950x.usb-bridge: EP0 request %u complete "
"(status=%d, bytes=%u)\n",
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_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;
}
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;
s->regs[USB_OTG_DOEPTSIZ0 / 4] &= ~0x7f;
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.type != QA6_USB_MESSAGE_REQUEST ||
request.payload_length > QA6_USB_MAX_PAYLOAD ||
expected_payload != s->bridge_rx_len ||
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");
if (request.magic == QA6_USB_MAGIC) {
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_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;
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");
}
}
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)
{
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;
qemu_log_mask(LOG_UNIMP,
"s5l8950x.usb-host: enumeration complete; DFU control "
"interface ready\n");
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;
s->regs[USB_OTG_DOEPTSIZ0 / 4] &= ~0x7f;
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_finish_statuses < 2) {
return USB_HOST_WAIT_DFU_GETSTATUS_SETUP;
}
qemu_log_mask(LOG_UNIMP,
"s5l8950x.usb-host: DFU image handed to SecureROM "
"(%zu bytes, final state=%u)\n",
s->dfu_image_size, s->dfu_state);
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 (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 (offset == USB_OTG_GINTMSK &&
(value & USB_OTG_GINT_USBRST) && !s->usb_reset_sent) {
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 ((value & USB_OTG_EPCTL_STALL) &&
s->bridge_request_active) {
s5l8950x_usb_bridge_finish(s, QA6_USB_STATUS_STALL);
} else 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) {
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 ((value & USB_OTG_EPCTL_STALL) &&
s->bridge_request_active) {
s5l8950x_usb_bridge_finish(s, QA6_USB_STATUS_STALL);
} else 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 ((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: SecureROM stalled EP0; "
"DFU image rejected after block %u\n",
s->dfu_packet_block);
} 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);
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->host_stage = USB_HOST_WAIT_DEVICE_SETUP;
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->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->dfu_packet, 0, sizeof(s->dfu_packet));
memset(s->dfu_status, 0, sizeof(s->dfu_status));
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));
s5l8950x_usb_otg_update_irq(s);
}
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 EP0 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);
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);
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);
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),
};
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)