| #include <asm/map.h> |
| #include <asm/memory.h> |
| #include <asm/uaccess.h> |
| |
| #include <linux/slab.h> |
| #include <linux/device.h> |
| #include <linux/errno.h> |
| #include <linux/fs.h> |
| #include <linux/file.h> |
| #include <linux/module.h> |
| #include <linux/vmalloc.h> |
| #include <linux/of_address.h> |
| #include <linux/of_reserved_mem.h> |
| #include <linux/memblock.h> |
| #include <linux/kthread.h> |
| #include <linux/delay.h> |
| #include <asm/tlbflush.h> |
| #include <asm/cacheflush.h> |
| #include <linux/kallsyms.h> |
| |
| #define S5P_VA_EPX (VMALLOC_START + 0xF6000000 + 0x03000000) |
| #define EPX_SIZE (1024 * 1024) |
| |
| static struct vm_struct epx_early_vm; |
| static bool epx_loaded = false; |
| |
| typedef unsigned long (*pfn_kallsyms_lookup_name)(const char *name); |
| typedef int (*pfn_binary_entry)(unsigned long address, pfn_kallsyms_lookup_name ksyms); |
| |
| struct page_change_data { |
| pgprot_t set_mask; |
| pgprot_t clear_mask; |
| }; |
| |
| static int __init epx_rmem_remap(void) |
| { |
| unsigned long i; |
| pgprot_t prot = PAGE_KERNEL_EXEC; |
| int page_size, ret; |
| struct page *page; |
| struct page **pages; |
| |
| page_size = epx_early_vm.size / PAGE_SIZE; |
| pages = kzalloc(sizeof(struct page*) * page_size, GFP_KERNEL); |
| page = phys_to_page(epx_early_vm.phys_addr); |
| |
| for (i = 0; i < page_size; i++) |
| pages[i] = page++; |
| |
| ret = map_vm_area(&epx_early_vm, prot, pages); |
| if (ret) { |
| pr_err("EPX: failed to map virtual memory area!\n"); |
| kfree(pages); |
| return -ENOMEM; |
| } |
| kfree(pages); |
| |
| epx_loaded = false; |
| |
| return 0; |
| } |
| postcore_initcall(epx_rmem_remap); |
| |
| static int __init epx_mem_reserved_mem_setup(char *cmd) |
| { |
| epx_early_vm.phys_addr = memblock_alloc(EPX_SIZE, SZ_4K); |
| epx_early_vm.size = EPX_SIZE + SZ_4K; |
| epx_early_vm.addr = (void *)S5P_VA_EPX; |
| |
| vm_area_add_early(&epx_early_vm); |
| |
| return 0; |
| } |
| __setup("epx_activate=", epx_mem_reserved_mem_setup); |
| |
| static int epx_change_page_range(pte_t *ptep, pgtable_t token, unsigned long addr, |
| void *data) |
| { |
| struct page_change_data *cdata = data; |
| pte_t pte = *ptep; |
| |
| pte = clear_pte_bit(pte, cdata->clear_mask); |
| pte = set_pte_bit(pte, cdata->set_mask); |
| |
| set_pte(ptep, pte); |
| return 0; |
| } |
| |
| static int epx_change_memory_common(unsigned long addr, int numpages, |
| pgprot_t set_mask, pgprot_t clear_mask) |
| { |
| unsigned long start = addr; |
| unsigned long size = PAGE_SIZE*numpages; |
| unsigned long end = start + size; |
| int ret; |
| struct page_change_data data; |
| |
| if (!PAGE_ALIGNED(addr)) { |
| start &= PAGE_MASK; |
| end = start + size; |
| WARN_ON_ONCE(1); |
| } |
| |
| if (!numpages) |
| return 0; |
| |
| data.set_mask = set_mask; |
| data.clear_mask = clear_mask; |
| |
| ret = apply_to_page_range(&init_mm, start, size, epx_change_page_range, |
| &data); |
| |
| flush_tlb_kernel_range(start, end); |
| return ret; |
| } |
| |
| static int epx_set_memory_rw(unsigned long addr, int numpages) |
| { |
| return epx_change_memory_common(addr, numpages, |
| __pgprot(PTE_WRITE), |
| __pgprot(PTE_RDONLY)); |
| } |
| |
| static int epx_set_memory_nx(unsigned long addr, int numpages) |
| { |
| return epx_change_memory_common(addr, numpages, |
| __pgprot(PTE_PXN), |
| __pgprot(0)); |
| } |
| |
| static int epx_set_memory_x(unsigned long addr, int numpages) |
| { |
| return epx_change_memory_common(addr, numpages, |
| __pgprot(0), |
| __pgprot(PTE_PXN)); |
| } |
| |
| static ssize_t show_epx_activate(struct class *class, |
| struct class_attribute *attr, char *buf) |
| { |
| int ret = 0; |
| long epx_fd; |
| struct file *fp; |
| mm_segment_t old_fs; |
| loff_t pos = 0; |
| unsigned int epx_signature = 0x585045; |
| unsigned long long epx_offset; |
| char *binary_buffer; |
| void *epx_ptr; |
| pfn_binary_entry binary_entry; |
| |
| if (epx_loaded) |
| return 0; |
| |
| old_fs = get_fs(); |
| set_fs(KERNEL_DS); |
| epx_fd = do_sys_open(AT_FDCWD, "/vendor/firmware/epx.bin", O_RDONLY | O_NOFOLLOW, 0664); |
| if (epx_fd < 0) { |
| pr_err("EPX : error to open file\n"); |
| ret = -EINVAL; |
| goto finalize; |
| } |
| |
| epx_set_memory_nx((unsigned long)epx_early_vm.addr, PFN_UP(EPX_SIZE)); |
| epx_set_memory_rw((unsigned long)epx_early_vm.addr, PFN_UP(EPX_SIZE)); |
| |
| fp = fget(epx_fd); |
| if (!fp) { |
| pr_err("[EPX] : error to load file\n"); |
| ret = -EINVAL; |
| goto finalize; |
| } |
| |
| binary_buffer = kzalloc(EPX_SIZE, GFP_KERNEL); |
| if (binary_buffer != NULL) { |
| vfs_read(fp, binary_buffer, EPX_SIZE, &pos); |
| |
| memcpy((void *)epx_early_vm.addr, binary_buffer, EPX_SIZE); |
| |
| flush_icache_range((unsigned long)(epx_early_vm.addr), (unsigned long)(epx_early_vm.addr + EPX_SIZE)); |
| epx_set_memory_x((unsigned long)epx_early_vm.addr, PFN_UP(EPX_SIZE)); |
| |
| epx_ptr = (void *)epx_early_vm.addr; |
| if (*((unsigned int *)epx_ptr) != epx_signature) { |
| pr_err("[EPX] : signature not matched!\n"); |
| ret = -EINVAL; |
| goto free_buffer; |
| } |
| |
| epx_ptr += 8; |
| epx_offset = *((unsigned long long *)epx_ptr); |
| |
| binary_entry = (pfn_binary_entry)(epx_early_vm.addr + epx_offset); |
| binary_entry((unsigned long)epx_early_vm.addr, kallsyms_lookup_name); |
| |
| epx_loaded = true; |
| free_buffer: |
| kfree(binary_buffer); |
| } |
| fput(fp); |
| |
| finalize: |
| set_fs(old_fs); |
| |
| return 0; |
| } |
| |
| static struct class_attribute class_attr_epx_activate = __ATTR(epx_activate, S_IRUSR, show_epx_activate, NULL); |
| |
| static int __init epx_load(void) |
| { |
| static struct class *epx_class; |
| |
| epx_class = class_create(THIS_MODULE, "epx"); |
| if (IS_ERR(epx_class)) { |
| pr_err("EPX : couldn't create class (%s : %d)\n", __FILE__, __LINE__); |
| return PTR_ERR(epx_class); |
| } |
| |
| if (class_create_file(epx_class, &class_attr_epx_activate)) { |
| pr_err("EPX : couldn't create class (%s : %d)\n", __FILE__, __LINE__); |
| return -EINVAL; |
| } |
| |
| return 0; |
| } |
| late_initcall(epx_load); |