910 lines
28 KiB
C
Executable file
910 lines
28 KiB
C
Executable file
/********************************************************************************
|
|
*
|
|
* Copyright (c) 2016 - 2018 Samsung Electronics Co., Ltd. All rights reserved.
|
|
*
|
|
********************************************************************************/
|
|
#include "scsc_logring_main.h"
|
|
#include "scsc_logring_ring.h"
|
|
#include "scsc_logring_debugfs.h"
|
|
#include <scsc/scsc_logring.h>
|
|
#if IS_ENABLED(CONFIG_SCSC_LOG_COLLECTION)
|
|
#include <scsc/scsc_log_collector.h>
|
|
#endif
|
|
#if IS_ENABLED(CONFIG_SCSC_MEMLOG)
|
|
#include "soc/samsung/memlogger.h"
|
|
#endif
|
|
|
|
/* Global module parameters */
|
|
static int cached_enable;
|
|
static int enable = DEFAULT_ENABLE_LOGRING;
|
|
static bool initialized;
|
|
#ifndef CONFIG_SCSC_STATIC_RING_SIZE
|
|
static int ringsize = DEFAULT_RING_BUFFER_SZ;
|
|
#else
|
|
static int ringsize = CONFIG_SCSC_STATIC_RING_SIZE;
|
|
#endif
|
|
static int prepend_header = DEFAULT_ENABLE_HEADER;
|
|
static int default_dbglevel = DEFAULT_DBGLEVEL;
|
|
static int scsc_droplevel_wlbt = DEFAULT_DROPLEVEL;
|
|
static int scsc_droplevel_all = DEFAULT_ALL_DISABLED;
|
|
static int scsc_droplevel_atomic = DEFAULT_DROPLEVEL;
|
|
static int scsc_redirect_to_printk_droplvl = DEFAULT_REDIRECT_DROPLVL;
|
|
static int scsc_reset_all_droplevels_to;
|
|
|
|
static struct scsc_ring_buffer *the_ringbuf;
|
|
|
|
#if IS_ENABLED(CONFIG_SCSC_LOG_COLLECTION)
|
|
static int logring_collect(struct scsc_log_collector_client *collect_client, size_t size);
|
|
|
|
static struct scsc_log_collector_client logring_collect_client = {
|
|
.name = "Logring",
|
|
.type = SCSC_LOG_CHUNK_LOGRING,
|
|
.collect_init = NULL,
|
|
.collect = logring_collect,
|
|
.collect_end = NULL,
|
|
.prv = NULL,
|
|
};
|
|
#endif
|
|
|
|
/* Module init and ring buffer allocation */
|
|
int __init samlog_init(void)
|
|
{
|
|
struct scsc_ring_buffer *rb = NULL;
|
|
|
|
pr_debug("wlbt: Samlog Init\n");
|
|
if (!enable) {
|
|
pr_info("wlbt: Samlog: module disabled...NOT starting.\n");
|
|
return 0;
|
|
}
|
|
if (the_ringbuf != NULL) {
|
|
pr_info("wlbt: Samlog: Ring:%s already initialized...skipping.\n",
|
|
the_ringbuf->name);
|
|
return 0;
|
|
}
|
|
/* Check for power of two compliance with std Kernel func */
|
|
if (!is_power_of_2(ringsize)) {
|
|
ringsize = DEFAULT_RING_BUFFER_SZ;
|
|
pr_info("wlbt: Samlog: scsc_logring.ringsize MUST be power-of-two. Using default: %d\n",
|
|
ringsize);
|
|
}
|
|
rb = alloc_ring_buffer(ringsize, BASE_SPARE_SZ, DEBUGFS_RING0_ROOT);
|
|
|
|
if (!rb)
|
|
goto tfail;
|
|
rb->private = samlog_debugfs_init(rb->name, rb);
|
|
if (!rb->private)
|
|
pr_info("wlbt: Samlog: Cannot Initialize DebugFS.\n");
|
|
#ifndef CONFIG_SCSC_STATIC_RING_SIZE
|
|
pr_info("wlbt: scsc_logring:: Allocated ring buffer of size %zd bytes.\n",
|
|
rb->bsz);
|
|
#else
|
|
pr_info("wlbt: scsc_logring: Allocated STATIC ring buffer of size %zd bytes.\n",
|
|
rb->bsz);
|
|
#endif
|
|
the_ringbuf = rb;
|
|
initialized = true;
|
|
pr_info("wlbt: Samlog Loaded.\n");
|
|
scsc_printk_tag(FORCE_PRK, NO_TAG, "wlbt: Samlog Started.\n");
|
|
scsc_printk_tag(NO_ECHO_PRK, NO_TAG,
|
|
"wlbt: Allocated ring buffer of size %zd bytes at %p - %p\n",
|
|
rb->bsz, virt_to_phys((const volatile void *)rb->buf),
|
|
virt_to_phys((const volatile void *)(rb->buf + rb->bsz)));
|
|
scsc_printk_tag(NO_ECHO_PRK, NO_TAG,
|
|
"wlbt: Using THROWAWAY DYNAMIC per-reader buffer.\n");
|
|
|
|
#if IS_ENABLED(CONFIG_SCSC_LOG_COLLECTION)
|
|
scsc_log_collector_register_client(&logring_collect_client);
|
|
#endif
|
|
return 0;
|
|
|
|
tfail:
|
|
pr_err("wlbt: Samlog Initialization Failed. LogRing disabled.\n");
|
|
return -ENODEV;
|
|
}
|
|
|
|
void __exit samlog_exit(void)
|
|
{
|
|
if (!the_ringbuf) {
|
|
pr_err("wlbt: Cannot UNLOAD ringbuf\n");
|
|
return;
|
|
}
|
|
if (the_ringbuf && the_ringbuf->private)
|
|
samlog_debugfs_exit(&the_ringbuf->private);
|
|
initialized = false;
|
|
free_ring_buffer(the_ringbuf);
|
|
the_ringbuf = NULL;
|
|
#if IS_ENABLED(CONFIG_SCSC_LOG_COLLECTION)
|
|
scsc_log_collector_unregister_client(&logring_collect_client);
|
|
#endif
|
|
pr_info("Samlog Unloaded\n");
|
|
}
|
|
|
|
module_init(samlog_init);
|
|
module_exit(samlog_exit);
|
|
|
|
module_param(enable, int, S_IRUGO | S_IWUSR);
|
|
SCSC_MODPARAM_DESC(enable, "Enable/Disable scsc_logring as a whole.",
|
|
"load-time", DEFAULT_ENABLE_LOGRING);
|
|
|
|
#ifndef CONFIG_SCSC_STATIC_RING_SIZE
|
|
module_param(ringsize, int, S_IRUGO);
|
|
SCSC_MODPARAM_DESC(ringsize,
|
|
"Ring buffer size. Available ONLY if ring is NOT statically allocated.",
|
|
"run-time", DEFAULT_RING_BUFFER_SZ);
|
|
#endif
|
|
|
|
module_param(prepend_header, int, S_IRUGO | S_IWUSR);
|
|
SCSC_MODPARAM_DESC(prepend_header, "Enable/disable header prepending. ",
|
|
"run-time", DEFAULT_ENABLE_HEADER);
|
|
|
|
module_param(default_dbglevel, int, S_IRUGO | S_IWUSR);
|
|
SCSC_MODPARAM_DESC(default_dbglevel,
|
|
"The default debug level assigned to messages when NOT explicitly specified.",
|
|
"run-time", DEFAULT_DBGLEVEL);
|
|
|
|
module_param(scsc_droplevel_wlbt, int, S_IRUGO | S_IWUSR);
|
|
SCSC_MODPARAM_DESC(scsc_droplevel_wlbt,
|
|
"Droplevels for the 'no_tag/wlbt' tag family.", "run-time",
|
|
DEFAULT_DROP_ALL);
|
|
|
|
module_param(scsc_droplevel_all, int, S_IRUGO | S_IWUSR);
|
|
SCSC_MODPARAM_DESC(scsc_droplevel_all,
|
|
"This droplevel overrides any other, if set to a value >= 0",
|
|
"run-time", DEFAULT_ALL_DISABLED);
|
|
|
|
module_param(scsc_redirect_to_printk_droplvl, int, S_IRUGO | S_IWUSR);
|
|
SCSC_MODPARAM_DESC(scsc_redirect_to_printk_droplvl,
|
|
"Echoing messages up to the specified loglevel also to kernel standard ring buffer.",
|
|
"run-time", DEFAULT_REDIRECT_DROPLVL);
|
|
|
|
module_param(scsc_droplevel_atomic, int, S_IRUGO | S_IWUSR);
|
|
SCSC_MODPARAM_DESC(scsc_droplevel_atomic,
|
|
"This droplevel is applied to logmsg emitted in atomic context.",
|
|
"run-time", DEFAULT_KEEP_ALL);
|
|
|
|
/**
|
|
* This macro code has been freely 'inspired' (read copied) from the
|
|
* slsi_ original/old debug.c implementaion: it takes care to register
|
|
* a new TAG with the debug subsystem including a module param to
|
|
* dynamically configure the droplevel for the specified tag at runtime.
|
|
*
|
|
* ALL debug is ON by default at FULL DEBUG LEVEL.
|
|
* There are THREE intended exceptions to this that instead stick to
|
|
* level 7: PLAT_MIF MBULK and MXLOG_TRANS tags.
|
|
*
|
|
* NOTE_CREATING_TAGS: when adding a tag here REMEMBER to add it also
|
|
* where required, taking care to maintain the same ordering.
|
|
* (Search 4 NOTE_CREATING_TAGS)
|
|
*/
|
|
|
|
/**
|
|
* This macros define module parameters used to configure per-subsystem
|
|
* filtering, and assign a default DROPLEVEL.
|
|
*
|
|
* NOTE THAT the default DROPLEVEL indicates the default value FROM which
|
|
* the system will start to discard messages, so as an example:
|
|
*
|
|
* - if set to SCSC_DBG1 (7) every message with a dbglvl >= 7 will be discarded
|
|
* - if set to SCSC_FULL_DEBUG (11) every message is logged
|
|
*
|
|
* Name, Default DROPLEVEL , FilterTag
|
|
*/
|
|
ADD_DEBUG_MODULE_PARAM(binary, SCSC_FULL_DEBUG, BINARY);
|
|
ADD_DEBUG_MODULE_PARAM(bin_wifi_ctrl_rx, SCSC_FULL_DEBUG, BIN_WIFI_CTRL_RX);
|
|
ADD_DEBUG_MODULE_PARAM(bin_wifi_data_rx, SCSC_DEBUG, BIN_WIFI_DATA_RX);
|
|
ADD_DEBUG_MODULE_PARAM(bin_wifi_ctrl_tx, SCSC_FULL_DEBUG, BIN_WIFI_CTRL_TX);
|
|
ADD_DEBUG_MODULE_PARAM(bin_wifi_data_tx, SCSC_FULL_DEBUG, BIN_WIFI_DATA_TX);
|
|
ADD_DEBUG_MODULE_PARAM(wifi_rx, SCSC_FULL_DEBUG, WIFI_RX);
|
|
ADD_DEBUG_MODULE_PARAM(wifi_tx, SCSC_FULL_DEBUG, WIFI_TX);
|
|
ADD_DEBUG_MODULE_PARAM(bt_common, SCSC_FULL_DEBUG, BT_COMMON);
|
|
ADD_DEBUG_MODULE_PARAM(bt_h4, SCSC_FULL_DEBUG, BT_H4);
|
|
ADD_DEBUG_MODULE_PARAM(bt_fw, SCSC_FULL_DEBUG, BT_FW);
|
|
ADD_DEBUG_MODULE_PARAM(bt_rx, SCSC_FULL_DEBUG, BT_RX);
|
|
ADD_DEBUG_MODULE_PARAM(bt_tx, SCSC_FULL_DEBUG, BT_TX);
|
|
ADD_DEBUG_MODULE_PARAM(cpktbuff, SCSC_DBG4, CPKTBUFF);
|
|
ADD_DEBUG_MODULE_PARAM(fw_load, SCSC_FULL_DEBUG, FW_LOAD);
|
|
ADD_DEBUG_MODULE_PARAM(fw_panic, SCSC_FULL_DEBUG, FW_PANIC);
|
|
ADD_DEBUG_MODULE_PARAM(gdb_trans, SCSC_DBG1, GDB_TRANS);
|
|
ADD_DEBUG_MODULE_PARAM(mif, SCSC_FULL_DEBUG, MIF);
|
|
ADD_DEBUG_MODULE_PARAM(clk20, SCSC_FULL_DEBUG, CLK20);
|
|
ADD_DEBUG_MODULE_PARAM(clk20_test, SCSC_FULL_DEBUG, CLK20_TEST);
|
|
ADD_DEBUG_MODULE_PARAM(fm, SCSC_FULL_DEBUG, FM);
|
|
ADD_DEBUG_MODULE_PARAM(fm_test, SCSC_FULL_DEBUG, FM_TEST);
|
|
ADD_DEBUG_MODULE_PARAM(mx_file, SCSC_FULL_DEBUG, MX_FILE);
|
|
ADD_DEBUG_MODULE_PARAM(mx_fw, SCSC_FULL_DEBUG, MX_FW);
|
|
ADD_DEBUG_MODULE_PARAM(mx_sampler, SCSC_FULL_DEBUG, MX_SAMPLER);
|
|
ADD_DEBUG_MODULE_PARAM(mxlog_trans, SCSC_DBG1, MXLOG_TRANS);
|
|
ADD_DEBUG_MODULE_PARAM(mxman, SCSC_FULL_DEBUG, MXMAN);
|
|
ADD_DEBUG_MODULE_PARAM(mxman_test, SCSC_FULL_DEBUG, MXMAN_TEST);
|
|
ADD_DEBUG_MODULE_PARAM(mxmgt_trans, SCSC_FULL_DEBUG, MXMGT_TRANS);
|
|
ADD_DEBUG_MODULE_PARAM(mx_mmap, SCSC_DBG1, MX_MMAP);
|
|
ADD_DEBUG_MODULE_PARAM(mx_proc, SCSC_FULL_DEBUG, MX_PROC);
|
|
ADD_DEBUG_MODULE_PARAM(panic_mon, SCSC_FULL_DEBUG, PANIC_MON);
|
|
ADD_DEBUG_MODULE_PARAM(pcie_mif, SCSC_FULL_DEBUG, PCIE_MIF);
|
|
ADD_DEBUG_MODULE_PARAM(plat_mif, SCSC_DBG1, PLAT_MIF);
|
|
ADD_DEBUG_MODULE_PARAM(kic_common, SCSC_FULL_DEBUG, KIC_COMMON);
|
|
ADD_DEBUG_MODULE_PARAM(wlbtd, SCSC_FULL_DEBUG, WLBTD);
|
|
ADD_DEBUG_MODULE_PARAM(wlog, SCSC_DEBUG, WLOG);
|
|
ADD_DEBUG_MODULE_PARAM(lerna, SCSC_FULL_DEBUG, LERNA);
|
|
ADD_DEBUG_MODULE_PARAM(mxcfg, SCSC_FULL_DEBUG, MX_CFG);
|
|
#ifdef CONFIG_SCSC_DEBUG_COMPATIBILITY
|
|
ADD_DEBUG_MODULE_PARAM(init_deinit, SCSC_FULL_DEBUG, SLSI_INIT_DEINIT);
|
|
ADD_DEBUG_MODULE_PARAM(netdev, SCSC_DBG4, SLSI_NETDEV);
|
|
ADD_DEBUG_MODULE_PARAM(cfg80211, SCSC_FULL_DEBUG, SLSI_CFG80211);
|
|
ADD_DEBUG_MODULE_PARAM(mlme, SCSC_FULL_DEBUG, SLSI_MLME);
|
|
ADD_DEBUG_MODULE_PARAM(summary_frames, SCSC_FULL_DEBUG, SLSI_SUMMARY_FRAMES);
|
|
ADD_DEBUG_MODULE_PARAM(hydra, SCSC_FULL_DEBUG, SLSI_HYDRA);
|
|
ADD_DEBUG_MODULE_PARAM(tx, SCSC_FULL_DEBUG, SLSI_TX);
|
|
ADD_DEBUG_MODULE_PARAM(rx, SCSC_DBG4, SLSI_RX);
|
|
ADD_DEBUG_MODULE_PARAM(udi, SCSC_DBG4, SLSI_UDI);
|
|
ADD_DEBUG_MODULE_PARAM(wifi_fcq, SCSC_DBG4, SLSI_WIFI_FCQ);
|
|
ADD_DEBUG_MODULE_PARAM(hip, SCSC_FULL_DEBUG, SLSI_HIP);
|
|
ADD_DEBUG_MODULE_PARAM(hip_init_deinit, SCSC_FULL_DEBUG, SLSI_HIP_INIT_DEINIT);
|
|
ADD_DEBUG_MODULE_PARAM(hip_fw_dl, SCSC_FULL_DEBUG, SLSI_HIP_FW_DL);
|
|
ADD_DEBUG_MODULE_PARAM(hip_sdio_op, SCSC_FULL_DEBUG, SLSI_HIP_SDIO_OP);
|
|
ADD_DEBUG_MODULE_PARAM(hip_ps, SCSC_FULL_DEBUG, SLSI_HIP_PS);
|
|
ADD_DEBUG_MODULE_PARAM(hip_th, SCSC_FULL_DEBUG, SLSI_HIP_TH);
|
|
ADD_DEBUG_MODULE_PARAM(hip_fh, SCSC_FULL_DEBUG, SLSI_HIP_FH);
|
|
ADD_DEBUG_MODULE_PARAM(hip_sig, SCSC_FULL_DEBUG, SLSI_HIP_SIG);
|
|
ADD_DEBUG_MODULE_PARAM(func_trace, SCSC_FULL_DEBUG, SLSI_FUNC_TRACE);
|
|
ADD_DEBUG_MODULE_PARAM(test, SCSC_FULL_DEBUG, SLSI_TEST);
|
|
ADD_DEBUG_MODULE_PARAM(src_sink, SCSC_FULL_DEBUG, SLSI_SRC_SINK);
|
|
ADD_DEBUG_MODULE_PARAM(fw_test, SCSC_DBG4, SLSI_FW_TEST);
|
|
ADD_DEBUG_MODULE_PARAM(rx_ba, SCSC_DBG4, SLSI_RX_BA);
|
|
ADD_DEBUG_MODULE_PARAM(tdls, SCSC_FULL_DEBUG, SLSI_TDLS);
|
|
ADD_DEBUG_MODULE_PARAM(gscan, SCSC_FULL_DEBUG, SLSI_GSCAN);
|
|
ADD_DEBUG_MODULE_PARAM(mbulk, SCSC_DBG1, SLSI_MBULK);
|
|
ADD_DEBUG_MODULE_PARAM(flowc, SCSC_FULL_DEBUG, SLSI_FLOWC);
|
|
ADD_DEBUG_MODULE_PARAM(smapper, SCSC_DBG1, SLSI_SMAPPER);
|
|
ADD_DEBUG_MODULE_PARAM(pm, SCSC_DBG1, SLSI_PM);
|
|
#endif
|
|
ADD_DEBUG_MODULE_PARAM(test_me, SCSC_FULL_DEBUG, TEST_ME);
|
|
|
|
/* Extend this list when you add ADD_DEBUG_MODULE_PARAM, above.
|
|
* You must also extend "enum scsc_logring_tags"
|
|
*/
|
|
static int *scsc_droplevels[] = {
|
|
&scsc_droplevel_binary,
|
|
&scsc_droplevel_bin_wifi_ctrl_rx,
|
|
&scsc_droplevel_bin_wifi_data_rx,
|
|
&scsc_droplevel_bin_wifi_ctrl_tx,
|
|
&scsc_droplevel_bin_wifi_data_tx,
|
|
&scsc_droplevel_wlbt,
|
|
&scsc_droplevel_wifi_rx,
|
|
&scsc_droplevel_wifi_tx,
|
|
&scsc_droplevel_bt_common,
|
|
&scsc_droplevel_bt_h4,
|
|
&scsc_droplevel_bt_fw,
|
|
&scsc_droplevel_bt_rx,
|
|
&scsc_droplevel_bt_tx,
|
|
&scsc_droplevel_cpktbuff,
|
|
&scsc_droplevel_fw_load,
|
|
&scsc_droplevel_fw_panic,
|
|
&scsc_droplevel_gdb_trans,
|
|
&scsc_droplevel_mif,
|
|
&scsc_droplevel_clk20,
|
|
&scsc_droplevel_clk20_test,
|
|
&scsc_droplevel_fm,
|
|
&scsc_droplevel_fm_test,
|
|
&scsc_droplevel_mx_file,
|
|
&scsc_droplevel_mx_fw,
|
|
&scsc_droplevel_mx_sampler,
|
|
&scsc_droplevel_mxlog_trans,
|
|
&scsc_droplevel_mxman,
|
|
&scsc_droplevel_mxman_test,
|
|
&scsc_droplevel_mxmgt_trans,
|
|
&scsc_droplevel_mx_mmap,
|
|
&scsc_droplevel_mx_proc,
|
|
&scsc_droplevel_panic_mon,
|
|
&scsc_droplevel_pcie_mif,
|
|
&scsc_droplevel_plat_mif,
|
|
&scsc_droplevel_kic_common,
|
|
&scsc_droplevel_wlbtd,
|
|
&scsc_droplevel_wlog,
|
|
&scsc_droplevel_lerna,
|
|
&scsc_droplevel_mxcfg,
|
|
#ifdef CONFIG_SCSC_DEBUG_COMPATIBILITY
|
|
&scsc_droplevel_init_deinit,
|
|
&scsc_droplevel_netdev,
|
|
&scsc_droplevel_cfg80211,
|
|
&scsc_droplevel_mlme,
|
|
&scsc_droplevel_summary_frames,
|
|
&scsc_droplevel_hydra,
|
|
&scsc_droplevel_tx,
|
|
&scsc_droplevel_rx,
|
|
&scsc_droplevel_udi,
|
|
&scsc_droplevel_wifi_fcq,
|
|
&scsc_droplevel_hip,
|
|
&scsc_droplevel_hip_init_deinit,
|
|
&scsc_droplevel_hip_fw_dl,
|
|
&scsc_droplevel_hip_sdio_op,
|
|
&scsc_droplevel_hip_ps,
|
|
&scsc_droplevel_hip_th,
|
|
&scsc_droplevel_hip_fh,
|
|
&scsc_droplevel_hip_sig,
|
|
&scsc_droplevel_func_trace,
|
|
&scsc_droplevel_test,
|
|
&scsc_droplevel_src_sink,
|
|
&scsc_droplevel_fw_test,
|
|
&scsc_droplevel_rx_ba,
|
|
&scsc_droplevel_tdls,
|
|
&scsc_droplevel_gscan,
|
|
&scsc_droplevel_mbulk,
|
|
&scsc_droplevel_flowc,
|
|
&scsc_droplevel_smapper,
|
|
&scsc_droplevel_pm,
|
|
#endif
|
|
&scsc_droplevel_test_me, /* Must be last */
|
|
};
|
|
_Static_assert(
|
|
ARRAY_SIZE(scsc_droplevels) == MAX_TAG + 1,
|
|
"You must keep 'enum scsc_logring_tags' and 'scsc_droplevels' in sync"
|
|
);
|
|
|
|
#if IS_ENABLED(CONFIG_SCSC_LOG_COLLECTION)
|
|
static int logring_collect(struct scsc_log_collector_client *collect_client, size_t size)
|
|
{
|
|
int ret = 0, saved_droplevel;
|
|
|
|
if (!the_ringbuf)
|
|
return 0;
|
|
|
|
/**
|
|
* Inhibit logring during collection overriding with scsc_droplevel_all
|
|
*/
|
|
saved_droplevel = scsc_droplevel_all;
|
|
scsc_droplevel_all = DEFAULT_DROP_ALL;
|
|
|
|
/* Write buffer */
|
|
ret = scsc_log_collector_write(the_ringbuf->buf, the_ringbuf->bsz, 1);
|
|
|
|
scsc_droplevel_all = saved_droplevel;
|
|
|
|
return ret;
|
|
}
|
|
#endif
|
|
|
|
static int scsc_reset_all_droplevels_to_set_param_cb(const char *val,
|
|
const struct kernel_param *kp)
|
|
{
|
|
int ret = 0, i = 0;
|
|
long rval = 0;
|
|
|
|
if (!val)
|
|
return -EINVAL;
|
|
ret = kstrtol(val, 10, &rval);
|
|
if (!ret) {
|
|
if (rval < 0)
|
|
return -EINVAL;
|
|
scsc_droplevel_wlbt = (int)rval;
|
|
for (i = 0; i < ARRAY_SIZE(scsc_droplevels); i++)
|
|
*scsc_droplevels[i] = (int)rval;
|
|
scsc_reset_all_droplevels_to = (int)rval;
|
|
scsc_printk_tag(FORCE_PRK, NO_TAG,
|
|
KERN_INFO"Reset ALL droplevels to %d\n",
|
|
scsc_reset_all_droplevels_to);
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
static struct kernel_param_ops scsc_reset_droplvl_ops = {
|
|
.set = scsc_reset_all_droplevels_to_set_param_cb,
|
|
.get = NULL,
|
|
};
|
|
module_param_cb(scsc_reset_all_droplevels_to, &scsc_reset_droplvl_ops,
|
|
NULL, 0200);
|
|
MODULE_PARM_DESC(scsc_reset_all_droplevels_to,
|
|
"Reset ALL droplevels to the requested value. Effective @run-time.");
|
|
|
|
|
|
/* SCSC_PRINTK API and Helpers */
|
|
static inline int get_debug_level(const char *fmt)
|
|
{
|
|
int level;
|
|
|
|
if (fmt && *fmt == SCSC_SOH && *(fmt + 1))
|
|
level = *(fmt + 1) - '0';
|
|
else
|
|
level = default_dbglevel;
|
|
return level;
|
|
}
|
|
|
|
static inline void drop_message_level_macro(const char *fmt, char **msg)
|
|
{
|
|
if (fmt && *fmt == SCSC_SOH && *(fmt + 1)) {
|
|
if (msg)
|
|
*msg = (char *)(fmt + 2);
|
|
} else if (msg) {
|
|
*msg = (char *)fmt;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* The workhorse function that receiving a droplevel to enforce, and a pair
|
|
* format_string/va_list decides what to do:
|
|
* - drop
|
|
* OR
|
|
* - insert into ring buffer accounting for wrapping
|
|
*
|
|
* ... then wakes up any waiting reading process
|
|
*/
|
|
static inline int _scsc_printk(int level, int tag,
|
|
const char *fmt, va_list args)
|
|
{
|
|
int written = 0;
|
|
char *msg_head = NULL;
|
|
|
|
if (!initialized || !enable || !fmt ||
|
|
((scsc_droplevel_all < 0 && level >= *scsc_droplevels[tag]) ||
|
|
(scsc_droplevel_all >= 0 && level >= scsc_droplevel_all)))
|
|
return written;
|
|
drop_message_level_macro(fmt, &msg_head);
|
|
written = push_record_string(the_ringbuf, tag, level,
|
|
prepend_header, msg_head, args);
|
|
return written;
|
|
}
|
|
|
|
#if IS_ENABLED(CONFIG_SCSC_MEMLOG)
|
|
#define SCSC_LVL2MEMLOG(lv) (((lv) < 3) ? MEMLOG_LEVEL_EMERG : ((lv) < 7) ? (lv) - 2 : MEMLOG_LEVEL_DEBUG)
|
|
#define IS_MEMLOG_ALLOWED(ff, level, memlog_lv) \
|
|
((ff) == FORCE_PRK || \
|
|
((ff) != NO_ECHO_PRK && (level) < scsc_redirect_to_printk_droplvl) || \
|
|
((ff) != NO_ECHO_PRK && SCSC_LVL2MEMLOG(level) <= (memlog_lv)))
|
|
|
|
static struct memlog_obj *memlog_print_obj;
|
|
static struct memlog_obj *memlog_pm_obj;
|
|
extern const char *tagstr[MAX_TAG + 1];
|
|
|
|
static inline bool memlog_get_obj_pm(void)
|
|
{
|
|
struct memlog *memlog_desc = NULL;
|
|
struct memlog_obj *file_obj = NULL;
|
|
|
|
if (!preemptible() || in_interrupt() || rcu_preempt_depth() > 0)
|
|
return false;
|
|
|
|
memlog_desc = memlog_get_desc("WB_LOG");
|
|
if (!memlog_desc)
|
|
return false;
|
|
|
|
memlog_pm_obj = memlog_get_obj_by_name(memlog_desc, "ker_mem2");
|
|
if (!memlog_pm_obj) {
|
|
file_obj = memlog_alloc_file(memlog_desc, "ker_fil2", SZ_32K, SZ_128K, 1000, 1);
|
|
if (file_obj)
|
|
memlog_pm_obj = memlog_alloc_printf(memlog_desc, SZ_16K, file_obj, "ker_mem2", false);
|
|
|
|
if (!memlog_pm_obj)
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
static inline bool memlog_get_obj(void)
|
|
{
|
|
struct memlog *memlog_desc = NULL;
|
|
struct memlog_obj *file_obj = NULL;
|
|
|
|
if (!preemptible() || in_interrupt() || rcu_preempt_depth() > 0)
|
|
return false;
|
|
|
|
memlog_desc = memlog_get_desc("WB_LOG");
|
|
if (!memlog_desc)
|
|
return false;
|
|
|
|
memlog_print_obj = memlog_get_obj_by_name(memlog_desc, "ker_mem");
|
|
if (!memlog_print_obj) {
|
|
file_obj = memlog_alloc_file(memlog_desc, "ker_fil", SZ_1M, SZ_1M, 1000, 1);
|
|
if (file_obj)
|
|
memlog_print_obj = memlog_alloc_printf(memlog_desc, SZ_512K, file_obj, "ker_mem", false);
|
|
|
|
if (!memlog_print_obj)
|
|
return false;
|
|
}
|
|
|
|
/* Invoke in advance to avoid call memlog_get_obj_pm() when resume.
|
|
* memlog seems to have a bug for file alloc early in resume.
|
|
*/
|
|
memlog_get_obj_pm();
|
|
|
|
return true;
|
|
}
|
|
|
|
#ifdef CONFIG_SCSC_DEBUG_COMPATIBILITY
|
|
static inline
|
|
void memlog_out_string_pm(int tag, const char *fmt, va_list args)
|
|
{
|
|
struct va_format vaf;
|
|
char *msg_head = NULL;
|
|
|
|
if (memlog_pm_obj == NULL)
|
|
return;
|
|
|
|
drop_message_level_macro(fmt, &msg_head);
|
|
vaf.fmt = msg_head;
|
|
vaf.va = &args;
|
|
|
|
memlog_write_printf(memlog_pm_obj, 1, "[%s] :: %pV", tagstr[tag], &vaf);
|
|
memlog_sync_to_file(memlog_pm_obj);
|
|
}
|
|
#endif
|
|
|
|
static inline
|
|
void memlog_out_string_print(int level, int tag, const char *fmt,
|
|
va_list args, int force)
|
|
{
|
|
unsigned long flags;
|
|
struct va_format vaf;
|
|
u8 ctx;
|
|
u8 core;
|
|
char *msg_head = NULL;
|
|
|
|
if (memlog_print_obj == NULL && !memlog_get_obj())
|
|
return;
|
|
|
|
if (!IS_MEMLOG_ALLOWED(force, level, memlog_print_obj->log_level))
|
|
return;
|
|
|
|
drop_message_level_macro(fmt, &msg_head);
|
|
vaf.fmt = msg_head;
|
|
vaf.va = &args;
|
|
|
|
local_irq_save(flags);
|
|
ctx = in_interrupt() ? (in_softirq() ? 'S' : 'I') : 'P';
|
|
core = smp_processor_id();
|
|
local_irq_restore(flags);
|
|
|
|
memlog_write_printf(memlog_print_obj, SCSC_LVL2MEMLOG(level),
|
|
"<%d> [c%d] [%c] [%s] :: %pV",
|
|
level, core, ctx, tagstr[tag], &vaf);
|
|
|
|
}
|
|
|
|
static inline
|
|
void memlog_out_string(int level, int tag, const char *fmt, va_list args,
|
|
int force)
|
|
{
|
|
if (!enable)
|
|
return;
|
|
|
|
#ifdef CONFIG_SCSC_DEBUG_COMPATIBILITY
|
|
if (tag == SLSI_PM)
|
|
memlog_out_string_pm(tag, fmt, args);
|
|
#endif
|
|
memlog_out_string_print(level, tag, fmt, args, force);
|
|
}
|
|
|
|
#ifdef CONFIG_SCSC_DEBUG_COMPATIBILITY
|
|
static inline
|
|
void memlog_out_bin_pm(int tag, const void *start, size_t len)
|
|
{
|
|
const u8 *ptr = start;
|
|
int i, linelen, rowsize = 32, remaining = len;
|
|
unsigned char linebuf[64 * 3 + 1];
|
|
|
|
if (memlog_pm_obj == NULL)
|
|
return;
|
|
|
|
for (i = 0; i < len; i += rowsize) {
|
|
linelen = min(remaining, rowsize);
|
|
remaining -= rowsize;
|
|
|
|
hex_dump_to_buffer(ptr + i, linelen, rowsize, 1,
|
|
linebuf, sizeof(linebuf), false);
|
|
|
|
memlog_write_printf(memlog_pm_obj, 1, "[%s] HEX->|%s\n",
|
|
tagstr[tag], linebuf);
|
|
}
|
|
}
|
|
#endif
|
|
|
|
static inline
|
|
void memlog_out_bin_print(int level, int tag, const void *start,
|
|
size_t len, int force)
|
|
{
|
|
const u8 *ptr = start;
|
|
int i, linelen, rowsize = 32, remaining = len;
|
|
unsigned char linebuf[64 * 3 + 1];
|
|
unsigned long flags;
|
|
u8 ctx;
|
|
u8 core;
|
|
|
|
if (memlog_print_obj == NULL && !memlog_get_obj())
|
|
return;
|
|
|
|
if (!IS_MEMLOG_ALLOWED(force, level, memlog_print_obj->log_level))
|
|
return;
|
|
|
|
local_irq_save(flags);
|
|
ctx = in_interrupt() ? (in_softirq() ? 'S' : 'I') : 'P';
|
|
core = smp_processor_id();
|
|
local_irq_restore(flags);
|
|
|
|
for (i = 0; i < len; i += rowsize) {
|
|
linelen = min(remaining, rowsize);
|
|
remaining -= rowsize;
|
|
|
|
hex_dump_to_buffer(ptr + i, linelen, rowsize, 1,
|
|
linebuf, sizeof(linebuf), false);
|
|
|
|
memlog_write_printf(memlog_print_obj, SCSC_LVL2MEMLOG(level),
|
|
"<%d> [c%d] [%c] [%s] "SCSC_PREFIX"SCSC_HEX->|%s\n",
|
|
level, core, ctx, tagstr[tag], linebuf);
|
|
}
|
|
}
|
|
|
|
|
|
static inline
|
|
void memlog_out_bin(int level, int tag, const void *start, size_t len,
|
|
int force)
|
|
{
|
|
if (!enable)
|
|
return;
|
|
|
|
#ifdef CONFIG_SCSC_DEBUG_COMPATIBILITY
|
|
if (tag == SLSI_PM)
|
|
memlog_out_bin_pm(tag, start, len);
|
|
#endif
|
|
|
|
memlog_out_bin_print(level, tag, start, len, force);
|
|
}
|
|
#endif
|
|
|
|
/**
|
|
* Embeds the filtering behaviour towards std kenrel ring buffer for
|
|
* non binary stuff, and decides what to do based on current user config.
|
|
* Note that printk redirect doplevel filter is now completely disjoint
|
|
* from normal LogRing droplevel filters.
|
|
*/
|
|
static inline
|
|
void handle_klogbuf_out_string(int level, struct device *dev, int tag,
|
|
const char *fmt, va_list args, int force)
|
|
{
|
|
if (IS_PRINTK_REDIRECT_ALLOWED(force, level, tag)) {
|
|
if (!dev)
|
|
#if (LINUX_VERSION_CODE >= KERNEL_VERSION(5, 10, 0))
|
|
vprintk_emit(0, level, NULL, fmt, args);
|
|
#else
|
|
vprintk_emit(0, level, NULL, 0, fmt, args);
|
|
#endif
|
|
else
|
|
dev_vprintk_emit(level, dev, fmt, args);
|
|
}
|
|
}
|
|
|
|
static const char * const map2kern[] = {
|
|
KERN_EMERG,
|
|
KERN_ALERT,
|
|
KERN_CRIT,
|
|
KERN_ERR,
|
|
KERN_WARNING,
|
|
KERN_NOTICE,
|
|
KERN_INFO,
|
|
KERN_DEBUG
|
|
};
|
|
|
|
/**
|
|
* Embeds the filtering behaviour towards std kenrel ring buffer for
|
|
* BINARY stuff, and decides what to do based on current user config.
|
|
* Note that printk redirect doplevel filter is now completely disjoint
|
|
* from normal LogRing droplevel filters.
|
|
*/
|
|
static inline
|
|
void handle_klogbuf_out_binary(int level, int tag, const void *start,
|
|
size_t len, int force)
|
|
{
|
|
if (IS_PRINTK_REDIRECT_ALLOWED(force, level, tag)) {
|
|
if (level < SCSC_MIN_DBG || level >= ARRAY_SIZE(map2kern))
|
|
level = ARRAY_SIZE(map2kern) - 1;
|
|
print_hex_dump(map2kern[level], SCSC_PREFIX"SCSC_HEX->|",
|
|
DUMP_PREFIX_NONE, 16, 1, start, len, false);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* scsc_printk - it's main API entry to the event logging mechanism. Prints
|
|
* the specified format string to the underlying ring_buffer, injecting
|
|
* timestamp and context information at the start of the line while
|
|
* classifying and filtering the message suing the specified TAG identifier.
|
|
*
|
|
* This function assumes that you'll never write a line longer than
|
|
* BASE_SPARE_SZ bytes; if this limit is obeyed any input string is correctly
|
|
* wrapped when placed at the end of the buffer. Any longer line will be
|
|
* trucated.
|
|
* This function recognize Kernel style debug level KERN_, checking the FIRST
|
|
* byte for ASCII SOH in order to recognize if some printk style kernel debug
|
|
* level has been specified.
|
|
* SO you can use KERN_INFO KERN_ERR etc etc INLINE macros to specify the
|
|
* desired debug level: that will be checked against the droplevel specified.
|
|
* If NOT specified a default debug level is assigned following what specified
|
|
* in module parameter default_dbglevel.
|
|
*
|
|
* It's usually NOT used directly but through the means of utility macros that
|
|
* can be easily compiled out in production builds.
|
|
*/
|
|
int scsc_printk_tag(int force, int tag, const char *fmt, ...)
|
|
{
|
|
int ret = 0, level = 0;
|
|
va_list args;
|
|
|
|
/* Cannot use BINARY tag with strings logging */
|
|
if (tag < NO_TAG || tag > MAX_TAG)
|
|
return ret;
|
|
level = get_debug_level(fmt);
|
|
if ((in_interrupt() && level >= scsc_droplevel_atomic))
|
|
return ret;
|
|
|
|
#if IS_ENABLED(CONFIG_SCSC_MEMLOG)
|
|
va_start(args, fmt);
|
|
memlog_out_string(level, tag, fmt, args, force);
|
|
va_end(args);
|
|
#endif
|
|
va_start(args, fmt);
|
|
handle_klogbuf_out_string(level, NULL, tag, fmt, args, force);
|
|
va_end(args);
|
|
/* restart varargs */
|
|
va_start(args, fmt);
|
|
ret = _scsc_printk(level, tag, fmt, args);
|
|
va_end(args);
|
|
return ret;
|
|
}
|
|
EXPORT_SYMBOL(scsc_printk_tag);
|
|
|
|
|
|
/**
|
|
* This is a variation on the main API that allows to specify loglevel
|
|
* by number.
|
|
*/
|
|
int scsc_printk_tag_lvl(int tag, int level, const char *fmt, ...)
|
|
{
|
|
int ret = 0;
|
|
va_list args;
|
|
|
|
/* Cannot use BINARY tag with strings logging */
|
|
if (tag < NO_TAG || tag > MAX_TAG)
|
|
return ret;
|
|
if ((in_interrupt() && level >= scsc_droplevel_atomic))
|
|
return ret;
|
|
|
|
#if IS_ENABLED(CONFIG_SCSC_MEMLOG)
|
|
va_start(args, fmt);
|
|
memlog_out_string(level, tag, fmt, args, NO_FORCE_PRK);
|
|
va_end(args);
|
|
#endif
|
|
va_start(args, fmt);
|
|
handle_klogbuf_out_string(level, NULL, tag, fmt, args, NO_FORCE_PRK);
|
|
va_end(args);
|
|
/* restart varargs */
|
|
va_start(args, fmt);
|
|
ret = _scsc_printk(level, tag, fmt, args);
|
|
va_end(args);
|
|
return ret;
|
|
}
|
|
EXPORT_SYMBOL(scsc_printk_tag_lvl);
|
|
|
|
|
|
/**
|
|
* This is a variation on the main API that allows to specify a
|
|
* struct device reference.
|
|
*/
|
|
int scsc_printk_tag_dev(int force, int tag, struct device *dev,
|
|
const char *fmt, ...)
|
|
{
|
|
int ret = 0, level = 0;
|
|
va_list args;
|
|
|
|
/* Cannot use BINARY tag with strings logging */
|
|
if (tag < NO_TAG || tag > MAX_TAG)
|
|
return ret;
|
|
level = get_debug_level(fmt);
|
|
if ((in_interrupt() && level >= scsc_droplevel_atomic))
|
|
return ret;
|
|
|
|
#if IS_ENABLED(CONFIG_SCSC_MEMLOG)
|
|
va_start(args, fmt);
|
|
memlog_out_string(level, tag, fmt, args, force);
|
|
va_end(args);
|
|
#endif
|
|
va_start(args, fmt);
|
|
handle_klogbuf_out_string(level, dev, tag, fmt, args, force);
|
|
va_end(args);
|
|
/* restart varargs */
|
|
va_start(args, fmt);
|
|
ret = _scsc_printk(level, tag, fmt, args);
|
|
va_end(args);
|
|
return ret;
|
|
}
|
|
EXPORT_SYMBOL(scsc_printk_tag_dev);
|
|
|
|
/**
|
|
* This is a variation on the main API that allows to specify a
|
|
* struct device reference and an explicit numerical debug level.
|
|
*/
|
|
int scsc_printk_tag_dev_lvl(int force, int tag, struct device *dev,
|
|
int level, const char *fmt, ...)
|
|
{
|
|
int ret = 0;
|
|
va_list args;
|
|
|
|
/* Cannot use BINARY tag with strings logging */
|
|
if (tag < NO_TAG || tag > MAX_TAG)
|
|
return ret;
|
|
if ((in_interrupt() && level >= scsc_droplevel_atomic))
|
|
return ret;
|
|
|
|
#if IS_ENABLED(CONFIG_SCSC_MEMLOG)
|
|
va_start(args, fmt);
|
|
memlog_out_string(level, tag, fmt, args, force);
|
|
va_end(args);
|
|
#endif
|
|
va_start(args, fmt);
|
|
handle_klogbuf_out_string(level, dev, tag, fmt, args, force);
|
|
va_end(args);
|
|
/* restart varargs */
|
|
va_start(args, fmt);
|
|
ret = _scsc_printk(level, tag, fmt, args);
|
|
va_end(args);
|
|
return ret;
|
|
}
|
|
EXPORT_SYMBOL(scsc_printk_tag_dev_lvl);
|
|
|
|
/**
|
|
* This is a variation on the main API used to push binary blob into the ring.
|
|
*/
|
|
int scsc_printk_bin(int force, int tag, int dlev, const void *start, size_t len)
|
|
{
|
|
int ret = 0;
|
|
/* Cannot use NON BINARY tag with strings logging
|
|
* or NULLs start/len
|
|
*/
|
|
if (!start || !len || tag < FIRST_BIN_TAG || tag > LAST_BIN_TAG)
|
|
return ret;
|
|
dlev = (dlev >= 0) ? dlev : default_dbglevel;
|
|
if ((in_interrupt() && dlev >= scsc_droplevel_atomic))
|
|
return ret;
|
|
#if IS_ENABLED(CONFIG_SCSC_MEMLOG)
|
|
memlog_out_bin(dlev, tag, start, len, force);
|
|
#endif
|
|
handle_klogbuf_out_binary(dlev, tag, start, len, force);
|
|
/* consider proper tag droplevel */
|
|
if (!initialized || !enable || !start ||
|
|
(scsc_droplevel_all < 0 && *scsc_droplevels[tag] <= dlev) ||
|
|
(scsc_droplevel_all >= 0 && scsc_droplevel_all <= dlev))
|
|
return ret;
|
|
ret = push_record_blob(the_ringbuf, tag, dlev,
|
|
prepend_header, start, len);
|
|
return ret;
|
|
}
|
|
EXPORT_SYMBOL(scsc_printk_bin);
|
|
|
|
/*
|
|
* This is a very basic mechanism to have implement the dynamic switch
|
|
* for one user (currently WLAN). If multiple users are
|
|
* required to use the dynamic logring switch, a new registration
|
|
* mechanism based on requests and use_count should be implemented to avoid one service
|
|
* re-enabling logring when some other has requested not to do so.
|
|
*/
|
|
int scsc_logring_enable(bool logging_enable)
|
|
{
|
|
scsc_printk_tag(FORCE_PRK, NO_TAG, "scsc_logring %s\n", logging_enable ? "enable" : "disable");
|
|
/* User has requested to disable logring */
|
|
if (!logging_enable && enable) {
|
|
cached_enable = true;
|
|
enable = 0;
|
|
scsc_printk_tag(FORCE_PRK, NO_TAG, "Logring disabled\n");
|
|
} else if (logging_enable && cached_enable) {
|
|
cached_enable = false;
|
|
enable = 1;
|
|
scsc_printk_tag(FORCE_PRK, NO_TAG, "Logring re-enabled\n");
|
|
} else {
|
|
scsc_printk_tag(FORCE_PRK, NO_TAG, "Ignored\n");
|
|
}
|
|
return 0;
|
|
}
|
|
EXPORT_SYMBOL(scsc_logring_enable);
|
|
|
|
|
|
MODULE_DESCRIPTION("SCSC Event Logger");
|
|
MODULE_AUTHOR("SLSI");
|
|
MODULE_LICENSE("GPL and additional rights");
|