acpi_drv: reduce capability quota requirements

Map ACPI tables entirely as one dataspaces (not page-by-page). Note, the
current approach does only merge overlapping but not consecutive I/O
memory regions, which would reduce the amount of capabilities required
even further.

Fixes #3495
This commit is contained in:
Christian Helmuth 2019-09-17 16:23:42 +02:00
parent 5c25e0bdb0
commit 11a7ac0536
2 changed files with 151 additions and 69 deletions

View File

@ -469,15 +469,11 @@ class Table_wrapper
Table_wrapper(Acpi::Memory &memory, addr_t base)
: _base(base), _table(0)
{
/* if table is on page boundary, map two pages, otherwise one page */
size_t const map_size = 0x1000UL - _offset() < 8 ? 0x1000UL : 1UL;
/* make table header accessible */
_table = reinterpret_cast<Generic *>(memory.phys_to_virt(base, map_size));
_table = reinterpret_cast<Generic *>(memory.map_region(base, 8));
/* table size is known now - make it complete accessible */
if (_offset() + _table->size > 0x1000UL)
memory.phys_to_virt(base, _table->size);
/* table size is known now - make it completely accessible (in place) */
memory.map_region(base, _table->size);
memset(_name, 0, 5);
memcpy(_name, _table->signature, 4);

View File

@ -20,97 +20,183 @@
#include <rm_session/connection.h>
#include <region_map/client.h>
namespace Acpi { class Memory; }
namespace Acpi {
using namespace Genode;
class Memory;
}
class Acpi::Memory
{
public:
struct Unsupported_range { };
private:
class Io_mem : public Genode::List<Io_mem>::Element
/*
* We wrap the connection into Constructible to prevent a "accessible
* non-virtual destructor" compiler error with Allocator_avl_base::Block.
*/
struct Io_mem
{
private:
Genode::Io_mem_connection _io_mem;
struct Region
{
addr_t _base;
size_t _size;
public:
Io_mem(Genode::Env &env, Genode::addr_t phys)
: _io_mem(env, phys, 0x1000UL) { }
Genode::Io_mem_dataspace_capability dataspace()
static addr_t _base_align(addr_t base)
{
return _io_mem.dataspace();
return base & ~0xfffUL;
}
static addr_t _size_align(addr_t base, size_t size)
{
return align_addr(base + (size - 1) - _base_align(base), 12);
}
Region(addr_t base, size_t size)
:
_base(_base_align(base)),
_size(_size_align(base, size))
{ }
addr_t base() const { return _base; }
addr_t last() const { return _base + (_size - 1); }
size_t size() const { return _size; }
bool contains(Region const &o) const
{
return o.base() >= base() && o.last() <= last();
}
void print(Output &o) const
{
Genode::print(o, Hex_range<addr_t>(_base, _size));
}
} region;
Constructible<Io_mem_connection> connection { };
Io_mem(Env &env, Region region) : region(region)
{
connection.construct(env, region.base(), region.size());
}
};
Genode::Env &_env;
Genode::addr_t const ACPI_REGION_SIZE_LOG2;
Genode::Rm_connection _rm;
Genode::Region_map_client _rm_acpi;
Genode::addr_t const _acpi_base;
Genode::Allocator &_heap;
Genode::Allocator_avl _range;
Genode::List<Io_mem> _io_mem_list { };
static constexpr unsigned long
ACPI_REGION_SIZE_LOG2 = 30, /* 1 GiB range */
ACPI_REGION_SIZE = 1UL << ACPI_REGION_SIZE_LOG2;
Env &_env;
Allocator &_heap;
Rm_connection _rm { _env };
Region_map_client _acpi_window { _rm.create(ACPI_REGION_SIZE) };
addr_t const _acpi_base { _env.rm().attach(_acpi_window.dataspace()) };
Constructible<Io_mem::Region> _io_region { };
addr_t _acpi_ptr(addr_t base) const
{
/* virtual address inside the mapped ACPI window */
return _acpi_base + (base - _io_region->base());
}
Allocator_avl_tpl<Io_mem> _range { &_heap };
public:
Memory(Genode::Env &env, Genode::Allocator &heap)
:
_env(env),
/* 1 GB range */
ACPI_REGION_SIZE_LOG2(30),
_rm(env),
_rm_acpi(_rm.create(1UL << ACPI_REGION_SIZE_LOG2)),
_acpi_base(env.rm().attach(_rm_acpi.dataspace())),
_heap(heap),
_range(&_heap)
Memory(Env &env, Allocator &heap) : _env(env), _heap(heap)
{
_range.add_range(0, 1UL << ACPI_REGION_SIZE_LOG2);
_range.add_range(0, ~0UL);
}
Genode::addr_t phys_to_virt(Genode::addr_t const phys, Genode::addr_t const p_size)
addr_t map_region(addr_t const req_base, addr_t const req_size)
{
using namespace Genode;
/*
* The first caller sets the upper physical bits of addresses and,
* thereby, determines the valid range of addresses.
*/
/* the first caller sets the upper physical bits of addresses */
static addr_t const high = phys & _align_mask(ACPI_REGION_SIZE_LOG2);
/* sanity check that physical address is in range we support */
if ((phys & _align_mask(ACPI_REGION_SIZE_LOG2)) != high) {
addr_t const end = high + (1UL << ACPI_REGION_SIZE_LOG2) - 1;
error("acpi table out of range - ", Hex(phys), " "
"not in ", Hex_range<addr_t>(high, end - high));
throw -1;
if (!_io_region.constructed()) {
_io_region.construct(req_base & _align_mask(ACPI_REGION_SIZE_LOG2),
ACPI_REGION_SIZE);
}
addr_t const phys_aligned = phys & _align_mask(12);
addr_t const size_aligned = align_addr(p_size + (phys & _align_offset(12)), 12);
/* requested region of I/O memory */
Io_mem::Region loop_region { req_base, req_size };
for (addr_t size = 0; size < size_aligned; size += 0x1000UL) {
addr_t const low = (phys_aligned + size) &
_align_offset(ACPI_REGION_SIZE_LOG2);
if (!_range.alloc_addr(0x1000UL, low).ok())
continue;
/* allocate acpi page as io memory */
Io_mem *mem = new (_heap) Io_mem(_env, phys_aligned + size);
/* attach acpi page to this process */
_rm_acpi.attach_at(mem->dataspace(), low, 0x1000UL);
/* add to list to free when parsing acpi table is done */
_io_mem_list.insert(mem);
/* check that physical region fits into supported range */
if (!_io_region->contains(loop_region)) {
error("acpi table out of range - ", loop_region, " not in ", *_io_region);
throw Unsupported_range();
}
return _acpi_base + (phys & _align_offset(ACPI_REGION_SIZE_LOG2));
/* early return if the region is already mapped */
if (Io_mem *m = _range.metadata((void *)req_base)) {
if (m->region.contains(loop_region)) {
return _acpi_ptr(req_base);
}
}
/*
* We iterate over the requested region looking for collisions with
* existing mappings. On a collision, we extend the requested range
* to comprise also the existing mapping and destroy the mapping.
* Finally, we request the compound region as on I/O memory
* mapping.
*
* Note, this approach unfortunately does not merge consecutive
* regions.
*/
addr_t loop_offset = 0;
while (loop_offset < loop_region.size()) {
void * const addr = (void *)(loop_region.base() + loop_offset);
if (Io_mem *m = _range.metadata(addr)) {
addr_t const region_base = m->region.base();
addr_t const region_size = m->region.size();
addr_t const compound_base = min(loop_region.base(), region_base);
addr_t const compound_end = max(loop_region.base() + loop_region.size(),
region_base + region_size);
m->~Io_mem();
_range.free((void *)region_base);
/* now start over */
loop_region = Io_mem::Region(compound_base, compound_end - compound_base);
loop_offset = 0;
}
loop_offset += 0x1000;
}
/* allocate ACPI range as I/O memory */
_range.alloc_addr(loop_region.size(), loop_region.base());
_range.construct_metadata((void *)loop_region.base(), _env, loop_region);
/*
* We attach the I/O memory dataspace into a virtual-memory window,
* which starts at _io_region.base(). Therefore, the attachment
* address is the offset of loop_region.base() from
* _io_region.base().
*/
_acpi_window.attach_at(
_range.metadata((void *)loop_region.base())->connection->dataspace(),
loop_region.base() - _io_region->base(), loop_region.size());
return _acpi_ptr(req_base);
}
void free_io_memory()
{
while (Io_mem * io_mem = _io_mem_list.first()) {
_io_mem_list.remove(io_mem);
destroy(_heap, io_mem);
}
Genode::addr_t out_addr;
while (_range.any_block_addr(&out_addr))
addr_t out_addr = 0;
while (_range.any_block_addr(&out_addr)) {
_range.metadata((void *)out_addr)->~Io_mem();
_range.free((void *)out_addr);
}
}
};