summaryrefslogtreecommitdiffstats
path: root/drivers/hw_random/core.c
blob: 86214dc8bae74c06d967a6c7d2452e8bf2dcc196 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
/*
 * Copyright (c) 2016 Pengutronix, Steffen Trumtrar <kernel@pengutronix.de>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 2
 * as published by the Free Software Foundation.
 *
 * derived from Linux kernel drivers/char/hw_random/core.c
 */

#include <common.h>
#include <linux/hw_random.h>
#include <malloc.h>

static LIST_HEAD(hwrngs);

#define RNG_BUFFER_SIZE		32

int hwrng_get_data(struct hwrng *rng, void *buffer, size_t size, int wait)
{
	return rng->read(rng, buffer, size, wait);
}

static int hwrng_init(struct hwrng *rng)
{
	int ret = 0;

	if (rng->init)
		ret = rng->init(rng);

	if (!ret)
		list_add_tail(&rng->list, &hwrngs);

	return ret;
}

static ssize_t rng_dev_read(struct cdev *cdev, void *buf, size_t size,
			    loff_t offset, unsigned long flags)
{
	struct hwrng *rng = container_of(cdev, struct hwrng, cdev);
	size_t count = size;
	ssize_t cur = 0;
	int len;

	memset(buf, 0, size);

	while (count) {
		int max = min(count, (size_t)RNG_BUFFER_SIZE);
		len = hwrng_get_data(rng, rng->buf, max, true);
		if (len < 0) {
			cur = len;
			break;
		}

		memcpy(buf + cur, rng->buf, len);

		count -= len;
		cur += len;
	}

	return cur;
}

static struct cdev_operations rng_chrdev_ops = {
	.read  = rng_dev_read,
};

static int hwrng_register_cdev(struct hwrng *rng)
{
	struct device_d *dev = rng->dev;
	const char *alias;
	char *devname;
	int err;

	alias = of_alias_get(dev->device_node);
	if (alias) {
		devname = xstrdup(alias);
	} else {
		err = cdev_find_free_index("hwrng");
		if (err < 0) {
			dev_err(dev, "no index found to name device\n");
			return err;
		}
		devname = xasprintf("hwrng%d", err);
	}

	rng->cdev.name = devname;
	rng->cdev.flags = DEVFS_IS_CHARACTER_DEV;
	rng->cdev.ops = &rng_chrdev_ops;
	rng->cdev.dev = rng->dev;

	return devfs_create(&rng->cdev);
}

static void hwrng_unregister_cdev(struct hwrng *rng)
{
	devfs_remove(&rng->cdev);
	free(rng->cdev.name);
}

struct hwrng *hwrng_get_first(void)
{
	if (list_empty(&hwrngs))
		return ERR_PTR(-ENODEV);
	else
		return list_first_entry(&hwrngs, struct hwrng, list);
}

int hwrng_register(struct device_d *dev, struct hwrng *rng)
{
	int err;

	if (rng->name == NULL || rng->read == NULL)
		return -EINVAL;

	rng->buf = xzalloc(RNG_BUFFER_SIZE);
	rng->dev = dev;

	err = hwrng_init(rng);
	if (err) {
		free(rng->buf);
		return err;
	}

	err = hwrng_register_cdev(rng);
	if (err)
		free(rng->buf);

	return err;
}

void hwrng_unregister(struct hwrng *rng)
{
	hwrng_unregister_cdev(rng);
	free(rng->buf);
}