| // 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 "xhci-transfer.h" |
| |
| #include <stdio.h> |
| #include <string.h> |
| #include <threads.h> |
| #include <zircon/assert.h> |
| #include <zircon/hw/usb.h> |
| |
| #include <ddk/debug.h> |
| #include <ddk/phys-iter.h> |
| #include <ddk/protocol/usb/hci.h> |
| #include <fbl/auto_lock.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", 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(DEBUG, "xhci_reset_endpoint %d %d ep_ctx_state %d", 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", 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", 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", __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 (zxlog_level_enabled(TRACE)) |
| 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"); |
| 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"); |
| 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"); |
| 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 (zxlog_level_enabled(TRACE)) |
| 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(SERIAL, "xhci_queue_transfer slot_id: %d setup: %p ep_index: %d length: %lu", 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(DEBUG, "xhci_cancel_transfers slot_id: %d ep_index: %d", 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", 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(SERIAL, |
| "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) { |
| size_t copy_size = usb_request_copy_to(req, data, length, 0); |
| ZX_ASSERT(copy_size == length); |
| } |
| |
| 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"); |
| 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(DEBUG, "xhci_control_transfer got %d", status); |
| if (status == ZX_OK && out_actual != nullptr) { |
| *out_actual = req->response.actual; |
| |
| if (length > 0 && !out) { |
| size_t copy_size = usb_request_copy_from(req, data, req->response.actual, 0); |
| ZX_ASSERT(copy_size == req->response.actual); |
| } |
| } |
| |
| if (usb_request_pool_add(&xhci->free_reqs, req) != ZX_OK) { |
| zxlogf(DEBUG, "xhci_control_transfer: Unable to add back request to the free pool"); |
| usb_request_release(req); |
| } |
| |
| zxlogf(DEBUG, "xhci_control_request returning %d", 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(SERIAL, "xhci_handle_transfer_event: %08X %08X %08X %08X", ((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(DEBUG, "xhci_handle_transfer_event: TRB_CC_BABBLE_DETECTED_ERROR"); |
| result = ZX_ERR_IO_OVERRUN; |
| break; |
| case TRB_CC_TRB_ERROR: |
| zxlogf(DEBUG, "xhci_handle_transfer_event: TRB_CC_TRB_ERROR"); |
| 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(DEBUG, "xhci_handle_transfer_event: cc %d ep_ctx_state %d", 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(DEBUG, "xhci_handle_transfer_event: TRB_CC_RING_UNDERRUN"); |
| return; |
| case TRB_CC_RING_OVERRUN: |
| // non-fatal error that happens when no transfers are available for isochronous |
| // endpoint |
| zxlogf(DEBUG, "xhci_handle_transfer_event: TRB_CC_RING_OVERRUN"); |
| return; |
| case TRB_CC_MISSED_SERVICE_ERROR: |
| zxlogf(DEBUG, "xhci_handle_transfer_event: TRB_CC_MISSED_SERVICE_ERROR"); |
| 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", 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(DEBUG, "xhci_handle_transfer_event: ep ep_ctx_state %d cc %d", ep_ctx_state, cc); |
| } |
| |
| if (!req) { |
| // no req expected for this condition code |
| if (cc != TRB_CC_STOPPED_LENGTH_INVALID) { |
| zxlogf(DEBUG, "xhci_handle_transfer_event: unable to find request to complete!"); |
| } |
| 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(DEBUG, |
| "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 |