From 6ca0e28ef6bc2bd1b3ff1b63fac0a747ecbb783e Mon Sep 17 00:00:00 2001 From: Amaury Pouly Date: Wed, 29 Oct 2025 18:04:50 +0100 Subject: [PATCH] [ot] hw/opentitan: ot_usbdev: Implementation data transfers This commit implements the logic to support data transfers and wire them in the server. The protocol is updated with commands to support this. Isochronous transfers are not properly supported at the moment. Signed-off-by: Amaury Pouly --- docs/opentitan/usbdev.md | 238 +++++-- hw/opentitan/ot_usbdev.c | 1276 +++++++++++++++++++++++++++++++++++-- hw/opentitan/trace-events | 12 + 3 files changed, 1422 insertions(+), 104 deletions(-) diff --git a/docs/opentitan/usbdev.md b/docs/opentitan/usbdev.md index 64eece11b3508..004a8603424a7 100644 --- a/docs/opentitan/usbdev.md +++ b/docs/opentitan/usbdev.md @@ -4,8 +4,8 @@ ## `usbdev-cmd` Chardev -The USBDEV driver exposes a chardev with ID `usbdev-cmd` which can used to control some aspects of the -emulation. +The USBDEV driver exposes a chardev with ID `usbdev-cmd` which can used to control some aspects of +the emulation. Once connected, the driver accepts textual commands. Each command must end with a newline. The following commands are recognized: @@ -14,18 +14,21 @@ The following commands are recognized: ## `usbdev-host` Chardev -The USBDEV driver exposes a chardev with ID `usbdev-host` which can used to simulate the presence of -a USB host. The implementation is such that the USBDEV behaves as a server, waiting for a connection -from a virtual host. Once connected, the host and device exchange binary commands following the protocol -described in the [host simulation](#host-simulation) section. +The USBDEV driver exposes a chardev with ID `usbdev-host` which can used to simulate the presence +of a USB host. +The implementation is such that the USBDEV behaves as a server, waiting for a connection from a +virtual host. +Once connected, the host and device exchange binary commands following the protocol described in +the [host simulation](#host-simulation) section. ## VBUS handling -On a real machine, the VBUS sense pin is usually connected to the VBUS connector so -that the chip can detect when a cable is plugged in. For the purpose of emulation, a different -approach needs to be taken. The driver supports two modes of operation which are controlled -by the `vbus-override` property which can be set on the command-line by -`-global ot-usbdev.vbus-override=`. The following modes are supported: +On a real machine, the VBUS sense pin is usually connected to the VBUS connector so that the chip +can detect when a cable is plugged in. +For the purpose of emulation, a different approach needs to be taken. The driver supports two modes +of operation which are controlled by the `vbus-override` property which can be set on the +command-line by `-global ot-usbdev.vbus-override=`. +The following modes are supported: - `vbus-override=on`: in this mode, the VBUS sense pin is entirely managed over the `usbdev` chardev. @@ -40,9 +43,11 @@ by the `vbus-override` property which can be set on the command-line by ## Host simulation The QEMU USBDEV driver only simulates a USB device controller (UDC) which requires the presence -of a USB host. A (virtual) host exchanges messages with the UDC over the [`udev-host` chardev](#usbdev-host-chardev). -This binary protocol is somewhat similar to the [USB/IP][usbip] -protocol but lower level. This protocol is also different from the [USB network redirection (a.k.a usbredir)][usbredir] +of a USB host. +A (virtual) host exchanges messages with the UDC over the +[`udev-host` chardev](#usbdev-host-chardev). +This binary protocol is somewhat similar to the [USB/IP][usbip] protocol but lower level. +This protocol is also different from the [USB network redirection (a.k.a usbredir)][usbredir] protocol supported by QEMU. [usbip]: (https://docs.kernel.org/usb/usbip_protocol.html) @@ -50,46 +55,56 @@ protocol supported by QEMU. ### Rationale for a different protocol -Both the USB/IP and usbredir protocols are too high-level for the purpose of emulating and testing a low-level -UDC driver. For example, both protocols require that the device be fully enumerated and configured before the -host is even made aware of its presence. In contrast, we want to be able to fully emulate the enumeration sequence. -Another big difference is bus management: USB/IP does not support bus resets and usbredir does not support resume/suspend, -and neither models VBUS. This is a side-effect of the intended use case of these protocols: to connect a real USB device -to a virtual USB host. However, this new protocol should be low-level enough that it is possible to implement a -bridge from either usbredir or USB/IP to this protocol (while losing some features). +Both the USB/IP and usbredir protocols are too high-level for the purpose of emulating and testing +a low-level UDC driver. +For example, both protocols require that the device be fully enumerated and configured before the +host is even made aware of its presence. +In contrast, we want to be able to fully emulate the enumeration sequence. +Another big difference is bus management: USB/IP does not support bus resets and usbredir does not +support resume/suspend, and neither models VBUS. +This is a side-effect of the intended use case of these protocols: to connect a real USB device to +a virtual USB host. +However, this new protocol should be low-level enough that it is possible to implement a bridge +from either usbredir or USB/IP to this protocol (while losing some features). ### High-level overview This protocol does not specify which of the device or the host should be the client/guest. -On connection, the client must send a [`Hello` command](#hello-command), to which the server -must respond with another `Hello` command. After that, messages are exchanged asynchronously. +On connection, the client must send a [`Hello` command](#hello-command), to which the server must +respond with another `Hello` command. +After that, messages are exchanged asynchronously. #### Bus states -The protocol reflects the low-level details of the USB bus and provides independent -handling of VBUS (controlled by the host) and connection (controlled by the device). -The host can turn VBUS on and off at any point. When VBUS is on, the device can freely -connect or disconnect by asynchronously sending a message to the host. As on a real bus, -turning off VBUS disconnects the device. VBUS is assumed to be initially off. +The protocol reflects the low-level details of the USB bus and provides independent handling of +VBUS (controlled by the host) and connection (controlled by the device). +The host can turn VBUS on and off at any point. When VBUS is on, the device can freely connect or +disconnect by asynchronously sending a message to the host. +As on a real bus, turning off VBUS disconnects the device. +VBUS is assumed to be initially off. It is an error for the device to send a message when VBUS is not on and the host must ignore such -messages. However due to the asynchronous nature of the protocol, it is possible for the host to -receive a message event *after* sending a `VBUS Off` message which was sent *before* reception of -this message by the device. The ID of the message makes it clear when this is the case so that -the host can safely ignore those messages. +messages. +However due to the asynchronous nature of the protocol, it is possible for the host to receive a +message event *after* sending a `VBUS Off` message which was sent *before* reception of this +message by the device. +The ID of the message makes it clear when this is the case so that the host can safely ignore those +messages. -When VBUS is turned on, the host assumes that the device is *not* connected. The device must -send a `Connect` message to notify the host. +When VBUS is turned on, the host assumes that the device is *not* connected. The device must send +a `Connect` message to notify the host. -While VBUS is turned on, the host can reset, suspend or resume the device. As on a real bus, -the device will become unconfigured after a bus reset and an enumeration sequence must be performed. +While VBUS is turned on, the host can reset, suspend or resume the device. +As on a real bus, the device will become unconfigured after a bus reset and an enumeration sequence +must be performed. **TODO** Clarify suspend/resume ### Packet format -The protocol is based on packets which are exchanged asynchronously by the device and host. Each packet starts -with a header followed by a payload. All fields are in little-endian. +The protocol is based on packets which are exchanged asynchronously by the device and host. +Each packet starts with a header followed by a payload. +All fields are in little-endian. | **Field** | **Offset** | **Size** | **Value** | | --------- | ---------- | -------- | --------- | @@ -105,7 +120,7 @@ The following commands are defined. | **Command** | **Value** | **Direction** | **Description** | **Reference** | | ----------- | --------- | ------------- | --------------- | ------------- | | Invalid | 0 | N/A | To avoid 0 meaning something | | -| Hello | 1 | Both | First packet sent when connecting | [`Hello` command](#hello-command) | +| Hello | 1 | Both | First packet sent when connecting | [Hello command](#hello-command) | | VBUS On | 2 | Host to device | Turn VBUS on | [Bus commands](#bus-commands) | | VBUS Off | 3 | Host to device | Turn VBUS off | [Bus commands](#bus-commands) | | Connect | 4 | Device to host | Report device connection | [Bus commands](#bus-commands) | @@ -113,8 +128,11 @@ The following commands are defined. | Reset | 6 | Host to device | Reset the device | [Bus commands](#bus-commands) | | Resume | 7 | Host to device | Resume the device | [Bus commands](#bus-commands) | | Suspend | 8 | Host to device | Suspend the device | [Bus commands](#bus-commands) | +| Setup | 9 | Host to device | Send a SETUP packet | [Setup command](#setup-command) | +| Transfer | 10 | Host to device | Start a transfer | [Transfer command](#transfer-command) | +| Complete | 11 | Device to host | Complete a transfer | [Complete command](#complete-command) | + -**TODO** Add transfer commands #### Hello command @@ -132,5 +150,141 @@ The payload of this command is defined as follows. These commands (VBUS On/Off, (Dis)connection, Reset, Suspend/Resume) do not have any payload. See the [bus states](#bus-states) section for more detail. -For the Connect/Disconnect commands, which are sent by the device, the ID should be the ID of the *last command* -processed by the device. +For the Connect/Disconnect commands, which are sent by the device, the ID should be the ID of the +*last command* processed by the device. + +Sending a `Reset`, `VBUS Off` or `Suspend` command to the device automatically cancels any pending +`Transfer` command. +The device *may* send a `Complete` event with the `Cancelled` status for such transfers. + +#### Setup command + +This command is used to send a SETUP packet to the device. +The payload of this command starts with a header defined as follows. +See [Transfer handling](#transfer-handling) for more details. + +| **Field** | **Offset** | **Size** | **Value** | +| --------- | ---------- | -------- | --------- | +| Address | 0 | 1 | Device address | +| Endpoint | 1 | 1 | Endpoint number | +| Reserved | 2 | 2 | Set to zero | +| Setup | 4 | 8 | SETUP packet | + +Sending a `Setup` command to an endpoint automatically cancels any pending transfer on this +endpoint. +The device *may* send a `Complete` event to the host with the `Cancelled` status. + +#### Transfer command + +This command is used to start a transfer to or from the device. +See [Transfer handling](#transfer-handling) for more details. +The payload of this command is defined below. + +| **Field** | **Offset** | **Size** | **Value** | +| --------- | ---------- | -------- | --------- | +| Address | 0 | 1 | Device address | +| Endpoint | 1 | 1 | Endpoint (see below) | +| Packet Size | 2 | 2 | Packet size (see [Transfer handling](#transfer-handling)) | +| Flags | 4 | 1 | Transfer flags (see below) | +| Reserved | 5 | 3 | Set to 0 | +| Transfer Size | 8 | 4 | Size of the data | +| Data | 12 | variable | Transfer data (see below) | + +The `Endpoint` field is encoded as follows: +| **Field** | **Bits** | **Value** | +| --------- | -------- | -------- | +| EP Number | 3:0 | The endpoint number | +| Reserved | 6:4 | Set to 0 | +| Direction | 7 | Set to 1 for IN endpoint and 0 for OUT endpoint | + +Note that when submitting a control transfer, the `Direction` field must be set to indicate whether +this is control OUT or IN transaction. + +The following flags are defined. + +| **Flag** | **Bit** | **Meaning** | +| -------- | ------- | ----------- | +| ZLP | 0 | A zero-length packet must be sent after the data (only valid for OUT transfers) | + +For OUT transfers, the `Transfer Size` field indicates the size of the data in the payload after +the header. +For IN transfers, the `Transfer Size` field indicates the maximum size of the transfers to return +to the host. + +Sending a `Transfer` command to an endpoint while one is already in progress is an error. +The device must immediately reply with a `Complete` event with the `Error` status. + +#### Complete command + +The ID of this command must be the ID of the corresponding `Transfer` command. +See [Transfer handling](#transfer-handling) for more details. +The payload of this command is defined as follows. + +| **Field** | **Offset** | **Size** | **Value** | +| --------- | ---------- | -------- | --------- | +| Status | 0 | 1 | Status of the transfer (see below) | +| Reserved | 1 | 3 | Set to 0 | +| Transfer Size | 4 | 4 | Amount of data transferred | +| Data | 8 | variable | Transfer data (see below) | + +The `Transfer Size` field indicates the size of the data. +The data must only be present when completing an IN transaction. + +The following status codes are defined: + +| **Status** | **Value** | **Meaning** | +| -------- | ------- | ----------- | +| Success | 0 | The data was successfully transferred | +| Stalled | 1 | The device stalled the transfer | +| Cancelled | 2 | Transfer was cancelled (see below) | +| Error | 3 | An unspecified error occurred | + +A transfer may be cancelled by any bus event such a turning VBUS off, the device disconnecting, +the host resetting or suspending the device, or the host sending a `Setup` command. + + +### Transfer handling + +In order to perform data transfers, the host must send a correct sequence of Setup and Data +commands, in accordance to the USB specification. +The protocol is designed to be very low-level and only provides minimal help. +The host is allowed to send non-spec compliant sequences to fuzz the device. +For properly sequenced transfers, the host should follow the guidelines below. + +Note that the `Transfer` command allows the host to send/receive more data than the indicated +packet size. +In this case, the transfer will automatically be split into multiple packets of size `Packet Size`. +For OUT transfers, all packets except possibly the last one will be of the size indicated in the +command. +For IN transfers, the transfer stops as soon as a short packet is received (packet of size less +than the one indicated in the command). +If the `ZLP` flag is set for an OUT transfer, the device will additionally receive a zero-length +packet. +If at any point during this sequence, the device stalls the transfer, the transfer stops and the +device must send a `Complete` event to the host with the `Stalled` status and the `Size` field +indicating how much data was transferred successfully (and for IN transfers, the data must be +present in the payload). +If the transfer succeeds, the device must send a `Complete` event with the `Success` status, the +`Size` field indicating how much data was transferred successfully (and for IN transfers, the data +must be present in the payload). + +### Control transfers + +The host should first send a `Setup` command at the selected address and endpoint. +The device does not reply to this command. Note that the USB specification specifies that sending +a SETUP packet to an endpoint immediately cancels any transaction on this endpoint. +The device *may* send a `Complete` events for such transfers with the `Cancelled` status. + +The rest of the sequence depends on the direction of the transfer: +- OUT transfers: the host should send one or more `Transfer` commands to the endpoint + (with the OUT direction) with the control data. + Once all transfers are complete, the host should send a `Transfer` command to the endpoint + with the IN direction and `Size` set to 0. +- IN transfers: the host should send one or more `Transfer` commands to the endpoint, + (with the IN direction) to receive the control data. + Once all transfers are complete, the host should send a `Transfer` command to the endpoint + with the OUT direction and `Size` set to 0. + +### Bulk and interrupt transfers + +TODO diff --git a/hw/opentitan/ot_usbdev.c b/hw/opentitan/ot_usbdev.c index 9061b9671047a..91c1119cc611d 100644 --- a/hw/opentitan/ot_usbdev.c +++ b/hw/opentitan/ot_usbdev.c @@ -34,9 +34,11 @@ */ #include "qemu/osdep.h" +#include "qemu/fifo8.h" #include "qemu/log.h" #include "chardev/char-fe.h" #include "hw/opentitan/ot_alert.h" +#include "hw/opentitan/ot_fifo32.h" #include "hw/opentitan/ot_usbdev.h" #include "hw/qdev-properties-system.h" #include "hw/qdev-properties.h" @@ -50,6 +52,19 @@ #define USBDEV_PARAM_NUM_ALERTS 1u #define USBDEV_PARAM_REG_WIDTH 32u +/* + * The following are not parameters of the IP but hardcoded + * constants in the RTL. + */ +#define OT_USBDEV_RX_FIFO_DEPTH 8u +#define OT_USBDEV_AV_SETUP_FIFO_DEPTH 4u +#define OT_USBDEV_AV_OUT_FIFO_DEPTH 8u +#define OT_USBDEV_MAX_PACKET_SIZE 64u +#define OT_USBDEV_BUFFER_COUNT 32u + +/* Standard constants */ +#define OT_USBDEV_SETUP_PACKET_SIZE 8u + /* NOLINTNEXTLINE(misc-redundant-expression) */ static_assert(USBDEV_PARAM_N_ENDPOINTS == 12u, "This driver only supports 12 endpoints"); @@ -243,6 +258,8 @@ REG32(COUNT_ERRORS, 0xa8u) #define USBDEV_USBCTRL_R_MASK \ (R_USBCTRL_ENABLE_MASK | R_USBCTRL_DEVICE_ADDRESS_MASK) +#define USBDEV_CONFIGIN_RW1C_MASK (CONFIGIN_PEND_MASK | CONFIGIN_SENDING_MASK) + static_assert(USBDEV_INTR_MASK == (1u << USBDEV_INTR_NUM) - 1u, "Interrupt mask mismatch"); @@ -251,6 +268,10 @@ static_assert(USBDEV_INTR_MASK == (1u << USBDEV_INTR_NUM) - 1u, #define USBDEV_BUFFER_OFFSET 0x800u #define USBDEV_BUFFER_SIZE 0x800u +static_assert(OT_USBDEV_MAX_PACKET_SIZE * OT_USBDEV_BUFFER_COUNT == + USBDEV_BUFFER_SIZE, + "USBDEV buffer size mismatch"); + /* * Link state: this is the register field value, which we also use to * track the state of the link in general. @@ -328,6 +349,9 @@ typedef enum { OT_USBDEV_SERVER_CMD_RESET, OT_USBDEV_SERVER_CMD_RESUME, OT_USBDEV_SERVER_CMD_SUSPEND, + OT_USBDEV_SERVER_CMD_SETUP, + OT_USBDEV_SERVER_CMD_TRANSFER, + OT_USBDEV_SERVER_CMD_COMPLETE, } OtUsbdevServerCmd; typedef struct { @@ -336,6 +360,9 @@ typedef struct { uint32_t id; /* Unique ID */ } OtUsbdevServerPktHdr; +static_assert(sizeof(OtUsbdevServerPktHdr) == 12u, + "Packet header has the wrong size"); + #define OT_USBDEV_SERVER_HELLO_MAGIC "UDCX" #define OT_USBDEV_SERVER_MAJOR_VER 1u #define OT_USBDEV_SERVER_MINOR_VER 0u @@ -346,6 +373,69 @@ typedef struct { uint16_t minor_version; } OtUsbdevServerHelloPkt; +static_assert(sizeof(OtUsbdevServerHelloPkt) == 8u, + "Hello packet has the wrong size"); + +typedef struct { + uint8_t address; + uint8_t endpoint; + uint8_t reserved[2]; + uint8_t setup[OT_USBDEV_SETUP_PACKET_SIZE]; +} OtUsbdevServerSetupPkt; + +static_assert(sizeof(OtUsbdevServerSetupPkt) == 12u, + "Setup packet has the wrong size"); + +#define OT_USBDEV_EP_DIR_IN 0x80u +#define OT_USBDEV_EP_NUM_MASK 0x7fu + +typedef enum { + OT_USBDEV_SERVER_FLAG_ZLP = 1u << 0u, +} OtUsbdevServerTransferFlags; + +typedef struct { + uint8_t address; + uint8_t endpoint; + uint16_t packet_size; + uint8_t flags; + uint8_t reserved[3]; + uint32_t transfer_size; +} OtUsbdevServerTransferPkt; + +static_assert(sizeof(OtUsbdevServerTransferPkt) == 12u, + "Transfer packet has the wrong size"); + +typedef enum { + OT_USBDEV_SERVER_STATUS_SUCCESS, + OT_USBDEV_SERVER_STATUS_STALLED, + OT_USBDEV_SERVER_STATUS_CANCELLED, + OT_USBDEV_SERVER_STATUS_ERROR, +} OtUsbdevServerTransferStatus; + +#define XFER_STATUS_ENTRY(_name) \ + [OT_USBDEV_SERVER_STATUS_##_name] = stringify(_name) + +static const char *XFER_STATUS_NAME[] = { + /* clang-format off */ + XFER_STATUS_ENTRY(SUCCESS), + XFER_STATUS_ENTRY(STALLED), + XFER_STATUS_ENTRY(CANCELLED), + XFER_STATUS_ENTRY(ERROR), + /* clang-format on */ +}; +#undef XFER_STATUS_ENTRY + +#define XFER_STATUS_NAME(_st_) \ + (((unsigned)(_st_)) < ARRAY_SIZE(XFER_STATUS_NAME) ? \ + XFER_STATUS_NAME[(_st_)] : \ + "?") + +typedef struct { + uint8_t status; + uint8_t reserved[3]; + uint32_t transfer_size; +} OtUsbdevServerCompletePkt; + /* State machine for the server receive path */ typedef enum { /* Wait for packet header */ @@ -353,6 +443,32 @@ typedef enum { OT_USBDEV_SERVER_RECV_WAIT_DATA, } OtUsbdevServerRecvState; +typedef struct { + bool pending; /* Is there a transfer pending? */ + uint32_t id; /* ID of the transfer */ + uint32_t xfer_len; /* Maximum length of the transfer */ + uint8_t *xfer_buf; /* Buffer allocated to hold the data */ + uint16_t max_packet_size; /* Maximum packet size */ + uint32_t recv_rem; /* Remaining quantity to receive */ + uint8_t *recv_buf; /* Pointer to buffer where to receive */ +} OtUsbdevServerEpInXfer; + +typedef struct { + bool pending; /* Is there a transfer pending? */ + bool send_zlp; /* Send a ZLP */ + uint32_t id; /* ID of the transfer */ + uint32_t xfer_len; /* Length of the transfer */ + uint8_t *xfer_buf; /* Buffer allocated to hold the data */ + uint16_t max_packet_size; /* Maximum packet size */ + uint32_t send_rem; /* Remaining quantity to send */ + uint8_t *send_buf; /* Pointer to buffer where to send */ +} OtUsbdevServerEpOutXfer; + +typedef struct { + OtUsbdevServerEpInXfer in; + OtUsbdevServerEpOutXfer out; +} OtUsbdevServerEpXfer; + typedef struct { /* Current state of the receiver */ OtUsbdevServerRecvState recv_state; @@ -363,6 +479,9 @@ typedef struct { /* Packet data under reception or processing */ uint8_t *recv_data; + /* endpoint transfers in progress */ + OtUsbdevServerEpXfer ep[USBDEV_PARAM_N_ENDPOINTS]; + /* Current state of server */ bool client_connected; /* We have a client */ bool vbus_connected; /* The host has turned on VBUS */ @@ -384,6 +503,18 @@ struct OtUsbdevState { /* VBUS gate: meaning depends on the vbus_override mode */ bool vbus_gate; + /* + * Content of the RX FIFO: each entry is encoded like the + * RXFIFO register. + */ + OtFifo32 rx_fifo; + /* + * Content of the available SETUP and OUT buffer FIFOs: + * each entry is a buffer ID. + */ + Fifo8 av_setup_fifo; + Fifo8 av_out_fifo; + /* Buffer content */ uint32_t buffer[USBDEV_BUFFER_SIZE / sizeof(uint32_t)]; @@ -463,12 +594,51 @@ static const char *REG_NAMES[REGS_COUNT] = { * Forward definitions */ static void ot_usbdev_server_report_connected(OtUsbdevState *s, bool connected); +static void ot_usbdev_server_complete_transfer( + OtUsbdevState *s, uint32_t id, OtUsbdevServerTransferStatus status, + uint32_t xfer_size, const uint8_t *data, uint32_t data_size, + const char *msg); +static void ot_usbdev_complete_transfer_in(OtUsbdevState *s, uint8_t epnum, + OtUsbdevServerTransferStatus status, + const char *msg); +static void ot_usbdev_complete_transfer_out(OtUsbdevState *s, uint8_t epnum, + OtUsbdevServerTransferStatus status, + const char *msg); +static bool ot_usbdev_is_enabled(const OtUsbdevState *s); + +/* + * Update the INTR_STATE register for status interrupts. + */ +static void ot_usbdev_update_status_irqs(OtUsbdevState *s) +{ + uint32_t set_mask = 0u; + if (ot_usbdev_is_enabled(s)) { + if (s->regs[R_IN_SENT]) { + set_mask |= USBDEV_INTR_PKT_SENT_MASK; + } + if (fifo8_is_empty(&s->av_setup_fifo)) { + set_mask |= USBDEV_INTR_AV_SETUP_EMPTY_MASK; + } + if (fifo8_is_empty(&s->av_out_fifo)) { + set_mask |= USBDEV_INTR_AV_OUT_EMPTY_MASK; + } + if (ot_fifo32_is_full(&s->rx_fifo)) { + set_mask |= USBDEV_INTR_RX_FULL_MASK; + } + if (!ot_fifo32_is_empty(&s->rx_fifo)) { + set_mask |= USBDEV_INTR_PKT_RECEIVED_MASK; + } + } + s->regs[R_USBDEV_INTR_STATE] |= set_mask; +} /* - * State handling + * Update the IRQs at the Ibex given the content of the INTR_* + * register. */ static void ot_usbdev_update_irqs(OtUsbdevState *s) { + ot_usbdev_update_status_irqs(s); uint32_t state_masked = s->regs[R_USBDEV_INTR_STATE] & s->regs[R_USBDEV_INTR_ENABLE]; @@ -481,6 +651,10 @@ static void ot_usbdev_update_irqs(OtUsbdevState *s) } } +/* + * Update the alerts at the Ibex given the content of the ALERT_TEST + * register. + */ static void ot_usbdev_update_alerts(OtUsbdevState *s) { uint32_t level = s->regs[R_ALERT_TEST]; @@ -499,7 +673,7 @@ static void ot_usbdev_update_alerts(OtUsbdevState *s) * * @return true if enabled (USBCTRL.ENABLE is set). */ -static bool ot_usbdev_is_enabled(const OtUsbdevState *s) +bool ot_usbdev_is_enabled(const OtUsbdevState *s) { /* * Note: this works even if the device is in reset because @@ -534,6 +708,100 @@ static OtUsbdevLinkState ot_usbdev_get_link_state(const OtUsbdevState *s) return (OtUsbdevLinkState)v; } +/* + * Return the current device address. + * + * @return value of USBCTRL.DEVICE_ADDRESS + */ +static uint8_t ot_usbdev_get_address(const OtUsbdevState *s) +{ + return (uint8_t)FIELD_EX32(s->regs[R_USBCTRL], USBCTRL, DEVICE_ADDRESS); +} + +/* + * Determine whether an OUT endpoint is enabled. + * + * @ep endpoint number + * @return value of ep_out_enable.enable_ + */ +static bool ot_usbdev_is_ep_out_enabled(const OtUsbdevState *s, uint8_t ep) +{ + g_assert(ep < USBDEV_PARAM_N_ENDPOINTS); + return (bool)((s->regs[R_EP_OUT_ENABLE] >> ep) & 1u); +} + +/* + * Determine whether an OUT endpoint can receive a packet. + * + * @ep endpoint number + * @return value of rxenable_out.out_ + */ +static bool ot_usbdev_is_ep_out_rxenabled(const OtUsbdevState *s, uint8_t ep) +{ + g_assert(ep < USBDEV_PARAM_N_ENDPOINTS); + return (bool)((s->regs[R_RXENABLE_OUT] >> ep) & 1u); +} + +/* + * Determine whether RX reception is enabled on an endpoint. + * + * @ep endpoint number + * @return value of rxenable_setup.setup_ + */ +static bool ot_usbdev_is_ep_setup_rx_enabled(const OtUsbdevState *s, uint8_t ep) +{ + g_assert(ep < USBDEV_PARAM_N_ENDPOINTS); + return (bool)((s->regs[R_RXENABLE_SETUP] >> ep) & 1u); +} + +/* + * Determine whether an IN endpoint is enabled. + * + * @ep endpoint number + * @return value of ep_in_enable.enable_ + */ +static bool ot_usbdev_is_ep_in_enabled(const OtUsbdevState *s, uint8_t ep) +{ + g_assert(ep < USBDEV_PARAM_N_ENDPOINTS); + return (bool)((s->regs[R_EP_IN_ENABLE] >> ep) & 1u); +} + +/* + * Determine whether an IN endpoint is stalled. + * + * @ep endpoint number + * @return value of in_stall.endpoint_ + */ +static bool ot_usbdev_is_ep_in_stalled(const OtUsbdevState *s, uint8_t ep) +{ + g_assert(ep < USBDEV_PARAM_N_ENDPOINTS); + return (bool)((s->regs[R_IN_STALL] >> ep) & 1u); +} + +/* + * Determine whether an OUT endpoint is stalled. + * + * @ep endpoint number + * @return value of out_stall.endpoint_ + */ +static bool ot_usbdev_is_ep_out_stalled(const OtUsbdevState *s, uint8_t ep) +{ + g_assert(ep < USBDEV_PARAM_N_ENDPOINTS); + return (bool)((s->regs[R_OUT_STALL] >> ep) & 1u); +} + +/* + * Determine whether an OUT endpoint has "auto NAK" enabled. + * + * @ep endpoint number + * @return value of set_nak_out.endpoint_ + */ +static bool ot_usbdev_is_set_nak_out_enabled(const OtUsbdevState *s, uint8_t ep) +{ + g_assert(ep < USBDEV_PARAM_N_ENDPOINTS); + return (bool)((s->regs[R_SET_NAK_OUT] >> ep) & 1u); +} + /* * Change the link state in the USBSTAT register and trace change. * @@ -553,6 +821,115 @@ ot_usbdev_set_raw_link_state(OtUsbdevState *s, OtUsbdevLinkState state) FIELD_DP32(s->regs[R_USBSTAT], USBSTAT, LINK_STATE, (uint32_t)state); } +/* + * Write the content of a USB buffer. + * + * @buf_id ID of the buffer + * @buf Buffer holding the data + * @size How much data to copy + */ +static void ot_usbdev_write_buffer(OtUsbdevState *s, uint8_t buf_id, + const uint8_t *data, size_t size) +{ + g_assert(size <= OT_USBDEV_MAX_PACKET_SIZE); + g_assert(buf_id < OT_USBDEV_BUFFER_COUNT); + + /* + * See BFM (write_pkt_bytes). + * + * The buffer is stored as words, which means that if the packet size + * is not a multiple of 4, the RTL will zero out the bytes at the end + * of the last word. + * + * This function does not fully emulate the complex behaviour w.r.t + * the CRC being written to the buffer or data being remembered in + * the flops (see BFM). + */ + uint32_t *ptr = + &s->buffer[buf_id * OT_USBDEV_MAX_PACKET_SIZE / sizeof(uint32_t)]; + while (size >= 4u) { + *ptr++ = ldl_le_p(data); + data += sizeof(uint32_t); + size -= 4u; + } + if (size == 0u) { + return; + } + /* Handle last incomplete word */ + uint32_t val32 = 0u; + val32 |= *data++; + if (size >= 2u) { + val32 |= *data++ << 8u; + } + if (size >= 3u) { + val32 |= *data++ << 16u; + } + *ptr++ = val32; +} + +/* + * Read the content of a USB buffer. + * + * @buf_id ID of the buffer + * @buf Buffer where to copy the data + * @size How much data to copy + */ +static void ot_usbdev_read_buffer(OtUsbdevState *s, uint8_t buf_id, + uint8_t *buf, size_t size) +{ + g_assert(size <= OT_USBDEV_MAX_PACKET_SIZE); + g_assert(buf_id < OT_USBDEV_BUFFER_COUNT); + + /* + * The buffer is stored as words, we need to emulate that. Could be + * optimized. + */ + uint32_t *ptr = + &s->buffer[buf_id * OT_USBDEV_MAX_PACKET_SIZE / sizeof(uint32_t)]; + while (size >= 4u) { + stl_le_p(buf, *ptr++); + buf += sizeof(uint32_t); + size -= 4u; + } + /* Handle last incomplete word */ + if (size >= 1u) { + uint32_t val32 = *ptr; + *buf++ = (uint8_t)val32; + val32 >>= 8u; + if (size >= 2u) { + *buf++ = (uint8_t)val32; + val32 >>= 8u; + } + if (size >= 3u) { + *buf++ = (uint8_t)val32; + } + } +} + +/* + * Update the content of the USBSTAT buffer to reflect the FIFO state + * and update the corresponding IRQs. + */ +static void ot_usbdev_update_fifos_status(OtUsbdevState *s) +{ + uint32_t val = s->regs[R_USBSTAT]; + val = FIELD_DP32(val, USBSTAT, RX_EMPTY, + (uint32_t)ot_fifo32_is_empty(&s->rx_fifo)); + val = FIELD_DP32(val, USBSTAT, AV_SETUP_FULL, + (uint32_t)fifo8_is_full(&s->av_setup_fifo)); + val = FIELD_DP32(val, USBSTAT, RX_DEPTH, + (uint32_t)ot_fifo32_num_used(&s->rx_fifo)); + val = FIELD_DP32(val, USBSTAT, AV_OUT_FULL, + (uint32_t)fifo8_is_full(&s->av_out_fifo)); + val = FIELD_DP32(val, USBSTAT, AV_SETUP_DEPTH, + (uint32_t)fifo8_num_used(&s->av_setup_fifo)); + val = FIELD_DP32(val, USBSTAT, AV_OUT_DEPTH, + (uint32_t)fifo8_num_used(&s->av_out_fifo)); + s->regs[R_USBSTAT] = val; + + ot_usbdev_update_irqs(s); +} + /* * Update the device state after a potential VBUS change. * @@ -632,22 +1009,42 @@ static void ot_usbdev_set_vbus_gate(OtUsbdevState *s, bool gate) ot_usbdev_update_vbus(s); } +/* + * Retire any IN packet on the given endpoint. + * + * Mark any ready packet as pending but not ready and not sending. + */ +static void ot_usbdev_retire_in_packets(OtUsbdevState *s, uint8_t ep) +{ + g_assert(ep < USBDEV_PARAM_N_ENDPOINTS); + + uint32_t configin = s->regs[R_CONFIGIN_0 + ep]; + if ((bool)SHARED_FIELD_EX32(configin, CONFIGIN_RDY)) { + configin = SHARED_FIELD_DP32(configin, CONFIGIN_RDY, 0u); + configin = SHARED_FIELD_DP32(configin, CONFIGIN_PEND, 1u); + + trace_ot_usbdev_retire_in_packet(s->ot_id, ep); + } + configin = SHARED_FIELD_DP32(configin, CONFIGIN_SENDING, 0u); + s->regs[R_CONFIGIN_0 + ep] = configin; +} + /* * Simulate a link reset. * * This function will trigger all necessary state and IRQs changes necessary to * perform a link reset. - * - * @todo document what happens to transfers when done */ static void ot_usbdev_simulate_link_reset(OtUsbdevState *s) { g_assert(!resettable_is_in_reset(OBJECT(s))); + trace_ot_usbdev_link_reset(s->ot_id); + /* We cannot simulate a reset if the device is disconnected! */ if (ot_usbdev_get_link_state(s) == OT_USBDEV_LINK_STATE_DISCONNECTED) { - error_report("%s: %s Link reset while disconnected?!", __func__, - s->ot_id); + trace_ot_usbdev_server_protocol_error(s->ot_id, + "link reset while disconnected"); return; } @@ -655,15 +1052,395 @@ static void ot_usbdev_simulate_link_reset(OtUsbdevState *s) s->regs[R_USBCTRL] = FIELD_DP32(s->regs[R_USBCTRL], USBCTRL, DEVICE_ADDRESS, 0u); s->regs[R_USBDEV_INTR_STATE] |= USBDEV_INTR_LINK_RESET_MASK; - /* @todo cancel all transfers */ + + for (unsigned ep = 0; ep < USBDEV_PARAM_N_ENDPOINTS; ep++) { + /* Cancel any pending IN packets */ + ot_usbdev_retire_in_packets(s, ep); + /* Cancel all pending transfers on the server */ + ot_usbdev_complete_transfer_in(s, ep, OT_USBDEV_SERVER_STATUS_CANCELLED, + "cancelled by link reset"); + ot_usbdev_complete_transfer_out(s, ep, + OT_USBDEV_SERVER_STATUS_CANCELLED, + "cancelled by link reset"); + } if (ot_usbdev_has_vbus(s) && ot_usbdev_is_enabled(s)) { - /* @todo BFM has some extra state processing but it seems incorrect */ - ot_usbdev_set_raw_link_state(s, OT_USBDEV_LINK_STATE_ACTIVE_NOSOF); + /* + * @todo BFM has some extra state processing but it seems incorrect + * @todo If we start tracking frames, this should transition the link + * state to ACTIVE_NOSOF and then simulate a transition to ACTIVE on + * the next SOF. + */ + ot_usbdev_set_raw_link_state(s, OT_USBDEV_LINK_STATE_ACTIVE); + } + ot_usbdev_update_irqs(s); +} + +/* + * Simulate the reception of a SETUP token followed by an 8-byte + * DATA packet containing the SETUP packet. + * + * Note: when called, the trace_ot_usbdev_setup event has not been called yet. + */ +static void ot_usbdev_simulate_setup(OtUsbdevState *s, uint8_t ep, + uint8_t setup[OT_USBDEV_SETUP_PACKET_SIZE]) +{ + g_assert(!resettable_is_in_reset(OBJECT(s))); + g_assert(ep < USBDEV_PARAM_N_ENDPOINTS); + + /* We cannot simulate a reset if the device is disconnected! */ + if (ot_usbdev_get_link_state(s) == OT_USBDEV_LINK_STATE_DISCONNECTED) { + trace_ot_usbdev_server_protocol_error( + s->ot_id, "SETUP packet while disconnected"); + trace_ot_usbdev_setup(s->ot_id, ep, + "ignored (device is not connected)"); + return; + } + + /* See BFM (token_packet) */ + + /* A SETUP packet resets the data toggles of enabled endpoints */ + if (ot_usbdev_is_ep_out_enabled(s, ep)) { + s->regs[R_OUT_DATA_TOGGLE] &= + ~FIELD_DP32(0u, OUT_DATA_TOGGLE, STATUS, 1u << ep); + } + if (ot_usbdev_is_ep_in_enabled(s, ep)) { + s->regs[R_IN_DATA_TOGGLE] |= + FIELD_DP32(0u, IN_DATA_TOGGLE, STATUS, 1u << ep); + } + + /* Drop SETUP if EP is not enabled or RX is not enabled for SETUP. */ + if (!ot_usbdev_is_ep_out_enabled(s, ep)) { + /* Packet is silently ignored without triggering an error. */ + trace_ot_usbdev_setup(s->ot_id, ep, + "dropped (device has not enabled OUT endpoint)"); + return; + } + + /* See BFM (data_packet and out_packet) */ + if (!ot_usbdev_is_ep_setup_rx_enabled(s, ep)) { + /* Packet is silently ignored without triggering an error. */ + trace_ot_usbdev_setup( + s->ot_id, ep, "dropped (device has not enabled SETUP reception)"); + return; + } + if (fifo8_is_empty(&s->av_setup_fifo) || ot_fifo32_is_full(&s->rx_fifo)) { + trace_ot_usbdev_setup(s->ot_id, ep, + "dropped (AV SETUP FIFO empty or RX FIFO full)"); + s->regs[R_USBDEV_INTR_STATE] |= USBDEV_INTR_LINK_OUT_ERR_MASK; + ot_usbdev_update_irqs(s); + return; } + + trace_ot_usbdev_setup(s->ot_id, ep, "accepted"); + + /* + * Obtain the next SETUP buffer ID and write the SETUP packet to the buffer + */ + uint8_t buf_id = fifo8_pop(&s->av_setup_fifo); + ot_usbdev_write_buffer(s, buf_id, setup, OT_USBDEV_SETUP_PACKET_SIZE); + + /* Create and push an entry in the RX FIFO */ + uint32_t rx_fifo_entry = 0u; + rx_fifo_entry = FIELD_DP32(rx_fifo_entry, RXFIFO, BUFFER, buf_id); + rx_fifo_entry = FIELD_DP32(rx_fifo_entry, RXFIFO, SETUP, 1u); + rx_fifo_entry = FIELD_DP32(rx_fifo_entry, RXFIFO, EP, ep); + rx_fifo_entry = + FIELD_DP32(rx_fifo_entry, RXFIFO, SIZE, OT_USBDEV_SETUP_PACKET_SIZE); + ot_fifo32_push(&s->rx_fifo, rx_fifo_entry); + + /* clear any STALL condition */ + s->regs[R_OUT_STALL] &= ~(1u << ep); + s->regs[R_IN_STALL] &= ~(1u << ep); + + /* Cancel any pending IN packet on this endpoint */ + ot_usbdev_retire_in_packets(s, ep); + + /* Cancel any transfer on the server side on this endpoint */ + ot_usbdev_complete_transfer_in(s, ep, OT_USBDEV_SERVER_STATUS_CANCELLED, + "cancelled by setup packet"); + ot_usbdev_complete_transfer_out(s, ep, OT_USBDEV_SERVER_STATUS_CANCELLED, + "cancelled by setup packet"); + + ot_usbdev_update_fifos_status(s); ot_usbdev_update_irqs(s); } +/* + * Complete an IN transfer and notify the host. + * + * If there is no transfer pending on this endpoint, this function does nothing. + * + * @epnum Endpoint number + * @status Status of the transfer (sent to the host) + * @msg Details on the transfer status (using for tracing only) + */ +void ot_usbdev_complete_transfer_in(OtUsbdevState *s, uint8_t epnum, + OtUsbdevServerTransferStatus status, + const char *msg) +{ + g_assert(epnum < USBDEV_PARAM_N_ENDPOINTS); + OtUsbdevServer *server = &s->usb_server; + OtUsbdevServerEpInXfer *xfer = &server->ep[epnum].in; + + /* Nothing to do if no transfer is pending */ + if (!xfer->pending) { + return; + } + + uint32_t xfered_len = xfer->xfer_len - xfer->recv_rem; + ot_usbdev_server_complete_transfer(s, xfer->id, status, xfered_len, + xfer->xfer_buf, xfered_len, msg); + + /* cleanup */ + g_free(xfer->xfer_buf); + memset(xfer, 0, sizeof(OtUsbdevServerEpInXfer)); +} + +/* + * Complete an OUT transfer and notify the host. + * + * If there is no transfer pending on this endpoint, this function does nothing. + * + * @epnum Endpoint number + * @status Status of the transfer (sent to the host) + * @msg Details on the transfer status (using for tracing only) + */ +void ot_usbdev_complete_transfer_out(OtUsbdevState *s, uint8_t epnum, + OtUsbdevServerTransferStatus status, + const char *msg) +{ + g_assert(epnum < USBDEV_PARAM_N_ENDPOINTS); + OtUsbdevServer *server = &s->usb_server; + OtUsbdevServerEpOutXfer *xfer = &server->ep[epnum].out; + + /* Nothing to do if no transfer is pending */ + if (!xfer->pending) { + return; + } + + uint32_t xfered_len = xfer->xfer_len - xfer->send_rem; + ot_usbdev_server_complete_transfer(s, xfer->id, status, xfered_len, NULL, + 0u, msg); + + /* cleanup */ + g_free(xfer->xfer_buf); + memset(xfer, 0, sizeof(OtUsbdevServerEpOutXfer)); +} + +/* + * Try to make progress with an IN transfer on this endpoint. + * + * This function will check if the necessary conditions are present + * to send a new packet to the host and trigger the necessary events. + * If a STALL condition is detected, the transfer will automatically + * be cancelled and the host will be notified. + * If a NAK condition is detected (e.g. host has a pending transfer + * but the device has not yet given a packet for transmission), this + * function will do nothing. + */ +static void ot_usbdev_advance_transfer_in(OtUsbdevState *s, uint8_t epnum) +{ + g_assert(epnum < USBDEV_PARAM_N_ENDPOINTS); + /* See BFM (in_packet and handshake_packet) */ + + /* Nothing to do if there is no pending transfer */ + OtUsbdevServer *server = &s->usb_server; + OtUsbdevServerEpInXfer *xfer = &server->ep[epnum].in; + if (!xfer->pending) { + return; + } + + /* + * If the endpoint is not enabled, the device will silently ignore the + * packet, causing an error. + */ + if (!ot_usbdev_is_ep_in_enabled(s, epnum)) { + ot_usbdev_complete_transfer_in(s, epnum, OT_USBDEV_SERVER_STATUS_ERROR, + "endpoint IN not enabled"); + return; + } + + /* Check for STALL */ + if (ot_usbdev_is_ep_in_stalled(s, epnum)) { + ot_usbdev_complete_transfer_in(s, epnum, + OT_USBDEV_SERVER_STATUS_STALLED, + "stalled by device"); + return; + } + + hwaddr reg = R_CONFIGIN_0 + epnum; + uint32_t configin = s->regs[reg]; + bool ready = (bool)SHARED_FIELD_EX32(configin, CONFIGIN_RDY); + uint32_t pkt_size = SHARED_FIELD_EX32(configin, CONFIGIN_SIZE); + uint8_t buf_id = (uint8_t)SHARED_FIELD_EX32(configin, CONFIGIN_BUFFER); + + /* Nothing to do if no packet is ready */ + if (!ready) { + return; + } + + /* + * Make sure buffer ID is valid. With the current constants, this check is + * always true but potentially could before false if the IP became more + * parametrized. + */ + if (buf_id >= OT_USBDEV_BUFFER_COUNT) { + qemu_log_mask(LOG_GUEST_ERROR, + "%s: %s: write to CONFIGIN%d with invalid BUFFER (%u)\n", + __func__, s->ot_id, epnum, buf_id); + return; + } + + trace_ot_usbdev_packet_sent(s->ot_id, epnum, buf_id, pkt_size); + + /* Mark the packet as sent and acknowledged by the host */ + configin = SHARED_FIELD_DP32(configin, CONFIGIN_RDY, 0u); + s->regs[reg] = configin; + s->regs[R_IN_DATA_TOGGLE] ^= + FIELD_DP32(0u, IN_DATA_TOGGLE, STATUS, 1u << epnum); + s->regs[R_IN_SENT] |= 1u << epnum; + ot_usbdev_update_irqs(s); + + /* Check for overflow */ + if (pkt_size > xfer->max_packet_size) { + ot_usbdev_complete_transfer_in( + s, epnum, OT_USBDEV_SERVER_STATUS_ERROR, + "device sent packet bigger than max packet size"); + return; + } + if (pkt_size > xfer->recv_rem) { + ot_usbdev_complete_transfer_in(s, epnum, OT_USBDEV_SERVER_STATUS_ERROR, + "device sent more data than expected"); + return; + } + + /* Copy data and advance buffers */ + ot_usbdev_read_buffer(s, buf_id, xfer->recv_buf, pkt_size); + + xfer->recv_buf += pkt_size; + xfer->recv_rem -= pkt_size; + + /* Handle completion */ + if (pkt_size < xfer->max_packet_size || xfer->recv_rem == 0u) { + ot_usbdev_complete_transfer_in(s, epnum, + OT_USBDEV_SERVER_STATUS_SUCCESS, + "success"); + return; + } +} + +/* + * Try to make progress with an OUT transfer on this endpoint. + * + * This function will check if the necessary conditions are present + * to receive a new packet from the host and trigger the necessary events. + * If a STALL condition is detected, the transfer will automatically + * be cancelled and the host will be notified. + * If a NAK condition is detected (e.g. the host has no transfer pending, + * or the device has not enabled reception), this + * function will do nothing. + */ +static void ot_usbdev_advance_transfer_out(OtUsbdevState *s, uint8_t epnum) +{ + g_assert(epnum < USBDEV_PARAM_N_ENDPOINTS); + /* See BFM (out_packet) */ + + /* Nothing to do if there is no pending transfer */ + OtUsbdevServer *server = &s->usb_server; + OtUsbdevServerEpOutXfer *xfer = &server->ep[epnum].out; + if (!xfer->pending) { + return; + } + + /* + * If the endpoint OUT is not enabled, the device will silently ignore the + * packet, causing an error. + */ + if (!ot_usbdev_is_ep_out_enabled(s, epnum)) { + ot_usbdev_complete_transfer_out(s, epnum, OT_USBDEV_SERVER_STATUS_ERROR, + "endpoint OUT not enabled"); + return; + } + + /* Check for STALL */ + if (ot_usbdev_is_ep_out_stalled(s, epnum)) { + ot_usbdev_complete_transfer_out(s, epnum, + OT_USBDEV_SERVER_STATUS_STALLED, + "stalled by device"); + s->regs[R_USBDEV_INTR_STATE] |= USBDEV_INTR_LINK_OUT_ERR_MASK; + ot_usbdev_update_irqs(s); + return; + } + + /* + * Only accept the DATA packet if the endpoint is enabled, and there is + * buffer available and there is space in the RX FIFO. The last entry of the + * FIFO is reserved for SETUP packets. + */ + if (!ot_usbdev_is_ep_out_rxenabled(s, epnum) || + fifo8_is_empty(&s->av_out_fifo) || + ot_fifo32_num_free(&s->rx_fifo) <= 1u) { + /* Notify the software about the error */ + s->regs[R_USBDEV_INTR_STATE] |= USBDEV_INTR_LINK_OUT_ERR_MASK; + ot_usbdev_update_irqs(s); + /* "NAK" packet, OUT transfer will be retried */ + return; + } + + /* Write data to buffer and update transfer status */ + uint8_t buf_id = fifo8_pop(&s->av_out_fifo); + uint32_t size = MIN((uint32_t)xfer->max_packet_size, xfer->send_rem); + ot_usbdev_write_buffer(s, buf_id, xfer->send_buf, (size_t)size); + xfer->send_buf += size; + xfer->send_rem -= size; + + /* Create and push an entry in the RX FIFO */ + uint32_t rx_fifo_entry = 0u; + rx_fifo_entry = FIELD_DP32(rx_fifo_entry, RXFIFO, BUFFER, buf_id); + rx_fifo_entry = FIELD_DP32(rx_fifo_entry, RXFIFO, SETUP, 0u); + rx_fifo_entry = FIELD_DP32(rx_fifo_entry, RXFIFO, EP, epnum); + rx_fifo_entry = FIELD_DP32(rx_fifo_entry, RXFIFO, SIZE, size); + ot_fifo32_push(&s->rx_fifo, rx_fifo_entry); + + s->regs[R_OUT_DATA_TOGGLE] ^= + FIELD_DP32(0u, IN_DATA_TOGGLE, STATUS, 1u << epnum); + + trace_ot_usbdev_packet_received(s->ot_id, epnum, buf_id, size); + + /* Disable RX if auto-NAK on OUT is set. */ + if (ot_usbdev_is_set_nak_out_enabled(s, epnum)) { + s->regs[R_RXENABLE_OUT] &= ~(1u << epnum); + } + + ot_usbdev_update_fifos_status(s); + ot_usbdev_update_irqs(s); + + /* Handle completion */ + if (xfer->send_rem == 0 && !xfer->send_zlp) { + ot_usbdev_complete_transfer_out(s, epnum, + OT_USBDEV_SERVER_STATUS_SUCCESS, + "success"); + return; + } + if (xfer->send_rem == 0 && xfer->send_zlp) { + /* + * If we have sent everything and a ZLP is requested, go for a last + * round + */ + xfer->send_zlp = false; + } + + /* + * @todo If the transfer is not complete and RX is still active, we need to + * trigger one or more packet reception. However we do not want to do that + * in a loop here because it could lead to starvation on other endpoints. + * Need to have a timer to do some scheduling. + */ + qemu_log_mask(LOG_UNIMP, "%s: %s: unfinished transfer on EP%u OUT\n", + __func__, s->ot_id, epnum); +} + /* * Update the value of the USBCTRL register. * @@ -697,7 +1474,6 @@ static void ot_usbdev_write_usbctrl(OtUsbdevState *s, uint32_t val32) ot_usbdev_set_raw_link_state(s, OT_USBDEV_LINK_STATE_POWERED); ot_usbdev_server_report_connected(s, true); } - /* @todo Handle FIFO interrupts */ } else { /* See BFM (set_enable) */ ot_usbdev_set_raw_link_state(s, OT_USBDEV_LINK_STATE_DISCONNECTED); @@ -708,12 +1484,119 @@ static void ot_usbdev_write_usbctrl(OtUsbdevState *s, uint32_t val32) if (ot_usbdev_has_vbus(s)) { ot_usbdev_server_report_connected(s, false); } - /* @todo Handle FIFO interrupts */ } + /* + * Status interrupts are only active when enabled, we may need to update + * them + */ + ot_usbdev_update_status_irqs(s); ot_usbdev_update_irqs(s); } +/* + * Update the value of a CONFIGINx register. + * + * This function will update the CONFIGINx register after a write and + * trigger the necessary changes. If as a result of this change a transfer + * completes, the server will be notified. + * + * @reg Register index + * @val32 New value + */ +static void ot_usbdev_write_configin(OtUsbdevState *s, hwaddr reg, + uint32_t val32) +{ + uint8_t ep = (uint8_t)(reg - R_CONFIGIN_0); + g_assert(ep < USBDEV_PARAM_N_ENDPOINTS); + + /* + * @todo clarify what writing 1 to SENDING is supposed to do since this + * is a hardware signal and not a status bit, doc is unclear. + * In this emulated driver, the SENDING bit is never set by the code. + */ + + /* clear RW1C fields */ + uint32_t rw1c = val32 & USBDEV_CONFIGIN_RW1C_MASK; + s->regs[reg] &= ~rw1c; + /* set RW fields */ + s->regs[reg] &= USBDEV_CONFIGIN_RW1C_MASK; + s->regs[reg] |= val32 & ~USBDEV_CONFIGIN_RW1C_MASK; + + ot_usbdev_advance_transfer_in(s, ep); +} + +/* + * Update the transfers pending on one or more endpoints whose status + * has changed. + * + * After changing the status of an endpoint (e.g. (un)stall, nak, + * RX/TX enable), this function must be called to re-evaluate whether + * the pending transfers can make progress. Only endpoints set in the + * mask will be considered. + * + * @dir_in True for IN transfers, false for OUT. + * @ep_mask The n-th bit represents the n-th endpoint. + * + */ +static void ot_usbdev_update_ep_xfers(OtUsbdevState *s, bool dir_in, + uint32_t ep_mask) +{ + /* For each disabled ep, update the transfer to trigger an error */ + for (uint8_t ep = 0u; ep < USBDEV_PARAM_N_ENDPOINTS; ep++) { + if (((ep_mask >> ep) & 1u) == 0u) { + continue; + } + if (dir_in) { + ot_usbdev_advance_transfer_in(s, ep); + } else { + ot_usbdev_advance_transfer_out(s, ep); + } + } +} + +/* + * Handle write to register which changes whether an endpoint is + * enabled or not. Trigger the necessary changes to transfers if any. + * + * This function will update the content of the register with the given value. + * + * @reg One of R_EP_IN_ENABLE, R_EP_OUT_ENABLE, R_RXENABLE_SETUP or + * R_RXENABLE_OUT + * @val32 New value of the register. + */ +static void +ot_usbdev_update_ep_enabled(OtUsbdevState *s, hwaddr reg, uint32_t val32) +{ + /* Find which endpoints have been disabled */ + uint32_t disabled_ep = s->regs[reg] & ~val32; + s->regs[reg] = val32 & ((1u << USBDEV_PARAM_N_ENDPOINTS) - 1u); + + bool dir_in = reg == R_EP_IN_ENABLE; + ot_usbdev_update_ep_xfers(s, dir_in, disabled_ep); +} + +/* + * Handle write to register which changes whether an endpoint is + * stalled or not. Trigger the necessary changes to transfers if any. + * + * This function will update the content of the register with the given value. + * + * @reg One of R_OUT_STALL or R_IN_STALL: + * @val32 New value of the register. + */ +static void +ot_usbdev_update_ep_stalled(OtUsbdevState *s, hwaddr reg, uint32_t val32) +{ + /* Find which endpoints have been stalled */ + uint32_t stalled_ep = + (~s->regs[reg] & val32) & ((1u << USBDEV_PARAM_N_ENDPOINTS) - 1u); + s->regs[reg] = val32 & ((1u << USBDEV_PARAM_N_ENDPOINTS) - 1u); + + bool dir_in = reg == R_IN_STALL; + ot_usbdev_update_ep_xfers(s, dir_in, stalled_ep); +} + /* * Register read/write handling */ @@ -736,7 +1619,6 @@ static uint64_t ot_usbdev_read(void *opaque, hwaddr addr, unsigned size) case R_EP_IN_ENABLE: case R_AVOUTBUFFER: case R_AVSETUPBUFFER: - case R_RXFIFO: case R_RXENABLE_SETUP: case R_RXENABLE_OUT: case R_SET_NAK_OUT: @@ -770,6 +1652,19 @@ static uint64_t ot_usbdev_read(void *opaque, hwaddr addr, unsigned size) case R_COUNT_ERRORS: val32 = s->regs[reg]; break; + case R_RXFIFO: + if (ot_fifo32_is_empty(&s->rx_fifo)) { + qemu_log_mask(LOG_GUEST_ERROR, + "%s: %s: read to RXFIFO but FIFO is empty\n", + __func__, s->ot_id); + val32 = 0u; + } else { + val32 = ot_fifo32_pop(&s->rx_fifo); + trace_ot_usbdev_pop_rx_fifo(s->ot_id, val32); + ot_usbdev_update_fifos_status(s); + /* @todo trigger out transfers potentially */ + } + break; case R_USBDEV_INTR_TEST: case R_ALERT_TEST: qemu_log_mask(LOG_GUEST_ERROR, @@ -806,13 +1701,6 @@ static void ot_usbdev_write(void *opaque, hwaddr addr, uint64_t val64, switch (reg) { case R_USBDEV_INTR_STATE: - if (val32 & ~USBDEV_INTR_RW1C_MASK) { - qemu_log_mask(LOG_GUEST_ERROR, - "%s: %s: write to R/O register 0x%02" HWADDR_PRIx - " (%s) field 0x%08x\n", - __func__, s->ot_id, addr, REG_NAME(reg), - val32 & ~USBDEV_INTR_RW1C_MASK); - } val32 &= USBDEV_INTR_RW1C_MASK; s->regs[reg] &= ~val32; ot_usbdev_update_irqs(s); @@ -833,13 +1721,13 @@ static void ot_usbdev_write(void *opaque, hwaddr addr, uint64_t val64, s->regs[R_ALERT_TEST] = val32; ot_usbdev_update_alerts(s); break; + case R_RXFIFO: case R_USBSTAT: qemu_log_mask(LOG_GUEST_ERROR, "%s: %s: write to R/O register 0x%02" HWADDR_PRIx " (%s)\n", __func__, s->ot_id, addr, REG_NAME(reg)); break; - /* Writes without side-effects */ case R_PHY_CONFIG: /* @todo mask against actual fields? */ s->regs[R_PHY_CONFIG] = val32; @@ -847,17 +1735,41 @@ static void ot_usbdev_write(void *opaque, hwaddr addr, uint64_t val64, case R_USBCTRL: ot_usbdev_write_usbctrl(s, val32); break; - case R_EP_OUT_ENABLE: case R_EP_IN_ENABLE: - case R_AVOUTBUFFER: - case R_AVSETUPBUFFER: - case R_RXFIFO: + case R_EP_OUT_ENABLE: case R_RXENABLE_SETUP: case R_RXENABLE_OUT: - case R_SET_NAK_OUT: - case R_IN_SENT: + ot_usbdev_update_ep_enabled(s, reg, val32); + break; case R_OUT_STALL: case R_IN_STALL: + ot_usbdev_update_ep_stalled(s, reg, val32); + break; + case R_AVOUTBUFFER: + if (fifo8_is_full(&s->av_out_fifo)) { + qemu_log_mask(LOG_GUEST_ERROR, + "%s: %s: write to AVOUTBUFFER but FIFO is full\n", + __func__, s->ot_id); + } else { + uint8_t buf_id = (uint8_t)FIELD_EX32(val32, AVOUTBUFFER, BUFFER); + fifo8_push(&s->av_out_fifo, buf_id); + trace_ot_usbdev_push_av_out_buffer(s->ot_id, buf_id); + ot_usbdev_update_fifos_status(s); + /* @todo trigger out transfers potentially */ + } + break; + case R_AVSETUPBUFFER: + if (fifo8_is_full(&s->av_setup_fifo)) { + qemu_log_mask(LOG_GUEST_ERROR, + "%s: %s: write to AVSETUPBUFFER but FIFO is full\n", + __func__, s->ot_id); + } else { + uint8_t buf_id = (uint8_t)FIELD_EX32(val32, AVSETUPBUFFER, BUFFER); + fifo8_push(&s->av_setup_fifo, buf_id); + trace_ot_usbdev_push_av_setup_buffer(s->ot_id, buf_id); + ot_usbdev_update_fifos_status(s); + } + break; case R_CONFIGIN_0: case R_CONFIGIN_1: case R_CONFIGIN_2: @@ -870,6 +1782,15 @@ static void ot_usbdev_write(void *opaque, hwaddr addr, uint64_t val64, case R_CONFIGIN_9: case R_CONFIGIN_10: case R_CONFIGIN_11: + ot_usbdev_write_configin(s, reg, val32); + break; + case R_IN_SENT: + /* register is rw1c */ + s->regs[reg] &= ~val32; + break; + case R_SET_NAK_OUT: + s->regs[reg] = val32; + break; case R_OUT_ISO: case R_IN_ISO: case R_OUT_DATA_TOGGLE: @@ -972,11 +1893,24 @@ static void ot_usbdev_server_close(OtUsbdevState *s) /* * Send a message to the client. * - * @todo clarify blocking/non-blocking + * To avoid an allocation, this function supports sending the payload + * data in two segments, represented by two buffers (data0 and data1). + * Any buffer with size zero will be ignored. + * + * @todo This function must be non-blocking, clarify if that's really + * the case. + * + * @s Device state + * @cmd Command + * @id Packet ID + * @data0 First part of the payload data (can be NULL if size0 is set) + * @data0 Size of the first part of the payload data + * @data1 Second part of the payload data (can be NULL if size1 is set) + * @data1 Size of the second part of the payload data */ -static void ot_usbdev_server_write_packet(OtUsbdevState *s, - OtUsbdevServerCmd cmd, uint32_t id, - const void *data, size_t size) +static void ot_usbdev_server_write_packet( + OtUsbdevState *s, OtUsbdevServerCmd cmd, uint32_t id, const uint8_t *data0, + size_t size0, const uint8_t *data1, size_t size1) { /* * @todo Update this to handle partial writes, by adding them to a queue and @@ -984,7 +1918,7 @@ static void ot_usbdev_server_write_packet(OtUsbdevState *s, */ const OtUsbdevServerPktHdr hdr = { .cmd = cmd, - .size = size, + .size = size0 + size1, .id = id, }; int res = @@ -994,9 +1928,17 @@ static void ot_usbdev_server_write_packet(OtUsbdevState *s, __func__, s->ot_id); return; } - if (size > 0u) { - res = qemu_chr_fe_write(&s->usb_chr, (const uint8_t *)data, (int)size); - if (res < size) { + if (size0 > 0u) { + res = qemu_chr_fe_write(&s->usb_chr, data0, (int)size0); + if (res < size0) { + qemu_log_mask(LOG_UNIMP, "%s: %s server: unhandled partial write\n", + __func__, s->ot_id); + return; + } + } + if (size1 > 0u) { + res = qemu_chr_fe_write(&s->usb_chr, data1, (int)size1); + if (res < size1) { qemu_log_mask(LOG_UNIMP, "%s: %s server: unhandled partial write\n", __func__, s->ot_id); return; @@ -1026,7 +1968,7 @@ void ot_usbdev_server_report_connected(OtUsbdevState *s, bool connected) ot_usbdev_server_write_packet(s, connected ? OT_USBDEV_SERVER_CMD_CONNECT : OT_USBDEV_SERVER_CMD_DISCONNECT, - 0u, NULL, 0u); + 0u, NULL, 0u, NULL, 0u); } /* @@ -1041,13 +1983,13 @@ static void ot_usbdev_server_process_hello(OtUsbdevState *s) OtUsbdevServer *server = &s->usb_server; if (server->hello_done) { - error_report("%s: %s server: unexpected HELLO\n", __func__, s->ot_id); + trace_ot_usbdev_server_protocol_error(s->ot_id, "unexpected HELLO"); /* Not a fatal error */ } if (server->recv_pkt.size != sizeof(OtUsbdevServerHelloPkt)) { - error_report("%s: %s server: HELLO packet with unexpected payload " - "size %u, ignoring packet\n", - __func__, s->ot_id, server->recv_pkt.size); + trace_ot_usbdev_server_protocol_error( + s->ot_id, + "HELLO packet with unexpected payload size, ignoring packet"); return; } /* @@ -1058,17 +2000,15 @@ static void ot_usbdev_server_process_hello(OtUsbdevState *s) memcpy(&hello, server->recv_data, sizeof(OtUsbdevServerHelloPkt)); if (memcmp(hello.magic, OT_USBDEV_SERVER_HELLO_MAGIC, sizeof(hello.magic)) != 0) { - error_report("%s: %s server: HELLO packet with unexpected magic value " - "%.4s, ignoring packet\n", - __func__, s->ot_id, hello.magic); + trace_ot_usbdev_server_protocol_error( + s->ot_id, + "HELLO packet with unexpected magic value, ignoring packet"); return; } if (hello.major_version != OT_USBDEV_SERVER_MAJOR_VER || hello.minor_version != OT_USBDEV_SERVER_MINOR_VER) { - error_report("%s: %s server: HELLO packet with unexpected version " - "version %d.%d, ignoring packet\n", - __func__, s->ot_id, hello.major_version, - hello.minor_version); + trace_ot_usbdev_server_protocol_error( + s->ot_id, "HELLO packet with unexpected version, ignoring packet"); return; } server->hello_done = true; @@ -1084,8 +2024,8 @@ static void ot_usbdev_server_process_hello(OtUsbdevState *s) resp_hello.minor_version = OT_USBDEV_SERVER_MINOR_VER; ot_usbdev_server_write_packet(s, OT_USBDEV_SERVER_CMD_HELLO, server->recv_pkt.id, - (const void *)&resp_hello, - sizeof(resp_hello)); + (const void *)&resp_hello, sizeof(resp_hello), + NULL, 0u); } static void ot_usbdev_server_process_reset(OtUsbdevState *s) @@ -1093,16 +2033,14 @@ static void ot_usbdev_server_process_reset(OtUsbdevState *s) OtUsbdevServer *server = &s->usb_server; if (server->recv_pkt.size != 0u) { - error_report("%s: %s server: RESET packet with unexpected payload, " - "ignoring packet\n", - __func__, s->ot_id); + trace_ot_usbdev_server_protocol_error( + s->ot_id, "RESET packet with unexpected payload, ignoring packet"); return; } /* Ignore if device is not enabled */ if (resettable_is_in_reset(OBJECT(s))) { - error_report( - "%s: %s server: RESET when device is in reset, ignoring packet\n", - __func__, s->ot_id); + trace_ot_usbdev_server_protocol_error( + s->ot_id, "RESET when device is in reset, ignoring packet"); return; } @@ -1114,9 +2052,9 @@ static void ot_usbdev_server_process_vbus_changed(OtUsbdevState *s, bool vbus) OtUsbdevServer *server = &s->usb_server; if (server->recv_pkt.size != 0u) { - error_report("%s: %s server: VBUS_ON/OFF packet with unexpected " - "payload, ignoring packet\n", - __func__, s->ot_id); + trace_ot_usbdev_server_protocol_error( + s->ot_id, + "VBUS_ON/OFF packet with unexpected payload, ignoring packet"); return; } @@ -1124,6 +2062,202 @@ static void ot_usbdev_server_process_vbus_changed(OtUsbdevState *s, bool vbus) ot_usbdev_update_vbus(s); } +static void ot_usbdev_server_process_setup(OtUsbdevState *s) +{ + OtUsbdevServer *server = &s->usb_server; + + if (server->recv_pkt.size != sizeof(OtUsbdevServerSetupPkt)) { + trace_ot_usbdev_server_protocol_error( + s->ot_id, + "SETUP packet with unexpected payload size, ignoring packet"); + return; + } + /* Ignore if device is not enabled */ + if (resettable_is_in_reset(OBJECT(s))) { + trace_ot_usbdev_server_protocol_error( + s->ot_id, "SETUP when device is in reset, ignoring packet"); + return; + } + + OtUsbdevServerSetupPkt setup; + memcpy(&setup, server->recv_data, sizeof(OtUsbdevServerSetupPkt)); + + /* @todo enforce that reserved is 0 */ + + /* Ignore SETUP if address or endpoint is not ours */ + if (setup.address != ot_usbdev_get_address(s) || + setup.endpoint >= USBDEV_PARAM_N_ENDPOINTS) { + trace_ot_usbdev_setup_addr( + s->ot_id, setup.address, setup.endpoint, + "ignore (mismatching address or invalid endpoint)"); + return; + } + + ot_usbdev_simulate_setup(s, setup.endpoint, setup.setup); +} + +void ot_usbdev_server_complete_transfer(OtUsbdevState *s, uint32_t id, + OtUsbdevServerTransferStatus status, + uint32_t xfer_size, const uint8_t *data, + uint32_t data_size, const char *msg) +{ + trace_ot_usbdev_complete_transfer(s->ot_id, id, xfer_size, + XFER_STATUS_NAME(status), msg); + + OtUsbdevServerCompletePkt pkt; + memset(&pkt, 0u, sizeof(pkt)); + pkt.status = (uint8_t)status; + pkt.transfer_size = xfer_size; + ot_usbdev_server_write_packet(s, OT_USBDEV_SERVER_CMD_COMPLETE, id, + (const uint8_t *)&pkt, sizeof(pkt), data, + data_size); +} + +static void ot_usbdev_server_process_transfer_in( + OtUsbdevState *s, uint8_t ep, uint8_t flags, uint16_t max_packet_size, + uint32_t transfer_size) +{ + OtUsbdevServer *server = &s->usb_server; + OtUsbdevServerEpInXfer *xfer = &server->ep[ep].in; + + /* It is an error to send a transfer if one is pending */ + if (xfer->pending) { + trace_ot_usbdev_transfer(s->ot_id, server->recv_pkt.id, ep, "IN", flags, + max_packet_size, transfer_size, + "error (transfer already pending)"); + ot_usbdev_server_complete_transfer(s, server->recv_pkt.id, + OT_USBDEV_SERVER_STATUS_ERROR, 0u, + NULL, 0u, + "transfer already pending"); + } + + /* @todo check for unknown flags */ + + trace_ot_usbdev_transfer(s->ot_id, server->recv_pkt.id, ep, "IN", flags, + max_packet_size, transfer_size, "accepted"); + + /* Sanity check */ + g_assert(xfer->xfer_buf == NULL); + + /* record transfer */ + xfer->id = server->recv_pkt.id; + xfer->xfer_len = transfer_size; + /* @todo do some basic checks on reasonable length? */ + xfer->xfer_buf = g_malloc(transfer_size); + xfer->max_packet_size = max_packet_size; + xfer->recv_buf = xfer->xfer_buf; + xfer->recv_rem = transfer_size; + xfer->pending = true; + + ot_usbdev_advance_transfer_in(s, ep); +} + +static void ot_usbdev_server_process_transfer_out( + OtUsbdevState *s, uint8_t ep, uint8_t flags, uint16_t max_packet_size, + uint32_t transfer_size, const uint8_t *data) +{ + OtUsbdevServer *server = &s->usb_server; + OtUsbdevServerEpOutXfer *xfer = &server->ep[ep].out; + + /* It is an error to send a transfer if one is pending */ + if (xfer->pending) { + trace_ot_usbdev_transfer(s->ot_id, server->recv_pkt.id, ep, "OUT", + flags, max_packet_size, transfer_size, + "error (transfer already pending)"); + ot_usbdev_server_complete_transfer(s, server->recv_pkt.id, + OT_USBDEV_SERVER_STATUS_ERROR, 0u, + NULL, 0u, + "transfer already pending"); + } + + /* @todo check for unknown flags */ + bool zlp = (flags & OT_USBDEV_SERVER_FLAG_ZLP) != 0u; + + /* @todo check max packet size is smaller than buffer size! */ + + trace_ot_usbdev_transfer(s->ot_id, server->recv_pkt.id, ep, "OUT", flags, + max_packet_size, transfer_size, "accepted"); + + /* sanity check */ + g_assert(xfer->xfer_buf == NULL); + + /* record transfer */ + xfer->id = server->recv_pkt.id; + /* @todo do some basic checks on reasonable length? */ + xfer->xfer_buf = g_malloc(transfer_size); + memcpy(xfer->xfer_buf, data, transfer_size); + xfer->xfer_len = transfer_size; + xfer->max_packet_size = max_packet_size; + xfer->send_buf = xfer->xfer_buf; + xfer->send_rem = transfer_size; + /* + * Due to the logic in ot_usbdev_advance_transfer_out, the ZLP flag + * must only be set for non-zero length transfers, otherwise we will + * send two ZLPs. + */ + xfer->send_zlp = zlp && transfer_size != 0u; + xfer->pending = true; + + ot_usbdev_advance_transfer_out(s, ep); +} + +static void ot_usbdev_server_process_transfer(OtUsbdevState *s) +{ + OtUsbdevServer *server = &s->usb_server; + + if (server->recv_pkt.size < sizeof(OtUsbdevServerTransferPkt)) { + trace_ot_usbdev_server_protocol_error( + s->ot_id, "TRANSFER packet with unexpectedly small payload size, " + "ignoring packet"); + return; + } + + OtUsbdevServerTransferPkt xfer; + memcpy(&xfer, server->recv_data, sizeof(OtUsbdevServerTransferPkt)); + + uint8_t epnum = xfer.endpoint & OT_USBDEV_EP_NUM_MASK; + bool dir_in = (xfer.endpoint & OT_USBDEV_EP_DIR_IN) == OT_USBDEV_EP_DIR_IN; + + /* Ignore if device is not enabled */ + if (resettable_is_in_reset(OBJECT(s))) { + trace_ot_usbdev_transfer(s->ot_id, server->recv_pkt.id, epnum, + dir_in ? "IN" : "OUT", xfer.flags, + xfer.packet_size, xfer.transfer_size, + "ignored (device is in reset)"); + ot_usbdev_server_complete_transfer(s, server->recv_pkt.id, + OT_USBDEV_SERVER_STATUS_ERROR, 0u, + NULL, 0u, "device is in reset"); + return; + } + + if (epnum >= USBDEV_PARAM_N_ENDPOINTS) { + trace_ot_usbdev_server_protocol_error( + s->ot_id, "TRANSFER to non-existent endpoint, ignoring packet"); + return; + } + + uint32_t expected_data_len = dir_in ? 0u : xfer.transfer_size; + if (server->recv_pkt.size != + sizeof(OtUsbdevServerTransferPkt) + expected_data_len) { + trace_ot_usbdev_server_protocol_error( + s->ot_id, + "TRANSFER packet with unexpected payload size, ignoring packet"); + return; + } + + if (dir_in) { + ot_usbdev_server_process_transfer_in(s, epnum, xfer.flags, + xfer.packet_size, + xfer.transfer_size); + } else { + const uint8_t *data = + server->recv_data + sizeof(OtUsbdevServerTransferPkt); + ot_usbdev_server_process_transfer_out(s, epnum, xfer.flags, + xfer.packet_size, + xfer.transfer_size, data); + } +} + static void ot_usbdev_server_process_packet(OtUsbdevState *s) { OtUsbdevServer *server = &s->usb_server; @@ -1138,8 +2272,8 @@ static void ot_usbdev_server_process_packet(OtUsbdevState *s) /* If HELLO hasn't been received yet, ignore everything else */ if (server->recv_pkt.cmd != OT_USBDEV_SERVER_CMD_HELLO && !server->hello_done) { - error_report("%s: %s server: unexpected command %u before HELLO\n", - __func__, s->ot_id, server->recv_pkt.cmd); + trace_ot_usbdev_server_protocol_error( + s->ot_id, "unexpected command before HELLO, ignoring packet"); /* Do not process packet */ return; } @@ -1157,9 +2291,15 @@ static void ot_usbdev_server_process_packet(OtUsbdevState *s) case OT_USBDEV_SERVER_CMD_VBUS_OFF: ot_usbdev_server_process_vbus_changed(s, false); break; + case OT_USBDEV_SERVER_CMD_SETUP: + ot_usbdev_server_process_setup(s); + break; + case OT_USBDEV_SERVER_CMD_TRANSFER: + ot_usbdev_server_process_transfer(s); + break; default: - error_report("%s: %s server: unknown packet type %u, ignoring packet\n", - __func__, s->ot_id, server->recv_pkt.cmd); + trace_ot_usbdev_server_protocol_error( + s->ot_id, "unknown command, ignoring packet"); break; } @@ -1173,6 +2313,8 @@ static void ot_usbdev_server_advance_recv_state(OtUsbdevState *s) if (server->recv_state == OT_USBDEV_SERVER_RECV_WAIT_HEADER) { /* We received a header, now receive data if necessary */ if (server->recv_pkt.size > 0u) { + /* sanity check */ + g_assert(server->recv_data == NULL); /* @todo have some bound on allocation size here? */ server->recv_data = g_malloc(server->recv_pkt.size); server->recv_rem = server->recv_pkt.size; @@ -1385,7 +2527,12 @@ static void ot_usbdev_reset_enter(Object *obj, ResetType type) /* @todo cancel everything here. */ /* See BFM (dut_reset) */ - memset(s->regs, 0, sizeof(s->regs)); + memset(s->regs, 0u, sizeof(s->regs)); + + ot_fifo32_reset(&s->rx_fifo); + fifo8_reset(&s->av_setup_fifo); + fifo8_reset(&s->av_out_fifo); + /* * @todo The BFM says that 'Disconnected' is held at 1 during IP * reset, need to investigate this detail. @@ -1405,6 +2552,7 @@ static void ot_usbdev_reset_exit(Object *obj, ResetType type) } ot_usbdev_update_vbus(s); + ot_usbdev_update_fifos_status(s); } static void ot_usbdev_init(Object *obj) @@ -1427,6 +2575,10 @@ static void ot_usbdev_init(Object *obj) TYPE_OT_USBDEV ".buffer", USBDEV_BUFFER_SIZE); memory_region_add_subregion(&s->mmio.main, USBDEV_BUFFER_OFFSET, &s->mmio.buffer); + + ot_fifo32_create(&s->rx_fifo, OT_USBDEV_RX_FIFO_DEPTH); + fifo8_create(&s->av_out_fifo, OT_USBDEV_AV_OUT_FIFO_DEPTH); + fifo8_create(&s->av_setup_fifo, OT_USBDEV_AV_SETUP_FIFO_DEPTH); } static void ot_usbdev_class_init(ObjectClass *klass, void *data) diff --git a/hw/opentitan/trace-events b/hw/opentitan/trace-events index c3764ccebda1a..17957c7d92710 100644 --- a/hw/opentitan/trace-events +++ b/hw/opentitan/trace-events @@ -639,14 +639,26 @@ ot_uart_irqs(const char *id, uint32_t active, uint32_t mask, uint32_t eff) "%s: ot_usbdev_chr_process_cmd(const char *id, const char *cmd) "%s: %s" ot_usbdev_chr_usb_event_handler(const char *id, int event) "%s: server event=%d" +ot_usbdev_complete_transfer(const char *id, uint32_t cmdid, uint32_t xfered_len, const char *status, const char *msg) "%s: cmdid=%u, len=%u, status=%s: %s" ot_usbdev_enable_changed(const char *id, bool enable) "%s: enable=%u" ot_usbdev_io_buffer_read_out(const char *id, uint32_t addr, uint32_t val, uint32_t pc) "%s: addr=0x%04x, val=0x%x, pc=0x%x" ot_usbdev_io_buffer_write(const char *id, uint32_t addr, uint32_t val, uint32_t pc) "%s: addr=0x%04x, val=0x%x, pc=0x%x" ot_usbdev_io_read_out(const char *id, uint32_t addr, const char *regname, uint32_t val, uint32_t pc) "%s: addr=0x%02x (%s), val=0x%x, pc=0x%x" ot_usbdev_io_write(const char *id, uint32_t addr, const char *regname, uint32_t val, uint32_t pc) "%s: addr=0x%02x (%s), val=0x%x, pc=0x%x" ot_usbdev_irqs(const char *id, uint32_t active, uint32_t mask, uint32_t eff) "%s: act:0x%08x msk:0x%08x eff:0x%08x" +ot_usbdev_link_reset(const char *id) "%s" +ot_usbdev_packet_received(const char *id, uint8_t ep, uint8_t bufid, uint32_t size) "%s: ep=%u, buf_id=%u, size=%u" +ot_usbdev_packet_sent(const char *id, uint8_t ep, uint8_t bufid, uint32_t size) "%s: ep=%u, buf_id=%u, size=%u" ot_usbdev_link_state_changed(const char *id, const char *state) "%s: link_state=%s" +ot_usbdev_pop_rx_fifo(const char *id, uint32_t rxfifo_entry) "%s: entry=0x%x" +ot_usbdev_push_av_out_buffer(const char *id, uint8_t buf_id) "%s: buf_id=%u" +ot_usbdev_push_av_setup_buffer(const char *id, uint8_t buf_id) "%s: buf_id=%u" ot_usbdev_reset(const char *id, const char *stage) "%s: %s" +ot_usbdev_retire_in_packet(const char *id, uint32_t ep) "%s: ep=%u" +ot_usbdev_setup(const char *id, uint8_t ep, const char *msg) "%s: ep=%u: %s" +ot_usbdev_setup_addr(const char *id, uint8_t addr, uint8_t ep, const char *msg) "%s: addr=%u, ep=%u: %s" +ot_usbdev_transfer(const char *id, uint32_t cmdid, uint8_t ep, const char *dir, uint8_t flags, uint16_t mps, uint32_t xfer_len, const char *msg) "%s: cmdid=%u, ep=%u, dir=%s, flags=0x%x, mps=%u, len=%u: %s" +ot_usbdev_server_protocol_error(const char *id, const char *msg) "%s: %s" ot_usbdev_server_hello(const char *id, uint16_t major, uint16_t minor) "%s: major=%u, minor=%u" ot_usbdev_server_report_connected(const char *id, bool connected) "%s: connected=%u" ot_usbdev_vbus_changed(const char *id, bool vbus) "%s: vbus=%u"