/***************************************************************************** * * Copyright (c) 2014 - 2016 Samsung Electronics Co., Ltd. All rights reserved * ****************************************************************************/ #include #include #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; }