481 lines
13 KiB
C++
481 lines
13 KiB
C++
/*
|
|
* \brief USBProxyDevice implementation for Genode
|
|
* \author Christian Prochaska
|
|
* \date 2015-04-13
|
|
*/
|
|
|
|
/*
|
|
* Copyright (C) 2015 Genode Labs GmbH
|
|
*
|
|
* This file is distributed under the terms of the GNU General Public License
|
|
* version 2.
|
|
*/
|
|
|
|
#include <base/allocator_avl.h>
|
|
#include <base/printf.h>
|
|
#include <timer_session/connection.h>
|
|
#include <usb_session/connection.h>
|
|
#include <util/construct_at.h>
|
|
|
|
#include <VBox/vmm/pdm.h>
|
|
|
|
#include "USBProxyDevice.h"
|
|
|
|
namespace Usb_proxy_device_genode {
|
|
|
|
static constexpr bool debug = false;
|
|
|
|
struct State_change_signal_context : Genode::Signal_context { };
|
|
struct Ack_avail_signal_context : Genode::Signal_context { };
|
|
struct Timeout_signal_context : Genode::Signal_context { };
|
|
struct Wakeup_signal_context : Genode::Signal_context { };
|
|
|
|
class Data
|
|
{
|
|
private:
|
|
|
|
Genode::Allocator_avl _alloc;
|
|
|
|
public:
|
|
|
|
Usb::Connection usb_connection;
|
|
Timer::Connection timer;
|
|
|
|
State_change_signal_context state_change_signal_context;
|
|
Ack_avail_signal_context ack_avail_signal_context;
|
|
Timeout_signal_context timeout_signal_context;
|
|
Wakeup_signal_context wakeup_signal_context;
|
|
Genode::Signal_context_capability wakeup_signal_context_cap;
|
|
|
|
Genode::Signal_receiver signal_receiver;
|
|
|
|
Data(unsigned int vendor_id, unsigned int product_id)
|
|
: _alloc(Genode::env()->heap()),
|
|
usb_connection(&_alloc)
|
|
{
|
|
/* wait until device and server are ready */
|
|
|
|
Genode::Signal_context_capability state_change_signal_context_cap =
|
|
signal_receiver.manage(&state_change_signal_context);
|
|
|
|
usb_connection.sigh_state_change(state_change_signal_context_cap);
|
|
|
|
signal_receiver.wait_for_signal();
|
|
|
|
/* register the other signal handlers */
|
|
|
|
Genode::Signal_context_capability ack_avail_signal_context_cap =
|
|
signal_receiver.manage(&ack_avail_signal_context);
|
|
usb_connection.tx_channel()->sigh_ack_avail(ack_avail_signal_context_cap);
|
|
|
|
Genode::Signal_context_capability timeout_signal_context_cap =
|
|
signal_receiver.manage(&timeout_signal_context);
|
|
timer.sigh(timeout_signal_context_cap);
|
|
|
|
wakeup_signal_context_cap = signal_receiver.manage(&wakeup_signal_context);
|
|
}
|
|
|
|
};
|
|
|
|
/* keep a reference to the VirtualBox URB in a packet descriptor */
|
|
struct Urb_preserve_completion : Usb::Completion
|
|
{
|
|
PVUSBURB pUrb;
|
|
|
|
void complete(Usb::Packet_descriptor &p) { }
|
|
|
|
Urb_preserve_completion(PVUSBURB pUrb) : pUrb(pUrb) { }
|
|
virtual ~Urb_preserve_completion() { }
|
|
};
|
|
|
|
static int open(PUSBPROXYDEV pProxyDev, const char *pszAddress, void *pvBackend);
|
|
static int init(PUSBPROXYDEV pProxyDev);
|
|
static void close(PUSBPROXYDEV pProxyDev);
|
|
static int reset(PUSBPROXYDEV pProxyDev, bool fResetOnLinux);
|
|
static int set_config(PUSBPROXYDEV pProxyDev, int iCfg);
|
|
static int claim_interface(PUSBPROXYDEV pProxyDev, int iIf);
|
|
static int release_interface(PUSBPROXYDEV pProxyDev, int iIf);
|
|
static int set_interface(PUSBPROXYDEV pProxyDev, int iIf, int iSetting);
|
|
static int clear_halted_endpoint(PUSBPROXYDEV pDev, unsigned int iEp);
|
|
static int urb_queue(PUSBPROXYDEV pProxyDev, PVUSBURB pUrb);
|
|
static int urb_cancel(PUSBPROXYDEV pProxyDev, PVUSBURB pUrb);
|
|
static PVUSBURB urb_reap(PUSBPROXYDEV pProxyDev, RTMSINTERVAL cMillies);
|
|
static int wakeup(PUSBPROXYDEV pProxyDev);
|
|
|
|
};
|
|
|
|
int Usb_proxy_device_genode::open(PUSBPROXYDEV pProxyDev, const char *pszAddress, void *pvBackend)
|
|
{
|
|
char vendor_id_hex_str[7] = "0x";
|
|
char product_id_hex_str[7] = "0x";
|
|
const char *separator = strchr(pszAddress, ':');
|
|
|
|
Genode::strncpy(&vendor_id_hex_str[2], pszAddress, 5);
|
|
Genode::strncpy(&product_id_hex_str[2], separator + 1, 5);
|
|
|
|
unsigned int vendor_id;
|
|
unsigned int product_id;
|
|
|
|
Genode::ascii_to(vendor_id_hex_str, vendor_id);
|
|
Genode::ascii_to(product_id_hex_str, product_id);
|
|
|
|
if (debug)
|
|
PDBG("vendor_id: %x, product_id: %x", vendor_id, product_id);
|
|
|
|
Usb_proxy_device_genode::Data *data =
|
|
USBPROXYDEV_2_DATA(pProxyDev, Usb_proxy_device_genode::Data*);
|
|
|
|
Genode::construct_at<Usb_proxy_device_genode::Data>(data, vendor_id, product_id);
|
|
|
|
return VINF_SUCCESS;
|
|
}
|
|
|
|
int Usb_proxy_device_genode::init(PUSBPROXYDEV pProxyDev)
|
|
{
|
|
if (debug)
|
|
PDBG("not implemented, returning VINF_SUCCESS anyway");
|
|
return VINF_SUCCESS;
|
|
}
|
|
|
|
void Usb_proxy_device_genode::close(PUSBPROXYDEV pProxyDev)
|
|
{
|
|
Usb_proxy_device_genode::Data *data =
|
|
USBPROXYDEV_2_DATA(pProxyDev, Usb_proxy_device_genode::Data*);
|
|
|
|
data->~Data();
|
|
|
|
if (debug)
|
|
PDBG("not implemented");
|
|
}
|
|
|
|
int Usb_proxy_device_genode::reset(PUSBPROXYDEV pProxyDev, bool fResetOnLinux)
|
|
{
|
|
if (debug)
|
|
PDBG("not implemented, returning VINF_SUCCESS anyway");
|
|
return VINF_SUCCESS;
|
|
}
|
|
|
|
int Usb_proxy_device_genode::set_config(PUSBPROXYDEV pProxyDev, int iCfg)
|
|
{
|
|
if (iCfg == 1) {
|
|
/* default configuration */
|
|
if (debug)
|
|
PDBG("not implemented, returning VINF_SUCCESS anyway");
|
|
return VINF_SUCCESS;
|
|
} else {
|
|
if (debug)
|
|
PDBG("not implemented and iCfg != 1");
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
int Usb_proxy_device_genode::claim_interface(PUSBPROXYDEV pProxyDev, int iIf)
|
|
{
|
|
if (debug)
|
|
PDBG("not implemented, returning VINF_SUCCESS anyway");
|
|
return VINF_SUCCESS;
|
|
}
|
|
|
|
int Usb_proxy_device_genode::release_interface(PUSBPROXYDEV pProxyDev, int iIf)
|
|
{
|
|
if (debug)
|
|
PDBG("not implemented");
|
|
return -1;
|
|
}
|
|
|
|
int Usb_proxy_device_genode::set_interface(PUSBPROXYDEV pProxyDev, int iIf, int iSetting)
|
|
{
|
|
if (debug)
|
|
PDBG("not implemented");
|
|
return -1;
|
|
}
|
|
|
|
int Usb_proxy_device_genode::clear_halted_endpoint(PUSBPROXYDEV pDev, unsigned int iEp)
|
|
{
|
|
if (debug)
|
|
PDBG("not implemented, returning VINF_SUCCESS anyway");
|
|
return VINF_SUCCESS;
|
|
}
|
|
|
|
int Usb_proxy_device_genode::urb_queue(PUSBPROXYDEV pProxyDev, PVUSBURB pUrb)
|
|
{
|
|
if (debug)
|
|
PDBG("EndPt = %u, enmType = %d, enmDir = %d, cbData = %u",
|
|
pUrb->EndPt, pUrb->enmType, pUrb->enmDir, pUrb->cbData);
|
|
|
|
static int count = 0;
|
|
|
|
const char *dir_str = (pUrb->enmDir == VUSBDIRECTION_OUT) ? "OUT" : "IN";
|
|
|
|
Usb_proxy_device_genode::Data *data =
|
|
USBPROXYDEV_2_DATA(pProxyDev, Usb_proxy_device_genode::Data*);
|
|
|
|
if (!data->usb_connection.source()->ready_to_submit()) {
|
|
PERR("%s: not ready to submit", __PRETTY_FUNCTION__);
|
|
return -1;
|
|
}
|
|
|
|
try {
|
|
|
|
if (pUrb->enmType == VUSBXFERTYPE_MSG) {
|
|
|
|
if (pUrb->cbData < sizeof(VUSBSETUP))
|
|
return VERR_BUFFER_UNDERFLOW;
|
|
|
|
PVUSBSETUP pSetup = (PVUSBSETUP)pUrb->abData;
|
|
|
|
if (debug)
|
|
PDBG("control %s transfer: request_type = 0x%x, request = 0x%x,"
|
|
"value = 0x%x, index = 0x%x, length = %u, cbData = %u",
|
|
dir_str, pSetup->bmRequestType, pSetup->bRequest,
|
|
pSetup->wValue, pSetup->wIndex, pSetup->wLength, pUrb->cbData);
|
|
|
|
Usb::Packet_descriptor p = data->usb_connection.source()->alloc_packet(pSetup->wLength);
|
|
|
|
p.type = Usb::Packet_descriptor::CTRL;
|
|
p.succeded = false;
|
|
p.control.request = pSetup->bRequest;
|
|
p.control.request_type = pSetup->bmRequestType;
|
|
p.control.value = pSetup->wValue;
|
|
p.control.index = pSetup->wIndex;
|
|
p.control.timeout = 1000;
|
|
p.completion = new Urb_preserve_completion(pUrb);
|
|
|
|
if ((pUrb->enmDir == VUSBDIRECTION_OUT) && (pSetup->wLength > 0)) {
|
|
char *packet_content = data->usb_connection.source()->packet_content(p);
|
|
Genode::memcpy(packet_content, (pSetup + 1), pSetup->wLength);
|
|
}
|
|
|
|
if (debug)
|
|
PDBG("submitting packet: %d", ++count);
|
|
|
|
data->usb_connection.source()->submit_packet(p);
|
|
|
|
return VINF_SUCCESS;
|
|
|
|
} else if ((pUrb->enmType == VUSBXFERTYPE_INTR) ||
|
|
(pUrb->enmType == VUSBXFERTYPE_BULK)) {
|
|
|
|
Usb::Packet_descriptor p = data->usb_connection.source()->alloc_packet(pUrb->cbData);
|
|
|
|
if (pUrb->enmType == VUSBXFERTYPE_INTR) {
|
|
if (debug)
|
|
PDBG("interrupt %s transfer", dir_str);
|
|
p.type = Usb::Packet_descriptor::IRQ;
|
|
} else {
|
|
if (debug)
|
|
PDBG("bulk %s transfer", dir_str);
|
|
p.type = Usb::Packet_descriptor::BULK;
|
|
}
|
|
|
|
p.succeded = false;
|
|
p.transfer.ep = pUrb->EndPt | (pUrb->enmDir == VUSBDIRECTION_IN ?
|
|
Usb::ENDPOINT_IN :
|
|
Usb::ENDPOINT_OUT);
|
|
p.transfer.timeout = 100;
|
|
p.completion = new Urb_preserve_completion(pUrb);
|
|
|
|
if ((pUrb->enmDir == VUSBDIRECTION_OUT) && (pUrb->cbData > 0)) {
|
|
char *packet_content = data->usb_connection.source()->packet_content(p);
|
|
Genode::memcpy(packet_content, pUrb->abData, pUrb->cbData);
|
|
}
|
|
|
|
if (debug)
|
|
PDBG("submitting packet: %d", ++count);
|
|
|
|
data->usb_connection.source()->submit_packet(p);
|
|
|
|
return VINF_SUCCESS;
|
|
|
|
} else
|
|
PERR("%s: unsupported transfer type %d", __PRETTY_FUNCTION__, (int)pUrb->enmType);
|
|
|
|
} catch (Usb::Session::Tx::Source::Packet_alloc_failed) {
|
|
|
|
if (debug)
|
|
PDBG("packet allocation failed");
|
|
|
|
return -1;
|
|
|
|
} catch (...) {
|
|
|
|
PWRN("%s: an unhandled exception occured", __PRETTY_FUNCTION__);
|
|
return -1;
|
|
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
|
|
int Usb_proxy_device_genode::urb_cancel(PUSBPROXYDEV pProxyDev, PVUSBURB pUrb)
|
|
{
|
|
if (debug)
|
|
PDBG("not implemented, returning VINF_SUCCESS anyway");
|
|
return VINF_SUCCESS;
|
|
}
|
|
|
|
PVUSBURB Usb_proxy_device_genode::urb_reap(PUSBPROXYDEV pProxyDev, RTMSINTERVAL cMillies)
|
|
{
|
|
if (debug)
|
|
PDBG("cMillies = %u", cMillies);
|
|
|
|
Usb_proxy_device_genode::Data *data =
|
|
USBPROXYDEV_2_DATA(pProxyDev, Usb_proxy_device_genode::Data*);
|
|
|
|
if (!data->usb_connection.source()->ack_avail()) {
|
|
|
|
unsigned long elapsed_ms_start = data->timer.elapsed_ms();
|
|
|
|
if (cMillies != RT_INDEFINITE_WAIT)
|
|
data->timer.trigger_once(cMillies * 1000);
|
|
|
|
for (;;) {
|
|
|
|
if (debug)
|
|
PDBG("waiting for signal");
|
|
|
|
Genode::Signal signal = data->signal_receiver.wait_for_signal();
|
|
|
|
Genode::Signal_context *context = signal.context();
|
|
|
|
if (dynamic_cast<Timeout_signal_context*>(context)) {
|
|
|
|
if (cMillies == RT_INDEFINITE_WAIT) {
|
|
/* received an old signal */
|
|
if (debug)
|
|
PDBG("old timeout signal received");
|
|
continue;
|
|
}
|
|
|
|
unsigned long elapsed_ms_now = data->timer.elapsed_ms();
|
|
|
|
if (elapsed_ms_now - elapsed_ms_start < cMillies) {
|
|
/* received an old signal */
|
|
if (debug)
|
|
PDBG("old timeout signal received");
|
|
continue;
|
|
}
|
|
|
|
if (debug)
|
|
PDBG("timeout signal received");
|
|
|
|
return 0;
|
|
|
|
} else if (dynamic_cast<Wakeup_signal_context*>(context)) {
|
|
|
|
if (debug)
|
|
PDBG("wakeup signal received");
|
|
|
|
return 0;
|
|
|
|
} else if (dynamic_cast<Ack_avail_signal_context*>(context)) {
|
|
|
|
if (debug)
|
|
PDBG("ack avail signal received");
|
|
|
|
if (data->usb_connection.source()->ack_avail())
|
|
break;
|
|
else
|
|
continue;
|
|
|
|
} else if (dynamic_cast<State_change_signal_context*>(context)) {
|
|
|
|
if (debug)
|
|
PDBG("state change signal received");
|
|
|
|
return 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
Usb::Packet_descriptor p = data->usb_connection.source()->get_acked_packet();
|
|
|
|
static int count = 0;
|
|
|
|
if (debug)
|
|
PDBG("got packet: %d, succeded = %d", ++count, p.succeded);
|
|
|
|
Urb_preserve_completion *completion = static_cast<Urb_preserve_completion*>(p.completion);
|
|
PVUSBURB pUrb = completion->pUrb;
|
|
delete completion;
|
|
|
|
if (!p.succeded) {
|
|
pUrb->enmStatus = VUSBSTATUS_DNR;
|
|
return pUrb;
|
|
}
|
|
|
|
if (pUrb->enmType == VUSBXFERTYPE_MSG) {
|
|
|
|
PVUSBSETUP pSetup = (PVUSBSETUP)pUrb->abData;
|
|
|
|
pSetup->wLength = p.control.actual_size;
|
|
pUrb->cbData = sizeof(VUSBSETUP) + pSetup->wLength;
|
|
|
|
if (debug)
|
|
PDBG("pSetup->wLength = %u, pUrb->cbData = %u",
|
|
pSetup->wLength, pUrb->cbData);
|
|
|
|
if ((pUrb->enmDir == VUSBDIRECTION_IN) && (pSetup->wLength > 0)) {
|
|
char *packet_content = data->usb_connection.source()->packet_content(p);
|
|
Genode::memcpy((pSetup + 1), packet_content, pSetup->wLength);
|
|
}
|
|
|
|
data->usb_connection.source()->release_packet(p);
|
|
|
|
pUrb->enmStatus = VUSBSTATUS_OK;
|
|
|
|
} else if ((pUrb->enmType == VUSBXFERTYPE_INTR) ||
|
|
(pUrb->enmType == VUSBXFERTYPE_BULK)) {
|
|
|
|
pUrb->cbData = p.transfer.actual_size;
|
|
|
|
if ((pUrb->enmDir == VUSBDIRECTION_IN) && (pUrb->cbData > 0)) {
|
|
char *packet_content = data->usb_connection.source()->packet_content(p);
|
|
Genode::memcpy(pUrb->abData, packet_content, pUrb->cbData);
|
|
}
|
|
|
|
data->usb_connection.source()->release_packet(p);
|
|
|
|
pUrb->enmStatus = VUSBSTATUS_OK;
|
|
|
|
} else {
|
|
PERR("%s: unsupported transfer type %d", __PRETTY_FUNCTION__, (int)pUrb->enmType);
|
|
}
|
|
|
|
return pUrb;
|
|
|
|
}
|
|
|
|
int Usb_proxy_device_genode::wakeup(PUSBPROXYDEV pProxyDev)
|
|
{
|
|
if (debug)
|
|
PDBG("wakeup()");
|
|
|
|
Usb_proxy_device_genode::Data *data =
|
|
USBPROXYDEV_2_DATA(pProxyDev, Usb_proxy_device_genode::Data*);
|
|
|
|
Genode::Signal_transmitter(data->wakeup_signal_context_cap).submit();
|
|
|
|
return VINF_SUCCESS;
|
|
}
|
|
|
|
extern const USBPROXYBACK g_USBProxyDeviceHost =
|
|
{
|
|
"host",
|
|
sizeof(Usb_proxy_device_genode::Data),
|
|
Usb_proxy_device_genode::open,
|
|
Usb_proxy_device_genode::init,
|
|
Usb_proxy_device_genode::close,
|
|
Usb_proxy_device_genode::reset,
|
|
Usb_proxy_device_genode::set_config,
|
|
Usb_proxy_device_genode::claim_interface,
|
|
Usb_proxy_device_genode::release_interface,
|
|
Usb_proxy_device_genode::set_interface,
|
|
Usb_proxy_device_genode::clear_halted_endpoint,
|
|
Usb_proxy_device_genode::urb_queue,
|
|
Usb_proxy_device_genode::urb_cancel,
|
|
Usb_proxy_device_genode::urb_reap,
|
|
Usb_proxy_device_genode::wakeup,
|
|
0
|
|
};
|