1089 lines
27 KiB
C
1089 lines
27 KiB
C
/* Copyright 2013-2014 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.
|
|
*/
|
|
|
|
/*
|
|
* Service Processor serial console handling code
|
|
*/
|
|
#include <skiboot.h>
|
|
#include <processor.h>
|
|
#include <io.h>
|
|
#include <fsp.h>
|
|
#include <console.h>
|
|
#include <opal.h>
|
|
#include <timebase.h>
|
|
#include <device.h>
|
|
#include <fsp-sysparam.h>
|
|
#include <errorlog.h>
|
|
#include <lock.h>
|
|
|
|
DEFINE_LOG_ENTRY(OPAL_RC_CONSOLE_HANG, OPAL_PLATFORM_ERR_EVT, OPAL_CONSOLE,
|
|
OPAL_PLATFORM_FIRMWARE,
|
|
OPAL_PREDICTIVE_ERR_GENERAL, OPAL_NA);
|
|
|
|
struct fsp_serbuf_hdr {
|
|
u16 partition_id;
|
|
u8 session_id;
|
|
u8 hmc_id;
|
|
u16 data_offset;
|
|
u16 last_valid;
|
|
u16 ovf_count;
|
|
u16 next_in;
|
|
u8 flags;
|
|
u8 reserved;
|
|
u16 next_out;
|
|
u8 data[];
|
|
};
|
|
#define SER_BUF_DATA_SIZE (0x10000 - sizeof(struct fsp_serbuf_hdr))
|
|
|
|
struct fsp_serial {
|
|
bool available;
|
|
bool open;
|
|
bool has_part0;
|
|
bool has_part1;
|
|
bool log_port;
|
|
bool out_poke;
|
|
char loc_code[LOC_CODE_SIZE];
|
|
u16 rsrc_id;
|
|
struct fsp_serbuf_hdr *in_buf;
|
|
struct fsp_serbuf_hdr *out_buf;
|
|
struct fsp_msg *poke_msg;
|
|
u8 waiting;
|
|
u64 irq;
|
|
u16 out_buf_prev_len;
|
|
u64 out_buf_timeout;
|
|
};
|
|
|
|
#define SER_BUFFER_SIZE 0x00040000UL
|
|
#define MAX_SERIAL 4
|
|
|
|
#define SER_BUFFER_OUT_TIMEOUT 10
|
|
|
|
static struct fsp_serial fsp_serials[MAX_SERIAL];
|
|
static bool got_intf_query;
|
|
static struct lock fsp_con_lock = LOCK_UNLOCKED;
|
|
static void* ser_buffer = NULL;
|
|
|
|
static void fsp_console_reinit(void)
|
|
{
|
|
int i;
|
|
void *base;
|
|
struct fsp_msg *msg;
|
|
|
|
/* Initialize out data structure pointers & TCE maps */
|
|
base = ser_buffer;
|
|
for (i = 0; i < MAX_SERIAL; i++) {
|
|
struct fsp_serial *ser = &fsp_serials[i];
|
|
|
|
ser->in_buf = base;
|
|
ser->out_buf = base + SER_BUFFER_SIZE/2;
|
|
base += SER_BUFFER_SIZE;
|
|
}
|
|
fsp_tce_map(PSI_DMA_SER0_BASE, ser_buffer,
|
|
4 * PSI_DMA_SER0_SIZE);
|
|
|
|
for (i = 0; i < MAX_SERIAL; i++) {
|
|
struct fsp_serial *fs = &fsp_serials[i];
|
|
|
|
if (!fs->available)
|
|
continue;
|
|
|
|
if (fs->rsrc_id == 0xffff)
|
|
continue;
|
|
prlog(PR_DEBUG, "FSP: Reassociating HVSI console %d\n", i);
|
|
msg = fsp_mkmsg(FSP_CMD_ASSOC_SERIAL, 2,
|
|
(fs->rsrc_id << 16) | 1, i);
|
|
if (!msg) {
|
|
prerror("FSPCON: Failed to allocate associate msg\n");
|
|
return;
|
|
}
|
|
if (fsp_queue_msg(msg, fsp_freemsg)) {
|
|
fsp_freemsg(msg);
|
|
prerror("FSPCON: Failed to queue associate msg\n");
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
static void fsp_close_consoles(void)
|
|
{
|
|
unsigned int i;
|
|
|
|
for (i = 0; i < MAX_SERIAL; i++) {
|
|
struct fsp_serial *fs = &fsp_serials[i];
|
|
|
|
if (!fs->available)
|
|
continue;
|
|
|
|
lock(&fsp_con_lock);
|
|
if (fs->open) {
|
|
fs->open = false;
|
|
fs->out_poke = false;
|
|
if (fs->poke_msg->state != fsp_msg_unused)
|
|
fsp_cancelmsg(fs->poke_msg);
|
|
fsp_freemsg(fs->poke_msg);
|
|
fs->poke_msg = NULL;
|
|
}
|
|
unlock(&fsp_con_lock);
|
|
}
|
|
prlog(PR_DEBUG, "FSPCON: Closed consoles due to FSP reset/reload\n");
|
|
}
|
|
|
|
static void fsp_pokemsg_reclaim(struct fsp_msg *msg)
|
|
{
|
|
struct fsp_serial *fs = msg->user_data;
|
|
|
|
/*
|
|
* The poke_msg might have been "detached" from the console
|
|
* in vserial_close, so we need to check whether it's current
|
|
* before touching the state, otherwise, just free it
|
|
*/
|
|
lock(&fsp_con_lock);
|
|
if (fs->open && fs->poke_msg == msg) {
|
|
if (fs->out_poke) {
|
|
if (fsp_queue_msg(fs->poke_msg, fsp_pokemsg_reclaim)) {
|
|
prerror("FSPCON: failed to queue poke msg\n");
|
|
} else {
|
|
fs->out_poke = false;
|
|
}
|
|
} else
|
|
fs->poke_msg->state = fsp_msg_unused;
|
|
} else
|
|
fsp_freemsg(msg);
|
|
unlock(&fsp_con_lock);
|
|
}
|
|
|
|
/* Called with the fsp_con_lock held */
|
|
static size_t fsp_write_vserial(struct fsp_serial *fs, const char *buf,
|
|
size_t len)
|
|
{
|
|
struct fsp_serbuf_hdr *sb = fs->out_buf;
|
|
u16 old_nin = sb->next_in;
|
|
u16 space, chunk;
|
|
|
|
if (!fs->open)
|
|
return 0;
|
|
|
|
space = (sb->next_out + SER_BUF_DATA_SIZE - old_nin - 1)
|
|
% SER_BUF_DATA_SIZE;
|
|
if (space < len)
|
|
len = space;
|
|
if (!len)
|
|
return 0;
|
|
|
|
chunk = SER_BUF_DATA_SIZE - old_nin;
|
|
if (chunk > len)
|
|
chunk = len;
|
|
memcpy(&sb->data[old_nin], buf, chunk);
|
|
if (chunk < len)
|
|
memcpy(&sb->data[0], buf + chunk, len - chunk);
|
|
lwsync();
|
|
sb->next_in = (old_nin + len) % SER_BUF_DATA_SIZE;
|
|
sync();
|
|
|
|
if (sb->next_out == old_nin && fs->poke_msg) {
|
|
if (fs->poke_msg->state == fsp_msg_unused) {
|
|
if (fsp_queue_msg(fs->poke_msg, fsp_pokemsg_reclaim))
|
|
prerror("FSPCON: poke msg queuing failed\n");
|
|
} else
|
|
fs->out_poke = true;
|
|
}
|
|
#ifndef DISABLE_CON_PENDING_EVT
|
|
opal_update_pending_evt(OPAL_EVENT_CONSOLE_OUTPUT,
|
|
OPAL_EVENT_CONSOLE_OUTPUT);
|
|
#endif
|
|
return len;
|
|
}
|
|
|
|
#ifdef DVS_CONSOLE
|
|
static int fsp_con_port = -1;
|
|
static bool fsp_con_full;
|
|
|
|
/*
|
|
* This is called by the code in console.c without the con_lock
|
|
* held. However it can be called as the result of any printf
|
|
* thus any other lock might be held including possibly the
|
|
* FSP lock
|
|
*/
|
|
static size_t fsp_con_write(const char *buf, size_t len)
|
|
{
|
|
size_t written;
|
|
|
|
if (fsp_con_port < 0)
|
|
return 0;
|
|
|
|
lock(&fsp_con_lock);
|
|
written = fsp_write_vserial(&fsp_serials[fsp_con_port], buf, len);
|
|
fsp_con_full = (written < len);
|
|
unlock(&fsp_con_lock);
|
|
|
|
return written;
|
|
}
|
|
|
|
static struct con_ops fsp_con_ops = {
|
|
.write = fsp_con_write,
|
|
};
|
|
#endif /* DVS_CONSOLE */
|
|
|
|
static void fsp_open_vserial(struct fsp_msg *msg)
|
|
{
|
|
struct fsp_msg *resp;
|
|
|
|
u16 part_id = msg->data.words[0] & 0xffff;
|
|
u16 sess_id = msg->data.words[1] & 0xffff;
|
|
u8 hmc_sess = msg->data.bytes[0];
|
|
u8 hmc_indx = msg->data.bytes[1];
|
|
u8 authority = msg->data.bytes[4];
|
|
u32 tce_in, tce_out;
|
|
struct fsp_serial *fs;
|
|
|
|
prlog(PR_INFO, "FSPCON: Got VSerial Open\n");
|
|
prlog(PR_DEBUG, " part_id = 0x%04x\n", part_id);
|
|
prlog(PR_DEBUG, " sess_id = 0x%04x\n", sess_id);
|
|
prlog(PR_DEBUG, " hmc_sess = 0x%02x\n", hmc_sess);
|
|
prlog(PR_DEBUG, " hmc_indx = 0x%02x\n", hmc_indx);
|
|
prlog(PR_DEBUG, " authority = 0x%02x\n", authority);
|
|
|
|
if (sess_id >= MAX_SERIAL || !fsp_serials[sess_id].available) {
|
|
prlog(PR_WARNING, "FSPCON: 0x%04x NOT AVAILABLE!\n", sess_id);
|
|
resp = fsp_mkmsg(FSP_RSP_OPEN_VSERIAL | 0x2f, 0);
|
|
if (!resp) {
|
|
prerror("FSPCON: Response allocation failed\n");
|
|
return;
|
|
}
|
|
if (fsp_queue_msg(resp, fsp_freemsg)) {
|
|
fsp_freemsg(resp);
|
|
prerror("FSPCON: Failed to queue response msg\n");
|
|
}
|
|
return;
|
|
}
|
|
|
|
fs = &fsp_serials[sess_id];
|
|
|
|
/* Hack ! On blades, the console opened via the mm has partition 1
|
|
* while the debug DVS generally has partition 0 (though you can
|
|
* use what you want really).
|
|
* We don't want a DVS open/close to crap on the blademm console
|
|
* thus if it's a raw console, gets an open with partID 1, we
|
|
* set a flag that ignores the close of partid 0
|
|
*/
|
|
if (fs->rsrc_id == 0xffff) {
|
|
if (part_id == 0)
|
|
fs->has_part0 = true;
|
|
if (part_id == 1)
|
|
fs->has_part1 = true;
|
|
}
|
|
|
|
tce_in = PSI_DMA_SER0_BASE + PSI_DMA_SER0_SIZE * sess_id;
|
|
tce_out = tce_in + SER_BUFFER_SIZE/2;
|
|
|
|
lock(&fsp_con_lock);
|
|
if (fs->open) {
|
|
prlog(PR_DEBUG, " already open, skipping init !\n");
|
|
unlock(&fsp_con_lock);
|
|
goto already_open;
|
|
}
|
|
|
|
fs->poke_msg = fsp_mkmsg(FSP_CMD_VSERIAL_OUT, 2,
|
|
msg->data.words[0],
|
|
msg->data.words[1] & 0xffff);
|
|
if (fs->poke_msg == NULL) {
|
|
prerror("FSPCON: Failed to allocate poke_msg\n");
|
|
unlock(&fsp_con_lock);
|
|
return;
|
|
}
|
|
|
|
fs->open = true;
|
|
fs->poke_msg->user_data = fs;
|
|
|
|
fs->in_buf->partition_id = fs->out_buf->partition_id = part_id;
|
|
fs->in_buf->session_id = fs->out_buf->session_id = sess_id;
|
|
fs->in_buf->hmc_id = fs->out_buf->hmc_id = hmc_indx;
|
|
fs->in_buf->data_offset = fs->out_buf->data_offset =
|
|
sizeof(struct fsp_serbuf_hdr);
|
|
fs->in_buf->last_valid = fs->out_buf->last_valid =
|
|
SER_BUF_DATA_SIZE - 1;
|
|
fs->in_buf->ovf_count = fs->out_buf->ovf_count = 0;
|
|
fs->in_buf->next_in = fs->out_buf->next_in = 0;
|
|
fs->in_buf->flags = fs->out_buf->flags = 0;
|
|
fs->in_buf->reserved = fs->out_buf->reserved = 0;
|
|
fs->in_buf->next_out = fs->out_buf->next_out = 0;
|
|
fs->out_buf_prev_len = 0;
|
|
fs->out_buf_timeout = 0;
|
|
unlock(&fsp_con_lock);
|
|
|
|
already_open:
|
|
resp = fsp_mkmsg(FSP_RSP_OPEN_VSERIAL, 6, msg->data.words[0],
|
|
msg->data.words[1] & 0xffff, 0, tce_in, 0, tce_out);
|
|
if (!resp) {
|
|
prerror("FSPCON: Failed to allocate open msg response\n");
|
|
return;
|
|
}
|
|
if (fsp_queue_msg(resp, fsp_freemsg)) {
|
|
fsp_freemsg(resp);
|
|
prerror("FSPCON: Failed to queue open msg response\n");
|
|
return;
|
|
}
|
|
|
|
#ifdef DVS_CONSOLE
|
|
prlog(PR_DEBUG, " log_port = %d\n", fs->log_port);
|
|
if (fs->log_port) {
|
|
fsp_con_port = sess_id;
|
|
sync();
|
|
/*
|
|
* We mark the FSP lock as being in the console
|
|
* path. We do that only once, we never unmark it
|
|
* (there is really no much point)
|
|
*/
|
|
fsp_used_by_console();
|
|
fsp_con_lock.in_con_path = true;
|
|
/* See comment in fsp_used_by_console */
|
|
lock(&fsp_con_lock);
|
|
unlock(&fsp_con_lock);
|
|
set_console(&fsp_con_ops);
|
|
}
|
|
#endif
|
|
}
|
|
|
|
static void fsp_close_vserial(struct fsp_msg *msg)
|
|
{
|
|
u16 part_id = msg->data.words[0] & 0xffff;
|
|
u16 sess_id = msg->data.words[1] & 0xffff;
|
|
u8 hmc_sess = msg->data.bytes[0];
|
|
u8 hmc_indx = msg->data.bytes[1];
|
|
u8 authority = msg->data.bytes[4];
|
|
struct fsp_serial *fs;
|
|
struct fsp_msg *resp;
|
|
|
|
prlog(PR_INFO, "FSPCON: Got VSerial Close\n");
|
|
prlog(PR_DEBUG, " part_id = 0x%04x\n", part_id);
|
|
prlog(PR_DEBUG, " sess_id = 0x%04x\n", sess_id);
|
|
prlog(PR_DEBUG, " hmc_sess = 0x%02x\n", hmc_sess);
|
|
prlog(PR_DEBUG, " hmc_indx = 0x%02x\n", hmc_indx);
|
|
prlog(PR_DEBUG, " authority = 0x%02x\n", authority);
|
|
|
|
if (sess_id >= MAX_SERIAL || !fsp_serials[sess_id].available) {
|
|
prlog(PR_WARNING, "FSPCON: 0x%04x NOT AVAILABLE!\n", sess_id);
|
|
goto skip_close;
|
|
}
|
|
|
|
fs = &fsp_serials[sess_id];
|
|
|
|
/* See "HACK" comment in open */
|
|
if (fs->rsrc_id == 0xffff) {
|
|
if (part_id == 0)
|
|
fs->has_part0 = false;
|
|
if (part_id == 1)
|
|
fs->has_part1 = false;
|
|
if (fs->has_part0 || fs->has_part1) {
|
|
prlog(PR_DEBUG, " skipping close !\n");
|
|
goto skip_close;
|
|
}
|
|
}
|
|
|
|
#ifdef DVS_CONSOLE
|
|
if (fs->log_port) {
|
|
fsp_con_port = -1;
|
|
set_console(NULL);
|
|
}
|
|
#endif
|
|
|
|
lock(&fsp_con_lock);
|
|
if (fs->open) {
|
|
fs->open = false;
|
|
fs->out_poke = false;
|
|
if (fs->poke_msg && fs->poke_msg->state == fsp_msg_unused) {
|
|
fsp_freemsg(fs->poke_msg);
|
|
fs->poke_msg = NULL;
|
|
}
|
|
}
|
|
unlock(&fsp_con_lock);
|
|
skip_close:
|
|
resp = fsp_mkmsg(FSP_RSP_CLOSE_VSERIAL, 2, msg->data.words[0],
|
|
msg->data.words[1] & 0xffff);
|
|
if (!resp) {
|
|
prerror("FSPCON: Failed to allocate close msg response\n");
|
|
return;
|
|
}
|
|
if (fsp_queue_msg(resp, fsp_freemsg)) {
|
|
fsp_freemsg(resp);
|
|
prerror("FSPCON: Failed to queue close msg response\n");
|
|
}
|
|
}
|
|
|
|
static bool fsp_con_msg_hmc(u32 cmd_sub_mod, struct fsp_msg *msg)
|
|
{
|
|
struct fsp_msg *resp;
|
|
|
|
/* Associate response */
|
|
if ((cmd_sub_mod >> 8) == 0xe08a) {
|
|
prlog(PR_TRACE, "FSPCON: Got associate response, status"
|
|
" 0x%02x\n", cmd_sub_mod & 0xff);
|
|
return true;
|
|
}
|
|
if ((cmd_sub_mod >> 8) == 0xe08b) {
|
|
prlog(PR_TRACE, "Got unassociate response, status 0x%02x\n",
|
|
cmd_sub_mod & 0xff);
|
|
return true;
|
|
}
|
|
switch(cmd_sub_mod) {
|
|
case FSP_CMD_OPEN_VSERIAL:
|
|
fsp_open_vserial(msg);
|
|
return true;
|
|
case FSP_CMD_CLOSE_VSERIAL:
|
|
fsp_close_vserial(msg);
|
|
return true;
|
|
case FSP_CMD_HMC_INTF_QUERY:
|
|
prlog(PR_DEBUG, "FSPCON: Got HMC interface query\n");
|
|
got_intf_query = true;
|
|
resp = fsp_mkmsg(FSP_RSP_HMC_INTF_QUERY, 1,
|
|
msg->data.words[0] & 0x00ffffff);
|
|
if (!resp) {
|
|
prerror("FSPCON: Failed to allocate hmc intf response\n");
|
|
return true;
|
|
}
|
|
if (fsp_queue_msg(resp, fsp_freemsg)) {
|
|
fsp_freemsg(resp);
|
|
prerror("FSPCON: Failed to queue hmc intf response\n");
|
|
}
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
static bool fsp_con_msg_vt(u32 cmd_sub_mod, struct fsp_msg *msg)
|
|
{
|
|
u16 sess_id = msg->data.words[1] & 0xffff;
|
|
|
|
if (cmd_sub_mod == FSP_CMD_VSERIAL_IN && sess_id < MAX_SERIAL) {
|
|
struct fsp_serial *fs = &fsp_serials[sess_id];
|
|
|
|
if (!fs->open)
|
|
return true;
|
|
|
|
/* FSP is signaling some incoming data. We take the console
|
|
* lock to avoid racing with a simultaneous read, though we
|
|
* might want to consider to simplify all that locking into
|
|
* one single lock that covers the console and the pending
|
|
* events.
|
|
*/
|
|
lock(&fsp_con_lock);
|
|
opal_update_pending_evt(OPAL_EVENT_CONSOLE_INPUT,
|
|
OPAL_EVENT_CONSOLE_INPUT);
|
|
opal_update_pending_evt(fs->irq, fs->irq);
|
|
unlock(&fsp_con_lock);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
static bool fsp_con_msg_rr(u32 cmd_sub_mod, struct fsp_msg *msg)
|
|
{
|
|
assert(msg == NULL);
|
|
|
|
switch (cmd_sub_mod) {
|
|
case FSP_RESET_START:
|
|
fsp_close_consoles();
|
|
return true;
|
|
case FSP_RELOAD_COMPLETE:
|
|
fsp_console_reinit();
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
static struct fsp_client fsp_con_client_hmc = {
|
|
.message = fsp_con_msg_hmc,
|
|
};
|
|
|
|
static struct fsp_client fsp_con_client_vt = {
|
|
.message = fsp_con_msg_vt,
|
|
};
|
|
|
|
static struct fsp_client fsp_con_client_rr = {
|
|
.message = fsp_con_msg_rr,
|
|
};
|
|
|
|
static void fsp_serial_add(int index, u16 rsrc_id, const char *loc_code,
|
|
bool log_port)
|
|
{
|
|
struct fsp_serial *ser;
|
|
struct fsp_msg *msg;
|
|
|
|
lock(&fsp_con_lock);
|
|
ser = &fsp_serials[index];
|
|
|
|
if (ser->available) {
|
|
unlock(&fsp_con_lock);
|
|
return;
|
|
}
|
|
|
|
ser->rsrc_id = rsrc_id;
|
|
memset(ser->loc_code, 0x00, LOC_CODE_SIZE);
|
|
strncpy(ser->loc_code, loc_code, LOC_CODE_SIZE - 1);
|
|
ser->available = true;
|
|
ser->log_port = log_port;
|
|
unlock(&fsp_con_lock);
|
|
|
|
/* DVS doesn't have that */
|
|
if (rsrc_id != 0xffff) {
|
|
msg = fsp_mkmsg(FSP_CMD_ASSOC_SERIAL, 2,
|
|
(rsrc_id << 16) | 1, index);
|
|
if (!msg) {
|
|
prerror("FSPCON: Assoc serial alloc failed\n");
|
|
return;
|
|
}
|
|
if (fsp_queue_msg(msg, fsp_freemsg)) {
|
|
fsp_freemsg(msg);
|
|
prerror("FSPCON: Assoc serial queue failed\n");
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
void fsp_console_preinit(void)
|
|
{
|
|
int i;
|
|
void *base;
|
|
|
|
if (!fsp_present())
|
|
return;
|
|
|
|
ser_buffer = memalign(TCE_PSIZE, SER_BUFFER_SIZE * MAX_SERIAL);
|
|
|
|
/* Initialize out data structure pointers & TCE maps */
|
|
base = ser_buffer;
|
|
for (i = 0; i < MAX_SERIAL; i++) {
|
|
struct fsp_serial *ser = &fsp_serials[i];
|
|
|
|
ser->in_buf = base;
|
|
ser->out_buf = base + SER_BUFFER_SIZE/2;
|
|
base += SER_BUFFER_SIZE;
|
|
}
|
|
fsp_tce_map(PSI_DMA_SER0_BASE, ser_buffer,
|
|
4 * PSI_DMA_SER0_SIZE);
|
|
|
|
/* Register for class E0 and E1 */
|
|
fsp_register_client(&fsp_con_client_hmc, FSP_MCLASS_HMC_INTFMSG);
|
|
fsp_register_client(&fsp_con_client_vt, FSP_MCLASS_HMC_VT);
|
|
fsp_register_client(&fsp_con_client_rr, FSP_MCLASS_RR_EVENT);
|
|
|
|
/* Add DVS ports. We currently have session 0 and 3, 0 is for
|
|
* OS use. 3 is our debug port. We need to add those before
|
|
* we complete the OPL or we'll potentially miss the
|
|
* console setup on Firebird blades.
|
|
*/
|
|
fsp_serial_add(0, 0xffff, "DVS_OS", false);
|
|
op_display(OP_LOG, OP_MOD_FSPCON, 0x0001);
|
|
fsp_serial_add(3, 0xffff, "DVS_FW", true);
|
|
op_display(OP_LOG, OP_MOD_FSPCON, 0x0002);
|
|
|
|
}
|
|
|
|
static int64_t fsp_console_write(int64_t term_number, int64_t *length,
|
|
const uint8_t *buffer)
|
|
{
|
|
struct fsp_serial *fs;
|
|
size_t written, requested;
|
|
|
|
if (term_number < 0 || term_number >= MAX_SERIAL)
|
|
return OPAL_PARAMETER;
|
|
fs = &fsp_serials[term_number];
|
|
if (!fs->available || fs->log_port)
|
|
return OPAL_PARAMETER;
|
|
lock(&fsp_con_lock);
|
|
if (!fs->open) {
|
|
unlock(&fsp_con_lock);
|
|
return OPAL_CLOSED;
|
|
}
|
|
/* Clamp to a reasonable size */
|
|
requested = *length;
|
|
if (requested > 0x1000)
|
|
requested = 0x1000;
|
|
written = fsp_write_vserial(fs, buffer, requested);
|
|
|
|
if (written) {
|
|
/* If we wrote anything, reset timeout */
|
|
fs->out_buf_prev_len = 0;
|
|
fs->out_buf_timeout = 0;
|
|
}
|
|
|
|
#ifdef OPAL_DEBUG_CONSOLE_IO
|
|
prlog(PR_TRACE, "OPAL: console write req=%ld written=%ld"
|
|
" ni=%d no=%d\n",
|
|
requested, written, fs->out_buf->next_in, fs->out_buf->next_out);
|
|
prlog(PR_TRACE, " %02x %02x %02x %02x "
|
|
"%02x \'%c\' %02x \'%c\' %02x \'%c\'.%02x \'%c\'..\n",
|
|
buffer[0], buffer[1], buffer[2], buffer[3],
|
|
buffer[4], buffer[4], buffer[5], buffer[5],
|
|
buffer[6], buffer[6], buffer[7], buffer[7]);
|
|
#endif /* OPAL_DEBUG_CONSOLE_IO */
|
|
|
|
*length = written;
|
|
unlock(&fsp_con_lock);
|
|
|
|
if (written)
|
|
return OPAL_SUCCESS;
|
|
|
|
return OPAL_HARDWARE;
|
|
}
|
|
|
|
static int64_t fsp_console_write_buffer_space(int64_t term_number,
|
|
int64_t *length)
|
|
{
|
|
static bool elog_generated = false;
|
|
struct fsp_serial *fs;
|
|
struct fsp_serbuf_hdr *sb;
|
|
|
|
if (term_number < 0 || term_number >= MAX_SERIAL)
|
|
return OPAL_PARAMETER;
|
|
fs = &fsp_serials[term_number];
|
|
if (!fs->available || fs->log_port)
|
|
return OPAL_PARAMETER;
|
|
lock(&fsp_con_lock);
|
|
if (!fs->open) {
|
|
unlock(&fsp_con_lock);
|
|
return OPAL_CLOSED;
|
|
}
|
|
sb = fs->out_buf;
|
|
*length = (sb->next_out + SER_BUF_DATA_SIZE - sb->next_in - 1)
|
|
% SER_BUF_DATA_SIZE;
|
|
unlock(&fsp_con_lock);
|
|
|
|
/* Console buffer has enough space to write incoming data */
|
|
if (*length != fs->out_buf_prev_len) {
|
|
fs->out_buf_prev_len = *length;
|
|
fs->out_buf_timeout = 0;
|
|
|
|
return OPAL_SUCCESS;
|
|
}
|
|
|
|
/*
|
|
* Buffer is full, start internal timer. We will continue returning
|
|
* SUCCESS until timeout happens, hoping FSP will consume data within
|
|
* timeout period.
|
|
*/
|
|
if (fs->out_buf_timeout == 0) {
|
|
fs->out_buf_timeout = mftb() +
|
|
secs_to_tb(SER_BUFFER_OUT_TIMEOUT);
|
|
}
|
|
|
|
if (tb_compare(mftb(), fs->out_buf_timeout) != TB_AAFTERB)
|
|
return OPAL_SUCCESS;
|
|
|
|
/*
|
|
* FSP is still active but not reading console data. Hence
|
|
* our console buffer became full. Most likely IPMI daemon
|
|
* on FSP is buggy. Lets log error and return OPAL_RESOURCE
|
|
* to payload (Linux).
|
|
*/
|
|
if (!elog_generated) {
|
|
elog_generated = true;
|
|
log_simple_error(&e_info(OPAL_RC_CONSOLE_HANG), "FSPCON: Console "
|
|
"buffer is full, dropping console data\n");
|
|
}
|
|
|
|
/* Timeout happened. Lets drop incoming data */
|
|
return OPAL_RESOURCE;
|
|
}
|
|
|
|
static int64_t fsp_console_read(int64_t term_number, int64_t *length,
|
|
uint8_t *buffer)
|
|
{
|
|
struct fsp_serial *fs;
|
|
struct fsp_serbuf_hdr *sb;
|
|
bool pending = false;
|
|
uint32_t old_nin, n, i, chunk, req = *length;
|
|
int rc = OPAL_SUCCESS;
|
|
|
|
if (term_number < 0 || term_number >= MAX_SERIAL)
|
|
return OPAL_PARAMETER;
|
|
fs = &fsp_serials[term_number];
|
|
if (!fs->available || fs->log_port)
|
|
return OPAL_PARAMETER;
|
|
lock(&fsp_con_lock);
|
|
if (!fs->open) {
|
|
rc = OPAL_CLOSED;
|
|
goto clr_flag;
|
|
}
|
|
if (fs->waiting)
|
|
fs->waiting = 0;
|
|
sb = fs->in_buf;
|
|
old_nin = sb->next_in;
|
|
lwsync();
|
|
n = (old_nin + SER_BUF_DATA_SIZE - sb->next_out)
|
|
% SER_BUF_DATA_SIZE;
|
|
if (n > req) {
|
|
pending = true;
|
|
n = req;
|
|
}
|
|
*length = n;
|
|
|
|
chunk = SER_BUF_DATA_SIZE - sb->next_out;
|
|
if (chunk > n)
|
|
chunk = n;
|
|
memcpy(buffer, &sb->data[sb->next_out], chunk);
|
|
if (chunk < n)
|
|
memcpy(buffer + chunk, &sb->data[0], n - chunk);
|
|
sb->next_out = (sb->next_out + n) % SER_BUF_DATA_SIZE;
|
|
|
|
#ifdef OPAL_DEBUG_CONSOLE_IO
|
|
prlog(PR_TRACE, "OPAL: console read req=%d read=%d ni=%d no=%d\n",
|
|
req, n, sb->next_in, sb->next_out);
|
|
prlog(PR_TRACE, " %02x %02x %02x %02x %02x %02x %02x %02x ...\n",
|
|
buffer[0], buffer[1], buffer[2], buffer[3],
|
|
buffer[4], buffer[5], buffer[6], buffer[7]);
|
|
#endif /* OPAL_DEBUG_CONSOLE_IO */
|
|
|
|
clr_flag:
|
|
/* Might clear the input pending flag */
|
|
for (i = 0; i < MAX_SERIAL && !pending; i++) {
|
|
struct fsp_serial *fs = &fsp_serials[i];
|
|
struct fsp_serbuf_hdr *sb = fs->in_buf;
|
|
|
|
if (fs->log_port || !fs->open)
|
|
continue;
|
|
if (sb->next_out != sb->next_in) {
|
|
/*
|
|
* HACK: Some kernels (4.1+) may fail to properly
|
|
* register hvc1 and will never read it. This can lead
|
|
* to RCU stalls, so if we notice this console is not
|
|
* being read, do not set OPAL_EVENT_CONSOLE_INPUT even
|
|
* if it has data
|
|
*/
|
|
if (fs->waiting < 5) {
|
|
pending = true;
|
|
fs->waiting++;
|
|
}
|
|
}
|
|
}
|
|
if (!pending) {
|
|
opal_update_pending_evt(fs->irq, 0);
|
|
opal_update_pending_evt(OPAL_EVENT_CONSOLE_INPUT, 0);
|
|
}
|
|
|
|
unlock(&fsp_con_lock);
|
|
|
|
return rc;
|
|
}
|
|
|
|
void fsp_console_poll(void *data __unused)
|
|
{
|
|
#ifdef OPAL_DEBUG_CONSOLE_POLL
|
|
static int debug;
|
|
#endif
|
|
|
|
/*
|
|
* We don't get messages for out buffer being consumed, so we
|
|
* need to poll. We also defer sending of poke messages from
|
|
* the sapphire console to avoid a locking nightmare with
|
|
* beging called from printf() deep into an existing lock nest
|
|
* stack.
|
|
*/
|
|
if (fsp_con_full ||
|
|
(opal_pending_events & OPAL_EVENT_CONSOLE_OUTPUT)) {
|
|
unsigned int i;
|
|
bool pending = false;
|
|
|
|
/* We take the console lock. This is somewhat inefficient
|
|
* but it guarantees we aren't racing with a write, and
|
|
* thus clearing an event improperly
|
|
*/
|
|
lock(&fsp_con_lock);
|
|
for (i = 0; i < MAX_SERIAL && !pending; i++) {
|
|
struct fsp_serial *fs = &fsp_serials[i];
|
|
struct fsp_serbuf_hdr *sb = fs->out_buf;
|
|
|
|
if (!fs->open)
|
|
continue;
|
|
if (sb->next_out == sb->next_in) {
|
|
continue;
|
|
}
|
|
if (fs->log_port) {
|
|
flush_console();
|
|
} else {
|
|
#ifdef OPAL_DEBUG_CONSOLE_POLL
|
|
if (debug < 5) {
|
|
prlog(PR_DEBUG,"OPAL: %d still pending"
|
|
" ni=%d no=%d\n",
|
|
i, sb->next_in, sb->next_out);
|
|
debug++;
|
|
}
|
|
#endif /* OPAL_DEBUG_CONSOLE_POLL */
|
|
pending = true;
|
|
}
|
|
}
|
|
if (!pending) {
|
|
opal_update_pending_evt(OPAL_EVENT_CONSOLE_OUTPUT, 0);
|
|
#ifdef OPAL_DEBUG_CONSOLE_POLL
|
|
debug = 0;
|
|
#endif
|
|
}
|
|
unlock(&fsp_con_lock);
|
|
}
|
|
}
|
|
|
|
void fsp_console_init(void)
|
|
{
|
|
struct dt_node *serials, *ser;
|
|
int i;
|
|
|
|
if (!fsp_present())
|
|
return;
|
|
|
|
/* Wait until we got the intf query before moving on */
|
|
while (!got_intf_query)
|
|
opal_run_pollers();
|
|
|
|
op_display(OP_LOG, OP_MOD_FSPCON, 0x0000);
|
|
|
|
/* Register poller */
|
|
opal_add_poller(fsp_console_poll, NULL);
|
|
|
|
/* Register OPAL console backend */
|
|
set_opal_console(&fsp_opal_con);
|
|
|
|
/* Parse serial port data */
|
|
serials = dt_find_by_path(dt_root, "ipl-params/fsp-serial");
|
|
if (!serials) {
|
|
prerror("FSPCON: No FSP serial ports in device-tree\n");
|
|
return;
|
|
}
|
|
|
|
i = 1;
|
|
dt_for_each_child(serials, ser) {
|
|
u32 rsrc_id = dt_prop_get_u32(ser, "reg");
|
|
const void *lc = dt_prop_get(ser, "ibm,loc-code");
|
|
|
|
prlog(PR_NOTICE, "FSPCON: Serial %d rsrc: %04x loc: %s\n",
|
|
i, rsrc_id, (const char *)lc);
|
|
fsp_serial_add(i++, rsrc_id, lc, false);
|
|
op_display(OP_LOG, OP_MOD_FSPCON, 0x0010 + i);
|
|
}
|
|
|
|
op_display(OP_LOG, OP_MOD_FSPCON, 0x0005);
|
|
}
|
|
|
|
static int64_t fsp_console_flush(int64_t terminal __unused)
|
|
{
|
|
/* FIXME: There's probably something we can do here... */
|
|
return OPAL_PARAMETER;
|
|
}
|
|
|
|
struct opal_con_ops fsp_opal_con = {
|
|
.name = "FSP OPAL console",
|
|
.init = NULL, /* all the required setup is done in fsp_console_init() */
|
|
.read = fsp_console_read,
|
|
.write = fsp_console_write,
|
|
.space = fsp_console_write_buffer_space,
|
|
.flush = fsp_console_flush,
|
|
};
|
|
|
|
static void flush_all_input(void)
|
|
{
|
|
unsigned int i;
|
|
|
|
lock(&fsp_con_lock);
|
|
for (i = 0; i < MAX_SERIAL; i++) {
|
|
struct fsp_serial *fs = &fsp_serials[i];
|
|
struct fsp_serbuf_hdr *sb = fs->in_buf;
|
|
|
|
if (fs->log_port)
|
|
continue;
|
|
|
|
sb->next_out = sb->next_in;
|
|
}
|
|
unlock(&fsp_con_lock);
|
|
}
|
|
|
|
static bool send_all_hvsi_close(void)
|
|
{
|
|
unsigned int i;
|
|
bool has_hvsi = false;
|
|
static const uint8_t close_packet[] = { 0xfe, 6, 0, 1, 0, 3 };
|
|
|
|
for (i = 0; i < MAX_SERIAL; i++) {
|
|
struct fsp_serial *fs = &fsp_serials[i];
|
|
struct fsp_serbuf_hdr *sb = fs->out_buf;
|
|
unsigned int space, timeout = 10;
|
|
|
|
if (fs->log_port)
|
|
continue;
|
|
if (fs->rsrc_id == 0xffff)
|
|
continue;
|
|
has_hvsi = true;
|
|
|
|
/* Do we have room ? Wait a bit if not */
|
|
while(timeout--) {
|
|
space = (sb->next_out + SER_BUF_DATA_SIZE -
|
|
sb->next_in - 1) % SER_BUF_DATA_SIZE;
|
|
if (space >= 6)
|
|
break;
|
|
time_wait_ms(500);
|
|
}
|
|
lock(&fsp_con_lock);
|
|
fsp_write_vserial(fs, close_packet, 6);
|
|
unlock(&fsp_con_lock);
|
|
}
|
|
|
|
return has_hvsi;
|
|
}
|
|
|
|
static void reopen_all_hvsi(void)
|
|
{
|
|
unsigned int i;
|
|
|
|
for (i = 0; i < MAX_SERIAL; i++) {
|
|
struct fsp_serial *fs = &fsp_serials[i];
|
|
|
|
if (!fs->available)
|
|
continue;
|
|
|
|
if (fs->rsrc_id == 0xffff)
|
|
continue;
|
|
prlog(PR_NOTICE, "FSP: Deassociating HVSI console %d\n", i);
|
|
fsp_sync_msg(fsp_mkmsg(FSP_CMD_UNASSOC_SERIAL, 1,
|
|
(i << 16) | 1), true);
|
|
}
|
|
for (i = 0; i < MAX_SERIAL; i++) {
|
|
struct fsp_serial *fs = &fsp_serials[i];
|
|
|
|
if (!fs->available)
|
|
continue;
|
|
|
|
if (fs->rsrc_id == 0xffff)
|
|
continue;
|
|
prlog(PR_NOTICE, "FSP: Reassociating HVSI console %d\n", i);
|
|
fsp_sync_msg(fsp_mkmsg(FSP_CMD_ASSOC_SERIAL, 2,
|
|
(fs->rsrc_id << 16) | 1, i), true);
|
|
}
|
|
}
|
|
|
|
void fsp_console_reset(void)
|
|
{
|
|
if (!fsp_present())
|
|
return;
|
|
|
|
prlog(PR_NOTICE, "FSP: Console reset !\n");
|
|
|
|
/* This is called on a fast-reset. To work around issues with HVSI
|
|
* initial negotiation, before we reboot the kernel, we flush all
|
|
* input and send an HVSI close packet.
|
|
*/
|
|
flush_all_input();
|
|
|
|
/* Returns false if there is no HVSI console */
|
|
if (!send_all_hvsi_close())
|
|
return;
|
|
|
|
time_wait_ms(500);
|
|
|
|
reopen_all_hvsi();
|
|
|
|
}
|
|
|
|
void fsp_console_add_nodes(void)
|
|
{
|
|
struct dt_node *opal_event;
|
|
unsigned int i;
|
|
|
|
opal_event = dt_find_by_name(opal_node, "event");
|
|
|
|
for (i = 0; i < MAX_SERIAL; i++) {
|
|
struct fsp_serial *fs = &fsp_serials[i];
|
|
struct dt_node *fs_node;
|
|
const char *type;
|
|
|
|
if (fs->log_port || !fs->available)
|
|
continue;
|
|
|
|
if (fs->rsrc_id == 0xffff)
|
|
type = "raw";
|
|
else
|
|
type = "hvsi";
|
|
|
|
fs_node = add_opal_console_node(i, type, SER_BUF_DATA_SIZE);
|
|
|
|
fs->irq = opal_dynamic_event_alloc();
|
|
dt_add_property_cells(fs_node, "interrupts", ilog2(fs->irq));
|
|
|
|
if (opal_event)
|
|
dt_add_property_cells(fs_node, "interrupt-parent",
|
|
opal_event->phandle);
|
|
}
|
|
}
|
|
|
|
void fsp_console_select_stdout(void)
|
|
{
|
|
bool use_serial = false;
|
|
|
|
if (!fsp_present())
|
|
return;
|
|
|
|
/* On P8, we have a sysparam ! yay ! */
|
|
if (proc_gen >= proc_gen_p8) {
|
|
int rc;
|
|
u8 param;
|
|
|
|
rc = fsp_get_sys_param(SYS_PARAM_CONSOLE_SELECT,
|
|
¶m, 1, NULL, NULL);
|
|
if (rc != 1)
|
|
prerror("FSPCON: Failed to get console"
|
|
" sysparam rc %d\n", rc);
|
|
else {
|
|
switch(param) {
|
|
case 0:
|
|
use_serial = false;
|
|
break;
|
|
case 1:
|
|
use_serial = true;
|
|
break;
|
|
default:
|
|
prerror("FSPCON: Unknown console"
|
|
" sysparam %d\n", param);
|
|
}
|
|
}
|
|
} else {
|
|
struct dt_node *iplp;
|
|
u32 ipl_mode = 0;
|
|
|
|
/*
|
|
* We hijack the "os-ipl-mode" setting in iplparams to select
|
|
* out output console. This is the "i5/OS partition mode boot"
|
|
* setting in ASMI converted to an integer: 0=A, 1=B.
|
|
*/
|
|
iplp = dt_find_by_path(dt_root, "ipl-params/ipl-params");
|
|
if (iplp) {
|
|
ipl_mode = dt_prop_get_u32_def(iplp, "os-ipl-mode", 0);
|
|
use_serial = ipl_mode > 0;
|
|
|
|
/*
|
|
* Now, if ipl_mode is > 0, we use serial port A else
|
|
* we use IPMI/SOL/DVS
|
|
*/
|
|
}
|
|
}
|
|
dt_check_del_prop(dt_chosen, "linux,stdout-path");
|
|
|
|
if (fsp_serials[1].open && use_serial) {
|
|
dt_add_property_string(dt_chosen, "linux,stdout-path",
|
|
"/ibm,opal/consoles/serial@1");
|
|
prlog(PR_NOTICE, "FSPCON: default console set to serial A\n");
|
|
} else {
|
|
dt_add_property_string(dt_chosen, "linux,stdout-path",
|
|
"/ibm,opal/consoles/serial@0");
|
|
prlog(PR_NOTICE, "FSPCON: default console set to SOL/DVS\n");
|
|
}
|
|
}
|
|
|