| /* |
| * Copyright (C) 2017 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| // #undef NDEBUG |
| |
| #include "guest/commands/usbforward/usb_server.h" |
| |
| #include <string> |
| #include <vector> |
| #include <strings.h> |
| #include <log/log.h> |
| #include <libusb/libusb.h> |
| #include "common/libs/fs/shared_select.h" |
| #include "common/libs/usbforward/protocol.h" |
| #include "guest/commands/usbforward/transport_request.h" |
| |
| namespace usb_forward { |
| namespace { |
| // USBServer exports device kExportedVendorID:kExportedProductID to the server. |
| // We will not support exporting multiple USB devices as there's no practical |
| // need for this. |
| constexpr uint16_t kExportedVendorID = 0x18d1; |
| constexpr uint16_t kExportedProductID = 0x4ee7; |
| |
| // Use default BUS and DEVICE IDs so that it's easier to attach over USB/IP. |
| constexpr uint8_t kDefaultBusID = 1; |
| constexpr uint8_t kDefaultDevID = 1; |
| |
| std::shared_ptr<libusb_device_handle> GetDevice() { |
| std::shared_ptr<libusb_device_handle> res( |
| libusb_open_device_with_vid_pid(nullptr, kExportedVendorID, |
| kExportedProductID), |
| [](libusb_device_handle* h) { |
| // Apparently, deleter is called even on an uninitialized shared_ptr. |
| if (h != nullptr) { |
| libusb_release_interface(h, 0); |
| libusb_close(h); |
| } |
| }); |
| |
| if (res) libusb_claim_interface(res.get(), 0); |
| |
| return res; |
| } |
| |
| } // anonymous namespace |
| |
| bool USBServer::GetDeviceInfo( |
| DeviceInfo* info, std::vector<InterfaceInfo>* ifaces) { |
| if (!handle_) return false; |
| |
| // This function does not modify the reference count of the returned device, |
| // so do not feel compelled to unreference it when you are done. |
| libusb_device* dev = libusb_get_device(handle_.get()); |
| |
| libusb_device_descriptor desc; |
| libusb_config_descriptor* conf; |
| memset(info, 0, sizeof(*info)); |
| |
| int res = libusb_get_device_descriptor(dev, &desc); |
| if (res < 0) { |
| // This shouldn't really happen. |
| ALOGE("libusb_get_device_descriptor failed %d", res); |
| return false; |
| } |
| |
| res = libusb_get_active_config_descriptor(dev, &conf); |
| if (res < 0) { |
| // This shouldn't really happen. |
| ALOGE("libusb_get_active_config_descriptor failed %d", res); |
| libusb_free_config_descriptor(conf); |
| return false; |
| } |
| |
| info->vendor_id = desc.idVendor; |
| info->product_id = desc.idProduct; |
| info->dev_version = desc.bcdDevice; |
| info->dev_class = desc.bDeviceClass; |
| info->dev_subclass = desc.bDeviceSubClass; |
| info->dev_protocol = desc.bDeviceProtocol; |
| info->speed = libusb_get_device_speed(dev); |
| info->num_configurations = desc.bNumConfigurations; |
| info->num_interfaces = conf->bNumInterfaces; |
| info->cur_configuration = conf->bConfigurationValue; |
| info->bus_id = kDefaultBusID; |
| info->dev_id = kDefaultDevID; |
| |
| if (ifaces != nullptr) { |
| for (int ifidx = 0; ifidx < conf->bNumInterfaces; ++ifidx) { |
| const libusb_interface& iface = conf->interface[ifidx]; |
| for (int altidx = 0; altidx < iface.num_altsetting; ++altidx) { |
| const libusb_interface_descriptor& alt = iface.altsetting[altidx]; |
| ifaces->push_back(InterfaceInfo{alt.bInterfaceClass, |
| alt.bInterfaceSubClass, |
| alt.bInterfaceProtocol, 0}); |
| } |
| } |
| } |
| libusb_free_config_descriptor(conf); |
| return true; |
| } |
| |
| USBServer::USBServer(const cvd::SharedFD& fd) |
| : fd_{fd}, |
| device_event_fd_{cvd::SharedFD::Event(0, 0)}, |
| thread_event_fd_{cvd::SharedFD::Event(0, 0)} {} |
| |
| void USBServer::HandleDeviceList(uint32_t tag) { |
| // Iterate all devices and send structure for every found device. |
| // Write header: number of devices. |
| DeviceInfo info; |
| std::vector<InterfaceInfo> ifaces; |
| bool found = GetDeviceInfo(&info, &ifaces); |
| |
| cvd::LockGuard<cvd::Mutex> lock(write_mutex_); |
| ResponseHeader rsp{StatusSuccess, tag}; |
| fd_->Write(&rsp, sizeof(rsp)); |
| if (found) { |
| uint32_t cnt = 1; |
| fd_->Write(&cnt, sizeof(cnt)); |
| fd_->Write(&info, sizeof(info)); |
| fd_->Write(ifaces.data(), ifaces.size() * sizeof(InterfaceInfo)); |
| } else { |
| // No devices. |
| uint32_t cnt = 0; |
| fd_->Write(&cnt, sizeof(cnt)); |
| } |
| } |
| |
| void USBServer::HandleAttach(uint32_t tag) { |
| // We read the request, but it no longer plays any significant role here. |
| AttachRequest req; |
| if (fd_->Read(&req, sizeof(req)) != sizeof(req)) return; |
| |
| cvd::LockGuard<cvd::Mutex> lock(write_mutex_); |
| ResponseHeader rsp{handle_ ? StatusSuccess : StatusFailure, tag}; |
| fd_->Write(&rsp, sizeof(rsp)); |
| } |
| |
| void USBServer::HandleHeartbeat(uint32_t tag) { |
| cvd::LockGuard<cvd::Mutex> lock(write_mutex_); |
| ResponseHeader rsp{handle_ ? StatusSuccess : StatusFailure, tag}; |
| fd_->Write(&rsp, sizeof(rsp)); |
| } |
| |
| void USBServer::HandleControlTransfer(uint32_t tag) { |
| ControlTransfer req; |
| // If disconnected prematurely, don't send response. |
| if (fd_->Read(&req, sizeof(req)) != sizeof(req)) return; |
| |
| // Technically speaking this isn't endpoint, but names, masks, values and |
| // meaning here is exactly same. |
| bool is_data_in = |
| ((req.type & LIBUSB_ENDPOINT_DIR_MASK) == LIBUSB_ENDPOINT_IN); |
| |
| std::unique_ptr<TransportRequest> treq(new TransportRequest( |
| handle_, |
| [this, is_data_in, tag](bool is_success, const uint8_t* data, |
| int32_t length) { |
| OnTransferComplete(tag, is_data_in, is_success, data, length); |
| }, |
| req)); |
| |
| if (!is_data_in && req.length) { |
| // If disconnected prematurely, don't send response. |
| int32_t got = 0; |
| while (got < req.length) { |
| auto read = fd_->Read(&treq->Buffer()[got], req.length - got); |
| if (fd_->GetErrno() != 0) { |
| ALOGE("Failed to read from client: %s", fd_->StrError()); |
| return; |
| } else if (read == 0) { |
| ALOGE("Failed to read from client: short read"); |
| return; |
| } |
| got += read; |
| } |
| } |
| |
| // At this point we store transport request internally until it completes. |
| TransportRequest* treq_ptr = treq.get(); |
| { |
| cvd::LockGuard<cvd::Mutex> lock(requests_mutex_); |
| requests_in_flight_[tag] = std::move(treq); |
| } |
| |
| if (!treq_ptr->Submit()) { |
| OnTransferComplete(tag, is_data_in, false, nullptr, 0); |
| } |
| } |
| |
| void USBServer::HandleDataTransfer(uint32_t tag) { |
| DataTransfer req; |
| // If disconnected prematurely, don't send response. |
| if (fd_->Read(&req, sizeof(req)) != sizeof(req)) return; |
| |
| bool is_data_in = !req.is_host_to_device; |
| |
| std::unique_ptr<TransportRequest> treq(new TransportRequest( |
| handle_, |
| [this, is_data_in, tag](bool is_success, const uint8_t* data, |
| int32_t length) { |
| OnTransferComplete(tag, is_data_in, is_success, data, length); |
| }, |
| req)); |
| |
| if (!is_data_in && req.length) { |
| // If disconnected prematurely, don't send response. |
| int32_t got = 0; |
| while (got < req.length) { |
| auto read = fd_->Read(&treq->Buffer()[got], req.length - got); |
| if (fd_->GetErrno() != 0) { |
| ALOGE("Failed to read from client: %s", fd_->StrError()); |
| return; |
| } else if (read == 0) { |
| ALOGE("Failed to read from client: short read"); |
| return; |
| } |
| got += read; |
| } |
| } |
| |
| // At this point we store transport request internally until it completes. |
| TransportRequest* treq_ptr = treq.get(); |
| { |
| cvd::LockGuard<cvd::Mutex> lock(requests_mutex_); |
| requests_in_flight_[tag] = std::move(treq); |
| } |
| |
| if (!treq_ptr->Submit()) { |
| OnTransferComplete(tag, is_data_in, false, nullptr, 0); |
| } |
| } |
| |
| void USBServer::OnTransferComplete(uint32_t tag, bool is_data_in, |
| bool is_success, const uint8_t* buffer, |
| int32_t actual_length) { |
| ResponseHeader rsp{is_success ? StatusSuccess : StatusFailure, tag}; |
| |
| cvd::LockGuard<cvd::Mutex> lock(write_mutex_); |
| fd_->Write(&rsp, sizeof(rsp)); |
| if (is_success && is_data_in) { |
| fd_->Write(&actual_length, sizeof(actual_length)); |
| if (actual_length > 0) { |
| // NOTE: don't use buffer_ here directly, as libusb uses first few bytes |
| // to store control data there. |
| int32_t sent = 0; |
| while (sent < actual_length) { |
| int packet_size = fd_->Write(&buffer[sent], actual_length - sent); |
| sent += packet_size; |
| ALOGV("Sending response, %d / %d bytes sent", sent, actual_length); |
| if (fd_->GetErrno() != 0) { |
| ALOGE("Send failed: %s", fd_->StrError()); |
| return; |
| } |
| } |
| } |
| } |
| |
| { |
| cvd::LockGuard<cvd::Mutex> lock(requests_mutex_); |
| requests_in_flight_.erase(tag); |
| } |
| } |
| |
| int USBServer::HandleDeviceEvent(libusb_context*, libusb_device*, |
| libusb_hotplug_event event, void* self_raw) { |
| auto self = reinterpret_cast<USBServer*>(self_raw); |
| int64_t dummy = 1; |
| self->device_event_fd_->Write(&dummy, sizeof(dummy)); |
| return 0; |
| } |
| |
| void* USBServer::ProcessLibUSBRequests(void* self_raw) { |
| USBServer* self = reinterpret_cast<USBServer*>(self_raw); |
| ALOGI("Starting hotplug thread."); |
| |
| cvd::SharedFDSet rset; |
| while (true) { |
| // Do not wait if there's no event. |
| timeval select_timeout{0, 0}; |
| rset.Zero(); |
| rset.Set(self->thread_event_fd_); |
| int ret = cvd::Select(&rset, nullptr, nullptr, &select_timeout); |
| if (ret > 0) break; |
| |
| timeval libusb_timeout{1, 0}; |
| libusb_handle_events_timeout_completed(nullptr, &libusb_timeout, nullptr); |
| } |
| |
| int64_t dummy; |
| self->thread_event_fd_->Read(&dummy, sizeof(dummy)); |
| ALOGI("Shutting down hotplug thread."); |
| return nullptr; |
| } |
| |
| void USBServer::InitLibUSB() { |
| if (libusb_init(nullptr) != 0) return; |
| libusb_hotplug_register_callback( |
| nullptr, |
| libusb_hotplug_event(LIBUSB_HOTPLUG_EVENT_DEVICE_LEFT), |
| libusb_hotplug_flag(0), kExportedVendorID, kExportedProductID, |
| LIBUSB_HOTPLUG_MATCH_ANY, &USBServer::HandleDeviceEvent, this, |
| &hotplug_handle_); |
| handle_ = GetDevice(); |
| libusb_thread_.reset(new cvd::ScopedThread(&ProcessLibUSBRequests, this)); |
| } |
| |
| void USBServer::ExitLibUSB() { |
| if (!libusb_thread_) return; |
| libusb_hotplug_deregister_callback(nullptr, hotplug_handle_); |
| int64_t dummy = 1; |
| thread_event_fd_->Write(&dummy, sizeof(dummy)); |
| libusb_thread_.reset(); |
| handle_.reset(); |
| libusb_exit(nullptr); |
| } |
| |
| void USBServer::Serve() { |
| cvd::SharedFDSet rset; |
| while (true) { |
| timeval retry_timeout{1, 0}; |
| timeval* select_timeout = nullptr; |
| if (!handle_) { |
| select_timeout = &retry_timeout; |
| } |
| |
| rset.Zero(); |
| rset.Set(fd_); |
| rset.Set(device_event_fd_); |
| int ret = cvd::Select(&rset, nullptr, nullptr, select_timeout); |
| |
| // device_event_fd_ is reset each time libusb notices device has re-appeared |
| // or is gone. In both cases, the existing handle is no longer valid. |
| if (rset.IsSet(device_event_fd_)) { |
| int64_t dummy; |
| device_event_fd_->Read(&dummy, sizeof(dummy)); |
| handle_.reset(); |
| } |
| |
| if (!handle_) { |
| ExitLibUSB(); |
| InitLibUSB(); |
| if (handle_) { |
| ALOGI("Device present."); |
| } |
| } |
| |
| if (ret < 0) continue; |
| |
| if (rset.IsSet(fd_)) { |
| RequestHeader req; |
| if (fd_->Read(&req, sizeof(req)) != sizeof(req)) { |
| // There's nobody on the other side. |
| sleep(3); |
| continue; |
| } |
| |
| switch (req.command) { |
| case CmdDeviceList: |
| ALOGV("Processing DeviceList command, tag=%d", req.tag); |
| HandleDeviceList(req.tag); |
| break; |
| |
| case CmdAttach: |
| ALOGV("Processing Attach command, tag=%d", req.tag); |
| HandleAttach(req.tag); |
| break; |
| |
| case CmdControlTransfer: |
| ALOGV("Processing ControlTransfer command, tag=%d", req.tag); |
| HandleControlTransfer(req.tag); |
| break; |
| |
| case CmdDataTransfer: |
| ALOGV("Processing DataTransfer command, tag=%d", req.tag); |
| HandleDataTransfer(req.tag); |
| break; |
| |
| case CmdHeartbeat: |
| ALOGV("Processing Heartbeat command, tag=%d", req.tag); |
| HandleHeartbeat(req.tag); |
| break; |
| |
| default: |
| ALOGE("Discarding unknown command %08x, tag=%d", req.command, |
| req.tag); |
| } |
| } |
| } |
| } |
| |
| } // namespace usb_forward |