Commit 50f7101a authored by danh-arm's avatar danh-arm Committed by GitHub
Browse files

Merge pull request #650 from Xilinx/zynqmp-updates

Zynqmp updates
parents f9455cea 2ba68959
...@@ -126,7 +126,7 @@ static void cm_init_context_common(cpu_context_t *ctx, const entry_point_info_t ...@@ -126,7 +126,7 @@ static void cm_init_context_common(cpu_context_t *ctx, const entry_point_info_t
/* /*
* Set up SCTLR_ELx for the target exception level: * Set up SCTLR_ELx for the target exception level:
* EE bit is taken from the entrpoint attributes * EE bit is taken from the entrypoint attributes
* M, C and I bits must be zero (as required by PSCI specification) * M, C and I bits must be zero (as required by PSCI specification)
* *
* The target exception level is based on the spsr mode requested. * The target exception level is based on the spsr mode requested.
......
...@@ -21,14 +21,17 @@ make ERROR_DEPRECATED=1 RESET_TO_BL31=1 CROSS_COMPILE=aarch64-none-elf- PLAT=zyn ...@@ -21,14 +21,17 @@ make ERROR_DEPRECATED=1 RESET_TO_BL31=1 CROSS_COMPILE=aarch64-none-elf- PLAT=zyn
``` ```
# ZynqMP platform specific build options # ZynqMP platform specific build options
* `ZYNQMP_ATF_LOCATION`: Specifies the location of the bl31 binary. Options: * `ZYNQMP_ATF_MEM_BASE`: Specifies the base address of the bl31 binary.
- `tsram` : bl31 will be located in OCM (default) * `ZYNQMP_ATF_MEM_SIZE`: Specifies the size of the memory region of the bl31 binary.
- `tdram` : bl31 will be located in DRAM (address: 0x30000000) * `ZYNQMP_BL32_MEM_BASE`: Specifies the base address of the bl32 binary.
* `ZYNQMP_BL32_MEM_SIZE`: Specifies the size of the memory region of the bl32 binary.
* `ZYNQMP_TSP_RAM_LOCATION`: Specifies the location of the bl32 binary and # FSBL->ATF Parameter Passing
secure payload dispatcher. Options: The FSBL populates a data structure with image information for the ATF. The ATF
- `tsram` : bl32/spd will be located in OCM (default) uses that data to hand off to the loaded images. The address of the handoff data
- `tdram` : bl32/spd will be located in DRAM (address: 0x30000000) structure is passed in the ```PMU_GLOBAL.GLOBAL_GEN_STORAGE6``` register. The
register is free to be used by other software once the ATF is bringing up
further firmware images.
# Power Domain Tree # Power Domain Tree
The following power domain tree represents the power domain model used by the The following power domain tree represents the power domain model used by the
......
...@@ -28,12 +28,8 @@ ...@@ -28,12 +28,8 @@
* POSSIBILITY OF SUCH DAMAGE. * POSSIBILITY OF SUCH DAMAGE.
*/ */
#include <arch_helpers.h>
#include <cci.h>
#include <debug.h> #include <debug.h>
#include <gicv2.h>
#include <mmio.h> #include <mmio.h>
#include <plat_arm.h>
#include <platform.h> #include <platform.h>
#include <xlat_tables.h> #include <xlat_tables.h>
#include "../zynqmp_private.h" #include "../zynqmp_private.h"
...@@ -187,11 +183,9 @@ static char *zynqmp_print_silicon_idcode(void) ...@@ -187,11 +183,9 @@ static char *zynqmp_print_silicon_idcode(void)
tmp = id; tmp = id;
tmp &= ZYNQMP_CSU_IDCODE_XILINX_ID_MASK | tmp &= ZYNQMP_CSU_IDCODE_XILINX_ID_MASK |
ZYNQMP_CSU_IDCODE_FAMILY_MASK | ZYNQMP_CSU_IDCODE_FAMILY_MASK;
ZYNQMP_CSU_IDCODE_REVISION_MASK;
maskid = ZYNQMP_CSU_IDCODE_XILINX_ID << ZYNQMP_CSU_IDCODE_XILINX_ID_SHIFT | maskid = ZYNQMP_CSU_IDCODE_XILINX_ID << ZYNQMP_CSU_IDCODE_XILINX_ID_SHIFT |
ZYNQMP_CSU_IDCODE_FAMILY << ZYNQMP_CSU_IDCODE_FAMILY_SHIFT | ZYNQMP_CSU_IDCODE_FAMILY << ZYNQMP_CSU_IDCODE_FAMILY_SHIFT;
ZYNQMP_CSU_IDCODE_REVISION << ZYNQMP_CSU_IDCODE_REVISION_SHIFT;
if (tmp != maskid) { if (tmp != maskid) {
ERROR("Incorrect XILINX IDCODE 0x%x, maskid 0x%x\n", id, maskid); ERROR("Incorrect XILINX IDCODE 0x%x, maskid 0x%x\n", id, maskid);
return "UNKN"; return "UNKN";
...@@ -275,13 +269,13 @@ int zynqmp_is_pmu_up(void) ...@@ -275,13 +269,13 @@ int zynqmp_is_pmu_up(void)
return zynqmp_pmufw_present; return zynqmp_pmufw_present;
} }
/* unsigned int zynqmp_get_bootmode(void)
* A single boot loader stack is expected to work on both the Foundation ZYNQMP {
* models and the two flavours of the Base ZYNQMP models (AEMv8 & Cortex). The uint32_t r = mmio_read_32(CRL_APB_BOOT_MODE_USER);
* SYS_ID register provides a mechanism for detecting the differences between
* these platforms. This information is stored in a per-BL array to allow the return r & CRL_APB_BOOT_MODE_MASK;
* code to take the correct path.Per BL platform configuration. }
*/
void zynqmp_config_setup(void) void zynqmp_config_setup(void)
{ {
zynqmp_discover_pmufw(); zynqmp_discover_pmufw();
......
...@@ -113,25 +113,25 @@ void bl31_early_platform_setup(bl31_params_t *from_bl2, ...@@ -113,25 +113,25 @@ void bl31_early_platform_setup(bl31_params_t *from_bl2,
* present. * present.
*/ */
/* Populate entry point information for BL32 and BL33 */ /* Populate common information for BL32 and BL33 */
SET_PARAM_HEAD(&bl32_image_ep_info, PARAM_EP, VERSION_1, 0); SET_PARAM_HEAD(&bl32_image_ep_info, PARAM_EP, VERSION_1, 0);
SET_SECURITY_STATE(bl32_image_ep_info.h.attr, SECURE); SET_SECURITY_STATE(bl32_image_ep_info.h.attr, SECURE);
bl32_image_ep_info.pc = BL32_BASE;
bl32_image_ep_info.spsr = arm_get_spsr_for_bl32_entry();
NOTICE("BL31: Secure code at 0x%lx\n", bl32_image_ep_info.pc);
SET_PARAM_HEAD(&bl33_image_ep_info, PARAM_EP, VERSION_1, 0); SET_PARAM_HEAD(&bl33_image_ep_info, PARAM_EP, VERSION_1, 0);
SET_SECURITY_STATE(bl33_image_ep_info.h.attr, NON_SECURE);
/* if (zynqmp_get_bootmode() == ZYNQMP_BOOTMODE_JTAG) {
* Tell BL31 where the non-trusted software image /* use build time defaults in JTAG boot mode */
* is located and the entry state information bl32_image_ep_info.pc = BL32_BASE;
*/ bl32_image_ep_info.spsr = arm_get_spsr_for_bl32_entry();
bl33_image_ep_info.pc = plat_get_ns_image_entrypoint(); bl33_image_ep_info.pc = plat_get_ns_image_entrypoint();
bl33_image_ep_info.spsr = SPSR_64(MODE_EL2, MODE_SP_ELX, bl33_image_ep_info.spsr = SPSR_64(MODE_EL2, MODE_SP_ELX,
DISABLE_ALL_EXCEPTIONS); DISABLE_ALL_EXCEPTIONS);
SET_SECURITY_STATE(bl33_image_ep_info.h.attr, NON_SECURE); } else {
/* use parameters from FSBL */
fsbl_atf_handover(&bl32_image_ep_info, &bl33_image_ep_info);
}
NOTICE("BL31: Secure code at 0x%lx\n", bl32_image_ep_info.pc);
NOTICE("BL31: Non secure code at 0x%lx\n", bl33_image_ep_info.pc); NOTICE("BL31: Non secure code at 0x%lx\n", bl33_image_ep_info.pc);
} }
......
...@@ -50,64 +50,62 @@ ...@@ -50,64 +50,62 @@
/******************************************************************************* /*******************************************************************************
* BL31 specific defines. * BL31 specific defines.
******************************************************************************/ ******************************************************************************/
#define ZYNQMP_BL31_SIZE 0x1b000
/* /*
* Put BL31 at the top of the Trusted SRAM (just below the shared memory, if * Put BL31 at the top of the Trusted SRAM (just below the shared memory, if
* present). BL31_BASE is calculated using the current BL31 debug size plus a * present). BL31_BASE is calculated using the current BL31 debug size plus a
* little space for growth. * little space for growth.
*/ */
#if ZYNQMP_ATF_LOCATION_ID == ZYNQMP_IN_TRUSTED_SRAM #ifndef ZYNQMP_ATF_MEM_BASE
# define BL31_BASE (ZYNQMP_TRUSTED_SRAM_LIMIT - \ # define BL31_BASE 0xfffe5000
ZYNQMP_BL31_SIZE) # define BL31_PROGBITS_LIMIT 0xffffa000
# define BL31_PROGBITS_LIMIT (ZYNQMP_TRUSTED_SRAM_LIMIT - 0x6000) # define BL31_LIMIT 0xffffffff
# define BL31_LIMIT ZYNQMP_TRUSTED_SRAM_LIMIT
#elif ZYNQMP_ATF_LOCATION_ID == ZYNQMP_IN_TRUSTED_DRAM
# define BL31_BASE (ZYNQMP_TRUSTED_DRAM_LIMIT - \
ZYNQMP_BL31_SIZE)
# define BL31_PROGBITS_LIMIT (ZYNQMP_TRUSTED_DRAM_LIMIT - 0x6000)
# define BL31_LIMIT (ZYNQMP_TRUSTED_DRAM_BASE + \
ZYNQMP_TRUSTED_DRAM_SIZE)
#else #else
# error "Unsupported ZYNQMP_ATF_LOCATION_ID value" # define BL31_BASE (ZYNQMP_ATF_MEM_BASE)
# define BL31_LIMIT (ZYNQMP_ATF_MEM_BASE + ZYNQMP_ATF_MEM_SIZE - 1)
# ifdef ZYNQMP_ATF_MEM_PROGBITS_SIZE
# define BL31_PROGBITS_LIMIT (ZYNQMP_ATF_MEM_BASE + ZYNQMP_ATF_MEM_PROGBITS_SIZE - 1)
# endif
#endif #endif
/******************************************************************************* /*******************************************************************************
* BL32 specific defines. * BL32 specific defines.
******************************************************************************/ ******************************************************************************/
/* #ifndef ZYNQMP_BL32_MEM_BASE
* On ZYNQMP, the TSP can execute either from Trusted SRAM or Trusted DRAM. # define BL32_BASE 0x60000000
*/ # define BL32_LIMIT 0x7fffffff
#if ZYNQMP_TSP_RAM_LOCATION_ID == ZYNQMP_IN_TRUSTED_SRAM
# define TSP_SEC_MEM_BASE ZYNQMP_TRUSTED_SRAM_BASE
# define TSP_SEC_MEM_SIZE ZYNQMP_TRUSTED_SRAM_SIZE
# define TSP_PROGBITS_LIMIT (ZYNQMP_TRUSTED_SRAM_LIMIT - \
ZYNQMP_BL31_SIZE)
# define BL32_BASE ZYNQMP_TRUSTED_SRAM_BASE
# define BL32_LIMIT (ZYNQMP_TRUSTED_SRAM_LIMIT - \
ZYNQMP_BL31_SIZE)
#elif ZYNQMP_TSP_RAM_LOCATION_ID == ZYNQMP_IN_TRUSTED_DRAM
# define TSP_SEC_MEM_BASE ZYNQMP_TRUSTED_DRAM_BASE
# define TSP_SEC_MEM_SIZE (ZYNQMP_TRUSTED_DRAM_LIMIT - \
ZYNQMP_BL31_SIZE)
# define BL32_BASE ZYNQMP_TRUSTED_DRAM_BASE
# define BL32_LIMIT (ZYNQMP_TRUSTED_DRAM_LIMIT - \
ZYNQMP_BL31_SIZE)
#else #else
# error "Unsupported ZYNQMP_TSP_RAM_LOCATION_ID value" # define BL32_BASE (ZYNQMP_BL32_MEM_BASE)
# define BL32_LIMIT (ZYNQMP_BL32_MEM_BASE + ZYNQMP_BL32_MEM_SIZE - 1)
#endif #endif
/* /*******************************************************************************
* ID of the secure physical generic timer interrupt used by the TSP. * BL33 specific defines.
*/ ******************************************************************************/
#ifndef PRELOADED_BL33_BASE
# define PLAT_ARM_NS_IMAGE_OFFSET 0x8000000
#else
# define PLAT_ARM_NS_IMAGE_OFFSET PRELOADED_BL33_BASE
#endif
/*******************************************************************************
* TSP specific defines.
******************************************************************************/
#define TSP_SEC_MEM_BASE BL32_BASE
#define TSP_SEC_MEM_SIZE (BL32_LIMIT - BL32_BASE + 1)
/* ID of the secure physical generic timer interrupt used by the TSP */
#define TSP_IRQ_SEC_PHY_TIMER ARM_IRQ_SEC_PHY_TIMER #define TSP_IRQ_SEC_PHY_TIMER ARM_IRQ_SEC_PHY_TIMER
/******************************************************************************* /*******************************************************************************
* Platform specific page table and MMU setup constants * Platform specific page table and MMU setup constants
******************************************************************************/ ******************************************************************************/
#define ADDR_SPACE_SIZE (1ull << 32) #define ADDR_SPACE_SIZE (1ull << 32)
#define MAX_XLAT_TABLES 5 #define MAX_MMAP_REGIONS 6
#define MAX_MMAP_REGIONS 7 #if IMAGE_BL32
# define MAX_XLAT_TABLES 5
#else
# define MAX_XLAT_TABLES 4
#endif
#define CACHE_WRITEBACK_SHIFT 6 #define CACHE_WRITEBACK_SHIFT 6
#define CACHE_WRITEBACK_GRANULE (1 << CACHE_WRITEBACK_SHIFT) #define CACHE_WRITEBACK_GRANULE (1 << CACHE_WRITEBACK_SHIFT)
......
...@@ -104,7 +104,7 @@ static int zynqmp_pwr_domain_on(u_register_t mpidr) ...@@ -104,7 +104,7 @@ static int zynqmp_pwr_domain_on(u_register_t mpidr)
proc = pm_get_proc(cpu_id); proc = pm_get_proc(cpu_id);
/* Send request to PMU to wake up selected APU CPU core */ /* Send request to PMU to wake up selected APU CPU core */
pm_req_wakeup(proc->node_id, 1, zynqmp_sec_entry, REQ_ACK_NO); pm_req_wakeup(proc->node_id, 1, zynqmp_sec_entry, REQ_ACK_BLOCKING);
return PSCI_E_SUCCESS; return PSCI_E_SUCCESS;
} }
......
/*
* Copyright (c) 2014-2016, ARM Limited and Contributors. All rights reserved.
*
* 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 <arch_helpers.h>
#include <assert.h>
#include <debug.h>
#include <mmio.h>
#include "zynqmp_def.h"
/*
* ATFHandoffParams
* Parameter bitfield encoding
* -----------------------------------------------------------------------------
* Exec State 0 0 -> Aarch64, 1-> Aarch32
* endianness 1 0 -> LE, 1 -> BE
* secure (TZ) 2 0 -> Non secure, 1 -> secure
* EL 3:4 00 -> EL0, 01 -> EL1, 10 -> EL2, 11 -> EL3
* CPU# 5:6 00 -> A53_0, 01 -> A53_1, 10 -> A53_2, 11 -> A53_3
*/
#define FSBL_FLAGS_ESTATE_SHIFT 0
#define FSBL_FLAGS_ESTATE_MASK (1 << FSBL_FLAGS_ESTATE_SHIFT)
#define FSBL_FLAGS_ESTATE_A64 0
#define FSBL_FLAGS_ESTATE_A32 1
#define FSBL_FLAGS_ENDIAN_SHIFT 1
#define FSBL_FLAGS_ENDIAN_MASK (1 << FSBL_FLAGS_ENDIAN_SHIFT)
#define FSBL_FLAGS_ENDIAN_LE 0
#define FSBL_FLAGS_ENDIAN_BE 1
#define FSBL_FLAGS_TZ_SHIFT 2
#define FSBL_FLAGS_TZ_MASK (1 << FSBL_FLAGS_TZ_SHIFT)
#define FSBL_FLAGS_NON_SECURE 0
#define FSBL_FLAGS_SECURE 1
#define FSBL_FLAGS_EL_SHIFT 3
#define FSBL_FLAGS_EL_MASK (3 << FSBL_FLAGS_EL_SHIFT)
#define FSBL_FLAGS_EL0 0
#define FSBL_FLAGS_EL1 1
#define FSBL_FLAGS_EL2 2
#define FSBL_FLAGS_EL3 3
#define FSBL_FLAGS_CPU_SHIFT 5
#define FSBL_FLAGS_CPU_MASK (3 << FSBL_FLAGS_CPU_SHIFT)
#define FSBL_FLAGS_A53_0 0
#define FSBL_FLAGS_A53_1 1
#define FSBL_FLAGS_A53_2 2
#define FSBL_FLAGS_A53_3 3
#define FSBL_MAX_PARTITIONS 8
/* Structure corresponding to each partition entry */
struct xfsbl_partition {
uint64_t entry_point;
uint64_t flags;
};
/* Structure for handoff parameters to ARM Trusted Firmware (ATF) */
struct xfsbl_atf_handoff_params {
uint8_t magic[4];
uint32_t num_entries;
struct xfsbl_partition partition[FSBL_MAX_PARTITIONS];
};
/**
* @partition: Pointer to partition struct
*
* Get the target CPU for @partition.
*
* Return: FSBL_FLAGS_A53_0, FSBL_FLAGS_A53_1, FSBL_FLAGS_A53_2 or FSBL_FLAGS_A53_3
*/
static int get_fsbl_cpu(const struct xfsbl_partition *partition)
{
uint64_t flags = partition->flags & FSBL_FLAGS_CPU_MASK;
return flags >> FSBL_FLAGS_CPU_SHIFT;
}
/**
* @partition: Pointer to partition struct
*
* Get the target exception level for @partition.
*
* Return: FSBL_FLAGS_EL0, FSBL_FLAGS_EL1, FSBL_FLAGS_EL2 or FSBL_FLAGS_EL3
*/
static int get_fsbl_el(const struct xfsbl_partition *partition)
{
uint64_t flags = partition->flags & FSBL_FLAGS_EL_MASK;
return flags >> FSBL_FLAGS_EL_SHIFT;
}
/**
* @partition: Pointer to partition struct
*
* Get the target security state for @partition.
*
* Return: FSBL_FLAGS_NON_SECURE or FSBL_FLAGS_SECURE
*/
static int get_fsbl_ss(const struct xfsbl_partition *partition)
{
uint64_t flags = partition->flags & FSBL_FLAGS_TZ_MASK;
return flags >> FSBL_FLAGS_TZ_SHIFT;
}
/**
* @partition: Pointer to partition struct
*
* Get the target endianness for @partition.
*
* Return: SPSR_E_LITTLE or SPSR_E_BIG
*/
static int get_fsbl_endian(const struct xfsbl_partition *partition)
{
uint64_t flags = partition->flags & FSBL_FLAGS_ENDIAN_MASK;
flags >>= FSBL_FLAGS_ENDIAN_SHIFT;
if (flags == FSBL_FLAGS_ENDIAN_BE)
return SPSR_E_BIG;
else
return SPSR_E_LITTLE;
}
/**
* @partition: Pointer to partition struct
*
* Get the target execution state for @partition.
*
* Return: FSBL_FLAGS_ESTATE_A32 or FSBL_FLAGS_ESTATE_A64
*/
static int get_fsbl_estate(const struct xfsbl_partition *partition)
{
uint64_t flags = partition->flags & FSBL_FLAGS_ESTATE_MASK;
return flags >> FSBL_FLAGS_ESTATE_SHIFT;
}
/**
* Populates the bl32 and bl33 image info structures
* @bl32: BL32 image info structure
* @bl33: BL33 image info structure
*
* Process the handoff paramters from the FSBL and populate the BL32 and BL33
* image info structures accordingly.
*/
void fsbl_atf_handover(entry_point_info_t *bl32, entry_point_info_t *bl33)
{
uint64_t atf_handoff_addr;
const struct xfsbl_atf_handoff_params *ATFHandoffParams;
atf_handoff_addr = mmio_read_32(PMU_GLOBAL_GEN_STORAGE6);
assert((atf_handoff_addr < BL31_BASE) ||
(atf_handoff_addr > (uint64_t)&__BL31_END__));
if (!atf_handoff_addr) {
ERROR("BL31: No ATF handoff structure passed\n");
panic();
}
ATFHandoffParams = (struct xfsbl_atf_handoff_params *)atf_handoff_addr;
if ((ATFHandoffParams->magic[0] != 'X') ||
(ATFHandoffParams->magic[1] != 'L') ||
(ATFHandoffParams->magic[2] != 'N') ||
(ATFHandoffParams->magic[3] != 'X')) {
ERROR("BL31: invalid ATF handoff structure at %lx\n",
atf_handoff_addr);
panic();
}
VERBOSE("BL31: ATF handoff params at:0x%lx, entries:%u\n",
atf_handoff_addr, ATFHandoffParams->num_entries);
if (ATFHandoffParams->num_entries > FSBL_MAX_PARTITIONS) {
ERROR("BL31: ATF handoff params: too many partitions (%u/%u)\n",
ATFHandoffParams->num_entries, FSBL_MAX_PARTITIONS);
panic();
}
/*
* we loop over all passed entries but only populate two image structs
* (bl32, bl33). I.e. the last applicable images in the handoff
* structure will be used for the hand off
*/
for (size_t i = 0; i < ATFHandoffParams->num_entries; i++) {
entry_point_info_t *image;
int target_estate, target_secure;
int target_cpu, target_endianness, target_el;
VERBOSE("BL31: %zd: entry:0x%lx, flags:0x%lx\n", i,
ATFHandoffParams->partition[i].entry_point,
ATFHandoffParams->partition[i].flags);
target_cpu = get_fsbl_cpu(&ATFHandoffParams->partition[i]);
if (target_cpu != FSBL_FLAGS_A53_0) {
WARN("BL31: invalid target CPU (%i)\n", target_cpu);
continue;
}
target_el = get_fsbl_el(&ATFHandoffParams->partition[i]);
if ((target_el == FSBL_FLAGS_EL3) ||
(target_el == FSBL_FLAGS_EL0)) {
WARN("BL31: invalid exception level (%i)\n", target_el);
continue;
}
target_secure = get_fsbl_ss(&ATFHandoffParams->partition[i]);
if (target_secure == FSBL_FLAGS_SECURE &&
target_el == FSBL_FLAGS_EL2) {
WARN("BL31: invalid security state (%i) for exception level (%i)\n",
target_secure, target_el);
continue;
}
target_estate = get_fsbl_estate(&ATFHandoffParams->partition[i]);
target_endianness = get_fsbl_endian(&ATFHandoffParams->partition[i]);
if (target_secure == FSBL_FLAGS_SECURE) {
image = bl32;
if (target_estate == FSBL_FLAGS_ESTATE_A32)
bl32->spsr = SPSR_MODE32(MODE32_svc, SPSR_T_ARM,
target_endianness,
DISABLE_ALL_EXCEPTIONS);
else
bl32->spsr = SPSR_64(MODE_EL1, MODE_SP_ELX,
DISABLE_ALL_EXCEPTIONS);
} else {
image = bl33;
if (target_estate == FSBL_FLAGS_ESTATE_A32) {
if (target_el == FSBL_FLAGS_EL2)
target_el = MODE32_hyp;
else
target_el = MODE32_sys;
bl33->spsr = SPSR_MODE32(target_el, SPSR_T_ARM,
target_endianness,
DISABLE_ALL_EXCEPTIONS);
} else {
if (target_el == FSBL_FLAGS_EL2)
target_el = MODE_EL2;
else
target_el = MODE_EL1;
bl33->spsr = SPSR_64(target_el, MODE_SP_ELX,
DISABLE_ALL_EXCEPTIONS);
}
}
VERBOSE("Setting up %s entry point to:%lx, el:%x\n",
target_secure == FSBL_FLAGS_SECURE ? "BL32" : "BL33",
ATFHandoffParams->partition[i].entry_point,
target_el);
image->pc = ATFHandoffParams->partition[i].entry_point;
if (target_endianness == SPSR_E_BIG)
EP_SET_EE(image->h.attr, EP_EE_BIG);
else
EP_SET_EE(image->h.attr, EP_EE_LITTLE);
}
}
...@@ -28,9 +28,6 @@ ...@@ -28,9 +28,6 @@
* POSSIBILITY OF SUCH DAMAGE. * POSSIBILITY OF SUCH DAMAGE.
*/ */
#include <platform_def.h>
#include <psci.h>
static const unsigned char plat_power_domain_tree_desc[] = {1, 4}; static const unsigned char plat_power_domain_tree_desc[] = {1, 4};
const unsigned char *plat_get_power_domain_tree_desc(void) const unsigned char *plat_get_power_domain_tree_desc(void)
......
...@@ -31,29 +31,27 @@ PROGRAMMABLE_RESET_ADDRESS := 1 ...@@ -31,29 +31,27 @@ PROGRAMMABLE_RESET_ADDRESS := 1
PSCI_EXTENDED_STATE_ID := 1 PSCI_EXTENDED_STATE_ID := 1
A53_DISABLE_NON_TEMPORAL_HINT := 0 A53_DISABLE_NON_TEMPORAL_HINT := 0
ZYNQMP_ATF_LOCATION ?= tsram ifdef ZYNQMP_ATF_MEM_BASE
ifeq (${ZYNQMP_ATF_LOCATION}, tsram) $(eval $(call add_define,ZYNQMP_ATF_MEM_BASE))
ZYNQMP_ATF_LOCATION_ID := ZYNQMP_IN_TRUSTED_SRAM
else ifeq (${ZYNQMP_ATF_LOCATION}, tdram) ifndef ZYNQMP_ATF_MEM_SIZE
ZYNQMP_ATF_LOCATION_ID := ZYNQMP_IN_TRUSTED_DRAM $(error "ZYNQMP_ATF_BASE defined without ZYNQMP_ATF_SIZE")
else endif
$(error "Unsupported ZYNQMP_ATF_LOCATION value") $(eval $(call add_define,ZYNQMP_ATF_MEM_SIZE))
endif
# On ZYNQMP, the TSP can execute either from Trusted SRAM or Trusted DRAM. ifdef ZYNQMP_ATF_MEM_PROGBITS_SIZE
# Trusted SRAM is the default. $(eval $(call add_define,ZYNQMP_ATF_MEM_PROGBITS_SIZE))
ZYNQMP_TSP_RAM_LOCATION ?= tsram endif
ifeq (${ZYNQMP_TSP_RAM_LOCATION}, tsram)
ZYNQMP_TSP_RAM_LOCATION_ID := ZYNQMP_IN_TRUSTED_SRAM
else ifeq (${ZYNQMP_TSP_RAM_LOCATION}, tdram)
ZYNQMP_TSP_RAM_LOCATION_ID := ZYNQMP_IN_TRUSTED_DRAM
else
$(error "Unsupported ZYNQMP_TSP_RAM_LOCATION value")
endif endif
# Process flags ifdef ZYNQMP_BL32_MEM_BASE
$(eval $(call add_define,ZYNQMP_ATF_LOCATION_ID)) $(eval $(call add_define,ZYNQMP_BL32_MEM_BASE))
$(eval $(call add_define,ZYNQMP_TSP_RAM_LOCATION_ID))
ifndef ZYNQMP_BL32_MEM_SIZE
$(error "ZYNQMP_BL32_BASE defined without ZYNQMP_BL32_SIZE")
endif
$(eval $(call add_define,ZYNQMP_BL32_MEM_SIZE))
endif
PLAT_INCLUDES := -Iinclude/plat/arm/common/ \ PLAT_INCLUDES := -Iinclude/plat/arm/common/ \
-Iinclude/plat/arm/common/aarch64/ \ -Iinclude/plat/arm/common/aarch64/ \
...@@ -84,6 +82,7 @@ BL31_SOURCES += drivers/arm/cci/cci.c \ ...@@ -84,6 +82,7 @@ BL31_SOURCES += drivers/arm/cci/cci.c \
plat/xilinx/zynqmp/bl31_zynqmp_setup.c \ plat/xilinx/zynqmp/bl31_zynqmp_setup.c \
plat/xilinx/zynqmp/plat_psci.c \ plat/xilinx/zynqmp/plat_psci.c \
plat/xilinx/zynqmp/plat_zynqmp.c \ plat/xilinx/zynqmp/plat_zynqmp.c \
plat/xilinx/zynqmp/plat_startup.c \
plat/xilinx/zynqmp/plat_topology.c \ plat/xilinx/zynqmp/plat_topology.c \
plat/xilinx/zynqmp/sip_svc_setup.c \ plat/xilinx/zynqmp/sip_svc_setup.c \
plat/xilinx/zynqmp/pm_service/pm_svc_main.c \ plat/xilinx/zynqmp/pm_service/pm_svc_main.c \
......
...@@ -36,6 +36,7 @@ ...@@ -36,6 +36,7 @@
#include <arch_helpers.h> #include <arch_helpers.h>
#include <platform.h> #include <platform.h>
#include "pm_client.h" #include "pm_client.h"
#include "pm_ipi.h"
#include "pm_common.h" #include "pm_common.h"
#include "pm_api_sys.h" #include "pm_api_sys.h"
...@@ -390,21 +391,34 @@ enum pm_ret_status pm_register_notifier(enum pm_node_id nid, ...@@ -390,21 +391,34 @@ enum pm_ret_status pm_register_notifier(enum pm_node_id nid,
unsigned int wake, unsigned int wake,
unsigned int enable) unsigned int enable)
{ {
return PM_RET_ERROR_NOTSUPPORTED; uint32_t payload[PAYLOAD_ARG_CNT];
PM_PACK_PAYLOAD5(payload, PM_REGISTER_NOTIFIER,
nid, event, wake, enable);
return pm_ipi_send(primary_proc, payload);
} }
/** /**
* pm_get_op_characteristic() - PM call to get a particular operating * pm_get_op_characteristic() - PM call to request operating characteristics
* characteristic of a node * of a node
* @nid Node ID * @nid Node id of the slave
* @type Operating characterstic type to be returned * @type Type of the operating characteristic
* (power, temperature and latency)
* @result Returns the operating characteristic for the requested node,
* specified by the type
* *
* @return Returns status, either success or error+reason * @return Returns status, either success or error+reason
*/ */
enum pm_ret_status pm_get_op_characteristic(enum pm_node_id nid, enum pm_ret_status pm_get_op_characteristic(enum pm_node_id nid,
enum pm_opchar_type type) enum pm_opchar_type type,
uint32_t *result)
{ {
return PM_RET_ERROR_NOTSUPPORTED; uint32_t payload[PAYLOAD_ARG_CNT];
/* Send request to the PMU */
PM_PACK_PAYLOAD3(payload, PM_GET_OP_CHARACTERISTIC, nid, type);
return pm_ipi_send_sync(primary_proc, payload, result);
} }
/* Direct-Control API functions */ /* Direct-Control API functions */
......
...@@ -91,7 +91,8 @@ enum pm_ret_status pm_register_notifier(enum pm_node_id nid, ...@@ -91,7 +91,8 @@ enum pm_ret_status pm_register_notifier(enum pm_node_id nid,
unsigned int wake, unsigned int wake,
unsigned int enable); unsigned int enable);
enum pm_ret_status pm_get_op_characteristic(enum pm_node_id nid, enum pm_ret_status pm_get_op_characteristic(enum pm_node_id nid,
enum pm_opchar_type type); enum pm_opchar_type type,
uint32_t *result);
enum pm_ret_status pm_acknowledge_cb(enum pm_node_id nid, enum pm_ret_status pm_acknowledge_cb(enum pm_node_id nid,
enum pm_ret_status status, enum pm_ret_status status,
unsigned int oppoint); unsigned int oppoint);
......
...@@ -33,6 +33,7 @@ ...@@ -33,6 +33,7 @@
* for getting information about and changing state of the APU. * for getting information about and changing state of the APU.
*/ */
#include <bakery_lock.h>
#include <gicv2.h> #include <gicv2.h>
#include <bl_common.h> #include <bl_common.h>
#include <mmio.h> #include <mmio.h>
...@@ -47,6 +48,7 @@ ...@@ -47,6 +48,7 @@
#define OCM_BANK_3 (OCM_BANK_2 + 0x10000) #define OCM_BANK_3 (OCM_BANK_2 + 0x10000)
#define UNDEFINED_CPUID (~0) #define UNDEFINED_CPUID (~0)
DEFINE_BAKERY_LOCK(pm_client_secure_lock);
/* Declaration of linker defined symbol */ /* Declaration of linker defined symbol */
extern unsigned long __BL31_END__; extern unsigned long __BL31_END__;
...@@ -162,8 +164,12 @@ const struct pm_proc *primary_proc = &pm_procs_all[0]; ...@@ -162,8 +164,12 @@ const struct pm_proc *primary_proc = &pm_procs_all[0];
*/ */
void pm_client_suspend(const struct pm_proc *proc) void pm_client_suspend(const struct pm_proc *proc)
{ {
bakery_lock_get(&pm_client_secure_lock);
/* Set powerdown request */ /* Set powerdown request */
mmio_write_32(APU_PWRCTL, mmio_read_32(APU_PWRCTL) | proc->pwrdn_mask); mmio_write_32(APU_PWRCTL, mmio_read_32(APU_PWRCTL) | proc->pwrdn_mask);
bakery_lock_release(&pm_client_secure_lock);
} }
...@@ -177,9 +183,14 @@ void pm_client_abort_suspend(void) ...@@ -177,9 +183,14 @@ void pm_client_abort_suspend(void)
{ {
/* Enable interrupts at processor level (for current cpu) */ /* Enable interrupts at processor level (for current cpu) */
gicv2_cpuif_enable(); gicv2_cpuif_enable();
bakery_lock_get(&pm_client_secure_lock);
/* Clear powerdown request */ /* Clear powerdown request */
mmio_write_32(APU_PWRCTL, mmio_write_32(APU_PWRCTL,
mmio_read_32(APU_PWRCTL) & ~primary_proc->pwrdn_mask); mmio_read_32(APU_PWRCTL) & ~primary_proc->pwrdn_mask);
bakery_lock_release(&pm_client_secure_lock);
} }
/** /**
...@@ -195,8 +206,12 @@ void pm_client_wakeup(const struct pm_proc *proc) ...@@ -195,8 +206,12 @@ void pm_client_wakeup(const struct pm_proc *proc)
if (cpuid == UNDEFINED_CPUID) if (cpuid == UNDEFINED_CPUID)
return; return;
bakery_lock_get(&pm_client_secure_lock);
/* clear powerdown bit for affected cpu */ /* clear powerdown bit for affected cpu */
uint32_t val = mmio_read_32(APU_PWRCTL); uint32_t val = mmio_read_32(APU_PWRCTL);
val &= ~(proc->pwrdn_mask); val &= ~(proc->pwrdn_mask);
mmio_write_32(APU_PWRCTL, val); mmio_write_32(APU_PWRCTL, val);
bakery_lock_release(&pm_client_secure_lock);
} }
...@@ -40,11 +40,6 @@ ...@@ -40,11 +40,6 @@
#include "pm_common.h" #include "pm_common.h"
/* Functions to be implemented by each PU */ /* Functions to be implemented by each PU */
enum pm_ret_status pm_ipi_send(const struct pm_proc *proc,
uint32_t payload[PAYLOAD_ARG_CNT]);
enum pm_ret_status pm_ipi_send_sync(const struct pm_proc *proc,
uint32_t payload[PAYLOAD_ARG_CNT],
uint32_t *val);
void pm_client_suspend(const struct pm_proc *proc); void pm_client_suspend(const struct pm_proc *proc);
void pm_client_abort_suspend(void); void pm_client_abort_suspend(void);
void pm_client_wakeup(const struct pm_proc *proc); void pm_client_wakeup(const struct pm_proc *proc);
......
...@@ -142,6 +142,7 @@ enum pm_node_id { ...@@ -142,6 +142,7 @@ enum pm_node_id {
NODE_RPLL, NODE_RPLL,
NODE_IOPLL, NODE_IOPLL,
NODE_DDR, NODE_DDR,
NODE_IPI_APU,
}; };
enum pm_request_ack { enum pm_request_ack {
...@@ -171,8 +172,8 @@ enum pm_ram_state { ...@@ -171,8 +172,8 @@ enum pm_ram_state {
enum pm_opchar_type { enum pm_opchar_type {
PM_OPCHAR_TYPE_POWER = 1, PM_OPCHAR_TYPE_POWER = 1,
PM_OPCHAR_TYPE_ENERGY,
PM_OPCHAR_TYPE_TEMP, PM_OPCHAR_TYPE_TEMP,
PM_OPCHAR_TYPE_LATENCY,
}; };
/** /**
......
...@@ -66,8 +66,7 @@ ...@@ -66,8 +66,7 @@
#define IPI_APU_ISR (IPI_BASEADDR + 0X00000010) #define IPI_APU_ISR (IPI_BASEADDR + 0X00000010)
#define IPI_APU_IER (IPI_BASEADDR + 0X00000018) #define IPI_APU_IER (IPI_BASEADDR + 0X00000018)
#define IPI_APU_IDR (IPI_BASEADDR + 0X0000001C) #define IPI_APU_IDR (IPI_BASEADDR + 0X0000001C)
#define IPI_APU_ISR_PMU_0_MASK 0X00010000 #define IPI_APU_IXR_PMU_0_MASK (1 << 16)
#define IPI_APU_IER_PMU_0_MASK 0X00010000
#define IPI_TRIG_OFFSET 0 #define IPI_TRIG_OFFSET 0
#define IPI_OBS_OFFSET 4 #define IPI_OBS_OFFSET 4
...@@ -75,14 +74,14 @@ ...@@ -75,14 +74,14 @@
/* Power Management IPI interrupt number */ /* Power Management IPI interrupt number */
#define PM_INT_NUM 0 #define PM_INT_NUM 0
#define IPI_PMU_PM_INT_BASE (IPI_PMU_0_TRIG + (PM_INT_NUM * 0x1000)) #define IPI_PMU_PM_INT_BASE (IPI_PMU_0_TRIG + (PM_INT_NUM * 0x1000))
#define IPI_PMU_PM_INT_MASK (IPI_APU_ISR_PMU_0_MASK << PM_INT_NUM) #define IPI_PMU_PM_INT_MASK (IPI_APU_IXR_PMU_0_MASK << PM_INT_NUM)
#if (PM_INT_NUM < 0 || PM_INT_NUM > 3) #if (PM_INT_NUM < 0 || PM_INT_NUM > 3)
#error PM_INT_NUM value out of range #error PM_INT_NUM value out of range
#endif #endif
#define IPI_APU_MASK 1U #define IPI_APU_MASK 1U
static bakery_lock_t pm_secure_lock; DEFINE_BAKERY_LOCK(pm_secure_lock);
const struct pm_ipi apu_ipi = { const struct pm_ipi apu_ipi = {
.mask = IPI_APU_MASK, .mask = IPI_APU_MASK,
......
...@@ -191,8 +191,12 @@ uint64_t pm_smc_handler(uint32_t smc_fid, uint64_t x1, uint64_t x2, uint64_t x3, ...@@ -191,8 +191,12 @@ uint64_t pm_smc_handler(uint32_t smc_fid, uint64_t x1, uint64_t x2, uint64_t x3,
SMC_RET1(handle, (uint64_t)ret); SMC_RET1(handle, (uint64_t)ret);
case PM_GET_OP_CHARACTERISTIC: case PM_GET_OP_CHARACTERISTIC:
ret = pm_get_op_characteristic(pm_arg[0], pm_arg[1]); {
SMC_RET1(handle, (uint64_t)ret); uint32_t result;
ret = pm_get_op_characteristic(pm_arg[0], pm_arg[1], &result);
SMC_RET1(handle, (uint64_t)ret | ((uint64_t)result << 32));
}
case PM_REGISTER_NOTIFIER: case PM_REGISTER_NOTIFIER:
ret = pm_register_notifier(pm_arg[0], pm_arg[1], pm_arg[2], ret = pm_register_notifier(pm_arg[0], pm_arg[1], pm_arg[2],
......
...@@ -32,9 +32,7 @@ ...@@ -32,9 +32,7 @@
#include <console.h> #include <console.h>
#include <debug.h> #include <debug.h>
#include <platform_tsp.h> #include <platform_tsp.h>
#include <xlat_tables.h>
#include <plat_arm.h> #include <plat_arm.h>
#include "../zynqmp_def.h"
#include "../zynqmp_private.h" #include "../zynqmp_private.h"
/* /*
......
...@@ -43,24 +43,11 @@ ...@@ -43,24 +43,11 @@
/******************************************************************************* /*******************************************************************************
* ZYNQMP memory map related constants * ZYNQMP memory map related constants
******************************************************************************/ ******************************************************************************/
#define ZYNQMP_TRUSTED_SRAM_BASE 0xFFFC0000
#define ZYNQMP_TRUSTED_SRAM_SIZE 0x00040000
#define ZYNQMP_TRUSTED_SRAM_LIMIT (ZYNQMP_TRUSTED_SRAM_BASE + \
ZYNQMP_TRUSTED_SRAM_SIZE)
/* Location of trusted dram on the base zynqmp */
#define ZYNQMP_TRUSTED_DRAM_BASE 0x30000000 /* Can't overlap TZROM area */
#define ZYNQMP_TRUSTED_DRAM_SIZE 0x10000000
#define ZYNQMP_TRUSTED_DRAM_LIMIT (ZYNQMP_TRUSTED_DRAM_BASE + \
ZYNQMP_TRUSTED_DRAM_SIZE)
/* Aggregate of all devices in the first GB */ /* Aggregate of all devices in the first GB */
#define DEVICE0_BASE 0xFF000000 #define DEVICE0_BASE 0xFF000000
#define DEVICE0_SIZE 0x00E00000 #define DEVICE0_SIZE 0x00E00000
#define DEVICE1_BASE 0xF9000000 #define DEVICE1_BASE 0xF9000000
#define DEVICE1_SIZE 0x01000000 #define DEVICE1_SIZE 0x00800000
/* For cpu reset APU space here too 0xFE5F1000 CRF_APB*/ /* For cpu reset APU space here too 0xFE5F1000 CRF_APB*/
#define CRF_APB_BASE 0xFD1A0000 #define CRF_APB_BASE 0xFD1A0000
...@@ -76,6 +63,7 @@ ...@@ -76,6 +63,7 @@
#define CRL_APB_BASE 0xFF5E0000 #define CRL_APB_BASE 0xFF5E0000
#define CRL_APB_RPLL_CTRL (CRL_APB_BASE + 0x30) #define CRL_APB_RPLL_CTRL (CRL_APB_BASE + 0x30)
#define CRL_APB_TIMESTAMP_REF_CTRL (CRL_APB_BASE + 0x128) #define CRL_APB_TIMESTAMP_REF_CTRL (CRL_APB_BASE + 0x128)
#define CRL_APB_BOOT_MODE_USER (CRL_APB_BASE + 0x200)
#define CRL_APB_RESET_CTRL (CRL_APB_BASE + 0x218) #define CRL_APB_RESET_CTRL (CRL_APB_BASE + 0x218)
#define CRL_APB_TIMESTAMP_REF_CTRL_CLKACT_BIT (1 << 24) #define CRL_APB_TIMESTAMP_REF_CTRL_CLKACT_BIT (1 << 24)
...@@ -84,6 +72,9 @@ ...@@ -84,6 +72,9 @@
#define CRL_APB_RESET_CTRL_SOFT_RESET (1 << 4) #define CRL_APB_RESET_CTRL_SOFT_RESET (1 << 4)
#define CRL_APB_BOOT_MODE_MASK (0xf << 0)
#define ZYNQMP_BOOTMODE_JTAG 0
/* system counter registers and bitfields */ /* system counter registers and bitfields */
#define IOU_SCNTRS_BASE 0xFF260000 #define IOU_SCNTRS_BASE 0xFF260000
#define IOU_SCNTRS_CONTROL (IOU_SCNTRS_BASE + 0) #define IOU_SCNTRS_CONTROL (IOU_SCNTRS_BASE + 0)
...@@ -107,6 +98,7 @@ ...@@ -107,6 +98,7 @@
/* PMU registers and bitfields */ /* PMU registers and bitfields */
#define PMU_GLOBAL_BASE 0xFFD80000 #define PMU_GLOBAL_BASE 0xFFD80000
#define PMU_GLOBAL_CNTRL (PMU_GLOBAL_BASE + 0) #define PMU_GLOBAL_CNTRL (PMU_GLOBAL_BASE + 0)
#define PMU_GLOBAL_GEN_STORAGE6 (PMU_GLOBAL_BASE + 0x48)
#define PMU_GLOBAL_REQ_PWRUP_STATUS (PMU_GLOBAL_BASE + 0x110) #define PMU_GLOBAL_REQ_PWRUP_STATUS (PMU_GLOBAL_BASE + 0x110)
#define PMU_GLOBAL_REQ_PWRUP_EN (PMU_GLOBAL_BASE + 0x118) #define PMU_GLOBAL_REQ_PWRUP_EN (PMU_GLOBAL_BASE + 0x118)
#define PMU_GLOBAL_REQ_PWRUP_DIS (PMU_GLOBAL_BASE + 0x11c) #define PMU_GLOBAL_REQ_PWRUP_DIS (PMU_GLOBAL_BASE + 0x11c)
...@@ -114,16 +106,6 @@ ...@@ -114,16 +106,6 @@
#define PMU_GLOBAL_CNTRL_FW_IS_PRESENT (1 << 4) #define PMU_GLOBAL_CNTRL_FW_IS_PRESENT (1 << 4)
#define DRAM1_BASE 0x00000000ull
#define DRAM1_SIZE 0x10000000ull
#define DRAM1_END (DRAM1_BASE + DRAM1_SIZE - 1)
#define DRAM_BASE DRAM1_BASE
#define DRAM_SIZE DRAM1_SIZE
/* Load address of BL33 in the ZYNQMP port */
#define PLAT_ARM_NS_IMAGE_OFFSET (DRAM1_BASE + 0x8000000) /* DRAM + 128MB */
/******************************************************************************* /*******************************************************************************
* CCI-400 related constants * CCI-400 related constants
******************************************************************************/ ******************************************************************************/
......
...@@ -38,5 +38,10 @@ void zynqmp_config_setup(void); ...@@ -38,5 +38,10 @@ void zynqmp_config_setup(void);
/* ZynqMP specific functions */ /* ZynqMP specific functions */
unsigned int zynqmp_get_uart_clk(void); unsigned int zynqmp_get_uart_clk(void);
int zynqmp_is_pmu_up(void); int zynqmp_is_pmu_up(void);
unsigned int zynqmp_get_bootmode(void);
/* For FSBL handover */
void fsbl_atf_handover(entry_point_info_t *bl32_image_ep_info,
entry_point_info_t *bl33_image_ep_info);
#endif /* __ZYNQMP_PRIVATE_H__ */ #endif /* __ZYNQMP_PRIVATE_H__ */
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment