blob: e15afba58cfad9b177ac2de10c3d3625a74c4c5f [file] [log] [blame]
/*****************************************************************************
*
* Copyright (c) 2014 - 2017 Samsung Electronics Co., Ltd. All rights reserved
*
****************************************************************************/
#include <scsc/scsc_logring.h>
#include <scsc/scsc_mx.h>
#include "scsc_mx_impl.h"
#include "mxmgmt_transport.h"
#include "mxlog_transport.h"
#include "fwhdr.h"
#include "mxlog.h"
/*
* Receive handler for messages from the FW along the maxwell management transport
*/
static inline void mxlog_phase4_message_handler(const void *message,
size_t length, u32 level,
void *data)
{
unsigned char *buf = (unsigned char *)message;
SCSC_TAG_LVL(MX_FW, level, SCSC_PREFIX"%d: %s\n", (int)length, buf);
}
/**
* This function is used to parse a NULL terminated format string
* and report on the provided output bitmaps smap/lmap which args
* are 'long' and which are signed..
*
* We will care only about length and specifier fields
*
* %[flags][width][.precision][length]specifier
*
* and since flags width and .precision are represented
* by NON chars, we will grossly compare simply against an 'A',
* because we are NOT trying to make a full sanity check here BUT only
* to search for long and signed values to provide the proper cast.
*
* Supporting:
* - ESCAPES %%ld
*
* - %x %X %d %ld %lld %i %li %lli %u %lu %llu %hd %hhd %hu %hhu
*
* NOT supporting:
* - %s -> MARKED AS UNSUPPORTED
*/
static inline void build_len_sign_maps(char *fmt, u32 *smap, u32 *lmap,
u32 *strmap)
{
u32 p = 0;
char *s = fmt;
bool escaping = false;
if (!s)
return;
for (; *s != '\0'; ++s) {
/* Skip any escaped fmtstring like %%d and move on */
if (escaping) {
if (*s == ' ')
escaping = false;
continue;
}
if (*s != '%')
continue;
/* Start escape seq ... */
if (*(s + 1) == '%') {
escaping = true;
continue;
}
/* skip [flags][width][.precision] if any */
for (; *++s < 'A';)
;
if (*s == 'l') {
*lmap |= (1 << p);
/* %lld ? skip */
if (*++s == 'l')
s++;
} else if (*s == 'h') {
/* just skip h modifiers */
/* hhd ? */
if (*++s == 'h')
s++;
}
if (*s == 'd' || *s == 'i')
*smap |= (1 << p);
else if (*s == 's')
*strmap |= (1 << p);
p++;
}
}
/**
* The binary protocol described at:
*
* http://wiki/Maxwell_common_firmware/Mxlog#Phase_5_:_string_decoded_on_the_host
*
* states that we'd receive the following record content on each mxlog
* message from FW, where:
*
* - each element is a 32bit word
* - 1st element is a record header
* - len = number of elements following the first element
*
* | 1st | 2nd | 3rd | 4th | 5th | 6th
* -----------------------------------------------------------
* | sync|lvl|len || tstamp || offset || arg1 || arg2 || arg3.
* -----------------------------------------------------------
* | e l o g m s g |
*
* BUT NOTE THAT: here we DO NOT receive 1st header element BUT
* instead we got:
* @message: pointer to 2nd element
* @length: in bytes of the message (so starting from 2nd element) and
* including tstamp and offset elements: we must calculate
* num_args accordingly.
* @level: the debug level already remapped from FW to Kernel namespace
*/
static inline void mxlog_phase5_message_handler(const void *message,
size_t length, u32 level,
void *data)
{
struct mxlog *mxlog = (struct mxlog *)data;
struct mxlog_event_log_msg *elogmsg =
(struct mxlog_event_log_msg *)message;
if (length < MINIMUM_MXLOG_MSG_LEN_BYTES)
return;
if (mxlog && elogmsg) {
int num_args = 0;
char spare[MAX_SPARE_FMT + TSTAMP_LEN] = {};
char *fmt = NULL;
size_t fmt_sz = 0;
u32 smap = 0, lmap = 0, strmap = 0;
u32 *args = NULL;
/* Check OFFSET sanity... beware of FW guys :D ! */
if (elogmsg->offset >= MXLS_SZ(mxlog)) {
SCSC_TAG_ERR(MX_FW,
"Received fmtstr OFFSET(%d) is OUT OF range(%zd)...skip..\n",
elogmsg->offset, MXLS_SZ(mxlog));
return;
}
args = (u32 *)(elogmsg + 1);
num_args =
(length - MINIMUM_MXLOG_MSG_LEN_BYTES) /
MXLOG_ELEMENT_SIZE;
fmt = (char *)(MXLS_DATA(mxlog) + elogmsg->offset);
/* Avoid being fooled by a NON NULL-terminated strings too ! */
fmt_sz = strnlen(fmt, MXLS_SZ(mxlog) - elogmsg->offset);
if (fmt_sz >= MAX_SPARE_FMT - 1) {
SCSC_TAG_ERR(MX_FW,
"UNSUPPORTED message length %zd ... truncated.\n",
fmt_sz);
fmt_sz = MAX_SPARE_FMT - 2;
}
/* Pre-Process fmt string to be able to do proper casting */
if (num_args)
build_len_sign_maps(fmt, &smap, &lmap, &strmap);
/* Add FW provided tstamp on front and proper \n at
* the end when needed
*/
snprintf(spare, MAX_SPARE_FMT + TSTAMP_LEN - 2, SCSC_PREFIX"%08X %s%c",
elogmsg->timestamp, fmt,
(fmt[fmt_sz] != '\n') ? '\n' : '\0');
fmt = spare;
switch (num_args) {
case 0:
SCSC_TAG_LVL(MX_FW, level, fmt);
break;
case 1:
SCSC_TAG_LVL(MX_FW, level, fmt,
MXLOG_CAST(args[0], 0, smap, lmap, strmap,
MXLS_DATA(mxlog), MXLS_SZ(mxlog)));
break;
case 2:
SCSC_TAG_LVL(MX_FW, level, fmt,
MXLOG_CAST(args[0], 0, smap, lmap, strmap,
MXLS_DATA(mxlog), MXLS_SZ(mxlog)),
MXLOG_CAST(args[1], 1, smap, lmap, strmap,
MXLS_DATA(mxlog), MXLS_SZ(mxlog)));
break;
case 3:
SCSC_TAG_LVL(MX_FW, level, fmt,
MXLOG_CAST(args[0], 0, smap, lmap, strmap,
MXLS_DATA(mxlog), MXLS_SZ(mxlog)),
MXLOG_CAST(args[1], 1, smap, lmap, strmap,
MXLS_DATA(mxlog), MXLS_SZ(mxlog)),
MXLOG_CAST(args[2], 2, smap, lmap, strmap,
MXLS_DATA(mxlog), MXLS_SZ(mxlog)));
break;
case 4:
SCSC_TAG_LVL(MX_FW, level, fmt,
MXLOG_CAST(args[0], 0, smap, lmap, strmap,
MXLS_DATA(mxlog), MXLS_SZ(mxlog)),
MXLOG_CAST(args[1], 1, smap, lmap, strmap,
MXLS_DATA(mxlog), MXLS_SZ(mxlog)),
MXLOG_CAST(args[2], 2, smap, lmap, strmap,
MXLS_DATA(mxlog), MXLS_SZ(mxlog)),
MXLOG_CAST(args[3], 3, smap, lmap, strmap,
MXLS_DATA(mxlog), MXLS_SZ(mxlog)));
break;
case 5:
SCSC_TAG_LVL(MX_FW, level, fmt,
MXLOG_CAST(args[0], 0, smap, lmap, strmap,
MXLS_DATA(mxlog), MXLS_SZ(mxlog)),
MXLOG_CAST(args[1], 1, smap, lmap, strmap,
MXLS_DATA(mxlog), MXLS_SZ(mxlog)),
MXLOG_CAST(args[2], 2, smap, lmap, strmap,
MXLS_DATA(mxlog), MXLS_SZ(mxlog)),
MXLOG_CAST(args[3], 3, smap, lmap, strmap,
MXLS_DATA(mxlog), MXLS_SZ(mxlog)),
MXLOG_CAST(args[4], 4, smap, lmap, strmap,
MXLS_DATA(mxlog), MXLS_SZ(mxlog)));
break;
case 6:
SCSC_TAG_LVL(MX_FW, level, fmt,
MXLOG_CAST(args[0], 0, smap, lmap, strmap,
MXLS_DATA(mxlog), MXLS_SZ(mxlog)),
MXLOG_CAST(args[1], 1, smap, lmap, strmap,
MXLS_DATA(mxlog), MXLS_SZ(mxlog)),
MXLOG_CAST(args[2], 2, smap, lmap, strmap,
MXLS_DATA(mxlog), MXLS_SZ(mxlog)),
MXLOG_CAST(args[3], 3, smap, lmap, strmap,
MXLS_DATA(mxlog), MXLS_SZ(mxlog)),
MXLOG_CAST(args[4], 4, smap, lmap, strmap,
MXLS_DATA(mxlog), MXLS_SZ(mxlog)),
MXLOG_CAST(args[5], 5, smap, lmap, strmap,
MXLS_DATA(mxlog), MXLS_SZ(mxlog)));
break;
case 7:
SCSC_TAG_LVL(MX_FW, level, fmt,
MXLOG_CAST(args[0], 0, smap, lmap, strmap,
MXLS_DATA(mxlog), MXLS_SZ(mxlog)),
MXLOG_CAST(args[1], 1, smap, lmap, strmap,
MXLS_DATA(mxlog), MXLS_SZ(mxlog)),
MXLOG_CAST(args[2], 2, smap, lmap, strmap,
MXLS_DATA(mxlog), MXLS_SZ(mxlog)),
MXLOG_CAST(args[3], 3, smap, lmap, strmap,
MXLS_DATA(mxlog), MXLS_SZ(mxlog)),
MXLOG_CAST(args[4], 4, smap, lmap, strmap,
MXLS_DATA(mxlog), MXLS_SZ(mxlog)),
MXLOG_CAST(args[5], 5, smap, lmap, strmap,
MXLS_DATA(mxlog), MXLS_SZ(mxlog)),
MXLOG_CAST(args[6], 6, smap, lmap, strmap,
MXLS_DATA(mxlog), MXLS_SZ(mxlog)));
break;
case 8:
default:
if (num_args > MAX_MX_LOG_ARGS)
SCSC_TAG_ERR(MX_FW,
"MXLOG: Too many args:%d ... print only first %d\n",
num_args, MAX_MX_LOG_ARGS);
SCSC_TAG_LVL(MX_FW, level, fmt,
MXLOG_CAST(args[0], 0, smap, lmap, strmap,
MXLS_DATA(mxlog), MXLS_SZ(mxlog)),
MXLOG_CAST(args[1], 1, smap, lmap, strmap,
MXLS_DATA(mxlog), MXLS_SZ(mxlog)),
MXLOG_CAST(args[2], 2, smap, lmap, strmap,
MXLS_DATA(mxlog), MXLS_SZ(mxlog)),
MXLOG_CAST(args[3], 3, smap, lmap, strmap,
MXLS_DATA(mxlog), MXLS_SZ(mxlog)),
MXLOG_CAST(args[4], 4, smap, lmap, strmap,
MXLS_DATA(mxlog), MXLS_SZ(mxlog)),
MXLOG_CAST(args[5], 5, smap, lmap, strmap,
MXLS_DATA(mxlog), MXLS_SZ(mxlog)),
MXLOG_CAST(args[6], 6, smap, lmap, strmap,
MXLS_DATA(mxlog), MXLS_SZ(mxlog)),
MXLOG_CAST(args[7], 7, smap, lmap, strmap,
MXLS_DATA(mxlog), MXLS_SZ(mxlog)));
break;
}
}
}
/* A generic message handler to multiplex between phases */
static void mxlog_message_handler(u8 phase, const void *message,
size_t length, u32 level, void *data)
{
struct mxlog *mxlog = (struct mxlog *)data;
if (!mxlog) {
SCSC_TAG_ERR(MX_FW, "Missing MXLOG reference.\n");
return;
}
switch (phase) {
case MX_LOG_PHASE_4:
mxlog_phase4_message_handler(message, length, level, data);
break;
case MX_LOG_PHASE_5:
if (mxlog->logstrings)
mxlog_phase5_message_handler(message, length,
level, data);
else
SCSC_TAG_ERR(MX_FW,
"Missing LogStrings...dropping incoming PHASE5 message !\n");
break;
default:
SCSC_TAG_ERR(MX_FW,
"MXLOG Unsupported phase %d ... dropping message !\n",
phase);
break;
}
}
static int mxlog_header_parser(u32 header, u8 *phase,
u8 *level, u32 *num_bytes)
{
u32 fw2kern_map[] = {
0, /* 0 MX_ERROR --> 0 KERN_EMERG .. it's panic.*/
4, /* 1 MX_WARN --> 4 KERN_WARNING */
5, /* 2 MX_MAJOR --> 5 KERN_NOTICE */
6, /* 3 MX_MINOR --> 6 KERN_INFO */
7, /* 4 MX_DETAIL --> 7 KERN_DEBUG */
};
u16 sync = ((header & 0xFFFF0000) >> 16);
switch (sync) {
case SYNC_VALUE_PHASE_4:
*phase = MX_LOG_PHASE_4;
/* len() field represent number of chars bytes */
*num_bytes = header & 0x000000FF;
break;
case SYNC_VALUE_PHASE_5:
*phase = MX_LOG_PHASE_5;
/* len() field represent number of 4 bytes words */
*num_bytes = (header & 0x000000FF) * 4;
break;
default:
return -1;
}
/* Remap FW debug levels to KERN debug levels domain */
*level = (header & 0x0000FF00) >> 8;
if (*level < ARRAY_SIZE(fw2kern_map)) {
*level = fw2kern_map[*level];
} else {
SCSC_TAG_ERR(MX_FW,
"UNKNOWN MX debug level %d ... marking as MX_DETAIL.\n",
*level);
*level = fw2kern_map[ARRAY_SIZE(fw2kern_map) - 1];
}
return 0;
}
void mxlog_init(struct mxlog *mxlog, struct scsc_mx *mx, char *fw_build_id)
{
int ret = 0;
mxlog->mx = mx;
mxlog->index = 0;
mxlog->logstrings = NULL;
/* File is in f/w profile directory */
ret = mx140_file_request_debug_conf(mx,
(const struct firmware **)&mxlog->logstrings,
MX_LOG_LOGSTRINGS_PATH);
if (!ret && mxlog->logstrings && mxlog->logstrings->data) {
SCSC_TAG_INFO(MX_FW, "Loaded %zd bytes of log-strings from %s\n",
mxlog->logstrings->size, MX_LOG_LOGSTRINGS_PATH);
if (fw_build_id && mxlog->logstrings->data[0] != 0x00 &&
mxlog->logstrings->size >= FW_BUILD_ID_SZ) {
SCSC_TAG_INFO(MX_FW, "Log-strings is versioned...checking against fw_build_id.\n");
if (strncmp(fw_build_id, mxlog->logstrings->data, FW_BUILD_ID_SZ)) {
char found[FW_BUILD_ID_SZ] = {};
/**
* NULL-terminate it just in case we fetched
* never-ending garbage.
*/
strncpy(found, mxlog->logstrings->data,
FW_BUILD_ID_SZ - 1);
SCSC_TAG_WARNING(MX_FW,
"--> Log-strings VERSION MISMATCH !!!\n");
SCSC_TAG_WARNING(MX_FW,
"--> Expected: |%s|\n", fw_build_id);
SCSC_TAG_WARNING(MX_FW,
"--> FOUND: |%s|\n", found);
SCSC_TAG_WARNING(MX_FW,
"As a consequence the following mxlog debug messages could be corrupted.\n");
SCSC_TAG_WARNING(MX_FW,
"The whole firmware package should be pushed to device when updating (not only the mx140.bin).\n");
}
} else {
SCSC_TAG_INFO(MX_FW, "Log-strings is not versioned.\n");
}
} else {
SCSC_TAG_ERR(MX_FW, "Failed to read %s needed by MXlog Phase 5\n",
MX_LOG_LOGSTRINGS_PATH);
}
/* Registering a generic channel handler */
mxlog_transport_register_channel_handler(scsc_mx_get_mxlog_transport(mx),
&mxlog_header_parser,
&mxlog_message_handler, mxlog);
}
void mxlog_release(struct mxlog *mxlog)
{
mxlog_transport_register_channel_handler(scsc_mx_get_mxlog_transport(mxlog->mx),
NULL, NULL, NULL);
if (mxlog->logstrings)
mx140_release_file(mxlog->mx, mxlog->logstrings);
mxlog->logstrings = NULL;
}