| // SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later |
| /* |
| * POWER8 Self Boot Engine (SLW - SLeep/Winkle) |
| * |
| * Copyright 2013-2018 IBM Corp. |
| */ |
| |
| #include <device.h> |
| #include <sbe.h> |
| #include <sbe-p8.h> |
| #include <skiboot.h> |
| #include <timebase.h> |
| #include <xscom.h> |
| |
| /* SLW timer related stuff */ |
| static uint64_t sbe_timer_inc; |
| static uint64_t sbe_timer_target; |
| static uint32_t sbe_timer_chip; |
| static uint64_t sbe_last_gen; |
| static uint64_t sbe_last_gen_stamp; |
| |
| static void p8_sbe_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 |
| }; |
| |
| /** |
| * @fwts-label SLWRegisterDump |
| * @fwts-advice An error condition occurred in sleep/winkle |
| * engines timer state machine. Dumping debug information to |
| * root-cause. OPAL/skiboot may be stuck on some operation that |
| * requires SLW timer state machine (e.g. core powersaving) |
| */ |
| prlog(PR_DEBUG, "SLW: Register state:\n"); |
| |
| for (i = 0; i < ARRAY_SIZE(dump_regs); i++) { |
| uint32_t reg = dump_regs[i]; |
| rc = xscom_read(sbe_timer_chip, reg, &val); |
| if (rc) { |
| prlog(PR_DEBUG, "SLW: XSCOM error %lld reading" |
| " reg 0x%x\n", rc, reg); |
| break; |
| } |
| prlog(PR_DEBUG, "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 p8_sbe_update_timer_expiry(uint64_t new_target) |
| { |
| uint64_t count, gen, gen2, req, now; |
| int64_t rc; |
| |
| if (new_target == sbe_timer_target) |
| return; |
| |
| sbe_timer_target = new_target; |
| |
| _xscom_lock(); |
| now = mftb(); |
| /* Calculate how many increments from now, rounded up */ |
| if (now < new_target) |
| count = (new_target - now + sbe_timer_inc - 1) / sbe_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(sbe_timer_chip, 0xE0006, &gen, false); |
| if (rc) { |
| prerror("SLW: Error %lld reading tmr gen " |
| " count\n", rc); |
| _xscom_unlock(); |
| return; |
| } |
| if (!(gen & 1)) |
| break; |
| if (tb_compare(now + msecs_to_tb(1), mftb()) == TB_ABEFOREB) { |
| /** |
| * @fwts-label SLWTimerStuck |
| * @fwts-advice The SLeep/Winkle Engine (SLW) |
| * failed to increment the generation number |
| * within our timeout period (it *should* have |
| * done so within ~10us, not >1ms. OPAL uses |
| * the SLW timer to schedule some operations, |
| * but can fall back to the (much less frequent |
| * OPAL poller, which although does not affect |
| * functionality, runs *much* less frequently. |
| * This could have the effect of slow I2C |
| * operations (for example). It may also mean |
| * that you *had* an increase in jitter, due |
| * to slow interactions with SLW. |
| * This error may also occur if the machine |
| * is connected to via soft FSI. |
| */ |
| prerror("SLW: timer stuck, falling back to OPAL pollers. You will likely have slower I2C and may have experienced increased jitter.\n"); |
| prlog(PR_DEBUG, "SLW: Stuck with odd generation !\n"); |
| _xscom_unlock(); |
| sbe_has_timer = false; |
| p8_sbe_dump_timer_ffdc(); |
| return; |
| } |
| } |
| |
| rc = _xscom_write(sbe_timer_chip, 0x5003A, req, false); |
| if (rc) { |
| prerror("SLW: Error %lld writing tmr request\n", rc); |
| _xscom_unlock(); |
| return; |
| } |
| |
| /* Re-check gen count */ |
| rc = _xscom_read(sbe_timer_chip, 0xE0006, &gen2, false); |
| if (rc) { |
| prerror("SLW: Error %lld re-reading tmr gen " |
| " count\n", rc); |
| _xscom_unlock(); |
| return; |
| } |
| } while(gen != gen2); |
| _xscom_unlock(); |
| |
| /* 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(sbe_last_gen_stamp + msecs_to_tb(1), now) |
| == TB_ABEFOREB) { |
| if (sbe_last_gen == gen) { |
| prlog(PR_ERR, |
| "SLW: Timer appears to not be running !\n"); |
| sbe_has_timer = false; |
| p8_sbe_dump_timer_ffdc(); |
| } |
| sbe_last_gen = gen; |
| sbe_last_gen_stamp = mftb(); |
| } |
| |
| prlog(PR_TRACE, "SLW: gen: %llx\n", gen); |
| } |
| |
| void p8_sbe_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; |
| |
| sbe_timer_chip = dt_get_chip_id(np); |
| tick_us = dt_prop_get_u32(np, "tick-time-us"); |
| sbe_timer_inc = usecs_to_tb(tick_us); |
| sbe_timer_target = ~0ull; |
| |
| rc = xscom_read(sbe_timer_chip, 0xE0006, &sbe_last_gen); |
| if (rc) { |
| prerror("SLW: Error %lld reading tmr gen count\n", rc); |
| return; |
| } |
| sbe_last_gen_stamp = mftb(); |
| |
| prlog(PR_INFO, "SLW: Timer facility on chip %d, resolution %dus\n", |
| sbe_timer_chip, tick_us); |
| sbe_has_timer = true; |
| } |