NetBSD/sys/dev/ic/adwlib.c
1998-09-26 19:53:34 +00:00

1334 lines
38 KiB
C

/* $NetBSD: adwlib.c,v 1.2 1998/09/26 19:54:22 dante Exp $ */
/*
* Low level routines for the Advanced Systems Inc. SCSI controllers chips
*
* Copyright (c) 1998 The NetBSD Foundation, Inc.
* All rights reserved.
*
* Author: Baldassare Dante Profeta <dante@mclink.it>
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. All advertising materials mentioning features or use of this software
* must display the following acknowledgement:
* This product includes software developed by the NetBSD
* Foundation, Inc. and its contributors.
* 4. Neither the name of The NetBSD Foundation nor the names of its
* contributors may be used to endorse or promote products derived
* from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
* ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
* TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
* BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) 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 OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
/*
* Ported from:
*/
/*
* advansys.c - Linux Host Driver for AdvanSys SCSI Adapters
*
* Copyright (c) 1995-1998 Advanced System Products, Inc.
* All Rights Reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that redistributions of source
* code retain the above copyright notice and this comment without
* modification.
*/
#include <sys/types.h>
#include <sys/param.h>
#include <sys/systm.h>
#include <sys/malloc.h>
#include <sys/kernel.h>
#include <sys/queue.h>
#include <sys/device.h>
#include <machine/bus.h>
#include <machine/intr.h>
#include <dev/scsipi/scsi_all.h>
#include <dev/scsipi/scsipi_all.h>
#include <dev/scsipi/scsiconf.h>
#include <vm/vm.h>
#include <vm/vm_param.h>
#include <vm/pmap.h>
#include <dev/ic/adwlib.h>
#include <dev/ic/adw.h>
#include <dev/ic/adwmcode.h>
/* Static Functions */
static u_int16_t AdvGetEEPConfig __P((bus_space_tag_t, bus_space_handle_t,
ADWEEP_CONFIG *));
static u_int16_t AdvReadEEPWord __P((bus_space_tag_t, bus_space_handle_t,
int));
static void AdvWaitEEPCmd __P((bus_space_tag_t, bus_space_handle_t));
static void AdvSetEEPConfig __P((bus_space_tag_t, bus_space_handle_t,
ADWEEP_CONFIG *));
static int AdvSendScsiCmd __P((ADW_SOFTC *, ADW_SCSI_REQ_Q *));
static void AdvInquiryHandling __P((ADW_SOFTC *, ADW_SCSI_REQ_Q *));
static void DvcSleepMilliSecond __P((ulong));
static void DvcDelayMicroSecond __P((ulong));
/*
* EEPROM Configuration.
*
* All drivers should use this structure to set the default EEPROM
* configuration. The BIOS now uses this structure when it is built.
* Additional structure information can be found in advlib.h where
* the structure is defined.
*/
static ADWEEP_CONFIG
Default_EEPROM_Config = {
ADW_EEPROM_BIOS_ENABLE, /* cfg_msw */
0x0000, /* cfg_lsw */
0xFFFF, /* disc_enable */
0xFFFF, /* wdtr_able */
0xFFFF, /* sdtr_able */
0xFFFF, /* start_motor */
0xFFFF, /* tagqng_able */
0xFFFF, /* bios_scan */
0, /* scam_tolerant */
7, /* adapter_scsi_id */
0, /* bios_boot_delay */
3, /* scsi_reset_delay */
0, /* bios_id_lun */
0, /* termination */
0, /* reserved1 */
0xFFEF, /* bios_ctrl */
0xFFFF, /* ultra_able */
0, /* reserved2 */
ASC_DEF_MAX_HOST_QNG, /* max_host_qng */
ASC_DEF_MAX_DVC_QNG, /* max_dvc_qng */
0, /* dvc_cntl */
0, /* bug_fix */
0, /* serial_number_word1 */
0, /* serial_number_word2 */
0, /* serial_number_word3 */
0, /* check_sum */
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, /* oem_name[16] */
0, /* dvc_err_code */
0, /* adv_err_code */
0, /* adv_err_addr */
0, /* saved_dvc_err_code */
0, /* saved_adv_err_code */
0, /* saved_adv_err_addr */
0 /* num_of_err */
};
/*
* Initialize the ASC3550.
*
* On failure set the ADW_SOFTC field 'err_code' and return ADW_ERROR.
*
* For a non-fatal error return a warning code. If there are no warnings
* then 0 is returned.
*/
int
AdvInitAsc3550Driver(sc)
ADW_SOFTC *sc;
{
bus_space_tag_t iot = sc->sc_iot;
bus_space_handle_t ioh = sc->sc_ioh;
u_int16_t warn_code;
u_int32_t sum;
int begin_addr;
int end_addr;
int code_sum;
int word;
int rql_addr; /* RISC Queue List address */
int i;
u_int16_t scsi_cfg1;
u_int8_t biosmem[ASC_MC_BIOSLEN]; /* BIOS RISC Memory
* 0x40-0x8F */
warn_code = 0;
/*
* Save the RISC memory BIOS region before writing the microcode.
* The BIOS may already be loaded and using its RISC LRAM region
* so its region must be saved and restored.
*
* Note: This code makes the assumption, which is currently true,
* that a chip reset does not clear RISC LRAM.
*/
for (i = 0; i < ASC_MC_BIOSLEN; i++) {
ADW_READ_BYTE_LRAM(iot, ioh, ASC_MC_BIOSMEM + i, biosmem[i]);
}
/*
* Load the Microcode
*
* Write the microcode image to RISC memory starting at address 0.
*/
ADW_WRITE_WORD_REGISTER(iot, ioh, IOPW_RAM_ADDR, 0);
for (word = 0; word < adv_mcode_size; word += 2) {
ADW_WRITE_WORD_AUTO_INC_LRAM(iot, ioh,
*((u_int16_t *) (&adv_mcode[word])));
}
/*
* Clear the rest of Condor's Internal RAM (8KB).
*/
for (; word < ADW_CONDOR_MEMSIZE; word += 2) {
ADW_WRITE_WORD_AUTO_INC_LRAM(iot, ioh, 0);
}
/*
* Verify the microcode checksum.
*/
sum = 0;
ADW_WRITE_WORD_REGISTER(iot, ioh, IOPW_RAM_ADDR, 0);
for (word = 0; word < adv_mcode_size; word += 2) {
sum += ADW_READ_WORD_AUTO_INC_LRAM(iot, ioh);
}
if (sum != adv_mcode_chksum)
return ASC_IERR_MCODE_CHKSUM;
/*
* Restore the RISC memory BIOS region.
*/
for (i = 0; i < ASC_MC_BIOSLEN; i++) {
ADW_WRITE_BYTE_LRAM(iot, ioh, ASC_MC_BIOSMEM + i, biosmem[i]);
}
/*
* Calculate and write the microcode code checksum to the microcode
* code checksum location ASC_MC_CODE_CHK_SUM (0x2C).
*/
ADW_READ_WORD_LRAM(iot, ioh, ASC_MC_CODE_BEGIN_ADDR, begin_addr);
ADW_READ_WORD_LRAM(iot, ioh, ASC_MC_CODE_END_ADDR, end_addr);
code_sum = 0;
for (word = begin_addr; word < end_addr; word += 2) {
code_sum += *((u_int16_t *) (&adv_mcode[word]));
}
ADW_WRITE_WORD_LRAM(iot, ioh, ASC_MC_CODE_CHK_SUM, code_sum);
/*
* Read microcode version and date.
*/
ADW_READ_WORD_LRAM(iot, ioh, ASC_MC_VERSION_DATE, sc->cfg.mcode_date);
ADW_READ_WORD_LRAM(iot, ioh, ASC_MC_VERSION_NUM, sc->cfg.mcode_version);
/*
* Initialize microcode operating variables
*/
ADW_WRITE_WORD_LRAM(iot, ioh, ASC_MC_ADAPTER_SCSI_ID,
sc->chip_scsi_id);
/*
* If the PCI Configuration Command Register "Parity Error Response
* Control" Bit was clear (0), then set the microcode variable
* 'control_flag' CONTROL_FLAG_IGNORE_PERR flag to tell the microcode
* to ignore DMA parity errors.
*/
if (sc->cfg.control_flag & CONTROL_FLAG_IGNORE_PERR) {
ADW_READ_WORD_LRAM(iot, ioh, ASC_MC_CONTROL_FLAG, word);
word |= CONTROL_FLAG_IGNORE_PERR;
ADW_WRITE_WORD_LRAM(iot, ioh, ASC_MC_CONTROL_FLAG, word);
}
/*
* Set default microcode operating variables for WDTR, SDTR, and
* command tag queuing based on the EEPROM configuration values.
*
* These ADW_DVC_VAR fields and the microcode variables will be
* changed in AdvInquiryHandling() if it is found a device is
* incapable of a particular feature.
*/
/*
* Set the microcode ULTRA target mask from EEPROM value. The
* SDTR target mask overrides the ULTRA target mask in the
* microcode so it is safe to set this value without determining
* whether the device supports SDTR.
*
* Note: There is no way to know whether a device supports ULTRA
* speed without attempting a SDTR ULTRA speed negotiation with
* the device. The device will reject the speed if it does not
* support it by responding with an SDTR message containing a
* slower speed.
*/
ADW_WRITE_WORD_LRAM(iot, ioh, ASC_MC_ULTRA_ABLE, sc->ultra_able);
ADW_WRITE_WORD_LRAM(iot, ioh, ASC_MC_DISC_ENABLE, sc->cfg.disc_enable);
/*
* Set SCSI_CFG0 Microcode Default Value.
*
* The microcode will set the SCSI_CFG0 register using this value
* after it is started below.
*/
ADW_WRITE_WORD_LRAM(iot, ioh, ASC_MC_DEFAULT_SCSI_CFG0,
ADW_PARITY_EN | ADW_SEL_TMO_LONG | ADW_OUR_ID_EN | sc->chip_scsi_id);
/*
* Determine SCSI_CFG1 Microcode Default Value.
*
* The microcode will set the SCSI_CFG1 register using this value
* after it is started below.
*/
/* Read current SCSI_CFG1 Register value. */
scsi_cfg1 = ADW_READ_WORD_REGISTER(iot, ioh, IOPW_SCSI_CFG1);
/*
* If all three connectors are in use, return an error.
*/
if ((scsi_cfg1 & CABLE_ILLEGAL_A) == 0 ||
(scsi_cfg1 & CABLE_ILLEGAL_B) == 0) {
return ASC_IERR_ILLEGAL_CONNECTION;
}
/*
* If the internal narrow cable is reversed all of the SCSI_CTRL
* register signals will be set. Check for and return an error if
* this condition is found.
*/
if ((ADW_READ_WORD_REGISTER(iot, ioh, IOPW_SCSI_CTRL) & 0x3F07) ==
0x3F07) {
return ASC_IERR_REVERSED_CABLE;
}
/*
* If this is a differential board and a single-ended device
* is attached to one of the connectors, return an error.
*/
if ((scsi_cfg1 & ADW_DIFF_MODE) && (scsi_cfg1 & ADW_DIFF_SENSE) == 0)
return ASC_IERR_SINGLE_END_DEVICE;
/*
* If automatic termination control is enabled, then set the
* termination value based on a table listed in advlib.h.
*
* If manual termination was specified with an EEPROM setting
* then 'termination' was set-up in AdvInitFromEEP() and
* is ready to be 'ored' into SCSI_CFG1.
*/
if (sc->cfg.termination == 0) {
/*
* The software always controls termination by setting
* ADW_TERM_CTL_SEL.
* If ADW_TERM_CTL_SEL were set to 0, the hardware would
* set termination.
*/
sc->cfg.termination |= ADW_TERM_CTL_SEL;
switch (scsi_cfg1 & ADW_CABLE_DETECT) {
/* ADW_TERM_CTL_H: on, ADW_TERM_CTL_L: on */
case 0x3:
case 0x7:
case 0xB:
case 0xD:
case 0xE:
case 0xF:
sc->cfg.termination |= (ADW_TERM_CTL_H |
ADW_TERM_CTL_L);
break;
/* ADW_TERM_CTL_H: on, ADW_TERM_CTL_L: off */
case 0x1:
case 0x5:
case 0x9:
case 0xA:
case 0xC:
sc->cfg.termination |= ADW_TERM_CTL_H;
break;
/* ADW_TERM_CTL_H: off, ADW_TERM_CTL_L: off */
case 0x2:
case 0x6:
break;
}
}
/*
* Clear any set ADW_TERM_CTL_H and ADW_TERM_CTL_L bits.
*/
scsi_cfg1 &= ~ADW_TERM_CTL;
/*
* Invert the ADW_TERM_CTL_H and ADW_TERM_CTL_L bits and then
* set 'scsi_cfg1'. The ADW_TERM_POL bit does not need to be
* referenced, because the hardware internally inverts
* the Termination High and Low bits if ADW_TERM_POL is set.
*/
scsi_cfg1 |= (ADW_TERM_CTL_SEL | (~sc->cfg.termination & ADW_TERM_CTL));
/*
* Set SCSI_CFG1 Microcode Default Value
*
* Set filter value and possibly modified termination control
* bits in the Microcode SCSI_CFG1 Register Value.
*
* The microcode will set the SCSI_CFG1 register using this value
* after it is started below.
*/
ADW_WRITE_WORD_LRAM(iot, ioh, ASC_MC_DEFAULT_SCSI_CFG1,
ADW_FLTR_11_TO_20NS | scsi_cfg1);
/*
* Set SEL_MASK Microcode Default Value
*
* The microcode will set the SEL_MASK register using this value
* after it is started below.
*/
ADW_WRITE_WORD_LRAM(iot, ioh, ASC_MC_DEFAULT_SEL_MASK,
ADW_TID_TO_TIDMASK(sc->chip_scsi_id));
/*
* Link all the RISC Queue Lists together in a doubly-linked
* NULL terminated list.
*
* Skip the NULL (0) queue which is not used.
*/
for (i = 1, rql_addr = ASC_MC_RISC_Q_LIST_BASE+ASC_MC_RISC_Q_LIST_SIZE;
i < ASC_MC_RISC_Q_TOTAL_CNT;
i++, rql_addr += ASC_MC_RISC_Q_LIST_SIZE) {
/*
* Set the current RISC Queue List's RQL_FWD and
* RQL_BWD pointers in a one word write and set
* the state (RQL_STATE) to free.
*/
ADW_WRITE_WORD_LRAM(iot, ioh, rql_addr,
((i + 1) + ((i - 1) << 8)));
ADW_WRITE_BYTE_LRAM(iot, ioh, rql_addr + RQL_STATE,
ASC_MC_QS_FREE);
}
/*
* Set the Host and RISC Queue List pointers.
*
* Both sets of pointers are initialized with the same values:
* ASC_MC_RISC_Q_FIRST(0x01) and ASC_MC_RISC_Q_LAST (0xFF).
*/
ADW_WRITE_BYTE_LRAM(iot, ioh, ASC_MC_HOST_NEXT_READY,
ASC_MC_RISC_Q_FIRST);
ADW_WRITE_BYTE_LRAM(iot, ioh, ASC_MC_HOST_NEXT_DONE,
ASC_MC_RISC_Q_LAST);
ADW_WRITE_BYTE_LRAM(iot, ioh, ASC_MC_RISC_NEXT_READY,
ASC_MC_RISC_Q_FIRST);
ADW_WRITE_BYTE_LRAM(iot, ioh, ASC_MC_RISC_NEXT_DONE,
ASC_MC_RISC_Q_LAST);
/*
* Finally, set up the last RISC Queue List (255) with
* a NULL forward pointer.
*/
ADW_WRITE_WORD_LRAM(iot, ioh, rql_addr,
(ASC_MC_NULL_Q + ((i - 1) << 8)));
ADW_WRITE_BYTE_LRAM(iot, ioh, rql_addr + RQL_STATE, ASC_MC_QS_FREE);
ADW_WRITE_BYTE_REGISTER(iot, ioh, IOPB_INTR_ENABLES,
(ADW_INTR_ENABLE_HOST_INTR | ADW_INTR_ENABLE_GLOBAL_INTR));
ADW_READ_WORD_LRAM(iot, ioh, ASC_MC_CODE_BEGIN_ADDR, word);
ADW_WRITE_WORD_REGISTER(iot, ioh, IOPW_PC, word);
/* finally, finally, gentlemen, start your engine */
ADW_WRITE_WORD_REGISTER(iot, ioh, IOPW_RISC_CSR, ADW_RISC_CSR_RUN);
return warn_code;
}
/*
* Read the board's EEPROM configuration. Set fields in ADW_SOFTC and
* ADW_DVC_CFG based on the EEPROM settings. The chip is stopped while
* all of this is done.
*
* On failure set the ADW_DVC_VAR field 'err_code' and return ADW_ERROR.
*
* For a non-fatal error return a warning code. If there are no warnings
* then 0 is returned.
*
* Note: Chip is stopped on entry.
*/
int
AdvInitFromEEP(sc)
ADW_SOFTC *sc;
{
bus_space_tag_t iot = sc->sc_iot;
bus_space_handle_t ioh = sc->sc_ioh;
u_int16_t warn_code;
ADWEEP_CONFIG eep_config;
int eep_chksum, i;
warn_code = 0;
/*
* Read the board's EEPROM configuration.
*
* Set default values if a bad checksum is found.
*/
eep_chksum = AdvGetEEPConfig(iot, ioh, &eep_config);
if (eep_chksum != eep_config.check_sum) {
warn_code |= ASC_WARN_EEPROM_CHKSUM;
/*
* Set EEPROM default values.
*/
for (i = 0; i < sizeof(ADWEEP_CONFIG); i++) {
*((u_int8_t *) & eep_config + i) =
*((u_int8_t *) & Default_EEPROM_Config + i);
}
/*
* Assume the 6 byte board serial number that was read
* from EEPROM is correct even if the EEPROM checksum
* failed.
*/
eep_config.serial_number_word3 =
AdvReadEEPWord(iot, ioh, ASC_EEP_DVC_CFG_END - 1);
eep_config.serial_number_word2 =
AdvReadEEPWord(iot, ioh, ASC_EEP_DVC_CFG_END - 2);
eep_config.serial_number_word1 =
AdvReadEEPWord(iot, ioh, ASC_EEP_DVC_CFG_END - 3);
AdvSetEEPConfig(iot, ioh, &eep_config);
}
/*
* Set ADW_DVC_VAR and ADW_DVC_CFG variables from the
* EEPROM configuration that was read.
*
* This is the mapping of EEPROM fields to Adv Library fields.
*/
sc->wdtr_able = eep_config.wdtr_able;
sc->sdtr_able = eep_config.sdtr_able;
sc->ultra_able = eep_config.ultra_able;
sc->tagqng_able = eep_config.tagqng_able;
sc->cfg.disc_enable = eep_config.disc_enable;
sc->max_host_qng = eep_config.max_host_qng;
sc->max_dvc_qng = eep_config.max_dvc_qng;
sc->chip_scsi_id = (eep_config.adapter_scsi_id & ADW_MAX_TID);
sc->start_motor = eep_config.start_motor;
sc->scsi_reset_wait = eep_config.scsi_reset_delay;
sc->cfg.bios_boot_wait = eep_config.bios_boot_delay;
sc->bios_ctrl = eep_config.bios_ctrl;
sc->no_scam = eep_config.scam_tolerant;
sc->cfg.serial1 = eep_config.serial_number_word1;
sc->cfg.serial2 = eep_config.serial_number_word2;
sc->cfg.serial3 = eep_config.serial_number_word3;
/*
* Set the host maximum queuing (max. 253, min. 16) and the per device
* maximum queuing (max. 63, min. 4).
*/
if (eep_config.max_host_qng > ASC_DEF_MAX_HOST_QNG) {
eep_config.max_host_qng = ASC_DEF_MAX_HOST_QNG;
} else if (eep_config.max_host_qng < ASC_DEF_MIN_HOST_QNG) {
/* If the value is zero, assume it is uninitialized. */
if (eep_config.max_host_qng == 0) {
eep_config.max_host_qng = ASC_DEF_MAX_HOST_QNG;
} else {
eep_config.max_host_qng = ASC_DEF_MIN_HOST_QNG;
}
}
if (eep_config.max_dvc_qng > ASC_DEF_MAX_DVC_QNG) {
eep_config.max_dvc_qng = ASC_DEF_MAX_DVC_QNG;
} else if (eep_config.max_dvc_qng < ASC_DEF_MIN_DVC_QNG) {
/* If the value is zero, assume it is uninitialized. */
if (eep_config.max_dvc_qng == 0) {
eep_config.max_dvc_qng = ASC_DEF_MAX_DVC_QNG;
} else {
eep_config.max_dvc_qng = ASC_DEF_MIN_DVC_QNG;
}
}
/*
* If 'max_dvc_qng' is greater than 'max_host_qng', then
* set 'max_dvc_qng' to 'max_host_qng'.
*/
if (eep_config.max_dvc_qng > eep_config.max_host_qng) {
eep_config.max_dvc_qng = eep_config.max_host_qng;
}
/*
* Set ADW_DVC_VAR 'max_host_qng' and ADW_DVC_CFG 'max_dvc_qng'
* values based on possibly adjusted EEPROM values.
*/
sc->max_host_qng = eep_config.max_host_qng;
sc->max_dvc_qng = eep_config.max_dvc_qng;
/*
* If the EEPROM 'termination' field is set to automatic (0), then set
* the ADW_DVC_CFG 'termination' field to automatic also.
*
* If the termination is specified with a non-zero 'termination'
* value check that a legal value is set and set the ADW_DVC_CFG
* 'termination' field appropriately.
*/
if (eep_config.termination == 0) {
sc->cfg.termination = 0; /* auto termination */
} else {
/* Enable manual control with low off / high off. */
if (eep_config.termination == 1) {
sc->cfg.termination = ADW_TERM_CTL_SEL;
/* Enable manual control with low off / high on. */
} else if (eep_config.termination == 2) {
sc->cfg.termination = ADW_TERM_CTL_SEL | ADW_TERM_CTL_H;
/* Enable manual control with low on / high on. */
} else if (eep_config.termination == 3) {
sc->cfg.termination = ADW_TERM_CTL_SEL |
ADW_TERM_CTL_H | ADW_TERM_CTL_L;
} else {
/*
* The EEPROM 'termination' field contains a bad value.
* Use automatic termination instead.
*/
sc->cfg.termination = 0;
warn_code |= ASC_WARN_EEPROM_TERMINATION;
}
}
return warn_code;
}
/*
* Read EEPROM configuration into the specified buffer.
*
* Return a checksum based on the EEPROM configuration read.
*/
static u_int16_t
AdvGetEEPConfig(iot, ioh, cfg_buf)
bus_space_tag_t iot;
bus_space_handle_t ioh;
ADWEEP_CONFIG *cfg_buf;
{
u_int16_t wval, chksum;
u_int16_t *wbuf;
int eep_addr;
wbuf = (u_int16_t *) cfg_buf;
chksum = 0;
for (eep_addr = ASC_EEP_DVC_CFG_BEGIN;
eep_addr < ASC_EEP_DVC_CFG_END;
eep_addr++, wbuf++) {
wval = AdvReadEEPWord(iot, ioh, eep_addr);
chksum += wval;
*wbuf = wval;
}
*wbuf = AdvReadEEPWord(iot, ioh, eep_addr);
wbuf++;
for (eep_addr = ASC_EEP_DVC_CTL_BEGIN;
eep_addr < ASC_EEP_MAX_WORD_ADDR;
eep_addr++, wbuf++) {
*wbuf = AdvReadEEPWord(iot, ioh, eep_addr);
}
return chksum;
}
/*
* Read the EEPROM from specified location
*/
static u_int16_t
AdvReadEEPWord(iot, ioh, eep_word_addr)
bus_space_tag_t iot;
bus_space_handle_t ioh;
int eep_word_addr;
{
ADW_WRITE_WORD_REGISTER(iot, ioh, IOPW_EE_CMD,
ASC_EEP_CMD_READ | eep_word_addr);
AdvWaitEEPCmd(iot, iot);
return ADW_READ_WORD_REGISTER(iot, ioh, IOPW_EE_DATA);
}
/*
* Wait for EEPROM command to complete
*/
static void
AdvWaitEEPCmd(iot, ioh)
bus_space_tag_t iot;
bus_space_handle_t ioh;
{
DvcSleepMilliSecond(1);
for (;;) {
if (ADW_READ_WORD_REGISTER(iot, ioh, IOPW_EE_CMD) &
ASC_EEP_CMD_DONE) {
break;
}
DvcSleepMilliSecond(1);
}
return;
}
/*
* Write the EEPROM from 'cfg_buf'.
*/
static void
AdvSetEEPConfig(iot, ioh, cfg_buf)
bus_space_tag_t iot;
bus_space_handle_t ioh;
ADWEEP_CONFIG *cfg_buf;
{
u_int16_t *wbuf;
u_int16_t addr, chksum;
wbuf = (u_int16_t *) cfg_buf;
chksum = 0;
ADW_WRITE_WORD_REGISTER(iot, ioh, IOPW_EE_CMD, ASC_EEP_CMD_WRITE_ABLE);
AdvWaitEEPCmd(iot, ioh);
/*
* Write EEPROM from word 0 to word 15
*/
for (addr = ASC_EEP_DVC_CFG_BEGIN;
addr < ASC_EEP_DVC_CFG_END; addr++, wbuf++) {
chksum += *wbuf;
ADW_WRITE_WORD_REGISTER(iot, ioh, IOPW_EE_DATA, *wbuf);
ADW_WRITE_WORD_REGISTER(iot, ioh,
IOPW_EE_CMD, ASC_EEP_CMD_WRITE | addr);
AdvWaitEEPCmd(iot, ioh);
DvcSleepMilliSecond(ASC_EEP_DELAY_MS);
}
/*
* Write EEPROM checksum at word 18
*/
ADW_WRITE_WORD_REGISTER(iot, ioh, IOPW_EE_DATA, chksum);
ADW_WRITE_WORD_REGISTER(iot, ioh, IOPW_EE_CMD,
ASC_EEP_CMD_WRITE | addr);
AdvWaitEEPCmd(iot, ioh);
wbuf++; /* skip over check_sum */
/*
* Write EEPROM OEM name at words 19 to 26
*/
for (addr = ASC_EEP_DVC_CTL_BEGIN;
addr < ASC_EEP_MAX_WORD_ADDR; addr++, wbuf++) {
ADW_WRITE_WORD_REGISTER(iot, ioh, IOPW_EE_DATA, *wbuf);
ADW_WRITE_WORD_REGISTER(iot, ioh,
IOPW_EE_CMD, ASC_EEP_CMD_WRITE | addr);
AdvWaitEEPCmd(iot, ioh);
}
ADW_WRITE_WORD_REGISTER(iot, ioh, IOPW_EE_CMD,
ASC_EEP_CMD_WRITE_DISABLE);
AdvWaitEEPCmd(iot, ioh);
return;
}
/*
* This function resets the chip and SCSI bus
*
* It is up to the caller to add a delay to let the bus settle after
* calling this function.
*
* The SCSI_CFG0, SCSI_CFG1, and MEM_CFG registers are set-up in
* AdvInitAsc3550Driver(). Here when doing a write to one of these
* registers read first and then write.
*
* Note: A SCSI Bus Reset can not be done until after the EEPROM
* configuration is read to determine whether SCSI Bus Resets
* should be performed.
*/
void
AdvResetChip(iot, ioh)
bus_space_tag_t iot;
bus_space_handle_t ioh;
{
u_int16_t word;
u_int8_t byte;
/*
* Reset Chip.
*/
ADW_WRITE_WORD_REGISTER(iot, ioh, IOPW_CTRL_REG,
ADW_CTRL_REG_CMD_RESET);
DvcSleepMilliSecond(100);
ADW_WRITE_WORD_REGISTER(iot, ioh, IOPW_CTRL_REG,
ADW_CTRL_REG_CMD_WR_IO_REG);
/*
* Initialize Chip registers.
*
* Note: Don't remove the use of a temporary variable in the following
* code, otherwise the Microsoft C compiler will turn the following
* lines into a no-op.
*/
byte = ADW_READ_BYTE_REGISTER(iot, ioh, IOPB_MEM_CFG);
byte |= RAM_SZ_8KB;
ADW_WRITE_BYTE_REGISTER(iot, ioh, IOPB_MEM_CFG, byte);
word = ADW_READ_WORD_REGISTER(iot, ioh, IOPW_SCSI_CFG1);
word &= ~BIG_ENDIAN;
ADW_WRITE_WORD_REGISTER(iot, ioh, IOPW_SCSI_CFG1, word);
/*
* Setting the START_CTL_EMFU 3:2 bits sets a FIFO threshold
* of 128 bytes. This register is only accessible to the host.
*/
ADW_WRITE_BYTE_REGISTER(iot, ioh, IOPB_DMA_CFG0,
START_CTL_EMFU | READ_CMD_MRM);
}
/*
* Description:
* Send a SCSI request to the ASC3550 chip
*
* If there is no SG list for the request, set 'sg_entry_cnt' to 0.
*
* If 'sg_real_addr' is non-zero on entry, AscGetSGList() will not be
* called. It is assumed the caller has already initialized 'sg_real_addr'.
*
* Return:
* ADW_SUCCESS(1) - the request is in the mailbox
* ADW_BUSY(0) - total request count > 253, try later
* ADW_ERROR(-1) - invalid scsi request Q
*/
int
AdvExeScsiQueue(sc, scsiq)
ADW_SOFTC *sc;
ADW_SCSI_REQ_Q *scsiq;
{
return AdvSendScsiCmd(sc, scsiq);
}
/*
* Reset SCSI Bus and purge all outstanding requests.
*
* Return Value:
* ADW_TRUE(1) - All requests are purged and SCSI Bus is reset.
*
* Note: Should always return ADW_TRUE.
*/
int
AdvResetCCB(sc)
ADW_SOFTC *sc;
{
int status;
status = AdvSendIdleCmd(sc, (u_int16_t) IDLE_CMD_SCSI_RESET, 0L, 0);
AdvResetSCSIBus(sc);
return status;
}
/*
* Reset SCSI Bus and delay.
*/
void
AdvResetSCSIBus(sc)
ADW_SOFTC *sc;
{
bus_space_tag_t iot = sc->sc_iot;
bus_space_handle_t ioh = sc->sc_ioh;
u_int16_t scsi_ctrl;
/*
* The microcode currently sets the SCSI Bus Reset signal while
* handling the AdvSendIdleCmd() IDLE_CMD_SCSI_RESET command above.
* But the SCSI Bus Reset Hold Time in the uCode is not deterministic
* (it may in fact be for less than the SCSI Spec. minimum of 25 us).
* Therefore on return the Adv Library sets the SCSI Bus Reset signal
* for ASC_SCSI_RESET_HOLD_TIME_US, which is defined to be greater
* than 25 us.
*/
scsi_ctrl = ADW_READ_WORD_REGISTER(iot, ioh, IOPW_SCSI_CTRL);
ADW_WRITE_WORD_REGISTER(iot, ioh, IOPW_SCSI_CTRL,
scsi_ctrl | ADW_SCSI_CTRL_RSTOUT);
DvcDelayMicroSecond((u_int16_t) ASC_SCSI_RESET_HOLD_TIME_US);
ADW_WRITE_WORD_REGISTER(iot, ioh, IOPW_SCSI_CTRL,
scsi_ctrl & ~ADW_SCSI_CTRL_RSTOUT);
DvcSleepMilliSecond((ulong) sc->scsi_reset_wait * 1000);
}
/*
* Adv Library Interrupt Service Routine
*
* This function is called by a driver's interrupt service routine.
* The function disables and re-enables interrupts.
*
* When a microcode idle command is completed, the ADW_DVC_VAR
* 'idle_cmd_done' field is set to ADW_TRUE.
*
* Note: AdvISR() can be called when interrupts are disabled or even
* when there is no hardware interrupt condition present. It will
* always check for completed idle commands and microcode requests.
* This is an important feature that shouldn't be changed because it
* allows commands to be completed from polling mode loops.
*
* Return:
* ADW_TRUE(1) - interrupt was pending
* ADW_FALSE(0) - no interrupt was pending
*/
int
AdvISR(sc)
ADW_SOFTC *sc;
{
bus_space_tag_t iot = sc->sc_iot;
bus_space_handle_t ioh = sc->sc_ioh;
u_int8_t int_stat;
u_int16_t next_done_loc, target_bit;
int completed_q;
ADW_SCSI_REQ_Q *scsiq;
ASC_REQ_SENSE *sense_data;
int ret;
ret = (ADW_IS_INT_PENDING(iot, ioh)) ? ADW_TRUE : ADW_FALSE;
/* Reading the register clears the interrupt. */
int_stat = ADW_READ_BYTE_REGISTER(iot, ioh, IOPB_INTR_STATUS_REG);
if (int_stat & ADW_INTR_STATUS_INTRB) {
sc->idle_cmd_done = ADW_TRUE;
}
/*
* Notify the driver of a hardware detected SCSI Bus Reset.
*/
if (int_stat & ADW_INTR_STATUS_INTRC) {
if (sc->sbreset_callback) {
(*(ADW_SBRESET_CALLBACK) sc->sbreset_callback) (sc);
}
}
/*
* ASC_MC_HOST_NEXT_DONE (0x129) is actually the last completed RISC
* Queue List request. Its forward pointer (RQL_FWD) points to the
* current completed RISC Queue List request.
*/
ADW_READ_BYTE_LRAM(iot, ioh, ASC_MC_HOST_NEXT_DONE, next_done_loc);
next_done_loc = ASC_MC_RISC_Q_LIST_BASE +
(next_done_loc * ASC_MC_RISC_Q_LIST_SIZE) + RQL_FWD;
ADW_READ_BYTE_LRAM(iot, ioh, next_done_loc, completed_q);
/* Loop until all completed Q's are processed. */
while (completed_q != ASC_MC_NULL_Q) {
ADW_WRITE_BYTE_LRAM(iot, ioh, ASC_MC_HOST_NEXT_DONE,
completed_q);
next_done_loc = ASC_MC_RISC_Q_LIST_BASE +
(completed_q * ASC_MC_RISC_Q_LIST_SIZE);
/*
* Read the ADW_SCSI_REQ_Q virtual address pointer from
* the RISC list entry. The microcode has changed the
* ADW_SCSI_REQ_Q physical address to its virtual address.
*
* Refer to comments at the end of AdvSendScsiCmd() for
* more information on the RISC list structure.
*/
{
ushort lsw, msw;
ADW_READ_WORD_LRAM(iot, ioh,
next_done_loc + RQL_PHYADDR, lsw);
ADW_READ_WORD_LRAM(iot, ioh,
next_done_loc + RQL_PHYADDR + 2, msw);
scsiq = (ADW_SCSI_REQ_Q *)
(((u_int32_t) msw << 16) | lsw);
}
target_bit = ADW_TID_TO_TIDMASK(scsiq->target_id);
/*
* Clear request microcode control flag.
*/
scsiq->cntl = 0;
/*
* Check Condition handling
*/
if ((scsiq->done_status == QD_WITH_ERROR) &&
(scsiq->scsi_status == SS_CHK_CONDITION) &&
(sense_data = (ASC_REQ_SENSE *) scsiq->vsense_addr) != 0 &&
(scsiq->orig_sense_len - scsiq->sense_len) >=
ASC_MIN_SENSE_LEN) {
/*
* Command returned with a check condition and valid
* sense data.
*/
}
/*
* If the command that completed was a SCSI INQUIRY and
* LUN 0 was sent the command, then process the INQUIRY
* command information for the device.
*/
else if (scsiq->done_status == QD_NO_ERROR &&
scsiq->cdb[0] == INQUIRY &&
scsiq->target_lun == 0) {
AdvInquiryHandling(sc, scsiq);
}
/* Change the RISC Queue List state to free. */
ADW_WRITE_BYTE_LRAM(iot, ioh,
next_done_loc + RQL_STATE, ASC_MC_QS_FREE);
/* Get the RISC Queue List forward pointer. */
ADW_READ_BYTE_LRAM(iot, ioh,
next_done_loc + RQL_FWD, completed_q);
/*
* Notify the driver of the completed request by passing
* the ADW_SCSI_REQ_Q pointer to its callback function.
*/
sc->cur_host_qng--;
scsiq->a_flag |= ADW_SCSIQ_DONE;
(*(ADW_ISR_CALLBACK) sc->isr_callback) (sc, scsiq);
/*
* Note: After the driver callback function is called, 'scsiq'
* can no longer be referenced.
*
* Fall through and continue processing other completed
* requests...
*/
}
return ret;
}
/*
* Send an idle command to the chip and wait for completion.
*
* Interrupts do not have to be enabled on entry.
*
* Return Values:
* ADW_TRUE - command completed successfully
* ADW_FALSE - command failed
*/
int
AdvSendIdleCmd(sc, idle_cmd, idle_cmd_parameter, flags)
ADW_SOFTC *sc;
u_int16_t idle_cmd;
u_int32_t idle_cmd_parameter;
int flags;
{
bus_space_tag_t iot = sc->sc_iot;
bus_space_handle_t ioh = sc->sc_ioh;
u_int32_t i;
int ret;
sc->idle_cmd_done = 0;
/*
* Write the idle command value after the idle command parameter
* has been written to avoid a race condition. If the order is not
* followed, the microcode may process the idle command before the
* parameters have been written to LRAM.
*/
ADW_WRITE_DWORD_LRAM(iot, ioh, ASC_MC_IDLE_PARA_STAT,
idle_cmd_parameter);
ADW_WRITE_WORD_LRAM(iot, ioh, ASC_MC_IDLE_CMD, idle_cmd);
/*
* If the 'flags' argument contains the ADW_NOWAIT flag, then
* return with success.
*/
if (flags & ADW_NOWAIT)
return ADW_TRUE;
for (i = 0; i < SCSI_WAIT_10_SEC * SCSI_MS_PER_SEC; i++) {
/*
* 'idle_cmd_done' is set by AdvISR().
*/
if (sc->idle_cmd_done)
break;
DvcSleepMilliSecond(1);
/*
* If interrupts were disabled on entry to AdvSendIdleCmd(),
* then they will still be disabled here. Call AdvISR() to
* check for the idle command completion.
*/
(void) AdvISR(sc);
}
if (sc->idle_cmd_done == ADW_FALSE) {
return ADW_FALSE;
} else {
ADW_READ_WORD_LRAM(iot, ioh, ASC_MC_IDLE_PARA_STAT, ret);
return ret;
}
}
/*
* Send the SCSI request block to the adapter
*
* Each of the 255 Adv Library/Microcode RISC Lists or mailboxes has the
* following structure:
*
* 0: RQL_FWD - RISC list forward pointer (1 byte)
* 1: RQL_BWD - RISC list backward pointer (1 byte)
* 2: RQL_STATE - RISC list state byte - free, ready, done, aborted (1 byte)
* 3: RQL_TID - request target id (1 byte)
* 4: RQL_PHYADDR - ADW_SCSI_REQ_Q physical pointer (4 bytes)
*
* Return:
* ADW_SUCCESS(1) - the request is in the mailbox
* ADW_BUSY(0) - total request count > 253, try later
*/
static int
AdvSendScsiCmd(sc, scsiq)
ADW_SOFTC *sc;
ADW_SCSI_REQ_Q *scsiq;
{
bus_space_tag_t iot = sc->sc_iot;
bus_space_handle_t ioh = sc->sc_ioh;
ADW_CCB *ccb = (ADW_CCB *) scsiq->ccb_ptr;
u_int16_t next_ready_loc;
u_int8_t next_ready_loc_fwd;
long req_size;
u_int32_t q_phy_addr;
if (sc->cur_host_qng >= sc->max_host_qng) {
return ADW_BUSY;
} else {
sc->cur_host_qng++;
}
/*
* Clear the ADW_SCSI_REQ_Q done flag.
*/
scsiq->a_flag &= ~ADW_SCSIQ_DONE;
/*
* Save the original sense buffer length.
*
* After the request completes 'sense_len' will be set to the residual
* byte count of the Auto-Request Sense if a command returns CHECK
* CONDITION and the Sense Data is valid indicated by 'host_status' not
* being set to QHSTA_M_AUTO_REQ_SENSE_FAIL. To determine the valid
* Sense Data Length subtract 'sense_len' from 'orig_sense_len'.
*/
scsiq->orig_sense_len = scsiq->sense_len;
ADW_READ_BYTE_LRAM(iot, ioh, ASC_MC_HOST_NEXT_READY, next_ready_loc);
next_ready_loc = ASC_MC_RISC_Q_LIST_BASE +
(next_ready_loc * ASC_MC_RISC_Q_LIST_SIZE);
/*
* Write the physical address of the Q to the mailbox.
* We need to skip the first four bytes, because the microcode
* uses them internally for linking Q's together.
*/
req_size = sizeof(ADW_SCSI_REQ_Q);
q_phy_addr = sc->sc_dmamap_control->dm_segs[0].ds_addr +
ADW_CCB_OFF(ccb) + offsetof(struct adw_ccb, scsiq);
scsiq->scsiq_ptr = scsiq;
/*
* The RISC list structure, which 'next_ready_loc' is a pointer
* to in microcode LRAM, has the format detailed in the comment
* header for this function.
*
* Write the ADW_SCSI_REQ_Q physical pointer to
* 'next_ready_loc' request.
*/
ADW_WRITE_DWORD_LRAM(iot, ioh, next_ready_loc + RQL_PHYADDR,
q_phy_addr);
/* Write target_id to 'next_ready_loc' request. */
ADW_WRITE_BYTE_LRAM(iot, ioh, next_ready_loc + RQL_TID,
scsiq->target_id);
/*
* Set the ASC_MC_HOST_NEXT_READY (0x128) microcode variable to
* the 'next_ready_loc' request forward pointer.
*
* Do this *before* changing the 'next_ready_loc' queue to QS_READY.
* After the state is changed to QS_READY 'RQL_FWD' will be changed
* by the microcode.
*
* NOTE: The temporary variable 'next_ready_loc_fwd' is required to
* prevent some compilers from optimizing out 'AdvReadByteLram()' if
* it were used as the 3rd argument to 'AdvWriteByteLram()'.
*/
ADW_READ_BYTE_LRAM(iot, ioh, next_ready_loc + RQL_FWD,
next_ready_loc_fwd);
ADW_WRITE_BYTE_LRAM(iot, ioh, ASC_MC_HOST_NEXT_READY,
next_ready_loc_fwd);
/*
* Change the state of 'next_ready_loc' request from QS_FREE to
* QS_READY which will cause the microcode to pick it up and
* execute it.
*
* Can't reference 'next_ready_loc' after changing the request
* state to QS_READY. The microcode now owns the request.
*/
ADW_WRITE_BYTE_LRAM(iot, ioh, next_ready_loc + RQL_STATE,
ASC_MC_QS_READY);
return ADW_SUCCESS;
}
/*
* Inquiry Information Byte 7 Handling
*
* Handle SCSI Inquiry Command information for a device by setting
* microcode operating variables that affect WDTR, SDTR, and Tag
* Queuing.
*/
static void
AdvInquiryHandling(sc, scsiq)
ADW_SOFTC *sc;
ADW_SCSI_REQ_Q *scsiq;
{
bus_space_tag_t iot = sc->sc_iot;
bus_space_handle_t ioh = sc->sc_ioh;
ASC_SCSI_INQUIRY *inq;
u_int16_t cfg_word;
u_int16_t tidmask;
u_int8_t tid;
/*
* AdvInquiryHandling() requires up to INQUIRY information Byte 7
* to be available.
*
* If less than 8 bytes of INQUIRY information were requested or less
* than 8 bytes were transferred, then return. cdb[4] is the request
* length and the ADW_SCSI_REQ_Q 'data_cnt' field is set by the
* microcode to the transfer residual count.
*/
if (scsiq->cdb[4] < 8 || (scsiq->cdb[4] - scsiq->data_cnt) < 8) {
return;
}
tid = scsiq->target_id;
inq = (ASC_SCSI_INQUIRY *) scsiq->vdata_addr;
/*
* WDTR, SDTR, and Tag Queuing cannot be enabled for old devices.
*/
if (inq->byte3.rsp_data_fmt < 2 && inq->byte2.ansi_apr_ver < 2) {
return;
} else {
/*
* INQUIRY Byte 7 Handling
*
* Use a device's INQUIRY byte 7 to determine whether it
* supports WDTR, SDTR, and Tag Queuing. If the feature
* is enabled in the EEPROM and the device supports the
* feature, then enable it in the microcode.
*/
tidmask = ADW_TID_TO_TIDMASK(tid);
/*
* Wide Transfers
*
* If the EEPROM enabled WDTR for the device and the device
* supports wide bus (16 bit) transfers, then turn on the
* device's 'wdtr_able' bit and write the new value to the
* microcode.
*/
if ((sc->wdtr_able & tidmask) && inq->byte7.WBus16) {
ADW_READ_WORD_LRAM(iot, ioh, ASC_MC_WDTR_ABLE,
cfg_word);
if ((cfg_word & tidmask) == 0) {
cfg_word |= tidmask;
ADW_WRITE_WORD_LRAM(iot, ioh, ASC_MC_WDTR_ABLE,
cfg_word);
/*
* Clear the microcode "WDTR negotiation" done
* indicator for the target to cause it
* to negotiate with the new setting set above.
*/
ADW_READ_WORD_LRAM(iot, ioh, ASC_MC_WDTR_DONE,
cfg_word);
cfg_word &= ~tidmask;
ADW_WRITE_WORD_LRAM(iot, ioh, ASC_MC_WDTR_DONE,
cfg_word);
}
}
/*
* Synchronous Transfers
*
* If the EEPROM enabled SDTR for the device and the device
* supports synchronous transfers, then turn on the device's
* 'sdtr_able' bit. Write the new value to the microcode.
*/
if ((sc->sdtr_able & tidmask) && inq->byte7.Sync) {
ADW_READ_WORD_LRAM(iot, ioh, ASC_MC_SDTR_ABLE,
cfg_word);
if ((cfg_word & tidmask) == 0) {
cfg_word |= tidmask;
ADW_WRITE_WORD_LRAM(iot, ioh, ASC_MC_SDTR_ABLE,
cfg_word);
/*
* Clear the microcode "SDTR negotiation" done
* indicator for the target to cause it
* to negotiate with the new setting set above.
*/
ADW_READ_WORD_LRAM(iot, ioh, ASC_MC_SDTR_DONE,
cfg_word);
cfg_word &= ~tidmask;
ADW_WRITE_WORD_LRAM(iot, ioh, ASC_MC_SDTR_DONE,
cfg_word);
}
}
/*
* If the EEPROM enabled Tag Queuing for device and the
* device supports Tag Queuing, then turn on the device's
* 'tagqng_enable' bit in the microcode and set the microcode
* maximum command count to the ADW_DVC_VAR 'max_dvc_qng'
* value.
*
* Tag Queuing is disabled for the BIOS which runs in polled
* mode and would see no benefit from Tag Queuing. Also by
* disabling Tag Queuing in the BIOS devices with Tag Queuing
* bugs will at least work with the BIOS.
*/
if ((sc->tagqng_able & tidmask) && inq->byte7.CmdQue) {
ADW_READ_WORD_LRAM(iot, ioh, ASC_MC_TAGQNG_ABLE,
cfg_word);
cfg_word |= tidmask;
ADW_WRITE_WORD_LRAM(iot, ioh, ASC_MC_TAGQNG_ABLE,
cfg_word);
ADW_WRITE_BYTE_LRAM(iot, ioh,
ASC_MC_NUMBER_OF_MAX_CMD + tid,
sc->max_dvc_qng);
}
}
}
static void
DvcSleepMilliSecond(n)
ulong n;
{
DELAY(n * 1000);
}
static void
DvcDelayMicroSecond(n)
ulong n;
{
DELAY(n);
}