blob: b352c3078659de1c63459bc990f8920fd1f21f5d [file] [log] [blame]
// Copyright 2023 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 "src/devices/usb/drivers/xhci/xhci-endpoint.h"
#include <lib/fit/defer.h>
#include "src/devices/usb/drivers/xhci/usb-xhci.h"
namespace usb_xhci {
Endpoint::Endpoint(UsbXhci* hci, uint32_t device_id, uint8_t address)
: usb_endpoint::UsbEndpoint(hci->bti(), address), hci_(hci), device_id_(device_id) {
loop_.StartThread("endpoint-thread");
}
namespace {
inline usb_setup_t ToBanjo(fuchsia_hardware_usb_descriptor::UsbSetup setup) {
return usb_setup_t{
.bm_request_type = setup.bm_request_type(),
.b_request = setup.b_request(),
.w_value = setup.w_value(),
.w_index = setup.w_index(),
.w_length = setup.w_length(),
};
}
} // namespace
zx_status_t Endpoint::Init(EventRing* event_ring, fdf::MmioBuffer* mmio) {
return transfer_ring_.Init(hci_->GetPageSize(), hci_->bti(), event_ring,
hci_->Is32BitController(), mmio, hci_);
}
void Endpoint::OnUnbound(fidl::UnbindInfo info,
fidl::ServerEnd<fuchsia_hardware_usb_endpoint::Endpoint> server_end) {
if (hci_->Running()) {
auto status = hci_->UsbHciCancelAll(device_id_, ep_addr());
if (status != ZX_OK) {
zxlogf(ERROR, "Could not cancel all %d", status);
}
status = hci_->RunSynchronously(kPrimaryInterrupter,
hci_->UsbHciDisableEndpoint(device_id_, ep_addr()));
if (status != ZX_OK) {
zxlogf(ERROR, "Could not disable endpoint %d", status);
}
}
usb_endpoint::UsbEndpoint::OnUnbound(info, std::move(server_end));
}
void Endpoint::QueueRequests(QueueRequestsRequest& request,
QueueRequestsCompleter::Sync& completer) {
for (auto& req : request.req()) {
QueueRequest(usb::FidlRequest{std::move(req)});
}
uint8_t index = static_cast<uint8_t>(XhciEndpointIndex(ep_addr()) - 1);
hci_->RingDoorbell(hci_->GetDeviceState()[device_id_]->GetSlot(), ep_addr() ? 2 + index : 1);
}
void Endpoint::CancelAll(CancelAllCompleter::Sync& completer) {
auto status = hci_->UsbHciCancelAll(device_id_, ep_addr());
if (status != ZX_OK) {
completer.Reply(fit::as_error(status));
return;
}
completer.Reply(fit::ok());
}
void Endpoint::QueueRequest(usb_endpoint::RequestVariant request) {
if (!hci_->Running()) {
RequestComplete(ZX_ERR_IO_NOT_PRESENT, 0, std::move(request));
return;
}
{
fbl::AutoLock _(&hci_->GetDeviceState()[device_id_]->transaction_lock());
if (!hci_->GetDeviceState()[device_id_]->GetSlot()) {
RequestComplete(ZX_ERR_IO_NOT_PRESENT, 0, std::move(request));
return;
}
}
if (unlikely(ep_addr() == 0)) {
return ControlRequestQueue(std::move(request));
}
NormalRequestQueue(std::move(request));
}
void Endpoint::ControlRequestQueue(usb_endpoint::RequestVariant request) {
fbl::AutoLock transaction_lock(&hci_->GetDeviceState()[device_id_]->transaction_lock());
if (hci_->GetDeviceState()[device_id_]->IsDisconnecting()) {
// Device is disconnecting. Release lock because we no longer will be using device_state,
// complete request, and return from function.
transaction_lock.release();
RequestComplete(ZX_ERR_IO_NOT_PRESENT, 0, std::move(request));
return;
}
if (transfer_ring_.stalled()) {
transaction_lock.release();
RequestComplete(ZX_ERR_IO_REFUSED, 0, std::move(request));
return;
}
auto context = transfer_ring_.AllocateContext();
if (!context) {
transaction_lock.release();
RequestComplete(ZX_ERR_NO_MEMORY, 0, std::move(request));
return;
}
TransferRing::State transaction;
TRB* setup;
zx_status_t status = transfer_ring_.AllocateTRB(&setup, &transaction);
auto rollback_transaction = [=]() __TA_NO_THREAD_SAFETY_ANALYSIS {
transfer_ring_.Restore(transaction);
};
if (status != ZX_OK) {
rollback_transaction();
transaction_lock.release();
RequestComplete(status, 0, std::move(request));
return;
}
context->request.emplace(std::move(request));
UsbRequestState pending_transfer;
pending_transfer.context = std::move(context);
pending_transfer.setup = setup;
pending_transfer.transaction = transaction;
status = ControlRequestAllocationPhase(&pending_transfer);
auto call = fit::defer([&]() __TA_NO_THREAD_SAFETY_ANALYSIS {
rollback_transaction();
transaction_lock.release();
RequestComplete(status, 0, std::move(*context->request));
});
if (status != ZX_OK) {
return;
}
status = ControlRequestStatusPhase(&pending_transfer);
if (status != ZX_OK) {
return;
}
status = ControlRequestDataPhase(&pending_transfer);
if (status != ZX_OK) {
return;
}
ControlRequestSetupPhase(&pending_transfer);
ControlRequestCommit(&pending_transfer);
call.cancel();
}
zx_status_t Endpoint::ControlRequestAllocationPhase(UsbRequestState* state) {
state->setup_cycle = state->setup->status;
state->setup->status = 0;
size_t length = std::holds_alternative<usb::FidlRequest>(*state->context->request)
? std::get<usb::FidlRequest>(*state->context->request).length()
: std::get<Request>(*state->context->request).request()->header.length;
if (length) {
auto status = std::visit([&](auto&& req) -> zx_status_t { return req.PhysMap(hci_->bti()); },
*state->context->request);
if (status != ZX_OK) {
return status;
}
auto iters = get_iter(*state->context->request, k64KiB);
if (iters.is_error()) {
return iters.error_value();
}
TRB* current_trb = nullptr;
for (auto& iter : iters.value()) {
for (auto [paddr, len] : iter) {
if (!len) {
break;
}
state->packet_count++;
TRB* prev = current_trb;
zx_status_t status = transfer_ring_.AllocateTRB(&current_trb, nullptr);
if (status != ZX_OK) {
return status;
}
static_assert(sizeof(TRB*) == sizeof(uint64_t));
if (likely(prev)) {
prev->ptr = reinterpret_cast<uint64_t>(current_trb);
} else {
state->first_trb = current_trb;
}
}
}
}
return ZX_OK;
}
zx_status_t Endpoint::ControlRequestStatusPhase(UsbRequestState* state) {
state->interrupter = 0;
bool status_in = true;
// See table 4-7 in section 4.11.2.2
auto bm_request_type =
std::holds_alternative<Request>(*state->context->request)
? std::get<Request>(*state->context->request).request()->setup.bm_request_type
: std::get<usb::FidlRequest>(*state->context->request)
->information()
->control()
->setup()
->bm_request_type();
if (state->first_trb && (bm_request_type & USB_DIR_IN)) {
status_in = false;
}
zx_status_t status = transfer_ring_.AllocateTRB(&state->status_trb_ptr, nullptr);
if (status != ZX_OK) {
return status;
}
Control::FromTRB(state->status_trb_ptr)
.set_Cycle(state->status_trb_ptr->status)
.set_Type(Control::Status)
.ToTrb(state->status_trb_ptr);
state->status_trb_ptr->status = 0;
auto* status_trb = static_cast<Status*>(state->status_trb_ptr);
status_trb->set_DIRECTION(status_in).set_INTERRUPTER(state->interrupter).set_IOC(1);
return ZX_OK;
}
zx_status_t Endpoint::ControlRequestDataPhase(UsbRequestState* state) {
// Data stage
if (state->first_trb) {
auto iters = get_iter(*state->context->request, k64KiB);
if (iters.is_error()) {
return iters.error_value();
}
TRB* current = state->first_trb;
for (auto& iter : iters.value()) {
for (auto [paddr, len] : iter) {
if (!len) {
break;
}
state->packet_count--;
TRB* next = reinterpret_cast<TRB*>(current->ptr);
uint32_t pcs = current->status;
current->status = 0;
enum Control::Type type;
if (current == state->first_trb) {
type = Control::Data;
ControlData* data = reinterpret_cast<ControlData*>(current);
auto bm_request_type =
std::holds_alternative<Request>(*state->context->request)
? std::get<Request>(*state->context->request).request()->setup.bm_request_type
: std::get<usb::FidlRequest>(*state->context->request)
->information()
->control()
->setup()
->bm_request_type();
// Control transfers always get interrupter 0 (we consider those to be low-priority)
// TODO(https://fxbug.dev/42109334): Change bus snooping options based on input from higher-level
// drivers.
data->set_CHAIN(next != nullptr)
.set_DIRECTION((bm_request_type & USB_DIR_IN) != 0)
.set_INTERRUPTER(0)
.set_LENGTH(len)
.set_SIZE(state->packet_count)
.set_ISP(true)
.set_NO_SNOOP(!hci_->HasCoherentCache());
} else {
type = Control::Normal;
Normal* data = reinterpret_cast<Normal*>(current);
data->set_CHAIN(next != nullptr)
.set_INTERRUPTER(0)
.set_LENGTH(len)
.set_SIZE(state->packet_count)
.set_ISP(true)
.set_NO_SNOOP(!hci_->HasCoherentCache());
}
current->ptr = paddr;
Control::FromTRB(current).set_Cycle(pcs).set_Type(type).ToTrb(current);
current = next;
}
}
}
return ZX_OK;
}
void Endpoint::ControlRequestSetupPhase(UsbRequestState* state) {
// Setup phase (4.11.2.2)
auto bm_request_type =
std::holds_alternative<Request>(*state->context->request)
? std::get<Request>(*state->context->request).request()->setup.bm_request_type
: std::get<usb::FidlRequest>(*state->context->request)
->information()
->control()
->setup()
->bm_request_type();
const auto& setup = std::holds_alternative<Request>(*state->context->request)
? std::get<Request>(*state->context->request).request()->setup
: ToBanjo(*std::get<usb::FidlRequest>(*state->context->request)
->information()
->control()
->setup());
memcpy(&state->setup->ptr, &setup, sizeof(setup));
Setup* setup_trb = reinterpret_cast<Setup*>(state->setup);
setup_trb->set_INTERRUPTER(state->interrupter)
.set_length(8)
.set_IDT(1)
.set_TRT(((bm_request_type & USB_DIR_IN) != 0) ? Setup::IN : Setup::OUT);
hw_mb();
}
void Endpoint::ControlRequestCommit(UsbRequestState* state) {
// Start the transaction!
if (std::holds_alternative<Request>(*state->context->request) && !hci_->HasCoherentCache()) {
usb_request_cache_flush_invalidate(
std::get<Request>(*state->context->request).request(), 0,
std::get<Request>(*state->context->request).request()->header.length);
}
transfer_ring_.AssignContext(state->status_trb_ptr, std::move(state->context), state->first_trb);
Control::FromTRB(state->setup)
.set_Type(Control::Setup)
.set_Cycle(state->setup_cycle)
.ToTrb(state->setup);
transfer_ring_.CommitTransaction(state->transaction);
}
void Endpoint::NormalRequestQueue(usb_endpoint::RequestVariant request) {
UsbRequestState pending_transfer;
uint8_t index = static_cast<uint8_t>(XhciEndpointIndex(ep_addr()) - 1);
fbl::AutoLock transaction_lock(&hci_->GetDeviceState()[device_id_]->transaction_lock());
if (hci_->GetDeviceState()[device_id_]->IsDisconnecting()) {
transaction_lock.release();
RequestComplete(ZX_ERR_IO_NOT_PRESENT, 0, std::move(request));
return;
}
if (transfer_ring_.stalled()) {
transaction_lock.release();
RequestComplete(ZX_ERR_IO_REFUSED, 0, std::move(request));
return;
}
auto* control =
reinterpret_cast<uint32_t*>(hci_->GetDeviceState()[device_id_]->GetInputContext()->virt());
auto* endpoint_context = reinterpret_cast<EndpointContext*>(
reinterpret_cast<unsigned char*>(control) + (hci_->slot_size_bytes() * (2 + (index + 1))));
if (!transfer_ring_.active()) {
transaction_lock.release();
RequestComplete(ZX_ERR_INTERNAL, 0, std::move(request));
return;
}
pending_transfer.burst_size = endpoint_context->MaxBurstSize() + 1;
pending_transfer.max_packet_size = endpoint_context->MAX_PACKET_SIZE();
pending_transfer.context = transfer_ring_.AllocateContext();
if (!pending_transfer.context) {
transaction_lock.release();
RequestComplete(ZX_ERR_NO_MEMORY, 0, std::move(request));
return;
}
pending_transfer.context->request.emplace(std::move(request));
if (transfer_ring_.IsIsochronous()) {
// Isoc endpoints of the default interface (i.e. b_alternate_setting=0) are required to have a
// w_max_packet_size=0 to prevent enumerated devices from reserving bandwidth by default. We'll
// consider any request for a zero-length pipe as invalid (see USB 2.0 spec. 5.6.2).
if (pending_transfer.max_packet_size == 0) {
transaction_lock.release();
RequestComplete(ZX_ERR_INVALID_ARGS, 0, std::move(*pending_transfer.context->request));
return;
}
// Release the lock while we're sleeping to avoid blocking
// other operations.
hci_->GetDeviceState()[device_id_]->transaction_lock().Release();
auto status = WaitForIsochronousReady(
std::holds_alternative<usb::FidlRequest>(*pending_transfer.context->request)
? *std::get<usb::FidlRequest>(*pending_transfer.context->request)
->information()
->isochronous()
->frame_id()
: std::get<Request>(*pending_transfer.context->request).request()->header.frame);
hci_->GetDeviceState()[device_id_]->transaction_lock().Acquire();
if (status != ZX_OK) {
transaction_lock.release();
RequestComplete(status, 0, std::move(*pending_transfer.context->request));
return;
}
// Check again since we've re-acquired lock
if (hci_->GetDeviceState()[device_id_]->IsDisconnecting()) {
transaction_lock.release();
RequestComplete(ZX_ERR_IO_NOT_PRESENT, 0, std::move(*pending_transfer.context->request));
return;
}
if (transfer_ring_.stalled()) {
transaction_lock.release();
RequestComplete(ZX_ERR_IO_REFUSED, 0, std::move(*pending_transfer.context->request));
return;
}
if (!transfer_ring_.active()) {
transaction_lock.release();
RequestComplete(ZX_ERR_INTERNAL, 0, std::move(*pending_transfer.context->request));
return;
}
}
// Start the transaction
pending_transfer.transaction = transfer_ring_.SaveState();
auto rollback_transaction = [&]() __TA_NO_THREAD_SAFETY_ANALYSIS {
transfer_ring_.Restore(pending_transfer.transaction);
};
auto status = StartNormalTransaction(
&pending_transfer,
static_cast<uint8_t>(hci_->GetDeviceState()[device_id_]->GetInterrupterTarget()));
if (status != ZX_OK) {
rollback_transaction();
transaction_lock.release();
RequestComplete(status, 0, std::move(*pending_transfer.context->request));
return;
}
// Continue the transaction
status = ContinueNormalTransaction(&pending_transfer);
if (status != ZX_OK) {
rollback_transaction();
transaction_lock.release();
RequestComplete(status, 0, std::move(*pending_transfer.context->request));
return;
}
// Commit the transaction -- starting the actual transfer
CommitNormalTransaction(&pending_transfer);
}
zx_status_t Endpoint::WaitForIsochronousReady(uint64_t target_frame) {
// Cannot schedule more than 895 ms into the future per section 4.11.2.5
// in the xHCI specification (revision 1.2)
constexpr int kMaxSchedulingInterval = 895;
if (target_frame) {
uint64_t frame = hci_->UsbHciGetCurrentFrame();
while (static_cast<int32_t>(target_frame - frame) > kMaxSchedulingInterval) {
uint32_t time = static_cast<uint32_t>((target_frame - frame) - kMaxSchedulingInterval);
zx::nanosleep(zx::deadline_after(zx::msec(time)));
frame = hci_->UsbHciGetCurrentFrame();
}
if (target_frame < frame) {
return ZX_ERR_IO_MISSED_DEADLINE;
}
}
return ZX_OK;
}
zx_status_t Endpoint::StartNormalTransaction(UsbRequestState* state, uint8_t interrupter_target) {
size_t packet_count = 0;
// Normal transfer
auto status = std::visit([&](auto&& req) -> zx_status_t { return req.PhysMap(hci_->bti()); },
*state->context->request);
if (status != ZX_OK) {
return status;
}
size_t pending_len = std::holds_alternative<usb::FidlRequest>(*state->context->request)
? std::get<usb::FidlRequest>(*state->context->request).length()
: std::get<Request>(*state->context->request).request()->header.length;
uint32_t total_len = 0;
auto iters = get_iter(*state->context->request, k64KiB);
if (iters.is_error()) {
return iters.error_value();
}
for (auto& iter : iters.value()) {
for (auto [paddr, len] : iter) {
if (len > pending_len) {
len = pending_len;
}
if (!paddr) {
break;
}
if (!len) {
continue;
}
total_len += static_cast<uint32_t>(len);
packet_count++;
pending_len -= len;
}
}
if (pending_len) {
// Something doesn't add up here....
return ZX_ERR_BAD_STATE;
}
// Allocate contiguous memory
auto contig_trb_info = transfer_ring_.AllocateContiguous(packet_count);
if (contig_trb_info.is_error()) {
return contig_trb_info.error_value();
}
state->info = contig_trb_info.value();
state->total_len = total_len;
state->packet_count = packet_count;
state->first_cycle = state->info.first()[0].status;
state->first_trb = state->info.first().data();
state->last_trb = state->info.trbs.data() + (packet_count - 1);
state->interrupter = static_cast<uint8_t>(interrupter_target);
return ZX_OK;
}
zx_status_t Endpoint::ContinueNormalTransaction(UsbRequestState* state) {
// Data stage
size_t pending_len = std::holds_alternative<usb::FidlRequest>(*state->context->request)
? std::get<usb::FidlRequest>(*state->context->request).length()
: std::get<Request>(*state->context->request).request()->header.length;
auto current_nop = state->info.nop.data();
if (current_nop) {
while (Control::FromTRB(current_nop).Type() == Control::Nop) {
bool producer_cycle_state = current_nop->status;
bool cycle = (current_nop == state->first_trb) ? !producer_cycle_state : producer_cycle_state;
Control::FromTRB(current_nop).set_Cycle(cycle).ToTrb(current_nop);
current_nop->status = 0;
current_nop++;
}
}
if (state->first_trb) {
TRB* current = state->info.trbs.data();
auto iters = get_iter(*state->context->request, k64KiB);
if (iters.is_error()) {
return iters.error_value();
}
for (auto& iter : iters.value()) {
for (auto [paddr, len] : iter) {
if (!len) {
break;
}
len = std::min(len, pending_len);
pending_len -= len;
state->packet_count--;
TRB* next = current + 1;
if (next == state->last_trb + 1) {
next = nullptr;
}
uint32_t pcs = current->status;
current->status = 0;
enum Control::Type type;
if ((transfer_ring_.IsIsochronous() && state->first_trb == current)) {
// Force direct mode as workaround for USB audio latency issue.
type = Control::Isoch;
Isoch* data = reinterpret_cast<Isoch*>(current);
// Burst size is number of packets, not bytes
uint32_t burst_size = state->burst_size;
uint32_t packet_size = state->max_packet_size;
uint32_t packet_count = state->total_len / packet_size;
if (!packet_count) {
packet_count = 1;
}
// Number of bursts - 1
uint32_t burst_count = packet_count / burst_size;
if (burst_count) {
burst_count--;
}
// Zero-based last-burst-packet count (where 0 == 1 packet)
uint32_t last_burst_packet_count = packet_count % burst_size;
if (last_burst_packet_count) {
last_burst_packet_count--;
}
auto frame_id = std::holds_alternative<usb::FidlRequest>(*state->context->request)
? *std::get<usb::FidlRequest>(*state->context->request)
->information()
->isochronous()
->frame_id()
: std::get<Request>(*state->context->request).request()->header.frame;
data->set_CHAIN(next != nullptr)
.set_SIA(frame_id == 0)
.set_TLBPC(last_burst_packet_count)
.set_FrameID(frame_id % 2048)
.set_TBC(burst_count)
.set_INTERRUPTER(state->interrupter)
.set_LENGTH(len)
.set_SIZE(packet_count)
.set_NO_SNOOP(!hci_->HasCoherentCache())
.set_IOC(next == nullptr)
.set_ISP(true);
} else {
type = Control::Normal;
Normal* data = reinterpret_cast<Normal*>(current);
data->set_CHAIN(next != nullptr)
.set_INTERRUPTER(state->interrupter)
.set_LENGTH(len)
.set_SIZE(state->packet_count)
.set_NO_SNOOP(!hci_->HasCoherentCache())
.set_IOC(next == nullptr)
.set_ISP(true);
}
current->ptr = paddr;
Control::FromTRB(current)
.set_Cycle(unlikely(current == state->first_trb) ? !pcs : pcs)
.set_Type(type)
.ToTrb(current);
current = next;
}
}
}
return ZX_OK;
}
void Endpoint::CommitNormalTransaction(UsbRequestState* state) {
hw_mb();
// Start the transaction!
if (std::holds_alternative<Request>(*state->context->request) && !hci_->HasCoherentCache()) {
usb_request_cache_flush_invalidate(
std::get<Request>(*state->context->request).request(), 0,
std::get<Request>(*state->context->request).request()->header.length);
}
// In FIDL mode, we expect cache to be flushed already
transfer_ring_.AssignContext(state->last_trb, std::move(state->context), state->first_trb);
Control::FromTRB(state->first_trb).set_Cycle(state->first_cycle).ToTrb(state->first_trb);
transfer_ring_.CommitTransaction(state->transaction);
}
} // namespace usb_xhci