X-Git-Url: http://git.onelab.eu/?a=blobdiff_plain;ds=sidebyside;f=drivers%2Fscsi%2Fscsi_transport_spi.c;h=7ee95eb83ddaa52c5fe1aa50a52642b150293eba;hb=987b0145d94eecf292d8b301228356f44611ab7c;hp=f01133b4c580bfb307d8510e3740bff05120de90;hpb=5167311cae6aa3a5ff5afd39f88c32a435c969ef;p=linux-2.6.git diff --git a/drivers/scsi/scsi_transport_spi.c b/drivers/scsi/scsi_transport_spi.c index f01133b4c..7ee95eb83 100644 --- a/drivers/scsi/scsi_transport_spi.c +++ b/drivers/scsi/scsi_transport_spi.c @@ -2,6 +2,7 @@ * Parallel SCSI (SPI) transport specific attributes exported to sysfs. * * Copyright (c) 2003 Silicon Graphics, Inc. All rights reserved. + * Copyright (c) 2004, 2005 James Bottomley * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -17,38 +18,37 @@ * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ -#include +#include +#include #include -#include -#include -#include -#include +#include #include -#include -#include +#include +#include #include #include "scsi_priv.h" #include #include -#include +#include +#include #include #include -#define SPI_PRINTK(x, l, f, a...) dev_printk(l, &(x)->dev, f , ##a) - -static void transport_class_release(struct class_device *class_dev); -static void host_class_release(struct class_device *class_dev); - -#define SPI_NUM_ATTRS 10 /* increase this if you add attributes */ +#define SPI_NUM_ATTRS 14 /* increase this if you add attributes */ #define SPI_OTHER_ATTRS 1 /* Increase this if you add "always * on" attributes */ #define SPI_HOST_ATTRS 1 #define SPI_MAX_ECHO_BUFFER_SIZE 4096 +#define DV_LOOPS 3 +#define DV_TIMEOUT (10*HZ) +#define DV_RETRIES 3 /* should only need at most + * two cc/ua clears */ + /* Private data accessors (keep these out of the header file) */ #define spi_dv_pending(x) (((struct spi_transport_attrs *)&(x)->starget_data)->dv_pending) -#define spi_dv_sem(x) (((struct spi_transport_attrs *)&(x)->starget_data)->dv_sem) +#define spi_dv_mutex(x) (((struct spi_transport_attrs *)&(x)->starget_data)->dv_mutex) struct spi_internal { struct scsi_transport_template t; @@ -64,27 +64,74 @@ struct spi_internal { #define to_spi_internal(tmpl) container_of(tmpl, struct spi_internal, t) -static const char *const ppr_to_ns[] = { +static const int ppr_to_ps[] = { /* The PPR values 0-6 are reserved, fill them in when * the committee defines them */ - NULL, /* 0x00 */ - NULL, /* 0x01 */ - NULL, /* 0x02 */ - NULL, /* 0x03 */ - NULL, /* 0x04 */ - NULL, /* 0x05 */ - NULL, /* 0x06 */ - "3.125", /* 0x07 */ - "6.25", /* 0x08 */ - "12.5", /* 0x09 */ - "25", /* 0x0a */ - "30.3", /* 0x0b */ - "50", /* 0x0c */ + -1, /* 0x00 */ + -1, /* 0x01 */ + -1, /* 0x02 */ + -1, /* 0x03 */ + -1, /* 0x04 */ + -1, /* 0x05 */ + -1, /* 0x06 */ + 3125, /* 0x07 */ + 6250, /* 0x08 */ + 12500, /* 0x09 */ + 25000, /* 0x0a */ + 30300, /* 0x0b */ + 50000, /* 0x0c */ }; /* The PPR values at which you calculate the period in ns by multiplying * by 4 */ #define SPI_STATIC_PPR 0x0c +static int sprint_frac(char *dest, int value, int denom) +{ + int frac = value % denom; + int result = sprintf(dest, "%d", value / denom); + + if (frac == 0) + return result; + dest[result++] = '.'; + + do { + denom /= 10; + sprintf(dest + result, "%d", frac / denom); + result++; + frac %= denom; + } while (frac); + + dest[result++] = '\0'; + return result; +} + +static int spi_execute(struct scsi_device *sdev, const void *cmd, + enum dma_data_direction dir, + void *buffer, unsigned bufflen, + struct scsi_sense_hdr *sshdr) +{ + int i, result; + unsigned char sense[SCSI_SENSE_BUFFERSIZE]; + + for(i = 0; i < DV_RETRIES; i++) { + result = scsi_execute(sdev, cmd, dir, buffer, bufflen, + sense, DV_TIMEOUT, /* retries */ 1, + REQ_FAILFAST); + if (result & DRIVER_SENSE) { + struct scsi_sense_hdr sshdr_tmp; + if (!sshdr) + sshdr = &sshdr_tmp; + + if (scsi_normalize_sense(sense, sizeof(*sense), + sshdr) + && sshdr->sense_key == UNIT_ATTENTION) + continue; + } + break; + } + return result; +} + static struct { enum spi_signal_type value; char *name; @@ -118,40 +165,46 @@ static inline enum spi_signal_type spi_signal_to_value(const char *name) return SPI_SIGNAL_UNKNOWN; } +static int spi_host_setup(struct transport_container *tc, struct device *dev, + struct class_device *cdev) +{ + struct Scsi_Host *shost = dev_to_shost(dev); -struct class spi_transport_class = { - .name = "spi_transport", - .release = transport_class_release, -}; - -struct class spi_host_class = { - .name = "spi_host", - .release = host_class_release, -}; + spi_signalling(shost) = SPI_SIGNAL_UNKNOWN; -static __init int spi_transport_init(void) -{ - int error = class_register(&spi_host_class); - if (error) - return error; - return class_register(&spi_transport_class); + return 0; } -static void __exit spi_transport_exit(void) -{ - class_unregister(&spi_transport_class); - class_unregister(&spi_host_class); -} +static DECLARE_TRANSPORT_CLASS(spi_host_class, + "spi_host", + spi_host_setup, + NULL, + NULL); -static int spi_setup_host_attrs(struct Scsi_Host *shost) +static int spi_host_match(struct attribute_container *cont, + struct device *dev) { - spi_signalling(shost) = SPI_SIGNAL_UNKNOWN; + struct Scsi_Host *shost; + struct spi_internal *i; - return 0; + if (!scsi_is_host_device(dev)) + return 0; + + shost = dev_to_shost(dev); + if (!shost->transportt || shost->transportt->host_attrs.ac.class + != &spi_host_class.class) + return 0; + + i = to_spi_internal(shost->transportt); + + return &i->t.host_attrs.ac == cont; } -static int spi_configure_device(struct scsi_device *sdev) +static int spi_device_configure(struct transport_container *tc, + struct device *dev, + struct class_device *cdev) { + struct scsi_device *sdev = to_scsi_device(dev); struct scsi_target *starget = sdev->sdev_target; /* Populate the target capability fields with the values @@ -167,11 +220,18 @@ static int spi_configure_device(struct scsi_device *sdev) return 0; } -static int spi_setup_transport_attrs(struct scsi_target *starget) +static int spi_setup_transport_attrs(struct transport_container *tc, + struct device *dev, + struct class_device *cdev) { + struct scsi_target *starget = to_scsi_target(dev); + spi_period(starget) = -1; /* illegal value */ + spi_min_period(starget) = 0; spi_offset(starget) = 0; /* async */ + spi_max_offset(starget) = 255; spi_width(starget) = 0; /* narrow */ + spi_max_width(starget) = 1; spi_iu(starget) = 0; /* no IU */ spi_dt(starget) = 0; /* ST */ spi_qas(starget) = 0; @@ -179,23 +239,40 @@ static int spi_setup_transport_attrs(struct scsi_target *starget) spi_rd_strm(starget) = 0; spi_rti(starget) = 0; spi_pcomp_en(starget) = 0; + spi_hold_mcs(starget) = 0; spi_dv_pending(starget) = 0; spi_initial_dv(starget) = 0; - init_MUTEX(&spi_dv_sem(starget)); + mutex_init(&spi_dv_mutex(starget)); return 0; } -static void transport_class_release(struct class_device *class_dev) -{ - struct scsi_target *starget = transport_class_to_starget(class_dev); - put_device(&starget->dev); +#define spi_transport_show_simple(field, format_string) \ + \ +static ssize_t \ +show_spi_transport_##field(struct class_device *cdev, char *buf) \ +{ \ + struct scsi_target *starget = transport_class_to_starget(cdev); \ + struct spi_transport_attrs *tp; \ + \ + tp = (struct spi_transport_attrs *)&starget->starget_data; \ + return snprintf(buf, 20, format_string, tp->field); \ } -static void host_class_release(struct class_device *class_dev) -{ - struct Scsi_Host *shost = transport_class_to_shost(class_dev); - put_device(&shost->shost_gendev); +#define spi_transport_store_simple(field, format_string) \ + \ +static ssize_t \ +store_spi_transport_##field(struct class_device *cdev, const char *buf, \ + size_t count) \ +{ \ + int val; \ + struct scsi_target *starget = transport_class_to_starget(cdev); \ + struct spi_transport_attrs *tp; \ + \ + tp = (struct spi_transport_attrs *)&starget->starget_data; \ + val = simple_strtoul(buf, NULL, 0); \ + tp->field = val; \ + return count; \ } #define spi_transport_show_function(field, format_string) \ @@ -224,6 +301,25 @@ store_spi_transport_##field(struct class_device *cdev, const char *buf, \ struct spi_internal *i = to_spi_internal(shost->transportt); \ \ val = simple_strtoul(buf, NULL, 0); \ + i->f->set_##field(starget, val); \ + return count; \ +} + +#define spi_transport_store_max(field, format_string) \ +static ssize_t \ +store_spi_transport_##field(struct class_device *cdev, const char *buf, \ + size_t count) \ +{ \ + int val; \ + struct scsi_target *starget = transport_class_to_starget(cdev); \ + struct Scsi_Host *shost = dev_to_shost(starget->dev.parent); \ + struct spi_internal *i = to_spi_internal(shost->transportt); \ + struct spi_transport_attrs *tp \ + = (struct spi_transport_attrs *)&starget->starget_data; \ + \ + val = simple_strtoul(buf, NULL, 0); \ + if (val > tp->max_##field) \ + val = tp->max_##field; \ i->f->set_##field(starget, val); \ return count; \ } @@ -235,9 +331,24 @@ static CLASS_DEVICE_ATTR(field, S_IRUGO | S_IWUSR, \ show_spi_transport_##field, \ store_spi_transport_##field); +#define spi_transport_simple_attr(field, format_string) \ + spi_transport_show_simple(field, format_string) \ + spi_transport_store_simple(field, format_string) \ +static CLASS_DEVICE_ATTR(field, S_IRUGO | S_IWUSR, \ + show_spi_transport_##field, \ + store_spi_transport_##field); + +#define spi_transport_max_attr(field, format_string) \ + spi_transport_show_function(field, format_string) \ + spi_transport_store_max(field, format_string) \ + spi_transport_simple_attr(max_##field, format_string) \ +static CLASS_DEVICE_ATTR(field, S_IRUGO | S_IWUSR, \ + show_spi_transport_##field, \ + store_spi_transport_##field); + /* The Parallel SCSI Tranport Attributes: */ -spi_transport_rd_attr(offset, "%d\n"); -spi_transport_rd_attr(width, "%d\n"); +spi_transport_max_attr(offset, "%d\n"); +spi_transport_max_attr(width, "%d\n"); spi_transport_rd_attr(iu, "%d\n"); spi_transport_rd_attr(dt, "%d\n"); spi_transport_rd_attr(qas, "%d\n"); @@ -245,54 +356,110 @@ spi_transport_rd_attr(wr_flow, "%d\n"); spi_transport_rd_attr(rd_strm, "%d\n"); spi_transport_rd_attr(rti, "%d\n"); spi_transport_rd_attr(pcomp_en, "%d\n"); +spi_transport_rd_attr(hold_mcs, "%d\n"); + +/* we only care about the first child device so we return 1 */ +static int child_iter(struct device *dev, void *data) +{ + struct scsi_device *sdev = to_scsi_device(dev); + + spi_dv_device(sdev); + return 1; +} static ssize_t store_spi_revalidate(struct class_device *cdev, const char *buf, size_t count) { struct scsi_target *starget = transport_class_to_starget(cdev); - /* FIXME: we're relying on an awful lot of device internals - * here. We really need a function to get the first available - * child */ - struct device *dev = container_of(starget->dev.children.next, struct device, node); - struct scsi_device *sdev = to_scsi_device(dev); - spi_dv_device(sdev); + device_for_each_child(&starget->dev, NULL, child_iter); return count; } static CLASS_DEVICE_ATTR(revalidate, S_IWUSR, NULL, store_spi_revalidate); /* Translate the period into ns according to the current spec * for SDTR/PPR messages */ -static ssize_t show_spi_transport_period(struct class_device *cdev, char *buf) - +static int period_to_str(char *buf, int period) { - struct scsi_target *starget = transport_class_to_starget(cdev); - struct Scsi_Host *shost = dev_to_shost(starget->dev.parent); - struct spi_transport_attrs *tp; - const char *str; - struct spi_internal *i = to_spi_internal(shost->transportt); + int len, picosec; + + if (period < 0 || period > 0xff) { + picosec = -1; + } else if (period <= SPI_STATIC_PPR) { + picosec = ppr_to_ps[period]; + } else { + picosec = period * 4000; + } - tp = (struct spi_transport_attrs *)&starget->starget_data; + if (picosec == -1) { + len = sprintf(buf, "reserved"); + } else { + len = sprint_frac(buf, picosec, 1000); + } - if (i->f->get_period) - i->f->get_period(starget); + return len; +} - switch(tp->period) { +static ssize_t +show_spi_transport_period_helper(struct class_device *cdev, char *buf, + int period) +{ + int len = period_to_str(buf, period); + buf[len++] = '\n'; + buf[len] = '\0'; + return len; +} - case 0x07 ... SPI_STATIC_PPR: - str = ppr_to_ns[tp->period]; - if(!str) - str = "reserved"; +static ssize_t +store_spi_transport_period_helper(struct class_device *cdev, const char *buf, + size_t count, int *periodp) +{ + int j, picosec, period = -1; + char *endp; + + picosec = simple_strtoul(buf, &endp, 10) * 1000; + if (*endp == '.') { + int mult = 100; + do { + endp++; + if (!isdigit(*endp)) + break; + picosec += (*endp - '0') * mult; + mult /= 10; + } while (mult > 0); + } + + for (j = 0; j <= SPI_STATIC_PPR; j++) { + if (ppr_to_ps[j] < picosec) + continue; + period = j; break; + } + if (period == -1) + period = picosec / 4000; - case (SPI_STATIC_PPR+1) ... 0xff: - return sprintf(buf, "%d\n", tp->period * 4); + if (period > 0xff) + period = 0xff; - default: - str = "unknown"; - } - return sprintf(buf, "%s\n", str); + *periodp = period; + + return count; +} + +static ssize_t +show_spi_transport_period(struct class_device *cdev, char *buf) +{ + struct scsi_target *starget = transport_class_to_starget(cdev); + struct Scsi_Host *shost = dev_to_shost(starget->dev.parent); + struct spi_internal *i = to_spi_internal(shost->transportt); + struct spi_transport_attrs *tp = + (struct spi_transport_attrs *)&starget->starget_data; + + if (i->f->get_period) + i->f->get_period(starget); + + return show_spi_transport_period_helper(cdev, buf, tp->period); } static ssize_t @@ -302,46 +469,51 @@ store_spi_transport_period(struct class_device *cdev, const char *buf, struct scsi_target *starget = transport_class_to_starget(cdev); struct Scsi_Host *shost = dev_to_shost(starget->dev.parent); struct spi_internal *i = to_spi_internal(shost->transportt); - int j, period = -1; + struct spi_transport_attrs *tp = + (struct spi_transport_attrs *)&starget->starget_data; + int period, retval; - for (j = 0; j < SPI_STATIC_PPR; j++) { - int len; + retval = store_spi_transport_period_helper(cdev, buf, count, &period); - if(ppr_to_ns[j] == NULL) - continue; + if (period < tp->min_period) + period = tp->min_period; - len = strlen(ppr_to_ns[j]); + i->f->set_period(starget, period); - if(strncmp(ppr_to_ns[j], buf, len) != 0) - continue; + return retval; +} - if(buf[len] != '\n') - continue; - - period = j; - break; - } +static CLASS_DEVICE_ATTR(period, S_IRUGO | S_IWUSR, + show_spi_transport_period, + store_spi_transport_period); - if (period == -1) { - int val = simple_strtoul(buf, NULL, 0); +static ssize_t +show_spi_transport_min_period(struct class_device *cdev, char *buf) +{ + struct scsi_target *starget = transport_class_to_starget(cdev); + struct spi_transport_attrs *tp = + (struct spi_transport_attrs *)&starget->starget_data; + return show_spi_transport_period_helper(cdev, buf, tp->min_period); +} - /* Should probably check limits here, but this - * gets reasonably close to OK for most things */ - period = val/4; - } +static ssize_t +store_spi_transport_min_period(struct class_device *cdev, const char *buf, + size_t count) +{ + struct scsi_target *starget = transport_class_to_starget(cdev); + struct spi_transport_attrs *tp = + (struct spi_transport_attrs *)&starget->starget_data; - if (period > 0xff) - period = 0xff; + return store_spi_transport_period_helper(cdev, buf, count, + &tp->min_period); +} - i->f->set_period(starget, period); - return count; -} - -static CLASS_DEVICE_ATTR(period, S_IRUGO | S_IWUSR, - show_spi_transport_period, - store_spi_transport_period); +static CLASS_DEVICE_ATTR(min_period, S_IRUGO | S_IWUSR, + show_spi_transport_min_period, + store_spi_transport_min_period); + static ssize_t show_spi_host_signalling(struct class_device *cdev, char *buf) { @@ -373,22 +545,23 @@ static CLASS_DEVICE_ATTR(signalling, S_IRUGO | S_IWUSR, if(i->f->set_##x) \ i->f->set_##x(sdev->sdev_target, y) -#define DV_LOOPS 3 -#define DV_TIMEOUT (10*HZ) -#define DV_RETRIES 3 /* should only need at most - * two cc/ua clears */ +enum spi_compare_returns { + SPI_COMPARE_SUCCESS, + SPI_COMPARE_FAILURE, + SPI_COMPARE_SKIP_TEST, +}; /* This is for read/write Domain Validation: If the device supports * an echo buffer, we do read/write tests to it */ -static int -spi_dv_device_echo_buffer(struct scsi_request *sreq, u8 *buffer, +static enum spi_compare_returns +spi_dv_device_echo_buffer(struct scsi_device *sdev, u8 *buffer, u8 *ptr, const int retries) { - struct scsi_device *sdev = sreq->sr_device; int len = ptr - buffer; - int j, k, r; + int j, k, r, result; unsigned int pattern = 0x0000ffff; + struct scsi_sense_hdr sshdr; const char spi_write_buffer[] = { WRITE_BUFFER, 0x0a, 0, 0, 0, 0, 0, len >> 8, len & 0xff, 0 @@ -433,54 +606,59 @@ spi_dv_device_echo_buffer(struct scsi_request *sreq, u8 *buffer, } for (r = 0; r < retries; r++) { - sreq->sr_cmd_len = 0; /* wait_req to fill in */ - sreq->sr_data_direction = DMA_TO_DEVICE; - scsi_wait_req(sreq, spi_write_buffer, buffer, len, - DV_TIMEOUT, DV_RETRIES); - if(sreq->sr_result || !scsi_device_online(sdev)) { + result = spi_execute(sdev, spi_write_buffer, DMA_TO_DEVICE, + buffer, len, &sshdr); + if(result || !scsi_device_online(sdev)) { + scsi_device_set_state(sdev, SDEV_QUIESCE); - SPI_PRINTK(sdev->sdev_target, KERN_ERR, "Write Buffer failure %x\n", sreq->sr_result); - return 0; + if (scsi_sense_valid(&sshdr) + && sshdr.sense_key == ILLEGAL_REQUEST + /* INVALID FIELD IN CDB */ + && sshdr.asc == 0x24 && sshdr.ascq == 0x00) + /* This would mean that the drive lied + * to us about supporting an echo + * buffer (unfortunately some Western + * Digital drives do precisely this) + */ + return SPI_COMPARE_SKIP_TEST; + + + sdev_printk(KERN_ERR, sdev, "Write Buffer failure %x\n", result); + return SPI_COMPARE_FAILURE; } memset(ptr, 0, len); - sreq->sr_cmd_len = 0; /* wait_req to fill in */ - sreq->sr_data_direction = DMA_FROM_DEVICE; - scsi_wait_req(sreq, spi_read_buffer, ptr, len, - DV_TIMEOUT, DV_RETRIES); + spi_execute(sdev, spi_read_buffer, DMA_FROM_DEVICE, + ptr, len, NULL); scsi_device_set_state(sdev, SDEV_QUIESCE); if (memcmp(buffer, ptr, len) != 0) - return 0; + return SPI_COMPARE_FAILURE; } - return 1; + return SPI_COMPARE_SUCCESS; } /* This is for the simplest form of Domain Validation: a read test * on the inquiry data from the device */ -static int -spi_dv_device_compare_inquiry(struct scsi_request *sreq, u8 *buffer, +static enum spi_compare_returns +spi_dv_device_compare_inquiry(struct scsi_device *sdev, u8 *buffer, u8 *ptr, const int retries) { - int r; - const int len = sreq->sr_device->inquiry_len; - struct scsi_device *sdev = sreq->sr_device; + int r, result; + const int len = sdev->inquiry_len; const char spi_inquiry[] = { INQUIRY, 0, 0, 0, len, 0 }; for (r = 0; r < retries; r++) { - sreq->sr_cmd_len = 0; /* wait_req to fill in */ - sreq->sr_data_direction = DMA_FROM_DEVICE; - memset(ptr, 0, len); - scsi_wait_req(sreq, spi_inquiry, ptr, len, - DV_TIMEOUT, DV_RETRIES); + result = spi_execute(sdev, spi_inquiry, DMA_FROM_DEVICE, + ptr, len, NULL); - if(sreq->sr_result || !scsi_device_online(sdev)) { + if(result || !scsi_device_online(sdev)) { scsi_device_set_state(sdev, SDEV_QUIESCE); - return 0; + return SPI_COMPARE_FAILURE; } /* If we don't have the inquiry data already, the @@ -493,53 +671,73 @@ spi_dv_device_compare_inquiry(struct scsi_request *sreq, u8 *buffer, if (memcmp(buffer, ptr, len) != 0) /* failure */ - return 0; + return SPI_COMPARE_FAILURE; } - return 1; + return SPI_COMPARE_SUCCESS; } -static int -spi_dv_retrain(struct scsi_request *sreq, u8 *buffer, u8 *ptr, - int (*compare_fn)(struct scsi_request *, u8 *, u8 *, int)) +static enum spi_compare_returns +spi_dv_retrain(struct scsi_device *sdev, u8 *buffer, u8 *ptr, + enum spi_compare_returns + (*compare_fn)(struct scsi_device *, u8 *, u8 *, int)) { - struct spi_internal *i = to_spi_internal(sreq->sr_host->transportt); - struct scsi_device *sdev = sreq->sr_device; + struct spi_internal *i = to_spi_internal(sdev->host->transportt); + struct scsi_target *starget = sdev->sdev_target; int period = 0, prevperiod = 0; + enum spi_compare_returns retval; for (;;) { int newperiod; - if (compare_fn(sreq, buffer, ptr, DV_LOOPS)) - /* Successful DV */ + retval = compare_fn(sdev, buffer, ptr, DV_LOOPS); + + if (retval == SPI_COMPARE_SUCCESS + || retval == SPI_COMPARE_SKIP_TEST) break; /* OK, retrain, fallback */ + if (i->f->get_iu) + i->f->get_iu(starget); + if (i->f->get_qas) + i->f->get_qas(starget); if (i->f->get_period) i->f->get_period(sdev->sdev_target); - newperiod = spi_period(sdev->sdev_target); - period = newperiod > period ? newperiod : period; - if (period < 0x0d) - period++; - else - period += period >> 1; - if (unlikely(period > 0xff || period == prevperiod)) { - /* Total failure; set to async and return */ - SPI_PRINTK(sdev->sdev_target, KERN_ERR, "Domain Validation Failure, dropping back to Asynchronous\n"); - DV_SET(offset, 0); - return 0; + /* Here's the fallback sequence; first try turning off + * IU, then QAS (if we can control them), then finally + * fall down the periods */ + if (i->f->set_iu && spi_iu(starget)) { + starget_printk(KERN_ERR, starget, "Domain Validation Disabing Information Units\n"); + DV_SET(iu, 0); + } else if (i->f->set_qas && spi_qas(starget)) { + starget_printk(KERN_ERR, starget, "Domain Validation Disabing Quick Arbitration and Selection\n"); + DV_SET(qas, 0); + } else { + newperiod = spi_period(starget); + period = newperiod > period ? newperiod : period; + if (period < 0x0d) + period++; + else + period += period >> 1; + + if (unlikely(period > 0xff || period == prevperiod)) { + /* Total failure; set to async and return */ + starget_printk(KERN_ERR, starget, "Domain Validation Failure, dropping back to Asynchronous\n"); + DV_SET(offset, 0); + return SPI_COMPARE_FAILURE; + } + starget_printk(KERN_ERR, starget, "Domain Validation detected failure, dropping back\n"); + DV_SET(period, period); + prevperiod = period; } - SPI_PRINTK(sdev->sdev_target, KERN_ERR, "Domain Validation detected failure, dropping back\n"); - DV_SET(period, period); - prevperiod = period; } - return 1; + return retval; } static int -spi_dv_device_get_echo_buffer(struct scsi_request *sreq, u8 *buffer) +spi_dv_device_get_echo_buffer(struct scsi_device *sdev, u8 *buffer) { - int l; + int l, result; /* first off do a test unit ready. This can error out * because of reservations or some other reason. If it @@ -555,19 +753,16 @@ spi_dv_device_get_echo_buffer(struct scsi_request *sreq, u8 *buffer) }; - sreq->sr_cmd_len = 0; - sreq->sr_data_direction = DMA_NONE; - /* We send a set of three TURs to clear any outstanding * unit attention conditions if they exist (Otherwise the * buffer tests won't be happy). If the TUR still fails * (reservation conflict, device not ready, etc) just * skip the write tests */ for (l = 0; ; l++) { - scsi_wait_req(sreq, spi_test_unit_ready, NULL, 0, - DV_TIMEOUT, DV_RETRIES); + result = spi_execute(sdev, spi_test_unit_ready, DMA_NONE, + NULL, 0, NULL); - if(sreq->sr_result) { + if(result) { if(l >= 3) return 0; } else { @@ -576,13 +771,10 @@ spi_dv_device_get_echo_buffer(struct scsi_request *sreq, u8 *buffer) } } - sreq->sr_cmd_len = 0; - sreq->sr_data_direction = DMA_FROM_DEVICE; + result = spi_execute(sdev, spi_read_buffer_descriptor, + DMA_FROM_DEVICE, buffer, 4, NULL); - scsi_wait_req(sreq, spi_read_buffer_descriptor, buffer, 4, - DV_TIMEOUT, DV_RETRIES); - - if (sreq->sr_result) + if (result) /* Device has no echo buffer */ return 0; @@ -590,30 +782,33 @@ spi_dv_device_get_echo_buffer(struct scsi_request *sreq, u8 *buffer) } static void -spi_dv_device_internal(struct scsi_request *sreq, u8 *buffer) +spi_dv_device_internal(struct scsi_device *sdev, u8 *buffer) { - struct spi_internal *i = to_spi_internal(sreq->sr_host->transportt); - struct scsi_device *sdev = sreq->sr_device; + struct spi_internal *i = to_spi_internal(sdev->host->transportt); + struct scsi_target *starget = sdev->sdev_target; int len = sdev->inquiry_len; /* first set us up for narrow async */ DV_SET(offset, 0); DV_SET(width, 0); - if (!spi_dv_device_compare_inquiry(sreq, buffer, buffer, DV_LOOPS)) { - SPI_PRINTK(sdev->sdev_target, KERN_ERR, "Domain Validation Initial Inquiry Failed\n"); + if (spi_dv_device_compare_inquiry(sdev, buffer, buffer, DV_LOOPS) + != SPI_COMPARE_SUCCESS) { + starget_printk(KERN_ERR, starget, "Domain Validation Initial Inquiry Failed\n"); /* FIXME: should probably offline the device here? */ return; } /* test width */ - if (i->f->set_width && sdev->wdtr) { - i->f->set_width(sdev->sdev_target, 1); + if (i->f->set_width && spi_max_width(starget) && + scsi_device_wide(sdev)) { + i->f->set_width(starget, 1); - if (!spi_dv_device_compare_inquiry(sreq, buffer, + if (spi_dv_device_compare_inquiry(sdev, buffer, buffer + len, - DV_LOOPS)) { - SPI_PRINTK(sdev->sdev_target, KERN_ERR, "Wide Transfers Fail\n"); - i->f->set_width(sdev->sdev_target, 0); + DV_LOOPS) + != SPI_COMPARE_SUCCESS) { + starget_printk(KERN_ERR, starget, "Wide Transfers Fail\n"); + i->f->set_width(starget, 0); } } @@ -621,34 +816,68 @@ spi_dv_device_internal(struct scsi_request *sreq, u8 *buffer) return; /* device can't handle synchronous */ - if(!sdev->ppr && !sdev->sdtr) + if (!scsi_device_sync(sdev) && !scsi_device_dt(sdev)) return; - /* now set up to the maximum */ - DV_SET(offset, 255); - DV_SET(period, 1); - if (!spi_dv_retrain(sreq, buffer, buffer + len, - spi_dv_device_compare_inquiry)) - return; + /* len == -1 is the signal that we need to ascertain the + * presence of an echo buffer before trying to use it. len == + * 0 means we don't have an echo buffer */ + len = -1; - /* OK, now we have our initial speed set by the read only inquiry - * test, now try an echo buffer test (if the device allows it) */ + retry: - len = 0; - if (sdev->ppr) - len = spi_dv_device_get_echo_buffer(sreq, buffer); - - if (len == 0) { - SPI_PRINTK(sdev->sdev_target, KERN_INFO, "Domain Validation skipping write tests\n"); + /* now set up to the maximum */ + DV_SET(offset, spi_max_offset(starget)); + DV_SET(period, spi_min_period(starget)); + /* try QAS requests; this should be harmless to set if the + * target supports it */ + if (scsi_device_qas(sdev)) + DV_SET(qas, 1); + /* Also try IU transfers */ + if (scsi_device_ius(sdev)) + DV_SET(iu, 1); + if (spi_min_period(starget) < 9) { + /* This u320 (or u640). Ignore the coupled parameters + * like DT and IU, but set the optional ones */ + DV_SET(rd_strm, 1); + DV_SET(wr_flow, 1); + DV_SET(rti, 1); + if (spi_min_period(starget) == 8) + DV_SET(pcomp_en, 1); + } + /* Do the read only INQUIRY tests */ + spi_dv_retrain(sdev, buffer, buffer + sdev->inquiry_len, + spi_dv_device_compare_inquiry); + /* See if we actually managed to negotiate and sustain DT */ + if (i->f->get_dt) + i->f->get_dt(starget); + + /* see if the device has an echo buffer. If it does we can do + * the SPI pattern write tests. Because of some broken + * devices, we *only* try this on a device that has actually + * negotiated DT */ + + if (len == -1 && spi_dt(starget)) + len = spi_dv_device_get_echo_buffer(sdev, buffer); + + if (len <= 0) { + starget_printk(KERN_INFO, starget, "Domain Validation skipping write tests\n"); return; } + if (len > SPI_MAX_ECHO_BUFFER_SIZE) { - SPI_PRINTK(sdev->sdev_target, KERN_WARNING, "Echo buffer size %d is too big, trimming to %d\n", len, SPI_MAX_ECHO_BUFFER_SIZE); + starget_printk(KERN_WARNING, starget, "Echo buffer size %d is too big, trimming to %d\n", len, SPI_MAX_ECHO_BUFFER_SIZE); len = SPI_MAX_ECHO_BUFFER_SIZE; } - spi_dv_retrain(sreq, buffer, buffer + len, - spi_dv_device_echo_buffer); + if (spi_dv_retrain(sdev, buffer, buffer + len, + spi_dv_device_echo_buffer) + == SPI_COMPARE_SKIP_TEST) { + /* OK, the stupid drive can't do a write echo buffer + * test after all, fall back to the read tests */ + len = 0; + goto retry; + } } @@ -664,16 +893,12 @@ spi_dv_device_internal(struct scsi_request *sreq, u8 *buffer) void spi_dv_device(struct scsi_device *sdev) { - struct scsi_request *sreq = scsi_allocate_request(sdev, GFP_KERNEL); struct scsi_target *starget = sdev->sdev_target; u8 *buffer; const int len = SPI_MAX_ECHO_BUFFER_SIZE*2; - if (unlikely(!sreq)) - return; - if (unlikely(scsi_device_get(sdev))) - goto out_free_req; + return; buffer = kmalloc(len, GFP_KERNEL); @@ -690,15 +915,15 @@ spi_dv_device(struct scsi_device *sdev) scsi_target_quiesce(starget); spi_dv_pending(starget) = 1; - down(&spi_dv_sem(starget)); + mutex_lock(&spi_dv_mutex(starget)); - SPI_PRINTK(starget, KERN_INFO, "Beginning Domain Validation\n"); + starget_printk(KERN_INFO, starget, "Beginning Domain Validation\n"); - spi_dv_device_internal(sreq, buffer); + spi_dv_device_internal(sdev, buffer); - SPI_PRINTK(starget, KERN_INFO, "Ending Domain Validation\n"); + starget_printk(KERN_INFO, starget, "Ending Domain Validation\n"); - up(&spi_dv_sem(starget)); + mutex_unlock(&spi_dv_mutex(starget)); spi_dv_pending(starget) = 0; scsi_target_resume(starget); @@ -709,8 +934,6 @@ spi_dv_device(struct scsi_device *sdev) kfree(buffer); out_put: scsi_device_put(sdev); - out_free_req: - scsi_release_request(sreq); } EXPORT_SYMBOL(spi_dv_device); @@ -769,6 +992,189 @@ spi_schedule_dv_device(struct scsi_device *sdev) } EXPORT_SYMBOL(spi_schedule_dv_device); +/** + * spi_display_xfer_agreement - Print the current target transfer agreement + * @starget: The target for which to display the agreement + * + * Each SPI port is required to maintain a transfer agreement for each + * other port on the bus. This function prints a one-line summary of + * the current agreement; more detailed information is available in sysfs. + */ +void spi_display_xfer_agreement(struct scsi_target *starget) +{ + struct spi_transport_attrs *tp; + tp = (struct spi_transport_attrs *)&starget->starget_data; + + if (tp->offset > 0 && tp->period > 0) { + unsigned int picosec, kb100; + char *scsi = "FAST-?"; + char tmp[8]; + + if (tp->period <= SPI_STATIC_PPR) { + picosec = ppr_to_ps[tp->period]; + switch (tp->period) { + case 7: scsi = "FAST-320"; break; + case 8: scsi = "FAST-160"; break; + case 9: scsi = "FAST-80"; break; + case 10: + case 11: scsi = "FAST-40"; break; + case 12: scsi = "FAST-20"; break; + } + } else { + picosec = tp->period * 4000; + if (tp->period < 25) + scsi = "FAST-20"; + else if (tp->period < 50) + scsi = "FAST-10"; + else + scsi = "FAST-5"; + } + + kb100 = (10000000 + picosec / 2) / picosec; + if (tp->width) + kb100 *= 2; + sprint_frac(tmp, picosec, 1000); + + dev_info(&starget->dev, + "%s %sSCSI %d.%d MB/s %s%s%s%s%s%s%s%s (%s ns, offset %d)\n", + scsi, tp->width ? "WIDE " : "", kb100/10, kb100 % 10, + tp->dt ? "DT" : "ST", + tp->iu ? " IU" : "", + tp->qas ? " QAS" : "", + tp->rd_strm ? " RDSTRM" : "", + tp->rti ? " RTI" : "", + tp->wr_flow ? " WRFLOW" : "", + tp->pcomp_en ? " PCOMP" : "", + tp->hold_mcs ? " HMCS" : "", + tmp, tp->offset); + } else { + dev_info(&starget->dev, "%sasynchronous\n", + tp->width ? "wide " : ""); + } +} +EXPORT_SYMBOL(spi_display_xfer_agreement); + +#ifdef CONFIG_SCSI_CONSTANTS +static const char * const one_byte_msgs[] = { +/* 0x00 */ "Command Complete", NULL, "Save Pointers", +/* 0x03 */ "Restore Pointers", "Disconnect", "Initiator Error", +/* 0x06 */ "Abort", "Message Reject", "Nop", "Message Parity Error", +/* 0x0a */ "Linked Command Complete", "Linked Command Complete w/flag", +/* 0x0c */ "Bus device reset", "Abort Tag", "Clear Queue", +/* 0x0f */ "Initiate Recovery", "Release Recovery" +}; + +static const char * const two_byte_msgs[] = { +/* 0x20 */ "Simple Queue Tag", "Head of Queue Tag", "Ordered Queue Tag", +/* 0x23 */ "Ignore Wide Residue" +}; + +static const char * const extended_msgs[] = { +/* 0x00 */ "Modify Data Pointer", "Synchronous Data Transfer Request", +/* 0x02 */ "SCSI-I Extended Identify", "Wide Data Transfer Request", +/* 0x04 */ "Parallel Protocol Request" +}; + +static void print_nego(const unsigned char *msg, int per, int off, int width) +{ + if (per) { + char buf[20]; + period_to_str(buf, msg[per]); + printk("period = %s ns ", buf); + } + + if (off) + printk("offset = %d ", msg[off]); + if (width) + printk("width = %d ", 8 << msg[width]); +} + +int spi_print_msg(const unsigned char *msg) +{ + int len = 0, i; + if (msg[0] == EXTENDED_MESSAGE) { + len = 3 + msg[1]; + if (msg[2] < ARRAY_SIZE(extended_msgs)) + printk ("%s ", extended_msgs[msg[2]]); + else + printk ("Extended Message, reserved code (0x%02x) ", + (int) msg[2]); + switch (msg[2]) { + case EXTENDED_MODIFY_DATA_POINTER: + printk("pointer = %d", (int) (msg[3] << 24) | + (msg[4] << 16) | (msg[5] << 8) | msg[6]); + break; + case EXTENDED_SDTR: + print_nego(msg, 3, 4, 0); + break; + case EXTENDED_WDTR: + print_nego(msg, 0, 0, 3); + break; + case EXTENDED_PPR: + print_nego(msg, 3, 5, 6); + break; + default: + for (i = 2; i < len; ++i) + printk("%02x ", msg[i]); + } + /* Identify */ + } else if (msg[0] & 0x80) { + printk("Identify disconnect %sallowed %s %d ", + (msg[0] & 0x40) ? "" : "not ", + (msg[0] & 0x20) ? "target routine" : "lun", + msg[0] & 0x7); + len = 1; + /* Normal One byte */ + } else if (msg[0] < 0x1f) { + if (msg[0] < ARRAY_SIZE(one_byte_msgs)) + printk(one_byte_msgs[msg[0]]); + else + printk("reserved (%02x) ", msg[0]); + len = 1; + /* Two byte */ + } else if (msg[0] <= 0x2f) { + if ((msg[0] - 0x20) < ARRAY_SIZE(two_byte_msgs)) + printk("%s %02x ", two_byte_msgs[msg[0] - 0x20], + msg[1]); + else + printk("reserved two byte (%02x %02x) ", + msg[0], msg[1]); + len = 2; + } else + printk("reserved"); + return len; +} +EXPORT_SYMBOL(spi_print_msg); + +#else /* ifndef CONFIG_SCSI_CONSTANTS */ + +int spi_print_msg(const unsigned char *msg) +{ + int len = 0, i; + + if (msg[0] == EXTENDED_MESSAGE) { + len = 3 + msg[1]; + for (i = 0; i < len; ++i) + printk("%02x ", msg[i]); + /* Identify */ + } else if (msg[0] & 0x80) { + printk("%02x ", msg[0]); + len = 1; + /* Normal One byte */ + } else if (msg[0] < 0x1f) { + printk("%02x ", msg[0]); + len = 1; + /* Two byte */ + } else if (msg[0] <= 0x2f) { + printk("%02x %02x", msg[0], msg[1]); + len = 2; + } else + printk("%02x ", msg[0]); + return len; +} +EXPORT_SYMBOL(spi_print_msg); +#endif /* ! CONFIG_SCSI_CONSTANTS */ + #define SETUP_ATTRIBUTE(field) \ i->private_attrs[count] = class_device_attr_##field; \ if (!i->f->set_##field) { \ @@ -779,6 +1185,16 @@ EXPORT_SYMBOL(spi_schedule_dv_device); if (i->f->show_##field) \ count++ +#define SETUP_RELATED_ATTRIBUTE(field, rel_field) \ + i->private_attrs[count] = class_device_attr_##field; \ + if (!i->f->set_##rel_field) { \ + i->private_attrs[count].attr.mode = S_IRUGO; \ + i->private_attrs[count].store = NULL; \ + } \ + i->attrs[count] = &i->private_attrs[count]; \ + if (i->f->show_##rel_field) \ + count++ + #define SETUP_HOST_ATTRIBUTE(field) \ i->private_host_attrs[count] = class_device_attr_##field; \ if (!i->f->set_##field) { \ @@ -788,6 +1204,64 @@ EXPORT_SYMBOL(spi_schedule_dv_device); i->host_attrs[count] = &i->private_host_attrs[count]; \ count++ +static int spi_device_match(struct attribute_container *cont, + struct device *dev) +{ + struct scsi_device *sdev; + struct Scsi_Host *shost; + struct spi_internal *i; + + if (!scsi_is_sdev_device(dev)) + return 0; + + sdev = to_scsi_device(dev); + shost = sdev->host; + if (!shost->transportt || shost->transportt->host_attrs.ac.class + != &spi_host_class.class) + return 0; + /* Note: this class has no device attributes, so it has + * no per-HBA allocation and thus we don't need to distinguish + * the attribute containers for the device */ + i = to_spi_internal(shost->transportt); + if (i->f->deny_binding && i->f->deny_binding(sdev->sdev_target)) + return 0; + return 1; +} + +static int spi_target_match(struct attribute_container *cont, + struct device *dev) +{ + struct Scsi_Host *shost; + struct scsi_target *starget; + struct spi_internal *i; + + if (!scsi_is_target_device(dev)) + return 0; + + shost = dev_to_shost(dev->parent); + if (!shost->transportt || shost->transportt->host_attrs.ac.class + != &spi_host_class.class) + return 0; + + i = to_spi_internal(shost->transportt); + starget = to_scsi_target(dev); + + if (i->f->deny_binding && i->f->deny_binding(starget)) + return 0; + + return &i->t.target_attrs.ac == cont; +} + +static DECLARE_TRANSPORT_CLASS(spi_transport_class, + "spi_transport", + spi_setup_transport_attrs, + NULL, + NULL); + +static DECLARE_ANON_TRANSPORT_CLASS(spi_device_class, + spi_device_match, + spi_device_configure); + struct scsi_transport_template * spi_attach_transport(struct spi_function_template *ft) { @@ -800,20 +1274,24 @@ spi_attach_transport(struct spi_function_template *ft) memset(i, 0, sizeof(struct spi_internal)); - i->t.target_attrs = &i->attrs[0]; - i->t.target_class = &spi_transport_class; - i->t.target_setup = &spi_setup_transport_attrs; - i->t.device_configure = &spi_configure_device; + i->t.target_attrs.ac.class = &spi_transport_class.class; + i->t.target_attrs.ac.attrs = &i->attrs[0]; + i->t.target_attrs.ac.match = spi_target_match; + transport_container_register(&i->t.target_attrs); i->t.target_size = sizeof(struct spi_transport_attrs); - i->t.host_attrs = &i->host_attrs[0]; - i->t.host_class = &spi_host_class; - i->t.host_setup = &spi_setup_host_attrs; + i->t.host_attrs.ac.class = &spi_host_class.class; + i->t.host_attrs.ac.attrs = &i->host_attrs[0]; + i->t.host_attrs.ac.match = spi_host_match; + transport_container_register(&i->t.host_attrs); i->t.host_size = sizeof(struct spi_host_attrs); i->f = ft; SETUP_ATTRIBUTE(period); + SETUP_RELATED_ATTRIBUTE(min_period, period); SETUP_ATTRIBUTE(offset); + SETUP_RELATED_ATTRIBUTE(max_offset, offset); SETUP_ATTRIBUTE(width); + SETUP_RELATED_ATTRIBUTE(max_width, width); SETUP_ATTRIBUTE(iu); SETUP_ATTRIBUTE(dt); SETUP_ATTRIBUTE(qas); @@ -821,6 +1299,7 @@ spi_attach_transport(struct spi_function_template *ft) SETUP_ATTRIBUTE(rd_strm); SETUP_ATTRIBUTE(rti); SETUP_ATTRIBUTE(pcomp_en); + SETUP_ATTRIBUTE(hold_mcs); /* if you add an attribute but forget to increase SPI_NUM_ATTRS * this bug will trigger */ @@ -845,10 +1324,28 @@ void spi_release_transport(struct scsi_transport_template *t) { struct spi_internal *i = to_spi_internal(t); + transport_container_unregister(&i->t.target_attrs); + transport_container_unregister(&i->t.host_attrs); + kfree(i); } EXPORT_SYMBOL(spi_release_transport); +static __init int spi_transport_init(void) +{ + int error = transport_class_register(&spi_transport_class); + if (error) + return error; + error = anon_transport_class_register(&spi_device_class); + return transport_class_register(&spi_host_class); +} + +static void __exit spi_transport_exit(void) +{ + transport_class_unregister(&spi_transport_class); + anon_transport_class_unregister(&spi_device_class); + transport_class_unregister(&spi_host_class); +} MODULE_AUTHOR("Martin Hicks"); MODULE_DESCRIPTION("SPI Transport Attributes");