genode/repos/base-hw/src/include/hw/spec/arm/gicv3.h

271 lines
6.4 KiB
C++

/*
* \brief GICv3 interrupt controller for core
* \author Sebastian Sumpf
* \date 2019-07-08
*/
/*
* Copyright (C) 2019 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 _SRC__INCLUDE__HW__SPEC__ARM__GIC_V3_H_
#define _SRC__INCLUDE__HW__SPEC__ARM__GIC_V3_H_
#include <util/mmio.h>
namespace Hw { class Pic; }
#define SYSTEM_REGISTER(sz, name, reg, ...) \
struct name : Genode::Register<sz> \
{ \
static access_t read() \
{ \
access_t v; \
asm volatile ("mrs %0, " reg : "=r" (v)); \
return v; \
} \
\
static void write(access_t const v) { \
asm volatile ("msr " reg ", %0" :: "r" (v)); } \
\
__VA_ARGS__; \
};
class Hw::Pic
{
protected:
static constexpr unsigned min_spi = 32;
static constexpr unsigned spurious_id = 1023;
struct Distributor : Genode::Mmio
{
static constexpr unsigned nr_of_irq = 1024;
/**
* Control register (secure access)
*/
/* XXX: CAUTION this is different in EL3/EL2/EL1! */
struct Ctlr : Register<0x0, 32>
{
struct Enable_grp0 : Bitfield<0, 1> { };
struct Enable_grp1_a : Bitfield<1, 1> { };
struct Are_ns : Bitfield<5, 1> { };
struct Rwp : Bitfield<31, 1> { };
};
struct Typer : Register<0x004, 32> {
struct It_lines_number : Bitfield<0,5> { }; };
struct Igroup0r : Register_array<0x80, 32, nr_of_irq, 1> {
struct Group1 : Bitfield<0, 1> { }; };
/**
* Interrupt Set-Enable register
*/
struct Isenabler : Register_array<0x100, 32, nr_of_irq, 1, true> {
struct Set_enable : Bitfield<0, 1> { }; };
/**
* Interrupt clear enable registers
*/
struct Icenabler : Register_array<0x180, 32, nr_of_irq, 1, true> {
struct Clear_enable : Bitfield<0, 1> { }; };
/**
* Interrupt clear pending registers
*/
struct Icpendr : Register_array<0x280, 32, nr_of_irq, 1, true> {
struct Clear_pending : Bitfield<0, 1> { }; };
/**
* Interrupt priority level registers
*/
struct Ipriorityr : Register_array<0x400, 32, nr_of_irq, 8> {
struct Priority : Bitfield<0, 8> { }; };
struct Icfgr : Register_array<0xc00, 32, nr_of_irq, 2> {
struct Edge_triggered : Bitfield<1, 1> { }; };
struct Irouter : Register_array<0x6000, 64, 1020, 64, true> { };
void wait_for_rwp()
{
for (unsigned i = 0; i < 1000; i++)
if (read<Ctlr::Rwp>() == 0)
return;
}
unsigned max_irq() { return 32 * (read<Typer::It_lines_number>() + 1) - 1; }
Distributor(Genode::addr_t const base) : Genode::Mmio(base)
{ }
};
struct Redistributor : Genode::Mmio
{
struct Ctlr : Register<0x0, 32>
{
struct Uwp : Bitfield<31, 1> { };
};
Redistributor(Genode::addr_t const base) : Genode::Mmio(base)
{ }
/* wait for upstream writes */
void wait_for_uwp()
{
for (unsigned i = 0; i < 1000; i++)
if (read<Ctlr::Uwp>() == 0)
return;
}
};
struct Redistributor_sgi_ppi : Genode::Mmio
{
struct Igroupr0 : Register<0x80, 32> { };
struct Isenabler0 : Register_array<0x100, 32, min_spi, 1, true>
{ };
struct Icenabler0 : Register_array<0x180, 32, min_spi, 1, true>
{ };
struct Icactiver0 : Register<0x380, 32, true> { };
struct Ipriorityr : Register_array<0x400, 32, min_spi, 8> {
struct Priority : Bitfield<0, 8> { }; };
struct Icfgr1 : Register<0xc04, 32> { };
Redistributor_sgi_ppi(Genode::addr_t const base) : Genode::Mmio(base)
{
}
};
struct Cpu_interface
{
SYSTEM_REGISTER(32, Icc_sre_el1, "S3_0_C12_C12_5",
struct Sre : Bitfield<0, 1> { };
);
SYSTEM_REGISTER(32, Icc_iar1_el1, "S3_0_C12_C12_0");
SYSTEM_REGISTER(32, Icc_br1_el1, "S3_0_C12_C12_3");
SYSTEM_REGISTER(32, Icc_pmr_el1, "S3_0_C4_C6_0");
SYSTEM_REGISTER(32, Icc_igrpen1_el1, "S3_0_C12_C12_7");
SYSTEM_REGISTER(32, Icc_eoir1_el1, "S3_0_C12_C12_1");
SYSTEM_REGISTER(64, Icc_sgi1r_el1, "S3_0_C12_C11_5");
void init()
{
/* enable register access */
Icc_sre_el1::access_t sre = Icc_sre_el1::read();
Icc_sre_el1::Sre::set(sre, 1);
Icc_sre_el1::write(sre);
/* XXX: check if needed or move somewhere else */
asm volatile("isb sy" ::: "memory");
/* no priority grouping */
Icc_br1_el1::write(0);
/* allow all priorities */
Icc_pmr_el1::write(0xff);
/* enable GRP1 interrupts */
Icc_igrpen1_el1::write(1);
/* XXX: check if needed or move somewhere else */
asm volatile("isb sy" ::: "memory");
}
};
void _redistributor_init()
{
/* diactivate SGI/PPI */
_redistr_sgi.write<Redistributor_sgi_ppi::Icactiver0>(~0u);
for (unsigned i = 0; i < min_spi; i++) {
_redistr_sgi.write<Redistributor_sgi_ppi::Ipriorityr::Priority>(0xa0, i); }
/* set group 1 for all PPI/SGIs */
_redistr_sgi.write<Redistributor_sgi_ppi::Igroupr0>(~0);
/* disable SGI/PPI */
_redistr_sgi.write<Redistributor_sgi_ppi::Icenabler0>(~0);
/* set PPIs to level triggered */
_redistr_sgi.write<Redistributor_sgi_ppi::Icfgr1>(0);
_redistr.wait_for_uwp();
}
Distributor _distr;
Redistributor _redistr;
Redistributor_sgi_ppi _redistr_sgi;
Cpu_interface _cpui { };
unsigned const _max_irq;
Cpu_interface::Icc_iar1_el1::access_t _last_iar { spurious_id };
bool _valid(unsigned const irq_id) const { return irq_id <= _max_irq; }
public:
Pic();
enum { IPI = 0 };
enum { NR_OF_IRQ = Distributor::nr_of_irq };
bool take_request(unsigned &irq)
{
_last_iar = Cpu_interface::Icc_iar1_el1::read();
irq = _last_iar;
return _valid(irq);
}
void finish_request()
{
Cpu_interface::Icc_eoir1_el1::write(_last_iar);
_last_iar = spurious_id;
}
void unmask(unsigned const irq_id, unsigned const /* cpu_id */)
{
if (irq_id < min_spi) {
_redistr_sgi.write<Redistributor_sgi_ppi::Isenabler0>(1, irq_id);
} else {
_distr.write<Distributor::Isenabler::Set_enable>(1, irq_id);
}
}
void mask(unsigned const irq_id)
{
if (irq_id < min_spi) {
_redistr_sgi.write<Redistributor_sgi_ppi::Icenabler0>(1, irq_id);
} else {
_distr.write<Distributor::Icenabler::Clear_enable>(1, irq_id);
}
}
void irq_mode(unsigned, unsigned, unsigned) { }
void send_ipi(unsigned const cpu_id)
{
Cpu_interface::Icc_sgi1r_el1::write(1ULL << cpu_id);
}
};
#undef SYSTEM_REGISTER
#endif /* _SRC__INCLUDE__HW__SPEC__ARM__GIC_V3_H_ */