| /* | 
 |  * Copyright (c) 2015 Wind River Systems, Inc. | 
 |  * | 
 |  * Licensed under the Apache License, Version 2.0 (the "License"); | 
 |  * you may not use this file except in compliance with the License. | 
 |  * You may obtain a copy of the License at | 
 |  * | 
 |  *     http://www.apache.org/licenses/LICENSE-2.0 | 
 |  * | 
 |  * Unless required by applicable law or agreed to in writing, software | 
 |  * distributed under the License is distributed on an "AS IS" BASIS, | 
 |  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | 
 |  * See the License for the specific language governing permissions and | 
 |  * limitations under the License. | 
 |  */ | 
 |  | 
 | /** | 
 |  * @file safe memory access routines, software implementation that verifies | 
 |  *       accesses are within memory region boundaries. | 
 |  * | 
 |  * @details See debug/Kconfig and the "Safe memory access" help for details. | 
 |  */ | 
 |  | 
 | #include <nanokernel.h> | 
 | #include <init.h> | 
 | #include <errno.h> | 
 | #include <toolchain.h> | 
 | #include <linker-defs.h> | 
 | #include <misc/util.h> | 
 | #include <misc/debug/mem_safe.h> | 
 | #include <string.h> | 
 |  | 
 | #define NUM_REGIONS (CONFIG_MEM_SAFE_NUM_EXTRA_REGIONS + 2) | 
 |  | 
 | /* | 
 |  * The table of regions has the RO regions at the bottom and the RW regions at | 
 |  * the top, and regions are added by moving the ro_end/rw_end pointers towards | 
 |  * each other. The table is full when the pointers cross, i.e. when ro_end > | 
 |  * rw_end. | 
 |  */ | 
 | struct { | 
 | 	vaddr_t addr; | 
 | 	vaddr_t last_byte; | 
 | } mem_regions[NUM_REGIONS]; | 
 |  | 
 | #define ro_base 0 | 
 | #define rw_base (NUM_REGIONS - 1) | 
 |  | 
 | static int ro_end = ro_base; | 
 | static int rw_end = rw_base; | 
 |  | 
 | #define IMAGE_ROM_START  ((vaddr_t)&_image_rom_start) | 
 | #define IMAGE_ROM_END    ((vaddr_t)&_image_rom_end) | 
 |  | 
 | #define IMAGE_RAM_START  ((vaddr_t)&_image_ram_start) | 
 | #define IMAGE_RAM_END    ((vaddr_t)&_image_ram_end) | 
 |  | 
 | #define IMAGE_TEXT_START ((vaddr_t)&_image_text_start) | 
 | #define IMAGE_TEXT_END   ((vaddr_t)&_image_text_end) | 
 |  | 
 | #define VALID_PERMISSION_MASK  0x00000001   /* permissions use only the lsb */ | 
 |  | 
 | static inline void write_to_mem(void *dest, void *src, int width) | 
 | { | 
 | 	switch (width) { | 
 | 	case 4: | 
 | 		*((vaddr_t *)dest) = *((const vaddr_t *)src); | 
 | 		break; | 
 | 	case 2: | 
 | 		*((uint16_t *)dest) = *((const uint16_t *)src); | 
 | 		break; | 
 | 	case 1: | 
 | 		*((char *)dest) = *((const char *)src); | 
 | 		break; | 
 | 	} | 
 | } | 
 |  | 
 | static inline int is_in_region(int slot, vaddr_t addr, vaddr_t end_addr) | 
 | { | 
 | 	vaddr_t region_start = mem_regions[slot].addr; | 
 | 	vaddr_t region_last_byte = mem_regions[slot].last_byte; | 
 |  | 
 | 	return addr >= region_start && end_addr <= region_last_byte; | 
 | } | 
 |  | 
 | static inline int is_in_a_ro_region(vaddr_t addr, vaddr_t end_addr) | 
 | { | 
 | 	int slot = ro_base; | 
 |  | 
 | 	while (slot < ro_end && slot <= rw_end) { | 
 | 		if (is_in_region(slot, addr, end_addr)) { | 
 | 			return 1; | 
 | 		} | 
 | 		++slot; | 
 | 	} | 
 |  | 
 | 	return 0; | 
 | } | 
 |  | 
 | static inline int is_in_a_rw_region(vaddr_t addr, vaddr_t end_addr) | 
 | { | 
 | 	int slot = rw_base; | 
 |  | 
 | 	while (slot > rw_end && slot >= ro_end) { | 
 | 		if (is_in_region(slot, addr, end_addr)) { | 
 | 			return 1; | 
 | 		} | 
 | 		--slot; | 
 | 	} | 
 |  | 
 | 	return 0; | 
 | } | 
 |  | 
 | static inline int mem_probe_no_check(void *p, int perm, size_t num_bytes, | 
 | 										void *buf) | 
 | { | 
 | 	vaddr_t addr = (vaddr_t)p; | 
 | 	vaddr_t end_addr = addr + num_bytes - 1; | 
 |  | 
 | 	int is_in_rw = is_in_a_rw_region(addr, end_addr); | 
 | 	int is_in_ro = is_in_a_ro_region(addr, end_addr); | 
 |  | 
 | 	int valid_mem; | 
 | 	void *src, *dest; | 
 |  | 
 | 	if (perm == SYS_MEM_SAFE_READ) { | 
 | 		dest = buf; src = p; | 
 | 		valid_mem = is_in_rw || is_in_ro; | 
 | 	} else { | 
 | 		dest = p; src = buf; | 
 | 		valid_mem = is_in_rw; | 
 | 	} | 
 |  | 
 | 	if (likely(valid_mem)) { | 
 | 		write_to_mem(dest, src, num_bytes); | 
 | 		return 0; | 
 | 	} | 
 | 	return -EFAULT; | 
 | } | 
 |  | 
 | static inline int is_perm_valid(int perm) | 
 | { | 
 | 	return !(perm & ~VALID_PERMISSION_MASK); | 
 | } | 
 |  | 
 | static inline int is_num_bytes_valid(size_t num_bytes) | 
 | { | 
 | 	return is_power_of_two(num_bytes) && num_bytes <= sizeof(vaddr_t); | 
 | } | 
 |  | 
 | int _mem_probe(void *p, int perm, size_t num_bytes, void *buf) | 
 | { | 
 | 	if (unlikely(!is_perm_valid(perm))) { | 
 | 		return -EINVAL; | 
 | 	} | 
 |  | 
 | 	if (unlikely(!is_num_bytes_valid(num_bytes))) { | 
 | 		return -EINVAL; | 
 | 	} | 
 |  | 
 | 	return mem_probe_no_check(p, perm, num_bytes, buf); | 
 | } | 
 |  | 
 | static inline int mem_access(void *p, void *buf, size_t num_bytes, | 
 | 								int len, int perm) | 
 | { | 
 | 	char *p_char = p, *buf_char = buf, *p_end = ((char *)p + len); | 
 |  | 
 | 	while (p_char < p_end) { | 
 | 		int error = mem_probe_no_check(p_char, perm, num_bytes, buf_char); | 
 |  | 
 | 		if (unlikely(error < 0)) { | 
 | 			return error; | 
 | 		} | 
 | 		p_char += num_bytes; | 
 | 		buf_char += num_bytes; | 
 | 	} | 
 |  | 
 | 	return 0; | 
 | } | 
 |  | 
 | static inline int get_align(const uint32_t value) | 
 | { | 
 | 	return (value & 1) ? 1 : (value & 2) ? 2 : 4; | 
 | } | 
 |  | 
 | static inline int get_width(const void *p1, const void *p2, | 
 | 							size_t num_bytes, int width) | 
 | { | 
 | 	vaddr_t p1_addr = (vaddr_t)p1, p2_addr = (vaddr_t)p2; | 
 |  | 
 | 	if (width == 0) { | 
 | 		uint32_t align_check = num_bytes | p1_addr | p2_addr; | 
 |  | 
 | 		return get_align(align_check); | 
 | 	} | 
 |  | 
 | 	if (unlikely(p1_addr & (width - 1) || num_bytes & (width - 1))) { | 
 | 		return -EINVAL; | 
 | 	} | 
 |  | 
 | 	return width; | 
 | } | 
 |  | 
 | int _mem_safe_read(void *src, char *buf, size_t num_bytes, int width) | 
 | { | 
 | 	width = get_width(src, buf, num_bytes, width); | 
 | 	return  unlikely(width < 0) ? -EINVAL : | 
 | 			mem_access(src, buf, width, num_bytes, SYS_MEM_SAFE_READ); | 
 | } | 
 |  | 
 | int _mem_safe_write(void *dest, char *buf, size_t num_bytes, int width) | 
 | { | 
 | 	width = get_width(dest, buf, num_bytes, width); | 
 | 	return  unlikely(width < 0) ? -EINVAL : | 
 | 			mem_access(dest, buf, width, num_bytes, SYS_MEM_SAFE_WRITE); | 
 | } | 
 |  | 
 | #if defined(CONFIG_XIP) | 
 | int _mem_safe_write_to_text_section(void *dest, char *buf, size_t num_bytes) | 
 | { | 
 | 	ARG_UNUSED(dest); | 
 | 	ARG_UNUSED(buf); | 
 | 	ARG_UNUSED(num_bytes); | 
 |  | 
 | 	/* cannot write to text section when it's in ROM */ | 
 | 	return -EFAULT; | 
 | } | 
 | #else | 
 | int _mem_safe_write_to_text_section(void *dest, char *buf, size_t num_bytes) | 
 | { | 
 | 	vaddr_t v = (vaddr_t)dest; | 
 | 	int is_in_text = ((v >= IMAGE_TEXT_START) && | 
 | 						((v + num_bytes) <= IMAGE_TEXT_END)); | 
 |  | 
 | 	if (unlikely(!is_in_text)) { | 
 | 		return -EFAULT; | 
 | 	} | 
 |  | 
 | 	memcpy(dest, buf, num_bytes); | 
 | 	return 0; | 
 | } | 
 | #endif /* CONFIG_XIP */ | 
 |  | 
 | int _mem_safe_region_add(void *addr, size_t num_bytes, int perm) | 
 | { | 
 | 	if (unlikely(!is_perm_valid(perm))) { | 
 | 		return -EINVAL; | 
 | 	} | 
 |  | 
 | 	int slot; | 
 | 	int key = irq_lock(); | 
 |  | 
 | 	if (unlikely(ro_end > rw_end)) { | 
 | 		irq_unlock(key); | 
 | 		return -ENOMEM; | 
 | 	} | 
 |  | 
 | 	if (perm == SYS_MEM_SAFE_WRITE) { | 
 | 		slot = rw_end; | 
 | 		--rw_end; | 
 | 	} else { | 
 | 		slot = ro_end; | 
 | 		++ro_end; | 
 | 	} | 
 |  | 
 | 	mem_regions[slot].addr = (vaddr_t)addr; | 
 | 	mem_regions[slot].last_byte = mem_regions[slot].addr + num_bytes - 1; | 
 |  | 
 | 	irq_unlock(key); | 
 |  | 
 | 	return 0; | 
 | } | 
 |  | 
 | static int init(struct device *unused) | 
 | { | 
 | 	void *addr; | 
 | 	size_t num_bytes; | 
 |  | 
 | 	ARG_UNUSED(unused); | 
 |  | 
 | 	addr = (void *)IMAGE_ROM_START; | 
 | 	num_bytes = (int)(IMAGE_ROM_END - IMAGE_ROM_START); | 
 | 	(void)_mem_safe_region_add(addr, num_bytes, SYS_MEM_SAFE_READ); | 
 |  | 
 | 	addr = (void *)IMAGE_RAM_START; | 
 | 	num_bytes = (int)(IMAGE_RAM_END - IMAGE_RAM_START); | 
 | 	(void)_mem_safe_region_add(addr, num_bytes, SYS_MEM_SAFE_WRITE); | 
 |  | 
 | 	return 0; | 
 | } | 
 |  | 
 | SYS_INIT(init, PRE_KERNEL_1, CONFIG_KERNEL_INIT_PRIORITY_DEFAULT); |