root/drivers/net/wimax/i2400m/usb-fw.c

/* [<][>][^][v][top][bottom][index][help] */

DEFINITIONS

This source file includes following definitions.
  1. i2400mu_tx_bulk_out
  2. i2400mu_bus_bm_cmd_send
  3. __i2400mu_bm_notif_cb
  4. i2400mu_notif_submit
  5. i2400mu_bus_bm_wait_for_ack

   1 /*
   2  * Intel Wireless WiMAX Connection 2400m
   3  * Firmware uploader's USB specifics
   4  *
   5  *
   6  * Copyright (C) 2007-2008 Intel Corporation. All rights reserved.
   7  *
   8  * Redistribution and use in source and binary forms, with or without
   9  * modification, are permitted provided that the following conditions
  10  * are met:
  11  *
  12  *   * Redistributions of source code must retain the above copyright
  13  *     notice, this list of conditions and the following disclaimer.
  14  *   * Redistributions in binary form must reproduce the above copyright
  15  *     notice, this list of conditions and the following disclaimer in
  16  *     the documentation and/or other materials provided with the
  17  *     distribution.
  18  *   * Neither the name of Intel Corporation nor the names of its
  19  *     contributors may be used to endorse or promote products derived
  20  *     from this software without specific prior written permission.
  21  *
  22  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
  23  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
  24  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
  25  * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
  26  * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
  27  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
  28  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
  29  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
  30  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
  31  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
  32  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  33  *
  34  *
  35  * Intel Corporation <linux-wimax@intel.com>
  36  * Yanir Lubetkin <yanirx.lubetkin@intel.com>
  37  * Inaky Perez-Gonzalez <inaky.perez-gonzalez@intel.com>
  38  *  - Initial implementation
  39  *
  40  * Inaky Perez-Gonzalez <inaky.perez-gonzalez@intel.com>
  41  *  - bus generic/specific split
  42  *
  43  * THE PROCEDURE
  44  *
  45  * See fw.c for the generic description of this procedure.
  46  *
  47  * This file implements only the USB specifics. It boils down to how
  48  * to send a command and waiting for an acknowledgement from the
  49  * device.
  50  *
  51  * This code (and process) is single threaded. It assumes it is the
  52  * only thread poking around (guaranteed by fw.c).
  53  *
  54  * COMMAND EXECUTION
  55  *
  56  * A write URB is posted with the buffer to the bulk output endpoint.
  57  *
  58  * ACK RECEPTION
  59  *
  60  * We just post a URB to the notification endpoint and wait for
  61  * data. We repeat until we get all the data we expect (as indicated
  62  * by the call from the bus generic code).
  63  *
  64  * The data is not read from the bulk in endpoint for boot mode.
  65  *
  66  * ROADMAP
  67  *
  68  * i2400mu_bus_bm_cmd_send
  69  *   i2400m_bm_cmd_prepare...
  70  *   i2400mu_tx_bulk_out
  71  *
  72  * i2400mu_bus_bm_wait_for_ack
  73  *   i2400m_notif_submit
  74  */
  75 #include <linux/usb.h>
  76 #include <linux/gfp.h>
  77 #include "i2400m-usb.h"
  78 
  79 
  80 #define D_SUBMODULE fw
  81 #include "usb-debug-levels.h"
  82 
  83 
  84 /*
  85  * Synchronous write to the device
  86  *
  87  * Takes care of updating EDC counts and thus, handle device errors.
  88  */
  89 static
  90 ssize_t i2400mu_tx_bulk_out(struct i2400mu *i2400mu, void *buf, size_t buf_size)
  91 {
  92         int result;
  93         struct device *dev = &i2400mu->usb_iface->dev;
  94         int len;
  95         struct usb_endpoint_descriptor *epd;
  96         int pipe, do_autopm = 1;
  97 
  98         result = usb_autopm_get_interface(i2400mu->usb_iface);
  99         if (result < 0) {
 100                 dev_err(dev, "BM-CMD: can't get autopm: %d\n", result);
 101                 do_autopm = 0;
 102         }
 103         epd = usb_get_epd(i2400mu->usb_iface, i2400mu->endpoint_cfg.bulk_out);
 104         pipe = usb_sndbulkpipe(i2400mu->usb_dev, epd->bEndpointAddress);
 105 retry:
 106         result = usb_bulk_msg(i2400mu->usb_dev, pipe, buf, buf_size, &len, 200);
 107         switch (result) {
 108         case 0:
 109                 if (len != buf_size) {
 110                         dev_err(dev, "BM-CMD: short write (%u B vs %zu "
 111                                 "expected)\n", len, buf_size);
 112                         result = -EIO;
 113                         break;
 114                 }
 115                 result = len;
 116                 break;
 117         case -EPIPE:
 118                 /*
 119                  * Stall -- maybe the device is choking with our
 120                  * requests. Clear it and give it some time. If they
 121                  * happen to often, it might be another symptom, so we
 122                  * reset.
 123                  *
 124                  * No error handling for usb_clear_halt(0; if it
 125                  * works, the retry works; if it fails, this switch
 126                  * does the error handling for us.
 127                  */
 128                 if (edc_inc(&i2400mu->urb_edc,
 129                             10 * EDC_MAX_ERRORS, EDC_ERROR_TIMEFRAME)) {
 130                         dev_err(dev, "BM-CMD: too many stalls in "
 131                                 "URB; resetting device\n");
 132                         usb_queue_reset_device(i2400mu->usb_iface);
 133                 } else {
 134                         usb_clear_halt(i2400mu->usb_dev, pipe);
 135                         msleep(10);     /* give the device some time */
 136                         goto retry;
 137                 }
 138                 /* fall through */
 139         case -EINVAL:                   /* while removing driver */
 140         case -ENODEV:                   /* dev disconnect ... */
 141         case -ENOENT:                   /* just ignore it */
 142         case -ESHUTDOWN:                /* and exit */
 143         case -ECONNRESET:
 144                 result = -ESHUTDOWN;
 145                 break;
 146         case -ETIMEDOUT:                        /* bah... */
 147                 break;
 148         default:                                /* any other? */
 149                 if (edc_inc(&i2400mu->urb_edc,
 150                             EDC_MAX_ERRORS, EDC_ERROR_TIMEFRAME)) {
 151                                 dev_err(dev, "BM-CMD: maximum errors in "
 152                                         "URB exceeded; resetting device\n");
 153                                 usb_queue_reset_device(i2400mu->usb_iface);
 154                                 result = -ENODEV;
 155                                 break;
 156                 }
 157                 dev_err(dev, "BM-CMD: URB error %d, retrying\n",
 158                         result);
 159                 goto retry;
 160         }
 161         if (do_autopm)
 162                 usb_autopm_put_interface(i2400mu->usb_iface);
 163         return result;
 164 }
 165 
 166 
 167 /*
 168  * Send a boot-mode command over the bulk-out pipe
 169  *
 170  * Command can be a raw command, which requires no preparation (and
 171  * which might not even be following the command format). Checks that
 172  * the right amount of data was transferred.
 173  *
 174  * To satisfy USB requirements (no onstack, vmalloc or in data segment
 175  * buffers), we copy the command to i2400m->bm_cmd_buf and send it from
 176  * there.
 177  *
 178  * @flags: pass thru from i2400m_bm_cmd()
 179  * @return: cmd_size if ok, < 0 errno code on error.
 180  */
 181 ssize_t i2400mu_bus_bm_cmd_send(struct i2400m *i2400m,
 182                                 const struct i2400m_bootrom_header *_cmd,
 183                                 size_t cmd_size, int flags)
 184 {
 185         ssize_t result;
 186         struct device *dev = i2400m_dev(i2400m);
 187         struct i2400mu *i2400mu = container_of(i2400m, struct i2400mu, i2400m);
 188         int opcode = _cmd == NULL ? -1 : i2400m_brh_get_opcode(_cmd);
 189         struct i2400m_bootrom_header *cmd;
 190         size_t cmd_size_a = ALIGN(cmd_size, 16);        /* USB restriction */
 191 
 192         d_fnstart(8, dev, "(i2400m %p cmd %p size %zu)\n",
 193                   i2400m, _cmd, cmd_size);
 194         result = -E2BIG;
 195         if (cmd_size > I2400M_BM_CMD_BUF_SIZE)
 196                 goto error_too_big;
 197         if (_cmd != i2400m->bm_cmd_buf)
 198                 memmove(i2400m->bm_cmd_buf, _cmd, cmd_size);
 199         cmd = i2400m->bm_cmd_buf;
 200         if (cmd_size_a > cmd_size)                      /* Zero pad space */
 201                 memset(i2400m->bm_cmd_buf + cmd_size, 0, cmd_size_a - cmd_size);
 202         if ((flags & I2400M_BM_CMD_RAW) == 0) {
 203                 if (WARN_ON(i2400m_brh_get_response_required(cmd) == 0))
 204                         dev_warn(dev, "SW BUG: response_required == 0\n");
 205                 i2400m_bm_cmd_prepare(cmd);
 206         }
 207         result = i2400mu_tx_bulk_out(i2400mu, i2400m->bm_cmd_buf, cmd_size);
 208         if (result < 0) {
 209                 dev_err(dev, "boot-mode cmd %d: cannot send: %zd\n",
 210                         opcode, result);
 211                 goto error_cmd_send;
 212         }
 213         if (result != cmd_size) {               /* all was transferred? */
 214                 dev_err(dev, "boot-mode cmd %d: incomplete transfer "
 215                         "(%zd vs %zu submitted)\n",  opcode, result, cmd_size);
 216                 result = -EIO;
 217                 goto error_cmd_size;
 218         }
 219 error_cmd_size:
 220 error_cmd_send:
 221 error_too_big:
 222         d_fnend(8, dev, "(i2400m %p cmd %p size %zu) = %zd\n",
 223                 i2400m, _cmd, cmd_size, result);
 224         return result;
 225 }
 226 
 227 
 228 static
 229 void __i2400mu_bm_notif_cb(struct urb *urb)
 230 {
 231         complete(urb->context);
 232 }
 233 
 234 
 235 /*
 236  * submit a read to the notification endpoint
 237  *
 238  * @i2400m: device descriptor
 239  * @urb: urb to use
 240  * @completion: completion variable to complete when done
 241  *
 242  * Data is always read to i2400m->bm_ack_buf
 243  */
 244 static
 245 int i2400mu_notif_submit(struct i2400mu *i2400mu, struct urb *urb,
 246                          struct completion *completion)
 247 {
 248         struct i2400m *i2400m = &i2400mu->i2400m;
 249         struct usb_endpoint_descriptor *epd;
 250         int pipe;
 251 
 252         epd = usb_get_epd(i2400mu->usb_iface,
 253                           i2400mu->endpoint_cfg.notification);
 254         pipe = usb_rcvintpipe(i2400mu->usb_dev, epd->bEndpointAddress);
 255         usb_fill_int_urb(urb, i2400mu->usb_dev, pipe,
 256                          i2400m->bm_ack_buf, I2400M_BM_ACK_BUF_SIZE,
 257                          __i2400mu_bm_notif_cb, completion,
 258                          epd->bInterval);
 259         return usb_submit_urb(urb, GFP_KERNEL);
 260 }
 261 
 262 
 263 /*
 264  * Read an ack from  the notification endpoint
 265  *
 266  * @i2400m:
 267  * @_ack: pointer to where to store the read data
 268  * @ack_size: how many bytes we should read
 269  *
 270  * Returns: < 0 errno code on error; otherwise, amount of received bytes.
 271  *
 272  * Submits a notification read, appends the read data to the given ack
 273  * buffer and then repeats (until @ack_size bytes have been
 274  * received).
 275  */
 276 ssize_t i2400mu_bus_bm_wait_for_ack(struct i2400m *i2400m,
 277                                     struct i2400m_bootrom_header *_ack,
 278                                     size_t ack_size)
 279 {
 280         ssize_t result = -ENOMEM;
 281         struct device *dev = i2400m_dev(i2400m);
 282         struct i2400mu *i2400mu = container_of(i2400m, struct i2400mu, i2400m);
 283         struct urb notif_urb;
 284         void *ack = _ack;
 285         size_t offset, len;
 286         long val;
 287         int do_autopm = 1;
 288         DECLARE_COMPLETION_ONSTACK(notif_completion);
 289 
 290         d_fnstart(8, dev, "(i2400m %p ack %p size %zu)\n",
 291                   i2400m, ack, ack_size);
 292         BUG_ON(_ack == i2400m->bm_ack_buf);
 293         result = usb_autopm_get_interface(i2400mu->usb_iface);
 294         if (result < 0) {
 295                 dev_err(dev, "BM-ACK: can't get autopm: %d\n", (int) result);
 296                 do_autopm = 0;
 297         }
 298         usb_init_urb(&notif_urb);       /* ready notifications */
 299         usb_get_urb(&notif_urb);
 300         offset = 0;
 301         while (offset < ack_size) {
 302                 init_completion(&notif_completion);
 303                 result = i2400mu_notif_submit(i2400mu, &notif_urb,
 304                                               &notif_completion);
 305                 if (result < 0)
 306                         goto error_notif_urb_submit;
 307                 val = wait_for_completion_interruptible_timeout(
 308                         &notif_completion, HZ);
 309                 if (val == 0) {
 310                         result = -ETIMEDOUT;
 311                         usb_kill_urb(&notif_urb);       /* Timedout */
 312                         goto error_notif_wait;
 313                 }
 314                 if (val == -ERESTARTSYS) {
 315                         result = -EINTR;                /* Interrupted */
 316                         usb_kill_urb(&notif_urb);
 317                         goto error_notif_wait;
 318                 }
 319                 result = notif_urb.status;              /* How was the ack? */
 320                 switch (result) {
 321                 case 0:
 322                         break;
 323                 case -EINVAL:                   /* while removing driver */
 324                 case -ENODEV:                   /* dev disconnect ... */
 325                 case -ENOENT:                   /* just ignore it */
 326                 case -ESHUTDOWN:                /* and exit */
 327                 case -ECONNRESET:
 328                         result = -ESHUTDOWN;
 329                         goto error_dev_gone;
 330                 default:                                /* any other? */
 331                         usb_kill_urb(&notif_urb);       /* Timedout */
 332                         if (edc_inc(&i2400mu->urb_edc,
 333                                     EDC_MAX_ERRORS, EDC_ERROR_TIMEFRAME))
 334                                 goto error_exceeded;
 335                         dev_err(dev, "BM-ACK: URB error %d, "
 336                                 "retrying\n", notif_urb.status);
 337                         continue;       /* retry */
 338                 }
 339                 if (notif_urb.actual_length == 0) {
 340                         d_printf(6, dev, "ZLP received, retrying\n");
 341                         continue;
 342                 }
 343                 /* Got data, append it to the buffer */
 344                 len = min(ack_size - offset, (size_t) notif_urb.actual_length);
 345                 memcpy(ack + offset, i2400m->bm_ack_buf, len);
 346                 offset += len;
 347         }
 348         result = offset;
 349 error_notif_urb_submit:
 350 error_notif_wait:
 351 error_dev_gone:
 352 out:
 353         if (do_autopm)
 354                 usb_autopm_put_interface(i2400mu->usb_iface);
 355         d_fnend(8, dev, "(i2400m %p ack %p size %zu) = %ld\n",
 356                 i2400m, ack, ack_size, (long) result);
 357         usb_put_urb(&notif_urb);
 358         return result;
 359 
 360 error_exceeded:
 361         dev_err(dev, "bm: maximum errors in notification URB exceeded; "
 362                 "resetting device\n");
 363         usb_queue_reset_device(i2400mu->usb_iface);
 364         goto out;
 365 }

/* [<][>][^][v][top][bottom][index][help] */