Hello, system hang when monitor interface is enabled/disabled should be fixed thanks to the 3 patches in attachment: 07-fix_procfs_handling.patch 08-enable_error_traces.patch 09-fix_system_hang_when_deleting_monitor.patch It should be applied after original Debian's patches and it has been tested on Linux 3.14.
Regards, Mickael
fix kernel crash/system hang because of incorrect pointer argument use while retrieving cookie in procfs handling. based on: http://ix.io/9DV tested on: kernel 3.14 diff -p -u5 -r broadcom-sta_6.30.223.248-2_deb/src/wl/sys/wl_linux.c broadcom-sta_6.30.223.248-2_fix/src/wl/sys/wl_linux.c --- broadcom-sta_6.30.223.248-2_deb/src/wl/sys/wl_linux.c +++ broadcom-sta_6.30.223.248-2_fix/src/wl/sys/wl_linux.c @@ -3223,70 +3223,112 @@ wl_linux_watchdog(void *ctx) } #if LINUX_VERSION_CODE < KERNEL_VERSION(3, 10, 0) static int wl_proc_read(char *buffer, char **start, off_t offset, int length, int *eof, void *data) +{ + wl_info_t * wl = (wl_info_t *)data; #else static ssize_t -wl_proc_read(struct file *filp, char __user *buffer, size_t length, loff_t *data) -#endif +wl_proc_read(struct file *filp, char __user *buffer, size_t length, loff_t *offp) { - wl_info_t * wl = (wl_info_t *)data; - int to_user; - int len; + wl_info_t * wl = PDE_DATA(file_inode(filp)); +#endif + int bcmerror, len; + int to_user = 0; + char tmp[8]; #if LINUX_VERSION_CODE < KERNEL_VERSION(3, 10, 0) if (offset > 0) { *eof = 1; return 0; } +#else + if (*offp > 0) { /* for example, stop: cat /proc/brcm_monitor0 */ + return 0; /* 0 <=> EOF */ + } #endif - if (!length) { - WL_ERROR(("%s: Not enough return buf space\n", __FUNCTION__)); - return 0; - } WL_LOCK(wl); - wlc_ioctl(wl->wlc, WLC_GET_MONITOR, &to_user, sizeof(int), NULL); - len = sprintf(buffer, "%d\n", to_user); - WL_UNLOCK(wl); - return len; + bcmerror = wlc_ioctl(wl->wlc, WLC_GET_MONITOR, &to_user, sizeof(int), NULL); + WL_UNLOCK(wl); + + if (bcmerror != BCME_OK) { + WL_ERROR(("%s: GET_MONITOR failed with %d\n", __FUNCTION__, bcmerror)); + return -EIO; + } + + len = snprintf(tmp, ARRAY_SIZE(tmp), "%d\n", to_user); + tmp[ARRAY_SIZE(tmp) - 1] = '\0'; + if (len >= ARRAY_SIZE(tmp)) { + printk(KERN_ERR "%s:%d [%s()] output would be truncated (ret=%d)!", __FILE__, __LINE__, __FUNCTION__, len); + return -ERANGE; + } + else if (len < 0) { + printk(KERN_ERR "%s:%d [%s()] unable to convert value (ret=%d)!", __FILE__, __LINE__, __FUNCTION__, len); + return len; + } + if (length < len) { + printk(KERN_ERR "%s:%d [%s()] user buffer is too small (at least=%d ; user=%d)!", __FILE__, __LINE__, __FUNCTION__, len, (int)length); + return -EMSGSIZE; + } + if (copy_to_user(buffer, tmp, len) != 0) { + printk(KERN_ERR "%s:%d [%s()] unable to copy data!", __FILE__, __LINE__, __FUNCTION__); + return -EFAULT; + } + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 10, 0) + *offp += len; +#endif + + return len; } #if LINUX_VERSION_CODE < KERNEL_VERSION(3, 10, 0) static int wl_proc_write(struct file *filp, const char *buff, unsigned long length, void *data) +{ + wl_info_t * wl = (wl_info_t *)data; #else static ssize_t -wl_proc_write(struct file *filp, const char __user *buff, size_t length, loff_t *data) -#endif +wl_proc_write(struct file *filp, const char __user *buff, size_t length, loff_t *offp) { - wl_info_t * wl = (wl_info_t *)data; + wl_info_t * wl = PDE_DATA(file_inode(filp)); +#endif int from_user = 0; int bcmerror; if (length == 0 || length > 2) { WL_ERROR(("%s: Invalid data length\n", __FUNCTION__)); return -EIO; } if (copy_from_user(&from_user, buff, 1)) { WL_ERROR(("%s: copy from user failed\n", __FUNCTION__)); - return -EIO; +#if LINUX_VERSION_CODE < KERNEL_VERSION(3, 10, 0) + return -EIO; +#else + return -EFAULT; +#endif } if (from_user >= 0x30) from_user -= 0x30; WL_LOCK(wl); bcmerror = wlc_ioctl(wl->wlc, WLC_SET_MONITOR, &from_user, sizeof(int), NULL); WL_UNLOCK(wl); - if (bcmerror < 0) { + if (bcmerror != BCME_OK) { WL_ERROR(("%s: SET_MONITOR failed with %d\n", __FUNCTION__, bcmerror)); return -EIO; } + +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(3, 10, 0)) && 0 /* no need to update offset because this file should only trigger action... */ + *offp += length; +#endif + return length; } #if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 10, 0) static const struct file_operations wl_fops = { @@ -3303,12 +3345,12 @@ wl_reg_proc_entry(wl_info_t *wl) sprintf(tmp, "%s%d", HYBRID_PROC, wl->pub->unit); #if LINUX_VERSION_CODE < KERNEL_VERSION(3, 10, 0) if ((wl->proc_entry = create_proc_entry(tmp, 0644, NULL)) == NULL) { WL_ERROR(("%s: create_proc_entry %s failed\n", __FUNCTION__, tmp)); #else - if ((wl->proc_entry = proc_create(tmp, 0644, NULL, &wl_fops)) == NULL) { - WL_ERROR(("%s: proc_create %s failed\n", __FUNCTION__, tmp)); + if ((wl->proc_entry = proc_create_data(tmp, 0644, NULL, &wl_fops, wl)) == NULL) { + WL_ERROR(("%s: proc_create_data %s failed\n", __FUNCTION__, tmp)); #endif ASSERT(0); return -1; } #if LINUX_VERSION_CODE < KERNEL_VERSION(3, 10, 0)
enable error + add some traces tested on: kernel 3.14 diff -p -r -u5 broadcom-sta_6.30.223.248-2_deb/src/wl/sys/wl_dbg.h broadcom-sta_6.30.223.248-2_fix/src/wl/sys/wl_dbg.h --- broadcom-sta_6.30.223.248-2_deb/src/wl/sys/wl_dbg.h +++ broadcom-sta_6.30.223.248-2_fix/src/wl/sys/wl_dbg.h @@ -53,14 +53,16 @@ extern int osl_printf(const char *fmt, . #else #define WL_NONE(args) +#define FORCE_TRACE_LEVEL(fmt, ...) do { printk(KERN_ERR fmt, ## __VA_ARGS__); } while (0) /* ## is GCC specific syntax to remove comma when single arg */ + #ifdef BCMDBG_ERR #define WL_ERROR(args) WL_PRINT(args) #else -#define WL_ERROR(args) +#define WL_ERROR(args) FORCE_TRACE_LEVEL args #endif #define WL_TRACE(args) #define WL_APSTA_UPDN(args) #define WL_APSTA_RX(args) #define WL_WSEC(args) diff -p -r -u5 broadcom-sta_6.30.223.248-2_deb/src/wl/sys/wl_linux.c broadcom-sta_6.30.223.248-2_fix/src/wl/sys/wl_linux.c --- broadcom-sta_6.30.223.248-2_deb/src/wl/sys/wl_linux.c +++ broadcom-sta_6.30.223.248-2_fix/src/wl/sys/wl_linux.c @@ -1270,10 +1270,11 @@ wl_free_if(wl_info_t *wl, wl_if_t *wlif) #if (LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 24)) MFREE(wl->osh, wlif->dev->priv, sizeof(priv_link_t)); MFREE(wl->osh, wlif->dev, sizeof(struct net_device)); #else free_netdev(wlif->dev); + wlif->dev = NULL; #endif } MFREE(wl->osh, wlif, sizeof(wl_if_t)); } @@ -2454,12 +2455,14 @@ wl_monitor(wl_info_t *wl, wl_rxsts_t *rx if (wl->monitor_type == 1) { p80211msg_t *phdr; len = sizeof(p80211msg_t) + oskb->len - D11_PHY_HDR_LEN; - if ((skb = dev_alloc_skb(len)) == NULL) + if ((skb = dev_alloc_skb(len)) == NULL) { + WL_ERROR(("in %s:%d [%s()] dev_alloc_skb() failure!", __FILE__, __LINE__, __FUNCTION__)); return; + } skb_put(skb, len); phdr = (p80211msg_t*)skb->data; phdr->msgcode = WL_MON_FRAME; @@ -2534,12 +2537,14 @@ wl_monitor(wl_info_t *wl, wl_rxsts_t *rx rtap_len = sizeof(wl_radiotap_legacy_t); else rtap_len = sizeof(wl_radiotap_ht_brcm_2_t); len = rtap_len + (oskb->len - D11_PHY_HDR_LEN); - if ((skb = dev_alloc_skb(len)) == NULL) + if ((skb = dev_alloc_skb(len)) == NULL) { + WL_ERROR(("in %s:%d [%s()] dev_alloc_skb() failure!", __FILE__, __LINE__, __FUNCTION__)); return; + } skb_put(skb, len); if (CHSPEC_IS2G(rxsts->chanspec)) { channel_flags = IEEE80211_CHAN_2GHZ | IEEE80211_CHAN_DYN; @@ -2663,12 +2668,14 @@ wl_monitor(wl_info_t *wl, wl_rxsts_t *rx amsdu_p = amsdu_p->next; } len += amsdu_len; } - if ((skb = dev_alloc_skb(len)) == NULL) + if ((skb = dev_alloc_skb(len)) == NULL) { + WL_ERROR(("in %s:%d [%s()] dev_alloc_skb() failure!", __FILE__, __LINE__, __FUNCTION__)); return; + } skb_put(skb, len); if (CHSPEC_IS2G(rxsts->chanspec)) { channel_flags = IEEE80211_CHAN_2GHZ | IEEE80211_CHAN_DYN; @@ -2989,11 +2996,11 @@ _wl_del_monitor(wl_task_t *task) MFREE(wl->osh, task, sizeof(wl_task_t)); atomic_dec(&wl->callbacks); } void -wl_set_monitor(wl_info_t *wl, int val) +wl_set_monitor(wl_info_t *wl, int val) /* public => is called by wlc_hybrid.o_shipped */ { const char *devname; wl_if_t *wlif; WL_TRACE(("wl%d: wl_set_monitor: val %d\n", wl->pub->unit, val));
fix kernel crash/system hang when disabling monitor interface: echo 0 > /proc/brcm_monitor0 tested on: kernel 3.14 diff -p -r -u5 broadcom-sta_6.30.223.248-2_deb/src/wl/sys/wl_cfg80211_hybrid.c broadcom-sta_6.30.223.248-2_fix/src/wl/sys/wl_cfg80211_hybrid.c --- broadcom-sta_6.30.223.248-2_deb/src/wl/sys/wl_cfg80211_hybrid.c +++ broadcom-sta_6.30.223.248-2_fix/src/wl/sys/wl_cfg80211_hybrid.c @@ -2631,11 +2631,19 @@ cfg80211_attach_out: return err; } void wl_cfg80211_detach(struct net_device *ndev) { - struct wl_cfg80211_priv *wl = ndev_to_wl(ndev); + struct wl_cfg80211_priv *wl; + struct wireless_dev *wdev; + + wdev = ndev->ieee80211_ptr; + if (wdev == NULL) { + printk(KERN_ERR "[%s()] in ndev=%p: IEEE80211ptr=%p\n", __FUNCTION__, ndev, wdev); + return; + } + wl = ndev_to_wl(ndev); wl_deinit_cfg80211_priv(wl); wl_free_wdev(wl); }