genode/repos/os/include/os/pin_driver.h
Norman Feske f2a627c107 os: Pin_control and Pin_state session interfaces
The new interfaces are meant to gradually replace the existing
Gpio_session interface.

- Each session refers to a single pin.
- The session types distiguish the direction of the signal as input or
  output.
- Pin coordinates can be selected via session labels.
- GPIO interrupts are covered by the regular IRQ session interface.

The interfaces are accompanied by framework utilities and interfaces:

- os/pin_driver.h
- pin_control_session/component.h
- pin_state_session/component.h

These headers relieve GPIO drivers from implementing boilerplate code by
providing device-agnostic portions. The A64 pio driver serves as
reference for using those utilities.

  https://github.com/nfeske/genode-allwinner/tree/master/src/drivers/pin/a64

Fixes #4315
2021-11-29 15:10:52 +01:00

358 lines
7.8 KiB
C++

/*
* \brief Utilities and interfaces for implementing pin drivers
* \author Norman Feske
* \date 2021-04-20
*/
/*
* Copyright (C) 2021 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.
*/
#ifndef _INCLUDE__OS__PIN_DRIVER_H_
#define _INCLUDE__OS__PIN_DRIVER_H_
#include <util/reconstructible.h>
#include <base/session_label.h>
#include <base/registry.h>
#include <root/component.h>
#include <irq_session/irq_session.h>
namespace Pin {
using namespace Genode;
enum class Direction { IN, OUT };
template <typename> struct Driver;
template <typename> struct Assignment;
template <typename, Direction> class Root;
template <typename> class Irq_session_component;
template <typename> class Irq_root;
template <typename ID>
struct Irq_root : Pin::Root<Irq_session_component<ID>, Direction::IN>
{
using Pin::Root<Irq_session_component<ID>, Direction::IN>::Root;
};
}
template <typename ID>
struct Pin::Driver : Interface
{
/**
* Request pin state
*/
virtual bool pin_state(ID) const = 0;
/**
* Set pin state
*/
virtual void pin_state(ID, bool enabled) = 0;
/**
* Return pin ID assigned to the specified session label
*
* \throw Service_denied
*/
virtual ID assigned_pin(Session_label, Direction) const = 0;
/**
* Inform driver that the specified pin is in use
*
* The driver may use this information to maintain a reference counter
* per pin. The specified direction allows the driver to select one of
* multiple pin declarations used for time-multiplexing a pin between input
* and output.
*/
virtual void acquire_pin(ID, Direction) { };
/**
* Inform driver that the specified pin is not longer in use
*
* This is the counter part of 'acquire_pin'.
*
* When releasing an output pin, the driver may respond by resetting the
* pin to its default state.
*/
virtual void release_pin(ID, Direction) = 0;
/**
* Enable/disable interrupt for specified pin
*/
virtual void irq_enabled(ID, bool) = 0;
/**
* Return true if IRQ is pending for specified pin
*/
virtual bool irq_pending(ID) const = 0;
/**
* Acknowledge interrupt of specified pin
*/
virtual void ack_irq(ID) = 0;
struct Irq_subscriber : Interface
{
ID id;
Signal_context_capability sigh;
bool outstanding_ack = false;
void submit_irq()
{
Signal_transmitter(sigh).submit();
outstanding_ack = true;
}
Irq_subscriber(ID id, Signal_context_capability sigh)
: id(id), sigh(sigh) { }
};
Registry<Registered<Irq_subscriber>> irq_subscribers { };
void deliver_pin_irqs()
{
irq_subscribers.for_each([&] (Irq_subscriber &subscriber) {
if (subscriber.outstanding_ack || !irq_pending(subscriber.id))
return;
/* mask IRQ until acked */
irq_enabled(subscriber.id, false);
subscriber.submit_irq();
});
}
};
template <typename ID>
struct Pin::Assignment : Noncopyable
{
Driver<ID> &driver;
struct Target
{
ID id;
Pin::Direction direction;
Target(ID id, Pin::Direction dir) : id(id), direction(dir) { }
bool operator != (Target const &other) const
{
return (id != other.id) || (direction != other.direction);
}
};
Constructible<Target> target { };
void _release()
{
if (target.constructed()) {
driver.release_pin(target->id, target->direction);
target.destruct();
}
}
Assignment(Driver<ID> &driver) : driver(driver) { }
~Assignment() { _release(); }
enum class Update { UNCHANGED, CHANGED };
/**
* Trigger an update of the pin assignment
*
* This method called in response to dynamic configuration changes.
*
* \return CHANGED if assignment to physical pin was modified
*/
Update update(Session_label const &label, Pin::Direction direction)
{
bool changed = false;
try {
/*
* \throw Service_denied
*/
Target const new_target { driver.assigned_pin(label, direction), direction };
/* assignment changed from one pin to another */
if (target.constructed() && (*target != new_target)) {
_release();
changed = true;
}
if (!target.constructed()) {
driver.acquire_pin(new_target.id, new_target.direction);
target.construct(new_target.id, new_target.direction);
changed = true;
}
}
catch (Service_denied) { _release(); }
return changed ? Update::CHANGED : Update::UNCHANGED;
}
};
/**
* Common root component for 'Pin_state' and 'Pin_control' services
*
* \param SC session-component type
*/
template <typename SC, Pin::Direction DIRECTION>
class Pin::Root : Genode::Root_component<SC>
{
private:
using Pin_id = typename SC::Pin_id;
using Driver = Pin::Driver<Pin_id>;
using Session = Registered<SC>;
Entrypoint &_ep;
Driver &_driver;
Registry<Session> _sessions { };
SC *_create_session(char const *args) override
{
return new (Root_component<SC>::md_alloc())
Session(_sessions, _ep,
session_resources_from_args(args),
session_label_from_args(args),
session_diag_from_args(args),
_driver);
}
public:
Root(Env &env, Allocator &alloc, Driver &driver)
:
Genode::Root_component<SC>(env.ep(), alloc),
_ep(env.ep()), _driver(driver)
{
env.parent().announce(_ep.manage(*this));
}
~Root() { _ep.dissolve(*this); }
void update_assignments()
{
_sessions.for_each([&] (Session &session) {
session.update_assignment(); });
}
};
template <typename ID>
class Pin::Irq_session_component : public Session_object<Irq_session>
{
public:
using Pin_id = ID; /* expected by 'Pin::Root' */
private:
using Irq_subscriber = typename Driver<ID>::Irq_subscriber;
using Assignment = Pin::Assignment<Pin_id>;
Assignment _assignment;
Signal_context_capability _sigh { };
Constructible<Registered<Irq_subscriber>> _subscriber { };
void _ack_dangling_irq()
{
if (_subscriber.constructed() && _subscriber->outstanding_ack)
ack_irq();
}
public:
Irq_session_component(Entrypoint &ep, Resources const &resources,
Label const &label, Diag &diag, Driver<ID> &driver)
:
Session_object<Irq_session>(ep, resources, label, diag),
_assignment(driver)
{
update_assignment();
}
~Irq_session_component() { _ack_dangling_irq(); }
void update_assignment()
{
if (_assignment.target.constructed())
_assignment.driver.irq_enabled(_assignment.target->id, false);
typename Assignment::Update const update_result =
_assignment.update(label(), Pin::Direction::IN);
if (update_result == Assignment::Update::CHANGED) {
_ack_dangling_irq();
if (_assignment.target.constructed())
_subscriber.construct(_assignment.driver.irq_subscribers,
_assignment.target->id, _sigh);
else
_subscriber.destruct();
}
bool const charged = _assignment.target.constructed()
&& _subscriber.constructed()
&& !_subscriber->outstanding_ack;
if (charged)
_assignment.driver.irq_enabled(_assignment.target->id, true);
}
/**
* Irq_session interface
*/
void ack_irq() override
{
if (_assignment.target.constructed()) {
_assignment.driver.ack_irq(_assignment.target->id);
_assignment.driver.irq_enabled(_assignment.target->id, true);
}
if (_subscriber.constructed())
_subscriber->outstanding_ack = false;
}
/**
* Irq_session interface
*/
void sigh(Signal_context_capability sigh) override
{
_sigh = sigh;
if (_subscriber.constructed())
_subscriber->sigh = sigh;
update_assignment();
bool initial_irq = _subscriber.constructed()
&& _assignment.target.constructed()
&& _assignment.driver.irq_pending(_assignment.target->id);
if (initial_irq)
_subscriber->submit_irq();
}
/**
* Irq_session interface
*/
Info info() override { return Info { }; }
};
#endif /* _INCLUDE__OS__PIN_DRIVER_H_ */