wd33c93.c

来自「linux 内核源代码」· C语言 代码 · 共 2,145 行 · 第 1/5 页

C
2,145
字号
/* * Copyright (c) 1996 John Shifflett, GeoLog Consulting *    john@geolog.com *    jshiffle@netcom.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; 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. *//* * Drew Eckhardt's excellent 'Generic NCR5380' sources from Linux-PC * provided much of the inspiration and some of the code for this * driver. Everything I know about Amiga DMA was gleaned from careful * reading of Hamish Mcdonald's original wd33c93 driver; in fact, I * borrowed shamelessly from all over that source. Thanks Hamish! * * _This_ driver is (I feel) an improvement over the old one in * several respects: * *    -  Target Disconnection/Reconnection  is now supported. Any *          system with more than one device active on the SCSI bus *          will benefit from this. The driver defaults to what I *          call 'adaptive disconnect' - meaning that each command *          is evaluated individually as to whether or not it should *          be run with the option to disconnect/reselect (if the *          device chooses), or as a "SCSI-bus-hog". * *    -  Synchronous data transfers are now supported. Because of *          a few devices that choke after telling the driver that *          they can do sync transfers, we don't automatically use *          this faster protocol - it can be enabled via the command- *          line on a device-by-device basis. * *    -  Runtime operating parameters can now be specified through *       the 'amiboot' or the 'insmod' command line. For amiboot do: *          "amiboot [usual stuff] wd33c93=blah,blah,blah" *       The defaults should be good for most people. See the comment *       for 'setup_strings' below for more details. * *    -  The old driver relied exclusively on what the Western Digital *          docs call "Combination Level 2 Commands", which are a great *          idea in that the CPU is relieved of a lot of interrupt *          overhead. However, by accepting a certain (user-settable) *          amount of additional interrupts, this driver achieves *          better control over the SCSI bus, and data transfers are *          almost as fast while being much easier to define, track, *          and debug. * * * TODO: *       more speed. linked commands. * * * People with bug reports, wish-lists, complaints, comments, * or improvements are asked to pah-leeez email me (John Shifflett) * at john@geolog.com or jshiffle@netcom.com! I'm anxious to get * this thing into as good a shape as possible, and I'm positive * there are lots of lurking bugs and "Stupid Places". * * Updates: * * Added support for pre -A chips, which don't have advanced features * and will generate CSR_RESEL rather than CSR_RESEL_AM. *	Richard Hirst <richard@sleepie.demon.co.uk>  August 2000 * * Added support for Burst Mode DMA and Fast SCSI. Enabled the use of * default_sx_per for asynchronous data transfers. Added adjustment * of transfer periods in sx_table to the actual input-clock. *  peter fuerst <post@pfrst.de>  February 2007 */#include <linux/module.h>#include <linux/string.h>#include <linux/delay.h>#include <linux/init.h>#include <linux/interrupt.h>#include <linux/blkdev.h>#include <scsi/scsi.h>#include <scsi/scsi_cmnd.h>#include <scsi/scsi_device.h>#include <scsi/scsi_host.h>#include <asm/irq.h>#include "wd33c93.h"#define optimum_sx_per(hostdata) (hostdata)->sx_table[1].period_ns#define WD33C93_VERSION    "1.26++"#define WD33C93_DATE       "10/Feb/2007"MODULE_AUTHOR("John Shifflett");MODULE_DESCRIPTION("Generic WD33C93 SCSI driver");MODULE_LICENSE("GPL");/* * 'setup_strings' is a single string used to pass operating parameters and * settings from the kernel/module command-line to the driver. 'setup_args[]' * is an array of strings that define the compile-time default values for * these settings. If Linux boots with an amiboot or insmod command-line, * those settings are combined with 'setup_args[]'. Note that amiboot * command-lines are prefixed with "wd33c93=" while insmod uses a * "setup_strings=" prefix. The driver recognizes the following keywords * (lower case required) and arguments: * * -  nosync:bitmask -bitmask is a byte where the 1st 7 bits correspond with *                    the 7 possible SCSI devices. Set a bit to negotiate for *                    asynchronous transfers on that device. To maintain *                    backwards compatibility, a command-line such as *                    "wd33c93=255" will be automatically translated to *                    "wd33c93=nosync:0xff". * -  nodma:x        -x = 1 to disable DMA, x = 0 to enable it. Argument is *                    optional - if not present, same as "nodma:1". * -  period:ns      -ns is the minimum # of nanoseconds in a SCSI data transfer *                    period. Default is 500; acceptable values are 250 - 1000. * -  disconnect:x   -x = 0 to never allow disconnects, 2 to always allow them. *                    x = 1 does 'adaptive' disconnects, which is the default *                    and generally the best choice. * -  debug:x        -If 'DEBUGGING_ON' is defined, x is a bit mask that causes *                    various types of debug output to printed - see the DB_xxx *                    defines in wd33c93.h * -  clock:x        -x = clock input in MHz for WD33c93 chip. Normal values *                    would be from 8 through 20. Default is 8. * -  burst:x        -x = 1 to use Burst Mode (or Demand-Mode) DMA, x = 0 to use *                    Single Byte DMA, which is the default. Argument is *                    optional - if not present, same as "burst:1". * -  fast:x         -x = 1 to enable Fast SCSI, which is only effective with *                    input-clock divisor 4 (WD33C93_FS_16_20), x = 0 to disable *                    it, which is the default.  Argument is optional - if not *                    present, same as "fast:1". * -  next           -No argument. Used to separate blocks of keywords when *                    there's more than one host adapter in the system. * * Syntax Notes: * -  Numeric arguments can be decimal or the '0x' form of hex notation. There *    _must_ be a colon between a keyword and its numeric argument, with no *    spaces. * -  Keywords are separated by commas, no spaces, in the standard kernel *    command-line manner. * -  A keyword in the 'nth' comma-separated command-line member will overwrite *    the 'nth' element of setup_args[]. A blank command-line member (in *    other words, a comma with no preceding keyword) will _not_ overwrite *    the corresponding setup_args[] element. * -  If a keyword is used more than once, the first one applies to the first *    SCSI host found, the second to the second card, etc, unless the 'next' *    keyword is used to change the order. * * Some amiboot examples (for insmod, use 'setup_strings' instead of 'wd33c93'): * -  wd33c93=nosync:255 * -  wd33c93=nodma * -  wd33c93=nodma:1 * -  wd33c93=disconnect:2,nosync:0x08,period:250 * -  wd33c93=debug:0x1c *//* Normally, no defaults are specified */static char *setup_args[] = { "", "", "", "", "", "", "", "", "", "" };static char *setup_strings;module_param(setup_strings, charp, 0);static void wd33c93_execute(struct Scsi_Host *instance);#ifdef CONFIG_WD33C93_PIOstatic inline ucharread_wd33c93(const wd33c93_regs regs, uchar reg_num){	uchar data;	outb(reg_num, regs.SASR);	data = inb(regs.SCMD);	return data;}static inline unsigned longread_wd33c93_count(const wd33c93_regs regs){	unsigned long value;	outb(WD_TRANSFER_COUNT_MSB, regs.SASR);	value = inb(regs.SCMD) << 16;	value |= inb(regs.SCMD) << 8;	value |= inb(regs.SCMD);	return value;}static inline ucharread_aux_stat(const wd33c93_regs regs){	return inb(regs.SASR);}static inline voidwrite_wd33c93(const wd33c93_regs regs, uchar reg_num, uchar value){      outb(reg_num, regs.SASR);      outb(value, regs.SCMD);}static inline voidwrite_wd33c93_count(const wd33c93_regs regs, unsigned long value){	outb(WD_TRANSFER_COUNT_MSB, regs.SASR);	outb((value >> 16) & 0xff, regs.SCMD);	outb((value >> 8) & 0xff, regs.SCMD);	outb( value & 0xff, regs.SCMD);}#define write_wd33c93_cmd(regs, cmd) \	write_wd33c93((regs), WD_COMMAND, (cmd))static inline voidwrite_wd33c93_cdb(const wd33c93_regs regs, uint len, uchar cmnd[]){	int i;	outb(WD_CDB_1, regs.SASR);	for (i=0; i<len; i++)		outb(cmnd[i], regs.SCMD);}#else /* CONFIG_WD33C93_PIO */static inline ucharread_wd33c93(const wd33c93_regs regs, uchar reg_num){	*regs.SASR = reg_num;	mb();	return (*regs.SCMD);}static unsigned longread_wd33c93_count(const wd33c93_regs regs){	unsigned long value;	*regs.SASR = WD_TRANSFER_COUNT_MSB;	mb();	value = *regs.SCMD << 16;	value |= *regs.SCMD << 8;	value |= *regs.SCMD;	mb();	return value;}static inline ucharread_aux_stat(const wd33c93_regs regs){	return *regs.SASR;}static inline voidwrite_wd33c93(const wd33c93_regs regs, uchar reg_num, uchar value){	*regs.SASR = reg_num;	mb();	*regs.SCMD = value;	mb();}static voidwrite_wd33c93_count(const wd33c93_regs regs, unsigned long value){	*regs.SASR = WD_TRANSFER_COUNT_MSB;	mb();	*regs.SCMD = value >> 16;	*regs.SCMD = value >> 8;	*regs.SCMD = value;	mb();}static inline voidwrite_wd33c93_cmd(const wd33c93_regs regs, uchar cmd){	*regs.SASR = WD_COMMAND;	mb();	*regs.SCMD = cmd;	mb();}static inline voidwrite_wd33c93_cdb(const wd33c93_regs regs, uint len, uchar cmnd[]){	int i;	*regs.SASR = WD_CDB_1;	for (i = 0; i < len; i++)		*regs.SCMD = cmnd[i];}#endif /* CONFIG_WD33C93_PIO */static inline ucharread_1_byte(const wd33c93_regs regs){	uchar asr;	uchar x = 0;	write_wd33c93(regs, WD_CONTROL, CTRL_IDI | CTRL_EDI | CTRL_POLLED);	write_wd33c93_cmd(regs, WD_CMD_TRANS_INFO | 0x80);	do {		asr = read_aux_stat(regs);		if (asr & ASR_DBR)			x = read_wd33c93(regs, WD_DATA);	} while (!(asr & ASR_INT));	return x;}static intround_period(unsigned int period, const struct sx_period *sx_table){	int x;	for (x = 1; sx_table[x].period_ns; x++) {		if ((period <= sx_table[x - 0].period_ns) &&		    (period > sx_table[x - 1].period_ns)) {			return x;		}	}	return 7;}/* * Calculate Synchronous Transfer Register value from SDTR code. */static ucharcalc_sync_xfer(unsigned int period, unsigned int offset, unsigned int fast,               const struct sx_period *sx_table){	/* When doing Fast SCSI synchronous data transfers, the corresponding	 * value in 'sx_table' is two times the actually used transfer period.	 */	uchar result;	if (offset && fast) {		fast = STR_FSS;		period *= 2;	} else {		fast = 0;	}	period *= 4;		/* convert SDTR code to ns */	result = sx_table[round_period(period,sx_table)].reg_value;	result |= (offset < OPTIMUM_SX_OFF) ? offset : OPTIMUM_SX_OFF;	result |= fast;	return result;}/* * Calculate SDTR code bytes [3],[4] from period and offset. */static inline voidcalc_sync_msg(unsigned int period, unsigned int offset, unsigned int fast,                uchar  msg[2]){	/* 'period' is a "normal"-mode value, like the ones in 'sx_table'. The	 * actually used transfer period for Fast SCSI synchronous data	 * transfers is half that value.	 */	period /= 4;	if (offset && fast)		period /= 2;	msg[0] = period;	msg[1] = offset;}intwd33c93_queuecommand(struct scsi_cmnd *cmd,		void (*done)(struct scsi_cmnd *)){	struct WD33C93_hostdata *hostdata;	struct scsi_cmnd *tmp;	hostdata = (struct WD33C93_hostdata *) cmd->device->host->hostdata;	DB(DB_QUEUE_COMMAND,	   printk("Q-%d-%02x-%ld( ", cmd->device->id, cmd->cmnd[0], cmd->serial_number))/* Set up a few fields in the scsi_cmnd structure for our own use: *  - host_scribble is the pointer to the next cmd in the input queue *  - scsi_done points to the routine we call when a cmd is finished *  - result is what you'd expect */	cmd->host_scribble = NULL;	cmd->scsi_done = done;	cmd->result = 0;/* We use the Scsi_Pointer structure that's included with each command * as a scratchpad (as it's intended to be used!). The handy thing about * the SCp.xxx fields is that they're always associated with a given * cmd, and are preserved across disconnect-reselect. This means we * can pretty much ignore SAVE_POINTERS and RESTORE_POINTERS messages * if we keep all the critical pointers and counters in SCp: *  - SCp.ptr is the pointer into the RAM buffer *  - SCp.this_residual is the size of that buffer *  - SCp.buffer points to the current scatter-gather buffer *  - SCp.buffers_residual tells us how many S.G. buffers there are *  - SCp.have_data_in is not used *  - SCp.sent_command is not used *  - SCp.phase records this command's SRCID_ER bit setting */	if (cmd->use_sg) {		cmd->SCp.buffer = (struct scatterlist *) cmd->request_buffer;		cmd->SCp.buffers_residual = cmd->use_sg - 1;		cmd->SCp.ptr = sg_virt(cmd->SCp.buffer);		cmd->SCp.this_residual = cmd->SCp.buffer->length;	} else {		cmd->SCp.buffer = NULL;		cmd->SCp.buffers_residual = 0;		cmd->SCp.ptr = (char *) cmd->request_buffer;		cmd->SCp.this_residual = cmd->request_bufflen;	}/* WD docs state that at the conclusion of a "LEVEL2" command, the * status byte can be retrieved from the LUN register. Apparently, * this is the case only for *uninterrupted* LEVEL2 commands! If * there are any unexpected phases entered, even if they are 100% * legal (different devices may choose to do things differently), * the LEVEL2 command sequence is exited. This often occurs prior * to receiving the status byte, in which case the driver does a * status phase interrupt and gets the status byte on its own.

⌨️ 快捷键说明

复制代码Ctrl + C
搜索代码Ctrl + F
全屏模式F11
增大字号Ctrl + =
减小字号Ctrl + -
显示快捷键?