genode/repos/libports/src/lib/qemu-usb/host.cc

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;
}