brintos

brintos / linux-shallow public Read only

0
0
Text · 9.2 KiB · 0dd8baf Raw
400 lines · c
1// SPDX-License-Identifier: GPL-2.02/*3 * exynos-rng.c - Random Number Generator driver for the Exynos4 *5 * Copyright (c) 2017 Krzysztof Kozlowski <krzk@kernel.org>6 *7 * Loosely based on old driver from drivers/char/hw_random/exynos-rng.c:8 * Copyright (C) 2012 Samsung Electronics9 * Jonghwa Lee <jonghwa3.lee@samsung.com>10 */11 12#include <linux/clk.h>13#include <linux/crypto.h>14#include <linux/err.h>15#include <linux/io.h>16#include <linux/module.h>17#include <linux/mutex.h>18#include <linux/of.h>19#include <linux/platform_device.h>20 21#include <crypto/internal/rng.h>22 23#define EXYNOS_RNG_CONTROL		0x024#define EXYNOS_RNG_STATUS		0x1025 26#define EXYNOS_RNG_SEED_CONF		0x1427#define EXYNOS_RNG_GEN_PRNG	        BIT(1)28 29#define EXYNOS_RNG_SEED_BASE		0x14030#define EXYNOS_RNG_SEED(n)		(EXYNOS_RNG_SEED_BASE + (n * 0x4))31#define EXYNOS_RNG_OUT_BASE		0x16032#define EXYNOS_RNG_OUT(n)		(EXYNOS_RNG_OUT_BASE + (n * 0x4))33 34/* EXYNOS_RNG_CONTROL bit fields */35#define EXYNOS_RNG_CONTROL_START	0x1836/* EXYNOS_RNG_STATUS bit fields */37#define EXYNOS_RNG_STATUS_SEED_SETTING_DONE	BIT(1)38#define EXYNOS_RNG_STATUS_RNG_DONE		BIT(5)39 40/* Five seed and output registers, each 4 bytes */41#define EXYNOS_RNG_SEED_REGS		542#define EXYNOS_RNG_SEED_SIZE		(EXYNOS_RNG_SEED_REGS * 4)43 44enum exynos_prng_type {45	EXYNOS_PRNG_UNKNOWN = 0,46	EXYNOS_PRNG_EXYNOS4,47	EXYNOS_PRNG_EXYNOS5,48};49 50/*51 * Driver re-seeds itself with generated random numbers to hinder52 * backtracking of the original seed.53 *54 * Time for next re-seed in ms.55 */56#define EXYNOS_RNG_RESEED_TIME		100057#define EXYNOS_RNG_RESEED_BYTES		6553658 59/*60 * In polling mode, do not wait infinitely for the engine to finish the work.61 */62#define EXYNOS_RNG_WAIT_RETRIES		10063 64/* Context for crypto */65struct exynos_rng_ctx {66	struct exynos_rng_dev		*rng;67};68 69/* Device associated memory */70struct exynos_rng_dev {71	struct device			*dev;72	enum exynos_prng_type		type;73	void __iomem			*mem;74	struct clk			*clk;75	struct mutex 			lock;76	/* Generated numbers stored for seeding during resume */77	u8				seed_save[EXYNOS_RNG_SEED_SIZE];78	unsigned int			seed_save_len;79	/* Time of last seeding in jiffies */80	unsigned long			last_seeding;81	/* Bytes generated since last seeding */82	unsigned long			bytes_seeding;83};84 85static struct exynos_rng_dev *exynos_rng_dev;86 87static u32 exynos_rng_readl(struct exynos_rng_dev *rng, u32 offset)88{89	return readl_relaxed(rng->mem + offset);90}91 92static void exynos_rng_writel(struct exynos_rng_dev *rng, u32 val, u32 offset)93{94	writel_relaxed(val, rng->mem + offset);95}96 97static int exynos_rng_set_seed(struct exynos_rng_dev *rng,98			       const u8 *seed, unsigned int slen)99{100	u32 val;101	int i;102 103	/* Round seed length because loop iterates over full register size */104	slen = ALIGN_DOWN(slen, 4);105 106	if (slen < EXYNOS_RNG_SEED_SIZE)107		return -EINVAL;108 109	for (i = 0; i < slen ; i += 4) {110		unsigned int seed_reg = (i / 4) % EXYNOS_RNG_SEED_REGS;111 112		val = seed[i] << 24;113		val |= seed[i + 1] << 16;114		val |= seed[i + 2] << 8;115		val |= seed[i + 3] << 0;116 117		exynos_rng_writel(rng, val, EXYNOS_RNG_SEED(seed_reg));118	}119 120	val = exynos_rng_readl(rng, EXYNOS_RNG_STATUS);121	if (!(val & EXYNOS_RNG_STATUS_SEED_SETTING_DONE)) {122		dev_warn(rng->dev, "Seed setting not finished\n");123		return -EIO;124	}125 126	rng->last_seeding = jiffies;127	rng->bytes_seeding = 0;128 129	return 0;130}131 132/*133 * Start the engine and poll for finish.  Then read from output registers134 * filling the 'dst' buffer up to 'dlen' bytes or up to size of generated135 * random data (EXYNOS_RNG_SEED_SIZE).136 *137 * On success: return 0 and store number of read bytes under 'read' address.138 * On error: return -ERRNO.139 */140static int exynos_rng_get_random(struct exynos_rng_dev *rng,141				 u8 *dst, unsigned int dlen,142				 unsigned int *read)143{144	int retry = EXYNOS_RNG_WAIT_RETRIES;145 146	if (rng->type == EXYNOS_PRNG_EXYNOS4) {147		exynos_rng_writel(rng, EXYNOS_RNG_CONTROL_START,148				  EXYNOS_RNG_CONTROL);149	} else if (rng->type == EXYNOS_PRNG_EXYNOS5) {150		exynos_rng_writel(rng, EXYNOS_RNG_GEN_PRNG,151				  EXYNOS_RNG_SEED_CONF);152	}153 154	while (!(exynos_rng_readl(rng,155			EXYNOS_RNG_STATUS) & EXYNOS_RNG_STATUS_RNG_DONE) && --retry)156		cpu_relax();157 158	if (!retry)159		return -ETIMEDOUT;160 161	/* Clear status bit */162	exynos_rng_writel(rng, EXYNOS_RNG_STATUS_RNG_DONE,163			  EXYNOS_RNG_STATUS);164	*read = min_t(size_t, dlen, EXYNOS_RNG_SEED_SIZE);165	memcpy_fromio(dst, rng->mem + EXYNOS_RNG_OUT_BASE, *read);166	rng->bytes_seeding += *read;167 168	return 0;169}170 171/* Re-seed itself from time to time */172static void exynos_rng_reseed(struct exynos_rng_dev *rng)173{174	unsigned long next_seeding = rng->last_seeding + \175				     msecs_to_jiffies(EXYNOS_RNG_RESEED_TIME);176	unsigned long now = jiffies;177	unsigned int read = 0;178	u8 seed[EXYNOS_RNG_SEED_SIZE];179 180	if (time_before(now, next_seeding) &&181	    rng->bytes_seeding < EXYNOS_RNG_RESEED_BYTES)182		return;183 184	if (exynos_rng_get_random(rng, seed, sizeof(seed), &read))185		return;186 187	exynos_rng_set_seed(rng, seed, read);188 189	/* Let others do some of their job. */190	mutex_unlock(&rng->lock);191	mutex_lock(&rng->lock);192}193 194static int exynos_rng_generate(struct crypto_rng *tfm,195			       const u8 *src, unsigned int slen,196			       u8 *dst, unsigned int dlen)197{198	struct exynos_rng_ctx *ctx = crypto_rng_ctx(tfm);199	struct exynos_rng_dev *rng = ctx->rng;200	unsigned int read = 0;201	int ret;202 203	ret = clk_prepare_enable(rng->clk);204	if (ret)205		return ret;206 207	mutex_lock(&rng->lock);208	do {209		ret = exynos_rng_get_random(rng, dst, dlen, &read);210		if (ret)211			break;212 213		dlen -= read;214		dst += read;215 216		exynos_rng_reseed(rng);217	} while (dlen > 0);218	mutex_unlock(&rng->lock);219 220	clk_disable_unprepare(rng->clk);221 222	return ret;223}224 225static int exynos_rng_seed(struct crypto_rng *tfm, const u8 *seed,226			   unsigned int slen)227{228	struct exynos_rng_ctx *ctx = crypto_rng_ctx(tfm);229	struct exynos_rng_dev *rng = ctx->rng;230	int ret;231 232	ret = clk_prepare_enable(rng->clk);233	if (ret)234		return ret;235 236	mutex_lock(&rng->lock);237	ret = exynos_rng_set_seed(ctx->rng, seed, slen);238	mutex_unlock(&rng->lock);239 240	clk_disable_unprepare(rng->clk);241 242	return ret;243}244 245static int exynos_rng_kcapi_init(struct crypto_tfm *tfm)246{247	struct exynos_rng_ctx *ctx = crypto_tfm_ctx(tfm);248 249	ctx->rng = exynos_rng_dev;250 251	return 0;252}253 254static struct rng_alg exynos_rng_alg = {255	.generate		= exynos_rng_generate,256	.seed			= exynos_rng_seed,257	.seedsize		= EXYNOS_RNG_SEED_SIZE,258	.base			= {259		.cra_name		= "stdrng",260		.cra_driver_name	= "exynos_rng",261		.cra_priority		= 300,262		.cra_ctxsize		= sizeof(struct exynos_rng_ctx),263		.cra_module		= THIS_MODULE,264		.cra_init		= exynos_rng_kcapi_init,265	}266};267 268static int exynos_rng_probe(struct platform_device *pdev)269{270	struct exynos_rng_dev *rng;271	int ret;272 273	if (exynos_rng_dev)274		return -EEXIST;275 276	rng = devm_kzalloc(&pdev->dev, sizeof(*rng), GFP_KERNEL);277	if (!rng)278		return -ENOMEM;279 280	rng->type = (uintptr_t)of_device_get_match_data(&pdev->dev);281 282	mutex_init(&rng->lock);283 284	rng->dev = &pdev->dev;285	rng->clk = devm_clk_get(&pdev->dev, "secss");286	if (IS_ERR(rng->clk)) {287		dev_err(&pdev->dev, "Couldn't get clock.\n");288		return PTR_ERR(rng->clk);289	}290 291	rng->mem = devm_platform_ioremap_resource(pdev, 0);292	if (IS_ERR(rng->mem))293		return PTR_ERR(rng->mem);294 295	platform_set_drvdata(pdev, rng);296 297	exynos_rng_dev = rng;298 299	ret = crypto_register_rng(&exynos_rng_alg);300	if (ret) {301		dev_err(&pdev->dev,302			"Couldn't register rng crypto alg: %d\n", ret);303		exynos_rng_dev = NULL;304	}305 306	return ret;307}308 309static void exynos_rng_remove(struct platform_device *pdev)310{311	crypto_unregister_rng(&exynos_rng_alg);312 313	exynos_rng_dev = NULL;314}315 316static int __maybe_unused exynos_rng_suspend(struct device *dev)317{318	struct exynos_rng_dev *rng = dev_get_drvdata(dev);319	int ret;320 321	/* If we were never seeded then after resume it will be the same */322	if (!rng->last_seeding)323		return 0;324 325	rng->seed_save_len = 0;326	ret = clk_prepare_enable(rng->clk);327	if (ret)328		return ret;329 330	mutex_lock(&rng->lock);331 332	/* Get new random numbers and store them for seeding on resume. */333	exynos_rng_get_random(rng, rng->seed_save, sizeof(rng->seed_save),334			      &(rng->seed_save_len));335 336	mutex_unlock(&rng->lock);337 338	dev_dbg(rng->dev, "Stored %u bytes for seeding on system resume\n",339		rng->seed_save_len);340 341	clk_disable_unprepare(rng->clk);342 343	return 0;344}345 346static int __maybe_unused exynos_rng_resume(struct device *dev)347{348	struct exynos_rng_dev *rng = dev_get_drvdata(dev);349	int ret;350 351	/* Never seeded so nothing to do */352	if (!rng->last_seeding)353		return 0;354 355	ret = clk_prepare_enable(rng->clk);356	if (ret)357		return ret;358 359	mutex_lock(&rng->lock);360 361	ret = exynos_rng_set_seed(rng, rng->seed_save, rng->seed_save_len);362 363	mutex_unlock(&rng->lock);364 365	clk_disable_unprepare(rng->clk);366 367	return ret;368}369 370static SIMPLE_DEV_PM_OPS(exynos_rng_pm_ops, exynos_rng_suspend,371			 exynos_rng_resume);372 373static const struct of_device_id exynos_rng_dt_match[] = {374	{375		.compatible = "samsung,exynos4-rng",376		.data = (const void *)EXYNOS_PRNG_EXYNOS4,377	}, {378		.compatible = "samsung,exynos5250-prng",379		.data = (const void *)EXYNOS_PRNG_EXYNOS5,380	},381	{ },382};383MODULE_DEVICE_TABLE(of, exynos_rng_dt_match);384 385static struct platform_driver exynos_rng_driver = {386	.driver		= {387		.name	= "exynos-rng",388		.pm	= &exynos_rng_pm_ops,389		.of_match_table = exynos_rng_dt_match,390	},391	.probe		= exynos_rng_probe,392	.remove_new	= exynos_rng_remove,393};394 395module_platform_driver(exynos_rng_driver);396 397MODULE_DESCRIPTION("Exynos H/W Random Number Generator driver");398MODULE_AUTHOR("Krzysztof Kozlowski <krzk@kernel.org>");399MODULE_LICENSE("GPL v2");400