mirror of
https://github.com/AetherDroid/android_kernel_samsung_on5xelte.git
synced 2025-09-08 01:08:03 -04:00
304 lines
8.8 KiB
C
304 lines
8.8 KiB
C
/*****************************************************************************
|
|
*
|
|
* Copyright (c) 2014 - 2016 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 "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, "%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 signedi...as in %ld.
|
|
*/
|
|
static inline void build_len_sign_maps(char *fmt, u32 *smap, u32 *lmap)
|
|
{
|
|
u32 p = 0;
|
|
char *s = fmt;
|
|
|
|
if (!s)
|
|
return;
|
|
for (; *s != '\0'; ++s) {
|
|
if (*s != '%')
|
|
continue;
|
|
if (*++s == 'l') {
|
|
*lmap |= (1 << p);
|
|
++s;
|
|
}
|
|
if (*s == 'd')
|
|
*smap |= (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;
|
|
u32 *args = NULL;
|
|
|
|
/* Check OFFSET sanity... beware of FW guys :D ! */
|
|
if (elogmsg->offset >= mxlog->logstrings->size) {
|
|
SCSC_TAG_ERR(MX_FW,
|
|
"Received message OFFSET(%d) is OUT OF range(%zd)...skip..\n",
|
|
elogmsg->offset, mxlog->logstrings->size);
|
|
return;
|
|
}
|
|
args = (u32 *)(elogmsg + 1);
|
|
num_args =
|
|
(length - MINIMUM_MXLOG_MSG_LEN_BYTES) /
|
|
MXLOG_ELEMENT_SIZE;
|
|
fmt = (char *)(mxlog->logstrings->data + elogmsg->offset);
|
|
/* Avoid being fooled by a NON NULL-terminated strings too ! */
|
|
fmt_sz = strnlen(fmt,
|
|
mxlog->logstrings->size - 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);
|
|
/* Add FW provided tstamp on front and proper \n at
|
|
* the end when needed
|
|
*/
|
|
snprintf(spare, MAX_SPARE_FMT + TSTAMP_LEN - 2, "%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));
|
|
break;
|
|
case 2:
|
|
SCSC_TAG_LVL(MX_FW, level, fmt,
|
|
MXLOG_CAST(args[0], 0, smap, lmap),
|
|
MXLOG_CAST(args[1], 1, smap, lmap));
|
|
break;
|
|
case 3:
|
|
SCSC_TAG_LVL(MX_FW, level, fmt,
|
|
MXLOG_CAST(args[0], 0, smap, lmap),
|
|
MXLOG_CAST(args[1], 1, smap, lmap),
|
|
MXLOG_CAST(args[2], 2, smap, lmap));
|
|
break;
|
|
case 4:
|
|
SCSC_TAG_LVL(MX_FW, level, fmt,
|
|
MXLOG_CAST(args[0], 0, smap, lmap),
|
|
MXLOG_CAST(args[1], 1, smap, lmap),
|
|
MXLOG_CAST(args[2], 2, smap, lmap),
|
|
MXLOG_CAST(args[3], 3, smap, lmap));
|
|
break;
|
|
case 5:
|
|
SCSC_TAG_LVL(MX_FW, level, fmt,
|
|
MXLOG_CAST(args[0], 0, smap, lmap),
|
|
MXLOG_CAST(args[1], 1, smap, lmap),
|
|
MXLOG_CAST(args[2], 2, smap, lmap),
|
|
MXLOG_CAST(args[3], 3, smap, lmap),
|
|
MXLOG_CAST(args[4], 4, smap, lmap));
|
|
break;
|
|
case 6:
|
|
SCSC_TAG_LVL(MX_FW, level, fmt,
|
|
MXLOG_CAST(args[0], 0, smap, lmap),
|
|
MXLOG_CAST(args[1], 1, smap, lmap),
|
|
MXLOG_CAST(args[2], 2, smap, lmap),
|
|
MXLOG_CAST(args[3], 3, smap, lmap),
|
|
MXLOG_CAST(args[4], 4, smap, lmap),
|
|
MXLOG_CAST(args[5], 5, smap, lmap));
|
|
break;
|
|
case 7:
|
|
SCSC_TAG_LVL(MX_FW, level, fmt,
|
|
MXLOG_CAST(args[0], 0, smap, lmap),
|
|
MXLOG_CAST(args[1], 1, smap, lmap),
|
|
MXLOG_CAST(args[2], 2, smap, lmap),
|
|
MXLOG_CAST(args[3], 3, smap, lmap),
|
|
MXLOG_CAST(args[4], 4, smap, lmap),
|
|
MXLOG_CAST(args[5], 5, smap, lmap),
|
|
MXLOG_CAST(args[6], 6, smap, lmap));
|
|
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),
|
|
MXLOG_CAST(args[1], 1, smap, lmap),
|
|
MXLOG_CAST(args[2], 2, smap, lmap),
|
|
MXLOG_CAST(args[3], 3, smap, lmap),
|
|
MXLOG_CAST(args[4], 4, smap, lmap),
|
|
MXLOG_CAST(args[5], 5, smap, lmap),
|
|
MXLOG_CAST(args[6], 6, smap, lmap),
|
|
MXLOG_CAST(args[7], 7, smap, lmap));
|
|
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)
|
|
{
|
|
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)
|
|
SCSC_TAG_INFO(MX_FW, "Loaded %zd bytes of log-strings from %s\n",
|
|
mxlog->logstrings->size, MX_LOG_LOGSTRINGS_PATH);
|
|
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;
|
|
}
|
|
|