| // Copyright 2020 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-event-ring.h" |
| |
| #include <lib/async-loop/cpp/loop.h> |
| #include <lib/async/cpp/executor.h> |
| #include <lib/fpromise/promise.h> |
| #include <lib/zx/clock.h> |
| |
| #include "usb-xhci.h" |
| |
| namespace usb_xhci { |
| |
| // The minimum required number of event ring segment table entries. |
| static constexpr uint16_t kMinERSTEntries = 16; |
| |
| zx_status_t EventRingSegmentTable::Init(size_t page_size, const zx::bti& bti, bool is_32bit, |
| uint32_t erst_max, ERSTSZ erst_size, |
| const dma_buffer::BufferFactory& factory, |
| fdf::MmioBuffer* mmio) { |
| erst_size_ = erst_size; |
| bti_ = &bti; |
| page_size_ = page_size; |
| is_32bit_ = is_32bit; |
| mmio_.emplace(mmio->View(0)); |
| zx_status_t status = factory.CreatePaged(bti, page_size_, false, &erst_); |
| if (status != ZX_OK) { |
| return status; |
| } |
| if (is_32bit && (erst_->phys()[0] >= UINT32_MAX)) { |
| return ZX_ERR_NO_MEMORY; |
| } |
| |
| count_ = page_size / sizeof(ERSTEntry); |
| if (count_ > erst_max) { |
| count_ = erst_max; |
| } |
| entries_ = static_cast<ERSTEntry*>(erst_->virt()); |
| return ZX_OK; |
| } |
| |
| zx_status_t EventRingSegmentTable::AddSegment(zx_paddr_t paddr) { |
| if (offset_ >= count_) { |
| if (offset_ > count_) { |
| return ZX_ERR_BAD_STATE; |
| } |
| return ZX_ERR_NO_MEMORY; |
| } |
| ERSTEntry entry; |
| entry.address_low = static_cast<uint32_t>(paddr & UINT32_MAX); |
| entry.address_high = static_cast<uint32_t>(paddr >> 32); |
| entry.size = static_cast<uint16_t>(page_size_ / kMinERSTEntries); |
| entries_[offset_] = entry; |
| hw_mb(); |
| offset_++; |
| erst_size_.set_TableSize(offset_).WriteTo(&mmio_.value()); |
| erst_pressure_++; |
| return ZX_OK; |
| } |
| |
| zx_status_t EventRing::Init(size_t page_size, const zx::bti& bti, fdf::MmioBuffer* buffer, |
| bool is_32bit, uint32_t erst_max, ERSTSZ erst_size, ERDP erdp_reg, |
| IMAN iman_reg, uint8_t cap_length, HCSPARAMS1 hcs_params_1, |
| CommandRing* command_ring, DoorbellOffset doorbell_offset, UsbXhci* hci, |
| HCCPARAMS1 hcc_params_1, uint64_t* dcbaa, uint16_t interrupter) { |
| fbl::AutoLock l(&segment_mutex_); |
| erdp_reg_ = erdp_reg; |
| hcs_params_1_ = hcs_params_1; |
| mmio_ = buffer; |
| bti_ = &bti; |
| page_size_ = page_size; |
| is_32bit_ = is_32bit; |
| mmio_ = buffer; |
| iman_reg_ = iman_reg; |
| cap_length_ = cap_length; |
| command_ring_ = command_ring; |
| doorbell_offset_ = doorbell_offset; |
| hci_ = hci; |
| hcc_params_1_ = hcc_params_1; |
| dcbaa_ = dcbaa; |
| interrupter_ = interrupter; |
| auto status = |
| segments_.Init(page_size, bti, is_32bit, erst_max, erst_size, hci->buffer_factory(), mmio_); |
| if (status != ZX_OK) { |
| return status; |
| } |
| return AddSegmentIfNone(); |
| } |
| |
| void EventRing::RemovePressure() { |
| fbl::AutoLock l(&segment_mutex_); |
| segments_.RemovePressure(); |
| } |
| |
| size_t EventRing::GetPressure() { |
| fbl::AutoLock l(&segment_mutex_); |
| return segments_.Pressure(); |
| } |
| |
| zx_status_t EventRing::AddSegmentIfNone() { |
| if (!erdp_phys_) { |
| return AddSegment(); |
| } |
| return ZX_OK; |
| } |
| |
| zx_status_t EventRing::AddTRB() { |
| fbl::AutoLock l(&segment_mutex_); |
| trbs_++; |
| if (trbs_ == segments_.TrbCount()) { |
| zx_status_t status = AddSegment(); |
| if (status != ZX_OK) { |
| return status; |
| } |
| return ZX_OK; |
| } |
| return ZX_OK; |
| } |
| |
| zx_status_t EventRing::AddSegment() { |
| if (segments_.Pressure() < segments_.SegmentCount()) { |
| segments_.AddPressure(); |
| return ZX_OK; |
| } |
| std::unique_ptr<dma_buffer::ContiguousBuffer> buffer; |
| { |
| std::unique_ptr<dma_buffer::ContiguousBuffer> buffer_tmp; |
| zx_status_t status = hci_->buffer_factory().CreateContiguous( |
| *bti_, page_size_, |
| static_cast<uint32_t>(page_size_ == zx_system_get_page_size() ? 0 : page_size_ >> 12), |
| &buffer_tmp); |
| if (status != ZX_OK) { |
| return status; |
| } |
| buffer = std::move(buffer_tmp); |
| } |
| if (is_32bit_ && (buffer->phys() >= UINT32_MAX)) { |
| return ZX_ERR_NO_MEMORY; |
| } |
| zx_status_t status = segments_.AddSegment(buffer->phys()); |
| if (status != ZX_OK) { |
| return status; |
| } |
| bool needs_iterator = false; |
| if (!erdp_phys_) { |
| erdp_phys_ = buffer->phys(); |
| erdp_virt_ = static_cast<TRB*>(buffer->virt()); |
| needs_iterator = true; |
| } |
| buffers_.push_back(std::make_unique<SegmentBuf>(std::move(buffer), true)); |
| if (needs_iterator) { |
| buffers_it_ = buffers_.begin(); |
| } |
| return ZX_OK; |
| } |
| |
| TRBPromise EventRing::HandlePortStatusChangeEvent(uint8_t port_id) { |
| auto sc = PORTSC::Get(cap_length_, port_id).ReadFrom(mmio_); |
| std::optional<TRBPromise> pending_enumeration; |
| // Read status bits |
| bool needs_enum = false; |
| |
| // xHCI doesn't provide a way of retrieving the port speed prior to a device being fully |
| // online (without using ACPI or another out-of-band mechanism). |
| // In order to correctly enumerate devices, we use heuristics to try and determine |
| // whether or not a port is 2.0 or 3.0. |
| if (sc.CCS()) { |
| // Wait for the port to exit polling state, if applicable. |
| // Only 2.0 ports should go into a polling state, so if we get here, |
| // we can be sure that it's a 2.0 port. Some controllers may skip this step though.... |
| if ((sc.PLS() == PORTSC::Polling) || (hci_->GetPortState()[port_id - 1].is_connected && |
| !hci_->GetPortState()[port_id - 1].is_USB3)) { |
| // USB 2.0 port connect |
| if (!hci_->GetPortState()[port_id - 1].is_connected) { |
| // USB 2.0 requires a port reset to advance to U0 |
| Usb2DeviceAttach(port_id); |
| needs_enum = true; |
| } |
| } else { |
| // USB 3.0 port connect, since we got a connect status bit set, |
| // and were not polling. |
| if (!hci_->GetPortState()[port_id - 1].is_connected) { |
| Usb3DeviceAttach(port_id); |
| needs_enum = true; |
| } |
| if ((sc.PLS() == PORTSC::U0) && (sc.PED()) && (!sc.PR()) && |
| !hci_->GetPortState()[port_id - 1].link_active) { |
| // Set the link active bit here to prevent us from onlining the same device twice. |
| hci_->GetPortState()[port_id - 1].link_active = true; |
| needs_enum = false; |
| pending_enumeration = LinkUp(port_id); |
| } |
| } |
| |
| // Link could be active from connect status change above. |
| // To prevent enumerating a device twice, we ensure that the link wasn't previously active |
| // before enumerating. |
| if ((sc.PLS() == PORTSC::U0) && sc.CCS() && !(hci_->GetPortState()[port_id - 1].link_active)) { |
| if (!hci_->GetPortState()[port_id - 1].is_connected) { |
| // Spontaneous initialization of USB 3.0 port without going through |
| // CSC event. We know this is USB 3.0 since this cannot possibly happen |
| // with a 2.0 port. |
| hci_->GetPortState()[port_id - 1].is_USB3 = true; |
| hci_->GetPortState()[port_id - 1].is_connected = true; |
| } |
| hci_->GetPortState()[port_id - 1].link_active = true; |
| if (!hci_->GetPortState()[port_id - 1].is_USB3) { |
| // USB 2.0 specification section 9.2.6.3 |
| // states that we must wait 10 milliseconds. |
| needs_enum = false; |
| pending_enumeration = |
| hci_->Timeout(interrupter_, zx::deadline_after(zx::msec(10))) |
| .and_then([=](TRB*& result) { return LinkUp(static_cast<uint8_t>(port_id)); }) |
| .box(); |
| } else { |
| needs_enum = false; |
| pending_enumeration = LinkUp(static_cast<uint8_t>(port_id)); |
| } |
| } |
| |
| } else { |
| // For hubs, we need to take the device offline from the bus's standpoint before tearing down |
| // the hub. This means that the slot has to be kept alive until the hub driver is removed. |
| hci_->GetPortState()[port_id - 1].retry = false; |
| hci_->GetPortState()[port_id - 1].link_active = false; |
| hci_->GetPortState()[port_id - 1].is_connected = false; |
| hci_->GetPortState()[port_id - 1].is_USB3 = false; |
| if (hci_->GetPortState()[port_id - 1].slot_id) { |
| ScheduleTask(hci_->DeviceOffline(hci_->GetPortState()[port_id - 1].slot_id, nullptr).box()); |
| } |
| } |
| |
| // Update registers if not init |
| if (sc.OCC()) { |
| bool overcurrent = sc.OCA(); |
| PORTSC::Get(cap_length_, port_id) |
| .FromValue(0) |
| .set_CCS(sc.CCS()) |
| .set_PortSpeed(sc.PortSpeed()) |
| .set_PIC(sc.PIC()) |
| .set_PLS(sc.PLS()) |
| .set_PP(sc.PP()) |
| .set_OCC(1) |
| .WriteTo(mmio_); |
| if (overcurrent) { |
| zxlogf(ERROR, "Port %i has overcurrent active.", static_cast<int>(port_id)); |
| } else { |
| zxlogf(ERROR, "Overcurrent event on port %i cleared.", static_cast<int>(port_id)); |
| } |
| } |
| if (sc.CSC()) { |
| // Connect status change |
| hci_->GetPortState()[port_id - 1].retry = false; |
| PORTSC::Get(cap_length_, port_id) |
| .FromValue(0) |
| .set_CCS(sc.CCS()) |
| .set_PLC(sc.PLC()) |
| .set_PortSpeed(sc.PortSpeed()) |
| .set_PIC(sc.PIC()) |
| .set_PLS(sc.PLS()) |
| .set_PP(sc.PP()) |
| .set_CSC(sc.CSC()) |
| .WriteTo(mmio_); |
| } |
| if (sc.PEC()) { |
| return fpromise::make_error_promise(ZX_ERR_BAD_STATE); |
| } |
| if (sc.PRC() || sc.WRC()) { |
| PORTSC::Get(cap_length_, port_id) |
| .FromValue(0) |
| .set_CCS(sc.CCS()) |
| .set_PortSpeed(sc.PortSpeed()) |
| .set_PIC(sc.PIC()) |
| .set_PLS(sc.PLS()) |
| .set_PP(sc.PP()) |
| .set_PRC(sc.PRC()) |
| .set_WRC(sc.WRC()) |
| .WriteTo(mmio_); |
| } |
| if (pending_enumeration.has_value()) { |
| return *std::move(pending_enumeration); |
| } |
| if (needs_enum) { |
| return WaitForPortStatusChange(port_id) |
| .and_then([=](TRB*& trb) { |
| // Retry enumeration |
| HandlePortStatusChangeEventInterrupt(port_id, true); |
| return fpromise::ok(trb); |
| }) |
| .box(); |
| } |
| return fpromise::make_ok_promise(static_cast<TRB*>(nullptr)); |
| } |
| |
| TRBPromise EventRing::WaitForPortStatusChange(uint8_t port_id) { |
| fpromise::bridge<TRB*, zx_status_t> bridge; |
| auto context = hci_->GetCommandRing()->AllocateContext(); |
| context->completer = std::move(bridge.completer); |
| hci_->GetPortState()[port_id - 1].wait_for_port_status_change_ = std::move(context); |
| return bridge.consumer.promise(); |
| } |
| |
| void EventRing::CallPortStatusChanged(fbl::RefPtr<PortStatusChangeState> state) { |
| if (state->port_index < state->port_count) { |
| ScheduleTask(HandlePortStatusChangeEvent(static_cast<uint8_t>(state->port_index)) |
| .then([=](fpromise::result<TRB*, zx_status_t>& trb) |
| -> fpromise::result<TRB*, zx_status_t> { |
| if (trb.is_error()) { |
| if (trb.error() == ZX_ERR_BAD_STATE) { |
| return trb; |
| } |
| } |
| state->port_index++; |
| CallPortStatusChanged(state); |
| return fpromise::ok(nullptr); |
| }) |
| .box()); |
| } else { |
| if (enumeration_queue_.is_empty()) { |
| enumerating_ = false; |
| } else { |
| enumerating_ = true; |
| auto enum_task = enumeration_queue_.pop_front(); |
| ScheduleTask(HandlePortStatusChangeEvent(enum_task->port_number) |
| .then([this, state, task = std::move(enum_task)]( |
| fpromise::result<TRB*, zx_status_t>& trb) mutable { |
| if (trb.is_error()) { |
| if (trb.error() == ZX_ERR_BAD_STATE) { |
| return trb; |
| } |
| task->completer->complete_error(trb.error()); |
| } else { |
| task->completer->complete_ok(trb.value()); |
| } |
| state->port_index = state->port_count; |
| CallPortStatusChanged(state); |
| return trb; |
| })); |
| } |
| } |
| } |
| |
| void EventRing::HandlePortStatusChangeEventInterrupt(uint8_t port_id, bool preempt) { |
| auto ctx = hci_->GetCommandRing()->AllocateContext(); |
| ctx->port_number = port_id; |
| fpromise::bridge<TRB*, zx_status_t> bridge; |
| ctx->completer = std::move(bridge.completer); |
| ScheduleTask(bridge.consumer.promise() |
| .then([=](fpromise::result<TRB*, zx_status_t>& result) { return result; }) |
| .box()); |
| if (preempt) { |
| enumeration_queue_.push_front(std::move(ctx)); |
| } else { |
| enumeration_queue_.push_back(std::move(ctx)); |
| } |
| if (!enumerating_) { |
| auto state = fbl::MakeRefCounted<PortStatusChangeState>(0, 0); |
| CallPortStatusChanged(std::move(state)); |
| } |
| } |
| |
| zx_status_t EventRing::Ring0Bringup() { |
| hci_->WaitForBringup(); |
| enumerating_ = false; |
| return ZX_OK; |
| } |
| |
| void EventRing::ScheduleTask(fpromise::promise<TRB*, zx_status_t> promise) { |
| { |
| auto continuation = promise.then([=](fpromise::result<TRB*, zx_status_t>& result) { |
| if (result.is_error()) { |
| // ZX_ERR_BAD_STATE is a special value that we use to signal |
| // a fatal error in xHCI. When this occurs, we should immediately |
| // attempt to shutdown the controller. This error cannot be recovered from. |
| if (result.error() == ZX_ERR_BAD_STATE) { |
| hci_->Shutdown(ZX_ERR_BAD_STATE); |
| } |
| } |
| return result; |
| }); |
| executor_.schedule_task(std::move(continuation)); |
| } |
| } |
| |
| void EventRing::RunUntilIdle() { executor_.run_until_idle(); } |
| |
| std::variant<bool, std::unique_ptr<TRBContext>> EventRing::StallWorkaroundForDefectiveHubs( |
| std::unique_ptr<TRBContext> context) { |
| // Workaround for full-speed hub issue in Gateway keyboard |
| auto request = context->request->request(); |
| if ((request->header.ep_address == 0) && (request->setup.b_request == USB_REQ_GET_DESCRIPTOR) && |
| (request->setup.w_index == 0) && (request->setup.w_value == (USB_DT_DEVICE_QUALIFIER << 8))) { |
| usb_device_qualifier_descriptor_t* desc; |
| if ((context->request->Mmap(reinterpret_cast<void**>(&desc)) == ZX_OK) && |
| (request->header.length >= sizeof(desc))) { |
| desc->b_device_protocol = |
| 0; // Don't support multi-TT unless we're sure the device supports it. |
| ScheduleTask(hci_->UsbHciResetEndpointAsync(request->header.device_id, 0) |
| .and_then([ctx = std::move(context)](TRB*& result) { |
| ctx->request->Complete(ZX_OK, sizeof(*desc)); |
| return fpromise::ok(result); |
| })); |
| |
| return true; |
| } |
| } |
| return context; |
| } |
| |
| Control EventRing::AdvanceErdp() { |
| enum { |
| STOP = 0, |
| INCREMENT = 1, |
| NEXT = 2, |
| WRAP_AROUND = 3, |
| } action = STOP; |
| |
| reevaluate_ = false; |
| fbl::AutoLock l(&segment_mutex_); |
| if (unlikely((reinterpret_cast<size_t>(erdp_virt_ + 1) / 4096) != |
| (reinterpret_cast<size_t>(erdp_virt_) / 4096))) { |
| // Page transition -- next buffer |
| auto next_buffer = buffers_it_; |
| next_buffer++; |
| ZX_DEBUG_ASSERT(next_buffer != buffers_it_); |
| if (unlikely(next_buffer == buffers_.end())) { |
| // Last buffer: wrap around |
| action = WRAP_AROUND; |
| buffers_it_->new_segment = false; |
| } else if (unlikely(next_buffer->new_segment)) { |
| // New segment |
| // Check for valid Completion Code |
| if (static_cast<CommandCompletionEvent*>(next_buffer->buf->virt())->CompletionCode() == |
| CommandCompletionEvent::Invalid) { |
| // Invalid completion code. New segment not in use yet. |
| if (Control::FromTRB(reinterpret_cast<TRB*>(buffers_.front().buf->virt())).Cycle() == |
| ccs_) { |
| // Empty ring, according to spec, we should re-evaluate next time. Set the reevaluate_ bit |
| // to true and return an invalid TRB to stop advancement of pointer. |
| reevaluate_ = true; |
| return Control::Get().FromValue(0).set_Cycle(!ccs_); |
| } |
| // Non-empty ring. Wrap around to beginning. |
| action = WRAP_AROUND; |
| } else { |
| // Valid completion code. New segment already in use. |
| action = NEXT; |
| } |
| } else { |
| // Not new segment. |
| action = NEXT; |
| } |
| buffers_it_->new_segment = false; |
| } else { |
| action = INCREMENT; |
| } |
| |
| switch (action) { |
| case INCREMENT: { |
| // Increment within segment |
| erdp_virt_++; |
| erdp_phys_ += sizeof(TRB); |
| } break; |
| case NEXT: { |
| // Next segment |
| buffers_it_++; |
| erdp_virt_ = reinterpret_cast<TRB*>((*buffers_it_).buf->virt()); |
| erdp_phys_ = (*buffers_it_).buf->phys(); |
| segment_index_ = (segment_index_ + 1) & 0b111; |
| } break; |
| case WRAP_AROUND: { |
| // Wrap around to first segment |
| ccs_ = !ccs_; |
| buffers_it_ = buffers_.begin(); |
| erdp_virt_ = reinterpret_cast<TRB*>((*buffers_it_).buf->virt()); |
| erdp_phys_ = (*buffers_it_).buf->phys(); |
| segment_index_ = 0; |
| } break; |
| default: { |
| zxlogf(ERROR, "This should not happen."); |
| } break; |
| } |
| |
| trbs_--; |
| if (unlikely(buffers_it_->new_segment)) { |
| // New buffer. CCS is invalid. Increment only if completion code is not invalid. |
| return Control::FromTRB(erdp_virt_) |
| .set_Cycle((static_cast<CommandCompletionEvent*>(erdp_virt_)->CompletionCode() != |
| CommandCompletionEvent::Invalid) |
| ? ccs_ |
| : !ccs_); |
| } |
| return Control::FromTRB(erdp_virt_); |
| } |
| |
| zx_status_t EventRing::HandleIRQ() { |
| iman_reg_.set_IP(1).set_IE(1).WriteTo(mmio_); |
| bool avoid_yield = false; |
| zx_paddr_t last_phys = 0; |
| // avoid_yield is used to indicate that we are in "realtime mode" |
| // When in this mode, we should avoid yielding our timeslice to the scheduler |
| // if at all possible, because yielding could result in us getting behind on our |
| // deadlines. Currently; we only ever need this on systems |
| // that don't support cache coherency where we may have to go through the loop |
| // several times due to stale values in the cache (after invalidating of course). |
| // On systems with a coherent cache this isn't necessary. |
| // Additionally; if we had a guarantee from the scheduler that we |
| // would be woken up in <125 microseconds (length of USB frame), |
| // we could safely yield after flushing our caches and wouldn't need this loop. |
| do { |
| avoid_yield = false; |
| for (Control control = reevaluate_ ? AdvanceErdp() : Control::FromTRB(erdp_virt_); |
| control.Cycle() == ccs_; control = AdvanceErdp()) { |
| switch (control.Type()) { |
| case Control::PortStatusChangeEvent: { |
| // Section 4.3 -- USB device intialization |
| // Section 6.4.2.3 (Port Status change TRB) |
| auto change_event = static_cast<PortStatusChangeEvent*>(erdp_virt_); |
| uint8_t port_id = static_cast<uint8_t>(change_event->PortID()); |
| auto event = std::move(hci_->GetPortState()[port_id - 1].wait_for_port_status_change_); |
| // Resume interrupted wait |
| if (event) { |
| event->completer->complete_ok(nullptr); |
| } else { |
| HandlePortStatusChangeEventInterrupt(port_id); |
| } |
| } break; |
| case Control::CommandCompletionEvent: { |
| auto completion_event = static_cast<CommandCompletionEvent*>(erdp_virt_); |
| if (completion_event->CompletionCode() != CommandCompletionEvent::Success) { |
| } |
| TRB* trb = command_ring_->PhysToVirt(erdp_virt_->ptr); |
| // Advance dequeue pointer |
| std::unique_ptr<TRBContext> context; |
| zx_status_t status = command_ring_->CompleteTRB(trb, &context); |
| if (status != ZX_OK) { |
| hci_->Shutdown(ZX_ERR_BAD_STATE); |
| return ZX_ERR_BAD_STATE; |
| } |
| if (status != ZX_OK) { |
| hci_->Shutdown(status); |
| return status; |
| } |
| if (completion_event->CompletionCode() == CommandCompletionEvent::SlotNotEnabledError) { |
| break; |
| } |
| // Invoke the callback to pre-process the command first. |
| // The command MAY mutate the state of the completion event. |
| // It is important that it be called prior to further processing of the event. |
| if (context->completer.has_value()) { |
| context->completer.value().complete_ok(completion_event); |
| } |
| } break; |
| case Control::TransferEvent: { |
| auto completion = static_cast<TransferEvent*>(erdp_virt_); |
| auto state = &hci_->GetDeviceState()[completion->SlotID() - 1]; |
| fbl::AutoLock l(&state->transaction_lock()); |
| if (!state->IsValid()) { |
| break; |
| } |
| std::unique_ptr<TRBContext> context; |
| TransferRing* ring; |
| uint8_t endpoint_id = static_cast<uint8_t>(completion->EndpointID() - 1); |
| if (unlikely(endpoint_id == 0)) { |
| ring = &state->GetTransferRing(); |
| } else { |
| ring = &state->GetTransferRing(endpoint_id - 1); |
| } |
| if (completion->CompletionCode() == CommandCompletionEvent::RingOverrun) { |
| break; |
| } |
| if (completion->CompletionCode() == CommandCompletionEvent::RingUnderrun) { |
| break; |
| } |
| TRB* trb; |
| if (unlikely(!erdp_virt_->ptr || (completion->CompletionCode() == |
| CommandCompletionEvent::EndpointNotEnabledError))) { |
| trb = nullptr; |
| } else { |
| trb = ring->PhysToVirt(erdp_virt_->ptr); |
| } |
| if (completion->CompletionCode() == CommandCompletionEvent::MissedServiceError) { |
| // Resynchronize (4.11.2.5.2) |
| // Advance until resynchronized or ring is exhausted |
| auto completions = ring->Resynchronize(hci_->UsbHciGetCurrentFrame()); |
| l.release(); |
| for (auto& completion : completions) { |
| completion.request->Complete(ZX_ERR_IO, 0); |
| } |
| break; |
| } |
| |
| zx_status_t status = ZX_ERR_IO; |
| size_t short_transfer_len = 0; |
| TRB* first_trb = trb; |
| if (trb) { |
| if (completion->CompletionCode() == CommandCompletionEvent::ShortPacket) { |
| ring->HandleShortPacket(trb, &short_transfer_len, &first_trb, |
| completion->TransferLength()); |
| if (trb != first_trb) { |
| // We'll get a second event for this TRB -- but we need to log the fact that this |
| // was a short transfer. |
| break; |
| } |
| } |
| status = ring->CompleteTRB(first_trb, &context); |
| } |
| if (completion->CompletionCode() == CommandCompletionEvent::StallError) { |
| ring->set_stall(true); |
| auto completions = ring->TakePendingTRBs(); |
| l.release(); |
| if (context) { |
| if (completions.is_empty()) { |
| auto result = StallWorkaroundForDefectiveHubs(std::move(context)); |
| if (std::holds_alternative<bool>(result)) { |
| break; |
| } |
| context = std::move(std::get<std::unique_ptr<TRBContext>>(result)); |
| } |
| context->request->Complete(ZX_ERR_IO_REFUSED, 0); |
| } |
| for (auto cb = completions.begin(); cb != completions.end(); cb++) { |
| cb->request->Complete(ZX_ERR_IO_REFUSED, 0); |
| } |
| |
| break; |
| } |
| if (status != ZX_OK) { |
| auto completions = ring->TakePendingTRBs(); |
| l.release(); |
| if (context) { |
| context->request->Complete(ZX_ERR_IO, 0); |
| } |
| for (auto cb = completions.begin(); cb != completions.end(); cb++) { |
| cb->request->Complete(ZX_ERR_IO, 0); |
| } |
| ring->ResetShortCount(); |
| // NOTE: No need to shutdown the whole slot. |
| // It may only be an endpoint-specific failure. |
| break; |
| } |
| l.release(); |
| |
| if ((completion->CompletionCode() != CommandCompletionEvent::Success) && |
| (completion->CompletionCode() != CommandCompletionEvent::ShortPacket)) { |
| // asix-88179 will stall the endpoint if we're sending data too fast. |
| // The driver expects us to give it a ZX_ERR_IO_INVALID response when |
| // this happens. |
| context->request->Complete(ZX_ERR_IO_INVALID, 0); |
| break; |
| } |
| if (context->short_length || context->transfer_len_including_short_trb) { |
| context->request->Complete( |
| ZX_OK, context->transfer_len_including_short_trb - context->short_length); |
| } else { |
| context->request->Complete(ZX_OK, context->request->request()->header.length); |
| } |
| ring->ResetShortCount(); |
| } break; |
| case Control::MFIndexWrapEvent: { |
| hci_->MfIndexWrapped(); |
| } break; |
| case Control::HostControllerEvent: { |
| // NOTE: We can't really do anything here. |
| // This typically indicates some kind of error condition. |
| // If something strange is happening, it might be a good idea |
| // to add a printf here and log the completion code. |
| } break; |
| } |
| } |
| if (last_phys != erdp_phys_) { |
| executor_.run_until_idle(); |
| { |
| fbl::AutoLock l(&segment_mutex_); |
| erdp_reg_ = |
| erdp_reg_.set_Pointer(erdp_phys_).set_DESI(segment_index_).set_EHB(1).WriteTo(mmio_); |
| } |
| last_phys = erdp_phys_; |
| } |
| if (!hci_->HasCoherentState()) { |
| // Check for stale value in cache |
| InvalidatePageCache(erdp_virt_, ZX_CACHE_FLUSH_INVALIDATE | ZX_CACHE_FLUSH_DATA); |
| if (Control::FromTRB(erdp_virt_).Cycle() == ccs_) { |
| avoid_yield = true; |
| } |
| } |
| } while (avoid_yield); |
| return ZX_OK; |
| } |
| |
| TRBPromise EventRing::LinkUp(uint8_t port_id) { |
| // Port is in U0 state (link up) |
| // Enumerate device |
| return EnumerateDevice(hci_, port_id, std::nullopt); |
| } |
| |
| void EventRing::Usb2DeviceAttach(uint16_t port_id) { |
| hci_->GetPortState()[port_id - 1].is_connected = true; |
| hci_->GetPortState()[port_id - 1].is_USB3 = false; |
| auto sc = PORTSC::Get(cap_length_, port_id).ReadFrom(mmio_); |
| PORTSC::Get(cap_length_, port_id) |
| .FromValue(0) |
| .set_CCS(sc.CCS()) |
| .set_PortSpeed(sc.PortSpeed()) |
| .set_PIC(sc.PIC()) |
| .set_PLS(sc.PLS()) |
| .set_PP(sc.PP()) |
| .set_PR(1) |
| .WriteTo(mmio_); |
| } |
| |
| void EventRing::Usb3DeviceAttach(uint16_t port_id) { |
| hci_->GetPortState()[port_id - 1].is_connected = true; |
| hci_->GetPortState()[port_id - 1].is_USB3 = true; |
| } |
| |
| } // namespace usb_xhci |