diff options
Diffstat (limited to 'drivers/message/fusion/mptfc.c')
-rw-r--r-- | drivers/message/fusion/mptfc.c | 1554 |
1 files changed, 1554 insertions, 0 deletions
diff --git a/drivers/message/fusion/mptfc.c b/drivers/message/fusion/mptfc.c new file mode 100644 index 000000000..b15fdc626 --- /dev/null +++ b/drivers/message/fusion/mptfc.c @@ -0,0 +1,1554 @@ +/* + * linux/drivers/message/fusion/mptfc.c + * For use with LSI PCI chip/adapter(s) + * running LSI Fusion MPT (Message Passing Technology) firmware. + * + * Copyright (c) 1999-2008 LSI Corporation + * (mailto:DL-MPTFusionLinux@lsi.com) + * + */ +/*=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=*/ +/* + 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; version 2 of the License. + + 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. + + NO WARRANTY + THE PROGRAM IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OR + CONDITIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED INCLUDING, WITHOUT + LIMITATION, ANY WARRANTIES OR CONDITIONS OF TITLE, NON-INFRINGEMENT, + MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Each Recipient is + solely responsible for determining the appropriateness of using and + distributing the Program and assumes all risks associated with its + exercise of rights under this Agreement, including but not limited to + the risks and costs of program errors, damage to or loss of data, + programs or equipment, and unavailability or interruption of operations. + + DISCLAIMER OF LIABILITY + NEITHER RECIPIENT NOR ANY CONTRIBUTORS SHALL HAVE ANY LIABILITY FOR ANY + DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING WITHOUT LIMITATION LOST PROFITS), HOWEVER CAUSED AND + ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR + TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE + USE OR DISTRIBUTION OF THE PROGRAM OR THE EXERCISE OF ANY RIGHTS GRANTED + HEREUNDER, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGES + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ +/*=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=*/ +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/init.h> +#include <linux/errno.h> +#include <linux/kdev_t.h> +#include <linux/blkdev.h> +#include <linux/delay.h> /* for mdelay */ +#include <linux/interrupt.h> /* needed for in_interrupt() proto */ +#include <linux/reboot.h> /* notifier code */ +#include <linux/workqueue.h> +#include <linux/sort.h> +#include <linux/slab.h> + +#include <scsi/scsi.h> +#include <scsi/scsi_cmnd.h> +#include <scsi/scsi_device.h> +#include <scsi/scsi_host.h> +#include <scsi/scsi_tcq.h> +#include <scsi/scsi_transport_fc.h> + +#include "mptbase.h" +#include "mptscsih.h" + +/*=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=*/ +#define my_NAME "Fusion MPT FC Host driver" +#define my_VERSION MPT_LINUX_VERSION_COMMON +#define MYNAM "mptfc" + +MODULE_AUTHOR(MODULEAUTHOR); +MODULE_DESCRIPTION(my_NAME); +MODULE_LICENSE("GPL"); +MODULE_VERSION(my_VERSION); + +/* Command line args */ +#define MPTFC_DEV_LOSS_TMO (60) +static int mptfc_dev_loss_tmo = MPTFC_DEV_LOSS_TMO; /* reasonable default */ +module_param(mptfc_dev_loss_tmo, int, 0); +MODULE_PARM_DESC(mptfc_dev_loss_tmo, " Initial time the driver programs the " + " transport to wait for an rport to " + " return following a device loss event." + " Default=60."); + +/* scsi-mid layer global parmeter is max_report_luns, which is 511 */ +#define MPTFC_MAX_LUN (16895) +static int max_lun = MPTFC_MAX_LUN; +module_param(max_lun, int, 0); +MODULE_PARM_DESC(max_lun, " max lun, default=16895 "); + +static u8 mptfcDoneCtx = MPT_MAX_PROTOCOL_DRIVERS; +static u8 mptfcTaskCtx = MPT_MAX_PROTOCOL_DRIVERS; +static u8 mptfcInternalCtx = MPT_MAX_PROTOCOL_DRIVERS; + +static int mptfc_target_alloc(struct scsi_target *starget); +static int mptfc_slave_alloc(struct scsi_device *sdev); +static int mptfc_qcmd(struct Scsi_Host *shost, struct scsi_cmnd *SCpnt); +static void mptfc_target_destroy(struct scsi_target *starget); +static void mptfc_set_rport_loss_tmo(struct fc_rport *rport, uint32_t timeout); +static void mptfc_remove(struct pci_dev *pdev); +static int mptfc_abort(struct scsi_cmnd *SCpnt); +static int mptfc_dev_reset(struct scsi_cmnd *SCpnt); +static int mptfc_bus_reset(struct scsi_cmnd *SCpnt); + +static struct scsi_host_template mptfc_driver_template = { + .module = THIS_MODULE, + .proc_name = "mptfc", + .show_info = mptscsih_show_info, + .name = "MPT FC Host", + .info = mptscsih_info, + .queuecommand = mptfc_qcmd, + .target_alloc = mptfc_target_alloc, + .slave_alloc = mptfc_slave_alloc, + .slave_configure = mptscsih_slave_configure, + .target_destroy = mptfc_target_destroy, + .slave_destroy = mptscsih_slave_destroy, + .change_queue_depth = mptscsih_change_queue_depth, + .eh_timed_out = fc_eh_timed_out, + .eh_abort_handler = mptfc_abort, + .eh_device_reset_handler = mptfc_dev_reset, + .eh_bus_reset_handler = mptfc_bus_reset, + .eh_host_reset_handler = mptscsih_host_reset, + .bios_param = mptscsih_bios_param, + .can_queue = MPT_FC_CAN_QUEUE, + .this_id = -1, + .sg_tablesize = MPT_SCSI_SG_DEPTH, + .max_sectors = 8192, + .cmd_per_lun = 7, + .use_clustering = ENABLE_CLUSTERING, + .shost_attrs = mptscsih_host_attrs, +}; + +/**************************************************************************** + * Supported hardware + */ + +static struct pci_device_id mptfc_pci_table[] = { + { PCI_VENDOR_ID_LSI_LOGIC, MPI_MANUFACTPAGE_DEVICEID_FC909, + PCI_ANY_ID, PCI_ANY_ID }, + { PCI_VENDOR_ID_LSI_LOGIC, MPI_MANUFACTPAGE_DEVICEID_FC919, + PCI_ANY_ID, PCI_ANY_ID }, + { PCI_VENDOR_ID_LSI_LOGIC, MPI_MANUFACTPAGE_DEVICEID_FC929, + PCI_ANY_ID, PCI_ANY_ID }, + { PCI_VENDOR_ID_LSI_LOGIC, MPI_MANUFACTPAGE_DEVICEID_FC919X, + PCI_ANY_ID, PCI_ANY_ID }, + { PCI_VENDOR_ID_LSI_LOGIC, MPI_MANUFACTPAGE_DEVICEID_FC929X, + PCI_ANY_ID, PCI_ANY_ID }, + { PCI_VENDOR_ID_LSI_LOGIC, MPI_MANUFACTPAGE_DEVICEID_FC939X, + PCI_ANY_ID, PCI_ANY_ID }, + { PCI_VENDOR_ID_LSI_LOGIC, MPI_MANUFACTPAGE_DEVICEID_FC949X, + PCI_ANY_ID, PCI_ANY_ID }, + { PCI_VENDOR_ID_LSI_LOGIC, MPI_MANUFACTPAGE_DEVICEID_FC949E, + PCI_ANY_ID, PCI_ANY_ID }, + { PCI_VENDOR_ID_BROCADE, MPI_MANUFACTPAGE_DEVICEID_FC949E, + PCI_ANY_ID, PCI_ANY_ID }, + {0} /* Terminating entry */ +}; +MODULE_DEVICE_TABLE(pci, mptfc_pci_table); + +static struct scsi_transport_template *mptfc_transport_template = NULL; + +static struct fc_function_template mptfc_transport_functions = { + .dd_fcrport_size = 8, + .show_host_node_name = 1, + .show_host_port_name = 1, + .show_host_supported_classes = 1, + .show_host_port_id = 1, + .show_rport_supported_classes = 1, + .show_starget_node_name = 1, + .show_starget_port_name = 1, + .show_starget_port_id = 1, + .set_rport_dev_loss_tmo = mptfc_set_rport_loss_tmo, + .show_rport_dev_loss_tmo = 1, + .show_host_supported_speeds = 1, + .show_host_maxframe_size = 1, + .show_host_speed = 1, + .show_host_fabric_name = 1, + .show_host_port_type = 1, + .show_host_port_state = 1, + .show_host_symbolic_name = 1, +}; + +static int +mptfc_block_error_handler(struct scsi_cmnd *SCpnt, + int (*func)(struct scsi_cmnd *SCpnt), + const char *caller) +{ + MPT_SCSI_HOST *hd; + struct scsi_device *sdev = SCpnt->device; + struct Scsi_Host *shost = sdev->host; + struct fc_rport *rport = starget_to_rport(scsi_target(sdev)); + unsigned long flags; + int ready; + MPT_ADAPTER *ioc; + int loops = 40; /* seconds */ + + hd = shost_priv(SCpnt->device->host); + ioc = hd->ioc; + spin_lock_irqsave(shost->host_lock, flags); + while ((ready = fc_remote_port_chkready(rport) >> 16) == DID_IMM_RETRY + || (loops > 0 && ioc->active == 0)) { + spin_unlock_irqrestore(shost->host_lock, flags); + dfcprintk (ioc, printk(MYIOC_s_DEBUG_FMT + "mptfc_block_error_handler.%d: %d:%llu, port status is " + "%x, active flag %d, deferring %s recovery.\n", + ioc->name, ioc->sh->host_no, + SCpnt->device->id, SCpnt->device->lun, + ready, ioc->active, caller)); + msleep(1000); + spin_lock_irqsave(shost->host_lock, flags); + loops --; + } + spin_unlock_irqrestore(shost->host_lock, flags); + + if (ready == DID_NO_CONNECT || !SCpnt->device->hostdata + || ioc->active == 0) { + dfcprintk (ioc, printk(MYIOC_s_DEBUG_FMT + "%s.%d: %d:%llu, failing recovery, " + "port state %x, active %d, vdevice %p.\n", caller, + ioc->name, ioc->sh->host_no, + SCpnt->device->id, SCpnt->device->lun, ready, + ioc->active, SCpnt->device->hostdata)); + return FAILED; + } + dfcprintk (ioc, printk(MYIOC_s_DEBUG_FMT + "%s.%d: %d:%llu, executing recovery.\n", caller, + ioc->name, ioc->sh->host_no, + SCpnt->device->id, SCpnt->device->lun)); + return (*func)(SCpnt); +} + +static int +mptfc_abort(struct scsi_cmnd *SCpnt) +{ + return + mptfc_block_error_handler(SCpnt, mptscsih_abort, __func__); +} + +static int +mptfc_dev_reset(struct scsi_cmnd *SCpnt) +{ + return + mptfc_block_error_handler(SCpnt, mptscsih_dev_reset, __func__); +} + +static int +mptfc_bus_reset(struct scsi_cmnd *SCpnt) +{ + return + mptfc_block_error_handler(SCpnt, mptscsih_bus_reset, __func__); +} + +static void +mptfc_set_rport_loss_tmo(struct fc_rport *rport, uint32_t timeout) +{ + if (timeout > 0) + rport->dev_loss_tmo = timeout; + else + rport->dev_loss_tmo = mptfc_dev_loss_tmo; +} + +static int +mptfc_FcDevPage0_cmp_func(const void *a, const void *b) +{ + FCDevicePage0_t **aa = (FCDevicePage0_t **)a; + FCDevicePage0_t **bb = (FCDevicePage0_t **)b; + + if ((*aa)->CurrentBus == (*bb)->CurrentBus) { + if ((*aa)->CurrentTargetID == (*bb)->CurrentTargetID) + return 0; + if ((*aa)->CurrentTargetID < (*bb)->CurrentTargetID) + return -1; + return 1; + } + if ((*aa)->CurrentBus < (*bb)->CurrentBus) + return -1; + return 1; +} + +static int +mptfc_GetFcDevPage0(MPT_ADAPTER *ioc, int ioc_port, + void(*func)(MPT_ADAPTER *ioc,int channel, FCDevicePage0_t *arg)) +{ + ConfigPageHeader_t hdr; + CONFIGPARMS cfg; + FCDevicePage0_t *ppage0_alloc, *fc; + dma_addr_t page0_dma; + int data_sz; + int ii; + + FCDevicePage0_t *p0_array=NULL, *p_p0; + FCDevicePage0_t **pp0_array=NULL, **p_pp0; + + int rc = -ENOMEM; + U32 port_id = 0xffffff; + int num_targ = 0; + int max_bus = ioc->facts.MaxBuses; + int max_targ; + + max_targ = (ioc->facts.MaxDevices == 0) ? 256 : ioc->facts.MaxDevices; + + data_sz = sizeof(FCDevicePage0_t) * max_bus * max_targ; + p_p0 = p0_array = kzalloc(data_sz, GFP_KERNEL); + if (!p0_array) + goto out; + + data_sz = sizeof(FCDevicePage0_t *) * max_bus * max_targ; + p_pp0 = pp0_array = kzalloc(data_sz, GFP_KERNEL); + if (!pp0_array) + goto out; + + do { + /* Get FC Device Page 0 header */ + hdr.PageVersion = 0; + hdr.PageLength = 0; + hdr.PageNumber = 0; + hdr.PageType = MPI_CONFIG_PAGETYPE_FC_DEVICE; + cfg.cfghdr.hdr = &hdr; + cfg.physAddr = -1; + cfg.action = MPI_CONFIG_ACTION_PAGE_HEADER; + cfg.dir = 0; + cfg.pageAddr = port_id; + cfg.timeout = 0; + + if ((rc = mpt_config(ioc, &cfg)) != 0) + break; + + if (hdr.PageLength <= 0) + break; + + data_sz = hdr.PageLength * 4; + ppage0_alloc = pci_alloc_consistent(ioc->pcidev, data_sz, + &page0_dma); + rc = -ENOMEM; + if (!ppage0_alloc) + break; + + cfg.physAddr = page0_dma; + cfg.action = MPI_CONFIG_ACTION_PAGE_READ_CURRENT; + + if ((rc = mpt_config(ioc, &cfg)) == 0) { + ppage0_alloc->PortIdentifier = + le32_to_cpu(ppage0_alloc->PortIdentifier); + + ppage0_alloc->WWNN.Low = + le32_to_cpu(ppage0_alloc->WWNN.Low); + + ppage0_alloc->WWNN.High = + le32_to_cpu(ppage0_alloc->WWNN.High); + + ppage0_alloc->WWPN.Low = + le32_to_cpu(ppage0_alloc->WWPN.Low); + + ppage0_alloc->WWPN.High = + le32_to_cpu(ppage0_alloc->WWPN.High); + + ppage0_alloc->BBCredit = + le16_to_cpu(ppage0_alloc->BBCredit); + + ppage0_alloc->MaxRxFrameSize = + le16_to_cpu(ppage0_alloc->MaxRxFrameSize); + + port_id = ppage0_alloc->PortIdentifier; + num_targ++; + *p_p0 = *ppage0_alloc; /* save data */ + *p_pp0++ = p_p0++; /* save addr */ + } + pci_free_consistent(ioc->pcidev, data_sz, + (u8 *) ppage0_alloc, page0_dma); + if (rc != 0) + break; + + } while (port_id <= 0xff0000); + + if (num_targ) { + /* sort array */ + if (num_targ > 1) + sort (pp0_array, num_targ, sizeof(FCDevicePage0_t *), + mptfc_FcDevPage0_cmp_func, NULL); + /* call caller's func for each targ */ + for (ii = 0; ii < num_targ; ii++) { + fc = *(pp0_array+ii); + func(ioc, ioc_port, fc); + } + } + + out: + kfree(pp0_array); + kfree(p0_array); + return rc; +} + +static int +mptfc_generate_rport_ids(FCDevicePage0_t *pg0, struct fc_rport_identifiers *rid) +{ + /* not currently usable */ + if (pg0->Flags & (MPI_FC_DEVICE_PAGE0_FLAGS_PLOGI_INVALID | + MPI_FC_DEVICE_PAGE0_FLAGS_PRLI_INVALID)) + return -1; + + if (!(pg0->Flags & MPI_FC_DEVICE_PAGE0_FLAGS_TARGETID_BUS_VALID)) + return -1; + + if (!(pg0->Protocol & MPI_FC_DEVICE_PAGE0_PROT_FCP_TARGET)) + return -1; + + /* + * board data structure already normalized to platform endianness + * shifted to avoid unaligned access on 64 bit architecture + */ + rid->node_name = ((u64)pg0->WWNN.High) << 32 | (u64)pg0->WWNN.Low; + rid->port_name = ((u64)pg0->WWPN.High) << 32 | (u64)pg0->WWPN.Low; + rid->port_id = pg0->PortIdentifier; + rid->roles = FC_RPORT_ROLE_UNKNOWN; + + return 0; +} + +static void +mptfc_register_dev(MPT_ADAPTER *ioc, int channel, FCDevicePage0_t *pg0) +{ + struct fc_rport_identifiers rport_ids; + struct fc_rport *rport; + struct mptfc_rport_info *ri; + int new_ri = 1; + u64 pn, nn; + VirtTarget *vtarget; + u32 roles = FC_RPORT_ROLE_UNKNOWN; + + if (mptfc_generate_rport_ids(pg0, &rport_ids) < 0) + return; + + roles |= FC_RPORT_ROLE_FCP_TARGET; + if (pg0->Protocol & MPI_FC_DEVICE_PAGE0_PROT_FCP_INITIATOR) + roles |= FC_RPORT_ROLE_FCP_INITIATOR; + + /* scan list looking for a match */ + list_for_each_entry(ri, &ioc->fc_rports, list) { + pn = (u64)ri->pg0.WWPN.High << 32 | (u64)ri->pg0.WWPN.Low; + if (pn == rport_ids.port_name) { /* match */ + list_move_tail(&ri->list, &ioc->fc_rports); + new_ri = 0; + break; + } + } + if (new_ri) { /* allocate one */ + ri = kzalloc(sizeof(struct mptfc_rport_info), GFP_KERNEL); + if (!ri) + return; + list_add_tail(&ri->list, &ioc->fc_rports); + } + + ri->pg0 = *pg0; /* add/update pg0 data */ + ri->flags &= ~MPT_RPORT_INFO_FLAGS_MISSING; + + /* MPT_RPORT_INFO_FLAGS_REGISTERED - rport not previously deleted */ + if (!(ri->flags & MPT_RPORT_INFO_FLAGS_REGISTERED)) { + ri->flags |= MPT_RPORT_INFO_FLAGS_REGISTERED; + rport = fc_remote_port_add(ioc->sh, channel, &rport_ids); + if (rport) { + ri->rport = rport; + if (new_ri) /* may have been reset by user */ + rport->dev_loss_tmo = mptfc_dev_loss_tmo; + /* + * if already mapped, remap here. If not mapped, + * target_alloc will allocate vtarget and map, + * slave_alloc will fill in vdevice from vtarget. + */ + if (ri->starget) { + vtarget = ri->starget->hostdata; + if (vtarget) { + vtarget->id = pg0->CurrentTargetID; + vtarget->channel = pg0->CurrentBus; + vtarget->deleted = 0; + } + } + *((struct mptfc_rport_info **)rport->dd_data) = ri; + /* scan will be scheduled once rport becomes a target */ + fc_remote_port_rolechg(rport,roles); + + pn = (u64)ri->pg0.WWPN.High << 32 | (u64)ri->pg0.WWPN.Low; + nn = (u64)ri->pg0.WWNN.High << 32 | (u64)ri->pg0.WWNN.Low; + dfcprintk (ioc, printk(MYIOC_s_DEBUG_FMT + "mptfc_reg_dev.%d: %x, %llx / %llx, tid %d, " + "rport tid %d, tmo %d\n", + ioc->name, + ioc->sh->host_no, + pg0->PortIdentifier, + (unsigned long long)nn, + (unsigned long long)pn, + pg0->CurrentTargetID, + ri->rport->scsi_target_id, + ri->rport->dev_loss_tmo)); + } else { + list_del(&ri->list); + kfree(ri); + ri = NULL; + } + } +} + +/* + * OS entry point to allow for host driver to free allocated memory + * Called if no device present or device being unloaded + */ +static void +mptfc_target_destroy(struct scsi_target *starget) +{ + struct fc_rport *rport; + struct mptfc_rport_info *ri; + + rport = starget_to_rport(starget); + if (rport) { + ri = *((struct mptfc_rport_info **)rport->dd_data); + if (ri) /* better be! */ + ri->starget = NULL; + } + kfree(starget->hostdata); + starget->hostdata = NULL; +} + +/* + * OS entry point to allow host driver to alloc memory + * for each scsi target. Called once per device the bus scan. + * Return non-zero if allocation fails. + */ +static int +mptfc_target_alloc(struct scsi_target *starget) +{ + VirtTarget *vtarget; + struct fc_rport *rport; + struct mptfc_rport_info *ri; + int rc; + + vtarget = kzalloc(sizeof(VirtTarget), GFP_KERNEL); + if (!vtarget) + return -ENOMEM; + starget->hostdata = vtarget; + + rc = -ENODEV; + rport = starget_to_rport(starget); + if (rport) { + ri = *((struct mptfc_rport_info **)rport->dd_data); + if (ri) { /* better be! */ + vtarget->id = ri->pg0.CurrentTargetID; + vtarget->channel = ri->pg0.CurrentBus; + ri->starget = starget; + rc = 0; + } + } + if (rc != 0) { + kfree(vtarget); + starget->hostdata = NULL; + } + + return rc; +} +/* + * mptfc_dump_lun_info + * @ioc + * @rport + * @sdev + * + */ +static void +mptfc_dump_lun_info(MPT_ADAPTER *ioc, struct fc_rport *rport, struct scsi_device *sdev, + VirtTarget *vtarget) +{ + u64 nn, pn; + struct mptfc_rport_info *ri; + + ri = *((struct mptfc_rport_info **)rport->dd_data); + pn = (u64)ri->pg0.WWPN.High << 32 | (u64)ri->pg0.WWPN.Low; + nn = (u64)ri->pg0.WWNN.High << 32 | (u64)ri->pg0.WWNN.Low; + dfcprintk (ioc, printk(MYIOC_s_DEBUG_FMT + "mptfc_slv_alloc.%d: num_luns %d, sdev.id %d, " + "CurrentTargetID %d, %x %llx %llx\n", + ioc->name, + sdev->host->host_no, + vtarget->num_luns, + sdev->id, ri->pg0.CurrentTargetID, + ri->pg0.PortIdentifier, + (unsigned long long)pn, + (unsigned long long)nn)); +} + + +/* + * OS entry point to allow host driver to alloc memory + * for each scsi device. Called once per device the bus scan. + * Return non-zero if allocation fails. + * Init memory once per LUN. + */ +static int +mptfc_slave_alloc(struct scsi_device *sdev) +{ + MPT_SCSI_HOST *hd; + VirtTarget *vtarget; + VirtDevice *vdevice; + struct scsi_target *starget; + struct fc_rport *rport; + MPT_ADAPTER *ioc; + + starget = scsi_target(sdev); + rport = starget_to_rport(starget); + + if (!rport || fc_remote_port_chkready(rport)) + return -ENXIO; + + hd = shost_priv(sdev->host); + ioc = hd->ioc; + + vdevice = kzalloc(sizeof(VirtDevice), GFP_KERNEL); + if (!vdevice) { + printk(MYIOC_s_ERR_FMT "slave_alloc kmalloc(%zd) FAILED!\n", + ioc->name, sizeof(VirtDevice)); + return -ENOMEM; + } + + + sdev->hostdata = vdevice; + vtarget = starget->hostdata; + + if (vtarget->num_luns == 0) { + vtarget->ioc_id = ioc->id; + vtarget->tflags = MPT_TARGET_FLAGS_Q_YES; + } + + vdevice->vtarget = vtarget; + vdevice->lun = sdev->lun; + + vtarget->num_luns++; + + + mptfc_dump_lun_info(ioc, rport, sdev, vtarget); + + return 0; +} + +static int +mptfc_qcmd(struct Scsi_Host *shost, struct scsi_cmnd *SCpnt) +{ + struct mptfc_rport_info *ri; + struct fc_rport *rport = starget_to_rport(scsi_target(SCpnt->device)); + int err; + VirtDevice *vdevice = SCpnt->device->hostdata; + + if (!vdevice || !vdevice->vtarget) { + SCpnt->result = DID_NO_CONNECT << 16; + SCpnt->scsi_done(SCpnt); + return 0; + } + + err = fc_remote_port_chkready(rport); + if (unlikely(err)) { + SCpnt->result = err; + SCpnt->scsi_done(SCpnt); + return 0; + } + + /* dd_data is null until finished adding target */ + ri = *((struct mptfc_rport_info **)rport->dd_data); + if (unlikely(!ri)) { + SCpnt->result = DID_IMM_RETRY << 16; + SCpnt->scsi_done(SCpnt); + return 0; + } + + return mptscsih_qcmd(SCpnt); +} + +/* + * mptfc_display_port_link_speed - displaying link speed + * @ioc: Pointer to MPT_ADAPTER structure + * @portnum: IOC Port number + * @pp0dest: port page0 data payload + * + */ +static void +mptfc_display_port_link_speed(MPT_ADAPTER *ioc, int portnum, FCPortPage0_t *pp0dest) +{ + u8 old_speed, new_speed, state; + char *old, *new; + + if (portnum >= 2) + return; + + old_speed = ioc->fc_link_speed[portnum]; + new_speed = pp0dest->CurrentSpeed; + state = pp0dest->PortState; + + if (state != MPI_FCPORTPAGE0_PORTSTATE_OFFLINE && + new_speed != MPI_FCPORTPAGE0_CURRENT_SPEED_UNKNOWN) { + + old = old_speed == MPI_FCPORTPAGE0_CURRENT_SPEED_1GBIT ? "1 Gbps" : + old_speed == MPI_FCPORTPAGE0_CURRENT_SPEED_2GBIT ? "2 Gbps" : + old_speed == MPI_FCPORTPAGE0_CURRENT_SPEED_4GBIT ? "4 Gbps" : + "Unknown"; + new = new_speed == MPI_FCPORTPAGE0_CURRENT_SPEED_1GBIT ? "1 Gbps" : + new_speed == MPI_FCPORTPAGE0_CURRENT_SPEED_2GBIT ? "2 Gbps" : + new_speed == MPI_FCPORTPAGE0_CURRENT_SPEED_4GBIT ? "4 Gbps" : + "Unknown"; + if (old_speed == 0) + printk(MYIOC_s_NOTE_FMT + "FC Link Established, Speed = %s\n", + ioc->name, new); + else if (old_speed != new_speed) + printk(MYIOC_s_WARN_FMT + "FC Link Speed Change, Old Speed = %s, New Speed = %s\n", + ioc->name, old, new); + + ioc->fc_link_speed[portnum] = new_speed; + } +} + +/* + * mptfc_GetFcPortPage0 - Fetch FCPort config Page0. + * @ioc: Pointer to MPT_ADAPTER structure + * @portnum: IOC Port number + * + * Return: 0 for success + * -ENOMEM if no memory available + * -EPERM if not allowed due to ISR context + * -EAGAIN if no msg frames currently available + * -EFAULT for non-successful reply or no reply (timeout) + * -EINVAL portnum arg out of range (hardwired to two elements) + */ +static int +mptfc_GetFcPortPage0(MPT_ADAPTER *ioc, int portnum) +{ + ConfigPageHeader_t hdr; + CONFIGPARMS cfg; + FCPortPage0_t *ppage0_alloc; + FCPortPage0_t *pp0dest; + dma_addr_t page0_dma; + int data_sz; + int copy_sz; + int rc; + int count = 400; + + if (portnum > 1) + return -EINVAL; + + /* Get FCPort Page 0 header */ + hdr.PageVersion = 0; + hdr.PageLength = 0; + hdr.PageNumber = 0; + hdr.PageType = MPI_CONFIG_PAGETYPE_FC_PORT; + cfg.cfghdr.hdr = &hdr; + cfg.physAddr = -1; + cfg.action = MPI_CONFIG_ACTION_PAGE_HEADER; + cfg.dir = 0; + cfg.pageAddr = portnum; + cfg.timeout = 0; + + if ((rc = mpt_config(ioc, &cfg)) != 0) + return rc; + + if (hdr.PageLength == 0) + return 0; + + data_sz = hdr.PageLength * 4; + rc = -ENOMEM; + ppage0_alloc = (FCPortPage0_t *) pci_alloc_consistent(ioc->pcidev, data_sz, &page0_dma); + if (ppage0_alloc) { + + try_again: + memset((u8 *)ppage0_alloc, 0, data_sz); + cfg.physAddr = page0_dma; + cfg.action = MPI_CONFIG_ACTION_PAGE_READ_CURRENT; + + if ((rc = mpt_config(ioc, &cfg)) == 0) { + /* save the data */ + pp0dest = &ioc->fc_port_page0[portnum]; + copy_sz = min_t(int, sizeof(FCPortPage0_t), data_sz); + memcpy(pp0dest, ppage0_alloc, copy_sz); + + /* + * Normalize endianness of structure data, + * by byte-swapping all > 1 byte fields! + */ + pp0dest->Flags = le32_to_cpu(pp0dest->Flags); + pp0dest->PortIdentifier = le32_to_cpu(pp0dest->PortIdentifier); + pp0dest->WWNN.Low = le32_to_cpu(pp0dest->WWNN.Low); + pp0dest->WWNN.High = le32_to_cpu(pp0dest->WWNN.High); + pp0dest->WWPN.Low = le32_to_cpu(pp0dest->WWPN.Low); + pp0dest->WWPN.High = le32_to_cpu(pp0dest->WWPN.High); + pp0dest->SupportedServiceClass = le32_to_cpu(pp0dest->SupportedServiceClass); + pp0dest->SupportedSpeeds = le32_to_cpu(pp0dest->SupportedSpeeds); + pp0dest->CurrentSpeed = le32_to_cpu(pp0dest->CurrentSpeed); + pp0dest->MaxFrameSize = le32_to_cpu(pp0dest->MaxFrameSize); + pp0dest->FabricWWNN.Low = le32_to_cpu(pp0dest->FabricWWNN.Low); + pp0dest->FabricWWNN.High = le32_to_cpu(pp0dest->FabricWWNN.High); + pp0dest->FabricWWPN.Low = le32_to_cpu(pp0dest->FabricWWPN.Low); + pp0dest->FabricWWPN.High = le32_to_cpu(pp0dest->FabricWWPN.High); + pp0dest->DiscoveredPortsCount = le32_to_cpu(pp0dest->DiscoveredPortsCount); + pp0dest->MaxInitiators = le32_to_cpu(pp0dest->MaxInitiators); + + /* + * if still doing discovery, + * hang loose a while until finished + */ + if ((pp0dest->PortState == MPI_FCPORTPAGE0_PORTSTATE_UNKNOWN) || + (pp0dest->PortState == MPI_FCPORTPAGE0_PORTSTATE_ONLINE && + (pp0dest->Flags & MPI_FCPORTPAGE0_FLAGS_ATTACH_TYPE_MASK) + == MPI_FCPORTPAGE0_FLAGS_ATTACH_NO_INIT)) { + if (count-- > 0) { + msleep(100); + goto try_again; + } + printk(MYIOC_s_INFO_FMT "Firmware discovery not" + " complete.\n", + ioc->name); + } + mptfc_display_port_link_speed(ioc, portnum, pp0dest); + } + + pci_free_consistent(ioc->pcidev, data_sz, (u8 *) ppage0_alloc, page0_dma); + } + + return rc; +} + +static int +mptfc_WriteFcPortPage1(MPT_ADAPTER *ioc, int portnum) +{ + ConfigPageHeader_t hdr; + CONFIGPARMS cfg; + int rc; + + if (portnum > 1) + return -EINVAL; + + if (!(ioc->fc_data.fc_port_page1[portnum].data)) + return -EINVAL; + + /* get fcport page 1 header */ + hdr.PageVersion = 0; + hdr.PageLength = 0; + hdr.PageNumber = 1; + hdr.PageType = MPI_CONFIG_PAGETYPE_FC_PORT; + cfg.cfghdr.hdr = &hdr; + cfg.physAddr = -1; + cfg.action = MPI_CONFIG_ACTION_PAGE_HEADER; + cfg.dir = 0; + cfg.pageAddr = portnum; + cfg.timeout = 0; + + if ((rc = mpt_config(ioc, &cfg)) != 0) + return rc; + + if (hdr.PageLength == 0) + return -ENODEV; + + if (hdr.PageLength*4 != ioc->fc_data.fc_port_page1[portnum].pg_sz) + return -EINVAL; + + cfg.physAddr = ioc->fc_data.fc_port_page1[portnum].dma; + cfg.action = MPI_CONFIG_ACTION_PAGE_WRITE_CURRENT; + cfg.dir = 1; + + rc = mpt_config(ioc, &cfg); + + return rc; +} + +static int +mptfc_GetFcPortPage1(MPT_ADAPTER *ioc, int portnum) +{ + ConfigPageHeader_t hdr; + CONFIGPARMS cfg; + FCPortPage1_t *page1_alloc; + dma_addr_t page1_dma; + int data_sz; + int rc; + + if (portnum > 1) + return -EINVAL; + + /* get fcport page 1 header */ + hdr.PageVersion = 0; + hdr.PageLength = 0; + hdr.PageNumber = 1; + hdr.PageType = MPI_CONFIG_PAGETYPE_FC_PORT; + cfg.cfghdr.hdr = &hdr; + cfg.physAddr = -1; + cfg.action = MPI_CONFIG_ACTION_PAGE_HEADER; + cfg.dir = 0; + cfg.pageAddr = portnum; + cfg.timeout = 0; + + if ((rc = mpt_config(ioc, &cfg)) != 0) + return rc; + + if (hdr.PageLength == 0) + return -ENODEV; + +start_over: + + if (ioc->fc_data.fc_port_page1[portnum].data == NULL) { + data_sz = hdr.PageLength * 4; + if (data_sz < sizeof(FCPortPage1_t)) + data_sz = sizeof(FCPortPage1_t); + + page1_alloc = (FCPortPage1_t *) pci_alloc_consistent(ioc->pcidev, + data_sz, + &page1_dma); + if (!page1_alloc) + return -ENOMEM; + } + else { + page1_alloc = ioc->fc_data.fc_port_page1[portnum].data; + page1_dma = ioc->fc_data.fc_port_page1[portnum].dma; + data_sz = ioc->fc_data.fc_port_page1[portnum].pg_sz; + if (hdr.PageLength * 4 > data_sz) { + ioc->fc_data.fc_port_page1[portnum].data = NULL; + pci_free_consistent(ioc->pcidev, data_sz, (u8 *) + page1_alloc, page1_dma); + goto start_over; + } + } + + memset(page1_alloc,0,data_sz); + + cfg.physAddr = page1_dma; + cfg.action = MPI_CONFIG_ACTION_PAGE_READ_CURRENT; + + if ((rc = mpt_config(ioc, &cfg)) == 0) { + ioc->fc_data.fc_port_page1[portnum].data = page1_alloc; + ioc->fc_data.fc_port_page1[portnum].pg_sz = data_sz; + ioc->fc_data.fc_port_page1[portnum].dma = page1_dma; + } + else { + ioc->fc_data.fc_port_page1[portnum].data = NULL; + pci_free_consistent(ioc->pcidev, data_sz, (u8 *) + page1_alloc, page1_dma); + } + + return rc; +} + +static void +mptfc_SetFcPortPage1_defaults(MPT_ADAPTER *ioc) +{ + int ii; + FCPortPage1_t *pp1; + + #define MPTFC_FW_DEVICE_TIMEOUT (1) + #define MPTFC_FW_IO_PEND_TIMEOUT (1) + #define ON_FLAGS (MPI_FCPORTPAGE1_FLAGS_IMMEDIATE_ERROR_REPLY) + #define OFF_FLAGS (MPI_FCPORTPAGE1_FLAGS_VERBOSE_RESCAN_EVENTS) + + for (ii=0; ii<ioc->facts.NumberOfPorts; ii++) { + if (mptfc_GetFcPortPage1(ioc, ii) != 0) + continue; + pp1 = ioc->fc_data.fc_port_page1[ii].data; + if ((pp1->InitiatorDeviceTimeout == MPTFC_FW_DEVICE_TIMEOUT) + && (pp1->InitiatorIoPendTimeout == MPTFC_FW_IO_PEND_TIMEOUT) + && ((pp1->Flags & ON_FLAGS) == ON_FLAGS) + && ((pp1->Flags & OFF_FLAGS) == 0)) + continue; + pp1->InitiatorDeviceTimeout = MPTFC_FW_DEVICE_TIMEOUT; + pp1->InitiatorIoPendTimeout = MPTFC_FW_IO_PEND_TIMEOUT; + pp1->Flags &= ~OFF_FLAGS; + pp1->Flags |= ON_FLAGS; + mptfc_WriteFcPortPage1(ioc, ii); + } +} + + +static void +mptfc_init_host_attr(MPT_ADAPTER *ioc,int portnum) +{ + unsigned class = 0; + unsigned cos = 0; + unsigned speed; + unsigned port_type; + unsigned port_state; + FCPortPage0_t *pp0; + struct Scsi_Host *sh; + char *sn; + + /* don't know what to do as only one scsi (fc) host was allocated */ + if (portnum != 0) + return; + + pp0 = &ioc->fc_port_page0[portnum]; + sh = ioc->sh; + + sn = fc_host_symbolic_name(sh); + snprintf(sn, FC_SYMBOLIC_NAME_SIZE, "%s %s%08xh", + ioc->prod_name, + MPT_FW_REV_MAGIC_ID_STRING, + ioc->facts.FWVersion.Word); + + fc_host_tgtid_bind_type(sh) = FC_TGTID_BIND_BY_WWPN; + + fc_host_maxframe_size(sh) = pp0->MaxFrameSize; + + fc_host_node_name(sh) = + (u64)pp0->WWNN.High << 32 | (u64)pp0->WWNN.Low; + + fc_host_port_name(sh) = + (u64)pp0->WWPN.High << 32 | (u64)pp0->WWPN.Low; + + fc_host_port_id(sh) = pp0->PortIdentifier; + + class = pp0->SupportedServiceClass; + if (class & MPI_FCPORTPAGE0_SUPPORT_CLASS_1) + cos |= FC_COS_CLASS1; + if (class & MPI_FCPORTPAGE0_SUPPORT_CLASS_2) + cos |= FC_COS_CLASS2; + if (class & MPI_FCPORTPAGE0_SUPPORT_CLASS_3) + cos |= FC_COS_CLASS3; + fc_host_supported_classes(sh) = cos; + + if (pp0->CurrentSpeed == MPI_FCPORTPAGE0_CURRENT_SPEED_1GBIT) + speed = FC_PORTSPEED_1GBIT; + else if (pp0->CurrentSpeed == MPI_FCPORTPAGE0_CURRENT_SPEED_2GBIT) + speed = FC_PORTSPEED_2GBIT; + else if (pp0->CurrentSpeed == MPI_FCPORTPAGE0_CURRENT_SPEED_4GBIT) + speed = FC_PORTSPEED_4GBIT; + else if (pp0->CurrentSpeed == MPI_FCPORTPAGE0_CURRENT_SPEED_10GBIT) + speed = FC_PORTSPEED_10GBIT; + else + speed = FC_PORTSPEED_UNKNOWN; + fc_host_speed(sh) = speed; + + speed = 0; + if (pp0->SupportedSpeeds & MPI_FCPORTPAGE0_SUPPORT_1GBIT_SPEED) + speed |= FC_PORTSPEED_1GBIT; + if (pp0->SupportedSpeeds & MPI_FCPORTPAGE0_SUPPORT_2GBIT_SPEED) + speed |= FC_PORTSPEED_2GBIT; + if (pp0->SupportedSpeeds & MPI_FCPORTPAGE0_SUPPORT_4GBIT_SPEED) + speed |= FC_PORTSPEED_4GBIT; + if (pp0->SupportedSpeeds & MPI_FCPORTPAGE0_SUPPORT_10GBIT_SPEED) + speed |= FC_PORTSPEED_10GBIT; + fc_host_supported_speeds(sh) = speed; + + port_state = FC_PORTSTATE_UNKNOWN; + if (pp0->PortState == MPI_FCPORTPAGE0_PORTSTATE_ONLINE) + port_state = FC_PORTSTATE_ONLINE; + else if (pp0->PortState == MPI_FCPORTPAGE0_PORTSTATE_OFFLINE) + port_state = FC_PORTSTATE_LINKDOWN; + fc_host_port_state(sh) = port_state; + + port_type = FC_PORTTYPE_UNKNOWN; + if (pp0->Flags & MPI_FCPORTPAGE0_FLAGS_ATTACH_POINT_TO_POINT) + port_type = FC_PORTTYPE_PTP; + else if (pp0->Flags & MPI_FCPORTPAGE0_FLAGS_ATTACH_PRIVATE_LOOP) + port_type = FC_PORTTYPE_LPORT; + else if (pp0->Flags & MPI_FCPORTPAGE0_FLAGS_ATTACH_PUBLIC_LOOP) + port_type = FC_PORTTYPE_NLPORT; + else if (pp0->Flags & MPI_FCPORTPAGE0_FLAGS_ATTACH_FABRIC_DIRECT) + port_type = FC_PORTTYPE_NPORT; + fc_host_port_type(sh) = port_type; + + fc_host_fabric_name(sh) = + (pp0->Flags & MPI_FCPORTPAGE0_FLAGS_FABRIC_WWN_VALID) ? + (u64) pp0->FabricWWNN.High << 32 | (u64) pp0->FabricWWPN.Low : + (u64)pp0->WWNN.High << 32 | (u64)pp0->WWNN.Low; + +} + +static void +mptfc_link_status_change(struct work_struct *work) +{ + MPT_ADAPTER *ioc = + container_of(work, MPT_ADAPTER, fc_rescan_work); + int ii; + + for (ii=0; ii < ioc->facts.NumberOfPorts; ii++) + (void) mptfc_GetFcPortPage0(ioc, ii); + +} + +static void +mptfc_setup_reset(struct work_struct *work) +{ + MPT_ADAPTER *ioc = + container_of(work, MPT_ADAPTER, fc_setup_reset_work); + u64 pn; + struct mptfc_rport_info *ri; + struct scsi_target *starget; + VirtTarget *vtarget; + + /* reset about to happen, delete (block) all rports */ + list_for_each_entry(ri, &ioc->fc_rports, list) { + if (ri->flags & MPT_RPORT_INFO_FLAGS_REGISTERED) { + ri->flags &= ~MPT_RPORT_INFO_FLAGS_REGISTERED; + fc_remote_port_delete(ri->rport); /* won't sleep */ + ri->rport = NULL; + starget = ri->starget; + if (starget) { + vtarget = starget->hostdata; + if (vtarget) + vtarget->deleted = 1; + } + + pn = (u64)ri->pg0.WWPN.High << 32 | + (u64)ri->pg0.WWPN.Low; + dfcprintk (ioc, printk(MYIOC_s_DEBUG_FMT + "mptfc_setup_reset.%d: %llx deleted\n", + ioc->name, + ioc->sh->host_no, + (unsigned long long)pn)); + } + } +} + +static void +mptfc_rescan_devices(struct work_struct *work) +{ + MPT_ADAPTER *ioc = + container_of(work, MPT_ADAPTER, fc_rescan_work); + int ii; + u64 pn; + struct mptfc_rport_info *ri; + struct scsi_target *starget; + VirtTarget *vtarget; + + /* start by tagging all ports as missing */ + list_for_each_entry(ri, &ioc->fc_rports, list) { + if (ri->flags & MPT_RPORT_INFO_FLAGS_REGISTERED) { + ri->flags |= MPT_RPORT_INFO_FLAGS_MISSING; + } + } + + /* + * now rescan devices known to adapter, + * will reregister existing rports + */ + for (ii=0; ii < ioc->facts.NumberOfPorts; ii++) { + (void) mptfc_GetFcPortPage0(ioc, ii); + mptfc_init_host_attr(ioc, ii); /* refresh */ + mptfc_GetFcDevPage0(ioc, ii, mptfc_register_dev); + } + + /* delete devices still missing */ + list_for_each_entry(ri, &ioc->fc_rports, list) { + /* if newly missing, delete it */ + if (ri->flags & MPT_RPORT_INFO_FLAGS_MISSING) { + + ri->flags &= ~(MPT_RPORT_INFO_FLAGS_REGISTERED| + MPT_RPORT_INFO_FLAGS_MISSING); + fc_remote_port_delete(ri->rport); /* won't sleep */ + ri->rport = NULL; + starget = ri->starget; + if (starget) { + vtarget = starget->hostdata; + if (vtarget) + vtarget->deleted = 1; + } + + pn = (u64)ri->pg0.WWPN.High << 32 | + (u64)ri->pg0.WWPN.Low; + dfcprintk (ioc, printk(MYIOC_s_DEBUG_FMT + "mptfc_rescan.%d: %llx deleted\n", + ioc->name, + ioc->sh->host_no, + (unsigned long long)pn)); + } + } +} + +static int +mptfc_probe(struct pci_dev *pdev, const struct pci_device_id *id) +{ + struct Scsi_Host *sh; + MPT_SCSI_HOST *hd; + MPT_ADAPTER *ioc; + unsigned long flags; + int ii; + int numSGE = 0; + int scale; + int ioc_cap; + int error=0; + int r; + + if ((r = mpt_attach(pdev,id)) != 0) + return r; + + ioc = pci_get_drvdata(pdev); + ioc->DoneCtx = mptfcDoneCtx; + ioc->TaskCtx = mptfcTaskCtx; + ioc->InternalCtx = mptfcInternalCtx; + + /* Added sanity check on readiness of the MPT adapter. + */ + if (ioc->last_state != MPI_IOC_STATE_OPERATIONAL) { + printk(MYIOC_s_WARN_FMT + "Skipping because it's not operational!\n", + ioc->name); + error = -ENODEV; + goto out_mptfc_probe; + } + + if (!ioc->active) { + printk(MYIOC_s_WARN_FMT "Skipping because it's disabled!\n", + ioc->name); + error = -ENODEV; + goto out_mptfc_probe; + } + + /* Sanity check - ensure at least 1 port is INITIATOR capable + */ + ioc_cap = 0; + for (ii=0; ii < ioc->facts.NumberOfPorts; ii++) { + if (ioc->pfacts[ii].ProtocolFlags & + MPI_PORTFACTS_PROTOCOL_INITIATOR) + ioc_cap ++; + } + + if (!ioc_cap) { + printk(MYIOC_s_WARN_FMT + "Skipping ioc=%p because SCSI Initiator mode is NOT enabled!\n", + ioc->name, ioc); + return 0; + } + + sh = scsi_host_alloc(&mptfc_driver_template, sizeof(MPT_SCSI_HOST)); + + if (!sh) { + printk(MYIOC_s_WARN_FMT + "Unable to register controller with SCSI subsystem\n", + ioc->name); + error = -1; + goto out_mptfc_probe; + } + + spin_lock_init(&ioc->fc_rescan_work_lock); + INIT_WORK(&ioc->fc_rescan_work, mptfc_rescan_devices); + INIT_WORK(&ioc->fc_setup_reset_work, mptfc_setup_reset); + INIT_WORK(&ioc->fc_lsc_work, mptfc_link_status_change); + + spin_lock_irqsave(&ioc->FreeQlock, flags); + + /* Attach the SCSI Host to the IOC structure + */ + ioc->sh = sh; + + sh->io_port = 0; + sh->n_io_port = 0; + sh->irq = 0; + + /* set 16 byte cdb's */ + sh->max_cmd_len = 16; + + sh->max_id = ioc->pfacts->MaxDevices; + sh->max_lun = max_lun; + + /* Required entry. + */ + sh->unique_id = ioc->id; + + /* Verify that we won't exceed the maximum + * number of chain buffers + * We can optimize: ZZ = req_sz/sizeof(SGE) + * For 32bit SGE's: + * numSGE = 1 + (ZZ-1)*(maxChain -1) + ZZ + * + (req_sz - 64)/sizeof(SGE) + * A slightly different algorithm is required for + * 64bit SGEs. + */ + scale = ioc->req_sz/ioc->SGE_size; + if (ioc->sg_addr_size == sizeof(u64)) { + numSGE = (scale - 1) * + (ioc->facts.MaxChainDepth-1) + scale + + (ioc->req_sz - 60) / ioc->SGE_size; + } else { + numSGE = 1 + (scale - 1) * + (ioc->facts.MaxChainDepth-1) + scale + + (ioc->req_sz - 64) / ioc->SGE_size; + } + + if (numSGE < sh->sg_tablesize) { + /* Reset this value */ + dprintk(ioc, printk(MYIOC_s_DEBUG_FMT + "Resetting sg_tablesize to %d from %d\n", + ioc->name, numSGE, sh->sg_tablesize)); + sh->sg_tablesize = numSGE; + } + + spin_unlock_irqrestore(&ioc->FreeQlock, flags); + + hd = shost_priv(sh); + hd->ioc = ioc; + + /* SCSI needs scsi_cmnd lookup table! + * (with size equal to req_depth*PtrSz!) + */ + ioc->ScsiLookup = kcalloc(ioc->req_depth, sizeof(void *), GFP_KERNEL); + if (!ioc->ScsiLookup) { + error = -ENOMEM; + goto out_mptfc_probe; + } + spin_lock_init(&ioc->scsi_lookup_lock); + + dprintk(ioc, printk(MYIOC_s_DEBUG_FMT "ScsiLookup @ %p\n", + ioc->name, ioc->ScsiLookup)); + + hd->last_queue_full = 0; + + sh->transportt = mptfc_transport_template; + error = scsi_add_host (sh, &ioc->pcidev->dev); + if(error) { + dprintk(ioc, printk(MYIOC_s_ERR_FMT + "scsi_add_host failed\n", ioc->name)); + goto out_mptfc_probe; + } + + /* initialize workqueue */ + + snprintf(ioc->fc_rescan_work_q_name, sizeof(ioc->fc_rescan_work_q_name), + "mptfc_wq_%d", sh->host_no); + ioc->fc_rescan_work_q = + alloc_ordered_workqueue(ioc->fc_rescan_work_q_name, + WQ_MEM_RECLAIM); + if (!ioc->fc_rescan_work_q) { + error = -ENOMEM; + goto out_mptfc_host; + } + + /* + * Pre-fetch FC port WWN and stuff... + * (FCPortPage0_t stuff) + */ + for (ii=0; ii < ioc->facts.NumberOfPorts; ii++) { + (void) mptfc_GetFcPortPage0(ioc, ii); + } + mptfc_SetFcPortPage1_defaults(ioc); + + /* + * scan for rports - + * by doing it via the workqueue, some locking is eliminated + */ + + queue_work(ioc->fc_rescan_work_q, &ioc->fc_rescan_work); + flush_workqueue(ioc->fc_rescan_work_q); + + return 0; + +out_mptfc_host: + scsi_remove_host(sh); + +out_mptfc_probe: + + mptscsih_remove(pdev); + return error; +} + +static struct pci_driver mptfc_driver = { + .name = "mptfc", + .id_table = mptfc_pci_table, + .probe = mptfc_probe, + .remove = mptfc_remove, + .shutdown = mptscsih_shutdown, +#ifdef CONFIG_PM + .suspend = mptscsih_suspend, + .resume = mptscsih_resume, +#endif +}; + +static int +mptfc_event_process(MPT_ADAPTER *ioc, EventNotificationReply_t *pEvReply) +{ + MPT_SCSI_HOST *hd; + u8 event = le32_to_cpu(pEvReply->Event) & 0xFF; + unsigned long flags; + int rc=1; + + if (ioc->bus_type != FC) + return 0; + + devtverboseprintk(ioc, printk(MYIOC_s_DEBUG_FMT "MPT event (=%02Xh) routed to SCSI host driver!\n", + ioc->name, event)); + + if (ioc->sh == NULL || + ((hd = shost_priv(ioc->sh)) == NULL)) + return 1; + + switch (event) { + case MPI_EVENT_RESCAN: + spin_lock_irqsave(&ioc->fc_rescan_work_lock, flags); + if (ioc->fc_rescan_work_q) { + queue_work(ioc->fc_rescan_work_q, + &ioc->fc_rescan_work); + } + spin_unlock_irqrestore(&ioc->fc_rescan_work_lock, flags); + break; + case MPI_EVENT_LINK_STATUS_CHANGE: + spin_lock_irqsave(&ioc->fc_rescan_work_lock, flags); + if (ioc->fc_rescan_work_q) { + queue_work(ioc->fc_rescan_work_q, + &ioc->fc_lsc_work); + } + spin_unlock_irqrestore(&ioc->fc_rescan_work_lock, flags); + break; + default: + rc = mptscsih_event_process(ioc,pEvReply); + break; + } + return rc; +} + +static int +mptfc_ioc_reset(MPT_ADAPTER *ioc, int reset_phase) +{ + int rc; + unsigned long flags; + + rc = mptscsih_ioc_reset(ioc,reset_phase); + if ((ioc->bus_type != FC) || (!rc)) + return rc; + + + dtmprintk(ioc, printk(MYIOC_s_DEBUG_FMT + ": IOC %s_reset routed to FC host driver!\n",ioc->name, + reset_phase==MPT_IOC_SETUP_RESET ? "setup" : ( + reset_phase==MPT_IOC_PRE_RESET ? "pre" : "post"))); + + if (reset_phase == MPT_IOC_SETUP_RESET) { + spin_lock_irqsave(&ioc->fc_rescan_work_lock, flags); + if (ioc->fc_rescan_work_q) { + queue_work(ioc->fc_rescan_work_q, + &ioc->fc_setup_reset_work); + } + spin_unlock_irqrestore(&ioc->fc_rescan_work_lock, flags); + } + + else if (reset_phase == MPT_IOC_PRE_RESET) { + } + + else { /* MPT_IOC_POST_RESET */ + mptfc_SetFcPortPage1_defaults(ioc); + spin_lock_irqsave(&ioc->fc_rescan_work_lock, flags); + if (ioc->fc_rescan_work_q) { + queue_work(ioc->fc_rescan_work_q, + &ioc->fc_rescan_work); + } + spin_unlock_irqrestore(&ioc->fc_rescan_work_lock, flags); + } + return 1; +} + +/*=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=*/ +/** + * mptfc_init - Register MPT adapter(s) as SCSI host(s) with SCSI mid-layer. + * + * Returns 0 for success, non-zero for failure. + */ +static int __init +mptfc_init(void) +{ + int error; + + show_mptmod_ver(my_NAME, my_VERSION); + + /* sanity check module parameters */ + if (mptfc_dev_loss_tmo <= 0) + mptfc_dev_loss_tmo = MPTFC_DEV_LOSS_TMO; + + mptfc_transport_template = + fc_attach_transport(&mptfc_transport_functions); + + if (!mptfc_transport_template) + return -ENODEV; + + mptfcDoneCtx = mpt_register(mptscsih_io_done, MPTFC_DRIVER, + "mptscsih_scandv_complete"); + mptfcTaskCtx = mpt_register(mptscsih_taskmgmt_complete, MPTFC_DRIVER, + "mptscsih_scandv_complete"); + mptfcInternalCtx = mpt_register(mptscsih_scandv_complete, MPTFC_DRIVER, + "mptscsih_scandv_complete"); + + mpt_event_register(mptfcDoneCtx, mptfc_event_process); + mpt_reset_register(mptfcDoneCtx, mptfc_ioc_reset); + + error = pci_register_driver(&mptfc_driver); + if (error) + fc_release_transport(mptfc_transport_template); + + return error; +} + +/*=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=*/ +/** + * mptfc_remove - Remove fc infrastructure for devices + * @pdev: Pointer to pci_dev structure + * + */ +static void mptfc_remove(struct pci_dev *pdev) +{ + MPT_ADAPTER *ioc = pci_get_drvdata(pdev); + struct mptfc_rport_info *p, *n; + struct workqueue_struct *work_q; + unsigned long flags; + int ii; + + /* destroy workqueue */ + if ((work_q=ioc->fc_rescan_work_q)) { + spin_lock_irqsave(&ioc->fc_rescan_work_lock, flags); + ioc->fc_rescan_work_q = NULL; + spin_unlock_irqrestore(&ioc->fc_rescan_work_lock, flags); + destroy_workqueue(work_q); + } + + fc_remove_host(ioc->sh); + + list_for_each_entry_safe(p, n, &ioc->fc_rports, list) { + list_del(&p->list); + kfree(p); + } + + for (ii=0; ii<ioc->facts.NumberOfPorts; ii++) { + if (ioc->fc_data.fc_port_page1[ii].data) { + pci_free_consistent(ioc->pcidev, + ioc->fc_data.fc_port_page1[ii].pg_sz, + (u8 *) ioc->fc_data.fc_port_page1[ii].data, + ioc->fc_data.fc_port_page1[ii].dma); + ioc->fc_data.fc_port_page1[ii].data = NULL; + } + } + + scsi_remove_host(ioc->sh); + + mptscsih_remove(pdev); +} + +/*=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=*/ +/*=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=*/ +/** + * mptfc_exit - Unregisters MPT adapter(s) + * + */ +static void __exit +mptfc_exit(void) +{ + pci_unregister_driver(&mptfc_driver); + fc_release_transport(mptfc_transport_template); + + mpt_reset_deregister(mptfcDoneCtx); + mpt_event_deregister(mptfcDoneCtx); + + mpt_deregister(mptfcInternalCtx); + mpt_deregister(mptfcTaskCtx); + mpt_deregister(mptfcDoneCtx); +} + +module_init(mptfc_init); +module_exit(mptfc_exit); |