You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
360 lines
8.6 KiB
360 lines
8.6 KiB
/* 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)
|
|
{
|
|
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;
|
|
|
|
if (is_scm_armv8()) {
|
|
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");
|
|
return ret;
|
|
}
|
|
}
|
|
|
|
/* 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, 1);
|
|
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]);
|
|
return -ENOMEM;
|
|
}
|
|
|
|
if (is_scm_armv8()) {
|
|
struct scm_desc desc = {0};
|
|
|
|
desc.args[0] = paddr;
|
|
desc.args[1] = psize[0];
|
|
desc.args[2] = 0;
|
|
desc.arginfo = SCM_ARGS(3, SCM_RW, SCM_VAL, SCM_VAL);
|
|
|
|
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");
|
|
return ret;
|
|
}
|
|
|
|
if (ptbl_ret) {
|
|
pr_err("scm call IOMMU_SECURE_PTBL_INIT extended ret fail\n");
|
|
return ret;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
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,
|
|
};
|
|
|