blob: e0314f9ebc3bcd8f7d6dec5676f9d74ad66039c1 [file] [log] [blame]
/* Copyright (c) 2016-2018, The Linux Foundation. All rights reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 and
* only version 2 as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
#define pr_fmt(fmt) "io-pgtable-msm-secure: " fmt
#include <linux/iommu.h>
#include <linux/kernel.h>
#include <linux/scatterlist.h>
#include <linux/sizes.h>
#include <linux/slab.h>
#include <linux/types.h>
#include <soc/qcom/scm.h>
#include <linux/dma-mapping.h>
#include <asm/cacheflush.h>
#include "io-pgtable.h"
#define IOMMU_SECURE_PTBL_SIZE 3
#define IOMMU_SECURE_PTBL_INIT 4
#define IOMMU_SECURE_MAP2_FLAT 0x12
#define IOMMU_SECURE_UNMAP2_FLAT 0x13
#define IOMMU_TLBINVAL_FLAG 0x00000001
#define io_pgtable_to_data(x) \
container_of((x), struct msm_secure_io_pgtable, iop)
#define io_pgtable_ops_to_pgtable(x) \
container_of((x), struct io_pgtable, ops)
#define io_pgtable_ops_to_data(x) \
io_pgtable_to_data(io_pgtable_ops_to_pgtable(x))
struct msm_secure_io_pgtable {
struct io_pgtable iop;
/* lock required while operating on page tables */
struct mutex pgtbl_lock;
};
int msm_iommu_sec_pgtbl_init(void)
{
struct msm_scm_ptbl_init {
unsigned int paddr;
unsigned int size;
unsigned int spare;
} pinit = {0};
int psize[2] = {0, 0};
unsigned int spare = 0;
int ret, ptbl_ret = 0;
struct device dev = {0};
void *cpu_addr;
dma_addr_t paddr;
unsigned long attrs = 0;
struct scm_desc desc = {0};
if (!is_scm_armv8()) {
ret = scm_call(SCM_SVC_MP, IOMMU_SECURE_PTBL_SIZE, &spare,
sizeof(spare), psize, sizeof(psize));
} else {
struct scm_desc desc = {0};
desc.args[0] = spare;
desc.arginfo = SCM_ARGS(1);
ret = scm_call2(SCM_SIP_FNID(SCM_SVC_MP,
IOMMU_SECURE_PTBL_SIZE), &desc);
psize[0] = desc.ret[0];
psize[1] = desc.ret[1];
}
if (ret || psize[1]) {
pr_err("scm call IOMMU_SECURE_PTBL_SIZE failed\n");
goto fail;
}
/* Now allocate memory for the secure page tables */
attrs = DMA_ATTR_NO_KERNEL_MAPPING;
dev.coherent_dma_mask = DMA_BIT_MASK(sizeof(dma_addr_t) * 8);
arch_setup_dma_ops(&dev, 0, 0, NULL, 0);
cpu_addr = dma_alloc_attrs(&dev, psize[0], &paddr, GFP_KERNEL, attrs);
if (!cpu_addr) {
pr_err("%s: Failed to allocate %d bytes for PTBL\n",
__func__, psize[0]);
ret = -ENOMEM;
goto fail;
}
pinit.paddr = (unsigned int)paddr;
/* paddr may be a physical address > 4GB */
desc.args[0] = paddr;
desc.args[1] = pinit.size = psize[0];
desc.args[2] = pinit.spare;
desc.arginfo = SCM_ARGS(3, SCM_RW, SCM_VAL, SCM_VAL);
if (!is_scm_armv8()) {
ret = scm_call(SCM_SVC_MP, IOMMU_SECURE_PTBL_INIT, &pinit,
sizeof(pinit), &ptbl_ret, sizeof(ptbl_ret));
} else {
ret = scm_call2(SCM_SIP_FNID(SCM_SVC_MP,
IOMMU_SECURE_PTBL_INIT), &desc);
ptbl_ret = desc.ret[0];
}
if (ret) {
pr_err("scm call IOMMU_SECURE_PTBL_INIT failed\n");
goto fail_mem;
}
if (ptbl_ret) {
pr_err("scm call IOMMU_SECURE_PTBL_INIT extended ret fail\n");
goto fail_mem;
}
return 0;
fail_mem:
dma_free_attrs(&dev, psize[0], cpu_addr, paddr, attrs);
fail:
return ret;
}
EXPORT_SYMBOL(msm_iommu_sec_pgtbl_init);
static int msm_secure_map(struct io_pgtable_ops *ops, unsigned long iova,
phys_addr_t paddr, size_t size, int iommu_prot)
{
struct msm_secure_io_pgtable *data = io_pgtable_ops_to_data(ops);
struct io_pgtable_cfg *cfg = &data->iop.cfg;
void *flush_va, *flush_va_end;
struct scm_desc desc = {0};
int ret = -EINVAL;
u32 resp;
if (!IS_ALIGNED(iova, SZ_1M) || !IS_ALIGNED(paddr, SZ_1M) ||
!IS_ALIGNED(size, SZ_1M))
return -EINVAL;
desc.args[0] = virt_to_phys(&paddr);
desc.args[1] = 1;
desc.args[2] = size;
desc.args[3] = cfg->arm_msm_secure_cfg.sec_id;
desc.args[4] = cfg->arm_msm_secure_cfg.cbndx;
desc.args[5] = iova;
desc.args[6] = size;
desc.args[7] = 0;
flush_va = &paddr;
flush_va_end = (void *)
(((unsigned long) flush_va) + sizeof(phys_addr_t));
mutex_lock(&data->pgtbl_lock);
/*
* Ensure that the buffer is in RAM by the time it gets to TZ
*/
dmac_clean_range(flush_va, flush_va_end);
desc.arginfo = SCM_ARGS(8, SCM_RW, SCM_VAL, SCM_VAL, SCM_VAL, SCM_VAL,
SCM_VAL, SCM_VAL, SCM_VAL);
if (is_scm_armv8()) {
ret = scm_call2(SCM_SIP_FNID(SCM_SVC_MP,
IOMMU_SECURE_MAP2_FLAT), &desc);
resp = desc.ret[0];
}
mutex_unlock(&data->pgtbl_lock);
if (ret || resp)
return -EINVAL;
return 0;
}
static dma_addr_t msm_secure_get_phys_addr(struct scatterlist *sg)
{
/*
* Try sg_dma_address first so that we can
* map carveout regions that do not have a
* struct page associated with them.
*/
dma_addr_t pa = sg_dma_address(sg);
if (pa == 0)
pa = sg_phys(sg);
return pa;
}
static int msm_secure_map_sg(struct io_pgtable_ops *ops, unsigned long iova,
struct scatterlist *sg, unsigned int nents,
int iommu_prot, size_t *size)
{
struct msm_secure_io_pgtable *data = io_pgtable_ops_to_data(ops);
struct io_pgtable_cfg *cfg = &data->iop.cfg;
int ret = -EINVAL;
struct scatterlist *tmp, *sgiter;
dma_addr_t *pa_list = 0;
unsigned int cnt, offset = 0, chunk_offset = 0;
dma_addr_t pa;
void *flush_va, *flush_va_end;
unsigned long len = 0;
struct scm_desc desc = {0};
int i;
u32 resp;
for_each_sg(sg, tmp, nents, i)
len += tmp->length;
if (!IS_ALIGNED(iova, SZ_1M) || !IS_ALIGNED(len, SZ_1M))
return -EINVAL;
if (sg->length == len) {
cnt = 1;
pa = msm_secure_get_phys_addr(sg);
if (!IS_ALIGNED(pa, SZ_1M))
return -EINVAL;
desc.args[0] = virt_to_phys(&pa);
desc.args[1] = cnt;
desc.args[2] = len;
flush_va = &pa;
} else {
sgiter = sg;
if (!IS_ALIGNED(sgiter->length, SZ_1M))
return -EINVAL;
cnt = sg->length / SZ_1M;
while ((sgiter = sg_next(sgiter))) {
if (!IS_ALIGNED(sgiter->length, SZ_1M))
return -EINVAL;
cnt += sgiter->length / SZ_1M;
}
pa_list = kmalloc_array(cnt, sizeof(*pa_list), GFP_KERNEL);
if (!pa_list)
return -ENOMEM;
sgiter = sg;
cnt = 0;
pa = msm_secure_get_phys_addr(sgiter);
while (offset < len) {
if (!IS_ALIGNED(pa, SZ_1M)) {
kfree(pa_list);
return -EINVAL;
}
pa_list[cnt] = pa + chunk_offset;
chunk_offset += SZ_1M;
offset += SZ_1M;
cnt++;
if (chunk_offset >= sgiter->length && offset < len) {
chunk_offset = 0;
sgiter = sg_next(sgiter);
pa = msm_secure_get_phys_addr(sgiter);
}
}
desc.args[0] = virt_to_phys(pa_list);
desc.args[1] = cnt;
desc.args[2] = SZ_1M;
flush_va = pa_list;
}
desc.args[3] = cfg->arm_msm_secure_cfg.sec_id;
desc.args[4] = cfg->arm_msm_secure_cfg.cbndx;
desc.args[5] = iova;
desc.args[6] = len;
desc.args[7] = 0;
desc.arginfo = SCM_ARGS(8, SCM_RW, SCM_VAL, SCM_VAL, SCM_VAL, SCM_VAL,
SCM_VAL, SCM_VAL, SCM_VAL);
/*
* Ensure that the buffer is in RAM by the time it gets to TZ
*/
flush_va_end = (void *) (((unsigned long) flush_va) +
(cnt * sizeof(*pa_list)));
mutex_lock(&data->pgtbl_lock);
dmac_clean_range(flush_va, flush_va_end);
if (is_scm_armv8()) {
ret = scm_call2(SCM_SIP_FNID(SCM_SVC_MP,
IOMMU_SECURE_MAP2_FLAT), &desc);
resp = desc.ret[0];
if (ret || resp)
ret = -EINVAL;
else
ret = len;
}
mutex_unlock(&data->pgtbl_lock);
kfree(pa_list);
return ret;
}
static size_t msm_secure_unmap(struct io_pgtable_ops *ops, unsigned long iova,
size_t len)
{
struct msm_secure_io_pgtable *data = io_pgtable_ops_to_data(ops);
struct io_pgtable_cfg *cfg = &data->iop.cfg;
int ret = -EINVAL;
struct scm_desc desc = {0};
if (!IS_ALIGNED(iova, SZ_1M) || !IS_ALIGNED(len, SZ_1M))
return ret;
desc.args[0] = cfg->arm_msm_secure_cfg.sec_id;
desc.args[1] = cfg->arm_msm_secure_cfg.cbndx;
desc.args[2] = iova;
desc.args[3] = len;
desc.args[4] = IOMMU_TLBINVAL_FLAG;
desc.arginfo = SCM_ARGS(5);
mutex_lock(&data->pgtbl_lock);
if (is_scm_armv8()) {
ret = scm_call2(SCM_SIP_FNID(SCM_SVC_MP,
IOMMU_SECURE_UNMAP2_FLAT), &desc);
if (!ret)
ret = len;
}
mutex_unlock(&data->pgtbl_lock);
return ret;
}
static phys_addr_t msm_secure_iova_to_phys(struct io_pgtable_ops *ops,
unsigned long iova)
{
return -EINVAL;
}
static struct msm_secure_io_pgtable *
msm_secure_alloc_pgtable_data(struct io_pgtable_cfg *cfg)
{
struct msm_secure_io_pgtable *data;
data = kmalloc(sizeof(*data), GFP_KERNEL);
if (!data)
return NULL;
data->iop.ops = (struct io_pgtable_ops) {
.map = msm_secure_map,
.map_sg = msm_secure_map_sg,
.unmap = msm_secure_unmap,
.iova_to_phys = msm_secure_iova_to_phys,
};
mutex_init(&data->pgtbl_lock);
return data;
}
static struct io_pgtable *
msm_secure_alloc_pgtable(struct io_pgtable_cfg *cfg, void *cookie)
{
struct msm_secure_io_pgtable *data =
msm_secure_alloc_pgtable_data(cfg);
return &data->iop;
}
static void msm_secure_free_pgtable(struct io_pgtable *iop)
{
struct msm_secure_io_pgtable *data = io_pgtable_to_data(iop);
kfree(data);
}
struct io_pgtable_init_fns io_pgtable_arm_msm_secure_init_fns = {
.alloc = msm_secure_alloc_pgtable,
.free = msm_secure_free_pgtable,
};