X-Git-Url: http://git.onelab.eu/?a=blobdiff_plain;f=drivers%2Fscsi%2Fscsi_transport_spi.c;h=014d7fea1ff375d5a5c982c8d5c2f2d67d3be28b;hb=refs%2Fheads%2Fvserver;hp=f2cd32a141f1994c97c62a96849f15d2dd58576b;hpb=5273a3df6485dc2ad6aa7ddd441b9a21970f003b;p=linux-2.6.git diff --git a/drivers/scsi/scsi_transport_spi.c b/drivers/scsi/scsi_transport_spi.c index f2cd32a14..014d7fea1 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,35 +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 "scsi_priv.h" #include #include -#include +#include +#include #include #include -#define SPI_PRINTK(x, l, f, a...) printk(l "scsi(%d:%d:%d:%d): " f, (x)->host->host_no, (x)->channel, (x)->id, (x)->lun , ##a) - -static void transport_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)->transport_data)->dv_pending) -#define spi_dv_sem(x) (((struct spi_transport_attrs *)&(x)->transport_data)->dv_sem) +#define spi_dv_pending(x) (((struct spi_transport_attrs *)&(x)->starget_data)->dv_pending) +#define spi_dv_in_progress(x) (((struct spi_transport_attrs *)&(x)->starget_data)->dv_in_progress) +#define spi_dv_mutex(x) (((struct spi_transport_attrs *)&(x)->starget_data)->dv_mutex) struct spi_internal { struct scsi_transport_template t; @@ -55,68 +58,222 @@ struct spi_internal { /* The array of null terminated pointers to attributes * needed by scsi_sysfs.c */ struct class_device_attribute *attrs[SPI_NUM_ATTRS + SPI_OTHER_ATTRS + 1]; + struct class_device_attribute private_host_attrs[SPI_HOST_ATTRS]; + struct class_device_attribute *host_attrs[SPI_HOST_ATTRS + 1]; }; #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 -struct class spi_transport_class = { - .name = "spi_transport", - .release = transport_class_release, +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, SCSI_SENSE_BUFFERSIZE, + sshdr) + && sshdr->sense_key == UNIT_ATTENTION) + continue; + } + break; + } + return result; +} + +static struct { + enum spi_signal_type value; + char *name; +} signal_types[] = { + { SPI_SIGNAL_UNKNOWN, "unknown" }, + { SPI_SIGNAL_SE, "SE" }, + { SPI_SIGNAL_LVD, "LVD" }, + { SPI_SIGNAL_HVD, "HVD" }, }; -static __init int spi_transport_init(void) +static inline const char *spi_signal_to_string(enum spi_signal_type type) { - return class_register(&spi_transport_class); + int i; + + for (i = 0; i < ARRAY_SIZE(signal_types); i++) { + if (type == signal_types[i].value) + return signal_types[i].name; + } + return NULL; } +static inline enum spi_signal_type spi_signal_to_value(const char *name) +{ + int i, len; -static void __exit spi_transport_exit(void) + for (i = 0; i < ARRAY_SIZE(signal_types); i++) { + len = strlen(signal_types[i].name); + if (strncmp(name, signal_types[i].name, len) == 0 && + (name[len] == '\n' || name[len] == '\0')) + return signal_types[i].value; + } + return SPI_SIGNAL_UNKNOWN; +} + +static int spi_host_setup(struct transport_container *tc, struct device *dev, + struct class_device *cdev) { - class_unregister(&spi_transport_class); + struct Scsi_Host *shost = dev_to_shost(dev); + + spi_signalling(shost) = SPI_SIGNAL_UNKNOWN; + + return 0; } -static int spi_setup_transport_attrs(struct scsi_device *sdev) +static DECLARE_TRANSPORT_CLASS(spi_host_class, + "spi_host", + spi_host_setup, + NULL, + NULL); + +static int spi_host_match(struct attribute_container *cont, + struct device *dev) { - spi_period(sdev) = -1; /* illegal value */ - spi_offset(sdev) = 0; /* async */ - spi_width(sdev) = 0; /* narrow */ - spi_iu(sdev) = 0; /* no IU */ - spi_dt(sdev) = 0; /* ST */ - spi_qas(sdev) = 0; - spi_wr_flow(sdev) = 0; - spi_rd_strm(sdev) = 0; - spi_rti(sdev) = 0; - spi_pcomp_en(sdev) = 0; - spi_dv_pending(sdev) = 0; - init_MUTEX(&spi_dv_sem(sdev)); + struct Scsi_Host *shost; + struct spi_internal *i; + + 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_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 + * gleaned from the device inquiry */ + + spi_support_sync(starget) = scsi_device_sync(sdev); + spi_support_wide(starget) = scsi_device_wide(sdev); + spi_support_dt(starget) = scsi_device_dt(sdev); + spi_support_dt_only(starget) = scsi_device_dt_only(sdev); + spi_support_ius(starget) = scsi_device_ius(sdev); + spi_support_qas(starget) = scsi_device_qas(sdev); return 0; } -static void transport_class_release(struct class_device *class_dev) +static int spi_setup_transport_attrs(struct transport_container *tc, + struct device *dev, + struct class_device *cdev) { - struct scsi_device *sdev = transport_class_to_sdev(class_dev); - put_device(&sdev->sdev_gendev); + 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; + spi_wr_flow(starget) = 0; + 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_dv_in_progress(starget) = 0; + spi_initial_dv(starget) = 0; + mutex_init(&spi_dv_mutex(starget)); + + return 0; +} + +#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); \ +} + +#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) \ @@ -124,12 +281,13 @@ static void transport_class_release(struct class_device *class_dev) static ssize_t \ show_spi_transport_##field(struct class_device *cdev, char *buf) \ { \ - struct scsi_device *sdev = transport_class_to_sdev(cdev); \ + struct scsi_target *starget = transport_class_to_starget(cdev); \ + struct Scsi_Host *shost = dev_to_shost(starget->dev.parent); \ struct spi_transport_attrs *tp; \ - struct spi_internal *i = to_spi_internal(sdev->host->transportt); \ - tp = (struct spi_transport_attrs *)&sdev->transport_data; \ + struct spi_internal *i = to_spi_internal(shost->transportt); \ + tp = (struct spi_transport_attrs *)&starget->starget_data; \ if (i->f->get_##field) \ - i->f->get_##field(sdev); \ + i->f->get_##field(starget); \ return snprintf(buf, 20, format_string, tp->field); \ } @@ -139,11 +297,31 @@ store_spi_transport_##field(struct class_device *cdev, const char *buf, \ size_t count) \ { \ int val; \ - struct scsi_device *sdev = transport_class_to_sdev(cdev); \ - struct spi_internal *i = to_spi_internal(sdev->host->transportt); \ + 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); \ + \ + 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); \ - i->f->set_##field(sdev, val); \ + if (val > tp->max_##field) \ + val = tp->max_##field; \ + i->f->set_##field(starget, val); \ return count; \ } @@ -152,11 +330,26 @@ store_spi_transport_##field(struct class_device *cdev, const char *buf, \ spi_transport_store_function(field, format_string) \ static CLASS_DEVICE_ATTR(field, S_IRUGO | S_IWUSR, \ show_spi_transport_##field, \ - store_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"); @@ -164,117 +357,211 @@ 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_device *sdev = transport_class_to_sdev(cdev); + struct scsi_target *starget = transport_class_to_starget(cdev); - 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) +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_device *sdev = transport_class_to_sdev(cdev); - struct spi_transport_attrs *tp; - const char *str; - struct spi_internal *i = to_spi_internal(sdev->host->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 *)&sdev->transport_data; + if (picosec == -1) { + len = sprintf(buf, "reserved"); + } else { + len = sprint_frac(buf, picosec, 1000); + } - if (i->f->get_period) - i->f->get_period(sdev); + return len; +} + +static ssize_t +show_spi_transport_period_helper(char *buf, int period) +{ + int len = period_to_str(buf, period); + buf[len++] = '\n'; + buf[len] = '\0'; + return len; +} - switch(tp->period) { +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); + } - case 0x07 ... SPI_STATIC_PPR: - str = ppr_to_ns[tp->period]; - if(!str) - str = "reserved"; + 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(buf, tp->period); } static ssize_t store_spi_transport_period(struct class_device *cdev, const char *buf, size_t count) { - struct scsi_device *sdev = transport_class_to_sdev(cdev); - struct spi_internal *i = to_spi_internal(sdev->host->transportt); - int j, period = -1; + 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; + 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(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; + + return store_spi_transport_period_helper(cdev, buf, count, + &tp->min_period); +} - if (period > 0xff) - period = 0xff; - i->f->set_period(sdev, 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) +{ + struct Scsi_Host *shost = transport_class_to_shost(cdev); + struct spi_internal *i = to_spi_internal(shost->transportt); + + if (i->f->get_signalling) + i->f->get_signalling(shost); + + return sprintf(buf, "%s\n", spi_signal_to_string(spi_signalling(shost))); +} +static ssize_t store_spi_host_signalling(struct class_device *cdev, + const char *buf, size_t count) +{ + struct Scsi_Host *shost = transport_class_to_shost(cdev); + struct spi_internal *i = to_spi_internal(shost->transportt); + enum spi_signal_type type = spi_signal_to_value(buf); + + if (type != SPI_SIGNAL_UNKNOWN) + i->f->set_signalling(shost, type); return count; } - -static CLASS_DEVICE_ATTR(period, S_IRUGO | S_IWUSR, - show_spi_transport_period, - store_spi_transport_period); +static CLASS_DEVICE_ATTR(signalling, S_IRUGO | S_IWUSR, + show_spi_host_signalling, + store_spi_host_signalling); #define DV_SET(x, y) \ if(i->f->set_##x) \ - i->f->set_##x(sdev, y) + 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 @@ -319,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, 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 @@ -379,51 +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; - int period, prevperiod = 0; + 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 (;;) { - if (compare_fn(sreq, buffer, ptr, DV_LOOPS)) - /* Successful DV */ + int newperiod; + 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); - period = spi_period(sdev); - if (period < 0x0d) - period++; - else - period += period >> 1; - - if (unlikely(period > 0xff || period == prevperiod)) { - /* Total failure; set to async and return */ - SPI_PRINTK(sdev, KERN_ERR, "Domain Validation Failure, dropping back to Asynchronous\n"); - DV_SET(offset, 0); - return 0; + i->f->get_period(sdev->sdev_target); + + /* 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, 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 @@ -439,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 { @@ -460,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; - - scsi_wait_req(sreq, spi_read_buffer_descriptor, buffer, 4, - DV_TIMEOUT, DV_RETRIES); + result = spi_execute(sdev, spi_read_buffer_descriptor, + DMA_FROM_DEVICE, buffer, 4, NULL); - if (sreq->sr_result) + if (result) /* Device has no echo buffer */ return 0; @@ -474,30 +782,34 @@ 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; + struct Scsi_Host *shost = sdev->host; 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, 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, 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, KERN_ERR, "Wide Transfers Fail\n"); - i->f->set_width(sdev, 0); + DV_LOOPS) + != SPI_COMPARE_SUCCESS) { + starget_printk(KERN_ERR, starget, "Wide Transfers Fail\n"); + i->f->set_width(starget, 0); } } @@ -505,30 +817,85 @@ 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; + /* 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; + + retry: + /* 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; + 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); + } else { + DV_SET(qas, 0); + } - /* 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) */ + if (scsi_device_ius(sdev) && spi_min_period(starget) < 9) { + /* This u320 (or u640). Set IU transfers */ + DV_SET(iu, 1); + /* Then set the optional parameters */ + 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); + } else { + DV_SET(iu, 0); + } - if ((len = spi_dv_device_get_echo_buffer(sreq, buffer)) == 0) { - SPI_PRINTK(sdev, KERN_INFO, "Domain Validation skipping write tests\n"); + /* now that we've done all this, actually check the bus + * signal type (if known). Some devices are stupid on + * a SE bus and still claim they can try LVD only settings */ + if (i->f->get_signalling) + i->f->get_signalling(shost); + if (spi_signalling(shost) == SPI_SIGNAL_SE || + spi_signalling(shost) == SPI_SIGNAL_HVD || + !scsi_device_dt(sdev)) { + DV_SET(dt, 0); + } else { + DV_SET(dt, 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, 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; + } } @@ -544,46 +911,50 @@ 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)) + if (unlikely(scsi_device_get(sdev))) return; - if (unlikely(scsi_device_get(sdev))) - goto out_free_req; + if (unlikely(spi_dv_in_progress(starget))) + return; + spi_dv_in_progress(starget) = 1; - buffer = kmalloc(len, GFP_KERNEL); + buffer = kzalloc(len, GFP_KERNEL); if (unlikely(!buffer)) goto out_put; - memset(buffer, 0, len); - + /* We need to verify that the actual device will quiesce; the + * later target quiesce is just a nice to have */ if (unlikely(scsi_device_quiesce(sdev))) goto out_free; - spi_dv_pending(sdev) = 1; - down(&spi_dv_sem(sdev)); + scsi_target_quiesce(starget); + + spi_dv_pending(starget) = 1; + mutex_lock(&spi_dv_mutex(starget)); + + starget_printk(KERN_INFO, starget, "Beginning Domain Validation\n"); - SPI_PRINTK(sdev, KERN_INFO, "Beginning Domain Validation\n"); + spi_dv_device_internal(sdev, buffer); - spi_dv_device_internal(sreq, buffer); + starget_printk(KERN_INFO, starget, "Ending Domain Validation\n"); - SPI_PRINTK(sdev, KERN_INFO, "Ending Domain Validation\n"); + mutex_unlock(&spi_dv_mutex(starget)); + spi_dv_pending(starget) = 0; - up(&spi_dv_sem(sdev)); - spi_dv_pending(sdev) = 0; + scsi_target_resume(starget); - scsi_device_resume(sdev); + spi_initial_dv(starget) = 1; out_free: kfree(buffer); out_put: + spi_dv_in_progress(starget) = 0; scsi_device_put(sdev); - out_free_req: - scsi_release_request(sreq); } EXPORT_SYMBOL(spi_dv_device); @@ -593,14 +964,15 @@ struct work_queue_wrapper { }; static void -spi_dv_device_work_wrapper(void *data) +spi_dv_device_work_wrapper(struct work_struct *work) { - struct work_queue_wrapper *wqw = (struct work_queue_wrapper *)data; + struct work_queue_wrapper *wqw = + container_of(work, struct work_queue_wrapper, work); struct scsi_device *sdev = wqw->sdev; kfree(wqw); spi_dv_device(sdev); - spi_dv_pending(sdev) = 0; + spi_dv_pending(sdev->sdev_target) = 0; scsi_device_put(sdev); } @@ -623,25 +995,258 @@ spi_schedule_dv_device(struct scsi_device *sdev) if (unlikely(!wqw)) return; - if (unlikely(spi_dv_pending(sdev))) { + if (unlikely(spi_dv_pending(sdev->sdev_target))) { kfree(wqw); return; } /* Set pending early (dv_device doesn't check it, only sets it) */ - spi_dv_pending(sdev) = 1; + spi_dv_pending(sdev->sdev_target) = 1; if (unlikely(scsi_device_get(sdev))) { kfree(wqw); - spi_dv_pending(sdev) = 0; + spi_dv_pending(sdev->sdev_target) = 0; return; } - INIT_WORK(&wqw->work, spi_dv_device_work_wrapper, wqw); + INIT_WORK(&wqw->work, spi_dv_device_work_wrapper); wqw->sdev = sdev; schedule_work(&wqw->work); } 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); + +int spi_populate_width_msg(unsigned char *msg, int width) +{ + msg[0] = EXTENDED_MESSAGE; + msg[1] = 2; + msg[2] = EXTENDED_WDTR; + msg[3] = width; + return 4; +} +EXPORT_SYMBOL_GPL(spi_populate_width_msg); + +int spi_populate_sync_msg(unsigned char *msg, int period, int offset) +{ + msg[0] = EXTENDED_MESSAGE; + msg[1] = 3; + msg[2] = EXTENDED_SDTR; + msg[3] = period; + msg[4] = offset; + return 5; +} +EXPORT_SYMBOL_GPL(spi_populate_sync_msg); + +int spi_populate_ppr_msg(unsigned char *msg, int period, int offset, + int width, int options) +{ + msg[0] = EXTENDED_MESSAGE; + msg[1] = 6; + msg[2] = EXTENDED_PPR; + msg[3] = period; + msg[4] = 0; + msg[5] = offset; + msg[6] = width; + msg[7] = options; + return 8; +} +EXPORT_SYMBOL_GPL(spi_populate_ppr_msg); + +#ifdef CONFIG_SCSI_CONSTANTS +static const char * const one_byte_msgs[] = { +/* 0x00 */ "Task Complete", NULL /* Extended Message */, "Save Pointers", +/* 0x03 */ "Restore Pointers", "Disconnect", "Initiator Error", +/* 0x06 */ "Abort Task Set", "Message Reject", "Nop", "Message Parity Error", +/* 0x0a */ "Linked Command Complete", "Linked Command Complete w/flag", +/* 0x0c */ "Target Reset", "Abort Task", "Clear Task Set", +/* 0x0f */ "Initiate Recovery", "Release Recovery", +/* 0x11 */ "Terminate Process", "Continue Task", "Target Transfer Disable", +/* 0x14 */ NULL, NULL, "Clear ACA", "LUN Reset" +}; + +static const char * const two_byte_msgs[] = { +/* 0x20 */ "Simple Queue Tag", "Head of Queue Tag", "Ordered Queue Tag", +/* 0x23 */ "Ignore Wide Residue", "ACA" +}; + +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", "Modify Bidirectional Data Pointer" +}; + +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]); +} + +static void print_ptr(const unsigned char *msg, int msb, const char *desc) +{ + int ptr = (msg[msb] << 24) | (msg[msb+1] << 16) | (msg[msb+2] << 8) | + msg[msb+3]; + printk("%s = %d ", desc, ptr); +} + +int spi_print_msg(const unsigned char *msg) +{ + int len = 1, i; + if (msg[0] == EXTENDED_MESSAGE) { + len = 2 + msg[1]; + if (len == 2) + len += 256; + 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: + print_ptr(msg, 3, "pointer"); + 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; + case EXTENDED_MODIFY_BIDI_DATA_PTR: + print_ptr(msg, 3, "out"); + print_ptr(msg, 7, "in"); + 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); + /* Normal One byte */ + } else if (msg[0] < 0x1f) { + if (msg[0] < ARRAY_SIZE(one_byte_msgs) && one_byte_msgs[msg[0]]) + printk("%s ", one_byte_msgs[msg[0]]); + else + printk("reserved (%02x) ", msg[0]); + } else if (msg[0] == 0x55) { + printk("QAS Request "); + /* 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 = 1, i; + + if (msg[0] == EXTENDED_MESSAGE) { + len = 2 + msg[1]; + if (len == 2) + len += 256; + for (i = 0; i < len; ++i) + printk("%02x ", msg[i]); + /* Identify */ + } else if (msg[0] & 0x80) { + printk("%02x ", msg[0]); + /* Normal One byte */ + } else if ((msg[0] < 0x1f) || (msg[0] == 0x55)) { + printk("%02x ", msg[0]); + /* 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) { \ @@ -652,27 +1257,111 @@ 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) { \ + i->private_host_attrs[count].attr.mode = S_IRUGO; \ + i->private_host_attrs[count].store = NULL; \ + } \ + 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) { - struct spi_internal *i = kmalloc(sizeof(struct spi_internal), - GFP_KERNEL); int count = 0; + struct spi_internal *i = kzalloc(sizeof(struct spi_internal), + GFP_KERNEL); + if (unlikely(!i)) return NULL; - memset(i, 0, sizeof(struct spi_internal)); - - - i->t.attrs = &i->attrs[0]; - i->t.class = &spi_transport_class; - i->t.setup = &spi_setup_transport_attrs; - i->t.size = sizeof(struct spi_transport_attrs); + 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.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); @@ -680,6 +1369,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 */ @@ -689,6 +1379,13 @@ spi_attach_transport(struct spi_function_template *ft) i->attrs[count] = NULL; + count = 0; + SETUP_HOST_ATTRIBUTE(signalling); + + BUG_ON(count > SPI_HOST_ATTRS); + + i->host_attrs[count] = NULL; + return &i->t; } EXPORT_SYMBOL(spi_attach_transport); @@ -697,10 +1394,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");