/*
* \brief USB device interface
* \author Stefan Kalkowski
* \date 2023-08-28
*/
/*
* Copyright (C) 2023-2024 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.
*/
#ifndef _INCLUDE__USB_SESSION__DEVICE_H_
#define _INCLUDE__USB_SESSION__DEVICE_H_
#include
#include
#include
#include
#include
#include
#include
namespace Usb {
template class Urb_handler;
class Endpoint;
class Interface;
class Device;
}
class Usb::Endpoint
{
public:
enum Direction { OUT, IN };
enum Type { CONTROL, ISOC, BULK, IRQ };
private:
enum { MAX_NUMBER = 0xf, INVALID = 0xff };
struct Address : Register<8>
{
struct Number : Bitfield<0, 4> {};
struct Direction : Bitfield<7, 1> {};
};
struct Attributes : Register<8>
{
struct Type : Bitfield<0, 2> {};
};
Address::access_t _address { INVALID };
Attributes::access_t _attributes { INVALID };
public:
struct Endpoint_not_avail : Exception {};
Endpoint(Address::access_t addr, Attributes::access_t attr)
: _address(addr), _attributes(attr) {}
Endpoint(Interface &iface, Direction d, Type t);
Endpoint() {}
bool valid() const {
return _address != INVALID || _attributes != INVALID; }
uint8_t address() const {
return _address; }
uint8_t number() const {
return Address::Number::get(_address); }
Type type() const {
return (Type) Attributes::Type::get(_attributes); }
Direction direction() const {
return Address::Direction::get(_address) ? IN : OUT; }
};
template
class Usb::Urb_handler
{
protected:
using Tx = typename SESSION::Tx;
using Packet_descriptor = typename SESSION::Packet_descriptor;
using Payload = typename Packet_descriptor::Payload;
public:
class Urb : Noncopyable
{
protected:
friend class Urb_handler;
using Direction = Endpoint::Direction;
using Isoc_header = genode_usb_isoc_transfer_header;
using Isoc_descriptor = genode_usb_isoc_descriptor;
Urb_handler &_urb_handler;
Direction const _direction;
uint32_t const _isoc_packets;
size_t const _size;
Payload _payload { };
bool _completed { false };
Constructible::Element> _tag {};
Fifo_element _pending_elem { *this };
virtual Packet_descriptor _create() const
{
Tagged_packet::Tag const tag { _tag->id().value };
Packet_descriptor const p(_payload, tag);
return p;
}
size_t _isoc_payload_offset()
{
return _isoc_packets ? sizeof(Isoc_header) +
_isoc_packets*sizeof(Isoc_descriptor)
: 0;
}
template
void _submit(URB &urb,
Tx::Source &tx,
OUT_FN const &out_fn,
ISOC_OUT_FN const &isoc_out_fn)
{
if (!_tag.constructed())
return;
Packet_descriptor const p = _create();
if (!_isoc_packets &&
_direction == Direction::OUT &&
_size) {
Byte_range_ptr dst { (char*)tx.packet_content(p), _size };
out_fn(urb, dst);
}
if (_isoc_packets) {
addr_t payload = (addr_t)tx.packet_content(p);
size_t off = _isoc_payload_offset();
Isoc_header &hdr = *(Isoc_header*)(payload);
hdr.number_of_packets = _isoc_packets;
for (uint32_t i = 0; i < _isoc_packets; i++) {
Byte_range_ptr dst { (char*)(payload+off), _size-off };
uint32_t psize = isoc_out_fn(urb, i, dst);
hdr.packets[i].actual_size = 0;
hdr.packets[i].size = psize;
off += psize;
}
}
tx.try_submit_packet(p);
}
template
void _in_results(URB &urb,
Packet_descriptor p,
Tx::Source &tx,
IN_FN const &in_fn,
ISOC_IN_FN const &isoc_in_fn)
{
if (!_isoc_packets) {
Const_byte_range_ptr src { (const char*)tx.packet_content(p),
p.payload_return_size };
in_fn(urb, src);
return;
}
addr_t payload = (addr_t)tx.packet_content(p);
size_t off = _isoc_payload_offset();
Isoc_header &hdr = *(Isoc_header*)(payload);
for (uint32_t i = 0; i < _isoc_packets; i++) {
Const_byte_range_ptr src { (const char*)(payload+off),
hdr.packets[i].actual_size };
isoc_in_fn(urb, i, src);
off += hdr.packets[i].size;
}
}
Urb(Urb_handler &handler,
Direction direction,
size_t size = 0,
uint32_t isoc_packets = 0)
:
_urb_handler(handler),
_direction(direction),
_isoc_packets(isoc_packets),
_size(size + _isoc_payload_offset())
{
_urb_handler._pending.enqueue(_pending_elem);
}
public:
virtual ~Urb()
{
if (pending())
_urb_handler._pending.remove(_pending_elem);
else if (in_progress())
warning("usb-session urb prematurely destructed");
}
bool in_progress() const { return _tag.constructed(); }
bool completed() const { return _completed; }
bool pending() const { return !in_progress() && !_completed; }
};
protected:
enum { URB_ALLOC_GRANULARITY = 512 };
Packet_allocator _alloc;
Packet_stream_tx::Client _tx;
Id_space _tags { };
Fifo> _pending { };
template
bool _try_process_ack(Tx::Source &, IN_FN const &,
ISOC_IN_FN const &, CPL_FN const &);
template
bool _try_submit_pending_urb(Tx::Source &, OUT_FN const &,
ISOC_OUT_FN const &);
public:
Urb_handler(Capability cap,
Region_map &rm, Allocator &md_alloc)
:
_alloc(&md_alloc, URB_ALLOC_GRANULARITY),
_tx(cap, rm, _alloc) {}
/**
* Handle the submission and completion of URBs
*
* \return true if progress was made
*/
template
bool update_urbs(OUT_FN const &out_fn,
IN_FN const &in_fn,
ISOC_OUT_FN const &isoc_out_fn,
ISOC_IN_FN const &isoc_in_fn,
CPL_FN const &complete_fn)
{
typename Tx::Source &tx = *_tx.source();
bool overall_progress = false;
for (;;) {
/* track progress of a single iteration */
bool progress = false;
/* process acknowledgements */
while (_try_process_ack(tx, in_fn, isoc_in_fn, complete_fn))
progress = true;
/* try to submit pending requests */
while (_try_submit_pending_urb(tx, out_fn, isoc_out_fn))
progress = true;
overall_progress |= progress;
if (!progress)
break;
}
if (overall_progress)
tx.wakeup();
return overall_progress;
}
/**
* Call 'fn' with each urb as argument
*
* This method is intended for the destruction of the urbs associated
* with the handler before destructing the 'Urb_handler' object.
*/
template
void dissolve_all_urbs(FN const &fn)
{
_pending.dequeue_all([&] (Fifo_element &elem) {
fn(static_cast(elem.object())); });
auto discard_tag_and_apply_fn = [&] (Urb &urb) {
urb._tag.destruct();
Packet_descriptor const p { urb._payload.offset,
urb._payload.bytes };
fn(static_cast(urb));
_tx.source()->release_packet(p);
};
while (_tags.template apply_any(discard_tag_and_apply_fn));
}
void sigh(Signal_context_capability cap)
{
_tx.sigh_ack_avail(cap);
_tx.sigh_ready_to_submit(cap);
}
};
class Usb::Interface
:
Noncopyable,
Interface_capability,
public Interface_session
{
public:
struct Index
{
uint8_t number;
uint8_t alt_setting;
};
struct Type
{
uint8_t cla;
uint8_t subcla;
uint8_t prot;
};
class Urb : public Urb_handler::Urb
{
protected:
using Base = Urb_handler::Urb;
Packet_descriptor::Type _type;
Endpoint _ep;
Packet_descriptor _create() const override
{
Packet_descriptor p = Base::_create();
p.index = _ep.address();
p.type = _type;
return p;
}
public:
Urb(Interface &iface, Endpoint &ep,
Packet_descriptor::Type type,
size_t size = 0,
uint32_t isoc_packets = 0)
:
Base(iface._urb_handler, ep.direction(),
size, isoc_packets),
_type(type), _ep(ep) { }
};
struct Alt_setting;
private:
friend class Endpoint;
friend class Urb;
enum { MAX_EPS = 16 };
Device &_device;
Index _idx;
Urb_handler _urb_handler;
Endpoint _eps[2][MAX_EPS] { };
public:
struct Interface_not_avail : Exception {};
Interface(Device &device, Index idx, size_t buffer_size);
Interface(Device &device, Type type, size_t buffer_size);
Interface(Device &device, size_t buffer_size);
~Interface();
Index index() const { return _idx; }
void sigh(Signal_context_capability cap) {
_urb_handler.sigh(cap); }
template
bool update_urbs(OUT_FN const &out_fn,
IN_FN const &in_fn,
ISOC_OUT_FN const &isoc_out_fn,
ISOC_IN_FN const &isoc_in_fn,
CPL_FN const &complete_fn)
{
return _urb_handler.update_urbs(out_fn, in_fn, isoc_out_fn,
isoc_in_fn, complete_fn);
}
template
bool update_urbs(OUT_FN const &out_fn,
IN_FN const &in_fn,
CPL_FN const &complete_fn)
{
auto isoc_out = [] (URB&, uint32_t, Byte_range_ptr&) { return 0; };
auto isoc_in = [] (URB&, uint32_t, Const_byte_range_ptr&) { };
return _urb_handler.update_urbs(out_fn, in_fn, isoc_out,
isoc_in, complete_fn);
}
template
void dissolve_all_urbs(FN const &fn) {
_urb_handler.dissolve_all_urbs(fn); }
template
void for_each_endpoint(FN const &fn)
{
for (unsigned d = Endpoint::OUT; d <= Endpoint::IN; d++)
for (unsigned n = 0; n < MAX_EPS; n++)
if (_eps[d][n].valid()) fn(_eps[d][n]);
}
};
class Usb::Device
:
Noncopyable,
Device_capability,
public Device_session
{
public:
using Interface = Usb::Interface;
using Name = Usb::Session::Device_name;
class Urb : public Urb_handler::Urb
{
public:
using Type = Packet_descriptor::Request_type;
protected:
using Base = Urb_handler::Urb;
uint8_t _request;
Type::access_t _request_type;
uint16_t _value;
uint16_t _index;
Packet_descriptor _create() const override
{
Packet_descriptor p = Base::_create();
p.request_type = _request_type;
p.request = _request;
p.value = _value;
p.index = _index;
return p;
}
public:
Urb(Device &device,
uint8_t request, Type::access_t request_type,
uint16_t value, uint16_t index, size_t size = 0)
:
Base(device._urb_handler,
Type::D::get(request_type) ? Endpoint::IN
: Endpoint::OUT,
size),
_request(request), _request_type(request_type),
_value(value), _index(index) {}
};
private:
friend class Usb::Interface;
friend class Endpoint;
friend class Urb;
::Usb::Connection &_session;
Allocator &_md_alloc;
Region_map &_rm;
Name const _name { };
Urb_handler _urb_handler;
Interface_capability _interface_cap(uint8_t num, size_t buf_size);
Name _first_device_name();
template
void _for_each_iface(FN const & fn);
Interface::Index _interface_index(Interface::Type);
public:
Device(Connection &usb_session, Allocator &md_alloc,
Region_map &rm, Name name);
Device(Connection &session, Allocator &md_alloc, Region_map &rm);
~Device() { _session.release_device(*this); }
void sigh(Signal_context_capability cap) {
_urb_handler.sigh(cap); }
template
bool update_urbs(OUT_FN const &out_fn,
IN_FN const &in_fn,
CPL_FN const &complete_fn)
{
auto isoc_out = [] (URB&, uint32_t, Byte_range_ptr&) { return 0; };
auto isoc_in = [] (URB&, uint32_t, Const_byte_range_ptr&) { };
return _urb_handler.update_urbs(out_fn, in_fn, isoc_out,
isoc_in, complete_fn);
}
template
void dissolve_all_urbs(FN const &fn) {
_urb_handler.dissolve_all_urbs(fn); }
};
struct Usb::Interface::Alt_setting : Device::Urb
{
using P = Device::Packet_descriptor;
using Rt = P::Request_type;
Alt_setting(Device &dev, Interface &iface)
:
Device::Urb(dev,
P::Request::SET_INTERFACE,
Rt::value(P::Recipient::IFACE, P::Type::STANDARD,
P::Direction::IN),
iface.index().number, iface.index().alt_setting) {}
};
template
template
bool
Usb::Urb_handler::_try_process_ack(Tx::Source &tx,
IN_FN const &in_fn,
ISOC_IN_FN const &isoc_in,
CPL_FN const &complete_fn)
{
if (!tx.ack_avail())
return false;
Packet_descriptor const p = tx.try_get_acked_packet();
typename Id_space::Id const id { p.tag.value };
try {
_tags.template apply(id, [&] (Urb &urb) {
if (urb._direction == Urb::Direction::IN &&
p.return_value == Packet_descriptor::OK)
urb._in_results(static_cast(urb), p, tx, in_fn, isoc_in);
urb._completed = true;
urb._tag.destruct();
complete_fn(static_cast(urb), p.return_value);
});
} catch (typename Id_space::Unknown_id) {
warning("spurious usb-session urb acknowledgement");
}
tx.release_packet(p);
return true;
}
template
template
bool
Usb::Urb_handler::_try_submit_pending_urb(Tx::Source &tx,
OUT_FN const &out_fn,
ISOC_FN const &isoc_fn)
{
if (_pending.empty())
return false;
if (!tx.ready_to_submit())
return false;
/*
* Allocate space for the payload in the packet-stream buffer.
*/
Payload payload { };
try {
_pending.head([&] (Fifo_element const &elem) {
Urb const &urb = elem.object();
size_t const align = Tagged_packet::PACKET_ALIGNMENT;
payload = {
.offset = tx.alloc_packet(urb._size, align).offset(),
.bytes = urb._size
};
});
} catch (typename Tx::Source::Packet_alloc_failed) {
/* the packet-stream buffer is saturated */
return false;
}
/*
* All preconditions for the submission of the urb are satisfied.
* So the urb can go from the pending to the in-progress stage.
*/
_pending.dequeue([&] (Genode::Fifo_element &elem) {
Urb &urb = elem.object();
/* let the urb join the tag ID space, allocating a tag */
urb._tag.construct(elem.object(), _tags);
urb._payload = payload;
urb._submit(static_cast(elem.object()), tx, out_fn, isoc_fn);
});
return true;
}
inline Usb::Endpoint::Endpoint(Interface &iface, Direction d, Type t)
{
bool found = false;
iface.for_each_endpoint([&] (Endpoint ep) {
if (ep.type() == t && ep.direction() == d) {
_attributes = ep._attributes;
_address = ep._address;
found = true;
}
});
if (!found)
throw Endpoint_not_avail();
};
inline Usb::Interface::Interface(Device &device, Index idx, size_t buffer_size)
:
Interface_capability(device._interface_cap(idx.number, buffer_size)),
_device(device), _idx(idx),
_urb_handler(call(), device._rm, device._md_alloc)
{
static constexpr uint16_t INVALID = 256;
device._for_each_iface([&] (Xml_node node) {
if (node.attribute_value("number", INVALID) != idx.number)
return;
node.for_each_sub_node("endpoint", [&] (Xml_node node) {
Endpoint ep { node.attribute_value("address", 0),
node.attribute_value("attributes", 0) };
if (!_eps[ep.direction()][ep.number()].valid())
_eps[ep.direction()][ep.number()] = ep;
});
});
}
inline Usb::Interface::Interface(Device &device, Type type, size_t buffer_size)
: Interface(device, device._interface_index(type), buffer_size) { }
inline Usb::Interface::Interface(Device &device, size_t buffer_size)
: Interface(device, Index { 0, 0 }, buffer_size) { }
inline Usb::Interface::~Interface() {
_device.call(*this); }
inline Usb::Interface_capability
Usb::Device::_interface_cap(uint8_t num, size_t buf_size)
{
return _session.retry_with_upgrade(Ram_quota{buf_size + 4096},
Cap_quota{6}, [&] () {
return call(num, buf_size); });
}
inline Usb::Device::Name Usb::Device::_first_device_name()
{
Name ret;
_session.with_xml([&] (Xml_node & xml) {
xml.with_optional_sub_node("device", [&] (Xml_node node) {
ret = node.attribute_value("name", Name()); });
});
return ret;
}
template
void Usb::Device::_for_each_iface(FN const & fn)
{
_session.with_xml([&] (Xml_node & xml) {
xml.for_each_sub_node("device", [&] (Xml_node node) {
if (node.attribute_value("name", Name()) == _name)
node.for_each_sub_node("config", [&] (Xml_node node) {
if (node.attribute_value("active", false))
node.for_each_sub_node("interface", fn);
});
});
});
}
inline Usb::Interface::Index
Usb::Device::_interface_index(Interface::Type t)
{
static constexpr uint16_t INVALID = 256;
uint16_t num = INVALID, alt = INVALID;
_for_each_iface([&] (Xml_node node) {
uint16_t c = node.attribute_value("class", INVALID);
uint16_t s = node.attribute_value("subclass", INVALID);
uint16_t p = node.attribute_value("protocol", INVALID);
if (c == t.cla && s == t.subcla && p == t.prot) {
num = node.attribute_value("number", INVALID);
alt = node.attribute_value("alt_setting", INVALID);
}
});
if (num < INVALID && alt < INVALID)
return { (uint8_t)num, (uint8_t)alt };
throw Interface::Interface_not_avail();
}
inline Usb::Device::Device(Connection &session,
Allocator &md_alloc,
Region_map &rm,
Name name)
:
Device_capability(session.acquire_device(name)),
_session(session),
_md_alloc(md_alloc),
_rm(rm),
_name(name),
_urb_handler(call(), rm, md_alloc) {}
inline Usb::Device::Device(Connection &session,
Allocator &md_alloc,
Region_map &rm)
:
Device_capability(session.acquire_device()),
_session(session),
_md_alloc(md_alloc),
_rm(rm),
_name(_first_device_name()),
_urb_handler(call(), rm, md_alloc) {}
#endif /* _INCLUDE__USB_SESSION__DEVICE_H_ */