// SPDX-License-Identifier: GPL-2.0 | |
/* | |
* Copyright (C) 2023 ASR Micro Limited | |
* | |
*/ | |
#include <linux/module.h> | |
#include <linux/kernel.h> | |
#include <linux/platform_device.h> | |
#include <linux/of.h> | |
#include <linux/clk.h> | |
#include <linux/io.h> | |
#include <linux/slab.h> | |
#include <linux/miscdevice.h> | |
#include <linux/sched.h> | |
#include <linux/fs.h> | |
#include <linux/uaccess.h> | |
#ifdef CONFIG_TEE | |
#include <linux/tee_drv.h> | |
#endif | |
#include <crypto/scatterwalk.h> | |
#include <linux/cputype.h> | |
#include <uapi/linux/geu_ioctl.h> | |
#include "asr-geu-optee.h" | |
static void asrgeu_uuid_to_octets(uint8_t d[TEE_IOCTL_UUID_LEN], struct teec_uuid *s) | |
{ | |
d[0] = s->timeLow >> 24; | |
d[1] = s->timeLow >> 16; | |
d[2] = s->timeLow >> 8; | |
d[3] = s->timeLow; | |
d[4] = s->timeMid >> 8; | |
d[5] = s->timeMid; | |
d[6] = s->timeHiAndVersion >> 8; | |
d[7] = s->timeHiAndVersion; | |
memcpy(d + 8, s->clockSeqAndNode, sizeof(s->clockSeqAndNode)); | |
} | |
static int asrgeu_tee_match_cb(struct tee_ioctl_version_data *ver, const void *data) | |
{ | |
return 1; | |
} | |
static int asrgeu_optee_open_ta(struct asrgeu_tee_context *ctx, struct teec_uuid *uuid) | |
{ | |
struct tee_ioctl_open_session_arg open_session_arg; | |
int ret; | |
if (ctx == NULL) | |
return -EINVAL; | |
ctx->session = 0; | |
ctx->tee_ctx = tee_client_open_context(NULL, asrgeu_tee_match_cb, NULL, NULL); | |
if (IS_ERR(ctx->tee_ctx)) { | |
ret = PTR_ERR(ctx->tee_ctx); | |
ctx->tee_ctx = NULL; | |
return ret; | |
} | |
memset(&open_session_arg, 0x0, sizeof(struct tee_ioctl_open_session_arg)); | |
asrgeu_uuid_to_octets(open_session_arg.uuid, uuid); | |
open_session_arg.clnt_login = TEE_IOCTL_LOGIN_PUBLIC; | |
open_session_arg.num_params = 0; | |
ret = tee_client_open_session(ctx->tee_ctx, &open_session_arg, NULL); | |
if (ret != 0) { | |
goto err_exit; | |
} else if (open_session_arg.ret != 0) { | |
ret = -EIO; | |
goto err_exit; | |
} | |
ctx->session = open_session_arg.session; | |
return ret; | |
err_exit: | |
tee_client_close_context(ctx->tee_ctx); | |
ctx->tee_ctx = NULL; | |
return ret; | |
} | |
static int asrgeu_optee_close_ta(struct asrgeu_tee_context *ctx) | |
{ | |
int ret; | |
if (ctx == NULL) | |
return -EINVAL; | |
ret = tee_client_close_session(ctx->tee_ctx, ctx->session); | |
tee_client_close_context(ctx->tee_ctx); | |
return ret; | |
} | |
int asrgeu_optee_acquire_ta_data(struct teec_uuid *uuid, u32 cmd, u32 *data) | |
{ | |
struct tee_ioctl_invoke_arg invoke_arg; | |
struct tee_param params[1]; | |
struct asrgeu_tee_context asrgeu_tee_ctx; | |
int ret = 0; | |
ret = asrgeu_optee_open_ta(&asrgeu_tee_ctx, uuid); | |
if (ret != 0) { | |
return ret; | |
} | |
memset(&invoke_arg, 0x0, sizeof(struct tee_ioctl_invoke_arg)); | |
invoke_arg.func = cmd; | |
invoke_arg.session = asrgeu_tee_ctx.session; | |
invoke_arg.num_params = 1; | |
params[0].attr = TEE_IOCTL_PARAM_ATTR_TYPE_VALUE_OUTPUT; | |
params[0].u.value.a = 0; | |
params[0].u.value.b = 0; | |
params[0].u.value.c = 0; | |
ret = tee_client_invoke_func(asrgeu_tee_ctx.tee_ctx, &invoke_arg, params); | |
if (ret != 0) { | |
goto exit; | |
} else if (invoke_arg.ret != 0) { | |
ret = -EIO; | |
goto exit; | |
} | |
*data = params[0].u.value.a; | |
exit: | |
asrgeu_optee_close_ta(&asrgeu_tee_ctx); | |
return ret; | |
} | |
int asrgeu_optee_acquire_ta_buff(struct teec_uuid *uuid, u32 cmd, void *buff, size_t len, size_t *outlen) | |
{ | |
struct tee_ioctl_invoke_arg invoke_arg; | |
struct tee_param params; | |
struct asrgeu_tee_context asrgeu_tee_ctx; | |
struct tee_shm *shm; | |
int ret = 0; | |
size_t size; | |
char *ma = NULL; | |
ret = asrgeu_optee_open_ta(&asrgeu_tee_ctx, uuid); | |
if (ret != 0) { | |
return ret; | |
} | |
memset(&invoke_arg, 0x0, sizeof(struct tee_ioctl_invoke_arg)); | |
invoke_arg.func = cmd; | |
invoke_arg.session = asrgeu_tee_ctx.session; | |
invoke_arg.num_params = 1; | |
shm = tee_shm_alloc(asrgeu_tee_ctx.tee_ctx, len, TEE_SHM_MAPPED | TEE_SHM_DMA_BUF); | |
if (!shm) { | |
ret = -EINVAL; | |
goto exit; | |
} | |
params.attr = TEE_IOCTL_PARAM_ATTR_TYPE_MEMREF_OUTPUT; | |
params.u.memref.shm_offs = 0; | |
params.u.memref.size = len; | |
params.u.memref.shm = shm; | |
ret = tee_client_invoke_func(asrgeu_tee_ctx.tee_ctx, &invoke_arg, ¶ms); | |
if (ret != 0) { | |
goto free_shm; | |
} else if (invoke_arg.ret != 0) { | |
ret = -EIO; | |
goto free_shm; | |
} | |
size = (params.u.memref.size > len) ? (int)len: (int)params.u.memref.size; | |
ma = tee_shm_get_va(shm, 0); | |
memcpy(buff, ma, size); | |
if (outlen) | |
*outlen = size; | |
free_shm: | |
tee_shm_free(shm); | |
exit: | |
asrgeu_optee_close_ta(&asrgeu_tee_ctx); | |
return ret; | |
} | |
int asrgeu_optee_aes_acquire_ta_dma(struct teec_uuid *uuid, u32 cmd, | |
struct scatterlist *src, struct scatterlist *dst, | |
size_t srclen, size_t dstlen, | |
u32 para_a, u32 para_b, | |
u8 *parabuf, u32 paralen) | |
{ | |
struct tee_ioctl_invoke_arg invoke_arg; | |
struct tee_param params[4]; | |
struct asrgeu_tee_context asrgeu_tee_ctx; | |
struct tee_shm *shm; | |
int ret = 0; | |
char *ma = NULL; | |
ret = asrgeu_optee_open_ta(&asrgeu_tee_ctx, uuid); | |
if (ret != 0) { | |
return ret; | |
} | |
memset(&invoke_arg, 0x0, sizeof(struct tee_ioctl_invoke_arg)); | |
invoke_arg.func = cmd; | |
invoke_arg.session = asrgeu_tee_ctx.session; | |
shm = tee_shm_alloc(asrgeu_tee_ctx.tee_ctx, srclen + dstlen + paralen, TEE_SHM_MAPPED | TEE_SHM_DMA_BUF); | |
if (!shm) { | |
ret = -EINVAL; | |
goto exit; | |
} | |
params[0].attr = TEE_IOCTL_PARAM_ATTR_TYPE_MEMREF_INPUT; | |
params[0].u.memref.shm_offs = 0; | |
params[0].u.memref.size = srclen; | |
params[0].u.memref.shm = shm; | |
params[1].attr = TEE_IOCTL_PARAM_ATTR_TYPE_MEMREF_OUTPUT; | |
params[1].u.memref.shm_offs = srclen; | |
params[1].u.memref.size = dstlen; | |
params[1].u.memref.shm = shm; | |
params[2].attr = TEE_IOCTL_PARAM_ATTR_TYPE_VALUE_INPUT; | |
params[2].u.value.a = para_a; | |
params[2].u.value.b = para_b; | |
params[2].u.value.c = 0; | |
ma = tee_shm_get_va(shm, 0); | |
sg_copy_to_buffer(src, sg_nents(src), ma, srclen); | |
if (parabuf && paralen) { | |
params[3].attr = TEE_IOCTL_PARAM_ATTR_TYPE_MEMREF_INPUT; | |
params[3].u.memref.shm_offs = srclen + dstlen; | |
params[3].u.memref.size = paralen; | |
params[3].u.memref.shm = shm; | |
memcpy(ma + srclen + dstlen, parabuf, paralen); | |
invoke_arg.num_params = 4; | |
} else { | |
invoke_arg.num_params = 3; | |
} | |
ret = tee_client_invoke_func(asrgeu_tee_ctx.tee_ctx, &invoke_arg, params); | |
if (ret != 0) { | |
goto free_shm; | |
} else if (invoke_arg.ret != 0) { | |
ret = -EIO; | |
goto free_shm; | |
} | |
sg_copy_from_buffer(dst, sg_nents(dst), ma + srclen, dstlen); | |
free_shm: | |
tee_shm_free(shm); | |
exit: | |
asrgeu_optee_close_ta(&asrgeu_tee_ctx); | |
return ret; | |
} | |
#if defined(CONFIG_OF) | |
static const struct of_device_id asr_geu_dt_ids[] = { | |
{ .compatible = "asr,asr-geu" }, | |
{ /* sentinel */ } | |
}; | |
MODULE_DEVICE_TABLE(of, asr_geu_dt_ids); | |
#endif | |
static int asr_geu_probe(struct platform_device *pdev) | |
{ | |
struct asr_geu_dev *geu_dd; | |
struct device *dev = &pdev->dev; | |
struct device_node *np = NULL; | |
int err = 0, devnum = 0; | |
geu_dd = devm_kzalloc(&pdev->dev, sizeof(*geu_dd), GFP_KERNEL); | |
if (geu_dd == NULL) { | |
err = -ENOMEM; | |
goto res_err; | |
} | |
np = dev->of_node; | |
geu_dd->dev = dev; | |
platform_set_drvdata(pdev, geu_dd); | |
#ifdef CONFIG_ASR_FUSE | |
if (of_get_property(np, "asr,asr-fuse", NULL)) { | |
err = asr_geu_fuse_register(geu_dd); | |
if (err) | |
goto no_dev_error; | |
dev_info(dev, "Fuse is initialized\n"); | |
devnum ++; | |
} | |
#endif | |
#ifdef CONFIG_ASR_RNG | |
if (of_get_property(np, "asr,asr-hwrng", NULL)) { | |
err = asr_geu_rng_register(geu_dd); | |
if (err) | |
goto rng_error; | |
dev_info(dev, "H/W RNG is initialized\n"); | |
devnum ++; | |
} | |
#endif | |
#ifdef CONFIG_ASR_AES | |
if (of_get_property(np, "asr,asr-aes", NULL)) { | |
if (!cpu_is_asr1903_b0()) { | |
err = asr_geu_aes_register(geu_dd); | |
if (err) | |
goto aes_error; | |
dev_info(dev, "AES engine is initialized\n"); | |
devnum ++; | |
} | |
} | |
#endif | |
if (!devnum) { | |
dev_err(dev, "No GEU device enabled\n"); | |
err = -ENODEV; | |
goto no_dev_error; | |
} | |
return 0; | |
aes_error: | |
#ifdef CONFIG_ASR_RNG | |
asr_geu_rng_unregister(geu_dd); | |
#endif | |
rng_error: | |
#ifdef CONFIG_ASR_FUSE | |
asr_geu_fuse_unregister(geu_dd); | |
#endif | |
no_dev_error: | |
devm_kfree(dev, geu_dd); | |
res_err: | |
dev_err(dev, "initialization failed.\n"); | |
return err; | |
} | |
static int asr_geu_remove(struct platform_device *pdev) | |
{ | |
struct asr_geu_dev *geu_dd; | |
geu_dd = platform_get_drvdata(pdev); | |
if (!geu_dd) | |
return -ENODEV; | |
#ifdef CONFIG_ASR_RNG | |
asr_geu_rng_unregister(geu_dd); | |
#endif | |
#ifdef CONFIG_ASR_FUSE | |
asr_geu_fuse_unregister(geu_dd); | |
#endif | |
#ifdef CONFIG_ASR_AES | |
asr_geu_aes_unregister(geu_dd); | |
#endif | |
return 0; | |
} | |
static struct platform_driver asr_geu_driver = { | |
.probe = asr_geu_probe, | |
.remove = asr_geu_remove, | |
.driver = { | |
.name = "asr_geu", | |
.of_match_table = of_match_ptr(asr_geu_dt_ids), | |
}, | |
}; | |
static int __init asr_geu_init(void) | |
{ | |
int ret; | |
ret = platform_driver_register(&asr_geu_driver); | |
return ret; | |
} | |
device_initcall_sync(asr_geu_init); | |
MODULE_DESCRIPTION("ASR Generic Encryption Unit support."); | |
MODULE_LICENSE("GPL v2"); | |
MODULE_AUTHOR("Yu Zhang"); |