genode/repos/os/include/usb/usb.h

564 lines
13 KiB
C
Raw Normal View History

/**
* \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 <base/entrypoint.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 bulk() const { return (attributes & 0x3) == ENDPOINT_BULK; }
bool interrupt() const { return (attributes & 0x3) == ENDPOINT_INTERRUPT; }
/*
* \deprecated use 'bulk' and 'interrupt' instead
*/
bool is_bulk() const { return bulk(); }
bool is_interrupt() const { return 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 &current() { 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,
Genode::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_ */