genode/repos/base-nova/src/core/irq_session_component.cc
Norman Feske 4d442bca30 Streamline exception types
This patch reduces the number of exception types by facilitating
globally defined exceptions for common usage patterns shared by most
services. In particular, RPC functions that demand a session-resource
upgrade not longer reflect this condition via a session-specific
exception but via the 'Out_of_ram' or 'Out_of_caps' types.

Furthermore, the 'Parent::Service_denied', 'Parent::Unavailable',
'Root::Invalid_args', 'Root::Unavailable', 'Service::Invalid_args',
'Service::Unavailable', and 'Local_service::Factory::Denied' types have
been replaced by the single 'Service_denied' exception type defined in
'session/session.h'.

This consolidation eases the error handling (there are fewer exceptions
to handle), alleviates the need to convert exceptions along the
session-creation call chain, and avoids possible aliasing problems
(catching the wrong type with the same name but living in a different
scope).
2017-05-31 13:16:07 +02:00

263 lines
6.4 KiB
C++

/*
* \brief Implementation of IRQ session component
* \author Norman Feske
* \author Sebastian Sumpf
* \author Alexander Boettcher
* \date 2009-10-02
*/
/*
* Copyright (C) 2009-2017 Genode Labs GmbH
*
* This file is part of the Genode OS framework, which is distributed
* under the terms of the GNU Affero General Public License version 3.
*/
/* Genode includes */
#include <base/log.h>
/* core includes */
#include <irq_root.h>
#include <platform.h>
/* NOVA includes */
#include <nova/syscalls.h>
#include <nova_util.h>
using namespace Genode;
static bool irq_ctrl(Genode::addr_t irq_sel,
Genode::addr_t &msi_addr, Genode::addr_t &msi_data,
Genode::addr_t sig_sel, Genode::addr_t virt_addr = 0)
{
/* assign IRQ to CPU && request msi data to be used by driver */
uint8_t res = Nova::assign_gsi(irq_sel, virt_addr, boot_cpu(),
msi_addr, msi_data, sig_sel);
if (res != Nova::NOVA_OK)
error("setting up MSI failed - error ", res);
/* nova syscall interface specifies msi addr/data to be 32bit */
msi_addr = msi_addr & ~0U;
msi_data = msi_data & ~0U;
return res == Nova::NOVA_OK;
}
static bool associate(Genode::addr_t irq_sel,
Genode::addr_t &msi_addr, Genode::addr_t &msi_data,
Genode::Signal_context_capability sig_cap,
Genode::addr_t virt_addr = 0)
{
return irq_ctrl(irq_sel, msi_addr, msi_data, sig_cap.local_name(),
virt_addr);
}
static void deassociate(Genode::addr_t irq_sel)
{
addr_t dummy1 = 0, dummy2 = 0;
if (!irq_ctrl(irq_sel, dummy1, dummy2, irq_sel))
warning("Irq could not be de-associated");
}
static bool msi(Genode::addr_t irq_sel, Genode::addr_t phys_mem,
Genode::addr_t &msi_addr, Genode::addr_t &msi_data,
Genode::Signal_context_capability sig_cap)
{
void * virt = 0;
if (platform()->region_alloc()->alloc_aligned(4096, &virt, 12).error())
return false;
Genode::addr_t virt_addr = reinterpret_cast<Genode::addr_t>(virt);
if (!virt_addr)
return false;
using Nova::Rights;
using Nova::Utcb;
Nova::Mem_crd phys_crd(phys_mem >> 12, 0, Rights(true, false, false));
Nova::Mem_crd virt_crd(virt_addr >> 12, 0, Rights(true, false, false));
Utcb * utcb = reinterpret_cast<Utcb *>(Thread::myself()->utcb());
if (map_local_phys_to_virt(utcb, phys_crd, virt_crd)) {
platform()->region_alloc()->free(virt, 4096);
return false;
}
/* try to assign MSI to device */
bool res = associate(irq_sel, msi_addr, msi_data, sig_cap, virt_addr);
unmap_local(Nova::Mem_crd(virt_addr >> 12, 0, Rights(true, true, true)));
platform()->region_alloc()->free(virt, 4096);
return res;
}
void Irq_object::sigh(Signal_context_capability cap)
{
if (!_sigh_cap.valid() && !cap.valid())
return;
if ((_sigh_cap.valid() && !cap.valid())) {
deassociate(irq_sel());
_sigh_cap = Signal_context_capability();
return;
}
bool ok = false;
if (_device_phys)
ok = msi(irq_sel(), _device_phys, _msi_addr, _msi_data, cap);
else
ok = associate(irq_sel(), _msi_addr, _msi_data, cap);
if (!ok) {
deassociate(irq_sel());
_sigh_cap = Signal_context_capability();
return;
}
_sigh_cap = cap;
}
void Irq_object::ack_irq()
{
if (Nova::NOVA_OK != Nova::sm_ctrl(irq_sel(), Nova::SEMAPHORE_DOWN))
error("Unmasking irq of selector ", irq_sel(), " failed");
}
void Irq_object::start(unsigned irq, Genode::addr_t const device_phys)
{
/* map IRQ SM cap from kernel to core at irq_sel selector */
using Nova::Obj_crd;
Obj_crd src(platform_specific()->gsi_base_sel() + irq, 0);
Obj_crd dst(irq_sel(), 0);
enum { MAP_FROM_KERNEL_TO_CORE = true };
int ret = map_local((Nova::Utcb *)Thread::myself()->utcb(),
src, dst, MAP_FROM_KERNEL_TO_CORE);
if (ret) {
error("getting IRQ from kernel failed - ", irq);
throw Service_denied();
}
/* associate GSI or MSI to device belonging to device_phys */
bool ok = false;
if (device_phys)
ok = msi(irq_sel(), device_phys, _msi_addr, _msi_data, _sigh_cap);
else
ok = associate(irq_sel(), _msi_addr, _msi_data, _sigh_cap);
if (!ok)
throw Service_denied();
_device_phys = device_phys;
}
Irq_object::Irq_object()
:
_kernel_caps(cap_map()->insert(KERNEL_CAP_COUNT_LOG2)),
_msi_addr(0UL), _msi_data(0UL)
{ }
Irq_object::~Irq_object()
{
if (_sigh_cap.valid())
deassociate(irq_sel());
/* revoke IRQ SM */
Nova::revoke(Nova::Obj_crd(_kernel_caps, KERNEL_CAP_COUNT_LOG2));
enum { NO_REVOKE_REQUIRED = false };
cap_map()->remove(_kernel_caps, KERNEL_CAP_COUNT_LOG2, NO_REVOKE_REQUIRED);
}
/***************************
** IRQ session component **
***************************/
static Nova::Hip * kernel_hip()
{
/**
* Initial value of esp register, saved by the crt0 startup code.
* This value contains the address of the hypervisor information page.
*/
extern addr_t __initial_sp;
return reinterpret_cast<Nova::Hip *>(__initial_sp);
}
Irq_session_component::Irq_session_component(Range_allocator *irq_alloc,
const char *args)
:
_irq_number(~0U), _irq_alloc(irq_alloc)
{
long irq_number = Arg_string::find_arg(args, "irq_number").long_value(-1);
long device_phys = Arg_string::find_arg(args, "device_config_phys").long_value(0);
if (device_phys) {
if ((unsigned long)irq_number >= kernel_hip()->sel_gsi)
throw Service_denied();
irq_number = kernel_hip()->sel_gsi - 1 - irq_number;
/* XXX last GSI number unknown - assume 40 GSIs (depends on IO-APIC) */
if (irq_number < 40)
throw Service_denied();
}
if (!irq_alloc || irq_alloc->alloc_addr(1, irq_number).error()) {
error("unavailable IRQ ", irq_number, " requested");
throw Service_denied();
}
_irq_number = irq_number;
_irq_object.start(_irq_number, device_phys);
}
Irq_session_component::~Irq_session_component()
{
if (_irq_number == ~0U)
return;
Genode::addr_t free_irq = _irq_number;
_irq_alloc->free((void *)free_irq);
}
void Irq_session_component::ack_irq()
{
_irq_object.ack_irq();
}
void Irq_session_component::sigh(Genode::Signal_context_capability cap)
{
_irq_object.sigh(cap);
}
Genode::Irq_session::Info Irq_session_component::info()
{
if (!_irq_object.msi_address() || !_irq_object.msi_value())
return { .type = Genode::Irq_session::Info::Type::INVALID };
return {
.type = Genode::Irq_session::Info::Type::MSI,
.address = _irq_object.msi_address(),
.value = _irq_object.msi_value()
};
}