204 lines
5.5 KiB
C
204 lines
5.5 KiB
C
/* 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 <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;
|
|
}
|