diff --git a/sys/arm/broadcom/bcm2835/bcm2835_rng.c b/sys/arm/broadcom/bcm2835/bcm2835_rng.c index b73580c5eb53..c403bc3542e0 100644 --- a/sys/arm/broadcom/bcm2835/bcm2835_rng.c +++ b/sys/arm/broadcom/bcm2835/bcm2835_rng.c @@ -1,467 +1,559 @@ /* * Copyright (c) 2015, 2016, Stephen J. Kiernan * All rights reserved. * * 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 AUTHOR 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 AUTHOR 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. */ #include __FBSDID("$FreeBSD$"); #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include static device_attach_t bcm2835_rng_attach; static device_detach_t bcm2835_rng_detach; static device_probe_t bcm2835_rng_probe; #define RNG_CTRL 0x00 /* RNG Control Register */ #define RNG_COMBLK1_OSC 0x003f0000 /* Combiner Blk 1 Oscillator */ #define RNG_COMBLK1_OSC_SHIFT 16 #define RNG_COMBLK2_OSC 0x0fc00000 /* Combiner Blk 2 Oscillator */ #define RNG_COMBLK2_OSC_SHIFT 22 #define RNG_JCLK_BYP_DIV_CNT 0x0000ff00 /* Jitter clk bypass divider count */ #define RNG_JCLK_BYP_DIV_CNT_SHIFT 8 #define RNG_JCLK_BYP_SRC 0x00000020 /* Jitter clk bypass source */ #define RNG_JCLK_BYP_SEL 0x00000010 /* Jitter clk bypass select */ #define RNG_RBG2X 0x00000002 /* RBG 2X SPEED */ #define RNG_RBGEN_BIT 0x00000001 /* Enable RNG bit */ -#define RNG_STATUS 0x04 /* RNG status register */ -#define RND_VAL_SHIFT 24 /* Shift for valid words */ -#define RND_VAL_MASK 0x000000ff /* Number valid words mask */ -#define RND_VAL_WARM_CNT 0x40000 /* RNG Warm Up count */ -#define RND_WARM_CNT 0xfffff /* RNG Warm Up Count mask */ +#define BCM2835_RNG_STATUS 0x04 /* BCM2835 RNG status register */ +#define BCM2838_RNG_STATUS 0x18 /* BCM2838 RNG status register */ -#define RNG_DATA 0x08 /* RNG Data Register */ +#define BCM2838_RNG_COUNT 0x24 /* How many values available */ +#define BCM2838_COUNT_VAL_MASK 0x000000ff + +#define BCM2835_RND_VAL_SHIFT 24 /* Shift for valid words */ +#define BCM2835_RND_VAL_MASK 0x000000ff /* Number valid words mask */ +#define BCM2835_RND_VAL_WARM_CNT 0x40000 /* RNG Warm Up count */ +#define BCM2835_RND_WARM_CNT 0xfffff /* RNG Warm Up Count mask */ + +#define BCM2835_RNG_DATA 0x08 /* RNG Data Register */ +#define BCM2838_RNG_DATA 0x20 #define RNG_FF_THRES 0x0c #define RNG_FF_THRES_MASK 0x0000001f -#define RNG_INT_MASK 0x10 -#define RNG_INT_OFF_BIT 0x00000001 +#define BCM2835_RNG_INT_MASK 0x10 +#define BCM2835_RNG_INT_OFF_BIT 0x00000001 #define RNG_FF_DEFAULT 0x10 /* FIFO threshold default */ - #define RNG_FIFO_WORDS (RNG_FF_DEFAULT / sizeof(uint32_t)) #define RNG_NUM_OSCILLATORS 6 #define RNG_STALL_COUNT_DEFAULT 10 #define RNG_CALLOUT_TICKS (hz * 4) +struct bcm_rng_conf { + bus_size_t control_reg; + bus_size_t status_reg; + bus_size_t count_reg; + bus_size_t data_reg; + bus_size_t intr_mask_reg; + uint32_t intr_disable_bit; + uint32_t count_value_shift; + uint32_t count_value_mask; + uint32_t warmup_count; + bool allow_2x_mode; + bool can_diagnose; + /* XXX diag regs */ +}; + +static const struct bcm_rng_conf bcm2835_rng_conf = { + .control_reg = RNG_CTRL, + .status_reg = BCM2835_RNG_STATUS, + .count_reg = BCM2835_RNG_STATUS, /* Same register */ + .data_reg = BCM2835_RNG_DATA, + .intr_mask_reg = BCM2835_RNG_INT_MASK, + .intr_disable_bit = BCM2835_RNG_INT_OFF_BIT, + .count_value_shift = BCM2835_RND_VAL_SHIFT, + .count_value_mask = BCM2835_RND_VAL_MASK, + .warmup_count = BCM2835_RND_VAL_WARM_CNT, + .allow_2x_mode = true, + .can_diagnose = true +}; + +static const struct bcm_rng_conf bcm2838_rng_conf = { + .control_reg = RNG_CTRL, + .status_reg = BCM2838_RNG_STATUS, + .count_reg = BCM2838_RNG_COUNT, + .data_reg = BCM2838_RNG_DATA, + .intr_mask_reg = 0, + .intr_disable_bit = 0, + .count_value_shift = 0, + .count_value_mask = BCM2838_COUNT_VAL_MASK, + .warmup_count = 0, + .allow_2x_mode = false, + .can_diagnose = false +}; + struct bcm2835_rng_softc { device_t sc_dev; struct resource * sc_mem_res; struct resource * sc_irq_res; void * sc_intr_hdl; + struct bcm_rng_conf const* conf; uint32_t sc_buf[RNG_FIFO_WORDS]; struct callout sc_rngto; int sc_stall_count; int sc_rbg2x; long sc_underrun; }; static struct ofw_compat_data compat_data[] = { - {"broadcom,bcm2835-rng", 1}, - {"brcm,bcm2835-rng", 1}, + {"broadcom,bcm2835-rng", (uintptr_t)&bcm2835_rng_conf}, + {"brcm,bcm2835-rng", (uintptr_t)&bcm2835_rng_conf}, + + {"brcm,bcm2711-rng200", (uintptr_t)&bcm2838_rng_conf}, + {"brcm,bcm2838-rng", (uintptr_t)&bcm2838_rng_conf}, + {"brcm,bcm2838-rng200", (uintptr_t)&bcm2838_rng_conf}, + {"brcm,bcm7211-rng", (uintptr_t)&bcm2838_rng_conf}, + {"brcm,bcm7278-rng", (uintptr_t)&bcm2838_rng_conf}, + {"brcm,iproc-rng200", (uintptr_t)&bcm2838_rng_conf}, {NULL, 0} }; static __inline void bcm2835_rng_stat_inc_underrun(struct bcm2835_rng_softc *sc) { atomic_add_long(&sc->sc_underrun, 1); } static __inline uint32_t bcm2835_rng_read4(struct bcm2835_rng_softc *sc, bus_size_t off) { return bus_read_4(sc->sc_mem_res, off); } static __inline void bcm2835_rng_read_multi4(struct bcm2835_rng_softc *sc, bus_size_t off, uint32_t *datap, bus_size_t count) { bus_read_multi_4(sc->sc_mem_res, off, datap, count); } static __inline void bcm2835_rng_write4(struct bcm2835_rng_softc *sc, bus_size_t off, uint32_t val) { bus_write_4(sc->sc_mem_res, off, val); } static void bcm2835_rng_dump_registers(struct bcm2835_rng_softc *sc, struct sbuf *sbp) { uint32_t comblk2_osc, comblk1_osc, jclk_byp_div, val; int i; + if (!sc->conf->can_diagnose) + /* Not implemented. */ + return; + /* Display RNG control register contents */ - val = bcm2835_rng_read4(sc, RNG_CTRL); + val = bcm2835_rng_read4(sc, sc->conf->control_reg); sbuf_printf(sbp, "RNG_CTRL (%08x)\n", val); comblk2_osc = (val & RNG_COMBLK2_OSC) >> RNG_COMBLK2_OSC_SHIFT; sbuf_printf(sbp, " RNG_COMBLK2_OSC (%02x)\n", comblk2_osc); for (i = 0; i < RNG_NUM_OSCILLATORS; i++) if ((comblk2_osc & (1 << i)) == 0) sbuf_printf(sbp, " Oscillator %d enabled\n", i + 1); comblk1_osc = (val & RNG_COMBLK1_OSC) >> RNG_COMBLK1_OSC_SHIFT; sbuf_printf(sbp, " RNG_COMBLK1_OSC (%02x)\n", comblk1_osc); for (i = 0; i < RNG_NUM_OSCILLATORS; i++) if ((comblk1_osc & (1 << i)) == 0) sbuf_printf(sbp, " Oscillator %d enabled\n", i + 1); jclk_byp_div = (val & RNG_JCLK_BYP_DIV_CNT) >> RNG_JCLK_BYP_DIV_CNT_SHIFT; sbuf_printf(sbp, " RNG_JCLK_BYP_DIV_CNT (%02x)\n APB clock frequency / %d\n", jclk_byp_div, 2 * (jclk_byp_div + 1)); sbuf_printf(sbp, " RNG_JCLK_BYP_SRC:\n %s\n", (val & RNG_JCLK_BYP_SRC) ? "Use divided down APB clock" : "Use RNG clock (APB clock)"); sbuf_printf(sbp, " RNG_JCLK_BYP_SEL:\n %s\n", (val & RNG_JCLK_BYP_SEL) ? "Bypass internal jitter clock" : "Use internal jitter clock"); if ((val & RNG_RBG2X) != 0) sbuf_cat(sbp, " RNG_RBG2X: RNG 2X SPEED enabled\n"); if ((val & RNG_RBGEN_BIT) != 0) sbuf_cat(sbp, " RNG_RBGEN_BIT: RBG enabled\n"); /* Display RNG status register contents */ - val = bcm2835_rng_read4(sc, RNG_STATUS); + val = bcm2835_rng_read4(sc, sc->conf->status_reg); sbuf_printf(sbp, "RNG_CTRL (%08x)\n", val); sbuf_printf(sbp, " RND_VAL: %02x\n", - (val >> RND_VAL_SHIFT) & RND_VAL_MASK); - sbuf_printf(sbp, " RND_WARM_CNT: %05x\n", val & RND_WARM_CNT); + (val >> sc->conf->count_value_shift) & sc->conf->count_value_mask); + sbuf_printf(sbp, " RND_WARM_CNT: %05x\n", val & sc->conf->warmup_count); /* Display FIFO threshold register contents */ val = bcm2835_rng_read4(sc, RNG_FF_THRES); sbuf_printf(sbp, "RNG_FF_THRES: %05x\n", val & RNG_FF_THRES_MASK); /* Display interrupt mask register contents */ - val = bcm2835_rng_read4(sc, RNG_INT_MASK); + val = bcm2835_rng_read4(sc, sc->conf->intr_mask_reg); sbuf_printf(sbp, "RNG_INT_MASK: interrupt %s\n", - ((val & RNG_INT_OFF_BIT) != 0) ? "disabled" : "enabled"); + ((val & sc->conf->intr_disable_bit) != 0) ? "disabled" : "enabled"); } static void bcm2835_rng_disable_intr(struct bcm2835_rng_softc *sc) { uint32_t mask; /* Set the interrupt off bit in the interrupt mask register */ - mask = bcm2835_rng_read4(sc, RNG_INT_MASK); - mask |= RNG_INT_OFF_BIT; - bcm2835_rng_write4(sc, RNG_INT_MASK, mask); + mask = bcm2835_rng_read4(sc, sc->conf->intr_mask_reg); + mask |= sc->conf->intr_disable_bit; + bcm2835_rng_write4(sc, sc->conf->intr_mask_reg, mask); } static void bcm2835_rng_start(struct bcm2835_rng_softc *sc) { uint32_t ctrl; /* Disable the interrupt */ - bcm2835_rng_disable_intr(sc); + if (sc->conf->intr_mask_reg) + bcm2835_rng_disable_intr(sc); /* Set the warmup count */ - bcm2835_rng_write4(sc, RNG_STATUS, RND_VAL_WARM_CNT); + if (sc->conf->warmup_count > 0) + bcm2835_rng_write4(sc, sc->conf->status_reg, + sc->conf->warmup_count); /* Enable the RNG */ - ctrl = bcm2835_rng_read4(sc, RNG_CTRL); + ctrl = bcm2835_rng_read4(sc, sc->conf->control_reg); ctrl |= RNG_RBGEN_BIT; - if (sc->sc_rbg2x) + if (sc->sc_rbg2x && sc->conf->allow_2x_mode) ctrl |= RNG_RBG2X; - bcm2835_rng_write4(sc, RNG_CTRL, ctrl); + bcm2835_rng_write4(sc, sc->conf->control_reg, ctrl); } static void bcm2835_rng_stop(struct bcm2835_rng_softc *sc) { uint32_t ctrl; /* Disable the RNG */ - ctrl = bcm2835_rng_read4(sc, RNG_CTRL); + ctrl = bcm2835_rng_read4(sc, sc->conf->control_reg); ctrl &= ~RNG_RBGEN_BIT; - bcm2835_rng_write4(sc, RNG_CTRL, ctrl); + bcm2835_rng_write4(sc, sc->conf->control_reg, ctrl); +} + +static void +bcm2835_rng_enqueue_harvest(struct bcm2835_rng_softc *sc, uint32_t nread) +{ + char *sc_buf_chunk; + uint32_t chunk_size; + uint32_t cnt; + + chunk_size = sizeof(((struct harvest_event *)0)->he_entropy); + cnt = nread * sizeof(uint32_t); + sc_buf_chunk = (void*)sc->sc_buf; + + while (cnt > 0) { + uint32_t size; + + size = MIN(cnt, chunk_size); + + random_harvest_queue(sc_buf_chunk, size, RANDOM_PURE_BROADCOM); + + sc_buf_chunk += size; + cnt -= size; + } } static void bcm2835_rng_harvest(void *arg) { uint32_t *dest; - uint32_t status; + uint32_t hwcount; u_int cnt, nread, num_avail, num_words; int seen_underrun, num_stalls; struct bcm2835_rng_softc *sc = arg; dest = sc->sc_buf; nread = num_words = 0; seen_underrun = num_stalls = 0; + for (cnt = sizeof(sc->sc_buf) / sizeof(uint32_t); cnt > 0; cnt -= num_words) { - /* Read status register to find out how many words available */ - status = bcm2835_rng_read4(sc, RNG_STATUS); - num_avail = (status >> RND_VAL_SHIFT) & RND_VAL_MASK; + /* Read count register to find out how many words available */ + hwcount = bcm2835_rng_read4(sc, sc->conf->count_reg); + num_avail = (hwcount >> sc->conf->count_value_shift) & + sc->conf->count_value_mask; /* If we have none... */ if (num_avail == 0) { bcm2835_rng_stat_inc_underrun(sc); if (++seen_underrun >= sc->sc_stall_count) { if (num_stalls++ > 0) { device_printf(sc->sc_dev, "RNG stalled, disabling device\n"); bcm2835_rng_stop(sc); break; } else { device_printf(sc->sc_dev, "Too many underruns, resetting\n"); bcm2835_rng_stop(sc); bcm2835_rng_start(sc); seen_underrun = 0; } } /* Try again */ continue; } CTR2(KTR_DEV, "%s: %d words available in RNG FIFO", device_get_nameunit(sc->sc_dev), num_avail); /* Pull MIN(num_avail, cnt) words from the FIFO */ num_words = (num_avail > cnt) ? cnt : num_avail; - bcm2835_rng_read_multi4(sc, RNG_DATA, dest, + bcm2835_rng_read_multi4(sc, sc->conf->data_reg, dest, num_words); dest += num_words; nread += num_words; } - cnt = nread * sizeof(uint32_t); - if (cnt > 0) - random_harvest_queue(sc->sc_buf, cnt, RANDOM_PURE_BROADCOM); + bcm2835_rng_enqueue_harvest(sc, nread); callout_reset(&sc->sc_rngto, RNG_CALLOUT_TICKS, bcm2835_rng_harvest, sc); } static int sysctl_bcm2835_rng_2xspeed(SYSCTL_HANDLER_ARGS) { struct bcm2835_rng_softc *sc = arg1; int error, rbg2x; rbg2x = sc->sc_rbg2x; error = sysctl_handle_int(oidp, &rbg2x, 0, req); if (error) return (error); if (req->newptr == NULL) return (error); if (rbg2x == sc->sc_rbg2x) return (0); /* Reset the RNG */ bcm2835_rng_stop(sc); sc->sc_rbg2x = rbg2x; bcm2835_rng_start(sc); return (0); } #ifdef BCM2835_RNG_DEBUG_REGISTERS static int sysctl_bcm2835_rng_dump(SYSCTL_HANDLER_ARGS) { struct sbuf sb; struct bcm2835_rng_softc *sc = arg1; int error; error = sysctl_wire_old_buffer(req, 0); if (error != 0) return (error); sbuf_new_for_sysctl(&sb, NULL, 128, req); bcm2835_rng_dump_registers(sc, &sb); error = sbuf_finish(&sb); sbuf_delete(&sb); return (error); } #endif static int bcm2835_rng_probe(device_t dev) { if (!ofw_bus_status_okay(dev)) return (ENXIO); if (ofw_bus_search_compatible(dev, compat_data)->ocd_data == 0) return (ENXIO); - device_set_desc(dev, "Broadcom BCM2835 RNG"); + device_set_desc(dev, "Broadcom BCM2835/BCM2838 RNG"); return (BUS_PROBE_DEFAULT); } static int bcm2835_rng_attach(device_t dev) { struct bcm2835_rng_softc *sc; struct sysctl_ctx_list *sysctl_ctx; struct sysctl_oid *sysctl_tree; int error, rid; error = 0; sc = device_get_softc(dev); sc->sc_dev = dev; + + sc->conf = (void const*)ofw_bus_search_compatible(dev, compat_data)->ocd_data; + KASSERT(sc->conf != NULL, ("bcm2835_rng_attach: sc->conf == NULL")); + sc->sc_stall_count = RNG_STALL_COUNT_DEFAULT; /* Initialize callout */ callout_init(&sc->sc_rngto, CALLOUT_MPSAFE); - TUNABLE_INT_FETCH("bcmrng.2xspeed", &sc->sc_rbg2x); TUNABLE_INT_FETCH("bcmrng.stall_count", &sc->sc_stall_count); + if (sc->conf->allow_2x_mode) + TUNABLE_INT_FETCH("bcmrng.2xspeed", &sc->sc_rbg2x); /* Allocate memory resources */ rid = 0; sc->sc_mem_res = bus_alloc_resource_any(dev, SYS_RES_MEMORY, &rid, RF_ACTIVE); if (sc->sc_mem_res == NULL) { bcm2835_rng_detach(dev); return (ENXIO); } /* Start the RNG */ bcm2835_rng_start(sc); /* Dump the registers if booting verbose */ if (bootverbose) { struct sbuf sb; (void) sbuf_new(&sb, NULL, 256, SBUF_AUTOEXTEND | SBUF_INCLUDENUL); bcm2835_rng_dump_registers(sc, &sb); sbuf_trim(&sb); error = sbuf_finish(&sb); if (error == 0) device_printf(dev, "%s", sbuf_data(&sb)); sbuf_delete(&sb); } sysctl_ctx = device_get_sysctl_ctx(dev); sysctl_tree = device_get_sysctl_tree(dev); SYSCTL_ADD_LONG(sysctl_ctx, SYSCTL_CHILDREN(sysctl_tree), OID_AUTO, "underrun", CTLFLAG_RD, &sc->sc_underrun, "Number of FIFO underruns"); - SYSCTL_ADD_PROC(sysctl_ctx, SYSCTL_CHILDREN(sysctl_tree), OID_AUTO, - "2xspeed", CTLTYPE_INT | CTLFLAG_RW | CTLFLAG_NEEDGIANT, sc, 0, - sysctl_bcm2835_rng_2xspeed, "I", "Enable RBG 2X SPEED"); + if (sc->conf->allow_2x_mode) + SYSCTL_ADD_PROC(sysctl_ctx, SYSCTL_CHILDREN(sysctl_tree), OID_AUTO, + "2xspeed", CTLTYPE_INT | CTLFLAG_RW | CTLFLAG_NEEDGIANT, sc, 0, + sysctl_bcm2835_rng_2xspeed, "I", "Enable RBG 2X SPEED"); SYSCTL_ADD_INT(sysctl_ctx, SYSCTL_CHILDREN(sysctl_tree), OID_AUTO, "stall_count", CTLFLAG_RW, &sc->sc_stall_count, RNG_STALL_COUNT_DEFAULT, "Number of underruns to assume RNG stall"); #ifdef BCM2835_RNG_DEBUG_REGISTERS SYSCTL_ADD_PROC(sysctl_ctx, SYSCTL_CHILDREN(sysctl_tree), OID_AUTO, "dumpregs", CTLTYPE_STRING | CTLFLAG_RD | CTLFLAG_NEEDGIANT, sc, 0, sysctl_bcm2835_rng_dump, "S", "Dump RNG registers"); #endif - /* + /* * Schedule the initial harvesting one second from now, which should give the * hardware RNG plenty of time to generate the first random bytes. */ callout_reset(&sc->sc_rngto, hz, bcm2835_rng_harvest, sc); return (0); } static int bcm2835_rng_detach(device_t dev) { struct bcm2835_rng_softc *sc; sc = device_get_softc(dev); /* Stop the RNG */ bcm2835_rng_stop(sc); /* Drain the callout it */ callout_drain(&sc->sc_rngto); /* Release memory resource */ if (sc->sc_mem_res != NULL) bus_release_resource(dev, SYS_RES_MEMORY, 0, sc->sc_mem_res); return (0); } static device_method_t bcm2835_rng_methods[] = { /* Device interface */ DEVMETHOD(device_probe, bcm2835_rng_probe), DEVMETHOD(device_attach, bcm2835_rng_attach), DEVMETHOD(device_detach, bcm2835_rng_detach), DEVMETHOD_END }; static driver_t bcm2835_rng_driver = { "bcmrng", bcm2835_rng_methods, sizeof(struct bcm2835_rng_softc) }; static devclass_t bcm2835_rng_devclass; DRIVER_MODULE(bcm2835_rng, simplebus, bcm2835_rng_driver, bcm2835_rng_devclass, 0, 0); DRIVER_MODULE(bcm2835_rng, ofwbus, bcm2835_rng_driver, bcm2835_rng_devclass, 0, 0); MODULE_VERSION(bcm2835_rng, 1); MODULE_DEPEND(bcm2835_rng, randomdev, 1, 1, 1);