libertas: support boot commands to write persistent firmware and bootloader
authorBrian Cavagnolo <brian@cozybit.com>
Mon, 21 Jul 2008 18:02:46 +0000 (11:02 -0700)
committerJohn W. Linville <linville@tuxdriver.com>
Fri, 22 Aug 2008 20:29:49 +0000 (16:29 -0400)
Add locking and non-locking versions of if_usb_prog_firmware to support
programming firmware after and before driver bring-up respectively.  Add more
suitable error codes for firmware programming process.  Add capability checks
for persistent features before attempting to use them.

Based on patches from Brajesh Dave and Priyank Singh.

Signed-off-by: Brian Cavagnolo <brian@cozybit.com>
Acked-by: Dan Williams <dcbw@redhat.com>
Signed-off-by: John W. Linville <linville@tuxdriver.com>
drivers/net/wireless/libertas/cmd.c
drivers/net/wireless/libertas/defs.h
drivers/net/wireless/libertas/if_usb.c
drivers/net/wireless/libertas/if_usb.h

index 75427e61898dfbecb84d7db4024e34ae254e723b..af5fd709887fddb59b246e8b3ebcc7c7546f343f 100644 (file)
@@ -1033,9 +1033,9 @@ int lbs_mesh_access(struct lbs_private *priv, uint16_t cmd_action,
        return ret;
 }
 
-int lbs_mesh_config_send(struct lbs_private *priv,
-                        struct cmd_ds_mesh_config *cmd,
-                        uint16_t action, uint16_t type)
+static int __lbs_mesh_config_send(struct lbs_private *priv,
+                                 struct cmd_ds_mesh_config *cmd,
+                                 uint16_t action, uint16_t type)
 {
        int ret;
 
@@ -1054,6 +1054,19 @@ int lbs_mesh_config_send(struct lbs_private *priv,
        return ret;
 }
 
+int lbs_mesh_config_send(struct lbs_private *priv,
+                        struct cmd_ds_mesh_config *cmd,
+                        uint16_t action, uint16_t type)
+{
+       int ret;
+
+       if (!(priv->fwcapinfo & FW_CAPINFO_PERSISTENT_CONFIG))
+               return -EOPNOTSUPP;
+
+       ret = __lbs_mesh_config_send(priv, cmd, action, type);
+       return ret;
+}
+
 /* This function is the CMD_MESH_CONFIG legacy function.  It only handles the
  * START and STOP actions.  The extended actions supported by CMD_MESH_CONFIG
  * are all handled by preparing a struct cmd_ds_mesh_config and passing it to
@@ -1095,7 +1108,7 @@ int lbs_mesh_config(struct lbs_private *priv, uint16_t action, uint16_t chan)
                    action, priv->mesh_tlv, chan,
                    escape_essid(priv->mesh_ssid, priv->mesh_ssid_len));
 
-       return lbs_mesh_config_send(priv, &cmd, action, priv->mesh_tlv);
+       return __lbs_mesh_config_send(priv, &cmd, action, priv->mesh_tlv);
 }
 
 static int lbs_cmd_bcn_ctrl(struct lbs_private * priv,
index 12e687550bcefd6b7be7f5d30a7bcc6ac75551fb..4b2428ac2223805843c498c186b10004633dc220 100644 (file)
@@ -243,6 +243,9 @@ static inline void lbs_deb_hex(unsigned int grp, const char *prompt, u8 *buf, in
 
 #define        CMD_F_HOSTCMD           (1 << 0)
 #define FW_CAPINFO_WPA         (1 << 0)
+#define FW_CAPINFO_FIRMWARE_UPGRADE    (1 << 13)
+#define FW_CAPINFO_BOOT2_UPGRADE       (1<<14)
+#define FW_CAPINFO_PERSISTENT_CONFIG   (1<<15)
 
 #define KEY_LEN_WPA_AES                        16
 #define KEY_LEN_WPA_TKIP               32
@@ -316,7 +319,8 @@ enum PS_STATE {
 enum DNLD_STATE {
        DNLD_RES_RECEIVED,
        DNLD_DATA_SENT,
-       DNLD_CMD_SENT
+       DNLD_CMD_SENT,
+       DNLD_BOOTCMD_SENT,
 };
 
 /** LBS_MEDIA_STATE */
index 632c291404ab5ccbbe7a7a862f24ab600aca8fa8..b5013ce31b9abd7732005b918f5ad53dbcab6a04 100644 (file)
@@ -39,7 +39,10 @@ MODULE_DEVICE_TABLE(usb, if_usb_table);
 
 static void if_usb_receive(struct urb *urb);
 static void if_usb_receive_fwload(struct urb *urb);
-static int if_usb_prog_firmware(struct if_usb_card *cardp);
+static int __if_usb_prog_firmware(struct if_usb_card *cardp,
+                                       const char *fwname, int cmd);
+static int if_usb_prog_firmware(struct if_usb_card *cardp,
+                                       const char *fwname, int cmd);
 static int if_usb_host_to_card(struct lbs_private *priv, uint8_t type,
                               uint8_t *payload, uint16_t nb);
 static int usb_tx_block(struct if_usb_card *cardp, uint8_t *payload,
@@ -66,10 +69,10 @@ static void if_usb_write_bulk_callback(struct urb *urb)
                lbs_deb_usb2(&urb->dev->dev, "Actual length transmitted %d\n",
                             urb->actual_length);
 
-               /* Used for both firmware TX and regular TX.  priv isn't
-                * valid at firmware load time.
+               /* Boot commands such as UPDATE_FW and UPDATE_BOOT2 are not
+                * passed up to the lbs level.
                 */
-               if (priv)
+               if (priv && priv->dnld_sent != DNLD_BOOTCMD_SENT)
                        lbs_host_to_card_done(priv);
        } else {
                /* print the failure status number for debug */
@@ -231,7 +234,7 @@ static int if_usb_probe(struct usb_interface *intf,
        }
 
        /* Upload firmware */
-       if (if_usb_prog_firmware(cardp)) {
+       if (__if_usb_prog_firmware(cardp, lbs_fw_name, BOOT_CMD_FW_BY_USB)) {
                lbs_deb_usbd(&udev->dev, "FW upload failed\n");
                goto err_prog_firmware;
        }
@@ -510,7 +513,7 @@ static void if_usb_receive_fwload(struct urb *urb)
                if (le16_to_cpu(cardp->udev->descriptor.bcdDevice) < 0x3106) {
                        kfree_skb(skb);
                        if_usb_submit_rx_urb_fwload(cardp);
-                       cardp->bootcmdresp = 1;
+                       cardp->bootcmdresp = BOOT_CMD_RESP_OK;
                        lbs_deb_usbd(&cardp->udev->dev,
                                     "Received valid boot command response\n");
                        return;
@@ -526,7 +529,9 @@ static void if_usb_receive_fwload(struct urb *urb)
                                lbs_pr_info("boot cmd response wrong magic number (0x%x)\n",
                                            le32_to_cpu(bootcmdresp.magic));
                        }
-               } else if (bootcmdresp.cmd != BOOT_CMD_FW_BY_USB) {
+               } else if ((bootcmdresp.cmd != BOOT_CMD_FW_BY_USB) &&
+                          (bootcmdresp.cmd != BOOT_CMD_UPDATE_FW) &&
+                          (bootcmdresp.cmd != BOOT_CMD_UPDATE_BOOT2)) {
                        lbs_pr_info("boot cmd response cmd_tag error (%d)\n",
                                    bootcmdresp.cmd);
                } else if (bootcmdresp.result != BOOT_CMD_RESP_OK) {
@@ -564,8 +569,8 @@ static void if_usb_receive_fwload(struct urb *urb)
 
        kfree_skb(skb);
 
-       /* reschedule timer for 200ms hence */
-       mod_timer(&cardp->fw_timeout, jiffies + (HZ/5));
+       /* Give device 5s to either write firmware to its RAM or eeprom */
+       mod_timer(&cardp->fw_timeout, jiffies + (HZ*5));
 
        if (cardp->fwfinalblk) {
                cardp->fwdnldover = 1;
@@ -809,7 +814,54 @@ static int check_fwfile_format(const uint8_t *data, uint32_t totlen)
 }
 
 
-static int if_usb_prog_firmware(struct if_usb_card *cardp)
+/**
+*  @brief This function programs the firmware subject to cmd
+*
+*  @param cardp             the if_usb_card descriptor
+*         fwname            firmware or boot2 image file name
+*         cmd               either BOOT_CMD_FW_BY_USB, BOOT_CMD_UPDATE_FW,
+*                           or BOOT_CMD_UPDATE_BOOT2.
+*  @return     0 or error code
+*/
+static int if_usb_prog_firmware(struct if_usb_card *cardp,
+                               const char *fwname, int cmd)
+{
+       struct lbs_private *priv = cardp->priv;
+       unsigned long flags, caps;
+       int ret;
+
+       caps = priv->fwcapinfo;
+       if (((cmd == BOOT_CMD_UPDATE_FW) && !(caps & FW_CAPINFO_FIRMWARE_UPGRADE)) ||
+           ((cmd == BOOT_CMD_UPDATE_BOOT2) && !(caps & FW_CAPINFO_BOOT2_UPGRADE)))
+               return -EOPNOTSUPP;
+
+       /* Ensure main thread is idle. */
+       spin_lock_irqsave(&priv->driver_lock, flags);
+       while (priv->cur_cmd != NULL || priv->dnld_sent != DNLD_RES_RECEIVED) {
+               spin_unlock_irqrestore(&priv->driver_lock, flags);
+               if (wait_event_interruptible(priv->waitq,
+                               (priv->cur_cmd == NULL &&
+                               priv->dnld_sent == DNLD_RES_RECEIVED))) {
+                       return -ERESTARTSYS;
+               }
+               spin_lock_irqsave(&priv->driver_lock, flags);
+       }
+       priv->dnld_sent = DNLD_BOOTCMD_SENT;
+       spin_unlock_irqrestore(&priv->driver_lock, flags);
+
+       ret = __if_usb_prog_firmware(cardp, fwname, cmd);
+
+       spin_lock_irqsave(&priv->driver_lock, flags);
+       priv->dnld_sent = DNLD_RES_RECEIVED;
+       spin_unlock_irqrestore(&priv->driver_lock, flags);
+
+       wake_up_interruptible(&priv->waitq);
+
+       return ret;
+}
+
+static int __if_usb_prog_firmware(struct if_usb_card *cardp,
+                                       const char *fwname, int cmd)
 {
        int i = 0;
        static int reset_count = 10;
@@ -817,20 +869,32 @@ static int if_usb_prog_firmware(struct if_usb_card *cardp)
 
        lbs_deb_enter(LBS_DEB_USB);
 
-       if ((ret = request_firmware(&cardp->fw, lbs_fw_name,
-                                   &cardp->udev->dev)) < 0) {
+       ret = request_firmware(&cardp->fw, fwname, &cardp->udev->dev);
+       if (ret < 0) {
                lbs_pr_err("request_firmware() failed with %#x\n", ret);
-               lbs_pr_err("firmware %s not found\n", lbs_fw_name);
+               lbs_pr_err("firmware %s not found\n", fwname);
                goto done;
        }
 
-       if (check_fwfile_format(cardp->fw->data, cardp->fw->size))
+       if (check_fwfile_format(cardp->fw->data, cardp->fw->size)) {
+               ret = -EINVAL;
                goto release_fw;
+       }
+
+       /* Cancel any pending usb business */
+       usb_kill_urb(cardp->rx_urb);
+       usb_kill_urb(cardp->tx_urb);
+
+       cardp->fwlastblksent = 0;
+       cardp->fwdnldover = 0;
+       cardp->totalbytes = 0;
+       cardp->fwfinalblk = 0;
+       cardp->bootcmdresp = 0;
 
 restart:
        if (if_usb_submit_rx_urb_fwload(cardp) < 0) {
                lbs_deb_usbd(&cardp->udev->dev, "URB submission is failed\n");
-               ret = -1;
+               ret = -EIO;
                goto release_fw;
        }
 
@@ -838,8 +902,7 @@ restart:
        do {
                int j = 0;
                i++;
-               /* Issue Boot command = 1, Boot from Download-FW */
-               if_usb_issue_boot_command(cardp, BOOT_CMD_FW_BY_USB);
+               if_usb_issue_boot_command(cardp, cmd);
                /* wait for command response */
                do {
                        j++;
@@ -847,12 +910,21 @@ restart:
                } while (cardp->bootcmdresp == 0 && j < 10);
        } while (cardp->bootcmdresp == 0 && i < 5);
 
-       if (cardp->bootcmdresp <= 0) {
+       if (cardp->bootcmdresp == BOOT_CMD_RESP_NOT_SUPPORTED) {
+               /* Return to normal operation */
+               ret = -EOPNOTSUPP;
+               usb_kill_urb(cardp->rx_urb);
+               usb_kill_urb(cardp->tx_urb);
+               if (if_usb_submit_rx_urb(cardp) < 0)
+                       ret = -EIO;
+               goto release_fw;
+       } else if (cardp->bootcmdresp <= 0) {
                if (--reset_count >= 0) {
                        if_usb_reset_device(cardp);
                        goto restart;
                }
-               return -1;
+               ret = -EIO;
+               goto release_fw;
        }
 
        i = 0;
@@ -882,7 +954,7 @@ restart:
                }
 
                lbs_pr_info("FW download failure, time = %d ms\n", i * 100);
-               ret = -1;
+               ret = -EIO;
                goto release_fw;
        }
 
index 5771a83a43f0abd14f6e9a49f8d87e4a5293fc09..5ba0aee0eb2f7e6eac289583a990f51fc1daf2f6 100644 (file)
@@ -30,6 +30,7 @@ struct bootcmd
 
 #define BOOT_CMD_RESP_OK               0x0001
 #define BOOT_CMD_RESP_FAIL             0x0000
+#define BOOT_CMD_RESP_NOT_SUPPORTED    0x0002
 
 struct bootcmdresp
 {
@@ -50,6 +51,10 @@ struct if_usb_card {
        uint8_t ep_in;
        uint8_t ep_out;
 
+       /* bootcmdresp == 0 means command is pending
+        * bootcmdresp < 0 means error
+        * bootcmdresp > 0 is a BOOT_CMD_RESP_* from firmware
+        */
        int8_t bootcmdresp;
 
        int ep_in_size;