From 7e00ef96eea57dbc6b0f805f15dea3269a686822 Mon Sep 17 00:00:00 2001 From: Sebastian Sumpf Date: Sat, 25 Feb 2012 21:40:23 +0100 Subject: [PATCH] ACPI: GSI parser, fix #34 Read GSIs from ACPI tables and rewrite PCI-config space IRQs --- os/src/drivers/acpi/README | 35 + os/src/drivers/acpi/acpi.cc | 1073 +++++++++++++++++++++++++++++ os/src/drivers/acpi/acpi.h | 31 + os/src/drivers/acpi/main.cc | 155 +++++ os/src/drivers/acpi/x86/target.mk | 9 + 5 files changed, 1303 insertions(+) create mode 100644 os/src/drivers/acpi/README create mode 100644 os/src/drivers/acpi/acpi.cc create mode 100644 os/src/drivers/acpi/acpi.h create mode 100644 os/src/drivers/acpi/main.cc create mode 100644 os/src/drivers/acpi/x86/target.mk diff --git a/os/src/drivers/acpi/README b/os/src/drivers/acpi/README new file mode 100644 index 000000000..c9fc7ab91 --- /dev/null +++ b/os/src/drivers/acpi/README @@ -0,0 +1,35 @@ +This directory contains an implementation of a simple ACPI parser + +Behavior +-------- + +This server should be used when using a kernel (like Fiasco.OC or Nova) that +takes advantage of x86's APIC. The server traverses the ACPI tables and sets the +interrupt line of devices within the PCI config space to the GSIs found in the +ACPI tables. + +Usage +----- + +Start the 'acpi_drv' in your Genode environment. Do not start the 'pci_drv' +since this will be used as a slave of the 'acpi_drv'. You still must load the +'pci_drv' in your boot loader. + +Configuration snipped: + +! +! +! +! +! +! +! +! +! + +Limitations and known issues +---------------------------- + +Currently there is no interface to set the interrupt mode for Irq_sessions +(e.g., level or edge triggered). This is required by Fiasco.OCs kernel +interface. We regard this as future work. diff --git a/os/src/drivers/acpi/acpi.cc b/os/src/drivers/acpi/acpi.cc new file mode 100644 index 000000000..47052f03c --- /dev/null +++ b/os/src/drivers/acpi/acpi.cc @@ -0,0 +1,1073 @@ +/* + * \brief ACPI parsing and PCI rewriting code + * \author Sebastian Sumpf + * \date 2012-02-25 + * + * This code parses the DSDT and SSDT-ACPI tables and extracts the PCI-bridge + * to GSI interrupt mappings as described by "ATARE: ACPI Tables and Regular + * Expressions, Bernhard Kauer, TU Dresden technical report TUD-FI09-09, + * Dresden, Germany, August 2009". + */ + + /* + * Copyright (C) 2009-2012 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. + */ + +#include +#include +#include +#include "acpi.h" + +using namespace Genode; + +/* Enable debugging output */ +static const bool verbose = false; + +/* ACPI spec 5.2.6 */ +struct Generic +{ + uint8_t signature[4]; + uint32_t size; + uint8_t rev; + uint8_t checksum; + uint8_t oemid[6]; + uint8_t oemtabid[8]; + uint32_t oemrev; + uint8_t creator[4]; + uint32_t creator_rev; + + uint8_t const *data() { return reinterpret_cast(this); } +}; + + +/** + * ACPI table wrapper that for mapping tables to this address space + */ +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, 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 */ + uint32_t _offset() const { return (_base & 0xfff); } + + /* compare table name with 'name' */ + bool _cmp(char const *name) const { return !memcmp(_table->signature, name, 4); } + + public: + + /** + * Accessors + */ + Generic* operator -> () { return _table; } + Generic* table() { return _table; } + char const *name() const { return _name; } + + /** + * Copy table data to 'ptr' + */ + void copy_entries(uint32_t *ptr) + { + memcpy(ptr, _table + 1, _table->size - sizeof(Generic)); + } + + /** + * Create ACPI checksum (is zero if valid) + */ + static uint8_t checksum(uint8_t *table, uint32_t count) + { + uint8_t sum = 0; + while (count--) + sum += table[count]; + return sum; + } + + /** + * Is this the FACP table + */ + bool is_facp() const { return _cmp("FACP");} + + /** + * Look for DSDT and SSDT tables + */ + bool is_searched() const { return _cmp("DSDT") || _cmp("SSDT"); } + + Table_wrapper(addr_t base) + : _base(base), _io_mem(0), _table(0) + { + _map(0x1000); + + /* remap if table size is larger than current size */ + if (_offset() + _table->size > 0x1000) { + size_t size = _table->size; + _cleanup(); + _map(size); + } + + memset(_name, 0, 5); + memcpy(_name, _table->signature, 4); + + if (verbose) + PDBG("Table mapped '%s' at %p (from %lx) size %x", _name, _table, _base, _table->size); + + if (checksum((uint8_t *)_table, _table->size)) { + PERR("Checksum mismatch for %s", _name); + throw -1; + } + } + + ~Table_wrapper() { _cleanup(); } +}; + + +/** + * PCI routing information + */ +class Pci_routing : public List::Element +{ + private: + + uint32_t _adr; /* address (6.1.1) */ + uint32_t _pin; /* IRQ pin */ + uint32_t _gsi; /* global system interrupt */ + + public: + + Pci_routing(uint32_t adr, uint32_t pin, uint32_t gsi) + : _adr(adr), _pin(pin), _gsi(gsi) { } + + /** + * Compare BDF of this object to given bdf + */ + bool match_bdf(uint32_t bdf) const { return (_adr >> 16) == ((bdf >> 3) & 0x1f); } + + /** + * Accessors + */ + uint32_t pin() const { return _pin; } + uint32_t gsi() const { return _gsi; } + + /* debug */ + void dump() { if (verbose) PDBG("Pci: adr %x pin %x gsi: %u", _adr, _pin, _gsi); } +}; + + +/** + * A table element (method, device, scope or name) + */ +class Element : public List::Element +{ + private: + + uint8_t _type; /* the type of this element */ + uint32_t _size; /* size in bytes */ + uint32_t _size_len; /* length of size in bytes */ + char *_name; /* name of element */ + uint32_t _name_len; /* length of name in bytes */ + uint32_t _bdf; /* bus device function */ + uint8_t const *_data; /* pointer to the data section */ + bool _valid; /* true if this is a valid element */ + bool _routed; /* has the PCI information been read */ + List *_pci; /* list of PCI routing elements for this element */ + + /* packages we are looking for */ + enum { DEVICE = 0x5b, SUB_DEVICE = 0x82, DEVICE_NAME = 0x8, SCOPE = 0x10, METHOD = 0x14, PACKAGE_OP = 0x12 }; + + /* name prefixes */ + enum { ROOT_PREFIX = 0x5c, PARENT_PREFIX = 0x5e, DUAL_NAME_PREFIX = 0x2e, MULTI_NAME_PREFIX = 0x2f, }; + + /* default signature length of ACPI elements */ + enum { NAME_LEN = 4 }; + + /* return address of 'name' in Element */ + uint8_t const *_name_addr() const { return _data + _size_len + 1; } + + /** + * See ACPI spec 5.4 + */ + uint32_t _read_size_encoding() + { + /* + * Most sig. 2 bits set number of bytes (1-4), next two bits are only used + * in one byte encoding, if bits are set in all two areas it is no valid + * size + */ + uint32_t encoding = _data[1]; + return ((encoding & 0xc0) && (encoding & 0x30)) ? 0 : 1 + (encoding >> 6); + } + + /** + * See ACPI spec. 5.4 + */ + void _read_size() + { + _size = _data[1] & 0x3f; + for (uint32_t i = 1; i < _read_size_encoding(); i++) + _size += _data[i + 1] << (8 * i - 4); + } + + /** + * Return the length of the name prefix + */ + uint32_t _prefix_len(uint8_t const *name) + { + uint8_t const *n = name; + if (*n == ROOT_PREFIX) + n++; + else while (*n == PARENT_PREFIX) + n++; + + if (*n == DUAL_NAME_PREFIX) + n++; + else if (*n == MULTI_NAME_PREFIX) + n += 2; + + return n - name; + } + + /** + * Return length of a given name + */ + uint32_t _read_name_len(uint8_t const *name = 0) + { + uint8_t const *name_addr = name ? name : _name_addr(); + uint8_t const *n = name_addr; + + /* skip prefixes (ACPI 18.2.1) */ + if (*n == ROOT_PREFIX) + n++; + else while (*n == PARENT_PREFIX) + n++; + + /* two names follow */ + if (*n == DUAL_NAME_PREFIX) { + /* check first + second name */ + if (_check_name_segment(n + 1) && _check_name_segment(n + NAME_LEN + 1)) + + /* prefixes + dual prefixe + 2xname */ + return n - name_addr + 1 + 2 * NAME_LEN; + } + + /* multiple name segments ('MultiNamePrefix SegCount NameSeg(SegCount)') */ + else if (*n == MULTI_NAME_PREFIX) { + uint32_t i; + for (i = 0; i < n[1]; i++) + + /* check segment */ + if (!_check_name_segment(n + 2 + NAME_LEN * i)) + return 0; + + if (i) + + /* prefix + multi prefix + seg. count + name length * seg. count */ + return n - name_addr + 2 + NAME_LEN * i; + } + + /* single name segment */ + else if (_check_name_segment(n)) + + /* prefix + name */ + return n - name_addr + NAME_LEN; + + return n - name_addr; + } + + /** + * Check if name is a valid ASL name (18.2.1) + */ + bool _check_name_segment(uint8_t const *name) + { + for (uint32_t i = 0; i < NAME_LEN; i++) { + uint8_t c = name[i]; + if (!((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || c == '_' || + (i > 0 && c >= '0' && c <= '9'))) + return false; + } + + return true; + } + + /** + * Return parent of this element + */ + Element *_parent() + { + Element *parent = list()->first(); + + /* set length of previous element */ + if (parent && !parent->size()) + parent->size(_data - parent->data()); + + /* find parent */ + for (; parent; parent = parent->next()) + + /* parent surrounds child */ + if ((parent->data() < _data) && ((parent->data() + parent->size()) > _data)) + break; + + return parent; + } + + /** + * Set the name of this object + */ + void _set_name() + { + uint8_t const *name = _name_addr(); + Element *parent = _parent(); + uint32_t prefix_len = _prefix_len(name); + + if (_name_len <= prefix_len) { + _name_len = 0; + return; + } + + _name_len -= prefix_len; + + /* is absolute name */ + if (*name == ROOT_PREFIX || !parent) { + _name = (char *)env()->heap()->alloc(_name_len); + memcpy(_name, name + prefix_len, _name_len); + } + else { + /* skip parts */ + int parent_len = parent->_name_len; + parent_len = (parent_len < 0) ? 0 : parent_len; + + /* skip parent prefix */ + for (uint32_t p = 0; name[p] == PARENT_PREFIX; p++, parent_len -= NAME_LEN) ; + _name = (char *)env()->heap()->alloc(_name_len + parent_len); + + memcpy(_name, parent->_name, parent_len); + memcpy(_name + parent_len, name + prefix_len, _name_len); + + _name_len += parent_len; + } + } + + /** + * Compare 'sub_string' with '_name' + */ + Element *_compare(char const *sub_string, uint32_t skip = 0) + { + size_t sub_len = strlen(sub_string); + + Element *other = list()->first(); + while (other) { + + if ((other->_name_len == _name_len + sub_len - skip) && + + /* compare our part */ + !(memcmp(other->_name, _name, _name_len - skip)) && + + /* compare other part */ + !memcmp(other->_name + _name_len - skip, sub_string, sub_len)) + return other; + + other = other->next(); + } + + return 0; + } + + /** + * Read value of element that matches 'sub_string' + */ + uint32_t _value(char const *sub_string) + { + Element *other = _compare(sub_string); + + if (!other || !other->is_device_name()) + return 0; + + uint32_t data_len; + uint32_t data = other->_read(other->_data + other->_read_name_len() + 1, data_len); + + return data_len ? data : 0; + } + + /** + * Read data if return length of data read + */ + uint32_t _read(uint8_t const *data, uint32_t &length) + { + switch (data[0]) { + case 0: length = 1; return 0; + case 1: length = 1; return 1; + case 0xff: length = 1; return 0xffffffff; + case 0x0a: length = 2; return data[1]; + case 0x0b: length = 3; return data[1] | (data[2] << 8); + case 0x0c: length = 5; return data[1] | (data[2] << 8) | (data[3] << 16) | (data[4] << 24); + default: length = 0; return 0; + } + } + + /** + * Try to find an element containing four values of data + */ + Element _packet(uint8_t const *table, long len) + { + for (uint8_t const *data = table; data < table + len; data++) { + Element e(data, true); + if (e.valid()) + return Element(data, true); + } + return Element(); + } + + /** + * Try to locate _PRT table and its GSI values for device + * (data has to be located within the device data) + */ + void _direct_prt(Element *dev) + { + uint32_t len = 0; + + for (uint32_t offset = 0; offset < size(); offset += len) { + len = 1; + + /* search for four value packet */ + Element e = _packet(_data + offset, size() - offset); + + if (!e.valid()) + continue; + + /* read values */ + uint32_t val[4]; + uint32_t read_offset = 0; + uint32_t i; + for (i = 0; i < 4; i++) { + val[i] = e._read(e.data() + e.size_len() + 2 + read_offset, len); + if (!len) break; + read_offset += len; + } + + if (i == 4) { + Pci_routing * r = new (env()->heap()) Pci_routing(val[0], val[1], val[3]); + + /* set _ADR, _PIN, _GSI */ + dev->pci_list()->insert(r); + dev->pci_list()->first()->dump(); + } + + len = len ? (e.data() - (_data + offset)) + e.size() : 1; + } + } + + /** + * Search for _PRT outside of device + */ + void _indirect_prt(Element *dev) + { + uint32_t name_len; + uint32_t found = 0; + for (uint32_t offset = size_len(); offset < size(); offset += name_len) { + name_len = _read_name_len(_data + offset); + if (name_len) { + if (!found++) + continue; + + char name[name_len + 1]; + memcpy(name, _data + offset, name_len); + name[name_len] = 0; + + char name_dev[dev->_name_len + 1]; + memcpy(name_dev, dev->_name, dev->_name_len); + name_dev[dev->_name_len] = 0; + + if (verbose) + PDBG("Indirect %s %s", name, name_dev); + + for (uint32_t skip = 0; skip <= dev->_name_len / NAME_LEN; skip++) { + Element *e = dev->_compare(name, skip * NAME_LEN); + if (e) + e->_direct_prt(dev); + } + } + else + name_len = 1; + } + } + + Element(uint8_t const *data, bool package_op4 = false) + : + _type(0), _size(0), _size_len(0), _name(0), _name_len(0), _bdf(0), _data(data), + _valid(false), _routed(false), _pci(0) + { + /* special handle for four value packet */ + if (package_op4) { + /* scan for data package with four entries */ + if (data[0] != PACKAGE_OP) + return; + + /* area there four entries */ + if (!(_size_len = _read_size_encoding()) || _data[1 + _size_len] != 0x04) + return; + + _read_size(); + _valid = true; + return; + } + + switch (data[0]) { + case DEVICE: + + data++; _data++; + + if (data[0] != SUB_DEVICE) + return; + + case SCOPE: + case METHOD: + + if (!(_size_len = _read_size_encoding())) + return; + + _read_size(); + + if (_size) { + /* check if element is larger than parent */ + Element *p = _parent(); + for (; p; p = p->_parent()) + if (p->_size && p->_size < _size) + return; + } + + case DEVICE_NAME: + + if (!(_name_len = _read_name_len())) + return; + + _valid = true; + + /* set absolute name of this element */ + _set_name(); + _type = data[0]; + + dump(); + + default: + + return; + } + } + + /** + * Default constructor + */ + Element() : _valid(false) {} + + /** + * Copy constructor + */ + Element(Element const &other) + { + _type = other._type; + _size = other._size; + _size_len = other._size_len; + _name_len = other._name_len; + _bdf = other._bdf; + _data = other._data; + _valid = other._valid; + _routed = other._routed; + _pci = other._pci; + + if (other._name) { + _name = (char *)env()->heap()->alloc(other._name_len); + memcpy(_name, other._name, _name_len); + } + } + + 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; } + + /** + * Debugging + */ + void dump() + { + if (!verbose) + return; + + char n[_name_len + 1]; + memcpy(n, _name, _name_len); + n[_name_len] = 0; + PDBG("Found package %x size %u name_len %u name: %s", _data[0], _size, _name_len, n); + } + + public: + + /** + * Accessors + */ + uint32_t size() const { return _size; } + void size(uint32_t size) { _size = size; } + uint32_t size_len() const { return _size_len; } + uint8_t const *data() const { return _data; } + bool valid() const { return _valid; } + + static bool supported_acpi_format() + { + /* check if _PIC method is present */ + for (Element *e = list()->first(); e; e = e->next()) + if (e->_name_len == 4 && !memcmp(e->_name, "_PIC", 4)) + return true; + return false; + } + + /** + * Return list of elements + */ + static List *list() + { + static List _list; + return &_list; + } + + /** + * Return list of PCI information for this element + */ + List *pci_list() + { + if (!_pci) + _pci = new (env()->heap()) List(); + return _pci; + } + + /** + * Parse elements of table + */ + static void parse(Generic *table) + { + for (uint8_t const *data = table->data(); data < table->data() + table->size; data++) { + Element e(data); + + if (!e.valid() || !e._name_len) + continue; + + if (data + e.size() > table->data() + table->size) + break; + + Element *i = new (env()->heap()) Element(e); + list()->insert(i); + + /* skip header */ + data += e.size_len(); + } + + parse_bdf(); + } + + /** + * Parse BDF and GSI information + */ + static void parse_bdf() + { + for (Element *e = list()->first(); e; e = e->next()) { + + if (!e->is_device() || e->_routed) + continue; + + /* address (high word = device, low word = function) (6.1.1) */ + uint32_t adr = e->_value("_ADR"); + + /* Base bus number (6.5.5) */ + uint32_t bbn = e->_value("_BBN"); + + /* Segment object located under host bridge (6.5.6) */ + uint32_t seg = e->_value("_SEG"); + + /* build BDF */ + e->_bdf = (seg << 16) | (bbn << 8) | (adr >> 16) << 3 | (adr & 0xffff); + + /* add routing */ + Element *prt = e->_compare("_PRT"); + if (prt) prt->dump(); + + if (prt) { + if (verbose) + PDBG("Scanning device %x", e->_bdf); + + prt->_direct_prt(e); + prt->_indirect_prt(e); + } + + e->_routed = true; + } + } + + /** + * Search for GSI of given device, bridge, and pin + */ + static uint32_t search_gsi(uint32_t device_bdf, uint32_t bridge_bdf, uint32_t pin) + { + Element *e = list()->first(); + + for (; e; e = e->next()) { + if (!e->is_device() || e->_bdf != bridge_bdf) + continue; + + Pci_routing *r = e->pci_list()->first(); + for (; r; r = r->next()) { + if (r->match_bdf(device_bdf) && r->pin() == pin) { + if (verbose) PDBG("Found GSI: %u device : %x pin %u", r->gsi(), device_bdf, pin); + return r->gsi(); + } + } + } + throw -1; + } +}; + + +/** + * Locate and parse PCI tables we are looking for + */ +class Acpi_table +{ + private: + + /* BIOS range to scan for RSDP */ + enum { BIOS_BASE = 0xe0000, BIOS_SIZE = 0x20000 }; + + /** + * Map table and return address and session cap + */ + uint8_t *_map_io(addr_t base, size_t size, Io_mem_session_capability &cap) + { + Io_mem_connection io_mem(base, size); + io_mem.on_destruction(Io_mem_connection::KEEP_OPEN); + Io_mem_dataspace_capability io_ds = io_mem.dataspace(); + if (!io_ds.valid()) + throw -1; + + uint8_t *ret = env()->rm_session()->attach(io_ds, size); + cap = io_mem.cap(); + return ret; + } + + /** + * Search for RSDP pointer signature in area + */ + uint8_t *_search_rsdp(uint8_t *area) + { + for (addr_t addr = 0; area && addr < BIOS_SIZE; addr += 16) + if (!memcmp(area + addr, "RSD PTR ", 8) && !Table_wrapper::checksum(area + addr, 20)) + return area + addr; + + throw -2; + } + + /** + * Return 'Root System Descriptor Pointer' (ACPI spec 5.2.5.1) + */ + uint8_t *_rsdp(Io_mem_session_capability &cap) + { + uint8_t *area = 0; + + /* try BIOS area (0xe0000 - 0xfffffh)*/ + try { + area = _search_rsdp(_map_io(BIOS_BASE, BIOS_SIZE, cap)); + return area; + } catch (...) { env()->parent()->close(cap); } + + /* search EBDA (BIOS addr + 0x40e) */ + try { + area = _map_io(0x0, 0x1000, cap); + if (area) { + unsigned short base = (*reinterpret_cast(area + 0x40e)) << 4; + env()->parent()->close(cap); + area = _map_io(base, 1024, cap); + area = _search_rsdp(area); + } + return area; + } catch (...) { env()->parent()->close(cap); } + + return 0; + } + + public: + + Acpi_table() + { + Io_mem_session_capability io_mem; + uint8_t *rsdp = _rsdp(io_mem); + + if (verbose) + PDBG("RSDP %p", rsdp); + + if (!rsdp) + return; + + uint32_t rsdt_entries[36]; + memset(rsdt_entries, 0, sizeof(uint32_t)*36); + + { + /* table pointer at 16 byte offset in RSDP structure (5.2.5.3) */ + Table_wrapper rsdt(*reinterpret_cast(rsdp + 0x10)); + rsdt.copy_entries(rsdt_entries); + } + + env()->parent()->close(io_mem); + + /* search for SSDT and DSDT tables */ + for (int i = 0; rsdt_entries[i]; i++) { + uint32_t dsdt = 0; + { + Table_wrapper table(rsdt_entries[i]); + if (table.is_facp()) + dsdt = *reinterpret_cast(table->signature + 40); + + if (table.is_searched()) { + + if (verbose) + PDBG("Found %s", table.name()); + + Element::parse(table.table()); + } + } + + if (dsdt) { + Table_wrapper table(dsdt); + if (table.is_searched()) { + if (verbose) + PDBG("Found dsdt %s", table.name()); + + Element::parse(table.table()); + } + } + } + } +}; + + +/** + * Pci::Device_client extensions identifies bridges and adds IRQ line re-write + */ +class Pci_client : public ::Pci::Device_client +{ + public: + + Pci_client(Pci::Device_capability &cap) : ::Pci::Device_client(cap) { } + + /** + * Return true if this is a PCI-PCI bridge + */ + bool is_bridge() + { + enum { BRIDGE_CLASS = 0x6 }; + if ((class_code() >> 16) != BRIDGE_CLASS) + return false; + + /* see PCI bridge spec (3.2) */ + enum { BRIDGE = 0x1 }; + uint16_t header = config_read(0xe, ::Pci::Device::ACCESS_16BIT); + + /* skip multi function flag 0x80) */ + return ((header & 0x3f) != BRIDGE) ? false : true; + } + + /** + * Return bus-device function of this device + */ + uint32_t bdf() + { + uint8_t bus, dev, func; + bus_address(&bus, &dev, &func); + return (bus << 8) | ((dev & 0x1f) << 3) | (func & 0x7); + } + + /** + * Return IRQ pin + */ + uint32_t irq_pin() + { + return ((config_read(0x3c, ::Pci::Device::ACCESS_32BIT) >> 8) & 0xf); + } + + /** + * Return IRQ line + */ + uint8_t irq_line() + { + return (config_read(0x3c, ::Pci::Device::ACCESS_8BIT)); + } + + /** + * Write line to config space + */ + void irq_line(uint32_t gsi) + { + config_write(0x3c, gsi, ::Pci::Device::ACCESS_8BIT); + } +}; + + +/** + * List of PCI-bridge devices + */ +class Pci_bridge : public List::Element +{ + private: + + /* PCI config space fields of bridge */ + uint32_t _bdf; + uint32_t _secondary_bus; + uint32_t _subordinate_bus; + + Pci_bridge(Pci_client &client) : _bdf(client.bdf()) + { + /* PCI bridge spec 3.2.5.3, 3.2.5.4 */ + uint32_t bus = client.config_read(0x18, ::Pci::Device::ACCESS_32BIT); + _secondary_bus = (bus >> 8) & 0xff; + _subordinate_bus = (bus >> 16) & 0xff; + + if (verbose) + PDBG("New bridge: bdf %x se: %u su: %u", _bdf, _secondary_bus, _subordinate_bus); + } + + static List *_list() + { + static List list; + return &list; + } + + public: + + /** + * Scan PCI bus for bridges + */ + Pci_bridge(Pci::Session_capability &session) + { + Pci::Session_client pci(session); + Pci::Device_capability device_cap = pci.first_device(), prev_device_cap; + + /* search for bridges */ + while (device_cap.valid()) { + prev_device_cap = device_cap; + Pci_client device(device_cap); + + if (device.is_bridge()) + _list()->insert(new (env()->heap()) Pci_bridge(device)); + + device_cap = pci.next_device(device_cap); + pci.release_device(prev_device_cap); + } + } + + /** + * Locate BDF of bridge belonging to given bdf + */ + static uint32_t bridge_bdf(uint32_t bdf) + { + Pci_bridge *bridge = _list()->first(); + uint32_t bus = bdf >> 8; + + for (; bridge; bridge = bridge->next()) + if (bridge->_secondary_bus <= bus && bridge->_subordinate_bus >= bus) + return bridge->_bdf; + + return 0; + } + +}; + + +/** + * Debugging + */ +static void dump_bdf(uint32_t a, uint32_t b, uint32_t pin) +{ + if (verbose) + PDBG("Device bdf %02x:%02x.%u (%x) bridge %02x:%02x.%u (%x) Pin: %u", + (a >> 8), (a >> 3) & 0x1f, (a & 0x7), a, + (b >> 8), (b >> 3) & 0x1f, (b & 0x7), b, pin); +} + +static void dump_rewrite(uint32_t bdf, uint8_t line, uint8_t gsi) +{ + PINF("Rewriting %02x:%02x.%u IRQ: %02u -> GSI: %02u", + (bdf >> 8), (bdf >> 3) & 0x1f, (bdf & 0x7), line, gsi); +} + + +/** + * Rewrite GSIs of PCI config space + */ +void Acpi::rewrite_irq(Pci::Session_capability &session) +{ + static Acpi_table table; + static Pci_bridge bridge(session); + + /* if no _PIC method could be found return */ + if (Element::supported_acpi_format()) + PINF("ACPI table format is supported by this driver"); + else { + PERR("ACPI table format not supported (is too old) by this driver"); + return; + } + + Pci::Session_client pci(session); + Pci::Device_capability device_cap = pci.first_device(), prev_device_cap; + + while (device_cap.valid()) { + prev_device_cap = device_cap; + Pci_client device(device_cap); + + if (!device.is_bridge()) { + uint32_t device_bdf = device.bdf(); + uint32_t bridge_bdf = Pci_bridge::bridge_bdf(device_bdf); + uint32_t irq_pin = device.irq_pin(); + if (irq_pin) { + dump_bdf(device_bdf, bridge_bdf, irq_pin - 1); + try { + uint8_t gsi = Element::search_gsi(device_bdf, bridge_bdf, irq_pin - 1); + dump_rewrite(device_bdf, device.irq_line(), gsi); + device.irq_line(gsi); + } catch (...) { } + } + } + + device_cap = pci.next_device(device_cap); + pci.release_device(prev_device_cap); + } +} + diff --git a/os/src/drivers/acpi/acpi.h b/os/src/drivers/acpi/acpi.h new file mode 100644 index 000000000..b0b6661e3 --- /dev/null +++ b/os/src/drivers/acpi/acpi.h @@ -0,0 +1,31 @@ +/* + * \brief Interface to ACPI + * \author Sebastian Sumpf + * \date 2012-02-25 + */ + + /* + * Copyright (C) 2009-2012 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 _ACPI_H_ +#define _ACPI_H_ + +#include +#include + +class Acpi +{ + public: + + /** + * Rewrite PCI-config space with GSIs found in ACPI tables + */ + static void rewrite_irq(Pci::Session_capability &session); +}; + +#endif /* _ACPI_H_ */ + diff --git a/os/src/drivers/acpi/main.cc b/os/src/drivers/acpi/main.cc new file mode 100644 index 000000000..6f0dbbffd --- /dev/null +++ b/os/src/drivers/acpi/main.cc @@ -0,0 +1,155 @@ +/* + * \brief Service and session interface + * \author Sebastian Sumpf + * \date 2012-02-25 + * + * The 'acpi_drv' provides the 'PCI' after rewriting the IRQ information of PCI + * devices. For this it uses the 'pci_drv' as a client and forwards the session + * capability of the 'pci_drv' afterwards + */ + + /* + * Copyright (C) 2009-2012 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. + */ + +#include +#include +#include +#include +#include +#include + +#include "acpi.h" + +namespace Pci { + + struct Provider + { + bool ready_to_use() { return root().valid(); } + + virtual Genode::Root_capability root() = 0; + }; + + /** + * Root interface of PCI service + */ + class Root : public Genode::Rpc_object > + { + private: + + Provider &_pci_provider; + + public: + + Genode::Session_capability session(Session_args const &args) + { + if (!args.is_valid_string()) throw Invalid_args(); + + if (!_pci_provider.ready_to_use()) + throw Unavailable(); + + try { + return Genode::Root_client(_pci_provider.root()).session(args.string()); + } catch (...) { + throw Unavailable(); + } + } + + void upgrade(Genode::Session_capability, Upgrade_args const &) { } + + void close(Genode::Session_capability session) + { + Genode::Root_client(_pci_provider.root()).close(session); + } + + Root(Provider &pci_provider) : _pci_provider(pci_provider) { } + }; +} + +typedef Genode::Capability > Service_capability; + +class Pci_policy : public Genode::Slave_policy, public Pci::Provider +{ + private: + + Genode::Root_capability _cap; + Genode::Rpc_entrypoint &_ep; + + protected: + + char const **_permitted_services() const + { + static char const *permitted_services[] = { + "CAP", "RM", "LOG", "IO_PORT", 0 }; + + return permitted_services; + }; + + /** + * Parse ACPI tables and announce slave PCI service + */ + void _acpi_session() + { + Pci::Session_capability session; + const char *args = "ram_quota=4K"; + + try { + using namespace Genode; + session = static_cap_cast(Root_client(_cap).session(args)); + } catch (...) { return; } + + Acpi::rewrite_irq(session); + + /* announce service PCI to parent */ + static Pci::Root pci_root(*this); + Genode::env()->parent()->announce(_ep.manage(&pci_root)); + + Genode::Root_client(_cap).close(session); + } + + public: + + Pci_policy(Genode::Rpc_entrypoint &slave_ep, Genode::Rpc_entrypoint &ep) + : Slave_policy("pci_drv", slave_ep), _ep(ep) + { } + + bool announce_service(const char *service_name, + Genode::Root_capability root, + Genode::Allocator *alloc) + { + /* wait for 'pci_drv' to announce the PCI service */ + if (Genode::strcmp(service_name, "PCI")) + return false; + + _cap = root; + + /* connect session and start ACPI parsing */ + _acpi_session(); + + return true; + } + + Genode::Root_capability root() { return _cap; } +}; + + +int main(int argc, char **argv) +{ + using namespace Genode; + + enum { STACK_SIZE = 2*4096 }; + static Cap_connection cap; + static Rpc_entrypoint ep(&cap, STACK_SIZE, "acpi_ep"); + + /* use 'pci_drv' as slave service */ + static Rpc_entrypoint pci_ep(&cap, STACK_SIZE, "pci_slave"); + static Pci_policy pci_policy(pci_ep, ep); + static Genode::Slave pci_slave(pci_ep, pci_policy, 512 * 1024); + + Genode::sleep_forever(); + return 0; +} + diff --git a/os/src/drivers/acpi/x86/target.mk b/os/src/drivers/acpi/x86/target.mk new file mode 100644 index 000000000..8b0f34699 --- /dev/null +++ b/os/src/drivers/acpi/x86/target.mk @@ -0,0 +1,9 @@ +TARGET = acpi_drv +REQUIRES = x86 +SRC_CC = main.cc acpi.cc +LIBS = cxx env server process + +INC_DIR = $(PRG_DIR)/.. + +vpath main.cc $(PRG_DIR)/.. +vpath acpi.cc $(PRG_DIR)/..