/* * \brief platform session component * \author Norman Feske * \date 2008-01-28 */ /* * Copyright (C) 2008-2013 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. */ #pragma once /* base */ #include #include #include #include #include #include #include #include #include /* os */ #include #include #include /* local */ #include "pci_device_component.h" #include "pci_config_access.h" #include "pci_device_pd_ipc.h" #include "device_pd.h" namespace Platform { bool bus_valid(int bus = 0); unsigned short bridge_bdf(unsigned char bus); class Rmrr; class Root; } class Platform::Rmrr : public Genode::List::Element { public: class Bdf : public Genode::List::Element { private: Genode::uint8_t _bus, _dev, _func; public: Bdf(Genode::uint8_t bus, Genode::uint8_t dev, Genode::uint8_t func) : _bus(bus), _dev(dev), _func(func) { } bool match(Genode::uint8_t bus, Genode::uint8_t dev, Genode::uint8_t func) { return bus == _bus && dev == _dev && func == _func; } }; private: Genode::uint64_t _start, _end; Genode::Io_mem_dataspace_capability _cap; Genode::List _bdf_list; public: Rmrr(Genode::uint64_t start, Genode::uint64_t end) : _start(start), _end(end) { } Genode::Io_mem_dataspace_capability match(Device_config config) { Genode::uint8_t bus = config.bus_number(); Genode::uint8_t device = config.device_number(); Genode::uint8_t function = config.function_number(); for (Bdf *bdf = _bdf_list.first(); bdf; bdf = bdf->next()) { if (!bdf->match(bus, device, function)) continue; if (_cap.valid()) return _cap; Genode::Io_mem_connection io_mem(_start, _end - _start + 1); io_mem.on_destruction(Genode::Io_mem_connection::KEEP_OPEN); _cap = io_mem.dataspace(); return _cap; } return Genode::Io_mem_dataspace_capability(); } void add(Bdf * bdf) { _bdf_list.insert(bdf); } static Genode::List *list() { static Genode::List _list; return &_list; } }; namespace Platform { class Session_component : public Genode::Rpc_object { private: Genode::Rpc_entrypoint *_ep; Genode::Allocator_guard _md_alloc; Genode::Tslab _device_slab; Genode::List _device_list; Genode::Rpc_entrypoint &_device_pd_ep; struct Resources { Genode::Ram_connection _ram; Resources() : /* restrict physical address to 3G on 32bit */ _ram("dma", 0, (sizeof(void *) == 4) ? 0xc0000000UL : 0x100000000ULL) { /* associate _ram session with platform_drv _ram session */ _ram.ref_account(Genode::env()->ram_session_cap()); } Genode::Ram_connection &ram() { return _ram; } } _resources; struct Devicepd { Device_pd_policy *policy; Device_pd_client child; Genode::Allocator_guard &_md_alloc; enum { DEVICE_PD_RAM_QUOTA = 180 * 4096 }; Devicepd (Genode::Rpc_entrypoint &ep, Genode::Allocator_guard &md_alloc, Genode::Ram_session_capability ram_ref_cap) : policy(nullptr), child(Genode::reinterpret_cap_cast(Genode::Native_capability())), _md_alloc(md_alloc) { if (!md_alloc.withdraw(DEVICE_PD_RAM_QUOTA)) throw Out_of_metadata(); if (Genode::env()->ram_session()->transfer_quota(ram_ref_cap, DEVICE_PD_RAM_QUOTA)) { PERR("Transferring quota for device pd failed"); md_alloc.upgrade(DEVICE_PD_RAM_QUOTA); throw Platform::Session::Fatal(); } try { policy = new (md_alloc) Device_pd_policy(ep, ram_ref_cap, DEVICE_PD_RAM_QUOTA); using Genode::Session_capability; using Genode::Affinity; Session_capability session = Genode::Root_client(policy->root()).session("", Affinity()); child = Device_pd_client(Genode::reinterpret_cap_cast(session)); } catch (Genode::Rom_connection::Rom_connection_failed) { PWRN("PCI device protection domain for IOMMU support " "is not available"); if (policy) { destroy(md_alloc, policy); policy = nullptr; } md_alloc.upgrade(DEVICE_PD_RAM_QUOTA); } catch (Genode::Allocator::Out_of_memory) { md_alloc.upgrade(DEVICE_PD_RAM_QUOTA); throw Out_of_metadata(); } catch(...) { PERR("unknown exception"); md_alloc.upgrade(DEVICE_PD_RAM_QUOTA); throw Platform::Session::Fatal(); } } ~Devicepd() { if (!policy) return; destroy(_md_alloc, policy); _md_alloc.upgrade(DEVICE_PD_RAM_QUOTA); } bool valid() { return policy && policy->root().valid() && child.valid(); } }; Genode::Lazy_volatile_object _device_pd; Genode::Session_label _label; Genode::Session_policy _policy; enum { MAX_PCI_DEVICES = Device_config::MAX_BUSES * Device_config::MAX_DEVICES * Device_config::MAX_FUNCTIONS }; static Genode::Bit_array bdf_in_use; /** * Scan PCI buses for a device * * \param bus start scanning at bus number * \param device start scanning at device number * \param function start scanning at function number * \param out_device_config device config information of the * found device * \param config_access interface for accessing the PCI * configuration * space * * \retval true device was found * \retval false no device was found */ bool _find_next(int bus, int device, int function, Device_config *out_device_config, Config_access *config_access) { for (; bus < Device_config::MAX_BUSES; bus++) { if (!bus_valid(bus)) continue; for (; device < Device_config::MAX_DEVICES; device++) { for (; function < Device_config::MAX_FUNCTIONS; function++) { /* read config space */ Device_config config(bus, device, function, config_access); if (config.valid()) { *out_device_config = config; return true; } } function = 0; /* init value for next device */ } device = 0; /* init value for next bus */ } return false; } /** * List containing extended PCI config space information */ static Genode::List &config_space_list() { static Genode::List config_space; return config_space; } /** * Find for a given PCI device described by the bus:dev:func triple * the corresponding extended 4K PCI config space address. * A io mem dataspace is created and returned. */ Genode::addr_t lookup_config_space(Genode::uint16_t const bdf) { using namespace Genode; addr_t config_space = ~0UL; /* invalid */ Config_space *e = config_space_list().first(); for (; e && (config_space == ~0UL); e = e->next()) config_space = e->lookup_config_space(bdf); return config_space; } /* * List of aliases for PCI Class/Subclas/Prog I/F triple used * by xml config for this platform driver */ unsigned class_subclass_prog(const char * name) { using namespace Genode; static struct { const char * alias; uint8_t pci_class, pci_subclass, pci_progif; } const aliases [] = { { "AHCI" , 0x1, 0x06, 0x0}, { "ALL" , 0x0, 0x00, 0x0}, { "AUDIO" , 0x4, 0x01, 0x0}, { "ETHERNET" , 0x2, 0x00, 0x0}, { "HDAUDIO" , 0x4, 0x03, 0x0}, { "USB" , 0xc, 0x03, 0x0}, { "VGA" , 0x3, 0x00, 0x0}, { "WIFI" , 0x2, 0x80, 0x0}, { "ISABRIDGE", 0x6, 0x01, 0x0} }; for (unsigned i = 0; i < sizeof(aliases) / sizeof(aliases[0]); i++) { if (strcmp(aliases[i].alias, name)) continue; return 0U | aliases[i].pci_class << 16 | aliases[i].pci_subclass << 8 | aliases[i].pci_progif; } return ~0U; } /** * Check device usage according to session policy */ bool permit_device(const char * name) { using namespace Genode; try { _policy.for_each_sub_node("device", [&] (Xml_node dev) { try { /* enforce restriction based on name name */ char policy_name[8]; dev.attribute("name").value(policy_name, sizeof(policy_name)); if (!strcmp(policy_name, name)) /* found identical match - permit access */ throw true; } catch (Xml_attribute::Nonexistent_attribute) { } }); } catch (bool result) { return result; } return false; } /** * Check according session policy device usage */ bool permit_device(Genode::uint8_t b, Genode::uint8_t d, Genode::uint8_t f, unsigned class_code) { using namespace Genode; try { _policy.for_each_sub_node("pci", [&] (Xml_node node) { try { unsigned bus, device, function; node.attribute("bus").value(&bus); node.attribute("device").value(&device); node.attribute("function").value(&function); if (b == bus && d == device && f == function) throw true; return; } catch (Xml_attribute::Nonexistent_attribute) { } /* enforce restriction based upon classes */ unsigned class_sub_prog = 0; try { char alias_class[32]; node.attribute("class").value(alias_class, sizeof(alias_class)); class_sub_prog = class_subclass_prog(alias_class); } catch (Xml_attribute::Nonexistent_attribute) { return; } enum { DONT_CHECK_PROGIF = 8 }; /* if class/subclass don't match - deny */ if (class_sub_prog && (class_sub_prog ^ class_code) >> DONT_CHECK_PROGIF) return; /* if this bdf is used by some policy - deny */ if (find_dev_in_policy(b, d, f)) return; throw true; }); } catch (bool result) { return result; } return false; } /** * Lookup a given device name. */ bool find_dev_in_policy(const char * dev_name, bool once = true) { using namespace Genode; try { config()->xml_node().for_each_sub_node("policy", [&] (Xml_node policy) { policy.for_each_sub_node("device", [&] (Xml_node device) { try { /* device attribute from policy node */ char policy_device[8]; device.attribute("name").value(policy_device, sizeof(policy_device)); if (!strcmp(policy_device, dev_name)) { if (once) throw true; once = true; } } catch (Xml_node::Nonexistent_attribute) { } }); }); } catch (bool result) { return result; } return false; } /** * Lookup a given device name. */ bool find_dev_in_policy(Genode::uint8_t b, Genode::uint8_t d, Genode::uint8_t f, bool once = true) { using namespace Genode; try { Xml_node xml(config()->xml_node()); xml.for_each_sub_node("policy", [&] (Xml_node policy) { policy.for_each_sub_node("pci", [&] (Xml_node node) { try { unsigned bus, device, function; node.attribute("bus").value(&bus); node.attribute("device").value(&device); node.attribute("function").value(&function); if (b == bus && d == device && f == function) { if (once) throw true; once = true; } } catch (Xml_node::Nonexistent_attribute) { } }); }); } catch (bool result) { return result; } return false; } public: /** * Constructor */ Session_component(Genode::Rpc_entrypoint *ep, Genode::Allocator *md_alloc, const char *args, Genode::Rpc_entrypoint &device_pd_ep) : _ep(ep), _md_alloc(md_alloc, Genode::Arg_string::find_arg(args, "ram_quota").long_value(0)), _device_slab(&_md_alloc), _device_pd_ep(device_pd_ep), _label(args), _policy(_label) { /* non-pci devices */ _policy.for_each_sub_node("device", [&] (Genode::Xml_node device_node) { try { char policy_device[8]; device_node.attribute("name").value(policy_device, sizeof(policy_device)); enum { DOUBLET = false }; if (!find_dev_in_policy(policy_device, DOUBLET)) return; PERR("'%s' - device '%s' is part of more than one policy", _label.string(), policy_device); } catch (Genode::Xml_node::Nonexistent_attribute) { PERR("'%s' - device node misses a 'name' attribute", _label.string()); } throw Genode::Root::Unavailable(); }); /* pci devices */ _policy.for_each_sub_node("pci", [&] (Genode::Xml_node node) { enum { INVALID_CLASS = 0x1000000U }; unsigned class_sub_prog = INVALID_CLASS; using Genode::Xml_attribute; /** * Valid input is either a triple of 'bus', 'device', * 'function' attributes or a single 'class' attribute. * All other attribute names are traded as wrong. */ try { char alias_class[32]; node.attribute("class").value(alias_class, sizeof(alias_class)); class_sub_prog = class_subclass_prog(alias_class); if (class_sub_prog >= INVALID_CLASS) { PERR("'%s' - invalid 'class' attribute '%s'", _label.string(), alias_class); throw Genode::Root::Unavailable(); } } catch (Xml_attribute::Nonexistent_attribute) { } /* if we read a class attribute all is fine */ if (class_sub_prog < INVALID_CLASS) { /* sanity check that 'class' is the only attribute */ try { node.attribute(1); PERR("'%s' - attributes beside 'class' detected", _label.string()); throw Genode::Root::Unavailable(); } catch (Xml_attribute::Nonexistent_attribute) { } /* we have a class and it is the only attribute */ return; } /* no 'class' attribute - now check for valid bdf triple */ try { node.attribute(3); PERR("'%s' - invalid number of pci node attributes", _label.string()); throw Genode::Root::Unavailable(); } catch (Xml_attribute::Nonexistent_attribute) { } try { unsigned bus, device, function; node.attribute("bus").value(&bus); node.attribute("device").value(&device); node.attribute("function").value(&function); if ((bus >= Device_config::MAX_BUSES) || (device >= Device_config::MAX_DEVICES) || (function >= Device_config::MAX_FUNCTIONS)) throw Xml_attribute::Nonexistent_attribute(); enum { DOUBLET = false }; if (!find_dev_in_policy(bus, device, function, DOUBLET)) return; PERR("'%s' - device '%2x:%2x.%x' is part of more than " "one policy", _label.string(), bus, device, function); } catch (Xml_attribute::Nonexistent_attribute) { PERR("'%s' - invalid pci node attributes for bdf", _label.string()); } throw Genode::Root::Unavailable(); }); } /** * Destructor */ ~Session_component() { /* release all elements of the session's device list */ while (_device_list.first()) release_device(_device_list.first()->cap()); } void upgrade_ram_quota(long quota) { _md_alloc.upgrade(quota); } static void add_config_space(Genode::uint32_t bdf_start, Genode::uint32_t func_count, Genode::addr_t base) { using namespace Genode; Config_space * space = new (env()->heap()) Config_space(bdf_start, func_count, base); config_space_list().insert(space); } /** * Check whether msi usage was explicitly switched off */ bool msi_usage() { try { char mode[8]; _policy.attribute("irq_mode").value(mode, sizeof(mode)); if (!Genode::strcmp("nomsi", mode)) return false; } catch (Genode::Xml_node::Nonexistent_attribute) { } return true; } /*************************** ** PCI session interface ** ***************************/ Device_capability first_device(unsigned device_class, unsigned class_mask) { return next_device(Device_capability(), device_class, class_mask); } Device_capability next_device(Device_capability prev_device, unsigned device_class, unsigned class_mask) { /* * Create the interface to the PCI config space. * This involves the creation of I/O port sessions. */ Config_access config_access; /* lookup device component for previous device */ auto lambda = [&] (Device_component *prev) { /* * Start bus scanning after the previous device's location. * If no valid device was specified for 'prev_device', * start at the beginning. */ int bus = 0, device = 0, function = -1; if (prev) { Device_config config = prev->config(); bus = config.bus_number(); device = config.device_number(); function = config.function_number(); } /* * Scan buses for devices. * If no device is found, return an invalid capability. */ Device_config config; while (true) { function += 1; if (!_find_next(bus, device, function, &config, &config_access)) return Device_capability(); /* get new bdf values */ bus = config.bus_number(); device = config.device_number(); function = config.function_number(); /* if filter of driver don't match skip and continue */ if ((config.class_code() ^ device_class) & class_mask) continue; /* check that policy permit access to the matched device */ if (permit_device(bus, device, function, config.class_code())) break; } /* lookup if we have a extended pci config space */ Genode::addr_t config_space = lookup_config_space(config.bdf()); /* * A device was found. Create a new device component for the * device and return its capability. */ try { Device_component * dev = new (_device_slab) Device_component(config, config_space, _ep, this, &_md_alloc); /* if more than one driver uses the device - warn about */ if (bdf_in_use.get(Device_config::MAX_BUSES * bus + Device_config::MAX_DEVICES * device + function, 1)) PERR("Device %2x:%2x.%u is used by more than one " "driver - session '%s'.", bus, device, function, _label.string()); else bdf_in_use.set(Device_config::MAX_BUSES * bus + Device_config::MAX_DEVICES * device + function, 1); _device_list.insert(dev); return _ep->manage(dev); } catch (Genode::Allocator::Out_of_memory) { throw Out_of_metadata(); } }; return _ep->apply(prev_device, lambda); } void release_device(Device_capability device_cap) { Device_component * device; auto lambda = [&] (Device_component *d) { device = d; if (!device) return; unsigned const bus = device->config().bus_number(); unsigned const dev = device->config().device_number(); unsigned const func = device->config().function_number(); if (bdf_in_use.get(Device_config::MAX_BUSES * bus + Device_config::MAX_DEVICES * dev + func, 1)) bdf_in_use.clear(Device_config::MAX_BUSES * bus + Device_config::MAX_DEVICES * dev + func, 1); _device_list.remove(device); _ep->dissolve(device); }; /* lookup device component for previous device */ _ep->apply(device_cap, lambda); if (!device) return; if (device->config().valid()) destroy(_device_slab, device); else destroy(_md_alloc, device); } void assign_device(Device_component * device) { using namespace Genode; if (!device || !device->get_config_space().valid()) return; Io_mem_dataspace_capability io_mem = device->get_config_space(); if (!_device_pd.is_constructed()) _device_pd.construct(_device_pd_ep, _md_alloc, _resources.ram().cap()); if (!_device_pd->valid()) return; try { _device_pd->child.assign_pci(io_mem, device->config().bdf()); for (Rmrr *r = Rmrr::list()->first(); r; r = r->next()) { Io_mem_dataspace_capability rmrr_cap = r->match(device->config()); if (rmrr_cap.valid()) _device_pd->child.attach_dma_mem(rmrr_cap); } } catch (...) { PERR("assignment to device pd or of RMRR region failed"); } } /** * De-/Allocation of dma capable dataspaces */ typedef Genode::Ram_dataspace_capability Ram_capability; Ram_capability alloc_dma_buffer(Genode::size_t const size) { if (!_device_pd.is_constructed()) _device_pd.construct(_device_pd_ep, _md_alloc, _resources.ram().cap()); if (!_md_alloc.withdraw(size)) throw Out_of_metadata(); /* transfer ram quota to session specific ram session */ Genode::Ram_session * const rs = Genode::env()->ram_session(); if (rs->transfer_quota(_resources.ram().cap(), size)) { _md_alloc.upgrade(size); throw Fatal(); } enum { UPGRADE_QUOTA = 4096 }; /* allocate dataspace from session specific ram session */ Ram_capability ram_cap = Genode::retry( [&] () { return _resources.ram().alloc(size, Genode::UNCACHED); }, [&] () { if (!_md_alloc.withdraw(UPGRADE_QUOTA)) { /* role-back */ if (_resources.ram().transfer_quota(Genode::env()->ram_session_cap(), size)) throw Fatal(); _md_alloc.upgrade(size); throw Out_of_metadata(); } char buf[32]; Genode::snprintf(buf, sizeof(buf), "ram_quota=%u", UPGRADE_QUOTA); Genode::env()->parent()->upgrade(_resources.ram().cap(), buf); }); if (!_device_pd->valid()) return ram_cap; Genode::retry( [&] () { _device_pd->child.attach_dma_mem(ram_cap); }, [&] () { if (!_md_alloc.withdraw(UPGRADE_QUOTA)) { /* role-back */ _resources.ram().free(ram_cap); if (_resources.ram().transfer_quota(Genode::env()->ram_session_cap(), size)) throw Fatal(); _md_alloc.upgrade(size); throw Out_of_metadata(); } if (rs->transfer_quota(_resources.ram().cap(), UPGRADE_QUOTA)) throw Fatal(); Genode::Ram_connection &slave = _device_pd->policy->ram_slave(); if (_resources.ram().transfer_quota(slave.cap(), UPGRADE_QUOTA)) throw Fatal(); }); return ram_cap; } void free_dma_buffer(Ram_capability ram) { if (ram.valid()) _resources.ram().free(ram); } Device_capability device(String const &name) override; }; } class Platform::Root : public Genode::Root_component { private: Genode::Rpc_entrypoint _device_pd_ep; void _parse_report_rom(const char * acpi_rom) { using namespace Genode; Config_access config_access; Xml_node xml_acpi(acpi_rom); if (!xml_acpi.has_type("acpi")) throw 1; for (unsigned i = 0; i < xml_acpi.num_sub_nodes(); i++) { Xml_node node = xml_acpi.sub_node(i); if (node.has_type("bdf")) { uint32_t bdf_start = 0; uint32_t func_count = 0; addr_t base = 0; node.attribute("start").value(&bdf_start); node.attribute("count").value(&func_count); node.attribute("base").value(&base); Session_component::add_config_space(bdf_start, func_count, base); } if (node.has_type("irq_override")) { unsigned irq = 0xff; unsigned gsi = 0xff; unsigned flags = 0xff; node.attribute("irq").value(&irq); node.attribute("gsi").value(&gsi); node.attribute("flags").value(&flags); using Platform::Irq_override; Irq_override * o = new (env()->heap()) Irq_override(irq, gsi, flags); Irq_override::list()->insert(o); } if (node.has_type("rmrr")) { uint64_t mem_start, mem_end; node.attribute("start").value(&mem_start); node.attribute("end").value(&mem_end); if (node.num_sub_nodes() == 0) throw 2; Rmrr * rmrr = new (env()->heap()) Rmrr(mem_start, mem_end); Rmrr::list()->insert(rmrr); for (unsigned s = 0; s < node.num_sub_nodes(); s++) { Xml_node scope = node.sub_node(s); if (!scope.num_sub_nodes() || !scope.has_type("scope")) throw 3; unsigned bus, dev, func; scope.attribute("bus_start").value(&bus); for (unsigned p = 0; p < scope.num_sub_nodes(); p++) { Xml_node path = scope.sub_node(p); if (!path.has_type("path")) throw 4; path.attribute("dev").value(&dev); path.attribute("func").value(&func); Device_config bridge(bus, dev, func, &config_access); if (bridge.is_pci_bridge()) /* PCI bridge spec 3.2.5.3, 3.2.5.4 */ bus = bridge.read(&config_access, 0x19, Device::ACCESS_8BIT); } rmrr->add(new (env()->heap()) Rmrr::Bdf(bus, dev, func)); } } if (!node.has_type("routing")) continue; unsigned gsi; unsigned bridge_bdf; unsigned device; unsigned device_pin; node.attribute("gsi").value(&gsi); node.attribute("bridge_bdf").value(&bridge_bdf); node.attribute("device").value(&device); node.attribute("device_pin").value(&device_pin); /* check that bridge bdf is actually a valid device */ Device_config config((bridge_bdf >> 8 & 0xff), (bridge_bdf >> 3) & 0x1f, bridge_bdf & 0x7, &config_access); if (!config.valid()) continue; if (!config.is_pci_bridge() && bridge_bdf != 0) /** * If the bridge bdf has not a type header of a bridge in * the pci config space, then it should be the host bridge * device. The host bridge device need not to be * necessarily at 0:0.0, it may be on another location. The * irq routing information for the host bridge however * contain entries for the bridge bdf to be 0:0.0 - * therefore we override it here for the irq rerouting * information of host bridge devices. */ bridge_bdf = 0; Irq_routing * r = new (env()->heap()) Irq_routing(gsi, bridge_bdf, device, device_pin); Irq_routing::list()->insert(r); } } protected: Session_component *_create_session(const char *args) { try { return new (md_alloc()) Session_component(ep(), md_alloc(), args, _device_pd_ep); } catch (Genode::Session_policy::No_policy_defined) { PERR("Invalid session request, no matching policy for '%s'", Genode::Session_label(args).string()); throw Genode::Root::Unavailable(); } } void _upgrade_session(Session_component *s, const char *args) override { long ram_quota = Genode::Arg_string::find_arg(args, "ram_quota").long_value(0); s->upgrade_ram_quota(ram_quota); } public: /** * Constructor * * \param ep entry point to be used for serving the PCI session * and PCI device interface * \param md_alloc meta-data allocator for allocating PCI-session * components and PCI-device components */ Root(Genode::Rpc_entrypoint *ep, Genode::Allocator *md_alloc, const char *acpi_rom, Genode::Cap_connection &cap) : Genode::Root_component(ep, md_alloc), _device_pd_ep(&cap, STACK_SIZE, "device_pd_slave") { /* enforce initial bus scan */ bus_valid(); if (acpi_rom) { try { _parse_report_rom(acpi_rom); } catch (...) { PERR("PCI config space data could not be parsed."); } } } };