libusb: handle 'ack_avail' signals in a VFS plugin

Issue #4392
This commit is contained in:
Christian Prochaska 2022-02-01 17:39:37 +01:00 committed by Norman Feske
parent cf0d007fd4
commit d4d875f2e6
12 changed files with 329 additions and 193 deletions

View File

@ -0,0 +1,7 @@
SRC_CC = vfs_libusb.cc
vpath %.cc $(REP_DIR)/src/lib/vfs/libusb
SHARED_LIB = yes
CC_CXX_WARN_STRICT =

View File

@ -11,5 +11,5 @@ _/src/rom_filter
_/src/report_rom
_/src/stdcxx
_/src/vfs
_/src/vfs_libusb
_/src/vfs_pipe

View File

@ -6,6 +6,7 @@
<rom label="init"/>
<rom label="jpeg.lib.so"/>
<rom label="vfs.lib.so"/>
<rom label="vfs_libusb.lib.so"/>
<rom label="vfs_pipe.lib.so"/>
<rom label="libc.lib.so"/>
<rom label="libm.lib.so"/>

View File

@ -75,7 +75,11 @@
<resource name="RAM" quantum="48M"/>
<config ld_verbose="no" enabled="yes" width="640" height="480" format="yuv" fps="15">
<vfs>
<dir name="dev"> <log/> <inline name="rtc">2018-01-01 00:01</inline> </dir>
<dir name="dev">
<log/>
<inline name="rtc">2018-01-01 00:01</inline>
<libusb/>
</dir>
<dir name="pipe"> <pipe/> </dir>
</vfs>
<libc stdout="/dev/log" stderr="/dev/log" rtc="/dev/rtc" pipe="/pipe"/>

View File

@ -0,0 +1,9 @@
MIRROR_FROM_REP_DIR := lib/mk/vfs_libusb.mk src/lib/vfs/libusb
content: $(MIRROR_FROM_REP_DIR) LICENSE
$(MIRROR_FROM_REP_DIR):
$(mirror_from_rep_dir)
LICENSE:
cp $(GENODE_DIR)/LICENSE $@

View File

@ -0,0 +1 @@
2022-02-01 a4dec9e3717d7b06e20b057905b2c5e8c0608d93

View File

@ -0,0 +1,5 @@
base
os
so
usb_session
vfs

View File

@ -26,6 +26,7 @@ set build_components {
core init timer
drivers/usb_host
test/smartcard
lib/vfs/libusb
lib/vfs/pipe
}
@ -91,7 +92,11 @@ append config {
<resource name="RAM" quantum="4M"/>
<config>
<vfs>
<dir name="dev"> <log/> <inline name="rtc">2018-01-01 00:01</inline> </dir>
<dir name="dev">
<log/>
<inline name="rtc">2018-01-01 00:01</inline>
<libusb/>
</dir>
<dir name="pipe"> <pipe/> </dir>
<dir name="ifd-ccid.bundle">
<dir name="Contents">
@ -116,7 +121,7 @@ set boot_modules {
core init timer test-smartcard
ld.lib.so pcsc-lite.lib.so ccid.lib.so libusb.lib.so
libc.lib.so vfs.lib.so libm.lib.so posix.lib.so
Info.plist vfs_pipe.lib.so
Info.plist vfs_libusb.lib.so vfs_pipe.lib.so
}
lappend boot_modules [usb_host_drv_binary]

View File

@ -11,3 +11,9 @@ the application using libusb:
</config>
See also the README file of the USB driver for additional policy attributes.
The Genode USB connection object resides in a VFS plugin named 'vfs_libusb',
which is necessary to handle the 'ack_avail' signal in the libc kernel context.
So, an application using libusb needs to have a '<dir name="dev"><libusb/></dev>'
node in its VFS configuration and the 'vfs_libusb.lib.so' file loadable at
runtime.

View File

@ -17,6 +17,7 @@
#include <usb/usb.h>
#include <usb_session/connection.h>
#include <fcntl.h>
#include <time.h>
#include <libc/allocator.h>
#include <libc-plugin/plugin.h>
@ -36,66 +37,9 @@ static Genode::Env &genode_env()
abort();
}
struct Usb_ep
{
Genode::Entrypoint _ep;
pthread_t _pthread;
void _handle_pthread_registration()
{
Genode::Thread *myself = Genode::Thread::myself();
if (!myself || Libc::pthread_create_from_thread(&_pthread, *myself, &myself)) {
Genode::error("cannot register thread for pthread");
return;
}
}
Genode::Io_signal_handler<Usb_ep> _pthread_reg_sigh {
_ep, *this, &Usb_ep::_handle_pthread_registration };
Usb_ep(Genode::Env &env, size_t stack_size, char const *name,
Genode::Affinity::Location location)
: _ep { env, stack_size, name, location }
{
Genode::Signal_transmitter(_pthread_reg_sigh).submit();
}
Genode::Entrypoint &ep() { return _ep; }
};
/*
* 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 Usb_ep instance(genode_env(),
2*1024*sizeof(Genode::addr_t),
"usb_ack_ep",
Genode::Affinity::Location());
return instance.ep();
}
static Libc::Allocator libc_alloc { };
/*
* Prevent modification of packet allocator
* by multiple threads.
*/
static Genode::Mutex &usb_packet_allocator_mutex()
{
static Genode::Mutex instance;
return instance;
}
struct Completion : Usb::Completion
{
struct usbi_transfer *itransfer;
@ -111,36 +55,95 @@ 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 };
unsigned _open { 0 };
void _handle_state_changed()
public:
Usb::Connection *usb_connection;
Usb::Device_descriptor device_descriptor;
Usb::Config_descriptor config_descriptor;
char *raw_config_descriptor = nullptr;
Usb_device(Usb::Connection *usb)
: usb_connection(usb)
{
/*
* The handler is installed only to receive state-change signals
* from the USB connection using the 'Usb_device' constructor.
*/
Genode::log("libusb: waiting until device is plugged...");
while (!usb_connection->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);
while (!usb_connection->source()->ack_avail())
genode_env().ep().wait_and_dispatch_one_io_signal();
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->source()->release_packet(p);
}
Genode::Io_signal_handler<Usb_device> _ack_avail_handler {
ep(), *this, &Usb_device::_handle_ack_avail };
~Usb_device()
{
free(raw_config_descriptor);
}
void _handle_ack_avail()
bool altsetting(int number, int alt_setting)
{
if (!usb_connection->source()->ready_to_submit())
return false;
Usb::Packet_descriptor p =
usb_connection->source()->alloc_packet(0);
p.type = Usb::Packet_descriptor::ALT_SETTING;
p.interface.number = number;
p.interface.alt_setting = alt_setting;
usb_connection->source()->submit_packet(p);
return true;
}
void close() { _open--; }
void open() { _open++; }
void handle_events()
{
struct libusb_context *ctx = nullptr;
while (usb_connection.source()->ack_avail()) {
while (usb_connection->source()->ack_avail()) {
Usb::Packet_descriptor p =
usb_connection.source()->get_acked_packet();
usb_connection->source()->get_acked_packet();
if (p.type == Usb::Packet_descriptor::ALT_SETTING) {
Genode::Mutex::Guard guard(usb_packet_allocator_mutex());
usb_connection.source()->release_packet(p);
usb_connection->source()->release_packet(p);
continue;
}
@ -153,8 +156,7 @@ struct Usb_device
destroy(libc_alloc, completion);
if (_open == 0) {
Genode::Mutex::Guard guard(usb_packet_allocator_mutex());
usb_connection.source()->release_packet(p);
usb_connection->source()->release_packet(p);
continue;
}
@ -162,15 +164,12 @@ struct Usb_device
if (!p.succeded)
Genode::error("USB transfer failed: ", (unsigned)p.type);
itransfer->transferred = 0;
{
Genode::Mutex::Guard guard(usb_packet_allocator_mutex());
usb_connection.source()->release_packet(p);
}
usb_connection->source()->release_packet(p);
usbi_signal_transfer_completion(itransfer);
continue;
}
char *packet_content = usb_connection.source()->packet_content(p);
char *packet_content = usb_connection->source()->packet_content(p);
struct libusb_transfer *transfer =
USBI_TRANSFER_TO_LIBUSB_TRANSFER(itransfer);
@ -239,15 +238,11 @@ struct Usb_device
default:
Genode::error(__PRETTY_FUNCTION__,
": unsupported transfer type");
Genode::Mutex::Guard guard(usb_packet_allocator_mutex());
usb_connection.source()->release_packet(p);
usb_connection->source()->release_packet(p);
continue;
}
{
Genode::Mutex::Guard guard(usb_packet_allocator_mutex());
usb_connection.source()->release_packet(p);
}
usb_connection->source()->release_packet(p);
usbi_signal_transfer_completion(itransfer);
}
@ -255,100 +250,38 @@ struct Usb_device
if (ctx != nullptr)
usbi_signal_event(ctx);
}
public:
Usb::Connection usb_connection { genode_env(),
&_alloc,
"usb_device",
1024*1024,
_state_changed_handler };
Usb::Device_descriptor device_descriptor;
Usb::Config_descriptor config_descriptor;
char *raw_config_descriptor = nullptr;
Usb_device()
{
Genode::log("libusb: waiting until device is plugged...");
while (!usb_connection.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);
while (!usb_connection.source()->ack_avail())
genode_env().ep().wait_and_dispatch_one_io_signal();
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.source()->release_packet(p);
usb_connection.tx_channel()->sigh_ack_avail(_ack_avail_handler);
}
~Usb_device()
{
free(raw_config_descriptor);
}
bool altsetting(int number, int alt_setting)
{
Genode::Mutex::Guard guard(usb_packet_allocator_mutex());
if (!usb_connection.source()->ready_to_submit())
return false;
Usb::Packet_descriptor p =
usb_connection.source()->alloc_packet(0);
p.type = Usb::Packet_descriptor::ALT_SETTING;
p.interface.number = number;
p.interface.alt_setting = alt_setting;
usb_connection.source()->submit_packet(p);
return true;
}
void close() { _open--; }
void open() { _open++; }
};
static Usb_device *device_instance;
static int vfs_libusb_fd { -1 };
static Usb::Connection *usb_connection { nullptr };
static Usb_device *device_instance { nullptr };
/*
* This function is called by the VFS plugin on 'open()'
* to pass a pointer to the USB connection.
*/
void libusb_genode_usb_connection(Usb::Connection *usb)
{
usb_connection = usb;
}
static int genode_init(struct libusb_context* ctx)
{
if (!device_instance) {
device_instance = new (libc_alloc) Usb_device;
vfs_libusb_fd = open("/dev/libusb", O_RDONLY);
if (vfs_libusb_fd == -1) {
Genode::error("could not open /dev/libusb");
return LIBUSB_ERROR_OTHER;
}
device_instance = new (libc_alloc) Usb_device(usb_connection);
} else {
Genode::error("tried to init genode usb context twice");
return LIBUSB_ERROR_OTHER;
}
return LIBUSB_SUCCESS;
}
@ -358,6 +291,9 @@ static void genode_exit(void)
if (device_instance) {
destroy(libc_alloc, device_instance);
device_instance = nullptr;
close(vfs_libusb_fd);
usb_connection = nullptr;
vfs_libusb_fd = -1;
}
}
@ -436,7 +372,8 @@ static int genode_open(struct libusb_device_handle *dev_handle)
if (device_instance)
device_instance->open();
return LIBUSB_SUCCESS;
return usbi_add_pollfd(HANDLE_CTX(dev_handle), vfs_libusb_fd,
POLLIN);
}
@ -444,6 +381,8 @@ static void genode_close(struct libusb_device_handle *dev_handle)
{
if (device_instance)
device_instance->close();
usbi_remove_pollfd(HANDLE_CTX(dev_handle), vfs_libusb_fd);
}
@ -511,7 +450,7 @@ static int genode_claim_interface(struct libusb_device_handle *dev_handle,
Usb_device *usb_device = *(Usb_device**)dev_handle->dev->os_priv;
try {
usb_device->usb_connection.claim_interface(interface_number);
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;
@ -533,7 +472,7 @@ static int genode_release_interface(struct libusb_device_handle *dev_handle,
Usb_device *usb_device = *(Usb_device**)dev_handle->dev->os_priv;
try {
usb_device->usb_connection.release_interface(interface_number);
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;
@ -564,7 +503,7 @@ static int genode_submit_transfer(struct usbi_transfer * itransfer)
Usb_device *usb_device = *(Usb_device**)transfer->dev_handle->dev->os_priv;
if (!usb_device->usb_connection.source()->ready_to_submit())
if (!usb_device->usb_connection->source()->ready_to_submit())
return LIBUSB_ERROR_BUSY;
switch (transfer->type) {
@ -577,8 +516,7 @@ static int genode_submit_transfer(struct usbi_transfer * itransfer)
Usb::Packet_descriptor p;
try {
Genode::Mutex::Guard guard(usb_packet_allocator_mutex());
p = usb_device->usb_connection.source()->alloc_packet(setup->wLength);
p = usb_device->usb_connection->source()->alloc_packet(setup->wLength);
} catch (Usb::Session::Tx::Source::Packet_alloc_failed) {
return LIBUSB_ERROR_BUSY;
}
@ -596,14 +534,14 @@ static int genode_submit_transfer(struct usbi_transfer * itransfer)
LIBUSB_ENDPOINT_OUT) {
char *packet_content =
usb_device->usb_connection.source()->packet_content(p);
usb_device->usb_connection->source()->packet_content(p);
Genode::memcpy(packet_content,
transfer->buffer + LIBUSB_CONTROL_SETUP_SIZE,
setup->wLength);
}
usb_device->usb_connection.source()->submit_packet(p);
usb_device->usb_connection->source()->submit_packet(p);
return LIBUSB_SUCCESS;
}
@ -622,8 +560,7 @@ static int genode_submit_transfer(struct usbi_transfer * itransfer)
Usb::Packet_descriptor p;
try {
Genode::Mutex::Guard guard(usb_packet_allocator_mutex());
p = usb_device->usb_connection.source()->alloc_packet(transfer->length);
p = usb_device->usb_connection->source()->alloc_packet(transfer->length);
} catch (Usb::Session::Tx::Source::Packet_alloc_failed) {
return LIBUSB_ERROR_BUSY;
}
@ -640,12 +577,12 @@ static int genode_submit_transfer(struct usbi_transfer * itransfer)
if (IS_XFEROUT(transfer)) {
char *packet_content =
usb_device->usb_connection.source()->packet_content(p);
usb_device->usb_connection->source()->packet_content(p);
Genode::memcpy(packet_content, transfer->buffer,
transfer->length);
}
usb_device->usb_connection.source()->submit_packet(p);
usb_device->usb_connection->source()->submit_packet(p);
return LIBUSB_SUCCESS;
}
@ -659,8 +596,7 @@ static int genode_submit_transfer(struct usbi_transfer * itransfer)
Usb::Packet_descriptor p;
try {
Genode::Mutex::Guard guard(usb_packet_allocator_mutex());
p = usb_device->usb_connection.source()->alloc_packet(total_length);
p = usb_device->usb_connection->source()->alloc_packet(total_length);
} catch (Usb::Session::Tx::Source::Packet_alloc_failed) {
return LIBUSB_ERROR_BUSY;
}
@ -679,12 +615,12 @@ static int genode_submit_transfer(struct usbi_transfer * itransfer)
if (IS_XFEROUT(transfer)) {
char *packet_content =
usb_device->usb_connection.source()->packet_content(p);
usb_device->usb_connection->source()->packet_content(p);
Genode::memcpy(packet_content, transfer->buffer,
transfer->length);
}
usb_device->usb_connection.source()->submit_packet(p);
usb_device->usb_connection->source()->submit_packet(p);
return LIBUSB_SUCCESS;
}
@ -706,6 +642,19 @@ static int genode_cancel_transfer(struct usbi_transfer * itransfer)
static void genode_clear_transfer_priv(struct usbi_transfer * itransfer) { }
static int genode_handle_events(struct libusb_context *,
struct pollfd *,
POLL_NFDS_TYPE, int)
{
if (device_instance) {
device_instance->handle_events();
return LIBUSB_SUCCESS;
}
return LIBUSB_ERROR_NO_DEVICE;
}
static int genode_handle_transfer_completion(struct usbi_transfer * itransfer)
{
enum libusb_transfer_status status = LIBUSB_TRANSFER_COMPLETED;
@ -768,7 +717,7 @@ const struct usbi_os_backend genode_usb_raw_backend = {
/*.cancel_transfer =*/ genode_cancel_transfer,
/*.clear_transfer_priv =*/ genode_clear_transfer_priv,
/*.handle_events =*/ NULL,
/*.handle_events =*/ genode_handle_events,
/*.handle_transfer_completion =*/ genode_handle_transfer_completion,
/*.clock_gettime =*/ genode_clock_gettime,
@ -808,3 +757,4 @@ void __attribute__((constructor)) init_libc_libusb(void)
{
static Plugin plugin;
}

View File

@ -0,0 +1,4 @@
TARGET = dummy-vfs_libusb
LIBS = vfs_libusb
CC_CXX_WARN_STRICT =

View File

@ -0,0 +1,144 @@
/*
* \brief libusb file system
* \author Christian Prochaska
* \date 2022-01-31
*/
/*
* Copyright (C) 2022 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.
*/
/* Genode includes */
#include <base/allocator_avl.h>
#include <usb_session/connection.h>
#include <util/xml_node.h>
#include <vfs/file_system_factory.h>
#include <vfs/single_file_system.h>
/*
* This function is implemented in the Genode backend
* of libusb.
*/
extern void libusb_genode_usb_connection(Usb::Connection *);
class Libusb_file_system : public Vfs::Single_file_system
{
private:
Vfs::Env &_env;
class Libusb_vfs_handle : public Single_vfs_handle
{
private:
Genode::Env &_env;
Genode::Allocator_avl _alloc_avl;
Usb::Connection _usb_connection;
Genode::Io_signal_handler<Libusb_vfs_handle> _state_changed_handler {
_env.ep(), *this, &Libusb_vfs_handle::_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<Libusb_vfs_handle> _ack_avail_handler {
_env.ep(), *this, &Libusb_vfs_handle::_handle_ack_avail };
void _handle_ack_avail()
{
io_progress_response();
}
public:
Libusb_vfs_handle(Directory_service &ds,
File_io_service &fs,
Genode::Allocator &alloc,
Genode::Env &env)
: Single_vfs_handle(ds, fs, alloc, 0),
_env(env), _alloc_avl(&alloc),
_usb_connection(_env, &_alloc_avl,
"usb_device",
1024*1024,
_state_changed_handler)
{
_usb_connection.tx_channel()->sigh_ack_avail(_ack_avail_handler);
libusb_genode_usb_connection(&_usb_connection);
}
bool read_ready() override
{
return _usb_connection.source()->ack_avail();
}
Read_result read(char *dst, Vfs::file_size count,
Vfs::file_size &out_count) override
{
return READ_ERR_IO;
}
Write_result write(char const *src, Vfs::file_size count,
Vfs::file_size &out_count) override
{
return WRITE_ERR_IO;
}
};
public:
Libusb_file_system(Vfs::Env &env,
Genode::Xml_node config)
:
Single_file_system(Vfs::Node_type::CONTINUOUS_FILE, name(),
Vfs::Node_rwx::ro(), config),
_env(env) { }
~Libusb_file_system() { }
static char const *name() { return "libusb"; }
char const *type() override { return "libusb"; }
/*********************************
** Directory service interface **
*********************************/
Open_result open(char const *path, unsigned,
Vfs::Vfs_handle **out_handle,
Genode::Allocator &alloc) override
{
if (!_single_file(path))
return OPEN_ERR_UNACCESSIBLE;
*out_handle = new (alloc)
Libusb_vfs_handle(*this, *this, alloc, _env.env());
return OPEN_OK;
}
};
struct Libusb_factory : Vfs::File_system_factory
{
Vfs::File_system *create(Vfs::Env &env, Genode::Xml_node node) override
{
return new (env.alloc()) Libusb_file_system(env, node);
}
};
extern "C" Vfs::File_system_factory *vfs_file_system_factory(void)
{
static Libusb_factory factory;
return &factory;
}