genode/repos/os/src/drivers/platform/spec/x86/pci_session_component.h
Norman Feske e44f65f3b2 core: RAM service based on 'Session_object'
This patch reworks the implementation of core's RAM service to make use
of the 'Session_object' and to remove the distinction between the
"metadata" quota and the managed RAM quota. With the new implementation,
the session implicitly allocates its metadata from its own account. So
there is not need to handle 'Out_of_metadata' and 'Quota_exceeded' via
different exceptions. Instead, the new version solely uses the
'Out_of_ram' exception.

Furthermore, the 'Allocator::Out_of_memory' exception has become an alias
for 'Out_of_ram', which simplifies the error handling.

Issue #2398
2017-05-31 13:16:06 +02:00

1273 lines
35 KiB
C++

/*
* \brief platform session component
* \author Norman Feske
* \date 2008-01-28
*/
/*
* Copyright (C) 2008-2017 Genode Labs GmbH
*
* This file is part of the Genode OS framework, which is distributed
* under the terms of the GNU Affero General Public License version 3.
*/
#pragma once
/* base */
#include <base/allocator_guard.h>
#include <base/rpc_server.h>
#include <base/tslab.h>
#include <base/attached_rom_dataspace.h>
#include <ram_session/connection.h>
#include <root/component.h>
#include <util/mmio.h>
#include <util/retry.h>
#include <util/reconstructible.h>
/* os */
#include <io_mem_session/connection.h>
#include <os/ram_session_guard.h>
#include <os/session_policy.h>
#include <platform_session/platform_session.h>
/* local */
#include "pci_device_component.h"
#include "pci_config_access.h"
#include "pci_device_pd_ipc.h"
#include "device_pd.h"
namespace Platform {
unsigned short bridge_bdf(unsigned char bus);
class Pci_buses;
class Ram_dataspace;
class Rmrr;
class Root;
class Session_component;
}
class Platform::Ram_dataspace : public Genode::List<Ram_dataspace>::Element {
private:
Genode::Ram_dataspace_capability _cap;
public:
Ram_dataspace(Genode::Ram_dataspace_capability c) : _cap(c) { }
bool match(const Genode::Ram_dataspace_capability &cap) const {
return cap.local_name() == _cap.local_name(); }
};
class Platform::Rmrr : public Genode::List<Platform::Rmrr>::Element
{
public:
class Bdf : public Genode::List<Bdf>::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 const _start, _end;
Genode::Io_mem_dataspace_capability _cap;
Genode::List<Bdf> _bdf_list;
Genode::Constructible<Genode::Io_mem_connection> _io_mem;
public:
Rmrr(Genode::uint64_t start, Genode::uint64_t end)
: _start(start), _end(end)
{ }
Genode::Io_mem_dataspace_capability match(Genode::Env &env,
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;
_io_mem.construct(env, _start, _end - _start + 1);
_cap = _io_mem->dataspace();
return _cap;
}
return Genode::Io_mem_dataspace_capability();
}
void add(Bdf * bdf) { _bdf_list.insert(bdf); }
static Genode::List<Rmrr> *list()
{
static Genode::List<Rmrr> _list;
return &_list;
}
};
class Platform::Pci_buses
{
private:
Genode::Bit_array<Device_config::MAX_BUSES> _valid;
void scan_bus(Config_access &config_access, Genode::Allocator &heap,
unsigned char bus = 0);
bool _bus_valid(int bus)
{
if (bus >= Device_config::MAX_BUSES)
return false;
return _valid.get(bus, 1);
}
public:
Pci_buses(Genode::Env &env, Genode::Allocator &heap)
{
Config_access c(env);
scan_bus(c, heap);
}
/**
* 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;
}
};
class Platform::Session_component : public Genode::Rpc_object<Session>
{
private:
Genode::Env &_env;
Genode::Rpc_entrypoint &_device_pd_ep;
Genode::Attached_rom_dataspace &_config;
Genode::Ram_session_guard _env_ram;
Genode::Ram_session_capability _env_ram_cap;
Genode::Region_map &_local_rm;
Genode::Heap _md_alloc;
Genode::Session_label const _label;
Genode::Session_policy const _policy { _label, _config.xml() };
Genode::List<Device_component> _device_list;
Platform::Pci_buses &_pci_bus;
Genode::Heap &_global_heap;
bool _no_device_pd = false;
/**
* Registry of RAM dataspaces allocated by the session
*/
Genode::List<Platform::Ram_dataspace> _ram_caps;
void _insert(Genode::Ram_dataspace_capability cap) {
_ram_caps.insert(new (_md_alloc) Platform::Ram_dataspace(cap)); }
bool _remove(Genode::Ram_dataspace_capability cap)
{
for (Platform::Ram_dataspace *ds = _ram_caps.first(); ds;
ds = ds->next()) {
if (!ds->match(cap))
continue;
_ram_caps.remove(ds);
destroy(_md_alloc, ds);
return true;
}
return false;
}
/**
* Session-local RAM account
*
* Restrict physical address to 3G on 32bit, 4G on 64bit
*/
Genode::Ram_connection _ram { _env, _label.string(), 0,
(sizeof(void *) == 4) ? 0xc0000000UL : 0x100000000UL };
/*
* Associate session RAM session with platform_drv _ram session and
* equip RAM session with initial quota to account for core-internal
* allocation meta-data overhead.
*/
void _init_ram()
{
_ram.ref_account(_env_ram_cap);
enum { OVERHEAD = 4096 };
try { _env_ram.transfer_quota(_ram, Genode::Ram_quota{OVERHEAD}); }
catch (...) { throw Genode::Insufficient_ram_quota(); }
}
bool const _ram_initialized = (_init_ram(), true);
/**
* Deduce specified amount of quota from an allocator guard, or throw
* an 'Out_of_metadata' exception if the guard's quota is depleted.
*/
struct Quota_reservation
{
Genode::Ram_session_guard &guard;
Genode::size_t const amount;
Quota_reservation(Genode::Ram_session_guard &guard,
Genode::size_t amount)
: guard(guard), amount(amount)
{
if (!guard.withdraw(amount))
throw Out_of_metadata();
}
~Quota_reservation() noexcept(false)
{
if (!guard.revert_withdraw(amount))
throw Fatal();
}
};
class Device_pd
{
public:
class Startup_failed : Genode::Exception { };
private:
enum { RAM_QUOTA = 190 * 4096 };
Quota_reservation const _reservation;
Device_pd_policy _policy;
Genode::Child _child;
void _check_child_started_up() const {
if (!_child.active())
throw Startup_failed(); }
bool const _active = (_check_child_started_up(), true);
Genode::Slave::Connection<Device_pd_connection> _connection;
public:
/**
* Constructor
*
* \throw Out_of_metadata session RAM does not suffice
* for creating device PD
* \throw Startup_failed child could not be started
* \throw Parent::Service_denied by 'Slave::Connection'
*/
Device_pd(Genode::Region_map &local_rm,
Genode::Rpc_entrypoint &ep,
Genode::Ram_session_guard &ref_ram,
Genode::Ram_session_capability ref_ram_cap,
Genode::Session_label const &label)
try :
_reservation(ref_ram, RAM_QUOTA),
_policy(ep, local_rm,
ref_ram, ref_ram_cap, Genode::Ram_quota{RAM_QUOTA}, label),
_child(local_rm, ep, _policy),
_connection(_policy, Genode::Slave::Args())
{ }
/* thrown by 'Quota_reservation' */
catch (Out_of_metadata) { throw; }
/* thrown by 'Device_pd_policy' or 'Child' */
catch (Genode::Out_of_ram) { throw Out_of_metadata(); }
/* throw by 'Slave::Connection' */
catch (Genode::Insufficient_ram_quota) { throw Out_of_metadata(); }
Device_pd_client &session() { return _connection; }
Genode::Ram_session_capability ram_session_cap() {
return _child.ram_session_cap(); }
};
Device_pd *_device_pd = nullptr;
/**
* Free device PD at session destruction
*/
struct Device_pd_guard
{
Session_component &session;
~Device_pd_guard() {
if (session._device_pd)
Genode::destroy(session._md_alloc, session._device_pd); }
} _device_pd_guard { *this };
/**
* Attempt to initialize device PD
*
* \throw Out_of_metadata session quota does not suffice to spawn the
* device PD
*/
void _try_init_device_pd()
{
if (_device_pd || _no_device_pd)
return;
try {
_device_pd = new (_md_alloc)
Device_pd(_local_rm, _device_pd_ep, _env_ram,
_env_ram_cap, _label);
}
/* thrown by '_md_alloc' */
catch (Genode::Out_of_ram) { throw Out_of_metadata(); }
/* thrown by 'Device_pd' */
catch (Out_of_metadata) { throw; }
catch (...) {
Genode::warning("PCI device protection domain for IOMMU support "
"is not available");
_no_device_pd = true;
}
}
enum { MAX_PCI_DEVICES = Device_config::MAX_BUSES *
Device_config::MAX_DEVICES *
Device_config::MAX_FUNCTIONS };
static Genode::Bit_array<MAX_PCI_DEVICES> bdf_in_use;
/**
* List containing extended PCI config space information
*/
static Genode::List<Config_space> &config_space_list() {
static Genode::List<Config_space> 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().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();
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::Env &env,
Genode::Attached_rom_dataspace &config,
Genode::Rpc_entrypoint &device_pd_ep,
Platform::Pci_buses &buses,
Genode::Heap &global_heap,
char const *args)
:
_env(env), _device_pd_ep(device_pd_ep),
_config(config),
_env_ram(env.ram(), env.ram_session_cap(),
Genode::Arg_string::find_arg(args, "ram_quota").long_value(0)),
_env_ram_cap(env.ram_session_cap()),
_local_rm(env.rm()),
_md_alloc(_env_ram, env.rm()),
_label(Genode::label_from_args(args)),
_pci_bus(buses), _global_heap(global_heap)
{
/* 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;
Genode::error("'", _label, "' - device "
"'", Genode::Cstring(policy_device), "' "
"is part of more than one policy");
} catch (Genode::Xml_node::Nonexistent_attribute) {
Genode::error("'", _label, "' - device node "
"misses a 'name' attribute");
}
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) {
Genode::error("'", _label, "' - invalid 'class' ",
"attribute '", Genode::Cstring(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);
Genode::error("'", _label, "' - attributes beside 'class' detected");
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);
Genode::error("'", _label, "' - "
"invalid number of pci node attributes");
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;
Genode::error("'", _label, "' - device '",
Genode::Hex(bus), ":",
Genode::Hex(device), ".", function, "' "
"is part of more than one policy");
} catch (Xml_attribute::Nonexistent_attribute) {
Genode::error("'", _label, "' - "
"invalid pci node attributes for bdf");
}
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) { _env_ram.upgrade(quota); }
static void add_config_space(Genode::uint32_t bdf_start,
Genode::uint32_t func_count,
Genode::addr_t base,
Genode::Allocator &heap)
{
using namespace Genode;
Config_space * space =
new (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) override {
return next_device(Device_capability(), device_class, class_mask); }
Device_capability next_device(Device_capability prev_device,
unsigned device_class,
unsigned class_mask) override
{
/*
* Create the interface to the PCI config space.
* This involves the creation of I/O port sessions.
*/
Config_access config_access(_env);
/* 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 (!_pci_bus.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 (_md_alloc)
Device_component(_env, config, config_space, *this,
_md_alloc, _global_heap);
/* 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))
Genode::error("Device ",
Genode::Hex(bus), ":",
Genode::Hex(device), ".", function, " "
"is used by more than one driver - "
"session '", _label, "'.");
else
bdf_in_use.set(Device_config::MAX_BUSES * bus +
Device_config::MAX_DEVICES * device +
function, 1);
_device_list.insert(dev);
return _env.ep().rpc_ep().manage(dev);
} catch (Genode::Allocator::Out_of_memory) {
throw Out_of_metadata();
}
};
return _env.ep().rpc_ep().apply(prev_device, lambda);
}
void release_device(Device_capability device_cap) override
{
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);
_env.ep().rpc_ep().dissolve(device);
};
/* lookup device component for previous device */
_env.ep().rpc_ep().apply(device_cap, lambda);
if (!device) return;
if (device->config().valid())
destroy(_md_alloc, 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();
_try_init_device_pd();
if (!_device_pd)
return;
try {
_device_pd->session().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(_env, device->config());
if (rmrr_cap.valid())
_device_pd->session().attach_dma_mem(rmrr_cap);
}
} catch (...) {
Genode::error("assignment to device pd or of RMRR region failed");
}
}
/**
* De-/Allocation of dma capable dataspaces
*/
typedef Genode::Ram_dataspace_capability Ram_capability;
/**
* Helper method for rollback
*/
void _rollback(Genode::size_t const size,
Genode::Ram_dataspace_capability const ram_cap = Genode::Ram_dataspace_capability(),
bool const throw_oom = true)
{
if (ram_cap.valid())
_ram.free(ram_cap);
if (_env_ram.revert_transfer_quota(_ram, size))
throw Fatal();
if (throw_oom)
throw Out_of_metadata();
}
Genode::Ram_dataspace_capability alloc_dma_buffer(Genode::size_t const size) override
{
/*
* We always try to create the device PD first because
* otherwise we might consume the requested size multiple
* times which breaks our accounting.
*/
_try_init_device_pd();
/* transfer ram quota to session specific ram session */
try { _env_ram.transfer_quota(_ram, Genode::Ram_quota{size}); }
catch (Genode::Out_of_ram) { throw Out_of_metadata(); }
catch (...) { throw Fatal(); }
enum { UPGRADE_QUOTA = 4096 };
/* allocate dataspace from session specific ram session */
Ram_capability ram_cap = Genode::retry<Genode::Out_of_ram>(
[&] () {
try {
return _ram.alloc(size, Genode::UNCACHED);
}
catch (Genode::Out_of_ram) {
if (!_env_ram.withdraw(UPGRADE_QUOTA))
_rollback(size);
throw;
}
},
[&] () {
/*
* This condition is mostly triggered when the session
* specific ram session is low on quota and it cannot
* process the request due to book-keeping overhead.
* It is therefore enough to increase the quota in
* UPGRADE_QUOTA steps.
*/
try { _env_ram.transfer_quota(_ram, Genode::Ram_quota{UPGRADE_QUOTA}); }
catch (...) { throw Genode::Out_of_ram(); }
});
if (!ram_cap.valid())
return ram_cap;
if (_device_pd) {
Genode::retry<Genode::Rm_session::Out_of_metadata>(
[&] () { _device_pd->session().attach_dma_mem(ram_cap); },
[&] () {
if (!_env_ram.withdraw(UPGRADE_QUOTA))
_rollback(size, ram_cap);
using namespace Genode;
try { _env_ram.transfer_quota(_ram, Ram_quota{UPGRADE_QUOTA}); }
catch (...) { throw Fatal(); }
try { _ram.transfer_quota(_device_pd->ram_session_cap(),
Ram_quota{UPGRADE_QUOTA}); }
catch (...) { throw Fatal(); }
});
}
try { _insert(ram_cap); }
catch (Genode::Allocator::Out_of_memory) {
_rollback(size, ram_cap); }
return ram_cap;
}
void free_dma_buffer(Genode::Ram_dataspace_capability ram_cap) override
{
if (!ram_cap.valid() || !_remove(ram_cap))
return;
Genode::size_t size = Genode::Dataspace_client(ram_cap).size();
_rollback(size, ram_cap, false);
}
Device_capability device(String const &name) override;
};
class Platform::Root : public Genode::Root_component<Session_component>
{
private:
struct Fadt {
Genode::uint32_t features = 0, reset_type = 0, reset_value = 0;
Genode::uint64_t reset_addr = 0;
/* Table 5-35 Fixed ACPI Description Table Fixed Feature Flags */
struct Features : Genode::Register<32> {
struct Reset : Bitfield<10, 1> { };
};
/* ACPI spec - 5.2.3.2 Generic Address Structure */
struct Gas : Genode::Register<32>
{
struct Address_space : Bitfield <0, 8> {
enum { SYSTEM_IO = 1 };
};
struct Access_size : Bitfield<24,8> {
enum { UNDEFINED = 0, BYTE = 1, WORD = 2, DWORD = 3, QWORD = 4};
};
};
} fadt;
Genode::Env &_env;
Genode::Attached_rom_dataspace &_config;
Genode::Rpc_entrypoint _device_pd_ep;
Genode::Heap _heap { _env.ram(), _env.rm() };
Platform::Pci_buses _buses { _env, _heap };
void _parse_report_rom(Genode::Env &env, const char * acpi_rom)
{
using namespace Genode;
Config_access config_access(env);
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, _heap);
}
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 (_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 (_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.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 (_heap) Rmrr::Bdf(bus, dev, func));
}
}
if (node.has_type("fadt")) {
node.attribute("features").value(&fadt.features);
node.attribute("reset_type").value(&fadt.reset_type);
node.attribute("reset_addr").value(&fadt.reset_addr);
node.attribute("reset_value").value(&fadt.reset_value);
}
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.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 (_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(_env, _config, _device_pd_ep, _buses, _heap, args);
}
catch (Genode::Session_policy::No_policy_defined) {
Genode::error("Invalid session request, no matching policy for ",
"'", Genode::label_from_args(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::Env &env, Genode::Allocator &md_alloc,
Genode::Attached_rom_dataspace &config,
const char *acpi_rom)
:
Genode::Root_component<Session_component>(&env.ep().rpc_ep(),
&md_alloc),
_env(env), _config(config),
_device_pd_ep(&env.pd(), STACK_SIZE, "device_pd_slave")
{
if (acpi_rom) {
try {
_parse_report_rom(env, acpi_rom);
} catch (...) {
Genode::error("PCI config space data could not be parsed.");
}
}
}
void system_reset()
{
const bool io_port_space = (Fadt::Gas::Address_space::get(fadt.reset_type) == Fadt::Gas::Address_space::SYSTEM_IO);
if (!io_port_space)
return;
Config_access config_access(_env);
const unsigned raw_access_size = Fadt::Gas::Access_size::get(fadt.reset_type);
const bool reset_support = config_access.reset_support(fadt.reset_addr, raw_access_size);
if (!reset_support)
return;
const bool feature_reset = Fadt::Features::Reset::get(fadt.features);
if (!feature_reset) {
Genode::warning("system reset failed - feature not supported");
return;
}
Device::Access_size access_size = Device::ACCESS_8BIT;
unsigned raw_size = Fadt::Gas::Access_size::get(fadt.reset_type);
switch (raw_size) {
case Fadt::Gas::Access_size::WORD:
access_size = Device::ACCESS_16BIT;
break;
case Fadt::Gas::Access_size::DWORD:
access_size = Device::ACCESS_32BIT;
break;
case Fadt::Gas::Access_size::QWORD:
Genode::error("system reset failed - unsupported access size");
return;
default:
break;
}
config_access.system_reset(fadt.reset_addr, fadt.reset_value,
access_size);
/* if we are getting here - the reset failed */
Genode::warning("system reset failed");
}
};