GK SDK 源码库: XMIPCLinuxV100R005C00SPC030 (kernel/tools/open_source excluded)

This commit is contained in:
lai
2026-09-06 03:52:57 +08:00
commit b1928b41c0
21813 changed files with 4413081 additions and 0 deletions
@@ -0,0 +1,186 @@
/*
* Copyright (c) 2015-2020, ARM Limited and Contributors. All rights reserved.
*
* SPDX-License-Identifier: BSD-3-Clause
*/
#include <assert.h>
#include <stdbool.h>
#include <stdint.h>
#include <arch.h>
#include <arch_helpers.h>
#include <common/debug.h>
#include <drivers/arm/cci.h>
#include <lib/mmio.h>
#define MAKE_CCI_PART_NUMBER(hi, lo) (((hi) << 8) | (lo))
#define CCI_PART_LO_MASK U(0xff)
#define CCI_PART_HI_MASK U(0xf)
/* CCI part number codes read from Peripheral ID registers 0 and 1 */
#define CCI400_PART_NUM 0x420
#define CCI500_PART_NUM 0x422
#define CCI550_PART_NUM 0x423
#define CCI400_SLAVE_PORTS 5
#define CCI500_SLAVE_PORTS 7
#define CCI550_SLAVE_PORTS 7
static uintptr_t cci_base;
static const int *cci_slave_if_map;
#if ENABLE_ASSERTIONS
static unsigned int max_master_id;
static int cci_num_slave_ports;
static bool validate_cci_map(const int *map)
{
unsigned int valid_cci_map = 0U;
int slave_if_id;
unsigned int i;
/* Validate the map */
for (i = 0U; i <= max_master_id; i++) {
slave_if_id = map[i];
if (slave_if_id < 0)
continue;
if (slave_if_id >= cci_num_slave_ports) {
ERROR("Slave interface ID is invalid\n");
return false;
}
if ((valid_cci_map & (1UL << slave_if_id)) != 0U) {
ERROR("Multiple masters are assigned same slave interface ID\n");
return false;
}
valid_cci_map |= 1UL << slave_if_id;
}
if (valid_cci_map == 0U) {
ERROR("No master is assigned a valid slave interface\n");
return false;
}
return true;
}
/*
* Read CCI part number from Peripheral ID registers
*/
static unsigned int read_cci_part_number(uintptr_t base)
{
unsigned int part_lo, part_hi;
part_lo = mmio_read_32(base + PERIPHERAL_ID0) & CCI_PART_LO_MASK;
part_hi = mmio_read_32(base + PERIPHERAL_ID1) & CCI_PART_HI_MASK;
return MAKE_CCI_PART_NUMBER(part_hi, part_lo);
}
/*
* Identify a CCI device, and return the number of slaves. Return -1 for an
* unidentified device.
*/
static int get_slave_ports(unsigned int part_num)
{
int num_slave_ports = -1;
switch (part_num) {
case CCI400_PART_NUM:
num_slave_ports = CCI400_SLAVE_PORTS;
break;
case CCI500_PART_NUM:
num_slave_ports = CCI500_SLAVE_PORTS;
break;
case CCI550_PART_NUM:
num_slave_ports = CCI550_SLAVE_PORTS;
break;
default:
/* Do nothing in default case */
break;
}
return num_slave_ports;
}
#endif /* ENABLE_ASSERTIONS */
void __init cci_init(uintptr_t base, const int *map,
unsigned int num_cci_masters)
{
assert(map != NULL);
assert(base != 0U);
cci_base = base;
cci_slave_if_map = map;
#if ENABLE_ASSERTIONS
/*
* Master Id's are assigned from zero, So in an array of size n
* the max master id is (n - 1).
*/
max_master_id = num_cci_masters - 1U;
cci_num_slave_ports = get_slave_ports(read_cci_part_number(base));
#endif
assert(cci_num_slave_ports >= 0);
assert(validate_cci_map(map));
}
void cci_enable_snoop_dvm_reqs(unsigned int master_id)
{
int slave_if_id = cci_slave_if_map[master_id];
assert(master_id <= max_master_id);
assert((slave_if_id < cci_num_slave_ports) && (slave_if_id >= 0));
assert(cci_base != 0U);
/*
* Enable Snoops and DVM messages, no need for Read/Modify/Write as
* rest of bits are write ignore
*/
mmio_write_32(cci_base +
SLAVE_IFACE_OFFSET(slave_if_id) + SNOOP_CTRL_REG,
DVM_EN_BIT | SNOOP_EN_BIT);
/*
* Wait for the completion of the write to the Snoop Control Register
* before testing the change_pending bit
*/
dsbish();
/* Wait for the dust to settle down */
while ((mmio_read_32(cci_base + STATUS_REG) & CHANGE_PENDING_BIT) != 0U)
;
}
void cci_disable_snoop_dvm_reqs(unsigned int master_id)
{
int slave_if_id = cci_slave_if_map[master_id];
assert(master_id <= max_master_id);
assert((slave_if_id < cci_num_slave_ports) && (slave_if_id >= 0));
assert(cci_base != 0U);
/*
* Disable Snoops and DVM messages, no need for Read/Modify/Write as
* rest of bits are write ignore.
*/
mmio_write_32(cci_base +
SLAVE_IFACE_OFFSET(slave_if_id) + SNOOP_CTRL_REG,
~(DVM_EN_BIT | SNOOP_EN_BIT));
/*
* Wait for the completion of the write to the Snoop Control Register
* before testing the change_pending bit
*/
dsbish();
/* Wait for the dust to settle down */
while ((mmio_read_32(cci_base + STATUS_REG) & CHANGE_PENDING_BIT) != 0U)
;
}
@@ -0,0 +1,621 @@
/*
* Copyright (c) 2015-2020, ARM Limited and Contributors. All rights reserved.
*
* SPDX-License-Identifier: BSD-3-Clause
*/
#include <assert.h>
#include <stdbool.h>
#include <arch.h>
#include <common/debug.h>
#include <drivers/arm/ccn.h>
#include <lib/bakery_lock.h>
#include <lib/mmio.h>
#include "ccn_private.h"
static const ccn_desc_t *ccn_plat_desc;
#if defined(IMAGE_BL31) || (!defined(__aarch64__) && defined(IMAGE_BL32))
DEFINE_BAKERY_LOCK(ccn_lock);
#endif
/*******************************************************************************
* This function takes the base address of the CCN's programmer's view (PV), a
* region ID of one of the 256 regions (0-255) and a register offset within the
* region. It converts the first two parameters into a base address and uses it
* to read the register at the offset.
******************************************************************************/
static inline unsigned long long ccn_reg_read(uintptr_t periphbase,
unsigned int region_id,
unsigned int register_offset)
{
uintptr_t region_base;
assert(periphbase);
assert(region_id < REGION_ID_LIMIT);
region_base = periphbase + region_id_to_base(region_id);
return mmio_read_64(region_base + register_offset);
}
/*******************************************************************************
* This function takes the base address of the CCN's programmer's view (PV), a
* region ID of one of the 256 regions (0-255), a register offset within the
* region and a value. It converts the first two parameters into a base address
* and uses it to write the value in the register at the offset.
******************************************************************************/
static inline void ccn_reg_write(uintptr_t periphbase,
unsigned int region_id,
unsigned int register_offset,
unsigned long long value)
{
uintptr_t region_base;
assert(periphbase);
assert(region_id < REGION_ID_LIMIT);
region_base = periphbase + region_id_to_base(region_id);
mmio_write_64(region_base + register_offset, value);
}
#if ENABLE_ASSERTIONS
typedef struct rn_info {
unsigned char node_desc[MAX_RN_NODES];
} rn_info_t;
/*******************************************************************************
* This function takes the base address of the CCN's programmer's view (PV) and
* the node ID of a Request Node (RN-D or RN-I). It returns the maximum number
* of master interfaces resident on that node. This number is equal to the least
* significant two bits of the node type ID + 1.
******************************************************************************/
static unsigned int ccn_get_rni_mcount(uintptr_t periphbase,
unsigned int rn_id)
{
unsigned int rn_type_id;
/* Use the node id to find the type of RN-I/D node */
rn_type_id = get_node_type(ccn_reg_read(periphbase,
rn_id + RNI_REGION_ID_START,
REGION_ID_OFFSET));
/* Return the number master interfaces based on node type */
return rn_type_id_to_master_cnt(rn_type_id);
}
/*******************************************************************************
* This function reads the CCN registers to find the following information about
* the ACE/ACELite/ACELite+DVM/CHI interfaces resident on the various types of
* Request Nodes (RN-Fs, RN-Is and RN-Ds) in the system:
*
* 1. The total number of such interfaces that this CCN IP supports. This is the
* cumulative number of interfaces across all Request node types. It is
* passed back as the return value of this function.
*
* 2. The maximum number of interfaces of a type resident on a Request node of
* one of the three types. This information is populated in the 'info'
* array provided by the caller as described next.
*
* The array has 64 entries. Each entry corresponds to a Request node. The
* Miscellaneous node's programmer's view has RN-F, RN-I and RN-D ID
* registers. For each RN-I and RN-D ID indicated as being present in these
* registers, its identification register (offset 0xFF00) is read. This
* register specifies the maximum number of master interfaces the node
* supports. For RN-Fs it is assumed that there can be only a single fully
* coherent master resident on each node. The counts for each type of node
* are use to populate the array entry at the index corresponding to the node
* ID i.e. rn_info[node ID] = <number of master interfaces>
******************************************************************************/
static unsigned int ccn_get_rn_master_info(uintptr_t periphbase,
rn_info_t *info)
{
unsigned int num_masters = 0;
rn_types_t rn_type;
assert (info);
for (rn_type = RN_TYPE_RNF; rn_type < NUM_RN_TYPES; rn_type++) {
unsigned int mn_reg_off, node_id;
unsigned long long rn_bitmap;
/*
* RN-F, RN-I, RN-D node registers in the MN region occupy
* contiguous 16 byte apart offsets.
*/
mn_reg_off = MN_RNF_NODEID_OFFSET + (rn_type << 4);
rn_bitmap = ccn_reg_read(periphbase, MN_REGION_ID, mn_reg_off);
FOR_EACH_PRESENT_NODE_ID(node_id, rn_bitmap) {
unsigned int node_mcount;
/*
* A RN-F does not have a node type since it does not
* export a programmer's interface. It can only have a
* single fully coherent master residing on it. If the
* offset of the MN(Miscellaneous Node) register points
* to a RN-I/D node then the master count is set to the
* maximum number of master interfaces that can possibly
* reside on the node.
*/
node_mcount = (mn_reg_off == MN_RNF_NODEID_OFFSET ? 1 :
ccn_get_rni_mcount(periphbase, node_id));
/*
* Use this value to increment the maximum possible
* master interfaces in the system.
*/
num_masters += node_mcount;
/*
* Update the entry in 'info' for this node ID with
* the maximum number of masters than can sit on
* it. This information will be used to validate the
* node information passed by the platform later.
*/
info->node_desc[node_id] = node_mcount;
}
}
return num_masters;
}
/*******************************************************************************
* This function validates parameters passed by the platform (in a debug build).
* It collects information about the maximum number of master interfaces that:
* a) the CCN IP can accommodate and
* b) can exist on each Request node.
* It compares this with the information provided by the platform to determine
* the validity of the latter.
******************************************************************************/
static void __init ccn_validate_plat_params(const ccn_desc_t *plat_desc)
{
unsigned int master_id, num_rn_masters;
rn_info_t info = { {0} };
assert(plat_desc);
assert(plat_desc->periphbase);
assert(plat_desc->master_to_rn_id_map);
assert(plat_desc->num_masters);
assert(plat_desc->num_masters < CCN_MAX_RN_MASTERS);
/*
* Find the number and properties of fully coherent, IO coherent and IO
* coherent + DVM master interfaces
*/
num_rn_masters = ccn_get_rn_master_info(plat_desc->periphbase, &info);
assert(plat_desc->num_masters < num_rn_masters);
/*
* Iterate through the Request nodes specified by the platform.
* Decrement the count of the masters in the 'info' array for each
* Request node encountered. If the count would drop below 0 then the
* platform's view of this aspect of CCN configuration is incorrect.
*/
for (master_id = 0; master_id < plat_desc->num_masters; master_id++) {
unsigned int node_id;
node_id = plat_desc->master_to_rn_id_map[master_id];
assert(node_id < MAX_RN_NODES);
assert(info.node_desc[node_id]);
info.node_desc[node_id]--;
}
}
#endif /* ENABLE_ASSERTIONS */
/*******************************************************************************
* This function validates parameters passed by the platform (in a debug build)
* and initialises its internal data structures. A lock is required to prevent
* simultaneous CCN operations at runtime (only BL31) to add and remove Request
* nodes from coherency.
******************************************************************************/
void __init ccn_init(const ccn_desc_t *plat_desc)
{
#if ENABLE_ASSERTIONS
ccn_validate_plat_params(plat_desc);
#endif
ccn_plat_desc = plat_desc;
}
/*******************************************************************************
* This function converts a bit map of master interface IDs to a bit map of the
* Request node IDs that they reside on.
******************************************************************************/
static unsigned long long ccn_master_to_rn_id_map(unsigned long long master_map)
{
unsigned long long rn_id_map = 0;
unsigned int node_id, iface_id;
assert(master_map);
assert(ccn_plat_desc);
FOR_EACH_PRESENT_MASTER_INTERFACE(iface_id, master_map) {
assert(iface_id < ccn_plat_desc->num_masters);
/* Convert the master ID into the node ID */
node_id = ccn_plat_desc->master_to_rn_id_map[iface_id];
/* Set the bit corresponding to this node ID */
rn_id_map |= (1ULL << node_id);
}
return rn_id_map;
}
/*******************************************************************************
* This function executes the necessary operations to add or remove Request node
* IDs specified in the 'rn_id_map' bitmap from the snoop/DVM domains specified
* in the 'hn_id_map'. The 'region_id' specifies the ID of the first HN-F/MN
* on which the operation should be performed. 'op_reg_offset' specifies the
* type of operation (add/remove). 'stat_reg_offset' specifies the register
* which should be polled to determine if the operation has completed or not.
******************************************************************************/
static void ccn_snoop_dvm_do_op(unsigned long long rn_id_map,
unsigned long long hn_id_map,
unsigned int region_id,
unsigned int op_reg_offset,
unsigned int stat_reg_offset)
{
unsigned int start_region_id;
assert(ccn_plat_desc);
assert(ccn_plat_desc->periphbase);
#if defined(IMAGE_BL31) || (!defined(__aarch64__) && defined(IMAGE_BL32))
bakery_lock_get(&ccn_lock);
#endif
start_region_id = region_id;
FOR_EACH_PRESENT_REGION_ID(start_region_id, hn_id_map) {
ccn_reg_write(ccn_plat_desc->periphbase,
start_region_id,
op_reg_offset,
rn_id_map);
}
start_region_id = region_id;
FOR_EACH_PRESENT_REGION_ID(start_region_id, hn_id_map) {
WAIT_FOR_DOMAIN_CTRL_OP_COMPLETION(start_region_id,
stat_reg_offset,
op_reg_offset,
rn_id_map);
}
#if defined(IMAGE_BL31) || (!defined(__aarch64__) && defined(IMAGE_BL32))
bakery_lock_release(&ccn_lock);
#endif
}
/*******************************************************************************
* The following functions provide the boot and runtime API to the platform for
* adding and removing master interfaces from the snoop/DVM domains. A bitmap of
* master interfaces IDs is passed as a parameter. It is converted into a bitmap
* of Request node IDs using the mapping provided by the platform while
* initialising the driver.
* For example, consider a dual cluster system where the clusters have values 0
* & 1 in the affinity level 1 field of their respective MPIDRs. While
* initialising this driver, the platform provides the mapping between each
* cluster and the corresponding Request node. To add or remove a cluster from
* the snoop and dvm domain, the bit position corresponding to the cluster ID
* should be set in the 'master_iface_map' i.e. to remove both clusters the
* bitmap would equal 0x11.
******************************************************************************/
void ccn_enter_snoop_dvm_domain(unsigned long long master_iface_map)
{
unsigned long long rn_id_map;
rn_id_map = ccn_master_to_rn_id_map(master_iface_map);
ccn_snoop_dvm_do_op(rn_id_map,
CCN_GET_HN_NODEID_MAP(ccn_plat_desc->periphbase,
MN_HNF_NODEID_OFFSET),
HNF_REGION_ID_START,
HNF_SDC_SET_OFFSET,
HNF_SDC_STAT_OFFSET);
ccn_snoop_dvm_do_op(rn_id_map,
CCN_GET_MN_NODEID_MAP(ccn_plat_desc->periphbase),
MN_REGION_ID,
MN_DDC_SET_OFFSET,
MN_DDC_STAT_OFFSET);
}
void ccn_exit_snoop_dvm_domain(unsigned long long master_iface_map)
{
unsigned long long rn_id_map;
rn_id_map = ccn_master_to_rn_id_map(master_iface_map);
ccn_snoop_dvm_do_op(rn_id_map,
CCN_GET_HN_NODEID_MAP(ccn_plat_desc->periphbase,
MN_HNF_NODEID_OFFSET),
HNF_REGION_ID_START,
HNF_SDC_CLR_OFFSET,
HNF_SDC_STAT_OFFSET);
ccn_snoop_dvm_do_op(rn_id_map,
CCN_GET_MN_NODEID_MAP(ccn_plat_desc->periphbase),
MN_REGION_ID,
MN_DDC_CLR_OFFSET,
MN_DDC_STAT_OFFSET);
}
void ccn_enter_dvm_domain(unsigned long long master_iface_map)
{
unsigned long long rn_id_map;
rn_id_map = ccn_master_to_rn_id_map(master_iface_map);
ccn_snoop_dvm_do_op(rn_id_map,
CCN_GET_MN_NODEID_MAP(ccn_plat_desc->periphbase),
MN_REGION_ID,
MN_DDC_SET_OFFSET,
MN_DDC_STAT_OFFSET);
}
void ccn_exit_dvm_domain(unsigned long long master_iface_map)
{
unsigned long long rn_id_map;
rn_id_map = ccn_master_to_rn_id_map(master_iface_map);
ccn_snoop_dvm_do_op(rn_id_map,
CCN_GET_MN_NODEID_MAP(ccn_plat_desc->periphbase),
MN_REGION_ID,
MN_DDC_CLR_OFFSET,
MN_DDC_STAT_OFFSET);
}
/*******************************************************************************
* This function returns the run mode of all the L3 cache partitions in the
* system. The state is expected to be one of NO_L3, SF_ONLY, L3_HAM or
* L3_FAM. Instead of comparing the states reported by all HN-Fs, the state of
* the first present HN-F node is reported. Since the driver does not export an
* interface to program them separately, there is no reason to perform this
* check. An HN-F could report that the L3 cache is transitioning from one mode
* to another e.g. HNF_PM_NOL3_2_SFONLY. In this case, the function waits for
* the transition to complete and reports the final state.
******************************************************************************/
unsigned int ccn_get_l3_run_mode(void)
{
unsigned long long hnf_pstate_stat;
assert(ccn_plat_desc);
assert(ccn_plat_desc->periphbase);
/*
* Wait for a L3 cache partition to enter any run mode. The pstate
* parameter is read from an HN-F P-state status register. A non-zero
* value in bits[1:0] means that the cache is transitioning to a run
* mode.
*/
do {
hnf_pstate_stat = ccn_reg_read(ccn_plat_desc->periphbase,
HNF_REGION_ID_START,
HNF_PSTATE_STAT_OFFSET);
} while (hnf_pstate_stat & 0x3);
return PSTATE_TO_RUN_MODE(hnf_pstate_stat);
}
/*******************************************************************************
* This function sets the run mode of all the L3 cache partitions in the
* system to one of NO_L3, SF_ONLY, L3_HAM or L3_FAM depending upon the state
* specified by the 'mode' argument.
******************************************************************************/
void ccn_set_l3_run_mode(unsigned int mode)
{
unsigned long long mn_hnf_id_map, hnf_pstate_stat;
unsigned int region_id;
assert(ccn_plat_desc);
assert(ccn_plat_desc->periphbase);
assert(mode <= CCN_L3_RUN_MODE_FAM);
mn_hnf_id_map = ccn_reg_read(ccn_plat_desc->periphbase,
MN_REGION_ID,
MN_HNF_NODEID_OFFSET);
region_id = HNF_REGION_ID_START;
/* Program the desired run mode */
FOR_EACH_PRESENT_REGION_ID(region_id, mn_hnf_id_map) {
ccn_reg_write(ccn_plat_desc->periphbase,
region_id,
HNF_PSTATE_REQ_OFFSET,
mode);
}
/* Wait for the caches to transition to the run mode */
region_id = HNF_REGION_ID_START;
FOR_EACH_PRESENT_REGION_ID(region_id, mn_hnf_id_map) {
/*
* Wait for a L3 cache partition to enter a target run
* mode. The pstate parameter is read from an HN-F P-state
* status register.
*/
do {
hnf_pstate_stat = ccn_reg_read(ccn_plat_desc->periphbase,
region_id,
HNF_PSTATE_STAT_OFFSET);
} while (((hnf_pstate_stat & HNF_PSTATE_MASK) >> 2) != mode);
}
}
/*******************************************************************************
* This function configures system address map and provides option to enable the
* 3SN striping mode of Slave node operation. The Slave node IDs and the Top
* Address bit1 and bit0 are provided as parameters to this function. This
* configuration is needed only if network contains a single SN-F or 3 SN-F and
* must be completed before the first request by the system to normal memory.
******************************************************************************/
void ccn_program_sys_addrmap(unsigned int sn0_id,
unsigned int sn1_id,
unsigned int sn2_id,
unsigned int top_addr_bit0,
unsigned int top_addr_bit1,
unsigned char three_sn_en)
{
unsigned long long mn_hnf_id_map, hnf_sam_ctrl_value;
unsigned int region_id;
assert(ccn_plat_desc);
assert(ccn_plat_desc->periphbase);
mn_hnf_id_map = ccn_reg_read(ccn_plat_desc->periphbase,
MN_REGION_ID,
MN_HNF_NODEID_OFFSET);
region_id = HNF_REGION_ID_START;
hnf_sam_ctrl_value = MAKE_HNF_SAM_CTRL_VALUE(sn0_id,
sn1_id,
sn2_id,
top_addr_bit0,
top_addr_bit1,
three_sn_en);
FOR_EACH_PRESENT_REGION_ID(region_id, mn_hnf_id_map) {
/* Program the SAM control register */
ccn_reg_write(ccn_plat_desc->periphbase,
region_id,
HNF_SAM_CTRL_OFFSET,
hnf_sam_ctrl_value);
}
}
/*******************************************************************************
* This function returns the part0 id from the peripheralID 0 register
* in CCN. This id can be used to distinguish the CCN variant present in the
* system.
******************************************************************************/
int ccn_get_part0_id(uintptr_t periphbase)
{
assert(periphbase);
return (int)(mmio_read_64(periphbase
+ MN_PERIPH_ID_0_1_OFFSET) & 0xFF);
}
/*******************************************************************************
* This function returns the region id corresponding to a node_id of node_type.
******************************************************************************/
static unsigned int get_region_id_for_node(node_types_t node_type,
unsigned int node_id)
{
unsigned int mn_reg_off, region_id;
unsigned long long node_bitmap;
unsigned int loc_node_id, node_pos_in_map = 0;
assert(node_type < NUM_NODE_TYPES);
assert(node_id < MAX_RN_NODES);
switch (node_type) {
case NODE_TYPE_RNI:
region_id = RNI_REGION_ID_START;
break;
case NODE_TYPE_HNF:
region_id = HNF_REGION_ID_START;
break;
case NODE_TYPE_HNI:
region_id = HNI_REGION_ID_START;
break;
case NODE_TYPE_SN:
region_id = SBSX_REGION_ID_START;
break;
default:
ERROR("Un-supported Node Type = %d.\n", node_type);
assert(false);
return REGION_ID_LIMIT;
}
/*
* RN-I, HN-F, HN-I, SN node registers in the MN region
* occupy contiguous 16 byte apart offsets.
*
* RN-F and RN-D node are not supported as
* none of them exposes any memory map to
* configure any of their offset registers.
*/
mn_reg_off = MN_RNF_NODEID_OFFSET + (node_type << 4);
node_bitmap = ccn_reg_read(ccn_plat_desc->periphbase,
MN_REGION_ID, mn_reg_off);
assert((node_bitmap & (1ULL << (node_id))) != 0U);
FOR_EACH_PRESENT_NODE_ID(loc_node_id, node_bitmap) {
INFO("Index = %u with loc_nod=%u and input nod=%u\n",
node_pos_in_map, loc_node_id, node_id);
if (loc_node_id == node_id)
break;
node_pos_in_map++;
}
if (node_pos_in_map == CCN_MAX_RN_MASTERS) {
ERROR("Node Id = %d, is not found.\n", node_id);
assert(false);
return REGION_ID_LIMIT;
}
/*
* According to section 3.1.1 in CCN specification, region offset for
* the RN-I components is calculated as (128 + NodeID of RN-I).
*/
if (node_type == NODE_TYPE_RNI)
region_id += node_id;
else
region_id += node_pos_in_map;
return region_id;
}
/*******************************************************************************
* This function sets the value 'val' to the register at register_offset from
* the base address pointed to by the region_id.
* where, region id is mapped to a node_id of node_type.
******************************************************************************/
void ccn_write_node_reg(node_types_t node_type, unsigned int node_id,
unsigned int reg_offset, unsigned long long val)
{
unsigned int region_id = get_region_id_for_node(node_type, node_id);
if (reg_offset > REGION_ID_OFFSET) {
ERROR("Invalid Register offset 0x%x is provided.\n",
reg_offset);
assert(false);
return;
}
/* Setting the value of Auxiliary Control Register of the Node */
ccn_reg_write(ccn_plat_desc->periphbase, region_id, reg_offset, val);
VERBOSE("Value is successfully written at address 0x%lx.\n",
(ccn_plat_desc->periphbase
+ region_id_to_base(region_id))
+ reg_offset);
}
/*******************************************************************************
* This function read the value 'val' stored in the register at register_offset
* from the base address pointed to by the region_id.
* where, region id is mapped to a node_id of node_type.
******************************************************************************/
unsigned long long ccn_read_node_reg(node_types_t node_type,
unsigned int node_id,
unsigned int reg_offset)
{
unsigned long long val;
unsigned int region_id = get_region_id_for_node(node_type, node_id);
if (reg_offset > REGION_ID_OFFSET) {
ERROR("Invalid Register offset 0x%x is provided.\n",
reg_offset);
assert(false);
return ULL(0);
}
/* Setting the value of Auxiliary Control Register of the Node */
val = ccn_reg_read(ccn_plat_desc->periphbase, region_id, reg_offset);
VERBOSE("Value is successfully read from address 0x%lx.\n",
(ccn_plat_desc->periphbase
+ region_id_to_base(region_id))
+ reg_offset);
return val;
}
@@ -0,0 +1,233 @@
/*
* Copyright (c) 2015-2016, ARM Limited and Contributors. All rights reserved.
*
* SPDX-License-Identifier: BSD-3-Clause
*/
#ifndef CCN_PRIVATE_H
#define CCN_PRIVATE_H
/*
* A CCN implementation can have a maximum of 64 Request nodes with node IDs
* from 0-63. These IDs are split across the three types of Request nodes
* i.e. RN-F, RN-D and RN-I.
*/
#define MAX_RN_NODES 64
/* Enum used to loop through the 3 types of Request nodes */
typedef enum rn_types {
RN_TYPE_RNF = 0,
RN_TYPE_RNI,
RN_TYPE_RND,
NUM_RN_TYPES
} rn_types_t;
/* Macro to convert a region id to its base address */
#define region_id_to_base(id) ((id) << 16)
/*
* Macro to calculate the number of master interfaces resident on a RN-I/RN-D.
* Value of first two bits of the RN-I/D node type + 1 == Maximum number of
* ACE-Lite or ACE-Lite+DVM interfaces supported on this node. E.g.
*
* 0x14 : RN-I with 1 ACE-Lite interface
* 0x15 : RN-I with 2 ACE-Lite interfaces
* 0x16 : RN-I with 3 ACE-Lite interfaces
*/
#define rn_type_id_to_master_cnt(id) (((id) & 0x3) + 1)
/*
* Constants used to identify a region in the programmer's view. These are
* common for all regions.
*/
#define REGION_ID_LIMIT 256
#define REGION_ID_OFFSET 0xFF00
#define REGION_NODE_ID_SHIFT 8
#define REGION_NODE_ID_MASK 0x7f
#define get_node_id(id_reg) (((id_reg) >> REGION_NODE_ID_SHIFT) \
& REGION_NODE_ID_MASK)
#define REGION_NODE_TYPE_SHIFT 0
#define REGION_NODE_TYPE_MASK 0x1f
#define get_node_type(id_reg) (((id_reg) >> REGION_NODE_TYPE_SHIFT) \
& REGION_NODE_TYPE_MASK)
/* Common offsets of registers to enter or exit a snoop/dvm domain */
#define DOMAIN_CTRL_STAT_OFFSET 0x0200
#define DOMAIN_CTRL_SET_OFFSET 0x0210
#define DOMAIN_CTRL_CLR_OFFSET 0x0220
/*
* Thess macros are used to determine if an operation to add or remove a Request
* node from the snoop/dvm domain has completed. 'rn_id_map' is a bit map of
* nodes. It was used to program the SET or CLEAR control register. The type of
* register is specified by 'op_reg_offset'. 'status_reg' is the bit map of
* nodes currently present in the snoop/dvm domain. 'rn_id_map' and 'status_reg'
* are logically ANDed and the result it stored back in the 'status_reg'. There
* are two outcomes of this operation:
*
* 1. If the DOMAIN_CTRL_SET_OFFSET register was programmed, then the set bits in
* 'rn_id_map' should appear in 'status_reg' when the operation completes. So
* after the AND operation, at some point of time 'status_reg' should equal
* 'rn_id_map'.
*
* 2. If the DOMAIN_CTRL_CLR_OFFSET register was programmed, then the set bits in
* 'rn_id_map' should disappear in 'status_reg' when the operation
* completes. So after the AND operation, at some point of time 'status_reg'
* should equal 0.
*/
#define WAIT_FOR_DOMAIN_CTRL_OP_COMPLETION(region_id, stat_reg_offset, \
op_reg_offset, rn_id_map) \
{ \
unsigned long long status_reg; \
do { \
status_reg = ccn_reg_read((ccn_plat_desc->periphbase), \
(region_id), \
(stat_reg_offset)); \
status_reg &= (rn_id_map); \
} while ((op_reg_offset) == DOMAIN_CTRL_SET_OFFSET ? \
(rn_id_map) != status_reg : status_reg); \
}
/*
* Region ID of the Miscellaneous Node is always 0 as its located at the base of
* the programmer's view.
*/
#define MN_REGION_ID 0
#define MN_REGION_ID_START 0
#define DEBUG_REGION_ID_START 1
#define HNI_REGION_ID_START 8
#define SBSX_REGION_ID_START 16
#define HNF_REGION_ID_START 32
#define XP_REGION_ID_START 64
#define RNI_REGION_ID_START 128
/* Selected register offsets from the base of a HNF region */
#define HNF_CFG_CTRL_OFFSET 0x0000
#define HNF_SAM_CTRL_OFFSET 0x0008
#define HNF_PSTATE_REQ_OFFSET 0x0010
#define HNF_PSTATE_STAT_OFFSET 0x0018
#define HNF_SDC_STAT_OFFSET DOMAIN_CTRL_STAT_OFFSET
#define HNF_SDC_SET_OFFSET DOMAIN_CTRL_SET_OFFSET
#define HNF_SDC_CLR_OFFSET DOMAIN_CTRL_CLR_OFFSET
#define HNF_AUX_CTRL_OFFSET 0x0500
/* Selected register offsets from the base of a MN region */
#define MN_SAR_OFFSET 0x0000
#define MN_RNF_NODEID_OFFSET 0x0180
#define MN_RNI_NODEID_OFFSET 0x0190
#define MN_RND_NODEID_OFFSET 0x01A0
#define MN_HNF_NODEID_OFFSET 0x01B0
#define MN_HNI_NODEID_OFFSET 0x01C0
#define MN_SN_NODEID_OFFSET 0x01D0
#define MN_DDC_STAT_OFFSET DOMAIN_CTRL_STAT_OFFSET
#define MN_DDC_SET_OFFSET DOMAIN_CTRL_SET_OFFSET
#define MN_DDC_CLR_OFFSET DOMAIN_CTRL_CLR_OFFSET
#define MN_PERIPH_ID_0_1_OFFSET 0xFE0
#define MN_ID_OFFSET REGION_ID_OFFSET
/* HNF System Address Map register bit masks and shifts */
#define HNF_SAM_CTRL_SN_ID_MASK 0x7f
#define HNF_SAM_CTRL_SN0_ID_SHIFT 0
#define HNF_SAM_CTRL_SN1_ID_SHIFT 8
#define HNF_SAM_CTRL_SN2_ID_SHIFT 16
#define HNF_SAM_CTRL_TAB0_MASK ULL(0x3f)
#define HNF_SAM_CTRL_TAB0_SHIFT 48
#define HNF_SAM_CTRL_TAB1_MASK ULL(0x3f)
#define HNF_SAM_CTRL_TAB1_SHIFT 56
#define HNF_SAM_CTRL_3SN_ENB_SHIFT 32
#define HNF_SAM_CTRL_3SN_ENB_MASK ULL(0x01)
/*
* Macro to create a value suitable for programming into a HNF SAM Control
* register for enabling 3SN striping.
*/
#define MAKE_HNF_SAM_CTRL_VALUE(sn0, sn1, sn2, tab0, tab1, three_sn_en) \
((((sn0) & HNF_SAM_CTRL_SN_ID_MASK) << HNF_SAM_CTRL_SN0_ID_SHIFT) | \
(((sn1) & HNF_SAM_CTRL_SN_ID_MASK) << HNF_SAM_CTRL_SN1_ID_SHIFT) | \
(((sn2) & HNF_SAM_CTRL_SN_ID_MASK) << HNF_SAM_CTRL_SN2_ID_SHIFT) | \
(((tab0) & HNF_SAM_CTRL_TAB0_MASK) << HNF_SAM_CTRL_TAB0_SHIFT) | \
(((tab1) & HNF_SAM_CTRL_TAB1_MASK) << HNF_SAM_CTRL_TAB1_SHIFT) | \
(((three_sn_en) & HNF_SAM_CTRL_3SN_ENB_MASK) << HNF_SAM_CTRL_3SN_ENB_SHIFT))
/* Mask to read the power state value from an HN-F P-state register */
#define HNF_PSTATE_MASK 0xf
/* Macro to extract the run mode from a p-state value */
#define PSTATE_TO_RUN_MODE(pstate) (((pstate) & HNF_PSTATE_MASK) >> 2)
/*
* Helper macro that iterates through a given bit map. In each iteration,
* it returns the position of the set bit.
* It can be used by other utility macros to iterates through all nodes
* or masters given a bit map of them.
*/
#define FOR_EACH_BIT(bit_pos, bit_map) \
for (bit_pos = __builtin_ctzll(bit_map); \
bit_map; \
bit_map &= ~(1ULL << (bit_pos)), \
bit_pos = __builtin_ctzll(bit_map))
/*
* Utility macro that iterates through a bit map of node IDs. In each
* iteration, it returns the ID of the next present node in the bit map. Node
* ID of a present node == Position of set bit == Number of zeroes trailing the
* bit.
*/
#define FOR_EACH_PRESENT_NODE_ID(node_id, bit_map) \
FOR_EACH_BIT(node_id, bit_map)
/*
* Helper function to return number of set bits in bitmap
*/
static inline unsigned int count_set_bits(unsigned long long bitmap)
{
unsigned int count = 0;
for (; bitmap; bitmap &= bitmap - 1)
++count;
return count;
}
/*
* Utility macro that iterates through a bit map of node IDs. In each iteration,
* it returns the ID of the next present region corresponding to a node present
* in the bit map. Region ID of a present node is in between passed region id
* and region id + number of set bits in the bitmap i.e. the number of present
* nodes.
*/
#define FOR_EACH_PRESENT_REGION_ID(region_id, bit_map) \
for (unsigned long long region_id_limit = count_set_bits(bit_map) \
+ region_id; \
region_id < region_id_limit; \
region_id++)
/*
* Same macro as FOR_EACH_PRESENT_NODE, but renamed to indicate it traverses
* through a bit map of master interfaces.
*/
#define FOR_EACH_PRESENT_MASTER_INTERFACE(iface_id, bit_map) \
FOR_EACH_BIT(iface_id, bit_map)
/*
* Macro that returns the node id bit map for the Miscellaneous Node
*/
#define CCN_GET_MN_NODEID_MAP(periphbase) \
(1 << get_node_id(ccn_reg_read(periphbase, MN_REGION_ID, \
REGION_ID_OFFSET)))
/*
* This macro returns the bitmap of Home nodes on the basis of the
* 'mn_hn_id_reg_offset' parameter from the Miscellaneous node's (MN)
* programmer's view. The MN has a register which carries the bitmap of present
* Home nodes of each type i.e. HN-Fs, HN-Is & HN-Ds.
*/
#define CCN_GET_HN_NODEID_MAP(periphbase, mn_hn_id_reg_offset) \
ccn_reg_read(periphbase, MN_REGION_ID, mn_hn_id_reg_offset)
#endif /* CCN_PRIVATE_H */
@@ -0,0 +1,100 @@
/*
* Copyright (c) 2014-2018, ARM Limited and Contributors. All rights reserved.
*
* SPDX-License-Identifier: BSD-3-Clause
*/
#include <assert.h>
#include <platform_def.h>
#include <arch_helpers.h>
#include <drivers/arm/css/css_mhu.h>
#include <lib/bakery_lock.h>
#include <lib/mmio.h>
#include <plat/arm/common/plat_arm.h>
/* SCP MHU secure channel registers */
#define SCP_INTR_S_STAT 0x200
#define SCP_INTR_S_SET 0x208
#define SCP_INTR_S_CLEAR 0x210
/* CPU MHU secure channel registers */
#define CPU_INTR_S_STAT 0x300
#define CPU_INTR_S_SET 0x308
#define CPU_INTR_S_CLEAR 0x310
ARM_INSTANTIATE_LOCK;
/* Weak definition may be overridden in specific CSS based platform */
#pragma weak plat_arm_pwrc_setup
/*
* Slot 31 is reserved because the MHU hardware uses this register bit to
* indicate a non-secure access attempt. The total number of available slots is
* therefore 31 [30:0].
*/
#define MHU_MAX_SLOT_ID 30
void mhu_secure_message_start(unsigned int slot_id)
{
assert(slot_id <= MHU_MAX_SLOT_ID);
arm_lock_get();
/* Make sure any previous command has finished */
while (mmio_read_32(PLAT_CSS_MHU_BASE + CPU_INTR_S_STAT) &
(1 << slot_id))
;
}
void mhu_secure_message_send(unsigned int slot_id)
{
assert(slot_id <= MHU_MAX_SLOT_ID);
assert(!(mmio_read_32(PLAT_CSS_MHU_BASE + CPU_INTR_S_STAT) &
(1 << slot_id)));
/* Send command to SCP */
mmio_write_32(PLAT_CSS_MHU_BASE + CPU_INTR_S_SET, 1 << slot_id);
}
uint32_t mhu_secure_message_wait(void)
{
/* Wait for response from SCP */
uint32_t response;
while (!(response = mmio_read_32(PLAT_CSS_MHU_BASE + SCP_INTR_S_STAT)))
;
return response;
}
void mhu_secure_message_end(unsigned int slot_id)
{
assert(slot_id <= MHU_MAX_SLOT_ID);
/*
* Clear any response we got by writing one in the relevant slot bit to
* the CLEAR register
*/
mmio_write_32(PLAT_CSS_MHU_BASE + SCP_INTR_S_CLEAR, 1 << slot_id);
arm_lock_release();
}
void __init mhu_secure_init(void)
{
arm_lock_init();
/*
* The STAT register resets to zero. Ensure it is in the expected state,
* as a stale or garbage value would make us think it's a message we've
* already sent.
*/
assert(mmio_read_32(PLAT_CSS_MHU_BASE + CPU_INTR_S_STAT) == 0);
}
void __init plat_arm_pwrc_setup(void)
{
mhu_secure_init();
}
@@ -0,0 +1,40 @@
/*
* Copyright (c) 2014-2020, ARM Limited and Contributors. All rights reserved.
*
* SPDX-License-Identifier: BSD-3-Clause
*/
#include <platform_def.h>
#include <arch_helpers.h>
#include <drivers/arm/css/css_mhu_doorbell.h>
#include <drivers/arm/css/scmi.h>
void mhu_ring_doorbell(struct scmi_channel_plat_info *plat_info)
{
MHU_RING_DOORBELL(plat_info->db_reg_addr,
plat_info->db_modify_mask,
plat_info->db_preserve_mask);
return;
}
void mhuv2_ring_doorbell(struct scmi_channel_plat_info *plat_info)
{
uintptr_t mhuv2_base = plat_info->db_reg_addr & MHU_V2_FRAME_BASE_MASK;
/* wake receiver */
MHU_V2_ACCESS_REQUEST(mhuv2_base);
/* wait for receiver to acknowledge its ready */
while (MHU_V2_IS_ACCESS_READY(mhuv2_base) == 0)
;
MHU_RING_DOORBELL(plat_info->db_reg_addr,
plat_info->db_modify_mask,
plat_info->db_preserve_mask);
/* clear the access request for the receiver */
MHU_V2_CLEAR_REQUEST(mhuv2_base);
return;
}
@@ -0,0 +1,81 @@
/*
* Copyright (c) 2018-2019, ARM Limited and Contributors. All rights reserved.
*
* SPDX-License-Identifier: BSD-3-Clause
*/
#include <assert.h>
#include <arch_helpers.h>
#include <common/debug.h>
#include <drivers/arm/css/scmi.h>
#include "scmi_private.h"
/*
* API to set the SCMI AP core reset address and attributes
*/
int scmi_ap_core_set_reset_addr(void *p, uint64_t reset_addr, uint32_t attr)
{
mailbox_mem_t *mbx_mem;
unsigned int token = 0;
int ret;
scmi_channel_t *ch = (scmi_channel_t *)p;
validate_scmi_channel(ch);
scmi_get_channel(ch);
mbx_mem = (mailbox_mem_t *)(ch->info->scmi_mbx_mem);
mbx_mem->msg_header = SCMI_MSG_CREATE(SCMI_AP_CORE_PROTO_ID,
SCMI_AP_CORE_RESET_ADDR_SET_MSG, token);
mbx_mem->len = SCMI_AP_CORE_RESET_ADDR_SET_MSG_LEN;
mbx_mem->flags = SCMI_FLAG_RESP_POLL;
SCMI_PAYLOAD_ARG3(mbx_mem->payload, reset_addr & 0xffffffff,
reset_addr >> 32, attr);
scmi_send_sync_command(ch);
/* Get the return values */
SCMI_PAYLOAD_RET_VAL1(mbx_mem->payload, ret);
assert(mbx_mem->len == SCMI_AP_CORE_RESET_ADDR_SET_RESP_LEN);
assert(token == SCMI_MSG_GET_TOKEN(mbx_mem->msg_header));
scmi_put_channel(ch);
return ret;
}
/*
* API to get the SCMI AP core reset address and attributes
*/
int scmi_ap_core_get_reset_addr(void *p, uint64_t *reset_addr, uint32_t *attr)
{
mailbox_mem_t *mbx_mem;
unsigned int token = 0;
int ret;
scmi_channel_t *ch = (scmi_channel_t *)p;
uint32_t lo_addr, hi_addr;
validate_scmi_channel(ch);
scmi_get_channel(ch);
mbx_mem = (mailbox_mem_t *)(ch->info->scmi_mbx_mem);
mbx_mem->msg_header = SCMI_MSG_CREATE(SCMI_AP_CORE_PROTO_ID,
SCMI_AP_CORE_RESET_ADDR_GET_MSG, token);
mbx_mem->len = SCMI_AP_CORE_RESET_ADDR_GET_MSG_LEN;
mbx_mem->flags = SCMI_FLAG_RESP_POLL;
scmi_send_sync_command(ch);
/* Get the return values */
SCMI_PAYLOAD_RET_VAL4(mbx_mem->payload, ret, lo_addr, hi_addr, *attr);
*reset_addr = lo_addr | (uint64_t)hi_addr << 32;
assert(mbx_mem->len == SCMI_AP_CORE_RESET_ADDR_GET_RESP_LEN);
assert(token == SCMI_MSG_GET_TOKEN(mbx_mem->msg_header));
scmi_put_channel(ch);
return ret;
}
@@ -0,0 +1,210 @@
/*
* Copyright (c) 2017-2019, ARM Limited and Contributors. All rights reserved.
*
* SPDX-License-Identifier: BSD-3-Clause
*/
#include <assert.h>
#include <arch_helpers.h>
#include <common/debug.h>
#include <drivers/arm/css/scmi.h>
#include "scmi_private.h"
#if HW_ASSISTED_COHERENCY
#define scmi_lock_init(lock)
#define scmi_lock_get(lock) spin_lock(lock)
#define scmi_lock_release(lock) spin_unlock(lock)
#else
#define scmi_lock_init(lock) bakery_lock_init(lock)
#define scmi_lock_get(lock) bakery_lock_get(lock)
#define scmi_lock_release(lock) bakery_lock_release(lock)
#endif
/*
* Private helper function to get exclusive access to SCMI channel.
*/
void scmi_get_channel(scmi_channel_t *ch)
{
assert(ch->lock);
scmi_lock_get(ch->lock);
/* Make sure any previous command has finished */
assert(SCMI_IS_CHANNEL_FREE(
((mailbox_mem_t *)(ch->info->scmi_mbx_mem))->status));
}
/*
* Private helper function to transfer ownership of channel from AP to SCP.
*/
void scmi_send_sync_command(scmi_channel_t *ch)
{
mailbox_mem_t *mbx_mem = (mailbox_mem_t *)(ch->info->scmi_mbx_mem);
SCMI_MARK_CHANNEL_BUSY(mbx_mem->status);
/*
* Ensure that any write to the SCMI payload area is seen by SCP before
* we write to the doorbell register. If these 2 writes were reordered
* by the CPU then SCP would read stale payload data
*/
dmbst();
ch->info->ring_doorbell(ch->info);
/*
* Ensure that the write to the doorbell register is ordered prior to
* checking whether the channel is free.
*/
dmbsy();
/* Wait for channel to be free */
while (!SCMI_IS_CHANNEL_FREE(mbx_mem->status))
;
/*
* Ensure that any read to the SCMI payload area is done after reading
* mailbox status. If these 2 reads were reordered then the CPU would
* read invalid payload data
*/
dmbld();
}
/*
* Private helper function to release exclusive access to SCMI channel.
*/
void scmi_put_channel(scmi_channel_t *ch)
{
/* Make sure any previous command has finished */
assert(SCMI_IS_CHANNEL_FREE(
((mailbox_mem_t *)(ch->info->scmi_mbx_mem))->status));
assert(ch->lock);
scmi_lock_release(ch->lock);
}
/*
* API to query the SCMI protocol version.
*/
int scmi_proto_version(void *p, uint32_t proto_id, uint32_t *version)
{
mailbox_mem_t *mbx_mem;
unsigned int token = 0;
int ret;
scmi_channel_t *ch = (scmi_channel_t *)p;
validate_scmi_channel(ch);
scmi_get_channel(ch);
mbx_mem = (mailbox_mem_t *)(ch->info->scmi_mbx_mem);
mbx_mem->msg_header = SCMI_MSG_CREATE(proto_id, SCMI_PROTO_VERSION_MSG,
token);
mbx_mem->len = SCMI_PROTO_VERSION_MSG_LEN;
mbx_mem->flags = SCMI_FLAG_RESP_POLL;
scmi_send_sync_command(ch);
/* Get the return values */
SCMI_PAYLOAD_RET_VAL2(mbx_mem->payload, ret, *version);
assert(mbx_mem->len == SCMI_PROTO_VERSION_RESP_LEN);
assert(token == SCMI_MSG_GET_TOKEN(mbx_mem->msg_header));
scmi_put_channel(ch);
return ret;
}
/*
* API to query the protocol message attributes for a SCMI protocol.
*/
int scmi_proto_msg_attr(void *p, uint32_t proto_id,
uint32_t command_id, uint32_t *attr)
{
mailbox_mem_t *mbx_mem;
unsigned int token = 0;
int ret;
scmi_channel_t *ch = (scmi_channel_t *)p;
validate_scmi_channel(ch);
scmi_get_channel(ch);
mbx_mem = (mailbox_mem_t *)(ch->info->scmi_mbx_mem);
mbx_mem->msg_header = SCMI_MSG_CREATE(proto_id,
SCMI_PROTO_MSG_ATTR_MSG, token);
mbx_mem->len = SCMI_PROTO_MSG_ATTR_MSG_LEN;
mbx_mem->flags = SCMI_FLAG_RESP_POLL;
SCMI_PAYLOAD_ARG1(mbx_mem->payload, command_id);
scmi_send_sync_command(ch);
/* Get the return values */
SCMI_PAYLOAD_RET_VAL2(mbx_mem->payload, ret, *attr);
assert(mbx_mem->len == SCMI_PROTO_MSG_ATTR_RESP_LEN);
assert(token == SCMI_MSG_GET_TOKEN(mbx_mem->msg_header));
scmi_put_channel(ch);
return ret;
}
/*
* SCMI Driver initialization API. Returns initialized channel on success
* or NULL on error. The return type is an opaque void pointer.
*/
void *scmi_init(scmi_channel_t *ch)
{
uint32_t version;
int ret;
assert(ch && ch->info);
assert(ch->info->db_reg_addr);
assert(ch->info->db_modify_mask);
assert(ch->info->db_preserve_mask);
assert(ch->info->ring_doorbell != NULL);
assert(ch->lock);
scmi_lock_init(ch->lock);
ch->is_initialized = 1;
ret = scmi_proto_version(ch, SCMI_PWR_DMN_PROTO_ID, &version);
if (ret != SCMI_E_SUCCESS) {
WARN("SCMI power domain protocol version message failed\n");
goto error;
}
if (!is_scmi_version_compatible(SCMI_PWR_DMN_PROTO_VER, version)) {
WARN("SCMI power domain protocol version 0x%x incompatible with driver version 0x%x\n",
version, SCMI_PWR_DMN_PROTO_VER);
goto error;
}
VERBOSE("SCMI power domain protocol version 0x%x detected\n", version);
ret = scmi_proto_version(ch, SCMI_SYS_PWR_PROTO_ID, &version);
if ((ret != SCMI_E_SUCCESS)) {
WARN("SCMI system power protocol version message failed\n");
goto error;
}
if (!is_scmi_version_compatible(SCMI_SYS_PWR_PROTO_VER, version)) {
WARN("SCMI system power management protocol version 0x%x incompatible with driver version 0x%x\n",
version, SCMI_SYS_PWR_PROTO_VER);
goto error;
}
VERBOSE("SCMI system power management protocol version 0x%x detected\n",
version);
INFO("SCMI driver initialized\n");
return (void *)ch;
error:
ch->is_initialized = 0;
return NULL;
}
@@ -0,0 +1,160 @@
/*
* Copyright (c) 2017-2018, ARM Limited and Contributors. All rights reserved.
*
* SPDX-License-Identifier: BSD-3-Clause
*/
#ifndef SCMI_PRIVATE_H
#define SCMI_PRIVATE_H
#include <lib/mmio.h>
/*
* SCMI power domain management protocol message and response lengths. It is
* calculated as sum of length in bytes of the message header (4) and payload
* area (the number of bytes of parameters or return values in the payload).
*/
#define SCMI_PROTO_VERSION_MSG_LEN 4
#define SCMI_PROTO_VERSION_RESP_LEN 12
#define SCMI_PROTO_MSG_ATTR_MSG_LEN 8
#define SCMI_PROTO_MSG_ATTR_RESP_LEN 12
#define SCMI_AP_CORE_RESET_ADDR_SET_MSG_LEN 16
#define SCMI_AP_CORE_RESET_ADDR_SET_RESP_LEN 8
#define SCMI_AP_CORE_RESET_ADDR_GET_MSG_LEN 4
#define SCMI_AP_CORE_RESET_ADDR_GET_RESP_LEN 20
#define SCMI_PWR_STATE_SET_MSG_LEN 16
#define SCMI_PWR_STATE_SET_RESP_LEN 8
#define SCMI_PWR_STATE_GET_MSG_LEN 8
#define SCMI_PWR_STATE_GET_RESP_LEN 12
#define SCMI_SYS_PWR_STATE_SET_MSG_LEN 12
#define SCMI_SYS_PWR_STATE_SET_RESP_LEN 8
#define SCMI_SYS_PWR_STATE_GET_MSG_LEN 4
#define SCMI_SYS_PWR_STATE_GET_RESP_LEN 12
/* SCMI message header format bit field */
#define SCMI_MSG_ID_SHIFT 0
#define SCMI_MSG_ID_WIDTH 8
#define SCMI_MSG_ID_MASK ((1 << SCMI_MSG_ID_WIDTH) - 1)
#define SCMI_MSG_TYPE_SHIFT 8
#define SCMI_MSG_TYPE_WIDTH 2
#define SCMI_MSG_TYPE_MASK ((1 << SCMI_MSG_TYPE_WIDTH) - 1)
#define SCMI_MSG_PROTO_ID_SHIFT 10
#define SCMI_MSG_PROTO_ID_WIDTH 8
#define SCMI_MSG_PROTO_ID_MASK ((1 << SCMI_MSG_PROTO_ID_WIDTH) - 1)
#define SCMI_MSG_TOKEN_SHIFT 18
#define SCMI_MSG_TOKEN_WIDTH 10
#define SCMI_MSG_TOKEN_MASK ((1 << SCMI_MSG_TOKEN_WIDTH) - 1)
/* SCMI mailbox flags */
#define SCMI_FLAG_RESP_POLL 0
#define SCMI_FLAG_RESP_INT 1
/* SCMI power domain protocol `POWER_STATE_SET` message flags */
#define SCMI_PWR_STATE_SET_FLAG_SYNC 0
#define SCMI_PWR_STATE_SET_FLAG_ASYNC 1
/*
* Helper macro to create an SCMI message header given protocol, message id
* and token.
*/
#define SCMI_MSG_CREATE(_protocol, _msg_id, _token) \
((((_protocol) & SCMI_MSG_PROTO_ID_MASK) << SCMI_MSG_PROTO_ID_SHIFT) | \
(((_msg_id) & SCMI_MSG_ID_MASK) << SCMI_MSG_ID_SHIFT) | \
(((_token) & SCMI_MSG_TOKEN_MASK) << SCMI_MSG_TOKEN_SHIFT))
/* Helper macro to get the token from a SCMI message header */
#define SCMI_MSG_GET_TOKEN(_msg) \
(((_msg) >> SCMI_MSG_TOKEN_SHIFT) & SCMI_MSG_TOKEN_MASK)
/* SCMI Channel Status bit fields */
#define SCMI_CH_STATUS_RES0_MASK 0xFFFFFFFE
#define SCMI_CH_STATUS_FREE_SHIFT 0
#define SCMI_CH_STATUS_FREE_WIDTH 1
#define SCMI_CH_STATUS_FREE_MASK ((1 << SCMI_CH_STATUS_FREE_WIDTH) - 1)
/* Helper macros to check and write the channel status */
#define SCMI_IS_CHANNEL_FREE(status) \
(!!(((status) >> SCMI_CH_STATUS_FREE_SHIFT) & SCMI_CH_STATUS_FREE_MASK))
#define SCMI_MARK_CHANNEL_BUSY(status) do { \
assert(SCMI_IS_CHANNEL_FREE(status)); \
(status) &= ~(SCMI_CH_STATUS_FREE_MASK << \
SCMI_CH_STATUS_FREE_SHIFT); \
} while (0)
/* Helper macros to copy arguments to the mailbox payload */
#define SCMI_PAYLOAD_ARG1(payld_arr, arg1) \
mmio_write_32((uintptr_t)&payld_arr[0], arg1)
#define SCMI_PAYLOAD_ARG2(payld_arr, arg1, arg2) do { \
SCMI_PAYLOAD_ARG1(payld_arr, arg1); \
mmio_write_32((uintptr_t)&payld_arr[1], arg2); \
} while (0)
#define SCMI_PAYLOAD_ARG3(payld_arr, arg1, arg2, arg3) do { \
SCMI_PAYLOAD_ARG2(payld_arr, arg1, arg2); \
mmio_write_32((uintptr_t)&payld_arr[2], arg3); \
} while (0)
/* Helper macros to read return values from the mailbox payload */
#define SCMI_PAYLOAD_RET_VAL1(payld_arr, val1) \
(val1) = mmio_read_32((uintptr_t)&payld_arr[0])
#define SCMI_PAYLOAD_RET_VAL2(payld_arr, val1, val2) do { \
SCMI_PAYLOAD_RET_VAL1(payld_arr, val1); \
(val2) = mmio_read_32((uintptr_t)&payld_arr[1]); \
} while (0)
#define SCMI_PAYLOAD_RET_VAL3(payld_arr, val1, val2, val3) do { \
SCMI_PAYLOAD_RET_VAL2(payld_arr, val1, val2); \
(val3) = mmio_read_32((uintptr_t)&payld_arr[2]); \
} while (0)
#define SCMI_PAYLOAD_RET_VAL4(payld_arr, val1, val2, val3, val4) do { \
SCMI_PAYLOAD_RET_VAL3(payld_arr, val1, val2, val3); \
(val4) = mmio_read_32((uintptr_t)&payld_arr[3]); \
} while (0)
/*
* Private data structure for representing the mailbox memory layout. Refer
* the SCMI specification for more details.
*/
typedef struct mailbox_mem {
uint32_t res_a; /* Reserved */
volatile uint32_t status;
uint64_t res_b; /* Reserved */
uint32_t flags;
volatile uint32_t len;
volatile uint32_t msg_header;
uint32_t payload[];
} mailbox_mem_t;
/* Private APIs for use within SCMI driver */
void scmi_get_channel(scmi_channel_t *ch);
void scmi_send_sync_command(scmi_channel_t *ch);
void scmi_put_channel(scmi_channel_t *ch);
static inline void validate_scmi_channel(scmi_channel_t *ch)
{
assert(ch && ch->is_initialized);
assert(ch->info && ch->info->scmi_mbx_mem);
}
/*
* SCMI vendor specific protocol
*/
#define SCMI_SYS_VENDOR_EXT_PROTO_ID 0x80
#endif /* SCMI_PRIVATE_H */
@@ -0,0 +1,88 @@
/*
* Copyright (c) 2017-2019, ARM Limited and Contributors. All rights reserved.
*
* SPDX-License-Identifier: BSD-3-Clause
*/
#include <assert.h>
#include <arch_helpers.h>
#include <common/debug.h>
#include <drivers/arm/css/scmi.h>
#include "scmi_private.h"
/*
* API to set the SCMI power domain power state.
*/
int scmi_pwr_state_set(void *p, uint32_t domain_id,
uint32_t scmi_pwr_state)
{
mailbox_mem_t *mbx_mem;
unsigned int token = 0;
int ret;
/*
* Only asynchronous mode of `set power state` command is allowed on
* application processors.
*/
uint32_t pwr_state_set_msg_flag = SCMI_PWR_STATE_SET_FLAG_ASYNC;
scmi_channel_t *ch = (scmi_channel_t *)p;
validate_scmi_channel(ch);
scmi_get_channel(ch);
mbx_mem = (mailbox_mem_t *)(ch->info->scmi_mbx_mem);
mbx_mem->msg_header = SCMI_MSG_CREATE(SCMI_PWR_DMN_PROTO_ID,
SCMI_PWR_STATE_SET_MSG, token);
mbx_mem->len = SCMI_PWR_STATE_SET_MSG_LEN;
mbx_mem->flags = SCMI_FLAG_RESP_POLL;
SCMI_PAYLOAD_ARG3(mbx_mem->payload, pwr_state_set_msg_flag,
domain_id, scmi_pwr_state);
scmi_send_sync_command(ch);
/* Get the return values */
SCMI_PAYLOAD_RET_VAL1(mbx_mem->payload, ret);
assert(mbx_mem->len == SCMI_PWR_STATE_SET_RESP_LEN);
assert(token == SCMI_MSG_GET_TOKEN(mbx_mem->msg_header));
scmi_put_channel(ch);
return ret;
}
/*
* API to get the SCMI power domain power state.
*/
int scmi_pwr_state_get(void *p, uint32_t domain_id,
uint32_t *scmi_pwr_state)
{
mailbox_mem_t *mbx_mem;
unsigned int token = 0;
int ret;
scmi_channel_t *ch = (scmi_channel_t *)p;
validate_scmi_channel(ch);
scmi_get_channel(ch);
mbx_mem = (mailbox_mem_t *)(ch->info->scmi_mbx_mem);
mbx_mem->msg_header = SCMI_MSG_CREATE(SCMI_PWR_DMN_PROTO_ID,
SCMI_PWR_STATE_GET_MSG, token);
mbx_mem->len = SCMI_PWR_STATE_GET_MSG_LEN;
mbx_mem->flags = SCMI_FLAG_RESP_POLL;
SCMI_PAYLOAD_ARG1(mbx_mem->payload, domain_id);
scmi_send_sync_command(ch);
/* Get the return values */
SCMI_PAYLOAD_RET_VAL2(mbx_mem->payload, ret, *scmi_pwr_state);
assert(mbx_mem->len == SCMI_PWR_STATE_GET_RESP_LEN);
assert(token == SCMI_MSG_GET_TOKEN(mbx_mem->msg_header));
scmi_put_channel(ch);
return ret;
}
@@ -0,0 +1,78 @@
/*
* Copyright (c) 2017-2019, ARM Limited and Contributors. All rights reserved.
*
* SPDX-License-Identifier: BSD-3-Clause
*/
#include <assert.h>
#include <arch_helpers.h>
#include <common/debug.h>
#include <drivers/arm/css/scmi.h>
#include "scmi_private.h"
/*
* API to set the SCMI system power state
*/
int scmi_sys_pwr_state_set(void *p, uint32_t flags, uint32_t system_state)
{
mailbox_mem_t *mbx_mem;
unsigned int token = 0;
int ret;
scmi_channel_t *ch = (scmi_channel_t *)p;
validate_scmi_channel(ch);
scmi_get_channel(ch);
mbx_mem = (mailbox_mem_t *)(ch->info->scmi_mbx_mem);
mbx_mem->msg_header = SCMI_MSG_CREATE(SCMI_SYS_PWR_PROTO_ID,
SCMI_SYS_PWR_STATE_SET_MSG, token);
mbx_mem->len = SCMI_SYS_PWR_STATE_SET_MSG_LEN;
mbx_mem->flags = SCMI_FLAG_RESP_POLL;
SCMI_PAYLOAD_ARG2(mbx_mem->payload, flags, system_state);
scmi_send_sync_command(ch);
/* Get the return values */
SCMI_PAYLOAD_RET_VAL1(mbx_mem->payload, ret);
assert(mbx_mem->len == SCMI_SYS_PWR_STATE_SET_RESP_LEN);
assert(token == SCMI_MSG_GET_TOKEN(mbx_mem->msg_header));
scmi_put_channel(ch);
return ret;
}
/*
* API to get the SCMI system power state
*/
int scmi_sys_pwr_state_get(void *p, uint32_t *system_state)
{
mailbox_mem_t *mbx_mem;
unsigned int token = 0;
int ret;
scmi_channel_t *ch = (scmi_channel_t *)p;
validate_scmi_channel(ch);
scmi_get_channel(ch);
mbx_mem = (mailbox_mem_t *)(ch->info->scmi_mbx_mem);
mbx_mem->msg_header = SCMI_MSG_CREATE(SCMI_SYS_PWR_PROTO_ID,
SCMI_SYS_PWR_STATE_GET_MSG, token);
mbx_mem->len = SCMI_SYS_PWR_STATE_GET_MSG_LEN;
mbx_mem->flags = SCMI_FLAG_RESP_POLL;
scmi_send_sync_command(ch);
/* Get the return values */
SCMI_PAYLOAD_RET_VAL2(mbx_mem->payload, ret, *system_state);
assert(mbx_mem->len == SCMI_SYS_PWR_STATE_GET_RESP_LEN);
assert(token == SCMI_MSG_GET_TOKEN(mbx_mem->msg_header));
scmi_put_channel(ch);
return ret;
}
@@ -0,0 +1,62 @@
/*
* Copyright (c) 2019, ARM Limited and Contributors. All rights reserved.
*
* SPDX-License-Identifier: BSD-3-Clause
*/
#include <assert.h>
#include <arch_helpers.h>
#include <common/debug.h>
#include <drivers/arm/css/scmi.h>
#include "scmi_private.h"
#include "scmi_sq.h"
#include <sq_common.h>
/* SCMI messge ID to get the available DRAM region */
#define SCMI_VENDOR_EXT_MEMINFO_GET_MSG 0x3
#define SCMI_VENDOR_EXT_MEMINFO_GET_MSG_LEN 4
/*
* API to get the available DRAM region
*/
int scmi_get_draminfo(void *p, struct draminfo *info)
{
mailbox_mem_t *mbx_mem;
int token = 0, ret;
scmi_channel_t *ch = (scmi_channel_t *)p;
struct dram_info_resp response;
validate_scmi_channel(ch);
scmi_get_channel(ch);
mbx_mem = (mailbox_mem_t *)(ch->info->scmi_mbx_mem);
mbx_mem->msg_header = SCMI_MSG_CREATE(SCMI_SYS_VENDOR_EXT_PROTO_ID,
SCMI_VENDOR_EXT_MEMINFO_GET_MSG, token);
mbx_mem->len = SCMI_VENDOR_EXT_MEMINFO_GET_MSG_LEN;
mbx_mem->flags = SCMI_FLAG_RESP_POLL;
scmi_send_sync_command(ch);
/*
* Ensure that any read to the SCPI payload area is done after reading
* the MHU register. If these 2 reads were reordered then the CPU would
* read invalid payload data
*/
dmbld();
/* Get the return values */
SCMI_PAYLOAD_RET_VAL1(mbx_mem->payload, ret);
memcpy(&response, (void *)mbx_mem->payload, sizeof(response));
scmi_put_channel(ch);
*info = response.info;
return ret;
}
@@ -0,0 +1,25 @@
/*
* Copyright (c) 2019, ARM Limited and Contributors. All rights reserved.
*
* SPDX-License-Identifier: BSD-3-Clause
*/
#ifndef SCMI_SQ_H
#define SCMI_SQ_H
#include <stddef.h>
#include <stdint.h>
#include <sq_common.h>
/* Structure to represent available DRAM region */
struct dram_info_resp {
int status;
int reserved;
struct draminfo info;
};
/* API to get the available DRAM region */
int scmi_get_draminfo(void *p, struct draminfo *info);
#endif /* SCMI_SQ_H */
@@ -0,0 +1,195 @@
/*
* Copyright (c) 2014-2020, ARM Limited and Contributors. All rights reserved.
*
* SPDX-License-Identifier: BSD-3-Clause
*/
#include <assert.h>
#include <stdint.h>
#include <arch_helpers.h>
#include <common/debug.h>
#include <drivers/arm/css/css_mhu.h>
#include <drivers/arm/css/css_scp.h>
#include <drivers/arm/css/css_scpi.h>
#include <plat/common/platform.h>
#include <platform_def.h>
/* ID of the MHU slot used for the BOM protocol */
#define BOM_MHU_SLOT_ID 0
/* Boot commands sent from AP -> SCP */
#define BOOT_CMD_INFO 0x00
#define BOOT_CMD_DATA 0x01
/* BOM command header */
typedef struct {
uint32_t id : 8;
uint32_t reserved : 24;
} bom_cmd_t;
typedef struct {
uint32_t image_size;
uint32_t checksum;
} cmd_info_payload_t;
/*
* Unlike the SCPI protocol, the boot protocol uses the same memory region
* for both AP -> SCP and SCP -> AP transfers; define the address of this...
*/
#define BOM_SHARED_MEM PLAT_CSS_SCP_COM_SHARED_MEM_BASE
#define BOM_CMD_HEADER ((bom_cmd_t *) BOM_SHARED_MEM)
#define BOM_CMD_PAYLOAD ((void *) (BOM_SHARED_MEM + sizeof(bom_cmd_t)))
typedef struct {
/* Offset from the base address of the Trusted RAM */
uint32_t offset;
uint32_t block_size;
} cmd_data_payload_t;
/*
* All CSS platforms load SCP_BL2/SCP_BL2U just below BL2 (this is where BL31
* usually resides except when ARM_BL31_IN_DRAM is
* set). Ensure that SCP_BL2/SCP_BL2U do not overflow into shared RAM and
* the fw_config.
*/
CASSERT(SCP_BL2_LIMIT <= BL2_BASE, assert_scp_bl2_overwrite_bl2);
CASSERT(SCP_BL2U_LIMIT <= BL2_BASE, assert_scp_bl2u_overwrite_bl2);
CASSERT(SCP_BL2_BASE >= ARM_FW_CONFIG_LIMIT, assert_scp_bl2_overflow);
CASSERT(SCP_BL2U_BASE >= ARM_FW_CONFIG_LIMIT, assert_scp_bl2u_overflow);
static void scp_boot_message_start(void)
{
mhu_secure_message_start(BOM_MHU_SLOT_ID);
}
static void scp_boot_message_send(size_t payload_size)
{
/* Ensure that any write to the BOM payload area is seen by SCP before
* we write to the MHU register. If these 2 writes were reordered by
* the CPU then SCP would read stale payload data */
dmbst();
/* Send command to SCP */
mhu_secure_message_send(BOM_MHU_SLOT_ID);
}
static uint32_t scp_boot_message_wait(size_t size)
{
uint32_t mhu_status;
mhu_status = mhu_secure_message_wait();
/* Expect an SCP Boot Protocol message, reject any other protocol */
if (mhu_status != (1 << BOM_MHU_SLOT_ID)) {
ERROR("MHU: Unexpected protocol (MHU status: 0x%x)\n",
mhu_status);
panic();
}
/* Ensure that any read to the BOM payload area is done after reading
* the MHU register. If these 2 reads were reordered then the CPU would
* read invalid payload data */
dmbld();
return *(uint32_t *) BOM_SHARED_MEM;
}
static void scp_boot_message_end(void)
{
mhu_secure_message_end(BOM_MHU_SLOT_ID);
}
int css_scp_boot_image_xfer(void *image, unsigned int image_size)
{
uint32_t response;
uint32_t checksum;
cmd_info_payload_t *cmd_info_payload;
cmd_data_payload_t *cmd_data_payload;
assert((uintptr_t) image == SCP_BL2_BASE);
if ((image_size == 0) || (image_size % 4 != 0)) {
ERROR("Invalid size for the SCP_BL2 image. Must be a multiple of "
"4 bytes and not zero (current size = 0x%x)\n",
image_size);
return -1;
}
/* Extract the checksum from the image */
checksum = *(uint32_t *) image;
image = (char *) image + sizeof(checksum);
image_size -= sizeof(checksum);
mhu_secure_init();
VERBOSE("Send info about the SCP_BL2 image to be transferred to SCP\n");
/*
* Send information about the SCP firmware image about to be transferred
* to SCP
*/
scp_boot_message_start();
BOM_CMD_HEADER->id = BOOT_CMD_INFO;
cmd_info_payload = BOM_CMD_PAYLOAD;
cmd_info_payload->image_size = image_size;
cmd_info_payload->checksum = checksum;
scp_boot_message_send(sizeof(*cmd_info_payload));
#if CSS_DETECT_PRE_1_7_0_SCP
{
const uint32_t deprecated_scp_nack_cmd = 0x404;
uint32_t mhu_status;
VERBOSE("Detecting SCP version incompatibility\n");
mhu_status = mhu_secure_message_wait();
if (mhu_status == deprecated_scp_nack_cmd) {
ERROR("Detected an incompatible version of the SCP firmware.\n");
ERROR("Only versions from v1.7.0 onwards are supported.\n");
ERROR("Please update the SCP firmware.\n");
return -1;
}
VERBOSE("SCP version looks OK\n");
}
#endif /* CSS_DETECT_PRE_1_7_0_SCP */
response = scp_boot_message_wait(sizeof(response));
scp_boot_message_end();
if (response != 0) {
ERROR("SCP BOOT_CMD_INFO returned error %u\n", response);
return -1;
}
VERBOSE("Transferring SCP_BL2 image to SCP\n");
/* Transfer SCP_BL2 image to SCP */
scp_boot_message_start();
BOM_CMD_HEADER->id = BOOT_CMD_DATA;
cmd_data_payload = BOM_CMD_PAYLOAD;
cmd_data_payload->offset = (uintptr_t) image - ARM_TRUSTED_SRAM_BASE;
cmd_data_payload->block_size = image_size;
scp_boot_message_send(sizeof(*cmd_data_payload));
response = scp_boot_message_wait(sizeof(response));
scp_boot_message_end();
if (response != 0) {
ERROR("SCP BOOT_CMD_DATA returned error %u\n", response);
return -1;
}
return 0;
}
int css_scp_boot_ready(void)
{
VERBOSE("Waiting for SCP to signal it is ready to go on\n");
/* Wait for SCP to signal it's ready */
return scpi_wait_ready();
}
@@ -0,0 +1,499 @@
/*
* Copyright (c) 2017-2022, Arm Limited and Contributors. All rights reserved.
*
* SPDX-License-Identifier: BSD-3-Clause
*/
#include <assert.h>
#include <string.h>
#include <arch_helpers.h>
#include <common/debug.h>
#include <drivers/arm/css/css_scp.h>
#include <drivers/arm/css/scmi.h>
#include <lib/mmio.h>
#include <plat/arm/common/plat_arm.h>
#include <plat/arm/css/common/css_pm.h>
#include <plat/common/platform.h>
#include <platform_def.h>
/*
* This file implements the SCP helper functions using SCMI protocol.
*/
/*
* SCMI power state parameter bit field encoding for ARM CSS platforms.
*
* 31 20 19 16 15 12 11 8 7 4 3 0
* +-------------------------------------------------------------+
* | SBZ | Max level | Level 3 | Level 2 | Level 1 | Level 0 |
* | | | state | state | state | state |
* +-------------------------------------------------------------+
*
* `Max level` encodes the highest level that has a valid power state
* encoded in the power state.
*/
#define SCMI_PWR_STATE_MAX_PWR_LVL_SHIFT 16
#define SCMI_PWR_STATE_MAX_PWR_LVL_WIDTH 4
#define SCMI_PWR_STATE_MAX_PWR_LVL_MASK \
((1 << SCMI_PWR_STATE_MAX_PWR_LVL_WIDTH) - 1)
#define SCMI_SET_PWR_STATE_MAX_PWR_LVL(_power_state, _max_level) \
(_power_state) |= ((_max_level) & SCMI_PWR_STATE_MAX_PWR_LVL_MASK)\
<< SCMI_PWR_STATE_MAX_PWR_LVL_SHIFT
#define SCMI_GET_PWR_STATE_MAX_PWR_LVL(_power_state) \
(((_power_state) >> SCMI_PWR_STATE_MAX_PWR_LVL_SHIFT) \
& SCMI_PWR_STATE_MAX_PWR_LVL_MASK)
#define SCMI_PWR_STATE_LVL_WIDTH 4
#define SCMI_PWR_STATE_LVL_MASK \
((1 << SCMI_PWR_STATE_LVL_WIDTH) - 1)
#define SCMI_SET_PWR_STATE_LVL(_power_state, _level, _level_state) \
(_power_state) |= ((_level_state) & SCMI_PWR_STATE_LVL_MASK) \
<< (SCMI_PWR_STATE_LVL_WIDTH * (_level))
#define SCMI_GET_PWR_STATE_LVL(_power_state, _level) \
(((_power_state) >> (SCMI_PWR_STATE_LVL_WIDTH * (_level))) & \
SCMI_PWR_STATE_LVL_MASK)
/*
* The SCMI power state enumeration for a power domain level
*/
typedef enum {
scmi_power_state_off = 0,
scmi_power_state_on = 1,
scmi_power_state_sleep = 2,
} scmi_power_state_t;
/*
* The global handles for invoking the SCMI driver APIs after the driver
* has been initialized.
*/
static void *scmi_handles[PLAT_ARM_SCMI_CHANNEL_COUNT];
/* The global SCMI channels array */
static scmi_channel_t scmi_channels[PLAT_ARM_SCMI_CHANNEL_COUNT];
/*
* Channel ID for the default SCMI channel.
* The default channel is used to issue SYSTEM level SCMI requests and is
* initialized to the channel which has the boot cpu as its resource.
*/
static uint32_t default_scmi_channel_id;
/*
* TODO: Allow use of channel specific lock instead of using a single lock for
* all the channels.
*/
ARM_SCMI_INSTANTIATE_LOCK;
/*
* Function to obtain the SCMI Domain ID and SCMI Channel number from the linear
* core position. The SCMI Channel number is encoded in the upper 16 bits and
* the Domain ID is encoded in the lower 16 bits in each entry of the mapping
* array exported by the platform.
*/
static void css_scp_core_pos_to_scmi_channel(unsigned int core_pos,
unsigned int *scmi_domain_id, unsigned int *scmi_channel_id)
{
unsigned int composite_id;
composite_id = plat_css_core_pos_to_scmi_dmn_id_map[core_pos];
*scmi_channel_id = GET_SCMI_CHANNEL_ID(composite_id);
*scmi_domain_id = GET_SCMI_DOMAIN_ID(composite_id);
}
/*
* Helper function to suspend a CPU power domain and its parent power domains
* if applicable.
*/
void css_scp_suspend(const struct psci_power_state *target_state)
{
int ret;
/* At least power domain level 0 should be specified to be suspended */
assert(target_state->pwr_domain_state[ARM_PWR_LVL0] ==
ARM_LOCAL_STATE_OFF);
/* Check if power down at system power domain level is requested */
if (css_system_pwr_state(target_state) == ARM_LOCAL_STATE_OFF) {
/* Issue SCMI command for SYSTEM_SUSPEND on all SCMI channels */
ret = scmi_sys_pwr_state_set(
scmi_handles[default_scmi_channel_id],
SCMI_SYS_PWR_FORCEFUL_REQ, SCMI_SYS_PWR_SUSPEND);
if (ret != SCMI_E_SUCCESS) {
ERROR("SCMI system power domain suspend return 0x%x unexpected\n",
ret);
panic();
}
return;
}
#if !HW_ASSISTED_COHERENCY
unsigned int lvl, channel_id, domain_id;
uint32_t scmi_pwr_state = 0;
/*
* If we reach here, then assert that power down at system power domain
* level is running.
*/
assert(css_system_pwr_state(target_state) == ARM_LOCAL_STATE_RUN);
/* For level 0, specify `scmi_power_state_sleep` as the power state */
SCMI_SET_PWR_STATE_LVL(scmi_pwr_state, ARM_PWR_LVL0,
scmi_power_state_sleep);
for (lvl = ARM_PWR_LVL1; lvl <= PLAT_MAX_PWR_LVL; lvl++) {
if (target_state->pwr_domain_state[lvl] == ARM_LOCAL_STATE_RUN)
break;
assert(target_state->pwr_domain_state[lvl] ==
ARM_LOCAL_STATE_OFF);
/*
* Specify `scmi_power_state_off` as power state for higher
* levels.
*/
SCMI_SET_PWR_STATE_LVL(scmi_pwr_state, lvl,
scmi_power_state_off);
}
SCMI_SET_PWR_STATE_MAX_PWR_LVL(scmi_pwr_state, lvl - 1);
css_scp_core_pos_to_scmi_channel(plat_my_core_pos(),
&domain_id, &channel_id);
ret = scmi_pwr_state_set(scmi_handles[channel_id],
domain_id, scmi_pwr_state);
if (ret != SCMI_E_SUCCESS) {
ERROR("SCMI set power state command return 0x%x unexpected\n",
ret);
panic();
}
#endif
}
/*
* Helper function to turn off a CPU power domain and its parent power domains
* if applicable.
*/
void css_scp_off(const struct psci_power_state *target_state)
{
unsigned int lvl = 0, channel_id, domain_id;
int ret;
uint32_t scmi_pwr_state = 0;
/* At-least the CPU level should be specified to be OFF */
assert(target_state->pwr_domain_state[ARM_PWR_LVL0] ==
ARM_LOCAL_STATE_OFF);
/* PSCI CPU OFF cannot be used to turn OFF system power domain */
assert(css_system_pwr_state(target_state) == ARM_LOCAL_STATE_RUN);
for (; lvl <= PLAT_MAX_PWR_LVL; lvl++) {
if (target_state->pwr_domain_state[lvl] == ARM_LOCAL_STATE_RUN)
break;
assert(target_state->pwr_domain_state[lvl] ==
ARM_LOCAL_STATE_OFF);
SCMI_SET_PWR_STATE_LVL(scmi_pwr_state, lvl,
scmi_power_state_off);
}
SCMI_SET_PWR_STATE_MAX_PWR_LVL(scmi_pwr_state, lvl - 1);
css_scp_core_pos_to_scmi_channel(plat_my_core_pos(),
&domain_id, &channel_id);
ret = scmi_pwr_state_set(scmi_handles[channel_id],
domain_id, scmi_pwr_state);
if (ret != SCMI_E_QUEUED && ret != SCMI_E_SUCCESS) {
ERROR("SCMI set power state command return 0x%x unexpected\n",
ret);
panic();
}
}
/*
* Helper function to turn ON a CPU power domain and its parent power domains
* if applicable.
*/
void css_scp_on(u_register_t mpidr)
{
unsigned int lvl = 0, channel_id, core_pos, domain_id;
int ret;
uint32_t scmi_pwr_state = 0;
for (; lvl <= PLAT_MAX_PWR_LVL; lvl++)
SCMI_SET_PWR_STATE_LVL(scmi_pwr_state, lvl,
scmi_power_state_on);
SCMI_SET_PWR_STATE_MAX_PWR_LVL(scmi_pwr_state, lvl - 1);
core_pos = (unsigned int)plat_core_pos_by_mpidr(mpidr);
assert(core_pos < PLATFORM_CORE_COUNT);
css_scp_core_pos_to_scmi_channel(core_pos, &domain_id,
&channel_id);
ret = scmi_pwr_state_set(scmi_handles[channel_id],
domain_id, scmi_pwr_state);
if (ret != SCMI_E_QUEUED && ret != SCMI_E_SUCCESS) {
ERROR("SCMI set power state command return 0x%x unexpected\n",
ret);
panic();
}
}
/*
* Helper function to get the power state of a power domain node as reported
* by the SCP.
*/
int css_scp_get_power_state(u_register_t mpidr, unsigned int power_level)
{
int ret;
uint32_t scmi_pwr_state = 0, lvl_state;
unsigned int channel_id, cpu_idx, domain_id;
/* We don't support get power state at the system power domain level */
if ((power_level > PLAT_MAX_PWR_LVL) ||
(power_level == CSS_SYSTEM_PWR_DMN_LVL)) {
WARN("Invalid power level %u specified for SCMI get power state\n",
power_level);
return PSCI_E_INVALID_PARAMS;
}
cpu_idx = (unsigned int)plat_core_pos_by_mpidr(mpidr);
assert(cpu_idx < PLATFORM_CORE_COUNT);
css_scp_core_pos_to_scmi_channel(cpu_idx, &domain_id, &channel_id);
ret = scmi_pwr_state_get(scmi_handles[channel_id],
domain_id, &scmi_pwr_state);
if (ret != SCMI_E_SUCCESS) {
WARN("SCMI get power state command return 0x%x unexpected\n",
ret);
return PSCI_E_INVALID_PARAMS;
}
/*
* Find the maximum power level described in the get power state
* command. If it is less than the requested power level, then assume
* the requested power level is ON.
*/
if (SCMI_GET_PWR_STATE_MAX_PWR_LVL(scmi_pwr_state) < power_level)
return HW_ON;
lvl_state = SCMI_GET_PWR_STATE_LVL(scmi_pwr_state, power_level);
if (lvl_state == scmi_power_state_on)
return HW_ON;
assert((lvl_state == scmi_power_state_off) ||
(lvl_state == scmi_power_state_sleep));
return HW_OFF;
}
/*
* Callback function to raise a SGI designated to trigger the CPU power down
* sequence on all the online secondary cores.
*/
static void css_raise_pwr_down_interrupt(u_register_t mpidr)
{
#if CSS_SYSTEM_GRACEFUL_RESET
plat_ic_raise_el3_sgi(CSS_CPU_PWR_DOWN_REQ_INTR, mpidr);
#endif
}
void __dead2 css_scp_system_off(int state)
{
int ret;
/*
* Before issuing the system power down command, set the trusted mailbox
* to 0. This will ensure that in the case of a warm/cold reset, the
* primary CPU executes from the cold boot sequence.
*/
mmio_write_64(PLAT_ARM_TRUSTED_MAILBOX_BASE, 0U);
/*
* Send powerdown request to online secondary core(s)
*/
ret = psci_stop_other_cores(0, css_raise_pwr_down_interrupt);
if (ret != PSCI_E_SUCCESS) {
ERROR("Failed to powerdown secondary core(s)\n");
}
/*
* Disable GIC CPU interface to prevent pending interrupt from waking
* up the AP from WFI.
*/
plat_arm_gic_cpuif_disable();
plat_arm_gic_redistif_off();
/*
* Issue SCMI command. First issue a graceful
* request and if that fails force the request.
*/
ret = scmi_sys_pwr_state_set(scmi_handles[default_scmi_channel_id],
SCMI_SYS_PWR_FORCEFUL_REQ,
state);
if (ret != SCMI_E_SUCCESS) {
ERROR("SCMI system power state set 0x%x returns unexpected 0x%x\n",
state, ret);
panic();
}
/* Powerdown of primary core */
psci_pwrdown_cpu(PLAT_MAX_PWR_LVL);
wfi();
ERROR("CSS set power state: operation not handled.\n");
panic();
}
/*
* Helper function to shutdown the system via SCMI.
*/
void __dead2 css_scp_sys_shutdown(void)
{
css_scp_system_off(SCMI_SYS_PWR_SHUTDOWN);
}
/*
* Helper function to reset the system via SCMI.
*/
void __dead2 css_scp_sys_reboot(void)
{
css_scp_system_off(SCMI_SYS_PWR_COLD_RESET);
}
static int scmi_ap_core_init(scmi_channel_t *ch)
{
#if PROGRAMMABLE_RESET_ADDRESS
uint32_t version;
int ret;
ret = scmi_proto_version(ch, SCMI_AP_CORE_PROTO_ID, &version);
if (ret != SCMI_E_SUCCESS) {
WARN("SCMI AP core protocol version message failed\n");
return -1;
}
if (!is_scmi_version_compatible(SCMI_AP_CORE_PROTO_VER, version)) {
WARN("SCMI AP core protocol version 0x%x incompatible with driver version 0x%x\n",
version, SCMI_AP_CORE_PROTO_VER);
return -1;
}
INFO("SCMI AP core protocol version 0x%x detected\n", version);
#endif
return 0;
}
void __init plat_arm_pwrc_setup(void)
{
unsigned int composite_id, idx;
for (idx = 0; idx < PLAT_ARM_SCMI_CHANNEL_COUNT; idx++) {
INFO("Initializing SCMI driver on channel %d\n", idx);
scmi_channels[idx].info = plat_css_get_scmi_info(idx);
scmi_channels[idx].lock = ARM_SCMI_LOCK_GET_INSTANCE;
scmi_handles[idx] = scmi_init(&scmi_channels[idx]);
if (scmi_handles[idx] == NULL) {
ERROR("SCMI Initialization failed on channel %d\n", idx);
panic();
}
if (scmi_ap_core_init(&scmi_channels[idx]) < 0) {
ERROR("SCMI AP core protocol initialization failed\n");
panic();
}
}
composite_id = plat_css_core_pos_to_scmi_dmn_id_map[plat_my_core_pos()];
default_scmi_channel_id = GET_SCMI_CHANNEL_ID(composite_id);
}
/******************************************************************************
* This function overrides the default definition for ARM platforms. Initialize
* the SCMI driver, query capability via SCMI and modify the PSCI capability
* based on that.
*****************************************************************************/
const plat_psci_ops_t *css_scmi_override_pm_ops(plat_psci_ops_t *ops)
{
uint32_t msg_attr;
int ret;
void *scmi_handle = scmi_handles[default_scmi_channel_id];
assert(scmi_handle);
/* Check that power domain POWER_STATE_SET message is supported */
ret = scmi_proto_msg_attr(scmi_handle, SCMI_PWR_DMN_PROTO_ID,
SCMI_PWR_STATE_SET_MSG, &msg_attr);
if (ret != SCMI_E_SUCCESS) {
ERROR("Set power state command is not supported by SCMI\n");
panic();
}
/*
* Don't support PSCI NODE_HW_STATE call if SCMI doesn't support
* POWER_STATE_GET message.
*/
ret = scmi_proto_msg_attr(scmi_handle, SCMI_PWR_DMN_PROTO_ID,
SCMI_PWR_STATE_GET_MSG, &msg_attr);
if (ret != SCMI_E_SUCCESS)
ops->get_node_hw_state = NULL;
/* Check if the SCMI SYSTEM_POWER_STATE_SET message is supported */
ret = scmi_proto_msg_attr(scmi_handle, SCMI_SYS_PWR_PROTO_ID,
SCMI_SYS_PWR_STATE_SET_MSG, &msg_attr);
if (ret != SCMI_E_SUCCESS) {
/* System power management operations are not supported */
ops->system_off = NULL;
ops->system_reset = NULL;
ops->get_sys_suspend_power_state = NULL;
} else {
if (!(msg_attr & SCMI_SYS_PWR_SUSPEND_SUPPORTED)) {
/*
* System power management protocol is available, but
* it does not support SYSTEM SUSPEND.
*/
ops->get_sys_suspend_power_state = NULL;
}
if (!(msg_attr & SCMI_SYS_PWR_WARM_RESET_SUPPORTED)) {
/*
* WARM reset is not available.
*/
ops->system_reset2 = NULL;
}
}
return ops;
}
int css_system_reset2(int is_vendor, int reset_type, u_register_t cookie)
{
if (is_vendor || (reset_type != PSCI_RESET2_SYSTEM_WARM_RESET))
return PSCI_E_INVALID_PARAMS;
css_scp_system_off(SCMI_SYS_PWR_WARM_RESET);
/*
* css_scp_system_off cannot return (it is a __dead function),
* but css_system_reset2 has to return some value, even in
* this case.
*/
return 0;
}
#if PROGRAMMABLE_RESET_ADDRESS
void plat_arm_program_trusted_mailbox(uintptr_t address)
{
int ret, i;
for (i = 0; i < PLAT_ARM_SCMI_CHANNEL_COUNT; i++) {
assert(scmi_handles[i]);
ret = scmi_ap_core_set_reset_addr(scmi_handles[i], address,
SCMI_AP_CORE_LOCK_ATTR);
if (ret != SCMI_E_SUCCESS) {
ERROR("CSS: Failed to program reset address: %d\n", ret);
panic();
}
}
}
#endif
@@ -0,0 +1,165 @@
/*
* Copyright (c) 2016-2018, ARM Limited and Contributors. All rights reserved.
*
* SPDX-License-Identifier: BSD-3-Clause
*/
#include <assert.h>
#include <arch_helpers.h>
#include <common/debug.h>
#include <drivers/arm/css/css_scp.h>
#include <drivers/arm/css/css_scpi.h>
#include <plat/arm/common/plat_arm.h>
#include <plat/arm/css/common/css_pm.h>
/*
* This file implements the SCP power management functions using SCPI protocol.
*/
/*
* Helper function to inform power down state to SCP.
*/
void css_scp_suspend(const struct psci_power_state *target_state)
{
uint32_t cluster_state = scpi_power_on;
uint32_t system_state = scpi_power_on;
/* Check if power down at system power domain level is requested */
if (css_system_pwr_state(target_state) == ARM_LOCAL_STATE_OFF)
system_state = scpi_power_retention;
/* Cluster is to be turned off, so disable coherency */
if (CSS_CLUSTER_PWR_STATE(target_state) == ARM_LOCAL_STATE_OFF)
cluster_state = scpi_power_off;
/*
* Ask the SCP to power down the appropriate components depending upon
* their state.
*/
scpi_set_css_power_state(read_mpidr_el1(),
scpi_power_off,
cluster_state,
system_state);
}
/*
* Helper function to turn off a CPU power domain and its parent power domains
* if applicable. Since SCPI doesn't differentiate between OFF and suspend, we
* call the suspend helper here.
*/
void css_scp_off(const struct psci_power_state *target_state)
{
css_scp_suspend(target_state);
}
/*
* Helper function to turn ON a CPU power domain and its parent power domains
* if applicable.
*/
void css_scp_on(u_register_t mpidr)
{
/*
* SCP takes care of powering up parent power domains so we
* only need to care about level 0
*/
scpi_set_css_power_state(mpidr, scpi_power_on, scpi_power_on,
scpi_power_on);
}
/*
* Helper function to get the power state of a power domain node as reported
* by the SCP.
*/
int css_scp_get_power_state(u_register_t mpidr, unsigned int power_level)
{
int rc, element;
unsigned int cpu_state, cluster_state;
/*
* The format of 'power_level' is implementation-defined, but 0 must
* mean a CPU. We also allow 1 to denote the cluster
*/
if (power_level != ARM_PWR_LVL0 && power_level != ARM_PWR_LVL1)
return PSCI_E_INVALID_PARAMS;
/* Query SCP */
rc = scpi_get_css_power_state(mpidr, &cpu_state, &cluster_state);
if (rc != 0)
return PSCI_E_INVALID_PARAMS;
/* Map power states of CPU and cluster to expected PSCI return codes */
if (power_level == ARM_PWR_LVL0) {
/*
* The CPU state returned by SCP is an 8-bit bit mask
* corresponding to each CPU in the cluster
*/
#if ARM_PLAT_MT
/*
* The current SCPI driver only caters for single-threaded
* platforms. Hence we ignore the thread ID (which is always 0)
* for such platforms.
*/
element = (mpidr >> MPIDR_AFF1_SHIFT) & MPIDR_AFFLVL_MASK;
#else
element = mpidr & MPIDR_AFFLVL_MASK;
#endif /* ARM_PLAT_MT */
return CSS_CPU_PWR_STATE(cpu_state, element) ==
CSS_CPU_PWR_STATE_ON ? HW_ON : HW_OFF;
} else {
assert(cluster_state == CSS_CLUSTER_PWR_STATE_ON ||
cluster_state == CSS_CLUSTER_PWR_STATE_OFF);
return cluster_state == CSS_CLUSTER_PWR_STATE_ON ? HW_ON :
HW_OFF;
}
}
/*
* Helper function to shutdown the system via SCPI.
*/
void __dead2 css_scp_sys_shutdown(void)
{
uint32_t response;
/*
* Disable GIC CPU interface to prevent pending interrupt
* from waking up the AP from WFI.
*/
plat_arm_gic_cpuif_disable();
/* Send the power down request to the SCP */
response = scpi_sys_power_state(scpi_system_shutdown);
if (response != SCP_OK) {
ERROR("CSS System Off: SCP error %u.\n", response);
panic();
}
wfi();
ERROR("CSS System Off: operation not handled.\n");
panic();
}
/*
* Helper function to reset the system via SCPI.
*/
void __dead2 css_scp_sys_reboot(void)
{
uint32_t response;
/*
* Disable GIC CPU interface to prevent pending interrupt
* from waking up the AP from WFI.
*/
plat_arm_gic_cpuif_disable();
/* Send the system reset request to the SCP */
response = scpi_sys_power_state(scpi_system_reboot);
if (response != SCP_OK) {
ERROR("CSS System Reset: SCP error %u.\n", response);
panic();
}
wfi();
ERROR("CSS System Reset: operation not handled.\n");
panic();
}
@@ -0,0 +1,95 @@
/*
* Copyright (c) 2014-2017, ARM Limited and Contributors. All rights reserved.
*
* SPDX-License-Identifier: BSD-3-Clause
*/
#include <assert.h>
#include <stdint.h>
#include <arch_helpers.h>
#include <common/debug.h>
#include <drivers/arm/css/css_scp.h>
#include <drivers/arm/css/sds.h>
#include <drivers/delay_timer.h>
#include <plat/common/platform.h>
#include <platform_def.h>
int css_scp_boot_image_xfer(void *image, unsigned int image_size)
{
int ret;
unsigned int image_offset, image_flags;
ret = sds_init();
if (ret != SDS_OK) {
ERROR("SCP SDS initialization failed\n");
panic();
}
VERBOSE("Writing SCP image metadata\n");
image_offset = (uintptr_t) image - ARM_TRUSTED_SRAM_BASE;
ret = sds_struct_write(SDS_SCP_IMG_STRUCT_ID, SDS_SCP_IMG_ADDR_OFFSET,
&image_offset, SDS_SCP_IMG_ADDR_SIZE,
SDS_ACCESS_MODE_NON_CACHED);
if (ret != SDS_OK)
goto sds_fail;
ret = sds_struct_write(SDS_SCP_IMG_STRUCT_ID, SDS_SCP_IMG_SIZE_OFFSET,
&image_size, SDS_SCP_IMG_SIZE_SIZE,
SDS_ACCESS_MODE_NON_CACHED);
if (ret != SDS_OK)
goto sds_fail;
VERBOSE("Marking SCP image metadata as valid\n");
image_flags = SDS_SCP_IMG_VALID_FLAG_BIT;
ret = sds_struct_write(SDS_SCP_IMG_STRUCT_ID, SDS_SCP_IMG_FLAG_OFFSET,
&image_flags, SDS_SCP_IMG_FLAG_SIZE,
SDS_ACCESS_MODE_NON_CACHED);
if (ret != SDS_OK)
goto sds_fail;
return 0;
sds_fail:
ERROR("SCP SDS write to SCP IMG struct failed\n");
panic();
}
/*
* API to wait for SCP to signal till it's ready after booting the transferred
* image.
*/
int css_scp_boot_ready(void)
{
uint32_t scp_feature_availability_flags;
int ret, retry = CSS_SCP_READY_10US_RETRIES;
VERBOSE("Waiting for SCP RAM to complete its initialization process\n");
/* Wait for the SCP RAM Firmware to complete its initialization process */
while (retry > 0) {
ret = sds_struct_read(SDS_FEATURE_AVAIL_STRUCT_ID, 0,
&scp_feature_availability_flags,
SDS_FEATURE_AVAIL_SIZE,
SDS_ACCESS_MODE_NON_CACHED);
if (ret == SDS_ERR_STRUCT_NOT_FINALIZED)
continue;
if (ret != SDS_OK) {
ERROR(" sds_struct_read failed\n");
panic();
}
if (scp_feature_availability_flags &
SDS_FEATURE_AVAIL_SCP_RAM_READY_BIT)
return 0;
udelay(10);
retry--;
}
ERROR("Timeout of %d ms expired waiting for SCP RAM Ready flag\n",
CSS_SCP_READY_10US_RETRIES/100);
plat_panic_handler();
}
@@ -0,0 +1,272 @@
/*
* Copyright (c) 2014-2020, ARM Limited and Contributors. All rights reserved.
*
* SPDX-License-Identifier: BSD-3-Clause
*/
#include <assert.h>
#include <string.h>
#include <arch_helpers.h>
#include <common/debug.h>
#include <drivers/arm/css/css_mhu.h>
#include <drivers/arm/css/css_scpi.h>
#include <lib/utils.h>
#include <plat/common/platform.h>
#include <platform_def.h>
#define SCPI_SHARED_MEM_SCP_TO_AP PLAT_CSS_SCP_COM_SHARED_MEM_BASE
#define SCPI_SHARED_MEM_AP_TO_SCP (PLAT_CSS_SCP_COM_SHARED_MEM_BASE \
+ 0x100)
/* Header and payload addresses for commands from AP to SCP */
#define SCPI_CMD_HEADER_AP_TO_SCP \
((scpi_cmd_t *) SCPI_SHARED_MEM_AP_TO_SCP)
#define SCPI_CMD_PAYLOAD_AP_TO_SCP \
((void *) (SCPI_SHARED_MEM_AP_TO_SCP + sizeof(scpi_cmd_t)))
/* Header and payload addresses for responses from SCP to AP */
#define SCPI_RES_HEADER_SCP_TO_AP \
((scpi_cmd_t *) SCPI_SHARED_MEM_SCP_TO_AP)
#define SCPI_RES_PAYLOAD_SCP_TO_AP \
((void *) (SCPI_SHARED_MEM_SCP_TO_AP + sizeof(scpi_cmd_t)))
/* ID of the MHU slot used for the SCPI protocol */
#define SCPI_MHU_SLOT_ID 0
static void scpi_secure_message_start(void)
{
mhu_secure_message_start(SCPI_MHU_SLOT_ID);
}
static void scpi_secure_message_send(size_t payload_size)
{
/*
* Ensure that any write to the SCPI payload area is seen by SCP before
* we write to the MHU register. If these 2 writes were reordered by
* the CPU then SCP would read stale payload data
*/
dmbst();
mhu_secure_message_send(SCPI_MHU_SLOT_ID);
}
static int scpi_secure_message_receive(scpi_cmd_t *cmd)
{
uint32_t mhu_status;
assert(cmd != NULL);
mhu_status = mhu_secure_message_wait();
/* Expect an SCPI message, reject any other protocol */
if (mhu_status != (1 << SCPI_MHU_SLOT_ID)) {
ERROR("MHU: Unexpected protocol (MHU status: 0x%x)\n",
mhu_status);
return -1;
}
/*
* Ensure that any read to the SCPI payload area is done after reading
* the MHU register. If these 2 reads were reordered then the CPU would
* read invalid payload data
*/
dmbld();
memcpy(cmd, (void *) SCPI_SHARED_MEM_SCP_TO_AP, sizeof(*cmd));
return 0;
}
static void scpi_secure_message_end(void)
{
mhu_secure_message_end(SCPI_MHU_SLOT_ID);
}
int scpi_wait_ready(void)
{
scpi_cmd_t scpi_cmd;
int rc;
VERBOSE("Waiting for SCP_READY command...\n");
/* Get a message from the SCP */
scpi_secure_message_start();
rc = scpi_secure_message_receive(&scpi_cmd);
scpi_secure_message_end();
/* If no message was received, don't send a response */
if (rc != 0)
return rc;
/* We are expecting 'SCP Ready', produce correct error if it's not */
scpi_status_t status = SCP_OK;
if (scpi_cmd.id != SCPI_CMD_SCP_READY) {
ERROR("Unexpected SCP command: expected command #%u, got command #%u\n",
SCPI_CMD_SCP_READY, scpi_cmd.id);
status = SCP_E_SUPPORT;
} else if (scpi_cmd.size != 0) {
ERROR("SCP_READY command has incorrect size: expected 0, got %u\n",
scpi_cmd.size);
status = SCP_E_SIZE;
}
VERBOSE("Sending response for SCP_READY command\n");
/*
* Send our response back to SCP.
* We are using the same SCPI header, just update the status field.
*/
scpi_cmd.status = status;
scpi_secure_message_start();
memcpy((void *) SCPI_SHARED_MEM_AP_TO_SCP, &scpi_cmd, sizeof(scpi_cmd));
scpi_secure_message_send(0);
scpi_secure_message_end();
return status == SCP_OK ? 0 : -1;
}
void scpi_set_css_power_state(unsigned int mpidr,
scpi_power_state_t cpu_state, scpi_power_state_t cluster_state,
scpi_power_state_t css_state)
{
scpi_cmd_t *cmd;
uint32_t state = 0;
uint32_t *payload_addr;
#if ARM_PLAT_MT
/*
* The current SCPI driver only caters for single-threaded platforms.
* Hence we ignore the thread ID (which is always 0) for such platforms.
*/
state |= (mpidr >> MPIDR_AFF1_SHIFT) & 0x0f; /* CPU ID */
state |= ((mpidr >> MPIDR_AFF2_SHIFT) & 0x0f) << 4; /* Cluster ID */
#else
state |= mpidr & 0x0f; /* CPU ID */
state |= (mpidr & 0xf00) >> 4; /* Cluster ID */
#endif /* ARM_PLAT_MT */
state |= cpu_state << 8;
state |= cluster_state << 12;
state |= css_state << 16;
scpi_secure_message_start();
/* Populate the command header */
cmd = SCPI_CMD_HEADER_AP_TO_SCP;
cmd->id = SCPI_CMD_SET_CSS_POWER_STATE;
cmd->set = SCPI_SET_NORMAL;
cmd->sender = 0;
cmd->size = sizeof(state);
/* Populate the command payload */
payload_addr = SCPI_CMD_PAYLOAD_AP_TO_SCP;
*payload_addr = state;
scpi_secure_message_send(sizeof(state));
/*
* SCP does not reply to this command in order to avoid MHU interrupts
* from the sender, which could interfere with its power state request.
*/
scpi_secure_message_end();
}
/*
* Query and obtain CSS power state from SCP.
*
* In response to the query, SCP returns power states of all CPUs in all
* clusters of the system. The returned response is then filtered based on the
* supplied MPIDR. Power states of requested cluster and CPUs within are updated
* via supplied non-NULL pointer arguments.
*
* Returns 0 on success, or -1 on errors.
*/
int scpi_get_css_power_state(unsigned int mpidr, unsigned int *cpu_state_p,
unsigned int *cluster_state_p)
{
scpi_cmd_t *cmd;
scpi_cmd_t response;
int power_state, cpu, cluster, rc = -1;
/*
* Extract CPU and cluster membership of the given MPIDR. SCPI caters
* for only up to 0xf clusters, and 8 CPUs per cluster
*/
#if ARM_PLAT_MT
/*
* The current SCPI driver only caters for single-threaded platforms.
* Hence we ignore the thread ID (which is always 0) for such platforms.
*/
cpu = (mpidr >> MPIDR_AFF1_SHIFT) & MPIDR_AFFLVL_MASK;
cluster = (mpidr >> MPIDR_AFF2_SHIFT) & MPIDR_AFFLVL_MASK;
#else
cpu = mpidr & MPIDR_AFFLVL_MASK;
cluster = (mpidr >> MPIDR_AFF1_SHIFT) & MPIDR_AFFLVL_MASK;
#endif /* ARM_PLAT_MT */
if (cpu >= 8 || cluster >= 0xf)
return -1;
scpi_secure_message_start();
/* Populate request headers */
zeromem(SCPI_CMD_HEADER_AP_TO_SCP, sizeof(*cmd));
cmd = SCPI_CMD_HEADER_AP_TO_SCP;
cmd->id = SCPI_CMD_GET_CSS_POWER_STATE;
/*
* Send message and wait for SCP's response
*/
scpi_secure_message_send(0);
if (scpi_secure_message_receive(&response) != 0)
goto exit;
if (response.status != SCP_OK)
goto exit;
/* Validate SCP response */
if (!CHECK_RESPONSE(response, cluster))
goto exit;
/* Extract power states for required cluster */
power_state = *(((uint16_t *) SCPI_RES_PAYLOAD_SCP_TO_AP) + cluster);
if (CLUSTER_ID(power_state) != cluster)
goto exit;
/* Update power state via pointers */
if (cluster_state_p)
*cluster_state_p = CLUSTER_POWER_STATE(power_state);
if (cpu_state_p)
*cpu_state_p = CPU_POWER_STATE(power_state);
rc = 0;
exit:
scpi_secure_message_end();
return rc;
}
uint32_t scpi_sys_power_state(scpi_system_state_t system_state)
{
scpi_cmd_t *cmd;
uint8_t *payload_addr;
scpi_cmd_t response;
scpi_secure_message_start();
/* Populate the command header */
cmd = SCPI_CMD_HEADER_AP_TO_SCP;
cmd->id = SCPI_CMD_SYS_POWER_STATE;
cmd->set = 0;
cmd->sender = 0;
cmd->size = sizeof(*payload_addr);
/* Populate the command payload */
payload_addr = SCPI_CMD_PAYLOAD_AP_TO_SCP;
*payload_addr = system_state & 0xff;
scpi_secure_message_send(sizeof(*payload_addr));
/* If no response is received, fill in an error status */
if (scpi_secure_message_receive(&response) != 0)
response.status = SCP_E_TIMEOUT;
scpi_secure_message_end();
return response.status;
}
@@ -0,0 +1,64 @@
/*
* Copyright (c) 2017, ARM Limited and Contributors. All rights reserved.
*
* SPDX-License-Identifier: BSD-3-Clause
*/
#include <arch.h>
#include <asm_macros.S>
#include <drivers/arm/css/sds.h>
#include <platform_def.h>
#include "../sds_private.h"
.globl sds_get_primary_cpu_id
/*
* int sds_get_primary_cpu_id(void);
* Return the primary CPU ID from SDS Structure
* Returns CPUID on success or -1 on failure
*/
func sds_get_primary_cpu_id
ldr r0, =PLAT_ARM_SDS_MEM_BASE
ldr r2, =SDS_REGION_SIGNATURE
ldr r1, [r0]
ubfx r3, r1, #0, #16
/* Check if the SDS region signature found */
cmp r2, r3
bne 2f
/* Get the structure count from region descriptor in r1 */
ubfx r1, r1, #SDS_REGION_STRUCT_COUNT_SHIFT, #SDS_REGION_STRUCT_COUNT_WIDTH
cmp r1, #0
beq 2f
add r0, r0, #SDS_REGION_DESC_SIZE
/* Initialize the loop iterator count in r3 */
mov r3, #0
loop_begin:
ldrh r2, [r0]
cmp r2, #SDS_AP_CPU_INFO_STRUCT_ID
bne continue_loop
/* We have found the required structure */
ldr r0, [r0,#(SDS_HEADER_SIZE + SDS_AP_CPU_INFO_PRIMARY_CPUID_OFFSET)]
bx lr
continue_loop:
/* Increment the loop counter and exit loop if counter == structure count */
add r3, r3, #0x1
cmp r1, r3
beq 2f
/* Read the 2nd word in header */
ldr r2, [r0,#4]
/* Get the structure size from header */
ubfx r2, r2, #SDS_HEADER_STRUCT_SIZE_SHIFT, #SDS_HEADER_STRUCT_SIZE_WIDTH
/* Add the structure size and SDS HEADER SIZE to point to next header */
add r2, r2, #SDS_HEADER_SIZE
add r0, r0, r2
b loop_begin
2:
mov r0, #0xffffffff
bx lr
endfunc sds_get_primary_cpu_id
@@ -0,0 +1,62 @@
/*
* Copyright (c) 2017, ARM Limited and Contributors. All rights reserved.
*
* SPDX-License-Identifier: BSD-3-Clause
*/
#include <arch.h>
#include <asm_macros.S>
#include <drivers/arm/css/sds.h>
#include <platform_def.h>
#include "../sds_private.h"
.globl sds_get_primary_cpu_id
/*
* int sds_get_primary_cpu_id(void);
* Return the primary CPI ID from SDS Structure
* Returns CPUID on success or -1 on failure
*/
func sds_get_primary_cpu_id
mov_imm x0, PLAT_ARM_SDS_MEM_BASE
mov w2, #SDS_REGION_SIGNATURE
ldr w1, [x0]
/* Check if the SDS region signature found */
cmp w2, w1, uxth
b.ne 2f
/* Get the structure count from region descriptor in `w1 */
ubfx w1, w1, #SDS_REGION_STRUCT_COUNT_SHIFT, #SDS_REGION_STRUCT_COUNT_WIDTH
cbz w1, 2f
add x0, x0, #SDS_REGION_DESC_SIZE
/* Initialize the loop iterator count in w3 */
mov w3, #0
loop_begin:
ldrh w2, [x0]
cmp w2, #SDS_AP_CPU_INFO_STRUCT_ID
b.ne continue_loop
/* We have found the required structure */
ldr w0, [x0,#(SDS_HEADER_SIZE + SDS_AP_CPU_INFO_PRIMARY_CPUID_OFFSET)]
ret
continue_loop:
/* Increment the loop counter and exit loop if counter == structure count */
add w3, w3, #0x1
cmp w1, w3
b.eq 2f
/* Read the 2nd word in header */
ldr w2, [x0,#4]
/* Get the structure size from header */
ubfx x2, x2, #SDS_HEADER_STRUCT_SIZE_SHIFT, #SDS_HEADER_STRUCT_SIZE_WIDTH
/* Add the structure size and SDS HEADER SIZE to point to next header */
add x2, x2, #SDS_HEADER_SIZE
add x0, x0, x2
b loop_begin
2:
mov w0, #0xffffffff
ret
endfunc sds_get_primary_cpu_id
@@ -0,0 +1,259 @@
/*
* Copyright (c) 2017, ARM Limited and Contributors. All rights reserved.
*
* SPDX-License-Identifier: BSD-3-Clause
*/
#include <assert.h>
#include <stdint.h>
#include <string.h>
#include <arch_helpers.h>
#include <common/debug.h>
#include <drivers/arm/css/sds.h>
#include <platform_def.h>
#include "sds_private.h"
/*
* Variables used to track and maintain the state of the memory region reserved
* for usage by the SDS framework.
*/
/* Pointer to the base of the SDS memory region */
static uintptr_t sds_mem_base;
/* Size of the SDS memory region in bytes */
static size_t sds_mem_size;
/*
* Perform some non-exhaustive tests to determine whether any of the fields
* within a Structure Header contain obviously invalid data.
* Returns SDS_OK on success, SDS_ERR_FAIL on error.
*/
static int sds_struct_is_valid(uintptr_t header)
{
size_t struct_size = GET_SDS_HEADER_STRUCT_SIZE(header);
/* Zero is not a valid identifier */
if (GET_SDS_HEADER_ID(header) == 0)
return SDS_ERR_FAIL;
/* Check SDS Schema version */
if (GET_SDS_HEADER_VERSION(header) == SDS_REGION_SCH_VERSION)
return SDS_ERR_FAIL;
/* The SDS Structure sizes have to be multiple of 8 */
if ((struct_size == 0) || ((struct_size % 8) != 0))
return SDS_ERR_FAIL;
if (struct_size > sds_mem_size)
return SDS_ERR_FAIL;
return SDS_OK;
}
/*
* Validate the SDS structure headers.
* Returns SDS_OK on success, SDS_ERR_FAIL on error.
*/
static int validate_sds_struct_headers(void)
{
unsigned int i, structure_count;
uintptr_t header;
structure_count = GET_SDS_REGION_STRUCTURE_COUNT(sds_mem_base);
if (structure_count == 0)
return SDS_ERR_FAIL;
header = sds_mem_base + SDS_REGION_DESC_SIZE;
/* Iterate over structure headers and validate each one */
for (i = 0; i < structure_count; i++) {
if (sds_struct_is_valid(header) != SDS_OK) {
WARN("SDS: Invalid structure header detected\n");
return SDS_ERR_FAIL;
}
header += GET_SDS_HEADER_STRUCT_SIZE(header) + SDS_HEADER_SIZE;
}
return SDS_OK;
}
/*
* Get the structure header pointer corresponding to the structure ID.
* Returns SDS_OK on success, SDS_ERR_STRUCT_NOT_FOUND on error.
*/
static int get_struct_header(uint32_t structure_id, struct_header_t **header)
{
unsigned int i, structure_count;
uintptr_t current_header;
assert(header);
structure_count = GET_SDS_REGION_STRUCTURE_COUNT(sds_mem_base);
if (structure_count == 0)
return SDS_ERR_STRUCT_NOT_FOUND;
current_header = ((uintptr_t)sds_mem_base) + SDS_REGION_DESC_SIZE;
/* Iterate over structure headers to find one with a matching ID */
for (i = 0; i < structure_count; i++) {
if (GET_SDS_HEADER_ID(current_header) == structure_id) {
*header = (struct_header_t *)current_header;
return SDS_OK;
}
current_header += GET_SDS_HEADER_STRUCT_SIZE(current_header) +
SDS_HEADER_SIZE;
}
*header = NULL;
return SDS_ERR_STRUCT_NOT_FOUND;
}
/*
* Check if a structure header corresponding to the structure ID exists.
* Returns SDS_OK if structure header exists else SDS_ERR_STRUCT_NOT_FOUND
* if not found.
*/
int sds_struct_exists(unsigned int structure_id)
{
struct_header_t *header = NULL;
int ret;
ret = get_struct_header(structure_id, &header);
if (ret == SDS_OK) {
assert(header);
}
return ret;
}
/*
* Read from field in the structure corresponding to `structure_id`.
* `fld_off` is the offset to the field in the structure and `mode`
* indicates whether cache maintenance need to performed prior to the read.
* The `data` is the pointer to store the read data of size specified by `size`.
* Returns SDS_OK on success or corresponding error codes on failure.
*/
int sds_struct_read(uint32_t structure_id, unsigned int fld_off,
void *data, size_t size, sds_access_mode_t mode)
{
int status;
uintptr_t field_base;
struct_header_t *header = NULL;
if (!data)
return SDS_ERR_INVALID_PARAMS;
/* Check if a structure with this ID exists */
status = get_struct_header(structure_id, &header);
if (status != SDS_OK)
return status;
assert(header);
if (mode == SDS_ACCESS_MODE_CACHED)
inv_dcache_range((uintptr_t)header, SDS_HEADER_SIZE + size);
if (!IS_SDS_HEADER_VALID(header)) {
WARN("SDS: Reading from un-finalized structure 0x%x\n",
structure_id);
return SDS_ERR_STRUCT_NOT_FINALIZED;
}
if ((fld_off + size) > GET_SDS_HEADER_STRUCT_SIZE(header))
return SDS_ERR_FAIL;
field_base = (uintptr_t)header + SDS_HEADER_SIZE + fld_off;
if (check_uptr_overflow(field_base, size - 1))
return SDS_ERR_FAIL;
/* Copy the required field in the struct */
memcpy(data, (void *)field_base, size);
return SDS_OK;
}
/*
* Write to the field in the structure corresponding to `structure_id`.
* `fld_off` is the offset to the field in the structure and `mode`
* indicates whether cache maintenance need to performed for the write.
* The `data` is the pointer to data of size specified by `size`.
* Returns SDS_OK on success or corresponding error codes on failure.
*/
int sds_struct_write(uint32_t structure_id, unsigned int fld_off,
void *data, size_t size, sds_access_mode_t mode)
{
int status;
uintptr_t field_base;
struct_header_t *header = NULL;
if (!data)
return SDS_ERR_INVALID_PARAMS;
/* Check if a structure with this ID exists */
status = get_struct_header(structure_id, &header);
if (status != SDS_OK)
return status;
assert(header);
if (mode == SDS_ACCESS_MODE_CACHED)
inv_dcache_range((uintptr_t)header, SDS_HEADER_SIZE + size);
if (!IS_SDS_HEADER_VALID(header)) {
WARN("SDS: Writing to un-finalized structure 0x%x\n",
structure_id);
return SDS_ERR_STRUCT_NOT_FINALIZED;
}
if ((fld_off + size) > GET_SDS_HEADER_STRUCT_SIZE(header))
return SDS_ERR_FAIL;
field_base = (uintptr_t)header + SDS_HEADER_SIZE + fld_off;
if (check_uptr_overflow(field_base, size - 1))
return SDS_ERR_FAIL;
/* Copy the required field in the struct */
memcpy((void *)field_base, data, size);
if (mode == SDS_ACCESS_MODE_CACHED)
flush_dcache_range((uintptr_t)field_base, size);
return SDS_OK;
}
/*
* Initialize the SDS driver. Also verifies the SDS version and sanity of
* the SDS structure headers.
* Returns SDS_OK on success, SDS_ERR_FAIL on error.
*/
int sds_init(void)
{
sds_mem_base = (uintptr_t)PLAT_ARM_SDS_MEM_BASE;
if (!IS_SDS_REGION_VALID(sds_mem_base)) {
WARN("SDS: No valid SDS Memory Region found\n");
return SDS_ERR_FAIL;
}
if (GET_SDS_REGION_SCHEMA_VERSION(sds_mem_base)
!= SDS_REGION_SCH_VERSION) {
WARN("SDS: Unsupported SDS schema version\n");
return SDS_ERR_FAIL;
}
sds_mem_size = GET_SDS_REGION_SIZE(sds_mem_base);
if (sds_mem_size > PLAT_ARM_SDS_MEM_SIZE_MAX) {
WARN("SDS: SDS Memory Region exceeds size limit\n");
return SDS_ERR_FAIL;
}
INFO("SDS: Detected SDS Memory Region (%zu bytes)\n", sds_mem_size);
if (validate_sds_struct_headers() != SDS_OK)
return SDS_ERR_FAIL;
return SDS_OK;
}
@@ -0,0 +1,100 @@
/*
* Copyright (c) 2017, ARM Limited and Contributors. All rights reserved.
*
* SPDX-License-Identifier: BSD-3-Clause
*/
#ifndef SDS_PRIVATE_H
#define SDS_PRIVATE_H
/* SDS Header defines */
#define SDS_HEADER_ID_SHIFT 0
#define SDS_HEADER_ID_WIDTH 16
#define SDS_HEADER_ID_MASK ((1 << SDS_HEADER_ID_WIDTH) - 1)
#define SDS_HEADER_MINOR_VERSION_WIDTH 8
#define SDS_HEADER_MINOR_VERSION_SHIFT 16
#define SDS_HEADER_MAJOR_VERSION_WIDTH 8
#define MAKE_SDS_HEADER_VERSION(major, minor) \
(((((major) & 0xff) << SDS_HEADER_MINOR_VERSION_WIDTH) | ((minor) & 0xff)))
#define SDS_HEADER_VERSION_MASK \
((1 << (SDS_HEADER_MINOR_VERSION_WIDTH + SDS_HEADER_MAJOR_VERSION_WIDTH)) - 1)
#define SDS_HEADER_VERSION MAKE_SDS_HEADER_VERSION(1, 0)
#define SDS_HEADER_STRUCT_SIZE_WIDTH 23
#define SDS_HEADER_STRUCT_SIZE_SHIFT 1
#define SDS_HEADER_STRUCT_SIZE_MASK ((1 << SDS_HEADER_STRUCT_SIZE_WIDTH) - 1)
#define SDS_HEADER_VALID_MASK 0x1
#define SDS_HEADER_VALID_SHIFT 0
#define SDS_HEADER_SIZE 0x8
/* Arbitrary, 16 bit value that indicates a valid SDS Memory Region */
#define SDS_REGION_SIGNATURE 0xAA7A
#define SDS_REGION_SIGNATURE_WIDTH 16
#define SDS_REGION_SIGNATURE_SHIFT 0
#define SDS_REGION_SIGNATURE_MASK ((1 << SDS_REGION_SIGNATURE_WIDTH) - 1)
#define SDS_REGION_STRUCT_COUNT_SHIFT 16
#define SDS_REGION_STRUCT_COUNT_WIDTH 8
#define SDS_REGION_STRUCT_COUNT_MASK ((1 << SDS_REGION_STRUCT_COUNT_WIDTH) - 1)
#define SDS_REGION_SCH_MINOR_SHIFT 24
#define SDS_REGION_SCH_MINOR_WIDTH 4
#define SDS_REGION_SCH_MINOR_MASK ((1 << SDS_REGION_SCH_MINOR_WIDTH) - 1)
#define SDS_REGION_SCH_MAJOR_SHIFT 28
#define SDS_REGION_SCH_MAJOR_WIDTH 4
#define SDS_REGION_SCH_MAJOR_MASK ((1 << SDS_REGION_SCH_MAJOR_WIDTH) - 1)
#define SDS_REGION_SCH_VERSION_MASK \
((1 << (SDS_REGION_SCH_MINOR_WIDTH + SDS_REGION_SCH_MAJOR_WIDTH)) - 1)
#define MAKE_SDS_REGION_SCH_VERSION(maj, min) \
((((maj) & SDS_REGION_SCH_MAJOR_MASK) << SDS_REGION_SCH_MINOR_WIDTH) | \
((min) & SDS_REGION_SCH_MINOR_MASK))
#define SDS_REGION_SCH_VERSION MAKE_SDS_REGION_SCH_VERSION(1, 0)
#define SDS_REGION_REGIONSIZE_OFFSET 0x4
#define SDS_REGION_DESC_SIZE 0x8
#ifndef __ASSEMBLER__
#include <stddef.h>
#include <stdint.h>
/* Header containing Shared Data Structure metadata */
typedef struct structure_header {
uint32_t reg[2];
} struct_header_t;
#define GET_SDS_HEADER_ID(_header) \
((((struct_header_t *)(_header))->reg[0]) & SDS_HEADER_ID_MASK)
#define GET_SDS_HEADER_VERSION(_header) \
(((((struct_header_t *)(_header))->reg[0]) >> SDS_HEADER_MINOR_VERSION_SHIFT)\
& SDS_HEADER_VERSION_MASK)
#define GET_SDS_HEADER_STRUCT_SIZE(_header) \
(((((struct_header_t *)(_header))->reg[1]) >> SDS_HEADER_STRUCT_SIZE_SHIFT)\
& SDS_HEADER_STRUCT_SIZE_MASK)
#define IS_SDS_HEADER_VALID(_header) \
((((struct_header_t *)(_header))->reg[1]) & SDS_HEADER_VALID_MASK)
#define GET_SDS_STRUCT_FIELD(_header, _field_offset) \
((((uint8_t *)(_header)) + sizeof(struct_header_t)) + (_field_offset))
/* Region Descriptor describing the SDS Memory Region */
typedef struct region_descriptor {
uint32_t reg[2];
} region_desc_t;
#define IS_SDS_REGION_VALID(region) \
(((((region_desc_t *)(region))->reg[0]) & SDS_REGION_SIGNATURE_MASK) == SDS_REGION_SIGNATURE)
#define GET_SDS_REGION_STRUCTURE_COUNT(region) \
(((((region_desc_t *)(region))->reg[0]) >> SDS_REGION_STRUCT_COUNT_SHIFT)\
& SDS_REGION_STRUCT_COUNT_MASK)
#define GET_SDS_REGION_SCHEMA_VERSION(region) \
(((((region_desc_t *)(region))->reg[0]) >> SDS_REGION_SCH_MINOR_SHIFT)\
& SDS_REGION_SCH_VERSION_MASK)
#define GET_SDS_REGION_SIZE(region) ((((region_desc_t *)(region))->reg[1]))
#endif /* __ASSEMBLER__ */
#endif /* SDS_PRIVATE_H */
@@ -0,0 +1,152 @@
/*
* Copyright (c) 2015-2021, Xilinx Inc.
* Written by Michal Simek.
*
* SPDX-License-Identifier: BSD-3-Clause
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* Neither the name of ARM nor the names of its contributors may be used
* to endorse or promote products derived from this software without specific
* prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
#include <errno.h>
#include <stddef.h>
#include <arch_helpers.h>
#include <drivers/arm/dcc.h>
#include <drivers/console.h>
#include <drivers/delay_timer.h>
#include <lib/mmio.h>
/* DCC Status Bits */
#define DCC_STATUS_RX BIT(30)
#define DCC_STATUS_TX BIT(29)
#define TIMEOUT_COUNT_US U(0x10624)
struct dcc_console {
struct console console;
};
static inline uint32_t __dcc_getstatus(void)
{
return read_mdccsr_el0();
}
static inline char __dcc_getchar(void)
{
char c;
c = read_dbgdtrrx_el0();
return c;
}
static inline void __dcc_putchar(char c)
{
/*
* The typecast is to make absolutely certain that 'c' is
* zero-extended.
*/
write_dbgdtrtx_el0((unsigned char)c);
}
static int32_t dcc_status_timeout(uint32_t mask)
{
const unsigned int timeout_count = TIMEOUT_COUNT_US;
uint64_t timeout;
unsigned int status;
timeout = timeout_init_us(timeout_count);
do {
status = (__dcc_getstatus() & mask);
if (timeout_elapsed(timeout)) {
return -ETIMEDOUT;
}
} while ((status != 0U));
return 0;
}
static int32_t dcc_console_putc(int32_t ch, struct console *console)
{
unsigned int status;
status = dcc_status_timeout(DCC_STATUS_TX);
if (status != 0U) {
return status;
}
__dcc_putchar(ch);
return ch;
}
static int32_t dcc_console_getc(struct console *console)
{
unsigned int status;
status = dcc_status_timeout(DCC_STATUS_RX);
if (status != 0U) {
return status;
}
return __dcc_getchar();
}
int32_t dcc_console_init(unsigned long base_addr, uint32_t uart_clk,
uint32_t baud_rate)
{
return 0; /* No init needed */
}
/**
* dcc_console_flush() - Function to force a write of all buffered data
* that hasn't been output.
* @console Console struct
*
*/
static void dcc_console_flush(struct console *console)
{
unsigned int status;
status = dcc_status_timeout(DCC_STATUS_TX);
if (status != 0U) {
return;
}
}
static struct dcc_console dcc_console = {
.console = {
.flags = CONSOLE_FLAG_BOOT |
CONSOLE_FLAG_RUNTIME,
.putc = dcc_console_putc,
.getc = dcc_console_getc,
.flush = dcc_console_flush,
},
};
int console_dcc_register(void)
{
return console_register(&dcc_console.console);
}
@@ -0,0 +1,229 @@
/*
* Copyright (c) 2021-2022, Arm Limited. All rights reserved.
*
* SPDX-License-Identifier: BSD-3-Clause
*/
#include <stdint.h>
#include <stdbool.h>
#include <common/debug.h>
#include <common/runtime_svc.h>
#include <drivers/arm/ethosn.h>
#include <drivers/delay_timer.h>
#include <lib/mmio.h>
#include <lib/utils_def.h>
#include <plat/arm/common/fconf_ethosn_getter.h>
/*
* Number of Arm(R) Ethos(TM)-N NPU (NPU) devices available
*/
#define ETHOSN_NUM_DEVICES \
FCONF_GET_PROPERTY(hw_config, ethosn_config, num_devices)
#define ETHOSN_GET_DEVICE(dev_idx) \
FCONF_GET_PROPERTY(hw_config, ethosn_device, dev_idx)
/* NPU core sec registry address */
#define ETHOSN_CORE_SEC_REG(core_addr, reg_offset) \
(core_addr + reg_offset)
/* Reset timeout in us */
#define ETHOSN_RESET_TIMEOUT_US U(10 * 1000 * 1000)
#define ETHOSN_RESET_WAIT_US U(1)
#define SEC_DEL_REG U(0x0004)
#define SEC_DEL_VAL U(0x81C)
#define SEC_DEL_EXCC_MASK U(0x20)
#define SEC_SECCTLR_REG U(0x0010)
#define SEC_SECCTLR_VAL U(0x3)
#define SEC_DEL_ADDR_EXT_REG U(0x201C)
#define SEC_DEL_ADDR_EXT_VAL U(0x15)
#define SEC_SYSCTRL0_REG U(0x0018)
#define SEC_SYSCTRL0_SOFT_RESET U(3U << 29)
#define SEC_SYSCTRL0_HARD_RESET U(1U << 31)
#define SEC_MMUSID_REG_BASE U(0x3008)
#define SEC_MMUSID_OFFSET U(0x1000)
static bool ethosn_get_device_and_core(uintptr_t core_addr,
const struct ethosn_device_t **dev_match,
const struct ethosn_core_t **core_match)
{
uint32_t dev_idx;
uint32_t core_idx;
for (dev_idx = 0U; dev_idx < ETHOSN_NUM_DEVICES; ++dev_idx) {
const struct ethosn_device_t *dev = ETHOSN_GET_DEVICE(dev_idx);
for (core_idx = 0U; core_idx < dev->num_cores; ++core_idx) {
const struct ethosn_core_t *core = &(dev->cores[core_idx]);
if (core->addr == core_addr) {
*dev_match = dev;
*core_match = core;
return true;
}
}
}
WARN("ETHOSN: Unknown core address given to SMC call.\n");
return false;
}
static void ethosn_configure_smmu_streams(const struct ethosn_device_t *device,
const struct ethosn_core_t *core,
uint32_t asset_alloc_idx)
{
const struct ethosn_main_allocator_t *main_alloc =
&(core->main_allocator);
const struct ethosn_asset_allocator_t *asset_alloc =
&(device->asset_allocators[asset_alloc_idx]);
const uint32_t streams[9] = {
main_alloc->firmware.stream_id,
main_alloc->working_data.stream_id,
asset_alloc->command_stream.stream_id,
0U, /* Not used*/
main_alloc->firmware.stream_id,
asset_alloc->weight_data.stream_id,
asset_alloc->buffer_data.stream_id,
asset_alloc->intermediate_data.stream_id,
asset_alloc->buffer_data.stream_id
};
size_t i;
for (i = 0U; i < ARRAY_SIZE(streams); ++i) {
const uintptr_t reg_addr = SEC_MMUSID_REG_BASE +
(SEC_MMUSID_OFFSET * i);
mmio_write_32(ETHOSN_CORE_SEC_REG(core->addr, reg_addr),
streams[i]);
}
}
static void ethosn_delegate_to_ns(uintptr_t core_addr)
{
mmio_setbits_32(ETHOSN_CORE_SEC_REG(core_addr, SEC_SECCTLR_REG),
SEC_SECCTLR_VAL);
mmio_setbits_32(ETHOSN_CORE_SEC_REG(core_addr, SEC_DEL_REG),
SEC_DEL_VAL);
mmio_setbits_32(ETHOSN_CORE_SEC_REG(core_addr, SEC_DEL_ADDR_EXT_REG),
SEC_DEL_ADDR_EXT_VAL);
}
static int ethosn_is_sec(uintptr_t core_addr)
{
if ((mmio_read_32(ETHOSN_CORE_SEC_REG(core_addr, SEC_DEL_REG))
& SEC_DEL_EXCC_MASK) != 0U) {
return 0;
}
return 1;
}
static bool ethosn_reset(uintptr_t core_addr, int hard_reset)
{
unsigned int timeout;
const uintptr_t sysctrl0_reg =
ETHOSN_CORE_SEC_REG(core_addr, SEC_SYSCTRL0_REG);
const uint32_t reset_val = (hard_reset != 0) ? SEC_SYSCTRL0_HARD_RESET
: SEC_SYSCTRL0_SOFT_RESET;
mmio_write_32(sysctrl0_reg, reset_val);
/* Wait for reset to complete */
for (timeout = 0U; timeout < ETHOSN_RESET_TIMEOUT_US;
timeout += ETHOSN_RESET_WAIT_US) {
if ((mmio_read_32(sysctrl0_reg) & reset_val) == 0U) {
break;
}
udelay(ETHOSN_RESET_WAIT_US);
}
return timeout < ETHOSN_RESET_TIMEOUT_US;
}
uintptr_t ethosn_smc_handler(uint32_t smc_fid,
u_register_t core_addr,
u_register_t asset_alloc_idx,
u_register_t x3,
u_register_t x4,
void *cookie,
void *handle,
u_register_t flags)
{
int hard_reset = 0;
const struct ethosn_device_t *device = NULL;
const struct ethosn_core_t *core = NULL;
const uint32_t fid = smc_fid & FUNCID_NUM_MASK;
/* Only SiP fast calls are expected */
if ((GET_SMC_TYPE(smc_fid) != SMC_TYPE_FAST) ||
(GET_SMC_OEN(smc_fid) != OEN_SIP_START)) {
SMC_RET1(handle, SMC_UNK);
}
/* Truncate parameters to 32-bits for SMC32 */
if (GET_SMC_CC(smc_fid) == SMC_32) {
core_addr &= 0xFFFFFFFF;
asset_alloc_idx &= 0xFFFFFFFF;
x3 &= 0xFFFFFFFF;
x4 &= 0xFFFFFFFF;
}
if (!is_ethosn_fid(smc_fid) ||
(fid < ETHOSN_FNUM_VERSION || fid > ETHOSN_FNUM_SOFT_RESET)) {
WARN("ETHOSN: Unknown SMC call: 0x%x\n", smc_fid);
SMC_RET1(handle, SMC_UNK);
}
/* Commands that do not require a valid core address */
switch (fid) {
case ETHOSN_FNUM_VERSION:
SMC_RET2(handle, ETHOSN_VERSION_MAJOR, ETHOSN_VERSION_MINOR);
}
if (!ethosn_get_device_and_core(core_addr, &device, &core)) {
SMC_RET1(handle, ETHOSN_UNKNOWN_CORE_ADDRESS);
}
/* Commands that require a valid core address */
switch (fid) {
case ETHOSN_FNUM_IS_SEC:
SMC_RET1(handle, ethosn_is_sec(core->addr));
}
if (!device->has_reserved_memory &&
asset_alloc_idx >= device->num_allocators) {
WARN("ETHOSN: Unknown asset allocator index given to SMC call.\n");
SMC_RET1(handle, ETHOSN_UNKNOWN_ALLOCATOR_IDX);
}
/* Commands that require a valid device, core and asset allocator */
switch (fid) {
case ETHOSN_FNUM_HARD_RESET:
hard_reset = 1;
/* Fallthrough */
case ETHOSN_FNUM_SOFT_RESET:
if (!ethosn_reset(core->addr, hard_reset)) {
SMC_RET1(handle, ETHOSN_FAILURE);
}
if (!device->has_reserved_memory) {
ethosn_configure_smmu_streams(device, core,
asset_alloc_idx);
}
ethosn_delegate_to_ns(core->addr);
SMC_RET1(handle, ETHOSN_SUCCESS);
default:
WARN("ETHOSN: Unimplemented SMC call: 0x%x\n", fid);
SMC_RET1(handle, SMC_UNK);
}
}
@@ -0,0 +1,78 @@
/*
* Copyright (c) 2013-2018, ARM Limited and Contributors. All rights reserved.
*
* SPDX-License-Identifier: BSD-3-Clause
*/
#include <drivers/arm/fvp/fvp_pwrc.h>
#include <lib/bakery_lock.h>
#include <lib/mmio.h>
#include <plat/arm/common/plat_arm.h>
#include <platform_def.h>
/*
* TODO: Someday there will be a generic power controller api. At the moment
* each platform has its own pwrc so just exporting functions is fine.
*/
ARM_INSTANTIATE_LOCK;
unsigned int fvp_pwrc_get_cpu_wkr(u_register_t mpidr)
{
return PSYSR_WK(fvp_pwrc_read_psysr(mpidr));
}
unsigned int fvp_pwrc_read_psysr(u_register_t mpidr)
{
unsigned int rc;
arm_lock_get();
mmio_write_32(PWRC_BASE + PSYSR_OFF, (unsigned int) mpidr);
rc = mmio_read_32(PWRC_BASE + PSYSR_OFF);
arm_lock_release();
return rc;
}
void fvp_pwrc_write_pponr(u_register_t mpidr)
{
arm_lock_get();
mmio_write_32(PWRC_BASE + PPONR_OFF, (unsigned int) mpidr);
arm_lock_release();
}
void fvp_pwrc_write_ppoffr(u_register_t mpidr)
{
arm_lock_get();
mmio_write_32(PWRC_BASE + PPOFFR_OFF, (unsigned int) mpidr);
arm_lock_release();
}
void fvp_pwrc_set_wen(u_register_t mpidr)
{
arm_lock_get();
mmio_write_32(PWRC_BASE + PWKUPR_OFF,
(unsigned int) (PWKUPR_WEN | mpidr));
arm_lock_release();
}
void fvp_pwrc_clr_wen(u_register_t mpidr)
{
arm_lock_get();
mmio_write_32(PWRC_BASE + PWKUPR_OFF,
(unsigned int) mpidr);
arm_lock_release();
}
void fvp_pwrc_write_pcoffr(u_register_t mpidr)
{
arm_lock_get();
mmio_write_32(PWRC_BASE + PCOFFR_OFF, (unsigned int) mpidr);
arm_lock_release();
}
/* Nothing else to do here apart from initializing the lock */
void __init plat_arm_pwrc_setup(void)
{
arm_lock_init();
}
@@ -0,0 +1,342 @@
/*
* Copyright (c) 2015-2020, ARM Limited and Contributors. All rights reserved.
*
* SPDX-License-Identifier: BSD-3-Clause
*/
#pragma message __FILE__ " is deprecated, use gicv2.mk instead"
#include <assert.h>
#include <drivers/arm/gic_common.h>
#include <lib/mmio.h>
#include "gic_common_private.h"
/*******************************************************************************
* GIC Distributor interface accessors for reading entire registers
******************************************************************************/
/*
* Accessor to read the GIC Distributor IGROUPR corresponding to the interrupt
* `id`, 32 interrupt ids at a time.
*/
unsigned int gicd_read_igroupr(uintptr_t base, unsigned int id)
{
unsigned int n = id >> IGROUPR_SHIFT;
return mmio_read_32(base + GICD_IGROUPR + (n << 2));
}
/*
* Accessor to read the GIC Distributor ISENABLER corresponding to the
* interrupt `id`, 32 interrupt ids at a time.
*/
unsigned int gicd_read_isenabler(uintptr_t base, unsigned int id)
{
unsigned int n = id >> ISENABLER_SHIFT;
return mmio_read_32(base + GICD_ISENABLER + (n << 2));
}
/*
* Accessor to read the GIC Distributor ICENABLER corresponding to the
* interrupt `id`, 32 interrupt IDs at a time.
*/
unsigned int gicd_read_icenabler(uintptr_t base, unsigned int id)
{
unsigned int n = id >> ICENABLER_SHIFT;
return mmio_read_32(base + GICD_ICENABLER + (n << 2));
}
/*
* Accessor to read the GIC Distributor ISPENDR corresponding to the
* interrupt `id`, 32 interrupt IDs at a time.
*/
unsigned int gicd_read_ispendr(uintptr_t base, unsigned int id)
{
unsigned int n = id >> ISPENDR_SHIFT;
return mmio_read_32(base + GICD_ISPENDR + (n << 2));
}
/*
* Accessor to read the GIC Distributor ICPENDR corresponding to the
* interrupt `id`, 32 interrupt IDs at a time.
*/
unsigned int gicd_read_icpendr(uintptr_t base, unsigned int id)
{
unsigned int n = id >> ICPENDR_SHIFT;
return mmio_read_32(base + GICD_ICPENDR + (n << 2));
}
/*
* Accessor to read the GIC Distributor ISACTIVER corresponding to the
* interrupt `id`, 32 interrupt IDs at a time.
*/
unsigned int gicd_read_isactiver(uintptr_t base, unsigned int id)
{
unsigned int n = id >> ISACTIVER_SHIFT;
return mmio_read_32(base + GICD_ISACTIVER + (n << 2));
}
/*
* Accessor to read the GIC Distributor ICACTIVER corresponding to the
* interrupt `id`, 32 interrupt IDs at a time.
*/
unsigned int gicd_read_icactiver(uintptr_t base, unsigned int id)
{
unsigned int n = id >> ICACTIVER_SHIFT;
return mmio_read_32(base + GICD_ICACTIVER + (n << 2));
}
/*
* Accessor to read the GIC Distributor IPRIORITYR corresponding to the
* interrupt `id`, 4 interrupt IDs at a time.
*/
unsigned int gicd_read_ipriorityr(uintptr_t base, unsigned int id)
{
unsigned int n = id >> IPRIORITYR_SHIFT;
return mmio_read_32(base + GICD_IPRIORITYR + (n << 2));
}
/*
* Accessor to read the GIC Distributor ICGFR corresponding to the
* interrupt `id`, 16 interrupt IDs at a time.
*/
unsigned int gicd_read_icfgr(uintptr_t base, unsigned int id)
{
unsigned int n = id >> ICFGR_SHIFT;
return mmio_read_32(base + GICD_ICFGR + (n << 2));
}
/*
* Accessor to read the GIC Distributor NSACR corresponding to the
* interrupt `id`, 16 interrupt IDs at a time.
*/
unsigned int gicd_read_nsacr(uintptr_t base, unsigned int id)
{
unsigned int n = id >> NSACR_SHIFT;
return mmio_read_32(base + GICD_NSACR + (n << 2));
}
/*******************************************************************************
* GIC Distributor interface accessors for writing entire registers
******************************************************************************/
/*
* Accessor to write the GIC Distributor IGROUPR corresponding to the
* interrupt `id`, 32 interrupt IDs at a time.
*/
void gicd_write_igroupr(uintptr_t base, unsigned int id, unsigned int val)
{
unsigned int n = id >> IGROUPR_SHIFT;
mmio_write_32(base + GICD_IGROUPR + (n << 2), val);
}
/*
* Accessor to write the GIC Distributor ISENABLER corresponding to the
* interrupt `id`, 32 interrupt IDs at a time.
*/
void gicd_write_isenabler(uintptr_t base, unsigned int id, unsigned int val)
{
unsigned int n = id >> ISENABLER_SHIFT;
mmio_write_32(base + GICD_ISENABLER + (n << 2), val);
}
/*
* Accessor to write the GIC Distributor ICENABLER corresponding to the
* interrupt `id`, 32 interrupt IDs at a time.
*/
void gicd_write_icenabler(uintptr_t base, unsigned int id, unsigned int val)
{
unsigned int n = id >> ICENABLER_SHIFT;
mmio_write_32(base + GICD_ICENABLER + (n << 2), val);
}
/*
* Accessor to write the GIC Distributor ISPENDR corresponding to the
* interrupt `id`, 32 interrupt IDs at a time.
*/
void gicd_write_ispendr(uintptr_t base, unsigned int id, unsigned int val)
{
unsigned int n = id >> ISPENDR_SHIFT;
mmio_write_32(base + GICD_ISPENDR + (n << 2), val);
}
/*
* Accessor to write the GIC Distributor ICPENDR corresponding to the
* interrupt `id`, 32 interrupt IDs at a time.
*/
void gicd_write_icpendr(uintptr_t base, unsigned int id, unsigned int val)
{
unsigned int n = id >> ICPENDR_SHIFT;
mmio_write_32(base + GICD_ICPENDR + (n << 2), val);
}
/*
* Accessor to write the GIC Distributor ISACTIVER corresponding to the
* interrupt `id`, 32 interrupt IDs at a time.
*/
void gicd_write_isactiver(uintptr_t base, unsigned int id, unsigned int val)
{
unsigned int n = id >> ISACTIVER_SHIFT;
mmio_write_32(base + GICD_ISACTIVER + (n << 2), val);
}
/*
* Accessor to write the GIC Distributor ICACTIVER corresponding to the
* interrupt `id`, 32 interrupt IDs at a time.
*/
void gicd_write_icactiver(uintptr_t base, unsigned int id, unsigned int val)
{
unsigned int n = id >> ICACTIVER_SHIFT;
mmio_write_32(base + GICD_ICACTIVER + (n << 2), val);
}
/*
* Accessor to write the GIC Distributor IPRIORITYR corresponding to the
* interrupt `id`, 4 interrupt IDs at a time.
*/
void gicd_write_ipriorityr(uintptr_t base, unsigned int id, unsigned int val)
{
unsigned int n = id >> IPRIORITYR_SHIFT;
mmio_write_32(base + GICD_IPRIORITYR + (n << 2), val);
}
/*
* Accessor to write the GIC Distributor ICFGR corresponding to the
* interrupt `id`, 16 interrupt IDs at a time.
*/
void gicd_write_icfgr(uintptr_t base, unsigned int id, unsigned int val)
{
unsigned int n = id >> ICFGR_SHIFT;
mmio_write_32(base + GICD_ICFGR + (n << 2), val);
}
/*
* Accessor to write the GIC Distributor NSACR corresponding to the
* interrupt `id`, 16 interrupt IDs at a time.
*/
void gicd_write_nsacr(uintptr_t base, unsigned int id, unsigned int val)
{
unsigned int n = id >> NSACR_SHIFT;
mmio_write_32(base + GICD_NSACR + (n << 2), val);
}
/*******************************************************************************
* GIC Distributor functions for accessing the GIC registers
* corresponding to a single interrupt ID. These functions use bitwise
* operations or appropriate register accesses to modify or return
* the bit-field corresponding the single interrupt ID.
******************************************************************************/
unsigned int gicd_get_igroupr(uintptr_t base, unsigned int id)
{
unsigned int bit_num = id & ((1U << IGROUPR_SHIFT) - 1U);
unsigned int reg_val = gicd_read_igroupr(base, id);
return (reg_val >> bit_num) & 0x1U;
}
void gicd_set_igroupr(uintptr_t base, unsigned int id)
{
unsigned int bit_num = id & ((1U << IGROUPR_SHIFT) - 1U);
unsigned int reg_val = gicd_read_igroupr(base, id);
gicd_write_igroupr(base, id, reg_val | (1U << bit_num));
}
void gicd_clr_igroupr(uintptr_t base, unsigned int id)
{
unsigned int bit_num = id & ((1U << IGROUPR_SHIFT) - 1U);
unsigned int reg_val = gicd_read_igroupr(base, id);
gicd_write_igroupr(base, id, reg_val & ~(1U << bit_num));
}
void gicd_set_isenabler(uintptr_t base, unsigned int id)
{
unsigned int bit_num = id & ((1U << ISENABLER_SHIFT) - 1U);
gicd_write_isenabler(base, id, (1U << bit_num));
}
void gicd_set_icenabler(uintptr_t base, unsigned int id)
{
unsigned int bit_num = id & ((1U << ICENABLER_SHIFT) - 1U);
gicd_write_icenabler(base, id, (1U << bit_num));
}
void gicd_set_ispendr(uintptr_t base, unsigned int id)
{
unsigned int bit_num = id & ((1U << ISPENDR_SHIFT) - 1U);
gicd_write_ispendr(base, id, (1U << bit_num));
}
void gicd_set_icpendr(uintptr_t base, unsigned int id)
{
unsigned int bit_num = id & ((1U << ICPENDR_SHIFT) - 1U);
gicd_write_icpendr(base, id, (1U << bit_num));
}
unsigned int gicd_get_isactiver(uintptr_t base, unsigned int id)
{
unsigned int bit_num = id & ((1U << ISACTIVER_SHIFT) - 1U);
unsigned int reg_val = gicd_read_isactiver(base, id);
return (reg_val >> bit_num) & 0x1U;
}
void gicd_set_isactiver(uintptr_t base, unsigned int id)
{
unsigned int bit_num = id & ((1U << ISACTIVER_SHIFT) - 1U);
gicd_write_isactiver(base, id, (1U << bit_num));
}
void gicd_set_icactiver(uintptr_t base, unsigned int id)
{
unsigned int bit_num = id & ((1U << ICACTIVER_SHIFT) - 1U);
gicd_write_icactiver(base, id, (1U << bit_num));
}
void gicd_set_ipriorityr(uintptr_t base, unsigned int id, unsigned int pri)
{
uint8_t val = pri & GIC_PRI_MASK;
mmio_write_8(base + GICD_IPRIORITYR + id, val);
}
void gicd_set_icfgr(uintptr_t base, unsigned int id, unsigned int cfg)
{
/* Interrupt configuration is a 2-bit field */
unsigned int bit_num = id & ((1U << ICFGR_SHIFT) - 1U);
unsigned int bit_shift = bit_num << 1;
uint32_t reg_val = gicd_read_icfgr(base, id);
/* Clear the field, and insert required configuration */
reg_val &= ~(GIC_CFG_MASK << bit_shift);
reg_val |= ((cfg & GIC_CFG_MASK) << bit_shift);
gicd_write_icfgr(base, id, reg_val);
}
@@ -0,0 +1,89 @@
/*
* Copyright (c) 2016-2018, ARM Limited and Contributors. All rights reserved.
*
* SPDX-License-Identifier: BSD-3-Clause
*/
#ifndef GIC_COMMON_PRIVATE_H
#define GIC_COMMON_PRIVATE_H
#include <stdint.h>
#include <drivers/arm/gic_common.h>
#include <lib/mmio.h>
/*******************************************************************************
* GIC Distributor interface register accessors that are common to GICv3 & GICv2
******************************************************************************/
static inline unsigned int gicd_read_ctlr(uintptr_t base)
{
return mmio_read_32(base + GICD_CTLR);
}
static inline unsigned int gicd_read_typer(uintptr_t base)
{
return mmio_read_32(base + GICD_TYPER);
}
static inline unsigned int gicd_read_iidr(uintptr_t base)
{
return mmio_read_32(base + GICD_IIDR);
}
static inline void gicd_write_ctlr(uintptr_t base, unsigned int val)
{
mmio_write_32(base + GICD_CTLR, val);
}
/*******************************************************************************
* GIC Distributor function prototypes for accessing entire registers.
* Note: The raw register values correspond to multiple interrupt IDs and
* the number of interrupt IDs involved depends on the register accessed.
******************************************************************************/
unsigned int gicd_read_igroupr(uintptr_t base, unsigned int id);
unsigned int gicd_read_isenabler(uintptr_t base, unsigned int id);
unsigned int gicd_read_icenabler(uintptr_t base, unsigned int id);
unsigned int gicd_read_ispendr(uintptr_t base, unsigned int id);
unsigned int gicd_read_icpendr(uintptr_t base, unsigned int id);
unsigned int gicd_read_isactiver(uintptr_t base, unsigned int id);
unsigned int gicd_read_icactiver(uintptr_t base, unsigned int id);
unsigned int gicd_read_ipriorityr(uintptr_t base, unsigned int id);
unsigned int gicd_read_icfgr(uintptr_t base, unsigned int id);
unsigned int gicd_read_nsacr(uintptr_t base, unsigned int id);
unsigned int gicd_read_spendsgir(uintptr_t base, unsigned int id);
unsigned int gicd_read_cpendsgir(uintptr_t base, unsigned int id);
unsigned int gicd_read_itargetsr(uintptr_t base, unsigned int id);
void gicd_write_igroupr(uintptr_t base, unsigned int id, unsigned int val);
void gicd_write_isenabler(uintptr_t base, unsigned int id, unsigned int val);
void gicd_write_icenabler(uintptr_t base, unsigned int id, unsigned int val);
void gicd_write_ispendr(uintptr_t base, unsigned int id, unsigned int val);
void gicd_write_icpendr(uintptr_t base, unsigned int id, unsigned int val);
void gicd_write_isactiver(uintptr_t base, unsigned int id, unsigned int val);
void gicd_write_icactiver(uintptr_t base, unsigned int id, unsigned int val);
void gicd_write_ipriorityr(uintptr_t base, unsigned int id, unsigned int val);
void gicd_write_icfgr(uintptr_t base, unsigned int id, unsigned int val);
void gicd_write_nsacr(uintptr_t base, unsigned int id, unsigned int val);
void gicd_write_spendsgir(uintptr_t base, unsigned int id, unsigned int val);
void gicd_write_cpendsgir(uintptr_t base, unsigned int id, unsigned int val);
void gicd_write_itargetsr(uintptr_t base, unsigned int id, unsigned int val);
/*******************************************************************************
* GIC Distributor function prototypes for accessing the GIC registers
* corresponding to a single interrupt ID. These functions use bitwise
* operations or appropriate register accesses to modify or return
* the bit-field corresponding the single interrupt ID.
******************************************************************************/
unsigned int gicd_get_igroupr(uintptr_t base, unsigned int id);
void gicd_set_igroupr(uintptr_t base, unsigned int id);
void gicd_clr_igroupr(uintptr_t base, unsigned int id);
void gicd_set_isenabler(uintptr_t base, unsigned int id);
void gicd_set_icenabler(uintptr_t base, unsigned int id);
void gicd_set_ispendr(uintptr_t base, unsigned int id);
void gicd_set_icpendr(uintptr_t base, unsigned int id);
unsigned int gicd_get_isactiver(uintptr_t base, unsigned int id);
void gicd_set_isactiver(uintptr_t base, unsigned int id);
void gicd_set_icactiver(uintptr_t base, unsigned int id);
void gicd_set_ipriorityr(uintptr_t base, unsigned int id, unsigned int pri);
void gicd_set_icfgr(uintptr_t base, unsigned int id, unsigned int cfg);
#endif /* GIC_COMMON_PRIVATE_H */
@@ -0,0 +1,340 @@
/*
* Copyright (c) 2015-2020, ARM Limited and Contributors. All rights reserved.
*
* SPDX-License-Identifier: BSD-3-Clause
*/
#include <assert.h>
#include <drivers/arm/gic_common.h>
#include <lib/mmio.h>
#include "../common/gic_common_private.h"
/*******************************************************************************
* GIC Distributor interface accessors for reading entire registers
******************************************************************************/
/*
* Accessor to read the GIC Distributor IGROUPR corresponding to the interrupt
* `id`, 32 interrupt ids at a time.
*/
unsigned int gicd_read_igroupr(uintptr_t base, unsigned int id)
{
unsigned int n = id >> IGROUPR_SHIFT;
return mmio_read_32(base + GICD_IGROUPR + (n << 2));
}
/*
* Accessor to read the GIC Distributor ISENABLER corresponding to the
* interrupt `id`, 32 interrupt ids at a time.
*/
unsigned int gicd_read_isenabler(uintptr_t base, unsigned int id)
{
unsigned int n = id >> ISENABLER_SHIFT;
return mmio_read_32(base + GICD_ISENABLER + (n << 2));
}
/*
* Accessor to read the GIC Distributor ICENABLER corresponding to the
* interrupt `id`, 32 interrupt IDs at a time.
*/
unsigned int gicd_read_icenabler(uintptr_t base, unsigned int id)
{
unsigned int n = id >> ICENABLER_SHIFT;
return mmio_read_32(base + GICD_ICENABLER + (n << 2));
}
/*
* Accessor to read the GIC Distributor ISPENDR corresponding to the
* interrupt `id`, 32 interrupt IDs at a time.
*/
unsigned int gicd_read_ispendr(uintptr_t base, unsigned int id)
{
unsigned int n = id >> ISPENDR_SHIFT;
return mmio_read_32(base + GICD_ISPENDR + (n << 2));
}
/*
* Accessor to read the GIC Distributor ICPENDR corresponding to the
* interrupt `id`, 32 interrupt IDs at a time.
*/
unsigned int gicd_read_icpendr(uintptr_t base, unsigned int id)
{
unsigned int n = id >> ICPENDR_SHIFT;
return mmio_read_32(base + GICD_ICPENDR + (n << 2));
}
/*
* Accessor to read the GIC Distributor ISACTIVER corresponding to the
* interrupt `id`, 32 interrupt IDs at a time.
*/
unsigned int gicd_read_isactiver(uintptr_t base, unsigned int id)
{
unsigned int n = id >> ISACTIVER_SHIFT;
return mmio_read_32(base + GICD_ISACTIVER + (n << 2));
}
/*
* Accessor to read the GIC Distributor ICACTIVER corresponding to the
* interrupt `id`, 32 interrupt IDs at a time.
*/
unsigned int gicd_read_icactiver(uintptr_t base, unsigned int id)
{
unsigned int n = id >> ICACTIVER_SHIFT;
return mmio_read_32(base + GICD_ICACTIVER + (n << 2));
}
/*
* Accessor to read the GIC Distributor IPRIORITYR corresponding to the
* interrupt `id`, 4 interrupt IDs at a time.
*/
unsigned int gicd_read_ipriorityr(uintptr_t base, unsigned int id)
{
unsigned int n = id >> IPRIORITYR_SHIFT;
return mmio_read_32(base + GICD_IPRIORITYR + (n << 2));
}
/*
* Accessor to read the GIC Distributor ICGFR corresponding to the
* interrupt `id`, 16 interrupt IDs at a time.
*/
unsigned int gicd_read_icfgr(uintptr_t base, unsigned int id)
{
unsigned int n = id >> ICFGR_SHIFT;
return mmio_read_32(base + GICD_ICFGR + (n << 2));
}
/*
* Accessor to read the GIC Distributor NSACR corresponding to the
* interrupt `id`, 16 interrupt IDs at a time.
*/
unsigned int gicd_read_nsacr(uintptr_t base, unsigned int id)
{
unsigned int n = id >> NSACR_SHIFT;
return mmio_read_32(base + GICD_NSACR + (n << 2));
}
/*******************************************************************************
* GIC Distributor interface accessors for writing entire registers
******************************************************************************/
/*
* Accessor to write the GIC Distributor IGROUPR corresponding to the
* interrupt `id`, 32 interrupt IDs at a time.
*/
void gicd_write_igroupr(uintptr_t base, unsigned int id, unsigned int val)
{
unsigned int n = id >> IGROUPR_SHIFT;
mmio_write_32(base + GICD_IGROUPR + (n << 2), val);
}
/*
* Accessor to write the GIC Distributor ISENABLER corresponding to the
* interrupt `id`, 32 interrupt IDs at a time.
*/
void gicd_write_isenabler(uintptr_t base, unsigned int id, unsigned int val)
{
unsigned int n = id >> ISENABLER_SHIFT;
mmio_write_32(base + GICD_ISENABLER + (n << 2), val);
}
/*
* Accessor to write the GIC Distributor ICENABLER corresponding to the
* interrupt `id`, 32 interrupt IDs at a time.
*/
void gicd_write_icenabler(uintptr_t base, unsigned int id, unsigned int val)
{
unsigned int n = id >> ICENABLER_SHIFT;
mmio_write_32(base + GICD_ICENABLER + (n << 2), val);
}
/*
* Accessor to write the GIC Distributor ISPENDR corresponding to the
* interrupt `id`, 32 interrupt IDs at a time.
*/
void gicd_write_ispendr(uintptr_t base, unsigned int id, unsigned int val)
{
unsigned int n = id >> ISPENDR_SHIFT;
mmio_write_32(base + GICD_ISPENDR + (n << 2), val);
}
/*
* Accessor to write the GIC Distributor ICPENDR corresponding to the
* interrupt `id`, 32 interrupt IDs at a time.
*/
void gicd_write_icpendr(uintptr_t base, unsigned int id, unsigned int val)
{
unsigned int n = id >> ICPENDR_SHIFT;
mmio_write_32(base + GICD_ICPENDR + (n << 2), val);
}
/*
* Accessor to write the GIC Distributor ISACTIVER corresponding to the
* interrupt `id`, 32 interrupt IDs at a time.
*/
void gicd_write_isactiver(uintptr_t base, unsigned int id, unsigned int val)
{
unsigned int n = id >> ISACTIVER_SHIFT;
mmio_write_32(base + GICD_ISACTIVER + (n << 2), val);
}
/*
* Accessor to write the GIC Distributor ICACTIVER corresponding to the
* interrupt `id`, 32 interrupt IDs at a time.
*/
void gicd_write_icactiver(uintptr_t base, unsigned int id, unsigned int val)
{
unsigned int n = id >> ICACTIVER_SHIFT;
mmio_write_32(base + GICD_ICACTIVER + (n << 2), val);
}
/*
* Accessor to write the GIC Distributor IPRIORITYR corresponding to the
* interrupt `id`, 4 interrupt IDs at a time.
*/
void gicd_write_ipriorityr(uintptr_t base, unsigned int id, unsigned int val)
{
unsigned int n = id >> IPRIORITYR_SHIFT;
mmio_write_32(base + GICD_IPRIORITYR + (n << 2), val);
}
/*
* Accessor to write the GIC Distributor ICFGR corresponding to the
* interrupt `id`, 16 interrupt IDs at a time.
*/
void gicd_write_icfgr(uintptr_t base, unsigned int id, unsigned int val)
{
unsigned int n = id >> ICFGR_SHIFT;
mmio_write_32(base + GICD_ICFGR + (n << 2), val);
}
/*
* Accessor to write the GIC Distributor NSACR corresponding to the
* interrupt `id`, 16 interrupt IDs at a time.
*/
void gicd_write_nsacr(uintptr_t base, unsigned int id, unsigned int val)
{
unsigned int n = id >> NSACR_SHIFT;
mmio_write_32(base + GICD_NSACR + (n << 2), val);
}
/*******************************************************************************
* GIC Distributor functions for accessing the GIC registers
* corresponding to a single interrupt ID. These functions use bitwise
* operations or appropriate register accesses to modify or return
* the bit-field corresponding the single interrupt ID.
******************************************************************************/
unsigned int gicd_get_igroupr(uintptr_t base, unsigned int id)
{
unsigned int bit_num = id & ((1U << IGROUPR_SHIFT) - 1U);
unsigned int reg_val = gicd_read_igroupr(base, id);
return (reg_val >> bit_num) & 0x1U;
}
void gicd_set_igroupr(uintptr_t base, unsigned int id)
{
unsigned int bit_num = id & ((1U << IGROUPR_SHIFT) - 1U);
unsigned int reg_val = gicd_read_igroupr(base, id);
gicd_write_igroupr(base, id, reg_val | (1U << bit_num));
}
void gicd_clr_igroupr(uintptr_t base, unsigned int id)
{
unsigned int bit_num = id & ((1U << IGROUPR_SHIFT) - 1U);
unsigned int reg_val = gicd_read_igroupr(base, id);
gicd_write_igroupr(base, id, reg_val & ~(1U << bit_num));
}
void gicd_set_isenabler(uintptr_t base, unsigned int id)
{
unsigned int bit_num = id & ((1U << ISENABLER_SHIFT) - 1U);
gicd_write_isenabler(base, id, (1U << bit_num));
}
void gicd_set_icenabler(uintptr_t base, unsigned int id)
{
unsigned int bit_num = id & ((1U << ICENABLER_SHIFT) - 1U);
gicd_write_icenabler(base, id, (1U << bit_num));
}
void gicd_set_ispendr(uintptr_t base, unsigned int id)
{
unsigned int bit_num = id & ((1U << ISPENDR_SHIFT) - 1U);
gicd_write_ispendr(base, id, (1U << bit_num));
}
void gicd_set_icpendr(uintptr_t base, unsigned int id)
{
unsigned int bit_num = id & ((1U << ICPENDR_SHIFT) - 1U);
gicd_write_icpendr(base, id, (1U << bit_num));
}
unsigned int gicd_get_isactiver(uintptr_t base, unsigned int id)
{
unsigned int bit_num = id & ((1U << ISACTIVER_SHIFT) - 1U);
unsigned int reg_val = gicd_read_isactiver(base, id);
return (reg_val >> bit_num) & 0x1U;
}
void gicd_set_isactiver(uintptr_t base, unsigned int id)
{
unsigned int bit_num = id & ((1U << ISACTIVER_SHIFT) - 1U);
gicd_write_isactiver(base, id, (1U << bit_num));
}
void gicd_set_icactiver(uintptr_t base, unsigned int id)
{
unsigned int bit_num = id & ((1U << ICACTIVER_SHIFT) - 1U);
gicd_write_icactiver(base, id, (1U << bit_num));
}
void gicd_set_ipriorityr(uintptr_t base, unsigned int id, unsigned int pri)
{
uint8_t val = pri & GIC_PRI_MASK;
mmio_write_8(base + GICD_IPRIORITYR + id, val);
}
void gicd_set_icfgr(uintptr_t base, unsigned int id, unsigned int cfg)
{
/* Interrupt configuration is a 2-bit field */
unsigned int bit_num = id & ((1U << ICFGR_SHIFT) - 1U);
unsigned int bit_shift = bit_num << 1;
uint32_t reg_val = gicd_read_icfgr(base, id);
/* Clear the field, and insert required configuration */
reg_val &= ~(GIC_CFG_MASK << bit_shift);
reg_val |= ((cfg & GIC_CFG_MASK) << bit_shift);
gicd_write_icfgr(base, id, reg_val);
}
@@ -0,0 +1,15 @@
#
# Copyright (c) 2020, Arm Limited. All rights reserved.
#
# SPDX-License-Identifier: BSD-3-Clause
#
# No support for extended PPI and SPI range
GIC_EXT_INTID := 0
GICV2_SOURCES += drivers/arm/gic/v2/gicv2_main.c \
drivers/arm/gic/v2/gicv2_helpers.c \
drivers/arm/gic/v2/gicdv2_helpers.c
# Set GICv2 build option
$(eval $(call add_define,GIC_EXT_INTID))
@@ -0,0 +1,220 @@
/*
* Copyright (c) 2015-2020, ARM Limited and Contributors. All rights reserved.
*
* SPDX-License-Identifier: BSD-3-Clause
*/
#include <assert.h>
#include <arch.h>
#include <common/debug.h>
#include <common/interrupt_props.h>
#include <drivers/arm/gic_common.h>
#include <drivers/arm/gicv2.h>
#include "../common/gic_common_private.h"
#include "gicv2_private.h"
/*
* Accessor to read the GIC Distributor ITARGETSR corresponding to the
* interrupt `id`, 4 interrupt IDs at a time.
*/
unsigned int gicd_read_itargetsr(uintptr_t base, unsigned int id)
{
unsigned n = id >> ITARGETSR_SHIFT;
return mmio_read_32(base + GICD_ITARGETSR + (n << 2));
}
/*
* Accessor to read the GIC Distributor CPENDSGIR corresponding to the
* interrupt `id`, 4 interrupt IDs at a time.
*/
unsigned int gicd_read_cpendsgir(uintptr_t base, unsigned int id)
{
unsigned n = id >> CPENDSGIR_SHIFT;
return mmio_read_32(base + GICD_CPENDSGIR + (n << 2));
}
/*
* Accessor to read the GIC Distributor SPENDSGIR corresponding to the
* interrupt `id`, 4 interrupt IDs at a time.
*/
unsigned int gicd_read_spendsgir(uintptr_t base, unsigned int id)
{
unsigned n = id >> SPENDSGIR_SHIFT;
return mmio_read_32(base + GICD_SPENDSGIR + (n << 2));
}
/*
* Accessor to write the GIC Distributor ITARGETSR corresponding to the
* interrupt `id`, 4 interrupt IDs at a time.
*/
void gicd_write_itargetsr(uintptr_t base, unsigned int id, unsigned int val)
{
unsigned n = id >> ITARGETSR_SHIFT;
mmio_write_32(base + GICD_ITARGETSR + (n << 2), val);
}
/*
* Accessor to write the GIC Distributor CPENDSGIR corresponding to the
* interrupt `id`, 4 interrupt IDs at a time.
*/
void gicd_write_cpendsgir(uintptr_t base, unsigned int id, unsigned int val)
{
unsigned n = id >> CPENDSGIR_SHIFT;
mmio_write_32(base + GICD_CPENDSGIR + (n << 2), val);
}
/*
* Accessor to write the GIC Distributor SPENDSGIR corresponding to the
* interrupt `id`, 4 interrupt IDs at a time.
*/
void gicd_write_spendsgir(uintptr_t base, unsigned int id, unsigned int val)
{
unsigned n = id >> SPENDSGIR_SHIFT;
mmio_write_32(base + GICD_SPENDSGIR + (n << 2), val);
}
/*******************************************************************************
* Get the current CPU bit mask from GICD_ITARGETSR0
******************************************************************************/
unsigned int gicv2_get_cpuif_id(uintptr_t base)
{
unsigned int val;
val = gicd_read_itargetsr(base, 0);
return val & GIC_TARGET_CPU_MASK;
}
/*******************************************************************************
* Helper function to configure the default attributes of SPIs.
******************************************************************************/
void gicv2_spis_configure_defaults(uintptr_t gicd_base)
{
unsigned int index, num_ints;
num_ints = gicd_read_typer(gicd_base);
num_ints &= TYPER_IT_LINES_NO_MASK;
num_ints = (num_ints + 1U) << 5;
/*
* Treat all SPIs as G1NS by default. The number of interrupts is
* calculated as 32 * (IT_LINES + 1). We do 32 at a time.
*/
for (index = MIN_SPI_ID; index < num_ints; index += 32U)
gicd_write_igroupr(gicd_base, index, ~0U);
/* Setup the default SPI priorities doing four at a time */
for (index = MIN_SPI_ID; index < num_ints; index += 4U)
gicd_write_ipriorityr(gicd_base,
index,
GICD_IPRIORITYR_DEF_VAL);
/* Treat all SPIs as level triggered by default, 16 at a time */
for (index = MIN_SPI_ID; index < num_ints; index += 16U)
gicd_write_icfgr(gicd_base, index, 0U);
}
/*******************************************************************************
* Helper function to configure properties of secure G0 SPIs.
******************************************************************************/
void gicv2_secure_spis_configure_props(uintptr_t gicd_base,
const interrupt_prop_t *interrupt_props,
unsigned int interrupt_props_num)
{
unsigned int i;
const interrupt_prop_t *prop_desc;
/* Make sure there's a valid property array */
if (interrupt_props_num != 0U)
assert(interrupt_props != NULL);
for (i = 0; i < interrupt_props_num; i++) {
prop_desc = &interrupt_props[i];
if (prop_desc->intr_num < MIN_SPI_ID)
continue;
/* Configure this interrupt as a secure interrupt */
assert(prop_desc->intr_grp == GICV2_INTR_GROUP0);
gicd_clr_igroupr(gicd_base, prop_desc->intr_num);
/* Set the priority of this interrupt */
gicd_set_ipriorityr(gicd_base, prop_desc->intr_num,
prop_desc->intr_pri);
/* Target the secure interrupts to primary CPU */
gicd_set_itargetsr(gicd_base, prop_desc->intr_num,
gicv2_get_cpuif_id(gicd_base));
/* Set interrupt configuration */
gicd_set_icfgr(gicd_base, prop_desc->intr_num,
prop_desc->intr_cfg);
/* Enable this interrupt */
gicd_set_isenabler(gicd_base, prop_desc->intr_num);
}
}
/*******************************************************************************
* Helper function to configure properties of secure G0 SGIs and PPIs.
******************************************************************************/
void gicv2_secure_ppi_sgi_setup_props(uintptr_t gicd_base,
const interrupt_prop_t *interrupt_props,
unsigned int interrupt_props_num)
{
unsigned int i;
uint32_t sec_ppi_sgi_mask = 0;
const interrupt_prop_t *prop_desc;
/* Make sure there's a valid property array */
if (interrupt_props_num != 0U)
assert(interrupt_props != NULL);
/*
* Disable all SGIs (imp. def.)/PPIs before configuring them. This is a
* more scalable approach as it avoids clearing the enable bits in the
* GICD_CTLR.
*/
gicd_write_icenabler(gicd_base, 0U, ~0U);
/* Setup the default PPI/SGI priorities doing four at a time */
for (i = 0U; i < MIN_SPI_ID; i += 4U)
gicd_write_ipriorityr(gicd_base, i, GICD_IPRIORITYR_DEF_VAL);
for (i = 0U; i < interrupt_props_num; i++) {
prop_desc = &interrupt_props[i];
if (prop_desc->intr_num >= MIN_SPI_ID)
continue;
/* Configure this interrupt as a secure interrupt */
assert(prop_desc->intr_grp == GICV2_INTR_GROUP0);
/*
* Set interrupt configuration for PPIs. Configuration for SGIs
* are ignored.
*/
if ((prop_desc->intr_num >= MIN_PPI_ID) &&
(prop_desc->intr_num < MIN_SPI_ID)) {
gicd_set_icfgr(gicd_base, prop_desc->intr_num,
prop_desc->intr_cfg);
}
/* We have an SGI or a PPI. They are Group0 at reset */
sec_ppi_sgi_mask |= (1u << prop_desc->intr_num);
/* Set the priority of this interrupt */
gicd_set_ipriorityr(gicd_base, prop_desc->intr_num,
prop_desc->intr_pri);
}
/*
* Invert the bitmask to create a mask for non-secure PPIs and SGIs.
* Program the GICD_IGROUPR0 with this bit mask.
*/
gicd_write_igroupr(gicd_base, 0, ~sec_ppi_sgi_mask);
/* Enable the Group 0 SGIs and PPIs */
gicd_write_isenabler(gicd_base, 0, sec_ppi_sgi_mask);
}
@@ -0,0 +1,556 @@
/*
* Copyright (c) 2015-2020, ARM Limited and Contributors. All rights reserved.
* Portions copyright (c) 2021-2022, ProvenRun S.A.S. All rights reserved.
*
* SPDX-License-Identifier: BSD-3-Clause
*/
#include <assert.h>
#include <stdbool.h>
#include <arch.h>
#include <arch_helpers.h>
#include <common/debug.h>
#include <common/interrupt_props.h>
#include <drivers/arm/gic_common.h>
#include <drivers/arm/gicv2.h>
#include <lib/spinlock.h>
#include "../common/gic_common_private.h"
#include "gicv2_private.h"
static const gicv2_driver_data_t *driver_data;
/*
* Spinlock to guard registers needing read-modify-write. APIs protected by this
* spinlock are used either at boot time (when only a single CPU is active), or
* when the system is fully coherent.
*/
static spinlock_t gic_lock;
/*******************************************************************************
* Enable secure interrupts and use FIQs to route them. Disable legacy bypass
* and set the priority mask register to allow all interrupts to trickle in.
******************************************************************************/
void gicv2_cpuif_enable(void)
{
unsigned int val;
assert(driver_data != NULL);
assert(driver_data->gicc_base != 0U);
/*
* Enable the Group 0 interrupts, FIQEn and disable Group 0/1
* bypass.
*/
val = CTLR_ENABLE_G0_BIT | FIQ_EN_BIT | FIQ_BYP_DIS_GRP0;
val |= IRQ_BYP_DIS_GRP0 | FIQ_BYP_DIS_GRP1 | IRQ_BYP_DIS_GRP1;
/* Program the idle priority in the PMR */
gicc_write_pmr(driver_data->gicc_base, GIC_PRI_MASK);
gicc_write_ctlr(driver_data->gicc_base, val);
}
/*******************************************************************************
* Place the cpu interface in a state where it can never make a cpu exit wfi as
* as result of an asserted interrupt. This is critical for powering down a cpu
******************************************************************************/
void gicv2_cpuif_disable(void)
{
unsigned int val;
assert(driver_data != NULL);
assert(driver_data->gicc_base != 0U);
/* Disable secure, non-secure interrupts and disable their bypass */
val = gicc_read_ctlr(driver_data->gicc_base);
val &= ~(CTLR_ENABLE_G0_BIT | CTLR_ENABLE_G1_BIT);
val |= FIQ_BYP_DIS_GRP1 | FIQ_BYP_DIS_GRP0;
val |= IRQ_BYP_DIS_GRP0 | IRQ_BYP_DIS_GRP1;
gicc_write_ctlr(driver_data->gicc_base, val);
}
/*******************************************************************************
* Per cpu gic distributor setup which will be done by all cpus after a cold
* boot/hotplug. This marks out the secure SPIs and PPIs & enables them.
******************************************************************************/
void gicv2_pcpu_distif_init(void)
{
unsigned int ctlr;
assert(driver_data != NULL);
assert(driver_data->gicd_base != 0U);
gicv2_secure_ppi_sgi_setup_props(driver_data->gicd_base,
driver_data->interrupt_props,
driver_data->interrupt_props_num);
/* Enable G0 interrupts if not already */
ctlr = gicd_read_ctlr(driver_data->gicd_base);
if ((ctlr & CTLR_ENABLE_G0_BIT) == 0U) {
gicd_write_ctlr(driver_data->gicd_base,
ctlr | CTLR_ENABLE_G0_BIT);
}
}
/*******************************************************************************
* Global gic distributor init which will be done by the primary cpu after a
* cold boot. It marks out the secure SPIs, PPIs & SGIs and enables them. It
* then enables the secure GIC distributor interface.
******************************************************************************/
void gicv2_distif_init(void)
{
unsigned int ctlr;
assert(driver_data != NULL);
assert(driver_data->gicd_base != 0U);
/* Disable the distributor before going further */
ctlr = gicd_read_ctlr(driver_data->gicd_base);
gicd_write_ctlr(driver_data->gicd_base,
ctlr & ~(CTLR_ENABLE_G0_BIT | CTLR_ENABLE_G1_BIT));
/* Set the default attribute of all SPIs */
gicv2_spis_configure_defaults(driver_data->gicd_base);
gicv2_secure_spis_configure_props(driver_data->gicd_base,
driver_data->interrupt_props,
driver_data->interrupt_props_num);
/* Re-enable the secure SPIs now that they have been configured */
gicd_write_ctlr(driver_data->gicd_base, ctlr | CTLR_ENABLE_G0_BIT);
}
/*******************************************************************************
* Initialize the ARM GICv2 driver with the provided platform inputs
******************************************************************************/
void gicv2_driver_init(const gicv2_driver_data_t *plat_driver_data)
{
unsigned int gic_version;
assert(plat_driver_data != NULL);
assert(plat_driver_data->gicd_base != 0U);
assert(plat_driver_data->gicc_base != 0U);
assert(plat_driver_data->interrupt_props_num > 0 ?
plat_driver_data->interrupt_props != NULL : 1);
/* Ensure that this is a GICv2 system */
gic_version = gicd_read_pidr2(plat_driver_data->gicd_base);
gic_version = (gic_version >> PIDR2_ARCH_REV_SHIFT)
& PIDR2_ARCH_REV_MASK;
/*
* GICv1 with security extension complies with trusted firmware
* GICv2 driver as far as virtualization and few tricky power
* features are not used. GICv2 features that are not supported
* by GICv1 with Security Extensions are:
* - virtual interrupt support.
* - wake up events.
* - writeable GIC state register (for power sequences)
* - interrupt priority drop.
* - interrupt signal bypass.
*/
assert((gic_version == ARCH_REV_GICV2) ||
(gic_version == ARCH_REV_GICV1));
driver_data = plat_driver_data;
/*
* The GIC driver data is initialized by the primary CPU with caches
* enabled. When the secondary CPU boots up, it initializes the
* GICC/GICR interface with the caches disabled. Hence flush the
* driver_data to ensure coherency. This is not required if the
* platform has HW_ASSISTED_COHERENCY or WARMBOOT_ENABLE_DCACHE_EARLY
* enabled.
*/
#if !(HW_ASSISTED_COHERENCY || WARMBOOT_ENABLE_DCACHE_EARLY)
flush_dcache_range((uintptr_t) &driver_data, sizeof(driver_data));
flush_dcache_range((uintptr_t) driver_data, sizeof(*driver_data));
#endif
INFO("ARM GICv2 driver initialized\n");
}
/******************************************************************************
* This function returns whether FIQ is enabled in the GIC CPU interface.
*****************************************************************************/
unsigned int gicv2_is_fiq_enabled(void)
{
unsigned int gicc_ctlr;
assert(driver_data != NULL);
assert(driver_data->gicc_base != 0U);
gicc_ctlr = gicc_read_ctlr(driver_data->gicc_base);
return (gicc_ctlr >> FIQ_EN_SHIFT) & 0x1U;
}
/*******************************************************************************
* This function returns the type of the highest priority pending interrupt at
* the GIC cpu interface. The return values can be one of the following :
* PENDING_G1_INTID : The interrupt type is non secure Group 1.
* 0 - 1019 : The interrupt type is secure Group 0.
* GIC_SPURIOUS_INTERRUPT : there is no pending interrupt with
* sufficient priority to be signaled
******************************************************************************/
unsigned int gicv2_get_pending_interrupt_type(void)
{
assert(driver_data != NULL);
assert(driver_data->gicc_base != 0U);
return gicc_read_hppir(driver_data->gicc_base) & INT_ID_MASK;
}
/*******************************************************************************
* This function returns the id of the highest priority pending interrupt at
* the GIC cpu interface. GIC_SPURIOUS_INTERRUPT is returned when there is no
* interrupt pending.
******************************************************************************/
unsigned int gicv2_get_pending_interrupt_id(void)
{
unsigned int id;
assert(driver_data != NULL);
assert(driver_data->gicc_base != 0U);
id = gicc_read_hppir(driver_data->gicc_base) & INT_ID_MASK;
/*
* Find out which non-secure interrupt it is under the assumption that
* the GICC_CTLR.AckCtl bit is 0.
*/
if (id == PENDING_G1_INTID)
id = gicc_read_ahppir(driver_data->gicc_base) & INT_ID_MASK;
return id;
}
/*******************************************************************************
* This functions reads the GIC cpu interface Interrupt Acknowledge register
* to start handling the pending secure 0 interrupt. It returns the
* contents of the IAR.
******************************************************************************/
unsigned int gicv2_acknowledge_interrupt(void)
{
assert(driver_data != NULL);
assert(driver_data->gicc_base != 0U);
return gicc_read_IAR(driver_data->gicc_base);
}
/*******************************************************************************
* This functions writes the GIC cpu interface End Of Interrupt register with
* the passed value to finish handling the active secure group 0 interrupt.
******************************************************************************/
void gicv2_end_of_interrupt(unsigned int id)
{
assert(driver_data != NULL);
assert(driver_data->gicc_base != 0U);
/*
* Ensure the write to peripheral registers are *complete* before the write
* to GIC_EOIR.
*
* Note: The completion gurantee depends on various factors of system design
* and the barrier is the best core can do by which execution of further
* instructions waits till the barrier is alive.
*/
dsbishst();
gicc_write_EOIR(driver_data->gicc_base, id);
}
/*******************************************************************************
* This function returns the type of the interrupt id depending upon the group
* this interrupt has been configured under by the interrupt controller i.e.
* group0 secure or group1 non secure. It returns zero for Group 0 secure and
* one for Group 1 non secure interrupt.
******************************************************************************/
unsigned int gicv2_get_interrupt_group(unsigned int id)
{
assert(driver_data != NULL);
assert(driver_data->gicd_base != 0U);
return gicd_get_igroupr(driver_data->gicd_base, id);
}
/*******************************************************************************
* This function returns the priority of the interrupt the processor is
* currently servicing.
******************************************************************************/
unsigned int gicv2_get_running_priority(void)
{
assert(driver_data != NULL);
assert(driver_data->gicc_base != 0U);
return gicc_read_rpr(driver_data->gicc_base);
}
/*******************************************************************************
* This function sets the GICv2 target mask pattern for the current PE. The PE
* target mask is used to translate linear PE index (returned by platform core
* position) to a bit mask used when targeting interrupts to a PE (for example
* when raising SGIs and routing SPIs).
******************************************************************************/
void gicv2_set_pe_target_mask(unsigned int proc_num)
{
assert(driver_data != NULL);
assert(driver_data->gicd_base != 0U);
assert(driver_data->target_masks != NULL);
assert(proc_num < GICV2_MAX_TARGET_PE);
assert(proc_num < driver_data->target_masks_num);
/* Return if the target mask is already populated */
if (driver_data->target_masks[proc_num] != 0U)
return;
/*
* Update target register corresponding to this CPU and flush for it to
* be visible to other CPUs.
*/
if (driver_data->target_masks[proc_num] == 0U) {
driver_data->target_masks[proc_num] =
gicv2_get_cpuif_id(driver_data->gicd_base);
#if !(HW_ASSISTED_COHERENCY || WARMBOOT_ENABLE_DCACHE_EARLY)
/*
* PEs only update their own masks. Primary updates it with
* caches on. But because secondaries does it with caches off,
* all updates go to memory directly, and there's no danger of
* secondaries overwriting each others' mask, despite
* target_masks[] not being cache line aligned.
*/
flush_dcache_range((uintptr_t)
&driver_data->target_masks[proc_num],
sizeof(driver_data->target_masks[proc_num]));
#endif
}
}
/*******************************************************************************
* This function returns the active status of the interrupt (either because the
* state is active, or active and pending).
******************************************************************************/
unsigned int gicv2_get_interrupt_active(unsigned int id)
{
assert(driver_data != NULL);
assert(driver_data->gicd_base != 0U);
assert(id <= MAX_SPI_ID);
return gicd_get_isactiver(driver_data->gicd_base, id);
}
/*******************************************************************************
* This function enables the interrupt identified by id.
******************************************************************************/
void gicv2_enable_interrupt(unsigned int id)
{
assert(driver_data != NULL);
assert(driver_data->gicd_base != 0U);
assert(id <= MAX_SPI_ID);
/*
* Ensure that any shared variable updates depending on out of band
* interrupt trigger are observed before enabling interrupt.
*/
dsbishst();
gicd_set_isenabler(driver_data->gicd_base, id);
}
/*******************************************************************************
* This function disables the interrupt identified by id.
******************************************************************************/
void gicv2_disable_interrupt(unsigned int id)
{
assert(driver_data != NULL);
assert(driver_data->gicd_base != 0U);
assert(id <= MAX_SPI_ID);
/*
* Disable interrupt, and ensure that any shared variable updates
* depending on out of band interrupt trigger are observed afterwards.
*/
gicd_set_icenabler(driver_data->gicd_base, id);
dsbishst();
}
/*******************************************************************************
* This function sets the interrupt priority as supplied for the given interrupt
* id.
******************************************************************************/
void gicv2_set_interrupt_priority(unsigned int id, unsigned int priority)
{
assert(driver_data != NULL);
assert(driver_data->gicd_base != 0U);
assert(id <= MAX_SPI_ID);
gicd_set_ipriorityr(driver_data->gicd_base, id, priority);
}
/*******************************************************************************
* This function assigns group for the interrupt identified by id. The group can
* be any of GICV2_INTR_GROUP*
******************************************************************************/
void gicv2_set_interrupt_type(unsigned int id, unsigned int type)
{
assert(driver_data != NULL);
assert(driver_data->gicd_base != 0U);
assert(id <= MAX_SPI_ID);
/* Serialize read-modify-write to Distributor registers */
spin_lock(&gic_lock);
switch (type) {
case GICV2_INTR_GROUP1:
gicd_set_igroupr(driver_data->gicd_base, id);
break;
case GICV2_INTR_GROUP0:
gicd_clr_igroupr(driver_data->gicd_base, id);
break;
default:
assert(false);
break;
}
spin_unlock(&gic_lock);
}
/*******************************************************************************
* This function raises the specified SGI to requested targets.
*
* The proc_num parameter must be the linear index of the target PE in the
* system.
******************************************************************************/
void gicv2_raise_sgi(int sgi_num, bool ns, int proc_num)
{
unsigned int sgir_val, target;
assert(driver_data != NULL);
assert(proc_num >= 0);
assert(proc_num < (int)GICV2_MAX_TARGET_PE);
assert(driver_data->gicd_base != 0U);
/*
* Target masks array must have been supplied, and the core position
* should be valid.
*/
assert(driver_data->target_masks != NULL);
assert(proc_num < (int)driver_data->target_masks_num);
/* Don't raise SGI if the mask hasn't been populated */
target = driver_data->target_masks[proc_num];
assert(target != 0U);
sgir_val = GICV2_SGIR_VALUE(SGIR_TGT_SPECIFIC, target, ns, sgi_num);
/*
* Ensure that any shared variable updates depending on out of band
* interrupt trigger are observed before raising SGI.
*/
dsbishst();
gicd_write_sgir(driver_data->gicd_base, sgir_val);
}
/*******************************************************************************
* This function sets the interrupt routing for the given SPI interrupt id.
* The interrupt routing is specified in routing mode. The proc_num parameter is
* linear index of the PE to target SPI. When proc_num < 0, the SPI may target
* all PEs.
******************************************************************************/
void gicv2_set_spi_routing(unsigned int id, int proc_num)
{
unsigned int target;
assert(driver_data != NULL);
assert(driver_data->gicd_base != 0U);
assert((id >= MIN_SPI_ID) && (id <= MAX_SPI_ID));
/*
* Target masks array must have been supplied, and the core position
* should be valid.
*/
assert(driver_data->target_masks != NULL);
assert(proc_num < (int)GICV2_MAX_TARGET_PE);
assert(driver_data->target_masks_num < INT_MAX);
assert(proc_num < (int)driver_data->target_masks_num);
if (proc_num < 0) {
/* Target all PEs */
target = GIC_TARGET_CPU_MASK;
} else {
/* Don't route interrupt if the mask hasn't been populated */
target = driver_data->target_masks[proc_num];
assert(target != 0U);
}
gicd_set_itargetsr(driver_data->gicd_base, id, target);
}
/*******************************************************************************
* This function clears the pending status of an interrupt identified by id.
******************************************************************************/
void gicv2_clear_interrupt_pending(unsigned int id)
{
assert(driver_data != NULL);
assert(driver_data->gicd_base != 0U);
/* SGIs can't be cleared pending */
assert(id >= MIN_PPI_ID);
/*
* Clear pending interrupt, and ensure that any shared variable updates
* depending on out of band interrupt trigger are observed afterwards.
*/
gicd_set_icpendr(driver_data->gicd_base, id);
dsbishst();
}
/*******************************************************************************
* This function sets the pending status of an interrupt identified by id.
******************************************************************************/
void gicv2_set_interrupt_pending(unsigned int id)
{
assert(driver_data != NULL);
assert(driver_data->gicd_base != 0U);
/* SGIs can't be cleared pending */
assert(id >= MIN_PPI_ID);
/*
* Ensure that any shared variable updates depending on out of band
* interrupt trigger are observed before setting interrupt pending.
*/
dsbishst();
gicd_set_ispendr(driver_data->gicd_base, id);
}
/*******************************************************************************
* This function sets the PMR register with the supplied value. Returns the
* original PMR.
******************************************************************************/
unsigned int gicv2_set_pmr(unsigned int mask)
{
unsigned int old_mask;
assert(driver_data != NULL);
assert(driver_data->gicc_base != 0U);
old_mask = gicc_read_pmr(driver_data->gicc_base);
/*
* Order memory updates w.r.t. PMR write, and ensure they're visible
* before potential out of band interrupt trigger because of PMR update.
*/
dmbishst();
gicc_write_pmr(driver_data->gicc_base, mask);
dsbishst();
return old_mask;
}
/*******************************************************************************
* This function updates single interrupt configuration to be level/edge
* triggered
******************************************************************************/
void gicv2_interrupt_set_cfg(unsigned int id, unsigned int cfg)
{
gicd_set_icfgr(driver_data->gicd_base, id, cfg);
}
@@ -0,0 +1,150 @@
/*
* Copyright (c) 2015-2017, ARM Limited and Contributors. All rights reserved.
*
* SPDX-License-Identifier: BSD-3-Clause
*/
#ifndef GICV2_PRIVATE_H
#define GICV2_PRIVATE_H
#include <stdint.h>
#include <drivers/arm/gicv2.h>
#include <lib/mmio.h>
/*******************************************************************************
* Private function prototypes
******************************************************************************/
void gicv2_spis_configure_defaults(uintptr_t gicd_base);
void gicv2_secure_spis_configure_props(uintptr_t gicd_base,
const interrupt_prop_t *interrupt_props,
unsigned int interrupt_props_num);
void gicv2_secure_ppi_sgi_setup_props(uintptr_t gicd_base,
const interrupt_prop_t *interrupt_props,
unsigned int interrupt_props_num);
unsigned int gicv2_get_cpuif_id(uintptr_t base);
/*******************************************************************************
* GIC Distributor interface accessors for reading entire registers
******************************************************************************/
static inline unsigned int gicd_read_pidr2(uintptr_t base)
{
return mmio_read_32(base + GICD_PIDR2_GICV2);
}
/*******************************************************************************
* GIC Distributor interface accessors for writing entire registers
******************************************************************************/
static inline unsigned int gicd_get_itargetsr(uintptr_t base, unsigned int id)
{
return mmio_read_8(base + GICD_ITARGETSR + id);
}
static inline void gicd_set_itargetsr(uintptr_t base, unsigned int id,
unsigned int target)
{
uint8_t val = target & GIC_TARGET_CPU_MASK;
mmio_write_8(base + GICD_ITARGETSR + id, val);
}
static inline void gicd_write_sgir(uintptr_t base, unsigned int val)
{
mmio_write_32(base + GICD_SGIR, val);
}
/*******************************************************************************
* GIC CPU interface accessors for reading entire registers
******************************************************************************/
static inline unsigned int gicc_read_ctlr(uintptr_t base)
{
return mmio_read_32(base + GICC_CTLR);
}
static inline unsigned int gicc_read_pmr(uintptr_t base)
{
return mmio_read_32(base + GICC_PMR);
}
static inline unsigned int gicc_read_BPR(uintptr_t base)
{
return mmio_read_32(base + GICC_BPR);
}
static inline unsigned int gicc_read_IAR(uintptr_t base)
{
return mmio_read_32(base + GICC_IAR);
}
static inline unsigned int gicc_read_EOIR(uintptr_t base)
{
return mmio_read_32(base + GICC_EOIR);
}
static inline unsigned int gicc_read_hppir(uintptr_t base)
{
return mmio_read_32(base + GICC_HPPIR);
}
static inline unsigned int gicc_read_ahppir(uintptr_t base)
{
return mmio_read_32(base + GICC_AHPPIR);
}
static inline unsigned int gicc_read_dir(uintptr_t base)
{
return mmio_read_32(base + GICC_DIR);
}
static inline unsigned int gicc_read_iidr(uintptr_t base)
{
return mmio_read_32(base + GICC_IIDR);
}
static inline unsigned int gicc_read_rpr(uintptr_t base)
{
return mmio_read_32(base + GICC_RPR);
}
/*******************************************************************************
* GIC CPU interface accessors for writing entire registers
******************************************************************************/
static inline void gicc_write_ctlr(uintptr_t base, unsigned int val)
{
mmio_write_32(base + GICC_CTLR, val);
}
static inline void gicc_write_pmr(uintptr_t base, unsigned int val)
{
mmio_write_32(base + GICC_PMR, val);
}
static inline void gicc_write_BPR(uintptr_t base, unsigned int val)
{
mmio_write_32(base + GICC_BPR, val);
}
static inline void gicc_write_IAR(uintptr_t base, unsigned int val)
{
mmio_write_32(base + GICC_IAR, val);
}
static inline void gicc_write_EOIR(uintptr_t base, unsigned int val)
{
mmio_write_32(base + GICC_EOIR, val);
}
static inline void gicc_write_hppir(uintptr_t base, unsigned int val)
{
mmio_write_32(base + GICC_HPPIR, val);
}
static inline void gicc_write_dir(uintptr_t base, unsigned int val)
{
mmio_write_32(base + GICC_DIR, val);
}
#endif /* GICV2_PRIVATE_H */
@@ -0,0 +1,115 @@
/*
* Copyright (c) 2017, ARM Limited and Contributors. All rights reserved.
*
* SPDX-License-Identifier: BSD-3-Clause
*/
/*
* Driver for implementation defined features that are identical in ARM GICv3
* implementations (GIC-500 and GIC-600 for now). This driver only overrides
* APIs that are different to those generic ones in GICv3 driver.
*/
#include <assert.h>
#include <arch_helpers.h>
#include <drivers/arm/arm_gicv3_common.h>
#include <drivers/arm/gicv3.h>
#include "gicv3_private.h"
/*
* Flush the internal GIC cache of the LPIs pending tables to memory before
* saving the state of the Redistributor. This is required before powering off
* the GIC when the pending status must be preserved.
* `rdist_proc_num` is the processor number corresponding to the Redistributor of the
* current CPU.
*/
void arm_gicv3_distif_pre_save(unsigned int rdist_proc_num)
{
uintptr_t gicr_base = 0;
assert(gicv3_driver_data);
assert(gicv3_driver_data->rdistif_base_addrs);
/*
* The GICR_WAKER.Sleep bit should be set only when both
* GICR_WAKER.ChildrenAsleep and GICR_WAKER.ProcessorSleep are set on
* all the Redistributors.
*/
for (unsigned int i = 0; i < gicv3_driver_data->rdistif_num; i++) {
gicr_base = gicv3_driver_data->rdistif_base_addrs[i];
assert(gicr_base);
assert(gicr_read_waker(gicr_base) & WAKER_CA_BIT);
assert(gicr_read_waker(gicr_base) & WAKER_PS_BIT);
}
gicr_base = gicv3_driver_data->rdistif_base_addrs[rdist_proc_num];
/*
* According to the TRM, there is only one instance of the
* GICR_WAKER.Sleep and GICR_WAKER.Quiescent bits that can be accessed
* through any of the Redistributor.
*/
/*
* Set GICR_WAKER.Sleep
* After this point, the system must be configured so that the
* wake_request signals for the right cores are asserted when a wakeup
* interrupt is detected. The GIC will not be able to do that anymore
* when the GICR_WAKER.Sleep bit is set to 1.
*/
gicr_write_waker(gicr_base, gicr_read_waker(gicr_base) | WAKER_SL_BIT);
/* Wait until the GICR_WAKER.Quiescent bit is set */
while (!(gicr_read_waker(gicr_base) & WAKER_QSC_BIT))
;
}
/*
* Allow the LPIs pending state to be read back from the tables in memory after
* having restored the state of the GIC Redistributor.
*/
void arm_gicv3_distif_post_restore(unsigned int rdist_proc_num)
{
uintptr_t gicr_base;
assert(gicv3_driver_data);
assert(gicv3_driver_data->rdistif_base_addrs);
/*
* According to the TRM, there is only one instance of the
* GICR_WAKER.Sleep and GICR_WAKER.Quiescent bits that can be accessed
* through any of the Redistributor.
*/
gicr_base = gicv3_driver_data->rdistif_base_addrs[rdist_proc_num];
assert(gicr_base);
/*
* If the GIC had power removed, the GICR_WAKER state will be reset.
* Since the GICR_WAKER.Sleep and GICR_WAKER.Quiescent bits are cleared,
* we can exit early. This also prevents the following assert from
* erroneously triggering.
*/
if (!(gicr_read_waker(gicr_base) & WAKER_SL_BIT))
return;
/*
* Writes to GICR_WAKER.Sleep bit are ignored if GICR_WAKER.Quiescent
* bit is not set. We should be alright on power on path, therefore
* coming out of sleep and Quiescent should be set, but we assert in
* case.
*/
assert(gicr_read_waker(gicr_base) & WAKER_QSC_BIT);
/* Clear GICR_WAKER.Sleep */
gicr_write_waker(gicr_base, gicr_read_waker(gicr_base) & ~WAKER_SL_BIT);
/*
* We don't know if the effects of setting GICR_WAKER.Sleep bit is
* instantaneous, so we wait until the interface is not Quiescent
* anymore.
*/
while (gicr_read_waker(gicr_base) & WAKER_QSC_BIT)
;
}
@@ -0,0 +1,232 @@
/*
* Copyright (c) 2017-2022, Arm Limited and Contributors. All rights reserved.
* Copyright (c) 2020, NVIDIA Corporation. All rights reserved.
*
* SPDX-License-Identifier: BSD-3-Clause
*/
/*
* Driver for GIC-500 and GIC-600 specific features. This driver only
* overrides APIs that are different to those generic ones in GICv3
* driver.
*
* GIC-600 supports independently power-gating redistributor interface.
*/
#include <assert.h>
#include <arch_helpers.h>
#include <common/debug.h>
#include <drivers/arm/arm_gicv3_common.h>
#include <drivers/arm/gicv3.h>
#include "gicv3_private.h"
/* GIC-600 specific register offsets */
#define GICR_PWRR 0x24U
/* GICR_PWRR fields */
#define PWRR_RDPD_SHIFT 0
#define PWRR_RDAG_SHIFT 1
#define PWRR_RDGPD_SHIFT 2
#define PWRR_RDGPO_SHIFT 3
#define PWRR_RDPD (1U << PWRR_RDPD_SHIFT)
#define PWRR_RDAG (1U << PWRR_RDAG_SHIFT)
#define PWRR_RDGPD (1U << PWRR_RDGPD_SHIFT)
#define PWRR_RDGPO (1U << PWRR_RDGPO_SHIFT)
/*
* Values to write to GICR_PWRR register to power redistributor
* for operating through the core (GICR_PWRR.RDAG = 0)
*/
#define PWRR_ON (0U << PWRR_RDPD_SHIFT)
#define PWRR_OFF (1U << PWRR_RDPD_SHIFT)
static bool gic600_errata_wa_2384374 __unused;
#if GICV3_SUPPORT_GIC600
/* GIC-600/700 specific accessor functions */
static void gicr_write_pwrr(uintptr_t base, unsigned int val)
{
mmio_write_32(base + GICR_PWRR, val);
}
static uint32_t gicr_read_pwrr(uintptr_t base)
{
return mmio_read_32(base + GICR_PWRR);
}
static void gicr_wait_group_not_in_transit(uintptr_t base)
{
uint32_t pwrr;
do {
pwrr = gicr_read_pwrr(base);
/* Check group not transitioning: RDGPD == RDGPO */
} while (((pwrr & PWRR_RDGPD) >> PWRR_RDGPD_SHIFT) !=
((pwrr & PWRR_RDGPO) >> PWRR_RDGPO_SHIFT));
}
static void gic600_pwr_on(uintptr_t base)
{
do { /* Wait until group not transitioning */
gicr_wait_group_not_in_transit(base);
/* Power on redistributor */
gicr_write_pwrr(base, PWRR_ON);
/*
* Wait until the power on state is reflected.
* If RDPD == 0 then powered on.
*/
} while ((gicr_read_pwrr(base) & PWRR_RDPD) != PWRR_ON);
}
static void gic600_pwr_off(uintptr_t base)
{
/* Wait until group not transitioning */
gicr_wait_group_not_in_transit(base);
/* Power off redistributor */
gicr_write_pwrr(base, PWRR_OFF);
/*
* If this is the last man, turning this redistributor frame off will
* result in the group itself being powered off and RDGPD = 1.
* In that case, wait as long as it's in transition, or has aborted
* the transition altogether for any reason.
*/
if ((gicr_read_pwrr(base) & PWRR_RDGPD) != 0U) {
/* Wait until group not transitioning */
gicr_wait_group_not_in_transit(base);
}
}
static uintptr_t get_gicr_base(unsigned int proc_num)
{
uintptr_t gicr_base;
assert(gicv3_driver_data != NULL);
assert(proc_num < gicv3_driver_data->rdistif_num);
assert(gicv3_driver_data->rdistif_base_addrs != NULL);
gicr_base = gicv3_driver_data->rdistif_base_addrs[proc_num];
assert(gicr_base != 0UL);
return gicr_base;
}
static bool gicv3_redists_need_power_mgmt(uintptr_t gicr_base)
{
uint32_t reg = mmio_read_32(gicr_base + GICR_IIDR);
/*
* The Arm GIC-600 and GIC-700 models have their redistributors
* powered down at reset.
*/
return (((reg & IIDR_MODEL_MASK) == IIDR_MODEL_ARM_GIC_600) ||
((reg & IIDR_MODEL_MASK) == IIDR_MODEL_ARM_GIC_600AE) ||
((reg & IIDR_MODEL_MASK) == IIDR_MODEL_ARM_GIC_700));
}
#endif /* GICV3_SUPPORT_GIC600 */
void gicv3_distif_pre_save(unsigned int proc_num)
{
arm_gicv3_distif_pre_save(proc_num);
}
void gicv3_distif_post_restore(unsigned int proc_num)
{
arm_gicv3_distif_post_restore(proc_num);
}
/*
* Power off GIC-600 redistributor (if configured and detected)
*/
void gicv3_rdistif_off(unsigned int proc_num)
{
#if GICV3_SUPPORT_GIC600
uintptr_t gicr_base = get_gicr_base(proc_num);
/* Attempt to power redistributor off */
if (gicv3_redists_need_power_mgmt(gicr_base)) {
gic600_pwr_off(gicr_base);
}
#endif
}
/*
* Power on GIC-600 redistributor (if configured and detected)
*/
void gicv3_rdistif_on(unsigned int proc_num)
{
#if GICV3_SUPPORT_GIC600
uintptr_t gicr_base = get_gicr_base(proc_num);
/* Power redistributor on */
if (gicv3_redists_need_power_mgmt(gicr_base)) {
gic600_pwr_on(gicr_base);
}
#endif
}
#if GIC600_ERRATA_WA_2384374
/*******************************************************************************
* Apply part 2 of workaround for errata-2384374 as per SDEN:
* https://developer.arm.com/documentation/sden892601/latest/
******************************************************************************/
void gicv3_apply_errata_wa_2384374(uintptr_t gicr_base)
{
if (gic600_errata_wa_2384374) {
uint32_t gicr_ctlr_val = gicr_read_ctlr(gicr_base);
gicr_write_ctlr(gicr_base, gicr_ctlr_val |
(GICR_CTLR_DPG0_BIT | GICR_CTLR_DPG1NS_BIT |
GICR_CTLR_DPG1S_BIT));
gicr_write_ctlr(gicr_base, gicr_ctlr_val &
~(GICR_CTLR_DPG0_BIT | GICR_CTLR_DPG1NS_BIT |
GICR_CTLR_DPG1S_BIT));
}
}
#endif /* GIC600_ERRATA_WA_2384374 */
void gicv3_check_erratas_applies(uintptr_t gicd_base)
{
unsigned int gic_prod_id;
uint8_t gic_rev;
assert(gicd_base != 0UL);
gicv3_get_component_prodid_rev(gicd_base, &gic_prod_id, &gic_rev);
/*
* This workaround applicable only to GIC600 and GIC600AE products with
* revision less than r1p6 and r0p2 respectively.
* As per GIC600/GIC600AE specification -
* r1p6 = 0x17 => GICD_IIDR[19:12]
* r0p2 = 0x04 => GICD_IIDR[19:12]
*/
if ((gic_prod_id == GIC_PRODUCT_ID_GIC600) ||
(gic_prod_id == GIC_PRODUCT_ID_GIC600AE)) {
if (((gic_prod_id == GIC_PRODUCT_ID_GIC600) &&
(gic_rev <= GIC_REV(GIC_VARIANT_R1, GIC_REV_P6))) ||
((gic_prod_id == GIC_PRODUCT_ID_GIC600AE) &&
(gic_rev <= GIC_REV(GIC_VARIANT_R0, GIC_REV_P2)))) {
#if GIC600_ERRATA_WA_2384374
gic600_errata_wa_2384374 = true;
VERBOSE("%s applies\n",
"GIC600/GIC600AE errata workaround 2384374");
#else
WARN("%s missing\n",
"GIC600/GIC600AE errata workaround 2384374");
#endif /* GIC600_ERRATA_WA_2384374 */
} else {
VERBOSE("%s not applies\n",
"GIC600/GIC600AE errata workaround 2384374");
}
}
}
@@ -0,0 +1,391 @@
/*
* Copyright (c) 2019, Arm Limited. All rights reserved.
* Copyright (c) 2022-2023, NVIDIA Corporation. All rights reserved.
*
* SPDX-License-Identifier: BSD-3-Clause
*/
/*
* GIC-600 driver extension for multichip setup
*/
#include <assert.h>
#include <common/debug.h>
#include <drivers/arm/arm_gicv3_common.h>
#include <drivers/arm/gic600_multichip.h>
#include <drivers/arm/gicv3.h>
#include "../common/gic_common_private.h"
#include "gic600_multichip_private.h"
static struct gic600_multichip_data *plat_gic_multichip_data;
/*******************************************************************************
* Retrieve the address of the chip owner for a given SPI ID
******************************************************************************/
uintptr_t gic600_multichip_gicd_base_for_spi(uint32_t spi_id)
{
unsigned int i;
/* Find the multichip instance */
for (i = 0U; i < GIC600_MAX_MULTICHIP; i++) {
if ((spi_id <= plat_gic_multichip_data->spi_ids[i].spi_id_max) &&
(spi_id >= plat_gic_multichip_data->spi_ids[i].spi_id_min)) {
break;
}
}
/* Ensure that plat_gic_multichip_data contains valid values */
assert(i < GIC600_MAX_MULTICHIP);
return plat_gic_multichip_data->spi_ids[i].gicd_base;
}
/*******************************************************************************
* GIC-600 multichip operation related helper functions
******************************************************************************/
static void gicd_dchipr_wait_for_power_update_progress(uintptr_t base)
{
unsigned int retry = GICD_PUP_UPDATE_RETRIES;
while ((read_gicd_dchipr(base) & GICD_DCHIPR_PUP_BIT) != 0U) {
if (retry-- == 0U) {
ERROR("GIC-600 connection to Routing Table Owner timed "
"out\n");
panic();
}
}
}
/*******************************************************************************
* Sets up the routing table owner.
******************************************************************************/
static void set_gicd_dchipr_rt_owner(uintptr_t base, unsigned int rt_owner)
{
/*
* Ensure that Group enables in GICD_CTLR are disabled and no pending
* register writes to GICD_CTLR.
*/
if ((gicd_read_ctlr(base) &
(CTLR_ENABLE_G0_BIT | CTLR_ENABLE_G1S_BIT |
CTLR_ENABLE_G1NS_BIT | GICD_CTLR_RWP_BIT)) != 0) {
ERROR("GICD_CTLR group interrupts are either enabled or have "
"pending writes. Cannot set RT owner.\n");
panic();
}
/* Poll till PUP is zero before intiating write */
gicd_dchipr_wait_for_power_update_progress(base);
write_gicd_dchipr(base, read_gicd_dchipr(base) |
(rt_owner << GICD_DCHIPR_RT_OWNER_SHIFT));
/* Poll till PUP is zero to ensure write is complete */
gicd_dchipr_wait_for_power_update_progress(base);
}
/*******************************************************************************
* Configures the Chip Register to make connections to GICDs on
* a multichip platform.
******************************************************************************/
static void set_gicd_chipr_n(uintptr_t base,
unsigned int chip_id,
uint64_t chip_addr,
unsigned int spi_id_min,
unsigned int spi_id_max)
{
unsigned int spi_block_min, spi_blocks;
unsigned int gicd_iidr_val = gicd_read_iidr(base);
uint64_t chipr_n_val;
/*
* Ensure that group enables in GICD_CTLR are disabled and no pending
* register writes to GICD_CTLR.
*/
if ((gicd_read_ctlr(base) &
(CTLR_ENABLE_G0_BIT | CTLR_ENABLE_G1S_BIT |
CTLR_ENABLE_G1NS_BIT | GICD_CTLR_RWP_BIT)) != 0) {
ERROR("GICD_CTLR group interrupts are either enabled or have "
"pending writes. Cannot set CHIPR register.\n");
panic();
}
/*
* spi_id_min and spi_id_max of value 0 is used to intidicate that the
* chip doesn't own any SPI block. Re-assign min and max values as SPI
* id starts from 32.
*/
if (spi_id_min == 0 && spi_id_max == 0) {
spi_id_min = GIC600_SPI_ID_MIN;
spi_id_max = GIC600_SPI_ID_MIN;
}
switch ((gicd_iidr_val & IIDR_MODEL_MASK)) {
case IIDR_MODEL_ARM_GIC_600:
spi_block_min = SPI_BLOCK_MIN_VALUE(spi_id_min);
spi_blocks = SPI_BLOCKS_VALUE(spi_id_min, spi_id_max);
chipr_n_val = GICD_CHIPR_VALUE_GIC_600(chip_addr,
spi_block_min,
spi_blocks);
break;
case IIDR_MODEL_ARM_GIC_700:
/* Calculate the SPI_ID_MIN value for ESPI */
if (spi_id_min >= GIC700_ESPI_ID_MIN) {
spi_block_min = ESPI_BLOCK_MIN_VALUE(spi_id_min);
spi_block_min += SPI_BLOCKS_VALUE(GIC700_SPI_ID_MIN,
GIC700_SPI_ID_MAX);
} else {
spi_block_min = SPI_BLOCK_MIN_VALUE(spi_id_min);
}
/* Calculate the total number of blocks */
spi_blocks = SPI_BLOCKS_VALUE(spi_id_min, spi_id_max);
chipr_n_val = GICD_CHIPR_VALUE_GIC_700(chip_addr,
spi_block_min,
spi_blocks);
break;
default:
ERROR("Unsupported GIC model 0x%x for multichip setup.\n",
gicd_iidr_val);
panic();
break;
}
chipr_n_val |= GICD_CHIPRx_SOCKET_STATE;
/*
* Wait for DCHIPR.PUP to be zero before commencing writes to
* GICD_CHIPRx.
*/
gicd_dchipr_wait_for_power_update_progress(base);
/*
* Assign chip addr, spi min block, number of spi blocks and bring chip
* online by setting SocketState.
*/
write_gicd_chipr_n(base, chip_id, chipr_n_val);
/*
* Poll until DCHIP.PUP is zero to verify connection to rt_owner chip
* is complete.
*/
gicd_dchipr_wait_for_power_update_progress(base);
/*
* Ensure that write to GICD_CHIPRx is successful and the chip_n came
* online.
*/
if (read_gicd_chipr_n(base, chip_id) != chipr_n_val) {
ERROR("GICD_CHIPR%u write failed\n", chip_id);
panic();
}
/* Ensure that chip is in consistent state */
if (((read_gicd_chipsr(base) & GICD_CHIPSR_RTS_MASK) >>
GICD_CHIPSR_RTS_SHIFT) !=
GICD_CHIPSR_RTS_STATE_CONSISTENT) {
ERROR("Chip %u routing table is not in consistent state\n",
chip_id);
panic();
}
}
/*******************************************************************************
* Validates the GIC-600 Multichip data structure passed by the platform.
******************************************************************************/
static void gic600_multichip_validate_data(
struct gic600_multichip_data *multichip_data)
{
unsigned int i, spi_id_min, spi_id_max, blocks_of_32;
unsigned int multichip_spi_blocks = 0;
assert(multichip_data != NULL);
if (multichip_data->chip_count > GIC600_MAX_MULTICHIP) {
ERROR("GIC-600 Multichip count should not exceed %d\n",
GIC600_MAX_MULTICHIP);
panic();
}
for (i = 0U; i < multichip_data->chip_count; i++) {
spi_id_min = multichip_data->spi_ids[i].spi_id_min;
spi_id_max = multichip_data->spi_ids[i].spi_id_max;
if ((spi_id_min != 0U) || (spi_id_max != 0U)) {
/* SPI IDs range check */
if (!(spi_id_min >= GIC600_SPI_ID_MIN) ||
!(spi_id_max < GIC600_SPI_ID_MAX) ||
!(spi_id_min <= spi_id_max) ||
!((spi_id_max - spi_id_min + 1) % 32 == 0)) {
ERROR("Invalid SPI IDs {%u, %u} passed for "
"Chip %u\n", spi_id_min,
spi_id_max, i);
panic();
}
/* SPI IDs overlap check */
blocks_of_32 = BLOCKS_OF_32(spi_id_min, spi_id_max);
if ((multichip_spi_blocks & blocks_of_32) != 0) {
ERROR("SPI IDs of Chip %u overlapping\n", i);
panic();
}
multichip_spi_blocks |= blocks_of_32;
}
}
}
/*******************************************************************************
* Validates the GIC-700 Multichip data structure passed by the platform.
******************************************************************************/
static void gic700_multichip_validate_data(
struct gic600_multichip_data *multichip_data)
{
unsigned int i, spi_id_min, spi_id_max, blocks_of_32;
unsigned int multichip_spi_blocks = 0U, multichip_espi_blocks = 0U;
assert(multichip_data != NULL);
if (multichip_data->chip_count > GIC600_MAX_MULTICHIP) {
ERROR("GIC-700 Multichip count (%u) should not exceed %u\n",
multichip_data->chip_count, GIC600_MAX_MULTICHIP);
panic();
}
for (i = 0U; i < multichip_data->chip_count; i++) {
spi_id_min = multichip_data->spi_ids[i].spi_id_min;
spi_id_max = multichip_data->spi_ids[i].spi_id_max;
if ((spi_id_min == 0U) || (spi_id_max == 0U)) {
continue;
}
/* MIN SPI ID check */
if ((spi_id_min < GIC700_SPI_ID_MIN) ||
((spi_id_min >= GIC700_SPI_ID_MAX) &&
(spi_id_min < GIC700_ESPI_ID_MIN))) {
ERROR("Invalid MIN SPI ID {%u} passed for "
"Chip %u\n", spi_id_min, i);
panic();
}
if ((spi_id_min > spi_id_max) ||
((spi_id_max - spi_id_min + 1) % 32 != 0)) {
ERROR("Unaligned SPI IDs {%u, %u} passed for "
"Chip %u\n", spi_id_min,
spi_id_max, i);
panic();
}
/* ESPI IDs range check */
if ((spi_id_min >= GIC700_ESPI_ID_MIN) &&
(spi_id_max > GIC700_ESPI_ID_MAX)) {
ERROR("Invalid ESPI IDs {%u, %u} passed for "
"Chip %u\n", spi_id_min,
spi_id_max, i);
panic();
}
/* SPI IDs range check */
if (((spi_id_min < GIC700_SPI_ID_MAX) &&
(spi_id_max > GIC700_SPI_ID_MAX))) {
ERROR("Invalid SPI IDs {%u, %u} passed for "
"Chip %u\n", spi_id_min,
spi_id_max, i);
panic();
}
/* SPI IDs overlap check */
if (spi_id_max < GIC700_SPI_ID_MAX) {
blocks_of_32 = BLOCKS_OF_32(spi_id_min, spi_id_max);
if ((multichip_spi_blocks & blocks_of_32) != 0) {
ERROR("SPI IDs of Chip %u overlapping\n", i);
panic();
}
multichip_spi_blocks |= blocks_of_32;
}
/* ESPI IDs overlap check */
if (spi_id_max > GIC700_ESPI_ID_MIN) {
blocks_of_32 = BLOCKS_OF_32(spi_id_min - GIC700_ESPI_ID_MIN,
spi_id_max - GIC700_ESPI_ID_MIN);
if ((multichip_espi_blocks & blocks_of_32) != 0) {
ERROR("SPI IDs of Chip %u overlapping\n", i);
panic();
}
multichip_espi_blocks |= blocks_of_32;
}
}
}
/*******************************************************************************
* Intialize GIC-600 and GIC-700 Multichip operation.
******************************************************************************/
void gic600_multichip_init(struct gic600_multichip_data *multichip_data)
{
unsigned int i;
uint32_t gicd_iidr_val = gicd_read_iidr(multichip_data->rt_owner_base);
if ((gicd_iidr_val & IIDR_MODEL_MASK) == IIDR_MODEL_ARM_GIC_600) {
gic600_multichip_validate_data(multichip_data);
}
if ((gicd_iidr_val & IIDR_MODEL_MASK) == IIDR_MODEL_ARM_GIC_700) {
gic700_multichip_validate_data(multichip_data);
}
/*
* Ensure that G0/G1S/G1NS interrupts are disabled. This also ensures
* that GIC-600 Multichip configuration is done first.
*/
if ((gicd_read_ctlr(multichip_data->rt_owner_base) &
(CTLR_ENABLE_G0_BIT | CTLR_ENABLE_G1S_BIT |
CTLR_ENABLE_G1NS_BIT | GICD_CTLR_RWP_BIT)) != 0) {
ERROR("GICD_CTLR group interrupts are either enabled or have "
"pending writes.\n");
panic();
}
/* Ensure that the routing table owner is in disconnected state */
if (((read_gicd_chipsr(multichip_data->rt_owner_base) &
GICD_CHIPSR_RTS_MASK) >> GICD_CHIPSR_RTS_SHIFT) !=
GICD_CHIPSR_RTS_STATE_DISCONNECTED) {
ERROR("GIC-600 routing table owner is not in disconnected "
"state to begin multichip configuration\n");
panic();
}
/* Initialize the GICD which is marked as routing table owner first */
set_gicd_dchipr_rt_owner(multichip_data->rt_owner_base,
multichip_data->rt_owner);
set_gicd_chipr_n(multichip_data->rt_owner_base, multichip_data->rt_owner,
multichip_data->chip_addrs[multichip_data->rt_owner],
multichip_data->
spi_ids[multichip_data->rt_owner].spi_id_min,
multichip_data->
spi_ids[multichip_data->rt_owner].spi_id_max);
for (i = 0; i < multichip_data->chip_count; i++) {
if (i == multichip_data->rt_owner)
continue;
set_gicd_chipr_n(multichip_data->rt_owner_base, i,
multichip_data->chip_addrs[i],
multichip_data->spi_ids[i].spi_id_min,
multichip_data->spi_ids[i].spi_id_max);
}
plat_gic_multichip_data = multichip_data;
}
/*******************************************************************************
* Allow a way to query the status of the GIC600 multichip driver
******************************************************************************/
bool gic600_multichip_is_initialized(void)
{
return (plat_gic_multichip_data != NULL);
}
@@ -0,0 +1,111 @@
/*
* Copyright (c) 2019-2022, ARM Limited. All rights reserved.
*
* SPDX-License-Identifier: BSD-3-Clause
*/
#ifndef GIC600_MULTICHIP_PRIVATE_H
#define GIC600_MULTICHIP_PRIVATE_H
#include <drivers/arm/gic600_multichip.h>
#include "gicv3_private.h"
/* GIC600 GICD multichip related offsets */
#define GICD_CHIPSR U(0xC000)
#define GICD_DCHIPR U(0xC004)
#define GICD_CHIPR U(0xC008)
/* GIC600 GICD multichip related masks */
#define GICD_CHIPRx_PUP_BIT BIT_64(1)
#define GICD_CHIPRx_SOCKET_STATE BIT_64(0)
#define GICD_DCHIPR_PUP_BIT BIT_32(0)
#define GICD_CHIPSR_RTS_MASK (BIT_32(4) | BIT_32(5))
/* GIC600 GICD multichip related shifts */
#define GICD_CHIPRx_ADDR_SHIFT 16
#define GICD_CHIPSR_RTS_SHIFT 4
#define GICD_DCHIPR_RT_OWNER_SHIFT 4
/* Other shifts and masks remain the same between GIC-600 and GIC-700. */
#define GIC_700_SPI_BLOCK_MIN_SHIFT 9
#define GIC_700_SPI_BLOCKS_SHIFT 3
#define GIC_600_SPI_BLOCK_MIN_SHIFT 10
#define GIC_600_SPI_BLOCKS_SHIFT 5
#define GICD_CHIPSR_RTS_STATE_DISCONNECTED U(0)
#define GICD_CHIPSR_RTS_STATE_UPDATING U(1)
#define GICD_CHIPSR_RTS_STATE_CONSISTENT U(2)
/* SPI interrupt id minimum and maximum range */
#define GIC600_SPI_ID_MIN 32
#define GIC600_SPI_ID_MAX 960
#define GIC700_SPI_ID_MIN 32
#define GIC700_SPI_ID_MAX 991
#define GIC700_ESPI_ID_MIN 4096
#define GIC700_ESPI_ID_MAX 5119
/* Number of retries for PUP update */
#define GICD_PUP_UPDATE_RETRIES 10000
#define SPI_BLOCK_MIN_VALUE(spi_id_min) \
(((spi_id_min) - GIC600_SPI_ID_MIN) / \
GIC600_SPI_ID_MIN)
#define SPI_BLOCKS_VALUE(spi_id_min, spi_id_max) \
(((spi_id_max) - (spi_id_min) + 1) / \
GIC600_SPI_ID_MIN)
#define ESPI_BLOCK_MIN_VALUE(spi_id_min) \
(((spi_id_min) - GIC700_ESPI_ID_MIN + 1) / \
GIC700_SPI_ID_MIN)
#define GICD_CHIPR_VALUE_GIC_700(chip_addr, spi_block_min, spi_blocks) \
(((chip_addr) << GICD_CHIPRx_ADDR_SHIFT) | \
((spi_block_min) << GIC_700_SPI_BLOCK_MIN_SHIFT) | \
((spi_blocks) << GIC_700_SPI_BLOCKS_SHIFT))
#define GICD_CHIPR_VALUE_GIC_600(chip_addr, spi_block_min, spi_blocks) \
(((chip_addr) << GICD_CHIPRx_ADDR_SHIFT) | \
((spi_block_min) << GIC_600_SPI_BLOCK_MIN_SHIFT) | \
((spi_blocks) << GIC_600_SPI_BLOCKS_SHIFT))
/*
* Multichip data assertion macros
*/
/* Set bits from 0 to ((spi_id_max + 1) / 32) */
#define SPI_BLOCKS_TILL_MAX(spi_id_max) \
((1ULL << (((spi_id_max) + 1) >> 5)) - 1)
/* Set bits from 0 to (spi_id_min / 32) */
#define SPI_BLOCKS_TILL_MIN(spi_id_min) ((1 << ((spi_id_min) >> 5)) - 1)
/* Set bits from (spi_id_min / 32) to ((spi_id_max + 1) / 32) */
#define BLOCKS_OF_32(spi_id_min, spi_id_max) \
SPI_BLOCKS_TILL_MAX(spi_id_max) ^ \
SPI_BLOCKS_TILL_MIN(spi_id_min)
/*******************************************************************************
* GIC-600 multichip operation related helper functions
******************************************************************************/
static inline uint32_t read_gicd_dchipr(uintptr_t base)
{
return mmio_read_32(base + GICD_DCHIPR);
}
static inline uint64_t read_gicd_chipr_n(uintptr_t base, uint8_t n)
{
return mmio_read_64(base + (GICD_CHIPR + (8U * n)));
}
static inline uint32_t read_gicd_chipsr(uintptr_t base)
{
return mmio_read_32(base + GICD_CHIPSR);
}
static inline void write_gicd_dchipr(uintptr_t base, uint32_t val)
{
mmio_write_32(base + GICD_DCHIPR, val);
}
static inline void write_gicd_chipr_n(uintptr_t base, uint8_t n, uint64_t val)
{
mmio_write_64(base + (GICD_CHIPR + (8U * n)), val);
}
#endif /* GIC600_MULTICHIP_PRIVATE_H */
@@ -0,0 +1,384 @@
/*
* Copyright (c) 2021-2022, NVIDIA Corporation. All rights reserved.
*
* SPDX-License-Identifier: BSD-3-Clause
*/
/*
* Driver for GIC-600AE Fault Management Unit
*/
#include <assert.h>
#include <inttypes.h>
#include <arch_helpers.h>
#include <common/debug.h>
#include <drivers/arm/gic600ae_fmu.h>
#include <drivers/arm/gicv3.h>
/* GIC-600 AE FMU specific register offsets */
/* GIC-600 AE FMU specific macros */
#define FMU_ERRIDR_NUM U(44)
#define FMU_ERRIDR_NUM_MASK U(0xFFFF)
/* Safety mechanisms for GICD block */
static char *gicd_sm_info[] = {
"Reserved",
"GICD dual lockstep error",
"GICD AXI4 slave interface error",
"GICD-PPI AXI4-Stream interface error",
"GICD-ITS AXI4-Stream interface error",
"GICD-SPI-Collator AXI4-Stream interface error",
"GICD AXI4 master interface error",
"SPI RAM DED error",
"SGI RAM DED error",
"Reserved",
"LPI RAM DED error",
"GICD-remote-GICD AXI4-Stream interface error",
"GICD Q-Channel interface error",
"GICD P-Channel interface error",
"SPI RAM address decode error",
"SGI RAM address decode error",
"Reserved",
"LPI RAM address decode error",
"FMU dual lockstep error",
"FMU ping ACK error",
"FMU APB parity error",
"GICD-Wake AXI4-Stream interface error",
"GICD PageOffset or Chip ID error",
"MBIST REQ error",
"SPI RAM SEC error",
"SGI RAM SEC error",
"Reserved",
"LPI RAM SEC error",
"User custom SM0 error",
"User custom SM1 error",
"GICD-ITS Monolithic switch error",
"GICD-ITS Q-Channel interface error",
"GICD-ITS Monolithic interface error",
"GICD FMU ClkGate override"
};
/* Safety mechanisms for PPI block */
static char *ppi_sm_info[] = {
"Reserved",
"PPI dual lockstep error",
"PPI-GICD AXI4-Stream interface error",
"PPI-CPU-IF AXI4-Stream interface error",
"PPI Q-Channel interface error",
"PPI RAM DED error",
"PPI RAM address decode error",
"PPI RAM SEC error",
"PPI User0 SM",
"PPI User1 SM",
"MBIST REQ error",
"PPI interrupt parity protection error",
"PPI FMU ClkGate override"
};
/* Safety mechanisms for ITS block */
static char *its_sm_info[] = {
"Reserved",
"ITS dual lockstep error",
"ITS-GICD AXI4-Stream interface error",
"ITS AXI4 slave interface error",
"ITS AXI4 master interface error",
"ITS Q-Channel interface error",
"ITS RAM DED error",
"ITS RAM address decode error",
"Bypass ACE switch error",
"ITS RAM SEC error",
"ITS User0 SM",
"ITS User1 SM",
"ITS-GICD Monolithic interface error",
"MBIST REQ error",
"ITS FMU ClkGate override"
};
/* Safety mechanisms for SPI Collator block */
static char *spicol_sm_info[] = {
"Reserved",
"SPI Collator dual lockstep error",
"SPI-Collator-GICD AXI4-Stream interface error",
"SPI Collator Q-Channel interface error",
"SPI Collator Q-Channel clock error",
"SPI interrupt parity error"
};
/* Safety mechanisms for Wake Request block */
static char *wkrqst_sm_info[] = {
"Reserved",
"Wake dual lockstep error",
"Wake-GICD AXI4-Stream interface error"
};
/* Helper function to find detailed information for a specific IERR */
static char __unused *ras_ierr_to_str(unsigned int blkid, unsigned int ierr)
{
char *str = NULL;
/* Find the correct record */
switch (blkid) {
case FMU_BLK_GICD:
assert(ierr < ARRAY_SIZE(gicd_sm_info));
str = gicd_sm_info[ierr];
break;
case FMU_BLK_SPICOL:
assert(ierr < ARRAY_SIZE(spicol_sm_info));
str = spicol_sm_info[ierr];
break;
case FMU_BLK_WAKERQ:
assert(ierr < ARRAY_SIZE(wkrqst_sm_info));
str = wkrqst_sm_info[ierr];
break;
case FMU_BLK_ITS0...FMU_BLK_ITS7:
assert(ierr < ARRAY_SIZE(its_sm_info));
str = its_sm_info[ierr];
break;
case FMU_BLK_PPI0...FMU_BLK_PPI31:
assert(ierr < ARRAY_SIZE(ppi_sm_info));
str = ppi_sm_info[ierr];
break;
default:
assert(false);
break;
}
return str;
}
/*
* Probe for error in memory-mapped registers containing error records.
* Upon detecting an error, set probe data to the index of the record
* in error, and return 1; otherwise, return 0.
*/
int gic600_fmu_probe(uint64_t base, int *probe_data)
{
uint64_t gsr;
assert(base != 0UL);
/*
* Read ERR_GSR to find the error record 'M'
*/
gsr = gic_fmu_read_errgsr(base);
if (gsr == U(0)) {
return 0;
}
/* Return the index of the record in error */
if (probe_data != NULL) {
*probe_data = (int)__builtin_ctzll(gsr);
}
return 1;
}
/*
* The handler function to read RAS records and find the safety
* mechanism with the error.
*/
int gic600_fmu_ras_handler(uint64_t base, int probe_data)
{
uint64_t errstatus;
unsigned int blkid = (unsigned int)probe_data, ierr, serr;
assert(base != 0UL);
/*
* FMU_ERRGSR indicates the ID of the GIC
* block that faulted.
*/
assert(blkid <= FMU_BLK_PPI31);
/*
* Find more information by reading FMU_ERR<M>STATUS
* register
*/
errstatus = gic_fmu_read_errstatus(base, blkid);
/*
* If FMU_ERR<M>STATUS.V is set to 0, no RAS records
* need to be scanned.
*/
if ((errstatus & FMU_ERRSTATUS_V_BIT) == U(0)) {
return 0;
}
/*
* FMU_ERR<M>STATUS.IERR indicates which Safety Mechanism
* reported the error.
*/
ierr = (errstatus >> FMU_ERRSTATUS_IERR_SHIFT) &
FMU_ERRSTATUS_IERR_MASK;
/*
* FMU_ERR<M>STATUS.SERR indicates architecturally
* defined primary error code.
*/
serr = errstatus & FMU_ERRSTATUS_SERR_MASK;
ERROR("**************************************\n");
ERROR("RAS %s Error detected by GIC600 AE FMU\n",
((errstatus & FMU_ERRSTATUS_UE_BIT) != 0U) ?
"Uncorrectable" : "Corrected");
ERROR("\tStatus = 0x%lx \n", errstatus);
ERROR("\tBlock ID = 0x%x\n", blkid);
ERROR("\tSafety Mechanism ID = 0x%x (%s)\n", ierr,
ras_ierr_to_str(blkid, ierr));
ERROR("\tArchitecturally defined primary error code = 0x%x\n",
serr);
ERROR("**************************************\n");
/* Clear FMU_ERR<M>STATUS */
gic_fmu_write_errstatus(base, probe_data, errstatus);
return 0;
}
/*
* Initialization sequence for the FMU
*
* 1. enable error detection for error records that are passed in the blk_present_mask
* 2. enable MBIST REQ and FMU Clk Gate override safety mechanisms for error records
* that are present on the platform
*
* The platforms are expected to pass `errctlr_ce_en` and `errctlr_ue_en`.
*/
void gic600_fmu_init(uint64_t base, uint64_t blk_present_mask,
bool errctlr_ce_en, bool errctlr_ue_en)
{
unsigned int num_blk = gic_fmu_read_erridr(base) & FMU_ERRIDR_NUM_MASK;
uint64_t errctlr;
uint32_t smen;
INFO("GIC600-AE FMU supports %d error records\n", num_blk);
assert(num_blk == FMU_ERRIDR_NUM);
/* sanitize block present mask */
blk_present_mask &= FMU_BLK_PRESENT_MASK;
/* Enable error detection for all error records */
for (unsigned int i = 0U; i < num_blk; i++) {
/*
* Disable all safety mechanisms for blocks that are not
* present and skip the next steps.
*/
if ((blk_present_mask & BIT(i)) == 0U) {
gic_fmu_disable_all_sm_blkid(base, i);
continue;
}
/* Read the error record control register */
errctlr = gic_fmu_read_errctlr(base, i);
/* Enable error reporting and logging, if it is disabled */
if ((errctlr & FMU_ERRCTLR_ED_BIT) == 0U) {
errctlr |= FMU_ERRCTLR_ED_BIT;
}
/* Enable client provided ERRCTLR settings */
errctlr |= (errctlr_ce_en ? (FMU_ERRCTLR_CI_BIT | FMU_ERRCTLR_CE_EN_BIT) : 0);
errctlr |= (errctlr_ue_en ? FMU_ERRCTLR_UI_BIT : 0U);
gic_fmu_write_errctlr(base, i, errctlr);
}
/*
* Enable MBIST REQ error and FMU CLK gate override safety mechanisms for
* all blocks
*
* GICD, SMID 23 and SMID 33
* PPI, SMID 10 and SMID 12
* ITS, SMID 13 and SMID 14
*/
if ((blk_present_mask & BIT(FMU_BLK_GICD)) != 0U) {
smen = (GICD_MBIST_REQ_ERROR << FMU_SMEN_SMID_SHIFT) |
(FMU_BLK_GICD << FMU_SMEN_BLK_SHIFT) |
FMU_SMEN_EN_BIT;
gic_fmu_write_smen(base, smen);
smen = (GICD_FMU_CLKGATE_ERROR << FMU_SMEN_SMID_SHIFT) |
(FMU_BLK_GICD << FMU_SMEN_BLK_SHIFT) |
FMU_SMEN_EN_BIT;
gic_fmu_write_smen(base, smen);
}
for (unsigned int i = FMU_BLK_PPI0; i < FMU_BLK_PPI31; i++) {
if ((blk_present_mask & BIT(i)) != 0U) {
smen = (PPI_MBIST_REQ_ERROR << FMU_SMEN_SMID_SHIFT) |
(i << FMU_SMEN_BLK_SHIFT) |
FMU_SMEN_EN_BIT;
gic_fmu_write_smen(base, smen);
smen = (PPI_FMU_CLKGATE_ERROR << FMU_SMEN_SMID_SHIFT) |
(i << FMU_SMEN_BLK_SHIFT) |
FMU_SMEN_EN_BIT;
gic_fmu_write_smen(base, smen);
}
}
for (unsigned int i = FMU_BLK_ITS0; i < FMU_BLK_ITS7; i++) {
if ((blk_present_mask & BIT(i)) != 0U) {
smen = (ITS_MBIST_REQ_ERROR << FMU_SMEN_SMID_SHIFT) |
(i << FMU_SMEN_BLK_SHIFT) |
FMU_SMEN_EN_BIT;
gic_fmu_write_smen(base, smen);
smen = (ITS_FMU_CLKGATE_ERROR << FMU_SMEN_SMID_SHIFT) |
(i << FMU_SMEN_BLK_SHIFT) |
FMU_SMEN_EN_BIT;
gic_fmu_write_smen(base, smen);
}
}
}
/*
* This function enable the GICD background ping engine. The GICD sends ping
* messages to each remote GIC block, and expects a PING_ACK back within the
* specified timeout. Pings need to be enabled after programming the timeout
* value.
*/
void gic600_fmu_enable_ping(uint64_t base, uint64_t blk_present_mask,
unsigned int timeout_val, unsigned int interval_diff)
{
/*
* Populate the PING Mask to skip a specific block while generating
* background ping messages and enable the ping mechanism.
*/
gic_fmu_write_pingmask(base, ~blk_present_mask);
gic_fmu_write_pingctlr(base, (interval_diff << FMU_PINGCTLR_INTDIFF_SHIFT) |
(timeout_val << FMU_PINGCTLR_TIMEOUTVAL_SHIFT) | FMU_PINGCTLR_EN_BIT);
}
/* Print the safety mechanism description for a given block */
void gic600_fmu_print_sm_info(uint64_t base, unsigned int blk, unsigned int smid)
{
if (blk == FMU_BLK_GICD && smid <= FMU_SMID_GICD_MAX) {
INFO("GICD, SMID %d: %s\n", smid, gicd_sm_info[smid]);
}
if (blk == FMU_BLK_SPICOL && smid <= FMU_SMID_SPICOL_MAX) {
INFO("SPI Collator, SMID %d: %s\n", smid, spicol_sm_info[smid]);
}
if (blk == FMU_BLK_WAKERQ && (smid <= FMU_SMID_WAKERQ_MAX)) {
INFO("Wake Request, SMID %d: %s\n", smid, wkrqst_sm_info[smid]);
}
if (((blk >= FMU_BLK_ITS0) && (blk <= FMU_BLK_ITS7)) && (smid <= FMU_SMID_ITS_MAX)) {
INFO("ITS, SMID %d: %s\n", smid, its_sm_info[smid]);
}
if (((blk >= FMU_BLK_PPI0) && (blk <= FMU_BLK_PPI31)) && (smid <= FMU_SMID_PPI_MAX)) {
INFO("PPI, SMID %d: %s\n", smid, ppi_sm_info[smid]);
}
}
@@ -0,0 +1,304 @@
/*
* Copyright (c) 2021-2022, NVIDIA Corporation. All rights reserved.
*
* SPDX-License-Identifier: BSD-3-Clause
*/
#include <assert.h>
#include <errno.h>
#include <arch.h>
#include <arch_helpers.h>
#include <drivers/arm/gic600ae_fmu.h>
#include <drivers/delay_timer.h>
#include <lib/mmio.h>
#define GICFMU_IDLE_TIMEOUT_US U(2000000)
/* Macro to write 32-bit FMU registers */
#define GIC_FMU_WRITE_32(base, reg, val) \
do { \
/* \
* This register receives the unlock key that is required for \
* writes to FMU registers to be successful. \
*/ \
mmio_write_32(base + GICFMU_KEY, 0xBE); \
/* Perform the actual write */ \
mmio_write_32((base) + (reg), (val)); \
} while (false)
/* Macro to write 64-bit FMU registers */
#define GIC_FMU_WRITE_64(base, reg, n, val) \
do { \
/* \
* This register receives the unlock key that is required for \
* writes to FMU registers to be successful. \
*/ \
mmio_write_32(base + GICFMU_KEY, 0xBE); \
/* \
* APB bus is 32-bit wide; so split the 64-bit write into \
* two 32-bit writes \
*/ \
mmio_write_32((base) + reg##_LO + (n * 64), (val)); \
mmio_write_32((base) + reg##_HI + (n * 64), (val)); \
} while (false)
/* Helper function to wait until FMU is ready to accept the next command */
static void wait_until_fmu_is_idle(uintptr_t base)
{
uint32_t timeout_count = GICFMU_IDLE_TIMEOUT_US;
uint64_t status;
/* wait until status is 'busy' */
do {
status = (gic_fmu_read_status(base) & BIT(0));
if (timeout_count-- == 0U) {
ERROR("GIC600 AE FMU is not responding\n");
panic();
}
udelay(1U);
} while (status == U(0));
}
#define GIC_FMU_WRITE_ON_IDLE_32(base, reg, val) \
do { \
/* Wait until FMU is ready */ \
wait_until_fmu_is_idle(base); \
/* Actual register write */ \
GIC_FMU_WRITE_32(base, reg, val); \
/* Wait until FMU is ready */ \
wait_until_fmu_is_idle(base); \
} while (false)
#define GIC_FMU_WRITE_ON_IDLE_64(base, reg, n, val) \
do { \
/* Wait until FMU is ready */ \
wait_until_fmu_is_idle(base); \
/* Actual register write */ \
GIC_FMU_WRITE_64(base, reg, n, val); \
/* Wait until FMU is ready */ \
wait_until_fmu_is_idle(base); \
} while (false)
/*******************************************************************************
* GIC FMU functions for accessing the Fault Management Unit registers
******************************************************************************/
/*
* Accessors to read the Error Record Feature Register bits corresponding
* to an error record 'n'
*/
uint64_t gic_fmu_read_errfr(uintptr_t base, unsigned int n)
{
/*
* APB bus is 32-bit wide; so split the 64-bit read into
* two 32-bit reads
*/
uint64_t reg_val = (uint64_t)mmio_read_32(base + GICFMU_ERRFR_LO + n * 64U);
reg_val |= ((uint64_t)mmio_read_32(base + GICFMU_ERRFR_HI + n * 64U) << 32);
return reg_val;
}
/*
* Accessors to read the Error Record Control Register bits corresponding
* to an error record 'n'
*/
uint64_t gic_fmu_read_errctlr(uintptr_t base, unsigned int n)
{
/*
* APB bus is 32-bit wide; so split the 64-bit read into
* two 32-bit reads
*/
uint64_t reg_val = (uint64_t)mmio_read_32(base + GICFMU_ERRCTLR_LO + n * 64U);
reg_val |= ((uint64_t)mmio_read_32(base + GICFMU_ERRCTLR_HI + n * 64U) << 32);
return reg_val;
}
/*
* Accessors to read the Error Record Primary Status Register bits
* corresponding to an error record 'n'
*/
uint64_t gic_fmu_read_errstatus(uintptr_t base, unsigned int n)
{
/*
* APB bus is 32-bit wide; so split the 64-bit read into
* two 32-bit reads
*/
uint64_t reg_val = (uint64_t)mmio_read_32(base + GICFMU_ERRSTATUS_LO + n * 64U);
reg_val |= ((uint64_t)mmio_read_32(base + GICFMU_ERRSTATUS_HI + n * 64U) << 32);
return reg_val;
}
/*
* Accessors to read the Error Group Status Register
*/
uint64_t gic_fmu_read_errgsr(uintptr_t base)
{
/*
* APB bus is 32-bit wide; so split the 64-bit read into
* two 32-bit reads
*/
uint64_t reg_val = (uint64_t)mmio_read_32(base + GICFMU_ERRGSR_LO);
reg_val |= ((uint64_t)mmio_read_32(base + GICFMU_ERRGSR_HI) << 32);
return reg_val;
}
/*
* Accessors to read the Ping Control Register
*/
uint32_t gic_fmu_read_pingctlr(uintptr_t base)
{
return mmio_read_32(base + GICFMU_PINGCTLR);
}
/*
* Accessors to read the Ping Now Register
*/
uint32_t gic_fmu_read_pingnow(uintptr_t base)
{
return mmio_read_32(base + GICFMU_PINGNOW);
}
/*
* Accessors to read the Ping Mask Register
*/
uint64_t gic_fmu_read_pingmask(uintptr_t base)
{
/*
* APB bus is 32-bit wide; so split the 64-bit read into
* two 32-bit reads
*/
uint64_t reg_val = (uint64_t)mmio_read_32(base + GICFMU_PINGMASK_LO);
reg_val |= ((uint64_t)mmio_read_32(base + GICFMU_PINGMASK_HI) << 32);
return reg_val;
}
/*
* Accessors to read the FMU Status Register
*/
uint32_t gic_fmu_read_status(uintptr_t base)
{
return mmio_read_32(base + GICFMU_STATUS);
}
/*
* Accessors to read the Error Record ID Register
*/
uint32_t gic_fmu_read_erridr(uintptr_t base)
{
return mmio_read_32(base + GICFMU_ERRIDR);
}
/*
* Accessors to write a 64 bit value to the Error Record Control Register
*/
void gic_fmu_write_errctlr(uintptr_t base, unsigned int n, uint64_t val)
{
GIC_FMU_WRITE_64(base, GICFMU_ERRCTLR, n, val);
}
/*
* Accessors to write a 64 bit value to the Error Record Primary Status
* Register
*/
void gic_fmu_write_errstatus(uintptr_t base, unsigned int n, uint64_t val)
{
/* Wait until FMU is ready before writing */
GIC_FMU_WRITE_ON_IDLE_64(base, GICFMU_ERRSTATUS, n, val);
}
/*
* Accessors to write a 32 bit value to the Ping Control Register
*/
void gic_fmu_write_pingctlr(uintptr_t base, uint32_t val)
{
GIC_FMU_WRITE_32(base, GICFMU_PINGCTLR, val);
}
/*
* Accessors to write a 32 bit value to the Ping Now Register
*/
void gic_fmu_write_pingnow(uintptr_t base, uint32_t val)
{
/* Wait until FMU is ready before writing */
GIC_FMU_WRITE_ON_IDLE_32(base, GICFMU_PINGNOW, val);
}
/*
* Accessors to write a 32 bit value to the Safety Mechanism Enable Register
*/
void gic_fmu_write_smen(uintptr_t base, uint32_t val)
{
/* Wait until FMU is ready before writing */
GIC_FMU_WRITE_ON_IDLE_32(base, GICFMU_SMEN, val);
}
/*
* Accessors to write a 32 bit value to the Safety Mechanism Inject Error
* Register
*/
void gic_fmu_write_sminjerr(uintptr_t base, uint32_t val)
{
/* Wait until FMU is ready before writing */
GIC_FMU_WRITE_ON_IDLE_32(base, GICFMU_SMINJERR, val);
}
/*
* Accessors to write a 64 bit value to the Ping Mask Register
*/
void gic_fmu_write_pingmask(uintptr_t base, uint64_t val)
{
GIC_FMU_WRITE_64(base, GICFMU_PINGMASK, 0, val);
}
/*
* Helper function to disable all safety mechanisms for a given block
*/
void gic_fmu_disable_all_sm_blkid(uintptr_t base, unsigned int blkid)
{
uint32_t smen, max_smid = U(0);
/* Sanity check block ID */
assert((blkid >= FMU_BLK_GICD) && (blkid <= FMU_BLK_PPI31));
/* Find the max safety mechanism ID for the block */
switch (blkid) {
case FMU_BLK_GICD:
max_smid = FMU_SMID_GICD_MAX;
break;
case FMU_BLK_SPICOL:
max_smid = FMU_SMID_SPICOL_MAX;
break;
case FMU_BLK_WAKERQ:
max_smid = FMU_SMID_WAKERQ_MAX;
break;
case FMU_BLK_ITS0...FMU_BLK_ITS7:
max_smid = FMU_SMID_ITS_MAX;
break;
case FMU_BLK_PPI0...FMU_BLK_PPI31:
max_smid = FMU_SMID_PPI_MAX;
break;
default:
assert(false);
break;
}
/* Disable all Safety Mechanisms for a given block id */
for (unsigned int i = 0U; i < max_smid; i++) {
smen = (blkid << FMU_SMEN_BLK_SHIFT) | (i << FMU_SMEN_SMID_SHIFT);
gic_fmu_write_smen(base, smen);
}
}
@@ -0,0 +1,244 @@
/*
* Copyright (c) 2015-2020, ARM Limited and Contributors. All rights reserved.
*
* SPDX-License-Identifier: BSD-3-Clause
*/
#include <stdint.h>
#include "gicv3_private.h"
/*******************************************************************************
* GIC Distributor functions for accessing the GIC registers
* corresponding to a single interrupt ID. These functions use bitwise
* operations or appropriate register accesses to modify or return
* the bit-field corresponding the single interrupt ID.
******************************************************************************/
/*
* Accessors to set the bits corresponding to interrupt ID
* in GIC Distributor ICFGR and ICFGRE.
*/
void gicd_set_icfgr(uintptr_t base, unsigned int id, unsigned int cfg)
{
/* Interrupt configuration is a 2-bit field */
unsigned int bit_shift = BIT_NUM(ICFG, id) << 1U;
/* Clear the field, and insert required configuration */
mmio_clrsetbits_32(base + GICD_OFFSET(ICFG, id),
(uint32_t)GIC_CFG_MASK << bit_shift,
(cfg & GIC_CFG_MASK) << bit_shift);
}
/*
* Accessors to get/set/clear the bit corresponding to interrupt ID
* in GIC Distributor IGROUPR and IGROUPRE.
*/
unsigned int gicd_get_igroupr(uintptr_t base, unsigned int id)
{
return GICD_GET_BIT(IGROUP, base, id);
}
void gicd_set_igroupr(uintptr_t base, unsigned int id)
{
GICD_SET_BIT(IGROUP, base, id);
}
void gicd_clr_igroupr(uintptr_t base, unsigned int id)
{
GICD_CLR_BIT(IGROUP, base, id);
}
/*
* Accessors to get/set/clear the bit corresponding to interrupt ID
* in GIC Distributor IGRPMODR and IGRPMODRE.
*/
unsigned int gicd_get_igrpmodr(uintptr_t base, unsigned int id)
{
return GICD_GET_BIT(IGRPMOD, base, id);
}
void gicd_set_igrpmodr(uintptr_t base, unsigned int id)
{
GICD_SET_BIT(IGRPMOD, base, id);
}
void gicd_clr_igrpmodr(uintptr_t base, unsigned int id)
{
GICD_CLR_BIT(IGRPMOD, base, id);
}
/*
* Accessors to set the bit corresponding to interrupt ID
* in GIC Distributor ICENABLER and ICENABLERE.
*/
void gicd_set_icenabler(uintptr_t base, unsigned int id)
{
GICD_WRITE_BIT(ICENABLE, base, id);
}
/*
* Accessors to set the bit corresponding to interrupt ID
* in GIC Distributor ICPENDR and ICPENDRE.
*/
void gicd_set_icpendr(uintptr_t base, unsigned int id)
{
GICD_WRITE_BIT(ICPEND, base, id);
}
/*
* Accessors to get/set the bit corresponding to interrupt ID
* in GIC Distributor ISACTIVER and ISACTIVERE.
*/
unsigned int gicd_get_isactiver(uintptr_t base, unsigned int id)
{
return GICD_GET_BIT(ISACTIVE, base, id);
}
void gicd_set_isactiver(uintptr_t base, unsigned int id)
{
GICD_WRITE_BIT(ISACTIVE, base, id);
}
/*
* Accessors to set the bit corresponding to interrupt ID
* in GIC Distributor ISENABLER and ISENABLERE.
*/
void gicd_set_isenabler(uintptr_t base, unsigned int id)
{
GICD_WRITE_BIT(ISENABLE, base, id);
}
/*
* Accessors to set the bit corresponding to interrupt ID
* in GIC Distributor ISPENDR and ISPENDRE.
*/
void gicd_set_ispendr(uintptr_t base, unsigned int id)
{
GICD_WRITE_BIT(ISPEND, base, id);
}
/*
* Accessors to set the bit corresponding to interrupt ID
* in GIC Distributor IPRIORITYR and IPRIORITYRE.
*/
void gicd_set_ipriorityr(uintptr_t base, unsigned int id, unsigned int pri)
{
GICD_WRITE_8(IPRIORITY, base, id, (uint8_t)(pri & GIC_PRI_MASK));
}
/*******************************************************************************
* GIC Distributor interface accessors for reading/writing entire registers
******************************************************************************/
/*
* Accessors to read/write the GIC Distributor ICGFR and ICGFRE
* corresponding to the interrupt ID, 16 interrupt IDs at a time.
*/
unsigned int gicd_read_icfgr(uintptr_t base, unsigned int id)
{
return GICD_READ(ICFG, base, id);
}
void gicd_write_icfgr(uintptr_t base, unsigned int id, unsigned int val)
{
GICD_WRITE(ICFG, base, id, val);
}
/*
* Accessors to read/write the GIC Distributor IGROUPR and IGROUPRE
* corresponding to the interrupt ID, 32 interrupt IDs at a time.
*/
unsigned int gicd_read_igroupr(uintptr_t base, unsigned int id)
{
return GICD_READ(IGROUP, base, id);
}
void gicd_write_igroupr(uintptr_t base, unsigned int id, unsigned int val)
{
GICD_WRITE(IGROUP, base, id, val);
}
/*
* Accessors to read/write the GIC Distributor IGRPMODR and IGRPMODRE
* corresponding to the interrupt ID, 32 interrupt IDs at a time.
*/
unsigned int gicd_read_igrpmodr(uintptr_t base, unsigned int id)
{
return GICD_READ(IGRPMOD, base, id);
}
void gicd_write_igrpmodr(uintptr_t base, unsigned int id, unsigned int val)
{
GICD_WRITE(IGRPMOD, base, id, val);
}
/*
* Accessors to read/write the GIC Distributor IPRIORITYR and IPRIORITYRE
* corresponding to the interrupt ID, 4 interrupt IDs at a time.
*/
unsigned int gicd_read_ipriorityr(uintptr_t base, unsigned int id)
{
return GICD_READ(IPRIORITY, base, id);
}
void gicd_write_ipriorityr(uintptr_t base, unsigned int id, unsigned int val)
{
GICD_WRITE(IPRIORITY, base, id, val);
}
/*
* Accessors to read/write the GIC Distributor ISACTIVER and ISACTIVERE
* corresponding to the interrupt ID, 32 interrupt IDs at a time.
*/
unsigned int gicd_read_isactiver(uintptr_t base, unsigned int id)
{
return GICD_READ(ISACTIVE, base, id);
}
void gicd_write_isactiver(uintptr_t base, unsigned int id, unsigned int val)
{
GICD_WRITE(ISACTIVE, base, id, val);
}
/*
* Accessors to read/write the GIC Distributor ISENABLER and ISENABLERE
* corresponding to the interrupt ID, 32 interrupt IDs at a time.
*/
unsigned int gicd_read_isenabler(uintptr_t base, unsigned int id)
{
return GICD_READ(ISENABLE, base, id);
}
void gicd_write_isenabler(uintptr_t base, unsigned int id, unsigned int val)
{
GICD_WRITE(ISENABLE, base, id, val);
}
/*
* Accessors to read/write the GIC Distributor ISPENDR and ISPENDRE
* corresponding to the interrupt ID, 32 interrupt IDs at a time.
*/
unsigned int gicd_read_ispendr(uintptr_t base, unsigned int id)
{
return GICD_READ(ISPEND, base, id);
}
void gicd_write_ispendr(uintptr_t base, unsigned int id, unsigned int val)
{
GICD_WRITE(ISPEND, base, id, val);
}
/*
* Accessors to read/write the GIC Distributor NSACR and NSACRE
* corresponding to the interrupt ID, 16 interrupt IDs at a time.
*/
unsigned int gicd_read_nsacr(uintptr_t base, unsigned int id)
{
return GICD_READ(NSAC, base, id);
}
void gicd_write_nsacr(uintptr_t base, unsigned int id, unsigned int val)
{
GICD_WRITE(NSAC, base, id, val);
}
@@ -0,0 +1,139 @@
/*
* Copyright (c) 2015-2020, Arm Limited and Contributors. All rights reserved.
*
* SPDX-License-Identifier: BSD-3-Clause
*/
#include <arch.h>
#include <arch_helpers.h>
#include <common/debug.h>
#include <common/interrupt_props.h>
#include <drivers/arm/gicv3.h>
#include "gicv3_private.h"
/*******************************************************************************
* GIC Redistributor functions
* Note: The raw register values correspond to multiple interrupt `id`s and
* the number of interrupt `id`s involved depends on the register accessed.
******************************************************************************/
/*
* Accessors to read/write the GIC Redistributor IPRIORITYR and IPRIORITYRE
* register corresponding to the interrupt `id`, 4 interrupts IDs at a time.
*/
unsigned int gicr_read_ipriorityr(uintptr_t base, unsigned int id)
{
return GICR_READ(IPRIORITY, base, id);
}
void gicr_write_ipriorityr(uintptr_t base, unsigned int id, unsigned int val)
{
GICR_WRITE(IPRIORITY, base, id, val);
}
/*
* Accessor to set the byte corresponding to interrupt `id`
* in GIC Redistributor IPRIORITYR and IPRIORITYRE.
*/
void gicr_set_ipriorityr(uintptr_t base, unsigned int id, unsigned int pri)
{
GICR_WRITE_8(IPRIORITY, base, id, (uint8_t)(pri & GIC_PRI_MASK));
}
/*
* Accessors to get/set/clear the bit corresponding to interrupt `id`
* from GIC Redistributor IGROUPR0 and IGROUPRE
*/
unsigned int gicr_get_igroupr(uintptr_t base, unsigned int id)
{
return GICR_GET_BIT(IGROUP, base, id);
}
void gicr_set_igroupr(uintptr_t base, unsigned int id)
{
GICR_SET_BIT(IGROUP, base, id);
}
void gicr_clr_igroupr(uintptr_t base, unsigned int id)
{
GICR_CLR_BIT(IGROUP, base, id);
}
/*
* Accessors to get/set/clear the bit corresponding to interrupt `id`
* from GIC Redistributor IGRPMODR0 and IGRPMODRE
*/
unsigned int gicr_get_igrpmodr(uintptr_t base, unsigned int id)
{
return GICR_GET_BIT(IGRPMOD, base, id);
}
void gicr_set_igrpmodr(uintptr_t base, unsigned int id)
{
GICR_SET_BIT(IGRPMOD, base, id);
}
void gicr_clr_igrpmodr(uintptr_t base, unsigned int id)
{
GICR_CLR_BIT(IGRPMOD, base, id);
}
/*
* Accessor to write the bit corresponding to interrupt `id`
* in GIC Redistributor ISENABLER0 and ISENABLERE
*/
void gicr_set_isenabler(uintptr_t base, unsigned int id)
{
GICR_WRITE_BIT(ISENABLE, base, id);
}
/*
* Accessor to write the bit corresponding to interrupt `id`
* in GIC Redistributor ICENABLER0 and ICENABLERE
*/
void gicr_set_icenabler(uintptr_t base, unsigned int id)
{
GICR_WRITE_BIT(ICENABLE, base, id);
}
/*
* Accessor to get the bit corresponding to interrupt `id`
* in GIC Redistributor ISACTIVER0 and ISACTIVERE
*/
unsigned int gicr_get_isactiver(uintptr_t base, unsigned int id)
{
return GICR_GET_BIT(ISACTIVE, base, id);
}
/*
* Accessor to clear the bit corresponding to interrupt `id`
* in GIC Redistributor ICPENDR0 and ICPENDRE
*/
void gicr_set_icpendr(uintptr_t base, unsigned int id)
{
GICR_WRITE_BIT(ICPEND, base, id);
}
/*
* Accessor to write the bit corresponding to interrupt `id`
* in GIC Redistributor ISPENDR0 and ISPENDRE
*/
void gicr_set_ispendr(uintptr_t base, unsigned int id)
{
GICR_WRITE_BIT(ISPEND, base, id);
}
/*
* Accessor to set the bit fields corresponding to interrupt `id`
* in GIC Redistributor ICFGR0, ICFGR1 and ICFGRE
*/
void gicr_set_icfgr(uintptr_t base, unsigned int id, unsigned int cfg)
{
/* Interrupt configuration is a 2-bit field */
unsigned int bit_shift = BIT_NUM(ICFG, id) << 1U;
/* Clear the field, and insert required configuration */
mmio_clrsetbits_32(base + GICR_OFFSET(ICFG, id),
(uint32_t)GIC_CFG_MASK << bit_shift,
(cfg & GIC_CFG_MASK) << bit_shift);
}
@@ -0,0 +1,58 @@
#
# Copyright (c) 2013-2022, Arm Limited and Contributors. All rights reserved.
# Copyright (c) 2021, NVIDIA Corporation. All rights reserved.
#
# SPDX-License-Identifier: BSD-3-Clause
#
# Default configuration values
GICV3_SUPPORT_GIC600 ?= 0
GICV3_SUPPORT_GIC600AE_FMU ?= 0
GICV3_IMPL_GIC600_MULTICHIP ?= 0
GICV3_OVERRIDE_DISTIF_PWR_OPS ?= 0
GIC_ENABLE_V4_EXTN ?= 0
GIC_EXT_INTID ?= 0
GIC600_ERRATA_WA_2384374 ?= ${GICV3_SUPPORT_GIC600}
GICV3_SOURCES += drivers/arm/gic/v3/gicv3_main.c \
drivers/arm/gic/v3/gicv3_helpers.c \
drivers/arm/gic/v3/gicdv3_helpers.c \
drivers/arm/gic/v3/gicrv3_helpers.c
ifeq (${GICV3_SUPPORT_GIC600AE_FMU}, 1)
GICV3_SOURCES += drivers/arm/gic/v3/gic600ae_fmu.c \
drivers/arm/gic/v3/gic600ae_fmu_helpers.c
endif
ifeq (${GICV3_OVERRIDE_DISTIF_PWR_OPS}, 0)
GICV3_SOURCES += drivers/arm/gic/v3/arm_gicv3_common.c
endif
GICV3_SOURCES += drivers/arm/gic/v3/gic-x00.c
ifeq (${GICV3_IMPL_GIC600_MULTICHIP}, 1)
GICV3_SOURCES += drivers/arm/gic/v3/gic600_multichip.c
endif
# Set GIC-600 support
$(eval $(call assert_boolean,GICV3_SUPPORT_GIC600))
$(eval $(call add_define,GICV3_SUPPORT_GIC600))
# Set GIC-600AE FMU support
$(eval $(call assert_boolean,GICV3_SUPPORT_GIC600AE_FMU))
$(eval $(call add_define,GICV3_SUPPORT_GIC600AE_FMU))
# Set GIC-600 multichip support
$(eval $(call assert_boolean,GICV3_IMPL_GIC600_MULTICHIP))
$(eval $(call add_define,GICV3_IMPL_GIC600_MULTICHIP))
# Set GICv4 extension
$(eval $(call assert_boolean,GIC_ENABLE_V4_EXTN))
$(eval $(call add_define,GIC_ENABLE_V4_EXTN))
# Set support for extended PPI and SPI range
$(eval $(call assert_boolean,GIC_EXT_INTID))
$(eval $(call add_define,GIC_EXT_INTID))
# Set errata workaround for GIC600/GIC600AE
$(eval $(call assert_boolean,GIC600_ERRATA_WA_2384374))
$(eval $(call add_define,GIC600_ERRATA_WA_2384374))
@@ -0,0 +1,457 @@
/*
* Copyright (c) 2015-2022, Arm Limited and Contributors. All rights reserved.
* Copyright (c) 2023, NVIDIA Corporation. All rights reserved.
*
* SPDX-License-Identifier: BSD-3-Clause
*/
#include <assert.h>
#include <arch.h>
#include <arch_helpers.h>
#include <common/debug.h>
#include <common/interrupt_props.h>
#include <drivers/arm/gic600_multichip.h>
#include <drivers/arm/gic_common.h>
#include <platform_def.h>
#include "../common/gic_common_private.h"
#include "gicv3_private.h"
uintptr_t gicv3_get_multichip_base(uint32_t spi_id, uintptr_t gicd_base)
{
#if GICV3_IMPL_GIC600_MULTICHIP
if (gic600_multichip_is_initialized()) {
return gic600_multichip_gicd_base_for_spi(spi_id);
}
#endif
return gicd_base;
}
/******************************************************************************
* This function marks the core as awake in the re-distributor and
* ensures that the interface is active.
*****************************************************************************/
void gicv3_rdistif_mark_core_awake(uintptr_t gicr_base)
{
/*
* The WAKER_PS_BIT should be changed to 0
* only when WAKER_CA_BIT is 1.
*/
assert((gicr_read_waker(gicr_base) & WAKER_CA_BIT) != 0U);
/* Mark the connected core as awake */
gicr_write_waker(gicr_base, gicr_read_waker(gicr_base) & ~WAKER_PS_BIT);
/* Wait till the WAKER_CA_BIT changes to 0 */
while ((gicr_read_waker(gicr_base) & WAKER_CA_BIT) != 0U) {
}
}
/******************************************************************************
* This function marks the core as asleep in the re-distributor and ensures
* that the interface is quiescent.
*****************************************************************************/
void gicv3_rdistif_mark_core_asleep(uintptr_t gicr_base)
{
/* Mark the connected core as asleep */
gicr_write_waker(gicr_base, gicr_read_waker(gicr_base) | WAKER_PS_BIT);
/* Wait till the WAKER_CA_BIT changes to 1 */
while ((gicr_read_waker(gicr_base) & WAKER_CA_BIT) == 0U) {
}
}
/*******************************************************************************
* This function probes the Redistributor frames when the driver is initialised
* and saves their base addresses. These base addresses are used later to
* initialise each Redistributor interface.
******************************************************************************/
void gicv3_rdistif_base_addrs_probe(uintptr_t *rdistif_base_addrs,
unsigned int rdistif_num,
uintptr_t gicr_base,
mpidr_hash_fn mpidr_to_core_pos)
{
u_register_t mpidr;
unsigned int proc_num;
uint64_t typer_val;
uintptr_t rdistif_base = gicr_base;
assert(rdistif_base_addrs != NULL);
/*
* Iterate over the Redistributor frames. Store the base address of each
* frame in the platform provided array. Use the "Processor Number"
* field to index into the array if the platform has not provided a hash
* function to convert an MPIDR (obtained from the "Affinity Value"
* field into a linear index.
*/
do {
typer_val = gicr_read_typer(rdistif_base);
if (mpidr_to_core_pos != NULL) {
mpidr = mpidr_from_gicr_typer(typer_val);
proc_num = mpidr_to_core_pos(mpidr);
} else {
proc_num = (typer_val >> TYPER_PROC_NUM_SHIFT) &
TYPER_PROC_NUM_MASK;
}
if (proc_num < rdistif_num) {
rdistif_base_addrs[proc_num] = rdistif_base;
}
rdistif_base += gicv3_redist_size(typer_val);
} while ((typer_val & TYPER_LAST_BIT) == 0U);
}
/*******************************************************************************
* Helper function to get the maximum SPI INTID + 1.
******************************************************************************/
unsigned int gicv3_get_spi_limit(uintptr_t gicd_base)
{
unsigned int spi_limit;
unsigned int typer_reg = gicd_read_typer(gicd_base);
/* (maximum SPI INTID + 1) is equal to 32 * (GICD_TYPER.ITLinesNumber+1) */
spi_limit = ((typer_reg & TYPER_IT_LINES_NO_MASK) + 1U) << 5;
/* Filter out special INTIDs 1020-1023 */
if (spi_limit > (MAX_SPI_ID + 1U)) {
return MAX_SPI_ID + 1U;
}
return spi_limit;
}
#if GIC_EXT_INTID
/*******************************************************************************
* Helper function to get the maximum ESPI INTID + 1.
******************************************************************************/
unsigned int gicv3_get_espi_limit(uintptr_t gicd_base)
{
unsigned int typer_reg = gicd_read_typer(gicd_base);
/* Check if extended SPI range is implemented */
if ((typer_reg & TYPER_ESPI) != 0U) {
/*
* (maximum ESPI INTID + 1) is equal to
* 32 * (GICD_TYPER.ESPI_range + 1) + 4096
*/
return ((((typer_reg >> TYPER_ESPI_RANGE_SHIFT) &
TYPER_ESPI_RANGE_MASK) + 1U) << 5) + MIN_ESPI_ID;
}
return 0U;
}
#endif /* GIC_EXT_INTID */
/*******************************************************************************
* Helper function to configure the default attributes of (E)SPIs.
******************************************************************************/
void gicv3_spis_config_defaults(uintptr_t gicd_base)
{
unsigned int i, num_ints;
#if GIC_EXT_INTID
unsigned int num_eints;
#endif
num_ints = gicv3_get_spi_limit(gicd_base);
INFO("Maximum SPI INTID supported: %u\n", num_ints - 1);
/* Treat all (E)SPIs as G1NS by default. We do 32 at a time. */
for (i = MIN_SPI_ID; i < num_ints; i += (1U << IGROUPR_SHIFT)) {
gicd_write_igroupr(gicv3_get_multichip_base(i, gicd_base), i, ~0U);
}
#if GIC_EXT_INTID
num_eints = gicv3_get_espi_limit(gicd_base);
if (num_eints != 0U) {
INFO("Maximum ESPI INTID supported: %u\n", num_eints - 1);
for (i = MIN_ESPI_ID; i < num_eints;
i += (1U << IGROUPR_SHIFT)) {
gicd_write_igroupr(gicv3_get_multichip_base(i, gicd_base), i, ~0U);
}
} else {
INFO("ESPI range is not implemented.\n");
}
#endif
/* Setup the default (E)SPI priorities doing four at a time */
for (i = MIN_SPI_ID; i < num_ints; i += (1U << IPRIORITYR_SHIFT)) {
gicd_write_ipriorityr(gicv3_get_multichip_base(i, gicd_base), i, GICD_IPRIORITYR_DEF_VAL);
}
#if GIC_EXT_INTID
for (i = MIN_ESPI_ID; i < num_eints;
i += (1U << IPRIORITYR_SHIFT)) {
gicd_write_ipriorityr(gicv3_get_multichip_base(i, gicd_base), i, GICD_IPRIORITYR_DEF_VAL);
}
#endif
/*
* Treat all (E)SPIs as level triggered by default, write 16 at a time
*/
for (i = MIN_SPI_ID; i < num_ints; i += (1U << ICFGR_SHIFT)) {
gicd_write_icfgr(gicv3_get_multichip_base(i, gicd_base), i, 0U);
}
#if GIC_EXT_INTID
for (i = MIN_ESPI_ID; i < num_eints; i += (1U << ICFGR_SHIFT)) {
gicd_write_icfgr(gicv3_get_multichip_base(i, gicd_base), i, 0U);
}
#endif
}
/*******************************************************************************
* Helper function to configure properties of secure (E)SPIs
******************************************************************************/
unsigned int gicv3_secure_spis_config_props(uintptr_t gicd_base,
const interrupt_prop_t *interrupt_props,
unsigned int interrupt_props_num)
{
unsigned int i;
const interrupt_prop_t *current_prop;
unsigned long long gic_affinity_val;
unsigned int ctlr_enable = 0U;
/* Make sure there's a valid property array */
if (interrupt_props_num > 0U) {
assert(interrupt_props != NULL);
}
for (i = 0U; i < interrupt_props_num; i++) {
current_prop = &interrupt_props[i];
unsigned int intr_num = current_prop->intr_num;
uintptr_t multichip_gicd_base = gicv3_get_multichip_base(intr_num, gicd_base);
/* Skip SGI, (E)PPI and LPI interrupts */
if (!IS_SPI(intr_num)) {
continue;
}
/* Configure this interrupt as a secure interrupt */
gicd_clr_igroupr(multichip_gicd_base, intr_num);
/* Configure this interrupt as G0 or a G1S interrupt */
assert((current_prop->intr_grp == INTR_GROUP0) ||
(current_prop->intr_grp == INTR_GROUP1S));
if (current_prop->intr_grp == INTR_GROUP1S) {
gicd_set_igrpmodr(multichip_gicd_base, intr_num);
ctlr_enable |= CTLR_ENABLE_G1S_BIT;
} else {
gicd_clr_igrpmodr(multichip_gicd_base, intr_num);
ctlr_enable |= CTLR_ENABLE_G0_BIT;
}
/* Set interrupt configuration */
gicd_set_icfgr(multichip_gicd_base, intr_num,
current_prop->intr_cfg);
/* Set the priority of this interrupt */
gicd_set_ipriorityr(multichip_gicd_base, intr_num,
current_prop->intr_pri);
/* Target (E)SPIs to the primary CPU */
gic_affinity_val =
gicd_irouter_val_from_mpidr(read_mpidr(), 0U);
gicd_write_irouter(multichip_gicd_base, intr_num,
gic_affinity_val);
/* Enable this interrupt */
gicd_set_isenabler(multichip_gicd_base, intr_num);
}
return ctlr_enable;
}
/*******************************************************************************
* Helper function to configure the default attributes of (E)SPIs
******************************************************************************/
void gicv3_ppi_sgi_config_defaults(uintptr_t gicr_base)
{
unsigned int i, ppi_regs_num, regs_num;
#if GIC_EXT_INTID
/* Calculate number of PPI registers */
ppi_regs_num = (unsigned int)((gicr_read_typer(gicr_base) >>
TYPER_PPI_NUM_SHIFT) & TYPER_PPI_NUM_MASK) + 1;
/* All other values except PPInum [0-2] are reserved */
if (ppi_regs_num > 3U) {
ppi_regs_num = 1U;
}
#else
ppi_regs_num = 1U;
#endif
/*
* Disable all SGIs (imp. def.)/(E)PPIs before configuring them.
* This is a more scalable approach as it avoids clearing
* the enable bits in the GICD_CTLR.
*/
for (i = 0U; i < ppi_regs_num; ++i) {
gicr_write_icenabler(gicr_base, i, ~0U);
}
/* Wait for pending writes to GICR_ICENABLER */
gicr_wait_for_pending_write(gicr_base);
/* 32 interrupt IDs per GICR_IGROUPR register */
for (i = 0U; i < ppi_regs_num; ++i) {
/* Treat all SGIs/(E)PPIs as G1NS by default */
gicr_write_igroupr(gicr_base, i, ~0U);
}
/* 4 interrupt IDs per GICR_IPRIORITYR register */
regs_num = ppi_regs_num << 3;
for (i = 0U; i < regs_num; ++i) {
/* Setup the default (E)PPI/SGI priorities doing 4 at a time */
gicr_write_ipriorityr(gicr_base, i, GICD_IPRIORITYR_DEF_VAL);
}
/* 16 interrupt IDs per GICR_ICFGR register */
regs_num = ppi_regs_num << 1;
for (i = (MIN_PPI_ID >> ICFGR_SHIFT); i < regs_num; ++i) {
/* Configure all (E)PPIs as level triggered by default */
gicr_write_icfgr(gicr_base, i, 0U);
}
}
/*******************************************************************************
* Helper function to configure properties of secure G0 and G1S (E)PPIs and SGIs
******************************************************************************/
unsigned int gicv3_secure_ppi_sgi_config_props(uintptr_t gicr_base,
const interrupt_prop_t *interrupt_props,
unsigned int interrupt_props_num)
{
unsigned int i;
const interrupt_prop_t *current_prop;
unsigned int ctlr_enable = 0U;
/* Make sure there's a valid property array */
if (interrupt_props_num > 0U) {
assert(interrupt_props != NULL);
}
for (i = 0U; i < interrupt_props_num; i++) {
current_prop = &interrupt_props[i];
unsigned int intr_num = current_prop->intr_num;
/* Skip (E)SPI interrupt */
if (!IS_SGI_PPI(intr_num)) {
continue;
}
/* Configure this interrupt as a secure interrupt */
gicr_clr_igroupr(gicr_base, intr_num);
/* Configure this interrupt as G0 or a G1S interrupt */
assert((current_prop->intr_grp == INTR_GROUP0) ||
(current_prop->intr_grp == INTR_GROUP1S));
if (current_prop->intr_grp == INTR_GROUP1S) {
gicr_set_igrpmodr(gicr_base, intr_num);
ctlr_enable |= CTLR_ENABLE_G1S_BIT;
} else {
gicr_clr_igrpmodr(gicr_base, intr_num);
ctlr_enable |= CTLR_ENABLE_G0_BIT;
}
/* Set the priority of this interrupt */
gicr_set_ipriorityr(gicr_base, intr_num,
current_prop->intr_pri);
/*
* Set interrupt configuration for (E)PPIs.
* Configurations for SGIs 0-15 are ignored.
*/
if (intr_num >= MIN_PPI_ID) {
gicr_set_icfgr(gicr_base, intr_num,
current_prop->intr_cfg);
}
/* Enable this interrupt */
gicr_set_isenabler(gicr_base, intr_num);
}
return ctlr_enable;
}
/**
* gicv3_rdistif_get_number_frames() - determine size of GICv3 GICR region
* @gicr_frame: base address of the GICR region to check
*
* This iterates over the GICR_TYPER registers of multiple GICR frames in
* a GICR region, to find the instance which has the LAST bit set. For most
* systems this corresponds to the number of cores handled by a redistributor,
* but there could be disabled cores among them.
* It assumes that each GICR region is fully accessible (till the LAST bit
* marks the end of the region).
* If a platform has multiple GICR regions, this function would need to be
* called multiple times, providing the respective GICR base address each time.
*
* Return: number of valid GICR frames (at least 1, up to PLATFORM_CORE_COUNT)
******************************************************************************/
unsigned int gicv3_rdistif_get_number_frames(const uintptr_t gicr_frame)
{
uintptr_t rdistif_base = gicr_frame;
unsigned int count;
for (count = 1U; count < PLATFORM_CORE_COUNT; count++) {
uint64_t typer_val = gicr_read_typer(rdistif_base);
if ((typer_val & TYPER_LAST_BIT) != 0U) {
break;
}
rdistif_base += gicv3_redist_size(typer_val);
}
return count;
}
unsigned int gicv3_get_component_partnum(const uintptr_t gic_frame)
{
unsigned int part_id;
/*
* The lower 8 bits of PIDR0, complemented by the lower 4 bits of
* PIDR1 contain a part number identifying the GIC component at a
* particular base address.
*/
part_id = mmio_read_32(gic_frame + GICD_PIDR0_GICV3) & 0xff;
part_id |= (mmio_read_32(gic_frame + GICD_PIDR1_GICV3) << 8) & 0xf00;
return part_id;
}
/*******************************************************************************
* Helper function to return product ID and revision of GIC
* @gicd_base: base address of the GIC distributor
* @gic_prod_id: retrieved product id of GIC
* @gic_rev: retrieved revision of GIC
******************************************************************************/
void gicv3_get_component_prodid_rev(const uintptr_t gicd_base,
unsigned int *gic_prod_id,
uint8_t *gic_rev)
{
unsigned int gicd_iidr;
uint8_t gic_variant;
gicd_iidr = gicd_read_iidr(gicd_base);
*gic_prod_id = gicd_iidr >> IIDR_PRODUCT_ID_SHIFT;
*gic_prod_id &= IIDR_PRODUCT_ID_MASK;
gic_variant = gicd_iidr >> IIDR_VARIANT_SHIFT;
gic_variant &= IIDR_VARIANT_MASK;
*gic_rev = gicd_iidr >> IIDR_REV_SHIFT;
*gic_rev &= IIDR_REV_MASK;
/*
* pack gic variant and gic_rev in 1 byte
* gic_rev = gic_variant[7:4] and gic_rev[0:3]
*/
*gic_rev = *gic_rev | gic_variant << 0x4;
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,709 @@
/*
* Copyright (c) 2015-2021, ARM Limited and Contributors. All rights reserved.
* Copyright (c) 2023, NVIDIA Corporation. All rights reserved.
*
* SPDX-License-Identifier: BSD-3-Clause
*/
#ifndef GICV3_PRIVATE_H
#define GICV3_PRIVATE_H
#include <assert.h>
#include <stdint.h>
#include <drivers/arm/gic_common.h>
#include <drivers/arm/gicv3.h>
#include <lib/mmio.h>
#include "../common/gic_common_private.h"
/*******************************************************************************
* GICv3 private macro definitions
******************************************************************************/
/* Constants to indicate the status of the RWP bit */
#define RWP_TRUE U(1)
#define RWP_FALSE U(0)
/* Calculate GIC register bit number corresponding to its interrupt ID */
#define BIT_NUM(REG, id) \
((id) & ((1U << REG##R_SHIFT) - 1U))
/*
* Calculate 8, 32 and 64-bit GICD register offset
* corresponding to its interrupt ID
*/
#if GIC_EXT_INTID
/* GICv3.1 */
#define GICD_OFFSET_8(REG, id) \
(((id) <= MAX_SPI_ID) ? \
GICD_##REG##R + (uintptr_t)(id) : \
GICD_##REG##RE + (uintptr_t)(id) - MIN_ESPI_ID)
#define GICD_OFFSET(REG, id) \
(((id) <= MAX_SPI_ID) ? \
GICD_##REG##R + (((uintptr_t)(id) >> REG##R_SHIFT) << 2) : \
GICD_##REG##RE + ((((uintptr_t)(id) - MIN_ESPI_ID) >> \
REG##R_SHIFT) << 2))
#define GICD_OFFSET_64(REG, id) \
(((id) <= MAX_SPI_ID) ? \
GICD_##REG##R + (((uintptr_t)(id) >> REG##R_SHIFT) << 3) : \
GICD_##REG##RE + ((((uintptr_t)(id) - MIN_ESPI_ID) >> \
REG##R_SHIFT) << 3))
#else /* GICv3 */
#define GICD_OFFSET_8(REG, id) \
(GICD_##REG##R + (uintptr_t)(id))
#define GICD_OFFSET(REG, id) \
(GICD_##REG##R + (((uintptr_t)(id) >> REG##R_SHIFT) << 2))
#define GICD_OFFSET_64(REG, id) \
(GICD_##REG##R + (((uintptr_t)(id) >> REG##R_SHIFT) << 3))
#endif /* GIC_EXT_INTID */
/*
* Read/Write 8, 32 and 64-bit GIC Distributor register
* corresponding to its interrupt ID
*/
#define GICD_READ(REG, base, id) \
mmio_read_32((base) + GICD_OFFSET(REG, (id)))
#define GICD_READ_64(REG, base, id) \
mmio_read_64((base) + GICD_OFFSET_64(REG, (id)))
#define GICD_WRITE_8(REG, base, id, val) \
mmio_write_8((base) + GICD_OFFSET_8(REG, (id)), (val))
#define GICD_WRITE(REG, base, id, val) \
mmio_write_32((base) + GICD_OFFSET(REG, (id)), (val))
#define GICD_WRITE_64(REG, base, id, val) \
mmio_write_64((base) + GICD_OFFSET_64(REG, (id)), (val))
/*
* Bit operations on GIC Distributor register corresponding
* to its interrupt ID
*/
/* Get bit in GIC Distributor register */
#define GICD_GET_BIT(REG, base, id) \
((mmio_read_32((base) + GICD_OFFSET(REG, (id))) >> \
BIT_NUM(REG, (id))) & 1U)
/* Set bit in GIC Distributor register */
#define GICD_SET_BIT(REG, base, id) \
mmio_setbits_32((base) + GICD_OFFSET(REG, (id)), \
((uint32_t)1 << BIT_NUM(REG, (id))))
/* Clear bit in GIC Distributor register */
#define GICD_CLR_BIT(REG, base, id) \
mmio_clrbits_32((base) + GICD_OFFSET(REG, (id)), \
((uint32_t)1 << BIT_NUM(REG, (id))))
/* Write bit in GIC Distributor register */
#define GICD_WRITE_BIT(REG, base, id) \
mmio_write_32((base) + GICD_OFFSET(REG, (id)), \
((uint32_t)1 << BIT_NUM(REG, (id))))
/*
* Calculate 8 and 32-bit GICR register offset
* corresponding to its interrupt ID
*/
#if GIC_EXT_INTID
/* GICv3.1 */
#define GICR_OFFSET_8(REG, id) \
(((id) <= MAX_PPI_ID) ? \
GICR_##REG##R + (uintptr_t)(id) : \
GICR_##REG##R + (uintptr_t)(id) - (MIN_EPPI_ID - MIN_SPI_ID))
#define GICR_OFFSET(REG, id) \
(((id) <= MAX_PPI_ID) ? \
GICR_##REG##R + (((uintptr_t)(id) >> REG##R_SHIFT) << 2) : \
GICR_##REG##R + ((((uintptr_t)(id) - (MIN_EPPI_ID - MIN_SPI_ID))\
>> REG##R_SHIFT) << 2))
#else /* GICv3 */
#define GICR_OFFSET_8(REG, id) \
(GICR_##REG##R + (uintptr_t)(id))
#define GICR_OFFSET(REG, id) \
(GICR_##REG##R + (((uintptr_t)(id) >> REG##R_SHIFT) << 2))
#endif /* GIC_EXT_INTID */
/* Read/Write GIC Redistributor register corresponding to its interrupt ID */
#define GICR_READ(REG, base, id) \
mmio_read_32((base) + GICR_OFFSET(REG, (id)))
#define GICR_WRITE_8(REG, base, id, val) \
mmio_write_8((base) + GICR_OFFSET_8(REG, (id)), (val))
#define GICR_WRITE(REG, base, id, val) \
mmio_write_32((base) + GICR_OFFSET(REG, (id)), (val))
/*
* Bit operations on GIC Redistributor register
* corresponding to its interrupt ID
*/
/* Get bit in GIC Redistributor register */
#define GICR_GET_BIT(REG, base, id) \
((mmio_read_32((base) + GICR_OFFSET(REG, (id))) >> \
BIT_NUM(REG, (id))) & 1U)
/* Write bit in GIC Redistributor register */
#define GICR_WRITE_BIT(REG, base, id) \
mmio_write_32((base) + GICR_OFFSET(REG, (id)), \
((uint32_t)1 << BIT_NUM(REG, (id))))
/* Set bit in GIC Redistributor register */
#define GICR_SET_BIT(REG, base, id) \
mmio_setbits_32((base) + GICR_OFFSET(REG, (id)), \
((uint32_t)1 << BIT_NUM(REG, (id))))
/* Clear bit in GIC Redistributor register */
#define GICR_CLR_BIT(REG, base, id) \
mmio_clrbits_32((base) + GICR_OFFSET(REG, (id)), \
((uint32_t)1 << BIT_NUM(REG, (id))))
/*
* Macro to convert an mpidr to a value suitable for programming into a
* GICD_IROUTER. Bits[31:24] in the MPIDR are cleared as they are not relevant
* to GICv3.
*/
static inline u_register_t gicd_irouter_val_from_mpidr(u_register_t mpidr,
unsigned int irm)
{
return (mpidr & MPIDR_AFFINITY_MASK) |
((irm & IROUTER_IRM_MASK) << IROUTER_IRM_SHIFT);
}
/*
* Macro to convert a GICR_TYPER affinity value into a MPIDR value. Bits[31:24]
* are zeroes.
*/
#ifdef __aarch64__
static inline u_register_t mpidr_from_gicr_typer(uint64_t typer_val)
{
return (((typer_val >> 56) & MPIDR_AFFLVL_MASK) << MPIDR_AFF3_SHIFT) |
((typer_val >> 32) & U(0xffffff));
}
#else
static inline u_register_t mpidr_from_gicr_typer(uint64_t typer_val)
{
return (((typer_val) >> 32) & U(0xffffff));
}
#endif
/*******************************************************************************
* GICv3 private global variables declarations
******************************************************************************/
extern const gicv3_driver_data_t *gicv3_driver_data;
/*******************************************************************************
* Private GICv3 function prototypes for accessing entire registers.
* Note: The raw register values correspond to multiple interrupt IDs and
* the number of interrupt IDs involved depends on the register accessed.
******************************************************************************/
unsigned int gicd_read_igrpmodr(uintptr_t base, unsigned int id);
unsigned int gicr_read_ipriorityr(uintptr_t base, unsigned int id);
void gicd_write_igrpmodr(uintptr_t base, unsigned int id, unsigned int val);
void gicr_write_ipriorityr(uintptr_t base, unsigned int id, unsigned int val);
/*******************************************************************************
* Private GICv3 function prototypes for accessing the GIC registers
* corresponding to a single interrupt ID. These functions use bitwise
* operations or appropriate register accesses to modify or return
* the bit-field corresponding the single interrupt ID.
******************************************************************************/
unsigned int gicd_get_igrpmodr(uintptr_t base, unsigned int id);
unsigned int gicr_get_igrpmodr(uintptr_t base, unsigned int id);
unsigned int gicr_get_igroupr(uintptr_t base, unsigned int id);
unsigned int gicr_get_isactiver(uintptr_t base, unsigned int id);
void gicd_set_igrpmodr(uintptr_t base, unsigned int id);
void gicr_set_igrpmodr(uintptr_t base, unsigned int id);
void gicr_set_isenabler(uintptr_t base, unsigned int id);
void gicr_set_icenabler(uintptr_t base, unsigned int id);
void gicr_set_ispendr(uintptr_t base, unsigned int id);
void gicr_set_icpendr(uintptr_t base, unsigned int id);
void gicr_set_igroupr(uintptr_t base, unsigned int id);
void gicd_clr_igrpmodr(uintptr_t base, unsigned int id);
void gicr_clr_igrpmodr(uintptr_t base, unsigned int id);
void gicr_clr_igroupr(uintptr_t base, unsigned int id);
void gicr_set_ipriorityr(uintptr_t base, unsigned int id, unsigned int pri);
void gicr_set_icfgr(uintptr_t base, unsigned int id, unsigned int cfg);
/*******************************************************************************
* Private GICv3 helper function prototypes
******************************************************************************/
uintptr_t gicv3_get_multichip_base(uint32_t spi_id, uintptr_t gicd_base);
unsigned int gicv3_get_spi_limit(uintptr_t gicd_base);
unsigned int gicv3_get_espi_limit(uintptr_t gicd_base);
void gicv3_spis_config_defaults(uintptr_t gicd_base);
void gicv3_ppi_sgi_config_defaults(uintptr_t gicr_base);
unsigned int gicv3_secure_ppi_sgi_config_props(uintptr_t gicr_base,
const interrupt_prop_t *interrupt_props,
unsigned int interrupt_props_num);
unsigned int gicv3_secure_spis_config_props(uintptr_t gicd_base,
const interrupt_prop_t *interrupt_props,
unsigned int interrupt_props_num);
void gicv3_rdistif_base_addrs_probe(uintptr_t *rdistif_base_addrs,
unsigned int rdistif_num,
uintptr_t gicr_base,
mpidr_hash_fn mpidr_to_core_pos);
void gicv3_rdistif_mark_core_awake(uintptr_t gicr_base);
void gicv3_rdistif_mark_core_asleep(uintptr_t gicr_base);
/*******************************************************************************
* GIC Distributor interface accessors
******************************************************************************/
/*
* Wait for updates to:
* GICD_CTLR[2:0] - the Group Enables
* GICD_CTLR[7:4] - the ARE bits, E1NWF bit and DS bit
* GICD_ICENABLER<n> - the clearing of enable state for SPIs
*/
static inline void gicd_wait_for_pending_write(uintptr_t gicd_base)
{
while ((gicd_read_ctlr(gicd_base) & GICD_CTLR_RWP_BIT) != 0U) {
}
}
static inline uint32_t gicd_read_pidr2(uintptr_t base)
{
return mmio_read_32(base + GICD_PIDR2_GICV3);
}
static inline uint64_t gicd_read_irouter(uintptr_t base, unsigned int id)
{
assert(id >= MIN_SPI_ID);
return GICD_READ_64(IROUTE, base, id);
}
static inline void gicd_write_irouter(uintptr_t base,
unsigned int id,
uint64_t affinity)
{
assert(id >= MIN_SPI_ID);
GICD_WRITE_64(IROUTE, base, id, affinity);
}
static inline void gicd_clr_ctlr(uintptr_t base,
unsigned int bitmap,
unsigned int rwp)
{
gicd_write_ctlr(base, gicd_read_ctlr(base) & ~bitmap);
if (rwp != 0U) {
gicd_wait_for_pending_write(base);
}
}
static inline void gicd_set_ctlr(uintptr_t base,
unsigned int bitmap,
unsigned int rwp)
{
gicd_write_ctlr(base, gicd_read_ctlr(base) | bitmap);
if (rwp != 0U) {
gicd_wait_for_pending_write(base);
}
}
/*******************************************************************************
* GIC Redistributor interface accessors
******************************************************************************/
static inline uint32_t gicr_read_ctlr(uintptr_t base)
{
return mmio_read_32(base + GICR_CTLR);
}
static inline void gicr_write_ctlr(uintptr_t base, uint32_t val)
{
mmio_write_32(base + GICR_CTLR, val);
}
static inline uint64_t gicr_read_typer(uintptr_t base)
{
return mmio_read_64(base + GICR_TYPER);
}
static inline uint32_t gicr_read_waker(uintptr_t base)
{
return mmio_read_32(base + GICR_WAKER);
}
static inline void gicr_write_waker(uintptr_t base, uint32_t val)
{
mmio_write_32(base + GICR_WAKER, val);
}
/*
* Wait for updates to:
* GICR_ICENABLER0
* GICR_CTLR.DPG1S
* GICR_CTLR.DPG1NS
* GICR_CTLR.DPG0
* GICR_CTLR, which clears EnableLPIs from 1 to 0
*/
static inline void gicr_wait_for_pending_write(uintptr_t gicr_base)
{
while ((gicr_read_ctlr(gicr_base) & GICR_CTLR_RWP_BIT) != 0U) {
}
}
static inline void gicr_wait_for_upstream_pending_write(uintptr_t gicr_base)
{
while ((gicr_read_ctlr(gicr_base) & GICR_CTLR_UWP_BIT) != 0U) {
}
}
/* Private implementation of Distributor power control hooks */
void arm_gicv3_distif_pre_save(unsigned int rdist_proc_num);
void arm_gicv3_distif_post_restore(unsigned int rdist_proc_num);
/*******************************************************************************
* GIC Redistributor functions for accessing entire registers.
* Note: The raw register values correspond to multiple interrupt IDs and
* the number of interrupt IDs involved depends on the register accessed.
******************************************************************************/
/*
* Accessors to read/write GIC Redistributor ICENABLER0 register
*/
static inline unsigned int gicr_read_icenabler0(uintptr_t base)
{
return mmio_read_32(base + GICR_ICENABLER0);
}
static inline void gicr_write_icenabler0(uintptr_t base, unsigned int val)
{
mmio_write_32(base + GICR_ICENABLER0, val);
}
/*
* Accessors to read/write GIC Redistributor ICENABLER0 and ICENABLERE
* register corresponding to its number
*/
static inline unsigned int gicr_read_icenabler(uintptr_t base,
unsigned int reg_num)
{
return mmio_read_32(base + GICR_ICENABLER + (reg_num << 2));
}
static inline void gicr_write_icenabler(uintptr_t base, unsigned int reg_num,
unsigned int val)
{
mmio_write_32(base + GICR_ICENABLER + (reg_num << 2), val);
}
/*
* Accessors to read/write GIC Redistributor ICFGR0, ICFGR1 registers
*/
static inline unsigned int gicr_read_icfgr0(uintptr_t base)
{
return mmio_read_32(base + GICR_ICFGR0);
}
static inline unsigned int gicr_read_icfgr1(uintptr_t base)
{
return mmio_read_32(base + GICR_ICFGR1);
}
static inline void gicr_write_icfgr0(uintptr_t base, unsigned int val)
{
mmio_write_32(base + GICR_ICFGR0, val);
}
static inline void gicr_write_icfgr1(uintptr_t base, unsigned int val)
{
mmio_write_32(base + GICR_ICFGR1, val);
}
/*
* Accessors to read/write GIC Redistributor ICFGR0, ICFGR1 and ICFGRE
* register corresponding to its number
*/
static inline unsigned int gicr_read_icfgr(uintptr_t base, unsigned int reg_num)
{
return mmio_read_32(base + GICR_ICFGR + (reg_num << 2));
}
static inline void gicr_write_icfgr(uintptr_t base, unsigned int reg_num,
unsigned int val)
{
mmio_write_32(base + GICR_ICFGR + (reg_num << 2), val);
}
/*
* Accessor to write GIC Redistributor ICPENDR0 register
*/
static inline void gicr_write_icpendr0(uintptr_t base, unsigned int val)
{
mmio_write_32(base + GICR_ICPENDR0, val);
}
/*
* Accessor to write GIC Redistributor ICPENDR0 and ICPENDRE
* register corresponding to its number
*/
static inline void gicr_write_icpendr(uintptr_t base, unsigned int reg_num,
unsigned int val)
{
mmio_write_32(base + GICR_ICPENDR + (reg_num << 2), val);
}
/*
* Accessors to read/write GIC Redistributor IGROUPR0 register
*/
static inline unsigned int gicr_read_igroupr0(uintptr_t base)
{
return mmio_read_32(base + GICR_IGROUPR0);
}
static inline void gicr_write_igroupr0(uintptr_t base, unsigned int val)
{
mmio_write_32(base + GICR_IGROUPR0, val);
}
/*
* Accessors to read/write GIC Redistributor IGROUPR0 and IGROUPRE
* register corresponding to its number
*/
static inline unsigned int gicr_read_igroupr(uintptr_t base,
unsigned int reg_num)
{
return mmio_read_32(base + GICR_IGROUPR + (reg_num << 2));
}
static inline void gicr_write_igroupr(uintptr_t base, unsigned int reg_num,
unsigned int val)
{
mmio_write_32(base + GICR_IGROUPR + (reg_num << 2), val);
}
/*
* Accessors to read/write GIC Redistributor IGRPMODR0 register
*/
static inline unsigned int gicr_read_igrpmodr0(uintptr_t base)
{
return mmio_read_32(base + GICR_IGRPMODR0);
}
static inline void gicr_write_igrpmodr0(uintptr_t base, unsigned int val)
{
mmio_write_32(base + GICR_IGRPMODR0, val);
}
/*
* Accessors to read/write GIC Redistributor IGRPMODR0 and IGRPMODRE
* register corresponding to its number
*/
static inline unsigned int gicr_read_igrpmodr(uintptr_t base,
unsigned int reg_num)
{
return mmio_read_32(base + GICR_IGRPMODR + (reg_num << 2));
}
static inline void gicr_write_igrpmodr(uintptr_t base, unsigned int reg_num,
unsigned int val)
{
mmio_write_32(base + GICR_IGRPMODR + (reg_num << 2), val);
}
/*
* Accessors to read/write the GIC Redistributor IPRIORITYR(E) register
* corresponding to its number, 4 interrupts IDs at a time.
*/
static inline unsigned int gicr_ipriorityr_read(uintptr_t base,
unsigned int reg_num)
{
return mmio_read_32(base + GICR_IPRIORITYR + (reg_num << 2));
}
static inline void gicr_ipriorityr_write(uintptr_t base, unsigned int reg_num,
unsigned int val)
{
mmio_write_32(base + GICR_IPRIORITYR + (reg_num << 2), val);
}
/*
* Accessors to read/write GIC Redistributor ISACTIVER0 register
*/
static inline unsigned int gicr_read_isactiver0(uintptr_t base)
{
return mmio_read_32(base + GICR_ISACTIVER0);
}
static inline void gicr_write_isactiver0(uintptr_t base, unsigned int val)
{
mmio_write_32(base + GICR_ISACTIVER0, val);
}
/*
* Accessors to read/write GIC Redistributor ISACTIVER0 and ISACTIVERE
* register corresponding to its number
*/
static inline unsigned int gicr_read_isactiver(uintptr_t base,
unsigned int reg_num)
{
return mmio_read_32(base + GICR_ISACTIVER + (reg_num << 2));
}
static inline void gicr_write_isactiver(uintptr_t base, unsigned int reg_num,
unsigned int val)
{
mmio_write_32(base + GICR_ISACTIVER + (reg_num << 2), val);
}
/*
* Accessors to read/write GIC Redistributor ISENABLER0 register
*/
static inline unsigned int gicr_read_isenabler0(uintptr_t base)
{
return mmio_read_32(base + GICR_ISENABLER0);
}
static inline void gicr_write_isenabler0(uintptr_t base, unsigned int val)
{
mmio_write_32(base + GICR_ISENABLER0, val);
}
/*
* Accessors to read/write GIC Redistributor ISENABLER0 and ISENABLERE
* register corresponding to its number
*/
static inline unsigned int gicr_read_isenabler(uintptr_t base,
unsigned int reg_num)
{
return mmio_read_32(base + GICR_ISENABLER + (reg_num << 2));
}
static inline void gicr_write_isenabler(uintptr_t base, unsigned int reg_num,
unsigned int val)
{
mmio_write_32(base + GICR_ISENABLER + (reg_num << 2), val);
}
/*
* Accessors to read/write GIC Redistributor ISPENDR0 register
*/
static inline unsigned int gicr_read_ispendr0(uintptr_t base)
{
return mmio_read_32(base + GICR_ISPENDR0);
}
static inline void gicr_write_ispendr0(uintptr_t base, unsigned int val)
{
mmio_write_32(base + GICR_ISPENDR0, val);
}
/*
* Accessors to read/write GIC Redistributor ISPENDR0 and ISPENDRE
* register corresponding to its number
*/
static inline unsigned int gicr_read_ispendr(uintptr_t base,
unsigned int reg_num)
{
return mmio_read_32(base + GICR_ISPENDR + (reg_num << 2));
}
static inline void gicr_write_ispendr(uintptr_t base, unsigned int reg_num,
unsigned int val)
{
mmio_write_32(base + GICR_ISPENDR + (reg_num << 2), val);
}
/*
* Accessors to read/write GIC Redistributor NSACR register
*/
static inline unsigned int gicr_read_nsacr(uintptr_t base)
{
return mmio_read_32(base + GICR_NSACR);
}
static inline void gicr_write_nsacr(uintptr_t base, unsigned int val)
{
mmio_write_32(base + GICR_NSACR, val);
}
/*
* Accessors to read/write GIC Redistributor PROPBASER register
*/
static inline uint64_t gicr_read_propbaser(uintptr_t base)
{
return mmio_read_64(base + GICR_PROPBASER);
}
static inline void gicr_write_propbaser(uintptr_t base, uint64_t val)
{
mmio_write_64(base + GICR_PROPBASER, val);
}
/*
* Accessors to read/write GIC Redistributor PENDBASER register
*/
static inline uint64_t gicr_read_pendbaser(uintptr_t base)
{
return mmio_read_64(base + GICR_PENDBASER);
}
static inline void gicr_write_pendbaser(uintptr_t base, uint64_t val)
{
mmio_write_64(base + GICR_PENDBASER, val);
}
/*******************************************************************************
* GIC ITS functions to read and write entire ITS registers.
******************************************************************************/
static inline uint32_t gits_read_ctlr(uintptr_t base)
{
return mmio_read_32(base + GITS_CTLR);
}
static inline void gits_write_ctlr(uintptr_t base, uint32_t val)
{
mmio_write_32(base + GITS_CTLR, val);
}
static inline uint64_t gits_read_cbaser(uintptr_t base)
{
return mmio_read_64(base + GITS_CBASER);
}
static inline void gits_write_cbaser(uintptr_t base, uint64_t val)
{
mmio_write_64(base + GITS_CBASER, val);
}
static inline uint64_t gits_read_cwriter(uintptr_t base)
{
return mmio_read_64(base + GITS_CWRITER);
}
static inline void gits_write_cwriter(uintptr_t base, uint64_t val)
{
mmio_write_64(base + GITS_CWRITER, val);
}
static inline uint64_t gits_read_baser(uintptr_t base,
unsigned int its_table_id)
{
assert(its_table_id < 8U);
return mmio_read_64(base + GITS_BASER + (8U * its_table_id));
}
static inline void gits_write_baser(uintptr_t base, unsigned int its_table_id,
uint64_t val)
{
assert(its_table_id < 8U);
mmio_write_64(base + GITS_BASER + (8U * its_table_id), val);
}
/*
* Wait for Quiescent bit when GIC ITS is disabled
*/
static inline void gits_wait_for_quiescent_bit(uintptr_t gits_base)
{
assert((gits_read_ctlr(gits_base) & GITS_CTLR_ENABLED_BIT) == 0U);
while ((gits_read_ctlr(gits_base) & GITS_CTLR_QUIESCENT_BIT) == 0U) {
}
}
#endif /* GICV3_PRIVATE_H */
@@ -0,0 +1,379 @@
/*
* Copyright (c) 2020-2022, Arm Limited. All rights reserved.
*
* SPDX-License-Identifier: BSD-3-Clause
*/
#include <assert.h>
#include <stdbool.h>
#include <stdint.h>
#include "mhu_v2_x.h"
#define MHU_V2_X_MAX_CHANNELS 124
#define MHU_V2_1_MAX_CHCOMB_INT 4
#define ENABLE 0x1
#define DISABLE 0x0
#define CLEAR_INTR 0x1
#define CH_PER_CH_COMB 0x20
#define SEND_FRAME(p_mhu) ((struct mhu_v2_x_send_frame_t *)p_mhu)
#define RECV_FRAME(p_mhu) ((struct mhu_v2_x_recv_frame_t *)p_mhu)
#define MHU_MAJOR_REV_V2 0x1u
#define MHU_MINOR_REV_2_0 0x0u
#define MHU_MINOR_REV_2_1 0x1u
struct mhu_v2_x_send_ch_window_t {
/* Offset: 0x00 (R/ ) Channel Status */
volatile uint32_t ch_st;
/* Offset: 0x04 (R/ ) Reserved */
volatile uint32_t reserved_0;
/* Offset: 0x08 (R/ ) Reserved */
volatile uint32_t reserved_1;
/* Offset: 0x0C ( /W) Channel Set */
volatile uint32_t ch_set;
/* Offset: 0x10 (R/ ) Channel Interrupt Status (Reserved in 2.0) */
volatile uint32_t ch_int_st;
/* Offset: 0x14 ( /W) Channel Interrupt Clear (Reserved in 2.0) */
volatile uint32_t ch_int_clr;
/* Offset: 0x18 (R/W) Channel Interrupt Enable (Reserved in 2.0) */
volatile uint32_t ch_int_en;
/* Offset: 0x1C (R/ ) Reserved */
volatile uint32_t reserved_2;
};
struct mhu_v2_x_send_frame_t {
/* Offset: 0x000 ( / ) Sender Channel Window 0 -123 */
struct mhu_v2_x_send_ch_window_t send_ch_window[MHU_V2_X_MAX_CHANNELS];
/* Offset: 0xF80 (R/ ) Message Handling Unit Configuration */
volatile uint32_t mhu_cfg;
/* Offset: 0xF84 (R/W) Response Configuration */
volatile uint32_t resp_cfg;
/* Offset: 0xF88 (R/W) Access Request */
volatile uint32_t access_request;
/* Offset: 0xF8C (R/ ) Access Ready */
volatile uint32_t access_ready;
/* Offset: 0xF90 (R/ ) Interrupt Status */
volatile uint32_t int_st;
/* Offset: 0xF94 ( /W) Interrupt Clear */
volatile uint32_t int_clr;
/* Offset: 0xF98 (R/W) Interrupt Enable */
volatile uint32_t int_en;
/* Offset: 0xF9C (R/ ) Reserved */
volatile uint32_t reserved_0;
/* Offset: 0xFA0 (R/W) Channel Combined IRQ Stat (Reserved in 2.0) */
volatile uint32_t ch_comb_int_st[MHU_V2_1_MAX_CHCOMB_INT];
/* Offset: 0xFC4 (R/ ) Reserved */
volatile uint32_t reserved_1[6];
/* Offset: 0xFC8 (R/ ) Implementer Identification Register */
volatile uint32_t iidr;
/* Offset: 0xFCC (R/ ) Architecture Identification Register */
volatile uint32_t aidr;
/* Offset: 0xFD0 (R/ ) */
volatile uint32_t pid_1[4];
/* Offset: 0xFE0 (R/ ) */
volatile uint32_t pid_0[4];
/* Offset: 0xFF0 (R/ ) */
volatile uint32_t cid[4];
};
struct mhu_v2_x_rec_ch_window_t {
/* Offset: 0x00 (R/ ) Channel Status */
volatile uint32_t ch_st;
/* Offset: 0x04 (R/ ) Channel Status Masked */
volatile uint32_t ch_st_msk;
/* Offset: 0x08 ( /W) Channel Clear */
volatile uint32_t ch_clr;
/* Offset: 0x0C (R/ ) Reserved */
volatile uint32_t reserved_0;
/* Offset: 0x10 (R/ ) Channel Mask Status */
volatile uint32_t ch_msk_st;
/* Offset: 0x14 ( /W) Channel Mask Set */
volatile uint32_t ch_msk_set;
/* Offset: 0x18 ( /W) Channel Mask Clear */
volatile uint32_t ch_msk_clr;
/* Offset: 0x1C (R/ ) Reserved */
volatile uint32_t reserved_1;
};
struct mhu_v2_x_recv_frame_t {
/* Offset: 0x000 ( / ) Receiver Channel Window 0 -123 */
struct mhu_v2_x_rec_ch_window_t rec_ch_window[MHU_V2_X_MAX_CHANNELS];
/* Offset: 0xF80 (R/ ) Message Handling Unit Configuration */
volatile uint32_t mhu_cfg;
/* Offset: 0xF84 (R/ ) Reserved */
volatile uint32_t reserved_0[3];
/* Offset: 0xF90 (R/ ) Interrupt Status (Reserved in 2.0) */
volatile uint32_t int_st;
/* Offset: 0xF94 (R/ ) Interrupt Clear (Reserved in 2.0) */
volatile uint32_t int_clr;
/* Offset: 0xF98 (R/W) Interrupt Enable (Reserved in 2.0) */
volatile uint32_t int_en;
/* Offset: 0xF9C (R/ ) Reserved */
volatile uint32_t reserved_1;
/* Offset: 0xFA0 (R/ ) Channel Combined IRQ Stat (Reserved in 2.0) */
volatile uint32_t ch_comb_int_st[MHU_V2_1_MAX_CHCOMB_INT];
/* Offset: 0xFB0 (R/ ) Reserved */
volatile uint32_t reserved_2[6];
/* Offset: 0xFC8 (R/ ) Implementer Identification Register */
volatile uint32_t iidr;
/* Offset: 0xFCC (R/ ) Architecture Identification Register */
volatile uint32_t aidr;
/* Offset: 0xFD0 (R/ ) */
volatile uint32_t pid_1[4];
/* Offset: 0xFE0 (R/ ) */
volatile uint32_t pid_0[4];
/* Offset: 0xFF0 (R/ ) */
volatile uint32_t cid[4];
};
union mhu_v2_x_frame {
struct mhu_v2_x_send_frame_t send_frame;
struct mhu_v2_x_recv_frame_t recv_frame;
};
enum mhu_v2_x_error_t mhu_v2_x_driver_init(struct mhu_v2_x_dev_t *dev,
enum mhu_v2_x_supported_revisions rev)
{
uint32_t AIDR = 0;
union mhu_v2_x_frame *p_mhu;
assert(dev != NULL);
p_mhu = (union mhu_v2_x_frame *)dev->base;
if (dev->is_initialized) {
return MHU_V_2_X_ERR_ALREADY_INIT;
}
if (rev == MHU_REV_READ_FROM_HW) {
/* Read revision from HW */
if (dev->frame == MHU_V2_X_RECEIVER_FRAME) {
AIDR = p_mhu->recv_frame.aidr;
} else {
AIDR = p_mhu->send_frame.aidr;
}
/* Get bits 7:4 to read major revision */
if (((AIDR >> 4) & 0b1111) != MHU_MAJOR_REV_V2) {
/* Unsupported MHU version */
return MHU_V_2_X_ERR_UNSUPPORTED_VERSION;
} /* No need to save major version, driver only supports MHUv2 */
/* Get bits 3:0 to read minor revision */
dev->subversion = AIDR & 0b1111;
if (dev->subversion != MHU_MINOR_REV_2_0 &&
dev->subversion != MHU_MINOR_REV_2_1) {
/* Unsupported subversion */
return MHU_V_2_X_ERR_UNSUPPORTED_VERSION;
}
} else {
/* Revisions were provided by caller */
if (rev == MHU_REV_2_0) {
dev->subversion = MHU_MINOR_REV_2_0;
} else if (rev == MHU_REV_2_1) {
dev->subversion = MHU_MINOR_REV_2_1;
} else {
/* Unsupported subversion */
return MHU_V_2_X_ERR_UNSUPPORTED_VERSION;
} /* No need to save major version, driver only supports MHUv2 */
}
dev->is_initialized = true;
return MHU_V_2_X_ERR_NONE;
}
uint32_t mhu_v2_x_get_num_channel_implemented(const struct mhu_v2_x_dev_t *dev)
{
union mhu_v2_x_frame *p_mhu;
assert(dev != NULL);
p_mhu = (union mhu_v2_x_frame *)dev->base;
if (!(dev->is_initialized)) {
return MHU_V_2_X_ERR_NOT_INIT;
}
if (dev->frame == MHU_V2_X_SENDER_FRAME) {
return (SEND_FRAME(p_mhu))->mhu_cfg;
} else {
assert(dev->frame == MHU_V2_X_RECEIVER_FRAME);
return (RECV_FRAME(p_mhu))->mhu_cfg;
}
}
enum mhu_v2_x_error_t mhu_v2_x_channel_send(const struct mhu_v2_x_dev_t *dev,
uint32_t channel, uint32_t val)
{
union mhu_v2_x_frame *p_mhu;
assert(dev != NULL);
p_mhu = (union mhu_v2_x_frame *)dev->base;
if (!(dev->is_initialized)) {
return MHU_V_2_X_ERR_NOT_INIT;
}
if (dev->frame == MHU_V2_X_SENDER_FRAME) {
(SEND_FRAME(p_mhu))->send_ch_window[channel].ch_set = val;
return MHU_V_2_X_ERR_NONE;
} else {
return MHU_V_2_X_ERR_INVALID_ARG;
}
}
enum mhu_v2_x_error_t mhu_v2_x_channel_poll(const struct mhu_v2_x_dev_t *dev,
uint32_t channel, uint32_t *value)
{
union mhu_v2_x_frame *p_mhu;
assert(dev != NULL);
p_mhu = (union mhu_v2_x_frame *)dev->base;
if (!(dev->is_initialized)) {
return MHU_V_2_X_ERR_NOT_INIT;
}
if (dev->frame == MHU_V2_X_SENDER_FRAME) {
*value = (SEND_FRAME(p_mhu))->send_ch_window[channel].ch_st;
return MHU_V_2_X_ERR_NONE;
} else {
return MHU_V_2_X_ERR_INVALID_ARG;
}
}
enum mhu_v2_x_error_t mhu_v2_x_channel_clear(const struct mhu_v2_x_dev_t *dev,
uint32_t channel)
{
union mhu_v2_x_frame *p_mhu;
assert(dev != NULL);
p_mhu = (union mhu_v2_x_frame *)dev->base;
if (!(dev->is_initialized)) {
return MHU_V_2_X_ERR_NOT_INIT;
}
if (dev->frame == MHU_V2_X_RECEIVER_FRAME) {
(RECV_FRAME(p_mhu))->rec_ch_window[channel].ch_clr = UINT32_MAX;
return MHU_V_2_X_ERR_NONE;
} else {
return MHU_V_2_X_ERR_INVALID_ARG;
}
}
enum mhu_v2_x_error_t mhu_v2_x_channel_receive(
const struct mhu_v2_x_dev_t *dev, uint32_t channel, uint32_t *value)
{
union mhu_v2_x_frame *p_mhu;
assert(dev != NULL);
p_mhu = (union mhu_v2_x_frame *)dev->base;
if (!(dev->is_initialized)) {
return MHU_V_2_X_ERR_NOT_INIT;
}
if (dev->frame == MHU_V2_X_RECEIVER_FRAME) {
*value = (RECV_FRAME(p_mhu))->rec_ch_window[channel].ch_st;
return MHU_V_2_X_ERR_NONE;
} else {
return MHU_V_2_X_ERR_INVALID_ARG;
}
}
enum mhu_v2_x_error_t mhu_v2_x_channel_mask_set(
const struct mhu_v2_x_dev_t *dev, uint32_t channel, uint32_t mask)
{
union mhu_v2_x_frame *p_mhu;
assert(dev != NULL);
p_mhu = (union mhu_v2_x_frame *)dev->base;
if (!(dev->is_initialized)) {
return MHU_V_2_X_ERR_NOT_INIT;
}
if (dev->frame == MHU_V2_X_RECEIVER_FRAME) {
(RECV_FRAME(p_mhu))->rec_ch_window[channel].ch_msk_set = mask;
return MHU_V_2_X_ERR_NONE;
} else {
return MHU_V_2_X_ERR_INVALID_ARG;
}
}
enum mhu_v2_x_error_t mhu_v2_x_channel_mask_clear(
const struct mhu_v2_x_dev_t *dev, uint32_t channel, uint32_t mask)
{
union mhu_v2_x_frame *p_mhu;
assert(dev != NULL);
p_mhu = (union mhu_v2_x_frame *)dev->base;
if (!(dev->is_initialized)) {
return MHU_V_2_X_ERR_NOT_INIT;
}
if (dev->frame == MHU_V2_X_RECEIVER_FRAME) {
(RECV_FRAME(p_mhu))->rec_ch_window[channel].ch_msk_clr = mask;
return MHU_V_2_X_ERR_NONE;
} else {
return MHU_V_2_X_ERR_INVALID_ARG;
}
}
enum mhu_v2_x_error_t mhu_v2_x_initiate_transfer(
const struct mhu_v2_x_dev_t *dev)
{
union mhu_v2_x_frame *p_mhu;
assert(dev != NULL);
p_mhu = (union mhu_v2_x_frame *)dev->base;
if (!(dev->is_initialized)) {
return MHU_V_2_X_ERR_NOT_INIT;
}
if (dev->frame != MHU_V2_X_SENDER_FRAME) {
return MHU_V_2_X_ERR_INVALID_ARG;
}
(SEND_FRAME(p_mhu))->access_request = ENABLE;
while (!((SEND_FRAME(p_mhu))->access_ready)) {
/* Wait in a loop for access ready signal to be high */
;
}
return MHU_V_2_X_ERR_NONE;
}
enum mhu_v2_x_error_t mhu_v2_x_close_transfer(const struct mhu_v2_x_dev_t *dev)
{
union mhu_v2_x_frame *p_mhu;
assert(dev != NULL);
p_mhu = (union mhu_v2_x_frame *)dev->base;
if (!(dev->is_initialized)) {
return MHU_V_2_X_ERR_NOT_INIT;
}
if (dev->frame != MHU_V2_X_SENDER_FRAME) {
return MHU_V_2_X_ERR_INVALID_ARG;
}
(SEND_FRAME(p_mhu))->access_request = DISABLE;
return MHU_V_2_X_ERR_NONE;
}
@@ -0,0 +1,210 @@
/*
* Copyright (c) 2020-2022, Arm Limited. All rights reserved.
*
* SPDX-License-Identifier: BSD-3-Clause
*/
#ifndef MHU_V2_X_H
#define MHU_V2_X_H
#include <stdbool.h>
#include <stdint.h>
#define MHU_2_X_INTR_NR2R_OFF (0x0u)
#define MHU_2_X_INTR_R2NR_OFF (0x1u)
#define MHU_2_1_INTR_CHCOMB_OFF (0x2u)
#define MHU_2_X_INTR_NR2R_MASK (0x1u << MHU_2_X_INTR_NR2R_OFF)
#define MHU_2_X_INTR_R2NR_MASK (0x1u << MHU_2_X_INTR_R2NR_OFF)
#define MHU_2_1_INTR_CHCOMB_MASK (0x1u << MHU_2_1_INTR_CHCOMB_OFF)
enum mhu_v2_x_frame_t {
MHU_V2_X_SENDER_FRAME = 0x0u,
MHU_V2_X_RECEIVER_FRAME = 0x1u,
};
enum mhu_v2_x_supported_revisions {
MHU_REV_READ_FROM_HW = 0,
MHU_REV_2_0,
MHU_REV_2_1,
};
struct mhu_v2_x_dev_t {
uintptr_t base;
enum mhu_v2_x_frame_t frame;
uint32_t subversion; /*!< Hardware subversion: v2.X */
bool is_initialized; /*!< Indicates if the MHU driver
* is initialized and enabled
*/
};
/**
* MHU v2 error enumeration types.
*/
enum mhu_v2_x_error_t {
MHU_V_2_X_ERR_NONE = 0,
MHU_V_2_X_ERR_NOT_INIT = -1,
MHU_V_2_X_ERR_ALREADY_INIT = -2,
MHU_V_2_X_ERR_UNSUPPORTED_VERSION = -3,
MHU_V_2_X_ERR_INVALID_ARG = -4,
MHU_V_2_X_ERR_GENERAL = -5
};
/**
* Initializes the driver.
*
* dev MHU device struct mhu_v2_x_dev_t.
* rev MHU revision (if can't be identified from HW).
*
* Reads the MHU hardware version.
*
* Returns mhu_v2_x_error_t error code.
*
* MHU revision only has to be specified when versions can't be read
* from HW (ARCH_MAJOR_REV reg reads as 0x0).
*
* This function doesn't check if dev is NULL.
*/
enum mhu_v2_x_error_t mhu_v2_x_driver_init(struct mhu_v2_x_dev_t *dev,
enum mhu_v2_x_supported_revisions rev);
/**
* Returns the number of channels implemented.
*
* dev MHU device struct mhu_v2_x_dev_t.
*
* This function doesn't check if dev is NULL.
*/
uint32_t mhu_v2_x_get_num_channel_implemented(
const struct mhu_v2_x_dev_t *dev);
/**
* Sends the value over a channel.
*
* dev MHU device struct mhu_v2_x_dev_t.
* channel Channel to send the value over.
* val Value to send.
*
* Sends the value over a channel.
*
* Returns mhu_v2_x_error_t error code.
*
* This function doesn't check if dev is NULL.
* This function doesn't check if channel is implemented.
*/
enum mhu_v2_x_error_t mhu_v2_x_channel_send(const struct mhu_v2_x_dev_t *dev,
uint32_t channel, uint32_t val);
/**
* Polls sender channel status.
*
* dev MHU device struct mhu_v2_x_dev_t.
* channel Channel to poll the status of.
* value Pointer to variable that will store the value.
*
* Polls sender channel status.
*
* Returns mhu_v2_x_error_t error code.
*
* This function doesn't check if dev is NULL.
* This function doesn't check if channel is implemented.
*/
enum mhu_v2_x_error_t mhu_v2_x_channel_poll(const struct mhu_v2_x_dev_t *dev,
uint32_t channel, uint32_t *value);
/**
* Clears the channel after the value is send over it.
*
* dev MHU device struct mhu_v2_x_dev_t.
* channel Channel to clear.
*
* Clears the channel after the value is send over it.
*
* Returns mhu_v2_x_error_t error code..
*
* This function doesn't check if dev is NULL.
* This function doesn't check if channel is implemented.
*/
enum mhu_v2_x_error_t mhu_v2_x_channel_clear(const struct mhu_v2_x_dev_t *dev,
uint32_t channel);
/**
* Receives the value over a channel.
*
* dev MHU device struct mhu_v2_x_dev_t.
* channel Channel to receive the value from.
* value Pointer to variable that will store the value.
*
* Receives the value over a channel.
*
* Returns mhu_v2_x_error_t error code.
*
* This function doesn't check if dev is NULL.
* This function doesn't check if channel is implemented.
*/
enum mhu_v2_x_error_t mhu_v2_x_channel_receive(
const struct mhu_v2_x_dev_t *dev, uint32_t channel, uint32_t *value);
/**
* Sets bits in the Channel Mask.
*
* dev MHU device struct mhu_v2_x_dev_t.
* channel Which channel's mask to set.
* mask Mask to be set over a receiver frame.
*
* Sets bits in the Channel Mask.
*
* Returns mhu_v2_x_error_t error code..
*
* This function doesn't check if dev is NULL.
* This function doesn't check if channel is implemented.
*/
enum mhu_v2_x_error_t mhu_v2_x_channel_mask_set(
const struct mhu_v2_x_dev_t *dev, uint32_t channel, uint32_t mask);
/**
* Clears bits in the Channel Mask.
*
* dev MHU device struct mhu_v2_x_dev_t.
* channel Which channel's mask to clear.
* mask Mask to be clear over a receiver frame.
*
* Clears bits in the Channel Mask.
*
* Returns mhu_v2_x_error_t error code.
*
* This function doesn't check if dev is NULL.
* This function doesn't check if channel is implemented.
*/
enum mhu_v2_x_error_t mhu_v2_x_channel_mask_clear(
const struct mhu_v2_x_dev_t *dev, uint32_t channel, uint32_t mask);
/**
* Initiates a MHU transfer with the handshake signals.
*
* dev MHU device struct mhu_v2_x_dev_t.
*
* Initiates a MHU transfer with the handshake signals in a blocking mode.
*
* Returns mhu_v2_x_error_t error code.
*
* This function doesn't check if dev is NULL.
*/
enum mhu_v2_x_error_t mhu_v2_x_initiate_transfer(
const struct mhu_v2_x_dev_t *dev);
/**
* Closes a MHU transfer with the handshake signals.
*
* dev MHU device struct mhu_v2_x_dev_t.
*
* Closes a MHU transfer with the handshake signals in a blocking mode.
*
* Returns mhu_v2_x_error_t error code.
*
* This function doesn't check if dev is NULL.
*/
enum mhu_v2_x_error_t mhu_v2_x_close_transfer(
const struct mhu_v2_x_dev_t *dev);
#endif /* MHU_V2_X_H */
@@ -0,0 +1,312 @@
/*
* Copyright (c) 2022, Arm Limited. All rights reserved.
*
* SPDX-License-Identifier: BSD-3-Clause
*/
#include <assert.h>
#include <stddef.h>
#include <stdint.h>
#include <string.h>
#include <drivers/arm/mhu.h>
#include "mhu_v2_x.h"
#define MHU_NOTIFY_VALUE (1234u)
/*
* MHU devices for host:
* HSE: Host to Secure Enclave (sender device)
* SEH: Secure Enclave to Host (receiver device)
*/
struct mhu_v2_x_dev_t MHU1_HSE_DEV = {0, MHU_V2_X_SENDER_FRAME};
struct mhu_v2_x_dev_t MHU1_SEH_DEV = {0, MHU_V2_X_RECEIVER_FRAME};
static enum mhu_error_t error_mapping_to_mhu_error_t(enum mhu_v2_x_error_t err)
{
switch (err) {
case MHU_V_2_X_ERR_NONE:
return MHU_ERR_NONE;
case MHU_V_2_X_ERR_NOT_INIT:
return MHU_ERR_NOT_INIT;
case MHU_V_2_X_ERR_ALREADY_INIT:
return MHU_ERR_ALREADY_INIT;
case MHU_V_2_X_ERR_UNSUPPORTED_VERSION:
return MHU_ERR_UNSUPPORTED_VERSION;
case MHU_V_2_X_ERR_INVALID_ARG:
return MHU_ERR_INVALID_ARG;
case MHU_V_2_X_ERR_GENERAL:
return MHU_ERR_GENERAL;
default:
return MHU_ERR_GENERAL;
}
}
static enum mhu_v2_x_error_t signal_and_wait_for_clear(void)
{
enum mhu_v2_x_error_t err;
struct mhu_v2_x_dev_t *dev = &MHU1_HSE_DEV;
uint32_t val = MHU_NOTIFY_VALUE;
/* Using the last channel for notifications */
uint32_t channel_notify = mhu_v2_x_get_num_channel_implemented(dev) - 1;
err = mhu_v2_x_channel_send(dev, channel_notify, val);
if (err != MHU_V_2_X_ERR_NONE) {
return err;
}
do {
err = mhu_v2_x_channel_poll(dev, channel_notify, &val);
if (err != MHU_V_2_X_ERR_NONE) {
break;
}
} while (val != 0);
return err;
}
static enum mhu_v2_x_error_t wait_for_signal(void)
{
enum mhu_v2_x_error_t err;
struct mhu_v2_x_dev_t *dev = &MHU1_SEH_DEV;
uint32_t val = 0;
/* Using the last channel for notifications */
uint32_t channel_notify = mhu_v2_x_get_num_channel_implemented(dev) - 1;
do {
err = mhu_v2_x_channel_receive(dev, channel_notify, &val);
if (err != MHU_V_2_X_ERR_NONE) {
break;
}
} while (val != MHU_NOTIFY_VALUE);
return err;
}
static enum mhu_v2_x_error_t clear_and_wait_for_next_signal(void)
{
enum mhu_v2_x_error_t err;
struct mhu_v2_x_dev_t *dev = &MHU1_SEH_DEV;
uint32_t num_channels = mhu_v2_x_get_num_channel_implemented(dev);
uint32_t i;
/* Clear all channels */
for (i = 0; i < num_channels; ++i) {
err = mhu_v2_x_channel_clear(dev, i);
if (err != MHU_V_2_X_ERR_NONE) {
return err;
}
}
return wait_for_signal();
}
enum mhu_error_t mhu_init_sender(uintptr_t mhu_sender_base)
{
enum mhu_v2_x_error_t err;
assert(mhu_sender_base != (uintptr_t)NULL);
MHU1_HSE_DEV.base = mhu_sender_base;
err = mhu_v2_x_driver_init(&MHU1_HSE_DEV, MHU_REV_READ_FROM_HW);
return error_mapping_to_mhu_error_t(err);
}
enum mhu_error_t mhu_init_receiver(uintptr_t mhu_receiver_base)
{
enum mhu_v2_x_error_t err;
uint32_t num_channels, i;
assert(mhu_receiver_base != (uintptr_t)NULL);
MHU1_SEH_DEV.base = mhu_receiver_base;
err = mhu_v2_x_driver_init(&MHU1_SEH_DEV, MHU_REV_READ_FROM_HW);
if (err != MHU_V_2_X_ERR_NONE) {
return error_mapping_to_mhu_error_t(err);
}
num_channels = mhu_v2_x_get_num_channel_implemented(&MHU1_SEH_DEV);
/* Mask all channels except the notifying channel */
for (i = 0; i < (num_channels - 1); ++i) {
err = mhu_v2_x_channel_mask_set(&MHU1_SEH_DEV, i, UINT32_MAX);
if (err != MHU_V_2_X_ERR_NONE) {
return error_mapping_to_mhu_error_t(err);
}
}
/* The last channel is used for notifications */
err = mhu_v2_x_channel_mask_clear(
&MHU1_SEH_DEV, (num_channels - 1), UINT32_MAX);
return error_mapping_to_mhu_error_t(err);
}
/*
* Public function. See mhu.h
*
* The basic steps of transferring a message:
* 1. Initiate MHU transfer.
* 2. Send over the size of the payload on Channel 1. It is the very first
* 4 Bytes of the transfer. Continue with Channel 2.
* 3. Send over the payload, writing the channels one after the other
* (4 Bytes each). The last available channel is reserved for controlling
* the transfer.
* When the last channel is reached or no more data is left, STOP.
* 4. Notify the receiver using the last channel and wait for acknowledge.
* If there is still data to transfer, jump to step 3. Otherwise, proceed.
* 5. Close MHU transfer.
*
*/
enum mhu_error_t mhu_send_data(const uint8_t *send_buffer, size_t size)
{
enum mhu_v2_x_error_t err;
struct mhu_v2_x_dev_t *dev = &MHU1_HSE_DEV;
uint32_t num_channels = mhu_v2_x_get_num_channel_implemented(dev);
uint32_t chan = 0;
uint32_t i;
uint32_t *p;
/* For simplicity, require the send_buffer to be 4-byte aligned */
if ((uintptr_t)send_buffer & 0x3U) {
return MHU_ERR_INVALID_ARG;
}
err = mhu_v2_x_initiate_transfer(dev);
if (err != MHU_V_2_X_ERR_NONE) {
return error_mapping_to_mhu_error_t(err);
}
/* First send over the size of the actual message */
err = mhu_v2_x_channel_send(dev, chan, (uint32_t)size);
if (err != MHU_V_2_X_ERR_NONE) {
return error_mapping_to_mhu_error_t(err);
}
chan++;
p = (uint32_t *)send_buffer;
for (i = 0; i < size; i += 4) {
err = mhu_v2_x_channel_send(dev, chan, *p++);
if (err != MHU_V_2_X_ERR_NONE) {
return error_mapping_to_mhu_error_t(err);
}
if (++chan == (num_channels - 1)) {
err = signal_and_wait_for_clear();
if (err != MHU_V_2_X_ERR_NONE) {
return error_mapping_to_mhu_error_t(err);
}
chan = 0;
}
}
/* Signal the end of transfer.
* It's not required to send a signal when the message was
* perfectly-aligned (num_channels - 1 channels were used in the last
* round) preventing it from signaling twice at the end of transfer.
*/
if (chan != 0) {
err = signal_and_wait_for_clear();
if (err != MHU_V_2_X_ERR_NONE) {
return error_mapping_to_mhu_error_t(err);
}
}
err = mhu_v2_x_close_transfer(dev);
return error_mapping_to_mhu_error_t(err);
}
/*
* Public function. See mhu.h
*
* The basic steps of receiving a message:
* 1. Read the size of the payload from Channel 1. It is the very first
* 4 Bytes of the transfer. Continue with Channel 2.
* 2. Receive the payload, read the channels one after the other
* (4 Bytes each). The last available channel is reserved for controlling
* the transfer.
* When the last channel is reached clear all the channels
* (also sending an acknowledge on the last channel).
* 3. If there is still data to receive wait for a notification on the last
* channel and jump to step 2 as soon as it arrived. Otherwise, proceed.
* 4. End of transfer.
*
*/
enum mhu_error_t mhu_receive_data(uint8_t *receive_buffer, size_t *size)
{
enum mhu_v2_x_error_t err;
struct mhu_v2_x_dev_t *dev = &MHU1_SEH_DEV;
uint32_t num_channels = mhu_v2_x_get_num_channel_implemented(dev);
uint32_t chan = 0;
uint32_t message_len;
uint32_t i;
uint32_t *p;
/* For simplicity, require:
* - the receive_buffer to be 4-byte aligned,
* - the buffer size to be a multiple of 4.
*/
if (((uintptr_t)receive_buffer & 0x3U) || (*size & 0x3U)) {
return MHU_ERR_INVALID_ARG;
}
/* Busy wait for incoming reply */
err = wait_for_signal();
if (err != MHU_V_2_X_ERR_NONE) {
return error_mapping_to_mhu_error_t(err);
}
/* The first word is the length of the actual message */
err = mhu_v2_x_channel_receive(dev, chan, &message_len);
if (err != MHU_V_2_X_ERR_NONE) {
return error_mapping_to_mhu_error_t(err);
}
chan++;
if (message_len > *size) {
/* Message buffer too small */
*size = message_len;
return MHU_ERR_BUFFER_TOO_SMALL;
}
p = (uint32_t *)receive_buffer;
for (i = 0; i < message_len; i += 4) {
err = mhu_v2_x_channel_receive(dev, chan, p++);
if (err != MHU_V_2_X_ERR_NONE) {
return error_mapping_to_mhu_error_t(err);
}
/* Only wait for next transfer if there is still missing data */
if (++chan == (num_channels - 1) && (message_len - i) > 4) {
/* Busy wait for next transfer */
err = clear_and_wait_for_next_signal();
if (err != MHU_V_2_X_ERR_NONE) {
return error_mapping_to_mhu_error_t(err);
}
chan = 0;
}
}
/* Clear all channels */
for (i = 0; i < num_channels; ++i) {
err = mhu_v2_x_channel_clear(dev, i);
if (err != MHU_V_2_X_ERR_NONE) {
return error_mapping_to_mhu_error_t(err);
}
}
*size = message_len;
return MHU_ERR_NONE;
}
size_t mhu_get_max_message_size(void)
{
struct mhu_v2_x_dev_t *dev = &MHU1_SEH_DEV;
uint32_t num_channels = mhu_v2_x_get_num_channel_implemented(dev);
assert(num_channels != 0);
return num_channels * sizeof(uint32_t);
}
@@ -0,0 +1,264 @@
/*
* Copyright (c) 2016-2020, ARM Limited and Contributors. All rights reserved.
*
* SPDX-License-Identifier: BSD-3-Clause
*/
#include <arch.h>
#include <asm_macros.S>
#include <assert_macros.S>
#include <console_macros.S>
#include <drivers/arm/pl011.h>
/*
* "core" functions are low-level implementations that don't require
* writeable memory and are thus safe to call in BL1 crash context.
*/
.globl console_pl011_core_init
.globl console_pl011_core_putc
.globl console_pl011_core_getc
.globl console_pl011_core_flush
.globl console_pl011_putc
.globl console_pl011_getc
.globl console_pl011_flush
/* -----------------------------------------------
* int console_core_init(uintptr_t base_addr,
* unsigned int uart_clk, unsigned int baud_rate)
* Function to initialize the console without a
* C Runtime to print debug information. This
* function will be accessed by console_init and
* crash reporting.
* In: r0 - console base address
* r1 - Uart clock in Hz
* r2 - Baud rate
* Out: return 1 on success else 0 on error
* Clobber list : r1, r2, r3
* -----------------------------------------------
*/
func console_pl011_core_init
/* Check the input base address */
cmp r0, #0
beq core_init_fail
#if !PL011_GENERIC_UART
/* Check baud rate and uart clock for sanity */
cmp r1, #0
beq core_init_fail
cmp r2, #0
beq core_init_fail
/* Disable the UART before initialization */
ldr r3, [r0, #UARTCR]
bic r3, r3, #PL011_UARTCR_UARTEN
str r3, [r0, #UARTCR]
/* Program the baudrate */
/* Divisor = (Uart clock * 4) / baudrate */
lsl r1, r1, #2
#if (ARM_ARCH_MAJOR == 7) && !defined(ARMV7_SUPPORTS_VIRTUALIZATION)
push {r0,r3}
softudiv r0,r1,r2,r3
mov r2, r0
pop {r0,r3}
#else
udiv r2, r1, r2
#endif
/* IBRD = Divisor >> 6 */
lsr r1, r2, #6
/* Write the IBRD */
str r1, [r0, #UARTIBRD]
/* FBRD = Divisor & 0x3F */
and r1, r2, #0x3f
/* Write the FBRD */
str r1, [r0, #UARTFBRD]
mov r1, #PL011_LINE_CONTROL
str r1, [r0, #UARTLCR_H]
/* Clear any pending errors */
mov r1, #0
str r1, [r0, #UARTECR]
/* Enable tx, rx, and uart overall */
ldr r1, =(PL011_UARTCR_RXE | PL011_UARTCR_TXE | PL011_UARTCR_UARTEN)
str r1, [r0, #UARTCR]
#endif
mov r0, #1
bx lr
core_init_fail:
mov r0, #0
bx lr
endfunc console_pl011_core_init
.globl console_pl011_register
/* -------------------------------------------------------
* int console_pl011_register(uintptr_t baseaddr,
* uint32_t clock, uint32_t baud,
* console_t *console);
* Function to initialize and register a new PL011
* console. Storage passed in for the console struct
* *must* be persistent (i.e. not from the stack).
* In: r0 - UART register base address
* r1 - UART clock in Hz
* r2 - Baud rate
* r3 - pointer to empty console_t struct
* Out: return 1 on success, 0 on error
* Clobber list : r0, r1, r2
* -------------------------------------------------------
*/
func console_pl011_register
push {r4, lr}
mov r4, r3
cmp r4, #0
beq register_fail
str r0, [r4, #CONSOLE_T_BASE]
bl console_pl011_core_init
cmp r0, #0
beq register_fail
mov r0, r4
pop {r4, lr}
finish_console_register pl011 putc=1, getc=1, flush=1
register_fail:
pop {r4, pc}
endfunc console_pl011_register
/* --------------------------------------------------------
* int console_core_putc(int c, uintptr_t base_addr)
* Function to output a character over the console. It
* returns the character printed on success or -1 on error.
* In : r0 - character to be printed
* r1 - console base address
* Out : return -1 on error else return character.
* Clobber list : r2
* --------------------------------------------------------
*/
func console_pl011_core_putc
/* Check the input parameter */
cmp r1, #0
beq putc_error
/* Prepend '\r' to '\n' */
cmp r0, #0xA
bne 2f
1:
/* Check if the transmit FIFO is full */
ldr r2, [r1, #UARTFR]
tst r2, #PL011_UARTFR_TXFF
bne 1b
mov r2, #0xD
str r2, [r1, #UARTDR]
2:
/* Check if the transmit FIFO is full */
ldr r2, [r1, #UARTFR]
tst r2, #PL011_UARTFR_TXFF
bne 2b
str r0, [r1, #UARTDR]
bx lr
putc_error:
mov r0, #-1
bx lr
endfunc console_pl011_core_putc
/* --------------------------------------------------------
* int console_pl011_putc(int c, console_t *console)
* Function to output a character over the console. It
* returns the character printed on success or -1 on error.
* In: r0 - character to be printed
* r1 - pointer to console_t structure
* Out : return -1 on error else return character.
* Clobber list: r2
* -------------------------------------------------------
*/
func console_pl011_putc
#if ENABLE_ASSERTIONS
cmp r1, #0
ASM_ASSERT(ne)
#endif /* ENABLE_ASSERTIONS */
ldr r1, [r1, #CONSOLE_T_BASE]
b console_pl011_core_putc
endfunc console_pl011_putc
/* ---------------------------------------------
* int console_core_getc(uintptr_t base_addr)
* Function to get a character from the console.
* It returns the character grabbed on success
* or -1 on error.
* In : r0 - console base address
* Clobber list : r0, r1
* ---------------------------------------------
*/
func console_pl011_core_getc
cmp r0, #0
beq getc_error
1:
/* Check if the receive FIFO is empty */
ldr r1, [r0, #UARTFR]
tst r1, #PL011_UARTFR_RXFE
bne 1b
ldr r1, [r0, #UARTDR]
mov r0, r1
bx lr
getc_error:
mov r0, #-1
bx lr
endfunc console_pl011_core_getc
/* ------------------------------------------------
* int console_pl011_getc(console_t *console)
* Function to get a character from the console.
* It returns the character grabbed on success
* or -1 if no character is available.
* In : r0 - pointer to console_t structure
* Out: r0 - character if available, else -1
* Clobber list: r0, r1
* ------------------------------------------------
*/
func console_pl011_getc
#if ENABLE_ASSERTIONS
cmp r0, #0
ASM_ASSERT(ne)
#endif /* ENABLE_ASSERTIONS */
ldr r0, [r0, #CONSOLE_T_BASE]
b console_pl011_core_getc
endfunc console_pl011_getc
/* ---------------------------------------------
* void console_core_flush(uintptr_t base_addr)
* Function to force a write of all buffered
* data that hasn't been output.
* In : r0 - console base address
* Out : void
* Clobber list : r0, r1
* ---------------------------------------------
*/
func console_pl011_core_flush
#if ENABLE_ASSERTIONS
cmp r0, #0
ASM_ASSERT(ne)
#endif /* ENABLE_ASSERTIONS */
1:
/* Loop while the transmit FIFO is busy */
ldr r1, [r0, #UARTFR]
tst r1, #PL011_UARTFR_BUSY
bne 1b
bx lr
endfunc console_pl011_core_flush
/* ---------------------------------------------
* void console_pl011_flush(console_t *console)
* Function to force a write of all buffered
* data that hasn't been output.
* In : r0 - pointer to console_t structure
* Out : void
* Clobber list: r0, r1
* ---------------------------------------------
*/
func console_pl011_flush
#if ENABLE_ASSERTIONS
cmp r0, #0
ASM_ASSERT(ne)
#endif /* ENABLE_ASSERTIONS */
ldr r0, [r0, #CONSOLE_T_BASE]
b console_pl011_core_flush
endfunc console_pl011_flush
@@ -0,0 +1,247 @@
/*
* Copyright (c) 2013-2020, ARM Limited and Contributors. All rights reserved.
*
* SPDX-License-Identifier: BSD-3-Clause
*/
#include <arch.h>
#include <asm_macros.S>
#include <assert_macros.S>
#include <console_macros.S>
#include <drivers/arm/pl011.h>
/*
* "core" functions are low-level implementations that don't require
* writable memory and are thus safe to call in BL1 crash context.
*/
.globl console_pl011_core_init
.globl console_pl011_core_putc
.globl console_pl011_core_getc
.globl console_pl011_core_flush
.globl console_pl011_putc
.globl console_pl011_getc
.globl console_pl011_flush
/* -----------------------------------------------
* int console_pl011_core_init(uintptr_t base_addr,
* unsigned int uart_clk, unsigned int baud_rate)
* Function to initialize the console without a
* C Runtime to print debug information. This
* function will be accessed by console_init and
* crash reporting.
* In: x0 - console base address
* w1 - Uart clock in Hz
* w2 - Baud rate
* Out: return 1 on success else 0 on error
* Clobber list : x1, x2, x3, x4
* -----------------------------------------------
*/
func console_pl011_core_init
/* Check the input base address */
cbz x0, core_init_fail
#if !PL011_GENERIC_UART
/* Check baud rate and uart clock for sanity */
cbz w1, core_init_fail
cbz w2, core_init_fail
/* Disable uart before programming */
ldr w3, [x0, #UARTCR]
mov w4, #PL011_UARTCR_UARTEN
bic w3, w3, w4
str w3, [x0, #UARTCR]
/* Program the baudrate */
/* Divisor = (Uart clock * 4) / baudrate */
lsl w1, w1, #2
udiv w2, w1, w2
/* IBRD = Divisor >> 6 */
lsr w1, w2, #6
/* Write the IBRD */
str w1, [x0, #UARTIBRD]
/* FBRD = Divisor & 0x3F */
and w1, w2, #0x3f
/* Write the FBRD */
str w1, [x0, #UARTFBRD]
mov w1, #PL011_LINE_CONTROL
str w1, [x0, #UARTLCR_H]
/* Clear any pending errors */
str wzr, [x0, #UARTECR]
/* Enable tx, rx, and uart overall */
mov w1, #(PL011_UARTCR_RXE | PL011_UARTCR_TXE | PL011_UARTCR_UARTEN)
str w1, [x0, #UARTCR]
#endif
mov w0, #1
ret
core_init_fail:
mov w0, wzr
ret
endfunc console_pl011_core_init
.globl console_pl011_register
/* -----------------------------------------------
* int console_pl011_register(uintptr_t baseaddr,
* uint32_t clock, uint32_t baud,
* console_t *console);
* Function to initialize and register a new PL011
* console. Storage passed in for the console struct
* *must* be persistent (i.e. not from the stack).
* In: x0 - UART register base address
* w1 - UART clock in Hz
* w2 - Baud rate
* x3 - pointer to empty console_t struct
* Out: return 1 on success, 0 on error
* Clobber list : x0, x1, x2, x6, x7, x14
* -----------------------------------------------
*/
func console_pl011_register
mov x7, x30
mov x6, x3
cbz x6, register_fail
str x0, [x6, #CONSOLE_T_BASE]
bl console_pl011_core_init
cbz x0, register_fail
mov x0, x6
mov x30, x7
finish_console_register pl011 putc=1, getc=1, flush=1
register_fail:
ret x7
endfunc console_pl011_register
/* --------------------------------------------------------
* int console_pl011_core_putc(int c, uintptr_t base_addr)
* Function to output a character over the console. It
* returns the character printed on success or -1 on error.
* In : w0 - character to be printed
* x1 - console base address
* Out : return -1 on error else return character.
* Clobber list : x2
* --------------------------------------------------------
*/
func console_pl011_core_putc
#if ENABLE_ASSERTIONS
cmp x1, #0
ASM_ASSERT(ne)
#endif /* ENABLE_ASSERTIONS */
/* Prepend '\r' to '\n' */
cmp w0, #0xA
b.ne 2f
1:
/* Check if the transmit FIFO is full */
ldr w2, [x1, #UARTFR]
tbnz w2, #PL011_UARTFR_TXFF_BIT, 1b
mov w2, #0xD
str w2, [x1, #UARTDR]
2:
/* Check if the transmit FIFO is full */
ldr w2, [x1, #UARTFR]
tbnz w2, #PL011_UARTFR_TXFF_BIT, 2b
str w0, [x1, #UARTDR]
ret
endfunc console_pl011_core_putc
/* --------------------------------------------------------
* int console_pl011_putc(int c, console_t *console)
* Function to output a character over the console. It
* returns the character printed on success or -1 on error.
* In : w0 - character to be printed
* x1 - pointer to console_t structure
* Out : return -1 on error else return character.
* Clobber list : x2
* --------------------------------------------------------
*/
func console_pl011_putc
#if ENABLE_ASSERTIONS
cmp x1, #0
ASM_ASSERT(ne)
#endif /* ENABLE_ASSERTIONS */
ldr x1, [x1, #CONSOLE_T_BASE]
b console_pl011_core_putc
endfunc console_pl011_putc
/* ---------------------------------------------
* int console_pl011_core_getc(uintptr_t base_addr)
* Function to get a character from the console.
* It returns the character grabbed on success
* or -1 if no character is available.
* In : x0 - console base address
* Out: w0 - character if available, else -1
* Clobber list : x0, x1
* ---------------------------------------------
*/
func console_pl011_core_getc
#if ENABLE_ASSERTIONS
cmp x0, #0
ASM_ASSERT(ne)
#endif /* ENABLE_ASSERTIONS */
/* Check if the receive FIFO is empty */
ldr w1, [x0, #UARTFR]
tbnz w1, #PL011_UARTFR_RXFE_BIT, no_char
ldr w1, [x0, #UARTDR]
mov w0, w1
ret
no_char:
mov w0, #ERROR_NO_PENDING_CHAR
ret
endfunc console_pl011_core_getc
/* ---------------------------------------------
* int console_pl011_getc(console_t *console)
* Function to get a character from the console.
* It returns the character grabbed on success
* or -1 if no character is available.
* In : x0 - pointer to console_t structure
* Out: w0 - character if available, else -1
* Clobber list : x0, x1
* ---------------------------------------------
*/
func console_pl011_getc
#if ENABLE_ASSERTIONS
cmp x0, #0
ASM_ASSERT(ne)
#endif /* ENABLE_ASSERTIONS */
ldr x0, [x0, #CONSOLE_T_BASE]
b console_pl011_core_getc
endfunc console_pl011_getc
/* ---------------------------------------------
* void console_pl011_core_flush(uintptr_t base_addr)
* Function to force a write of all buffered
* data that hasn't been output.
* In : x0 - console base address
* Out : void.
* Clobber list : x0, x1
* ---------------------------------------------
*/
func console_pl011_core_flush
#if ENABLE_ASSERTIONS
cmp x0, #0
ASM_ASSERT(ne)
#endif /* ENABLE_ASSERTIONS */
1:
/* Loop until the transmit FIFO is empty */
ldr w1, [x0, #UARTFR]
tbnz w1, #PL011_UARTFR_BUSY_BIT, 1b
ret
endfunc console_pl011_core_flush
/* ---------------------------------------------
* void console_pl011_flush(console_t *console)
* Function to force a write of all buffered
* data that hasn't been output.
* In : x0 - pointer to console_t structure
* Out : void
* Clobber list : x0, x1
* ---------------------------------------------
*/
func console_pl011_flush
#if ENABLE_ASSERTIONS
cmp x0, #0
ASM_ASSERT(ne)
#endif /* ENABLE_ASSERTIONS */
ldr x0, [x0, #CONSOLE_T_BASE]
b console_pl011_core_flush
endfunc console_pl011_flush
@@ -0,0 +1,142 @@
/*
* Copyright (c) 2016, ARM Limited and Contributors. All rights reserved.
*
* SPDX-License-Identifier: BSD-3-Clause
*
* ARM PL061 GPIO Driver.
* Reference to ARM DDI 0190B document.
*
*/
#include <assert.h>
#include <errno.h>
#include <common/debug.h>
#include <drivers/arm/pl061_gpio.h>
#include <drivers/gpio.h>
#include <lib/cassert.h>
#include <lib/mmio.h>
#include <lib/utils.h>
#if !PLAT_PL061_MAX_GPIOS
# define PLAT_PL061_MAX_GPIOS 32
#endif /* PLAT_PL061_MAX_GPIOS */
CASSERT(PLAT_PL061_MAX_GPIOS > 0, assert_plat_pl061_max_gpios);
#define MAX_GPIO_DEVICES ((PLAT_PL061_MAX_GPIOS + \
(GPIOS_PER_PL061 - 1)) / GPIOS_PER_PL061)
#define PL061_GPIO_DIR 0x400
#define GPIOS_PER_PL061 8
static int pl061_get_direction(int gpio);
static void pl061_set_direction(int gpio, int direction);
static int pl061_get_value(int gpio);
static void pl061_set_value(int gpio, int value);
static uintptr_t pl061_reg_base[MAX_GPIO_DEVICES];
static const gpio_ops_t pl061_gpio_ops = {
.get_direction = pl061_get_direction,
.set_direction = pl061_set_direction,
.get_value = pl061_get_value,
.set_value = pl061_set_value,
};
static int pl061_get_direction(int gpio)
{
uintptr_t base_addr;
unsigned int data, offset;
assert((gpio >= 0) && (gpio < PLAT_PL061_MAX_GPIOS));
base_addr = pl061_reg_base[gpio / GPIOS_PER_PL061];
offset = gpio % GPIOS_PER_PL061;
data = mmio_read_8(base_addr + PL061_GPIO_DIR);
if (data & BIT(offset))
return GPIO_DIR_OUT;
return GPIO_DIR_IN;
}
static void pl061_set_direction(int gpio, int direction)
{
uintptr_t base_addr;
unsigned int data, offset;
assert((gpio >= 0) && (gpio < PLAT_PL061_MAX_GPIOS));
base_addr = pl061_reg_base[gpio / GPIOS_PER_PL061];
offset = gpio % GPIOS_PER_PL061;
if (direction == GPIO_DIR_OUT) {
data = mmio_read_8(base_addr + PL061_GPIO_DIR) | BIT(offset);
mmio_write_8(base_addr + PL061_GPIO_DIR, data);
} else {
data = mmio_read_8(base_addr + PL061_GPIO_DIR) & ~BIT(offset);
mmio_write_8(base_addr + PL061_GPIO_DIR, data);
}
}
/*
* The offset of GPIODATA register is 0.
* The values read from GPIODATA are determined for each bit, by the mask bit
* derived from the address used to access the data register, PADDR[9:2].
* Bits that are 1 in the address mask cause the corresponding bits in GPIODATA
* to be read, and bits that are 0 in the address mask cause the corresponding
* bits in GPIODATA to be read as 0, regardless of their value.
*/
static int pl061_get_value(int gpio)
{
uintptr_t base_addr;
unsigned int offset;
assert((gpio >= 0) && (gpio < PLAT_PL061_MAX_GPIOS));
base_addr = pl061_reg_base[gpio / GPIOS_PER_PL061];
offset = gpio % GPIOS_PER_PL061;
if (mmio_read_8(base_addr + BIT(offset + 2)))
return GPIO_LEVEL_HIGH;
return GPIO_LEVEL_LOW;
}
/*
* In order to write GPIODATA, the corresponding bits in the mask, resulting
* from the address bus, PADDR[9:2], must be HIGH. Otherwise the bit values
* remain unchanged by the write.
*/
static void pl061_set_value(int gpio, int value)
{
uintptr_t base_addr;
int offset;
assert((gpio >= 0) && (gpio < PLAT_PL061_MAX_GPIOS));
base_addr = pl061_reg_base[gpio / GPIOS_PER_PL061];
offset = gpio % GPIOS_PER_PL061;
if (value == GPIO_LEVEL_HIGH)
mmio_write_8(base_addr + BIT(offset + 2), BIT(offset));
else
mmio_write_8(base_addr + BIT(offset + 2), 0);
}
/*
* Register the PL061 GPIO controller with a base address and the offset
* of start pin in this GPIO controller.
* This function is called after pl061_gpio_ops_init().
*/
void pl061_gpio_register(uintptr_t base_addr, int gpio_dev)
{
assert((gpio_dev >= 0) && (gpio_dev < MAX_GPIO_DEVICES));
pl061_reg_base[gpio_dev] = base_addr;
}
/*
* Initialize PL061 GPIO controller with the total GPIO numbers in SoC.
*/
void pl061_gpio_init(void)
{
gpio_init(&pl061_gpio_ops);
}
@@ -0,0 +1,170 @@
/*
* Copyright (c) 2022, Arm Limited. All rights reserved.
*
* SPDX-License-Identifier: BSD-3-Clause
*/
#include <stdint.h>
#include <string.h>
#include <common/debug.h>
#include <drivers/arm/mhu.h>
#include <drivers/arm/rss_comms.h>
#include <psa/client.h>
#include <rss_comms_protocol.h>
/* Union as message space and reply space are never used at the same time, and this saves space as
* we can overlap them.
*/
union __packed __attribute__((aligned(4))) rss_comms_io_buffer_t {
struct serialized_rss_comms_msg_t msg;
struct serialized_rss_comms_reply_t reply;
};
static uint8_t select_protocol_version(const psa_invec *in_vec, size_t in_len,
const psa_outvec *out_vec, size_t out_len)
{
size_t comms_mhu_msg_size;
size_t comms_embed_msg_min_size;
size_t comms_embed_reply_min_size;
size_t in_size_total = 0;
size_t out_size_total = 0;
size_t i;
for (i = 0U; i < in_len; ++i) {
in_size_total += in_vec[i].len;
}
for (i = 0U; i < out_len; ++i) {
out_size_total += out_vec[i].len;
}
comms_mhu_msg_size = mhu_get_max_message_size();
comms_embed_msg_min_size = sizeof(struct serialized_rss_comms_header_t) +
sizeof(struct rss_embed_msg_t) -
PLAT_RSS_COMMS_PAYLOAD_MAX_SIZE;
comms_embed_reply_min_size = sizeof(struct serialized_rss_comms_header_t) +
sizeof(struct rss_embed_reply_t) -
PLAT_RSS_COMMS_PAYLOAD_MAX_SIZE;
/* Use embed if we can pack into one message and reply, else use
* pointer_access. The underlying MHU transport protocol uses a
* single uint32_t to track the length, so the amount of data that
* can be in a message is 4 bytes less than mhu_get_max_message_size
* reports.
*
* TODO tune this with real performance numbers, it's possible a
* pointer_access message is less performant than multiple embed
* messages due to ATU configuration costs to allow access to the
* pointers.
*/
if ((comms_embed_msg_min_size + in_size_total > comms_mhu_msg_size - sizeof(uint32_t))
|| (comms_embed_reply_min_size + out_size_total > comms_mhu_msg_size) - sizeof(uint32_t)) {
return RSS_COMMS_PROTOCOL_POINTER_ACCESS;
} else {
return RSS_COMMS_PROTOCOL_EMBED;
}
}
psa_status_t psa_call(psa_handle_t handle, int32_t type, const psa_invec *in_vec, size_t in_len,
psa_outvec *out_vec, size_t out_len)
{
/* Declared statically to avoid using huge amounts of stack space. Maybe revisit if
* functions not being reentrant becomes a problem.
*/
static union rss_comms_io_buffer_t io_buf;
enum mhu_error_t err;
psa_status_t status;
static uint8_t seq_num = 1U;
size_t msg_size;
size_t reply_size = sizeof(io_buf.reply);
psa_status_t return_val;
size_t idx;
if (type > INT16_MAX || type < INT16_MIN || in_len > PSA_MAX_IOVEC
|| out_len > PSA_MAX_IOVEC) {
return PSA_ERROR_INVALID_ARGUMENT;
}
io_buf.msg.header.seq_num = seq_num,
/* No need to distinguish callers (currently concurrent calls are not supported). */
io_buf.msg.header.client_id = 1U,
io_buf.msg.header.protocol_ver = select_protocol_version(in_vec, in_len, out_vec, out_len);
status = rss_protocol_serialize_msg(handle, type, in_vec, in_len, out_vec,
out_len, &io_buf.msg, &msg_size);
if (status != PSA_SUCCESS) {
return status;
}
VERBOSE("[RSS-COMMS] Sending message\n");
VERBOSE("protocol_ver=%u\n", io_buf.msg.header.protocol_ver);
VERBOSE("seq_num=%u\n", io_buf.msg.header.seq_num);
VERBOSE("client_id=%u\n", io_buf.msg.header.client_id);
for (idx = 0; idx < in_len; idx++) {
VERBOSE("in_vec[%lu].len=%lu\n", idx, in_vec[idx].len);
VERBOSE("in_vec[%lu].buf=%p\n", idx, (void *)in_vec[idx].base);
}
err = mhu_send_data((uint8_t *)&io_buf.msg, msg_size);
if (err != MHU_ERR_NONE) {
return PSA_ERROR_COMMUNICATION_FAILURE;
}
#if DEBUG
/*
* Poisoning the message buffer (with a known pattern).
* Helps in detecting hypothetical RSS communication bugs.
*/
memset(&io_buf.msg, 0xA5, msg_size);
#endif
err = mhu_receive_data((uint8_t *)&io_buf.reply, &reply_size);
if (err != MHU_ERR_NONE) {
return PSA_ERROR_COMMUNICATION_FAILURE;
}
VERBOSE("[RSS-COMMS] Received reply\n");
VERBOSE("protocol_ver=%u\n", io_buf.reply.header.protocol_ver);
VERBOSE("seq_num=%u\n", io_buf.reply.header.seq_num);
VERBOSE("client_id=%u\n", io_buf.reply.header.client_id);
status = rss_protocol_deserialize_reply(out_vec, out_len, &return_val,
&io_buf.reply, reply_size);
if (status != PSA_SUCCESS) {
return status;
}
VERBOSE("return_val=%d\n", return_val);
for (idx = 0U; idx < out_len; idx++) {
VERBOSE("out_vec[%lu].len=%lu\n", idx, out_vec[idx].len);
VERBOSE("out_vec[%lu].buf=%p\n", idx, (void *)out_vec[idx].base);
}
/* Clear the MHU message buffer to remove assets from memory */
memset(&io_buf, 0x0, sizeof(io_buf));
seq_num++;
return return_val;
}
int rss_comms_init(uintptr_t mhu_sender_base, uintptr_t mhu_receiver_base)
{
enum mhu_error_t err;
err = mhu_init_sender(mhu_sender_base);
if (err != MHU_ERR_NONE) {
ERROR("[RSS-COMMS] Host to RSS MHU driver initialization failed: %d\n", err);
return -1;
}
err = mhu_init_receiver(mhu_receiver_base);
if (err != MHU_ERR_NONE) {
ERROR("[RSS-COMMS] RSS to Host MHU driver initialization failed: %d\n", err);
return -1;
}
return 0;
}
@@ -0,0 +1,22 @@
#
# Copyright (c) 2022, Arm Limited. All rights reserved.
#
# SPDX-License-Identifier: BSD-3-Clause
#
$(warning "RSS driver is an experimental feature")
RSS_COMMS_SOURCES := $(addprefix drivers/arm/rss/, \
rss_comms.c \
rss_comms_protocol.c \
rss_comms_protocol_embed.c \
rss_comms_protocol_pointer_access.c \
)
RSS_COMMS_SOURCES += $(addprefix drivers/arm/mhu/, \
mhu_v2_x.c \
mhu_wrapper_v2_x.c \
)
PLAT_INCLUDES += -Idrivers/arm/rss \
-Idrivers/arm/mhu
@@ -0,0 +1,75 @@
/*
* Copyright (c) 2022, Arm Limited. All rights reserved.
*
* SPDX-License-Identifier: BSD-3-Clause
*
*/
#include <assert.h>
#include <common/debug.h>
#include "rss_comms_protocol.h"
psa_status_t rss_protocol_serialize_msg(psa_handle_t handle,
int16_t type,
const psa_invec *in_vec,
uint8_t in_len,
const psa_outvec *out_vec,
uint8_t out_len,
struct serialized_rss_comms_msg_t *msg,
size_t *msg_len)
{
psa_status_t status;
assert(msg != NULL);
assert(msg_len != NULL);
assert(in_vec != NULL);
switch (msg->header.protocol_ver) {
case RSS_COMMS_PROTOCOL_EMBED:
status = rss_protocol_embed_serialize_msg(handle, type, in_vec, in_len, out_vec,
out_len, &msg->msg.embed, msg_len);
if (status != PSA_SUCCESS) {
return status;
}
break;
case RSS_COMMS_PROTOCOL_POINTER_ACCESS:
status = rss_protocol_pointer_access_serialize_msg(handle, type, in_vec, in_len,
out_vec, out_len,
&msg->msg.pointer_access,
msg_len);
if (status != PSA_SUCCESS) {
return status;
}
break;
default:
return PSA_ERROR_NOT_SUPPORTED;
}
*msg_len += sizeof(struct serialized_rss_comms_header_t);
return PSA_SUCCESS;
}
psa_status_t rss_protocol_deserialize_reply(psa_outvec *out_vec,
uint8_t out_len,
psa_status_t *return_val,
const struct serialized_rss_comms_reply_t *reply,
size_t reply_size)
{
assert(reply != NULL);
assert(return_val != NULL);
switch (reply->header.protocol_ver) {
case RSS_COMMS_PROTOCOL_EMBED:
return rss_protocol_embed_deserialize_reply(out_vec, out_len, return_val,
&reply->reply.embed, reply_size);
case RSS_COMMS_PROTOCOL_POINTER_ACCESS:
return rss_protocol_pointer_access_deserialize_reply(out_vec, out_len, return_val,
&reply->reply.pointer_access,
reply_size);
default:
return PSA_ERROR_NOT_SUPPORTED;
}
return PSA_SUCCESS;
}
@@ -0,0 +1,67 @@
/*
* Copyright (c) 2022, Arm Limited. All rights reserved.
*
* SPDX-License-Identifier: BSD-3-Clause
*
*/
#ifndef __RSS_COMMS_PROTOCOL_H__
#define __RSS_COMMS_PROTOCOL_H__
#include <cdefs.h>
#include <stdint.h>
#include <psa/client.h>
#include "rss_comms_protocol_embed.h"
#include "rss_comms_protocol_pointer_access.h"
enum rss_comms_protocol_version_t {
RSS_COMMS_PROTOCOL_EMBED = 0,
RSS_COMMS_PROTOCOL_POINTER_ACCESS = 1,
};
struct __packed serialized_rss_comms_header_t {
uint8_t protocol_ver;
uint8_t seq_num;
uint16_t client_id;
};
/* MHU message passed from Host to RSS to deliver a PSA client call */
struct __packed serialized_rss_comms_msg_t {
struct serialized_rss_comms_header_t header;
union __packed {
struct rss_embed_msg_t embed;
struct rss_pointer_access_msg_t pointer_access;
} msg;
};
/* MHU reply message to hold the PSA client reply result returned by RSS */
struct __packed serialized_rss_comms_reply_t {
struct serialized_rss_comms_header_t header;
union __packed {
struct rss_embed_reply_t embed;
struct rss_pointer_access_reply_t pointer_access;
} reply;
};
/* in_len and out_len are uint8_ts, therefore if there are more than 255 iovecs
* an error may occur.
*/
CASSERT(PSA_MAX_IOVEC <= UINT8_MAX, assert_rss_comms_max_iovec_too_large);
psa_status_t rss_protocol_serialize_msg(psa_handle_t handle,
int16_t type,
const psa_invec *in_vec,
uint8_t in_len,
const psa_outvec *out_vec,
uint8_t out_len,
struct serialized_rss_comms_msg_t *msg,
size_t *msg_len);
psa_status_t rss_protocol_deserialize_reply(psa_outvec *out_vec,
uint8_t out_len,
psa_status_t *return_val,
const struct serialized_rss_comms_reply_t *reply,
size_t reply_size);
#endif /* __RSS_COMMS_PROTOCOL_H__ */
@@ -0,0 +1,91 @@
/*
* Copyright (c) 2022, Arm Limited. All rights reserved.
*
* SPDX-License-Identifier: BSD-3-Clause
*
*/
#include <assert.h>
#include <string.h>
#include <common/debug.h>
#include "rss_comms_protocol_embed.h"
#define TYPE_OFFSET (16U)
#define TYPE_MASK (0xFFFFUL << TYPE_OFFSET)
#define IN_LEN_OFFSET (8U)
#define IN_LEN_MASK (0xFFUL << IN_LEN_OFFSET)
#define OUT_LEN_OFFSET (0U)
#define OUT_LEN_MASK (0xFFUL << OUT_LEN_OFFSET)
#define PARAM_PACK(type, in_len, out_len) \
(((((uint32_t)type) << TYPE_OFFSET) & TYPE_MASK) | \
((((uint32_t)in_len) << IN_LEN_OFFSET) & IN_LEN_MASK) | \
((((uint32_t)out_len) << OUT_LEN_OFFSET) & OUT_LEN_MASK))
psa_status_t rss_protocol_embed_serialize_msg(psa_handle_t handle,
int16_t type,
const psa_invec *in_vec,
uint8_t in_len,
const psa_outvec *out_vec,
uint8_t out_len,
struct rss_embed_msg_t *msg,
size_t *msg_len)
{
uint32_t payload_size = 0;
uint32_t i;
assert(msg != NULL);
assert(msg_len != NULL);
assert(in_vec != NULL);
msg->ctrl_param = PARAM_PACK(type, in_len, out_len);
msg->handle = handle;
/* Fill msg iovec lengths */
for (i = 0U; i < in_len; ++i) {
msg->io_size[i] = in_vec[i].len;
}
for (i = 0U; i < out_len; ++i) {
msg->io_size[in_len + i] = out_vec[i].len;
}
for (i = 0U; i < in_len; ++i) {
if (in_vec[i].len > sizeof(msg->trailer) - payload_size) {
return PSA_ERROR_INVALID_ARGUMENT;
}
memcpy(msg->trailer + payload_size, in_vec[i].base, in_vec[i].len);
payload_size += in_vec[i].len;
}
/* Output the actual size of the message, to optimize sending */
*msg_len = sizeof(*msg) - sizeof(msg->trailer) + payload_size;
return PSA_SUCCESS;
}
psa_status_t rss_protocol_embed_deserialize_reply(psa_outvec *out_vec,
uint8_t out_len,
psa_status_t *return_val,
const struct rss_embed_reply_t *reply,
size_t reply_size)
{
uint32_t payload_offset = 0;
uint32_t i;
assert(reply != NULL);
assert(return_val != NULL);
for (i = 0U; i < out_len; ++i) {
if (sizeof(reply) - sizeof(reply->trailer) + payload_offset > reply_size) {
return PSA_ERROR_INVALID_ARGUMENT;
}
memcpy(out_vec[i].base, reply->trailer + payload_offset, out_vec[i].len);
payload_offset += out_vec[i].len;
}
*return_val = reply->return_val;
return PSA_SUCCESS;
}
@@ -0,0 +1,47 @@
/*
* Copyright (c) 2022, Arm Limited. All rights reserved.
*
* SPDX-License-Identifier: BSD-3-Clause
*
*/
#ifndef __RSS_COMMS_PROTOCOL_EMBED_H__
#define __RSS_COMMS_PROTOCOL_EMBED_H__
#include <cdefs.h>
#include <psa/client.h>
#include <platform_def.h>
struct __packed rss_embed_msg_t {
psa_handle_t handle;
uint32_t ctrl_param; /* type, in_len, out_len */
uint16_t io_size[PSA_MAX_IOVEC];
uint8_t trailer[PLAT_RSS_COMMS_PAYLOAD_MAX_SIZE];
};
struct __packed rss_embed_reply_t {
int32_t return_val;
uint16_t out_size[PSA_MAX_IOVEC];
uint8_t trailer[PLAT_RSS_COMMS_PAYLOAD_MAX_SIZE];
};
psa_status_t rss_protocol_embed_serialize_msg(psa_handle_t handle,
int16_t type,
const psa_invec *in_vec,
uint8_t in_len,
const psa_outvec *out_vec,
uint8_t out_len,
struct rss_embed_msg_t *msg,
size_t *msg_len);
psa_status_t rss_protocol_embed_deserialize_reply(psa_outvec *out_vec,
uint8_t out_len,
psa_status_t *return_val,
const struct rss_embed_reply_t *reply,
size_t reply_size);
#endif /* __RSS_COMMS_PROTOCOL_EMBED_H__ */
@@ -0,0 +1,74 @@
/*
* Copyright (c) 2022, Arm Limited. All rights reserved.
*
* SPDX-License-Identifier: BSD-3-Clause
*
*/
#include <assert.h>
#include "rss_comms_protocol_pointer_access.h"
#define TYPE_OFFSET (16U)
#define TYPE_MASK (0xFFFFUL << TYPE_OFFSET)
#define IN_LEN_OFFSET (8U)
#define IN_LEN_MASK (0xFFUL << IN_LEN_OFFSET)
#define OUT_LEN_OFFSET (0U)
#define OUT_LEN_MASK (0xFFUL << OUT_LEN_OFFSET)
#define PARAM_PACK(type, in_len, out_len) \
(((((uint32_t)type) << TYPE_OFFSET) & TYPE_MASK) | \
((((uint32_t)in_len) << IN_LEN_OFFSET) & IN_LEN_MASK) | \
((((uint32_t)out_len) << OUT_LEN_OFFSET) & OUT_LEN_MASK))
psa_status_t rss_protocol_pointer_access_serialize_msg(psa_handle_t handle,
int16_t type,
const psa_invec *in_vec,
uint8_t in_len,
const psa_outvec *out_vec,
uint8_t out_len,
struct rss_pointer_access_msg_t *msg,
size_t *msg_len)
{
unsigned int i;
assert(msg != NULL);
assert(msg_len != NULL);
assert(in_vec != NULL);
msg->ctrl_param = PARAM_PACK(type, in_len, out_len);
msg->handle = handle;
/* Fill msg iovec lengths */
for (i = 0U; i < in_len; ++i) {
msg->io_sizes[i] = in_vec[i].len;
msg->host_ptrs[i] = (uint64_t)in_vec[i].base;
}
for (i = 0U; i < out_len; ++i) {
msg->io_sizes[in_len + i] = out_vec[i].len;
msg->host_ptrs[in_len + i] = (uint64_t)out_vec[i].base;
}
*msg_len = sizeof(*msg);
return PSA_SUCCESS;
}
psa_status_t rss_protocol_pointer_access_deserialize_reply(psa_outvec *out_vec,
uint8_t out_len,
psa_status_t *return_val,
const struct rss_pointer_access_reply_t *reply,
size_t reply_size)
{
unsigned int i;
assert(reply != NULL);
assert(return_val != NULL);
for (i = 0U; i < out_len; ++i) {
out_vec[i].len = reply->out_sizes[i];
}
*return_val = reply->return_val;
return PSA_SUCCESS;
}
@@ -0,0 +1,42 @@
/*
* Copyright (c) 2022, Arm Limited. All rights reserved.
*
* SPDX-License-Identifier: BSD-3-Clause
*
*/
#ifndef __RSS_COMMS_PROTOCOL_POINTER_ACCESS_H__
#define __RSS_COMMS_PROTOCOL_POINTER_ACCESS_H__
#include <cdefs.h>
#include <psa/client.h>
struct __packed rss_pointer_access_msg_t {
psa_handle_t handle;
uint32_t ctrl_param;
uint32_t io_sizes[PSA_MAX_IOVEC];
uint64_t host_ptrs[PSA_MAX_IOVEC];
};
struct __packed rss_pointer_access_reply_t {
int32_t return_val;
uint32_t out_sizes[PSA_MAX_IOVEC];
};
psa_status_t rss_protocol_pointer_access_serialize_msg(psa_handle_t handle,
int16_t type,
const psa_invec *in_vec,
uint8_t in_len,
const psa_outvec *out_vec,
uint8_t out_len,
struct rss_pointer_access_msg_t *msg,
size_t *msg_len);
psa_status_t rss_protocol_pointer_access_deserialize_reply(psa_outvec *out_vec,
uint8_t out_len,
psa_status_t *return_val,
const struct rss_pointer_access_reply_t *reply,
size_t reply_size);
#endif /* __RSS_COMMS_PROTOCOL_POINTER_ACCESS_H__ */
@@ -0,0 +1,42 @@
/*
* Copyright (c) 2019, ARM Limited. All rights reserved.
*
* SPDX-License-Identifier: BSD-3-Clause
*/
#include <assert.h>
#include <stdint.h>
#include <drivers/arm/sbsa.h>
#include <lib/mmio.h>
#include <plat/common/platform.h>
void sbsa_watchdog_offset_reg_write(uintptr_t base, uint64_t value)
{
assert((value >> SBSA_WDOG_WOR_WIDTH) == 0);
mmio_write_32(base + SBSA_WDOG_WOR_LOW_OFFSET,
((uint32_t)value & UINT32_MAX));
mmio_write_32(base + SBSA_WDOG_WOR_HIGH_OFFSET, (uint32_t)(value >> 32));
}
/*
* Start the watchdog timer at base address "base" for a
* period of "ms" milliseconds.The watchdog has to be
* refreshed within this time period.
*/
void sbsa_wdog_start(uintptr_t base, uint64_t ms)
{
uint64_t counter_freq;
uint64_t offset_reg_value;
counter_freq = (uint64_t)plat_get_syscnt_freq2();
offset_reg_value = ms * counter_freq / 1000;
sbsa_watchdog_offset_reg_write(base, offset_reg_value);
mmio_write_32(base + SBSA_WDOG_WCS_OFFSET, SBSA_WDOG_WCS_EN);
}
/* Stop the watchdog */
void sbsa_wdog_stop(uintptr_t base)
{
mmio_write_32(base + SBSA_WDOG_WCS_OFFSET, (0x0));
}
@@ -0,0 +1,51 @@
/*
* Copyright (c) 2019, Arm Limited. All rights reserved.
*
* SPDX-License-Identifier: BSD-3-Clause
*/
#include <assert.h>
#include <drivers/arm/scu.h>
#include <lib/mmio.h>
#include <plat/common/platform.h>
#include <stdint.h>
/*******************************************************************************
* Turn ON snoop control unit. This is needed to synchronize the data between
* CPU's.
******************************************************************************/
void enable_snoop_ctrl_unit(uintptr_t base)
{
uint32_t scu_ctrl;
INFO("[SCU]: enabling snoop control unit ... \n");
assert(base != 0U);
scu_ctrl = mmio_read_32(base + SCU_CTRL_REG);
/* already enabled? */
if ((scu_ctrl & SCU_ENABLE_BIT) != 0) {
return;
}
scu_ctrl |= SCU_ENABLE_BIT;
mmio_write_32(base + SCU_CTRL_REG, scu_ctrl);
}
/*******************************************************************************
* Snoop Control Unit configuration register. This is read-only register and
* contains information such as
* - number of CPUs present
* - is a particular CPU operating in SMP mode or AMP mode
* - data cache size of a particular CPU
* - does SCU has ACP port
* - is L2CPRESENT
* NOTE: user of this API should interpert the bits in this register according
* to the TRM
******************************************************************************/
uint32_t read_snoop_ctrl_unit_cfg(uintptr_t base)
{
assert(base != 0U);
return mmio_read_32(base + SCU_CFG_REG);
}
@@ -0,0 +1,182 @@
/*
* Copyright (c) 2017-2022, ARM Limited and Contributors. All rights reserved.
*
* SPDX-License-Identifier: BSD-3-Clause
*/
#include <common/debug.h>
#include <cdefs.h>
#include <drivers/arm/smmu_v3.h>
#include <drivers/delay_timer.h>
#include <lib/mmio.h>
#include <arch_features.h>
/* SMMU poll number of retries */
#define SMMU_POLL_TIMEOUT_US U(1000)
static int smmuv3_poll(uintptr_t smmu_reg, uint32_t mask,
uint32_t value)
{
uint32_t reg_val;
uint64_t timeout;
/* Set 1ms timeout value */
timeout = timeout_init_us(SMMU_POLL_TIMEOUT_US);
do {
reg_val = mmio_read_32(smmu_reg);
if ((reg_val & mask) == value)
return 0;
} while (!timeout_elapsed(timeout));
ERROR("Timeout polling SMMUv3 register @%p\n", (void *)smmu_reg);
ERROR("Read value 0x%x, expected 0x%x\n", reg_val,
value == 0U ? reg_val & ~mask : reg_val | mask);
return -1;
}
/*
* Abort all incoming transactions in order to implement a default
* deny policy on reset.
*/
int __init smmuv3_security_init(uintptr_t smmu_base)
{
/* Attribute update has completed when SMMU_(S)_GBPA.Update bit is 0 */
if (smmuv3_poll(smmu_base + SMMU_GBPA, SMMU_GBPA_UPDATE, 0U) != 0U)
return -1;
/*
* SMMU_(S)_CR0 resets to zero with all streams bypassing the SMMU,
* so just abort all incoming transactions.
*/
mmio_setbits_32(smmu_base + SMMU_GBPA,
SMMU_GBPA_UPDATE | SMMU_GBPA_ABORT);
if (smmuv3_poll(smmu_base + SMMU_GBPA, SMMU_GBPA_UPDATE, 0U) != 0U)
return -1;
/* Check if the SMMU supports secure state */
if ((mmio_read_32(smmu_base + SMMU_S_IDR1) &
SMMU_S_IDR1_SECURE_IMPL) == 0U)
return 0;
/* Abort all incoming secure transactions */
if (smmuv3_poll(smmu_base + SMMU_S_GBPA, SMMU_S_GBPA_UPDATE, 0U) != 0U)
return -1;
mmio_setbits_32(smmu_base + SMMU_S_GBPA,
SMMU_S_GBPA_UPDATE | SMMU_S_GBPA_ABORT);
return smmuv3_poll(smmu_base + SMMU_S_GBPA, SMMU_S_GBPA_UPDATE, 0U);
}
/*
* Initialize the SMMU by invalidating all secure caches and TLBs.
* Abort all incoming transactions in order to implement a default
* deny policy on reset
*/
int __init smmuv3_init(uintptr_t smmu_base)
{
/* Abort all incoming transactions */
if (smmuv3_security_init(smmu_base) != 0)
return -1;
#if ENABLE_RME
if (get_armv9_2_feat_rme_support() != 0U) {
if ((mmio_read_32(smmu_base + SMMU_ROOT_IDR0) &
SMMU_ROOT_IDR0_ROOT_IMPL) == 0U) {
WARN("Skip SMMU GPC configuration.\n");
} else {
uint64_t gpccr_el3 = read_gpccr_el3();
uint64_t gptbr_el3 = read_gptbr_el3();
/* SMMU_ROOT_GPT_BASE_CFG[16] is RES0. */
gpccr_el3 &= ~(1UL << 16);
/*
* TODO: SMMU_ROOT_GPT_BASE_CFG is 64b in the spec,
* but SMMU model only accepts 32b access.
*/
mmio_write_32(smmu_base + SMMU_ROOT_GPT_BASE_CFG,
gpccr_el3);
/*
* pa_gpt_table_base[51:12] maps to GPTBR_EL3[39:0]
* whereas it maps to SMMU_ROOT_GPT_BASE[51:12]
* hence needs a 12 bit left shit.
*/
mmio_write_64(smmu_base + SMMU_ROOT_GPT_BASE,
gptbr_el3 << 12);
/*
* ACCESSEN=1: SMMU- and client-originated accesses are
* not terminated by this mechanism.
* GPCEN=1: All clients and SMMU-originated accesses,
* except GPT-walks, are subject to GPC.
*/
mmio_setbits_32(smmu_base + SMMU_ROOT_CR0,
SMMU_ROOT_CR0_GPCEN |
SMMU_ROOT_CR0_ACCESSEN);
/* Poll for ACCESSEN and GPCEN ack bits. */
if (smmuv3_poll(smmu_base + SMMU_ROOT_CR0ACK,
SMMU_ROOT_CR0_GPCEN |
SMMU_ROOT_CR0_ACCESSEN,
SMMU_ROOT_CR0_GPCEN |
SMMU_ROOT_CR0_ACCESSEN) != 0) {
WARN("Failed enabling SMMU GPC.\n");
/*
* Do not return in error, but fall back to
* invalidating all entries through the secure
* register file.
*/
}
}
}
#endif /* ENABLE_RME */
/*
* Initiate invalidation of secure caches and TLBs if the SMMU
* supports secure state. If not, it's implementation defined
* as to how SMMU_S_INIT register is accessed.
* Arm SMMU Arch RME supplement, section 3.4: all SMMU registers
* specified to be accessible only in secure physical address space are
* additionally accessible in root physical address space in an SMMU
* with RME.
* Section 3.3: as GPT information is permitted to be cached in a TLB,
* the SMMU_S_INIT.INV_ALL mechanism also invalidates GPT information
* cached in TLBs.
*/
mmio_write_32(smmu_base + SMMU_S_INIT, SMMU_S_INIT_INV_ALL);
/* Wait for global invalidation operation to finish */
return smmuv3_poll(smmu_base + SMMU_S_INIT,
SMMU_S_INIT_INV_ALL, 0U);
}
int smmuv3_ns_set_abort_all(uintptr_t smmu_base)
{
/* Attribute update has completed when SMMU_GBPA.Update bit is 0 */
if (smmuv3_poll(smmu_base + SMMU_GBPA, SMMU_GBPA_UPDATE, 0U) != 0U) {
return -1;
}
/*
* Set GBPA's ABORT bit. Other GBPA fields are presumably ignored then,
* so simply preserve their value.
*/
mmio_setbits_32(smmu_base + SMMU_GBPA, SMMU_GBPA_UPDATE | SMMU_GBPA_ABORT);
if (smmuv3_poll(smmu_base + SMMU_GBPA, SMMU_GBPA_UPDATE, 0U) != 0U) {
return -1;
}
/* Disable the SMMU to engage the GBPA fields previously configured. */
mmio_clrbits_32(smmu_base + SMMU_CR0, SMMU_CR0_SMMUEN);
if (smmuv3_poll(smmu_base + SMMU_CR0ACK, SMMU_CR0_SMMUEN, 0U) != 0U) {
return -1;
}
return 0;
}
@@ -0,0 +1,57 @@
/*
* Copyright (c) 2015-2019, ARM Limited and Contributors. All rights reserved.
*
* SPDX-License-Identifier: BSD-3-Clause
*/
#include <assert.h>
#include <drivers/arm/sp804_delay_timer.h>
#include <drivers/delay_timer.h>
#include <lib/mmio.h>
uintptr_t sp804_base_addr;
#define SP804_TIMER1_LOAD (sp804_base_addr + 0x000)
#define SP804_TIMER1_VALUE (sp804_base_addr + 0x004)
#define SP804_TIMER1_CONTROL (sp804_base_addr + 0x008)
#define SP804_TIMER1_BGLOAD (sp804_base_addr + 0x018)
#define TIMER_CTRL_ONESHOT (1 << 0)
#define TIMER_CTRL_32BIT (1 << 1)
#define TIMER_CTRL_DIV1 (0 << 2)
#define TIMER_CTRL_DIV16 (1 << 2)
#define TIMER_CTRL_DIV256 (2 << 2)
#define TIMER_CTRL_IE (1 << 5)
#define TIMER_CTRL_PERIODIC (1 << 6)
#define TIMER_CTRL_ENABLE (1 << 7)
/********************************************************************
* The SP804 timer delay function
********************************************************************/
uint32_t sp804_get_timer_value(void)
{
return mmio_read_32(SP804_TIMER1_VALUE);
}
/********************************************************************
* Initialize the 1st timer in the SP804 dual timer with a base
* address and a timer ops
********************************************************************/
void sp804_timer_ops_init(uintptr_t base_addr, const timer_ops_t *ops)
{
assert(base_addr != 0);
assert(ops != 0 && ops->get_timer_value == sp804_get_timer_value);
sp804_base_addr = base_addr;
timer_init(ops);
/* disable timer1 */
mmio_write_32(SP804_TIMER1_CONTROL, 0);
mmio_write_32(SP804_TIMER1_LOAD, UINT32_MAX);
mmio_write_32(SP804_TIMER1_VALUE, UINT32_MAX);
/* enable as a free running 32-bit counter */
mmio_write_32(SP804_TIMER1_CONTROL,
TIMER_CTRL_32BIT | TIMER_CTRL_ENABLE);
}
@@ -0,0 +1,51 @@
/*
* Copyright (c) 2015-2018, ARM Limited and Contributors. All rights reserved.
*
* SPDX-License-Identifier: BSD-3-Clause
*/
#include <stdint.h>
#include <drivers/arm/sp805.h>
#include <lib/mmio.h>
/* Inline register access functions */
static inline void sp805_write_wdog_load(uintptr_t base, uint32_t value)
{
mmio_write_32(base + SP805_WDOG_LOAD_OFF, value);
}
static inline void sp805_write_wdog_ctrl(uintptr_t base, uint32_t value)
{
mmio_write_32(base + SP805_WDOG_CTR_OFF, value);
}
static inline void sp805_write_wdog_lock(uintptr_t base, uint32_t value)
{
mmio_write_32(base + SP805_WDOG_LOCK_OFF, value);
}
/* Public API implementation */
void sp805_start(uintptr_t base, unsigned int ticks)
{
sp805_write_wdog_load(base, ticks);
sp805_write_wdog_ctrl(base, SP805_CTR_RESEN | SP805_CTR_INTEN);
/* Lock registers access */
sp805_write_wdog_lock(base, 0U);
}
void sp805_stop(uintptr_t base)
{
sp805_write_wdog_lock(base, WDOG_UNLOCK_KEY);
sp805_write_wdog_ctrl(base, 0U);
}
void sp805_refresh(uintptr_t base, unsigned int ticks)
{
sp805_write_wdog_lock(base, WDOG_UNLOCK_KEY);
sp805_write_wdog_load(base, ticks);
sp805_write_wdog_lock(base, 0U);
}
@@ -0,0 +1,104 @@
/*
* Copyright (c) 2018, ARM Limited and Contributors. All rights reserved.
*
* SPDX-License-Identifier: BSD-3-Clause
*/
#include <assert.h>
#include <stddef.h>
#include <common/debug.h>
#include <drivers/arm/tzc380.h>
#include <lib/mmio.h>
struct tzc380_instance {
uintptr_t base;
uint8_t addr_width;
uint8_t num_regions;
};
struct tzc380_instance tzc380;
static unsigned int tzc380_read_build_config(uintptr_t base)
{
return mmio_read_32(base + TZC380_CONFIGURATION_OFF);
}
static void tzc380_write_action(uintptr_t base, unsigned int action)
{
mmio_write_32(base + ACTION_OFF, action);
}
static void tzc380_write_region_base_low(uintptr_t base, unsigned int region,
unsigned int val)
{
mmio_write_32(base + REGION_SETUP_LOW_OFF(region), val);
}
static void tzc380_write_region_base_high(uintptr_t base, unsigned int region,
unsigned int val)
{
mmio_write_32(base + REGION_SETUP_HIGH_OFF(region), val);
}
static void tzc380_write_region_attributes(uintptr_t base, unsigned int region,
unsigned int val)
{
mmio_write_32(base + REGION_ATTRIBUTES_OFF(region), val);
}
void tzc380_init(uintptr_t base)
{
unsigned int tzc_build;
assert(base != 0U);
tzc380.base = base;
/* Save values we will use later. */
tzc_build = tzc380_read_build_config(tzc380.base);
tzc380.addr_width = ((tzc_build >> BUILD_CONFIG_AW_SHIFT) &
BUILD_CONFIG_AW_MASK) + 1;
tzc380.num_regions = ((tzc_build >> BUILD_CONFIG_NR_SHIFT) &
BUILD_CONFIG_NR_MASK) + 1;
}
static uint32_t addr_low(uintptr_t addr)
{
return (uint32_t)addr;
}
static uint32_t addr_high(uintptr_t addr __unused)
{
#if (UINTPTR_MAX == UINT64_MAX)
return addr >> 32;
#else
return 0;
#endif
}
/*
* `tzc380_configure_region` is used to program regions into the TrustZone
* controller.
*/
void tzc380_configure_region(uint8_t region, uintptr_t region_base, unsigned int attr)
{
assert(tzc380.base != 0U);
assert(region < tzc380.num_regions);
tzc380_write_region_base_low(tzc380.base, region, addr_low(region_base));
tzc380_write_region_base_high(tzc380.base, region, addr_high(region_base));
tzc380_write_region_attributes(tzc380.base, region, attr);
}
void tzc380_set_action(unsigned int action)
{
assert(tzc380.base != 0U);
/*
* - Currently no handler is provided to trap an error via interrupt
* or exception.
* - The interrupt action has not been tested.
*/
tzc380_write_action(tzc380.base, action);
}
@@ -0,0 +1,360 @@
/*
* Copyright (c) 2016-2022, ARM Limited and Contributors. All rights reserved.
*
* SPDX-License-Identifier: BSD-3-Clause
*/
#include <assert.h>
#include <stddef.h>
#include <common/debug.h>
#include <drivers/arm/tzc400.h>
#include <lib/mmio.h>
#include <lib/utils_def.h>
#include "tzc_common_private.h"
/*
* Macros which will be used by common core functions.
*/
#define TZC_400_REGION_BASE_LOW_0_OFFSET U(0x100)
#define TZC_400_REGION_BASE_HIGH_0_OFFSET U(0x104)
#define TZC_400_REGION_TOP_LOW_0_OFFSET U(0x108)
#define TZC_400_REGION_TOP_HIGH_0_OFFSET U(0x10c)
#define TZC_400_REGION_ATTR_0_OFFSET U(0x110)
#define TZC_400_REGION_ID_ACCESS_0_OFFSET U(0x114)
/*
* Implementation defined values used to validate inputs later.
* Filters : max of 4 ; 0 to 3
* Regions : max of 9 ; 0 to 8
* Address width : Values between 32 to 64
*/
typedef struct tzc400_instance {
uintptr_t base;
uint8_t addr_width;
uint8_t num_filters;
uint8_t num_regions;
} tzc400_instance_t;
static tzc400_instance_t tzc400;
static inline unsigned int _tzc400_read_build_config(uintptr_t base)
{
return mmio_read_32(base + BUILD_CONFIG_OFF);
}
static inline unsigned int _tzc400_read_gate_keeper(uintptr_t base)
{
return mmio_read_32(base + GATE_KEEPER_OFF);
}
static inline void _tzc400_write_gate_keeper(uintptr_t base, unsigned int val)
{
mmio_write_32(base + GATE_KEEPER_OFF, val);
}
/*
* Get the open status information for all filter units.
*/
#define get_gate_keeper_os(_base) ((_tzc400_read_gate_keeper(_base) >> \
GATE_KEEPER_OS_SHIFT) & \
GATE_KEEPER_OS_MASK)
/* Define common core functions used across different TZC peripherals. */
DEFINE_TZC_COMMON_WRITE_ACTION(400, 400)
DEFINE_TZC_COMMON_WRITE_REGION_BASE(400, 400)
DEFINE_TZC_COMMON_WRITE_REGION_TOP(400, 400)
DEFINE_TZC_COMMON_WRITE_REGION_ATTRIBUTES(400, 400)
DEFINE_TZC_COMMON_WRITE_REGION_ID_ACCESS(400, 400)
DEFINE_TZC_COMMON_UPDATE_FILTERS(400, 400)
DEFINE_TZC_COMMON_CONFIGURE_REGION0(400)
DEFINE_TZC_COMMON_CONFIGURE_REGION(400)
static void _tzc400_clear_it(uintptr_t base, uint32_t filter)
{
mmio_write_32(base + INT_CLEAR, BIT_32(filter));
}
static uint32_t _tzc400_get_int_by_filter(uintptr_t base, uint32_t filter)
{
return mmio_read_32(base + INT_STATUS) & BIT_32(filter);
}
#if DEBUG
static unsigned long _tzc400_get_fail_address(uintptr_t base, uint32_t filter)
{
unsigned long fail_address;
fail_address = mmio_read_32(base + FAIL_ADDRESS_LOW_OFF +
(filter * FILTER_OFFSET));
#ifdef __aarch64__
fail_address += (unsigned long)mmio_read_32(base + FAIL_ADDRESS_HIGH_OFF +
(filter * FILTER_OFFSET)) << 32;
#endif
return fail_address;
}
static uint32_t _tzc400_get_fail_id(uintptr_t base, uint32_t filter)
{
return mmio_read_32(base + FAIL_ID + (filter * FILTER_OFFSET));
}
static uint32_t _tzc400_get_fail_control(uintptr_t base, uint32_t filter)
{
return mmio_read_32(base + FAIL_CONTROL_OFF + (filter * FILTER_OFFSET));
}
static void _tzc400_dump_fail_filter(uintptr_t base, uint32_t filter)
{
uint32_t control_fail;
uint32_t fail_id;
unsigned long address_fail;
address_fail = _tzc400_get_fail_address(base, filter);
ERROR("Illegal access to 0x%lx:\n", address_fail);
fail_id = _tzc400_get_fail_id(base, filter);
ERROR("\tFAIL_ID = 0x%x\n", fail_id);
control_fail = _tzc400_get_fail_control(base, filter);
if (((control_fail & BIT_32(FAIL_CONTROL_NS_SHIFT)) >> FAIL_CONTROL_NS_SHIFT) ==
FAIL_CONTROL_NS_NONSECURE) {
ERROR("\tNon-Secure\n");
} else {
ERROR("\tSecure\n");
}
if (((control_fail & BIT_32(FAIL_CONTROL_PRIV_SHIFT)) >> FAIL_CONTROL_PRIV_SHIFT) ==
FAIL_CONTROL_PRIV_PRIV) {
ERROR("\tPrivilege\n");
} else {
ERROR("\tUnprivilege\n");
}
if (((control_fail & BIT_32(FAIL_CONTROL_DIR_SHIFT)) >> FAIL_CONTROL_DIR_SHIFT) ==
FAIL_CONTROL_DIR_WRITE) {
ERROR("\tWrite\n");
} else {
ERROR("\tRead\n");
}
}
#endif /* DEBUG */
static unsigned int _tzc400_get_gate_keeper(uintptr_t base,
unsigned int filter)
{
unsigned int open_status;
open_status = get_gate_keeper_os(base);
return (open_status >> filter) & GATE_KEEPER_FILTER_MASK;
}
/* This function is not MP safe. */
static void _tzc400_set_gate_keeper(uintptr_t base,
unsigned int filter,
int val)
{
unsigned int open_status;
/* Upper half is current state. Lower half is requested state. */
open_status = get_gate_keeper_os(base);
if (val != 0)
open_status |= (1UL << filter);
else
open_status &= ~(1UL << filter);
_tzc400_write_gate_keeper(base, (open_status & GATE_KEEPER_OR_MASK) <<
GATE_KEEPER_OR_SHIFT);
/* Wait here until we see the change reflected in the TZC status. */
while ((get_gate_keeper_os(base)) != open_status)
;
}
void tzc400_set_action(unsigned int action)
{
assert(tzc400.base != 0U);
assert(action <= TZC_ACTION_ERR_INT);
_tzc400_write_action(tzc400.base, action);
}
void tzc400_init(uintptr_t base)
{
#if DEBUG
unsigned int tzc400_id;
#endif
unsigned int tzc400_build;
assert(base != 0U);
tzc400.base = base;
#if DEBUG
tzc400_id = _tzc_read_peripheral_id(base);
if (tzc400_id != TZC_400_PERIPHERAL_ID) {
ERROR("TZC-400 : Wrong device ID (0x%x).\n", tzc400_id);
panic();
}
#endif
/* Save values we will use later. */
tzc400_build = _tzc400_read_build_config(tzc400.base);
tzc400.num_filters = (uint8_t)((tzc400_build >> BUILD_CONFIG_NF_SHIFT) &
BUILD_CONFIG_NF_MASK) + 1U;
tzc400.addr_width = (uint8_t)((tzc400_build >> BUILD_CONFIG_AW_SHIFT) &
BUILD_CONFIG_AW_MASK) + 1U;
tzc400.num_regions = (uint8_t)((tzc400_build >> BUILD_CONFIG_NR_SHIFT) &
BUILD_CONFIG_NR_MASK) + 1U;
}
/*
* `tzc400_configure_region0` is used to program region 0 into the TrustZone
* controller. Region 0 covers the whole address space that is not mapped
* to any other region, and is enabled on all filters; this cannot be
* changed. This function only changes the access permissions.
*/
void tzc400_configure_region0(unsigned int sec_attr,
unsigned int ns_device_access)
{
assert(tzc400.base != 0U);
assert(sec_attr <= TZC_REGION_S_RDWR);
_tzc400_configure_region0(tzc400.base, sec_attr, ns_device_access);
}
/*
* `tzc400_configure_region` is used to program regions into the TrustZone
* controller. A region can be associated with more than one filter. The
* associated filters are passed in as a bitmap (bit0 = filter0), except that
* the value TZC_400_REGION_ATTR_FILTER_BIT_ALL selects all filters, based on
* the value of tzc400.num_filters.
* NOTE:
* Region 0 is special; it is preferable to use tzc400_configure_region0
* for this region (see comment for that function).
*/
void tzc400_configure_region(unsigned int filters,
unsigned int region,
unsigned long long region_base,
unsigned long long region_top,
unsigned int sec_attr,
unsigned int nsaid_permissions)
{
assert(tzc400.base != 0U);
/* Adjust filter mask by real filter number */
if (filters == TZC_400_REGION_ATTR_FILTER_BIT_ALL) {
filters = (1U << tzc400.num_filters) - 1U;
}
/* Do range checks on filters and regions. */
assert(((filters >> tzc400.num_filters) == 0U) &&
(region < tzc400.num_regions));
/*
* Do address range check based on TZC configuration. A 64bit address is
* the max and expected case.
*/
assert((region_top <= (UINT64_MAX >> (64U - tzc400.addr_width))) &&
(region_base < region_top));
/* region_base and (region_top + 1) must be 4KB aligned */
assert(((region_base | (region_top + 1U)) & (4096U - 1U)) == 0U);
assert(sec_attr <= TZC_REGION_S_RDWR);
_tzc400_configure_region(tzc400.base, filters, region, region_base,
region_top,
sec_attr, nsaid_permissions);
}
void tzc400_update_filters(unsigned int region, unsigned int filters)
{
/* Do range checks on filters and regions. */
assert(((filters >> tzc400.num_filters) == 0U) &&
(region < tzc400.num_regions));
_tzc400_update_filters(tzc400.base, region, tzc400.num_filters, filters);
}
void tzc400_enable_filters(void)
{
unsigned int state;
unsigned int filter;
assert(tzc400.base != 0U);
for (filter = 0U; filter < tzc400.num_filters; filter++) {
state = _tzc400_get_gate_keeper(tzc400.base, filter);
if (state != 0U) {
/* Filter 0 is special and cannot be disabled.
* So here we allow it being already enabled. */
if (filter == 0U) {
continue;
}
/*
* The TZC filter is already configured. Changing the
* programmer's view in an active system can cause
* unpredictable behavior therefore panic for now rather
* than try to determine whether this is safe in this
* instance.
*
* See the 'ARM (R) CoreLink TM TZC-400 TrustZone (R)
* Address Space Controller' Technical Reference Manual.
*/
ERROR("TZC-400 : Filter %u Gatekeeper already enabled.\n",
filter);
panic();
}
_tzc400_set_gate_keeper(tzc400.base, filter, 1);
}
}
void tzc400_disable_filters(void)
{
unsigned int filter;
unsigned int state;
unsigned int start = 0U;
assert(tzc400.base != 0U);
/* Filter 0 is special and cannot be disabled. */
state = _tzc400_get_gate_keeper(tzc400.base, 0);
if (state != 0U) {
start++;
}
for (filter = start; filter < tzc400.num_filters; filter++)
_tzc400_set_gate_keeper(tzc400.base, filter, 0);
}
int tzc400_it_handler(void)
{
uint32_t filter;
uint32_t filter_it_pending = tzc400.num_filters;
assert(tzc400.base != 0U);
for (filter = 0U; filter < tzc400.num_filters; filter++) {
if (_tzc400_get_int_by_filter(tzc400.base, filter) != 0U) {
filter_it_pending = filter;
break;
}
}
if (filter_it_pending == tzc400.num_filters) {
ERROR("TZC-400: No interrupt pending!\n");
return -1;
}
#if DEBUG
_tzc400_dump_fail_filter(tzc400.base, filter_it_pending);
#endif
_tzc400_clear_it(tzc400.base, filter_it_pending);
return 0;
}
@@ -0,0 +1,204 @@
/*
* Copyright (c) 2016-2021, ARM Limited and Contributors. All rights reserved.
*
* SPDX-License-Identifier: BSD-3-Clause
*/
#ifndef TZC_COMMON_PRIVATE_H
#define TZC_COMMON_PRIVATE_H
#include <arch.h>
#include <arch_helpers.h>
#include <drivers/arm/tzc_common.h>
#include <lib/mmio.h>
#define DEFINE_TZC_COMMON_WRITE_ACTION(fn_name, macro_name) \
static inline void _tzc##fn_name##_write_action( \
uintptr_t base, \
unsigned int action) \
{ \
mmio_write_32(base + TZC_##macro_name##_ACTION_OFF, \
action); \
}
#define DEFINE_TZC_COMMON_WRITE_REGION_BASE(fn_name, macro_name) \
static inline void _tzc##fn_name##_write_region_base( \
uintptr_t base, \
unsigned int region_no, \
unsigned long long region_base) \
{ \
mmio_write_32(base + \
TZC_REGION_OFFSET( \
TZC_##macro_name##_REGION_SIZE, \
(u_register_t)region_no) + \
TZC_##macro_name##_REGION_BASE_LOW_0_OFFSET, \
(uint32_t)region_base); \
mmio_write_32(base + \
TZC_REGION_OFFSET( \
TZC_##macro_name##_REGION_SIZE, \
(u_register_t)region_no) + \
TZC_##macro_name##_REGION_BASE_HIGH_0_OFFSET, \
(uint32_t)(region_base >> 32)); \
}
#define DEFINE_TZC_COMMON_WRITE_REGION_TOP(fn_name, macro_name) \
static inline void _tzc##fn_name##_write_region_top( \
uintptr_t base, \
unsigned int region_no, \
unsigned long long region_top) \
{ \
mmio_write_32(base + \
TZC_REGION_OFFSET( \
TZC_##macro_name##_REGION_SIZE, \
(u_register_t)region_no) + \
TZC_##macro_name##_REGION_TOP_LOW_0_OFFSET, \
(uint32_t)region_top); \
mmio_write_32(base + \
TZC_REGION_OFFSET( \
TZC_##macro_name##_REGION_SIZE, \
(u_register_t)region_no) + \
TZC_##macro_name##_REGION_TOP_HIGH_0_OFFSET, \
(uint32_t)(region_top >> 32)); \
}
#define DEFINE_TZC_COMMON_WRITE_REGION_ATTRIBUTES(fn_name, macro_name) \
static inline void _tzc##fn_name##_write_region_attributes( \
uintptr_t base, \
unsigned int region_no, \
unsigned int attr) \
{ \
mmio_write_32(base + \
TZC_REGION_OFFSET( \
TZC_##macro_name##_REGION_SIZE, \
(u_register_t)region_no) + \
TZC_##macro_name##_REGION_ATTR_0_OFFSET, \
attr); \
}
#define DEFINE_TZC_COMMON_WRITE_REGION_ID_ACCESS(fn_name, macro_name) \
static inline void _tzc##fn_name##_write_region_id_access( \
uintptr_t base, \
unsigned int region_no, \
unsigned int val) \
{ \
mmio_write_32(base + \
TZC_REGION_OFFSET( \
TZC_##macro_name##_REGION_SIZE, \
(u_register_t)region_no) + \
TZC_##macro_name##_REGION_ID_ACCESS_0_OFFSET, \
val); \
}
/*
* It is used to modify the filters status for a defined region.
*/
#define DEFINE_TZC_COMMON_UPDATE_FILTERS(fn_name, macro_name) \
static inline void _tzc##fn_name##_update_filters( \
uintptr_t base, \
unsigned int region_no, \
unsigned int nbfilters, \
unsigned int filters) \
{ \
uint32_t filters_mask = GENMASK(nbfilters - 1U, 0); \
\
mmio_clrsetbits_32(base + \
TZC_REGION_OFFSET( \
TZC_##macro_name##_REGION_SIZE, \
region_no) + \
TZC_##macro_name##_REGION_ATTR_0_OFFSET, \
filters_mask << TZC_REGION_ATTR_F_EN_SHIFT, \
filters << TZC_REGION_ATTR_F_EN_SHIFT); \
}
/*
* It is used to program region 0 ATTRIBUTES and ACCESS register.
*/
#define DEFINE_TZC_COMMON_CONFIGURE_REGION0(fn_name) \
static void _tzc##fn_name##_configure_region0(uintptr_t base, \
unsigned int sec_attr, \
unsigned int ns_device_access) \
{ \
assert(base != 0U); \
VERBOSE("TrustZone : Configuring region 0 " \
"(TZC Interface Base=0x%lx sec_attr=0x%x," \
" ns_devs=0x%x)\n", base, \
sec_attr, ns_device_access); \
\
/* Set secure attributes on region 0 */ \
_tzc##fn_name##_write_region_attributes(base, 0, \
sec_attr << TZC_REGION_ATTR_SEC_SHIFT); \
\
/***************************************************/ \
/* Specify which non-secure devices have permission*/ \
/* to access region 0. */ \
/***************************************************/ \
_tzc##fn_name##_write_region_id_access(base, \
0, \
ns_device_access); \
}
/*
* It is used to program a region from 1 to 8 in the TrustZone controller.
* NOTE:
* Region 0 is special; it is preferable to use
* ##fn_name##_configure_region0 for this region (see comment for
* that function).
*/
#define DEFINE_TZC_COMMON_CONFIGURE_REGION(fn_name) \
static void _tzc##fn_name##_configure_region(uintptr_t base, \
unsigned int filters, \
unsigned int region_no, \
unsigned long long region_base, \
unsigned long long region_top, \
unsigned int sec_attr, \
unsigned int nsaid_permissions) \
{ \
assert(base != 0U); \
VERBOSE("TrustZone : Configuring region " \
"(TZC Interface Base: 0x%lx, region_no = %u)" \
"...\n", base, region_no); \
VERBOSE("TrustZone : ... base = %llx, top = %llx," \
"\n", region_base, region_top); \
VERBOSE("TrustZone : ... sec_attr = 0x%x," \
" ns_devs = 0x%x)\n", \
sec_attr, nsaid_permissions); \
\
/***************************************************/ \
/* Inputs look ok, start programming registers. */ \
/* All the address registers are 32 bits wide and */ \
/* have a LOW and HIGH */ \
/* component used to construct an address up to a */ \
/* 64bit. */ \
/***************************************************/ \
_tzc##fn_name##_write_region_base(base, \
region_no, region_base); \
_tzc##fn_name##_write_region_top(base, \
region_no, region_top); \
\
/* Enable filter to the region and set secure attributes */\
_tzc##fn_name##_write_region_attributes(base, \
region_no, \
(sec_attr << TZC_REGION_ATTR_SEC_SHIFT) |\
(filters << TZC_REGION_ATTR_F_EN_SHIFT));\
\
/***************************************************/ \
/* Specify which non-secure devices have permission*/ \
/* to access this region. */ \
/***************************************************/ \
_tzc##fn_name##_write_region_id_access(base, \
region_no, \
nsaid_permissions); \
}
static inline unsigned int _tzc_read_peripheral_id(uintptr_t base)
{
unsigned int id;
id = mmio_read_32(base + PID0_OFF);
/* Masks DESC part in PID1 */
id |= ((mmio_read_32(base + PID1_OFF) & 0xFU) << 8U);
return id;
}
#endif /* TZC_COMMON_PRIVATE_H */
@@ -0,0 +1,287 @@
/*
* Copyright (c) 2016-2018, ARM Limited and Contributors. All rights reserved.
*
* SPDX-License-Identifier: BSD-3-Clause
*/
#include <assert.h>
#include <common/debug.h>
#include <drivers/arm/tzc_dmc500.h>
#include <drivers/arm/tzc_common.h>
#include <lib/mmio.h>
#include "tzc_common_private.h"
/*
* Macros which will be used by common core functions.
*/
#define TZC_DMC500_REGION_BASE_LOW_0_OFFSET 0x054
#define TZC_DMC500_REGION_BASE_HIGH_0_OFFSET 0x058
#define TZC_DMC500_REGION_TOP_LOW_0_OFFSET 0x05C
#define TZC_DMC500_REGION_TOP_HIGH_0_OFFSET 0x060
#define TZC_DMC500_REGION_ATTR_0_OFFSET 0x064
#define TZC_DMC500_REGION_ID_ACCESS_0_OFFSET 0x068
#define TZC_DMC500_ACTION_OFF 0x50
/* Pointer to the tzc_dmc500_driver_data structure populated by the platform */
static const tzc_dmc500_driver_data_t *g_driver_data;
static unsigned int g_sys_if_count;
#define verify_region_attr(region, attr) \
((g_conf_regions[(region)].sec_attr == \
((attr) >> TZC_REGION_ATTR_SEC_SHIFT)) \
&& ((attr) & (0x1 << TZC_REGION_ATTR_F_EN_SHIFT)))
/*
* Structure for configured regions attributes in DMC500.
*/
typedef struct tzc_dmc500_regions {
unsigned int sec_attr;
int is_enabled;
} tzc_dmc500_regions_t;
/*
* Array storing the attributes of the configured regions. This array
* will be used by the `tzc_dmc500_verify_complete` to verify the flush
* completion.
*/
static tzc_dmc500_regions_t g_conf_regions[MAX_REGION_VAL + 1];
/* Helper Macros for making the code readable */
#define DMC_INST_BASE_ADDR(instance) (g_driver_data->dmc_base[instance])
#define DMC_INST_SI_BASE(instance, interface) \
(DMC_INST_BASE_ADDR(instance) + IFACE_OFFSET(interface))
DEFINE_TZC_COMMON_WRITE_ACTION(_dmc500, DMC500)
DEFINE_TZC_COMMON_WRITE_REGION_BASE(_dmc500, DMC500)
DEFINE_TZC_COMMON_WRITE_REGION_TOP(_dmc500, DMC500)
DEFINE_TZC_COMMON_WRITE_REGION_ATTRIBUTES(_dmc500, DMC500)
DEFINE_TZC_COMMON_WRITE_REGION_ID_ACCESS(_dmc500, DMC500)
DEFINE_TZC_COMMON_CONFIGURE_REGION0(_dmc500)
DEFINE_TZC_COMMON_CONFIGURE_REGION(_dmc500)
static inline unsigned int _tzc_dmc500_read_region_attr_0(
uintptr_t dmc_si_base,
unsigned int region_no)
{
return mmio_read_32(dmc_si_base +
TZC_REGION_OFFSET(TZC_DMC500_REGION_SIZE, region_no) +
TZC_DMC500_REGION_ATTR_0_OFFSET);
}
static inline void _tzc_dmc500_write_flush_control(uintptr_t dmc_si_base)
{
mmio_write_32(dmc_si_base + SI_FLUSH_CTRL_OFFSET, 1);
}
/*
* Sets the Flush controls for all the DMC Instances and System Interfaces.
* This initiates the flush of configuration settings from the shadow
* registers to the actual configuration register. The caller should poll
* changed register to confirm update.
*/
void tzc_dmc500_config_complete(void)
{
int dmc_inst, sys_if;
assert(g_driver_data);
for (dmc_inst = 0; dmc_inst < g_driver_data->dmc_count; dmc_inst++) {
assert(DMC_INST_BASE_ADDR(dmc_inst));
for (sys_if = 0; sys_if < g_sys_if_count; sys_if++)
_tzc_dmc500_write_flush_control(
DMC_INST_SI_BASE(dmc_inst, sys_if));
}
}
/*
* This function reads back the secure attributes from the configuration
* register for each DMC Instance and System Interface and compares it with
* the configured value. The successful verification of the region attributes
* confirms that the flush operation has completed.
* If the verification fails, the caller is expected to invoke this API again
* till it succeeds.
* Returns 0 on success and 1 on failure.
*/
int tzc_dmc500_verify_complete(void)
{
int dmc_inst, sys_if, region_no;
unsigned int attr;
assert(g_driver_data);
/* Region 0 must be configured */
assert(g_conf_regions[0].is_enabled);
/* Iterate over all configured regions */
for (region_no = 0; region_no <= MAX_REGION_VAL; region_no++) {
if (!g_conf_regions[region_no].is_enabled)
continue;
for (dmc_inst = 0; dmc_inst < g_driver_data->dmc_count;
dmc_inst++) {
assert(DMC_INST_BASE_ADDR(dmc_inst));
for (sys_if = 0; sys_if < g_sys_if_count;
sys_if++) {
attr = _tzc_dmc500_read_region_attr_0(
DMC_INST_SI_BASE(dmc_inst, sys_if),
region_no);
VERBOSE("Verifying DMC500 region:%d"
" dmc_inst:%d sys_if:%d attr:%x\n",
region_no, dmc_inst, sys_if, attr);
if (!verify_region_attr(region_no, attr))
return 1;
}
}
}
return 0;
}
/*
* `tzc_dmc500_configure_region0` is used to program region 0 in both the
* system interfaces of all the DMC-500 instances. Region 0 covers the whole
* address space that is not mapped to any other region for a system interface,
* and is always enabled; this cannot be changed. This function only changes
* the access permissions.
*/
void tzc_dmc500_configure_region0(unsigned int sec_attr,
unsigned int nsaid_permissions)
{
int dmc_inst, sys_if;
/* Assert if DMC-500 is not initialized */
assert(g_driver_data);
/* Configure region_0 in all DMC instances */
for (dmc_inst = 0; dmc_inst < g_driver_data->dmc_count; dmc_inst++) {
assert(DMC_INST_BASE_ADDR(dmc_inst));
for (sys_if = 0; sys_if < g_sys_if_count; sys_if++)
_tzc_dmc500_configure_region0(
DMC_INST_SI_BASE(dmc_inst, sys_if),
sec_attr, nsaid_permissions);
}
g_conf_regions[0].sec_attr = sec_attr;
g_conf_regions[0].is_enabled = 1;
}
/*
* `tzc_dmc500_configure_region` is used to program a region into all system
* interfaces of all the DMC instances.
* NOTE:
* Region 0 is special; it is preferable to use tzc_dmc500_configure_region0
* for this region (see comment for that function).
*/
void tzc_dmc500_configure_region(unsigned int region_no,
unsigned long long region_base,
unsigned long long region_top,
unsigned int sec_attr,
unsigned int nsaid_permissions)
{
int dmc_inst, sys_if;
assert(g_driver_data);
/* Do range checks on regions. */
assert((region_no >= 0U) && (region_no <= MAX_REGION_VAL));
/*
* Do address range check based on DMC-TZ configuration. A 43bit address
* is the max and expected case.
*/
assert(((region_top <= (UINT64_MAX >> (64U - 43U))) &&
(region_base < region_top)));
/* region_base and (region_top + 1) must be 4KB aligned */
assert(((region_base | (region_top + 1U)) & (4096U - 1U)) == 0U);
for (dmc_inst = 0; dmc_inst < g_driver_data->dmc_count; dmc_inst++) {
assert(DMC_INST_BASE_ADDR(dmc_inst));
for (sys_if = 0; sys_if < g_sys_if_count; sys_if++)
_tzc_dmc500_configure_region(
DMC_INST_SI_BASE(dmc_inst, sys_if),
TZC_DMC500_REGION_ATTR_F_EN_MASK,
region_no, region_base, region_top,
sec_attr, nsaid_permissions);
}
g_conf_regions[region_no].sec_attr = sec_attr;
g_conf_regions[region_no].is_enabled = 1;
}
/* Sets the action value for all the DMC instances */
void tzc_dmc500_set_action(unsigned int action)
{
int dmc_inst;
assert(g_driver_data);
for (dmc_inst = 0; dmc_inst < g_driver_data->dmc_count; dmc_inst++) {
assert(DMC_INST_BASE_ADDR(dmc_inst));
/*
* - Currently no handler is provided to trap an error via
* interrupt or exception.
* - The interrupt action has not been tested.
*/
_tzc_dmc500_write_action(DMC_INST_BASE_ADDR(dmc_inst), action);
}
}
/*
* A DMC-500 instance must be present at each base address provided by the
* platform. It also expects platform to pass at least one instance of
* DMC-500.
*/
static void validate_plat_driver_data(
const tzc_dmc500_driver_data_t *plat_driver_data)
{
#if ENABLE_ASSERTIONS
int i;
unsigned int dmc_id;
uintptr_t dmc_base;
assert(plat_driver_data);
assert(plat_driver_data->dmc_count > 0 &&
(plat_driver_data->dmc_count <= MAX_DMC_COUNT));
for (i = 0; i < plat_driver_data->dmc_count; i++) {
dmc_base = plat_driver_data->dmc_base[i];
assert(dmc_base);
dmc_id = _tzc_read_peripheral_id(dmc_base);
assert(dmc_id == DMC500_PERIPHERAL_ID);
}
#endif /* ENABLE_ASSERTIONS */
}
/*
* Initializes the base address and count of DMC instances.
*
* Note : Only pointer to plat_driver_data is saved, so it is caller's
* responsibility to keep it valid until the driver is used.
*/
void tzc_dmc500_driver_init(const tzc_dmc500_driver_data_t *plat_driver_data)
{
/* Check valid pointer is passed */
assert(plat_driver_data);
/*
* NOTE: This driver expects the DMC-500 controller is already in
* READY state. Hence, it uses the reconfiguration method for
* programming TrustZone regions
*/
/* Validates the information passed by platform */
validate_plat_driver_data(plat_driver_data);
g_driver_data = plat_driver_data;
/* Check valid system interface count */
assert(g_driver_data->sys_if_count <= MAX_SYS_IF_COUNT);
g_sys_if_count = g_driver_data->sys_if_count;
/* If interface count is not present then assume max */
if (g_sys_if_count == 0U)
g_sys_if_count = MAX_SYS_IF_COUNT;
}
@@ -0,0 +1,177 @@
/*
* Copyright (c) 2018-2020, ARM Limited and Contributors. All rights reserved.
*
* SPDX-License-Identifier: BSD-3-Clause
*/
#include <assert.h>
#include <common/debug.h>
#include <drivers/arm/tzc_dmc620.h>
#include <lib/mmio.h>
/* Mask to extract bit 31 to 16 */
#define MASK_31_16 UINT64_C(0x0000ffff0000)
/* Mask to extract bit 47 to 32 */
#define MASK_47_32 UINT64_C(0xffff00000000)
/* Helper macro for getting dmc_base addr of a dmc_inst */
#define DMC_BASE(plat_data, dmc_inst) \
((uintptr_t)((plat_data)->dmc_base[(dmc_inst)]))
/* Pointer to the tzc_dmc620_config_data structure populated by the platform */
static const tzc_dmc620_config_data_t *g_plat_config_data;
#if ENABLE_ASSERTIONS
/*
* Helper function to check if the DMC-620 instance is present at the
* base address provided by the platform and also check if at least
* one dmc instance is present.
*/
static void tzc_dmc620_validate_plat_driver_data(
const tzc_dmc620_driver_data_t *plat_driver_data)
{
unsigned int dmc_inst, dmc_count, dmc_id;
uintptr_t base;
assert(plat_driver_data != NULL);
dmc_count = plat_driver_data->dmc_count;
assert(dmc_count > 0U);
for (dmc_inst = 0U; dmc_inst < dmc_count; dmc_inst++) {
base = DMC_BASE(plat_driver_data, dmc_inst);
dmc_id = mmio_read_32(base + DMC620_PERIPHERAL_ID_0);
assert(dmc_id == DMC620_PERIPHERAL_ID_0_VALUE);
}
}
#endif
/*
* Program a region with region base and region top addresses of all
* DMC-620 instances.
*/
static void tzc_dmc620_configure_region(int region_no,
unsigned long long region_base,
unsigned long long region_top,
unsigned int sec_attr)
{
uint32_t min_31_00, min_47_32;
uint32_t max_31_00, max_47_32;
unsigned int dmc_inst, dmc_count;
uintptr_t base;
const tzc_dmc620_driver_data_t *plat_driver_data;
plat_driver_data = g_plat_config_data->plat_drv_data;
assert(plat_driver_data != NULL);
/* Do range checks on regions. */
assert((region_no >= 0) && (region_no <= DMC620_ACC_ADDR_COUNT));
/* region_base and (region_top + 1) must be 4KB aligned */
assert(((region_base | (region_top + 1U)) & (4096U - 1U)) == 0U);
dmc_count = plat_driver_data->dmc_count;
for (dmc_inst = 0U; dmc_inst < dmc_count; dmc_inst++) {
min_31_00 = (uint32_t)((region_base & MASK_31_16) | sec_attr);
min_47_32 = (uint32_t)((region_base & MASK_47_32)
>> DMC620_ACC_ADDR_WIDTH);
max_31_00 = (uint32_t)(region_top & MASK_31_16);
max_47_32 = (uint32_t)((region_top & MASK_47_32)
>> DMC620_ACC_ADDR_WIDTH);
/* Extract the base address of the DMC-620 instance */
base = DMC_BASE(plat_driver_data, dmc_inst);
/* Configure access address region registers */
mmio_write_32(base + DMC620_ACC_ADDR_MIN_31_00_NEXT(region_no),
min_31_00);
mmio_write_32(base + DMC620_ACC_ADDR_MIN_47_32_NEXT(region_no),
min_47_32);
mmio_write_32(base + DMC620_ACC_ADDR_MAX_31_00_NEXT(region_no),
max_31_00);
mmio_write_32(base + DMC620_ACC_ADDR_MAX_47_32_NEXT(region_no),
max_47_32);
}
}
/*
* Set the action value for all the DMC-620 instances.
*/
static void tzc_dmc620_set_action(void)
{
unsigned int dmc_inst, dmc_count;
uintptr_t base;
const tzc_dmc620_driver_data_t *plat_driver_data;
plat_driver_data = g_plat_config_data->plat_drv_data;
dmc_count = plat_driver_data->dmc_count;
for (dmc_inst = 0U; dmc_inst < dmc_count; dmc_inst++) {
/* Extract the base address of the DMC-620 instance */
base = DMC_BASE(plat_driver_data, dmc_inst);
/* Switch to READY */
mmio_write_32(base + DMC620_MEMC_CMD, DMC620_MEMC_CMD_GO);
mmio_write_32(base + DMC620_MEMC_CMD, DMC620_MEMC_CMD_EXECUTE);
}
}
/*
* Verify whether the DMC-620 configuration is complete by reading back
* configuration registers and comparing it with the configured value. If
* configuration is incomplete, loop till the configured value is reflected in
* the register.
*/
static void tzc_dmc620_verify_complete(void)
{
unsigned int dmc_inst, dmc_count;
uintptr_t base;
const tzc_dmc620_driver_data_t *plat_driver_data;
plat_driver_data = g_plat_config_data->plat_drv_data;
dmc_count = plat_driver_data->dmc_count;
for (dmc_inst = 0U; dmc_inst < dmc_count; dmc_inst++) {
/* Extract the base address of the DMC-620 instance */
base = DMC_BASE(plat_driver_data, dmc_inst);
while ((mmio_read_32(base + DMC620_MEMC_STATUS) &
DMC620_MEMC_CMD_MASK) != DMC620_MEMC_CMD_GO) {
continue;
}
}
}
/*
* Initialize the DMC-620 TrustZone Controller using the region configuration
* supplied by the platform. The DMC620 controller should be enabled elsewhere
* before invoking this function.
*/
void arm_tzc_dmc620_setup(const tzc_dmc620_config_data_t *plat_config_data)
{
uint8_t i;
/* Check if valid pointer is passed */
assert(plat_config_data != NULL);
/*
* Check if access address count passed by the platform is less than or
* equal to DMC620's access address count
*/
assert(plat_config_data->acc_addr_count <= DMC620_ACC_ADDR_COUNT);
#if ENABLE_ASSERTIONS
/* Validates the information passed by platform */
tzc_dmc620_validate_plat_driver_data(plat_config_data->plat_drv_data);
#endif
g_plat_config_data = plat_config_data;
INFO("Configuring DMC-620 TZC settings\n");
for (i = 0U; i < g_plat_config_data->acc_addr_count; i++) {
tzc_dmc620_configure_region(i,
g_plat_config_data->plat_acc_addr_data[i].region_base,
g_plat_config_data->plat_acc_addr_data[i].region_top,
g_plat_config_data->plat_acc_addr_data[i].sec_attr);
}
tzc_dmc620_set_action();
tzc_dmc620_verify_complete();
INFO("DMC-620 TZC setup completed\n");
}