genode/repos/dde_ipxe/src/lib/dde_ipxe/dde_support.cc

669 lines
14 KiB
C++

/*
* \brief DDE iPXE wrappers to C++ backend
* \author Sebastian Sumpf
* \author Josef Soentgen
* \date 2010-10-21
*/
/*
* Copyright (C) 2010-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.
*/
/*
* 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/printf.h>
#include <base/slab.h>
#include <base/sleep.h>
#include <dataspace/client.h>
#include <io_mem_session/connection.h>
#include <io_port_session/connection.h>
#include <irq_session/connection.h>
#include <os/server.h>
#include <pci_device/client.h>
#include <pci_session/connection.h>
#include <rm_session/connection.h>
#include <timer_session/connection.h>
#include <util/misc_math.h>
/* local includes */
#include <dde_support.h>
//using namespace Genode;
/****************
** Migriation **
****************/
static Server::Entrypoint *_ep;
extern "C" void dde_init(void *ep)
{
_ep = (Server::Entrypoint*)ep;
}
/************
** printf **
************/
extern "C" void dde_vprintf(const char *fmt, va_list va) {
Genode::vprintf(fmt, va); }
extern "C" void dde_printf(const char *fmt, ...)
{
va_list args;
va_start(args, fmt);
dde_vprintf(fmt, args);
va_end(args);
}
/***********
** Timer **
***********/
extern "C" void dde_udelay(unsigned long usecs)
{
/*
* This function is called only once during rdtsc calibration (usecs will be
* 10000, see dde.c 'udelay'. We do not use DDE timers here, since Genode's
* timer connection is the most precise one around.
*/
Timer::Connection timer;
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)
{
/*
* Using one static timer connection here is safe because
* this function is only called while initializing the device
* and only be the same thread.
*/
static Timer::Connection timer;
timer.msleep(msecs);
}
/******************
** PCI handling **
******************/
struct Pci_driver
{
enum {
PCI_BASE_CLASS_NETWORK = 0x02,
CLASS_MASK = 0xff0000,
CLASS_NETWORK = PCI_BASE_CLASS_NETWORK << 16
};
Pci::Connection _pci;
Pci::Device_capability _cap;
Pci::Device_capability _last_cap;
struct Region
{
Genode::addr_t base;
Genode::addr_t mapped_base;
} _region;
template <typename T>
Pci::Device::Access_size _access_size(T t)
{
switch (sizeof(T)) {
case 1:
return Pci::Device::ACCESS_8BIT;
case 2:
return Pci::Device::ACCESS_16BIT;
default:
return Pci::Device::ACCESS_32BIT;
}
}
void _bus_address(int *bus, int *dev, int *fun)
{
Pci::Device_client client(_cap);
unsigned char b, d, f;
client.bus_address(&b, &d, &f);
*bus = b;
*dev = d;
*fun = f;
}
Pci_driver() { }
template <typename T>
void config_read(unsigned int devfn, T *val)
{
Pci::Device_client client(_cap);
*val = client.config_read(devfn, _access_size(*val));
}
template <typename T>
void config_write(unsigned int devfn, T val)
{
Pci::Device_client client(_cap);
client.config_write(devfn, val, _access_size(val));
}
int first_device(int *bus, int *dev, int *fun)
{
_cap = _pci.first_device(CLASS_NETWORK, CLASS_MASK);
_bus_address(bus, dev, fun);
return 0;
}
int next_device(int *bus, int *dev, int *fun)
{
_last_cap = _cap;
_cap = _pci.next_device(_cap, CLASS_NETWORK, CLASS_MASK);
_bus_address(bus, dev, fun);
if (_last_cap.valid())
_pci.release_device(_last_cap);
return 0;
}
Genode::addr_t alloc_dma_memory(Genode::size_t size)
{
try {
using namespace Genode;
/* trigger that the device gets assigned to this driver */
for (unsigned i = 0; i < 2; i++) {
try {
_pci.config_extended(_cap);
break;
} catch (Pci::Device::Quota_exceeded) {
Genode::env()->parent()->upgrade(_pci.cap(), "ram_quota=4096");
}
}
/* transfer quota to pci driver, otherwise it will give us a exception */
char buf[32];
Genode::snprintf(buf, sizeof(buf), "ram_quota=%zd", size);
Genode::env()->parent()->upgrade(_pci.cap(), buf);
Ram_dataspace_capability ram_cap = _pci.alloc_dma_buffer(size);
_region.mapped_base = (Genode::addr_t)env()->rm_session()->attach(ram_cap);
_region.base = Dataspace_client(ram_cap).phys_addr();
return _region.mapped_base;
} catch (...) { 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;
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
{
Server::Entrypoint &ep;
Genode::Irq_session_client irq;
Genode::Signal_rpc_member<Irq_handler> dispatcher;
typedef void (*irq_handler)(void*);
irq_handler handler;
void *priv;
void handle(unsigned)
{
handler(priv);
irq.ack_irq();
}
Irq_handler(Server::Entrypoint &ep, Genode::Irq_session_capability cap,
irq_handler handler, void *priv)
:
ep(ep), 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 Irq_handler *_irq_handler;
extern "C" int dde_interrupt_attach(void(*handler)(void *), void *priv)
{
if (_irq_handler) {
PERR("Irq_handler already registered");
Genode::sleep_forever();
}
try {
Pci::Device_client device(pci_drv()._cap);
_irq_handler = new (Genode::env()->heap())
Irq_handler(*_ep, device.irq(0), handler, priv);
} catch (...) { return -1; }
return 0;
}
/***************************************************
** Support for aligned and DMA memory allocation **
***************************************************/
enum { BACKING_STORE_SIZE = 1024 * 1024 };
static Genode::Allocator_avl& allocator()
{
static Genode::Allocator_avl _avl(Genode::env()->heap());
return _avl;
}
extern "C" int dde_dma_mem_init()
{
try {
Genode::addr_t base = pci_drv().alloc_dma_memory(BACKING_STORE_SIZE);
/* add to allocator */
allocator().add_range(base, BACKING_STORE_SIZE);
} catch (...) { return false; }
return true;
}
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)).is_error()) {
PERR("memory allocation failed in alloc_memblock (size=%zu, align=%zx,"
" offset=%zx)", (Genode::size_t)size, (Genode::size_t)align, (Genode::size_t)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::Io_port_session_client *_io_port;
extern "C" void dde_request_io(dde_uint8_t virt_bar_ioport)
{
using namespace Genode;
if (_io_port) {
PERR("Io_port_connection already open");
sleep_forever();
}
Pci::Device_client device(pci_drv()._cap);
Io_port_session_capability cap = device.io_port(virt_bar_ioport);
_io_port = new (env()->heap()) Io_port_session_client(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
{
enum {
VM_SIZE = 512 * 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_session &_ram;
bool _alloc_block()
{
using namespace Genode;
if (_index == ELEMENTS) {
PERR("Slab-backend exhausted!");
return false;
}
try {
_ds_cap[_index] = _ram.alloc(BLOCK_SIZE);
Rm_connection::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::Ram_session &ram)
:
Rm_connection(0, VM_SIZE),
_index(0), _range(Genode::env()->heap()), _ram(ram)
{
/* reserver attach us, anywere */
_base = Genode::env()->rm_session()->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) {
PERR("Backend allocator exhausted\n");
return false;
}
return _range.alloc(size, out_addr);
}
void free(void *addr, Genode::size_t size) { }
Genode::size_t overhead(Genode::size_t size) const { return 0; }
bool need_size_for_free() const { return false; }
};
struct Slab_alloc : public Genode::Slab
{
/*
* Each slab block in the slab contains about 8 objects (slab entries)
* as proposed in the paper by Bonwick and block sizes are multiples of
* page size.
*/
static Genode::size_t _calculate_block_size(Genode::size_t object_size)
{
Genode::size_t block_size = 8 * (object_size + sizeof(Genode::Slab_entry))
+ sizeof(Genode::Slab_block);
return Genode::align_addr(block_size, 12);
}
Slab_alloc(Genode::size_t object_size, Slab_backend_alloc &allocator)
: Slab(object_size, _calculate_block_size(object_size), 0, &allocator) { }
/**
* Convenience slabe-entry allocation
*/
Genode::addr_t alloc()
{
Genode::addr_t result;
return (Slab::alloc(slab_size(), (void **)&result) ? result : 0);
}
};
struct Slab
{
enum {
SLAB_START_LOG2 = 5, /* 32 B */
SLAB_STOP_LOG2 = 10, /* 1 KiB */
NUM_SLABS = (SLAB_STOP_LOG2 - SLAB_START_LOG2) + 1,
};
Slab_backend_alloc &_back_alloc;
Slab_alloc *_allocator[NUM_SLABS];
Genode::addr_t _start;
Genode::addr_t _end;
Slab(Slab_backend_alloc &alloc)
: _back_alloc(alloc), _start(alloc.start()), _end(alloc.end())
{
for (unsigned i = 0; i < NUM_SLABS; i++)
_allocator[i] = new (Genode::env()->heap())
Slab_alloc(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)
{
using namespace Genode;
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(*Genode::env()->ram_session());
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::addr_t base, Genode::Io_mem_session_capability cap)
:
_mem(cap),
_mem_ds(_mem.dataspace())
{
if (!_mem_ds.valid())
throw Genode::Exception();
_vaddr = Genode::env()->rm_session()->attach(_mem_ds);
_vaddr |= base & 0xfff;
}
Genode::addr_t vaddr() const { return _vaddr; }
};
static Io_memory *_io_mem;
extern "C" int dde_request_iomem(dde_addr_t start, dde_addr_t *vaddr)
{
if (_io_mem) {
PERR("Io_memory already requested");
Genode::sleep_forever();
}
Pci::Device_client device(pci_drv()._cap);
Genode::Io_mem_session_capability cap;
Genode::uint8_t virt_iomem_bar = 0;
for (unsigned i = 0; i < Pci::Device::NUM_RESOURCES; i++) {
Pci::Device::Resource res = device.resource(i);
if (res.type() == Pci::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 = new (Genode::env()->heap()) Io_memory(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 {
destroy(Genode::env()->heap(), _io_mem);
_io_mem = 0;
return 0;
} catch (...) { return -1; }
}