Home | History | Annotate | Download | only in usbip
      1 /*
      2  * Copyright (C) 2017 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 #include <errno.h>
     18 #include <string.h>
     19 
     20 #include <arpa/inet.h>
     21 #include <netdb.h>
     22 #include <sys/socket.h>
     23 
     24 #include <glog/logging.h>
     25 #include <fstream>
     26 #include <limits>
     27 #include <sstream>
     28 #include "common/libs/fs/shared_select.h"
     29 
     30 #include "common/libs/fs/shared_fd.h"
     31 #include "host/libs/usbip/vhci_instrument.h"
     32 
     33 namespace vadb {
     34 namespace usbip {
     35 namespace {
     36 // Device ID is specified as a concatenated pair of BUS and DEVICE id.
     37 // Since we only export one device and our server doesn't care much about
     38 // its number, we use the default value of BUS=1 and DEVICE=1.
     39 // This can be set to something else and should still work, as long as
     40 // numbers are valid in USB sense.
     41 constexpr uint32_t kDefaultDeviceID = (1 << 16) | 1;
     42 
     43 // Request Highspeed configuration. Superspeed isn't supported by vhci.
     44 // Supported configurations are:
     45 //  4 -> wireless
     46 //  3 -> highspeed
     47 //  2 -> full speed
     48 //  1 -> low speed
     49 //  Please refer to the Kernel source tree in the following locations:
     50 //     include/uapi/linux/usb/ch9.h
     51 //     drivers/usb/usbip/vhci_sysfs.c
     52 constexpr uint32_t kDefaultDeviceSpeed = 3;
     53 
     54 // Subsystem and device type where VHCI driver is located.
     55 const char* const kVHCIPlatformPaths[] = {
     56     "/sys/devices/platform/vhci_hcd",
     57     "/sys/devices/platform/vhci_hcd.1",
     58 };
     59 
     60 // Control messages.
     61 // Attach tells thread to attach remote device.
     62 // Detach tells thread to detach remote device.
     63 using ControlMsgType = uint8_t;
     64 constexpr ControlMsgType kControlAttach = 'A';
     65 constexpr ControlMsgType kControlDetach = 'D';
     66 constexpr ControlMsgType kControlExit = 'E';
     67 
     68 // Used with EPOLL as epoll_data to determine event type.
     69 enum EpollEventType {
     70   kControlEvent,
     71   kVHCIEvent,
     72 };
     73 
     74 }  // anonymous namespace
     75 
     76 VHCIInstrument::VHCIInstrument(int port, const std::string& name)
     77     : name_(name), port_{port} {}
     78 
     79 VHCIInstrument::~VHCIInstrument() {
     80   control_write_end_->Write(&kControlExit, sizeof(kControlExit));
     81   attach_thread_.join();
     82 }
     83 
     84 bool VHCIInstrument::Init() {
     85   cvd::SharedFD::Pipe(&control_read_end_, &control_write_end_);
     86 
     87   struct stat buf;
     88   for (const auto* path : kVHCIPlatformPaths) {
     89     if (stat(path, &buf) == 0) {
     90       syspath_ = path;
     91       break;
     92     }
     93   }
     94 
     95   if (syspath_.empty()) {
     96     LOG(ERROR) << "VHCI not available. Is the driver loaded?";
     97     LOG(ERROR) << "Try: sudo modprobe vhci_hcd";
     98     LOG(ERROR) << "The driver is part of linux-image-extra-`uname -r` package";
     99     return false;
    100   }
    101 
    102   if (!VerifyPortIsFree()) {
    103     LOG(ERROR) << "Trying to use VHCI port " << port_ << " but it is already in"
    104                << " use.";
    105     return false;
    106   }
    107 
    108   LOG(INFO) << "Using VHCI port " << port_;
    109   attach_thread_ = std::thread([this] { AttachThread(); });
    110   return true;
    111 }
    112 
    113 bool VHCIInstrument::VerifyPortIsFree() const {
    114   std::ifstream status_file(syspath_ + "/status");
    115 
    116   if (!status_file.good()) {
    117     LOG(ERROR) << "Could not open usb-ip status file.";
    118     return false;
    119   }
    120 
    121   // Skip past the header line.
    122   status_file.ignore(std::numeric_limits<std::streamsize>::max(), '\n');
    123 
    124   while (true) {
    125     // Port status values deducted from /sys/devices/platform/vhci_hcd/status
    126     // kVHCIPortFree indicates the port is not currently in use.
    127     constexpr static int kVHCIStatusPortFree = 4;
    128 
    129     int port{};
    130     int status{};
    131     status_file >> port >> status;
    132     if (!status_file.good()) {
    133       break;
    134     }
    135 
    136     status_file.ignore(std::numeric_limits<std::streamsize>::max(), '\n');
    137     if (port_ == port) {
    138       return status == kVHCIStatusPortFree;
    139     }
    140   }
    141   LOG(ERROR) << "Couldn't find status for VHCI port " << port_;
    142   return false;
    143 }
    144 
    145 void VHCIInstrument::TriggerAttach() {
    146   control_write_end_->Write(&kControlAttach, sizeof(kControlAttach));
    147 }
    148 
    149 void VHCIInstrument::TriggerDetach() {
    150   control_write_end_->Write(&kControlDetach, sizeof(kControlDetach));
    151 }
    152 
    153 void VHCIInstrument::AttachThread() {
    154   cvd::SharedFD epoll = cvd::SharedFD::Epoll();
    155   // Trigger attach upon start.
    156   bool want_attach = true;
    157   // Operation is pending on read.
    158   bool is_pending = false;
    159 
    160   epoll_event control_event;
    161   control_event.events = EPOLLIN;
    162   control_event.data.u64 = kControlEvent;
    163   epoll_event vhci_event;
    164   vhci_event.events = EPOLLRDHUP | EPOLLONESHOT;
    165   vhci_event.data.u64 = kVHCIEvent;
    166 
    167   epoll->EpollCtl(EPOLL_CTL_ADD, control_read_end_, &control_event);
    168   while (true) {
    169     if (vhci_socket_->IsOpen()) {
    170       epoll->EpollCtl(EPOLL_CTL_ADD, vhci_socket_, &vhci_event);
    171     }
    172 
    173     epoll_event found_event{};
    174     ControlMsgType request_type;
    175 
    176     if (epoll->EpollWait(&found_event, 1, 1000)) {
    177       switch (found_event.data.u64) {
    178         case kControlEvent:
    179           control_read_end_->Read(&request_type, sizeof(request_type));
    180           is_pending = true;
    181           want_attach = request_type == kControlAttach;
    182           LOG(INFO) << (want_attach ? "Attach" : "Detach") << " triggered.";
    183           break;
    184         case kVHCIEvent:
    185           vhci_socket_ = cvd::SharedFD();
    186           // Only re-establish VHCI if it was already established before.
    187           is_pending = want_attach;
    188           // Do not immediately fall into attach cycle. It will likely complete
    189           // before VHCI finishes deregistering this callback.
    190           continue;
    191       }
    192     }
    193 
    194     // Make an attempt to re-attach. If successful, clear pending attach flag.
    195     if (is_pending) {
    196       if (want_attach && Attach()) {
    197         is_pending = false;
    198       } else if (!want_attach && Detach()) {
    199         is_pending = false;
    200       } else {
    201         LOG(INFO) << (want_attach ? "Attach" : "Detach") << " unsuccessful. "
    202                   << "Will re-try.";
    203         sleep(1);
    204       }
    205     }
    206   }
    207 }
    208 
    209 bool VHCIInstrument::Detach() {
    210   std::stringstream result;
    211   result << port_;
    212   std::ofstream detach(syspath_ + "/detach");
    213 
    214   if (!detach.is_open()) {
    215     LOG(WARNING) << "Could not open VHCI detach file.";
    216     return false;
    217   }
    218   detach << result.str();
    219   return detach.rdstate() == std::ios_base::goodbit;
    220 }
    221 
    222 bool VHCIInstrument::Attach() {
    223   if (!vhci_socket_->IsOpen()) {
    224     vhci_socket_ =
    225         cvd::SharedFD::SocketLocalClient(name_.c_str(), true, SOCK_STREAM);
    226     if (!vhci_socket_->IsOpen()) return false;
    227   }
    228 
    229   int sys_fd = vhci_socket_->UNMANAGED_Dup();
    230   bool success = false;
    231 
    232   {
    233     std::stringstream result;
    234     result << port_ << ' ' << sys_fd << ' ' << kDefaultDeviceID << ' '
    235            << kDefaultDeviceSpeed;
    236     std::string path = syspath_ + "/attach";
    237     std::ofstream attach(path);
    238 
    239     if (!attach.is_open()) {
    240       LOG(WARNING) << "Could not open VHCI attach file " << path << " ("
    241                    << strerror(errno) << ")";
    242       close(sys_fd);
    243       return false;
    244     }
    245     attach << result.str();
    246 
    247     // It is unclear whether duplicate FD should remain open or not. There are
    248     // cases supporting both assumptions, likely related to kernel version.
    249     // Kernel 4.10 is having problems communicating with USB/IP server if the
    250     // socket is closed after it's passed to kernel. It is a clear indication
    251     // that the kernel requires the socket to be kept open.
    252     success = attach.rdstate() == std::ios_base::goodbit;
    253     // Make sure everything was written and flushed. This happens when we close
    254     // the ofstream attach.
    255   }
    256 
    257   close(sys_fd);
    258   return success;
    259 }
    260 
    261 }  // namespace usbip
    262 }  // namespace vadb
    263