blob: ba48d7cdc2804cf52dc612ce3d78ce4b9e74f70d [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.
#include "usb-peripheral.h"
#include <fuchsia/hardware/platform/device/c/banjo.h>
#include <fuchsia/hardware/usb/dci/c/banjo.h>
#include <fuchsia/hardware/usb/dci/cpp/banjo.h>
#include <lib/ddk/binding.h>
#include <lib/ddk/device.h>
#include <lib/ddk/driver.h>
#include <lib/ddk/metadata.h>
#include <lib/fake_ddk/fake_ddk.h>
#include <lib/zx/clock.h>
#include <lib/zx/interrupt.h>
#include <zircon/errors.h>
#include <zircon/hw/usb.h>
#include <zircon/syscalls.h>
#include <cstring>
#include <list>
#include <map>
#include <memory>
#include <ddk/usb-peripheral-config.h>
#include <fake-mmio-reg/fake-mmio-reg.h>
#include <zxtest/zxtest.h>
#include "usb-function.h"
struct zx_device : std::enable_shared_from_this<zx_device> {
std::list<std::shared_ptr<zx_device>> devices;
std::weak_ptr<zx_device> parent;
std::vector<zx_device_prop_t> props;
void* proto_ops;
uint32_t proto_id;
void* ctx;
zx_protocol_device_t dev_ops;
std::map<uint32_t, std::vector<uint8_t>> metadata;
virtual ~zx_device() = default;
};
class FakeDevice : public ddk::UsbDciProtocol<FakeDevice, ddk::base_protocol> {
public:
FakeDevice() : proto_({&usb_dci_protocol_ops_, this}) {}
// USB DCI protocol implementation.
void UsbDciRequestQueue(usb_request_t* req, const usb_request_complete_callback_t* cb) {}
zx_status_t UsbDciSetInterface(const usb_dci_interface_protocol_t* interface) {
interface_ = *interface;
return ZX_OK;
}
zx_status_t UsbDciConfigEp(const usb_endpoint_descriptor_t* ep_desc,
const usb_ss_ep_comp_descriptor_t* ss_comp_desc) {
return ZX_ERR_NOT_SUPPORTED;
}
zx_status_t UsbDciDisableEp(uint8_t ep_address) { return ZX_ERR_NOT_SUPPORTED; }
zx_status_t UsbDciEpSetStall(uint8_t ep_address) { return ZX_ERR_NOT_SUPPORTED; }
zx_status_t UsbDciEpClearStall(uint8_t ep_address) { return ZX_ERR_NOT_SUPPORTED; }
size_t UsbDciGetRequestSize() { return sizeof(usb_request_t); }
zx_status_t UsbDciCancelAll(uint8_t ep_address) { return ZX_OK; }
usb_dci_protocol_t* proto() { return &proto_; }
usb_dci_interface_protocol_t* interface() { return &interface_; }
private:
usb_dci_interface_protocol_t interface_;
usb_dci_protocol_t proto_;
};
static void DestroyDevices(zx_device_t* node) {
for (auto& dev : node->devices) {
DestroyDevices(dev.get());
if (dev->dev_ops.unbind) {
dev->dev_ops.unbind(dev->ctx);
}
dev->dev_ops.release(dev->ctx);
}
}
class Ddk : public fake_ddk::Bind {
public:
Ddk() = default;
zx_status_t DeviceGetProtocol(const zx_device_t* device, uint32_t proto_id,
void* protocol) override {
if (device->proto_id != proto_id) {
return ZX_ERR_NOT_SUPPORTED;
}
memcpy(protocol, device->proto_ops, 16);
return ZX_OK;
}
zx_status_t DeviceAdd(zx_driver_t* drv, zx_device_t* parent, device_add_args_t* args,
zx_device_t** out) override {
auto dev = std::make_shared<zx_device>();
dev->ctx = args->ctx;
dev->proto_ops = args->proto_ops;
if (args->props) {
dev->props.resize(args->prop_count);
memcpy(dev->props.data(), args->props, args->prop_count * sizeof(zx_device_prop_t));
}
dev->dev_ops = *args->ops;
dev->parent = parent->weak_from_this();
dev->proto_id = args->proto_id;
parent->devices.push_back(dev);
*out = dev.get();
return ZX_OK;
}
zx_status_t DeviceRemove(zx_device_t* device) override {
DestroyDevices(device);
return ZX_OK;
}
};
class UsbPeripheralHarness : public zxtest::Test {
public:
void SetUp() override {
static const UsbConfig kConfig = []() {
UsbConfig config = {};
memcpy(config.serial, kSerialNumber, sizeof(kSerialNumber));
return config;
}();
ddk_.SetMetadata(DEVICE_METADATA_USB_CONFIG, &kConfig, sizeof(kConfig));
ddk_.SetMetadata(DEVICE_METADATA_SERIAL_NUMBER, &kSerialNumber, sizeof(kSerialNumber));
dci_ = std::make_unique<FakeDevice>();
root_device_ = std::make_shared<zx_device_t>();
root_device_->proto_ops = dci_->proto();
root_device_->ctx = dci_.get();
root_device_->proto_id = ZX_PROTOCOL_USB_DCI;
zx::interrupt irq;
ASSERT_OK(zx::interrupt::create(zx::resource(), 0, ZX_INTERRUPT_VIRTUAL, &irq));
ASSERT_OK(usb_peripheral::UsbPeripheral::Create(nullptr, root_device_.get()));
auto dev = root_device_->devices.front();
client_ = ddk::UsbDciInterfaceProtocolClient(dci_->interface());
}
void TearDown() override {}
protected:
std::unique_ptr<FakeDevice> dci_;
std::shared_ptr<zx_device_t> root_device_;
Ddk ddk_;
ddk::UsbDciInterfaceProtocolClient client_;
static constexpr char kSerialNumber[] = "Test serial number";
};
TEST_F(UsbPeripheralHarness, AddsCorrectSerialNumberMetadata) {
char serial[256];
usb_setup_t setup;
setup.w_length = sizeof(serial);
setup.w_value = 0x3 | (USB_DT_STRING << 8);
setup.bm_request_type = USB_DIR_IN | USB_RECIP_DEVICE | USB_TYPE_STANDARD;
setup.b_request = USB_REQ_GET_DESCRIPTOR;
size_t actual;
ASSERT_OK(client_.Control(&setup, nullptr, 0, reinterpret_cast<uint8_t*>(&serial), sizeof(serial),
&actual));
ASSERT_EQ(serial[0], sizeof(kSerialNumber) * 2);
ASSERT_EQ(serial[1], USB_DT_STRING);
for (size_t i = 0; i < sizeof(kSerialNumber) - 1; i++) {
ASSERT_EQ(serial[2 + (i * 2)], kSerialNumber[i]);
}
DestroyDevices(root_device_.get());
}
TEST_F(UsbPeripheralHarness, WorksWithVendorSpecificCommandWhenConfigurationIsZero) {
char serial[256];
usb_setup_t setup;
setup.w_length = sizeof(serial);
setup.w_value = 0x3 | (USB_DT_STRING << 8);
setup.bm_request_type = USB_DIR_IN | USB_RECIP_DEVICE | USB_TYPE_VENDOR;
setup.b_request = USB_REQ_GET_DESCRIPTOR;
size_t actual;
ASSERT_EQ(client_.Control(&setup, nullptr, 0, reinterpret_cast<uint8_t*>(&serial), sizeof(serial),
&actual),
ZX_ERR_BAD_STATE);
DestroyDevices(root_device_.get());
}