mirror of
https://github.com/genodelabs/genode.git
synced 2024-12-23 15:32:25 +00:00
0c2bdf9edd
Issue #1329
557 lines
12 KiB
C++
557 lines
12 KiB
C++
/**
|
|
* \brief USB session wrapper
|
|
* \author Sebastian Sumpf
|
|
* \date 2014-12-08
|
|
*/
|
|
|
|
/*
|
|
* Copyright (C) 2014 Genode Labs GmbH
|
|
*
|
|
* This file is part of the Genode OS framework, which is distributed
|
|
* under the terms of the GNU General Public License version 2.
|
|
*/
|
|
#ifndef _INCLUDE__USB__USB_H_
|
|
#define _INCLUDE__USB__USB_H_
|
|
|
|
#include <base/allocator.h>
|
|
#include <usb/types.h>
|
|
#include <usb/packet_handler.h>
|
|
#include <usb_session/connection.h>
|
|
|
|
#include <base/printf.h>
|
|
|
|
namespace Usb {
|
|
|
|
/* debugging */
|
|
bool constexpr verbose_descr = false;
|
|
|
|
class Device;
|
|
class Config;
|
|
class Alternate_interface;
|
|
class Interface;
|
|
class Endpoint;
|
|
class Meta_data;
|
|
class Sync_completion;
|
|
}
|
|
|
|
|
|
class Usb::Meta_data
|
|
{
|
|
protected:
|
|
|
|
Genode::Allocator * const _md_alloc;
|
|
Connection &_connection;
|
|
Packet_handler &_handler;
|
|
|
|
public:
|
|
|
|
Meta_data(Genode::Allocator * const md_alloc, Connection &device,
|
|
Packet_handler &handler)
|
|
: _md_alloc(md_alloc), _connection(device), _handler(handler) { }
|
|
};
|
|
|
|
/**
|
|
* Completion for synchronous calls
|
|
*/
|
|
class Usb::Sync_completion : Completion
|
|
{
|
|
private:
|
|
|
|
bool _completed = false;
|
|
Packet_descriptor &_p;
|
|
|
|
public:
|
|
|
|
Sync_completion(Packet_handler &handler, Packet_descriptor &p)
|
|
: _p(p)
|
|
{
|
|
Completion *c = p.completion;
|
|
p.completion = this;
|
|
|
|
handler.submit(p);
|
|
|
|
while (!_completed)
|
|
handler.wait_for_packet();
|
|
|
|
if (c)
|
|
c->complete(p);
|
|
}
|
|
|
|
void complete(Packet_descriptor &p) override
|
|
{
|
|
_p = p;
|
|
_completed = true;
|
|
}
|
|
};
|
|
|
|
|
|
class Usb::Endpoint : public Endpoint_descriptor
|
|
{
|
|
public:
|
|
|
|
Endpoint(Endpoint_descriptor &endpoint_descr)
|
|
: Endpoint_descriptor(endpoint_descr) { }
|
|
|
|
bool is_bulk() const { return (attributes & 0x3) == ENDPOINT_BULK; }
|
|
bool is_interrupt() const { return (attributes & 0x3) == ENDPOINT_INTERRUPT; }
|
|
|
|
void dump()
|
|
{
|
|
if (verbose_descr)
|
|
PLOG("\tEndpoint: len: %x type: %x address: %x attributes: %x",
|
|
length, type, address, attributes);
|
|
}
|
|
};
|
|
|
|
|
|
class Usb::Alternate_interface : public Interface_descriptor,
|
|
public Meta_data
|
|
{
|
|
private:
|
|
|
|
enum { MAX_ENDPOINTS = 16 };
|
|
Endpoint *_endpoints[MAX_ENDPOINTS];
|
|
|
|
public:
|
|
|
|
String interface_string;
|
|
|
|
Alternate_interface(Interface_descriptor &interface_desc,
|
|
Meta_data &md)
|
|
: Interface_descriptor(interface_desc), Meta_data(md)
|
|
{
|
|
dump();
|
|
|
|
for (Genode::uint8_t i = 0; i < num_endpoints; i++)
|
|
{
|
|
Endpoint_descriptor descr;
|
|
_connection.endpoint_descriptor(number, alt_settings, i, &descr);
|
|
_endpoints[i] = new (_md_alloc) Endpoint(descr);
|
|
_endpoints[i]->dump();
|
|
}
|
|
}
|
|
|
|
~Alternate_interface()
|
|
{
|
|
for (unsigned i = 0; i < num_endpoints; i++)
|
|
destroy(_md_alloc, _endpoints[i]);
|
|
|
|
interface_string.free(_md_alloc);
|
|
}
|
|
|
|
Endpoint &endpoint(unsigned index)
|
|
{
|
|
if (index >= num_endpoints)
|
|
throw Session::Invalid_endpoint();
|
|
|
|
return *_endpoints[index];
|
|
}
|
|
|
|
void dump()
|
|
{
|
|
if (!verbose_descr)
|
|
return;
|
|
|
|
PWRN("Interface: len: %x: type: %x number: %x alt_setting: %x",
|
|
length, type, number, alt_settings);
|
|
PWRN(" num_endpoints: %x class: %x subclass: %x: protocol: %x",
|
|
num_endpoints, iclass, isubclass, iprotocol);
|
|
}
|
|
};
|
|
|
|
|
|
class Usb::Interface : public Meta_data
|
|
{
|
|
private:
|
|
|
|
friend class Config;
|
|
|
|
enum { MAX_ALT = 10 };
|
|
|
|
Alternate_interface *_interface[MAX_ALT];
|
|
unsigned _count = 0;
|
|
Alternate_interface *_current = nullptr;
|
|
bool _claimed = false;
|
|
|
|
|
|
void _check()
|
|
{
|
|
if (!_claimed)
|
|
throw Session::Interface_not_claimed();
|
|
}
|
|
|
|
protected:
|
|
|
|
void _add(Alternate_interface *iface)
|
|
{
|
|
_interface[_count++] = iface;
|
|
|
|
if (iface->active)
|
|
_current = iface;
|
|
}
|
|
|
|
public:
|
|
|
|
Interface(Meta_data &md)
|
|
: Meta_data(md) { }
|
|
|
|
|
|
/***************
|
|
** Accessors **
|
|
***************/
|
|
|
|
unsigned alternate_count() const { return _count; }
|
|
Alternate_interface ¤t() { return *_current; }
|
|
|
|
Alternate_interface &alternate_interface(unsigned index)
|
|
{
|
|
if (index >= _count)
|
|
throw Session::Interface_not_found();
|
|
|
|
return *_interface[index];
|
|
}
|
|
|
|
Endpoint &endpoint(unsigned index) { return _current->endpoint(index); }
|
|
|
|
|
|
/***************************
|
|
** Packet stream helpers **
|
|
***************************/
|
|
|
|
Packet_descriptor alloc(size_t size)
|
|
{
|
|
return _handler.alloc(size);
|
|
}
|
|
|
|
void submit(Packet_descriptor &p)
|
|
{
|
|
_handler.submit(p);
|
|
}
|
|
|
|
void release(Packet_descriptor &p)
|
|
{
|
|
_handler.release(p);
|
|
}
|
|
|
|
void *content(Packet_descriptor &p)
|
|
{
|
|
return _handler.content(p);
|
|
}
|
|
|
|
/******************************
|
|
** Interface to USB service **
|
|
******************************/
|
|
|
|
/**
|
|
* Claim interface
|
|
*/
|
|
void claim()
|
|
{
|
|
_connection.claim_interface(_interface[0]->number);
|
|
_claimed = true;
|
|
}
|
|
|
|
/**
|
|
* Release interface
|
|
*/
|
|
void release()
|
|
{
|
|
if (_claimed)
|
|
return;
|
|
|
|
Packet_descriptor p = alloc(0);
|
|
p.type = Packet_descriptor::RELEASE_IF;
|
|
p.number = _interface[0]->number;
|
|
p.succeded = false;
|
|
|
|
Sync_completion sync(_handler, p);
|
|
|
|
if (p.succeded)
|
|
_claimed = false;
|
|
}
|
|
|
|
void set_alternate_interface(Alternate_interface &alternate)
|
|
{
|
|
_check();
|
|
|
|
Packet_descriptor p = alloc(0);
|
|
p.type = Packet_descriptor::ALT_SETTING;
|
|
p.succeded = false;
|
|
p.interface.number = alternate.number;
|
|
p.interface.alt_setting = alternate.alt_settings;
|
|
|
|
Sync_completion sync(_handler, p);
|
|
|
|
if (p.succeded)
|
|
_current = _interface[p.interface.alt_setting];
|
|
}
|
|
|
|
void control_transfer(Packet_descriptor &p, uint8_t request_type, uint8_t request,
|
|
uint16_t value, uint16_t index, int timeout,
|
|
bool block = true, Completion *c = nullptr)
|
|
{
|
|
_check();
|
|
|
|
p.type = Usb::Packet_descriptor::CTRL;
|
|
p.succeded = false;
|
|
p.control.request = request;
|
|
p.control.request_type = request_type;
|
|
p.control.value = value;
|
|
p.control.index = index;
|
|
p.control.timeout = timeout;
|
|
p.completion = c;
|
|
|
|
if(block) Sync_completion sync(_handler, p);
|
|
else _handler.submit(p);
|
|
}
|
|
|
|
void bulk_transfer(Packet_descriptor &p, Endpoint &ep, int timeout,
|
|
bool block = true, Completion *c = nullptr)
|
|
{
|
|
_check();
|
|
|
|
if (!ep.is_bulk())
|
|
throw Session::Invalid_endpoint();
|
|
|
|
p.type = Usb::Packet_descriptor::BULK;
|
|
p.succeded = false;
|
|
p.transfer.ep = ep.address;
|
|
p.transfer.timeout = timeout;
|
|
p.completion = c;
|
|
|
|
if(block) Sync_completion sync(_handler, p);
|
|
else _handler.submit(p);
|
|
}
|
|
|
|
void interrupt_transfer(Packet_descriptor &p, Endpoint &ep, int timeout,
|
|
bool block = true, Completion *c = nullptr)
|
|
{
|
|
_check();
|
|
|
|
if (!ep.is_interrupt())
|
|
throw Session::Invalid_endpoint();
|
|
|
|
p.type = Usb::Packet_descriptor::IRQ;
|
|
p.succeded = false;
|
|
p.transfer.ep = ep.address;
|
|
p.transfer.timeout = timeout;
|
|
p.completion = c;
|
|
|
|
if(block) Sync_completion sync(_handler, p);
|
|
else _handler.submit(p);
|
|
}
|
|
};
|
|
|
|
|
|
class Usb::Config : public Config_descriptor,
|
|
public Meta_data
|
|
{
|
|
private:
|
|
|
|
enum { MAX_INTERFACES = 32 };
|
|
|
|
Interface *_interfaces[MAX_INTERFACES];
|
|
unsigned _total_interfaces = 0;
|
|
|
|
public:
|
|
|
|
String config_string;
|
|
|
|
Config(Config_descriptor &config_desc, Meta_data &md)
|
|
: Config_descriptor(config_desc), Meta_data(md)
|
|
{
|
|
dump();
|
|
|
|
for (Genode::uint8_t i = 0; i < num_interfaces; i++) {
|
|
|
|
Interface_descriptor descr;
|
|
_connection.interface_descriptor(i, 0, &descr);
|
|
_interfaces[descr.number] = new(_md_alloc) Interface(md);
|
|
|
|
/* read number of alternative settings */
|
|
unsigned alt_settings = _connection.alt_settings(i);
|
|
_total_interfaces += alt_settings;
|
|
|
|
/* alt settings */
|
|
for (unsigned j = 0; j < alt_settings; j++) {
|
|
_connection.interface_descriptor(i, j, &descr);
|
|
if (descr.number != i)
|
|
PERR("Interface number != index");
|
|
|
|
_interfaces[descr.number]->_add(new(_md_alloc) Alternate_interface(descr, md));
|
|
}
|
|
}
|
|
}
|
|
|
|
~Config()
|
|
{
|
|
for (unsigned i = 0; i < num_interfaces; i++)
|
|
destroy(_md_alloc, _interfaces[i]);
|
|
|
|
config_string.free(_md_alloc);
|
|
}
|
|
|
|
Interface &interface(unsigned num)
|
|
{
|
|
|
|
if (num >= num_interfaces)
|
|
throw Session::Interface_not_found();
|
|
|
|
return *_interfaces[num];
|
|
}
|
|
|
|
void dump()
|
|
{
|
|
if (verbose_descr)
|
|
PINF("Config: len: %x type %x total_len: %x num_intf: %x config_value: %x",
|
|
length, type, total_length, num_interfaces, config_value);
|
|
}
|
|
};
|
|
|
|
|
|
class Usb::Device : public Meta_data
|
|
{
|
|
private:
|
|
|
|
Packet_handler _handler;
|
|
|
|
void _clear()
|
|
{
|
|
if (!config)
|
|
return;
|
|
|
|
manufactorer_string.free(_md_alloc);
|
|
product_string.free(_md_alloc);
|
|
serial_number_string.free(_md_alloc);
|
|
destroy(_md_alloc, config);
|
|
}
|
|
|
|
public:
|
|
|
|
enum Speed {
|
|
SPEED_UNKNOWN = 0, /* enumerating */
|
|
SPEED_LOW,
|
|
SPEED_FULL, /* usb 1.1 */
|
|
SPEED_HIGH, /* usb 2.0 */
|
|
SPEED_WIRELESS, /* wireless (usb 2.5) */
|
|
SPEED_SUPER, /* usb 3.0 */
|
|
};
|
|
|
|
Device_descriptor device_descr;
|
|
Config *config = nullptr;
|
|
|
|
String manufactorer_string;
|
|
String product_string;
|
|
String serial_number_string;
|
|
|
|
Device(Genode::Allocator * const md_alloc, Connection &connection,
|
|
Server::Entrypoint &ep)
|
|
: Meta_data(md_alloc, connection, _handler), _handler(connection, ep)
|
|
{ }
|
|
|
|
Device_descriptor const *descriptor() { return &device_descr; }
|
|
Config *config_descriptor() { return config; }
|
|
|
|
char const *speed_string(unsigned speed)
|
|
{
|
|
switch (speed) {
|
|
case SPEED_LOW : return "LOW";
|
|
case SPEED_FULL : return "FULL";
|
|
case SPEED_HIGH : return "HIGH";
|
|
case SPEED_WIRELESS: return "WIRELESS";
|
|
case SPEED_SUPER : return "SUPER";
|
|
|
|
default: return "<unknown>";
|
|
}
|
|
}
|
|
|
|
void string_descriptor(uint8_t index, String *target)
|
|
{
|
|
Packet_descriptor p = _handler.alloc(128);
|
|
p.type = Packet_descriptor::STRING;
|
|
p.string.index = index;
|
|
p.string.length = 128;
|
|
|
|
Sync_completion sync(_handler, p);
|
|
target->copy(p.string.length, _handler.content(p), _md_alloc);
|
|
}
|
|
|
|
/**
|
|
* Re-read all descriptors (device, config, interface, and endpoints)
|
|
* Must be called before Usb::Device can be used (asynchronous)
|
|
*/
|
|
void update_config()
|
|
{
|
|
/* free info from previous call */
|
|
_clear();
|
|
|
|
Config_descriptor config_descr;
|
|
|
|
_connection.config_descriptor(&device_descr, &config_descr);
|
|
dump();
|
|
|
|
config = new (_md_alloc) Config(config_descr, *this);
|
|
|
|
/* retrieve string descriptors */
|
|
string_descriptor(device_descr.manufactorer_index, &manufactorer_string);
|
|
string_descriptor(device_descr.product_index, &product_string);
|
|
string_descriptor(device_descr.serial_number_index, &serial_number_string);
|
|
string_descriptor(config->config_index, &config->config_string);
|
|
|
|
for (unsigned i = 0; i < config->num_interfaces; i++) {
|
|
Interface &iface = config->interface(i);
|
|
|
|
for (unsigned j = 0; j < iface.alternate_count(); j++)
|
|
string_descriptor(iface.alternate_interface(j).interface_index,
|
|
&iface.alternate_interface(j).interface_string);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Set configuration, no interfaces can be claimed (asynchronous)
|
|
*/
|
|
void set_configuration(uint8_t num)
|
|
{
|
|
if (!config) {
|
|
PERR("No current configuration found");
|
|
return;
|
|
}
|
|
|
|
if (!num || num > device_descr.num_configs) {
|
|
PERR("Valid configuration values: 1 ... %u", device_descr.num_configs);
|
|
return;
|
|
}
|
|
|
|
if (config && num == config->config_value)
|
|
return;
|
|
|
|
Packet_descriptor p = _handler.alloc(0);
|
|
p.type = Packet_descriptor::CONFIG;
|
|
p.number = num;
|
|
|
|
Sync_completion sync(_handler, p);
|
|
|
|
if (p.succeded)
|
|
update_config();
|
|
}
|
|
|
|
Interface &interface(unsigned interface_num)
|
|
{
|
|
return config->interface(interface_num);
|
|
}
|
|
|
|
void dump()
|
|
{
|
|
if (!verbose_descr)
|
|
return;
|
|
|
|
PINF("Device: len: %x type: %x class: %x sub-class: %x proto: %x max_packet: %x",
|
|
device_descr.length, device_descr.type, device_descr.dclass, device_descr.dsubclass,
|
|
device_descr.dprotocol, device_descr.max_packet_size);
|
|
PINF(" vendor: %x product: %x configs: %x",
|
|
device_descr.vendor_id, device_descr.product_id, device_descr.num_configs);
|
|
}
|
|
};
|
|
|
|
#endif /* _INCLUDE__USB__USB_H_ */
|