blob: 94b1bc3160a65e0799f423e1424be7a893463027 [file] [log] [blame]
// Copyright 2016 The Fuchsia Authors
// Copyright (c) 2016, Google Inc. All rights reserved.
//
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file or at
// https://opensource.org/licenses/MIT
#include <assert.h>
#include <dev/interrupt/arm_gic.h>
#include <dev/interrupt/arm_gicv2m.h>
#include <dev/interrupt/arm_gic_regs.h>
#include <err.h>
#include <string.h>
#include <trace.h>
#define LOCAL_TRACE 0
// Section 9.7
#define MSI_TYPER_OFFSET (0x008) // Type Register
#define MSI_SETSPI_NS_OFFSET (0x040) // Doorbell register (whack here for interrupt)
#define MSI_IIDR_OFFSET (0xFCC) // Interface ID register
#define REG_RD(base, off) (((volatile uint32_t*)(base))[(off) >> 2])
// Section 9.9.1
#define MIN_VALID_MSI_SPI (32)
#define MAX_VALID_MSI_SPI (1020)
const paddr_t* g_reg_frames;
const vaddr_t* g_reg_frames_virt;
uint g_reg_frame_count;
void arm_gicv2m_init(const paddr_t* reg_frames, const vaddr_t* reg_frames_virt, const uint reg_frame_count) {
// Protect against double init.
DEBUG_ASSERT(!g_reg_frames);
DEBUG_ASSERT(!g_reg_frame_count);
// If the user has no register frames, they should be using arm_gic, not
// arm_gicv2m
DEBUG_ASSERT(reg_frames);
DEBUG_ASSERT(reg_frame_count);
// Stash the frame info
g_reg_frames = reg_frames;
g_reg_frames_virt = reg_frames_virt;
g_reg_frame_count = reg_frame_count;
// Walk the list of regions, and make sure that all of the controlled SPIs
// are configured for edge triggered mode.
for (uint i = 0; i < g_reg_frame_count; ++i) {
uint32_t type_reg = REG_RD(g_reg_frames_virt[i], MSI_TYPER_OFFSET);
uint base_spi = (type_reg >> 16) & 0x3FF;
uint num_spi = type_reg & 0x3FF;
for (uint i = 0; i < num_spi; ++i) {
uint spi_id = base_spi + i;
if ((spi_id < MIN_VALID_MSI_SPI) || (spi_id > MAX_VALID_MSI_SPI)) {
TRACEF("Invalid SPI ID (%u) found in GICv2m register frame @%p\n",
spi_id, (void*)g_reg_frames[i]);
continue;
}
uint reg_ndx = spi_id >> 4;
uint bit_shift = ((spi_id & 0xF) << 1) + 1;
uint32_t reg_val = GICREG(0, GICD_ICFGR(reg_ndx));
reg_val |= (0x1u << bit_shift);
GICREG(0, GICD_ICFGR(reg_ndx)) = reg_val;
}
}
}
status_t arm_gicv2m_get_frame_info(const uint frame_ndx, arm_gicv2m_frame_info_t* out_info) {
if (!out_info)
return ZX_ERR_INVALID_ARGS;
memset(out_info, 0, sizeof(*out_info));
if (!g_reg_frames || !g_reg_frame_count)
return ZX_ERR_UNAVAILABLE;
if (frame_ndx >= g_reg_frame_count)
return ZX_ERR_NOT_FOUND;
uint32_t type_reg = REG_RD(g_reg_frames_virt[frame_ndx], MSI_TYPER_OFFSET);
uint base_spi = (type_reg >> 16) & 0x3FF;
uint num_spi = type_reg & 0x3FF;
uint last_spi = base_spi + num_spi - 1;
if (!num_spi ||
(base_spi < MIN_VALID_MSI_SPI) ||
(last_spi > MAX_VALID_MSI_SPI))
return ZX_ERR_BAD_STATE;
out_info->start_spi_id = base_spi;
out_info->end_spi_id = last_spi;
out_info->doorbell = g_reg_frames[frame_ndx] + MSI_SETSPI_NS_OFFSET;
out_info->iid = REG_RD(g_reg_frames_virt[frame_ndx], MSI_IIDR_OFFSET);
return ZX_OK;
}