Contributors: 1
Author Tokens Token Proportion Commits Commit Proportion
Zhao Qunqin 944 100.00% 1 100.00%
Total 944 1


// SPDX-License-Identifier: GPL-2.0
/* Copyright (c) 2019 HiSilicon Limited. */
/* Copyright (c) 2025 Loongson Technology Corporation Limited. */

#include <linux/crypto.h>
#include <linux/err.h>
#include <linux/hw_random.h>
#include <linux/io.h>
#include <linux/iopoll.h>
#include <linux/kernel.h>
#include <linux/list.h>
#include <linux/mfd/loongson-se.h>
#include <linux/module.h>
#include <linux/mutex.h>
#include <linux/platform_device.h>
#include <linux/random.h>
#include <crypto/internal/rng.h>

#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 <guyinggang@loongson.cn>");
MODULE_AUTHOR("Qunqin Zhao <zhaoqunqin@loongson.cn>");
MODULE_DESCRIPTION("Loongson Random Number Generator driver");