| /* |
| * Broadcom Dongle Host Driver (DHD) |
| * |
| * Copyright (C) 1999-2018, 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. |
| * |
| * $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)); |
| } |
| } |
| } |