text: /*
   * Copyright (c) 2017 Linaro Limited.
   *
   * SPDX-License-Identifier: Apache-2.0
   */
  
  #include <device.h>
  #include <init.h>
  #include <kernel.h>
  #include <soc.h>
  
  #include "arm_core_mpu_dev.h"
  #include <linker/linker-defs.h>
  
  #define LOG_LEVEL CONFIG_MPU_LOG_LEVEL
  #include <logging/log.h>
  LOG_MODULE_REGISTER(mpu);
  
  /*
   * Maximum number of dynamic memory partitions that may be supplied to the MPU
   * driver for programming during run-time. Note that the actual number of the
   * available MPU regions for dynamic programming depends on the number of the
   * static MPU regions currently being programmed, and the total number of HW-
   * available MPU regions. This macro is only used internally in function
   * z_arch_configure_dynamic_mpu_regions(), to reserve sufficient area for the
   * array of dynamic regions passed to the underlying driver.
   */
  #if defined(CONFIG_USERSPACE)
  #define _MAX_DYNAMIC_MPU_REGIONS_NUM \
  	CONFIG_MAX_DOMAIN_PARTITIONS + /* User thread stack */ 1 + \
  	(IS_ENABLED(CONFIG_MPU_STACK_GUARD) ? 1 : 0)
  #else
  #define _MAX_DYNAMIC_MPU_REGIONS_NUM \
  	(IS_ENABLED(CONFIG_MPU_STACK_GUARD) ? 1 : 0)
  #endif /* CONFIG_USERSPACE */
  
  /* Convenience macros to denote the start address and the size of the system
   * memory area, where dynamic memory regions may be programmed at run-time.
   */
  #if defined(CONFIG_USERSPACE)
  #define _MPU_DYNAMIC_REGIONS_AREA_START ((u32_t)&_app_smem_start)
  #else
  #define _MPU_DYNAMIC_REGIONS_AREA_START ((u32_t)&__kernel_ram_start)
  #endif /* CONFIG_USERSPACE */
  #define _MPU_DYNAMIC_REGIONS_AREA_SIZE ((u32_t)&__kernel_ram_end - \
  		_MPU_DYNAMIC_REGIONS_AREA_START)
  
  /**
   * @brief Use the HW-specific MPU driver to program
   *        the static MPU regions.
   *
   * Program the static MPU regions using the HW-specific MPU driver. The
   * function is meant to be invoked only once upon system initialization.
   *
   * If the function attempts to configure a number of regions beyond the
   * MPU HW limitations, the system behavior will be undefined.
   *
   * For some MPU architectures, such as the unmodified ARMv8-M MPU,
   * the function must execute with MPU enabled.
   */
  void z_arch_configure_static_mpu_regions(void)
  {
  #if defined(CONFIG_COVERAGE_GCOV) && defined(CONFIG_USERSPACE)
  		const struct k_mem_partition gcov_region =
  		{
  		.start = (u32_t)&__gcov_bss_start,
  		.size = (u32_t)&__gcov_bss_size,
  		.attr = K_MEM_PARTITION_P_RW_U_RW,
  		};
  #endif /* CONFIG_COVERAGE_GCOV && CONFIG_USERSPACE */
  #if defined(CONFIG_NOCACHE_MEMORY)
  		const struct k_mem_partition nocache_region =
  		{
  		.start = (u32_t)&_nocache_ram_start,
  		.size = (u32_t)&_nocache_ram_size,
  		.attr = K_MEM_PARTITION_P_RW_U_NA_NOCACHE,
  		};
  #endif /* CONFIG_NOCACHE_MEMORY */
  #if defined(CONFIG_ARCH_HAS_RAMFUNC_SUPPORT)
  		const struct k_mem_partition ramfunc_region =
  		{
  		.start = (u32_t)&_ramfunc_ram_start,
  		.size = (u32_t)&_ramfunc_ram_size,
  		.attr = K_MEM_PARTITION_P_RX_U_RX,
  		};
  #endif /* CONFIG_ARCH_HAS_RAMFUNC_SUPPORT */
  
  	/* Define a constant array of k_mem_partition objects
  	 * to hold the configuration of the respective static
  	 * MPU regions.
  	 */
  	const struct k_mem_partition *static_regions[] = {
  #if defined(CONFIG_COVERAGE_GCOV) && defined(CONFIG_USERSPACE)
  		&gcov_region,
  #endif /* CONFIG_COVERAGE_GCOV && CONFIG_USERSPACE */
  #if defined(CONFIG_NOCACHE_MEMORY)
  		&nocache_region,
  #endif /* CONFIG_NOCACHE_MEMORY */
  #if defined(CONFIG_ARCH_HAS_RAMFUNC_SUPPORT)
  		&ramfunc_region
  #endif /* CONFIG_ARCH_HAS_RAMFUNC_SUPPORT */
  	};
  
  	/* Configure the static MPU regions within firmware SRAM boundaries.
  	 * Start address of the image is given by _image_ram_start. The end
  	 * of the firmware SRAM area is marked by __kernel_ram_end, taking
  	 * into account the unused SRAM area, as well.
  	 */
  	arm_core_mpu_configure_static_mpu_regions(static_regions,
  		ARRAY_SIZE(static_regions),
  		(u32_t)&_image_ram_start,
  		(u32_t)&__kernel_ram_end);
  
  #if defined(CONFIG_MPU_REQUIRES_NON_OVERLAPPING_REGIONS)
  	/* Define a constant array of k_mem_partition objects that holds the
  	 * boundaries of the areas, inside which dynamic region programming
  	 * is allowed. The information is passed to the underlying driver at
  	 * initialization.
  	 */
  	const struct k_mem_partition dyn_region_areas[] = {
  		{
  		.start = _MPU_DYNAMIC_REGIONS_AREA_START,
  		.size =  _MPU_DYNAMIC_REGIONS_AREA_SIZE,
  		}
  	};
  
  	arm_core_mpu_mark_areas_for_dynamic_regions(dyn_region_areas,
  		ARRAY_SIZE(dyn_region_areas));
  #endif /* CONFIG_MPU_REQUIRES_NON_OVERLAPPING_REGIONS */
  }
  
  /**
   * @brief Use the HW-specific MPU driver to program
   *        the dynamic MPU regions.
   *
   * Program the dynamic MPU regions using the HW-specific MPU
   * driver. This function is meant to be invoked every time the
   * memory map is to be re-programmed, e.g during thread context
   * switch, entering user mode, reconfiguring memory domain, etc.
   *
   * For some MPU architectures, such as the unmodified ARMv8-M MPU,
   * the function must execute with MPU enabled.
   */
  void z_arch_configure_dynamic_mpu_regions(struct k_thread *thread)
  {
  	/* Define an array of k_mem_partition objects to hold the configuration
  	 * of the respective dynamic MPU regions to be programmed for
  	 * the given thread. The array of partitions (along with its
  	 * actual size) will be supplied to the underlying MPU driver.
  	 */
  	struct k_mem_partition *dynamic_regions[_MAX_DYNAMIC_MPU_REGIONS_NUM];
  
  	u8_t region_num = 0U;
  
  #if defined(CONFIG_USERSPACE)
  	struct k_mem_partition thread_stack;
  
  	/* Memory domain */
  	LOG_DBG("configure thread %p's domain", thread);
  	struct k_mem_domain *mem_domain = thread->mem_domain_info.mem_domain;
  
  	if (mem_domain) {
  		LOG_DBG("configure domain: %p", mem_domain);
  		u32_t num_partitions = mem_domain->num_partitions;
  		struct k_mem_partition partition;
  		int i;
  
  		LOG_DBG("configure domain: %p", mem_domain);
  
  		for (i = 0; i < CONFIG_MAX_DOMAIN_PARTITIONS; i++) {
  			partition = mem_domain->partitions[i];
  			if (partition.size == 0) {
  				/* Zero size indicates a non-existing
  				 * memory partition.
  				 */
  				continue;
  			}
  			LOG_DBG("set region 0x%lx 0x%x",
  				partition.start, partition.size);
  			__ASSERT(region_num < _MAX_DYNAMIC_MPU_REGIONS_NUM,
  				"Out-of-bounds error for dynamic region map.");
  			dynamic_regions[region_num] =
  				&mem_domain->partitions[i];
  
  			region_num++;
  			num_partitions--;
  			if (num_partitions == 0U) {
  				break;
  			}
  		}
  	}
  	/* Thread user stack */
  	LOG_DBG("configure user thread %p's context", thread);
  	if (thread->arch.priv_stack_start) {
  		u32_t base = (u32_t)thread->stack_obj;
  		u32_t size = thread->stack_info.size +
  			(thread->stack_info.start - base);
  
  		__ASSERT(region_num < _MAX_DYNAMIC_MPU_REGIONS_NUM,
  			"Out-of-bounds error for dynamic region map.");
  		thread_stack = (const struct k_mem_partition)
  			{base, size, K_MEM_PARTITION_P_RW_U_RW};
  
  		dynamic_regions[region_num] = &thread_stack;
  
  		region_num++;
  	}
  #endif /* CONFIG_USERSPACE */
  
  #if defined(CONFIG_MPU_STACK_GUARD)
  	struct k_mem_partition guard;
  
  	/* Privileged stack guard */
  	u32_t guard_start;
  	u32_t guard_size = MPU_GUARD_ALIGN_AND_SIZE;
  
  #if defined(CONFIG_FLOAT) && defined(CONFIG_FP_SHARING)
  	if ((thread->base.user_options & K_FP_REGS) != 0) {
  		guard_size = MPU_GUARD_ALIGN_AND_SIZE_FLOAT;
  	}
  #endif
  
  #if defined(CONFIG_USERSPACE)
  	if (thread->arch.priv_stack_start) {
  		guard_start = thread->arch.priv_stack_start -
  			MPU_GUARD_ALIGN_AND_SIZE;
  		__ASSERT((u32_t)&z_priv_stacks_ram_start <= guard_start,
  		"Guard start: (0x%x) below privilege stacks boundary: (0x%x)",
  		guard_start, (u32_t)&z_priv_stacks_ram_start);
  	} else {
  		guard_start = thread->stack_info.start - guard_size;
  
  		__ASSERT((u32_t)thread->stack_obj == guard_start,
  		"Guard start (0x%x) not beginning at stack object (0x%x)\n",
  		guard_start, (u32_t)thread->stack_obj);
  	}
  #else
  	guard_start = thread->stack_info.start - guard_size;
  #endif /* CONFIG_USERSPACE */
  
  	__ASSERT(region_num < _MAX_DYNAMIC_MPU_REGIONS_NUM,
  		"Out-of-bounds error for dynamic region map.");
  	guard = (const struct k_mem_partition)
  	{
  		guard_start,
  		guard_size,
  		K_MEM_PARTITION_P_RO_U_NA
  	};
  	dynamic_regions[region_num] = &guard;
  
  	region_num++;
  #endif /* CONFIG_MPU_STACK_GUARD */
  
  	/* Configure the dynamic MPU regions */
  	arm_core_mpu_configure_dynamic_mpu_regions(
  		(const struct k_mem_partition **)dynamic_regions,
  		region_num);
  }
  
  #if defined(CONFIG_USERSPACE)
  
  /**
   * @brief Get the maximum number of partitions for a memory domain
   *        that is supported by the MPU hardware, and with respect
   *        to the current static memory region configuration.
   */
  int z_arch_mem_domain_max_partitions_get(void)
  {
  	int available_regions = arm_core_mpu_get_max_available_dyn_regions();
  
  	available_regions -=
  		ARM_CORE_MPU_NUM_MPU_REGIONS_FOR_THREAD_STACK;
  
  	if (IS_ENABLED(CONFIG_MPU_STACK_GUARD)) {
  		available_regions -=
  			ARM_CORE_MPU_NUM_MPU_REGIONS_FOR_MPU_STACK_GUARD;
  	}
  
  	return ARM_CORE_MPU_MAX_DOMAIN_PARTITIONS_GET(available_regions);
  }
  
  /**
   * @brief Configure the memory domain of the thread.
   */
  void z_arch_mem_domain_configure(struct k_thread *thread)
  {
  	/* Request to configure memory domain for a thread.
  	 * This triggers re-programming of the entire dynamic
  	 * memory map.
  	 */
  	z_arch_configure_dynamic_mpu_regions(thread);
  }
  
  /*
   * @brief Reset the MPU configuration related to the memory domain
   *        partitions
   *
   * @param domain pointer to the memory domain (must be valid)
   */
  void z_arch_mem_domain_destroy(struct k_mem_domain *domain)
  {
  	/* This function will reset the access permission configuration
  	 * of the active partitions of the memory domain.
  	 */
  	int i;
  	struct k_mem_partition partition;
  	/* Partitions belonging to the memory domain will be reset
  	 * to default (Privileged RW, Unprivileged NA) permissions.
  	 */
  	k_mem_partition_attr_t reset_attr = K_MEM_PARTITION_P_RW_U_NA;
  
  	for (i = 0; i < CONFIG_MAX_DOMAIN_PARTITIONS; i++) {
  		partition = domain->partitions[i];
  		if (partition.size == 0U) {
  			/* Zero size indicates a non-existing
  			 * memory partition.
  			 */
  			continue;
  		}
  		arm_core_mpu_mem_partition_config_update(&partition,
  			&reset_attr);
  	}
  }
  
  /*
   * @brief Remove a partition from the memory domain
   *
   * @param domain pointer to the memory domain (must be valid
   * @param partition_id the ID (sequence) number of the memory domain
   *        partition (must be a valid partition).
   */
  void z_arch_mem_domain_partition_remove(struct k_mem_domain *domain,
  				       u32_t  partition_id)
  {
  	/* Request to remove a partition from a memory domain.
  	 * This resets the access permissions of the partition
  	 * to default (Privileged RW, Unprivileged NA).
  	 */
  	k_mem_partition_attr_t reset_attr = K_MEM_PARTITION_P_RW_U_NA;
  
  	arm_core_mpu_mem_partition_config_update(
  		&domain->partitions[partition_id], &reset_attr);
  }
  
  void z_arch_mem_domain_partition_add(struct k_mem_domain *domain,
  				    u32_t partition_id)
  {
  	/* No-op on this architecture */
  }
  
  /*
   * Validate the given buffer is user accessible or not
   */
  int z_arch_buffer_validate(void *addr, size_t size, int write)
  {
  	return arm_core_mpu_buffer_validate(addr, size, write);
  }
  
  #endif /* CONFIG_USERSPACE */
  