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 Linaro Limited.
 *
 * SPDX-License-Identifier: Apache-2.0
 */

#include <device.h>
#include <init.h>
#include <kernel.h>
#include <soc.h>
#include <arch/arm/cortex_m/cmsis.h>
#include <arch/arm/cortex_m/mpu/arm_mpu.h>
#include <arch/arm/cortex_m/mpu/arm_core_mpu.h>
#include <logging/sys_log.h>

#define ARM_MPU_DEV ((volatile struct arm_mpu *) ARM_MPU_BASE)

/* ARM MPU Enabled state */
static u8_t arm_mpu_enabled;

/**
 * The attributes referenced in this function are described at:
 * https://goo.gl/hMry3r
 * This function is private to the driver.
 */
static inline u32_t _get_region_attr(u32_t xn, u32_t ap, u32_t tex,
				     u32_t c, u32_t b, u32_t s,
				     u32_t srd, u32_t size)
{
	return ((xn << 28) | (ap) | (tex << 19) | (s << 18)
		| (c << 17) | (b << 16) | (srd << 5) | (size));
}

/**
 * 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:
		return _get_region_attr(1, P_RO_U_RO, 0, 1, 0,
					1, 0, REGION_32B);
	default:
		/* Size 0 region */
		return 0;
	}
}

static inline u8_t _get_num_regions(void)
{
	u32_t type = ARM_MPU_DEV->type;

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

	return (u8_t)type;
}

static void _region_init(u32_t index, u32_t region_addr,
			 u32_t region_attr)
{
	/* Select the region you want to access */
	ARM_MPU_DEV->rnr = index;
	/* Configure the region */
	ARM_MPU_DEV->rbar = region_addr | REGION_VALID | index;
	ARM_MPU_DEV->rasr = region_attr | REGION_ENABLE;
}

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

/**
 * @brief enable the MPU
 */
void arm_core_mpu_enable(void)
{
	if (arm_mpu_enabled == 0) {
		/* Enable MPU */
		ARM_MPU_DEV->ctrl = ARM_MPU_ENABLE | ARM_MPU_PRIVDEFENA;

		arm_mpu_enabled = 1;
	}
}

/**
 * @brief disable the MPU
 */
void arm_core_mpu_disable(void)
{
	if (arm_mpu_enabled == 1) {
		/* Disable MPU */
		ARM_MPU_DEV->ctrl = 0;

		arm_mpu_enabled = 0;
	}
}

/**
 * @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 arm_core_mpu_configure(u8_t type, u32_t base, u32_t size)
{
	SYS_LOG_DBG("Region info: 0x%x 0x%x", base, size);
	/*
	 * The new MPU regions are are allocated per type after the statically
	 * configured regions.
	 */
	u32_t region_index = mpu_config.num_regions + type;
	u32_t region_attr = _get_region_attr_by_type(type, size);

	/* ARM MPU supports up to 16 Regions */
	if (region_index > _get_num_regions()) {
		return;
	}

	_region_init(region_index, base, region_attr);
}

/* ARM MPU Driver Initial Setup */

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

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

	/* Disable MPU */
	ARM_MPU_DEV->ctrl = 0;

	/* Configure regions */
	for (r_index = 0; r_index < mpu_config.num_regions; r_index++) {
		_region_init(r_index,
			     mpu_config.mpu_regions[r_index].base,
			     mpu_config.mpu_regions[r_index].attr);
	}

	/* Enable MPU */
	ARM_MPU_DEV->ctrl = ARM_MPU_ENABLE | ARM_MPU_PRIVDEFENA;

	arm_mpu_enabled = 1;

	/* Make sure that all the registers are set before proceeding */
	__DSB();
	__ISB();
}

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

	_arm_mpu_config();

	return 0;
}

SYS_INIT(arm_mpu_init, PRE_KERNEL_1,
	 CONFIG_KERNEL_INIT_PRIORITY_DEFAULT);