Stefan Kalkowski e223be32ce dde_linux: decouple Lx_kit::Irq and Platform::Device
The Lx_kit::Irq abstraction of DDE Linux was always using the very first
interrupt of a Platform::Device stored in it. Instead of handing over the
platform device it is much more flexible and sane to hand over the actual
interrupt capability.

Ref #3865
2020-08-25 11:50:12 +02:00

288 lines
6.0 KiB
C++

/*
* \brief Signal context for IRQ's
* \author Josef Soentgen
* \author Christian Helmuth
* \author Stefan Kalkowski
* \date 2014-10-14
*/
/*
* Copyright (C) 2014-2017 Genode Labs GmbH
*
* This file is distributed under the terms of the GNU General Public License
* version 2.
*/
/* Genode includes */
#include <base/entrypoint.h>
#include <base/snprintf.h>
#include <base/tslab.h>
#include <irq_session/client.h>
/* Linux emulation environment */
#include <lx_emul.h>
/* Linux kit includes */
#include <lx_kit/irq.h>
#include <lx_kit/scheduler.h>
namespace Lx_kit { class Irq; }
class Lx_kit::Irq : public Lx::Irq
{
private:
/**
* Helper utilty for composing IRQ related names
*/
struct Name_composer
{
char name[16];
Name_composer(unsigned number)
{
Genode::snprintf(name, sizeof(name),
"irq_%02x", number);
}
};
/**
* This contains the Linux-driver handlers
*/
class Handler : public Lx_kit::List<Handler>::Element
{
private:
void *_dev; /* Linux device */
unsigned int _irq; /* Linux IRQ number */
irq_handler_t _handler; /* Linux handler */
irq_handler_t _thread_fn; /* Linux thread function */
public:
Handler(void *dev, unsigned int irq, irq_handler_t handler,
irq_handler_t thread_fn)
: _dev(dev), _irq(irq), _handler(handler),
_thread_fn(thread_fn) { }
bool handle()
{
if (!_handler) {
/* on Linux, having no handler implies IRQ_WAKE_THREAD */
_thread_fn(_irq, _dev);
return true;
}
switch (_handler(_irq, _dev)) {
case IRQ_WAKE_THREAD:
_thread_fn(_irq, _dev);
case IRQ_HANDLED:
return true;
case IRQ_NONE:
break;
}
return false;
}
};
public:
/**
* Context encapsulates the handling of an IRQ
*/
class Context : public Lx_kit::List<Context>::Element
{
private:
Name_composer _name;
unsigned int _irq;
Genode::Irq_session_client _irq_sess;
Lx_kit::List<Handler> _handler;
Lx::Task _task;
bool _irq_enabled;
bool _irq_ack_pending;
Genode::Signal_handler<Context> _dispatcher;
static void _run_irq(void *args)
{
Context *ctx = static_cast<Context*>(args);
while (1) {
Lx::scheduler().current()->block_and_schedule();
ctx->handle_irq();
}
}
public:
/**
* Constructor
*/
Context(Genode::Entrypoint &ep,
Genode::Irq_session_capability cap,
unsigned int irq)
:
_name(irq),
_irq(irq),
_irq_sess(cap),
_task(_run_irq, this, _name.name, Lx::Task::PRIORITY_3, Lx::scheduler()),
_irq_enabled(true),
_irq_ack_pending(false),
_dispatcher(ep, *this, &Context::unblock)
{
_irq_sess.sigh(_dispatcher);
/* initial ack to receive further IRQ signals */
_irq_sess.ack_irq();
}
/**
* Unblock this context, e.g., as result of an IRQ signal
*/
void unblock()
{
_task.unblock();
/* kick off scheduling */
Lx::scheduler().schedule();
}
/**
* Handle IRQ
*/
void handle_irq()
{
if (_irq_enabled) {
/* report IRQ to all clients */
for (Handler *h = _handler.first(); h; h = h->next())
h->handle();
_irq_sess.ack_irq();
} else {
/*
* IRQs are disabled by not acknowledging, so one IRQ
* can still occur in the 'disabled' state. It must be
* acknowledged later by 'enable_irq()'.
*/
_irq_ack_pending = true;
}
}
/**
* Add linux handler to context
*/
void add_handler(Handler *h) { _handler.append(h); }
bool irq(unsigned int irq) {
return (irq == _irq); }
void disable_irq()
{
_irq_enabled = false;
}
void enable_irq()
{
if (_irq_enabled)
return;
if (_irq_ack_pending) {
_irq_sess.ack_irq();
_irq_ack_pending = false;
}
_irq_enabled = true;
}
};
private:
using Context_slab = Genode::Tslab<Context, 3 * sizeof(Context)>;
using Handler_slab = Genode::Tslab<Handler, 3 * sizeof(Handler)>;
Genode::Entrypoint &_ep;
Lx_kit::List<Context> _list;
Context_slab _context_alloc;
Handler_slab _handler_alloc;
/**
* Find context for given IRQ number
*/
Context *_find_context(unsigned int irq)
{
for (Context *i = _list.first(); i; i = i->next())
if (i->irq(irq)) return i;
return nullptr;
}
Irq(Genode::Entrypoint &ep, Genode::Allocator &alloc)
: _ep(ep),
_context_alloc(&alloc),
_handler_alloc(&alloc) { }
public:
static Irq &irq(Genode::Entrypoint &ep, Genode::Allocator &alloc)
{
static Irq inst(ep, alloc);
return inst;
}
/***********************
** Lx::Irq interface **
***********************/
void request_irq(Genode::Irq_session_capability cap,
unsigned int irq,
irq_handler_t handler,
void * dev_id,
irq_handler_t thread_fn = 0) override
{
Context *ctx = _find_context(irq);
/* if this IRQ is not registered */
if (!ctx) {
ctx = new (&_context_alloc) Context(_ep, cap, irq);
_list.insert(ctx);
}
/* register Linux handler */
Handler *h = new (&_handler_alloc)
Handler(dev_id, irq, handler, thread_fn);
ctx->add_handler(h);
}
void inject_irq(unsigned int irq)
{
Context *ctx = _find_context(irq);
if (ctx) ctx->unblock();
}
void disable_irq(unsigned int irq)
{
Context *ctx = _find_context(irq);
if (ctx) ctx->disable_irq();
}
void enable_irq(unsigned int irq)
{
Context *ctx = _find_context(irq);
if (ctx) ctx->enable_irq();
}
};
/****************************
** Lx::Irq implementation **
****************************/
Lx::Irq &Lx::Irq::irq(Genode::Entrypoint *ep, Genode::Allocator *alloc) {
return Lx_kit::Irq::irq(*ep, *alloc); }