blob: bc0021a5d76297d6f81679e2cb7e86763b20bb49 [file] [log] [blame]
/*
* Broadcom Dongle Host Driver (DHD)
*
* Copyright 1999-2016, Broadcom Corporation
* All rights reserved,
*
* Redistribution and use in source and binary forms, with or without modification,
* are permitted provided that the following conditions are met:
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* This software is provided by the copyright holder "as is" and any express or
* implied warranties, including, but not limited to, the implied warranties of
* merchantability and fitness for a particular purpose are disclaimed. In no event
* shall copyright holder be liable for any direct, indirect, incidental, special,
* exemplary, or consequential damages (including, but not limited to, procurement
* of substitute goods or services; loss of use, data, or profits; or business
* interruption) however caused and on any theory of liability, whether in
* contract, strict liability, or tort (including negligence or otherwise) arising
* in any way out of the use of this software, even if advised of the possibility
* of such damage
*
* $Id: dhd_csi.c 606280 2015-12-15 05:28:25Z $
*/
#include <osl.h>
#include <bcmutils.h>
#include <bcmendian.h>
#include <linuxver.h>
#include <linux/list.h>
#include <linux/sort.h>
#include <dngl_stats.h>
#include <wlioctl.h>
#include <proto/bcmevent.h>
#include <dhd.h>
#include <dhd_dbg.h>
#include <dhd_csi.h>
#define NULL_CHECK(p, s, err) \
do { \
if (!(p)) { \
printf("NULL POINTER (%s) : %s\n", __FUNCTION__, (s)); \
err = BCME_ERROR; \
return err; \
} \
} while (0)
#define TIMESPEC_TO_US(ts) (((uint64)(ts).tv_sec * USEC_PER_SEC) + \
(ts).tv_nsec / NSEC_PER_USEC)
#define NULL_ADDR "\x00\x00\x00\x00\x00\x00"
int
dhd_csi_event_handler(dhd_pub_t *dhd, wl_event_msg_t *event, void *event_data)
{
int ret = BCME_OK, event_type;
bool is_new = TRUE;
struct timespec ts;
cfr_dump_data_t *p_event;
cfr_dump_list_t *ptr, *next, *new_res;
struct list_head * csi_list;
int ifidx = 0;
NULL_CHECK(dhd, "dhd is NULL", ret);
event_type = ntoh32_ua((void *)&event->event_type);
DHD_TRACE(("Enter %s\n", __FUNCTION__));
if (!event_data) {
DHD_ERROR(("%s: event_data is NULL\n", __FUNCTION__));
ret = BCME_ERROR;
return ret;
}
p_event = (cfr_dump_data_t *)event_data;
mutex_lock(&dhd->wl_csi_mutex);
ifidx = event->ifidx;
csi_list = get_dhd_csilist(dhd, ifidx);
if (!csi_list) {
DHD_ERROR(("%s: Can not get csi_list. ifidx %d\n", __FUNCTION__, ifidx));
ret = BCME_ERROR;
goto done;
}
/* check if this addr exist */
if (!list_empty(csi_list)) {
list_for_each_entry_safe(ptr, next, csi_list, list) {
if (bcmp(&ptr->entry.header.peer_macaddr, &p_event->header.peer_macaddr,
ETHER_ADDR_LEN) == 0) {
is_new = FALSE;
DHD_TRACE(("CSI data exist. Update to newest one.\n"));
/* update data to newest and success one and timestamp */
if ((p_event->header.cfr_capture_status == 0) &&
(p_event->header.cfr_dump_length <=
(MAXIMUM_CFR_DATA * (sizeof(unsigned int))))) {
bcopy(&p_event->header, &ptr->entry.header,
sizeof(cfr_dump_header_t));
get_monotonic_boottime(&ts);
ptr->entry.header.cfr_timestamp =
(uint64)TIMESPEC_TO_US(ts);
bcopy(&p_event->data, &ptr->entry.data,
p_event->header.cfr_dump_length);
}
else {
if (p_event->header.cfr_dump_length >
(MAXIMUM_CFR_DATA * (sizeof(unsigned int)))) {
DHD_ERROR(("data corrupted. No update.\n"));
ret = BCME_ERROR;
}
}
break;
}
}
}
if (is_new) {
new_res = (cfr_dump_list_t *)MALLOCZ(dhd->osh, sizeof(cfr_dump_list_t));
if (!new_res){
DHD_ERROR(("Malloc cfr dump list error\n"));
ret = BCME_NOMEM;
goto done;
}
if (p_event->header.cfr_dump_length <=
(MAXIMUM_CFR_DATA * (sizeof(unsigned int)))) {
bcopy(&p_event->header, &new_res->entry.header, sizeof(cfr_dump_header_t));
/* put the timestamp here */
get_monotonic_boottime(&ts);
new_res->entry.header.cfr_timestamp = (uint64)TIMESPEC_TO_US(ts);
bcopy(&p_event->data, &new_res->entry.data,
p_event->header.cfr_dump_length);
}
else {
DHD_ERROR(("data size is over limitation %d\n",
p_event->header.cfr_dump_length));
MFREE(dhd->osh, new_res, sizeof(cfr_dump_list_t));
ret = BCME_ERROR;
goto done;
}
DHD_TRACE(("New entry data size %d\n", p_event->header.cfr_dump_length));
INIT_LIST_HEAD(&(new_res->list));
list_add_tail(&(new_res->list), csi_list);
/* if never update before, send update uevent up */
if (!get_dhd_csiupdate(dhd, ifidx)) {
struct kobject *kobj = get_dhd_kobj(dhd);
if (kobj) {
int status = 0;
char event_string[UEVENT_LENGTH];
char *envp[] = { event_string, NULL };
snprintf(event_string, UEVENT_LENGTH, "IFACE=%s", dhd_ifname(dhd, ifidx));
/* Sent notification uevent */
status = kobject_uevent_env(kobj, KOBJ_ADD, envp);
if (status)
DHD_ERROR(("Send out uevent failed %d\n", status));
else
set_dhd_csiupdate(dhd, ifidx, 1);
} else {
DHD_ERROR(("Can not find dhd kobj.\n"));
}
}
}
done:
mutex_unlock(&dhd->wl_csi_mutex);
return ret;
}
int
dhd_csi_init(dhd_pub_t *dhd)
{
int err = BCME_OK;
struct kobject *kobj = get_dhd_kobj(dhd);
struct list_head *csi_list;
NULL_CHECK(dhd, "dhd is NULL", err);
mutex_init(&dhd->wl_csi_mutex);
if (!kobj) {
err = BCME_ERROR;
DHD_ERROR(("%s: Can't get kobj.\n", __FUNCTION__));
return err;
}
dhd->dhd_kset = kset_create_and_add("kset", NULL, kobj);
if (!dhd->dhd_kset) {
err = BCME_NOMEM;
DHD_ERROR(("%s: Can't init kset.\n", __FUNCTION__));
return err;
}
kobj->kset = dhd->dhd_kset;
/* Init sysfs and csi list for wlan0 here. wlan0 is created before dhd_sysfs_init() */
csi_list = get_dhd_csilist(dhd, get_dhd_ifidx(dhd->info, "wlan0"));
set_dhd_csiupdate(dhd, get_dhd_ifidx(dhd->info, "wlan0"), 0);
if (csi_list) {
INIT_LIST_HEAD(csi_list);
err = create_sysfs_entry(dhd, "wlan0");
}
else {
DHD_ERROR(("%s Can't init CSI list.\n", __FUNCTION__));
err = BCME_ERROR;
}
return err;
}
int
dhd_csi_deinit(dhd_pub_t *dhd)
{
int err = BCME_OK;
struct kobject *kobj = get_dhd_kobj(dhd);
struct list_head *csi_list;
struct bin_attribute * bin_attr= NULL;
NULL_CHECK(dhd, "dhd is NULL", err);
/* remove wlan0 csi info here */
csi_list = get_dhd_csilist(dhd, get_dhd_ifidx(dhd->info, "wlan0"));
if (csi_list) {
remove_csi_list(dhd, csi_list);
bin_attr = get_dhd_binattr(dhd, get_dhd_ifidx(dhd->info, "wlan0"));
sysfs_remove_bin_file(kobj, bin_attr);
}
kset_unregister(dhd->dhd_kset);
return err;
}
int
dhd_csi_dump_list(dhd_pub_t *dhd, char *buf, size_t count, int ifidx)
{
int ret = BCME_OK;
cfr_dump_list_t *ptr, *next;
uint8 * pbuf = buf;
int num = 0;
int length = 0;
uint32 demarcation1 = 0xDADBEEAB;
uint32 demarcation2 = 0xBABEEDAD;
struct list_head *csi_list;
NULL_CHECK(dhd, "dhd is NULL", ret);
mutex_lock(&dhd->wl_csi_mutex);
csi_list = get_dhd_csilist(dhd, ifidx);
if (!csi_list) {
DHD_ERROR(("%s: Can not get csi_list. ifidx %d\n", __FUNCTION__, ifidx));
mutex_unlock(&dhd->wl_csi_mutex);
return BCME_ERROR;
}
/* check if csi record exists */
if (!list_empty(csi_list)) {
list_for_each_entry_safe(ptr, next, csi_list, list) {
/* if next record > buf count, just return */
if ((length + sizeof(cfr_dump_header_t) +
ptr->entry.header.cfr_dump_length) > count) {
DHD_ERROR(("Record over buffer size. Please read again.\n"));
mutex_unlock(&dhd->wl_csi_mutex);
return length;
}
/* add 4 bytes demarcation 0xDADBEEAB for csi data start */
bcopy(&demarcation1, pbuf, sizeof(unsigned int));
length += sizeof(unsigned int);
pbuf += sizeof(unsigned int);
bcopy(&ptr->entry.header, pbuf, sizeof(cfr_dump_header_t));
length += sizeof(cfr_dump_header_t);
pbuf += sizeof(cfr_dump_header_t);
DHD_TRACE(("Copy data size %d\n", ptr->entry.header.cfr_dump_length));
bcopy(&ptr->entry.data, pbuf, ptr->entry.header.cfr_dump_length);
length += ptr->entry.header.cfr_dump_length;
pbuf += ptr->entry.header.cfr_dump_length;
num++;
/* clean dumped list */
list_del(&ptr->list);
MFREE(dhd->osh, ptr, sizeof(cfr_dump_list_t));
/* add 4 bytes demarcation 0xBABEEDAD for csi data end */
bcopy(&demarcation2, pbuf, sizeof(unsigned int));
length += sizeof(unsigned int);
pbuf += sizeof(unsigned int);
}
}
DHD_TRACE(("dump %d record %d bytes\n", num, length));
/* Dump finished. Clear update flag */
set_dhd_csiupdate(dhd, ifidx, 0);
mutex_unlock(&dhd->wl_csi_mutex);
return length;
}
void remove_csi_list(dhd_pub_t *dhd, struct list_head *csi_list)
{
cfr_dump_list_t *ptr, *next;
int num = 0;
if (!list_empty(csi_list)) {
list_for_each_entry_safe(ptr, next, csi_list, list) {
list_del(&ptr->list);
num++;
MFREE(dhd->osh, ptr, sizeof(cfr_dump_list_t));
}
}
}