Devicetree
 help / color / mirror / Atom feed
* [PATCH 0/3] hwrng: add Microchip RNG90 support
@ 2026-10-24 20:18 Patrick Tudor
  2026-10-24 20:18 ` [PATCH 1/3] dt-bindings: trivial-devices: add Microchip RNG90 Patrick Tudor
                   ` (2 more replies)
  0 siblings, 3 replies; 6+ messages in thread
From: Patrick Tudor @ 2026-10-24 20:18 UTC (permalink / raw)
  To: Rob Herring, Krzysztof Kozlowski, Conor Dooley, Olivia Mackall,
	Herbert Xu
  Cc: devicetree, linux-kernel, linux-crypto, Patrick Tudor

The Microchip RNG90 is an I2C cryptographic random number generator at
the fixed 0x40 address. It combines a physical noise source with a
SHA-256 Hash_DRBG and returns a 256-bit result for each Random command.
The device performs runtime health tests and self-tests before releasing
output.

This series documents the compatible, adds the hwrng driver, and adds a
KUnit suite using a scripted I2C adapter. The driver wakes and identifies
the part, validates framing and CRCs, polls within data-sheet deadlines,
recovers once from transport or synchronization failures, and withholds
output after any device verdict or malformed response.

The data sheet specifies 128-bit security strength for each 256-bit
result, but that does not establish entropy per output bit. The driver
therefore starts at the hwrng framework's minimum nonzero quality of 1.
An administrator can raise rng_quality after qualifying the complete
platform and its operating conditions.

Validation against the base commit:

  - dt_binding_check for trivial-devices.yaml
  - full x86_64 defconfig build and a separate W=1 module rebuild
  - UML KUnit: 32 tests passed

The driver was also exercised on physical RNG90 hardware through the
in-tree hid-cp2112 adapter: three 500-block streams, hot unplug/replug,
unbind/rebind, and S3 suspend/resume completed without driver errors.

Signed-off-by: Patrick Tudor <ptudor@ptudor.net>
---
Patrick Tudor (3):
      dt-bindings: trivial-devices: add Microchip RNG90
      hwrng: add Microchip RNG90 driver
      hwrng: rng90: add KUnit tests

 .../devicetree/bindings/trivial-devices.yaml       |   2 +
 drivers/char/hw_random/Kconfig                     |  26 +
 drivers/char/hw_random/Makefile                    |   1 +
 drivers/char/hw_random/rng90-kunit.c               | 820 +++++++++++++++++++++
 drivers/char/hw_random/rng90.c                     | 555 ++++++++++++++
 5 files changed, 1404 insertions(+)
---
base-commit: 5b0764a53235d3c77e1ed674e836e1b3fe1a4b1f
change-id: 20261024-rng90-submit-v1-705c3c70a116

Best regards,
-- 
Patrick Tudor <ptudor@ptudor.net>


^ permalink raw reply	[flat|nested] 6+ messages in thread

* [PATCH 1/3] dt-bindings: trivial-devices: add Microchip RNG90
  2026-10-24 20:18 [PATCH 0/3] hwrng: add Microchip RNG90 support Patrick Tudor
@ 2026-10-24 20:18 ` Patrick Tudor
  2026-10-26 12:43   ` Krzysztof Kozlowski
  2026-10-24 20:18 ` [PATCH 2/3] hwrng: add Microchip RNG90 driver Patrick Tudor
  2026-10-24 20:18 ` [PATCH 3/3] hwrng: rng90: add KUnit tests Patrick Tudor
  2 siblings, 1 reply; 6+ messages in thread
From: Patrick Tudor @ 2026-10-24 20:18 UTC (permalink / raw)
  To: Rob Herring, Krzysztof Kozlowski, Conor Dooley, Olivia Mackall,
	Herbert Xu
  Cc: devicetree, linux-kernel, linux-crypto, Patrick Tudor

Document the Microchip RNG90 I2C cryptographic random number generator.
The device needs only a compatible string and reg property, so the
trivial-devices schema is sufficient.

Signed-off-by: Patrick Tudor <ptudor@ptudor.net>
---
 Documentation/devicetree/bindings/trivial-devices.yaml | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/Documentation/devicetree/bindings/trivial-devices.yaml b/Documentation/devicetree/bindings/trivial-devices.yaml
index 3b7876c5a..503e18b2b 100644
--- a/Documentation/devicetree/bindings/trivial-devices.yaml
+++ b/Documentation/devicetree/bindings/trivial-devices.yaml
@@ -314,6 +314,8 @@ properties:
           - microchip,mcp4019-502
             # Microchip 7-bit Single I2C Digital POT (50k)
           - microchip,mcp4019-503
+            # Microchip RNG90 random number generator
+          - microchip,rng90
             # PWM Fan Speed Controller With Fan Fault Detection
           - microchip,tc654
             # PWM Fan Speed Controller With Fan Fault Detection

-- 
2.56.0


^ permalink raw reply related	[flat|nested] 6+ messages in thread

* [PATCH 2/3] hwrng: add Microchip RNG90 driver
  2026-10-24 20:18 [PATCH 0/3] hwrng: add Microchip RNG90 support Patrick Tudor
  2026-10-24 20:18 ` [PATCH 1/3] dt-bindings: trivial-devices: add Microchip RNG90 Patrick Tudor
@ 2026-10-24 20:18 ` Patrick Tudor
  2026-10-24 20:18 ` [PATCH 3/3] hwrng: rng90: add KUnit tests Patrick Tudor
  2 siblings, 0 replies; 6+ messages in thread
From: Patrick Tudor @ 2026-10-24 20:18 UTC (permalink / raw)
  To: Rob Herring, Krzysztof Kozlowski, Conor Dooley, Olivia Mackall,
	Herbert Xu
  Cc: devicetree, linux-kernel, linux-crypto, Patrick Tudor

Add support for the Microchip RNG90 cryptographic random number generator
over I2C.

The driver wakes and identifies the device, frames Info and Random
commands, polls bounded execution deadlines, verifies response lengths and
CRCs, and handles device health and self-test failures without exposing
rejected output.

Use the minimum nonzero hwrng quality because the data sheet specifies
output security strength but does not quantify entropy per output bit. The
value remains adjustable through the hwrng sysfs interface after platform
qualification.

Tested on physical RNG90 hardware through the in-tree hid-cp2112 adapter.

Signed-off-by: Patrick Tudor <ptudor@ptudor.net>
---
 drivers/char/hw_random/Kconfig  |  15 ++
 drivers/char/hw_random/Makefile |   1 +
 drivers/char/hw_random/rng90.c  | 551 ++++++++++++++++++++++++++++++++++++++++
 3 files changed, 567 insertions(+)

diff --git a/drivers/char/hw_random/Kconfig b/drivers/char/hw_random/Kconfig
index 1bf5e3f8a..af276d476 100644
--- a/drivers/char/hw_random/Kconfig
+++ b/drivers/char/hw_random/Kconfig
@@ -88,6 +88,21 @@ config HW_RANDOM_ATMEL
 
 	  If unsure, say Y.
 
+config HW_RANDOM_RNG90
+	tristate "Microchip RNG90 I2C random number generator"
+	depends on I2C
+	select CRC16
+	select BITREVERSE
+	help
+	  Support the Microchip RNG90 random number generator on I2C at 0x40.
+	  Command responses are checked for framing, device errors and CRC.
+	  The driver uses the minimum nonzero hwrng entropy estimate because
+	  the device's security strength does not quantify entropy per output
+	  bit. Administrators can raise the estimate after qualification.
+
+	  To compile this driver as a module, choose M here: the module
+	  will be called rng90.
+
 config HW_RANDOM_BA431
 	tristate "Silex Insight BA431 Random Number Generator support"
 	depends on HAS_IOMEM
diff --git a/drivers/char/hw_random/Makefile b/drivers/char/hw_random/Makefile
index 8fce2aa6c..852848297 100644
--- a/drivers/char/hw_random/Makefile
+++ b/drivers/char/hw_random/Makefile
@@ -10,6 +10,7 @@ obj-$(CONFIG_HW_RANDOM_INTEL) += intel-rng.o
 obj-$(CONFIG_HW_RANDOM_AMD) += amd-rng.o
 obj-$(CONFIG_HW_RANDOM_AIROHA) += airoha-trng.o
 obj-$(CONFIG_HW_RANDOM_ATMEL) += atmel-rng.o
+obj-$(CONFIG_HW_RANDOM_RNG90) += rng90.o
 obj-$(CONFIG_HW_RANDOM_BA431) += ba431-rng.o
 obj-$(CONFIG_HW_RANDOM_GEODE) += geode-rng.o
 obj-$(CONFIG_HW_RANDOM_N2RNG) += n2-rng.o
diff --git a/drivers/char/hw_random/rng90.c b/drivers/char/hw_random/rng90.c
new file mode 100644
index 000000000..ec9ee3e27
--- /dev/null
+++ b/drivers/char/hw_random/rng90.c
@@ -0,0 +1,551 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/* Microchip RNG90 I2C hardware random number generator, DS40002499A. */
+
+#include <linux/bitrev.h>
+#include <linux/crc16.h>
+#include <linux/delay.h>
+#include <linux/err.h>
+#include <linux/hw_random.h>
+#include <linux/i2c.h>
+#include <linux/ktime.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/of.h>
+#include <linux/pm.h>
+#include <linux/slab.h>
+#include <linux/string.h>
+#include <linux/unaligned.h>
+
+#define RNG90_ADDRESS		0x40
+#define RNG90_WORD_RESET		0x00
+#define RNG90_WORD_SLEEP		0x01
+#define RNG90_WORD_COMMAND	0x03
+#define RNG90_OP_INFO		0x30
+#define RNG90_OP_RANDOM		0x16
+#define RNG90_STATUS_SELFTEST	0x07
+#define RNG90_STATUS_HEALTH	0x08
+#define RNG90_STATUS_WAKE		0x11
+#define RNG90_STATUS_COMM		0xff
+#define RNG90_DEVICE_ID		0xd0
+#define RNG90_RANDOM_SIZE		32
+#define RNG90_RANDOM_FILLER	20
+#define RNG90_STATUS_SIZE		4
+#define RNG90_INFO_SIZE		7
+#define RNG90_REPLY_SIZE		(1 + RNG90_RANDOM_SIZE + 2)
+#define RNG90_COMMAND_SIZE	(1 + 7 + RNG90_RANDOM_FILLER)
+
+/*
+ * Latch a fault after this many health or self-test failures without an
+ * intervening verified Random block. Before the latch is set, the next read
+ * performs a sleep/wake so its first Random reruns the self-tests. Only a
+ * verified block resets the count; rebind clears the latch.
+ */
+#define RNG90_HEALTH_STRIKES	3
+
+/*
+ * Table 4-1's wake delay and Table 5-6's Random execution times. Random waits
+ * tEXEC(typ) and then polls, as section 3.4.3 recommends; its deadline is
+ * tEXEC(max) + 25 % + 2 ms, matching the firmware's margin. Info uses a 1 ms
+ * initial delay and a 10 ms deadline to allow for scheduling slack. These
+ * deadlines limit busy polling, not individual adapter transfer timeouts.
+ */
+#define RNG90_WAKE_US			2000
+#define RNG90_POLL_US			1000
+#define RNG90_INFO_TYP_US		1000
+#define RNG90_INFO_DEADLINE_US		10000
+#define RNG90_RANDOM_TYP_US		20200
+#define RNG90_RANDOM_DEADLINE_US	34000
+#define RNG90_FIRST_RANDOM_TYP_US	57000
+#define RNG90_FIRST_RANDOM_DEADLINE_US	92000
+/* Section 3.2.3, step 3: allow a previously started Random to complete. */
+#define RNG90_SETTLE_US			RNG90_FIRST_RANDOM_DEADLINE_US
+
+enum rng90_state {
+	RNG90_UNKNOWN,
+	RNG90_ASLEEP,
+	RNG90_AWAKE,
+};
+
+struct rng90 {
+	struct i2c_client *client;
+	struct hwrng rng;
+	struct mutex lock; /* Protects device transactions, state and buffers. */
+	enum rng90_state state;
+	bool first_random;
+	bool failed;
+	bool suspended;
+	unsigned int strikes; /* Health/self-test failures since a verified block. */
+	u8 command[RNG90_COMMAND_SIZE];
+	u8 reply[RNG90_REPLY_SIZE];
+};
+
+static u16 rng90_crc(const u8 *data, size_t len)
+{
+	return bitrev16(crc16(0, data, len));
+}
+
+static void rng90_wipe(struct rng90 *priv)
+{
+	memzero_explicit(priv->command, sizeof(priv->command));
+	memzero_explicit(priv->reply, sizeof(priv->reply));
+}
+
+static void rng90_invalidate(struct rng90 *priv)
+{
+	priv->state = RNG90_UNKNOWN;
+	priv->first_random = true;
+	rng90_wipe(priv);
+}
+
+static int rng90_write(struct rng90 *priv, const u8 *data, size_t len)
+{
+	int ret = i2c_master_send(priv->client, data, len);
+
+	if (ret < 0)
+		return ret;
+	return ret == len ? 0 : -EPROTO;
+}
+
+static int rng90_word(struct rng90 *priv, u8 word)
+{
+	return rng90_write(priv, &word, sizeof(word));
+}
+
+/*
+ * Errors accepted on a wake write or retried while polling a busy chip:
+ * -ENXIO (standard I2C address NACK), -EREMOTEIO (Pi controllers) and
+ * -ETIMEDOUT (hid-cp2112 reports both NACK and bus timeout this way). None
+ * proves the chip is present. It NACKs while asleep (section 3.2.1) or busy
+ * (section 3.4.3).
+ */
+static bool rng90_nack(int ret)
+{
+	return ret == -ENXIO || ret == -EREMOTEIO || ret == -ETIMEDOUT;
+}
+
+/*
+ * The device is gone: an unplugged USB adapter answers -ENODEV, or -ESHUTDOWN
+ * for the transfer in flight at the disconnect. Nothing is retried or
+ * recovered, and removal of such a device is expected, not worth a warning.
+ */
+static bool rng90_gone(int ret)
+{
+	return ret == -ENODEV || ret == -ESHUTDOWN;
+}
+
+/*
+ * Interpret a CRC-verified four-byte status reply. Mark device verdicts
+ * separately from errno: an adapter may also return -EIO. A Random read may
+ * retry a transport failure once, but a verdict ends that read.
+ */
+static int rng90_status(struct rng90 *priv, u8 status, bool *verdict)
+{
+	struct device *dev = &priv->client->dev;
+
+	switch (status) {
+	case RNG90_STATUS_WAKE:
+		/* A wake reply indicates lost command/result state. */
+		dev_dbg(dev, "wake status in place of a result\n");
+		return -ECONNRESET;
+	case RNG90_STATUS_COMM:
+		dev_warn_ratelimited(dev, "command not received by the chip\n");
+		return -EBADMSG;
+	case RNG90_STATUS_SELFTEST:
+	case RNG90_STATUS_HEALTH:
+		if (verdict)
+			*verdict = true;
+		if (++priv->strikes < RNG90_HEALTH_STRIKES) {
+			dev_warn_ratelimited(dev, "health/self-test failure %#04x, %u of %u\n",
+					     status, priv->strikes,
+					     RNG90_HEALTH_STRIKES);
+		} else {
+			/* Suspend/resume and resynchronization never clear this latch. */
+			priv->failed = true;
+			dev_err(dev, "health/self-test failure %#04x, %u without a verified block; rebind required\n",
+				status, priv->strikes);
+		}
+		return -EIO;
+	default:
+		if (verdict)
+			*verdict = true;
+		dev_err_ratelimited(dev, "unexpected device status %#04x\n",
+				    status);
+		return -EIO;
+	}
+}
+
+static int rng90_receive(struct rng90 *priv, size_t expected, bool *verdict)
+{
+	u8 *reply = priv->reply;
+	size_t count;
+	int ret;
+
+	memzero_explicit(reply, sizeof(priv->reply));
+	ret = i2c_master_recv(priv->client, reply, expected);
+	if (ret < 0)
+		return ret;
+	if (ret != expected)
+		return -EPROTO;
+
+	/* Over-reading a four-byte status reply yields 0xff padding (section 3.3). */
+	count = reply[0];
+	if (count != expected && count != RNG90_STATUS_SIZE)
+		return -EPROTO;
+	if (rng90_crc(reply, count - 2) != get_unaligned_le16(reply + count - 2))
+		return -EBADMSG;
+	if (count != expected)
+		return rng90_status(priv, reply[1], verdict);
+
+	return 0;
+}
+
+static size_t rng90_build_command(u8 *command, u8 opcode)
+{
+	size_t count = 7;
+
+	if (opcode == RNG90_OP_RANDOM)
+		count += RNG90_RANDOM_FILLER;
+	memset(command, 0, RNG90_COMMAND_SIZE);
+	command[0] = RNG90_WORD_COMMAND;
+	command[1] = count;
+	command[2] = opcode;
+	/* Param1, Param2 and all 20 mandatory Random filler bytes are zero. */
+	put_unaligned_le16(rng90_crc(command + 1, count - 2), command + count - 1);
+
+	return count + 1;
+}
+
+static int rng90_execute(struct rng90 *priv, u8 opcode, unsigned int typ_us,
+			 unsigned int deadline_us, size_t reply_size,
+			 bool *verdict)
+{
+	ktime_t deadline;
+	size_t len;
+	int ret;
+
+	if (verdict)
+		*verdict = false;
+
+	/* Reset both FIFO counters before starting a new transaction. */
+	ret = rng90_word(priv, RNG90_WORD_RESET);
+	if (ret)
+		return ret;
+	len = rng90_build_command(priv->command, opcode);
+	ret = rng90_write(priv, priv->command, len);
+	memzero_explicit(priv->command, sizeof(priv->command));
+	if (ret)
+		return ret;
+
+	/*
+	 * The separate write supplies the STOP that starts execution. The chip
+	 * NACKs while busy. hid-cp2112 adds USB status/data exchanges to each poll;
+	 * its transfer timeout can extend elapsed time beyond this deadline.
+	 */
+	deadline = ktime_add_us(ktime_get(), deadline_us);
+	usleep_range(typ_us, typ_us + RNG90_POLL_US);
+	for (;;) {
+		ret = rng90_receive(priv, reply_size, verdict);
+		if (!rng90_nack(ret) || ktime_after(ktime_get(), deadline))
+			return ret;
+		usleep_range(RNG90_POLL_US, 2 * RNG90_POLL_US);
+	}
+}
+
+static int rng90_wake_once(struct rng90 *priv)
+{
+	int ret;
+
+	/* Addressing an asleep chip wakes it; RESET is harmless if awake. */
+	ret = rng90_word(priv, RNG90_WORD_RESET);
+	if (ret && !rng90_nack(ret))
+		return ret;
+	usleep_range(RNG90_WAKE_US, RNG90_WAKE_US + 500);
+
+	/* An acknowledged sleep discards any stale command/result state. */
+	ret = rng90_word(priv, RNG90_WORD_SLEEP);
+	if (ret)
+		return ret;
+
+	/*
+	 * Do not require zero-length writes or I2C_M_IGNORE_NAK. The chip
+	 * ignores bytes following its address during wake (section 3.2.1).
+	 * An adapter may retry and ACK RESET after the chip has woken.
+	 */
+	ret = rng90_word(priv, RNG90_WORD_RESET);
+	if (ret && !rng90_nack(ret))
+		return ret;
+	usleep_range(RNG90_WAKE_US, RNG90_WAKE_US + 500);
+	ret = rng90_receive(priv, RNG90_STATUS_SIZE, NULL);
+	if (ret)
+		return ret;
+	if (priv->reply[1] != RNG90_STATUS_WAKE)
+		return rng90_status(priv, priv->reply[1], NULL);
+
+	return 0;
+}
+
+static int rng90_start(struct rng90 *priv)
+{
+	int attempt, ret = -EIO;
+
+	lockdep_assert_held(&priv->lock);
+	if (priv->failed)
+		return -EIO;
+	if (priv->suspended)
+		return -EHOSTDOWN;
+	if (priv->state == RNG90_AWAKE)
+		return 0;
+
+	for (attempt = 0; attempt < 2; attempt++) {
+		/* Allow a command already in progress to finish (section 3.2.3). */
+		if (attempt)
+			usleep_range(RNG90_SETTLE_US,
+				     RNG90_SETTLE_US + RNG90_POLL_US);
+		ret = rng90_wake_once(priv);
+		if (!ret || priv->failed || rng90_gone(ret))
+			break;
+	}
+	if (ret)
+		goto fail;
+
+	ret = rng90_execute(priv, RNG90_OP_INFO, RNG90_INFO_TYP_US,
+			    RNG90_INFO_DEADLINE_US, RNG90_INFO_SIZE, NULL);
+	if (ret)
+		goto fail;
+	/* Byte 1 of the four-byte Info payload identifies RNG90. */
+	if (priv->reply[2] != RNG90_DEVICE_ID) {
+		ret = -ENODEV;
+		goto fail;
+	}
+	priv->state = RNG90_AWAKE;
+	priv->first_random = true;
+	rng90_wipe(priv);
+	return 0;
+
+fail:
+	rng90_invalidate(priv);
+	return ret;
+}
+
+static void rng90_stop(struct rng90 *priv)
+{
+	int attempt, ret = 0;
+
+	lockdep_assert_held(&priv->lock);
+	if (priv->state == RNG90_ASLEEP)
+		goto out;
+
+	for (attempt = 0; attempt < 2; attempt++) {
+		/*
+		 * An unknown device may be asleep (the first address wakes it) or
+		 * busy, and any state can see a transient transfer failure. Wait
+		 * out the device cases before the second bounded attempt.
+		 */
+		if (attempt)
+			usleep_range(RNG90_SETTLE_US,
+				     RNG90_SETTLE_US + RNG90_POLL_US);
+		ret = rng90_word(priv, RNG90_WORD_SLEEP);
+		if (!ret) {
+			priv->state = RNG90_ASLEEP;
+			goto out;
+		}
+		if (rng90_gone(ret))
+			break;
+	}
+	if (!rng90_gone(ret))
+		dev_warn_ratelimited(&priv->client->dev, "sleep failed: %d\n",
+				     ret);
+	priv->state = RNG90_UNKNOWN;
+out:
+	priv->first_random = true;
+	rng90_wipe(priv);
+}
+
+/* The first Random after a wake also runs the self-tests (Table 5-6, note 1). */
+static int rng90_random(struct rng90 *priv, bool *verdict)
+{
+	if (priv->first_random)
+		return rng90_execute(priv, RNG90_OP_RANDOM,
+				     RNG90_FIRST_RANDOM_TYP_US,
+				     RNG90_FIRST_RANDOM_DEADLINE_US,
+				     RNG90_REPLY_SIZE, verdict);
+	return rng90_execute(priv, RNG90_OP_RANDOM, RNG90_RANDOM_TYP_US,
+			     RNG90_RANDOM_DEADLINE_US, RNG90_REPLY_SIZE,
+			     verdict);
+}
+
+static int rng90_init(struct hwrng *rng)
+{
+	struct rng90 *priv = container_of(rng, struct rng90, rng);
+	int ret;
+
+	mutex_lock(&priv->lock);
+	ret = rng90_start(priv);
+	mutex_unlock(&priv->lock);
+	return ret;
+}
+
+static void rng90_cleanup(struct hwrng *rng)
+{
+	struct rng90 *priv = container_of(rng, struct rng90, rng);
+
+	mutex_lock(&priv->lock);
+	rng90_stop(priv);
+	mutex_unlock(&priv->lock);
+}
+
+static int rng90_read(struct hwrng *rng, void *data, size_t max, bool wait)
+{
+	struct rng90 *priv = container_of(rng, struct rng90, rng);
+	bool verdict = false;
+	int ret = 0;
+
+	if (!max)
+		return 0;
+	if (!wait) {
+		if (!mutex_trylock(&priv->lock))
+			return 0;
+	} else if (mutex_lock_interruptible(&priv->lock)) {
+		return -ERESTARTSYS;
+	}
+	if (priv->failed) {
+		ret = -EIO;
+		goto out;
+	}
+	if (priv->suspended) {
+		ret = -EHOSTDOWN;
+		goto out;
+	}
+	/* No asynchronous prefetch; a nonblocking read starts no I2C transfers. */
+	if (!wait)
+		goto out;
+	ret = rng90_start(priv);
+	if (ret)
+		goto fail;
+	ret = rng90_random(priv, &verdict);
+	if (ret && !verdict && !rng90_gone(ret)) {
+		/*
+		 * Recover once from transport/framing errors or lost command state.
+		 * An acknowledged Sleep discards stale output; a fresh Random
+		 * supplies the retried result. Never reread the failed response.
+		 * A CRC-verified device verdict is final for this read, and so is
+		 * a vanished device.
+		 */
+		rng90_invalidate(priv);
+		ret = rng90_start(priv);
+		if (!ret)
+			ret = rng90_random(priv, &verdict);
+	}
+	if (ret)
+		goto fail;
+
+	/* Publish nothing until the complete frame and its CRC have passed. */
+	max = min_t(size_t, max, RNG90_RANDOM_SIZE);
+	memcpy(data, priv->reply + 1, max);
+	priv->first_random = false;
+	priv->strikes = 0;
+	rng90_wipe(priv);
+	ret = max;
+	goto out;
+
+fail:
+	rng90_invalidate(priv);
+	if (ret == -ECONNRESET)
+		ret = -EIO;
+	else if (ret == -ESHUTDOWN)
+		ret = -ENODEV;
+out:
+	mutex_unlock(&priv->lock);
+	return ret;
+}
+
+static int rng90_suspend(struct device *dev)
+{
+	struct rng90 *priv = dev_get_drvdata(dev);
+
+	mutex_lock(&priv->lock);
+	priv->suspended = true;
+	rng90_stop(priv);
+	mutex_unlock(&priv->lock);
+	return 0;
+}
+
+static int rng90_resume(struct device *dev)
+{
+	struct rng90 *priv = dev_get_drvdata(dev);
+
+	mutex_lock(&priv->lock);
+	priv->suspended = false;
+	/* The rail may have been lost; re-identify on the next blocking read. */
+	rng90_invalidate(priv);
+	mutex_unlock(&priv->lock);
+	return 0;
+}
+
+static DEFINE_SIMPLE_DEV_PM_OPS(rng90_pm_ops, rng90_suspend, rng90_resume);
+
+static int rng90_probe(struct i2c_client *client)
+{
+	struct device *dev = &client->dev;
+	struct rng90 *priv;
+	int ret;
+
+	if (client->addr != RNG90_ADDRESS || client->flags & I2C_CLIENT_TEN)
+		return dev_err_probe(dev, -ENODEV, "RNG90 requires 7-bit address 0x40\n");
+	if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C))
+		return dev_err_probe(dev, -EOPNOTSUPP, "raw I2C transfers are required\n");
+	priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
+	if (!priv)
+		return -ENOMEM;
+	priv->client = client;
+	mutex_init(&priv->lock);
+	i2c_set_clientdata(client, priv);
+	priv->rng.name = devm_kasprintf(dev, GFP_KERNEL, "rng90-%s", dev_name(dev));
+	if (!priv->rng.name)
+		return -ENOMEM;
+	priv->rng.init = rng90_init;
+	priv->rng.cleanup = rng90_cleanup;
+	priv->rng.read = rng90_read;
+	/* Security strength does not establish entropy density. */
+	priv->rng.quality = 1;
+
+	/* Identify even when a higher-quality hwrng is already selected. */
+	ret = rng90_init(&priv->rng);
+	if (ret)
+		return dev_err_probe(dev, ret, "wake/identity check failed\n");
+	rng90_cleanup(&priv->rng);
+	return devm_hwrng_register(dev, &priv->rng);
+}
+
+static void rng90_shutdown(struct i2c_client *client)
+{
+	rng90_suspend(&client->dev);
+}
+
+static const struct of_device_id rng90_of_match[] = {
+	{ .compatible = "microchip,rng90" },
+	{ }
+};
+MODULE_DEVICE_TABLE(of, rng90_of_match);
+
+static const struct i2c_device_id rng90_id[] = {
+	{ "rng90" },
+	{ }
+};
+MODULE_DEVICE_TABLE(i2c, rng90_id);
+
+static struct i2c_driver rng90_driver = {
+	.driver = {
+		.name = "rng90",
+		.of_match_table = rng90_of_match,
+		.pm = pm_sleep_ptr(&rng90_pm_ops),
+	},
+	.probe = rng90_probe,
+	.shutdown = rng90_shutdown,
+	.id_table = rng90_id,
+};
+module_i2c_driver(rng90_driver);
+
+MODULE_AUTHOR("Patrick Tudor <ptudor@ptudor.net>");
+MODULE_DESCRIPTION("Microchip RNG90 I2C hardware random number generator");
+MODULE_LICENSE("GPL");

-- 
2.56.0


^ permalink raw reply related	[flat|nested] 6+ messages in thread

* [PATCH 3/3] hwrng: rng90: add KUnit tests
  2026-10-24 20:18 [PATCH 0/3] hwrng: add Microchip RNG90 support Patrick Tudor
  2026-10-24 20:18 ` [PATCH 1/3] dt-bindings: trivial-devices: add Microchip RNG90 Patrick Tudor
  2026-10-24 20:18 ` [PATCH 2/3] hwrng: add Microchip RNG90 driver Patrick Tudor
@ 2026-10-24 20:18 ` Patrick Tudor
  2026-10-24 23:22   ` sashiko-bot
  2 siblings, 1 reply; 6+ messages in thread
From: Patrick Tudor @ 2026-10-24 20:18 UTC (permalink / raw)
  To: Rob Herring, Krzysztof Kozlowski, Conor Dooley, Olivia Mackall,
	Herbert Xu
  Cc: devicetree, linux-kernel, linux-crypto, Patrick Tudor

Exercise the RNG90 driver through the I2C core with a scripted adapter.
The 32 cases cover published CRC vectors, exact command framing, wake and
busy behavior, response validation, bounded recovery, health-failure
latching, nonblocking reads, suspend and resume, cleanup, and device
removal.

Keep the tests built-in-only so their deterministic fixture data can never
be registered with the hwrng core.

Signed-off-by: Patrick Tudor <ptudor@ptudor.net>
---
 drivers/char/hw_random/Kconfig       |  11 +
 drivers/char/hw_random/rng90-kunit.c | 820 +++++++++++++++++++++++++++++++++++
 drivers/char/hw_random/rng90.c       |   4 +
 3 files changed, 835 insertions(+)

diff --git a/drivers/char/hw_random/Kconfig b/drivers/char/hw_random/Kconfig
index af276d476..e0e9eaa44 100644
--- a/drivers/char/hw_random/Kconfig
+++ b/drivers/char/hw_random/Kconfig
@@ -103,6 +103,17 @@ config HW_RANDOM_RNG90
 	  To compile this driver as a module, choose M here: the module
 	  will be called rng90.
 
+config HW_RANDOM_RNG90_KUNIT_TEST
+	bool "KUnit tests for the RNG90 driver" if !KUNIT_ALL_TESTS
+	depends on KUNIT=y && HW_RANDOM_RNG90=y
+	default KUNIT_ALL_TESTS
+	help
+	  Exercise the real driver against a scripted I2C adapter, without
+	  RNG90 hardware.
+	  Tests cover command framing, CRC validation and polling behavior.
+	  They also cover recovery, health failures, and suspend and resume.
+	  No predictable test data is registered with the hwrng core.
+
 config HW_RANDOM_BA431
 	tristate "Silex Insight BA431 Random Number Generator support"
 	depends on HAS_IOMEM
diff --git a/drivers/char/hw_random/rng90-kunit.c b/drivers/char/hw_random/rng90-kunit.c
new file mode 100644
index 000000000..f6daffb2e
--- /dev/null
+++ b/drivers/char/hw_random/rng90-kunit.c
@@ -0,0 +1,820 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/* Included by rng90.c only for built-in KUnit tests. */
+
+#include <kunit/test.h>
+#include <linux/limits.h>
+
+static const u8 rng90_test_reset[] = { 0x00 };
+static const u8 rng90_test_sleep[] = { 0x01 };
+static const u8 rng90_test_wake[] = { 0x04, 0x11, 0x33, 0x43 };
+static const u8 rng90_test_info_command[] = {
+	0x03, 0x07, 0x30, 0x00, 0x00, 0x00, 0x03, 0x5d,
+};
+
+static const u8 rng90_test_info_reply[] = {
+	0x07, 0x00, 0xd0, 0x20, 0x10, 0xac, 0x35,
+};
+
+static const u8 rng90_test_random_command[] = {
+	0x03, 0x1b, 0x16, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x7d, 0xe0,
+};
+
+static const u8 rng90_test_random_reply[] = {
+	0x23, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06,
+	0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e,
+	0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16,
+	0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e,
+	0x1f, 0x70, 0xfa,
+};
+
+/* A busy step NACKs every matching poll until the driver moves on. */
+#define RNG90_TEST_BUSY		UINT_MAX
+
+struct rng90_test_step {
+	bool read;
+	const u8 *bytes;
+	size_t len;
+	int result;
+	unsigned int repeat;
+	unsigned int hits;
+};
+
+struct rng90_test_context {
+	struct kunit *test;
+	struct i2c_adapter adapter;
+	struct i2c_client *client;
+	struct rng90 priv;
+	struct rng90_test_step steps[48];
+	unsigned int count;
+	unsigned int next;
+};
+
+static bool rng90_test_matches(const struct rng90_test_step *step,
+			       const struct i2c_msg *msg)
+{
+	if (msg->flags != (step->read ? I2C_M_RD : 0) || msg->len != step->len)
+		return false;
+	return step->read || !memcmp(msg->buf, step->bytes, step->len);
+}
+
+static int rng90_test_xfer(struct i2c_adapter *adapter, struct i2c_msg *msgs,
+			   int num)
+{
+	struct rng90_test_context *ctx = adapter->algo_data;
+	struct rng90_test_step *step;
+	struct kunit *test = ctx->test;
+
+	if (num != 1 || ctx->next >= ctx->count)
+		goto unexpected;
+	step = &ctx->steps[ctx->next];
+	if (step->repeat == RNG90_TEST_BUSY && step->hits &&
+	    !rng90_test_matches(step, msgs)) {
+		/* The driver stopped polling; the next step is due. */
+		if (++ctx->next >= ctx->count)
+			goto unexpected;
+		step = &ctx->steps[ctx->next];
+	}
+	KUNIT_EXPECT_EQ(test, msgs[0].addr, RNG90_ADDRESS);
+	KUNIT_EXPECT_EQ(test, msgs[0].flags, step->read ? I2C_M_RD : 0);
+	KUNIT_EXPECT_EQ(test, msgs[0].len, step->len);
+	if (msgs[0].len != step->len)
+		return -EIO;
+	if (!step->read)
+		KUNIT_EXPECT_MEMEQ(test, msgs[0].buf, step->bytes, step->len);
+	else if (step->result == 1)
+		memcpy(msgs[0].buf, step->bytes, step->len);
+	if (++step->hits == step->repeat)
+		ctx->next++;
+
+	return step->result;
+
+unexpected:
+	KUNIT_FAIL(test, "unexpected I2C transfer");
+	return -EIO;
+}
+
+static u32 rng90_test_functionality(struct i2c_adapter *adapter)
+{
+	return I2C_FUNC_I2C;
+}
+
+static const struct i2c_algorithm rng90_test_algorithm = {
+	.master_xfer = rng90_test_xfer,
+	.functionality = rng90_test_functionality,
+};
+
+static int rng90_test_init(struct kunit *test)
+{
+	struct rng90_test_context *ctx;
+	int ret;
+
+	ctx = kunit_kzalloc(test, sizeof(*ctx), GFP_KERNEL);
+	if (!ctx)
+		return -ENOMEM;
+	test->priv = ctx;
+	ctx->test = test;
+	ctx->adapter.owner = THIS_MODULE;
+	ctx->adapter.algo = &rng90_test_algorithm;
+	ctx->adapter.algo_data = ctx;
+	strscpy(ctx->adapter.name, "rng90-kunit", sizeof(ctx->adapter.name));
+	ret = i2c_add_adapter(&ctx->adapter);
+	if (ret)
+		return ret;
+	ctx->client = i2c_new_dummy_device(&ctx->adapter, RNG90_ADDRESS);
+	if (IS_ERR(ctx->client)) {
+		ret = PTR_ERR(ctx->client);
+		i2c_del_adapter(&ctx->adapter);
+		return ret;
+	}
+	ctx->priv.client = ctx->client;
+	mutex_init(&ctx->priv.lock);
+	i2c_set_clientdata(ctx->client, &ctx->priv);
+	/* Never register this predictable test source with the hwrng core. */
+	return 0;
+}
+
+/* Verify the script was consumed, allowing a final busy step at the deadline. */
+static void rng90_test_settled(struct rng90_test_context *ctx)
+{
+	if (ctx->next < ctx->count &&
+	    ctx->steps[ctx->next].repeat == RNG90_TEST_BUSY &&
+	    ctx->steps[ctx->next].hits)
+		ctx->next++;
+	KUNIT_EXPECT_EQ(ctx->test, ctx->next, ctx->count);
+}
+
+static void rng90_test_exit(struct kunit *test)
+{
+	struct rng90_test_context *ctx = test->priv;
+
+	rng90_test_settled(ctx);
+	i2c_unregister_device(ctx->client);
+	i2c_del_adapter(&ctx->adapter);
+}
+
+/* Verify the previous script was consumed before starting another. */
+static void rng90_test_restart(struct rng90_test_context *ctx)
+{
+	rng90_test_settled(ctx);
+	memset(ctx->steps, 0, sizeof(ctx->steps));
+	ctx->count = 0;
+	ctx->next = 0;
+}
+
+static void rng90_test_add(struct rng90_test_context *ctx, bool read,
+			   const u8 *bytes, size_t len, int result)
+{
+	struct rng90_test_step *step;
+
+	KUNIT_ASSERT_LT(ctx->test, ctx->count, ARRAY_SIZE(ctx->steps));
+	step = &ctx->steps[ctx->count++];
+	step->read = read;
+	step->bytes = bytes;
+	step->len = len;
+	step->result = result;
+	step->repeat = 1;
+	step->hits = 0;
+}
+
+/* NACK a read of len bytes repeat times. */
+static void rng90_test_add_busy(struct rng90_test_context *ctx, size_t len,
+				int nack, unsigned int repeat)
+{
+	rng90_test_add(ctx, true, NULL, len, nack);
+	ctx->steps[ctx->count - 1].repeat = repeat;
+}
+
+static void rng90_test_start_script(struct rng90_test_context *ctx, int wake_ret)
+{
+	rng90_test_add(ctx, false, rng90_test_reset, 1, wake_ret);
+	rng90_test_add(ctx, false, rng90_test_sleep, 1, 1);
+	rng90_test_add(ctx, false, rng90_test_reset, 1, wake_ret);
+	rng90_test_add(ctx, true, rng90_test_wake, 4, 1);
+	rng90_test_add(ctx, false, rng90_test_reset, 1, 1);
+	rng90_test_add(ctx, false, rng90_test_info_command, 8, 1);
+	rng90_test_add(ctx, true, rng90_test_info_reply, 7, 1);
+}
+
+static void rng90_test_random_script(struct rng90_test_context *ctx,
+				     const u8 *reply, int read_ret)
+{
+	rng90_test_add(ctx, false, rng90_test_reset, 1, 1);
+	rng90_test_add(ctx, false, rng90_test_random_command, 28, 1);
+	rng90_test_add(ctx, true, reply, 35, read_ret);
+}
+
+/* Script busy NACKs for Random, followed by an optional reply. */
+static void rng90_test_random_busy_script(struct rng90_test_context *ctx,
+					  int nack, unsigned int busy,
+					  const u8 *reply)
+{
+	rng90_test_add(ctx, false, rng90_test_reset, 1, 1);
+	rng90_test_add(ctx, false, rng90_test_random_command, 28, 1);
+	rng90_test_add_busy(ctx, 35, nack, busy);
+	if (reply)
+		rng90_test_add(ctx, true, reply, 35, 1);
+}
+
+/* A four-byte status reply with the 0xff padding returned by an over-read. */
+static void rng90_test_status_reply(u8 *reply, u8 status)
+{
+	memset(reply, 0xff, RNG90_REPLY_SIZE);
+	reply[0] = RNG90_STATUS_SIZE;
+	reply[1] = status;
+	put_unaligned_le16(rng90_crc(reply, 2), reply + 2);
+}
+
+static void rng90_test_wiped(struct kunit *test)
+{
+	struct rng90_test_context *ctx = test->priv;
+	u8 zero[RNG90_REPLY_SIZE] = { };
+
+	KUNIT_EXPECT_MEMEQ(test, ctx->priv.command, zero, sizeof(ctx->priv.command));
+	KUNIT_EXPECT_MEMEQ(test, ctx->priv.reply, zero, sizeof(ctx->priv.reply));
+}
+
+static void rng90_crc_vectors_test(struct kunit *test)
+{
+	static const u8 info[] = { 0x07, 0x30, 0x01, 0x00, 0x00 };
+	static const u8 text[] = "MICROCHIPTECHNOLOGY";
+
+	/* Independent published vectors: DS40002499A section 5.4. */
+	KUNIT_EXPECT_EQ(test, rng90_crc(info, sizeof(info)), 0xd700);
+	KUNIT_EXPECT_EQ(test, rng90_crc(text, sizeof(text) - 1), 0xfee3);
+	KUNIT_EXPECT_EQ(test, rng90_crc(rng90_test_wake, 2), 0x4333);
+}
+
+static void rng90_command_vectors_test(struct kunit *test)
+{
+	u8 command[RNG90_COMMAND_SIZE];
+
+	KUNIT_EXPECT_EQ(test, rng90_build_command(command, RNG90_OP_RANDOM), 28);
+	KUNIT_EXPECT_MEMEQ(test, command, rng90_test_random_command, 28);
+	KUNIT_EXPECT_EQ(test, rng90_build_command(command, RNG90_OP_INFO), 8);
+	KUNIT_EXPECT_MEMEQ(test, command, rng90_test_info_command, 8);
+}
+
+static void rng90_wake_nack_test(struct kunit *test)
+{
+	struct rng90_test_context *ctx = test->priv;
+
+	rng90_test_start_script(ctx, -ENXIO);
+	KUNIT_EXPECT_EQ(test, rng90_init(&ctx->priv.rng), 0);
+	KUNIT_EXPECT_EQ(test, ctx->priv.state, RNG90_AWAKE);
+	KUNIT_EXPECT_TRUE(test, ctx->priv.first_random);
+	rng90_test_wiped(test);
+}
+
+static void rng90_cp2112_wake_timeout_test(struct kunit *test)
+{
+	struct rng90_test_context *ctx = test->priv;
+
+	rng90_test_start_script(ctx, -ETIMEDOUT);
+	KUNIT_EXPECT_EQ(test, rng90_init(&ctx->priv.rng), 0);
+	KUNIT_EXPECT_EQ(test, ctx->priv.state, RNG90_AWAKE);
+	KUNIT_EXPECT_TRUE(test, ctx->priv.first_random);
+}
+
+static void rng90_wake_adapter_retry_test(struct kunit *test)
+{
+	struct rng90_test_context *ctx = test->priv;
+
+	rng90_test_start_script(ctx, 1);
+	KUNIT_EXPECT_EQ(test, rng90_init(&ctx->priv.rng), 0);
+}
+
+/* A vanished device ends the wake loop after one attempt. */
+static void rng90_vanished_wake_test(struct kunit *test)
+{
+	struct rng90_test_context *ctx = test->priv;
+
+	rng90_test_add(ctx, false, rng90_test_reset, 1, -ENODEV);
+	KUNIT_EXPECT_EQ(test, rng90_init(&ctx->priv.rng), -ENODEV);
+	KUNIT_EXPECT_EQ(test, ctx->priv.state, RNG90_UNKNOWN);
+	rng90_test_wiped(test);
+}
+
+static void rng90_absent_bounded_test(struct kunit *test)
+{
+	struct rng90_test_context *ctx = test->priv;
+	int i;
+
+	for (i = 0; i < 2; i++) {
+		rng90_test_add(ctx, false, rng90_test_reset, 1, -ENXIO);
+		rng90_test_add(ctx, false, rng90_test_sleep, 1, -ENXIO);
+	}
+	KUNIT_EXPECT_EQ(test, rng90_init(&ctx->priv.rng), -ENXIO);
+	KUNIT_EXPECT_EQ(test, ctx->priv.state, RNG90_UNKNOWN);
+	rng90_test_wiped(test);
+}
+
+static void rng90_wake_requires_valid_reply_test(struct kunit *test)
+{
+	struct rng90_test_context *ctx = test->priv;
+	u8 bad_wake[] = { 0x04, 0x11, 0x33, 0x42 };
+	int i;
+
+	/* Accepting a bridge wake timeout must not bypass wake validation. */
+	for (i = 0; i < 2; i++) {
+		rng90_test_add(ctx, false, rng90_test_reset, 1, -ETIMEDOUT);
+		rng90_test_add(ctx, false, rng90_test_sleep, 1, 1);
+		rng90_test_add(ctx, false, rng90_test_reset, 1, -ETIMEDOUT);
+		rng90_test_add(ctx, true, bad_wake, sizeof(bad_wake), 1);
+	}
+	KUNIT_EXPECT_EQ(test, rng90_init(&ctx->priv.rng), -EBADMSG);
+	KUNIT_EXPECT_EQ(test, ctx->priv.state, RNG90_UNKNOWN);
+	rng90_test_wiped(test);
+}
+
+static void rng90_identity_test(struct kunit *test)
+{
+	struct rng90_test_context *ctx = test->priv;
+	u8 wrong[7];
+
+	memcpy(wrong, rng90_test_info_reply, sizeof(wrong));
+	wrong[2] = 0xd1;
+	put_unaligned_le16(rng90_crc(wrong, 5), wrong + 5);
+	rng90_test_start_script(ctx, -ENXIO);
+	ctx->steps[ctx->count - 1].bytes = wrong;
+	KUNIT_EXPECT_EQ(test, rng90_init(&ctx->priv.rng), -ENODEV);
+	KUNIT_EXPECT_EQ(test, ctx->priv.state, RNG90_UNKNOWN);
+	rng90_test_wiped(test);
+}
+
+static void rng90_info_busy_test(struct kunit *test)
+{
+	struct rng90_test_context *ctx = test->priv;
+
+	rng90_test_add(ctx, false, rng90_test_reset, 1, -ENXIO);
+	rng90_test_add(ctx, false, rng90_test_sleep, 1, 1);
+	rng90_test_add(ctx, false, rng90_test_reset, 1, -ENXIO);
+	rng90_test_add(ctx, true, rng90_test_wake, 4, 1);
+	rng90_test_add(ctx, false, rng90_test_reset, 1, 1);
+	rng90_test_add(ctx, false, rng90_test_info_command, 8, 1);
+	rng90_test_add_busy(ctx, 7, -ENXIO, 2);
+	rng90_test_add(ctx, true, rng90_test_info_reply, 7, 1);
+	KUNIT_EXPECT_EQ(test, rng90_init(&ctx->priv.rng), 0);
+	KUNIT_EXPECT_EQ(test, ctx->priv.state, RNG90_AWAKE);
+	rng90_test_wiped(test);
+}
+
+static void rng90_random_test(struct kunit *test)
+{
+	struct rng90_test_context *ctx = test->priv;
+	u8 output[40];
+	u8 untouched[8];
+
+	ctx->priv.state = RNG90_AWAKE;
+	ctx->priv.first_random = true;
+	memset(output, 0xa5, sizeof(output));
+	memset(untouched, 0xa5, sizeof(untouched));
+	rng90_test_random_script(ctx, rng90_test_random_reply, 1);
+	KUNIT_EXPECT_EQ(test, rng90_read(&ctx->priv.rng, output, sizeof(output), true), 32);
+	KUNIT_EXPECT_MEMEQ(test, output, rng90_test_random_reply + 1, 32);
+	KUNIT_EXPECT_MEMEQ(test, output + 32, untouched, sizeof(untouched));
+	KUNIT_EXPECT_FALSE(test, ctx->priv.first_random);
+	rng90_test_wiped(test);
+}
+
+static void rng90_small_buffer_test(struct kunit *test)
+{
+	struct rng90_test_context *ctx = test->priv;
+	u8 output[8] = { };
+	u8 zero[4] = { };
+
+	ctx->priv.state = RNG90_AWAKE;
+	rng90_test_random_script(ctx, rng90_test_random_reply, 1);
+	KUNIT_EXPECT_EQ(test, rng90_read(&ctx->priv.rng, output, 4, true), 4);
+	KUNIT_EXPECT_MEMEQ(test, output, rng90_test_random_reply + 1, 4);
+	KUNIT_EXPECT_MEMEQ(test, output + 4, zero, 4);
+	rng90_test_wiped(test);
+}
+
+static void rng90_busy_poll_test(struct kunit *test)
+{
+	struct rng90_test_context *ctx = test->priv;
+	/* The I2C core, the Raspberry Pi controllers and hid-cp2112. */
+	static const int nacks[] = { -ENXIO, -EREMOTEIO, -ETIMEDOUT };
+	u8 output[32];
+	int i;
+
+	for (i = 0; i < ARRAY_SIZE(nacks); i++) {
+		ctx->priv.state = RNG90_AWAKE;
+		ctx->priv.first_random = false;
+		rng90_test_random_busy_script(ctx, nacks[i], 3, rng90_test_random_reply);
+		KUNIT_EXPECT_EQ(test, rng90_read(&ctx->priv.rng, output, sizeof(output), true), 32);
+		KUNIT_EXPECT_MEMEQ(test, output, rng90_test_random_reply + 1, 32);
+		KUNIT_EXPECT_EQ(test, ctx->priv.state, RNG90_AWAKE);
+		rng90_test_wiped(test);
+		rng90_test_restart(ctx);
+	}
+}
+
+static void rng90_busy_deadline_test(struct kunit *test)
+{
+	struct rng90_test_context *ctx = test->priv;
+	u8 output[32], untouched[32];
+	ktime_t start;
+	s64 elapsed;
+
+	ctx->priv.state = RNG90_AWAKE;
+	ctx->priv.first_random = false;
+	memset(output, 0xa5, sizeof(output));
+	memset(untouched, 0xa5, sizeof(untouched));
+	/* Still busy at the deadline, and again after resynchronization. */
+	rng90_test_random_busy_script(ctx, -ETIMEDOUT, RNG90_TEST_BUSY, NULL);
+	rng90_test_start_script(ctx, -ETIMEDOUT);
+	rng90_test_random_busy_script(ctx, -ETIMEDOUT, RNG90_TEST_BUSY, NULL);
+	start = ktime_get();
+	KUNIT_EXPECT_EQ(test, rng90_read(&ctx->priv.rng, output, sizeof(output), true), -ETIMEDOUT);
+	elapsed = ktime_ms_delta(ktime_get(), start);
+	/* The subsequent-Random deadline, then the first-Random one: 34 + 92 ms. */
+	KUNIT_EXPECT_GE(test, elapsed, 126);
+	KUNIT_EXPECT_MEMEQ(test, output, untouched, sizeof(output));
+	KUNIT_EXPECT_EQ(test, ctx->priv.state, RNG90_UNKNOWN);
+	KUNIT_EXPECT_TRUE(test, ctx->priv.first_random);
+	KUNIT_EXPECT_FALSE(test, ctx->priv.failed);
+	rng90_test_wiped(test);
+}
+
+/* A device verdict ends the read without changing the caller's buffer. */
+static void rng90_rejected_read(struct kunit *test, const u8 *reply, int read_ret,
+				int expected)
+{
+	struct rng90_test_context *ctx = test->priv;
+	u8 output[32], untouched[32];
+
+	ctx->priv.state = RNG90_AWAKE;
+	memset(output, 0xa5, sizeof(output));
+	memset(untouched, 0xa5, sizeof(untouched));
+	rng90_test_random_script(ctx, reply, read_ret);
+	KUNIT_EXPECT_EQ(test, rng90_read(&ctx->priv.rng, output, sizeof(output), true), expected);
+	KUNIT_EXPECT_MEMEQ(test, output, untouched, sizeof(output));
+	KUNIT_EXPECT_EQ(test, ctx->priv.state, RNG90_UNKNOWN);
+	KUNIT_EXPECT_TRUE(test, ctx->priv.first_random);
+	rng90_test_wiped(test);
+}
+
+/* A repeated transport failure is returned after one recovery attempt. */
+static void rng90_rejected_twice(struct kunit *test, const u8 *reply, int read_ret,
+				 int expected)
+{
+	struct rng90_test_context *ctx = test->priv;
+	u8 output[32], untouched[32];
+
+	ctx->priv.state = RNG90_AWAKE;
+	memset(output, 0xa5, sizeof(output));
+	memset(untouched, 0xa5, sizeof(untouched));
+	rng90_test_random_script(ctx, reply, read_ret);
+	rng90_test_start_script(ctx, -ENXIO);
+	rng90_test_random_script(ctx, reply, read_ret);
+	KUNIT_EXPECT_EQ(test, rng90_read(&ctx->priv.rng, output, sizeof(output), true), expected);
+	KUNIT_EXPECT_MEMEQ(test, output, untouched, sizeof(output));
+	KUNIT_EXPECT_EQ(test, ctx->priv.state, RNG90_UNKNOWN);
+	KUNIT_EXPECT_TRUE(test, ctx->priv.first_random);
+	rng90_test_wiped(test);
+}
+
+static void rng90_crc_failure_test(struct kunit *test)
+{
+	u8 reply[35];
+
+	memcpy(reply, rng90_test_random_reply, sizeof(reply));
+	reply[17] ^= 0x01;
+	rng90_rejected_twice(test, reply, 1, -EBADMSG);
+}
+
+static void rng90_resync_recovery_test(struct kunit *test)
+{
+	struct rng90_test_context *ctx = test->priv;
+	u8 reply[35], output[32];
+
+	memcpy(reply, rng90_test_random_reply, sizeof(reply));
+	reply[17] ^= 0x01;
+	ctx->priv.state = RNG90_AWAKE;
+	/* Recover from one damaged frame with sleep, wake, Info and a fresh Random. */
+	rng90_test_random_script(ctx, reply, 1);
+	rng90_test_start_script(ctx, -ENXIO);
+	rng90_test_random_script(ctx, rng90_test_random_reply, 1);
+	KUNIT_EXPECT_EQ(test, rng90_read(&ctx->priv.rng, output, sizeof(output), true), 32);
+	KUNIT_EXPECT_MEMEQ(test, output, rng90_test_random_reply + 1, sizeof(output));
+	KUNIT_EXPECT_EQ(test, ctx->priv.state, RNG90_AWAKE);
+	KUNIT_EXPECT_FALSE(test, ctx->priv.failed);
+	rng90_test_wiped(test);
+}
+
+static void rng90_transport_eio_recovery_test(struct kunit *test)
+{
+	struct rng90_test_context *ctx = test->priv;
+	u8 output[32];
+
+	ctx->priv.state = RNG90_AWAKE;
+	/* An adapter's generic I/O failure is transport, not a chip verdict. */
+	rng90_test_random_script(ctx, rng90_test_random_reply, -EIO);
+	rng90_test_start_script(ctx, -ENXIO);
+	rng90_test_random_script(ctx, rng90_test_random_reply, 1);
+	KUNIT_EXPECT_EQ(test, rng90_read(&ctx->priv.rng, output, sizeof(output), true), 32);
+	KUNIT_EXPECT_MEMEQ(test, output, rng90_test_random_reply + 1, sizeof(output));
+	KUNIT_EXPECT_EQ(test, ctx->priv.state, RNG90_AWAKE);
+	rng90_test_wiped(test);
+}
+
+/*
+ * The transfer in flight at a USB disconnect fails with -ESHUTDOWN: no
+ * recovery attempt, nothing published, -ENODEV to the caller.
+ */
+static void rng90_vanished_read_test(struct kunit *test)
+{
+	rng90_rejected_read(test, rng90_test_random_reply, -ESHUTDOWN, -ENODEV);
+}
+
+static void rng90_corrupt_status_test(struct kunit *test)
+{
+	struct rng90_test_context *ctx = test->priv;
+	u8 reply[35];
+
+	rng90_test_status_reply(reply, RNG90_STATUS_HEALTH);
+	put_unaligned_le16(rng90_crc(reply, 2) ^ 1, reply + 2);
+	rng90_rejected_twice(test, reply, 1, -EBADMSG);
+	/* A corrupted frame is rejected before its status can be trusted. */
+	KUNIT_EXPECT_FALSE(test, ctx->priv.failed);
+	KUNIT_EXPECT_EQ(test, ctx->priv.strikes, 0U);
+}
+
+static void rng90_count_failure_test(struct kunit *test)
+{
+	struct rng90_test_context *ctx = test->priv;
+	static const u8 counts[] = { 0, 3, 5, 34, 36, 255 };
+	u8 reply[35];
+	int i;
+
+	memcpy(reply, rng90_test_random_reply, sizeof(reply));
+	for (i = 0; i < ARRAY_SIZE(counts); i++) {
+		reply[0] = counts[i];
+		rng90_rejected_twice(test, reply, 1, -EPROTO);
+		rng90_test_restart(ctx);
+	}
+}
+
+static void rng90_short_read_test(struct kunit *test)
+{
+	rng90_rejected_twice(test, rng90_test_random_reply, 0, -EPROTO);
+}
+
+static void rng90_short_write_test(struct kunit *test)
+{
+	struct rng90_test_context *ctx = test->priv;
+	u8 output[32] = { }, zero[32] = { };
+
+	ctx->priv.state = RNG90_AWAKE;
+	rng90_test_add(ctx, false, rng90_test_reset, 1, 1);
+	rng90_test_add(ctx, false, rng90_test_random_command, 28, 0);
+	rng90_test_start_script(ctx, -ENXIO);
+	rng90_test_add(ctx, false, rng90_test_reset, 1, 1);
+	rng90_test_add(ctx, false, rng90_test_random_command, 28, 0);
+	KUNIT_EXPECT_EQ(test, rng90_read(&ctx->priv.rng, output, sizeof(output), true), -EPROTO);
+	KUNIT_EXPECT_MEMEQ(test, output, zero, sizeof(output));
+	KUNIT_EXPECT_EQ(test, ctx->priv.state, RNG90_UNKNOWN);
+	rng90_test_wiped(test);
+}
+
+static void rng90_status_is_not_entropy_test(struct kunit *test)
+{
+	struct rng90_test_context *ctx = test->priv;
+	/* Success, parse error, execution error and an unknown code. */
+	static const u8 statuses[] = { 0x00, 0x03, 0x0f, 0x55 };
+	u8 reply[35];
+	int i;
+
+	for (i = 0; i < ARRAY_SIZE(statuses); i++) {
+		rng90_test_status_reply(reply, statuses[i]);
+		rng90_rejected_read(test, reply, 1, -EIO);
+		KUNIT_EXPECT_FALSE(test, ctx->priv.failed);
+		KUNIT_EXPECT_EQ(test, ctx->priv.strikes, 0U);
+		rng90_test_restart(ctx);
+	}
+}
+
+static void rng90_transient_status_test(struct kunit *test)
+{
+	struct rng90_test_context *ctx = test->priv;
+	/* A wake group or a rejected command where a result was due. */
+	static const u8 statuses[] = { RNG90_STATUS_WAKE, RNG90_STATUS_COMM };
+	static const int reported[] = { -EIO, -EBADMSG };
+	u8 reply[35], output[32];
+	int i;
+
+	for (i = 0; i < ARRAY_SIZE(statuses); i++) {
+		rng90_test_status_reply(reply, statuses[i]);
+		ctx->priv.state = RNG90_AWAKE;
+		rng90_test_random_script(ctx, reply, 1);
+		rng90_test_start_script(ctx, -ENXIO);
+		rng90_test_random_script(ctx, rng90_test_random_reply, 1);
+		KUNIT_EXPECT_EQ(test, rng90_read(&ctx->priv.rng, output, sizeof(output), true), 32);
+		KUNIT_EXPECT_MEMEQ(test, output, rng90_test_random_reply + 1, 32);
+		rng90_test_restart(ctx);
+		rng90_rejected_twice(test, reply, 1, reported[i]);
+		KUNIT_EXPECT_FALSE(test, ctx->priv.failed);
+		KUNIT_EXPECT_EQ(test, ctx->priv.strikes, 0U);
+		rng90_test_restart(ctx);
+	}
+}
+
+static void rng90_latched_failure(struct kunit *test, u8 status)
+{
+	struct rng90_test_context *ctx = test->priv;
+	u8 reply[35], output[32] = { };
+	unsigned int i;
+
+	rng90_test_status_reply(reply, status);
+	for (i = 1; i <= RNG90_HEALTH_STRIKES; i++) {
+		/* Each failure requires a sleep/wake before the next Random. */
+		if (i == 1)
+			ctx->priv.state = RNG90_AWAKE;
+		else
+			rng90_test_start_script(ctx, -ENXIO);
+		rng90_test_random_script(ctx, reply, 1);
+		KUNIT_EXPECT_EQ(test, rng90_read(&ctx->priv.rng, output, sizeof(output), true),
+				-EIO);
+		KUNIT_EXPECT_EQ(test, ctx->priv.strikes, i);
+		KUNIT_EXPECT_EQ(test, ctx->priv.state, RNG90_UNKNOWN);
+		if (i < RNG90_HEALTH_STRIKES)
+			KUNIT_EXPECT_FALSE(test, ctx->priv.failed);
+		else
+			KUNIT_EXPECT_TRUE(test, ctx->priv.failed);
+		rng90_test_restart(ctx);
+	}
+	/* The latch blocks reads and init; suspend may still send Sleep. */
+	KUNIT_EXPECT_EQ(test, rng90_read(&ctx->priv.rng, output, sizeof(output), true), -EIO);
+	KUNIT_EXPECT_EQ(test, rng90_init(&ctx->priv.rng), -EIO);
+	/* Model an awake chip after a verdict, despite the driver's unknown state. */
+	rng90_test_add(ctx, false, rng90_test_sleep, 1, 1);
+	KUNIT_EXPECT_EQ(test, rng90_suspend(&ctx->client->dev), 0);
+	KUNIT_EXPECT_EQ(test, ctx->priv.state, RNG90_ASLEEP);
+	KUNIT_EXPECT_EQ(test, rng90_resume(&ctx->client->dev), 0);
+	KUNIT_EXPECT_EQ(test, rng90_read(&ctx->priv.rng, output, sizeof(output), false), -EIO);
+	rng90_test_wiped(test);
+}
+
+static void rng90_health_failure_test(struct kunit *test)
+{
+	rng90_latched_failure(test, RNG90_STATUS_HEALTH);
+}
+
+static void rng90_selftest_failure_test(struct kunit *test)
+{
+	rng90_latched_failure(test, RNG90_STATUS_SELFTEST);
+}
+
+static void rng90_strikes_reset_test(struct kunit *test)
+{
+	struct rng90_test_context *ctx = test->priv;
+	u8 reply[35], output[32];
+
+	rng90_test_status_reply(reply, RNG90_STATUS_HEALTH);
+	/* A verified block resets the count between two pairs of failures. */
+	ctx->priv.state = RNG90_AWAKE;
+	rng90_test_random_script(ctx, reply, 1);
+	KUNIT_EXPECT_EQ(test, rng90_read(&ctx->priv.rng, output, sizeof(output), true), -EIO);
+	rng90_test_restart(ctx);
+	rng90_test_start_script(ctx, -ENXIO);
+	rng90_test_random_script(ctx, reply, 1);
+	KUNIT_EXPECT_EQ(test, rng90_read(&ctx->priv.rng, output, sizeof(output), true), -EIO);
+	KUNIT_EXPECT_EQ(test, ctx->priv.strikes, 2U);
+	rng90_test_restart(ctx);
+	rng90_test_start_script(ctx, -ENXIO);
+	rng90_test_random_script(ctx, rng90_test_random_reply, 1);
+	KUNIT_EXPECT_EQ(test, rng90_read(&ctx->priv.rng, output, sizeof(output), true), 32);
+	KUNIT_EXPECT_EQ(test, ctx->priv.strikes, 0U);
+	rng90_test_restart(ctx);
+	rng90_test_random_script(ctx, reply, 1);
+	KUNIT_EXPECT_EQ(test, rng90_read(&ctx->priv.rng, output, sizeof(output), true), -EIO);
+	rng90_test_restart(ctx);
+	rng90_test_start_script(ctx, -ENXIO);
+	rng90_test_random_script(ctx, reply, 1);
+	KUNIT_EXPECT_EQ(test, rng90_read(&ctx->priv.rng, output, sizeof(output), true), -EIO);
+	KUNIT_EXPECT_EQ(test, ctx->priv.strikes, 2U);
+	KUNIT_EXPECT_FALSE(test, ctx->priv.failed);
+	rng90_test_wiped(test);
+}
+
+static void rng90_nonblocking_test(struct kunit *test)
+{
+	struct rng90_test_context *ctx = test->priv;
+	u8 output[32] = { };
+
+	KUNIT_EXPECT_EQ(test, rng90_read(&ctx->priv.rng, output, sizeof(output), false), 0);
+	mutex_lock(&ctx->priv.lock);
+	KUNIT_EXPECT_EQ(test, rng90_read(&ctx->priv.rng, output, sizeof(output), false), 0);
+	mutex_unlock(&ctx->priv.lock);
+	KUNIT_EXPECT_EQ(test, rng90_read(&ctx->priv.rng, output, 0, true), 0);
+}
+
+static void rng90_suspend_resume_test(struct kunit *test)
+{
+	struct rng90_test_context *ctx = test->priv;
+	u8 output[32] = { };
+
+	ctx->priv.state = RNG90_AWAKE;
+	memset(ctx->priv.reply, 0xa5, sizeof(ctx->priv.reply));
+	rng90_test_add(ctx, false, rng90_test_sleep, 1, 1);
+	KUNIT_EXPECT_EQ(test, rng90_suspend(&ctx->client->dev), 0);
+	KUNIT_EXPECT_EQ(test, rng90_read(&ctx->priv.rng, output, sizeof(output), true), -EHOSTDOWN);
+	rng90_test_wiped(test);
+	KUNIT_EXPECT_EQ(test, rng90_resume(&ctx->client->dev), 0);
+	rng90_test_start_script(ctx, -ENXIO);
+	rng90_test_random_script(ctx, rng90_test_random_reply, 1);
+	KUNIT_EXPECT_EQ(test, rng90_read(&ctx->priv.rng, output, sizeof(output), true), 32);
+	KUNIT_EXPECT_MEMEQ(test, output, rng90_test_random_reply + 1, 32);
+	rng90_test_wiped(test);
+}
+
+static void rng90_cleanup_test(struct kunit *test)
+{
+	struct rng90_test_context *ctx = test->priv;
+
+	ctx->priv.state = RNG90_AWAKE;
+	rng90_test_add(ctx, false, rng90_test_sleep, 1, 1);
+	rng90_cleanup(&ctx->priv.rng);
+	KUNIT_EXPECT_EQ(test, ctx->priv.state, RNG90_ASLEEP);
+	rng90_cleanup(&ctx->priv.rng);
+	rng90_test_wiped(test);
+}
+
+static void rng90_unknown_cleanup_test(struct kunit *test)
+{
+	struct rng90_test_context *ctx = test->priv;
+
+	ctx->priv.state = RNG90_UNKNOWN;
+	/* Model an asleep chip: the first address wakes it, then the retry sleeps it. */
+	rng90_test_add(ctx, false, rng90_test_sleep, 1, -ENXIO);
+	rng90_test_add(ctx, false, rng90_test_sleep, 1, 1);
+	rng90_cleanup(&ctx->priv.rng);
+	KUNIT_EXPECT_EQ(test, ctx->priv.state, RNG90_ASLEEP);
+	rng90_test_wiped(test);
+}
+
+/*
+ * A removed USB adapter can answer -ENODEV: cleanup writes Sleep once, does
+ * not wait to retry, and records the state unknown.
+ * The one scripted step is the attempt budget; a second write fails the test.
+ * The adapter is not deleted here, so the client outlives the cleanup.
+ */
+static void rng90_vanished_cleanup_test(struct kunit *test)
+{
+	struct rng90_test_context *ctx = test->priv;
+
+	ctx->priv.state = RNG90_AWAKE;
+	rng90_test_add(ctx, false, rng90_test_sleep, 1, -ENODEV);
+	rng90_cleanup(&ctx->priv.rng);
+	KUNIT_EXPECT_EQ(test, ctx->priv.state, RNG90_UNKNOWN);
+	rng90_test_wiped(test);
+}
+
+static struct kunit_case rng90_test_cases[] = {
+	KUNIT_CASE(rng90_crc_vectors_test),
+	KUNIT_CASE(rng90_command_vectors_test),
+	KUNIT_CASE(rng90_wake_nack_test),
+	KUNIT_CASE(rng90_cp2112_wake_timeout_test),
+	KUNIT_CASE(rng90_wake_adapter_retry_test),
+	KUNIT_CASE(rng90_vanished_wake_test),
+	KUNIT_CASE(rng90_absent_bounded_test),
+	KUNIT_CASE(rng90_wake_requires_valid_reply_test),
+	KUNIT_CASE(rng90_identity_test),
+	KUNIT_CASE(rng90_info_busy_test),
+	KUNIT_CASE(rng90_random_test),
+	KUNIT_CASE(rng90_small_buffer_test),
+	KUNIT_CASE(rng90_busy_poll_test),
+	KUNIT_CASE(rng90_busy_deadline_test),
+	KUNIT_CASE(rng90_crc_failure_test),
+	KUNIT_CASE(rng90_resync_recovery_test),
+	KUNIT_CASE(rng90_transport_eio_recovery_test),
+	KUNIT_CASE(rng90_vanished_read_test),
+	KUNIT_CASE(rng90_corrupt_status_test),
+	KUNIT_CASE(rng90_count_failure_test),
+	KUNIT_CASE(rng90_short_read_test),
+	KUNIT_CASE(rng90_short_write_test),
+	KUNIT_CASE(rng90_status_is_not_entropy_test),
+	KUNIT_CASE(rng90_transient_status_test),
+	KUNIT_CASE(rng90_health_failure_test),
+	KUNIT_CASE(rng90_selftest_failure_test),
+	KUNIT_CASE(rng90_strikes_reset_test),
+	KUNIT_CASE(rng90_nonblocking_test),
+	KUNIT_CASE(rng90_suspend_resume_test),
+	KUNIT_CASE(rng90_cleanup_test),
+	KUNIT_CASE(rng90_unknown_cleanup_test),
+	KUNIT_CASE(rng90_vanished_cleanup_test),
+	{ }
+};
+
+static struct kunit_suite rng90_test_suite = {
+	.name = "rng90",
+	.init = rng90_test_init,
+	.exit = rng90_test_exit,
+	.test_cases = rng90_test_cases,
+};
+
+kunit_test_suite(rng90_test_suite);
diff --git a/drivers/char/hw_random/rng90.c b/drivers/char/hw_random/rng90.c
index ec9ee3e27..9bac89cc4 100644
--- a/drivers/char/hw_random/rng90.c
+++ b/drivers/char/hw_random/rng90.c
@@ -549,3 +549,7 @@ module_i2c_driver(rng90_driver);
 MODULE_AUTHOR("Patrick Tudor <ptudor@ptudor.net>");
 MODULE_DESCRIPTION("Microchip RNG90 I2C hardware random number generator");
 MODULE_LICENSE("GPL");
+
+#if IS_ENABLED(CONFIG_HW_RANDOM_RNG90_KUNIT_TEST)
+#include "rng90-kunit.c"
+#endif

-- 
2.56.0


^ permalink raw reply related	[flat|nested] 6+ messages in thread

* Re: [PATCH 3/3] hwrng: rng90: add KUnit tests
  2026-10-24 20:18 ` [PATCH 3/3] hwrng: rng90: add KUnit tests Patrick Tudor
@ 2026-10-24 23:22   ` sashiko-bot
  0 siblings, 0 replies; 6+ messages in thread
From: sashiko-bot @ 2026-10-24 23:22 UTC (permalink / raw)
  To: Patrick Tudor, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
	Olivia Mackall, Herbert Xu
  Cc: devicetree, linux-crypto

Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [Medium] hwrng: rng90: NULL dereference in rng90_test_exit() if rng90_test_init() fails
--

commit 738c911c8957ee3bcb09ed18f6daa72a41daed79
Author: Patrick Tudor <ptudor@ptudor.net>

hwrng: rng90: add KUnit tests

This commit adds built-in KUnit tests for the RNG90 hardware random number
generator driver using a scripted I2C adapter to exercise command framing,
CRC validation, polling, recovery, health failures, and power management.

> diff --git a/drivers/char/hw_random/rng90-kunit.c b/drivers/char/hw_random/rng90-kunit.c
> new file mode 100644
> index 0000000000000..f6daffb2e3d97
> --- /dev/null
> +++ b/drivers/char/hw_random/rng90-kunit.c
> @@ -0,0 +1,820 @@

[ ... ]

> +static int rng90_test_init(struct kunit *test)
> +{
> +	struct rng90_test_context *ctx;
> +	int ret;
> +
> +	ctx = kunit_kzalloc(test, sizeof(*ctx), GFP_KERNEL);
> +	if (!ctx)
> +		return -ENOMEM;
> +	test->priv = ctx;
> +	ctx->test = test;
> +	ctx->adapter.owner = THIS_MODULE;
> +	ctx->adapter.algo = &rng90_test_algorithm;
> +	ctx->adapter.algo_data = ctx;
> +	strscpy(ctx->adapter.name, "rng90-kunit", sizeof(ctx->adapter.name));
> +	ret = i2c_add_adapter(&ctx->adapter);
> +	if (ret)
> +		return ret;
> +	ctx->client = i2c_new_dummy_device(&ctx->adapter, RNG90_ADDRESS);
> +	if (IS_ERR(ctx->client)) {
> +		ret = PTR_ERR(ctx->client);
> +		i2c_del_adapter(&ctx->adapter);
> +		return ret;
> +	}
> +	ctx->priv.client = ctx->client;
> +	mutex_init(&ctx->priv.lock);
> +	i2c_set_clientdata(ctx->client, &ctx->priv);
> +	/* Never register this predictable test source with the hwrng core. */
> +	return 0;
> +}
> +
> +/* Verify the script was consumed, allowing a final busy step at the deadline. */
> +static void rng90_test_settled(struct rng90_test_context *ctx)
> +{
> +	if (ctx->next < ctx->count &&
> +	    ctx->steps[ctx->next].repeat == RNG90_TEST_BUSY &&
> +	    ctx->steps[ctx->next].hits)
> +		ctx->next++;
> +	KUNIT_EXPECT_EQ(ctx->test, ctx->next, ctx->count);
> +}
> +
> +static void rng90_test_exit(struct kunit *test)
> +{
> +	struct rng90_test_context *ctx = test->priv;
> +
> +	rng90_test_settled(ctx);
> +	i2c_unregister_device(ctx->client);
> +	i2c_del_adapter(&ctx->adapter);
> +}

[Severity: Medium]
Can rng90_test_exit() dereference a null pointer or repeat adapter teardown
if rng90_test_init() fails?

In KUnit, kunit_run_case_catch_errors() invokes kunit_try_run_case_cleanup()
-> kunit_run_case_cleanup() -> suite->exit(test) even when suite->init(test)
returns a non-zero error code.

If kunit_kzalloc() fails in rng90_test_init(), test->priv remains NULL, and
rng90_test_exit() will dereference ctx in rng90_test_settled(ctx),
i2c_unregister_device(ctx->client), and i2c_del_adapter(&ctx->adapter).

Additionally, test->priv = ctx is assigned before i2c_add_adapter() and
i2c_new_dummy_device(). If i2c_new_dummy_device() fails, rng90_test_init()
calls i2c_del_adapter(&ctx->adapter) and leaves ctx->client as an error
pointer, after which rng90_test_exit() will run and call
i2c_del_adapter(&ctx->adapter) a second time.

Would it make sense to assign test->priv = ctx only after initialization
succeeds in rng90_test_init(), and check for !ctx in rng90_test_exit() before
cleaning up?

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20261024-rng90-submit-v1-v1-0-e265cfabc9af@ptudor.net?part=3

^ permalink raw reply	[flat|nested] 6+ messages in thread

* Re: [PATCH 1/3] dt-bindings: trivial-devices: add Microchip RNG90
  2026-10-24 20:18 ` [PATCH 1/3] dt-bindings: trivial-devices: add Microchip RNG90 Patrick Tudor
@ 2026-10-26 12:43   ` Krzysztof Kozlowski
  0 siblings, 0 replies; 6+ messages in thread
From: Krzysztof Kozlowski @ 2026-10-26 12:43 UTC (permalink / raw)
  To: Patrick Tudor
  Cc: Rob Herring, Krzysztof Kozlowski, Conor Dooley, Olivia Mackall,
	Herbert Xu, devicetree, linux-kernel, linux-crypto

On Sat, Oct 24, 2026 at 08:18:22PM +0000, Patrick Tudor wrote:
> Document the Microchip RNG90 I2C cryptographic random number generator.
> The device needs only a compatible string and reg property, so the
> trivial-devices schema is sufficient.
> 
> Signed-off-by: Patrick Tudor <ptudor@ptudor.net>
> ---
>  Documentation/devicetree/bindings/trivial-devices.yaml | 2 ++
>  1 file changed, 2 insertions(+)

Acked-by: Krzysztof Kozlowski <krzysztof.kozlowski@oss.qualcomm.com>

Best regards,
Krzysztof


^ permalink raw reply	[flat|nested] 6+ messages in thread

end of thread, other threads:[~2026-10-26 12:43 UTC | newest]

Thread overview: 6+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-10-24 20:18 [PATCH 0/3] hwrng: add Microchip RNG90 support Patrick Tudor
2026-10-24 20:18 ` [PATCH 1/3] dt-bindings: trivial-devices: add Microchip RNG90 Patrick Tudor
2026-10-26 12:43   ` Krzysztof Kozlowski
2026-10-24 20:18 ` [PATCH 2/3] hwrng: add Microchip RNG90 driver Patrick Tudor
2026-10-24 20:18 ` [PATCH 3/3] hwrng: rng90: add KUnit tests Patrick Tudor
2026-10-24 23:22   ` sashiko-bot

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox