diff options
author | Benjamin Herrenschmidt <benh@au1.ibm.com> | 2015-09-10 10:09:26 +1000 |
---|---|---|
committer | Stewart Smith <stewart@linux.vnet.ibm.com> | 2015-09-11 16:42:17 +1000 |
commit | 9af5751a00faf9ad3fe4b52f56de9bd7835d9bb7 (patch) | |
tree | 5be21b54d2b9d301b9983cf18554c1386cdce00d /hw | |
parent | b5f05ac337b3495cf506f5492fd053e90ae0bda3 (diff) | |
download | skiboot-9af5751a00faf9ad3fe4b52f56de9bd7835d9bb7.zip skiboot-9af5751a00faf9ad3fe4b52f56de9bd7835d9bb7.tar.gz skiboot-9af5751a00faf9ad3fe4b52f56de9bd7835d9bb7.tar.bz2 |
slw/timer: SBE based timer support
Recent HostBoot & SBE firmware provide a HW timer facility that can
be used to implement OPAL timers and thus limit the reliance on the
Linux heartbeat.
This implements support for it. The side effect is that i2c from Centaurs
is now usable.
Signed-off-by: Benjamin Herrenschmidt <benh@kernel.crashing.org>
[stewart@linux.vnet.ibm.com: fix run-timer unit test]
Signed-off-by: Stewart Smith <stewart@linux.vnet.ibm.com>
Diffstat (limited to 'hw')
-rw-r--r-- | hw/occ.c | 7 | ||||
-rw-r--r-- | hw/slw.c | 183 |
2 files changed, 174 insertions, 16 deletions
@@ -26,6 +26,7 @@ #include <errorlog.h> #include <opal-api.h> #include <opal-msg.h> +#include <timer.h> /* OCC Communication Area for PStates */ @@ -715,9 +716,11 @@ static struct fsp_client fsp_occ_client = { #define OCB_OCI_OCCMISC_OR 0x6a022 #define OCB_OCI_OCIMISC_IRQ PPC_BIT(0) #define OCB_OCI_OCIMISC_IRQ_TMGT PPC_BIT(1) +#define OCB_OCI_OCIMISC_IRQ_SLW_TMR PPC_BIT(14) #define OCB_OCI_OCIMISC_IRQ_OPAL_DUMMY PPC_BIT(15) #define OCB_OCI_OCIMISC_MASK (OCB_OCI_OCIMISC_IRQ_TMGT | \ - OCB_OCI_OCIMISC_IRQ_OPAL_DUMMY ) + OCB_OCI_OCIMISC_IRQ_OPAL_DUMMY | \ + OCB_OCI_OCIMISC_IRQ_SLW_TMR) void occ_send_dummy_interrupt(void) { @@ -765,6 +768,8 @@ void occ_interrupt(uint32_t chip_id) /* Dispatch */ if (ireg & OCB_OCI_OCIMISC_IRQ_TMGT) prd_tmgt_interrupt(chip_id); + if (ireg & OCB_OCI_OCIMISC_IRQ_SLW_TMR) + check_timers(true); /* We may have masked-out OCB_OCI_OCIMISC_IRQ in the previous * OCCMISC_AND write. Check if there are any new source bits set, @@ -40,6 +40,14 @@ static uint32_t slw_saved_reset[MAX_RESET_PATCH_SIZE]; static bool slw_current_le = false; #endif /* __HAVE_LIBPORE__ */ +/* SLW timer related stuff */ +static bool slw_has_timer; +static uint64_t slw_timer_inc; +static uint64_t slw_timer_target; +static uint32_t slw_timer_chip; +static uint64_t slw_last_gen; +static uint64_t slw_last_gen_stamp; + /* Assembly in head.S */ extern void enter_rvwinkle(void); @@ -897,7 +905,7 @@ static void slw_patch_regs(struct proc_chip *chip) static void slw_init_chip(struct proc_chip *chip) { - int rc __unused; + int64_t rc; struct cpu_thread *c; prlog(PR_DEBUG, "SLW: Init chip 0x%x\n", chip->id); @@ -913,7 +921,7 @@ static void slw_init_chip(struct proc_chip *chip) &chip->slw_image_size); if (rc != 0) { log_simple_error(&e_info(OPAL_RC_SLW_INIT), - "SLW: Error %d reading SLW image size\n", rc); + "SLW: Error %lld reading SLW image size\n", rc); /* XXX Panic ? */ chip->slw_base = 0; chip->slw_bar_size = 0; @@ -939,19 +947,6 @@ static void slw_init_chip(struct proc_chip *chip) } } -void slw_init(void) -{ - struct proc_chip *chip; - - if (proc_gen != proc_gen_p8) - return; - - for_each_chip(chip) - slw_init_chip(chip); - - add_cpu_idle_state_properties(); -} - /* Workarounds while entering fast-sleep */ static void fast_sleep_enter(void) @@ -1085,3 +1080,161 @@ static int64_t opal_slw_set_reg(uint64_t cpu_pir, uint64_t sprn, uint64_t val) opal_call(OPAL_SLW_SET_REG, opal_slw_set_reg, 3); #endif /* __HAVE_LIBPORE__ */ + +static void slw_dump_timer_ffdc(void) +{ + uint64_t i, val; + int64_t rc; + + static const uint32_t dump_regs[] = { + 0xe0000, 0xe0001, 0xe0002, 0xe0003, + 0xe0004, 0xe0005, 0xe0006, 0xe0007, + 0xe0008, 0xe0009, 0xe000a, 0xe000b, + 0xe000c, 0xe000d, 0xe000e, 0xe000f, + 0xe0010, 0xe0011, 0xe0012, 0xe0013, + 0xe0014, 0xe0015, 0xe0016, 0xe0017, + 0xe0018, 0xe0019, + 0x5001c, + 0x50038, 0x50039, 0x5003a, 0x5003b + }; + + prlog(PR_ERR, "SLW: Register state:\n"); + + for (i = 0; i < ARRAY_SIZE(dump_regs); i++) { + uint32_t reg = dump_regs[i]; + rc = xscom_read(slw_timer_chip, reg, &val); + if (rc) { + prlog(PR_ERR, "SLW: XSCOM error %lld reading" + " reg 0x%x\n", rc, reg); + break; + } + prlog(PR_ERR, "SLW: %5x = %016llx\n", reg, val); + } +} + +/* This is called with the timer lock held, so there is no + * issue with re-entrancy or concurrence + */ +void slw_update_timer_expiry(uint64_t new_target) +{ + uint64_t count, gen, gen2, req, now = mftb(); + int64_t rc; + + if (!slw_has_timer || new_target == slw_timer_target) + return; + + slw_timer_target = new_target; + + /* Calculate how many increments from now, rounded up */ + if (now < new_target) + count = (new_target - now + slw_timer_inc - 1) / slw_timer_inc; + else + count = 1; + + /* Max counter is 24-bit */ + if (count > 0xffffff) + count = 0xffffff; + /* Fabricate update request */ + req = (1ull << 63) | (count << 32); + + prlog(PR_TRACE, "SLW: TMR expiry: 0x%llx, req: %016llx\n", count, req); + + do { + /* Grab generation and spin if odd */ + for (;;) { + rc = xscom_read(slw_timer_chip, 0xE0006, &gen); + if (rc) { + prerror("SLW: Error %lld reading tmr gen " + " count\n", rc); + return; + } + if (!(gen & 1)) + break; + if (tb_compare(now + msecs_to_tb(1), mftb()) == TB_ABEFOREB) { + prerror("SLW: Stuck with odd generation !\n"); + slw_has_timer = false; + slw_dump_timer_ffdc(); + return; + } + } + + rc = xscom_write(slw_timer_chip, 0x5003A, req); + if (rc) { + prerror("SLW: Error %lld writing tmr request\n", rc); + return; + } + + /* Re-check gen count */ + rc = xscom_read(slw_timer_chip, 0xE0006, &gen2); + if (rc) { + prerror("SLW: Error %lld re-reading tmr gen " + " count\n", rc); + return; + } + } while(gen != gen2); + + /* Check if the timer is working. If at least 1ms has elapsed + * since the last call to this function, check that the gen + * count has changed + */ + if (tb_compare(slw_last_gen_stamp + msecs_to_tb(1), now) + == TB_ABEFOREB) { + if (slw_last_gen == gen) { + prlog(PR_ERR, + "SLW: Timer appears to not be running !\n"); + slw_has_timer = false; + slw_dump_timer_ffdc(); + } + slw_last_gen = gen; + slw_last_gen_stamp = mftb(); + } + + prlog(PR_TRACE, "SLW: gen: %llx\n", gen); +} + +bool slw_timer_ok(void) +{ + return slw_has_timer; +} + +static void slw_init_timer(void) +{ + struct dt_node *np; + int64_t rc; + uint32_t tick_us; + + np = dt_find_compatible_node(dt_root, NULL, "ibm,power8-sbe-timer"); + if (!np) + return; + + slw_timer_chip = dt_get_chip_id(np); + tick_us = dt_prop_get_u32(np, "tick-time-us"); + slw_timer_inc = usecs_to_tb(tick_us); + slw_timer_target = ~0ull; + + rc = xscom_read(slw_timer_chip, 0xE0006, &slw_last_gen); + if (rc) { + prerror("SLW: Error %lld reading tmr gen count\n", rc); + return; + } + slw_last_gen_stamp = mftb(); + + prlog(PR_INFO, "SLW: Timer facility on chip %d, resolution %dus\n", + slw_timer_chip, tick_us); + slw_has_timer = true; +} + +void slw_init(void) +{ + struct proc_chip *chip; + + if (proc_gen != proc_gen_p8) + return; + + for_each_chip(chip) + slw_init_chip(chip); + + add_cpu_idle_state_properties(); + + slw_init_timer(); +} |