435 lines
11 KiB
C++
435 lines
11 KiB
C++
/*
|
|
* \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 <base/allocator_avl.h>
|
|
#include <os/attached_rom_dataspace.h>
|
|
#include <os/config.h>
|
|
#include <os/reporter.h>
|
|
#include <os/server.h>
|
|
#include <util/list.h>
|
|
#include <util/string.h>
|
|
#include <util/xml_generator.h>
|
|
#include <util/xml_node.h>
|
|
#include <file_system_session/connection.h>
|
|
#include <file_system/util.h>
|
|
|
|
|
|
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<Entry>::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<Entry> _list;
|
|
|
|
enum { MAX_LABEL_LEN = 512 };
|
|
typedef Genode::String<MAX_LABEL_LEN> Label;
|
|
Label _client_label;
|
|
|
|
template <typename FUNC>
|
|
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<unsigned long>(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<bool>("uhci", false);
|
|
bool const ehci_enabled = drv_config.attribute_value<bool>("ehci", false);
|
|
bool const xhci_enabled = drv_config.attribute_value<bool>("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<Device_registry> _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<char>());
|
|
|
|
Xml_node usb_devices(_devices_rom.local_addr<char>(), _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 <raw> 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<char>(), _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<Device_registry> _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<char>(),
|
|
_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 *>(&entry));
|
|
Genode::destroy(&_alloc, const_cast<Entry *>(&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<Main> _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); }
|
|
}
|