summaryrefslogtreecommitdiffstats
path: root/drivers/net/wimax/i2400m
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/net/wimax/i2400m')
-rw-r--r--drivers/net/wimax/i2400m/control.c124
-rw-r--r--drivers/net/wimax/i2400m/driver.c45
-rw-r--r--drivers/net/wimax/i2400m/fw.c58
-rw-r--r--drivers/net/wimax/i2400m/i2400m-sdio.h9
-rw-r--r--drivers/net/wimax/i2400m/i2400m.h50
-rw-r--r--drivers/net/wimax/i2400m/netdev.c4
-rw-r--r--drivers/net/wimax/i2400m/op-rfkill.c4
-rw-r--r--drivers/net/wimax/i2400m/rx.c15
-rw-r--r--drivers/net/wimax/i2400m/sdio-fw.c109
-rw-r--r--drivers/net/wimax/i2400m/sdio-rx.c47
-rw-r--r--drivers/net/wimax/i2400m/sdio.c68
-rw-r--r--drivers/net/wimax/i2400m/tx.c75
-rw-r--r--drivers/net/wimax/i2400m/usb.c40
13 files changed, 429 insertions, 219 deletions
diff --git a/drivers/net/wimax/i2400m/control.c b/drivers/net/wimax/i2400m/control.c
index b3cadb626fe0..07308686dbcf 100644
--- a/drivers/net/wimax/i2400m/control.c
+++ b/drivers/net/wimax/i2400m/control.c
@@ -292,8 +292,6 @@ void i2400m_report_tlv_system_state(struct i2400m *i2400m,
d_fnstart(3, dev, "(i2400m %p ss %p [%u])\n", i2400m, ss, i2400m_state);
- if (unlikely(i2400m->ready == 0)) /* act if up */
- goto out;
if (i2400m->state != i2400m_state) {
i2400m->state = i2400m_state;
wake_up_all(&i2400m->state_wq);
@@ -341,7 +339,6 @@ void i2400m_report_tlv_system_state(struct i2400m *i2400m,
i2400m->bus_reset(i2400m, I2400M_RT_WARM);
break;
};
-out:
d_fnend(3, dev, "(i2400m %p ss %p [%u]) = void\n",
i2400m, ss, i2400m_state);
}
@@ -372,8 +369,6 @@ void i2400m_report_tlv_media_status(struct i2400m *i2400m,
d_fnstart(3, dev, "(i2400m %p ms %p [%u])\n", i2400m, ms, status);
- if (unlikely(i2400m->ready == 0)) /* act if up */
- goto out;
switch (status) {
case I2400M_MEDIA_STATUS_LINK_UP:
netif_carrier_on(net_dev);
@@ -393,14 +388,59 @@ void i2400m_report_tlv_media_status(struct i2400m *i2400m,
dev_err(dev, "HW BUG? unknown media status %u\n",
status);
};
-out:
d_fnend(3, dev, "(i2400m %p ms %p [%u]) = void\n",
i2400m, ms, status);
}
/*
- * Parse a 'state report' and extract carrier on/off information
+ * Process a TLV from a 'state report'
+ *
+ * @i2400m: device descriptor
+ * @tlv: pointer to the TLV header; it has been already validated for
+ * consistent size.
+ * @tag: for error messages
+ *
+ * Act on the TLVs from a 'state report'.
+ */
+static
+void i2400m_report_state_parse_tlv(struct i2400m *i2400m,
+ const struct i2400m_tlv_hdr *tlv,
+ const char *tag)
+{
+ struct device *dev = i2400m_dev(i2400m);
+ const struct i2400m_tlv_media_status *ms;
+ const struct i2400m_tlv_system_state *ss;
+ const struct i2400m_tlv_rf_switches_status *rfss;
+
+ if (0 == i2400m_tlv_match(tlv, I2400M_TLV_SYSTEM_STATE, sizeof(*ss))) {
+ ss = container_of(tlv, typeof(*ss), hdr);
+ d_printf(2, dev, "%s: system state TLV "
+ "found (0x%04x), state 0x%08x\n",
+ tag, I2400M_TLV_SYSTEM_STATE,
+ le32_to_cpu(ss->state));
+ i2400m_report_tlv_system_state(i2400m, ss);
+ }
+ if (0 == i2400m_tlv_match(tlv, I2400M_TLV_RF_STATUS, sizeof(*rfss))) {
+ rfss = container_of(tlv, typeof(*rfss), hdr);
+ d_printf(2, dev, "%s: RF status TLV "
+ "found (0x%04x), sw 0x%02x hw 0x%02x\n",
+ tag, I2400M_TLV_RF_STATUS,
+ le32_to_cpu(rfss->sw_rf_switch),
+ le32_to_cpu(rfss->hw_rf_switch));
+ i2400m_report_tlv_rf_switches_status(i2400m, rfss);
+ }
+ if (0 == i2400m_tlv_match(tlv, I2400M_TLV_MEDIA_STATUS, sizeof(*ms))) {
+ ms = container_of(tlv, typeof(*ms), hdr);
+ d_printf(2, dev, "%s: Media Status TLV: %u\n",
+ tag, le32_to_cpu(ms->media_status));
+ i2400m_report_tlv_media_status(i2400m, ms);
+ }
+}
+
+
+/*
+ * Parse a 'state report' and extract information
*
* @i2400m: device descriptor
* @l3l4_hdr: pointer to message; it has been already validated for
@@ -409,13 +449,7 @@ out:
* declaration is assumed to be congruent with @size (as in
* sizeof(*l3l4_hdr) + l3l4_hdr->length == size)
*
- * Extract from the report state the system state TLV and infer from
- * there if we have a carrier or not. Update our local state and tell
- * netdev.
- *
- * When setting the carrier, it's fine to set OFF twice (for example),
- * as netif_carrier_off() will not generate two OFF events (just on
- * the transitions).
+ * Walk over the TLVs in a report state and act on them.
*/
static
void i2400m_report_state_hook(struct i2400m *i2400m,
@@ -424,9 +458,6 @@ void i2400m_report_state_hook(struct i2400m *i2400m,
{
struct device *dev = i2400m_dev(i2400m);
const struct i2400m_tlv_hdr *tlv;
- const struct i2400m_tlv_system_state *ss;
- const struct i2400m_tlv_rf_switches_status *rfss;
- const struct i2400m_tlv_media_status *ms;
size_t tlv_size = le16_to_cpu(l3l4_hdr->length);
d_fnstart(4, dev, "(i2400m %p, l3l4_hdr %p, size %zu, %s)\n",
@@ -434,34 +465,8 @@ void i2400m_report_state_hook(struct i2400m *i2400m,
tlv = NULL;
while ((tlv = i2400m_tlv_buffer_walk(i2400m, &l3l4_hdr->pl,
- tlv_size, tlv))) {
- if (0 == i2400m_tlv_match(tlv, I2400M_TLV_SYSTEM_STATE,
- sizeof(*ss))) {
- ss = container_of(tlv, typeof(*ss), hdr);
- d_printf(2, dev, "%s: system state TLV "
- "found (0x%04x), state 0x%08x\n",
- tag, I2400M_TLV_SYSTEM_STATE,
- le32_to_cpu(ss->state));
- i2400m_report_tlv_system_state(i2400m, ss);
- }
- if (0 == i2400m_tlv_match(tlv, I2400M_TLV_RF_STATUS,
- sizeof(*rfss))) {
- rfss = container_of(tlv, typeof(*rfss), hdr);
- d_printf(2, dev, "%s: RF status TLV "
- "found (0x%04x), sw 0x%02x hw 0x%02x\n",
- tag, I2400M_TLV_RF_STATUS,
- le32_to_cpu(rfss->sw_rf_switch),
- le32_to_cpu(rfss->hw_rf_switch));
- i2400m_report_tlv_rf_switches_status(i2400m, rfss);
- }
- if (0 == i2400m_tlv_match(tlv, I2400M_TLV_MEDIA_STATUS,
- sizeof(*ms))) {
- ms = container_of(tlv, typeof(*ms), hdr);
- d_printf(2, dev, "%s: Media Status TLV: %u\n",
- tag, le32_to_cpu(ms->media_status));
- i2400m_report_tlv_media_status(i2400m, ms);
- }
- }
+ tlv_size, tlv)))
+ i2400m_report_state_parse_tlv(i2400m, tlv, tag);
d_fnend(4, dev, "(i2400m %p, l3l4_hdr %p, size %zu, %s) = void\n",
i2400m, l3l4_hdr, size, tag);
}
@@ -500,8 +505,15 @@ void i2400m_report_hook(struct i2400m *i2400m,
* it. */
case I2400M_MT_REPORT_POWERSAVE_READY: /* zzzzz */
if (l3l4_hdr->status == cpu_to_le16(I2400M_MS_DONE_OK)) {
- d_printf(1, dev, "ready for powersave, requesting\n");
- i2400m_cmd_enter_powersave(i2400m);
+ if (i2400m_power_save_disabled)
+ d_printf(1, dev, "ready for powersave, "
+ "not requesting (disabled by module "
+ "parameter)\n");
+ else {
+ d_printf(1, dev, "ready for powersave, "
+ "requesting\n");
+ i2400m_cmd_enter_powersave(i2400m);
+ }
}
break;
};
@@ -683,8 +695,9 @@ struct sk_buff *i2400m_msg_to_dev(struct i2400m *i2400m,
d_fnstart(3, dev, "(i2400m %p buf %p len %zu)\n",
i2400m, buf, buf_len);
+ rmb(); /* Make sure we see what i2400m_dev_reset_handle() */
if (i2400m->boot_mode)
- return ERR_PTR(-ENODEV);
+ return ERR_PTR(-EL3RST);
msg_l3l4_hdr = buf;
/* Check msg & payload consistency */
@@ -721,6 +734,8 @@ struct sk_buff *i2400m_msg_to_dev(struct i2400m *i2400m,
ack_timeout = HZ;
};
+ if (unlikely(i2400m->trace_msg_from_user))
+ wimax_msg(&i2400m->wimax_dev, "echo", buf, buf_len, GFP_KERNEL);
/* The RX path in rx.c will put any response for this message
* in i2400m->ack_skb and wake us up. If we cancel the wait,
* we need to change the value of i2400m->ack_skb to something
@@ -755,6 +770,9 @@ struct sk_buff *i2400m_msg_to_dev(struct i2400m *i2400m,
ack_l3l4_hdr = wimax_msg_data_len(ack_skb, &ack_len);
/* Check the ack and deliver it if it is ok */
+ if (unlikely(i2400m->trace_msg_from_user))
+ wimax_msg(&i2400m->wimax_dev, "echo",
+ ack_l3l4_hdr, ack_len, GFP_KERNEL);
result = i2400m_msg_size_check(i2400m, ack_l3l4_hdr, ack_len);
if (result < 0) {
dev_err(dev, "HW BUG? reply to message 0x%04x: %d\n",
@@ -1379,16 +1397,16 @@ error:
*
* @i2400m: device descriptor
*
- * Gracefully stops the device, moving it to the lowest power
- * consumption state possible.
+ * Release resources acquired during the running of the device; in
+ * theory, should also tell the device to go to sleep, switch off the
+ * radio, all that, but at this point, in most cases (driver
+ * disconnection, reset handling) we can't even talk to the device.
*/
void i2400m_dev_shutdown(struct i2400m *i2400m)
{
- int result = -ENODEV;
struct device *dev = i2400m_dev(i2400m);
d_fnstart(3, dev, "(i2400m %p)\n", i2400m);
- result = i2400m->bus_reset(i2400m, I2400M_RT_WARM);
- d_fnend(3, dev, "(i2400m %p) = void [%d]\n", i2400m, result);
+ d_fnend(3, dev, "(i2400m %p) = void\n", i2400m);
return;
}
diff --git a/drivers/net/wimax/i2400m/driver.c b/drivers/net/wimax/i2400m/driver.c
index 07a54bad237b..304f0443ca4b 100644
--- a/drivers/net/wimax/i2400m/driver.c
+++ b/drivers/net/wimax/i2400m/driver.c
@@ -62,6 +62,7 @@
* unregister_netdev()
*/
#include "i2400m.h"
+#include <linux/etherdevice.h>
#include <linux/wimax/i2400m.h>
#include <linux/module.h>
#include <linux/moduleparam.h>
@@ -81,6 +82,14 @@ 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
*
@@ -171,7 +180,6 @@ int i2400m_schedule_work(struct i2400m *i2400m,
int result;
struct i2400m_work *iw;
- BUG_ON(i2400m->work_queue == NULL);
result = -ENOMEM;
iw = kzalloc(sizeof(*iw), gfp_flags);
if (iw == NULL)
@@ -234,9 +242,6 @@ int i2400m_op_msg_from_user(struct wimax_dev *wimax_dev,
result = PTR_ERR(ack_skb);
if (IS_ERR(ack_skb))
goto error_msg_to_dev;
- if (unlikely(i2400m->trace_msg_from_user))
- wimax_msg(&i2400m->wimax_dev, "trace",
- msg_buf, msg_len, GFP_KERNEL);
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 "
@@ -379,6 +384,11 @@ 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).
*/
@@ -389,7 +399,7 @@ int __i2400m_dev_start(struct i2400m *i2400m, enum i2400m_bri flags)
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 = 3;
+ int times = i2400m->bus_bm_retries;
d_fnstart(3, dev, "(i2400m %p)\n", i2400m);
retry:
@@ -404,15 +414,15 @@ retry:
result = i2400m_rx_setup(i2400m);
if (result < 0)
goto error_rx_setup;
- result = i2400m->bus_dev_start(i2400m);
- if (result < 0)
- goto error_bus_dev_start;
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;
@@ -434,17 +444,17 @@ retry:
error_dev_initialize:
error_check_mac_addr:
error_fw_check:
- destroy_workqueue(i2400m->work_queue);
-error_create_workqueue:
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 == -ERESTARTSYS && times-- > 0) {
- flags = I2400M_BRI_SOFT;
+ 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",
@@ -473,7 +483,9 @@ int i2400m_dev_start(struct i2400m *i2400m, enum i2400m_bri bm_flags)
*
* Returns: 0 if ok, < 0 errno code on error.
*
- * Releases all the resources allocated to communicate with the device.
+ * 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)
@@ -485,8 +497,8 @@ void __i2400m_dev_stop(struct i2400m *i2400m)
wimax_state_change(wimax_dev, __WIMAX_ST_QUIESCING);
i2400m_dev_shutdown(i2400m);
i2400m->ready = 0;
- destroy_workqueue(i2400m->work_queue);
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);
@@ -548,7 +560,7 @@ void __i2400m_dev_reset_handle(struct work_struct *ws)
* 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, -ERESTARTSYS);
+ i2400m_msg_to_dev_cancel_wait(i2400m, -EL3RST);
complete(&i2400m->msg_completion);
goto out;
}
@@ -598,6 +610,8 @@ out:
*/
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);
}
@@ -650,6 +664,7 @@ int i2400m_setup(struct i2400m *i2400m, enum i2400m_bri bm_flags)
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) {
diff --git a/drivers/net/wimax/i2400m/fw.c b/drivers/net/wimax/i2400m/fw.c
index 675c6ce810c0..e81750e54452 100644
--- a/drivers/net/wimax/i2400m/fw.c
+++ b/drivers/net/wimax/i2400m/fw.c
@@ -397,7 +397,7 @@ static int i2400m_download_chunk(struct i2400m *i2400m, const void *chunk,
unsigned int direct, unsigned int do_csum)
{
int ret;
- size_t chunk_len = ALIGN(__chunk_len, I2400M_PL_PAD);
+ size_t chunk_len = ALIGN(__chunk_len, I2400M_PL_ALIGN);
struct device *dev = i2400m_dev(i2400m);
struct {
struct i2400m_bootrom_header cmd;
@@ -532,14 +532,14 @@ int i2400m_dnload_finalize(struct i2400m *i2400m,
cmd = (void *) bcf + offset;
if (i2400m->sboot == 0) {
struct i2400m_bootrom_header jump_ack;
- d_printf(3, dev, "unsecure boot, jumping to 0x%08x\n",
+ d_printf(1, dev, "unsecure boot, jumping to 0x%08x\n",
le32_to_cpu(cmd->target_addr));
i2400m_brh_set_opcode(cmd, I2400M_BRH_JUMP);
cmd->data_size = 0;
ret = i2400m_bm_cmd(i2400m, cmd, sizeof(*cmd),
&jump_ack, sizeof(jump_ack), 0);
} else {
- d_printf(3, dev, "secure boot, jumping to 0x%08x\n",
+ d_printf(1, dev, "secure boot, jumping to 0x%08x\n",
le32_to_cpu(cmd->target_addr));
cmd_buf = i2400m->bm_cmd_buf;
memcpy(&cmd_buf->cmd, cmd, sizeof(*cmd));
@@ -696,8 +696,7 @@ error_dev_gone:
return result;
error_timeout:
- dev_err(dev, "Timed out waiting for reboot ack, resetting\n");
- i2400m->bus_reset(i2400m, I2400M_RT_BUS);
+ dev_err(dev, "Timed out waiting for reboot ack\n");
result = -ETIMEDOUT;
goto exit_timeout;
}
@@ -770,40 +769,21 @@ error_read_mac:
static
int i2400m_dnload_init_nonsigned(struct i2400m *i2400m)
{
-#define POKE(a, d) { \
- .address = cpu_to_le32(a), \
- .data = cpu_to_le32(d) \
-}
- static const struct {
- __le32 address;
- __le32 data;
- } i2400m_pokes[] = {
- POKE(0x081A58, 0xA7810230),
- POKE(0x080040, 0x00000000),
- POKE(0x080048, 0x00000082),
- POKE(0x08004C, 0x0000081F),
- POKE(0x080054, 0x00000085),
- POKE(0x080058, 0x00000180),
- POKE(0x08005C, 0x00000018),
- POKE(0x080060, 0x00000010),
- POKE(0x080574, 0x00000001),
- POKE(0x080550, 0x00000005),
- POKE(0xAE0000, 0x00000000),
- };
-#undef POKE
- unsigned i;
- int ret;
+ unsigned i = 0;
+ int ret = 0;
struct device *dev = i2400m_dev(i2400m);
-
- dev_warn(dev, "WARNING!!! non-signed boot UNTESTED PATH!\n");
-
d_fnstart(5, dev, "(i2400m %p)\n", i2400m);
- for (i = 0; i < ARRAY_SIZE(i2400m_pokes); i++) {
- ret = i2400m_download_chunk(i2400m, &i2400m_pokes[i].data,
- sizeof(i2400m_pokes[i].data),
- i2400m_pokes[i].address, 1, 1);
- if (ret < 0)
- break;
+ if (i2400m->bus_bm_pokes_table) {
+ while (i2400m->bus_bm_pokes_table[i].address) {
+ ret = i2400m_download_chunk(
+ i2400m,
+ &i2400m->bus_bm_pokes_table[i].data,
+ sizeof(i2400m->bus_bm_pokes_table[i].data),
+ i2400m->bus_bm_pokes_table[i].address, 1, 1);
+ if (ret < 0)
+ break;
+ i++;
+ }
}
d_fnend(5, dev, "(i2400m %p) = %d\n", i2400m, ret);
return ret;
@@ -980,11 +960,12 @@ int i2400m_fw_dnload(struct i2400m *i2400m, const struct i2400m_bcf_hdr *bcf,
{
int ret = 0;
struct device *dev = i2400m_dev(i2400m);
- int count = I2400M_BOOT_RETRIES;
+ int count = i2400m->bus_bm_retries;
d_fnstart(5, dev, "(i2400m %p bcf %p size %zu)\n",
i2400m, bcf, bcf_size);
i2400m->boot_mode = 1;
+ wmb(); /* Make sure other readers see it */
hw_reboot:
if (count-- == 0) {
ret = -ERESTARTSYS;
@@ -1033,6 +1014,7 @@ hw_reboot:
d_printf(2, dev, "fw %s successfully uploaded\n",
i2400m->fw_name);
i2400m->boot_mode = 0;
+ wmb(); /* Make sure i2400m_msg_to_dev() sees boot_mode */
error_dnload_finalize:
error_dnload_bcf:
error_dnload_init:
diff --git a/drivers/net/wimax/i2400m/i2400m-sdio.h b/drivers/net/wimax/i2400m/i2400m-sdio.h
index 08c2fb739234..9c4e3189f7b5 100644
--- a/drivers/net/wimax/i2400m/i2400m-sdio.h
+++ b/drivers/net/wimax/i2400m/i2400m-sdio.h
@@ -78,6 +78,8 @@ enum {
/* 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,
};
@@ -105,6 +107,10 @@ struct i2400ms {
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;
};
@@ -129,4 +135,7 @@ extern ssize_t i2400ms_bus_bm_cmd_send(struct i2400m *,
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__ */
diff --git a/drivers/net/wimax/i2400m/i2400m.h b/drivers/net/wimax/i2400m/i2400m.h
index 3ae2df38b59a..60330f313f27 100644
--- a/drivers/net/wimax/i2400m/i2400m.h
+++ b/drivers/net/wimax/i2400m/i2400m.h
@@ -150,11 +150,33 @@
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
@@ -224,6 +246,17 @@ struct i2400m_roq;
* 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
@@ -252,6 +285,12 @@ struct i2400m_roq;
* 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
@@ -323,6 +362,10 @@ struct i2400m_roq;
* 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
@@ -389,12 +432,14 @@ struct i2400m {
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 debugfs/u8 can tweak */
+ /* 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 *);
@@ -406,6 +451,7 @@ 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;
@@ -421,6 +467,7 @@ struct i2400m {
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;
@@ -704,6 +751,7 @@ static const __le32 i2400m_SBOOT_BARKER[4] = {
cpu_to_le32(I2400M_SBOOT_BARKER)
};
+extern int i2400m_power_save_disabled;
/*
* Utility functions
diff --git a/drivers/net/wimax/i2400m/netdev.c b/drivers/net/wimax/i2400m/netdev.c
index 6b1fe7a81f25..9653f478b382 100644
--- a/drivers/net/wimax/i2400m/netdev.c
+++ b/drivers/net/wimax/i2400m/netdev.c
@@ -404,10 +404,12 @@ 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));
- memset(eth_hdr->h_source, 0, sizeof(eth_hdr->h_dest));
+ memcpy(eth_hdr->h_source, i2400m->src_mac_addr,
+ sizeof(eth_hdr->h_source));
eth_hdr->h_proto = protocol;
}
diff --git a/drivers/net/wimax/i2400m/op-rfkill.c b/drivers/net/wimax/i2400m/op-rfkill.c
index 487ec58cea46..43927b5d7ad6 100644
--- a/drivers/net/wimax/i2400m/op-rfkill.c
+++ b/drivers/net/wimax/i2400m/op-rfkill.c
@@ -54,8 +54,10 @@ int i2400m_radio_is(struct i2400m *i2400m, enum wimax_rf_state state)
/* state == WIMAX_RF_ON */
return i2400m->state != I2400M_SS_RF_OFF
&& i2400m->state != I2400M_SS_RF_SHUTDOWN;
- else
+ else {
BUG();
+ return -EINVAL; /* shut gcc warnings on certain arches */
+ }
}
diff --git a/drivers/net/wimax/i2400m/rx.c b/drivers/net/wimax/i2400m/rx.c
index 02419bfd64b5..07c32e68909f 100644
--- a/drivers/net/wimax/i2400m/rx.c
+++ b/drivers/net/wimax/i2400m/rx.c
@@ -177,7 +177,8 @@ void i2400m_report_hook_work(struct work_struct *ws)
struct i2400m_work *iw =
container_of(ws, struct i2400m_work, ws);
struct i2400m_report_hook_args *args = (void *) iw->pl;
- i2400m_report_hook(iw->i2400m, args->l3l4_hdr, args->size);
+ if (iw->i2400m->ready)
+ i2400m_report_hook(iw->i2400m, args->l3l4_hdr, args->size);
kfree_skb(args->skb_rx);
i2400m_put(iw->i2400m);
kfree(iw);
@@ -309,6 +310,9 @@ void i2400m_rx_ctl(struct i2400m *i2400m, struct sk_buff *skb_rx,
skb_get(skb_rx);
i2400m_queue_work(i2400m, i2400m_report_hook_work,
GFP_KERNEL, &args, sizeof(args));
+ if (unlikely(i2400m->trace_msg_from_user))
+ wimax_msg(&i2400m->wimax_dev, "echo",
+ l3l4_hdr, size, GFP_KERNEL);
result = wimax_msg(&i2400m->wimax_dev, NULL, l3l4_hdr, size,
GFP_KERNEL);
if (result < 0)
@@ -819,10 +823,9 @@ void i2400m_roq_queue_update_ws(struct i2400m *i2400m, struct i2400m_roq *roq,
roq_data = (struct i2400m_roq_data *) &skb->cb;
i2400m_net_erx(i2400m, skb, roq_data->cs);
}
- else {
+ else
__i2400m_roq_queue(i2400m, roq, skb, sn, nsn);
- __i2400m_roq_update_ws(i2400m, roq, sn + 1);
- }
+ __i2400m_roq_update_ws(i2400m, roq, sn + 1);
i2400m_roq_log_add(i2400m, roq, I2400M_RO_TYPE_PACKET_WS,
old_ws, len, sn, nsn, roq->ws);
}
@@ -1145,7 +1148,7 @@ int i2400m_rx(struct i2400m *i2400m, struct sk_buff *skb)
num_pls = le16_to_cpu(msg_hdr->num_pls);
pl_itr = sizeof(*msg_hdr) + /* Check payload descriptor(s) */
num_pls * sizeof(msg_hdr->pld[0]);
- pl_itr = ALIGN(pl_itr, I2400M_PL_PAD);
+ pl_itr = ALIGN(pl_itr, I2400M_PL_ALIGN);
if (pl_itr > skb->len) { /* got all the payload descriptors? */
dev_err(dev, "RX: HW BUG? message too short (%u bytes) for "
"%u payload descriptors (%zu each, total %zu)\n",
@@ -1163,7 +1166,7 @@ int i2400m_rx(struct i2400m *i2400m, struct sk_buff *skb)
single_last = num_pls == 1 || i == num_pls - 1;
i2400m_rx_payload(i2400m, skb, single_last, &msg_hdr->pld[i],
skb->data + pl_itr);
- pl_itr += ALIGN(pl_size, I2400M_PL_PAD);
+ pl_itr += ALIGN(pl_size, I2400M_PL_ALIGN);
cond_resched(); /* Don't monopolize */
}
kfree_skb(skb);
diff --git a/drivers/net/wimax/i2400m/sdio-fw.c b/drivers/net/wimax/i2400m/sdio-fw.c
index 3487205d8f50..7d6ec0f475f8 100644
--- a/drivers/net/wimax/i2400m/sdio-fw.c
+++ b/drivers/net/wimax/i2400m/sdio-fw.c
@@ -46,17 +46,24 @@
* 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. We do polled reads.
+ * 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()
+ * 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
@@ -64,12 +71,15 @@
*
* ACK RECEPTION
*
- * This works in polling mode -- the fw loader says when to wait for
- * data and for that it calls i2400ms_bus_bm_wait_for_ack().
+ * 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 will poll the device for data until it is received. We need to
- * receive at least as much bytes as where asked for (although it'll
- * always be a multiple of 256 bytes).
+ * 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"
@@ -78,6 +88,7 @@
#define D_SUBMODULE fw
#include "sdio-debug-levels.h"
+
/*
* Send a boot-mode command to the SDIO function
*
@@ -139,7 +150,7 @@ error_too_big:
/*
- * Read an ack from the device's boot-mode (polling)
+ * Read an ack from the device's boot-mode
*
* @i2400m:
* @_ack: pointer to where to store the read data
@@ -150,75 +161,49 @@ error_too_big:
* 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
*
- * NOTE: We do an artificial timeout of 1 sec over the SDIO timeout;
- * this way we have control over it...there is no way that I know
- * of setting an SDIO transaction timeout.
*/
ssize_t i2400ms_bus_bm_wait_for_ack(struct i2400m *i2400m,
struct i2400m_bootrom_header *ack,
size_t ack_size)
{
- int result;
- ssize_t rx_size;
- u64 timeout;
+ 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);
- timeout = get_jiffies_64() + 2 * HZ;
- sdio_claim_host(func);
- while (1) {
- if (time_after64(get_jiffies_64(), timeout)) {
- rx_size = -ETIMEDOUT;
- dev_err(dev, "timeout waiting for ack data\n");
- goto error_timedout;
- }
+ spin_lock(&i2400m->rx_lock);
+ i2400ms->bm_ack_size = -EINPROGRESS;
+ spin_unlock(&i2400m->rx_lock);
- /* Find the RX size, check if it fits or not -- it if
- * doesn't fit, fail, as we have no way to dispose of
- * the extra data. */
- rx_size = __i2400ms_rx_get_size(i2400ms);
- if (rx_size < 0)
- goto error_rx_get_size;
- result = -ENOSPC; /* Check it fits */
- if (rx_size < sizeof(*ack)) {
- rx_size = -EIO;
- dev_err(dev, "HW BUG? received is too small (%zu vs "
- "%zu needed)\n", sizeof(*ack), rx_size);
- goto error_too_small;
- }
- if (rx_size > I2400M_BM_ACK_BUF_SIZE) {
- dev_err(dev, "SW BUG? BM_ACK_BUF is too small (%u vs "
- "%zu needed)\n", I2400M_BM_ACK_BUF_SIZE,
- rx_size);
- goto error_too_small;
- }
+ 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;
+ }
- /* Read it */
- result = sdio_memcpy_fromio(func, i2400m->bm_ack_buf,
- I2400MS_DATA_ADDR, rx_size);
- if (result == -ETIMEDOUT || result == -ETIME)
- continue;
- if (result < 0) {
- dev_err(dev, "BM SDIO receive (%zu B) failed: %d\n",
- rx_size, result);
- goto error_read;
- } else
- break;
+ 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);
}
- rx_size = min((ssize_t)ack_size, rx_size);
- memcpy(ack, i2400m->bm_ack_buf, rx_size);
-error_read:
-error_too_small:
-error_rx_get_size:
-error_timedout:
- sdio_release_host(func);
- d_fnend(5, dev, "(i2400m %p ack %p size %zu) = %ld\n",
- i2400m, ack, ack_size, (long) rx_size);
- return rx_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;
}
diff --git a/drivers/net/wimax/i2400m/sdio-rx.c b/drivers/net/wimax/i2400m/sdio-rx.c
index a3008b904f7d..321beadf6e47 100644
--- a/drivers/net/wimax/i2400m/sdio-rx.c
+++ b/drivers/net/wimax/i2400m/sdio-rx.c
@@ -69,6 +69,13 @@
#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
@@ -131,25 +138,35 @@ void i2400ms_rx(struct i2400ms *i2400ms)
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;
}
- /* Check if device has reset */
- if (!memcmp(skb->data, i2400m_NBOOT_BARKER,
- sizeof(i2400m_NBOOT_BARKER))
- || !memcmp(skb->data, i2400m_SBOOT_BARKER,
- sizeof(i2400m_SBOOT_BARKER))) {
+
+ 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);
@@ -179,7 +196,6 @@ void i2400ms_irq(struct sdio_func *func)
{
int ret;
struct i2400ms *i2400ms = sdio_get_drvdata(func);
- struct i2400m *i2400m = &i2400ms->i2400m;
struct device *dev = &func->dev;
int val;
@@ -194,10 +210,7 @@ void i2400ms_irq(struct sdio_func *func)
goto error_no_irq;
}
sdio_writeb(func, 1, I2400MS_INTR_CLEAR_ADDR, &ret);
- if (WARN_ON(i2400m->boot_mode != 0))
- dev_err(dev, "RX: SW BUG? boot mode and IRQ is up?\n");
- else
- i2400ms_rx(i2400ms);
+ i2400ms_rx(i2400ms);
error_no_irq:
d_fnend(6, dev, "(i2400ms %p) = void\n", i2400ms);
return;
@@ -214,8 +227,15 @@ 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) {
@@ -245,8 +265,13 @@ 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);
diff --git a/drivers/net/wimax/i2400m/sdio.c b/drivers/net/wimax/i2400m/sdio.c
index 5ac5e76701cd..2538825d1c66 100644
--- a/drivers/net/wimax/i2400m/sdio.c
+++ b/drivers/net/wimax/i2400m/sdio.c
@@ -78,6 +78,14 @@ static const char *i2400ms_bus_fw_names[] = {
};
+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
*
@@ -148,19 +156,14 @@ int i2400ms_bus_dev_start(struct i2400m *i2400m)
d_fnstart(3, dev, "(i2400m %p)\n", i2400m);
msleep(200);
- result = i2400ms_rx_setup(i2400ms);
- if (result < 0)
- goto error_rx_setup;
result = i2400ms_tx_setup(i2400ms);
if (result < 0)
goto error_tx_setup;
d_fnend(3, dev, "(i2400m %p) = %d\n", i2400m, result);
return result;
- i2400ms_tx_release(i2400ms);
error_tx_setup:
- i2400ms_rx_release(i2400ms);
-error_rx_setup:
+ i2400ms_tx_release(i2400ms);
d_fnend(3, dev, "(i2400m %p) = void\n", i2400m);
return result;
}
@@ -174,7 +177,6 @@ void i2400ms_bus_dev_stop(struct i2400m *i2400m)
struct device *dev = &func->dev;
d_fnstart(3, dev, "(i2400m %p)\n", i2400m);
- i2400ms_rx_release(i2400ms);
i2400ms_tx_release(i2400ms);
d_fnend(3, dev, "(i2400m %p) = void\n", i2400m);
}
@@ -255,7 +257,7 @@ error_kzalloc:
static
int i2400ms_bus_reset(struct i2400m *i2400m, enum i2400m_reset_type rt)
{
- int result;
+ int result = 0;
struct i2400ms *i2400ms =
container_of(i2400m, struct i2400ms, i2400m);
struct device *dev = i2400m_dev(i2400m);
@@ -280,8 +282,25 @@ int i2400ms_bus_reset(struct i2400m *i2400m, enum i2400m_reset_type rt)
sizeof(i2400m_COLD_BOOT_BARKER));
else if (rt == I2400M_RT_BUS) {
do_bus_reset:
- dev_err(dev, "FIXME: SDIO bus reset not implemented\n");
- result = rt == I2400M_RT_WARM ? -ENODEV : -ENOSYS;
+ /* 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) {
@@ -404,24 +423,32 @@ int i2400ms_probe(struct sdio_func *func,
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;
-
- result = i2400ms_enable_function(i2400ms->func);
- if (result < 0) {
- dev_err(dev, "Cannot enable SDIO function: %d\n", result);
- goto error_func_enable;
- }
+ 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;
}
- sdio_release_host(func);
+
+ 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) {
@@ -440,12 +467,14 @@ int i2400ms_probe(struct sdio_func *func,
error_debugfs_add:
i2400m_release(i2400m);
error_setup:
- sdio_set_drvdata(func, NULL);
+ i2400ms_rx_release(i2400ms);
+error_rx_setup:
sdio_claim_host(func);
-error_set_blk_size:
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;
@@ -462,6 +491,7 @@ void i2400ms_remove(struct sdio_func *func)
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);
diff --git a/drivers/net/wimax/i2400m/tx.c b/drivers/net/wimax/i2400m/tx.c
index 613a88ffd651..fa16ccf8e26a 100644
--- a/drivers/net/wimax/i2400m/tx.c
+++ b/drivers/net/wimax/i2400m/tx.c
@@ -278,6 +278,48 @@ enum {
#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
@@ -338,7 +380,7 @@ void *i2400m_tx_fifo_push(struct i2400m *i2400m, size_t size, size_t padding)
return NULL;
}
/* Is there space at the tail? */
- tail_room = I2400M_TX_BUF_SIZE - i2400m->tx_in % I2400M_TX_BUF_SIZE;
+ 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) {
@@ -367,17 +409,29 @@ void *i2400m_tx_fifo_push(struct i2400m *i2400m, size_t size, size_t padding)
* (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_BUF_SIZE - tx_in;
+ 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",
@@ -474,10 +528,18 @@ void i2400m_tx_close(struct i2400m *i2400m)
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
@@ -491,7 +553,7 @@ void i2400m_tx_close(struct i2400m *i2400m)
*/
hdr_size = sizeof(*tx_msg)
+ le16_to_cpu(tx_msg->num_pls) * sizeof(tx_msg->pld[0]);
- hdr_size = ALIGN(hdr_size, I2400M_PL_PAD);
+ 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);
@@ -574,7 +636,7 @@ int i2400m_tx(struct i2400m *i2400m, const void *buf, size_t buf_len,
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_PAD);
+ 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,
@@ -591,6 +653,8 @@ try_new:
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);
@@ -773,7 +837,6 @@ void i2400m_tx_msg_sent(struct i2400m *i2400m)
n = i2400m->tx_out / I2400M_TX_BUF_SIZE;
i2400m->tx_out %= I2400M_TX_BUF_SIZE;
i2400m->tx_in -= n * I2400M_TX_BUF_SIZE;
- netif_start_queue(i2400m->wimax_dev.net_dev);
spin_unlock_irqrestore(&i2400m->tx_lock, flags);
d_fnend(3, dev, "(i2400m %p) = void\n", i2400m);
}
diff --git a/drivers/net/wimax/i2400m/usb.c b/drivers/net/wimax/i2400m/usb.c
index ca4151a9e222..cfdaf69da9d1 100644
--- a/drivers/net/wimax/i2400m/usb.c
+++ b/drivers/net/wimax/i2400m/usb.c
@@ -254,8 +254,10 @@ do_bus_reset:
dev_err(dev, "USB reset failed (%d), giving up!\n",
result);
}
- } else
+ } else {
+ result = -EINVAL; /* shut gcc up in certain arches */
BUG();
+ }
if (result < 0
&& result != -EINVAL /* device is gone */
&& rt != I2400M_RT_BUS) {
@@ -399,6 +401,7 @@ int i2400mu_probe(struct usb_interface *iface,
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;
@@ -505,27 +508,52 @@ int i2400mu_suspend(struct usb_interface *iface, pm_message_t pm_msg)
#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;
- d_printf(1, dev, "fw up, requesting standby\n");
+ 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);
-#ifdef CONFIG_PM
- if (result < 0 && usb_dev->auto_pm == 0) {
+ if (result < 0 && !is_autosuspend) {
/* System suspend, can't fail */
dev_err(dev, "failed to suspend, will reset on resume\n");
result = 0;
}
-#endif
if (result < 0)
goto error_enter_powersave;
i2400mu_notification_release(i2400mu);
- d_printf(1, dev, "fw up, got standby\n");
+ 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);