blob: 9aecd94b12e842ca63e792af80bbee4789abac00 [file] [log] [blame]
// Copyright 2016 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-hid.h"
#include <endian.h>
#include <fuchsia/hardware/usb/c/banjo.h>
#include <fuchsia/hardware/usb/cpp/banjo.h>
#include <fuchsia/hardware/usb/descriptor/c/banjo.h>
#include <lib/driver/compat/cpp/compat.h>
#include <lib/driver/component/cpp/driver_export.h>
#include <lib/sync/completion.h>
#include <stdlib.h>
#include <string.h>
#include <zircon/status.h>
#include <zircon/types.h>
#include <thread>
#include <fbl/auto_lock.h>
#include <pretty/hexdump.h>
#include <usb/hid.h>
#include <usb/usb-request.h>
#include <usb/usb.h>
namespace usb_hid {
namespace fendpoint = fuchsia_hardware_usb_endpoint;
namespace fhidbus = fuchsia_hardware_hidbus;
#define to_usb_hid(d) containerof(d, usb_hid_device_t, hiddev)
// This driver binds on any USB device that exposes HID reports. It passes the
// reports to the HID driver by implementing the HidBus protocol.
void UsbHidbus::HandleInterrupt(fendpoint::Completion completion) {
ZX_ASSERT(completion.request().has_value());
ZX_ASSERT(completion.status().has_value());
ZX_ASSERT(completion.transfer_size().has_value());
usb::FidlRequest req(std::move(completion.request().value()));
std::vector<uint8_t> buffer(*completion.transfer_size());
auto actual = req.CopyFrom(0, buffer.data(), *completion.transfer_size(), ep_in_.GetMapped());
ZX_ASSERT(actual.size() == 1);
ZX_ASSERT(actual[0] == *completion.transfer_size());
fdf::trace("usb-hid: callback request status {}", *completion.status());
if (zxlog_level_enabled(TRACE)) {
hexdump(buffer.data(), *completion.transfer_size());
}
bool requeue = true;
switch (*completion.status()) {
case ZX_ERR_IO_NOT_PRESENT:
requeue = false;
break;
case ZX_OK:
if (started_ && binding_) {
fidl::Arena arena;
auto report =
fhidbus::wire::Report::Builder(arena)
.buf(fidl::VectorView<uint8_t>::FromExternal(buffer.data(), buffer.size()))
.timestamp(zx_clock_get_monotonic());
if (completion.wake_lease().has_value()) {
report.wake_lease(std::move(completion.wake_lease().value()));
}
auto result = fidl::WireSendEvent(*binding_)->OnReportReceived(report.Build());
if (!result.ok()) {
fdf::error("OnReportReceived failed: {}", result.error().FormatDescription());
}
}
break;
default:
fdf::error("Not requeuing req: Unknown interrupt status {}", *completion.status());
requeue = false;
break;
}
if (requeue) {
req.reset_buffers(ep_in_.GetMapped());
req.CacheFlushInvalidate(ep_in_.GetMapped());
std::vector<fuchsia_hardware_usb_request::Request> requests;
requests.push_back(req.take_request());
auto result = ep_in_->QueueRequests(std::move(requests));
if (result.is_error()) {
fdf::error("Failed to queue requests: {}", result.error_value().FormatDescription());
}
} else {
ep_in_.PutRequest(std::move(req));
}
}
void UsbHidbus::Query(QueryCompleter::Sync& completer) { completer.ReplySuccess(info_); }
void UsbHidbus::Start(
fidl::WireServer<fuchsia_hardware_hidbus::Hidbus>::StartCompleter::Sync& completer) {
if (started_) {
fdf::error("Already started");
completer.ReplyError(ZX_ERR_ALREADY_BOUND);
return;
}
started_ = true;
auto req = ep_in_.GetRequest();
if (req.has_value()) {
req->reset_buffers(ep_in_.GetMapped());
req->CacheFlushInvalidate(ep_in_.GetMapped());
std::vector<fuchsia_hardware_usb_request::Request> requests;
requests.push_back(req->take_request());
auto result = ep_in_->QueueRequests(std::move(requests));
if (result.is_error()) {
fdf::error("Failed to queue requests: {}", result.error_value().FormatDescription());
}
}
completer.ReplySuccess();
}
void UsbHidbus::Stop(StopCompleter::Sync& completer) { StopHidbus(); }
void UsbHidbus::StopHidbus() {
started_ = false;
// TODO(tkilbourn) set flag to stop requeueing the interrupt request when we start using this
// callback
if (set_report_completer_.has_value()) {
set_report_completer_->ReplyError(ZX_ERR_IO_NOT_PRESENT);
}
}
zx_status_t UsbHidbus::UsbHidControlIn(uint8_t req_type, uint8_t request, uint16_t value,
uint16_t index, void* data, size_t length,
size_t* out_length) {
zx_status_t status;
status = usb_.ControlIn(req_type, request, value, index, ZX_TIME_INFINITE,
reinterpret_cast<uint8_t*>(data), length, out_length);
if (status == ZX_ERR_IO_REFUSED || status == ZX_ERR_IO_INVALID) {
status = usb_.ResetEndpoint(0);
}
return status;
}
zx_status_t UsbHidbus::UsbHidControlOut(uint8_t req_type, uint8_t request, uint16_t value,
uint16_t index, const void* data, size_t length,
size_t* out_length) {
zx_status_t status;
status = usb_.ControlOut(req_type, request, value, index, ZX_TIME_INFINITE,
reinterpret_cast<const uint8_t*>(data), length);
if (status == ZX_ERR_IO_REFUSED || status == ZX_ERR_IO_INVALID) {
status = usb_.ResetEndpoint(0);
}
return status;
}
void UsbHidbus::GetDescriptor(fhidbus::wire::HidbusGetDescriptorRequest* request,
GetDescriptorCompleter::Sync& completer) {
int desc_idx = -1;
for (int i = 0; i < hid_desc_->bNumDescriptors; i++) {
if (hid_desc_->descriptors[i].bDescriptorType == static_cast<uint16_t>(request->desc_type)) {
desc_idx = i;
break;
}
}
if (desc_idx < 0) {
completer.ReplyError(ZX_ERR_NOT_FOUND);
return;
}
size_t desc_len = hid_desc_->descriptors[desc_idx].wDescriptorLength;
std::vector<uint8_t> desc;
desc.resize(desc_len);
zx_status_t status =
UsbHidControlIn(USB_DIR_IN | USB_TYPE_STANDARD | USB_RECIP_INTERFACE, USB_REQ_GET_DESCRIPTOR,
static_cast<uint16_t>(static_cast<uint16_t>(request->desc_type) << 8),
interface_, desc.data(), desc_len, nullptr);
if (status < 0) {
fdf::error("Failed to read report descriptor {:#02x}: {}",
static_cast<uint16_t>(request->desc_type), zx_status_get_string(status));
completer.ReplyError(status);
return;
}
completer.ReplySuccess(fidl::VectorView<uint8_t>::FromExternal(desc.data(), desc.size()));
}
void UsbHidbus::GetReport(fhidbus::wire::HidbusGetReportRequest* request,
GetReportCompleter::Sync& completer) {
std::vector<uint8_t> report;
report.resize(request->len);
size_t actual;
auto status = UsbHidControlIn(
USB_DIR_IN | USB_TYPE_CLASS | USB_RECIP_INTERFACE, USB_HID_GET_REPORT,
static_cast<uint16_t>(static_cast<uint16_t>(request->rpt_type) << 8 | request->rpt_id),
interface_, report.data(), report.size(), &actual);
if (status != ZX_OK) {
completer.ReplyError(status);
return;
}
report.resize(actual);
completer.ReplySuccess(fidl::VectorView<uint8_t>::FromExternal(report.data(), report.size()));
}
void UsbHidbus::SetReportComplete(fendpoint::Completion completion) {
ep_out_->PutRequest(usb::FidlRequest(std::move(*completion.request())));
if (!set_report_completer_.has_value()) {
// Shutting down. Probably has already replied.
return;
}
auto completer = std::move(*set_report_completer_);
set_report_completer_.reset();
if (*completion.status() == ZX_OK) {
completer.ReplySuccess();
return;
}
completer.ReplyError(*completion.status());
}
void UsbHidbus::SetReport(fhidbus::wire::HidbusSetReportRequest* request,
SetReportCompleter::Sync& completer) {
if (ep_out_.has_value()) {
if (set_report_completer_.has_value()) {
completer.ReplyError(ZX_ERR_SHOULD_WAIT);
return;
}
auto req = ep_out_->GetRequest();
if (!req.has_value()) {
completer.ReplyError(ZX_ERR_SHOULD_WAIT);
return;
}
auto actual = req->CopyTo(0, request->data.data(), request->data.size(), ep_out_->GetMapped());
ZX_ASSERT(actual.size() == 1);
if (request->data.size() != actual[0]) {
completer.ReplyError(ZX_ERR_BUFFER_TOO_SMALL);
return;
}
(*req)->data()->at(0).size(actual[0]);
auto status = req->CacheFlush(ep_out_->GetMappedLocked());
if (status != ZX_OK) {
fdf::error("Failed to flush cache: {}", zx_status_get_string(status));
}
std::vector<fuchsia_hardware_usb_request::Request> requests;
requests.push_back(req->take_request());
set_report_completer_ = completer.ToAsync();
auto result = (*ep_out_)->QueueRequests(std::move(requests));
if (result.is_error()) {
fdf::error("Failed to queue requests: {}", result.error_value().FormatDescription());
set_report_completer_->ReplyError(result.error_value().status());
}
return;
}
auto status = UsbHidControlOut(
USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE, USB_HID_SET_REPORT,
(static_cast<uint16_t>(static_cast<uint16_t>(request->rpt_type) << 8 | request->rpt_id)),
interface_, request->data.data(), request->data.size(), NULL);
if (status != ZX_OK) {
completer.ReplyError(status);
return;
}
completer.ReplySuccess();
}
void UsbHidbus::GetIdle(fhidbus::wire::HidbusGetIdleRequest* request,
GetIdleCompleter::Sync& completer) {
uint8_t duration;
auto status = UsbHidControlIn(USB_DIR_IN | USB_TYPE_CLASS | USB_RECIP_INTERFACE, USB_HID_GET_IDLE,
request->rpt_id, interface_, &duration, sizeof(duration), NULL);
if (status != ZX_OK) {
completer.ReplyError(status);
return;
}
completer.ReplySuccess(duration);
}
void UsbHidbus::SetIdle(fhidbus::wire::HidbusSetIdleRequest* request,
SetIdleCompleter::Sync& completer) {
auto status = UsbHidControlOut(
USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE, USB_HID_SET_IDLE,
static_cast<uint16_t>((request->duration << 8) | request->rpt_id), interface_, NULL, 0, NULL);
if (status != ZX_OK) {
completer.ReplyError(status);
return;
}
completer.ReplySuccess();
}
void UsbHidbus::GetProtocol(GetProtocolCompleter::Sync& completer) {
uint8_t protocol;
auto status =
UsbHidControlIn(USB_DIR_IN | USB_TYPE_CLASS | USB_RECIP_INTERFACE, USB_HID_GET_PROTOCOL, 0,
interface_, &protocol, sizeof(protocol), NULL);
if (status != ZX_OK) {
completer.ReplyError(status);
return;
}
completer.ReplySuccess(static_cast<fhidbus::wire::HidProtocol>(protocol));
}
void UsbHidbus::SetProtocol(fhidbus::wire::HidbusSetProtocolRequest* request,
SetProtocolCompleter::Sync& completer) {
auto status =
UsbHidControlOut(USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE, USB_HID_SET_PROTOCOL,
static_cast<uint8_t>(request->protocol), interface_, NULL, 0, NULL);
if (status != ZX_OK) {
completer.ReplyError(status);
return;
}
completer.ReplySuccess();
}
void UsbHidbus::PrepareStop(fdf::PrepareStopCompleter completer) {
unbind_thread_ = std::thread([this, completer = std::move(completer)]() mutable {
ep_in_->CancelAll().Then([](fidl::Result<fendpoint::Endpoint::CancelAll>& result) {
if (result.is_error()) {
fdf::error("Failed to cancel all for in endpoint: {}",
result.error_value().FormatDescription().c_str());
}
});
if (ep_out_.has_value()) {
(*ep_out_)->CancelAll().Then([](fidl::Result<fendpoint::Endpoint::CancelAll>& result) {
if (result.is_error()) {
fdf::error("Failed to cancel all for out endpoint: {}",
result.error_value().FormatDescription().c_str());
}
});
}
completer(zx::ok());
});
}
void UsbHidbus::Stop() {
usb_desc_iter_release(&desc_iter_);
unbind_thread_.join();
}
void UsbHidbus::FindDescriptors(usb::Interface interface, const usb_hid_descriptor_t** hid_desc,
const usb_endpoint_descriptor_t** endptin,
const usb_endpoint_descriptor_t** endptout) {
for (const auto& descriptor : interface.GetDescriptorList()) {
if (descriptor.b_descriptor_type == USB_DT_HID) {
*hid_desc = reinterpret_cast<const usb_hid_descriptor_t*>(&descriptor);
} else if (descriptor.b_descriptor_type == USB_DT_ENDPOINT) {
auto endpt_desc = reinterpret_cast<const usb_endpoint_descriptor_t*>(&descriptor);
if (usb_ep_type(endpt_desc) == USB_ENDPOINT_INTERRUPT) {
switch (usb_ep_direction(endpt_desc)) {
case USB_ENDPOINT_IN:
*endptin = endpt_desc;
break;
case USB_ENDPOINT_OUT:
*endptout = endpt_desc;
break;
default:
break;
}
}
}
}
}
zx::result<> UsbHidbus::Start() {
zx::result usb_banjo = compat::ConnectBanjo<ddk::UsbProtocolClient>(incoming());
if (usb_banjo.is_error()) {
fdf::error("Failed to connect to usb banjo: {}", usb_banjo);
return usb_banjo.take_error();
}
usb_ = usb_banjo.value();
zx::result usb_fidl = incoming()->Connect<fuchsia_hardware_usb::UsbService::Device>();
if (usb_fidl.is_error()) {
fdf::error("Failed to connect to usb fidl: {}", usb_fidl.is_error());
return usb_fidl.take_error();
}
dispatcher_loop_.StartThread("usb-hid-dispatcher-loop");
zx_status_t status;
usb_device_descriptor_t device_desc;
usb_.GetDeviceDescriptor(&device_desc);
auto info_builder = fhidbus::wire::HidInfo::Builder(arena_);
info_builder.vendor_id(le16toh(device_desc.id_vendor))
.product_id(le16toh(device_desc.id_product))
.version(0);
parent_req_size_ = usb_.GetRequestSize();
status = usb::InterfaceList::Create(usb_, true, &usb_interface_list_);
if (status != ZX_OK) {
return zx::error(status);
}
const usb_hid_descriptor_t* hid_desc = NULL;
const usb_endpoint_descriptor_t* endptin = NULL;
const usb_endpoint_descriptor_t* endptout = NULL;
auto interface = *usb_interface_list_->begin();
FindDescriptors(interface, &hid_desc, &endptin, &endptout);
if (!hid_desc) {
status = ZX_ERR_NOT_SUPPORTED;
return zx::error(status);
}
if (!endptin) {
status = ZX_ERR_NOT_SUPPORTED;
return zx::error(status);
}
hid_desc_ = hid_desc;
status =
ep_in_.Init(endptin->b_endpoint_address, usb_fidl.value(), dispatcher_loop_.dispatcher());
if (status != ZX_OK) {
fdf::error("Failed to init IN ep: {}", zx_status_get_string(status));
return zx::error(status);
}
// Calculation according to 9.6.6 of USB2.0 Spec for interrupt endpoints
switch (auto speed = usb_.GetSpeed()) {
case USB_SPEED_LOW:
case USB_SPEED_FULL:
if (endptin->b_interval > 255 || endptin->b_interval < 1) {
fdf::error("bInterval for LOW/FULL Speed EPs must be between 1 and 255. bInterval = {}",
endptin->b_interval);
return zx::error(ZX_ERR_OUT_OF_RANGE);
}
info_builder.polling_rate(zx::msec(endptin->b_interval).to_usecs());
break;
case USB_SPEED_HIGH:
if (endptin->b_interval > 16 || endptin->b_interval < 1) {
fdf::error("bInterval for HIGH Speed EPs must be between 1 and 16. bInterval = {}",
endptin->b_interval);
return zx::error(ZX_ERR_OUT_OF_RANGE);
}
info_builder.polling_rate((uint64_t{1} << (endptin->b_interval - 1)) *
zx::usec(125).to_usecs());
break;
default:
fdf::error("Unrecognized USB Speed {}", speed);
return zx::error(ZX_ERR_NOT_SUPPORTED);
}
if (endptout) {
ep_out_.emplace(usb::EndpointType::INTERRUPT, this, std::mem_fn(&UsbHidbus::SetReportComplete));
status = ep_out_->Init(endptout->b_endpoint_address, usb_fidl.value(),
dispatcher_loop_.dispatcher());
if (status != ZX_OK) {
fdf::error("Failed to init IN ep {}", status);
return zx::error(status);
}
auto actual = ep_out_->AddRequests(1, usb_ep_max_packet(endptout),
fuchsia_hardware_usb_request::Buffer::Tag::kData);
if (actual == 0) {
fdf::error("Could not add any requests!");
return zx::error(ZX_ERR_INTERNAL);
}
if (actual != 1) {
fdf::warn("Wanted {} request, got {} requests", 1, actual);
}
}
interface_ = interface.descriptor()->b_interface_number;
info_builder.dev_num(interface_);
if (interface.descriptor()->b_interface_protocol == USB_HID_PROTOCOL_KBD) {
info_builder.boot_protocol(fhidbus::wire::HidBootProtocol::kKbd);
} else if (interface.descriptor()->b_interface_protocol == USB_HID_PROTOCOL_MOUSE) {
info_builder.boot_protocol(fhidbus::wire::HidBootProtocol::kPointer);
} else {
info_builder.boot_protocol(fhidbus::wire::HidBootProtocol::kNone);
}
info_ = info_builder.Build();
auto actual = ep_in_.AddRequests(1, usb_ep_max_packet(endptin),
fuchsia_hardware_usb_request::Buffer::Tag::kVmoId);
if (actual == 0) {
fdf::error("Could not add any requests!");
return zx::error(ZX_ERR_INTERNAL);
}
if (actual != 1) {
fdf::warn("Wanted {} request, got {} requests", 1, actual);
}
auto result = outgoing()->AddService<fhidbus::Service>(fhidbus::Service::InstanceHandler({
.device =
[this](fidl::ServerEnd<fhidbus::Hidbus> server_end) {
if (binding_) {
server_end.Close(ZX_ERR_ALREADY_BOUND);
return;
}
binding_.emplace(dispatcher(), std::move(server_end), this,
[this](fidl::UnbindInfo info) {
StopHidbus();
binding_.reset();
});
},
}));
if (result.is_error()) {
fdf::error("Failed to add Hidbus protocol: {}", result);
return result.take_error();
}
std::vector offers = {fdf::MakeOffer2<fhidbus::Service>()};
std::vector properties = {
fdf::MakeProperty2(bind_fuchsia::PROTOCOL, static_cast<uint32_t>(ZX_PROTOCOL_HIDBUS))};
zx::result child = AddChild(kChildNodeName, properties, offers);
if (child.is_error()) {
fdf::error("Failed to add child: {}", child);
return child.take_error();
}
child_ = std::move(child.value());
return zx::ok();
}
} // namespace usb_hid
FUCHSIA_DRIVER_EXPORT(usb_hid::UsbHidbus);