blob: b99598964f6b28c576499f2263788b885e81688f [file] [log] [blame]
// 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.
#ifndef SRC_DEVICES_USB_DRIVERS_USB_HUB_FAKE_DEVICE_H_
#define SRC_DEVICES_USB_DRIVERS_USB_HUB_FAKE_DEVICE_H_
enum class OperationType {
kUsbBusDeviceAdded,
kUsbBusDeviceRemoved,
kUsbBusSetHubInterface,
kUsbRequestQueue,
kUsbEnableEndpoint,
kUsbCancelAll,
kHasOps,
kConnectDevice,
kDisconnectDevice,
kResetPort,
kResetPending,
kInterrupt,
kUnplug,
kPowerOnEvent,
kInitCompleteEvent,
kExitEventLoop,
kUnbindReplied,
kSetChildContext,
};
class IOQueue;
struct IOEntry;
struct IOEntry : fbl::DoublyLinkedListable<std::unique_ptr<IOEntry>> {
IOQueue* complete_queue;
OperationType type;
uint8_t ep_address;
void* ctx;
usb_request_t* request;
IOQueue* request_dispatch_queue;
/* zx_device_t* */ uint64_t hub_device;
uint32_t port;
zx_status_t status;
usb_hub_descriptor_t hub_desc;
const usb_endpoint_descriptor_t* ep_desc;
const usb_ss_ep_comp_descriptor_t* ss_com_desc;
bool enable;
usb_speed_t speed;
usb_request_complete_callback_t completion;
usb_hub_interface_protocol_t hub_interface;
bool multi_tt;
IOEntry(IOQueue* complete_queue, OperationType type)
: complete_queue(complete_queue), type(type) {}
};
class IOQueue {
public:
void Insert(std::unique_ptr<IOEntry> entry) {
fbl::AutoLock l(&mutex_);
entries_.push_back(std::move(entry));
event_.Broadcast();
}
template <typename Callback>
void StartThread(Callback callback) {
thread_.emplace(callback);
}
void Join() {
thread_->join();
thread_.reset();
}
std::unique_ptr<IOEntry> Wait() {
fbl::AutoLock l(&mutex_);
while (entries_.is_empty()) {
event_.Wait(&mutex_);
}
return entries_.pop_front();
}
~IOQueue() {
if (thread_) {
auto entry = std::make_unique<IOEntry>(nullptr, OperationType::kExitEventLoop);
Insert(std::move(entry));
thread_->join();
}
}
private:
std::optional<std::thread> thread_;
fbl::Mutex mutex_;
fbl::ConditionVariable event_ __TA_GUARDED(mutex_);
fbl::DoublyLinkedList<std::unique_ptr<IOEntry>> entries_ __TA_GUARDED(mutex_);
};
// Raw descriptor from SMAYS hub obtained via USB packet capture.
const uint8_t kSmaysHubDescriptor[] = {9, 2, 25, 0, 1, 1, 0, 224, 50, 9, 4, 0, 0,
1, 9, 0, 0, 0, 7, 5, 129, 3, 1, 0, 12};
const uint8_t kSmaysHubDescriptor2[] = {9, 41, 4, 0, 0, 50, 100, 0, 255};
const uint8_t kSmaysDeviceDescriptor[] = {18, 1, 0, 2, 9, 0, 1, 64, 64,
26, 1, 1, 17, 1, 0, 1, 0, 1};
// Descriptor from an unbranded USB hub frequently used with Pixelbook -- obtained through USB
// packet capture.
const uint8_t kUnbrandedHubDescriptor[] = {9, 2, 31, 0, 1, 1, 0, 224, 0, 9, 4, 0, 0, 1, 9, 0,
0, 0, 7, 5, 129, 19, 2, 0, 8, 6, 48, 0, 0, 2, 0};
const uint8_t kUnbrandedHubDescriptor2[] = {12, 42, 4, 9, 0, 100, 0, 4, 250, 0, 0, 0};
const uint8_t kUnbrandedDeviceDescriptor[] = {18, 1, 16, 2, 9, 0, 1, 64, 9,
33, 19, 40, 17, 144, 1, 2, 0, 1};
// Hub to emulate
enum class EmulationMode {
// SMAYS OTG hub
Smays = 0,
// Unbranded USB type C hub that is frequently used with Pixelbook.
Unbranded = 1,
};
struct EmulationMetadata {
uint8_t port_count = 0;
cpp20::span<const uint8_t> device_descriptor;
cpp20::span<const uint8_t> secondary_descriptor;
cpp20::span<const uint8_t> descriptor;
usb_speed_t speed;
EmulationMode mode;
explicit EmulationMetadata(EmulationMode mode) : mode(mode) {
switch (mode) {
case EmulationMode::Smays:
descriptor = cpp20::span(kSmaysHubDescriptor, sizeof(kSmaysHubDescriptor));
secondary_descriptor = cpp20::span(kSmaysHubDescriptor2, sizeof(kSmaysHubDescriptor2));
device_descriptor = cpp20::span(kSmaysDeviceDescriptor, sizeof(kSmaysDeviceDescriptor));
port_count = 4;
speed = USB_SPEED_HIGH;
break;
case EmulationMode::Unbranded:
descriptor = cpp20::span(kUnbrandedHubDescriptor, sizeof(kUnbrandedHubDescriptor));
device_descriptor =
cpp20::span(kUnbrandedDeviceDescriptor, sizeof(kUnbrandedDeviceDescriptor));
secondary_descriptor =
cpp20::span(kUnbrandedHubDescriptor2, sizeof(kUnbrandedHubDescriptor2));
port_count = 4;
speed = USB_SPEED_SUPER;
break;
}
}
};
enum class PortStatusBit : uint8_t {
kConnected = 0,
kEnabled = 1,
kSuspended = 2,
kOvercurrent = 3,
kReset = 4,
kBHPortReset = 5,
kLinkState = 6,
kConfigError = 7,
kPower = 8,
kLowSpeed = 9,
kHighSpeed = 10,
kTestMode = 11,
kIndicatorControl = 12,
};
class PortStatus {
public:
// Returns the status mask in response to a GET_PORT_STATUS request
// and clears the change mask.
usb_port_status_t GetStatus() {
fbl::AutoLock l(&mutex_);
usb_port_status_t status_value;
status_value.w_port_change = change_mask_;
status_value.w_port_status = status_mask_;
return status_value;
}
void ClearFeature(PortStatusBit bit) {
fbl::AutoLock l(&mutex_);
change_mask_ &= ~(1 << static_cast<uint8_t>(bit));
}
void SetBit(PortStatusBit bit) {
fbl::AutoLock l(&mutex_);
status_mask_ |= (1 << static_cast<uint8_t>(bit));
// HighSpeed and LowSpeed bits don't generate change notifications
if (!((bit == PortStatusBit::kHighSpeed) || (bit == PortStatusBit::kLowSpeed) ||
(bit == PortStatusBit::kPower))) {
change_mask_ |= (1 << static_cast<uint8_t>(bit));
}
}
bool CheckBit(PortStatusBit bit) {
fbl::AutoLock l(&mutex_);
return status_mask_ & (1 << static_cast<uint8_t>(bit));
}
void ClearBit(PortStatusBit bit) {
fbl::AutoLock l(&mutex_);
status_mask_ &= ~(1 << static_cast<uint8_t>(bit));
if (!((bit == PortStatusBit::kHighSpeed) || (bit == PortStatusBit::kLowSpeed) ||
(bit == PortStatusBit::kPower))) {
change_mask_ |= (1 << static_cast<uint8_t>(bit));
}
}
private:
fbl::Mutex mutex_;
uint16_t status_mask_ __TA_GUARDED(mutex_) = 0;
uint16_t change_mask_ __TA_GUARDED(mutex_) = 0;
};
static std::unique_ptr<IOEntry> MakeSyncEntry(OperationType type) {
return std::make_unique<IOEntry>(nullptr, type);
}
static void Complete(std::unique_ptr<IOEntry> entry) {
if (!entry->complete_queue) {
return;
}
entry->complete_queue->Insert(std::move(entry));
}
class FakeDevice;
class FakeDevice : public ddk::UsbBusProtocol<FakeDevice>, public ddk::UsbProtocol<FakeDevice> {
public:
explicit FakeDevice(EmulationMode mode)
: loop_(&kAsyncLoopConfigNeverAttachToThread), emulation_(mode) {
queue_.StartThread(fit::bind_member(this, &FakeDevice::MessageLoop));
request_completion_.StartThread(fit::bind_member(this, &FakeDevice::CompletionThread));
outgoing_synchronous_methods_.StartThread(
fit::bind_member(this, &FakeDevice::SynchronousDispatchThread));
outgoing_asynchronous_methods_.StartThread(
fit::bind_member(this, &FakeDevice::AsyncCompletionThread));
}
void AsyncCompletionThread() {
while (true) {
auto message = outgoing_asynchronous_methods_.Wait();
switch (message->type) {
case OperationType::kExitEventLoop:
Complete(std::move(message));
return;
default:
abort();
}
}
}
void SynchronousDispatchThread() {
while (true) {
auto message = outgoing_synchronous_methods_.Wait();
switch (message->type) {
case OperationType::kResetPort:
message->status = ResetPortDispatch(static_cast<uint8_t>(message->port));
Complete(std::move(message));
break;
case OperationType::kExitEventLoop:
Complete(std::move(message));
return;
default:
abort();
}
}
}
void CompletionThread() {
while (true) {
auto message = request_completion_.Wait();
switch (message->type) {
case OperationType::kUsbRequestQueue:
message->completion.callback(message->completion.ctx, message->request);
pending_requests_--;
break;
case OperationType::kExitEventLoop:
Complete(std::move(message));
return;
default:
abort();
}
}
}
void MessageLoop() {
while (true) {
auto message = queue_.Wait();
switch (message->type) {
case OperationType::kUsbRequestQueue:
UsbRequestQueueDispatch(std::move(message));
break;
case OperationType::kResetPort:
outgoing_synchronous_methods_.Insert(std::move(message));
break;
case OperationType::kInitCompleteEvent:
state_change_queue_.Insert(std::move(message));
break;
case OperationType::kConnectDevice:
ConnectDeviceDispatch(static_cast<uint8_t>(message->port), message->speed);
Complete(std::move(message));
break;
case OperationType::kDisconnectDevice:
DisconnectDeviceDispatch(static_cast<uint8_t>(message->port));
Complete(std::move(message));
break;
case OperationType::kUsbBusDeviceAdded:
UsbBusDeviceAddedDispatch(std::move(message));
break;
case OperationType::kUsbBusDeviceRemoved:
UsbBusDeviceRemovedDispatch(std::move(message));
break;
case OperationType::kExitEventLoop:
Complete(std::move(message));
return;
case OperationType::kHasOps:
message->ctx = const_cast<void*>(static_cast<const void*>(ctx_));
Complete(std::move(message));
break;
case OperationType::kInterrupt:
InterruptDispatch();
Complete(std::move(message));
break;
case OperationType::kUnbindReplied:
case OperationType::kPowerOnEvent:
// Invalid within this context
abort();
break;
case OperationType::kResetPending:
message->port = ResetPendingDispatch(static_cast<uint8_t>(message->port));
Complete(std::move(message));
break;
case OperationType::kSetChildContext:
SetChildContextDispatch(message->ctx);
Complete(std::move(message));
break;
case OperationType::kUnplug:
UnplugDispatch();
Complete(std::move(message));
break;
case OperationType::kUsbBusSetHubInterface:
UsbBusSetHubInterfaceDispatch(std::move(message));
break;
case OperationType::kUsbCancelAll:
message->status = UsbCancelAllDispatch(message->ep_address);
if (pending_requests_) {
// TODO(https://fxbug.dev/42139221): Make CancelAll async
queue_.Insert(std::move(message));
} else {
Complete(std::move(message));
}
break;
case OperationType::kUsbEnableEndpoint:
message->status =
UsbEnableEndpointDispatch(message->ep_desc, message->ss_com_desc, message->enable);
Complete(std::move(message));
break;
}
}
}
void SetChildContextDispatch(void* ctx) { ctx_ = ctx; }
zx_status_t UsbBusConfigureHub(/* zx_device_t* */ uint64_t hub_device, usb_speed_t speed,
const usb_hub_descriptor_t* desc, bool multi_tt) {
if (desc->b_nbr_ports != emulation_.port_count) {
return ZX_ERR_INVALID_ARGS;
}
if (speed != emulation_.speed) {
return ZX_ERR_INVALID_ARGS;
}
if (multi_tt) {
return ZX_ERR_INVALID_ARGS;
}
return ZX_OK;
}
void UsbBusDeviceAddedDispatch(std::unique_ptr<IOEntry> entry) {
state_change_queue_.Insert(std::move(entry));
}
zx_status_t UsbBusDeviceAdded(/* zx_device_t* */ uint64_t hub_device, uint32_t port,
usb_speed_t speed) {
auto entry = MakeSyncEntry(OperationType::kUsbBusDeviceAdded);
entry->hub_device = hub_device;
entry->port = port;
entry->speed = speed;
return SendMessageSync(std::move(entry))->status;
}
void UsbBusDeviceRemovedDispatch(std::unique_ptr<IOEntry> entry) {
state_change_queue_.Insert(std::move(entry));
}
zx_status_t UsbBusDeviceRemoved(/* zx_device_t* */ uint64_t hub_device, uint32_t port) {
auto entry = MakeSyncEntry(OperationType::kUsbBusDeviceRemoved);
entry->hub_device = hub_device;
entry->port = port;
return SendMessageSync(std::move(entry))->status;
}
void UsbBusSetHubInterfaceDispatch(std::unique_ptr<IOEntry> entry) {
hub_protocol_ = entry->hub_interface;
entry->status = ZX_OK;
Complete(std::move(entry));
}
zx_status_t UsbBusSetHubInterface(/* zx_device_t* */ uint64_t usb_device,
const usb_hub_interface_protocol_t* hub) {
auto entry = MakeSyncEntry(OperationType::kUsbBusSetHubInterface);
entry->hub_device = usb_device;
entry->hub_interface = *hub;
return SendMessageSync(std::move(entry))->status;
}
void UsbBusRequestQueue(usb_request_t* req, const usb_request_complete_callback_t* complete_cb) {
usb_request_complete(req, ZX_ERR_NOT_SUPPORTED, 0, complete_cb);
}
// USB protocol implementation
zx_status_t UsbControlIn(uint8_t request_type, uint8_t request, uint16_t value, uint16_t index,
int64_t timeout, uint8_t* out_read_buffer, size_t read_size,
size_t* out_read_actual) {
return ZX_ERR_NOT_SUPPORTED;
}
zx_status_t UsbControlOut(uint8_t request_type, uint8_t request, uint16_t value, uint16_t index,
int64_t timeout, const uint8_t* write_buffer, size_t write_size) {
return ZX_ERR_NOT_SUPPORTED;
}
uint8_t GeneratePortBitmask() {
uint8_t mask = 0;
size_t i = 1;
for (auto& port : port_status_) {
if (port.GetStatus().w_port_change) {
mask |= (1 << i);
}
i++;
}
return mask;
}
zx_status_t ControlOut(uint8_t request_type, uint8_t request, uint16_t value, uint16_t index,
int64_t timeout, const void* write_buffer, size_t write_size) {
enum class Opcode : uint16_t {
SetFeature = 0x323,
ClearFeature = 0x123,
};
auto request_opcode = static_cast<Opcode>(request_type | (request << 8));
switch (request_opcode) {
case Opcode::SetFeature: {
switch (value) {
case USB_FEATURE_PORT_POWER: {
if ((index != power_on_expected_) || (index > emulation_.port_count)) {
return ZX_ERR_INVALID_ARGS;
}
port_status_[index - 1].SetBit(PortStatusBit::kPower);
power_on_expected_++;
if (power_on_expected_ > emulation_.port_count) {
auto msg = std::make_unique<IOEntry>(nullptr, OperationType::kPowerOnEvent);
state_change_queue_.Insert(std::move(msg));
}
InterruptDispatch();
return ZX_OK;
} break;
case USB_FEATURE_PORT_RESET: {
if (port_status_[index - 1].CheckBit(PortStatusBit::kConnected)) {
port_status_[index - 1].SetBit(PortStatusBit::kEnabled);
port_status_[index - 1].ClearFeature(PortStatusBit::kReset);
} else {
port_status_[index - 1].SetBit(PortStatusBit::kReset);
}
InterruptDispatch();
return ZX_OK;
} break;
}
} break;
case Opcode::ClearFeature: {
switch (value) {
case USB_FEATURE_C_PORT_CONNECTION:
port_status_[index - 1].ClearFeature(PortStatusBit::kConnected);
return ZX_OK;
case USB_FEATURE_C_PORT_ENABLE:
port_status_[index - 1].ClearFeature(PortStatusBit::kEnabled);
return ZX_OK;
case USB_FEATURE_C_PORT_SUSPEND:
port_status_[index - 1].ClearFeature(PortStatusBit::kSuspended);
return ZX_OK;
case USB_FEATURE_C_PORT_OVER_CURRENT:
port_status_[index - 1].ClearFeature(PortStatusBit::kOvercurrent);
return ZX_OK;
case USB_FEATURE_C_PORT_RESET:
port_status_[index - 1].ClearFeature(PortStatusBit::kReset);
return ZX_OK;
case USB_C_BH_PORT_RESET:
port_status_[index - 1].ClearFeature(PortStatusBit::kBHPortReset);
return ZX_OK;
case USB_FEATURE_C_PORT_LINK_STATE:
port_status_[index - 1].ClearFeature(PortStatusBit::kLinkState);
return ZX_OK;
case USB_FEATURE_C_PORT_CONFIG_ERROR:
port_status_[index - 1].ClearFeature(PortStatusBit::kConfigError);
return ZX_OK;
}
} break;
}
return ZX_ERR_NOT_SUPPORTED;
}
zx_status_t ControlIn(uint8_t request_type, uint8_t request, uint16_t value, uint16_t index,
int64_t timeout, void* out_read_buffer, size_t read_size,
size_t* out_read_actual) {
enum class Opcode : uint16_t {
GetClassDescriptor = 0x6A0,
GetStandardDescriptor = 0x680,
GetPortStatus = 0xA3,
};
auto request_opcode = static_cast<Opcode>(request_type | (request << 8));
switch (request_opcode) {
case Opcode::GetClassDescriptor: {
// Type field
switch (value >> 8) {
case USB_HUB_DESC_TYPE_SS:
if (emulation_.speed != USB_SPEED_SUPER) {
break;
}
__FALLTHROUGH;
case USB_HUB_DESC_TYPE:
// Fetch secondary hub descriptor
memcpy(out_read_buffer, emulation_.secondary_descriptor.data(),
emulation_.secondary_descriptor.size());
*out_read_actual = emulation_.secondary_descriptor.size();
return ZX_OK;
}
} break;
case Opcode::GetStandardDescriptor: {
switch (value >> 8) {
case USB_DT_DEVICE: {
memcpy(out_read_buffer, emulation_.device_descriptor.data(),
emulation_.device_descriptor.size());
*out_read_actual = emulation_.device_descriptor.size();
}
return ZX_OK;
}
} break;
case Opcode::GetPortStatus: {
auto status = port_status_[index - 1].GetStatus();
memcpy(out_read_buffer, &status, sizeof(status));
*out_read_actual = sizeof(status);
return ZX_OK;
} break;
}
return ZX_ERR_NOT_SUPPORTED;
}
void CompleteRequest(std::unique_ptr<IOEntry> request, zx_status_t status, zx_off_t actual) {
request->request->response.status = status;
request->request->response.actual = actual;
if (synthetic_) {
request->completion.callback(request->completion.ctx, request->request);
pending_requests_--;
return;
}
request_completion_.Insert(std::move(request));
}
void UsbRequestQueueDispatch(std::unique_ptr<IOEntry> entry) {
if (request_callback_) {
(*request_callback_)(entry->request, entry->completion);
return;
}
auto usb_request = entry->request;
if (usb_request->header.ep_address == 0) {
// Control request
if (usb_request->setup.bm_request_type & USB_DIR_IN) {
void* buffer;
usb_request_mmap(usb_request, &buffer);
size_t size = 0;
zx_status_t status =
ControlIn(usb_request->setup.bm_request_type, usb_request->setup.b_request,
usb_request->setup.w_value, usb_request->setup.w_index, ZX_TIME_INFINITE,
buffer, usb_request->setup.w_length, &size);
CompleteRequest(std::move(entry), status, size);
} else {
void* buffer;
usb_request_mmap(usb_request, &buffer);
size_t size = usb_request->setup.w_length;
zx_status_t status =
ControlOut(usb_request->setup.bm_request_type, usb_request->setup.b_request,
usb_request->setup.w_value, usb_request->setup.w_index, ZX_TIME_INFINITE,
buffer, usb_request->setup.w_length);
CompleteRequest(std::move(entry), status, size);
}
return;
}
if (interrupt_pending_ || GeneratePortBitmask()) {
interrupt_pending_ = false;
uint8_t* mask;
usb_request_mmap(usb_request, reinterpret_cast<void**>(&mask));
*mask = GeneratePortBitmask();
CompleteRequest(std::move(entry), ZX_OK, 1);
return;
}
{
if (unplugged_) {
CompleteRequest(std::move(entry), ZX_ERR_IO_REFUSED, 0);
return;
}
request_ = std::move(entry);
}
}
void UsbRequestQueue(usb_request_t* request, const usb_request_complete_callback_t* completion) {
if (synthetic_) {
auto entry = MakeSyncEntry(OperationType::kUsbRequestQueue);
entry->request = request;
entry->completion = *completion;
UsbRequestQueueDispatch(std::move(entry));
return;
}
auto entry = MakeSyncEntry(OperationType::kUsbRequestQueue);
pending_requests_++;
entry->request = request;
entry->completion = *completion;
queue_.Insert(std::move(entry));
}
usb_speed_t UsbGetSpeed() { return emulation_.speed; }
zx_status_t UsbSetInterface(uint8_t interface_number, uint8_t alt_setting) {
return ZX_ERR_NOT_SUPPORTED;
}
uint8_t UsbGetConfiguration() { return 0; }
zx_status_t UsbSetConfiguration(uint8_t configuration) { return ZX_ERR_NOT_SUPPORTED; }
zx_status_t UsbEnableEndpointDispatch(const usb_endpoint_descriptor_t* ep_desc,
const usb_ss_ep_comp_descriptor_t* ss_com_desc,
bool enable) {
interrupt_endpoint_ = ep_desc->b_endpoint_address;
return ZX_OK;
}
zx_status_t UsbEnableEndpoint(const usb_endpoint_descriptor_t* ep_desc,
const usb_ss_ep_comp_descriptor_t* ss_com_desc, bool enable) {
auto entry = MakeSyncEntry(OperationType::kUsbEnableEndpoint);
entry->ep_desc = ep_desc;
entry->ss_com_desc = ss_com_desc;
entry->enable = enable;
return SendMessageSync(std::move(entry))->status;
}
zx_status_t UsbResetEndpoint(uint8_t ep_address) { return ZX_ERR_NOT_SUPPORTED; }
zx_status_t UsbResetDevice() { return ZX_ERR_NOT_SUPPORTED; }
size_t UsbGetMaxTransferSize(uint8_t ep_address) { return 0; }
uint32_t UsbGetDeviceId() { return 0; }
void UsbGetDeviceDescriptor(usb_device_descriptor_t* out_desc) {}
zx_status_t UsbGetConfigurationDescriptorLength(uint8_t configuration, size_t* out_length) {
return 0;
}
zx_status_t UsbGetConfigurationDescriptor(uint8_t configuration, uint8_t* out_desc_buffer,
size_t desc_size, size_t* out_desc_actual) {
return ZX_ERR_NOT_SUPPORTED;
}
size_t UsbGetDescriptorsLength() { return emulation_.descriptor.size(); }
void UsbGetDescriptors(uint8_t* out_descs_buffer, size_t descs_size, size_t* out_descs_actual) {
memcpy(out_descs_buffer, emulation_.descriptor.data(), emulation_.descriptor.size());
*out_descs_actual = emulation_.descriptor.size();
}
zx_status_t UsbGetStringDescriptor(uint8_t desc_id, uint16_t lang_id, uint16_t* out_lang_id,
uint8_t* out_string_buffer, size_t string_size,
size_t* out_string_actual) {
return ZX_ERR_NOT_SUPPORTED;
}
zx_status_t UsbCancelAllDispatch(uint8_t ep_address) {
if (request_) {
auto req = std::move(request_);
usb_request_complete(req->request, ZX_ERR_CANCELED, 0, &req->completion);
pending_requests_--;
}
return ZX_OK;
}
zx_status_t UsbCancelAll(uint8_t ep_address) {
auto entry = MakeSyncEntry(OperationType::kUsbCancelAll);
entry->ep_address = ep_address;
return SendMessageSync(std::move(entry))->status;
}
uint64_t UsbGetCurrentFrame() { return 0; }
size_t UsbGetRequestSize() { return sizeof(usb_request_t); }
usb_hub::UsbHubDevice* device() { return static_cast<usb_hub::UsbHubDevice*>(ctx_); }
zx_status_t GetProtocol(uint32_t proto, void* protocol) {
switch (proto) {
case ZX_PROTOCOL_USB: {
auto proto = static_cast<usb_protocol_t*>(protocol);
proto->ctx = this;
proto->ops = &usb_protocol_ops_;
}
return ZX_OK;
case ZX_PROTOCOL_USB_BUS: {
auto proto = static_cast<usb_bus_protocol_t*>(protocol);
proto->ctx = this;
proto->ops = &usb_bus_protocol_ops_;
}
return ZX_OK;
default:
return ZX_ERR_PROTOCOL_NOT_SUPPORTED;
}
}
void ConnectDeviceDispatch(uint8_t port, usb_speed_t speed) {
// We use zero-based indexing for ports, USB uses 1-based indexing.
if (speed == USB_SPEED_HIGH) {
port_status_[port].SetBit(PortStatusBit::kHighSpeed);
}
if (speed == USB_SPEED_LOW) {
port_status_[port].SetBit(PortStatusBit::kLowSpeed);
}
port_status_[port].SetBit(PortStatusBit::kConnected);
}
void DisconnectDeviceDispatch(uint8_t port) {
port_status_[port].ClearBit(PortStatusBit::kConnected);
port_status_[port].ClearBit(PortStatusBit::kEnabled);
port_status_[port].ClearBit(PortStatusBit::kReset);
}
zx_status_t ResetPortDispatch(uint8_t port) {
return hub_protocol_.ops->reset_port(hub_protocol_.ctx, port + 1);
}
bool ResetPendingDispatch(uint8_t port) {
return port_status_[port].CheckBit(PortStatusBit::kReset);
}
void InterruptDispatch() {
if (request_) {
uint8_t* mask;
auto req = request_->request;
usb_request_mmap(req, reinterpret_cast<void**>(&mask));
*mask = GeneratePortBitmask();
CompleteRequest(std::move(request_), ZX_OK, 1);
} else {
interrupt_pending_ = true;
}
}
void UnplugDispatch() {
if (request_) {
CompleteRequest(std::move(request_), ZX_ERR_IO_REFUSED, 0);
}
}
void SetSynthetic(bool synthetic) { synthetic_ = synthetic; }
bool IsSynthetic() const { return synthetic_; }
void SendMessage(std::unique_ptr<IOEntry> entry) { queue_.Insert(std::move(entry)); }
std::unique_ptr<IOEntry> SendMessageSync(std::unique_ptr<IOEntry> entry) {
IOQueue sync_queue;
entry->complete_queue = &sync_queue;
queue_.Insert(std::move(entry));
return sync_queue.Wait();
}
bool HasOps() {
auto entry = SendMessageSync(MakeSyncEntry(OperationType::kHasOps));
return entry->ctx;
}
void ConnectDevice(uint8_t port, usb_speed_t speed) {
auto message = MakeSyncEntry(OperationType::kConnectDevice);
message->port = port;
message->speed = speed;
SendMessageSync(std::move(message));
}
void Interrupt() { SendMessageSync(MakeSyncEntry(OperationType::kInterrupt)); }
void Unplug() { SendMessageSync(MakeSyncEntry(OperationType::kUnplug)); }
void SetChildContext(void* ctx) {
auto entry = MakeSyncEntry(OperationType::kSetChildContext);
entry->ctx = ctx;
SendMessageSync(std::move(entry));
}
zx_status_t ResetPort(uint8_t port) {
auto message = MakeSyncEntry(OperationType::kResetPort);
message->port = port;
message = SendMessageSync(std::move(message));
return message->status;
}
bool ResetPending(uint8_t port) {
auto message = MakeSyncEntry(OperationType::kResetPending);
message->port = port;
message = SendMessageSync(std::move(message));
return message->port;
}
void InitComplete() { queue_.Insert(MakeSyncEntry(OperationType::kInitCompleteEvent)); }
void NotifyRemoved() {
auto entry = MakeSyncEntry(OperationType::kUnbindReplied);
state_change_queue_.Insert(std::move(entry));
}
void DisconnectDevice(uint8_t port) {
auto message = MakeSyncEntry(OperationType::kDisconnectDevice);
message->port = port;
SendMessageSync(std::move(message));
}
IOQueue& GetStateChangeQueue() { return state_change_queue_; }
void SetRequestCallback(
fit::function<void(usb_request_t*, usb_request_complete_callback_t)> callback) {
ZX_ASSERT(synthetic_);
request_callback_ = std::move(callback);
}
private:
std::atomic_size_t pending_requests_ = 0;
// State change queue which is read from by a test
IOQueue state_change_queue_;
// Incoming request queue
IOQueue queue_;
// Queue for outgoing synchronous method invocations
IOQueue outgoing_synchronous_methods_;
// Queue for outgoing calls to async methods.
IOQueue outgoing_asynchronous_methods_;
// Request completion queue
IOQueue request_completion_;
// Indicates whether or not this test is synthetic.
// Thread-safety: Must only be written during object construction.
// May be read safely from any thread.
bool synthetic_ = false;
// Control requests pending count
// Thread-safety: May be safely read or written from any thread provided that
// accesses are performed atomically.
std::atomic_int32_t control_requests_pending_ = 0;
// Completion event that indicates all control requests have been cleared during shutdown
sync_completion_t control_request_cleared_;
// Indicates whether or not a simulated interrupt is pending (simulated hardware register).
bool interrupt_pending_ = false;
async::Loop loop_;
// Event waiting for an unbind to complete
bool unplugged_ = false;
std::unique_ptr<IOEntry> request_;
fit::function<zx_status_t(uint32_t, usb_speed_t)> connect_callback_;
// Whether or not power on is expected (simulated hardware register)
uint8_t power_on_expected_ = 1;
// Port status (simulated hardware register)
PortStatus port_status_[7];
// Interrupt endpoint set by UsbEnableEndpoint
uint8_t interrupt_endpoint_ = 0;
void* ctx_;
EmulationMetadata emulation_;
usb_hub_interface_protocol_t hub_protocol_;
std::optional<fit::function<void(usb_request_t*, usb_request_complete_callback_t)>>
request_callback_;
};
#endif // SRC_DEVICES_USB_DRIVERS_USB_HUB_FAKE_DEVICE_H_