sym_glue.c
来自「linux 内核源代码」· C语言 代码 · 共 2,086 行 · 第 1/4 页
C
2,086 行
/* * Device driver for the SYMBIOS/LSILOGIC 53C8XX and 53C1010 family * of PCI-SCSI IO processors. * * Copyright (C) 1999-2001 Gerard Roudier <groudier@free.fr> * Copyright (c) 2003-2005 Matthew Wilcox <matthew@wil.cx> * * This driver is derived from the Linux sym53c8xx driver. * Copyright (C) 1998-2000 Gerard Roudier * * The sym53c8xx driver is derived from the ncr53c8xx driver that had been * a port of the FreeBSD ncr driver to Linux-1.2.13. * * The original ncr driver has been written for 386bsd and FreeBSD by * Wolfgang Stanglmeier <wolf@cologne.de> * Stefan Esser <se@mi.Uni-Koeln.de> * Copyright (C) 1994 Wolfgang Stanglmeier * * Other major contributions: * * NVRAM detection and reading. * Copyright (C) 1997 Richard Waltham <dormouse@farsrobt.demon.co.uk> * *----------------------------------------------------------------------------- * * 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 of the License, 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; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */#include <linux/ctype.h>#include <linux/init.h>#include <linux/module.h>#include <linux/moduleparam.h>#include <linux/spinlock.h>#include <scsi/scsi.h>#include <scsi/scsi_tcq.h>#include <scsi/scsi_device.h>#include <scsi/scsi_transport.h>#include "sym_glue.h"#include "sym_nvram.h"#define NAME53C "sym53c"#define NAME53C8XX "sym53c8xx"struct sym_driver_setup sym_driver_setup = SYM_LINUX_DRIVER_SETUP;unsigned int sym_debug_flags = 0;static char *excl_string;static char *safe_string;module_param_named(cmd_per_lun, sym_driver_setup.max_tag, ushort, 0);module_param_named(burst, sym_driver_setup.burst_order, byte, 0);module_param_named(led, sym_driver_setup.scsi_led, byte, 0);module_param_named(diff, sym_driver_setup.scsi_diff, byte, 0);module_param_named(irqm, sym_driver_setup.irq_mode, byte, 0);module_param_named(buschk, sym_driver_setup.scsi_bus_check, byte, 0);module_param_named(hostid, sym_driver_setup.host_id, byte, 0);module_param_named(verb, sym_driver_setup.verbose, byte, 0);module_param_named(debug, sym_debug_flags, uint, 0);module_param_named(settle, sym_driver_setup.settle_delay, byte, 0);module_param_named(nvram, sym_driver_setup.use_nvram, byte, 0);module_param_named(excl, excl_string, charp, 0);module_param_named(safe, safe_string, charp, 0);MODULE_PARM_DESC(cmd_per_lun, "The maximum number of tags to use by default");MODULE_PARM_DESC(burst, "Maximum burst. 0 to disable, 255 to read from registers");MODULE_PARM_DESC(led, "Set to 1 to enable LED support");MODULE_PARM_DESC(diff, "0 for no differential mode, 1 for BIOS, 2 for always, 3 for not GPIO3");MODULE_PARM_DESC(irqm, "0 for open drain, 1 to leave alone, 2 for totem pole");MODULE_PARM_DESC(buschk, "0 to not check, 1 for detach on error, 2 for warn on error");MODULE_PARM_DESC(hostid, "The SCSI ID to use for the host adapters");MODULE_PARM_DESC(verb, "0 for minimal verbosity, 1 for normal, 2 for excessive");MODULE_PARM_DESC(debug, "Set bits to enable debugging");MODULE_PARM_DESC(settle, "Settle delay in seconds. Default 3");MODULE_PARM_DESC(nvram, "Option currently not used");MODULE_PARM_DESC(excl, "List ioport addresses here to prevent controllers from being attached");MODULE_PARM_DESC(safe, "Set other settings to a \"safe mode\"");MODULE_LICENSE("GPL");MODULE_VERSION(SYM_VERSION);MODULE_AUTHOR("Matthew Wilcox <matthew@wil.cx>");MODULE_DESCRIPTION("NCR, Symbios and LSI 8xx and 1010 PCI SCSI adapters");static void sym2_setup_params(void){ char *p = excl_string; int xi = 0; while (p && (xi < 8)) { char *next_p; int val = (int) simple_strtoul(p, &next_p, 0); sym_driver_setup.excludes[xi++] = val; p = next_p; } if (safe_string) { if (*safe_string == 'y') { sym_driver_setup.max_tag = 0; sym_driver_setup.burst_order = 0; sym_driver_setup.scsi_led = 0; sym_driver_setup.scsi_diff = 1; sym_driver_setup.irq_mode = 0; sym_driver_setup.scsi_bus_check = 2; sym_driver_setup.host_id = 7; sym_driver_setup.verbose = 2; sym_driver_setup.settle_delay = 10; sym_driver_setup.use_nvram = 1; } else if (*safe_string != 'n') { printk(KERN_WARNING NAME53C8XX "Ignoring parameter %s" " passed to safe option", safe_string); } }}static struct scsi_transport_template *sym2_transport_template = NULL;/* * Driver private area in the SCSI command structure. */struct sym_ucmd { /* Override the SCSI pointer structure */ struct completion *eh_done; /* SCSI error handling */};#define SYM_UCMD_PTR(cmd) ((struct sym_ucmd *)(&(cmd)->SCp))#define SYM_SOFTC_PTR(cmd) sym_get_hcb(cmd->device->host)/* * Complete a pending CAM CCB. */void sym_xpt_done(struct sym_hcb *np, struct scsi_cmnd *cmd){ struct sym_ucmd *ucmd = SYM_UCMD_PTR(cmd); BUILD_BUG_ON(sizeof(struct scsi_pointer) < sizeof(struct sym_ucmd)); if (ucmd->eh_done) complete(ucmd->eh_done); scsi_dma_unmap(cmd); cmd->scsi_done(cmd);}/* * Tell the SCSI layer about a BUS RESET. */void sym_xpt_async_bus_reset(struct sym_hcb *np){ printf_notice("%s: SCSI BUS has been reset.\n", sym_name(np)); np->s.settle_time = jiffies + sym_driver_setup.settle_delay * HZ; np->s.settle_time_valid = 1; if (sym_verbose >= 2) printf_info("%s: command processing suspended for %d seconds\n", sym_name(np), sym_driver_setup.settle_delay);}/* * Choose the more appropriate CAM status if * the IO encountered an extended error. */static int sym_xerr_cam_status(int cam_status, int x_status){ if (x_status) { if (x_status & XE_PARITY_ERR) cam_status = DID_PARITY; else if (x_status &(XE_EXTRA_DATA|XE_SODL_UNRUN|XE_SWIDE_OVRUN)) cam_status = DID_ERROR; else if (x_status & XE_BAD_PHASE) cam_status = DID_ERROR; else cam_status = DID_ERROR; } return cam_status;}/* * Build CAM result for a failed or auto-sensed IO. */void sym_set_cam_result_error(struct sym_hcb *np, struct sym_ccb *cp, int resid){ struct scsi_cmnd *cmd = cp->cmd; u_int cam_status, scsi_status, drv_status; drv_status = 0; cam_status = DID_OK; scsi_status = cp->ssss_status; if (cp->host_flags & HF_SENSE) { scsi_status = cp->sv_scsi_status; resid = cp->sv_resid; if (sym_verbose && cp->sv_xerr_status) sym_print_xerr(cmd, cp->sv_xerr_status); if (cp->host_status == HS_COMPLETE && cp->ssss_status == S_GOOD && cp->xerr_status == 0) { cam_status = sym_xerr_cam_status(DID_OK, cp->sv_xerr_status); drv_status = DRIVER_SENSE; /* * Bounce back the sense data to user. */ memset(&cmd->sense_buffer, 0, sizeof(cmd->sense_buffer)); memcpy(cmd->sense_buffer, cp->sns_bbuf, min(sizeof(cmd->sense_buffer), (size_t)SYM_SNS_BBUF_LEN));#if 0 /* * If the device reports a UNIT ATTENTION condition * due to a RESET condition, we should consider all * disconnect CCBs for this unit as aborted. */ if (1) { u_char *p; p = (u_char *) cmd->sense_data; if (p[0]==0x70 && p[2]==0x6 && p[12]==0x29) sym_clear_tasks(np, DID_ABORT, cp->target,cp->lun, -1); }#endif } else { /* * Error return from our internal request sense. This * is bad: we must clear the contingent allegiance * condition otherwise the device will always return * BUSY. Use a big stick. */ sym_reset_scsi_target(np, cmd->device->id); cam_status = DID_ERROR; } } else if (cp->host_status == HS_COMPLETE) /* Bad SCSI status */ cam_status = DID_OK; else if (cp->host_status == HS_SEL_TIMEOUT) /* Selection timeout */ cam_status = DID_NO_CONNECT; else if (cp->host_status == HS_UNEXPECTED) /* Unexpected BUS FREE*/ cam_status = DID_ERROR; else { /* Extended error */ if (sym_verbose) { sym_print_addr(cmd, "COMMAND FAILED (%x %x %x).\n", cp->host_status, cp->ssss_status, cp->xerr_status); } /* * Set the most appropriate value for CAM status. */ cam_status = sym_xerr_cam_status(DID_ERROR, cp->xerr_status); } scsi_set_resid(cmd, resid); cmd->result = (drv_status << 24) + (cam_status << 16) + scsi_status;}static int sym_scatter(struct sym_hcb *np, struct sym_ccb *cp, struct scsi_cmnd *cmd){ int segment; int use_sg; cp->data_len = 0; use_sg = scsi_dma_map(cmd); if (use_sg > 0) { struct scatterlist *sg; struct sym_tcb *tp = &np->target[cp->target]; struct sym_tblmove *data; if (use_sg > SYM_CONF_MAX_SG) { scsi_dma_unmap(cmd); return -1; } data = &cp->phys.data[SYM_CONF_MAX_SG - use_sg]; scsi_for_each_sg(cmd, sg, use_sg, segment) { dma_addr_t baddr = sg_dma_address(sg); unsigned int len = sg_dma_len(sg); if ((len & 1) && (tp->head.wval & EWS)) { len++; cp->odd_byte_adjustment++; } sym_build_sge(np, &data[segment], baddr, len); cp->data_len += len; } } else { segment = -2; } return segment;}/* * Queue a SCSI command. */static int sym_queue_command(struct sym_hcb *np, struct scsi_cmnd *cmd){ struct scsi_device *sdev = cmd->device; struct sym_tcb *tp; struct sym_lcb *lp; struct sym_ccb *cp; int order; /* * Retrieve the target descriptor. */ tp = &np->target[sdev->id]; /* * Select tagged/untagged. */ lp = sym_lp(tp, sdev->lun); order = (lp && lp->s.reqtags) ? M_SIMPLE_TAG : 0; /* * Queue the SCSI IO. */ cp = sym_get_ccb(np, cmd, order); if (!cp) return 1; /* Means resource shortage */ sym_queue_scsiio(np, cmd, cp); return 0;}/* * Setup buffers and pointers that address the CDB. */static inline int sym_setup_cdb(struct sym_hcb *np, struct scsi_cmnd *cmd, struct sym_ccb *cp){ memcpy(cp->cdb_buf, cmd->cmnd, cmd->cmd_len); cp->phys.cmd.addr = CCB_BA(cp, cdb_buf[0]); cp->phys.cmd.size = cpu_to_scr(cmd->cmd_len); return 0;}/* * Setup pointers that address the data and start the I/O. */int sym_setup_data_and_start(struct sym_hcb *np, struct scsi_cmnd *cmd, struct sym_ccb *cp){ u32 lastp, goalp; int dir; /* * Build the CDB. */ if (sym_setup_cdb(np, cmd, cp)) goto out_abort; /* * No direction means no data. */ dir = cmd->sc_data_direction; if (dir != DMA_NONE) { cp->segments = sym_scatter(np, cp, cmd); if (cp->segments < 0) { sym_set_cam_status(cmd, DID_ERROR); goto out_abort; } /* * No segments means no data. */ if (!cp->segments) dir = DMA_NONE; } else { cp->data_len = 0; cp->segments = 0; } /* * Set the data pointer. */ switch (dir) { case DMA_BIDIRECTIONAL: scmd_printk(KERN_INFO, cmd, "got DMA_BIDIRECTIONAL command"); sym_set_cam_status(cmd, DID_ERROR); goto out_abort; case DMA_TO_DEVICE: goalp = SCRIPTA_BA(np, data_out2) + 8; lastp = goalp - 8 - (cp->segments * (2*4)); break; case DMA_FROM_DEVICE: cp->host_flags |= HF_DATA_IN; goalp = SCRIPTA_BA(np, data_in2) + 8; lastp = goalp - 8 - (cp->segments * (2*4)); break; case DMA_NONE: default: lastp = goalp = SCRIPTB_BA(np, no_data); break; } /* * Set all pointers values needed by SCRIPTS. */ cp->phys.head.lastp = cpu_to_scr(lastp); cp->phys.head.savep = cpu_to_scr(lastp); cp->startp = cp->phys.head.savep; cp->goalp = cpu_to_scr(goalp); /* * When `#ifed 1', the code below makes the driver * panic on the first attempt to write to a SCSI device. * It is the first test we want to do after a driver * change that does not seem obviously safe. :) */#if 0 switch (cp->cdb_buf[0]) { case 0x0A: case 0x2A: case 0xAA: panic("XXXXXXXXXXXXX WRITE NOT YET ALLOWED XXXXXXXXXXXXXX\n"); break; default: break; }#endif /* * activate this job. */ sym_put_start_queue(np, cp); return 0;out_abort: sym_free_ccb(np, cp); sym_xpt_done(np, cmd); return 0;}/* * timer daemon. * * Misused to keep the driver running when * interrupts are not configured correctly. */static void sym_timer(struct sym_hcb *np){ unsigned long thistime = jiffies; /* * Restart the timer. */ np->s.timer.expires = thistime + SYM_CONF_TIMER_INTERVAL; add_timer(&np->s.timer); /* * If we are resetting the ncr, wait for settle_time before * clearing it. Then command processing will be resumed. */ if (np->s.settle_time_valid) { if (time_before_eq(np->s.settle_time, thistime)) { if (sym_verbose >= 2 ) printk("%s: command processing resumed\n", sym_name(np)); np->s.settle_time_valid = 0; } return; } /* * Nothing to do for now, but that may come. */ if (np->s.lasttime + 4*HZ < thistime) { np->s.lasttime = thistime; }#ifdef SYM_CONF_PCIQ_MAY_MISS_COMPLETIONS /* * Some way-broken PCI bridges may lead to * completions being lost when the clearing * of the INTFLY flag by the CPU occurs * concurrently with the chip raising this flag. * If this ever happen, lost completions will * be reaped here. */ sym_wakeup_done(np);#endif}/* * PCI BUS error handler. */void sym_log_bus_error(struct Scsi_Host *shost){ struct sym_data *sym_data = shost_priv(shost); struct pci_dev *pdev = sym_data->pdev; unsigned short pci_sts; pci_read_config_word(pdev, PCI_STATUS, &pci_sts); if (pci_sts & 0xf900) { pci_write_config_word(pdev, PCI_STATUS, pci_sts); shost_printk(KERN_WARNING, shost, "PCI bus error: status = 0x%04x\n", pci_sts & 0xf900); }}/* * queuecommand method. Entered with the host adapter lock held and * interrupts disabled. */static int sym53c8xx_queue_command(struct scsi_cmnd *cmd, void (*done)(struct scsi_cmnd *)){ struct sym_hcb *np = SYM_SOFTC_PTR(cmd); struct sym_ucmd *ucp = SYM_UCMD_PTR(cmd); int sts = 0; cmd->scsi_done = done; memset(ucp, 0, sizeof(*ucp)); /* * Shorten our settle_time if needed for * this command not to time out. */
⌨️ 快捷键说明
复制代码Ctrl + C
搜索代码Ctrl + F
全屏模式F11
增大字号Ctrl + =
减小字号Ctrl + -
显示快捷键?