1420 lines
		
	
	
		
			34 KiB
		
	
	
	
		
			C
		
	
	
		
			Executable File
		
	
	
	
	
			
		
		
	
	
			1420 lines
		
	
	
		
			34 KiB
		
	
	
	
		
			C
		
	
	
		
			Executable File
		
	
	
	
	
| /*
 | |
|  * Broadcom Dongle Host Driver (DHD), Linux-specific network interface
 | |
|  * Basically selected code segments from usb-cdc.c and usb-rndis.c
 | |
|  *
 | |
|  * Copyright (C) 1999-2019, Broadcom.
 | |
|  *
 | |
|  *      Unless you and Broadcom execute a separate written software license
 | |
|  * agreement governing use of this software, this software is licensed to you
 | |
|  * under the terms of the GNU General Public License version 2 (the "GPL"),
 | |
|  * available at http://www.broadcom.com/licenses/GPLv2.php, with the
 | |
|  * following added to such license:
 | |
|  *
 | |
|  *      As a special exception, the copyright holders of this software give you
 | |
|  * permission to link this software with independent modules, and to copy and
 | |
|  * distribute the resulting executable under terms of your choice, provided that
 | |
|  * you also meet, for each linked independent module, the terms and conditions of
 | |
|  * the license of that module.  An independent module is a module which is not
 | |
|  * derived from this software.  The special exception does not apply to any
 | |
|  * modifications of the software.
 | |
|  *
 | |
|  *      Notwithstanding the above, under no circumstances may you combine this
 | |
|  * software in any way with any other Broadcom software provided under a license
 | |
|  * other than the GPL, without Broadcom's express prior written consent.
 | |
|  *
 | |
|  *
 | |
|  * <<Broadcom-WL-IPTag/Open:>>
 | |
|  *
 | |
|  * $Id: dhd_linux_exportfs.c 808905 2019-03-11 10:32:39Z $
 | |
|  */
 | |
| #include <linux/kobject.h>
 | |
| #include <linux/proc_fs.h>
 | |
| #include <linux/sysfs.h>
 | |
| #include <osl.h>
 | |
| #include <dhd_dbg.h>
 | |
| #include <dhd_linux_priv.h>
 | |
| #ifdef DHD_ADPS_BAM_EXPORT
 | |
| #include <wl_bam.h>
 | |
| #endif // endif
 | |
| #ifdef CSI_SUPPORT
 | |
| #include <dhd_csi.h>
 | |
| #endif /* CSI_SUPPORT */
 | |
| 
 | |
| #ifdef SHOW_LOGTRACE
 | |
| extern dhd_pub_t* g_dhd_pub;
 | |
| static int dhd_ring_proc_open(struct inode *inode, struct file *file);
 | |
| ssize_t dhd_ring_proc_read(struct file *file, char *buffer, size_t tt, loff_t *loff);
 | |
| 
 | |
| static const struct file_operations dhd_ring_proc_fops = {
 | |
| 	.open = dhd_ring_proc_open,
 | |
| 	.read = dhd_ring_proc_read,
 | |
| 	.release = single_release,
 | |
| };
 | |
| 
 | |
| static int
 | |
| dhd_ring_proc_open(struct inode *inode, struct file *file)
 | |
| {
 | |
| 	int ret = BCME_ERROR;
 | |
| 	if (inode) {
 | |
| #if (LINUX_VERSION_CODE >= KERNEL_VERSION(3, 10, 0))
 | |
| 		ret = single_open(file, 0, PDE_DATA(inode));
 | |
| #else
 | |
| 		/* This feature is not supported for lower kernel versions */
 | |
| 		ret = single_open(file, 0, NULL);
 | |
| #endif // endif
 | |
| 	} else {
 | |
| 		DHD_ERROR(("%s: inode is NULL\n", __FUNCTION__));
 | |
| 	}
 | |
| 	return ret;
 | |
| }
 | |
| 
 | |
| ssize_t
 | |
| dhd_ring_proc_read(struct file *file, char __user *buffer, size_t tt, loff_t *loff)
 | |
| {
 | |
| 	trace_buf_info_t *trace_buf_info;
 | |
| 	int ret = BCME_ERROR;
 | |
| 	dhd_dbg_ring_t *ring = (dhd_dbg_ring_t *)((struct seq_file *)(file->private_data))->private;
 | |
| 
 | |
| 	if (ring == NULL) {
 | |
| 		DHD_ERROR(("%s: ring is NULL\n", __FUNCTION__));
 | |
| 		return ret;
 | |
| 	}
 | |
| 
 | |
| 	ASSERT(g_dhd_pub);
 | |
| 
 | |
| 	trace_buf_info = (trace_buf_info_t *)MALLOCZ(g_dhd_pub->osh, sizeof(trace_buf_info_t));
 | |
| 	if (trace_buf_info) {
 | |
| 		dhd_dbg_read_ring_into_trace_buf(ring, trace_buf_info);
 | |
| 		if (copy_to_user(buffer, (void*)trace_buf_info->buf, MIN(trace_buf_info->size, tt)))
 | |
| 		{
 | |
| 			ret = -EFAULT;
 | |
| 			goto exit;
 | |
| 		}
 | |
| 		if (trace_buf_info->availability == BUF_NOT_AVAILABLE)
 | |
| 			ret = BUF_NOT_AVAILABLE;
 | |
| 		else
 | |
| 			ret = trace_buf_info->size;
 | |
| 	} else
 | |
| 		DHD_ERROR(("Memory allocation Failed\n"));
 | |
| 
 | |
| exit:
 | |
| 	if (trace_buf_info) {
 | |
| 		MFREE(g_dhd_pub->osh, trace_buf_info, sizeof(trace_buf_info_t));
 | |
| 	}
 | |
| 	return ret;
 | |
| }
 | |
| 
 | |
| void
 | |
| dhd_dbg_ring_proc_create(dhd_pub_t *dhdp)
 | |
| {
 | |
| #ifdef DEBUGABILITY
 | |
| 	dhd_dbg_ring_t *dbg_verbose_ring = NULL;
 | |
| 
 | |
| 	dbg_verbose_ring = dhd_dbg_get_ring_from_ring_id(dhdp, FW_VERBOSE_RING_ID);
 | |
| 	if (dbg_verbose_ring) {
 | |
| 		if (!proc_create_data("dhd_trace", S_IRUSR, NULL, &dhd_ring_proc_fops,
 | |
| 			dbg_verbose_ring)) {
 | |
| 			DHD_ERROR(("Failed to create /proc/dhd_trace procfs interface\n"));
 | |
| 		} else {
 | |
| 			DHD_ERROR(("Created /proc/dhd_trace procfs interface\n"));
 | |
| 		}
 | |
| 	} else {
 | |
| 		DHD_ERROR(("dbg_verbose_ring is NULL, /proc/dhd_trace not created\n"));
 | |
| 	}
 | |
| #endif /* DEBUGABILITY */
 | |
| 
 | |
| #ifdef EWP_ECNTRS_LOGGING
 | |
| 	if (!proc_create_data("dhd_ecounters", S_IRUSR, NULL, &dhd_ring_proc_fops,
 | |
| 		dhdp->ecntr_dbg_ring)) {
 | |
| 		DHD_ERROR(("Failed to create /proc/dhd_ecounters procfs interface\n"));
 | |
| 	} else {
 | |
| 		DHD_ERROR(("Created /proc/dhd_ecounters procfs interface\n"));
 | |
| 	}
 | |
| #endif /* EWP_ECNTRS_LOGGING */
 | |
| 
 | |
| #ifdef EWP_RTT_LOGGING
 | |
| 	if (!proc_create_data("dhd_rtt", S_IRUSR, NULL, &dhd_ring_proc_fops,
 | |
| 		dhdp->rtt_dbg_ring)) {
 | |
| 		DHD_ERROR(("Failed to create /proc/dhd_rtt procfs interface\n"));
 | |
| 	} else {
 | |
| 		DHD_ERROR(("Created /proc/dhd_rtt procfs interface\n"));
 | |
| 	}
 | |
| #endif /* EWP_RTT_LOGGING */
 | |
| }
 | |
| 
 | |
| void
 | |
| dhd_dbg_ring_proc_destroy(dhd_pub_t *dhdp)
 | |
| {
 | |
| #ifdef DEBUGABILITY
 | |
| 	remove_proc_entry("dhd_trace", NULL);
 | |
| #endif /* DEBUGABILITY */
 | |
| 
 | |
| #ifdef EWP_ECNTRS_LOGGING
 | |
| 	remove_proc_entry("dhd_ecounters", NULL);
 | |
| #endif /* EWP_ECNTRS_LOGGING */
 | |
| 
 | |
| #ifdef EWP_RTT_LOGGING
 | |
| 	remove_proc_entry("dhd_rtt", NULL);
 | |
| #endif /* EWP_RTT_LOGGING */
 | |
| 
 | |
| }
 | |
| #endif /* SHOW_LOGTRACE */
 | |
| 
 | |
| /* ----------------------------------------------------------------------------
 | |
|  * Infrastructure code for sysfs interface support for DHD
 | |
|  *
 | |
|  * What is sysfs interface?
 | |
|  * https://www.kernel.org/doc/Documentation/filesystems/sysfs.txt
 | |
|  *
 | |
|  * Why sysfs interface?
 | |
|  * This is the Linux standard way of changing/configuring Run Time parameters
 | |
|  * for a driver. We can use this interface to control "linux" specific driver
 | |
|  * parameters.
 | |
|  *
 | |
|  * -----------------------------------------------------------------------------
 | |
|  */
 | |
| 
 | |
| #if defined(DHD_TRACE_WAKE_LOCK)
 | |
| extern atomic_t trace_wklock_onoff;
 | |
| 
 | |
| /* Function to show the history buffer */
 | |
| static ssize_t
 | |
| show_wklock_trace(struct dhd_info *dev, char *buf)
 | |
| {
 | |
| 	ssize_t ret = 0;
 | |
| 	dhd_info_t *dhd = (dhd_info_t *)dev;
 | |
| 
 | |
| 	buf[ret] = '\n';
 | |
| 	buf[ret+1] = 0;
 | |
| 
 | |
| 	dhd_wk_lock_stats_dump(&dhd->pub);
 | |
| 	return ret+1;
 | |
| }
 | |
| 
 | |
| /* Function to enable/disable wakelock trace */
 | |
| static ssize_t
 | |
| wklock_trace_onoff(struct dhd_info *dev, const char *buf, size_t count)
 | |
| {
 | |
| 	unsigned long onoff;
 | |
| 	dhd_info_t *dhd = (dhd_info_t *)dev;
 | |
| 	BCM_REFERENCE(dhd);
 | |
| 
 | |
| 	onoff = bcm_strtoul(buf, NULL, 10);
 | |
| 	if (onoff != 0 && onoff != 1) {
 | |
| 		return -EINVAL;
 | |
| 	}
 | |
| 
 | |
| 	atomic_set(&trace_wklock_onoff, onoff);
 | |
| 	if (atomic_read(&trace_wklock_onoff)) {
 | |
| 		printk("ENABLE WAKLOCK TRACE\n");
 | |
| 	} else {
 | |
| 		printk("DISABLE WAKELOCK TRACE\n");
 | |
| 	}
 | |
| 
 | |
| 	return (ssize_t)(onoff+1);
 | |
| }
 | |
| #endif /* DHD_TRACE_WAKE_LOCK */
 | |
| 
 | |
| #if defined(DHD_LB_TXP)
 | |
| static ssize_t
 | |
| show_lbtxp(struct dhd_info *dev, char *buf)
 | |
| {
 | |
| 	ssize_t ret = 0;
 | |
| 	unsigned long onoff;
 | |
| 	dhd_info_t *dhd = (dhd_info_t *)dev;
 | |
| 
 | |
| 	onoff = atomic_read(&dhd->lb_txp_active);
 | |
| 	ret = scnprintf(buf, PAGE_SIZE - 1, "%lu \n",
 | |
| 		onoff);
 | |
| 	return ret;
 | |
| }
 | |
| 
 | |
| static ssize_t
 | |
| lbtxp_onoff(struct dhd_info *dev, const char *buf, size_t count)
 | |
| {
 | |
| 	unsigned long onoff;
 | |
| 	dhd_info_t *dhd = (dhd_info_t *)dev;
 | |
| 	int i;
 | |
| 
 | |
| 	onoff = bcm_strtoul(buf, NULL, 10);
 | |
| 
 | |
| 	sscanf(buf, "%lu", &onoff);
 | |
| 	if (onoff != 0 && onoff != 1) {
 | |
| 		return -EINVAL;
 | |
| 	}
 | |
| 	atomic_set(&dhd->lb_txp_active, onoff);
 | |
| 
 | |
| 	/* Since the scheme is changed clear the counters */
 | |
| 	for (i = 0; i < NR_CPUS; i++) {
 | |
| 		DHD_LB_STATS_CLR(dhd->txp_percpu_run_cnt[i]);
 | |
| 		DHD_LB_STATS_CLR(dhd->tx_start_percpu_run_cnt[i]);
 | |
| 	}
 | |
| 
 | |
| 	return count;
 | |
| }
 | |
| 
 | |
| #endif /* DHD_LB_TXP */
 | |
| 
 | |
| #if defined(DHD_LB_RXP)
 | |
| static ssize_t
 | |
| show_lbrxp(struct dhd_info *dev, char *buf)
 | |
| {
 | |
| 	ssize_t ret = 0;
 | |
| 	unsigned long onoff;
 | |
| 	dhd_info_t *dhd = (dhd_info_t *)dev;
 | |
| 
 | |
| 	onoff = atomic_read(&dhd->lb_rxp_active);
 | |
| 	ret = scnprintf(buf, PAGE_SIZE - 1, "%lu \n",
 | |
| 		onoff);
 | |
| 	return ret;
 | |
| }
 | |
| 
 | |
| static ssize_t
 | |
| lbrxp_onoff(struct dhd_info *dev, const char *buf, size_t count)
 | |
| {
 | |
| 	unsigned long onoff;
 | |
| 	dhd_info_t *dhd = (dhd_info_t *)dev;
 | |
| 	int i, j;
 | |
| 
 | |
| 	onoff = bcm_strtoul(buf, NULL, 10);
 | |
| 
 | |
| 	sscanf(buf, "%lu", &onoff);
 | |
| 	if (onoff != 0 && onoff != 1) {
 | |
| 		return -EINVAL;
 | |
| 	}
 | |
| 	atomic_set(&dhd->lb_rxp_active, onoff);
 | |
| 
 | |
| 	/* Since the scheme is changed clear the counters */
 | |
| 	for (i = 0; i < NR_CPUS; i++) {
 | |
| 		DHD_LB_STATS_CLR(dhd->napi_percpu_run_cnt[i]);
 | |
| 		for (j = 0; j < HIST_BIN_SIZE; j++) {
 | |
| 			DHD_LB_STATS_CLR(dhd->napi_rx_hist[j][i]);
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	return count;
 | |
| }
 | |
| #endif /* DHD_LB_RXP */
 | |
| 
 | |
| #ifdef DHD_LOG_DUMP
 | |
| extern int logdump_periodic_flush;
 | |
| extern int logdump_ecntr_enable;
 | |
| static ssize_t
 | |
| show_logdump_periodic_flush(struct dhd_info *dev, char *buf)
 | |
| {
 | |
| 	ssize_t ret = 0;
 | |
| 	unsigned long val;
 | |
| 
 | |
| 	val = logdump_periodic_flush;
 | |
| 	ret = scnprintf(buf, PAGE_SIZE - 1, "%lu \n", val);
 | |
| 	return ret;
 | |
| }
 | |
| 
 | |
| static ssize_t
 | |
| logdump_periodic_flush_onoff(struct dhd_info *dev, const char *buf, size_t count)
 | |
| {
 | |
| 	unsigned long val;
 | |
| 
 | |
| 	val = bcm_strtoul(buf, NULL, 10);
 | |
| 
 | |
| 	sscanf(buf, "%lu", &val);
 | |
| 	if (val != 0 && val != 1) {
 | |
| 		 return -EINVAL;
 | |
| 	}
 | |
| 	logdump_periodic_flush = val;
 | |
| 	return count;
 | |
| }
 | |
| 
 | |
| static ssize_t
 | |
| show_logdump_ecntr(struct dhd_info *dev, char *buf)
 | |
| {
 | |
| 	ssize_t ret = 0;
 | |
| 	unsigned long val;
 | |
| 
 | |
| 	val = logdump_ecntr_enable;
 | |
| 	ret = scnprintf(buf, PAGE_SIZE - 1, "%lu \n", val);
 | |
| 	return ret;
 | |
| }
 | |
| 
 | |
| static ssize_t
 | |
| logdump_ecntr_onoff(struct dhd_info *dev, const char *buf, size_t count)
 | |
| {
 | |
| 	unsigned long val;
 | |
| 
 | |
| 	val = bcm_strtoul(buf, NULL, 10);
 | |
| 
 | |
| 	sscanf(buf, "%lu", &val);
 | |
| 	if (val != 0 && val != 1) {
 | |
| 		 return -EINVAL;
 | |
| 	}
 | |
| 	logdump_ecntr_enable = val;
 | |
| 	return count;
 | |
| }
 | |
| 
 | |
| #endif /* DHD_LOG_DUMP */
 | |
| 
 | |
| extern uint enable_ecounter;
 | |
| static ssize_t
 | |
| show_enable_ecounter(struct dhd_info *dev, char *buf)
 | |
| {
 | |
| 	ssize_t ret = 0;
 | |
| 	unsigned long onoff;
 | |
| 
 | |
| 	onoff = enable_ecounter;
 | |
| 	ret = scnprintf(buf, PAGE_SIZE - 1, "%lu \n",
 | |
| 		onoff);
 | |
| 	return ret;
 | |
| }
 | |
| 
 | |
| static ssize_t
 | |
| ecounter_onoff(struct dhd_info *dev, const char *buf, size_t count)
 | |
| {
 | |
| 	unsigned long onoff;
 | |
| 	dhd_info_t *dhd = (dhd_info_t *)dev;
 | |
| 	dhd_pub_t *dhdp;
 | |
| 
 | |
| 	if (!dhd) {
 | |
| 		DHD_ERROR(("%s: dhd is NULL\n", __FUNCTION__));
 | |
| 		return count;
 | |
| 	}
 | |
| 	dhdp = &dhd->pub;
 | |
| 	if (!FW_SUPPORTED(dhdp, ecounters)) {
 | |
| 		DHD_ERROR(("%s: ecounters not supported by FW\n", __FUNCTION__));
 | |
| 		return count;
 | |
| 	}
 | |
| 
 | |
| 	onoff = bcm_strtoul(buf, NULL, 10);
 | |
| 
 | |
| 	sscanf(buf, "%lu", &onoff);
 | |
| 	if (onoff != 0 && onoff != 1) {
 | |
| 		return -EINVAL;
 | |
| 	}
 | |
| 
 | |
| 	if (enable_ecounter == onoff) {
 | |
| 		DHD_ERROR(("%s: ecounters already %d\n", __FUNCTION__, enable_ecounter));
 | |
| 		return count;
 | |
| 	}
 | |
| 
 | |
| 	enable_ecounter = onoff;
 | |
| 	dhd_ecounter_configure(dhdp, enable_ecounter);
 | |
| 
 | |
| 	return count;
 | |
| }
 | |
| 
 | |
| /*
 | |
|  * Generic Attribute Structure for DHD.
 | |
|  * If we have to add a new sysfs entry under /sys/bcm-dhd/, we have
 | |
|  * to instantiate an object of type dhd_attr,  populate it with
 | |
|  * the required show/store functions (ex:- dhd_attr_cpumask_primary)
 | |
|  * and add the object to default_attrs[] array, that gets registered
 | |
|  * to the kobject of dhd (named bcm-dhd).
 | |
|  */
 | |
| 
 | |
| struct dhd_attr {
 | |
| 	struct attribute attr;
 | |
| 	ssize_t(*show)(struct dhd_info *, char *);
 | |
| 	ssize_t(*store)(struct dhd_info *, const char *, size_t count);
 | |
| };
 | |
| 
 | |
| #if defined(DHD_TRACE_WAKE_LOCK)
 | |
| static struct dhd_attr dhd_attr_wklock =
 | |
| 	__ATTR(wklock_trace, 0660, show_wklock_trace, wklock_trace_onoff);
 | |
| #endif /* defined(DHD_TRACE_WAKE_LOCK */
 | |
| 
 | |
| #if defined(DHD_LB_TXP)
 | |
| static struct dhd_attr dhd_attr_lbtxp =
 | |
| 	__ATTR(lbtxp, 0660, show_lbtxp, lbtxp_onoff);
 | |
| #endif /* DHD_LB_TXP */
 | |
| 
 | |
| #if defined(DHD_LB_RXP)
 | |
| static struct dhd_attr dhd_attr_lbrxp =
 | |
| 	__ATTR(lbrxp, 0660, show_lbrxp, lbrxp_onoff);
 | |
| #endif /* DHD_LB_RXP */
 | |
| 
 | |
| #ifdef DHD_LOG_DUMP
 | |
| static struct dhd_attr dhd_attr_logdump_periodic_flush =
 | |
|      __ATTR(logdump_periodic_flush, 0660, show_logdump_periodic_flush,
 | |
| 		logdump_periodic_flush_onoff);
 | |
| static struct dhd_attr dhd_attr_logdump_ecntr =
 | |
| 	__ATTR(logdump_ecntr_enable, 0660, show_logdump_ecntr,
 | |
| 		logdump_ecntr_onoff);
 | |
| #endif /* DHD_LOG_DUMP */
 | |
| 
 | |
| static struct dhd_attr dhd_attr_ecounters =
 | |
| 	__ATTR(ecounters, 0660, show_enable_ecounter, ecounter_onoff);
 | |
| 
 | |
| /* Attribute object that gets registered with "bcm-dhd" kobject tree */
 | |
| static struct attribute *default_attrs[] = {
 | |
| #if defined(DHD_TRACE_WAKE_LOCK)
 | |
| 	&dhd_attr_wklock.attr,
 | |
| #endif // endif
 | |
| #if defined(DHD_LB_TXP)
 | |
| 	&dhd_attr_lbtxp.attr,
 | |
| #endif /* DHD_LB_TXP */
 | |
| #if defined(DHD_LB_RXP)
 | |
| 	&dhd_attr_lbrxp.attr,
 | |
| #endif /* DHD_LB_RXP */
 | |
| #ifdef DHD_LOG_DUMP
 | |
| 	&dhd_attr_logdump_periodic_flush.attr,
 | |
| 	&dhd_attr_logdump_ecntr.attr,
 | |
| #endif // endif
 | |
| 	&dhd_attr_ecounters.attr,
 | |
| 	NULL
 | |
| };
 | |
| 
 | |
| #define to_dhd(k) container_of(k, struct dhd_info, dhd_kobj)
 | |
| #define to_attr(a) container_of(a, struct dhd_attr, attr)
 | |
| 
 | |
| /*
 | |
|  * bcm-dhd kobject show function, the "attr" attribute specifices to which
 | |
|  * node under "bcm-dhd" the show function is called.
 | |
|  */
 | |
| static ssize_t dhd_show(struct kobject *kobj, struct attribute *attr, char *buf)
 | |
| {
 | |
| #if defined(STRICT_GCC_WARNINGS) && defined(__GNUC__)
 | |
| #pragma GCC diagnostic push
 | |
| #pragma GCC diagnostic ignored "-Wcast-qual"
 | |
| #endif // endif
 | |
| 	dhd_info_t *dhd = to_dhd(kobj);
 | |
| 	struct dhd_attr *d_attr = to_attr(attr);
 | |
| #if defined(STRICT_GCC_WARNINGS) && defined(__GNUC__)
 | |
| #pragma GCC diagnostic pop
 | |
| #endif // endif
 | |
| 	int ret;
 | |
| 
 | |
| 	if (d_attr->show)
 | |
| 		ret = d_attr->show(dhd, buf);
 | |
| 	else
 | |
| 		ret = -EIO;
 | |
| 
 | |
| 	return ret;
 | |
| }
 | |
| 
 | |
| /*
 | |
|  * bcm-dhd kobject show function, the "attr" attribute specifices to which
 | |
|  * node under "bcm-dhd" the store function is called.
 | |
|  */
 | |
| static ssize_t dhd_store(struct kobject *kobj, struct attribute *attr,
 | |
| 	const char *buf, size_t count)
 | |
| {
 | |
| #if defined(STRICT_GCC_WARNINGS) && defined(__GNUC__)
 | |
| #pragma GCC diagnostic push
 | |
| #pragma GCC diagnostic ignored "-Wcast-qual"
 | |
| #endif // endif
 | |
| 	dhd_info_t *dhd = to_dhd(kobj);
 | |
| 	struct dhd_attr *d_attr = to_attr(attr);
 | |
| #if defined(STRICT_GCC_WARNINGS) && defined(__GNUC__)
 | |
| #pragma GCC diagnostic pop
 | |
| #endif // endif
 | |
| 	int ret;
 | |
| 
 | |
| 	if (d_attr->store)
 | |
| 		ret = d_attr->store(dhd, buf, count);
 | |
| 	else
 | |
| 		ret = -EIO;
 | |
| 
 | |
| 	return ret;
 | |
| 
 | |
| }
 | |
| 
 | |
| static struct sysfs_ops dhd_sysfs_ops = {
 | |
| 	.show = dhd_show,
 | |
| 	.store = dhd_store,
 | |
| };
 | |
| 
 | |
| static struct kobj_type dhd_ktype = {
 | |
| 	.sysfs_ops = &dhd_sysfs_ops,
 | |
| 	.default_attrs = default_attrs,
 | |
| };
 | |
| 
 | |
| #ifdef DHD_MAC_ADDR_EXPORT
 | |
| struct ether_addr sysfs_mac_addr;
 | |
| static ssize_t
 | |
| show_mac_addr(struct dhd_info *dev, char *buf)
 | |
| {
 | |
| 	ssize_t ret = 0;
 | |
| 
 | |
| 	ret = scnprintf(buf, PAGE_SIZE - 1, MACF,
 | |
| 		(uint32)sysfs_mac_addr.octet[0], (uint32)sysfs_mac_addr.octet[1],
 | |
| 		(uint32)sysfs_mac_addr.octet[2], (uint32)sysfs_mac_addr.octet[3],
 | |
| 		(uint32)sysfs_mac_addr.octet[4], (uint32)sysfs_mac_addr.octet[5]);
 | |
| 
 | |
| 	return ret;
 | |
| }
 | |
| 
 | |
| static ssize_t
 | |
| set_mac_addr(struct dhd_info *dev, const char *buf, size_t count)
 | |
| {
 | |
| 	if (!bcm_ether_atoe(buf, &sysfs_mac_addr)) {
 | |
| 		DHD_ERROR(("Invalid Mac Address \n"));
 | |
| 		return -EINVAL;
 | |
| 	}
 | |
| 
 | |
| 	DHD_ERROR(("Mac Address set with "MACDBG"\n", MAC2STRDBG(&sysfs_mac_addr)));
 | |
| 
 | |
| 	return count;
 | |
| }
 | |
| 
 | |
| static struct dhd_attr dhd_attr_cntl_macaddr =
 | |
| 	__ATTR(mac_addr, 0660, show_mac_addr, set_mac_addr);
 | |
| #endif /* DHD_MAC_ADDR_EXPORT */
 | |
| 
 | |
| #ifdef DHD_FW_COREDUMP
 | |
| 
 | |
| #define MEMDUMPINFO "/data/misc/wifi/.memdump.info"
 | |
| 
 | |
| uint32
 | |
| get_mem_val_from_file(void)
 | |
| {
 | |
| 	struct file *fp = NULL;
 | |
| 	uint32 mem_val = DUMP_MEMFILE_MAX;
 | |
| 	char *p_mem_val = NULL;
 | |
| 	char *filepath = MEMDUMPINFO;
 | |
| 	int ret = 0;
 | |
| 
 | |
| 	/* Read memdump info from the file */
 | |
| 	fp = filp_open(filepath, O_RDONLY, 0);
 | |
| 	if (IS_ERR(fp)) {
 | |
| 		DHD_ERROR(("%s: File [%s] doesn't exist\n", __FUNCTION__, filepath));
 | |
| #if defined(CONFIG_X86)
 | |
| 		/* Check if it is Live Brix Image */
 | |
| 		if (strcmp(filepath, MEMDUMPINFO_LIVE) != 0) {
 | |
| 			goto done;
 | |
| 		}
 | |
| 		/* Try if it is Installed Brix Image */
 | |
| 		filepath = MEMDUMPINFO_INST;
 | |
| 		DHD_ERROR(("%s: Try File [%s]\n", __FUNCTION__, filepath));
 | |
| 		fp = filp_open(filepath, O_RDONLY, 0);
 | |
| 		if (IS_ERR(fp)) {
 | |
| 			DHD_ERROR(("%s: File [%s] doesn't exist\n", __FUNCTION__, filepath));
 | |
| 			goto done;
 | |
| 		}
 | |
| #else /* Non Brix Android platform */
 | |
| 		goto done;
 | |
| #endif /* CONFIG_X86 && OEM_ANDROID */
 | |
| 	}
 | |
| 
 | |
| 	/* Handle success case */
 | |
| 	ret = compat_kernel_read(fp, 0, (char *)&mem_val, sizeof(uint32));
 | |
| 	if (ret < 0) {
 | |
| 		DHD_ERROR(("%s: File read error, ret=%d\n", __FUNCTION__, ret));
 | |
| 		filp_close(fp, NULL);
 | |
| 		goto done;
 | |
| 	}
 | |
| 
 | |
| 	p_mem_val = (char*)&mem_val;
 | |
| 	p_mem_val[sizeof(uint32) - 1] = '\0';
 | |
| 	mem_val = bcm_atoi(p_mem_val);
 | |
| 
 | |
| 	filp_close(fp, NULL);
 | |
| 
 | |
| done:
 | |
| 	return mem_val;
 | |
| }
 | |
| 
 | |
| void dhd_get_memdump_info(dhd_pub_t *dhd)
 | |
| {
 | |
| #ifndef DHD_EXPORT_CNTL_FILE
 | |
| 	uint32 mem_val = DUMP_MEMFILE_MAX;
 | |
| 
 | |
| 	mem_val = get_mem_val_from_file();
 | |
| 	if (mem_val != DUMP_MEMFILE_MAX)
 | |
| 		dhd->memdump_enabled = mem_val;
 | |
| #ifdef DHD_INIT_DEFAULT_MEMDUMP
 | |
| 	if (mem_val == 0 || mem_val == DUMP_MEMFILE_MAX)
 | |
| 		mem_val = DUMP_MEMFILE_BUGON;
 | |
| #endif /* DHD_INIT_DEFAULT_MEMDUMP */
 | |
| #else
 | |
| #ifdef DHD_INIT_DEFAULT_MEMDUMP
 | |
| 	if (dhd->memdump_enabled == 0 || dhd->memdump_enabled == DUMP_MEMFILE_MAX)
 | |
| 		dhd->memdump_enabled = DUMP_MEMFILE_BUGON;
 | |
| #endif /* DHD_INIT_DEFAULT_MEMDUMP */
 | |
| #endif /* !DHD_EXPORT_CNTL_FILE */
 | |
| 	DHD_ERROR(("%s: MEMDUMP ENABLED = %d\n", __FUNCTION__, dhd->memdump_enabled));
 | |
| }
 | |
| 
 | |
| #ifdef DHD_EXPORT_CNTL_FILE
 | |
| static ssize_t
 | |
| show_memdump_info(struct dhd_info *dev, char *buf)
 | |
| {
 | |
| 	ssize_t ret = 0;
 | |
| 	dhd_pub_t *dhdp;
 | |
| 
 | |
| 	if (!dev) {
 | |
| 		DHD_ERROR(("%s: dhd is NULL\n", __FUNCTION__));
 | |
| 		return ret;
 | |
| 	}
 | |
| 
 | |
| 	dhdp = &dev->pub;
 | |
| 	ret = scnprintf(buf, PAGE_SIZE -1, "%u\n", dhdp->memdump_enabled);
 | |
| 	return ret;
 | |
| }
 | |
| 
 | |
| static ssize_t
 | |
| set_memdump_info(struct dhd_info *dev, const char *buf, size_t count)
 | |
| {
 | |
| 	unsigned long memval;
 | |
| 	dhd_pub_t *dhdp;
 | |
| 
 | |
| 	if (!dev) {
 | |
| 		DHD_ERROR(("%s: dhd is NULL\n", __FUNCTION__));
 | |
| 		return count;
 | |
| 	}
 | |
| 	dhdp = &dev->pub;
 | |
| 
 | |
| 	memval = bcm_strtoul(buf, NULL, 10);
 | |
| 	sscanf(buf, "%lu", &memval);
 | |
| 
 | |
| 	dhdp->memdump_enabled = (uint32)memval;
 | |
| 
 | |
| 	DHD_ERROR(("%s: MEMDUMP ENABLED = %iu\n", __FUNCTION__, dhdp->memdump_enabled));
 | |
| 	return count;
 | |
| }
 | |
| 
 | |
| static struct dhd_attr dhd_attr_cntl_memdump =
 | |
| 	__ATTR(memdump, 0660, show_memdump_info, set_memdump_info);
 | |
| #endif /* DHD_EXPORT_CNTL_FILE */
 | |
| #endif /* DHD_FW_COREDUMP */
 | |
| 
 | |
| #ifdef BCMASSERT_LOG
 | |
| #define ASSERTINFO "/data/misc/wifi/.assert.info"
 | |
| int
 | |
| get_assert_val_from_file(void)
 | |
| {
 | |
| 	struct file *fp = NULL;
 | |
| 	char *filepath = ASSERTINFO;
 | |
| 	char *p_mem_val = NULL;
 | |
| 	int mem_val = -1;
 | |
| 
 | |
| 	/*
 | |
| 	 * Read assert info from the file
 | |
| 	 * 0: Trigger Kernel crash by panic()
 | |
| 	 * 1: Print out the logs and don't trigger Kernel panic. (default)
 | |
| 	 * 2: Trigger Kernel crash by BUG()
 | |
| 	 * File doesn't exist: Keep default value (1).
 | |
| 	 */
 | |
| 	fp = filp_open(filepath, O_RDONLY, 0);
 | |
| 	if (IS_ERR(fp)) {
 | |
| 		DHD_ERROR(("%s: File [%s] doesn't exist\n", __FUNCTION__, filepath));
 | |
| 	} else {
 | |
| 		int ret = compat_kernel_read(fp, 0, (char *)&mem_val, sizeof(uint32));
 | |
| 		if (ret < 0) {
 | |
| 			DHD_ERROR(("%s: File read error, ret=%d\n", __FUNCTION__, ret));
 | |
| 		} else {
 | |
| 			p_mem_val = (char *)&mem_val;
 | |
| 			p_mem_val[sizeof(uint32) - 1] = '\0';
 | |
| 			mem_val = bcm_atoi(p_mem_val);
 | |
| 			DHD_ERROR(("%s: ASSERT ENABLED = %d\n", __FUNCTION__, mem_val));
 | |
| 		}
 | |
| 		filp_close(fp, NULL);
 | |
| 	}
 | |
| 
 | |
| 	mem_val = (mem_val >= 0) ? mem_val : 0;
 | |
| 	return mem_val;
 | |
| }
 | |
| 
 | |
| void dhd_get_assert_info(dhd_pub_t *dhd)
 | |
| {
 | |
| #ifndef DHD_EXPORT_CNTL_FILE
 | |
| 	int mem_val = -1;
 | |
| 
 | |
| 	mem_val = get_assert_val_from_file();
 | |
| 
 | |
| 	g_assert_type = mem_val;
 | |
| #endif /* !DHD_EXPORT_CNTL_FILE */
 | |
| }
 | |
| 
 | |
| #ifdef DHD_EXPORT_CNTL_FILE
 | |
| static ssize_t
 | |
| show_assert_info(struct dhd_info *dev, char *buf)
 | |
| {
 | |
| 	ssize_t ret = 0;
 | |
| 
 | |
| 	if (!dev) {
 | |
| 		DHD_ERROR(("%s: dhd is NULL\n", __FUNCTION__));
 | |
| 		return ret;
 | |
| 	}
 | |
| 
 | |
| 	ret = scnprintf(buf, PAGE_SIZE -1, "%d\n", g_assert_type);
 | |
| 	return ret;
 | |
| 
 | |
| }
 | |
| 
 | |
| static ssize_t
 | |
| set_assert_info(struct dhd_info *dev, const char *buf, size_t count)
 | |
| {
 | |
| 	unsigned long assert_val;
 | |
| 
 | |
| 	assert_val = bcm_strtoul(buf, NULL, 10);
 | |
| 	sscanf(buf, "%lu", &assert_val);
 | |
| 
 | |
| 	g_assert_type = (uint32)assert_val;
 | |
| 
 | |
| 	DHD_ERROR(("%s: ASSERT ENABLED = %lu\n", __FUNCTION__, assert_val));
 | |
| 	return count;
 | |
| 
 | |
| }
 | |
| 
 | |
| static struct dhd_attr dhd_attr_cntl_assert =
 | |
| 	__ATTR(assert, 0660, show_assert_info, set_assert_info);
 | |
| #endif /* DHD_EXPORT_CNTL_FILE */
 | |
| #endif /* BCMASSERT_LOG */
 | |
| 
 | |
| #ifdef DHD_EXPORT_CNTL_FILE
 | |
| #if defined(WRITE_WLANINFO)
 | |
| static ssize_t
 | |
| show_wifiver_info(struct dhd_info *dev, char *buf)
 | |
| {
 | |
| 	ssize_t ret = 0;
 | |
| 
 | |
| 	ret = scnprintf(buf, PAGE_SIZE -1, "%s", version_info);
 | |
| 	return ret;
 | |
| }
 | |
| 
 | |
| static ssize_t
 | |
| set_wifiver_info(struct dhd_info *dev, const char *buf, size_t count)
 | |
| {
 | |
| 	DHD_ERROR(("Do not set version info\n"));
 | |
| 	return -EINVAL;
 | |
| }
 | |
| 
 | |
| static struct dhd_attr dhd_attr_cntl_wifiver =
 | |
| 	__ATTR(wifiver, 0660, show_wifiver_info, set_wifiver_info);
 | |
| #endif /* WRITE_WLANINFO */
 | |
| 
 | |
| #if defined(USE_CID_CHECK)
 | |
| char cidinfostr[MAX_VNAME_LEN];
 | |
| 
 | |
| static ssize_t
 | |
| show_cid_info(struct dhd_info *dev, char *buf)
 | |
| {
 | |
| 	ssize_t ret = 0;
 | |
| 
 | |
| 	ret = scnprintf(buf, PAGE_SIZE -1, "%s", cidinfostr);
 | |
| 	return ret;
 | |
| }
 | |
| 
 | |
| static ssize_t
 | |
| set_cid_info(struct dhd_info *dev, const char *buf, size_t count)
 | |
| {
 | |
| 	int len = strlen(buf) + 1;
 | |
| 	int maxstrsz;
 | |
| 	maxstrsz = MAX_VNAME_LEN;
 | |
| 
 | |
| 	scnprintf(cidinfostr, ((len > maxstrsz) ? maxstrsz : len), "%s", buf);
 | |
| 	DHD_INFO(("%s : CID info string\n", cidinfostr));
 | |
| 	return count;
 | |
| }
 | |
| 
 | |
| static struct dhd_attr dhd_attr_cntl_cidinfo =
 | |
| 	__ATTR(cid, 0660, show_cid_info, set_cid_info);
 | |
| #endif /* USE_CID_CHECK */
 | |
| 
 | |
| #if defined(GEN_SOFTAP_INFO_FILE)
 | |
| char softapinfostr[SOFTAP_INFO_BUF_SZ];
 | |
| static ssize_t
 | |
| show_softap_info(struct dhd_info *dev, char *buf)
 | |
| {
 | |
| 	ssize_t ret = 0;
 | |
| 
 | |
| 	ret = scnprintf(buf, PAGE_SIZE -1, "%s", softapinfostr);
 | |
| 	return ret;
 | |
| }
 | |
| 
 | |
| static ssize_t
 | |
| set_softap_info(struct dhd_info *dev, const char *buf, size_t count)
 | |
| {
 | |
| 	DHD_ERROR(("Do not set sofap related info\n"));
 | |
| 	return -EINVAL;
 | |
| }
 | |
| 
 | |
| static struct dhd_attr dhd_attr_cntl_softapinfo =
 | |
| 	__ATTR(softap, 0660, show_softap_info, set_softap_info);
 | |
| #endif /* GEN_SOFTAP_INFO_FILE */
 | |
| 
 | |
| #if defined(MIMO_ANT_SETTING)
 | |
| unsigned long antsel;
 | |
| 
 | |
| static ssize_t
 | |
| show_ant_info(struct dhd_info *dev, char *buf)
 | |
| {
 | |
| 	ssize_t ret = 0;
 | |
| 
 | |
| 	ret = scnprintf(buf, PAGE_SIZE -1, "%lu\n", antsel);
 | |
| 	return ret;
 | |
| }
 | |
| 
 | |
| static ssize_t
 | |
| set_ant_info(struct dhd_info *dev, const char *buf, size_t count)
 | |
| {
 | |
| 	unsigned long ant_val;
 | |
| 
 | |
| 	ant_val = bcm_strtoul(buf, NULL, 10);
 | |
| 	sscanf(buf, "%lu", &ant_val);
 | |
| 
 | |
| 	/*
 | |
| 	 * Check value
 | |
| 	 * 0 - Not set, handle same as file not exist
 | |
| 	 */
 | |
| 	if (ant_val > 3) {
 | |
| 		DHD_ERROR(("[WIFI_SEC] %s: Set Invalid value %lu \n",
 | |
| 			__FUNCTION__, ant_val));
 | |
| 		return -EINVAL;
 | |
| 	}
 | |
| 
 | |
| 	antsel = ant_val;
 | |
| 	DHD_ERROR(("[WIFI_SEC] %s: Set Antinfo val = %lu \n", __FUNCTION__, antsel));
 | |
| 	return count;
 | |
| }
 | |
| 
 | |
| static struct dhd_attr dhd_attr_cntl_antinfo =
 | |
| 	__ATTR(ant, 0660, show_ant_info, set_ant_info);
 | |
| #endif /* MIMO_ANT_SETTING */
 | |
| 
 | |
| #ifdef DHD_PM_CONTROL_FROM_FILE
 | |
| extern bool g_pm_control;
 | |
| extern uint32 pmmode_val;
 | |
| static ssize_t
 | |
| show_pm_info(struct dhd_info *dev, char *buf)
 | |
| {
 | |
| 	ssize_t ret = 0;
 | |
| 
 | |
| 	if (!g_pm_control) {
 | |
| 		ret = scnprintf(buf, PAGE_SIZE -1, "PM mode is not set\n");
 | |
| 	} else {
 | |
| 		ret = scnprintf(buf, PAGE_SIZE -1, "%u\n", pmmode_val);
 | |
| 	}
 | |
| 	return ret;
 | |
| }
 | |
| 
 | |
| static ssize_t
 | |
| set_pm_info(struct dhd_info *dev, const char *buf, size_t count)
 | |
| {
 | |
| 	unsigned long pm_val;
 | |
| 
 | |
| 	pm_val = bcm_strtoul(buf, NULL, 10);
 | |
| 	sscanf(buf, "%lu", &pm_val);
 | |
| 
 | |
| 	if (pm_val > 2) {
 | |
| 		DHD_ERROR(("[WIFI_SEC] %s: Set Invalid value %lu \n",
 | |
| 			__FUNCTION__, pm_val));
 | |
| 		return -EINVAL;
 | |
| 	}
 | |
| 
 | |
| 	if (!pm_val) {
 | |
| 		g_pm_control = TRUE;
 | |
| 	} else {
 | |
| 		g_pm_control = FALSE;
 | |
| 	}
 | |
| 
 | |
| 	pmmode_val = (uint32)pm_val;
 | |
| 	DHD_ERROR(("[WIFI_SEC] %s: Set pminfo val = %u\n", __FUNCTION__, pmmode_val));
 | |
| 	return count;
 | |
| }
 | |
| 
 | |
| static struct dhd_attr dhd_attr_cntl_pminfo =
 | |
| 	__ATTR(pm, 0660, show_pm_info, set_pm_info);
 | |
| #endif /* DHD_PM_CONTROL_FROM_FILE */
 | |
| 
 | |
| #ifdef LOGTRACE_FROM_FILE
 | |
| unsigned long logtrace_val = 1;
 | |
| 
 | |
| static ssize_t
 | |
| show_logtrace_info(struct dhd_info *dev, char *buf)
 | |
| {
 | |
| 	ssize_t ret = 0;
 | |
| 
 | |
| 	ret = scnprintf(buf, PAGE_SIZE -1, "%lu\n", logtrace_val);
 | |
| 	return ret;
 | |
| }
 | |
| 
 | |
| static ssize_t
 | |
| set_logtrace_info(struct dhd_info *dev, const char *buf, size_t count)
 | |
| {
 | |
| 	unsigned long onoff;
 | |
| 
 | |
| 	onoff = bcm_strtoul(buf, NULL, 10);
 | |
| 	sscanf(buf, "%lu", &onoff);
 | |
| 
 | |
| 	if (onoff > 2) {
 | |
| 		DHD_ERROR(("[WIFI_SEC] %s: Set Invalid value %lu \n",
 | |
| 			__FUNCTION__, onoff));
 | |
| 		return -EINVAL;
 | |
| 	}
 | |
| 
 | |
| 	logtrace_val = onoff;
 | |
| 	DHD_ERROR(("[WIFI_SEC] %s: LOGTRACE On/Off from sysfs = %lu\n",
 | |
| 		__FUNCTION__, logtrace_val));
 | |
| 	return count;
 | |
| }
 | |
| 
 | |
| static struct dhd_attr dhd_attr_cntl_logtraceinfo =
 | |
| 	__ATTR(logtrace, 0660, show_logtrace_info, set_logtrace_info);
 | |
| #endif /* LOGTRACE_FROM_FILE */
 | |
| 
 | |
| #ifdef  USE_WFA_CERT_CONF
 | |
| #ifdef BCMSDIO
 | |
| uint32 bus_txglom = VALUENOTSET;
 | |
| 
 | |
| static ssize_t
 | |
| show_bustxglom(struct dhd_info *dev, char *buf)
 | |
| {
 | |
| 	ssize_t ret = 0;
 | |
| 
 | |
| 	if (bus_txglom == VALUENOTSET) {
 | |
| 		ret = scnprintf(buf, PAGE_SIZE - 1, "%s\n", "bustxglom not set from sysfs");
 | |
| 	} else {
 | |
| 		ret = scnprintf(buf, PAGE_SIZE -1, "%u\n", bus_txglom);
 | |
| 	}
 | |
| 	return ret;
 | |
| }
 | |
| 
 | |
| static ssize_t
 | |
| set_bustxglom(struct dhd_info *dev, const char *buf, size_t count)
 | |
| {
 | |
| 	uint32 onoff;
 | |
| 
 | |
| 	onoff = (uint32)bcm_atoi(buf);
 | |
| 	sscanf(buf, "%u", &onoff);
 | |
| 
 | |
| 	if (onoff > 2) {
 | |
| 		DHD_ERROR(("[WIFI_SEC] %s: Set Invalid value %u \n",
 | |
| 			__FUNCTION__, onoff));
 | |
| 		return -EINVAL;
 | |
| 	}
 | |
| 
 | |
| 	bus_txglom = onoff;
 | |
| 	DHD_ERROR(("[WIFI_SEC] %s: BUS TXGLOM On/Off from sysfs = %u\n",
 | |
| 			__FUNCTION__, bus_txglom));
 | |
| 	return count;
 | |
| }
 | |
| 
 | |
| static struct dhd_attr dhd_attr_cntl_bustxglom =
 | |
| 	__ATTR(bustxglom, 0660, show_bustxglom, set_bustxglom);
 | |
| #endif /* BCMSDIO */
 | |
| 
 | |
| #if defined(ROAM_ENABLE) || defined(DISABLE_BUILTIN_ROAM)
 | |
| uint32 roam_off = VALUENOTSET;
 | |
| 
 | |
| static ssize_t
 | |
| show_roamoff(struct dhd_info *dev, char *buf)
 | |
| {
 | |
| 	ssize_t ret = 0;
 | |
| 
 | |
| 	if (roam_off == VALUENOTSET) {
 | |
| 		ret = scnprintf(buf, PAGE_SIZE -1, "%s\n", "roam_off not set from sysfs");
 | |
| 	} else {
 | |
| 		ret = scnprintf(buf, PAGE_SIZE -1, "%u\n", roam_off);
 | |
| 	}
 | |
| 	return ret;
 | |
| }
 | |
| 
 | |
| static ssize_t
 | |
| set_roamoff(struct dhd_info *dev, const char *buf, size_t count)
 | |
| {
 | |
| 	uint32 onoff;
 | |
| 
 | |
| 	onoff = bcm_atoi(buf);
 | |
| 	sscanf(buf, "%u", &onoff);
 | |
| 
 | |
| 	if (onoff > 2) {
 | |
| 		DHD_ERROR(("[WIFI_SEC] %s: Set Invalid value %u \n",
 | |
| 			__FUNCTION__, onoff));
 | |
| 		return -EINVAL;
 | |
| 	}
 | |
| 
 | |
| 	roam_off = onoff;
 | |
| 	DHD_ERROR(("[WIFI_SEC] %s: ROAM On/Off from sysfs = %u\n",
 | |
| 		__FUNCTION__, roam_off));
 | |
| 	return count;
 | |
| }
 | |
| 
 | |
| static struct dhd_attr dhd_attr_cntl_roamoff =
 | |
| 	__ATTR(roamoff, 0660, show_roamoff, set_roamoff);
 | |
| #endif /* ROAM_ENABLE || DISABLE_BUILTIN_ROAM */
 | |
| 
 | |
| #ifdef USE_WL_FRAMEBURST
 | |
| uint32 frameburst = VALUENOTSET;
 | |
| 
 | |
| static ssize_t
 | |
| show_frameburst(struct dhd_info *dev, char *buf)
 | |
| {
 | |
| 	ssize_t ret = 0;
 | |
| 
 | |
| 	if (frameburst == VALUENOTSET) {
 | |
| 		ret = scnprintf(buf, PAGE_SIZE -1, "%s\n", "frameburst not set from sysfs");
 | |
| 	} else {
 | |
| 		ret = scnprintf(buf, PAGE_SIZE -1, "%u\n", frameburst);
 | |
| 	}
 | |
| 	return ret;
 | |
| }
 | |
| 
 | |
| static ssize_t
 | |
| set_frameburst(struct dhd_info *dev, const char *buf, size_t count)
 | |
| {
 | |
| 	uint32 onoff;
 | |
| 
 | |
| 	onoff = bcm_atoi(buf);
 | |
| 	sscanf(buf, "%u", &onoff);
 | |
| 
 | |
| 	if (onoff > 2) {
 | |
| 		DHD_ERROR(("[WIFI_SEC] %s: Set Invalid value %u \n",
 | |
| 			__FUNCTION__, onoff));
 | |
| 		return -EINVAL;
 | |
| 	}
 | |
| 
 | |
| 	frameburst = onoff;
 | |
| 	DHD_ERROR(("[WIFI_SEC] %s: FRAMEBURST On/Off from sysfs = %u\n",
 | |
| 		__FUNCTION__, frameburst));
 | |
| 	return count;
 | |
| }
 | |
| 
 | |
| static struct dhd_attr dhd_attr_cntl_frameburst =
 | |
| 	__ATTR(frameburst, 0660, show_frameburst, set_frameburst);
 | |
| #endif /* USE_WL_FRAMEBURST */
 | |
| 
 | |
| #ifdef USE_WL_TXBF
 | |
| uint32 txbf = VALUENOTSET;
 | |
| 
 | |
| static ssize_t
 | |
| show_txbf(struct dhd_info *dev, char *buf)
 | |
| {
 | |
| 	ssize_t ret = 0;
 | |
| 
 | |
| 	if (txbf == VALUENOTSET) {
 | |
| 		ret = scnprintf(buf, PAGE_SIZE -1, "%s\n", "txbf not set from sysfs");
 | |
| 	} else {
 | |
| 		ret = scnprintf(buf, PAGE_SIZE -1, "%u\n", txbf);
 | |
| 	}
 | |
| 	return ret;
 | |
| }
 | |
| 
 | |
| static ssize_t
 | |
| set_txbf(struct dhd_info *dev, const char *buf, size_t count)
 | |
| {
 | |
| 	uint32 onoff;
 | |
| 
 | |
| 	onoff = bcm_atoi(buf);
 | |
| 	sscanf(buf, "%u", &onoff);
 | |
| 
 | |
| 	if (onoff > 2) {
 | |
| 		DHD_ERROR(("[WIFI_SEC] %s: Set Invalid value %u \n",
 | |
| 			__FUNCTION__, onoff));
 | |
| 		return -EINVAL;
 | |
| 	}
 | |
| 
 | |
| 	txbf = onoff;
 | |
| 	DHD_ERROR(("[WIFI_SEC] %s: FRAMEBURST On/Off from sysfs = %u\n",
 | |
| 		__FUNCTION__, txbf));
 | |
| 	return count;
 | |
| }
 | |
| 
 | |
| static struct dhd_attr dhd_attr_cntl_txbf =
 | |
| 	__ATTR(txbf, 0660, show_txbf, set_txbf);
 | |
| #endif /* USE_WL_TXBF */
 | |
| 
 | |
| #ifdef PROP_TXSTATUS
 | |
| uint32 proptx = VALUENOTSET;
 | |
| 
 | |
| static ssize_t
 | |
| show_proptx(struct dhd_info *dev, char *buf)
 | |
| {
 | |
| 	ssize_t ret = 0;
 | |
| 
 | |
| 	if (proptx == VALUENOTSET) {
 | |
| 		ret = scnprintf(buf, PAGE_SIZE -1, "%s\n", "proptx not set from sysfs");
 | |
| 	} else {
 | |
| 		ret = scnprintf(buf, PAGE_SIZE -1, "%u\n", proptx);
 | |
| 	}
 | |
| 	return ret;
 | |
| }
 | |
| 
 | |
| static ssize_t
 | |
| set_proptx(struct dhd_info *dev, const char *buf, size_t count)
 | |
| {
 | |
| 	uint32 onoff;
 | |
| 
 | |
| 	onoff = bcm_strtoul(buf, NULL, 10);
 | |
| 	sscanf(buf, "%u", &onoff);
 | |
| 
 | |
| 	if (onoff > 2) {
 | |
| 		DHD_ERROR(("[WIFI_SEC] %s: Set Invalid value %u \n",
 | |
| 			__FUNCTION__, onoff));
 | |
| 		return -EINVAL;
 | |
| 	}
 | |
| 
 | |
| 	proptx = onoff;
 | |
| 	DHD_ERROR(("[WIFI_SEC] %s: FRAMEBURST On/Off from sysfs = %u\n",
 | |
| 		__FUNCTION__, txbf));
 | |
| 	return count;
 | |
| }
 | |
| 
 | |
| static struct dhd_attr dhd_attr_cntl_proptx =
 | |
| 	__ATTR(proptx, 0660, show_proptx, set_proptx);
 | |
| 
 | |
| #endif /* PROP_TXSTATUS */
 | |
| #endif /* USE_WFA_CERT_CONF */
 | |
| #endif /* DHD_EXPORT_CNTL_FILE */
 | |
| 
 | |
| #ifdef DHD_SEND_HANG_PRIVCMD_ERRORS
 | |
| uint32 report_hang_privcmd_err = 1;
 | |
| 
 | |
| static ssize_t
 | |
| show_hang_privcmd_err(struct dhd_info *dev, char *buf)
 | |
| {
 | |
| 	ssize_t ret = 0;
 | |
| 
 | |
| 	ret = scnprintf(buf, PAGE_SIZE - 1, "%u\n", report_hang_privcmd_err);
 | |
| 	return ret;
 | |
| }
 | |
| 
 | |
| static ssize_t
 | |
| set_hang_privcmd_err(struct dhd_info *dev, const char *buf, size_t count)
 | |
| {
 | |
| 	uint32 val;
 | |
| 
 | |
| 	val = bcm_atoi(buf);
 | |
| 	sscanf(buf, "%u", &val);
 | |
| 
 | |
| 	report_hang_privcmd_err = val ? 1 : 0;
 | |
| 	DHD_INFO(("%s: Set report HANG for private cmd error: %d\n",
 | |
| 		__FUNCTION__, report_hang_privcmd_err));
 | |
| 	return count;
 | |
| }
 | |
| 
 | |
| static struct dhd_attr dhd_attr_hang_privcmd_err =
 | |
| 	__ATTR(hang_privcmd_err, 0660, show_hang_privcmd_err, set_hang_privcmd_err);
 | |
| #endif /* DHD_SEND_HANG_PRIVCMD_ERRORS */
 | |
| 
 | |
| #if defined(DISABLE_HE_ENAB) || defined(CUSTOM_CONTROL_HE_ENAB)
 | |
| uint8 control_he_enab = 1;
 | |
| #endif /* DISABLE_HE_ENAB || CUSTOM_CONTROL_HE_ENAB */
 | |
| 
 | |
| #if defined(CUSTOM_CONTROL_HE_ENAB)
 | |
| static ssize_t
 | |
| show_control_he_enab(struct dhd_info *dev, char *buf)
 | |
| {
 | |
| 	ssize_t ret = 0;
 | |
| 
 | |
| 	ret = scnprintf(buf, PAGE_SIZE - 1, "%d\n", control_he_enab);
 | |
| 	return ret;
 | |
| }
 | |
| 
 | |
| static ssize_t
 | |
| set_control_he_enab(struct dhd_info *dev, const char *buf, size_t count)
 | |
| {
 | |
| 	uint32 val;
 | |
| 
 | |
| 	val = bcm_atoi(buf);
 | |
| 
 | |
| 	control_he_enab = val ? 1 : 0;
 | |
| 	DHD_ERROR(("%s: Set control he enab: %d\n", __FUNCTION__, control_he_enab));
 | |
| 	return count;
 | |
| }
 | |
| 
 | |
| static struct dhd_attr dhd_attr_control_he_enab=
 | |
| __ATTR(control_he_enab, 0660, show_control_he_enab, set_control_he_enab);
 | |
| #endif /* CUSTOM_CONTROL_HE_ENAB */
 | |
| /* Attribute object that gets registered with "wifi" kobject tree */
 | |
| static struct attribute *control_file_attrs[] = {
 | |
| #ifdef DHD_MAC_ADDR_EXPORT
 | |
| 	&dhd_attr_cntl_macaddr.attr,
 | |
| #endif /* DHD_MAC_ADDR_EXPORT */
 | |
| #ifdef DHD_EXPORT_CNTL_FILE
 | |
| #ifdef DHD_FW_COREDUMP
 | |
| 	&dhd_attr_cntl_memdump.attr,
 | |
| #endif /* DHD_FW_COREDUMP */
 | |
| #ifdef BCMASSERT_LOG
 | |
| 	&dhd_attr_cntl_assert.attr,
 | |
| #endif /* BCMASSERT_LOG */
 | |
| #ifdef WRITE_WLANINFO
 | |
| 	&dhd_attr_cntl_wifiver.attr,
 | |
| #endif /* WRITE_WLANINFO */
 | |
| #ifdef USE_CID_CHECK
 | |
| 	&dhd_attr_cntl_cidinfo.attr,
 | |
| #endif /* USE_CID_CHECK */
 | |
| #ifdef GEN_SOFTAP_INFO_FILE
 | |
| 	&dhd_attr_cntl_softapinfo.attr,
 | |
| #endif /* GEN_SOFTAP_INFO_FILE */
 | |
| #ifdef MIMO_ANT_SETTING
 | |
| 	&dhd_attr_cntl_antinfo.attr,
 | |
| #endif /* MIMO_ANT_SETTING */
 | |
| #ifdef DHD_PM_CONTROL_FROM_FILE
 | |
| 	&dhd_attr_cntl_pminfo.attr,
 | |
| #endif /* DHD_PM_CONTROL_FROM_FILE */
 | |
| #ifdef LOGTRACE_FROM_FILE
 | |
| 	&dhd_attr_cntl_logtraceinfo.attr,
 | |
| #endif /* LOGTRACE_FROM_FILE */
 | |
| #ifdef USE_WFA_CERT_CONF
 | |
| #ifdef BCMSDIO
 | |
| 	&dhd_attr_cntl_bustxglom.attr,
 | |
| #endif /* BCMSDIO */
 | |
| 	&dhd_attr_cntl_roamoff.attr,
 | |
| #ifdef USE_WL_FRAMEBURST
 | |
| 	&dhd_attr_cntl_frameburst.attr,
 | |
| #endif /* USE_WL_FRAMEBURST */
 | |
| #ifdef USE_WL_TXBF
 | |
| 	&dhd_attr_cntl_txbf.attr,
 | |
| #endif /* USE_WL_TXBF */
 | |
| #ifdef PROP_TXSTATUS
 | |
| 	&dhd_attr_cntl_proptx.attr,
 | |
| #endif /* PROP_TXSTATUS */
 | |
| #endif /* USE_WFA_CERT_CONF */
 | |
| #endif /* DHD_EXPORT_CNTL_FILE */
 | |
| #ifdef DHD_ADPS_BAM_EXPORT
 | |
| 	&dhd_attr_adps_bam.attr,
 | |
| #endif	/* DHD_ADPS_BAM_EXPORT */
 | |
| #ifdef DHD_SEND_HANG_PRIVCMD_ERRORS
 | |
| 	&dhd_attr_hang_privcmd_err.attr,
 | |
| #endif /* DHD_SEND_HANG_PRIVCMD_ERRORS */
 | |
| #if defined(CUSTOM_CONTROL_HE_ENAB)
 | |
| 	&dhd_attr_control_he_enab.attr,
 | |
| #endif /* CUSTOM_CONTROL_HE_ENAB */
 | |
| 	NULL
 | |
| };
 | |
| 
 | |
| #define to_cntl_dhd(k) container_of(k, struct dhd_info, dhd_conf_file_kobj)
 | |
| 
 | |
| /*
 | |
|  * wifi kobject show function, the "attr" attribute specifices to which
 | |
|  * node under "sys/wifi" the show function is called.
 | |
|  */
 | |
| static ssize_t dhd_cntl_show(struct kobject *kobj, struct attribute *attr, char *buf)
 | |
| {
 | |
| #if defined(STRICT_GCC_WARNINGS) && defined(__GNUC__)
 | |
| #pragma GCC diagnostic push
 | |
| #pragma GCC diagnostic ignored "-Wcast-qual"
 | |
| #endif // endif
 | |
| 	dhd_info_t *dhd = to_cntl_dhd(kobj);
 | |
| 	struct dhd_attr *d_attr = to_attr(attr);
 | |
| #if defined(STRICT_GCC_WARNINGS) && defined(__GNUC__)
 | |
| #pragma GCC diagnostic pop
 | |
| #endif // endif
 | |
| 	int ret;
 | |
| 
 | |
| 	if (d_attr->show)
 | |
| 		ret = d_attr->show(dhd, buf);
 | |
| 	else
 | |
| 		ret = -EIO;
 | |
| 
 | |
| 	return ret;
 | |
| }
 | |
| 
 | |
| /*
 | |
|  * wifi kobject show function, the "attr" attribute specifices to which
 | |
|  * node under "sys/wifi" the store function is called.
 | |
|  */
 | |
| static ssize_t dhd_cntl_store(struct kobject *kobj, struct attribute *attr,
 | |
| 	const char *buf, size_t count)
 | |
| {
 | |
| #if defined(STRICT_GCC_WARNINGS) && defined(__GNUC__)
 | |
| #pragma GCC diagnostic push
 | |
| #pragma GCC diagnostic ignored "-Wcast-qual"
 | |
| #endif // endif
 | |
| 	dhd_info_t *dhd = to_cntl_dhd(kobj);
 | |
| 	struct dhd_attr *d_attr = to_attr(attr);
 | |
| #if defined(STRICT_GCC_WARNINGS) && defined(__GNUC__)
 | |
| #pragma GCC diagnostic pop
 | |
| #endif // endif
 | |
| 	int ret;
 | |
| 
 | |
| 	if (d_attr->store)
 | |
| 		ret = d_attr->store(dhd, buf, count);
 | |
| 	else
 | |
| 		ret = -EIO;
 | |
| 
 | |
| 	return ret;
 | |
| 
 | |
| }
 | |
| 
 | |
| static struct sysfs_ops dhd_sysfs_cntl_ops = {
 | |
| 	.show = dhd_cntl_show,
 | |
| 	.store = dhd_cntl_store,
 | |
| };
 | |
| 
 | |
| static struct kobj_type dhd_cntl_file_ktype = {
 | |
| 	.sysfs_ops = &dhd_sysfs_cntl_ops,
 | |
| 	.default_attrs = control_file_attrs,
 | |
| };
 | |
| 
 | |
| #ifdef CSI_SUPPORT
 | |
| /* Function to show current ccode */
 | |
| static ssize_t read_csi_data(struct file *filp, struct kobject *kobj,
 | |
| 	struct bin_attribute *bin_attr, char *buf, loff_t off, size_t count)
 | |
| {
 | |
| 	dhd_info_t *dhd = to_dhd(kobj);
 | |
| 	int n = 0;
 | |
| 
 | |
| 	n = dhd_csi_dump_list(&dhd->pub, buf);
 | |
| 	DHD_INFO(("Dump data to file, size %d\n", n));
 | |
| 	dhd_csi_clean_list(&dhd->pub);
 | |
| 
 | |
| 	return n;
 | |
| }
 | |
| 
 | |
| static struct bin_attribute dhd_attr_csi = {
 | |
| 	.attr = { .name = "csi",
 | |
| 		  .mode = 0660, },
 | |
| 	.size = MAX_CSI_FILESZ,
 | |
| 	.read = read_csi_data,
 | |
| };
 | |
| #endif /* CSI_SUPPORT */
 | |
| 
 | |
| /* Create a kobject and attach to sysfs interface */
 | |
| int dhd_sysfs_init(dhd_info_t *dhd)
 | |
| {
 | |
| 	int ret = -1;
 | |
| 
 | |
| 	if (dhd == NULL) {
 | |
| 		DHD_ERROR(("%s(): dhd is NULL \r\n", __FUNCTION__));
 | |
| 		return ret;
 | |
| 	}
 | |
| 
 | |
| 	/* Initialize the kobject */
 | |
| 	ret = kobject_init_and_add(&dhd->dhd_kobj, &dhd_ktype, NULL, "bcm-dhd");
 | |
| 	if (ret) {
 | |
| 		kobject_put(&dhd->dhd_kobj);
 | |
| 		DHD_ERROR(("%s(): Unable to allocate kobject \r\n", __FUNCTION__));
 | |
| 		return ret;
 | |
| 	}
 | |
| 	ret = kobject_init_and_add(&dhd->dhd_conf_file_kobj, &dhd_cntl_file_ktype, NULL, "wifi");
 | |
| 	if (ret) {
 | |
| 		kobject_put(&dhd->dhd_conf_file_kobj);
 | |
| 		DHD_ERROR(("%s(): Unable to allocate kobject \r\n", __FUNCTION__));
 | |
| 		return ret;
 | |
| 	}
 | |
| 
 | |
| #ifdef CSI_SUPPORT
 | |
| 	ret = sysfs_create_bin_file(&dhd->dhd_kobj, &dhd_attr_csi);
 | |
| 	if (ret) {
 | |
| 		DHD_ERROR(("%s: can't create %s\n", __FUNCTION__, dhd_attr_csi.attr.name));
 | |
| 		kobject_put(&dhd->dhd_kobj);
 | |
| 		return ret;
 | |
| 	}
 | |
| #endif /* CSI_SUPPORT */
 | |
| 
 | |
| 	/*
 | |
| 	 * We are always responsible for sending the uevent that the kobject
 | |
| 	 * was added to the system.
 | |
| 	 */
 | |
| 	kobject_uevent(&dhd->dhd_kobj, KOBJ_ADD);
 | |
| 	kobject_uevent(&dhd->dhd_conf_file_kobj, KOBJ_ADD);
 | |
| 
 | |
| 	return ret;
 | |
| }
 | |
| 
 | |
| /* Done with the kobject and detach the sysfs interface */
 | |
| void dhd_sysfs_exit(dhd_info_t *dhd)
 | |
| {
 | |
| 	if (dhd == NULL) {
 | |
| 		DHD_ERROR(("%s(): dhd is NULL \r\n", __FUNCTION__));
 | |
| 		return;
 | |
| 	}
 | |
| 
 | |
| 	/* Releae the kobject */
 | |
| 	if (dhd->dhd_kobj.state_initialized)
 | |
| 		kobject_put(&dhd->dhd_kobj);
 | |
| 	if (dhd->dhd_conf_file_kobj.state_initialized)
 | |
| 		kobject_put(&dhd->dhd_conf_file_kobj);
 | |
| }
 | 
