blob: 06ef8564ac654391fcc235005c96edc24e0bd884 [file] [log] [blame]
// Copyright 2016 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <ddk/debug.h>
#include <ddk/phys-iter.h>
#include <ddk/protocol/usb/hci.h>
#include <fbl/auto_lock.h>
#include <zircon/assert.h>
#include <zircon/hw/usb.h>
#include <stdio.h>
#include <string.h>
#include <threads.h>
#include "xhci-transfer.h"
#include "xhci-trb.h"
#include "xhci-util.h"
namespace usb_xhci {
// reads a range of bits from an integer
#define READ_FIELD(i, start, bits) (((i) >> (start)) & ((1 << (bits)) - 1))
// This resets the transfer ring's dequeue pointer just past the last completed transfer.
// This can only be called when the endpoint is stopped and we are locked on ep->lock.
static zx_status_t xhci_reset_dequeue_ptr_locked(xhci_t* xhci, uint32_t slot_id,
uint32_t ep_index) {
xhci_slot_t* slot = &xhci->slots[slot_id];
xhci_endpoint_t* ep = &slot->eps[ep_index];
xhci_transfer_ring_t* transfer_ring = &ep->transfer_ring;
xhci_sync_command_t command;
xhci_sync_command_init(&command);
uint64_t ptr = xhci_transfer_ring_current_phys(transfer_ring);
ptr |= transfer_ring->pcs;
// command expects device context index, so increment ep_index by 1
uint32_t control = (slot_id << TRB_SLOT_ID_START) |
((ep_index + 1) << TRB_ENDPOINT_ID_START);
zx_status_t status = xhci_post_command(xhci, TRB_CMD_SET_TR_DEQUEUE, ptr, control,
&command.context);
if (status != ZX_OK) {
return status;
}
int cc = xhci_sync_command_wait(&command);
if (cc != TRB_CC_SUCCESS) {
zxlogf(ERROR, "TRB_CMD_SET_TR_DEQUEUE failed cc: %d\n", cc);
return ZX_ERR_INTERNAL;
}
xhci_set_dequeue_ptr(transfer_ring, transfer_ring->current_trb);
return ZX_OK;
}
static void xhci_process_transactions_locked(xhci_t* xhci, xhci_slot_t* slot, uint8_t ep_index,
list_node_t* completed_reqs);
zx_status_t xhci_reset_endpoint(xhci_t* xhci, uint32_t slot_id, uint8_t ep_address) {
xhci_slot_t* slot = &xhci->slots[slot_id];
uint8_t ep_index = xhci_endpoint_index(ep_address);
xhci_endpoint_t* ep = &slot->eps[ep_index];
usb_request_t* req;
// Recover from Halted and Error conditions. See section 4.8.3 of the XHCI spec.
ep->lock.Acquire();
if (ep->state != EP_STATE_HALTED && ep->state != EP_STATE_ERROR) {
ep->lock.Release();
return ZX_OK;
}
int ep_ctx_state = xhci_get_ep_ctx_state(slot, ep);
zxlogf(TRACE, "xhci_reset_endpoint %d %d ep_ctx_state %d\n", slot_id, ep_index, ep_ctx_state);
if (ep_ctx_state == EP_CTX_STATE_STOPPED || ep_ctx_state == EP_CTX_STATE_RUNNING) {
ep->state = EP_STATE_RUNNING;
ep->lock.Release();
return ZX_OK;
}
if (ep_ctx_state == EP_CTX_STATE_HALTED) {
// reset the endpoint to move from Halted to Stopped state
xhci_sync_command_t command;
xhci_sync_command_init(&command);
// command expects device context index, so increment ep_index by 1
uint32_t control = (slot_id << TRB_SLOT_ID_START) |
((ep_index + 1) << TRB_ENDPOINT_ID_START);
zx_status_t status = xhci_post_command(xhci, TRB_CMD_RESET_ENDPOINT, 0, control,
&command.context);
if (status != ZX_OK) {
ep->lock.Release();
return status;
}
// Release the lock before waiting for the command. The command may not complete,
// if there is another transfer event on the completer thread waiting for the lock
// on the same endpoint.
ep->lock.Release();
int cc = xhci_sync_command_wait(&command);
if (cc != TRB_CC_SUCCESS) {
zxlogf(ERROR, "xhci_reset_endpoint: TRB_CMD_RESET_ENDPOINT failed cc: %d\n", cc);
return ZX_ERR_INTERNAL;
}
ep->lock.Acquire();
// calling USB_REQ_CLEAR_FEATURE on a stalled control endpoint will not work,
// so we only do this for the other endpoints
if (ep_address != 0) {
// This should come after the successful completion of a Reset Endpoint Command.
// See XHCI spec, section 4.6.8
xhci_control_request(xhci, slot_id, USB_DIR_OUT | USB_TYPE_STANDARD | USB_RECIP_ENDPOINT,
USB_REQ_CLEAR_FEATURE, USB_ENDPOINT_HALT, ep_address, nullptr, 0,
nullptr);
}
}
// resetting the dequeue pointer gets us out of ERROR state, and is also necessary
// after TRB_CMD_RESET_ENDPOINT.
if (ep_ctx_state == EP_CTX_STATE_ERROR || ep_ctx_state == EP_CTX_STATE_HALTED) {
// move transfer ring's dequeue pointer passed the failed transaction
zx_status_t status = xhci_reset_dequeue_ptr_locked(xhci, slot_id, ep_index);
if (status != ZX_OK) {
ep->lock.Release();
return status;
}
}
// xhci_reset_dequeue_ptr_locked will skip past all pending transactions,
// so move them all to the queued list so they will be requeued
// Completed these with ZX_ERR_CANCELED out of the lock.
// Remove from tail and add to head to preserve the ordering
while (xhci_remove_from_list_tail(xhci, &ep->pending_reqs, &req)) {
xhci_add_to_list_head(xhci, &ep->queued_reqs, req);
}
ep_ctx_state = xhci_get_ep_ctx_state(slot, ep);
zx_status_t status;
switch (ep_ctx_state) {
case EP_CTX_STATE_DISABLED:
ep->state = EP_STATE_DEAD;
status = ZX_ERR_IO_NOT_PRESENT;
break;
case EP_CTX_STATE_RUNNING:
case EP_CTX_STATE_STOPPED:
ep->state = EP_STATE_RUNNING;
status = ZX_OK;
break;
case EP_CTX_STATE_ERROR:
ep->state = EP_STATE_ERROR;
status = ZX_ERR_IO_INVALID;
break;
case EP_CTX_STATE_HALTED:
ep->state = EP_STATE_HALTED;
status = ZX_ERR_IO_REFUSED;
break;
default:
ep->state = EP_STATE_HALTED;
status = ZX_ERR_INTERNAL;
break;
}
list_node_t completed_reqs = LIST_INITIAL_VALUE(completed_reqs);
if (ep->state == EP_STATE_RUNNING) {
// start processing transactions again
xhci_process_transactions_locked(xhci, slot, ep_index, &completed_reqs);
}
ep->lock.Release();
xhci_usb_request_internal_t* req_int = nullptr;
// call complete callbacks out of the lock
while ((req_int = list_remove_head_type(&completed_reqs,
xhci_usb_request_internal_t, node)) != nullptr) {
req = XHCI_INTERNAL_TO_USB_REQ(req_int);
usb_request_complete(req, req->response.status, req->response.actual,
&req_int->complete_cb);
}
return status;
}
// locked on ep->lock
static zx_status_t xhci_start_transfer_locked(xhci_t* xhci, xhci_slot_t* slot, uint32_t ep_index,
usb_request_t* req) {
xhci_endpoint_t* ep = &slot->eps[ep_index];
xhci_transfer_ring_t* ring = &ep->transfer_ring;
if (ep->state != EP_STATE_RUNNING) {
zxlogf(ERROR, "xhci_start_transfer_locked bad ep->state %d\n", ep->state);
return ZX_ERR_BAD_STATE;
}
if (req->header.length > 0) {
zx_status_t status = usb_request_physmap(req, xhci->bti_handle.get());
if (status != ZX_OK) {
zxlogf(ERROR, "%s: usb_request_physmap failed: %d\n", __FUNCTION__, status);
return status;
}
}
xhci_transfer_state_t* state = ep->transfer_state;
xhci_transfer_state_init(state, req, ep->ep_type, ep->max_packet_size);
if (req->header.length > UINT32_MAX) {
return ZX_ERR_BUFFER_TOO_SMALL;
}
auto length = static_cast<uint32_t>(req->header.length);
uint32_t interrupter_target = 0;
usb_setup_t* setup = (req->header.ep_address == 0 ? &req->setup : nullptr);
if (setup) {
// Setup Stage
xhci_trb_t* trb = ring->current_trb;
xhci_clear_trb(trb);
XHCI_SET_BITS32(&trb->ptr_low, SETUP_TRB_REQ_TYPE_START, SETUP_TRB_REQ_TYPE_BITS,
setup->bmRequestType);
XHCI_SET_BITS32(&trb->ptr_low, SETUP_TRB_REQUEST_START, SETUP_TRB_REQUEST_BITS,
setup->bRequest);
XHCI_SET_BITS32(&trb->ptr_low, SETUP_TRB_VALUE_START, SETUP_TRB_VALUE_BITS, setup->wValue);
XHCI_SET_BITS32(&trb->ptr_high, SETUP_TRB_INDEX_START, SETUP_TRB_INDEX_BITS, setup->wIndex);
XHCI_SET_BITS32(&trb->ptr_high, SETUP_TRB_LENGTH_START, SETUP_TRB_LENGTH_BITS, length);
XHCI_SET_BITS32(&trb->status, XFER_TRB_XFER_LENGTH_START, XFER_TRB_XFER_LENGTH_BITS, 8);
XHCI_SET_BITS32(&trb->status, XFER_TRB_INTR_TARGET_START, XFER_TRB_INTR_TARGET_BITS,
interrupter_target);
uint32_t control_bits = (length == 0 ? XFER_TRB_TRT_NONE :
(state->direction == USB_DIR_IN ? XFER_TRB_TRT_IN : XFER_TRB_TRT_OUT));
control_bits |= XFER_TRB_IDT; // immediate data flag
trb_set_control(trb, TRB_TRANSFER_SETUP, control_bits);
if (driver_get_log_flags() & DDK_LOG_SPEW) xhci_print_trb(ring, trb);
xhci_increment_ring(ring);
}
return ZX_OK;
}
// returns ZX_OK if req has been successfully queued,
// ZX_ERR_SHOULD_WAIT if we ran out of TRBs and need to try again later,
// or other error for a hard failure.
static zx_status_t xhci_continue_transfer_locked(xhci_t* xhci, xhci_slot_t* slot,
uint32_t ep_index, usb_request_t* req) {
xhci_endpoint_t* ep = &slot->eps[ep_index];
xhci_transfer_ring_t* ring = &ep->transfer_ring;
usb_header_t* header = &req->header;
xhci_transfer_state_t* state = ep->transfer_state;
size_t length = header->length;
size_t free_trbs = xhci_transfer_ring_free_trbs(&ep->transfer_ring);
uint8_t direction = state->direction;
bool isochronous = (ep->ep_type == USB_ENDPOINT_ISOCHRONOUS);
uint64_t frame = header->frame;
uint32_t interrupter_target = 0;
if (isochronous) {
if (length == 0) return ZX_ERR_INVALID_ARGS;
if (xhci->num_interrupts > 1) {
interrupter_target = ISOCH_INTERRUPTER;
}
}
if (frame != 0) {
if (!isochronous) {
zxlogf(ERROR, "frame scheduling only supported for isochronous transfers\n");
return ZX_ERR_INVALID_ARGS;
}
uint64_t current_frame = xhci_get_current_frame(xhci);
if (frame < current_frame) {
zxlogf(ERROR, "can't schedule transfer into the past\n");
return ZX_ERR_INVALID_ARGS;
}
if (frame - current_frame >= 895) {
// See XHCI spec, section 4.11.2.5
zxlogf(ERROR, "can't schedule transfer more than 895ms into the future\n");
return ZX_ERR_INVALID_ARGS;
}
}
// need to clean the cache for both IN and OUT transfers, invalidate only for IN
if (direction == USB_DIR_IN) {
usb_request_cache_flush_invalidate(req, 0, header->length);
} else {
usb_request_cache_flush(req, 0, header->length);
}
zx_status_t status = xhci_queue_data_trbs(ring, state, req, interrupter_target, isochronous);
if (status != ZX_OK) {
return status;
}
if (state->needs_status) {
if (free_trbs == 0) {
// will need to do this later
return ZX_ERR_SHOULD_WAIT;
}
// Status Stage
xhci_trb_t* trb = ring->current_trb;
xhci_clear_trb(trb);
XHCI_SET_BITS32(&trb->status, XFER_TRB_INTR_TARGET_START, XFER_TRB_INTR_TARGET_BITS,
interrupter_target);
uint32_t control_bits = (direction == USB_DIR_IN && length > 0 ? XFER_TRB_DIR_OUT
: XFER_TRB_DIR_IN);
// generate an event for the status phase so we can catch stalls or other errors
// before completing control transfer requests
control_bits |= XFER_TRB_IOC;
trb_set_control(trb, TRB_TRANSFER_STATUS, control_bits);
if (driver_get_log_flags() & DDK_LOG_SPEW) xhci_print_trb(ring, trb);
xhci_increment_ring(ring);
free_trbs--;
state->needs_status = false;
}
xhci_usb_request_internal_t* req_int = USB_REQ_TO_XHCI_INTERNAL(req);
// if we get here, then we are ready to ring the doorbell
// update dequeue_ptr to TRB following this transaction
req_int->context = ring->current_trb;
XHCI_WRITE32(&xhci->doorbells[header->device_id], ep_index + 1);
// it seems we need to ring the doorbell a second time when transitioning from STOPPED
while (xhci_get_ep_ctx_state(slot, ep) == EP_CTX_STATE_STOPPED) {
zx_nanosleep(zx_deadline_after(ZX_MSEC(1)));
XHCI_WRITE32(&xhci->doorbells[header->device_id], ep_index + 1);
}
return ZX_OK;
}
static void xhci_process_transactions_locked(xhci_t* xhci, xhci_slot_t* slot, uint8_t ep_index,
list_node_t* completed_reqs) {
xhci_endpoint_t* ep = &slot->eps[ep_index];
// loop until we fill our transfer ring or run out of requests to process
while (1) {
if (xhci_transfer_ring_free_trbs(&ep->transfer_ring) == 0) {
// no available TRBs - need to wait for some complete
return;
}
while (!ep->current_req) {
// start the next transaction in the queue
usb_request_t* req;
xhci_remove_from_list_head(xhci, &ep->queued_reqs, &req);
if (!req) {
// nothing to do
return;
}
zx_status_t status = xhci_start_transfer_locked(xhci, slot, ep_index, req);
if (status == ZX_OK) {
xhci_add_to_list_tail(xhci, &ep->pending_reqs, req);
ep->current_req = req;
} else {
req->response.status = status;
req->response.actual = 0;
xhci_add_to_list_tail(xhci, completed_reqs, req);
}
}
if (ep->current_req) {
usb_request_t* req = ep->current_req;
zx_status_t status = xhci_continue_transfer_locked(xhci, slot, ep_index, req);
if (status == ZX_ERR_SHOULD_WAIT) {
// no available TRBs - need to wait for some complete
return;
} else {
if (status != ZX_OK) {
req->response.status = status;
req->response.actual = 0;
xhci_delete_req_node(xhci, req);
xhci_add_to_list_tail(xhci, completed_reqs, req);
}
ep->current_req = nullptr;
}
}
}
}
zx_status_t xhci_queue_transfer(xhci_t* xhci, usb_request_t* req) {
uint32_t slot_id = req->header.device_id;
uint8_t ep_index = xhci_endpoint_index(req->header.ep_address);
__UNUSED usb_setup_t* setup = (ep_index == 0 ? &req->setup : nullptr);
zxlogf(LSPEW, "xhci_queue_transfer slot_id: %d setup: %p ep_index: %d length: %lu\n",
slot_id, setup, ep_index, req->header.length);
int rh_index = xhci_get_root_hub_index(xhci, slot_id);
if (rh_index >= 0) {
return xhci_rh_usb_request_queue(xhci, req, rh_index);
}
if (slot_id < 1 || slot_id > xhci->max_slots) {
return ZX_ERR_INVALID_ARGS;
}
if (ep_index >= XHCI_NUM_EPS) {
return ZX_ERR_INVALID_ARGS;
}
xhci_slot_t* slot = &xhci->slots[slot_id];
xhci_endpoint_t* ep = &slot->eps[ep_index];
if (!slot->sc) {
// slot no longer enabled
return ZX_ERR_IO_NOT_PRESENT;
}
list_node_t completed_reqs = LIST_INITIAL_VALUE(completed_reqs);
{
fbl::AutoLock al(&ep->lock);
zx_status_t status;
switch (ep->state) {
case EP_STATE_DEAD:
status = ZX_ERR_IO_NOT_PRESENT;
break;
case EP_STATE_RUNNING:
status = ZX_OK;
break;
case EP_STATE_PAUSED:
status = ZX_ERR_BAD_STATE;
break;
case EP_STATE_ERROR:
status = ZX_ERR_IO_INVALID;
break;
case EP_STATE_HALTED:
status = ZX_ERR_IO_REFUSED;
break;
case EP_STATE_DISABLED:
status = ZX_ERR_BAD_STATE;
break;
default:
status = ZX_ERR_INTERNAL;
break;
}
if (status != ZX_OK) {
return status;
}
xhci_add_to_list_tail(xhci, &ep->queued_reqs, req);
xhci_process_transactions_locked(xhci, slot, ep_index, &completed_reqs);
}
xhci_usb_request_internal_t* req_int = nullptr;
// call complete callbacks out of the lock
while ((req_int = list_remove_head_type(&completed_reqs,
xhci_usb_request_internal_t, node)) != nullptr) {
req = XHCI_INTERNAL_TO_USB_REQ(req_int);
usb_request_complete(req, req->response.status, req->response.actual,
&req_int->complete_cb);
}
return ZX_OK;
}
zx_status_t xhci_cancel_transfers(xhci_t* xhci, uint32_t slot_id, uint32_t ep_index) {
zxlogf(TRACE, "xhci_cancel_transfers slot_id: %d ep_index: %d\n", slot_id, ep_index);
if (slot_id < 1 || slot_id > xhci->max_slots) {
return ZX_ERR_INVALID_ARGS;
}
if (ep_index >= XHCI_NUM_EPS) {
return ZX_ERR_INVALID_ARGS;
}
xhci_slot_t* slot = &xhci->slots[slot_id];
xhci_endpoint_t* ep = &slot->eps[ep_index];
list_node_t completed_reqs = LIST_INITIAL_VALUE(completed_reqs);
usb_request_t* req;
zx_status_t status = ZX_OK;
ep->lock.Acquire();
if (ep->state == EP_STATE_HALTED) {
// xhci_reset_endpoint will be issued, when the transaction
// that caused the STALL is completed. Let xhci_reset_endpoint
// take care of resetting the endpoint to a running state.
ep->lock.Release();
return status;
}
if (!list_is_empty(&ep->pending_reqs)) {
// stop the endpoint and remove transactions that have already been queued
// in the transfer ring
ep->state = EP_STATE_PAUSED;
xhci_sync_command_t command;
xhci_sync_command_init(&command);
// command expects device context index, so increment ep_index by 1
uint32_t control = (slot_id << TRB_SLOT_ID_START) |
((ep_index + 1) << TRB_ENDPOINT_ID_START);
zx_status_t status = xhci_post_command(xhci, TRB_CMD_STOP_ENDPOINT, 0, control,
&command.context);
if (status != ZX_OK) {
ep->lock.Release();
return status;
}
// We can't block on command completion while holding the lock.
// It is safe to unlock here because no additional transactions will be
// queued on the endpoint when ep->state is EP_STATE_PAUSED.
ep->lock.Release();
int cc = xhci_sync_command_wait(&command);
if (cc != TRB_CC_SUCCESS) {
// TRB_CC_CONTEXT_STATE_ERROR is normal here in the case of a disconnected device,
// since by then the endpoint would already be in error state.
zxlogf(ERROR, "xhci_cancel_transfers: TRB_CMD_STOP_ENDPOINT failed cc: %d\n", cc);
return ZX_ERR_INTERNAL;
}
ep->lock.Acquire();
// TRB_CMD_STOP_ENDPOINT may have have completed a currently executing request
// but we may still have other pending requests. xhci_reset_dequeue_ptr_locked()
// will set the dequeue pointer after the last completed request.
while (xhci_remove_from_list_head(xhci, &ep->queued_reqs, &req)) {
req->response.status = ZX_ERR_CANCELED;
req->response.actual = 0;
xhci_add_to_list_head(xhci, &completed_reqs, req);
}
status = xhci_reset_dequeue_ptr_locked(xhci, slot_id, ep_index);
if (status == ZX_OK) {
ep->state = EP_STATE_RUNNING;
}
}
// elements of the queued_reqs list can simply be removed and completed.
while (xhci_remove_from_list_head(xhci, &ep->queued_reqs, &req)) {
req->response.status = ZX_ERR_CANCELED;
req->response.actual = 0;
xhci_add_to_list_head(xhci, &completed_reqs, req);
}
ep->lock.Release();
xhci_usb_request_internal_t* req_int;
// call complete callbacks out of the lock
while ((req_int = list_remove_head_type(&completed_reqs,
xhci_usb_request_internal_t, node)) != NULL) {
req = XHCI_INTERNAL_TO_USB_REQ(req_int);
usb_request_complete(req, req->response.status, req->response.actual,
&req_int->complete_cb);
}
return status;
}
static void xhci_control_complete(void* ctx, usb_request_t* req) {
sync_completion_signal((sync_completion_t*)ctx);
}
zx_status_t xhci_control_request(xhci_t* xhci, uint32_t slot_id, uint8_t request_type,
uint8_t request, uint16_t value, uint16_t index, void* data,
uint16_t length, size_t* out_actual) {
zxlogf(LTRACE, "xhci_control_request slot_id: %d type: 0x%02X req: %d value: %d index: %d "
"length: %d\n", slot_id, request_type, request, value, index, length);
// xhci_control_request is only used for reading first 8 bytes of the device descriptor,
// so it makes sense to pool them.
usb_request_t* req = usb_request_pool_get(&xhci->free_reqs, length);
if (req == nullptr) {
uint64_t total_req_size = sizeof(usb_request_t) + sizeof(xhci_usb_request_internal_t);
auto status = usb_request_alloc(&req, length, 0, total_req_size);
if (status != ZX_OK) {
return status;
}
}
usb_setup_t* setup = &req->setup;
setup->bmRequestType = request_type;
setup->bRequest = request;
setup->wValue = value;
setup->wIndex = index;
setup->wLength = length;
req->header.device_id = slot_id;
bool out = !!((request_type & USB_DIR_MASK) == USB_DIR_OUT);
if (length > 0 && out) {
usb_request_copy_to(req, data, length, 0);
}
sync_completion_t completion;
req->header.length = length;
usb_request_complete_t complete = {
.callback = xhci_control_complete,
.ctx = &completion,
};
xhci_request_queue(xhci, req, &complete);
auto status = sync_completion_wait(&completion, ZX_SEC(1));
if (status == ZX_OK) {
status = req->response.status;
} else if (status == ZX_ERR_TIMED_OUT) {
zxlogf(ERROR, "xhci_control_request ZX_ERR_TIMED_OUT\n");
sync_completion_reset(&completion);
status = xhci_cancel_transfers(xhci, slot_id, 0);
if (status == ZX_OK) {
sync_completion_wait(&completion, ZX_TIME_INFINITE);
status = ZX_ERR_TIMED_OUT;
}
}
zxlogf(TRACE, "xhci_control_transfer got %d\n", status);
if (status == ZX_OK && out_actual != nullptr) {
*out_actual = req->response.actual;
if (length > 0 && !out) {
usb_request_copy_from(req, data, req->response.actual, 0);
}
}
if (usb_request_pool_add(&xhci->free_reqs, req) != ZX_OK) {
zxlogf(TRACE, "xhci_control_transfer: Unable to add back request to the free pool\n");
usb_request_release(req);
}
zxlogf(TRACE, "xhci_control_request returning %d\n", status);
return status;
}
zx_status_t xhci_get_descriptor(xhci_t* xhci, uint32_t slot_id, uint8_t type, uint16_t value,
uint16_t index, void* data, uint16_t length, size_t* out_actual) {
return xhci_control_request(xhci, slot_id, USB_DIR_IN | type | USB_RECIP_DEVICE,
USB_REQ_GET_DESCRIPTOR, value, index, data, length, out_actual);
}
void xhci_handle_transfer_event(xhci_t* xhci, xhci_trb_t* trb) {
zxlogf(LTRACE, "xhci_handle_transfer_event: %08X %08X %08X %08X\n",
((uint32_t*)trb)[0], ((uint32_t*)trb)[1], ((uint32_t*)trb)[2], ((uint32_t*)trb)[3]);
uint32_t control = XHCI_READ32(&trb->control);
uint32_t status = XHCI_READ32(&trb->status);
uint32_t slot_id = READ_FIELD(control, TRB_SLOT_ID_START, TRB_SLOT_ID_BITS);
// ep_index is device context index, so decrement by 1 to get zero based index
auto ep_index = static_cast<uint8_t>(
READ_FIELD(control, TRB_ENDPOINT_ID_START, TRB_ENDPOINT_ID_BITS) - 1);
xhci_slot_t* slot = &xhci->slots[slot_id];
xhci_endpoint_t* ep = &slot->eps[ep_index];
xhci_transfer_ring_t* ring = &ep->transfer_ring;
uint32_t cc = READ_FIELD(status, EVT_TRB_CC_START, EVT_TRB_CC_BITS);
uint32_t length = READ_FIELD(status, EVT_TRB_XFER_LENGTH_START, EVT_TRB_XFER_LENGTH_BITS);
usb_request_t* req = nullptr;
list_node_t completed_reqs = LIST_INITIAL_VALUE(completed_reqs);
{
fbl::AutoLock al(&ep->lock);
zx_status_t result;
switch (cc) {
case TRB_CC_SUCCESS:
case TRB_CC_SHORT_PACKET:
result = length;
break;
case TRB_CC_BABBLE_DETECTED_ERROR:
zxlogf(TRACE, "xhci_handle_transfer_event: TRB_CC_BABBLE_DETECTED_ERROR\n");
result = ZX_ERR_IO_OVERRUN;
break;
case TRB_CC_TRB_ERROR:
zxlogf(TRACE, "xhci_handle_transfer_event: TRB_CC_TRB_ERROR\n");
int ep_ctx_state;
ep_ctx_state = xhci_get_ep_ctx_state(slot, ep);
/*
* For usb-c ethernet adapters on Intel xhci controller, we receive this error
* when a packet fails with NRDY token on the bus.see NET:97 for more details.
* Slow down the requests in the client when this error is received.
*/
if (ep_ctx_state == EP_CTX_STATE_ERROR) {
result = ZX_ERR_IO_INVALID;
} else {
result = ZX_ERR_IO;
}
break;
case TRB_CC_USB_TRANSACTION_ERROR:
case TRB_CC_STALL_ERROR: {
int ep_ctx_state = xhci_get_ep_ctx_state(slot, ep);
zxlogf(TRACE, "xhci_handle_transfer_event: cc %d ep_ctx_state %d\n", cc, ep_ctx_state);
if (ep_ctx_state == EP_CTX_STATE_HALTED) {
result = ZX_ERR_IO_REFUSED;
} else {
result = ZX_ERR_IO;
}
break;
}
case TRB_CC_RING_UNDERRUN:
// non-fatal error that happens when no transfers are available for isochronous
// endpoint
zxlogf(TRACE, "xhci_handle_transfer_event: TRB_CC_RING_UNDERRUN\n");
return;
case TRB_CC_RING_OVERRUN:
// non-fatal error that happens when no transfers are available for isochronous
// endpoint
zxlogf(TRACE, "xhci_handle_transfer_event: TRB_CC_RING_OVERRUN\n");
return;
case TRB_CC_MISSED_SERVICE_ERROR:
zxlogf(TRACE, "xhci_handle_transfer_event: TRB_CC_MISSED_SERVICE_ERROR\n");
result = ZX_ERR_IO_MISSED_DEADLINE;
break;
case TRB_CC_STOPPED:
case TRB_CC_STOPPED_LENGTH_INVALID:
case TRB_CC_STOPPED_SHORT_PACKET:
case TRB_CC_ENDPOINT_NOT_ENABLED_ERROR:
switch (ep->state) {
case EP_STATE_PAUSED:
result = ZX_ERR_CANCELED;
break;
case EP_STATE_DISABLED:
result = ZX_ERR_BAD_STATE;
break;
case EP_STATE_DEAD:
result = ZX_ERR_IO_NOT_PRESENT;
break;
default:
zxlogf(ERROR, "xhci_handle_transfer_event: bad state for stopped req: %d\n",
ep->state);
result = ZX_ERR_INTERNAL;
}
break;
default: {
int ep_ctx_state = xhci_get_ep_ctx_state(slot, ep);
zxlogf(ERROR,
"xhci_handle_transfer_event: unhandled transfer event condition"
" code %d ep_ctx_state %d: %08X %08X %08X %08X\n",
cc, ep_ctx_state, ((uint32_t*)trb)[0], ((uint32_t*)trb)[1], ((uint32_t*)trb)[2],
((uint32_t*)trb)[3]);
if (ep_ctx_state == EP_CTX_STATE_HALTED) {
result = ZX_ERR_IO_REFUSED;
} else if (ep_ctx_state == EP_CTX_STATE_ERROR) {
result = ZX_ERR_IO_INVALID;
} else {
result = ZX_ERR_IO;
}
break;
}
}
bool req_status_set = false;
if (trb->ptr && !list_is_empty(&ep->pending_reqs) && ep->state != EP_STATE_DISABLED &&
ep->state != EP_STATE_DEAD) {
if (control & EVT_TRB_ED) {
req = reinterpret_cast<usb_request_t*>(trb->ptr);
if (ep_index == 0) {
// For control requests we are expecting a second transfer event to signal the
// end of the status phase. So here we record the status and actual for the
// data phase but wait for the status phase to complete before completing the
// request.
slot->current_ctrl_req = req;
if (result < 0) {
req->response.status = result;
req->response.actual = 0;
} else {
req->response.status = 0;
req->response.actual = result;
}
return;
}
} else {
trb = xhci_read_trb_ptr(ring, trb);
if (trb_get_type(trb) == TRB_TRANSFER_STATUS && slot->current_ctrl_req) {
// complete current control request
req = slot->current_ctrl_req;
slot->current_ctrl_req = nullptr;
if (result < 0) {
// sometimes we receive stall errors in the status phase so update
// request status if necessary
req->response.status = result;
req->response.actual = 0;
}
req_status_set = true;
} else {
for (uint i = 0; i < TRANSFER_RING_SIZE && trb; i++) {
if (trb_get_type(trb) == TRB_TRANSFER_EVENT_DATA) {
req = reinterpret_cast<usb_request_t*>(trb->ptr);
break;
}
trb = xhci_get_next_trb(ring, trb);
}
}
}
}
int ep_ctx_state = xhci_get_ep_ctx_state(slot, ep);
if (ep_ctx_state != EP_CTX_STATE_RUNNING) {
zxlogf(TRACE, "xhci_handle_transfer_event: ep ep_ctx_state %d cc %d\n", ep_ctx_state,
cc);
}
if (!req) {
// no req expected for this condition code
if (cc != TRB_CC_STOPPED_LENGTH_INVALID) {
zxlogf(TRACE, "xhci_handle_transfer_event: unable to find request to complete!\n");
}
return;
}
// When transaction errors occur, we sometimes receive multiple events for the same
// transfer. Here we check to make sure that this event doesn't correspond to a transfer
// that has already been completed. In the typical case, the context will be found at the
// head of pending_reqs.
bool found_req = false;
usb_request_t* test;
xhci_usb_request_internal_t* req_int = nullptr;
list_for_every_entry(&ep->pending_reqs, req_int, xhci_usb_request_internal_t, node) {
test = XHCI_INTERNAL_TO_USB_REQ(req_int);
if (test == req) {
found_req = true;
break;
}
}
if (!found_req) {
zxlogf(TRACE, "xhci_handle_transfer_event: ignoring transfer event for completed "
"transfer\n");
return;
}
// update dequeue_ptr to TRB following this transaction
xhci_set_dequeue_ptr(ring, req_int->context);
// remove request from pending_reqs
xhci_delete_req_node(xhci, req);
if (!req_status_set) {
if (result < 0) {
req->response.status = result;
req->response.actual = 0;
} else {
req->response.status = 0;
req->response.actual = result;
}
}
xhci_add_to_list_head(xhci, &completed_reqs, req);
if (result == ZX_ERR_IO_REFUSED && ep->state != EP_STATE_DEAD) {
ep->state = EP_STATE_HALTED;
} else if (result == ZX_ERR_IO_INVALID && ep->state != EP_STATE_DEAD) {
ep->state = EP_STATE_ERROR;
} else if (ep->state == EP_STATE_RUNNING) {
xhci_process_transactions_locked(xhci, slot, ep_index, &completed_reqs);
}
}
// call complete callbacks out of the lock
xhci_usb_request_internal_t* req_int;
while ((req_int = list_remove_head_type(&completed_reqs,
xhci_usb_request_internal_t, node)) != NULL) {
req = XHCI_INTERNAL_TO_USB_REQ(req_int);
usb_request_complete(req, req->response.status, req->response.actual,
&req_int->complete_cb);
}
}
} // namespace usb_xhci