Logo Search packages:      
Sourcecode: hdparm version File versions  Download package

fwdownload.c

/*
 * Copyright (c) EMC Corporation 2008
 * Copyright (c) Mark Lord 2008
 *
 * You may use/distribute this freely, under the terms of either
 * (your choice) the GNU General Public License version 2,
 * or a BSD style license.
 */
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include <fcntl.h>
#include <errno.h>
#include <sys/ioctl.h>
#include <sys/stat.h>
#include <linux/types.h>
#include <linux/fs.h>
#include <sys/mman.h>

#include "hdparm.h"
#include "sgio.h"

/* glibc-2.2 / linux-2.4 don't have MAP_POPULATE */
#ifndef MAP_POPULATE
#define MAP_POPULATE    0
#endif

extern int verbose;

/* Download a firmware segment to the drive */
static int send_firmware (int fd, unsigned int xfer_mode, unsigned int offset,
                    const void *data, unsigned int bytecount)
{
      int err = 0;
      struct hdio_taskfile *r;
      unsigned int blockcount = bytecount / 512;
      unsigned int timeout_secs = 20;
      __u64 lba;

      lba = ((offset / 512) << 8) | ((blockcount >> 8) & 0xff);
      r = malloc(sizeof(struct hdio_taskfile) + bytecount);
      if (!r) {
            if (xfer_mode == 3) { putchar('\n'); fflush(stdout); }
            err = errno;
            perror("malloc()");
            return err;
      }
      init_hdio_taskfile(r, ATA_OP_DOWNLOAD_MICROCODE, RW_WRITE, LBA28_OK, lba, blockcount & 0xff, bytecount);

      r->lob.feat = xfer_mode;
      r->oflags.lob.feat  = 1;
      r->iflags.lob.nsect = 1;

      if (data && bytecount)
            memcpy(r->data, data, bytecount);

      if (do_taskfile_cmd(fd, r, timeout_secs)) {
            err = errno;
            if (xfer_mode == 3) { putchar('\n'); fflush(stdout); }
            perror("FAILED");
      } else {
            if (xfer_mode == 3) {
                  if (!verbose) { putchar('.'); fflush(stdout); }
                  switch (r->lob.nsect) {
                        case 1:     // drive wants more data
                        case 2:     // drive thinks it is all done
                              err = - r->lob.nsect;
                              break;
                        default: // no status indication
                              err = 0;
                              break;
                  }
            }
      }
      free(r);
      return err;
}

int fwdownload (int fd, __u16 *id, const char *fwpath, int xfer_mode)
{
      int fwfd, err = 0;
      struct stat st;
      const char *fw = NULL;
      const int max_bytes = 0xffff * 512;
      int xfer_min = 1, xfer_max = 0xffff, xfer_size;
      ssize_t offset;

      if ((fwfd = open(fwpath, O_RDONLY)) == -1 || fstat(fwfd, &st) == -1) {
            err = errno;
            perror(fwpath);
            return err;
      }

      if (!S_ISREG(st.st_mode)) {
            fprintf(stderr, "%s: not a regular file\n", fwpath);
            err = EINVAL;
            goto done;
      }

      if (st.st_size > max_bytes) {
            fprintf(stderr, "%s: file size too large, max=%u bytes\n", fwpath, max_bytes);
            err = EINVAL;
            goto done;
      }

      if (st.st_size == 0 || st.st_size % 512) {
            fprintf(stderr, "%s: file size (%llu) not a multiple of 512\n",
                  fwpath, (unsigned long long) st.st_size);
            err = EINVAL;
            goto done;
      }

      if (verbose)
            printf("%s: %llu bytes\n", fwpath, (unsigned long long)st.st_size);

      fw = mmap(NULL, st.st_size, PROT_READ, MAP_SHARED|MAP_POPULATE|MAP_LOCKED, fwfd, 0);
      if (fw == MAP_FAILED) {
            err = errno;
            perror(fwpath);
            goto done;
      }

      /* Check drive for fwdownload support */
      if (!((id[83] & 1) && (id[86] & 1))) {
            fprintf(stderr, "DOWNLOAD_MICROCODE: not supported by device\n");
            err = ENOTSUP;
            goto done;
      }
      if (xfer_mode == 0) {
            if ((id[119] & 0x10) && (id[120] & 0x10))
                  xfer_mode = 3;
            else
                  xfer_mode = 7;
      }

      if (xfer_mode == 3 || xfer_mode == 30) {
            /* the newer, segmented transfer mode */
            xfer_min = id[234];
            if (xfer_min == 0 || xfer_min == 0xffff)
                  xfer_min = 1;
            xfer_max = id[235];
            if (xfer_max == 0 || xfer_max == 0xffff)
                  xfer_max = xfer_min;
      }

      if (xfer_mode == 30) {  // mode-3, using xfer_max
            xfer_mode = 3;
            xfer_size = xfer_max;
      } else if (xfer_mode == 3) {
            xfer_size = xfer_min;
      } else {
            xfer_size = st.st_size / 512;
            if (xfer_size > 0xffff) {
                  fprintf(stderr, "Error: file size (%llu) too large for mode7 transfers\n", (__u64)st.st_size);
                  err = EINVAL;
                  goto done;
            }
      }

      xfer_size *= 512; /* bytecount */

      fprintf(stderr, "%s: xfer_mode=%d min=%u max=%u size=%u\n",
            __func__, xfer_mode, xfer_min, xfer_max, xfer_size);

      /* Perform the fwdownload, in segments if appropriate */
      for (offset = 0; !err && offset < st.st_size;) {
            if ((offset + xfer_size) >= st.st_size)
                  xfer_size = st.st_size - offset;
            err = send_firmware(fd, xfer_mode, offset, fw + offset, xfer_size);
            offset += xfer_size;

            if (err == -2) {  // drive has had enough?
                  if (offset >= st.st_size) { // transfer complete?
                        err = 0;
                  } else {
                        if (xfer_mode == 3) { putchar('\n'); fflush(stdout); }
                        fprintf(stderr, "Error: drive completed transfer at %llu/%llu bytes\n",
                                    (unsigned long long)offset, (unsigned long long)st.st_size);
                        err = EIO;
                  }
            } else if (err == -1) {
                  if (offset >= st.st_size) { // no more data?
#if 0
                        /* Try sending an empty segment before complaining */
                        err = send_firmware(fd, xfer_mode, offset, NULL, 0);
                        if (err == 0 || err == -2) {
                              err = 0;
                              break;
                        }
#endif
                        if (xfer_mode == 3) { putchar('\n'); fflush(stdout); }
                        fprintf(stderr, "Error: drive expects more data than provided,\n");
                        fprintf(stderr, "but the transfer may have worked regardless.\n");
                        err = EIO;
                  } else {
                        err = 0;
                  }
            }
      }
      if (!err)
            printf(" Done.\n");
done:
      munlock(fw, st.st_size);
      close (fwfd);
      return err;
}

Generated by  Doxygen 1.6.0   Back to index