| /* |
| * Copyright (C) 2014 Samsung Electronics Co.Ltd |
| * http://www.samsung.com |
| * |
| * Shared memory driver |
| * |
| * This program is free software; you can redistribute it and/or modify it |
| * under the terms of the GNU General Public License as published by the |
| * Free Software Foundation; either version 2 of the License, or (at your |
| * option) any later version. |
| */ |
| |
| #include <linux/io.h> |
| #include <linux/platform_device.h> |
| #include <linux/module.h> |
| #include <linux/of.h> |
| #include <linux/of_platform.h> |
| #include <linux/dma-mapping.h> |
| #include <linux/slab.h> |
| #include <linux/shm_ipc.h> |
| #include <linux/of_reserved_mem.h> |
| |
| #ifdef CONFIG_CP_RAM_LOGGING |
| #include <linux/uaccess.h> |
| #include <linux/miscdevice.h> |
| #include <linux/fs.h> |
| #include <linux/memblock.h> |
| |
| #define MEMSHARE_DEV_NAME "memshare" |
| #endif |
| |
| struct shm_plat_data { |
| unsigned long p_addr; |
| unsigned p_acpm_addr; |
| void __iomem *v_boot; |
| void __iomem *v_ipc; |
| void __iomem *v_zmb; |
| #ifdef CONFIG_CP_PKTPROC |
| void __iomem *v_pktproc_desc; |
| void __iomem *v_pktproc_data; |
| #endif |
| void __iomem *v_vss; |
| void __iomem *v_vparam; |
| void __iomem *v_acpm; |
| unsigned t_size; |
| unsigned cp_size; |
| unsigned vss_off; |
| unsigned vss_size; |
| unsigned l2b_off; |
| unsigned l2b_size; |
| unsigned ipc_off; |
| unsigned ipc_size; |
| unsigned zmb_off; |
| unsigned zmb_size; |
| unsigned vparam_off; |
| unsigned vparam_size; |
| unsigned acpm_size; |
| |
| unsigned long p_sysram_addr; |
| unsigned t_sysram_size; |
| int use_cp_memory_map; |
| |
| #ifdef CONFIG_CP_RAM_LOGGING |
| int cplog_on; |
| unsigned long p_cplog_addr; |
| unsigned cplog_size; |
| void __iomem *v_cplog; |
| |
| char name[256]; |
| struct miscdevice mdev; |
| int data_ready; |
| #endif |
| |
| #ifdef CONFIG_LINK_DEVICE_PCIE |
| unsigned long p_msi_addr; |
| unsigned t_msi_size; |
| #endif |
| |
| #ifdef CONFIG_CP_LINEAR_WA |
| unsigned long p_spare_addr; |
| unsigned int spare_size; |
| #endif |
| |
| #ifdef CONFIG_SEC_SIPC_DUAL_MODEM_IF |
| unsigned long p_s5100_ipc_addr; |
| unsigned int t_s5100_ipc_size; |
| unsigned int t_s5100_modem_if2_size; |
| void __iomem *v_s5100_ipc; |
| |
| unsigned long p_s5100_cp2cp_addr; |
| unsigned int t_s5100_cp2cp_size; |
| unsigned int s5100_cp2cp_off; |
| #endif |
| } pdata; |
| |
| #ifdef CONFIG_CP_RAM_LOGGING |
| static int memshare_open(struct inode *inode, struct file *filep) |
| { |
| shm_get_cplog_region(); |
| return 0; |
| } |
| |
| static int memshare_release(struct inode *inode, struct file *filep) |
| { |
| if (pdata.v_cplog) { |
| vunmap(pdata.v_cplog); |
| pdata.v_cplog = NULL; |
| } |
| return 0; |
| } |
| |
| static ssize_t memshare_read(struct file *filep, char __user *buf, |
| size_t count, loff_t *pos) |
| { |
| struct shm_plat_data *rd_dev = &pdata; |
| void *device_mem = NULL; |
| unsigned long data_left = 0; |
| unsigned long addr = 0; |
| int copy_size = 0; |
| int ret = 0; |
| |
| if ((filep->f_flags & O_NONBLOCK) && !rd_dev->data_ready) |
| return -EAGAIN; |
| |
| if (*pos < 0 || *pos >= rd_dev->cplog_size) { |
| pr_err("%s: tried to read over limit (%lld)\n", __func__, *pos); |
| return 0; |
| } |
| |
| data_left = rd_dev->cplog_size - *pos; |
| addr = rd_dev->p_cplog_addr + *pos; |
| |
| /* EOF check */ |
| if (data_left == 0) { |
| pr_info("%s(%s): Ramdump complete. %lld bytes read.", __func__, |
| rd_dev->name, *pos); |
| ret = 0; |
| goto ramdump_done; |
| } |
| |
| copy_size = min(count, (size_t)SZ_1M); |
| copy_size = min((unsigned long)copy_size, data_left); |
| device_mem = shm_get_cplog_region() + *pos; |
| |
| if (device_mem == NULL) { |
| pr_err("%s(%s): Unable to ioremap: addr %lx, size %d\n", __func__, |
| pdata.name, addr, copy_size); |
| ret = -ENOMEM; |
| goto ramdump_done; |
| } |
| |
| if (copy_to_user(buf, device_mem, copy_size)) { |
| pr_err("%s(%s): Couldn't copy all data to user.", __func__, |
| rd_dev->name); |
| ret = -EFAULT; |
| goto ramdump_done; |
| } |
| |
| *pos += copy_size; |
| |
| pr_debug("%s(%s): Read %d bytes from address %lx.", __func__, |
| pdata.name, copy_size, addr); |
| |
| return copy_size; |
| |
| ramdump_done: |
| *pos = 0; |
| return ret; |
| } |
| |
| static const struct file_operations memshare_file_ops = { |
| .open = memshare_open, |
| .release = memshare_release, |
| .read = memshare_read |
| }; |
| |
| static int create_memshare_device(struct shm_plat_data *pdata, |
| const char *dev_name, struct device *parent) |
| { |
| int ret = -1; |
| |
| if (!dev_name) { |
| pr_err("%s: Invalid device name\n", __func__); |
| goto create_memshare_device_exit; |
| } |
| |
| if (!pdata) { |
| pr_err("%s: Invalid pdata", __func__); |
| goto create_memshare_device_exit; |
| } |
| |
| pdata->data_ready = 0; |
| |
| snprintf(pdata->name, ARRAY_SIZE(pdata->name), "ramdump_%s", |
| dev_name); |
| |
| pdata->mdev.minor = MISC_DYNAMIC_MINOR; |
| pdata->mdev.name = pdata->name; |
| pdata->mdev.fops = &memshare_file_ops; |
| pdata->mdev.parent = parent; |
| |
| ret = misc_register(&pdata->mdev); |
| |
| if (ret) { |
| pr_err("%s: misc_register failed for %s (%d)", __func__, |
| pdata->name, ret); |
| } |
| else |
| pdata->data_ready = 1; |
| |
| create_memshare_device_exit: |
| return ret; |
| } |
| |
| static void destroy_memshare_device(void) |
| { |
| misc_deregister(&pdata.mdev); |
| } |
| #endif |
| |
| unsigned long shm_get_phys_base(void) |
| { |
| return pdata.p_addr; |
| } |
| |
| unsigned shm_get_phys_size(void) |
| { |
| return pdata.t_size; |
| } |
| |
| unsigned long shm_get_sysram_base(void) |
| { |
| return pdata.p_sysram_addr; |
| } |
| |
| unsigned shm_get_sysram_size(void) |
| { |
| return pdata.t_sysram_size; |
| } |
| |
| unsigned shm_get_boot_size(void) |
| { |
| return pdata.cp_size + pdata.vss_size; |
| } |
| |
| unsigned shm_get_ipc_rgn_offset(void) |
| { |
| return pdata.ipc_off; |
| } |
| |
| unsigned shm_get_ipc_rgn_size(void) |
| { |
| return pdata.ipc_size; |
| } |
| |
| #ifdef CONFIG_CP_PKTPROC_V2 |
| unsigned int shm_get_pktproc_v2_base(void) { return pdata.p_s5100_ipc_addr + pdata.t_s5100_ipc_size; } |
| unsigned int shm_get_pktproc_v2_size(void) { return pdata.t_s5100_modem_if2_size - pdata.t_s5100_ipc_size; } |
| #endif |
| |
| #if defined(CONFIG_CP_PKTPROC) |
| /* |
| * Spare size : 0x0160_0000 |
| * |
| * PktProc desc : 0x5000_0000~0x50FF_FFFF |
| * PktProc data : 0x5010_0000~0x510F_FFFF |
| * ZMB data : 0x5110_0000~0x515F_FFFF |
| */ |
| #define PKT_PROC_LEGACY_SIZE 0x00500000 |
| #define PKT_PROC_DESC_SIZE 0x00100000 |
| |
| unsigned int shm_get_zmb_size(void) |
| { |
| return PKT_PROC_LEGACY_SIZE; |
| } |
| |
| unsigned int shm_get_pktproc_desc_size(void) |
| { |
| return PKT_PROC_DESC_SIZE; |
| } |
| |
| unsigned int shm_get_pktproc_data_size(void) |
| { |
| #ifdef CONFIG_CP_LINEAR_WA |
| return (pdata.spare_size - shm_get_zmb_size() - shm_get_pktproc_desc_size()); |
| #else |
| return (pdata.zmb_size - shm_get_zmb_size() - shm_get_pktproc_desc_size()); |
| #endif |
| } |
| |
| u8 *shm_get_pktproc_data_base_addr(void) |
| { |
| return phys_to_virt(pdata.p_spare_addr + shm_get_pktproc_desc_size()); |
| } |
| |
| unsigned int shm_get_databuf_size(void) |
| { |
| return shm_get_pktproc_desc_size() + shm_get_pktproc_data_size() + PKT_PROC_LEGACY_SIZE; |
| } |
| |
| unsigned int shm_get_pktproc_base(void) { return pdata.p_spare_addr; } |
| unsigned int shm_get_pktproc_size(void) { return shm_get_pktproc_desc_size() + shm_get_pktproc_data_size(); } |
| #else /* CONFIG_CP_PKTPROC */ |
| unsigned shm_get_zmb_size(void) |
| { |
| #ifdef CONFIG_CP_LINEAR_WA |
| return pdata.spare_size; |
| #else |
| return pdata.zmb_size; |
| #endif |
| } |
| |
| unsigned int shm_get_databuf_size(void) |
| { |
| return shm_get_zmb_size(); |
| } |
| #endif /* CONFIG_CP_PKTPROC */ |
| |
| unsigned shm_get_vss_base(void) |
| { |
| return shm_get_phys_base() + pdata.vss_off; |
| } |
| |
| unsigned shm_get_vss_size(void) |
| { |
| return pdata.vss_size; |
| } |
| |
| unsigned shm_get_vparam_base(void) |
| { |
| return shm_get_phys_base() + pdata.vparam_off; |
| } |
| |
| unsigned shm_get_vparam_size(void) |
| { |
| return pdata.vparam_size; |
| } |
| |
| unsigned shm_get_acpm_size(void) |
| { |
| return pdata.acpm_size; |
| } |
| |
| unsigned shm_get_cp_size(void) |
| { |
| return pdata.cp_size; |
| } |
| |
| #ifdef CONFIG_CP_RAM_LOGGING |
| unsigned long shm_get_cplog_base(void) |
| { |
| return pdata.p_cplog_addr; |
| } |
| |
| unsigned shm_get_cplog_size(void) |
| { |
| return pdata.cplog_size; |
| } |
| |
| int shm_get_cplog_flag(void) |
| { |
| return pdata.cplog_on; |
| } |
| #endif |
| |
| #ifdef CONFIG_LINK_DEVICE_PCIE |
| unsigned long shm_get_msi_base(void) |
| { |
| return pdata.p_msi_addr; |
| } |
| unsigned shm_get_msi_size(void) |
| { |
| return pdata.t_msi_size; |
| } |
| #endif |
| |
| #ifdef CONFIG_SEC_SIPC_DUAL_MODEM_IF |
| unsigned long shm_get_s5100_ipc_base(void) |
| { |
| return pdata.p_s5100_ipc_addr; |
| } |
| |
| unsigned int shm_get_s5100_ipc_size(void) |
| { |
| return pdata.t_s5100_ipc_size; |
| } |
| |
| unsigned long shm_get_s5100_cp2cp_base(void) |
| { |
| return pdata.p_s5100_cp2cp_addr; |
| } |
| |
| unsigned int shm_get_s5100_cp2cp_size(void) |
| { |
| return pdata.t_s5100_cp2cp_size; |
| } |
| |
| unsigned int shm_get_s5100_cp2cp_offset(void) |
| { |
| return pdata.s5100_cp2cp_off; |
| } |
| #endif |
| |
| int shm_get_use_cp_memory_map_flag(void) |
| { |
| return pdata.use_cp_memory_map; |
| } |
| |
| unsigned long shm_get_security_param3(unsigned long mode, u32 main_size) |
| { |
| unsigned long ret; |
| |
| switch (mode) { |
| case 0: /* CP_BOOT_MODE_NORMAL */ |
| ret = main_size; |
| break; |
| case 1: /* CP_BOOT_MODE_DUMP */ |
| #ifdef CP_NONSECURE_BOOT |
| ret = pdata.p_addr; |
| #else |
| ret = pdata.p_addr + pdata.ipc_off; |
| #endif |
| break; |
| case 2: /* CP_BOOT_RE_INIT */ |
| ret = 0; |
| break; |
| case 7: /* CP_BOOT_MODE_MANUAL */ |
| ret = main_size; |
| break; |
| default: |
| pr_info("%s: Invalid sec_mode(%lu)\n", __func__, mode); |
| ret = 0; |
| break; |
| } |
| return ret; |
| } |
| |
| unsigned long shm_get_security_param2(unsigned long mode, u32 bl_size) |
| { |
| unsigned long ret; |
| |
| switch (mode) { |
| case 0: /* CP_BOOT_MODE_NORMAL */ |
| case 1: /* CP_BOOT_MODE_DUMP */ |
| ret = bl_size; |
| break; |
| case 2: /* CP_BOOT_RE_INIT */ |
| ret = 0; |
| break; |
| case 7: /* CP_BOOT_MODE_MANUAL */ |
| ret = pdata.p_addr + bl_size; |
| break; |
| default: |
| pr_info("%s: Invalid sec_mode(%lu)\n", __func__, mode); |
| ret = 0; |
| break; |
| } |
| return ret; |
| } |
| |
| void __iomem *shm_request_region(unsigned long sh_addr, unsigned size) |
| { |
| int i; |
| unsigned int num_pages = (size >> PAGE_SHIFT); |
| pgprot_t prot = pgprot_writecombine(PAGE_KERNEL); |
| struct page **pages; |
| void *v_addr; |
| |
| if (!sh_addr) |
| return NULL; |
| |
| if (size > (num_pages << PAGE_SHIFT)) |
| num_pages++; |
| |
| pages = kmalloc(sizeof(struct page *) * num_pages, GFP_ATOMIC); |
| if (!pages) { |
| pr_err("%s: pages allocation fail!\n", __func__); |
| return NULL; |
| } |
| |
| for (i = 0; i < (num_pages); i++) { |
| pages[i] = phys_to_page(sh_addr); |
| sh_addr += PAGE_SIZE; |
| } |
| |
| v_addr = vmap(pages, num_pages, VM_MAP, prot); |
| if (v_addr == NULL) |
| pr_err("%s: Failed to vmap pages\n", __func__); |
| |
| kfree(pages); |
| |
| return (void __iomem *)v_addr; |
| } |
| |
| void __iomem *shm_get_boot_region(void) |
| { |
| if (!pdata.v_boot) |
| pdata.v_boot = shm_request_region(pdata.p_addr, |
| pdata.cp_size + pdata.vss_size); |
| |
| return pdata.v_boot; |
| } |
| |
| void __iomem *shm_get_ipc_region(void) |
| { |
| if (!pdata.v_ipc) |
| pdata.v_ipc = shm_request_region(pdata.p_addr + pdata.ipc_off, |
| pdata.ipc_size); |
| |
| return pdata.v_ipc; |
| } |
| |
| void __iomem *shm_get_databuf_region(void) |
| { |
| #ifdef CONFIG_CP_LINEAR_WA |
| return (void __iomem *)phys_to_virt(pdata.p_spare_addr); |
| #else |
| return (void __iomem *)phys_to_virt(pdata.p_addr + pdata.zmb_off); |
| #endif |
| } |
| |
| #if defined(CONFIG_CP_PKTPROC) |
| void __iomem *shm_get_zmb_region(void) |
| { |
| if (!pdata.v_zmb) |
| pdata.v_zmb = (void __iomem *)((unsigned long)shm_get_databuf_region() |
| + shm_get_pktproc_desc_size() |
| + shm_get_pktproc_data_size()); |
| |
| return pdata.v_zmb; |
| } |
| |
| void __iomem *shm_get_pktproc_desc_region(void) |
| { |
| #ifdef CONFIG_CP_LINEAR_WA |
| if (!pdata.v_pktproc_desc) |
| pdata.v_pktproc_desc = shm_request_region(pdata.p_spare_addr, shm_get_pktproc_desc_size()); |
| #else |
| if (!pdata.v_pktproc_desc) |
| pdata.v_pktproc_desc = shm_request_region(pdata.p_addr, shm_get_pktproc_desc_size()); |
| #endif |
| |
| return pdata.v_pktproc_desc; |
| } |
| |
| void __iomem *shm_get_pktproc_data_region(void) |
| { |
| #ifdef CONFIG_CP_LINEAR_WA |
| if (!pdata.v_pktproc_data) |
| pdata.v_pktproc_data = (void __iomem *)phys_to_virt(pdata.p_spare_addr + |
| shm_get_pktproc_desc_size()); |
| #else |
| if (!pdata.v_pktproc_cache) |
| pdata.v_pktproc_data = shm_request_region(pdata.p_addr + pdata.zmb_off + shm_get_pktproc_desc_size(), |
| shm_get_pktproc_data_size()); |
| #endif |
| |
| return pdata.v_pktproc_data; |
| } |
| #else /* CONFIG_CP_PKTPROC */ |
| void __iomem *shm_get_zmb_region(void) |
| { |
| if (!pdata.v_zmb) |
| pdata.v_zmb = shm_get_databuf_region(); |
| |
| return pdata.v_zmb; |
| } |
| #endif /* CONFIG_CP_PKTPROC */ |
| |
| void __iomem *shm_get_vss_region(void) |
| { |
| if (!pdata.v_vss) |
| pdata.v_vss = shm_request_region(pdata.p_addr + pdata.vss_off, |
| pdata.vss_size); |
| |
| return pdata.v_vss; |
| } |
| |
| void __iomem *shm_get_vparam_region(void) |
| { |
| if (!pdata.v_vparam) |
| pdata.v_vparam = shm_request_region(pdata.p_addr + pdata.vparam_off, |
| pdata.vparam_size); |
| |
| return pdata.v_vparam; |
| } |
| |
| void __iomem *shm_get_acpm_region(void) |
| { |
| if (!pdata.v_acpm) |
| pdata.v_acpm = shm_request_region(pdata.p_acpm_addr, |
| pdata.acpm_size); |
| |
| return pdata.v_acpm; |
| } |
| |
| #ifdef CONFIG_CP_RAM_LOGGING |
| void __iomem *shm_get_cplog_region(void) |
| { |
| if (!pdata.v_cplog) |
| pdata.v_cplog = shm_request_region(pdata.p_cplog_addr, |
| pdata.cplog_size); |
| |
| return pdata.v_cplog; |
| } |
| #endif |
| |
| #ifdef CONFIG_SEC_SIPC_DUAL_MODEM_IF |
| void __iomem *shm_get_s5100_ipc_region(void) |
| { |
| pr_err("%s: memory reserved: paddr=0x%08x, t_size=0x%08x\n", |
| __func__, (u32)pdata.p_s5100_ipc_addr, (u32)pdata.t_s5100_ipc_size); |
| |
| if (!pdata.v_s5100_ipc) |
| pdata.v_s5100_ipc = shm_request_region(pdata.p_s5100_ipc_addr, |
| pdata.t_s5100_ipc_size); |
| |
| return pdata.v_s5100_ipc; |
| } |
| #endif |
| |
| void shm_release_region(void *v_addr) |
| { |
| vunmap(v_addr); |
| } |
| |
| void shm_release_regions(void) |
| { |
| if (pdata.v_boot) |
| vunmap(pdata.v_boot); |
| |
| if (pdata.v_ipc) |
| vunmap(pdata.v_ipc); |
| |
| if (pdata.v_vss) |
| vunmap(pdata.v_vss); |
| |
| if (pdata.v_acpm) |
| vunmap(pdata.v_acpm); |
| |
| if (pdata.v_zmb) |
| vunmap(pdata.v_zmb); |
| |
| #ifdef CONFIG_CP_RAM_LOGGING |
| if (pdata.v_cplog) |
| vunmap(pdata.v_cplog); |
| #endif |
| |
| #ifdef CONFIG_SEC_SIPC_DUAL_MODEM_IF |
| if (pdata.v_s5100_ipc) |
| vunmap(pdata.v_s5100_ipc); |
| #endif |
| } |
| |
| #if defined(CONFIG_CP_RAM_LOGGING) && !defined(CONFIG_CP_LINEAR_WA) |
| static void shm_free_reserved_mem(unsigned long addr, unsigned size) |
| { |
| int i; |
| struct page *page; |
| |
| pr_err("Release cplog reserved memory\n"); |
| free_memsize_reserved(addr, size); |
| for (i = 0; i < (size >> PAGE_SHIFT); i++) { |
| page = phys_to_page(addr); |
| addr += PAGE_SIZE; |
| free_reserved_page(page); |
| } |
| } |
| #endif |
| |
| #ifdef CONFIG_OF_RESERVED_MEM |
| static int __init modem_if_reserved_mem_setup(struct reserved_mem *remem) |
| { |
| pdata.p_addr = remem->base; |
| pdata.t_size = remem->size; |
| |
| pr_err("%s: memory reserved: paddr=0x%08x, t_size=0x%08x\n", |
| __func__, (u32)pdata.p_addr, (u32)pdata.t_size); |
| |
| return 0; |
| } |
| RESERVEDMEM_OF_DECLARE(modem_if, "exynos,modem_if", modem_if_reserved_mem_setup); |
| |
| #ifdef CONFIG_LINK_DEVICE_PCIE |
| static int __init s5100_msi_reserved_mem_setup(struct reserved_mem *remem) |
| { |
| pdata.p_msi_addr = remem->base; |
| pdata.t_msi_size = remem->size; |
| |
| pr_err("%s: memory reserved: paddr=0x%08x, t_size=0x%08x\n", |
| __func__, (u32)pdata.p_msi_addr, (u32)pdata.t_msi_size); |
| |
| return 0; |
| } |
| RESERVEDMEM_OF_DECLARE(s5100_msi, "exynos,s5100-msi", s5100_msi_reserved_mem_setup); |
| #endif |
| |
| #ifdef CONFIG_SEC_SIPC_DUAL_MODEM_IF |
| static int __init s5100_ipc_reserved_mem_setup(struct reserved_mem *remem) |
| { |
| pdata.p_s5100_ipc_addr = remem->base; |
| pdata.t_s5100_modem_if2_size = remem->size; |
| |
| pr_err("%s: memory reserved: paddr=0x%08x, t_size=0x%08x\n", |
| __func__, (u32)pdata.p_s5100_ipc_addr, |
| (u32)remem->size); |
| return 0; |
| } |
| RESERVEDMEM_OF_DECLARE(modem_if2, "exynos,modem_if2", s5100_ipc_reserved_mem_setup); |
| |
| static int __init s5100_cp2cp_reserved_mem_setup(struct reserved_mem *remem) |
| { |
| pdata.p_s5100_cp2cp_addr = remem->base; |
| pdata.t_s5100_cp2cp_size = remem->size; |
| |
| pr_err("%s: memory reserved: paddr=0x%08x, t_size=0x%08x\n", |
| __func__, (u32)pdata.p_s5100_cp2cp_addr, |
| (u32)pdata.t_s5100_cp2cp_size); |
| return 0; |
| } |
| RESERVEDMEM_OF_DECLARE(cp2cp_shm, "exynos,cp2cp_shm", s5100_cp2cp_reserved_mem_setup); |
| #endif |
| |
| #ifdef CONFIG_CP_RAM_LOGGING |
| static int __init modem_if_reserved_cplog_setup(struct reserved_mem *remem) |
| { |
| pdata.p_cplog_addr = remem->base; |
| pdata.cplog_size = remem->size; |
| |
| pr_err("%s: cplog memory reserved: paddr=0x%08x, t_size=0x%08x\n", |
| __func__, (u32)pdata.p_cplog_addr, (u32)pdata.cplog_size); |
| |
| return 0; |
| } |
| RESERVEDMEM_OF_DECLARE(cp_ram_logging, "exynos,cp_ram_logging", |
| modem_if_reserved_cplog_setup); |
| #endif |
| |
| #ifdef CONFIG_CP_LINEAR_WA |
| static int __init modem_if_reserved_spare_setup(struct reserved_mem *remem) |
| { |
| pdata.p_spare_addr = remem->base; |
| pdata.spare_size = remem->size; |
| |
| pr_err("%s: spare memory reserved: paddr=0x%08x, t_size=0x%08x\n", |
| __func__, (u32)pdata.p_spare_addr, (u32)pdata.spare_size); |
| |
| return 0; |
| } |
| RESERVEDMEM_OF_DECLARE(modem_spare, "exynos,modem_spare", |
| modem_if_reserved_spare_setup); |
| #endif |
| |
| #if !defined (CONFIG_SOC_EXYNOS7570) |
| static int __init deliver_cp_reserved_mem_setup(struct reserved_mem *remem) |
| { |
| pdata.p_sysram_addr = remem->base; |
| pdata.t_sysram_size = remem->size; |
| |
| pr_err("%s: memory reserved: paddr=0x%08x, t_size=0x%08x\n", |
| __func__, (u32)remem->base, (u32)remem->size); |
| |
| return 0; |
| } |
| RESERVEDMEM_OF_DECLARE(deliver_cp, "exynos,deliver_cp", deliver_cp_reserved_mem_setup); |
| #endif |
| #endif |
| |
| #ifdef CONFIG_CP_RAM_LOGGING |
| static int __init console_setup(char *str) |
| { |
| if (!strcmp(str, "ON") || !strcmp(str, "on")) |
| pdata.cplog_on = 1; |
| |
| pr_info("cplog_on=%s, %d\n", str, pdata.cplog_on); |
| return 0; |
| } |
| __setup("androidboot.cp_reserved_mem=", console_setup); |
| #endif |
| |
| #define EXTERN_BIN_MAX_COUNT 10 |
| |
| struct extern_mem_bin_info_tag { |
| unsigned int ext_bin_tag; // Tag |
| unsigned int ext_bin_addr; // Offset address |
| unsigned int ext_bin_size; // binary size |
| }; |
| |
| struct cp_reserved_map_table { |
| unsigned int table_id_ver; // MEMn |
| unsigned int dram_size; // dram size |
| unsigned int ext_mem_size; // extern mem size |
| unsigned int ext_bin_count; // extern mem size |
| struct extern_mem_bin_info_tag sExtBin[EXTERN_BIN_MAX_COUNT]; |
| unsigned int end_flag_not_used; // Memory guard for CP boot code |
| }; |
| |
| struct cp_toc_element { |
| char name[12]; // Binary name |
| u32 b_offset; // Binary offset in the file |
| u32 m_offset; // Memory Offset to be loaded |
| u32 size; // Binary size |
| u32 crc; // CRC value |
| u32 toc_count; // Reserved |
| } __packed; |
| |
| struct cp_reserved_map_table cp_mem_map; |
| |
| static int verify_cp_memory_map(struct cp_reserved_map_table *tab) |
| { |
| #ifndef CONFIG_CP_LINEAR_WA |
| unsigned int total_bin = tab->ext_bin_count; |
| int i; |
| #endif |
| int verify = 1; |
| |
| if (tab->ext_bin_count <= 0 || (tab->ext_bin_count > EXTERN_BIN_MAX_COUNT)) { |
| pr_err("ERROR ext_bin_count=%d\n", tab->ext_bin_count); |
| verify = 0; |
| goto exit; |
| } |
| |
| #ifndef CONFIG_CP_LINEAR_WA |
| for (i = 1; i < total_bin; i++) { |
| if (cp_mem_map.sExtBin[i-1].ext_bin_addr + cp_mem_map.sExtBin[i-1].ext_bin_size |
| != cp_mem_map.sExtBin[i].ext_bin_addr) { |
| pr_err("ERROR ext_bin_addr[%d]=0x%08x ext_bin_size[%d]=0x%08x ext_bin_addr[%d]=0x%08x\n", |
| i-1, cp_mem_map.sExtBin[i-1].ext_bin_addr, |
| i-1, cp_mem_map.sExtBin[i-1].ext_bin_size, |
| i, cp_mem_map.sExtBin[i].ext_bin_addr); |
| verify = 0; |
| goto exit; |
| } |
| } |
| #endif |
| |
| exit: |
| return verify; |
| } |
| |
| static int shm_probe(struct platform_device *pdev) |
| { |
| struct device *dev = &pdev->dev; |
| int ret; |
| int verify; |
| unsigned int tmp; |
| int i; |
| char *ptr; |
| struct device_node *np_acpm = of_find_node_by_name(NULL, "acpm_ipc"); |
| void __iomem *cp_mem_base; |
| struct cp_toc_element *cp_toc_info; |
| char table_id_ver[5]; |
| |
| dev_err(dev, "%s: shmem driver init\n", __func__); |
| |
| cp_mem_base = shm_request_region(pdata.p_addr, PAGE_SIZE); |
| dev_info(dev, "cp_mem_base: 0x%lx, 0x%pK\n", pdata.p_addr, cp_mem_base); |
| |
| /* get 2nd TOC table : BOOT bin info */ |
| cp_toc_info = (struct cp_toc_element *)(cp_mem_base + sizeof(struct cp_toc_element)); |
| dev_info(dev, "cp_boot_offset: 0x%x\n", cp_toc_info->b_offset); |
| |
| /* 0xa0: cp memory map offset */ |
| memcpy(&cp_mem_map, cp_mem_base + cp_toc_info->b_offset + 0xa0, sizeof(struct cp_reserved_map_table)); |
| |
| if (cp_mem_base) { |
| vunmap(cp_mem_base); |
| cp_mem_base = NULL; |
| } |
| |
| tmp = ntohl(cp_mem_map.table_id_ver + 0x30); |
| if (strncmp((const char *)&tmp, "MEM", 3) == 0) { |
| dev_info(dev, "CP memory map on CP binary\n"); |
| verify = verify_cp_memory_map(&cp_mem_map); |
| dev_info(dev, "CP memory map verification: %s\n", verify == 1 ? "PASS!" : "FAIL!"); |
| if (verify) { |
| pdata.use_cp_memory_map = 1; |
| strncpy(table_id_ver, (void *)&tmp, 4); |
| table_id_ver[4] = '\0'; |
| pdata.cp_size = cp_mem_map.dram_size; |
| dev_info(dev, "table_id_ver: %s\n", table_id_ver); |
| dev_info(dev, "bin_count: %d\n", cp_mem_map.ext_bin_count); |
| dev_info(dev, "cp dram_size: 0x%08X\n", pdata.cp_size); |
| dev_info(dev, "ext_mem_size: 0x%08X\n", cp_mem_map.ext_mem_size); |
| for (i = 0; i < cp_mem_map.ext_bin_count; i++) { |
| tmp = ntohl(cp_mem_map.sExtBin[i].ext_bin_tag); |
| ptr = ((char *)&tmp) + 1; |
| |
| if (strncmp((const char *)ptr, "IPC", 3) == 0) { |
| pdata.ipc_off = cp_mem_map.sExtBin[i].ext_bin_addr; |
| pdata.ipc_size = cp_mem_map.sExtBin[i].ext_bin_size; |
| dev_err(dev, "IPC: 0x%08X 0x%08X\n", |
| pdata.ipc_off, pdata.ipc_size); |
| } else if (strncmp((const char *)ptr, "VSS", 3) == 0) { |
| pdata.vss_off = cp_mem_map.sExtBin[i].ext_bin_addr; |
| pdata.vss_size = cp_mem_map.sExtBin[i].ext_bin_size; |
| dev_err(dev, "VSS: 0x%08X 0x%08X\n", |
| pdata.vss_off, pdata.vss_size); |
| } else if (strncmp((const char *)ptr, "VPA", 3) == 0) { |
| pdata.vparam_off = cp_mem_map.sExtBin[i].ext_bin_addr; |
| pdata.vparam_size = cp_mem_map.sExtBin[i].ext_bin_size; |
| dev_err(dev, "VSS PARAM: 0x%08X 0x%08X\n", |
| pdata.vparam_off, pdata.vparam_size); |
| } else if (strncmp((const char *)ptr, "ZMC", 3) == 0) { |
| #ifdef CONFIG_CP_LINEAR_WA |
| dev_info(dev, "Skip ZMC on the memory map\n"); |
| #else |
| pdata.zmb_off = cp_mem_map.sExtBin[i].ext_bin_addr; |
| pdata.zmb_size = cp_mem_map.sExtBin[i].ext_bin_size; |
| dev_err(dev, "ZMC: 0x%08X 0x%08X\n", |
| pdata.zmb_off, pdata.zmb_size); |
| #endif |
| } else if (strncmp((const char *)ptr, "LOG", 3) == 0) { |
| dev_err(dev, "LOG: 0x%08X 0x%08X\n", |
| cp_mem_map.sExtBin[i].ext_bin_addr, |
| cp_mem_map.sExtBin[i].ext_bin_size); |
| } else if (strncmp((const char *)ptr, "L2B", 3) == 0) { |
| pdata.l2b_off = cp_mem_map.sExtBin[i].ext_bin_addr; |
| pdata.l2b_size = cp_mem_map.sExtBin[i].ext_bin_size; |
| dev_err(dev, "L2B: 0x%08X 0x%08X\n", |
| pdata.l2b_off, pdata.l2b_size); |
| } |
| } |
| } else { |
| WARN_ONCE(!verify, "cp memory map verification fail\n"); |
| return -EINVAL; |
| } |
| } else if (dev->of_node) { |
| dev_info(dev, "CP memory map on DT\n"); |
| ret = of_property_read_u32(dev->of_node, "shmem,ipc_offset", |
| &pdata.ipc_off); |
| if (ret) { |
| dev_err(dev, "failed to get property, ipc_offset\n"); |
| return -EINVAL; |
| } |
| ret = of_property_read_u32(dev->of_node, "shmem,ipc_size", |
| &pdata.ipc_size); |
| if (ret) { |
| dev_err(dev, "failed to get property, ipc_size\n"); |
| return -EINVAL; |
| } |
| |
| #if defined(CONFIG_SEC_SIPC_MODEM_IF) && !defined(CONFIG_CP_LINEAR_WA) |
| ret = of_property_read_u32(dev->of_node, "shmem,zmb_offset", |
| &pdata.zmb_off); |
| if (ret) { |
| dev_err(dev, "failed to get property, zmb_offset\n"); |
| return -EINVAL; |
| } |
| ret = of_property_read_u32(dev->of_node, "shmem,zmb_size", |
| &pdata.zmb_size); |
| if (ret) { |
| dev_err(dev, "failed to get property, zmb_size\n"); |
| return -EINVAL; |
| } |
| #endif |
| |
| ret = of_property_read_u32(dev->of_node, "shmem,cp_size", |
| &pdata.cp_size); |
| if (ret) |
| dev_err(dev, "failed to get property, cp_size\n"); |
| |
| ret = of_property_read_u32(dev->of_node, "shmem,vss_offset", |
| &pdata.vss_off); |
| if (ret) |
| dev_err(dev, "failed to get property, vss_off\n"); |
| |
| ret = of_property_read_u32(dev->of_node, "shmem,vss_size", |
| &pdata.vss_size); |
| if (ret) |
| dev_err(dev, "failed to get property, vss_size\n"); |
| |
| ret = of_property_read_u32(dev->of_node, "shmem,vparam_offset", |
| &pdata.vparam_off); |
| if (ret) |
| dev_err(dev, "failed to get property, vparam_size\n"); |
| |
| ret = of_property_read_u32(dev->of_node, "shmem,vparam_size", |
| &pdata.vparam_size); |
| if (ret) |
| dev_err(dev, "failed to get property, vparam_size\n"); |
| } else { |
| /* To do: In case of non-DT */ |
| } |
| |
| #ifdef CONFIG_SEC_SIPC_DUAL_MODEM_IF |
| if (dev->of_node) { |
| ret = of_property_read_u32(dev->of_node, "shmem,s5100_ipc_size", |
| &pdata.t_s5100_ipc_size); |
| if (ret) |
| dev_err(dev, "failed to get property, s5100_ipc_size\n"); |
| |
| ret = of_property_read_u32(dev->of_node, "shmem,s5100_cp2cp_offset", |
| &pdata.s5100_cp2cp_off); |
| if (ret) |
| dev_err(dev, "failed to get property, s5100_cp2cp_offset\n"); |
| } |
| #endif |
| |
| if (np_acpm) { |
| ret = of_property_read_u32(np_acpm, "dump-base", |
| &pdata.p_acpm_addr); |
| if (ret) |
| dev_err(dev, "failed to get property, acpm_base\n"); |
| |
| ret = of_property_read_u32(np_acpm, "dump-size", |
| &pdata.acpm_size); |
| if (ret) |
| dev_err(dev, "failed to get property, acpm_size\n"); |
| } |
| |
| dev_info(dev, "CP memory map:\n"); |
| dev_info(dev, "base=0x%lX total=0x%08X\n", |
| pdata.p_addr, pdata.t_size); |
| dev_info(dev, "cp_size=0x%08X vss_size=0x%08X\n", |
| pdata.cp_size, pdata.vss_size); |
| dev_info(dev, "ipc_off=0x%08X ipc_size=0x%08X\n", |
| pdata.ipc_off, pdata.ipc_size); |
| #ifdef CONFIG_CP_LINEAR_WA |
| dev_info(dev, "zmb_addr=0x%08lX zmb_size=0x%08X\n", |
| pdata.p_spare_addr, pdata.spare_size); |
| #else |
| dev_info(dev, "zmb_off=0x%08X zmb_size=0x%08X\n", |
| pdata.zmb_off, pdata.zmb_size); |
| #endif |
| dev_info(dev, "vparam_off=0x%08X vparam_size=0x%08X\n", |
| pdata.vparam_off, pdata.vparam_size); |
| dev_info(dev, "acpm_base=0x%08X acpm_size=0x%08X\n", |
| pdata.p_acpm_addr, pdata.acpm_size); |
| |
| #ifdef CONFIG_LINK_DEVICE_PCIE |
| dev_info(dev, "msi_base=0x%08X msi_size=0x%08X\n", |
| pdata.p_msi_addr, pdata.t_msi_size); |
| #endif |
| |
| #ifdef CONFIG_SEC_SIPC_DUAL_MODEM_IF |
| dev_info(dev, "s5100_ipc_base=0x%08X s5100_ipc_size=0x%08X\n", |
| pdata.p_s5100_ipc_addr, pdata.t_s5100_ipc_size); |
| |
| dev_info(dev, "s5100_cp2cp_addr=0x%08X s5100_cp2cp_size=0x%08X s5100_cp2cp_offset=0x%08X\n", |
| pdata.p_s5100_ipc_addr, pdata.t_s5100_ipc_size, |
| pdata.s5100_cp2cp_off); |
| #endif |
| |
| #ifdef CONFIG_CP_RAM_LOGGING |
| if (pdata.cplog_on) { |
| dev_err(dev, "cplog_base=0x%08lX, cplog_size=0x%08X\n", |
| pdata.p_cplog_addr, pdata.cplog_size); |
| |
| /* create memshare driver */ |
| ret = create_memshare_device(&pdata, MEMSHARE_DEV_NAME, dev); |
| if (ret) { |
| dev_err(dev, "failed to create memshare device\n"); |
| } |
| } else { |
| #ifdef CONFIG_CP_LINEAR_WA |
| dev_info(dev, "CP linear work around. Do not free memory for CP RAM logging\n"); |
| #else |
| shm_free_reserved_mem(pdata.p_cplog_addr, pdata.cplog_size); |
| #endif |
| } |
| #endif |
| return 0; |
| } |
| |
| static int shm_remove(struct platform_device *pdev) |
| { |
| #ifdef CONFIG_CP_RAM_LOGGING |
| destroy_memshare_device(); |
| #endif |
| return 0; |
| } |
| |
| static const struct of_device_id exynos_shm_dt_match[] = { |
| { .compatible = "samsung,exynos7580-shm_ipc", }, |
| { .compatible = "samsung,exynos8890-shm_ipc", }, |
| { .compatible = "samsung,exynos7870-shm_ipc", }, |
| { .compatible = "samsung,exynos-shm_ipc", }, |
| {}, |
| }; |
| MODULE_DEVICE_TABLE(of, exynos_shm_dt_match); |
| |
| static struct platform_driver shmem_driver = { |
| .probe = shm_probe, |
| .remove = shm_remove, |
| .driver = { |
| .name = "shm_ipc", |
| .owner = THIS_MODULE, |
| .of_match_table = of_match_ptr(exynos_shm_dt_match), |
| .suppress_bind_attrs = true, |
| }, |
| }; |
| module_platform_driver(shmem_driver); |
| |
| MODULE_DESCRIPTION(""); |
| MODULE_AUTHOR(""); |
| MODULE_LICENSE("GPL"); |