Commit 75388acd authored by Larry Finger's avatar Larry Finger Committed by David S. Miller
Browse files

[B43LEGACY]: add mac80211-based driver for legacy BCM43xx devices


Signed-off-by: default avatarLarry Finger <Larry.Finger@lwfinger.net>
Signed-off-by: default avatarJohn W. Linville <linville@tuxdriver.com>
Signed-off-by: default avatarDavid S. Miller <davem@davemloft.net>
parent e4d6b795
......@@ -804,6 +804,13 @@ L: linux-wireless@vger.kernel.org
W: http://bcm43xx.berlios.de/
S: Maintained
B43LEGACY WIRELESS DRIVER
P: Larry Finger
M: Larry.Finger@lwfinger.net
L: linux-wireless@vger.kernel.org
W: http://bcm43xx.berlios.de/
S: Maintained
BCM43XX WIRELESS DRIVER (SOFTMAC BASED VERSION)
P: Larry Finger
M: Larry.Finger@lwfinger.net
......
......@@ -580,6 +580,7 @@ config ADM8211
source "drivers/net/wireless/hostap/Kconfig"
source "drivers/net/wireless/bcm43xx/Kconfig"
source "drivers/net/wireless/b43/Kconfig"
source "drivers/net/wireless/b43legacy/Kconfig"
source "drivers/net/wireless/zd1211rw/Kconfig"
endmenu
......@@ -37,6 +37,7 @@ obj-$(CONFIG_PRISM54) += prism54/
obj-$(CONFIG_HOSTAP) += hostap/
obj-$(CONFIG_BCM43XX) += bcm43xx/
obj-$(CONFIG_B43) += b43/
obj-$(CONFIG_B43LEGACY) += b43legacy/
obj-$(CONFIG_ZD1211RW) += zd1211rw/
# 16-bit wireless PCMCIA client drivers
......
config B43LEGACY
tristate "Broadcom 43xx-legacy wireless support (mac80211 stack)"
depends on SSB_POSSIBLE && MAC80211 && WLAN_80211
select SSB
select FW_LOADER
select HW_RANDOM
---help---
b43legacy is a driver for 802.11b devices from Broadcom (BCM4301 and
BCM4303) and early model 802.11g chips (BCM4306 Ver. 2) used in the
Linksys WPC54G V1 PCMCIA devices.
Newer 802.11g and 802.11a devices need b43.
It is safe to include both b43 and b43legacy as the underlying glue
layer will automatically load the correct version for your device.
This driver uses V3 firmware, which must be installed separately using
b43-fwcutter.
This driver can be built as a module (recommended) that will be
called "b43legacy". If unsure, say M.
# Auto-select SSB PCI-HOST support, if possible
config B43LEGACY_PCI_AUTOSELECT
bool
depends on B43LEGACY && SSB_PCIHOST_POSSIBLE
select SSB_PCIHOST
default y
# Auto-select SSB PCICORE driver, if possible
config B43LEGACY_PCICORE_AUTOSELECT
bool
depends on B43LEGACY && SSB_DRIVER_PCICORE_POSSIBLE
select SSB_DRIVER_PCICORE
default y
config B43LEGACY_DEBUG
bool "Broadcom 43xx-legacy debugging"
depends on B43LEGACY
default y
---help---
Say Y, because this information will help you get the driver running.
This option generates a minimum of log output.
config B43LEGACY_DMA
bool
depends on B43LEGACY
config B43LEGACY_PIO
bool
depends on B43LEGACY
choice
prompt "Broadcom 43xx-legacy data transfer mode"
depends on B43LEGACY
default B43LEGACY_DMA_AND_PIO_MODE
config B43LEGACY_DMA_AND_PIO_MODE
bool "DMA + PIO"
select B43LEGACY_DMA
select B43LEGACY_PIO
---help---
Include both, Direct Memory Access (DMA) and Programmed I/O (PIO)
data transfer modes. The mode actually used is selectable through
the module parameter "pio". With pio=0 as a module parameter, the
default DMA is used, otherwise PIO is used.
If unsure, choose this option.
config B43LEGACY_DMA_MODE
bool "DMA (Direct Memory Access) only"
select B43LEGACY_DMA
---help---
Only include Direct Memory Access (DMA).
This reduces the size of the driver module, by omitting the PIO code.
config B43LEGACY_PIO_MODE
bool "PIO (Programmed I/O) only"
select B43LEGACY_PIO
---help---
Only include Programmed I/O (PIO).
This reduces the size of the driver module, by omitting the DMA code.
Please note that PIO transfers are slow (compared to DMA).
Also note that not all devices of the b43legacy series support PIO.
You should use PIO only if DMA does not work for you.
endchoice
obj-$(CONFIG_B43LEGACY) += b43legacy.o
b43legacy-obj-$(CONFIG_B43LEGACY_DEBUG) += debugfs.o
b43legacy-obj-$(CONFIG_B43LEGACY_DMA) += dma.o
b43legacy-obj-$(CONFIG_B43LEGACY_PIO) += pio.o
b43legacy-objs := main.o \
ilt.o \
leds.o \
phy.o \
radio.o \
sysfs.o \
xmit.o \
$(b43legacy-obj-y)
This diff is collapsed.
/*
Broadcom B43legacy wireless driver
debugfs driver debugging code
Copyright (c) 2005-2007 Michael Buesch <mb@bu3sch.de>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; see the file COPYING. If not, write to
the Free Software Foundation, Inc., 51 Franklin Steet, Fifth Floor,
Boston, MA 02110-1301, USA.
*/
#include <linux/fs.h>
#include <linux/debugfs.h>
#include <linux/slab.h>
#include <linux/netdevice.h>
#include <linux/pci.h>
#include <linux/mutex.h>
#include "b43legacy.h"
#include "main.h"
#include "debugfs.h"
#include "dma.h"
#include "pio.h"
#include "xmit.h"
/* The root directory. */
static struct dentry *rootdir;
struct b43legacy_debugfs_fops {
ssize_t (*read)(struct b43legacy_wldev *dev, char *buf, size_t bufsize);
int (*write)(struct b43legacy_wldev *dev, const char *buf, size_t count);
struct file_operations fops;
/* Offset of struct b43legacy_dfs_file in struct b43legacy_dfsentry */
size_t file_struct_offset;
/* Take wl->irq_lock before calling read/write? */
bool take_irqlock;
};
static inline
struct b43legacy_dfs_file * fops_to_dfs_file(struct b43legacy_wldev *dev,
const struct b43legacy_debugfs_fops *dfops)
{
void *p;
p = dev->dfsentry;
p += dfops->file_struct_offset;
return p;
}
#define fappend(fmt, x...) \
do { \
if (bufsize - count) \
count += snprintf(buf + count, \
bufsize - count, \
fmt , ##x); \
else \
printk(KERN_ERR "b43legacy: fappend overflow\n"); \
} while (0)
/* wl->irq_lock is locked */
static ssize_t tsf_read_file(struct b43legacy_wldev *dev, char *buf, size_t bufsize)
{
ssize_t count = 0;
u64 tsf;
b43legacy_tsf_read(dev, &tsf);
fappend("0x%08x%08x\n",
(unsigned int)((tsf & 0xFFFFFFFF00000000ULL) >> 32),
(unsigned int)(tsf & 0xFFFFFFFFULL));
return count;
}
/* wl->irq_lock is locked */
static int tsf_write_file(struct b43legacy_wldev *dev, const char *buf, size_t count)
{
u64 tsf;
if (sscanf(buf, "%llu", (unsigned long long *)(&tsf)) != 1)
return -EINVAL;
b43legacy_tsf_write(dev, tsf);
return 0;
}
/* wl->irq_lock is locked */
static ssize_t ucode_regs_read_file(struct b43legacy_wldev *dev, char *buf, size_t bufsize)
{
ssize_t count = 0;
int i;
for (i = 0; i < 64; i++) {
fappend("r%d = 0x%04x\n", i,
b43legacy_shm_read16(dev, B43legacy_SHM_WIRELESS, i));
}
return count;
}
/* wl->irq_lock is locked */
static ssize_t shm_read_file(struct b43legacy_wldev *dev, char *buf, size_t bufsize)
{
ssize_t count = 0;
int i;
u16 tmp;
__le16 *le16buf = (__le16 *)buf;
for (i = 0; i < 0x1000; i++) {
if (bufsize <= 0)
break;
tmp = b43legacy_shm_read16(dev, B43legacy_SHM_SHARED, 2 * i);
le16buf[i] = cpu_to_le16(tmp);
count += sizeof(tmp);
bufsize -= sizeof(tmp);
}
return count;
}
static ssize_t txstat_read_file(struct b43legacy_wldev *dev, char *buf, size_t bufsize)
{
struct b43legacy_txstatus_log *log = &dev->dfsentry->txstatlog;
ssize_t count = 0;
unsigned long flags;
int i, idx;
struct b43legacy_txstatus *stat;
spin_lock_irqsave(&log->lock, flags);
if (log->end < 0) {
fappend("Nothing transmitted, yet\n");
goto out_unlock;
}
fappend("b43legacy TX status reports:\n\n"
"index | cookie | seq | phy_stat | frame_count | "
"rts_count | supp_reason | pm_indicated | "
"intermediate | for_ampdu | acked\n" "---\n");
i = log->end + 1;
idx = 0;
while (1) {
if (i == B43legacy_NR_LOGGED_TXSTATUS)
i = 0;
stat = &(log->log[i]);
if (stat->cookie) {
fappend("%03d | "
"0x%04X | 0x%04X | 0x%02X | "
"0x%X | 0x%X | "
"%u | %u | "
"%u | %u | %u\n",
idx,
stat->cookie, stat->seq, stat->phy_stat,
stat->frame_count, stat->rts_count,
stat->supp_reason, stat->pm_indicated,
stat->intermediate, stat->for_ampdu,
stat->acked);
idx++;
}
if (i == log->end)
break;
i++;
}
out_unlock:
spin_unlock_irqrestore(&log->lock, flags);
return count;
}
/* wl->irq_lock is locked */
static int restart_write_file(struct b43legacy_wldev *dev, const char *buf, size_t count)
{
int err = 0;
if (count > 0 && buf[0] == '1') {
b43legacy_controller_restart(dev, "manually restarted");
} else
err = -EINVAL;
return err;
}
#undef fappend
static int b43legacy_debugfs_open(struct inode *inode, struct file *file)
{
file->private_data = inode->i_private;
return 0;
}
static ssize_t b43legacy_debugfs_read(struct file *file, char __user *userbuf,
size_t count, loff_t *ppos)
{
struct b43legacy_wldev *dev;
struct b43legacy_debugfs_fops *dfops;
struct b43legacy_dfs_file *dfile;
ssize_t ret = 0;
char *buf;
const size_t bufsize = 1024 * 128;
const size_t buforder = get_order(bufsize);
int err = 0;
if (!count)
return 0;
dev = file->private_data;
if (!dev)
return -ENODEV;
mutex_lock(&dev->wl->mutex);
if (b43legacy_status(dev) < B43legacy_STAT_INITIALIZED) {
err = -ENODEV;
goto out_unlock;
}
dfops = container_of(file->f_op, struct b43legacy_debugfs_fops, fops);
if (!dfops->read) {
err = -ENOSYS;
goto out_unlock;
}
dfile = fops_to_dfs_file(dev, dfops);
if (!dfile->buffer) {
buf = (char *)__get_free_pages(GFP_KERNEL, buforder);
if (!buf) {
err = -ENOMEM;
goto out_unlock;
}
memset(buf, 0, bufsize);
if (dfops->take_irqlock) {
spin_lock_irq(&dev->wl->irq_lock);
ret = dfops->read(dev, buf, bufsize);
spin_unlock_irq(&dev->wl->irq_lock);
} else
ret = dfops->read(dev, buf, bufsize);
if (ret <= 0) {
free_pages((unsigned long)buf, buforder);
err = ret;
goto out_unlock;
}
dfile->data_len = ret;
dfile->buffer = buf;
}
ret = simple_read_from_buffer(userbuf, count, ppos,
dfile->buffer,
dfile->data_len);
if (*ppos >= dfile->data_len) {
free_pages((unsigned long)dfile->buffer, buforder);
dfile->buffer = NULL;
dfile->data_len = 0;
}
out_unlock:
mutex_unlock(&dev->wl->mutex);
return err ? err : ret;
}
static ssize_t b43legacy_debugfs_write(struct file *file,
const char __user *userbuf,
size_t count, loff_t *ppos)
{
struct b43legacy_wldev *dev;
struct b43legacy_debugfs_fops *dfops;
char *buf;
int err = 0;
if (!count)
return 0;
if (count > PAGE_SIZE)
return -E2BIG;
dev = file->private_data;
if (!dev)
return -ENODEV;
mutex_lock(&dev->wl->mutex);
if (b43legacy_status(dev) < B43legacy_STAT_INITIALIZED) {
err = -ENODEV;
goto out_unlock;
}
dfops = container_of(file->f_op, struct b43legacy_debugfs_fops, fops);
if (!dfops->write) {
err = -ENOSYS;
goto out_unlock;
}
buf = (char *)get_zeroed_page(GFP_KERNEL);
if (!buf) {
err = -ENOMEM;
goto out_unlock;
}
if (copy_from_user(buf, userbuf, count)) {
err = -EFAULT;
goto out_freepage;
}
if (dfops->take_irqlock) {
spin_lock_irq(&dev->wl->irq_lock);
err = dfops->write(dev, buf, count);
spin_unlock_irq(&dev->wl->irq_lock);
} else
err = dfops->write(dev, buf, count);
if (err)
goto out_freepage;
out_freepage:
free_page((unsigned long)buf);
out_unlock:
mutex_unlock(&dev->wl->mutex);
return err ? err : count;
}
#define B43legacy_DEBUGFS_FOPS(name, _read, _write, _take_irqlock) \
static struct b43legacy_debugfs_fops fops_##name = { \
.read = _read, \
.write = _write, \
.fops = { \
.open = b43legacy_debugfs_open, \
.read = b43legacy_debugfs_read, \
.write = b43legacy_debugfs_write, \
}, \
.file_struct_offset = offsetof(struct b43legacy_dfsentry, \
file_##name), \
.take_irqlock = _take_irqlock, \
}
B43legacy_DEBUGFS_FOPS(tsf, tsf_read_file, tsf_write_file, 1);
B43legacy_DEBUGFS_FOPS(ucode_regs, ucode_regs_read_file, NULL, 1);
B43legacy_DEBUGFS_FOPS(shm, shm_read_file, NULL, 1);
B43legacy_DEBUGFS_FOPS(txstat, txstat_read_file, NULL, 0);
B43legacy_DEBUGFS_FOPS(restart, NULL, restart_write_file, 1);
int b43legacy_debug(struct b43legacy_wldev *dev, enum b43legacy_dyndbg feature)
{
return !!(dev->dfsentry && dev->dfsentry->dyn_debug[feature]);
}
static void b43legacy_remove_dynamic_debug(struct b43legacy_wldev *dev)
{
struct b43legacy_dfsentry *e = dev->dfsentry;
int i;
for (i = 0; i < __B43legacy_NR_DYNDBG; i++)
debugfs_remove(e->dyn_debug_dentries[i]);
}
static void b43legacy_add_dynamic_debug(struct b43legacy_wldev *dev)
{
struct b43legacy_dfsentry *e = dev->dfsentry;
struct dentry *d;
#define add_dyn_dbg(name, id, initstate) do { \
e->dyn_debug[id] = (initstate); \
d = debugfs_create_bool(name, 0600, e->subdir, \
&(e->dyn_debug[id])); \
if (!IS_ERR(d)) \
e->dyn_debug_dentries[id] = d; \
} while (0)
add_dyn_dbg("debug_xmitpower", B43legacy_DBG_XMITPOWER, 0);
add_dyn_dbg("debug_dmaoverflow", B43legacy_DBG_DMAOVERFLOW, 0);
add_dyn_dbg("debug_dmaverbose", B43legacy_DBG_DMAVERBOSE, 0);
add_dyn_dbg("debug_pwork_fast", B43legacy_DBG_PWORK_FAST, 0);
add_dyn_dbg("debug_pwork_stop", B43legacy_DBG_PWORK_STOP, 0);
#undef add_dyn_dbg
}
void b43legacy_debugfs_add_device(struct b43legacy_wldev *dev)
{
struct b43legacy_dfsentry *e;
struct b43legacy_txstatus_log *log;
char devdir[16];
B43legacy_WARN_ON(!dev);
e = kzalloc(sizeof(*e), GFP_KERNEL);
if (!e) {
b43legacyerr(dev->wl, "debugfs: add device OOM\n");
return;
}
e->dev = dev;
log = &e->txstatlog;
log->log = kcalloc(B43legacy_NR_LOGGED_TXSTATUS,
sizeof(struct b43legacy_txstatus), GFP_KERNEL);
if (!log->log) {
b43legacyerr(dev->wl, "debugfs: add device txstatus OOM\n");
kfree(e);
return;
}
log->end = -1;
spin_lock_init(&log->lock);
dev->dfsentry = e;
snprintf(devdir, sizeof(devdir), "%s", wiphy_name(dev->wl->hw->wiphy));
e->subdir = debugfs_create_dir(devdir, rootdir);
if (!e->subdir || IS_ERR(e->subdir)) {
if (e->subdir == ERR_PTR(-ENODEV)) {
b43legacydbg(dev->wl, "DebugFS (CONFIG_DEBUG_FS) not "
"enabled in kernel config\n");
} else {
b43legacyerr(dev->wl, "debugfs: cannot create %s directory\n",
devdir);
}
dev->dfsentry = NULL;
kfree(log->log);
kfree(e);
return;
}
#define ADD_FILE(name, mode) \
do { \
struct dentry *d; \
d = debugfs_create_file(__stringify(name), \
mode, e->subdir, dev, \
&fops_##name.fops); \
e->file_##name.dentry = NULL; \
if (!IS_ERR(d)) \
e->file_##name.dentry = d; \
} while (0)
ADD_FILE(tsf, 0600);
ADD_FILE(ucode_regs, 0400);
ADD_FILE(shm, 0400);
ADD_FILE(txstat, 0400);
ADD_FILE(restart, 0200);
#undef ADD_FILE
b43legacy_add_dynamic_debug(dev);
}
void b43legacy_debugfs_remove_device(struct b43legacy_wldev *dev)
{
struct b43legacy_dfsentry *e;
if (!dev)
return;
e = dev->dfsentry;
if (!e)
return;
b43legacy_remove_dynamic_debug(dev);
debugfs_remove(e->file_tsf.dentry);
debugfs_remove(e->file_ucode_regs.dentry);
debugfs_remove(e->file_shm.dentry);
debugfs_remove(e->file_txstat.dentry);
debugfs_remove(e->file_restart.dentry);