blob: a8ac00b7a5f87ff5bf268d0f279684120c151549 [file] [log] [blame]
// SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later
/*
* Small LCD screen on the front of FSP machines
*
* Copyright 2013-2019 IBM Corp.
*/
#include <skiboot.h>
#include <fsp.h>
#include <lock.h>
#include <opal.h>
#include <device.h>
#include <processor.h>
#include <opal-msg.h>
#include <errorlog.h>
DEFINE_LOG_ENTRY(OPAL_RC_PANEL_WRITE, OPAL_PLATFORM_ERR_EVT, OPAL_OP_PANEL,
OPAL_MISC_SUBSYSTEM, OPAL_PREDICTIVE_ERR_GENERAL, OPAL_NA);
/* For OPAL OP_PANEL API we can only have one in flight due to TCEs */
static struct fsp_msg *op_req;
static uint64_t op_async_token;
static struct lock op_lock = LOCK_UNLOCKED;
static void fsp_op_display_fatal(uint32_t w0, uint32_t w1)
{
static struct fsp_msg op_msg_resp;
static struct fsp_msg op_msg = {
.resp = &op_msg_resp,
};
fsp_fillmsg(&op_msg, FSP_CMD_DISP_SRC_DIRECT, 3, 1, w0, w1);
/*
* A special way to send a message: it doesn't run pollers.
* This means we can call it while in a poller, which we may
* well be in when we're terminating (and thus displaying a *fatal*
* message on the op-panel).
*/
fsp_fatal_msg(&op_msg);
}
void fsp_op_display(enum op_severity sev, enum op_module mod, uint16_t code)
{
struct fsp_msg *op_msg;
uint32_t w0;
uint32_t w1;
if (!fsp_present())
return;
w0 = sev << 16 | mod;
w1 = tohex((code >> 12) & 0xf) << 24;
w1 |= tohex((code >> 8) & 0xf) << 16;
w1 |= tohex((code >> 4) & 0xf) << 8;
w1 |= tohex((code ) & 0xf);
if (sev == OP_FATAL) {
fsp_op_display_fatal(w0, w1);
} else {
op_msg = fsp_allocmsg(true);
if (!op_msg) {
prerror("Failed to allocate FSP message for PANEL\n");
return;
}
fsp_fillmsg(op_msg, FSP_CMD_DISP_SRC_DIRECT, 3, 1, w0, w1);
if(fsp_queue_msg(op_msg, fsp_freemsg))
prerror("Failed to queue FSP message for OP PANEL\n");
}
}
void op_panel_disable_src_echo(void)
{
struct fsp_msg op_msg_resp;
struct fsp_msg op_msg = {
.resp = &op_msg_resp,
};
if (!fsp_present())
return;
fsp_fillmsg(&op_msg, FSP_CMD_DIS_SRC_ECHO, 0);
fsp_sync_msg(&op_msg, false);
}
void op_panel_clear_src(void)
{
struct fsp_msg op_msg_resp;
struct fsp_msg op_msg = {
.resp = &op_msg_resp,
};
if (!fsp_present())
return;
fsp_fillmsg(&op_msg, FSP_CMD_CLEAR_SRC, 0);
fsp_sync_msg(&op_msg, false);
}
/* opal_write_oppanel - Write to the physical op panel.
*
* Pass in an array of oppanel_line_t structs defining the ASCII characters
* to display on each line of the oppanel. If there are two lines on the
* physical panel, and you only want to write to the first line, you only
* need to pass in one line. If you only want to write to the second line,
* you need to pass in both lines, and set the line_len of the first line
* to zero.
*
* This command is asynchronous. If OPAL_SUCCESS is returned, then the
* operation was initiated successfully. Subsequent calls will return
* OPAL_BUSY until the current operation is complete.
*/
struct op_src {
uint8_t version;
#define OP_SRC_VERSION 2
uint8_t flags;
uint8_t reserved;
uint8_t hex_word_cnt;
__be16 reserved2;
__be16 total_size;
__be32 word2; /* SRC format in low byte */
__be32 word3;
__be32 word4;
__be32 word5;
__be32 word6;
__be32 word7;
__be32 word8;
__be32 word9;
uint8_t ascii[OP_PANEL_NUM_LINES * OP_PANEL_LINE_LEN]; /* Word 11 */
} __packed __align(4);
/* Page align for the sake of TCE mapping */
static struct op_src op_src __align(0x1000);
static void __op_panel_write_complete(struct fsp_msg *msg)
{
fsp_tce_unmap(PSI_DMA_OP_PANEL_MISC, 0x1000);
lock(&op_lock);
op_req = NULL;
unlock(&op_lock);
fsp_freemsg(msg);
}
static void op_panel_write_complete(struct fsp_msg *msg)
{
uint8_t rc = (msg->resp->word1 >> 8) & 0xff;
if (rc)
prerror("OPPANEL: Error 0x%02x in display command\n", rc);
__op_panel_write_complete(msg);
opal_queue_msg(OPAL_MSG_ASYNC_COMP, NULL, NULL,
cpu_to_be64(1),
cpu_to_be64(op_async_token));
}
static int64_t __opal_write_oppanel(oppanel_line_t *lines, uint64_t num_lines,
uint64_t async_token)
{
int64_t rc = OPAL_ASYNC_COMPLETION;
int len;
int i;
if (num_lines < 1 || num_lines > OP_PANEL_NUM_LINES)
return OPAL_PARAMETER;
/* Only one in flight */
lock(&op_lock);
if (op_req) {
rc = OPAL_BUSY_EVENT;
unlock(&op_lock);
goto bail;
}
op_req = fsp_allocmsg(true);
if (!op_req) {
rc = OPAL_NO_MEM;
unlock(&op_lock);
goto bail;
}
unlock(&op_lock);
op_async_token = async_token;
memset(&op_src, 0, sizeof(op_src));
op_src.version = OP_SRC_VERSION;
op_src.flags = 0;
op_src.reserved = 0;
op_src.hex_word_cnt = 1; /* header word only */
op_src.reserved2 = 0;
op_src.total_size = cpu_to_be16(sizeof(op_src));
op_src.word2 = 0; /* should be unneeded */
for (i = 0; i < num_lines; i++) {
uint8_t *current_line = op_src.ascii + (i * OP_PANEL_LINE_LEN);
len = be64_to_cpu(lines[i].line_len);
if (len < OP_PANEL_LINE_LEN)
memset(current_line + len, ' ', OP_PANEL_LINE_LEN-len);
else
len = OP_PANEL_LINE_LEN;
memcpy(current_line, (void *) be64_to_cpu(lines[i].line), len);
}
for (i = 0; i < sizeof(op_src.ascii); i++) {
/*
* So, there's this interesting thing if you send
* HTML/Javascript through the Operator Panel.
* You get to inject it into the ASM web ui!
* So we filter out anything suspect here,
* at least for the time being.
*
* Allowed characters:
* . / 0-9 : a-z A-Z SPACE
*/
if (! ((op_src.ascii[i] >= '.' && op_src.ascii[i] <= ':') ||
(op_src.ascii[i] >= 'a' && op_src.ascii[i] <= 'z') ||
(op_src.ascii[i] >= 'A' && op_src.ascii[i] <= 'Z') ||
op_src.ascii[i] == ' ')) {
op_src.ascii[i] = '.';
}
}
fsp_tce_map(PSI_DMA_OP_PANEL_MISC, &op_src, 0x1000);
fsp_fillmsg(op_req, FSP_CMD_DISP_SRC_INDIR, 3, 0,
PSI_DMA_OP_PANEL_MISC, sizeof(struct op_src));
rc = fsp_queue_msg(op_req, op_panel_write_complete);
if (rc) {
__op_panel_write_complete(op_req);
rc = OPAL_INTERNAL_ERROR;
}
bail:
log_simple_error(&e_info(OPAL_RC_PANEL_WRITE),
"FSP: Error updating Op Panel: %lld\n", rc);
return rc;
}
static int64_t opal_write_oppanel_async(uint64_t async_token,
oppanel_line_t *lines,
uint64_t num_lines)
{
return __opal_write_oppanel(lines, num_lines, async_token);
}
void fsp_oppanel_init(void)
{
struct dt_node *oppanel;
if (!fsp_present())
return;
opal_register(OPAL_WRITE_OPPANEL_ASYNC, opal_write_oppanel_async, 3);
oppanel = dt_new(opal_node, "oppanel");
dt_add_property_cells(oppanel, "#length", OP_PANEL_LINE_LEN);
dt_add_property_cells(oppanel, "#lines", OP_PANEL_NUM_LINES);
dt_add_property_string(oppanel, "compatible", "ibm,opal-oppanel");
}