345 lines
6.4 KiB
C++
345 lines
6.4 KiB
C++
/*
|
|
* \brief Hardware-resource access
|
|
* \author Christian Helmuth
|
|
* \date 2008-10-21
|
|
*/
|
|
|
|
/*
|
|
* 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.
|
|
*/
|
|
|
|
#include <base/lock.h>
|
|
#include <base/stdint.h>
|
|
#include <util/avl_tree.h>
|
|
|
|
#include <io_port_session/connection.h>
|
|
#include <io_mem_session/connection.h>
|
|
|
|
#include <dataspace/client.h>
|
|
|
|
extern "C" {
|
|
#include <dde_kit/resources.h>
|
|
#include <dde_kit/pgtab.h>
|
|
}
|
|
|
|
#include "device.h"
|
|
|
|
using namespace Genode;
|
|
|
|
static const bool verbose = false;
|
|
|
|
class Range : public Avl_node<Range>
|
|
{
|
|
public:
|
|
|
|
class Not_found : public Exception { };
|
|
class Overlap : public Exception { };
|
|
class Resource_not_accessible : public Exception { };
|
|
|
|
private:
|
|
|
|
addr_t _base;
|
|
size_t _size;
|
|
|
|
public:
|
|
|
|
Range(addr_t base, size_t size) : _base(base), _size(size) { }
|
|
|
|
/** AVL node comparison */
|
|
bool higher(Range *range) {
|
|
return (_base + _size <= range->_base); }
|
|
|
|
/** AVL node lookup */
|
|
Range *lookup(addr_t addr, size_t size)
|
|
{
|
|
Range *r = this;
|
|
|
|
do {
|
|
if (addr >= r->_base) {
|
|
if (addr + size <= r->_base + r->_size)
|
|
return r;
|
|
else if (addr < r->_base + r->_size)
|
|
throw Overlap();
|
|
}
|
|
|
|
if (addr < r->_base)
|
|
r = r->child(LEFT);
|
|
else
|
|
r = r->child(RIGHT);
|
|
} while (r);
|
|
|
|
throw Not_found();
|
|
}
|
|
|
|
/**
|
|
* Log ranges of node and all children
|
|
*/
|
|
void log_ranges()
|
|
{
|
|
if (child(LEFT))
|
|
child(LEFT)->log_ranges();
|
|
|
|
PLOG(" [%08lx,%08lx)", _base, _base + _size);
|
|
|
|
if (child(RIGHT))
|
|
child(RIGHT)->log_ranges();
|
|
}
|
|
|
|
addr_t base() const { return _base; }
|
|
size_t size() const { return _size; }
|
|
};
|
|
|
|
|
|
template <typename TYPE>
|
|
class Range_database : Avl_tree<Range>
|
|
{
|
|
private:
|
|
|
|
Lock _lock;
|
|
|
|
void _log_ranges(const char *op, addr_t b, size_t s)
|
|
{
|
|
PLOG("Range_db %p: %s [%08lx,%08lx)", this, op, b, b + s);
|
|
if (!first())
|
|
PLOG(" <no ranges>");
|
|
else
|
|
first()->log_ranges();
|
|
}
|
|
|
|
public:
|
|
|
|
TYPE *lookup(addr_t addr, size_t size)
|
|
{
|
|
Lock::Guard lock_guard(_lock);
|
|
|
|
if (!first()) throw Range::Not_found();
|
|
|
|
return static_cast<TYPE *>(first()->lookup(addr, size));
|
|
}
|
|
|
|
void insert(Range *range)
|
|
{
|
|
Lock::Guard lock_guard(_lock);
|
|
|
|
Avl_tree<Range>::insert(range);
|
|
|
|
if (verbose)
|
|
_log_ranges("INSERT", range->base(), range->size());
|
|
}
|
|
|
|
void remove(Range *range)
|
|
{
|
|
Lock::Guard lock_guard(_lock);
|
|
|
|
Avl_tree<Range>::remove(range);
|
|
|
|
if (verbose)
|
|
_log_ranges("REMOVE", range->base(), range->size());
|
|
}
|
|
};
|
|
|
|
|
|
/***************
|
|
** I/O ports **
|
|
***************/
|
|
|
|
class Port_range;
|
|
static Range_database<Port_range> *ports()
|
|
{
|
|
static Range_database<Port_range> _ports;
|
|
return &_ports;
|
|
}
|
|
|
|
|
|
class Port_range : public Range, public Io_port_session_client
|
|
{
|
|
public:
|
|
|
|
Port_range(addr_t base, size_t size, Io_port_session_capability cap)
|
|
: Range(base, size), Io_port_session_client(cap) {
|
|
ports()->insert(this); }
|
|
|
|
~Port_range() { ports()->remove(this); }
|
|
};
|
|
|
|
|
|
extern "C" int dde_kit_request_io(dde_kit_addr_t addr, dde_kit_size_t size,
|
|
unsigned short bar, dde_kit_uint8_t bus,
|
|
dde_kit_uint8_t dev, dde_kit_uint8_t func)
|
|
{
|
|
try {
|
|
|
|
new (env()->heap()) Port_range(addr, size, Dde_kit::Device::io_port(bus, dev, func, bar));
|
|
|
|
return 0;
|
|
} catch (...) {
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
|
|
extern "C" int dde_kit_release_io(dde_kit_addr_t addr, dde_kit_size_t size)
|
|
{
|
|
try {
|
|
destroy(env()->heap(), ports()->lookup(addr, size));
|
|
|
|
return 0;
|
|
} catch (...) {
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
|
|
extern "C" unsigned char dde_kit_inb(dde_kit_addr_t port)
|
|
{
|
|
try {
|
|
return ports()->lookup(port, 1)->inb(port);
|
|
} catch (...) {
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
|
|
extern "C" unsigned short dde_kit_inw(dde_kit_addr_t port)
|
|
{
|
|
try {
|
|
return ports()->lookup(port, 2)->inw(port);
|
|
} catch (...) {
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
|
|
extern "C" unsigned long dde_kit_inl(dde_kit_addr_t port)
|
|
{
|
|
try {
|
|
return ports()->lookup(port, 4)->inl(port);
|
|
} catch (...) {
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
|
|
extern "C" void dde_kit_outb(dde_kit_addr_t port, unsigned char val)
|
|
{
|
|
try {
|
|
ports()->lookup(port, 1)->outb(port, val);
|
|
} catch (...) { }
|
|
}
|
|
|
|
|
|
extern "C" void dde_kit_outw(dde_kit_addr_t port, unsigned short val)
|
|
{
|
|
try {
|
|
ports()->lookup(port, 2)->outw(port, val);
|
|
} catch (...) { }
|
|
}
|
|
|
|
|
|
extern "C" void dde_kit_outl(dde_kit_addr_t port, unsigned long val)
|
|
{
|
|
try {
|
|
ports()->lookup(port, 4)->outl(port, val);
|
|
} catch (...) { }
|
|
}
|
|
|
|
|
|
/******************
|
|
** MMIO regions **
|
|
******************/
|
|
|
|
class Mem_range;
|
|
static Range_database<Mem_range> *mem_db()
|
|
{
|
|
static Range_database<Mem_range> _mem_db;
|
|
return &_mem_db;
|
|
}
|
|
|
|
|
|
class Mem_range : public Range, public Io_mem_connection
|
|
{
|
|
private:
|
|
|
|
bool _wc;
|
|
|
|
Io_mem_dataspace_capability _ds;
|
|
|
|
addr_t _vaddr;
|
|
|
|
public:
|
|
|
|
Mem_range(addr_t base, size_t size, bool wc)
|
|
:
|
|
Range(base, size), Io_mem_connection(base, size, wc),
|
|
_wc(wc), _ds(dataspace())
|
|
{
|
|
if (!_ds.valid()) throw Resource_not_accessible();
|
|
|
|
_vaddr = env()->rm_session()->attach(_ds);
|
|
_vaddr |= base & 0xfff;
|
|
|
|
dde_kit_pgtab_set_region_with_size((void *)_vaddr, base, size);
|
|
mem_db()->insert(this);
|
|
}
|
|
|
|
~Mem_range()
|
|
{
|
|
mem_db()->remove(this);
|
|
dde_kit_pgtab_clear_region((void *)_vaddr);
|
|
}
|
|
|
|
addr_t vaddr() const { return _vaddr; }
|
|
bool wc() const { return _wc; }
|
|
};
|
|
|
|
|
|
extern "C" int dde_kit_request_mem(dde_kit_addr_t addr, dde_kit_size_t size,
|
|
int wc, dde_kit_addr_t *vaddr)
|
|
{
|
|
/*
|
|
* We check if a resource comprising the requested region was allocated
|
|
* before (with the same access type, i.e., wc flag) and then return the
|
|
* mapping address. In case of overlapping requests, there's nothing else
|
|
* for it but to return an error.
|
|
*/
|
|
try {
|
|
Mem_range *r = mem_db()->lookup(addr, size);
|
|
|
|
if (!!wc != r->wc()) {
|
|
PERR("I/O memory access type mismatch");
|
|
return -1;
|
|
}
|
|
|
|
*vaddr = r->vaddr() + (addr - r->base());
|
|
|
|
return 0;
|
|
} catch (Mem_range::Overlap) {
|
|
PERR("overlapping I/O memory region requested");
|
|
return -1;
|
|
} catch (Mem_range::Not_found) { }
|
|
|
|
/* request resource if no previous allocation was found */
|
|
try {
|
|
*vaddr = (new (env()->heap()) Mem_range(addr, size, !!wc))->vaddr();
|
|
|
|
return 0;
|
|
} catch (...) {
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
|
|
extern "C" int dde_kit_release_mem(dde_kit_addr_t addr, dde_kit_size_t size)
|
|
{
|
|
try {
|
|
destroy(env()->heap(), mem_db()->lookup(addr, size));
|
|
|
|
return 0;
|
|
} catch (...) {
|
|
return -1;
|
|
}
|
|
}
|