Linux preempt-rt

Check our new training course

Real-Time Linux with PREEMPT_RT

Check our new training course
with Creative Commons CC-BY-SA
lecture and lab materials

Bootlin logo

Elixir Cross Referencer

/*
 * Copyright (c) 2017 Synopsys.
 *
 * SPDX-License-Identifier: Apache-2.0
 */

#include <device.h>
#include <init.h>
#include <kernel.h>
#include <soc.h>
#include <arch/arc/v2/aux_regs.h>
#include <arch/arc/v2/mpu/arc_mpu.h>
#include <arch/arc/v2/mpu/arc_core_mpu.h>
#include <logging/sys_log.h>


#define AUX_MPU_RDB_VALID_MASK (0x1)
#define AUX_MPU_EN_ENABLE   (0x40000000)
#define AUX_MPU_EN_DISABLE  (0xBFFFFFFF)

#define AUX_MPU_RDP_REGION_SIZE(bits)  \
			(((bits - 1) & 0x3) | (((bits - 1) & 0x1C) << 7))
#define AUX_MPU_RDP_ATTR_MASK (0xFFF)

#define _ARC_V2_MPU_EN   (0x409)
#define _ARC_V2_MPU_RDB0 (0x422)
#define _ARC_V2_MPU_RDP0 (0x423)

/* For MPU version 2, the minimum protection region size is 2048 bytes */
/* FOr MPU version 3, the minimum protection region size is 32 bytes */
#if CONFIG_ARC_MPU_VER == 2
#define ARC_FEATURE_MPU_ALIGNMENT_BITS 11
#elif CONFIG_ARC_MPU_VER == 3
#define ARC_FEATURE_MPU_ALIGNMENT_BITS 5
#endif

/**
 * @brief Get the number of supported mpu regions
 *
 */
static inline u8_t _get_num_regions(void)
{
	u32_t num = _arc_v2_aux_reg_read(_ARC_V2_MPU_BUILD);

	num = (num & 0xFF00) >> 8;

	return (u8_t)num;
}


/**
 * This internal function is utilized by the MPU driver to parse the intent
 * type (i.e. THREAD_STACK_REGION) and return the correct parameter set.
 */
static inline u32_t _get_region_attr_by_type(u32_t type, u32_t size)
{
	switch (type) {
	case THREAD_STACK_REGION:
		return 0;
	case THREAD_STACK_GUARD_REGION:
	/* no Read, Write and Execute to guard region */
		return  AUX_MPU_RDP_REGION_SIZE(
			ARC_FEATURE_MPU_ALIGNMENT_BITS);
	default:
		/* Size 0 region */
		return 0;
	}
}

static inline void _region_init(u32_t index, u32_t region_addr,
			 u32_t region_attr)
{

	index = 2 * index;

	_arc_v2_aux_reg_write(_ARC_V2_MPU_RDP0 + index, region_attr);
	_arc_v2_aux_reg_write(_ARC_V2_MPU_RDB0 + index, region_addr);
}


/* ARC Core MPU Driver API Implementation for ARC MPU */

/**
 * @brief enable the MPU
 */
void arc_core_mpu_enable(void)
{
	/* Enable MPU */
	_arc_v2_aux_reg_write(_ARC_V2_MPU_EN,
		_arc_v2_aux_reg_read(_ARC_V2_MPU_EN) | AUX_MPU_EN_ENABLE);
}

/**
 * @brief disable the MPU
 */
void arc_core_mpu_disable(void)
{
	/* Disable MPU */
	_arc_v2_aux_reg_write(_ARC_V2_MPU_EN,
		_arc_v2_aux_reg_read(_ARC_V2_MPU_EN) & AUX_MPU_EN_DISABLE);
}


/**
 * @brief configure the base address and size for an MPU region
 *
 * @param   type    MPU region type
 * @param   base    base address in RAM
 * @param   size    size of the region
 */
void arc_core_mpu_configure(u8_t type, u32_t base, u32_t size)
{
	u32_t region_index;
	u32_t region_attr;

	SYS_LOG_DBG("Region info: 0x%x 0x%x", base, size);
	/*
	 * The new MPU regions are allocated per type before
	 * the statically configured regions.
	 *
	 * For ARC MPU v2, MPU regions can be overlapped, smaller
	 * region index has higher priority.
	 */

	region_index = _get_num_regions() - mpu_config.num_regions;

	if (type > region_index) {
		return;
	}

	region_index -= type;
	region_attr = _get_region_attr_by_type(type, size);

	if (region_attr == 0) {
		return;
	}

	base |= AUX_MPU_RDB_VALID_MASK;

	_region_init(region_index, base, region_attr);
}

/**
 * @brief configure the default region
 *
 * @param   region_attr region attribute of default region
 */
void arc_core_mpu_default(u32_t region_attr)
{
	u32_t val =  _arc_v2_aux_reg_read(_ARC_V2_MPU_EN) &
			(~AUX_MPU_RDP_ATTR_MASK);

	region_attr &= AUX_MPU_RDP_ATTR_MASK;

	_arc_v2_aux_reg_write(_ARC_V2_MPU_EN, region_attr | val);
}


/**
 * @brief configure the mpu region
 *
 * @param   index   MPU region index
 * @param   base    base address
 * @param   region_attr region attribute
 */
void arc_core_mpu_region(u32_t index, u32_t base, u32_t region_attr)
{
	if (index >= _get_num_regions()) {
		return;
	}

	base |= AUX_MPU_RDB_VALID_MASK;
	region_attr &= AUX_MPU_RDP_ATTR_MASK;

	_region_init(index, base, region_attr);
}

/* ARC MPU Driver Initial Setup */

/*
 * @brief MPU default configuration
 *
 * This function provides the default configuration mechanism for the Memory
 * Protection Unit (MPU).
 */
static void _arc_mpu_config(void)
{
	u32_t r_index;
	u32_t num_regions;
	u32_t i;

	num_regions = _get_num_regions();

	/* ARC MPU supports up to 16 Regions */
	if (mpu_config.num_regions > num_regions) {
		return;
	}

	/*
	 * the MPU regions are filled in the reverse order.
	 * According to ARCv2 ISA, the mpu region with smaller
	 * index has higher priority. The static background mpu
	 * regions in mpu_config will be in the bottom. Then
	 * the special type regions will be above.
	 *
	 */
	r_index = num_regions - mpu_config.num_regions;
	/* Disable MPU */
	arc_core_mpu_disable();

	/* clear the regions reserved for special type */
	for (i = 0; i < r_index; i++) {
		_region_init(i, 0, 0);
	}

	/* configure the static regions */
	for (r_index = 0; i < num_regions; i++) {
		_region_init(i,
			mpu_config.mpu_regions[r_index].base
			| AUX_MPU_RDB_VALID_MASK,
			mpu_config.mpu_regions[r_index].attr);
		r_index++;
	}

	/* default region: no read, write and execute */
	arc_core_mpu_default(0);

	/* Enable MPU */
	arc_core_mpu_enable();
}

static int arc_mpu_init(struct device *arg)
{
	ARG_UNUSED(arg);

	_arc_mpu_config();

	return 0;
}

SYS_INIT(arc_mpu_init, PRE_KERNEL_1,
	 CONFIG_KERNEL_INIT_PRIORITY_DEFAULT);