From: Ryan Powers <rpowers@redhat.com> Subject: [RHEL5.1 PATCH] Repost: Update aic94xx and libsas. Date: Thu, 21 Jun 2007 19:18:21 -0400 Bugzilla: 224694 Message-Id: <467B073D.1060104@redhat.com> Changelog: [scsi] Update aic94xx and libsas to 1.0.3 The original posting is as follows: >This patch comes from Adaptec and IBM and brings aic94xx in line with >upstream's 1.0.3 (from 2.6.20-rc6). I've tested this on x86_64 and >i386 and have not run into any problems, but we lack the hardware to >test on more architectures. >These are the notes from Adaptec: >Released Note: >1. This patch is based on scsi-misc-2.6.git source code on > 01/29/2007 that is equivalent to Kernel 2.6.20-rc6. >2. This patch still use embedded Sequencer Firmware rather than > load firmware from file. >3. Fix ascb race condition on platforms with expanders. >4. Add REQ_TASK_ABORT and DEVICE_RESET handlers. >5. Proper clean up of phys/port after a discovery error. >6. Don't BUG when connecting two expanders via wide. >7. Add the ability to enable/disable phys through sysfs. >8. Extend use of DDB lock to prevent race condition of DDB.] > >These are the upstream commits that have been included in this patch: >f456393e195e0aa16029985f63cd93b601a0d315 >79a5eb609b74e7b3638861c41b98eafa74920a1f >fe4a36cf652031d2744a536ba5121032840380cb >dea22214790d1306f3a3444db13d2c726037b189 >7b4feee973ca7be63345b92a987ef7ef879b179b >8880839815265ccc0edaff52ba08d750eea57acb >bf451207511d049189ddb0a4eae3acdb086a3c82 >b218a0d8e250e0ae8fd4d4e45bd66a588b380752 >acbf167d4ad8c27f9743a4b539d51ae9535bf21c >6d4dcd4dae25c48e8932326aaedfe560d7f2c7bb >cde3f74bac3e4a6bcdc3a6370af38179fd8ef1f2 >f12164200f09ec10764f2cf96da335fd83062bc4 >c8490f3a77805d04321d9e44486a679801a035b8 >8f3b8fa9afe75cafc4feb317d305444f6c5271fb >37958fb040cf6f88b354b9fa7e846014ffbd3b73 >3ebf6922b0833807e54c73f4794c74baf9945fc8 >396819fba821ad56f1b90090d256f0ab726c89c5 >3cd041fb7f50f4cee3bc3a2b0ce02b1562894894 >bf2a1928f3e5d44934e974940a8260a57fcc8a58 >3b709df5f7c83b6b0907217a248a1414a37ffcb6 >57ba07dc54b7657e69fe8ac42d83df21e415c85b >fe3b5bfe73ace420709f0cfb198b0ffc704bd38b >e7571c152dea576f8c80ca240befc93d4f16551d >980fa2f9d64b9be96107c89e165953ace311af54 >6b0efb8516a5298e12033df61f9e0c376a306adb >02cd743bb3a37f27681c487608fb819493fa4010 >3b6e9fafc40e36f50f0bd0f1ee758eecd79f1098 >6f63caae2172e97e528b58319480217b0b36542e >21434966462d57145c861b43f6206d945ac57630 >dca84e4694419adf61ad052b1e5a50ac82726597 >ad689233bee854dced741c91aff12a8771a22f6f >111367f5c9a0af0f3a42c39dee7360ca217cba1d >f27708fc7523a87e3e69cae5628015961f0d3061 In addition to the above information, this patch now includes >8fdcf86af61bfba744f5868ec04dad71637ac33a >214fbb75075efa677b614be79a2d62dd79785b4f >058e2c474897dc53c88ac9162f9a9b16a879b8cd >a9344e68ac0a656475006737dbc258d69fe4f7b0 >423f7cf467045eab616f97309aed87a54b5e351d >63bb1bf0400414c0bc51cf276daa0fb5168d1e61 Some of the changes in the patch were modified so as to not break KABI. I ran it through the checking script and it comes out as not breaking anything. The patch builds cleanly, and I have tested it on i686. I have asked IBM to aid in testing the patch on other architectures. --- drivers/scsi/aic94xx/aic94xx.h | 7 drivers/scsi/aic94xx/aic94xx_dev.c | 16 - drivers/scsi/aic94xx/aic94xx_hwi.c | 30 ++- drivers/scsi/aic94xx/aic94xx_hwi.h | 19 +- drivers/scsi/aic94xx/aic94xx_init.c | 108 +++++++++--- drivers/scsi/aic94xx/aic94xx_reg_def.h | 2 drivers/scsi/aic94xx/aic94xx_sas.h | 2 drivers/scsi/aic94xx/aic94xx_scb.c | 240 ++++++++++++++++++++++++-- drivers/scsi/aic94xx/aic94xx_sds.c | 8 drivers/scsi/aic94xx/aic94xx_seq.c | 43 +++- drivers/scsi/aic94xx/aic94xx_seq.h | 4 drivers/scsi/aic94xx/aic94xx_task.c | 17 + drivers/scsi/aic94xx/aic94xx_tmf.c | 18 +- drivers/scsi/libsas/sas_discover.c | 64 ++++--- drivers/scsi/libsas/sas_event.c | 11 - drivers/scsi/libsas/sas_expander.c | 218 +++++++++++++++--------- drivers/scsi/libsas/sas_init.c | 95 +++++++++- drivers/scsi/libsas/sas_internal.h | 11 - drivers/scsi/libsas/sas_phy.c | 18 +- drivers/scsi/libsas/sas_port.c | 14 - drivers/scsi/libsas/sas_scsi_host.c | 294 +++++++++++++++++++++++++++++---- drivers/scsi/scsi_error.c | 47 +++-- drivers/scsi/scsi_priv.h | 8 drivers/scsi/scsi_transport_sas.c | 204 +++++++++++++++++++--- include/scsi/libsas.h | 57 +++++- include/scsi/sas.h | 17 - include/scsi/scsi_transport_sas.h | 84 +++++++++ 27 files changed, 1321 insertions(+), 335 deletions(-) --- diff -urNp linux-2.6.18.orig/drivers/scsi/aic94xx/aic94xx_dev.c linux-2.6.18/drivers/scsi/aic94xx/aic94xx_dev.c --- linux-2.6.18.orig/drivers/scsi/aic94xx/aic94xx_dev.c 2007-06-21 16:43:05.000000000 -0400 +++ linux-2.6.18/drivers/scsi/aic94xx/aic94xx_dev.c 2007-06-21 16:47:04.000000000 -0400 @@ -37,18 +37,14 @@ static inline int asd_get_ddb(struct asd_ha_struct *asd_ha) { - unsigned long flags; int ddb, i; - spin_lock_irqsave(&asd_ha->hw_prof.ddb_lock, flags); ddb = FIND_FREE_DDB(asd_ha); if (ddb >= asd_ha->hw_prof.max_ddbs) { ddb = -ENOMEM; - spin_unlock_irqrestore(&asd_ha->hw_prof.ddb_lock, flags); goto out; } SET_DDB(ddb, asd_ha); - spin_unlock_irqrestore(&asd_ha->hw_prof.ddb_lock, flags); for (i = 0; i < sizeof(struct asd_ddb_ssp_smp_target_port); i+= 4) asd_ddbsite_write_dword(asd_ha, ddb, i, 0); @@ -77,14 +73,10 @@ out: static inline void asd_free_ddb(struct asd_ha_struct *asd_ha, int ddb) { - unsigned long flags; - if (!ddb || ddb >= 0xFFFF) return; asd_ddbsite_write_byte(asd_ha, ddb, DDB_TYPE, DDB_TYPE_UNUSED); - spin_lock_irqsave(&asd_ha->hw_prof.ddb_lock, flags); CLEAR_DDB(ddb, asd_ha); - spin_unlock_irqrestore(&asd_ha->hw_prof.ddb_lock, flags); } static inline void asd_set_ddb_type(struct domain_device *dev) @@ -320,8 +312,11 @@ out: int asd_dev_found(struct domain_device *dev) { + unsigned long flags; int res = 0; + struct asd_ha_struct *asd_ha = dev->port->ha->lldd_ha; + spin_lock_irqsave(&asd_ha->hw_prof.ddb_lock, flags); switch (dev->dev_type) { case SATA_PM: res = asd_init_sata_pm_ddb(dev); @@ -335,14 +330,18 @@ int asd_dev_found(struct domain_device * else res = asd_init_initiator_ddb(dev); } + spin_unlock_irqrestore(&asd_ha->hw_prof.ddb_lock, flags); + return res; } void asd_dev_gone(struct domain_device *dev) { int ddb, sister_ddb; + unsigned long flags; struct asd_ha_struct *asd_ha = dev->port->ha->lldd_ha; + spin_lock_irqsave(&asd_ha->hw_prof.ddb_lock, flags); ddb = (int) (unsigned long) dev->lldd_dev; sister_ddb = asd_ddbsite_read_word(asd_ha, ddb, SISTER_DDB); @@ -350,4 +349,5 @@ void asd_dev_gone(struct domain_device * asd_free_ddb(asd_ha, sister_ddb); asd_free_ddb(asd_ha, ddb); dev->lldd_dev = NULL; + spin_unlock_irqrestore(&asd_ha->hw_prof.ddb_lock, flags); } diff -urNp linux-2.6.18.orig/drivers/scsi/aic94xx/aic94xx.h linux-2.6.18/drivers/scsi/aic94xx/aic94xx.h --- linux-2.6.18.orig/drivers/scsi/aic94xx/aic94xx.h 2007-06-21 16:43:05.000000000 -0400 +++ linux-2.6.18/drivers/scsi/aic94xx/aic94xx.h 2007-06-21 17:41:13.000000000 -0400 @@ -56,8 +56,8 @@ /* 2*ITNL timeout + 1 second */ #define AIC94XX_SCB_TIMEOUT (5*HZ) -extern kmem_cache_t *asd_dma_token_cache; -extern kmem_cache_t *asd_ascb_cache; +extern struct kmem_cache *asd_dma_token_cache; +extern struct kmem_cache *asd_ascb_cache; extern char sas_addr_str[2*SAS_ADDR_SIZE + 1]; static inline void asd_stringify_sas_addr(char *p, const u8 *sas_addr) @@ -109,6 +109,7 @@ int asd_clear_nexus_port(struct asd_sas int asd_clear_nexus_ha(struct sas_ha_struct *sas_ha); /* ---------- Phy Management ---------- */ -int asd_control_phy(struct asd_sas_phy *phy, enum phy_func func); +int asd_control_phy_wrap(struct asd_sas_phy *phy, enum phy_func func); +int asd_control_phy(struct asd_sas_phy *phy, enum phy_func func, void *arg); #endif diff -urNp linux-2.6.18.orig/drivers/scsi/aic94xx/aic94xx_hwi.c linux-2.6.18/drivers/scsi/aic94xx/aic94xx_hwi.c --- linux-2.6.18.orig/drivers/scsi/aic94xx/aic94xx_hwi.c 2007-06-21 16:43:05.000000000 -0400 +++ linux-2.6.18/drivers/scsi/aic94xx/aic94xx_hwi.c 2007-06-21 16:55:13.000000000 -0400 @@ -96,7 +96,7 @@ static int asd_init_phy(struct asd_phy * sas_phy->type = PHY_TYPE_PHYSICAL; sas_phy->role = PHY_ROLE_INITIATOR; sas_phy->oob_mode = OOB_NOT_CONNECTED; - sas_phy->linkrate = PHY_LINKRATE_NONE; + sas_phy->linkrate = SAS_LINK_RATE_UNKNOWN; phy->id_frm_tok = asd_alloc_coherent(asd_ha, sizeof(*phy->identify_frame), @@ -112,6 +112,21 @@ static int asd_init_phy(struct asd_phy * return 0; } +static void asd_init_ports(struct asd_ha_struct *asd_ha) +{ + int i; + + spin_lock_init(&asd_ha->asd_ports_lock); + for (i = 0; i < ASD_MAX_PHYS; i++) { + struct asd_port *asd_port = &asd_ha->asd_ports[i]; + + memset(asd_port->sas_addr, 0, SAS_ADDR_SIZE); + memset(asd_port->attached_sas_addr, 0, SAS_ADDR_SIZE); + asd_port->phy_mask = 0; + asd_port->num_phys = 0; + } +} + static int asd_init_phys(struct asd_ha_struct *asd_ha) { u8 i; @@ -121,6 +136,7 @@ static int asd_init_phys(struct asd_ha_s struct asd_phy *phy = &asd_ha->phys[i]; phy->phy_desc = &asd_ha->hw_prof.phy_desc[i]; + phy->asd_port = NULL; phy->sas_phy.enabled = 0; phy->sas_phy.id = i; @@ -267,7 +283,7 @@ static int asd_init_dl(struct asd_ha_str /* ---------- EDB and ESCB init ---------- */ -static int asd_alloc_edbs(struct asd_ha_struct *asd_ha, unsigned int gfp_flags) +static int asd_alloc_edbs(struct asd_ha_struct *asd_ha, gfp_t gfp_flags) { struct asd_seq_data *seq = &asd_ha->seq; int i; @@ -298,7 +314,7 @@ Err_unroll: } static int asd_alloc_escbs(struct asd_ha_struct *asd_ha, - unsigned int gfp_flags) + gfp_t gfp_flags) { struct asd_seq_data *seq = &asd_ha->seq; struct asd_ascb *escb; @@ -658,6 +674,8 @@ int asd_init_hw(struct asd_ha_struct *as goto Out; } + asd_init_ports(asd_ha); + err = asd_init_scbs(asd_ha); if (err) { asd_printk("couldn't initialize scbs for %s\n", @@ -1028,9 +1046,9 @@ irqreturn_t asd_hw_isr(int irq, void *de /* ---------- SCB handling ---------- */ static inline struct asd_ascb *asd_ascb_alloc(struct asd_ha_struct *asd_ha, - unsigned int gfp_flags) + gfp_t gfp_flags) { - extern kmem_cache_t *asd_ascb_cache; + extern struct kmem_cache *asd_ascb_cache; struct asd_seq_data *seq = &asd_ha->seq; struct asd_ascb *ascb; unsigned long flags; @@ -1086,7 +1104,7 @@ undo: */ struct asd_ascb *asd_ascb_alloc_list(struct asd_ha_struct *asd_ha, int *num, - unsigned int gfp_flags) + gfp_t gfp_flags) { struct asd_ascb *first = NULL; diff -urNp linux-2.6.18.orig/drivers/scsi/aic94xx/aic94xx_hwi.h linux-2.6.18/drivers/scsi/aic94xx/aic94xx_hwi.h --- linux-2.6.18.orig/drivers/scsi/aic94xx/aic94xx_hwi.h 2007-06-21 16:43:05.000000000 -0400 +++ linux-2.6.18/drivers/scsi/aic94xx/aic94xx_hwi.h 2007-06-21 16:47:04.000000000 -0400 @@ -46,6 +46,7 @@ #define PCI_DEVICE_ID_ADAPTEC2_RAZOR10 0x410 #define PCI_DEVICE_ID_ADAPTEC2_RAZOR12 0x412 #define PCI_DEVICE_ID_ADAPTEC2_RAZOR1E 0x41E +#define PCI_DEVICE_ID_ADAPTEC2_RAZOR1F 0x41F #define PCI_DEVICE_ID_ADAPTEC2_RAZOR30 0x430 #define PCI_DEVICE_ID_ADAPTEC2_RAZOR32 0x432 #define PCI_DEVICE_ID_ADAPTEC2_RAZOR3E 0x43E @@ -192,6 +193,16 @@ struct asd_seq_data { struct asd_ascb **escb_arr; /* array of pointers to escbs */ }; +/* This is an internal port structure. These are used to get accurate + * phy_mask for updating DDB 0. + */ +struct asd_port { + u8 sas_addr[SAS_ADDR_SIZE]; + u8 attached_sas_addr[SAS_ADDR_SIZE]; + u32 phy_mask; + int num_phys; +}; + /* This is the Host Adapter structure. It describes the hardware * SAS adapter. */ @@ -210,6 +221,8 @@ struct asd_ha_struct { struct hw_profile hw_prof; struct asd_phy phys[ASD_MAX_PHYS]; + spinlock_t asd_ports_lock; + struct asd_port asd_ports[ASD_MAX_PHYS]; struct asd_sas_port ports[ASD_MAX_PHYS]; struct dma_pool *scb_pool; @@ -242,7 +255,7 @@ struct asd_ha_struct { /* ---------- DMA allocs ---------- */ -static inline struct asd_dma_tok *asd_dmatok_alloc(unsigned int flags) +static inline struct asd_dma_tok *asd_dmatok_alloc(gfp_t flags) { return kmem_cache_alloc(asd_dma_token_cache, flags); } @@ -254,7 +267,7 @@ static inline void asd_dmatok_free(struc static inline struct asd_dma_tok *asd_alloc_coherent(struct asd_ha_struct * asd_ha, size_t size, - unsigned int flags) + gfp_t flags) { struct asd_dma_tok *token = asd_dmatok_alloc(flags); if (token) { @@ -376,7 +389,7 @@ irqreturn_t asd_hw_isr(int irq, void *de struct asd_ascb *asd_ascb_alloc_list(struct asd_ha_struct *asd_ha, int *num, - unsigned int gfp_mask); + gfp_t gfp_mask); int asd_post_ascb_list(struct asd_ha_struct *asd_ha, struct asd_ascb *ascb, int num); diff -urNp linux-2.6.18.orig/drivers/scsi/aic94xx/aic94xx_init.c linux-2.6.18/drivers/scsi/aic94xx/aic94xx_init.c --- linux-2.6.18.orig/drivers/scsi/aic94xx/aic94xx_init.c 2007-06-21 16:43:05.000000000 -0400 +++ linux-2.6.18/drivers/scsi/aic94xx/aic94xx_init.c 2007-06-21 16:49:40.000000000 -0400 @@ -38,7 +38,7 @@ #include "aic94xx_seq.h" /* The format is "version.release.patchlevel" */ -#define ASD_DRIVER_VERSION "1.0.2" +#define ASD_DRIVER_VERSION "1.0.2-1" static int use_msi = 0; module_param_named(use_msi, use_msi, int, S_IRUGO); @@ -57,6 +57,8 @@ MODULE_PARM_DESC(collector, "\n" char sas_addr_str[2*SAS_ADDR_SIZE + 1] = ""; static struct scsi_transport_template *aic94xx_transport_template; +static int asd_scan_finished(struct Scsi_Host *, unsigned long); +static void asd_scan_start(struct Scsi_Host *); static struct scsi_host_template aic94xx_sht = { .module = THIS_MODULE, @@ -66,6 +68,8 @@ static struct scsi_host_template aic94xx .target_alloc = sas_target_alloc, .slave_configure = sas_slave_configure, .slave_destroy = sas_slave_destroy, + .scan_finished = asd_scan_finished, + .scan_start = asd_scan_start, .change_queue_depth = sas_change_queue_depth, .change_queue_type = sas_change_queue_type, .bios_param = sas_bios_param, @@ -75,6 +79,8 @@ static struct scsi_host_template aic94xx .sg_tablesize = SG_ALL, .max_sectors = SCSI_DEFAULT_MAX_SECTORS, .use_clustering = ENABLE_CLUSTERING, + .eh_device_reset_handler = sas_eh_device_reset_handler, + .eh_bus_reset_handler = sas_eh_bus_reset_handler, }; static int __devinit asd_map_memio(struct asd_ha_struct *asd_ha) @@ -234,15 +240,19 @@ static int __devinit asd_common_setup(st } /* Provide some sane default values. */ asd_ha->hw_prof.max_scbs = 512; - asd_ha->hw_prof.max_ddbs = 128; + asd_ha->hw_prof.max_ddbs = ASD_MAX_DDBS; asd_ha->hw_prof.num_phys = ASD_MAX_PHYS; /* All phys are enabled, by default. */ asd_ha->hw_prof.enabled_phys = 0xFF; for (i = 0; i < ASD_MAX_PHYS; i++) { - asd_ha->hw_prof.phy_desc[i].max_sas_lrate = PHY_LINKRATE_3; - asd_ha->hw_prof.phy_desc[i].min_sas_lrate = PHY_LINKRATE_1_5; - asd_ha->hw_prof.phy_desc[i].max_sata_lrate= PHY_LINKRATE_1_5; - asd_ha->hw_prof.phy_desc[i].min_sata_lrate= PHY_LINKRATE_1_5; + asd_ha->hw_prof.phy_desc[i].max_sas_lrate = + PHY_LINKRATE_3; + asd_ha->hw_prof.phy_desc[i].min_sas_lrate = + PHY_LINKRATE_1_5; + asd_ha->hw_prof.phy_desc[i].max_sata_lrate = + PHY_LINKRATE_1_5; + asd_ha->hw_prof.phy_desc[i].min_sata_lrate = + PHY_LINKRATE_1_5; } return 0; @@ -305,11 +315,29 @@ static ssize_t asd_show_dev_pcba_sn(stru } static DEVICE_ATTR(pcba_sn, S_IRUGO, asd_show_dev_pcba_sn, NULL); -static void asd_create_dev_attrs(struct asd_ha_struct *asd_ha) +static int asd_create_dev_attrs(struct asd_ha_struct *asd_ha) { - device_create_file(&asd_ha->pcidev->dev, &dev_attr_revision); - device_create_file(&asd_ha->pcidev->dev, &dev_attr_bios_build); - device_create_file(&asd_ha->pcidev->dev, &dev_attr_pcba_sn); + int err; + + err = device_create_file(&asd_ha->pcidev->dev, &dev_attr_revision); + if (err) + return err; + + err = device_create_file(&asd_ha->pcidev->dev, &dev_attr_bios_build); + if (err) + goto err_rev; + + err = device_create_file(&asd_ha->pcidev->dev, &dev_attr_pcba_sn); + if (err) + goto err_biosb; + + return 0; + +err_biosb: + device_remove_file(&asd_ha->pcidev->dev, &dev_attr_bios_build); +err_rev: + device_remove_file(&asd_ha->pcidev->dev, &dev_attr_revision); + return err; } static void asd_remove_dev_attrs(struct asd_ha_struct *asd_ha) @@ -428,8 +456,8 @@ static inline void asd_destroy_ha_caches asd_ha->scb_pool = NULL; } -kmem_cache_t *asd_dma_token_cache; -kmem_cache_t *asd_ascb_cache; +struct kmem_cache *asd_dma_token_cache; +struct kmem_cache *asd_ascb_cache; static int asd_create_global_caches(void) { @@ -504,6 +532,7 @@ static int asd_register_sas_ha(struct as asd_ha->sas_ha.num_phys= ASD_MAX_PHYS; asd_ha->sas_ha.lldd_queue_size = asd_ha->seq.can_queue; + asd_ha->sas_ha.lldd_max_execute_num = lldd_max_execute_num; return sas_register_ha(&asd_ha->sas_ha); } @@ -641,7 +670,9 @@ static int __devinit asd_pci_probe(struc } ASD_DPRINTK("escbs posted\n"); - asd_create_dev_attrs(asd_ha); + err = asd_create_dev_attrs(asd_ha); + if (err) + goto Err_dev_attrs; err = asd_register_sas_ha(asd_ha); if (err) @@ -664,6 +695,7 @@ Err_en_phys: asd_unregister_sas_ha(asd_ha); Err_reg_sas: asd_remove_dev_attrs(asd_ha); +Err_dev_attrs: Err_escbs: asd_disable_ints(asd_ha); free_irq(dev->irq, asd_ha); @@ -699,6 +731,15 @@ static void asd_free_queues(struct asd_h list_for_each_safe(pos, n, &pending) { struct asd_ascb *ascb = list_entry(pos, struct asd_ascb, list); + /* + * Delete unexpired ascb timers. This may happen if we issue + * a CONTROL PHY scb to an adapter and rmmod before the scb + * times out. Apparently we don't wait for the CONTROL PHY + * to complete, so it doesn't matter if we kill the timer. + */ + del_timer_sync(&ascb->timer); + WARN_ON(ascb->scb->header.opcode != CONTROL_PHY); + list_del_init(pos); ASD_DPRINTK("freeing from pending\n"); asd_ascb_free(ascb); @@ -744,15 +785,37 @@ static void __devexit asd_pci_remove(str return; } +static void asd_scan_start(struct Scsi_Host *shost) +{ + struct asd_ha_struct *asd_ha; + int err; + + asd_ha = SHOST_TO_SAS_HA(shost)->lldd_ha; + err = asd_enable_phys(asd_ha, asd_ha->hw_prof.enabled_phys); + if (err) + asd_printk("Couldn't enable phys, err:%d\n", err); +} + +static int asd_scan_finished(struct Scsi_Host *shost, unsigned long time) +{ + /* give the phy enabling interrupt event time to come in (1s + * is empirically about all it takes) */ + if (time < HZ) + return 0; + /* Wait for discovery to finish */ + scsi_flush_work(shost); + return 1; +} + static ssize_t asd_version_show(struct device_driver *driver, char *buf) { return snprintf(buf, PAGE_SIZE, "%s\n", ASD_DRIVER_VERSION); } static DRIVER_ATTR(version, S_IRUGO, asd_version_show, NULL); -static void asd_create_driver_attrs(struct device_driver *driver) +static int asd_create_driver_attrs(struct device_driver *driver) { - driver_create_file(driver, &driver_attr_version); + return driver_create_file(driver, &driver_attr_version); } static void asd_remove_driver_attrs(struct device_driver *driver) @@ -761,8 +824,6 @@ static void asd_remove_driver_attrs(stru } static struct sas_domain_function_template aic94xx_transport_functions = { - .lldd_port_formed = asd_update_port_links, - .lldd_dev_found = asd_dev_found, .lldd_dev_gone = asd_dev_gone, @@ -779,7 +840,8 @@ static struct sas_domain_function_templa .lldd_clear_nexus_port = asd_clear_nexus_port, .lldd_clear_nexus_ha = asd_clear_nexus_ha, - .lldd_control_phy = asd_control_phy, + .lldd_control_phy = asd_control_phy_wrap, + .lldd_control_phy_new = asd_control_phy, }; static const struct pci_device_id aic94xx_pci_table[] __devinitdata = { @@ -789,6 +851,8 @@ static const struct pci_device_id aic94x 0, 0, 1}, {PCI_DEVICE(PCI_VENDOR_ID_ADAPTEC2, PCI_DEVICE_ID_ADAPTEC2_RAZOR1E), 0, 0, 1}, + {PCI_DEVICE(PCI_VENDOR_ID_ADAPTEC2, PCI_DEVICE_ID_ADAPTEC2_RAZOR1F), + 0, 0, 1}, {PCI_DEVICE(PCI_VENDOR_ID_ADAPTEC2, PCI_DEVICE_ID_ADAPTEC2_RAZOR30), 0, 0, 2}, {PCI_DEVICE(PCI_VENDOR_ID_ADAPTEC2, PCI_DEVICE_ID_ADAPTEC2_RAZOR32), @@ -823,17 +887,21 @@ static int __init aic94xx_init(void) aic94xx_transport_template = sas_domain_attach_transport(&aic94xx_transport_functions); - if (err) + if (!aic94xx_transport_template) goto out_destroy_caches; err = pci_register_driver(&aic94xx_pci_driver); if (err) goto out_release_transport; - asd_create_driver_attrs(&aic94xx_pci_driver.driver); + err = asd_create_driver_attrs(&aic94xx_pci_driver.driver); + if (err) + goto out_unregister_pcidrv; return err; + out_unregister_pcidrv: + pci_unregister_driver(&aic94xx_pci_driver); out_release_transport: sas_release_transport(aic94xx_transport_template); out_destroy_caches: diff -urNp linux-2.6.18.orig/drivers/scsi/aic94xx/aic94xx_reg_def.h linux-2.6.18/drivers/scsi/aic94xx/aic94xx_reg_def.h --- linux-2.6.18.orig/drivers/scsi/aic94xx/aic94xx_reg_def.h 2007-06-21 16:43:05.000000000 -0400 +++ linux-2.6.18/drivers/scsi/aic94xx/aic94xx_reg_def.h 2007-06-21 16:47:04.000000000 -0400 @@ -2000,7 +2000,7 @@ * The host accesses this scratch in a different manner from the * central sequencer. The sequencer has to use CSEQ registers CSCRPAGE * and CMnSCRPAGE to access the scratch memory. A flat mapping of the - * scratch memory is avaliable for software convenience and to prevent + * scratch memory is available for software convenience and to prevent * corruption while the sequencer is running. This memory is mapped * onto addresses 800h - BFFh, total of 400h bytes. * diff -urNp linux-2.6.18.orig/drivers/scsi/aic94xx/aic94xx_sas.h linux-2.6.18/drivers/scsi/aic94xx/aic94xx_sas.h --- linux-2.6.18.orig/drivers/scsi/aic94xx/aic94xx_sas.h 2007-06-21 16:43:05.000000000 -0400 +++ linux-2.6.18/drivers/scsi/aic94xx/aic94xx_sas.h 2007-06-21 16:47:04.000000000 -0400 @@ -34,6 +34,7 @@ * domain that this sequencer can maintain low-level connections for * us. They are be 64 bytes. */ +#define ASD_MAX_DDBS 128 struct asd_ddb_ssp_smp_target_port { u8 conn_type; /* byte 0 */ @@ -733,6 +734,7 @@ struct asd_phy { struct sas_identify_frame *identify_frame; struct asd_dma_tok *id_frm_tok; + struct asd_port *asd_port; u8 frame_rcvd[ASD_EDB_SIZE]; }; diff -urNp linux-2.6.18.orig/drivers/scsi/aic94xx/aic94xx_scb.c linux-2.6.18/drivers/scsi/aic94xx/aic94xx_scb.c --- linux-2.6.18.orig/drivers/scsi/aic94xx/aic94xx_scb.c 2007-06-21 16:43:05.000000000 -0400 +++ linux-2.6.18/drivers/scsi/aic94xx/aic94xx_scb.c 2007-06-21 16:50:29.000000000 -0400 @@ -25,6 +25,7 @@ */ #include <linux/pci.h> +#include <scsi/scsi_host.h> #include "aic94xx.h" #include "aic94xx_reg.h" @@ -52,6 +53,8 @@ static inline void get_lrate_mode(struct asd_phy *phy, u8 oob_mode) { + struct sas_phy *sas_phy = phy->sas_phy.phy; + switch (oob_mode & 7) { case PHY_SPEED_60: /* FIXME: sas transport class doesn't have this */ @@ -59,7 +62,7 @@ static inline void get_lrate_mode(struct phy->sas_phy.phy->negotiated_linkrate = SAS_LINK_RATE_6_0_GBPS; break; case PHY_SPEED_30: - phy->sas_phy.linkrate = PHY_LINKRATE_3; + phy->sas_phy.linkrate = PHY_LINKRATE_6; phy->sas_phy.phy->negotiated_linkrate = SAS_LINK_RATE_3_0_GBPS; break; case PHY_SPEED_15: @@ -67,6 +70,12 @@ static inline void get_lrate_mode(struct phy->sas_phy.phy->negotiated_linkrate = SAS_LINK_RATE_1_5_GBPS; break; } + sas_phy->negotiated_linkrate = phy_linkrate_to_linkrate(phy->sas_phy.linkrate); + sas_phy->maximum_linkrate_hw = SAS_LINK_RATE_3_0_GBPS; + sas_phy->minimum_linkrate_hw = SAS_LINK_RATE_1_5_GBPS; + sas_phy->maximum_linkrate = phy_linkrate_to_linkrate(phy->phy_desc->max_sas_lrate); + sas_phy->minimum_linkrate = phy_linkrate_to_linkrate(phy->phy_desc->min_sas_lrate); + if (oob_mode & SAS_MODE) phy->sas_phy.oob_mode = SAS_OOB_MODE; else if (oob_mode & SATA_MODE) @@ -160,6 +169,70 @@ static inline void asd_get_attached_sas_ } } +static void asd_form_port(struct asd_ha_struct *asd_ha, struct asd_phy *phy) +{ + int i; + struct asd_port *free_port = NULL; + struct asd_port *port; + struct asd_sas_phy *sas_phy = &phy->sas_phy; + unsigned long flags; + + spin_lock_irqsave(&asd_ha->asd_ports_lock, flags); + if (!phy->asd_port) { + for (i = 0; i < ASD_MAX_PHYS; i++) { + port = &asd_ha->asd_ports[i]; + + /* Check for wide port */ + if (port->num_phys > 0 && + memcmp(port->sas_addr, sas_phy->sas_addr, + SAS_ADDR_SIZE) == 0 && + memcmp(port->attached_sas_addr, + sas_phy->attached_sas_addr, + SAS_ADDR_SIZE) == 0) { + break; + } + + /* Find a free port */ + if (port->num_phys == 0 && free_port == NULL) { + free_port = port; + } + } + + /* Use a free port if this doesn't form a wide port */ + if (i >= ASD_MAX_PHYS) { + port = free_port; + BUG_ON(!port); + memcpy(port->sas_addr, sas_phy->sas_addr, + SAS_ADDR_SIZE); + memcpy(port->attached_sas_addr, + sas_phy->attached_sas_addr, + SAS_ADDR_SIZE); + } + port->num_phys++; + port->phy_mask |= (1U << sas_phy->id); + phy->asd_port = port; + } + ASD_DPRINTK("%s: updating phy_mask 0x%x for phy%d\n", + __FUNCTION__, phy->asd_port->phy_mask, sas_phy->id); + asd_update_port_links(asd_ha, phy); + spin_unlock_irqrestore(&asd_ha->asd_ports_lock, flags); +} + +static void asd_deform_port(struct asd_ha_struct *asd_ha, struct asd_phy *phy) +{ + struct asd_port *port = phy->asd_port; + struct asd_sas_phy *sas_phy = &phy->sas_phy; + unsigned long flags; + + spin_lock_irqsave(&asd_ha->asd_ports_lock, flags); + if (port) { + port->num_phys--; + port->phy_mask &= ~(1U << sas_phy->id); + phy->asd_port = NULL; + } + spin_unlock_irqrestore(&asd_ha->asd_ports_lock, flags); +} + static inline void asd_bytes_dmaed_tasklet(struct asd_ascb *ascb, struct done_list_struct *dl, int edb_id, int phy_id) @@ -179,6 +252,7 @@ static inline void asd_bytes_dmaed_taskl asd_get_attached_sas_addr(phy, phy->sas_phy.attached_sas_addr); spin_unlock_irqrestore(&phy->sas_phy.frame_rcvd_lock, flags); asd_dump_frame_rcvd(phy, dl); + asd_form_port(ascb->ha, phy); sas_ha->notify_port_event(&phy->sas_phy, PORTE_BYTES_DMAED); } @@ -189,6 +263,7 @@ static inline void asd_link_reset_err_ta struct asd_ha_struct *asd_ha = ascb->ha; struct sas_ha_struct *sas_ha = &asd_ha->sas_ha; struct asd_sas_phy *sas_phy = sas_ha->sas_phy[phy_id]; + struct asd_phy *phy = &asd_ha->phys[phy_id]; u8 lr_error = dl->status_block[1]; u8 retries_left = dl->status_block[2]; @@ -213,6 +288,7 @@ static inline void asd_link_reset_err_ta asd_turn_led(asd_ha, phy_id, 0); sas_phy_disconnected(sas_phy); + asd_deform_port(asd_ha, phy); sas_ha->notify_port_event(sas_phy, PORTE_LINK_RESET_ERR); if (retries_left == 0) { @@ -240,6 +316,8 @@ static inline void asd_primitive_rcvd_ta unsigned long flags; struct sas_ha_struct *sas_ha = &ascb->ha->sas_ha; struct asd_sas_phy *sas_phy = sas_ha->sas_phy[phy_id]; + struct asd_ha_struct *asd_ha = ascb->ha; + struct asd_phy *phy = &asd_ha->phys[phy_id]; u8 reg = dl->status_block[1]; u32 cont = dl->status_block[2] << ((reg & 3)*8); @@ -276,6 +354,7 @@ static inline void asd_primitive_rcvd_ta phy_id); /* The sequencer disables all phys on that port. * We have to re-enable the phys ourselves. */ + asd_deform_port(asd_ha, phy); sas_ha->notify_port_event(sas_phy, PORTE_HARD_RESET); break; @@ -343,6 +422,7 @@ static void escb_tasklet_complete(struct u8 sb_opcode = dl->status_block[0]; int phy_id = sb_opcode & DL_PHY_MASK; struct asd_sas_phy *sas_phy = sas_ha->sas_phy[phy_id]; + struct asd_phy *phy = &asd_ha->phys[phy_id]; if (edb > 6 || edb < 0) { ASD_DPRINTK("edb is 0x%x! dl->opcode is 0x%x\n", @@ -360,6 +440,118 @@ static void escb_tasklet_complete(struct ascb->scb->header.opcode); } + /* Catch these before we mask off the sb_opcode bits */ + switch (sb_opcode) { + case REQ_TASK_ABORT: { + struct asd_ascb *a, *b; + u16 tc_abort; + struct domain_device *failed_dev = NULL; + + ASD_DPRINTK("%s: REQ_TASK_ABORT, reason=0x%X\n", + __FUNCTION__, dl->status_block[3]); + + /* + * Find the task that caused the abort and abort it first. + * The sequencer won't put anything on the done list until + * that happens. + */ + tc_abort = *((u16*)(&dl->status_block[1])); + tc_abort = le16_to_cpu(tc_abort); + + list_for_each_entry_safe(a, b, &asd_ha->seq.pend_q, list) { + struct sas_task *task = ascb->uldd_task; + + if (task && a->tc_index == tc_abort) { + failed_dev = task->dev; + sas_task_abort(task); + break; + } + } + + if (!failed_dev) { + ASD_DPRINTK("%s: Can't find task (tc=%d) to abort!\n", + __FUNCTION__, tc_abort); + goto out; + } + + /* + * Now abort everything else for that device (hba?) so + * that the EH will wake up and do something. + */ + list_for_each_entry_safe(a, b, &asd_ha->seq.pend_q, list) { + struct sas_task *task = ascb->uldd_task; + + if (task && + task->dev == failed_dev && + a->tc_index != tc_abort) + sas_task_abort(task); + } + + goto out; + } + case REQ_DEVICE_RESET: { + struct asd_ascb *a; + u16 conn_handle; + unsigned long flags; + struct sas_task *last_dev_task = NULL; + + conn_handle = *((u16*)(&dl->status_block[1])); + conn_handle = le16_to_cpu(conn_handle); + + ASD_DPRINTK("%s: REQ_DEVICE_RESET, reason=0x%X\n", __FUNCTION__, + dl->status_block[3]); + + /* Find the last pending task for the device... */ + list_for_each_entry(a, &asd_ha->seq.pend_q, list) { + u16 x; + struct domain_device *dev; + struct sas_task *task = a->uldd_task; + + if (!task) + continue; + dev = task->dev; + + x = (unsigned long)dev->lldd_dev; + if (x == conn_handle) + last_dev_task = task; + } + + if (!last_dev_task) { + ASD_DPRINTK("%s: Device reset for idle device %d?\n", + __FUNCTION__, conn_handle); + goto out; + } + + /* ...and set the reset flag */ + spin_lock_irqsave(&last_dev_task->task_state_lock, flags); + last_dev_task->task_state_flags |= SAS_TASK_NEED_DEV_RESET; + spin_unlock_irqrestore(&last_dev_task->task_state_lock, flags); + + /* Kill all pending tasks for the device */ + list_for_each_entry(a, &asd_ha->seq.pend_q, list) { + u16 x; + struct domain_device *dev; + struct sas_task *task = a->uldd_task; + + if (!task) + continue; + dev = task->dev; + + x = (unsigned long)dev->lldd_dev; + if (x == conn_handle) + sas_task_abort(task); + } + + goto out; + } + case SIGNAL_NCQ_ERROR: + ASD_DPRINTK("%s: SIGNAL_NCQ_ERROR\n", __FUNCTION__); + goto out; + case CLEAR_NCQ_ERROR: + ASD_DPRINTK("%s: CLEAR_NCQ_ERROR\n", __FUNCTION__); + goto out; + } + sb_opcode &= ~DL_PHY_MASK; switch (sb_opcode) { @@ -387,24 +579,9 @@ static void escb_tasklet_complete(struct asd_turn_led(asd_ha, phy_id, 0); /* the device is gone */ sas_phy_disconnected(sas_phy); + asd_deform_port(asd_ha, phy); sas_ha->notify_port_event(sas_phy, PORTE_TIMER_EVENT); break; - case REQ_TASK_ABORT: - ASD_DPRINTK("%s: phy%d: REQ_TASK_ABORT\n", __FUNCTION__, - phy_id); - break; - case REQ_DEVICE_RESET: - ASD_DPRINTK("%s: phy%d: REQ_DEVICE_RESET\n", __FUNCTION__, - phy_id); - break; - case SIGNAL_NCQ_ERROR: - ASD_DPRINTK("%s: phy%d: SIGNAL_NCQ_ERROR\n", __FUNCTION__, - phy_id); - break; - case CLEAR_NCQ_ERROR: - ASD_DPRINTK("%s: phy%d: CLEAR_NCQ_ERROR\n", __FUNCTION__, - phy_id); - break; default: ASD_DPRINTK("%s: phy%d: unknown event:0x%x\n", __FUNCTION__, phy_id, sb_opcode); @@ -424,7 +601,7 @@ static void escb_tasklet_complete(struct break; } - +out: asd_invalidate_edb(ascb, edb); } @@ -710,14 +887,37 @@ static const int phy_func_table[] = { [PHY_FUNC_RELEASE_SPINUP_HOLD] = RELEASE_SPINUP_HOLD, }; -int asd_control_phy(struct asd_sas_phy *phy, enum phy_func func) +int asd_control_phy_wrap(struct asd_sas_phy *phy, enum phy_func func) +{ + return asd_control_phy(phy, func, NULL); +} + +int asd_control_phy(struct asd_sas_phy *phy, enum phy_func func, void *arg) { struct asd_ha_struct *asd_ha = phy->ha->lldd_ha; + struct asd_phy_desc *pd = asd_ha->phys[phy->id].phy_desc; struct asd_ascb *ascb; + struct sas_phy_linkrates *rates; int res = 1; - if (func == PHY_FUNC_CLEAR_ERROR_LOG) + switch (func) { + case PHY_FUNC_CLEAR_ERROR_LOG: return -ENOSYS; + case PHY_FUNC_SET_LINK_RATE: + rates = arg; + if (rates->minimum_linkrate) { + pd->min_sas_lrate = rates->minimum_linkrate; + pd->min_sata_lrate = rates->minimum_linkrate; + } + if (rates->maximum_linkrate) { + pd->max_sas_lrate = rates->maximum_linkrate; + pd->max_sata_lrate = rates->maximum_linkrate; + } + func = PHY_FUNC_LINK_RESET; + break; + default: + break; + } ascb = asd_ascb_alloc_list(asd_ha, &res, GFP_KERNEL); if (!ascb) diff -urNp linux-2.6.18.orig/drivers/scsi/aic94xx/aic94xx_sds.c linux-2.6.18/drivers/scsi/aic94xx/aic94xx_sds.c --- linux-2.6.18.orig/drivers/scsi/aic94xx/aic94xx_sds.c 2007-06-21 16:43:05.000000000 -0400 +++ linux-2.6.18/drivers/scsi/aic94xx/aic94xx_sds.c 2007-06-21 16:47:04.000000000 -0400 @@ -64,7 +64,7 @@ struct asd_ocm_dir { #define OCM_INIT_DIR_ENTRIES 5 /*************************************************************************** -* OCM dircetory default +* OCM directory default ***************************************************************************/ static struct asd_ocm_dir OCMDirInit = { @@ -73,7 +73,7 @@ static struct asd_ocm_dir OCMDirInit = }; /*************************************************************************** -* OCM dircetory Entries default +* OCM directory Entries default ***************************************************************************/ static struct asd_ocm_dir_ent OCMDirEntriesInit[OCM_INIT_DIR_ENTRIES] = { @@ -630,10 +630,6 @@ static int asd_flash_getid(struct asd_ha reg = asd_read_reg_dword(asd_ha, EXSICNFGR); - if (!(reg & FLASHEX)) { - ASD_DPRINTK("flash doesn't exist\n"); - return -ENOENT; - } if (pci_read_config_dword(asd_ha->pcidev, PCI_CONF_FLSH_BAR, &asd_ha->hw_prof.flash.bar)) { asd_printk("couldn't read PCI_CONF_FLSH_BAR of %s\n", diff -urNp linux-2.6.18.orig/drivers/scsi/aic94xx/aic94xx_seq.c linux-2.6.18/drivers/scsi/aic94xx/aic94xx_seq.c --- linux-2.6.18.orig/drivers/scsi/aic94xx/aic94xx_seq.c 2007-06-21 16:43:05.000000000 -0400 +++ linux-2.6.18/drivers/scsi/aic94xx/aic94xx_seq.c 2007-06-21 16:47:04.000000000 -0400 @@ -757,7 +757,7 @@ static void asd_init_lseq_mdp(struct asd asd_write_reg_word(asd_ha, LmSEQ_FIRST_INV_SCB_SITE(lseq), (u16)last_scb_site_no+1); asd_write_reg_word(asd_ha, LmSEQ_INTEN_SAVE(lseq), - (u16) LmM0INTEN_MASK & 0xFFFF0000 >> 16); + (u16) ((LmM0INTEN_MASK & 0xFFFF0000) >> 16)); asd_write_reg_word(asd_ha, LmSEQ_INTEN_SAVE(lseq) + 2, (u16) LmM0INTEN_MASK & 0xFFFF); asd_write_reg_byte(asd_ha, LmSEQ_LINK_RST_FRM_LEN(lseq), 0); @@ -900,6 +900,16 @@ static void asd_init_scb_sites(struct as for (i = 0; i < ASD_SCB_SIZE; i += 4) asd_scbsite_write_dword(asd_ha, site_no, i, 0); + /* Initialize SCB Site Opcode field to invalid. */ + asd_scbsite_write_byte(asd_ha, site_no, + offsetof(struct scb_header, opcode), + 0xFF); + + /* Initialize SCB Site Flags field to mean a response + * frame has been received. This means inadvertent + * frames received to be dropped. */ + asd_scbsite_write_byte(asd_ha, site_no, 0x49, 0x01); + /* Workaround needed by SEQ to fix a SATA issue is to exclude * certain SCB sites from the free list. */ if (!SCB_SITE_VALID(site_no)) @@ -915,16 +925,6 @@ static void asd_init_scb_sites(struct as /* Q_NEXT field of the last SCB is invalidated. */ asd_scbsite_write_word(asd_ha, site_no, 0, first_scb_site_no); - /* Initialize SCB Site Opcode field to invalid. */ - asd_scbsite_write_byte(asd_ha, site_no, - offsetof(struct scb_header, opcode), - 0xFF); - - /* Initialize SCB Site Flags field to mean a response - * frame has been received. This means inadvertent - * frames received to be dropped. */ - asd_scbsite_write_byte(asd_ha, site_no, 0x49, 0x01); - first_scb_site_no = site_no; max_scbs++; } @@ -1166,6 +1166,16 @@ static void asd_init_ddb_0(struct asd_ha set_bit(0, asd_ha->hw_prof.ddb_bitmap); } +static void asd_seq_init_ddb_sites(struct asd_ha_struct *asd_ha) +{ + unsigned int i; + unsigned int ddb_site; + + for (ddb_site = 0 ; ddb_site < ASD_MAX_DDBS; ddb_site++) + for (i = 0; i < sizeof(struct asd_ddb_ssp_smp_target_port); i+= 4) + asd_ddbsite_write_dword(asd_ha, ddb_site, i, 0); +} + /** * asd_seq_setup_seqs -- setup and initialize central and link sequencers * @asd_ha: pointer to host adapter structure @@ -1175,6 +1185,9 @@ static void asd_seq_setup_seqs(struct as int lseq; u8 lseq_mask; + /* Initialize DDB sites */ + asd_seq_init_ddb_sites(asd_ha); + /* Initialize SCB sites. Done first to compute some values which * the rest of the init code depends on. */ asd_init_scb_sites(asd_ha); @@ -1285,14 +1298,15 @@ int asd_start_seqs(struct asd_ha_struct * port_map_by_links is also used as the conn_mask byte in the * initiator/target port DDB. */ -void asd_update_port_links(struct asd_sas_phy *sas_phy) +void asd_update_port_links(struct asd_ha_struct *asd_ha, struct asd_phy *phy) { - struct asd_ha_struct *asd_ha = sas_phy->ha->lldd_ha; - const u8 phy_mask = (u8) sas_phy->port->phy_mask; + const u8 phy_mask = (u8) phy->asd_port->phy_mask; u8 phy_is_up; u8 mask; int i, err; + unsigned long flags; + spin_lock_irqsave(&asd_ha->hw_prof.ddb_lock, flags); for_each_phy(phy_mask, mask, i) asd_ddbsite_write_byte(asd_ha, 0, offsetof(struct asd_ddb_seq_shared, @@ -1312,6 +1326,7 @@ void asd_update_port_links(struct asd_sa break; } } + spin_unlock_irqrestore(&asd_ha->hw_prof.ddb_lock, flags); if (err) asd_printk("couldn't update DDB 0:error:%d\n", err); diff -urNp linux-2.6.18.orig/drivers/scsi/aic94xx/aic94xx_seq.h linux-2.6.18/drivers/scsi/aic94xx/aic94xx_seq.h --- linux-2.6.18.orig/drivers/scsi/aic94xx/aic94xx_seq.h 2007-06-21 16:43:05.000000000 -0400 +++ linux-2.6.18/drivers/scsi/aic94xx/aic94xx_seq.h 2007-06-21 16:47:04.000000000 -0400 @@ -27,6 +27,7 @@ #ifndef _AIC94XX_SEQ_H_ #define _AIC94XX_SEQ_H_ +#ifdef __KERNEL__ int asd_pause_cseq(struct asd_ha_struct *asd_ha); int asd_unpause_cseq(struct asd_ha_struct *asd_ha); int asd_pause_lseq(struct asd_ha_struct *asd_ha, u8 lseq_mask); @@ -34,6 +35,7 @@ int asd_unpause_lseq(struct asd_ha_struc int asd_init_seqs(struct asd_ha_struct *asd_ha); int asd_start_seqs(struct asd_ha_struct *asd_ha); -void asd_update_port_links(struct asd_sas_phy *phy); +void asd_update_port_links(struct asd_ha_struct *asd_ha, struct asd_phy *phy); +#endif #endif diff -urNp linux-2.6.18.orig/drivers/scsi/aic94xx/aic94xx_task.c linux-2.6.18/drivers/scsi/aic94xx/aic94xx_task.c --- linux-2.6.18.orig/drivers/scsi/aic94xx/aic94xx_task.c 2007-06-21 16:43:05.000000000 -0400 +++ linux-2.6.18/drivers/scsi/aic94xx/aic94xx_task.c 2007-06-21 16:54:13.000000000 -0400 @@ -53,7 +53,7 @@ static const u8 data_dir_flags[] = { static inline int asd_map_scatterlist(struct sas_task *task, struct sg_el *sg_arr, - unsigned long gfp_flags) + gfp_t gfp_flags) { struct asd_ascb *ascb = task->lldd_task; struct asd_ha_struct *asd_ha = ascb->ha; @@ -349,6 +349,7 @@ Again: spin_lock_irqsave(&task->task_state_lock, flags); task->task_state_flags &= ~SAS_TASK_STATE_PENDING; + task->task_state_flags &= ~SAS_TASK_AT_INITIATOR; task->task_state_flags |= SAS_TASK_STATE_DONE; if (unlikely((task->task_state_flags & SAS_TASK_STATE_ABORTED))) { spin_unlock_irqrestore(&task->task_state_lock, flags); @@ -368,7 +369,7 @@ Again: /* ---------- ATA ---------- */ static int asd_build_ata_ascb(struct asd_ascb *ascb, struct sas_task *task, - unsigned long gfp_flags) + gfp_t gfp_flags) { struct domain_device *dev = task->dev; struct scb *scb; @@ -437,7 +438,7 @@ static void asd_unbuild_ata_ascb(struct /* ---------- SMP ---------- */ static int asd_build_smp_ascb(struct asd_ascb *ascb, struct sas_task *task, - unsigned long gfp_flags) + gfp_t gfp_flags) { struct asd_ha_struct *asd_ha = ascb->ha; struct domain_device *dev = task->dev; @@ -487,7 +488,7 @@ static void asd_unbuild_smp_ascb(struct /* ---------- SSP ---------- */ static int asd_build_ssp_ascb(struct asd_ascb *ascb, struct sas_task *task, - unsigned long gfp_flags) + gfp_t gfp_flags) { struct domain_device *dev = task->dev; struct scb *scb; @@ -557,6 +558,7 @@ int asd_execute_task(struct sas_task *ta struct sas_task *t = task; struct asd_ascb *ascb = NULL, *a; struct asd_ha_struct *asd_ha = task->dev->port->ha->lldd_ha; + unsigned long flags; res = asd_can_queue(asd_ha, num); if (res) @@ -599,6 +601,10 @@ int asd_execute_task(struct sas_task *ta } if (res) goto out_err_unmap; + + spin_lock_irqsave(&t->task_state_lock, flags); + t->task_state_flags |= SAS_TASK_AT_INITIATOR; + spin_unlock_irqrestore(&t->task_state_lock, flags); } list_del_init(&alist); @@ -617,6 +623,9 @@ out_err_unmap: if (a == b) break; t = a->uldd_task; + spin_lock_irqsave(&t->task_state_lock, flags); + t->task_state_flags &= ~SAS_TASK_AT_INITIATOR; + spin_unlock_irqrestore(&t->task_state_lock, flags); switch (t->task_proto) { case SATA_PROTO: case SAS_PROTO_STP: diff -urNp linux-2.6.18.orig/drivers/scsi/aic94xx/aic94xx_tmf.c linux-2.6.18/drivers/scsi/aic94xx/aic94xx_tmf.c --- linux-2.6.18.orig/drivers/scsi/aic94xx/aic94xx_tmf.c 2007-06-21 16:43:05.000000000 -0400 +++ linux-2.6.18/drivers/scsi/aic94xx/aic94xx_tmf.c 2007-06-21 16:47:04.000000000 -0400 @@ -290,6 +290,7 @@ static void asd_tmf_tasklet_complete(str static inline int asd_clear_nexus(struct sas_task *task) { int res = TMF_RESP_FUNC_FAILED; + int leftover; struct asd_ascb *tascb = task->lldd_task; unsigned long flags; @@ -298,10 +299,12 @@ static inline int asd_clear_nexus(struct res = asd_clear_nexus_tag(task); else res = asd_clear_nexus_index(task); - wait_for_completion_timeout(&tascb->completion, - AIC94XX_SCB_TIMEOUT); + leftover = wait_for_completion_timeout(&tascb->completion, + AIC94XX_SCB_TIMEOUT); ASD_DPRINTK("came back from clear nexus\n"); spin_lock_irqsave(&task->task_state_lock, flags); + if (leftover < 1) + res = TMF_RESP_FUNC_FAILED; if (task->task_state_flags & SAS_TASK_STATE_DONE) res = TMF_RESP_FUNC_COMPLETE; spin_unlock_irqrestore(&task->task_state_lock, flags); @@ -350,6 +353,7 @@ int asd_abort_task(struct sas_task *task unsigned long flags; struct asd_ascb *ascb = NULL; struct scb *scb; + int leftover; spin_lock_irqsave(&task->task_state_lock, flags); if (task->task_state_flags & SAS_TASK_STATE_DONE) { @@ -455,9 +459,11 @@ int asd_abort_task(struct sas_task *task break; case TF_TMF_TASK_DONE + 0xFF00: /* done but not reported yet */ res = TMF_RESP_FUNC_FAILED; - wait_for_completion_timeout(&tascb->completion, - AIC94XX_SCB_TIMEOUT); + leftover = wait_for_completion_timeout(&tascb->completion, + AIC94XX_SCB_TIMEOUT); spin_lock_irqsave(&task->task_state_lock, flags); + if (leftover < 1) + res = TMF_RESP_FUNC_FAILED; if (task->task_state_flags & SAS_TASK_STATE_DONE) res = TMF_RESP_FUNC_COMPLETE; spin_unlock_irqrestore(&task->task_state_lock, flags); @@ -566,9 +572,7 @@ static int asd_initiate_ssp_tmf(struct d res = TMF_RESP_FUNC_ESUPP; break; default: - ASD_DPRINTK("%s: converting result 0x%x to TMF_RESP_FUNC_FAILED\n", - __FUNCTION__, res); - res = TMF_RESP_FUNC_FAILED; + /* Allow TMF response codes to propagate upwards */ break; } out_err: diff -urNp linux-2.6.18.orig/drivers/scsi/libsas/sas_discover.c linux-2.6.18/drivers/scsi/libsas/sas_discover.c --- linux-2.6.18.orig/drivers/scsi/libsas/sas_discover.c 2007-06-21 16:43:05.000000000 -0400 +++ linux-2.6.18/drivers/scsi/libsas/sas_discover.c 2007-06-21 16:47:04.000000000 -0400 @@ -548,7 +548,7 @@ int sas_discover_sata(struct domain_devi res = sas_notify_lldd_dev_found(dev); if (res) - return res; + goto out_err2; switch (dev->dev_type) { case SATA_DEV: @@ -560,11 +560,23 @@ int sas_discover_sata(struct domain_devi default: break; } + if (res) + goto out_err; sas_notify_lldd_dev_gone(dev); - if (!res) { - sas_notify_lldd_dev_found(dev); - } + res = sas_notify_lldd_dev_found(dev); + if (res) + goto out_err2; + + res = sas_rphy_add(dev->rphy); + if (res) + goto out_err; + + return res; + +out_err: + sas_notify_lldd_dev_gone(dev); +out_err2: return res; } @@ -580,21 +592,17 @@ int sas_discover_end_dev(struct domain_d res = sas_notify_lldd_dev_found(dev); if (res) - return res; + goto out_err2; res = sas_rphy_add(dev->rphy); if (res) goto out_err; - /* do this to get the end device port attributes which will have - * been scanned in sas_rphy_add */ - sas_notify_lldd_dev_gone(dev); - sas_notify_lldd_dev_found(dev); - return 0; out_err: sas_notify_lldd_dev_gone(dev); +out_err2: return res; } @@ -649,6 +657,7 @@ void sas_unregister_domain_devices(struc */ static void sas_discover_domain(void *data) { + struct domain_device *dev; int error = 0; struct asd_sas_port *port = data; @@ -656,35 +665,42 @@ static void sas_discover_domain(void *da &port->disc.pending); if (port->port_dev) - return ; - else { - error = sas_get_port_device(port); - if (error) - return; - } + return; + + error = sas_get_port_device(port); + if (error) + return; + dev = port->port_dev; SAS_DPRINTK("DOING DISCOVERY on port %d, pid:%d\n", port->id, current->pid); - switch (port->port_dev->dev_type) { + switch (dev->dev_type) { case SAS_END_DEV: - error = sas_discover_end_dev(port->port_dev); + error = sas_discover_end_dev(dev); break; case EDGE_DEV: case FANOUT_DEV: - error = sas_discover_root_expander(port->port_dev); + error = sas_discover_root_expander(dev); break; case SATA_DEV: case SATA_PM: - error = sas_discover_sata(port->port_dev); + error = sas_discover_sata(dev); break; default: - SAS_DPRINTK("unhandled device %d\n", port->port_dev->dev_type); + SAS_DPRINTK("unhandled device %d\n", dev->dev_type); break; } if (error) { - kfree(port->port_dev); /* not kobject_register-ed yet */ + sas_rphy_free(dev->rphy); + dev->rphy = NULL; + + spin_lock(&port->dev_list_lock); + list_del_init(&dev->dev_list_node); + spin_unlock(&port->dev_list_lock); + + kfree(dev); /* not kobject_register-ed yet */ port->port_dev = NULL; } @@ -722,7 +738,7 @@ int sas_discover_event(struct asd_sas_po BUG_ON(ev >= DISC_NUM_EVENTS); sas_queue_event(ev, &disc->disc_event_lock, &disc->pending, - &disc->disc_work[ev], port->ha->core.shost); + &disc->disc_work[ev], port->ha); return 0; } @@ -745,5 +761,5 @@ void sas_init_disc(struct sas_discovery spin_lock_init(&disc->disc_event_lock); disc->pending = 0; for (i = 0; i < DISC_NUM_EVENTS; i++) - INIT_WORK(&disc->disc_work[i], sas_event_fns[i], port); + INIT_WORK(&disc->disc_work[i], sas_event_fns[i],port); } diff -urNp linux-2.6.18.orig/drivers/scsi/libsas/sas_event.c linux-2.6.18/drivers/scsi/libsas/sas_event.c --- linux-2.6.18.orig/drivers/scsi/libsas/sas_event.c 2007-06-21 16:43:05.000000000 -0400 +++ linux-2.6.18/drivers/scsi/libsas/sas_event.c 2007-06-21 16:47:04.000000000 -0400 @@ -31,7 +31,7 @@ static void notify_ha_event(struct sas_h BUG_ON(event >= HA_NUM_EVENTS); sas_queue_event(event, &sas_ha->event_lock, &sas_ha->pending, - &sas_ha->ha_events[event], sas_ha->core.shost); + &sas_ha->ha_events[event], sas_ha); } static void notify_port_event(struct asd_sas_phy *phy, enum port_event event) @@ -41,7 +41,7 @@ static void notify_port_event(struct asd BUG_ON(event >= PORT_NUM_EVENTS); sas_queue_event(event, &ha->event_lock, &phy->port_events_pending, - &phy->port_events[event], ha->core.shost); + &phy->port_events[event], ha); } static void notify_phy_event(struct asd_sas_phy *phy, enum phy_event event) @@ -51,7 +51,7 @@ static void notify_phy_event(struct asd_ BUG_ON(event >= PHY_NUM_EVENTS); sas_queue_event(event, &ha->event_lock, &phy->phy_events_pending, - &phy->phy_events[event], ha->core.shost); + &phy->phy_events[event], ha); } int sas_init_events(struct sas_ha_struct *sas_ha) @@ -64,8 +64,9 @@ int sas_init_events(struct sas_ha_struct spin_lock_init(&sas_ha->event_lock); - for (i = 0; i < HA_NUM_EVENTS; i++) - INIT_WORK(&sas_ha->ha_events[i], sas_ha_event_fns[i], sas_ha); + for (i = 0; i < HA_NUM_EVENTS; i++) { + INIT_WORK(&sas_ha->ha_events[i], sas_ha_event_fns[i],sas_ha); + } sas_ha->notify_ha_event = notify_ha_event; sas_ha->notify_port_event = notify_port_event; diff -urNp linux-2.6.18.orig/drivers/scsi/libsas/sas_expander.c linux-2.6.18/drivers/scsi/libsas/sas_expander.c --- linux-2.6.18.orig/drivers/scsi/libsas/sas_expander.c 2007-06-21 16:43:05.000000000 -0400 +++ linux-2.6.18/drivers/scsi/libsas/sas_expander.c 2007-06-21 16:47:04.000000000 -0400 @@ -71,55 +71,65 @@ static void smp_task_done(struct sas_tas static int smp_execute_task(struct domain_device *dev, void *req, int req_size, void *resp, int resp_size) { - int res; - struct sas_task *task = sas_alloc_task(GFP_KERNEL); + int res, retry; + struct sas_task *task = NULL; struct sas_internal *i = to_sas_internal(dev->port->ha->core.shost->transportt); - if (!task) - return -ENOMEM; - - task->dev = dev; - task->task_proto = dev->tproto; - sg_init_one(&task->smp_task.smp_req, req, req_size); - sg_init_one(&task->smp_task.smp_resp, resp, resp_size); - - task->task_done = smp_task_done; + for (retry = 0; retry < 3; retry++) { + task = sas_alloc_task(GFP_KERNEL); + if (!task) + return -ENOMEM; + + task->dev = dev; + task->task_proto = dev->tproto; + sg_init_one(&task->smp_task.smp_req, req, req_size); + sg_init_one(&task->smp_task.smp_resp, resp, resp_size); + + task->task_done = smp_task_done; + + task->timer.data = (unsigned long) task; + task->timer.function = smp_task_timedout; + task->timer.expires = jiffies + SMP_TIMEOUT*HZ; + add_timer(&task->timer); - task->timer.data = (unsigned long) task; - task->timer.function = smp_task_timedout; - task->timer.expires = jiffies + SMP_TIMEOUT*HZ; - add_timer(&task->timer); - - res = i->dft->lldd_execute_task(task, 1, GFP_KERNEL); - - if (res) { - del_timer(&task->timer); - SAS_DPRINTK("executing SMP task failed:%d\n", res); - goto ex_err; - } + res = i->dft->lldd_execute_task(task, 1, GFP_KERNEL); - wait_for_completion(&task->completion); - res = -ETASK; - if ((task->task_state_flags & SAS_TASK_STATE_ABORTED)) { - SAS_DPRINTK("smp task timed out or aborted\n"); - i->dft->lldd_abort_task(task); - if (!(task->task_state_flags & SAS_TASK_STATE_DONE)) { - SAS_DPRINTK("SMP task aborted and not done\n"); + if (res) { + del_timer(&task->timer); + SAS_DPRINTK("executing SMP task failed:%d\n", res); goto ex_err; } + + wait_for_completion(&task->completion); + res = -ETASK; + if ((task->task_state_flags & SAS_TASK_STATE_ABORTED)) { + SAS_DPRINTK("smp task timed out or aborted\n"); + i->dft->lldd_abort_task(task); + if (!(task->task_state_flags & SAS_TASK_STATE_DONE)) { + SAS_DPRINTK("SMP task aborted and not done\n"); + goto ex_err; + } + } + if (task->task_status.resp == SAS_TASK_COMPLETE && + task->task_status.stat == SAM_GOOD) { + res = 0; + break; + } else { + SAS_DPRINTK("%s: task to dev %016llx response: 0x%x " + "status 0x%x\n", __FUNCTION__, + SAS_ADDR(dev->sas_addr), + task->task_status.resp, + task->task_status.stat); + sas_free_task(task); + task = NULL; + } } - if (task->task_status.resp == SAS_TASK_COMPLETE && - task->task_status.stat == SAM_GOOD) - res = 0; - else - SAS_DPRINTK("%s: task to dev %016llx response: 0x%x " - "status 0x%x\n", __FUNCTION__, - SAS_ADDR(dev->sas_addr), - task->task_status.resp, - task->task_status.stat); ex_err: - sas_free_task(task); + BUG_ON(retry == 3 && task != NULL); + if (task != NULL) { + sas_free_task(task); + } return res; } @@ -187,24 +197,11 @@ static void sas_set_ex_phy(struct domain phy->phy->identify.initiator_port_protocols = phy->attached_iproto; phy->phy->identify.target_port_protocols = phy->attached_tproto; phy->phy->identify.phy_identifier = phy_id; - phy->phy->minimum_linkrate_hw = SAS_LINK_RATE_1_5_GBPS; - phy->phy->maximum_linkrate_hw = SAS_LINK_RATE_3_0_GBPS; - phy->phy->minimum_linkrate = SAS_LINK_RATE_1_5_GBPS; - phy->phy->maximum_linkrate = SAS_LINK_RATE_3_0_GBPS; - switch (phy->linkrate) { - case PHY_LINKRATE_1_5: - phy->phy->negotiated_linkrate = SAS_LINK_RATE_1_5_GBPS; - break; - case PHY_LINKRATE_3: - phy->phy->negotiated_linkrate = SAS_LINK_RATE_3_0_GBPS; - break; - case PHY_LINKRATE_6: - phy->phy->negotiated_linkrate = SAS_LINK_RATE_6_0_GBPS; - break; - default: - phy->phy->negotiated_linkrate = SAS_LINK_RATE_UNKNOWN; - break; - } + phy->phy->minimum_linkrate_hw = phy_linkrate_to_linkrate(dr->hmin_linkrate); + phy->phy->maximum_linkrate_hw = phy_linkrate_to_linkrate(dr->hmax_linkrate); + phy->phy->minimum_linkrate = phy_linkrate_to_linkrate(dr->pmin_linkrate); + phy->phy->maximum_linkrate = phy_linkrate_to_linkrate(dr->pmax_linkrate); + phy->phy->negotiated_linkrate = phy_linkrate_to_linkrate(phy->linkrate); if (!rediscover) sas_phy_add(phy->phy); @@ -417,7 +414,8 @@ out: #define PC_RESP_SIZE 8 int sas_smp_phy_control(struct domain_device *dev, int phy_id, - enum phy_func phy_func) + enum phy_func phy_func, + struct sas_phy_linkrates *rates) { u8 *pc_req; u8 *pc_resp; @@ -436,6 +434,10 @@ int sas_smp_phy_control(struct domain_de pc_req[1] = SMP_PHY_CONTROL; pc_req[9] = phy_id; pc_req[10]= phy_func; + if (rates) { + pc_req[32] = rates->minimum_linkrate << 4; + pc_req[33] = rates->maximum_linkrate << 4; + } res = smp_execute_task(dev, pc_req, PC_REQ_SIZE, pc_resp,PC_RESP_SIZE); @@ -449,8 +451,8 @@ static void sas_ex_disable_phy(struct do struct expander_device *ex = &dev->ex_dev; struct ex_phy *phy = &ex->ex_phy[phy_id]; - sas_smp_phy_control(dev, phy_id, PHY_FUNC_DISABLE); - phy->linkrate = PHY_DISABLED; + sas_smp_phy_control(dev, phy_id, PHY_FUNC_DISABLE, NULL); + phy->linkrate = SAS_PHY_DISABLED; } static void sas_ex_disable_port(struct domain_device *dev, u8 *sas_addr) @@ -595,10 +597,15 @@ static struct domain_device *sas_ex_disc child->iproto = phy->attached_iproto; memcpy(child->sas_addr, phy->attached_sas_addr, SAS_ADDR_SIZE); sas_hash_addr(child->hashed_sas_addr, child->sas_addr); - phy->port = sas_port_alloc(&parent->rphy->dev, phy_id); - BUG_ON(!phy->port); - /* FIXME: better error handling*/ - BUG_ON(sas_port_add(phy->port) != 0); + if (!phy->port) { + phy->port = sas_port_alloc(&parent->rphy->dev, phy_id); + if (unlikely(!phy->port)) + goto out_err; + if (unlikely(sas_port_add(phy->port) != 0)) { + sas_port_free(phy->port); + goto out_err; + } + } sas_ex_get_linkrate(parent, child, phy); if ((phy->attached_tproto & SAS_PROTO_STP) || phy->attached_sata_dev) { @@ -613,8 +620,7 @@ static struct domain_device *sas_ex_disc SAS_DPRINTK("report phy sata to %016llx:0x%x returned " "0x%x\n", SAS_ADDR(parent->sas_addr), phy_id, res); - kfree(child); - return NULL; + goto out_free; } memcpy(child->frame_rcvd, &child->sata_dev.rps_resp.rps.fis, sizeof(struct dev_to_host_fis)); @@ -625,14 +631,14 @@ static struct domain_device *sas_ex_disc "%016llx:0x%x returned 0x%x\n", SAS_ADDR(child->sas_addr), SAS_ADDR(parent->sas_addr), phy_id, res); - kfree(child); - return NULL; + goto out_free; } } else if (phy->attached_tproto & SAS_PROTO_SSP) { child->dev_type = SAS_END_DEV; rphy = sas_end_device_alloc(phy->port); /* FIXME: error handling */ - BUG_ON(!rphy); + if (unlikely(!rphy)) + goto out_free; child->tproto = phy->attached_tproto; sas_init_dev(child); @@ -649,9 +655,7 @@ static struct domain_device *sas_ex_disc "at %016llx:0x%x returned 0x%x\n", SAS_ADDR(child->sas_addr), SAS_ADDR(parent->sas_addr), phy_id, res); - /* FIXME: this kfrees list elements without removing them */ - //kfree(child); - return NULL; + goto out_list_del; } } else { SAS_DPRINTK("target proto 0x%x at %016llx:0x%x not handled\n", @@ -661,6 +665,40 @@ static struct domain_device *sas_ex_disc list_add_tail(&child->siblings, &parent_ex->children); return child; + + out_list_del: + sas_rphy_free(child->rphy); + child->rphy = NULL; + list_del(&child->dev_list_node); + out_free: + sas_port_delete(phy->port); + out_err: + phy->port = NULL; + kfree(child); + return NULL; +} + +/* See if this phy is part of a wide port */ +static int sas_ex_join_wide_port(struct domain_device *parent, int phy_id) +{ + struct ex_phy *phy = &parent->ex_dev.ex_phy[phy_id]; + int i; + + for (i = 0; i < parent->ex_dev.num_phys; i++) { + struct ex_phy *ephy = &parent->ex_dev.ex_phy[i]; + + if (ephy == phy) + continue; + + if (!memcmp(phy->attached_sas_addr, ephy->attached_sas_addr, + SAS_ADDR_SIZE)) { + sas_port_add_phy(ephy->port, phy->phy); + phy->phy_state = PHY_DEVICE_DISCOVERED; + return 0; + } + } + + return -ENODEV; } static struct domain_device *sas_ex_discover_expander( @@ -743,8 +781,8 @@ static int sas_ex_discover_dev(struct do int res = 0; /* Phy state */ - if (ex_phy->linkrate == PHY_SPINUP_HOLD) { - if (!sas_smp_phy_control(dev, phy_id, PHY_FUNC_LINK_RESET)) + if (ex_phy->linkrate == SAS_SATA_SPINUP_HOLD) { + if (!sas_smp_phy_control(dev, phy_id, PHY_FUNC_LINK_RESET, NULL)) res = sas_ex_phy_discover(dev, phy_id); if (res) return res; @@ -773,7 +811,7 @@ static int sas_ex_discover_dev(struct do sas_configure_routing(dev, ex_phy->attached_sas_addr); } return 0; - } else if (ex_phy->linkrate == PHY_LINKRATE_UNKNOWN) + } else if (ex_phy->linkrate == SAS_LINK_RATE_UNKNOWN) return 0; if (ex_phy->attached_dev_type != SAS_END_DEV && @@ -795,6 +833,13 @@ static int sas_ex_discover_dev(struct do return res; } + res = sas_ex_join_wide_port(dev, phy_id); + if (!res) { + SAS_DPRINTK("Attaching ex phy%d to wide port %016llx\n", + phy_id, SAS_ADDR(ex_phy->attached_sas_addr)); + return res; + } + switch (ex_phy->attached_dev_type) { case SAS_END_DEV: child = sas_ex_discover_end_dev(dev, phy_id); @@ -922,9 +967,8 @@ static int sas_ex_discover_devices(struc continue; switch (ex_phy->linkrate) { - case PHY_DISABLED: - case PHY_RESET_PROBLEM: - case PHY_PORT_SELECTOR: + case SAS_PHY_DISABLED: + case SAS_SATA_PORT_SELECTOR: continue; default: res = sas_ex_discover_dev(dev, i); @@ -1417,14 +1461,23 @@ int sas_discover_root_expander(struct do int res; struct sas_expander_device *ex = rphy_to_expander_device(dev->rphy); - sas_rphy_add(dev->rphy); + res = sas_rphy_add(dev->rphy); + if (res) + goto out_err; ex->level = dev->port->disc.max_level; /* 0 */ res = sas_discover_expander(dev); - if (!res) - sas_ex_bfs_disc(dev->port); + if (res) + goto out_err2; + + sas_ex_bfs_disc(dev->port); return res; + +out_err2: + sas_rphy_remove(dev->rphy); +out_err: + return res; } /* ---------- Domain revalidation ---------- */ @@ -1719,6 +1772,7 @@ static int sas_rediscover_dev(struct dom SAS_ADDR(phy->attached_sas_addr)) { SAS_DPRINTK("ex %016llx phy 0x%x broadcast flutter\n", SAS_ADDR(dev->sas_addr), phy_id); + sas_ex_phy_discover(dev, phy_id); } else res = sas_discover_new(dev, phy_id); out: diff -urNp linux-2.6.18.orig/drivers/scsi/libsas/sas_init.c linux-2.6.18/drivers/scsi/libsas/sas_init.c --- linux-2.6.18.orig/drivers/scsi/libsas/sas_init.c 2007-06-21 16:43:05.000000000 -0400 +++ linux-2.6.18/drivers/scsi/libsas/sas_init.c 2007-06-21 16:51:52.000000000 -0400 @@ -36,7 +36,7 @@ #include "../scsi_sas_internal.h" -kmem_cache_t *sas_task_cache; +struct kmem_cache *sas_task_cache; /*------------ SAS addr hash -----------*/ void sas_hash_addr(u8 *hashed, const u8 *sas_addr) @@ -85,6 +85,9 @@ int sas_register_ha(struct sas_ha_struct else if (sas_ha->lldd_queue_size == -1) sas_ha->lldd_queue_size = 128; /* Sanity */ + sas_ha->state = SAS_HA_REGISTERED; + spin_lock_init(&sas_ha->state_lock); + error = sas_register_phys(sas_ha); if (error) { printk(KERN_NOTICE "couldn't register sas phys:%d\n", error); @@ -112,6 +115,8 @@ int sas_register_ha(struct sas_ha_struct } } + INIT_LIST_HEAD(&sas_ha->eh_done_q); + return 0; Undo_ports: @@ -123,12 +128,22 @@ Undo_phys: int sas_unregister_ha(struct sas_ha_struct *sas_ha) { + unsigned long flags; + + /* Set the state to unregistered to avoid further + * events to be queued */ + spin_lock_irqsave(&sas_ha->state_lock, flags); + sas_ha->state = SAS_HA_UNREGISTERED; + spin_unlock_irqrestore(&sas_ha->state_lock, flags); + scsi_flush_work(sas_ha->core.shost); + + sas_unregister_ports(sas_ha); + if (sas_ha->lldd_max_execute_num > 1) { sas_shutdown_queue(sas_ha); + sas_ha->lldd_max_execute_num = 1; } - sas_unregister_ports(sas_ha); - return 0; } @@ -142,7 +157,37 @@ static int sas_get_linkerrors(struct sas return sas_smp_get_phy_events(phy); } -static int sas_phy_reset(struct sas_phy *phy, int hard_reset) +int sas_phy_enable(struct sas_phy *phy, int enable) +{ + int ret; + enum phy_func command; + + if (enable) + command = PHY_FUNC_LINK_RESET; + else + command = PHY_FUNC_DISABLE; + + if (scsi_is_sas_phy_local(phy)) { + struct Scsi_Host *shost = dev_to_shost(phy->dev.parent); + struct sas_ha_struct *sas_ha = SHOST_TO_SAS_HA(shost); + struct asd_sas_phy *asd_phy = sas_ha->sas_phy[phy->number]; + struct sas_internal *i = + to_sas_internal(sas_ha->core.shost->transportt); + + if (!enable) { + sas_phy_disconnected(asd_phy); + sas_ha->notify_phy_event(asd_phy, PHYE_LOSS_OF_SIGNAL); + } + ret = i->dft->lldd_control_phy_new(asd_phy, command, NULL); + } else { + struct sas_rphy *rphy = dev_to_rphy(phy->dev.parent); + struct domain_device *ddev = sas_find_dev_by_rphy(rphy); + ret = sas_smp_phy_control(ddev, phy->number, command, NULL); + } + return ret; +} + +int sas_phy_reset(struct sas_phy *phy, int hard_reset) { int ret; enum phy_func reset_type; @@ -158,18 +203,56 @@ static int sas_phy_reset(struct sas_phy struct asd_sas_phy *asd_phy = sas_ha->sas_phy[phy->number]; struct sas_internal *i = to_sas_internal(sas_ha->core.shost->transportt); + ret = i->dft->lldd_control_phy_new(asd_phy, reset_type, NULL); + } else { + struct sas_rphy *rphy = dev_to_rphy(phy->dev.parent); + struct domain_device *ddev = sas_find_dev_by_rphy(rphy); + ret = sas_smp_phy_control(ddev, phy->number, reset_type, NULL); + } + return ret; +} + +int sas_set_phy_speed(struct sas_phy *phy, + struct sas_phy_linkrates *rates) +{ + int ret; + + if ((rates->minimum_linkrate && + rates->minimum_linkrate > linkrate_to_phy_linkrate(phy->maximum_linkrate)) || + (rates->maximum_linkrate && + rates->maximum_linkrate < linkrate_to_phy_linkrate(phy->minimum_linkrate))) + return -EINVAL; - ret = i->dft->lldd_control_phy(asd_phy, reset_type); + if (rates->minimum_linkrate && + rates->minimum_linkrate < linkrate_to_phy_linkrate(phy->minimum_linkrate_hw)) + rates->minimum_linkrate = linkrate_to_phy_linkrate(phy->minimum_linkrate_hw); + + if (rates->maximum_linkrate && + rates->maximum_linkrate > linkrate_to_phy_linkrate(phy->maximum_linkrate_hw)) + rates->maximum_linkrate = linkrate_to_phy_linkrate(phy->maximum_linkrate_hw); + + if (scsi_is_sas_phy_local(phy)) { + struct Scsi_Host *shost = dev_to_shost(phy->dev.parent); + struct sas_ha_struct *sas_ha = SHOST_TO_SAS_HA(shost); + struct asd_sas_phy *asd_phy = sas_ha->sas_phy[phy->number]; + struct sas_internal *i = + to_sas_internal(sas_ha->core.shost->transportt); + ret = i->dft->lldd_control_phy_new(asd_phy, PHY_FUNC_SET_LINK_RATE, rates); } else { struct sas_rphy *rphy = dev_to_rphy(phy->dev.parent); struct domain_device *ddev = sas_find_dev_by_rphy(rphy); - ret = sas_smp_phy_control(ddev, phy->number, reset_type); + ret = sas_smp_phy_control(ddev, phy->number, + PHY_FUNC_LINK_RESET, rates); + } + return ret; } static struct sas_function_template sft = { + .phy_enable = sas_phy_enable, .phy_reset = sas_phy_reset, + .set_phy_speed = sas_set_phy_speed, .get_linkerrors = sas_get_linkerrors, }; diff -urNp linux-2.6.18.orig/drivers/scsi/libsas/sas_internal.h linux-2.6.18/drivers/scsi/libsas/sas_internal.h --- linux-2.6.18.orig/drivers/scsi/libsas/sas_internal.h 2007-06-21 16:43:05.000000000 -0400 +++ linux-2.6.18/drivers/scsi/libsas/sas_internal.h 2007-06-21 16:47:04.000000000 -0400 @@ -70,7 +70,7 @@ int sas_notify_lldd_dev_found(struct dom void sas_notify_lldd_dev_gone(struct domain_device *); int sas_smp_phy_control(struct domain_device *dev, int phy_id, - enum phy_func phy_func); + enum phy_func phy_func, struct sas_phy_linkrates *); int sas_smp_get_phy_events(struct sas_phy *phy); struct domain_device *sas_find_dev_by_rphy(struct sas_rphy *rphy); @@ -80,7 +80,7 @@ void sas_hae_reset(void *); static inline void sas_queue_event(int event, spinlock_t *lock, unsigned long *pending, struct work_struct *work, - struct Scsi_Host *shost) + struct sas_ha_struct *sas_ha) { unsigned long flags; @@ -91,7 +91,12 @@ static inline void sas_queue_event(int e } __set_bit(event, pending); spin_unlock_irqrestore(lock, flags); - scsi_queue_work(shost, work); + + spin_lock_irqsave(&sas_ha->state_lock, flags); + if (sas_ha->state != SAS_HA_UNREGISTERED) { + scsi_queue_work(sas_ha->core.shost, work); + } + spin_unlock_irqrestore(&sas_ha->state_lock, flags); } static inline void sas_begin_event(int event, spinlock_t *lock, diff -urNp linux-2.6.18.orig/drivers/scsi/libsas/sas_phy.c linux-2.6.18/drivers/scsi/libsas/sas_phy.c --- linux-2.6.18.orig/drivers/scsi/libsas/sas_phy.c 2007-06-21 16:43:05.000000000 -0400 +++ linux-2.6.18/drivers/scsi/libsas/sas_phy.c 2007-06-21 16:53:12.000000000 -0400 @@ -67,13 +67,16 @@ static void sas_phye_oob_error(void *dat switch (phy->error) { case 1: case 2: - i->dft->lldd_control_phy(phy, PHY_FUNC_HARD_RESET); + i->dft->lldd_control_phy_new(phy, PHY_FUNC_HARD_RESET, + NULL); + break; case 3: default: phy->error = 0; phy->enabled = 0; - i->dft->lldd_control_phy(phy, PHY_FUNC_DISABLE); + i->dft->lldd_control_phy_new(phy, PHY_FUNC_DISABLE, NULL); + break; } } @@ -90,7 +93,7 @@ static void sas_phye_spinup_hold(void *d &phy->phy_events_pending); phy->error = 0; - i->dft->lldd_control_phy(phy, PHY_FUNC_RELEASE_SPINUP_HOLD); + i->dft->lldd_control_phy_new(phy, PHY_FUNC_RELEASE_SPINUP_HOLD, NULL); } /* ---------- Phy class registration ---------- */ @@ -128,6 +131,7 @@ int sas_register_phys(struct sas_ha_stru for (k = 0; k < PHY_NUM_EVENTS; k++) INIT_WORK(&phy->phy_events[k], sas_phy_event_fns[k], phy); + phy->port = NULL; phy->ha = sas_ha; spin_lock_init(&phy->frame_rcvd_lock); @@ -144,10 +148,10 @@ int sas_register_phys(struct sas_ha_stru phy->phy->identify.target_port_protocols = phy->tproto; phy->phy->identify.sas_address = SAS_ADDR(sas_ha->sas_addr); phy->phy->identify.phy_identifier = i; - phy->phy->minimum_linkrate_hw = SAS_LINK_RATE_1_5_GBPS; - phy->phy->maximum_linkrate_hw = SAS_LINK_RATE_3_0_GBPS; - phy->phy->minimum_linkrate = SAS_LINK_RATE_1_5_GBPS; - phy->phy->maximum_linkrate = SAS_LINK_RATE_3_0_GBPS; + phy->phy->minimum_linkrate_hw = SAS_LINK_RATE_UNKNOWN; + phy->phy->maximum_linkrate_hw = SAS_LINK_RATE_UNKNOWN; + phy->phy->minimum_linkrate = SAS_LINK_RATE_UNKNOWN; + phy->phy->maximum_linkrate = SAS_LINK_RATE_UNKNOWN; phy->phy->negotiated_linkrate = SAS_LINK_RATE_UNKNOWN; sas_phy_add(phy->phy); diff -urNp linux-2.6.18.orig/drivers/scsi/libsas/sas_port.c linux-2.6.18/drivers/scsi/libsas/sas_port.c --- linux-2.6.18.orig/drivers/scsi/libsas/sas_port.c 2007-06-21 16:43:05.000000000 -0400 +++ linux-2.6.18/drivers/scsi/libsas/sas_port.c 2007-06-21 16:47:04.000000000 -0400 @@ -42,10 +42,11 @@ static void sas_form_port(struct asd_sas struct asd_sas_port *port = phy->port; struct sas_internal *si = to_sas_internal(sas_ha->core.shost->transportt); + unsigned long flags; if (port) { if (memcmp(port->attached_sas_addr, phy->attached_sas_addr, - SAS_ADDR_SIZE) == 0) + SAS_ADDR_SIZE) != 0) sas_deform_port(phy); else { SAS_DPRINTK("%s: phy%d belongs to port%d already(%d)!\n", @@ -56,7 +57,7 @@ static void sas_form_port(struct asd_sas } /* find a port */ - spin_lock(&sas_ha->phy_port_lock); + spin_lock_irqsave(&sas_ha->phy_port_lock, flags); for (i = 0; i < sas_ha->num_phys; i++) { port = sas_ha->sas_port[i]; spin_lock(&port->phy_list_lock); @@ -78,7 +79,7 @@ static void sas_form_port(struct asd_sas if (i >= sas_ha->num_phys) { printk(KERN_NOTICE "%s: couldn't find a free port, bug?\n", __FUNCTION__); - spin_unlock(&sas_ha->phy_port_lock); + spin_unlock_irqrestore(&sas_ha->phy_port_lock, flags); return; } @@ -105,7 +106,7 @@ static void sas_form_port(struct asd_sas } else port->linkrate = max(port->linkrate, phy->linkrate); spin_unlock(&port->phy_list_lock); - spin_unlock(&sas_ha->phy_port_lock); + spin_unlock_irqrestore(&sas_ha->phy_port_lock, flags); if (!port->port) { port->port = sas_port_alloc(phy->phy->dev.parent, port->id); @@ -137,6 +138,7 @@ void sas_deform_port(struct asd_sas_phy struct asd_sas_port *port = phy->port; struct sas_internal *si = to_sas_internal(sas_ha->core.shost->transportt); + unsigned long flags; if (!port) return; /* done by a phy event */ @@ -155,7 +157,7 @@ void sas_deform_port(struct asd_sas_phy if (si->dft->lldd_port_deformed) si->dft->lldd_port_deformed(phy); - spin_lock(&sas_ha->phy_port_lock); + spin_lock_irqsave(&sas_ha->phy_port_lock, flags); spin_lock(&port->phy_list_lock); list_del_init(&phy->port_phy_el); @@ -174,7 +176,7 @@ void sas_deform_port(struct asd_sas_phy port->phy_mask = 0; } spin_unlock(&port->phy_list_lock); - spin_unlock(&sas_ha->phy_port_lock); + spin_unlock_irqrestore(&sas_ha->phy_port_lock, flags); return; } diff -urNp linux-2.6.18.orig/drivers/scsi/libsas/sas_scsi_host.c linux-2.6.18/drivers/scsi/libsas/sas_scsi_host.c --- linux-2.6.18.orig/drivers/scsi/libsas/sas_scsi_host.c 2007-06-21 16:43:05.000000000 -0400 +++ linux-2.6.18/drivers/scsi/libsas/sas_scsi_host.c 2007-06-21 16:47:04.000000000 -0400 @@ -29,9 +29,12 @@ #include <scsi/scsi_device.h> #include <scsi/scsi_tcq.h> #include <scsi/scsi.h> +#include <scsi/scsi_eh.h> #include <scsi/scsi_transport.h> #include <scsi/scsi_transport_sas.h> #include "../scsi_sas_internal.h" +#include "../scsi_transport_api.h" +#include "../scsi_priv.h" #include <linux/err.h> #include <linux/blkdev.h> @@ -46,6 +49,7 @@ static void sas_scsi_task_done(struct sa { struct task_status_struct *ts = &task->task_status; struct scsi_cmnd *sc = task->uldd_task; + struct sas_ha_struct *sas_ha = SHOST_TO_SAS_HA(sc->device->host); unsigned ts_flags = task->task_state_flags; int hs = 0, stat = 0; @@ -116,7 +120,7 @@ static void sas_scsi_task_done(struct sa sas_free_task(task); /* This is very ugly but this is how SCSI Core works. */ if (ts_flags & SAS_TASK_STATE_ABORTED) - scsi_finish_command(sc); + scsi_eh_finish_cmd(sc, &sas_ha->eh_done_q); else sc->scsi_done(sc); } @@ -127,14 +131,14 @@ static enum task_attribute sas_scsi_get_ if (cmd->request && blk_rq_tagged(cmd->request)) { if (cmd->device->ordered_tags && (cmd->request->flags & REQ_HARDBARRIER)) - ta = TASK_ATTR_HOQ; + ta = TASK_ATTR_ORDERED; } return ta; } static struct sas_task *sas_create_task(struct scsi_cmnd *cmd, struct domain_device *dev, - unsigned long gfp_flags) + gfp_t gfp_flags) { struct sas_task *task = sas_alloc_task(gfp_flags); struct scsi_lun lun; @@ -278,6 +282,7 @@ enum task_disposition { TASK_IS_ABORTED, TASK_IS_AT_LU, TASK_IS_NOT_AT_LU, + TASK_ABORT_FAILED, }; static enum task_disposition sas_scsi_find_task(struct sas_task *task) @@ -328,15 +333,21 @@ static enum task_disposition sas_scsi_fi SAS_DPRINTK("%s: querying task 0x%p\n", __FUNCTION__, task); res = si->dft->lldd_query_task(task); - if (res == TMF_RESP_FUNC_SUCC) { + switch (res) { + case TMF_RESP_FUNC_SUCC: SAS_DPRINTK("%s: task 0x%p at LU\n", __FUNCTION__, task); return TASK_IS_AT_LU; - } else if (res == TMF_RESP_FUNC_COMPLETE) { + case TMF_RESP_FUNC_COMPLETE: SAS_DPRINTK("%s: task 0x%p not at LU\n", __FUNCTION__, task); return TASK_IS_NOT_AT_LU; - } + case TMF_RESP_FUNC_FAILED: + SAS_DPRINTK("%s: task 0x%p failed to abort\n", + __FUNCTION__, task); + return TASK_ABORT_FAILED; + } + } } return res; @@ -386,47 +397,132 @@ static int sas_recover_I_T(struct domain return res; } -void sas_scsi_recover_host(struct Scsi_Host *shost) +/* Find the sas_phy that's attached to this device */ +struct sas_phy *find_local_sas_phy(struct domain_device *dev) +{ + struct domain_device *pdev = dev->parent; + struct ex_phy *exphy = NULL; + int i; + + /* Directly attached device */ + if (!pdev) + return dev->port->phy; + + /* Otherwise look in the expander */ + for (i = 0; i < pdev->ex_dev.num_phys; i++) + if (!memcmp(dev->sas_addr, + pdev->ex_dev.ex_phy[i].attached_sas_addr, + SAS_ADDR_SIZE)) { + exphy = &pdev->ex_dev.ex_phy[i]; + break; + } + + BUG_ON(!exphy); + return exphy->phy; +} + +/* Attempt to send a LUN reset message to a device */ +int sas_eh_device_reset_handler(struct scsi_cmnd *cmd) +{ + struct domain_device *dev = cmd_to_domain_dev(cmd); + struct sas_internal *i = + to_sas_internal(dev->port->ha->core.shost->transportt); + struct scsi_lun lun; + int res; + + int_to_scsilun(cmd->device->lun, &lun); + + if (!i->dft->lldd_lu_reset) + return FAILED; + + res = i->dft->lldd_lu_reset(dev, lun.scsi_lun); + if (res == TMF_RESP_FUNC_SUCC || res == TMF_RESP_FUNC_COMPLETE) + return SUCCESS; + + return FAILED; +} + +/* Attempt to send a phy (bus) reset */ +int sas_eh_bus_reset_handler(struct scsi_cmnd *cmd) +{ + struct domain_device *dev = cmd_to_domain_dev(cmd); + struct sas_phy *phy = find_local_sas_phy(dev); + int res; + + res = sas_phy_reset(phy, 1); + if (res) + SAS_DPRINTK("Bus reset of %s failed 0x%x\n", + phy->dev.kobj.k_name, + res); + if (res == TMF_RESP_FUNC_SUCC || res == TMF_RESP_FUNC_COMPLETE) + return SUCCESS; + + return FAILED; +} + +/* Try to reset a device */ +static int try_to_reset_cmd_device(struct Scsi_Host *shost, + struct scsi_cmnd *cmd) +{ + int res; + + if (!shost->hostt->eh_device_reset_handler) + goto try_bus_reset; + + res = shost->hostt->eh_device_reset_handler(cmd); + if (res == SUCCESS) + return res; + +try_bus_reset: + if (shost->hostt->eh_bus_reset_handler) + return shost->hostt->eh_bus_reset_handler(cmd); + + return FAILED; +} + +static int sas_eh_handle_sas_errors(struct Scsi_Host *shost, + struct list_head *work_q, + struct list_head *done_q) { - struct sas_ha_struct *ha = SHOST_TO_SAS_HA(shost); - unsigned long flags; - LIST_HEAD(error_q); struct scsi_cmnd *cmd, *n; enum task_disposition res = TASK_IS_DONE; - int tmf_resp; + int tmf_resp, need_reset; struct sas_internal *i = to_sas_internal(shost->transportt); + unsigned long flags; + struct sas_ha_struct *ha = SHOST_TO_SAS_HA(shost); - spin_lock_irqsave(shost->host_lock, flags); - list_splice_init(&shost->eh_cmd_q, &error_q); - spin_unlock_irqrestore(shost->host_lock, flags); - - SAS_DPRINTK("Enter %s\n", __FUNCTION__); - - /* All tasks on this list were marked SAS_TASK_STATE_ABORTED - * by sas_scsi_timed_out() callback. - */ Again: - SAS_DPRINTK("going over list...\n"); - list_for_each_entry_safe(cmd, n, &error_q, eh_entry) { + list_for_each_entry_safe(cmd, n, work_q, eh_entry) { struct sas_task *task = TO_SAS_TASK(cmd); - SAS_DPRINTK("trying to find task 0x%p\n", task); + if (!task) + continue; + list_del_init(&cmd->eh_entry); + + spin_lock_irqsave(&task->task_state_lock, flags); + need_reset = task->task_state_flags & SAS_TASK_NEED_DEV_RESET; + spin_unlock_irqrestore(&task->task_state_lock, flags); + + SAS_DPRINTK("trying to find task 0x%p\n", task); res = sas_scsi_find_task(task); cmd->eh_eflags = 0; - shost->host_failed--; switch (res) { case TASK_IS_DONE: SAS_DPRINTK("%s: task 0x%p is done\n", __FUNCTION__, task); task->task_done(task); + if (need_reset) + try_to_reset_cmd_device(shost, cmd); continue; case TASK_IS_ABORTED: SAS_DPRINTK("%s: task 0x%p is aborted\n", __FUNCTION__, task); task->task_done(task); + if (need_reset) + try_to_reset_cmd_device(shost, cmd); continue; case TASK_IS_AT_LU: SAS_DPRINTK("task 0x%p is at LU: lu recover\n", task); @@ -437,11 +533,14 @@ Again: SAS_ADDR(task->dev), cmd->device->lun); task->task_done(task); - sas_scsi_clear_queue_lu(&error_q, cmd); + if (need_reset) + try_to_reset_cmd_device(shost, cmd); + sas_scsi_clear_queue_lu(work_q, cmd); goto Again; } /* fallthrough */ case TASK_IS_NOT_AT_LU: + case TASK_ABORT_FAILED: SAS_DPRINTK("task 0x%p is not at LU: I_T recover\n", task); tmf_resp = sas_recover_I_T(task->dev); @@ -449,7 +548,9 @@ Again: SAS_DPRINTK("I_T %016llx recovered\n", SAS_ADDR(task->dev->sas_addr)); task->task_done(task); - sas_scsi_clear_queue_I_T(&error_q, task->dev); + if (need_reset) + try_to_reset_cmd_device(shost, cmd); + sas_scsi_clear_queue_I_T(work_q, task->dev); goto Again; } /* Hammer time :-) */ @@ -462,7 +563,9 @@ Again: SAS_DPRINTK("clear nexus port:%d " "succeeded\n", port->id); task->task_done(task); - sas_scsi_clear_queue_port(&error_q, + if (need_reset) + try_to_reset_cmd_device(shost, cmd); + sas_scsi_clear_queue_port(work_q, port); goto Again; } @@ -474,6 +577,8 @@ Again: SAS_DPRINTK("clear nexus ha " "succeeded\n"); task->task_done(task); + if (need_reset) + try_to_reset_cmd_device(shost, cmd); goto out; } } @@ -487,19 +592,54 @@ Again: cmd->device->lun); task->task_done(task); + if (need_reset) + try_to_reset_cmd_device(shost, cmd); goto clear_q; } } out: - SAS_DPRINTK("--- Exit %s\n", __FUNCTION__); - return; + return list_empty(work_q); clear_q: SAS_DPRINTK("--- Exit %s -- clear_q\n", __FUNCTION__); - list_for_each_entry_safe(cmd, n, &error_q, eh_entry) { + list_for_each_entry_safe(cmd, n, work_q, eh_entry) { struct sas_task *task = TO_SAS_TASK(cmd); list_del_init(&cmd->eh_entry); task->task_done(task); } + return list_empty(work_q); +} + +void sas_scsi_recover_host(struct Scsi_Host *shost) +{ + struct sas_ha_struct *ha = SHOST_TO_SAS_HA(shost); + unsigned long flags; + LIST_HEAD(eh_work_q); + + spin_lock_irqsave(shost->host_lock, flags); + list_splice_init(&shost->eh_cmd_q, &eh_work_q); + spin_unlock_irqrestore(shost->host_lock, flags); + + SAS_DPRINTK("Enter %s\n", __FUNCTION__); + /* + * Deal with commands that still have SAS tasks (i.e. they didn't + * complete via the normal sas_task completion mechanism) + */ + if (sas_eh_handle_sas_errors(shost, &eh_work_q, &ha->eh_done_q)) + goto out; + + /* + * Now deal with SCSI commands that completed ok but have a an error + * code (and hopefully sense data) attached. This is roughly what + * scsi_unjam_host does, but we skip scsi_eh_abort_cmds because any + * command we see here has no sas_task and is thus unknown to the HA. + */ + if (!scsi_eh_get_sense(&eh_work_q, &ha->eh_done_q)) + scsi_eh_ready_devs(shost, &eh_work_q, &ha->eh_done_q); + +out: + scsi_eh_flush_done_q(&ha->eh_done_q); + SAS_DPRINTK("--- Exit %s\n", __FUNCTION__); + return; } enum scsi_eh_timer_return sas_scsi_timed_out(struct scsi_cmnd *cmd) @@ -508,18 +648,30 @@ enum scsi_eh_timer_return sas_scsi_timed unsigned long flags; if (!task) { - SAS_DPRINTK("command 0x%p, task 0x%p, timed out: EH_HANDLED\n", - cmd, task); - return EH_HANDLED; + cmd->timeout_per_command /= 2; + SAS_DPRINTK("command 0x%p, task 0x%p, gone: %s\n", + cmd, task, (cmd->timeout_per_command ? + "EH_RESET_TIMER" : "EH_NOT_HANDLED")); + if (!cmd->timeout_per_command) + return EH_NOT_HANDLED; + return EH_RESET_TIMER; } spin_lock_irqsave(&task->task_state_lock, flags); + BUG_ON(task->task_state_flags & SAS_TASK_STATE_ABORTED); if (task->task_state_flags & SAS_TASK_STATE_DONE) { spin_unlock_irqrestore(&task->task_state_lock, flags); SAS_DPRINTK("command 0x%p, task 0x%p, timed out: EH_HANDLED\n", cmd, task); return EH_HANDLED; } + if (!(task->task_state_flags & SAS_TASK_AT_INITIATOR)) { + spin_unlock_irqrestore(&task->task_state_lock, flags); + SAS_DPRINTK("command 0x%p, task 0x%p, not at initiator: " + "EH_RESET_TIMER\n", + cmd, task); + return EH_RESET_TIMER; + } task->task_state_flags |= SAS_TASK_STATE_ABORTED; spin_unlock_irqrestore(&task->task_state_lock, flags); @@ -535,8 +687,9 @@ struct domain_device *sas_find_dev_by_rp struct sas_ha_struct *ha = SHOST_TO_SAS_HA(shost); struct domain_device *found_dev = NULL; int i; + unsigned long flags; - spin_lock(&ha->phy_port_lock); + spin_lock_irqsave(&ha->phy_port_lock, flags); for (i = 0; i < ha->num_phys; i++) { struct asd_sas_port *port = ha->sas_port[i]; struct domain_device *dev; @@ -552,7 +705,7 @@ struct domain_device *sas_find_dev_by_rp spin_unlock(&port->dev_list_lock); } found: - spin_unlock(&ha->phy_port_lock); + spin_unlock_irqrestore(&ha->phy_port_lock, flags); return found_dev; } @@ -601,6 +754,8 @@ int sas_slave_configure(struct scsi_devi scsi_deactivate_tcq(scsi_dev, 1); } + scsi_dev->allow_restart = 1; + return 0; } @@ -777,6 +932,69 @@ void sas_shutdown_queue(struct sas_ha_st spin_unlock_irqrestore(&core->task_queue_lock, flags); } +/* + * Call the LLDD task abort routine directly. This function is intended for + * use by upper layers that need to tell the LLDD to abort a task. + */ +int __sas_task_abort(struct sas_task *task) +{ + struct sas_internal *si = + to_sas_internal(task->dev->port->ha->core.shost->transportt); + unsigned long flags; + int res; + + spin_lock_irqsave(&task->task_state_lock, flags); + if (task->task_state_flags & SAS_TASK_STATE_ABORTED || + task->task_state_flags & SAS_TASK_STATE_DONE) { + spin_unlock_irqrestore(&task->task_state_lock, flags); + SAS_DPRINTK("%s: Task %p already finished.\n", __FUNCTION__, + task); + return 0; + } + task->task_state_flags |= SAS_TASK_STATE_ABORTED; + spin_unlock_irqrestore(&task->task_state_lock, flags); + + if (!si->dft->lldd_abort_task) + return -ENODEV; + + res = si->dft->lldd_abort_task(task); + + spin_lock_irqsave(&task->task_state_lock, flags); + if ((task->task_state_flags & SAS_TASK_STATE_DONE) || + (res == TMF_RESP_FUNC_COMPLETE)) + { + spin_unlock_irqrestore(&task->task_state_lock, flags); + task->task_done(task); + return 0; + } + + if (!(task->task_state_flags & SAS_TASK_STATE_DONE)) + task->task_state_flags &= ~SAS_TASK_STATE_ABORTED; + spin_unlock_irqrestore(&task->task_state_lock, flags); + + return -EAGAIN; +} + +/* + * Tell an upper layer that it needs to initiate an abort for a given task. + * This should only ever be called by an LLDD. + */ +void sas_task_abort(struct sas_task *task) +{ + struct scsi_cmnd *sc = task->uldd_task; + + /* Escape for libsas internal commands */ + if (!sc) { + if (!del_timer(&task->timer)) + return; + task->timer.function(task->timer.data); + return; + } + + scsi_req_abort_cmd(sc); + scsi_schedule_eh(sc->device->host); +} + EXPORT_SYMBOL_GPL(sas_queuecommand); EXPORT_SYMBOL_GPL(sas_target_alloc); EXPORT_SYMBOL_GPL(sas_slave_configure); @@ -784,3 +1002,9 @@ EXPORT_SYMBOL_GPL(sas_slave_destroy); EXPORT_SYMBOL_GPL(sas_change_queue_depth); EXPORT_SYMBOL_GPL(sas_change_queue_type); EXPORT_SYMBOL_GPL(sas_bios_param); +EXPORT_SYMBOL_GPL(__sas_task_abort); +EXPORT_SYMBOL_GPL(sas_task_abort); +EXPORT_SYMBOL_GPL(sas_phy_reset); +EXPORT_SYMBOL_GPL(sas_phy_enable); +EXPORT_SYMBOL_GPL(sas_eh_device_reset_handler); +EXPORT_SYMBOL_GPL(sas_eh_bus_reset_handler); diff -urNp linux-2.6.18.orig/drivers/scsi/scsi_error.c linux-2.6.18/drivers/scsi/scsi_error.c --- linux-2.6.18.orig/drivers/scsi/scsi_error.c 2007-06-21 16:43:05.000000000 -0400 +++ linux-2.6.18/drivers/scsi/scsi_error.c 2007-06-21 16:47:04.000000000 -0400 @@ -575,9 +575,18 @@ static void scsi_abort_eh_cmnd(struct sc } /** - * scsi_send_eh_cmnd - send a cmd to a device as part of error recovery. - * @scmd: SCSI Cmd to send. - * @timeout: Timeout for cmd. + * scsi_send_eh_cmnd - submit a scsi command as part of error recory + * @scmd: SCSI command structure to hijack + * @cmnd: CDB to send + * @cmnd_size: size in bytes of @cmnd + * @timeout: timeout for this request + * @copy_sense: request sense data if set to 1 + * + * This function is used to send a scsi command down to a target device + * as part of the error recovery process. If @copy_sense is 0 the command + * sent must be one that does not transfer any data. If @copy_sense is 1 + * the command must be REQUEST_SENSE and this functions copies out the + * sense buffer it got into @scmd->sense_buffer. * * Return value: * SUCCESS or FAILED or NEEDS_RETRY @@ -591,6 +600,7 @@ static int scsi_send_eh_cmnd(struct scsi DECLARE_COMPLETION_ONSTACK(done); unsigned long timeleft; unsigned long flags; + struct scatterlist sgl; unsigned char old_cmnd[MAX_COMMAND_SIZE]; enum dma_data_direction old_data_direction; unsigned short old_use_sg; @@ -617,24 +627,29 @@ static int scsi_send_eh_cmnd(struct scsi memcpy(scmd->cmnd, cmnd, cmnd_size); if (copy_sense) { - int gfp_mask = GFP_ATOMIC; + gfp_t gfp_mask = GFP_ATOMIC; if (shost->hostt->unchecked_isa_dma) gfp_mask |= __GFP_DMA; - scmd->sc_data_direction = DMA_FROM_DEVICE; - scmd->request_bufflen = 252; - scmd->request_buffer = kzalloc(scmd->request_bufflen, gfp_mask); - if (!scmd->request_buffer) + sgl.page = alloc_page(gfp_mask); + if (!sgl.page) return FAILED; + sgl.offset = 0; + sgl.length = 252; + + scmd->sc_data_direction = DMA_FROM_DEVICE; + scmd->request_bufflen = sgl.length; + scmd->request_buffer = &sgl; + scmd->use_sg = 1; } else { scmd->request_buffer = NULL; scmd->request_bufflen = 0; scmd->sc_data_direction = DMA_NONE; + scmd->use_sg = 0; } scmd->underflow = 0; - scmd->use_sg = 0; scmd->cmd_len = COMMAND_SIZE(scmd->cmnd[0]); if (sdev->scsi_level <= SCSI_2) @@ -699,7 +714,7 @@ static int scsi_send_eh_cmnd(struct scsi memcpy(scmd->sense_buffer, scmd->request_buffer, sizeof(scmd->sense_buffer)); } - kfree(scmd->request_buffer); + __free_page(sgl.page); } @@ -773,8 +788,8 @@ EXPORT_SYMBOL(scsi_eh_finish_cmd); * XXX: Long term this code should go away, but that needs an audit of * all LLDDs first. **/ -static int scsi_eh_get_sense(struct list_head *work_q, - struct list_head *done_q) +int scsi_eh_get_sense(struct list_head *work_q, + struct list_head *done_q) { struct scsi_cmnd *scmd, *next; int rtn; @@ -816,6 +831,7 @@ static int scsi_eh_get_sense(struct list return list_empty(work_q); } +EXPORT_SYMBOL(scsi_eh_get_sense); /** * scsi_eh_tur - Send TUR to device. @@ -1403,9 +1419,9 @@ static void scsi_restart_operations(stru * @eh_done_q: list_head for processed commands. * **/ -static void scsi_eh_ready_devs(struct Scsi_Host *shost, - struct list_head *work_q, - struct list_head *done_q) +void scsi_eh_ready_devs(struct Scsi_Host *shost, + struct list_head *work_q, + struct list_head *done_q) { if (!scsi_eh_stu(shost, work_q, done_q)) if (!scsi_eh_bus_device_reset(shost, work_q, done_q)) @@ -1413,6 +1429,7 @@ static void scsi_eh_ready_devs(struct Sc if (!scsi_eh_host_reset(work_q, done_q)) scsi_eh_offline_sdevs(work_q, done_q); } +EXPORT_SYMBOL(scsi_eh_ready_devs); /** * scsi_eh_flush_done_q - finish processed commands or retry them. diff -urNp linux-2.6.18.orig/drivers/scsi/scsi_priv.h linux-2.6.18/drivers/scsi/scsi_priv.h --- linux-2.6.18.orig/drivers/scsi/scsi_priv.h 2007-06-21 16:43:05.000000000 -0400 +++ linux-2.6.18/drivers/scsi/scsi_priv.h 2007-06-21 16:47:04.000000000 -0400 @@ -39,6 +39,9 @@ static inline void scsi_log_completion(s { }; #endif +/* scsi_scan.c */ +int scsi_complete_async_scans(void); + /* scsi_devinfo.c */ extern int scsi_get_device_flags(struct scsi_device *sdev, const unsigned char *vendor, @@ -55,6 +58,11 @@ extern int scsi_error_handler(void *host extern int scsi_decide_disposition(struct scsi_cmnd *cmd); extern void scsi_eh_wakeup(struct Scsi_Host *shost); extern int scsi_eh_scmd_add(struct scsi_cmnd *, int); +void scsi_eh_ready_devs(struct Scsi_Host *shost, + struct list_head *work_q, + struct list_head *done_q); +int scsi_eh_get_sense(struct list_head *work_q, + struct list_head *done_q); /* scsi_lib.c */ extern int scsi_maybe_unblock_host(struct scsi_device *sdev); diff -urNp linux-2.6.18.orig/drivers/scsi/scsi_transport_sas.c linux-2.6.18/drivers/scsi/scsi_transport_sas.c --- linux-2.6.18.orig/drivers/scsi/scsi_transport_sas.c 2007-06-21 16:43:05.000000000 -0400 +++ linux-2.6.18/drivers/scsi/scsi_transport_sas.c 2007-06-21 16:47:04.000000000 -0400 @@ -25,6 +25,7 @@ #include <linux/init.h> #include <linux/module.h> +#include <linux/jiffies.h> #include <linux/err.h> #include <linux/slab.h> #include <linux/string.h> @@ -77,6 +78,24 @@ get_sas_##title##_names(u32 table_key, c return len; \ } +#define sas_bitfield_name_set(title, table) \ +static ssize_t \ +set_sas_##title##_names(u32 *table_key, const char *buf) \ +{ \ + ssize_t len = 0; \ + int i; \ + \ + for (i = 0; i < ARRAY_SIZE(table); i++) { \ + len = strlen(table[i].name); \ + if (strncmp(buf, table[i].name, len) == 0 && \ + (buf[len] == '\n' || buf[len] == '\0')) { \ + *table_key = table[i].value; \ + return 0; \ + } \ + } \ + return -EINVAL; \ +} + #define sas_bitfield_name_search(title, table) \ static ssize_t \ get_sas_##title##_names(u32 table_key, char *buf) \ @@ -122,16 +141,16 @@ static struct { u32 value; char *name; } sas_linkspeed_names[] = { - { SAS_LINK_RATE_UNKNOWN, "Unknown" }, - { SAS_PHY_DISABLED, "Phy disabled" }, - { SAS_LINK_RATE_FAILED, "Link Rate failed" }, - { SAS_SATA_SPINUP_HOLD, "Spin-up hold" }, - { SAS_LINK_RATE_1_5_GBPS, "1.5 Gbit" }, - { SAS_LINK_RATE_3_0_GBPS, "3.0 Gbit" }, - { SAS_LINK_RATE_6_0_GBPS, "6.0 Gbit" }, + { PHY_LINKRATE_UNKNOWN, "Unknown" }, + { PHY_DISABLED, "Phy disabled" }, + { 0x10, "Link Rate failed" }, + { PHY_SPINUP_HOLD, "Spin-up hold" }, + { PHY_LINKRATE_1_5, "1.5 Gbit" }, + { PHY_LINKRATE_3, "3.0 Gbit" }, + { PHY_LINKRATE_6, "6.0 Gbit" }, }; sas_bitfield_name_search(linkspeed, sas_linkspeed_names) - +sas_bitfield_name_set(linkspeed, sas_linkspeed_names) /* * SAS host attributes @@ -253,10 +272,39 @@ show_sas_phy_##field(struct class_device return get_sas_linkspeed_names(phy->field, buf); \ } +/* Fudge to tell if we're minimum or maximum */ +#define sas_phy_store_linkspeed(field) \ +static ssize_t \ +store_sas_phy_##field(struct class_device *cdev, const char *buf, \ + size_t count) \ +{ \ + struct sas_phy *phy = transport_class_to_phy(cdev); \ + struct Scsi_Host *shost = dev_to_shost(phy->dev.parent); \ + struct sas_internal *i = to_sas_internal(shost->transportt); \ + u32 value; \ + struct sas_phy_linkrates rates = {0}; \ + int error; \ + \ + error = set_sas_linkspeed_names(&value, buf); \ + if (error) \ + return error; \ + rates.field = value; \ + error = i->f->set_phy_speed(phy, &rates); \ + \ + return error ? error : count; \ +} + +#define sas_phy_linkspeed_rw_attr(field) \ + sas_phy_show_linkspeed(field) \ + sas_phy_store_linkspeed(field) \ +static CLASS_DEVICE_ATTR(field, S_IRUGO, show_sas_phy_##field, \ + store_sas_phy_##field) + #define sas_phy_linkspeed_attr(field) \ sas_phy_show_linkspeed(field) \ static CLASS_DEVICE_ATTR(field, S_IRUGO, show_sas_phy_##field, NULL) + #define sas_phy_show_linkerror(field) \ static ssize_t \ show_sas_phy_##field(struct class_device *cdev, char *buf) \ @@ -288,6 +336,51 @@ show_sas_device_type(struct class_device } static CLASS_DEVICE_ATTR(device_type, S_IRUGO, show_sas_device_type, NULL); +static ssize_t do_sas_phy_enable(struct class_device *cdev, + size_t count, int enable) +{ + struct sas_phy *phy = transport_class_to_phy(cdev); + struct Scsi_Host *shost = dev_to_shost(phy->dev.parent); + struct sas_internal *i = to_sas_internal(shost->transportt); + int error; + + error = i->f->phy_enable(phy, enable); + if (error) + return error; + phy->enabled = enable; + return count; +}; + +static ssize_t store_sas_phy_enable(struct class_device *cdev, + const char *buf, size_t count) +{ + if (count < 1) + return -EINVAL; + + switch (buf[0]) { + case '0': + do_sas_phy_enable(cdev, count, 0); + break; + case '1': + do_sas_phy_enable(cdev, count, 1); + break; + default: + return -EINVAL; + } + + return count; +} + +static ssize_t show_sas_phy_enable(struct class_device *cdev, char *buf) +{ + struct sas_phy *phy = transport_class_to_phy(cdev); + + return snprintf(buf, 20, "%d", phy->enabled); +} + +static CLASS_DEVICE_ATTR(enable, S_IRUGO | S_IWUSR, show_sas_phy_enable, + store_sas_phy_enable); + static ssize_t do_sas_phy_reset(struct class_device *cdev, size_t count, int hard_reset) { @@ -326,9 +419,9 @@ sas_phy_simple_attr(identify.phy_identif //sas_phy_simple_attr(port_identifier, port_identifier, "%d\n", int); sas_phy_linkspeed_attr(negotiated_linkrate); sas_phy_linkspeed_attr(minimum_linkrate_hw); -sas_phy_linkspeed_attr(minimum_linkrate); +sas_phy_linkspeed_rw_attr(minimum_linkrate); sas_phy_linkspeed_attr(maximum_linkrate_hw); -sas_phy_linkspeed_attr(maximum_linkrate); +sas_phy_linkspeed_rw_attr(maximum_linkrate); sas_phy_linkerror_attr(invalid_dword_count); sas_phy_linkerror_attr(running_disparity_error_count); sas_phy_linkerror_attr(loss_of_dword_sync_count); @@ -387,6 +480,7 @@ struct sas_phy *sas_phy_alloc(struct dev return NULL; phy->number = number; + phy->enabled = 1; device_initialize(&phy->dev); phy->dev.parent = get_device(parent); @@ -531,8 +625,19 @@ static void sas_port_release(struct devi static void sas_port_create_link(struct sas_port *port, struct sas_phy *phy) { - sysfs_create_link(&port->dev.kobj, &phy->dev.kobj, phy->dev.bus_id); - sysfs_create_link(&phy->dev.kobj, &port->dev.kobj, "port"); + int res; + + res = sysfs_create_link(&port->dev.kobj, &phy->dev.kobj, + phy->dev.bus_id); + if (res) + goto err; + res = sysfs_create_link(&phy->dev.kobj, &port->dev.kobj, "port"); + if (res) + goto err; + return; +err: + printk(KERN_ERR "%s: Cannot create port links, err=%d\n", + __FUNCTION__, res); } static void sas_port_delete_link(struct sas_port *port, @@ -770,13 +875,20 @@ EXPORT_SYMBOL(sas_port_delete_phy); void sas_port_mark_backlink(struct sas_port *port) { + int res; struct device *parent = port->dev.parent->parent->parent; if (port->is_backlink) return; port->is_backlink = 1; - sysfs_create_link(&port->dev.kobj, &parent->kobj, - parent->bus_id); + res = sysfs_create_link(&port->dev.kobj, &parent->kobj, + parent->bus_id); + if (res) + goto err; + return; +err: + printk(KERN_ERR "%s: Cannot create port backlink, err=%d\n", + __FUNCTION__, res); } EXPORT_SYMBOL(sas_port_mark_backlink); @@ -1189,7 +1301,7 @@ int sas_rphy_add(struct sas_rphy *rphy) if (identify->device_type == SAS_END_DEVICE && rphy->scsi_target_id != -1) { scsi_scan_target(&rphy->dev, 0, - rphy->scsi_target_id, ~0, 0); + rphy->scsi_target_id, SCAN_WILD_CARD, 0); } return 0; @@ -1205,7 +1317,7 @@ EXPORT_SYMBOL(sas_rphy_add); * Note: * This function must only be called on a remote * PHY that has not sucessfully been added using - * sas_rphy_add(). + * sas_rphy_add() (or has been sas_rphy_remove()'d) */ void sas_rphy_free(struct sas_rphy *rphy) { @@ -1224,18 +1336,30 @@ void sas_rphy_free(struct sas_rphy *rphy EXPORT_SYMBOL(sas_rphy_free); /** - * sas_rphy_delete -- remove SAS remote PHY - * @rphy: SAS remote PHY to remove + * sas_rphy_delete -- remove and free SAS remote PHY + * @rphy: SAS remote PHY to remove and free * - * Removes the specified SAS remote PHY. + * Removes the specified SAS remote PHY and frees it. */ void sas_rphy_delete(struct sas_rphy *rphy) { + sas_rphy_remove(rphy); + sas_rphy_free(rphy); +} +EXPORT_SYMBOL(sas_rphy_delete); + +/** + * sas_rphy_remove -- remove SAS remote PHY + * @rphy: SAS remote phy to remove + * + * Removes the specified SAS remote PHY. + */ +void +sas_rphy_remove(struct sas_rphy *rphy) +{ struct device *dev = &rphy->dev; struct sas_port *parent = dev_to_sas_port(dev->parent); - struct Scsi_Host *shost = dev_to_shost(parent->dev.parent); - struct sas_host_attrs *sas_host = to_sas_host_attrs(shost); switch (rphy->identify.device_type) { case SAS_END_DEVICE: @@ -1251,17 +1375,10 @@ sas_rphy_delete(struct sas_rphy *rphy) transport_remove_device(dev); device_del(dev); - transport_destroy_device(dev); - - mutex_lock(&sas_host->lock); - list_del(&rphy->list); - mutex_unlock(&sas_host->lock); parent->rphy = NULL; - - put_device(dev); } -EXPORT_SYMBOL(sas_rphy_delete); +EXPORT_SYMBOL(sas_rphy_remove); /** * scsi_is_sas_rphy -- check if a struct device represents a SAS remote PHY @@ -1310,13 +1427,23 @@ static int sas_user_scan(struct Scsi_Hos * Setup / Teardown code */ -#define SETUP_TEMPLATE(attrb, field, perm, test) \ +#define SETUP_TEMPLATE(attrb, field, perm, test) \ i->private_##attrb[count] = class_device_attr_##field; \ i->private_##attrb[count].attr.mode = perm; \ i->attrb[count] = &i->private_##attrb[count]; \ if (test) \ count++ +#define SETUP_TEMPLATE_RW(attrb, field, perm, test, ro_test, ro_perm) \ + i->private_##attrb[count] = class_device_attr_##field; \ + i->private_##attrb[count].attr.mode = perm; \ + if (ro_test) { \ + i->private_##attrb[count].attr.mode = ro_perm; \ + i->private_##attrb[count].store = NULL; \ + } \ + i->attrb[count] = &i->private_##attrb[count]; \ + if (test) \ + count++ #define SETUP_RPORT_ATTRIBUTE(field) \ SETUP_TEMPLATE(rphy_attrs, field, S_IRUGO, 1) @@ -1327,6 +1454,14 @@ static int sas_user_scan(struct Scsi_Hos #define SETUP_PHY_ATTRIBUTE(field) \ SETUP_TEMPLATE(phy_attrs, field, S_IRUGO, 1) +#define SETUP_PHY_ATTRIBUTE_RW(field) \ + SETUP_TEMPLATE_RW(phy_attrs, field, S_IRUGO | S_IWUSR, 1, \ + !i->f->set_phy_speed, S_IRUGO) + +#define SETUP_OPTIONAL_PHY_ATTRIBUTE_RW(field, func) \ + SETUP_TEMPLATE_RW(phy_attrs, field, S_IRUGO | S_IWUSR, 1, \ + !i->f->func, S_IRUGO) + #define SETUP_PORT_ATTRIBUTE(field) \ SETUP_TEMPLATE(port_attrs, field, S_IRUGO, 1) @@ -1334,10 +1469,10 @@ static int sas_user_scan(struct Scsi_Hos SETUP_TEMPLATE(phy_attrs, field, S_IRUGO, i->f->func) #define SETUP_PHY_ATTRIBUTE_WRONLY(field) \ - SETUP_TEMPLATE(phy_attrs, field, S_IWUGO, 1) + SETUP_TEMPLATE(phy_attrs, field, S_IWUSR, 1) #define SETUP_OPTIONAL_PHY_ATTRIBUTE_WRONLY(field, func) \ - SETUP_TEMPLATE(phy_attrs, field, S_IWUGO, i->f->func) + SETUP_TEMPLATE(phy_attrs, field, S_IWUSR, i->f->func) #define SETUP_END_DEV_ATTRIBUTE(field) \ SETUP_TEMPLATE(end_dev_attrs, field, S_IRUGO, 1) @@ -1407,9 +1542,9 @@ sas_attach_transport(struct sas_function //SETUP_PHY_ATTRIBUTE(port_identifier); SETUP_PHY_ATTRIBUTE(negotiated_linkrate); SETUP_PHY_ATTRIBUTE(minimum_linkrate_hw); - SETUP_PHY_ATTRIBUTE(minimum_linkrate); + SETUP_PHY_ATTRIBUTE_RW(minimum_linkrate); SETUP_PHY_ATTRIBUTE(maximum_linkrate_hw); - SETUP_PHY_ATTRIBUTE(maximum_linkrate); + SETUP_PHY_ATTRIBUTE_RW(maximum_linkrate); SETUP_PHY_ATTRIBUTE(invalid_dword_count); SETUP_PHY_ATTRIBUTE(running_disparity_error_count); @@ -1417,6 +1552,7 @@ sas_attach_transport(struct sas_function SETUP_PHY_ATTRIBUTE(phy_reset_problem_count); SETUP_OPTIONAL_PHY_ATTRIBUTE_WRONLY(link_reset, phy_reset); SETUP_OPTIONAL_PHY_ATTRIBUTE_WRONLY(hard_reset, phy_reset); + SETUP_OPTIONAL_PHY_ATTRIBUTE_RW(enable, phy_enable); i->phy_attrs[count] = NULL; count = 0; diff -urNp linux-2.6.18.orig/include/scsi/libsas.h linux-2.6.18/include/scsi/libsas.h --- linux-2.6.18.orig/include/scsi/libsas.h 2007-06-21 16:43:57.000000000 -0400 +++ linux-2.6.18/include/scsi/libsas.h 2007-06-21 16:53:45.000000000 -0400 @@ -32,10 +32,10 @@ #include <scsi/sas.h> #include <linux/list.h> #include <asm/semaphore.h> -#include <asm/scatterlist.h> #include <scsi/scsi_device.h> #include <scsi/scsi_cmnd.h> #include <scsi/scsi_transport_sas.h> +#include <asm/scatterlist.h> struct block_device; @@ -171,9 +171,9 @@ struct sata_device { struct domain_device { enum sas_dev_type dev_type; - enum sas_phy_linkrate linkrate; - enum sas_phy_linkrate min_linkrate; - enum sas_phy_linkrate max_linkrate; + enum sas_phy_linkrate linkrate; + enum sas_phy_linkrate min_linkrate; + enum sas_phy_linkrate max_linkrate; int pathways; @@ -249,6 +249,11 @@ struct asd_sas_port { void *lldd_port; /* not touched by the sas class code */ }; +struct asd_sas_event { + struct work_struct work; + struct asd_sas_phy *phy; +}; + /* The phy pretty much is controlled by the LLDD. * The class only reads those fields. */ @@ -308,6 +313,11 @@ struct scsi_core { int queue_thread_kill; }; +enum sas_ha_state { + SAS_HA_REGISTERED, + SAS_HA_UNREGISTERED +}; + struct sas_ha_struct { /* private: */ spinlock_t event_lock; @@ -339,6 +349,14 @@ struct sas_ha_struct { void (*notify_phy_event)(struct asd_sas_phy *, enum phy_event); void *lldd_ha; /* not touched by sas class code */ + +#ifndef __GENKSYMS__ + struct list_head eh_done_q; + + enum sas_ha_state state; + spinlock_t state_lock; +#endif + }; #define SHOST_TO_SAS_HA(_shost) (*(struct sas_ha_struct **)(_shost)->hostdata) @@ -369,7 +387,7 @@ void sas_hash_addr(u8 *hashed, const u8 static inline void sas_phy_disconnected(struct asd_sas_phy *phy) { phy->oob_mode = OOB_NOT_CONNECTED; - phy->linkrate = PHY_LINKRATE_NONE; + phy->linkrate = SAS_LINK_RATE_UNKNOWN; } /* ---------- Tasks ---------- */ @@ -527,17 +545,22 @@ struct sas_task { void *lldd_task; /* for use by LLDDs */ void *uldd_task; +#ifndef __GENKSYMS__ + struct work_struct abort_work; +#endif }; -#define SAS_TASK_STATE_PENDING 1 -#define SAS_TASK_STATE_DONE 2 -#define SAS_TASK_STATE_ABORTED 4 +#define SAS_TASK_STATE_PENDING 1 +#define SAS_TASK_STATE_DONE 2 +#define SAS_TASK_STATE_ABORTED 4 +#define SAS_TASK_NEED_DEV_RESET 8 +#define SAS_TASK_AT_INITIATOR 16 -static inline struct sas_task *sas_alloc_task(unsigned long flags) +static inline struct sas_task *sas_alloc_task(gfp_t flags) { - extern kmem_cache_t *sas_task_cache; + extern struct kmem_cache *sas_task_cache; struct sas_task *task = kmem_cache_alloc(sas_task_cache, flags); if (task) { @@ -555,7 +578,7 @@ static inline struct sas_task *sas_alloc static inline void sas_free_task(struct sas_task *task) { if (task) { - extern kmem_cache_t *sas_task_cache; + extern struct kmem_cache *sas_task_cache; BUG_ON(!list_empty(&task->list)); kmem_cache_free(sas_task_cache, task); } @@ -588,11 +611,18 @@ struct sas_domain_function_template { /* Phy management */ int (*lldd_control_phy)(struct asd_sas_phy *, enum phy_func); +#ifndef __GENKSYMS__ + int (*lldd_control_phy_new)(struct asd_sas_phy *, enum phy_func, void *); +#endif }; extern int sas_register_ha(struct sas_ha_struct *); extern int sas_unregister_ha(struct sas_ha_struct *); +int sas_set_phy_speed(struct sas_phy *phy, + struct sas_phy_linkrates *rates); +int sas_phy_enable(struct sas_phy *phy, int enabled); +int sas_phy_reset(struct sas_phy *phy, int hard_reset); extern int sas_queuecommand(struct scsi_cmnd *, void (*scsi_done)(struct scsi_cmnd *)); extern int sas_target_alloc(struct scsi_target *); @@ -625,4 +655,9 @@ void sas_unregister_dev(struct domain_de void sas_init_dev(struct domain_device *); +void sas_task_abort(struct sas_task *); +int __sas_task_abort(struct sas_task *); +int sas_eh_device_reset_handler(struct scsi_cmnd *cmd); +int sas_eh_bus_reset_handler(struct scsi_cmnd *cmd); + #endif /* _SASLIB_H_ */ diff -urNp linux-2.6.18.orig/include/scsi/sas.h linux-2.6.18/include/scsi/sas.h --- linux-2.6.18.orig/include/scsi/sas.h 2007-06-21 16:43:57.000000000 -0400 +++ linux-2.6.18/include/scsi/sas.h 2007-06-21 16:47:04.000000000 -0400 @@ -102,20 +102,6 @@ enum sas_dev_type { SATA_PM_PORT= 8, }; -enum sas_phy_linkrate { - PHY_LINKRATE_NONE = 0, - PHY_LINKRATE_UNKNOWN = 0, - PHY_DISABLED, - PHY_RESET_PROBLEM, - PHY_SPINUP_HOLD, - PHY_PORT_SELECTOR, - PHY_LINKRATE_1_5 = 0x08, - PHY_LINKRATE_G1 = PHY_LINKRATE_1_5, - PHY_LINKRATE_3 = 0x09, - PHY_LINKRATE_G2 = PHY_LINKRATE_3, - PHY_LINKRATE_6 = 0x0A, -}; - /* Partly from IDENTIFY address frame. */ enum sas_proto { SATA_PROTO = 1, @@ -135,6 +121,9 @@ enum phy_func { PHY_FUNC_CLEAR_AFFIL, PHY_FUNC_TX_SATA_PS_SIGNAL, PHY_FUNC_RELEASE_SPINUP_HOLD = 0x10, /* LOCAL PORT ONLY! */ +#ifndef __GENKSYMS__ + PHY_FUNC_SET_LINK_RATE, +#endif }; /* SAS LLDD would need to report only _very_few_ of those, like BROADCAST. diff -urNp linux-2.6.18.orig/include/scsi/scsi_transport_sas.h linux-2.6.18/include/scsi/scsi_transport_sas.h --- linux-2.6.18.orig/include/scsi/scsi_transport_sas.h 2007-06-21 16:43:57.000000000 -0400 +++ linux-2.6.18/include/scsi/scsi_transport_sas.h 2007-06-21 16:47:04.000000000 -0400 @@ -23,6 +23,26 @@ enum sas_protocol { SAS_PROTOCOL_SSP = 0x08, }; +/* The following two enums are pretty much a mess, but before 5.1 + sas_phy_linkrate existed in sas.h, and sas_linkrate existed in + scsi_transport_sas.h. These were merged into one with standard + values. In order to not break function CRCs, we need to have a + copy of each for genksyms. +*/ +enum sas_phy_linkrate { + PHY_LINKRATE_NONE = 0, + PHY_LINKRATE_UNKNOWN = 0, + PHY_DISABLED, + PHY_RESET_PROBLEM, + PHY_SPINUP_HOLD, + PHY_PORT_SELECTOR, + PHY_LINKRATE_1_5 = 0x08, + PHY_LINKRATE_G1 = PHY_LINKRATE_1_5, + PHY_LINKRATE_3 = 0x09, + PHY_LINKRATE_G2 = PHY_LINKRATE_3, + PHY_LINKRATE_6 = 0x0A, +}; + enum sas_linkrate { SAS_LINK_RATE_UNKNOWN, SAS_PHY_DISABLED, @@ -65,6 +85,11 @@ struct sas_phy { /* for the list of phys belonging to a port */ struct list_head port_siblings; + +#ifndef __GENKSYMS__ + struct work_struct reset_work; + int enabled; +#endif }; #define dev_to_phy(d) \ @@ -142,12 +167,21 @@ struct sas_port { #define transport_class_to_sas_port(cdev) \ dev_to_sas_port((cdev)->dev) +struct sas_phy_linkrates { + enum sas_phy_linkrate maximum_linkrate; + enum sas_phy_linkrate minimum_linkrate; +}; + /* The functions by which the transport class and the driver communicate */ struct sas_function_template { int (*get_linkerrors)(struct sas_phy *); int (*get_enclosure_identifier)(struct sas_rphy *, u64 *); int (*get_bay_identifier)(struct sas_rphy *); int (*phy_reset)(struct sas_phy *, int); +#ifndef __GENKSYMS__ + int (*set_phy_speed)(struct sas_phy *, struct sas_phy_linkrates *); + int (*phy_enable)(struct sas_phy *, int); +#endif }; @@ -164,6 +198,7 @@ extern struct sas_rphy *sas_end_device_a extern struct sas_rphy *sas_expander_alloc(struct sas_port *, enum sas_device_type); void sas_rphy_free(struct sas_rphy *); extern int sas_rphy_add(struct sas_rphy *); +extern void sas_rphy_remove(struct sas_rphy *); extern void sas_rphy_delete(struct sas_rphy *); extern int scsi_is_sas_rphy(const struct device *); @@ -195,4 +230,53 @@ scsi_is_sas_expander_device(struct devic #define scsi_is_sas_phy_local(phy) scsi_is_host_device((phy)->dev.parent) +static inline enum sas_linkrate +phy_linkrate_to_linkrate(enum sas_phy_linkrate lr) +{ + switch(lr) { + case PHY_LINKRATE_UNKNOWN: + return SAS_LINK_RATE_UNKNOWN; + case PHY_DISABLED: + return SAS_PHY_DISABLED; + case PHY_SPINUP_HOLD: + return SAS_SATA_SPINUP_HOLD; + case PHY_PORT_SELECTOR: + return SAS_SATA_PORT_SELECTOR; + case PHY_LINKRATE_1_5: + return SAS_LINK_RATE_1_5_GBPS; + case PHY_LINKRATE_3: + return SAS_LINK_RATE_3_0_GBPS; + case PHY_LINKRATE_6: + return SAS_LINK_RATE_6_0_GBPS; + case PHY_RESET_PROBLEM: + default: + return SAS_LINK_RATE_UNKNOWN; + } +} + +static inline enum sas_phy_linkrate +linkrate_to_phy_linkrate(enum sas_linkrate lr) +{ + switch(lr) { + case SAS_LINK_RATE_UNKNOWN: + return PHY_LINKRATE_UNKNOWN; + case SAS_PHY_DISABLED: + return PHY_DISABLED; + case SAS_SATA_SPINUP_HOLD: + return PHY_SPINUP_HOLD; + case SAS_SATA_PORT_SELECTOR: + return PHY_PORT_SELECTOR; + case SAS_LINK_RATE_1_5_GBPS: + return PHY_LINKRATE_1_5; + case SAS_LINK_RATE_3_0_GBPS: + return PHY_LINKRATE_3; + case SAS_LINK_RATE_6_0_GBPS: + return PHY_LINKRATE_6; + case SAS_LINK_RATE_FAILED: + case SAS_LINK_VIRTUAL: + default: + return PHY_LINKRATE_UNKNOWN; + } +} + #endif /* SCSI_TRANSPORT_SAS_H */