/* * drivers/soc/samsung/secmem.c * * Copyright (c) 2015 Samsung Electronics Co., Ltd. * http://www.samsung.com * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 as * published by the Free Software Foundation. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define SECMEM_DEV_NAME "s5p-smem" #define DRM_PROT_VER_CHUNK_BASED_PROT 0 #define DRM_PROT_VER_BUFFER_BASED_PROT 1 #define SMC_CM_SET_DRM_MEM_INFO (0x82001021) #define MAX_DRM_MEM_INFO_NUM 16 #define CUR_DRM_MEM_INFO_NUM 2 struct miscdevice secmem; struct secmem_crypto_driver_ftn *crypto_driver; uint32_t instance_count; #if defined(CONFIG_EXYNOS_DP_POWER_CONTROL) #define DP_POWER_OFF 0 #define DP_POWER_ON 1 static int ref_count_pm = 0; #endif struct device *secmem_dev = NULL; #if defined(CONFIG_SOC_EXYNOS5433) static uint32_t secmem_regions[] = { ION_EXYNOS_ID_G2D_WFD, ION_EXYNOS_ID_VIDEO, ION_EXYNOS_ID_SECTBL, ION_EXYNOS_ID_MFC_FW, ION_EXYNOS_ID_MFC_NFW, }; static char *secmem_regions_name[] = { "g2d_wfd", /* 0 */ "video", /* 1 */ "sectbl", /* 2 */ "mfc_fw", /* 3 */ "mfc_nfw", /* 4 */ NULL }; #elif defined(CONFIG_SOC_EXYNOS7420) static uint32_t secmem_regions[] = { ION_EXYNOS_ID_G2D_WFD, ION_EXYNOS_ID_VIDEO, ION_EXYNOS_ID_VIDEO_EXT, ION_EXYNOS_ID_MFC_FW, ION_EXYNOS_ID_MFC_NFW, }; static char *secmem_regions_name[] = { "g2d_wfd", /* 0 */ "video", /* 1 */ "video_ext", /* 2 */ "mfc_fw", /* 3 */ "mfc_nfw", /* 4 */ NULL }; #endif static bool drm_onoff; static DEFINE_MUTEX(drm_lock); static DEFINE_MUTEX(smc_lock); struct secmem_info { struct device *dev; bool drm_enabled; }; struct protect_info { uint32_t dev; uint32_t enable; }; struct addr_info_t { uint64_t addr; uint64_t size; }; #define SECMEM_IS_PAGE_ALIGNED(addr) (!((addr) & (~PAGE_MASK))) int drm_enable_locked(struct secmem_info *info, bool enable) { if (drm_onoff == enable) { pr_err("%s: DRM is already %s\n", __func__, drm_onoff ? "on" : "off"); return -EINVAL; } drm_onoff = enable; /* * this will only allow this instance to turn drm_off either by * calling the ioctl or by closing the fd */ info->drm_enabled = enable; return 0; } static int secmem_open(struct inode *inode, struct file *file) { struct miscdevice *miscdev = file->private_data; struct device *dev = miscdev->this_device; struct secmem_info *info; info = kzalloc(sizeof(struct secmem_info), GFP_KERNEL); if (!info) return -ENOMEM; info->dev = dev; file->private_data = info; mutex_lock(&drm_lock); instance_count++; mutex_unlock(&drm_lock); return 0; } static int secmem_release(struct inode *inode, struct file *file) { struct secmem_info *info = file->private_data; /* disable drm if we were the one to turn it on */ mutex_lock(&drm_lock); instance_count--; if (instance_count == 0) { if (info->drm_enabled) { int ret; ret = drm_enable_locked(info, false); if (ret < 0) pr_err("fail to lock/unlock drm status. lock = %d\n", false); } #if defined(CONFIG_EXYNOS_DP_POWER_CONTROL) if (ref_count_pm > 0) { int i; pr_err("ref_count_pm for DP remains (%d)\n", ref_count_pm); for (i = 0; i < ref_count_pm; i++) pm_runtime_put_sync(secmem_dev); ref_count_pm = 0; } #endif } mutex_unlock(&drm_lock); kfree(info); return 0; } static long secmem_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) { struct secmem_info *info = filp->private_data; switch (cmd) { case (uint32_t)SECMEM_IOC_GET_FD_PHYS_ADDR: { struct secfd_info fd_info; struct dma_buf *dmabuf; struct dma_buf_attachment *attachment; struct sg_table *sgt; if (copy_from_user(&fd_info, (int __user *)arg, sizeof(fd_info))) return -EFAULT; dmabuf = dma_buf_get(fd_info.fd); if (IS_ERR_OR_NULL(dmabuf)) { pr_err("smem ioctl error(%d)\n", __LINE__); return -ENOMEM; } attachment = dma_buf_attach(dmabuf, secmem_dev); if (!attachment) { pr_err("smem ioctl error(%d)\n", __LINE__); dma_buf_put(dmabuf); return -ENOMEM; } attachment->dma_map_attrs |= DMA_ATTR_SKIP_CPU_SYNC; sgt = dma_buf_map_attachment(attachment, DMA_TO_DEVICE); if (!sgt) { pr_err("smem ioctl error(%d)\n", __LINE__); dma_buf_detach(dmabuf, attachment); dma_buf_put(dmabuf); return -ENOMEM; } fd_info.phys = sg_phys(sgt->sgl); pr_debug("%s: physical addr from kernel space = 0x%08x\n", __func__, (unsigned int)fd_info.phys); dma_buf_unmap_attachment(attachment, sgt, DMA_TO_DEVICE); dma_buf_detach(dmabuf, attachment); dma_buf_put(dmabuf); if (copy_to_user((void __user *)arg, &fd_info, sizeof(fd_info))) return -EFAULT; break; } case (uint32_t)SECMEM_IOC_GET_DRM_ONOFF: smp_rmb(); if (copy_to_user((void __user *)arg, &drm_onoff, sizeof(bool))) return -EFAULT; break; case (uint32_t)SECMEM_IOC_SET_DRM_ONOFF: { int ret, val = 0; if (copy_from_user(&val, (int __user *)arg, sizeof(int))) return -EFAULT; mutex_lock(&drm_lock); if ((info->drm_enabled && !val) || (!info->drm_enabled && val)) { /* * 1. if we enabled drm, then disable it * 2. if we don't already hdrm enabled, * try to enable it. */ ret = drm_enable_locked(info, val); if (ret < 0) pr_err("fail to lock/unlock drm status. lock = %d\n", val); } mutex_unlock(&drm_lock); break; } case (uint32_t)SECMEM_IOC_GET_CRYPTO_LOCK: { break; } case (uint32_t)SECMEM_IOC_RELEASE_CRYPTO_LOCK: { break; } case (uint32_t)SECMEM_IOC_SET_TZPC: { break; } case (uint32_t)SECMEM_IOC_SET_VIDEO_EXT_PROC: { int val, ret; if (copy_from_user(&val, (int __user *)arg, sizeof(int))) return -EFAULT; mutex_lock(&smc_lock); ret = exynos_smc((uint32_t)(SMC_DRM_VIDEO_PROC), val, 0, 0); if (ret) { pr_err("Failed to control VIDEO EXT region protection. prot = %d\n", val); mutex_unlock(&smc_lock); return -ENOMEM; } mutex_unlock(&smc_lock); break; } case (uint32_t)SECMEM_IOC_GET_DRM_PROT_VER: { int val; val = DRM_PROT_VER_BUFFER_BASED_PROT; if (copy_to_user((void __user *)arg, &val, sizeof(int))) return -EFAULT; break; } #if defined(CONFIG_EXYNOS_DP_POWER_CONTROL) case (uint32_t)SECMEM_IOC_DP_POWER_CONTROL: { int val; mutex_lock(&drm_lock); if (copy_from_user(&val, (int __user *)arg, sizeof(int))) { mutex_unlock(&drm_lock); return -EFAULT; } if (ref_count_pm <= 0 && val == DP_POWER_OFF) { pr_err("Failed to hsi0 power control for DRM.(%d)\n", ref_count_pm); ref_count_pm = 0; mutex_unlock(&drm_lock); return -EFAULT; } if (val == DP_POWER_ON) { ref_count_pm++; pm_runtime_get_sync(secmem_dev); } else if (val == DP_POWER_OFF) { ref_count_pm--; pm_runtime_put_sync(secmem_dev); } else { mutex_unlock(&drm_lock); pr_err("Wrong hsi0-dp power status. (%d)\n", val); return -EFAULT; } mutex_unlock(&drm_lock); break; } #endif default: return -ENOTTY; } return 0; } static int exynos_secmem_probe(struct platform_device *pdev) { struct reserved_mem *rmem; struct device_node *rmem_np; struct addr_info_t *info_ctx; uint32_t idx = 0; unsigned long smc_ret; secmem_dev = &(pdev->dev); pdev->dev.dma_mask = &pdev->dev.coherent_dma_mask; dma_set_mask(&pdev->dev, DMA_BIT_MASK(36)); #if defined(CONFIG_EXYNOS_DP_POWER_CONTROL) pm_runtime_set_active(secmem_dev); pm_runtime_enable(secmem_dev); #endif info_ctx = kmalloc(sizeof(struct addr_info_t) * MAX_DRM_MEM_INFO_NUM, GFP_KERNEL); if (!info_ctx) goto out; for (idx = 0; idx < CUR_DRM_MEM_INFO_NUM; idx++) { rmem_np = of_parse_phandle(pdev->dev.of_node, "memory-region", idx); rmem = of_reserved_mem_lookup(rmem_np); if (!rmem) { pr_err("%s: Not found memory-region handle for %d\n", pdev->name, idx); break; } info_ctx[idx].addr = rmem->base; info_ctx[idx].size = rmem->size; } if (idx == CUR_DRM_MEM_INFO_NUM) { /* cache flush */ dma_map_single(secmem_dev, (void *)info_ctx, sizeof(struct addr_info_t) * MAX_DRM_MEM_INFO_NUM, DMA_TO_DEVICE); smc_ret = exynos_smc(SMC_CM_SET_DRM_MEM_INFO, virt_to_phys(info_ctx), idx, 0); if (smc_ret) pr_err("%s: exynos_smc ret %lu\n", pdev->name, smc_ret); dma_unmap_single(secmem_dev, phys_to_dma(secmem_dev, virt_to_phys(info_ctx)), sizeof(struct addr_info_t) * MAX_DRM_MEM_INFO_NUM, DMA_TO_DEVICE); } kfree(info_ctx); out: return 0; } static int exynos_secmem_remove(struct platform_device *pdev) { secmem_dev = &(pdev->dev); #if defined(CONFIG_EXYNOS_DP_POWER_CONTROL) pm_runtime_disable(secmem_dev); if (!pm_runtime_status_suspended(secmem_dev)) { pm_runtime_set_suspended(secmem_dev); } #endif return 0; } #if 0 void secmem_crypto_register(struct secmem_crypto_driver_ftn *ftn) { crypto_driver = ftn; } EXPORT_SYMBOL(secmem_crypto_register); void secmem_crypto_deregister(void) { crypto_driver = NULL; } EXPORT_SYMBOL(secmem_crypto_deregister); #endif static const struct of_device_id exynos_secmem_of_match_table[] = { { .compatible = "samsung,exynos-secmem", }, { }, }; static struct platform_driver exynos_secmem_driver = { .probe = exynos_secmem_probe, .remove = exynos_secmem_remove, .driver = { .name = "exynos-secmem", .owner = THIS_MODULE, .of_match_table = of_match_ptr(exynos_secmem_of_match_table), } }; static const struct file_operations secmem_fops = { .owner = THIS_MODULE, .open = secmem_open, .release = secmem_release, .compat_ioctl = secmem_ioctl, .unlocked_ioctl = secmem_ioctl, }; struct miscdevice secmem = { .minor = MISC_DYNAMIC_MINOR, .name = SECMEM_DEV_NAME, .fops = &secmem_fops, }; static int __init secmem_init(void) { int ret; ret = misc_register(&secmem); if (ret) { pr_err("%s: SECMEM can't register misc on minor=%d\n", __func__, MISC_DYNAMIC_MINOR); return ret; } crypto_driver = NULL; platform_driver_register(&exynos_secmem_driver); return 0; } static void __exit secmem_exit(void) { misc_deregister(&secmem); platform_driver_unregister(&exynos_secmem_driver); } module_init(secmem_init); module_exit(secmem_exit); MODULE_DESCRIPTION("Exynos Secure memory support driver"); MODULE_AUTHOR(""); MODULE_LICENSE("GPL");