| // SPDX-License-Identifier: GPL-2.0 |
| /* |
| * linux/drivers/char/hw_random/asr-rng.c - Random Number Generator driver |
| * |
| * 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/hw_random.h> |
| #include <linux/io.h> |
| #include <linux/slab.h> |
| #include <linux/sched.h> |
| #include <linux/fs.h> |
| #ifdef CONFIG_TEE |
| #include <linux/tee_drv.h> |
| #endif |
| |
| #include "asr-rng-optee.h" |
| |
| static struct teec_uuid pta_rng_uuid = ASR_RNG_ACCESS_UUID; |
| |
| static void asrrng_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 asrrng_tee_match_cb(struct tee_ioctl_version_data *ver, const void *data) |
| { |
| return 1; |
| } |
| |
| static int asrrng_optee_open_ta(struct asrrng_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, asrrng_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)); |
| asrrng_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 asrrng_optee_close_ta(struct asrrng_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; |
| } |
| |
| static int asrrng_optee_acquire_ta(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 asrrng_tee_context asrrng_tee_ctx; |
| struct tee_shm *shm; |
| int ret = 0; |
| size_t size; |
| char *ma = NULL; |
| |
| ret = asrrng_optee_open_ta(&asrrng_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 = asrrng_tee_ctx.session; |
| invoke_arg.num_params = 1; |
| |
| shm = tee_shm_alloc(asrrng_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(asrrng_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: |
| asrrng_optee_close_ta(&asrrng_tee_ctx); |
| return ret; |
| } |
| |
| static int asr_rng_read(struct hwrng *rng, void *data, size_t max, bool wait) |
| { |
| int ret = 0; |
| size_t readsize; |
| size_t size = max < 4096 ? max : 4096; |
| |
| (void)wait; |
| |
| ret = asrrng_optee_acquire_ta(&pta_rng_uuid, ASR_RNG_GET_DATA, |
| data, size, &readsize); |
| |
| if (!ret) |
| return readsize; |
| |
| return 0; |
| } |
| |
| static int asr_rng_probe(struct platform_device *pdev) |
| { |
| struct asr_rng *prng; |
| struct device *dev = &pdev->dev; |
| int err = 0; |
| |
| prng = devm_kzalloc(&pdev->dev, sizeof(*prng), GFP_KERNEL); |
| if (prng == NULL) |
| return -ENOMEM; |
| |
| prng->dev = dev; |
| platform_set_drvdata(pdev, prng); |
| |
| prng->rng.name = "asr"; |
| prng->rng.read = asr_rng_read; |
| prng->rng.quality = 1000; |
| |
| err = hwrng_register(&prng->rng); |
| if (err) { |
| dev_err(dev, "failed to register asr_rng!\n"); |
| goto res_err; |
| } |
| |
| dev_info(dev, "H/W RNG is initialized\n"); |
| return 0; |
| |
| res_err: |
| devm_kfree(dev, prng); |
| dev_err(dev, "initialization failed.\n"); |
| |
| return err; |
| } |
| |
| static int asr_rng_remove(struct platform_device *pdev) |
| { |
| struct asr_rng *prng; |
| |
| prng = platform_get_drvdata(pdev); |
| if (!prng) { |
| return -ENODEV; |
| } |
| hwrng_unregister(&prng->rng); |
| |
| devm_kfree(prng->dev, prng); |
| |
| return 0; |
| } |
| |
| #if defined(CONFIG_OF) |
| static const struct of_device_id asr_rng_dt_ids[] = { |
| { .compatible = "asr,asr-hwrng" }, |
| { /* sentinel */ } |
| }; |
| MODULE_DEVICE_TABLE(of, asr_rng_dt_ids); |
| #endif |
| |
| static struct platform_driver asr_rng_driver = { |
| .probe = asr_rng_probe, |
| .remove = asr_rng_remove, |
| .driver = { |
| .name = "asr_rng", |
| .of_match_table = of_match_ptr(asr_rng_dt_ids), |
| }, |
| }; |
| |
| static int __init asr_random_init(void) |
| { |
| int ret; |
| |
| ret = platform_driver_register(&asr_rng_driver); |
| |
| return ret; |
| } |
| |
| device_initcall_sync(asr_random_init); |
| |
| |
| MODULE_LICENSE("GPL v2"); |
| MODULE_AUTHOR("Yu Zhang <yuzhang@asrmicro.com>"); |
| MODULE_AUTHOR("Wang Yonggan <wangyonggan@asrmicro.com>"); |
| MODULE_DESCRIPTION("ASR H/W RNG driver with optee-os"); |