Newer
Older
barebox / arch / arm / cpu / sm.c
@Sascha Hauer Sascha Hauer on 13 Feb 2017 6 KB ARM: Add PSCI support
/*
 * (C) Copyright 2013
 * Andre Przywara, Linaro <andre.przywara@linaro.org>
 *
 * Routines to transition ARMv7 processors from secure into non-secure state
 * and from non-secure SVC into HYP mode
 * needed to enable ARMv7 virtualization for current hypervisors
 *
 * SPDX-License-Identifier:	GPL-2.0+
 */
#define pr_fmt(fmt) "secure: " fmt

#include <common.h>
#include <io.h>
#include <asm/gic.h>
#include <asm/system.h>
#include <init.h>
#include <globalvar.h>
#include <asm/arm-smccc.h>
#include <asm-generic/sections.h>
#include <asm/secure.h>

#include "mmu.h"

/* valid bits in CBAR register / PERIPHBASE value */
#define CBAR_MASK			0xFFFF8000

static unsigned int read_id_pfr1(void)
{
	unsigned int reg;

	asm("mrc p15, 0, %0, c0, c1, 1\n" : "=r"(reg));
	return reg;
}

static u32 read_nsacr(void)
{
	unsigned int reg;

	asm("mrc p15, 0, %0, c1, c1, 2\n" : "=r"(reg));
	return reg;
}

static void write_nsacr(u32 val)
{
	asm("mcr p15, 0, %0, c1, c1, 2" : : "r"(val));
}

static void write_mvbar(u32 val)
{
	asm("mcr p15, 0, %0, c12, c0, 1" : : "r"(val));
}

static unsigned long get_cbar(void)
{
	unsigned periphbase;

	/* get the GIC base address from the CBAR register */
	asm("mrc p15, 4, %0, c15, c0, 0\n" : "=r" (periphbase));

	/* the PERIPHBASE can be mapped above 4 GB (lower 8 bits used to
	 * encode this). Bail out here since we cannot access this without
	 * enabling paging.
	 */
	if ((periphbase & 0xff) != 0) {
		pr_err("PERIPHBASE is above 4 GB, no access.\n");
		return -1;
	}

	return periphbase & CBAR_MASK;
}

static unsigned long get_gicd_base_address(void)
{
	return get_cbar() + GIC_DIST_OFFSET;
}

static int cpu_is_virt_capable(void)
{
	return read_id_pfr1() & (1 << 12);
}

static unsigned long get_gicc_base_address(void)
{
	unsigned long adr = get_cbar();

	if (cpu_is_virt_capable())
		adr += GIC_CPU_OFFSET_A15;
	else
		adr += GIC_CPU_OFFSET_A9;

	return adr;
}

#define GICD_IGROUPRn             0x0080

int armv7_init_nonsec(void)
{
	void __iomem *gicd = IOMEM(get_gicd_base_address());
	unsigned itlinesnr, i;
	u32 val;

	/*
	 * the SCR register will be set directly in the monitor mode handler,
	 * according to the spec one should not tinker with it in secure state
	 * in SVC mode. Do not try to read it once in non-secure state,
	 * any access to it will trap.
	 */

	/* enable the GIC distributor */
	val = readl(gicd + GICD_CTLR);
	val |= 0x3;
	writel(val, gicd + GICD_CTLR);

	/* TYPER[4:0] contains an encoded number of available interrupts */
	itlinesnr = readl(gicd + GICD_TYPER) & 0x1f;

	/*
	 * Set all bits in the GIC group registers to one to allow access
	 * from non-secure state. The first 32 interrupts are private per
	 * CPU and will be set later when enabling the GIC for each core
	 */
	for (i = 1; i <= itlinesnr; i++)
		writel(0xffffffff, gicd + GICD_IGROUPRn + 4 * i);

	return 0;
}

/*
 * armv7_secure_monitor_install - install secure monitor
 *
 * This function is entered in secure mode. It installs the secure
 * monitor code and enters it using a smc call. This function is executed
 * on every CPU. We leave this function returns in nonsecure mode.
 */
int __armv7_secure_monitor_install(void)
{
	struct arm_smccc_res res;
	void __iomem *gicd = IOMEM(get_gicd_base_address());
	void __iomem *gicc = IOMEM(get_gicc_base_address());
	u32 nsacr;

	writel(0xffffffff, gicd + GICD_IGROUPRn);

	writel(0x3, gicc + GICC_CTLR);
	writel(0xff, gicc + GICC_PMR);

	nsacr = read_nsacr();
	nsacr |= 0x00043fff; /* all copros allowed in non-secure mode */
	write_nsacr(nsacr);

	write_mvbar((unsigned long)secure_monitor_init_vectors);

	isb();

	/* Initialize the secure monitor */
	arm_smccc_smc(0, 0, 0, 0, 0, 0, 0, 0, &res);

	/* We're in nonsecure mode now */

	return 0;
}

static bool armv7_have_security_extensions(void)
{
	return (read_id_pfr1() & 0xf0) != 0;
}

/*
 * armv7_secure_monitor_install - install secure monitor
 *
 * This function is entered in secure mode. It installs the secure
 * monitor code and enters it using a smc call. This function is executed
 * once on the primary CPU only. We leave this function returns in nonsecure
 * mode.
 */
int armv7_secure_monitor_install(void)
{
	int mmuon;
	unsigned long ttb, vbar;

	if (!armv7_have_security_extensions()) {
		pr_err("Security extensions not implemented.\n");
		return -EINVAL;
	}

	mmuon = get_cr() & CR_M;

	vbar = get_vbar();

	asm volatile ("mrc p15, 0, %0, c2, c0, 0" : "=r"(ttb));

	armv7_init_nonsec();
	__armv7_secure_monitor_install();

	asm volatile ("mcr p15, 0, %0, c2, c0, 0" : : "r"(ttb));

	set_vbar(vbar);

	if (mmuon) {
		/*
		 * If the MMU was already turned on in secure mode, enable it in
		 * non-secure mode aswell
		 */
		__mmu_cache_on();
	}

	pr_debug("Initialized secure monitor\n");

	return 0;
}

/*
 * of_secure_monitor_fixup - reserve memory region for secure monitor
 *
 * We currently do not support putting the secure monitor into onchip RAM,
 * hence it runs in SDRAM and we must reserve the memory region so that we
 * won't get overwritten from the Kernel.
 * Beware: despite the name this is not secure in any way. The Kernel obeys
 * the reserve map, but only because it's nice. It could always overwrite the
 * secure monitor and hijack secure mode.
 */
static int of_secure_monitor_fixup(struct device_node *root, void *unused)
{
	unsigned long res_start, res_end;

	res_start = (unsigned long)_stext;
	res_end = (unsigned long)__bss_stop;

	of_add_reserve_entry(res_start, res_end);

	pr_debug("Reserved memory range from 0x%08lx to 0x%08lx\n", res_start, res_end);

	return 0;
}

static enum arm_security_state bootm_secure_state;

static const char * const bootm_secure_state_names[] = {
	[ARM_STATE_SECURE] = "secure",
	[ARM_STATE_NONSECURE] = "nonsecure",
	[ARM_STATE_HYP] = "hyp",
};

enum arm_security_state bootm_arm_security_state(void)
{
	return bootm_secure_state;
}

const char *bootm_arm_security_state_name(enum arm_security_state state)
{
	return bootm_secure_state_names[state];
}

static int sm_init(void)
{
	of_register_fixup(of_secure_monitor_fixup, NULL);

	globalvar_add_simple_enum("bootm.secure_state",
				  (unsigned int *)&bootm_secure_state,
				  bootm_secure_state_names,
				  ARRAY_SIZE(bootm_secure_state_names));

	return 0;
}
device_initcall(sm_init);