1373 lines
50 KiB
C
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)
|