/* Copyright 2013-2018 IBM Corp. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or * implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include #include #include #include #include /* SLW timer related stuff */ static bool sbe_has_timer; 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 (!sbe_has_timer || 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); } bool p8_sbe_timer_ok(void) { return sbe_has_timer; } 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; }