Home
Reading
Searching
Subscribe
Sponsors
Statistics
Posting
Contact
Spam
Lists
Links
About
Hosting
Filtering
Features Download
Marketing
Archives
FAQ
Blog
 
Gmane
From: David Miller <davem <at> davemloft.net>
Subject: [PATCH 2/2]: sparc64: Add Niagara2 RNG driver.
Newsgroups: gmane.linux.ports.sparc
Date: Friday 16th May 2008 23:53:54 UTC (over 9 years ago)
Signed-off-by: David S. Miller 
---
 drivers/char/hw_random/Kconfig  |   13 +
 drivers/char/hw_random/Makefile |    2 +
 drivers/char/hw_random/n2-asm.S |   93 +++++
 drivers/char/hw_random/n2-drv.c |  771
+++++++++++++++++++++++++++++++++++++++
 drivers/char/hw_random/n2rng.h  |  118 ++++++
 5 files changed, 997 insertions(+), 0 deletions(-)
 create mode 100644 drivers/char/hw_random/n2-asm.S
 create mode 100644 drivers/char/hw_random/n2-drv.c
 create mode 100644 drivers/char/hw_random/n2rng.h

diff --git a/drivers/char/hw_random/Kconfig
b/drivers/char/hw_random/Kconfig
index 8d6c208..f4aecb6 100644
--- a/drivers/char/hw_random/Kconfig
+++ b/drivers/char/hw_random/Kconfig
@@ -59,6 +59,19 @@ config HW_RANDOM_GEODE
 
 	  If unsure, say Y.
 
+config HW_RANDOM_N2RNG
+	tristate "Niagara2 Random Number Generator support"
+	depends on HW_RANDOM && SPARC64
+	default HW_RANDOM
+	---help---
+	  This driver provides kernel-side support for the Random Number
+	  Generator hardware found on Niagara2 cpus.
+
+	  To compile this driver as a module, choose M here: the
+	  module will be called n2-rng.
+
+	  If unsure, say Y.
+
 config HW_RANDOM_VIA
 	tristate "VIA HW Random Number Generator support"
 	depends on HW_RANDOM && X86_32
diff --git a/drivers/char/hw_random/Makefile
b/drivers/char/hw_random/Makefile
index c8b7300..32b327b 100644
--- a/drivers/char/hw_random/Makefile
+++ b/drivers/char/hw_random/Makefile
@@ -7,6 +7,8 @@ rng-core-y := core.o
 obj-$(CONFIG_HW_RANDOM_INTEL) += intel-rng.o
 obj-$(CONFIG_HW_RANDOM_AMD) += amd-rng.o
 obj-$(CONFIG_HW_RANDOM_GEODE) += geode-rng.o
+obj-$(CONFIG_HW_RANDOM_N2RNG) += n2-rng.o
+n2-rng-objs := n2-drv.o n2-asm.o
 obj-$(CONFIG_HW_RANDOM_VIA) += via-rng.o
 obj-$(CONFIG_HW_RANDOM_IXP4XX) += ixp4xx-rng.o
 obj-$(CONFIG_HW_RANDOM_OMAP) += omap-rng.o
diff --git a/drivers/char/hw_random/n2-asm.S
b/drivers/char/hw_random/n2-asm.S
new file mode 100644
index 0000000..c504e53
--- /dev/null
+++ b/drivers/char/hw_random/n2-asm.S
@@ -0,0 +1,93 @@
+/* n2-asm.S: Niagara2 RNG hypervisor call assembler.
+ *
+ * Copyright (C) 2008 David S. Miller 
+ */
+#include 
+#include "n2rng.h"
+
+	.text
+	.globl	sun4v_rng_get_diag_ctl
+	.type	sun4v_rng_get_diag_ctl,#function
+sun4v_rng_get_diag_ctl:
+	mov	HV_FAST_RNG_GET_DIAG_CTL, %o5
+	ta	HV_FAST_TRAP
+	retl
+	 nop
+	.size	sun4v_rng_get_diag_ctl,.-sun4v_rng_get_diag_ctl
+
+	.globl	sun4v_rng_ctl_read_v1
+	.type	sun4v_rng_ctl_read_v1,#function
+sun4v_rng_ctl_read_v1:
+	mov	%o1, %o3
+	mov	%o2, %o4
+	mov	HV_FAST_RNG_CTL_READ, %o5
+	ta	HV_FAST_TRAP
+	stx	%o1, [%o3]
+	retl
+	 stx	%o2, [%o4]
+	.size	sun4v_rng_ctl_read_v1,.-sun4v_rng_ctl_read_v1
+
+	.globl	sun4v_rng_ctl_read_v2
+	.type	sun4v_rng_ctl_read_v2,#function
+sun4v_rng_ctl_read_v2:
+	save	%sp, -192, %sp
+	mov	%i0, %o0
+	mov	%i1, %o1
+	mov	HV_FAST_RNG_CTL_READ, %o5
+	ta	HV_FAST_TRAP
+	stx	%o1, [%i2]
+	stx	%o2, [%i3]
+	stx	%o3, [%i4]
+	stx	%o4, [%i5]
+	ret
+	restore	%g0, %o0, %o0
+	.size	sun4v_rng_ctl_read_v2,.-sun4v_rng_ctl_read_v2
+
+	.globl	sun4v_rng_ctl_write_v1
+	.type	sun4v_rng_ctl_write_v1,#function
+sun4v_rng_ctl_write_v1:
+	mov	%o3, %o4
+	mov	HV_FAST_RNG_CTL_WRITE, %o5
+	ta	HV_FAST_TRAP
+	retl
+	 stx	%o1, [%o4]
+	.size	sun4v_rng_ctl_write_v1,.-sun4v_rng_ctl_write_v1
+
+	.globl	sun4v_rng_ctl_write_v2
+	.type	sun4v_rng_ctl_write_v2,#function
+sun4v_rng_ctl_write_v2:
+	mov	HV_FAST_RNG_CTL_WRITE, %o5
+	ta	HV_FAST_TRAP
+	retl
+	 nop
+	.size	sun4v_rng_ctl_write_v2,.-sun4v_rng_ctl_write_v2
+
+	.globl	sun4v_rng_data_read_diag_v1
+	.type	sun4v_rng_data_read_diag_v1,#function
+sun4v_rng_data_read_diag_v1:
+	mov	%o2, %o4
+	mov	HV_FAST_RNG_DATA_READ_DIAG, %o5
+	ta	HV_FAST_TRAP
+	retl
+	 stx	%o1, [%o4]
+	.size	sun4v_rng_data_read_diag_v1,.-sun4v_rng_data_read_diag_v1
+
+	.globl	sun4v_rng_data_read_diag_v2
+	.type	sun4v_rng_data_read_diag_v2,#function
+sun4v_rng_data_read_diag_v2:
+	mov	%o3, %o4
+	mov	HV_FAST_RNG_DATA_READ_DIAG, %o5
+	ta	HV_FAST_TRAP
+	retl
+	 stx	%o1, [%o4]
+	.size	sun4v_rng_data_read_diag_v2,.-sun4v_rng_data_read_diag_v2
+
+	.globl	sun4v_rng_data_read
+	.type	sun4v_rng_data_read,#function
+sun4v_rng_data_read:
+	mov	%o1, %o4
+	mov	HV_FAST_RNG_DATA_READ, %o5
+	ta	HV_FAST_TRAP
+	retl
+	 stx	%o1, [%o4]
+	.size	sun4v_rng_data_read,.-sun4v_rng_data_read
diff --git a/drivers/char/hw_random/n2-drv.c
b/drivers/char/hw_random/n2-drv.c
new file mode 100644
index 0000000..5220f54
--- /dev/null
+++ b/drivers/char/hw_random/n2-drv.c
@@ -0,0 +1,771 @@
+/* n2-drv.c: Niagara-2 RNG driver.
+ *
+ * Copyright (C) 2008 David S. Miller 
+ */
+
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+
+#include 
+#include 
+
+#include 
+
+#include "n2rng.h"
+
+#define DRV_MODULE_NAME		"n2rng"
+#define PFX DRV_MODULE_NAME	": "
+#define DRV_MODULE_VERSION	"0.1"
+#define DRV_MODULE_RELDATE	"May 15, 2008"
+
+static char version[] __devinitdata =
+	DRV_MODULE_NAME ".c:v" DRV_MODULE_VERSION " (" DRV_MODULE_RELDATE ")\n";
+
+MODULE_AUTHOR("David S. Miller ([email protected])");
+MODULE_DESCRIPTION("Niagara2 RNG driver");
+MODULE_LICENSE("GPL");
+MODULE_VERSION(DRV_MODULE_VERSION);
+
+/* The Niagara2 RNG provides a 64-bit read-only random number
+ * register, plus a control register.  Access to the RNG is
+ * virtualized through the hypervisor so that both guests and control
+ * nodes can access the device.
+ *
+ * The entropy source consists of raw entropy sources, each
+ * constructed from a voltage controlled oscillator whose phase is
+ * jittered by thermal noise sources.
+ *
+ * The oscillator in each of the three raw entropy sources run at
+ * different frequencies.  Normally, all three generator outputs are
+ * gathered, xored together, and fed into a CRC circuit, the output of
+ * which is the 64-bit read-only register.
+ *
+ * Some time is necessary for all the necessary entropy to build up
+ * such that a full 64-bits of entropy are available in the register.
+ * In normal operating mode (RNG_CTL_LFSR is set), the chip implements
+ * an interlock which blocks register reads until sufficient entropy
+ * is available.
+ *
+ * A control register is provided for adjusting various aspects of RNG
+ * operation, and to enable diagnostic modes.  Each of the three raw
+ * entropy sources has an enable bit (RNG_CTL_ES{1,2,3}).  Also
+ * provided are fields for controlling the minimum time in cycles
+ * between read accesses to the register (RNG_CTL_WAIT, this controls
+ * the interlock described in the previous paragraph).
+ *
+ * The standard setting is to have the mode bit (RNG_CTL_LFSR) set,
+ * all three entropy sources enabled, and the interlock time set
+ * appropriately.
+ *
+ * The CRC polynomial used by the chip is:
+ *
+ * P(X) = x64 + x61 + x57 + x56 + x52 + x51 + x50 + x48 + x47 + x46 +
+ *        x43 + x42 + x41 + x39 + x38 + x37 + x35 + x32 + x28 + x25 +
+ *        x22 + x21 + x17 + x15 + x13 + x12 + x11 + x7 + x5 + x + 1
+ *
+ * The RNG_CTL_VCO value of each noise cell must be programmed
+ * seperately.  This is why 4 control register values must be provided
+ * to the hypervisor.  During a write, the hypervisor writes them all,
+ * one at a time, to the actual RNG_CTL register.  The first three
+ * values are used to setup the desired RNG_CTL_VCO for each entropy
+ * source, for example:
+ *
+ *	control 0: (1 << RNG_CTL_VCO_SHIFT) | RNG_CTL_ES1
+ *	control 1: (2 << RNG_CTL_VCO_SHIFT) | RNG_CTL_ES2
+ *	control 2: (3 << RNG_CTL_VCO_SHIFT) | RNG_CTL_ES3
+ *
+ * And then the fourth value sets the final chip state and enables
+ * desired.
+ */
+
+static int n2rng_hv_err_trans(unsigned long hv_err)
+{
+	switch (hv_err) {
+	case HV_EOK:
+		return 0;
+	case HV_EWOULDBLOCK:
+		return -EAGAIN;
+	case HV_ENOACCESS:
+		return -EPERM;
+	case HV_EIO:
+		return -EIO;
+	case HV_EBUSY:
+		return -EBUSY;
+	case HV_EBADALIGN:
+	case HV_ENORADDR:
+		return -EFAULT;
+	default:
+		return -EINVAL;
+	}
+}
+
+static unsigned long n2rng_generic_read_control_v2(unsigned long ra,
+						   unsigned long unit)
+{
+	unsigned long hv_err, state, ticks, watchdog_delta, watchdog_status;
+	int block = 0, busy = 0;
+
+	while (1) {
+		hv_err = sun4v_rng_ctl_read_v2(ra, unit, &state,
+					       &ticks,
+					       &watchdog_delta,
+					       &watchdog_status);
+		if (hv_err == HV_EOK)
+			break;
+
+		if (hv_err == HV_EBUSY) {
+			if (++busy >= N2RNG_BUSY_LIMIT)
+				break;
+
+			udelay(1);
+		} else if (hv_err == HV_EWOULDBLOCK) {
+			if (++block >= N2RNG_BLOCK_LIMIT)
+				break;
+
+			__delay(ticks);
+		} else
+			break;
+	}
+
+	return hv_err;
+}
+
+/* In multi-socket situations, the hypervisor might need to
+ * queue up the RNG control register write if it's for a unit
+ * that is on a cpu socket other than the one we are executing on.
+ *
+ * We poll here waiting for a successful read of that control
+ * register to make sure the write has been actually performed.
+ */
+static unsigned long n2rng_control_settle_v2(struct n2rng *np, int unit)
+{
+	unsigned long ra = __pa(&np->scratch_control[0]);
+
+	return n2rng_generic_read_control_v2(ra, unit);
+}
+
+static unsigned long n2rng_write_ctl_one(struct n2rng *np, int unit,
+					 unsigned long state,
+					 unsigned long control_ra,
+					 unsigned long watchdog_timeout,
+					 unsigned long *ticks)
+{
+	unsigned long hv_err;
+
+	if (np->hvapi_major == 1) {
+		hv_err = sun4v_rng_ctl_write_v1(control_ra, state,
+						watchdog_timeout, ticks);
+	} else {
+		hv_err = sun4v_rng_ctl_write_v2(control_ra, state,
+						watchdog_timeout, unit);
+		if (hv_err == HV_EOK)
+			hv_err = n2rng_control_settle_v2(np, unit);
+		*ticks = N2RNG_ACCUM_CYCLES_DEFAULT;
+	}
+
+	return hv_err;
+}
+
+static int n2rng_generic_read_data(unsigned long data_ra)
+{
+	unsigned long ticks, hv_err;
+	int block = 0, hcheck = 0;
+
+	while (1) {
+		hv_err = sun4v_rng_data_read(data_ra, &ticks);
+		if (hv_err == HV_EOK)
+			return 0;
+
+		if (hv_err == HV_EWOULDBLOCK) {
+			if (++block >= N2RNG_BLOCK_LIMIT)
+				return -EWOULDBLOCK;
+			__delay(ticks);
+		} else if (hv_err == HV_ENOACCESS) {
+			return -EPERM;
+		} else if (hv_err == HV_EIO) {
+			if (++hcheck >= N2RNG_HCHECK_LIMIT)
+				return -EIO;
+			udelay(10000);
+		} else
+			return -ENODEV;
+	}
+}
+
+static unsigned long n2rng_read_diag_data_one(struct n2rng *np,
+					      unsigned long unit,
+					      unsigned long data_ra,
+					      unsigned long data_len,
+					      unsigned long *ticks)
+{
+	unsigned long hv_err;
+
+	if (np->hvapi_major == 1) {
+		hv_err = sun4v_rng_data_read_diag_v1(data_ra, data_len, ticks);
+	} else {
+		hv_err = sun4v_rng_data_read_diag_v2(data_ra, data_len,
+						     unit, ticks);
+		if (!*ticks)
+			*ticks = N2RNG_ACCUM_CYCLES_DEFAULT;
+	}
+	return hv_err;
+}
+
+static int n2rng_generic_read_diag_data(struct n2rng *np,
+					unsigned long unit,
+					unsigned long data_ra,
+					unsigned long data_len)
+{
+	unsigned long ticks, hv_err;
+	int block = 0;
+
+	while (1) {
+		hv_err = n2rng_read_diag_data_one(np, unit,
+						  data_ra, data_len,
+						  &ticks);
+		if (hv_err == HV_EOK)
+			return 0;
+
+		if (hv_err == HV_EWOULDBLOCK) {
+			if (++block >= N2RNG_BLOCK_LIMIT)
+				return -EWOULDBLOCK;
+			__delay(ticks);
+		} else if (hv_err == HV_ENOACCESS) {
+			return -EPERM;
+		} else if (hv_err == HV_EIO) {
+			return -EIO;
+		} else
+			return -ENODEV;
+	}
+}
+
+
+static int n2rng_generic_write_control(struct n2rng *np,
+				       unsigned long control_ra,
+				       unsigned long unit,
+				       unsigned long state)
+{
+	unsigned long hv_err, ticks;
+	int block = 0, busy = 0;
+
+	while (1) {
+		hv_err = n2rng_write_ctl_one(np, unit, state, control_ra,
+					     np->wd_timeo, &ticks);
+		if (hv_err == HV_EOK)
+			return 0;
+
+		if (hv_err == HV_EWOULDBLOCK) {
+			if (++block >= N2RNG_BLOCK_LIMIT)
+				return -EWOULDBLOCK;
+			__delay(ticks);
+		} else if (hv_err == HV_EBUSY) {
+			if (++busy >= N2RNG_BUSY_LIMIT)
+				return -EBUSY;
+			udelay(1);
+		} else
+			return -ENODEV;
+	}
+}
+
+/* Just try to see if we can successfully access the control register
+ * of the RNG on the domain on which we are currently executing.
+ */
+static int n2rng_try_read_ctl(struct n2rng *np)
+{
+	unsigned long hv_err;
+	unsigned long x;
+
+	if (np->hvapi_major == 1) {
+		hv_err = sun4v_rng_get_diag_ctl();
+	} else {
+		/* We purposefully give invalid arguments, HV_NOACCESS
+		 * is higher priority than the errors we'd get from
+		 * these other cases, and that's the error we are
+		 * truly interested in.
+		 */
+		hv_err = sun4v_rng_ctl_read_v2(0UL, ~0UL, &x, &x, &x, &x);
+		switch (hv_err) {
+		case HV_EWOULDBLOCK:
+		case HV_ENOACCESS:
+			break;
+		default:
+			hv_err = HV_EOK;
+			break;
+		}
+	}
+
+	return n2rng_hv_err_trans(hv_err);
+}
+
+#define CONTROL_DEFAULT_BASE		\
+	((2 << RNG_CTL_ASEL_SHIFT) |	\
+	 (N2RNG_ACCUM_CYCLES_DEFAULT << RNG_CTL_WAIT_SHIFT) |	\
+	 RNG_CTL_LFSR)
+
+#define CONTROL_DEFAULT_0		\
+	(CONTROL_DEFAULT_BASE |		\
+	 (1 << RNG_CTL_VCO_SHIFT) |	\
+	 RNG_CTL_ES1)
+#define CONTROL_DEFAULT_1		\
+	(CONTROL_DEFAULT_BASE |		\
+	 (2 << RNG_CTL_VCO_SHIFT) |	\
+	 RNG_CTL_ES2)
+#define CONTROL_DEFAULT_2		\
+	(CONTROL_DEFAULT_BASE |		\
+	 (3 << RNG_CTL_VCO_SHIFT) |	\
+	 RNG_CTL_ES3)
+#define CONTROL_DEFAULT_3		\
+	(CONTROL_DEFAULT_BASE |		\
+	 RNG_CTL_ES1 | RNG_CTL_ES2 | RNG_CTL_ES3)
+
+static void n2rng_control_swstate_init(struct n2rng *np)
+{
+	int i;
+
+	np->flags |= N2RNG_FLAG_CONTROL;
+
+	np->health_check_sec = N2RNG_HEALTH_CHECK_SEC_DEFAULT;
+	np->accum_cycles = N2RNG_ACCUM_CYCLES_DEFAULT;
+	np->wd_timeo = N2RNG_WD_TIMEO_DEFAULT;
+
+	for (i = 0; i < np->num_units; i++) {
+		struct n2rng_unit *up = &np->units[i];
+
+		up->control[0] = CONTROL_DEFAULT_0;
+		up->control[1] = CONTROL_DEFAULT_1;
+		up->control[2] = CONTROL_DEFAULT_2;
+		up->control[3] = CONTROL_DEFAULT_3;
+	}
+
+	np->hv_state = HV_RNG_STATE_UNCONFIGURED;
+}
+
+static int n2rng_grab_diag_control(struct n2rng *np)
+{
+	int i, busy_count, err = -ENODEV;
+
+	busy_count = 0;
+	for (i = 0; i < 100; i++) {
+		err = n2rng_try_read_ctl(np);
+		if (err != -EAGAIN)
+			break;
+
+		if (++busy_count > 100) {
+			dev_err(&np->op->dev,
+				"Grab diag control timeout.\n");
+			return -ENODEV;
+		}
+
+		udelay(1);
+	}
+
+	return err;
+}
+
+static int n2rng_init_control(struct n2rng *np)
+{
+	int err = n2rng_grab_diag_control(np);
+
+	/* Not in the control domain, that's OK we are only a consumer
+	 * of the RNG data, we don't setup and program it.
+	 */
+	if (err == -EPERM)
+		return 0;
+	if (err)
+		return err;
+
+	n2rng_control_swstate_init(np);
+
+	return 0;
+}
+
+static int n2rng_data_read(struct hwrng *rng, u32 *data)
+{
+	struct n2rng *np = (struct n2rng *) rng->priv;
+	unsigned long ra = __pa(&np->test_data);
+	int len;
+
+	if (!(np->flags & N2RNG_FLAG_READY)) {
+		len = 0;
+	} else if (np->flags & N2RNG_FLAG_BUFFER_VALID) {
+		np->flags &= ~N2RNG_FLAG_BUFFER_VALID;
+		*data = np->buffer;
+		len = 4;
+	} else {
+		int err = n2rng_generic_read_data(ra);
+		if (!err) {
+			np->buffer = np->test_data >> 32;
+			*data = np->test_data & 0xffffffff;
+			len = 4;
+		} else {
+			dev_err(&np->op->dev, "RNG error, restesting\n");
+			np->flags &= ~N2RNG_FLAG_READY;
+			if (!(np->flags & N2RNG_FLAG_SHUTDOWN))
+				schedule_delayed_work(&np->work, 0);
+			len = 0;
+		}
+	}
+
+	return len;
+}
+
+/* On a guest node, just make sure we can read random data properly.
+ * If a control node reboots or reloads it's n2rng driver, this won't
+ * work during that time.  So we have to keep probing until the device
+ * becomes usable.
+ */
+static int n2rng_guest_check(struct n2rng *np)
+{
+	unsigned long ra = __pa(&np->test_data);
+
+	return n2rng_generic_read_data(ra);
+}
+
+static int n2rng_entropy_diag_read(struct n2rng *np, unsigned long unit,
+				   u64 *pre_control, u64 pre_state,
+				   u64 *buffer, unsigned long buf_len,
+				   u64 *post_control, u64 post_state)
+{
+	unsigned long post_ctl_ra = __pa(post_control);
+	unsigned long pre_ctl_ra = __pa(pre_control);
+	unsigned long buffer_ra = __pa(buffer);
+	int err;
+
+	err = n2rng_generic_write_control(np, pre_ctl_ra, unit, pre_state);
+	if (err)
+		return err;
+
+	err = n2rng_generic_read_diag_data(np, unit,
+					   buffer_ra, buf_len);
+
+	(void) n2rng_generic_write_control(np, post_ctl_ra, unit,
+					   post_state);
+
+	return err;
+}
+
+static u64 advance_polynomial(u64 poly, u64 val, int count)
+{
+	int i;
+
+	for (i = 0; i < count; i++) {
+		int highbit_set = ((s64)val < 0);
+
+		val <<= 1;
+		if (highbit_set)
+			val ^= poly;
+	}
+
+	return val;
+}
+
+static int n2rng_test_buffer_find(struct n2rng *np, u64 val)
+{
+	int i, count = 0;
+
+	/* Purposefully skip over the first word.  */
+	for (i = 1; i < SELFTEST_BUFFER_WORDS; i++) {
+		if (np->test_buffer[i] == val)
+			count++;
+	}
+	return count;
+}
+
+static void n2rng_dump_test_buffer(struct n2rng *np)
+{
+	int i;
+
+	for (i = 0; i < SELFTEST_BUFFER_WORDS; i++)
+		dev_err(&np->op->dev, "Test buffer slot %d [0x%016lx]\n",
+			i, np->test_buffer[i]);
+}
+
+static int n2rng_check_selftest_buffer(struct n2rng *np, unsigned long
unit)
+{
+	u64 val = SELFTEST_VAL;
+	int err, matches, limit;
+
+	matches = 0;
+	for (limit = 0; limit < SELFTEST_LOOPS_MAX; limit++) {
+		matches += n2rng_test_buffer_find(np, val);
+		if (matches >= SELFTEST_MATCH_GOAL)
+			break;
+		val = advance_polynomial(SELFTEST_POLY, val, 1);
+	}
+
+	err = 0;
+	if (limit >= SELFTEST_LOOPS_MAX) {
+		err = -ENODEV;
+		dev_err(&np->op->dev, "Selftest failed on unit %lu\n", unit);
+		n2rng_dump_test_buffer(np);
+	} else
+		dev_info(&np->op->dev, "Selftest passed on unit %lu\n", unit);
+
+	return err;
+}
+
+static int n2rng_control_selftest(struct n2rng *np, unsigned long unit)
+{
+	int err;
+
+	np->test_control[0] = (0x2 << RNG_CTL_ASEL_SHIFT);
+	np->test_control[1] = (0x2 << RNG_CTL_ASEL_SHIFT);
+	np->test_control[2] = (0x2 << RNG_CTL_ASEL_SHIFT);
+	np->test_control[3] = ((0x2 << RNG_CTL_ASEL_SHIFT) |
+			       RNG_CTL_LFSR |
+			       ((SELFTEST_TICKS - 2) << RNG_CTL_WAIT_SHIFT));
+
+
+	err = n2rng_entropy_diag_read(np, unit, np->test_control,
+				      HV_RNG_STATE_HEALTHCHECK,
+				      np->test_buffer,
+				      sizeof(np->test_buffer),
+				      &np->units[unit].control[0],
+				      np->hv_state);
+	if (err)
+		return err;
+
+	return n2rng_check_selftest_buffer(np, unit);
+}
+
+static int n2rng_control_check(struct n2rng *np)
+{
+	int i;
+
+	for (i = 0; i < np->num_units; i++) {
+		int err = n2rng_control_selftest(np, i);
+		if (err)
+			return err;
+	}
+	return 0;
+}
+
+/* The sanity checks passed, install the final configuration into the
+ * chip, it's ready to use.
+ */
+static int n2rng_control_configure_units(struct n2rng *np)
+{
+	int unit, err;
+
+	err = 0;
+	for (unit = 0; unit < np->num_units; unit++) {
+		struct n2rng_unit *up = &np->units[unit];
+		unsigned long ctl_ra = __pa(&up->control[0]);
+		int esrc;
+		u64 base;
+
+		base = ((np->accum_cycles << RNG_CTL_WAIT_SHIFT) |
+			(2 << RNG_CTL_ASEL_SHIFT) |
+			RNG_CTL_LFSR);
+
+		/* XXX This isn't the best.  We should fetch a bunch
+		 * XXX of words using each entropy source combined XXX
+		 * with each VCO setting, and see which combinations
+		 * XXX give the best random data.
+		 */
+		for (esrc = 0; esrc < 3; esrc++)
+			up->control[esrc] = base |
+				(esrc << RNG_CTL_VCO_SHIFT) |
+				(RNG_CTL_ES1 << esrc);
+
+		up->control[3] = base |
+			(RNG_CTL_ES1 | RNG_CTL_ES2 | RNG_CTL_ES3);
+
+		err = n2rng_generic_write_control(np, ctl_ra, unit,
+						  HV_RNG_STATE_CONFIGURED);
+		if (err)
+			break;
+	}
+
+	return err;
+}
+
+static void n2rng_work(struct work_struct *work)
+{
+	struct n2rng *np = container_of(work, struct n2rng, work.work);
+	int err = 0;
+
+	if (!(np->flags & N2RNG_FLAG_CONTROL)) {
+		err = n2rng_guest_check(np);
+	} else {
+		preempt_disable();
+		err = n2rng_control_check(np);
+		preempt_enable();
+
+		if (!err)
+			err = n2rng_control_configure_units(np);
+	}
+
+	if (!err) {
+		np->flags |= N2RNG_FLAG_READY;
+		dev_info(&np->op->dev, "RNG ready\n");
+	}
+
+	if (err && !(np->flags & N2RNG_FLAG_SHUTDOWN))
+		schedule_delayed_work(&np->work, HZ * 2);
+}
+
+static void __devinit n2rng_driver_version(void)
+{
+	static int n2rng_version_printed;
+
+	if (n2rng_version_printed++ == 0)
+		pr_info("%s", version);
+}
+
+static int __devinit n2rng_probe(struct of_device *op,
+				 const struct of_device_id *match)
+{
+	int victoria_falls = (match->data != NULL);
+	int err = -ENOMEM;
+	struct n2rng *np;
+
+	n2rng_driver_version();
+
+	np = kzalloc(sizeof(*np), GFP_KERNEL);
+	if (!np)
+		goto out;
+	np->op = op;
+
+	INIT_DELAYED_WORK(&np->work, n2rng_work);
+
+	if (victoria_falls)
+		np->flags |= N2RNG_FLAG_VF;
+
+	err = -ENODEV;
+	np->hvapi_major = 2;
+	if (sun4v_hvapi_register(HV_GRP_RNG,
+				 np->hvapi_major,
+				 &np->hvapi_minor)) {
+		np->hvapi_major = 1;
+		if (sun4v_hvapi_register(HV_GRP_RNG,
+					 np->hvapi_major,
+					 &np->hvapi_minor)) {
+			dev_err(&op->dev, "Cannot register suitable "
+				"HVAPI version.\n");
+			goto out_free;
+		}
+	}
+
+	if (np->flags & N2RNG_FLAG_VF) {
+		if (np->hvapi_major < 2) {
+			dev_err(&op->dev, "VF RNG requires HVAPI major "
+				"version 2 or later, got %lu\n",
+				np->hvapi_major);
+			goto out_hvapi_unregister;
+		}
+		np->num_units = of_getintprop_default(op->node,
+						      "rng-#units", 0);
+		if (!np->num_units) {
+			dev_err(&op->dev, "VF RNG lacks rng-#units property\n");
+			goto out_hvapi_unregister;
+		}
+	} else
+		np->num_units = 1;
+
+	dev_info(&op->dev, "Registered RNG HVAPI major %lu minor %lu\n",
+		 np->hvapi_major, np->hvapi_minor);
+
+	np->units = kzalloc(sizeof(struct n2rng_unit) * np->num_units,
+			    GFP_KERNEL);
+	err = -ENOMEM;
+	if (!np->units)
+		goto out_hvapi_unregister;
+
+	err = n2rng_init_control(np);
+	if (err)
+		goto out_free_units;
+
+	dev_info(&op->dev, "Found %s RNG, units: %d\n",
+		 ((np->flags & N2RNG_FLAG_VF) ?
+		  "Victoria Falls" : "Niagara2"),
+		 np->num_units);
+
+	np->hwrng.name = "n2rng";
+	np->hwrng.data_read = n2rng_data_read;
+	np->hwrng.priv = (unsigned long) np;
+
+	err = hwrng_register(&np->hwrng);
+	if (err)
+		goto out_free_units;
+
+	dev_set_drvdata(&op->dev, np);
+
+	schedule_delayed_work(&np->work, 0);
+
+	return 0;
+
+out_free_units:
+	kfree(np->units);
+	np->units = NULL;
+
+out_hvapi_unregister:
+	sun4v_hvapi_unregister(HV_GRP_RNG);
+
+out_free:
+	kfree(np);
+out:
+	return err;
+}
+
+static int __devexit n2rng_remove(struct of_device *op)
+{
+	struct n2rng *np = dev_get_drvdata(&op->dev);
+
+	np->flags |= N2RNG_FLAG_SHUTDOWN;
+
+	cancel_delayed_work_sync(&np->work);
+
+	hwrng_unregister(&np->hwrng);
+
+	sun4v_hvapi_unregister(HV_GRP_RNG);
+
+	kfree(np->units);
+	np->units = NULL;
+
+	kfree(np);
+
+	dev_set_drvdata(&op->dev, NULL);
+
+	return 0;
+}
+
+static struct of_device_id n2rng_match[] = {
+	{
+		.name		= "random-number-generator",
+		.compatible	= "SUNW,n2-rng",
+	},
+	{
+		.name		= "random-number-generator",
+		.compatible	= "SUNW,vf-rng",
+		.data		= (void *) 1,
+	},
+	{},
+};
+MODULE_DEVICE_TABLE(of, n2rng_match);
+
+static struct of_platform_driver n2rng_driver = {
+	.name		= "n2rng",
+	.match_table	= n2rng_match,
+	.probe		= n2rng_probe,
+	.remove		= __devexit_p(n2rng_remove),
+};
+
+static int __init n2rng_init(void)
+{
+	return of_register_driver(&n2rng_driver, &of_bus_type);
+}
+
+static void __exit n2rng_exit(void)
+{
+	of_unregister_driver(&n2rng_driver);
+}
+
+module_init(n2rng_init);
+module_exit(n2rng_exit);
diff --git a/drivers/char/hw_random/n2rng.h
b/drivers/char/hw_random/n2rng.h
new file mode 100644
index 0000000..a2b81e7
--- /dev/null
+++ b/drivers/char/hw_random/n2rng.h
@@ -0,0 +1,118 @@
+/* n2rng.h: Niagara2 RNG defines.
+ *
+ * Copyright (C) 2008 David S. Miller 
+ */
+
+#ifndef _N2RNG_H
+#define _N2RNG_H
+
+#define RNG_CTL_WAIT       0x0000000001fffe00ULL /* Minimum wait time     
 */
+#define RNG_CTL_WAIT_SHIFT 9
+#define RNG_CTL_BYPASS     0x0000000000000100ULL /* VCO voltage source    
 */
+#define RNG_CTL_VCO        0x00000000000000c0ULL /* VCO rate control      
 */
+#define RNG_CTL_VCO_SHIFT  6
+#define RNG_CTL_ASEL       0x0000000000000030ULL /* Analog MUX select     
 */
+#define RNG_CTL_ASEL_SHIFT 4
+#define RNG_CTL_LFSR       0x0000000000000008ULL /* Use LFSR or plain
shift */
+#define RNG_CTL_ES3        0x0000000000000004ULL /* Enable entropy source
3 */
+#define RNG_CTL_ES2        0x0000000000000002ULL /* Enable entropy source
2 */
+#define RNG_CTL_ES1        0x0000000000000001ULL /* Enable entropy source
1 */
+
+#define HV_FAST_RNG_GET_DIAG_CTL	0x130
+#define HV_FAST_RNG_CTL_READ		0x131
+#define HV_FAST_RNG_CTL_WRITE		0x132
+#define HV_FAST_RNG_DATA_READ_DIAG	0x133
+#define HV_FAST_RNG_DATA_READ		0x134
+
+#define HV_RNG_STATE_UNCONFIGURED	0
+#define HV_RNG_STATE_CONFIGURED		1
+#define HV_RNG_STATE_HEALTHCHECK	2
+#define HV_RNG_STATE_ERROR		3
+
+#define HV_RNG_NUM_CONTROL		4
+
+#ifndef __ASSEMBLY__
+extern unsigned long sun4v_rng_get_diag_ctl(void);
+extern unsigned long sun4v_rng_ctl_read_v1(unsigned long ctl_regs_ra,
+					   unsigned long *state,
+					   unsigned long *tick_delta);
+extern unsigned long sun4v_rng_ctl_read_v2(unsigned long ctl_regs_ra,
+					   unsigned long unit,
+					   unsigned long *state,
+					   unsigned long *tick_delta,
+					   unsigned long *watchdog,
+					   unsigned long *write_status);
+extern unsigned long sun4v_rng_ctl_write_v1(unsigned long ctl_regs_ra,
+					    unsigned long state,
+					    unsigned long write_timeout,
+					    unsigned long *tick_delta);
+extern unsigned long sun4v_rng_ctl_write_v2(unsigned long ctl_regs_ra,
+					    unsigned long state,
+					    unsigned long write_timeout,
+					    unsigned long unit);
+extern unsigned long sun4v_rng_data_read_diag_v1(unsigned long data_ra,
+						 unsigned long len,
+						 unsigned long *tick_delta);
+extern unsigned long sun4v_rng_data_read_diag_v2(unsigned long data_ra,
+						 unsigned long len,
+						 unsigned long unit,
+						 unsigned long *tick_delta);
+extern unsigned long sun4v_rng_data_read(unsigned long data_ra,
+					 unsigned long *tick_delta);
+
+struct n2rng_unit {
+	u64			control[HV_RNG_NUM_CONTROL];
+};
+
+struct n2rng {
+	struct of_device	*op;
+
+	unsigned long		flags;
+#define N2RNG_FLAG_VF		0x00000001 /* Victoria Falls RNG, else N2 */
+#define N2RNG_FLAG_CONTROL	0x00000002 /* Operating in control domain */
+#define N2RNG_FLAG_READY	0x00000008 /* Ready for hw-rng layer      */
+#define N2RNG_FLAG_SHUTDOWN	0x00000010 /* Driver unregistering        */
+#define N2RNG_FLAG_BUFFER_VALID	0x00000020 /* u32 buffer holds valid data
*/
+
+	int			num_units;
+	struct n2rng_unit	*units;
+
+	struct hwrng		hwrng;
+	u32			buffer;
+
+	/* Registered hypervisor group API major and minor version.  */
+	unsigned long		hvapi_major;
+	unsigned long		hvapi_minor;
+
+	struct delayed_work	work;
+
+	unsigned long		hv_state; /* HV_RNG_STATE_foo */
+
+	unsigned long		health_check_sec;
+	unsigned long		accum_cycles;
+	unsigned long		wd_timeo;
+#define N2RNG_HEALTH_CHECK_SEC_DEFAULT	0
+#define N2RNG_ACCUM_CYCLES_DEFAULT	2048
+#define N2RNG_WD_TIMEO_DEFAULT		0
+
+	u64			scratch_control[HV_RNG_NUM_CONTROL];
+
+#define SELFTEST_TICKS		38859
+#define SELFTEST_VAL		((u64)0xB8820C7BD387E32C)
+#define SELFTEST_POLY		((u64)0x231DCEE91262B8A3)
+#define SELFTEST_MATCH_GOAL	6
+#define SELFTEST_LOOPS_MAX	40000
+#define SELFTEST_BUFFER_WORDS	8
+
+	u64			test_data;
+	u64			test_control[HV_RNG_NUM_CONTROL];
+	u64			test_buffer[SELFTEST_BUFFER_WORDS];
+};
+
+#define N2RNG_BLOCK_LIMIT	60000
+#define N2RNG_BUSY_LIMIT	100
+#define N2RNG_HCHECK_LIMIT	100
+
+#endif /* !(__ASSEMBLY__) */
+
+#endif /* _N2RNG_H */
-- 
1.5.5.1.57.g5909c

--
To unsubscribe from this list: send the line "unsubscribe sparclinux" in
the body of a message to [email protected]
More majordomo info at  http://vger.kernel.org/majordomo-info.html
 
CD: 4ms