blob: 810208dbd97775bca3d044aa002a099532dcc428 [file] [log] [blame]
// Copyright 2021 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-device-state.h"
#include "src/devices/usb/drivers/xhci/usb-xhci.h"
namespace usb_xhci {
DeviceState::~DeviceState() {
// Each DeviceState corresponds to one slot. When the device is removed, we should call
// DisableSlotCommand on the corresponding slot.
if (hci_) {
auto status =
hci_->RunSynchronously(kPrimaryInterrupter, hci_->DisableSlotCommand(*this).box());
if (status != ZX_OK) {
zxlogf(ERROR, "Could not DisableSlot for %u on DeviceState destruction %d", GetSlot(),
status);
}
}
fbl::AutoLock _(&transaction_lock_);
disconnecting_ = true;
input_context_.reset();
device_context_.reset();
slot_ = 0;
hub_.reset();
}
zx_status_t DeviceState::InitEndpoint(uint8_t ep_addr, EventRing* event_ring, fdf::MmioBuffer* mmio)
__TA_REQUIRES(transaction_lock_) {
return GetEndpoint(XhciEndpointIndex(ep_addr) - 1).Init(event_ring, mmio);
}
zx_status_t DeviceState::InitializeSlotBuffer(const UsbXhci& hci, uint8_t slot_id, uint8_t port_id,
const std::optional<HubInfo>& hub_info,
std::unique_ptr<dma_buffer::PagedBuffer>* out) {
// Section 4.3.3
// 6.2.5 (Input Context initialization)
std::unique_ptr<dma_buffer::PagedBuffer> buffer;
zx_status_t status =
hci.buffer_factory().CreatePaged(hci.bti(), zx_system_get_page_size(), false, &buffer);
if (status != ZX_OK) {
return status;
}
if (hci.Is32BitController() && (buffer->phys()[0] >= UINT32_MAX)) {
return ZX_ERR_NO_MEMORY;
}
// 6.2.5.1 -- Initialize input control context
// NOTE: Input Control Context consumes 64 bytes if CSZ is 1
// Enable bit starts at offset 2
auto control = static_cast<uint32_t*>(buffer->virt());
control[1] = 0x3; // Enable both slot and endpoint context.
size_t slot_size = hci.slot_size_bytes();
// Initialize input slot context data structure (6.2.2) with 1 context entry
// Set root hub port number to port number and context entries to 1
auto slot_context =
reinterpret_cast<SlotContext*>(reinterpret_cast<unsigned char*>(control) + slot_size);
if (hub_info) {
slot_context->set_CONTEXT_ENTRIES(1)
.set_ROUTE_STRING(hub_info->route_string)
.set_PORTNO(hub_info->rh_port)
.set_SPEED(hub_info->speed)
.set_INTERRUPTER_TARGET(interrupter_target_);
} else {
slot_context->set_CONTEXT_ENTRIES(1).set_PORTNO(port_id).set_SPEED(hci.GetPortSpeed(port_id));
}
*out = std::move(buffer);
return ZX_OK;
}
zx_status_t DeviceState::InitializeEndpointContext(const UsbXhci& hci, uint8_t slot_id,
uint8_t port_id,
std::optional<HubInfo>& hub_info,
dma_buffer::PagedBuffer* slot_context_buffer) {
CRCR trb_phys = tr_.transfer_ring().phys(hci.CapLength());
auto* control = static_cast<uint32_t*>(slot_context_buffer->virt());
size_t slot_size = hci.slot_size_bytes();
auto slot_context =
reinterpret_cast<SlotContext*>(reinterpret_cast<unsigned char*>(control) + slot_size);
// Initialize endpoint context 0
// Set CERR to 3, TR dequeue pointer, max packet size, EP type = control, DCS = 1
auto endpoint_context = reinterpret_cast<EndpointContext*>(
reinterpret_cast<unsigned char*>(control) + (slot_size * 2));
uint16_t mps = 8;
usb_speed_t speed;
if (hub_info) {
speed = static_cast<uint8_t>(hub_info->speed);
// TODO(https://fxbug.dev/42109653): USB 3.1 support. Section 6.2.2
if (((speed == USB_SPEED_LOW) || (speed == USB_SPEED_FULL)) &&
(hub_info->hub_speed == USB_SPEED_HIGH)) {
hub_info->tt_info.emplace(
tt_info_t{.tt_slot_id = hub_info->hub_state->GetSlot(), .tt_port_number = port_id});
}
if (hub_info->tt_info) {
slot_context->set_PARENT_HUB_SLOT_ID(hub_info->tt_info->tt_slot_id)
.set_PARENT_PORT_NUMBER(hub_info->tt_info->tt_port_number);
}
} else {
speed = hci.GetPortSpeed(port_id);
}
switch (speed) {
case USB_SPEED_SUPER:
mps = 512;
break;
case USB_SPEED_FULL:
case USB_SPEED_HIGH:
mps = 64;
break;
case USB_SPEED_LOW:
default:
mps = 8;
break;
}
endpoint_context->Init(EndpointContext::Control, trb_phys, mps);
return ZX_OK;
}
zx_status_t DeviceState::InitializeOutputContextBuffer(
const UsbXhci& hci, uint8_t slot_id, uint8_t port_id, const std::optional<HubInfo>& hub_info,
uint64_t* dcbaa, std::unique_ptr<dma_buffer::PagedBuffer>* out) {
// Allocate an output device context data structure (6.2.1)
// Update the DCBAA entry for this slot.
std::unique_ptr<dma_buffer::PagedBuffer> output_context_buffer;
zx_status_t status = hci.buffer_factory().CreatePaged(hci.bti(), zx_system_get_page_size(), false,
&output_context_buffer);
if (status != ZX_OK) {
return status;
}
if (hci.Is32BitController() && (output_context_buffer->phys()[0] >= UINT32_MAX)) {
return ZX_ERR_NO_MEMORY;
}
dcbaa[slot_id] = output_context_buffer->phys()[0];
if (!hub_info) {
slot_id = static_cast<uint8_t>(slot_id);
}
hub_ = hub_info;
hw_mb();
*out = std::move(output_context_buffer);
return ZX_OK;
}
TRBPromise DeviceState::AddressDeviceCommand(UsbXhci* hci, uint8_t slot, uint8_t port,
std::optional<HubInfo> hub_info, uint64_t* dcbaa,
uint16_t interrupter_target, CommandRing* command_ring,
fdf::MmioBuffer* mmio, bool bsr) {
if (!hub_info.has_value()) {
hci->GetPortState()[port - 1].slot_id = slot;
}
interrupter_target_ = interrupter_target;
std::unique_ptr<dma_buffer::PagedBuffer> slot_context_buffer;
std::unique_ptr<dma_buffer::PagedBuffer> output_context_buffer;
zx_status_t status = InitializeSlotBuffer(*hci, slot, port, hub_info, &slot_context_buffer);
if (status != ZX_OK) {
zxlogf(ERROR, "Failed to initialize slot buffer: %s", zx_status_get_string(status));
return fpromise::make_error_promise(status);
}
// Allocate the transfer ring (see section 4.9)
fbl::AutoLock _(&transaction_lock_);
status = tr_.Init(&hci->interrupter(interrupter_target).ring(), mmio);
if (status != ZX_OK) {
zxlogf(ERROR, "Failed to allocate the transfer ring: %s", zx_status_get_string(status));
return fpromise::make_result_promise(
fpromise::result<TRB*, zx_status_t>(fpromise::error(status)))
.box();
}
status = InitializeEndpointContext(*hci, slot, port, hub_info, slot_context_buffer.get());
if (status != ZX_OK) {
zxlogf(ERROR, "Failed to initialize endpoint context: %s", zx_status_get_string(status));
return fpromise::make_result_promise(
fpromise::result<TRB*, zx_status_t>(fpromise::error(status)))
.box();
}
status = InitializeOutputContextBuffer(*hci, slot, port, hub_info, dcbaa, &output_context_buffer);
if (status != ZX_OK) {
zxlogf(ERROR, "Failed to initialize output context buffer: %s", zx_status_get_string(status));
return fpromise::make_result_promise(
fpromise::result<TRB*, zx_status_t>(fpromise::error(status)))
.box();
}
// Issue an address device command for the device slot
// See sections 3.3.4 and 6.4.3.4
usb_xhci::AddressDeviceStruct command;
command.ptr = slot_context_buffer->phys()[0];
command.set_SlotID(slot);
command.set_BSR(bsr);
auto command_context = command_ring->AllocateContext();
if (!command_context) {
zxlogf(ERROR, "No memory.");
return fpromise::make_result_promise(
fpromise::result<TRB*, zx_status_t>(fpromise::error(ZX_ERR_NO_MEMORY)))
.box();
}
command_context->port_number = port;
hw_mb();
input_context_ = std::move(slot_context_buffer);
device_context_ = std::move(output_context_buffer);
return hci->SubmitCommand(command, std::move(command_context));
}
void DeviceState::CreateInspectNode(inspect::Node node, uint16_t vendor_id, uint16_t product_id) {
inspect_node_ = std::move(node);
char vendor_id_string[5];
snprintf(vendor_id_string, sizeof(vendor_id_string), "%04x", vendor_id);
vendor_id_ = inspect_node_.CreateString("Vendor ID", vendor_id_string);
char product_id_string[5];
snprintf(product_id_string, sizeof(product_id_string), "%04x", product_id);
product_id_ = inspect_node_.CreateString("Product ID", product_id_string);
}
} // namespace usb_xhci