349 lines
7.9 KiB
C
349 lines
7.9 KiB
C
|
/*
|
||
|
* iwmc3200top - Intel Wireless MultiCom 3200 Top Driver
|
||
|
* drivers/misc/iwmc3200top/log.c
|
||
|
*
|
||
|
* Copyright (C) 2009 Intel Corporation. All rights reserved.
|
||
|
*
|
||
|
* This program is free software; you can redistribute it and/or
|
||
|
* modify it under the terms of the GNU General Public License version
|
||
|
* 2 as published by the Free Software Foundation.
|
||
|
*
|
||
|
* This program is distributed in the hope that it will be useful,
|
||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||
|
* GNU General Public License for more details.
|
||
|
*
|
||
|
* You should have received a copy of the GNU General Public License
|
||
|
* along with this program; if not, write to the Free Software
|
||
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
|
||
|
* 02110-1301, USA.
|
||
|
*
|
||
|
*
|
||
|
* Author Name: Maxim Grabarnik <maxim.grabarnink@intel.com>
|
||
|
* -
|
||
|
*
|
||
|
*/
|
||
|
|
||
|
#include <linux/kernel.h>
|
||
|
#include <linux/mmc/sdio_func.h>
|
||
|
#include <linux/slab.h>
|
||
|
#include <linux/ctype.h>
|
||
|
#include "fw-msg.h"
|
||
|
#include "iwmc3200top.h"
|
||
|
#include "log.h"
|
||
|
|
||
|
/* Maximal hexadecimal string size of the FW memdump message */
|
||
|
#define LOG_MSG_SIZE_MAX 12400
|
||
|
|
||
|
/* iwmct_logdefs is a global used by log macros */
|
||
|
u8 iwmct_logdefs[LOG_SRC_MAX];
|
||
|
static u8 iwmct_fw_logdefs[FW_LOG_SRC_MAX];
|
||
|
|
||
|
|
||
|
static int _log_set_log_filter(u8 *logdefs, int size, u8 src, u8 logmask)
|
||
|
{
|
||
|
int i;
|
||
|
|
||
|
if (src < size)
|
||
|
logdefs[src] = logmask;
|
||
|
else if (src == LOG_SRC_ALL)
|
||
|
for (i = 0; i < size; i++)
|
||
|
logdefs[i] = logmask;
|
||
|
else
|
||
|
return -1;
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
|
||
|
int iwmct_log_set_filter(u8 src, u8 logmask)
|
||
|
{
|
||
|
return _log_set_log_filter(iwmct_logdefs, LOG_SRC_MAX, src, logmask);
|
||
|
}
|
||
|
|
||
|
|
||
|
int iwmct_log_set_fw_filter(u8 src, u8 logmask)
|
||
|
{
|
||
|
return _log_set_log_filter(iwmct_fw_logdefs,
|
||
|
FW_LOG_SRC_MAX, src, logmask);
|
||
|
}
|
||
|
|
||
|
|
||
|
static int log_msg_format_hex(char *str, int slen, u8 *ibuf,
|
||
|
int ilen, char *pref)
|
||
|
{
|
||
|
int pos = 0;
|
||
|
int i;
|
||
|
int len;
|
||
|
|
||
|
for (pos = 0, i = 0; pos < slen - 2 && pref[i] != '\0'; i++, pos++)
|
||
|
str[pos] = pref[i];
|
||
|
|
||
|
for (i = 0; pos < slen - 2 && i < ilen; pos += len, i++)
|
||
|
len = snprintf(&str[pos], slen - pos - 1, " %2.2X", ibuf[i]);
|
||
|
|
||
|
if (i < ilen)
|
||
|
return -1;
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
/* NOTE: This function is not thread safe.
|
||
|
Currently it's called only from sdio rx worker - no race there
|
||
|
*/
|
||
|
void iwmct_log_top_message(struct iwmct_priv *priv, u8 *buf, int len)
|
||
|
{
|
||
|
struct top_msg *msg;
|
||
|
static char logbuf[LOG_MSG_SIZE_MAX];
|
||
|
|
||
|
msg = (struct top_msg *)buf;
|
||
|
|
||
|
if (len < sizeof(msg->hdr) + sizeof(msg->u.log.log_hdr)) {
|
||
|
LOG_ERROR(priv, FW_MSG, "Log message from TOP "
|
||
|
"is too short %d (expected %zd)\n",
|
||
|
len, sizeof(msg->hdr) + sizeof(msg->u.log.log_hdr));
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
if (!(iwmct_fw_logdefs[msg->u.log.log_hdr.logsource] &
|
||
|
BIT(msg->u.log.log_hdr.severity)) ||
|
||
|
!(iwmct_logdefs[LOG_SRC_FW_MSG] & BIT(msg->u.log.log_hdr.severity)))
|
||
|
return;
|
||
|
|
||
|
switch (msg->hdr.category) {
|
||
|
case COMM_CATEGORY_TESTABILITY:
|
||
|
if (!(iwmct_logdefs[LOG_SRC_TST] &
|
||
|
BIT(msg->u.log.log_hdr.severity)))
|
||
|
return;
|
||
|
if (log_msg_format_hex(logbuf, LOG_MSG_SIZE_MAX, buf,
|
||
|
le16_to_cpu(msg->hdr.length) +
|
||
|
sizeof(msg->hdr), "<TST>"))
|
||
|
LOG_WARNING(priv, TST,
|
||
|
"TOP TST message is too long, truncating...");
|
||
|
LOG_WARNING(priv, TST, "%s\n", logbuf);
|
||
|
break;
|
||
|
case COMM_CATEGORY_DEBUG:
|
||
|
if (msg->hdr.opcode == OP_DBG_ZSTR_MSG)
|
||
|
LOG_INFO(priv, FW_MSG, "%s %s", "<DBG>",
|
||
|
((u8 *)msg) + sizeof(msg->hdr)
|
||
|
+ sizeof(msg->u.log.log_hdr));
|
||
|
else {
|
||
|
if (log_msg_format_hex(logbuf, LOG_MSG_SIZE_MAX, buf,
|
||
|
le16_to_cpu(msg->hdr.length)
|
||
|
+ sizeof(msg->hdr),
|
||
|
"<DBG>"))
|
||
|
LOG_WARNING(priv, FW_MSG,
|
||
|
"TOP DBG message is too long,"
|
||
|
"truncating...");
|
||
|
LOG_WARNING(priv, FW_MSG, "%s\n", logbuf);
|
||
|
}
|
||
|
break;
|
||
|
default:
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
static int _log_get_filter_str(u8 *logdefs, int logdefsz, char *buf, int size)
|
||
|
{
|
||
|
int i, pos, len;
|
||
|
for (i = 0, pos = 0; (pos < size-1) && (i < logdefsz); i++) {
|
||
|
len = snprintf(&buf[pos], size - pos - 1, "0x%02X%02X,",
|
||
|
i, logdefs[i]);
|
||
|
pos += len;
|
||
|
}
|
||
|
buf[pos-1] = '\n';
|
||
|
buf[pos] = '\0';
|
||
|
|
||
|
if (i < logdefsz)
|
||
|
return -1;
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
int log_get_filter_str(char *buf, int size)
|
||
|
{
|
||
|
return _log_get_filter_str(iwmct_logdefs, LOG_SRC_MAX, buf, size);
|
||
|
}
|
||
|
|
||
|
int log_get_fw_filter_str(char *buf, int size)
|
||
|
{
|
||
|
return _log_get_filter_str(iwmct_fw_logdefs, FW_LOG_SRC_MAX, buf, size);
|
||
|
}
|
||
|
|
||
|
#define HEXADECIMAL_RADIX 16
|
||
|
#define LOG_SRC_FORMAT 7 /* log level is in format of "0xXXXX," */
|
||
|
|
||
|
ssize_t show_iwmct_log_level(struct device *d,
|
||
|
struct device_attribute *attr, char *buf)
|
||
|
{
|
||
|
struct iwmct_priv *priv = dev_get_drvdata(d);
|
||
|
char *str_buf;
|
||
|
int buf_size;
|
||
|
ssize_t ret;
|
||
|
|
||
|
buf_size = (LOG_SRC_FORMAT * LOG_SRC_MAX) + 1;
|
||
|
str_buf = kzalloc(buf_size, GFP_KERNEL);
|
||
|
if (!str_buf) {
|
||
|
LOG_ERROR(priv, DEBUGFS,
|
||
|
"failed to allocate %d bytes\n", buf_size);
|
||
|
ret = -ENOMEM;
|
||
|
goto exit;
|
||
|
}
|
||
|
|
||
|
if (log_get_filter_str(str_buf, buf_size) < 0) {
|
||
|
ret = -EINVAL;
|
||
|
goto exit;
|
||
|
}
|
||
|
|
||
|
ret = sprintf(buf, "%s", str_buf);
|
||
|
|
||
|
exit:
|
||
|
kfree(str_buf);
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
ssize_t store_iwmct_log_level(struct device *d,
|
||
|
struct device_attribute *attr,
|
||
|
const char *buf, size_t count)
|
||
|
{
|
||
|
struct iwmct_priv *priv = dev_get_drvdata(d);
|
||
|
char *token, *str_buf = NULL;
|
||
|
long val;
|
||
|
ssize_t ret = count;
|
||
|
u8 src, mask;
|
||
|
|
||
|
if (!count)
|
||
|
goto exit;
|
||
|
|
||
|
str_buf = kzalloc(count, GFP_KERNEL);
|
||
|
if (!str_buf) {
|
||
|
LOG_ERROR(priv, DEBUGFS,
|
||
|
"failed to allocate %zd bytes\n", count);
|
||
|
ret = -ENOMEM;
|
||
|
goto exit;
|
||
|
}
|
||
|
|
||
|
memcpy(str_buf, buf, count);
|
||
|
|
||
|
while ((token = strsep(&str_buf, ",")) != NULL) {
|
||
|
while (isspace(*token))
|
||
|
++token;
|
||
|
if (strict_strtol(token, HEXADECIMAL_RADIX, &val)) {
|
||
|
LOG_ERROR(priv, DEBUGFS,
|
||
|
"failed to convert string to long %s\n",
|
||
|
token);
|
||
|
ret = -EINVAL;
|
||
|
goto exit;
|
||
|
}
|
||
|
|
||
|
mask = val & 0xFF;
|
||
|
src = (val & 0XFF00) >> 8;
|
||
|
iwmct_log_set_filter(src, mask);
|
||
|
}
|
||
|
|
||
|
exit:
|
||
|
kfree(str_buf);
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
ssize_t show_iwmct_log_level_fw(struct device *d,
|
||
|
struct device_attribute *attr, char *buf)
|
||
|
{
|
||
|
struct iwmct_priv *priv = dev_get_drvdata(d);
|
||
|
char *str_buf;
|
||
|
int buf_size;
|
||
|
ssize_t ret;
|
||
|
|
||
|
buf_size = (LOG_SRC_FORMAT * FW_LOG_SRC_MAX) + 2;
|
||
|
|
||
|
str_buf = kzalloc(buf_size, GFP_KERNEL);
|
||
|
if (!str_buf) {
|
||
|
LOG_ERROR(priv, DEBUGFS,
|
||
|
"failed to allocate %d bytes\n", buf_size);
|
||
|
ret = -ENOMEM;
|
||
|
goto exit;
|
||
|
}
|
||
|
|
||
|
if (log_get_fw_filter_str(str_buf, buf_size) < 0) {
|
||
|
ret = -EINVAL;
|
||
|
goto exit;
|
||
|
}
|
||
|
|
||
|
ret = sprintf(buf, "%s", str_buf);
|
||
|
|
||
|
exit:
|
||
|
kfree(str_buf);
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
ssize_t store_iwmct_log_level_fw(struct device *d,
|
||
|
struct device_attribute *attr,
|
||
|
const char *buf, size_t count)
|
||
|
{
|
||
|
struct iwmct_priv *priv = dev_get_drvdata(d);
|
||
|
struct top_msg cmd;
|
||
|
char *token, *str_buf = NULL;
|
||
|
ssize_t ret = count;
|
||
|
u16 cmdlen = 0;
|
||
|
int i;
|
||
|
long val;
|
||
|
u8 src, mask;
|
||
|
|
||
|
if (!count)
|
||
|
goto exit;
|
||
|
|
||
|
str_buf = kzalloc(count, GFP_KERNEL);
|
||
|
if (!str_buf) {
|
||
|
LOG_ERROR(priv, DEBUGFS,
|
||
|
"failed to allocate %zd bytes\n", count);
|
||
|
ret = -ENOMEM;
|
||
|
goto exit;
|
||
|
}
|
||
|
|
||
|
memcpy(str_buf, buf, count);
|
||
|
|
||
|
cmd.hdr.type = COMM_TYPE_H2D;
|
||
|
cmd.hdr.category = COMM_CATEGORY_DEBUG;
|
||
|
cmd.hdr.opcode = CMD_DBG_LOG_LEVEL;
|
||
|
|
||
|
for (i = 0; ((token = strsep(&str_buf, ",")) != NULL) &&
|
||
|
(i < FW_LOG_SRC_MAX); i++) {
|
||
|
|
||
|
while (isspace(*token))
|
||
|
++token;
|
||
|
|
||
|
if (strict_strtol(token, HEXADECIMAL_RADIX, &val)) {
|
||
|
LOG_ERROR(priv, DEBUGFS,
|
||
|
"failed to convert string to long %s\n",
|
||
|
token);
|
||
|
ret = -EINVAL;
|
||
|
goto exit;
|
||
|
}
|
||
|
|
||
|
mask = val & 0xFF; /* LSB */
|
||
|
src = (val & 0XFF00) >> 8; /* 2nd least significant byte. */
|
||
|
iwmct_log_set_fw_filter(src, mask);
|
||
|
|
||
|
cmd.u.logdefs[i].logsource = src;
|
||
|
cmd.u.logdefs[i].sevmask = mask;
|
||
|
}
|
||
|
|
||
|
cmd.hdr.length = cpu_to_le16(i * sizeof(cmd.u.logdefs[0]));
|
||
|
cmdlen = (i * sizeof(cmd.u.logdefs[0]) + sizeof(cmd.hdr));
|
||
|
|
||
|
ret = iwmct_send_hcmd(priv, (u8 *)&cmd, cmdlen);
|
||
|
if (ret) {
|
||
|
LOG_ERROR(priv, DEBUGFS,
|
||
|
"Failed to send %d bytes of fwcmd, ret=%zd\n",
|
||
|
cmdlen, ret);
|
||
|
goto exit;
|
||
|
} else
|
||
|
LOG_INFO(priv, DEBUGFS, "fwcmd sent (%d bytes)\n", cmdlen);
|
||
|
|
||
|
ret = count;
|
||
|
|
||
|
exit:
|
||
|
kfree(str_buf);
|
||
|
return ret;
|
||
|
}
|
||
|
|