genode/repos/dde_ipxe/src/lib/dde_ipxe/dde_support.cc
2019-01-30 13:49:54 +01:00

714 lines
15 KiB
C++

/*
* \brief DDE iPXE wrappers to C++ backend
* \author Sebastian Sumpf
* \author Josef Soentgen
* \date 2010-10-21
*/
/*
* Copyright (C) 2010-2017 Genode Labs GmbH
*
* This file is distributed under the terms of the GNU General Public License
* version 2.
*/
/*
* The build system picks up stdarg.h from iPXE instead of the one
* provided by GCC. This header contains the FILE_LICENCE macro which
* is defined by iPXE's compiler.h.
*/
#define FILE_LICENCE(x)
/* Genode includes */
#include <base/allocator_avl.h>
#include <base/env.h>
#include <base/log.h>
#include <base/slab.h>
#include <dataspace/client.h>
#include <io_mem_session/connection.h>
#include <io_port_session/connection.h>
#include <irq_session/connection.h>
#include <platform_device/client.h>
#include <platform_session/connection.h>
#include <rm_session/connection.h>
#include <region_map/client.h>
#include <timer_session/connection.h>
#include <util/misc_math.h>
#include <util/retry.h>
#include <util/reconstructible.h>
/* local includes */
#include <dde_support.h>
/* DDE support includes */
#include <dde_ipxe/support.h>
static Genode::Entrypoint *_global_ep;
static Genode::Env *_global_env;
static Genode::Allocator *_global_alloc;
void dde_support_init(Genode::Env &env, Genode::Allocator &alloc)
{
_global_env = &env;
_global_ep = &env.ep();
_global_alloc = &alloc;
}
/**************************
** Initialization check **
**************************/
extern "C" int dde_support_initialized(void)
{
return !!_global_env;
}
/************
** printf **
************/
extern "C" void dde_vprintf(const char *format, va_list list)
{
using namespace Genode;
char buf[128] { };
String_console(buf, sizeof(buf)).vprintf(format, list);
log(Cstring(buf));
}
extern "C" void dde_printf(const char *fmt, ...)
{
va_list args;
va_start(args, fmt);
dde_vprintf(fmt, args);
va_end(args);
}
/***********
** Timer **
***********/
static Genode::Constructible<Timer::Connection> _timer;
extern "C" void dde_udelay(unsigned long usecs)
{
if (!_timer.constructed()) {
_timer.construct(*_global_env);
}
/*
* This function is called only once during rdtsc calibration (usecs will be
* 10000, see dde.c 'udelay'.
*/
_timer->usleep(usecs);
}
/***************************
** locking/synchronizing **
***************************/
/**
* DDE iPXE mutual exclusion lock
*/
static Genode::Lock _ipxe_lock;
extern "C" void dde_lock_enter(void) { _ipxe_lock.lock(); }
extern "C" void dde_lock_leave(void) { _ipxe_lock.unlock(); }
extern "C" void dde_mdelay(unsigned long msecs)
{
if (!_timer.constructed()) {
_timer.construct(*_global_env);
}
/*
* This function is only called while initializing the device
* and only be the same thread.
*/
_timer->msleep(msecs);
}
/******************
** PCI handling **
******************/
struct Pci_driver
{
enum {
PCI_BASE_CLASS_NETWORK = 0x02,
CLASS_MASK = 0xff0000,
CLASS_NETWORK = PCI_BASE_CLASS_NETWORK << 16
};
Genode::Region_map &_rm;
Platform::Connection _pci;
Platform::Device_capability _cap;
Platform::Device_capability _last_cap;
struct Region
{
Genode::addr_t base;
Genode::addr_t mapped_base;
} _region;
template <typename T>
Platform::Device::Access_size _access_size(T t)
{
switch (sizeof(T)) {
case 1: return Platform::Device::ACCESS_8BIT;
case 2: return Platform::Device::ACCESS_16BIT;
default: return Platform::Device::ACCESS_32BIT;
}
}
void _bus_address(int *bus, int *dev, int *fun)
{
Platform::Device_client client(_cap);
unsigned char b, d, f;
client.bus_address(&b, &d, &f);
*bus = b;
*dev = d;
*fun = f;
}
Pci_driver(Genode::Env &env, Genode::Region_map &rm)
: _rm(rm), _pci(env) { }
template <typename T>
void config_read(unsigned int devfn, T *val)
{
Platform::Device_client client(_cap);
*val = client.config_read(devfn, _access_size(*val));
}
template <typename T>
void config_write(unsigned int devfn, T val)
{
Platform::Device_client client(_cap);
_pci.with_upgrade([&] () {
client.config_write(devfn, val, _access_size(val)); });
}
int first_device(int *bus, int *dev, int *fun)
{
_cap = _pci.with_upgrade([&] () {
return _pci.first_device(CLASS_NETWORK, CLASS_MASK); });
if (!_cap.valid())
return -1;
_bus_address(bus, dev, fun);
return 0;
}
int next_device(int *bus, int *dev, int *fun)
{
int result = -1;
_last_cap = _cap;
_cap = _pci.with_upgrade([&] () {
return _pci.next_device(_cap, CLASS_NETWORK, CLASS_MASK); });
if (_cap.valid()) {
_bus_address(bus, dev, fun);
result = 0;
}
if (_last_cap.valid())
_pci.release_device(_last_cap);
return result;
}
Genode::addr_t alloc_dma_memory(Genode::size_t size)
{
try {
using namespace Genode;
size_t donate = size;
Ram_dataspace_capability ram_cap =
retry<Out_of_ram>(
[&] () {
return retry<Out_of_caps>(
[&] () { return _pci.alloc_dma_buffer(size); },
[&] () { _pci.upgrade_caps(2); });
},
[&] () {
_pci.upgrade_ram(donate);
donate = donate * 2 > size ? 4096 : donate * 2;
});
_region.mapped_base = _rm.attach(ram_cap);
_region.base = Dataspace_client(ram_cap).phys_addr();
return _region.mapped_base;
} catch (...) {
Genode::error("failed to allocate dma memory");
return 0;
}
}
Genode::addr_t virt_to_phys(Genode::addr_t virt) {
return virt - _region.mapped_base + _region.base; }
};
static Pci_driver& pci_drv()
{
static Pci_driver _pci_drv { *_global_env , _global_env->rm() };
return _pci_drv;
}
extern "C" int dde_pci_first_device(int *bus, int *dev, int *fun) {
return pci_drv().first_device(bus, dev, fun); }
extern "C" int dde_pci_next_device(int *bus, int *dev, int *fun) {
return pci_drv().next_device(bus, dev, fun); }
extern "C" void dde_pci_readb(int pos, dde_uint8_t *val) {
pci_drv().config_read(pos, val); }
extern "C" void dde_pci_readw(int pos, dde_uint16_t *val) {
pci_drv().config_read(pos, val); }
extern "C" void dde_pci_readl(int pos, dde_uint32_t *val) {
pci_drv().config_read(pos, val); }
extern "C" void dde_pci_writeb(int pos, dde_uint8_t val) {
pci_drv().config_write(pos, val); }
extern "C" void dde_pci_writew(int pos, dde_uint16_t val) {
pci_drv().config_write(pos, val); }
extern "C" void dde_pci_writel(int pos, dde_uint32_t val) {
pci_drv().config_write(pos, val); }
/************************
** Interrupt handling **
************************/
struct Irq_handler
{
Genode::Irq_session_client irq;
Genode::Signal_handler<Irq_handler> dispatcher;
typedef void (*irq_handler)(void*);
irq_handler handler;
void *priv;
void handle()
{
handler(priv);
irq.ack_irq();
}
Irq_handler(Genode::Entrypoint &ep, Genode::Irq_session_capability cap,
irq_handler handler, void *priv)
:
irq(cap), dispatcher(ep, *this, &Irq_handler::handle),
handler(handler), priv(priv)
{
irq.sigh(dispatcher);
/* intial ack so that we will receive IRQ signals */
irq.ack_irq();
}
};
static Genode::Constructible<Irq_handler> _irq_handler;
extern "C" int dde_interrupt_attach(void(*handler)(void *), void *priv)
{
if (_irq_handler.constructed()) {
Genode::error("Irq_handler already registered");
return -1;
}
try {
Platform::Device_client device(pci_drv()._cap);
_irq_handler.construct(*_global_ep, device.irq(0), handler, priv);
} catch (...) { return -1; }
return 0;
}
/***************************************************
** Support for aligned and DMA memory allocation **
***************************************************/
enum { BACKING_STORE_SIZE = 1024 * 1024 };
struct Backing_store
{
Genode::Allocator_avl _avl;
Backing_store (Genode::Allocator &alloc) : _avl(&alloc)
{
Genode::addr_t base = pci_drv().alloc_dma_memory(BACKING_STORE_SIZE);
/* add to allocator */
_avl.add_range(base, BACKING_STORE_SIZE);
}
};
static Genode::Allocator_avl& allocator()
{
static Backing_store _instance { *_global_alloc };
return _instance._avl;
}
extern "C" void *dde_dma_alloc(dde_size_t size, dde_size_t align,
dde_size_t offset)
{
void *ptr;
if (allocator().alloc_aligned(size, &ptr, Genode::log2(align)).error()) {
Genode::error("memory allocation failed in alloc_memblock ("
"size=", size, " "
"align=", Genode::Hex(align), " "
"offset=", Genode::Hex(offset), ")");
return 0;
}
return ptr;
}
extern "C" void dde_dma_free(void *p, dde_size_t size) {
allocator().free(p, size); }
extern "C" dde_addr_t dde_dma_get_physaddr(void *virt) {
return pci_drv().virt_to_phys((Genode::addr_t)virt); }
/**************
** I/O port **
**************/
static Genode::Constructible<Genode::Io_port_session_client> _io_port;
extern "C" void dde_request_io(dde_uint8_t virt_bar_ioport)
{
using namespace Genode;
if (_io_port.constructed()) { _io_port.destruct(); }
Platform::Device_client device(pci_drv()._cap);
Io_port_session_capability cap = device.io_port(virt_bar_ioport);
_io_port.construct(cap);
}
extern "C" dde_uint8_t dde_inb(dde_addr_t port) {
return _io_port->inb(port); }
extern "C" dde_uint16_t dde_inw(dde_addr_t port) {
return _io_port->inw(port); }
extern "C" dde_uint32_t dde_inl(dde_addr_t port) {
return _io_port->inl(port); }
extern "C" void dde_outb(dde_addr_t port, dde_uint8_t data) {
_io_port->outb(port, data); }
extern "C" void dde_outw(dde_addr_t port, dde_uint16_t data) {
_io_port->outw(port, data); }
extern "C" void dde_outl(dde_addr_t port, dde_uint32_t data) {
_io_port->outl(port, data); }
/**********************
** Slab memory pool **
**********************/
struct Slab_backend_alloc : public Genode::Allocator,
public Genode::Rm_connection,
public Genode::Region_map_client
{
enum {
VM_SIZE = 2 * 1024 * 1024,
BLOCK_SIZE = 64 * 1024,
ELEMENTS = VM_SIZE / BLOCK_SIZE,
};
Genode::addr_t _base;
Genode::Ram_dataspace_capability _ds_cap[ELEMENTS];
int _index;
Genode::Allocator_avl _range;
Genode::Ram_allocator &_ram;
bool _alloc_block()
{
using namespace Genode;
if (_index == ELEMENTS) {
error("slab backend exhausted!");
return false;
}
try {
_ds_cap[_index] = _ram.alloc(BLOCK_SIZE);
Region_map_client::attach_at(_ds_cap[_index], _index * BLOCK_SIZE, BLOCK_SIZE, 0);
} catch (...) { return false; }
/* return base + offset in VM area */
Genode::addr_t block_base = _base + (_index * BLOCK_SIZE);
++_index;
_range.add_range(block_base, BLOCK_SIZE);
return true;
}
Slab_backend_alloc(Genode::Env &env, Genode::Region_map &rm,
Genode::Ram_allocator &ram,
Genode::Allocator &md_alloc)
:
Rm_connection(env),
Region_map_client(Rm_connection::create(VM_SIZE)),
_index(0), _range(&md_alloc), _ram(ram)
{
/* reserver attach us, anywere */
_base = rm.attach(dataspace());
}
Genode::addr_t start() const { return _base; }
Genode::addr_t end() const { return _base + VM_SIZE - 1; }
/*************************
** Allocator interface **
*************************/
bool alloc(Genode::size_t size, void **out_addr)
{
bool done = _range.alloc(size, out_addr);
if (done)
return done;
done = _alloc_block();
if (!done) {
Genode::error("backend allocator exhausted");
return false;
}
return _range.alloc(size, out_addr);
}
void free(void *addr, Genode::size_t size) { _range.free(addr, size); }
Genode::size_t overhead(Genode::size_t size) const { return 0; }
bool need_size_for_free() const { return false; }
};
class Slab_alloc : public Genode::Slab
{
private:
Genode::size_t const _object_size;
static Genode::size_t _calculate_block_size(Genode::size_t object_size)
{
Genode::size_t const block_size = 8*object_size;
return Genode::align_addr(block_size, 12);
}
public:
Slab_alloc(Genode::size_t object_size, Slab_backend_alloc &allocator)
:
Slab(object_size, _calculate_block_size(object_size), 0, &allocator),
_object_size(object_size)
{ }
Genode::addr_t alloc()
{
Genode::addr_t result;
return (Slab::alloc(_object_size, (void **)&result) ? result : 0);
}
void free(void *ptr) { Slab::free(ptr, _object_size); }
};
struct Slab
{
enum {
SLAB_START_LOG2 = 5, /* 32 B */
SLAB_STOP_LOG2 = 13, /* 8 KiB */
NUM_SLABS = (SLAB_STOP_LOG2 - SLAB_START_LOG2) + 1,
};
Genode::Constructible<Slab_alloc> _allocator[NUM_SLABS];
Genode::addr_t _start;
Genode::addr_t _end;
Slab(Slab_backend_alloc &alloc)
: _start(alloc.start()), _end(alloc.end())
{
for (unsigned i = 0; i < NUM_SLABS; i++) {
_allocator[i].construct(1U << (SLAB_START_LOG2 + i), alloc);
}
}
void *alloc(Genode::size_t size)
{
using namespace Genode;
size += sizeof(Genode::addr_t);
int msb = Genode::log2(size);
if (size > (1U << msb))
msb++;
if (size < (1U << SLAB_START_LOG2))
msb = SLAB_STOP_LOG2;
if (msb > SLAB_STOP_LOG2)
return 0;
Genode::addr_t addr = _allocator[msb - SLAB_START_LOG2]->alloc();
if (!addr)
return 0;
*(Genode::addr_t*)addr = msb - SLAB_START_LOG2;
addr += sizeof(Genode::addr_t);
return (void*)addr;
}
void free(void *p)
{
Genode::addr_t *addr = ((Genode::addr_t *)p)-1;
unsigned index = *(unsigned*)(addr);
_allocator[index]->free((void*)(addr));
}
};
static ::Slab& slab()
{
static ::Slab_backend_alloc sb(*_global_env, _global_env->rm(),
_global_env->ram(), *_global_alloc);
static ::Slab s(sb);
return s;
}
extern "C" void *dde_slab_alloc(dde_size_t size) {
return slab().alloc(size); }
extern "C" void dde_slab_free(void *p) {
slab().free(p); }
/****************
** I/O memory **
****************/
struct Io_memory
{
Genode::Io_mem_session_client _mem;
Genode::Io_mem_dataspace_capability _mem_ds;
Genode::addr_t _vaddr;
Io_memory(Genode::Region_map &rm,
Genode::addr_t base, Genode::Io_mem_session_capability cap)
:
_mem(cap),
_mem_ds(_mem.dataspace())
{
if (!_mem_ds.valid())
throw Genode::Exception();
_vaddr = rm.attach(_mem_ds);
_vaddr |= base & 0xfff;
}
Genode::addr_t vaddr() const { return _vaddr; }
};
static Genode::Constructible<Io_memory> _io_mem;
extern "C" int dde_request_iomem(dde_addr_t start, dde_addr_t *vaddr)
{
if (_io_mem.constructed()) {
Genode::error("Io_memory already requested");
return -1;
}
Platform::Device_client device(pci_drv()._cap);
Genode::Io_mem_session_capability cap;
Genode::uint8_t virt_iomem_bar = 0;
for (unsigned i = 0; i < Platform::Device::NUM_RESOURCES; i++) {
Platform::Device::Resource res = device.resource(i);
if (res.type() == Platform::Device::Resource::MEMORY) {
if (res.base() == start) {
cap = device.io_mem(virt_iomem_bar);
break;
}
virt_iomem_bar ++;
}
}
if (!cap.valid()) { return -1; }
try {
_io_mem.construct(_global_env->rm(), start, cap);
} catch (...) { return -1; }
*vaddr = _io_mem->vaddr();
return 0;
}
extern "C" int dde_release_iomem(dde_addr_t start, dde_size_t size)
{
try {
_io_mem.destruct();
return 0;
} catch (...) { return -1; }
}