diff options
-rw-r--r-- | core/interrupts.c | 7 | ||||
-rw-r--r-- | core/test/run-timer.c | 6 | ||||
-rw-r--r-- | core/timer.c | 69 | ||||
-rw-r--r-- | hw/occ.c | 7 | ||||
-rw-r--r-- | hw/slw.c | 183 | ||||
-rw-r--r-- | include/skiboot.h | 6 |
6 files changed, 235 insertions, 43 deletions
diff --git a/core/interrupts.c b/core/interrupts.c index b91508f..32f43ef 100644 --- a/core/interrupts.c +++ b/core/interrupts.c @@ -296,9 +296,6 @@ static int64_t opal_handle_interrupt(uint32_t isn, uint64_t *outstanding_event_m struct irq_source *is = irq_find_source(isn); int64_t rc = OPAL_SUCCESS; - /* We run the timers first */ - check_timers(true); - /* No source ? return */ if (!is || !is->ops->interrupt) { rc = OPAL_PARAMETER; @@ -308,6 +305,10 @@ static int64_t opal_handle_interrupt(uint32_t isn, uint64_t *outstanding_event_m /* Run it */ is->ops->interrupt(is->data, isn); + /* Check timers if SLW timer isn't working */ + if (!slw_timer_ok()) + check_timers(true); + /* Update output events */ bail: if (outstanding_event_mask) diff --git a/core/test/run-timer.c b/core/test/run-timer.c index 13b175e..e1a7cef 100644 --- a/core/test/run-timer.c +++ b/core/test/run-timer.c @@ -39,6 +39,12 @@ static void expiry(struct timer *t, void *data, uint64_t now) count--; } +void slw_update_timer_expiry(uint64_t new_target) +{ + (void)new_target; + /* FIXME: do intersting SLW timer sim */ +} + int main(void) { unsigned int i; diff --git a/core/timer.c b/core/timer.c index d5a3477..e97dc07 100644 --- a/core/timer.c +++ b/core/timer.c @@ -12,6 +12,9 @@ #include <cpu.h> #endif +/* Heartbeat requested from Linux */ +#define HEARTBEAT_DEFAULT_MS 2000 + static struct lock timer_lock = LOCK_UNLOCKED; static LIST_HEAD(timer_list); static LIST_HEAD(timer_poll_list); @@ -72,28 +75,44 @@ void cancel_timer_async(struct timer *t) unlock(&timer_lock); } -void schedule_timer_at(struct timer *t, uint64_t when) +static void __schedule_timer_at(struct timer *t, uint64_t when) { struct timer *lt; - lock(&timer_lock); + /* If the timer is already scheduled, take it out */ if (t->link.next) __remove_timer(t); + + /* Update target */ t->target = when; + if (when == TIMER_POLL) { + /* It's a poller, add it to the poller list */ t->gen = timer_poll_gen; list_add_tail(&timer_poll_list, &t->link); } else { + /* It's a real timer, add it in the right spot in the + * ordered timer list + */ list_for_each(&timer_list, lt, link) { - if (when < lt->target) { - list_add_before(&timer_list, &t->link, - <->link); - unlock(&timer_lock); - return; - } + if (when >= lt->target) + continue; + list_add_before(&timer_list, &t->link, <->link); + goto bail; } list_add_tail(&timer_list, &t->link); } + bail: + /* Pick up the next timer and upddate the SBE HW timer */ + lt = list_top(&timer_list, struct timer, link); + if (lt) + slw_update_timer_expiry(lt->target); +} + +void schedule_timer_at(struct timer *t, uint64_t when) +{ + lock(&timer_lock); + __schedule_timer_at(t, when); unlock(&timer_lock); } @@ -134,18 +153,19 @@ static void __check_poll_timers(uint64_t now) t = list_top(&timer_poll_list, struct timer, link); /* Top timer has a different generation than current ? Must - * be older + * be older, we are done. */ if (!t || t->gen == timer_poll_gen) break; - /* Top of list still running, we have to delay handling - * it. For now just skip until the next poll, when we have - * SLW interrupts, we'll probably want to trip another one - * ASAP + /* Top of list still running, we have to delay handling it, + * let's reprogram the SLW with a small delay. We chose + * arbitrarily 1us. */ - if (t->running) + if (t->running) { + slw_update_timer_expiry(now + usecs_to_tb(1)); break; + } /* Allright, first remove it and mark it running */ __remove_timer(t); @@ -222,24 +242,25 @@ void check_timers(bool from_interrupt) } #ifndef __TEST__ + void late_init_timers(void) { /* Add a property requesting the OS to call opal_poll_event() at * a specified interval in order for us to run our background - * low priority poller. + * low priority pollers. * - * When we have a working SLW based HW timer, we'll be able to - * reduce this or even remove it, for now however, we want to be - * called at least every couple of seconds on FSP based machines - * and a bit faster on BMC based machines where the LPC and i2c - * interrupts might not be functional. + * If we have an SLW timer facility, we run this 10 times slower, + * we could possibly completely get rid of it. * * We use a value in milliseconds, we don't want this to ever be * faster than that. */ - if (fsp_present()) - dt_add_property_cells(opal_node, "ibm,heartbeat-ms", 2000); - else - dt_add_property_cells(opal_node, "ibm,heartbeat-ms", 250); + if (slw_timer_ok() || fsp_present()) { + dt_add_property_cells(opal_node, "ibm,heartbeat-ms", + HEARTBEAT_DEFAULT_MS); + } else { + dt_add_property_cells(opal_node, "ibm,heartbeat-ms", + HEARTBEAT_DEFAULT_MS / 10); + } } #endif @@ -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(); +} diff --git a/include/skiboot.h b/include/skiboot.h index 8788add..4b58597 100644 --- a/include/skiboot.h +++ b/include/skiboot.h @@ -253,6 +253,12 @@ extern void *create_dtb(const struct dt_node *root); /* SLW reinit function for switching core settings */ extern int64_t slw_reinit(uint64_t flags); +/* SLW update timer function */ +extern void slw_update_timer_expiry(uint64_t new_target); + +/* Is SLW timer available ? */ +extern bool slw_timer_ok(void); + /* Fallback fake RTC */ extern void fake_rtc_init(void); |