From 691b4773aa556d0975dbc25c93e6c8b839dad325 Mon Sep 17 00:00:00 2001 From: Yinghai Lu Date: Wed, 13 Feb 2008 16:25:16 -0800 Subject: [PATCH 01/25] [SCSI] ses: fix data corruption one system: initrd get courrupted: RAMDISK: Compressed image found at block 0 RAMDISK: incomplete write (-28 != 2048) 134217728 crc error VFS: Mounted root (ext2 filesystem). Freeing unused kernel memory: 388k freed init_special_inode: bogus i_mode (177777) Warning: unable to open an initial console. init_special_inode: bogus i_mode (177777) init_special_inode: bogus i_mode (177777) Kernel panic - not syncing: No init found. Try passing init= option to kernel. bisected to commit 9927c68864e9c39cc317b4f559309ba29e642168 Author: James Bottomley Date: Sun Feb 3 15:48:56 2008 -0600 [SCSI] ses: add new Enclosure ULD changes: 1. change char to unsigned char to avoid type change later. 2. preserve len for page1 3. need to move desc_ptr even the entry is not enclosure_component_device/raid. so keep desc_ptr on right position 4. record page7 len, and double check if desc_ptr out of boundary before touch. 5. fix typo in subenclosure checking: should use hdr_buf instead. [jejb: style fixes] Signed-off-by: Yinghai Lu Signed-off-by: James Bottomley --- drivers/scsi/ses.c | 122 ++++++++++++++++++++++++--------------------- 1 file changed, 65 insertions(+), 57 deletions(-) diff --git a/drivers/scsi/ses.c b/drivers/scsi/ses.c index a57fed47b39d..a6d96694d0a5 100644 --- a/drivers/scsi/ses.c +++ b/drivers/scsi/ses.c @@ -33,9 +33,9 @@ #include struct ses_device { - char *page1; - char *page2; - char *page10; + unsigned char *page1; + unsigned char *page2; + unsigned char *page10; short page1_len; short page2_len; short page10_len; @@ -67,7 +67,7 @@ static int ses_probe(struct device *dev) static int ses_recv_diag(struct scsi_device *sdev, int page_code, void *buf, int bufflen) { - char cmd[] = { + unsigned char cmd[] = { RECEIVE_DIAGNOSTIC, 1, /* Set PCV bit */ page_code, @@ -85,7 +85,7 @@ static int ses_send_diag(struct scsi_device *sdev, int page_code, { u32 result; - char cmd[] = { + unsigned char cmd[] = { SEND_DIAGNOSTIC, 0x10, /* Set PF bit */ 0, @@ -104,13 +104,13 @@ static int ses_send_diag(struct scsi_device *sdev, int page_code, static int ses_set_page2_descriptor(struct enclosure_device *edev, struct enclosure_component *ecomp, - char *desc) + unsigned char *desc) { int i, j, count = 0, descriptor = ecomp->number; struct scsi_device *sdev = to_scsi_device(edev->cdev.dev); struct ses_device *ses_dev = edev->scratch; - char *type_ptr = ses_dev->page1 + 12 + ses_dev->page1[11]; - char *desc_ptr = ses_dev->page2 + 8; + unsigned char *type_ptr = ses_dev->page1 + 12 + ses_dev->page1[11]; + unsigned char *desc_ptr = ses_dev->page2 + 8; /* Clear everything */ memset(desc_ptr, 0, ses_dev->page2_len - 8); @@ -133,14 +133,14 @@ static int ses_set_page2_descriptor(struct enclosure_device *edev, return ses_send_diag(sdev, 2, ses_dev->page2, ses_dev->page2_len); } -static char *ses_get_page2_descriptor(struct enclosure_device *edev, +static unsigned char *ses_get_page2_descriptor(struct enclosure_device *edev, struct enclosure_component *ecomp) { int i, j, count = 0, descriptor = ecomp->number; struct scsi_device *sdev = to_scsi_device(edev->cdev.dev); struct ses_device *ses_dev = edev->scratch; - char *type_ptr = ses_dev->page1 + 12 + ses_dev->page1[11]; - char *desc_ptr = ses_dev->page2 + 8; + unsigned char *type_ptr = ses_dev->page1 + 12 + ses_dev->page1[11]; + unsigned char *desc_ptr = ses_dev->page2 + 8; ses_recv_diag(sdev, 2, ses_dev->page2, ses_dev->page2_len); @@ -160,17 +160,18 @@ static char *ses_get_page2_descriptor(struct enclosure_device *edev, static void ses_get_fault(struct enclosure_device *edev, struct enclosure_component *ecomp) { - char *desc; + unsigned char *desc; desc = ses_get_page2_descriptor(edev, ecomp); - ecomp->fault = (desc[3] & 0x60) >> 4; + if (desc) + ecomp->fault = (desc[3] & 0x60) >> 4; } static int ses_set_fault(struct enclosure_device *edev, struct enclosure_component *ecomp, enum enclosure_component_setting val) { - char desc[4] = {0 }; + unsigned char desc[4] = {0 }; switch (val) { case ENCLOSURE_SETTING_DISABLED: @@ -190,26 +191,28 @@ static int ses_set_fault(struct enclosure_device *edev, static void ses_get_status(struct enclosure_device *edev, struct enclosure_component *ecomp) { - char *desc; + unsigned char *desc; desc = ses_get_page2_descriptor(edev, ecomp); - ecomp->status = (desc[0] & 0x0f); + if (desc) + ecomp->status = (desc[0] & 0x0f); } static void ses_get_locate(struct enclosure_device *edev, struct enclosure_component *ecomp) { - char *desc; + unsigned char *desc; desc = ses_get_page2_descriptor(edev, ecomp); - ecomp->locate = (desc[2] & 0x02) ? 1 : 0; + if (desc) + ecomp->locate = (desc[2] & 0x02) ? 1 : 0; } static int ses_set_locate(struct enclosure_device *edev, struct enclosure_component *ecomp, enum enclosure_component_setting val) { - char desc[4] = {0 }; + unsigned char desc[4] = {0 }; switch (val) { case ENCLOSURE_SETTING_DISABLED: @@ -229,7 +232,7 @@ static int ses_set_active(struct enclosure_device *edev, struct enclosure_component *ecomp, enum enclosure_component_setting val) { - char desc[4] = {0 }; + unsigned char desc[4] = {0 }; switch (val) { case ENCLOSURE_SETTING_DISABLED: @@ -409,11 +412,11 @@ static int ses_intf_add(struct class_device *cdev, { struct scsi_device *sdev = to_scsi_device(cdev->dev); struct scsi_device *tmp_sdev; - unsigned char *buf = NULL, *hdr_buf, *type_ptr, *desc_ptr, - *addl_desc_ptr; + unsigned char *buf = NULL, *hdr_buf, *type_ptr, *desc_ptr = NULL, + *addl_desc_ptr = NULL; struct ses_device *ses_dev; u32 result; - int i, j, types, len, components = 0; + int i, j, types, len, page7_len = 0, components = 0; int err = -ENOMEM; struct enclosure_device *edev; struct ses_component *scomp = NULL; @@ -447,7 +450,7 @@ static int ses_intf_add(struct class_device *cdev, * traversal routines more complex */ sdev_printk(KERN_ERR, sdev, "FIXME driver has no support for subenclosures (%d)\n", - buf[1]); + hdr_buf[1]); goto err_free; } @@ -461,9 +464,8 @@ static int ses_intf_add(struct class_device *cdev, goto recv_failed; types = buf[10]; - len = buf[11]; - type_ptr = buf + 12 + len; + type_ptr = buf + 12 + buf[11]; for (i = 0; i < types; i++, type_ptr += 4) { if (type_ptr[0] == ENCLOSURE_COMPONENT_DEVICE || @@ -494,22 +496,21 @@ static int ses_intf_add(struct class_device *cdev, /* The additional information page --- allows us * to match up the devices */ result = ses_recv_diag(sdev, 10, hdr_buf, INIT_ALLOC_SIZE); - if (result) - goto no_page10; + if (!result) { - len = (hdr_buf[2] << 8) + hdr_buf[3] + 4; - buf = kzalloc(len, GFP_KERNEL); - if (!buf) - goto err_free; + len = (hdr_buf[2] << 8) + hdr_buf[3] + 4; + buf = kzalloc(len, GFP_KERNEL); + if (!buf) + goto err_free; - result = ses_recv_diag(sdev, 10, buf, len); - if (result) - goto recv_failed; - ses_dev->page10 = buf; - ses_dev->page10_len = len; - buf = NULL; + result = ses_recv_diag(sdev, 10, buf, len); + if (result) + goto recv_failed; + ses_dev->page10 = buf; + ses_dev->page10_len = len; + buf = NULL; + } - no_page10: scomp = kzalloc(sizeof(struct ses_component) * components, GFP_KERNEL); if (!scomp) goto err_free; @@ -530,7 +531,7 @@ static int ses_intf_add(struct class_device *cdev, if (result) goto simple_populate; - len = (hdr_buf[2] << 8) + hdr_buf[3] + 4; + page7_len = len = (hdr_buf[2] << 8) + hdr_buf[3] + 4; /* add 1 for trailing '\0' we'll use */ buf = kzalloc(len + 1, GFP_KERNEL); if (!buf) @@ -547,7 +548,8 @@ static int ses_intf_add(struct class_device *cdev, len = (desc_ptr[2] << 8) + desc_ptr[3]; /* skip past overall descriptor */ desc_ptr += len + 4; - addl_desc_ptr = ses_dev->page10 + 8; + if (ses_dev->page10) + addl_desc_ptr = ses_dev->page10 + 8; } type_ptr = ses_dev->page1 + 12 + ses_dev->page1[11]; components = 0; @@ -557,29 +559,35 @@ static int ses_intf_add(struct class_device *cdev, struct enclosure_component *ecomp; if (desc_ptr) { - len = (desc_ptr[2] << 8) + desc_ptr[3]; - desc_ptr += 4; - /* Add trailing zero - pushes into - * reserved space */ - desc_ptr[len] = '\0'; - name = desc_ptr; + if (desc_ptr >= buf + page7_len) { + desc_ptr = NULL; + } else { + len = (desc_ptr[2] << 8) + desc_ptr[3]; + desc_ptr += 4; + /* Add trailing zero - pushes into + * reserved space */ + desc_ptr[len] = '\0'; + name = desc_ptr; + } } - if (type_ptr[0] != ENCLOSURE_COMPONENT_DEVICE && - type_ptr[0] != ENCLOSURE_COMPONENT_ARRAY_DEVICE) - continue; - ecomp = enclosure_component_register(edev, + if (type_ptr[0] == ENCLOSURE_COMPONENT_DEVICE || + type_ptr[0] == ENCLOSURE_COMPONENT_ARRAY_DEVICE) { + + ecomp = enclosure_component_register(edev, components++, type_ptr[0], name); - if (desc_ptr) { - desc_ptr += len; - if (!IS_ERR(ecomp)) + + if (!IS_ERR(ecomp) && addl_desc_ptr) ses_process_descriptor(ecomp, addl_desc_ptr); - - if (addl_desc_ptr) - addl_desc_ptr += addl_desc_ptr[1] + 2; } + if (desc_ptr) + desc_ptr += len; + + if (addl_desc_ptr) + addl_desc_ptr += addl_desc_ptr[1] + 2; + } } kfree(buf); From cb84e2d2ff3b50c0da5a7604a6d8634294a00a01 Mon Sep 17 00:00:00 2001 From: James Bottomley Date: Fri, 15 Feb 2008 09:28:43 -0600 Subject: [PATCH 02/25] [SCSI] aic94xx: fix REQ_TASK_ABORT and REQ_DEVICE_RESET This driver has been failing under heavy load with aic94xx: escb_tasklet_complete: REQ_TASK_ABORT, reason=0x6 aic94xx: escb_tasklet_complete: Can't find task (tc=4) to abort! The second message is because the driver fails to identify the task it's being asked to abort. On closer inpection, there's a thinko in the for each task loop over pending tasks in both the REQ_TASK_ABORT and REQ_DEVICE_RESET cases where it doesn't look at the task on the pending list but at the one on the ESCB (which is always NULL). Fix by looking at the right task. Also add a print for the case where the pending SCB doesn't have a task attached. Not sure if this will fix all the problems, but it's a definite first step. Cc: Stable Tree Signed-off-by: James Bottomley --- drivers/scsi/aic94xx/aic94xx_scb.c | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/drivers/scsi/aic94xx/aic94xx_scb.c b/drivers/scsi/aic94xx/aic94xx_scb.c index 0febad4dd75f..ab350504ca5a 100644 --- a/drivers/scsi/aic94xx/aic94xx_scb.c +++ b/drivers/scsi/aic94xx/aic94xx_scb.c @@ -458,13 +458,19 @@ static void escb_tasklet_complete(struct asd_ascb *ascb, 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; + struct sas_task *task = a->uldd_task; - if (task && a->tc_index == tc_abort) { + if (a->tc_index != tc_abort) + continue; + + if (task) { failed_dev = task->dev; sas_task_abort(task); - break; + } else { + ASD_DPRINTK("R_T_A for non TASK scb 0x%x\n", + a->scb->header.opcode); } + break; } if (!failed_dev) { @@ -478,7 +484,7 @@ static void escb_tasklet_complete(struct asd_ascb *ascb, * 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; + struct sas_task *task = a->uldd_task; if (task && task->dev == failed_dev && From 2939deaab257924c9afd87575dbd9100ae08160d Mon Sep 17 00:00:00 2001 From: Kyle McMartin Date: Wed, 17 Oct 2007 12:25:00 -0400 Subject: [PATCH 03/25] [SCSI] mpt fusion: kill warnings in mptbase.h on parisc64 Verified all the arches necessary select the CONFIG_64BIT symbol. This also kills the warning (since it was using the 32-bit case) on parisc64 and mips64. Signed-off-by: Kyle McMartin Signed-off-by: James Bottomley --- drivers/message/fusion/mptbase.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/message/fusion/mptbase.h b/drivers/message/fusion/mptbase.h index d83ea96fe135..caadc68c3000 100644 --- a/drivers/message/fusion/mptbase.h +++ b/drivers/message/fusion/mptbase.h @@ -923,7 +923,7 @@ extern struct proc_dir_entry *mpt_proc_root_dir; /*=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=*/ #endif /* } __KERNEL__ */ -#if defined(__alpha__) || defined(__sparc_v9__) || defined(__ia64__) || defined(__x86_64__) || defined(__powerpc__) +#ifdef CONFIG_64BIT #define CAST_U32_TO_PTR(x) ((void *)(u64)x) #define CAST_PTR_TO_U32(x) ((u32)(u64)x) #else From 7d1abbe82434d29dd0d7d69dc0e6acdf25a8c2b5 Mon Sep 17 00:00:00 2001 From: Alan Cox Date: Fri, 8 Feb 2008 15:34:24 +0000 Subject: [PATCH 04/25] [SCSI] megaraid: outb_p extermination From conversations with the maintainers the _p isn't needed so kill it. That removes the last non ISA _p user from the SCSI layer to my knowledge. Signed-off-by: Alan Cox Acked-by: "Yang, Bo" Signed-off-by: James Bottomley --- drivers/scsi/megaraid.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/drivers/scsi/megaraid.c b/drivers/scsi/megaraid.c index 4d59ae8491a4..b135a1ed4b2c 100644 --- a/drivers/scsi/megaraid.c +++ b/drivers/scsi/megaraid.c @@ -151,19 +151,19 @@ mega_setup_mailbox(adapter_t *adapter) */ if( adapter->flag & BOARD_IOMAP ) { - outb_p(adapter->mbox_dma & 0xFF, + outb(adapter->mbox_dma & 0xFF, adapter->host->io_port + MBOX_PORT0); - outb_p((adapter->mbox_dma >> 8) & 0xFF, + outb((adapter->mbox_dma >> 8) & 0xFF, adapter->host->io_port + MBOX_PORT1); - outb_p((adapter->mbox_dma >> 16) & 0xFF, + outb((adapter->mbox_dma >> 16) & 0xFF, adapter->host->io_port + MBOX_PORT2); - outb_p((adapter->mbox_dma >> 24) & 0xFF, + outb((adapter->mbox_dma >> 24) & 0xFF, adapter->host->io_port + MBOX_PORT3); - outb_p(ENABLE_MBOX_BYTE, + outb(ENABLE_MBOX_BYTE, adapter->host->io_port + ENABLE_MBOX_REGION); irq_ack(adapter); From 0e935c9ebacf2f54ab1226192d1a62f7ea1b9303 Mon Sep 17 00:00:00 2001 From: James Bottomley Date: Sat, 16 Feb 2008 15:53:21 -0600 Subject: [PATCH 05/25] [SCSI] fas216: fix up the previous fas216 commit Apparently the fix to [SCSI] fas216: Use scsi_eh API for REQUEST_SENSE invocation didn't show up in the final version sent to linus. Correct this omission. Cc: Russell King Signed-off-by: James Bottomley --- drivers/scsi/arm/fas216.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/scsi/arm/fas216.h b/drivers/scsi/arm/fas216.h index 3e73e264972e..b65f4cf0eec9 100644 --- a/drivers/scsi/arm/fas216.h +++ b/drivers/scsi/arm/fas216.h @@ -313,7 +313,7 @@ typedef struct { /* miscellaneous */ int internal_done; /* flag to indicate request done */ - struct scsi_eh_save *ses; /* holds request sense restore info */ + struct scsi_eh_save ses; /* holds request sense restore info */ unsigned long magic_end; } FAS216_Info; From cbccc207128e8bbdb047f6c5fc261acf207749c1 Mon Sep 17 00:00:00 2001 From: FUJITA Tomonori Date: Sat, 16 Feb 2008 23:57:15 +0900 Subject: [PATCH 06/25] [SCSI] scsi_debug: disable clustering scsi_debug does at several places: for_each_sg(sdb->table.sgl, sg, sdb->table.nents, k) { kaddr = (unsigned char *) kmap_atomic(sg_page(sg), KM_USER0); We cannot do something like that with the clustering enabled (or we can use scsi_kmap_atomic_sg). Signed-off-by: FUJITA Tomonori Acked-by: Douglas Gilbert Signed-off-by: James Bottomley --- drivers/scsi/scsi_debug.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/scsi/scsi_debug.c b/drivers/scsi/scsi_debug.c index 1541c174937a..d1777a9a9625 100644 --- a/drivers/scsi/scsi_debug.c +++ b/drivers/scsi/scsi_debug.c @@ -222,7 +222,7 @@ static struct scsi_host_template sdebug_driver_template = { .cmd_per_lun = 16, .max_sectors = 0xffff, .unchecked_isa_dma = 0, - .use_clustering = ENABLE_CLUSTERING, + .use_clustering = DISABLE_CLUSTERING, .module = THIS_MODULE, }; From 5e2f22d39ec29c33bc5a3a558ac545b952aab8b7 Mon Sep 17 00:00:00 2001 From: James Bottomley Date: Tue, 12 Feb 2008 15:55:48 -0600 Subject: [PATCH 07/25] [SCSI] aic7xx: mitigate HOST_MSG_LOOP invalid SCB ff panic The panic occurs if we get a MSGIN or MSGOUT for an unidentified SCB (meaning we didn't identify the outstanding command it was for). For MSGIN this is wrong because it could be an unsolicited negotiation MSGIN from the target. Still panic on unsolicited MSGOUT because this would represent a mistake in the negotiation phases. However, we should fix this as well. The specs say we should go to bus free for unexpected msgin. Signed-off-by: James Bottomley --- drivers/scsi/aic7xxx/aic7xxx_core.c | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/drivers/scsi/aic7xxx/aic7xxx_core.c b/drivers/scsi/aic7xxx/aic7xxx_core.c index 6d2ae641273c..64e62ce59c15 100644 --- a/drivers/scsi/aic7xxx/aic7xxx_core.c +++ b/drivers/scsi/aic7xxx/aic7xxx_core.c @@ -695,15 +695,16 @@ ahc_handle_seqint(struct ahc_softc *ahc, u_int intstat) scb_index = ahc_inb(ahc, SCB_TAG); scb = ahc_lookup_scb(ahc, scb_index); if (devinfo.role == ROLE_INITIATOR) { - if (scb == NULL) - panic("HOST_MSG_LOOP with " - "invalid SCB %x\n", scb_index); + if (bus_phase == P_MESGOUT) { + if (scb == NULL) + panic("HOST_MSG_LOOP with " + "invalid SCB %x\n", + scb_index); - if (bus_phase == P_MESGOUT) ahc_setup_initiator_msgout(ahc, &devinfo, scb); - else { + } else { ahc->msg_type = MSG_TYPE_INITIATOR_MSGIN; ahc->msgin_index = 0; From 279e7f5425c5e6da6ca61b1d8576356a939789f9 Mon Sep 17 00:00:00 2001 From: FUJITA Tomonori Date: Sat, 16 Feb 2008 15:24:41 +0900 Subject: [PATCH 08/25] [SCSI] qla2xxx: fix compile warning for printk format scsi/qla2xxx/qla_dfs.c: In function 'qla2x00_dfs_fce_show': scsi/qla2xxx/qla_dfs.c:26: warning: format '%llx' expects type 'long long unsigned int', but argument 3 has type 'uint64_t' Signed-off-by: FUJITA Tomonori Acked-by: Andrew Vasquez Signed-off-by: James Bottomley --- drivers/scsi/qla2xxx/qla_dfs.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/scsi/qla2xxx/qla_dfs.c b/drivers/scsi/qla2xxx/qla_dfs.c index 1479c60441c8..2cd899bfe84b 100644 --- a/drivers/scsi/qla2xxx/qla_dfs.c +++ b/drivers/scsi/qla2xxx/qla_dfs.c @@ -23,7 +23,7 @@ qla2x00_dfs_fce_show(struct seq_file *s, void *unused) mutex_lock(&ha->fce_mutex); seq_printf(s, "FCE Trace Buffer\n"); - seq_printf(s, "In Pointer = %llx\n\n", ha->fce_wr); + seq_printf(s, "In Pointer = %llx\n\n", (unsigned long long)ha->fce_wr); seq_printf(s, "Base = %llx\n\n", (unsigned long long) ha->fce_dma); seq_printf(s, "FCE Enable Registers\n"); seq_printf(s, "%08x %08x %08x %08x %08x %08x\n", From ff83efacf2b77a1fe8942db6613825a4b80ee5e2 Mon Sep 17 00:00:00 2001 From: James Bottomley Date: Sun, 17 Feb 2008 11:24:51 -0600 Subject: [PATCH 09/25] [SCSI] gdth: don't call pci_free_consistent under spinlock The spinlock is held over too large a region: pscratch is a permanent address (it's allocated at boot time and never changes). All you need the smp lock for is mediating the scratch in use flag, so fix this by moving the spinlock into the case where we set the pscratch_busy flag to false. Cc: Stable Tree Signed-off-by: James Bottomley --- drivers/scsi/gdth_proc.c | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/drivers/scsi/gdth_proc.c b/drivers/scsi/gdth_proc.c index de5773443c62..ce0228e26aec 100644 --- a/drivers/scsi/gdth_proc.c +++ b/drivers/scsi/gdth_proc.c @@ -694,15 +694,13 @@ static void gdth_ioctl_free(gdth_ha_str *ha, int size, char *buf, ulong64 paddr) { ulong flags; - spin_lock_irqsave(&ha->smp_lock, flags); - if (buf == ha->pscratch) { + spin_lock_irqsave(&ha->smp_lock, flags); ha->scratch_busy = FALSE; + spin_unlock_irqrestore(&ha->smp_lock, flags); } else { pci_free_consistent(ha->pdev, size, buf, paddr); } - - spin_unlock_irqrestore(&ha->smp_lock, flags); } #ifdef GDTH_IOCTL_PROC From 2b28a4721e068ac89bd5435472723a1bc44442fe Mon Sep 17 00:00:00 2001 From: FUJITA Tomonori Date: Tue, 19 Feb 2008 17:02:27 +0900 Subject: [PATCH 10/25] [SCSI] ips: fix data buffer accessors conversion bug This fixes a bug that can't handle a passthru command with more than two sg entries. Big thanks to Tim Pepper for debugging the problem. Signed-off-by: FUJITA Tomonori Acked-by: Mark Salyzyn Cc: Stable Tree Signed-off-by: James Bottomley --- drivers/scsi/ips.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/scsi/ips.c b/drivers/scsi/ips.c index bb152fb9fec7..7ed568f180ae 100644 --- a/drivers/scsi/ips.c +++ b/drivers/scsi/ips.c @@ -1576,7 +1576,7 @@ ips_make_passthru(ips_ha_t *ha, struct scsi_cmnd *SC, ips_scb_t *scb, int intr) METHOD_TRACE("ips_make_passthru", 1); scsi_for_each_sg(SC, sg, scsi_sg_count(SC), i) - length += sg[i].length; + length += sg->length; if (length < sizeof (ips_passthru_t)) { /* wrong size */ From 4d9db01ef8f62b03c46f1258fd45a8c8235687ba Mon Sep 17 00:00:00 2001 From: Adrian Bunk Date: Thu, 14 Feb 2008 23:24:02 +0200 Subject: [PATCH 11/25] [SCSI] lpfc: make lpfc_disable_node() static This patch makes the needlessly global lpfc_disable_node() static. Signed-off-by: Adrian Bunk Acked-by: James Smart Signed-off-by: James Bottomley --- drivers/scsi/lpfc/lpfc_crtn.h | 1 - drivers/scsi/lpfc/lpfc_hbadisc.c | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/drivers/scsi/lpfc/lpfc_crtn.h b/drivers/scsi/lpfc/lpfc_crtn.h index 848d97744b4d..0819f5f39de5 100644 --- a/drivers/scsi/lpfc/lpfc_crtn.h +++ b/drivers/scsi/lpfc/lpfc_crtn.h @@ -55,7 +55,6 @@ void lpfc_mbx_cmpl_ns_reg_login(struct lpfc_hba *, LPFC_MBOXQ_t *); void lpfc_mbx_cmpl_fdmi_reg_login(struct lpfc_hba *, LPFC_MBOXQ_t *); void lpfc_enqueue_node(struct lpfc_vport *, struct lpfc_nodelist *); void lpfc_dequeue_node(struct lpfc_vport *, struct lpfc_nodelist *); -void lpfc_disable_node(struct lpfc_vport *, struct lpfc_nodelist *); struct lpfc_nodelist *lpfc_enable_node(struct lpfc_vport *, struct lpfc_nodelist *, int); void lpfc_nlp_set_state(struct lpfc_vport *, struct lpfc_nodelist *, int); diff --git a/drivers/scsi/lpfc/lpfc_hbadisc.c b/drivers/scsi/lpfc/lpfc_hbadisc.c index bd572d6b60af..976653440fba 100644 --- a/drivers/scsi/lpfc/lpfc_hbadisc.c +++ b/drivers/scsi/lpfc/lpfc_hbadisc.c @@ -1694,7 +1694,7 @@ lpfc_dequeue_node(struct lpfc_vport *vport, struct lpfc_nodelist *ndlp) NLP_STE_UNUSED_NODE); } -void +static void lpfc_disable_node(struct lpfc_vport *vport, struct lpfc_nodelist *ndlp) { if ((ndlp->nlp_flag & NLP_DELAY_TMO) != 0) From 3b0f208a583f130f1e551a6b8673734f51ab7dcd Mon Sep 17 00:00:00 2001 From: Adrian Bunk Date: Wed, 13 Feb 2008 23:30:17 +0200 Subject: [PATCH 12/25] [SCSI] iscsi transport: make 2 functions static This patch makes the following needlessly global functions static: - __iscsi_unblock_session() - iscsi_session_state_name() Signed-off-by: Adrian Bunk Acked-by: Mike Christie Signed-off-by: James Bottomley --- drivers/scsi/scsi_transport_iscsi.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/scsi/scsi_transport_iscsi.c b/drivers/scsi/scsi_transport_iscsi.c index fac7534f3ec4..9981682d5302 100644 --- a/drivers/scsi/scsi_transport_iscsi.c +++ b/drivers/scsi/scsi_transport_iscsi.c @@ -231,7 +231,7 @@ static struct { { ISCSI_SESSION_FREE, "FREE" }, }; -const char *iscsi_session_state_name(int state) +static const char *iscsi_session_state_name(int state) { int i; char *name = NULL; @@ -373,7 +373,7 @@ static void session_recovery_timedout(struct work_struct *work) scsi_target_unblock(&session->dev); } -void __iscsi_unblock_session(struct iscsi_cls_session *session) +static void __iscsi_unblock_session(struct iscsi_cls_session *session) { if (!cancel_delayed_work(&session->recovery_work)) flush_workqueue(iscsi_eh_timer_workq); From ad008d42bcec99911b3270a8349f8ec8405a1c4e Mon Sep 17 00:00:00 2001 From: Adrian Bunk Date: Tue, 19 Feb 2008 20:03:57 -0600 Subject: [PATCH 13/25] [SCSI] mptbase: fix use-after-free's ioc->name is used in the printk's after ioc has been freed. Free after prinks to fix this. This patch fixes two use-after-free's introduced by commit e78d5b8f1e73ab82f3fd041d05824cfee7d83a2c and spotted by the Coverity checker. Signed-off-by: Adrian Bunk Signed-off-by: James Bottomley --- drivers/message/fusion/mptbase.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/message/fusion/mptbase.c b/drivers/message/fusion/mptbase.c index bfda731696f7..0c303c84b37b 100644 --- a/drivers/message/fusion/mptbase.c +++ b/drivers/message/fusion/mptbase.c @@ -1481,15 +1481,15 @@ mpt_attach(struct pci_dev *pdev, const struct pci_device_id *id) ioc->bars = pci_select_bars(pdev, IORESOURCE_MEM); if (pci_enable_device_mem(pdev)) { - kfree(ioc); printk(MYIOC_s_ERR_FMT "pci_enable_device_mem() " "failed\n", ioc->name); + kfree(ioc); return r; } if (pci_request_selected_regions(pdev, ioc->bars, "mpt")) { - kfree(ioc); printk(MYIOC_s_ERR_FMT "pci_request_selected_regions() with " "MEM failed\n", ioc->name); + kfree(ioc); return r; } From 69e562c234440fb7410877b5b24f4b29ef8521d1 Mon Sep 17 00:00:00 2001 From: Daniel Drake Date: Wed, 20 Feb 2008 13:29:05 +0000 Subject: [PATCH 14/25] [SCSI] arcmsr: fix message allocation arcmsr_iop_message_xfer() is called from atomic context under the queuecommand scsi_host_template handler. James Bottomley pointed out that the current GFP_KERNEL|GFP_DMA flags are wrong: firstly we are in atomic context, secondly this memory is not used for DMA. Also removed some unneeded casts. Signed-off-by: Daniel Drake Cc: Nick Cheng Signed-off-by: James Bottomley --- drivers/scsi/arcmsr/arcmsr_hba.c | 26 +++++++++++--------------- 1 file changed, 11 insertions(+), 15 deletions(-) diff --git a/drivers/scsi/arcmsr/arcmsr_hba.c b/drivers/scsi/arcmsr/arcmsr_hba.c index 4f9ff32cfed0..f91f79c8007d 100644 --- a/drivers/scsi/arcmsr/arcmsr_hba.c +++ b/drivers/scsi/arcmsr/arcmsr_hba.c @@ -1387,18 +1387,16 @@ static int arcmsr_iop_message_xfer(struct AdapterControlBlock *acb, \ switch(controlcode) { case ARCMSR_MESSAGE_READ_RQBUFFER: { - unsigned long *ver_addr; + unsigned char *ver_addr; uint8_t *pQbuffer, *ptmpQbuffer; int32_t allxfer_len = 0; - void *tmp; - tmp = kmalloc(1032, GFP_KERNEL|GFP_DMA); - ver_addr = (unsigned long *)tmp; - if (!tmp) { + ver_addr = kmalloc(1032, GFP_ATOMIC); + if (!ver_addr) { retvalue = ARCMSR_MESSAGE_FAIL; goto message_out; } - ptmpQbuffer = (uint8_t *) ver_addr; + ptmpQbuffer = ver_addr; while ((acb->rqbuf_firstindex != acb->rqbuf_lastindex) && (allxfer_len < 1031)) { pQbuffer = &acb->rqbuffer[acb->rqbuf_firstindex]; @@ -1427,26 +1425,24 @@ static int arcmsr_iop_message_xfer(struct AdapterControlBlock *acb, \ } arcmsr_iop_message_read(acb); } - memcpy(pcmdmessagefld->messagedatabuffer, (uint8_t *)ver_addr, allxfer_len); + memcpy(pcmdmessagefld->messagedatabuffer, ver_addr, allxfer_len); pcmdmessagefld->cmdmessage.Length = allxfer_len; pcmdmessagefld->cmdmessage.ReturnCode = ARCMSR_MESSAGE_RETURNCODE_OK; - kfree(tmp); + kfree(ver_addr); } break; case ARCMSR_MESSAGE_WRITE_WQBUFFER: { - unsigned long *ver_addr; + unsigned char *ver_addr; int32_t my_empty_len, user_len, wqbuf_firstindex, wqbuf_lastindex; uint8_t *pQbuffer, *ptmpuserbuffer; - void *tmp; - tmp = kmalloc(1032, GFP_KERNEL|GFP_DMA); - ver_addr = (unsigned long *)tmp; - if (!tmp) { + ver_addr = kmalloc(1032, GFP_ATOMIC); + if (!ver_addr) { retvalue = ARCMSR_MESSAGE_FAIL; goto message_out; } - ptmpuserbuffer = (uint8_t *)ver_addr; + ptmpuserbuffer = ver_addr; user_len = pcmdmessagefld->cmdmessage.Length; memcpy(ptmpuserbuffer, pcmdmessagefld->messagedatabuffer, user_len); wqbuf_lastindex = acb->wqbuf_lastindex; @@ -1492,7 +1488,7 @@ static int arcmsr_iop_message_xfer(struct AdapterControlBlock *acb, \ retvalue = ARCMSR_MESSAGE_FAIL; } } - kfree(tmp); + kfree(ver_addr); } break; From a8e14fec164cc01d8dfb18760ee9bddd91e127c2 Mon Sep 17 00:00:00 2001 From: James Bottomley Date: Tue, 19 Feb 2008 21:48:42 -0600 Subject: [PATCH 15/25] [SCSI] libsas: fix error handling The libsas error handler has two fairly fatal bugs 1. scsi_sas_task_done calls scsi_eh_finish_cmd() too early. This happens if the task completes after it has been aborted but before the error handler starts up. Because scsi_eh_finish_cmd() decrements host_failed and adds the task to the done list, the error handler start check (host_failed == host_busy) never passes and the eh never starts. 2. The multiple task completion paths sas_scsi_clear_queue_... all simply delete the task from the error queue. This causes it to disappear into the ether, since a command must be placed on the done queue to be finished off by the error handler. This behaviour causes the HBA to hang on pending commands. Fix 1. by moving the SAS_TASK_STATE_ABORTED check to an exit clause at the top of the routine and calling ->scsi_done() unconditionally (it is a nop if the timer has fired). This keeps the task in the error handling queue until the eh starts. Fix 2. by making sure every task goes through task complete followed by scsi_eh_finish_cmd(). Tested this by firing resets across a disk running a hammer test (now it actually survives without hanging the system) Signed-off-by: James Bottomley --- drivers/scsi/libsas/sas_scsi_host.c | 65 ++++++++++++++++++----------- 1 file changed, 41 insertions(+), 24 deletions(-) diff --git a/drivers/scsi/libsas/sas_scsi_host.c b/drivers/scsi/libsas/sas_scsi_host.c index f869fba86807..9c96d1bd36e2 100644 --- a/drivers/scsi/libsas/sas_scsi_host.c +++ b/drivers/scsi/libsas/sas_scsi_host.c @@ -51,10 +51,14 @@ static void sas_scsi_task_done(struct sas_task *task) { 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; + if (unlikely(task->task_state_flags & SAS_TASK_STATE_ABORTED)) { + /* Aborted tasks will be completed by the error handler */ + SAS_DPRINTK("task done but aborted\n"); + return; + } + if (unlikely(!sc)) { SAS_DPRINTK("task_done called with non existing SCSI cmnd!\n"); list_del_init(&task->list); @@ -120,11 +124,7 @@ static void sas_scsi_task_done(struct sas_task *task) sc->result = (hs << 16) | stat; list_del_init(&task->list); sas_free_task(task); - /* This is very ugly but this is how SCSI Core works. */ - if (ts_flags & SAS_TASK_STATE_ABORTED) - scsi_eh_finish_cmd(sc, &sas_ha->eh_done_q); - else - sc->scsi_done(sc); + sc->scsi_done(sc); } static enum task_attribute sas_scsi_get_task_attr(struct scsi_cmnd *cmd) @@ -255,13 +255,33 @@ out: return res; } +static void sas_eh_finish_cmd(struct scsi_cmnd *cmd) +{ + struct sas_task *task = TO_SAS_TASK(cmd); + struct sas_ha_struct *sas_ha = SHOST_TO_SAS_HA(cmd->device->host); + + /* remove the aborted task flag to allow the task to be + * completed now. At this point, we only get called following + * an actual abort of the task, so we should be guaranteed not + * to be racing with any completions from the LLD (hence we + * don't need the task state lock to clear the flag) */ + task->task_state_flags &= ~SAS_TASK_STATE_ABORTED; + /* Now call task_done. However, task will be free'd after + * this */ + task->task_done(task); + /* now finish the command and move it on to the error + * handler done list, this also takes it off the + * error handler pending list */ + scsi_eh_finish_cmd(cmd, &sas_ha->eh_done_q); +} + static void sas_scsi_clear_queue_lu(struct list_head *error_q, struct scsi_cmnd *my_cmd) { struct scsi_cmnd *cmd, *n; list_for_each_entry_safe(cmd, n, error_q, eh_entry) { if (cmd == my_cmd) - list_del_init(&cmd->eh_entry); + sas_eh_finish_cmd(cmd); } } @@ -274,7 +294,7 @@ static void sas_scsi_clear_queue_I_T(struct list_head *error_q, struct domain_device *x = cmd_to_domain_dev(cmd); if (x == dev) - list_del_init(&cmd->eh_entry); + sas_eh_finish_cmd(cmd); } } @@ -288,7 +308,7 @@ static void sas_scsi_clear_queue_port(struct list_head *error_q, struct asd_sas_port *x = dev->port; if (x == port) - list_del_init(&cmd->eh_entry); + sas_eh_finish_cmd(cmd); } } @@ -528,14 +548,14 @@ Again: case TASK_IS_DONE: SAS_DPRINTK("%s: task 0x%p is done\n", __FUNCTION__, task); - task->task_done(task); + sas_eh_finish_cmd(cmd); 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); + sas_eh_finish_cmd(cmd); if (need_reset) try_to_reset_cmd_device(shost, cmd); continue; @@ -547,7 +567,7 @@ Again: "recovered\n", SAS_ADDR(task->dev), cmd->device->lun); - task->task_done(task); + sas_eh_finish_cmd(cmd); if (need_reset) try_to_reset_cmd_device(shost, cmd); sas_scsi_clear_queue_lu(work_q, cmd); @@ -562,7 +582,7 @@ Again: if (tmf_resp == TMF_RESP_FUNC_COMPLETE) { SAS_DPRINTK("I_T %016llx recovered\n", SAS_ADDR(task->dev->sas_addr)); - task->task_done(task); + sas_eh_finish_cmd(cmd); if (need_reset) try_to_reset_cmd_device(shost, cmd); sas_scsi_clear_queue_I_T(work_q, task->dev); @@ -577,7 +597,7 @@ Again: if (res == TMF_RESP_FUNC_COMPLETE) { SAS_DPRINTK("clear nexus port:%d " "succeeded\n", port->id); - task->task_done(task); + sas_eh_finish_cmd(cmd); if (need_reset) try_to_reset_cmd_device(shost, cmd); sas_scsi_clear_queue_port(work_q, @@ -591,10 +611,10 @@ Again: if (res == TMF_RESP_FUNC_COMPLETE) { SAS_DPRINTK("clear nexus ha " "succeeded\n"); - task->task_done(task); + sas_eh_finish_cmd(cmd); if (need_reset) try_to_reset_cmd_device(shost, cmd); - goto out; + goto clear_q; } } /* If we are here -- this means that no amount @@ -606,21 +626,18 @@ Again: SAS_ADDR(task->dev->sas_addr), cmd->device->lun); - task->task_done(task); + sas_eh_finish_cmd(cmd); if (need_reset) try_to_reset_cmd_device(shost, cmd); goto clear_q; } } -out: return list_empty(work_q); clear_q: SAS_DPRINTK("--- Exit %s -- clear_q\n", __FUNCTION__); - 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); - } + list_for_each_entry_safe(cmd, n, work_q, eh_entry) + sas_eh_finish_cmd(cmd); + return list_empty(work_q); } From 65fecc77f3c47c5e8758d133b8ec47dcc16ed207 Mon Sep 17 00:00:00 2001 From: David Somayajulu Date: Thu, 21 Feb 2008 03:43:00 -0800 Subject: [PATCH 16/25] [SCSI] qla4xxx: fix up residual handling the check in the residual case has an incorrect test of scsi_status (the logic is reversed, it should be scsi_status != 0 instead of !scsi_status. Since we checked a few lines above that scsi_status was non-zero, just eliminate this test Signed-off-by: David C Somayajulu Signed-off-by: James Bottomley --- drivers/scsi/qla4xxx/ql4_isr.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/drivers/scsi/qla4xxx/ql4_isr.c b/drivers/scsi/qla4xxx/ql4_isr.c index 0f029d0d7315..fc84db4069f4 100644 --- a/drivers/scsi/qla4xxx/ql4_isr.c +++ b/drivers/scsi/qla4xxx/ql4_isr.c @@ -100,8 +100,7 @@ static void qla4xxx_status_entry(struct scsi_qla_host *ha, if (sts_entry->iscsiFlags &ISCSI_FLAG_RESIDUAL_UNDER) { scsi_set_resid(cmd, residual); - if (!scsi_status && ((scsi_bufflen(cmd) - residual) < - cmd->underflow)) { + if ((scsi_bufflen(cmd) - residual) < cmd->underflow) { cmd->result = DID_ERROR << 16; From eafe1df9e311034cce204e43c0e45c91723b802f Mon Sep 17 00:00:00 2001 From: Matthew Wilcox Date: Thu, 21 Feb 2008 05:44:33 -0700 Subject: [PATCH 17/25] [SCSI] lpfc: Balance locking Commit 3163f725a5d071eea1830bbbfab78cfe3fc9baaf introduced locking in lpfc_sli_hbqbuf_fill_hbqs, but missed unlocking on one exit. Reported-by: Harvey Harrison Signed-off-by: Matthew Wilcox Acked-by: James Smart Signed-off-by: James Bottomley --- drivers/scsi/lpfc/lpfc_sli.c | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/drivers/scsi/lpfc/lpfc_sli.c b/drivers/scsi/lpfc/lpfc_sli.c index f53206411cd8..fc0d9501aba6 100644 --- a/drivers/scsi/lpfc/lpfc_sli.c +++ b/drivers/scsi/lpfc/lpfc_sli.c @@ -648,28 +648,24 @@ lpfc_sli_hbqbuf_fill_hbqs(struct lpfc_hba *phba, uint32_t hbqno, uint32_t count) unsigned long flags; struct hbq_dmabuf *hbq_buffer; - if (!phba->hbqs[hbqno].hbq_alloc_buffer) { + if (!phba->hbqs[hbqno].hbq_alloc_buffer) return 0; - } start = phba->hbqs[hbqno].buffer_count; end = count + start; - if (end > lpfc_hbq_defs[hbqno]->entry_count) { + if (end > lpfc_hbq_defs[hbqno]->entry_count) end = lpfc_hbq_defs[hbqno]->entry_count; - } /* Check whether HBQ is still in use */ spin_lock_irqsave(&phba->hbalock, flags); - if (!phba->hbq_in_use) { - spin_unlock_irqrestore(&phba->hbalock, flags); - return 0; - } + if (!phba->hbq_in_use) + goto out; /* Populate HBQ entries */ for (i = start; i < end; i++) { hbq_buffer = (phba->hbqs[hbqno].hbq_alloc_buffer)(phba); if (!hbq_buffer) - return 1; + goto err; hbq_buffer->tag = (i | (hbqno << 16)); if (lpfc_sli_hbq_to_firmware(phba, hbqno, hbq_buffer)) phba->hbqs[hbqno].buffer_count++; @@ -677,8 +673,12 @@ lpfc_sli_hbqbuf_fill_hbqs(struct lpfc_hba *phba, uint32_t hbqno, uint32_t count) (phba->hbqs[hbqno].hbq_free_buffer)(phba, hbq_buffer); } + out: spin_unlock_irqrestore(&phba->hbalock, flags); return 0; + err: + spin_unlock_irqrestore(&phba->hbalock, flags); + return 1; } int From 26106e3ca379e30790c41d8835e79395437152ec Mon Sep 17 00:00:00 2001 From: FUJITA Tomonori Date: Fri, 22 Feb 2008 23:11:03 +0900 Subject: [PATCH 18/25] [SCSI] stex: stex_direct_copy shouldn't call dma_map_sg stex_direct_copy copies an in-kernel buffer to a sg list in order to spoof some SCSI commands. stex_direct_copy calls dma_map_sg and then stex_internal_copy with the value that dma_map_sg returned. It calls scsi_kmap_atomic_sg to copy data. scsi_kmap_atomic_sg doesn't see sg->dma_length so if dma_map_sg merges sg entries, stex_internal_copy gets the smaller number of sg entries than the acutual number, which means it wrongly think that the data length in the sg list is shorter than the actual length. stex_direct_copy shouldn't call dma_map_sg and it doesn't need since this code path doesn't involve dma transfers. This patch removes stex_direct_copy and simply calls stex_internal_copy with the actual number of sg entries. Signed-off-by: FUJITA Tomonori Acked-by: Ed Lin Signed-off-by: James Bottomley --- drivers/scsi/stex.c | 34 ++++++++++++---------------------- 1 file changed, 12 insertions(+), 22 deletions(-) diff --git a/drivers/scsi/stex.c b/drivers/scsi/stex.c index 72f6d8015358..4b6861cf0417 100644 --- a/drivers/scsi/stex.c +++ b/drivers/scsi/stex.c @@ -461,23 +461,6 @@ static void stex_internal_copy(struct scsi_cmnd *cmd, } } -static int stex_direct_copy(struct scsi_cmnd *cmd, - const void *src, size_t count) -{ - size_t cp_len = count; - int n_elem = 0; - - n_elem = scsi_dma_map(cmd); - if (n_elem < 0) - return 0; - - stex_internal_copy(cmd, src, &cp_len, n_elem, ST_TO_CMD); - - scsi_dma_unmap(cmd); - - return cp_len == count; -} - static void stex_controller_info(struct st_hba *hba, struct st_ccb *ccb) { struct st_frame *p; @@ -569,8 +552,10 @@ stex_queuecommand(struct scsi_cmnd *cmd, void (* done)(struct scsi_cmnd *)) unsigned char page; page = cmd->cmnd[2] & 0x3f; if (page == 0x8 || page == 0x3f) { - stex_direct_copy(cmd, ms10_caching_page, - sizeof(ms10_caching_page)); + size_t cp_len = sizeof(ms10_caching_page); + stex_internal_copy(cmd, ms10_caching_page, + &cp_len, scsi_sg_count(cmd), + ST_TO_CMD); cmd->result = DID_OK << 16 | COMMAND_COMPLETE << 8; done(cmd); } else @@ -599,8 +584,10 @@ stex_queuecommand(struct scsi_cmnd *cmd, void (* done)(struct scsi_cmnd *)) if (id != host->max_id - 1) break; if (lun == 0 && (cmd->cmnd[1] & INQUIRY_EVPD) == 0) { - stex_direct_copy(cmd, console_inq_page, - sizeof(console_inq_page)); + size_t cp_len = sizeof(console_inq_page); + stex_internal_copy(cmd, console_inq_page, + &cp_len, scsi_sg_count(cmd), + ST_TO_CMD); cmd->result = DID_OK << 16 | COMMAND_COMPLETE << 8; done(cmd); } else @@ -609,6 +596,7 @@ stex_queuecommand(struct scsi_cmnd *cmd, void (* done)(struct scsi_cmnd *)) case PASSTHRU_CMD: if (cmd->cmnd[1] == PASSTHRU_GET_DRVVER) { struct st_drvver ver; + size_t cp_len = sizeof(ver); ver.major = ST_VER_MAJOR; ver.minor = ST_VER_MINOR; ver.oem = ST_OEM; @@ -616,7 +604,9 @@ stex_queuecommand(struct scsi_cmnd *cmd, void (* done)(struct scsi_cmnd *)) ver.signature[0] = PASSTHRU_SIGNATURE; ver.console_id = host->max_id - 1; ver.host_no = hba->host->host_no; - cmd->result = stex_direct_copy(cmd, &ver, sizeof(ver)) ? + stex_internal_copy(cmd, &ver, &cp_len, + scsi_sg_count(cmd), ST_TO_CMD); + cmd->result = sizeof(ver) == cp_len ? DID_OK << 16 | COMMAND_COMPLETE << 8 : DID_ERROR << 16 | COMMAND_COMPLETE << 8; done(cmd); From c9872fe1add5709fffd42249e6ca1080999aa06a Mon Sep 17 00:00:00 2001 From: FUJITA Tomonori Date: Fri, 22 Feb 2008 23:11:04 +0900 Subject: [PATCH 19/25] [SCSI] stex: stex_internal_copy should be called with sg_count in struct st_ccb stex_internal_copy copies an in-kernel buffer to a sg list by using scsi_kmap_atomic_sg. Some functions calls stex_internal_copy with sg_count in struct st_ccb, which is the value that dma_map_sg returned. However it might be shorter than the actual number of sg entries (if the IOMMU merged the sg entries). scsi_kmap_atomic_sg doesn't see sg->dma_length so stex_internal_copy should be called with the actual number of sg entries (i.e. scsi_sg_count), because if the sg entries were merged, stex_direct_copy wrongly think that the data length in the sg list is shorter than the actual length. Signed-off-by: FUJITA Tomonori Acked-by: Ed Lin Signed-off-by: James Bottomley --- drivers/scsi/stex.c | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/drivers/scsi/stex.c b/drivers/scsi/stex.c index 4b6861cf0417..654430edf74d 100644 --- a/drivers/scsi/stex.c +++ b/drivers/scsi/stex.c @@ -467,7 +467,8 @@ static void stex_controller_info(struct st_hba *hba, struct st_ccb *ccb) size_t count = sizeof(struct st_frame); p = hba->copy_buffer; - stex_internal_copy(ccb->cmd, p, &count, ccb->sg_count, ST_FROM_CMD); + stex_internal_copy(ccb->cmd, p, &count, scsi_sg_count(ccb->cmd), + ST_FROM_CMD); memset(p->base, 0, sizeof(u32)*6); *(unsigned long *)(p->base) = pci_resource_start(hba->pdev, 0); p->rom_addr = 0; @@ -485,7 +486,8 @@ static void stex_controller_info(struct st_hba *hba, struct st_ccb *ccb) p->subid = hba->pdev->subsystem_vendor << 16 | hba->pdev->subsystem_device; - stex_internal_copy(ccb->cmd, p, &count, ccb->sg_count, ST_TO_CMD); + stex_internal_copy(ccb->cmd, p, &count, scsi_sg_count(ccb->cmd), + ST_TO_CMD); } static void @@ -699,7 +701,7 @@ static void stex_copy_data(struct st_ccb *ccb, if (ccb->cmd == NULL) return; stex_internal_copy(ccb->cmd, - resp->variable, &count, ccb->sg_count, ST_TO_CMD); + resp->variable, &count, scsi_sg_count(ccb->cmd), ST_TO_CMD); } static void stex_ys_commands(struct st_hba *hba, @@ -724,7 +726,7 @@ static void stex_ys_commands(struct st_hba *hba, count = STEX_EXTRA_SIZE; stex_internal_copy(ccb->cmd, hba->copy_buffer, - &count, ccb->sg_count, ST_FROM_CMD); + &count, scsi_sg_count(ccb->cmd), ST_FROM_CMD); inq_data = (ST_INQ *)hba->copy_buffer; if (inq_data->DeviceTypeQualifier != 0) ccb->srb_status = SRB_STATUS_SELECTION_TIMEOUT; From deee13dfd6dd6c18518ea725f11111ebd9bf4fa8 Mon Sep 17 00:00:00 2001 From: Kai Makisara Date: Fri, 22 Feb 2008 20:11:21 +0200 Subject: [PATCH 20/25] [SCSI] st: compile fix when DEBUG set to one Remove the now useless counting of adjacent pages from the debugging code in to make it compile when DEBUG is set non-zero. Signed-off-by: Kai Makisara Signed-off-by: James Bottomley --- drivers/scsi/st.c | 11 ++++------- drivers/scsi/st.h | 1 - 2 files changed, 4 insertions(+), 8 deletions(-) diff --git a/drivers/scsi/st.c b/drivers/scsi/st.c index 71952703125a..0a52d9d2da2c 100644 --- a/drivers/scsi/st.c +++ b/drivers/scsi/st.c @@ -17,7 +17,7 @@ Last modified: 18-JAN-1998 Richard Gooch Devfs support */ -static const char *verstr = "20080117"; +static const char *verstr = "20080221"; #include @@ -1172,7 +1172,7 @@ static int st_open(struct inode *inode, struct file *filp) STp->try_dio_now = STp->try_dio; STp->recover_count = 0; DEB( STp->nbr_waits = STp->nbr_finished = 0; - STp->nbr_requests = STp->nbr_dio = STp->nbr_pages = STp->nbr_combinable = 0; ) + STp->nbr_requests = STp->nbr_dio = STp->nbr_pages = 0; ) retval = check_tape(STp, filp); if (retval < 0) @@ -1226,8 +1226,8 @@ static int st_flush(struct file *filp, fl_owner_t id) } DEBC( if (STp->nbr_requests) - printk(KERN_DEBUG "%s: Number of r/w requests %d, dio used in %d, pages %d (%d).\n", - name, STp->nbr_requests, STp->nbr_dio, STp->nbr_pages, STp->nbr_combinable)); + printk(KERN_DEBUG "%s: Number of r/w requests %d, dio used in %d, pages %d.\n", + name, STp->nbr_requests, STp->nbr_dio, STp->nbr_pages)); if (STps->rw == ST_WRITING && !STp->pos_unknown) { struct st_cmdstatus *cmdstatp = &STp->buffer->cmdstat; @@ -1422,9 +1422,6 @@ static int setup_buffering(struct scsi_tape *STp, const char __user *buf, if (STbp->do_dio) { STp->nbr_dio++; STp->nbr_pages += STbp->do_dio; - for (i=1; i < STbp->do_dio; i++) - if (page_to_pfn(STbp->sg[i].page) == page_to_pfn(STbp->sg[i-1].page) + 1) - STp->nbr_combinable++; } ) } else diff --git a/drivers/scsi/st.h b/drivers/scsi/st.h index 6c8075712974..5931726fcf93 100644 --- a/drivers/scsi/st.h +++ b/drivers/scsi/st.h @@ -164,7 +164,6 @@ struct scsi_tape { int nbr_requests; int nbr_dio; int nbr_pages; - int nbr_combinable; unsigned char last_cmnd[6]; unsigned char last_sense[16]; #endif From 91b550604480eadcdadc7db8a7d19f496ccad6bd Mon Sep 17 00:00:00 2001 From: James Bottomley Date: Fri, 22 Feb 2008 17:01:59 -0600 Subject: [PATCH 21/25] [SCSI] aic94xx: fix sequencer hang on error recovery The clear nexus I_T and clear nexus I_T_L functions in the aic94xx specify the SUSPEND_TX flag which causes the sequencer to be suspended until it receives a RESUME_TX. Unfortunately, nothing ever sends the resume, so the sequencer on the link is stopped forever, leading to eventual timeouts and I/O errors. Since clear nexus commands are only executed as part of error recovery, it's perfectly fine to keep the sequencer running on the link ... as soon as the recovery function is completed, we'll send it the commands to retry. Signed-off-by: James Bottomley --- drivers/scsi/aic94xx/aic94xx_tmf.c | 4 ---- 1 file changed, 4 deletions(-) diff --git a/drivers/scsi/aic94xx/aic94xx_tmf.c b/drivers/scsi/aic94xx/aic94xx_tmf.c index b52124f3d3ac..144f5ad20453 100644 --- a/drivers/scsi/aic94xx/aic94xx_tmf.c +++ b/drivers/scsi/aic94xx/aic94xx_tmf.c @@ -151,8 +151,6 @@ static int asd_clear_nexus_I_T(struct domain_device *dev) CLEAR_NEXUS_PRE; scb->clear_nexus.nexus = NEXUS_I_T; scb->clear_nexus.flags = SEND_Q | EXEC_Q | NOTINQ; - if (dev->tproto) - scb->clear_nexus.flags |= SUSPEND_TX; scb->clear_nexus.conn_handle = cpu_to_le16((u16)(unsigned long) dev->lldd_dev); CLEAR_NEXUS_POST; @@ -169,8 +167,6 @@ static int asd_clear_nexus_I_T_L(struct domain_device *dev, u8 *lun) CLEAR_NEXUS_PRE; scb->clear_nexus.nexus = NEXUS_I_T_L; scb->clear_nexus.flags = SEND_Q | EXEC_Q | NOTINQ; - if (dev->tproto) - scb->clear_nexus.flags |= SUSPEND_TX; memcpy(scb->clear_nexus.ssp_task.lun, lun, 8); scb->clear_nexus.conn_handle = cpu_to_le16((u16)(unsigned long) dev->lldd_dev); From 63e4563b9cf77875286312758f61a20f912afbbb Mon Sep 17 00:00:00 2001 From: James Bottomley Date: Fri, 22 Feb 2008 17:07:52 -0600 Subject: [PATCH 22/25] [SCSI] libsas: correctly flush the LU queue on error recovery The current sas_scsi_clear_queue_lu() is wrongly checking for commands which match the pointer to the one passed in. It should be checking for commands which are on the same logical unit as the one passed in. Fix this by checking target pointer and LUN for equality. Signed-off-by: James Bottomley --- drivers/scsi/libsas/sas_scsi_host.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/scsi/libsas/sas_scsi_host.c b/drivers/scsi/libsas/sas_scsi_host.c index 9c96d1bd36e2..704ea06a6e50 100644 --- a/drivers/scsi/libsas/sas_scsi_host.c +++ b/drivers/scsi/libsas/sas_scsi_host.c @@ -280,7 +280,8 @@ static void sas_scsi_clear_queue_lu(struct list_head *error_q, struct scsi_cmnd struct scsi_cmnd *cmd, *n; list_for_each_entry_safe(cmd, n, error_q, eh_entry) { - if (cmd == my_cmd) + if (cmd->device->sdev_target == my_cmd->device->sdev_target && + cmd->device->lun == my_cmd->device->lun) sas_eh_finish_cmd(cmd); } } From b5762948263dd5e9725a380e7a9626f99e40ae9d Mon Sep 17 00:00:00 2001 From: Jeff Garzik Date: Thu, 25 Oct 2007 20:58:22 -0400 Subject: [PATCH 23/25] [SCSI] mvsas: Add Marvell 6440 SAS/SATA driver Signed-off-by: Jeff Garzik Signed-off-by: James Bottomley --- drivers/scsi/Kconfig | 10 + drivers/scsi/Makefile | 1 + drivers/scsi/mvsas.c | 1825 +++++++++++++++++++++++++++++++++++++++++ 3 files changed, 1836 insertions(+) create mode 100644 drivers/scsi/mvsas.c diff --git a/drivers/scsi/Kconfig b/drivers/scsi/Kconfig index a7a0813b24cb..c46666a24809 100644 --- a/drivers/scsi/Kconfig +++ b/drivers/scsi/Kconfig @@ -992,6 +992,16 @@ config SCSI_IZIP_SLOW_CTR Generally, saying N is fine. +config SCSI_MVSAS + tristate "Marvell 88SE6440 SAS/SATA support" + depends on PCI && SCSI + select SCSI_SAS_LIBSAS + help + This driver supports Marvell SAS/SATA PCI devices. + + To compiler this driver as a module, choose M here: the module + will be called mvsas. + config SCSI_NCR53C406A tristate "NCR53c406a SCSI support" depends on ISA && SCSI diff --git a/drivers/scsi/Makefile b/drivers/scsi/Makefile index 925c26b4fff9..23e6ecbd4778 100644 --- a/drivers/scsi/Makefile +++ b/drivers/scsi/Makefile @@ -119,6 +119,7 @@ obj-$(CONFIG_SCSI_IBMVSCSI) += ibmvscsi/ obj-$(CONFIG_SCSI_IBMVSCSIS) += ibmvscsi/ obj-$(CONFIG_SCSI_HPTIOP) += hptiop.o obj-$(CONFIG_SCSI_STEX) += stex.o +obj-$(CONFIG_SCSI_MVSAS) += mvsas.o obj-$(CONFIG_PS3_ROM) += ps3rom.o obj-$(CONFIG_ARM) += arm/ diff --git a/drivers/scsi/mvsas.c b/drivers/scsi/mvsas.c new file mode 100644 index 000000000000..03638b9bb283 --- /dev/null +++ b/drivers/scsi/mvsas.c @@ -0,0 +1,1825 @@ +/* + mvsas.c - Marvell 88SE6440 SAS/SATA support + + Copyright 2007 Red Hat, Inc. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2, + or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty + of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public + License along with this program; see the file COPYING. If not, + write to the Free Software Foundation, 675 Mass Ave, Cambridge, + MA 02139, USA. + + --------------------------------------------------------------- + + Random notes: + * hardware supports controlling the endian-ness of data + structures. this permits elimination of all the le32_to_cpu() + and cpu_to_le32() conversions. + + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define DRV_NAME "mvsas" +#define DRV_VERSION "0.1" + +#define mr32(reg) readl(regs + MVS_##reg) +#define mw32(reg,val) writel((val), regs + MVS_##reg) +#define mw32_f(reg,val) do { \ + writel((val), regs + MVS_##reg); \ + readl(regs + MVS_##reg); \ + } while (0) + +/* driver compile-time configuration */ +enum driver_configuration { + MVS_TX_RING_SZ = 1024, /* TX ring size (12-bit) */ + MVS_RX_RING_SZ = 1024, /* RX ring size (12-bit) */ + /* software requires power-of-2 + ring size */ + + MVS_SLOTS = 512, /* command slots */ + MVS_SLOT_BUF_SZ = 8192, /* cmd tbl + IU + status + PRD */ + MVS_SSP_CMD_SZ = 64, /* SSP command table buffer size */ + MVS_ATA_CMD_SZ = 128, /* SATA command table buffer size */ + MVS_OAF_SZ = 64, /* Open address frame buffer size */ + + MVS_RX_FIS_COUNT = 17, /* Optional rx'd FISs (max 17) */ +}; + +/* unchangeable hardware details */ +enum hardware_details { + MVS_MAX_PHYS = 8, /* max. possible phys */ + MVS_MAX_PORTS = 8, /* max. possible ports */ + MVS_RX_FISL_SZ = 0x400 + (MVS_RX_FIS_COUNT * 0x100), +}; + +/* peripheral registers (BAR2) */ +enum peripheral_registers { + SPI_CTL = 0x10, /* EEPROM control */ + SPI_CMD = 0x14, /* EEPROM command */ + SPI_DATA = 0x18, /* EEPROM data */ +}; + +enum peripheral_register_bits { + TWSI_RDY = (1U << 7), /* EEPROM interface ready */ + TWSI_RD = (1U << 4), /* EEPROM read access */ + + SPI_ADDR_MASK = 0x3ffff, /* bits 17:0 */ +}; + +/* enhanced mode registers (BAR4) */ +enum hw_registers { + MVS_GBL_CTL = 0x04, /* global control */ + MVS_GBL_INT_STAT = 0x08, /* global irq status */ + MVS_GBL_PI = 0x0C, /* ports implemented bitmask */ + MVS_GBL_PORT_TYPE = 0x00, /* port type */ + + MVS_CTL = 0x100, /* SAS/SATA port configuration */ + MVS_PCS = 0x104, /* SAS/SATA port control/status */ + MVS_CMD_LIST_LO = 0x108, /* cmd list addr */ + MVS_CMD_LIST_HI = 0x10C, + MVS_RX_FIS_LO = 0x110, /* RX FIS list addr */ + MVS_RX_FIS_HI = 0x114, + + MVS_TX_CFG = 0x120, /* TX configuration */ + MVS_TX_LO = 0x124, /* TX (delivery) ring addr */ + MVS_TX_HI = 0x128, + + MVS_RX_PROD_IDX = 0x12C, /* RX producer pointer */ + MVS_RX_CONS_IDX = 0x130, /* RX consumer pointer (RO) */ + MVS_RX_CFG = 0x134, /* RX configuration */ + MVS_RX_LO = 0x138, /* RX (completion) ring addr */ + MVS_RX_HI = 0x13C, + + MVS_INT_COAL = 0x148, /* Int coalescing config */ + MVS_INT_COAL_TMOUT = 0x14C, /* Int coalescing timeout */ + MVS_INT_STAT = 0x150, /* Central int status */ + MVS_INT_MASK = 0x154, /* Central int enable */ + MVS_INT_STAT_SRS = 0x158, /* SATA register set status */ + + /* ports 1-3 follow after this */ + MVS_P0_INT_STAT = 0x160, /* port0 interrupt status */ + MVS_P0_INT_MASK = 0x164, /* port0 interrupt mask */ + + /* ports 1-3 follow after this */ + MVS_P0_SER_CTLSTAT = 0x180, /* port0 serial control/status */ + + MVS_CMD_ADDR = 0x1B8, /* Command register port (addr) */ + MVS_CMD_DATA = 0x1BC, /* Command register port (data) */ + + /* ports 1-3 follow after this */ + MVS_P0_CFG_ADDR = 0x1C0, /* port0 phy register address */ + MVS_P0_CFG_DATA = 0x1C4, /* port0 phy register data */ +}; + +enum hw_register_bits { + /* MVS_GBL_CTL */ + INT_EN = (1U << 1), /* Global int enable */ + HBA_RST = (1U << 0), /* HBA reset */ + + /* MVS_GBL_INT_STAT */ + INT_XOR = (1U << 4), /* XOR engine event */ + INT_SAS_SATA = (1U << 0), /* SAS/SATA event */ + + /* MVS_GBL_PORT_TYPE */ /* shl for ports 1-3 */ + SATA_TARGET = (1U << 16), /* port0 SATA target enable */ + AUTO_DET = (1U << 8), /* port0 SAS/SATA autodetect */ + SAS_MODE = (1U << 0), /* port0 SAS(1), SATA(0) mode */ + /* SAS_MODE value may be + * dictated (in hw) by values + * of SATA_TARGET & AUTO_DET + */ + + /* MVS_TX_CFG */ + TX_EN = (1U << 16), /* Enable TX */ + TX_RING_SZ_MASK = 0xfff, /* TX ring size, bits 11:0 */ + + /* MVS_RX_CFG */ + RX_EN = (1U << 16), /* Enable RX */ + RX_RING_SZ_MASK = 0xfff, /* RX ring size, bits 11:0 */ + + /* MVS_INT_COAL */ + COAL_EN = (1U << 16), /* Enable int coalescing */ + + /* MVS_INT_STAT, MVS_INT_MASK */ + CINT_I2C = (1U << 31), /* I2C event */ + CINT_SW0 = (1U << 30), /* software event 0 */ + CINT_SW1 = (1U << 29), /* software event 1 */ + CINT_PRD_BC = (1U << 28), /* PRD BC err for read cmd */ + CINT_DMA_PCIE = (1U << 27), /* DMA to PCIE timeout */ + CINT_MEM = (1U << 26), /* int mem parity err */ + CINT_I2C_SLAVE = (1U << 25), /* slave I2C event */ + CINT_SRS = (1U << 3), /* SRS event */ + CINT_CI_STOP = (1U << 10), /* cmd issue stopped */ + CINT_DONE = (1U << 0), /* cmd completion */ + + /* shl for ports 1-3 */ + CINT_PORT_STOPPED = (1U << 16), /* port0 stopped */ + CINT_PORT = (1U << 8), /* port0 event */ + + /* TX (delivery) ring bits */ + TXQ_CMD_SHIFT = 29, + TXQ_CMD_SSP = 1, /* SSP protocol */ + TXQ_CMD_SMP = 2, /* SMP protocol */ + TXQ_CMD_STP = 3, /* STP/SATA protocol */ + TXQ_CMD_SSP_FREE_LIST = 4, /* add to SSP targ free list */ + TXQ_CMD_SLOT_RESET = 7, /* reset command slot */ + TXQ_MODE_I = (1U << 28), /* mode: 0=target,1=initiator */ + TXQ_PRIO_HI = (1U << 27), /* priority: 0=normal, 1=high */ + TXQ_SRS_SHIFT = 20, /* SATA register set */ + TXQ_SRS_MASK = 0x7f, + TXQ_PHY_SHIFT = 12, /* PHY bitmap */ + TXQ_PHY_MASK = 0xff, + TXQ_SLOT_MASK = 0xfff, /* slot number */ + + /* RX (completion) ring bits */ + RXQ_GOOD = (1U << 23), /* Response good */ + RXQ_SLOT_RESET = (1U << 21), /* Slot reset complete */ + RXQ_CMD_RX = (1U << 20), /* target cmd received */ + RXQ_ATTN = (1U << 19), /* attention */ + RXQ_RSP = (1U << 18), /* response frame xfer'd */ + RXQ_ERR = (1U << 17), /* err info rec xfer'd */ + RXQ_DONE = (1U << 16), /* cmd complete */ + RXQ_SLOT_MASK = 0xfff, /* slot number */ + + /* mvs_cmd_hdr bits */ + MCH_PRD_LEN_SHIFT = 16, /* 16-bit PRD table len */ + MCH_SSP_FR_TYPE_SHIFT = 13, /* SSP frame type */ + + /* SSP initiator only */ + MCH_SSP_FR_CMD = 0x0, /* COMMAND frame */ + + /* SSP initiator or target */ + MCH_SSP_FR_TASK = 0x1, /* TASK frame */ + + /* SSP target only */ + MCH_SSP_FR_XFER_RDY = 0x4, /* XFER_RDY frame */ + MCH_SSP_FR_RESP = 0x5, /* RESPONSE frame */ + MCH_SSP_FR_READ = 0x6, /* Read DATA frame(s) */ + MCH_SSP_FR_READ_RESP = 0x7, /* ditto, plus RESPONSE */ + + MCH_PASSTHRU = (1U << 12), /* pass-through (SSP) */ + MCH_FBURST = (1U << 11), /* first burst (SSP) */ + MCH_CHK_LEN = (1U << 10), /* chk xfer len (SSP) */ + MCH_RETRY = (1U << 9), /* tport layer retry (SSP) */ + MCH_PROTECTION = (1U << 8), /* protection info rec (SSP) */ + MCH_RESET = (1U << 7), /* Reset (STP/SATA) */ + MCH_FPDMA = (1U << 6), /* First party DMA (STP/SATA) */ + MCH_ATAPI = (1U << 5), /* ATAPI (STP/SATA) */ + MCH_BIST = (1U << 4), /* BIST activate (STP/SATA) */ + MCH_PMP_MASK = 0xf, /* PMP from cmd FIS (STP/SATA)*/ + + CCTL_RST = (1U << 5), /* port logic reset */ + + /* 0(LSB first), 1(MSB first) */ + CCTL_ENDIAN_DATA = (1U << 3), /* PRD data */ + CCTL_ENDIAN_RSP = (1U << 2), /* response frame */ + CCTL_ENDIAN_OPEN = (1U << 1), /* open address frame */ + CCTL_ENDIAN_CMD = (1U << 0), /* command table */ + + /* MVS_Px_SER_CTLSTAT (per-phy control) */ + PHY_SSP_RST = (1U << 3), /* reset SSP link layer */ + PHY_BCAST_CHG = (1U << 2), /* broadcast(change) notif */ + PHY_RST_HARD = (1U << 1), /* hard reset + phy reset */ + PHY_RST = (1U << 0), /* phy reset */ + + /* MVS_Px_INT_STAT, MVS_Px_INT_MASK (per-phy events) */ + PHYEV_UNASSOC_FIS = (1U << 19), /* unassociated FIS rx'd */ + PHYEV_AN = (1U << 18), /* SATA async notification */ + PHYEV_BIST_ACT = (1U << 17), /* BIST activate FIS */ + PHYEV_SIG_FIS = (1U << 16), /* signature FIS */ + PHYEV_POOF = (1U << 12), /* phy ready from 1 -> 0 */ + PHYEV_IU_BIG = (1U << 11), /* IU too long err */ + PHYEV_IU_SMALL = (1U << 10), /* IU too short err */ + PHYEV_UNK_TAG = (1U << 9), /* unknown tag */ + PHYEV_BROAD_CH = (1U << 8), /* broadcast(CHANGE) */ + PHYEV_COMWAKE = (1U << 7), /* COMWAKE rx'd */ + PHYEV_PORT_SEL = (1U << 6), /* port selector present */ + PHYEV_HARD_RST = (1U << 5), /* hard reset rx'd */ + PHYEV_ID_TMOUT = (1U << 4), /* identify timeout */ + PHYEV_ID_FAIL = (1U << 3), /* identify failed */ + PHYEV_ID_DONE = (1U << 2), /* identify done */ + PHYEV_HARD_RST_DONE = (1U << 1), /* hard reset done */ + PHYEV_RDY_CH = (1U << 0), /* phy ready changed state */ + + /* MVS_PCS */ + PCS_SATA_RETRY = (1U << 8), /* retry ctl FIS on R_ERR */ + PCS_RSP_RX_EN = (1U << 7), /* raw response rx */ + PCS_SELF_CLEAR = (1U << 5), /* self-clearing int mode */ + PCS_FIS_RX_EN = (1U << 4), /* FIS rx enable */ + PCS_CMD_STOP_ERR = (1U << 3), /* cmd stop-on-err enable */ + PCS_CMD_RST = (1U << 2), /* reset cmd issue */ + PCS_CMD_EN = (1U << 0), /* enable cmd issue */ +}; + +enum mvs_info_flags { + MVF_MSI = (1U << 0), /* MSI is enabled */ + MVF_PHY_PWR_FIX = (1U << 1), /* bug workaround */ +}; + +enum sas_cmd_port_registers { + CMD_CMRST_OOB_DET = 0x100, /* COMRESET OOB detect register */ + CMD_CMWK_OOB_DET = 0x104, /* COMWAKE OOB detect register */ + CMD_CMSAS_OOB_DET = 0x108, /* COMSAS OOB detect register */ + CMD_BRST_OOB_DET = 0x10c, /* burst OOB detect register */ + CMD_OOB_SPACE = 0x110, /* OOB space control register */ + CMD_OOB_BURST = 0x114, /* OOB burst control register */ + CMD_PHY_TIMER = 0x118, /* PHY timer control register */ + CMD_PHY_CONFIG0 = 0x11c, /* PHY config register 0 */ + CMD_PHY_CONFIG1 = 0x120, /* PHY config register 1 */ + CMD_SAS_CTL0 = 0x124, /* SAS control register 0 */ + CMD_SAS_CTL1 = 0x128, /* SAS control register 1 */ + CMD_SAS_CTL2 = 0x12c, /* SAS control register 2 */ + CMD_SAS_CTL3 = 0x130, /* SAS control register 3 */ + CMD_ID_TEST = 0x134, /* ID test register */ + CMD_PL_TIMER = 0x138, /* PL timer register */ + CMD_WD_TIMER = 0x13c, /* WD timer register */ + CMD_PORT_SEL_COUNT = 0x140, /* port selector count register */ + CMD_APP_MEM_CTL = 0x144, /* Application Memory Control */ + CMD_XOR_MEM_CTL = 0x148, /* XOR Block Memory Control */ + CMD_DMA_MEM_CTL = 0x14c, /* DMA Block Memory Control */ + CMD_PORT_MEM_CTL0 = 0x150, /* Port Memory Control 0 */ + CMD_PORT_MEM_CTL1 = 0x154, /* Port Memory Control 1 */ + CMD_SATA_PORT_MEM_CTL0 = 0x158, /* SATA Port Memory Control 0 */ + CMD_SATA_PORT_MEM_CTL1 = 0x15c, /* SATA Port Memory Control 1 */ + CMD_XOR_MEM_BIST_CTL = 0x160, /* XOR Memory BIST Control */ + CMD_XOR_MEM_BIST_STAT = 0x164, /* XOR Memroy BIST Status */ + CMD_DMA_MEM_BIST_CTL = 0x168, /* DMA Memory BIST Control */ + CMD_DMA_MEM_BIST_STAT = 0x16c, /* DMA Memory BIST Status */ + CMD_PORT_MEM_BIST_CTL = 0x170, /* Port Memory BIST Control */ + CMD_PORT_MEM_BIST_STAT0 = 0x174, /* Port Memory BIST Status 0 */ + CMD_PORT_MEM_BIST_STAT1 = 0x178, /* Port Memory BIST Status 1 */ + CMD_STP_MEM_BIST_CTL = 0x17c, /* STP Memory BIST Control */ + CMD_STP_MEM_BIST_STAT0 = 0x180, /* STP Memory BIST Status 0 */ + CMD_STP_MEM_BIST_STAT1 = 0x184, /* STP Memory BIST Status 1 */ + CMD_RESET_COUNT = 0x188, /* Reset Count */ + CMD_MONTR_DATA_SEL = 0x18C, /* Monitor Data/Select */ + CMD_PLL_PHY_CONFIG = 0x190, /* PLL/PHY Configuration */ + CMD_PHY_CTL = 0x194, /* PHY Control and Status */ + CMD_PHY_TEST_COUNT0 = 0x198, /* Phy Test Count 0 */ + CMD_PHY_TEST_COUNT1 = 0x19C, /* Phy Test Count 1 */ + CMD_PHY_TEST_COUNT2 = 0x1A0, /* Phy Test Count 2 */ + CMD_APP_ERR_CONFIG = 0x1A4, /* Application Error Configuration */ + CMD_PND_FIFO_CTL0 = 0x1A8, /* Pending FIFO Control 0 */ + CMD_HOST_CTL = 0x1AC, /* Host Control Status */ + CMD_HOST_WR_DATA = 0x1B0, /* Host Write Data */ + CMD_HOST_RD_DATA = 0x1B4, /* Host Read Data */ + CMD_PHY_MODE_21 = 0x1B8, /* Phy Mode 21 */ + CMD_SL_MODE0 = 0x1BC, /* SL Mode 0 */ + CMD_SL_MODE1 = 0x1C0, /* SL Mode 1 */ + CMD_PND_FIFO_CTL1 = 0x1C4, /* Pending FIFO Control 1 */ +}; + +/* SAS/SATA configuration port registers, aka phy registers */ +enum sas_sata_config_port_regs { + PHYR_IDENTIFY = 0x0, /* info for IDENTIFY frame */ + PHYR_ADDR_LO = 0x4, /* my SAS address (low) */ + PHYR_ADDR_HI = 0x8, /* my SAS address (high) */ + PHYR_ATT_DEV_INFO = 0xC, /* attached device info */ + PHYR_ATT_ADDR_LO = 0x10, /* attached dev SAS addr (low) */ + PHYR_ATT_ADDR_HI = 0x14, /* attached dev SAS addr (high) */ + PHYR_SATA_CTL = 0x18, /* SATA control */ + PHYR_PHY_STAT = 0x1C, /* PHY status */ + PHYR_WIDE_PORT = 0x38, /* wide port participating */ + PHYR_CURRENT0 = 0x80, /* current connection info 0 */ + PHYR_CURRENT1 = 0x84, /* current connection info 1 */ + PHYR_CURRENT2 = 0x88, /* current connection info 2 */ +}; + +enum pci_cfg_registers { + PCR_PHY_CTL = 0x40, + PCR_PHY_CTL2 = 0x90, +}; + +enum pci_cfg_register_bits { + PCTL_PWR_ON = (0xFU << 24), + PCTL_OFF = (0xFU << 12), +}; + +enum nvram_layout_offsets { + NVR_SIG = 0x00, /* 0xAA, 0x55 */ + NVR_SAS_ADDR = 0x02, /* 8-byte SAS address */ +}; + +enum chip_flavors { + chip_6320, + chip_6440, + chip_6480, +}; + +struct mvs_chip_info { + unsigned int n_phy; + unsigned int srs_sz; + unsigned int slot_width; +}; + +struct mvs_err_info { + __le32 flags; + __le32 flags2; +}; + +struct mvs_prd { + __le64 addr; /* 64-bit buffer address */ + __le32 reserved; + __le32 len; /* 16-bit length */ +}; + +struct mvs_cmd_hdr { + __le32 flags; /* PRD tbl len; SAS, SATA ctl */ + __le32 lens; /* cmd, max resp frame len */ + __le32 tags; /* targ port xfer tag; tag */ + __le32 data_len; /* data xfer len */ + __le64 cmd_tbl; /* command table address */ + __le64 open_frame; /* open addr frame address */ + __le64 status_buf; /* status buffer address */ + __le64 prd_tbl; /* PRD tbl address */ + __le32 reserved[4]; +}; + +struct mvs_slot_info { + struct sas_task *task; + unsigned int n_elem; + + /* DMA buffer for storing cmd tbl, open addr frame, status buffer, + * and PRD table + */ + void *buf; + dma_addr_t buf_dma; + + void *response; +}; + +struct mvs_port { + struct asd_sas_port sas_port; +}; + +struct mvs_phy { + struct mvs_port *port; + struct asd_sas_phy sas_phy; + + u8 frame_rcvd[24 + 1024]; +}; + +struct mvs_info { + unsigned long flags; + + spinlock_t lock; /* host-wide lock */ + struct pci_dev *pdev; /* our device */ + void __iomem *regs; /* enhanced mode registers */ + void __iomem *peri_regs; /* peripheral registers */ + + u8 sas_addr[SAS_ADDR_SIZE]; + struct sas_ha_struct sas; /* SCSI/SAS glue */ + struct Scsi_Host *shost; + + __le32 *tx; /* TX (delivery) DMA ring */ + dma_addr_t tx_dma; + u32 tx_prod; /* cached next-producer idx */ + + __le32 *rx; /* RX (completion) DMA ring */ + dma_addr_t rx_dma; + u32 rx_cons; /* RX consumer idx */ + + __le32 *rx_fis; /* RX'd FIS area */ + dma_addr_t rx_fis_dma; + + struct mvs_cmd_hdr *slot; /* DMA command header slots */ + dma_addr_t slot_dma; + + const struct mvs_chip_info *chip; + + /* further per-slot information */ + struct mvs_slot_info slot_info[MVS_SLOTS]; + unsigned long tags[(MVS_SLOTS / sizeof(unsigned long)) + 1]; + + struct mvs_phy phy[MVS_MAX_PHYS]; + struct mvs_port port[MVS_MAX_PHYS]; +}; + +static struct scsi_transport_template *mvs_stt; + +static const struct mvs_chip_info mvs_chips[] = { + [chip_6320] = { 2, 16, 9 }, + [chip_6440] = { 4, 16, 9 }, + [chip_6480] = { 8, 32, 10 }, +}; + +static struct scsi_host_template mvs_sht = { + .module = THIS_MODULE, + .name = DRV_NAME, + .queuecommand = sas_queuecommand, + .target_alloc = sas_target_alloc, + .slave_configure = sas_slave_configure, + .slave_destroy = sas_slave_destroy, + .change_queue_depth = sas_change_queue_depth, + .change_queue_type = sas_change_queue_type, + .bios_param = sas_bios_param, + .can_queue = 1, + .cmd_per_lun = 1, + .this_id = -1, + .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, + .slave_alloc = sas_slave_alloc, + .target_destroy = sas_target_destroy, + .ioctl = sas_ioctl, +}; + +static void mvs_int_rx(struct mvs_info *mvi, bool self_clear); + +/* move to PCI layer or libata core? */ +static int pci_go_64(struct pci_dev *pdev) +{ + int rc; + + if (!pci_set_dma_mask(pdev, DMA_64BIT_MASK)) { + rc = pci_set_consistent_dma_mask(pdev, DMA_64BIT_MASK); + if (rc) { + rc = pci_set_consistent_dma_mask(pdev, DMA_32BIT_MASK); + if (rc) { + dev_printk(KERN_ERR, &pdev->dev, + "64-bit DMA enable failed\n"); + return rc; + } + } + } else { + rc = pci_set_dma_mask(pdev, DMA_32BIT_MASK); + if (rc) { + dev_printk(KERN_ERR, &pdev->dev, + "32-bit DMA enable failed\n"); + return rc; + } + rc = pci_set_consistent_dma_mask(pdev, DMA_32BIT_MASK); + if (rc) { + dev_printk(KERN_ERR, &pdev->dev, + "32-bit consistent DMA enable failed\n"); + return rc; + } + } + + return rc; +} + +static void mvs_tag_clear(struct mvs_info *mvi, unsigned int tag) +{ + mvi->tags[tag / sizeof(unsigned long)] &= + ~(1UL << (tag % sizeof(unsigned long))); +} + +static void mvs_tag_set(struct mvs_info *mvi, unsigned int tag) +{ + mvi->tags[tag / sizeof(unsigned long)] |= + (1UL << (tag % sizeof(unsigned long))); +} + +static bool mvs_tag_test(struct mvs_info *mvi, unsigned int tag) +{ + return mvi->tags[tag / sizeof(unsigned long)] & + (1UL << (tag % sizeof(unsigned long))); +} + +static int mvs_tag_alloc(struct mvs_info *mvi, unsigned int *tag_out) +{ + unsigned int i; + + for (i = 0; i < MVS_SLOTS; i++) + if (!mvs_tag_test(mvi, i)) { + mvs_tag_set(mvi, i); + *tag_out = i; + return 0; + } + + return -EBUSY; +} + +static int mvs_eep_read(void __iomem *regs, unsigned int addr, u32 *data) +{ + int timeout = 1000; + + if (addr & ~SPI_ADDR_MASK) + return -EINVAL; + + writel(addr, regs + SPI_CMD); + writel(TWSI_RD, regs + SPI_CTL); + + while (timeout-- > 0) { + if (readl(regs + SPI_CTL) & TWSI_RDY) { + *data = readl(regs + SPI_DATA); + return 0; + } + + udelay(10); + } + + return -EBUSY; +} + +static int mvs_eep_read_buf(void __iomem *regs, unsigned int addr, + void *buf, unsigned int buflen) +{ + unsigned int addr_end, tmp_addr, i, j; + u32 tmp = 0; + int rc; + u8 *tmp8, *buf8 = buf; + + addr_end = addr + buflen; + tmp_addr = ALIGN(addr, 4); + if (addr > 0xff) + return -EINVAL; + + j = addr & 0x3; + if (j) { + rc = mvs_eep_read(regs, tmp_addr, &tmp); + if (rc) + return rc; + + tmp8 = (u8 *) &tmp; + for (i = j; i < 4; i++) + *buf8++ = tmp8[i]; + + tmp_addr += 4; + } + + for (j = ALIGN(addr_end, 4); tmp_addr < j; tmp_addr += 4) { + rc = mvs_eep_read(regs, tmp_addr, &tmp); + if (rc) + return rc; + + memcpy(buf8, &tmp, 4); + buf8 += 4; + } + + if (tmp_addr < addr_end) { + rc = mvs_eep_read(regs, tmp_addr, &tmp); + if (rc) + return rc; + + tmp8 = (u8 *) &tmp; + j = addr_end - tmp_addr; + for (i = 0; i < j; i++) + *buf8++ = tmp8[i]; + + tmp_addr += 4; + } + + return 0; +} + +static int mvs_nvram_read(struct mvs_info *mvi, unsigned int addr, + void *buf, unsigned int buflen) +{ + void __iomem *regs = mvi->regs; + int rc, i; + unsigned int sum; + u8 hdr[2], *tmp; + const char *msg; + + rc = mvs_eep_read_buf(regs, addr, &hdr, 2); + if (rc) { + msg = "nvram hdr read failed"; + goto err_out; + } + rc = mvs_eep_read_buf(regs, addr + 2, buf, buflen); + if (rc) { + msg = "nvram read failed"; + goto err_out; + } + + if (hdr[0] != 0x5A) { /* entry id */ + msg = "invalid nvram entry id"; + rc = -ENOENT; + goto err_out; + } + + tmp = buf; + sum = ((unsigned int)hdr[0]) + ((unsigned int)hdr[1]); + for (i = 0; i < buflen; i++) + sum += ((unsigned int)tmp[i]); + + if (sum) { + msg = "nvram checksum failure"; + rc = -EILSEQ; + goto err_out; + } + + return 0; + +err_out: + dev_printk(KERN_ERR, &mvi->pdev->dev, "%s", msg); + return rc; +} + +static void mvs_int_port(struct mvs_info *mvi, int port_no, u32 events) +{ + /* FIXME */ +} + +static void mvs_int_sata(struct mvs_info *mvi) +{ + /* FIXME */ +} + +static void mvs_slot_free(struct mvs_info *mvi, struct sas_task *task, + struct mvs_slot_info *slot, unsigned int slot_idx) +{ + if (slot->n_elem) + pci_unmap_sg(mvi->pdev, task->scatter, + slot->n_elem, task->data_dir); + + switch (task->task_proto) { + case SAS_PROTOCOL_SMP: + pci_unmap_sg(mvi->pdev, &task->smp_task.smp_resp, 1, + PCI_DMA_FROMDEVICE); + pci_unmap_sg(mvi->pdev, &task->smp_task.smp_req, 1, + PCI_DMA_TODEVICE); + break; + + case SAS_PROTOCOL_SATA: + case SAS_PROTOCOL_STP: + case SAS_PROTOCOL_SSP: + default: + /* do nothing */ + break; + } + + mvs_tag_clear(mvi, slot_idx); +} + +static void mvs_slot_err(struct mvs_info *mvi, struct sas_task *task, + unsigned int slot_idx) +{ + /* FIXME */ +} + +static void mvs_slot_complete(struct mvs_info *mvi, u32 rx_desc) +{ + unsigned int slot_idx = rx_desc & RXQ_SLOT_MASK; + struct mvs_slot_info *slot = &mvi->slot_info[slot_idx]; + struct sas_task *task = slot->task; + struct task_status_struct *tstat = &task->task_status; + bool aborted; + + spin_lock(&task->task_state_lock); + aborted = task->task_state_flags & SAS_TASK_STATE_ABORTED; + if (!aborted) { + task->task_state_flags &= + ~(SAS_TASK_STATE_PENDING | SAS_TASK_AT_INITIATOR); + task->task_state_flags |= SAS_TASK_STATE_DONE; + } + spin_unlock(&task->task_state_lock); + + if (aborted) + return; + + memset(tstat, 0, sizeof(*tstat)); + tstat->resp = SAS_TASK_COMPLETE; + + /* error info record present */ + if (rx_desc & RXQ_ERR) { + tstat->stat = SAM_CHECK_COND; + mvs_slot_err(mvi, task, slot_idx); + goto out; + } + + switch (task->task_proto) { + case SAS_PROTOCOL_SSP: + /* hw says status == 0, datapres == 0 */ + if (rx_desc & RXQ_GOOD) + tstat->stat = SAM_GOOD; + + /* response frame present */ + else if (rx_desc & RXQ_RSP) { + struct ssp_response_iu *iu = + slot->response + sizeof(struct mvs_err_info); + sas_ssp_task_response(&mvi->pdev->dev, task, iu); + } + + /* should never happen? */ + else + tstat->stat = SAM_CHECK_COND; + break; + + case SAS_PROTOCOL_SMP: + tstat->stat = SAM_GOOD; + break; + + case SAS_PROTOCOL_SATA: + case SAS_PROTOCOL_STP: + if ((rx_desc & (RXQ_DONE | RXQ_ERR | RXQ_ATTN)) == RXQ_DONE) + tstat->stat = SAM_GOOD; + else + tstat->stat = SAM_CHECK_COND; + /* FIXME: read taskfile data from SATA register set + * associated with SATA target + */ + break; + + default: + tstat->stat = SAM_CHECK_COND; + break; + } + +out: + mvs_slot_free(mvi, task, slot, slot_idx); + task->task_done(task); +} + +static void mvs_int_full(struct mvs_info *mvi) +{ + void __iomem *regs = mvi->regs; + u32 tmp, stat; + int i; + + stat = mr32(INT_STAT); + + for (i = 0; i < MVS_MAX_PORTS; i++) { + tmp = (stat >> i) & (CINT_PORT | CINT_PORT_STOPPED); + if (tmp) + mvs_int_port(mvi, i, tmp); + } + + if (stat & CINT_SRS) + mvs_int_sata(mvi); + + if (stat & (CINT_CI_STOP | CINT_DONE)) + mvs_int_rx(mvi, false); + + mw32(INT_STAT, stat); +} + +static void mvs_int_rx(struct mvs_info *mvi, bool self_clear) +{ + u32 rx_prod_idx, rx_desc; + bool attn = false; + + /* the first dword in the RX ring is special: it contains + * a mirror of the hardware's RX producer index, so that + * we don't have to stall the CPU reading that register. + * The actual RX ring is offset by one dword, due to this. + */ + rx_prod_idx = le32_to_cpu(mvi->rx[0]) & 0xfff; + if (rx_prod_idx == 0xfff) { /* h/w hasn't touched RX ring yet */ + mvi->rx_cons = 0xfff; + return; + } + if (mvi->rx_cons == 0xfff) + mvi->rx_cons = MVS_RX_RING_SZ - 1; + + while (mvi->rx_cons != rx_prod_idx) { + /* increment our internal RX consumer pointer */ + mvi->rx_cons = (mvi->rx_cons + 1) & (MVS_RX_RING_SZ - 1); + + /* Read RX descriptor at offset+1, due to above */ + rx_desc = le32_to_cpu(mvi->rx[mvi->rx_cons + 1]); + + if (rx_desc & RXQ_DONE) + /* we had a completion, error or no */ + mvs_slot_complete(mvi, rx_desc); + + if (rx_desc & RXQ_ATTN) + attn = true; + } + + if (attn && self_clear) + mvs_int_full(mvi); + +} + +static irqreturn_t mvs_interrupt(int irq, void *opaque) +{ + struct mvs_info *mvi = opaque; + void __iomem *regs = mvi->regs; + u32 stat; + + stat = mr32(GBL_INT_STAT); + if (stat == 0 || stat == 0xffffffff) + return IRQ_NONE; + + spin_lock(&mvi->lock); + + mvs_int_full(mvi); + + spin_unlock(&mvi->lock); + + return IRQ_HANDLED; +} + +static irqreturn_t mvs_msi_interrupt(int irq, void *opaque) +{ + struct mvs_info *mvi = opaque; + + spin_lock(&mvi->lock); + + mvs_int_rx(mvi, true); + + spin_unlock(&mvi->lock); + + return IRQ_HANDLED; +} + +struct mvs_task_exec_info { + struct sas_task *task; + struct mvs_cmd_hdr *hdr; + unsigned int tag; + int n_elem; +}; + +static int mvs_task_prep_smp(struct mvs_info *mvi, struct mvs_task_exec_info *tei) +{ + int elem, rc; + struct mvs_cmd_hdr *hdr = tei->hdr; + struct scatterlist *sg_req, *sg_resp; + unsigned int req_len, resp_len, tag = tei->tag; + + /* + * DMA-map SMP request, response buffers + */ + + sg_req = &tei->task->smp_task.smp_req; + elem = pci_map_sg(mvi->pdev, sg_req, 1, PCI_DMA_TODEVICE); + if (!elem) + return -ENOMEM; + req_len = sg_dma_len(sg_req); + + sg_resp = &tei->task->smp_task.smp_resp; + elem = pci_map_sg(mvi->pdev, sg_resp, 1, PCI_DMA_FROMDEVICE); + if (!elem) { + rc = -ENOMEM; + goto err_out; + } + resp_len = sg_dma_len(sg_resp); + + /* must be in dwords */ + if ((req_len & 0x3) || (resp_len & 0x3)) { + rc = -EINVAL; + goto err_out_2; + } + + /* + * Fill in TX ring and command slot header + */ + + mvi->tx[tag] = cpu_to_le32( + (TXQ_CMD_SMP << TXQ_CMD_SHIFT) | TXQ_MODE_I | tag); + + hdr->flags = 0; + hdr->lens = cpu_to_le32(((resp_len / 4) << 16) | (req_len / 4)); + hdr->tags = cpu_to_le32(tag); + hdr->data_len = 0; + hdr->cmd_tbl = cpu_to_le64(sg_dma_address(sg_req)); + hdr->open_frame = 0; + hdr->status_buf = cpu_to_le64(sg_dma_address(sg_resp)); + hdr->prd_tbl = 0; + + return 0; + +err_out_2: + pci_unmap_sg(mvi->pdev, &tei->task->smp_task.smp_resp, 1, + PCI_DMA_FROMDEVICE); +err_out: + pci_unmap_sg(mvi->pdev, &tei->task->smp_task.smp_req, 1, + PCI_DMA_TODEVICE); + return rc; +} + +static int mvs_task_prep_ata(struct mvs_info *mvi, + struct mvs_task_exec_info *tei) +{ + struct sas_task *task = tei->task; + struct domain_device *dev = task->dev; + struct mvs_cmd_hdr *hdr = tei->hdr; + struct asd_sas_port *sas_port = dev->port; + unsigned int tag = tei->tag; + struct mvs_slot_info *slot = &mvi->slot_info[tag]; + u32 flags = (tei->n_elem << MCH_PRD_LEN_SHIFT); + struct scatterlist *sg; + struct mvs_prd *buf_prd; + void *buf_tmp; + u8 *buf_cmd, *buf_oaf; + dma_addr_t buf_tmp_dma; + unsigned int i, req_len, resp_len; + + /* FIXME: fill in SATA register set */ + mvi->tx[tag] = cpu_to_le32(TXQ_MODE_I | tag | + (TXQ_CMD_STP << TXQ_CMD_SHIFT) | + (sas_port->phy_mask << TXQ_PHY_SHIFT)); + + if (task->ata_task.use_ncq) + flags |= MCH_FPDMA; + if (dev->sata_dev.command_set == ATAPI_COMMAND_SET) + flags |= MCH_ATAPI; + /* FIXME: fill in port multiplier number */ + + hdr->flags = cpu_to_le32(flags); + hdr->tags = cpu_to_le32(tag); + hdr->data_len = cpu_to_le32(task->total_xfer_len); + + /* + * arrange MVS_SLOT_BUF_SZ-sized DMA buffer according to our needs + */ + memset(slot->buf, 0, MVS_SLOT_BUF_SZ); + + /* region 1: command table area (MVS_ATA_CMD_SZ bytes) ***************/ + buf_cmd = + buf_tmp = slot->buf; + buf_tmp_dma = slot->buf_dma; + + hdr->cmd_tbl = cpu_to_le64(buf_tmp_dma); + + buf_tmp += MVS_ATA_CMD_SZ; + buf_tmp_dma += MVS_ATA_CMD_SZ; + + /* region 2: open address frame area (MVS_OAF_SZ bytes) **********/ + /* used for STP. unused for SATA? */ + buf_oaf = buf_tmp; + hdr->open_frame = cpu_to_le64(buf_tmp_dma); + + buf_tmp += MVS_OAF_SZ; + buf_tmp_dma += MVS_OAF_SZ; + + /* region 3: PRD table ***********************************************/ + buf_prd = buf_tmp; + hdr->prd_tbl = cpu_to_le64(buf_tmp_dma); + + i = sizeof(struct mvs_prd) * tei->n_elem; + buf_tmp += i; + buf_tmp_dma += i; + + /* region 4: status buffer (larger the PRD, smaller this buf) ********/ + /* FIXME: probably unused, for SATA. kept here just in case + * we get a STP/SATA error information record + */ + slot->response = buf_tmp; + hdr->status_buf = cpu_to_le64(buf_tmp_dma); + + req_len = sizeof(struct ssp_frame_hdr) + 28; + resp_len = MVS_SLOT_BUF_SZ - MVS_ATA_CMD_SZ - + sizeof(struct mvs_err_info) - i; + + /* request, response lengths */ + hdr->lens = cpu_to_le32(((resp_len / 4) << 16) | (req_len / 4)); + + /* fill in command FIS and ATAPI CDB */ + memcpy(buf_cmd, &task->ata_task.fis, + sizeof(struct host_to_dev_fis)); + memcpy(buf_cmd + 0x40, task->ata_task.atapi_packet, 16); + + /* fill in PRD (scatter/gather) table, if any */ + sg = task->scatter; + for (i = 0; i < tei->n_elem; i++) { + buf_prd->addr = cpu_to_le64(sg_dma_address(sg)); + buf_prd->len = cpu_to_le32(sg_dma_len(sg)); + + sg++; + buf_prd++; + } + + return 0; +} + +static int mvs_task_prep_ssp(struct mvs_info *mvi, + struct mvs_task_exec_info *tei) +{ + struct sas_task *task = tei->task; + struct asd_sas_port *sas_port = task->dev->port; + struct mvs_cmd_hdr *hdr = tei->hdr; + struct mvs_slot_info *slot; + struct scatterlist *sg; + unsigned int resp_len, req_len, i, tag = tei->tag; + struct mvs_prd *buf_prd; + struct ssp_frame_hdr *ssp_hdr; + void *buf_tmp; + u8 *buf_cmd, *buf_oaf, fburst = 0; + dma_addr_t buf_tmp_dma; + u32 flags; + + slot = &mvi->slot_info[tag]; + + mvi->tx[tag] = cpu_to_le32(TXQ_MODE_I | tag | + (TXQ_CMD_SSP << TXQ_CMD_SHIFT) | + (sas_port->phy_mask << TXQ_PHY_SHIFT)); + + flags = MCH_RETRY; + if (task->ssp_task.enable_first_burst) { + flags |= MCH_FBURST; + fburst = (1 << 7); + } + hdr->flags = cpu_to_le32(flags | + (tei->n_elem << MCH_PRD_LEN_SHIFT) | + (MCH_SSP_FR_CMD << MCH_SSP_FR_TYPE_SHIFT)); + + hdr->tags = cpu_to_le32(tag); + hdr->data_len = cpu_to_le32(task->total_xfer_len); + + /* + * arrange MVS_SLOT_BUF_SZ-sized DMA buffer according to our needs + */ + memset(slot->buf, 0, MVS_SLOT_BUF_SZ); + + /* region 1: command table area (MVS_SSP_CMD_SZ bytes) ***************/ + buf_cmd = + buf_tmp = slot->buf; + buf_tmp_dma = slot->buf_dma; + + hdr->cmd_tbl = cpu_to_le64(buf_tmp_dma); + + buf_tmp += MVS_SSP_CMD_SZ; + buf_tmp_dma += MVS_SSP_CMD_SZ; + + /* region 2: open address frame area (MVS_OAF_SZ bytes) **********/ + buf_oaf = buf_tmp; + hdr->open_frame = cpu_to_le64(buf_tmp_dma); + + buf_tmp += MVS_OAF_SZ; + buf_tmp_dma += MVS_OAF_SZ; + + /* region 3: PRD table ***********************************************/ + buf_prd = buf_tmp; + hdr->prd_tbl = cpu_to_le64(buf_tmp_dma); + + i = sizeof(struct mvs_prd) * tei->n_elem; + buf_tmp += i; + buf_tmp_dma += i; + + /* region 4: status buffer (larger the PRD, smaller this buf) ********/ + slot->response = buf_tmp; + hdr->status_buf = cpu_to_le64(buf_tmp_dma); + + req_len = sizeof(struct ssp_frame_hdr) + 28; + resp_len = MVS_SLOT_BUF_SZ - MVS_SSP_CMD_SZ - MVS_OAF_SZ - + sizeof(struct mvs_err_info) - i; + + /* request, response lengths */ + hdr->lens = cpu_to_le32(((resp_len / 4) << 16) | (req_len / 4)); + + /* generate open address frame hdr (first 12 bytes) */ + buf_oaf[0] = (1 << 7) | (1 << 4) | 0x1; /* initiator, SSP, ftype 1h */ + buf_oaf[1] = task->dev->linkrate & 0xf; + buf_oaf[2] = tag >> 8; + buf_oaf[3] = tag; + memcpy(buf_oaf + 4, task->dev->sas_addr, SAS_ADDR_SIZE); + + /* fill in SSP frame header */ + ssp_hdr = (struct ssp_frame_hdr *) buf_cmd; + ssp_hdr->frame_type = SSP_COMMAND; + memcpy(ssp_hdr->hashed_dest_addr, task->dev->hashed_sas_addr, + HASHED_SAS_ADDR_SIZE); + memcpy(ssp_hdr->hashed_src_addr, + task->dev->port->ha->hashed_sas_addr, HASHED_SAS_ADDR_SIZE); + ssp_hdr->tag = cpu_to_be16(tag); + + /* fill in command frame IU */ + buf_cmd += sizeof(*ssp_hdr); + memcpy(buf_cmd, &task->ssp_task.LUN, 8); + buf_cmd[9] = fburst | + task->ssp_task.task_attr | + (task->ssp_task.task_prio << 3); + memcpy(buf_cmd + 12, &task->ssp_task.cdb, 16); + + /* fill in PRD (scatter/gather) table, if any */ + sg = task->scatter; + for (i = 0; i < tei->n_elem; i++) { + buf_prd->addr = cpu_to_le64(sg_dma_address(sg)); + buf_prd->len = cpu_to_le32(sg_dma_len(sg)); + + sg++; + buf_prd++; + } + + return 0; +} + +static int mvs_task_exec(struct sas_task *task, const int num, gfp_t gfp_flags) +{ + struct mvs_info *mvi = task->dev->port->ha->lldd_ha; + unsigned int tag = 0xdeadbeef, rc, n_elem = 0; + void __iomem *regs = mvi->regs; + unsigned long flags; + struct mvs_task_exec_info tei; + + /* FIXME: STP/SATA support not complete yet */ + if (task->task_proto == SAS_PROTOCOL_SATA || task->task_proto == SAS_PROTOCOL_STP) + return -SAS_DEV_NO_RESPONSE; + + if (task->num_scatter) { + n_elem = pci_map_sg(mvi->pdev, task->scatter, + task->num_scatter, task->data_dir); + if (!n_elem) + return -ENOMEM; + } + + spin_lock_irqsave(&mvi->lock, flags); + + rc = mvs_tag_alloc(mvi, &tag); + if (rc) + goto err_out; + + mvi->slot_info[tag].task = task; + mvi->slot_info[tag].n_elem = n_elem; + tei.task = task; + tei.hdr = &mvi->slot[tag]; + tei.tag = tag; + tei.n_elem = n_elem; + + switch (task->task_proto) { + case SAS_PROTOCOL_SMP: + rc = mvs_task_prep_smp(mvi, &tei); + break; + case SAS_PROTOCOL_SSP: + rc = mvs_task_prep_ssp(mvi, &tei); + break; + case SAS_PROTOCOL_SATA: + case SAS_PROTOCOL_STP: + rc = mvs_task_prep_ata(mvi, &tei); + break; + default: + rc = -EINVAL; + break; + } + + if (rc) + goto err_out_tag; + + /* TODO: select normal or high priority */ + + mw32(RX_PROD_IDX, mvi->tx_prod); + + mvi->tx_prod = (mvi->tx_prod + 1) & (MVS_TX_RING_SZ - 1); + + spin_lock(&task->task_state_lock); + task->task_state_flags |= SAS_TASK_AT_INITIATOR; + spin_unlock(&task->task_state_lock); + + spin_unlock_irqrestore(&mvi->lock, flags); + return 0; + +err_out_tag: + mvs_tag_clear(mvi, tag); +err_out: + if (n_elem) + pci_unmap_sg(mvi->pdev, task->scatter, n_elem, task->data_dir); + spin_unlock_irqrestore(&mvi->lock, flags); + return rc; +} + +static void mvs_free(struct mvs_info *mvi) +{ + int i; + + if (!mvi) + return; + + for (i = 0; i < MVS_SLOTS; i++) { + struct mvs_slot_info *slot = &mvi->slot_info[i]; + + if (slot->buf) + dma_free_coherent(&mvi->pdev->dev, MVS_SLOT_BUF_SZ, + slot->buf, slot->buf_dma); + } + + if (mvi->tx) + dma_free_coherent(&mvi->pdev->dev, + sizeof(*mvi->tx) * MVS_TX_RING_SZ, + mvi->tx, mvi->tx_dma); + if (mvi->rx_fis) + dma_free_coherent(&mvi->pdev->dev, MVS_RX_FISL_SZ, + mvi->rx_fis, mvi->rx_fis_dma); + if (mvi->rx) + dma_free_coherent(&mvi->pdev->dev, + sizeof(*mvi->rx) * MVS_RX_RING_SZ, + mvi->rx, mvi->rx_dma); + if (mvi->slot) + dma_free_coherent(&mvi->pdev->dev, + sizeof(*mvi->slot) * MVS_RX_RING_SZ, + mvi->slot, mvi->slot_dma); + if (mvi->peri_regs) + iounmap(mvi->peri_regs); + if (mvi->regs) + iounmap(mvi->regs); + if (mvi->shost) + scsi_host_put(mvi->shost); + kfree(mvi->sas.sas_port); + kfree(mvi->sas.sas_phy); + kfree(mvi); +} + +/* FIXME: locking? */ +static int mvs_phy_control(struct asd_sas_phy *sas_phy, enum phy_func func, + void *funcdata) +{ + struct mvs_info *mvi = sas_phy->ha->lldd_ha; + void __iomem *reg; + int rc = 0, phy_id = sas_phy->id; + u32 tmp; + + reg = mvi->regs + MVS_P0_SER_CTLSTAT + (phy_id * 4); + + switch (func) { + case PHY_FUNC_SET_LINK_RATE: { + struct sas_phy_linkrates *rates = funcdata; + u32 lrmin = 0, lrmax = 0; + + lrmin = (rates->minimum_linkrate << 8); + lrmax = (rates->maximum_linkrate << 12); + + tmp = readl(reg); + if (lrmin) { + tmp &= ~(0xf << 8); + tmp |= lrmin; + } + if (lrmax) { + tmp &= ~(0xf << 12); + tmp |= lrmax; + } + writel(tmp, reg); + break; + } + + case PHY_FUNC_HARD_RESET: + tmp = readl(reg); + if (tmp & PHY_RST_HARD) + break; + writel(tmp | PHY_RST_HARD, reg); + break; + + case PHY_FUNC_LINK_RESET: + writel(readl(reg) | PHY_RST, reg); + break; + + case PHY_FUNC_DISABLE: + case PHY_FUNC_RELEASE_SPINUP_HOLD: + default: + rc = -EOPNOTSUPP; + } + + return rc; +} + +static void __devinit mvs_phy_init(struct mvs_info *mvi, int phy_id) +{ + struct mvs_phy *phy = &mvi->phy[phy_id]; + struct asd_sas_phy *sas_phy = &phy->sas_phy; + + sas_phy->enabled = (phy_id < mvi->chip->n_phy) ? 1 : 0; + sas_phy->class = SAS; + sas_phy->iproto = SAS_PROTOCOL_ALL; + sas_phy->tproto = 0; + sas_phy->type = PHY_TYPE_PHYSICAL; + sas_phy->role = PHY_ROLE_INITIATOR; + sas_phy->oob_mode = OOB_NOT_CONNECTED; + sas_phy->linkrate = SAS_LINK_RATE_UNKNOWN; + + sas_phy->id = phy_id; + sas_phy->sas_addr = &mvi->sas_addr[0]; + sas_phy->frame_rcvd = &phy->frame_rcvd[0]; + sas_phy->ha = &mvi->sas; + sas_phy->lldd_phy = phy; +} + +static struct mvs_info * __devinit mvs_alloc(struct pci_dev *pdev, + const struct pci_device_id *ent) +{ + struct mvs_info *mvi; + unsigned long res_start, res_len; + struct asd_sas_phy **arr_phy; + struct asd_sas_port **arr_port; + const struct mvs_chip_info *chip = &mvs_chips[ent->driver_data]; + int i; + + /* + * alloc and init our per-HBA mvs_info struct + */ + + mvi = kzalloc(sizeof(*mvi), GFP_KERNEL); + if (!mvi) + return NULL; + + spin_lock_init(&mvi->lock); + mvi->pdev = pdev; + mvi->chip = chip; + + if (pdev->device == 0x6440 && pdev->revision == 0) + mvi->flags |= MVF_PHY_PWR_FIX; + + /* + * alloc and init SCSI, SAS glue + */ + + mvi->shost = scsi_host_alloc(&mvs_sht, sizeof(void *)); + if (!mvi->shost) + goto err_out; + + arr_phy = kcalloc(MVS_MAX_PHYS, sizeof(void *), GFP_KERNEL); + arr_port = kcalloc(MVS_MAX_PHYS, sizeof(void *), GFP_KERNEL); + if (!arr_phy || !arr_port) + goto err_out; + + for (i = 0; i < MVS_MAX_PHYS; i++) { + mvs_phy_init(mvi, i); + arr_phy[i] = &mvi->phy[i].sas_phy; + arr_port[i] = &mvi->port[i].sas_port; + } + + SHOST_TO_SAS_HA(mvi->shost) = &mvi->sas; + mvi->shost->transportt = mvs_stt; + mvi->shost->max_id = ~0; + mvi->shost->max_lun = ~0; + mvi->shost->max_cmd_len = ~0; + + mvi->sas.sas_ha_name = DRV_NAME; + mvi->sas.dev = &pdev->dev; + mvi->sas.lldd_module = THIS_MODULE; + mvi->sas.sas_addr = &mvi->sas_addr[0]; + mvi->sas.sas_phy = arr_phy; + mvi->sas.sas_port = arr_port; + mvi->sas.num_phys = chip->n_phy; + mvi->sas.lldd_max_execute_num = MVS_TX_RING_SZ - 1;/* FIXME: correct? */ + mvi->sas.lldd_queue_size = MVS_TX_RING_SZ - 1; /* FIXME: correct? */ + mvi->sas.lldd_ha = mvi; + mvi->sas.core.shost = mvi->shost; + + mvs_tag_set(mvi, MVS_TX_RING_SZ - 1); + + /* + * ioremap main and peripheral registers + */ + + res_start = pci_resource_start(pdev, 2); + res_len = pci_resource_len(pdev, 2); + if (!res_start || !res_len) + goto err_out; + + mvi->peri_regs = ioremap_nocache(res_start, res_len); + if (!mvi->regs) + goto err_out; + + res_start = pci_resource_start(pdev, 4); + res_len = pci_resource_len(pdev, 4); + if (!res_start || !res_len) + goto err_out; + + mvi->regs = ioremap_nocache(res_start, res_len); + if (!mvi->regs) + goto err_out; + + /* + * alloc and init our DMA areas + */ + + mvi->tx = dma_alloc_coherent(&pdev->dev, + sizeof(*mvi->tx) * MVS_TX_RING_SZ, + &mvi->tx_dma, GFP_KERNEL); + if (!mvi->tx) + goto err_out; + memset(mvi->tx, 0, sizeof(*mvi->tx) * MVS_TX_RING_SZ); + + mvi->rx_fis = dma_alloc_coherent(&pdev->dev, MVS_RX_FISL_SZ, + &mvi->rx_fis_dma, GFP_KERNEL); + if (!mvi->rx_fis) + goto err_out; + memset(mvi->rx_fis, 0, MVS_RX_FISL_SZ); + + mvi->rx = dma_alloc_coherent(&pdev->dev, + sizeof(*mvi->rx) * MVS_RX_RING_SZ, + &mvi->rx_dma, GFP_KERNEL); + if (!mvi->rx) + goto err_out; + memset(mvi->rx, 0, sizeof(*mvi->rx) * MVS_RX_RING_SZ); + + mvi->rx[0] = cpu_to_le32(0xfff); + mvi->rx_cons = 0xfff; + + mvi->slot = dma_alloc_coherent(&pdev->dev, + sizeof(*mvi->slot) * MVS_SLOTS, + &mvi->slot_dma, GFP_KERNEL); + if (!mvi->slot) + goto err_out; + memset(mvi->slot, 0, sizeof(*mvi->slot) * MVS_SLOTS); + + for (i = 0; i < MVS_SLOTS; i++) { + struct mvs_slot_info *slot = &mvi->slot_info[i]; + + slot->buf = dma_alloc_coherent(&pdev->dev, MVS_SLOT_BUF_SZ, + &slot->buf_dma, GFP_KERNEL); + if (!slot->buf) + goto err_out; + memset(slot->buf, 0, MVS_SLOT_BUF_SZ); + } + + /* finally, read NVRAM to get our SAS address */ + if (mvs_nvram_read(mvi, NVR_SAS_ADDR, &mvi->sas_addr, 8)) + goto err_out; + + return mvi; + +err_out: + mvs_free(mvi); + return NULL; +} + +static u32 mvs_cr32(void __iomem *regs, u32 addr) +{ + mw32(CMD_ADDR, addr); + return mr32(CMD_DATA); +} + +static void mvs_cw32(void __iomem *regs, u32 addr, u32 val) +{ + mw32(CMD_ADDR, addr); + mw32(CMD_DATA, val); +} + +#if 0 +static u32 mvs_phy_read(struct mvs_info *mvi, unsigned int phy_id, u32 addr) +{ + void __iomem *regs = mvi->regs; + void __iomem *phy_regs = regs + MVS_P0_CFG_ADDR + (phy_id * 8); + + writel(addr, phy_regs); + return readl(phy_regs + 4); +} +#endif + +static void mvs_phy_write(struct mvs_info *mvi, unsigned int phy_id, + u32 addr, u32 val) +{ + void __iomem *regs = mvi->regs; + void __iomem *phy_regs = regs + MVS_P0_CFG_ADDR + (phy_id * 8); + + writel(addr, phy_regs); + writel(val, phy_regs + 4); + readl(phy_regs); /* flush */ +} + +static void __devinit mvs_phy_hacks(struct mvs_info *mvi) +{ + void __iomem *regs = mvi->regs; + u32 tmp; + + /* workaround for SATA R-ERR, to ignore phy glitch */ + tmp = mvs_cr32(regs, CMD_PHY_TIMER); + tmp &= ~(1 << 9); + tmp |= (1 << 10); + mvs_cw32(regs, CMD_PHY_TIMER, tmp); + + /* enable retry 127 times */ + mvs_cw32(regs, CMD_SAS_CTL1, 0x7f7f); + + /* extend open frame timeout to max */ + tmp = mvs_cr32(regs, CMD_SAS_CTL0); + tmp &= ~0xffff; + tmp |= 0x3fff; + mvs_cw32(regs, CMD_SAS_CTL0, tmp); + + /* workaround for WDTIMEOUT , set to 550 ms */ + mvs_cw32(regs, CMD_WD_TIMER, 0xffffff); + + /* not to halt for different port op during wideport link change */ + mvs_cw32(regs, CMD_APP_ERR_CONFIG, 0xffefbf7d); + + /* workaround for Seagate disk not-found OOB sequence, recv + * COMINIT before sending out COMWAKE */ + tmp = mvs_cr32(regs, CMD_PHY_MODE_21); + tmp &= 0x0000ffff; + tmp |= 0x00fa0000; + mvs_cw32(regs, CMD_PHY_MODE_21, tmp); + + tmp = mvs_cr32(regs, CMD_PHY_TIMER); + tmp &= 0x1fffffff; + tmp |= (2U << 29); /* 8 ms retry */ + mvs_cw32(regs, CMD_PHY_TIMER, tmp); +} + +static int __devinit mvs_hw_init(struct mvs_info *mvi) +{ + void __iomem *regs = mvi->regs; + int i; + u32 tmp, cctl; + + /* make sure interrupts are masked immediately (paranoia) */ + mw32(GBL_CTL, 0); + tmp = mr32(GBL_CTL); + + if (!(tmp & HBA_RST)) { + if (mvi->flags & MVF_PHY_PWR_FIX) { + pci_read_config_dword(mvi->pdev, PCR_PHY_CTL, &tmp); + tmp &= ~PCTL_PWR_ON; + tmp |= PCTL_OFF; + pci_write_config_dword(mvi->pdev, PCR_PHY_CTL, tmp); + + pci_read_config_dword(mvi->pdev, PCR_PHY_CTL2, &tmp); + tmp &= ~PCTL_PWR_ON; + tmp |= PCTL_OFF; + pci_write_config_dword(mvi->pdev, PCR_PHY_CTL2, tmp); + } + + /* global reset, incl. COMRESET/H_RESET_N (self-clearing) */ + mw32_f(GBL_CTL, HBA_RST); + } + + + /* wait for reset to finish; timeout is just a guess */ + i = 1000; + while (i-- > 0) { + msleep(10); + + if (!(mr32(GBL_CTL) & HBA_RST)) + break; + } + if (mr32(GBL_CTL) & HBA_RST) { + dev_printk(KERN_ERR, &mvi->pdev->dev, "HBA reset failed\n"); + return -EBUSY; + } + + /* make sure RST is set; HBA_RST /should/ have done that for us */ + cctl = mr32(CTL); + if (cctl & CCTL_RST) + cctl &= ~CCTL_RST; + else + mw32_f(CTL, cctl | CCTL_RST); + + pci_read_config_dword(mvi->pdev, PCR_PHY_CTL, &tmp); + tmp |= PCTL_PWR_ON; + tmp &= ~PCTL_OFF; + pci_write_config_dword(mvi->pdev, PCR_PHY_CTL, tmp); + + pci_read_config_dword(mvi->pdev, PCR_PHY_CTL2, &tmp); + tmp |= PCTL_PWR_ON; + tmp &= ~PCTL_OFF; + pci_write_config_dword(mvi->pdev, PCR_PHY_CTL2, tmp); + + mw32_f(CTL, cctl); + + mvs_phy_hacks(mvi); + + mw32(CMD_LIST_LO, mvi->slot_dma); + mw32(CMD_LIST_HI, (mvi->slot_dma >> 16) >> 16); + + mw32(RX_FIS_LO, mvi->rx_fis_dma); + mw32(RX_FIS_HI, (mvi->rx_fis_dma >> 16) >> 16); + + mw32(TX_CFG, MVS_TX_RING_SZ); + mw32(TX_LO, mvi->tx_dma); + mw32(TX_HI, (mvi->tx_dma >> 16) >> 16); + + mw32(RX_CFG, MVS_RX_RING_SZ); + mw32(RX_LO, mvi->rx_dma); + mw32(RX_HI, (mvi->rx_dma >> 16) >> 16); + + /* init and reset phys */ + for (i = 0; i < mvi->chip->n_phy; i++) { + /* FIXME: is this the correct dword order? */ + u32 lo = *((u32 *) &mvi->sas_addr[0]); + u32 hi = *((u32 *) &mvi->sas_addr[4]); + + /* set phy local SAS address */ + mvs_phy_write(mvi, i, PHYR_ADDR_LO, lo); + mvs_phy_write(mvi, i, PHYR_ADDR_HI, hi); + + /* reset phy */ + tmp = readl(regs + MVS_P0_SER_CTLSTAT + (i * 4)); + tmp |= PHY_RST; + writel(tmp, regs + MVS_P0_SER_CTLSTAT + (i * 4)); + } + + msleep(100); + + for (i = 0; i < mvi->chip->n_phy; i++) { + /* set phy int mask */ + writel(PHYEV_BROAD_CH | PHYEV_RDY_CH, + regs + MVS_P0_INT_MASK + (i * 8)); + + /* clear phy int status */ + tmp = readl(regs + MVS_P0_INT_STAT + (i * 8)); + writel(tmp, regs + MVS_P0_INT_STAT + (i * 8)); + } + + /* FIXME: update wide port bitmaps */ + + /* ladies and gentlemen, start your engines */ + mw32(TX_CFG, MVS_TX_RING_SZ | TX_EN); + mw32(RX_CFG, MVS_RX_RING_SZ | RX_EN); + mw32(PCS, PCS_SATA_RETRY | PCS_FIS_RX_EN | PCS_CMD_EN | + ((mvi->flags & MVF_MSI) ? PCS_SELF_CLEAR : 0)); + + /* re-enable interrupts globally */ + mw32(GBL_CTL, INT_EN); + + return 0; +} + +static void __devinit mvs_print_info(struct mvs_info *mvi) +{ + struct pci_dev *pdev = mvi->pdev; + static int printed_version; + + if (!printed_version++) + dev_printk(KERN_INFO, &pdev->dev, "version " DRV_VERSION "\n"); + + dev_printk(KERN_INFO, &pdev->dev, "%u phys, addr %llx\n", + mvi->chip->n_phy, SAS_ADDR(mvi->sas_addr)); +} + +static int __devinit mvs_pci_init(struct pci_dev *pdev, + const struct pci_device_id *ent) +{ + int rc; + struct mvs_info *mvi; + irq_handler_t irq_handler = mvs_interrupt; + + rc = pci_enable_device(pdev); + if (rc) + return rc; + + pci_set_master(pdev); + + rc = pci_request_regions(pdev, DRV_NAME); + if (rc) + goto err_out_disable; + + rc = pci_go_64(pdev); + if (rc) + goto err_out_regions; + + mvi = mvs_alloc(pdev, ent); + if (!mvi) { + rc = -ENOMEM; + goto err_out_regions; + } + + rc = mvs_hw_init(mvi); + if (rc) + goto err_out_mvi; + + if (!pci_enable_msi(pdev)) { + mvi->flags |= MVF_MSI; + irq_handler = mvs_msi_interrupt; + } + + rc = request_irq(pdev->irq, irq_handler, IRQF_SHARED, DRV_NAME, mvi); + if (rc) + goto err_out_msi; + + rc = scsi_add_host(mvi->shost, &pdev->dev); + if (rc) + goto err_out_irq; + + rc = sas_register_ha(&mvi->sas); + if (rc) + goto err_out_shost; + + pci_set_drvdata(pdev, mvi); + + mvs_print_info(mvi); + + scsi_scan_host(mvi->shost); + return 0; + +err_out_shost: + scsi_remove_host(mvi->shost); +err_out_irq: + free_irq(pdev->irq, mvi); +err_out_msi: + if (mvi->flags |= MVF_MSI) + pci_disable_msi(pdev); +err_out_mvi: + mvs_free(mvi); +err_out_regions: + pci_release_regions(pdev); +err_out_disable: + pci_disable_device(pdev); + return rc; +} + +static void __devexit mvs_pci_remove(struct pci_dev *pdev) +{ + struct mvs_info *mvi = pci_get_drvdata(pdev); + + pci_set_drvdata(pdev, NULL); + + sas_unregister_ha(&mvi->sas); + sas_remove_host(mvi->shost); + scsi_remove_host(mvi->shost); + + free_irq(pdev->irq, mvi); + if (mvi->flags & MVF_MSI) + pci_disable_msi(pdev); + mvs_free(mvi); + pci_release_regions(pdev); + pci_disable_device(pdev); +} + +static struct sas_domain_function_template mvs_transport_ops = { + .lldd_execute_task = mvs_task_exec, + .lldd_control_phy = mvs_phy_control, +}; + +static struct pci_device_id __devinitdata mvs_pci_table[] = { + { PCI_VDEVICE(MARVELL, 0x6320), chip_6320 }, + { PCI_VDEVICE(MARVELL, 0x6340), chip_6440 }, + { PCI_VDEVICE(MARVELL, 0x6440), chip_6440 }, + { PCI_VDEVICE(MARVELL, 0x6480), chip_6480 }, + + { } /* terminate list */ +}; + +static struct pci_driver mvs_pci_driver = { + .name = DRV_NAME, + .id_table = mvs_pci_table, + .probe = mvs_pci_init, + .remove = __devexit_p(mvs_pci_remove), +}; + +static int __init mvs_init(void) +{ + int rc; + + mvs_stt = sas_domain_attach_transport(&mvs_transport_ops); + if (!mvs_stt) + return -ENOMEM; + + rc = pci_register_driver(&mvs_pci_driver); + if (rc) + goto err_out; + + return 0; + +err_out: + sas_release_transport(mvs_stt); + return rc; +} + +static void __exit mvs_exit(void) +{ + pci_unregister_driver(&mvs_pci_driver); + sas_release_transport(mvs_stt); +} + +module_init(mvs_init); +module_exit(mvs_exit); + +MODULE_AUTHOR("Jeff Garzik "); +MODULE_DESCRIPTION("Marvell 88SE6440 SAS/SATA controller driver"); +MODULE_VERSION(DRV_VERSION); +MODULE_LICENSE("GPL"); +MODULE_DEVICE_TABLE(pci, mvs_pci_table); + From 8f261aaf9be5c1246013cf6a65b98586d24832a5 Mon Sep 17 00:00:00 2001 From: Ke Wei Date: Sat, 23 Feb 2008 21:15:27 +0800 Subject: [PATCH 24/25] [SCSI] mvsas: convert from rough draft to working driver Convert rough draft Marvell 6440 driver to a working driver. Added support for SAS and SATA devices, hotplug, wide port, and expanders. Signed-off-by: Ke Wei Signed-off-by: James Bottomley --- drivers/scsi/mvsas.c | 1788 ++++++++++++++++++++++++++++++++++-------- 1 file changed, 1472 insertions(+), 316 deletions(-) mode change 100644 => 100755 drivers/scsi/mvsas.c diff --git a/drivers/scsi/mvsas.c b/drivers/scsi/mvsas.c old mode 100644 new mode 100755 index 03638b9bb283..30e20e69715a --- a/drivers/scsi/mvsas.c +++ b/drivers/scsi/mvsas.c @@ -2,6 +2,7 @@ mvsas.c - Marvell 88SE6440 SAS/SATA support Copyright 2007 Red Hat, Inc. + Copyright 2008 Marvell. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as @@ -37,16 +38,35 @@ #include #include -#define DRV_NAME "mvsas" -#define DRV_VERSION "0.1" +#define DRV_NAME "mvsas" +#define DRV_VERSION "0.5" +#define _MV_DUMP 0 +#define MVS_DISABLE_NVRAM +#define MVS_DISABLE_MSI #define mr32(reg) readl(regs + MVS_##reg) #define mw32(reg,val) writel((val), regs + MVS_##reg) -#define mw32_f(reg,val) do { \ +#define mw32_f(reg,val) do { \ writel((val), regs + MVS_##reg); \ readl(regs + MVS_##reg); \ } while (0) +#define MVS_ID_NOT_MAPPED 0xff +#define MVS_CHIP_SLOT_SZ (1U << mvi->chip->slot_width) + +/* offset for D2H FIS in the Received FIS List Structure */ +#define SATA_RECEIVED_D2H_FIS(reg_set) \ + ((void *) mvi->rx_fis + 0x400 + 0x100 * reg_set + 0x40) +#define SATA_RECEIVED_PIO_FIS(reg_set) \ + ((void *) mvi->rx_fis + 0x400 + 0x100 * reg_set + 0x20) +#define UNASSOC_D2H_FIS(id) \ + ((void *) mvi->rx_fis + 0x100 * id) + +#define for_each_phy(__lseq_mask, __mc, __lseq, __rest) \ + for ((__mc) = (__lseq_mask), (__lseq) = 0; \ + (__mc) != 0 && __rest; \ + (++__lseq), (__mc) >>= 1) + /* driver compile-time configuration */ enum driver_configuration { MVS_TX_RING_SZ = 1024, /* TX ring size (12-bit) */ @@ -57,10 +77,12 @@ enum driver_configuration { MVS_SLOTS = 512, /* command slots */ MVS_SLOT_BUF_SZ = 8192, /* cmd tbl + IU + status + PRD */ MVS_SSP_CMD_SZ = 64, /* SSP command table buffer size */ - MVS_ATA_CMD_SZ = 128, /* SATA command table buffer size */ + MVS_ATA_CMD_SZ = 96, /* SATA command table buffer size */ MVS_OAF_SZ = 64, /* Open address frame buffer size */ MVS_RX_FIS_COUNT = 17, /* Optional rx'd FISs (max 17) */ + + MVS_QUEUE_SIZE = 30, /* Support Queue depth */ }; /* unchangeable hardware details */ @@ -89,7 +111,7 @@ enum hw_registers { MVS_GBL_CTL = 0x04, /* global control */ MVS_GBL_INT_STAT = 0x08, /* global irq status */ MVS_GBL_PI = 0x0C, /* ports implemented bitmask */ - MVS_GBL_PORT_TYPE = 0x00, /* port type */ + MVS_GBL_PORT_TYPE = 0xa0, /* port type */ MVS_CTL = 0x100, /* SAS/SATA port configuration */ MVS_PCS = 0x104, /* SAS/SATA port control/status */ @@ -102,24 +124,29 @@ enum hw_registers { MVS_TX_LO = 0x124, /* TX (delivery) ring addr */ MVS_TX_HI = 0x128, - MVS_RX_PROD_IDX = 0x12C, /* RX producer pointer */ - MVS_RX_CONS_IDX = 0x130, /* RX consumer pointer (RO) */ + MVS_TX_PROD_IDX = 0x12C, /* TX producer pointer */ + MVS_TX_CONS_IDX = 0x130, /* TX consumer pointer (RO) */ MVS_RX_CFG = 0x134, /* RX configuration */ MVS_RX_LO = 0x138, /* RX (completion) ring addr */ MVS_RX_HI = 0x13C, + MVS_RX_CONS_IDX = 0x140, /* RX consumer pointer (RO) */ MVS_INT_COAL = 0x148, /* Int coalescing config */ MVS_INT_COAL_TMOUT = 0x14C, /* Int coalescing timeout */ MVS_INT_STAT = 0x150, /* Central int status */ MVS_INT_MASK = 0x154, /* Central int enable */ MVS_INT_STAT_SRS = 0x158, /* SATA register set status */ + MVS_INT_MASK_SRS = 0x15C, /* ports 1-3 follow after this */ MVS_P0_INT_STAT = 0x160, /* port0 interrupt status */ MVS_P0_INT_MASK = 0x164, /* port0 interrupt mask */ + MVS_P4_INT_STAT = 0x200, /* Port 4 interrupt status */ + MVS_P4_INT_MASK = 0x204, /* Port 4 interrupt enable mask */ /* ports 1-3 follow after this */ MVS_P0_SER_CTLSTAT = 0x180, /* port0 serial control/status */ + MVS_P4_SER_CTLSTAT = 0x220, /* port4 serial control/status */ MVS_CMD_ADDR = 0x1B8, /* Command register port (addr) */ MVS_CMD_DATA = 0x1BC, /* Command register port (data) */ @@ -127,6 +154,14 @@ enum hw_registers { /* ports 1-3 follow after this */ MVS_P0_CFG_ADDR = 0x1C0, /* port0 phy register address */ MVS_P0_CFG_DATA = 0x1C4, /* port0 phy register data */ + MVS_P4_CFG_ADDR = 0x230, /* Port 4 config address */ + MVS_P4_CFG_DATA = 0x234, /* Port 4 config data */ + + /* ports 1-3 follow after this */ + MVS_P0_VSR_ADDR = 0x1E0, /* port0 VSR address */ + MVS_P0_VSR_DATA = 0x1E4, /* port0 VSR data */ + MVS_P4_VSR_ADDR = 0x250, /* port 4 VSR addr */ + MVS_P4_VSR_DATA = 0x254, /* port 4 VSR data */ }; enum hw_register_bits { @@ -140,12 +175,35 @@ enum hw_register_bits { /* MVS_GBL_PORT_TYPE */ /* shl for ports 1-3 */ SATA_TARGET = (1U << 16), /* port0 SATA target enable */ - AUTO_DET = (1U << 8), /* port0 SAS/SATA autodetect */ - SAS_MODE = (1U << 0), /* port0 SAS(1), SATA(0) mode */ - /* SAS_MODE value may be - * dictated (in hw) by values - * of SATA_TARGET & AUTO_DET - */ + MODE_AUTO_DET_PORT7 = (1U << 15), /* port0 SAS/SATA autodetect */ + MODE_AUTO_DET_PORT6 = (1U << 14), + MODE_AUTO_DET_PORT5 = (1U << 13), + MODE_AUTO_DET_PORT4 = (1U << 12), + MODE_AUTO_DET_PORT3 = (1U << 11), + MODE_AUTO_DET_PORT2 = (1U << 10), + MODE_AUTO_DET_PORT1 = (1U << 9), + MODE_AUTO_DET_PORT0 = (1U << 8), + MODE_AUTO_DET_EN = MODE_AUTO_DET_PORT0 | MODE_AUTO_DET_PORT1 | + MODE_AUTO_DET_PORT2 | MODE_AUTO_DET_PORT3 | + MODE_AUTO_DET_PORT4 | MODE_AUTO_DET_PORT5 | + MODE_AUTO_DET_PORT6 | MODE_AUTO_DET_PORT7, + MODE_SAS_PORT7_MASK = (1U << 7), /* port0 SAS(1), SATA(0) mode */ + MODE_SAS_PORT6_MASK = (1U << 6), + MODE_SAS_PORT5_MASK = (1U << 5), + MODE_SAS_PORT4_MASK = (1U << 4), + MODE_SAS_PORT3_MASK = (1U << 3), + MODE_SAS_PORT2_MASK = (1U << 2), + MODE_SAS_PORT1_MASK = (1U << 1), + MODE_SAS_PORT0_MASK = (1U << 0), + MODE_SAS_SATA = MODE_SAS_PORT0_MASK | MODE_SAS_PORT1_MASK | + MODE_SAS_PORT2_MASK | MODE_SAS_PORT3_MASK | + MODE_SAS_PORT4_MASK | MODE_SAS_PORT5_MASK | + MODE_SAS_PORT6_MASK | MODE_SAS_PORT7_MASK, + + /* SAS_MODE value may be + * dictated (in hw) by values + * of SATA_TARGET & AUTO_DET + */ /* MVS_TX_CFG */ TX_EN = (1U << 16), /* Enable TX */ @@ -167,12 +225,14 @@ enum hw_register_bits { CINT_MEM = (1U << 26), /* int mem parity err */ CINT_I2C_SLAVE = (1U << 25), /* slave I2C event */ CINT_SRS = (1U << 3), /* SRS event */ - CINT_CI_STOP = (1U << 10), /* cmd issue stopped */ + CINT_CI_STOP = (1U << 1), /* cmd issue stopped */ CINT_DONE = (1U << 0), /* cmd completion */ /* shl for ports 1-3 */ CINT_PORT_STOPPED = (1U << 16), /* port0 stopped */ CINT_PORT = (1U << 8), /* port0 event */ + CINT_PORT_MASK_OFFSET = 8, + CINT_PORT_MASK = (0xFF << CINT_PORT_MASK_OFFSET), /* TX (delivery) ring bits */ TXQ_CMD_SHIFT = 29, @@ -239,8 +299,15 @@ enum hw_register_bits { PHY_BCAST_CHG = (1U << 2), /* broadcast(change) notif */ PHY_RST_HARD = (1U << 1), /* hard reset + phy reset */ PHY_RST = (1U << 0), /* phy reset */ + PHY_MIN_SPP_PHYS_LINK_RATE_MASK = (0xF << 8), + PHY_MAX_SPP_PHYS_LINK_RATE_MASK = (0xF << 12), + PHY_NEG_SPP_PHYS_LINK_RATE_MASK_OFFSET = (16), + PHY_NEG_SPP_PHYS_LINK_RATE_MASK = + (0xF << PHY_NEG_SPP_PHYS_LINK_RATE_MASK_OFFSET), + PHY_READY_MASK = (1U << 20), /* MVS_Px_INT_STAT, MVS_Px_INT_MASK (per-phy events) */ + PHYEV_DEC_ERR = (1U << 24), /* Phy Decoding Error */ PHYEV_UNASSOC_FIS = (1U << 19), /* unassociated FIS rx'd */ PHYEV_AN = (1U << 18), /* SATA async notification */ PHYEV_BIST_ACT = (1U << 17), /* BIST activate FIS */ @@ -260,13 +327,37 @@ enum hw_register_bits { PHYEV_RDY_CH = (1U << 0), /* phy ready changed state */ /* MVS_PCS */ + PCS_EN_SATA_REG_SHIFT = (16), /* Enable SATA Register Set */ + PCS_EN_PORT_XMT_SHIFT = (12), /* Enable Port Transmit */ + PCS_EN_PORT_XMT_SHIFT2 = (8), /* For 6480 */ PCS_SATA_RETRY = (1U << 8), /* retry ctl FIS on R_ERR */ PCS_RSP_RX_EN = (1U << 7), /* raw response rx */ PCS_SELF_CLEAR = (1U << 5), /* self-clearing int mode */ PCS_FIS_RX_EN = (1U << 4), /* FIS rx enable */ PCS_CMD_STOP_ERR = (1U << 3), /* cmd stop-on-err enable */ - PCS_CMD_RST = (1U << 2), /* reset cmd issue */ + PCS_CMD_RST = (1U << 1), /* reset cmd issue */ PCS_CMD_EN = (1U << 0), /* enable cmd issue */ + + /* Port n Attached Device Info */ + PORT_DEV_SSP_TRGT = (1U << 19), + PORT_DEV_SMP_TRGT = (1U << 18), + PORT_DEV_STP_TRGT = (1U << 17), + PORT_DEV_SSP_INIT = (1U << 11), + PORT_DEV_SMP_INIT = (1U << 10), + PORT_DEV_STP_INIT = (1U << 9), + PORT_PHY_ID_MASK = (0xFFU << 24), + PORT_DEV_TRGT_MASK = (0x7U << 17), + PORT_DEV_INIT_MASK = (0x7U << 9), + PORT_DEV_TYPE_MASK = (0x7U << 0), + + /* Port n PHY Status */ + PHY_RDY = (1U << 2), + PHY_DW_SYNC = (1U << 1), + PHY_OOB_DTCTD = (1U << 0), + + /* VSR */ + /* PHYMODE 6 (CDB) */ + PHY_MODE6_DTL_SPEED = (1U << 27), }; enum mvs_info_flags { @@ -329,33 +420,60 @@ enum sas_cmd_port_registers { /* SAS/SATA configuration port registers, aka phy registers */ enum sas_sata_config_port_regs { - PHYR_IDENTIFY = 0x0, /* info for IDENTIFY frame */ - PHYR_ADDR_LO = 0x4, /* my SAS address (low) */ - PHYR_ADDR_HI = 0x8, /* my SAS address (high) */ - PHYR_ATT_DEV_INFO = 0xC, /* attached device info */ + PHYR_IDENTIFY = 0x00, /* info for IDENTIFY frame */ + PHYR_ADDR_LO = 0x04, /* my SAS address (low) */ + PHYR_ADDR_HI = 0x08, /* my SAS address (high) */ + PHYR_ATT_DEV_INFO = 0x0C, /* attached device info */ PHYR_ATT_ADDR_LO = 0x10, /* attached dev SAS addr (low) */ PHYR_ATT_ADDR_HI = 0x14, /* attached dev SAS addr (high) */ PHYR_SATA_CTL = 0x18, /* SATA control */ PHYR_PHY_STAT = 0x1C, /* PHY status */ + PHYR_SATA_SIG0 = 0x20, /*port SATA signature FIS(Byte 0-3) */ + PHYR_SATA_SIG1 = 0x24, /*port SATA signature FIS(Byte 4-7) */ + PHYR_SATA_SIG2 = 0x28, /*port SATA signature FIS(Byte 8-11) */ + PHYR_SATA_SIG3 = 0x2c, /*port SATA signature FIS(Byte 12-15) */ + PHYR_R_ERR_COUNT = 0x30, /* port R_ERR count register */ + PHYR_CRC_ERR_COUNT = 0x34, /* port CRC error count register */ PHYR_WIDE_PORT = 0x38, /* wide port participating */ PHYR_CURRENT0 = 0x80, /* current connection info 0 */ PHYR_CURRENT1 = 0x84, /* current connection info 1 */ PHYR_CURRENT2 = 0x88, /* current connection info 2 */ }; +/* SAS/SATA Vendor Specific Port Registers */ +enum sas_sata_vsp_regs { + VSR_PHY_STAT = 0x00, /* Phy Status */ + VSR_PHY_MODE1 = 0x01, /* phy tx */ + VSR_PHY_MODE2 = 0x02, /* tx scc */ + VSR_PHY_MODE3 = 0x03, /* pll */ + VSR_PHY_MODE4 = 0x04, /* VCO */ + VSR_PHY_MODE5 = 0x05, /* Rx */ + VSR_PHY_MODE6 = 0x06, /* CDR */ + VSR_PHY_MODE7 = 0x07, /* Impedance */ + VSR_PHY_MODE8 = 0x08, /* Voltage */ + VSR_PHY_MODE9 = 0x09, /* Test */ + VSR_PHY_MODE10 = 0x0A, /* Power */ + VSR_PHY_MODE11 = 0x0B, /* Phy Mode */ + VSR_PHY_VS0 = 0x0C, /* Vednor Specific 0 */ + VSR_PHY_VS1 = 0x0D, /* Vednor Specific 1 */ +}; + enum pci_cfg_registers { - PCR_PHY_CTL = 0x40, - PCR_PHY_CTL2 = 0x90, + PCR_PHY_CTL = 0x40, + PCR_PHY_CTL2 = 0x90, + PCR_DEV_CTRL = 0xE8, }; enum pci_cfg_register_bits { - PCTL_PWR_ON = (0xFU << 24), - PCTL_OFF = (0xFU << 12), + PCTL_PWR_ON = (0xFU << 24), + PCTL_OFF = (0xFU << 12), + PRD_REQ_SIZE = (0x4000), + PRD_REQ_MASK = (0x00007000), }; enum nvram_layout_offsets { - NVR_SIG = 0x00, /* 0xAA, 0x55 */ - NVR_SAS_ADDR = 0x02, /* 8-byte SAS address */ + NVR_SIG = 0x00, /* 0xAA, 0x55 */ + NVR_SAS_ADDR = 0x02, /* 8-byte SAS address */ }; enum chip_flavors { @@ -364,10 +482,41 @@ enum chip_flavors { chip_6480, }; +enum port_type { + PORT_TYPE_SAS = (1L << 1), + PORT_TYPE_SATA = (1L << 0), +}; + +/* Command Table Format */ +enum ct_format { + /* SSP */ + SSP_F_H = 0x00, + SSP_F_IU = 0x18, + SSP_F_MAX = 0x4D, + /* STP */ + STP_CMD_FIS = 0x00, + STP_ATAPI_CMD = 0x40, + STP_F_MAX = 0x10, + /* SMP */ + SMP_F_T = 0x00, + SMP_F_DEP = 0x01, + SMP_F_MAX = 0x101, +}; + +enum status_buffer { + SB_EIR_OFF = 0x00, /* Error Information Record */ + SB_RFB_OFF = 0x08, /* Response Frame Buffer */ + SB_RFB_MAX = 0x400, /* RFB size*/ +}; + +enum error_info_rec { + CMD_ISS_STPD = (1U << 31), /* Cmd Issue Stopped */ +}; + struct mvs_chip_info { - unsigned int n_phy; - unsigned int srs_sz; - unsigned int slot_width; + u32 n_phy; + u32 srs_sz; + u32 slot_width; }; struct mvs_err_info { @@ -395,26 +544,43 @@ struct mvs_cmd_hdr { struct mvs_slot_info { struct sas_task *task; - unsigned int n_elem; + u32 n_elem; + u32 tx; /* DMA buffer for storing cmd tbl, open addr frame, status buffer, * and PRD table */ void *buf; dma_addr_t buf_dma; +#if _MV_DUMP + u32 cmd_size; +#endif void *response; }; struct mvs_port { struct asd_sas_port sas_port; + u8 port_attached; + u8 taskfileset; + u8 wide_port_phymap; }; struct mvs_phy { struct mvs_port *port; struct asd_sas_phy sas_phy; - - u8 frame_rcvd[24 + 1024]; + struct sas_identify identify; + struct scsi_device *sdev; + u64 dev_sas_addr; + u64 att_dev_sas_addr; + u32 att_dev_info; + u32 dev_info; + u32 phy_type; + u32 phy_status; + u32 irq_status; + u32 frame_rcvd_size; + u8 frame_rcvd[32]; + u8 phy_attached; }; struct mvs_info { @@ -440,24 +606,62 @@ struct mvs_info { __le32 *rx_fis; /* RX'd FIS area */ dma_addr_t rx_fis_dma; - struct mvs_cmd_hdr *slot; /* DMA command header slots */ + struct mvs_cmd_hdr *slot; /* DMA command header slots */ dma_addr_t slot_dma; const struct mvs_chip_info *chip; - /* further per-slot information */ + unsigned long tags[MVS_SLOTS]; struct mvs_slot_info slot_info[MVS_SLOTS]; - unsigned long tags[(MVS_SLOTS / sizeof(unsigned long)) + 1]; - + /* further per-slot information */ struct mvs_phy phy[MVS_MAX_PHYS]; struct mvs_port port[MVS_MAX_PHYS]; + + u32 can_queue; /* per adapter */ + u32 tag_out; /*Get*/ + u32 tag_in; /*Give*/ }; +struct mvs_queue_task { + struct list_head list; + + void *uldd_task; +}; + +static int mvs_phy_control(struct asd_sas_phy *sas_phy, enum phy_func func, + void *funcdata); +static u32 mvs_read_phy_ctl(struct mvs_info *mvi, u32 port); +static void mvs_write_phy_ctl(struct mvs_info *mvi, u32 port, u32 val); +static u32 mvs_read_port(struct mvs_info *mvi, u32 off, u32 off2, u32 port); +static void mvs_write_port(struct mvs_info *mvi, u32 off, u32 off2, + u32 port, u32 val); +static u32 mvs_read_port_cfg_data(struct mvs_info *mvi, u32 port); +static void mvs_write_port_cfg_data(struct mvs_info *mvi, u32 port, u32 val); +static void mvs_write_port_cfg_addr(struct mvs_info *mvi, u32 port, u32 addr); +static u32 mvs_read_port_vsr_data(struct mvs_info *mvi, u32 port); +static void mvs_write_port_vsr_data(struct mvs_info *mvi, u32 port, u32 val); +static void mvs_write_port_vsr_addr(struct mvs_info *mvi, u32 port, u32 addr); +static u32 mvs_read_port_irq_stat(struct mvs_info *mvi, u32 port); +static void mvs_write_port_irq_stat(struct mvs_info *mvi, u32 port, u32 val); +static void mvs_write_port_irq_mask(struct mvs_info *mvi, u32 port, u32 val); +static u32 mvs_read_port_irq_mask(struct mvs_info *mvi, u32 port); + +static u32 mvs_is_phy_ready(struct mvs_info *mvi, int i); +static void mvs_detect_porttype(struct mvs_info *mvi, int i); +static void mvs_update_phyinfo(struct mvs_info *mvi, int i, int get_st); +static void mvs_free_reg_set(struct mvs_info *mvi, struct mvs_port *port); +static u8 mvs_assign_reg_set(struct mvs_info *mvi, struct mvs_port *port); +static u32 mvs_is_sig_fis_received(u32 irq_status); + +static int mvs_scan_finished(struct Scsi_Host *, unsigned long); +static void mvs_scan_start(struct Scsi_Host *); +static int mvs_sas_slave_alloc(struct scsi_device *scsi_dev); + static struct scsi_transport_template *mvs_stt; static const struct mvs_chip_info mvs_chips[] = { - [chip_6320] = { 2, 16, 9 }, - [chip_6440] = { 4, 16, 9 }, + [chip_6320] = { 2, 16, 9 }, + [chip_6440] = { 4, 16, 9 }, [chip_6480] = { 8, 32, 10 }, }; @@ -468,6 +672,8 @@ static struct scsi_host_template mvs_sht = { .target_alloc = sas_target_alloc, .slave_configure = sas_slave_configure, .slave_destroy = sas_slave_destroy, + .scan_finished = mvs_scan_finished, + .scan_start = mvs_scan_start, .change_queue_depth = sas_change_queue_depth, .change_queue_type = sas_change_queue_type, .bios_param = sas_bios_param, @@ -477,14 +683,154 @@ static struct scsi_host_template mvs_sht = { .sg_tablesize = SG_ALL, .max_sectors = SCSI_DEFAULT_MAX_SECTORS, .use_clustering = ENABLE_CLUSTERING, - .eh_device_reset_handler= sas_eh_device_reset_handler, + .eh_device_reset_handler = sas_eh_device_reset_handler, .eh_bus_reset_handler = sas_eh_bus_reset_handler, - .slave_alloc = sas_slave_alloc, + .slave_alloc = mvs_sas_slave_alloc, .target_destroy = sas_target_destroy, .ioctl = sas_ioctl, }; -static void mvs_int_rx(struct mvs_info *mvi, bool self_clear); +static void mvs_hexdump(u32 size, u8 *data, u32 baseaddr) +{ + u32 i; + u32 run; + u32 offset; + + offset = 0; + while (size) { + printk("%08X : ", baseaddr + offset); + if (size >= 16) + run = 16; + else + run = size; + size -= run; + for (i = 0; i < 16; i++) { + if (i < run) + printk("%02X ", (u32)data[i]); + else + printk(" "); + } + printk(": "); + for (i = 0; i < run; i++) + printk("%c", isalnum(data[i]) ? data[i] : '.'); + printk("\n"); + data = &data[16]; + offset += run; + } + printk("\n"); +} + +static void mvs_hba_sb_dump(struct mvs_info *mvi, u32 tag, + enum sas_protocol proto) +{ +#if _MV_DUMP + u32 offset; + struct pci_dev *pdev = mvi->pdev; + struct mvs_slot_info *slot = &mvi->slot_info[tag]; + + offset = slot->cmd_size + MVS_OAF_SZ + + sizeof(struct mvs_prd) * slot->n_elem; + dev_printk(KERN_DEBUG, &pdev->dev, "+---->Status buffer[%d] :\n", + tag); + mvs_hexdump(32, (u8 *) slot->response, + (u32) slot->buf_dma + offset); +#endif +} + +static void mvs_hba_memory_dump(struct mvs_info *mvi, u32 tag, + enum sas_protocol proto) +{ +#if _MV_DUMP + u32 sz, w_ptr, r_ptr; + u64 addr; + void __iomem *regs = mvi->regs; + struct pci_dev *pdev = mvi->pdev; + struct mvs_slot_info *slot = &mvi->slot_info[tag]; + + /*Delivery Queue */ + sz = mr32(TX_CFG) & TX_RING_SZ_MASK; + w_ptr = mr32(TX_PROD_IDX) & TX_RING_SZ_MASK; + r_ptr = mr32(TX_CONS_IDX) & TX_RING_SZ_MASK; + addr = mr32(TX_HI) << 16 << 16 | mr32(TX_LO); + dev_printk(KERN_DEBUG, &pdev->dev, + "Delivery Queue Size=%04d , WRT_PTR=%04X , RD_PTR=%04X\n", + sz, w_ptr, r_ptr); + dev_printk(KERN_DEBUG, &pdev->dev, + "Delivery Queue Base Address=0x%llX (PA)" + "(tx_dma=0x%llX), Entry=%04d\n", + addr, mvi->tx_dma, w_ptr); + mvs_hexdump(sizeof(u32), (u8 *)(&mvi->tx[mvi->tx_prod]), + (u32) mvi->tx_dma + sizeof(u32) * w_ptr); + /*Command List */ + addr = mr32(CMD_LIST_HI) << 16 << 16 | mr32(CMD_LIST_LO); + dev_printk(KERN_DEBUG, &pdev->dev, + "Command List Base Address=0x%llX (PA)" + "(slot_dma=0x%llX), Header=%03d\n", + addr, mvi->slot_dma, tag); + dev_printk(KERN_DEBUG, &pdev->dev, "Command Header[%03d]:\n", tag); + /*mvs_cmd_hdr */ + mvs_hexdump(sizeof(struct mvs_cmd_hdr), (u8 *)(&mvi->slot[tag]), + (u32) mvi->slot_dma + tag * sizeof(struct mvs_cmd_hdr)); + /*1.command table area */ + dev_printk(KERN_DEBUG, &pdev->dev, "+---->Command Table :\n"); + mvs_hexdump(slot->cmd_size, (u8 *) slot->buf, (u32) slot->buf_dma); + /*2.open address frame area */ + dev_printk(KERN_DEBUG, &pdev->dev, "+---->Open Address Frame :\n"); + mvs_hexdump(MVS_OAF_SZ, (u8 *) slot->buf + slot->cmd_size, + (u32) slot->buf_dma + slot->cmd_size); + /*3.status buffer */ + mvs_hba_sb_dump(mvi, tag, proto); + /*4.PRD table */ + dev_printk(KERN_DEBUG, &pdev->dev, "+---->PRD table :\n"); + mvs_hexdump(sizeof(struct mvs_prd) * slot->n_elem, + (u8 *) slot->buf + slot->cmd_size + MVS_OAF_SZ, + (u32) slot->buf_dma + slot->cmd_size + MVS_OAF_SZ); +#endif +} + +static void mvs_hba_cq_dump(struct mvs_info *mvi) +{ +#if _MV_DUMP + u64 addr; + void __iomem *regs = mvi->regs; + struct pci_dev *pdev = mvi->pdev; + u32 entry = mvi->rx_cons + 1; + u32 rx_desc = le32_to_cpu(mvi->rx[entry]); + + /*Completion Queue */ + addr = mr32(RX_HI) << 16 << 16 | mr32(RX_LO); + dev_printk(KERN_DEBUG, &pdev->dev, "Completion Task = 0x%08X\n", + (u32) mvi->slot_info[rx_desc & RXQ_SLOT_MASK].task); + dev_printk(KERN_DEBUG, &pdev->dev, + "Completion List Base Address=0x%llX (PA), " + "CQ_Entry=%04d, CQ_WP=0x%08X\n", + addr, entry - 1, mvi->rx[0]); + mvs_hexdump(sizeof(u32), (u8 *)(&rx_desc), + mvi->rx_dma + sizeof(u32) * entry); +#endif +} + +static void mvs_hba_interrupt_enable(struct mvs_info *mvi) +{ + void __iomem *regs = mvi->regs; + u32 tmp; + + tmp = mr32(GBL_CTL); + + mw32(GBL_CTL, tmp | INT_EN); +} + +static void mvs_hba_interrupt_disable(struct mvs_info *mvi) +{ + void __iomem *regs = mvi->regs; + u32 tmp; + + tmp = mr32(GBL_CTL); + + mw32(GBL_CTL, tmp & ~INT_EN); +} + +static int mvs_int_rx(struct mvs_info *mvi, bool self_clear); /* move to PCI layer or libata core? */ static int pci_go_64(struct pci_dev *pdev) @@ -519,39 +865,38 @@ static int pci_go_64(struct pci_dev *pdev) return rc; } -static void mvs_tag_clear(struct mvs_info *mvi, unsigned int tag) +static void mvs_tag_clear(struct mvs_info *mvi, u32 tag) { - mvi->tags[tag / sizeof(unsigned long)] &= - ~(1UL << (tag % sizeof(unsigned long))); + mvi->tag_in = (mvi->tag_in + 1) & (MVS_SLOTS - 1); + mvi->tags[mvi->tag_in] = tag; } -static void mvs_tag_set(struct mvs_info *mvi, unsigned int tag) +static void mvs_tag_free(struct mvs_info *mvi, u32 tag) { - mvi->tags[tag / sizeof(unsigned long)] |= - (1UL << (tag % sizeof(unsigned long))); + mvi->tag_out = (mvi->tag_out - 1) & (MVS_SLOTS - 1); } -static bool mvs_tag_test(struct mvs_info *mvi, unsigned int tag) +static int mvs_tag_alloc(struct mvs_info *mvi, u32 *tag_out) { - return mvi->tags[tag / sizeof(unsigned long)] & - (1UL << (tag % sizeof(unsigned long))); -} - -static int mvs_tag_alloc(struct mvs_info *mvi, unsigned int *tag_out) -{ - unsigned int i; - - for (i = 0; i < MVS_SLOTS; i++) - if (!mvs_tag_test(mvi, i)) { - mvs_tag_set(mvi, i); - *tag_out = i; - return 0; - } - + if (mvi->tag_out != mvi->tag_in) { + *tag_out = mvi->tags[mvi->tag_out]; + mvi->tag_out = (mvi->tag_out + 1) & (MVS_SLOTS - 1); + return 0; + } return -EBUSY; } -static int mvs_eep_read(void __iomem *regs, unsigned int addr, u32 *data) +static void mvs_tag_init(struct mvs_info *mvi) +{ + int i; + for (i = 0; i < MVS_SLOTS; ++i) + mvi->tags[i] = i; + mvi->tag_out = 0; + mvi->tag_in = MVS_SLOTS - 1; +} + +#ifndef MVS_DISABLE_NVRAM +static int mvs_eep_read(void __iomem *regs, u32 addr, u32 *data) { int timeout = 1000; @@ -573,10 +918,10 @@ static int mvs_eep_read(void __iomem *regs, unsigned int addr, u32 *data) return -EBUSY; } -static int mvs_eep_read_buf(void __iomem *regs, unsigned int addr, - void *buf, unsigned int buflen) +static int mvs_eep_read_buf(void __iomem *regs, u32 addr, + void *buf, u32 buflen) { - unsigned int addr_end, tmp_addr, i, j; + u32 addr_end, tmp_addr, i, j; u32 tmp = 0; int rc; u8 *tmp8, *buf8 = buf; @@ -592,7 +937,7 @@ static int mvs_eep_read_buf(void __iomem *regs, unsigned int addr, if (rc) return rc; - tmp8 = (u8 *) &tmp; + tmp8 = (u8 *)&tmp; for (i = j; i < 4; i++) *buf8++ = tmp8[i]; @@ -613,7 +958,7 @@ static int mvs_eep_read_buf(void __iomem *regs, unsigned int addr, if (rc) return rc; - tmp8 = (u8 *) &tmp; + tmp8 = (u8 *)&tmp; j = addr_end - tmp_addr; for (i = 0; i < j; i++) *buf8++ = tmp8[i]; @@ -623,13 +968,15 @@ static int mvs_eep_read_buf(void __iomem *regs, unsigned int addr, return 0; } +#endif -static int mvs_nvram_read(struct mvs_info *mvi, unsigned int addr, - void *buf, unsigned int buflen) +static int mvs_nvram_read(struct mvs_info *mvi, u32 addr, + void *buf, u32 buflen) { +#ifndef MVS_DISABLE_NVRAM void __iomem *regs = mvi->regs; int rc, i; - unsigned int sum; + u32 sum; u8 hdr[2], *tmp; const char *msg; @@ -644,16 +991,17 @@ static int mvs_nvram_read(struct mvs_info *mvi, unsigned int addr, goto err_out; } - if (hdr[0] != 0x5A) { /* entry id */ + if (hdr[0] != 0x5A) { + /* entry id */ msg = "invalid nvram entry id"; rc = -ENOENT; goto err_out; } tmp = buf; - sum = ((unsigned int)hdr[0]) + ((unsigned int)hdr[1]); + sum = ((u32)hdr[0]) + ((u32)hdr[1]); for (i = 0; i < buflen; i++) - sum += ((unsigned int)tmp[i]); + sum += ((u32)tmp[i]); if (sum) { msg = "nvram checksum failure"; @@ -665,12 +1013,123 @@ static int mvs_nvram_read(struct mvs_info *mvi, unsigned int addr, err_out: dev_printk(KERN_ERR, &mvi->pdev->dev, "%s", msg); + return rc; +#else + /* FIXME , For SAS target mode */ + memcpy(buf, "\x00\x00\xab\x11\x30\x04\x05\x50", 8); + return 0; +#endif +} + +static void mvs_bytes_dmaed(struct mvs_info *mvi, int i) +{ + struct mvs_phy *phy = &mvi->phy[i]; + + if (!phy->phy_attached) + return; + + if (phy->phy_type & PORT_TYPE_SAS) { + struct sas_identify_frame *id; + + id = (struct sas_identify_frame *)phy->frame_rcvd; + id->dev_type = phy->identify.device_type; + id->initiator_bits = SAS_PROTOCOL_ALL; + id->target_bits = phy->identify.target_port_protocols; + } else if (phy->phy_type & PORT_TYPE_SATA) { + /* TODO */ + } + mvi->sas.sas_phy[i]->frame_rcvd_size = phy->frame_rcvd_size; + mvi->sas.notify_port_event(mvi->sas.sas_phy[i], + PORTE_BYTES_DMAED); +} + +static int mvs_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 void mvs_scan_start(struct Scsi_Host *shost) +{ + int i; + struct mvs_info *mvi = SHOST_TO_SAS_HA(shost)->lldd_ha; + + for (i = 0; i < mvi->chip->n_phy; ++i) { + mvs_bytes_dmaed(mvi, i); + } +} + +static int mvs_sas_slave_alloc(struct scsi_device *scsi_dev) +{ + int rc; + + rc = sas_slave_alloc(scsi_dev); + return rc; } static void mvs_int_port(struct mvs_info *mvi, int port_no, u32 events) { - /* FIXME */ + struct pci_dev *pdev = mvi->pdev; + struct sas_ha_struct *sas_ha = &mvi->sas; + struct mvs_phy *phy = &mvi->phy[port_no]; + struct asd_sas_phy *sas_phy = &phy->sas_phy; + + phy->irq_status = mvs_read_port_irq_stat(mvi, port_no); + /* + * events is port event now , + * we need check the interrupt status which belongs to per port. + */ + dev_printk(KERN_DEBUG, &pdev->dev, + "Port %d Event = %X\n", + port_no, phy->irq_status); + + if (phy->irq_status & (PHYEV_POOF | PHYEV_DEC_ERR)) { + if (!mvs_is_phy_ready(mvi, port_no)) { + sas_phy_disconnected(sas_phy); + sas_ha->notify_phy_event(sas_phy, PHYE_LOSS_OF_SIGNAL); + } else + mvs_phy_control(sas_phy, PHY_FUNC_LINK_RESET, NULL); + } + if (!(phy->irq_status & PHYEV_DEC_ERR)) { + if (phy->irq_status & PHYEV_COMWAKE) { + u32 tmp = mvs_read_port_irq_mask(mvi, port_no); + mvs_write_port_irq_mask(mvi, port_no, + tmp | PHYEV_SIG_FIS); + } + if (phy->irq_status & (PHYEV_SIG_FIS | PHYEV_ID_DONE)) { + phy->phy_status = mvs_is_phy_ready(mvi, port_no); + if (phy->phy_status) { + mvs_detect_porttype(mvi, port_no); + + if (phy->phy_type & PORT_TYPE_SATA) { + u32 tmp = mvs_read_port_irq_mask(mvi, + port_no); + tmp &= ~PHYEV_SIG_FIS; + mvs_write_port_irq_mask(mvi, + port_no, tmp); + } + + mvs_update_phyinfo(mvi, port_no, 0); + sas_ha->notify_phy_event(sas_phy, + PHYE_OOB_DONE); + mvs_bytes_dmaed(mvi, port_no); + } else { + dev_printk(KERN_DEBUG, &pdev->dev, + "plugin interrupt but phy is gone\n"); + mvs_phy_control(sas_phy, PHY_FUNC_LINK_RESET, + NULL); + } + } else if (phy->irq_status & PHYEV_BROAD_CH) + sas_ha->notify_port_event(sas_phy, + PORTE_BROADCAST_RCVD); + } + mvs_write_port_irq_stat(mvi, port_no, phy->irq_status); } static void mvs_int_sata(struct mvs_info *mvi) @@ -679,11 +1138,12 @@ static void mvs_int_sata(struct mvs_info *mvi) } static void mvs_slot_free(struct mvs_info *mvi, struct sas_task *task, - struct mvs_slot_info *slot, unsigned int slot_idx) + struct mvs_slot_info *slot, u32 slot_idx) { - if (slot->n_elem) - pci_unmap_sg(mvi->pdev, task->scatter, - slot->n_elem, task->data_dir); + if (!sas_protocol_ata(task->task_proto)) + if (slot->n_elem) + pci_unmap_sg(mvi->pdev, task->scatter, + slot->n_elem, task->data_dir); switch (task->task_proto) { case SAS_PROTOCOL_SMP: @@ -701,40 +1161,60 @@ static void mvs_slot_free(struct mvs_info *mvi, struct sas_task *task, break; } + slot->task = NULL; mvs_tag_clear(mvi, slot_idx); } static void mvs_slot_err(struct mvs_info *mvi, struct sas_task *task, - unsigned int slot_idx) + u32 slot_idx) { - /* FIXME */ + struct mvs_slot_info *slot = &mvi->slot_info[slot_idx]; + u64 err_dw0 = *(u32 *) slot->response; + void __iomem *regs = mvi->regs; + u32 tmp; + + if (err_dw0 & CMD_ISS_STPD) + if (sas_protocol_ata(task->task_proto)) { + tmp = mr32(INT_STAT_SRS); + mw32(INT_STAT_SRS, tmp & 0xFFFF); + } + + mvs_hba_sb_dump(mvi, slot_idx, task->task_proto); } -static void mvs_slot_complete(struct mvs_info *mvi, u32 rx_desc) +static int mvs_slot_complete(struct mvs_info *mvi, u32 rx_desc) { - unsigned int slot_idx = rx_desc & RXQ_SLOT_MASK; + u32 slot_idx = rx_desc & RXQ_SLOT_MASK; struct mvs_slot_info *slot = &mvi->slot_info[slot_idx]; struct sas_task *task = slot->task; struct task_status_struct *tstat = &task->task_status; + struct mvs_port *port = &mvi->port[task->dev->port->id]; bool aborted; + void *to; spin_lock(&task->task_state_lock); aborted = task->task_state_flags & SAS_TASK_STATE_ABORTED; if (!aborted) { task->task_state_flags &= - ~(SAS_TASK_STATE_PENDING | SAS_TASK_AT_INITIATOR); + ~(SAS_TASK_STATE_PENDING | SAS_TASK_AT_INITIATOR); task->task_state_flags |= SAS_TASK_STATE_DONE; } spin_unlock(&task->task_state_lock); if (aborted) - return; + return -1; memset(tstat, 0, sizeof(*tstat)); tstat->resp = SAS_TASK_COMPLETE; + + if (unlikely(!port->port_attached)) { + tstat->stat = SAS_PHY_DOWN; + goto out; + } + /* error info record present */ - if (rx_desc & RXQ_ERR) { + if ((rx_desc & RXQ_ERR) && (*(u64 *) slot->response)) { tstat->stat = SAM_CHECK_COND; mvs_slot_err(mvi, task, slot_idx); goto out; @@ -743,13 +1223,14 @@ static void mvs_slot_complete(struct mvs_info *mvi, u32 rx_desc) switch (task->task_proto) { case SAS_PROTOCOL_SSP: /* hw says status == 0, datapres == 0 */ - if (rx_desc & RXQ_GOOD) + if (rx_desc & RXQ_GOOD) { tstat->stat = SAM_GOOD; - + tstat->resp = SAS_TASK_COMPLETE; + } /* response frame present */ else if (rx_desc & RXQ_RSP) { struct ssp_response_iu *iu = - slot->response + sizeof(struct mvs_err_info); + slot->response + sizeof(struct mvs_err_info); sas_ssp_task_response(&mvi->pdev->dev, task, iu); } @@ -758,20 +1239,37 @@ static void mvs_slot_complete(struct mvs_info *mvi, u32 rx_desc) tstat->stat = SAM_CHECK_COND; break; - case SAS_PROTOCOL_SMP: - tstat->stat = SAM_GOOD; - break; + case SAS_PROTOCOL_SMP: { + struct scatterlist *sg_resp = &task->smp_task.smp_resp; + tstat->stat = SAM_GOOD; + to = kmap_atomic(sg_page(sg_resp), KM_IRQ0); + memcpy(to + sg_resp->offset, + slot->response + sizeof(struct mvs_err_info), + sg_dma_len(sg_resp)); + kunmap_atomic(to, KM_IRQ0); + break; + } case SAS_PROTOCOL_SATA: case SAS_PROTOCOL_STP: - if ((rx_desc & (RXQ_DONE | RXQ_ERR | RXQ_ATTN)) == RXQ_DONE) - tstat->stat = SAM_GOOD; - else - tstat->stat = SAM_CHECK_COND; - /* FIXME: read taskfile data from SATA register set - * associated with SATA target - */ - break; + case SAS_PROTOCOL_SATA | SAS_PROTOCOL_STP: { + struct ata_task_resp *resp = + (struct ata_task_resp *)tstat->buf; + + if ((rx_desc & (RXQ_DONE | RXQ_ERR | RXQ_ATTN)) == + RXQ_DONE) + tstat->stat = SAM_GOOD; + else + tstat->stat = SAM_CHECK_COND; + + resp->frame_len = sizeof(struct dev_to_host_fis); + memcpy(&resp->ending_fis[0], + SATA_RECEIVED_D2H_FIS(port->taskfileset), + sizeof(struct dev_to_host_fis)); + if (resp->ending_fis[2] & ATA_ERR) + mvs_hexdump(16, resp->ending_fis, 0); + break; + } default: tstat->stat = SAM_CHECK_COND; @@ -781,6 +1279,7 @@ static void mvs_slot_complete(struct mvs_info *mvi, u32 rx_desc) out: mvs_slot_free(mvi, task, slot, slot_idx); task->task_done(task); + return tstat->stat; } static void mvs_int_full(struct mvs_info *mvi) @@ -791,6 +1290,8 @@ static void mvs_int_full(struct mvs_info *mvi) stat = mr32(INT_STAT); + mvs_int_rx(mvi, false); + for (i = 0; i < MVS_MAX_PORTS; i++) { tmp = (stat >> i) & (CINT_PORT | CINT_PORT_STOPPED); if (tmp) @@ -800,48 +1301,62 @@ static void mvs_int_full(struct mvs_info *mvi) if (stat & CINT_SRS) mvs_int_sata(mvi); - if (stat & (CINT_CI_STOP | CINT_DONE)) - mvs_int_rx(mvi, false); - mw32(INT_STAT, stat); } -static void mvs_int_rx(struct mvs_info *mvi, bool self_clear) +static int mvs_int_rx(struct mvs_info *mvi, bool self_clear) { + void __iomem *regs = mvi->regs; u32 rx_prod_idx, rx_desc; bool attn = false; + struct pci_dev *pdev = mvi->pdev; /* the first dword in the RX ring is special: it contains * a mirror of the hardware's RX producer index, so that * we don't have to stall the CPU reading that register. * The actual RX ring is offset by one dword, due to this. */ - rx_prod_idx = le32_to_cpu(mvi->rx[0]) & 0xfff; + rx_prod_idx = mr32(RX_CONS_IDX) & RX_RING_SZ_MASK; if (rx_prod_idx == 0xfff) { /* h/w hasn't touched RX ring yet */ mvi->rx_cons = 0xfff; - return; + return 0; } + + /* The CMPL_Q may come late, read from register and try again + * note: if coalescing is enabled, + * it will need to read from register every time for sure + */ + if (mvi->rx_cons == rx_prod_idx) + return 0; + if (mvi->rx_cons == 0xfff) mvi->rx_cons = MVS_RX_RING_SZ - 1; while (mvi->rx_cons != rx_prod_idx) { + /* increment our internal RX consumer pointer */ mvi->rx_cons = (mvi->rx_cons + 1) & (MVS_RX_RING_SZ - 1); - /* Read RX descriptor at offset+1, due to above */ rx_desc = le32_to_cpu(mvi->rx[mvi->rx_cons + 1]); - if (rx_desc & RXQ_DONE) - /* we had a completion, error or no */ - mvs_slot_complete(mvi, rx_desc); + mvs_hba_cq_dump(mvi); - if (rx_desc & RXQ_ATTN) + if (unlikely(rx_desc & RXQ_DONE)) + mvs_slot_complete(mvi, rx_desc); + if (rx_desc & RXQ_ATTN) { attn = true; + dev_printk(KERN_DEBUG, &pdev->dev, "ATTN %X\n", + rx_desc); + } else if (rx_desc & RXQ_ERR) { + dev_printk(KERN_DEBUG, &pdev->dev, "RXQ_ERR %X\n", + rx_desc); + } } if (attn && self_clear) mvs_int_full(mvi); + return 0; } static irqreturn_t mvs_interrupt(int irq, void *opaque) @@ -851,6 +1366,10 @@ static irqreturn_t mvs_interrupt(int irq, void *opaque) u32 stat; stat = mr32(GBL_INT_STAT); + + /* clear CMD_CMPLT ASAP */ + mw32_f(INT_STAT, CINT_DONE); + if (stat == 0 || stat == 0xffffffff) return IRQ_NONE; @@ -863,6 +1382,7 @@ static irqreturn_t mvs_interrupt(int irq, void *opaque) return IRQ_HANDLED; } +#ifndef MVS_DISABLE_MSI static irqreturn_t mvs_msi_interrupt(int irq, void *opaque) { struct mvs_info *mvi = opaque; @@ -875,32 +1395,46 @@ static irqreturn_t mvs_msi_interrupt(int irq, void *opaque) return IRQ_HANDLED; } +#endif struct mvs_task_exec_info { - struct sas_task *task; - struct mvs_cmd_hdr *hdr; - unsigned int tag; - int n_elem; + struct sas_task *task; + struct mvs_cmd_hdr *hdr; + struct mvs_port *port; + u32 tag; + int n_elem; }; -static int mvs_task_prep_smp(struct mvs_info *mvi, struct mvs_task_exec_info *tei) +static int mvs_task_prep_smp(struct mvs_info *mvi, + struct mvs_task_exec_info *tei) { - int elem, rc; + int elem, rc, i; + struct sas_task *task = tei->task; struct mvs_cmd_hdr *hdr = tei->hdr; struct scatterlist *sg_req, *sg_resp; - unsigned int req_len, resp_len, tag = tei->tag; - + u32 req_len, resp_len, tag = tei->tag; + void *buf_tmp; + u8 *buf_oaf; + dma_addr_t buf_tmp_dma; + struct mvs_prd *buf_prd; + struct scatterlist *sg; + struct mvs_slot_info *slot = &mvi->slot_info[tag]; + struct asd_sas_port *sas_port = task->dev->port; + u32 flags = (tei->n_elem << MCH_PRD_LEN_SHIFT); +#if _MV_DUMP + u8 *buf_cmd; + void *from; +#endif /* * DMA-map SMP request, response buffers */ - - sg_req = &tei->task->smp_task.smp_req; + sg_req = &task->smp_task.smp_req; elem = pci_map_sg(mvi->pdev, sg_req, 1, PCI_DMA_TODEVICE); if (!elem) return -ENOMEM; req_len = sg_dma_len(sg_req); - sg_resp = &tei->task->smp_task.smp_resp; + sg_resp = &task->smp_task.smp_resp; elem = pci_map_sg(mvi->pdev, sg_resp, 1, PCI_DMA_FROMDEVICE); if (!elem) { rc = -ENOMEM; @@ -915,21 +1449,77 @@ static int mvs_task_prep_smp(struct mvs_info *mvi, struct mvs_task_exec_info *te } /* - * Fill in TX ring and command slot header + * arrange MVS_SLOT_BUF_SZ-sized DMA buffer according to our needs */ - mvi->tx[tag] = cpu_to_le32( - (TXQ_CMD_SMP << TXQ_CMD_SHIFT) | TXQ_MODE_I | tag); + /* region 1: command table area (MVS_SSP_CMD_SZ bytes) ************** */ + buf_tmp = slot->buf; + buf_tmp_dma = slot->buf_dma; - hdr->flags = 0; - hdr->lens = cpu_to_le32(((resp_len / 4) << 16) | (req_len / 4)); +#if _MV_DUMP + buf_cmd = buf_tmp; + hdr->cmd_tbl = cpu_to_le64(buf_tmp_dma); + buf_tmp += req_len; + buf_tmp_dma += req_len; + slot->cmd_size = req_len; +#else + hdr->cmd_tbl = cpu_to_le64(sg_dma_address(sg_req)); +#endif + + /* region 2: open address frame area (MVS_OAF_SZ bytes) ********* */ + buf_oaf = buf_tmp; + hdr->open_frame = cpu_to_le64(buf_tmp_dma); + + buf_tmp += MVS_OAF_SZ; + buf_tmp_dma += MVS_OAF_SZ; + + /* region 3: PRD table ********************************************* */ + buf_prd = buf_tmp; + if (tei->n_elem) + hdr->prd_tbl = cpu_to_le64(buf_tmp_dma); + else + hdr->prd_tbl = 0; + + i = sizeof(struct mvs_prd) * tei->n_elem; + buf_tmp += i; + buf_tmp_dma += i; + + /* region 4: status buffer (larger the PRD, smaller this buf) ****** */ + slot->response = buf_tmp; + hdr->status_buf = cpu_to_le64(buf_tmp_dma); + + /* + * Fill in TX ring and command slot header + */ + slot->tx = mvi->tx_prod; + mvi->tx[mvi->tx_prod] = cpu_to_le32((TXQ_CMD_SMP << TXQ_CMD_SHIFT) | + TXQ_MODE_I | tag | + (sas_port->phy_mask << TXQ_PHY_SHIFT)); + + hdr->flags |= flags; + hdr->lens = cpu_to_le32(((resp_len / 4) << 16) | ((req_len - 4) / 4)); hdr->tags = cpu_to_le32(tag); hdr->data_len = 0; - hdr->cmd_tbl = cpu_to_le64(sg_dma_address(sg_req)); - hdr->open_frame = 0; - hdr->status_buf = cpu_to_le64(sg_dma_address(sg_resp)); - hdr->prd_tbl = 0; + /* generate open address frame hdr (first 12 bytes) */ + buf_oaf[0] = (1 << 7) | (0 << 4) | 0x01; /* initiator, SMP, ftype 1h */ + buf_oaf[1] = task->dev->linkrate & 0xf; + *(u16 *)(buf_oaf + 2) = 0xFFFF; /* SAS SPEC */ + memcpy(buf_oaf + 4, task->dev->sas_addr, SAS_ADDR_SIZE); + + /* fill in PRD (scatter/gather) table, if any */ + for_each_sg(task->scatter, sg, tei->n_elem, i) { + buf_prd->addr = cpu_to_le64(sg_dma_address(sg)); + buf_prd->len = cpu_to_le32(sg_dma_len(sg)); + buf_prd++; + } + +#if _MV_DUMP + /* copy cmd table */ + from = kmap_atomic(sg_page(sg_req), KM_IRQ0); + memcpy(buf_cmd, from + sg_req->offset, req_len); + kunmap_atomic(from, KM_IRQ0); +#endif return 0; err_out_2: @@ -941,6 +1531,73 @@ err_out: return rc; } +static void mvs_free_reg_set(struct mvs_info *mvi, struct mvs_port *port) +{ + void __iomem *regs = mvi->regs; + u32 tmp, offs; + u8 *tfs = &port->taskfileset; + + if (*tfs == MVS_ID_NOT_MAPPED) + return; + + offs = 1U << ((*tfs & 0x0f) + PCS_EN_SATA_REG_SHIFT); + if (*tfs < 16) { + tmp = mr32(PCS); + mw32(PCS, tmp & ~offs); + } else { + tmp = mr32(CTL); + mw32(CTL, tmp & ~offs); + } + + tmp = mr32(INT_STAT_SRS) & (1U << *tfs); + if (tmp) + mw32(INT_STAT_SRS, tmp); + + *tfs = MVS_ID_NOT_MAPPED; +} + +static u8 mvs_assign_reg_set(struct mvs_info *mvi, struct mvs_port *port) +{ + int i; + u32 tmp, offs; + void __iomem *regs = mvi->regs; + + if (port->taskfileset != MVS_ID_NOT_MAPPED) + return 0; + + tmp = mr32(PCS); + + for (i = 0; i < mvi->chip->srs_sz; i++) { + if (i == 16) + tmp = mr32(CTL); + offs = 1U << ((i & 0x0f) + PCS_EN_SATA_REG_SHIFT); + if (!(tmp & offs)) { + port->taskfileset = i; + + if (i < 16) + mw32(PCS, tmp | offs); + else + mw32(CTL, tmp | offs); + tmp = mr32(INT_STAT_SRS) & (1U << i); + if (tmp) + mw32(INT_STAT_SRS, tmp); + return 0; + } + } + return MVS_ID_NOT_MAPPED; +} + +static u32 mvs_get_ncq_tag(struct sas_task *task) +{ + u32 tag = 0; + struct ata_queued_cmd *qc = task->uldd_task; + + if (qc) + tag = qc->tag; + + return tag; +} + static int mvs_task_prep_ata(struct mvs_info *mvi, struct mvs_task_exec_info *tei) { @@ -948,47 +1605,65 @@ static int mvs_task_prep_ata(struct mvs_info *mvi, struct domain_device *dev = task->dev; struct mvs_cmd_hdr *hdr = tei->hdr; struct asd_sas_port *sas_port = dev->port; - unsigned int tag = tei->tag; - struct mvs_slot_info *slot = &mvi->slot_info[tag]; - u32 flags = (tei->n_elem << MCH_PRD_LEN_SHIFT); + struct mvs_slot_info *slot; struct scatterlist *sg; struct mvs_prd *buf_prd; + struct mvs_port *port = tei->port; + u32 tag = tei->tag; + u32 flags = (tei->n_elem << MCH_PRD_LEN_SHIFT); void *buf_tmp; u8 *buf_cmd, *buf_oaf; dma_addr_t buf_tmp_dma; - unsigned int i, req_len, resp_len; + u32 i, req_len, resp_len; + const u32 max_resp_len = SB_RFB_MAX; - /* FIXME: fill in SATA register set */ - mvi->tx[tag] = cpu_to_le32(TXQ_MODE_I | tag | - (TXQ_CMD_STP << TXQ_CMD_SHIFT) | - (sas_port->phy_mask << TXQ_PHY_SHIFT)); + if (mvs_assign_reg_set(mvi, port) == MVS_ID_NOT_MAPPED) + return -EBUSY; + + slot = &mvi->slot_info[tag]; + slot->tx = mvi->tx_prod; + mvi->tx[mvi->tx_prod] = cpu_to_le32(TXQ_MODE_I | tag | + (TXQ_CMD_STP << TXQ_CMD_SHIFT) | + (sas_port->phy_mask << TXQ_PHY_SHIFT) | + (port->taskfileset << TXQ_SRS_SHIFT)); if (task->ata_task.use_ncq) flags |= MCH_FPDMA; - if (dev->sata_dev.command_set == ATAPI_COMMAND_SET) - flags |= MCH_ATAPI; + if (dev->sata_dev.command_set == ATAPI_COMMAND_SET) { + if (task->ata_task.fis.command != ATA_CMD_ID_ATAPI) + flags |= MCH_ATAPI; + } + /* FIXME: fill in port multiplier number */ hdr->flags = cpu_to_le32(flags); - hdr->tags = cpu_to_le32(tag); + + /* FIXME: the low order order 5 bits for the TAG if enable NCQ */ + if (task->ata_task.use_ncq) { + hdr->tags = cpu_to_le32(mvs_get_ncq_tag(task)); + /*Fill in task file */ + task->ata_task.fis.sector_count = hdr->tags << 3; + } else + hdr->tags = cpu_to_le32(tag); hdr->data_len = cpu_to_le32(task->total_xfer_len); /* * arrange MVS_SLOT_BUF_SZ-sized DMA buffer according to our needs */ - memset(slot->buf, 0, MVS_SLOT_BUF_SZ); - /* region 1: command table area (MVS_ATA_CMD_SZ bytes) ***************/ - buf_cmd = - buf_tmp = slot->buf; + /* region 1: command table area (MVS_ATA_CMD_SZ bytes) ************** */ + buf_cmd = buf_tmp = slot->buf; buf_tmp_dma = slot->buf_dma; hdr->cmd_tbl = cpu_to_le64(buf_tmp_dma); buf_tmp += MVS_ATA_CMD_SZ; buf_tmp_dma += MVS_ATA_CMD_SZ; +#if _MV_DUMP + slot->cmd_size = MVS_ATA_CMD_SZ; +#endif - /* region 2: open address frame area (MVS_OAF_SZ bytes) **********/ + /* region 2: open address frame area (MVS_OAF_SZ bytes) ********* */ /* used for STP. unused for SATA? */ buf_oaf = buf_tmp; hdr->open_frame = cpu_to_le64(buf_tmp_dma); @@ -996,40 +1671,49 @@ static int mvs_task_prep_ata(struct mvs_info *mvi, buf_tmp += MVS_OAF_SZ; buf_tmp_dma += MVS_OAF_SZ; - /* region 3: PRD table ***********************************************/ + /* region 3: PRD table ********************************************* */ buf_prd = buf_tmp; - hdr->prd_tbl = cpu_to_le64(buf_tmp_dma); + if (tei->n_elem) + hdr->prd_tbl = cpu_to_le64(buf_tmp_dma); + else + hdr->prd_tbl = 0; i = sizeof(struct mvs_prd) * tei->n_elem; buf_tmp += i; buf_tmp_dma += i; - /* region 4: status buffer (larger the PRD, smaller this buf) ********/ + /* region 4: status buffer (larger the PRD, smaller this buf) ****** */ /* FIXME: probably unused, for SATA. kept here just in case * we get a STP/SATA error information record */ slot->response = buf_tmp; hdr->status_buf = cpu_to_le64(buf_tmp_dma); - req_len = sizeof(struct ssp_frame_hdr) + 28; + req_len = sizeof(struct host_to_dev_fis); resp_len = MVS_SLOT_BUF_SZ - MVS_ATA_CMD_SZ - - sizeof(struct mvs_err_info) - i; + sizeof(struct mvs_err_info) - i; /* request, response lengths */ + resp_len = min(resp_len, max_resp_len); hdr->lens = cpu_to_le32(((resp_len / 4) << 16) | (req_len / 4)); + task->ata_task.fis.flags |= 0x80; /* C=1: update ATA cmd reg */ /* fill in command FIS and ATAPI CDB */ - memcpy(buf_cmd, &task->ata_task.fis, - sizeof(struct host_to_dev_fis)); - memcpy(buf_cmd + 0x40, task->ata_task.atapi_packet, 16); + memcpy(buf_cmd, &task->ata_task.fis, sizeof(struct host_to_dev_fis)); + if (dev->sata_dev.command_set == ATAPI_COMMAND_SET) + memcpy(buf_cmd + STP_ATAPI_CMD, + task->ata_task.atapi_packet, 16); + + /* generate open address frame hdr (first 12 bytes) */ + buf_oaf[0] = (1 << 7) | (2 << 4) | 0x1; /* initiator, STP, ftype 1h */ + buf_oaf[1] = task->dev->linkrate & 0xf; + *(u16 *)(buf_oaf + 2) = cpu_to_be16(tag); + memcpy(buf_oaf + 4, task->dev->sas_addr, SAS_ADDR_SIZE); /* fill in PRD (scatter/gather) table, if any */ - sg = task->scatter; - for (i = 0; i < tei->n_elem; i++) { + for_each_sg(task->scatter, sg, tei->n_elem, i) { buf_prd->addr = cpu_to_le64(sg_dma_address(sg)); buf_prd->len = cpu_to_le32(sg_dma_len(sg)); - - sg++; buf_prd++; } @@ -1040,23 +1724,25 @@ static int mvs_task_prep_ssp(struct mvs_info *mvi, struct mvs_task_exec_info *tei) { struct sas_task *task = tei->task; - struct asd_sas_port *sas_port = task->dev->port; struct mvs_cmd_hdr *hdr = tei->hdr; + struct mvs_port *port = tei->port; struct mvs_slot_info *slot; struct scatterlist *sg; - unsigned int resp_len, req_len, i, tag = tei->tag; struct mvs_prd *buf_prd; struct ssp_frame_hdr *ssp_hdr; void *buf_tmp; u8 *buf_cmd, *buf_oaf, fburst = 0; dma_addr_t buf_tmp_dma; u32 flags; + u32 resp_len, req_len, i, tag = tei->tag; + const u32 max_resp_len = SB_RFB_MAX; slot = &mvi->slot_info[tag]; - mvi->tx[tag] = cpu_to_le32(TXQ_MODE_I | tag | - (TXQ_CMD_SSP << TXQ_CMD_SHIFT) | - (sas_port->phy_mask << TXQ_PHY_SHIFT)); + slot->tx = mvi->tx_prod; + mvi->tx[mvi->tx_prod] = cpu_to_le32(TXQ_MODE_I | tag | + (TXQ_CMD_SSP << TXQ_CMD_SHIFT) | + (port->wide_port_phymap << TXQ_PHY_SHIFT)); flags = MCH_RETRY; if (task->ssp_task.enable_first_burst) { @@ -1064,8 +1750,8 @@ static int mvs_task_prep_ssp(struct mvs_info *mvi, fburst = (1 << 7); } hdr->flags = cpu_to_le32(flags | - (tei->n_elem << MCH_PRD_LEN_SHIFT) | - (MCH_SSP_FR_CMD << MCH_SSP_FR_TYPE_SHIFT)); + (tei->n_elem << MCH_PRD_LEN_SHIFT) | + (MCH_SSP_FR_CMD << MCH_SSP_FR_TYPE_SHIFT)); hdr->tags = cpu_to_le32(tag); hdr->data_len = cpu_to_le32(task->total_xfer_len); @@ -1073,40 +1759,46 @@ static int mvs_task_prep_ssp(struct mvs_info *mvi, /* * arrange MVS_SLOT_BUF_SZ-sized DMA buffer according to our needs */ - memset(slot->buf, 0, MVS_SLOT_BUF_SZ); - /* region 1: command table area (MVS_SSP_CMD_SZ bytes) ***************/ - buf_cmd = - buf_tmp = slot->buf; + /* region 1: command table area (MVS_SSP_CMD_SZ bytes) ************** */ + buf_cmd = buf_tmp = slot->buf; buf_tmp_dma = slot->buf_dma; hdr->cmd_tbl = cpu_to_le64(buf_tmp_dma); buf_tmp += MVS_SSP_CMD_SZ; buf_tmp_dma += MVS_SSP_CMD_SZ; +#if _MV_DUMP + slot->cmd_size = MVS_SSP_CMD_SZ; +#endif - /* region 2: open address frame area (MVS_OAF_SZ bytes) **********/ + /* region 2: open address frame area (MVS_OAF_SZ bytes) ********* */ buf_oaf = buf_tmp; hdr->open_frame = cpu_to_le64(buf_tmp_dma); buf_tmp += MVS_OAF_SZ; buf_tmp_dma += MVS_OAF_SZ; - /* region 3: PRD table ***********************************************/ + /* region 3: PRD table ********************************************* */ buf_prd = buf_tmp; - hdr->prd_tbl = cpu_to_le64(buf_tmp_dma); + if (tei->n_elem) + hdr->prd_tbl = cpu_to_le64(buf_tmp_dma); + else + hdr->prd_tbl = 0; i = sizeof(struct mvs_prd) * tei->n_elem; buf_tmp += i; buf_tmp_dma += i; - /* region 4: status buffer (larger the PRD, smaller this buf) ********/ + /* region 4: status buffer (larger the PRD, smaller this buf) ****** */ slot->response = buf_tmp; hdr->status_buf = cpu_to_le64(buf_tmp_dma); - req_len = sizeof(struct ssp_frame_hdr) + 28; resp_len = MVS_SLOT_BUF_SZ - MVS_SSP_CMD_SZ - MVS_OAF_SZ - - sizeof(struct mvs_err_info) - i; + sizeof(struct mvs_err_info) - i; + resp_len = min(resp_len, max_resp_len); + + req_len = sizeof(struct ssp_frame_hdr) + 28; /* request, response lengths */ hdr->lens = cpu_to_le32(((resp_len / 4) << 16) | (req_len / 4)); @@ -1114,12 +1806,11 @@ static int mvs_task_prep_ssp(struct mvs_info *mvi, /* generate open address frame hdr (first 12 bytes) */ buf_oaf[0] = (1 << 7) | (1 << 4) | 0x1; /* initiator, SSP, ftype 1h */ buf_oaf[1] = task->dev->linkrate & 0xf; - buf_oaf[2] = tag >> 8; - buf_oaf[3] = tag; + *(u16 *)(buf_oaf + 2) = cpu_to_be16(tag); memcpy(buf_oaf + 4, task->dev->sas_addr, SAS_ADDR_SIZE); - /* fill in SSP frame header */ - ssp_hdr = (struct ssp_frame_hdr *) buf_cmd; + /* fill in SSP frame header (Command Table.SSP frame header) */ + ssp_hdr = (struct ssp_frame_hdr *)buf_cmd; ssp_hdr->frame_type = SSP_COMMAND; memcpy(ssp_hdr->hashed_dest_addr, task->dev->hashed_sas_addr, HASHED_SAS_ADDR_SIZE); @@ -1130,18 +1821,14 @@ static int mvs_task_prep_ssp(struct mvs_info *mvi, /* fill in command frame IU */ buf_cmd += sizeof(*ssp_hdr); memcpy(buf_cmd, &task->ssp_task.LUN, 8); - buf_cmd[9] = fburst | - task->ssp_task.task_attr | - (task->ssp_task.task_prio << 3); + buf_cmd[9] = fburst | task->ssp_task.task_attr | + (task->ssp_task.task_prio << 3); memcpy(buf_cmd + 12, &task->ssp_task.cdb, 16); /* fill in PRD (scatter/gather) table, if any */ - sg = task->scatter; - for (i = 0; i < tei->n_elem; i++) { + for_each_sg(task->scatter, sg, tei->n_elem, i) { buf_prd->addr = cpu_to_le64(sg_dma_address(sg)); buf_prd->len = cpu_to_le32(sg_dma_len(sg)); - - sg++; buf_prd++; } @@ -1150,77 +1837,157 @@ static int mvs_task_prep_ssp(struct mvs_info *mvi, static int mvs_task_exec(struct sas_task *task, const int num, gfp_t gfp_flags) { - struct mvs_info *mvi = task->dev->port->ha->lldd_ha; - unsigned int tag = 0xdeadbeef, rc, n_elem = 0; + struct domain_device *dev = task->dev; + struct mvs_info *mvi = dev->port->ha->lldd_ha; + struct pci_dev *pdev = mvi->pdev; void __iomem *regs = mvi->regs; - unsigned long flags; struct mvs_task_exec_info tei; - - /* FIXME: STP/SATA support not complete yet */ - if (task->task_proto == SAS_PROTOCOL_SATA || task->task_proto == SAS_PROTOCOL_STP) - return -SAS_DEV_NO_RESPONSE; - - if (task->num_scatter) { - n_elem = pci_map_sg(mvi->pdev, task->scatter, - task->num_scatter, task->data_dir); - if (!n_elem) - return -ENOMEM; - } + struct sas_task *t = task; + u32 tag = 0xdeadbeef, rc, n_elem = 0; + unsigned long flags; + u32 n = num, pass = 0; spin_lock_irqsave(&mvi->lock, flags); - rc = mvs_tag_alloc(mvi, &tag); - if (rc) - goto err_out; + do { + tei.port = &mvi->port[dev->port->id]; - mvi->slot_info[tag].task = task; - mvi->slot_info[tag].n_elem = n_elem; - tei.task = task; - tei.hdr = &mvi->slot[tag]; - tei.tag = tag; - tei.n_elem = n_elem; + if (!tei.port->port_attached) { + struct task_status_struct *ts = &t->task_status; + ts->stat = SAS_PHY_DOWN; + t->task_done(t); + rc = 0; + goto exec_exit; + } + if (!sas_protocol_ata(t->task_proto)) { + if (t->num_scatter) { + n_elem = pci_map_sg(mvi->pdev, t->scatter, + t->num_scatter, + t->data_dir); + if (!n_elem) { + rc = -ENOMEM; + goto err_out; + } + } + } else { + n_elem = t->num_scatter; + } - switch (task->task_proto) { - case SAS_PROTOCOL_SMP: - rc = mvs_task_prep_smp(mvi, &tei); - break; - case SAS_PROTOCOL_SSP: - rc = mvs_task_prep_ssp(mvi, &tei); - break; - case SAS_PROTOCOL_SATA: - case SAS_PROTOCOL_STP: - rc = mvs_task_prep_ata(mvi, &tei); - break; - default: - rc = -EINVAL; - break; - } + rc = mvs_tag_alloc(mvi, &tag); + if (rc) + goto err_out; - if (rc) - goto err_out_tag; + mvi->slot_info[tag].task = t; + mvi->slot_info[tag].n_elem = n_elem; + memset(mvi->slot_info[tag].buf, 0, MVS_SLOT_BUF_SZ); + tei.task = t; + tei.hdr = &mvi->slot[tag]; + tei.tag = tag; + tei.n_elem = n_elem; - /* TODO: select normal or high priority */ + switch (t->task_proto) { + case SAS_PROTOCOL_SMP: + rc = mvs_task_prep_smp(mvi, &tei); + break; + case SAS_PROTOCOL_SSP: + rc = mvs_task_prep_ssp(mvi, &tei); + break; + case SAS_PROTOCOL_SATA: + case SAS_PROTOCOL_STP: + case SAS_PROTOCOL_SATA | SAS_PROTOCOL_STP: + rc = mvs_task_prep_ata(mvi, &tei); + break; + default: + dev_printk(KERN_ERR, &pdev->dev, + "unknown sas_task proto: 0x%x\n", + t->task_proto); + rc = -EINVAL; + break; + } - mw32(RX_PROD_IDX, mvi->tx_prod); + if (rc) + goto err_out_tag; - mvi->tx_prod = (mvi->tx_prod + 1) & (MVS_TX_RING_SZ - 1); + /* TODO: select normal or high priority */ - spin_lock(&task->task_state_lock); - task->task_state_flags |= SAS_TASK_AT_INITIATOR; - spin_unlock(&task->task_state_lock); + spin_lock(&t->task_state_lock); + t->task_state_flags |= SAS_TASK_AT_INITIATOR; + spin_unlock(&t->task_state_lock); + + if (n == 1) { + spin_unlock_irqrestore(&mvi->lock, flags); + mw32(TX_PROD_IDX, mvi->tx_prod); + } + mvs_hba_memory_dump(mvi, tag, t->task_proto); + + ++pass; + mvi->tx_prod = (mvi->tx_prod + 1) & (MVS_CHIP_SLOT_SZ - 1); + + if (n == 1) + break; + + t = list_entry(t->list.next, struct sas_task, list); + } while (--n); - spin_unlock_irqrestore(&mvi->lock, flags); return 0; err_out_tag: - mvs_tag_clear(mvi, tag); + mvs_tag_free(mvi, tag); err_out: - if (n_elem) - pci_unmap_sg(mvi->pdev, task->scatter, n_elem, task->data_dir); + dev_printk(KERN_ERR, &pdev->dev, "mvsas exec failed[%d]!\n", rc); + if (!sas_protocol_ata(t->task_proto)) + if (n_elem) + pci_unmap_sg(mvi->pdev, t->scatter, n_elem, + t->data_dir); +exec_exit: + if (pass) + mw32(TX_PROD_IDX, (mvi->tx_prod - 1) & (MVS_CHIP_SLOT_SZ - 1)); spin_unlock_irqrestore(&mvi->lock, flags); return rc; } +static int mvs_task_abort(struct sas_task *task) +{ + int rc = 1; + unsigned long flags; + struct mvs_info *mvi = task->dev->port->ha->lldd_ha; + struct pci_dev *pdev = mvi->pdev; + + spin_lock_irqsave(&task->task_state_lock, flags); + if (task->task_state_flags & SAS_TASK_STATE_DONE) { + rc = TMF_RESP_FUNC_COMPLETE; + goto out_done; + } + spin_unlock_irqrestore(&task->task_state_lock, flags); + + /*FIXME*/ + rc = TMF_RESP_FUNC_COMPLETE; + + switch (task->task_proto) { + case SAS_PROTOCOL_SMP: + dev_printk(KERN_DEBUG, &pdev->dev, "SMP Abort! "); + break; + case SAS_PROTOCOL_SSP: + dev_printk(KERN_DEBUG, &pdev->dev, "SSP Abort! "); + break; + case SAS_PROTOCOL_SATA: + case SAS_PROTOCOL_STP: + case SAS_PROTOCOL_SATA | SAS_PROTOCOL_STP:{ + dev_printk(KERN_DEBUG, &pdev->dev, "STP Abort! " + "Dump D2H FIS: \n"); + mvs_hexdump(sizeof(struct host_to_dev_fis), + (void *)&task->ata_task.fis, 0); + dev_printk(KERN_DEBUG, &pdev->dev, "Dump ATAPI Cmd : \n"); + mvs_hexdump(16, task->ata_task.atapi_packet, 0); + break; + } + default: + break; + } +out_done: + return rc; +} + static void mvs_free(struct mvs_info *mvi) { int i; @@ -1238,7 +2005,7 @@ static void mvs_free(struct mvs_info *mvi) if (mvi->tx) dma_free_coherent(&mvi->pdev->dev, - sizeof(*mvi->tx) * MVS_TX_RING_SZ, + sizeof(*mvi->tx) * MVS_CHIP_SLOT_SZ, mvi->tx, mvi->tx_dma); if (mvi->rx_fis) dma_free_coherent(&mvi->pdev->dev, MVS_RX_FISL_SZ, @@ -1249,10 +2016,12 @@ static void mvs_free(struct mvs_info *mvi) mvi->rx, mvi->rx_dma); if (mvi->slot) dma_free_coherent(&mvi->pdev->dev, - sizeof(*mvi->slot) * MVS_RX_RING_SZ, + sizeof(*mvi->slot) * MVS_SLOTS, mvi->slot, mvi->slot_dma); +#ifdef MVS_ENABLE_PERI if (mvi->peri_regs) iounmap(mvi->peri_regs); +#endif if (mvi->regs) iounmap(mvi->regs); if (mvi->shost) @@ -1267,42 +2036,39 @@ static int mvs_phy_control(struct asd_sas_phy *sas_phy, enum phy_func func, void *funcdata) { struct mvs_info *mvi = sas_phy->ha->lldd_ha; - void __iomem *reg; int rc = 0, phy_id = sas_phy->id; u32 tmp; - reg = mvi->regs + MVS_P0_SER_CTLSTAT + (phy_id * 4); + tmp = mvs_read_phy_ctl(mvi, phy_id); switch (func) { - case PHY_FUNC_SET_LINK_RATE: { - struct sas_phy_linkrates *rates = funcdata; - u32 lrmin = 0, lrmax = 0; + case PHY_FUNC_SET_LINK_RATE:{ + struct sas_phy_linkrates *rates = funcdata; + u32 lrmin = 0, lrmax = 0; - lrmin = (rates->minimum_linkrate << 8); - lrmax = (rates->maximum_linkrate << 12); + lrmin = (rates->minimum_linkrate << 8); + lrmax = (rates->maximum_linkrate << 12); - tmp = readl(reg); - if (lrmin) { - tmp &= ~(0xf << 8); - tmp |= lrmin; + if (lrmin) { + tmp &= ~(0xf << 8); + tmp |= lrmin; + } + if (lrmax) { + tmp &= ~(0xf << 12); + tmp |= lrmax; + } + mvs_write_phy_ctl(mvi, phy_id, tmp); + break; } - if (lrmax) { - tmp &= ~(0xf << 12); - tmp |= lrmax; - } - writel(tmp, reg); - break; - } case PHY_FUNC_HARD_RESET: - tmp = readl(reg); if (tmp & PHY_RST_HARD) break; - writel(tmp | PHY_RST_HARD, reg); + mvs_write_phy_ctl(mvi, phy_id, tmp | PHY_RST_HARD); break; case PHY_FUNC_LINK_RESET: - writel(readl(reg) | PHY_RST, reg); + mvs_write_phy_ctl(mvi, phy_id, tmp | PHY_RST); break; case PHY_FUNC_DISABLE: @@ -1335,11 +2101,11 @@ static void __devinit mvs_phy_init(struct mvs_info *mvi, int phy_id) sas_phy->lldd_phy = phy; } -static struct mvs_info * __devinit mvs_alloc(struct pci_dev *pdev, - const struct pci_device_id *ent) +static struct mvs_info *__devinit mvs_alloc(struct pci_dev *pdev, + const struct pci_device_id *ent) { struct mvs_info *mvi; - unsigned long res_start, res_len; + unsigned long res_start, res_len, res_flag; struct asd_sas_phy **arr_phy; struct asd_sas_port **arr_port; const struct mvs_chip_info *chip = &mvs_chips[ent->driver_data]; @@ -1381,9 +2147,10 @@ static struct mvs_info * __devinit mvs_alloc(struct pci_dev *pdev, SHOST_TO_SAS_HA(mvi->shost) = &mvi->sas; mvi->shost->transportt = mvs_stt; - mvi->shost->max_id = ~0; + mvi->shost->max_id = 21; mvi->shost->max_lun = ~0; - mvi->shost->max_cmd_len = ~0; + mvi->shost->max_channel = 0; + mvi->shost->max_cmd_len = 16; mvi->sas.sas_ha_name = DRV_NAME; mvi->sas.dev = &pdev->dev; @@ -1392,32 +2159,40 @@ static struct mvs_info * __devinit mvs_alloc(struct pci_dev *pdev, mvi->sas.sas_phy = arr_phy; mvi->sas.sas_port = arr_port; mvi->sas.num_phys = chip->n_phy; - mvi->sas.lldd_max_execute_num = MVS_TX_RING_SZ - 1;/* FIXME: correct? */ - mvi->sas.lldd_queue_size = MVS_TX_RING_SZ - 1; /* FIXME: correct? */ + mvi->sas.lldd_max_execute_num = MVS_CHIP_SLOT_SZ - 1; + mvi->sas.lldd_queue_size = MVS_QUEUE_SIZE; + mvi->can_queue = (MVS_CHIP_SLOT_SZ >> 1) - 1; mvi->sas.lldd_ha = mvi; mvi->sas.core.shost = mvi->shost; - mvs_tag_set(mvi, MVS_TX_RING_SZ - 1); + mvs_tag_init(mvi); /* * ioremap main and peripheral registers */ +#ifdef MVS_ENABLE_PERI res_start = pci_resource_start(pdev, 2); res_len = pci_resource_len(pdev, 2); if (!res_start || !res_len) goto err_out; mvi->peri_regs = ioremap_nocache(res_start, res_len); - if (!mvi->regs) + if (!mvi->peri_regs) goto err_out; +#endif res_start = pci_resource_start(pdev, 4); res_len = pci_resource_len(pdev, 4); if (!res_start || !res_len) goto err_out; - mvi->regs = ioremap_nocache(res_start, res_len); + res_flag = pci_resource_flags(pdev, 4); + if (res_flag & IORESOURCE_CACHEABLE) + mvi->regs = ioremap(res_start, res_len); + else + mvi->regs = ioremap_nocache(res_start, res_len); + if (!mvi->regs) goto err_out; @@ -1426,14 +2201,14 @@ static struct mvs_info * __devinit mvs_alloc(struct pci_dev *pdev, */ mvi->tx = dma_alloc_coherent(&pdev->dev, - sizeof(*mvi->tx) * MVS_TX_RING_SZ, + sizeof(*mvi->tx) * MVS_CHIP_SLOT_SZ, &mvi->tx_dma, GFP_KERNEL); if (!mvi->tx) goto err_out; - memset(mvi->tx, 0, sizeof(*mvi->tx) * MVS_TX_RING_SZ); + memset(mvi->tx, 0, sizeof(*mvi->tx) * MVS_CHIP_SLOT_SZ); mvi->rx_fis = dma_alloc_coherent(&pdev->dev, MVS_RX_FISL_SZ, - &mvi->rx_fis_dma, GFP_KERNEL); + &mvi->rx_fis_dma, GFP_KERNEL); if (!mvi->rx_fis) goto err_out; memset(mvi->rx_fis, 0, MVS_RX_FISL_SZ); @@ -1459,7 +2234,7 @@ static struct mvs_info * __devinit mvs_alloc(struct pci_dev *pdev, struct mvs_slot_info *slot = &mvi->slot_info[i]; slot->buf = dma_alloc_coherent(&pdev->dev, MVS_SLOT_BUF_SZ, - &slot->buf_dma, GFP_KERNEL); + &slot->buf_dma, GFP_KERNEL); if (!slot->buf) goto err_out; memset(slot->buf, 0, MVS_SLOT_BUF_SZ); @@ -1468,7 +2243,6 @@ static struct mvs_info * __devinit mvs_alloc(struct pci_dev *pdev, /* finally, read NVRAM to get our SAS address */ if (mvs_nvram_read(mvi, NVR_SAS_ADDR, &mvi->sas_addr, 8)) goto err_out; - return mvi; err_out: @@ -1488,26 +2262,89 @@ static void mvs_cw32(void __iomem *regs, u32 addr, u32 val) mw32(CMD_DATA, val); } -#if 0 -static u32 mvs_phy_read(struct mvs_info *mvi, unsigned int phy_id, u32 addr) +static u32 mvs_read_phy_ctl(struct mvs_info *mvi, u32 port) { void __iomem *regs = mvi->regs; - void __iomem *phy_regs = regs + MVS_P0_CFG_ADDR + (phy_id * 8); - - writel(addr, phy_regs); - return readl(phy_regs + 4); + return (port < 4)?mr32(P0_SER_CTLSTAT + port * 4): + mr32(P4_SER_CTLSTAT + (port - 4) * 4); } -#endif -static void mvs_phy_write(struct mvs_info *mvi, unsigned int phy_id, - u32 addr, u32 val) +static void mvs_write_phy_ctl(struct mvs_info *mvi, u32 port, u32 val) { void __iomem *regs = mvi->regs; - void __iomem *phy_regs = regs + MVS_P0_CFG_ADDR + (phy_id * 8); + if (port < 4) + mw32(P0_SER_CTLSTAT + port * 4, val); + else + mw32(P4_SER_CTLSTAT + (port - 4) * 4, val); +} - writel(addr, phy_regs); - writel(val, phy_regs + 4); - readl(phy_regs); /* flush */ +static u32 mvs_read_port(struct mvs_info *mvi, u32 off, u32 off2, u32 port) +{ + void __iomem *regs = mvi->regs + off; + void __iomem *regs2 = mvi->regs + off2; + return (port < 4)?readl(regs + port * 8): + readl(regs2 + (port - 4) * 8); +} + +static void mvs_write_port(struct mvs_info *mvi, u32 off, u32 off2, + u32 port, u32 val) +{ + void __iomem *regs = mvi->regs + off; + void __iomem *regs2 = mvi->regs + off2; + if (port < 4) + writel(val, regs + port * 8); + else + writel(val, regs2 + (port - 4) * 8); +} + +static u32 mvs_read_port_cfg_data(struct mvs_info *mvi, u32 port) +{ + return mvs_read_port(mvi, MVS_P0_CFG_DATA, MVS_P4_CFG_DATA, port); +} + +static void mvs_write_port_cfg_data(struct mvs_info *mvi, u32 port, u32 val) +{ + mvs_write_port(mvi, MVS_P0_CFG_DATA, MVS_P4_CFG_DATA, port, val); +} + +static void mvs_write_port_cfg_addr(struct mvs_info *mvi, u32 port, u32 addr) +{ + mvs_write_port(mvi, MVS_P0_CFG_ADDR, MVS_P4_CFG_ADDR, port, addr); +} + +static u32 mvs_read_port_vsr_data(struct mvs_info *mvi, u32 port) +{ + return mvs_read_port(mvi, MVS_P0_VSR_DATA, MVS_P4_VSR_DATA, port); +} + +static void mvs_write_port_vsr_data(struct mvs_info *mvi, u32 port, u32 val) +{ + mvs_write_port(mvi, MVS_P0_VSR_DATA, MVS_P4_VSR_DATA, port, val); +} + +static void mvs_write_port_vsr_addr(struct mvs_info *mvi, u32 port, u32 addr) +{ + mvs_write_port(mvi, MVS_P0_VSR_ADDR, MVS_P4_VSR_ADDR, port, addr); +} + +static u32 mvs_read_port_irq_stat(struct mvs_info *mvi, u32 port) +{ + return mvs_read_port(mvi, MVS_P0_INT_STAT, MVS_P4_INT_STAT, port); +} + +static void mvs_write_port_irq_stat(struct mvs_info *mvi, u32 port, u32 val) +{ + mvs_write_port(mvi, MVS_P0_INT_STAT, MVS_P4_INT_STAT, port, val); +} + +static u32 mvs_read_port_irq_mask(struct mvs_info *mvi, u32 port) +{ + return mvs_read_port(mvi, MVS_P0_INT_MASK, MVS_P4_INT_MASK, port); +} + +static void mvs_write_port_irq_mask(struct mvs_info *mvi, u32 port, u32 val) +{ + mvs_write_port(mvi, MVS_P0_INT_MASK, MVS_P4_INT_MASK, port, val); } static void __devinit mvs_phy_hacks(struct mvs_info *mvi) @@ -1547,6 +2384,260 @@ static void __devinit mvs_phy_hacks(struct mvs_info *mvi) tmp &= 0x1fffffff; tmp |= (2U << 29); /* 8 ms retry */ mvs_cw32(regs, CMD_PHY_TIMER, tmp); + + /* TEST - for phy decoding error, adjust voltage levels */ + mw32(P0_VSR_ADDR + 0, 0x8); + mw32(P0_VSR_DATA + 0, 0x2F0); + + mw32(P0_VSR_ADDR + 8, 0x8); + mw32(P0_VSR_DATA + 8, 0x2F0); + + mw32(P0_VSR_ADDR + 16, 0x8); + mw32(P0_VSR_DATA + 16, 0x2F0); + + mw32(P0_VSR_ADDR + 24, 0x8); + mw32(P0_VSR_DATA + 24, 0x2F0); + +} + +static void mvs_enable_xmt(struct mvs_info *mvi, int PhyId) +{ + void __iomem *regs = mvi->regs; + u32 tmp; + + tmp = mr32(PCS); + if (mvi->chip->n_phy <= 4) + tmp |= 1 << (PhyId + PCS_EN_PORT_XMT_SHIFT); + else + tmp |= 1 << (PhyId + PCS_EN_PORT_XMT_SHIFT2); + mw32(PCS, tmp); +} + +static void mvs_detect_porttype(struct mvs_info *mvi, int i) +{ + void __iomem *regs = mvi->regs; + u32 reg; + struct mvs_phy *phy = &mvi->phy[i]; + + /* TODO check & save device type */ + reg = mr32(GBL_PORT_TYPE); + + if (reg & MODE_SAS_SATA & (1 << i)) + phy->phy_type |= PORT_TYPE_SAS; + else + phy->phy_type |= PORT_TYPE_SATA; +} + +static void *mvs_get_d2h_reg(struct mvs_info *mvi, int i, void *buf) +{ + u32 *s = (u32 *) buf; + + if (!s) + return NULL; + + mvs_write_port_cfg_addr(mvi, i, PHYR_SATA_SIG3); + s[3] = mvs_read_port_cfg_data(mvi, i); + + mvs_write_port_cfg_addr(mvi, i, PHYR_SATA_SIG2); + s[2] = mvs_read_port_cfg_data(mvi, i); + + mvs_write_port_cfg_addr(mvi, i, PHYR_SATA_SIG1); + s[1] = mvs_read_port_cfg_data(mvi, i); + + mvs_write_port_cfg_addr(mvi, i, PHYR_SATA_SIG0); + s[0] = mvs_read_port_cfg_data(mvi, i); + + return (void *)s; +} + +static u32 mvs_is_sig_fis_received(u32 irq_status) +{ + return irq_status & PHYEV_SIG_FIS; +} + +static void mvs_update_wideport(struct mvs_info *mvi, int i) +{ + struct mvs_phy *phy = &mvi->phy[i]; + struct mvs_port *port = phy->port; + int j, no; + + for_each_phy(port->wide_port_phymap, no, j, mvi->chip->n_phy) + if (no & 1) { + mvs_write_port_cfg_addr(mvi, no, PHYR_WIDE_PORT); + mvs_write_port_cfg_data(mvi, no, + port->wide_port_phymap); + } else { + mvs_write_port_cfg_addr(mvi, no, PHYR_WIDE_PORT); + mvs_write_port_cfg_data(mvi, no, 0); + } +} + +static u32 mvs_is_phy_ready(struct mvs_info *mvi, int i) +{ + u32 tmp; + struct mvs_phy *phy = &mvi->phy[i]; + struct mvs_port *port; + + tmp = mvs_read_phy_ctl(mvi, i); + + if ((tmp & PHY_READY_MASK) && !(phy->irq_status & PHYEV_POOF)) { + if (!phy->port) + phy->phy_attached = 1; + return tmp; + } + + port = phy->port; + if (port) { + if (phy->phy_type & PORT_TYPE_SAS) { + port->wide_port_phymap &= ~(1U << i); + if (!port->wide_port_phymap) + port->port_attached = 0; + mvs_update_wideport(mvi, i); + } else if (phy->phy_type & PORT_TYPE_SATA) + port->port_attached = 0; + mvs_free_reg_set(mvi, phy->port); + phy->port = NULL; + phy->phy_attached = 0; + phy->phy_type &= ~(PORT_TYPE_SAS | PORT_TYPE_SATA); + } + return 0; +} + +static void mvs_update_phyinfo(struct mvs_info *mvi, int i, + int get_st) +{ + struct mvs_phy *phy = &mvi->phy[i]; + struct pci_dev *pdev = mvi->pdev; + u32 tmp, j; + u64 tmp64; + + mvs_write_port_cfg_addr(mvi, i, PHYR_IDENTIFY); + phy->dev_info = mvs_read_port_cfg_data(mvi, i); + + mvs_write_port_cfg_addr(mvi, i, PHYR_ADDR_HI); + phy->dev_sas_addr = (u64) mvs_read_port_cfg_data(mvi, i) << 32; + + mvs_write_port_cfg_addr(mvi, i, PHYR_ADDR_LO); + phy->dev_sas_addr |= mvs_read_port_cfg_data(mvi, i); + + if (get_st) { + phy->irq_status = mvs_read_port_irq_stat(mvi, i); + phy->phy_status = mvs_is_phy_ready(mvi, i); + } + + if (phy->phy_status) { + u32 phy_st; + struct asd_sas_phy *sas_phy = mvi->sas.sas_phy[i]; + + mvs_write_port_cfg_addr(mvi, i, PHYR_PHY_STAT); + phy_st = mvs_read_port_cfg_data(mvi, i); + + sas_phy->linkrate = + (phy->phy_status & PHY_NEG_SPP_PHYS_LINK_RATE_MASK) >> + PHY_NEG_SPP_PHYS_LINK_RATE_MASK_OFFSET; + + /* Updated attached_sas_addr */ + mvs_write_port_cfg_addr(mvi, i, PHYR_ATT_ADDR_HI); + phy->att_dev_sas_addr = + (u64) mvs_read_port_cfg_data(mvi, i) << 32; + + mvs_write_port_cfg_addr(mvi, i, PHYR_ATT_ADDR_LO); + phy->att_dev_sas_addr |= mvs_read_port_cfg_data(mvi, i); + + dev_printk(KERN_DEBUG, &pdev->dev, + "phy[%d] Get Attached Address 0x%llX ," + " SAS Address 0x%llX\n", + i, phy->att_dev_sas_addr, phy->dev_sas_addr); + dev_printk(KERN_DEBUG, &pdev->dev, + "Rate = %x , type = %d\n", + sas_phy->linkrate, phy->phy_type); + +#if 1 + /* + * If the device is capable of supporting a wide port + * on its phys, it may configure the phys as a wide port. + */ + if (phy->phy_type & PORT_TYPE_SAS) + for (j = 0; j < mvi->chip->n_phy && j != i; ++j) { + if ((mvi->phy[j].phy_attached) && + (mvi->phy[j].phy_type & PORT_TYPE_SAS)) + if (phy->att_dev_sas_addr == + mvi->phy[j].att_dev_sas_addr - 1) { + phy->att_dev_sas_addr = + mvi->phy[j].att_dev_sas_addr; + break; + } + } + +#endif + + tmp64 = cpu_to_be64(phy->att_dev_sas_addr); + memcpy(sas_phy->attached_sas_addr, &tmp64, SAS_ADDR_SIZE); + + if (phy->phy_type & PORT_TYPE_SAS) { + mvs_write_port_cfg_addr(mvi, i, PHYR_ATT_DEV_INFO); + phy->att_dev_info = mvs_read_port_cfg_data(mvi, i); + phy->identify.device_type = + phy->att_dev_info & PORT_DEV_TYPE_MASK; + + if (phy->identify.device_type == SAS_END_DEV) + phy->identify.target_port_protocols = + SAS_PROTOCOL_SSP; + else if (phy->identify.device_type != NO_DEVICE) + phy->identify.target_port_protocols = + SAS_PROTOCOL_SMP; + if (phy_st & PHY_OOB_DTCTD) + sas_phy->oob_mode = SAS_OOB_MODE; + phy->frame_rcvd_size = + sizeof(struct sas_identify_frame); + } else if (phy->phy_type & PORT_TYPE_SATA) { + phy->identify.target_port_protocols = SAS_PROTOCOL_STP; + if (mvs_is_sig_fis_received(phy->irq_status)) { + if (phy_st & PHY_OOB_DTCTD) + sas_phy->oob_mode = SATA_OOB_MODE; + phy->frame_rcvd_size = + sizeof(struct dev_to_host_fis); + mvs_get_d2h_reg(mvi, i, + (void *)sas_phy->frame_rcvd); + } else { + dev_printk(KERN_DEBUG, &pdev->dev, + "No sig fis\n"); + } + } + /* workaround for HW phy decoding error on 1.5g disk drive */ + mvs_write_port_vsr_addr(mvi, i, VSR_PHY_MODE6); + tmp = mvs_read_port_vsr_data(mvi, i); + if (((phy->phy_status & PHY_NEG_SPP_PHYS_LINK_RATE_MASK) >> + PHY_NEG_SPP_PHYS_LINK_RATE_MASK_OFFSET) == + SAS_LINK_RATE_1_5_GBPS) + tmp &= ~PHY_MODE6_DTL_SPEED; + else + tmp |= PHY_MODE6_DTL_SPEED; + mvs_write_port_vsr_data(mvi, i, tmp); + + } + if (get_st) + mvs_write_port_irq_stat(mvi, i, phy->irq_status); +} + +static void mvs_port_formed(struct asd_sas_phy *sas_phy) +{ + struct sas_ha_struct *sas_ha = sas_phy->ha; + struct mvs_info *mvi = sas_ha->lldd_ha; + struct asd_sas_port *sas_port = sas_phy->port; + struct mvs_phy *phy = sas_phy->lldd_phy; + struct mvs_port *port = &mvi->port[sas_port->id]; + unsigned long flags; + + spin_lock_irqsave(&mvi->lock, flags); + port->port_attached = 1; + phy->port = port; + port->taskfileset = MVS_ID_NOT_MAPPED; + if (phy->phy_type & PORT_TYPE_SAS) { + port->wide_port_phymap = sas_port->phy_mask; + mvs_update_wideport(mvi, sas_phy->id); + } + spin_unlock_irqrestore(&mvi->lock, flags); } static int __devinit mvs_hw_init(struct mvs_info *mvi) @@ -1559,6 +2650,7 @@ static int __devinit mvs_hw_init(struct mvs_info *mvi) mw32(GBL_CTL, 0); tmp = mr32(GBL_CTL); + /* Reset Controller */ if (!(tmp & HBA_RST)) { if (mvi->flags & MVF_PHY_PWR_FIX) { pci_read_config_dword(mvi->pdev, PCR_PHY_CTL, &tmp); @@ -1576,7 +2668,6 @@ static int __devinit mvs_hw_init(struct mvs_info *mvi) mw32_f(GBL_CTL, HBA_RST); } - /* wait for reset to finish; timeout is just a guess */ i = 1000; while (i-- > 0) { @@ -1590,6 +2681,7 @@ static int __devinit mvs_hw_init(struct mvs_info *mvi) return -EBUSY; } + /* Init Chip */ /* make sure RST is set; HBA_RST /should/ have done that for us */ cctl = mr32(CTL); if (cctl & CCTL_RST) @@ -1597,6 +2689,12 @@ static int __devinit mvs_hw_init(struct mvs_info *mvi) else mw32_f(CTL, cctl | CCTL_RST); + /* write to device control _AND_ device status register? - A.C. */ + pci_read_config_dword(mvi->pdev, PCR_DEV_CTRL, &tmp); + tmp &= ~PRD_REQ_MASK; + tmp |= PRD_REQ_SIZE; + pci_write_config_dword(mvi->pdev, PCR_DEV_CTRL, tmp); + pci_read_config_dword(mvi->pdev, PCR_PHY_CTL, &tmp); tmp |= PCTL_PWR_ON; tmp &= ~PCTL_OFF; @@ -1609,6 +2707,9 @@ static int __devinit mvs_hw_init(struct mvs_info *mvi) mw32_f(CTL, cctl); + /* reset control */ + mw32(PCS, 0); /*MVS_PCS */ + mvs_phy_hacks(mvi); mw32(CMD_LIST_LO, mvi->slot_dma); @@ -1617,7 +2718,7 @@ static int __devinit mvs_hw_init(struct mvs_info *mvi) mw32(RX_FIS_LO, mvi->rx_fis_dma); mw32(RX_FIS_HI, (mvi->rx_fis_dma >> 16) >> 16); - mw32(TX_CFG, MVS_TX_RING_SZ); + mw32(TX_CFG, MVS_CHIP_SLOT_SZ); mw32(TX_LO, mvi->tx_dma); mw32(TX_HI, (mvi->tx_dma >> 16) >> 16); @@ -1625,44 +2726,88 @@ static int __devinit mvs_hw_init(struct mvs_info *mvi) mw32(RX_LO, mvi->rx_dma); mw32(RX_HI, (mvi->rx_dma >> 16) >> 16); + /* enable auto port detection */ + mw32(GBL_PORT_TYPE, MODE_AUTO_DET_EN); + msleep(100); /* init and reset phys */ for (i = 0; i < mvi->chip->n_phy; i++) { /* FIXME: is this the correct dword order? */ - u32 lo = *((u32 *) &mvi->sas_addr[0]); - u32 hi = *((u32 *) &mvi->sas_addr[4]); + u32 lo = *((u32 *)&mvi->sas_addr[0]); + u32 hi = *((u32 *)&mvi->sas_addr[4]); + + mvs_detect_porttype(mvi, i); /* set phy local SAS address */ - mvs_phy_write(mvi, i, PHYR_ADDR_LO, lo); - mvs_phy_write(mvi, i, PHYR_ADDR_HI, hi); + mvs_write_port_cfg_addr(mvi, i, PHYR_ADDR_LO); + mvs_write_port_cfg_data(mvi, i, lo); + mvs_write_port_cfg_addr(mvi, i, PHYR_ADDR_HI); + mvs_write_port_cfg_data(mvi, i, hi); /* reset phy */ - tmp = readl(regs + MVS_P0_SER_CTLSTAT + (i * 4)); + tmp = mvs_read_phy_ctl(mvi, i); tmp |= PHY_RST; - writel(tmp, regs + MVS_P0_SER_CTLSTAT + (i * 4)); + mvs_write_phy_ctl(mvi, i, tmp); } msleep(100); for (i = 0; i < mvi->chip->n_phy; i++) { - /* set phy int mask */ - writel(PHYEV_BROAD_CH | PHYEV_RDY_CH, - regs + MVS_P0_INT_MASK + (i * 8)); - /* clear phy int status */ - tmp = readl(regs + MVS_P0_INT_STAT + (i * 8)); - writel(tmp, regs + MVS_P0_INT_STAT + (i * 8)); + tmp = mvs_read_port_irq_stat(mvi, i); + tmp &= ~PHYEV_SIG_FIS; + mvs_write_port_irq_stat(mvi, i, tmp); + + /* set phy int mask */ + tmp = PHYEV_RDY_CH | PHYEV_BROAD_CH | PHYEV_UNASSOC_FIS | + PHYEV_ID_DONE | PHYEV_DEC_ERR; + mvs_write_port_irq_mask(mvi, i, tmp); + + msleep(100); + mvs_update_phyinfo(mvi, i, 1); + mvs_enable_xmt(mvi, i); } /* FIXME: update wide port bitmaps */ + /* little endian for open address and command table, etc. */ + /* A.C. + * it seems that ( from the spec ) turning on big-endian won't + * do us any good on big-endian machines, need further confirmation + */ + cctl = mr32(CTL); + cctl |= CCTL_ENDIAN_CMD; + cctl |= CCTL_ENDIAN_DATA; + cctl &= ~CCTL_ENDIAN_OPEN; + cctl |= CCTL_ENDIAN_RSP; + mw32_f(CTL, cctl); + + /* reset CMD queue */ + tmp = mr32(PCS); + tmp |= PCS_CMD_RST; + mw32(PCS, tmp); + /* interrupt coalescing may cause missing HW interrput in some case, + * and the max count is 0x1ff, while our max slot is 0x200, + * it will make count 0. + */ + tmp = 0; + mw32(INT_COAL, tmp); + + tmp = 0x100; + mw32(INT_COAL_TMOUT, tmp); + /* ladies and gentlemen, start your engines */ - mw32(TX_CFG, MVS_TX_RING_SZ | TX_EN); + mw32(TX_CFG, 0); + mw32(TX_CFG, MVS_CHIP_SLOT_SZ | TX_EN); mw32(RX_CFG, MVS_RX_RING_SZ | RX_EN); - mw32(PCS, PCS_SATA_RETRY | PCS_FIS_RX_EN | PCS_CMD_EN | - ((mvi->flags & MVF_MSI) ? PCS_SELF_CLEAR : 0)); + /* enable CMD/CMPL_Q/RESP mode */ + mw32(PCS, PCS_SATA_RETRY | PCS_FIS_RX_EN | PCS_CMD_EN); /* re-enable interrupts globally */ - mw32(GBL_CTL, INT_EN); + mvs_hba_interrupt_enable(mvi); + + /* enable completion queue interrupt */ + tmp = (CINT_PORT_MASK | CINT_DONE | CINT_MEM); + mw32(INT_MASK, tmp); return 0; } @@ -1680,7 +2825,7 @@ static void __devinit mvs_print_info(struct mvs_info *mvi) } static int __devinit mvs_pci_init(struct pci_dev *pdev, - const struct pci_device_id *ent) + const struct pci_device_id *ent) { int rc; struct mvs_info *mvi; @@ -1710,10 +2855,16 @@ static int __devinit mvs_pci_init(struct pci_dev *pdev, if (rc) goto err_out_mvi; +#ifndef MVS_DISABLE_MSI if (!pci_enable_msi(pdev)) { + u32 tmp; + void __iomem *regs = mvi->regs; mvi->flags |= MVF_MSI; irq_handler = mvs_msi_interrupt; + tmp = mr32(PCS); + mw32(PCS, tmp | PCS_SELF_CLEAR); } +#endif rc = request_irq(pdev->irq, irq_handler, IRQF_SHARED, DRV_NAME, mvi); if (rc) @@ -1732,6 +2883,7 @@ static int __devinit mvs_pci_init(struct pci_dev *pdev, mvs_print_info(mvi); scsi_scan_host(mvi->shost); + return 0; err_out_shost: @@ -1756,21 +2908,26 @@ static void __devexit mvs_pci_remove(struct pci_dev *pdev) pci_set_drvdata(pdev, NULL); - sas_unregister_ha(&mvi->sas); - sas_remove_host(mvi->shost); - scsi_remove_host(mvi->shost); + if (mvi) { + sas_unregister_ha(&mvi->sas); + mvs_hba_interrupt_disable(mvi); + sas_remove_host(mvi->shost); + scsi_remove_host(mvi->shost); - free_irq(pdev->irq, mvi); - if (mvi->flags & MVF_MSI) - pci_disable_msi(pdev); - mvs_free(mvi); - pci_release_regions(pdev); + free_irq(pdev->irq, mvi); + if (mvi->flags & MVF_MSI) + pci_disable_msi(pdev); + mvs_free(mvi); + pci_release_regions(pdev); + } pci_disable_device(pdev); } static struct sas_domain_function_template mvs_transport_ops = { .lldd_execute_task = mvs_task_exec, .lldd_control_phy = mvs_phy_control, + .lldd_abort_task = mvs_task_abort, + .lldd_port_formed = mvs_port_formed }; static struct pci_device_id __devinitdata mvs_pci_table[] = { @@ -1822,4 +2979,3 @@ MODULE_DESCRIPTION("Marvell 88SE6440 SAS/SATA controller driver"); MODULE_VERSION(DRV_VERSION); MODULE_LICENSE("GPL"); MODULE_DEVICE_TABLE(pci, mvs_pci_table); - From cfb37ae1e9d31fe2c1d21734ab51405e0c3afb7e Mon Sep 17 00:00:00 2001 From: Adrian Bunk Date: Wed, 30 Jan 2008 22:03:36 +0200 Subject: [PATCH 25/25] [SCSI] qlogicpt: section fixes In current mainline, __devinit qpti_sbus_probe() still is calling __init qpti_chain_add(). Change occurrences of __init to __devinit to fix. Signed-off-by: Adrian Bunk Signed-off-by: James Bottomley --- drivers/scsi/qlogicpti.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/drivers/scsi/qlogicpti.c b/drivers/scsi/qlogicpti.c index 65455ab1f3b9..4a1cf6377f6c 100644 --- a/drivers/scsi/qlogicpti.c +++ b/drivers/scsi/qlogicpti.c @@ -651,7 +651,7 @@ static int qlogicpti_verify_tmon(struct qlogicpti *qpti) static irqreturn_t qpti_intr(int irq, void *dev_id); -static void __init qpti_chain_add(struct qlogicpti *qpti) +static void __devinit qpti_chain_add(struct qlogicpti *qpti) { spin_lock_irq(&qptichain_lock); if (qptichain != NULL) { @@ -667,7 +667,7 @@ static void __init qpti_chain_add(struct qlogicpti *qpti) spin_unlock_irq(&qptichain_lock); } -static void __init qpti_chain_del(struct qlogicpti *qpti) +static void __devexit qpti_chain_del(struct qlogicpti *qpti) { spin_lock_irq(&qptichain_lock); if (qptichain == qpti) { @@ -682,7 +682,7 @@ static void __init qpti_chain_del(struct qlogicpti *qpti) spin_unlock_irq(&qptichain_lock); } -static int __init qpti_map_regs(struct qlogicpti *qpti) +static int __devinit qpti_map_regs(struct qlogicpti *qpti) { struct sbus_dev *sdev = qpti->sdev; @@ -705,7 +705,7 @@ static int __init qpti_map_regs(struct qlogicpti *qpti) return 0; } -static int __init qpti_register_irq(struct qlogicpti *qpti) +static int __devinit qpti_register_irq(struct qlogicpti *qpti) { struct sbus_dev *sdev = qpti->sdev; @@ -730,7 +730,7 @@ fail: return -1; } -static void __init qpti_get_scsi_id(struct qlogicpti *qpti) +static void __devinit qpti_get_scsi_id(struct qlogicpti *qpti) { qpti->scsi_id = prom_getintdefault(qpti->prom_node, "initiator-id", @@ -783,7 +783,7 @@ static void qpti_get_clock(struct qlogicpti *qpti) /* The request and response queues must each be aligned * on a page boundary. */ -static int __init qpti_map_queues(struct qlogicpti *qpti) +static int __devinit qpti_map_queues(struct qlogicpti *qpti) { struct sbus_dev *sdev = qpti->sdev;