blob: 5cb33821519d826d75727ac8b1ac1384528244ae [file] [log] [blame]
// Copyright 2018 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <fbl/auto_call.h>
#include <xdc-host-utils/conn.h>
#include <xdc-server-utils/msg.h>
#include <xdc-server-utils/packet.h>
#include <zircon/device/debug.h>
#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <sys/file.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <sys/un.h>
#include <poll.h>
#include <unistd.h>
#include "usb-handler.h"
#include "xdc-server.h"
namespace xdc {
static constexpr uint32_t MAX_PENDING_CONN_BACKLOG = 128;
static const char* XDC_LOCK_PATH = "/tmp/xdc.lock";
void Client::SetStreamId(uint32_t stream_id) {
registered_ = true;
stream_id_ = stream_id;
}
void Client::SetConnected(bool connected) {
if (connected == connected_) {
fprintf(stderr, "tried to set client with stream id %u as %s again.\n",
stream_id(), connected ? "connected" : "disconnected");
return;
}
printf("client with stream id %u is now %s to the xdc device stream.\n",
stream_id(), connected ? "connected" : "disconnected");
connected_ = connected;
}
// static
std::unique_ptr<XdcServer> XdcServer::Create() {
auto conn = std::make_unique<XdcServer>(ConstructorTag{});
if (!conn->Init()) {
return nullptr;
}
return conn;
}
bool XdcServer::Init() {
usb_handler_ = UsbHandler::Create();
if (!usb_handler_) {
return false;
}
socket_fd_.reset(socket(AF_UNIX, SOCK_STREAM, 0));
if (!socket_fd_) {
fprintf(stderr, "failed to create socket, err: %s\n", strerror(errno));
return false;
}
sockaddr_un addr = {};
addr.sun_family = AF_UNIX;
strncpy(addr.sun_path, XDC_SOCKET_PATH, sizeof(addr.sun_path));
// Check if another instance of the xdc server is running.
socket_lock_fd_.reset(open(XDC_LOCK_PATH, O_CREAT | O_RDONLY, 0666));
if (!socket_lock_fd_) {
return false;
}
int res = flock(socket_lock_fd_.get(), LOCK_EX | LOCK_NB);
if (res != 0) {
fprintf(stderr, "Failed to acquire socket lock, err: %s.\n", strerror(errno));
return false;
}
// Remove the socket file if it exists.
unlink(XDC_SOCKET_PATH);
if (bind(socket_fd_.get(), (sockaddr *)&addr, sizeof(addr)) != 0) {
fprintf(stderr, "Could not bind socket with pathname: %s, err: %s\n",
XDC_SOCKET_PATH, strerror(errno));
return false;
}
if (listen(socket_fd_.get(), MAX_PENDING_CONN_BACKLOG) < 0) {
fprintf(stderr, "Could not listen on socket fd: %d, err: %s\n",
socket_fd_.get(), strerror(errno));
return false;
}
return true;
}
void XdcServer::UpdateUsbHandlerFds() {
std::map<int, short> added_fds;
std::set<int> removed_fds;
usb_handler_->GetFdUpdates(added_fds, removed_fds);
for (auto iter : added_fds) {
int fd = iter.first;
short events = iter.second;
auto match = std::find_if(poll_fds_.begin(), poll_fds_.end(),
[&fd](auto& pollfd) { return pollfd.fd == fd; } );
if (match != poll_fds_.end()) {
fprintf(stderr, "already have usb handler fd: %d\n", fd);
continue;
}
poll_fds_.push_back(pollfd { fd, events, 0 });
printf("usb handler added fd: %d\n", fd);
}
for (auto fd : removed_fds) {
auto match = std::remove_if(poll_fds_.begin(), poll_fds_.end(),
[&fd](auto& pollfd) { return pollfd.fd == fd; } );
if (match == poll_fds_.end()) {
fprintf(stderr, "could not find usb handler fd: %d to delete\n", fd);
continue;
}
poll_fds_.erase(match, poll_fds_.end());
printf("usb handler removed fd: %d\n", fd);
}
}
void XdcServer::Run() {
printf("Waiting for connections on: %s\n", XDC_SOCKET_PATH);
// Listen for new client connections.
poll_fds_.push_back(pollfd{ socket_fd_.get(), POLLIN, 0 });
// Initialize to true as we want to get the initial usb handler fds.
bool update_usb_handler_fds = true;
for (;;) {
if (update_usb_handler_fds) {
UpdateUsbHandlerFds();
update_usb_handler_fds = false;
}
// poll expects an array of pollfds.
int num = poll(&poll_fds_[0], poll_fds_.size(), -1 /* timeout */);
if (num < 0) {
fprintf(stderr, "poll failed, err: %s\n", strerror(errno));
break;
}
// Not using an iterator for poll_fds_ as we might add/remove elements.
int num_sockets = poll_fds_.size();
int i = 0;
while (i < num_sockets) {
if (poll_fds_[i].fd == socket_fd_.get()) {
// A new client is trying to connect.
if (poll_fds_[i].revents & POLLIN) {
ClientConnect();
// Don't need to increment num_sockets as there aren't poll events for it yet.
}
} else if (usb_handler_->IsValidFd(poll_fds_[i].fd)) {
if (poll_fds_[i].revents) {
std::vector<std::unique_ptr<UsbHandler::Transfer>> completed_reads;
update_usb_handler_fds = usb_handler_->HandleEvents(completed_reads);
for (auto& usb_transfer : completed_reads) {
UsbReadComplete(std::move(usb_transfer));
}
}
} else {
auto iter = clients_.find(poll_fds_[i].fd);
if (iter == clients_.end()) {
fprintf(stderr, "poll returned an unknown fd: %d\n", poll_fds_[i].fd);
poll_fds_.erase(poll_fds_.begin() + i);
--num_sockets;
continue;
}
std::shared_ptr<Client> client = iter->second;
// Received client disconnect signal.
bool delete_client = poll_fds_[i].revents & POLLHUP;
// The client sent us some data.
if (!delete_client && (poll_fds_[i].revents & POLLIN)) {
if (!client->registered()) {
// Delete the client if registering the stream failed.
delete_client = !RegisterStream(client);
}
}
if (delete_client) {
poll_fds_.erase(poll_fds_.begin() + i);
--num_sockets;
printf("fd %d stream %u disconnected\n", client->fd(), client->stream_id());
clients_.erase(iter);
continue;
}
// TODO(jocelyndang): handle client reads / writes.
}
++i;
}
}
}
void XdcServer::ClientConnect() {
struct sockaddr_un addr;
socklen_t len = sizeof(addr);
// Most of the time we want non-blocking transfers, so we can handle other clients / libusb.
int client_fd = accept(socket_fd_.get(), (struct sockaddr *)&addr, &len);
if (client_fd < 0) {
fprintf(stderr, "Socket accept failed, err: %s\n", strerror(errno));
return;
}
if (clients_.count(client_fd) > 0) {
fprintf(stderr, "Client already connected, socket fd: %d\n", client_fd);
return;
}
int flags = fcntl(client_fd, F_GETFL, 0);
if (flags < 0) {
fprintf(stderr, "Could not get socket flags, err: %s\n", strerror(errno));
close(client_fd);
return;
}
int res = fcntl(client_fd, F_SETFL, flags | O_NONBLOCK);
if (res != 0) {
fprintf(stderr, "Could not set socket as nonblocking, err: %s\n", strerror(errno));
close(client_fd);
return;
}
printf("Client connected, socket fd: %d\n", client_fd);
clients_[client_fd] = std::make_shared<Client>(client_fd);
poll_fds_.push_back(pollfd{ client_fd, POLLIN, 0 });
}
bool XdcServer::RegisterStream(std::shared_ptr<Client> client) {
RegisterStreamRequest stream_id;
ssize_t n = recv(client->fd(), &stream_id, sizeof(stream_id), MSG_WAITALL);
if (n != sizeof(stream_id)) {
fprintf(stderr, "failed to read stream id from client fd: %d, got len: %ld, got err: %s\n",
client->fd(), n, strerror(errno));
return false;
}
// Client has disconnected. This will be handled in the main poll thread.
if (n == 0) {
return false;
}
RegisterStreamResponse resp = false;
if (stream_id == DEBUG_STREAM_ID_RESERVED) {
fprintf(stderr, "cannot register stream id %u\n", DEBUG_STREAM_ID_RESERVED);
} else if (GetClient(stream_id)) {
fprintf(stderr, "stream id %u was already registered\n", stream_id);
} else {
client->SetStreamId(stream_id);
printf("registered stream id %u\n", stream_id);
if (dev_stream_ids_.count(stream_id)) {
client->SetConnected(true);
}
resp = true;
}
ssize_t res = send(client->fd(), &resp, sizeof(resp), MSG_WAITALL);
if (res != sizeof(resp)) {
// Failed to send reply, disconnect the client.
return false;
}
return resp;
}
std::shared_ptr<Client> XdcServer::GetClient(uint32_t stream_id) {
auto is_client = [stream_id](auto& pair) -> bool {
return pair.second->stream_id() == stream_id;
};
auto iter = std::find_if(clients_.begin(), clients_.end(), is_client);
return iter == clients_.end() ? nullptr : iter->second;
}
void XdcServer::UsbReadComplete(std::unique_ptr<UsbHandler::Transfer> transfer) {
auto requeue = fbl::MakeAutoCall([&]() { usb_handler_->RequeueRead(std::move(transfer)); });
bool is_new_packet;
uint32_t stream_id;
zx_status_t status = xdc_update_packet_state(&read_packet_state_, transfer->data(),
transfer->actual_length(), &is_new_packet);
if (status != ZX_OK) {
fprintf(stderr, "error processing transfer: %d, dropping read of size %d\n",
status, transfer->actual_length());
}
stream_id = read_packet_state_.header.stream_id;
if (is_new_packet && stream_id == XDC_MSG_STREAM) {
HandleCtrlMsg(transfer->data(), transfer->actual_length());
}
// TODO(jocelyndang): check if the completed transfer should be send to a registered client.
}
void XdcServer::HandleCtrlMsg(unsigned char* transfer_buf, int transfer_len) {
int data_len = transfer_len - (int)sizeof(xdc_packet_header_t);
if (data_len < (int)sizeof(xdc_msg_t)) {
fprintf(stderr, "malformed msg, got %d bytes, need %lu\n", data_len, sizeof(xdc_msg_t));
return;
}
xdc_msg_t* msg = reinterpret_cast<xdc_msg_t*>(transfer_buf + sizeof(xdc_packet_header_t));
switch (msg->opcode) {
case XDC_NOTIFY_STREAM_STATE: {
uint32_t stream_id = msg->notify_stream_state.stream_id;
bool online = msg->notify_stream_state.online;
auto dev_stream = dev_stream_ids_.find(stream_id);
bool saved_online_state = dev_stream != dev_stream_ids_.end();
if (online == saved_online_state) {
fprintf(stderr, "tried to set stream %u to %s again\n",
stream_id, online ? "online" : "offline");
return;
}
if (online) {
dev_stream_ids_.insert(stream_id);
} else {
dev_stream_ids_.erase(dev_stream);
}
printf("xdc device stream id %u is now %s\n", stream_id, online ? "online" : "offline");
// Update the host client's connected status.
auto client = GetClient(stream_id);
if (!client) {
break;
}
client->SetConnected(online);
break;
}
default:
fprintf(stderr, "unknown msg opcode: %u\n", msg->opcode);
}
}
} // namespace xdc
int main(int argc, char** argv) {
printf("Starting XHCI Debug Capability server...\n");
std::unique_ptr<xdc::XdcServer> xdc_server = xdc::XdcServer::Create();
if (!xdc_server) {
return -1;
}
xdc_server->Run();
return 0;
}