// SPDX-License-Identifier: GPL-2.0 /* Copyright (c) 2019 HiSilicon Limited. */ /* Copyright (c) 2025 Loongson Technology Corporation Limited. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #define SE_SEED_SIZE 32 struct loongson_rng_list { struct mutex lock; struct list_head list; int registered; }; struct loongson_rng { u32 used; struct loongson_se_engine *engine; struct list_head list; struct mutex lock; }; struct loongson_rng_ctx { struct loongson_rng *rng; }; struct loongson_rng_cmd { u32 cmd_id; union { u32 len; u32 ret; } u; u32 seed_off; u32 out_off; u32 pad[4]; }; static struct loongson_rng_list rng_devices = { .lock = __MUTEX_INITIALIZER(rng_devices.lock), .list = LIST_HEAD_INIT(rng_devices.list), }; static int loongson_rng_generate(struct crypto_rng *tfm, const u8 *src, unsigned int slen, u8 *dstn, unsigned int dlen) { struct loongson_rng_ctx *ctx = crypto_rng_ctx(tfm); struct loongson_rng *rng = ctx->rng; struct loongson_rng_cmd *cmd = rng->engine->command; int err, len; mutex_lock(&rng->lock); cmd->seed_off = 0; do { len = min(dlen, rng->engine->buffer_size); cmd = rng->engine->command; cmd->u.len = len; err = loongson_se_send_engine_cmd(rng->engine); if (err) break; cmd = rng->engine->command_ret; if (cmd->u.ret) { err = -EIO; break; } memcpy(dstn, rng->engine->data_buffer, len); dlen -= len; dstn += len; } while (dlen > 0); mutex_unlock(&rng->lock); return err; } static int loongson_rng_init(struct crypto_tfm *tfm) { struct loongson_rng_ctx *ctx = crypto_tfm_ctx(tfm); struct loongson_rng *rng; u32 min_used = U32_MAX; mutex_lock(&rng_devices.lock); list_for_each_entry(rng, &rng_devices.list, list) { if (rng->used < min_used) { ctx->rng = rng; min_used = rng->used; } } ctx->rng->used++; mutex_unlock(&rng_devices.lock); return 0; } static void loongson_rng_exit(struct crypto_tfm *tfm) { struct loongson_rng_ctx *ctx = crypto_tfm_ctx(tfm); mutex_lock(&rng_devices.lock); ctx->rng->used--; mutex_unlock(&rng_devices.lock); } static int loongson_rng_seed(struct crypto_rng *tfm, const u8 *seed, unsigned int slen) { struct loongson_rng_ctx *ctx = crypto_rng_ctx(tfm); struct loongson_rng *rng = ctx->rng; struct loongson_rng_cmd *cmd; int err; if (slen < SE_SEED_SIZE) return -EINVAL; slen = min(slen, rng->engine->buffer_size); mutex_lock(&rng->lock); cmd = rng->engine->command; cmd->u.len = slen; cmd->seed_off = rng->engine->buffer_off; memcpy(rng->engine->data_buffer, seed, slen); err = loongson_se_send_engine_cmd(rng->engine); if (err) goto out; cmd = rng->engine->command_ret; if (cmd->u.ret) err = -EIO; out: mutex_unlock(&rng->lock); return err; } static struct rng_alg loongson_rng_alg = { .generate = loongson_rng_generate, .seed = loongson_rng_seed, .seedsize = SE_SEED_SIZE, .base = { .cra_name = "stdrng", .cra_driver_name = "loongson_stdrng", .cra_priority = 300, .cra_ctxsize = sizeof(struct loongson_rng_ctx), .cra_module = THIS_MODULE, .cra_init = loongson_rng_init, .cra_exit = loongson_rng_exit, }, }; static int loongson_rng_probe(struct platform_device *pdev) { struct loongson_rng_cmd *cmd; struct loongson_rng *rng; int ret = 0; rng = devm_kzalloc(&pdev->dev, sizeof(*rng), GFP_KERNEL); if (!rng) return -ENOMEM; rng->engine = loongson_se_init_engine(pdev->dev.parent, SE_ENGINE_RNG); if (!rng->engine) return -ENODEV; cmd = rng->engine->command; cmd->cmd_id = SE_CMD_RNG; cmd->out_off = rng->engine->buffer_off; mutex_init(&rng->lock); mutex_lock(&rng_devices.lock); if (!rng_devices.registered) { ret = crypto_register_rng(&loongson_rng_alg); if (ret) { dev_err(&pdev->dev, "failed to register crypto(%d)\n", ret); goto out; } rng_devices.registered = 1; } list_add_tail(&rng->list, &rng_devices.list); out: mutex_unlock(&rng_devices.lock); return ret; } static struct platform_driver loongson_rng_driver = { .probe = loongson_rng_probe, .driver = { .name = "loongson-rng", }, }; module_platform_driver(loongson_rng_driver); MODULE_ALIAS("platform:loongson-rng"); MODULE_LICENSE("GPL"); MODULE_AUTHOR("Yinggang Gu "); MODULE_AUTHOR("Qunqin Zhao "); MODULE_DESCRIPTION("Loongson Random Number Generator driver");