acpi: amend handling MMIO regions for ACPI tables

The handling of MMIO regions now supports more pathological cases with
weird cross references. Also, MMIO regions are releases after the
parsing is done.

Fixes #998
This commit is contained in:
Alexander Boettcher 2015-02-15 23:31:56 +01:00 committed by Christian Helmuth
parent 679ae1dd14
commit babe1d1026
3 changed files with 170 additions and 88 deletions

View File

@ -19,7 +19,9 @@
#include <io_mem_session/connection.h>
#include <pci_session/connection.h>
#include <pci_device/client.h>
#include "acpi.h"
#include "memory.h"
using namespace Genode;
@ -192,6 +194,13 @@ class Pci_config_space : public List<Pci_config_space>::Element
};
static Acpi::Memory & acpi_memory()
{
static Acpi::Memory _memory;
return _memory;
}
/**
* ACPI table wrapper that for mapping tables to this address space
*/
@ -200,35 +209,9 @@ class Table_wrapper
private:
addr_t _base; /* table base address */
Io_mem_connection *_io_mem; /* mapping connection */
Generic *_table; /* pointer to table header */
char _name[5]; /* table name */
/**
* Cleanup dynamically allocated memory
*/
void _cleanup()
{
if (_table)
env()->rm_session()->detach((uint8_t *)_table - _offset());
if (_io_mem)
destroy(env()->heap(), _io_mem);
}
/**
* Map table of 'size'
*/
void _map(size_t size)
{
_io_mem = new (env()->heap()) Io_mem_connection(_base - _offset(), size + _offset());
Io_mem_dataspace_capability io_ds = _io_mem->dataspace();
if (!io_ds.valid())
throw -1;
_table = (Generic *)((uint8_t *)env()->rm_session()->attach(io_ds, size + _offset()) + _offset());
}
/* return offset of '_base' to page boundary */
addr_t _offset() const { return (_base & 0xfff); }
@ -245,17 +228,12 @@ class Table_wrapper
char const *name() const { return _name; }
/**
* Copy table data to 'ptr'
* Determine maximal number of potential elements
*/
template <typename T>
T * copy_entries(T &count)
addr_t entry_count(T *)
{
addr_t size = _table->size - sizeof (Generic);
count = size / sizeof(T);
T * entries = new (env()->heap()) T [count];
memcpy(entries, _table + 1, size);
return entries;
return (_table->size - sizeof (Generic)) / sizeof(T);
}
/**
@ -344,20 +322,17 @@ class Table_wrapper
dmar->base, dmar->limit);
}
Table_wrapper(addr_t base) : _base(base), _io_mem(0), _table(0)
Table_wrapper(addr_t base) : _base(base), _table(0)
{
/*
* Try to map one page only, if table is on page boundary, map two pages
*/
size_t map_size = 0x1000 - _offset();
_map(map_size < 8 ? 0x1000 : map_size);
/* if table is on page boundary, map two pages, otherwise one page */
size_t const map_size = 0x1000UL - _offset() < 8 ? 0x1000UL : 1UL;
/* remap if table size is larger than current size */
if (_offset() + _table->size > 0x1000) {
size_t size = _table->size;
_cleanup();
_map(size);
}
/* make table header accessible */
_table = reinterpret_cast<Generic *>(acpi_memory().phys_to_virt(base, map_size));
/* table size is known now - make it complete accessible */
if (_offset() + _table->size > 0x1000UL)
acpi_memory().phys_to_virt(base, _table->size);
memset(_name, 0, 5);
memcpy(_name, _table->signature, 4);
@ -370,8 +345,6 @@ class Table_wrapper
throw -1;
}
}
~Table_wrapper() { _cleanup(); }
};
@ -844,12 +817,6 @@ class Element : public List<Element>::Element
}
}
virtual ~Element()
{
if (_name)
env()->heap()->free(_name, _name_len);
}
bool is_device() { return _type == SUB_DEVICE; }
bool is_device_name() { return _type == DEVICE_NAME; }
@ -870,6 +837,12 @@ class Element : public List<Element>::Element
public:
virtual ~Element()
{
if (_name)
env()->heap()->free(_name, _name_len);
}
/**
* Accessors
*/
@ -897,6 +870,30 @@ class Element : public List<Element>::Element
return &_list;
}
static void clean_list()
{
unsigned long freed_up = 0;
Element * element = list()->first();
while (element) {
if (element->is_device() || (element->_name_len == 4 &&
!memcmp(element->_name, "_PIC", 4))) {
element = element->next();
continue;
}
freed_up += sizeof(*element) + element->_name ? element->_name_len : 0;
Element * next = element->next();
Element::list()->remove(element);
destroy(env()->heap(), element);
element = next;
}
if (verbose)
PDBG("Freeing up memory of elements - %lu bytes", freed_up);
}
/**
* Return list of PCI information for this element
*/
@ -1188,32 +1185,21 @@ class Acpi_table
if (xsdt && sizeof(addr_t) != sizeof(uint32_t)) {
/* running 64bit and xsdt is valid */
addr_t entries_count;
addr_t * entries;
{
Table_wrapper table(xsdt);
entries = table.copy_entries(entries_count);
}
_parse_tables(entries, entries_count);
if (entries)
env()->heap()->free(entries, 0);
Table_wrapper table(xsdt);
uint64_t * entries = reinterpret_cast<uint64_t *>(table.table() + 1);
_parse_tables(entries, table.entry_count(entries));
} else {
/* running (32bit) or (64bit and xsdt isn't valid) */
uint32_t entries_count;
uint32_t * entries;
{
Table_wrapper table(rsdt);
entries = table.copy_entries(entries_count);
}
_parse_tables(entries, entries_count);
if (entries)
env()->heap()->free(entries, 0);
Table_wrapper table(rsdt);
uint32_t * entries = reinterpret_cast<uint32_t *>(table.table() + 1);
_parse_tables(entries, table.entry_count(entries));
}
/* free up memory of elements not of any use */
Element::clean_list();
/* free up io memory */
acpi_memory().free_io_memory();
}
};

View File

@ -14,33 +14,27 @@
#ifndef _ACPI_H_
#define _ACPI_H_
#include <base/thread.h>
#include <pci_session/capability.h>
struct Acpi
namespace Acpi
{
/**
* Constructor
*/
Acpi();
/**
* Generate config file for pci_drv containing pointers to the
* extended PCI config space (since PCI Express)
*/
static void create_pci_config_file(char * config_space,
void create_pci_config_file(char * config_space,
Genode::size_t config_space_max);
/**
* Rewrite PCI-config space with GSIs found in ACPI tables.
*/
static void configure_pci_devices(Pci::Session_capability &session);
void configure_pci_devices(Pci::Session_capability &session);
/**
* Return override GSI for IRQ
*/
static unsigned override(unsigned irq, unsigned *mode);
};
unsigned override(unsigned irq, unsigned *mode);
}
#endif /* _ACPI_H_ */

View File

@ -0,0 +1,102 @@
/*
* \brief Internal acpi io memory management
* \author Alexander Boettcher
* \date 2015-02-16
*/
/*
* Copyright (C) 2015 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.
*/
#ifndef _MEMORY_H_
#define _MEMORY_H_
#include <base/allocator_avl.h>
#include <rm_session/connection.h>
namespace Acpi { class Memory; }
class Acpi::Memory
{
private:
class Io_mem : public Genode::List<Io_mem>::Element
{
private:
Genode::Io_mem_connection _io_mem;
public:
Io_mem(Genode::addr_t phys) : _io_mem(phys, 0x1000UL) { }
Genode::Io_mem_dataspace_capability dataspace()
{
return _io_mem.dataspace();
}
};
Genode::addr_t const ACPI_REGION_SIZE_LOG2;
Genode::Rm_connection _rm_acpi;
Genode::addr_t const _acpi_base;
Genode::Allocator_avl _range;
Genode::List<Io_mem> _io_mem_list;
public:
Memory()
:
/* 1 GB range */
ACPI_REGION_SIZE_LOG2(30),
_rm_acpi(~0UL, 1UL << ACPI_REGION_SIZE_LOG2),
_acpi_base(Genode::env()->rm_session()->attach(_rm_acpi.dataspace())),
_range(Genode::env()->heap())
{
_range.add_range(0, 1UL << ACPI_REGION_SIZE_LOG2);
}
Genode::addr_t phys_to_virt(Genode::addr_t const phys, Genode::addr_t const p_size)
{
using namespace Genode;
/* 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) {
PERR("acpi table out of range - 0x%lx not in [0x%lx,0x%lx)",
phys, high, high + (1UL << ACPI_REGION_SIZE_LOG2) - 1);
throw -1;
}
addr_t const phys_aligned = phys & _align_mask(12);
addr_t const size_aligned = align_addr(p_size + (phys & _align_offset(12)), 12);
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).is_ok())
continue;
/* allocate acpi page as io memory */
Io_mem *mem = new (Genode::env()->heap()) Io_mem(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);
}
return _acpi_base + (phys & _align_offset(ACPI_REGION_SIZE_LOG2));
}
void free_io_memory()
{
while (Io_mem * io_mem = _io_mem_list.first()) {
_io_mem_list.remove(io_mem);
destroy(Genode::env()->heap(), io_mem);
}
}
};
#endif /* _MEMORY_H_ */