98e2f91036
bus or device should be different (not both) when comparing a device to already present devices. Because of this the second USB device was marked as existent when the bus matched or the bus did not but the device number.
1024 lines
25 KiB
C++
1024 lines
25 KiB
C++
/**
|
|
* \brief USB session back end
|
|
* \author Josef Soentgen
|
|
* \author Sebastian Sumpf
|
|
* \date 2015-12-18
|
|
*/
|
|
|
|
#include <base/allocator_avl.h>
|
|
#include <base/log.h>
|
|
#include <base/attached_rom_dataspace.h>
|
|
#include <usb_session/connection.h>
|
|
#include <usb/usb.h>
|
|
#include <util/xml_node.h>
|
|
|
|
#include <extern_c_begin.h>
|
|
#include <qemu_emul.h>
|
|
#include <hw/usb.h>
|
|
#include <extern_c_end.h>
|
|
#include <base/debug.h>
|
|
|
|
|
|
using namespace Genode;
|
|
|
|
static bool const verbose_devices = false;
|
|
static bool const verbose_host = false;
|
|
static bool const verbose_warnings = false;
|
|
Lock _lock;
|
|
|
|
static void update_ep(USBDevice *);
|
|
static bool claim_interfaces(USBDevice *dev);
|
|
|
|
using Packet_alloc_failed = Usb::Session::Tx::Source::Packet_alloc_failed;
|
|
using Packet_type = Usb::Packet_descriptor::Type;
|
|
using Packet_error = Usb::Packet_descriptor::Error;
|
|
|
|
|
|
static unsigned endpoint_number(USBEndpoint const *usb_ep)
|
|
{
|
|
bool in = usb_ep->pid == USB_TOKEN_IN;
|
|
return usb_ep->nr | (in ? USB_DIR_IN : 0);
|
|
|
|
}
|
|
|
|
class Isoc_packet : Fifo<Isoc_packet>::Element
|
|
{
|
|
friend class Fifo<Isoc_packet>;
|
|
friend class Usb_host_device;
|
|
|
|
private:
|
|
|
|
Usb::Packet_descriptor _packet;
|
|
int _offset { 0 };
|
|
char *_content;
|
|
int _size;
|
|
|
|
public:
|
|
|
|
Isoc_packet(Usb::Packet_descriptor packet, char *content)
|
|
: _packet(packet), _content(content),
|
|
_size (_packet.read_transfer() ? _packet.transfer.actual_size : _packet.size())
|
|
{ }
|
|
|
|
bool copy(USBPacket *usb_packet)
|
|
{
|
|
if (!valid()) return false;
|
|
|
|
unsigned remaining = _size - _offset;
|
|
int copy_size = min(usb_packet->iov.size, remaining);
|
|
|
|
usb_packet_copy(usb_packet, _content + _offset, copy_size);
|
|
|
|
_offset += copy_size;
|
|
|
|
if (!_packet.read_transfer()) {
|
|
_packet.transfer.packet_size[_packet.transfer.number_of_packets] = copy_size;
|
|
_packet.transfer.number_of_packets++;
|
|
}
|
|
|
|
return remaining <= usb_packet->iov.size;
|
|
}
|
|
|
|
bool valid() const { return _content != nullptr; }
|
|
unsigned packet_count() const { return _packet.transfer.number_of_packets; }
|
|
|
|
Usb::Packet_descriptor& packet() { return _packet; }
|
|
};
|
|
|
|
|
|
struct Completion : Usb::Completion
|
|
{
|
|
USBPacket *p = nullptr;
|
|
USBDevice *dev = nullptr;
|
|
uint8_t *data = nullptr;
|
|
unsigned endpoint = 0;
|
|
|
|
enum State { VALID, FREE, CANCELED };
|
|
State state = FREE;
|
|
|
|
void complete(Usb::Packet_descriptor &p) override { }
|
|
|
|
void complete(Usb::Packet_descriptor &packet, char *content)
|
|
{
|
|
if (state != VALID) {
|
|
return;
|
|
}
|
|
|
|
int actual_size = 0;
|
|
|
|
switch (packet.type) {
|
|
case Packet_type::CTRL:
|
|
actual_size = packet.control.actual_size;
|
|
break;
|
|
case Packet_type::BULK:
|
|
case Packet_type::IRQ:
|
|
actual_size = packet.transfer.actual_size;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
if (actual_size < 0) actual_size = 0;
|
|
|
|
if (verbose_host)
|
|
log(__func__, ": packet: ", p, " packet.type: ", (int)packet.type, " "
|
|
"actual_size: ", Hex(actual_size));
|
|
|
|
p->actual_length = 0;
|
|
|
|
if (p->pid == USB_TOKEN_IN && actual_size > 0) {
|
|
if (data) Genode::memcpy(data, content, actual_size);
|
|
else usb_packet_copy(p, content, actual_size);
|
|
}
|
|
|
|
p->actual_length = actual_size;
|
|
|
|
if (packet.succeded)
|
|
p->status = USB_RET_SUCCESS;
|
|
else {
|
|
if (packet.error == Packet_error::STALL_ERROR)
|
|
p->status = USB_RET_STALL;
|
|
else
|
|
p->status = USB_RET_IOERROR;
|
|
}
|
|
|
|
switch (packet.type) {
|
|
case Packet_type::CONFIG:
|
|
if (!claim_interfaces(dev))
|
|
p->status = USB_RET_IOERROR;
|
|
case Packet_type::ALT_SETTING:
|
|
update_ep(dev);
|
|
case Packet_type::CTRL:
|
|
usb_generic_async_ctrl_complete(dev, p);
|
|
break;
|
|
case Packet_type::BULK:
|
|
case Packet_type::IRQ:
|
|
usb_packet_complete(dev, p);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
bool valid() const { return state == VALID; }
|
|
void cancel() { state = CANCELED; }
|
|
void free() { state = FREE; }
|
|
unsigned ep() const { return p ? endpoint_number(p->ep) : endpoint; }
|
|
};
|
|
|
|
|
|
/**
|
|
* Helper for the formatted output and device info
|
|
*/
|
|
struct Dev_info
|
|
{
|
|
uint32_t const vendor, product;
|
|
uint16_t const bus, dev;
|
|
|
|
Dev_info(uint16_t bus, uint16_t dev, uint32_t vendor, uint32_t product)
|
|
:
|
|
vendor(vendor), product(product), bus(bus), dev(dev)
|
|
{ }
|
|
|
|
void print(Output &out) const
|
|
{
|
|
Genode::print(out, Hex(bus, Hex::OMIT_PREFIX, Hex::PAD), ":",
|
|
Hex(dev, Hex::OMIT_PREFIX, Hex::PAD), " (",
|
|
"vendor=", Hex(vendor, Hex::OMIT_PREFIX), ", ",
|
|
"product=", Hex(product, Hex::OMIT_PREFIX), ")");
|
|
}
|
|
|
|
bool operator != (Dev_info const &other) const
|
|
{
|
|
if (bus && dev)
|
|
return bus != other.bus || dev != other.dev;
|
|
|
|
if (vendor && product)
|
|
return vendor != other.vendor || product != other.product;
|
|
|
|
return true;
|
|
}
|
|
};
|
|
|
|
|
|
struct Usb_host_device : List<Usb_host_device>::Element
|
|
{
|
|
Usb_host_device(const Usb_host_device&);
|
|
const Usb_host_device& operator=(const Usb_host_device&);
|
|
|
|
struct Could_not_create_device : Exception { };
|
|
|
|
bool deleted = false;
|
|
char const *label = nullptr;
|
|
Dev_info const info;
|
|
|
|
USBHostDevice *qemu_dev { nullptr };
|
|
|
|
/* submit queue + ack queue + 1 -> max nr of packets in flight */
|
|
enum { NUM_COMPLETIONS = Usb::Session::TX_QUEUE_SIZE * 2 + 1 };
|
|
Completion completion[NUM_COMPLETIONS];
|
|
|
|
Fifo<Isoc_packet> isoc_read_queue { };
|
|
Reconstructible<Isoc_packet> isoc_write_packet { Usb::Packet_descriptor(), nullptr };
|
|
|
|
Entrypoint &_ep;
|
|
Signal_handler<Usb_host_device> state_dispatcher { _ep, *this, &Usb_host_device::state_change };
|
|
|
|
Allocator &_alloc;
|
|
Allocator_avl _usb_alloc { &_alloc };
|
|
Usb::Connection usb_raw; //{ &_usb_alloc, label, 1024*1024, state_dispatcher };
|
|
|
|
Signal_handler<Usb_host_device> ack_avail_dispatcher { _ep, *this, &Usb_host_device::ack_avail };
|
|
|
|
void _release_interfaces()
|
|
{
|
|
Usb::Config_descriptor cdescr;
|
|
Usb::Device_descriptor ddescr;
|
|
|
|
try { usb_raw.config_descriptor(&ddescr, &cdescr); }
|
|
catch (Usb::Session::Device_not_found) { return; }
|
|
|
|
for (unsigned i = 0; i < cdescr.num_interfaces; i++) {
|
|
try { usb_raw.release_interface(i); }
|
|
catch (Usb::Session::Device_not_found) { return; }
|
|
}
|
|
}
|
|
|
|
bool _claim_interfaces()
|
|
{
|
|
Usb::Config_descriptor cdescr;
|
|
Usb::Device_descriptor ddescr;
|
|
|
|
try { usb_raw.config_descriptor(&ddescr, &cdescr); }
|
|
catch (Usb::Session::Device_not_found) { return false; }
|
|
|
|
bool result = true;
|
|
for (unsigned i = 0; i < cdescr.num_interfaces; i++) {
|
|
try {
|
|
usb_raw.claim_interface(i);
|
|
} catch (Usb::Session::Interface_already_claimed) {
|
|
result = false;
|
|
}
|
|
}
|
|
|
|
if (!result) error("device already claimed");
|
|
|
|
return result;
|
|
}
|
|
|
|
Usb_host_device(Entrypoint &ep, Allocator &alloc,
|
|
Env &env, char const *label,
|
|
Dev_info info)
|
|
:
|
|
label(label), _alloc(alloc),
|
|
usb_raw(env, &_usb_alloc, label, 6*1024*1024, state_dispatcher),
|
|
info(info), _ep(ep)
|
|
{
|
|
usb_raw.tx_channel()->sigh_ack_avail(ack_avail_dispatcher);
|
|
|
|
if (!_claim_interfaces())
|
|
throw Could_not_create_device();
|
|
|
|
qemu_dev = create_usbdevice(this);
|
|
|
|
if (!qemu_dev)
|
|
throw Could_not_create_device();
|
|
}
|
|
|
|
~Usb_host_device()
|
|
{
|
|
isoc_in_flush(0, true);
|
|
}
|
|
|
|
static int to_qemu_speed(unsigned speed)
|
|
{
|
|
switch (speed) {
|
|
case Usb::Device::SPEED_LOW: return USB_SPEED_LOW;
|
|
case Usb::Device::SPEED_FULL: return USB_SPEED_FULL;
|
|
case Usb::Device::SPEED_HIGH: return USB_SPEED_HIGH;
|
|
case Usb::Device::SPEED_SUPER: return USB_SPEED_SUPER;
|
|
default: return 0;
|
|
}
|
|
}
|
|
|
|
void ack_avail()
|
|
{
|
|
Lock::Guard g(_lock);
|
|
|
|
/* we are already dead, do nothing */
|
|
if (deleted == true) return;
|
|
|
|
while (usb_raw.source()->ack_avail()) {
|
|
Usb::Packet_descriptor packet = usb_raw.source()->get_acked_packet();
|
|
Completion *c = dynamic_cast<Completion *>(packet.completion);
|
|
|
|
if ((packet.type == Packet_type::ISOC && !packet.read_transfer()) ||
|
|
(c && c->state == Completion::CANCELED)) {
|
|
free_packet(packet);
|
|
continue;
|
|
}
|
|
|
|
char *content = usb_raw.source()->packet_content(packet);
|
|
|
|
if (packet.type != Packet_type::ISOC) {
|
|
c->complete(packet, content);
|
|
free_packet(packet);
|
|
} else {
|
|
/* isochronous in */
|
|
free_completion(packet);
|
|
_isoc_in_pending--;
|
|
Isoc_packet *new_packet = new (_alloc)
|
|
Isoc_packet(packet, content);
|
|
isoc_read_queue.enqueue(*new_packet);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/**********************************
|
|
** Isochronous packet handling **
|
|
**********************************/
|
|
|
|
bool isoc_read(USBPacket *packet)
|
|
{
|
|
if (isoc_read_queue.empty()) {
|
|
return false;
|
|
}
|
|
|
|
isoc_read_queue.head([&] (Isoc_packet &head) {
|
|
if (head.copy(packet)) {
|
|
isoc_read_queue.remove(head);
|
|
free_packet(&head);
|
|
}
|
|
});
|
|
|
|
return true;
|
|
}
|
|
|
|
unsigned _isoc_in_pending = 0;
|
|
|
|
bool isoc_new_packet()
|
|
{
|
|
unsigned count = 0;
|
|
isoc_read_queue.for_each([&count] (Isoc_packet&) { count++; });
|
|
return (count + _isoc_in_pending) < 3 ? true : false;
|
|
}
|
|
|
|
void isoc_in_packet(USBPacket *usb_packet)
|
|
{
|
|
enum { NUMBER_OF_PACKETS = 2 };
|
|
isoc_read(usb_packet);
|
|
|
|
if (!isoc_new_packet())
|
|
return;
|
|
|
|
size_t size = usb_packet->ep->max_packet_size * NUMBER_OF_PACKETS;
|
|
try {
|
|
Usb::Packet_descriptor packet = alloc_packet(size);
|
|
packet.type = Packet_type::ISOC;
|
|
packet.transfer.ep = usb_packet->ep->nr | USB_DIR_IN;
|
|
packet.transfer.polling_interval = Usb::Packet_descriptor::DEFAULT_POLLING_INTERVAL;
|
|
packet.transfer.number_of_packets = NUMBER_OF_PACKETS;
|
|
for (unsigned i = 0; i < NUMBER_OF_PACKETS; i++) {
|
|
packet.transfer.packet_size[i] = usb_packet->ep->max_packet_size;
|
|
}
|
|
|
|
Completion *c = dynamic_cast<Completion *>(packet.completion);
|
|
c->p = nullptr;
|
|
c->dev = usb_packet->ep->dev;
|
|
c->data = nullptr;
|
|
c->endpoint = endpoint_number(usb_packet->ep);
|
|
|
|
_isoc_in_pending++;
|
|
|
|
submit(packet);
|
|
} catch (Packet_alloc_failed) {
|
|
if (verbose_warnings)
|
|
warning("xHCI: packet allocation failed (size ", Hex(size), "in ", __func__, ")");
|
|
}
|
|
}
|
|
|
|
void isoc_in_flush(unsigned ep, bool all = false)
|
|
{
|
|
/* flush finished and stored data */
|
|
isoc_read_queue.for_each([&] (Isoc_packet &packet) {
|
|
if (!all && (!packet.valid() || packet.packet().transfer.ep != ep)) {
|
|
return;
|
|
}
|
|
|
|
isoc_read_queue.remove(packet);
|
|
free_packet(&packet);
|
|
});
|
|
|
|
/* flush in flight packets */
|
|
flush_completion(ep);
|
|
}
|
|
|
|
void isoc_out_packet(USBPacket *usb_packet)
|
|
{
|
|
enum { NUMBER_OF_PACKETS = 32 };
|
|
|
|
bool valid = isoc_write_packet->valid();
|
|
if (valid) {
|
|
isoc_write_packet->copy(usb_packet);
|
|
|
|
if (isoc_write_packet->packet_count() < NUMBER_OF_PACKETS)
|
|
return;
|
|
|
|
submit(isoc_write_packet->packet());
|
|
}
|
|
|
|
size_t size = usb_packet->ep->max_packet_size * NUMBER_OF_PACKETS;
|
|
try {
|
|
Usb::Packet_descriptor packet = alloc_packet(size, false);
|
|
packet.type = Packet_type::ISOC;
|
|
packet.transfer.ep = usb_packet->ep->nr;
|
|
packet.transfer.polling_interval = Usb::Packet_descriptor::DEFAULT_POLLING_INTERVAL;
|
|
packet.transfer.number_of_packets = 0;
|
|
|
|
isoc_write_packet.construct(packet, usb_raw.source()->packet_content(packet));
|
|
if (!valid) isoc_write_packet->copy(usb_packet);
|
|
|
|
} catch (Packet_alloc_failed) {
|
|
if (verbose_warnings)
|
|
warning("xHCI: packet allocation failed (size ", Hex(size), "in ", __func__, ")");
|
|
isoc_write_packet.construct(Usb::Packet_descriptor(), nullptr);
|
|
return;
|
|
}
|
|
}
|
|
|
|
|
|
/***********************
|
|
** Device disconnect **
|
|
***********************/
|
|
|
|
void _destroy()
|
|
{
|
|
/* mark delete before removing */
|
|
deleted = true;
|
|
|
|
/* remove from USB bus */
|
|
remove_usbdevice(qemu_dev);
|
|
qemu_dev = nullptr;
|
|
}
|
|
|
|
void state_change()
|
|
{
|
|
Lock::Guard g(_lock);
|
|
if (usb_raw.plugged())
|
|
return;
|
|
|
|
_destroy();
|
|
}
|
|
|
|
void destroy()
|
|
{
|
|
Lock::Guard g(_lock);
|
|
|
|
_release_interfaces();
|
|
|
|
_destroy();
|
|
}
|
|
|
|
|
|
/*******************
|
|
** Packet stream **
|
|
*******************/
|
|
|
|
Usb::Packet_descriptor alloc_packet(int length, bool completion = true)
|
|
{
|
|
if (!usb_raw.source()->ready_to_submit()) {
|
|
throw Packet_alloc_failed();
|
|
}
|
|
|
|
Usb::Packet_descriptor packet = usb_raw.source()->alloc_packet(length);
|
|
|
|
if (!completion) {
|
|
packet.completion = nullptr;
|
|
return packet;
|
|
}
|
|
|
|
packet.completion = alloc_completion();
|
|
if (!packet.completion) {
|
|
usb_raw.source()->release_packet(packet);
|
|
throw Packet_alloc_failed();
|
|
}
|
|
|
|
return packet;
|
|
}
|
|
|
|
void free_packet(Usb::Packet_descriptor &packet)
|
|
{
|
|
free_completion(packet);
|
|
usb_raw.source()->release_packet(packet);
|
|
}
|
|
|
|
void free_packet(Isoc_packet *packet)
|
|
{
|
|
free_packet(packet->packet());
|
|
Genode::destroy(_alloc, packet);
|
|
}
|
|
|
|
Completion *alloc_completion()
|
|
{
|
|
for (unsigned i = 0; i < NUM_COMPLETIONS; i++)
|
|
if (completion[i].state == Completion::FREE) {
|
|
completion[i].state = Completion::VALID;
|
|
return &completion[i];
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
void free_completion(Usb::Packet_descriptor &packet)
|
|
{
|
|
if (packet.completion) {
|
|
dynamic_cast<Completion *>(packet.completion)->free();
|
|
}
|
|
}
|
|
|
|
Completion *find_valid_completion(USBPacket *p)
|
|
{
|
|
for (unsigned i = 0; i < NUM_COMPLETIONS; i++)
|
|
if (completion[i].p == p && completion[i].valid())
|
|
return &completion[i];
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
void flush_completion(unsigned ep)
|
|
{
|
|
for (unsigned i = 0; i < NUM_COMPLETIONS; i++)
|
|
{
|
|
if (!completion[i].valid())
|
|
continue;
|
|
|
|
if (completion[i].ep() == ep) {
|
|
completion[i].cancel();
|
|
}
|
|
}
|
|
}
|
|
|
|
void submit(Usb::Packet_descriptor p)
|
|
{
|
|
usb_raw.source()->submit_packet(p);
|
|
}
|
|
|
|
bool claim_interfaces() { return _claim_interfaces(); }
|
|
|
|
void set_configuration(uint8_t value, USBPacket *p)
|
|
{
|
|
_release_interfaces();
|
|
|
|
try {
|
|
Usb::Packet_descriptor packet = alloc_packet(0);
|
|
packet.type = Usb::Packet_descriptor::CONFIG;
|
|
packet.number = value;
|
|
|
|
Completion *c = dynamic_cast<Completion *>(packet.completion);
|
|
c->p = p;
|
|
c->dev = cast_USBDevice(qemu_dev);
|
|
submit(packet);
|
|
p->status = USB_RET_ASYNC;
|
|
} catch (...) {
|
|
if (verbose_warnings)
|
|
warning(__func__, " packet allocation failed");
|
|
p->status = USB_RET_NAK;
|
|
}
|
|
|
|
}
|
|
|
|
void set_interface(int index, uint8_t value, USBPacket *p)
|
|
{
|
|
try {
|
|
Usb::Packet_descriptor packet = alloc_packet(0);
|
|
packet.type = Usb::Packet_descriptor::ALT_SETTING;
|
|
packet.interface.number = index;
|
|
packet.interface.alt_setting = value;
|
|
|
|
Completion *c = dynamic_cast<Completion *>(packet.completion);
|
|
c->p = p;
|
|
c->dev = cast_USBDevice(qemu_dev);
|
|
submit(packet);
|
|
p->status = USB_RET_ASYNC;
|
|
} catch (...) {
|
|
if (verbose_warnings)
|
|
warning(__func__, " packet allocation failed");
|
|
p->status = USB_RET_NAK;
|
|
}
|
|
}
|
|
|
|
void update_ep(USBDevice *udev)
|
|
{
|
|
usb_ep_reset(udev);
|
|
|
|
/* retrieve device speed */
|
|
Usb::Config_descriptor cdescr;
|
|
Usb::Device_descriptor ddescr;
|
|
|
|
try { usb_raw.config_descriptor(&ddescr, &cdescr); }
|
|
catch (Usb::Session::Device_not_found) { return; }
|
|
|
|
for (unsigned i = 0; i < cdescr.num_interfaces; i++) {
|
|
udev->altsetting[i] = usb_raw.alt_settings(i);
|
|
}
|
|
|
|
for (unsigned i = 0; i < cdescr.num_interfaces; i++) {
|
|
for (int j = 0; j < udev->altsetting[i]; j++) {
|
|
Usb::Interface_descriptor iface;
|
|
usb_raw.interface_descriptor(i, j, &iface);
|
|
for (unsigned k = 0; k < iface.num_endpoints; k++) {
|
|
Usb::Endpoint_descriptor endp;
|
|
usb_raw.endpoint_descriptor(i, j, k, &endp);
|
|
|
|
int const pid = (endp.address & USB_DIR_IN) ? USB_TOKEN_IN : USB_TOKEN_OUT;
|
|
int const ep = (endp.address & 0xf);
|
|
uint8_t const type = (endp.attributes & 0x3);
|
|
|
|
usb_ep_set_max_packet_size(udev, pid, ep, endp.max_packet_size);
|
|
usb_ep_set_type(udev, pid, ep, type);
|
|
usb_ep_set_ifnum(udev, pid, ep, i);
|
|
usb_ep_set_halted(udev, pid, ep, 0);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
|
|
/********************
|
|
** Qemu interface **
|
|
********************/
|
|
|
|
#define TRACE_AND_STOP do { warning(__func__, " not implemented"); } while(false)
|
|
|
|
#define USB_HOST_DEVICE(obj) \
|
|
OBJECT_CHECK(USBHostDevice, (obj), TYPE_USB_HOST_DEVICE)
|
|
|
|
|
|
static void update_ep(USBDevice *udev)
|
|
{
|
|
USBHostDevice *d = USB_HOST_DEVICE(udev);
|
|
Usb_host_device *dev = (Usb_host_device *)d->data;
|
|
|
|
dev->update_ep(udev);
|
|
}
|
|
|
|
|
|
static bool claim_interfaces(USBDevice *udev)
|
|
{
|
|
USBHostDevice *d = USB_HOST_DEVICE(udev);
|
|
Usb_host_device *dev = (Usb_host_device *)d->data;
|
|
|
|
return dev->claim_interfaces();
|
|
}
|
|
|
|
|
|
static void usb_host_realize(USBDevice *udev, Error **errp)
|
|
{
|
|
USBHostDevice *d = USB_HOST_DEVICE(udev);
|
|
Usb_host_device *dev = (Usb_host_device *)d->data;
|
|
|
|
/* retrieve device speed */
|
|
Usb::Config_descriptor cdescr;
|
|
Usb::Device_descriptor ddescr;
|
|
|
|
try { dev->usb_raw.config_descriptor(&ddescr, &cdescr); }
|
|
catch (Usb::Session::Device_not_found) { return; }
|
|
|
|
if (verbose_host)
|
|
log("set udev->speed to %d", Usb_host_device::to_qemu_speed(ddescr.speed));
|
|
|
|
udev->speed = Usb_host_device::to_qemu_speed(ddescr.speed);
|
|
udev->speedmask = (1 << udev->speed);
|
|
|
|
udev->flags |= (1 << USB_DEV_FLAG_IS_HOST);
|
|
|
|
dev->update_ep(udev);
|
|
}
|
|
|
|
|
|
static void usb_host_cancel_packet(USBDevice *udev, USBPacket *p)
|
|
{
|
|
USBHostDevice *d = USB_HOST_DEVICE(udev);
|
|
Usb_host_device *dev = (Usb_host_device *)d->data;
|
|
Completion *c = dev->find_valid_completion(p);
|
|
|
|
if (c)
|
|
c->cancel();
|
|
}
|
|
|
|
|
|
static void usb_host_handle_data(USBDevice *udev, USBPacket *p)
|
|
{
|
|
USBHostDevice *d = USB_HOST_DEVICE(udev);
|
|
Usb_host_device *dev = (Usb_host_device *)d->data;
|
|
Genode::size_t size = 0;
|
|
Usb::Packet_descriptor::Type type = Usb::Packet_descriptor::BULK;
|
|
bool const in = p->pid == USB_TOKEN_IN;
|
|
|
|
switch (usb_ep_get_type(udev, p->pid, p->ep->nr)) {
|
|
case USB_ENDPOINT_XFER_BULK:
|
|
type = Usb::Packet_descriptor::BULK;
|
|
size = usb_packet_size(p);
|
|
p->status = USB_RET_ASYNC;
|
|
break;
|
|
case USB_ENDPOINT_XFER_INT:
|
|
type = Usb::Packet_descriptor::IRQ;
|
|
size = p->iov.size;
|
|
p->status = USB_RET_ASYNC;
|
|
break;
|
|
case USB_ENDPOINT_XFER_ISOC:
|
|
if (in)
|
|
dev->isoc_in_packet(p);
|
|
else
|
|
dev->isoc_out_packet(p);
|
|
return;
|
|
default:
|
|
error("not supported data request");
|
|
break;
|
|
}
|
|
|
|
try {
|
|
Usb::Packet_descriptor packet = dev->alloc_packet(size);
|
|
packet.type = type;
|
|
packet.transfer.ep = p->ep->nr | (in ? USB_DIR_IN : 0);
|
|
packet.transfer.polling_interval = Usb::Packet_descriptor::DEFAULT_POLLING_INTERVAL;
|
|
|
|
if (!in) {
|
|
char * const content = dev->usb_raw.source()->packet_content(packet);
|
|
usb_packet_copy(p, content, size);
|
|
}
|
|
|
|
Completion *c = dynamic_cast<Completion *>(packet.completion);
|
|
c->p = p;
|
|
c->dev = udev;
|
|
c->data = nullptr;
|
|
|
|
dev->submit(packet);
|
|
} catch (Packet_alloc_failed) {
|
|
if (verbose_warnings)
|
|
warning("xHCI: packet allocation failed (size ", Hex(size), "in ", __func__, ")");
|
|
p->status = USB_RET_NAK;
|
|
}
|
|
}
|
|
|
|
|
|
static void usb_host_handle_control(USBDevice *udev, USBPacket *p,
|
|
int request, int value, int index,
|
|
int length, uint8_t *data)
|
|
{
|
|
USBHostDevice *d = USB_HOST_DEVICE(udev);
|
|
Usb_host_device *dev = (Usb_host_device *)d->data;
|
|
|
|
if (verbose_host)
|
|
log("r: ", Hex(request), " v: ", Hex(value), " "
|
|
"i: ", Hex(index), " length: ", length);
|
|
|
|
switch (request) {
|
|
case DeviceOutRequest | USB_REQ_SET_ADDRESS:
|
|
udev->addr = value;
|
|
return;
|
|
case DeviceOutRequest | USB_REQ_SET_CONFIGURATION:
|
|
dev->set_configuration(value & 0xff, p);
|
|
return;
|
|
case InterfaceOutRequest | USB_REQ_SET_INTERFACE:
|
|
dev->set_interface(index, value, p);
|
|
return;
|
|
}
|
|
|
|
if (udev->speed == USB_SPEED_SUPER &&
|
|
!(udev->port->speedmask & USB_SPEED_MASK_SUPER) &&
|
|
request == 0x8006 && value == 0x100 && index == 0) {
|
|
error("r->usb3ep0quirk = true");
|
|
}
|
|
|
|
Usb::Packet_descriptor packet;
|
|
try {
|
|
packet = dev->alloc_packet(length);
|
|
} catch (...) {
|
|
if (verbose_warnings)
|
|
warning("Packet allocation failed");
|
|
return;
|
|
}
|
|
|
|
packet.type = Usb::Packet_descriptor::CTRL;
|
|
packet.control.request_type = request >> 8;
|
|
packet.control.request = request & 0xff;
|
|
packet.control.index = index;
|
|
packet.control.value = value;
|
|
|
|
Completion *c = dynamic_cast<Completion *>(packet.completion);
|
|
c->p = p;
|
|
c->dev = udev;
|
|
c->data = data;
|
|
|
|
if (!(packet.control.request_type & USB_DIR_IN) && length) {
|
|
char *packet_content = dev->usb_raw.source()->packet_content(packet);
|
|
Genode::memcpy(packet_content, data, length);
|
|
}
|
|
|
|
dev->submit(packet);
|
|
p->status = USB_RET_ASYNC;
|
|
}
|
|
|
|
|
|
static void usb_host_ep_stopped(USBDevice *udev, USBEndpoint *usb_ep)
|
|
{
|
|
USBHostDevice *d = USB_HOST_DEVICE(udev);
|
|
Usb_host_device *dev = (Usb_host_device *)d->data;
|
|
|
|
bool in = usb_ep->pid == USB_TOKEN_IN;
|
|
unsigned ep = endpoint_number(usb_ep);
|
|
|
|
switch (usb_ep->type) {
|
|
case USB_ENDPOINT_XFER_ISOC:
|
|
if (in)
|
|
dev->isoc_in_flush(ep);
|
|
default:
|
|
return;
|
|
}
|
|
}
|
|
|
|
|
|
static Property usb_host_dev_properties[] = {
|
|
DEFINE_PROP_END_OF_LIST(),
|
|
};
|
|
|
|
|
|
static void usb_host_class_initfn(ObjectClass *klass, void *data)
|
|
{
|
|
DeviceClass *dc = DEVICE_CLASS(klass);
|
|
USBDeviceClass *uc = USB_DEVICE_CLASS(klass);
|
|
|
|
uc->realize = usb_host_realize;
|
|
uc->product_desc = "USB Host Device";
|
|
uc->cancel_packet = usb_host_cancel_packet;
|
|
uc->handle_data = usb_host_handle_data;
|
|
uc->handle_control = usb_host_handle_control;
|
|
uc->ep_stopped = usb_host_ep_stopped;
|
|
dc->props = usb_host_dev_properties;
|
|
}
|
|
|
|
|
|
static TypeInfo usb_host_dev_info;
|
|
|
|
|
|
static void usb_host_register_types(void)
|
|
{
|
|
usb_host_dev_info.name = TYPE_USB_HOST_DEVICE;
|
|
usb_host_dev_info.parent = TYPE_USB_DEVICE;
|
|
usb_host_dev_info.instance_size = sizeof(USBHostDevice);
|
|
usb_host_dev_info.class_init = usb_host_class_initfn;
|
|
|
|
type_register_static(&usb_host_dev_info);
|
|
}
|
|
|
|
|
|
struct Usb_devices : List<Usb_host_device>
|
|
{
|
|
Entrypoint &_ep;
|
|
Env &_env;
|
|
Allocator &_alloc;
|
|
Signal_handler<Usb_devices> _device_dispatcher { _ep, *this, &Usb_devices::_devices_update };
|
|
Attached_rom_dataspace _devices_rom = { _env, "usb_devices" };
|
|
|
|
void _garbage_collect()
|
|
{
|
|
for (Usb_host_device *next = first(); next;) {
|
|
Usb_host_device *current = next;
|
|
next = current->next();
|
|
|
|
if (current->deleted == false)
|
|
continue;
|
|
|
|
remove(current);
|
|
Genode::destroy(_alloc, current);
|
|
}
|
|
}
|
|
|
|
template <typename FUNC>
|
|
void for_each(FUNC const &fn)
|
|
{
|
|
for (Usb_host_device *d = first(); d; d = d->next())
|
|
fn(*d);
|
|
}
|
|
|
|
void _devices_update()
|
|
{
|
|
Lock::Guard g(_lock);
|
|
|
|
_garbage_collect();
|
|
|
|
_devices_rom.update();
|
|
if (!_devices_rom.valid())
|
|
return;
|
|
|
|
for_each([] (Usb_host_device &device) {
|
|
device.deleted = true;
|
|
});
|
|
|
|
if (verbose_devices)
|
|
log(_devices_rom.local_addr<char const>());
|
|
|
|
Xml_node devices_node(_devices_rom.local_addr<char>(), _devices_rom.size());
|
|
devices_node.for_each_sub_node("device", [&] (Xml_node const &node) {
|
|
|
|
unsigned product = node.attribute_value<unsigned>("product_id", 0);
|
|
unsigned vendor = node.attribute_value<unsigned>("vendor_id", 0);
|
|
unsigned bus = node.attribute_value<unsigned>("bus", 0);
|
|
unsigned dev = node.attribute_value<unsigned>("dev", 0);
|
|
|
|
Dev_info const dev_info(bus, dev, vendor, product);
|
|
|
|
String<128> label;
|
|
try {
|
|
node.attribute("label").value(&label);
|
|
} catch (Xml_attribute::Nonexistent_attribute) {
|
|
error("no label found for device ", dev_info);
|
|
return;
|
|
}
|
|
|
|
/* ignore if already created */
|
|
bool exists = false;
|
|
for_each([&] (Usb_host_device &device) {
|
|
if (device.info != dev_info)
|
|
return;
|
|
|
|
exists = true;
|
|
device.deleted = false;
|
|
});
|
|
|
|
if (exists)
|
|
return;
|
|
|
|
try {
|
|
Usb_host_device *new_device = new (_alloc)
|
|
Usb_host_device(_ep, _alloc, _env, label.string(),
|
|
dev_info);
|
|
|
|
insert(new_device);
|
|
|
|
log("Attach USB device ", dev_info);
|
|
} catch (...) {
|
|
error("could not attach USB device ", dev_info);
|
|
}
|
|
});
|
|
|
|
/* remove devices deleted by update */
|
|
for_each([] (Usb_host_device &device) {
|
|
if (device.deleted == false) return;
|
|
|
|
device._release_interfaces();
|
|
device._destroy();
|
|
});
|
|
_garbage_collect();
|
|
}
|
|
|
|
Usb_devices(Entrypoint &ep, Allocator &alloc, Env &env)
|
|
: _ep(ep), _env(env), _alloc(alloc)
|
|
{
|
|
_devices_rom.sigh(_device_dispatcher);
|
|
}
|
|
|
|
void destroy()
|
|
{
|
|
for (Usb_host_device *d = first(); d; d = d->next())
|
|
d->destroy();
|
|
|
|
_garbage_collect();
|
|
}
|
|
};
|
|
|
|
|
|
static Usb_devices *_devices;
|
|
|
|
|
|
extern "C" void usb_host_destroy()
|
|
{
|
|
if (_devices == nullptr) return;
|
|
|
|
_devices->destroy();
|
|
}
|
|
|
|
|
|
extern "C" void usb_host_update_devices()
|
|
{
|
|
if (_devices == nullptr) return;
|
|
|
|
_devices->_devices_update();
|
|
}
|
|
|
|
|
|
/*
|
|
* Do not use type_init macro because of name mangling
|
|
*/
|
|
extern "C" void _type_init_usb_host_register_types(Entrypoint *ep,
|
|
Allocator *alloc,
|
|
Env *env)
|
|
{
|
|
usb_host_register_types();
|
|
|
|
static Usb_devices devices(*ep, *alloc, *env);
|
|
_devices = &devices;
|
|
}
|