blob: 930c9ab5839e17fee50e9a4d2bba893e3079632b [file] [log] [blame]
/* Copyright 2017-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.
*/
/*
* P9 OPAL - SBE communication driver
*
* P9 chip has Self Boot Engine (SBE). OPAL uses SBE for various purpose like
* timer, scom, MPIPL, etc,. Every chip has SBE. OPAL can communicate to SBE
* on all chips. Based on message type it selects appropriate SBE (ex: schedule
* timer on any chip).
*
* OPAL communicates to SBE via a set of data and control registers provided by
* the PSU block in P9 chip.
* - Four 8 byte registers for Host to send command packets to SBE.
* - Four 8 byte registers for SBE to send response packets to Host.
* - Two doorbell registers (1 on each side) to alert either party
* when data is placed in above mentioned data registers. Once Host/SBE reads
* incoming data, it should clear doorbell register. Interrupt is disabled
* as soon as doorbell register is cleared.
*
* OPAL - SBE message format:
* - OPAL communicates to SBE via set of well defined commands.
* - Reg0 contains message header (command class, subclass, flags etc).
* - Reg1-3 contains actual data. If data is big then it uses indirect method
* (data is passed via memory and memory address/size is passed in Reg1-3).
* - Every message has defined timeout. SBE must respond within specified
* time. Otherwise OPAL discards message and sends error message to caller.
*
* Constraints:
* - Only one command is accepted in the command buffer until the response for
* the command is enqueued in the response buffer by SBE.
*/
#define pr_fmt(fmt) "SBE: " fmt
#include <chip.h>
#include <errorlog.h>
#include <lock.h>
#include <opal.h>
#include <sbe-p9.h>
#include <skiboot.h>
#include <timebase.h>
#include <timer.h>
#include <trace.h>
#include <xscom.h>
enum p9_sbe_mbox_state {
sbe_mbox_idle = 0, /* Ready to send message */
sbe_mbox_send, /* Message sent, waiting for ack/response */
sbe_mbox_rr, /* SBE in R/R */
};
struct p9_sbe {
/* Chip ID to send message */
u32 chip_id;
/* List to hold SBE queue messages */
struct list_head msg_list;
struct lock lock;
enum p9_sbe_mbox_state state;
/* SBE MBOX message sequence number */
u16 cur_seq;
};
/* Default SBE chip ID */
static int sbe_default_chip_id = -1;
/* Is SBE timer running? */
static bool sbe_has_timer = false;
static bool sbe_timer_in_progress = false;
static bool has_new_target = false;
/* Inflight and next timer in TB */
static uint64_t sbe_last_gen_stamp;
static uint64_t sbe_timer_target;
/* Timer lock */
struct lock sbe_timer_lock;
/*
* Minimum timeout value for P9 is 500 microseconds. After that
* SBE timer can handle granularity of 1 microsecond.
*/
#define SBE_TIMER_DEFAULT_US 500
static uint64_t sbe_timer_def_tb;
/*
* Rate limit continuous timer update.
* We can update inflight timer if new timer request is lesser than inflight
* one. Limit such updates so that SBE gets time to handle FIFO side requests.
*/
#define SBE_TIMER_UPDATE_MAX 2
static uint32_t timer_update_cnt = 0;
/* Timer control message */
static struct p9_sbe_msg *timer_ctrl_msg;
#define SBE_STATUS_PRI_SHIFT 0x30
#define SBE_STATUS_SEC_SHIFT 0x20
/* Forward declaration */
static void p9_sbe_timeout_poll_one(struct p9_sbe *sbe);
static void p9_sbe_timer_schedule(void);
/* bit 0-15 : Primary status code */
static inline u16 p9_sbe_get_primary_rc(struct p9_sbe_msg *resp)
{
return (resp->reg[0] >> SBE_STATUS_PRI_SHIFT);
}
static inline void p9_sbe_set_primary_rc(struct p9_sbe_msg *resp, u64 rc)
{
resp->reg[0] |= (rc << SBE_STATUS_PRI_SHIFT);
}
static u64 p9_sbe_rreg(u32 chip_id, u64 reg)
{
u64 data = 0;
int rc;
rc = xscom_read(chip_id, reg, &data);
if (rc != OPAL_SUCCESS) {
prlog(PR_DEBUG, "XSCOM error %d reading reg 0x%llx\n", rc, reg);
return 0xffffffff;
}
return be64_to_cpu(data);
}
static void p9_sbe_reg_dump(u32 chip_id)
{
#define SBE_DUMP_REG_ONE(chip_id, x) \
prlog(PR_DEBUG, " %20s: %016llx\n", #x, p9_sbe_rreg(chip_id, x))
prlog(PR_DEBUG, "MBOX register dump for chip : %x\n", chip_id);
SBE_DUMP_REG_ONE(chip_id, PSU_SBE_DOORBELL_REG_RW);
SBE_DUMP_REG_ONE(chip_id, PSU_HOST_SBE_MBOX_REG0);
SBE_DUMP_REG_ONE(chip_id, PSU_HOST_SBE_MBOX_REG1);
SBE_DUMP_REG_ONE(chip_id, PSU_HOST_SBE_MBOX_REG2);
SBE_DUMP_REG_ONE(chip_id, PSU_HOST_SBE_MBOX_REG3);
SBE_DUMP_REG_ONE(chip_id, PSU_HOST_DOORBELL_REG_RW);
SBE_DUMP_REG_ONE(chip_id, PSU_HOST_SBE_MBOX_REG4);
SBE_DUMP_REG_ONE(chip_id, PSU_HOST_SBE_MBOX_REG5);
SBE_DUMP_REG_ONE(chip_id, PSU_HOST_SBE_MBOX_REG6);
SBE_DUMP_REG_ONE(chip_id, PSU_HOST_SBE_MBOX_REG7);
}
void p9_sbe_freemsg(struct p9_sbe_msg *msg)
{
if (msg && msg->resp)
free(msg->resp);
free(msg);
}
static void p9_sbe_fillmsg(struct p9_sbe_msg *msg, u16 cmd,
u16 ctrl_flag, u64 reg1, u64 reg2, u64 reg3)
{
bool response = !!(ctrl_flag & SBE_CMD_CTRL_RESP_REQ);
u16 flag;
/*
* Always set ack required flag. SBE will interrupt OPAL once it read
* message from mailbox register. If OPAL is expecting response, then
* it will update message timeout, otherwise it will send next message.
*/
flag = ctrl_flag | SBE_CMD_CTRL_ACK_REQ;
/* Seqence ID is filled by p9_sbe_queue_msg() */
msg->reg[0] = ((u64)flag << 32) | cmd;
msg->reg[1] = reg1;
msg->reg[2] = reg2;
msg->reg[3] = reg3;
msg->state = sbe_msg_unused;
msg->response = response;
}
static struct p9_sbe_msg *p9_sbe_allocmsg(bool alloc_resp)
{
struct p9_sbe_msg *msg;
msg = zalloc(sizeof(struct p9_sbe_msg));
if (!msg) {
prlog(PR_ERR, "Failed to allocate SBE message\n");
return NULL;
}
if (alloc_resp) {
msg->resp = zalloc(sizeof(struct p9_sbe_msg));
if (!msg->resp) {
prlog(PR_ERR, "Failed to allocate SBE resp message\n");
free(msg);
return NULL;
}
}
return msg;
}
/*
* Handles "command with direct data" format only.
*
* Note: All mbox messages of our interest uses direct data format. If we need
* indirect data format then we may have to enhance this function.
*/
struct p9_sbe_msg *p9_sbe_mkmsg(u16 cmd, u16 ctrl_flag,
u64 reg1, u64 reg2, u64 reg3)
{
struct p9_sbe_msg *msg;
msg = p9_sbe_allocmsg(!!(ctrl_flag & SBE_CMD_CTRL_RESP_REQ));
if (!msg)
return NULL;
p9_sbe_fillmsg(msg, cmd, ctrl_flag, reg1, reg2, reg3);
return msg;
}
static inline bool p9_sbe_mbox_busy(struct p9_sbe *sbe)
{
return (sbe->state != sbe_mbox_idle);
}
static inline bool p9_sbe_msg_busy(struct p9_sbe_msg *msg)
{
switch (msg->state) {
case sbe_msg_queued:
/* fall through */
case sbe_msg_sent:
case sbe_msg_wresp:
return true;
default: /* + sbe_msg_unused, sbe_msg_done,
sbe_msg_timeout, sbe_msg_error */
break;
}
return false;
}
static inline struct p9_sbe *p9_sbe_get_sbe(u32 chip_id)
{
struct proc_chip *chip;
/* Default to SBE on master chip */
if (chip_id == -1) {
if (sbe_default_chip_id == -1)
return NULL;
chip = get_chip(sbe_default_chip_id);
} else {
chip = get_chip(chip_id);
}
if (chip == NULL || chip->sbe == NULL)
return NULL;
return chip->sbe;
}
static int p9_sbe_msg_send(struct p9_sbe *sbe, struct p9_sbe_msg *msg)
{
int rc, i;
u64 addr, *data;
addr = PSU_HOST_SBE_MBOX_REG0;
data = &msg->reg[0];
for (i = 0; i < NR_HOST_SBE_MBOX_REG; i++) {
rc = xscom_write(sbe->chip_id, addr, *data);
if (rc)
return rc;
addr++;
data++;
}
rc = xscom_write(sbe->chip_id, PSU_SBE_DOORBELL_REG_OR,
HOST_SBE_MSG_WAITING);
if (rc != OPAL_SUCCESS)
return rc;
prlog(PR_TRACE, "Message queued [chip id = 0x%x]:\n\t Reg0 : %016llx"
"\n\t Reg1 : %016llx\n\t Reg2 : %016llx\n\t Reg3 : %016llx\n",
sbe->chip_id, msg->reg[0], msg->reg[1], msg->reg[2], msg->reg[3]);
msg->timeout = mftb() + msecs_to_tb(SBE_CMD_TIMEOUT_MAX);
sbe->state = sbe_mbox_send;
msg->state = sbe_msg_sent;
return rc;
}
static int p9_sbe_msg_receive(u32 chip_id, struct p9_sbe_msg *resp)
{
int i;
int rc = OPAL_SUCCESS;
u64 addr, *data;
addr = PSU_HOST_SBE_MBOX_REG4;
data = &resp->reg[0];
for (i = 0; i < NR_HOST_SBE_MBOX_REG; i++) {
rc = xscom_read(chip_id, addr, data);
if (rc)
return rc;
addr++;
data++;
}
return rc;
}
/* WARNING: This will drop sbe->lock */
static void p9_sbe_msg_complete(struct p9_sbe *sbe, struct p9_sbe_msg *msg,
enum p9_sbe_msg_state msg_state)
{
void (*comp)(struct p9_sbe_msg *msg);
prlog(PR_TRACE, "Completing msg [chip id = %x], reg0 : 0x%llx\n",
sbe->chip_id, msg->reg[0]);
comp = msg->complete;
list_del(&msg->link);
sync();
msg->state = msg_state;
if (comp) {
unlock(&sbe->lock);
comp(msg);
lock(&sbe->lock);
}
}
/* WARNING: This will drop sbe->lock */
static void p9_sbe_send_complete(struct p9_sbe *sbe)
{
struct p9_sbe_msg *msg;
if (list_empty(&sbe->msg_list))
return;
msg = list_top(&sbe->msg_list, struct p9_sbe_msg, link);
/* Need response */
if (msg->response) {
msg->state = sbe_msg_wresp;
} else {
sbe->state = sbe_mbox_idle;
p9_sbe_msg_complete(sbe, msg, sbe_msg_done);
}
}
/* WARNING: This will drop sbe->lock */
static void p9_sbe_process_queue(struct p9_sbe *sbe)
{
int rc, retry_cnt = 0;
struct p9_sbe_msg *msg = NULL;
if (p9_sbe_mbox_busy(sbe))
return;
while (!list_empty(&sbe->msg_list)) {
msg = list_top(&sbe->msg_list, struct p9_sbe_msg, link);
/* Send message */
rc = p9_sbe_msg_send(sbe, msg);
if (rc == OPAL_SUCCESS)
return;
prlog(PR_ERR, "Failed to send message to SBE [chip id = %x]\n",
sbe->chip_id);
if (msg->resp) {
p9_sbe_set_primary_rc(msg->resp,
SBE_STATUS_PRI_GENERIC_ERR);
}
p9_sbe_msg_complete(sbe, msg, sbe_msg_error);
/*
* Repeatedly failed to send message to SBE. Lets stop
* sending message.
*/
if (retry_cnt++ >= 3) {
prlog(PR_ERR, "Temporarily stopped sending "
"message to SBE\n");
return;
}
}
}
/*
* WARNING:
* Only one command is accepted in the command buffer until response
* to the command is enqueued in the response buffer by SBE.
*
* Head of msg_list contains in-flight message. Hence we should always
* add new message to tail of the list.
*/
int p9_sbe_queue_msg(u32 chip_id, struct p9_sbe_msg *msg,
void (*comp)(struct p9_sbe_msg *msg))
{
struct p9_sbe *sbe;
if (!msg)
return OPAL_PARAMETER;
sbe = p9_sbe_get_sbe(chip_id);
if (!sbe)
return OPAL_HARDWARE;
lock(&sbe->lock);
/* Set completion and update sequence number */
msg->complete = comp;
msg->state = sbe_msg_queued;
msg->reg[0] = msg->reg[0] | ((u64)sbe->cur_seq << 16);
sbe->cur_seq++;
/* Reset sequence number */
if (sbe->cur_seq == 0xffff)
sbe->cur_seq = 1;
/* Add message to queue */
list_add_tail(&sbe->msg_list, &msg->link);
p9_sbe_process_queue(sbe);
unlock(&sbe->lock);
return OPAL_SUCCESS;
}
int p9_sbe_sync_msg(u32 chip_id, struct p9_sbe_msg *msg, bool autofree)
{
int rc;
struct p9_sbe *sbe;
rc = p9_sbe_queue_msg(chip_id, msg, NULL);
if (rc)
goto free_msg;
sbe = p9_sbe_get_sbe(chip_id);
if (!sbe) {
rc = OPAL_HARDWARE;
goto free_msg;
}
while (p9_sbe_msg_busy(msg)) {
cpu_relax();
p9_sbe_timeout_poll_one(sbe);
}
if (msg->state == sbe_msg_done)
rc = SBE_STATUS_PRI_SUCCESS;
else
rc = SBE_STATUS_PRI_GENERIC_ERR;
if (msg->response && msg->resp)
rc = p9_sbe_get_primary_rc(msg->resp);
free_msg:
if (autofree)
p9_sbe_freemsg(msg);
return rc;
}
/* Remove SBE message from queue. It will not remove inflight message */
int p9_sbe_cancelmsg(u32 chip_id, struct p9_sbe_msg *msg)
{
struct p9_sbe *sbe;
sbe = p9_sbe_get_sbe(chip_id);
if (!sbe)
return OPAL_PARAMETER;
lock(&sbe->lock);
if (msg->state != sbe_msg_queued) {
unlock(&sbe->lock);
return OPAL_BUSY;
}
list_del(&msg->link);
msg->state = sbe_msg_done;
unlock(&sbe->lock);
return OPAL_SUCCESS;
}
static void p9_sbe_handle_response(u32 chip_id, struct p9_sbe_msg *msg)
{
u16 send_seq, resp_seq;
int rc;
if (msg == NULL || msg->resp == NULL)
return;
memset(msg->resp, 0, sizeof(struct p9_sbe_msg));
rc = p9_sbe_msg_receive(chip_id, msg->resp);
if (rc != OPAL_SUCCESS) {
prlog(PR_ERR, "Failed to read response message "
"[chip id = %x]\n", chip_id);
p9_sbe_set_primary_rc(msg->resp, SBE_STATUS_PRI_GENERIC_ERR);
return;
}
/* Validate sequence number */
send_seq = (msg->reg[0] >> 16) & 0xffff;
resp_seq = (msg->resp->reg[0] >> 16) & 0xffff;
if (send_seq != resp_seq) {
/*
* XXX Handle SBE R/R.
* Lets send sequence error to caller until SBE reset works.
*/
prlog(PR_ERR, "Invalid sequence id [chip id = %x]\n", chip_id);
p9_sbe_set_primary_rc(msg->resp, SBE_STATUS_PRI_SEQ_ERR);
return;
}
}
static int p9_sbe_clear_interrupt(struct p9_sbe *sbe, u64 bits)
{
int rc;
u64 val;
/* Clear doorbell register */
val = SBE_HOST_RESPONSE_MASK & ~bits;
rc = xscom_write(sbe->chip_id, PSU_HOST_DOORBELL_REG_AND, val);
if (rc) {
prlog(PR_ERR, "Failed to clear SBE to Host doorbell "
"interrupt [chip id = %x]\n", sbe->chip_id);
}
return rc;
}
/* WARNING: This will drop sbe->lock */
static void p9_sbe_timer_response(struct p9_sbe *sbe)
{
if (sbe->chip_id != sbe_default_chip_id)
return;
sbe_timer_in_progress = false;
/* Drop lock and call timers */
unlock(&sbe->lock);
lock(&sbe_timer_lock);
/*
* Once we get timer expiry interrupt (even if its suprious interrupt)
* we can schedule next timer request.
*/
timer_update_cnt = 0;
unlock(&sbe_timer_lock);
check_timers(true);
lock(&sbe->lock);
}
/* WARNING: This will drop sbe->lock */
static void __p9_sbe_interrupt(struct p9_sbe *sbe)
{
bool has_response;
int rc;
u64 data = 0, val;
struct p9_sbe_msg *msg = NULL;
again:
/* Read doorbell register */
rc = xscom_read(sbe->chip_id, PSU_HOST_DOORBELL_REG_RW, &data);
if (rc) {
prlog(PR_ERR, "Failed to read SBE to Host doorbell register "
"[chip id = %x]\n", sbe->chip_id);
p9_sbe_reg_dump(sbe->chip_id);
return;
}
/* Completed processing all the bits */
if (!data)
return;
/* SBE came back from reset */
if (data & SBE_HOST_RESET) {
/* Clear all bits and restart sending message */
rc = p9_sbe_clear_interrupt(sbe, data);
if (rc)
return;
prlog(PR_NOTICE,
"Back from reset [chip id = %x]\n", sbe->chip_id);
/* Reset SBE MBOX state */
sbe->state = sbe_mbox_idle;
/* Reset message state */
if (!list_empty(&sbe->msg_list)) {
msg = list_top(&sbe->msg_list, struct p9_sbe_msg, link);
msg->state = sbe_msg_queued;
}
return;
}
/* Process ACK message before response */
if (data & SBE_HOST_MSG_READ) {
rc = p9_sbe_clear_interrupt(sbe, SBE_HOST_MSG_READ);
if (rc)
return;
p9_sbe_send_complete(sbe);
goto again;
}
/* Read SBE response before clearing doorbell register */
if (data & SBE_HOST_RESPONSE_WAITING) {
if (!list_empty(&sbe->msg_list)) {
msg = list_top(&sbe->msg_list, struct p9_sbe_msg, link);
p9_sbe_handle_response(sbe->chip_id, msg);
has_response = true;
} else {
has_response = false;
prlog(PR_DEBUG,
"Got response with no pending message\n");
}
rc = p9_sbe_clear_interrupt(sbe, SBE_HOST_RESPONSE_WAITING);
if (rc)
return;
/* Reset SBE MBOX state */
sbe->state = sbe_mbox_idle;
if (has_response)
p9_sbe_msg_complete(sbe, msg, sbe_msg_done);
goto again;
}
/* SBE passthrough command, call prd handler */
if (data & SBE_HOST_PASSTHROUGH) {
rc = p9_sbe_clear_interrupt(sbe, SBE_HOST_PASSTHROUGH);
if (rc)
return;
prd_sbe_passthrough(sbe->chip_id);
goto again;
}
/* Timer expired */
if (data & SBE_HOST_TIMER_EXPIRY) {
rc = p9_sbe_clear_interrupt(sbe, SBE_HOST_TIMER_EXPIRY);
if (rc)
return;
p9_sbe_timer_response(sbe);
goto again;
}
/* Unhandled bits */
val = data & ~(SBE_HOST_RESPONSE_MASK);
if (val) {
prlog(PR_ERR, "Unhandled interrupt bit [chip id = %x] : "
" %016llx\n", sbe->chip_id, val);
rc = p9_sbe_clear_interrupt(sbe, data);
if (rc)
return;
goto again;
}
}
void p9_sbe_interrupt(uint32_t chip_id)
{
struct proc_chip *chip;
struct p9_sbe *sbe;
chip = get_chip(chip_id);
if (chip == NULL || chip->sbe == NULL)
return;
sbe = chip->sbe;
lock(&sbe->lock);
__p9_sbe_interrupt(sbe);
p9_sbe_process_queue(sbe);
unlock(&sbe->lock);
}
/*
* Check if the timer is working. If at least 10ms elapsed since
* last scheduled timer expiry.
*/
static void p9_sbe_timer_poll(struct p9_sbe *sbe)
{
if (sbe->chip_id != sbe_default_chip_id)
return;
if (!sbe_has_timer || !sbe_timer_in_progress)
return;
if (tb_compare(mftb(), sbe_last_gen_stamp + msecs_to_tb(10))
!= TB_AAFTERB)
return;
prlog(PR_ERR, "Timer stuck, falling back to OPAL pollers.\n");
prlog(PR_ERR, "You will likely have slower I2C and may have "
"experienced increased jitter.\n");
p9_sbe_reg_dump(sbe->chip_id);
sbe_has_timer = false;
sbe_timer_in_progress = false;
}
static void p9_sbe_timeout_poll_one(struct p9_sbe *sbe)
{
struct p9_sbe_msg *msg;
if (sbe->chip_id == sbe_default_chip_id) {
if (list_empty_nocheck(&sbe->msg_list) &&
!sbe_timer_in_progress)
return;
} else {
if (list_empty_nocheck(&sbe->msg_list))
return;
}
lock(&sbe->lock);
/*
* In some cases there will be a delay in calling OPAL interrupt
* handler routine (opal_handle_interrupt). In such cases its
* possible that SBE has responded, but OPAL didn't act on that.
* Hence check for SBE response.
*/
__p9_sbe_interrupt(sbe);
p9_sbe_timer_poll(sbe);
if (list_empty(&sbe->msg_list))
goto out;
/*
* For some reason OPAL didn't sent message to SBE.
* Lets try to send message again.
*/
if (!p9_sbe_mbox_busy(sbe)) {
p9_sbe_process_queue(sbe);
goto out;
}
msg = list_top(&sbe->msg_list, struct p9_sbe_msg, link);
if (tb_compare(mftb(), msg->timeout) != TB_AAFTERB)
goto out;
/* Message timeout */
prlog(PR_ERR, "Message timeout [chip id = %x], cmd = %llx, "
"subcmd = %llx\n", sbe->chip_id,
(msg->reg[0] >> 8) & 0xff, msg->reg[0] & 0xff);
p9_sbe_reg_dump(sbe->chip_id);
if (msg->resp) {
p9_sbe_set_primary_rc(msg->resp,
SBE_STATUS_PRI_GENERIC_ERR);
}
/* XXX Handle SBE R/R. Reset SBE state until SBE R/R works. */
sbe->state = sbe_mbox_idle;
p9_sbe_msg_complete(sbe, msg, sbe_msg_timeout);
p9_sbe_process_queue(sbe);
out:
unlock(&sbe->lock);
}
static void p9_sbe_timeout_poll(void *user_data __unused)
{
struct p9_sbe *sbe;
struct proc_chip *chip;
for_each_chip(chip) {
if (chip->sbe == NULL)
continue;
sbe = chip->sbe;
p9_sbe_timeout_poll_one(sbe);
}
}
static void p9_sbe_timer_resp(struct p9_sbe_msg *msg)
{
if (msg->state != sbe_msg_done) {
prlog(PR_DEBUG, "Failed to schedule timer [chip id %x]\n",
sbe_default_chip_id);
} else {
/* Update last scheduled timer value */
sbe_last_gen_stamp = mftb() +
usecs_to_tb(timer_ctrl_msg->reg[1]);
sbe_timer_in_progress = true;
}
if (!has_new_target)
return;
lock(&sbe_timer_lock);
if (has_new_target) {
if (!p9_sbe_msg_busy(timer_ctrl_msg)) {
has_new_target = false;
p9_sbe_timer_schedule();
}
}
unlock(&sbe_timer_lock);
}
static void p9_sbe_timer_schedule(void)
{
int rc;
u32 tick_us = SBE_TIMER_DEFAULT_US;
u64 tb_cnt, now = mftb();
if (sbe_timer_in_progress) {
if (sbe_timer_target >= sbe_last_gen_stamp)
return;
if (now >= sbe_last_gen_stamp)
return;
/* Remaining time of inflight timer <= sbe_timer_def_tb */
if ((sbe_last_gen_stamp - now) <= sbe_timer_def_tb)
return;
}
/* Stop sending timer update chipop until inflight timer expires */
if (timer_update_cnt > SBE_TIMER_UPDATE_MAX)
return;
timer_update_cnt++;
if (now < sbe_timer_target) {
/* Calculate how many microseconds from now, rounded up */
if ((sbe_timer_target - now) > sbe_timer_def_tb) {
tb_cnt = sbe_timer_target - now + usecs_to_tb(1) - 1;
tick_us = tb_to_usecs(tb_cnt);
}
}
/* Clear sequence number. p9_sbe_queue_msg will add new sequene ID */
timer_ctrl_msg->reg[0] &= ~(PPC_BITMASK(32, 47));
/* Update timeout value */
timer_ctrl_msg->reg[1] = tick_us;
rc = p9_sbe_queue_msg(sbe_default_chip_id, timer_ctrl_msg,
p9_sbe_timer_resp);
if (rc != OPAL_SUCCESS) {
prlog(PR_ERR, "Failed to start timer [chip id = %x]\n",
sbe_default_chip_id);
return;
}
}
/*
* This is called with the timer lock held, so there is no
* issue with re-entrancy or concurrence
*/
void p9_sbe_update_timer_expiry(uint64_t new_target)
{
if (!sbe_has_timer || new_target == sbe_timer_target)
return;
lock(&sbe_timer_lock);
/* Timer message is in flight. Record new timer and schedule later */
if (p9_sbe_msg_busy(timer_ctrl_msg) || has_new_target) {
if (new_target < sbe_timer_target) {
sbe_timer_target = new_target;
has_new_target = true;
}
} else {
sbe_timer_target = new_target;
p9_sbe_timer_schedule();
}
unlock(&sbe_timer_lock);
}
/* Initialize SBE timer */
static void p9_sbe_timer_init(void)
{
timer_ctrl_msg = p9_sbe_mkmsg(SBE_CMD_CONTROL_TIMER,
CONTROL_TIMER_START, 0, 0, 0);
assert(timer_ctrl_msg);
init_lock(&sbe_timer_lock);
sbe_has_timer = true;
sbe_timer_target = mftb();
sbe_last_gen_stamp = ~0ull;
sbe_timer_def_tb = usecs_to_tb(SBE_TIMER_DEFAULT_US);
prlog(PR_INFO, "Timer facility on chip %x\n", sbe_default_chip_id);
}
bool p9_sbe_timer_ok(void)
{
return sbe_has_timer;
}
void p9_sbe_init(void)
{
struct dt_node *xn;
struct proc_chip *chip;
struct p9_sbe *sbe;
if (proc_gen < proc_gen_p9)
return;
dt_for_each_compatible(dt_root, xn, "ibm,xscom") {
sbe = zalloc(sizeof(struct p9_sbe));
assert(sbe);
sbe->chip_id = dt_get_chip_id(xn);
sbe->cur_seq = 1;
sbe->state = sbe_mbox_idle;
list_head_init(&sbe->msg_list);
init_lock(&sbe->lock);
chip = get_chip(sbe->chip_id);
assert(chip);
chip->sbe = sbe;
if (dt_has_node_property(xn, "primary", NULL)) {
sbe_default_chip_id = sbe->chip_id;
prlog(PR_DEBUG, "Master chip id : %x\n", sbe->chip_id);
}
}
if (sbe_default_chip_id == -1) {
prlog(PR_ERR, "Master chip ID not found.\n");
return;
}
/* Initiate SBE timer */
p9_sbe_timer_init();
/* Initiate SBE timeout poller */
opal_add_poller(p9_sbe_timeout_poll, NULL);
}