blob: c7d5f2e8b63d062799d8619873a391c936938d27 [file] [log] [blame]
// SPDX-License-Identifier: BSD-2-Clause
/*
* Copyright 2017 NXP
* All rights reserved.
*
* Peng Fan <peng.fan@nxp.com>
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* 2. 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.
*
* 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 <assert.h>
#include <drivers/tzc380.h>
#include <io.h>
#include <kernel/panic.h>
#include <mm/core_memprot.h>
#include <mm/core_mmu.h>
#include <stddef.h>
#include <trace.h>
#include <util.h>
/*
* 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
*/
struct tzc_instance {
vaddr_t base;
uint8_t addr_width;
uint8_t num_regions;
};
static struct tzc_instance tzc;
static uint32_t tzc_read_build_config(vaddr_t base)
{
return io_read32(base + BUILD_CONFIG_OFF);
}
static void tzc_write_action(vaddr_t base, enum tzc_action action)
{
io_write32(base + ACTION_OFF, action);
}
static uint32_t tzc_read_action(vaddr_t base)
{
return io_read32(base + ACTION_OFF);
}
static void tzc_write_region_base_low(vaddr_t base, uint32_t region,
uint32_t val)
{
io_write32(base + REGION_SETUP_LOW_OFF(region), val);
}
static void tzc_write_region_base_high(vaddr_t base, uint32_t region,
uint32_t val)
{
io_write32(base + REGION_SETUP_HIGH_OFF(region), val);
}
static uint32_t tzc_read_region_attributes(vaddr_t base, uint32_t region)
{
return io_read32(base + REGION_ATTRIBUTES_OFF(region));
}
static void tzc_write_region_attributes(vaddr_t base, uint32_t region,
uint32_t val)
{
io_write32(base + REGION_ATTRIBUTES_OFF(region), val);
}
void tzc_init(vaddr_t base)
{
uint32_t tzc_build;
assert(base);
tzc.base = base;
/* Save values we will use later. */
tzc_build = tzc_read_build_config(tzc.base);
tzc.addr_width = ((tzc_build >> BUILD_CONFIG_AW_SHIFT) &
BUILD_CONFIG_AW_MASK) + 1;
tzc.num_regions = ((tzc_build >> BUILD_CONFIG_NR_SHIFT) &
BUILD_CONFIG_NR_MASK) + 1;
}
/*
* There are two modes of operation for the region security
* permissions, with or without security inversion.
* Check TZC380 "2.2.5 Region security permissions" for
* more details.
*/
void tzc_security_inversion_en(vaddr_t base)
{
io_write32(base + SECURITY_INV_EN_OFF, 1);
}
/*
* Enable a single region. Sometimes we could not use tzc_configure_region
* to enable the region, when security inversion is on.
* When need security inversion, we need to first configure
* region address and attribute, then configure security inversion,
* then enable the regions.
*/
void tzc_region_enable(uint8_t region)
{
uint32_t val;
val = tzc_read_region_attributes(tzc.base, region);
val |= TZC_ATTR_REGION_EN_MASK;
tzc_write_region_attributes(tzc.base, region, val);
}
/*
* Dump info when TZC380 catchs an unallowed access with TZC
* interrupt enabled.
*/
void tzc_fail_dump(void)
{
vaddr_t base __maybe_unused = core_mmu_get_va(tzc.base,
MEM_AREA_IO_SEC);
EMSG("Fail address Low 0x%" PRIx32,
io_read32(base + FAIL_ADDRESS_LOW_OFF));
EMSG("Fail address High 0x%" PRIx32,
io_read32(base + FAIL_ADDRESS_HIGH_OFF));
EMSG("Fail Control 0x%" PRIx32, io_read32(base + FAIL_CONTROL_OFF));
EMSG("Fail Id 0x%" PRIx32, io_read32(base + FAIL_ID));
}
void tzc_int_clear(void)
{
vaddr_t base = core_mmu_get_va(tzc.base, MEM_AREA_IO_SEC);
io_write32(base + INT_CLEAR, 0);
}
static uint32_t addr_low(vaddr_t addr)
{
return (uint32_t)addr;
}
static uint32_t addr_high(vaddr_t addr __maybe_unused)
{
#if (UINTPTR_MAX == UINT64_MAX)
return addr >> 32;
#else
return 0;
#endif
}
/*
* `tzc_configure_region` is used to program regions into the TrustZone
* controller.
*/
void tzc_configure_region(uint8_t region, vaddr_t region_base, uint32_t attr)
{
assert(tzc.base);
assert(region < tzc.num_regions);
/*
* For region 0, this high/low/size/en field is Read Only (RO).
* So should not configure those field for region 0.
*/
if (region) {
tzc_write_region_base_low(tzc.base, region,
addr_low(region_base));
tzc_write_region_base_high(tzc.base, region,
addr_high(region_base));
tzc_write_region_attributes(tzc.base, region, attr);
} else {
tzc_write_region_attributes(tzc.base, region,
attr & TZC_ATTR_SP_MASK);
}
}
void tzc_set_action(enum tzc_action action)
{
assert(tzc.base);
/*
* - Currently no handler is provided to trap an error via interrupt
* or exception.
* - The interrupt action has not been tested.
*/
tzc_write_action(tzc.base, action);
}
uint32_t tzc_get_action(void)
{
assert(tzc.base);
return tzc_read_action(tzc.base);
}
int tzc_auto_configure(vaddr_t addr, vaddr_t size, uint32_t attr,
uint8_t region)
{
uint64_t sub_region_size = 0;
uint64_t area = 0;
uint8_t lregion = region;
uint64_t region_size = 0;
vaddr_t sub_address = 0;
vaddr_t address = addr;
uint64_t lsize = size;
uint32_t mask = 0;
int i = 0;
uint8_t pow = TZC380_POW;
while (lsize != 0 && pow > 15) {
region_size = 1ULL << pow;
/* Case region fits alignment and covers requested area */
if ((address % region_size == 0) &&
((address + lsize) % region_size == 0)) {
tzc_configure_region(lregion, address,
TZC_ATTR_REGION_SIZE(pow - 1) |
TZC_ATTR_REGION_EN_MASK |
attr);
lregion++;
address += region_size;
lsize -= region_size;
pow--;
continue;
}
/* Cover area using several subregions */
sub_region_size = region_size / 8;
if (address % sub_region_size == 0 &&
lsize > 2 * sub_region_size) {
sub_address = (address / region_size) * region_size;
mask = 0;
for (i = 0; i < 8; i++) {
area = (i + 1) * sub_region_size;
if (sub_address + area <= address ||
sub_address + area > address + lsize) {
mask |= TZC_ATTR_SUBREGION_DIS(i);
} else {
address += sub_region_size;
lsize -= sub_region_size;
}
}
tzc_configure_region(lregion, sub_address,
TZC_ATTR_REGION_SIZE(pow - 1) |
TZC_ATTR_REGION_EN_MASK |
mask | attr);
lregion++;
pow--;
continue;
}
pow--;
}
assert(lsize == 0);
assert(address == addr + size);
return lregion;
}
#if TRACE_LEVEL >= TRACE_DEBUG
static uint32_t tzc_read_region_base_low(vaddr_t base, uint32_t region)
{
return io_read32(base + REGION_SETUP_LOW_OFF(region));
}
static uint32_t tzc_read_region_base_high(vaddr_t base, uint32_t region)
{
return io_read32(base + REGION_SETUP_HIGH_OFF(region));
}
#define REGION_MAX 16
void tzc_dump_state(void)
{
uint32_t n;
uint32_t temp_32reg, temp_32reg_h;
DMSG("enter");
DMSG("security_inversion_en %x",
io_read32(tzc.base + SECURITY_INV_EN_OFF));
for (n = 0; n <= REGION_MAX; n++) {
temp_32reg = tzc_read_region_attributes(tzc.base, n);
if (!(temp_32reg & TZC_ATTR_REGION_EN_MASK))
continue;
DMSG("");
DMSG("region %d", n);
temp_32reg = tzc_read_region_base_low(tzc.base, n);
temp_32reg_h = tzc_read_region_base_high(tzc.base, n);
DMSG("region_base: 0x%08x%08x", temp_32reg_h, temp_32reg);
temp_32reg = tzc_read_region_attributes(tzc.base, n);
DMSG("region sp: %x", temp_32reg >> TZC_ATTR_SP_SHIFT);
DMSG("region size: %x", (temp_32reg & TZC_REGION_SIZE_MASK) >>
TZC_REGION_SIZE_SHIFT);
}
DMSG("exit");
}
#endif /* CFG_TRACE_LEVEL >= TRACE_DEBUG */