linux/drivers/net/wireless/ath/ar5523/ar5523.c

1828 lines
48 KiB
C
Raw Permalink Normal View History

/*
* Copyright (c) 2006 Damien Bergamini <damien.bergamini@free.fr>
* Copyright (c) 2006 Sam Leffler, Errno Consulting
* Copyright (c) 2007 Christoph Hellwig <hch@lst.de>
* Copyright (c) 2008-2009 Weongyo Jeong <weongyo@freebsd.org>
* Copyright (c) 2012 Pontus Fuchs <pontus.fuchs@gmail.com>
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
/*
* This driver is based on the uath driver written by Damien Bergamini for
* OpenBSD, who did black-box analysis of the Windows binary driver to find
* out how the hardware works. It contains a lot magic numbers because of
* that and only has minimal functionality.
*/
#include <linux/compiler.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/list.h>
#include <linux/completion.h>
#include <linux/firmware.h>
#include <linux/skbuff.h>
#include <linux/usb.h>
#include <net/mac80211.h>
#include "ar5523.h"
#include "ar5523_hw.h"
/*
* Various supported device vendors/products.
* UB51: AR5005UG 802.11b/g, UB52: AR5005UX 802.11a/b/g
*/
static int ar5523_submit_rx_cmd(struct ar5523 *ar);
static void ar5523_data_tx_pkt_put(struct ar5523 *ar);
static void ar5523_read_reply(struct ar5523 *ar, struct ar5523_cmd_hdr *hdr,
struct ar5523_tx_cmd *cmd)
{
int dlen, olen;
__be32 *rp;
dlen = be32_to_cpu(hdr->len) - sizeof(*hdr);
if (dlen < 0) {
WARN_ON(1);
goto out;
}
ar5523_dbg(ar, "Code = %d len = %d\n", be32_to_cpu(hdr->code) & 0xff,
dlen);
rp = (__be32 *)(hdr + 1);
if (dlen >= sizeof(u32)) {
olen = be32_to_cpu(rp[0]);
dlen -= sizeof(u32);
if (olen == 0) {
/* convention is 0 =>'s one word */
olen = sizeof(u32);
}
} else
olen = 0;
if (cmd->odata) {
if (cmd->olen < olen) {
ar5523_err(ar, "olen too small %d < %d\n",
cmd->olen, olen);
cmd->olen = 0;
cmd->res = -EOVERFLOW;
} else {
cmd->olen = olen;
memcpy(cmd->odata, &rp[1], olen);
cmd->res = 0;
}
}
out:
complete(&cmd->done);
}
static void ar5523_cmd_rx_cb(struct urb *urb)
{
struct ar5523 *ar = urb->context;
struct ar5523_tx_cmd *cmd = &ar->tx_cmd;
struct ar5523_cmd_hdr *hdr = ar->rx_cmd_buf;
int dlen;
u32 code, hdrlen;
if (urb->status) {
if (urb->status != -ESHUTDOWN)
ar5523_err(ar, "RX USB error %d.\n", urb->status);
goto skip;
}
if (urb->actual_length < sizeof(struct ar5523_cmd_hdr)) {
ar5523_err(ar, "RX USB too short.\n");
goto skip;
}
ar5523_dbg(ar, "%s code %02x priv %d\n", __func__,
be32_to_cpu(hdr->code) & 0xff, hdr->priv);
code = be32_to_cpu(hdr->code);
hdrlen = be32_to_cpu(hdr->len);
switch (code & 0xff) {
default:
/* reply to a read command */
if (hdr->priv != AR5523_CMD_ID) {
ar5523_err(ar, "Unexpected command id: %02x\n",
code & 0xff);
goto skip;
}
ar5523_read_reply(ar, hdr, cmd);
break;
case WDCMSG_DEVICE_AVAIL:
ar5523_dbg(ar, "WDCMSG_DEVICE_AVAIL\n");
cmd->res = 0;
cmd->olen = 0;
complete(&cmd->done);
break;
case WDCMSG_SEND_COMPLETE:
ar5523_dbg(ar, "WDCMSG_SEND_COMPLETE: %d pending\n",
atomic_read(&ar->tx_nr_pending));
if (!test_bit(AR5523_HW_UP, &ar->flags))
ar5523_dbg(ar, "Unexpected WDCMSG_SEND_COMPLETE\n");
else {
mod_timer(&ar->tx_wd_timer,
jiffies + AR5523_TX_WD_TIMEOUT);
ar5523_data_tx_pkt_put(ar);
}
break;
case WDCMSG_TARGET_START:
/* This command returns a bogus id so it needs special
handling */
dlen = hdrlen - sizeof(*hdr);
if (dlen != (int)sizeof(u32)) {
ar5523_err(ar, "Invalid reply to WDCMSG_TARGET_START");
return;
}
if (!cmd->odata) {
ar5523_err(ar, "Unexpected WDCMSG_TARGET_START reply");
return;
}
memcpy(cmd->odata, hdr + 1, sizeof(u32));
cmd->olen = sizeof(u32);
cmd->res = 0;
complete(&cmd->done);
break;
case WDCMSG_STATS_UPDATE:
ar5523_dbg(ar, "WDCMSG_STATS_UPDATE\n");
break;
}
skip:
ar5523_submit_rx_cmd(ar);
}
static int ar5523_alloc_rx_cmd(struct ar5523 *ar)
{
ar->rx_cmd_urb = usb_alloc_urb(0, GFP_KERNEL);
if (!ar->rx_cmd_urb)
return -ENOMEM;
ar->rx_cmd_buf = usb_alloc_coherent(ar->dev, AR5523_MAX_RXCMDSZ,
GFP_KERNEL,
&ar->rx_cmd_urb->transfer_dma);
if (!ar->rx_cmd_buf) {
usb_free_urb(ar->rx_cmd_urb);
return -ENOMEM;
}
return 0;
}
static void ar5523_cancel_rx_cmd(struct ar5523 *ar)
{
usb_kill_urb(ar->rx_cmd_urb);
}
static void ar5523_free_rx_cmd(struct ar5523 *ar)
{
usb_free_coherent(ar->dev, AR5523_MAX_RXCMDSZ,
ar->rx_cmd_buf, ar->rx_cmd_urb->transfer_dma);
usb_free_urb(ar->rx_cmd_urb);
}
static int ar5523_submit_rx_cmd(struct ar5523 *ar)
{
int error;
usb_fill_bulk_urb(ar->rx_cmd_urb, ar->dev,
ar5523_cmd_rx_pipe(ar->dev), ar->rx_cmd_buf,
AR5523_MAX_RXCMDSZ, ar5523_cmd_rx_cb, ar);
ar->rx_cmd_urb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP;
error = usb_submit_urb(ar->rx_cmd_urb, GFP_ATOMIC);
if (error) {
if (error != -ENODEV)
ar5523_err(ar, "error %d when submitting rx urb\n",
error);
return error;
}
return 0;
}
/*
* Command submitted cb
*/
static void ar5523_cmd_tx_cb(struct urb *urb)
{
struct ar5523_tx_cmd *cmd = urb->context;
struct ar5523 *ar = cmd->ar;
if (urb->status) {
ar5523_err(ar, "Failed to TX command. Status = %d\n",
urb->status);
cmd->res = urb->status;
complete(&cmd->done);
return;
}
if (!(cmd->flags & AR5523_CMD_FLAG_READ)) {
cmd->res = 0;
complete(&cmd->done);
}
}
wifi: ar5523: Fix use-after-free on ar5523_cmd() timed out syzkaller reported use-after-free with the stack trace like below [1]: [ 38.960489][ C3] ================================================================== [ 38.963216][ C3] BUG: KASAN: use-after-free in ar5523_cmd_tx_cb+0x220/0x240 [ 38.964950][ C3] Read of size 8 at addr ffff888048e03450 by task swapper/3/0 [ 38.966363][ C3] [ 38.967053][ C3] CPU: 3 PID: 0 Comm: swapper/3 Not tainted 6.0.0-09039-ga6afa4199d3d-dirty #18 [ 38.968464][ C3] Hardware name: QEMU Standard PC (i440FX + PIIX, 1996), BIOS 1.16.0-1.fc36 04/01/2014 [ 38.969959][ C3] Call Trace: [ 38.970841][ C3] <IRQ> [ 38.971663][ C3] dump_stack_lvl+0xfc/0x174 [ 38.972620][ C3] print_report.cold+0x2c3/0x752 [ 38.973626][ C3] ? ar5523_cmd_tx_cb+0x220/0x240 [ 38.974644][ C3] kasan_report+0xb1/0x1d0 [ 38.975720][ C3] ? ar5523_cmd_tx_cb+0x220/0x240 [ 38.976831][ C3] ar5523_cmd_tx_cb+0x220/0x240 [ 38.978412][ C3] __usb_hcd_giveback_urb+0x353/0x5b0 [ 38.979755][ C3] usb_hcd_giveback_urb+0x385/0x430 [ 38.981266][ C3] dummy_timer+0x140c/0x34e0 [ 38.982925][ C3] ? notifier_call_chain+0xb5/0x1e0 [ 38.984761][ C3] ? rcu_read_lock_sched_held+0xb/0x60 [ 38.986242][ C3] ? lock_release+0x51c/0x790 [ 38.987323][ C3] ? _raw_read_unlock_irqrestore+0x37/0x70 [ 38.988483][ C3] ? __wake_up_common_lock+0xde/0x130 [ 38.989621][ C3] ? reacquire_held_locks+0x4a0/0x4a0 [ 38.990777][ C3] ? lock_acquire+0x472/0x550 [ 38.991919][ C3] ? rcu_read_lock_sched_held+0xb/0x60 [ 38.993138][ C3] ? lock_acquire+0x472/0x550 [ 38.994890][ C3] ? dummy_urb_enqueue+0x860/0x860 [ 38.996266][ C3] ? do_raw_spin_unlock+0x16f/0x230 [ 38.997670][ C3] ? dummy_urb_enqueue+0x860/0x860 [ 38.999116][ C3] call_timer_fn+0x1a0/0x6a0 [ 39.000668][ C3] ? add_timer_on+0x4a0/0x4a0 [ 39.002137][ C3] ? reacquire_held_locks+0x4a0/0x4a0 [ 39.003809][ C3] ? __next_timer_interrupt+0x226/0x2a0 [ 39.005509][ C3] __run_timers.part.0+0x69a/0xac0 [ 39.007025][ C3] ? dummy_urb_enqueue+0x860/0x860 [ 39.008716][ C3] ? call_timer_fn+0x6a0/0x6a0 [ 39.010254][ C3] ? cpuacct_percpu_seq_show+0x10/0x10 [ 39.011795][ C3] ? kvm_sched_clock_read+0x14/0x40 [ 39.013277][ C3] ? sched_clock_cpu+0x69/0x2b0 [ 39.014724][ C3] run_timer_softirq+0xb6/0x1d0 [ 39.016196][ C3] __do_softirq+0x1d2/0x9be [ 39.017616][ C3] __irq_exit_rcu+0xeb/0x190 [ 39.019004][ C3] irq_exit_rcu+0x5/0x20 [ 39.020361][ C3] sysvec_apic_timer_interrupt+0x8f/0xb0 [ 39.021965][ C3] </IRQ> [ 39.023237][ C3] <TASK> In ar5523_probe(), ar5523_host_available() calls ar5523_cmd() as below (there are other functions which finally call ar5523_cmd()): ar5523_probe() -> ar5523_host_available() -> ar5523_cmd_read() -> ar5523_cmd() If ar5523_cmd() timed out, then ar5523_host_available() failed and ar5523_probe() freed the device structure. So, ar5523_cmd_tx_cb() might touch the freed structure. This patch fixes this issue by canceling in-flight tx cmd if submitted urb timed out. Link: https://syzkaller.appspot.com/bug?id=9e12b2d54300842b71bdd18b54971385ff0d0d3a [1] Reported-by: syzbot+95001b1fd6dfcc716c29@syzkaller.appspotmail.com Signed-off-by: Shigeru Yoshida <syoshida@redhat.com> Signed-off-by: Kalle Valo <quic_kvalo@quicinc.com> Link: https://lore.kernel.org/r/20221009183223.420015-1-syoshida@redhat.com
2022-10-10 03:32:23 +09:00
static void ar5523_cancel_tx_cmd(struct ar5523 *ar)
{
usb_kill_urb(ar->tx_cmd.urb_tx);
}
static int ar5523_cmd(struct ar5523 *ar, u32 code, const void *idata,
int ilen, void *odata, int olen, int flags)
{
struct ar5523_cmd_hdr *hdr;
struct ar5523_tx_cmd *cmd = &ar->tx_cmd;
int xferlen, error;
/* always bulk-out a multiple of 4 bytes */
xferlen = (sizeof(struct ar5523_cmd_hdr) + ilen + 3) & ~3;
hdr = cmd->buf_tx;
memset(hdr, 0, sizeof(struct ar5523_cmd_hdr));
hdr->len = cpu_to_be32(xferlen);
hdr->code = cpu_to_be32(code);
hdr->priv = AR5523_CMD_ID;
if (flags & AR5523_CMD_FLAG_MAGIC)
hdr->magic = cpu_to_be32(1 << 24);
if (ilen)
memcpy(hdr + 1, idata, ilen);
cmd->odata = odata;
cmd->olen = olen;
cmd->flags = flags;
ar5523_dbg(ar, "do cmd %02x\n", code);
usb_fill_bulk_urb(cmd->urb_tx, ar->dev, ar5523_cmd_tx_pipe(ar->dev),
cmd->buf_tx, xferlen, ar5523_cmd_tx_cb, cmd);
cmd->urb_tx->transfer_flags |= URB_NO_TRANSFER_DMA_MAP;
error = usb_submit_urb(cmd->urb_tx, GFP_KERNEL);
if (error) {
ar5523_err(ar, "could not send command 0x%x, error=%d\n",
code, error);
return error;
}
if (!wait_for_completion_timeout(&cmd->done, 2 * HZ)) {
wifi: ar5523: Fix use-after-free on ar5523_cmd() timed out syzkaller reported use-after-free with the stack trace like below [1]: [ 38.960489][ C3] ================================================================== [ 38.963216][ C3] BUG: KASAN: use-after-free in ar5523_cmd_tx_cb+0x220/0x240 [ 38.964950][ C3] Read of size 8 at addr ffff888048e03450 by task swapper/3/0 [ 38.966363][ C3] [ 38.967053][ C3] CPU: 3 PID: 0 Comm: swapper/3 Not tainted 6.0.0-09039-ga6afa4199d3d-dirty #18 [ 38.968464][ C3] Hardware name: QEMU Standard PC (i440FX + PIIX, 1996), BIOS 1.16.0-1.fc36 04/01/2014 [ 38.969959][ C3] Call Trace: [ 38.970841][ C3] <IRQ> [ 38.971663][ C3] dump_stack_lvl+0xfc/0x174 [ 38.972620][ C3] print_report.cold+0x2c3/0x752 [ 38.973626][ C3] ? ar5523_cmd_tx_cb+0x220/0x240 [ 38.974644][ C3] kasan_report+0xb1/0x1d0 [ 38.975720][ C3] ? ar5523_cmd_tx_cb+0x220/0x240 [ 38.976831][ C3] ar5523_cmd_tx_cb+0x220/0x240 [ 38.978412][ C3] __usb_hcd_giveback_urb+0x353/0x5b0 [ 38.979755][ C3] usb_hcd_giveback_urb+0x385/0x430 [ 38.981266][ C3] dummy_timer+0x140c/0x34e0 [ 38.982925][ C3] ? notifier_call_chain+0xb5/0x1e0 [ 38.984761][ C3] ? rcu_read_lock_sched_held+0xb/0x60 [ 38.986242][ C3] ? lock_release+0x51c/0x790 [ 38.987323][ C3] ? _raw_read_unlock_irqrestore+0x37/0x70 [ 38.988483][ C3] ? __wake_up_common_lock+0xde/0x130 [ 38.989621][ C3] ? reacquire_held_locks+0x4a0/0x4a0 [ 38.990777][ C3] ? lock_acquire+0x472/0x550 [ 38.991919][ C3] ? rcu_read_lock_sched_held+0xb/0x60 [ 38.993138][ C3] ? lock_acquire+0x472/0x550 [ 38.994890][ C3] ? dummy_urb_enqueue+0x860/0x860 [ 38.996266][ C3] ? do_raw_spin_unlock+0x16f/0x230 [ 38.997670][ C3] ? dummy_urb_enqueue+0x860/0x860 [ 38.999116][ C3] call_timer_fn+0x1a0/0x6a0 [ 39.000668][ C3] ? add_timer_on+0x4a0/0x4a0 [ 39.002137][ C3] ? reacquire_held_locks+0x4a0/0x4a0 [ 39.003809][ C3] ? __next_timer_interrupt+0x226/0x2a0 [ 39.005509][ C3] __run_timers.part.0+0x69a/0xac0 [ 39.007025][ C3] ? dummy_urb_enqueue+0x860/0x860 [ 39.008716][ C3] ? call_timer_fn+0x6a0/0x6a0 [ 39.010254][ C3] ? cpuacct_percpu_seq_show+0x10/0x10 [ 39.011795][ C3] ? kvm_sched_clock_read+0x14/0x40 [ 39.013277][ C3] ? sched_clock_cpu+0x69/0x2b0 [ 39.014724][ C3] run_timer_softirq+0xb6/0x1d0 [ 39.016196][ C3] __do_softirq+0x1d2/0x9be [ 39.017616][ C3] __irq_exit_rcu+0xeb/0x190 [ 39.019004][ C3] irq_exit_rcu+0x5/0x20 [ 39.020361][ C3] sysvec_apic_timer_interrupt+0x8f/0xb0 [ 39.021965][ C3] </IRQ> [ 39.023237][ C3] <TASK> In ar5523_probe(), ar5523_host_available() calls ar5523_cmd() as below (there are other functions which finally call ar5523_cmd()): ar5523_probe() -> ar5523_host_available() -> ar5523_cmd_read() -> ar5523_cmd() If ar5523_cmd() timed out, then ar5523_host_available() failed and ar5523_probe() freed the device structure. So, ar5523_cmd_tx_cb() might touch the freed structure. This patch fixes this issue by canceling in-flight tx cmd if submitted urb timed out. Link: https://syzkaller.appspot.com/bug?id=9e12b2d54300842b71bdd18b54971385ff0d0d3a [1] Reported-by: syzbot+95001b1fd6dfcc716c29@syzkaller.appspotmail.com Signed-off-by: Shigeru Yoshida <syoshida@redhat.com> Signed-off-by: Kalle Valo <quic_kvalo@quicinc.com> Link: https://lore.kernel.org/r/20221009183223.420015-1-syoshida@redhat.com
2022-10-10 03:32:23 +09:00
ar5523_cancel_tx_cmd(ar);
cmd->odata = NULL;
ar5523_err(ar, "timeout waiting for command %02x reply\n",
code);
cmd->res = -ETIMEDOUT;
}
return cmd->res;
}
static int ar5523_cmd_write(struct ar5523 *ar, u32 code, const void *data,
int len, int flags)
{
flags &= ~AR5523_CMD_FLAG_READ;
return ar5523_cmd(ar, code, data, len, NULL, 0, flags);
}
static int ar5523_cmd_read(struct ar5523 *ar, u32 code, const void *idata,
int ilen, void *odata, int olen, int flags)
{
flags |= AR5523_CMD_FLAG_READ;
return ar5523_cmd(ar, code, idata, ilen, odata, olen, flags);
}
static int ar5523_config(struct ar5523 *ar, u32 reg, u32 val)
{
struct ar5523_write_mac write;
int error;
write.reg = cpu_to_be32(reg);
write.len = cpu_to_be32(0); /* 0 = single write */
*(__be32 *)write.data = cpu_to_be32(val);
error = ar5523_cmd_write(ar, WDCMSG_TARGET_SET_CONFIG, &write,
3 * sizeof(u32), 0);
if (error != 0)
ar5523_err(ar, "could not write register 0x%02x\n", reg);
return error;
}
static int ar5523_config_multi(struct ar5523 *ar, u32 reg, const void *data,
int len)
{
struct ar5523_write_mac write;
int error;
write.reg = cpu_to_be32(reg);
write.len = cpu_to_be32(len);
memcpy(write.data, data, len);
/* properly handle the case where len is zero (reset) */
error = ar5523_cmd_write(ar, WDCMSG_TARGET_SET_CONFIG, &write,
(len == 0) ? sizeof(u32) : 2 * sizeof(u32) + len, 0);
if (error != 0)
ar5523_err(ar, "could not write %d bytes to register 0x%02x\n",
len, reg);
return error;
}
static int ar5523_get_status(struct ar5523 *ar, u32 which, void *odata,
int olen)
{
int error;
__be32 which_be;
which_be = cpu_to_be32(which);
error = ar5523_cmd_read(ar, WDCMSG_TARGET_GET_STATUS,
&which_be, sizeof(which_be), odata, olen, AR5523_CMD_FLAG_MAGIC);
if (error != 0)
ar5523_err(ar, "could not read EEPROM offset 0x%02x\n", which);
return error;
}
static int ar5523_get_capability(struct ar5523 *ar, u32 cap, u32 *val)
{
int error;
__be32 cap_be, val_be;
cap_be = cpu_to_be32(cap);
error = ar5523_cmd_read(ar, WDCMSG_TARGET_GET_CAPABILITY, &cap_be,
sizeof(cap_be), &val_be, sizeof(__be32),
AR5523_CMD_FLAG_MAGIC);
if (error != 0) {
ar5523_err(ar, "could not read capability %u\n", cap);
return error;
}
*val = be32_to_cpu(val_be);
return error;
}
static int ar5523_get_devcap(struct ar5523 *ar)
{
#define GETCAP(x) do { \
error = ar5523_get_capability(ar, x, &cap); \
if (error != 0) \
return error; \
ar5523_info(ar, "Cap: " \
"%s=0x%08x\n", #x, cap); \
} while (0)
int error;
u32 cap;
/* collect device capabilities */
GETCAP(CAP_TARGET_VERSION);
GETCAP(CAP_TARGET_REVISION);
GETCAP(CAP_MAC_VERSION);
GETCAP(CAP_MAC_REVISION);
GETCAP(CAP_PHY_REVISION);
GETCAP(CAP_ANALOG_5GHz_REVISION);
GETCAP(CAP_ANALOG_2GHz_REVISION);
GETCAP(CAP_REG_DOMAIN);
GETCAP(CAP_REG_CAP_BITS);
GETCAP(CAP_WIRELESS_MODES);
GETCAP(CAP_CHAN_SPREAD_SUPPORT);
GETCAP(CAP_COMPRESS_SUPPORT);
GETCAP(CAP_BURST_SUPPORT);
GETCAP(CAP_FAST_FRAMES_SUPPORT);
GETCAP(CAP_CHAP_TUNING_SUPPORT);
GETCAP(CAP_TURBOG_SUPPORT);
GETCAP(CAP_TURBO_PRIME_SUPPORT);
GETCAP(CAP_DEVICE_TYPE);
GETCAP(CAP_WME_SUPPORT);
GETCAP(CAP_TOTAL_QUEUES);
GETCAP(CAP_CONNECTION_ID_MAX);
GETCAP(CAP_LOW_5GHZ_CHAN);
GETCAP(CAP_HIGH_5GHZ_CHAN);
GETCAP(CAP_LOW_2GHZ_CHAN);
GETCAP(CAP_HIGH_2GHZ_CHAN);
GETCAP(CAP_TWICE_ANTENNAGAIN_5G);
GETCAP(CAP_TWICE_ANTENNAGAIN_2G);
GETCAP(CAP_CIPHER_AES_CCM);
GETCAP(CAP_CIPHER_TKIP);
GETCAP(CAP_MIC_TKIP);
return 0;
}
static int ar5523_set_ledsteady(struct ar5523 *ar, int lednum, int ledmode)
{
struct ar5523_cmd_ledsteady led;
led.lednum = cpu_to_be32(lednum);
led.ledmode = cpu_to_be32(ledmode);
ar5523_dbg(ar, "set %s led %s (steady)\n",
(lednum == UATH_LED_LINK) ? "link" : "activity",
ledmode ? "on" : "off");
return ar5523_cmd_write(ar, WDCMSG_SET_LED_STEADY, &led, sizeof(led),
0);
}
static int ar5523_set_rxfilter(struct ar5523 *ar, u32 bits, u32 op)
{
struct ar5523_cmd_rx_filter rxfilter;
rxfilter.bits = cpu_to_be32(bits);
rxfilter.op = cpu_to_be32(op);
ar5523_dbg(ar, "setting Rx filter=0x%x flags=0x%x\n", bits, op);
return ar5523_cmd_write(ar, WDCMSG_RX_FILTER, &rxfilter,
sizeof(rxfilter), 0);
}
static int ar5523_reset_tx_queues(struct ar5523 *ar)
{
__be32 qid = cpu_to_be32(0);
ar5523_dbg(ar, "resetting Tx queue\n");
return ar5523_cmd_write(ar, WDCMSG_RELEASE_TX_QUEUE,
&qid, sizeof(qid), 0);
}
static int ar5523_set_chan(struct ar5523 *ar)
{
struct ieee80211_conf *conf = &ar->hw->conf;
struct ar5523_cmd_reset reset;
memset(&reset, 0, sizeof(reset));
reset.flags |= cpu_to_be32(UATH_CHAN_2GHZ);
reset.flags |= cpu_to_be32(UATH_CHAN_OFDM);
reset.freq = cpu_to_be32(conf->chandef.chan->center_freq);
reset.maxrdpower = cpu_to_be32(50); /* XXX */
reset.channelchange = cpu_to_be32(1);
reset.keeprccontent = cpu_to_be32(0);
ar5523_dbg(ar, "set chan flags 0x%x freq %d\n",
be32_to_cpu(reset.flags),
conf->chandef.chan->center_freq);
return ar5523_cmd_write(ar, WDCMSG_RESET, &reset, sizeof(reset), 0);
}
static int ar5523_queue_init(struct ar5523 *ar)
{
struct ar5523_cmd_txq_setup qinfo;
ar5523_dbg(ar, "setting up Tx queue\n");
qinfo.qid = cpu_to_be32(0);
qinfo.len = cpu_to_be32(sizeof(qinfo.attr));
qinfo.attr.priority = cpu_to_be32(0); /* XXX */
qinfo.attr.aifs = cpu_to_be32(3);
qinfo.attr.logcwmin = cpu_to_be32(4);
qinfo.attr.logcwmax = cpu_to_be32(10);
qinfo.attr.bursttime = cpu_to_be32(0);
qinfo.attr.mode = cpu_to_be32(0);
qinfo.attr.qflags = cpu_to_be32(1); /* XXX? */
return ar5523_cmd_write(ar, WDCMSG_SETUP_TX_QUEUE, &qinfo,
sizeof(qinfo), 0);
}
static int ar5523_switch_chan(struct ar5523 *ar)
{
int error;
error = ar5523_set_chan(ar);
if (error) {
ar5523_err(ar, "could not set chan, error %d\n", error);
goto out_err;
}
/* reset Tx rings */
error = ar5523_reset_tx_queues(ar);
if (error) {
ar5523_err(ar, "could not reset Tx queues, error %d\n",
error);
goto out_err;
}
/* set Tx rings WME properties */
error = ar5523_queue_init(ar);
if (error)
ar5523_err(ar, "could not init wme, error %d\n", error);
out_err:
return error;
}
static void ar5523_rx_data_put(struct ar5523 *ar,
struct ar5523_rx_data *data)
{
unsigned long flags;
spin_lock_irqsave(&ar->rx_data_list_lock, flags);
list_move(&data->list, &ar->rx_data_free);
spin_unlock_irqrestore(&ar->rx_data_list_lock, flags);
}
static void ar5523_data_rx_cb(struct urb *urb)
{
struct ar5523_rx_data *data = urb->context;
struct ar5523 *ar = data->ar;
struct ar5523_rx_desc *desc;
struct ar5523_chunk *chunk;
struct ieee80211_hw *hw = ar->hw;
struct ieee80211_rx_status *rx_status;
u32 rxlen;
int usblen = urb->actual_length;
int hdrlen, pad;
ar5523_dbg(ar, "%s\n", __func__);
/* sync/async unlink faults aren't errors */
if (urb->status) {
if (urb->status != -ESHUTDOWN)
ar5523_err(ar, "%s: USB err: %d\n", __func__,
urb->status);
goto skip;
}
if (usblen < AR5523_MIN_RXBUFSZ) {
ar5523_err(ar, "RX: wrong xfer size (usblen=%d)\n", usblen);
goto skip;
}
chunk = (struct ar5523_chunk *) data->skb->data;
if (((chunk->flags & UATH_CFLAGS_FINAL) == 0) ||
chunk->seqnum != 0) {
ar5523_dbg(ar, "RX: No final flag. s: %d f: %02x l: %d\n",
chunk->seqnum, chunk->flags,
be16_to_cpu(chunk->length));
goto skip;
}
/* Rx descriptor is located at the end, 32-bit aligned */
desc = (struct ar5523_rx_desc *)
(data->skb->data + usblen - sizeof(struct ar5523_rx_desc));
rxlen = be32_to_cpu(desc->len);
if (rxlen > ar->rxbufsz) {
ar5523_dbg(ar, "RX: Bad descriptor (len=%d)\n",
be32_to_cpu(desc->len));
goto skip;
}
if (!rxlen) {
ar5523_dbg(ar, "RX: rxlen is 0\n");
goto skip;
}
if (be32_to_cpu(desc->status) != 0) {
ar5523_dbg(ar, "Bad RX status (0x%x len = %d). Skip\n",
be32_to_cpu(desc->status), be32_to_cpu(desc->len));
goto skip;
}
skb_reserve(data->skb, sizeof(*chunk));
skb_put(data->skb, rxlen - sizeof(struct ar5523_rx_desc));
hdrlen = ieee80211_get_hdrlen_from_skb(data->skb);
if (!IS_ALIGNED(hdrlen, 4)) {
ar5523_dbg(ar, "eek, alignment workaround activated\n");
pad = ALIGN(hdrlen, 4) - hdrlen;
memmove(data->skb->data + pad, data->skb->data, hdrlen);
skb_pull(data->skb, pad);
skb_put(data->skb, pad);
}
rx_status = IEEE80211_SKB_RXCB(data->skb);
memset(rx_status, 0, sizeof(*rx_status));
rx_status->freq = be32_to_cpu(desc->channel);
rx_status->band = hw->conf.chandef.chan->band;
rx_status->signal = -95 + be32_to_cpu(desc->rssi);
ieee80211_rx_irqsafe(hw, data->skb);
data->skb = NULL;
skip:
if (data->skb) {
dev_kfree_skb_irq(data->skb);
data->skb = NULL;
}
ar5523_rx_data_put(ar, data);
if (atomic_inc_return(&ar->rx_data_free_cnt) >=
AR5523_RX_DATA_REFILL_COUNT &&
test_bit(AR5523_HW_UP, &ar->flags))
queue_work(ar->wq, &ar->rx_refill_work);
}
static void ar5523_rx_refill_work(struct work_struct *work)
{
struct ar5523 *ar = container_of(work, struct ar5523, rx_refill_work);
struct ar5523_rx_data *data;
unsigned long flags;
int error;
ar5523_dbg(ar, "%s\n", __func__);
do {
spin_lock_irqsave(&ar->rx_data_list_lock, flags);
if (!list_empty(&ar->rx_data_free))
data = (struct ar5523_rx_data *) ar->rx_data_free.next;
else
data = NULL;
spin_unlock_irqrestore(&ar->rx_data_list_lock, flags);
if (!data)
goto done;
data->skb = alloc_skb(ar->rxbufsz, GFP_KERNEL);
if (!data->skb) {
ar5523_err(ar, "could not allocate rx skbuff\n");
return;
}
usb_fill_bulk_urb(data->urb, ar->dev,
ar5523_data_rx_pipe(ar->dev), data->skb->data,
ar->rxbufsz, ar5523_data_rx_cb, data);
spin_lock_irqsave(&ar->rx_data_list_lock, flags);
list_move(&data->list, &ar->rx_data_used);
spin_unlock_irqrestore(&ar->rx_data_list_lock, flags);
atomic_dec(&ar->rx_data_free_cnt);
error = usb_submit_urb(data->urb, GFP_KERNEL);
if (error) {
kfree_skb(data->skb);
if (error != -ENODEV)
ar5523_err(ar, "Err sending rx data urb %d\n",
error);
ar5523_rx_data_put(ar, data);
atomic_inc(&ar->rx_data_free_cnt);
return;
}
} while (true);
done:
return;
}
static void ar5523_cancel_rx_bufs(struct ar5523 *ar)
{
struct ar5523_rx_data *data;
unsigned long flags;
do {
spin_lock_irqsave(&ar->rx_data_list_lock, flags);
if (!list_empty(&ar->rx_data_used))
data = (struct ar5523_rx_data *) ar->rx_data_used.next;
else
data = NULL;
spin_unlock_irqrestore(&ar->rx_data_list_lock, flags);
if (!data)
break;
usb_kill_urb(data->urb);
list_move(&data->list, &ar->rx_data_free);
atomic_inc(&ar->rx_data_free_cnt);
} while (data);
}
static void ar5523_free_rx_bufs(struct ar5523 *ar)
{
struct ar5523_rx_data *data;
ar5523_cancel_rx_bufs(ar);
while (!list_empty(&ar->rx_data_free)) {
data = (struct ar5523_rx_data *) ar->rx_data_free.next;
list_del(&data->list);
usb_free_urb(data->urb);
}
}
static int ar5523_alloc_rx_bufs(struct ar5523 *ar)
{
int i;
for (i = 0; i < AR5523_RX_DATA_COUNT; i++) {
struct ar5523_rx_data *data = &ar->rx_data[i];
data->ar = ar;
data->urb = usb_alloc_urb(0, GFP_KERNEL);
if (!data->urb)
goto err;
list_add_tail(&data->list, &ar->rx_data_free);
atomic_inc(&ar->rx_data_free_cnt);
}
return 0;
err:
ar5523_free_rx_bufs(ar);
return -ENOMEM;
}
static void ar5523_data_tx_pkt_put(struct ar5523 *ar)
{
atomic_dec(&ar->tx_nr_total);
if (!atomic_dec_return(&ar->tx_nr_pending)) {
timer_delete(&ar->tx_wd_timer);
wake_up(&ar->tx_flush_waitq);
}
if (atomic_read(&ar->tx_nr_total) < AR5523_TX_DATA_RESTART_COUNT) {
ar5523_dbg(ar, "restart tx queue\n");
ieee80211_wake_queues(ar->hw);
}
}
static void ar5523_data_tx_cb(struct urb *urb)
{
struct sk_buff *skb = urb->context;
struct ieee80211_tx_info *txi = IEEE80211_SKB_CB(skb);
struct ar5523_tx_data *data = (struct ar5523_tx_data *)
txi->driver_data;
struct ar5523 *ar = data->ar;
unsigned long flags;
ar5523_dbg(ar, "data tx urb completed: %d\n", urb->status);
spin_lock_irqsave(&ar->tx_data_list_lock, flags);
list_del(&data->list);
spin_unlock_irqrestore(&ar->tx_data_list_lock, flags);
if (urb->status) {
ar5523_dbg(ar, "%s: urb status: %d\n", __func__, urb->status);
ar5523_data_tx_pkt_put(ar);
ieee80211_free_txskb(ar->hw, skb);
} else {
skb_pull(skb, sizeof(struct ar5523_tx_desc) + sizeof(__be32));
ieee80211_tx_status_irqsafe(ar->hw, skb);
}
usb_free_urb(urb);
}
static void ar5523_tx(struct ieee80211_hw *hw,
struct ieee80211_tx_control *control,
struct sk_buff *skb)
{
struct ieee80211_tx_info *txi = IEEE80211_SKB_CB(skb);
struct ar5523_tx_data *data = (struct ar5523_tx_data *)
txi->driver_data;
struct ar5523 *ar = hw->priv;
unsigned long flags;
ar5523_dbg(ar, "tx called\n");
if (atomic_inc_return(&ar->tx_nr_total) >= AR5523_TX_DATA_COUNT) {
ar5523_dbg(ar, "tx queue full\n");
ar5523_dbg(ar, "stop queues (tot %d pend %d)\n",
atomic_read(&ar->tx_nr_total),
atomic_read(&ar->tx_nr_pending));
ieee80211_stop_queues(hw);
}
spin_lock_irqsave(&ar->tx_data_list_lock, flags);
list_add_tail(&data->list, &ar->tx_queue_pending);
spin_unlock_irqrestore(&ar->tx_data_list_lock, flags);
ieee80211_queue_work(ar->hw, &ar->tx_work);
}
static void ar5523_tx_work_locked(struct ar5523 *ar)
{
struct ar5523_tx_data *data;
struct ar5523_tx_desc *desc;
struct ar5523_chunk *chunk;
struct ieee80211_tx_info *txi;
struct urb *urb;
struct sk_buff *skb;
int error = 0, paylen;
u32 txqid;
unsigned long flags;
BUILD_BUG_ON(sizeof(struct ar5523_tx_data) >
IEEE80211_TX_INFO_DRIVER_DATA_SIZE);
ar5523_dbg(ar, "%s\n", __func__);
do {
spin_lock_irqsave(&ar->tx_data_list_lock, flags);
if (!list_empty(&ar->tx_queue_pending)) {
data = (struct ar5523_tx_data *)
ar->tx_queue_pending.next;
list_del(&data->list);
} else
data = NULL;
spin_unlock_irqrestore(&ar->tx_data_list_lock, flags);
if (!data)
break;
txi = container_of((void *)data, struct ieee80211_tx_info,
driver_data);
txqid = 0;
skb = container_of((void *)txi, struct sk_buff, cb);
paylen = skb->len;
urb = usb_alloc_urb(0, GFP_KERNEL);
if (!urb) {
ieee80211_free_txskb(ar->hw, skb);
continue;
}
data->ar = ar;
data->urb = urb;
desc = skb_push(skb, sizeof(*desc));
chunk = skb_push(skb, sizeof(*chunk));
chunk->seqnum = 0;
chunk->flags = UATH_CFLAGS_FINAL;
chunk->length = cpu_to_be16(skb->len);
desc->msglen = cpu_to_be32(skb->len);
desc->msgid = AR5523_DATA_ID;
desc->buflen = cpu_to_be32(paylen);
desc->type = cpu_to_be32(WDCMSG_SEND);
desc->flags = cpu_to_be32(UATH_TX_NOTIFY);
if (test_bit(AR5523_CONNECTED, &ar->flags))
desc->connid = cpu_to_be32(AR5523_ID_BSS);
else
desc->connid = cpu_to_be32(AR5523_ID_BROADCAST);
if (txi->flags & IEEE80211_TX_CTL_USE_MINRATE)
txqid |= UATH_TXQID_MINRATE;
desc->txqid = cpu_to_be32(txqid);
urb->transfer_flags = URB_ZERO_PACKET;
usb_fill_bulk_urb(urb, ar->dev, ar5523_data_tx_pipe(ar->dev),
skb->data, skb->len, ar5523_data_tx_cb, skb);
spin_lock_irqsave(&ar->tx_data_list_lock, flags);
list_add_tail(&data->list, &ar->tx_queue_submitted);
spin_unlock_irqrestore(&ar->tx_data_list_lock, flags);
mod_timer(&ar->tx_wd_timer, jiffies + AR5523_TX_WD_TIMEOUT);
atomic_inc(&ar->tx_nr_pending);
ar5523_dbg(ar, "TX Frame (%d pending)\n",
atomic_read(&ar->tx_nr_pending));
error = usb_submit_urb(urb, GFP_KERNEL);
if (error) {
ar5523_err(ar, "error %d when submitting tx urb\n",
error);
spin_lock_irqsave(&ar->tx_data_list_lock, flags);
list_del(&data->list);
spin_unlock_irqrestore(&ar->tx_data_list_lock, flags);
atomic_dec(&ar->tx_nr_pending);
ar5523_data_tx_pkt_put(ar);
usb_free_urb(urb);
ieee80211_free_txskb(ar->hw, skb);
}
} while (true);
}
static void ar5523_tx_work(struct work_struct *work)
{
struct ar5523 *ar = container_of(work, struct ar5523, tx_work);
ar5523_dbg(ar, "%s\n", __func__);
mutex_lock(&ar->mutex);
ar5523_tx_work_locked(ar);
mutex_unlock(&ar->mutex);
}
static void ar5523_tx_wd_timer(struct timer_list *t)
{
struct ar5523 *ar = timer_container_of(ar, t, tx_wd_timer);
ar5523_dbg(ar, "TX watchdog timer triggered\n");
ieee80211_queue_work(ar->hw, &ar->tx_wd_work);
}
static void ar5523_tx_wd_work(struct work_struct *work)
{
struct ar5523 *ar = container_of(work, struct ar5523, tx_wd_work);
/* Occasionally the TX queues stop responding. The only way to
* recover seems to be to reset the dongle.
*/
mutex_lock(&ar->mutex);
ar5523_err(ar, "TX queue stuck (tot %d pend %d)\n",
atomic_read(&ar->tx_nr_total),
atomic_read(&ar->tx_nr_pending));
ar5523_err(ar, "Will restart dongle.\n");
ar5523_cmd_write(ar, WDCMSG_TARGET_RESET, NULL, 0, 0);
mutex_unlock(&ar->mutex);
}
static void ar5523_flush_tx(struct ar5523 *ar)
{
ar5523_tx_work_locked(ar);
/* Don't waste time trying to flush if USB is disconnected */
if (test_bit(AR5523_USB_DISCONNECTED, &ar->flags))
return;
if (!wait_event_timeout(ar->tx_flush_waitq,
!atomic_read(&ar->tx_nr_pending), AR5523_FLUSH_TIMEOUT))
ar5523_err(ar, "flush timeout (tot %d pend %d)\n",
atomic_read(&ar->tx_nr_total),
atomic_read(&ar->tx_nr_pending));
}
static void ar5523_free_tx_cmd(struct ar5523 *ar)
{
struct ar5523_tx_cmd *cmd = &ar->tx_cmd;
usb_free_coherent(ar->dev, AR5523_MAX_RXCMDSZ, cmd->buf_tx,
cmd->urb_tx->transfer_dma);
usb_free_urb(cmd->urb_tx);
}
static int ar5523_alloc_tx_cmd(struct ar5523 *ar)
{
struct ar5523_tx_cmd *cmd = &ar->tx_cmd;
cmd->ar = ar;
init_completion(&cmd->done);
cmd->urb_tx = usb_alloc_urb(0, GFP_KERNEL);
if (!cmd->urb_tx)
return -ENOMEM;
cmd->buf_tx = usb_alloc_coherent(ar->dev, AR5523_MAX_TXCMDSZ,
GFP_KERNEL,
&cmd->urb_tx->transfer_dma);
if (!cmd->buf_tx) {
usb_free_urb(cmd->urb_tx);
return -ENOMEM;
}
return 0;
}
/*
* This function is called periodically (every second) when associated to
* query device statistics.
*/
static void ar5523_stat_work(struct work_struct *work)
{
struct ar5523 *ar = container_of(work, struct ar5523, stat_work.work);
int error;
ar5523_dbg(ar, "%s\n", __func__);
mutex_lock(&ar->mutex);
/*
* Send request for statistics asynchronously once a second. This
* seems to be important. Throughput is a lot better if this is done.
*/
error = ar5523_cmd_write(ar, WDCMSG_TARGET_GET_STATS, NULL, 0, 0);
if (error)
ar5523_err(ar, "could not query stats, error %d\n", error);
mutex_unlock(&ar->mutex);
ieee80211_queue_delayed_work(ar->hw, &ar->stat_work, HZ);
}
/*
* Interface routines to the mac80211 stack.
*/
static int ar5523_start(struct ieee80211_hw *hw)
{
struct ar5523 *ar = hw->priv;
int error;
__be32 val;
ar5523_dbg(ar, "start called\n");
mutex_lock(&ar->mutex);
val = cpu_to_be32(0);
ar5523_cmd_write(ar, WDCMSG_BIND, &val, sizeof(val), 0);
/* set MAC address */
ar5523_config_multi(ar, CFG_MAC_ADDR, &ar->hw->wiphy->perm_addr,
ETH_ALEN);
/* XXX honor net80211 state */
ar5523_config(ar, CFG_RATE_CONTROL_ENABLE, 0x00000001);
ar5523_config(ar, CFG_DIVERSITY_CTL, 0x00000001);
ar5523_config(ar, CFG_ABOLT, 0x0000003f);
ar5523_config(ar, CFG_WME_ENABLED, 0x00000000);
ar5523_config(ar, CFG_SERVICE_TYPE, 1);
ar5523_config(ar, CFG_TP_SCALE, 0x00000000);
ar5523_config(ar, CFG_TPC_HALF_DBM5, 0x0000003c);
ar5523_config(ar, CFG_TPC_HALF_DBM2, 0x0000003c);
ar5523_config(ar, CFG_OVERRD_TX_POWER, 0x00000000);
ar5523_config(ar, CFG_GMODE_PROTECTION, 0x00000000);
ar5523_config(ar, CFG_GMODE_PROTECT_RATE_INDEX, 0x00000003);
ar5523_config(ar, CFG_PROTECTION_TYPE, 0x00000000);
ar5523_config(ar, CFG_MODE_CTS, 0x00000002);
error = ar5523_cmd_read(ar, WDCMSG_TARGET_START, NULL, 0,
&val, sizeof(val), AR5523_CMD_FLAG_MAGIC);
if (error) {
ar5523_dbg(ar, "could not start target, error %d\n", error);
goto err;
}
ar5523_dbg(ar, "WDCMSG_TARGET_START returns handle: 0x%x\n",
be32_to_cpu(val));
ar5523_switch_chan(ar);
val = cpu_to_be32(TARGET_DEVICE_AWAKE);
ar5523_cmd_write(ar, WDCMSG_SET_PWR_MODE, &val, sizeof(val), 0);
/* XXX? check */
ar5523_cmd_write(ar, WDCMSG_RESET_KEY_CACHE, NULL, 0, 0);
set_bit(AR5523_HW_UP, &ar->flags);
queue_work(ar->wq, &ar->rx_refill_work);
/* enable Rx */
ar5523_set_rxfilter(ar, 0, UATH_FILTER_OP_INIT);
ar5523_set_rxfilter(ar,
UATH_FILTER_RX_UCAST | UATH_FILTER_RX_MCAST |
UATH_FILTER_RX_BCAST | UATH_FILTER_RX_BEACON,
UATH_FILTER_OP_SET);
ar5523_set_ledsteady(ar, UATH_LED_ACTIVITY, UATH_LED_ON);
ar5523_dbg(ar, "start OK\n");
err:
mutex_unlock(&ar->mutex);
return error;
}
static void ar5523_stop(struct ieee80211_hw *hw, bool suspend)
{
struct ar5523 *ar = hw->priv;
ar5523_dbg(ar, "stop called\n");
cancel_delayed_work_sync(&ar->stat_work);
mutex_lock(&ar->mutex);
clear_bit(AR5523_HW_UP, &ar->flags);
ar5523_set_ledsteady(ar, UATH_LED_LINK, UATH_LED_OFF);
ar5523_set_ledsteady(ar, UATH_LED_ACTIVITY, UATH_LED_OFF);
ar5523_cmd_write(ar, WDCMSG_TARGET_STOP, NULL, 0, 0);
timer_delete_sync(&ar->tx_wd_timer);
cancel_work_sync(&ar->tx_wd_work);
cancel_work_sync(&ar->rx_refill_work);
ar5523_cancel_rx_bufs(ar);
mutex_unlock(&ar->mutex);
}
wifi: cfg80211/mac80211: Add support to get radio index Currently, per-radio attributes are set on per-phy basis, i.e., all the radios present in a wiphy will take attributes values sent from user. But each radio in a wiphy can get different values from userspace based on its requirement. To extend support to set per-radio attributes, add support to get radio index from userspace. Add an NL attribute - NL80211_ATTR_WIPHY_RADIO_INDEX, to get user specified radio index for which attributes should be changed. Pass this to individual drivers, so that the drivers can use this radio index to change per-radio attributes when necessary. Currently, per-radio attributes identified are: NL80211_ATTR_WIPHY_TX_POWER_LEVEL NL80211_ATTR_WIPHY_ANTENNA_TX NL80211_ATTR_WIPHY_ANTENNA_RX NL80211_ATTR_WIPHY_RETRY_SHORT NL80211_ATTR_WIPHY_RETRY_LONG NL80211_ATTR_WIPHY_FRAG_THRESHOLD NL80211_ATTR_WIPHY_RTS_THRESHOLD NL80211_ATTR_WIPHY_COVERAGE_CLASS NL80211_ATTR_TXQ_LIMIT NL80211_ATTR_TXQ_MEMORY_LIMIT NL80211_ATTR_TXQ_QUANTUM By default, the radio index is set to -1. This means the attribute should be treated as a global configuration. If the user has not specified any index, then the radio index passed to individual drivers would be -1. This would indicate that the attribute applies to all radios in that wiphy. Signed-off-by: Roopni Devanathan <quic_rdevanat@quicinc.com> Link: https://patch.msgid.link/20250615082312.619639-2-quic_rdevanat@quicinc.com Signed-off-by: Johannes Berg <johannes.berg@intel.com>
2025-06-15 13:53:09 +05:30
static int ar5523_set_rts_threshold(struct ieee80211_hw *hw, int radio_idx,
u32 value)
{
struct ar5523 *ar = hw->priv;
int ret;
ar5523_dbg(ar, "set_rts_threshold called\n");
mutex_lock(&ar->mutex);
ret = ar5523_config(ar, CFG_USER_RTS_THRESHOLD, value);
mutex_unlock(&ar->mutex);
return ret;
}
static void ar5523_flush(struct ieee80211_hw *hw, struct ieee80211_vif *vif,
u32 queues, bool drop)
{
struct ar5523 *ar = hw->priv;
ar5523_dbg(ar, "flush called\n");
ar5523_flush_tx(ar);
}
static int ar5523_add_interface(struct ieee80211_hw *hw,
struct ieee80211_vif *vif)
{
struct ar5523 *ar = hw->priv;
ar5523_dbg(ar, "add interface called\n");
if (ar->vif) {
ar5523_dbg(ar, "invalid add_interface\n");
return -EOPNOTSUPP;
}
switch (vif->type) {
case NL80211_IFTYPE_STATION:
ar->vif = vif;
break;
default:
return -EOPNOTSUPP;
}
return 0;
}
static void ar5523_remove_interface(struct ieee80211_hw *hw,
struct ieee80211_vif *vif)
{
struct ar5523 *ar = hw->priv;
ar5523_dbg(ar, "remove interface called\n");
ar->vif = NULL;
}
wifi: cfg80211/mac80211: Add support to get radio index Currently, per-radio attributes are set on per-phy basis, i.e., all the radios present in a wiphy will take attributes values sent from user. But each radio in a wiphy can get different values from userspace based on its requirement. To extend support to set per-radio attributes, add support to get radio index from userspace. Add an NL attribute - NL80211_ATTR_WIPHY_RADIO_INDEX, to get user specified radio index for which attributes should be changed. Pass this to individual drivers, so that the drivers can use this radio index to change per-radio attributes when necessary. Currently, per-radio attributes identified are: NL80211_ATTR_WIPHY_TX_POWER_LEVEL NL80211_ATTR_WIPHY_ANTENNA_TX NL80211_ATTR_WIPHY_ANTENNA_RX NL80211_ATTR_WIPHY_RETRY_SHORT NL80211_ATTR_WIPHY_RETRY_LONG NL80211_ATTR_WIPHY_FRAG_THRESHOLD NL80211_ATTR_WIPHY_RTS_THRESHOLD NL80211_ATTR_WIPHY_COVERAGE_CLASS NL80211_ATTR_TXQ_LIMIT NL80211_ATTR_TXQ_MEMORY_LIMIT NL80211_ATTR_TXQ_QUANTUM By default, the radio index is set to -1. This means the attribute should be treated as a global configuration. If the user has not specified any index, then the radio index passed to individual drivers would be -1. This would indicate that the attribute applies to all radios in that wiphy. Signed-off-by: Roopni Devanathan <quic_rdevanat@quicinc.com> Link: https://patch.msgid.link/20250615082312.619639-2-quic_rdevanat@quicinc.com Signed-off-by: Johannes Berg <johannes.berg@intel.com>
2025-06-15 13:53:09 +05:30
static int ar5523_hwconfig(struct ieee80211_hw *hw, int radio_idx, u32 changed)
{
struct ar5523 *ar = hw->priv;
ar5523_dbg(ar, "config called\n");
mutex_lock(&ar->mutex);
if (changed & IEEE80211_CONF_CHANGE_CHANNEL) {
ar5523_dbg(ar, "Do channel switch\n");
ar5523_flush_tx(ar);
ar5523_switch_chan(ar);
}
mutex_unlock(&ar->mutex);
return 0;
}
static int ar5523_get_wlan_mode(struct ar5523 *ar,
struct ieee80211_bss_conf *bss_conf)
{
struct ieee80211_supported_band *band;
int bit;
struct ieee80211_sta *sta;
u32 sta_rate_set;
band = ar->hw->wiphy->bands[ar->hw->conf.chandef.chan->band];
sta = ieee80211_find_sta(ar->vif, bss_conf->bssid);
if (!sta) {
ar5523_info(ar, "STA not found!\n");
return WLAN_MODE_11b;
}
mac80211: prepare sta handling for MLO support Currently in mac80211 each STA object is represented using sta_info datastructure with the associated STA specific information and drivers access ieee80211_sta part of it. With MLO (Multi Link Operation) support being added in 802.11be standard, though the association is logically with a single Multi Link capable STA, at the physical level communication can happen via different advertised links (uniquely identified by Channel, operating class, BSSID) and hence the need to handle multiple link STA parameters within a composite sta_info object called the MLD STA. The different link STA part of MLD STA are identified using the link address which can be same or different as the MLD STA address and unique link id based on the link vif. To support extension of such a model, the sta_info datastructure is modified to hold multiple link STA objects with link specific params currently within sta_info moved to this new structure. Similarly this is done for ieee80211_sta as well which will be accessed within mac80211 as well as by drivers, hence trivial driver changes are expected to support this. For current non MLO supported drivers, only one link STA is present and link information is accessed via 'deflink' member. For MLO drivers, we still need to define the APIs etc. to get the correct link ID and access the correct part of the station info. Currently in mac80211, all link STA info are accessed directly via deflink. These will be updated to access via link pointers indexed by link id with MLO support patches, with link id being 0 for non MLO supported cases. Except for couple of macro related changes, below spatch takes care of updating mac80211 and driver code to access to the link STA info via deflink. @ieee80211_sta@ struct ieee80211_sta *s; struct sta_info *si; identifier var = {supp_rates, ht_cap, vht_cap, he_cap, he_6ghz_capa, eht_cap, rx_nss, bandwidth, txpwr}; @@ ( s-> - var + deflink.var | si->sta. - var + deflink.var ) @sta_info@ struct sta_info *si; identifier var = {gtk, pcpu_rx_stats, rx_stats, rx_stats_avg, status_stats, tx_stats, cur_max_bandwidth}; @@ ( si-> - var + deflink.var ) Signed-off-by: Sriram R <quic_srirrama@quicinc.com> Link: https://lore.kernel.org/r/1649086883-13246-1-git-send-email-quic_srirrama@quicinc.com [remove MLO-drivers notes from commit message, not clear yet; run spatch] Signed-off-by: Johannes Berg <johannes.berg@intel.com>
2022-04-04 21:11:23 +05:30
sta_rate_set = sta->deflink.supp_rates[ar->hw->conf.chandef.chan->band];
for (bit = 0; bit < band->n_bitrates; bit++) {
if (sta_rate_set & 1) {
int rate = band->bitrates[bit].bitrate;
switch (rate) {
case 60:
case 90:
case 120:
case 180:
case 240:
case 360:
case 480:
case 540:
return WLAN_MODE_11g;
}
}
sta_rate_set >>= 1;
}
return WLAN_MODE_11b;
}
static void ar5523_create_rateset(struct ar5523 *ar,
struct ieee80211_bss_conf *bss_conf,
struct ar5523_cmd_rateset *rs,
bool basic)
{
struct ieee80211_supported_band *band;
struct ieee80211_sta *sta;
int bit, i = 0;
u32 sta_rate_set, basic_rate_set;
sta = ieee80211_find_sta(ar->vif, bss_conf->bssid);
basic_rate_set = bss_conf->basic_rates;
if (!sta) {
ar5523_info(ar, "STA not found. Cannot set rates\n");
sta_rate_set = bss_conf->basic_rates;
} else
mac80211: prepare sta handling for MLO support Currently in mac80211 each STA object is represented using sta_info datastructure with the associated STA specific information and drivers access ieee80211_sta part of it. With MLO (Multi Link Operation) support being added in 802.11be standard, though the association is logically with a single Multi Link capable STA, at the physical level communication can happen via different advertised links (uniquely identified by Channel, operating class, BSSID) and hence the need to handle multiple link STA parameters within a composite sta_info object called the MLD STA. The different link STA part of MLD STA are identified using the link address which can be same or different as the MLD STA address and unique link id based on the link vif. To support extension of such a model, the sta_info datastructure is modified to hold multiple link STA objects with link specific params currently within sta_info moved to this new structure. Similarly this is done for ieee80211_sta as well which will be accessed within mac80211 as well as by drivers, hence trivial driver changes are expected to support this. For current non MLO supported drivers, only one link STA is present and link information is accessed via 'deflink' member. For MLO drivers, we still need to define the APIs etc. to get the correct link ID and access the correct part of the station info. Currently in mac80211, all link STA info are accessed directly via deflink. These will be updated to access via link pointers indexed by link id with MLO support patches, with link id being 0 for non MLO supported cases. Except for couple of macro related changes, below spatch takes care of updating mac80211 and driver code to access to the link STA info via deflink. @ieee80211_sta@ struct ieee80211_sta *s; struct sta_info *si; identifier var = {supp_rates, ht_cap, vht_cap, he_cap, he_6ghz_capa, eht_cap, rx_nss, bandwidth, txpwr}; @@ ( s-> - var + deflink.var | si->sta. - var + deflink.var ) @sta_info@ struct sta_info *si; identifier var = {gtk, pcpu_rx_stats, rx_stats, rx_stats_avg, status_stats, tx_stats, cur_max_bandwidth}; @@ ( si-> - var + deflink.var ) Signed-off-by: Sriram R <quic_srirrama@quicinc.com> Link: https://lore.kernel.org/r/1649086883-13246-1-git-send-email-quic_srirrama@quicinc.com [remove MLO-drivers notes from commit message, not clear yet; run spatch] Signed-off-by: Johannes Berg <johannes.berg@intel.com>
2022-04-04 21:11:23 +05:30
sta_rate_set = sta->deflink.supp_rates[ar->hw->conf.chandef.chan->band];
ar5523_dbg(ar, "sta rate_set = %08x\n", sta_rate_set);
band = ar->hw->wiphy->bands[ar->hw->conf.chandef.chan->band];
for (bit = 0; bit < band->n_bitrates; bit++) {
BUG_ON(i >= AR5523_MAX_NRATES);
ar5523_dbg(ar, "Considering rate %d : %d\n",
band->bitrates[bit].hw_value, sta_rate_set & 1);
if (sta_rate_set & 1) {
rs->set[i] = band->bitrates[bit].hw_value;
if (basic_rate_set & 1 && basic)
rs->set[i] |= 0x80;
i++;
}
sta_rate_set >>= 1;
basic_rate_set >>= 1;
}
rs->length = i;
}
static int ar5523_set_basic_rates(struct ar5523 *ar,
struct ieee80211_bss_conf *bss)
{
struct ar5523_cmd_rates rates;
memset(&rates, 0, sizeof(rates));
rates.connid = cpu_to_be32(2); /* XXX */
rates.size = cpu_to_be32(sizeof(struct ar5523_cmd_rateset));
ar5523_create_rateset(ar, bss, &rates.rateset, true);
return ar5523_cmd_write(ar, WDCMSG_SET_BASIC_RATE, &rates,
sizeof(rates), 0);
}
static int ar5523_create_connection(struct ar5523 *ar,
struct ieee80211_vif *vif,
struct ieee80211_bss_conf *bss)
{
struct ar5523_cmd_create_connection create;
int wlan_mode;
memset(&create, 0, sizeof(create));
create.connid = cpu_to_be32(2);
create.bssid = cpu_to_be32(0);
/* XXX packed or not? */
create.size = cpu_to_be32(sizeof(struct ar5523_cmd_rateset));
ar5523_create_rateset(ar, bss, &create.connattr.rateset, false);
wlan_mode = ar5523_get_wlan_mode(ar, bss);
create.connattr.wlanmode = cpu_to_be32(wlan_mode);
return ar5523_cmd_write(ar, WDCMSG_CREATE_CONNECTION, &create,
sizeof(create), 0);
}
static int ar5523_write_associd(struct ar5523 *ar, struct ieee80211_vif *vif)
{
struct ieee80211_bss_conf *bss = &vif->bss_conf;
struct ar5523_cmd_set_associd associd;
memset(&associd, 0, sizeof(associd));
associd.defaultrateix = cpu_to_be32(0); /* XXX */
associd.associd = cpu_to_be32(vif->cfg.aid);
associd.timoffset = cpu_to_be32(0x3b); /* XXX */
memcpy(associd.bssid, bss->bssid, ETH_ALEN);
return ar5523_cmd_write(ar, WDCMSG_WRITE_ASSOCID, &associd,
sizeof(associd), 0);
}
static void ar5523_bss_info_changed(struct ieee80211_hw *hw,
struct ieee80211_vif *vif,
struct ieee80211_bss_conf *bss,
u64 changed)
{
struct ar5523 *ar = hw->priv;
int error;
ar5523_dbg(ar, "bss_info_changed called\n");
mutex_lock(&ar->mutex);
if (!(changed & BSS_CHANGED_ASSOC))
goto out_unlock;
if (vif->cfg.assoc) {
error = ar5523_create_connection(ar, vif, bss);
if (error) {
ar5523_err(ar, "could not create connection\n");
goto out_unlock;
}
error = ar5523_set_basic_rates(ar, bss);
if (error) {
ar5523_err(ar, "could not set negotiated rate set\n");
goto out_unlock;
}
error = ar5523_write_associd(ar, vif);
if (error) {
ar5523_err(ar, "could not set association\n");
goto out_unlock;
}
/* turn link LED on */
ar5523_set_ledsteady(ar, UATH_LED_LINK, UATH_LED_ON);
set_bit(AR5523_CONNECTED, &ar->flags);
ieee80211_queue_delayed_work(hw, &ar->stat_work, HZ);
} else {
cancel_delayed_work(&ar->stat_work);
clear_bit(AR5523_CONNECTED, &ar->flags);
ar5523_set_ledsteady(ar, UATH_LED_LINK, UATH_LED_OFF);
}
out_unlock:
mutex_unlock(&ar->mutex);
}
#define AR5523_SUPPORTED_FILTERS (FIF_ALLMULTI | \
FIF_FCSFAIL | \
FIF_OTHER_BSS)
static void ar5523_configure_filter(struct ieee80211_hw *hw,
unsigned int changed_flags,
unsigned int *total_flags,
u64 multicast)
{
struct ar5523 *ar = hw->priv;
u32 filter = 0;
ar5523_dbg(ar, "configure_filter called\n");
mutex_lock(&ar->mutex);
ar5523_flush_tx(ar);
*total_flags &= AR5523_SUPPORTED_FILTERS;
/* The filters seems strange. UATH_FILTER_RX_BCAST and
* UATH_FILTER_RX_MCAST does not result in those frames being RXed.
* The only way I have found to get [mb]cast frames seems to be
* to set UATH_FILTER_RX_PROM. */
filter |= UATH_FILTER_RX_UCAST | UATH_FILTER_RX_MCAST |
UATH_FILTER_RX_BCAST | UATH_FILTER_RX_BEACON |
UATH_FILTER_RX_PROM;
ar5523_set_rxfilter(ar, 0, UATH_FILTER_OP_INIT);
ar5523_set_rxfilter(ar, filter, UATH_FILTER_OP_SET);
mutex_unlock(&ar->mutex);
}
static const struct ieee80211_ops ar5523_ops = {
.add_chanctx = ieee80211_emulate_add_chanctx,
.remove_chanctx = ieee80211_emulate_remove_chanctx,
.change_chanctx = ieee80211_emulate_change_chanctx,
.switch_vif_chanctx = ieee80211_emulate_switch_vif_chanctx,
.start = ar5523_start,
.stop = ar5523_stop,
.tx = ar5523_tx,
.wake_tx_queue = ieee80211_handle_wake_tx_queue,
.set_rts_threshold = ar5523_set_rts_threshold,
.add_interface = ar5523_add_interface,
.remove_interface = ar5523_remove_interface,
.config = ar5523_hwconfig,
.bss_info_changed = ar5523_bss_info_changed,
.configure_filter = ar5523_configure_filter,
.flush = ar5523_flush,
};
static int ar5523_host_available(struct ar5523 *ar)
{
struct ar5523_cmd_host_available setup;
/* inform target the host is available */
setup.sw_ver_major = cpu_to_be32(ATH_SW_VER_MAJOR);
setup.sw_ver_minor = cpu_to_be32(ATH_SW_VER_MINOR);
setup.sw_ver_patch = cpu_to_be32(ATH_SW_VER_PATCH);
setup.sw_ver_build = cpu_to_be32(ATH_SW_VER_BUILD);
return ar5523_cmd_read(ar, WDCMSG_HOST_AVAILABLE,
&setup, sizeof(setup), NULL, 0, 0);
}
static int ar5523_get_devstatus(struct ar5523 *ar)
{
u8 macaddr[ETH_ALEN];
int error;
/* retrieve MAC address */
error = ar5523_get_status(ar, ST_MAC_ADDR, macaddr, ETH_ALEN);
if (error) {
ar5523_err(ar, "could not read MAC address\n");
return error;
}
SET_IEEE80211_PERM_ADDR(ar->hw, macaddr);
error = ar5523_get_status(ar, ST_SERIAL_NUMBER,
&ar->serial[0], sizeof(ar->serial));
if (error) {
ar5523_err(ar, "could not read device serial number\n");
return error;
}
return 0;
}
#define AR5523_SANE_RXBUFSZ 2000
static int ar5523_get_max_rxsz(struct ar5523 *ar)
{
int error;
__be32 rxsize;
/* Get max rx size */
error = ar5523_get_status(ar, ST_WDC_TRANSPORT_CHUNK_SIZE, &rxsize,
sizeof(rxsize));
if (error != 0) {
ar5523_err(ar, "could not read max RX size\n");
return error;
}
ar->rxbufsz = be32_to_cpu(rxsize);
if (!ar->rxbufsz || ar->rxbufsz > AR5523_SANE_RXBUFSZ) {
ar5523_err(ar, "Bad rxbufsz from device. Using %d instead\n",
AR5523_SANE_RXBUFSZ);
ar->rxbufsz = AR5523_SANE_RXBUFSZ;
}
ar5523_dbg(ar, "Max RX buf size: %d\n", ar->rxbufsz);
return 0;
}
/*
* This is copied from rtl818x, but we should probably move this
* to common code as in OpenBSD.
*/
static const struct ieee80211_rate ar5523_rates[] = {
{ .bitrate = 10, .hw_value = 2, },
{ .bitrate = 20, .hw_value = 4 },
{ .bitrate = 55, .hw_value = 11, },
{ .bitrate = 110, .hw_value = 22, },
{ .bitrate = 60, .hw_value = 12, },
{ .bitrate = 90, .hw_value = 18, },
{ .bitrate = 120, .hw_value = 24, },
{ .bitrate = 180, .hw_value = 36, },
{ .bitrate = 240, .hw_value = 48, },
{ .bitrate = 360, .hw_value = 72, },
{ .bitrate = 480, .hw_value = 96, },
{ .bitrate = 540, .hw_value = 108, },
};
static const struct ieee80211_channel ar5523_channels[] = {
{ .center_freq = 2412 },
{ .center_freq = 2417 },
{ .center_freq = 2422 },
{ .center_freq = 2427 },
{ .center_freq = 2432 },
{ .center_freq = 2437 },
{ .center_freq = 2442 },
{ .center_freq = 2447 },
{ .center_freq = 2452 },
{ .center_freq = 2457 },
{ .center_freq = 2462 },
{ .center_freq = 2467 },
{ .center_freq = 2472 },
{ .center_freq = 2484 },
};
static int ar5523_init_modes(struct ar5523 *ar)
{
BUILD_BUG_ON(sizeof(ar->channels) != sizeof(ar5523_channels));
BUILD_BUG_ON(sizeof(ar->rates) != sizeof(ar5523_rates));
memcpy(ar->channels, ar5523_channels, sizeof(ar5523_channels));
memcpy(ar->rates, ar5523_rates, sizeof(ar5523_rates));
ar->band.band = NL80211_BAND_2GHZ;
ar->band.channels = ar->channels;
ar->band.n_channels = ARRAY_SIZE(ar5523_channels);
ar->band.bitrates = ar->rates;
ar->band.n_bitrates = ARRAY_SIZE(ar5523_rates);
ar->hw->wiphy->bands[NL80211_BAND_2GHZ] = &ar->band;
return 0;
}
/*
* Load the MIPS R4000 microcode into the device. Once the image is loaded,
* the device will detach itself from the bus and reattach later with a new
* product Id (a la ezusb).
*/
static int ar5523_load_firmware(struct usb_device *dev)
{
struct ar5523_fwblock *txblock, *rxblock;
const struct firmware *fw;
void *fwbuf;
int len, offset;
int foolen; /* XXX(hch): handle short transfers */
int error = -ENXIO;
if (request_firmware(&fw, AR5523_FIRMWARE_FILE, &dev->dev)) {
dev_err(&dev->dev, "no firmware found: %s\n",
AR5523_FIRMWARE_FILE);
return -ENOENT;
}
txblock = kzalloc(sizeof(*txblock), GFP_KERNEL);
if (!txblock)
goto out;
rxblock = kmalloc(sizeof(*rxblock), GFP_KERNEL);
if (!rxblock)
goto out_free_txblock;
fwbuf = kmalloc(AR5523_MAX_FWBLOCK_SIZE, GFP_KERNEL);
if (!fwbuf)
goto out_free_rxblock;
txblock->flags = cpu_to_be32(AR5523_WRITE_BLOCK);
txblock->total = cpu_to_be32(fw->size);
offset = 0;
len = fw->size;
while (len > 0) {
int mlen = min(len, AR5523_MAX_FWBLOCK_SIZE);
txblock->remain = cpu_to_be32(len - mlen);
txblock->len = cpu_to_be32(mlen);
/* send firmware block meta-data */
error = usb_bulk_msg(dev, ar5523_cmd_tx_pipe(dev),
txblock, sizeof(*txblock), &foolen,
AR5523_CMD_TIMEOUT);
if (error) {
dev_err(&dev->dev,
"could not send firmware block info\n");
goto out_free_fwbuf;
}
/* send firmware block data */
memcpy(fwbuf, fw->data + offset, mlen);
error = usb_bulk_msg(dev, ar5523_data_tx_pipe(dev),
fwbuf, mlen, &foolen,
AR5523_DATA_TIMEOUT);
if (error) {
dev_err(&dev->dev,
"could not send firmware block data\n");
goto out_free_fwbuf;
}
/* wait for ack from firmware */
error = usb_bulk_msg(dev, ar5523_cmd_rx_pipe(dev),
rxblock, sizeof(*rxblock), &foolen,
AR5523_CMD_TIMEOUT);
if (error) {
dev_err(&dev->dev,
"could not read firmware answer\n");
goto out_free_fwbuf;
}
len -= mlen;
offset += mlen;
}
/*
* Set the error to -ENXIO to make sure we continue probing for
* a driver.
*/
error = -ENXIO;
out_free_fwbuf:
kfree(fwbuf);
out_free_rxblock:
kfree(rxblock);
out_free_txblock:
kfree(txblock);
out:
release_firmware(fw);
return error;
}
static int ar5523_probe(struct usb_interface *intf,
const struct usb_device_id *id)
{
struct usb_device *dev = interface_to_usbdev(intf);
struct ieee80211_hw *hw;
struct ar5523 *ar;
int error = -ENOMEM;
wifi: ar5523: enable proper endpoint verification Syzkaller reports [1] hitting a warning about an endpoint in use not having an expected type to it. Fix the issue by checking for the existence of all proper endpoints with their according types intact. Sadly, this patch has not been tested on real hardware. [1] Syzkaller report: ------------[ cut here ]------------ usb 1-1: BOGUS urb xfer, pipe 3 != type 1 WARNING: CPU: 0 PID: 3643 at drivers/usb/core/urb.c:504 usb_submit_urb+0xed6/0x1880 drivers/usb/core/urb.c:504 ... Call Trace: <TASK> ar5523_cmd+0x41b/0x780 drivers/net/wireless/ath/ar5523/ar5523.c:275 ar5523_cmd_read drivers/net/wireless/ath/ar5523/ar5523.c:302 [inline] ar5523_host_available drivers/net/wireless/ath/ar5523/ar5523.c:1376 [inline] ar5523_probe+0x14b0/0x1d10 drivers/net/wireless/ath/ar5523/ar5523.c:1655 usb_probe_interface+0x30f/0x7f0 drivers/usb/core/driver.c:396 call_driver_probe drivers/base/dd.c:560 [inline] really_probe+0x249/0xb90 drivers/base/dd.c:639 __driver_probe_device+0x1df/0x4d0 drivers/base/dd.c:778 driver_probe_device+0x4c/0x1a0 drivers/base/dd.c:808 __device_attach_driver+0x1d4/0x2e0 drivers/base/dd.c:936 bus_for_each_drv+0x163/0x1e0 drivers/base/bus.c:427 __device_attach+0x1e4/0x530 drivers/base/dd.c:1008 bus_probe_device+0x1e8/0x2a0 drivers/base/bus.c:487 device_add+0xbd9/0x1e90 drivers/base/core.c:3517 usb_set_configuration+0x101d/0x1900 drivers/usb/core/message.c:2170 usb_generic_driver_probe+0xbe/0x100 drivers/usb/core/generic.c:238 usb_probe_device+0xd8/0x2c0 drivers/usb/core/driver.c:293 call_driver_probe drivers/base/dd.c:560 [inline] really_probe+0x249/0xb90 drivers/base/dd.c:639 __driver_probe_device+0x1df/0x4d0 drivers/base/dd.c:778 driver_probe_device+0x4c/0x1a0 drivers/base/dd.c:808 __device_attach_driver+0x1d4/0x2e0 drivers/base/dd.c:936 bus_for_each_drv+0x163/0x1e0 drivers/base/bus.c:427 __device_attach+0x1e4/0x530 drivers/base/dd.c:1008 bus_probe_device+0x1e8/0x2a0 drivers/base/bus.c:487 device_add+0xbd9/0x1e90 drivers/base/core.c:3517 usb_new_device.cold+0x685/0x10ad drivers/usb/core/hub.c:2573 hub_port_connect drivers/usb/core/hub.c:5353 [inline] hub_port_connect_change drivers/usb/core/hub.c:5497 [inline] port_event drivers/usb/core/hub.c:5653 [inline] hub_event+0x26cb/0x45d0 drivers/usb/core/hub.c:5735 process_one_work+0x9bf/0x1710 kernel/workqueue.c:2289 worker_thread+0x669/0x1090 kernel/workqueue.c:2436 kthread+0x2e8/0x3a0 kernel/kthread.c:376 ret_from_fork+0x1f/0x30 arch/x86/entry/entry_64.S:306 </TASK> Reported-and-tested-by: syzbot+1bc2c2afd44f820a669f@syzkaller.appspotmail.com Fixes: b7d572e1871d ("ar5523: Add new driver") Signed-off-by: Nikita Zhandarovich <n.zhandarovich@fintech.ru> Signed-off-by: Kalle Valo <quic_kvalo@quicinc.com> Link: https://msgid.link/20240408121425.29392-1-n.zhandarovich@fintech.ru
2024-04-08 05:14:25 -07:00
static const u8 bulk_ep_addr[] = {
AR5523_CMD_TX_PIPE | USB_DIR_OUT,
AR5523_DATA_TX_PIPE | USB_DIR_OUT,
AR5523_CMD_RX_PIPE | USB_DIR_IN,
AR5523_DATA_RX_PIPE | USB_DIR_IN,
0};
if (!usb_check_bulk_endpoints(intf, bulk_ep_addr)) {
dev_err(&dev->dev,
"Could not find all expected endpoints\n");
error = -ENODEV;
goto out;
}
/*
* Load firmware if the device requires it. This will return
* -ENXIO on success and we'll get called back afer the usb
* id changes to indicate that the firmware is present.
*/
if (id->driver_info & AR5523_FLAG_PRE_FIRMWARE)
return ar5523_load_firmware(dev);
hw = ieee80211_alloc_hw(sizeof(*ar), &ar5523_ops);
if (!hw)
goto out;
SET_IEEE80211_DEV(hw, &intf->dev);
ar = hw->priv;
ar->hw = hw;
ar->dev = dev;
mutex_init(&ar->mutex);
INIT_DELAYED_WORK(&ar->stat_work, ar5523_stat_work);
timer_setup(&ar->tx_wd_timer, ar5523_tx_wd_timer, 0);
INIT_WORK(&ar->tx_wd_work, ar5523_tx_wd_work);
INIT_WORK(&ar->tx_work, ar5523_tx_work);
INIT_LIST_HEAD(&ar->tx_queue_pending);
INIT_LIST_HEAD(&ar->tx_queue_submitted);
spin_lock_init(&ar->tx_data_list_lock);
atomic_set(&ar->tx_nr_total, 0);
atomic_set(&ar->tx_nr_pending, 0);
init_waitqueue_head(&ar->tx_flush_waitq);
atomic_set(&ar->rx_data_free_cnt, 0);
INIT_WORK(&ar->rx_refill_work, ar5523_rx_refill_work);
INIT_LIST_HEAD(&ar->rx_data_free);
INIT_LIST_HEAD(&ar->rx_data_used);
spin_lock_init(&ar->rx_data_list_lock);
ar->wq = create_singlethread_workqueue("ar5523");
if (!ar->wq) {
ar5523_err(ar, "Could not create wq\n");
goto out_free_ar;
}
error = ar5523_alloc_rx_bufs(ar);
if (error) {
ar5523_err(ar, "Could not allocate rx buffers\n");
goto out_free_wq;
}
error = ar5523_alloc_rx_cmd(ar);
if (error) {
ar5523_err(ar, "Could not allocate rx command buffers\n");
goto out_free_rx_bufs;
}
error = ar5523_alloc_tx_cmd(ar);
if (error) {
ar5523_err(ar, "Could not allocate tx command buffers\n");
goto out_free_rx_cmd;
}
error = ar5523_submit_rx_cmd(ar);
if (error) {
ar5523_err(ar, "Failed to submit rx cmd\n");
goto out_free_tx_cmd;
}
/*
* We're now ready to send/receive firmware commands.
*/
error = ar5523_host_available(ar);
if (error) {
ar5523_err(ar, "could not initialize adapter\n");
goto out_cancel_rx_cmd;
}
error = ar5523_get_max_rxsz(ar);
if (error) {
ar5523_err(ar, "could not get caps from adapter\n");
goto out_cancel_rx_cmd;
}
error = ar5523_get_devcap(ar);
if (error) {
ar5523_err(ar, "could not get caps from adapter\n");
goto out_cancel_rx_cmd;
}
error = ar5523_get_devstatus(ar);
if (error != 0) {
ar5523_err(ar, "could not get device status\n");
goto out_cancel_rx_cmd;
}
ar5523_info(ar, "MAC/BBP AR5523, RF AR%c112\n",
(id->driver_info & AR5523_FLAG_ABG) ? '5' : '2');
ar->vif = NULL;
ieee80211_hw_set(hw, HAS_RATE_CONTROL);
ieee80211_hw_set(hw, RX_INCLUDES_FCS);
ieee80211_hw_set(hw, SIGNAL_DBM);
hw->extra_tx_headroom = sizeof(struct ar5523_tx_desc) +
sizeof(struct ar5523_chunk);
hw->wiphy->interface_modes = BIT(NL80211_IFTYPE_STATION);
hw->queues = 1;
error = ar5523_init_modes(ar);
if (error)
goto out_cancel_rx_cmd;
wiphy_ext_feature_set(hw->wiphy, NL80211_EXT_FEATURE_CQM_RSSI_LIST);
usb_set_intfdata(intf, hw);
error = ieee80211_register_hw(hw);
if (error) {
ar5523_err(ar, "could not register device\n");
goto out_cancel_rx_cmd;
}
ar5523_info(ar, "Found and initialized AR5523 device\n");
return 0;
out_cancel_rx_cmd:
ar5523_cancel_rx_cmd(ar);
out_free_tx_cmd:
ar5523_free_tx_cmd(ar);
out_free_rx_cmd:
ar5523_free_rx_cmd(ar);
out_free_rx_bufs:
ar5523_free_rx_bufs(ar);
out_free_wq:
destroy_workqueue(ar->wq);
out_free_ar:
ieee80211_free_hw(hw);
out:
return error;
}
static void ar5523_disconnect(struct usb_interface *intf)
{
struct ieee80211_hw *hw = usb_get_intfdata(intf);
struct ar5523 *ar = hw->priv;
ar5523_dbg(ar, "detaching\n");
set_bit(AR5523_USB_DISCONNECTED, &ar->flags);
ieee80211_unregister_hw(hw);
ar5523_cancel_rx_cmd(ar);
ar5523_free_tx_cmd(ar);
ar5523_free_rx_cmd(ar);
ar5523_free_rx_bufs(ar);
destroy_workqueue(ar->wq);
ieee80211_free_hw(hw);
usb_set_intfdata(intf, NULL);
}
#define AR5523_DEVICE_UG(vendor, device) \
{ USB_DEVICE((vendor), (device)) }, \
{ USB_DEVICE((vendor), (device) + 1), \
.driver_info = AR5523_FLAG_PRE_FIRMWARE }
#define AR5523_DEVICE_UX(vendor, device) \
{ USB_DEVICE((vendor), (device)), \
.driver_info = AR5523_FLAG_ABG }, \
{ USB_DEVICE((vendor), (device) + 1), \
.driver_info = AR5523_FLAG_ABG|AR5523_FLAG_PRE_FIRMWARE }
static const struct usb_device_id ar5523_id_table[] = {
AR5523_DEVICE_UG(0x168c, 0x0001), /* Atheros / AR5523 */
AR5523_DEVICE_UG(0x0cf3, 0x0001), /* Atheros2 / AR5523_1 */
AR5523_DEVICE_UG(0x0cf3, 0x0003), /* Atheros2 / AR5523_2 */
AR5523_DEVICE_UX(0x0cf3, 0x0005), /* Atheros2 / AR5523_3 */
AR5523_DEVICE_UG(0x0d8e, 0x7801), /* Conceptronic / AR5523_1 */
AR5523_DEVICE_UX(0x0d8e, 0x7811), /* Conceptronic / AR5523_2 */
AR5523_DEVICE_UX(0x2001, 0x3a00), /* Dlink / DWLAG132 */
AR5523_DEVICE_UG(0x2001, 0x3a02), /* Dlink / DWLG132 */
AR5523_DEVICE_UX(0x2001, 0x3a04), /* Dlink / DWLAG122 */
AR5523_DEVICE_UG(0x07d1, 0x3a07), /* D-Link / WUA-2340 rev A1 */
AR5523_DEVICE_UG(0x1690, 0x0712), /* Gigaset / AR5523 */
AR5523_DEVICE_UG(0x1690, 0x0710), /* Gigaset / SMCWUSBTG */
AR5523_DEVICE_UG(0x129b, 0x160b), /* Gigaset / USB stick 108
(CyberTAN Technology) */
AR5523_DEVICE_UG(0x16ab, 0x7801), /* Globalsun / AR5523_1 */
AR5523_DEVICE_UX(0x16ab, 0x7811), /* Globalsun / AR5523_2 */
AR5523_DEVICE_UG(0x0d8e, 0x7802), /* Globalsun / AR5523_3 */
AR5523_DEVICE_UX(0x0846, 0x4300), /* Netgear / WG111U */
AR5523_DEVICE_UG(0x0846, 0x4250), /* Netgear / WG111T */
AR5523_DEVICE_UG(0x0846, 0x5f00), /* Netgear / WPN111 */
AR5523_DEVICE_UG(0x083a, 0x4506), /* SMC / EZ Connect
SMCWUSBT-G2 */
AR5523_DEVICE_UG(0x157e, 0x3006), /* Umedia / AR5523_1, TEW444UBEU*/
AR5523_DEVICE_UX(0x157e, 0x3205), /* Umedia / AR5523_2 */
AR5523_DEVICE_UG(0x1435, 0x0826), /* Wistronneweb / AR5523_1 */
AR5523_DEVICE_UX(0x1435, 0x0828), /* Wistronneweb / AR5523_2 */
AR5523_DEVICE_UG(0x0cde, 0x0012), /* Zcom / AR5523 */
AR5523_DEVICE_UG(0x1385, 0x4250), /* Netgear3 / WG111T (2) */
AR5523_DEVICE_UG(0x1385, 0x5f00), /* Netgear / WPN111 */
AR5523_DEVICE_UG(0x1385, 0x5f02), /* Netgear / WPN111 */
{ }
};
MODULE_DEVICE_TABLE(usb, ar5523_id_table);
static struct usb_driver ar5523_driver = {
.name = "ar5523",
.id_table = ar5523_id_table,
.probe = ar5523_probe,
.disconnect = ar5523_disconnect,
};
module_usb_driver(ar5523_driver);
MODULE_DESCRIPTION("Atheros AR5523 wireless driver");
MODULE_LICENSE("Dual BSD/GPL");
MODULE_FIRMWARE(AR5523_FIRMWARE_FILE);