| /* |
| * 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. |
| */ |
| |
| #include <errno.h> |
| #include <string.h> |
| |
| #include <arpa/inet.h> |
| #include <netdb.h> |
| #include <sys/socket.h> |
| |
| #include <glog/logging.h> |
| #include <fstream> |
| #include <limits> |
| #include <sstream> |
| #include "common/libs/fs/shared_select.h" |
| |
| #include "common/libs/fs/shared_fd.h" |
| #include "host/commands/virtual_usb_manager/usbip/vhci_instrument.h" |
| |
| namespace vadb { |
| namespace usbip { |
| namespace { |
| // Device ID is specified as a concatenated pair of BUS and DEVICE id. |
| // Since we only export one device and our server doesn't care much about |
| // its number, we use the default value of BUS=1 and DEVICE=1. |
| // This can be set to something else and should still work, as long as |
| // numbers are valid in USB sense. |
| constexpr uint32_t kDefaultDeviceID = (1 << 16) | 1; |
| |
| // Request Highspeed configuration. Superspeed isn't supported by vhci. |
| // Supported configurations are: |
| // 4 -> wireless |
| // 3 -> highspeed |
| // 2 -> full speed |
| // 1 -> low speed |
| // Please refer to the Kernel source tree in the following locations: |
| // include/uapi/linux/usb/ch9.h |
| // drivers/usb/usbip/vhci_sysfs.c |
| constexpr uint32_t kDefaultDeviceSpeed = 3; |
| |
| // Subsystem and device type where VHCI driver is located. |
| const char* const kVHCIPlatformPaths[] = { |
| "/sys/devices/platform/vhci_hcd", |
| "/sys/devices/platform/vhci_hcd.1", |
| }; |
| |
| // Control messages. |
| // Attach tells thread to attach remote device. |
| // Detach tells thread to detach remote device. |
| using ControlMsgType = uint8_t; |
| constexpr ControlMsgType kControlAttach = 'A'; |
| constexpr ControlMsgType kControlDetach = 'D'; |
| constexpr ControlMsgType kControlExit = 'E'; |
| |
| // Used with EPOLL as epoll_data to determine event type. |
| enum EpollEventType { |
| kControlEvent, |
| kVHCIEvent, |
| }; |
| |
| } // anonymous namespace |
| |
| VHCIInstrument::VHCIInstrument(int port, const std::string& name) |
| : name_(name), port_{port} {} |
| |
| VHCIInstrument::~VHCIInstrument() { |
| control_write_end_->Write(&kControlExit, sizeof(kControlExit)); |
| attach_thread_.join(); |
| } |
| |
| bool VHCIInstrument::Init() { |
| cvd::SharedFD::Pipe(&control_read_end_, &control_write_end_); |
| |
| struct stat buf; |
| for (const auto* path : kVHCIPlatformPaths) { |
| if (stat(path, &buf) == 0) { |
| syspath_ = path; |
| break; |
| } |
| } |
| |
| if (syspath_.empty()) { |
| LOG(ERROR) << "VHCI not available. Is the driver loaded?"; |
| LOG(ERROR) << "Try: sudo modprobe vhci_hcd"; |
| LOG(ERROR) << "The driver is part of linux-image-extra-`uname -r` package"; |
| return false; |
| } |
| |
| if (!VerifyPortIsFree()) { |
| LOG(ERROR) << "Trying to use VHCI port " << port_ << " but it is already in" |
| << " use."; |
| return false; |
| } |
| |
| LOG(INFO) << "Using VHCI port " << port_; |
| attach_thread_ = std::thread([this] { AttachThread(); }); |
| return true; |
| } |
| |
| bool VHCIInstrument::VerifyPortIsFree() const { |
| std::ifstream status_file(syspath_ + "/status"); |
| |
| if (!status_file.good()) { |
| LOG(ERROR) << "Could not open usb-ip status file."; |
| return false; |
| } |
| |
| // Skip past the header line. |
| status_file.ignore(std::numeric_limits<std::streamsize>::max(), '\n'); |
| |
| while (true) { |
| // Port status values deducted from /sys/devices/platform/vhci_hcd/status |
| // kVHCIPortFree indicates the port is not currently in use. |
| constexpr static int kVHCIStatusPortFree = 4; |
| |
| int port{}; |
| int status{}; |
| status_file >> port >> status; |
| if (!status_file.good()) { |
| break; |
| } |
| |
| status_file.ignore(std::numeric_limits<std::streamsize>::max(), '\n'); |
| if (port_ == port) { |
| return status == kVHCIStatusPortFree; |
| } |
| } |
| LOG(ERROR) << "Couldn't find status for VHCI port " << port_; |
| return false; |
| } |
| |
| void VHCIInstrument::TriggerAttach() { |
| control_write_end_->Write(&kControlAttach, sizeof(kControlAttach)); |
| } |
| |
| void VHCIInstrument::TriggerDetach() { |
| control_write_end_->Write(&kControlDetach, sizeof(kControlDetach)); |
| } |
| |
| void VHCIInstrument::AttachThread() { |
| cvd::SharedFD epoll = cvd::SharedFD::Epoll(); |
| // Trigger attach upon start. |
| bool want_attach = true; |
| // Operation is pending on read. |
| bool is_pending = false; |
| |
| epoll_event control_event; |
| control_event.events = EPOLLIN; |
| control_event.data.u64 = kControlEvent; |
| epoll_event vhci_event; |
| vhci_event.events = EPOLLRDHUP | EPOLLONESHOT; |
| vhci_event.data.u64 = kVHCIEvent; |
| |
| epoll->EpollCtl(EPOLL_CTL_ADD, control_read_end_, &control_event); |
| while (true) { |
| if (vhci_socket_->IsOpen()) { |
| epoll->EpollCtl(EPOLL_CTL_ADD, vhci_socket_, &vhci_event); |
| } |
| |
| epoll_event found_event{}; |
| ControlMsgType request_type; |
| |
| if (epoll->EpollWait(&found_event, 1, 1000)) { |
| switch (found_event.data.u64) { |
| case kControlEvent: |
| control_read_end_->Read(&request_type, sizeof(request_type)); |
| is_pending = true; |
| want_attach = request_type == kControlAttach; |
| LOG(INFO) << (want_attach ? "Attach" : "Detach") << " triggered."; |
| break; |
| case kVHCIEvent: |
| vhci_socket_ = cvd::SharedFD(); |
| // Only re-establish VHCI if it was already established before. |
| is_pending = want_attach; |
| // Do not immediately fall into attach cycle. It will likely complete |
| // before VHCI finishes deregistering this callback. |
| continue; |
| } |
| } |
| |
| // Make an attempt to re-attach. If successful, clear pending attach flag. |
| if (is_pending) { |
| if (want_attach && Attach()) { |
| is_pending = false; |
| } else if (!want_attach && Detach()) { |
| is_pending = false; |
| } else { |
| LOG(INFO) << (want_attach ? "Attach" : "Detach") << " unsuccessful. " |
| << "Will re-try."; |
| sleep(1); |
| } |
| } |
| } |
| } |
| |
| bool VHCIInstrument::Detach() { |
| std::stringstream result; |
| result << port_; |
| std::ofstream detach(syspath_ + "/detach"); |
| |
| if (!detach.is_open()) { |
| LOG(WARNING) << "Could not open VHCI detach file."; |
| return false; |
| } |
| detach << result.str(); |
| return detach.rdstate() == std::ios_base::goodbit; |
| } |
| |
| bool VHCIInstrument::Attach() { |
| if (!vhci_socket_->IsOpen()) { |
| vhci_socket_ = |
| cvd::SharedFD::SocketLocalClient(name_.c_str(), true, SOCK_STREAM); |
| if (!vhci_socket_->IsOpen()) return false; |
| } |
| |
| int sys_fd = vhci_socket_->UNMANAGED_Dup(); |
| bool success = false; |
| |
| { |
| std::stringstream result; |
| result << port_ << ' ' << sys_fd << ' ' << kDefaultDeviceID << ' ' |
| << kDefaultDeviceSpeed; |
| std::string path = syspath_ + "/attach"; |
| std::ofstream attach(path); |
| |
| if (!attach.is_open()) { |
| LOG(WARNING) << "Could not open VHCI attach file " << path << " (" |
| << strerror(errno) << ")"; |
| close(sys_fd); |
| return false; |
| } |
| attach << result.str(); |
| |
| // It is unclear whether duplicate FD should remain open or not. There are |
| // cases supporting both assumptions, likely related to kernel version. |
| // Kernel 4.10 is having problems communicating with USB/IP server if the |
| // socket is closed after it's passed to kernel. It is a clear indication |
| // that the kernel requires the socket to be kept open. |
| success = attach.rdstate() == std::ios_base::goodbit; |
| // Make sure everything was written and flushed. This happens when we close |
| // the ofstream attach. |
| } |
| |
| close(sys_fd); |
| return success; |
| } |
| |
| } // namespace usbip |
| } // namespace vadb |