dde_linux: lx_emul interface for GPIO pin access

This patch introduces the lx_emul/pin.h interface that enables GPIO stub
drivers to interact with Genode's Pin_control and IRQ sessions via a
simple C API.

Fixes #4316
This commit is contained in:
Norman Feske 2021-11-05 14:55:20 +01:00 committed by Christian Helmuth
parent f2a627c107
commit 449f647e58
2 changed files with 281 additions and 0 deletions

View File

@ -0,0 +1,50 @@
/*
* \brief Lx_emul support for accessing GPIO pins
* \author Norman Feske
* \date 2021-11-02
*/
/*
* Copyright (C) 2021 Genode Labs GmbH
*
* This file is distributed under the terms of the GNU General Public License
* version 2.
*/
#ifndef _LX_EMUL__PIN_H_
#define _LX_EMUL__PIN_H_
#ifdef __cplusplus
extern "C" {
#endif
/**
* Set output state of GPIO pin
*
* \pin_name GPIO name used as label for corresponding 'Pin_control' session
*/
void lx_emul_pin_control(char const *pin_name, bool enabled);
/**
* Request interrupt backed by an IRQ session
*/
void lx_emul_pin_irq_unmask(unsigned gic_irq, unsigned pin_irq,
char const *pin_name);
/**
* Return pin IRQ number of most recently occurred pin interrupt
*
* This function is meant to be called by the PIO driver's interrupt handler.
*/
unsigned lx_emul_pin_last_irq(void);
/**
* Acknowledge GPIO interrupt
*/
void lx_emul_pin_irq_ack(unsigned pin_irq);
#ifdef __cplusplus
}
#endif
#endif /* _LX_EMUL__PIN_H_ */

View File

@ -0,0 +1,231 @@
/*
* \brief Lx_emul backend for accessing GPIO pins
* \author Norman Feske
* \date 2021-11-02
*/
/*
* Copyright (C) 2021 Genode Labs GmbH
*
* This file is distributed under the terms of the GNU General Public License
* version 2.
*/
/* Genode includes */
#include <base/registry.h>
#include <pin_control_session/connection.h>
#include <irq_session/connection.h>
#include <lx_emul/pin.h>
#include <lx_kit/env.h>
namespace {
using namespace Genode;
class Global_irq_controller : Noncopyable
{
private:
Lx_kit::Env &_env;
public:
struct Number { unsigned value; };
Global_irq_controller(Lx_kit::Env &env) : _env(env) { }
void trigger_irq(Number number)
{
/*
* Mirrored from 'Lx_kit::Device::Irq::handle'
*/
_env.last_irq = number.value;
_env.scheduler.unblock_irq_handler();
_env.scheduler.schedule();
}
};
using Pin_name = Session_label;
using Gic_irq_number = Global_irq_controller::Number;
struct Pin_irq_number { unsigned value; };
struct Irq_info
{
Gic_irq_number gic_irq_number;
Pin_irq_number pin_irq_number;
};
struct Pin_irq_handler : Interface
{
virtual void handle_pin_irq(Irq_info) = 0;
};
struct Pin : Interface
{
using Name = Session_label;
Env &_env;
Pin_irq_handler &_pin_irq_handler;
Irq_info _irq_info { };
Name const name;
Constructible<Pin_control::Connection> _control { };
Constructible<Irq_connection> _irq { };
Io_signal_handler<Pin> _irq_handler { _env.ep(), *this, &Pin::_handle_irq };
void _handle_irq()
{
_pin_irq_handler.handle_pin_irq(_irq_info);
}
Pin(Env &env, Name const &name, Pin_irq_handler &pin_irq_handler)
:
_env(env), _pin_irq_handler(pin_irq_handler), name(name)
{ }
void control(bool enabled)
{
if (_irq.constructed()) {
error("attempt to drive interrupt pin ", name, " as output");
return;
}
if (!_control.constructed())
_control.construct(_env, name.string());
_control->state(enabled);
}
void associate_with_gic_and_unmask_irq(Irq_info irq_info)
{
_control.destruct();
if (!_irq.constructed()) {
_irq_info = irq_info;
_irq.construct(_env, _irq_info.pin_irq_number.value);
_irq->sigh(_irq_handler);
_irq->ack_irq();
}
}
void ack_matching_irq(Pin_irq_number ack_pin)
{
if (ack_pin.value != _irq_info.pin_irq_number.value)
return;
if (_irq.constructed())
_irq->ack_irq();
}
};
struct Pins : private Pin_irq_handler
{
Env &_env;
Allocator &_alloc;
Global_irq_controller &_gic;
Registry<Registered<Pin> > _registry { };
Pin_irq_number last_irq { };
Pins(Env &env, Allocator &alloc, Global_irq_controller &gic)
:
_env(env), _alloc(alloc), _gic(gic)
{ }
template <typename FN>
void with_pin(Pin::Name const &name, FN const &fn)
{
Pin_irq_handler &pin_irq_handler = *this;
/*
* Construct 'Pin' object on demand, apply 'fn' if constructed
*/
for (unsigned i = 0; i < 2; i++) {
bool done = false;
_registry.for_each([&] (Pin &pin) {
if (pin.name == name) {
fn(pin);
done = true;
}
});
if (done)
break;
new (_alloc) Registered<Pin>(_registry, _env, name, pin_irq_handler);
/* ... apply 'fn' in second iteration */
}
}
void handle_pin_irq(Irq_info irq_info) override
{
last_irq = irq_info.pin_irq_number;
_gic.trigger_irq(irq_info.gic_irq_number);
}
void irq_ack(Pin_irq_number ack_pin_number)
{
_registry.for_each([&] (Pin &pin) {
pin.ack_matching_irq(ack_pin_number); });
}
};
};
static Pins &pins()
{
static Global_irq_controller gic { Lx_kit::env() };
static Pins pins { Lx_kit::env().env, Lx_kit::env().heap, gic };
return pins;
}
extern "C" void lx_emul_pin_control(char const *pin_name, bool enabled)
{
pins().with_pin(pin_name, [&] (Pin &pin) {
pin.control(enabled); });
}
extern "C" void lx_emul_pin_irq_unmask(unsigned gic_irq, unsigned pin_irq,
char const *pin_name)
{
/*
* Translate GIC IRQ number as known by the Linux kernel into the
* physical IRQ number expected by 'Lx_kit::Env::last_irq'.
*/
gic_irq += 32;
pins().with_pin(pin_name, [&] (Pin &pin) {
Irq_info const irq_info { .gic_irq_number = { gic_irq },
.pin_irq_number = { pin_irq } };
pin.associate_with_gic_and_unmask_irq(irq_info);
});
}
extern "C" void lx_emul_pin_irq_ack(unsigned pin_irq)
{
pins().irq_ack( Pin_irq_number { pin_irq } );
}
extern "C" unsigned lx_emul_pin_last_irq(void)
{
return pins().last_irq.value;
}