/*- * Copyright (c) 2015-2017 Ruslan Bukin * All rights reserved. * * Portions of this software were developed by SRI International and the * University of Cambridge Computer Laboratory under DARPA/AFRL contract * FA8750-10-C-0237 ("CTSRD"), as part of the DARPA CRASH research programme. * * Portions of this software were developed by the University of Cambridge * Computer Laboratory as part of the CTSRD Project, with support from the * UK Higher Education Innovation Fund (HEIF). * * 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. */ /* * RISC-V Timer */ #include #include #include #include #include #include #include #include #include #include #include #include #include "riscv_cpu_intc.h" #include #include #define TIMER_COUNTS 0x00 #define TIMER_MTIMECMP(cpu) (cpu * 8) #define TIMER_FREQUENCY 10 * 1000 * 1000 /* RISC-V time clock */ unsigned riscv_timer_get_timecount(struct timecounter *); static struct timecounter riscv_timer_timecount = { .tc_name = "RISC-V Timecounter", .tc_get_timecount = riscv_timer_get_timecount, .tc_poll_pps = NULL, .tc_counter_mask = ~0u, .tc_frequency = 0, .tc_quality = 1000, .tc_priv = NULL, }; struct riscv_timer_pcpu_softc { uint64_t pc_nexttickevent; uint64_t pc_nextstatevent; u_int32_t pc_ticks_err_sum; }; struct riscv_timer_softc { struct device sc_dev; int sc_node; struct riscv_timer_pcpu_softc sc_pstat[MAXCPUS]; u_int32_t sc_ticks_err_cnt; u_int32_t sc_ticks_per_second; // sc_clkfreq u_int32_t sc_ticks_per_intr; u_int32_t sc_statvar; u_int32_t sc_statmin; void *sc_ih; }; static struct riscv_timer_softc *riscv_timer_sc = NULL; int riscv_timer_get_freq(); int riscv_timer_match(struct device *, void *, void *); void riscv_timer_attach(struct device *, struct device *, void *); int riscv_timer_intr(void *); void riscv_timer_cpu_initclocks(); void riscv_timer_delay(u_int); void riscv_timer_setstatclockrate(int); void riscv_timer_startclock(); struct cfattach timer_ca = { sizeof (struct riscv_timer_softc), riscv_timer_match, riscv_timer_attach }; struct cfdriver timer_cd = { NULL, "timer", DV_DULL }; static inline uint64_t get_cycles() { return (rdtime()); } long get_counts(struct riscv_timer_softc *sc) { uint64_t counts; counts = get_cycles(); return (counts); } unsigned riscv_timer_get_timecount(struct timecounter *tc) { struct riscv_timer_softc *sc; sc = tc->tc_priv; return (get_counts(sc)); } int riscv_timer_get_freq() { int node, len; node = OF_finddevice("/cpus"); if (node == -1) { printf("Can't find cpus node.\n"); return (0); } len = OF_getproplen(node, "timebase-frequency"); if (len != 4) { printf("Can't find timebase-frequency property.\n"); return (0); } return OF_getpropint(node, "timebase-frequency", 0); } int riscv_timer_match(struct device *parent, void *cfdata, void *aux) { if (riscv_timer_sc) //already attached return 0; int node; // struct fdt_attach_args *fa = (struct fdt_attach_args *)aux; /* * return 1 if: * we can find valid "timebase-frequency" property from cpus */ if ( (node = OF_finddevice("/cpus")) == 0) return 0; return (OF_getproplen(node, "timebase-frequency") == 4);//32bit uint } void riscv_timer_attach(struct device *parent, struct device *self, void *aux) { struct riscv_timer_softc *sc = (struct riscv_timer_softc *)self; if (riscv_timer_sc)/* already attached */ return; sc->sc_ticks_per_second = riscv_timer_get_freq(); if (sc->sc_ticks_per_second == 0) { printf("Failed to resolve RISC-V Timer timebase\n"); return; } printf(": tick rate %d KHz\n", sc->sc_ticks_per_second/1000); riscv_timer_sc = sc; stathz = 0; riscv_clock_register(riscv_timer_cpu_initclocks, riscv_timer_delay, riscv_timer_setstatclockrate, riscv_timer_startclock); riscv_timer_timecount.tc_frequency = sc->sc_ticks_per_second; riscv_timer_timecount.tc_priv = sc; tc_init(&riscv_timer_timecount); } int timer_mindelta = 0; /* what should this be? */ int riscv_timer_intr(void *frame) { struct riscv_timer_softc *sc; uint64_t next, now, newnow; int timermissed = 0; u_int new_hz = 100; int s; #ifdef DEBUG_TIMER printf("RISC-V Timer Interrupt\n"); #endif sc = riscv_timer_sc; s = splclock(); if (s < IPL_CLOCK) hardclock(frame); // XXX should base timer interval from the expected // time of expiration, not 'now' now = get_cycles(); next = now + ((sc->sc_ticks_per_second / new_hz)); do { newnow = get_cycles(); if (next < (newnow + timer_mindelta)) { /* slowly scale up miss timer. */ if (timermissed > 1) timer_mindelta ++; } next = newnow + timer_mindelta; sbi_set_timer(next); csr_set(sip, SIE_STIE); /* re-read current time to verif * time hasn't been set into the past */ newnow = get_cycles(); /* if we missed more than once, increment the min period */ timermissed++; } while (next <= newnow); splx(s); return (1); // Handled } void riscv_timer_cpu_initclocks() { struct riscv_timer_softc *sc = timer_cd.cd_devs[0]; struct riscv_timer_pcpu_softc *pc = &sc->sc_pstat[CPU_INFO_UNIT(curcpu())]; uint64_t next; stathz = hz; profhz = hz * 10; riscv_timer_setstatclockrate(stathz); sc->sc_ticks_per_intr = sc->sc_ticks_per_second / hz; sc->sc_ticks_err_cnt = sc->sc_ticks_per_second % hz; pc->pc_ticks_err_sum = 0; /* configure virtual timer interrupt */ sc->sc_ih = riscv_intc_intr_establish(IRQ_TIMER_SUPERVISOR, 0, riscv_timer_intr, NULL, "timer"); next = get_cycles() + sc->sc_ticks_per_intr; pc->pc_nexttickevent = pc->pc_nextstatevent = next; sbi_set_timer(next); csr_set(sie, SIE_STIE); } void riscv_timer_delay(u_int usec) { int64_t counts, counts_per_usec; uint64_t first, last; /* * Check the timers are setup, if not just * use a for loop for the meantime */ if (riscv_timer_sc == NULL) { for (; usec > 0; usec--) for (counts = 200; counts > 0; counts--) /* * Prevent the compiler from optimizing * out the loop */ cpufunc_nullop(); return; } /* Get the number of times to count */ counts_per_usec = ((riscv_timer_timecount.tc_frequency / 1000000) + 1); /* * Clamp the timeout at a maximum value (about 32 seconds with * a 66MHz clock). *Nobody* should be delay()ing for anywhere * near that length of time and if they are, they should be hung * out to dry. */ if (usec >= (0x80000000U / counts_per_usec)) counts = (0x80000000U / counts_per_usec) - 1; else counts = usec * counts_per_usec; first = get_counts(riscv_timer_sc); while (counts > 0) { last = get_counts(riscv_timer_sc); counts -= (int64_t)(last - first); first = last; } } void riscv_timer_setstatclockrate(int newhz) { /* dummy: clockrate on riscv is fixed*/ } /* is only called from secondary cpu */ void riscv_timer_startclock() { struct riscv_timer_softc *sc = timer_cd.cd_devs[0]; struct riscv_timer_pcpu_softc *pc = &sc->sc_pstat[CPU_INFO_UNIT(curcpu())]; uint64_t nextevent; nextevent = get_cycles() + sc->sc_ticks_per_intr; pc->pc_nexttickevent = pc->pc_nextstatevent = nextevent; riscv_intr_route(sc->sc_ih, 1, curcpu()); sbi_set_timer(nextevent); csr_set(sie, SIE_STIE); } /* * called at early mainbus_attach, to provide delay func * before timer and interrupt is ready */ void riscv_timer_init(void) { uint64_t cntfrq = 0; cntfrq = riscv_timer_get_freq(); if (cntfrq != 0) { riscv_clock_register(NULL, riscv_timer_delay, NULL, NULL); } }