blob: 9df09adbdb972e18bae85cbbdefeb1dad2f97388 [file] [log] [blame]
/*
* 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