// SPDX-License-Identifier: GPL-2.0-or-later
/*
 * Copyright 2018 NXP
 * Copyright (C) 2018 Pengutronix, Roland Hieber <r.hieber@pengutronix.de>
 *
 * CAAM RNG self-test -- based on the vendor patch for U-Boot:
 * https://portland.source.codeaurora.org/patches/external/imxsupport/uboot-imx/imx_v2016.03_4.1.15_2.0.0_ga/HAB-238-Run-RNG-self-test-for-impacted-i.MX-chips.zip
 *
 * |    From: Utkarsh Gupta <utkarsh.gupta@nxp.com>
 * |    Subject: [PATCH] HAB-238 Run RNG self test for impacted i.MX chips
 * |
 * |    Patch is only applicable to imx_v2016.03_4.1.15_2.0.0_ga branch of u-boot.
 * |    Please adapt the patch for your respective release version.
 * |
 * |    Background:
 * |    Few i.MX chips which have HAB 4.2.3 or beyond, have oberserved following
 * |    warning message generated by HAB due to incorrect implementation of drng
 * |    self test in boot ROM.
 * |
 * |        Event       |0xdb|0x0024|0x42| SRCE Field: 69 30 e1 1d
 * |                    |    |      |    |             STS = HAB_WARNING (0x69)
 * |                    |    |      |    |             RSN = HAB_ENG_FAIL (0x30)
 * |                    |    |      |    |             CTX = HAB_CTX_ENTRY (0xE1)
 * |                    |    |      |    |             ENG = HAB_ENG_CAAM (0x1D)
 * |                    |    |      |    | Evt Data (hex):
 * |                    |    |      |    |  00 08 00 02 40 00 36 06 55 55 00 03 00 00 00 00
 * |                    |    |      |    |  00 00 00 00 00 00 00 00 00 00 00 01
 * |
 * |    It is recommended to run this rng self test before any RNG related crypto
 * |    implementations are done.
 * [...]
 * |
 * |    Signed-off-by: Utkarsh Gupta <utkarsh.gupta@nxp.com>
 *
 * Known impacted chips:
 *
 * - i.MX6DQ+ silicon revision 1.1
 * - i.MX6DQ silicon revision 1.6
 * - i.MX6DLS silicon revision 1.4
 * - i.MX6SX silicon revision 1.4
 * - i.MX6UL silicon revision 1.2
 * - i.MX67SD silicon revision 1.3
 */

#define pr_fmt(fmt) "rng_self_test: " fmt

#include <dma.h>
#include <common.h>
#include <linux/kernel.h>

#include "error.h"
#include "regs.h"
#include "jr.h"
#include "rng_self_test.h"

static const u32 rng_dsc1[] = {
	0xb0800036, 0x04800010, 0x3c85a15b, 0x50a9d0b1,
	0x71a09fee, 0x2eecf20b, 0x02800020, 0xb267292e,
	0x85bf712d, 0xe85ff43a, 0xa716b7fb, 0xc40bb528,
	0x27b6f564, 0x8821cb5d, 0x9b5f6c26, 0x12a00020,
	0x0a20de17, 0x6529357e, 0x316277ab, 0x2846254e,
	0x34d23ba5, 0x6f5e9c32, 0x7abdc1bb, 0x0197a385,
	0x82500405, 0xa2000001, 0x10880004, 0x00000005,
	0x12820004, 0x00000020, 0x82500001, 0xa2000001,
	0x10880004, 0x40000045, 0x02800020, 0x8f389cc7,
	0xe7f7cbb0, 0x6bf2073d, 0xfc380b6d, 0xb22e9d1a,
	0xee64fcb7, 0xa2b48d49, 0xdf9bc3a4, 0x82500009,
	0xa2000001, 0x10880004, 0x00000005, 0x82500001,
	0x60340020, 0xFFFFFFFF, 0xa2000001, 0x10880004,
	0x00000005, 0x8250000d
};

static const u8 rng_result1[] = {
	0x3a, 0xfe, 0x2c, 0x87, 0xcc, 0xb6, 0x44, 0x49,
	0x19, 0x16, 0x9a, 0x74, 0xa1, 0x31, 0x8b, 0xef,
	0xf4, 0x86, 0x0b, 0xb9, 0x5e, 0xee, 0xae, 0x91,
	0x92, 0xf4, 0xa9, 0x8f, 0xb0, 0x37, 0x18, 0xa4
};

static const u32 rng_dsc2[] = {
	0xb080003a, 0x04800020, 0x27b73130, 0x30b4b10f,
	0x7c62b1ad, 0x77abe899, 0x67452301, 0xefcdab89,
	0x98badcfe, 0x10325476, 0x02800020, 0x63f757cf,
	0xb9165584, 0xc3c1b407, 0xcc4ce8ad, 0x1ffe8a58,
	0xfb4fa893, 0xbb5f4af0, 0x3fb946a1, 0x12a00020,
	0x56cbcaa5, 0xfff3adad, 0xe804dcbf, 0x9a900c71,
	0xa42017e3, 0x826948e2, 0xd0cfeb3e, 0xaf1a136a,
	0x82500405, 0xa2000001, 0x10880004, 0x00000005,
	0x12820004, 0x00000020, 0x82500001, 0xa2000001,
	0x10880004, 0x40000045, 0x02800020, 0x2e882f8a,
	0xe929943e, 0x8132c0a8, 0x12037f90, 0x809fbd66,
	0x8684ea04, 0x00cbafa7, 0x7b82d12a, 0x82500009,
	0xa2000001, 0x10880004, 0x00000005, 0x82500001,
	0x60340020, 0xFFFFFFFF, 0xa2000001, 0x10880004,
	0x00000005, 0x8250000d
};

static const u8 rng_result2[] = {
	0x76, 0x87, 0x66, 0x4e, 0xd8, 0x1d, 0x1f, 0x43,
	0x76, 0x50, 0x85, 0x5d, 0x1e, 0x1d, 0x9d, 0x0f,
	0x93, 0x75, 0x83, 0xff, 0x9a, 0x9b, 0x61, 0xa9,
	0xa5, 0xeb, 0xa3, 0x28, 0x2a, 0x15, 0xc1, 0x57
};

/*
 * construct_rng_self_test_jobdesc() - Implement destination address in RNG self test descriptors
 * Returns zero on success, and negative on error.
 */
static void construct_rng_self_test_jobdesc(u32 *desc, const u32 *rng_st_dsc, u8 *res_addr, int desc_size)
{
	int result_addr_idx = desc_size - 5;
	int i;

	for (i = 0; i < desc_size; i++) {
		desc[i] = rng_st_dsc[i];
	}

	/* Replace destination address in the descriptor */
	desc[result_addr_idx] = (u32)res_addr;
}

/* rng_self_test_done() - callback for caam_jr_enqueue */
static void rng_self_test_done(struct device_d *dev, u32 *desc, u32 err, void *arg)
{
	int * job_err = arg;
	*job_err = err;
}

/*
 * caam_rng_self_test() - Perform RNG self test
 * Returns zero on success, and negative on error.
 *
 * Some chips with HAB >= 4.2.3 have an incorrect implementation of the
 * RNG self-test in ROM code. In this case, a software self-test should
 * be run to ensure correctness of the RNG. By enabling this config
 * option, the software self-test is run automatically when this case
 * is detected.
 *
 * Currently known impacted chips:
 * * i.MX6DQ+ silicon revision 1.1
 * * i.MX6DQ silicon revision 1.6
 * * i.MX6DLS silicon revision 1.4
 * * i.MX6SX silicon revision 1.4
 * * i.MX6UL silicon revision 1.2
 * * i.MX67SD silicon revision 1.3
 *
 */
int caam_rng_self_test(struct device_d *dev, const u8 caam_era, const u8 rngvid, const u8 rngrev)
{
	int ret, desc_size = 0, result_size = 0, job_err = 0;
	const u32 *rng_st_dsc;
	const u8 *exp_result;
	u32 *desc;
	u8 *result;

	pr_debug("got CAAM ERA %d, RNG Version ID %d, RNG revision %d\n",
		 caam_era, rngvid, rngrev);

	if (caam_era < 8 && rngvid == 4 && rngrev < 3) {
		/* older affected i.MX chipsets have CAAM < 8 and have RNG4 < 4.3 */
		rng_st_dsc = rng_dsc1;
		desc_size = ARRAY_SIZE(rng_dsc1);
		exp_result = rng_result1;
		result_size = ARRAY_SIZE(rng_result1);
	} else if (caam_era >= 8 || (rngvid >= 4 && rngrev >= 3)) {
		/* newer affected chipsets have CAAM >= 8 or RNG4 >= 4.3 */
		rng_st_dsc = rng_dsc2;
		desc_size = ARRAY_SIZE(rng_dsc2);
		exp_result = rng_result2;
		result_size = ARRAY_SIZE(rng_result2);
	} else {
		pr_err("Invalid CAAM version: %d,%d,%d\n",
				caam_era, rngvid, rngrev);
		return -EINVAL;
	}

	result = dma_alloc(sizeof(*result) * result_size);
	desc = dma_alloc(sizeof(*desc) * desc_size);

	if (!result || !desc) {
		ret = -ENOMEM;
		goto err;
	}

	construct_rng_self_test_jobdesc(desc, rng_st_dsc, result, desc_size);

	dma_sync_single_for_device((unsigned long)desc,
			desc_size * sizeof(*desc), DMA_TO_DEVICE);
	dma_sync_single_for_device((unsigned long)result,
			result_size * sizeof(*result), DMA_FROM_DEVICE);

	/* wait for job completion */
	ret = caam_jr_enqueue(dev, desc, rng_self_test_done, &job_err);
	if (ret) {
		pr_err("Running RNG self-test descriptor failed: %d %s\n",
				ret, strerror(ret));
		goto err;
	}
	if (job_err) {
		ret = -EINVAL;
		pr_err("Job Error:\n");
		caam_jr_strstatus(dev, job_err);
		goto err;
	}

	dma_sync_single_for_cpu((unsigned long)result, result_size * sizeof(*result),
			DMA_FROM_DEVICE);

	if (memcmp(result, exp_result, sizeof(*result) * result_size) != 0) {
		pr_err("RNG self-test failed with unexpected result\n");
		ret = -ERANGE;
		goto err;
	}

	pr_info("RNG software self-test passed\n");
	ret = 0;

err:
	dma_free(desc);
	dma_free(result);
	return ret;
}
