• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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/commands/virtual_usb_manager/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 
VHCIInstrument(int port,const std::string & name)76 VHCIInstrument::VHCIInstrument(int port, const std::string& name)
77     : name_(name), port_{port} {}
78 
~VHCIInstrument()79 VHCIInstrument::~VHCIInstrument() {
80   control_write_end_->Write(&kControlExit, sizeof(kControlExit));
81   attach_thread_.join();
82 }
83 
Init()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 
VerifyPortIsFree() const113 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 
TriggerAttach()145 void VHCIInstrument::TriggerAttach() {
146   control_write_end_->Write(&kControlAttach, sizeof(kControlAttach));
147 }
148 
TriggerDetach()149 void VHCIInstrument::TriggerDetach() {
150   control_write_end_->Write(&kControlDetach, sizeof(kControlDetach));
151 }
152 
AttachThread()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 
Detach()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 
Attach()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