blob: a7e2cb8197460263e351d405b69bc328ba2f2af5 [file] [log] [blame] [edit]
/*
* Copyright (C) 2016, Red Hat Inc, Andrew Jones <drjones@redhat.com>
*
* This work is licensed under the terms of the GNU LGPL, version 2.
*/
#include <asm/gic.h>
#include <asm/io.h>
#include <alloc_page.h>
void gicv3_set_redist_base(size_t stride)
{
u32 aff = mpidr_compress(get_mpidr());
u64 typer;
int i = 0;
while (gicv3_data.redist_bases[i]) {
void *ptr = gicv3_data.redist_bases[i];
do {
typer = gicv3_read_typer(ptr + GICR_TYPER);
if ((typer >> 32) == aff) {
gicv3_redist_base() = ptr;
return;
}
ptr += stride; /* skip RD_base, SGI_base, etc. */
} while (!(typer & GICR_TYPER_LAST));
++i;
}
/* should never reach here */
assert(0);
}
void gicv3_enable_defaults(void)
{
void *dist = gicv3_dist_base();
void *sgi_base;
unsigned int i;
gicv3_data.irq_nr = GICD_TYPER_IRQS(readl(dist + GICD_TYPER));
if (gicv3_data.irq_nr > 1020)
gicv3_data.irq_nr = 1020;
writel(0, dist + GICD_CTLR);
gicv3_dist_wait_for_rwp();
writel(GICD_CTLR_ARE_NS | GICD_CTLR_ENABLE_G1A | GICD_CTLR_ENABLE_G1,
dist + GICD_CTLR);
gicv3_dist_wait_for_rwp();
for (i = 0; i < gicv3_data.irq_nr; i += 4)
writel(~0, dist + GICD_IGROUPR + i);
if (!gicv3_redist_base())
gicv3_set_redist_base(SZ_64K * 2);
sgi_base = gicv3_sgi_base();
writel(~0, sgi_base + GICR_IGROUPR0);
for (i = 0; i < 16; i += 4)
writel(GICD_INT_DEF_PRI_X4, sgi_base + GICR_IPRIORITYR0 + i);
writel(GICD_INT_EN_SET_SGI, sgi_base + GICR_ISENABLER0);
gicv3_write_pmr(GICC_INT_PRI_THRESHOLD);
gicv3_write_grpen1(1);
}
u32 gicv3_iar_irqnr(u32 iar)
{
return iar & ((1 << 24) - 1);
}
void gicv3_ipi_send_mask(int irq, const cpumask_t *dest)
{
u16 tlist;
int cpu;
assert(irq < 16);
/*
* For each cpu in the mask collect its peers, which are also in
* the mask, in order to form target lists.
*/
for_each_cpu(cpu, dest) {
u64 mpidr = cpus[cpu], sgi1r;
u64 cluster_id;
/*
* GICv3 can send IPIs to up 16 peer cpus with a single
* write to ICC_SGI1R_EL1 (using the target list). Peers
* are cpus that have nearly identical MPIDRs, the only
* difference being Aff0. The matching upper affinity
* levels form the cluster ID.
*/
cluster_id = mpidr & ~0xffUL;
tlist = 0;
/*
* Sort of open code for_each_cpu in order to have a
* nested for_each_cpu loop.
*/
while (cpu < nr_cpus) {
if ((mpidr & 0xff) >= 16) {
printf("cpu%d MPIDR:aff0 is %d (>= 16)!\n",
cpu, (int)(mpidr & 0xff));
break;
}
tlist |= 1 << (mpidr & 0xf);
cpu = cpumask_next(cpu, dest);
if (cpu >= nr_cpus)
break;
mpidr = cpus[cpu];
if (cluster_id != (mpidr & ~0xffUL)) {
/*
* The next cpu isn't in our cluster. Roll
* back the cpu index allowing the outer
* for_each_cpu to find it again with
* cpumask_next
*/
--cpu;
break;
}
}
/* Send the IPIs for the target list of this cluster */
sgi1r = (MPIDR_TO_SGI_AFFINITY(cluster_id, 3) |
MPIDR_TO_SGI_AFFINITY(cluster_id, 2) |
irq << 24 |
MPIDR_TO_SGI_AFFINITY(cluster_id, 1) |
tlist);
gicv3_write_sgi1r(sgi1r);
}
/* Force the above writes to ICC_SGI1R_EL1 to be executed */
isb();
}
void gicv3_ipi_send_single(int irq, int cpu)
{
cpumask_t dest;
cpumask_clear(&dest);
cpumask_set_cpu(cpu, &dest);
gicv3_ipi_send_mask(irq, &dest);
}
#if defined(__aarch64__)
/*
* alloc_lpi_tables - Allocate LPI config and pending tables
* and set PROPBASER (shared by all rdistributors) and per
* redistributor PENDBASER.
*
* gicv3_set_redist_base() must be called before
*/
void gicv3_lpi_alloc_tables(void)
{
unsigned long n = SZ_64K >> PAGE_SHIFT;
unsigned long order = fls(n);
u64 prop_val;
int cpu;
assert(gicv3_redist_base());
gicv3_data.lpi_prop = alloc_pages(order);
/* ID bits = 13, ie. up to 14b LPI INTID */
prop_val = (u64)(virt_to_phys(gicv3_data.lpi_prop)) | 13;
for_each_present_cpu(cpu) {
u64 pend_val;
void *ptr;
ptr = gicv3_data.redist_base[cpu];
writeq(prop_val, ptr + GICR_PROPBASER);
gicv3_data.lpi_pend[cpu] = alloc_pages(order);
pend_val = (u64)(virt_to_phys(gicv3_data.lpi_pend[cpu]));
writeq(pend_val, ptr + GICR_PENDBASER);
}
}
void gicv3_lpi_set_clr_pending(int rdist, int n, bool set)
{
u8 *ptr = gicv3_data.lpi_pend[rdist];
u8 mask = 1 << (n % 8), byte;
ptr += (n / 8);
byte = *ptr;
if (set)
byte |= mask;
else
byte &= ~mask;
*ptr = byte;
}
static void gicv3_lpi_rdist_ctrl(u32 redist, bool set)
{
void *ptr;
u64 val;
assert(redist < nr_cpus);
ptr = gicv3_data.redist_base[redist];
val = readl(ptr + GICR_CTLR);
if (set)
val |= GICR_CTLR_ENABLE_LPIS;
else
val &= ~GICR_CTLR_ENABLE_LPIS;
writel(val, ptr + GICR_CTLR);
}
void gicv3_lpi_rdist_enable(int redist)
{
gicv3_lpi_rdist_ctrl(redist, true);
}
void gicv3_lpi_rdist_disable(int redist)
{
gicv3_lpi_rdist_ctrl(redist, false);
}
#endif /* __aarch64__ */