1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195
|
// 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-p8.h>
#include <skiboot.h>
#include <timebase.h>
#include <xscom.h>
/* 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;
}
|