diff options
Diffstat (limited to 'drivers/misc/iwmc3200top/log.c')
| -rw-r--r-- | drivers/misc/iwmc3200top/log.c | 347 | 
1 files changed, 347 insertions, 0 deletions
diff --git a/drivers/misc/iwmc3200top/log.c b/drivers/misc/iwmc3200top/log.c new file mode 100644 index 00000000000..d569279698f --- /dev/null +++ b/drivers/misc/iwmc3200top/log.c @@ -0,0 +1,347 @@ +/* + * 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/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; +} +  |