/* * \brief Component that filters USB device reports * \author Josef Soentgen * \date 2016-01-13 */ /* * Copyright (C) 2016 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. */ /* Genode includes */ #include #include #include #include #include #include #include #include #include #include #include namespace Usb_filter { using Genode::Xml_node; using Genode::Xml_generator; using Genode::Attached_rom_dataspace; using Genode::snprintf; struct Device_registry; struct Main; } static bool const verbose = false; static char const * const config_file = "usb_drv.config"; class Usb_filter::Device_registry { private: Genode::Allocator &_alloc; Server::Entrypoint &_ep; Genode::Reporter _reporter { "usb_devices" }; Attached_rom_dataspace _devices_rom { "devices" }; Attached_rom_dataspace _usb_drv_config_rom { "usb_drv_config" }; Genode::Allocator_avl _fs_packet_alloc { &_alloc }; File_system::Connection _fs { _fs_packet_alloc, 128*1024, "usb_drv.config" }; File_system::File_handle _file; struct Entry : public Genode::List::Element { unsigned bus; unsigned dev; unsigned vendor; unsigned product; Entry(unsigned b, unsigned d, unsigned v, unsigned p) : bus(b), dev(d), vendor(v), product(p) { } }; Genode::List _list; enum { MAX_LABEL_LEN = 512 }; typedef Genode::String Label; Label _client_label; template void _for_each_entry(FUNC const &func) const { Entry const *e = _list.first(); Entry const *next = nullptr; for (; e; e = next) { /* * Obtain next element prior calling the functor because * the functor may remove the current element from the list. */ next = e->next(); func(*e); } } static inline unsigned _get_value(Xml_node node, char const * const attr) { return node.attribute_value(attr, 0); } static bool _config_has_device(Xml_node config, Entry const &entry) { bool result = false; config.for_each_sub_node("device", [&] (Xml_node usb_device) { result |= (_get_value(usb_device, "bus") == entry.bus && _get_value(usb_device, "dev") == entry.dev); if (result) return; result |= (_get_value(usb_device, "vendor") == entry.vendor && _get_value(usb_device, "product") == entry.product); }); return result; } static bool _devices_matches(Xml_node &device, Entry const & entry) { unsigned const bus = _get_value(device, "bus"); unsigned const dev = _get_value(device, "dev"); unsigned const vendor = _get_value(device, "vendor_id"); unsigned const product = _get_value(device, "product_id"); return (bus == entry.bus && dev == entry.dev) || (vendor == entry.vendor && product == entry.product); } static void _gen_policy_entry(Xml_generator &xml, Xml_node &node, Entry const &entry, char const *label) { xml.node("policy", [&] { char buf[MAX_LABEL_LEN + 16]; unsigned const bus = _get_value(node, "bus"); unsigned const dev = _get_value(node, "dev"); snprintf(buf, sizeof(buf), "%s -> usb-%d-%d", label, bus, dev); xml.attribute("label", buf); snprintf(buf, sizeof(buf), "0x%4x", _get_value(node, "vendor_id")); xml.attribute("vendor_id", buf); snprintf(buf, sizeof(buf), "0x%4x", _get_value(node, "product_id")); xml.attribute("product_id", buf); snprintf(buf, sizeof(buf), "0x%4x", bus); xml.attribute("bus", buf); snprintf(buf, sizeof(buf), "0x%4x", dev); xml.attribute("dev", buf); }); } void _write_usb_drv_config(Xml_node & usb_devices) { using namespace Genode; try { File_system::Dir_handle root_dir = _fs.dir("/", false); _file = _fs.file(root_dir, config_file, File_system::READ_WRITE, false); } catch (...) { PERR("Could not open '%s'", config_file); return; } char old_file[1024]; size_t n = File_system::read(_fs, _file, old_file, sizeof(old_file)); if (n == 0) { PERR("Could not read '%s'", config_file); return; } Xml_node drv_config(old_file, n); bool const uhci_enabled = drv_config.attribute_value("uhci", false); bool const ehci_enabled = drv_config.attribute_value("ehci", false); bool const xhci_enabled = drv_config.attribute_value("xhci", false); char new_file[1024]; Genode::Xml_generator xml(new_file, sizeof(new_file), "config", [&] { if (uhci_enabled) xml.attribute("uhci", "yes"); if (ehci_enabled) xml.attribute("ehci", "yes"); if (xhci_enabled) xml.attribute("xhci", "yes"); /* copy other nodes */ drv_config.for_each_sub_node([&] (Xml_node &node) { if (!node.has_type("raw")) { xml.append(node.addr(), node.size()); return; } }); if (!drv_config.has_sub_node("raw")) PINF("enable raw support in usb_drv"); xml.node("raw", [&] { xml.node("report", [&] { xml.attribute("devices", "yes"); }); char const * const label = _client_label.string(); usb_devices.for_each_sub_node("device", [&] (Xml_node &node) { auto add_policy_entry = [&] (Entry const &entry) { if (!_devices_matches(node, entry)) return; _gen_policy_entry(xml, node, entry, label); }; _for_each_entry(add_policy_entry); }); }); }); new_file[xml.used()] = 0; if (verbose) PLOG("new usb_drv configuration:\n%s", new_file); n = File_system::write(_fs, _file, new_file, xml.used()); if (n == 0) PERR("Could not write '%s'", config_file); _fs.close(_file); } Genode::Signal_rpc_member _devices_dispatcher = { _ep, *this, &Device_registry::_handle_devices }; void _handle_devices(unsigned) { _devices_rom.update(); if (!_devices_rom.is_valid()) return; if (verbose) PLOG("device report:\n%s", _devices_rom.local_addr()); Xml_node usb_devices(_devices_rom.local_addr(), _devices_rom.size()); _write_usb_drv_config(usb_devices); } bool _check_config(Xml_node &drv_config) { if (!drv_config.has_sub_node("raw")) { PERR("Could not access node"); return false; } auto check_policy = [&] (Entry const &entry) { bool result = false; drv_config.sub_node("raw").for_each_sub_node("policy", [&] (Xml_node &node) { result |= (entry.bus == _get_value(node, "bus") && entry.dev == _get_value(node, "dev")); if (result) return; result |= (entry.vendor == _get_value(node, "vendor_id") && entry.product == _get_value(node, "product_id")); }); if (verbose && !result) PWRN("No matching policy was created for device %d-%d (%x:%x)", entry.bus, entry.dev, entry.vendor, entry.product); }; _for_each_entry(check_policy); return true; } static void _gen_device_entry(Xml_generator &xml, Xml_node &node, Entry const &entry) { xml.node("device", [&] { char buf[16]; unsigned const bus = _get_value(node, "bus"); unsigned const dev = _get_value(node, "dev"); snprintf(buf, sizeof(buf), "usb-%d-%d", bus, dev); xml.attribute("label", buf); snprintf(buf, sizeof(buf), "0x%4x", _get_value(node, "vendor_id")); xml.attribute("vendor_id", buf); snprintf(buf, sizeof(buf), "0x%4x", _get_value(node, "product_id")); xml.attribute("product_id", buf); snprintf(buf, sizeof(buf), "0x%4x", bus); xml.attribute("bus", buf); snprintf(buf, sizeof(buf), "0x%4x", dev); xml.attribute("dev", buf); }); } void _report_usb_devices() { using namespace Genode; /* * XXX it might happen that the device list has changed after we are * waiting for the usb_drv_config update */ Xml_node usb_devices(_devices_rom.local_addr(), _devices_rom.size()); Reporter::Xml_generator xml(_reporter, [&] () { usb_devices.for_each_sub_node("device", [&] (Xml_node &node) { auto check_entry = [&] (Entry const &entry) { if (!_devices_matches(node, entry)) return; _gen_device_entry(xml, node, entry); }; _for_each_entry(check_entry); }); }); } Genode::Signal_rpc_member _usb_drv_config_dispatcher = { _ep, *this, &Device_registry::_handle_usb_drv_config }; void _handle_usb_drv_config(unsigned) { _usb_drv_config_rom.update(); if (!_usb_drv_config_rom.is_valid()) return; Xml_node config(_usb_drv_config_rom.local_addr(), _usb_drv_config_rom.size()); if (!_check_config(config)) return; /* report devices if the USB drivers has changed its policies */ _report_usb_devices(); } bool _entry_exists(unsigned bus, unsigned dev, unsigned vendor, unsigned product) { bool result = false; auto check_exists = [&] (Entry const &entry) { result |= (bus && dev) && (entry.bus == bus && entry.dev == dev); result |= (vendor && product) && (entry.vendor == vendor && entry.product == product); }; _for_each_entry(check_exists); return result; } public: /** * Constructor */ Device_registry(Genode::Allocator &alloc, Server::Entrypoint &ep) : _alloc(alloc), _ep(ep) { _reporter.enabled(true); _devices_rom.sigh(_devices_dispatcher); _usb_drv_config_rom.sigh(_usb_drv_config_dispatcher); } /** * Update internal device registry */ void update_entries(Genode::Xml_node config) { auto remove_stale_entry = [&] (Entry const &entry) { if (_config_has_device(config, entry)) return; _list.remove(const_cast(&entry)); Genode::destroy(&_alloc, const_cast(&entry)); }; _for_each_entry(remove_stale_entry); auto add_new_entry = [&] (Xml_node const &node) { unsigned const bus = _get_value(node, "bus"); unsigned const dev = _get_value(node, "dev"); unsigned const vendor = _get_value(node, "vendor_id"); unsigned const product = _get_value(node, "product_id"); if (_entry_exists(bus, dev, vendor, product)) return; Entry *entry = new (&_alloc) Entry(bus, dev, vendor, product); _list.insert(entry); }; config.for_each_sub_node("device", add_new_entry); try { config.sub_node("client").attribute("label").value(&_client_label); } catch (...) { PERR("Could not update client label"); } } }; struct Usb_filter::Main { Server::Entrypoint &ep; Genode::Signal_rpc_member
_config_dispatcher = { ep, *this, &Main::_handle_config }; void _handle_config(unsigned) { Genode::config()->reload(); device_registry.update_entries(Genode::config()->xml_node()); } Device_registry device_registry { *Genode::env()->heap(), ep }; Main(Server::Entrypoint &ep) : ep(ep) { Genode::config()->sigh(_config_dispatcher); _handle_config(0); } }; namespace Server { char const *name() { return "usb_report_filter_ep"; } size_t stack_size() { return 4*1024*sizeof(addr_t); } void construct(Entrypoint &ep) { static Usb_filter::Main main(ep); } }