d7e552a169
Issue #1987
638 lines
17 KiB
C++
638 lines
17 KiB
C++
/*
|
|
* \brief Genode backend for libusb
|
|
* \author Christian Prochaska
|
|
* \date 2016-09-19
|
|
*/
|
|
|
|
/*
|
|
* Copyright (C) 2016-2017 Genode Labs GmbH
|
|
*
|
|
* This file is part of the Genode OS framework, which is distributed
|
|
* under the terms of the GNU Affero General Public License version 3.
|
|
*/
|
|
|
|
#include <base/log.h>
|
|
#include <base/allocator_avl.h>
|
|
#include <base/signal.h>
|
|
#include <usb/usb.h>
|
|
#include <usb_session/connection.h>
|
|
|
|
#include <time.h>
|
|
#include <libc/allocator.h>
|
|
#include <libc-plugin/plugin.h>
|
|
|
|
#include "libusbi.h"
|
|
|
|
static Genode::Env *_env_ptr = nullptr;
|
|
|
|
static Genode::Env &genode_env()
|
|
{
|
|
if (_env_ptr)
|
|
return *_env_ptr;
|
|
|
|
Genode::error("libusb: missing libc plugin initialization");
|
|
abort();
|
|
}
|
|
|
|
|
|
/*
|
|
* Entrypoint for handling 'ack avail' signals from the USB driver.
|
|
*
|
|
* The entrypoint is needed because the main thread of an application
|
|
* using libusb might be blocking on a pthread locking function, which
|
|
* currently do not dispatch signals while blocking.
|
|
*/
|
|
static Genode::Entrypoint &ep()
|
|
{
|
|
static Genode::Entrypoint instance(genode_env(),
|
|
2*1024*sizeof(Genode::addr_t),
|
|
"usb_ack_ep",
|
|
Genode::Affinity::Location());
|
|
return instance;
|
|
}
|
|
|
|
|
|
static Libc::Allocator libc_alloc { };
|
|
|
|
|
|
struct Completion : Usb::Completion
|
|
{
|
|
struct usbi_transfer *itransfer;
|
|
|
|
Completion(struct usbi_transfer *itransfer)
|
|
: itransfer(itransfer) { }
|
|
|
|
void complete(Usb::Packet_descriptor &p) override { }
|
|
};
|
|
|
|
|
|
struct Usb_device
|
|
{
|
|
private:
|
|
|
|
Genode::Allocator_avl _alloc { &libc_alloc };
|
|
|
|
Genode::Io_signal_handler<Usb_device> _state_changed_handler {
|
|
genode_env().ep(), *this, &Usb_device::_handle_state_changed };
|
|
|
|
void _handle_state_changed()
|
|
{
|
|
/*
|
|
* The handler is installed only to receive state-change signals
|
|
* from the USB connection using the 'Usb_device' constructor.
|
|
*/
|
|
}
|
|
|
|
Genode::Io_signal_handler<Usb_device> _ack_avail_handler {
|
|
ep(), *this, &Usb_device::_handle_ack_avail };
|
|
|
|
void _handle_ack_avail()
|
|
{
|
|
while (usb_connection.source()->ack_avail()) {
|
|
|
|
Usb::Packet_descriptor p =
|
|
usb_connection.source()->get_acked_packet();
|
|
|
|
Completion *completion = static_cast<Completion*>(p.completion);
|
|
struct usbi_transfer *itransfer = completion->itransfer;
|
|
destroy(libc_alloc, completion);
|
|
|
|
if (!p.succeded) {
|
|
Genode::error("USB transfer failed");
|
|
itransfer->transferred = 0;
|
|
usb_connection.source()->release_packet(p);
|
|
usbi_signal_transfer_completion(itransfer);
|
|
continue;
|
|
}
|
|
|
|
char *packet_content = usb_connection.source()->packet_content(p);
|
|
|
|
struct libusb_transfer *transfer =
|
|
USBI_TRANSFER_TO_LIBUSB_TRANSFER(itransfer);
|
|
|
|
switch (transfer->type) {
|
|
|
|
case LIBUSB_TRANSFER_TYPE_CONTROL: {
|
|
|
|
itransfer->transferred = p.control.actual_size;
|
|
|
|
struct libusb_control_setup *setup =
|
|
(struct libusb_control_setup*)transfer->buffer;
|
|
|
|
if ((setup->bmRequestType & LIBUSB_ENDPOINT_DIR_MASK) ==
|
|
LIBUSB_ENDPOINT_IN) {
|
|
Genode::memcpy(transfer->buffer + LIBUSB_CONTROL_SETUP_SIZE,
|
|
packet_content, p.control.actual_size);
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
case LIBUSB_TRANSFER_TYPE_BULK:
|
|
case LIBUSB_TRANSFER_TYPE_BULK_STREAM:
|
|
case LIBUSB_TRANSFER_TYPE_INTERRUPT: {
|
|
|
|
itransfer->transferred = p.transfer.actual_size;
|
|
|
|
if (IS_XFERIN(transfer))
|
|
Genode::memcpy(transfer->buffer, packet_content,
|
|
p.transfer.actual_size);
|
|
|
|
break;
|
|
}
|
|
|
|
default:
|
|
Genode::error(__PRETTY_FUNCTION__,
|
|
": unsupported transfer type");
|
|
usb_connection.source()->release_packet(p);
|
|
continue;
|
|
}
|
|
|
|
usb_connection.source()->release_packet(p);
|
|
|
|
usbi_signal_transfer_completion(itransfer);
|
|
}
|
|
}
|
|
|
|
public:
|
|
|
|
Usb::Connection usb_connection { genode_env(),
|
|
&_alloc,
|
|
"usb_device",
|
|
512*1024,
|
|
_state_changed_handler };
|
|
|
|
Usb::Device_descriptor device_descriptor;
|
|
Usb::Config_descriptor config_descriptor;
|
|
char *raw_config_descriptor = nullptr;
|
|
|
|
Usb_device()
|
|
{
|
|
while (!usb_connection.plugged()) {
|
|
Genode::log("libusb: waiting until device is plugged...");
|
|
genode_env().ep().wait_and_dispatch_one_io_signal();
|
|
Genode::log("libusb: device is plugged");
|
|
}
|
|
|
|
usb_connection.config_descriptor(&device_descriptor, &config_descriptor);
|
|
|
|
raw_config_descriptor = (char*)malloc(config_descriptor.total_length);
|
|
|
|
Usb::Packet_descriptor p =
|
|
usb_connection.source()->alloc_packet(config_descriptor.total_length);
|
|
|
|
p.type = Usb::Packet_descriptor::CTRL;
|
|
p.control.request = LIBUSB_REQUEST_GET_DESCRIPTOR;
|
|
p.control.request_type = LIBUSB_ENDPOINT_IN;
|
|
p.control.value = (LIBUSB_DT_CONFIG << 8) | 0;
|
|
p.control.index = 0;
|
|
|
|
usb_connection.source()->submit_packet(p);
|
|
|
|
p = usb_connection.source()->get_acked_packet();
|
|
|
|
if (!p.succeded)
|
|
Genode::error(__PRETTY_FUNCTION__,
|
|
": could not read raw configuration descriptor");
|
|
|
|
if (p.control.actual_size != config_descriptor.total_length)
|
|
Genode::error(__PRETTY_FUNCTION__,
|
|
": received configuration descriptor of unexpected size");
|
|
|
|
char *packet_content = usb_connection.source()->packet_content(p);
|
|
Genode::memcpy(raw_config_descriptor, packet_content,
|
|
config_descriptor.total_length);
|
|
|
|
usb_connection.tx_channel()->sigh_ack_avail(_ack_avail_handler);
|
|
}
|
|
|
|
~Usb_device()
|
|
{
|
|
free(raw_config_descriptor);
|
|
}
|
|
};
|
|
|
|
|
|
static int genode_init(struct libusb_context* ctx)
|
|
{
|
|
return LIBUSB_SUCCESS;
|
|
}
|
|
|
|
|
|
static void genode_exit(void) { }
|
|
|
|
|
|
int genode_get_device_list(struct libusb_context *ctx,
|
|
struct discovered_devs **discdevs)
|
|
{
|
|
unsigned long session_id;
|
|
struct libusb_device *dev;
|
|
|
|
uint8_t busnum = 1;
|
|
uint8_t devaddr = 1;
|
|
|
|
session_id = busnum << 8 | devaddr;
|
|
usbi_dbg("busnum %d devaddr %d session_id %ld", busnum, devaddr,
|
|
session_id);
|
|
|
|
dev = usbi_get_device_by_session_id(ctx, session_id);
|
|
|
|
if (!dev) {
|
|
|
|
usbi_dbg("allocating new device for %d/%d (session %ld)",
|
|
busnum, devaddr, session_id);
|
|
dev = usbi_alloc_device(ctx, session_id);
|
|
if (!dev)
|
|
return LIBUSB_ERROR_NO_MEM;
|
|
|
|
/* initialize device structure */
|
|
dev->bus_number = busnum;
|
|
dev->device_address = devaddr;
|
|
|
|
/* FIXME: find place to free the allocated memory */
|
|
Usb_device *usb_device = new (libc_alloc) Usb_device;
|
|
*(Usb_device**)dev->os_priv = usb_device;
|
|
|
|
switch (usb_device->device_descriptor.speed) {
|
|
case Usb::Device::SPEED_LOW:
|
|
dev->speed = LIBUSB_SPEED_LOW;
|
|
break;
|
|
case Usb::Device::SPEED_FULL:
|
|
dev->speed = LIBUSB_SPEED_FULL;
|
|
break;
|
|
case Usb::Device::SPEED_HIGH:
|
|
dev->speed = LIBUSB_SPEED_HIGH;
|
|
break;
|
|
case Usb::Device::SPEED_SUPER:
|
|
dev->speed = LIBUSB_SPEED_SUPER;
|
|
break;
|
|
default:
|
|
Genode::warning(__PRETTY_FUNCTION__, ": unknown device speed");
|
|
dev->speed = LIBUSB_SPEED_UNKNOWN;
|
|
}
|
|
|
|
int result = usbi_sanitize_device(dev);
|
|
if (result < 0) {
|
|
libusb_unref_device(dev);
|
|
return result;
|
|
}
|
|
|
|
} else {
|
|
usbi_dbg("session_id %ld already exists", session_id);
|
|
}
|
|
|
|
if (discovered_devs_append(*discdevs, dev) == NULL) {
|
|
libusb_unref_device(dev);
|
|
return LIBUSB_ERROR_NO_MEM;
|
|
}
|
|
|
|
libusb_unref_device(dev);
|
|
|
|
return LIBUSB_SUCCESS;
|
|
}
|
|
|
|
|
|
static int genode_open(struct libusb_device_handle *dev_handle)
|
|
{
|
|
return LIBUSB_SUCCESS;
|
|
}
|
|
|
|
|
|
static void genode_close(struct libusb_device_handle *dev_handle)
|
|
{
|
|
}
|
|
|
|
|
|
static int genode_get_device_descriptor(struct libusb_device *device,
|
|
unsigned char* buffer,
|
|
int *host_endian)
|
|
{
|
|
Usb_device *usb_device = *(Usb_device**)device->os_priv;
|
|
|
|
Genode::memcpy(buffer, &usb_device->device_descriptor,
|
|
sizeof(libusb_device_descriptor));
|
|
|
|
*host_endian = 0;
|
|
|
|
return LIBUSB_SUCCESS;
|
|
}
|
|
|
|
|
|
static int genode_get_config_descriptor(struct libusb_device *device,
|
|
uint8_t config_index,
|
|
unsigned char *buffer,
|
|
size_t len,
|
|
int *host_endian)
|
|
{
|
|
if (config_index != 0) {
|
|
Genode::error(__PRETTY_FUNCTION__,
|
|
": only the first configuration is supported");
|
|
return LIBUSB_ERROR_NOT_SUPPORTED;
|
|
}
|
|
|
|
Usb_device *usb_device = *(Usb_device**)device->os_priv;
|
|
|
|
Genode::memcpy(buffer, usb_device->raw_config_descriptor, len);
|
|
|
|
*host_endian = 0;
|
|
|
|
return len;
|
|
}
|
|
|
|
|
|
static int genode_get_active_config_descriptor(struct libusb_device *device,
|
|
unsigned char *buffer,
|
|
size_t len,
|
|
int *host_endian)
|
|
{
|
|
/* only configuration 0 is currently supported */
|
|
return genode_get_config_descriptor(device, 0, buffer, len, host_endian);
|
|
}
|
|
|
|
|
|
static int genode_set_configuration(struct libusb_device_handle *dev_handle,
|
|
int config)
|
|
{
|
|
Genode::error(__PRETTY_FUNCTION__,
|
|
": not implemented (return address: ",
|
|
Genode::Hex((Genode::addr_t)__builtin_return_address(0)),
|
|
") \n");
|
|
return LIBUSB_ERROR_NOT_SUPPORTED;
|
|
}
|
|
|
|
|
|
static int genode_claim_interface(struct libusb_device_handle *dev_handle,
|
|
int interface_number)
|
|
{
|
|
Usb_device *usb_device = *(Usb_device**)dev_handle->dev->os_priv;
|
|
|
|
try {
|
|
usb_device->usb_connection.claim_interface(interface_number);
|
|
} catch (Usb::Session::Interface_not_found) {
|
|
Genode::error(__PRETTY_FUNCTION__, ": interface not found");
|
|
return LIBUSB_ERROR_NOT_FOUND;
|
|
} catch (Usb::Session::Interface_already_claimed) {
|
|
Genode::error(__PRETTY_FUNCTION__, ": interface already claimed");
|
|
return LIBUSB_ERROR_BUSY;
|
|
} catch (...) {
|
|
Genode::error(__PRETTY_FUNCTION__, ": unknown exception");
|
|
return LIBUSB_ERROR_OTHER;
|
|
}
|
|
|
|
return LIBUSB_SUCCESS;
|
|
}
|
|
|
|
|
|
static int genode_release_interface(struct libusb_device_handle *dev_handle,
|
|
int interface_number)
|
|
{
|
|
Usb_device *usb_device = *(Usb_device**)dev_handle->dev->os_priv;
|
|
|
|
try {
|
|
usb_device->usb_connection.release_interface(interface_number);
|
|
} catch (Usb::Session::Interface_not_found) {
|
|
Genode::error(__PRETTY_FUNCTION__, ": interface not found");
|
|
return LIBUSB_ERROR_NOT_FOUND;
|
|
} catch (...) {
|
|
Genode::error(__PRETTY_FUNCTION__, ": unknown exception");
|
|
return LIBUSB_ERROR_OTHER;
|
|
}
|
|
|
|
return LIBUSB_SUCCESS;
|
|
}
|
|
|
|
|
|
static int genode_set_interface_altsetting(struct libusb_device_handle* dev_handle,
|
|
int interface_number,
|
|
int altsetting)
|
|
{
|
|
Genode::error(__PRETTY_FUNCTION__,
|
|
": not implemented (return address: ",
|
|
Genode::Hex((Genode::addr_t)__builtin_return_address(0)),
|
|
") \n");
|
|
return LIBUSB_ERROR_NOT_SUPPORTED;
|
|
}
|
|
|
|
|
|
static int genode_submit_transfer(struct usbi_transfer * itransfer)
|
|
{
|
|
struct libusb_transfer *transfer =
|
|
USBI_TRANSFER_TO_LIBUSB_TRANSFER(itransfer);
|
|
|
|
Usb_device *usb_device = *(Usb_device**)transfer->dev_handle->dev->os_priv;
|
|
|
|
switch (transfer->type) {
|
|
|
|
case LIBUSB_TRANSFER_TYPE_CONTROL: {
|
|
|
|
struct libusb_control_setup *setup =
|
|
(struct libusb_control_setup*)transfer->buffer;
|
|
|
|
Usb::Packet_descriptor p;
|
|
|
|
try {
|
|
p = usb_device->usb_connection.source()->alloc_packet(transfer->length);
|
|
} catch (Usb::Session::Tx::Source::Packet_alloc_failed) {
|
|
Genode::error(__PRETTY_FUNCTION__, ": packet allocation failed");
|
|
return LIBUSB_ERROR_BUSY;
|
|
}
|
|
|
|
p.completion = new (libc_alloc) Completion(itransfer);
|
|
|
|
p.type = Usb::Packet_descriptor::CTRL;
|
|
p.control.request = setup->bRequest;
|
|
p.control.request_type = setup->bmRequestType;
|
|
p.control.value = setup->wValue;
|
|
p.control.index = setup->wIndex;
|
|
p.control.timeout = transfer->timeout;
|
|
|
|
if ((setup->bmRequestType & LIBUSB_ENDPOINT_DIR_MASK) ==
|
|
LIBUSB_ENDPOINT_OUT) {
|
|
|
|
char *packet_content =
|
|
usb_device->usb_connection.source()->packet_content(p);
|
|
|
|
Genode::memcpy(packet_content,
|
|
transfer->buffer + LIBUSB_CONTROL_SETUP_SIZE,
|
|
setup->wLength);
|
|
}
|
|
|
|
try {
|
|
usb_device->usb_connection.source()->submit_packet(p);
|
|
} catch (...) {
|
|
Genode::error(__PRETTY_FUNCTION__,
|
|
": could not submit packet");
|
|
}
|
|
|
|
return LIBUSB_SUCCESS;
|
|
}
|
|
|
|
case LIBUSB_TRANSFER_TYPE_BULK:
|
|
case LIBUSB_TRANSFER_TYPE_BULK_STREAM:
|
|
case LIBUSB_TRANSFER_TYPE_INTERRUPT: {
|
|
|
|
if (IS_XFEROUT(transfer) &&
|
|
transfer->flags & LIBUSB_TRANSFER_ADD_ZERO_PACKET) {
|
|
Genode::error(__PRETTY_FUNCTION__,
|
|
": zero packet not supported");
|
|
return LIBUSB_ERROR_NOT_SUPPORTED;
|
|
}
|
|
|
|
Usb::Packet_descriptor p;
|
|
|
|
try {
|
|
p = usb_device->usb_connection.source()->alloc_packet(transfer->length);
|
|
} catch (Usb::Session::Tx::Source::Packet_alloc_failed) {
|
|
Genode::error(__PRETTY_FUNCTION__,
|
|
": packet allocation failed");
|
|
return LIBUSB_ERROR_BUSY;
|
|
}
|
|
|
|
if (transfer->type == LIBUSB_TRANSFER_TYPE_INTERRUPT) {
|
|
p.type = Usb::Packet_descriptor::IRQ;
|
|
p.transfer.polling_interval =
|
|
Usb::Packet_descriptor::DEFAULT_POLLING_INTERVAL;
|
|
} else
|
|
p.type = Usb::Packet_descriptor::BULK;
|
|
|
|
p.completion = new (libc_alloc) Completion(itransfer);
|
|
p.transfer.ep = transfer->endpoint;
|
|
|
|
if (IS_XFEROUT(transfer)) {
|
|
char *packet_content =
|
|
usb_device->usb_connection.source()->packet_content(p);
|
|
Genode::memcpy(packet_content, transfer->buffer,
|
|
transfer->length);
|
|
}
|
|
|
|
try {
|
|
usb_device->usb_connection.source()->submit_packet(p);
|
|
} catch (...) {
|
|
Genode::error(__PRETTY_FUNCTION__,
|
|
": could not submit packet");
|
|
}
|
|
|
|
return LIBUSB_SUCCESS;
|
|
}
|
|
|
|
case LIBUSB_TRANSFER_TYPE_ISOCHRONOUS:
|
|
Genode::error(__PRETTY_FUNCTION__,
|
|
": isochronous transfer - not implemented");
|
|
return LIBUSB_ERROR_NOT_SUPPORTED;
|
|
|
|
default:
|
|
usbi_err(TRANSFER_CTX(transfer),
|
|
"unknown endpoint type %d", transfer->type);
|
|
return LIBUSB_ERROR_INVALID_PARAM;
|
|
}
|
|
}
|
|
|
|
|
|
static int genode_cancel_transfer(struct usbi_transfer * itransfer)
|
|
{
|
|
return LIBUSB_ERROR_NOT_SUPPORTED;
|
|
}
|
|
|
|
|
|
static void genode_clear_transfer_priv(struct usbi_transfer * itransfer) { }
|
|
|
|
|
|
static int genode_handle_transfer_completion(struct usbi_transfer * itransfer)
|
|
{
|
|
return usbi_handle_transfer_completion(itransfer,
|
|
LIBUSB_TRANSFER_COMPLETED);
|
|
}
|
|
|
|
|
|
static int genode_clock_gettime(int clkid, struct timespec *tp)
|
|
{
|
|
switch (clkid) {
|
|
case USBI_CLOCK_MONOTONIC:
|
|
return clock_gettime(CLOCK_MONOTONIC, tp);
|
|
case USBI_CLOCK_REALTIME:
|
|
return clock_gettime(CLOCK_REALTIME, tp);
|
|
default:
|
|
return LIBUSB_ERROR_INVALID_PARAM;
|
|
}
|
|
}
|
|
|
|
|
|
const struct usbi_os_backend genode_usb_raw_backend = {
|
|
/*.name =*/ "Genode",
|
|
/*.caps =*/ 0,
|
|
/*.init =*/ genode_init,
|
|
/*.exit =*/ genode_exit,
|
|
/*.get_device_list =*/ genode_get_device_list,
|
|
/*.hotplug_poll =*/ NULL,
|
|
/*.open =*/ genode_open,
|
|
/*.close =*/ genode_close,
|
|
/*.get_device_descriptor =*/ genode_get_device_descriptor,
|
|
/*.get_active_config_descriptor =*/ genode_get_active_config_descriptor,
|
|
/*.get_config_descriptor =*/ genode_get_config_descriptor,
|
|
/*.get_config_descriptor_by_value =*/ NULL,
|
|
|
|
|
|
/*.get_configuration =*/ NULL,
|
|
/*.set_configuration =*/ genode_set_configuration,
|
|
/*.claim_interface =*/ genode_claim_interface,
|
|
/*.release_interface =*/ genode_release_interface,
|
|
|
|
/*.set_interface_altsetting =*/ genode_set_interface_altsetting,
|
|
/*.clear_halt =*/ NULL,
|
|
/*.reset_device =*/ NULL,
|
|
|
|
/*.alloc_streams =*/ NULL,
|
|
/*.free_streams =*/ NULL,
|
|
|
|
/*.kernel_driver_active =*/ NULL,
|
|
/*.detach_kernel_driver =*/ NULL,
|
|
/*.attach_kernel_driver =*/ NULL,
|
|
|
|
/*.destroy_device =*/ NULL,
|
|
|
|
/*.submit_transfer =*/ genode_submit_transfer,
|
|
/*.cancel_transfer =*/ genode_cancel_transfer,
|
|
/*.clear_transfer_priv =*/ genode_clear_transfer_priv,
|
|
|
|
/*.handle_events =*/ NULL,
|
|
/*.handle_transfer_completion =*/ genode_handle_transfer_completion,
|
|
|
|
/*.clock_gettime =*/ genode_clock_gettime,
|
|
|
|
#ifdef USBI_TIMERFD_AVAILABLE
|
|
/*.get_timerfd_clockid =*/ NULL,
|
|
#endif
|
|
|
|
/*.device_priv_size =*/ sizeof(Usb_device*),
|
|
/*.device_handle_priv_size =*/ 0,
|
|
/*.transfer_priv_size =*/ 0,
|
|
};
|
|
|
|
|
|
/*****************
|
|
** Libc plugin **
|
|
*****************/
|
|
|
|
/*
|
|
* Even though libusb is not an actual libc plugin, it uses the plugin
|
|
* interface to get hold of the 'Genode::Env'.
|
|
*/
|
|
|
|
namespace {
|
|
|
|
struct Plugin : Libc::Plugin
|
|
{
|
|
enum { PLUGIN_PRIORITY = 1 };
|
|
|
|
Plugin() : Libc::Plugin(PLUGIN_PRIORITY) { }
|
|
|
|
void init(Genode::Env &env) override { _env_ptr = &env; }
|
|
};
|
|
}
|
|
|
|
void __attribute__((constructor)) init_libc_libusb(void)
|
|
{
|
|
static Plugin plugin;
|
|
}
|
|
|