Logo Search packages:      
Sourcecode: linux-fsl-imx51 version File versions  Download package

stmp_updater.c

/*
 * Freescale STMP378X UUT driver
 *
 * Copyright 2008-2009 Freescale Semiconductor, Inc. All Rights Reserved.
 * Copyright 2008-2009 Embedded Alley Solutions, Inc All Rights Reserved.
 */

/*
 * The code contained herein is licensed under the GNU General Public
 * License. You may obtain a copy of the GNU General Public License
 * Version 2 or later at the following locations:
 *
 * http://www.opensource.org/licenses/gpl-license.html
 * http://www.gnu.org/copyleft/gpl.html
 */

static u64 get_be64(u8 *buf)
{
      return ((u64)get_unaligned_be32(buf) << 32) | get_unaligned_be32(buf + 4);
}

static int utp_init(struct fsg_dev *fsg)
{
      init_waitqueue_head(&utp_context.wq);
      INIT_LIST_HEAD(&utp_context.read);
      INIT_LIST_HEAD(&utp_context.write);
      mutex_init(&utp_context.lock);
      utp_context.buffer = vmalloc(0x10000);
      if (!utp_context.buffer)
            return -EIO;
      utp_context.utp_version = 0x1ull;
      fsg->utp = &utp_context;
      return misc_register(&utp_dev);
}

static void utp_exit(struct fsg_dev *fsg)
{
      vfree(utp_context.buffer);
      misc_deregister(&utp_dev);
}

static struct utp_user_data *utp_user_data_alloc(size_t size)
{
      struct utp_user_data *uud;

      uud = kzalloc(size + sizeof(*uud), GFP_KERNEL);
      if (!uud)
            return uud;
      uud->data.size = size + sizeof(uud->data);
      INIT_LIST_HEAD(&uud->link);
      return uud;
}

static void utp_user_data_free(struct utp_user_data *uud)
{
      list_del(&uud->link);
      kfree(uud);
}

#define WAIT_ACTIVITY(queue) \
 wait_event_interruptible(utp_context.wq, !list_empty(&utp_context.queue))

static ssize_t utp_file_read(struct file *file,
                       char __user *buf,
                       size_t size,
                       loff_t *off)
{
      struct utp_user_data *uud;
      size_t size_to_put;
      int free = 0;

      WAIT_ACTIVITY(read);

      mutex_lock(&utp_context.lock);
      uud = list_first_entry(&utp_context.read, struct utp_user_data, link);
      mutex_unlock(&utp_context.lock);
      size_to_put = uud->data.size;
      if (size >= size_to_put)
            free = !0;
      if (copy_to_user(buf, &uud->data, size_to_put))
            return -EACCES;
      if (free)
            utp_user_data_free(uud);
      else {
            pr_info("sizeof = %d, size = %d\n",
                  sizeof(uud->data),
                  uud->data.size);

            pr_err("Will not free utp_user_data, because buffer size = %d,"
                  "need to put %d\n", size, size_to_put);
      }

      return size_to_put;
}


static ssize_t utp_file_write(struct file *file, const char __user *buf,
                        size_t size, loff_t *off)
{
      struct utp_user_data *uud;

      if (size < sizeof(uud->data))
            return -EINVAL;
      uud = utp_user_data_alloc(size);
      if (copy_from_user(&uud->data, buf, size))
            return -EACCES;
      mutex_lock(&utp_context.lock);
      list_add_tail(&uud->link, &utp_context.write);
      wake_up(&utp_context.wq);
      mutex_unlock(&utp_context.lock);
      return size;
}

static int utp_get_sense(struct fsg_dev *fsg)
{
      if (UTP_CTX(fsg)->processed == 0)
            return -1;

      UTP_CTX(fsg)->processed = 0;
      return 0;
}

static int utp_do_read(struct fsg_dev *fsg, void *data, size_t size)
{
      struct fsg_buffhd *bh;
      int               rc;
      u32               amount_left;
      unsigned int            amount;

      /* Get the starting Logical Block Address and check that it's
       * not too big */

      amount_left = size;
      if (unlikely(amount_left == 0))
            return -EIO;            /* No default reply*/

      pr_debug("%s: sending %d\n", __func__, size);
      for (;;) {
            /* Figure out how much we need to read:
             * Try to read the remaining amount.
             * But don't read more than the buffer size.
             * And don't try to read past the end of the file.
             * Finally, if we're not at a page boundary, don't read past
             *    the next page.
             * If this means reading 0 then we were asked to read past
             *    the end of file. */
            amount = min((unsigned int) amount_left, mod_data.buflen);

            /* Wait for the next buffer to become available */
            bh = fsg->next_buffhd_to_fill;
            while (bh->state != BUF_STATE_EMPTY) {
                  rc = sleep_thread(fsg);
                  if (rc)
                        return rc;
            }

            /* If we were asked to read past the end of file,
             * end with an empty buffer. */
            if (amount == 0) {
                  bh->inreq->length = 0;
                  bh->state = BUF_STATE_FULL;
                  break;
            }

            /* Perform the read */
            pr_info("Copied to %p, %d bytes started from %d\n",
                        bh->buf, amount, size - amount_left);
            memcpy(bh->buf, data + size - amount_left, amount);
            amount_left  -= amount;
            fsg->residue -= amount;

            bh->inreq->length = amount;
            bh->state = BUF_STATE_FULL;

            /* Send this buffer and go read some more */
            bh->inreq->zero = 0;

            start_transfer(fsg, fsg->bulk_in, bh->inreq,
                        &bh->inreq_busy, &bh->state);

            fsg->next_buffhd_to_fill = bh->next;

            if (amount_left <= 0)
                  break;
      }

      return size - amount_left;
}

static int utp_do_write(struct fsg_dev *fsg, void *data, size_t size)
{
      struct fsg_buffhd *bh;
      int               get_some_more;
      u32               amount_left_to_req, amount_left_to_write;
      unsigned int            amount;
      int               rc;
      loff_t                  offset;

      /* Carry out the file writes */
      get_some_more = 1;
      amount_left_to_req = amount_left_to_write = size;

      if (unlikely(amount_left_to_write == 0))
            return -EIO;

      offset = 0;
      while (amount_left_to_write > 0) {

            /* Queue a request for more data from the host */
            bh = fsg->next_buffhd_to_fill;
            if (bh->state == BUF_STATE_EMPTY && get_some_more) {

                  /* Figure out how much we want to get:
                   * Try to get the remaining amount.
                   * But don't get more than the buffer size.
                   * And don't try to go past the end of the file.
                   * If we're not at a page boundary,
                   *    don't go past the next page.
                   * If this means getting 0, then we were asked
                   *    to write past the end of file.
                   * Finally, round down to a block boundary. */
                  amount = min(amount_left_to_req, mod_data.buflen);

                  if (amount == 0) {
                        get_some_more = 0;
                        /* cry now */
                        continue;
                  }

                  /* Get the next buffer */
                  amount_left_to_req -= amount;
                  if (amount_left_to_req == 0)
                        get_some_more = 0;

                  /* amount is always divisible by 512, hence by
                   * the bulk-out maxpacket size */
                  bh->outreq->length = bh->bulk_out_intended_length =
                              amount;
                  bh->outreq->short_not_ok = 1;
                  start_transfer(fsg, fsg->bulk_out, bh->outreq,
                              &bh->outreq_busy, &bh->state);
                  fsg->next_buffhd_to_fill = bh->next;
                  continue;
            }

            /* Write the received data to the backing file */
            bh = fsg->next_buffhd_to_drain;
            if (bh->state == BUF_STATE_EMPTY && !get_some_more)
                  break;                  /* We stopped early */
            if (bh->state == BUF_STATE_FULL) {
                  smp_rmb();
                  fsg->next_buffhd_to_drain = bh->next;
                  bh->state = BUF_STATE_EMPTY;

                  /* Did something go wrong with the transfer? */
                  if (bh->outreq->status != 0)
                        /* cry again, COMMUNICATION_FAILURE */
                        break;

                  amount = bh->outreq->actual;

                  /* Perform the write */
                  memcpy(data + offset, bh->buf, amount);

                  offset += amount;
                  if (signal_pending(current))
                        return -EINTR;          /* Interrupted!*/
                  amount_left_to_write -= amount;
                  fsg->residue -= amount;

                  /* Did the host decide to stop early? */
                  if (bh->outreq->actual != bh->outreq->length) {
                        fsg->short_packet_received = 1;
                        break;
                  }
                  continue;
            }

            /* Wait for something to happen */
            rc = sleep_thread(fsg);
            if (rc)
                  return rc;
      }

      return -EIO;
}

static inline void utp_set_sense(struct fsg_dev *fsg, u16 code, u64 reply)
{
      UTP_CTX(fsg)->processed = true;
      UTP_CTX(fsg)->sdinfo = reply & 0xFFFFFFFF;
      UTP_CTX(fsg)->sdinfo_h = (reply >> 32) & 0xFFFFFFFF;
      UTP_CTX(fsg)->sd = (UTP_SENSE_KEY << 16) | code;
}

static void utp_poll(struct fsg_dev *fsg)
{
      struct utp_context *ctx = UTP_CTX(fsg);
      struct utp_user_data *uud = NULL;

      mutex_lock(&ctx->lock);
      if (!list_empty(&ctx->write))
            uud = list_first_entry(&ctx->write, struct utp_user_data, link);
      mutex_unlock(&ctx->lock);

      if (uud) {
            if (uud->data.flags & UTP_FLAG_STATUS) {
                  pr_debug("%s: exit with status %d\n", __func__,
                        uud->data.status);
                  UTP_SS_EXIT(fsg, uud->data.status);
            } else {
                  pr_debug("%s: pass\n", __func__);
                  UTP_SS_PASS(fsg);
            }
            utp_user_data_free(uud);
      } else {
            pr_debug("%s: still busy...\n", __func__);
            UTP_SS_BUSY(fsg, --ctx->counter);
      }
}

static int utp_exec(struct fsg_dev *fsg,
                char *command,
                int cmdsize,
                unsigned long long payload)
{
      struct utp_user_data *uud = NULL, *uud2r;
      struct utp_context *ctx = UTP_CTX(fsg);

      uud2r = utp_user_data_alloc(cmdsize + 1);
      uud2r->data.flags = UTP_FLAG_COMMAND;
      uud2r->data.payload = payload;
      strncpy(uud2r->data.command, command, cmdsize);

      mutex_lock(&ctx->lock);
      list_add_tail(&uud2r->link, &ctx->read);
      mutex_unlock(&ctx->lock);
      wake_up(&ctx->wq);

      if (command[0] == '!')  /* there will be no response */
            return 0;

      WAIT_ACTIVITY(write);

      mutex_lock(&ctx->lock);
      if (!list_empty(&ctx->write)) {
            uud = list_first_entry(&ctx->write, struct utp_user_data, link);
#ifdef DEBUG
            pr_info("UUD:\n\tFlags = %02X\n", uud->data.flags);
            if (uud->data.flags & UTP_FLAG_DATA) {
                  pr_info("\tbufsize = %d\n", uud->data.bufsize);
                  print_hex_dump(KERN_DEBUG, "\t", DUMP_PREFIX_NONE,
                        16, 2, uud->data.data, uud->data.bufsize, true);
            }
            if (uud->data.flags & UTP_FLAG_REPORT_BUSY)
                  pr_info("\tBUSY\n");
#endif
      }
      mutex_unlock(&ctx->lock);

      if (uud->data.flags & UTP_FLAG_DATA) {
            memcpy(ctx->buffer, uud->data.data, uud->data.bufsize);
            UTP_SS_SIZE(fsg, uud->data.bufsize);
            utp_user_data_free(uud);
            return 0;
      }

      if (uud->data.flags & UTP_FLAG_REPORT_BUSY) {
            utp_user_data_free(uud);
            ctx->counter = 0xFFFF;
            UTP_SS_BUSY(fsg, ctx->counter);
            return 0;
      }

      utp_user_data_free(uud);
      UTP_SS_PASS(fsg);

      return -1;
}

static int utp_send_status(struct fsg_dev *fsg)
{
      struct fsg_buffhd *bh;
      u8                status = USB_STATUS_PASS;
      struct bulk_cs_wrap     *csw;
      int               rc;

      /* Wait for the next buffer to become available */
      bh = fsg->next_buffhd_to_fill;
      while (bh->state != BUF_STATE_EMPTY) {
            rc = sleep_thread(fsg);
            if (rc)
                  return rc;
      }

      if (fsg->phase_error) {
            DBG(fsg, "sending phase-error status\n");
            status = USB_STATUS_PHASE_ERROR;

      } else if ((UTP_CTX(fsg)->sd & 0xFFFF) != UTP_REPLY_PASS) {
            status = USB_STATUS_FAIL;
      }

      csw = bh->buf;

      /* Store and send the Bulk-only CSW */
      csw->Signature = __constant_cpu_to_le32(USB_BULK_CS_SIG);
      csw->Tag = fsg->tag;
      csw->Residue = cpu_to_le32(fsg->residue);
      csw->Status = status;

      bh->inreq->length = USB_BULK_CS_WRAP_LEN;
      bh->inreq->zero = 0;
      start_transfer(fsg, fsg->bulk_in, bh->inreq,
                  &bh->inreq_busy, &bh->state);
      fsg->next_buffhd_to_fill = bh->next;
      return 0;
}

static int utp_handle_message(struct fsg_dev *fsg,
                        char *cdb_data,
                        int default_reply)
{
      struct utp_msg *m = (struct utp_msg *)cdb_data;
      void *data = NULL;
      int r;
      struct utp_user_data *uud2r;
      unsigned long long param;
      unsigned long tag;

      if (m->f0 != 0xF0)
            return default_reply;

      tag = get_unaligned_be32((void *)&m->utp_msg_tag);
      param = get_be64((void *)&m->param);
      pr_debug("Type 0x%x, tag 0x%08lx, param %llx\n",
                  m->utp_msg_type, tag, param);

      switch ((enum utp_msg_type)m->utp_msg_type) {

      case UTP_POLL:
            if (get_be64((void *)&m->param) == 1) {
                  pr_debug("%s: version request\n", __func__);
                  UTP_SS_EXIT(fsg, UTP_CTX(fsg)->utp_version);
                  break;
            }
            utp_poll(fsg);
            break;
      case UTP_EXEC:
            pr_debug("%s: EXEC\n", __func__);
            data = kzalloc(fsg->data_size, GFP_KERNEL);
            utp_do_write(fsg, data, fsg->data_size);
            utp_exec(fsg, data, fsg->data_size, param);
            kfree(data);
            break;
      case UTP_GET:
            pr_debug("%s: GET, %d bytes\n", __func__, fsg->data_size);
            r = utp_do_read(fsg, UTP_CTX(fsg)->buffer, fsg->data_size);
            UTP_SS_PASS(fsg);
            break;
      case UTP_PUT:
            pr_debug("%s: PUT, %d bytes\n", __func__, fsg->data_size);
            uud2r = utp_user_data_alloc(fsg->data_size);
            uud2r->data.bufsize = fsg->data_size;
            uud2r->data.flags = UTP_FLAG_DATA;
            utp_do_write(fsg, uud2r->data.data, fsg->data_size);
            /* don't know what will be written */
            mutex_lock(&UTP_CTX(fsg)->lock);
            list_add_tail(&uud2r->link, &UTP_CTX(fsg)->read);
            mutex_unlock(&UTP_CTX(fsg)->lock);
            wake_up(&UTP_CTX(fsg)->wq);
            UTP_SS_PASS(fsg);
            break;
      }

      utp_send_status(fsg);
      return -1;
}


Generated by  Doxygen 1.6.0   Back to index