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