Module Name: src Committed By: martin Date: Mon May 18 18:45:40 UTC 2020
Modified Files: src/sys/arch/arm/dts [netbsd-9]: sun50i-a64.dtsi src/sys/arch/arm/sunxi [netbsd-9]: files.sunxi sun50i_a64_ccu.c src/sys/arch/evbarm/conf [netbsd-9]: GENERIC64 Added Files: src/sys/arch/arm/sunxi [netbsd-9]: sun8i_crypto.c sun8i_crypto.h Log Message: Pull up following revision(s) (requested by riastradh in ticket #912): sys/arch/arm/dts/sun50i-a64.dtsi: revision 1.14 sys/arch/evbarm/conf/GENERIC64: revision 1.116 sys/arch/evbarm/conf/GENERIC64: revision 1.131 sys/arch/arm/sunxi/sun50i_a64_ccu.c: revision 1.21 sys/arch/arm/sunxi/sun50i_a64_ccu.c: revision 1.22 sys/arch/arm/sunxi/sun8i_crypto.c: revision 1.10 sys/arch/arm/sunxi/sun8i_crypto.c: revision 1.11 sys/arch/arm/sunxi/sun8i_crypto.c: revision 1.12 sys/arch/arm/sunxi/sun8i_crypto.c: revision 1.1 sys/arch/arm/sunxi/sun8i_crypto.c: revision 1.2 sys/arch/arm/sunxi/sun8i_crypto.c: revision 1.3 sys/arch/arm/sunxi/sun8i_crypto.h: revision 1.1 sys/arch/arm/sunxi/sun8i_crypto.c: revision 1.4 sys/arch/arm/sunxi/sun8i_crypto.c: revision 1.5 sys/arch/arm/sunxi/sun8i_crypto.c: revision 1.6 sys/arch/arm/sunxi/sun8i_crypto.c: revision 1.7 sys/arch/arm/sunxi/sun8i_crypto.c: revision 1.8 sys/arch/arm/sunxi/sun8i_crypto.c: revision 1.9 sys/arch/arm/sunxi/files.sunxi: revision 1.67 Add crypto engine clock Add SUNXI_CCU_NM_ROUND_DOWN to CE clock, fix pll parents to use 2X outputs Add crypto engine block. Draft driver for Allwinner Crypto Engine. Found on, e.g., the Pinebook. Only used for TRNG at the moment, but hooking it up to opencrypto(9) shouldn't be too hard if anyone still cares about that these days. The distribution of the alleged TRNG is very nonuniform distributed seems to alternate between toward runs with exceptionally high fractions of 0 bits and runs with exceptionally high fractions of 1 bits -- initially all my samples were mostly 0's, and then all my samples were mostly 1's, and now I'm seeing more oscillation between these runs. So I've wired it up as RND_TYPE_UNKNOWN, not RND_TYPE_RNG (it will immediately flunk our rngtest and be disabled), and I estimated it to provide at most one bit of entropy per byte of data -- which may still be optimistic. I also added a sysctl node hw.sun8icryptoN.rng to read out 1024-byte samples for analysis, and I left the driver commented out in GENERIC64 for now. (If anyone has contacts at Allwinner who can tell us about how the alleged TRNG is supposed to work, please let me know!) Reduce some duplicated bus_dma clutter. Factor out some of the self-test logic used for debugging. Add missing bus_dmamap_sync(POSTWRITE) while here. Make sure ERESTART doesn't come flying out to userland. I picked ERESTART to mean `all channels are occupied' because that's what opencrypto(9) uses to decide whether to queue a request, but it's not appropriate for sysctl(2) to return that. Avoid a race between interruption and reacquisition of lock. Otherwise, we would have leaked the memory in this case. Tidy up comments. Oops -- forgot to kmem_free. Fix typo -- acknowledge interrupts _and_ errors. Reduce entropy estimate for sun8icrypto TRNG. NIST's SP800-90B entropy estimation tools put it at no more than .08 bits of entropy per byte of data(!), so estimate 100 bits of data per bit of entropy. This is probably not conservative enough -- the NIST tools were written without knowledge of how this alleged TRNG works! Knowledge of the physics of how the TRNG is supposed to work could probably enable a better job at predicting the outputs. While here, bump the size of data we can sample directly with sysctl to 4096 bytes. Enable sun8icrypto in GENERIC64. But set its entropy estimate to zero until we have a better idea of how it works. Can't really hurt this way unless sun8icrypto is maliciously dependent on all other inputs to the entropy pool, which seems unlikely. Fix (presently harmless) psato. Don't overwrite cy_root_node; use cy_trng_node as intended. Fix previous brainfart. Don't use the uninitialized trng node as the root node -- derp. Instead, use the root node as the root node, and initialize the trng node here. To generate a diff of this commit: cvs rdiff -u -r1.11.2.2 -r1.11.2.3 src/sys/arch/arm/dts/sun50i-a64.dtsi cvs rdiff -u -r1.65 -r1.65.2.1 src/sys/arch/arm/sunxi/files.sunxi cvs rdiff -u -r1.13.2.3 -r1.13.2.4 src/sys/arch/arm/sunxi/sun50i_a64_ccu.c cvs rdiff -u -r0 -r1.14.2.2 src/sys/arch/arm/sunxi/sun8i_crypto.c cvs rdiff -u -r0 -r1.1.10.2 src/sys/arch/arm/sunxi/sun8i_crypto.h cvs rdiff -u -r1.103.2.10 -r1.103.2.11 src/sys/arch/evbarm/conf/GENERIC64 Please note that diffs are not public domain; they are subject to the copyright notices on the relevant files.
Modified files: Index: src/sys/arch/arm/dts/sun50i-a64.dtsi diff -u src/sys/arch/arm/dts/sun50i-a64.dtsi:1.11.2.2 src/sys/arch/arm/dts/sun50i-a64.dtsi:1.11.2.3 --- src/sys/arch/arm/dts/sun50i-a64.dtsi:1.11.2.2 Mon Nov 18 19:31:00 2019 +++ src/sys/arch/arm/dts/sun50i-a64.dtsi Mon May 18 18:45:40 2020 @@ -1,4 +1,4 @@ -/* $NetBSD: sun50i-a64.dtsi,v 1.11.2.2 2019/11/18 19:31:00 martin Exp $ */ +/* $NetBSD: sun50i-a64.dtsi,v 1.11.2.3 2020/05/18 18:45:40 martin Exp $ */ /*- * Copyright (c) 2017 Jared McNeill <jmcne...@invisible.ca> @@ -50,6 +50,15 @@ resets = <&ccu RST_BUS_THS>; #thermal-sensor-cells = <0>; }; + + crypto: crypto@1c15000 { + compatible = "allwinner,sun50i-a64-crypto"; + reg = <0x01c15000 0x1000>; + interrupts = <GIC_SPI 94 IRQ_TYPE_LEVEL_HIGH>; + clocks = <&ccu CLK_BUS_CE>, <&ccu CLK_CE>; + clock-names = "bus", "mod"; + resets = <&ccu RST_BUS_CE>; + }; }; /* PMU interrupt numbers are wrong in mainline dts */ Index: src/sys/arch/arm/sunxi/files.sunxi diff -u src/sys/arch/arm/sunxi/files.sunxi:1.65 src/sys/arch/arm/sunxi/files.sunxi:1.65.2.1 --- src/sys/arch/arm/sunxi/files.sunxi:1.65 Fri Jun 14 21:26:36 2019 +++ src/sys/arch/arm/sunxi/files.sunxi Mon May 18 18:45:40 2020 @@ -1,4 +1,4 @@ -# $NetBSD: files.sunxi,v 1.65 2019/06/14 21:26:36 tnn Exp $ +# $NetBSD: files.sunxi,v 1.65.2.1 2020/05/18 18:45:40 martin Exp $ # # Configuration info for Allwinner sunxi family SoCs # @@ -361,6 +361,11 @@ device sunxihdmiphy: drmkms attach sunxihdmiphy at fdt with sunxi_hdmiphy file arch/arm/sunxi/sunxi_hdmiphy.c sunxi_hdmiphy | sunxi_dwhdmi +# Allwinner Crypto Engine +device sun8icrypto +attach sun8icrypto at fdt with sun8i_crypto +file arch/arm/sunxi/sun8i_crypto.c sun8i_crypto + # SOC parameters defflag opt_soc.h SOC_SUNXI defflag opt_soc.h SOC_SUNXI_MC Index: src/sys/arch/arm/sunxi/sun50i_a64_ccu.c diff -u src/sys/arch/arm/sunxi/sun50i_a64_ccu.c:1.13.2.3 src/sys/arch/arm/sunxi/sun50i_a64_ccu.c:1.13.2.4 --- src/sys/arch/arm/sunxi/sun50i_a64_ccu.c:1.13.2.3 Mon Nov 25 16:18:40 2019 +++ src/sys/arch/arm/sunxi/sun50i_a64_ccu.c Mon May 18 18:45:40 2020 @@ -1,4 +1,4 @@ -/* $NetBSD: sun50i_a64_ccu.c,v 1.13.2.3 2019/11/25 16:18:40 martin Exp $ */ +/* $NetBSD: sun50i_a64_ccu.c,v 1.13.2.4 2020/05/18 18:45:40 martin Exp $ */ /*- * Copyright (c) 2017 Jared McNeill <jmcne...@invisible.ca> @@ -28,7 +28,7 @@ #include <sys/cdefs.h> -__KERNEL_RCSID(1, "$NetBSD: sun50i_a64_ccu.c,v 1.13.2.3 2019/11/25 16:18:40 martin Exp $"); +__KERNEL_RCSID(1, "$NetBSD: sun50i_a64_ccu.c,v 1.13.2.4 2020/05/18 18:45:40 martin Exp $"); #include <sys/param.h> #include <sys/bus.h> @@ -60,6 +60,7 @@ __KERNEL_RCSID(1, "$NetBSD: sun50i_a64_c #define SDMMC0_CLK_REG 0x088 #define SDMMC1_CLK_REG 0x08c #define SDMMC2_CLK_REG 0x090 +#define CE_CLK_REG 0x09c #define SPI0_CLK_REG 0x0a0 #define SPI1_CLK_REG 0x0a4 #define I2SPCM0_CLK_REG 0x0b0 @@ -156,6 +157,7 @@ static const char *ahb1_parents[] = { "l static const char *ahb2_parents[] = { "ahb1", "pll_periph0" }; static const char *apb1_parents[] = { "ahb1" }; static const char *apb2_parents[] = { "losc", "hosc", "pll_periph0" }; +static const char *ce_parents[] = { "hosc", "pll_periph0_2x", "pll_periph1_2x" }; static const char *mmc_parents[] = { "hosc", "pll_periph0_2x", "pll_periph1_2x" }; static const char *ths_parents[] = { "hosc", NULL, NULL, NULL }; static const char *de_parents[] = { "pll_periph0_2x", "pll_de" }; @@ -397,6 +399,14 @@ static struct sunxi_ccu_clk sun50i_a64_c __BIT(31), /* enable */ SUNXI_CCU_NM_POWER_OF_TWO|SUNXI_CCU_NM_ROUND_DOWN|SUNXI_CCU_NM_DIVIDE_BY_TWO), + SUNXI_CCU_NM(A64_CLK_CE, "ce", ce_parents, + CE_CLK_REG, /* reg */ + __BITS(17,16), /* n */ + __BITS(3,0), /* m */ + __BITS(25,24), /* sel */ + __BIT(31), /* enable */ + SUNXI_CCU_NM_POWER_OF_TWO|SUNXI_CCU_NM_ROUND_DOWN), + SUNXI_CCU_DIV_GATE(A64_CLK_THS, "ths", ths_parents, THS_CLK_REG, /* reg */ __BITS(1,0), /* div */ Index: src/sys/arch/evbarm/conf/GENERIC64 diff -u src/sys/arch/evbarm/conf/GENERIC64:1.103.2.10 src/sys/arch/evbarm/conf/GENERIC64:1.103.2.11 --- src/sys/arch/evbarm/conf/GENERIC64:1.103.2.10 Mon May 18 18:12:25 2020 +++ src/sys/arch/evbarm/conf/GENERIC64 Mon May 18 18:45:40 2020 @@ -1,5 +1,5 @@ # -# $NetBSD: GENERIC64,v 1.103.2.10 2020/05/18 18:12:25 martin Exp $ +# $NetBSD: GENERIC64,v 1.103.2.11 2020/05/18 18:45:40 martin Exp $ # # GENERIC ARM (aarch64) kernel # @@ -369,6 +369,7 @@ amdccp* at fdt? # AMD Cryptograhic Co amdccp* at acpi? bcmrng* at fdt? # Broadcom BCM283x RNG mesonrng* at fdt? # Amlogic Meson RNG +sun8icrypto* at fdt? # Allwinner Crypto Engine # RTC plrtc* at fdt? # ARM PrimeCell RTC Added files: Index: src/sys/arch/arm/sunxi/sun8i_crypto.c diff -u /dev/null src/sys/arch/arm/sunxi/sun8i_crypto.c:1.14.2.2 --- /dev/null Mon May 18 18:45:40 2020 +++ src/sys/arch/arm/sunxi/sun8i_crypto.c Mon May 18 18:45:40 2020 @@ -0,0 +1,1338 @@ +/* $NetBSD: sun8i_crypto.c,v 1.14.2.2 2020/05/18 18:45:40 martin Exp $ */ + +/*- + * Copyright (c) 2019 The NetBSD Foundation, Inc. + * All rights reserved. + * + * This code is derived from software contributed to The NetBSD Foundation + * by Taylor R. Campbell. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +/* + * sun8i_crypto -- Allwinner Crypto Engine driver + * + * The Crypto Engine is documented in Sec. 3.15 of the Allwinner A64 + * User Manual v1.1, on pp. 230--241. We only use it for the TRNG at + * the moment, but in principle it could be wired up with opencrypto(9) + * to compute AES, DES, 3DES, MD5, SHA-1, SHA-224, SHA-256, HMAC-SHA1, + * HMAC-HA256, RSA, and an undocumented PRNG. It also seems to support + * AES keys in SRAM (for some kind of HDMI HDCP stuff?). + * + * https://linux-sunxi.org/images/b/b4/Allwinner_A64_User_Manual_V1.1.pdf + */ + +#include <sys/cdefs.h> +__KERNEL_RCSID(1, "$NetBSD: sun8i_crypto.c,v 1.14.2.2 2020/05/18 18:45:40 martin Exp $"); + +#include <sys/types.h> +#include <sys/param.h> +#include <sys/atomic.h> +#include <sys/bus.h> +#include <sys/callout.h> +#include <sys/conf.h> +#include <sys/device.h> +#include <sys/kernel.h> +#include <sys/kmem.h> +#include <sys/mutex.h> +#include <sys/rndpool.h> +#include <sys/rndsource.h> +#include <sys/sysctl.h> +#include <sys/workqueue.h> + +#include <dev/fdt/fdtvar.h> + +#include <arm/sunxi/sun8i_crypto.h> + +#define SUN8I_CRYPTO_TIMEOUT hz +#define SUN8I_CRYPTO_RNGENTROPY 100 /* estimated bits per bit of entropy */ +#define SUN8I_CRYPTO_RNGBYTES PAGE_SIZE + +struct sun8i_crypto_task; + +struct sun8i_crypto_buf { + bus_dma_segment_t cb_seg[1]; + int cb_nsegs; + bus_dmamap_t cb_map; + void *cb_kva; +}; + +struct sun8i_crypto_softc { + device_t sc_dev; + bus_space_tag_t sc_bst; + bus_space_handle_t sc_bsh; + bus_dma_tag_t sc_dmat; + kmutex_t sc_lock; + struct sun8i_crypto_chan { + struct sun8i_crypto_task *cc_task; + unsigned cc_starttime; + } sc_chan[SUN8I_CRYPTO_NCHAN]; + struct callout sc_timeout; + struct workqueue *sc_wq; + struct work sc_work; + void *sc_ih; + uint32_t sc_done; + uint32_t sc_esr; + bool sc_work_pending; + struct sun8i_crypto_rng { + struct sun8i_crypto_buf cr_buf; + struct sun8i_crypto_task *cr_task; + struct krndsource cr_rndsource; + bool cr_pending; + } sc_rng; + struct sun8i_crypto_selftest { + struct sun8i_crypto_buf cs_in; + struct sun8i_crypto_buf cs_key; + struct sun8i_crypto_buf cs_out; + struct sun8i_crypto_task *cs_task; + } sc_selftest; + struct sun8i_crypto_sysctl { + struct sysctllog *cy_log; + const struct sysctlnode *cy_root_node; + const struct sysctlnode *cy_trng_node; + } sc_sysctl; +}; + +struct sun8i_crypto_task { + struct sun8i_crypto_buf ct_buf; + struct sun8i_crypto_taskdesc *ct_desc; + void (*ct_callback)(struct sun8i_crypto_softc *, + struct sun8i_crypto_task *, void *, int); + void *ct_cookie; +}; + +/* + * Forward declarations + */ + +static int sun8i_crypto_match(device_t, cfdata_t, void *); +static void sun8i_crypto_attach(device_t, device_t, void *); + +static struct sun8i_crypto_task * + sun8i_crypto_task_get(struct sun8i_crypto_softc *, + void (*)(struct sun8i_crypto_softc *, + struct sun8i_crypto_task *, void *, int), + void *); +static void sun8i_crypto_task_put(struct sun8i_crypto_softc *, + struct sun8i_crypto_task *); +static void sun8i_crypto_task_reset(struct sun8i_crypto_task *); + +static void sun8i_crypto_task_set_key(struct sun8i_crypto_task *, + bus_dmamap_t); +static void sun8i_crypto_task_set_iv(struct sun8i_crypto_task *, + bus_dmamap_t); +static void sun8i_crypto_task_set_ctr(struct sun8i_crypto_task *, + bus_dmamap_t); +static void sun8i_crypto_task_set_input(struct sun8i_crypto_task *, + bus_dmamap_t); +static void sun8i_crypto_task_set_output(struct sun8i_crypto_task *, + bus_dmamap_t); + +static void sun8i_crypto_task_scatter(struct sun8i_crypto_adrlen *, + bus_dmamap_t); + +static int sun8i_crypto_submit_trng(struct sun8i_crypto_softc *, + struct sun8i_crypto_task *, uint32_t); +static int sun8i_crypto_submit_aesecb(struct sun8i_crypto_softc *, + struct sun8i_crypto_task *, uint32_t, uint32_t, uint32_t); +static int sun8i_crypto_submit(struct sun8i_crypto_softc *, + struct sun8i_crypto_task *); + +static void sun8i_crypto_timeout(void *); +static int sun8i_crypto_intr(void *); +static void sun8i_crypto_schedule_worker(struct sun8i_crypto_softc *); +static void sun8i_crypto_worker(struct work *, void *); +static void sun8i_crypto_chan_done(struct sun8i_crypto_softc *, unsigned, + int); + +static int sun8i_crypto_allocbuf(struct sun8i_crypto_softc *, size_t, + struct sun8i_crypto_buf *); +static void sun8i_crypto_freebuf(struct sun8i_crypto_softc *, size_t, + struct sun8i_crypto_buf *); + +static void sun8i_crypto_rng_attach(struct sun8i_crypto_softc *); +static void sun8i_crypto_rng_get(size_t, void *); +static void sun8i_crypto_rng_done(struct sun8i_crypto_softc *, + struct sun8i_crypto_task *, void *, int); + +static void sun8i_crypto_selftest(device_t); +static void sun8i_crypto_selftest_done(struct sun8i_crypto_softc *, + struct sun8i_crypto_task *, void *, int); + +static void sun8i_crypto_sysctl_attach(struct sun8i_crypto_softc *); +static int sun8i_crypto_sysctl_rng(SYSCTLFN_ARGS); +static void sun8i_crypto_sysctl_rng_done(struct sun8i_crypto_softc *, + struct sun8i_crypto_task *, void *, int); + +/* + * Register access + */ + +static uint32_t +sun8i_crypto_read(struct sun8i_crypto_softc *sc, bus_addr_t reg) +{ + return bus_space_read_4(sc->sc_bst, sc->sc_bsh, reg); +} + +static void +sun8i_crypto_write(struct sun8i_crypto_softc *sc, bus_addr_t reg, uint32_t v) +{ + bus_space_write_4(sc->sc_bst, sc->sc_bsh, reg, v); +} + +/* + * Autoconf goo + */ + +CFATTACH_DECL_NEW(sun8i_crypto, sizeof(struct sun8i_crypto_softc), + sun8i_crypto_match, sun8i_crypto_attach, NULL, NULL); + +static const struct of_compat_data compat_data[] = { + {"allwinner,sun50i-a64-crypto", 0}, + {NULL} +}; + +static int +sun8i_crypto_match(device_t parent, cfdata_t cf, void *aux) +{ + const struct fdt_attach_args *const faa = aux; + + return of_match_compat_data(faa->faa_phandle, compat_data); +} + +static void +sun8i_crypto_attach(device_t parent, device_t self, void *aux) +{ + struct sun8i_crypto_softc *const sc = device_private(self); + const struct fdt_attach_args *const faa = aux; + bus_addr_t addr; + bus_size_t size; + const int phandle = faa->faa_phandle; + char intrstr[128]; + struct clk *clk; + struct fdtbus_reset *rst; + + sc->sc_dev = self; + sc->sc_dmat = faa->faa_dmat; + sc->sc_bst = faa->faa_bst; + mutex_init(&sc->sc_lock, MUTEX_DEFAULT, IPL_VM); + callout_init(&sc->sc_timeout, CALLOUT_MPSAFE); + callout_setfunc(&sc->sc_timeout, &sun8i_crypto_timeout, sc); + if (workqueue_create(&sc->sc_wq, device_xname(self), + &sun8i_crypto_worker, sc, PRI_NONE, IPL_VM, WQ_MPSAFE) != 0) { + aprint_error(": couldn't create workqueue\n"); + return; + } + + /* Get and map device registers. */ + if (fdtbus_get_reg(phandle, 0, &addr, &size) != 0) { + aprint_error(": couldn't get registers\n"); + return; + } + if (bus_space_map(sc->sc_bst, addr, size, 0, &sc->sc_bsh) != 0) { + aprint_error(": couldn't map registers\n"); + return; + } + + /* Get an interrupt handle. */ + if (!fdtbus_intr_str(phandle, 0, intrstr, sizeof(intrstr))) { + aprint_error(": failed to decode interrupt\n"); + return; + } + + /* Enable the bus clock. */ + if (fdtbus_clock_enable(phandle, "bus", true) != 0) { + aprint_error(": couldn't enable bus clock\n"); + return; + } + + /* Get the module clock and set it to 300 MHz. */ + if ((clk = fdtbus_clock_get(phandle, "mod")) != NULL) { + if (clk_enable(clk) != 0) { + aprint_error(": couldn't enable CE clock\n"); + return; + } + if (clk_set_rate(clk, 300*1000*1000) != 0) { + aprint_error(": couldn't set CE clock to 300MHz\n"); + return; + } + } + + /* Get a reset handle if we need and try to deassert it. */ + if ((rst = fdtbus_reset_get_index(phandle, 0)) != NULL) { + if (fdtbus_reset_deassert(rst) != 0) { + aprint_error(": couldn't de-assert reset\n"); + return; + } + } + + aprint_naive("\n"); + aprint_normal(": Crypto Engine\n"); + aprint_debug_dev(self, ": clock freq %d\n", clk_get_rate(clk)); + + /* Disable and clear interrupts. */ + sun8i_crypto_write(sc, SUN8I_CRYPTO_ICR, 0); + sun8i_crypto_write(sc, SUN8I_CRYPTO_ISR, 0); + + /* Establish an interrupt handler. */ + sc->sc_ih = fdtbus_intr_establish(phandle, 0, IPL_VM, FDT_INTR_MPSAFE, + &sun8i_crypto_intr, sc); + if (sc->sc_ih == NULL) { + aprint_error_dev(self, "failed to establish interrupt on %s\n", + intrstr); + return; + } + aprint_normal_dev(self, "interrupting on %s\n", intrstr); + + /* Set up the RNG. */ + sun8i_crypto_rng_attach(sc); + + /* Attach the sysctl. */ + sun8i_crypto_sysctl_attach(sc); + + /* Perform self-tests. */ + config_interrupts(self, sun8i_crypto_selftest); +} + +/* + * Task allocation + */ + +static struct sun8i_crypto_task * +sun8i_crypto_task_get(struct sun8i_crypto_softc *sc, + void (*callback)(struct sun8i_crypto_softc *, struct sun8i_crypto_task *, + void *, int), + void *cookie) +{ + struct sun8i_crypto_task *task; + int error; + + /* Allocate a task. */ + task = kmem_zalloc(sizeof(*task), KM_SLEEP); + + /* Allocate a buffer for the descriptor. */ + error = sun8i_crypto_allocbuf(sc, sizeof(*task->ct_desc), + &task->ct_buf); + if (error) + goto fail0; + + /* Initialize the task object and return it. */ + task->ct_desc = task->ct_buf.cb_kva; + task->ct_callback = callback; + task->ct_cookie = cookie; + return task; + +fail1: __unused + sun8i_crypto_freebuf(sc, sizeof(*task->ct_desc), &task->ct_buf); +fail0: kmem_free(task, sizeof(*task)); + return NULL; +} + +static void +sun8i_crypto_task_put(struct sun8i_crypto_softc *sc, + struct sun8i_crypto_task *task) +{ + + sun8i_crypto_freebuf(sc, sizeof(*task->ct_desc), &task->ct_buf); + kmem_free(task, sizeof(*task)); +} + +/* + * Task descriptor setup + * + * WARNING: Task descriptor fields are little-endian, not host-endian. + */ + +static void +sun8i_crypto_task_reset(struct sun8i_crypto_task *task) +{ + + memset(task->ct_desc, 0, sizeof(*task->ct_desc)); +} + +static void +sun8i_crypto_task_set_key(struct sun8i_crypto_task *task, bus_dmamap_t map) +{ + + KASSERT(map->dm_nsegs == 1); + task->ct_desc->td_keydesc = htole32(map->dm_segs[0].ds_addr); +} + +static void __unused /* XXX opencrypto(9) */ +sun8i_crypto_task_set_iv(struct sun8i_crypto_task *task, bus_dmamap_t map) +{ + + KASSERT(map->dm_nsegs == 1); + task->ct_desc->td_ivdesc = htole32(map->dm_segs[0].ds_addr); +} + +static void __unused /* XXX opencrypto(9) */ +sun8i_crypto_task_set_ctr(struct sun8i_crypto_task *task, bus_dmamap_t map) +{ + + KASSERT(map->dm_nsegs == 1); + task->ct_desc->td_ctrdesc = htole32(map->dm_segs[0].ds_addr); +} + +static void +sun8i_crypto_task_set_input(struct sun8i_crypto_task *task, bus_dmamap_t map) +{ + + sun8i_crypto_task_scatter(task->ct_desc->td_src, map); +} + +static void +sun8i_crypto_task_set_output(struct sun8i_crypto_task *task, bus_dmamap_t map) +{ + + sun8i_crypto_task_scatter(task->ct_desc->td_dst, map); +} + +static void +sun8i_crypto_task_scatter(struct sun8i_crypto_adrlen *adrlen, bus_dmamap_t map) +{ + uint32_t total __diagused = 0; + unsigned i; + + KASSERT(map->dm_nsegs <= SUN8I_CRYPTO_MAXSEGS); + for (i = 0; i < map->dm_nsegs; i++) { + KASSERT((map->dm_segs[i].ds_addr % 4) == 0); + KASSERT(map->dm_segs[i].ds_addr <= UINT32_MAX); + KASSERT(map->dm_segs[i].ds_len <= UINT32_MAX - total); + adrlen[i].adr = htole32(map->dm_segs[i].ds_addr); + adrlen[i].len = htole32(map->dm_segs[i].ds_len/4); + total += map->dm_segs[i].ds_len; + } + + /* Verify the remainder are zero. */ + for (; i < SUN8I_CRYPTO_MAXSEGS; i++) { + KASSERT(adrlen[i].adr == 0); + KASSERT(adrlen[i].len == 0); + } + + /* Verify the total size matches the DMA map. */ + KASSERT(total == map->dm_mapsize); +} + +/* + * Task submission + * + * WARNING: Task descriptor fields are little-endian, not host-endian. + */ + +static int +sun8i_crypto_submit_trng(struct sun8i_crypto_softc *sc, + struct sun8i_crypto_task *task, uint32_t datalen) +{ + struct sun8i_crypto_taskdesc *desc = task->ct_desc; + uint32_t tdqc = 0; + uint32_t total __diagused; + unsigned i __diagused; + + /* Data length must be a multiple of 4 because...reasons. */ + KASSERT((datalen % 4) == 0); + + /* All of the sources should be empty. */ + for (total = 0, i = 0; i < SUN8I_CRYPTO_MAXSEGS; i++) + KASSERT(le32toh(task->ct_desc->td_src[i].len) == 0); + + /* Verify the total output length -- should be datalen/4. */ + for (total = 0, i = 0; i < SUN8I_CRYPTO_MAXSEGS; i++) { + uint32_t len = le32toh(task->ct_desc->td_dst[i].len); + KASSERT(len <= UINT32_MAX - total); + total += len; + } + KASSERT(total == datalen/4); + + /* Verify the key, IV, and CTR are unset. */ + KASSERT(desc->td_keydesc == 0); + KASSERT(desc->td_ivdesc == 0); + KASSERT(desc->td_ctrdesc == 0); + + /* Set up the task descriptor queue control words. */ + tdqc |= SUN8I_CRYPTO_TDQC_INTR_EN; + tdqc |= __SHIFTIN(SUN8I_CRYPTO_TDQC_METHOD_TRNG, + SUN8I_CRYPTO_TDQC_METHOD); + desc->td_tdqc = htole32(tdqc); + desc->td_tdqs = 0; /* no symmetric crypto */ + desc->td_tdqa = 0; /* no asymmetric crypto */ + + /* Set the data length for the output. */ + desc->td_datalen = htole32(datalen/4); + + /* Submit! */ + return sun8i_crypto_submit(sc, task); +} + +static int +sun8i_crypto_submit_aesecb(struct sun8i_crypto_softc *sc, + struct sun8i_crypto_task *task, + uint32_t datalen, uint32_t keysize, uint32_t dir) +{ + struct sun8i_crypto_taskdesc *desc = task->ct_desc; + uint32_t tdqc = 0, tdqs = 0; + uint32_t total __diagused; + unsigned i __diagused; + + /* + * Data length must be a multiple of 4 because...reasons. + * + * WARNING: For `AES-CTS' (maybe that means AES-XTS?), datalen + * is in units of bytes, not units of words -- but everything + * _else_ is in units of words. This routine applies only to + * AES-ECB for the self-test. + */ + KASSERT((datalen % 4) == 0); + + /* Verify the total input length -- should be datalen/4. */ + for (total = 0, i = 0; i < SUN8I_CRYPTO_MAXSEGS; i++) { + uint32_t len = le32toh(task->ct_desc->td_src[i].len); + KASSERT(len <= UINT32_MAX - total); + total += len; + } + KASSERT(total == datalen/4); + + /* Verify the total output length -- should be datalen/4. */ + for (total = 0, i = 0; i < SUN8I_CRYPTO_MAXSEGS; i++) { + uint32_t len = le32toh(task->ct_desc->td_dst[i].len); + KASSERT(len <= UINT32_MAX - total); + total += len; + } + KASSERT(total == datalen/4); + + /* Set up the task descriptor queue control word. */ + tdqc |= SUN8I_CRYPTO_TDQC_INTR_EN; + tdqc |= __SHIFTIN(SUN8I_CRYPTO_TDQC_METHOD_AES, + SUN8I_CRYPTO_TDQC_METHOD); + desc->td_tdqc = htole32(tdqc); + + /* Set up the symmetric control word. */ + tdqs |= __SHIFTIN(SUN8I_CRYPTO_TDQS_SKEY_SELECT_SS_KEYx, + SUN8I_CRYPTO_TDQS_SKEY_SELECT); + tdqs |= __SHIFTIN(SUN8I_CRYPTO_TDQS_OP_MODE_ECB, + SUN8I_CRYPTO_TDQS_OP_MODE); + tdqs |= __SHIFTIN(SUN8I_CRYPTO_TDQS_AES_KEYSIZE_128, + SUN8I_CRYPTO_TDQS_AES_KEYSIZE); + desc->td_tdqs = htole32(tdqs); + + desc->td_tdqa = 0; /* no asymmetric crypto */ + + /* Set the data length for the output. */ + desc->td_datalen = htole32(datalen/4); + + /* Submit! */ + return sun8i_crypto_submit(sc, task); +} + +static int +sun8i_crypto_submit(struct sun8i_crypto_softc *sc, + struct sun8i_crypto_task *task) +{ + unsigned i, retries = 0; + uint32_t icr; + int error = 0; + + /* One at a time at the device registers, please. */ + mutex_enter(&sc->sc_lock); + + /* Find a channel. */ + for (i = 0; i < SUN8I_CRYPTO_NCHAN; i++) { + if (sc->sc_chan[i].cc_task == NULL) + break; + } + if (i == SUN8I_CRYPTO_NCHAN) { + device_printf(sc->sc_dev, "no free channels\n"); + error = ERESTART; + goto out; + } + + /* + * Set the channel id. Caller is responsible for setting up + * all other parts of the descriptor. + */ + task->ct_desc->td_cid = htole32(i); + + /* Prepare to send the descriptor to the device by DMA. */ + bus_dmamap_sync(sc->sc_dmat, task->ct_buf.cb_map, 0, + sizeof(*task->ct_desc), BUS_DMASYNC_PREWRITE); + + /* Confirm we're ready to go. */ + if (sun8i_crypto_read(sc, SUN8I_CRYPTO_TLR) & SUN8I_CRYPTO_TLR_LOAD) { + device_printf(sc->sc_dev, "TLR not clear\n"); + error = EIO; + goto out; + } + + /* Enable interrupts for this channel. */ + icr = sun8i_crypto_read(sc, SUN8I_CRYPTO_ICR); + icr |= __SHIFTIN(SUN8I_CRYPTO_ICR_INTR_EN_CHAN(i), + SUN8I_CRYPTO_ICR_INTR_EN); + sun8i_crypto_write(sc, SUN8I_CRYPTO_ICR, icr); + + /* Set the task descriptor queue address. */ + sun8i_crypto_write(sc, SUN8I_CRYPTO_TDQ, + task->ct_buf.cb_map->dm_segs[0].ds_addr); + + /* Notify the engine to load it, and wait for acknowledgement. */ + sun8i_crypto_write(sc, SUN8I_CRYPTO_TLR, SUN8I_CRYPTO_TLR_LOAD); + while (sun8i_crypto_read(sc, SUN8I_CRYPTO_TLR) & SUN8I_CRYPTO_TLR_LOAD) + { + /* + * XXX Timeout pulled from arse. Is it even important + * to wait here? + */ + if (++retries == 1000) { + device_printf(sc->sc_dev, "TLR didn't clear: %08x\n", + sun8i_crypto_read(sc, SUN8I_CRYPTO_TLR)); + /* + * Hope it clears eventually; if not, we'll + * time out. + */ + break; + } + DELAY(1); + } + + /* Loaded up and ready to go. Start a timer ticking. */ + sc->sc_chan[i].cc_task = task; + sc->sc_chan[i].cc_starttime = atomic_load_relaxed(&hardclock_ticks); + callout_schedule(&sc->sc_timeout, SUN8I_CRYPTO_TIMEOUT); + + /* XXX Consider polling if cold to get entropy earlier. */ + +out: /* Done! */ + mutex_exit(&sc->sc_lock); + return error; +} + +static void +sun8i_crypto_timeout(void *cookie) +{ + struct sun8i_crypto_softc *sc = cookie; + unsigned i; + + mutex_enter(&sc->sc_lock); + + /* Check whether there are any tasks pending. */ + for (i = 0; i < SUN8I_CRYPTO_NCHAN; i++) { + if (sc->sc_chan[i].cc_task) + break; + } + if (i == SUN8I_CRYPTO_NCHAN) + /* None pending, so nothing to do. */ + goto out; + + /* + * Schedule the worker to check for timeouts, and schedule + * another timeout in case we need it. + */ + sun8i_crypto_schedule_worker(sc); + callout_schedule(&sc->sc_timeout, SUN8I_CRYPTO_TIMEOUT); + +out: mutex_exit(&sc->sc_lock); +} + +static int +sun8i_crypto_intr(void *cookie) +{ + struct sun8i_crypto_softc *sc = cookie; + uint32_t isr, esr; + + mutex_enter(&sc->sc_lock); + + /* + * Get and acknowledge the interrupts and error status. + * + * XXX Data sheet says the error status register is read-only, + * but then advises writing 1 to bit x1xx (keysram access error + * for AES, SUN8I_CRYPTO_ESR_KEYSRAMERR) to clear it. What do? + */ + isr = sun8i_crypto_read(sc, SUN8I_CRYPTO_ISR); + esr = sun8i_crypto_read(sc, SUN8I_CRYPTO_ESR); + sun8i_crypto_write(sc, SUN8I_CRYPTO_ISR, isr); + sun8i_crypto_write(sc, SUN8I_CRYPTO_ESR, esr); + + /* Start the worker if necessary. */ + sun8i_crypto_schedule_worker(sc); + + /* Tell the worker what to do. */ + sc->sc_done |= __SHIFTOUT(isr, SUN8I_CRYPTO_ISR_DONE); + sc->sc_esr |= esr; + + mutex_exit(&sc->sc_lock); + + return __SHIFTOUT(isr, SUN8I_CRYPTO_ISR_DONE) != 0; +} + +static void +sun8i_crypto_schedule_worker(struct sun8i_crypto_softc *sc) +{ + + KASSERT(mutex_owned(&sc->sc_lock)); + + /* Start the worker if necessary. */ + if (!sc->sc_work_pending) { + workqueue_enqueue(sc->sc_wq, &sc->sc_work, NULL); + sc->sc_work_pending = true; + } +} + +static void +sun8i_crypto_worker(struct work *wk, void *cookie) +{ + struct sun8i_crypto_softc *sc = cookie; + uint32_t done, esr, esr_chan; + unsigned i, now; + int error; + + /* + * Acquire the lock. Note: We will be releasing and + * reacquiring it throughout the loop. + */ + mutex_enter(&sc->sc_lock); + + /* Acknowledge the work. */ + KASSERT(sc->sc_work_pending); + sc->sc_work_pending = false; + + /* + * Claim the done mask and error status once; we will be + * releasing and reacquiring the lock for the callbacks, so + * they may change. + */ + done = sc->sc_done; + esr = sc->sc_esr; + sc->sc_done = 0; + sc->sc_esr = 0; + + /* Check the time to determine what's timed out. */ + now = atomic_load_relaxed(&hardclock_ticks); + + /* Process the channels. */ + for (i = 0; i < SUN8I_CRYPTO_NCHAN; i++) { + /* Check whether the channel is done. */ + if (!ISSET(done, SUN8I_CRYPTO_ISR_DONE_CHAN(i))) { + /* Nope. Do we have a task to time out? */ + if ((sc->sc_chan[i].cc_task != NULL) && + ((now - sc->sc_chan[i].cc_starttime) >= + SUN8I_CRYPTO_TIMEOUT)) + sun8i_crypto_chan_done(sc, i, ETIMEDOUT); + continue; + } + + /* Channel is done. Interpret the error if any. */ + esr_chan = __SHIFTOUT(esr, SUN8I_CRYPTO_ESR_CHAN(i)); + if (esr_chan & SUN8I_CRYPTO_ESR_CHAN_ALGNOTSUP) { + device_printf(sc->sc_dev, "channel %u:" + " alg not supported\n", i); + error = ENODEV; + } else if (esr_chan & SUN8I_CRYPTO_ESR_CHAN_DATALENERR) { + device_printf(sc->sc_dev, "channel %u:" + " data length error\n", i); + error = EIO; /* XXX */ + } else if (esr_chan & SUN8I_CRYPTO_ESR_CHAN_KEYSRAMERR) { + device_printf(sc->sc_dev, "channel %u:" + " key sram error\n", i); + error = EIO; /* XXX */ + } else if (esr_chan != 0) { + error = EIO; /* generic I/O error */ + } else { + error = 0; + } + + /* + * Notify the task of completion. May release the lock + * to invoke a callback. + */ + sun8i_crypto_chan_done(sc, i, error); + } + + /* All one; release the lock one last time. */ + mutex_exit(&sc->sc_lock); +} + +static void +sun8i_crypto_chan_done(struct sun8i_crypto_softc *sc, unsigned i, int error) +{ + struct sun8i_crypto_task *task; + uint32_t icr; + + KASSERT(mutex_owned(&sc->sc_lock)); + + /* Claim the task if there is one; bail if not. */ + if ((task = sc->sc_chan[i].cc_task) == NULL) { + device_printf(sc->sc_dev, "channel %u: no task but error=%d\n", + i, error); + return; + } + sc->sc_chan[i].cc_task = NULL; + + /* Disable interrupts on this channel. */ + icr = sun8i_crypto_read(sc, SUN8I_CRYPTO_ICR); + icr &= ~__SHIFTIN(SUN8I_CRYPTO_ICR_INTR_EN_CHAN(i), + SUN8I_CRYPTO_ICR_INTR_EN); + sun8i_crypto_write(sc, SUN8I_CRYPTO_ICR, icr); + + /* Finished sending the descriptor to the device by DMA. */ + bus_dmamap_sync(sc->sc_dmat, task->ct_buf.cb_map, 0, + sizeof(*task->ct_desc), BUS_DMASYNC_POSTWRITE); + + /* Temporarily release the lock to invoke the callback. */ + mutex_exit(&sc->sc_lock); + (*task->ct_callback)(sc, task, task->ct_cookie, error); + mutex_enter(&sc->sc_lock); +} + +/* + * DMA buffers + */ + +static int +sun8i_crypto_allocbuf(struct sun8i_crypto_softc *sc, size_t size, + struct sun8i_crypto_buf *buf) +{ + int error; + + /* Allocate a DMA-safe buffer. */ + error = bus_dmamem_alloc(sc->sc_dmat, size, 0, 0, buf->cb_seg, + __arraycount(buf->cb_seg), &buf->cb_nsegs, BUS_DMA_WAITOK); + if (error) + goto fail0; + + /* Map the buffer into kernel virtual address space. */ + error = bus_dmamem_map(sc->sc_dmat, buf->cb_seg, buf->cb_nsegs, + size, &buf->cb_kva, BUS_DMA_WAITOK); + if (error) + goto fail1; + + /* Create a DMA map for the buffer. */ + error = bus_dmamap_create(sc->sc_dmat, size, 1, size, 0, + BUS_DMA_WAITOK, &buf->cb_map); + if (error) + goto fail2; + + /* Load the buffer into the DMA map. */ + error = bus_dmamap_load(sc->sc_dmat, buf->cb_map, buf->cb_kva, size, + NULL, BUS_DMA_WAITOK); + if (error) + goto fail3; + + /* Success! */ + return 0; + +fail4: __unused + bus_dmamap_unload(sc->sc_dmat, buf->cb_map); +fail3: bus_dmamap_destroy(sc->sc_dmat, buf->cb_map); +fail2: bus_dmamem_unmap(sc->sc_dmat, buf->cb_kva, size); +fail1: bus_dmamem_free(sc->sc_dmat, buf->cb_seg, buf->cb_nsegs); +fail0: return error; +} + +static void +sun8i_crypto_freebuf(struct sun8i_crypto_softc *sc, size_t size, + struct sun8i_crypto_buf *buf) +{ + + bus_dmamap_unload(sc->sc_dmat, buf->cb_map); + bus_dmamap_destroy(sc->sc_dmat, buf->cb_map); + bus_dmamem_unmap(sc->sc_dmat, buf->cb_kva, size); + bus_dmamem_free(sc->sc_dmat, buf->cb_seg, buf->cb_nsegs); +} + +/* + * Crypto Engine - TRNG + */ + +static void +sun8i_crypto_rng_attach(struct sun8i_crypto_softc *sc) +{ + device_t self = sc->sc_dev; + struct sun8i_crypto_rng *rng = &sc->sc_rng; + int error; + + /* Preallocate a buffer to reuse. */ + error = sun8i_crypto_allocbuf(sc, SUN8I_CRYPTO_RNGBYTES, &rng->cr_buf); + if (error) + goto fail0; + + /* Create a task to reuse. */ + rng->cr_task = sun8i_crypto_task_get(sc, sun8i_crypto_rng_done, rng); + if (rng->cr_task == NULL) + goto fail1; + + /* + * Attach the rndsource. This is _not_ marked as RND_TYPE_RNG + * because the output is not uniformly distributed. The bits + * are heavily weighted toward 0 or 1, at different times, and + * I haven't scienced a satisfactory story out of it yet. + */ + rndsource_setcb(&rng->cr_rndsource, sun8i_crypto_rng_get, sc); + rnd_attach_source(&rng->cr_rndsource, device_xname(self), + RND_TYPE_UNKNOWN, + RND_FLAG_COLLECT_VALUE|RND_FLAG_ESTIMATE_VALUE|RND_FLAG_HASCB); + + /* Success! */ + return; + +fail2: __unused + sun8i_crypto_task_put(sc, rng->cr_task); +fail1: sun8i_crypto_freebuf(sc, SUN8I_CRYPTO_RNGBYTES, &rng->cr_buf); +fail0: aprint_error_dev(self, "failed to set up RNG, error=%d\n", error); +} + +static void +sun8i_crypto_rng_get(size_t nbytes, void *cookie) +{ + struct sun8i_crypto_softc *sc = cookie; + struct sun8i_crypto_rng *rng = &sc->sc_rng; + bool pending; + int error; + + /* + * Test and set the RNG-pending flag. If it's already in + * progress, nothing to do here. + */ + mutex_enter(&sc->sc_lock); + pending = rng->cr_pending; + rng->cr_pending = true; + mutex_exit(&sc->sc_lock); + if (pending) + return; + + /* Prepare for a DMA read into the buffer. */ + bus_dmamap_sync(sc->sc_dmat, rng->cr_buf.cb_map, + 0, SUN8I_CRYPTO_RNGBYTES, BUS_DMASYNC_PREREAD); + + /* Set the task up for TRNG to our buffer. */ + sun8i_crypto_task_reset(rng->cr_task); + sun8i_crypto_task_set_output(rng->cr_task, rng->cr_buf.cb_map); + + /* Submit the TRNG task. */ + error = sun8i_crypto_submit_trng(sc, rng->cr_task, + SUN8I_CRYPTO_RNGBYTES); + if (error) + goto fail; + + /* All done! */ + return; + +fail: mutex_enter(&sc->sc_lock); + rng->cr_pending = false; + mutex_exit(&sc->sc_lock); +} + +static void +sun8i_crypto_rng_done(struct sun8i_crypto_softc *sc, + struct sun8i_crypto_task *task, void *cookie, int error) +{ + struct sun8i_crypto_rng *rng = cookie; + uint8_t *buf = rng->cr_buf.cb_kva; + uint32_t entropybits; + + KASSERT(rng == &sc->sc_rng); + + /* Finished the DMA read into the buffer. */ + bus_dmamap_sync(sc->sc_dmat, rng->cr_buf.cb_map, + 0, SUN8I_CRYPTO_RNGBYTES, BUS_DMASYNC_POSTREAD); + + /* If anything went wrong, forget about it. */ + if (error) + goto out; + + /* + * This TRNG has quite low entropy at best. But if it fails a + * repeated output test, then assume it's busted. + */ + CTASSERT(SUN8I_CRYPTO_RNGBYTES <= UINT32_MAX/NBBY); + entropybits = (NBBY*SUN8I_CRYPTO_RNGBYTES)/SUN8I_CRYPTO_RNGENTROPY; + if (consttime_memequal(buf, buf + SUN8I_CRYPTO_RNGBYTES/2, + SUN8I_CRYPTO_RNGBYTES/2)) { + device_printf(sc->sc_dev, "failed repeated output test\n"); + entropybits = 0; + } + + /* + * Actually we don't believe in any of the entropy until this + * device has had more scrutiny. + */ + entropybits = 0; + + /* Success! Enter and erase the data. */ + rnd_add_data(&rng->cr_rndsource, buf, SUN8I_CRYPTO_RNGBYTES, + entropybits); + explicit_memset(buf, 0, SUN8I_CRYPTO_RNGBYTES); + +out: /* Done -- clear the RNG-pending flag. */ + mutex_enter(&sc->sc_lock); + rng->cr_pending = false; + mutex_exit(&sc->sc_lock); +} + +/* + * Self-test + */ + +static const uint8_t selftest_input[16]; +static const uint8_t selftest_key[16]; +static const uint8_t selftest_output[16] = { + 0x66,0xe9,0x4b,0xd4,0xef,0x8a,0x2c,0x3b, + 0x88,0x4c,0xfa,0x59,0xca,0x34,0x2b,0x2e, +}; + +static void +sun8i_crypto_selftest(device_t self) +{ + const size_t datalen = sizeof selftest_input; + struct sun8i_crypto_softc *sc = device_private(self); + struct sun8i_crypto_selftest *selftest = &sc->sc_selftest; + int error; + + CTASSERT(sizeof selftest_input == sizeof selftest_output); + + /* Allocate an input buffer. */ + error = sun8i_crypto_allocbuf(sc, sizeof selftest_input, + &selftest->cs_in); + if (error) + goto fail0; + + /* Allocate a key buffer. */ + error = sun8i_crypto_allocbuf(sc, sizeof selftest_key, + &selftest->cs_key); + if (error) + goto fail1; + + /* Allocate an output buffer. */ + error = sun8i_crypto_allocbuf(sc, sizeof selftest_output, + &selftest->cs_out); + if (error) + goto fail2; + + /* Allocate a task descriptor. */ + selftest->cs_task = sun8i_crypto_task_get(sc, + sun8i_crypto_selftest_done, selftest); + if (selftest->cs_task == NULL) + goto fail3; + + /* Copy the input and key into their buffers. */ + memcpy(selftest->cs_in.cb_kva, selftest_input, sizeof selftest_input); + memcpy(selftest->cs_key.cb_kva, selftest_key, sizeof selftest_key); + + /* Prepare for a DMA write from the input and key buffers. */ + bus_dmamap_sync(sc->sc_dmat, selftest->cs_in.cb_map, 0, + sizeof selftest_input, BUS_DMASYNC_PREWRITE); + bus_dmamap_sync(sc->sc_dmat, selftest->cs_key.cb_map, 0, + sizeof selftest_key, BUS_DMASYNC_PREWRITE); + + /* Prepare for a DMA read into the output buffer. */ + bus_dmamap_sync(sc->sc_dmat, selftest->cs_out.cb_map, 0, + sizeof selftest_output, BUS_DMASYNC_PREREAD); + + /* Set up the task descriptor. */ + sun8i_crypto_task_reset(selftest->cs_task); + sun8i_crypto_task_set_key(selftest->cs_task, selftest->cs_key.cb_map); + sun8i_crypto_task_set_input(selftest->cs_task, selftest->cs_in.cb_map); + sun8i_crypto_task_set_output(selftest->cs_task, + selftest->cs_out.cb_map); + + /* Submit the AES-128 ECB task. */ + error = sun8i_crypto_submit_aesecb(sc, selftest->cs_task, datalen, + SUN8I_CRYPTO_TDQS_AES_KEYSIZE_128, SUN8I_CRYPTO_TDQC_OP_DIR_ENC); + if (error) + goto fail4; + + device_printf(sc->sc_dev, "AES-128 self-test initiated\n"); + + /* Success! */ + return; + +fail4: sun8i_crypto_task_put(sc, selftest->cs_task); +fail3: sun8i_crypto_freebuf(sc, sizeof selftest_output, &selftest->cs_out); +fail2: sun8i_crypto_freebuf(sc, sizeof selftest_key, &selftest->cs_key); +fail1: sun8i_crypto_freebuf(sc, sizeof selftest_input, &selftest->cs_in); +fail0: aprint_error_dev(self, "failed to run self-test, error=%d\n", error); +} + +static bool +sun8i_crypto_selftest_check(struct sun8i_crypto_softc *sc, const char *title, + size_t n, const void *expected, const void *actual) +{ + const uint8_t *e = expected; + const uint8_t *a = actual; + size_t i; + + if (memcmp(e, a, n) == 0) + return true; + + device_printf(sc->sc_dev, "self-test: %s\n", title); + printf("expected: "); + for (i = 0; i < n; i++) + printf("%02hhx", e[i]); + printf("\n"); + printf("actual: "); + for (i = 0; i < n; i++) + printf("%02hhx", a[i]); + printf("\n"); + return false; +} + +static void +sun8i_crypto_selftest_done(struct sun8i_crypto_softc *sc, + struct sun8i_crypto_task *task, void *cookie, int error) +{ + struct sun8i_crypto_selftest *selftest = cookie; + bool ok = true; + + KASSERT(selftest == &sc->sc_selftest); + + /* + * Finished the DMA read into the output buffer, and finished + * the DMA writes from the key buffer and input buffer. + */ + bus_dmamap_sync(sc->sc_dmat, selftest->cs_out.cb_map, 0, + sizeof selftest_output, BUS_DMASYNC_POSTREAD); + bus_dmamap_sync(sc->sc_dmat, selftest->cs_key.cb_map, 0, + sizeof selftest_key, BUS_DMASYNC_POSTWRITE); + bus_dmamap_sync(sc->sc_dmat, selftest->cs_in.cb_map, 0, + sizeof selftest_input, BUS_DMASYNC_POSTWRITE); + + /* If anything went wrong, fail now. */ + if (error) { + device_printf(sc->sc_dev, "self-test error=%d\n", error); + goto out; + } + + /* + * Verify the input and key weren't clobbered, and verify the + * output matches what we expect. + */ + ok &= sun8i_crypto_selftest_check(sc, "input clobbered", + sizeof selftest_input, selftest_input, selftest->cs_in.cb_kva); + ok &= sun8i_crypto_selftest_check(sc, "key clobbered", + sizeof selftest_key, selftest_key, selftest->cs_key.cb_kva); + ok &= sun8i_crypto_selftest_check(sc, "output mismatch", + sizeof selftest_output, selftest_output, selftest->cs_out.cb_kva); + + /* XXX Disable the RNG and other stuff if this fails... */ + if (ok) + device_printf(sc->sc_dev, "AES-128 self-test passed\n"); + +out: sun8i_crypto_task_put(sc, task); + sun8i_crypto_freebuf(sc, sizeof selftest_output, &selftest->cs_out); + sun8i_crypto_freebuf(sc, sizeof selftest_key, &selftest->cs_key); + sun8i_crypto_freebuf(sc, sizeof selftest_input, &selftest->cs_in); +} + +/* + * Sysctl for testing + */ + +struct sun8i_crypto_userreq { + kmutex_t cu_lock; + kcondvar_t cu_cv; + size_t cu_size; + struct sun8i_crypto_buf cu_buf; + struct sun8i_crypto_task *cu_task; + int cu_error; + bool cu_done; + bool cu_cancel; +}; + +static void +sun8i_crypto_sysctl_attach(struct sun8i_crypto_softc *sc) +{ + struct sun8i_crypto_sysctl *cy = &sc->sc_sysctl; + int error; + + /* hw.sun8icryptoN (node) */ + error = sysctl_createv(&cy->cy_log, 0, NULL, &cy->cy_root_node, + CTLFLAG_PERMANENT, CTLTYPE_NODE, device_xname(sc->sc_dev), + SYSCTL_DESCR("sun8i crypto engine knobs"), + NULL, 0, NULL, 0, + CTL_HW, CTL_CREATE, CTL_EOL); + if (error) { + aprint_error_dev(sc->sc_dev, + "failed to set up sysctl hw.%s: %d\n", + device_xname(sc->sc_dev), error); + return; + } + + /* hw.sun8icryptoN.rng (`struct', 4096-byte array) */ + sysctl_createv(&cy->cy_log, 0, &cy->cy_root_node, &cy->cy_trng_node, + CTLFLAG_PERMANENT|CTLFLAG_READONLY|CTLFLAG_PRIVATE, CTLTYPE_STRUCT, + "rng", SYSCTL_DESCR("Read up to 4096 bytes out of the TRNG"), + &sun8i_crypto_sysctl_rng, 0, sc, 0, CTL_CREATE, CTL_EOL); + if (error) { + aprint_error_dev(sc->sc_dev, + "failed to set up sysctl hw.%s.rng: %d\n", + device_xname(sc->sc_dev), error); + return; + } +} + +static int +sun8i_crypto_sysctl_rng(SYSCTLFN_ARGS) +{ + struct sysctlnode node = *rnode; + struct sun8i_crypto_softc *sc = node.sysctl_data; + struct sun8i_crypto_userreq *req; + size_t size; + int error; + + /* If oldp == NULL, the caller wants to learn the size. */ + if (oldp == NULL) { + *oldlenp = 4096; + return 0; + } + + /* Verify the output buffer size is reasonable. */ + size = *oldlenp; + if (size > 4096) /* size_t, so never negative */ + return E2BIG; + if (size == 0) + return 0; /* nothing to do */ + + /* Allocate a request context. */ + req = kmem_alloc(sizeof(*req), KM_NOSLEEP); + if (req == NULL) + return ENOMEM; + + /* Initialize the request context. */ + mutex_init(&req->cu_lock, MUTEX_DEFAULT, IPL_NONE); + cv_init(&req->cu_cv, "sun8isy"); + req->cu_size = size; + req->cu_error = EIO; + req->cu_done = false; + req->cu_cancel = false; + + /* Allocate a buffer for the RNG output. */ + error = sun8i_crypto_allocbuf(sc, size, &req->cu_buf); + if (error) + goto out0; + + /* Allocate a task. */ + req->cu_task = sun8i_crypto_task_get(sc, sun8i_crypto_sysctl_rng_done, + req); + if (req->cu_task == NULL) + goto out1; + + /* Prepare for a DMA read into the buffer. */ + bus_dmamap_sync(sc->sc_dmat, req->cu_buf.cb_map, 0, size, + BUS_DMASYNC_PREREAD); + + /* Set the task up for TRNG to our buffer. */ + sun8i_crypto_task_reset(req->cu_task); + sun8i_crypto_task_set_output(req->cu_task, req->cu_buf.cb_map); + + /* Submit the TRNG task. */ + error = sun8i_crypto_submit_trng(sc, req->cu_task, size); + if (error) { + if (error == ERESTART) + error = EBUSY; + goto out2; + } + + /* Wait for the request to complete. */ + mutex_enter(&req->cu_lock); + while (!req->cu_done) { + error = cv_wait_sig(&req->cu_cv, &req->cu_lock); + if (error) { + /* + * If we finished while waiting to acquire the + * lock, ignore the error and just return now. + * Otherwise, notify the callback that it has + * to clean up after us. + */ + if (req->cu_done) + error = 0; + else + req->cu_cancel = true; + break; + } + } + mutex_exit(&req->cu_lock); + + /* + * Return early on error from cv_wait_sig, which means + * interruption; the callback will clean up instead. + */ + if (error) + return error; + + /* Check for error from the device. */ + error = req->cu_error; + if (error) + goto out2; + + /* Finished the DMA read into the buffer. */ + bus_dmamap_sync(sc->sc_dmat, req->cu_buf.cb_map, 0, req->cu_size, + BUS_DMASYNC_POSTREAD); + + /* Copy out the data. */ + node.sysctl_data = req->cu_buf.cb_kva; + node.sysctl_size = size; + error = sysctl_lookup(SYSCTLFN_CALL(&node)); + + /* Clear the buffer. */ + explicit_memset(req->cu_buf.cb_kva, 0, size); + + /* Clean up. */ +out2: sun8i_crypto_task_put(sc, req->cu_task); +out1: sun8i_crypto_freebuf(sc, req->cu_size, &req->cu_buf); +out0: cv_destroy(&req->cu_cv); + mutex_destroy(&req->cu_lock); + kmem_free(req, sizeof(*req)); + return error; +} + +static void +sun8i_crypto_sysctl_rng_done(struct sun8i_crypto_softc *sc, + struct sun8i_crypto_task *task, void *cookie, int error) +{ + struct sun8i_crypto_userreq *req = cookie; + bool cancel; + + /* + * Notify the waiting thread of the error, and find out whether + * that thread cancelled. + */ + mutex_enter(&req->cu_lock); + cancel = req->cu_cancel; + req->cu_error = error; + req->cu_done = true; + cv_broadcast(&req->cu_cv); + mutex_exit(&req->cu_lock); + + /* + * If it wasn't cancelled, we're done -- the main thread will + * clean up after itself. + */ + if (!cancel) + return; + + /* Clean up after the main thread cancelled. */ + sun8i_crypto_task_put(sc, req->cu_task); + sun8i_crypto_freebuf(sc, req->cu_size, &req->cu_buf); + cv_destroy(&req->cu_cv); + mutex_destroy(&req->cu_lock); + kmem_free(req, sizeof(*req)); +} Index: src/sys/arch/arm/sunxi/sun8i_crypto.h diff -u /dev/null src/sys/arch/arm/sunxi/sun8i_crypto.h:1.1.10.2 --- /dev/null Mon May 18 18:45:40 2020 +++ src/sys/arch/arm/sunxi/sun8i_crypto.h Mon May 18 18:45:40 2020 @@ -0,0 +1,152 @@ +/* $NetBSD: sun8i_crypto.h,v 1.1.10.2 2020/05/18 18:45:40 martin Exp $ */ + +/*- + * Copyright (c) 2019 The NetBSD Foundation, Inc. + * All rights reserved. + * + * This code is derived from software contributed to The NetBSD Foundation + * by Taylor R. Campbell. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef _ARM_SUN8I_CRYPTO_H +#define _ARM_SUN8I_CRYPTO_H + +#include <sys/cdefs.h> + +#define SUN8I_CRYPTO_NCHAN 4 + +/* task_descriptor_queue common control bitmap (32bit), p. 234 */ +#define SUN8I_CRYPTO_TDQC_INTR_EN __BIT(31) +#define SUN8I_CRYPTO_TDQC_IV_MODE __BIT(16) /* MD5/SHA-1/SHA-2 IV */ +#define SUN8I_CRYPTO_TDQC_IV_MODE_CONST 0 /* standard constants */ +#define SUN8I_CRYPTO_TDQC_IV_MODE_ARBITRARY 1 /* arbitrary */ +#define SUN8I_CRYPTO_TDQC_HMAC_PT_LAST __BIT(15) +#define SUN8I_CRYPTO_TDQC_OP_DIR __BIT(8) +#define SUN8I_CRYPTO_TDQC_OP_DIR_ENC 0 +#define SUN8I_CRYPTO_TDQC_OP_DIR_DEC 1 +#define SUN8I_CRYPTO_TDQC_METHOD __BITS(6,0) +#define SUN8I_CRYPTO_TDQC_METHOD_AES 0 +#define SUN8I_CRYPTO_TDQC_METHOD_DES 1 +#define SUN8I_CRYPTO_TDQC_METHOD_3DES 2 +#define SUN8I_CRYPTO_TDQC_METHOD_MD5 16 +#define SUN8I_CRYPTO_TDQC_METHOD_SHA1 17 +#define SUN8I_CRYPTO_TDQC_METHOD_SHA224 18 +#define SUN8I_CRYPTO_TDQC_METHOD_SHA256 19 +#define SUN8I_CRYPTO_TDQC_METHOD_HMAC_SHA1 22 +#define SUN8I_CRYPTO_TDQC_METHOD_HMAC_SHA256 23 +#define SUN8I_CRYPTO_TDQC_METHOD_RSA 32 +#define SUN8I_CRYPTO_TDQC_METHOD_TRNG 48 +#define SUN8I_CRYPTO_TDQC_METHOD_PRNG 49 + +/* task_descriptor_queue symmetric control (32bit), p. 235 */ +#define SUN8I_CRYPTO_TDQS_SKEY_SELECT __BITS(23,20) +#define SUN8I_CRYPTO_TDQS_SKEY_SELECT_SS_KEYx 0 +#define SUN8I_CRYPTO_TDQS_SKEY_SELECT_SSK 1 +#define SUN8I_CRYPTO_TDQS_SKEY_SELECT_HUK 2 +#define SUN8I_CRYPTO_TDQS_SKEY_SELECT_RSSK 3 +#define SUN8I_CRYPTO_TDQS_SKEY_SELECT_INTERNAL(n) (8 + (n)) +#define SUN8I_CRYPTO_TDQS_AES_CTS_LAST __BIT(16) +#define SUN8I_CRYPTO_TDQS_OP_MODE __BITS(11,8) +#define SUN8I_CRYPTO_TDQS_OP_MODE_ECB 0 +#define SUN8I_CRYPTO_TDQS_OP_MODE_CBC 1 +#define SUN8I_CRYPTO_TDQS_OP_MODE_CTR 2 +#define SUN8I_CRYPTO_TDQS_OP_MODE_CTS 3 +#define SUN8I_CRYPTO_TDQS_CTR_WIDTH __BITS(3,2) +#define SUN8I_CRYPTO_TDQS_CTR_WIDTH_16 0 +#define SUN8I_CRYPTO_TDQS_CTR_WIDTH_32 1 +#define SUN8I_CRYPTO_TDQS_CTR_WIDTH_64 2 +#define SUN8I_CRYPTO_TDQS_CTR_WIDTH_128 3 +#define SUN8I_CRYPTO_TDQS_AES_KEYSIZE __BITS(1,0) +#define SUN8I_CRYPTO_TDQS_AES_KEYSIZE_128 0 +#define SUN8I_CRYPTO_TDQS_AES_KEYSIZE_192 1 +#define SUN8I_CRYPTO_TDQS_AES_KEYSIZE_256 2 + +/* task_descriptor_queue asymmetric control (32bit), p. 236 */ +#define SUN8I_CRYPTO_TDQA_RSA_MODSIZE __BITS(30,28) +#define SUN8I_CRYPTO_TDQA_RSA_MODSIZE_512 0 +#define SUN8I_CRYPTO_TDQA_RSA_MODSIZE_1024 1 +#define SUN8I_CRYPTO_TDQA_RSA_MODSIZE_2048 2 + +/* Crypto Engine Register List */ + +/* Module */ +#define SUN8I_CRYPTO_NS 0x01c15000 +#define SUN8I_CRYPTO_S 0x01c15800 + +/* Register */ +#define SUN8I_CRYPTO_TDQ 0x00 /* Task Descriptor Queue Address */ +#define SUN8I_CRYPTO_CTR 0x04 /* Gating Control Register */ +#define SUN8I_CRYPTO_ICR 0x08 /* Interrupt Control Register */ +#define SUN8I_CRYPTO_ISR 0x0c /* Interrupt Status Register */ +#define SUN8I_CRYPTO_TLR 0x10 /* Task Load Register */ +#define SUN8I_CRYPTO_TSR 0x14 /* Task Status Register */ +#define SUN8I_CRYPTO_ESR 0x18 /* Task Error type Register */ +#define SUN8I_CRYPTO_CSAR 0x24 /* Current Source Address Register */ +#define SUN8I_CRYPTO_CDAR 0x28 /* Current Destination Address Register */ +#define SUN8I_CRYPTO_TPR 0x2c /* Throughput Register */ + +#define SUN8I_CRYPTO_CTR_RSA_CLK_EN __BIT(3) +#define SUN8I_CRYPTO_CTR_DIE_ID __BITS(2,0) + +#define SUN8I_CRYPTO_ICR_INTR_EN __BITS(3,0) +#define SUN8I_CRYPTO_ICR_INTR_EN_CHAN(n) (__BIT(0) << (n)) + +#define SUN8I_CRYPTO_ISR_DONE __BITS(3,0) +#define SUN8I_CRYPTO_ISR_DONE_CHAN(n) (__BIT(0) << (n)) + +#define SUN8I_CRYPTO_TLR_LOAD __BIT(0) + +#define SUN8I_CRYPTO_TSR_TASK __BITS(1,0) +/* task number, 0-3 */ + +#define SUN8I_CRYPTO_ESR_CHAN(n) (__BITS(3,0) << (n)) +#define SUN8I_CRYPTO_ESR_CHAN_ALGNOTSUP __BIT(0) +#define SUN8I_CRYPTO_ESR_CHAN_DATALENERR __BIT(1) +#define SUN8I_CRYPTO_ESR_CHAN_KEYSRAMERR __BIT(2) + +#define SUN8I_CRYPTO_MAXSEGS 8 +#define SUN8I_CRYPTO_MAXSEGSIZE UINT32_MAX +#define SUN8I_CRYPTO_MAXSIZE UINT32_MAX /* datalen */ + +struct sun8i_crypto_taskdesc { + uint32_t td_cid; /* task channel id */ + uint32_t td_tdqc; /* task descriptor queue common */ + uint32_t td_tdqs; /* task descriptor queue symmetric */ + uint32_t td_tdqa; /* task descriptor queue asymmetric */ + uint32_t td_keydesc; /* key descriptor */ + uint32_t td_ivdesc; /* IV descriptor */ + uint32_t td_ctrdesc; /* CTR descriptor */ + uint32_t td_datalen; /* data length */ + struct sun8i_crypto_adrlen { + uint32_t adr; + uint32_t len; + } td_src[SUN8I_CRYPTO_MAXSEGS], + td_dst[SUN8I_CRYPTO_MAXSEGS]; + uint32_t td_nextdesc; /* next descriptor */ + uint32_t td_reserved[3]; +} __packed; + +CTASSERT(sizeof(struct sun8i_crypto_taskdesc) == 44*4); + +#endif /* _ARM_SUN8I_CRYPTO_H */