From 663ebb4aa2fd0d19e3d1da0fed3fa3fa134a4ef0 Mon Sep 17 00:00:00 2001 From: Oliver Neukum Date: Wed, 19 Aug 2009 13:50:45 -0600 Subject: wimax/i2400m/usb: remove unnecessary power management primitive in i2400m This patch removes an unneeded power management primitive. Power management is automatically enabled as probe ends. Signed-off-by: Oliver Neukum Signed-off-by: Inaky Perez-Gonzalez --- drivers/net/wimax/i2400m/usb.c | 1 - 1 file changed, 1 deletion(-) (limited to 'drivers/net/wimax') diff --git a/drivers/net/wimax/i2400m/usb.c b/drivers/net/wimax/i2400m/usb.c index 7eadd11c815b..bfa45f571341 100644 --- a/drivers/net/wimax/i2400m/usb.c +++ b/drivers/net/wimax/i2400m/usb.c @@ -415,7 +415,6 @@ int i2400mu_probe(struct usb_interface *iface, #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 -- cgit v1.2.3 From 5b45bfe5010ae1ddaac463d1bcdb141a4ff4ff66 Mon Sep 17 00:00:00 2001 From: Cindy H Kao Date: Thu, 27 Aug 2009 15:25:12 -0700 Subject: wimax/i2400m: fix the bootmode RX deadlock in SDIO driver i2400ms_bus_bm_wait_for_ack() causes a race condition. It happens because this function clears i2400ms->bm_ack_size before waiting for an interrupt, which is set by the interrupt service routine i2400ms_rx() to indicate reception and size of received data; thus, if the interrupt came right before the clearing/waiting, it is lost. The fix is clear the bm_ack_size to -EINPROGRESS before we are enabling the RX interrupt configuration in i2400ms_rx_setup(). Then everytime when the interrupt service routine i2400ms_rx() is invoked during bootmode, bm_ack_size is updated with the actual rx_size and it is cleared to -EINPROGRESS again after the RX data is handled. Signed-off-by: Cindy H Kao Signed-off-by: Inaky Perez-Gonzalez --- drivers/net/wimax/i2400m/sdio-fw.c | 8 ++++---- drivers/net/wimax/i2400m/sdio-rx.c | 7 +++++++ 2 files changed, 11 insertions(+), 4 deletions(-) (limited to 'drivers/net/wimax') diff --git a/drivers/net/wimax/i2400m/sdio-fw.c b/drivers/net/wimax/i2400m/sdio-fw.c index 7d6ec0f475f8..c8dc538d40c1 100644 --- a/drivers/net/wimax/i2400m/sdio-fw.c +++ b/drivers/net/wimax/i2400m/sdio-fw.c @@ -177,10 +177,6 @@ ssize_t i2400ms_bus_bm_wait_for_ack(struct i2400m *i2400m, 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); @@ -199,6 +195,10 @@ ssize_t i2400ms_bus_bm_wait_for_ack(struct i2400m *i2400m, size = min(ack_size, i2400ms->bm_ack_size); memcpy(ack, i2400m->bm_ack_buf, size); } + /* + * Remember always to clear the bm_ack_size to -EINPROGRESS + * after the RX data is processed + */ i2400ms->bm_ack_size = -EINPROGRESS; spin_unlock(&i2400m->rx_lock); diff --git a/drivers/net/wimax/i2400m/sdio-rx.c b/drivers/net/wimax/i2400m/sdio-rx.c index 321beadf6e47..f6ca51ab216d 100644 --- a/drivers/net/wimax/i2400m/sdio-rx.c +++ b/drivers/net/wimax/i2400m/sdio-rx.c @@ -234,6 +234,13 @@ int i2400ms_rx_setup(struct i2400ms *i2400ms) init_waitqueue_head(&i2400ms->bm_wfa_wq); spin_lock(&i2400m->rx_lock); i2400ms->bm_wait_result = -EINPROGRESS; + /* + * Before we are about to enable the RX interrupt, make sure + * bm_ack_size is cleared to -EINPROGRESS which indicates + * no RX interrupt happened yet or the previous interrupt + * has been handled, we are ready to take the new interrupt + */ + i2400ms->bm_ack_size = -EINPROGRESS; spin_unlock(&i2400m->rx_lock); sdio_claim_host(func); -- cgit v1.2.3 From a6346fa583766a51b7723288fc5d73ee5ea93479 Mon Sep 17 00:00:00 2001 From: Roel Kluin Date: Mon, 13 Jul 2009 01:48:42 +0200 Subject: i2400m: keep index within ms_to_errno[] Ensure that index `status' remains within ms_to_errno[] Signed-off-by: Roel Kluin Signed-off-by: Inaky Perez-Gonzalez --- drivers/net/wimax/i2400m/control.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers/net/wimax') diff --git a/drivers/net/wimax/i2400m/control.c b/drivers/net/wimax/i2400m/control.c index 07308686dbcf..8fe70e75d030 100644 --- a/drivers/net/wimax/i2400m/control.c +++ b/drivers/net/wimax/i2400m/control.c @@ -263,7 +263,7 @@ int i2400m_msg_check_status(const struct i2400m_l3l4_hdr *l3l4_hdr, if (status == 0) return 0; - if (status > ARRAY_SIZE(ms_to_errno)) { + if (status >= ARRAY_SIZE(ms_to_errno)) { str = "unknown status code"; result = -EBADR; } else { -- cgit v1.2.3 From 2d44f204adf503eb1774f0ab7e404031168851ea Mon Sep 17 00:00:00 2001 From: Roel Kluin Date: Mon, 13 Jul 2009 00:59:53 +0200 Subject: wimax: misplaced parenthesis Fix misplaced parenthesis Signed-off-by: Roel Kluin Signed-off-by: Inaky Perez-Gonzalez --- drivers/net/wimax/i2400m/tx.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers/net/wimax') diff --git a/drivers/net/wimax/i2400m/tx.c b/drivers/net/wimax/i2400m/tx.c index fa16ccf8e26a..8c2080248aec 100644 --- a/drivers/net/wimax/i2400m/tx.c +++ b/drivers/net/wimax/i2400m/tx.c @@ -310,7 +310,7 @@ size_t __i2400m_tx_tail_room(struct i2400m *i2400m) size_t tail_room; size_t tx_in; - if (unlikely(i2400m->tx_in) == 0) + 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; -- cgit v1.2.3 From abb307338396bd58f5d9d32c4e56ef40ff668a74 Mon Sep 17 00:00:00 2001 From: Dan Williams Date: Thu, 17 Sep 2009 13:06:14 -0700 Subject: i2400m: minimal ethtool support Add minimal ethtool support for carrier detection. Signed-off-by: Dan Williams Signed-off-by: Inaky Perez-Gonzalez --- drivers/net/wimax/i2400m/netdev.c | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) (limited to 'drivers/net/wimax') diff --git a/drivers/net/wimax/i2400m/netdev.c b/drivers/net/wimax/i2400m/netdev.c index 796396cb4c82..960fb5467546 100644 --- a/drivers/net/wimax/i2400m/netdev.c +++ b/drivers/net/wimax/i2400m/netdev.c @@ -74,6 +74,7 @@ */ #include #include +#include #include "i2400m.h" @@ -559,6 +560,22 @@ static const struct net_device_ops i2400m_netdev_ops = { .ndo_change_mtu = i2400m_change_mtu, }; +static void i2400m_get_drvinfo(struct net_device *net_dev, + struct ethtool_drvinfo *info) +{ + struct i2400m *i2400m = net_dev_to_i2400m(net_dev); + + strncpy(info->driver, KBUILD_MODNAME, sizeof(info->driver) - 1); + strncpy(info->fw_version, i2400m->fw_name, sizeof(info->fw_version) - 1); + if (net_dev->dev.parent) + strncpy(info->bus_info, dev_name(net_dev->dev.parent), + sizeof(info->bus_info) - 1); +} + +static const struct ethtool_ops i2400m_ethtool_ops = { + .get_drvinfo = i2400m_get_drvinfo, + .get_link = ethtool_op_get_link, +}; /** * i2400m_netdev_setup - Setup setup @net_dev's i2400m private data @@ -580,6 +597,7 @@ void i2400m_netdev_setup(struct net_device *net_dev) & ~IFF_MULTICAST); net_dev->watchdog_timeo = I2400M_TX_TIMEOUT; net_dev->netdev_ops = &i2400m_netdev_ops; + net_dev->ethtool_ops = &i2400m_ethtool_ops; d_fnend(3, NULL, "(net_dev %p) = void\n", net_dev); } EXPORT_SYMBOL_GPL(i2400m_netdev_setup); -- cgit v1.2.3 From 81b182a7542c4282191fa0b1e8d9fcb022c03e68 Mon Sep 17 00:00:00 2001 From: Dirk Brandewie Date: Fri, 24 Jul 2009 09:04:33 -0700 Subject: wimax/i2400m: Update comments to talk about SDIO reset and not USB. Fixing comments from original cut and paste error Signed-off-by: Dirk Brandewie Signed-off-by: Inaky Perez-Gonzalez --- drivers/net/wimax/i2400m/sdio.c | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) (limited to 'drivers/net/wimax') diff --git a/drivers/net/wimax/i2400m/sdio.c b/drivers/net/wimax/i2400m/sdio.c index 2981e211e04f..c67b0264a282 100644 --- a/drivers/net/wimax/i2400m/sdio.c +++ b/drivers/net/wimax/i2400m/sdio.c @@ -233,18 +233,17 @@ error_kzalloc: * Warm reset: * * The device will be fully reset internally, but won't be - * disconnected from the USB bus (so no reenumeration will + * disconnected from the 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(). + * The device will send a reboot barker that will trigger the driver + * to reinitialize the state via __i2400m_dev_reset_handle. * - * Cold and bus (USB) reset: + * + * Cold and bus reset: * * The device will be fully reset internally, disconnected from the - * USB bus an a reenumeration will happen. Firmware upload will be + * 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. -- cgit v1.2.3 From a134fd6b103b78378e3beb6af94d8d8c2abdd19d Mon Sep 17 00:00:00 2001 From: Dirk Brandewie Date: Tue, 18 Aug 2009 08:51:52 -0700 Subject: wimax/i2400m: Ensure boot mode cmd and ack buffers are alloc'd before first message The change to the SDIO boot mode RX chain could try to use the cmd and ack buffers befor they were allocated. USB does not have the problem but both were changed for consistency's sake. Signed-off-by: Dirk Brandewie Signed-off-by: Inaky Perez-Gonzalez --- drivers/net/wimax/i2400m/driver.c | 54 +++++++++++++++++++++++++++++---------- drivers/net/wimax/i2400m/i2400m.h | 2 ++ drivers/net/wimax/i2400m/sdio.c | 14 ++++++++++ drivers/net/wimax/i2400m/usb.c | 8 ++++++ 4 files changed, 64 insertions(+), 14 deletions(-) (limited to 'drivers/net/wimax') diff --git a/drivers/net/wimax/i2400m/driver.c b/drivers/net/wimax/i2400m/driver.c index 304f0443ca4b..20d574ca9183 100644 --- a/drivers/net/wimax/i2400m/driver.c +++ b/drivers/net/wimax/i2400m/driver.c @@ -618,6 +618,46 @@ int i2400m_dev_reset_handle(struct i2400m *i2400m) EXPORT_SYMBOL_GPL(i2400m_dev_reset_handle); +/** + * i2400m_bm_buf_alloc - Alloc the command and ack buffers for boot mode + * + * Get the buffers needed to deal with boot mode messages. These + * buffers need to be allocated before the sdio recieve irq is setup. + */ +int i2400m_bm_buf_alloc(struct i2400m *i2400m) +{ + int result; + + result = -ENOMEM; + i2400m->bm_cmd_buf = kzalloc(I2400M_BM_CMD_BUF_SIZE, GFP_KERNEL); + if (i2400m->bm_cmd_buf == NULL) + goto error_bm_cmd_kzalloc; + i2400m->bm_ack_buf = kzalloc(I2400M_BM_ACK_BUF_SIZE, GFP_KERNEL); + if (i2400m->bm_ack_buf == NULL) + goto error_bm_ack_buf_kzalloc; + return 0; + +error_bm_ack_buf_kzalloc: + kfree(i2400m->bm_cmd_buf); +error_bm_cmd_kzalloc: + return result; +} +EXPORT_SYMBOL_GPL(i2400m_bm_buf_alloc); + +/** + * i2400m_bm_buf_free - Free boot mode command and ack buffers. + * + * Free the command and ack buffers + * + */ +void i2400m_bm_buf_free(struct i2400m *i2400m) +{ + kfree(i2400m->bm_ack_buf); + kfree(i2400m->bm_cmd_buf); + return; +} +EXPORT_SYMBOL_GPL(i2400m_bm_buf_free +); /** * i2400m_setup - bus-generic setup function for the i2400m device * @@ -645,16 +685,6 @@ int i2400m_setup(struct i2400m *i2400m, enum i2400m_bri bm_flags) 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 " @@ -713,10 +743,6 @@ error_dev_start: 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; } diff --git a/drivers/net/wimax/i2400m/i2400m.h b/drivers/net/wimax/i2400m/i2400m.h index 60330f313f27..a6e59f1c881d 100644 --- a/drivers/net/wimax/i2400m/i2400m.h +++ b/drivers/net/wimax/i2400m/i2400m.h @@ -725,6 +725,8 @@ void i2400m_put(struct i2400m *i2400m) } extern int i2400m_dev_reset_handle(struct i2400m *); +extern int i2400m_bm_buf_alloc(struct i2400m *i2400m); +extern void i2400m_bm_buf_free(struct i2400m *i2400m); /* * _setup()/_release() are called by the probe/disconnect functions of diff --git a/drivers/net/wimax/i2400m/sdio.c b/drivers/net/wimax/i2400m/sdio.c index c67b0264a282..a68232aa2915 100644 --- a/drivers/net/wimax/i2400m/sdio.c +++ b/drivers/net/wimax/i2400m/sdio.c @@ -451,6 +451,18 @@ int i2400ms_probe(struct sdio_func *func, goto error_func_enable; } + /* + * Before we are enabling the device interrupt register, make + * sure the buffer used during bootmode operation is setup so + * when the first D2H data interrupt comes, the memory is + * available for copying the D2H data. + */ + result = i2400m_bm_buf_alloc(i2400m); + if (result < 0) { + dev_err(dev, "cannot allocate SDIO bootmode buffer\n"); + goto error_bootmode_buf_setup; + } + result = i2400ms_rx_setup(i2400ms); if (result < 0) goto error_rx_setup; @@ -474,6 +486,8 @@ error_debugfs_add: error_setup: i2400ms_rx_release(i2400ms); error_rx_setup: + i2400m_bm_buf_free(i2400m); +error_bootmode_buf_setup: sdio_claim_host(func); sdio_disable_func(func); sdio_release_host(func); diff --git a/drivers/net/wimax/i2400m/usb.c b/drivers/net/wimax/i2400m/usb.c index bfa45f571341..49d7554ad707 100644 --- a/drivers/net/wimax/i2400m/usb.c +++ b/drivers/net/wimax/i2400m/usb.c @@ -419,6 +419,12 @@ int i2400mu_probe(struct usb_interface *iface, usb_dev->autosuspend_disabled = 0; #endif + result = i2400m_bm_buf_alloc(i2400m); + if (result < 0) { + dev_err(dev, "cannot allocate USB bootmode buffer\n"); + goto error_bm_buf_alloc; + } + result = i2400m_setup(i2400m, I2400M_BRI_MAC_REINIT); if (result < 0) { dev_err(dev, "cannot setup device: %d\n", result); @@ -434,6 +440,8 @@ int i2400mu_probe(struct usb_interface *iface, error_debugfs_add: i2400m_release(i2400m); error_setup: + i2400m_bm_buf_free(i2400m); +error_bm_buf_alloc: usb_set_intfdata(iface, NULL); usb_put_dev(i2400mu->usb_dev); free_netdev(net_dev); -- cgit v1.2.3 From c30836580b35ae5cab3de97a3df16878fe097868 Mon Sep 17 00:00:00 2001 From: Dirk Brandewie Date: Thu, 13 Aug 2009 13:48:29 -0700 Subject: wimax/i2400m: Make boot retries a BUS-specific parameter In i2400m-based devices, the driver's bootloader will retry to load the firmware when things go wrong. The driver currently has a constant (I2400M_BOOT_RETRIES) which governs the max number of tries. However, different SKUs of the same hardware may admit or require different numbers of retries due to it's particulars, so it is made a BUS specific parameter and different values are assigned for 5x50 devices versus the 3200 ones. Signed-off-by: Dirk Brandewie Signed-off-by: Cindy H Kao Signed-off-by: Inaky Perez-Gonzalez --- drivers/net/wimax/i2400m/fw.c | 2 +- drivers/net/wimax/i2400m/i2400m-sdio.h | 1 + drivers/net/wimax/i2400m/i2400m-usb.h | 1 + drivers/net/wimax/i2400m/i2400m.h | 3 --- drivers/net/wimax/i2400m/sdio.c | 2 +- drivers/net/wimax/i2400m/usb.c | 2 +- 6 files changed, 5 insertions(+), 6 deletions(-) (limited to 'drivers/net/wimax') diff --git a/drivers/net/wimax/i2400m/fw.c b/drivers/net/wimax/i2400m/fw.c index e81750e54452..55bd69e913b9 100644 --- a/drivers/net/wimax/i2400m/fw.c +++ b/drivers/net/wimax/i2400m/fw.c @@ -602,7 +602,7 @@ int i2400m_bootrom_init(struct i2400m *i2400m, enum i2400m_bri flags) struct device *dev = i2400m_dev(i2400m); struct i2400m_bootrom_header *cmd; struct i2400m_bootrom_header ack; - int count = I2400M_BOOT_RETRIES; + int count = i2400m->bus_bm_retries; int ack_timeout_cnt = 1; BUILD_BUG_ON(sizeof(*cmd) != sizeof(i2400m_NBOOT_BARKER)); diff --git a/drivers/net/wimax/i2400m/i2400m-sdio.h b/drivers/net/wimax/i2400m/i2400m-sdio.h index 9c4e3189f7b5..66884eb59b26 100644 --- a/drivers/net/wimax/i2400m/i2400m-sdio.h +++ b/drivers/net/wimax/i2400m/i2400m-sdio.h @@ -67,6 +67,7 @@ /* Host-Device interface for SDIO */ enum { + I2400M_SDIO_BOOT_RETRIES = 3, I2400MS_BLK_SIZE = 256, I2400MS_PL_SIZE_MAX = 0x3E00, diff --git a/drivers/net/wimax/i2400m/i2400m-usb.h b/drivers/net/wimax/i2400m/i2400m-usb.h index 6f76558b170f..79c37534c200 100644 --- a/drivers/net/wimax/i2400m/i2400m-usb.h +++ b/drivers/net/wimax/i2400m/i2400m-usb.h @@ -137,6 +137,7 @@ static inline int edc_inc(struct edc *edc, u16 max_err, u16 timeframe) /* Host-Device interface for USB */ enum { + I2400M_USB_BOOT_RETRIES = 3, I2400MU_MAX_NOTIFICATION_LEN = 256, I2400MU_BLK_SIZE = 16, I2400MU_PL_SIZE_MAX = 0x3EFF, diff --git a/drivers/net/wimax/i2400m/i2400m.h b/drivers/net/wimax/i2400m/i2400m.h index a6e59f1c881d..73b4e6a15135 100644 --- a/drivers/net/wimax/i2400m/i2400m.h +++ b/drivers/net/wimax/i2400m/i2400m.h @@ -148,9 +148,6 @@ /* 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, diff --git a/drivers/net/wimax/i2400m/sdio.c b/drivers/net/wimax/i2400m/sdio.c index a68232aa2915..1429608164b0 100644 --- a/drivers/net/wimax/i2400m/sdio.c +++ b/drivers/net/wimax/i2400m/sdio.c @@ -430,7 +430,7 @@ int i2400ms_probe(struct sdio_func *func, 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_retries = I2400M_SDIO_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; diff --git a/drivers/net/wimax/i2400m/usb.c b/drivers/net/wimax/i2400m/usb.c index 49d7554ad707..8f7b16a6bf3c 100644 --- a/drivers/net/wimax/i2400m/usb.c +++ b/drivers/net/wimax/i2400m/usb.c @@ -406,7 +406,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_retries = I2400M_USB_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; -- cgit v1.2.3 From f2696fbdb623993a9b4c05455df7ae3158a01111 Mon Sep 17 00:00:00 2001 From: Cindy H Kao Date: Mon, 17 Aug 2009 19:17:58 -0700 Subject: wimax/iwmc3200: overwrite SDIO IOR timeout value to avoid platform hang The default SDIO IOE wait timeout returned from iwmc3200-wimax's CCCR is not efficient. This inefficiency will actually cause problems on Moorestown platforms (system hang). This is a sillicon bug that requires a software patch to by overwritting func->enable_timeout. The new value I2400MS_IOR_TIMEOUT is recommended and verified from the system integration results. Future sillicon releases will have this default value corrected in the future. Signed-off-by: Cindy H Kao Signed-off-by: Inaky Perez-Gonzalez --- drivers/net/wimax/i2400m/i2400m-sdio.h | 2 ++ drivers/net/wimax/i2400m/sdio.c | 8 ++++++++ 2 files changed, 10 insertions(+) (limited to 'drivers/net/wimax') diff --git a/drivers/net/wimax/i2400m/i2400m-sdio.h b/drivers/net/wimax/i2400m/i2400m-sdio.h index 66884eb59b26..cfaedfa37892 100644 --- a/drivers/net/wimax/i2400m/i2400m-sdio.h +++ b/drivers/net/wimax/i2400m/i2400m-sdio.h @@ -81,6 +81,8 @@ enum { I2400MS_INIT_SLEEP_INTERVAL = 10, /* How long to wait for the device to settle after reset */ I2400MS_SETTLE_TIME = 40, + /* The number of msec to wait for IOR after sending IOE */ + IWMC3200_IOR_TIMEOUT = 10, }; diff --git a/drivers/net/wimax/i2400m/sdio.c b/drivers/net/wimax/i2400m/sdio.c index 1429608164b0..0d64d16bd264 100644 --- a/drivers/net/wimax/i2400m/sdio.c +++ b/drivers/net/wimax/i2400m/sdio.c @@ -115,6 +115,14 @@ int i2400ms_enable_function(struct sdio_func *func) err = -ENODEV; while (err != 0 && time_before64(get_jiffies_64(), timeout)) { sdio_claim_host(func); + /* + * There is a sillicon bug on the IWMC3200, where the + * IOE timeout will cause problems on Moorestown + * platforms (system hang). We explicitly overwrite + * func->enable_timeout here to work around the issue. + */ + if (func->device == SDIO_DEVICE_ID_INTEL_IWMC3200WIMAX) + func->enable_timeout = IWMC3200_IOR_TIMEOUT; err = sdio_enable_func(func); if (0 == err) { sdio_release_host(func); -- cgit v1.2.3 From 5c29e21d4768a7f1ce46f622b379802edeb1daf3 Mon Sep 17 00:00:00 2001 From: Cindy H Kao Date: Mon, 17 Aug 2009 19:39:12 -0700 Subject: wimax/iwmc3200: don't disable the SDIO function if enable fails In the iwmc3200, disabling the WiMAX SDIO function when enable fails would possibly result in a device reset triggered by the iwmc3200's top controller since it monitors the bus reset activities from each SDIO function. In any case, the disable makes no sense; if the enable fails, it should not be disabled. Thus we remove the unecessary sdio_disable_func() in i2400ms_enable_function(). Signed-off-by: Cindy H Kao Signed-off-by: Inaky Perez-Gonzalez --- drivers/net/wimax/i2400m/sdio.c | 1 - 1 file changed, 1 deletion(-) (limited to 'drivers/net/wimax') diff --git a/drivers/net/wimax/i2400m/sdio.c b/drivers/net/wimax/i2400m/sdio.c index 0d64d16bd264..9d6046f58cae 100644 --- a/drivers/net/wimax/i2400m/sdio.c +++ b/drivers/net/wimax/i2400m/sdio.c @@ -130,7 +130,6 @@ int i2400ms_enable_function(struct sdio_func *func) 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); } -- cgit v1.2.3 From 8bec9a5efd5691c5a32d85da1da09643290ebb72 Mon Sep 17 00:00:00 2001 From: Cindy H Kao Date: Mon, 17 Aug 2009 19:39:12 -0700 Subject: wimax/iwmc3200: increase wait time before enable retry When trying to enable the iwmc3200 WiMAX SDIO function, we loop waiting for the top controller to be up and running (at which point we can succesfully enable the function). Between each try we wait for I2400MS_INIT_SLEEP_INTERVAL ms. Integration tests have found the current value of 10ms to be too short; it was upped to 100ms to give more time to the top controller to be ready. Signed-off-by: Cindy H Kao Signed-off-by: Inaky Perez-Gonzalez --- drivers/net/wimax/i2400m/i2400m-sdio.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers/net/wimax') diff --git a/drivers/net/wimax/i2400m/i2400m-sdio.h b/drivers/net/wimax/i2400m/i2400m-sdio.h index cfaedfa37892..18218a24a8f8 100644 --- a/drivers/net/wimax/i2400m/i2400m-sdio.h +++ b/drivers/net/wimax/i2400m/i2400m-sdio.h @@ -78,7 +78,7 @@ enum { 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, + I2400MS_INIT_SLEEP_INTERVAL = 100, /* How long to wait for the device to settle after reset */ I2400MS_SETTLE_TIME = 40, /* The number of msec to wait for IOR after sending IOE */ -- cgit v1.2.3 From 2093586de29418820b89aae05746511392f8ad73 Mon Sep 17 00:00:00 2001 From: Dirk Brandewie Date: Wed, 12 Aug 2009 11:29:46 -0700 Subject: wimax/i2400m: USB driver uses a configurable endpoint map Newer generations of the i2400m USB WiMAX device use a different endpoint map; in order to make it easy to support it, we make the endpoint-to-function mapeable instead of static. Signed-off-by: Dirk Brandewie Signed-off-by: Inaky Perez-Gonzalez --- drivers/net/wimax/i2400m/i2400m-usb.h | 14 ++++++++------ drivers/net/wimax/i2400m/usb-fw.c | 5 +++-- drivers/net/wimax/i2400m/usb-notif.c | 3 ++- drivers/net/wimax/i2400m/usb-rx.c | 2 +- drivers/net/wimax/i2400m/usb-tx.c | 2 +- drivers/net/wimax/i2400m/usb.c | 20 ++++++++++++++------ 6 files changed, 29 insertions(+), 17 deletions(-) (limited to 'drivers/net/wimax') diff --git a/drivers/net/wimax/i2400m/i2400m-usb.h b/drivers/net/wimax/i2400m/i2400m-usb.h index 79c37534c200..f73a067e0668 100644 --- a/drivers/net/wimax/i2400m/i2400m-usb.h +++ b/drivers/net/wimax/i2400m/i2400m-usb.h @@ -88,6 +88,13 @@ struct edc { u16 errorcount; }; +struct i2400m_endpoint_cfg { + unsigned char bulk_out; + unsigned char notification; + unsigned char reset_cold; + unsigned char bulk_in; +}; + static inline void edc_init(struct edc *edc) { edc->timestart = jiffies; @@ -141,12 +148,6 @@ 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, }; @@ -216,6 +217,7 @@ struct i2400mu { struct usb_device *usb_dev; struct usb_interface *usb_iface; struct edc urb_edc; /* Error density counter */ + struct i2400m_endpoint_cfg endpoint_cfg; struct urb *notif_urb; struct task_struct *tx_kthread; diff --git a/drivers/net/wimax/i2400m/usb-fw.c b/drivers/net/wimax/i2400m/usb-fw.c index 5ad287c228b8..a2250e49a444 100644 --- a/drivers/net/wimax/i2400m/usb-fw.c +++ b/drivers/net/wimax/i2400m/usb-fw.c @@ -99,7 +99,7 @@ ssize_t i2400mu_tx_bulk_out(struct i2400mu *i2400mu, void *buf, size_t buf_size) 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); + epd = usb_get_epd(i2400mu->usb_iface, i2400mu->endpoint_cfg.bulk_out); pipe = usb_sndbulkpipe(i2400mu->usb_dev, epd->bEndpointAddress); retry: result = usb_bulk_msg(i2400mu->usb_dev, pipe, buf, buf_size, &len, HZ); @@ -226,7 +226,8 @@ int i2400mu_notif_submit(struct i2400mu *i2400mu, struct urb *urb, struct usb_endpoint_descriptor *epd; int pipe; - epd = usb_get_epd(i2400mu->usb_iface, I2400MU_EP_NOTIFICATION); + epd = usb_get_epd(i2400mu->usb_iface, + i2400mu->endpoint_cfg.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, diff --git a/drivers/net/wimax/i2400m/usb-notif.c b/drivers/net/wimax/i2400m/usb-notif.c index 6add27c3f35c..3e11e35cd696 100644 --- a/drivers/net/wimax/i2400m/usb-notif.c +++ b/drivers/net/wimax/i2400m/usb-notif.c @@ -220,7 +220,8 @@ int i2400mu_notification_setup(struct i2400mu *i2400mu) dev_err(dev, "notification: cannot allocate URB\n"); goto error_alloc_urb; } - epd = usb_get_epd(i2400mu->usb_iface, I2400MU_EP_NOTIFICATION); + epd = usb_get_epd(i2400mu->usb_iface, + i2400mu->endpoint_cfg.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, diff --git a/drivers/net/wimax/i2400m/usb-rx.c b/drivers/net/wimax/i2400m/usb-rx.c index a314799967cf..e494e3774ec0 100644 --- a/drivers/net/wimax/i2400m/usb-rx.c +++ b/drivers/net/wimax/i2400m/usb-rx.c @@ -204,7 +204,7 @@ struct sk_buff *i2400mu_rx(struct i2400mu *i2400mu, struct sk_buff *rx_skb) 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); + epd = usb_get_epd(i2400mu->usb_iface, i2400mu->endpoint_cfg.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; diff --git a/drivers/net/wimax/i2400m/usb-tx.c b/drivers/net/wimax/i2400m/usb-tx.c index dfd893356f49..90dfff1afb8a 100644 --- a/drivers/net/wimax/i2400m/usb-tx.c +++ b/drivers/net/wimax/i2400m/usb-tx.c @@ -101,7 +101,7 @@ int i2400mu_tx(struct i2400mu *i2400mu, struct i2400m_msg_hdr *tx_msg, 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); + epd = usb_get_epd(i2400mu->usb_iface, i2400mu->endpoint_cfg.bulk_out); usb_pipe = usb_sndbulkpipe(i2400mu->usb_dev, epd->bEndpointAddress); retry: result = usb_bulk_msg(i2400mu->usb_dev, usb_pipe, diff --git a/drivers/net/wimax/i2400m/usb.c b/drivers/net/wimax/i2400m/usb.c index 8f7b16a6bf3c..a5879e21bbf3 100644 --- a/drivers/net/wimax/i2400m/usb.c +++ b/drivers/net/wimax/i2400m/usb.c @@ -232,13 +232,15 @@ int i2400mu_bus_reset(struct i2400m *i2400m, enum i2400m_reset_type rt) 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); + result = __i2400mu_send_barker( + i2400mu, i2400m_WARM_BOOT_BARKER, + sizeof(i2400m_WARM_BOOT_BARKER), + i2400mu->endpoint_cfg.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); + result = __i2400mu_send_barker( + i2400mu, i2400m_COLD_BOOT_BARKER, + sizeof(i2400m_COLD_BOOT_BARKER), + i2400mu->endpoint_cfg.reset_cold); else if (rt == I2400M_RT_BUS) { do_bus_reset: result = usb_reset_device(i2400mu->usb_dev); @@ -412,6 +414,12 @@ int i2400mu_probe(struct usb_interface *iface, i2400m->bus_fw_names = i2400mu_bus_fw_names; i2400m->bus_bm_mac_addr_impaired = 0; + { + i2400mu->endpoint_cfg.bulk_out = 0; + i2400mu->endpoint_cfg.notification = 1; + i2400mu->endpoint_cfg.reset_cold = 2; + i2400mu->endpoint_cfg.bulk_in = 3; + } #ifdef CONFIG_PM iface->needs_remote_wakeup = 1; /* autosuspend (15s delay) */ device_init_wakeup(dev, 1); -- cgit v1.2.3 From 339ccc362cb60b48e478ff494172efadb385c0ab Mon Sep 17 00:00:00 2001 From: Cindy H Kao Date: Mon, 10 Aug 2009 18:36:15 -0700 Subject: wimax/i2400m/sdio: clear the INTR status bit after reading size In order to avoid issues during high-load traffic, the interrupt status register has to be cleared ONLY after the RX size is read. Signed-off-by: Cindy H Kao Signed-off-by: Inaky Perez-Gonzalez --- drivers/net/wimax/i2400m/sdio-rx.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) (limited to 'drivers/net/wimax') diff --git a/drivers/net/wimax/i2400m/sdio-rx.c b/drivers/net/wimax/i2400m/sdio-rx.c index f6ca51ab216d..1c9046914bd1 100644 --- a/drivers/net/wimax/i2400m/sdio-rx.c +++ b/drivers/net/wimax/i2400m/sdio-rx.c @@ -138,6 +138,11 @@ void i2400ms_rx(struct i2400ms *i2400ms) ret = rx_size; goto error_get_size; } + /* + * Hardware quirk: make sure to clear the INTR status register + * AFTER getting the data transfer size. + */ + sdio_writeb(func, 1, I2400MS_INTR_CLEAR_ADDR, &ret); ret = -ENOMEM; skb = alloc_skb(rx_size, GFP_ATOMIC); @@ -209,7 +214,6 @@ void i2400ms_irq(struct sdio_func *func) 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); -- cgit v1.2.3 From 77e1251a7cc64c1e483a31c41c139fdf8121e75e Mon Sep 17 00:00:00 2001 From: Inaky Perez-Gonzalez Date: Mon, 31 Aug 2009 17:15:49 -0700 Subject: wimax/i2400m: be smarter about copying command buffer to bm_cmd_buf Because some underlying bus APIs (like USB) don't like data buffers in the stack or vmalloced areas, the i2400m driver provides a scratch buffer (i2400m->bm_cmd_buf) for said low-level drivers to copy command data to before passing it to said API. This is only used during boot mode. However, at some the code was copying the buffer even when the command was already specified in said buffer. This is ok, but it needs to be more careful. As thus, change so that: (a) the copy happens only if command buffer is not the scratch buffer (b) use memmove() in case there is overlapping Signed-off-by: Inaky Perez-Gonzalez --- drivers/net/wimax/i2400m/fw.c | 1 - drivers/net/wimax/i2400m/sdio-fw.c | 3 ++- drivers/net/wimax/i2400m/usb-fw.c | 3 ++- 3 files changed, 4 insertions(+), 3 deletions(-) (limited to 'drivers/net/wimax') diff --git a/drivers/net/wimax/i2400m/fw.c b/drivers/net/wimax/i2400m/fw.c index 55bd69e913b9..0018cdbd0156 100644 --- a/drivers/net/wimax/i2400m/fw.c +++ b/drivers/net/wimax/i2400m/fw.c @@ -343,7 +343,6 @@ ssize_t i2400m_bm_cmd(struct i2400m *i2400m, BUG_ON(i2400m->boot_mode == 0); if (cmd != NULL) { /* send the command */ - memcpy(i2400m->bm_cmd_buf, cmd, cmd_size); result = i2400m->bus_bm_cmd_send(i2400m, cmd, cmd_size, flags); if (result < 0) goto error_cmd_send; diff --git a/drivers/net/wimax/i2400m/sdio-fw.c b/drivers/net/wimax/i2400m/sdio-fw.c index c8dc538d40c1..8e025418f5be 100644 --- a/drivers/net/wimax/i2400m/sdio-fw.c +++ b/drivers/net/wimax/i2400m/sdio-fw.c @@ -118,7 +118,8 @@ ssize_t i2400ms_bus_bm_cmd_send(struct i2400m *i2400m, if (cmd_size > I2400M_BM_CMD_BUF_SIZE) goto error_too_big; - memcpy(i2400m->bm_cmd_buf, _cmd, cmd_size); /* Prep command */ + if (_cmd != i2400m->bm_cmd_buf) + memmove(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); diff --git a/drivers/net/wimax/i2400m/usb-fw.c b/drivers/net/wimax/i2400m/usb-fw.c index a2250e49a444..f162c815d505 100644 --- a/drivers/net/wimax/i2400m/usb-fw.c +++ b/drivers/net/wimax/i2400m/usb-fw.c @@ -172,7 +172,8 @@ ssize_t i2400mu_bus_bm_cmd_send(struct i2400m *i2400m, result = -E2BIG; if (cmd_size > I2400M_BM_CMD_BUF_SIZE) goto error_too_big; - memcpy(i2400m->bm_cmd_buf, _cmd, cmd_size); + if (_cmd != i2400m->bm_cmd_buf) + memmove(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); -- cgit v1.2.3 From 8d8fe198c6a756ae96617548af4776e7a86c0d3e Mon Sep 17 00:00:00 2001 From: Cindy H Kao Date: Tue, 18 Aug 2009 19:27:18 -0700 Subject: wimax/i2400m: don't write to memory allocated by request_firmware() In kernel 2.6.31, the firmware requested to ram could be marked with read only attribute, and we can't write any thing directly to the memory when setting up the last JUMP brh cmd. Changed so that the scratch buffer is used. Signed-off-by: Cindy H Kao Signed-off-by: Inaky Perez-Gonzalez --- drivers/net/wimax/i2400m/fw.c | 4 ++++ 1 file changed, 4 insertions(+) (limited to 'drivers/net/wimax') diff --git a/drivers/net/wimax/i2400m/fw.c b/drivers/net/wimax/i2400m/fw.c index 0018cdbd0156..92d4d605dc2d 100644 --- a/drivers/net/wimax/i2400m/fw.c +++ b/drivers/net/wimax/i2400m/fw.c @@ -533,6 +533,10 @@ int i2400m_dnload_finalize(struct i2400m *i2400m, struct i2400m_bootrom_header jump_ack; d_printf(1, dev, "unsecure 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)); + cmd = &cmd_buf->cmd; + /* now cmd points to the actual bootrom_header in cmd_buf */ i2400m_brh_set_opcode(cmd, I2400M_BRH_JUMP); cmd->data_size = 0; ret = i2400m_bm_cmd(i2400m, cmd, sizeof(*cmd), -- cgit v1.2.3 From c77ca950abb587bcebad6dcd0b0b41d7c0255ce9 Mon Sep 17 00:00:00 2001 From: Inaky Perez-Gonzalez Date: Mon, 31 Aug 2009 17:57:56 -0700 Subject: wimax/i2400m: during probe, call sdio_disable at most once In the Intel Wireless Multicomm 3200, the initialization is orchestrated by a component called Top. This component also monitors how many times a function is reset (via sdio_disable) to detect possible issues and will reset the whole multifunction device if any function triggers a maximum reset level. During WiMAX's probe, the driver needs to wait for Top to come up before it can enable the WiMAX function. If it cannot, it will return -ENODEV and the Top driver will rescan the SDIO bus once done loading. Currently, the WiMAX SDIO probe routine was trying a few times before returning -ENODEV, and this was triggering Top's too-many-resets detector. This is, in any case, unnecessary because the Top driver will force the bus rescan when the functions can be probed successfully. Added then a maxtries argument to i2400ms_enable_func() and set it to 1 when calling from probe. We want to reuse this function instead of flat calling out sdio_enable_func() to take advantage of hardware quirk workarounds. Reported-by: Cindy H Kao Signed-off-by: Inaky Perez-Gonzalez --- drivers/net/wimax/i2400m/sdio.c | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) (limited to 'drivers/net/wimax') diff --git a/drivers/net/wimax/i2400m/sdio.c b/drivers/net/wimax/i2400m/sdio.c index 9d6046f58cae..7c1b843b63e9 100644 --- a/drivers/net/wimax/i2400m/sdio.c +++ b/drivers/net/wimax/i2400m/sdio.c @@ -95,17 +95,23 @@ static const struct i2400m_poke_table i2400ms_pokes[] = { * when we ask it to explicitly doing). Tries until a timeout is * reached. * + * The @maxtries argument indicates how many times (at most) it should + * be tried to enable the function. 0 means forever. This acts along + * with the timeout (ie: it'll stop trying as soon as the maximum + * number of tries is reached _or_ as soon as the 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) +int i2400ms_enable_function(struct sdio_func *func, unsigned maxtries) { u64 timeout; int err; struct device *dev = &func->dev; + unsigned tries = 0; d_fnstart(3, dev, "(func %p)\n", func); /* Setup timeout (FIXME: This needs to read the CIS table to @@ -131,6 +137,10 @@ int i2400ms_enable_function(struct sdio_func *func) } d_printf(2, dev, "SDIO function failed to enable: %d\n", err); sdio_release_host(func); + if (maxtries > 0 && ++tries >= maxtries) { + err = -ETIME; + break; + } msleep(I2400MS_INIT_SLEEP_INTERVAL); } /* If timed out, device is not there yet -- get -ENODEV so @@ -305,7 +315,7 @@ do_bus_reset: /* Wait for the device to settle */ msleep(40); - result = i2400ms_enable_function(i2400ms->func); + result = i2400ms_enable_function(i2400ms->func, 0); if (result >= 0) i2400ms_rx_setup(i2400ms); } else @@ -452,7 +462,7 @@ int i2400ms_probe(struct sdio_func *func, goto error_set_blk_size; } - result = i2400ms_enable_function(i2400ms->func); + result = i2400ms_enable_function(i2400ms->func, 1); if (result < 0) { dev_err(dev, "Cannot enable SDIO function: %d\n", result); goto error_func_enable; -- cgit v1.2.3 From 4dc1bf074e4db5aa281a7b82ceebb24df98922d2 Mon Sep 17 00:00:00 2001 From: Inaky Perez-Gonzalez Date: Wed, 2 Sep 2009 15:31:48 -0700 Subject: wimax/i2400m: add missing debug submodule definition The i2400m driver was missing the definition for the sysfs debug level, which is declared in debug-levels.h. Signed-off-by: Inaky Perez-Gonzalez --- drivers/net/wimax/i2400m/driver.c | 1 + 1 file changed, 1 insertion(+) (limited to 'drivers/net/wimax') diff --git a/drivers/net/wimax/i2400m/driver.c b/drivers/net/wimax/i2400m/driver.c index 20d574ca9183..7ba00de5dd9b 100644 --- a/drivers/net/wimax/i2400m/driver.c +++ b/drivers/net/wimax/i2400m/driver.c @@ -785,6 +785,7 @@ struct d_level D_LEVEL[] = { D_SUBMODULE_DEFINE(netdev), D_SUBMODULE_DEFINE(rfkill), D_SUBMODULE_DEFINE(rx), + D_SUBMODULE_DEFINE(sysfs), D_SUBMODULE_DEFINE(tx), }; size_t D_LEVEL_SIZE = ARRAY_SIZE(D_LEVEL); -- cgit v1.2.3 From 4c2b1a11646bf74e2926ce8b13a21884adc1e05c Mon Sep 17 00:00:00 2001 From: Inaky Perez-Gonzalez Date: Wed, 2 Sep 2009 15:36:05 -0700 Subject: wimax: allow specifying debug levels as command line option Add "debug" module options to all the wimax modules (including drivers) so that the debug levels can be set upon kernel boot or module load time. This is needed as currently there was a limitation where the debug levels could only be set when a device was succesfully enumerated. This made it difficult to debug issues that made a device not probe properly. Signed-off-by: Inaky Perez-Gonzalez --- drivers/net/wimax/i2400m/driver.c | 10 ++++++ drivers/net/wimax/i2400m/sdio.c | 10 ++++++ drivers/net/wimax/i2400m/usb.c | 9 +++++ include/linux/wimax/debug.h | 72 +++++++++++++++++++++++++++++++++++++++ net/wimax/stack.c | 11 ++++++ 5 files changed, 112 insertions(+) (limited to 'drivers/net/wimax') diff --git a/drivers/net/wimax/i2400m/driver.c b/drivers/net/wimax/i2400m/driver.c index 7ba00de5dd9b..e3b2c246cad7 100644 --- a/drivers/net/wimax/i2400m/driver.c +++ b/drivers/net/wimax/i2400m/driver.c @@ -90,6 +90,14 @@ MODULE_PARM_DESC(power_save_disabled, "False by default (so the device is told to do power " "saving)."); +static char i2400m_debug_params[128]; +module_param_string(debug, i2400m_debug_params, sizeof(i2400m_debug_params), + 0644); +MODULE_PARM_DESC(debug, + "String of space-separated NAME:VALUE pairs, where NAMEs " + "are the different debug submodules and VALUE are the " + "initial debug value to set."); + /** * i2400m_queue_work - schedule work on a i2400m's queue * @@ -794,6 +802,8 @@ size_t D_LEVEL_SIZE = ARRAY_SIZE(D_LEVEL); static int __init i2400m_driver_init(void) { + d_parse_params(D_LEVEL, D_LEVEL_SIZE, i2400m_debug_params, + "i2400m.debug"); return 0; } module_init(i2400m_driver_init); diff --git a/drivers/net/wimax/i2400m/sdio.c b/drivers/net/wimax/i2400m/sdio.c index 7c1b843b63e9..2d2cc5ac6d86 100644 --- a/drivers/net/wimax/i2400m/sdio.c +++ b/drivers/net/wimax/i2400m/sdio.c @@ -71,6 +71,14 @@ static int ioe_timeout = 2; module_param(ioe_timeout, int, 0); +static char i2400ms_debug_params[128]; +module_param_string(debug, i2400ms_debug_params, sizeof(i2400ms_debug_params), + 0644); +MODULE_PARM_DESC(debug, + "String of space-separated NAME:VALUE pairs, where NAMEs " + "are the different debug submodules and VALUE are the " + "initial debug value to set."); + /* Our firmware file name list */ static const char *i2400ms_bus_fw_names[] = { #define I2400MS_FW_FILE_NAME "i2400m-fw-sdio-1.3.sbcf" @@ -559,6 +567,8 @@ struct sdio_driver i2400m_sdio_driver = { static int __init i2400ms_driver_init(void) { + d_parse_params(D_LEVEL, D_LEVEL_SIZE, i2400ms_debug_params, + "i2400m_sdio.debug"); return sdio_register_driver(&i2400m_sdio_driver); } module_init(i2400ms_driver_init); diff --git a/drivers/net/wimax/i2400m/usb.c b/drivers/net/wimax/i2400m/usb.c index a5879e21bbf3..063422290a43 100644 --- a/drivers/net/wimax/i2400m/usb.c +++ b/drivers/net/wimax/i2400m/usb.c @@ -71,6 +71,13 @@ #define D_SUBMODULE usb #include "usb-debug-levels.h" +static char i2400mu_debug_params[128]; +module_param_string(debug, i2400mu_debug_params, sizeof(i2400mu_debug_params), + 0644); +MODULE_PARM_DESC(debug, + "String of space-separated NAME:VALUE pairs, where NAMEs " + "are the different debug submodules and VALUE are the " + "initial debug value to set."); /* Our firmware file name */ static const char *i2400mu_bus_fw_names[] = { @@ -633,6 +640,8 @@ struct usb_driver i2400mu_driver = { static int __init i2400mu_driver_init(void) { + d_parse_params(D_LEVEL, D_LEVEL_SIZE, i2400mu_debug_params, + "i2400m_usb.debug"); return usb_register(&i2400mu_driver); } module_init(i2400mu_driver_init); diff --git a/include/linux/wimax/debug.h b/include/linux/wimax/debug.h index c703e0340423..db8096e88533 100644 --- a/include/linux/wimax/debug.h +++ b/include/linux/wimax/debug.h @@ -450,4 +450,76 @@ do { \ }) +static inline +void d_submodule_set(struct d_level *d_level, size_t d_level_size, + const char *submodule, u8 level, const char *tag) +{ + struct d_level *itr, *top; + int index = -1; + + for (itr = d_level, top = itr + d_level_size; itr < top; itr++) { + index++; + if (itr->name == NULL) { + printk(KERN_ERR "%s: itr->name NULL?? (%p, #%d)\n", + tag, itr, index); + continue; + } + if (!strcmp(itr->name, submodule)) { + itr->level = level; + return; + } + } + printk(KERN_ERR "%s: unknown submodule %s\n", tag, submodule); +} + + +/** + * d_parse_params - Parse a string with debug parameters from the + * command line + * + * @d_level: level structure (D_LEVEL) + * @d_level_size: number of items in the level structure + * (D_LEVEL_SIZE). + * @_params: string with the parameters; this is a space (not tab!) + * separated list of NAME:VALUE, where value is the debug level + * and NAME is the name of the submodule. + * @tag: string for error messages (example: MODULE.ARGNAME). + */ +static inline +void d_parse_params(struct d_level *d_level, size_t d_level_size, + const char *_params, const char *tag) +{ + char submodule[130], *params, *params_orig, *token, *colon; + unsigned level, tokens; + + if (_params == NULL) + return; + params_orig = kstrdup(_params, GFP_KERNEL); + params = params_orig; + while (1) { + token = strsep(¶ms, " "); + if (token == NULL) + break; + if (*token == '\0') /* eat joint spaces */ + continue; + /* kernel's sscanf %s eats until whitespace, so we + * replace : by \n so it doesn't get eaten later by + * strsep */ + colon = strchr(token, ':'); + if (colon != NULL) + *colon = '\n'; + tokens = sscanf(token, "%s\n%u", submodule, &level); + if (colon != NULL) + *colon = ':'; /* set back, for error messages */ + if (tokens == 2) + d_submodule_set(d_level, d_level_size, + submodule, level, tag); + else + printk(KERN_ERR "%s: can't parse '%s' as a " + "SUBMODULE:LEVEL (%d tokens)\n", + tag, token, tokens); + } + kfree(params_orig); +} + #endif /* #ifndef __debug__h__ */ diff --git a/net/wimax/stack.c b/net/wimax/stack.c index 79fb7d7c640f..c8866412f830 100644 --- a/net/wimax/stack.c +++ b/net/wimax/stack.c @@ -60,6 +60,14 @@ #define D_SUBMODULE stack #include "debug-levels.h" +static char wimax_debug_params[128]; +module_param_string(debug, wimax_debug_params, sizeof(wimax_debug_params), + 0644); +MODULE_PARM_DESC(debug, + "String of space-separated NAME:VALUE pairs, where NAMEs " + "are the different debug submodules and VALUE are the " + "initial debug value to set."); + /* * Authoritative source for the RE_STATE_CHANGE attribute policy * @@ -562,6 +570,9 @@ int __init wimax_subsys_init(void) int result, cnt; d_fnstart(4, NULL, "()\n"); + d_parse_params(D_LEVEL, D_LEVEL_SIZE, wimax_debug_params, + "wimax.debug"); + snprintf(wimax_gnl_family.name, sizeof(wimax_gnl_family.name), "WiMAX"); result = genl_register_family(&wimax_gnl_family); -- cgit v1.2.3 From 59bdc4be0b819173a8f840fc11ccb82d6f2ca64b Mon Sep 17 00:00:00 2001 From: Inaky Perez-Gonzalez Date: Wed, 2 Sep 2009 15:41:23 -0700 Subject: wimax/i2400m: workaround not-so-working %zd printf format The kernel's %zd modifier does not really work. Use %ld (has to cast ssize_t to long). Signed-off-by: Inaky Perez-Gonzalez --- drivers/net/wimax/i2400m/usb-fw.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'drivers/net/wimax') diff --git a/drivers/net/wimax/i2400m/usb-fw.c b/drivers/net/wimax/i2400m/usb-fw.c index f162c815d505..b59aee0ee649 100644 --- a/drivers/net/wimax/i2400m/usb-fw.c +++ b/drivers/net/wimax/i2400m/usb-fw.c @@ -330,8 +330,8 @@ 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); + d_fnend(8, dev, "(i2400m %p ack %p size %zu) = %ld\n", + i2400m, ack, ack_size, (long) result); return result; error_exceeded: -- cgit v1.2.3 From 32742e6158657f19ad31653705bef56d983508e7 Mon Sep 17 00:00:00 2001 From: Inaky Perez-Gonzalez Date: Thu, 3 Sep 2009 15:56:40 -0700 Subject: wimax/i2400m: decide properly if using signed vs non-signed firmware loading The i2400m based devices can boot two main types of firmware images: signed and non-signed. Signed images have signature data included that must match that of a certificate stored in the device. Currently the code is making the decission on what type of firmware load (signed vs non-signed) is going to be loaded based on a hardcoded decission in __i2400m_ack_verify(), based on the barker the device sent upon boot. This is not flexible enough as future hardware will emit more barkers; thus the bit has to be set in a place where there is better knowledge of what is going on. This will be done in follow-up commits -- however this patch paves the way for it. So the querying of the mode is packed into i2400m_boot_is_signed(); the main changes are just using i2400m_boot_is_signed() to determine the method to follow and setting i2400m->sboot in i2400m_is_boot_barker(). The modifications in i2400m_dnload_init() and i2400m_dnload_finalize() are just reorganizing the order of the if blocks and thus look larger than they really are. Signed-off-by: Inaky Perez-Gonzalez --- drivers/net/wimax/i2400m/fw.c | 35 +++++++++++++++++++++-------------- include/linux/wimax/i2400m.h | 10 ---------- 2 files changed, 21 insertions(+), 24 deletions(-) (limited to 'drivers/net/wimax') diff --git a/drivers/net/wimax/i2400m/fw.c b/drivers/net/wimax/i2400m/fw.c index 92d4d605dc2d..c962a8d8df7e 100644 --- a/drivers/net/wimax/i2400m/fw.c +++ b/drivers/net/wimax/i2400m/fw.c @@ -508,6 +508,17 @@ error_send: } +/* + * Indicate if the device emitted a reboot barker that indicates + * "signed boot" + */ +static +unsigned i2400m_boot_is_signed(struct i2400m *i2400m) +{ + return likely(i2400m->sboot); +} + + /* * Do the final steps of uploading firmware * @@ -529,7 +540,7 @@ int i2400m_dnload_finalize(struct i2400m *i2400m, d_fnstart(3, dev, "offset %zu\n", offset); cmd = (void *) bcf + offset; - if (i2400m->sboot == 0) { + if (i2400m_boot_is_signed(i2400m) == 0) { struct i2400m_bootrom_header jump_ack; d_printf(1, dev, "unsecure boot, jumping to 0x%08x\n", le32_to_cpu(cmd->target_addr)); @@ -846,28 +857,24 @@ int i2400m_dnload_init(struct i2400m *i2400m, const struct i2400m_bcf_hdr *bcf) { int result; struct device *dev = i2400m_dev(i2400m); - u32 module_id = le32_to_cpu(bcf->module_id); - if (i2400m->sboot == 0 - && (module_id & I2400M_BCF_MOD_ID_POKES) == 0) { - /* non-signed boot process without pokes */ - result = i2400m_dnload_init_nonsigned(i2400m); + if (i2400m_boot_is_signed(i2400m)) { + d_printf(1, dev, "signed boot\n"); + result = i2400m_dnload_init_signed(i2400m, bcf); if (result == -ERESTARTSYS) return result; if (result < 0) - dev_err(dev, "fw %s: non-signed download " + dev_err(dev, "firmware %s: signed boot download " "initialization failed: %d\n", i2400m->fw_name, result); - } else if (i2400m->sboot == 0 - && (module_id & I2400M_BCF_MOD_ID_POKES)) { - /* non-signed boot process with pokes, nothing to do */ - result = 0; - } else { /* signed boot process */ - result = i2400m_dnload_init_signed(i2400m, bcf); + } else { + /* non-signed boot process without pokes */ + d_printf(1, dev, "non-signed boot\n"); + result = i2400m_dnload_init_nonsigned(i2400m); if (result == -ERESTARTSYS) return result; if (result < 0) - dev_err(dev, "fw %s: signed boot download " + dev_err(dev, "firmware %s: non-signed download " "initialization failed: %d\n", i2400m->fw_name, result); } diff --git a/include/linux/wimax/i2400m.h b/include/linux/wimax/i2400m.h index 433693ef2bb0..d6e2a3595682 100644 --- a/include/linux/wimax/i2400m.h +++ b/include/linux/wimax/i2400m.h @@ -168,16 +168,6 @@ enum i2400m_brh { }; -/* Constants for bcf->module_id */ -enum i2400m_bcf_mod_id { - /* Firmware file carries its own pokes -- pokes are a set of - * magical values that have to be written in certain memory - * addresses to get the device up and ready for firmware - * download when it is in non-signed boot mode. */ - I2400M_BCF_MOD_ID_POKES = 0x000000001, -}; - - /** * i2400m_bootrom_header - Header for a boot-mode command * -- cgit v1.2.3 From aba3792ac2d7c808a2d2fd2adf89531e083bdb90 Mon Sep 17 00:00:00 2001 From: Inaky Perez-Gonzalez Date: Thu, 3 Sep 2009 15:14:29 -0700 Subject: wimax/i2400m: rework bootrom initialization to be more flexible This modifies the bootrom initialization code of the i2400m driver so it can more easily support upcoming hardware. Currently, the code detects two types of barkers (magic numbers) sent by the device to indicate the types of firmware it would take (signed vs non-signed). This schema is extended so that multiple reboot barkers are recognized; upcoming hw will expose more types barkers which will have to match a header in the firmware image before we can load it. For that, a barker database is introduced; the first time the device sends a barker, it is matched in the database. That gives the driver the information needed to decide how to upload the firmware and which types of firmware to use. The database can be populated from module parameters. The execution flow is not altered; a new function (i2400m_is_boot_barker) is introduced to determine in the RX path if the device has sent a boot barker. This function is becoming heavier, so it is put away from the hot reception path [this is why there is some reorganization in sdio-rx.c:i2400ms_rx and usb-notifc.c:i2400mu_notification_grok()]. The documentation on the process has also been updated. All these modifications are heavily based on previous work by Dirk Brandewie . Signed-off-by: Inaky Perez-Gonzalez --- drivers/net/wimax/i2400m/driver.c | 12 +- drivers/net/wimax/i2400m/fw.c | 313 ++++++++++++++++++++++++++++++----- drivers/net/wimax/i2400m/i2400m.h | 36 ++-- drivers/net/wimax/i2400m/rx.c | 22 +++ drivers/net/wimax/i2400m/sdio-rx.c | 25 ++- drivers/net/wimax/i2400m/usb-notif.c | 32 ++-- 6 files changed, 355 insertions(+), 85 deletions(-) (limited to 'drivers/net/wimax') diff --git a/drivers/net/wimax/i2400m/driver.c b/drivers/net/wimax/i2400m/driver.c index e3b2c246cad7..73f45ea010a7 100644 --- a/drivers/net/wimax/i2400m/driver.c +++ b/drivers/net/wimax/i2400m/driver.c @@ -98,6 +98,15 @@ MODULE_PARM_DESC(debug, "are the different debug submodules and VALUE are the " "initial debug value to set."); +static char i2400m_barkers_params[128]; +module_param_string(barkers, i2400m_barkers_params, + sizeof(i2400m_barkers_params), 0644); +MODULE_PARM_DESC(barkers, + "String of comma-separated 32-bit values; each is " + "recognized as the value the device sends as a reboot " + "signal; values are appended to a list--setting one value " + "as zero cleans the existing list and starts a new one."); + /** * i2400m_queue_work - schedule work on a i2400m's queue * @@ -804,7 +813,7 @@ int __init i2400m_driver_init(void) { d_parse_params(D_LEVEL, D_LEVEL_SIZE, i2400m_debug_params, "i2400m.debug"); - return 0; + return i2400m_barker_db_init(i2400m_barkers_params); } module_init(i2400m_driver_init); @@ -813,6 +822,7 @@ void __exit i2400m_driver_exit(void) { /* for scheds i2400m_dev_reset_handle() */ flush_scheduled_work(); + i2400m_barker_db_exit(); return; } module_exit(i2400m_driver_exit); diff --git a/drivers/net/wimax/i2400m/fw.c b/drivers/net/wimax/i2400m/fw.c index c962a8d8df7e..798564eb0e99 100644 --- a/drivers/net/wimax/i2400m/fw.c +++ b/drivers/net/wimax/i2400m/fw.c @@ -40,11 +40,9 @@ * * THE PROCEDURE * - * (this is decribed for USB, but for SDIO is similar) - * - * The 2400m works in two modes: boot-mode or normal mode. In boot - * mode we can execute only a handful of commands targeted at - * uploading the firmware and launching it. + * The 2400m and derived devices work in two modes: boot-mode or + * normal mode. In boot mode we can execute only a handful of commands + * targeted at uploading the firmware and launching it. * * The 2400m enters boot mode when it is first connected to the * system, when it crashes and when you ask it to reboot. There are @@ -52,18 +50,26 @@ * firmwares signed with a certain private key, non-signed takes any * firmware. Normal hardware takes only signed firmware. * - * Upon entrance to boot mode, the device sends a few zero length - * packets (ZLPs) on the notification endpoint, then a reboot barker - * (4 le32 words with value I2400M_{S,N}BOOT_BARKER). We ack it by - * sending the same barker on the bulk out endpoint. The device acks - * with a reboot ack barker (4 le32 words with value 0xfeedbabe) and - * then the device is fully rebooted. At this point we can upload the - * firmware. + * On boot mode, in USB, we write to the device using the bulk out + * endpoint and read from it in the notification endpoint. In SDIO we + * talk to it via the write address and read from the read address. + * + * Upon entrance to boot mode, the device sends (preceeded with a few + * zero length packets (ZLPs) on the notification endpoint in USB) a + * reboot barker (4 le32 words with the same value). We ack it by + * sending the same barker to the device. The device acks with a + * reboot ack barker (4 le32 words with value I2400M_ACK_BARKER) and + * then is fully booted. At this point we can upload the firmware. + * + * Note that different iterations of the device and EEPROM + * configurations will send different [re]boot barkers; these are + * collected in i2400m_barker_db along with the firmware + * characteristics they require. * * This process is accomplished by the i2400m_bootrom_init() * function. All the device interaction happens through the * i2400m_bm_cmd() [boot mode command]. Special return values will - * indicate if the device resets. + * indicate if the device did reset during the process. * * After this, we read the MAC address and then (if needed) * reinitialize the device. We need to read it ahead of time because @@ -101,6 +107,11 @@ * * ROADMAP * + * i2400m_barker_db_init Called by i2400m_driver_init() + * i2400m_barker_db_add + * + * i2400m_barker_db_exit Called by i2400m_driver_exit() + * * i2400m_dev_bootstrap Called by __i2400m_dev_start() * request_firmware * i2400m_fw_check @@ -125,6 +136,7 @@ * i2400m->bus_bm_cmd_send() * i2400m->bus_bm_wait_for_ack * __i2400m_bm_ack_verify + * i2400m_is_boot_barker * * i2400m_bm_cmd_prepare Used by bus-drivers to prep * commands before sending @@ -174,6 +186,237 @@ void i2400m_bm_cmd_prepare(struct i2400m_bootrom_header *cmd) EXPORT_SYMBOL_GPL(i2400m_bm_cmd_prepare); +/* + * Database of known barkers. + * + * A barker is what the device sends indicating he is ready to be + * bootloaded. Different versions of the device will send different + * barkers. Depending on the barker, it might mean the device wants + * some kind of firmware or the other. + */ +static struct i2400m_barker_db { + __le32 data[4]; +} *i2400m_barker_db; +static size_t i2400m_barker_db_used, i2400m_barker_db_size; + + +static +int i2400m_zrealloc_2x(void **ptr, size_t *_count, size_t el_size, + gfp_t gfp_flags) +{ + size_t old_count = *_count, + new_count = old_count ? 2 * old_count : 2, + old_size = el_size * old_count, + new_size = el_size * new_count; + void *nptr = krealloc(*ptr, new_size, gfp_flags); + if (nptr) { + /* zero the other half or the whole thing if old_count + * was zero */ + if (old_size == 0) + memset(nptr, 0, new_size); + else + memset(nptr + old_size, 0, old_size); + *_count = new_count; + *ptr = nptr; + return 0; + } else + return -ENOMEM; +} + + +/* + * Add a barker to the database + * + * This cannot used outside of this module and only at at module_init + * time. This is to avoid the need to do locking. + */ +static +int i2400m_barker_db_add(u32 barker_id) +{ + int result; + + struct i2400m_barker_db *barker; + if (i2400m_barker_db_used >= i2400m_barker_db_size) { + result = i2400m_zrealloc_2x( + (void **) &i2400m_barker_db, &i2400m_barker_db_size, + sizeof(i2400m_barker_db[0]), GFP_KERNEL); + if (result < 0) + return result; + } + barker = i2400m_barker_db + i2400m_barker_db_used++; + barker->data[0] = le32_to_cpu(barker_id); + barker->data[1] = le32_to_cpu(barker_id); + barker->data[2] = le32_to_cpu(barker_id); + barker->data[3] = le32_to_cpu(barker_id); + return 0; +} + + +void i2400m_barker_db_exit(void) +{ + kfree(i2400m_barker_db); + i2400m_barker_db = NULL; + i2400m_barker_db_size = 0; + i2400m_barker_db_used = 0; +} + + +/* + * Helper function to add all the known stable barkers to the barker + * database. + */ +static +int i2400m_barker_db_known_barkers(void) +{ + int result; + + result = i2400m_barker_db_add(I2400M_NBOOT_BARKER); + if (result < 0) + goto error_add; + result = i2400m_barker_db_add(I2400M_SBOOT_BARKER); + if (result < 0) + goto error_add; +error_add: + return result; +} + + +/* + * Initialize the barker database + * + * This can only be used from the module_init function for this + * module; this is to avoid the need to do locking. + * + * @options: command line argument with extra barkers to + * recognize. This is a comma-separated list of 32-bit hex + * numbers. They are appended to the existing list. Setting 0 + * cleans the existing list and starts a new one. + */ +int i2400m_barker_db_init(const char *_options) +{ + int result; + char *options = NULL, *options_orig, *token; + + i2400m_barker_db = NULL; + i2400m_barker_db_size = 0; + i2400m_barker_db_used = 0; + + result = i2400m_barker_db_known_barkers(); + if (result < 0) + goto error_add; + /* parse command line options from i2400m.barkers */ + if (_options != NULL) { + unsigned barker; + + options_orig = kstrdup(_options, GFP_KERNEL); + if (options_orig == NULL) + goto error_parse; + options = options_orig; + + while ((token = strsep(&options, ",")) != NULL) { + if (*token == '\0') /* eat joint commas */ + continue; + if (sscanf(token, "%x", &barker) != 1 + || barker > 0xffffffff) { + printk(KERN_ERR "%s: can't recognize " + "i2400m.barkers value '%s' as " + "a 32-bit number\n", + __func__, token); + result = -EINVAL; + goto error_parse; + } + if (barker == 0) { + /* clean list and start new */ + i2400m_barker_db_exit(); + continue; + } + result = i2400m_barker_db_add(barker); + if (result < 0) + goto error_add; + } + kfree(options_orig); + } + return 0; + +error_parse: +error_add: + kfree(i2400m_barker_db); + return result; +} + + +/* + * Recognize a boot barker + * + * @buf: buffer where the boot barker. + * @buf_size: size of the buffer (has to be 16 bytes). It is passed + * here so the function can check it for the caller. + * + * Note that as a side effect, upon identifying the obtained boot + * barker, this function will set i2400m->barker to point to the right + * barker database entry. Subsequent calls to the function will result + * in verifying that the same type of boot barker is returned when the + * device [re]boots (as long as the same device instance is used). + * + * Return: 0 if @buf matches a known boot barker. -ENOENT if the + * buffer in @buf doesn't match any boot barker in the database or + * -EILSEQ if the buffer doesn't have the right size. + */ +int i2400m_is_boot_barker(struct i2400m *i2400m, + const void *buf, size_t buf_size) +{ + int result; + struct device *dev = i2400m_dev(i2400m); + struct i2400m_barker_db *barker; + int i; + + result = -ENOENT; + if (buf_size != sizeof(i2400m_barker_db[i].data)) + return result; + + /* Short circuit if we have already discovered the barker + * associated with the device. */ + if (i2400m->barker + && !memcmp(buf, i2400m->barker, sizeof(i2400m->barker->data))) { + unsigned index = (i2400m->barker - i2400m_barker_db) + / sizeof(*i2400m->barker); + d_printf(2, dev, "boot barker cache-confirmed #%u/%08x\n", + index, le32_to_cpu(i2400m->barker->data[0])); + return 0; + } + + for (i = 0; i < i2400m_barker_db_used; i++) { + barker = &i2400m_barker_db[i]; + BUILD_BUG_ON(sizeof(barker->data) != 16); + if (memcmp(buf, barker->data, sizeof(barker->data))) + continue; + + if (i2400m->barker == NULL) { + i2400m->barker = barker; + d_printf(1, dev, "boot barker set to #%u/%08x\n", + i, le32_to_cpu(barker->data[0])); + if (barker->data[0] == le32_to_cpu(I2400M_NBOOT_BARKER)) + i2400m->sboot = 0; + else + i2400m->sboot = 1; + } else if (i2400m->barker != barker) { + dev_err(dev, "HW inconsistency: device " + "reports a different boot barker " + "than set (from %08x to %08x)\n", + le32_to_cpu(i2400m->barker->data[0]), + le32_to_cpu(barker->data[0])); + result = -EIO; + } else + d_printf(2, dev, "boot barker confirmed #%u/%08x\n", + i, le32_to_cpu(barker->data[0])); + result = 0; + break; + } + return result; +} +EXPORT_SYMBOL_GPL(i2400m_is_boot_barker); + + /* * Verify the ack data received * @@ -204,20 +447,10 @@ ssize_t __i2400m_bm_ack_verify(struct i2400m *i2400m, int opcode, opcode, ack_size, sizeof(*ack)); goto error_ack_short; } - if (ack_size == sizeof(i2400m_NBOOT_BARKER) - && memcmp(ack, i2400m_NBOOT_BARKER, sizeof(*ack)) == 0) { - result = -ERESTARTSYS; - i2400m->sboot = 0; - d_printf(6, dev, "boot-mode cmd %d: " - "HW non-signed boot barker\n", opcode); - goto error_reboot; - } - if (ack_size == sizeof(i2400m_SBOOT_BARKER) - && memcmp(ack, i2400m_SBOOT_BARKER, sizeof(*ack)) == 0) { + result = i2400m_is_boot_barker(i2400m, ack, ack_size); + if (result >= 0) { result = -ERESTARTSYS; - i2400m->sboot = 1; - d_printf(6, dev, "boot-mode cmd %d: HW signed reboot barker\n", - opcode); + d_printf(6, dev, "boot-mode cmd %d: HW boot barker\n", opcode); goto error_reboot; } if (ack_size == sizeof(i2400m_ACK_BARKER) @@ -590,9 +823,6 @@ int i2400m_dnload_finalize(struct i2400m *i2400m, * * < 0 errno code on error, 0 if ok. * - * i2400m->sboot set to 0 for unsecure boot process, 1 for secure - * boot process. - * * Description: * * Tries hard enough to put the device in boot-mode. There are two @@ -619,7 +849,7 @@ int i2400m_bootrom_init(struct i2400m *i2400m, enum i2400m_bri flags) int count = i2400m->bus_bm_retries; int ack_timeout_cnt = 1; - BUILD_BUG_ON(sizeof(*cmd) != sizeof(i2400m_NBOOT_BARKER)); + BUILD_BUG_ON(sizeof(*cmd) != sizeof(i2400m_barker_db[0].data)); BUILD_BUG_ON(sizeof(ack) != sizeof(i2400m_ACK_BARKER)); d_fnstart(4, dev, "(i2400m %p flags 0x%08x)\n", i2400m, flags); @@ -647,8 +877,14 @@ do_reboot: case -ETIMEDOUT: /* device has timed out, we might be in boot * mode already and expecting an ack, let's try * that */ - dev_info(dev, "warm reset timed out, trying an ack\n"); - goto do_reboot_ack; + if (i2400m->barker == NULL) { + dev_info(dev, "warm reset timed out, unknown barker " + "type, rebooting\n"); + goto do_reboot; + } else { + dev_info(dev, "warm reset timed out, trying an ack\n"); + goto do_reboot_ack; + } case -EPROTO: case -ESHUTDOWN: /* dev is gone */ case -EINTR: /* user cancelled */ @@ -664,12 +900,7 @@ do_reboot: * notification and report it as -EISCONN. */ do_reboot_ack: d_printf(4, dev, "device reboot ack: sending ack [%d # left]\n", count); - if (i2400m->sboot == 0) - memcpy(cmd, i2400m_NBOOT_BARKER, - sizeof(i2400m_NBOOT_BARKER)); - else - memcpy(cmd, i2400m_SBOOT_BARKER, - sizeof(i2400m_SBOOT_BARKER)); + memcpy(cmd, i2400m->barker->data, sizeof(i2400m->barker->data)); result = i2400m_bm_cmd(i2400m, cmd, sizeof(*cmd), &ack, sizeof(ack), I2400M_BM_CMD_RAW); switch (result) { @@ -682,10 +913,8 @@ do_reboot_ack: d_printf(4, dev, "reboot ack: got ack barker - good\n"); break; case -ETIMEDOUT: /* no response, maybe it is the other type? */ - if (ack_timeout_cnt-- >= 0) { - d_printf(4, dev, "reboot ack timedout: " - "trying the other type?\n"); - i2400m->sboot = !i2400m->sboot; + if (ack_timeout_cnt-- < 0) { + d_printf(4, dev, "reboot ack timedout: retrying\n"); goto do_reboot_ack; } else { dev_err(dev, "reboot ack timedout too long: " diff --git a/drivers/net/wimax/i2400m/i2400m.h b/drivers/net/wimax/i2400m/i2400m.h index 73b4e6a15135..bcb1882ed741 100644 --- a/drivers/net/wimax/i2400m/i2400m.h +++ b/drivers/net/wimax/i2400m/i2400m.h @@ -194,6 +194,7 @@ enum i2400m_reset_type { struct i2400m_reset_ctx; struct i2400m_roq; +struct i2400m_barker_db; /** * struct i2400m - descriptor for an Intel 2400m @@ -419,6 +420,12 @@ struct i2400m_roq; * * @fw_version: version of the firmware interface, Major.minor, * encoded in the high word and low word (major << 16 | minor). + * + * @barker: barker type that the device uses; this is initialized by + * i2400m_is_boot_barker() the first time it is called. Then it + * won't change during the life cycle of the device and everytime + * a boot barker is received, it is just verified for it being the + * same. */ struct i2400m { struct wimax_dev wimax_dev; /* FIRST! See doc */ @@ -484,6 +491,7 @@ struct i2400m { struct dentry *debugfs_dentry; const char *fw_name; /* name of the current firmware image */ unsigned long fw_version; /* version of the firmware interface */ + struct i2400m_barker_db *barker; }; @@ -574,6 +582,14 @@ 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); +extern int i2400m_is_boot_barker(struct i2400m *, const void *, size_t); +static inline +int i2400m_is_d2h_barker(const void *buf) +{ + const __le32 *barker = buf; + return le32_to_cpu(*barker) == I2400M_D2H_MSG_BARKER; +} +extern void i2400m_unknown_barker(struct i2400m *, const void *, size_t); /* Make/grok boot-rom header commands */ @@ -736,20 +752,6 @@ 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; /* @@ -848,6 +850,12 @@ void __i2400m_msleep(unsigned ms) #endif } + +/* module initialization helpers */ +extern int i2400m_barker_db_init(const char *); +extern void i2400m_barker_db_exit(void); + + /* Module parameters */ extern int i2400m_idle_mode_disabled; diff --git a/drivers/net/wimax/i2400m/rx.c b/drivers/net/wimax/i2400m/rx.c index 07c32e68909f..bcd411f1a854 100644 --- a/drivers/net/wimax/i2400m/rx.c +++ b/drivers/net/wimax/i2400m/rx.c @@ -1194,6 +1194,28 @@ error_msg_hdr_check: EXPORT_SYMBOL_GPL(i2400m_rx); +void i2400m_unknown_barker(struct i2400m *i2400m, + const void *buf, size_t size) +{ + struct device *dev = i2400m_dev(i2400m); + char prefix[64]; + const __le32 *barker = buf; + dev_err(dev, "RX: HW BUG? unknown barker %08x, " + "dropping %zu bytes\n", le32_to_cpu(*barker), size); + snprintf(prefix, sizeof(prefix), "%s %s: ", + dev_driver_string(dev), dev_name(dev)); + if (size > 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, size, 0); +} +EXPORT_SYMBOL(i2400m_unknown_barker); + + /* * Initialize the RX queue and infrastructure * diff --git a/drivers/net/wimax/i2400m/sdio-rx.c b/drivers/net/wimax/i2400m/sdio-rx.c index 1c9046914bd1..87263be4eedb 100644 --- a/drivers/net/wimax/i2400m/sdio-rx.c +++ b/drivers/net/wimax/i2400m/sdio-rx.c @@ -53,6 +53,7 @@ * i2400ms_irq() * i2400ms_rx() * __i2400ms_rx_get_size() + * i2400m_is_boot_barker() * i2400m_rx() * * i2400ms_rx_setup() @@ -158,7 +159,7 @@ void i2400ms_rx(struct i2400ms *i2400ms) } rmb(); /* make sure we get boot_mode from dev_reset_handle */ - if (i2400m->boot_mode == 1) { + if (unlikely(i2400m->boot_mode == 1)) { spin_lock(&i2400m->rx_lock); i2400ms->bm_ack_size = rx_size; spin_unlock(&i2400m->rx_lock); @@ -166,17 +167,26 @@ void i2400ms_rx(struct i2400ms *i2400ms) 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)))) { + goto out; + } + ret = -EIO; + if (unlikely(rx_size < sizeof(__le32))) { + dev_err(dev, "HW BUG? only %zu bytes received\n", rx_size); + goto error_bad_size; + } + if (likely(i2400m_is_d2h_barker(skb->data))) { + skb_put(skb, rx_size); + i2400m_rx(i2400m, skb); + } else if (unlikely(i2400m_is_boot_barker(i2400m, + skb->data, rx_size))) { 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); + i2400m_unknown_barker(i2400m, skb->data, rx_size); + kfree_skb(skb); } +out: d_fnend(7, dev, "(i2400ms %p) = void\n", i2400ms); return; @@ -184,6 +194,7 @@ error_memcpy_fromio: kfree_skb(skb); error_alloc_skb: error_get_size: +error_bad_size: d_fnend(7, dev, "(i2400ms %p) = %d\n", i2400ms, ret); return; } diff --git a/drivers/net/wimax/i2400m/usb-notif.c b/drivers/net/wimax/i2400m/usb-notif.c index 3e11e35cd696..a0751a347cdc 100644 --- a/drivers/net/wimax/i2400m/usb-notif.c +++ b/drivers/net/wimax/i2400m/usb-notif.c @@ -51,6 +51,7 @@ * * i2400mu_usb_notification_cb() Called when a URB is ready * i2400mu_notif_grok() + * i2400m_is_boot_barker() * i2400m_dev_reset_handle() * i2400mu_rx_kick() */ @@ -87,32 +88,21 @@ int i2400mu_notification_grok(struct i2400mu *i2400mu, const void *buf, 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)) + if (buf_len < sizeof(i2400m_ZERO_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))) { + ret = 0; + 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); + goto out; } + ret = i2400m_is_boot_barker(i2400m, buf, buf_len); + if (unlikely(ret >= 0)) + ret = i2400m_dev_reset_handle(i2400m); + else /* Unknown or unexpected data in the notif message */ + i2400m_unknown_barker(i2400m, buf, buf_len); error_bad_size: +out: d_fnend(4, dev, "(i2400m %p buf %p buf_len %zu) = %d\n", i2400mu, buf, buf_len, ret); return ret; -- cgit v1.2.3 From ebc5f62b76ad540ff7b3e438506638009e7812a6 Mon Sep 17 00:00:00 2001 From: Inaky Perez-Gonzalez Date: Thu, 3 Sep 2009 15:53:30 -0700 Subject: wimax/i2400m: retry loading firmware files in sequence The i2400m firmware loader is given a list of firmware files to try to load by the probe() function (which can be different based on the device's model / generation). Current code didn't attempt to load, check and try to boot with each file, but just to try to load if off disk. This is limiting in some cases, where we might want to try to load a firmware and if it fails to load onto the device, just fall back to another one. This changes the behaviour so all files are tried for being loaded from disk, checked and uploaded to the device until one suceeds in bringing the device up. Signed-off-by: Inaky Perez-Gonzalez --- drivers/net/wimax/i2400m/fw.c | 35 +++++++++++++++++------------------ 1 file changed, 17 insertions(+), 18 deletions(-) (limited to 'drivers/net/wimax') diff --git a/drivers/net/wimax/i2400m/fw.c b/drivers/net/wimax/i2400m/fw.c index 798564eb0e99..55fe011a9633 100644 --- a/drivers/net/wimax/i2400m/fw.c +++ b/drivers/net/wimax/i2400m/fw.c @@ -1288,7 +1288,7 @@ error_dev_rebooted: */ int i2400m_dev_bootstrap(struct i2400m *i2400m, enum i2400m_bri flags) { - int ret = 0, itr = 0; + int ret, itr; struct device *dev = i2400m_dev(i2400m); const struct firmware *fw; const struct i2400m_bcf_hdr *bcf; /* Firmware data */ @@ -1297,32 +1297,31 @@ int i2400m_dev_bootstrap(struct i2400m *i2400m, enum i2400m_bri flags) d_fnstart(5, dev, "(i2400m %p)\n", i2400m); /* Load firmware files to memory. */ - itr = 0; - while(1) { + for (itr = 0, bcf = NULL, ret = -ENOENT; ; itr++) { fw_name = i2400m->bus_fw_names[itr]; if (fw_name == NULL) { dev_err(dev, "Could not find a usable firmware image\n"); ret = -ENOENT; - goto error_no_fw; + break; } + d_printf(1, dev, "trying firmware %s (%d)\n", fw_name, itr); ret = request_firmware(&fw, fw_name, dev); - if (ret == 0) - break; /* got it */ - if (ret < 0) + if (ret < 0) { dev_err(dev, "fw %s: cannot load file: %d\n", fw_name, ret); - itr++; + continue; + } + bcf = (void *) fw->data; + i2400m->fw_name = fw_name; + ret = i2400m_fw_check(i2400m, bcf, fw->size); + if (ret >= 0) { + ret = i2400m_fw_dnload(i2400m, bcf, fw->size, flags); + if (ret >= 0) + break; + } else + dev_err(dev, "%s: cannot use, skipping\n", fw_name); + release_firmware(fw); } - - bcf = (void *) fw->data; - i2400m->fw_name = fw_name; - ret = i2400m_fw_check(i2400m, bcf, fw->size); - if (ret < 0) - goto error_fw_bad; - ret = i2400m_fw_dnload(i2400m, bcf, fw->size, flags); -error_fw_bad: - release_firmware(fw); -error_no_fw: d_fnend(5, dev, "(i2400m %p) = %d\n", i2400m, ret); return ret; } -- cgit v1.2.3 From 923d708fed9d47c7b4d67694500d766337663e29 Mon Sep 17 00:00:00 2001 From: Inaky Perez-Gonzalez Date: Fri, 4 Sep 2009 14:50:59 -0700 Subject: wimax/i2400m: fix reboot echo/ack barker deadlock The i2400m based devices can get in a sort of a deadlock some times; when they boot, they send a reboot "barker" (a magic number) and then the driver has to echo that same barker to ack reception (echo/ack). Then the device does a final ack by sending an ACK barker. The first time this happens, we don't know ahead of time with barker the device is going to send, as different device models and SKUs will send different barker depending on the EEPROM programming. If the device has sent the barker before the driver has been able to read it, the driver looses, as it doesn't know which barker it has to echo/ack back. With older devices, we tried a couple of combinations and that always worked; but now, with adding support for more, in which we have an unlimited number of new barkers, that is not an option. So we rework said case so that when the device gets stuck, we just cycle through all the known types until one forces the device to send an ack. Otherwise, the driver gives up and aborts. Signed-off-by: Inaky Perez-Gonzalez --- drivers/net/wimax/i2400m/fw.c | 63 ++++++++++++++++++++++++++++++++++--------- include/linux/wimax/i2400m.h | 2 +- 2 files changed, 52 insertions(+), 13 deletions(-) (limited to 'drivers/net/wimax') diff --git a/drivers/net/wimax/i2400m/fw.c b/drivers/net/wimax/i2400m/fw.c index 55fe011a9633..eef236d85af3 100644 --- a/drivers/net/wimax/i2400m/fw.c +++ b/drivers/net/wimax/i2400m/fw.c @@ -812,7 +812,7 @@ int i2400m_dnload_finalize(struct i2400m *i2400m, * * @i2400m: device descriptor * @flags: - * I2400M_BRI_SOFT: a reboot notification has been seen + * I2400M_BRI_SOFT: a reboot barker has been seen * already, so don't wait for it. * * I2400M_BRI_NO_REBOOT: Don't send a reboot command, but wait @@ -829,8 +829,9 @@ int i2400m_dnload_finalize(struct i2400m *i2400m, * main phases to this: * * a. (1) send a reboot command and (2) get a reboot barker - * b. (1) ack the reboot sending a reboot barker and (2) getting an - * ack barker in return + * + * b. (1) echo/ack the reboot sending the reboot barker back and (2) + * getting an ack barker in return * * We want to skip (a) in some cases [soft]. The state machine is * horrible, but it is basically: on each phase, send what has to be @@ -838,6 +839,16 @@ int i2400m_dnload_finalize(struct i2400m *i2400m, * have to backtrack and retry, so we keep a max tries counter for * that. * + * It sucks because we don't know ahead of time which is going to be + * the reboot barker (the device might send different ones depending + * on its EEPROM config) and once the device reboots and waits for the + * echo/ack reboot barker being sent back, it doesn't understand + * anything else. So we can be left at the point where we don't know + * what to send to it -- cold reset and bus reset seem to have little + * effect. So the function iterates (in this case) through all the + * known barkers and tries them all until an ACK is + * received. Otherwise, it gives up. + * * If we get a timeout after sending a warm reset, we do it again. */ int i2400m_bootrom_init(struct i2400m *i2400m, enum i2400m_bri flags) @@ -848,6 +859,7 @@ int i2400m_bootrom_init(struct i2400m *i2400m, enum i2400m_bri flags) struct i2400m_bootrom_header ack; int count = i2400m->bus_bm_retries; int ack_timeout_cnt = 1; + unsigned i; BUILD_BUG_ON(sizeof(*cmd) != sizeof(i2400m_barker_db[0].data)); BUILD_BUG_ON(sizeof(ack) != sizeof(i2400m_ACK_BARKER)); @@ -858,6 +870,7 @@ int i2400m_bootrom_init(struct i2400m *i2400m, enum i2400m_bri flags) if (flags & I2400M_BRI_SOFT) goto do_reboot_ack; do_reboot: + ack_timeout_cnt = 1; if (--count < 0) goto error_timeout; d_printf(4, dev, "device reboot: reboot command [%d # left]\n", @@ -869,22 +882,47 @@ do_reboot: flags &= ~I2400M_BRI_NO_REBOOT; switch (result) { case -ERESTARTSYS: + /* + * at this point, i2400m_bm_cmd(), through + * __i2400m_bm_ack_process(), has updated + * i2400m->barker and we are good to go. + */ d_printf(4, dev, "device reboot: got reboot barker\n"); break; case -EISCONN: /* we don't know how it got here...but we follow it */ d_printf(4, dev, "device reboot: got ack barker - whatever\n"); goto do_reboot; - case -ETIMEDOUT: /* device has timed out, we might be in boot - * mode already and expecting an ack, let's try - * that */ - if (i2400m->barker == NULL) { - dev_info(dev, "warm reset timed out, unknown barker " - "type, rebooting\n"); - goto do_reboot; - } else { - dev_info(dev, "warm reset timed out, trying an ack\n"); + case -ETIMEDOUT: + /* + * Device has timed out, we might be in boot mode + * already and expecting an ack; if we don't know what + * the barker is, we just send them all. Cold reset + * and bus reset don't work. Beats me. + */ + if (i2400m->barker != NULL) { + dev_err(dev, "device boot: reboot barker timed out, " + "trying (set) %08x echo/ack\n", + le32_to_cpu(i2400m->barker->data[0])); goto do_reboot_ack; } + for (i = 0; i < i2400m_barker_db_used; i++) { + struct i2400m_barker_db *barker = &i2400m_barker_db[i]; + memcpy(cmd, barker->data, sizeof(barker->data)); + result = i2400m_bm_cmd(i2400m, cmd, sizeof(*cmd), + &ack, sizeof(ack), + I2400M_BM_CMD_RAW); + if (result == -EISCONN) { + dev_warn(dev, "device boot: got ack barker " + "after sending echo/ack barker " + "#%d/%08x; rebooting j.i.c.\n", + i, le32_to_cpu(barker->data[0])); + flags &= ~I2400M_BRI_NO_REBOOT; + goto do_reboot; + } + } + dev_err(dev, "device boot: tried all the echo/acks, could " + "not get device to respond; giving up"); + result = -ESHUTDOWN; case -EPROTO: case -ESHUTDOWN: /* dev is gone */ case -EINTR: /* user cancelled */ @@ -892,6 +930,7 @@ do_reboot: default: dev_err(dev, "device reboot: error %d while waiting " "for reboot barker - rebooting\n", result); + d_dump(1, dev, &ack, result); goto do_reboot; } /* At this point we ack back with 4 REBOOT barkers and expect diff --git a/include/linux/wimax/i2400m.h b/include/linux/wimax/i2400m.h index d6e2a3595682..fd5af05083cb 100644 --- a/include/linux/wimax/i2400m.h +++ b/include/linux/wimax/i2400m.h @@ -138,7 +138,7 @@ struct i2400m_bcf_hdr { __le32 module_id; __le32 module_vendor; __le32 date; /* BCD YYYMMDD */ - __le32 size; + __le32 size; /* in dwords */ __le32 key_size; /* in dwords */ __le32 modulus_size; /* in dwords */ __le32 exponent_size; /* in dwords */ -- cgit v1.2.3 From fabce1a485dd985c0e4c16f61f4ddb5e27e49cbf Mon Sep 17 00:00:00 2001 From: Inaky Perez-Gonzalez Date: Fri, 4 Sep 2009 14:53:43 -0700 Subject: wimax/i2400m: verify firmware format version is known Make sure the bootloading code checks that the format of the file is understood (major version match). This also fixes a dumb typo in extracting the major version field. Signed-off-by: Inaky Perez-Gonzalez --- drivers/net/wimax/i2400m/fw.c | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) (limited to 'drivers/net/wimax') diff --git a/drivers/net/wimax/i2400m/fw.c b/drivers/net/wimax/i2400m/fw.c index eef236d85af3..1fd2fee4c6c2 100644 --- a/drivers/net/wimax/i2400m/fw.c +++ b/drivers/net/wimax/i2400m/fw.c @@ -1179,7 +1179,7 @@ int i2400m_fw_check(struct i2400m *i2400m, module_type = bcf->module_type; header_len = sizeof(u32) * le32_to_cpu(bcf->header_len); - major_version = le32_to_cpu(bcf->header_version) & 0xffff0000 >> 16; + major_version = (le32_to_cpu(bcf->header_version) & 0xffff0000) >> 16; minor_version = le32_to_cpu(bcf->header_version) & 0x0000ffff; module_id = le32_to_cpu(bcf->module_id); module_vendor = le32_to_cpu(bcf->module_vendor); @@ -1205,6 +1205,12 @@ int i2400m_fw_check(struct i2400m *i2400m, goto error; } + if (major_version != 1) { + dev_err(dev, "%s: major header version v%u.%u not supported\n", + i2400m->fw_name, major_version, minor_version); + goto error; + } + /* Check soft-er errors */ result = 0; if (module_vendor != 0x8086) -- cgit v1.2.3 From bfc44187bbaeabf597be6566a24e8fa7d689b984 Mon Sep 17 00:00:00 2001 From: Inaky Perez-Gonzalez Date: Fri, 4 Sep 2009 17:07:21 -0700 Subject: wimax/i2400m: support extended firmware format The SBCF firmware format has been extended to support extra headers after the main payload. These extra headers are used to sign the firmware code with more than one certificate. This eases up distributing single code images that work in more than one SKU of the device. The changes to support this feature will be spread in a series of commits. This one just adds the support to parse the extra headers and store them in i2400m->fw_hdrs. Coming changes to the loader code will use that to determine which header to upload to the device. The i2400m_fw_check() function now iterates over all the headers and for each, calls i2400m_fw_hdr_check(), which does some basic checks on each header. It then stores the headers for the bootloader code to use. The i2400m_dev_bootstrap() function has been modified to cleanup i2400m->fw_hdrs when done. Signed-off-by: Inaky Perez-Gonzalez --- drivers/net/wimax/i2400m/fw.c | 189 ++++++++++++++++++++++++++------------ drivers/net/wimax/i2400m/i2400m.h | 4 + 2 files changed, 134 insertions(+), 59 deletions(-) (limited to 'drivers/net/wimax') diff --git a/drivers/net/wimax/i2400m/fw.c b/drivers/net/wimax/i2400m/fw.c index 1fd2fee4c6c2..897e0be698c6 100644 --- a/drivers/net/wimax/i2400m/fw.c +++ b/drivers/net/wimax/i2400m/fw.c @@ -78,11 +78,11 @@ * * We can then upload the firmware file. The file is composed of a BCF * header (basic data, keys and signatures) and a list of write - * commands and payloads. We first upload the header - * [i2400m_dnload_init()] and then pass the commands and payloads - * verbatim to the i2400m_bm_cmd() function - * [i2400m_dnload_bcf()]. Then we tell the device to jump to the new - * firmware [i2400m_dnload_finalize()]. + * commands and payloads. Optionally more BCF headers might follow the + * main payload. We first upload the header [i2400m_dnload_init()] and + * then pass the commands and payloads verbatim to the i2400m_bm_cmd() + * function [i2400m_dnload_bcf()]. Then we tell the device to jump to + * the new firmware [i2400m_dnload_finalize()]. * * Once firmware is uploaded, we are good to go :) * @@ -115,6 +115,7 @@ * i2400m_dev_bootstrap Called by __i2400m_dev_start() * request_firmware * i2400m_fw_check + * i2400m_fw_hdr_check * i2400m_fw_dnload * release_firmware * @@ -1151,75 +1152,142 @@ int i2400m_dnload_init(struct i2400m *i2400m, const struct i2400m_bcf_hdr *bcf) /* - * Run quick consistency tests on the firmware file + * Run consistency tests on the firmware file and load up headers * * Check for the firmware being made for the i2400m device, * etc...These checks are mostly informative, as the device will make * them too; but the driver's response is more informative on what * went wrong. + * + * This will also look at all the headers present on the firmware + * file, and update i2400m->fw_bcf_hdr to point to them. */ static -int i2400m_fw_check(struct i2400m *i2400m, - const struct i2400m_bcf_hdr *bcf, - size_t bcf_size) +int i2400m_fw_hdr_check(struct i2400m *i2400m, + const struct i2400m_bcf_hdr *bcf_hdr, + size_t index, size_t offset) { - int result; struct device *dev = i2400m_dev(i2400m); + unsigned module_type, header_len, major_version, minor_version, module_id, module_vendor, date, size; - /* Check hard errors */ - result = -EINVAL; - if (bcf_size < sizeof(*bcf)) { /* big enough header? */ - dev_err(dev, "firmware %s too short: " - "%zu B vs %zu (at least) expected\n", - i2400m->fw_name, bcf_size, sizeof(*bcf)); - goto error; - } - - module_type = bcf->module_type; - header_len = sizeof(u32) * le32_to_cpu(bcf->header_len); - major_version = (le32_to_cpu(bcf->header_version) & 0xffff0000) >> 16; - minor_version = le32_to_cpu(bcf->header_version) & 0x0000ffff; - module_id = le32_to_cpu(bcf->module_id); - module_vendor = le32_to_cpu(bcf->module_vendor); - date = le32_to_cpu(bcf->date); - size = sizeof(u32) * le32_to_cpu(bcf->size); - - if (bcf_size != size) { /* annoyingly paranoid */ - dev_err(dev, "firmware %s: bad size, got " - "%zu B vs %u expected\n", - i2400m->fw_name, bcf_size, size); - goto error; + module_type = bcf_hdr->module_type; + header_len = sizeof(u32) * le32_to_cpu(bcf_hdr->header_len); + major_version = (le32_to_cpu(bcf_hdr->header_version) & 0xffff0000) + >> 16; + minor_version = le32_to_cpu(bcf_hdr->header_version) & 0x0000ffff; + module_id = le32_to_cpu(bcf_hdr->module_id); + module_vendor = le32_to_cpu(bcf_hdr->module_vendor); + date = le32_to_cpu(bcf_hdr->date); + size = sizeof(u32) * le32_to_cpu(bcf_hdr->size); + + d_printf(1, dev, "firmware %s #%d@%08x: BCF header " + "type:vendor:id 0x%x:%x:%x v%u.%u (%zu/%zu B) built %08x\n", + i2400m->fw_name, index, offset, + module_type, module_vendor, module_id, + major_version, minor_version, header_len, size, date); + + /* Hard errors */ + if (major_version != 1) { + dev_err(dev, "firmware %s #%d@%08x: major header version " + "v%u.%u not supported\n", + i2400m->fw_name, index, offset, + major_version, minor_version); + return -EBADF; } - d_printf(2, dev, "type 0x%x id 0x%x vendor 0x%x; header v%u.%u (%zu B) " - "date %08x (%zu B)\n", - module_type, module_id, module_vendor, - major_version, minor_version, (size_t) header_len, - date, (size_t) size); - if (module_type != 6) { /* built for the right hardware? */ - dev_err(dev, "bad fw %s: unexpected module type 0x%x; " - "aborting\n", i2400m->fw_name, module_type); - goto error; + dev_err(dev, "firmware %s #%d@%08x: unexpected module " + "type 0x%x; aborting\n", + i2400m->fw_name, index, offset, + module_type); + return -EBADF; } - if (major_version != 1) { - dev_err(dev, "%s: major header version v%u.%u not supported\n", - i2400m->fw_name, major_version, minor_version); - goto error; + if (module_vendor != 0x8086) { + dev_err(dev, "firmware %s #%d@%08x: unexpected module " + "vendor 0x%x; aborting\n", + i2400m->fw_name, index, offset, module_vendor); + return -EBADF; } - /* Check soft-er errors */ - result = 0; - if (module_vendor != 0x8086) - dev_err(dev, "bad fw %s? unexpected vendor 0x%04x\n", - i2400m->fw_name, module_vendor); if (date < 0x20080300) - dev_err(dev, "bad fw %s? build date too old %08x\n", - i2400m->fw_name, date); -error: + dev_warn(dev, "firmware %s #%d@%08x: build date %08x " + "too old; unsupported\n", + i2400m->fw_name, index, offset, date); + return 0; +} + + +/* + * Run consistency tests on the firmware file and load up headers + * + * Check for the firmware being made for the i2400m device, + * etc...These checks are mostly informative, as the device will make + * them too; but the driver's response is more informative on what + * went wrong. + * + * This will also look at all the headers present on the firmware + * file, and update i2400m->fw_hdrs to point to them. + */ +static +int i2400m_fw_check(struct i2400m *i2400m, const void *bcf, size_t bcf_size) +{ + int result; + struct device *dev = i2400m_dev(i2400m); + size_t headers = 0; + const struct i2400m_bcf_hdr *bcf_hdr; + const void *itr, *next, *top; + unsigned slots = 0, used_slots = 0; + + for (itr = bcf, top = itr + bcf_size; + itr < top; + headers++, itr = next) { + size_t leftover, offset, header_len, size; + + leftover = top - itr; + offset = itr - (const void *) bcf; + if (leftover <= sizeof(*bcf_hdr)) { + dev_err(dev, "firmware %s: %zu B left at @%x, " + "not enough for BCF header\n", + i2400m->fw_name, leftover, offset); + break; + } + bcf_hdr = itr; + /* Only the first header is supposed to be followed by + * payload */ + header_len = sizeof(u32) * le32_to_cpu(bcf_hdr->header_len); + size = sizeof(u32) * le32_to_cpu(bcf_hdr->size); + if (headers == 0) + next = itr + size; + else + next = itr + header_len; + + result = i2400m_fw_hdr_check(i2400m, bcf_hdr, headers, offset); + if (result < 0) + continue; + if (used_slots + 1 >= slots) { + /* +1 -> we need to account for the one we'll + * occupy and at least an extra one for + * always being NULL */ + result = i2400m_zrealloc_2x( + (void **) &i2400m->fw_hdrs, &slots, + sizeof(i2400m->fw_hdrs[0]), + GFP_KERNEL); + if (result < 0) + goto error_zrealloc; + } + i2400m->fw_hdrs[used_slots] = bcf_hdr; + used_slots++; + } + if (headers == 0) { + dev_err(dev, "firmware %s: no usable headers found\n", + i2400m->fw_name); + result = -EBADF; + } else + result = 0; +error_zrealloc: return result; } @@ -1359,13 +1427,16 @@ int i2400m_dev_bootstrap(struct i2400m *i2400m, enum i2400m_bri flags) bcf = (void *) fw->data; i2400m->fw_name = fw_name; ret = i2400m_fw_check(i2400m, bcf, fw->size); - if (ret >= 0) { + if (ret >= 0) ret = i2400m_fw_dnload(i2400m, bcf, fw->size, flags); - if (ret >= 0) - break; - } else - dev_err(dev, "%s: cannot use, skipping\n", fw_name); + if (ret < 0) + dev_err(dev, "%s: cannot use: %d, skipping\n", + fw_name, ret); + kfree(i2400m->fw_hdrs); + i2400m->fw_hdrs = NULL; release_firmware(fw); + if (ret >= 0) /* firmware loaded succesfully */ + break; } d_fnend(5, dev, "(i2400m %p) = %d\n", i2400m, ret); return ret; diff --git a/drivers/net/wimax/i2400m/i2400m.h b/drivers/net/wimax/i2400m/i2400m.h index bcb1882ed741..5ac2cce88ba0 100644 --- a/drivers/net/wimax/i2400m/i2400m.h +++ b/drivers/net/wimax/i2400m/i2400m.h @@ -421,6 +421,9 @@ struct i2400m_barker_db; * @fw_version: version of the firmware interface, Major.minor, * encoded in the high word and low word (major << 16 | minor). * + * @fw_hdrs: NULL terminated array of pointers to the firmware + * headers. This is only available during firmware load time. + * * @barker: barker type that the device uses; this is initialized by * i2400m_is_boot_barker() the first time it is called. Then it * won't change during the life cycle of the device and everytime @@ -491,6 +494,7 @@ struct i2400m { struct dentry *debugfs_dentry; const char *fw_name; /* name of the current firmware image */ unsigned long fw_version; /* version of the firmware interface */ + const struct i2400m_bcf_hdr **fw_hdrs; struct i2400m_barker_db *barker; }; -- cgit v1.2.3 From 10607c86e163e3da8bdf58a934da7dd3a68dcb7c Mon Sep 17 00:00:00 2001 From: Inaky Perez-Gonzalez Date: Wed, 9 Sep 2009 17:11:57 -0700 Subject: wimax/i2400m: on firmware upload, select BCF header that matches device's request Devices based on the i2400m emit a "barker" (32 bit unsigned) when they boot. This barker is used to select, in the firmware file image, which header should be used to process the rest of the file. This commit implements said support, completing the series started by previous commits. We modify the i2400m_fw_dnload() firmware loading path by adding a call to i2400m_bcf_hdr_find() [new function], in which the right BCF header [as listed in i2400m->fw_hdrs by i2400m_fw_check()] is located. Then this header is fed to i2400m_dnload_init() and i2400m_dnload_finalize(). The changes to i2400m_dnload_finalize() are smaller than they look; they add the bcf_hdr argument and use that instead of bcf. Likewise in i2400m_dnload_init(). Signed-off-by: Inaky Perez-Gonzalez --- drivers/net/wimax/i2400m/fw.c | 111 ++++++++++++++++++++++++++++++++++-------- 1 file changed, 92 insertions(+), 19 deletions(-) (limited to 'drivers/net/wimax') diff --git a/drivers/net/wimax/i2400m/fw.c b/drivers/net/wimax/i2400m/fw.c index 897e0be698c6..84a39c30c3d3 100644 --- a/drivers/net/wimax/i2400m/fw.c +++ b/drivers/net/wimax/i2400m/fw.c @@ -665,8 +665,8 @@ static int i2400m_download_chunk(struct i2400m *i2400m, const void *chunk, * Download a BCF file's sections to the device * * @i2400m: device descriptor - * @bcf: pointer to firmware data (followed by the payloads). Assumed - * verified and consistent. + * @bcf: pointer to firmware data (first header followed by the + * payloads). Assumed verified and consistent. * @bcf_len: length (in bytes) of the @bcf buffer. * * Returns: < 0 errno code on error or the offset to the jump instruction. @@ -756,11 +756,17 @@ unsigned i2400m_boot_is_signed(struct i2400m *i2400m) /* * Do the final steps of uploading firmware * + * @bcf_hdr: BCF header we are actually using + * @bcf: pointer to the firmware image (which matches the first header + * that is followed by the actual payloads). + * @offset: [byte] offset into @bcf for the command we need to send. + * * Depending on the boot mode (signed vs non-signed), different * actions need to be taken. */ static int i2400m_dnload_finalize(struct i2400m *i2400m, + const struct i2400m_bcf_hdr *bcf_hdr, const struct i2400m_bcf_hdr *bcf, size_t offset) { int ret = 0; @@ -792,12 +798,13 @@ int i2400m_dnload_finalize(struct i2400m *i2400m, cmd_buf = i2400m->bm_cmd_buf; memcpy(&cmd_buf->cmd, cmd, sizeof(*cmd)); signature_block_offset = - sizeof(*bcf) - + le32_to_cpu(bcf->key_size) * sizeof(u32) - + le32_to_cpu(bcf->exponent_size) * sizeof(u32); + sizeof(*bcf_hdr) + + le32_to_cpu(bcf_hdr->key_size) * sizeof(u32) + + le32_to_cpu(bcf_hdr->exponent_size) * sizeof(u32); signature_block_size = - le32_to_cpu(bcf->modulus_size) * sizeof(u32); - memcpy(cmd_buf->cmd_pl, (void *) bcf + signature_block_offset, + le32_to_cpu(bcf_hdr->modulus_size) * sizeof(u32); + memcpy(cmd_buf->cmd_pl, + (void *) bcf_hdr + signature_block_offset, signature_block_size); ret = i2400m_bm_cmd(i2400m, &cmd_buf->cmd, sizeof(cmd_buf->cmd) + signature_block_size, @@ -1122,14 +1129,15 @@ int i2400m_dnload_init_signed(struct i2400m *i2400m, * (signed or non-signed). */ static -int i2400m_dnload_init(struct i2400m *i2400m, const struct i2400m_bcf_hdr *bcf) +int i2400m_dnload_init(struct i2400m *i2400m, + const struct i2400m_bcf_hdr *bcf_hdr) { int result; struct device *dev = i2400m_dev(i2400m); if (i2400m_boot_is_signed(i2400m)) { d_printf(1, dev, "signed boot\n"); - result = i2400m_dnload_init_signed(i2400m, bcf); + result = i2400m_dnload_init_signed(i2400m, bcf_hdr); if (result == -ERESTARTSYS) return result; if (result < 0) @@ -1182,15 +1190,15 @@ int i2400m_fw_hdr_check(struct i2400m *i2400m, date = le32_to_cpu(bcf_hdr->date); size = sizeof(u32) * le32_to_cpu(bcf_hdr->size); - d_printf(1, dev, "firmware %s #%d@%08x: BCF header " - "type:vendor:id 0x%x:%x:%x v%u.%u (%zu/%zu B) built %08x\n", + d_printf(1, dev, "firmware %s #%zd@%08zx: BCF header " + "type:vendor:id 0x%x:%x:%x v%u.%u (%u/%u B) built %08x\n", i2400m->fw_name, index, offset, module_type, module_vendor, module_id, major_version, minor_version, header_len, size, date); /* Hard errors */ if (major_version != 1) { - dev_err(dev, "firmware %s #%d@%08x: major header version " + dev_err(dev, "firmware %s #%zd@%08zx: major header version " "v%u.%u not supported\n", i2400m->fw_name, index, offset, major_version, minor_version); @@ -1198,7 +1206,7 @@ int i2400m_fw_hdr_check(struct i2400m *i2400m, } if (module_type != 6) { /* built for the right hardware? */ - dev_err(dev, "firmware %s #%d@%08x: unexpected module " + dev_err(dev, "firmware %s #%zd@%08zx: unexpected module " "type 0x%x; aborting\n", i2400m->fw_name, index, offset, module_type); @@ -1206,14 +1214,14 @@ int i2400m_fw_hdr_check(struct i2400m *i2400m, } if (module_vendor != 0x8086) { - dev_err(dev, "firmware %s #%d@%08x: unexpected module " + dev_err(dev, "firmware %s #%zd@%08zx: unexpected module " "vendor 0x%x; aborting\n", i2400m->fw_name, index, offset, module_vendor); return -EBADF; } if (date < 0x20080300) - dev_warn(dev, "firmware %s #%d@%08x: build date %08x " + dev_warn(dev, "firmware %s #%zd@%08zx: build date %08x " "too old; unsupported\n", i2400m->fw_name, index, offset, date); return 0; @@ -1239,7 +1247,7 @@ int i2400m_fw_check(struct i2400m *i2400m, const void *bcf, size_t bcf_size) size_t headers = 0; const struct i2400m_bcf_hdr *bcf_hdr; const void *itr, *next, *top; - unsigned slots = 0, used_slots = 0; + size_t slots = 0, used_slots = 0; for (itr = bcf, top = itr + bcf_size; itr < top; @@ -1249,7 +1257,7 @@ int i2400m_fw_check(struct i2400m *i2400m, const void *bcf, size_t bcf_size) leftover = top - itr; offset = itr - (const void *) bcf; if (leftover <= sizeof(*bcf_hdr)) { - dev_err(dev, "firmware %s: %zu B left at @%x, " + dev_err(dev, "firmware %s: %zu B left at @%zx, " "not enough for BCF header\n", i2400m->fw_name, leftover, offset); break; @@ -1292,6 +1300,60 @@ error_zrealloc: } +/* + * Match a barker to a BCF header module ID + * + * The device sends a barker which tells the firmware loader which + * header in the BCF file has to be used. This does the matching. + */ +static +unsigned i2400m_bcf_hdr_match(struct i2400m *i2400m, + const struct i2400m_bcf_hdr *bcf_hdr) +{ + u32 barker = le32_to_cpu(i2400m->barker->data[0]) + & 0x7fffffff; + u32 module_id = le32_to_cpu(bcf_hdr->module_id) + & 0x7fffffff; /* high bit used for something else */ + + /* special case for 5x50 */ + if (barker == I2400M_SBOOT_BARKER && module_id == 0) + return 1; + if (module_id == barker) + return 1; + return 0; +} + +static +const struct i2400m_bcf_hdr *i2400m_bcf_hdr_find(struct i2400m *i2400m) +{ + struct device *dev = i2400m_dev(i2400m); + const struct i2400m_bcf_hdr **bcf_itr, *bcf_hdr; + unsigned i = 0; + u32 barker = le32_to_cpu(i2400m->barker->data[0]); + + d_printf(2, dev, "finding BCF header for barker %08x\n", barker); + if (barker == I2400M_NBOOT_BARKER) { + bcf_hdr = i2400m->fw_hdrs[0]; + d_printf(1, dev, "using BCF header #%u/%08x for non-signed " + "barker\n", 0, le32_to_cpu(bcf_hdr->module_id)); + return bcf_hdr; + } + for (bcf_itr = i2400m->fw_hdrs; *bcf_itr != NULL; bcf_itr++, i++) { + bcf_hdr = *bcf_itr; + if (i2400m_bcf_hdr_match(i2400m, bcf_hdr)) { + d_printf(1, dev, "hit on BCF hdr #%u/%08x\n", + i, le32_to_cpu(bcf_hdr->module_id)); + return bcf_hdr; + } else + d_printf(1, dev, "miss on BCF hdr #%u/%08x\n", + i, le32_to_cpu(bcf_hdr->module_id)); + } + dev_err(dev, "cannot find a matching BCF header for barker %08x\n", + barker); + return NULL; +} + + /* * Download the firmware to the device * @@ -1313,6 +1375,7 @@ 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->bus_bm_retries; + const struct i2400m_bcf_hdr *bcf_hdr; d_fnstart(5, dev, "(i2400m %p bcf %p size %zu)\n", i2400m, bcf, bcf_size); @@ -1337,8 +1400,17 @@ hw_reboot: * Initialize the download, push the bytes to the device and * then jump to the new firmware. Note @ret is passed with the * offset of the jump instruction to _dnload_finalize() + * + * Note we need to use the BCF header in the firmware image + * that matches the barker that the device sent when it + * rebooted, so it has to be passed along. */ - ret = i2400m_dnload_init(i2400m, bcf); /* Init device's dnload */ + ret = -EBADF; + bcf_hdr = i2400m_bcf_hdr_find(i2400m); + if (bcf_hdr == NULL) + goto error_bcf_hdr_find; + + ret = i2400m_dnload_init(i2400m, bcf_hdr); if (ret == -ERESTARTSYS) goto error_dev_rebooted; if (ret < 0) @@ -1353,7 +1425,7 @@ hw_reboot: goto error_dnload_bcf; } - ret = i2400m_dnload_finalize(i2400m, bcf, ret); + ret = i2400m_dnload_finalize(i2400m, bcf_hdr, bcf, ret); if (ret == -ERESTARTSYS) goto error_dev_rebooted; if (ret < 0) { @@ -1370,6 +1442,7 @@ hw_reboot: error_dnload_finalize: error_dnload_bcf: error_dnload_init: +error_bcf_hdr_find: error_bootrom_init: error_too_many_reboots: d_fnend(5, dev, "(i2400m %p bcf %p size %zu) = %d\n", -- cgit v1.2.3 From f8fc3295570115267ce1ce901f362d13d194aefc Mon Sep 17 00:00:00 2001 From: Cindy H Kao Date: Fri, 4 Sep 2009 17:38:46 -0700 Subject: wimax/iwmc3200: add new sdio device ID to support iwmc3200 2.5GHz sku Different sdio device IDs are designated to support different intel wimax silicon sku. The new macro SDIO_DEVICE_ID_IWMC3200_WIMAX_2G5(0x1407) is added to support iwmc3200 2.5GHz sku. The existing SDIO_DEVICE_ID_IWMC3200_WIMAX(0x1402) is for iwmc3200 general sku. Signed-off-by: Cindy H Kao Signed-off-by: Inaky Perez-Gonzalez --- drivers/net/wimax/i2400m/sdio.c | 2 ++ include/linux/mmc/sdio_ids.h | 1 + 2 files changed, 3 insertions(+) (limited to 'drivers/net/wimax') diff --git a/drivers/net/wimax/i2400m/sdio.c b/drivers/net/wimax/i2400m/sdio.c index 2d2cc5ac6d86..de158ee0c32c 100644 --- a/drivers/net/wimax/i2400m/sdio.c +++ b/drivers/net/wimax/i2400m/sdio.c @@ -550,6 +550,8 @@ 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) }, + { SDIO_DEVICE(SDIO_VENDOR_ID_INTEL, + SDIO_DEVICE_ID_INTEL_IWMC3200WIMAX_2G5) }, { /* end: all zeroes */ }, }; MODULE_DEVICE_TABLE(sdio, i2400ms_sdio_ids); diff --git a/include/linux/mmc/sdio_ids.h b/include/linux/mmc/sdio_ids.h index 2dbfb5a05994..33b2ea09a4ad 100644 --- a/include/linux/mmc/sdio_ids.h +++ b/include/linux/mmc/sdio_ids.h @@ -28,6 +28,7 @@ #define SDIO_DEVICE_ID_INTEL_IWMC3200TOP 0x1404 #define SDIO_DEVICE_ID_INTEL_IWMC3200GPS 0x1405 #define SDIO_DEVICE_ID_INTEL_IWMC3200BT 0x1406 +#define SDIO_DEVICE_ID_INTEL_IWMC3200WIMAX_2G5 0x1407 #define SDIO_VENDOR_ID_MARVELL 0x02df #define SDIO_DEVICE_ID_MARVELL_LIBERTAS 0x9103 -- cgit v1.2.3 From 7329012e673231dee9a21567cfb9881f5ea462ba Mon Sep 17 00:00:00 2001 From: Dirk Brandewie Date: Wed, 12 Aug 2009 11:29:46 -0700 Subject: wimax/i6x50: add Intel WiFi/WiMAX Link 6050 Series support Add support for the WiMAX device in the Intel WiFi/WiMAX Link 6050 Series; this involves: - adding the device ID to bind to and an endpoint mapping for the driver to use. - at probe() time, some things are set depending on the device id: + the list of firmware names to try + mapping of endpoints Signed-off-by: Dirk Brandewie Signed-off-by: Inaky Perez-Gonzalez --- drivers/net/wimax/i2400m/fw.c | 3 +++ drivers/net/wimax/i2400m/i2400m-usb.h | 3 +++ drivers/net/wimax/i2400m/usb.c | 26 +++++++++++++++++++------- include/linux/wimax/i2400m.h | 1 + 4 files changed, 26 insertions(+), 7 deletions(-) (limited to 'drivers/net/wimax') diff --git a/drivers/net/wimax/i2400m/fw.c b/drivers/net/wimax/i2400m/fw.c index 84a39c30c3d3..5719f4a4080f 100644 --- a/drivers/net/wimax/i2400m/fw.c +++ b/drivers/net/wimax/i2400m/fw.c @@ -277,6 +277,9 @@ int i2400m_barker_db_known_barkers(void) result = i2400m_barker_db_add(I2400M_SBOOT_BARKER); if (result < 0) goto error_add; + result = i2400m_barker_db_add(I2400M_SBOOT_BARKER_6050); + if (result < 0) + goto error_add; error_add: return result; } diff --git a/drivers/net/wimax/i2400m/i2400m-usb.h b/drivers/net/wimax/i2400m/i2400m-usb.h index f73a067e0668..5cc0f279417e 100644 --- a/drivers/net/wimax/i2400m/i2400m-usb.h +++ b/drivers/net/wimax/i2400m/i2400m-usb.h @@ -148,6 +148,9 @@ enum { I2400MU_MAX_NOTIFICATION_LEN = 256, I2400MU_BLK_SIZE = 16, I2400MU_PL_SIZE_MAX = 0x3EFF, + + /* Device IDs */ + USB_DEVICE_ID_I6050 = 0x0186, }; diff --git a/drivers/net/wimax/i2400m/usb.c b/drivers/net/wimax/i2400m/usb.c index 063422290a43..77d08d928274 100644 --- a/drivers/net/wimax/i2400m/usb.c +++ b/drivers/net/wimax/i2400m/usb.c @@ -80,11 +80,16 @@ MODULE_PARM_DESC(debug, "initial debug value to set."); /* Our firmware file name */ -static const char *i2400mu_bus_fw_names[] = { +static const char *i2400mu_bus_fw_names_5x50[] = { #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 const char *i2400mu_bus_fw_names_6050[] = { +#define I6050U_FW_FILE_NAME_v1_5 "i6050-fw-usb-1.5.sbcf" + I6050U_FW_FILE_NAME_v1_5, NULL, }; @@ -418,10 +423,16 @@ int i2400mu_probe(struct usb_interface *iface, i2400m->bus_bm_retries = I2400M_USB_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; - { + if (id->idProduct == USB_DEVICE_ID_I6050) { + i2400m->bus_fw_names = i2400mu_bus_fw_names_6050; + i2400mu->endpoint_cfg.bulk_out = 0; + i2400mu->endpoint_cfg.notification = 3; + i2400mu->endpoint_cfg.reset_cold = 2; + i2400mu->endpoint_cfg.bulk_in = 1; + } else { + i2400m->bus_fw_names = i2400mu_bus_fw_names_5x50; i2400mu->endpoint_cfg.bulk_out = 0; i2400mu->endpoint_cfg.notification = 1; i2400mu->endpoint_cfg.reset_cold = 2; @@ -614,6 +625,7 @@ out: static struct usb_device_id i2400mu_id_table[] = { + { USB_DEVICE(0x8086, USB_DEVICE_ID_I6050) }, { USB_DEVICE(0x8086, 0x0181) }, { USB_DEVICE(0x8086, 0x1403) }, { USB_DEVICE(0x8086, 0x1405) }, @@ -656,7 +668,7 @@ void __exit i2400mu_driver_exit(void) module_exit(i2400mu_driver_exit); MODULE_AUTHOR("Intel Corporation "); -MODULE_DESCRIPTION("Intel 2400M WiMAX networking for USB"); +MODULE_DESCRIPTION("Driver for USB based Intel Wireless WiMAX Connection 2400M " + "(5x50 & 6050)"); MODULE_LICENSE("GPL"); MODULE_FIRMWARE(I2400MU_FW_FILE_NAME_v1_4); -MODULE_FIRMWARE(I2400MU_FW_FILE_NAME_v1_3); diff --git a/include/linux/wimax/i2400m.h b/include/linux/wimax/i2400m.h index fd5af05083cb..62d356153565 100644 --- a/include/linux/wimax/i2400m.h +++ b/include/linux/wimax/i2400m.h @@ -266,6 +266,7 @@ enum { I2400M_WARM_RESET_BARKER = 0x50f750f7, I2400M_NBOOT_BARKER = 0xdeadbeef, I2400M_SBOOT_BARKER = 0x0ff1c1a1, + I2400M_SBOOT_BARKER_6050 = 0x80000001, I2400M_ACK_BARKER = 0xfeedbabe, I2400M_D2H_MSG_BARKER = 0xbeefbabe, }; -- cgit v1.2.3 From b0fbcb2a0b54ee201fa8af61fdebe14c050f18fe Mon Sep 17 00:00:00 2001 From: Inaky Perez-Gonzalez Date: Mon, 14 Sep 2009 13:29:32 -0700 Subject: wimax/i2400m: clean up & add a payload argument to i2400m_schedule_work() Forthcoming commits use having a payload argument added to i2400m_schedule_work(), which then becomes nearly identical to i2400m_queue_work(). This patch thus cleans up both's implementation, making it share common helpers and adding the payload argument to i2400m_schedule_work(). Signed-off-by: Inaky Perez-Gonzalez --- drivers/net/wimax/i2400m/driver.c | 52 +++++++++++++++++++++++++-------------- drivers/net/wimax/i2400m/i2400m.h | 6 +++-- 2 files changed, 37 insertions(+), 21 deletions(-) (limited to 'drivers/net/wimax') diff --git a/drivers/net/wimax/i2400m/driver.c b/drivers/net/wimax/i2400m/driver.c index 73f45ea010a7..5803a2bfd6af 100644 --- a/drivers/net/wimax/i2400m/driver.c +++ b/drivers/net/wimax/i2400m/driver.c @@ -107,6 +107,24 @@ MODULE_PARM_DESC(barkers, "signal; values are appended to a list--setting one value " "as zero cleans the existing list and starts a new one."); +static +struct i2400m_work *__i2400m_work_setup( + struct i2400m *i2400m, void (*fn)(struct work_struct *), + gfp_t gfp_flags, const void *pl, size_t pl_size) +{ + struct i2400m_work *iw; + + iw = kzalloc(sizeof(*iw) + pl_size, gfp_flags); + if (iw == NULL) + return NULL; + iw->i2400m = i2400m_get(i2400m); + iw->pl_size = pl_size; + memcpy(iw->pl, pl, pl_size); + INIT_WORK(&iw->ws, fn); + return iw; +} + + /** * i2400m_queue_work - schedule work on a i2400m's queue * @@ -166,14 +184,12 @@ int i2400m_queue_work(struct i2400m *i2400m, 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: + iw = __i2400m_work_setup(i2400m, fn, gfp_flags, pl, pl_size); + if (iw != NULL) { + result = queue_work(i2400m->work_queue, &iw->ws); + if (WARN_ON(result == 0)) + result = -ENXIO; + } return result; } EXPORT_SYMBOL_GPL(i2400m_queue_work); @@ -192,21 +208,19 @@ EXPORT_SYMBOL_GPL(i2400m_queue_work); * it should not happen. */ int i2400m_schedule_work(struct i2400m *i2400m, - void (*fn)(struct work_struct *), gfp_t gfp_flags) + void (*fn)(struct work_struct *), gfp_t gfp_flags, + const void *pl, size_t pl_size) { 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: + iw = __i2400m_work_setup(i2400m, fn, gfp_flags, pl, pl_size); + if (iw != NULL) { + result = schedule_work(&iw->ws); + if (WARN_ON(result == 0)) + result = -ENXIO; + } return result; } @@ -630,7 +644,7 @@ 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); + GFP_ATOMIC, NULL, 0); } EXPORT_SYMBOL_GPL(i2400m_dev_reset_handle); diff --git a/drivers/net/wimax/i2400m/i2400m.h b/drivers/net/wimax/i2400m/i2400m.h index 5ac2cce88ba0..700f87be4acf 100644 --- a/drivers/net/wimax/i2400m/i2400m.h +++ b/drivers/net/wimax/i2400m/i2400m.h @@ -695,8 +695,6 @@ 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 */ @@ -778,10 +776,14 @@ struct device *i2400m_dev(struct i2400m *i2400m) struct i2400m_work { struct work_struct ws; struct i2400m *i2400m; + size_t pl_size; u8 pl[0]; }; extern int i2400m_queue_work(struct i2400m *, void (*)(struct work_struct *), gfp_t, + const void *, size_t); +extern int i2400m_schedule_work(struct i2400m *, + void (*)(struct work_struct *), gfp_t, const void *, size_t); extern int i2400m_msg_check_status(const struct i2400m_l3l4_hdr *, -- cgit v1.2.3 From 3ef6129e57b04c116b1907b72c7a20720e6dde75 Mon Sep 17 00:00:00 2001 From: Inaky Perez-Gonzalez Date: Mon, 14 Sep 2009 14:05:19 -0700 Subject: wimax/i2400m: add reason argument to i2400m_dev_reset_handle() In preparation for reset_resume support, in which the same code path is going to be used, add a diagnostic message to dev_reset_handle() that can be used to distinguish how the device got there. This uses the new payload argument added to i2400m_schedule_work() by the previous commit. Signed-off-by: Inaky Perez-Gonzalez --- drivers/net/wimax/i2400m/driver.c | 27 +++++++++++++++++++-------- drivers/net/wimax/i2400m/i2400m.h | 2 +- drivers/net/wimax/i2400m/sdio-rx.c | 2 +- drivers/net/wimax/i2400m/usb-notif.c | 2 +- 4 files changed, 22 insertions(+), 11 deletions(-) (limited to 'drivers/net/wimax') diff --git a/drivers/net/wimax/i2400m/driver.c b/drivers/net/wimax/i2400m/driver.c index 5803a2bfd6af..f07d8527b03b 100644 --- a/drivers/net/wimax/i2400m/driver.c +++ b/drivers/net/wimax/i2400m/driver.c @@ -573,18 +573,28 @@ void i2400m_dev_stop(struct i2400m *i2400m) * _stop()], don't do anything, let it fail and handle it. * * This function is ran always in a thread context + * + * This function gets passed, as payload to i2400m_work() a 'const + * char *' ptr with a "reason" why the reset happened (for messages). */ static void __i2400m_dev_reset_handle(struct work_struct *ws) { int result; struct i2400m_work *iw = container_of(ws, struct i2400m_work, ws); + const char *reason; 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); + if (WARN_ON(iw->pl_size != sizeof(reason))) + reason = "SW BUG: reason n/a"; + else + memcpy(&reason, iw->pl, sizeof(reason)); + + d_fnstart(3, dev, "(ws %p i2400m %p reason %s)\n", ws, i2400m, reason); + result = 0; if (mutex_trylock(&i2400m->init_mutex) == 0) { /* We are still in i2400m_dev_start() [let it fail] or @@ -597,17 +607,17 @@ void __i2400m_dev_reset_handle(struct work_struct *ws) } wimax_state = wimax_state_get(&i2400m->wimax_dev); if (wimax_state < WIMAX_ST_UNINITIALIZED) { - dev_info(dev, "device rebooted: it is down, ignoring\n"); + dev_info(dev, "%s: it is down, ignoring\n", reason); goto out_unlock; /* ifconfig up/down wasn't called */ } - dev_err(dev, "device rebooted: reinitializing driver\n"); + dev_err(dev, "%s: reinitializing driver\n", reason); __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); + dev_err(dev, "%s: cannot start the device: %d\n", + reason, result); result = i2400m->bus_reset(i2400m, I2400M_RT_BUS); if (result >= 0) result = -ENODEV; @@ -622,7 +632,8 @@ out_unlock: out: i2400m_put(i2400m); kfree(iw); - d_fnend(3, dev, "(ws %p i2400m %p) = void\n", ws, i2400m); + d_fnend(3, dev, "(ws %p i2400m %p reason %s) = void\n", + ws, i2400m, reason); return; } @@ -639,12 +650,12 @@ out: * reinitializing the driver to handle the reset, calling into the * bus-specific functions ops as needed. */ -int i2400m_dev_reset_handle(struct i2400m *i2400m) +int i2400m_dev_reset_handle(struct i2400m *i2400m, const char *reason) { 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, NULL, 0); + GFP_ATOMIC, &reason, sizeof(reason)); } EXPORT_SYMBOL_GPL(i2400m_dev_reset_handle); diff --git a/drivers/net/wimax/i2400m/i2400m.h b/drivers/net/wimax/i2400m/i2400m.h index 700f87be4acf..0c165de89b2d 100644 --- a/drivers/net/wimax/i2400m/i2400m.h +++ b/drivers/net/wimax/i2400m/i2400m.h @@ -739,7 +739,7 @@ void i2400m_put(struct i2400m *i2400m) dev_put(i2400m->wimax_dev.net_dev); } -extern int i2400m_dev_reset_handle(struct i2400m *); +extern int i2400m_dev_reset_handle(struct i2400m *, const char *); extern int i2400m_bm_buf_alloc(struct i2400m *i2400m); extern void i2400m_bm_buf_free(struct i2400m *i2400m); diff --git a/drivers/net/wimax/i2400m/sdio-rx.c b/drivers/net/wimax/i2400m/sdio-rx.c index 87263be4eedb..98ee7fdfd621 100644 --- a/drivers/net/wimax/i2400m/sdio-rx.c +++ b/drivers/net/wimax/i2400m/sdio-rx.c @@ -179,7 +179,7 @@ void i2400ms_rx(struct i2400ms *i2400ms) i2400m_rx(i2400m, skb); } else if (unlikely(i2400m_is_boot_barker(i2400m, skb->data, rx_size))) { - ret = i2400m_dev_reset_handle(i2400m); + ret = i2400m_dev_reset_handle(i2400m, "device rebooted"); dev_err(dev, "RX: SDIO reboot barker\n"); kfree_skb(skb); } else { diff --git a/drivers/net/wimax/i2400m/usb-notif.c b/drivers/net/wimax/i2400m/usb-notif.c index a0751a347cdc..f88d1c6e35cb 100644 --- a/drivers/net/wimax/i2400m/usb-notif.c +++ b/drivers/net/wimax/i2400m/usb-notif.c @@ -98,7 +98,7 @@ int i2400mu_notification_grok(struct i2400mu *i2400mu, const void *buf, } ret = i2400m_is_boot_barker(i2400m, buf, buf_len); if (unlikely(ret >= 0)) - ret = i2400m_dev_reset_handle(i2400m); + ret = i2400m_dev_reset_handle(i2400m, "device rebooted"); else /* Unknown or unexpected data in the notif message */ i2400m_unknown_barker(i2400m, buf, buf_len); error_bad_size: -- cgit v1.2.3 From 7b43ca708a767a5f68eeeb732c569c0f11a7d6f7 Mon Sep 17 00:00:00 2001 From: Inaky Perez-Gonzalez Date: Mon, 14 Sep 2009 14:10:16 -0700 Subject: wimax/i2400m: cache firmware on system suspend In preparation for a reset_resume implementation, have the firmware image be cached in memory when the system goes to suspend and released when out. This is needed in case the device resets during suspend; the driver can't load firmware until resume is completed or bad deadlocks happen. The modus operandi for this was copied from the Orinoco USB driver. The caching is done with a kobject to avoid race conditions when releasing it. The fw loader path is altered only to first check for a cached image before trying to load from disk. A Power Management event notifier is register to call i2400m_fw_cache() or i2400m_fw_uncache() which take care of the actual cache management. Signed-off-by: Inaky Perez-Gonzalez --- drivers/net/wimax/i2400m/driver.c | 51 ++++++++++++ drivers/net/wimax/i2400m/fw.c | 159 ++++++++++++++++++++++++++++++++++---- drivers/net/wimax/i2400m/i2400m.h | 16 ++++ 3 files changed, 213 insertions(+), 13 deletions(-) (limited to 'drivers/net/wimax') diff --git a/drivers/net/wimax/i2400m/driver.c b/drivers/net/wimax/i2400m/driver.c index f07d8527b03b..07d12be0cf81 100644 --- a/drivers/net/wimax/i2400m/driver.c +++ b/drivers/net/wimax/i2400m/driver.c @@ -66,6 +66,7 @@ #include #include #include +#include #define D_SUBMODULE driver #include "debug-levels.h" @@ -554,6 +555,51 @@ void i2400m_dev_stop(struct i2400m *i2400m) } +/* + * Listen to PM events to cache the firmware before suspend/hibernation + * + * When the device comes out of suspend, it might go into reset and + * firmware has to be uploaded again. At resume, most of the times, we + * can't load firmware images from disk, so we need to cache it. + * + * i2400m_fw_cache() will allocate a kobject and attach the firmware + * to it; that way we don't have to worry too much about the fw loader + * hitting a race condition. + * + * Note: modus operandi stolen from the Orinoco driver; thx. + */ +static +int i2400m_pm_notifier(struct notifier_block *notifier, + unsigned long pm_event, + void *unused) +{ + struct i2400m *i2400m = + container_of(notifier, struct i2400m, pm_notifier); + struct device *dev = i2400m_dev(i2400m); + + d_fnstart(3, dev, "(i2400m %p pm_event %lx)\n", i2400m, pm_event); + switch (pm_event) { + case PM_HIBERNATION_PREPARE: + case PM_SUSPEND_PREPARE: + i2400m_fw_cache(i2400m); + break; + case PM_POST_RESTORE: + /* Restore from hibernation failed. We need to clean + * up in exactly the same way, so fall through. */ + case PM_POST_HIBERNATION: + case PM_POST_SUSPEND: + i2400m_fw_uncache(i2400m); + break; + + case PM_RESTORE_PREPARE: + default: + break; + } + d_fnend(3, dev, "(i2400m %p pm_event %lx) = void\n", i2400m, pm_event); + return NOTIFY_DONE; +} + + /* * The device has rebooted; fix up the device and the driver * @@ -738,6 +784,9 @@ int i2400m_setup(struct i2400m *i2400m, enum i2400m_bri bm_flags) goto error_read_mac_addr; random_ether_addr(i2400m->src_mac_addr); + i2400m->pm_notifier.notifier_call = i2400m_pm_notifier; + register_pm_notifier(&i2400m->pm_notifier); + result = register_netdev(net_dev); /* Okey dokey, bring it up */ if (result < 0) { dev_err(dev, "cannot register i2400m network device: %d\n", @@ -783,6 +832,7 @@ error_wimax_dev_add: error_dev_start: unregister_netdev(net_dev); error_register_netdev: + unregister_pm_notifier(&i2400m->pm_notifier); error_read_mac_addr: error_bootrom_init: d_fnend(3, dev, "(i2400m %p) = %d\n", i2400m, result); @@ -809,6 +859,7 @@ void i2400m_release(struct i2400m *i2400m) wimax_dev_rm(&i2400m->wimax_dev); i2400m_dev_stop(i2400m); unregister_netdev(i2400m->wimax_dev.net_dev); + unregister_pm_notifier(&i2400m->pm_notifier); kfree(i2400m->bm_ack_buf); kfree(i2400m->bm_cmd_buf); d_fnend(3, dev, "(i2400m %p) = void\n", i2400m); diff --git a/drivers/net/wimax/i2400m/fw.c b/drivers/net/wimax/i2400m/fw.c index 5719f4a4080f..69f9e45eafbf 100644 --- a/drivers/net/wimax/i2400m/fw.c +++ b/drivers/net/wimax/i2400m/fw.c @@ -105,6 +105,13 @@ * read an acknolwedgement from it (or an asynchronous notification) * from it. * + * FIRMWARE LOADING + * + * Note that in some cases, we can't just load a firmware file (for + * example, when resuming). For that, we might cache the firmware + * file. Thus, when doing the bootstrap, if there is a cache firmware + * file, it is used; if not, loading from disk is attempted. + * * ROADMAP * * i2400m_barker_db_init Called by i2400m_driver_init() @@ -114,9 +121,10 @@ * * i2400m_dev_bootstrap Called by __i2400m_dev_start() * request_firmware - * i2400m_fw_check - * i2400m_fw_hdr_check - * i2400m_fw_dnload + * i2400m_fw_bootstrap + * i2400m_fw_check + * i2400m_fw_hdr_check + * i2400m_fw_dnload * release_firmware * * i2400m_fw_dnload @@ -141,6 +149,10 @@ * * i2400m_bm_cmd_prepare Used by bus-drivers to prep * commands before sending + * + * i2400m_pm_notifier Called on Power Management events + * i2400m_fw_cache + * i2400m_fw_uncache */ #include #include @@ -1459,6 +1471,61 @@ error_dev_rebooted: goto hw_reboot; } +static +int i2400m_fw_bootstrap(struct i2400m *i2400m, const struct firmware *fw, + enum i2400m_bri flags) +{ + int ret; + struct device *dev = i2400m_dev(i2400m); + const struct i2400m_bcf_hdr *bcf; /* Firmware data */ + + d_fnstart(5, dev, "(i2400m %p)\n", i2400m); + bcf = (void *) fw->data; + ret = i2400m_fw_check(i2400m, bcf, fw->size); + if (ret >= 0) + ret = i2400m_fw_dnload(i2400m, bcf, fw->size, flags); + if (ret < 0) + dev_err(dev, "%s: cannot use: %d, skipping\n", + i2400m->fw_name, ret); + kfree(i2400m->fw_hdrs); + i2400m->fw_hdrs = NULL; + d_fnend(5, dev, "(i2400m %p) = %d\n", i2400m, ret); + return ret; +} + + +/* Refcounted container for firmware data */ +struct i2400m_fw { + struct kref kref; + const struct firmware *fw; +}; + + +static +void i2400m_fw_destroy(struct kref *kref) +{ + struct i2400m_fw *i2400m_fw = + container_of(kref, struct i2400m_fw, kref); + release_firmware(i2400m_fw->fw); + kfree(i2400m_fw); +} + + +static +struct i2400m_fw *i2400m_fw_get(struct i2400m_fw *i2400m_fw) +{ + if (i2400m_fw != NULL && i2400m_fw != (void *) ~0) + kref_get(&i2400m_fw->kref); + return i2400m_fw; +} + + +static +void i2400m_fw_put(struct i2400m_fw *i2400m_fw) +{ + kref_put(&i2400m_fw->kref, i2400m_fw_destroy); +} + /** * i2400m_dev_bootstrap - Bring the device to a known state and upload firmware @@ -1479,12 +1546,28 @@ int i2400m_dev_bootstrap(struct i2400m *i2400m, enum i2400m_bri flags) { int ret, itr; struct device *dev = i2400m_dev(i2400m); - const struct firmware *fw; + struct i2400m_fw *i2400m_fw; const struct i2400m_bcf_hdr *bcf; /* Firmware data */ + const struct firmware *fw; const char *fw_name; d_fnstart(5, dev, "(i2400m %p)\n", i2400m); + ret = -ENODEV; + spin_lock(&i2400m->rx_lock); + i2400m_fw = i2400m_fw_get(i2400m->fw_cached); + spin_unlock(&i2400m->rx_lock); + if (i2400m_fw == (void *) ~0) { + dev_err(dev, "can't load firmware now!"); + goto out; + } else if (i2400m_fw != NULL) { + dev_info(dev, "firmware %s: loading from cache\n", + i2400m->fw_name); + ret = i2400m_fw_bootstrap(i2400m, i2400m_fw->fw, flags); + i2400m_fw_put(i2400m_fw); + goto out; + } + /* Load firmware files to memory. */ for (itr = 0, bcf = NULL, ret = -ENOENT; ; itr++) { fw_name = i2400m->bus_fw_names[itr]; @@ -1500,21 +1583,71 @@ int i2400m_dev_bootstrap(struct i2400m *i2400m, enum i2400m_bri flags) fw_name, ret); continue; } - bcf = (void *) fw->data; i2400m->fw_name = fw_name; - ret = i2400m_fw_check(i2400m, bcf, fw->size); - if (ret >= 0) - ret = i2400m_fw_dnload(i2400m, bcf, fw->size, flags); - if (ret < 0) - dev_err(dev, "%s: cannot use: %d, skipping\n", - fw_name, ret); - kfree(i2400m->fw_hdrs); - i2400m->fw_hdrs = NULL; + ret = i2400m_fw_bootstrap(i2400m, fw, flags); release_firmware(fw); if (ret >= 0) /* firmware loaded succesfully */ break; + i2400m->fw_name = NULL; } +out: d_fnend(5, dev, "(i2400m %p) = %d\n", i2400m, ret); return ret; } EXPORT_SYMBOL_GPL(i2400m_dev_bootstrap); + + +void i2400m_fw_cache(struct i2400m *i2400m) +{ + int result; + struct i2400m_fw *i2400m_fw; + struct device *dev = i2400m_dev(i2400m); + + /* if there is anything there, free it -- now, this'd be weird */ + spin_lock(&i2400m->rx_lock); + i2400m_fw = i2400m->fw_cached; + spin_unlock(&i2400m->rx_lock); + if (i2400m_fw != NULL && i2400m_fw != (void *) ~0) { + i2400m_fw_put(i2400m_fw); + WARN(1, "%s:%u: still cached fw still present?\n", + __func__, __LINE__); + } + + if (i2400m->fw_name == NULL) { + dev_err(dev, "firmware n/a: can't cache\n"); + i2400m_fw = (void *) ~0; + goto out; + } + + i2400m_fw = kzalloc(sizeof(*i2400m_fw), GFP_ATOMIC); + if (i2400m_fw == NULL) + goto out; + kref_init(&i2400m_fw->kref); + result = request_firmware(&i2400m_fw->fw, i2400m->fw_name, dev); + if (result < 0) { + dev_err(dev, "firmware %s: failed to cache: %d\n", + i2400m->fw_name, result); + kfree(i2400m_fw); + i2400m_fw = (void *) ~0; + } else + dev_info(dev, "firmware %s: cached\n", i2400m->fw_name); +out: + spin_lock(&i2400m->rx_lock); + i2400m->fw_cached = i2400m_fw; + spin_unlock(&i2400m->rx_lock); +} + + +void i2400m_fw_uncache(struct i2400m *i2400m) +{ + struct i2400m_fw *i2400m_fw; + + spin_lock(&i2400m->rx_lock); + i2400m_fw = i2400m->fw_cached; + i2400m->fw_cached = NULL; + spin_unlock(&i2400m->rx_lock); + + if (i2400m_fw != NULL && i2400m_fw != (void *) ~0) + i2400m_fw_put(i2400m_fw); +} + diff --git a/drivers/net/wimax/i2400m/i2400m.h b/drivers/net/wimax/i2400m/i2400m.h index 0c165de89b2d..916b1d319299 100644 --- a/drivers/net/wimax/i2400m/i2400m.h +++ b/drivers/net/wimax/i2400m/i2400m.h @@ -424,11 +424,21 @@ struct i2400m_barker_db; * @fw_hdrs: NULL terminated array of pointers to the firmware * headers. This is only available during firmware load time. * + * @fw_cached: Used to cache firmware when the system goes to + * suspend/standby/hibernation (as on resume we can't read it). If + * NULL, no firmware was cached, read it. If ~0, you can't read + * any firmware files (the system still didn't come out of suspend + * and failed to cache one), so abort; otherwise, a valid cached + * firmware to be used. Access to this variable is protected by + * the spinlock i2400m->rx_lock. + * * @barker: barker type that the device uses; this is initialized by * i2400m_is_boot_barker() the first time it is called. Then it * won't change during the life cycle of the device and everytime * a boot barker is received, it is just verified for it being the * same. + * + * @pm_notifier: used to register for PM events */ struct i2400m { struct wimax_dev wimax_dev; /* FIRST! See doc */ @@ -495,7 +505,10 @@ struct i2400m { const char *fw_name; /* name of the current firmware image */ unsigned long fw_version; /* version of the firmware interface */ const struct i2400m_bcf_hdr **fw_hdrs; + struct i2400m_fw *fw_cached; /* protected by rx_lock */ struct i2400m_barker_db *barker; + + struct notifier_block pm_notifier; }; @@ -671,6 +684,9 @@ extern void i2400m_tx_release(struct i2400m *); extern int i2400m_rx_setup(struct i2400m *); extern void i2400m_rx_release(struct i2400m *); +extern void i2400m_fw_cache(struct i2400m *); +extern void i2400m_fw_uncache(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 *, -- cgit v1.2.3 From 1a5a73c0c5459f991b871855eb36099df65ecb7e Mon Sep 17 00:00:00 2001 From: Inaky Perez-Gonzalez Date: Mon, 14 Sep 2009 15:28:14 -0700 Subject: wimax/i2400m: implement .reset_resume in USB subdriver Current driver didn't implement the .reset_resume method. The i2400m normally always reset on a comeback from system standby/hibernation. This requires previously applied commits to cache the firmware image file. Signed-off-by: Inaky Perez-Gonzalez --- drivers/net/wimax/i2400m/usb.c | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) (limited to 'drivers/net/wimax') diff --git a/drivers/net/wimax/i2400m/usb.c b/drivers/net/wimax/i2400m/usb.c index 77d08d928274..07653ded6c59 100644 --- a/drivers/net/wimax/i2400m/usb.c +++ b/drivers/net/wimax/i2400m/usb.c @@ -623,6 +623,21 @@ out: } +static +int i2400mu_reset_resume(struct usb_interface *iface) +{ + int result; + 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); + result = i2400m_dev_reset_handle(i2400m, "device reset on resume"); + d_fnend(3, dev, "(iface %p) = %d\n", iface, result); + return result < 0 ? result : 0; +} + + static struct usb_device_id i2400mu_id_table[] = { { USB_DEVICE(0x8086, USB_DEVICE_ID_I6050) }, @@ -643,6 +658,7 @@ struct usb_driver i2400mu_driver = { .name = KBUILD_MODNAME, .suspend = i2400mu_suspend, .resume = i2400mu_resume, + .reset_resume = i2400mu_reset_resume, .probe = i2400mu_probe, .disconnect = i2400mu_disconnect, .id_table = i2400mu_id_table, -- cgit v1.2.3 From cb5b756f746b77c5323ae413a41e9a40ea33c453 Mon Sep 17 00:00:00 2001 From: Inaky Perez-Gonzalez Date: Tue, 15 Sep 2009 15:25:20 -0700 Subject: wimax/i2400m: don't overwrite error codes when failing to load firmware Make sure that i2400m_dev_bootstrap() doesn't overwrite the last known error code with -ENOENT; when a firmware fails to load, we want to know the cause and not a generic error code. Signed-off-by: Inaky Perez-Gonzalez --- drivers/net/wimax/i2400m/fw.c | 1 - 1 file changed, 1 deletion(-) (limited to 'drivers/net/wimax') diff --git a/drivers/net/wimax/i2400m/fw.c b/drivers/net/wimax/i2400m/fw.c index 69f9e45eafbf..3d67bcfc7b58 100644 --- a/drivers/net/wimax/i2400m/fw.c +++ b/drivers/net/wimax/i2400m/fw.c @@ -1573,7 +1573,6 @@ int i2400m_dev_bootstrap(struct i2400m *i2400m, enum i2400m_bri flags) fw_name = i2400m->bus_fw_names[itr]; if (fw_name == NULL) { dev_err(dev, "Could not find a usable firmware image\n"); - ret = -ENOENT; break; } d_printf(1, dev, "trying firmware %s (%d)\n", fw_name, itr); -- cgit v1.2.3 From ac53aed9349242095a780f57ac0c995fb170c950 Mon Sep 17 00:00:00 2001 From: Inaky Perez-Gonzalez Date: Wed, 16 Sep 2009 16:30:39 -0700 Subject: wimax/i2400m: on device stop, clean up pending wake & TX work When the i2400m device needs to wake up an idle WiMAX connection, it schedules a workqueue job to do it. Currently, only when the network stack called the _stop() method this work struct was being cancelled. This has to be done every time the device is stopped. So add a call in i2400m_dev_stop() to take care of such cleanup, which is now wrapped in i2400m_net_wake_stop(). Signed-off-by: Inaky Perez-Gonzalez --- drivers/net/wimax/i2400m/driver.c | 1 + drivers/net/wimax/i2400m/i2400m.h | 1 + drivers/net/wimax/i2400m/netdev.c | 53 ++++++++++++++++++++++++--------------- 3 files changed, 35 insertions(+), 20 deletions(-) (limited to 'drivers/net/wimax') diff --git a/drivers/net/wimax/i2400m/driver.c b/drivers/net/wimax/i2400m/driver.c index 07d12be0cf81..a33df0431020 100644 --- a/drivers/net/wimax/i2400m/driver.c +++ b/drivers/net/wimax/i2400m/driver.c @@ -527,6 +527,7 @@ void __i2400m_dev_stop(struct i2400m *i2400m) d_fnstart(3, dev, "(i2400m %p)\n", i2400m); wimax_state_change(wimax_dev, __WIMAX_ST_QUIESCING); + i2400m_net_wake_stop(i2400m); i2400m_dev_shutdown(i2400m); i2400m->ready = 0; i2400m->bus_dev_stop(i2400m); diff --git a/drivers/net/wimax/i2400m/i2400m.h b/drivers/net/wimax/i2400m/i2400m.h index 916b1d319299..303eb78bce32 100644 --- a/drivers/net/wimax/i2400m/i2400m.h +++ b/drivers/net/wimax/i2400m/i2400m.h @@ -691,6 +691,7 @@ 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); +extern void i2400m_net_wake_stop(struct i2400m *); enum i2400m_pt; extern int i2400m_tx(struct i2400m *, const void *, size_t, enum i2400m_pt); diff --git a/drivers/net/wimax/i2400m/netdev.c b/drivers/net/wimax/i2400m/netdev.c index 960fb5467546..0e8f6a046b9b 100644 --- a/drivers/net/wimax/i2400m/netdev.c +++ b/drivers/net/wimax/i2400m/netdev.c @@ -113,11 +113,6 @@ int i2400m_open(struct net_device *net_dev) } -/* - * - * 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) { @@ -125,21 +120,7 @@ int i2400m_stop(struct net_device *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); - } + i2400m_net_wake_stop(i2400m); d_fnend(3, dev, "(net_dev %p [i2400m %p]) = 0\n", net_dev, i2400m); return 0; } @@ -230,6 +211,38 @@ void i2400m_tx_prep_header(struct sk_buff *skb) } + +/* + * Cleanup resources acquired during i2400m_net_wake_tx() + * + * This is called by __i2400m_dev_stop and means we have to make sure + * the workqueue is flushed from any pending work. + */ +void i2400m_net_wake_stop(struct i2400m *i2400m) +{ + struct device *dev = i2400m_dev(i2400m); + + d_fnstart(3, dev, "(i2400m %p)\n", 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, "(i2400m %p) = void\n", i2400m); + return; +} + + /* * TX an skb to an idle device * -- cgit v1.2.3 From 8f90f3ee83dc54e182d6a7548727cbae4b523e6e Mon Sep 17 00:00:00 2001 From: Inaky Perez-Gonzalez Date: Wed, 16 Sep 2009 17:53:57 -0700 Subject: wimax/i2400m: cleanup initialization/destruction flow Currently the i2400m driver was starting in a weird way: registering a network device, setting the device up and then registering a WiMAX device. This is an historic artifact, and was causing issues, a some early reports the device sends were getting lost by issue of the wimax_dev not being registered. Fix said situation by doing the wimax device registration in i2400m_setup() after network device registration and before starting thed device. As well, removed spurious setting of the state to UNINITIALIZED; i2400m.dev_start() does that already. Signed-off-by: Inaky Perez-Gonzalez --- drivers/net/wimax/i2400m/driver.c | 32 ++++++++++++++------------------ drivers/net/wimax/i2400m/i2400m.h | 30 +++++++++++++++++++++--------- drivers/net/wimax/i2400m/netdev.c | 10 ++++++---- 3 files changed, 41 insertions(+), 31 deletions(-) (limited to 'drivers/net/wimax') diff --git a/drivers/net/wimax/i2400m/driver.c b/drivers/net/wimax/i2400m/driver.c index a33df0431020..a0ae19966c0c 100644 --- a/drivers/net/wimax/i2400m/driver.c +++ b/drivers/net/wimax/i2400m/driver.c @@ -754,13 +754,9 @@ EXPORT_SYMBOL_GPL(i2400m_bm_buf_free * * 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. + * Sets up basic device comunication infrastructure, boots the ROM to + * read the MAC address, registers with the WiMAX and network stacks + * and then brings up the device. */ int i2400m_setup(struct i2400m *i2400m, enum i2400m_bri bm_flags) { @@ -796,18 +792,13 @@ int i2400m_setup(struct i2400m *i2400m, enum i2400m_bri bm_flags) } 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); @@ -815,22 +806,27 @@ int i2400m_setup(struct i2400m *i2400m, enum i2400m_bri bm_flags) 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; } + + result = i2400m_dev_start(i2400m, bm_flags); + if (result < 0) + goto error_dev_start; d_fnend(3, dev, "(i2400m %p) = %d\n", i2400m, result); return result; +error_dev_start: + i2400m_debugfs_rm(i2400m); 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: unregister_pm_notifier(&i2400m->pm_notifier); @@ -854,15 +850,15 @@ void i2400m_release(struct i2400m *i2400m) d_fnstart(3, dev, "(i2400m %p)\n", i2400m); netif_stop_queue(i2400m->wimax_dev.net_dev); + i2400m_dev_stop(i2400m); + 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); unregister_pm_notifier(&i2400m->pm_notifier); - kfree(i2400m->bm_ack_buf); - kfree(i2400m->bm_cmd_buf); + i2400m_bm_buf_free(i2400m); d_fnend(3, dev, "(i2400m %p) = void\n", i2400m); } EXPORT_SYMBOL_GPL(i2400m_release); diff --git a/drivers/net/wimax/i2400m/i2400m.h b/drivers/net/wimax/i2400m/i2400m.h index 303eb78bce32..2b9c400810b5 100644 --- a/drivers/net/wimax/i2400m/i2400m.h +++ b/drivers/net/wimax/i2400m/i2400m.h @@ -117,16 +117,28 @@ * 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. + * can register a network device. * - * At this point, control and data communications are possible. + * The high-level call flow is: + * + * bus_probe() + * i2400m_setup() + * boot rom initialization / read mac addr + * network / WiMAX stacks registration + * i2400m_dev_start() + * i2400m->bus_dev_start() + * i2400m_dev_initialize() + * + * The reverse applies for a disconnect() call: * - * 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. + * bus_disconnect() + * i2400m_release() + * i2400m_dev_stop() + * i2400m_dev_shutdown() + * i2400m->bus_dev_stop() + * network / WiMAX stack unregistration + * + * At this point, control and data communications are possible. * * 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 @@ -706,7 +718,7 @@ static inline int i2400m_debugfs_add(struct i2400m *i2400m) static inline void i2400m_debugfs_rm(struct i2400m *i2400m) {} #endif -/* Called by _dev_start()/_dev_stop() to initialize the device itself */ +/* Initialize/shutdown the device */ extern int i2400m_dev_initialize(struct i2400m *); extern void i2400m_dev_shutdown(struct i2400m *); diff --git a/drivers/net/wimax/i2400m/netdev.c b/drivers/net/wimax/i2400m/netdev.c index 0e8f6a046b9b..fefd794087ad 100644 --- a/drivers/net/wimax/i2400m/netdev.c +++ b/drivers/net/wimax/i2400m/netdev.c @@ -102,11 +102,13 @@ int i2400m_open(struct net_device *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 + /* Make sure we wait until init is complete... */ + mutex_lock(&i2400m->init_mutex); + if (i2400m->updown) result = 0; + else + result = -EBUSY; + mutex_unlock(&i2400m->init_mutex); d_fnend(3, dev, "(net_dev %p [i2400m %p]) = %d\n", net_dev, i2400m, result); return result; -- cgit v1.2.3 From c2315b4ea9ac9c3f8caf03c3511d86fabe4a5fcd Mon Sep 17 00:00:00 2001 From: Inaky Perez-Gonzalez Date: Wed, 16 Sep 2009 17:10:55 -0700 Subject: wimax/i2400m: clarify and fix i2400m->{ready,updown} The i2400m driver uses two different bits to distinguish how much the driver is up. i2400m->ready is used to denote that the infrastructure to communicate with the device is up and running. i2400m->updown is used to indicate if 'ready' and the device is up and running, ready to take control and data traffic. However, all this was pretty dirty and not clear, with many open spots where race conditions were present. This commit cleans up the situation by: - documenting the usage of both bits - setting them only in specific, well controlled places (i2400m_dev_start, i2400m_dev_stop) - ensuring the i2400m workqueue can't get in the middle of the setting by flushing it when i2400m->ready is set to zero. This allows the report hook not having to check again for the bit to be set [rx.c:i2400m_report_hook_work()]. - using i2400m->updown to determine if the device is up and running instead of the wimax state in i2400m_dev_reset_handle(). - not loosing missed messages sent by the hardware before i2400m->ready is set. In rx.c, whatever the device sends can be sent to user space over the message pipes as soon as the wimax device is registered, so don't wait for i2400m->ready to be set. Signed-off-by: Inaky Perez-Gonzalez --- drivers/net/wimax/i2400m/driver.c | 44 ++++++++++++++++++++++++++------------- drivers/net/wimax/i2400m/i2400m.h | 23 +++++++++++++++++++- drivers/net/wimax/i2400m/rx.c | 16 +++++++------- drivers/net/wimax/i2400m/usb.c | 7 ++++++- include/net/wimax.h | 6 ++++++ 5 files changed, 70 insertions(+), 26 deletions(-) (limited to 'drivers/net/wimax') diff --git a/drivers/net/wimax/i2400m/driver.c b/drivers/net/wimax/i2400m/driver.c index a0ae19966c0c..6280646d7d7f 100644 --- a/drivers/net/wimax/i2400m/driver.c +++ b/drivers/net/wimax/i2400m/driver.c @@ -455,6 +455,8 @@ retry: result = i2400m->bus_dev_start(i2400m); if (result < 0) goto error_bus_dev_start; + i2400m->ready = 1; + wmb(); /* see i2400m->ready's documentation */ result = i2400m_firmware_check(i2400m); /* fw versions ok? */ if (result < 0) goto error_fw_check; @@ -462,7 +464,6 @@ retry: 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) @@ -475,6 +476,9 @@ retry: error_dev_initialize: error_check_mac_addr: + i2400m->ready = 0; + wmb(); /* see i2400m->ready's documentation */ + flush_workqueue(i2400m->work_queue); error_fw_check: i2400m->bus_dev_stop(i2400m); error_bus_dev_start: @@ -498,11 +502,15 @@ error_bootstrap: static int i2400m_dev_start(struct i2400m *i2400m, enum i2400m_bri bm_flags) { - int result; + int result = 0; mutex_lock(&i2400m->init_mutex); /* Well, start the device */ - result = __i2400m_dev_start(i2400m, bm_flags); - if (result >= 0) - i2400m->updown = 1; + if (i2400m->updown == 0) { + result = __i2400m_dev_start(i2400m, bm_flags); + if (result >= 0) { + i2400m->updown = 1; + wmb(); /* see i2400m->updown's documentation */ + } + } mutex_unlock(&i2400m->init_mutex); return result; } @@ -529,7 +537,14 @@ void __i2400m_dev_stop(struct i2400m *i2400m) wimax_state_change(wimax_dev, __WIMAX_ST_QUIESCING); i2400m_net_wake_stop(i2400m); i2400m_dev_shutdown(i2400m); - i2400m->ready = 0; + /* + * Make sure no report hooks are running *before* we stop the + * communication infrastructure with the device. + */ + i2400m->ready = 0; /* nobody can queue work anymore */ + wmb(); /* see i2400m->ready's documentation */ + flush_workqueue(i2400m->work_queue); + i2400m->bus_dev_stop(i2400m); destroy_workqueue(i2400m->work_queue); i2400m_rx_release(i2400m); @@ -551,6 +566,7 @@ void i2400m_dev_stop(struct i2400m *i2400m) if (i2400m->updown) { __i2400m_dev_stop(i2400m); i2400m->updown = 0; + wmb(); /* see i2400m->updown's documentation */ } mutex_unlock(&i2400m->init_mutex); } @@ -632,7 +648,6 @@ void __i2400m_dev_reset_handle(struct work_struct *ws) const char *reason; struct i2400m *i2400m = iw->i2400m; struct device *dev = i2400m_dev(i2400m); - enum wimax_st wimax_state; struct i2400m_reset_ctx *ctx = i2400m->reset_ctx; if (WARN_ON(iw->pl_size != sizeof(reason))) @@ -647,29 +662,28 @@ void __i2400m_dev_reset_handle(struct work_struct *ws) /* 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"); + dev_err(dev, "device rebooted somewhere else?\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, "%s: it is down, ignoring\n", reason); - goto out_unlock; /* ifconfig up/down wasn't called */ + if (i2400m->updown == 0) { + dev_info(dev, "%s: device is down, doing nothing\n", reason); + goto out_unlock; } dev_err(dev, "%s: reinitializing driver\n", reason); __i2400m_dev_stop(i2400m); - i2400m->updown = 0; result = __i2400m_dev_start(i2400m, I2400M_BRI_SOFT | I2400M_BRI_MAC_REINIT); if (result < 0) { + i2400m->updown = 0; + wmb(); /* see i2400m->updown's documentation */ dev_err(dev, "%s: cannot start the device: %d\n", reason, 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; diff --git a/drivers/net/wimax/i2400m/i2400m.h b/drivers/net/wimax/i2400m/i2400m.h index 2b9c400810b5..fbc156db5bfd 100644 --- a/drivers/net/wimax/i2400m/i2400m.h +++ b/drivers/net/wimax/i2400m/i2400m.h @@ -307,6 +307,27 @@ struct i2400m_barker_db; * force this to be the first field so that we can get from * netdev_priv() the right pointer. * + * @updown: the device is up and ready for transmitting control and + * data packets. This implies @ready (communication infrastructure + * with the device is ready) and the device's firmware has been + * loaded and the device initialized. + * + * Write to it only inside a i2400m->init_mutex protected area + * followed with a wmb(); rmb() before accesing (unless locked + * inside i2400m->init_mutex). Read access can be loose like that + * [just using rmb()] because the paths that use this also do + * other error checks later on. + * + * @ready: Communication infrastructure with the device is ready, data + * frames can start to be passed around (this is lighter than + * using the WiMAX state for certain hot paths). + * + * Write to it only inside a i2400m->init_mutex protected area + * followed with a wmb(); rmb() before accesing (unless locked + * inside i2400m->init_mutex). Read access can be loose like that + * [just using rmb()] because the paths that use this also do + * other error checks later on. + * * @rx_reorder: 1 if RX reordering is enabled; this can only be * set at probe time. * @@ -458,7 +479,7 @@ struct i2400m { 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 ready:1; /* Device comm infrastructure ready */ 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 */ diff --git a/drivers/net/wimax/i2400m/rx.c b/drivers/net/wimax/i2400m/rx.c index bcd411f1a854..82c200ad9fdc 100644 --- a/drivers/net/wimax/i2400m/rx.c +++ b/drivers/net/wimax/i2400m/rx.c @@ -177,8 +177,7 @@ 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; - if (iw->i2400m->ready) - i2400m_report_hook(iw->i2400m, args->l3l4_hdr, args->size); + i2400m_report_hook(iw->i2400m, args->l3l4_hdr, args->size); kfree_skb(args->skb_rx); i2400m_put(iw->i2400m); kfree(iw); @@ -305,11 +304,12 @@ void i2400m_rx_ctl(struct i2400m *i2400m, struct sk_buff *skb_rx, .l3l4_hdr = l3l4_hdr, .size = size }; - if (unlikely(i2400m->ready == 0)) /* only send if up */ - return; - skb_get(skb_rx); - i2400m_queue_work(i2400m, i2400m_report_hook_work, - GFP_KERNEL, &args, sizeof(args)); + rmb(); /* see i2400m->ready's documentation */ + if (likely(i2400m->ready)) { /* only send if up */ + 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); @@ -363,8 +363,6 @@ void i2400m_rx_trace(struct i2400m *i2400m, msg_type & I2400M_MT_REPORT_MASK ? "REPORT" : "CMD/SET/GET", msg_type, size); d_dump(2, dev, l3l4_hdr, size); - if (unlikely(i2400m->ready == 0)) /* only send if up */ - return; result = wimax_msg(wimax_dev, "trace", l3l4_hdr, size, GFP_KERNEL); if (result < 0) dev_err(dev, "error sending trace to userspace: %d\n", diff --git a/drivers/net/wimax/i2400m/usb.c b/drivers/net/wimax/i2400m/usb.c index 07653ded6c59..f4dfb60bb628 100644 --- a/drivers/net/wimax/i2400m/usb.c +++ b/drivers/net/wimax/i2400m/usb.c @@ -516,7 +516,10 @@ void i2400mu_disconnect(struct usb_interface *iface) * 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. + * nothing has to be done. Note we can be "loose" about not reading + * i2400m->updown under i2400m->init_mutex. If it happens to change + * inmediately, other parts of the call flow will fail and effectively + * catch it. * * If the firmware is loaded, we need to: * @@ -555,6 +558,7 @@ int i2400mu_suspend(struct usb_interface *iface, pm_message_t pm_msg) #endif d_fnstart(3, dev, "(iface %p pm_msg %u)\n", iface, pm_msg.event); + rmb(); /* see i2400m->updown's documentation */ if (i2400m->updown == 0) goto no_firmware; if (i2400m->state == I2400M_SS_DATA_PATH_CONNECTED && is_autosuspend) { @@ -608,6 +612,7 @@ int i2400mu_resume(struct usb_interface *iface) struct i2400m *i2400m = &i2400mu->i2400m; d_fnstart(3, dev, "(iface %p)\n", iface); + rmb(); /* see i2400m->updown's documentation */ if (i2400m->updown == 0) { d_printf(1, dev, "fw was down, no resume neeed\n"); goto out; diff --git a/include/net/wimax.h b/include/net/wimax.h index 2af7bf839f23..d69c4a7a1267 100644 --- a/include/net/wimax.h +++ b/include/net/wimax.h @@ -195,6 +195,12 @@ * defining the `struct nla_policy` for each message, it has to have * an array size of WIMAX_GNL_ATTR_MAX+1. * + * The op_*() function pointers will not be called if the wimax_dev is + * in a state <= %WIMAX_ST_UNINITIALIZED. The exception is: + * + * - op_reset: can be called at any time after wimax_dev_add() has + * been called. + * * THE PIPE INTERFACE: * * This interface is kept intentionally simple. The driver can send -- cgit v1.2.3 From 0856ccf29dfbaf957e4be80dd3eb88d97810b633 Mon Sep 17 00:00:00 2001 From: Inaky Perez-Gonzalez Date: Wed, 16 Sep 2009 18:23:27 -0700 Subject: wimax/i2400m: introduce i2400m->bus_setup/release The SDIO subdriver of the i2400m requires certain steps to be done before we do any acces to the device, even for doing firmware upload. This lead to a few ugly hacks, which basically involve doing those steps in probe() before calling i2400m_setup() and undoing them in disconnect() after claling i2400m_release(); but then, much of those steps have to be repeated when resetting the device, suspending, etc (in upcoming pre/post reset support). Thus, a new pair of optional, bus-specific calls i2400m->bus_{setup/release} are introduced. These are used to setup basic infrastructure needed to load firmware onto the device. This commit also updates the SDIO subdriver to use said calls. Signed-off-by: Inaky Perez-Gonzalez --- drivers/net/wimax/i2400m/driver.c | 20 +++++++- drivers/net/wimax/i2400m/i2400m.h | 27 ++++++++++ drivers/net/wimax/i2400m/sdio.c | 100 ++++++++++++++++++++++++-------------- drivers/net/wimax/i2400m/usb.c | 2 + 4 files changed, 111 insertions(+), 38 deletions(-) (limited to 'drivers/net/wimax') diff --git a/drivers/net/wimax/i2400m/driver.c b/drivers/net/wimax/i2400m/driver.c index 6280646d7d7f..c57020f811cd 100644 --- a/drivers/net/wimax/i2400m/driver.c +++ b/drivers/net/wimax/i2400m/driver.c @@ -41,8 +41,10 @@ * __i2400m_dev_start() * * i2400m_setup() + * i2400m->bus_setup() * i2400m_bootrom_init() * register_netdev() + * wimax_dev_add() * i2400m_dev_start() * __i2400m_dev_start() * i2400m_dev_bootstrap() @@ -50,15 +52,15 @@ * 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() + * i2400m->bus_release() + * wimax_dev_rm() * unregister_netdev() */ #include "i2400m.h" @@ -784,6 +786,15 @@ int i2400m_setup(struct i2400m *i2400m, enum i2400m_bri bm_flags) snprintf(wimax_dev->name, sizeof(wimax_dev->name), "i2400m-%s:%s", dev->bus->name, dev_name(dev)); + if (i2400m->bus_setup) { + result = i2400m->bus_setup(i2400m); + if (result < 0) { + dev_err(dev, "bus-specific setup failed: %d\n", + result); + goto error_bus_setup; + } + } + result = i2400m_bootrom_init(i2400m, bm_flags); if (result < 0) { dev_err(dev, "read mac addr: bootrom init " @@ -846,6 +857,9 @@ error_register_netdev: unregister_pm_notifier(&i2400m->pm_notifier); error_read_mac_addr: error_bootrom_init: + if (i2400m->bus_release) + i2400m->bus_release(i2400m); +error_bus_setup: d_fnend(3, dev, "(i2400m %p) = %d\n", i2400m, result); return result; } @@ -872,6 +886,8 @@ void i2400m_release(struct i2400m *i2400m) wimax_dev_rm(&i2400m->wimax_dev); unregister_netdev(i2400m->wimax_dev.net_dev); unregister_pm_notifier(&i2400m->pm_notifier); + if (i2400m->bus_release) + i2400m->bus_release(i2400m); i2400m_bm_buf_free(i2400m); d_fnend(3, dev, "(i2400m %p) = void\n", i2400m); } diff --git a/drivers/net/wimax/i2400m/i2400m.h b/drivers/net/wimax/i2400m/i2400m.h index fbc156db5bfd..407d0972f2f6 100644 --- a/drivers/net/wimax/i2400m/i2400m.h +++ b/drivers/net/wimax/i2400m/i2400m.h @@ -123,6 +123,7 @@ * * bus_probe() * i2400m_setup() + * i2400m->bus_setup() * boot rom initialization / read mac addr * network / WiMAX stacks registration * i2400m_dev_start() @@ -137,6 +138,7 @@ * i2400m_dev_shutdown() * i2400m->bus_dev_stop() * network / WiMAX stack unregistration + * i2400m->bus_release() * * At this point, control and data communications are possible. * @@ -214,12 +216,35 @@ struct i2400m_barker_db; * Members marked with [fill] must be filled out/initialized before * calling i2400m_setup(). * + * Note the @bus_setup/@bus_release, @bus_dev_start/@bus_dev_release + * call pairs are very much doing almost the same, and depending on + * the underlying bus, some stuff has to be put in one or the + * other. The idea of setup/release is that they setup the minimal + * amount needed for loading firmware, where us dev_start/stop setup + * the rest needed to do full data/control traffic. + * * @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_setup: [optional fill] Function called by the bus-generic code + * [i2400m_setup()] to setup the basic bus-specific communications + * to the the device needed to load firmware. See LIFE CYCLE above. + * + * NOTE: Doesn't need to upload the firmware, as that is taken + * care of by the bus-generic code. + * + * @bus_release: [optional fill] Function called by the bus-generic + * code [i2400m_release()] to shutdown the basic bus-specific + * communications to the the device needed to load firmware. 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_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. @@ -490,8 +515,10 @@ struct i2400m { size_t bus_pl_size_max; unsigned bus_bm_retries; + int (*bus_setup)(struct i2400m *); int (*bus_dev_start)(struct i2400m *); void (*bus_dev_stop)(struct i2400m *); + void (*bus_release)(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 *, diff --git a/drivers/net/wimax/i2400m/sdio.c b/drivers/net/wimax/i2400m/sdio.c index de158ee0c32c..6e39665ecd89 100644 --- a/drivers/net/wimax/i2400m/sdio.c +++ b/drivers/net/wimax/i2400m/sdio.c @@ -164,6 +164,66 @@ function_enabled: } +/* + * Setup minimal device communication infrastructure needed to at + * least be able to update the firmware. + */ +static +int i2400ms_bus_setup(struct i2400m *i2400m) +{ + int result; + struct i2400ms *i2400ms = + container_of(i2400m, struct i2400ms, i2400m); + struct device *dev = i2400m_dev(i2400m); + struct sdio_func *func = i2400ms->func; + + 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(func, 1); + 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; + return 0; + +error_rx_setup: + sdio_claim_host(func); + sdio_disable_func(func); + sdio_release_host(func); +error_func_enable: +error_set_blk_size: + return result; +} + + +/* + * Tear down minimal device communication infrastructure needed to at + * least be able to update the firmware. + */ +static +void i2400ms_bus_release(struct i2400m *i2400m) +{ + struct i2400ms *i2400ms = + container_of(i2400m, struct i2400ms, i2400m); + struct sdio_func *func = i2400ms->func; + + i2400ms_rx_release(i2400ms); + sdio_claim_host(func); + sdio_disable_func(func); + sdio_release_host(func); +} + + /* * Setup driver resources needed to communicate with the device * @@ -315,17 +375,12 @@ do_bus_reset: 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); + i2400ms_bus_release(i2400m); /* Wait for the device to settle */ msleep(40); - result = i2400ms_enable_function(i2400ms->func, 0); - if (result >= 0) - i2400ms_rx_setup(i2400ms); + result = i2400ms_bus_setup(i2400m); } else BUG(); if (result < 0 && rt != I2400M_RT_BUS) { @@ -449,8 +504,10 @@ int i2400ms_probe(struct sdio_func *func, i2400m->bus_tx_block_size = I2400MS_BLK_SIZE; i2400m->bus_pl_size_max = I2400MS_PL_SIZE_MAX; + i2400m->bus_setup = i2400ms_bus_setup; i2400m->bus_dev_start = i2400ms_bus_dev_start; i2400m->bus_dev_stop = i2400ms_bus_dev_stop; + i2400m->bus_release = i2400ms_bus_release; i2400m->bus_tx_kick = i2400ms_bus_tx_kick; i2400m->bus_reset = i2400ms_bus_reset; /* The iwmc3200-wimax sometimes requires the driver to try @@ -462,20 +519,6 @@ int i2400ms_probe(struct sdio_func *func, 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, 1); - if (result < 0) { - dev_err(dev, "Cannot enable SDIO function: %d\n", result); - goto error_func_enable; - } - /* * Before we are enabling the device interrupt register, make * sure the buffer used during bootmode operation is setup so @@ -488,10 +531,6 @@ int i2400ms_probe(struct sdio_func *func, goto error_bootmode_buf_setup; } - 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); @@ -509,15 +548,8 @@ int i2400ms_probe(struct sdio_func *func, error_debugfs_add: i2400m_release(i2400m); error_setup: - i2400ms_rx_release(i2400ms); -error_rx_setup: i2400m_bm_buf_free(i2400m); error_bootmode_buf_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: @@ -535,12 +567,8 @@ 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); - sdio_disable_func(func); - sdio_release_host(func); free_netdev(net_dev); d_fnend(3, dev, "SDIO func %p\n", func); } diff --git a/drivers/net/wimax/i2400m/usb.c b/drivers/net/wimax/i2400m/usb.c index f4dfb60bb628..3bf3f7288fe6 100644 --- a/drivers/net/wimax/i2400m/usb.c +++ b/drivers/net/wimax/i2400m/usb.c @@ -416,8 +416,10 @@ int i2400mu_probe(struct usb_interface *iface, i2400m->bus_tx_block_size = I2400MU_BLK_SIZE; i2400m->bus_pl_size_max = I2400MU_PL_SIZE_MAX; + i2400m->bus_setup = NULL; i2400m->bus_dev_start = i2400mu_bus_dev_start; i2400m->bus_dev_stop = i2400mu_bus_dev_stop; + i2400m->bus_release = NULL; i2400m->bus_tx_kick = i2400mu_bus_tx_kick; i2400m->bus_reset = i2400mu_bus_reset; i2400m->bus_bm_retries = I2400M_USB_BOOT_RETRIES; -- cgit v1.2.3 From 2869da8587604e3fea5f85aeade486a08e8313bf Mon Sep 17 00:00:00 2001 From: Inaky Perez-Gonzalez Date: Wed, 16 Sep 2009 18:33:26 -0700 Subject: wimax/i2400m: do bootmode buffer management in i2400m_setup/release() After the introduction of i2400m->bus_setup/release, there is no more race condition where the bootmode buffers are needed before i2400m_setup() is called. Before, the SDIO driver would setup RX before calling i2400m_setup() and thus need those buffers; now RX setup is done in i2400m->bus_setup(), which is called by i2400m_setup(). Thus, all the bootmode buffer management can now be done completely inside i2400m_setup()/i2400m_release(), removing complexity from the bus-specific drivers. Signed-off-by: Inaky Perez-Gonzalez --- drivers/net/wimax/i2400m/driver.c | 28 +++++++++++++++++----------- drivers/net/wimax/i2400m/i2400m.h | 2 -- drivers/net/wimax/i2400m/sdio.c | 14 -------------- drivers/net/wimax/i2400m/usb.c | 8 -------- 4 files changed, 17 insertions(+), 35 deletions(-) (limited to 'drivers/net/wimax') diff --git a/drivers/net/wimax/i2400m/driver.c b/drivers/net/wimax/i2400m/driver.c index c57020f811cd..4fcdb18261fd 100644 --- a/drivers/net/wimax/i2400m/driver.c +++ b/drivers/net/wimax/i2400m/driver.c @@ -723,12 +723,13 @@ int i2400m_dev_reset_handle(struct i2400m *i2400m, const char *reason) EXPORT_SYMBOL_GPL(i2400m_dev_reset_handle); -/** - * i2400m_bm_buf_alloc - Alloc the command and ack buffers for boot mode +/* + * Alloc the command and ack buffers for boot mode * * Get the buffers needed to deal with boot mode messages. These * buffers need to be allocated before the sdio recieve irq is setup. */ +static int i2400m_bm_buf_alloc(struct i2400m *i2400m) { int result; @@ -747,22 +748,19 @@ error_bm_ack_buf_kzalloc: error_bm_cmd_kzalloc: return result; } -EXPORT_SYMBOL_GPL(i2400m_bm_buf_alloc); -/** - * i2400m_bm_buf_free - Free boot mode command and ack buffers. - * - * Free the command and ack buffers - * + +/* + * Free boot mode command and ack buffers. */ +static void i2400m_bm_buf_free(struct i2400m *i2400m) { kfree(i2400m->bm_ack_buf); kfree(i2400m->bm_cmd_buf); - return; } -EXPORT_SYMBOL_GPL(i2400m_bm_buf_free -); + + /** * i2400m_setup - bus-generic setup function for the i2400m device * @@ -786,6 +784,12 @@ int i2400m_setup(struct i2400m *i2400m, enum i2400m_bri bm_flags) snprintf(wimax_dev->name, sizeof(wimax_dev->name), "i2400m-%s:%s", dev->bus->name, dev_name(dev)); + result = i2400m_bm_buf_alloc(i2400m); + if (result < 0) { + dev_err(dev, "cannot allocate bootmode scratch buffers\n"); + goto error_bm_buf_alloc; + } + if (i2400m->bus_setup) { result = i2400m->bus_setup(i2400m); if (result < 0) { @@ -860,6 +864,8 @@ error_bootrom_init: if (i2400m->bus_release) i2400m->bus_release(i2400m); error_bus_setup: + i2400m_bm_buf_free(i2400m); +error_bm_buf_alloc: d_fnend(3, dev, "(i2400m %p) = %d\n", i2400m, result); return result; } diff --git a/drivers/net/wimax/i2400m/i2400m.h b/drivers/net/wimax/i2400m/i2400m.h index 407d0972f2f6..1724955e0fe9 100644 --- a/drivers/net/wimax/i2400m/i2400m.h +++ b/drivers/net/wimax/i2400m/i2400m.h @@ -817,8 +817,6 @@ void i2400m_put(struct i2400m *i2400m) } extern int i2400m_dev_reset_handle(struct i2400m *, const char *); -extern int i2400m_bm_buf_alloc(struct i2400m *i2400m); -extern void i2400m_bm_buf_free(struct i2400m *i2400m); /* * _setup()/_release() are called by the probe/disconnect functions of diff --git a/drivers/net/wimax/i2400m/sdio.c b/drivers/net/wimax/i2400m/sdio.c index 6e39665ecd89..14e66f06f235 100644 --- a/drivers/net/wimax/i2400m/sdio.c +++ b/drivers/net/wimax/i2400m/sdio.c @@ -519,18 +519,6 @@ int i2400ms_probe(struct sdio_func *func, i2400m->bus_bm_mac_addr_impaired = 1; i2400m->bus_bm_pokes_table = &i2400ms_pokes[0]; - /* - * Before we are enabling the device interrupt register, make - * sure the buffer used during bootmode operation is setup so - * when the first D2H data interrupt comes, the memory is - * available for copying the D2H data. - */ - result = i2400m_bm_buf_alloc(i2400m); - if (result < 0) { - dev_err(dev, "cannot allocate SDIO bootmode buffer\n"); - goto error_bootmode_buf_setup; - } - result = i2400m_setup(i2400m, I2400M_BRI_NO_REBOOT); if (result < 0) { dev_err(dev, "cannot setup device: %d\n", result); @@ -548,8 +536,6 @@ int i2400ms_probe(struct sdio_func *func, error_debugfs_add: i2400m_release(i2400m); error_setup: - i2400m_bm_buf_free(i2400m); -error_bootmode_buf_setup: sdio_set_drvdata(func, NULL); free_netdev(net_dev); error_alloc_netdev: diff --git a/drivers/net/wimax/i2400m/usb.c b/drivers/net/wimax/i2400m/usb.c index 3bf3f7288fe6..77567970fe9a 100644 --- a/drivers/net/wimax/i2400m/usb.c +++ b/drivers/net/wimax/i2400m/usb.c @@ -447,12 +447,6 @@ int i2400mu_probe(struct usb_interface *iface, usb_dev->autosuspend_disabled = 0; #endif - result = i2400m_bm_buf_alloc(i2400m); - if (result < 0) { - dev_err(dev, "cannot allocate USB bootmode buffer\n"); - goto error_bm_buf_alloc; - } - result = i2400m_setup(i2400m, I2400M_BRI_MAC_REINIT); if (result < 0) { dev_err(dev, "cannot setup device: %d\n", result); @@ -468,8 +462,6 @@ int i2400mu_probe(struct usb_interface *iface, error_debugfs_add: i2400m_release(i2400m); error_setup: - i2400m_bm_buf_free(i2400m); -error_bm_buf_alloc: usb_set_intfdata(iface, NULL); usb_put_dev(i2400mu->usb_dev); free_netdev(net_dev); -- cgit v1.2.3 From 3725d8c97436aeaa03eeb0c25361a7ec0f3f5bd2 Mon Sep 17 00:00:00 2001 From: Inaky Perez-Gonzalez Date: Thu, 17 Sep 2009 15:20:45 -0700 Subject: wimax/i2400m: Implement pre/post reset support in the USB driver The USB stack can callback a driver is about to be reset by an external entity and right after it, so the driver can save state and then restore it. This commit implements said support; it is implemented actually in the core, bus-generic driver [i2400m_{pre,post}_reset()] and used by the bus-specific drivers. This way the SDIO driver can also use it once said support is brought to the SDIO stack. Signed-off-by: Inaky Perez-Gonzalez --- drivers/net/wimax/i2400m/driver.c | 81 +++++++++++++++++++++++++++++++++++++++ drivers/net/wimax/i2400m/i2400m.h | 2 + drivers/net/wimax/i2400m/usb.c | 33 ++++++++++++++++ 3 files changed, 116 insertions(+) (limited to 'drivers/net/wimax') diff --git a/drivers/net/wimax/i2400m/driver.c b/drivers/net/wimax/i2400m/driver.c index 4fcdb18261fd..1f6aa2a55429 100644 --- a/drivers/net/wimax/i2400m/driver.c +++ b/drivers/net/wimax/i2400m/driver.c @@ -619,6 +619,87 @@ int i2400m_pm_notifier(struct notifier_block *notifier, } +/* + * pre-reset is called before a device is going on reset + * + * This has to be followed by a call to i2400m_post_reset(), otherwise + * bad things might happen. + */ +int i2400m_pre_reset(struct i2400m *i2400m) +{ + int result; + struct device *dev = i2400m_dev(i2400m); + + d_fnstart(3, dev, "(i2400m %p)\n", i2400m); + d_printf(1, dev, "pre-reset shut down\n"); + + result = 0; + mutex_lock(&i2400m->init_mutex); + if (i2400m->updown) { + netif_tx_disable(i2400m->wimax_dev.net_dev); + __i2400m_dev_stop(i2400m); + result = 0; + /* down't set updown to zero -- this way + * post_reset can restore properly */ + } + mutex_unlock(&i2400m->init_mutex); + if (i2400m->bus_release) + i2400m->bus_release(i2400m); + d_fnend(3, dev, "(i2400m %p) = %d\n", i2400m, result); + return result; +} +EXPORT_SYMBOL_GPL(i2400m_pre_reset); + + +/* + * Restore device state after a reset + * + * Do the work needed after a device reset to bring it up to the same + * state as it was before the reset. + * + * NOTE: this requires i2400m->init_mutex taken + */ +int i2400m_post_reset(struct i2400m *i2400m) +{ + int result = 0; + struct device *dev = i2400m_dev(i2400m); + + d_fnstart(3, dev, "(i2400m %p)\n", i2400m); + d_printf(1, dev, "post-reset start\n"); + if (i2400m->bus_setup) { + result = i2400m->bus_setup(i2400m); + if (result < 0) { + dev_err(dev, "bus-specific setup failed: %d\n", + result); + goto error_bus_setup; + } + } + mutex_lock(&i2400m->init_mutex); + if (i2400m->updown) { + result = __i2400m_dev_start( + i2400m, I2400M_BRI_SOFT | I2400M_BRI_MAC_REINIT); + if (result < 0) + goto error_dev_start; + } + mutex_unlock(&i2400m->init_mutex); + d_fnend(3, dev, "(i2400m %p) = %d\n", i2400m, result); + return result; + +error_dev_start: + if (i2400m->bus_release) + i2400m->bus_release(i2400m); +error_bus_setup: + /* even if the device was up, it could not be recovered, so we + * mark it as down. */ + i2400m->updown = 0; + wmb(); /* see i2400m->updown's documentation */ + mutex_unlock(&i2400m->init_mutex); + d_fnend(3, dev, "(i2400m %p) = %d\n", i2400m, result); + return result; +} +EXPORT_SYMBOL_GPL(i2400m_post_reset); + + /* * The device has rebooted; fix up the device and the driver * diff --git a/drivers/net/wimax/i2400m/i2400m.h b/drivers/net/wimax/i2400m/i2400m.h index 1724955e0fe9..8fc8a0ca5126 100644 --- a/drivers/net/wimax/i2400m/i2400m.h +++ b/drivers/net/wimax/i2400m/i2400m.h @@ -817,6 +817,8 @@ void i2400m_put(struct i2400m *i2400m) } extern int i2400m_dev_reset_handle(struct i2400m *, const char *); +extern int i2400m_pre_reset(struct i2400m *); +extern int i2400m_post_reset(struct i2400m *); /* * _setup()/_release() are called by the probe/disconnect functions of diff --git a/drivers/net/wimax/i2400m/usb.c b/drivers/net/wimax/i2400m/usb.c index 77567970fe9a..8b246cc498b1 100644 --- a/drivers/net/wimax/i2400m/usb.c +++ b/drivers/net/wimax/i2400m/usb.c @@ -637,6 +637,37 @@ int i2400mu_reset_resume(struct usb_interface *iface) } +/* + * Another driver or user space is triggering a reset on the device + * which contains the interface passed as an argument. Cease IO and + * save any device state you need to restore. + * + * If you need to allocate memory here, use GFP_NOIO or GFP_ATOMIC, if + * you are in atomic context. + */ +static +int i2400mu_pre_reset(struct usb_interface *iface) +{ + struct i2400mu *i2400mu = usb_get_intfdata(iface); + return i2400m_pre_reset(&i2400mu->i2400m); +} + + +/* + * The reset has completed. Restore any saved device state and begin + * using the device again. + * + * If you need to allocate memory here, use GFP_NOIO or GFP_ATOMIC, if + * you are in atomic context. + */ +static +int i2400mu_post_reset(struct usb_interface *iface) +{ + struct i2400mu *i2400mu = usb_get_intfdata(iface); + return i2400m_post_reset(&i2400mu->i2400m); +} + + static struct usb_device_id i2400mu_id_table[] = { { USB_DEVICE(0x8086, USB_DEVICE_ID_I6050) }, @@ -660,6 +691,8 @@ struct usb_driver i2400mu_driver = { .reset_resume = i2400mu_reset_resume, .probe = i2400mu_probe, .disconnect = i2400mu_disconnect, + .pre_reset = i2400mu_pre_reset, + .post_reset = i2400mu_post_reset, .id_table = i2400mu_id_table, .supports_autosuspend = 1, }; -- cgit v1.2.3 From e1633fd636f3ed0379fcf08c47962205eadddb6b Mon Sep 17 00:00:00 2001 From: Inaky Perez-Gonzalez Date: Fri, 18 Sep 2009 11:50:50 -0700 Subject: wimax/i2400m: reduce verbosity of debug messages in boot mode Missed a debug message that was being constantly printed as a dev_err(); became annoying. Demote it to a debug message. Signed-off-by: Inaky Perez-Gonzalez --- drivers/net/wimax/i2400m/sdio-rx.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers/net/wimax') diff --git a/drivers/net/wimax/i2400m/sdio-rx.c b/drivers/net/wimax/i2400m/sdio-rx.c index 98ee7fdfd621..8adf6c9b6f8f 100644 --- a/drivers/net/wimax/i2400m/sdio-rx.c +++ b/drivers/net/wimax/i2400m/sdio-rx.c @@ -165,7 +165,7 @@ void i2400ms_rx(struct i2400ms *i2400ms) 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"); + d_printf(5, dev, "RX: SDIO boot mode message\n"); kfree_skb(skb); goto out; } -- cgit v1.2.3 From 9835fd84990ca253c5b625005717a9be492788c0 Mon Sep 17 00:00:00 2001 From: Inaky Perez-Gonzalez Date: Tue, 29 Sep 2009 16:28:24 -0700 Subject: wimax/i2400m: fix race condition with tcpdump et al tcpdump and friends were not being able to decode packets sent via WiMAX; they had a zero ethernet type, even when the stack was properly sending them to the device with the right type. It happens that the driver was overwriting the (fake) ethernet header for creating the hardware header and that was bitting the cloning used by tcpdump (et al) to look into the packets. Use pkskb_expand_head() [method copied from the e1000 driver] to fix. Thanks to Herbert Xu and Andi Kleen for helping to diagnose and pointing to the right fix. Cc: Herbert Xu Cc: Andi Kleen Signed-off-by: Inaky Perez-Gonzalez --- drivers/net/wimax/i2400m/netdev.c | 21 ++++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) (limited to 'drivers/net/wimax') diff --git a/drivers/net/wimax/i2400m/netdev.c b/drivers/net/wimax/i2400m/netdev.c index fefd794087ad..e7d1a51ee3fe 100644 --- a/drivers/net/wimax/i2400m/netdev.c +++ b/drivers/net/wimax/i2400m/netdev.c @@ -358,6 +358,20 @@ netdev_tx_t i2400m_hard_start_xmit(struct sk_buff *skb, int result; d_fnstart(3, dev, "(skb %p net_dev %p)\n", skb, net_dev); + if (skb_header_cloned(skb)) { + /* + * Make tcpdump/wireshark happy -- if they are + * running, the skb is cloned and we will overwrite + * the mac fields in i2400m_tx_prep_header. Expand + * seems to fix this... + */ + result = pskb_expand_head(skb, 0, 0, GFP_ATOMIC); + if (result) { + result = NETDEV_TX_BUSY; + goto error_expand; + } + } + if (i2400m->state == I2400M_SS_IDLE) result = i2400m_net_wake_tx(i2400m, net_dev, skb); else @@ -368,10 +382,11 @@ netdev_tx_t i2400m_hard_start_xmit(struct sk_buff *skb, net_dev->stats.tx_packets++; net_dev->stats.tx_bytes += skb->len; } + result = NETDEV_TX_OK; +error_expand: kfree_skb(skb); - - d_fnend(3, dev, "(skb %p net_dev %p)\n", skb, net_dev); - return NETDEV_TX_OK; + d_fnend(3, dev, "(skb %p net_dev %p) = %d\n", skb, net_dev, result); + return result; } -- cgit v1.2.3 From 6f4fc90a36fbe87e3003b3f7c8090ecc89bd1353 Mon Sep 17 00:00:00 2001 From: Cindy H Kao Date: Fri, 2 Oct 2009 19:18:43 -0700 Subject: wimax/i2400m: use JUMP cmd for last FW chunk indication Both secure and non-secure boot must set the JUMP command in the bootmode header as the last FW chunk, so we change to use the JUMP command to decide if the FW chunk download is completed. Since we tend to use one single FW to support both secure and non-secure boot for most of the time, I2400M_BRH_SIGNED_JUMP is actually found even for non-secure boot. But in case the FW does come with I2400M_BRH_JUMP, we check for both of them in i2400m_dnload_bcf(). Signed-off-by: Cindy H Kao Signed-off-by: Inaky Perez-Gonzalez --- drivers/net/wimax/i2400m/fw.c | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) (limited to 'drivers/net/wimax') diff --git a/drivers/net/wimax/i2400m/fw.c b/drivers/net/wimax/i2400m/fw.c index 3d67bcfc7b58..97ea78406725 100644 --- a/drivers/net/wimax/i2400m/fw.c +++ b/drivers/net/wimax/i2400m/fw.c @@ -720,14 +720,17 @@ ssize_t i2400m_dnload_bcf(struct i2400m *i2400m, "downloading section #%zu (@%zu %zu B) to 0x%08x\n", section, offset, sizeof(*bh) + data_size, le32_to_cpu(bh->target_addr)); - if (i2400m_brh_get_opcode(bh) == I2400M_BRH_SIGNED_JUMP) { - /* Secure boot needs to stop here */ - d_printf(5, dev, "signed jump found @%zu\n", offset); + /* + * We look for JUMP cmd from the bootmode header, + * either I2400M_BRH_SIGNED_JUMP for secure boot + * or I2400M_BRH_JUMP for unsecure boot, the last chunk + * should be the bootmode header with JUMP cmd. + */ + if (i2400m_brh_get_opcode(bh) == I2400M_BRH_SIGNED_JUMP || + i2400m_brh_get_opcode(bh) == I2400M_BRH_JUMP) { + d_printf(5, dev, "jump found @%zu\n", offset); break; } - if (offset + section_size == bcf_len) - /* Non-secure boot stops here */ - break; if (offset + section_size > bcf_len) { dev_err(dev, "fw %s: bad section #%zu, " "end (@%zu) beyond EOF (@%zu)\n", -- cgit v1.2.3 From 28cff50d99ce9a1db65b7d4dcdcc0f1f8d9f9309 Mon Sep 17 00:00:00 2001 From: Cindy H Kao Date: Fri, 2 Oct 2009 19:31:17 -0700 Subject: wimax/i2400m: change the bcf_len to exclude the extended header size The actual fw->size may not equal to the bcf size indicated in the bcf header if the extended bcf debug header is added in the tail. To reflect the actual fw size that will be downloaded to the device, it is now retrived from from the size field indicated in the bcf header. All of the headers (if there are extended headers) should indicate same value for the size field since only one set of firmware chunks is downloaded Signed-off-by: Cindy H Kao --- drivers/net/wimax/i2400m/fw.c | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) (limited to 'drivers/net/wimax') diff --git a/drivers/net/wimax/i2400m/fw.c b/drivers/net/wimax/i2400m/fw.c index 97ea78406725..fda54bfdf70e 100644 --- a/drivers/net/wimax/i2400m/fw.c +++ b/drivers/net/wimax/i2400m/fw.c @@ -1388,15 +1388,16 @@ const struct i2400m_bcf_hdr *i2400m_bcf_hdr_find(struct i2400m *i2400m) */ static int i2400m_fw_dnload(struct i2400m *i2400m, const struct i2400m_bcf_hdr *bcf, - size_t bcf_size, enum i2400m_bri flags) + size_t fw_size, enum i2400m_bri flags) { int ret = 0; struct device *dev = i2400m_dev(i2400m); int count = i2400m->bus_bm_retries; const struct i2400m_bcf_hdr *bcf_hdr; + size_t bcf_size; - d_fnstart(5, dev, "(i2400m %p bcf %p size %zu)\n", - i2400m, bcf, bcf_size); + d_fnstart(5, dev, "(i2400m %p bcf %p fw size %zu)\n", + i2400m, bcf, fw_size); i2400m->boot_mode = 1; wmb(); /* Make sure other readers see it */ hw_reboot: @@ -1434,6 +1435,12 @@ hw_reboot: if (ret < 0) goto error_dnload_init; + /* + * bcf_size refers to one header size plus the fw sections size + * indicated by the header,ie. if there are other extended headers + * at the tail, they are not counted + */ + bcf_size = sizeof(u32) * le32_to_cpu(bcf_hdr->size); ret = i2400m_dnload_bcf(i2400m, bcf, bcf_size); if (ret == -ERESTARTSYS) goto error_dev_rebooted; @@ -1464,7 +1471,7 @@ error_bcf_hdr_find: error_bootrom_init: error_too_many_reboots: d_fnend(5, dev, "(i2400m %p bcf %p size %zu) = %d\n", - i2400m, bcf, bcf_size, ret); + i2400m, bcf, fw_size, ret); return ret; error_dev_rebooted: -- cgit v1.2.3 From 5eeae35b9a2e304fc4ae3d9eed63afeea23b482c Mon Sep 17 00:00:00 2001 From: Inaky Perez-Gonzalez Date: Wed, 7 Oct 2009 12:20:15 +0900 Subject: wimax/i2400m: when stopping the device, cancel any pending message The stop procedure for the device must make sure that any task that is waiting on a message is properly cancelled. This was being taken care of only by the __i2400m_dev_reset_handle() path and the rest was working by chance because the waits have a timeout. Fixed by adding a proper cancellation in __i2400m_dev_stop(). Signed-off-by: Inaky Perez-Gonzalez --- drivers/net/wimax/i2400m/driver.c | 2 ++ 1 file changed, 2 insertions(+) (limited to 'drivers/net/wimax') diff --git a/drivers/net/wimax/i2400m/driver.c b/drivers/net/wimax/i2400m/driver.c index 1f6aa2a55429..810eda7dbdba 100644 --- a/drivers/net/wimax/i2400m/driver.c +++ b/drivers/net/wimax/i2400m/driver.c @@ -537,6 +537,8 @@ void __i2400m_dev_stop(struct i2400m *i2400m) d_fnstart(3, dev, "(i2400m %p)\n", i2400m); wimax_state_change(wimax_dev, __WIMAX_ST_QUIESCING); + i2400m_msg_to_dev_cancel_wait(i2400m, -EL3RST); + complete(&i2400m->msg_completion); i2400m_net_wake_stop(i2400m); i2400m_dev_shutdown(i2400m); /* -- cgit v1.2.3 From b9ee95010bee6c0e17d18bc9d9c0cfab6e8cb73a Mon Sep 17 00:00:00 2001 From: Inaky Perez-Gonzalez Date: Wed, 7 Oct 2009 12:34:13 +0900 Subject: wimax/i2400m: fix deadlock: don't do BUS reset under i2400m->init_mutex Since the addition of the pre/post reset handlers, it became clear that we cannot do a I2400M-RT-BUS type reset while holding the init_mutex, as in the case of USB, it will deadlock when trying to call i2400m_pre_reset(). Thus, the following changes: - clarify the fact that calling bus_reset() w/ I2400M_RT_BUS while holding init_mutex is a no-no. - i2400m_dev_reset_handle() will do a BUS reset to recover a gone device after unlocking init_mutex. - in the USB reset implementation, when cold and warm reset fails, fallback to QUEUING a usb reset, not executing a USB reset, so it happens from another context and does not deadlock. Signed-off-by: Inaky Perez-Gonzalez --- drivers/net/wimax/i2400m/driver.c | 10 +++++++--- drivers/net/wimax/i2400m/i2400m.h | 3 +++ drivers/net/wimax/i2400m/usb.c | 14 ++++++++++---- 3 files changed, 20 insertions(+), 7 deletions(-) (limited to 'drivers/net/wimax') diff --git a/drivers/net/wimax/i2400m/driver.c b/drivers/net/wimax/i2400m/driver.c index 810eda7dbdba..10673af5a7f0 100644 --- a/drivers/net/wimax/i2400m/driver.c +++ b/drivers/net/wimax/i2400m/driver.c @@ -765,9 +765,7 @@ void __i2400m_dev_reset_handle(struct work_struct *ws) wmb(); /* see i2400m->updown's documentation */ dev_err(dev, "%s: cannot start the device: %d\n", reason, result); - result = i2400m->bus_reset(i2400m, I2400M_RT_BUS); - if (result >= 0) - result = -ENODEV; + result = -EUCLEAN; } out_unlock: if (i2400m->reset_ctx) { @@ -775,6 +773,12 @@ out_unlock: complete(&ctx->completion); } mutex_unlock(&i2400m->init_mutex); + if (result == -EUCLEAN) { + /* ops, need to clean up [w/ init_mutex not held] */ + result = i2400m->bus_reset(i2400m, I2400M_RT_BUS); + if (result >= 0) + result = -ENODEV; + } out: i2400m_put(i2400m); kfree(iw); diff --git a/drivers/net/wimax/i2400m/i2400m.h b/drivers/net/wimax/i2400m/i2400m.h index 8fc8a0ca5126..f5ed7d5cee47 100644 --- a/drivers/net/wimax/i2400m/i2400m.h +++ b/drivers/net/wimax/i2400m/i2400m.h @@ -281,6 +281,9 @@ struct i2400m_barker_db; * process, so it cannot rely on common infrastructure being laid * out. * + * IMPORTANT: don't call reset on RT_BUS with i2400m->init_mutex + * held, as the .pre/.post reset handlers will deadlock. + * * @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 diff --git a/drivers/net/wimax/i2400m/usb.c b/drivers/net/wimax/i2400m/usb.c index 8b246cc498b1..418db12b0cd7 100644 --- a/drivers/net/wimax/i2400m/usb.c +++ b/drivers/net/wimax/i2400m/usb.c @@ -254,7 +254,6 @@ int i2400mu_bus_reset(struct i2400m *i2400m, enum i2400m_reset_type rt) sizeof(i2400m_COLD_BOOT_BARKER), i2400mu->endpoint_cfg.reset_cold); else if (rt == I2400M_RT_BUS) { -do_bus_reset: result = usb_reset_device(i2400mu->usb_dev); switch (result) { case 0: @@ -262,7 +261,7 @@ do_bus_reset: case -ENODEV: case -ENOENT: case -ESHUTDOWN: - result = rt == I2400M_RT_WARM ? -ENODEV : 0; + result = 0; break; /* We assume the device is disconnected */ default: dev_err(dev, "USB reset failed (%d), giving up!\n", @@ -275,10 +274,17 @@ do_bus_reset: if (result < 0 && result != -EINVAL /* device is gone */ && rt != I2400M_RT_BUS) { + /* + * Things failed -- resort to lower level reset, that + * we queue in another context; the reason for this is + * that the pre and post reset functionality requires + * the i2400m->init_mutex; RT_WARM and RT_COLD can + * come from areas where i2400m->init_mutex is taken. + */ 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; + usb_queue_reset_device(i2400mu->usb_iface); + result = -ENODEV; } d_fnend(3, dev, "(i2400m %p rt %u) = %d\n", i2400m, rt, result); return result; -- cgit v1.2.3 From af77dfa7811cd4e533003a9e7e9bf27dece96c6d Mon Sep 17 00:00:00 2001 From: Inaky Perez-Gonzalez Date: Wed, 7 Oct 2009 21:37:53 +0900 Subject: wimax/i2400m: move i2400m_init() out of i2400m.h Upcoming changes will have to add things to this function that expose more internals, which would mean more forward declarators. Frankly, it doesn't need to be an inline, so moved to driver.c, where the declarations will be taken from the header file. Signed-off-by: Inaky Perez-Gonzalez --- drivers/net/wimax/i2400m/driver.c | 30 ++++++++++++++++++++++++++++++ drivers/net/wimax/i2400m/i2400m.h | 31 +------------------------------ 2 files changed, 31 insertions(+), 30 deletions(-) (limited to 'drivers/net/wimax') diff --git a/drivers/net/wimax/i2400m/driver.c b/drivers/net/wimax/i2400m/driver.c index 10673af5a7f0..9b78e059563d 100644 --- a/drivers/net/wimax/i2400m/driver.c +++ b/drivers/net/wimax/i2400m/driver.c @@ -848,6 +848,36 @@ void i2400m_bm_buf_free(struct i2400m *i2400m) } +/** + * i2400m_init - Initialize a 'struct i2400m' from all zeroes + * + * This is a bus-generic API call. + */ +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() */ +} +EXPORT_SYMBOL_GPL(i2400m_init); + + /** * i2400m_setup - bus-generic setup function for the i2400m device * diff --git a/drivers/net/wimax/i2400m/i2400m.h b/drivers/net/wimax/i2400m/i2400m.h index f5ed7d5cee47..4f8815d88874 100644 --- a/drivers/net/wimax/i2400m/i2400m.h +++ b/drivers/net/wimax/i2400m/i2400m.h @@ -575,36 +575,6 @@ struct i2400m { }; -/* - * 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 * ------------------------- @@ -737,6 +707,7 @@ unsigned i2400m_brh_get_signature(const struct i2400m_bootrom_header *hdr) /* * Driver / device setup and internal functions */ +extern void i2400m_init(struct i2400m *); 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 *); -- cgit v1.2.3 From a0beba21c3e2dff9a31739f1660ba3ff8c7150a7 Mon Sep 17 00:00:00 2001 From: Inaky Perez-Gonzalez Date: Wed, 7 Oct 2009 21:43:10 +0900 Subject: wimax/i2400m: queue device's report until the driver is ready for them The i2400m might start sending reports to the driver before it is done setting up all the infrastructure needed for handling them. Currently we were just dropping them when the driver wasn't ready and that is bad in certain situations, as the sync between the driver's idea of the device's state and the device's state dissapears. This changes that by implementing a queue for handling reports. Incoming reports are appended to it and a workstruct is woken to process the list of queued reports. When the device is not yet ready to handle them, the workstruct is not woken, but at soon as the device becomes ready again, the queue is processed. As a consequence of this, i2400m_queue_work() is no longer used, and thus removed. Signed-off-by: Inaky Perez-Gonzalez --- drivers/net/wimax/i2400m/driver.c | 74 ++------------------ drivers/net/wimax/i2400m/i2400m.h | 14 +++- drivers/net/wimax/i2400m/rx.c | 142 ++++++++++++++++++++++++++++++-------- 3 files changed, 128 insertions(+), 102 deletions(-) (limited to 'drivers/net/wimax') diff --git a/drivers/net/wimax/i2400m/driver.c b/drivers/net/wimax/i2400m/driver.c index 9b78e059563d..42102ebad1ad 100644 --- a/drivers/net/wimax/i2400m/driver.c +++ b/drivers/net/wimax/i2400m/driver.c @@ -128,76 +128,6 @@ struct i2400m_work *__i2400m_work_setup( } -/** - * 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 = __i2400m_work_setup(i2400m, fn, gfp_flags, pl, pl_size); - if (iw != NULL) { - result = queue_work(i2400m->work_queue, &iw->ws); - if (WARN_ON(result == 0)) - result = -ENXIO; - } - return result; -} -EXPORT_SYMBOL_GPL(i2400m_queue_work); - - /* * Schedule i2400m's specific work on the system's queue. * @@ -459,6 +389,8 @@ retry: goto error_bus_dev_start; i2400m->ready = 1; wmb(); /* see i2400m->ready's documentation */ + /* process pending reports from the device */ + queue_work(i2400m->work_queue, &i2400m->rx_report_ws); result = i2400m_firmware_check(i2400m); /* fw versions ok? */ if (result < 0) goto error_fw_check; @@ -868,6 +800,8 @@ void i2400m_init(struct i2400m *i2400m) spin_lock_init(&i2400m->rx_lock); i2400m->rx_pl_min = UINT_MAX; i2400m->rx_size_min = UINT_MAX; + INIT_LIST_HEAD(&i2400m->rx_reports); + INIT_WORK(&i2400m->rx_report_ws, i2400m_report_hook_work); mutex_init(&i2400m->msg_mutex); init_completion(&i2400m->msg_completion); diff --git a/drivers/net/wimax/i2400m/i2400m.h b/drivers/net/wimax/i2400m/i2400m.h index 4f8815d88874..55bca430c69b 100644 --- a/drivers/net/wimax/i2400m/i2400m.h +++ b/drivers/net/wimax/i2400m/i2400m.h @@ -421,6 +421,13 @@ struct i2400m_barker_db; * delivered. Then the driver can release them to the host. See * drivers/net/i2400m/rx.c for details. * + * @rx_reports: reports received from the device that couldn't be + * processed because the driver wasn't still ready; when ready, + * they are pulled from here and chewed. + * + * @rx_reports_ws: Work struct used to kick a scan of the RX reports + * list and to process each. + * * @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(). @@ -548,6 +555,8 @@ struct i2400m { 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 list_head rx_reports; /* under rx_lock! */ + struct work_struct rx_report_ws; struct mutex msg_mutex; /* serialize command execution */ struct completion msg_completion; @@ -830,9 +839,7 @@ struct i2400m_work { size_t pl_size; u8 pl[0]; }; -extern int i2400m_queue_work(struct i2400m *, - void (*)(struct work_struct *), gfp_t, - const void *, size_t); + extern int i2400m_schedule_work(struct i2400m *, void (*)(struct work_struct *), gfp_t, const void *, size_t); @@ -847,6 +854,7 @@ 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 void i2400m_report_hook_work(struct work_struct *); extern int i2400m_cmd_enter_powersave(struct i2400m *); extern int i2400m_cmd_get_state(struct i2400m *); extern int i2400m_cmd_exit_idle(struct i2400m *); diff --git a/drivers/net/wimax/i2400m/rx.c b/drivers/net/wimax/i2400m/rx.c index 82c200ad9fdc..64a44ca00675 100644 --- a/drivers/net/wimax/i2400m/rx.c +++ b/drivers/net/wimax/i2400m/rx.c @@ -158,29 +158,104 @@ struct i2400m_report_hook_args { struct sk_buff *skb_rx; const struct i2400m_l3l4_hdr *l3l4_hdr; size_t size; + struct list_head list_node; }; /* * Execute i2400m_report_hook in a workqueue * - * Unpacks arguments from the deferred call, executes it and then - * drops the references. + * Goes over the list of queued reports in i2400m->rx_reports and + * processes them. * - * Obvious NOTE: References are needed because we are a separate - * thread; otherwise the buffer changes under us because it is - * released by the original caller. + * NOTE: refcounts on i2400m are not needed because we flush the + * workqueue this runs on (i2400m->work_queue) before destroying + * i2400m. */ -static 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); - kfree_skb(args->skb_rx); - i2400m_put(iw->i2400m); - kfree(iw); + struct i2400m *i2400m = container_of(ws, struct i2400m, rx_report_ws); + struct device *dev = i2400m_dev(i2400m); + struct i2400m_report_hook_args *args, *args_next; + LIST_HEAD(list); + unsigned long flags; + + while (1) { + spin_lock_irqsave(&i2400m->rx_lock, flags); + list_splice_init(&i2400m->rx_reports, &list); + spin_unlock_irqrestore(&i2400m->rx_lock, flags); + if (list_empty(&list)) + break; + else + d_printf(1, dev, "processing queued reports\n"); + list_for_each_entry_safe(args, args_next, &list, list_node) { + d_printf(2, dev, "processing queued report %p\n", args); + i2400m_report_hook(i2400m, args->l3l4_hdr, args->size); + kfree_skb(args->skb_rx); + list_del(&args->list_node); + kfree(args); + } + } +} + + +/* + * Flush the list of queued reports + */ +static +void i2400m_report_hook_flush(struct i2400m *i2400m) +{ + struct device *dev = i2400m_dev(i2400m); + struct i2400m_report_hook_args *args, *args_next; + LIST_HEAD(list); + unsigned long flags; + + d_printf(1, dev, "flushing queued reports\n"); + spin_lock_irqsave(&i2400m->rx_lock, flags); + list_splice_init(&i2400m->rx_reports, &list); + spin_unlock_irqrestore(&i2400m->rx_lock, flags); + list_for_each_entry_safe(args, args_next, &list, list_node) { + d_printf(2, dev, "flushing queued report %p\n", args); + kfree_skb(args->skb_rx); + list_del(&args->list_node); + kfree(args); + } +} + + +/* + * Queue a report for later processing + * + * @i2400m: device descriptor + * @skb_rx: skb that contains the payload (for reference counting) + * @l3l4_hdr: pointer to the control + * @size: size of the message + */ +static +void i2400m_report_hook_queue(struct i2400m *i2400m, struct sk_buff *skb_rx, + const void *l3l4_hdr, size_t size) +{ + struct device *dev = i2400m_dev(i2400m); + unsigned long flags; + struct i2400m_report_hook_args *args; + + args = kzalloc(sizeof(*args), GFP_NOIO); + if (args) { + args->skb_rx = skb_get(skb_rx); + args->l3l4_hdr = l3l4_hdr; + args->size = size; + spin_lock_irqsave(&i2400m->rx_lock, flags); + list_add_tail(&args->list_node, &i2400m->rx_reports); + spin_unlock_irqrestore(&i2400m->rx_lock, flags); + d_printf(2, dev, "queued report %p\n", args); + rmb(); /* see i2400m->ready's documentation */ + if (likely(i2400m->ready)) /* only send if up */ + queue_work(i2400m->work_queue, &i2400m->rx_report_ws); + } else { + if (printk_ratelimit()) + dev_err(dev, "%s:%u: Can't allocate %zu B\n", + __func__, __LINE__, sizeof(*args)); + } } @@ -294,22 +369,29 @@ void i2400m_rx_ctl(struct i2400m *i2400m, struct sk_buff *skb_rx, msg_type, size); d_dump(2, dev, l3l4_hdr, size); if (msg_type & I2400M_MT_REPORT_MASK) { - /* These hooks have to be ran serialized; as well, the - * handling might force the execution of commands, and - * that might cause reentrancy issues with - * bus-specific subdrivers and workqueues. So we run - * it in a separate workqueue. */ - struct i2400m_report_hook_args args = { - .skb_rx = skb_rx, - .l3l4_hdr = l3l4_hdr, - .size = size - }; - rmb(); /* see i2400m->ready's documentation */ - if (likely(i2400m->ready)) { /* only send if up */ - skb_get(skb_rx); - i2400m_queue_work(i2400m, i2400m_report_hook_work, - GFP_KERNEL, &args, sizeof(args)); - } + /* + * Process each report + * + * - has to be ran serialized as well + * + * - the handling might force the execution of + * commands. That might cause reentrancy issues with + * bus-specific subdrivers and workqueues, so the we + * run it in a separate workqueue. + * + * - when the driver is not yet ready to handle them, + * they are queued and at some point the queue is + * restarted [NOTE: we can't queue SKBs directly, as + * this might be a piece of a SKB, not the whole + * thing, and this is cheaper than cloning the + * SKB]. + * + * Note we don't do refcounting for the device + * structure; this is because before destroying + * 'i2400m', we make sure to flush the + * i2400m->work_queue, so there are no issues. + */ + i2400m_report_hook_queue(i2400m, skb_rx, l3l4_hdr, size); if (unlikely(i2400m->trace_msg_from_user)) wimax_msg(&i2400m->wimax_dev, "echo", l3l4_hdr, size, GFP_KERNEL); @@ -1281,4 +1363,6 @@ void i2400m_rx_release(struct i2400m *i2400m) kfree(i2400m->rx_roq[0].log); kfree(i2400m->rx_roq); } + /* at this point, nothing can be received... */ + i2400m_report_hook_flush(i2400m); } -- cgit v1.2.3 From 46c501473d0a6fa62435dfd65c6884e1fd63b327 Mon Sep 17 00:00:00 2001 From: Inaky Perez-Gonzalez Date: Wed, 7 Oct 2009 22:46:29 +0900 Subject: wimax/i2400m: fix oops in TX when tearing down the device All the entry points into the TX module should check if the device has been torn down. Otherwise, when the device resets or shuts down, there are windows when a call to i2400m_tx*() will oops the system. For that, make i2400m_tx_release() set i2400m->tx_buf to NULL under the tx_lock. Then, any entry point [i2400m_tx(), _tx_msg_sent(), _tx_msg_get()] will check for i2400m->tx_buf to be NULL and exit gracefully. Signed-off-by: Inaky Perez-Gonzalez --- drivers/net/wimax/i2400m/sdio-tx.c | 5 ++++- drivers/net/wimax/i2400m/tx.c | 18 +++++++++++++++++- 2 files changed, 21 insertions(+), 2 deletions(-) (limited to 'drivers/net/wimax') diff --git a/drivers/net/wimax/i2400m/sdio-tx.c b/drivers/net/wimax/i2400m/sdio-tx.c index 5105a5ebc44f..de66d068c9cb 100644 --- a/drivers/net/wimax/i2400m/sdio-tx.c +++ b/drivers/net/wimax/i2400m/sdio-tx.c @@ -149,5 +149,8 @@ int i2400ms_tx_setup(struct i2400ms *i2400ms) void i2400ms_tx_release(struct i2400ms *i2400ms) { - destroy_workqueue(i2400ms->tx_workqueue); + if (i2400ms->tx_workqueue) { + destroy_workqueue(i2400ms->tx_workqueue); + i2400ms->tx_workqueue = NULL; + } } diff --git a/drivers/net/wimax/i2400m/tx.c b/drivers/net/wimax/i2400m/tx.c index 8c2080248aec..54480e8947f1 100644 --- a/drivers/net/wimax/i2400m/tx.c +++ b/drivers/net/wimax/i2400m/tx.c @@ -642,6 +642,9 @@ int i2400m_tx(struct i2400m *i2400m, const void *buf, size_t buf_len, * 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); + result = -ESHUTDOWN; + if (i2400m->tx_buf == NULL) + goto error_tx_new; try_new: if (unlikely(i2400m->tx_msg == NULL)) i2400m_tx_new(i2400m); @@ -697,7 +700,10 @@ try_new: } error_tx_new: spin_unlock_irqrestore(&i2400m->tx_lock, flags); - i2400m->bus_tx_kick(i2400m); /* always kick, might free up space */ + /* kick in most cases, except when the TX subsys is down, as + * it might free space */ + if (likely(result != -ESHUTDOWN)) + i2400m->bus_tx_kick(i2400m); d_fnend(3, dev, "(i2400m %p skb %p [%zu bytes] pt %u) = %d\n", i2400m, buf, buf_len, pl_type, result); return result; @@ -740,6 +746,9 @@ struct i2400m_msg_hdr *i2400m_tx_msg_get(struct i2400m *i2400m, d_fnstart(3, dev, "(i2400m %p bus_size %p)\n", i2400m, bus_size); spin_lock_irqsave(&i2400m->tx_lock, flags); + tx_msg_moved = NULL; + if (i2400m->tx_buf == NULL) + goto out_unlock; skip: tx_msg_moved = NULL; if (i2400m->tx_in == i2400m->tx_out) { /* Empty FIFO? */ @@ -829,6 +838,8 @@ void i2400m_tx_msg_sent(struct i2400m *i2400m) d_fnstart(3, dev, "(i2400m %p)\n", i2400m); spin_lock_irqsave(&i2400m->tx_lock, flags); + if (i2400m->tx_buf == NULL) + goto out_unlock; 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; @@ -837,6 +848,7 @@ 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; +out_unlock: spin_unlock_irqrestore(&i2400m->tx_lock, flags); d_fnend(3, dev, "(i2400m %p) = void\n", i2400m); } @@ -876,5 +888,9 @@ int i2400m_tx_setup(struct i2400m *i2400m) */ void i2400m_tx_release(struct i2400m *i2400m) { + unsigned long flags; + spin_lock_irqsave(&i2400m->tx_lock, flags); kfree(i2400m->tx_buf); + i2400m->tx_buf = NULL; + spin_unlock_irqrestore(&i2400m->tx_lock, flags); } -- cgit v1.2.3 From 0c96859d7a5f0286ed70d3c4e140ac6816a350da Mon Sep 17 00:00:00 2001 From: Inaky Perez-Gonzalez Date: Wed, 7 Oct 2009 22:57:39 +0900 Subject: wimax/i2400m: Let device's status reports change the device state Currently __i2400m_dev_start was forcing, after uploading firmware and doing a few checks to WIMAX_ST_UNINITIALIZED. This can be overriding state changes that the device might have caused by sending reports; thus it makes more sense to remove it and let the device update the status on its own by sending reports. Signed-off-by: Inaky Perez-Gonzalez --- drivers/net/wimax/i2400m/driver.c | 1 - 1 file changed, 1 deletion(-) (limited to 'drivers/net/wimax') diff --git a/drivers/net/wimax/i2400m/driver.c b/drivers/net/wimax/i2400m/driver.c index 42102ebad1ad..cc900b99e60a 100644 --- a/drivers/net/wimax/i2400m/driver.c +++ b/drivers/net/wimax/i2400m/driver.c @@ -398,7 +398,6 @@ retry: result = i2400m_check_mac_addr(i2400m); if (result < 0) goto error_check_mac_addr; - wimax_state_change(wimax_dev, WIMAX_ST_UNINITIALIZED); result = i2400m_dev_initialize(i2400m); if (result < 0) goto error_dev_initialize; -- cgit v1.2.3 From 4a78fd9a736db4c871bc8b583d66b61c38abd299 Mon Sep 17 00:00:00 2001 From: Inaky Perez-Gonzalez Date: Thu, 8 Oct 2009 08:11:38 +0900 Subject: wimax/i2400m: fix oops caused by race condition when exiting USB kthreads Current i2400m USB code had to threads (one for processing RX, one for TX). When calling i2400m_{tx,rx}_release(), it would crash if the thread had exited already due to an error. So changed the code to have the thread fill in/out i2400mu->{tx,rx}_kthread under a spinlock; then the _release() function will call kthread_stop() only if {rx,tx}_kthread is still set. Signed-off-by: Inaky Perez-Gonzalez --- drivers/net/wimax/i2400m/usb-rx.c | 35 +++++++++++++++++++++++++++++------ drivers/net/wimax/i2400m/usb-tx.c | 35 ++++++++++++++++++++++++++++++----- 2 files changed, 59 insertions(+), 11 deletions(-) (limited to 'drivers/net/wimax') diff --git a/drivers/net/wimax/i2400m/usb-rx.c b/drivers/net/wimax/i2400m/usb-rx.c index e494e3774ec0..245587feb8c8 100644 --- a/drivers/net/wimax/i2400m/usb-rx.c +++ b/drivers/net/wimax/i2400m/usb-rx.c @@ -316,10 +316,15 @@ int i2400mu_rxd(void *_i2400mu) size_t pending; int rx_size; struct sk_buff *rx_skb; + unsigned long flags; d_fnstart(4, dev, "(i2400mu %p)\n", i2400mu); + spin_lock_irqsave(&i2400m->rx_lock, flags); + BUG_ON(i2400mu->rx_kthread != NULL); + i2400mu->rx_kthread = current; + spin_unlock_irqrestore(&i2400m->rx_lock, flags); while (1) { - d_printf(2, dev, "TX: waiting for messages\n"); + d_printf(2, dev, "RX: waiting for messages\n"); pending = 0; wait_event_interruptible( i2400mu->rx_wq, @@ -367,6 +372,9 @@ int i2400mu_rxd(void *_i2400mu) } result = 0; out: + spin_lock_irqsave(&i2400m->rx_lock, flags); + i2400mu->rx_kthread = NULL; + spin_unlock_irqrestore(&i2400m->rx_lock, flags); d_fnend(4, dev, "(i2400mu %p) = %d\n", i2400mu, result); return result; @@ -403,18 +411,33 @@ int i2400mu_rx_setup(struct i2400mu *i2400mu) struct i2400m *i2400m = &i2400mu->i2400m; struct device *dev = &i2400mu->usb_iface->dev; struct wimax_dev *wimax_dev = &i2400m->wimax_dev; + struct task_struct *kthread; - 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); + kthread = kthread_run(i2400mu_rxd, i2400mu, "%s-rx", + wimax_dev->name); + /* the kthread function sets i2400mu->rx_thread */ + if (IS_ERR(kthread)) { + result = PTR_ERR(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); + unsigned long flags; + struct i2400m *i2400m = &i2400mu->i2400m; + struct device *dev = i2400m_dev(i2400m); + struct task_struct *kthread; + + spin_lock_irqsave(&i2400m->rx_lock, flags); + kthread = i2400mu->rx_kthread; + i2400mu->rx_kthread = NULL; + spin_unlock_irqrestore(&i2400m->rx_lock, flags); + if (kthread) + kthread_stop(kthread); + else + d_printf(1, dev, "RX: kthread had already exited\n"); } diff --git a/drivers/net/wimax/i2400m/usb-tx.c b/drivers/net/wimax/i2400m/usb-tx.c index 90dfff1afb8a..a3c46e99c804 100644 --- a/drivers/net/wimax/i2400m/usb-tx.c +++ b/drivers/net/wimax/i2400m/usb-tx.c @@ -161,9 +161,15 @@ int i2400mu_txd(void *_i2400mu) struct device *dev = &i2400mu->usb_iface->dev; struct i2400m_msg_hdr *tx_msg; size_t tx_msg_size; + unsigned long flags; d_fnstart(4, dev, "(i2400mu %p)\n", i2400mu); + spin_lock_irqsave(&i2400m->tx_lock, flags); + BUG_ON(i2400mu->tx_kthread != NULL); + i2400mu->tx_kthread = current; + spin_unlock_irqrestore(&i2400m->tx_lock, flags); + while (1) { d_printf(2, dev, "TX: waiting for messages\n"); tx_msg = NULL; @@ -183,6 +189,11 @@ int i2400mu_txd(void *_i2400mu) if (result < 0) break; } + + spin_lock_irqsave(&i2400m->tx_lock, flags); + i2400mu->tx_kthread = NULL; + spin_unlock_irqrestore(&i2400m->tx_lock, flags); + d_fnend(4, dev, "(i2400mu %p) = %d\n", i2400mu, result); return result; } @@ -213,11 +224,13 @@ int i2400mu_tx_setup(struct i2400mu *i2400mu) struct i2400m *i2400m = &i2400mu->i2400m; struct device *dev = &i2400mu->usb_iface->dev; struct wimax_dev *wimax_dev = &i2400m->wimax_dev; + struct task_struct *kthread; - 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); + kthread = kthread_run(i2400mu_txd, i2400mu, "%s-tx", + wimax_dev->name); + /* the kthread function sets i2400mu->tx_thread */ + if (IS_ERR(kthread)) { + result = PTR_ERR(kthread); dev_err(dev, "TX: cannot start thread: %d\n", result); } return result; @@ -225,5 +238,17 @@ int i2400mu_tx_setup(struct i2400mu *i2400mu) void i2400mu_tx_release(struct i2400mu *i2400mu) { - kthread_stop(i2400mu->tx_kthread); + unsigned long flags; + struct i2400m *i2400m = &i2400mu->i2400m; + struct device *dev = i2400m_dev(i2400m); + struct task_struct *kthread; + + spin_lock_irqsave(&i2400m->tx_lock, flags); + kthread = i2400mu->tx_kthread; + i2400mu->tx_kthread = NULL; + spin_unlock_irqrestore(&i2400m->tx_lock, flags); + if (kthread) + kthread_stop(kthread); + else + d_printf(1, dev, "TX: kthread had already exited\n"); } -- cgit v1.2.3 From 097acbeff98178e01c2f6adb2259ab4d811340cc Mon Sep 17 00:00:00 2001 From: Inaky Perez-Gonzalez Date: Thu, 8 Oct 2009 12:33:50 +0900 Subject: wimax/i2400m: make i2400m->bus_dev_{stop,start}() optional In coming commits, the i2400m SDIO driver will not use i2400m->bus_dev_stop(). Thus changed to check before calling, as an empty stub has more overhead than a call to check if the function pointer is non-NULL. Signed-off-by: Inaky Perez-Gonzalez --- drivers/net/wimax/i2400m/driver.c | 14 +++++++++----- drivers/net/wimax/i2400m/i2400m.h | 14 +++++++------- 2 files changed, 16 insertions(+), 12 deletions(-) (limited to 'drivers/net/wimax') diff --git a/drivers/net/wimax/i2400m/driver.c b/drivers/net/wimax/i2400m/driver.c index cc900b99e60a..cc58a864bd06 100644 --- a/drivers/net/wimax/i2400m/driver.c +++ b/drivers/net/wimax/i2400m/driver.c @@ -384,9 +384,11 @@ retry: 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; + if (i2400m->bus_dev_start) { + result = i2400m->bus_dev_start(i2400m); + if (result < 0) + goto error_bus_dev_start; + } i2400m->ready = 1; wmb(); /* see i2400m->ready's documentation */ /* process pending reports from the device */ @@ -413,7 +415,8 @@ error_check_mac_addr: wmb(); /* see i2400m->ready's documentation */ flush_workqueue(i2400m->work_queue); error_fw_check: - i2400m->bus_dev_stop(i2400m); + if (i2400m->bus_dev_stop) + i2400m->bus_dev_stop(i2400m); error_bus_dev_start: destroy_workqueue(i2400m->work_queue); error_create_workqueue: @@ -480,7 +483,8 @@ void __i2400m_dev_stop(struct i2400m *i2400m) wmb(); /* see i2400m->ready's documentation */ flush_workqueue(i2400m->work_queue); - i2400m->bus_dev_stop(i2400m); + if (i2400m->bus_dev_stop) + i2400m->bus_dev_stop(i2400m); destroy_workqueue(i2400m->work_queue); i2400m_rx_release(i2400m); i2400m_tx_release(i2400m); diff --git a/drivers/net/wimax/i2400m/i2400m.h b/drivers/net/wimax/i2400m/i2400m.h index 55bca430c69b..5eee985f2926 100644 --- a/drivers/net/wimax/i2400m/i2400m.h +++ b/drivers/net/wimax/i2400m/i2400m.h @@ -245,19 +245,19 @@ struct i2400m_barker_db; * all the host resources created to handle communication with * the device. * - * @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. + * @bus_dev_start: [optional fill] Function called by the bus-generic + * code [i2400m_dev_start()] to do things needed to start 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. + * @bus_dev_stop: [optional fill] Function called by the bus-generic + * code [i2400m_dev_stop()] to do things needed for stopping 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 + * all the host resources created to handle communication with * the device. * * @bus_tx_kick: [fill] Function called by the bus-generic code to let -- cgit v1.2.3 From a8ee303cae6fbdaa639afa50b9d03ce6f0c7d7da Mon Sep 17 00:00:00 2001 From: Inaky Perez-Gonzalez Date: Thu, 8 Oct 2009 12:36:03 +0900 Subject: wimax/i2400m: SDIO: fix oops on reset when TXing on uninitialized data Currently the SDIO part of the TX resources were initialized/released with bus_dev_{start,stop}. The generic code's TX subsystem is destroyed afterwards, so there is a window from the bus-TX destruction to the generic-TX destruction where the generic-TX code might call into bus-TX to do transactions. The SDIO code cannot really cope with this (whereas in USB, how it is laid out, it correctly ignores it). In any case, it made no sense for the SDIO TX code to be in i2400m->bus_dev_start/stop(), so moved to i2400m->bus_setup/release(), which also takes care of the oops. Signed-off-by: Inaky Perez-Gonzalez --- drivers/net/wimax/i2400m/sdio.c | 43 +++++++++-------------------------------- 1 file changed, 9 insertions(+), 34 deletions(-) (limited to 'drivers/net/wimax') diff --git a/drivers/net/wimax/i2400m/sdio.c b/drivers/net/wimax/i2400m/sdio.c index 14e66f06f235..ec178928914e 100644 --- a/drivers/net/wimax/i2400m/sdio.c +++ b/drivers/net/wimax/i2400m/sdio.c @@ -46,15 +46,6 @@ * 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 @@ -191,12 +182,17 @@ int i2400ms_bus_setup(struct i2400m *i2400m) goto error_func_enable; } + result = i2400ms_tx_setup(i2400ms); + if (result < 0) + goto error_tx_setup; result = i2400ms_rx_setup(i2400ms); if (result < 0) goto error_rx_setup; return 0; error_rx_setup: + i2400ms_tx_release(i2400ms); +error_tx_setup: sdio_claim_host(func); sdio_disable_func(func); sdio_release_host(func); @@ -218,6 +214,7 @@ void i2400ms_bus_release(struct i2400m *i2400m) struct sdio_func *func = i2400ms->func; i2400ms_rx_release(i2400ms); + i2400ms_tx_release(i2400ms); sdio_claim_host(func); sdio_disable_func(func); sdio_release_host(func); @@ -235,36 +232,14 @@ void i2400ms_bus_release(struct i2400m *i2400m) 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); + d_fnend(3, dev, "(i2400m %p) = %d\n", i2400m, 0); + return 0; } @@ -506,7 +481,7 @@ int i2400ms_probe(struct sdio_func *func, i2400m->bus_pl_size_max = I2400MS_PL_SIZE_MAX; i2400m->bus_setup = i2400ms_bus_setup; i2400m->bus_dev_start = i2400ms_bus_dev_start; - i2400m->bus_dev_stop = i2400ms_bus_dev_stop; + i2400m->bus_dev_stop = NULL; i2400m->bus_release = i2400ms_bus_release; i2400m->bus_tx_kick = i2400ms_bus_tx_kick; i2400m->bus_reset = i2400ms_bus_reset; -- cgit v1.2.3 From ce5eb7a29251a66e613a300532b642ddc23b48d8 Mon Sep 17 00:00:00 2001 From: Tomas Winkler Date: Sat, 17 Oct 2009 09:09:36 +0000 Subject: i2400m-sdio: select IWMC3200TOP in Kconfig i2400m-sdio requires iwmc3200top for its operation Signed-off-by: Tomas Winkler Acked-by: Inaky Perez-Gonzalez Signed-off-by: David S. Miller --- drivers/net/wimax/i2400m/Kconfig | 8 ++++++++ 1 file changed, 8 insertions(+) (limited to 'drivers/net/wimax') diff --git a/drivers/net/wimax/i2400m/Kconfig b/drivers/net/wimax/i2400m/Kconfig index d623b3d99a4b..3f703384295e 100644 --- a/drivers/net/wimax/i2400m/Kconfig +++ b/drivers/net/wimax/i2400m/Kconfig @@ -31,6 +31,14 @@ config WIMAX_I2400M_SDIO If unsure, it is safe to select M (module). +config WIMAX_IWMC3200_SDIO + bool "Intel Wireless Multicom WiMAX Connection 3200 over SDIO" + depends on WIMAX_I2400M_SDIO + select IWMC3200TOP + help + Select if you have a device based on the Intel Multicom WiMAX + Connection 3200 over SDIO. + config WIMAX_I2400M_DEBUG_LEVEL int "WiMAX i2400m debug level" depends on WIMAX_I2400M -- cgit v1.2.3 From 55a662d6468005ec3cd799fbd8d0ad03dfae6d2a Mon Sep 17 00:00:00 2001 From: Inaky Perez-Gonzalez Date: Mon, 19 Oct 2009 23:11:26 +0900 Subject: wimax/i2400m: implement passive mode as a module option Some versions of the user space Intel WiMAX daemon need to have full control over the device initialization sequence. By setting the module option i2400.passive_mode to 1, the driver defers all device configuration and initialization to user space. Signed-off-by: Inaky Perez-Gonzalez --- drivers/net/wimax/i2400m/control.c | 10 ++++++++++ 1 file changed, 10 insertions(+) (limited to 'drivers/net/wimax') diff --git a/drivers/net/wimax/i2400m/control.c b/drivers/net/wimax/i2400m/control.c index 8fe70e75d030..b69fd8834c78 100644 --- a/drivers/net/wimax/i2400m/control.c +++ b/drivers/net/wimax/i2400m/control.c @@ -82,6 +82,13 @@ #define D_SUBMODULE control #include "debug-levels.h" +int i2400m_passive_mode; /* 0 (passive mode disabled) by default */ +module_param_named(passive_mode, i2400m_passive_mode, int, 0644); +MODULE_PARM_DESC(passive_mode, + "If true, the driver will not do any device setup " + "and leave it up to user space, who must be properly " + "setup."); + /* * Return if a TLV is of a give type and size @@ -1335,6 +1342,8 @@ int i2400m_dev_initialize(struct i2400m *i2400m) unsigned argc = 0; d_fnstart(3, dev, "(i2400m %p)\n", i2400m); + if (i2400m_passive_mode) + goto out_passive; /* Disable idle mode? (enabled by default) */ if (i2400m_idle_mode_disabled) { if (i2400m_le_v1_3(i2400m)) { @@ -1377,6 +1386,7 @@ int i2400m_dev_initialize(struct i2400m *i2400m) result = i2400m_set_init_config(i2400m, args, argc); if (result < 0) goto error; +out_passive: /* * Update state: Here it just calls a get state; parsing the * result (System State TLV and RF Status TLV [done in the rx -- cgit v1.2.3 From c931ceeb780560ff652a8f9875f88778439ee87e Mon Sep 17 00:00:00 2001 From: Inaky Perez-Gonzalez Date: Mon, 19 Oct 2009 16:24:56 +0900 Subject: wimax/i2400m: introduce i2400m_reset(), stopping TX and carrier Currently the i2400m driver was resetting by just calling i2400m->bus_reset(). However, this was missing stopping the TX queue and downing the carrier. This was causing, for the corner case of the driver reseting a device that refuses to go out of idle mode, that a few packets would be queued and more than one reset would go through, making the recovery a wee bit messy. To avoid introducing the same cleanup in all the bus-specific driver, introduced a i2400m_reset() function that takes care of house cleaning and then calling the bus-level reset implementation. The bulk of the changes in all files are just to rename the call from i2400m->bus_reset() to i2400m_reset(). Signed-off-by: Inaky Perez-Gonzalez --- drivers/net/wimax/i2400m/control.c | 4 ++-- drivers/net/wimax/i2400m/debugfs.c | 2 +- drivers/net/wimax/i2400m/driver.c | 22 ++++++++++++++++++++-- drivers/net/wimax/i2400m/fw.c | 4 ++-- drivers/net/wimax/i2400m/i2400m.h | 1 + drivers/net/wimax/i2400m/netdev.c | 8 +++++--- drivers/net/wimax/i2400m/rx.c | 4 ++-- drivers/net/wimax/i2400m/sdio.c | 9 +-------- drivers/net/wimax/i2400m/usb.c | 2 +- 9 files changed, 35 insertions(+), 21 deletions(-) (limited to 'drivers/net/wimax') diff --git a/drivers/net/wimax/i2400m/control.c b/drivers/net/wimax/i2400m/control.c index b69fd8834c78..944945540391 100644 --- a/drivers/net/wimax/i2400m/control.c +++ b/drivers/net/wimax/i2400m/control.c @@ -54,7 +54,7 @@ * i2400m_set_init_config() * i2400m_cmd_get_state() * i2400m_dev_shutdown() Called by i2400m_dev_stop() - * i2400m->bus_reset() + * i2400m_reset() * * i2400m_{cmd,get,set}_*() * i2400m_msg_to_dev() @@ -343,7 +343,7 @@ void i2400m_report_tlv_system_state(struct i2400m *i2400m, /* Huh? just in case, shut it down */ dev_err(dev, "HW BUG? unknown state %u: shutting down\n", i2400m_state); - i2400m->bus_reset(i2400m, I2400M_RT_WARM); + i2400m_reset(i2400m, I2400M_RT_WARM); break; }; d_fnend(3, dev, "(i2400m %p ss %p [%u]) = void\n", diff --git a/drivers/net/wimax/i2400m/debugfs.c b/drivers/net/wimax/i2400m/debugfs.c index 9b81af3f80a9..b1aec3e1892f 100644 --- a/drivers/net/wimax/i2400m/debugfs.c +++ b/drivers/net/wimax/i2400m/debugfs.c @@ -214,7 +214,7 @@ int debugfs_i2400m_reset_set(void *data, u64 val) case I2400M_RT_WARM: case I2400M_RT_COLD: case I2400M_RT_BUS: - result = i2400m->bus_reset(i2400m, rt); + result = i2400m_reset(i2400m, rt); if (result >= 0) result = 0; default: diff --git a/drivers/net/wimax/i2400m/driver.c b/drivers/net/wimax/i2400m/driver.c index cc58a864bd06..96a615fe09de 100644 --- a/drivers/net/wimax/i2400m/driver.c +++ b/drivers/net/wimax/i2400m/driver.c @@ -255,7 +255,7 @@ int i2400m_op_reset(struct wimax_dev *wimax_dev) mutex_lock(&i2400m->init_mutex); i2400m->reset_ctx = &ctx; mutex_unlock(&i2400m->init_mutex); - result = i2400m->bus_reset(i2400m, I2400M_RT_WARM); + result = i2400m_reset(i2400m, I2400M_RT_WARM); if (result < 0) goto out; result = wait_for_completion_timeout(&ctx.completion, 4*HZ); @@ -710,7 +710,7 @@ out_unlock: mutex_unlock(&i2400m->init_mutex); if (result == -EUCLEAN) { /* ops, need to clean up [w/ init_mutex not held] */ - result = i2400m->bus_reset(i2400m, I2400M_RT_BUS); + result = i2400m_reset(i2400m, I2400M_RT_BUS); if (result >= 0) result = -ENODEV; } @@ -815,6 +815,24 @@ void i2400m_init(struct i2400m *i2400m) EXPORT_SYMBOL_GPL(i2400m_init); +int i2400m_reset(struct i2400m *i2400m, enum i2400m_reset_type rt) +{ + struct net_device *net_dev = i2400m->wimax_dev.net_dev; + + /* + * Make sure we stop TXs and down the carrier before + * resetting; this is needed to avoid things like + * i2400m_wake_tx() scheduling stuff in parallel. + */ + if (net_dev->reg_state == NETREG_REGISTERED) { + netif_tx_disable(net_dev); + netif_carrier_off(net_dev); + } + return i2400m->bus_reset(i2400m, rt); +} +EXPORT_SYMBOL_GPL(i2400m_reset); + + /** * i2400m_setup - bus-generic setup function for the i2400m device * diff --git a/drivers/net/wimax/i2400m/fw.c b/drivers/net/wimax/i2400m/fw.c index fda54bfdf70e..64cdfeb299ca 100644 --- a/drivers/net/wimax/i2400m/fw.c +++ b/drivers/net/wimax/i2400m/fw.c @@ -130,7 +130,7 @@ * i2400m_fw_dnload * i2400m_bootrom_init * i2400m_bm_cmd - * i2400m->bus_reset + * i2400m_reset * i2400m_dnload_init * i2400m_dnload_init_signed * i2400m_dnload_init_nonsigned @@ -902,7 +902,7 @@ do_reboot: d_printf(4, dev, "device reboot: reboot command [%d # left]\n", count); if ((flags & I2400M_BRI_NO_REBOOT) == 0) - i2400m->bus_reset(i2400m, I2400M_RT_WARM); + i2400m_reset(i2400m, I2400M_RT_WARM); result = i2400m_bm_cmd(i2400m, NULL, 0, &ack, sizeof(ack), I2400M_BM_CMD_RAW); flags &= ~I2400M_BRI_NO_REBOOT; diff --git a/drivers/net/wimax/i2400m/i2400m.h b/drivers/net/wimax/i2400m/i2400m.h index 5eee985f2926..04df9bbe340f 100644 --- a/drivers/net/wimax/i2400m/i2400m.h +++ b/drivers/net/wimax/i2400m/i2400m.h @@ -717,6 +717,7 @@ unsigned i2400m_brh_get_signature(const struct i2400m_bootrom_header *hdr) * Driver / device setup and internal functions */ extern void i2400m_init(struct i2400m *); +extern int i2400m_reset(struct i2400m *, enum i2400m_reset_type); 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 *); diff --git a/drivers/net/wimax/i2400m/netdev.c b/drivers/net/wimax/i2400m/netdev.c index e7d1a51ee3fe..f67af4291f8c 100644 --- a/drivers/net/wimax/i2400m/netdev.c +++ b/drivers/net/wimax/i2400m/netdev.c @@ -171,8 +171,9 @@ void i2400m_wake_tx_work(struct work_struct *ws) result = 0; if (result < 0) { dev_err(dev, "WAKE&TX: device didn't get out of idle: " - "%d\n", result); - goto error; + "%d - resetting\n", result); + i2400m_reset(i2400m, I2400M_RT_BUS); + goto error; } result = wait_event_timeout(i2400m->state_wq, i2400m->state != I2400M_SS_IDLE, 5 * HZ); @@ -180,7 +181,8 @@ void i2400m_wake_tx_work(struct work_struct *ws) result = -ETIMEDOUT; if (result < 0) { dev_err(dev, "WAKE&TX: error waiting for device to exit IDLE: " - "%d\n", result); + "%d - resetting\n", result); + i2400m_reset(i2400m, I2400M_RT_BUS); goto error; } msleep(20); /* device still needs some time or it drops it */ diff --git a/drivers/net/wimax/i2400m/rx.c b/drivers/net/wimax/i2400m/rx.c index 64a44ca00675..e3d2a9de023c 100644 --- a/drivers/net/wimax/i2400m/rx.c +++ b/drivers/net/wimax/i2400m/rx.c @@ -828,7 +828,7 @@ void i2400m_roq_queue(struct i2400m *i2400m, struct i2400m_roq *roq, dev_err(dev, "SW BUG? queue nsn %d (lbn %u ws %u)\n", nsn, lbn, roq->ws); i2400m_roq_log_dump(i2400m, roq); - i2400m->bus_reset(i2400m, I2400M_RT_WARM); + i2400m_reset(i2400m, I2400M_RT_WARM); } else { __i2400m_roq_queue(i2400m, roq, skb, lbn, nsn); i2400m_roq_log_add(i2400m, roq, I2400M_RO_TYPE_PACKET, @@ -894,7 +894,7 @@ void i2400m_roq_queue_update_ws(struct i2400m *i2400m, struct i2400m_roq *roq, dev_err(dev, "SW BUG? queue_update_ws nsn %u (sn %u ws %u)\n", nsn, sn, roq->ws); i2400m_roq_log_dump(i2400m, roq); - i2400m->bus_reset(i2400m, I2400M_RT_WARM); + i2400m_reset(i2400m, I2400M_RT_WARM); } else { /* if the queue is empty, don't bother as we'd queue * it and inmediately unqueue it -- just deliver it */ diff --git a/drivers/net/wimax/i2400m/sdio.c b/drivers/net/wimax/i2400m/sdio.c index ec178928914e..20ab22ec291b 100644 --- a/drivers/net/wimax/i2400m/sdio.c +++ b/drivers/net/wimax/i2400m/sdio.c @@ -43,7 +43,7 @@ * i2400m_release() * free_netdev(net_dev) * - * i2400ms_bus_reset() Called by i2400m->bus_reset + * i2400ms_bus_reset() Called by i2400m_reset * __i2400ms_reset() * __i2400ms_send_barker() */ @@ -342,13 +342,6 @@ 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: - /* 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_bus_release(i2400m); diff --git a/drivers/net/wimax/i2400m/usb.c b/drivers/net/wimax/i2400m/usb.c index 418db12b0cd7..5e079408f16e 100644 --- a/drivers/net/wimax/i2400m/usb.c +++ b/drivers/net/wimax/i2400m/usb.c @@ -58,7 +58,7 @@ * i2400mu_rx_release() * i2400mu_tx_release() * - * i2400mu_bus_reset() Called by i2400m->bus_reset + * i2400mu_bus_reset() Called by i2400m_reset * __i2400mu_reset() * __i2400mu_send_barker() * usb_reset_device() -- cgit v1.2.3 From 5ab5a7215a0cfd40572a9f09276ebcb071ee6fb7 Mon Sep 17 00:00:00 2001 From: Inaky Perez-Gonzalez Date: Thu, 15 Oct 2009 18:16:08 +0900 Subject: wimax/i2400m: fix device getting stuck in IDLE mode The i2400m, when conected, will negotiate with the WiMAX basestation to put the link in IDLE mode when it is not being used. Upon RX/TX traffic, the link has to be restablished and that might require some crypto handshakes and maybe a DHCP renew. This process might take up to 20 (!) seconds and in some cases we were seeing network watchdog warnings that weren't needed. So the network watchdog timeout is updated to be slightly above that 20s threshold. As well, the driver itself will double check if the device is stuck in IDLE mode -- if that happens, the device will be reset (in this case the queue is also woken up to remove bogus--once the device is reset--warnings). Signed-off-by: Inaky Perez-Gonzalez --- drivers/net/wimax/i2400m/netdev.c | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) (limited to 'drivers/net/wimax') diff --git a/drivers/net/wimax/i2400m/netdev.c b/drivers/net/wimax/i2400m/netdev.c index f67af4291f8c..599aa4eb9baa 100644 --- a/drivers/net/wimax/i2400m/netdev.c +++ b/drivers/net/wimax/i2400m/netdev.c @@ -89,7 +89,10 @@ enum { * The MTU is 1400 or less */ I2400M_MAX_MTU = 1400, - I2400M_TX_TIMEOUT = HZ, + /* 20 secs? yep, this is the maximum timeout that the device + * might take to get out of IDLE / negotiate it with the base + * station. We add 1sec for good measure. */ + I2400M_TX_TIMEOUT = 21 * HZ, I2400M_TX_QLEN = 5, }; @@ -151,6 +154,7 @@ void i2400m_wake_tx_work(struct work_struct *ws) { int result; struct i2400m *i2400m = container_of(ws, struct i2400m, wake_tx_ws); + struct net_device *net_dev = i2400m->wimax_dev.net_dev; struct device *dev = i2400m_dev(i2400m); struct sk_buff *skb = i2400m->wake_tx_skb; unsigned long flags; @@ -166,6 +170,11 @@ void i2400m_wake_tx_work(struct work_struct *ws) dev_err(dev, "WAKE&TX: skb dissapeared!\n"); goto out_put; } + /* If we have, somehow, lost the connection after this was + * queued, don't do anything; this might be the device got + * reset or just disconnected. */ + if (unlikely(!netif_carrier_ok(net_dev))) + goto out_kfree; result = i2400m_cmd_exit_idle(i2400m); if (result == -EILSEQ) result = 0; @@ -176,7 +185,8 @@ void i2400m_wake_tx_work(struct work_struct *ws) goto error; } result = wait_event_timeout(i2400m->state_wq, - i2400m->state != I2400M_SS_IDLE, 5 * HZ); + i2400m->state != I2400M_SS_IDLE, + net_dev->watchdog_timeo - HZ/2); if (result == 0) result = -ETIMEDOUT; if (result < 0) { @@ -187,8 +197,9 @@ void i2400m_wake_tx_work(struct work_struct *ws) } 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: + netif_wake_queue(net_dev); +out_kfree: kfree_skb(skb); /* refcount transferred by _hard_start_xmit() */ out_put: i2400m_put(i2400m); -- cgit v1.2.3 From 296bd4bdd0a43c5e56ee310bcb9b4722e5d52db8 Mon Sep 17 00:00:00 2001 From: Inaky Perez-Gonzalez Date: Tue, 20 Oct 2009 11:09:25 +0900 Subject: wimax/i2400m: Fix USB timeout specifications (to ms from HZ) The USB code was incorrectly specifiying timeouts to be in jiffies vs msecs. On top of that, lower it to 200ms, as 1s is really too long (doesn't allow the watchdog to trip a reset if the device timesout too often). Signed-off-by: Inaky Perez-Gonzalez --- drivers/net/wimax/i2400m/usb-fw.c | 2 +- drivers/net/wimax/i2400m/usb-rx.c | 2 +- drivers/net/wimax/i2400m/usb-tx.c | 2 +- drivers/net/wimax/i2400m/usb.c | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) (limited to 'drivers/net/wimax') diff --git a/drivers/net/wimax/i2400m/usb-fw.c b/drivers/net/wimax/i2400m/usb-fw.c index b59aee0ee649..8ec8b6d56a13 100644 --- a/drivers/net/wimax/i2400m/usb-fw.c +++ b/drivers/net/wimax/i2400m/usb-fw.c @@ -102,7 +102,7 @@ ssize_t i2400mu_tx_bulk_out(struct i2400mu *i2400mu, void *buf, size_t buf_size) epd = usb_get_epd(i2400mu->usb_iface, i2400mu->endpoint_cfg.bulk_out); pipe = usb_sndbulkpipe(i2400mu->usb_dev, epd->bEndpointAddress); retry: - result = usb_bulk_msg(i2400mu->usb_dev, pipe, buf, buf_size, &len, HZ); + result = usb_bulk_msg(i2400mu->usb_dev, pipe, buf, buf_size, &len, 200); switch (result) { case 0: if (len != buf_size) { diff --git a/drivers/net/wimax/i2400m/usb-rx.c b/drivers/net/wimax/i2400m/usb-rx.c index 245587feb8c8..22d127bf79e1 100644 --- a/drivers/net/wimax/i2400m/usb-rx.c +++ b/drivers/net/wimax/i2400m/usb-rx.c @@ -214,7 +214,7 @@ retry: } result = usb_bulk_msg( i2400mu->usb_dev, usb_pipe, rx_skb->data + rx_skb->len, - rx_size, &read_size, HZ); + rx_size, &read_size, 200); usb_mark_last_busy(i2400mu->usb_dev); switch (result) { case 0: diff --git a/drivers/net/wimax/i2400m/usb-tx.c b/drivers/net/wimax/i2400m/usb-tx.c index a3c46e99c804..6cdf0036a146 100644 --- a/drivers/net/wimax/i2400m/usb-tx.c +++ b/drivers/net/wimax/i2400m/usb-tx.c @@ -105,7 +105,7 @@ int i2400mu_tx(struct i2400mu *i2400mu, struct i2400m_msg_hdr *tx_msg, 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); + tx_msg, tx_msg_size, &sent_size, 200); usb_mark_last_busy(i2400mu->usb_dev); switch (result) { case 0: diff --git a/drivers/net/wimax/i2400m/usb.c b/drivers/net/wimax/i2400m/usb.c index 5e079408f16e..34df0906caae 100644 --- a/drivers/net/wimax/i2400m/usb.c +++ b/drivers/net/wimax/i2400m/usb.c @@ -173,7 +173,7 @@ int __i2400mu_send_barker(struct i2400mu *i2400mu, 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); + &actual_len, 200); if (ret < 0) { if (ret != -EINVAL) dev_err(dev, "E: barker error: %d\n", ret); -- cgit v1.2.3 From 02eb41ef2a8631022fd90e096c57562dec9e7a9a Mon Sep 17 00:00:00 2001 From: Inaky Perez-Gonzalez Date: Fri, 23 Oct 2009 17:45:07 -0700 Subject: wimax/i2400m: correctly identify all iwmc3200-based SKUs Different paths of the i2400m SDIO driver need to take care of a few SKU-specific quirks. For the ones that are common to to all the iwmc3200 based devices, introduce i2400ms->iwmc3200 [set in i2400ms_probe()], so it doesn't have to check against the list of iwmc3200 SKU IDs on each quirk site. Signed-off-by: Inaky Perez-Gonzalez --- drivers/net/wimax/i2400m/i2400m-sdio.h | 3 +++ drivers/net/wimax/i2400m/sdio.c | 16 +++++++++++++--- 2 files changed, 16 insertions(+), 3 deletions(-) (limited to 'drivers/net/wimax') diff --git a/drivers/net/wimax/i2400m/i2400m-sdio.h b/drivers/net/wimax/i2400m/i2400m-sdio.h index 18218a24a8f8..fba482c08d66 100644 --- a/drivers/net/wimax/i2400m/i2400m-sdio.h +++ b/drivers/net/wimax/i2400m/i2400m-sdio.h @@ -114,6 +114,9 @@ struct i2400ms { wait_queue_head_t bm_wfa_wq; int bm_wait_result; size_t bm_ack_size; + + /* Device is any of the iwmc3200 SKUs */ + unsigned iwmc3200:1; }; diff --git a/drivers/net/wimax/i2400m/sdio.c b/drivers/net/wimax/i2400m/sdio.c index 20ab22ec291b..079f900e9ae5 100644 --- a/drivers/net/wimax/i2400m/sdio.c +++ b/drivers/net/wimax/i2400m/sdio.c @@ -105,8 +105,9 @@ static const struct i2400m_poke_table i2400ms_pokes[] = { * error (-ENODEV when it was unable to enable the function). */ static -int i2400ms_enable_function(struct sdio_func *func, unsigned maxtries) +int i2400ms_enable_function(struct i2400ms *i2400ms, unsigned maxtries) { + struct sdio_func *func = i2400ms->func; u64 timeout; int err; struct device *dev = &func->dev; @@ -126,7 +127,7 @@ int i2400ms_enable_function(struct sdio_func *func, unsigned maxtries) * platforms (system hang). We explicitly overwrite * func->enable_timeout here to work around the issue. */ - if (func->device == SDIO_DEVICE_ID_INTEL_IWMC3200WIMAX) + if (i2400ms->iwmc3200) func->enable_timeout = IWMC3200_IOR_TIMEOUT; err = sdio_enable_func(func); if (0 == err) { @@ -176,7 +177,7 @@ int i2400ms_bus_setup(struct i2400m *i2400m) goto error_set_blk_size; } - result = i2400ms_enable_function(func, 1); + result = i2400ms_enable_function(i2400ms, 1); if (result < 0) { dev_err(dev, "Cannot enable SDIO function: %d\n", result); goto error_func_enable; @@ -487,6 +488,15 @@ int i2400ms_probe(struct sdio_func *func, i2400m->bus_bm_mac_addr_impaired = 1; i2400m->bus_bm_pokes_table = &i2400ms_pokes[0]; + switch (func->device) { + case SDIO_DEVICE_ID_INTEL_IWMC3200WIMAX: + case SDIO_DEVICE_ID_INTEL_IWMC3200WIMAX_2G5: + i2400ms->iwmc3200 = 1; + break; + default: + i2400ms->iwmc3200 = 0; + } + result = i2400m_setup(i2400m, I2400M_BRI_NO_REBOOT); if (result < 0) { dev_err(dev, "cannot setup device: %d\n", result); -- cgit v1.2.3 From fae92216da87d1c78aa51c4503acb312a47266e9 Mon Sep 17 00:00:00 2001 From: Inaky Perez-Gonzalez Date: Fri, 23 Oct 2009 17:48:36 -0700 Subject: wimax/i2400m: don't retry SDIO enable in probe() paths The iwmc3200 has a quirk where retrying SDIO enable during the probe() path causes bad interactions with the TOP function controller that causes a reset storm. The workaround is simply not to retry an SDIO enable in said path (and still do in the reset / reinitialization paths). The driver does so by checking i2400ms->debugfs_dentry to see if it has been initialized; if not, it is in the probe() path. Document said fact in i2400ms->debugfs_entry. Signed-off-by: Inaky Perez-Gonzalez --- drivers/net/wimax/i2400m/i2400m-sdio.h | 8 ++++++++ drivers/net/wimax/i2400m/sdio.c | 13 ++++++++++++- 2 files changed, 20 insertions(+), 1 deletion(-) (limited to 'drivers/net/wimax') diff --git a/drivers/net/wimax/i2400m/i2400m-sdio.h b/drivers/net/wimax/i2400m/i2400m-sdio.h index fba482c08d66..b9c4bed3b457 100644 --- a/drivers/net/wimax/i2400m/i2400m-sdio.h +++ b/drivers/net/wimax/i2400m/i2400m-sdio.h @@ -100,6 +100,14 @@ enum { * @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. + * + * @debugfs_dentry: dentry for the SDIO specific debugfs files + * + * Note this value is set to NULL upon destruction; this is + * because some routinges use it to determine if we are inside the + * probe() path or some other path. When debugfs is disabled, + * creation sets the dentry to '(void*) -ENODEV', which is valid + * for the test. */ struct i2400ms { struct i2400m i2400m; /* FIRST! See doc */ diff --git a/drivers/net/wimax/i2400m/sdio.c b/drivers/net/wimax/i2400m/sdio.c index 079f900e9ae5..e8ad85c7a799 100644 --- a/drivers/net/wimax/i2400m/sdio.c +++ b/drivers/net/wimax/i2400m/sdio.c @@ -159,6 +159,10 @@ function_enabled: /* * Setup minimal device communication infrastructure needed to at * least be able to update the firmware. + * + * Note the ugly trick: if we are in the probe path + * (i2400ms->debugfs_dentry == NULL), we only retry function + * enablement one, to avoid racing with the iwmc3200 top controller. */ static int i2400ms_bus_setup(struct i2400m *i2400m) @@ -168,6 +172,7 @@ int i2400ms_bus_setup(struct i2400m *i2400m) container_of(i2400m, struct i2400ms, i2400m); struct device *dev = i2400m_dev(i2400m); struct sdio_func *func = i2400ms->func; + int retries; sdio_claim_host(func); result = sdio_set_block_size(func, I2400MS_BLK_SIZE); @@ -177,7 +182,11 @@ int i2400ms_bus_setup(struct i2400m *i2400m) goto error_set_blk_size; } - result = i2400ms_enable_function(i2400ms, 1); + if (i2400ms->iwmc3200 && i2400ms->debugfs_dentry == NULL) + retries = 0; + else + retries = 1; + result = i2400ms_enable_function(i2400ms, retries); if (result < 0) { dev_err(dev, "Cannot enable SDIO function: %d\n", result); goto error_func_enable; @@ -415,6 +424,7 @@ int i2400ms_debugfs_add(struct i2400ms *i2400ms) error: debugfs_remove_recursive(i2400ms->debugfs_dentry); + i2400ms->debugfs_dentry = NULL; return result; } @@ -531,6 +541,7 @@ void i2400ms_remove(struct sdio_func *func) d_fnstart(3, dev, "SDIO func %p\n", func); debugfs_remove_recursive(i2400ms->debugfs_dentry); + i2400ms->debugfs_dentry = NULL; i2400m_release(i2400m); sdio_set_drvdata(func, NULL); free_netdev(net_dev); -- cgit v1.2.3 From faf57162e462eafe87458e21bf641f9d138f8171 Mon Sep 17 00:00:00 2001 From: Inaky Perez-Gonzalez Date: Tue, 20 Oct 2009 11:10:59 +0900 Subject: wimax/i2400m: handle USB stalls When the device stalls, clear it and retry; if it keeps failing too often, reset the device. This specially happens when running on virtual machines; the real hardware doesn't seem to trip on stalls too much, except for a few reports in the mailing list (still to be confirmed this is the cause, although it seems likely. NOTE: it is not clear if the URB has to be resubmitted fully or start only at the offset of the first transaction sent. Can't find documentation to clarify one end or the other. Tests that just resubmit the whole URB seemed to work in my environment. Signed-off-by: Inaky Perez-Gonzalez --- drivers/net/wimax/i2400m/usb-fw.c | 22 +++++++++++++++ drivers/net/wimax/i2400m/usb-rx.c | 21 +++++++++++++++ drivers/net/wimax/i2400m/usb-tx.c | 22 +++++++++++++++ drivers/net/wimax/i2400m/usb.c | 57 ++++++++++++++++++++++++++++++++++----- 4 files changed, 116 insertions(+), 6 deletions(-) (limited to 'drivers/net/wimax') diff --git a/drivers/net/wimax/i2400m/usb-fw.c b/drivers/net/wimax/i2400m/usb-fw.c index 8ec8b6d56a13..d8f6ce29efff 100644 --- a/drivers/net/wimax/i2400m/usb-fw.c +++ b/drivers/net/wimax/i2400m/usb-fw.c @@ -113,6 +113,28 @@ retry: } result = len; break; + case -EPIPE: + /* + * Stall -- maybe the device is choking with our + * requests. Clear it and give it some time. If they + * happen to often, it might be another symptom, so we + * reset. + * + * No error handling for usb_clear_halt(0; if it + * works, the retry works; if it fails, this switch + * does the error handling for us. + */ + if (edc_inc(&i2400mu->urb_edc, + 10 * EDC_MAX_ERRORS, EDC_ERROR_TIMEFRAME)) { + dev_err(dev, "BM-CMD: too many stalls in " + "URB; resetting device\n"); + usb_queue_reset_device(i2400mu->usb_iface); + /* fallthrough */ + } else { + usb_clear_halt(i2400mu->usb_dev, pipe); + msleep(10); /* give the device some time */ + goto retry; + } case -EINVAL: /* while removing driver */ case -ENODEV: /* dev disconnect ... */ case -ENOENT: /* just ignore it */ diff --git a/drivers/net/wimax/i2400m/usb-rx.c b/drivers/net/wimax/i2400m/usb-rx.c index 22d127bf79e1..ba1b02362dfc 100644 --- a/drivers/net/wimax/i2400m/usb-rx.c +++ b/drivers/net/wimax/i2400m/usb-rx.c @@ -222,6 +222,26 @@ retry: goto retry; /* ZLP, just resubmit */ skb_put(rx_skb, read_size); break; + case -EPIPE: + /* + * Stall -- maybe the device is choking with our + * requests. Clear it and give it some time. If they + * happen to often, it might be another symptom, so we + * reset. + * + * No error handling for usb_clear_halt(0; if it + * works, the retry works; if it fails, this switch + * does the error handling for us. + */ + if (edc_inc(&i2400mu->urb_edc, + 10 * EDC_MAX_ERRORS, EDC_ERROR_TIMEFRAME)) { + dev_err(dev, "BM-CMD: too many stalls in " + "URB; resetting device\n"); + goto do_reset; + } + usb_clear_halt(i2400mu->usb_dev, usb_pipe); + msleep(10); /* give the device some time */ + goto retry; case -EINVAL: /* while removing driver */ case -ENODEV: /* dev disconnect ... */ case -ENOENT: /* just ignore it */ @@ -283,6 +303,7 @@ out: error_reset: dev_err(dev, "RX: maximum errors in URB exceeded; " "resetting device\n"); +do_reset: usb_queue_reset_device(i2400mu->usb_iface); rx_skb = ERR_PTR(result); goto out; diff --git a/drivers/net/wimax/i2400m/usb-tx.c b/drivers/net/wimax/i2400m/usb-tx.c index 6cdf0036a146..c65b9979f87e 100644 --- a/drivers/net/wimax/i2400m/usb-tx.c +++ b/drivers/net/wimax/i2400m/usb-tx.c @@ -115,6 +115,28 @@ retry: result = -EIO; } break; + case -EPIPE: + /* + * Stall -- maybe the device is choking with our + * requests. Clear it and give it some time. If they + * happen to often, it might be another symptom, so we + * reset. + * + * No error handling for usb_clear_halt(0; if it + * works, the retry works; if it fails, this switch + * does the error handling for us. + */ + if (edc_inc(&i2400mu->urb_edc, + 10 * EDC_MAX_ERRORS, EDC_ERROR_TIMEFRAME)) { + dev_err(dev, "BM-CMD: too many stalls in " + "URB; resetting device\n"); + usb_queue_reset_device(i2400mu->usb_iface); + /* fallthrough */ + } else { + usb_clear_halt(i2400mu->usb_dev, usb_pipe); + msleep(10); /* give the device some time */ + goto retry; + } case -EINVAL: /* while removing driver */ case -ENODEV: /* dev disconnect ... */ case -ENOENT: /* just ignore it */ diff --git a/drivers/net/wimax/i2400m/usb.c b/drivers/net/wimax/i2400m/usb.c index 34df0906caae..47e84ef355c5 100644 --- a/drivers/net/wimax/i2400m/usb.c +++ b/drivers/net/wimax/i2400m/usb.c @@ -172,14 +172,59 @@ int __i2400mu_send_barker(struct i2400mu *i2400mu, epd = usb_get_epd(i2400mu->usb_iface, endpoint); pipe = usb_sndbulkpipe(i2400mu->usb_dev, epd->bEndpointAddress); memcpy(buffer, barker, barker_size); +retry: ret = usb_bulk_msg(i2400mu->usb_dev, pipe, buffer, barker_size, &actual_len, 200); - 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; + switch (ret) { + case 0: + if (actual_len != barker_size) { /* Too short? drop it */ + dev_err(dev, "E: %s: short write (%d B vs %zu " + "expected)\n", + __func__, actual_len, barker_size); + ret = -EIO; + } + break; + case -EPIPE: + /* + * Stall -- maybe the device is choking with our + * requests. Clear it and give it some time. If they + * happen to often, it might be another symptom, so we + * reset. + * + * No error handling for usb_clear_halt(0; if it + * works, the retry works; if it fails, this switch + * does the error handling for us. + */ + if (edc_inc(&i2400mu->urb_edc, + 10 * EDC_MAX_ERRORS, EDC_ERROR_TIMEFRAME)) { + dev_err(dev, "E: %s: too many stalls in " + "URB; resetting device\n", __func__); + usb_queue_reset_device(i2400mu->usb_iface); + /* fallthrough */ + } else { + usb_clear_halt(i2400mu->usb_dev, pipe); + msleep(10); /* give the device some time */ + goto retry; + } + case -EINVAL: /* while removing driver */ + case -ENODEV: /* dev disconnect ... */ + case -ENOENT: /* just ignore it */ + case -ESHUTDOWN: /* and exit */ + case -ECONNRESET: + ret = -ESHUTDOWN; + break; + default: /* Some error? */ + if (edc_inc(&i2400mu->urb_edc, + EDC_MAX_ERRORS, EDC_ERROR_TIMEFRAME)) { + dev_err(dev, "E: %s: maximum errors in URB " + "exceeded; resetting device\n", + __func__); + usb_queue_reset_device(i2400mu->usb_iface); + } else { + dev_warn(dev, "W: %s: cannot send URB: %d\n", + __func__, ret); + goto retry; + } } kfree(buffer); error_kzalloc: -- cgit v1.2.3 From 5a039e78522502067a68c969fb87fba1e39bc2a6 Mon Sep 17 00:00:00 2001 From: Inaky Perez-Gonzalez Date: Fri, 23 Oct 2009 17:31:31 -0700 Subject: wimax/i2400m: fix bad assignment of return value in i2400mu_tx_bulk_out The function was always setting the return value to the amount of bytes transferred, overwriting the error code in error paths. Signed-off-by: Inaky Perez-Gonzalez --- drivers/net/wimax/i2400m/usb-fw.c | 1 - 1 file changed, 1 deletion(-) (limited to 'drivers/net/wimax') diff --git a/drivers/net/wimax/i2400m/usb-fw.c b/drivers/net/wimax/i2400m/usb-fw.c index d8f6ce29efff..ce6b9938fde0 100644 --- a/drivers/net/wimax/i2400m/usb-fw.c +++ b/drivers/net/wimax/i2400m/usb-fw.c @@ -157,7 +157,6 @@ retry: result); goto retry; } - result = len; if (do_autopm) usb_autopm_put_interface(i2400mu->usb_iface); return result; -- cgit v1.2.3 From 20d57f8e2553fa6f5cd382131b7f18e91c7141fc Mon Sep 17 00:00:00 2001 From: Inaky Perez-Gonzalez Date: Mon, 26 Oct 2009 09:27:02 -0700 Subject: wimax/i2400m: fix SDIO debugfs dentry name The SDIO specific debugfs dentry was being misnamed "i2400m-usb" instead of "i2400m-sdio". Signed-off-by: Inaky Perez-Gonzalez --- drivers/net/wimax/i2400m/sdio.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers/net/wimax') diff --git a/drivers/net/wimax/i2400m/sdio.c b/drivers/net/wimax/i2400m/sdio.c index e8ad85c7a799..b06d52633a43 100644 --- a/drivers/net/wimax/i2400m/sdio.c +++ b/drivers/net/wimax/i2400m/sdio.c @@ -407,7 +407,7 @@ 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); + dentry = debugfs_create_dir("i2400m-sdio", dentry); result = PTR_ERR(dentry); if (IS_ERR(dentry)) { if (result == -ENODEV) -- cgit v1.2.3 From e7fec0bbf13b9bc2869a18e66f0cda7bb7f559f0 Mon Sep 17 00:00:00 2001 From: Inaky Perez-Gonzalez Date: Tue, 27 Oct 2009 14:33:45 -0700 Subject: wimax/i2400m: fix inverted value in i2400ms_bus_setup() Fix inverted setting of 'retries'; when we are in the probe() path, we should retry to enable the function only once; otherwise until it times out. Signed-off-by: Inaky Perez-Gonzalez --- drivers/net/wimax/i2400m/sdio.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'drivers/net/wimax') diff --git a/drivers/net/wimax/i2400m/sdio.c b/drivers/net/wimax/i2400m/sdio.c index b06d52633a43..76a50ac02ebb 100644 --- a/drivers/net/wimax/i2400m/sdio.c +++ b/drivers/net/wimax/i2400m/sdio.c @@ -183,9 +183,9 @@ int i2400ms_bus_setup(struct i2400m *i2400m) } if (i2400ms->iwmc3200 && i2400ms->debugfs_dentry == NULL) - retries = 0; - else retries = 1; + else + retries = 0; result = i2400ms_enable_function(i2400ms, retries); if (result < 0) { dev_err(dev, "Cannot enable SDIO function: %d\n", result); -- cgit v1.2.3