blob: 5dd01586d16dd4cb17e2426217e3f4067113fbd0 [file] [log] [blame]
/**
Copyright (c) 2008 - 2013 Quantenna Communications Inc
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
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.
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.
**/
#ifndef AUTOCONF_INCLUDED
#include <linux/config.h>
#endif
#include <linux/version.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/device.h>
#include "qdrv_features.h"
#include "qdrv_debug.h"
#include "qdrv_control.h"
#include "qdrv_mac.h"
#include "qdrv_soc.h"
#include "qdrv_muc_stats.h"
#include <qtn/bootcfg.h>
#include <asm/board/troubleshoot.h>
/*
* Define boiler plate module stuff
*/
MODULE_DESCRIPTION("802.11 Wireless Driver");
MODULE_AUTHOR("Quantenna Communications Inc., Mats Aretun");
MODULE_LICENSE("GPL");
MODULE_VERSION("1.0");
#define QDRV_DEV_NAME "qdrv"
/* 3.1 times seems safe for MuC (or AuC) crash - found by trial and error */
#define QDRV_CORE_DUMP_COMPRESS_RATIO (310)
struct qdrv_mac *qdrv_device_to_qdrv_mac(struct device *dev)
{
struct qdrv_cb *qcb;
const char *name_of_dev = NULL;
if (dev == NULL) {
return NULL;
}
name_of_dev = dev_name(dev);
if (strcmp(name_of_dev, QDRV_DEV_NAME) != 0) {
return NULL;
}
qcb = (struct qdrv_cb *) dev_get_drvdata(dev);
return &(qcb->macs[0]);
}
static ssize_t qdrv_attr_control_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
int count;
DBGPRINTF(DBG_LL_ALL, QDRV_LF_TRACE, "-->Enter\n");
if((count = qdrv_control_output(dev, buf)) < 0)
{
DBGPRINTF_E("Failed to generate output\n");
DBGPRINTF(DBG_LL_ALL, QDRV_LF_TRACE, "<--Exit\n");
return(0);
}
DBGPRINTF(DBG_LL_ALL, QDRV_LF_TRACE, "<--Exit\n");
return(count);
}
static ssize_t qdrv_attr_control_store(struct device *dev,
struct device_attribute *attr, const char *buf, size_t count)
{
if (count < 1) {
goto out;
}
qdrv_control_input(dev, (char *) buf, (unsigned int) count);
out:
/*
* FIXME: return value should reflect failure if qdrv_control_input
* above said so. And yet busybox echo seems to retry for failure.
* This is not what we expected. Have to make it success here, while
* what we really should fix is busybox echo
*/
return (ssize_t)count;
}
static DEVICE_ATTR(control, 0644,
qdrv_attr_control_show, qdrv_attr_control_store);
/* flag to trigger kernel panic on MuC halt isr */
static int panic_on_muc_halt = 1;
static void qdrv_panic_on_muc_halt_init(void)
{
char buf[32] = {0};
int dev_mode;
if (bootcfg_get_var("dev_mode", buf)) {
if (sscanf(buf, "=%d", &dev_mode) == 1) {
if (dev_mode) {
panic_on_muc_halt = 0;
}
}
}
}
static void muc_death_work(struct work_struct *work)
{
printk(KERN_ERR"Dumping register differences\n");
qdrv_control_dump_active_hwreg();
#if LINUX_VERSION_CODE < KERNEL_VERSION(4,7,0)
arc_save_to_sram_safe_area(QDRV_CORE_DUMP_COMPRESS_RATIO);
#endif
/* panic if appropriate - will eventually restart the system by calling machine_restart() */
if (panic_on_muc_halt) {
panic("MuC has halted; panic_on_muc_halt is %d", panic_on_muc_halt);
}
}
static DECLARE_DELAYED_WORK(muc_death_wq, &muc_death_work);
#define QTN_MUC_DEAD_TIMER (QTN_MPROC_TIMEOUT - HZ) /* Must be less than the mproc timeout */
void qdrv_muc_died_sysfs(void)
{
const unsigned long delay_jiff = QTN_MUC_DEAD_TIMER;
schedule_delayed_work(&muc_death_wq, delay_jiff);
}
static ssize_t show_panic_on_muc_halt(struct device *dev, struct device_attribute *attr, char *buf)
{
return snprintf(buf, PAGE_SIZE, "%d\n", panic_on_muc_halt);
}
static ssize_t store_panic_on_muc_halt(struct device *dev, struct device_attribute *attr,
const char *buf, size_t count)
{
if (count >= 1)
panic_on_muc_halt = (buf[0] == '1');
return count;
}
static DEVICE_ATTR(panic_on_muc_halt, 0644, show_panic_on_muc_halt, store_panic_on_muc_halt);
/* service entry points and corresponding attributes for PHY stats entries in the sys fs */
BIN_ATTR_ACCESS_DECL(qdrv_attr_rssi_phy_stats, filp, p_kobj, p_bin_attr, buf, offset, size)
{
ssize_t size_phy_stats = qdrv_muc_get_size_rssi_phy_stats();
struct qdrv_mac *mac = qdrv_device_to_qdrv_mac((struct device *) p_bin_attr->private);
struct ieee80211com *ic = NULL;
struct qtn_stats *addr_rssi_phy_stats = NULL;
if (mac != NULL) {
struct qdrv_wlan *qw = mac->data;
if (qw != NULL) {
ic = &(qw->ic);
}
}
if (ic == NULL) {
return -1;
}
addr_rssi_phy_stats = qtn_muc_stats_get_addr_latest_stats(mac, ic, MUC_PHY_STATS_RSSI_RCPI_ONLY);
if (addr_rssi_phy_stats == NULL) {
return -1;
}
/*
* avoid buffer overruns ...
* i.e. for command "hexdump /sys/devices/qdrv/rssi_phy_stats",
* size is only 16.
*/
if (size < size_phy_stats) {
size_phy_stats = size;
}
if (offset >= (loff_t) size_phy_stats) {
return 0;
}
memcpy(buf, addr_rssi_phy_stats, size_phy_stats);
return size_phy_stats;
}
static void qdrv_module_release(struct device *dev)
{
DBGPRINTF(DBG_LL_ALL, QDRV_LF_TRACE, "-->Enter\n");
DBGPRINTF(DBG_LL_ALL, QDRV_LF_TRACE, "<--Exit\n");
}
static struct device qdrv_device =
{
#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,30)
.bus_id = QDRV_DEV_NAME,
#endif
.release = qdrv_module_release,
};
struct device *qdrv_soc_get_addr_dev(void)
{
return &qdrv_device;
}
static struct bin_attribute qdrv_show_rssi_phy_stats =
{
.attr = { .name = "rssi_phy_stats", .mode = 0444 },
.private = (void *) &qdrv_device,
.read = qdrv_attr_rssi_phy_stats,
.write = NULL,
.mmap = NULL,
};
static int qdrv_module_create_sysfs_phy_stats(void)
{
if (device_create_bin_file(&qdrv_device, &qdrv_show_rssi_phy_stats) != 0) {
DBGPRINTF_E("Failed to create rssi_phy_stats sysfs file \"%s\"\n",
qdrv_show_rssi_phy_stats.attr.name);
goto rssi_phy_stats_fail;
}
return 0;
rssi_phy_stats_fail:
return -1;
}
static int __init qdrv_module_init(void)
{
void *data;
int size;
DBGPRINTF(DBG_LL_ALL, QDRV_LF_TRACE, "-->Enter\n");
/* Get the size of our private data data structure */
size = qdrv_soc_cb_size();
/* Allocate (zero filled) private memory for our device context */
if ((data = kzalloc(size, GFP_KERNEL)) == NULL) {
DBGPRINTF_E("Failed to allocate %d bytes for private data\n", size);
DBGPRINTF(DBG_LL_ALL, QDRV_LF_TRACE, "<--Exit\n");
return(-ENOMEM);
}
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,30)
dev_set_name(&qdrv_device, QDRV_DEV_NAME);
#endif
/* Attach the private memory to the device */
dev_set_drvdata(&qdrv_device, data);
qdrv_panic_on_muc_halt_init();
if (device_register(&qdrv_device) != 0) {
DBGPRINTF_E("Failed to register \"%s\"\n", QDRV_DEV_NAME);
goto device_reg_fail;
}
if (device_create_file(&qdrv_device, &dev_attr_control) != 0) {
DBGPRINTF_E("Failed to create control sysfs file \"%s\"\n", QDRV_DEV_NAME);
goto control_sysfs_fail;
}
if (device_create_file(&qdrv_device, &dev_attr_panic_on_muc_halt) != 0) {
DBGPRINTF_E("Failed to create panic_on_muc_halt sysfs file \"%s\"\n", QDRV_DEV_NAME);
goto muc_halt_sysfs_fail;
}
/* Initialize control interface */
if (qdrv_control_init(&qdrv_device) < 0) {
DBGPRINTF_E("Failed to initialize driver control interface\n");
goto control_init_fail;
}
if (qdrv_module_create_sysfs_phy_stats() < 0) {
/* no cleanup and no error reporting required if failure */
goto control_init_fail;
}
DBGPRINTF(DBG_LL_ALL, QDRV_LF_TRACE, "<--Exit\n");
return 0;
control_init_fail:
device_remove_file(&qdrv_device, &dev_attr_panic_on_muc_halt);
muc_halt_sysfs_fail:
device_remove_file(&qdrv_device, &dev_attr_control);
control_sysfs_fail:
device_unregister(&qdrv_device);
device_reg_fail:
kfree(data);
DBGPRINTF(DBG_LL_ALL, QDRV_LF_TRACE, "<--Exit\n");
return -1;
}
static void __exit qdrv_module_exit(void)
{
void *data;
DBGPRINTF(DBG_LL_ALL, QDRV_LF_TRACE, "-->Enter\n");
/* Cleanup in reverse order */
/* Start with executing a stop command */
if (qdrv_control_input(&qdrv_device, "stop", 4) < 0) {
DBGPRINTF_E("Failed to execute stop command\n");
}
/* Exit the control interface */
if (qdrv_control_exit(&qdrv_device) < 0) {
DBGPRINTF_E("Failed to exit driver control interface\n");
}
/* Get the private device data */
data = dev_get_drvdata(&qdrv_device);
device_remove_bin_file(&qdrv_device, &qdrv_show_rssi_phy_stats);
device_remove_file(&qdrv_device, &dev_attr_panic_on_muc_halt);
device_remove_file(&qdrv_device, &dev_attr_control);
device_unregister(&qdrv_device);
/* Free the private memory */
kfree(data);
DBGPRINTF(DBG_LL_ALL, QDRV_LF_TRACE, "<--Exit\n");
}
module_init(qdrv_module_init);
module_exit(qdrv_module_exit);