add idl4k kernel firmware version 1.13.0.105

This commit is contained in:
Jaroslav Kysela
2015-03-26 17:22:37 +01:00
parent 5194d2792e
commit e9070cdc77
31064 changed files with 12769984 additions and 0 deletions

View File

@@ -0,0 +1,49 @@
config WIMAX_I2400M
tristate
depends on WIMAX
select FW_LOADER
comment "Enable USB support to see WiMAX USB drivers"
depends on USB = n
comment "Enable MMC support to see WiMAX SDIO drivers"
depends on MMC = n
config WIMAX_I2400M_USB
tristate "Intel Wireless WiMAX Connection 2400 over USB (including 5x50)"
depends on WIMAX && USB
select WIMAX_I2400M
help
Select if you have a device based on the Intel WiMAX
Connection 2400 over USB (like any of the Intel Wireless
WiMAX/WiFi Link 5x50 series).
If unsure, it is safe to select M (module).
config WIMAX_I2400M_SDIO
tristate "Intel Wireless WiMAX Connection 2400 over SDIO"
depends on WIMAX && MMC
select WIMAX_I2400M
help
Select if you have a device based on the Intel WiMAX
Connection 2400 over SDIO.
If unsure, it is safe to select M (module).
config WIMAX_I2400M_DEBUG_LEVEL
int "WiMAX i2400m debug level"
depends on WIMAX_I2400M
default 8
help
Select the maximum debug verbosity level to be compiled into
the WiMAX i2400m driver code.
By default, this is disabled at runtime and can be
selectively enabled at runtime for different parts of the
code using the sysfs debug-levels file.
If set at zero, this will compile out all the debug code.
It is recommended that it is left at 8.

View File

@@ -0,0 +1,30 @@
obj-$(CONFIG_WIMAX_I2400M) += i2400m.o
obj-$(CONFIG_WIMAX_I2400M_USB) += i2400m-usb.o
obj-$(CONFIG_WIMAX_I2400M_SDIO) += i2400m-sdio.o
i2400m-y := \
control.o \
driver.o \
fw.o \
op-rfkill.o \
sysfs.o \
netdev.o \
tx.o \
rx.o
i2400m-$(CONFIG_DEBUG_FS) += debugfs.o
i2400m-usb-y := \
usb-fw.o \
usb-notif.o \
usb-tx.o \
usb-rx.o \
usb.o
i2400m-sdio-y := \
sdio.o \
sdio-tx.o \
sdio-fw.o \
sdio-rx.o

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,46 @@
/*
* Intel Wireless WiMAX Connection 2400m
* Debug levels control file for the i2400m module
*
*
* Copyright (C) 2007-2008 Intel Corporation <linux-wimax@intel.com>
* Inaky Perez-Gonzalez <inaky.perez-gonzalez@intel.com>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License version
* 2 as published by the Free Software Foundation.
*
* 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; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
* 02110-1301, USA.
*/
#ifndef __debug_levels__h__
#define __debug_levels__h__
/* Maximum compile and run time debug level for all submodules */
#define D_MODULENAME i2400m
#define D_MASTER CONFIG_WIMAX_I2400M_DEBUG_LEVEL
#include <linux/wimax/debug.h>
/* List of all the enabled modules */
enum d_module {
D_SUBMODULE_DECLARE(control),
D_SUBMODULE_DECLARE(driver),
D_SUBMODULE_DECLARE(debugfs),
D_SUBMODULE_DECLARE(fw),
D_SUBMODULE_DECLARE(netdev),
D_SUBMODULE_DECLARE(rfkill),
D_SUBMODULE_DECLARE(rx),
D_SUBMODULE_DECLARE(sysfs),
D_SUBMODULE_DECLARE(tx),
};
#endif /* #ifndef __debug_levels__h__ */

View File

@@ -0,0 +1,378 @@
/*
* Intel Wireless WiMAX Connection 2400m
* Debugfs interfaces to manipulate driver and device information
*
*
* Copyright (C) 2007 Intel Corporation <linux-wimax@intel.com>
* Inaky Perez-Gonzalez <inaky.perez-gonzalez@intel.com>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License version
* 2 as published by the Free Software Foundation.
*
* 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; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
* 02110-1301, USA.
*/
#include <linux/debugfs.h>
#include <linux/netdevice.h>
#include <linux/etherdevice.h>
#include <linux/spinlock.h>
#include <linux/device.h>
#include "i2400m.h"
#define D_SUBMODULE debugfs
#include "debug-levels.h"
static
int debugfs_netdev_queue_stopped_get(void *data, u64 *val)
{
struct i2400m *i2400m = data;
*val = netif_queue_stopped(i2400m->wimax_dev.net_dev);
return 0;
}
DEFINE_SIMPLE_ATTRIBUTE(fops_netdev_queue_stopped,
debugfs_netdev_queue_stopped_get,
NULL, "%llu\n");
static
struct dentry *debugfs_create_netdev_queue_stopped(
const char *name, struct dentry *parent, struct i2400m *i2400m)
{
return debugfs_create_file(name, 0400, parent, i2400m,
&fops_netdev_queue_stopped);
}
/*
* inode->i_private has the @data argument to debugfs_create_file()
*/
static
int i2400m_stats_open(struct inode *inode, struct file *filp)
{
filp->private_data = inode->i_private;
return 0;
}
/*
* We don't allow partial reads of this file, as then the reader would
* get weirdly confused data as it is updated.
*
* So or you read it all or nothing; if you try to read with an offset
* != 0, we consider you are done reading.
*/
static
ssize_t i2400m_rx_stats_read(struct file *filp, char __user *buffer,
size_t count, loff_t *ppos)
{
struct i2400m *i2400m = filp->private_data;
char buf[128];
unsigned long flags;
if (*ppos != 0)
return 0;
if (count < sizeof(buf))
return -ENOSPC;
spin_lock_irqsave(&i2400m->rx_lock, flags);
snprintf(buf, sizeof(buf), "%u %u %u %u %u %u %u\n",
i2400m->rx_pl_num, i2400m->rx_pl_min,
i2400m->rx_pl_max, i2400m->rx_num,
i2400m->rx_size_acc,
i2400m->rx_size_min, i2400m->rx_size_max);
spin_unlock_irqrestore(&i2400m->rx_lock, flags);
return simple_read_from_buffer(buffer, count, ppos, buf, strlen(buf));
}
/* Any write clears the stats */
static
ssize_t i2400m_rx_stats_write(struct file *filp, const char __user *buffer,
size_t count, loff_t *ppos)
{
struct i2400m *i2400m = filp->private_data;
unsigned long flags;
spin_lock_irqsave(&i2400m->rx_lock, flags);
i2400m->rx_pl_num = 0;
i2400m->rx_pl_max = 0;
i2400m->rx_pl_min = UINT_MAX;
i2400m->rx_num = 0;
i2400m->rx_size_acc = 0;
i2400m->rx_size_min = UINT_MAX;
i2400m->rx_size_max = 0;
spin_unlock_irqrestore(&i2400m->rx_lock, flags);
return count;
}
static
const struct file_operations i2400m_rx_stats_fops = {
.owner = THIS_MODULE,
.open = i2400m_stats_open,
.read = i2400m_rx_stats_read,
.write = i2400m_rx_stats_write,
};
/* See i2400m_rx_stats_read() */
static
ssize_t i2400m_tx_stats_read(struct file *filp, char __user *buffer,
size_t count, loff_t *ppos)
{
struct i2400m *i2400m = filp->private_data;
char buf[128];
unsigned long flags;
if (*ppos != 0)
return 0;
if (count < sizeof(buf))
return -ENOSPC;
spin_lock_irqsave(&i2400m->tx_lock, flags);
snprintf(buf, sizeof(buf), "%u %u %u %u %u %u %u\n",
i2400m->tx_pl_num, i2400m->tx_pl_min,
i2400m->tx_pl_max, i2400m->tx_num,
i2400m->tx_size_acc,
i2400m->tx_size_min, i2400m->tx_size_max);
spin_unlock_irqrestore(&i2400m->tx_lock, flags);
return simple_read_from_buffer(buffer, count, ppos, buf, strlen(buf));
}
/* Any write clears the stats */
static
ssize_t i2400m_tx_stats_write(struct file *filp, const char __user *buffer,
size_t count, loff_t *ppos)
{
struct i2400m *i2400m = filp->private_data;
unsigned long flags;
spin_lock_irqsave(&i2400m->tx_lock, flags);
i2400m->tx_pl_num = 0;
i2400m->tx_pl_max = 0;
i2400m->tx_pl_min = UINT_MAX;
i2400m->tx_num = 0;
i2400m->tx_size_acc = 0;
i2400m->tx_size_min = UINT_MAX;
i2400m->tx_size_max = 0;
spin_unlock_irqrestore(&i2400m->tx_lock, flags);
return count;
}
static
const struct file_operations i2400m_tx_stats_fops = {
.owner = THIS_MODULE,
.open = i2400m_stats_open,
.read = i2400m_tx_stats_read,
.write = i2400m_tx_stats_write,
};
/* Write 1 to ask the device to go into suspend */
static
int debugfs_i2400m_suspend_set(void *data, u64 val)
{
int result;
struct i2400m *i2400m = data;
result = i2400m_cmd_enter_powersave(i2400m);
if (result >= 0)
result = 0;
return result;
}
DEFINE_SIMPLE_ATTRIBUTE(fops_i2400m_suspend,
NULL, debugfs_i2400m_suspend_set,
"%llu\n");
static
struct dentry *debugfs_create_i2400m_suspend(
const char *name, struct dentry *parent, struct i2400m *i2400m)
{
return debugfs_create_file(name, 0200, parent, i2400m,
&fops_i2400m_suspend);
}
/*
* Reset the device
*
* Write 0 to ask the device to soft reset, 1 to cold reset, 2 to bus
* reset (as defined by enum i2400m_reset_type).
*/
static
int debugfs_i2400m_reset_set(void *data, u64 val)
{
int result;
struct i2400m *i2400m = data;
enum i2400m_reset_type rt = val;
switch(rt) {
case I2400M_RT_WARM:
case I2400M_RT_COLD:
case I2400M_RT_BUS:
result = i2400m->bus_reset(i2400m, rt);
if (result >= 0)
result = 0;
default:
result = -EINVAL;
}
return result;
}
DEFINE_SIMPLE_ATTRIBUTE(fops_i2400m_reset,
NULL, debugfs_i2400m_reset_set,
"%llu\n");
static
struct dentry *debugfs_create_i2400m_reset(
const char *name, struct dentry *parent, struct i2400m *i2400m)
{
return debugfs_create_file(name, 0200, parent, i2400m,
&fops_i2400m_reset);
}
#define __debugfs_register(prefix, name, parent) \
do { \
result = d_level_register_debugfs(prefix, name, parent); \
if (result < 0) \
goto error; \
} while (0)
int i2400m_debugfs_add(struct i2400m *i2400m)
{
int result;
struct device *dev = i2400m_dev(i2400m);
struct dentry *dentry = i2400m->wimax_dev.debugfs_dentry;
struct dentry *fd;
dentry = debugfs_create_dir("i2400m", dentry);
result = PTR_ERR(dentry);
if (IS_ERR(dentry)) {
if (result == -ENODEV)
result = 0; /* No debugfs support */
goto error;
}
i2400m->debugfs_dentry = dentry;
__debugfs_register("dl_", control, dentry);
__debugfs_register("dl_", driver, dentry);
__debugfs_register("dl_", debugfs, dentry);
__debugfs_register("dl_", fw, dentry);
__debugfs_register("dl_", netdev, dentry);
__debugfs_register("dl_", rfkill, dentry);
__debugfs_register("dl_", rx, dentry);
__debugfs_register("dl_", tx, dentry);
fd = debugfs_create_size_t("tx_in", 0400, dentry,
&i2400m->tx_in);
result = PTR_ERR(fd);
if (IS_ERR(fd) && result != -ENODEV) {
dev_err(dev, "Can't create debugfs entry "
"tx_in: %d\n", result);
goto error;
}
fd = debugfs_create_size_t("tx_out", 0400, dentry,
&i2400m->tx_out);
result = PTR_ERR(fd);
if (IS_ERR(fd) && result != -ENODEV) {
dev_err(dev, "Can't create debugfs entry "
"tx_out: %d\n", result);
goto error;
}
fd = debugfs_create_u32("state", 0600, dentry,
&i2400m->state);
result = PTR_ERR(fd);
if (IS_ERR(fd) && result != -ENODEV) {
dev_err(dev, "Can't create debugfs entry "
"state: %d\n", result);
goto error;
}
/*
* Trace received messages from user space
*
* In order to tap the bidirectional message stream in the
* 'msg' pipe, user space can read from the 'msg' pipe;
* however, due to limitations in libnl, we can't know what
* the different applications are sending down to the kernel.
*
* So we have this hack where the driver will echo any message
* received on the msg pipe from user space [through a call to
* wimax_dev->op_msg_from_user() into
* i2400m_op_msg_from_user()] into the 'trace' pipe that this
* driver creates.
*
* So then, reading from both the 'trace' and 'msg' pipes in
* user space will provide a full dump of the traffic.
*
* Write 1 to activate, 0 to clear.
*
* It is not really very atomic, but it is also not too
* critical.
*/
fd = debugfs_create_u8("trace_msg_from_user", 0600, dentry,
&i2400m->trace_msg_from_user);
result = PTR_ERR(fd);
if (IS_ERR(fd) && result != -ENODEV) {
dev_err(dev, "Can't create debugfs entry "
"trace_msg_from_user: %d\n", result);
goto error;
}
fd = debugfs_create_netdev_queue_stopped("netdev_queue_stopped",
dentry, i2400m);
result = PTR_ERR(fd);
if (IS_ERR(fd) && result != -ENODEV) {
dev_err(dev, "Can't create debugfs entry "
"netdev_queue_stopped: %d\n", result);
goto error;
}
fd = debugfs_create_file("rx_stats", 0600, dentry, i2400m,
&i2400m_rx_stats_fops);
result = PTR_ERR(fd);
if (IS_ERR(fd) && result != -ENODEV) {
dev_err(dev, "Can't create debugfs entry "
"rx_stats: %d\n", result);
goto error;
}
fd = debugfs_create_file("tx_stats", 0600, dentry, i2400m,
&i2400m_tx_stats_fops);
result = PTR_ERR(fd);
if (IS_ERR(fd) && result != -ENODEV) {
dev_err(dev, "Can't create debugfs entry "
"tx_stats: %d\n", result);
goto error;
}
fd = debugfs_create_i2400m_suspend("suspend", dentry, i2400m);
result = PTR_ERR(fd);
if (IS_ERR(fd) && result != -ENODEV) {
dev_err(dev, "Can't create debugfs entry suspend: %d\n",
result);
goto error;
}
fd = debugfs_create_i2400m_reset("reset", dentry, i2400m);
result = PTR_ERR(fd);
if (IS_ERR(fd) && result != -ENODEV) {
dev_err(dev, "Can't create debugfs entry reset: %d\n", result);
goto error;
}
result = 0;
error:
return result;
}
void i2400m_debugfs_rm(struct i2400m *i2400m)
{
debugfs_remove_recursive(i2400m->debugfs_dentry);
}

View File

@@ -0,0 +1,785 @@
/*
* Intel Wireless WiMAX Connection 2400m
* Generic probe/disconnect, reset and message passing
*
*
* Copyright (C) 2007-2008 Intel Corporation <linux-wimax@intel.com>
* Inaky Perez-Gonzalez <inaky.perez-gonzalez@intel.com>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License version
* 2 as published by the Free Software Foundation.
*
* 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; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
* 02110-1301, USA.
*
*
* See i2400m.h for driver documentation. This contains helpers for
* the driver model glue [_setup()/_release()], handling device resets
* [_dev_reset_handle()], and the backends for the WiMAX stack ops
* reset [_op_reset()] and message from user [_op_msg_from_user()].
*
* ROADMAP:
*
* i2400m_op_msg_from_user()
* i2400m_msg_to_dev()
* wimax_msg_to_user_send()
*
* i2400m_op_reset()
* i240m->bus_reset()
*
* i2400m_dev_reset_handle()
* __i2400m_dev_reset_handle()
* __i2400m_dev_stop()
* __i2400m_dev_start()
*
* i2400m_setup()
* i2400m_bootrom_init()
* register_netdev()
* i2400m_dev_start()
* __i2400m_dev_start()
* i2400m_dev_bootstrap()
* i2400m_tx_setup()
* i2400m->bus_dev_start()
* i2400m_firmware_check()
* i2400m_check_mac_addr()
* wimax_dev_add()
*
* i2400m_release()
* wimax_dev_rm()
* i2400m_dev_stop()
* __i2400m_dev_stop()
* i2400m_dev_shutdown()
* i2400m->bus_dev_stop()
* i2400m_tx_release()
* unregister_netdev()
*/
#include "i2400m.h"
#include <linux/etherdevice.h>
#include <linux/wimax/i2400m.h>
#include <linux/module.h>
#include <linux/moduleparam.h>
#define D_SUBMODULE driver
#include "debug-levels.h"
int i2400m_idle_mode_disabled; /* 0 (idle mode enabled) by default */
module_param_named(idle_mode_disabled, i2400m_idle_mode_disabled, int, 0644);
MODULE_PARM_DESC(idle_mode_disabled,
"If true, the device will not enable idle mode negotiation "
"with the base station (when connected) to save power.");
int i2400m_rx_reorder_disabled; /* 0 (rx reorder enabled) by default */
module_param_named(rx_reorder_disabled, i2400m_rx_reorder_disabled, int, 0644);
MODULE_PARM_DESC(rx_reorder_disabled,
"If true, RX reordering will be disabled.");
int i2400m_power_save_disabled; /* 0 (power saving enabled) by default */
module_param_named(power_save_disabled, i2400m_power_save_disabled, int, 0644);
MODULE_PARM_DESC(power_save_disabled,
"If true, the driver will not tell the device to enter "
"power saving mode when it reports it is ready for it. "
"False by default (so the device is told to do power "
"saving).");
/**
* i2400m_queue_work - schedule work on a i2400m's queue
*
* @i2400m: device descriptor
*
* @fn: function to run to execute work. It gets passed a 'struct
* work_struct' that is wrapped in a 'struct i2400m_work'. Once
* done, you have to (1) i2400m_put(i2400m_work->i2400m) and then
* (2) kfree(i2400m_work).
*
* @gfp_flags: GFP flags for memory allocation.
*
* @pl: pointer to a payload buffer that you want to pass to the _work
* function. Use this to pack (for example) a struct with extra
* arguments.
*
* @pl_size: size of the payload buffer.
*
* We do this quite often, so this just saves typing; allocate a
* wrapper for a i2400m, get a ref to it, pack arguments and launch
* the work.
*
* A usual workflow is:
*
* struct my_work_args {
* void *something;
* int whatever;
* };
* ...
*
* struct my_work_args my_args = {
* .something = FOO,
* .whaetever = BLAH
* };
* i2400m_queue_work(i2400m, 1, my_work_function, GFP_KERNEL,
* &args, sizeof(args))
*
* And now the work function can unpack the arguments and call the
* real function (or do the job itself):
*
* static
* void my_work_fn((struct work_struct *ws)
* {
* struct i2400m_work *iw =
* container_of(ws, struct i2400m_work, ws);
* struct my_work_args *my_args = (void *) iw->pl;
*
* my_work(iw->i2400m, my_args->something, my_args->whatevert);
* }
*/
int i2400m_queue_work(struct i2400m *i2400m,
void (*fn)(struct work_struct *), gfp_t gfp_flags,
const void *pl, size_t pl_size)
{
int result;
struct i2400m_work *iw;
BUG_ON(i2400m->work_queue == NULL);
result = -ENOMEM;
iw = kzalloc(sizeof(*iw) + pl_size, gfp_flags);
if (iw == NULL)
goto error_kzalloc;
iw->i2400m = i2400m_get(i2400m);
memcpy(iw->pl, pl, pl_size);
INIT_WORK(&iw->ws, fn);
result = queue_work(i2400m->work_queue, &iw->ws);
error_kzalloc:
return result;
}
EXPORT_SYMBOL_GPL(i2400m_queue_work);
/*
* Schedule i2400m's specific work on the system's queue.
*
* Used for a few cases where we really need it; otherwise, identical
* to i2400m_queue_work().
*
* Returns < 0 errno code on error, 1 if ok.
*
* If it returns zero, something really bad happened, as it means the
* works struct was already queued, but we have just allocated it, so
* it should not happen.
*/
int i2400m_schedule_work(struct i2400m *i2400m,
void (*fn)(struct work_struct *), gfp_t gfp_flags)
{
int result;
struct i2400m_work *iw;
result = -ENOMEM;
iw = kzalloc(sizeof(*iw), gfp_flags);
if (iw == NULL)
goto error_kzalloc;
iw->i2400m = i2400m_get(i2400m);
INIT_WORK(&iw->ws, fn);
result = schedule_work(&iw->ws);
if (result == 0)
result = -ENXIO;
error_kzalloc:
return result;
}
/*
* WiMAX stack operation: relay a message from user space
*
* @wimax_dev: device descriptor
* @pipe_name: named pipe the message is for
* @msg_buf: pointer to the message bytes
* @msg_len: length of the buffer
* @genl_info: passed by the generic netlink layer
*
* The WiMAX stack will call this function when a message was received
* from user space.
*
* For the i2400m, this is an L3L4 message, as specified in
* include/linux/wimax/i2400m.h, and thus prefixed with a 'struct
* i2400m_l3l4_hdr'. Driver (and device) expect the messages to be
* coded in Little Endian.
*
* This function just verifies that the header declaration and the
* payload are consistent and then deals with it, either forwarding it
* to the device or procesing it locally.
*
* In the i2400m, messages are basically commands that will carry an
* ack, so we use i2400m_msg_to_dev() and then deliver the ack back to
* user space. The rx.c code might intercept the response and use it
* to update the driver's state, but then it will pass it on so it can
* be relayed back to user space.
*
* Note that asynchronous events from the device are processed and
* sent to user space in rx.c.
*/
static
int i2400m_op_msg_from_user(struct wimax_dev *wimax_dev,
const char *pipe_name,
const void *msg_buf, size_t msg_len,
const struct genl_info *genl_info)
{
int result;
struct i2400m *i2400m = wimax_dev_to_i2400m(wimax_dev);
struct device *dev = i2400m_dev(i2400m);
struct sk_buff *ack_skb;
d_fnstart(4, dev, "(wimax_dev %p [i2400m %p] msg_buf %p "
"msg_len %zu genl_info %p)\n", wimax_dev, i2400m,
msg_buf, msg_len, genl_info);
ack_skb = i2400m_msg_to_dev(i2400m, msg_buf, msg_len);
result = PTR_ERR(ack_skb);
if (IS_ERR(ack_skb))
goto error_msg_to_dev;
result = wimax_msg_send(&i2400m->wimax_dev, ack_skb);
error_msg_to_dev:
d_fnend(4, dev, "(wimax_dev %p [i2400m %p] msg_buf %p msg_len %zu "
"genl_info %p) = %d\n", wimax_dev, i2400m, msg_buf, msg_len,
genl_info, result);
return result;
}
/*
* Context to wait for a reset to finalize
*/
struct i2400m_reset_ctx {
struct completion completion;
int result;
};
/*
* WiMAX stack operation: reset a device
*
* @wimax_dev: device descriptor
*
* See the documentation for wimax_reset() and wimax_dev->op_reset for
* the requirements of this function. The WiMAX stack guarantees
* serialization on calls to this function.
*
* Do a warm reset on the device; if it fails, resort to a cold reset
* and return -ENODEV. On successful warm reset, we need to block
* until it is complete.
*
* The bus-driver implementation of reset takes care of falling back
* to cold reset if warm fails.
*/
static
int i2400m_op_reset(struct wimax_dev *wimax_dev)
{
int result;
struct i2400m *i2400m = wimax_dev_to_i2400m(wimax_dev);
struct device *dev = i2400m_dev(i2400m);
struct i2400m_reset_ctx ctx = {
.completion = COMPLETION_INITIALIZER_ONSTACK(ctx.completion),
.result = 0,
};
d_fnstart(4, dev, "(wimax_dev %p)\n", wimax_dev);
mutex_lock(&i2400m->init_mutex);
i2400m->reset_ctx = &ctx;
mutex_unlock(&i2400m->init_mutex);
result = i2400m->bus_reset(i2400m, I2400M_RT_WARM);
if (result < 0)
goto out;
result = wait_for_completion_timeout(&ctx.completion, 4*HZ);
if (result == 0)
result = -ETIMEDOUT;
else if (result > 0)
result = ctx.result;
/* if result < 0, pass it on */
mutex_lock(&i2400m->init_mutex);
i2400m->reset_ctx = NULL;
mutex_unlock(&i2400m->init_mutex);
out:
d_fnend(4, dev, "(wimax_dev %p) = %d\n", wimax_dev, result);
return result;
}
/*
* Check the MAC address we got from boot mode is ok
*
* @i2400m: device descriptor
*
* Returns: 0 if ok, < 0 errno code on error.
*/
static
int i2400m_check_mac_addr(struct i2400m *i2400m)
{
int result;
struct device *dev = i2400m_dev(i2400m);
struct sk_buff *skb;
const struct i2400m_tlv_detailed_device_info *ddi;
struct net_device *net_dev = i2400m->wimax_dev.net_dev;
const unsigned char zeromac[ETH_ALEN] = { 0 };
d_fnstart(3, dev, "(i2400m %p)\n", i2400m);
skb = i2400m_get_device_info(i2400m);
if (IS_ERR(skb)) {
result = PTR_ERR(skb);
dev_err(dev, "Cannot verify MAC address, error reading: %d\n",
result);
goto error;
}
/* Extract MAC addresss */
ddi = (void *) skb->data;
BUILD_BUG_ON(ETH_ALEN != sizeof(ddi->mac_address));
d_printf(2, dev, "GET DEVICE INFO: mac addr "
"%02x:%02x:%02x:%02x:%02x:%02x\n",
ddi->mac_address[0], ddi->mac_address[1],
ddi->mac_address[2], ddi->mac_address[3],
ddi->mac_address[4], ddi->mac_address[5]);
if (!memcmp(net_dev->perm_addr, ddi->mac_address,
sizeof(ddi->mac_address)))
goto ok;
dev_warn(dev, "warning: device reports a different MAC address "
"to that of boot mode's\n");
dev_warn(dev, "device reports %02x:%02x:%02x:%02x:%02x:%02x\n",
ddi->mac_address[0], ddi->mac_address[1],
ddi->mac_address[2], ddi->mac_address[3],
ddi->mac_address[4], ddi->mac_address[5]);
dev_warn(dev, "boot mode reported %02x:%02x:%02x:%02x:%02x:%02x\n",
net_dev->perm_addr[0], net_dev->perm_addr[1],
net_dev->perm_addr[2], net_dev->perm_addr[3],
net_dev->perm_addr[4], net_dev->perm_addr[5]);
if (!memcmp(zeromac, ddi->mac_address, sizeof(zeromac)))
dev_err(dev, "device reports an invalid MAC address, "
"not updating\n");
else {
dev_warn(dev, "updating MAC address\n");
net_dev->addr_len = ETH_ALEN;
memcpy(net_dev->perm_addr, ddi->mac_address, ETH_ALEN);
memcpy(net_dev->dev_addr, ddi->mac_address, ETH_ALEN);
}
ok:
result = 0;
kfree_skb(skb);
error:
d_fnend(3, dev, "(i2400m %p) = %d\n", i2400m, result);
return result;
}
/**
* __i2400m_dev_start - Bring up driver communication with the device
*
* @i2400m: device descriptor
* @flags: boot mode flags
*
* Returns: 0 if ok, < 0 errno code on error.
*
* Uploads firmware and brings up all the resources needed to be able
* to communicate with the device.
*
* The workqueue has to be setup early, at least before RX handling
* (it's only real user for now) so it can process reports as they
* arrive. We also want to destroy it if we retry, to make sure it is
* flushed...easier like this.
*
* TX needs to be setup before the bus-specific code (otherwise on
* shutdown, the bus-tx code could try to access it).
*/
static
int __i2400m_dev_start(struct i2400m *i2400m, enum i2400m_bri flags)
{
int result;
struct wimax_dev *wimax_dev = &i2400m->wimax_dev;
struct net_device *net_dev = wimax_dev->net_dev;
struct device *dev = i2400m_dev(i2400m);
int times = i2400m->bus_bm_retries;
d_fnstart(3, dev, "(i2400m %p)\n", i2400m);
retry:
result = i2400m_dev_bootstrap(i2400m, flags);
if (result < 0) {
dev_err(dev, "cannot bootstrap device: %d\n", result);
goto error_bootstrap;
}
result = i2400m_tx_setup(i2400m);
if (result < 0)
goto error_tx_setup;
result = i2400m_rx_setup(i2400m);
if (result < 0)
goto error_rx_setup;
i2400m->work_queue = create_singlethread_workqueue(wimax_dev->name);
if (i2400m->work_queue == NULL) {
result = -ENOMEM;
dev_err(dev, "cannot create workqueue\n");
goto error_create_workqueue;
}
result = i2400m->bus_dev_start(i2400m);
if (result < 0)
goto error_bus_dev_start;
result = i2400m_firmware_check(i2400m); /* fw versions ok? */
if (result < 0)
goto error_fw_check;
/* At this point is ok to send commands to the device */
result = i2400m_check_mac_addr(i2400m);
if (result < 0)
goto error_check_mac_addr;
i2400m->ready = 1;
wimax_state_change(wimax_dev, WIMAX_ST_UNINITIALIZED);
result = i2400m_dev_initialize(i2400m);
if (result < 0)
goto error_dev_initialize;
/* At this point, reports will come for the device and set it
* to the right state if it is different than UNINITIALIZED */
d_fnend(3, dev, "(net_dev %p [i2400m %p]) = %d\n",
net_dev, i2400m, result);
return result;
error_dev_initialize:
error_check_mac_addr:
error_fw_check:
i2400m->bus_dev_stop(i2400m);
error_bus_dev_start:
destroy_workqueue(i2400m->work_queue);
error_create_workqueue:
i2400m_rx_release(i2400m);
error_rx_setup:
i2400m_tx_release(i2400m);
error_tx_setup:
error_bootstrap:
if (result == -EL3RST && times-- > 0) {
flags = I2400M_BRI_SOFT|I2400M_BRI_MAC_REINIT;
goto retry;
}
d_fnend(3, dev, "(net_dev %p [i2400m %p]) = %d\n",
net_dev, i2400m, result);
return result;
}
static
int i2400m_dev_start(struct i2400m *i2400m, enum i2400m_bri bm_flags)
{
int result;
mutex_lock(&i2400m->init_mutex); /* Well, start the device */
result = __i2400m_dev_start(i2400m, bm_flags);
if (result >= 0)
i2400m->updown = 1;
mutex_unlock(&i2400m->init_mutex);
return result;
}
/**
* i2400m_dev_stop - Tear down driver communication with the device
*
* @i2400m: device descriptor
*
* Returns: 0 if ok, < 0 errno code on error.
*
* Releases all the resources allocated to communicate with the
* device. Note we cannot destroy the workqueue earlier as until RX is
* fully destroyed, it could still try to schedule jobs.
*/
static
void __i2400m_dev_stop(struct i2400m *i2400m)
{
struct wimax_dev *wimax_dev = &i2400m->wimax_dev;
struct device *dev = i2400m_dev(i2400m);
d_fnstart(3, dev, "(i2400m %p)\n", i2400m);
wimax_state_change(wimax_dev, __WIMAX_ST_QUIESCING);
i2400m_dev_shutdown(i2400m);
i2400m->ready = 0;
i2400m->bus_dev_stop(i2400m);
destroy_workqueue(i2400m->work_queue);
i2400m_rx_release(i2400m);
i2400m_tx_release(i2400m);
wimax_state_change(wimax_dev, WIMAX_ST_DOWN);
d_fnend(3, dev, "(i2400m %p) = 0\n", i2400m);
}
/*
* Watch out -- we only need to stop if there is a need for it. The
* device could have reset itself and failed to come up again (see
* _i2400m_dev_reset_handle()).
*/
static
void i2400m_dev_stop(struct i2400m *i2400m)
{
mutex_lock(&i2400m->init_mutex);
if (i2400m->updown) {
__i2400m_dev_stop(i2400m);
i2400m->updown = 0;
}
mutex_unlock(&i2400m->init_mutex);
}
/*
* The device has rebooted; fix up the device and the driver
*
* Tear down the driver communication with the device, reload the
* firmware and reinitialize the communication with the device.
*
* If someone calls a reset when the device's firmware is down, in
* theory we won't see it because we are not listening. However, just
* in case, leave the code to handle it.
*
* If there is a reset context, use it; this means someone is waiting
* for us to tell him when the reset operation is complete and the
* device is ready to rock again.
*
* NOTE: if we are in the process of bringing up or down the
* communication with the device [running i2400m_dev_start() or
* _stop()], don't do anything, let it fail and handle it.
*
* This function is ran always in a thread context
*/
static
void __i2400m_dev_reset_handle(struct work_struct *ws)
{
int result;
struct i2400m_work *iw = container_of(ws, struct i2400m_work, ws);
struct i2400m *i2400m = iw->i2400m;
struct device *dev = i2400m_dev(i2400m);
enum wimax_st wimax_state;
struct i2400m_reset_ctx *ctx = i2400m->reset_ctx;
d_fnstart(3, dev, "(ws %p i2400m %p)\n", ws, i2400m);
result = 0;
if (mutex_trylock(&i2400m->init_mutex) == 0) {
/* We are still in i2400m_dev_start() [let it fail] or
* i2400m_dev_stop() [we are shutting down anyway, so
* ignore it] or we are resetting somewhere else. */
dev_err(dev, "device rebooted\n");
i2400m_msg_to_dev_cancel_wait(i2400m, -EL3RST);
complete(&i2400m->msg_completion);
goto out;
}
wimax_state = wimax_state_get(&i2400m->wimax_dev);
if (wimax_state < WIMAX_ST_UNINITIALIZED) {
dev_info(dev, "device rebooted: it is down, ignoring\n");
goto out_unlock; /* ifconfig up/down wasn't called */
}
dev_err(dev, "device rebooted: reinitializing driver\n");
__i2400m_dev_stop(i2400m);
i2400m->updown = 0;
result = __i2400m_dev_start(i2400m,
I2400M_BRI_SOFT | I2400M_BRI_MAC_REINIT);
if (result < 0) {
dev_err(dev, "device reboot: cannot start the device: %d\n",
result);
result = i2400m->bus_reset(i2400m, I2400M_RT_BUS);
if (result >= 0)
result = -ENODEV;
} else
i2400m->updown = 1;
out_unlock:
if (i2400m->reset_ctx) {
ctx->result = result;
complete(&ctx->completion);
}
mutex_unlock(&i2400m->init_mutex);
out:
i2400m_put(i2400m);
kfree(iw);
d_fnend(3, dev, "(ws %p i2400m %p) = void\n", ws, i2400m);
return;
}
/**
* i2400m_dev_reset_handle - Handle a device's reset in a thread context
*
* Schedule a device reset handling out on a thread context, so it
* is safe to call from atomic context. We can't use the i2400m's
* queue as we are going to destroy it and reinitialize it as part of
* the driver bringup/bringup process.
*
* See __i2400m_dev_reset_handle() for details; that takes care of
* reinitializing the driver to handle the reset, calling into the
* bus-specific functions ops as needed.
*/
int i2400m_dev_reset_handle(struct i2400m *i2400m)
{
i2400m->boot_mode = 1;
wmb(); /* Make sure i2400m_msg_to_dev() sees boot_mode */
return i2400m_schedule_work(i2400m, __i2400m_dev_reset_handle,
GFP_ATOMIC);
}
EXPORT_SYMBOL_GPL(i2400m_dev_reset_handle);
/**
* i2400m_setup - bus-generic setup function for the i2400m device
*
* @i2400m: device descriptor (bus-specific parts have been initialized)
*
* Returns: 0 if ok, < 0 errno code on error.
*
* Initializes the bus-generic parts of the i2400m driver; the
* bus-specific parts have been initialized, function pointers filled
* out by the bus-specific probe function.
*
* As well, this registers the WiMAX and net device nodes. Once this
* function returns, the device is operative and has to be ready to
* receive and send network traffic and WiMAX control operations.
*/
int i2400m_setup(struct i2400m *i2400m, enum i2400m_bri bm_flags)
{
int result = -ENODEV;
struct device *dev = i2400m_dev(i2400m);
struct wimax_dev *wimax_dev = &i2400m->wimax_dev;
struct net_device *net_dev = i2400m->wimax_dev.net_dev;
d_fnstart(3, dev, "(i2400m %p)\n", i2400m);
snprintf(wimax_dev->name, sizeof(wimax_dev->name),
"i2400m-%s:%s", dev->bus->name, dev_name(dev));
i2400m->bm_cmd_buf = kzalloc(I2400M_BM_CMD_BUF_SIZE, GFP_KERNEL);
if (i2400m->bm_cmd_buf == NULL) {
dev_err(dev, "cannot allocate USB command buffer\n");
goto error_bm_cmd_kzalloc;
}
i2400m->bm_ack_buf = kzalloc(I2400M_BM_ACK_BUF_SIZE, GFP_KERNEL);
if (i2400m->bm_ack_buf == NULL) {
dev_err(dev, "cannot allocate USB ack buffer\n");
goto error_bm_ack_buf_kzalloc;
}
result = i2400m_bootrom_init(i2400m, bm_flags);
if (result < 0) {
dev_err(dev, "read mac addr: bootrom init "
"failed: %d\n", result);
goto error_bootrom_init;
}
result = i2400m_read_mac_addr(i2400m);
if (result < 0)
goto error_read_mac_addr;
random_ether_addr(i2400m->src_mac_addr);
result = register_netdev(net_dev); /* Okey dokey, bring it up */
if (result < 0) {
dev_err(dev, "cannot register i2400m network device: %d\n",
result);
goto error_register_netdev;
}
netif_carrier_off(net_dev);
result = i2400m_dev_start(i2400m, bm_flags);
if (result < 0)
goto error_dev_start;
i2400m->wimax_dev.op_msg_from_user = i2400m_op_msg_from_user;
i2400m->wimax_dev.op_rfkill_sw_toggle = i2400m_op_rfkill_sw_toggle;
i2400m->wimax_dev.op_reset = i2400m_op_reset;
result = wimax_dev_add(&i2400m->wimax_dev, net_dev);
if (result < 0)
goto error_wimax_dev_add;
/* User space needs to do some init stuff */
wimax_state_change(wimax_dev, WIMAX_ST_UNINITIALIZED);
/* Now setup all that requires a registered net and wimax device. */
result = sysfs_create_group(&net_dev->dev.kobj, &i2400m_dev_attr_group);
if (result < 0) {
dev_err(dev, "cannot setup i2400m's sysfs: %d\n", result);
goto error_sysfs_setup;
}
result = i2400m_debugfs_add(i2400m);
if (result < 0) {
dev_err(dev, "cannot setup i2400m's debugfs: %d\n", result);
goto error_debugfs_setup;
}
d_fnend(3, dev, "(i2400m %p) = %d\n", i2400m, result);
return result;
error_debugfs_setup:
sysfs_remove_group(&i2400m->wimax_dev.net_dev->dev.kobj,
&i2400m_dev_attr_group);
error_sysfs_setup:
wimax_dev_rm(&i2400m->wimax_dev);
error_wimax_dev_add:
i2400m_dev_stop(i2400m);
error_dev_start:
unregister_netdev(net_dev);
error_register_netdev:
error_read_mac_addr:
error_bootrom_init:
kfree(i2400m->bm_ack_buf);
error_bm_ack_buf_kzalloc:
kfree(i2400m->bm_cmd_buf);
error_bm_cmd_kzalloc:
d_fnend(3, dev, "(i2400m %p) = %d\n", i2400m, result);
return result;
}
EXPORT_SYMBOL_GPL(i2400m_setup);
/**
* i2400m_release - release the bus-generic driver resources
*
* Sends a disconnect message and undoes any setup done by i2400m_setup()
*/
void i2400m_release(struct i2400m *i2400m)
{
struct device *dev = i2400m_dev(i2400m);
d_fnstart(3, dev, "(i2400m %p)\n", i2400m);
netif_stop_queue(i2400m->wimax_dev.net_dev);
i2400m_debugfs_rm(i2400m);
sysfs_remove_group(&i2400m->wimax_dev.net_dev->dev.kobj,
&i2400m_dev_attr_group);
wimax_dev_rm(&i2400m->wimax_dev);
i2400m_dev_stop(i2400m);
unregister_netdev(i2400m->wimax_dev.net_dev);
kfree(i2400m->bm_ack_buf);
kfree(i2400m->bm_cmd_buf);
d_fnend(3, dev, "(i2400m %p) = void\n", i2400m);
}
EXPORT_SYMBOL_GPL(i2400m_release);
/*
* Debug levels control; see debug.h
*/
struct d_level D_LEVEL[] = {
D_SUBMODULE_DEFINE(control),
D_SUBMODULE_DEFINE(driver),
D_SUBMODULE_DEFINE(debugfs),
D_SUBMODULE_DEFINE(fw),
D_SUBMODULE_DEFINE(netdev),
D_SUBMODULE_DEFINE(rfkill),
D_SUBMODULE_DEFINE(rx),
D_SUBMODULE_DEFINE(tx),
};
size_t D_LEVEL_SIZE = ARRAY_SIZE(D_LEVEL);
static
int __init i2400m_driver_init(void)
{
return 0;
}
module_init(i2400m_driver_init);
static
void __exit i2400m_driver_exit(void)
{
/* for scheds i2400m_dev_reset_handle() */
flush_scheduled_work();
return;
}
module_exit(i2400m_driver_exit);
MODULE_AUTHOR("Intel Corporation <linux-wimax@intel.com>");
MODULE_DESCRIPTION("Intel 2400M WiMAX networking bus-generic driver");
MODULE_LICENSE("GPL");

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,141 @@
/*
* Intel Wireless WiMAX Connection 2400m
* SDIO-specific i2400m driver definitions
*
*
* Copyright (C) 2007-2008 Intel Corporation. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in
* the documentation and/or other materials provided with the
* distribution.
* * Neither the name of Intel Corporation nor the names of its
* contributors may be used to endorse or promote products derived
* from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
*
* Intel Corporation <linux-wimax@intel.com>
* Brian Bian <brian.bian@intel.com>
* Dirk Brandewie <dirk.j.brandewie@intel.com>
* Inaky Perez-Gonzalez <inaky.perez-gonzalez@intel.com>
* Yanir Lubetkin <yanirx.lubetkin@intel.com>
* - Initial implementation
*
*
* This driver implements the bus-specific part of the i2400m for
* SDIO. Check i2400m.h for a generic driver description.
*
* ARCHITECTURE
*
* This driver sits under the bus-generic i2400m driver, providing the
* connection to the device.
*
* When probed, all the function pointers are setup and then the
* bus-generic code called. The generic driver will then use the
* provided pointers for uploading firmware (i2400ms_bus_bm*() in
* sdio-fw.c) and then setting up the device (i2400ms_dev_*() in
* sdio.c).
*
* Once firmware is uploaded, TX functions (sdio-tx.c) are called when
* data is ready for transmission in the TX fifo; then the SDIO IRQ is
* fired and data is available (sdio-rx.c), it is sent to the generic
* driver for processing with i2400m_rx.
*/
#ifndef __I2400M_SDIO_H__
#define __I2400M_SDIO_H__
#include "i2400m.h"
/* Host-Device interface for SDIO */
enum {
I2400MS_BLK_SIZE = 256,
I2400MS_PL_SIZE_MAX = 0x3E00,
I2400MS_DATA_ADDR = 0x0,
I2400MS_INTR_STATUS_ADDR = 0x13,
I2400MS_INTR_CLEAR_ADDR = 0x13,
I2400MS_INTR_ENABLE_ADDR = 0x14,
I2400MS_INTR_GET_SIZE_ADDR = 0x2C,
/* The number of ticks to wait for the device to signal that
* it is ready */
I2400MS_INIT_SLEEP_INTERVAL = 10,
/* How long to wait for the device to settle after reset */
I2400MS_SETTLE_TIME = 40,
};
/**
* struct i2400ms - descriptor for a SDIO connected i2400m
*
* @i2400m: bus-generic i2400m implementation; has to be first (see
* it's documentation in i2400m.h).
*
* @func: pointer to our SDIO function
*
* @tx_worker: workqueue struct used to TX data when the bus-generic
* code signals packets are pending for transmission to the device.
*
* @tx_workqueue: workqeueue used for data TX; we don't use the
* system's workqueue as that might cause deadlocks with code in
* the bus-generic driver.
*/
struct i2400ms {
struct i2400m i2400m; /* FIRST! See doc */
struct sdio_func *func;
struct work_struct tx_worker;
struct workqueue_struct *tx_workqueue;
char tx_wq_name[32];
struct dentry *debugfs_dentry;
wait_queue_head_t bm_wfa_wq;
int bm_wait_result;
size_t bm_ack_size;
};
static inline
void i2400ms_init(struct i2400ms *i2400ms)
{
i2400m_init(&i2400ms->i2400m);
}
extern int i2400ms_rx_setup(struct i2400ms *);
extern void i2400ms_rx_release(struct i2400ms *);
extern ssize_t __i2400ms_rx_get_size(struct i2400ms *);
extern int i2400ms_tx_setup(struct i2400ms *);
extern void i2400ms_tx_release(struct i2400ms *);
extern void i2400ms_bus_tx_kick(struct i2400m *);
extern ssize_t i2400ms_bus_bm_cmd_send(struct i2400m *,
const struct i2400m_bootrom_header *,
size_t, int);
extern ssize_t i2400ms_bus_bm_wait_for_ack(struct i2400m *,
struct i2400m_bootrom_header *,
size_t);
extern void i2400ms_bus_bm_release(struct i2400m *);
extern int i2400ms_bus_bm_setup(struct i2400m *);
#endif /* #ifndef __I2400M_SDIO_H__ */

View File

@@ -0,0 +1,264 @@
/*
* Intel Wireless WiMAX Connection 2400m
* USB-specific i2400m driver definitions
*
*
* Copyright (C) 2007-2008 Intel Corporation. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in
* the documentation and/or other materials provided with the
* distribution.
* * Neither the name of Intel Corporation nor the names of its
* contributors may be used to endorse or promote products derived
* from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
*
* Intel Corporation <linux-wimax@intel.com>
* Inaky Perez-Gonzalez <inaky.perez-gonzalez@intel.com>
* Yanir Lubetkin <yanirx.lubetkin@intel.com>
* - Initial implementation
*
*
* This driver implements the bus-specific part of the i2400m for
* USB. Check i2400m.h for a generic driver description.
*
* ARCHITECTURE
*
* This driver listens to notifications sent from the notification
* endpoint (in usb-notif.c); when data is ready to read, the code in
* there schedules a read from the device (usb-rx.c) and then passes
* the data to the generic RX code (rx.c).
*
* When the generic driver needs to send data (network or control), it
* queues up in the TX FIFO (tx.c) and that will notify the driver
* through the i2400m->bus_tx_kick() callback
* (usb-tx.c:i2400mu_bus_tx_kick) which will send the items in the
* FIFO queue.
*
* This driver, as well, implements the USB-specific ops for the generic
* driver to be able to setup/teardown communication with the device
* [i2400m_bus_dev_start() and i2400m_bus_dev_stop()], reseting the
* device [i2400m_bus_reset()] and performing firmware upload
* [i2400m_bus_bm_cmd() and i2400_bus_bm_wait_for_ack()].
*/
#ifndef __I2400M_USB_H__
#define __I2400M_USB_H__
#include "i2400m.h"
#include <linux/kthread.h>
/*
* Error Density Count: cheapo error density (over time) counter
*
* Originally by Reinette Chatre <reinette.chatre@intel.com>
*
* Embed an 'struct edc' somewhere. Each time there is a soft or
* retryable error, call edc_inc() and check if the error top
* watermark has been reached.
*/
enum {
EDC_MAX_ERRORS = 10,
EDC_ERROR_TIMEFRAME = HZ,
};
/* error density counter */
struct edc {
unsigned long timestart;
u16 errorcount;
};
static inline void edc_init(struct edc *edc)
{
edc->timestart = jiffies;
}
/**
* edc_inc - report a soft error and check if we are over the watermark
*
* @edc: pointer to error density counter.
* @max_err: maximum number of errors we can accept over the timeframe
* @timeframe: lenght of the timeframe (in jiffies).
*
* Returns: !0 1 if maximum acceptable errors per timeframe has been
* exceeded. 0 otherwise.
*
* This is way to determine if the number of acceptable errors per time
* period has been exceeded. It is not accurate as there are cases in which
* this scheme will not work, for example if there are periodic occurences
* of errors that straddle updates to the start time. This scheme is
* sufficient for our usage.
*
* To use, embed a 'struct edc' somewhere, initialize it with
* edc_init() and when an error hits:
*
* if (do_something_fails_with_a_soft_error) {
* if (edc_inc(&my->edc, MAX_ERRORS, MAX_TIMEFRAME))
* Ops, hard error, do something about it
* else
* Retry or ignore, depending on whatever
* }
*/
static inline int edc_inc(struct edc *edc, u16 max_err, u16 timeframe)
{
unsigned long now;
now = jiffies;
if (now - edc->timestart > timeframe) {
edc->errorcount = 1;
edc->timestart = now;
} else if (++edc->errorcount > max_err) {
edc->errorcount = 0;
edc->timestart = now;
return 1;
}
return 0;
}
/* Host-Device interface for USB */
enum {
I2400MU_MAX_NOTIFICATION_LEN = 256,
I2400MU_BLK_SIZE = 16,
I2400MU_PL_SIZE_MAX = 0x3EFF,
/* Endpoints */
I2400MU_EP_BULK_OUT = 0,
I2400MU_EP_NOTIFICATION,
I2400MU_EP_RESET_COLD,
I2400MU_EP_BULK_IN,
};
/**
* struct i2400mu - descriptor for a USB connected i2400m
*
* @i2400m: bus-generic i2400m implementation; has to be first (see
* it's documentation in i2400m.h).
*
* @usb_dev: pointer to our USB device
*
* @usb_iface: pointer to our USB interface
*
* @urb_edc: error density counter; used to keep a density-on-time tab
* on how many soft (retryable or ignorable) errors we get. If we
* go over the threshold, we consider the bus transport is failing
* too much and reset.
*
* @notif_urb: URB for receiving notifications from the device.
*
* @tx_kthread: thread we use for data TX. We use a thread because in
* order to do deep power saving and put the device to sleep, we
* need to call usb_autopm_*() [blocking functions].
*
* @tx_wq: waitqueue for the TX kthread to sleep when there is no data
* to be sent; when more data is available, it is woken up by
* i2400mu_bus_tx_kick().
*
* @rx_kthread: thread we use for data RX. We use a thread because in
* order to do deep power saving and put the device to sleep, we
* need to call usb_autopm_*() [blocking functions].
*
* @rx_wq: waitqueue for the RX kthread to sleep when there is no data
* to receive. When data is available, it is woken up by
* usb-notif.c:i2400mu_notification_grok().
*
* @rx_pending_count: number of rx-data-ready notifications that were
* still not handled by the RX kthread.
*
* @rx_size: current RX buffer size that is being used.
*
* @rx_size_acc: accumulator of the sizes of the previous read
* transactions.
*
* @rx_size_cnt: number of read transactions accumulated in
* @rx_size_acc.
*
* @do_autopm: disable(0)/enable(>0) calling the
* usb_autopm_get/put_interface() barriers when executing
* commands. See doc in i2400mu_suspend() for more information.
*
* @rx_size_auto_shrink: if true, the rx_size is shrinked
* automatically based on the average size of the received
* transactions. This allows the receive code to allocate smaller
* chunks of memory and thus reduce pressure on the memory
* allocator by not wasting so much space. By default it is
* enabled.
*
* @debugfs_dentry: hookup for debugfs files.
* These have to be in a separate directory, a child of
* (wimax_dev->debugfs_dentry) so they can be removed when the
* module unloads, as we don't keep each dentry.
*/
struct i2400mu {
struct i2400m i2400m; /* FIRST! See doc */
struct usb_device *usb_dev;
struct usb_interface *usb_iface;
struct edc urb_edc; /* Error density counter */
struct urb *notif_urb;
struct task_struct *tx_kthread;
wait_queue_head_t tx_wq;
struct task_struct *rx_kthread;
wait_queue_head_t rx_wq;
atomic_t rx_pending_count;
size_t rx_size, rx_size_acc, rx_size_cnt;
atomic_t do_autopm;
u8 rx_size_auto_shrink;
struct dentry *debugfs_dentry;
};
static inline
void i2400mu_init(struct i2400mu *i2400mu)
{
i2400m_init(&i2400mu->i2400m);
edc_init(&i2400mu->urb_edc);
init_waitqueue_head(&i2400mu->tx_wq);
atomic_set(&i2400mu->rx_pending_count, 0);
init_waitqueue_head(&i2400mu->rx_wq);
i2400mu->rx_size = PAGE_SIZE - sizeof(struct skb_shared_info);
atomic_set(&i2400mu->do_autopm, 1);
i2400mu->rx_size_auto_shrink = 1;
}
extern int i2400mu_notification_setup(struct i2400mu *);
extern void i2400mu_notification_release(struct i2400mu *);
extern int i2400mu_rx_setup(struct i2400mu *);
extern void i2400mu_rx_release(struct i2400mu *);
extern void i2400mu_rx_kick(struct i2400mu *);
extern int i2400mu_tx_setup(struct i2400mu *);
extern void i2400mu_tx_release(struct i2400mu *);
extern void i2400mu_bus_tx_kick(struct i2400m *);
extern ssize_t i2400mu_bus_bm_cmd_send(struct i2400m *,
const struct i2400m_bootrom_header *,
size_t, int);
extern ssize_t i2400mu_bus_bm_wait_for_ack(struct i2400m *,
struct i2400m_bootrom_header *,
size_t);
#endif /* #ifndef __I2400M_USB_H__ */

View File

@@ -0,0 +1,858 @@
/*
* Intel Wireless WiMAX Connection 2400m
* Declarations for bus-generic internal APIs
*
*
* Copyright (C) 2007-2008 Intel Corporation. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in
* the documentation and/or other materials provided with the
* distribution.
* * Neither the name of Intel Corporation nor the names of its
* contributors may be used to endorse or promote products derived
* from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
*
* Intel Corporation <linux-wimax@intel.com>
* Inaky Perez-Gonzalez <inaky.perez-gonzalez@intel.com>
* Yanir Lubetkin <yanirx.lubetkin@intel.com>
* - Initial implementation
*
*
* GENERAL DRIVER ARCHITECTURE
*
* The i2400m driver is split in the following two major parts:
*
* - bus specific driver
* - bus generic driver (this part)
*
* The bus specific driver sets up stuff specific to the bus the
* device is connected to (USB, SDIO, PCI, tam-tam...non-authoritative
* nor binding list) which is basically the device-model management
* (probe/disconnect, etc), moving data from device to kernel and
* back, doing the power saving details and reseting the device.
*
* For details on each bus-specific driver, see it's include file,
* i2400m-BUSNAME.h
*
* The bus-generic functionality break up is:
*
* - Firmware upload: fw.c - takes care of uploading firmware to the
* device. bus-specific driver just needs to provides a way to
* execute boot-mode commands and to reset the device.
*
* - RX handling: rx.c - receives data from the bus-specific code and
* feeds it to the network or WiMAX stack or uses it to modify
* the driver state. bus-specific driver only has to receive
* frames and pass them to this module.
*
* - TX handling: tx.c - manages the TX FIFO queue and provides means
* for the bus-specific TX code to pull data from the FIFO
* queue. bus-specific code just pulls frames from this module
* to sends them to the device.
*
* - netdev glue: netdev.c - interface with Linux networking
* stack. Pass around data frames, and configure when the
* device is up and running or shutdown (through ifconfig up /
* down). Bus-generic only.
*
* - control ops: control.c - implements various commmands for
* controlling the device. bus-generic only.
*
* - device model glue: driver.c - implements helpers for the
* device-model glue done by the bus-specific layer
* (setup/release the driver resources), turning the device on
* and off, handling the device reboots/resets and a few simple
* WiMAX stack ops.
*
* Code is also broken up in linux-glue / device-glue.
*
* Linux glue contains functions that deal mostly with gluing with the
* rest of the Linux kernel.
*
* Device-glue are functions that deal mostly with the way the device
* does things and talk the device's language.
*
* device-glue code is licensed BSD so other open source OSes can take
* it to implement their drivers.
*
*
* APIs AND HEADER FILES
*
* This bus generic code exports three APIs:
*
* - HDI (host-device interface) definitions common to all busses
* (include/linux/wimax/i2400m.h); these can be also used by user
* space code.
* - internal API for the bus-generic code
* - external API for the bus-specific drivers
*
*
* LIFE CYCLE:
*
* When the bus-specific driver probes, it allocates a network device
* with enough space for it's data structue, that must contain a
* &struct i2400m at the top.
*
* On probe, it needs to fill the i2400m members marked as [fill], as
* well as i2400m->wimax_dev.net_dev and call i2400m_setup(). The
* i2400m driver will only register with the WiMAX and network stacks;
* the only access done to the device is to read the MAC address so we
* can register a network device. This calls i2400m_dev_start() to
* load firmware, setup communication with the device and configure it
* for operation.
*
* At this point, control and data communications are possible.
*
* On disconnect/driver unload, the bus-specific disconnect function
* calls i2400m_release() to undo i2400m_setup(). i2400m_dev_stop()
* shuts the firmware down and releases resources uses to communicate
* with the device.
*
* While the device is up, it might reset. The bus-specific driver has
* to catch that situation and call i2400m_dev_reset_handle() to deal
* with it (reset the internal driver structures and go back to square
* one).
*/
#ifndef __I2400M_H__
#define __I2400M_H__
#include <linux/usb.h>
#include <linux/netdevice.h>
#include <linux/completion.h>
#include <linux/rwsem.h>
#include <asm/atomic.h>
#include <net/wimax.h>
#include <linux/wimax/i2400m.h>
#include <asm/byteorder.h>
/* Misc constants */
enum {
/* Firmware uploading */
I2400M_BOOT_RETRIES = 3,
I3200_BOOT_RETRIES = 3,
/* Size of the Boot Mode Command buffer */
I2400M_BM_CMD_BUF_SIZE = 16 * 1024,
I2400M_BM_ACK_BUF_SIZE = 256,
};
/**
* struct i2400m_poke_table - Hardware poke table for the Intel 2400m
*
* This structure will be used to create a device specific poke table
* to put the device in a consistant state at boot time.
*
* @address: The device address to poke
*
* @data: The data value to poke to the device address
*
*/
struct i2400m_poke_table{
__le32 address;
__le32 data;
};
#define I2400M_FW_POKE(a, d) { \
.address = cpu_to_le32(a), \
.data = cpu_to_le32(d) \
}
/**
* i2400m_reset_type - methods to reset a device
*
* @I2400M_RT_WARM: Reset without device disconnection, device handles
* are kept valid but state is back to power on, with firmware
* re-uploaded.
* @I2400M_RT_COLD: Tell the device to disconnect itself from the bus
* and reconnect. Renders all device handles invalid.
* @I2400M_RT_BUS: Tells the bus to reset the device; last measure
* used when both types above don't work.
*/
enum i2400m_reset_type {
I2400M_RT_WARM, /* first measure */
I2400M_RT_COLD, /* second measure */
I2400M_RT_BUS, /* call in artillery */
};
struct i2400m_reset_ctx;
struct i2400m_roq;
/**
* struct i2400m - descriptor for an Intel 2400m
*
* Members marked with [fill] must be filled out/initialized before
* calling i2400m_setup().
*
* @bus_tx_block_size: [fill] SDIO imposes a 256 block size, USB 16,
* so we have a tx_blk_size variable that the bus layer sets to
* tell the engine how much of that we need.
*
* @bus_pl_size_max: [fill] Maximum payload size.
*
* @bus_dev_start: [fill] Function called by the bus-generic code
* [i2400m_dev_start()] to setup the bus-specific communications
* to the the device. See LIFE CYCLE above.
*
* NOTE: Doesn't need to upload the firmware, as that is taken
* care of by the bus-generic code.
*
* @bus_dev_stop: [fill] Function called by the bus-generic code
* [i2400m_dev_stop()] to shutdown the bus-specific communications
* to the the device. See LIFE CYCLE above.
*
* This function does not need to reset the device, just tear down
* all the host resources created to handle communication with
* the device.
*
* @bus_tx_kick: [fill] Function called by the bus-generic code to let
* the bus-specific code know that there is data available in the
* TX FIFO for transmission to the device.
*
* This function cannot sleep.
*
* @bus_reset: [fill] Function called by the bus-generic code to reset
* the device in in various ways. Doesn't need to wait for the
* reset to finish.
*
* If warm or cold reset fail, this function is expected to do a
* bus-specific reset (eg: USB reset) to get the device to a
* working state (even if it implies device disconecction).
*
* Note the warm reset is used by the firmware uploader to
* reinitialize the device.
*
* IMPORTANT: this is called very early in the device setup
* process, so it cannot rely on common infrastructure being laid
* out.
*
* @bus_bm_retries: [fill] How many times shall a firmware upload /
* device initialization be retried? Different models of the same
* device might need different values, hence it is set by the
* bus-specific driver. Note this value is used in two places,
* i2400m_fw_dnload() and __i2400m_dev_start(); they won't become
* multiplicative (__i2400m_dev_start() calling N times
* i2400m_fw_dnload() and this trying N times to download the
* firmware), as if __i2400m_dev_start() only retries if the
* firmware crashed while initializing the device (not in a
* general case).
*
* @bus_bm_cmd_send: [fill] Function called to send a boot-mode
* command. Flags are defined in 'enum i2400m_bm_cmd_flags'. This
* is synchronous and has to return 0 if ok or < 0 errno code in
* any error condition.
*
* @bus_bm_wait_for_ack: [fill] Function called to wait for a
* boot-mode notification (that can be a response to a previously
* issued command or an asynchronous one). Will read until all the
* indicated size is read or timeout. Reading more or less data
* than asked for is an error condition. Return 0 if ok, < 0 errno
* code on error.
*
* The caller to this function will check if the response is a
* barker that indicates the device going into reset mode.
*
* @bus_fw_names: [fill] a NULL-terminated array with the names of the
* firmware images to try loading. This is made a list so we can
* support backward compatibility of firmware releases (eg: if we
* can't find the default v1.4, we try v1.3). In general, the name
* should be i2400m-fw-X-VERSION.sbcf, where X is the bus name.
* The list is tried in order and the first one that loads is
* used. The fw loader will set i2400m->fw_name to point to the
* active firmware image.
*
* @bus_bm_mac_addr_impaired: [fill] Set to true if the device's MAC
* address provided in boot mode is kind of broken and needs to
* be re-read later on.
*
* @bus_bm_pokes_table: [fill/optional] A table of device addresses
* and values that will be poked at device init time to move the
* device to the correct state for the type of boot/firmware being
* used. This table MUST be terminated with (0x000000,
* 0x00000000) or bad things will happen.
*
*
* @wimax_dev: WiMAX generic device for linkage into the kernel WiMAX
* stack. Due to the way a net_device is allocated, we need to
* force this to be the first field so that we can get from
* netdev_priv() the right pointer.
*
* @rx_reorder: 1 if RX reordering is enabled; this can only be
* set at probe time.
*
* @state: device's state (as reported by it)
*
* @state_wq: waitqueue that is woken up whenever the state changes
*
* @tx_lock: spinlock to protect TX members
*
* @tx_buf: FIFO buffer for TX; we queue data here
*
* @tx_in: FIFO index for incoming data. Note this doesn't wrap around
* and it is always greater than @tx_out.
*
* @tx_out: FIFO index for outgoing data
*
* @tx_msg: current TX message that is active in the FIFO for
* appending payloads.
*
* @tx_sequence: current sequence number for TX messages from the
* device to the host.
*
* @tx_msg_size: size of the current message being transmitted by the
* bus-specific code.
*
* @tx_pl_num: total number of payloads sent
*
* @tx_pl_max: maximum number of payloads sent in a TX message
*
* @tx_pl_min: minimum number of payloads sent in a TX message
*
* @tx_num: number of TX messages sent
*
* @tx_size_acc: number of bytes in all TX messages sent
* (this is different to net_dev's statistics as it also counts
* control messages).
*
* @tx_size_min: smallest TX message sent.
*
* @tx_size_max: biggest TX message sent.
*
* @rx_lock: spinlock to protect RX members
*
* @rx_pl_num: total number of payloads received
*
* @rx_pl_max: maximum number of payloads received in a RX message
*
* @rx_pl_min: minimum number of payloads received in a RX message
*
* @rx_num: number of RX messages received
*
* @rx_size_acc: number of bytes in all RX messages received
* (this is different to net_dev's statistics as it also counts
* control messages).
*
* @rx_size_min: smallest RX message received.
*
* @rx_size_max: buggest RX message received.
*
* @rx_roq: RX ReOrder queues. (fw >= v1.4) When packets are received
* out of order, the device will ask the driver to hold certain
* packets until the ones that are received out of order can be
* delivered. Then the driver can release them to the host. See
* drivers/net/i2400m/rx.c for details.
*
* @src_mac_addr: MAC address used to make ethernet packets be coming
* from. This is generated at i2400m_setup() time and used during
* the life cycle of the instance. See i2400m_fake_eth_header().
*
* @init_mutex: Mutex used for serializing the device bringup
* sequence; this way if the device reboots in the middle, we
* don't try to do a bringup again while we are tearing down the
* one that failed.
*
* Can't reuse @msg_mutex because from within the bringup sequence
* we need to send messages to the device and thus use @msg_mutex.
*
* @msg_mutex: mutex used to send control commands to the device (we
* only allow one at a time, per host-device interface design).
*
* @msg_completion: used to wait for an ack to a control command sent
* to the device.
*
* @ack_skb: used to store the actual ack to a control command if the
* reception of the command was successful. Otherwise, a ERR_PTR()
* errno code that indicates what failed with the ack reception.
*
* Only valid after @msg_completion is woken up. Only updateable
* if @msg_completion is armed. Only touched by
* i2400m_msg_to_dev().
*
* Protected by @rx_lock. In theory the command execution flow is
* sequential, but in case the device sends an out-of-phase or
* very delayed response, we need to avoid it trampling current
* execution.
*
* @bm_cmd_buf: boot mode command buffer for composing firmware upload
* commands.
*
* USB can't r/w to stack, vmalloc, etc...as well, we end up
* having to alloc/free a lot to compose commands, so we use these
* for stagging and not having to realloc all the time.
*
* This assumes the code always runs serialized. Only one thread
* can call i2400m_bm_cmd() at the same time.
*
* @bm_ack_buf: boot mode acknoledge buffer for staging reception of
* responses to commands.
*
* See @bm_cmd_buf.
*
* @work_queue: work queue for processing device reports. This
* workqueue cannot be used for processing TX or RX to the device,
* as from it we'll process device reports, which might require
* further communication with the device.
*
* @debugfs_dentry: hookup for debugfs files.
* These have to be in a separate directory, a child of
* (wimax_dev->debugfs_dentry) so they can be removed when the
* module unloads, as we don't keep each dentry.
*
* @fw_name: name of the firmware image that is currently being used.
*
* @fw_version: version of the firmware interface, Major.minor,
* encoded in the high word and low word (major << 16 | minor).
*/
struct i2400m {
struct wimax_dev wimax_dev; /* FIRST! See doc */
unsigned updown:1; /* Network device is up or down */
unsigned boot_mode:1; /* is the device in boot mode? */
unsigned sboot:1; /* signed or unsigned fw boot */
unsigned ready:1; /* all probing steps done */
unsigned rx_reorder:1; /* RX reorder is enabled */
u8 trace_msg_from_user; /* echo rx msgs to 'trace' pipe */
/* typed u8 so /sys/kernel/debug/u8 can tweak */
enum i2400m_system_state state;
wait_queue_head_t state_wq; /* Woken up when on state updates */
size_t bus_tx_block_size;
size_t bus_pl_size_max;
unsigned bus_bm_retries;
int (*bus_dev_start)(struct i2400m *);
void (*bus_dev_stop)(struct i2400m *);
void (*bus_tx_kick)(struct i2400m *);
int (*bus_reset)(struct i2400m *, enum i2400m_reset_type);
ssize_t (*bus_bm_cmd_send)(struct i2400m *,
const struct i2400m_bootrom_header *,
size_t, int flags);
ssize_t (*bus_bm_wait_for_ack)(struct i2400m *,
struct i2400m_bootrom_header *, size_t);
const char **bus_fw_names;
unsigned bus_bm_mac_addr_impaired:1;
const struct i2400m_poke_table *bus_bm_pokes_table;
spinlock_t tx_lock; /* protect TX state */
void *tx_buf;
size_t tx_in, tx_out;
struct i2400m_msg_hdr *tx_msg;
size_t tx_sequence, tx_msg_size;
/* TX stats */
unsigned tx_pl_num, tx_pl_max, tx_pl_min,
tx_num, tx_size_acc, tx_size_min, tx_size_max;
/* RX stuff */
spinlock_t rx_lock; /* protect RX state */
unsigned rx_pl_num, rx_pl_max, rx_pl_min,
rx_num, rx_size_acc, rx_size_min, rx_size_max;
struct i2400m_roq *rx_roq; /* not under rx_lock! */
u8 src_mac_addr[ETH_HLEN];
struct mutex msg_mutex; /* serialize command execution */
struct completion msg_completion;
struct sk_buff *ack_skb; /* protected by rx_lock */
void *bm_ack_buf; /* for receiving acks over USB */
void *bm_cmd_buf; /* for issuing commands over USB */
struct workqueue_struct *work_queue;
struct mutex init_mutex; /* protect bringup seq */
struct i2400m_reset_ctx *reset_ctx; /* protected by init_mutex */
struct work_struct wake_tx_ws;
struct sk_buff *wake_tx_skb;
struct dentry *debugfs_dentry;
const char *fw_name; /* name of the current firmware image */
unsigned long fw_version; /* version of the firmware interface */
};
/*
* Initialize a 'struct i2400m' from all zeroes
*
* This is a bus-generic API call.
*/
static inline
void i2400m_init(struct i2400m *i2400m)
{
wimax_dev_init(&i2400m->wimax_dev);
i2400m->boot_mode = 1;
i2400m->rx_reorder = 1;
init_waitqueue_head(&i2400m->state_wq);
spin_lock_init(&i2400m->tx_lock);
i2400m->tx_pl_min = UINT_MAX;
i2400m->tx_size_min = UINT_MAX;
spin_lock_init(&i2400m->rx_lock);
i2400m->rx_pl_min = UINT_MAX;
i2400m->rx_size_min = UINT_MAX;
mutex_init(&i2400m->msg_mutex);
init_completion(&i2400m->msg_completion);
mutex_init(&i2400m->init_mutex);
/* wake_tx_ws is initialized in i2400m_tx_setup() */
}
/*
* Bus-generic internal APIs
* -------------------------
*/
static inline
struct i2400m *wimax_dev_to_i2400m(struct wimax_dev *wimax_dev)
{
return container_of(wimax_dev, struct i2400m, wimax_dev);
}
static inline
struct i2400m *net_dev_to_i2400m(struct net_device *net_dev)
{
return wimax_dev_to_i2400m(netdev_priv(net_dev));
}
/*
* Boot mode support
*/
/**
* i2400m_bm_cmd_flags - flags to i2400m_bm_cmd()
*
* @I2400M_BM_CMD_RAW: send the command block as-is, without doing any
* extra processing for adding CRC.
*/
enum i2400m_bm_cmd_flags {
I2400M_BM_CMD_RAW = 1 << 2,
};
/**
* i2400m_bri - Boot-ROM indicators
*
* Flags for i2400m_bootrom_init() and i2400m_dev_bootstrap() [which
* are passed from things like i2400m_setup()]. Can be combined with
* |.
*
* @I2400M_BRI_SOFT: The device rebooted already and a reboot
* barker received, proceed directly to ack the boot sequence.
* @I2400M_BRI_NO_REBOOT: Do not reboot the device and proceed
* directly to wait for a reboot barker from the device.
* @I2400M_BRI_MAC_REINIT: We need to reinitialize the boot
* rom after reading the MAC adress. This is quite a dirty hack,
* if you ask me -- the device requires the bootrom to be
* intialized after reading the MAC address.
*/
enum i2400m_bri {
I2400M_BRI_SOFT = 1 << 1,
I2400M_BRI_NO_REBOOT = 1 << 2,
I2400M_BRI_MAC_REINIT = 1 << 3,
};
extern void i2400m_bm_cmd_prepare(struct i2400m_bootrom_header *);
extern int i2400m_dev_bootstrap(struct i2400m *, enum i2400m_bri);
extern int i2400m_read_mac_addr(struct i2400m *);
extern int i2400m_bootrom_init(struct i2400m *, enum i2400m_bri);
/* Make/grok boot-rom header commands */
static inline
__le32 i2400m_brh_command(enum i2400m_brh_opcode opcode, unsigned use_checksum,
unsigned direct_access)
{
return cpu_to_le32(
I2400M_BRH_SIGNATURE
| (direct_access ? I2400M_BRH_DIRECT_ACCESS : 0)
| I2400M_BRH_RESPONSE_REQUIRED /* response always required */
| (use_checksum ? I2400M_BRH_USE_CHECKSUM : 0)
| (opcode & I2400M_BRH_OPCODE_MASK));
}
static inline
void i2400m_brh_set_opcode(struct i2400m_bootrom_header *hdr,
enum i2400m_brh_opcode opcode)
{
hdr->command = cpu_to_le32(
(le32_to_cpu(hdr->command) & ~I2400M_BRH_OPCODE_MASK)
| (opcode & I2400M_BRH_OPCODE_MASK));
}
static inline
unsigned i2400m_brh_get_opcode(const struct i2400m_bootrom_header *hdr)
{
return le32_to_cpu(hdr->command) & I2400M_BRH_OPCODE_MASK;
}
static inline
unsigned i2400m_brh_get_response(const struct i2400m_bootrom_header *hdr)
{
return (le32_to_cpu(hdr->command) & I2400M_BRH_RESPONSE_MASK)
>> I2400M_BRH_RESPONSE_SHIFT;
}
static inline
unsigned i2400m_brh_get_use_checksum(const struct i2400m_bootrom_header *hdr)
{
return le32_to_cpu(hdr->command) & I2400M_BRH_USE_CHECKSUM;
}
static inline
unsigned i2400m_brh_get_response_required(
const struct i2400m_bootrom_header *hdr)
{
return le32_to_cpu(hdr->command) & I2400M_BRH_RESPONSE_REQUIRED;
}
static inline
unsigned i2400m_brh_get_direct_access(const struct i2400m_bootrom_header *hdr)
{
return le32_to_cpu(hdr->command) & I2400M_BRH_DIRECT_ACCESS;
}
static inline
unsigned i2400m_brh_get_signature(const struct i2400m_bootrom_header *hdr)
{
return (le32_to_cpu(hdr->command) & I2400M_BRH_SIGNATURE_MASK)
>> I2400M_BRH_SIGNATURE_SHIFT;
}
/*
* Driver / device setup and internal functions
*/
extern void i2400m_netdev_setup(struct net_device *net_dev);
extern int i2400m_sysfs_setup(struct device_driver *);
extern void i2400m_sysfs_release(struct device_driver *);
extern int i2400m_tx_setup(struct i2400m *);
extern void i2400m_wake_tx_work(struct work_struct *);
extern void i2400m_tx_release(struct i2400m *);
extern int i2400m_rx_setup(struct i2400m *);
extern void i2400m_rx_release(struct i2400m *);
extern void i2400m_net_rx(struct i2400m *, struct sk_buff *, unsigned,
const void *, int);
extern void i2400m_net_erx(struct i2400m *, struct sk_buff *,
enum i2400m_cs);
enum i2400m_pt;
extern int i2400m_tx(struct i2400m *, const void *, size_t, enum i2400m_pt);
#ifdef CONFIG_DEBUG_FS
extern int i2400m_debugfs_add(struct i2400m *);
extern void i2400m_debugfs_rm(struct i2400m *);
#else
static inline int i2400m_debugfs_add(struct i2400m *i2400m)
{
return 0;
}
static inline void i2400m_debugfs_rm(struct i2400m *i2400m) {}
#endif
/* Called by _dev_start()/_dev_stop() to initialize the device itself */
extern int i2400m_dev_initialize(struct i2400m *);
extern void i2400m_dev_shutdown(struct i2400m *);
extern struct attribute_group i2400m_dev_attr_group;
extern int i2400m_schedule_work(struct i2400m *,
void (*)(struct work_struct *), gfp_t);
/* HDI message's payload description handling */
static inline
size_t i2400m_pld_size(const struct i2400m_pld *pld)
{
return I2400M_PLD_SIZE_MASK & le32_to_cpu(pld->val);
}
static inline
enum i2400m_pt i2400m_pld_type(const struct i2400m_pld *pld)
{
return (I2400M_PLD_TYPE_MASK & le32_to_cpu(pld->val))
>> I2400M_PLD_TYPE_SHIFT;
}
static inline
void i2400m_pld_set(struct i2400m_pld *pld, size_t size,
enum i2400m_pt type)
{
pld->val = cpu_to_le32(
((type << I2400M_PLD_TYPE_SHIFT) & I2400M_PLD_TYPE_MASK)
| (size & I2400M_PLD_SIZE_MASK));
}
/*
* API for the bus-specific drivers
* --------------------------------
*/
static inline
struct i2400m *i2400m_get(struct i2400m *i2400m)
{
dev_hold(i2400m->wimax_dev.net_dev);
return i2400m;
}
static inline
void i2400m_put(struct i2400m *i2400m)
{
dev_put(i2400m->wimax_dev.net_dev);
}
extern int i2400m_dev_reset_handle(struct i2400m *);
/*
* _setup()/_release() are called by the probe/disconnect functions of
* the bus-specific drivers.
*/
extern int i2400m_setup(struct i2400m *, enum i2400m_bri bm_flags);
extern void i2400m_release(struct i2400m *);
extern int i2400m_rx(struct i2400m *, struct sk_buff *);
extern struct i2400m_msg_hdr *i2400m_tx_msg_get(struct i2400m *, size_t *);
extern void i2400m_tx_msg_sent(struct i2400m *);
static const __le32 i2400m_NBOOT_BARKER[4] = {
cpu_to_le32(I2400M_NBOOT_BARKER),
cpu_to_le32(I2400M_NBOOT_BARKER),
cpu_to_le32(I2400M_NBOOT_BARKER),
cpu_to_le32(I2400M_NBOOT_BARKER)
};
static const __le32 i2400m_SBOOT_BARKER[4] = {
cpu_to_le32(I2400M_SBOOT_BARKER),
cpu_to_le32(I2400M_SBOOT_BARKER),
cpu_to_le32(I2400M_SBOOT_BARKER),
cpu_to_le32(I2400M_SBOOT_BARKER)
};
extern int i2400m_power_save_disabled;
/*
* Utility functions
*/
static inline
struct device *i2400m_dev(struct i2400m *i2400m)
{
return i2400m->wimax_dev.net_dev->dev.parent;
}
/*
* Helper for scheduling simple work functions
*
* This struct can get any kind of payload attached (normally in the
* form of a struct where you pack the stuff you want to pass to the
* _work function).
*/
struct i2400m_work {
struct work_struct ws;
struct i2400m *i2400m;
u8 pl[0];
};
extern int i2400m_queue_work(struct i2400m *,
void (*)(struct work_struct *), gfp_t,
const void *, size_t);
extern int i2400m_msg_check_status(const struct i2400m_l3l4_hdr *,
char *, size_t);
extern int i2400m_msg_size_check(struct i2400m *,
const struct i2400m_l3l4_hdr *, size_t);
extern struct sk_buff *i2400m_msg_to_dev(struct i2400m *, const void *, size_t);
extern void i2400m_msg_to_dev_cancel_wait(struct i2400m *, int);
extern void i2400m_msg_ack_hook(struct i2400m *,
const struct i2400m_l3l4_hdr *, size_t);
extern void i2400m_report_hook(struct i2400m *,
const struct i2400m_l3l4_hdr *, size_t);
extern int i2400m_cmd_enter_powersave(struct i2400m *);
extern int i2400m_cmd_get_state(struct i2400m *);
extern int i2400m_cmd_exit_idle(struct i2400m *);
extern struct sk_buff *i2400m_get_device_info(struct i2400m *);
extern int i2400m_firmware_check(struct i2400m *);
extern int i2400m_set_init_config(struct i2400m *,
const struct i2400m_tlv_hdr **, size_t);
extern int i2400m_set_idle_timeout(struct i2400m *, unsigned);
static inline
struct usb_endpoint_descriptor *usb_get_epd(struct usb_interface *iface, int ep)
{
return &iface->cur_altsetting->endpoint[ep].desc;
}
extern int i2400m_op_rfkill_sw_toggle(struct wimax_dev *,
enum wimax_rf_state);
extern void i2400m_report_tlv_rf_switches_status(
struct i2400m *, const struct i2400m_tlv_rf_switches_status *);
/*
* Helpers for firmware backwards compability
*
* As we aim to support at least the firmware version that was
* released with the previous kernel/driver release, some code will be
* conditionally executed depending on the firmware version. On each
* release, the code to support fw releases past the last two ones
* will be purged.
*
* By making it depend on this macros, it is easier to keep it a tab
* on what has to go and what not.
*/
static inline
unsigned i2400m_le_v1_3(struct i2400m *i2400m)
{
/* running fw is lower or v1.3 */
return i2400m->fw_version <= 0x00090001;
}
static inline
unsigned i2400m_ge_v1_4(struct i2400m *i2400m)
{
/* running fw is higher or v1.4 */
return i2400m->fw_version >= 0x00090002;
}
/*
* Do a millisecond-sleep for allowing wireshark to dump all the data
* packets. Used only for debugging.
*/
static inline
void __i2400m_msleep(unsigned ms)
{
#if 1
#else
msleep(ms);
#endif
}
/* Module parameters */
extern int i2400m_idle_mode_disabled;
extern int i2400m_rx_reorder_disabled;
#endif /* #ifndef __I2400M_H__ */

View File

@@ -0,0 +1,586 @@
/*
* Intel Wireless WiMAX Connection 2400m
* Glue with the networking stack
*
*
* Copyright (C) 2007 Intel Corporation <linux-wimax@intel.com>
* Yanir Lubetkin <yanirx.lubetkin@intel.com>
* Inaky Perez-Gonzalez <inaky.perez-gonzalez@intel.com>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License version
* 2 as published by the Free Software Foundation.
*
* 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; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
* 02110-1301, USA.
*
*
* This implements an ethernet device for the i2400m.
*
* We fake being an ethernet device to simplify the support from user
* space and from the other side. The world is (sadly) configured to
* take in only Ethernet devices...
*
* Because of this, when using firmwares <= v1.3, there is an
* copy-each-rxed-packet overhead on the RX path. Each IP packet has
* to be reallocated to add an ethernet header (as there is no space
* in what we get from the device). This is a known drawback and
* firmwares >= 1.4 add header space that can be used to insert the
* ethernet header without having to reallocate and copy.
*
* TX error handling is tricky; because we have to FIFO/queue the
* buffers for transmission (as the hardware likes it aggregated), we
* just give the skb to the TX subsystem and by the time it is
* transmitted, we have long forgotten about it. So we just don't care
* too much about it.
*
* Note that when the device is in idle mode with the basestation, we
* need to negotiate coming back up online. That involves negotiation
* and possible user space interaction. Thus, we defer to a workqueue
* to do all that. By default, we only queue a single packet and drop
* the rest, as potentially the time to go back from idle to normal is
* long.
*
* ROADMAP
*
* i2400m_open Called on ifconfig up
* i2400m_stop Called on ifconfig down
*
* i2400m_hard_start_xmit Called by the network stack to send a packet
* i2400m_net_wake_tx Wake up device from basestation-IDLE & TX
* i2400m_wake_tx_work
* i2400m_cmd_exit_idle
* i2400m_tx
* i2400m_net_tx TX a data frame
* i2400m_tx
*
* i2400m_change_mtu Called on ifconfig mtu XXX
*
* i2400m_tx_timeout Called when the device times out
*
* i2400m_net_rx Called by the RX code when a data frame is
* available (firmware <= 1.3)
* i2400m_net_erx Called by the RX code when a data frame is
* available (firmware >= 1.4).
* i2400m_netdev_setup Called to setup all the netdev stuff from
* alloc_netdev.
*/
#include <linux/if_arp.h>
#include <linux/netdevice.h>
#include "i2400m.h"
#define D_SUBMODULE netdev
#include "debug-levels.h"
enum {
/* netdev interface */
/*
* Out of NWG spec (R1_v1.2.2), 3.3.3 ASN Bearer Plane MTU Size
*
* The MTU is 1400 or less
*/
I2400M_MAX_MTU = 1400,
I2400M_TX_TIMEOUT = HZ,
I2400M_TX_QLEN = 5,
};
static
int i2400m_open(struct net_device *net_dev)
{
int result;
struct i2400m *i2400m = net_dev_to_i2400m(net_dev);
struct device *dev = i2400m_dev(i2400m);
d_fnstart(3, dev, "(net_dev %p [i2400m %p])\n", net_dev, i2400m);
if (i2400m->ready == 0) {
dev_err(dev, "Device is still initializing\n");
result = -EBUSY;
} else
result = 0;
d_fnend(3, dev, "(net_dev %p [i2400m %p]) = %d\n",
net_dev, i2400m, result);
return result;
}
/*
*
* On kernel versions where cancel_work_sync() didn't return anything,
* we rely on wake_tx_skb() being non-NULL.
*/
static
int i2400m_stop(struct net_device *net_dev)
{
struct i2400m *i2400m = net_dev_to_i2400m(net_dev);
struct device *dev = i2400m_dev(i2400m);
d_fnstart(3, dev, "(net_dev %p [i2400m %p])\n", net_dev, i2400m);
/* See i2400m_hard_start_xmit(), references are taken there
* and here we release them if the work was still
* pending. Note we can't differentiate work not pending vs
* never scheduled, so the NULL check does that. */
if (cancel_work_sync(&i2400m->wake_tx_ws) == 0
&& i2400m->wake_tx_skb != NULL) {
unsigned long flags;
struct sk_buff *wake_tx_skb;
spin_lock_irqsave(&i2400m->tx_lock, flags);
wake_tx_skb = i2400m->wake_tx_skb; /* compat help */
i2400m->wake_tx_skb = NULL; /* compat help */
spin_unlock_irqrestore(&i2400m->tx_lock, flags);
i2400m_put(i2400m);
kfree_skb(wake_tx_skb);
}
d_fnend(3, dev, "(net_dev %p [i2400m %p]) = 0\n", net_dev, i2400m);
return 0;
}
/*
* Wake up the device and transmit a held SKB, then restart the net queue
*
* When the device goes into basestation-idle mode, we need to tell it
* to exit that mode; it will negotiate with the base station, user
* space may have to intervene to rehandshake crypto and then tell us
* when it is ready to transmit the packet we have "queued". Still we
* need to give it sometime after it reports being ok.
*
* On error, there is not much we can do. If the error was on TX, we
* still wake the queue up to see if the next packet will be luckier.
*
* If _cmd_exit_idle() fails...well, it could be many things; most
* commonly it is that something else took the device out of IDLE mode
* (for example, the base station). In that case we get an -EILSEQ and
* we are just going to ignore that one. If the device is back to
* connected, then fine -- if it is someother state, the packet will
* be dropped anyway.
*/
void i2400m_wake_tx_work(struct work_struct *ws)
{
int result;
struct i2400m *i2400m = container_of(ws, struct i2400m, wake_tx_ws);
struct device *dev = i2400m_dev(i2400m);
struct sk_buff *skb = i2400m->wake_tx_skb;
unsigned long flags;
spin_lock_irqsave(&i2400m->tx_lock, flags);
skb = i2400m->wake_tx_skb;
i2400m->wake_tx_skb = NULL;
spin_unlock_irqrestore(&i2400m->tx_lock, flags);
d_fnstart(3, dev, "(ws %p i2400m %p skb %p)\n", ws, i2400m, skb);
result = -EINVAL;
if (skb == NULL) {
dev_err(dev, "WAKE&TX: skb dissapeared!\n");
goto out_put;
}
result = i2400m_cmd_exit_idle(i2400m);
if (result == -EILSEQ)
result = 0;
if (result < 0) {
dev_err(dev, "WAKE&TX: device didn't get out of idle: "
"%d\n", result);
goto error;
}
result = wait_event_timeout(i2400m->state_wq,
i2400m->state != I2400M_SS_IDLE, 5 * HZ);
if (result == 0)
result = -ETIMEDOUT;
if (result < 0) {
dev_err(dev, "WAKE&TX: error waiting for device to exit IDLE: "
"%d\n", result);
goto error;
}
msleep(20); /* device still needs some time or it drops it */
result = i2400m_tx(i2400m, skb->data, skb->len, I2400M_PT_DATA);
netif_wake_queue(i2400m->wimax_dev.net_dev);
error:
kfree_skb(skb); /* refcount transferred by _hard_start_xmit() */
out_put:
i2400m_put(i2400m);
d_fnend(3, dev, "(ws %p i2400m %p skb %p) = void [%d]\n",
ws, i2400m, skb, result);
}
/*
* Prepare the data payload TX header
*
* The i2400m expects a 4 byte header in front of a data packet.
*
* Because we pretend to be an ethernet device, this packet comes with
* an ethernet header. Pull it and push our header.
*/
static
void i2400m_tx_prep_header(struct sk_buff *skb)
{
struct i2400m_pl_data_hdr *pl_hdr;
skb_pull(skb, ETH_HLEN);
pl_hdr = (struct i2400m_pl_data_hdr *) skb_push(skb, sizeof(*pl_hdr));
pl_hdr->reserved = 0;
}
/*
* TX an skb to an idle device
*
* When the device is in basestation-idle mode, we need to wake it up
* and then TX. So we queue a work_struct for doing so.
*
* We need to get an extra ref for the skb (so it is not dropped), as
* well as be careful not to queue more than one request (won't help
* at all). If more than one request comes or there are errors, we
* just drop the packets (see i2400m_hard_start_xmit()).
*/
static
int i2400m_net_wake_tx(struct i2400m *i2400m, struct net_device *net_dev,
struct sk_buff *skb)
{
int result;
struct device *dev = i2400m_dev(i2400m);
unsigned long flags;
d_fnstart(3, dev, "(skb %p net_dev %p)\n", skb, net_dev);
if (net_ratelimit()) {
d_printf(3, dev, "WAKE&NETTX: "
"skb %p sending %d bytes to radio\n",
skb, skb->len);
d_dump(4, dev, skb->data, skb->len);
}
/* We hold a ref count for i2400m and skb, so when
* stopping() the device, we need to cancel that work
* and if pending, release those resources. */
result = 0;
spin_lock_irqsave(&i2400m->tx_lock, flags);
if (!work_pending(&i2400m->wake_tx_ws)) {
netif_stop_queue(net_dev);
i2400m_get(i2400m);
i2400m->wake_tx_skb = skb_get(skb); /* transfer ref count */
i2400m_tx_prep_header(skb);
result = schedule_work(&i2400m->wake_tx_ws);
WARN_ON(result == 0);
}
spin_unlock_irqrestore(&i2400m->tx_lock, flags);
if (result == 0) {
/* Yes, this happens even if we stopped the
* queue -- blame the queue disciplines that
* queue without looking -- I guess there is a reason
* for that. */
if (net_ratelimit())
d_printf(1, dev, "NETTX: device exiting idle, "
"dropping skb %p, queue running %d\n",
skb, netif_queue_stopped(net_dev));
result = -EBUSY;
}
d_fnend(3, dev, "(skb %p net_dev %p) = %d\n", skb, net_dev, result);
return result;
}
/*
* Transmit a packet to the base station on behalf of the network stack.
*
* Returns: 0 if ok, < 0 errno code on error.
*
* We need to pull the ethernet header and add the hardware header,
* which is currently set to all zeroes and reserved.
*/
static
int i2400m_net_tx(struct i2400m *i2400m, struct net_device *net_dev,
struct sk_buff *skb)
{
int result;
struct device *dev = i2400m_dev(i2400m);
d_fnstart(3, dev, "(i2400m %p net_dev %p skb %p)\n",
i2400m, net_dev, skb);
/* FIXME: check eth hdr, only IPv4 is routed by the device as of now */
net_dev->trans_start = jiffies;
i2400m_tx_prep_header(skb);
d_printf(3, dev, "NETTX: skb %p sending %d bytes to radio\n",
skb, skb->len);
d_dump(4, dev, skb->data, skb->len);
result = i2400m_tx(i2400m, skb->data, skb->len, I2400M_PT_DATA);
d_fnend(3, dev, "(i2400m %p net_dev %p skb %p) = %d\n",
i2400m, net_dev, skb, result);
return result;
}
/*
* Transmit a packet to the base station on behalf of the network stack
*
*
* Returns: NETDEV_TX_OK (always, even in case of error)
*
* In case of error, we just drop it. Reasons:
*
* - we add a hw header to each skb, and if the network stack
* retries, we have no way to know if that skb has it or not.
*
* - network protocols have their own drop-recovery mechanisms
*
* - there is not much else we can do
*
* If the device is idle, we need to wake it up; that is an operation
* that will sleep. See i2400m_net_wake_tx() for details.
*/
static
netdev_tx_t i2400m_hard_start_xmit(struct sk_buff *skb,
struct net_device *net_dev)
{
struct i2400m *i2400m = net_dev_to_i2400m(net_dev);
struct device *dev = i2400m_dev(i2400m);
int result;
d_fnstart(3, dev, "(skb %p net_dev %p)\n", skb, net_dev);
if (i2400m->state == I2400M_SS_IDLE)
result = i2400m_net_wake_tx(i2400m, net_dev, skb);
else
result = i2400m_net_tx(i2400m, net_dev, skb);
if (result < 0)
net_dev->stats.tx_dropped++;
else {
net_dev->stats.tx_packets++;
net_dev->stats.tx_bytes += skb->len;
}
kfree_skb(skb);
d_fnend(3, dev, "(skb %p net_dev %p)\n", skb, net_dev);
return NETDEV_TX_OK;
}
static
int i2400m_change_mtu(struct net_device *net_dev, int new_mtu)
{
int result;
struct i2400m *i2400m = net_dev_to_i2400m(net_dev);
struct device *dev = i2400m_dev(i2400m);
if (new_mtu >= I2400M_MAX_MTU) {
dev_err(dev, "Cannot change MTU to %d (max is %d)\n",
new_mtu, I2400M_MAX_MTU);
result = -EINVAL;
} else {
net_dev->mtu = new_mtu;
result = 0;
}
return result;
}
static
void i2400m_tx_timeout(struct net_device *net_dev)
{
/*
* We might want to kick the device
*
* There is not much we can do though, as the device requires
* that we send the data aggregated. By the time we receive
* this, there might be data pending to be sent or not...
*/
net_dev->stats.tx_errors++;
return;
}
/*
* Create a fake ethernet header
*
* For emulating an ethernet device, every received IP header has to
* be prefixed with an ethernet header. Fake it with the given
* protocol.
*/
static
void i2400m_rx_fake_eth_header(struct net_device *net_dev,
void *_eth_hdr, __be16 protocol)
{
struct i2400m *i2400m = net_dev_to_i2400m(net_dev);
struct ethhdr *eth_hdr = _eth_hdr;
memcpy(eth_hdr->h_dest, net_dev->dev_addr, sizeof(eth_hdr->h_dest));
memcpy(eth_hdr->h_source, i2400m->src_mac_addr,
sizeof(eth_hdr->h_source));
eth_hdr->h_proto = protocol;
}
/*
* i2400m_net_rx - pass a network packet to the stack
*
* @i2400m: device instance
* @skb_rx: the skb where the buffer pointed to by @buf is
* @i: 1 if payload is the only one
* @buf: pointer to the buffer containing the data
* @len: buffer's length
*
* This is only used now for the v1.3 firmware. It will be deprecated
* in >= 2.6.31.
*
* Note that due to firmware limitations, we don't have space to add
* an ethernet header, so we need to copy each packet. Firmware
* versions >= v1.4 fix this [see i2400m_net_erx()].
*
* We just clone the skb and set it up so that it's skb->data pointer
* points to "buf" and it's length.
*
* Note that if the payload is the last (or the only one) in a
* multi-payload message, we don't clone the SKB but just reuse it.
*
* This function is normally run from a thread context. However, we
* still use netif_rx() instead of netif_receive_skb() as was
* recommended in the mailing list. Reason is in some stress tests
* when sending/receiving a lot of data we seem to hit a softlock in
* the kernel's TCP implementation [aroudn tcp_delay_timer()]. Using
* netif_rx() took care of the issue.
*
* This is, of course, still open to do more research on why running
* with netif_receive_skb() hits this softlock. FIXME.
*
* FIXME: currently we don't do any efforts at distinguishing if what
* we got was an IPv4 or IPv6 header, to setup the protocol field
* correctly.
*/
void i2400m_net_rx(struct i2400m *i2400m, struct sk_buff *skb_rx,
unsigned i, const void *buf, int buf_len)
{
struct net_device *net_dev = i2400m->wimax_dev.net_dev;
struct device *dev = i2400m_dev(i2400m);
struct sk_buff *skb;
d_fnstart(2, dev, "(i2400m %p buf %p buf_len %d)\n",
i2400m, buf, buf_len);
if (i) {
skb = skb_get(skb_rx);
d_printf(2, dev, "RX: reusing first payload skb %p\n", skb);
skb_pull(skb, buf - (void *) skb->data);
skb_trim(skb, (void *) skb_end_pointer(skb) - buf);
} else {
/* Yes, this is bad -- a lot of overhead -- see
* comments at the top of the file */
skb = __netdev_alloc_skb(net_dev, buf_len, GFP_KERNEL);
if (skb == NULL) {
dev_err(dev, "NETRX: no memory to realloc skb\n");
net_dev->stats.rx_dropped++;
goto error_skb_realloc;
}
memcpy(skb_put(skb, buf_len), buf, buf_len);
}
i2400m_rx_fake_eth_header(i2400m->wimax_dev.net_dev,
skb->data - ETH_HLEN,
cpu_to_be16(ETH_P_IP));
skb_set_mac_header(skb, -ETH_HLEN);
skb->dev = i2400m->wimax_dev.net_dev;
skb->protocol = htons(ETH_P_IP);
net_dev->stats.rx_packets++;
net_dev->stats.rx_bytes += buf_len;
d_printf(3, dev, "NETRX: receiving %d bytes to network stack\n",
buf_len);
d_dump(4, dev, buf, buf_len);
netif_rx_ni(skb); /* see notes in function header */
error_skb_realloc:
d_fnend(2, dev, "(i2400m %p buf %p buf_len %d) = void\n",
i2400m, buf, buf_len);
}
/*
* i2400m_net_erx - pass a network packet to the stack (extended version)
*
* @i2400m: device descriptor
* @skb: the skb where the packet is - the skb should be set to point
* at the IP packet; this function will add ethernet headers if
* needed.
* @cs: packet type
*
* This is only used now for firmware >= v1.4. Note it is quite
* similar to i2400m_net_rx() (used only for v1.3 firmware).
*
* This function is normally run from a thread context. However, we
* still use netif_rx() instead of netif_receive_skb() as was
* recommended in the mailing list. Reason is in some stress tests
* when sending/receiving a lot of data we seem to hit a softlock in
* the kernel's TCP implementation [aroudn tcp_delay_timer()]. Using
* netif_rx() took care of the issue.
*
* This is, of course, still open to do more research on why running
* with netif_receive_skb() hits this softlock. FIXME.
*/
void i2400m_net_erx(struct i2400m *i2400m, struct sk_buff *skb,
enum i2400m_cs cs)
{
struct net_device *net_dev = i2400m->wimax_dev.net_dev;
struct device *dev = i2400m_dev(i2400m);
int protocol;
d_fnstart(2, dev, "(i2400m %p skb %p [%u] cs %d)\n",
i2400m, skb, skb->len, cs);
switch(cs) {
case I2400M_CS_IPV4_0:
case I2400M_CS_IPV4:
protocol = ETH_P_IP;
i2400m_rx_fake_eth_header(i2400m->wimax_dev.net_dev,
skb->data - ETH_HLEN,
cpu_to_be16(ETH_P_IP));
skb_set_mac_header(skb, -ETH_HLEN);
skb->dev = i2400m->wimax_dev.net_dev;
skb->protocol = htons(ETH_P_IP);
net_dev->stats.rx_packets++;
net_dev->stats.rx_bytes += skb->len;
break;
default:
dev_err(dev, "ERX: BUG? CS type %u unsupported\n", cs);
goto error;
}
d_printf(3, dev, "ERX: receiving %d bytes to the network stack\n",
skb->len);
d_dump(4, dev, skb->data, skb->len);
netif_rx_ni(skb); /* see notes in function header */
error:
d_fnend(2, dev, "(i2400m %p skb %p [%u] cs %d) = void\n",
i2400m, skb, skb->len, cs);
}
static const struct net_device_ops i2400m_netdev_ops = {
.ndo_open = i2400m_open,
.ndo_stop = i2400m_stop,
.ndo_start_xmit = i2400m_hard_start_xmit,
.ndo_tx_timeout = i2400m_tx_timeout,
.ndo_change_mtu = i2400m_change_mtu,
};
/**
* i2400m_netdev_setup - Setup setup @net_dev's i2400m private data
*
* Called by alloc_netdev()
*/
void i2400m_netdev_setup(struct net_device *net_dev)
{
d_fnstart(3, NULL, "(net_dev %p)\n", net_dev);
ether_setup(net_dev);
net_dev->mtu = I2400M_MAX_MTU;
net_dev->tx_queue_len = I2400M_TX_QLEN;
net_dev->features =
NETIF_F_VLAN_CHALLENGED
| NETIF_F_HIGHDMA;
net_dev->flags =
IFF_NOARP /* i2400m is apure IP device */
& (~IFF_BROADCAST /* i2400m is P2P */
& ~IFF_MULTICAST);
net_dev->watchdog_timeo = I2400M_TX_TIMEOUT;
net_dev->netdev_ops = &i2400m_netdev_ops;
d_fnend(3, NULL, "(net_dev %p) = void\n", net_dev);
}
EXPORT_SYMBOL_GPL(i2400m_netdev_setup);

View File

@@ -0,0 +1,209 @@
/*
* Intel Wireless WiMAX Connection 2400m
* Implement backend for the WiMAX stack rfkill support
*
*
* Copyright (C) 2007-2008 Intel Corporation <linux-wimax@intel.com>
* Inaky Perez-Gonzalez <inaky.perez-gonzalez@intel.com>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License version
* 2 as published by the Free Software Foundation.
*
* 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; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
* 02110-1301, USA.
*
*
* The WiMAX kernel stack integrates into RF-Kill and keeps the
* switches's status. We just need to:
*
* - report changes in the HW RF Kill switch [with
* wimax_rfkill_{sw,hw}_report(), which happens when we detect those
* indications coming through hardware reports]. We also do it on
* initialization to let the stack know the intial HW state.
*
* - implement indications from the stack to change the SW RF Kill
* switch (coming from sysfs, the wimax stack or user space).
*/
#include "i2400m.h"
#include <linux/wimax/i2400m.h>
#define D_SUBMODULE rfkill
#include "debug-levels.h"
/*
* Return true if the i2400m radio is in the requested wimax_rf_state state
*
*/
static
int i2400m_radio_is(struct i2400m *i2400m, enum wimax_rf_state state)
{
if (state == WIMAX_RF_OFF)
return i2400m->state == I2400M_SS_RF_OFF
|| i2400m->state == I2400M_SS_RF_SHUTDOWN;
else if (state == WIMAX_RF_ON)
/* state == WIMAX_RF_ON */
return i2400m->state != I2400M_SS_RF_OFF
&& i2400m->state != I2400M_SS_RF_SHUTDOWN;
else {
BUG();
return -EINVAL; /* shut gcc warnings on certain arches */
}
}
/*
* WiMAX stack operation: implement SW RFKill toggling
*
* @wimax_dev: device descriptor
* @skb: skb where the message has been received; skb->data is
* expected to point to the message payload.
* @genl_info: passed by the generic netlink layer
*
* Generic Netlink will call this function when a message is sent from
* userspace to change the software RF-Kill switch status.
*
* This function will set the device's sofware RF-Kill switch state to
* match what is requested.
*
* NOTE: the i2400m has a strict state machine; we can only set the
* RF-Kill switch when it is on, the HW RF-Kill is on and the
* device is initialized. So we ignore errors steaming from not
* being in the right state (-EILSEQ).
*/
int i2400m_op_rfkill_sw_toggle(struct wimax_dev *wimax_dev,
enum wimax_rf_state state)
{
int result;
struct i2400m *i2400m = wimax_dev_to_i2400m(wimax_dev);
struct device *dev = i2400m_dev(i2400m);
struct sk_buff *ack_skb;
struct {
struct i2400m_l3l4_hdr hdr;
struct i2400m_tlv_rf_operation sw_rf;
} __attribute__((packed)) *cmd;
char strerr[32];
d_fnstart(4, dev, "(wimax_dev %p state %d)\n", wimax_dev, state);
result = -ENOMEM;
cmd = kzalloc(sizeof(*cmd), GFP_KERNEL);
if (cmd == NULL)
goto error_alloc;
cmd->hdr.type = cpu_to_le16(I2400M_MT_CMD_RF_CONTROL);
cmd->hdr.length = sizeof(cmd->sw_rf);
cmd->hdr.version = cpu_to_le16(I2400M_L3L4_VERSION);
cmd->sw_rf.hdr.type = cpu_to_le16(I2400M_TLV_RF_OPERATION);
cmd->sw_rf.hdr.length = cpu_to_le16(sizeof(cmd->sw_rf.status));
switch (state) {
case WIMAX_RF_OFF: /* RFKILL ON, radio OFF */
cmd->sw_rf.status = cpu_to_le32(2);
break;
case WIMAX_RF_ON: /* RFKILL OFF, radio ON */
cmd->sw_rf.status = cpu_to_le32(1);
break;
default:
BUG();
}
ack_skb = i2400m_msg_to_dev(i2400m, cmd, sizeof(*cmd));
result = PTR_ERR(ack_skb);
if (IS_ERR(ack_skb)) {
dev_err(dev, "Failed to issue 'RF Control' command: %d\n",
result);
goto error_msg_to_dev;
}
result = i2400m_msg_check_status(wimax_msg_data(ack_skb),
strerr, sizeof(strerr));
if (result < 0) {
dev_err(dev, "'RF Control' (0x%04x) command failed: %d - %s\n",
I2400M_MT_CMD_RF_CONTROL, result, strerr);
goto error_cmd;
}
/* Now we wait for the state to change to RADIO_OFF or RADIO_ON */
result = wait_event_timeout(
i2400m->state_wq, i2400m_radio_is(i2400m, state),
5 * HZ);
if (result == 0)
result = -ETIMEDOUT;
if (result < 0)
dev_err(dev, "Error waiting for device to toggle RF state: "
"%d\n", result);
result = 0;
error_cmd:
kfree_skb(ack_skb);
error_msg_to_dev:
error_alloc:
d_fnend(4, dev, "(wimax_dev %p state %d) = %d\n",
wimax_dev, state, result);
return result;
}
/*
* Inform the WiMAX stack of changes in the RF Kill switches reported
* by the device
*
* @i2400m: device descriptor
* @rfss: TLV for RF Switches status; already validated
*
* NOTE: the reports on RF switch status cannot be trusted
* or used until the device is in a state of RADIO_OFF
* or greater.
*/
void i2400m_report_tlv_rf_switches_status(
struct i2400m *i2400m,
const struct i2400m_tlv_rf_switches_status *rfss)
{
struct device *dev = i2400m_dev(i2400m);
enum i2400m_rf_switch_status hw, sw;
enum wimax_st wimax_state;
sw = le32_to_cpu(rfss->sw_rf_switch);
hw = le32_to_cpu(rfss->hw_rf_switch);
d_fnstart(3, dev, "(i2400m %p rfss %p [hw %u sw %u])\n",
i2400m, rfss, hw, sw);
/* We only process rw switch evens when the device has been
* fully initialized */
wimax_state = wimax_state_get(&i2400m->wimax_dev);
if (wimax_state < WIMAX_ST_RADIO_OFF) {
d_printf(3, dev, "ignoring RF switches report, state %u\n",
wimax_state);
goto out;
}
switch (sw) {
case I2400M_RF_SWITCH_ON: /* RF Kill disabled (radio on) */
wimax_report_rfkill_sw(&i2400m->wimax_dev, WIMAX_RF_ON);
break;
case I2400M_RF_SWITCH_OFF: /* RF Kill enabled (radio off) */
wimax_report_rfkill_sw(&i2400m->wimax_dev, WIMAX_RF_OFF);
break;
default:
dev_err(dev, "HW BUG? Unknown RF SW state 0x%x\n", sw);
}
switch (hw) {
case I2400M_RF_SWITCH_ON: /* RF Kill disabled (radio on) */
wimax_report_rfkill_hw(&i2400m->wimax_dev, WIMAX_RF_ON);
break;
case I2400M_RF_SWITCH_OFF: /* RF Kill enabled (radio off) */
wimax_report_rfkill_hw(&i2400m->wimax_dev, WIMAX_RF_OFF);
break;
default:
dev_err(dev, "HW BUG? Unknown RF HW state 0x%x\n", hw);
}
out:
d_fnend(3, dev, "(i2400m %p rfss %p [hw %u sw %u]) = void\n",
i2400m, rfss, hw, sw);
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,22 @@
/*
* debug levels control file for the i2400m module's
*/
#ifndef __debug_levels__h__
#define __debug_levels__h__
/* Maximum compile and run time debug level for all submodules */
#define D_MODULENAME i2400m_sdio
#define D_MASTER CONFIG_WIMAX_I2400M_DEBUG_LEVEL
#include <linux/wimax/debug.h>
/* List of all the enabled modules */
enum d_module {
D_SUBMODULE_DECLARE(main),
D_SUBMODULE_DECLARE(tx),
D_SUBMODULE_DECLARE(rx),
D_SUBMODULE_DECLARE(fw)
};
#endif /* #ifndef __debug_levels__h__ */

View File

@@ -0,0 +1,209 @@
/*
* Intel Wireless WiMAX Connection 2400m
* Firmware uploader's SDIO specifics
*
*
* Copyright (C) 2007-2008 Intel Corporation. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in
* the documentation and/or other materials provided with the
* distribution.
* * Neither the name of Intel Corporation nor the names of its
* contributors may be used to endorse or promote products derived
* from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
*
* Intel Corporation <linux-wimax@intel.com>
* Yanir Lubetkin <yanirx.lubetkin@intel.com>
* Inaky Perez-Gonzalez <inaky.perez-gonzalez@intel.com>
* - Initial implementation
*
* Inaky Perez-Gonzalez <inaky.perez-gonzalez@intel.com>
* - Bus generic/specific split for USB
*
* Dirk Brandewie <dirk.j.brandewie@intel.com>
* - Initial implementation for SDIO
*
* Inaky Perez-Gonzalez <inaky.perez-gonzalez@intel.com>
* - SDIO rehash for changes in the bus-driver model
*
* Dirk Brandewie <dirk.j.brandewie@intel.com>
* - Make it IRQ based, not polling
*
* THE PROCEDURE
*
* See fw.c for the generic description of this procedure.
*
* This file implements only the SDIO specifics. It boils down to how
* to send a command and waiting for an acknowledgement from the
* device.
*
* All this code is sequential -- all i2400ms_bus_bm_*() functions are
* executed in the same thread, except i2400ms_bm_irq() [on its own by
* the SDIO driver]. This makes it possible to avoid locking.
*
* COMMAND EXECUTION
*
* The generic firmware upload code will call i2400m_bus_bm_cmd_send()
* to send commands.
*
* The SDIO devices expects things in 256 byte blocks, so it will pad
* it, compute the checksum (if needed) and pass it to SDIO.
*
* ACK RECEPTION
*
* This works in IRQ mode -- the fw loader says when to wait for data
* and for that it calls i2400ms_bus_bm_wait_for_ack().
*
* This checks if there is any data available (RX size > 0); if not,
* waits for the IRQ handler to notify about it. Once there is data,
* it is read and passed to the caller. Doing it this way we don't
* need much coordination/locking, and it makes it much more difficult
* for an interrupt to be lost and the wait_for_ack() function getting
* stuck even when data is pending.
*/
#include <linux/mmc/sdio_func.h>
#include "i2400m-sdio.h"
#define D_SUBMODULE fw
#include "sdio-debug-levels.h"
/*
* Send a boot-mode command to the SDIO function
*
* We use a bounce buffer (i2400m->bm_cmd_buf) because we need to
* touch the header if the RAW flag is not set.
*
* @flags: pass thru from i2400m_bm_cmd()
* @return: cmd_size if ok, < 0 errno code on error.
*
* Note the command is padded to the SDIO block size for the device.
*/
ssize_t i2400ms_bus_bm_cmd_send(struct i2400m *i2400m,
const struct i2400m_bootrom_header *_cmd,
size_t cmd_size, int flags)
{
ssize_t result;
struct device *dev = i2400m_dev(i2400m);
struct i2400ms *i2400ms = container_of(i2400m, struct i2400ms, i2400m);
int opcode = _cmd == NULL ? -1 : i2400m_brh_get_opcode(_cmd);
struct i2400m_bootrom_header *cmd;
/* SDIO restriction */
size_t cmd_size_a = ALIGN(cmd_size, I2400MS_BLK_SIZE);
d_fnstart(5, dev, "(i2400m %p cmd %p size %zu)\n",
i2400m, _cmd, cmd_size);
result = -E2BIG;
if (cmd_size > I2400M_BM_CMD_BUF_SIZE)
goto error_too_big;
memcpy(i2400m->bm_cmd_buf, _cmd, cmd_size); /* Prep command */
cmd = i2400m->bm_cmd_buf;
if (cmd_size_a > cmd_size) /* Zero pad space */
memset(i2400m->bm_cmd_buf + cmd_size, 0, cmd_size_a - cmd_size);
if ((flags & I2400M_BM_CMD_RAW) == 0) {
if (WARN_ON(i2400m_brh_get_response_required(cmd) == 0))
dev_warn(dev, "SW BUG: response_required == 0\n");
i2400m_bm_cmd_prepare(cmd);
}
d_printf(4, dev, "BM cmd %d: %zu bytes (%zu padded)\n",
opcode, cmd_size, cmd_size_a);
d_dump(5, dev, cmd, cmd_size);
sdio_claim_host(i2400ms->func); /* Send & check */
result = sdio_memcpy_toio(i2400ms->func, I2400MS_DATA_ADDR,
i2400m->bm_cmd_buf, cmd_size_a);
sdio_release_host(i2400ms->func);
if (result < 0) {
dev_err(dev, "BM cmd %d: cannot send: %ld\n",
opcode, (long) result);
goto error_cmd_send;
}
result = cmd_size;
error_cmd_send:
error_too_big:
d_fnend(5, dev, "(i2400m %p cmd %p size %zu) = %d\n",
i2400m, _cmd, cmd_size, (int) result);
return result;
}
/*
* Read an ack from the device's boot-mode
*
* @i2400m:
* @_ack: pointer to where to store the read data
* @ack_size: how many bytes we should read
*
* Returns: < 0 errno code on error; otherwise, amount of received bytes.
*
* The ACK for a BM command is always at least sizeof(*ack) bytes, so
* check for that. We don't need to check for device reboots
*
*/
ssize_t i2400ms_bus_bm_wait_for_ack(struct i2400m *i2400m,
struct i2400m_bootrom_header *ack,
size_t ack_size)
{
ssize_t result;
struct i2400ms *i2400ms = container_of(i2400m, struct i2400ms, i2400m);
struct sdio_func *func = i2400ms->func;
struct device *dev = &func->dev;
int size;
BUG_ON(sizeof(*ack) > ack_size);
d_fnstart(5, dev, "(i2400m %p ack %p size %zu)\n",
i2400m, ack, ack_size);
spin_lock(&i2400m->rx_lock);
i2400ms->bm_ack_size = -EINPROGRESS;
spin_unlock(&i2400m->rx_lock);
result = wait_event_timeout(i2400ms->bm_wfa_wq,
i2400ms->bm_ack_size != -EINPROGRESS,
2 * HZ);
if (result == 0) {
result = -ETIMEDOUT;
dev_err(dev, "BM: error waiting for an ack\n");
goto error_timeout;
}
spin_lock(&i2400m->rx_lock);
result = i2400ms->bm_ack_size;
BUG_ON(result == -EINPROGRESS);
if (result < 0) /* so we exit when rx_release() is called */
dev_err(dev, "BM: %s failed: %zd\n", __func__, result);
else {
size = min(ack_size, i2400ms->bm_ack_size);
memcpy(ack, i2400m->bm_ack_buf, size);
}
i2400ms->bm_ack_size = -EINPROGRESS;
spin_unlock(&i2400m->rx_lock);
error_timeout:
d_fnend(5, dev, "(i2400m %p ack %p size %zu) = %zd\n",
i2400m, ack, ack_size, result);
return result;
}

View File

@@ -0,0 +1,280 @@
/*
* Intel Wireless WiMAX Connection 2400m
* SDIO RX handling
*
*
* Copyright (C) 2007-2008 Intel Corporation. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in
* the documentation and/or other materials provided with the
* distribution.
* * Neither the name of Intel Corporation nor the names of its
* contributors may be used to endorse or promote products derived
* from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
*
* Intel Corporation <linux-wimax@intel.com>
* Dirk Brandewie <dirk.j.brandewie@intel.com>
* - Initial implementation
*
*
* This handles the RX path on SDIO.
*
* The SDIO bus driver calls the "irq" routine when data is available.
* This is not a traditional interrupt routine since the SDIO bus
* driver calls us from its irq thread context. Because of this
* sleeping in the SDIO RX IRQ routine is okay.
*
* From there on, we obtain the size of the data that is available,
* allocate an skb, copy it and then pass it to the generic driver's
* RX routine [i2400m_rx()].
*
* ROADMAP
*
* i2400ms_irq()
* i2400ms_rx()
* __i2400ms_rx_get_size()
* i2400m_rx()
*
* i2400ms_rx_setup()
*
* i2400ms_rx_release()
*/
#include <linux/workqueue.h>
#include <linux/wait.h>
#include <linux/skbuff.h>
#include <linux/mmc/sdio.h>
#include <linux/mmc/sdio_func.h>
#include "i2400m-sdio.h"
#define D_SUBMODULE rx
#include "sdio-debug-levels.h"
static const __le32 i2400m_ACK_BARKER[4] = {
__constant_cpu_to_le32(I2400M_ACK_BARKER),
__constant_cpu_to_le32(I2400M_ACK_BARKER),
__constant_cpu_to_le32(I2400M_ACK_BARKER),
__constant_cpu_to_le32(I2400M_ACK_BARKER)
};
/*
* Read and return the amount of bytes available for RX
*
* The RX size has to be read like this: byte reads of three
* sequential locations; then glue'em together.
*
* sdio_readl() doesn't work.
*/
ssize_t __i2400ms_rx_get_size(struct i2400ms *i2400ms)
{
int ret, cnt, val;
ssize_t rx_size;
unsigned xfer_size_addr;
struct sdio_func *func = i2400ms->func;
struct device *dev = &i2400ms->func->dev;
d_fnstart(7, dev, "(i2400ms %p)\n", i2400ms);
xfer_size_addr = I2400MS_INTR_GET_SIZE_ADDR;
rx_size = 0;
for (cnt = 0; cnt < 3; cnt++) {
val = sdio_readb(func, xfer_size_addr + cnt, &ret);
if (ret < 0) {
dev_err(dev, "RX: Can't read byte %d of RX size from "
"0x%08x: %d\n", cnt, xfer_size_addr + cnt, ret);
rx_size = ret;
goto error_read;
}
rx_size = rx_size << 8 | (val & 0xff);
}
d_printf(6, dev, "RX: rx_size is %ld\n", (long) rx_size);
error_read:
d_fnend(7, dev, "(i2400ms %p) = %ld\n", i2400ms, (long) rx_size);
return rx_size;
}
/*
* Read data from the device (when in normal)
*
* Allocate an SKB of the right size, read the data in and then
* deliver it to the generic layer.
*
* We also check for a reboot barker. That means the device died and
* we have to reboot it.
*/
static
void i2400ms_rx(struct i2400ms *i2400ms)
{
int ret;
struct sdio_func *func = i2400ms->func;
struct device *dev = &func->dev;
struct i2400m *i2400m = &i2400ms->i2400m;
struct sk_buff *skb;
ssize_t rx_size;
d_fnstart(7, dev, "(i2400ms %p)\n", i2400ms);
rx_size = __i2400ms_rx_get_size(i2400ms);
if (rx_size < 0) {
ret = rx_size;
goto error_get_size;
}
ret = -ENOMEM;
skb = alloc_skb(rx_size, GFP_ATOMIC);
if (NULL == skb) {
dev_err(dev, "RX: unable to alloc skb\n");
goto error_alloc_skb;
}
ret = sdio_memcpy_fromio(func, skb->data,
I2400MS_DATA_ADDR, rx_size);
if (ret < 0) {
dev_err(dev, "RX: SDIO data read failed: %d\n", ret);
goto error_memcpy_fromio;
}
rmb(); /* make sure we get boot_mode from dev_reset_handle */
if (i2400m->boot_mode == 1) {
spin_lock(&i2400m->rx_lock);
i2400ms->bm_ack_size = rx_size;
spin_unlock(&i2400m->rx_lock);
memcpy(i2400m->bm_ack_buf, skb->data, rx_size);
wake_up(&i2400ms->bm_wfa_wq);
dev_err(dev, "RX: SDIO boot mode message\n");
kfree_skb(skb);
} else if (unlikely(!memcmp(skb->data, i2400m_NBOOT_BARKER,
sizeof(i2400m_NBOOT_BARKER))
|| !memcmp(skb->data, i2400m_SBOOT_BARKER,
sizeof(i2400m_SBOOT_BARKER)))) {
ret = i2400m_dev_reset_handle(i2400m);
dev_err(dev, "RX: SDIO reboot barker\n");
kfree_skb(skb);
} else {
skb_put(skb, rx_size);
i2400m_rx(i2400m, skb);
}
d_fnend(7, dev, "(i2400ms %p) = void\n", i2400ms);
return;
error_memcpy_fromio:
kfree_skb(skb);
error_alloc_skb:
error_get_size:
d_fnend(7, dev, "(i2400ms %p) = %d\n", i2400ms, ret);
return;
}
/*
* Process an interrupt from the SDIO card
*
* FIXME: need to process other events that are not just ready-to-read
*
* Checks there is data ready and then proceeds to read it.
*/
static
void i2400ms_irq(struct sdio_func *func)
{
int ret;
struct i2400ms *i2400ms = sdio_get_drvdata(func);
struct device *dev = &func->dev;
int val;
d_fnstart(6, dev, "(i2400ms %p)\n", i2400ms);
val = sdio_readb(func, I2400MS_INTR_STATUS_ADDR, &ret);
if (ret < 0) {
dev_err(dev, "RX: Can't read interrupt status: %d\n", ret);
goto error_no_irq;
}
if (!val) {
dev_err(dev, "RX: BUG? got IRQ but no interrupt ready?\n");
goto error_no_irq;
}
sdio_writeb(func, 1, I2400MS_INTR_CLEAR_ADDR, &ret);
i2400ms_rx(i2400ms);
error_no_irq:
d_fnend(6, dev, "(i2400ms %p) = void\n", i2400ms);
return;
}
/*
* Setup SDIO RX
*
* Hooks up the IRQ handler and then enables IRQs.
*/
int i2400ms_rx_setup(struct i2400ms *i2400ms)
{
int result;
struct sdio_func *func = i2400ms->func;
struct device *dev = &func->dev;
struct i2400m *i2400m = &i2400ms->i2400m;
d_fnstart(5, dev, "(i2400ms %p)\n", i2400ms);
init_waitqueue_head(&i2400ms->bm_wfa_wq);
spin_lock(&i2400m->rx_lock);
i2400ms->bm_wait_result = -EINPROGRESS;
spin_unlock(&i2400m->rx_lock);
sdio_claim_host(func);
result = sdio_claim_irq(func, i2400ms_irq);
if (result < 0) {
dev_err(dev, "Cannot claim IRQ: %d\n", result);
goto error_irq_claim;
}
result = 0;
sdio_writeb(func, 1, I2400MS_INTR_ENABLE_ADDR, &result);
if (result < 0) {
sdio_release_irq(func);
dev_err(dev, "Failed to enable interrupts %d\n", result);
}
error_irq_claim:
sdio_release_host(func);
d_fnend(5, dev, "(i2400ms %p) = %d\n", i2400ms, result);
return result;
}
/*
* Tear down SDIO RX
*
* Disables IRQs in the device and removes the IRQ handler.
*/
void i2400ms_rx_release(struct i2400ms *i2400ms)
{
int result;
struct sdio_func *func = i2400ms->func;
struct device *dev = &func->dev;
struct i2400m *i2400m = &i2400ms->i2400m;
d_fnstart(5, dev, "(i2400ms %p)\n", i2400ms);
spin_lock(&i2400m->rx_lock);
i2400ms->bm_ack_size = -EINTR;
spin_unlock(&i2400m->rx_lock);
wake_up_all(&i2400ms->bm_wfa_wq);
sdio_claim_host(func);
sdio_writeb(func, 0, I2400MS_INTR_ENABLE_ADDR, &result);
sdio_release_irq(func);
sdio_release_host(func);
d_fnend(5, dev, "(i2400ms %p) = %d\n", i2400ms, result);
}

View File

@@ -0,0 +1,153 @@
/*
* Intel Wireless WiMAX Connection 2400m
* SDIO TX transaction backends
*
*
* Copyright (C) 2007-2008 Intel Corporation. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in
* the documentation and/or other materials provided with the
* distribution.
* * Neither the name of Intel Corporation nor the names of its
* contributors may be used to endorse or promote products derived
* from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
*
* Intel Corporation <linux-wimax@intel.com>
* Dirk Brandewie <dirk.j.brandewie@intel.com>
* - Initial implementation
*
*
* Takes the TX messages in the i2400m's driver TX FIFO and sends them
* to the device until there are no more.
*
* If we fail sending the message, we just drop it. There isn't much
* we can do at this point. Most of the traffic is network, which has
* recovery methods for dropped packets.
*
* The SDIO functions are not atomic, so we can't run from the context
* where i2400m->bus_tx_kick() [i2400ms_bus_tx_kick()] is being called
* (some times atomic). Thus, the actual TX work is deferred to a
* workqueue.
*
* ROADMAP
*
* i2400ms_bus_tx_kick()
* i2400ms_tx_submit() [through workqueue]
*
* i2400m_tx_setup()
*
* i2400m_tx_release()
*/
#include <linux/mmc/sdio_func.h>
#include "i2400m-sdio.h"
#define D_SUBMODULE tx
#include "sdio-debug-levels.h"
/*
* Pull TX transations from the TX FIFO and send them to the device
* until there are no more.
*/
static
void i2400ms_tx_submit(struct work_struct *ws)
{
int result;
struct i2400ms *i2400ms = container_of(ws, struct i2400ms, tx_worker);
struct i2400m *i2400m = &i2400ms->i2400m;
struct sdio_func *func = i2400ms->func;
struct device *dev = &func->dev;
struct i2400m_msg_hdr *tx_msg;
size_t tx_msg_size;
d_fnstart(4, dev, "(i2400ms %p, i2400m %p)\n", i2400ms, i2400ms);
while (NULL != (tx_msg = i2400m_tx_msg_get(i2400m, &tx_msg_size))) {
d_printf(2, dev, "TX: submitting %zu bytes\n", tx_msg_size);
d_dump(5, dev, tx_msg, tx_msg_size);
sdio_claim_host(func);
result = sdio_memcpy_toio(func, 0, tx_msg, tx_msg_size);
sdio_release_host(func);
i2400m_tx_msg_sent(i2400m);
if (result < 0) {
dev_err(dev, "TX: cannot submit TX; tx_msg @%zu %zu B:"
" %d\n", (void *) tx_msg - i2400m->tx_buf,
tx_msg_size, result);
}
d_printf(2, dev, "TX: %zub submitted\n", tx_msg_size);
}
d_fnend(4, dev, "(i2400ms %p) = void\n", i2400ms);
}
/*
* The generic driver notifies us that there is data ready for TX
*
* Schedule a run of i2400ms_tx_submit() to handle it.
*/
void i2400ms_bus_tx_kick(struct i2400m *i2400m)
{
struct i2400ms *i2400ms = container_of(i2400m, struct i2400ms, i2400m);
struct device *dev = &i2400ms->func->dev;
d_fnstart(3, dev, "(i2400m %p) = void\n", i2400m);
/* schedule tx work, this is because tx may block, therefore
* it has to run in a thread context.
*/
queue_work(i2400ms->tx_workqueue, &i2400ms->tx_worker);
d_fnend(3, dev, "(i2400m %p) = void\n", i2400m);
}
int i2400ms_tx_setup(struct i2400ms *i2400ms)
{
int result;
struct device *dev = &i2400ms->func->dev;
struct i2400m *i2400m = &i2400ms->i2400m;
d_fnstart(5, dev, "(i2400ms %p)\n", i2400ms);
INIT_WORK(&i2400ms->tx_worker, i2400ms_tx_submit);
snprintf(i2400ms->tx_wq_name, sizeof(i2400ms->tx_wq_name),
"%s-tx", i2400m->wimax_dev.name);
i2400ms->tx_workqueue =
create_singlethread_workqueue(i2400ms->tx_wq_name);
if (NULL == i2400ms->tx_workqueue) {
dev_err(dev, "TX: failed to create workqueue\n");
result = -ENOMEM;
} else
result = 0;
d_fnend(5, dev, "(i2400ms %p) = %d\n", i2400ms, result);
return result;
}
void i2400ms_tx_release(struct i2400ms *i2400ms)
{
destroy_workqueue(i2400ms->tx_workqueue);
}

View File

@@ -0,0 +1,549 @@
/*
* Intel Wireless WiMAX Connection 2400m
* Linux driver model glue for the SDIO device, reset & fw upload
*
*
* Copyright (C) 2007-2008 Intel Corporation <linux-wimax@intel.com>
* Dirk Brandewie <dirk.j.brandewie@intel.com>
* Inaky Perez-Gonzalez <inaky.perez-gonzalez@intel.com>
* Yanir Lubetkin <yanirx.lubetkin@intel.com>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License version
* 2 as published by the Free Software Foundation.
*
* 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; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
* 02110-1301, USA.
*
*
* See i2400m-sdio.h for a general description of this driver.
*
* This file implements driver model glue, and hook ups for the
* generic driver to implement the bus-specific functions (device
* communication setup/tear down, firmware upload and resetting).
*
* ROADMAP
*
* i2400m_probe()
* alloc_netdev()
* i2400ms_netdev_setup()
* i2400ms_init()
* i2400m_netdev_setup()
* i2400ms_enable_function()
* i2400m_setup()
*
* i2400m_remove()
* i2400m_release()
* free_netdev(net_dev)
*
* i2400ms_bus_reset() Called by i2400m->bus_reset
* __i2400ms_reset()
* __i2400ms_send_barker()
*
* i2400ms_bus_dev_start() Called by i2400m_dev_start() [who is
* i2400ms_tx_setup() called by i2400m_setup()]
* i2400ms_rx_setup()
*
* i2400ms_bus_dev_stop() Called by i2400m_dev_stop() [who is
* i2400ms_rx_release() is called by i2400m_release()]
* i2400ms_tx_release()
*
*/
#include <linux/debugfs.h>
#include <linux/mmc/sdio_ids.h>
#include <linux/mmc/sdio.h>
#include <linux/mmc/sdio_func.h>
#include "i2400m-sdio.h"
#include <linux/wimax/i2400m.h>
#define D_SUBMODULE main
#include "sdio-debug-levels.h"
/* IOE WiMAX function timeout in seconds */
static int ioe_timeout = 2;
module_param(ioe_timeout, int, 0);
/* Our firmware file name list */
static const char *i2400ms_bus_fw_names[] = {
#define I2400MS_FW_FILE_NAME "i2400m-fw-sdio-1.3.sbcf"
I2400MS_FW_FILE_NAME,
NULL
};
static const struct i2400m_poke_table i2400ms_pokes[] = {
I2400M_FW_POKE(0x6BE260, 0x00000088),
I2400M_FW_POKE(0x080550, 0x00000005),
I2400M_FW_POKE(0xAE0000, 0x00000000),
I2400M_FW_POKE(0x000000, 0x00000000), /* MUST be 0 terminated or bad
* things will happen */
};
/*
* Enable the SDIO function
*
* Tries to enable the SDIO function; might fail if it is still not
* ready (in some hardware, the SDIO WiMAX function is only enabled
* when we ask it to explicitly doing). Tries until a timeout is
* reached.
*
* The reverse of this is...sdio_disable_function()
*
* Returns: 0 if the SDIO function was enabled, < 0 errno code on
* error (-ENODEV when it was unable to enable the function).
*/
static
int i2400ms_enable_function(struct sdio_func *func)
{
u64 timeout;
int err;
struct device *dev = &func->dev;
d_fnstart(3, dev, "(func %p)\n", func);
/* Setup timeout (FIXME: This needs to read the CIS table to
* get a real timeout) and then wait for the device to signal
* it is ready */
timeout = get_jiffies_64() + ioe_timeout * HZ;
err = -ENODEV;
while (err != 0 && time_before64(get_jiffies_64(), timeout)) {
sdio_claim_host(func);
err = sdio_enable_func(func);
if (0 == err) {
sdio_release_host(func);
d_printf(2, dev, "SDIO function enabled\n");
goto function_enabled;
}
d_printf(2, dev, "SDIO function failed to enable: %d\n", err);
sdio_disable_func(func);
sdio_release_host(func);
msleep(I2400MS_INIT_SLEEP_INTERVAL);
}
/* If timed out, device is not there yet -- get -ENODEV so
* the device driver core will retry later on. */
if (err == -ETIME) {
dev_err(dev, "Can't enable WiMAX function; "
" has the function been enabled?\n");
err = -ENODEV;
}
function_enabled:
d_fnend(3, dev, "(func %p) = %d\n", func, err);
return err;
}
/*
* Setup driver resources needed to communicate with the device
*
* The fw needs some time to settle, and it was just uploaded,
* so give it a break first. I'd prefer to just wait for the device to
* send something, but seems the poking we do to enable SDIO stuff
* interferes with it, so just give it a break before starting...
*/
static
int i2400ms_bus_dev_start(struct i2400m *i2400m)
{
int result;
struct i2400ms *i2400ms = container_of(i2400m, struct i2400ms, i2400m);
struct sdio_func *func = i2400ms->func;
struct device *dev = &func->dev;
d_fnstart(3, dev, "(i2400m %p)\n", i2400m);
msleep(200);
result = i2400ms_tx_setup(i2400ms);
if (result < 0)
goto error_tx_setup;
d_fnend(3, dev, "(i2400m %p) = %d\n", i2400m, result);
return result;
error_tx_setup:
i2400ms_tx_release(i2400ms);
d_fnend(3, dev, "(i2400m %p) = void\n", i2400m);
return result;
}
static
void i2400ms_bus_dev_stop(struct i2400m *i2400m)
{
struct i2400ms *i2400ms = container_of(i2400m, struct i2400ms, i2400m);
struct sdio_func *func = i2400ms->func;
struct device *dev = &func->dev;
d_fnstart(3, dev, "(i2400m %p)\n", i2400m);
i2400ms_tx_release(i2400ms);
d_fnend(3, dev, "(i2400m %p) = void\n", i2400m);
}
/*
* Sends a barker buffer to the device
*
* This helper will allocate a kmalloced buffer and use it to transmit
* (then free it). Reason for this is that the SDIO host controller
* expects alignment (unknown exactly which) which the stack won't
* really provide and certain arches/host-controller combinations
* cannot use stack/vmalloc/text areas for DMA transfers.
*/
static
int __i2400ms_send_barker(struct i2400ms *i2400ms,
const __le32 *barker, size_t barker_size)
{
int ret;
struct sdio_func *func = i2400ms->func;
struct device *dev = &func->dev;
void *buffer;
ret = -ENOMEM;
buffer = kmalloc(I2400MS_BLK_SIZE, GFP_KERNEL);
if (buffer == NULL)
goto error_kzalloc;
memcpy(buffer, barker, barker_size);
sdio_claim_host(func);
ret = sdio_memcpy_toio(func, 0, buffer, I2400MS_BLK_SIZE);
sdio_release_host(func);
if (ret < 0)
d_printf(0, dev, "E: barker error: %d\n", ret);
kfree(buffer);
error_kzalloc:
return ret;
}
/*
* Reset a device at different levels (warm, cold or bus)
*
* @i2400ms: device descriptor
* @reset_type: soft, warm or bus reset (I2400M_RT_WARM/SOFT/BUS)
*
* FIXME: not tested -- need to confirm expected effects
*
* Warm and cold resets get an SDIO reset if they fail (unimplemented)
*
* Warm reset:
*
* The device will be fully reset internally, but won't be
* disconnected from the USB bus (so no reenumeration will
* happen). Firmware upload will be neccessary.
*
* The device will send a reboot barker in the notification endpoint
* that will trigger the driver to reinitialize the state
* automatically from notif.c:i2400m_notification_grok() into
* i2400m_dev_bootstrap_delayed().
*
* Cold and bus (USB) reset:
*
* The device will be fully reset internally, disconnected from the
* USB bus an a reenumeration will happen. Firmware upload will be
* neccessary. Thus, we don't do any locking or struct
* reinitialization, as we are going to be fully disconnected and
* reenumerated.
*
* Note we need to return -ENODEV if a warm reset was requested and we
* had to resort to a bus reset. See i2400m_op_reset(), wimax_reset()
* and wimax_dev->op_reset.
*
* WARNING: no driver state saved/fixed
*/
static
int i2400ms_bus_reset(struct i2400m *i2400m, enum i2400m_reset_type rt)
{
int result = 0;
struct i2400ms *i2400ms =
container_of(i2400m, struct i2400ms, i2400m);
struct device *dev = i2400m_dev(i2400m);
static const __le32 i2400m_WARM_BOOT_BARKER[4] = {
cpu_to_le32(I2400M_WARM_RESET_BARKER),
cpu_to_le32(I2400M_WARM_RESET_BARKER),
cpu_to_le32(I2400M_WARM_RESET_BARKER),
cpu_to_le32(I2400M_WARM_RESET_BARKER),
};
static const __le32 i2400m_COLD_BOOT_BARKER[4] = {
cpu_to_le32(I2400M_COLD_RESET_BARKER),
cpu_to_le32(I2400M_COLD_RESET_BARKER),
cpu_to_le32(I2400M_COLD_RESET_BARKER),
cpu_to_le32(I2400M_COLD_RESET_BARKER),
};
if (rt == I2400M_RT_WARM)
result = __i2400ms_send_barker(i2400ms, i2400m_WARM_BOOT_BARKER,
sizeof(i2400m_WARM_BOOT_BARKER));
else if (rt == I2400M_RT_COLD)
result = __i2400ms_send_barker(i2400ms, i2400m_COLD_BOOT_BARKER,
sizeof(i2400m_COLD_BOOT_BARKER));
else if (rt == I2400M_RT_BUS) {
do_bus_reset:
/* call netif_tx_disable() before sending IOE disable,
* so that all the tx from network layer are stopped
* while IOE is being reset. Make sure it is called
* only after register_netdev() was issued.
*/
if (i2400m->wimax_dev.net_dev->reg_state == NETREG_REGISTERED)
netif_tx_disable(i2400m->wimax_dev.net_dev);
i2400ms_rx_release(i2400ms);
sdio_claim_host(i2400ms->func);
sdio_disable_func(i2400ms->func);
sdio_release_host(i2400ms->func);
/* Wait for the device to settle */
msleep(40);
result = i2400ms_enable_function(i2400ms->func);
if (result >= 0)
i2400ms_rx_setup(i2400ms);
} else
BUG();
if (result < 0 && rt != I2400M_RT_BUS) {
dev_err(dev, "%s reset failed (%d); trying SDIO reset\n",
rt == I2400M_RT_WARM ? "warm" : "cold", result);
rt = I2400M_RT_BUS;
goto do_bus_reset;
}
return result;
}
static
void i2400ms_netdev_setup(struct net_device *net_dev)
{
struct i2400m *i2400m = net_dev_to_i2400m(net_dev);
struct i2400ms *i2400ms = container_of(i2400m, struct i2400ms, i2400m);
i2400ms_init(i2400ms);
i2400m_netdev_setup(net_dev);
}
/*
* Debug levels control; see debug.h
*/
struct d_level D_LEVEL[] = {
D_SUBMODULE_DEFINE(main),
D_SUBMODULE_DEFINE(tx),
D_SUBMODULE_DEFINE(rx),
D_SUBMODULE_DEFINE(fw),
};
size_t D_LEVEL_SIZE = ARRAY_SIZE(D_LEVEL);
#define __debugfs_register(prefix, name, parent) \
do { \
result = d_level_register_debugfs(prefix, name, parent); \
if (result < 0) \
goto error; \
} while (0)
static
int i2400ms_debugfs_add(struct i2400ms *i2400ms)
{
int result;
struct dentry *dentry = i2400ms->i2400m.wimax_dev.debugfs_dentry;
dentry = debugfs_create_dir("i2400m-usb", dentry);
result = PTR_ERR(dentry);
if (IS_ERR(dentry)) {
if (result == -ENODEV)
result = 0; /* No debugfs support */
goto error;
}
i2400ms->debugfs_dentry = dentry;
__debugfs_register("dl_", main, dentry);
__debugfs_register("dl_", tx, dentry);
__debugfs_register("dl_", rx, dentry);
__debugfs_register("dl_", fw, dentry);
return 0;
error:
debugfs_remove_recursive(i2400ms->debugfs_dentry);
return result;
}
static struct device_type i2400ms_type = {
.name = "wimax",
};
/*
* Probe a i2400m interface and register it
*
* @func: SDIO function
* @id: SDIO device ID
* @returns: 0 if ok, < 0 errno code on error.
*
* Alloc a net device, initialize the bus-specific details and then
* calls the bus-generic initialization routine. That will register
* the wimax and netdev devices, upload the firmware [using
* _bus_bm_*()], call _bus_dev_start() to finalize the setup of the
* communication with the device and then will start to talk to it to
* finnish setting it up.
*
* Initialization is tricky; some instances of the hw are packed with
* others in a way that requires a third driver that enables the WiMAX
* function. In those cases, we can't enable the SDIO function and
* we'll return with -ENODEV. When the driver that enables the WiMAX
* function does its thing, it has to do a bus_rescan_devices() on the
* SDIO bus so this driver is called again to enumerate the WiMAX
* function.
*/
static
int i2400ms_probe(struct sdio_func *func,
const struct sdio_device_id *id)
{
int result;
struct net_device *net_dev;
struct device *dev = &func->dev;
struct i2400m *i2400m;
struct i2400ms *i2400ms;
/* Allocate instance [calls i2400m_netdev_setup() on it]. */
result = -ENOMEM;
net_dev = alloc_netdev(sizeof(*i2400ms), "wmx%d",
i2400ms_netdev_setup);
if (net_dev == NULL) {
dev_err(dev, "no memory for network device instance\n");
goto error_alloc_netdev;
}
SET_NETDEV_DEV(net_dev, dev);
SET_NETDEV_DEVTYPE(net_dev, &i2400ms_type);
i2400m = net_dev_to_i2400m(net_dev);
i2400ms = container_of(i2400m, struct i2400ms, i2400m);
i2400m->wimax_dev.net_dev = net_dev;
i2400ms->func = func;
sdio_set_drvdata(func, i2400ms);
i2400m->bus_tx_block_size = I2400MS_BLK_SIZE;
i2400m->bus_pl_size_max = I2400MS_PL_SIZE_MAX;
i2400m->bus_dev_start = i2400ms_bus_dev_start;
i2400m->bus_dev_stop = i2400ms_bus_dev_stop;
i2400m->bus_tx_kick = i2400ms_bus_tx_kick;
i2400m->bus_reset = i2400ms_bus_reset;
/* The iwmc3200-wimax sometimes requires the driver to try
* hard when we paint it into a corner. */
i2400m->bus_bm_retries = I3200_BOOT_RETRIES;
i2400m->bus_bm_cmd_send = i2400ms_bus_bm_cmd_send;
i2400m->bus_bm_wait_for_ack = i2400ms_bus_bm_wait_for_ack;
i2400m->bus_fw_names = i2400ms_bus_fw_names;
i2400m->bus_bm_mac_addr_impaired = 1;
i2400m->bus_bm_pokes_table = &i2400ms_pokes[0];
sdio_claim_host(func);
result = sdio_set_block_size(func, I2400MS_BLK_SIZE);
sdio_release_host(func);
if (result < 0) {
dev_err(dev, "Failed to set block size: %d\n", result);
goto error_set_blk_size;
}
result = i2400ms_enable_function(i2400ms->func);
if (result < 0) {
dev_err(dev, "Cannot enable SDIO function: %d\n", result);
goto error_func_enable;
}
result = i2400ms_rx_setup(i2400ms);
if (result < 0)
goto error_rx_setup;
result = i2400m_setup(i2400m, I2400M_BRI_NO_REBOOT);
if (result < 0) {
dev_err(dev, "cannot setup device: %d\n", result);
goto error_setup;
}
result = i2400ms_debugfs_add(i2400ms);
if (result < 0) {
dev_err(dev, "cannot create SDIO debugfs: %d\n",
result);
goto error_debugfs_add;
}
return 0;
error_debugfs_add:
i2400m_release(i2400m);
error_setup:
i2400ms_rx_release(i2400ms);
error_rx_setup:
sdio_claim_host(func);
sdio_disable_func(func);
sdio_release_host(func);
error_func_enable:
error_set_blk_size:
sdio_set_drvdata(func, NULL);
free_netdev(net_dev);
error_alloc_netdev:
return result;
}
static
void i2400ms_remove(struct sdio_func *func)
{
struct device *dev = &func->dev;
struct i2400ms *i2400ms = sdio_get_drvdata(func);
struct i2400m *i2400m = &i2400ms->i2400m;
struct net_device *net_dev = i2400m->wimax_dev.net_dev;
d_fnstart(3, dev, "SDIO func %p\n", func);
debugfs_remove_recursive(i2400ms->debugfs_dentry);
i2400ms_rx_release(i2400ms);
i2400m_release(i2400m);
sdio_set_drvdata(func, NULL);
sdio_claim_host(func);
sdio_disable_func(func);
sdio_release_host(func);
free_netdev(net_dev);
d_fnend(3, dev, "SDIO func %p\n", func);
}
static
const struct sdio_device_id i2400ms_sdio_ids[] = {
/* Intel: i2400m WiMAX (iwmc3200) over SDIO */
{ SDIO_DEVICE(SDIO_VENDOR_ID_INTEL,
SDIO_DEVICE_ID_INTEL_IWMC3200WIMAX) },
{ /* end: all zeroes */ },
};
MODULE_DEVICE_TABLE(sdio, i2400ms_sdio_ids);
static
struct sdio_driver i2400m_sdio_driver = {
.name = KBUILD_MODNAME,
.probe = i2400ms_probe,
.remove = i2400ms_remove,
.id_table = i2400ms_sdio_ids,
};
static
int __init i2400ms_driver_init(void)
{
return sdio_register_driver(&i2400m_sdio_driver);
}
module_init(i2400ms_driver_init);
static
void __exit i2400ms_driver_exit(void)
{
flush_scheduled_work(); /* for the stuff we schedule */
sdio_unregister_driver(&i2400m_sdio_driver);
}
module_exit(i2400ms_driver_exit);
MODULE_AUTHOR("Intel Corporation <linux-wimax@intel.com>");
MODULE_DESCRIPTION("Intel 2400M WiMAX networking for SDIO");
MODULE_LICENSE("GPL");
MODULE_FIRMWARE(I2400MS_FW_FILE_NAME);

View File

@@ -0,0 +1,80 @@
/*
* Intel Wireless WiMAX Connection 2400m
* Sysfs interfaces to show driver and device information
*
*
* Copyright (C) 2007 Intel Corporation <linux-wimax@intel.com>
* Inaky Perez-Gonzalez <inaky.perez-gonzalez@intel.com>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License version
* 2 as published by the Free Software Foundation.
*
* 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; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
* 02110-1301, USA.
*/
#include <linux/netdevice.h>
#include <linux/etherdevice.h>
#include <linux/spinlock.h>
#include <linux/device.h>
#include "i2400m.h"
#define D_SUBMODULE sysfs
#include "debug-levels.h"
/*
* Set the idle timeout (msecs)
*
* FIXME: eventually this should be a common WiMAX stack method, but
* would like to wait to see how other devices manage it.
*/
static
ssize_t i2400m_idle_timeout_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t size)
{
ssize_t result;
struct i2400m *i2400m = net_dev_to_i2400m(to_net_dev(dev));
unsigned val;
result = -EINVAL;
if (sscanf(buf, "%u\n", &val) != 1)
goto error_no_unsigned;
if (val != 0 && (val < 100 || val > 300000 || val % 100 != 0)) {
dev_err(dev, "idle_timeout: %u: invalid msecs specification; "
"valid values are 0, 100-300000 in 100 increments\n",
val);
goto error_bad_value;
}
result = i2400m_set_idle_timeout(i2400m, val);
if (result >= 0)
result = size;
error_no_unsigned:
error_bad_value:
return result;
}
static
DEVICE_ATTR(i2400m_idle_timeout, S_IWUSR,
NULL, i2400m_idle_timeout_store);
static
struct attribute *i2400m_dev_attrs[] = {
&dev_attr_i2400m_idle_timeout.attr,
NULL,
};
struct attribute_group i2400m_dev_attr_group = {
.name = NULL, /* we want them in the same directory */
.attrs = i2400m_dev_attrs,
};

View File

@@ -0,0 +1,880 @@
/*
* Intel Wireless WiMAX Connection 2400m
* Generic (non-bus specific) TX handling
*
*
* Copyright (C) 2007-2008 Intel Corporation. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in
* the documentation and/or other materials provided with the
* distribution.
* * Neither the name of Intel Corporation nor the names of its
* contributors may be used to endorse or promote products derived
* from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
*
* Intel Corporation <linux-wimax@intel.com>
* Yanir Lubetkin <yanirx.lubetkin@intel.com>
* - Initial implementation
*
* Intel Corporation <linux-wimax@intel.com>
* Inaky Perez-Gonzalez <inaky.perez-gonzalez@intel.com>
* - Rewritten to use a single FIFO to lower the memory allocation
* pressure and optimize cache hits when copying to the queue, as
* well as splitting out bus-specific code.
*
*
* Implements data transmission to the device; this is done through a
* software FIFO, as data/control frames can be coalesced (while the
* device is reading the previous tx transaction, others accumulate).
*
* A FIFO is used because at the end it is resource-cheaper that trying
* to implement scatter/gather over USB. As well, most traffic is going
* to be download (vs upload).
*
* The format for sending/receiving data to/from the i2400m is
* described in detail in rx.c:PROTOCOL FORMAT. In here we implement
* the transmission of that. This is split between a bus-independent
* part that just prepares everything and a bus-specific part that
* does the actual transmission over the bus to the device (in the
* bus-specific driver).
*
*
* The general format of a device-host transaction is MSG-HDR, PLD1,
* PLD2...PLDN, PL1, PL2,...PLN, PADDING.
*
* Because we need the send payload descriptors and then payloads and
* because it is kind of expensive to do scatterlists in USB (one URB
* per node), it becomes cheaper to append all the data to a FIFO
* (copying to a FIFO potentially in cache is cheaper).
*
* Then the bus-specific code takes the parts of that FIFO that are
* written and passes them to the device.
*
* So the concepts to keep in mind there are:
*
* We use a FIFO to queue the data in a linear buffer. We first append
* a MSG-HDR, space for I2400M_TX_PLD_MAX payload descriptors and then
* go appending payloads until we run out of space or of payload
* descriptors. Then we append padding to make the whole transaction a
* multiple of i2400m->bus_tx_block_size (as defined by the bus layer).
*
* - A TX message: a combination of a message header, payload
* descriptors and payloads.
*
* Open: it is marked as active (i2400m->tx_msg is valid) and we
* can keep adding payloads to it.
*
* Closed: we are not appending more payloads to this TX message
* (exahusted space in the queue, too many payloads or
* whichever). We have appended padding so the whole message
* length is aligned to i2400m->bus_tx_block_size (as set by the
* bus/transport layer).
*
* - Most of the time we keep a TX message open to which we append
* payloads.
*
* - If we are going to append and there is no more space (we are at
* the end of the FIFO), we close the message, mark the rest of the
* FIFO space unusable (skip_tail), create a new message at the
* beginning of the FIFO (if there is space) and append the message
* there.
*
* This is because we need to give linear TX messages to the bus
* engine. So we don't write a message to the remaining FIFO space
* until the tail and continue at the head of it.
*
* - We overload one of the fields in the message header to use it as
* 'size' of the TX message, so we can iterate over them. It also
* contains a flag that indicates if we have to skip it or not.
* When we send the buffer, we update that to its real on-the-wire
* value.
*
* - The MSG-HDR PLD1...PLD2 stuff has to be a size multiple of 16.
*
* It follows that if MSG-HDR says we have N messages, the whole
* header + descriptors is 16 + 4*N; for those to be a multiple of
* 16, it follows that N can be 4, 8, 12, ... (32, 48, 64, 80...
* bytes).
*
* So if we have only 1 payload, we have to submit a header that in
* all truth has space for 4.
*
* The implication is that we reserve space for 12 (64 bytes); but
* if we fill up only (eg) 2, our header becomes 32 bytes only. So
* the TX engine has to shift those 32 bytes of msg header and 2
* payloads and padding so that right after it the payloads start
* and the TX engine has to know about that.
*
* It is cheaper to move the header up than the whole payloads down.
*
* We do this in i2400m_tx_close(). See 'i2400m_msg_hdr->offset'.
*
* - Each payload has to be size-padded to 16 bytes; before appending
* it, we just do it.
*
* - The whole message has to be padded to i2400m->bus_tx_block_size;
* we do this at close time. Thus, when reserving space for the
* payload, we always make sure there is also free space for this
* padding that sooner or later will happen.
*
* When we append a message, we tell the bus specific code to kick in
* TXs. It will TX (in parallel) until the buffer is exhausted--hence
* the lockin we do. The TX code will only send a TX message at the
* time (which remember, might contain more than one payload). Of
* course, when the bus-specific driver attempts to TX a message that
* is still open, it gets closed first.
*
* Gee, this is messy; well a picture. In the example below we have a
* partially full FIFO, with a closed message ready to be delivered
* (with a moved message header to make sure it is size-aligned to
* 16), TAIL room that was unusable (and thus is marked with a message
* header that says 'skip this') and at the head of the buffer, an
* imcomplete message with a couple of payloads.
*
* N ___________________________________________________
* | |
* | TAIL room |
* | |
* | msg_hdr to skip (size |= 0x80000) |
* |---------------------------------------------------|-------
* | | /|\
* | | |
* | TX message padding | |
* | | |
* | | |
* |- - - - - - - - - - - - - - - - - - - - - - - - - -| |
* | | |
* | payload 1 | |
* | | N * tx_block_size
* | | |
* |- - - - - - - - - - - - - - - - - - - - - - - - - -| |
* | | |
* | payload 1 | |
* | | |
* | | |
* |- - - - - - - - - - - - - - - - - - - - - - - - - -|- -|- - - -
* | padding 3 /|\ | | /|\
* | padding 2 | | | |
* | pld 1 32 bytes (2 * 16) | | |
* | pld 0 | | | |
* | moved msg_hdr \|/ | \|/ |
* |- - - - - - - - - - - - - - - - - - - - - - - - - -|- - - |
* | | _PLD_SIZE
* | unused | |
* | | |
* |- - - - - - - - - - - - - - - - - - - - - - - - - -| |
* | msg_hdr (size X) [this message is closed] | \|/
* |===================================================|========== <=== OUT
* | |
* | |
* | |
* | Free rooom |
* | |
* | |
* | |
* | |
* | |
* | |
* | |
* | |
* | |
* |===================================================|========== <=== IN
* | |
* | |
* | |
* | |
* | payload 1 |
* | |
* | |
* |- - - - - - - - - - - - - - - - - - - - - - - - - -|
* | |
* | payload 0 |
* | |
* | |
* |- - - - - - - - - - - - - - - - - - - - - - - - - -|
* | pld 11 /|\ |
* | ... | |
* | pld 1 64 bytes (2 * 16) |
* | pld 0 | |
* | msg_hdr (size X) \|/ [message is open] |
* 0 ---------------------------------------------------
*
*
* ROADMAP
*
* i2400m_tx_setup() Called by i2400m_setup
* i2400m_tx_release() Called by i2400m_release()
*
* i2400m_tx() Called to send data or control frames
* i2400m_tx_fifo_push() Allocates append-space in the FIFO
* i2400m_tx_new() Opens a new message in the FIFO
* i2400m_tx_fits() Checks if a new payload fits in the message
* i2400m_tx_close() Closes an open message in the FIFO
* i2400m_tx_skip_tail() Marks unusable FIFO tail space
* i2400m->bus_tx_kick()
*
* Now i2400m->bus_tx_kick() is the the bus-specific driver backend
* implementation; that would do:
*
* i2400m->bus_tx_kick()
* i2400m_tx_msg_get() Gets first message ready to go
* ...sends it...
* i2400m_tx_msg_sent() Ack the message is sent; repeat from
* _tx_msg_get() until it returns NULL
* (FIFO empty).
*/
#include <linux/netdevice.h>
#include "i2400m.h"
#define D_SUBMODULE tx
#include "debug-levels.h"
enum {
/**
* TX Buffer size
*
* Doc says maximum transaction is 16KiB. If we had 16KiB en
* route and 16KiB being queued, it boils down to needing
* 32KiB.
*/
I2400M_TX_BUF_SIZE = 32768,
/**
* Message header and payload descriptors have to be 16
* aligned (16 + 4 * N = 16 * M). If we take that average sent
* packets are MTU size (~1400-~1500) it follows that we could
* fit at most 10-11 payloads in one transaction. To meet the
* alignment requirement, that means we need to leave space
* for 12 (64 bytes). To simplify, we leave space for that. If
* at the end there are less, we pad up to the nearest
* multiple of 16.
*/
I2400M_TX_PLD_MAX = 12,
I2400M_TX_PLD_SIZE = sizeof(struct i2400m_msg_hdr)
+ I2400M_TX_PLD_MAX * sizeof(struct i2400m_pld),
I2400M_TX_SKIP = 0x80000000,
};
#define TAIL_FULL ((void *)~(unsigned long)NULL)
/*
* Calculate how much tail room is available
*
* Note the trick here. This path is ONLY caleed for Case A (see
* i2400m_tx_fifo_push() below), where we have:
*
* Case A
* N ___________
* | tail room |
* | |
* |<- IN ->|
* | |
* | data |
* | |
* |<- OUT ->|
* | |
* | head room |
* 0 -----------
*
* When calculating the tail_room, tx_in might get to be zero if
* i2400m->tx_in is right at the end of the buffer (really full
* buffer) if there is no head room. In this case, tail_room would be
* I2400M_TX_BUF_SIZE, although it is actually zero. Hence the final
* mod (%) operation. However, when doing this kind of optimization,
* i2400m->tx_in being zero would fail, so we treat is an a special
* case.
*/
static inline
size_t __i2400m_tx_tail_room(struct i2400m *i2400m)
{
size_t tail_room;
size_t tx_in;
if (unlikely(i2400m->tx_in) == 0)
return I2400M_TX_BUF_SIZE;
tx_in = i2400m->tx_in % I2400M_TX_BUF_SIZE;
tail_room = I2400M_TX_BUF_SIZE - tx_in;
tail_room %= I2400M_TX_BUF_SIZE;
return tail_room;
}
/*
* Allocate @size bytes in the TX fifo, return a pointer to it
*
* @i2400m: device descriptor
* @size: size of the buffer we need to allocate
* @padding: ensure that there is at least this many bytes of free
* contiguous space in the fifo. This is needed because later on
* we might need to add padding.
*
* Returns:
*
* Pointer to the allocated space. NULL if there is no
* space. TAIL_FULL if there is no space at the tail but there is at
* the head (Case B below).
*
* These are the two basic cases we need to keep an eye for -- it is
* much better explained in linux/kernel/kfifo.c, but this code
* basically does the same. No rocket science here.
*
* Case A Case B
* N ___________ ___________
* | tail room | | data |
* | | | |
* |<- IN ->| |<- OUT ->|
* | | | |
* | data | | room |
* | | | |
* |<- OUT ->| |<- IN ->|
* | | | |
* | head room | | data |
* 0 ----------- -----------
*
* We allocate only *contiguous* space.
*
* We can allocate only from 'room'. In Case B, it is simple; in case
* A, we only try from the tail room; if it is not enough, we just
* fail and return TAIL_FULL and let the caller figure out if we wants to
* skip the tail room and try to allocate from the head.
*
* Note:
*
* Assumes i2400m->tx_lock is taken, and we use that as a barrier
*
* The indexes keep increasing and we reset them to zero when we
* pop data off the queue
*/
static
void *i2400m_tx_fifo_push(struct i2400m *i2400m, size_t size, size_t padding)
{
struct device *dev = i2400m_dev(i2400m);
size_t room, tail_room, needed_size;
void *ptr;
needed_size = size + padding;
room = I2400M_TX_BUF_SIZE - (i2400m->tx_in - i2400m->tx_out);
if (room < needed_size) { /* this takes care of Case B */
d_printf(2, dev, "fifo push %zu/%zu: no space\n",
size, padding);
return NULL;
}
/* Is there space at the tail? */
tail_room = __i2400m_tx_tail_room(i2400m);
if (tail_room < needed_size) {
if (i2400m->tx_out % I2400M_TX_BUF_SIZE
< i2400m->tx_in % I2400M_TX_BUF_SIZE) {
d_printf(2, dev, "fifo push %zu/%zu: tail full\n",
size, padding);
return TAIL_FULL; /* There might be head space */
} else {
d_printf(2, dev, "fifo push %zu/%zu: no head space\n",
size, padding);
return NULL; /* There is no space */
}
}
ptr = i2400m->tx_buf + i2400m->tx_in % I2400M_TX_BUF_SIZE;
d_printf(2, dev, "fifo push %zu/%zu: at @%zu\n", size, padding,
i2400m->tx_in % I2400M_TX_BUF_SIZE);
i2400m->tx_in += size;
return ptr;
}
/*
* Mark the tail of the FIFO buffer as 'to-skip'
*
* We should never hit the BUG_ON() because all the sizes we push to
* the FIFO are padded to be a multiple of 16 -- the size of *msg
* (I2400M_PL_PAD for the payloads, I2400M_TX_PLD_SIZE for the
* header).
*
* Tail room can get to be zero if a message was opened when there was
* space only for a header. _tx_close() will mark it as to-skip (as it
* will have no payloads) and there will be no more space to flush, so
* nothing has to be done here. This is probably cheaper than ensuring
* in _tx_new() that there is some space for payloads...as we could
* always possibly hit the same problem if the payload wouldn't fit.
*
* Note:
*
* Assumes i2400m->tx_lock is taken, and we use that as a barrier
*
* This path is only taken for Case A FIFO situations [see
* i2400m_tx_fifo_push()]
*/
static
void i2400m_tx_skip_tail(struct i2400m *i2400m)
{
struct device *dev = i2400m_dev(i2400m);
size_t tx_in = i2400m->tx_in % I2400M_TX_BUF_SIZE;
size_t tail_room = __i2400m_tx_tail_room(i2400m);
struct i2400m_msg_hdr *msg = i2400m->tx_buf + tx_in;
if (unlikely(tail_room == 0))
return;
BUG_ON(tail_room < sizeof(*msg));
msg->size = tail_room | I2400M_TX_SKIP;
d_printf(2, dev, "skip tail: skipping %zu bytes @%zu\n",
tail_room, tx_in);
i2400m->tx_in += tail_room;
}
/*
* Check if a skb will fit in the TX queue's current active TX
* message (if there are still descriptors left unused).
*
* Returns:
* 0 if the message won't fit, 1 if it will.
*
* Note:
*
* Assumes a TX message is active (i2400m->tx_msg).
*
* Assumes i2400m->tx_lock is taken, and we use that as a barrier
*/
static
unsigned i2400m_tx_fits(struct i2400m *i2400m)
{
struct i2400m_msg_hdr *msg_hdr = i2400m->tx_msg;
return le16_to_cpu(msg_hdr->num_pls) < I2400M_TX_PLD_MAX;
}
/*
* Start a new TX message header in the queue.
*
* Reserve memory from the base FIFO engine and then just initialize
* the message header.
*
* We allocate the biggest TX message header we might need (one that'd
* fit I2400M_TX_PLD_MAX payloads) -- when it is closed it will be
* 'ironed it out' and the unneeded parts removed.
*
* NOTE:
*
* Assumes that the previous message is CLOSED (eg: either
* there was none or 'i2400m_tx_close()' was called on it).
*
* Assumes i2400m->tx_lock is taken, and we use that as a barrier
*/
static
void i2400m_tx_new(struct i2400m *i2400m)
{
struct device *dev = i2400m_dev(i2400m);
struct i2400m_msg_hdr *tx_msg;
BUG_ON(i2400m->tx_msg != NULL);
try_head:
tx_msg = i2400m_tx_fifo_push(i2400m, I2400M_TX_PLD_SIZE, 0);
if (tx_msg == NULL)
goto out;
else if (tx_msg == TAIL_FULL) {
i2400m_tx_skip_tail(i2400m);
d_printf(2, dev, "new TX message: tail full, trying head\n");
goto try_head;
}
memset(tx_msg, 0, I2400M_TX_PLD_SIZE);
tx_msg->size = I2400M_TX_PLD_SIZE;
out:
i2400m->tx_msg = tx_msg;
d_printf(2, dev, "new TX message: %p @%zu\n",
tx_msg, (void *) tx_msg - i2400m->tx_buf);
}
/*
* Finalize the current TX message header
*
* Sets the message header to be at the proper location depending on
* how many descriptors we have (check documentation at the file's
* header for more info on that).
*
* Appends padding bytes to make sure the whole TX message (counting
* from the 'relocated' message header) is aligned to
* tx_block_size. We assume the _append() code has left enough space
* in the FIFO for that. If there are no payloads, just pass, as it
* won't be transferred.
*
* The amount of padding bytes depends on how many payloads are in the
* TX message, as the "msg header and payload descriptors" will be
* shifted up in the buffer.
*/
static
void i2400m_tx_close(struct i2400m *i2400m)
{
struct device *dev = i2400m_dev(i2400m);
struct i2400m_msg_hdr *tx_msg = i2400m->tx_msg;
struct i2400m_msg_hdr *tx_msg_moved;
size_t aligned_size, padding, hdr_size;
void *pad_buf;
unsigned num_pls;
if (tx_msg->size & I2400M_TX_SKIP) /* a skipper? nothing to do */
goto out;
num_pls = le16_to_cpu(tx_msg->num_pls);
/* We can get this situation when a new message was started
* and there was no space to add payloads before hitting the
tail (and taking padding into consideration). */
if (num_pls == 0) {
tx_msg->size |= I2400M_TX_SKIP;
goto out;
}
/* Relocate the message header
*
* Find the current header size, align it to 16 and if we need
* to move it so the tail is next to the payloads, move it and
* set the offset.
*
* If it moved, this header is good only for transmission; the
* original one (it is kept if we moved) is still used to
* figure out where the next TX message starts (and where the
* offset to the moved header is).
*/
hdr_size = sizeof(*tx_msg)
+ le16_to_cpu(tx_msg->num_pls) * sizeof(tx_msg->pld[0]);
hdr_size = ALIGN(hdr_size, I2400M_PL_ALIGN);
tx_msg->offset = I2400M_TX_PLD_SIZE - hdr_size;
tx_msg_moved = (void *) tx_msg + tx_msg->offset;
memmove(tx_msg_moved, tx_msg, hdr_size);
tx_msg_moved->size -= tx_msg->offset;
/*
* Now figure out how much we have to add to the (moved!)
* message so the size is a multiple of i2400m->bus_tx_block_size.
*/
aligned_size = ALIGN(tx_msg_moved->size, i2400m->bus_tx_block_size);
padding = aligned_size - tx_msg_moved->size;
if (padding > 0) {
pad_buf = i2400m_tx_fifo_push(i2400m, padding, 0);
if (unlikely(WARN_ON(pad_buf == NULL
|| pad_buf == TAIL_FULL))) {
/* This should not happen -- append should verify
* there is always space left at least to append
* tx_block_size */
dev_err(dev,
"SW BUG! Possible data leakage from memory the "
"device should not read for padding - "
"size %lu aligned_size %zu tx_buf %p in "
"%zu out %zu\n",
(unsigned long) tx_msg_moved->size,
aligned_size, i2400m->tx_buf, i2400m->tx_in,
i2400m->tx_out);
} else
memset(pad_buf, 0xad, padding);
}
tx_msg_moved->padding = cpu_to_le16(padding);
tx_msg_moved->size += padding;
if (tx_msg != tx_msg_moved)
tx_msg->size += padding;
out:
i2400m->tx_msg = NULL;
}
/**
* i2400m_tx - send the data in a buffer to the device
*
* @buf: pointer to the buffer to transmit
*
* @buf_len: buffer size
*
* @pl_type: type of the payload we are sending.
*
* Returns:
* 0 if ok, < 0 errno code on error (-ENOSPC, if there is no more
* room for the message in the queue).
*
* Appends the buffer to the TX FIFO and notifies the bus-specific
* part of the driver that there is new data ready to transmit.
* Once this function returns, the buffer has been copied, so it can
* be reused.
*
* The steps followed to append are explained in detail in the file
* header.
*
* Whenever we write to a message, we increase msg->size, so it
* reflects exactly how big the message is. This is needed so that if
* we concatenate two messages before they can be sent, the code that
* sends the messages can find the boundaries (and it will replace the
* size with the real barker before sending).
*
* Note:
*
* Cold and warm reset payloads need to be sent as a single
* payload, so we handle that.
*/
int i2400m_tx(struct i2400m *i2400m, const void *buf, size_t buf_len,
enum i2400m_pt pl_type)
{
int result = -ENOSPC;
struct device *dev = i2400m_dev(i2400m);
unsigned long flags;
size_t padded_len;
void *ptr;
unsigned is_singleton = pl_type == I2400M_PT_RESET_WARM
|| pl_type == I2400M_PT_RESET_COLD;
d_fnstart(3, dev, "(i2400m %p skb %p [%zu bytes] pt %u)\n",
i2400m, buf, buf_len, pl_type);
padded_len = ALIGN(buf_len, I2400M_PL_ALIGN);
d_printf(5, dev, "padded_len %zd buf_len %zd\n", padded_len, buf_len);
/* If there is no current TX message, create one; if the
* current one is out of payload slots or we have a singleton,
* close it and start a new one */
spin_lock_irqsave(&i2400m->tx_lock, flags);
try_new:
if (unlikely(i2400m->tx_msg == NULL))
i2400m_tx_new(i2400m);
else if (unlikely(!i2400m_tx_fits(i2400m)
|| (is_singleton && i2400m->tx_msg->num_pls != 0))) {
d_printf(2, dev, "closing TX message (fits %u singleton "
"%u num_pls %u)\n", i2400m_tx_fits(i2400m),
is_singleton, i2400m->tx_msg->num_pls);
i2400m_tx_close(i2400m);
i2400m_tx_new(i2400m);
}
if (i2400m->tx_msg == NULL)
goto error_tx_new;
if (i2400m->tx_msg->size + padded_len > I2400M_TX_BUF_SIZE / 2) {
d_printf(2, dev, "TX: message too big, going new\n");
i2400m_tx_close(i2400m);
i2400m_tx_new(i2400m);
}
if (i2400m->tx_msg == NULL)
goto error_tx_new;
/* So we have a current message header; now append space for
* the message -- if there is not enough, try the head */
ptr = i2400m_tx_fifo_push(i2400m, padded_len,
i2400m->bus_tx_block_size);
if (ptr == TAIL_FULL) { /* Tail is full, try head */
d_printf(2, dev, "pl append: tail full\n");
i2400m_tx_close(i2400m);
i2400m_tx_skip_tail(i2400m);
goto try_new;
} else if (ptr == NULL) { /* All full */
result = -ENOSPC;
d_printf(2, dev, "pl append: all full\n");
} else { /* Got space, copy it, set padding */
struct i2400m_msg_hdr *tx_msg = i2400m->tx_msg;
unsigned num_pls = le16_to_cpu(tx_msg->num_pls);
memcpy(ptr, buf, buf_len);
memset(ptr + buf_len, 0xad, padded_len - buf_len);
i2400m_pld_set(&tx_msg->pld[num_pls], buf_len, pl_type);
d_printf(3, dev, "pld 0x%08x (type 0x%1x len 0x%04zx\n",
le32_to_cpu(tx_msg->pld[num_pls].val),
pl_type, buf_len);
tx_msg->num_pls = le16_to_cpu(num_pls+1);
tx_msg->size += padded_len;
d_printf(2, dev, "TX: appended %zu b (up to %u b) pl #%u \n",
padded_len, tx_msg->size, num_pls+1);
d_printf(2, dev,
"TX: appended hdr @%zu %zu b pl #%u @%zu %zu/%zu b\n",
(void *)tx_msg - i2400m->tx_buf, (size_t)tx_msg->size,
num_pls+1, ptr - i2400m->tx_buf, buf_len, padded_len);
result = 0;
if (is_singleton)
i2400m_tx_close(i2400m);
}
error_tx_new:
spin_unlock_irqrestore(&i2400m->tx_lock, flags);
i2400m->bus_tx_kick(i2400m); /* always kick, might free up space */
d_fnend(3, dev, "(i2400m %p skb %p [%zu bytes] pt %u) = %d\n",
i2400m, buf, buf_len, pl_type, result);
return result;
}
EXPORT_SYMBOL_GPL(i2400m_tx);
/**
* i2400m_tx_msg_get - Get the first TX message in the FIFO to start sending it
*
* @i2400m: device descriptors
* @bus_size: where to place the size of the TX message
*
* Called by the bus-specific driver to get the first TX message at
* the FIF that is ready for transmission.
*
* It sets the state in @i2400m to indicate the bus-specific driver is
* transfering that message (i2400m->tx_msg_size).
*
* Once the transfer is completed, call i2400m_tx_msg_sent().
*
* Notes:
*
* The size of the TX message to be transmitted might be smaller than
* that of the TX message in the FIFO (in case the header was
* shorter). Hence, we copy it in @bus_size, for the bus layer to
* use. We keep the message's size in i2400m->tx_msg_size so that
* when the bus later is done transferring we know how much to
* advance the fifo.
*
* We collect statistics here as all the data is available and we
* assume it is going to work [see i2400m_tx_msg_sent()].
*/
struct i2400m_msg_hdr *i2400m_tx_msg_get(struct i2400m *i2400m,
size_t *bus_size)
{
struct device *dev = i2400m_dev(i2400m);
struct i2400m_msg_hdr *tx_msg, *tx_msg_moved;
unsigned long flags, pls;
d_fnstart(3, dev, "(i2400m %p bus_size %p)\n", i2400m, bus_size);
spin_lock_irqsave(&i2400m->tx_lock, flags);
skip:
tx_msg_moved = NULL;
if (i2400m->tx_in == i2400m->tx_out) { /* Empty FIFO? */
i2400m->tx_in = 0;
i2400m->tx_out = 0;
d_printf(2, dev, "TX: FIFO empty: resetting\n");
goto out_unlock;
}
tx_msg = i2400m->tx_buf + i2400m->tx_out % I2400M_TX_BUF_SIZE;
if (tx_msg->size & I2400M_TX_SKIP) { /* skip? */
d_printf(2, dev, "TX: skip: msg @%zu (%zu b)\n",
i2400m->tx_out % I2400M_TX_BUF_SIZE,
(size_t) tx_msg->size & ~I2400M_TX_SKIP);
i2400m->tx_out += tx_msg->size & ~I2400M_TX_SKIP;
goto skip;
}
if (tx_msg->num_pls == 0) { /* No payloads? */
if (tx_msg == i2400m->tx_msg) { /* open, we are done */
d_printf(2, dev,
"TX: FIFO empty: open msg w/o payloads @%zu\n",
(void *) tx_msg - i2400m->tx_buf);
tx_msg = NULL;
goto out_unlock;
} else { /* closed, skip it */
d_printf(2, dev,
"TX: skip msg w/o payloads @%zu (%zu b)\n",
(void *) tx_msg - i2400m->tx_buf,
(size_t) tx_msg->size);
i2400m->tx_out += tx_msg->size & ~I2400M_TX_SKIP;
goto skip;
}
}
if (tx_msg == i2400m->tx_msg) /* open msg? */
i2400m_tx_close(i2400m);
/* Now we have a valid TX message (with payloads) to TX */
tx_msg_moved = (void *) tx_msg + tx_msg->offset;
i2400m->tx_msg_size = tx_msg->size;
*bus_size = tx_msg_moved->size;
d_printf(2, dev, "TX: pid %d msg hdr at @%zu offset +@%zu "
"size %zu bus_size %zu\n",
current->pid, (void *) tx_msg - i2400m->tx_buf,
(size_t) tx_msg->offset, (size_t) tx_msg->size,
(size_t) tx_msg_moved->size);
tx_msg_moved->barker = le32_to_cpu(I2400M_H2D_PREVIEW_BARKER);
tx_msg_moved->sequence = le32_to_cpu(i2400m->tx_sequence++);
pls = le32_to_cpu(tx_msg_moved->num_pls);
i2400m->tx_pl_num += pls; /* Update stats */
if (pls > i2400m->tx_pl_max)
i2400m->tx_pl_max = pls;
if (pls < i2400m->tx_pl_min)
i2400m->tx_pl_min = pls;
i2400m->tx_num++;
i2400m->tx_size_acc += *bus_size;
if (*bus_size < i2400m->tx_size_min)
i2400m->tx_size_min = *bus_size;
if (*bus_size > i2400m->tx_size_max)
i2400m->tx_size_max = *bus_size;
out_unlock:
spin_unlock_irqrestore(&i2400m->tx_lock, flags);
d_fnstart(3, dev, "(i2400m %p bus_size %p [%zu]) = %p\n",
i2400m, bus_size, *bus_size, tx_msg_moved);
return tx_msg_moved;
}
EXPORT_SYMBOL_GPL(i2400m_tx_msg_get);
/**
* i2400m_tx_msg_sent - indicate the transmission of a TX message
*
* @i2400m: device descriptor
*
* Called by the bus-specific driver when a message has been sent;
* this pops it from the FIFO; and as there is space, start the queue
* in case it was stopped.
*
* Should be called even if the message send failed and we are
* dropping this TX message.
*/
void i2400m_tx_msg_sent(struct i2400m *i2400m)
{
unsigned n;
unsigned long flags;
struct device *dev = i2400m_dev(i2400m);
d_fnstart(3, dev, "(i2400m %p)\n", i2400m);
spin_lock_irqsave(&i2400m->tx_lock, flags);
i2400m->tx_out += i2400m->tx_msg_size;
d_printf(2, dev, "TX: sent %zu b\n", (size_t) i2400m->tx_msg_size);
i2400m->tx_msg_size = 0;
BUG_ON(i2400m->tx_out > i2400m->tx_in);
/* level them FIFO markers off */
n = i2400m->tx_out / I2400M_TX_BUF_SIZE;
i2400m->tx_out %= I2400M_TX_BUF_SIZE;
i2400m->tx_in -= n * I2400M_TX_BUF_SIZE;
spin_unlock_irqrestore(&i2400m->tx_lock, flags);
d_fnend(3, dev, "(i2400m %p) = void\n", i2400m);
}
EXPORT_SYMBOL_GPL(i2400m_tx_msg_sent);
/**
* i2400m_tx_setup - Initialize the TX queue and infrastructure
*
* Make sure we reset the TX sequence to zero, as when this function
* is called, the firmware has been just restarted.
*/
int i2400m_tx_setup(struct i2400m *i2400m)
{
int result;
/* Do this here only once -- can't do on
* i2400m_hard_start_xmit() as we'll cause race conditions if
* the WS was scheduled on another CPU */
INIT_WORK(&i2400m->wake_tx_ws, i2400m_wake_tx_work);
i2400m->tx_sequence = 0;
i2400m->tx_buf = kmalloc(I2400M_TX_BUF_SIZE, GFP_KERNEL);
if (i2400m->tx_buf == NULL)
result = -ENOMEM;
else
result = 0;
/* Huh? the bus layer has to define this... */
BUG_ON(i2400m->bus_tx_block_size == 0);
return result;
}
/**
* i2400m_tx_release - Tear down the TX queue and infrastructure
*/
void i2400m_tx_release(struct i2400m *i2400m)
{
kfree(i2400m->tx_buf);
}

View File

@@ -0,0 +1,42 @@
/*
* Intel Wireless WiMAX Connection 2400m
* Debug levels control file for the i2400m-usb module
*
*
* Copyright (C) 2007-2008 Intel Corporation <linux-wimax@intel.com>
* Inaky Perez-Gonzalez <inaky.perez-gonzalez@intel.com>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License version
* 2 as published by the Free Software Foundation.
*
* 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; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
* 02110-1301, USA.
*/
#ifndef __debug_levels__h__
#define __debug_levels__h__
/* Maximum compile and run time debug level for all submodules */
#define D_MODULENAME i2400m_usb
#define D_MASTER CONFIG_WIMAX_I2400M_DEBUG_LEVEL
#include <linux/wimax/debug.h>
/* List of all the enabled modules */
enum d_module {
D_SUBMODULE_DECLARE(usb),
D_SUBMODULE_DECLARE(fw),
D_SUBMODULE_DECLARE(notif),
D_SUBMODULE_DECLARE(rx),
D_SUBMODULE_DECLARE(tx),
};
#endif /* #ifndef __debug_levels__h__ */

View File

@@ -0,0 +1,340 @@
/*
* Intel Wireless WiMAX Connection 2400m
* Firmware uploader's USB specifics
*
*
* Copyright (C) 2007-2008 Intel Corporation. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in
* the documentation and/or other materials provided with the
* distribution.
* * Neither the name of Intel Corporation nor the names of its
* contributors may be used to endorse or promote products derived
* from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
*
* Intel Corporation <linux-wimax@intel.com>
* Yanir Lubetkin <yanirx.lubetkin@intel.com>
* Inaky Perez-Gonzalez <inaky.perez-gonzalez@intel.com>
* - Initial implementation
*
* Inaky Perez-Gonzalez <inaky.perez-gonzalez@intel.com>
* - bus generic/specific split
*
* THE PROCEDURE
*
* See fw.c for the generic description of this procedure.
*
* This file implements only the USB specifics. It boils down to how
* to send a command and waiting for an acknowledgement from the
* device.
*
* This code (and process) is single threaded. It assumes it is the
* only thread poking around (guaranteed by fw.c).
*
* COMMAND EXECUTION
*
* A write URB is posted with the buffer to the bulk output endpoint.
*
* ACK RECEPTION
*
* We just post a URB to the notification endpoint and wait for
* data. We repeat until we get all the data we expect (as indicated
* by the call from the bus generic code).
*
* The data is not read from the bulk in endpoint for boot mode.
*
* ROADMAP
*
* i2400mu_bus_bm_cmd_send
* i2400m_bm_cmd_prepare...
* i2400mu_tx_bulk_out
*
* i2400mu_bus_bm_wait_for_ack
* i2400m_notif_submit
*/
#include <linux/usb.h>
#include "i2400m-usb.h"
#define D_SUBMODULE fw
#include "usb-debug-levels.h"
/*
* Synchronous write to the device
*
* Takes care of updating EDC counts and thus, handle device errors.
*/
static
ssize_t i2400mu_tx_bulk_out(struct i2400mu *i2400mu, void *buf, size_t buf_size)
{
int result;
struct device *dev = &i2400mu->usb_iface->dev;
int len;
struct usb_endpoint_descriptor *epd;
int pipe, do_autopm = 1;
result = usb_autopm_get_interface(i2400mu->usb_iface);
if (result < 0) {
dev_err(dev, "BM-CMD: can't get autopm: %d\n", result);
do_autopm = 0;
}
epd = usb_get_epd(i2400mu->usb_iface, I2400MU_EP_BULK_OUT);
pipe = usb_sndbulkpipe(i2400mu->usb_dev, epd->bEndpointAddress);
retry:
result = usb_bulk_msg(i2400mu->usb_dev, pipe, buf, buf_size, &len, HZ);
switch (result) {
case 0:
if (len != buf_size) {
dev_err(dev, "BM-CMD: short write (%u B vs %zu "
"expected)\n", len, buf_size);
result = -EIO;
break;
}
result = len;
break;
case -EINVAL: /* while removing driver */
case -ENODEV: /* dev disconnect ... */
case -ENOENT: /* just ignore it */
case -ESHUTDOWN: /* and exit */
case -ECONNRESET:
result = -ESHUTDOWN;
break;
case -ETIMEDOUT: /* bah... */
break;
default: /* any other? */
if (edc_inc(&i2400mu->urb_edc,
EDC_MAX_ERRORS, EDC_ERROR_TIMEFRAME)) {
dev_err(dev, "BM-CMD: maximum errors in "
"URB exceeded; resetting device\n");
usb_queue_reset_device(i2400mu->usb_iface);
result = -ENODEV;
break;
}
dev_err(dev, "BM-CMD: URB error %d, retrying\n",
result);
goto retry;
}
result = len;
if (do_autopm)
usb_autopm_put_interface(i2400mu->usb_iface);
return result;
}
/*
* Send a boot-mode command over the bulk-out pipe
*
* Command can be a raw command, which requires no preparation (and
* which might not even be following the command format). Checks that
* the right amount of data was transfered.
*
* To satisfy USB requirements (no onstack, vmalloc or in data segment
* buffers), we copy the command to i2400m->bm_cmd_buf and send it from
* there.
*
* @flags: pass thru from i2400m_bm_cmd()
* @return: cmd_size if ok, < 0 errno code on error.
*/
ssize_t i2400mu_bus_bm_cmd_send(struct i2400m *i2400m,
const struct i2400m_bootrom_header *_cmd,
size_t cmd_size, int flags)
{
ssize_t result;
struct device *dev = i2400m_dev(i2400m);
struct i2400mu *i2400mu = container_of(i2400m, struct i2400mu, i2400m);
int opcode = _cmd == NULL ? -1 : i2400m_brh_get_opcode(_cmd);
struct i2400m_bootrom_header *cmd;
size_t cmd_size_a = ALIGN(cmd_size, 16); /* USB restriction */
d_fnstart(8, dev, "(i2400m %p cmd %p size %zu)\n",
i2400m, _cmd, cmd_size);
result = -E2BIG;
if (cmd_size > I2400M_BM_CMD_BUF_SIZE)
goto error_too_big;
memcpy(i2400m->bm_cmd_buf, _cmd, cmd_size);
cmd = i2400m->bm_cmd_buf;
if (cmd_size_a > cmd_size) /* Zero pad space */
memset(i2400m->bm_cmd_buf + cmd_size, 0, cmd_size_a - cmd_size);
if ((flags & I2400M_BM_CMD_RAW) == 0) {
if (WARN_ON(i2400m_brh_get_response_required(cmd) == 0))
dev_warn(dev, "SW BUG: response_required == 0\n");
i2400m_bm_cmd_prepare(cmd);
}
result = i2400mu_tx_bulk_out(i2400mu, i2400m->bm_cmd_buf, cmd_size);
if (result < 0) {
dev_err(dev, "boot-mode cmd %d: cannot send: %zd\n",
opcode, result);
goto error_cmd_send;
}
if (result != cmd_size) { /* all was transferred? */
dev_err(dev, "boot-mode cmd %d: incomplete transfer "
"(%zu vs %zu submitted)\n", opcode, result, cmd_size);
result = -EIO;
goto error_cmd_size;
}
error_cmd_size:
error_cmd_send:
error_too_big:
d_fnend(8, dev, "(i2400m %p cmd %p size %zu) = %zd\n",
i2400m, _cmd, cmd_size, result);
return result;
}
static
void __i2400mu_bm_notif_cb(struct urb *urb)
{
complete(urb->context);
}
/*
* submit a read to the notification endpoint
*
* @i2400m: device descriptor
* @urb: urb to use
* @completion: completion varible to complete when done
*
* Data is always read to i2400m->bm_ack_buf
*/
static
int i2400mu_notif_submit(struct i2400mu *i2400mu, struct urb *urb,
struct completion *completion)
{
struct i2400m *i2400m = &i2400mu->i2400m;
struct usb_endpoint_descriptor *epd;
int pipe;
epd = usb_get_epd(i2400mu->usb_iface, I2400MU_EP_NOTIFICATION);
pipe = usb_rcvintpipe(i2400mu->usb_dev, epd->bEndpointAddress);
usb_fill_int_urb(urb, i2400mu->usb_dev, pipe,
i2400m->bm_ack_buf, I2400M_BM_ACK_BUF_SIZE,
__i2400mu_bm_notif_cb, completion,
epd->bInterval);
return usb_submit_urb(urb, GFP_KERNEL);
}
/*
* Read an ack from the notification endpoint
*
* @i2400m:
* @_ack: pointer to where to store the read data
* @ack_size: how many bytes we should read
*
* Returns: < 0 errno code on error; otherwise, amount of received bytes.
*
* Submits a notification read, appends the read data to the given ack
* buffer and then repeats (until @ack_size bytes have been
* received).
*/
ssize_t i2400mu_bus_bm_wait_for_ack(struct i2400m *i2400m,
struct i2400m_bootrom_header *_ack,
size_t ack_size)
{
ssize_t result = -ENOMEM;
struct device *dev = i2400m_dev(i2400m);
struct i2400mu *i2400mu = container_of(i2400m, struct i2400mu, i2400m);
struct urb notif_urb;
void *ack = _ack;
size_t offset, len;
long val;
int do_autopm = 1;
DECLARE_COMPLETION_ONSTACK(notif_completion);
d_fnstart(8, dev, "(i2400m %p ack %p size %zu)\n",
i2400m, ack, ack_size);
BUG_ON(_ack == i2400m->bm_ack_buf);
result = usb_autopm_get_interface(i2400mu->usb_iface);
if (result < 0) {
dev_err(dev, "BM-ACK: can't get autopm: %d\n", (int) result);
do_autopm = 0;
}
usb_init_urb(&notif_urb); /* ready notifications */
usb_get_urb(&notif_urb);
offset = 0;
while (offset < ack_size) {
init_completion(&notif_completion);
result = i2400mu_notif_submit(i2400mu, &notif_urb,
&notif_completion);
if (result < 0)
goto error_notif_urb_submit;
val = wait_for_completion_interruptible_timeout(
&notif_completion, HZ);
if (val == 0) {
result = -ETIMEDOUT;
usb_kill_urb(&notif_urb); /* Timedout */
goto error_notif_wait;
}
if (val == -ERESTARTSYS) {
result = -EINTR; /* Interrupted */
usb_kill_urb(&notif_urb);
goto error_notif_wait;
}
result = notif_urb.status; /* How was the ack? */
switch (result) {
case 0:
break;
case -EINVAL: /* while removing driver */
case -ENODEV: /* dev disconnect ... */
case -ENOENT: /* just ignore it */
case -ESHUTDOWN: /* and exit */
case -ECONNRESET:
result = -ESHUTDOWN;
goto error_dev_gone;
default: /* any other? */
usb_kill_urb(&notif_urb); /* Timedout */
if (edc_inc(&i2400mu->urb_edc,
EDC_MAX_ERRORS, EDC_ERROR_TIMEFRAME))
goto error_exceeded;
dev_err(dev, "BM-ACK: URB error %d, "
"retrying\n", notif_urb.status);
continue; /* retry */
}
if (notif_urb.actual_length == 0) {
d_printf(6, dev, "ZLP received, retrying\n");
continue;
}
/* Got data, append it to the buffer */
len = min(ack_size - offset, (size_t) notif_urb.actual_length);
memcpy(ack + offset, i2400m->bm_ack_buf, len);
offset += len;
}
result = offset;
error_notif_urb_submit:
error_notif_wait:
error_dev_gone:
out:
if (do_autopm)
usb_autopm_put_interface(i2400mu->usb_iface);
d_fnend(8, dev, "(i2400m %p ack %p size %zu) = %zd\n",
i2400m, ack, ack_size, result);
return result;
error_exceeded:
dev_err(dev, "bm: maximum errors in notification URB exceeded; "
"resetting device\n");
usb_queue_reset_device(i2400mu->usb_iface);
goto out;
}

View File

@@ -0,0 +1,269 @@
/*
* Intel Wireless WiMAX Connection 2400m over USB
* Notification handling
*
*
* Copyright (C) 2007-2008 Intel Corporation. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in
* the documentation and/or other materials provided with the
* distribution.
* * Neither the name of Intel Corporation nor the names of its
* contributors may be used to endorse or promote products derived
* from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
*
* Intel Corporation <linux-wimax@intel.com>
* Yanir Lubetkin <yanirx.lubetkin@intel.com>
* Inaky Perez-Gonzalez <inaky.perez-gonzalez@intel.com>
* - Initial implementation
*
*
* The notification endpoint is active when the device is not in boot
* mode; in here we just read and get notifications; based on those,
* we act to either reinitialize the device after a reboot or to
* submit a RX request.
*
* ROADMAP
*
* i2400mu_usb_notification_setup()
*
* i2400mu_usb_notification_release()
*
* i2400mu_usb_notification_cb() Called when a URB is ready
* i2400mu_notif_grok()
* i2400m_dev_reset_handle()
* i2400mu_rx_kick()
*/
#include <linux/usb.h>
#include "i2400m-usb.h"
#define D_SUBMODULE notif
#include "usb-debug-levels.h"
static const
__le32 i2400m_ZERO_BARKER[4] = { 0, 0, 0, 0 };
/*
* Process a received notification
*
* In normal operation mode, we can only receive two types of payloads
* on the notification endpoint:
*
* - a reboot barker, we do a bootstrap (the device has reseted).
*
* - a block of zeroes: there is pending data in the IN endpoint
*/
static
int i2400mu_notification_grok(struct i2400mu *i2400mu, const void *buf,
size_t buf_len)
{
int ret;
struct device *dev = &i2400mu->usb_iface->dev;
struct i2400m *i2400m = &i2400mu->i2400m;
d_fnstart(4, dev, "(i2400m %p buf %p buf_len %zu)\n",
i2400mu, buf, buf_len);
ret = -EIO;
if (buf_len < sizeof(i2400m_NBOOT_BARKER))
/* Not a bug, just ignore */
goto error_bad_size;
if (!memcmp(i2400m_NBOOT_BARKER, buf, sizeof(i2400m_NBOOT_BARKER))
|| !memcmp(i2400m_SBOOT_BARKER, buf, sizeof(i2400m_SBOOT_BARKER)))
ret = i2400m_dev_reset_handle(i2400m);
else if (!memcmp(i2400m_ZERO_BARKER, buf, sizeof(i2400m_ZERO_BARKER))) {
i2400mu_rx_kick(i2400mu);
ret = 0;
} else { /* Unknown or unexpected data in the notif message */
char prefix[64];
ret = -EIO;
dev_err(dev, "HW BUG? Unknown/unexpected data in notification "
"message (%zu bytes)\n", buf_len);
snprintf(prefix, sizeof(prefix), "%s %s: ",
dev_driver_string(dev), dev_name(dev));
if (buf_len > 64) {
print_hex_dump(KERN_ERR, prefix, DUMP_PREFIX_OFFSET,
8, 4, buf, 64, 0);
printk(KERN_ERR "%s... (only first 64 bytes "
"dumped)\n", prefix);
} else
print_hex_dump(KERN_ERR, prefix, DUMP_PREFIX_OFFSET,
8, 4, buf, buf_len, 0);
}
error_bad_size:
d_fnend(4, dev, "(i2400m %p buf %p buf_len %zu) = %d\n",
i2400mu, buf, buf_len, ret);
return ret;
}
/*
* URB callback for the notification endpoint
*
* @urb: the urb received from the notification endpoint
*
* This function will just process the USB side of the transaction,
* checking everything is fine, pass the processing to
* i2400m_notification_grok() and resubmit the URB.
*/
static
void i2400mu_notification_cb(struct urb *urb)
{
int ret;
struct i2400mu *i2400mu = urb->context;
struct device *dev = &i2400mu->usb_iface->dev;
d_fnstart(4, dev, "(urb %p status %d actual_length %d)\n",
urb, urb->status, urb->actual_length);
ret = urb->status;
switch (ret) {
case 0:
ret = i2400mu_notification_grok(i2400mu, urb->transfer_buffer,
urb->actual_length);
if (ret == -EIO && edc_inc(&i2400mu->urb_edc, EDC_MAX_ERRORS,
EDC_ERROR_TIMEFRAME))
goto error_exceeded;
if (ret == -ENOMEM) /* uff...power cycle? shutdown? */
goto error_exceeded;
break;
case -EINVAL: /* while removing driver */
case -ENODEV: /* dev disconnect ... */
case -ENOENT: /* ditto */
case -ESHUTDOWN: /* URB killed */
case -ECONNRESET: /* disconnection */
goto out; /* Notify around */
default: /* Some error? */
if (edc_inc(&i2400mu->urb_edc,
EDC_MAX_ERRORS, EDC_ERROR_TIMEFRAME))
goto error_exceeded;
dev_err(dev, "notification: URB error %d, retrying\n",
urb->status);
}
usb_mark_last_busy(i2400mu->usb_dev);
ret = usb_submit_urb(i2400mu->notif_urb, GFP_ATOMIC);
switch (ret) {
case 0:
case -EINVAL: /* while removing driver */
case -ENODEV: /* dev disconnect ... */
case -ENOENT: /* ditto */
case -ESHUTDOWN: /* URB killed */
case -ECONNRESET: /* disconnection */
break; /* just ignore */
default: /* Some error? */
dev_err(dev, "notification: cannot submit URB: %d\n", ret);
goto error_submit;
}
d_fnend(4, dev, "(urb %p status %d actual_length %d) = void\n",
urb, urb->status, urb->actual_length);
return;
error_exceeded:
dev_err(dev, "maximum errors in notification URB exceeded; "
"resetting device\n");
error_submit:
usb_queue_reset_device(i2400mu->usb_iface);
out:
d_fnend(4, dev, "(urb %p status %d actual_length %d) = void\n",
urb, urb->status, urb->actual_length);
return;
}
/*
* setup the notification endpoint
*
* @i2400m: device descriptor
*
* This procedure prepares the notification urb and handler for receiving
* unsolicited barkers from the device.
*/
int i2400mu_notification_setup(struct i2400mu *i2400mu)
{
struct device *dev = &i2400mu->usb_iface->dev;
int usb_pipe, ret = 0;
struct usb_endpoint_descriptor *epd;
char *buf;
d_fnstart(4, dev, "(i2400m %p)\n", i2400mu);
buf = kmalloc(I2400MU_MAX_NOTIFICATION_LEN, GFP_KERNEL | GFP_DMA);
if (buf == NULL) {
dev_err(dev, "notification: buffer allocation failed\n");
ret = -ENOMEM;
goto error_buf_alloc;
}
i2400mu->notif_urb = usb_alloc_urb(0, GFP_KERNEL);
if (!i2400mu->notif_urb) {
ret = -ENOMEM;
dev_err(dev, "notification: cannot allocate URB\n");
goto error_alloc_urb;
}
epd = usb_get_epd(i2400mu->usb_iface, I2400MU_EP_NOTIFICATION);
usb_pipe = usb_rcvintpipe(i2400mu->usb_dev, epd->bEndpointAddress);
usb_fill_int_urb(i2400mu->notif_urb, i2400mu->usb_dev, usb_pipe,
buf, I2400MU_MAX_NOTIFICATION_LEN,
i2400mu_notification_cb, i2400mu, epd->bInterval);
ret = usb_submit_urb(i2400mu->notif_urb, GFP_KERNEL);
if (ret != 0) {
dev_err(dev, "notification: cannot submit URB: %d\n", ret);
goto error_submit;
}
d_fnend(4, dev, "(i2400m %p) = %d\n", i2400mu, ret);
return ret;
error_submit:
usb_free_urb(i2400mu->notif_urb);
error_alloc_urb:
kfree(buf);
error_buf_alloc:
d_fnend(4, dev, "(i2400m %p) = %d\n", i2400mu, ret);
return ret;
}
/*
* Tear down of the notification mechanism
*
* @i2400m: device descriptor
*
* Kill the interrupt endpoint urb, free any allocated resources.
*
* We need to check if we have done it before as for example,
* _suspend() call this; if after a suspend() we get a _disconnect()
* (as the case is when hibernating), nothing bad happens.
*/
void i2400mu_notification_release(struct i2400mu *i2400mu)
{
struct device *dev = &i2400mu->usb_iface->dev;
d_fnstart(4, dev, "(i2400mu %p)\n", i2400mu);
if (i2400mu->notif_urb != NULL) {
usb_kill_urb(i2400mu->notif_urb);
kfree(i2400mu->notif_urb->transfer_buffer);
usb_free_urb(i2400mu->notif_urb);
i2400mu->notif_urb = NULL;
}
d_fnend(4, dev, "(i2400mu %p)\n", i2400mu);
}

View File

@@ -0,0 +1,420 @@
/*
* Intel Wireless WiMAX Connection 2400m
* USB RX handling
*
*
* Copyright (C) 2007-2008 Intel Corporation. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in
* the documentation and/or other materials provided with the
* distribution.
* * Neither the name of Intel Corporation nor the names of its
* contributors may be used to endorse or promote products derived
* from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
*
* Intel Corporation <linux-wimax@intel.com>
* Yanir Lubetkin <yanirx.lubetkin@intel.com>
* - Initial implementation
* Inaky Perez-Gonzalez <inaky.perez-gonzalez@intel.com>
* - Use skb_clone(), break up processing in chunks
* - Split transport/device specific
* - Make buffer size dynamic to exert less memory pressure
*
*
* This handles the RX path on USB.
*
* When a notification is received that says 'there is RX data ready',
* we call i2400mu_rx_kick(); that wakes up the RX kthread, which
* reads a buffer from USB and passes it to i2400m_rx() in the generic
* handling code. The RX buffer has an specific format that is
* described in rx.c.
*
* We use a kernel thread in a loop because:
*
* - we want to be able to call the USB power management get/put
* functions (blocking) before each transaction.
*
* - We might get a lot of notifications and we don't want to submit
* a zillion reads; by serializing, we are throttling.
*
* - RX data processing can get heavy enough so that it is not
* appropiate for doing it in the USB callback; thus we run it in a
* process context.
*
* We provide a read buffer of an arbitrary size (short of a page); if
* the callback reports -EOVERFLOW, it means it was too small, so we
* just double the size and retry (being careful to append, as
* sometimes the device provided some data). Every now and then we
* check if the average packet size is smaller than the current packet
* size and if so, we halve it. At the end, the size of the
* preallocated buffer should be following the average received
* transaction size, adapting dynamically to it.
*
* ROADMAP
*
* i2400mu_rx_kick() Called from notif.c when we get a
* 'data ready' notification
* i2400mu_rxd() Kernel RX daemon
* i2400mu_rx() Receive USB data
* i2400m_rx() Send data to generic i2400m RX handling
*
* i2400mu_rx_setup() called from i2400mu_bus_dev_start()
*
* i2400mu_rx_release() called from i2400mu_bus_dev_stop()
*/
#include <linux/workqueue.h>
#include <linux/usb.h>
#include "i2400m-usb.h"
#define D_SUBMODULE rx
#include "usb-debug-levels.h"
/*
* Dynamic RX size
*
* We can't let the rx_size be a multiple of 512 bytes (the RX
* endpoint's max packet size). On some USB host controllers (we
* haven't been able to fully characterize which), if the device is
* about to send (for example) X bytes and we only post a buffer to
* receive n*512, it will fail to mark that as babble (so that
* i2400mu_rx() [case -EOVERFLOW] can resize the buffer and get the
* rest).
*
* So on growing or shrinking, if it is a multiple of the
* maxpacketsize, we remove some (instead of incresing some, so in a
* buddy allocator we try to waste less space).
*
* Note we also need a hook for this on i2400mu_rx() -- when we do the
* first read, we are sure we won't hit this spot because
* i240mm->rx_size has been set properly. However, if we have to
* double because of -EOVERFLOW, when we launch the read to get the
* rest of the data, we *have* to make sure that also is not a
* multiple of the max_pkt_size.
*/
static
size_t i2400mu_rx_size_grow(struct i2400mu *i2400mu)
{
struct device *dev = &i2400mu->usb_iface->dev;
size_t rx_size;
const size_t max_pkt_size = 512;
rx_size = 2 * i2400mu->rx_size;
if (rx_size % max_pkt_size == 0) {
rx_size -= 8;
d_printf(1, dev,
"RX: expected size grew to %zu [adjusted -8] "
"from %zu\n",
rx_size, i2400mu->rx_size);
} else
d_printf(1, dev,
"RX: expected size grew to %zu from %zu\n",
rx_size, i2400mu->rx_size);
return rx_size;
}
static
void i2400mu_rx_size_maybe_shrink(struct i2400mu *i2400mu)
{
const size_t max_pkt_size = 512;
struct device *dev = &i2400mu->usb_iface->dev;
if (unlikely(i2400mu->rx_size_cnt >= 100
&& i2400mu->rx_size_auto_shrink)) {
size_t avg_rx_size =
i2400mu->rx_size_acc / i2400mu->rx_size_cnt;
size_t new_rx_size = i2400mu->rx_size / 2;
if (avg_rx_size < new_rx_size) {
if (new_rx_size % max_pkt_size == 0) {
new_rx_size -= 8;
d_printf(1, dev,
"RX: expected size shrank to %zu "
"[adjusted -8] from %zu\n",
new_rx_size, i2400mu->rx_size);
} else
d_printf(1, dev,
"RX: expected size shrank to %zu "
"from %zu\n",
new_rx_size, i2400mu->rx_size);
i2400mu->rx_size = new_rx_size;
i2400mu->rx_size_cnt = 0;
i2400mu->rx_size_acc = i2400mu->rx_size;
}
}
}
/*
* Receive a message with payloads from the USB bus into an skb
*
* @i2400mu: USB device descriptor
* @rx_skb: skb where to place the received message
*
* Deals with all the USB-specifics of receiving, dynamically
* increasing the buffer size if so needed. Returns the payload in the
* skb, ready to process. On a zero-length packet, we retry.
*
* On soft USB errors, we retry (until they become too frequent and
* then are promoted to hard); on hard USB errors, we reset the
* device. On other errors (skb realloacation, we just drop it and
* hope for the next invocation to solve it).
*
* Returns: pointer to the skb if ok, ERR_PTR on error.
* NOTE: this function might realloc the skb (if it is too small),
* so always update with the one returned.
* ERR_PTR() is < 0 on error.
* Will return NULL if it cannot reallocate -- this can be
* considered a transient retryable error.
*/
static
struct sk_buff *i2400mu_rx(struct i2400mu *i2400mu, struct sk_buff *rx_skb)
{
int result = 0;
struct device *dev = &i2400mu->usb_iface->dev;
int usb_pipe, read_size, rx_size, do_autopm;
struct usb_endpoint_descriptor *epd;
const size_t max_pkt_size = 512;
d_fnstart(4, dev, "(i2400mu %p)\n", i2400mu);
do_autopm = atomic_read(&i2400mu->do_autopm);
result = do_autopm ?
usb_autopm_get_interface(i2400mu->usb_iface) : 0;
if (result < 0) {
dev_err(dev, "RX: can't get autopm: %d\n", result);
do_autopm = 0;
}
epd = usb_get_epd(i2400mu->usb_iface, I2400MU_EP_BULK_IN);
usb_pipe = usb_rcvbulkpipe(i2400mu->usb_dev, epd->bEndpointAddress);
retry:
rx_size = skb_end_pointer(rx_skb) - rx_skb->data - rx_skb->len;
if (unlikely(rx_size % max_pkt_size == 0)) {
rx_size -= 8;
d_printf(1, dev, "RX: rx_size adapted to %d [-8]\n", rx_size);
}
result = usb_bulk_msg(
i2400mu->usb_dev, usb_pipe, rx_skb->data + rx_skb->len,
rx_size, &read_size, HZ);
usb_mark_last_busy(i2400mu->usb_dev);
switch (result) {
case 0:
if (read_size == 0)
goto retry; /* ZLP, just resubmit */
skb_put(rx_skb, read_size);
break;
case -EINVAL: /* while removing driver */
case -ENODEV: /* dev disconnect ... */
case -ENOENT: /* just ignore it */
case -ESHUTDOWN:
case -ECONNRESET:
break;
case -EOVERFLOW: { /* too small, reallocate */
struct sk_buff *new_skb;
rx_size = i2400mu_rx_size_grow(i2400mu);
if (rx_size <= (1 << 16)) /* cap it */
i2400mu->rx_size = rx_size;
else if (printk_ratelimit()) {
dev_err(dev, "BUG? rx_size up to %d\n", rx_size);
result = -EINVAL;
goto out;
}
skb_put(rx_skb, read_size);
new_skb = skb_copy_expand(rx_skb, 0, rx_size - rx_skb->len,
GFP_KERNEL);
if (new_skb == NULL) {
if (printk_ratelimit())
dev_err(dev, "RX: Can't reallocate skb to %d; "
"RX dropped\n", rx_size);
kfree_skb(rx_skb);
rx_skb = NULL;
goto out; /* drop it...*/
}
kfree_skb(rx_skb);
rx_skb = new_skb;
i2400mu->rx_size_cnt = 0;
i2400mu->rx_size_acc = i2400mu->rx_size;
d_printf(1, dev, "RX: size changed to %d, received %d, "
"copied %d, capacity %ld\n",
rx_size, read_size, rx_skb->len,
(long) (skb_end_pointer(new_skb) - new_skb->head));
goto retry;
}
/* In most cases, it happens due to the hardware scheduling a
* read when there was no data - unfortunately, we have no way
* to tell this timeout from a USB timeout. So we just ignore
* it. */
case -ETIMEDOUT:
dev_err(dev, "RX: timeout: %d\n", result);
result = 0;
break;
default: /* Any error */
if (edc_inc(&i2400mu->urb_edc,
EDC_MAX_ERRORS, EDC_ERROR_TIMEFRAME))
goto error_reset;
dev_err(dev, "RX: error receiving URB: %d, retrying\n", result);
goto retry;
}
out:
if (do_autopm)
usb_autopm_put_interface(i2400mu->usb_iface);
d_fnend(4, dev, "(i2400mu %p) = %p\n", i2400mu, rx_skb);
return rx_skb;
error_reset:
dev_err(dev, "RX: maximum errors in URB exceeded; "
"resetting device\n");
usb_queue_reset_device(i2400mu->usb_iface);
rx_skb = ERR_PTR(result);
goto out;
}
/*
* Kernel thread for USB reception of data
*
* This thread waits for a kick; once kicked, it will allocate an skb
* and receive a single message to it from USB (using
* i2400mu_rx()). Once received, it is passed to the generic i2400m RX
* code for processing.
*
* When done processing, it runs some dirty statistics to verify if
* the last 100 messages received were smaller than half of the
* current RX buffer size. In that case, the RX buffer size is
* halved. This will helps lowering the pressure on the memory
* allocator.
*
* Hard errors force the thread to exit.
*/
static
int i2400mu_rxd(void *_i2400mu)
{
int result = 0;
struct i2400mu *i2400mu = _i2400mu;
struct i2400m *i2400m = &i2400mu->i2400m;
struct device *dev = &i2400mu->usb_iface->dev;
struct net_device *net_dev = i2400m->wimax_dev.net_dev;
size_t pending;
int rx_size;
struct sk_buff *rx_skb;
d_fnstart(4, dev, "(i2400mu %p)\n", i2400mu);
while (1) {
d_printf(2, dev, "TX: waiting for messages\n");
pending = 0;
wait_event_interruptible(
i2400mu->rx_wq,
(kthread_should_stop() /* check this first! */
|| (pending = atomic_read(&i2400mu->rx_pending_count)))
);
if (kthread_should_stop())
break;
if (pending == 0)
continue;
rx_size = i2400mu->rx_size;
d_printf(2, dev, "RX: reading up to %d bytes\n", rx_size);
rx_skb = __netdev_alloc_skb(net_dev, rx_size, GFP_KERNEL);
if (rx_skb == NULL) {
dev_err(dev, "RX: can't allocate skb [%d bytes]\n",
rx_size);
msleep(50); /* give it some time? */
continue;
}
/* Receive the message with the payloads */
rx_skb = i2400mu_rx(i2400mu, rx_skb);
result = PTR_ERR(rx_skb);
if (IS_ERR(rx_skb))
goto out;
atomic_dec(&i2400mu->rx_pending_count);
if (rx_skb == NULL || rx_skb->len == 0) {
/* some "ignorable" condition */
kfree_skb(rx_skb);
continue;
}
/* Deliver the message to the generic i2400m code */
i2400mu->rx_size_cnt++;
i2400mu->rx_size_acc += rx_skb->len;
result = i2400m_rx(i2400m, rx_skb);
if (result == -EIO
&& edc_inc(&i2400mu->urb_edc,
EDC_MAX_ERRORS, EDC_ERROR_TIMEFRAME)) {
goto error_reset;
}
/* Maybe adjust RX buffer size */
i2400mu_rx_size_maybe_shrink(i2400mu);
}
result = 0;
out:
d_fnend(4, dev, "(i2400mu %p) = %d\n", i2400mu, result);
return result;
error_reset:
dev_err(dev, "RX: maximum errors in received buffer exceeded; "
"resetting device\n");
usb_queue_reset_device(i2400mu->usb_iface);
goto out;
}
/*
* Start reading from the device
*
* @i2400m: device instance
*
* Notify the RX thread that there is data pending.
*/
void i2400mu_rx_kick(struct i2400mu *i2400mu)
{
struct i2400m *i2400m = &i2400mu->i2400m;
struct device *dev = &i2400mu->usb_iface->dev;
d_fnstart(3, dev, "(i2400mu %p)\n", i2400m);
atomic_inc(&i2400mu->rx_pending_count);
wake_up_all(&i2400mu->rx_wq);
d_fnend(3, dev, "(i2400m %p) = void\n", i2400m);
}
int i2400mu_rx_setup(struct i2400mu *i2400mu)
{
int result = 0;
struct i2400m *i2400m = &i2400mu->i2400m;
struct device *dev = &i2400mu->usb_iface->dev;
struct wimax_dev *wimax_dev = &i2400m->wimax_dev;
i2400mu->rx_kthread = kthread_run(i2400mu_rxd, i2400mu, "%s-rx",
wimax_dev->name);
if (IS_ERR(i2400mu->rx_kthread)) {
result = PTR_ERR(i2400mu->rx_kthread);
dev_err(dev, "RX: cannot start thread: %d\n", result);
}
return result;
}
void i2400mu_rx_release(struct i2400mu *i2400mu)
{
kthread_stop(i2400mu->rx_kthread);
}

View File

@@ -0,0 +1,229 @@
/*
* Intel Wireless WiMAX Connection 2400m
* USB specific TX handling
*
*
* Copyright (C) 2007-2008 Intel Corporation. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in
* the documentation and/or other materials provided with the
* distribution.
* * Neither the name of Intel Corporation nor the names of its
* contributors may be used to endorse or promote products derived
* from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
*
* Intel Corporation <linux-wimax@intel.com>
* Yanir Lubetkin <yanirx.lubetkin@intel.com>
* - Initial implementation
* Inaky Perez-Gonzalez <inaky.perez-gonzalez@intel.com>
* - Split transport/device specific
*
*
* Takes the TX messages in the i2400m's driver TX FIFO and sends them
* to the device until there are no more.
*
* If we fail sending the message, we just drop it. There isn't much
* we can do at this point. We could also retry, but the USB stack has
* already retried and still failed, so there is not much of a
* point. As well, most of the traffic is network, which has recovery
* methods for dropped packets.
*
* For sending we just obtain a FIFO buffer to send, send it to the
* USB bulk out, tell the TX FIFO code we have sent it; query for
* another one, etc... until done.
*
* We use a thread so we can call usb_autopm_enable() and
* usb_autopm_disable() for each transaction; this way when the device
* goes idle, it will suspend. It also has less overhead than a
* dedicated workqueue, as it is being used for a single task.
*
* ROADMAP
*
* i2400mu_tx_setup()
* i2400mu_tx_release()
*
* i2400mu_bus_tx_kick() - Called by the tx.c code when there
* is new data in the FIFO.
* i2400mu_txd()
* i2400m_tx_msg_get()
* i2400m_tx_msg_sent()
*/
#include "i2400m-usb.h"
#define D_SUBMODULE tx
#include "usb-debug-levels.h"
/*
* Get the next TX message in the TX FIFO and send it to the device
*
* Note that any iteration consumes a message to be sent, no matter if
* it succeeds or fails (we have no real way to retry or complain).
*
* Return: 0 if ok, < 0 errno code on hard error.
*/
static
int i2400mu_tx(struct i2400mu *i2400mu, struct i2400m_msg_hdr *tx_msg,
size_t tx_msg_size)
{
int result = 0;
struct i2400m *i2400m = &i2400mu->i2400m;
struct device *dev = &i2400mu->usb_iface->dev;
int usb_pipe, sent_size, do_autopm;
struct usb_endpoint_descriptor *epd;
d_fnstart(4, dev, "(i2400mu %p)\n", i2400mu);
do_autopm = atomic_read(&i2400mu->do_autopm);
result = do_autopm ?
usb_autopm_get_interface(i2400mu->usb_iface) : 0;
if (result < 0) {
dev_err(dev, "TX: can't get autopm: %d\n", result);
do_autopm = 0;
}
epd = usb_get_epd(i2400mu->usb_iface, I2400MU_EP_BULK_OUT);
usb_pipe = usb_sndbulkpipe(i2400mu->usb_dev, epd->bEndpointAddress);
retry:
result = usb_bulk_msg(i2400mu->usb_dev, usb_pipe,
tx_msg, tx_msg_size, &sent_size, HZ);
usb_mark_last_busy(i2400mu->usb_dev);
switch (result) {
case 0:
if (sent_size != tx_msg_size) { /* Too short? drop it */
dev_err(dev, "TX: short write (%d B vs %zu "
"expected)\n", sent_size, tx_msg_size);
result = -EIO;
}
break;
case -EINVAL: /* while removing driver */
case -ENODEV: /* dev disconnect ... */
case -ENOENT: /* just ignore it */
case -ESHUTDOWN: /* and exit */
case -ECONNRESET:
result = -ESHUTDOWN;
break;
default: /* Some error? */
if (edc_inc(&i2400mu->urb_edc,
EDC_MAX_ERRORS, EDC_ERROR_TIMEFRAME)) {
dev_err(dev, "TX: maximum errors in URB "
"exceeded; resetting device\n");
usb_queue_reset_device(i2400mu->usb_iface);
} else {
dev_err(dev, "TX: cannot send URB; retrying. "
"tx_msg @%zu %zu B [%d sent]: %d\n",
(void *) tx_msg - i2400m->tx_buf,
tx_msg_size, sent_size, result);
goto retry;
}
}
if (do_autopm)
usb_autopm_put_interface(i2400mu->usb_iface);
d_fnend(4, dev, "(i2400mu %p) = result\n", i2400mu);
return result;
}
/*
* Get the next TX message in the TX FIFO and send it to the device
*
* Note we exit the loop if i2400mu_tx() fails; that funtion only
* fails on hard error (failing to tx a buffer not being one of them,
* see its doc).
*
* Return: 0
*/
static
int i2400mu_txd(void *_i2400mu)
{
int result = 0;
struct i2400mu *i2400mu = _i2400mu;
struct i2400m *i2400m = &i2400mu->i2400m;
struct device *dev = &i2400mu->usb_iface->dev;
struct i2400m_msg_hdr *tx_msg;
size_t tx_msg_size;
d_fnstart(4, dev, "(i2400mu %p)\n", i2400mu);
while (1) {
d_printf(2, dev, "TX: waiting for messages\n");
tx_msg = NULL;
wait_event_interruptible(
i2400mu->tx_wq,
(kthread_should_stop() /* check this first! */
|| (tx_msg = i2400m_tx_msg_get(i2400m, &tx_msg_size)))
);
if (kthread_should_stop())
break;
WARN_ON(tx_msg == NULL); /* should not happen...*/
d_printf(2, dev, "TX: submitting %zu bytes\n", tx_msg_size);
d_dump(5, dev, tx_msg, tx_msg_size);
/* Yeah, we ignore errors ... not much we can do */
i2400mu_tx(i2400mu, tx_msg, tx_msg_size);
i2400m_tx_msg_sent(i2400m); /* ack it, advance the FIFO */
if (result < 0)
break;
}
d_fnend(4, dev, "(i2400mu %p) = %d\n", i2400mu, result);
return result;
}
/*
* i2400m TX engine notifies us that there is data in the FIFO ready
* for TX
*
* If there is a URB in flight, don't do anything; when it finishes,
* it will see there is data in the FIFO and send it. Else, just
* submit a write.
*/
void i2400mu_bus_tx_kick(struct i2400m *i2400m)
{
struct i2400mu *i2400mu = container_of(i2400m, struct i2400mu, i2400m);
struct device *dev = &i2400mu->usb_iface->dev;
d_fnstart(3, dev, "(i2400m %p) = void\n", i2400m);
wake_up_all(&i2400mu->tx_wq);
d_fnend(3, dev, "(i2400m %p) = void\n", i2400m);
}
int i2400mu_tx_setup(struct i2400mu *i2400mu)
{
int result = 0;
struct i2400m *i2400m = &i2400mu->i2400m;
struct device *dev = &i2400mu->usb_iface->dev;
struct wimax_dev *wimax_dev = &i2400m->wimax_dev;
i2400mu->tx_kthread = kthread_run(i2400mu_txd, i2400mu, "%s-tx",
wimax_dev->name);
if (IS_ERR(i2400mu->tx_kthread)) {
result = PTR_ERR(i2400mu->tx_kthread);
dev_err(dev, "TX: cannot start thread: %d\n", result);
}
return result;
}
void i2400mu_tx_release(struct i2400mu *i2400mu)
{
kthread_stop(i2400mu->tx_kthread);
}

View File

@@ -0,0 +1,638 @@
/*
* Intel Wireless WiMAX Connection 2400m
* Linux driver model glue for USB device, reset & fw upload
*
*
* Copyright (C) 2007-2008 Intel Corporation <linux-wimax@intel.com>
* Inaky Perez-Gonzalez <inaky.perez-gonzalez@intel.com>
* Yanir Lubetkin <yanirx.lubetkin@intel.com>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License version
* 2 as published by the Free Software Foundation.
*
* 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; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
* 02110-1301, USA.
*
*
* See i2400m-usb.h for a general description of this driver.
*
* This file implements driver model glue, and hook ups for the
* generic driver to implement the bus-specific functions (device
* communication setup/tear down, firmware upload and resetting).
*
* ROADMAP
*
* i2400mu_probe()
* alloc_netdev()...
* i2400mu_netdev_setup()
* i2400mu_init()
* i2400m_netdev_setup()
* i2400m_setup()...
*
* i2400mu_disconnect
* i2400m_release()
* free_netdev()
*
* i2400mu_suspend()
* i2400m_cmd_enter_powersave()
* i2400mu_notification_release()
*
* i2400mu_resume()
* i2400mu_notification_setup()
*
* i2400mu_bus_dev_start() Called by i2400m_dev_start() [who is
* i2400mu_tx_setup() called by i2400m_setup()]
* i2400mu_rx_setup()
* i2400mu_notification_setup()
*
* i2400mu_bus_dev_stop() Called by i2400m_dev_stop() [who is
* i2400mu_notification_release() called by i2400m_release()]
* i2400mu_rx_release()
* i2400mu_tx_release()
*
* i2400mu_bus_reset() Called by i2400m->bus_reset
* __i2400mu_reset()
* __i2400mu_send_barker()
* usb_reset_device()
*/
#include "i2400m-usb.h"
#include <linux/wimax/i2400m.h>
#include <linux/debugfs.h>
#define D_SUBMODULE usb
#include "usb-debug-levels.h"
/* Our firmware file name */
static const char *i2400mu_bus_fw_names[] = {
#define I2400MU_FW_FILE_NAME_v1_4 "i2400m-fw-usb-1.4.sbcf"
I2400MU_FW_FILE_NAME_v1_4,
#define I2400MU_FW_FILE_NAME_v1_3 "i2400m-fw-usb-1.3.sbcf"
I2400MU_FW_FILE_NAME_v1_3,
NULL,
};
static
int i2400mu_bus_dev_start(struct i2400m *i2400m)
{
int result;
struct i2400mu *i2400mu = container_of(i2400m, struct i2400mu, i2400m);
struct device *dev = &i2400mu->usb_iface->dev;
d_fnstart(3, dev, "(i2400m %p)\n", i2400m);
result = i2400mu_tx_setup(i2400mu);
if (result < 0)
goto error_usb_tx_setup;
result = i2400mu_rx_setup(i2400mu);
if (result < 0)
goto error_usb_rx_setup;
result = i2400mu_notification_setup(i2400mu);
if (result < 0)
goto error_notif_setup;
d_fnend(3, dev, "(i2400m %p) = %d\n", i2400m, result);
return result;
error_notif_setup:
i2400mu_rx_release(i2400mu);
error_usb_rx_setup:
i2400mu_tx_release(i2400mu);
error_usb_tx_setup:
d_fnend(3, dev, "(i2400m %p) = void\n", i2400m);
return result;
}
static
void i2400mu_bus_dev_stop(struct i2400m *i2400m)
{
struct i2400mu *i2400mu = container_of(i2400m, struct i2400mu, i2400m);
struct device *dev = &i2400mu->usb_iface->dev;
d_fnstart(3, dev, "(i2400m %p)\n", i2400m);
i2400mu_notification_release(i2400mu);
i2400mu_rx_release(i2400mu);
i2400mu_tx_release(i2400mu);
d_fnend(3, dev, "(i2400m %p) = void\n", i2400m);
}
/*
* Sends a barker buffer to the device
*
* This helper will allocate a kmalloced buffer and use it to transmit
* (then free it). Reason for this is that other arches cannot use
* stack/vmalloc/text areas for DMA transfers.
*
* Error recovery here is simpler: anything is considered a hard error
* and will move the reset code to use a last-resort bus-based reset.
*/
static
int __i2400mu_send_barker(struct i2400mu *i2400mu,
const __le32 *barker,
size_t barker_size,
unsigned endpoint)
{
struct usb_endpoint_descriptor *epd = NULL;
int pipe, actual_len, ret;
struct device *dev = &i2400mu->usb_iface->dev;
void *buffer;
int do_autopm = 1;
ret = usb_autopm_get_interface(i2400mu->usb_iface);
if (ret < 0) {
dev_err(dev, "RESET: can't get autopm: %d\n", ret);
do_autopm = 0;
}
ret = -ENOMEM;
buffer = kmalloc(barker_size, GFP_KERNEL);
if (buffer == NULL)
goto error_kzalloc;
epd = usb_get_epd(i2400mu->usb_iface, endpoint);
pipe = usb_sndbulkpipe(i2400mu->usb_dev, epd->bEndpointAddress);
memcpy(buffer, barker, barker_size);
ret = usb_bulk_msg(i2400mu->usb_dev, pipe, buffer, barker_size,
&actual_len, HZ);
if (ret < 0) {
if (ret != -EINVAL)
dev_err(dev, "E: barker error: %d\n", ret);
} else if (actual_len != barker_size) {
dev_err(dev, "E: only %d bytes transmitted\n", actual_len);
ret = -EIO;
}
kfree(buffer);
error_kzalloc:
if (do_autopm)
usb_autopm_put_interface(i2400mu->usb_iface);
return ret;
}
/*
* Reset a device at different levels (warm, cold or bus)
*
* @i2400m: device descriptor
* @reset_type: soft, warm or bus reset (I2400M_RT_WARM/SOFT/BUS)
*
* Warm and cold resets get a USB reset if they fail.
*
* Warm reset:
*
* The device will be fully reset internally, but won't be
* disconnected from the USB bus (so no reenumeration will
* happen). Firmware upload will be neccessary.
*
* The device will send a reboot barker in the notification endpoint
* that will trigger the driver to reinitialize the state
* automatically from notif.c:i2400m_notification_grok() into
* i2400m_dev_bootstrap_delayed().
*
* Cold and bus (USB) reset:
*
* The device will be fully reset internally, disconnected from the
* USB bus an a reenumeration will happen. Firmware upload will be
* neccessary. Thus, we don't do any locking or struct
* reinitialization, as we are going to be fully disconnected and
* reenumerated.
*
* Note we need to return -ENODEV if a warm reset was requested and we
* had to resort to a bus reset. See i2400m_op_reset(), wimax_reset()
* and wimax_dev->op_reset.
*
* WARNING: no driver state saved/fixed
*/
static
int i2400mu_bus_reset(struct i2400m *i2400m, enum i2400m_reset_type rt)
{
int result;
struct i2400mu *i2400mu =
container_of(i2400m, struct i2400mu, i2400m);
struct device *dev = i2400m_dev(i2400m);
static const __le32 i2400m_WARM_BOOT_BARKER[4] = {
cpu_to_le32(I2400M_WARM_RESET_BARKER),
cpu_to_le32(I2400M_WARM_RESET_BARKER),
cpu_to_le32(I2400M_WARM_RESET_BARKER),
cpu_to_le32(I2400M_WARM_RESET_BARKER),
};
static const __le32 i2400m_COLD_BOOT_BARKER[4] = {
cpu_to_le32(I2400M_COLD_RESET_BARKER),
cpu_to_le32(I2400M_COLD_RESET_BARKER),
cpu_to_le32(I2400M_COLD_RESET_BARKER),
cpu_to_le32(I2400M_COLD_RESET_BARKER),
};
d_fnstart(3, dev, "(i2400m %p rt %u)\n", i2400m, rt);
if (rt == I2400M_RT_WARM)
result = __i2400mu_send_barker(i2400mu, i2400m_WARM_BOOT_BARKER,
sizeof(i2400m_WARM_BOOT_BARKER),
I2400MU_EP_BULK_OUT);
else if (rt == I2400M_RT_COLD)
result = __i2400mu_send_barker(i2400mu, i2400m_COLD_BOOT_BARKER,
sizeof(i2400m_COLD_BOOT_BARKER),
I2400MU_EP_RESET_COLD);
else if (rt == I2400M_RT_BUS) {
do_bus_reset:
result = usb_reset_device(i2400mu->usb_dev);
switch (result) {
case 0:
case -EINVAL: /* device is gone */
case -ENODEV:
case -ENOENT:
case -ESHUTDOWN:
result = rt == I2400M_RT_WARM ? -ENODEV : 0;
break; /* We assume the device is disconnected */
default:
dev_err(dev, "USB reset failed (%d), giving up!\n",
result);
}
} else {
result = -EINVAL; /* shut gcc up in certain arches */
BUG();
}
if (result < 0
&& result != -EINVAL /* device is gone */
&& rt != I2400M_RT_BUS) {
dev_err(dev, "%s reset failed (%d); trying USB reset\n",
rt == I2400M_RT_WARM ? "warm" : "cold", result);
rt = I2400M_RT_BUS;
goto do_bus_reset;
}
d_fnend(3, dev, "(i2400m %p rt %u) = %d\n", i2400m, rt, result);
return result;
}
static
void i2400mu_netdev_setup(struct net_device *net_dev)
{
struct i2400m *i2400m = net_dev_to_i2400m(net_dev);
struct i2400mu *i2400mu = container_of(i2400m, struct i2400mu, i2400m);
i2400mu_init(i2400mu);
i2400m_netdev_setup(net_dev);
}
/*
* Debug levels control; see debug.h
*/
struct d_level D_LEVEL[] = {
D_SUBMODULE_DEFINE(usb),
D_SUBMODULE_DEFINE(fw),
D_SUBMODULE_DEFINE(notif),
D_SUBMODULE_DEFINE(rx),
D_SUBMODULE_DEFINE(tx),
};
size_t D_LEVEL_SIZE = ARRAY_SIZE(D_LEVEL);
#define __debugfs_register(prefix, name, parent) \
do { \
result = d_level_register_debugfs(prefix, name, parent); \
if (result < 0) \
goto error; \
} while (0)
static
int i2400mu_debugfs_add(struct i2400mu *i2400mu)
{
int result;
struct device *dev = &i2400mu->usb_iface->dev;
struct dentry *dentry = i2400mu->i2400m.wimax_dev.debugfs_dentry;
struct dentry *fd;
dentry = debugfs_create_dir("i2400m-usb", dentry);
result = PTR_ERR(dentry);
if (IS_ERR(dentry)) {
if (result == -ENODEV)
result = 0; /* No debugfs support */
goto error;
}
i2400mu->debugfs_dentry = dentry;
__debugfs_register("dl_", usb, dentry);
__debugfs_register("dl_", fw, dentry);
__debugfs_register("dl_", notif, dentry);
__debugfs_register("dl_", rx, dentry);
__debugfs_register("dl_", tx, dentry);
/* Don't touch these if you don't know what you are doing */
fd = debugfs_create_u8("rx_size_auto_shrink", 0600, dentry,
&i2400mu->rx_size_auto_shrink);
result = PTR_ERR(fd);
if (IS_ERR(fd) && result != -ENODEV) {
dev_err(dev, "Can't create debugfs entry "
"rx_size_auto_shrink: %d\n", result);
goto error;
}
fd = debugfs_create_size_t("rx_size", 0600, dentry,
&i2400mu->rx_size);
result = PTR_ERR(fd);
if (IS_ERR(fd) && result != -ENODEV) {
dev_err(dev, "Can't create debugfs entry "
"rx_size: %d\n", result);
goto error;
}
return 0;
error:
debugfs_remove_recursive(i2400mu->debugfs_dentry);
return result;
}
static struct device_type i2400mu_type = {
.name = "wimax",
};
/*
* Probe a i2400m interface and register it
*
* @iface: USB interface to link to
* @id: USB class/subclass/protocol id
* @returns: 0 if ok, < 0 errno code on error.
*
* Alloc a net device, initialize the bus-specific details and then
* calls the bus-generic initialization routine. That will register
* the wimax and netdev devices, upload the firmware [using
* _bus_bm_*()], call _bus_dev_start() to finalize the setup of the
* communication with the device and then will start to talk to it to
* finnish setting it up.
*/
static
int i2400mu_probe(struct usb_interface *iface,
const struct usb_device_id *id)
{
int result;
struct net_device *net_dev;
struct device *dev = &iface->dev;
struct i2400m *i2400m;
struct i2400mu *i2400mu;
struct usb_device *usb_dev = interface_to_usbdev(iface);
if (usb_dev->speed != USB_SPEED_HIGH)
dev_err(dev, "device not connected as high speed\n");
/* Allocate instance [calls i2400m_netdev_setup() on it]. */
result = -ENOMEM;
net_dev = alloc_netdev(sizeof(*i2400mu), "wmx%d",
i2400mu_netdev_setup);
if (net_dev == NULL) {
dev_err(dev, "no memory for network device instance\n");
goto error_alloc_netdev;
}
SET_NETDEV_DEV(net_dev, dev);
SET_NETDEV_DEVTYPE(net_dev, &i2400mu_type);
i2400m = net_dev_to_i2400m(net_dev);
i2400mu = container_of(i2400m, struct i2400mu, i2400m);
i2400m->wimax_dev.net_dev = net_dev;
i2400mu->usb_dev = usb_get_dev(usb_dev);
i2400mu->usb_iface = iface;
usb_set_intfdata(iface, i2400mu);
i2400m->bus_tx_block_size = I2400MU_BLK_SIZE;
i2400m->bus_pl_size_max = I2400MU_PL_SIZE_MAX;
i2400m->bus_dev_start = i2400mu_bus_dev_start;
i2400m->bus_dev_stop = i2400mu_bus_dev_stop;
i2400m->bus_tx_kick = i2400mu_bus_tx_kick;
i2400m->bus_reset = i2400mu_bus_reset;
i2400m->bus_bm_retries = I2400M_BOOT_RETRIES;
i2400m->bus_bm_cmd_send = i2400mu_bus_bm_cmd_send;
i2400m->bus_bm_wait_for_ack = i2400mu_bus_bm_wait_for_ack;
i2400m->bus_fw_names = i2400mu_bus_fw_names;
i2400m->bus_bm_mac_addr_impaired = 0;
#ifdef CONFIG_PM
iface->needs_remote_wakeup = 1; /* autosuspend (15s delay) */
device_init_wakeup(dev, 1);
usb_autopm_enable(i2400mu->usb_iface);
usb_dev->autosuspend_delay = 15 * HZ;
usb_dev->autosuspend_disabled = 0;
#endif
result = i2400m_setup(i2400m, I2400M_BRI_MAC_REINIT);
if (result < 0) {
dev_err(dev, "cannot setup device: %d\n", result);
goto error_setup;
}
result = i2400mu_debugfs_add(i2400mu);
if (result < 0) {
dev_err(dev, "Can't register i2400mu's debugfs: %d\n", result);
goto error_debugfs_add;
}
return 0;
error_debugfs_add:
i2400m_release(i2400m);
error_setup:
usb_set_intfdata(iface, NULL);
usb_put_dev(i2400mu->usb_dev);
free_netdev(net_dev);
error_alloc_netdev:
return result;
}
/*
* Disconect a i2400m from the system.
*
* i2400m_stop() has been called before, so al the rx and tx contexts
* have been taken down already. Make sure the queue is stopped,
* unregister netdev and i2400m, free and kill.
*/
static
void i2400mu_disconnect(struct usb_interface *iface)
{
struct i2400mu *i2400mu = usb_get_intfdata(iface);
struct i2400m *i2400m = &i2400mu->i2400m;
struct net_device *net_dev = i2400m->wimax_dev.net_dev;
struct device *dev = &iface->dev;
d_fnstart(3, dev, "(iface %p i2400m %p)\n", iface, i2400m);
debugfs_remove_recursive(i2400mu->debugfs_dentry);
i2400m_release(i2400m);
usb_set_intfdata(iface, NULL);
usb_put_dev(i2400mu->usb_dev);
free_netdev(net_dev);
d_fnend(3, dev, "(iface %p i2400m %p) = void\n", iface, i2400m);
}
/*
* Get the device ready for USB port or system standby and hibernation
*
* USB port and system standby are handled the same.
*
* When the system hibernates, the USB device is powered down and then
* up, so we don't really have to do much here, as it will be seen as
* a reconnect. Still for simplicity we consider this case the same as
* suspend, so that the device has a chance to do notify the base
* station (if connected).
*
* So at the end, the three cases require common handling.
*
* If at the time of this call the device's firmware is not loaded,
* nothing has to be done.
*
* If the firmware is loaded, we need to:
*
* - tell the device to go into host interface power save mode, wait
* for it to ack
*
* This is quite more interesting than it is; we need to execute a
* command, but this time, we don't want the code in usb-{tx,rx}.c
* to call the usb_autopm_get/put_interface() barriers as it'd
* deadlock, so we need to decrement i2400mu->do_autopm, that acts
* as a poor man's semaphore. Ugly, but it works.
*
* As well, the device might refuse going to sleep for whichever
* reason. In this case we just fail. For system suspend/hibernate,
* we *can't* fail. We look at usb_dev->auto_pm to see if the
* suspend call comes from the USB stack or from the system and act
* in consequence.
*
* - stop the notification endpoint polling
*/
static
int i2400mu_suspend(struct usb_interface *iface, pm_message_t pm_msg)
{
int result = 0;
struct device *dev = &iface->dev;
struct i2400mu *i2400mu = usb_get_intfdata(iface);
#ifdef CONFIG_PM
struct usb_device *usb_dev = i2400mu->usb_dev;
#endif
unsigned is_autosuspend = 0;
struct i2400m *i2400m = &i2400mu->i2400m;
#ifdef CONFIG_PM
if (usb_dev->auto_pm > 0)
is_autosuspend = 1;
#endif
d_fnstart(3, dev, "(iface %p pm_msg %u)\n", iface, pm_msg.event);
if (i2400m->updown == 0)
goto no_firmware;
if (i2400m->state == I2400M_SS_DATA_PATH_CONNECTED && is_autosuspend) {
/* ugh -- the device is connected and this suspend
* request is an autosuspend one (not a system standby
* / hibernate).
*
* The only way the device can go to standby is if the
* link with the base station is in IDLE mode; that
* were the case, we'd be in status
* I2400M_SS_CONNECTED_IDLE. But we are not.
*
* If we *tell* him to go power save now, it'll reset
* as a precautionary measure, so if this is an
* autosuspend thing, say no and it'll come back
* later, when the link is IDLE
*/
result = -EBADF;
d_printf(1, dev, "fw up, link up, not-idle, autosuspend: "
"not entering powersave\n");
goto error_not_now;
}
d_printf(1, dev, "fw up: entering powersave\n");
atomic_dec(&i2400mu->do_autopm);
result = i2400m_cmd_enter_powersave(i2400m);
atomic_inc(&i2400mu->do_autopm);
if (result < 0 && !is_autosuspend) {
/* System suspend, can't fail */
dev_err(dev, "failed to suspend, will reset on resume\n");
result = 0;
}
if (result < 0)
goto error_enter_powersave;
i2400mu_notification_release(i2400mu);
d_printf(1, dev, "powersave requested\n");
error_enter_powersave:
error_not_now:
no_firmware:
d_fnend(3, dev, "(iface %p pm_msg %u) = %d\n",
iface, pm_msg.event, result);
return result;
}
static
int i2400mu_resume(struct usb_interface *iface)
{
int ret = 0;
struct device *dev = &iface->dev;
struct i2400mu *i2400mu = usb_get_intfdata(iface);
struct i2400m *i2400m = &i2400mu->i2400m;
d_fnstart(3, dev, "(iface %p)\n", iface);
if (i2400m->updown == 0) {
d_printf(1, dev, "fw was down, no resume neeed\n");
goto out;
}
d_printf(1, dev, "fw was up, resuming\n");
i2400mu_notification_setup(i2400mu);
/* USB has flow control, so we don't need to give it time to
* come back; otherwise, we'd use something like a get-state
* command... */
out:
d_fnend(3, dev, "(iface %p) = %d\n", iface, ret);
return ret;
}
static
struct usb_device_id i2400mu_id_table[] = {
{ USB_DEVICE(0x8086, 0x0181) },
{ USB_DEVICE(0x8086, 0x1403) },
{ USB_DEVICE(0x8086, 0x1405) },
{ USB_DEVICE(0x8086, 0x0180) },
{ USB_DEVICE(0x8086, 0x0182) },
{ USB_DEVICE(0x8086, 0x1406) },
{ USB_DEVICE(0x8086, 0x1403) },
{ },
};
MODULE_DEVICE_TABLE(usb, i2400mu_id_table);
static
struct usb_driver i2400mu_driver = {
.name = KBUILD_MODNAME,
.suspend = i2400mu_suspend,
.resume = i2400mu_resume,
.probe = i2400mu_probe,
.disconnect = i2400mu_disconnect,
.id_table = i2400mu_id_table,
.supports_autosuspend = 1,
};
static
int __init i2400mu_driver_init(void)
{
return usb_register(&i2400mu_driver);
}
module_init(i2400mu_driver_init);
static
void __exit i2400mu_driver_exit(void)
{
flush_scheduled_work(); /* for the stuff we schedule from sysfs.c */
usb_deregister(&i2400mu_driver);
}
module_exit(i2400mu_driver_exit);
MODULE_AUTHOR("Intel Corporation <linux-wimax@intel.com>");
MODULE_DESCRIPTION("Intel 2400M WiMAX networking for USB");
MODULE_LICENSE("GPL");
MODULE_FIRMWARE(I2400MU_FW_FILE_NAME_v1_4);
MODULE_FIRMWARE(I2400MU_FW_FILE_NAME_v1_3);