blob: 24f3d27362df5e371f83718e9419885cfc9d1d6a [file] [log] [blame]
#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);