diff options
author | gibbs <gibbs@FreeBSD.org> | 1998-09-15 07:24:17 +0000 |
---|---|---|
committer | gibbs <gibbs@FreeBSD.org> | 1998-09-15 07:24:17 +0000 |
commit | b845ffee8074eddeeb44108a8646161fbcf33aff (patch) | |
tree | 29b6ded2164fbd85ddc231ce2953cec74858b5bc | |
parent | fd4d41dcf7b1812c21942f9a24ef7ee737d2f1dd (diff) | |
download | FreeBSD-src-b845ffee8074eddeeb44108a8646161fbcf33aff.zip FreeBSD-src-b845ffee8074eddeeb44108a8646161fbcf33aff.tar.gz |
Massive overhaul of the aic7xxx driver:
- Convert to CAM
- Use a new DMA based queuing and paging scheme
- Add preliminary target mode support
- Add support for the aic789X chips
- Take advantage of external SRAM on more controllers.
- Numerous bug fixes and performance improvements.
-rw-r--r-- | sys/dev/aic7xxx/93cx6.c | 174 | ||||
-rw-r--r-- | sys/dev/aic7xxx/93cx6.h | 95 | ||||
-rw-r--r-- | sys/dev/aic7xxx/aic7xxx.c | 4823 | ||||
-rw-r--r-- | sys/dev/aic7xxx/aic7xxx.h | 524 | ||||
-rw-r--r-- | sys/dev/aic7xxx/aic7xxx.reg | 458 | ||||
-rw-r--r-- | sys/dev/aic7xxx/aic7xxx.seq | 1686 | ||||
-rw-r--r-- | sys/dev/aic7xxx/aic7xxx_93cx6.c | 174 | ||||
-rw-r--r-- | sys/dev/aic7xxx/aic7xxx_93cx6.h | 95 | ||||
-rw-r--r-- | sys/dev/aic7xxx/aicasm.c | 422 | ||||
-rw-r--r-- | sys/dev/aic7xxx/aicasm.h | 19 | ||||
-rw-r--r-- | sys/dev/aic7xxx/aicasm/aicasm.c | 422 | ||||
-rw-r--r-- | sys/dev/aic7xxx/aicasm/aicasm.h | 19 | ||||
-rw-r--r-- | sys/dev/aic7xxx/aicasm/aicasm_gram.y | 186 | ||||
-rw-r--r-- | sys/dev/aic7xxx/aicasm/aicasm_scan.l | 55 | ||||
-rw-r--r-- | sys/dev/aic7xxx/aicasm/aicasm_symbol.c | 7 | ||||
-rw-r--r-- | sys/dev/aic7xxx/aicasm/aicasm_symbol.h | 42 | ||||
-rw-r--r-- | sys/dev/aic7xxx/aicasm_gram.y | 186 | ||||
-rw-r--r-- | sys/dev/aic7xxx/aicasm_scan.l | 55 | ||||
-rw-r--r-- | sys/dev/aic7xxx/aicasm_symbol.c | 7 | ||||
-rw-r--r-- | sys/dev/aic7xxx/aicasm_symbol.h | 42 | ||||
-rw-r--r-- | sys/dev/aic7xxx/sequencer.h | 50 |
21 files changed, 8428 insertions, 1113 deletions
diff --git a/sys/dev/aic7xxx/93cx6.c b/sys/dev/aic7xxx/93cx6.c new file mode 100644 index 0000000..d7f8ab5 --- /dev/null +++ b/sys/dev/aic7xxx/93cx6.c @@ -0,0 +1,174 @@ +/* + * Interface for the 93C66/56/46/26/06 serial eeprom parts. + * + * Copyright (c) 1995, 1996 Daniel M. Eischen + * All rights reserved. + * + * 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 immediately at the beginning of the file, without modification, + * 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. Absolutely no warranty of function or purpose is made by the author + * Daniel M. Eischen. + * 4. Modifications may be freely made to this file if the above conditions + * are met. + * + * $Id: 93cx6.c,v 1.10 1997/02/22 09:38:36 peter Exp $ + */ + +/* + * The instruction set of the 93C66/56/46/26/06 chips are as follows: + * + * Start OP * + * Function Bit Code Address** Data Description + * ------------------------------------------------------------------- + * READ 1 10 A5 - A0 Reads data stored in memory, + * starting at specified address + * EWEN 1 00 11XXXX Write enable must preceed + * all programming modes + * ERASE 1 11 A5 - A0 Erase register A5A4A3A2A1A0 + * WRITE 1 01 A5 - A0 D15 - D0 Writes register + * ERAL 1 00 10XXXX Erase all registers + * WRAL 1 00 01XXXX D15 - D0 Writes to all registers + * EWDS 1 00 00XXXX Disables all programming + * instructions + * *Note: A value of X for address is a don't care condition. + * **Note: There are 8 address bits for the 93C56/66 chips unlike + * the 93C46/26/06 chips which have 6 address bits. + * + * The 93C46 has a four wire interface: clock, chip select, data in, and + * data out. In order to perform one of the above functions, you need + * to enable the chip select for a clock period (typically a minimum of + * 1 usec, with the clock high and low a minimum of 750 and 250 nsec + * respectively). While the chip select remains high, you can clock in + * the instructions (above) starting with the start bit, followed by the + * OP code, Address, and Data (if needed). For the READ instruction, the + * requested 16-bit register contents is read from the data out line but + * is preceded by an initial zero (leading 0, followed by 16-bits, MSB + * first). The clock cycling from low to high initiates the next data + * bit to be sent from the chip. + * + */ + +#include <sys/param.h> +#include <sys/systm.h> +#include <machine/bus_memio.h> +#include <machine/bus_pio.h> +#include <machine/bus.h> +#include <dev/aic7xxx/93cx6.h> + +/* + * Right now, we only have to read the SEEPROM. But we make it easier to + * add other 93Cx6 functions. + */ +static struct seeprom_cmd { + unsigned char len; + unsigned char bits[3]; +} seeprom_read = {3, {1, 1, 0}}; + +/* + * Wait for the SEERDY to go high; about 800 ns. + */ +#define CLOCK_PULSE(sd, rdy) \ + while ((SEEPROM_STATUS_INB(sd) & rdy) == 0) { \ + ; /* Do nothing */ \ + } \ + (void)SEEPROM_INB(sd); /* Clear clock */ + +/* + * Read the serial EEPROM and returns 1 if successful and 0 if + * not successful. + */ +int +read_seeprom(sd, buf, start_addr, count) + struct seeprom_descriptor *sd; + u_int16_t *buf; + bus_size_t start_addr; + bus_size_t count; +{ + int i = 0; + u_int k = 0; + u_int16_t v; + u_int8_t temp; + + /* + * Read the requested registers of the seeprom. The loop + * will range from 0 to count-1. + */ + for (k = start_addr; k < count + start_addr; k++) { + /* Send chip select for one clock cycle. */ + temp = sd->sd_MS ^ sd->sd_CS; + SEEPROM_OUTB(sd, temp ^ sd->sd_CK); + CLOCK_PULSE(sd, sd->sd_RDY); + + /* + * Now we're ready to send the read command followed by the + * address of the 16-bit register we want to read. + */ + for (i = 0; i < seeprom_read.len; i++) { + if (seeprom_read.bits[i] != 0) + temp ^= sd->sd_DO; + SEEPROM_OUTB(sd, temp); + CLOCK_PULSE(sd, sd->sd_RDY); + SEEPROM_OUTB(sd, temp ^ sd->sd_CK); + CLOCK_PULSE(sd, sd->sd_RDY); + if (seeprom_read.bits[i] != 0) + temp ^= sd->sd_DO; + } + /* Send the 6 or 8 bit address (MSB first, LSB last). */ + for (i = (sd->sd_chip - 1); i >= 0; i--) { + if ((k & (1 << i)) != 0) + temp ^= sd->sd_DO; + SEEPROM_OUTB(sd, temp); + CLOCK_PULSE(sd, sd->sd_RDY); + SEEPROM_OUTB(sd, temp ^ sd->sd_CK); + CLOCK_PULSE(sd, sd->sd_RDY); + if ((k & (1 << i)) != 0) + temp ^= sd->sd_DO; + } + + /* + * Now read the 16 bit register. An initial 0 precedes the + * register contents which begins with bit 15 (MSB) and ends + * with bit 0 (LSB). The initial 0 will be shifted off the + * top of our word as we let the loop run from 0 to 16. + */ + v = 0; + for (i = 16; i >= 0; i--) { + SEEPROM_OUTB(sd, temp); + CLOCK_PULSE(sd, sd->sd_RDY); + v <<= 1; + if (SEEPROM_DATA_INB(sd) & sd->sd_DI) + v |= 1; + SEEPROM_OUTB(sd, temp ^ sd->sd_CK); + CLOCK_PULSE(sd, sd->sd_RDY); + } + + buf[k - start_addr] = v; + + /* Reset the chip select for the next command cycle. */ + temp = sd->sd_MS; + SEEPROM_OUTB(sd, temp); + CLOCK_PULSE(sd, sd->sd_RDY); + SEEPROM_OUTB(sd, temp ^ sd->sd_CK); + CLOCK_PULSE(sd, sd->sd_RDY); + SEEPROM_OUTB(sd, temp); + CLOCK_PULSE(sd, sd->sd_RDY); + } +#ifdef 93CX6_DUMP_EEPROM + printf("\nSerial EEPROM:"); + for (k = 0; k < count; k = k + 1) { + if (((k % 8) == 0) && (k != 0)) { + printf ("\n "); + } + printf (" 0x%x", buf[k]); + } + printf ("\n"); +#endif + return (1); +} diff --git a/sys/dev/aic7xxx/93cx6.h b/sys/dev/aic7xxx/93cx6.h new file mode 100644 index 0000000..32645e1 --- /dev/null +++ b/sys/dev/aic7xxx/93cx6.h @@ -0,0 +1,95 @@ +/* + * Interface to the 93C46 serial EEPROM that is used to store BIOS + * settings for the aic7xxx based adaptec SCSI controllers. It can + * also be used for 93C26 and 93C06 serial EEPROMS. + * + * Copyright (c) 1994, 1995 Justin T. Gibbs. + * All rights reserved. + * + * 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, + * without modification, immediately at the beginning of the file. + * 2. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * Where this Software is combined with software released under the terms of + * the GNU Public License ("GPL") and the terms of the GPL would require the + * combined work to also be released under the terms of the GPL, the terms + * and conditions of this License will apply in addition to those of the + * GPL with the exception of any terms or conditions of this License that + * conflict with, or are expressly prohibited by, the GPL. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR 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 AUTHOR 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. + * + * $Id$ + */ + +#include <sys/param.h> +#if !defined(__NetBSD__) +#include <sys/systm.h> +#endif + +#ifdef KERNEL + +typedef enum { + C46 = 6, + C56_66 = 8 +} seeprom_chip_t; + +struct seeprom_descriptor { + bus_space_tag_t sd_tag; + bus_space_handle_t sd_bsh; + bus_size_t sd_control_offset; + bus_size_t sd_status_offset; + bus_size_t sd_dataout_offset; + seeprom_chip_t sd_chip; + u_int16_t sd_MS; + u_int16_t sd_RDY; + u_int16_t sd_CS; + u_int16_t sd_CK; + u_int16_t sd_DO; + u_int16_t sd_DI; +}; + +/* + * This function will read count 16-bit words from the serial EEPROM and + * return their value in buf. The port address of the aic7xxx serial EEPROM + * control register is passed in as offset. The following parameters are + * also passed in: + * + * CS - Chip select + * CK - Clock + * DO - Data out + * DI - Data in + * RDY - SEEPROM ready + * MS - Memory port mode select + * + * A failed read attempt returns 0, and a successful read returns 1. + */ + +#define SEEPROM_INB(sd) \ + bus_space_read_1(sd->sd_tag, sd->sd_bsh, sd->sd_control_offset) +#define SEEPROM_OUTB(sd, value) \ + bus_space_write_1(sd->sd_tag, sd->sd_bsh, sd->sd_control_offset, value) +#define SEEPROM_STATUS_INB(sd) \ + bus_space_read_1(sd->sd_tag, sd->sd_bsh, sd->sd_status_offset) +#define SEEPROM_DATA_INB(sd) \ + bus_space_read_1(sd->sd_tag, sd->sd_bsh, sd->sd_dataout_offset) + +int read_seeprom(struct seeprom_descriptor *sd, u_int16_t *buf, + bus_size_t start_addr, bus_size_t count); + +#endif /* KERNEL */ diff --git a/sys/dev/aic7xxx/aic7xxx.c b/sys/dev/aic7xxx/aic7xxx.c new file mode 100644 index 0000000..67366f4 --- /dev/null +++ b/sys/dev/aic7xxx/aic7xxx.c @@ -0,0 +1,4823 @@ +/* + * Generic driver for the aic7xxx based adaptec SCSI controllers + * Product specific probe and attach routines can be found in: + * i386/eisa/ahc_eisa.c 27/284X and aic7770 motherboard controllers + * pci/ahc_pci.c 3985, 3980, 3940, 2940, aic7895, aic7890, + * aic7880, aic7870, aic7860, and aic7850 controllers + * + * Copyright (c) 1994, 1995, 1996, 1997, 1998 Justin T. Gibbs. + * All rights reserved. + * + * 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, + * without modification, immediately at the beginning of the file. + * 2. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * Where this Software is combined with software released under the terms of + * the GNU Public License ("GPL") and the terms of the GPL would require the + * combined work to also be released under the terms of the GPL, the terms + * and conditions of this License will apply in addition to those of the + * GPL with the exception of any terms or conditions of this License that + * conflict with, or are expressly prohibited by, the GPL. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR 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 AUTHOR 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. + * + * $Id: aic7xxx.c,v 1.113 1997/04/05 21:41:13 gibbs Exp $ + */ +/* + * A few notes on features of the driver. + * + * SCB paging takes advantage of the fact that devices stay disconnected + * from the bus a relatively long time and that while they're disconnected, + * having the SCBs for these transactions down on the host adapter is of + * little use. Instead of leaving this idle SCB down on the card we copy + * it back up into kernel memory and reuse the SCB slot on the card to + * schedule another transaction. This can be a real payoff when doing random + * I/O to tagged queueing devices since there are more transactions active at + * once for the device to sort for optimal seek reduction. The algorithm goes + * like this... + * + * The sequencer maintains two lists of its hardware SCBs. The first is the + * singly linked free list which tracks all SCBs that are not currently in + * use. The second is the doubly linked disconnected list which holds the + * SCBs of transactions that are in the disconnected state sorted most + * recently disconnected first. When the kernel queues a transaction to + * the card, a hardware SCB to "house" this transaction is retrieved from + * either of these two lists. If the SCB came from the disconnected list, + * a check is made to see if any data transfer or SCB linking (more on linking + * in a bit) information has been changed since it was copied from the host + * and if so, DMAs the SCB back up before it can be used. Once a hardware + * SCB has been obtained, the SCB is DMAed from the host. Before any work + * can begin on this SCB, the sequencer must ensure that either the SCB is + * for a tagged transaction or the target is not already working on another + * non-tagged transaction. If a conflict arises in the non-tagged case, the + * sequencer finds the SCB for the active transactions and sets the SCB_LINKED + * field in that SCB to this next SCB to execute. To facilitate finding + * active non-tagged SCBs, the last four bytes of up to the first four hardware + * SCBs serve as a storage area for the currently active SCB ID for each + * target. + * + * When a device reconnects, a search is made of the hardware SCBs to find + * the SCB for this transaction. If the search fails, a hardware SCB is + * pulled from either the free or disconnected SCB list and the proper + * SCB is DMAed from the host. If the MK_MESSAGE control bit is set + * in the control byte of the SCB while it was disconnected, the sequencer + * will assert ATN and attempt to issue a message to the host. + * + * When a command completes, a check for non-zero status and residuals is + * made. If either of these conditions exists, the SCB is DMAed back up to + * the host so that it can interpret this information. Additionally, in the + * case of bad status, the sequencer generates a special interrupt and pauses + * itself. This allows the host to setup a request sense command if it + * chooses for this target synchronously with the error so that sense + * information isn't lost. + * + */ + +#include <opt_aic7xxx.h> + +#include <pci.h> +#include <stddef.h> /* For offsetof */ + +#include <sys/param.h> +#include <sys/systm.h> +#include <sys/malloc.h> +#include <sys/buf.h> +#include <sys/proc.h> + +#include <cam/cam.h> +#include <cam/cam_ccb.h> +#include <cam/cam_sim.h> +#include <cam/cam_xpt_sim.h> +#include <cam/cam_debug.h> + +#include <cam/scsi/scsi_all.h> +#include <cam/scsi/scsi_message.h> + +#if NPCI > 0 +#include <machine/bus_memio.h> +#endif +#include <machine/bus_pio.h> +#include <machine/bus.h> +#include <machine/clock.h> + +#include <vm/vm.h> +#include <vm/vm_param.h> +#include <vm/pmap.h> + +#include <dev/aic7xxx/aic7xxx.h> +#include <dev/aic7xxx/sequencer.h> + +#include <aic7xxx_reg.h> +#include <aic7xxx_seq.h> + +#include <sys/kernel.h> + +#ifndef AHC_TMODE_ENABLE +#define AHC_TMODE_ENABLE 0 +#endif + +#define MAX(a,b) (((a) > (b)) ? (a) : (b)) +#define MIN(a,b) (((a) < (b)) ? (a) : (b)) +#define ALL_TARGETS (~0) +#define ALL_LUNS (~0) +#define ALL_CHANNELS '\0' + +#define SIM_IS_SCSIBUS_B(ahc, sim) \ + (sim == ahc->sim_b) +#define SCB_IS_SCSIBUS_B(scb) \ + (((scb)->hscb->tcl & SELBUSB) != 0) +#define SCB_TARGET(scb) \ + (((scb)->hscb->tcl & TID) >> 4) +#define SCB_CHANNEL(scb) \ + (SCB_IS_SCSIBUS_B(scb) ? 'B' : 'A') +#define SCB_LUN(scb) \ + ((scb)->hscb->tcl & LID) +#define SCB_TARGET_OFFSET(scb) \ + (SCB_TARGET(scb) + (SCB_IS_SCSIBUS_B(scb) ? 8 : 0)) +#define SCB_TARGET_MASK(scb) \ + (0x01 << (SCB_TARGET_OFFSET(scb))) + +#define ccb_scb_ptr spriv_ptr0 +#define ccb_ahc_ptr spriv_ptr1 + +struct ahc_devinfo { + int target_offset; + u_int16_t target_mask; + u_int8_t target; + char channel; +}; + +typedef enum { + SEARCH_COMPLETE, + SEARCH_COUNT, + SEARCH_REMOVE +} ahc_search_action; + +u_long ahc_unit = 0; + +#ifdef AHC_DEBUG +static int ahc_debug = AHC_DEBUG; +#endif + +#if NPCI > 0 +void ahc_pci_intr(struct ahc_softc *ahc); +#endif + +static void ahc_dump_targcmd(struct target_cmd *cmd); +static void ahc_shutdown(int howto, void *arg); +static void ahcminphys(struct buf *bp); +static cam_status + ahc_find_tmode_devs(struct ahc_softc *ahc, + struct cam_sim *sim, union ccb *ccb, + struct tmode_tstate **tstate, + struct tmode_lstate **lstate, + int notfound_failure); +static void ahc_action(struct cam_sim *sim, union ccb *ccb); +static void ahc_async(void *callback_arg, u_int32_t code, + struct cam_path *path, void *arg); +static void ahc_execute_scb(void *arg, bus_dma_segment_t *dm_segs, + int nsegments, int error); +static void ahc_poll(struct cam_sim *sim); +static void ahc_setup_data(struct ahc_softc *ahc, + struct ccb_scsiio *csio, struct scb *scb); +static void ahc_freeze_devq(struct ahc_softc *ahc, struct cam_path *path); +static struct scb * + ahc_get_scb(struct ahc_softc *ahc); +static void ahc_free_scb(struct ahc_softc *ahc, struct scb *scb); +static struct scb * + ahc_alloc_scb(struct ahc_softc *ahc); +static void ahc_fetch_devinfo(struct ahc_softc *ahc, + struct ahc_devinfo *devinfo); +static void ahc_compile_devinfo(struct ahc_devinfo *devinfo, + u_int target, char channel); +static u_int8_t ahc_abort_wscb(struct ahc_softc *ahc, u_int8_t scbpos, + u_int8_t prev); +static void ahc_done(struct ahc_softc *ahc, struct scb *scbp); +static void ahc_handle_target_cmd(struct ahc_softc *ahc); +static void ahc_handle_seqint(struct ahc_softc *ahc, u_int8_t intstat); +static void ahc_handle_scsiint(struct ahc_softc *ahc, + u_int8_t intstat); +static void ahc_handle_reqinit(struct ahc_softc *ahc, + struct scb *scb); +static int ahc_parse_msg(struct ahc_softc *ahc, struct scb *scb, + struct ahc_devinfo *devinfo); +static void ahc_handle_devreset(struct ahc_softc *ahc, int target, + char channel, cam_status status, + ac_code acode, char *message, + int verbose_only); +static void ahc_loadseq(struct ahc_softc *ahc); +static int ahc_check_patch(struct ahc_softc *ahc, + struct patch **start_patch, + int start_instr, int *skip_addr); +static void ahc_download_instr(struct ahc_softc *ahc, + int instrptr, u_int8_t *dconsts); +static int ahc_match_scb(struct scb *scb, int target, char channel, + int lun, u_int8_t tag); +#ifdef AHC_DEBUG +static void ahc_print_scb(struct scb *scb); +#endif +static u_int8_t ahc_find_scb(struct ahc_softc *ahc, struct scb *scb); +static int ahc_search_qinfifo(struct ahc_softc *ahc, int target, + char channel, int lun, u_int8_t tag, + u_int32_t status, ahc_search_action action); +static int ahc_reset_channel(struct ahc_softc *ahc, char channel, + int initiate_reset); +static int ahc_abort_scbs(struct ahc_softc *ahc, int target, + char channel, int lun, u_int8_t tag, + u_int32_t status); +static u_int8_t ahc_rem_scb_from_disc_list(struct ahc_softc *ahc, + u_int8_t prev, u_int8_t scbptr); +static void ahc_add_curscb_to_free_list(struct ahc_softc *ahc); +static void ahc_clear_intstat(struct ahc_softc *ahc); +static void ahc_reset_current_bus(struct ahc_softc *ahc); +static struct ahc_syncrate * + ahc_find_syncrate(struct ahc_softc *ahc, u_int *period, + u_int maxsync); +static u_int ahc_find_period(struct ahc_softc *ahc, u_int scsirate, + u_int maxsync); +static void ahc_validate_offset(struct ahc_softc *ahc, + struct ahc_syncrate *syncrate, + u_int *offset, int wide); +static void ahc_set_syncrate(struct ahc_softc *ahc, + struct ahc_devinfo *devinfo, + struct cam_path *path, + struct ahc_syncrate *syncrate, + u_int period, u_int offset, u_int type); +static void ahc_set_width(struct ahc_softc *ahc, + struct ahc_devinfo *devinfo, + struct cam_path *path, u_int width, u_int type); +static void ahc_construct_sdtr(struct ahc_softc *ahc, + u_int8_t period, u_int8_t offset); + +static void ahc_construct_wdtr(struct ahc_softc *ahc, + u_int8_t bus_width); + +static void ahc_calc_residual(struct scb *scb); + +static void ahc_update_pending_syncrates(struct ahc_softc *ahc); + +static void ahc_set_recoveryscb(struct ahc_softc *ahc, struct scb *scb); + +static timeout_t + ahc_timeout; +static __inline void pause_sequencer(struct ahc_softc *ahc); +static __inline void unpause_sequencer(struct ahc_softc *ahc, + int unpause_always); +static __inline void restart_sequencer(struct ahc_softc *ahc); +static __inline u_int8_t ahc_index_busy_tcl(struct ahc_softc *ahc, + u_int8_t tcl, int unbusy); + +static __inline void ahc_busy_tcl(struct ahc_softc *ahc, struct scb *scb); + +static __inline void ahc_freeze_ccb(union ccb* ccb); +static __inline cam_status ahc_ccb_status(union ccb* ccb); +static __inline void ahc_set_ccb_status(union ccb* ccb, + cam_status status); + +static __inline u_int32_t +ahc_hscb_busaddr(struct ahc_softc *ahc, u_int index) +{ + return (ahc->hscb_busaddr + (sizeof(struct hardware_scb) * index)); +} + +#define AHC_BUSRESET_DELAY 25 /* Reset delay in us */ + +static __inline void +pause_sequencer(struct ahc_softc *ahc) +{ + ahc_outb(ahc, HCNTRL, ahc->pause); + + /* + * Since the sequencer can disable pausing in a critical section, we + * must loop until it actually stops. + */ + while ((ahc_inb(ahc, HCNTRL) & PAUSE) == 0) + ; +} + +static __inline void +unpause_sequencer(struct ahc_softc *ahc, int unpause_always) +{ + if ((ahc->flags & AHC_HANDLING_REQINITS) == 0 + && (unpause_always + || (ahc_inb(ahc, INTSTAT) & (SCSIINT | SEQINT | BRKADRINT)) == 0)) + ahc_outb(ahc, HCNTRL, ahc->unpause); +} + +/* + * Restart the sequencer program from address zero + */ +static __inline void +restart_sequencer(struct ahc_softc *ahc) +{ + pause_sequencer(ahc); + ahc_outb(ahc, SEQCTL, FASTMODE|SEQRESET); + unpause_sequencer(ahc, /*unpause_always*/TRUE); +} + +static __inline u_int8_t +ahc_index_busy_tcl(struct ahc_softc *ahc, u_int8_t tcl, int unbusy) +{ + u_int8_t scbid; + + scbid = ahc->untagged_scbs[tcl]; + if (unbusy) + ahc->untagged_scbs[tcl] = SCB_LIST_NULL; + + return (scbid); +} + +static __inline void +ahc_busy_tcl(struct ahc_softc *ahc, struct scb *scb) +{ + ahc->untagged_scbs[scb->hscb->tcl] = scb->hscb->tag; +} + +static __inline void +ahc_freeze_ccb(union ccb* ccb) +{ + if ((ccb->ccb_h.status & CAM_DEV_QFRZN) == 0) { + ccb->ccb_h.status |= CAM_DEV_QFRZN; + xpt_freeze_devq(ccb->ccb_h.path, /*count*/1); + } +} + +static __inline cam_status +ahc_ccb_status(union ccb* ccb) +{ + return (ccb->ccb_h.status & CAM_STATUS_MASK); +} + +static __inline void +ahc_set_ccb_status(union ccb* ccb, cam_status status) +{ + ccb->ccb_h.status &= ~CAM_STATUS_MASK; + ccb->ccb_h.status |= status; +} + +char * +ahc_name(struct ahc_softc *ahc) +{ + static char name[10]; + + sprintf(name, "ahc%d", ahc->unit); + return (name); +} + +#ifdef AHC_DEBUG +static void +ahc_print_scb(struct scb *scb) +{ + struct hardware_scb *hscb = scb->hscb; + + printf("scb:%p control:0x%x tcl:0x%x cmdlen:%d cmdpointer:0x%lx\n", + scb, + hscb->control, + hscb->tcl, + hscb->cmdlen, + hscb->cmdpointer ); + printf(" datlen:%d data:0x%lx segs:0x%x segp:0x%lx\n", + hscb->datalen, + hscb->data, + hscb->SG_count, + hscb->SG_pointer); + printf(" sg_addr:%lx sg_len:%ld\n", + scb->ahc_dma[0].addr, + scb->ahc_dma[0].len); + printf(" cdb:%x %x %x %x %x %x %x %x %x %x %x %x\n", + hscb->cmdstore[0], hscb->cmdstore[1], hscb->cmdstore[2], + hscb->cmdstore[3], hscb->cmdstore[4], hscb->cmdstore[5], + hscb->cmdstore[6], hscb->cmdstore[7], hscb->cmdstore[8], + hscb->cmdstore[9], hscb->cmdstore[10], hscb->cmdstore[11]); +} +#endif + +static struct { + u_int8_t errno; + char *errmesg; +} hard_error[] = { + { ILLHADDR, "Illegal Host Access" }, + { ILLSADDR, "Illegal Sequencer Address referrenced" }, + { ILLOPCODE, "Illegal Opcode in sequencer program" }, + { SQPARERR, "Sequencer Parity Error" }, + { DPARERR, "Data-path Parity Error" }, + { MPARERR, "Scratch or SCB Memory Parity Error" }, + { PCIERRSTAT, "PCI Error detected" }, + { CIOPARERR, "CIOBUS Parity Error" }, +}; + + +/* + * Valid SCSIRATE values. (p. 3-17) + * Provides a mapping of tranfer periods in ns to the proper value to + * stick in the scsiscfr reg to use that transfer rate. + */ +#define AHC_SYNCRATE_ULTRA2 0 +#define AHC_SYNCRATE_ULTRA 2 +#define AHC_SYNCRATE_FAST 5 +static struct ahc_syncrate ahc_syncrates[] = { + /* ultra2 fast/ultra period rate */ + { 0x13, 0x000, 10, "40.0" }, + { 0x14, 0x000, 11, "33.0" }, + { 0x15, 0x100, 12, "20.0" }, + { 0x16, 0x110, 15, "16.0" }, + { 0x17, 0x120, 18, "13.4" }, + { 0x18, 0x000, 25, "10.0" }, + { 0x19, 0x010, 31, "8.0" }, + { 0x1a, 0x020, 37, "6.67" }, + { 0x1b, 0x030, 43, "5.7" }, + { 0x10, 0x040, 50, "5.0" }, + { 0x00, 0x050, 56, "4.4" }, + { 0x00, 0x060, 62, "4.0" }, + { 0x00, 0x070, 68, "3.6" }, + { 0x00, 0x000, 0, NULL } +}; + +/* + * Allocate a controller structure for a new device and initialize it. + */ +struct ahc_softc * +ahc_alloc(int unit, u_int32_t iobase, vm_offset_t maddr, ahc_chip chip, + ahc_feature features, ahc_flag flags, struct scb_data *scb_data) +{ + /* + * find unit and check we have that many defined + */ + struct ahc_softc *ahc; + size_t alloc_size; + + /* + * Allocate a storage area for us + */ + if (scb_data == NULL) + /* + * We are not sharing SCB space with another controller + * so allocate our own SCB data space. + */ + alloc_size = sizeof(struct full_ahc_softc); + else + alloc_size = sizeof(struct ahc_softc); + ahc = malloc(alloc_size, M_DEVBUF, M_NOWAIT); + if (!ahc) { + printf("ahc%d: cannot malloc!\n", unit); + return NULL; + } + bzero(ahc, alloc_size); + if (scb_data == NULL) { + struct full_ahc_softc* full_softc = (struct full_ahc_softc*)ahc; + ahc->scb_data = &full_softc->scb_data_storage; + STAILQ_INIT(&ahc->scb_data->free_scbs); + } else + ahc->scb_data = scb_data; + LIST_INIT(&ahc->pending_ccbs); + ahc->unit = unit; + + /* + * XXX This should be done by the bus specific probe stubs with + * the bus layer providing the bsh and tag. Unfortunately, + * we need to clean up how we configure things before this + * can happen. + */ + if (maddr != NULL) { + ahc->tag = I386_BUS_SPACE_MEM; + ahc->bsh = (bus_space_handle_t)maddr; + } else { + ahc->tag = I386_BUS_SPACE_IO; + ahc->bsh = (bus_space_handle_t)iobase; + } + ahc->chip = chip; + ahc->features = features; + ahc->flags = flags; + ahc->unpause = (ahc_inb(ahc, HCNTRL) & IRQMS) | INTEN; + ahc->pause = ahc->unpause | PAUSE; + + return (ahc); +} + +void +ahc_free(ahc) + struct ahc_softc *ahc; +{ + free(ahc, M_DEVBUF); + return; +} + +int +ahc_reset(struct ahc_softc *ahc) +{ + u_int8_t hcntrl; + u_int8_t sblkctl; + int wait; + + ahc_outb(ahc, HCNTRL, CHIPRST | ahc->pause); + /* + * Ensure that the reset has finished + */ + wait = 1000; + while (--wait && !(ahc_inb(ahc, HCNTRL) & CHIPRSTACK)) + DELAY(1000); + if (wait == 0) { + printf("%s: WARNING - Failed chip reset! " + "Trying to initialize anyway.\n", ahc_name(ahc)); + } + ahc_outb(ahc, HCNTRL, ahc->pause); + + /* Determine channel configuration */ + sblkctl = ahc_inb(ahc, SBLKCTL) & (SELBUSB|SELWIDE); + /* No Twin Channel PCI cards */ + if ((ahc->chip & AHC_PCI) != 0) + sblkctl &= ~SELBUSB; + switch (sblkctl) { + case 0: + /* Single Narrow Channel */ + break; + case 2: + /* Wide Channel */ + ahc->features |= AHC_WIDE; + break; + case 8: + /* Twin Channel */ + ahc->features |= AHC_TWIN; + break; + default: + printf(" Unsupported adapter type. Ignoring\n"); + return(-1); + } + return (0); +} + +/* + * Look up the valid period to SCSIRATE conversion in our table. + * Return the period and offset that should be sent to the target + * if this was the beginning of an SDTR. + */ +static struct ahc_syncrate * +ahc_find_syncrate(struct ahc_softc *ahc, u_int *period, u_int maxsync) +{ + struct ahc_syncrate *syncrate; + + syncrate = &ahc_syncrates[maxsync]; + while ((syncrate->rate != NULL) + && ((ahc->features & AHC_ULTRA2) == 0 + || (syncrate->sxfr_ultra2 != 0))) { + + if (*period <= syncrate->period) { + /* + * When responding to a target that requests + * sync, the requested rate may fall between + * two rates that we can output, but still be + * a rate that we can receive. Because of this, + * we want to respond to the target with + * the same rate that it sent to us even + * if the period we use to send data to it + * is lower. Only lower the response period + * if we must. + */ + if (syncrate == &ahc_syncrates[maxsync]) { + *period = syncrate->period; + } + break; + } + syncrate++; + } + + if ((*period == 0) + || (syncrate->rate == NULL) + || ((ahc->features & AHC_ULTRA2) != 0 + && (syncrate->sxfr_ultra2 == 0))) { + /* Use asynchronous transfers. */ + *period = 0; + syncrate = NULL; + } + return (syncrate); +} + +static u_int +ahc_find_period(struct ahc_softc *ahc, u_int scsirate, u_int maxsync) +{ + struct ahc_syncrate *syncrate; + + if ((ahc->features & AHC_ULTRA2) != 0) { + scsirate &= SXFR_ULTRA2; + } else { + scsirate &= SXFR; + } + + syncrate = &ahc_syncrates[maxsync]; + while (syncrate->rate != NULL) { + + if ((ahc->features & AHC_ULTRA2) != 0) { + if (syncrate->sxfr_ultra2 == 0) + break; + else if (scsirate == syncrate->sxfr_ultra2) + return (syncrate->period); + } else if (scsirate == (syncrate->sxfr & ~ULTRA_SXFR)) { + return (syncrate->period); + } + syncrate++; + } + return (0); /* async */ +} + +static void +ahc_validate_offset(struct ahc_softc *ahc, struct ahc_syncrate *syncrate, + u_int *offset, int wide) +{ + u_int maxoffset; + + /* Limit offset to what we can do */ + if (syncrate == NULL) { + maxoffset = 0; + } else if ((ahc->features & AHC_ULTRA2) != 0) { + maxoffset = MAX_OFFSET_ULTRA2; + } else { + if (wide) + maxoffset = MAX_OFFSET_16BIT; + else + maxoffset = MAX_OFFSET_8BIT; + } + *offset = MIN(*offset, maxoffset); +} + +static void +ahc_set_syncrate(struct ahc_softc *ahc, struct ahc_devinfo *devinfo, + struct cam_path *path, struct ahc_syncrate *syncrate, + u_int period, u_int offset, u_int type) +{ + u_int old_period; + u_int old_offset; + + if (syncrate == NULL) { + period = 0; + offset = 0; + } + + old_period = ahc->transinfo[devinfo->target_offset].current.period; + old_offset = ahc->transinfo[devinfo->target_offset].current.offset; + + if ((type & AHC_TRANS_CUR) != 0 + && (old_period != period || old_offset != offset)) { + struct ccb_trans_settings neg; + u_int scsirate; + + scsirate = ahc->transinfo[devinfo->target_offset].scsirate; + if ((ahc->features & AHC_ULTRA2) != 0) { + + scsirate &= ~SXFR_ULTRA2; + + if (syncrate != NULL) { + scsirate |= syncrate->sxfr_ultra2; + } + + if ((type & AHC_TRANS_ACTIVE) == AHC_TRANS_ACTIVE) { + ahc_outb(ahc, SCSIOFFSET, offset); + } + ahc_outb(ahc, TARG_OFFSET + devinfo->target_offset, + offset); + } else { + + scsirate &= ~(SXFR|SOFS); + /* + * Ensure Ultra mode is set properly for + * this target. + */ + ahc->ultraenb &= ~devinfo->target_mask; + if (syncrate != NULL) { + if (syncrate->sxfr & ULTRA_SXFR) { + ahc->ultraenb |= devinfo->target_mask; + } + scsirate |= syncrate->sxfr & SXFR; + scsirate |= offset & SOFS; + } + if ((type & AHC_TRANS_ACTIVE) == AHC_TRANS_ACTIVE) { + u_int8_t sxfrctl0; + + sxfrctl0 = ahc_inb(ahc, SXFRCTL0); + sxfrctl0 &= ~FAST20; + if (ahc->ultraenb & devinfo->target_mask) + sxfrctl0 |= FAST20; + ahc_outb(ahc, SXFRCTL0, sxfrctl0); + } + } + if ((type & AHC_TRANS_ACTIVE) == AHC_TRANS_ACTIVE) + ahc_outb(ahc, SCSIRATE, scsirate); + + ahc->transinfo[devinfo->target_offset].scsirate = scsirate; + ahc->transinfo[devinfo->target_offset].current.period = period; + ahc->transinfo[devinfo->target_offset].current.offset = offset; + + /* Update the syncrates in any pending scbs */ + ahc_update_pending_syncrates(ahc); + + /* + * Tell the SCSI layer about the + * new transfer parameters. + */ + neg.sync_period = period; + neg.sync_offset = offset; + neg.valid = CCB_TRANS_SYNC_RATE_VALID + | CCB_TRANS_SYNC_OFFSET_VALID; + xpt_setup_ccb(&neg.ccb_h, path, /*priority*/1); + xpt_async(AC_TRANSFER_NEG, path, &neg); + if (bootverbose) { + if (neg.sync_offset != 0) { + printf("%s: target %d synchronous at %sMHz, " + "offset = 0x%x\n", ahc_name(ahc), + devinfo->target, syncrate->rate, offset); + } else { + printf("%s: target %d using " + "asynchronous transfers\n", + ahc_name(ahc), devinfo->target); + } + } + } + + if ((type & AHC_TRANS_GOAL) != 0) { + ahc->transinfo[devinfo->target_offset].goal.period = period; + ahc->transinfo[devinfo->target_offset].goal.offset = offset; + } + + if ((type & AHC_TRANS_USER) != 0) { + ahc->transinfo[devinfo->target_offset].user.period = period; + ahc->transinfo[devinfo->target_offset].user.offset = offset; + } +} + +static void +ahc_set_width(struct ahc_softc *ahc, struct ahc_devinfo *devinfo, + struct cam_path *path, u_int width, u_int type) +{ + u_int oldwidth; + + oldwidth = ahc->transinfo[devinfo->target_offset].current.width; + + if ((type & AHC_TRANS_CUR) != 0 && oldwidth != width) { + struct ccb_trans_settings neg; + u_int8_t scsirate; + + scsirate = ahc->transinfo[devinfo->target_offset].scsirate; + scsirate &= ~WIDEXFER; + if (width == MSG_EXT_WDTR_BUS_16_BIT) + scsirate |= WIDEXFER; + + ahc->transinfo[devinfo->target_offset].scsirate = scsirate; + + if ((type & AHC_TRANS_ACTIVE) == AHC_TRANS_ACTIVE) + ahc_outb(ahc, SCSIRATE, scsirate); + + ahc->transinfo[devinfo->target_offset].current.width = width; + + /* Tell the SCSI layer about the new transfer params */ + neg.bus_width = width; + neg.valid = CCB_TRANS_BUS_WIDTH_VALID; + xpt_setup_ccb(&neg.ccb_h, path, /*priority*/1); + xpt_async(AC_TRANSFER_NEG, path, &neg); + if (bootverbose) { + printf("%s: target %d using %dbit transfers\n", + ahc_name(ahc), devinfo->target, + 8 * (0x01 << neg.bus_width)); + } + } + if ((type & AHC_TRANS_GOAL) != 0) { + ahc->transinfo[devinfo->target_offset].goal.width = width; + } + if ((type & AHC_TRANS_USER) != 0) { + ahc->transinfo[devinfo->target_offset].user.width = width; + } +} + +/* + * Attach all the sub-devices we can find + */ +int +ahc_attach(struct ahc_softc *ahc) +{ + struct ccb_setasync csa; + struct cam_devq *devq; + int bus_id; + + /* + * Create the device queue for our SIM. + */ + devq = cam_simq_alloc(ahc->scb_data->maxscbs); + if (devq == NULL) + return (0); + + /* + * Construct our SIM entry + */ + ahc->sim = cam_sim_alloc(ahc_action, ahc_poll, "ahc", ahc, ahc->unit, + 1, ahc->scb_data->maxscbs, devq); + if (ahc->sim == NULL) { + cam_simq_free(devq); + return (0); + } + bus_id = (ahc->flags & AHC_CHANNEL_B_PRIMARY) ? 1 : 0; + + if (xpt_bus_register(ahc->sim, bus_id) != CAM_SUCCESS) { + cam_sim_free(ahc->sim, /*free_devq*/TRUE); + return (0); + } + + if (xpt_create_path(&ahc->path, /*periph*/NULL, + cam_sim_path(ahc->sim), CAM_TARGET_WILDCARD, + CAM_LUN_WILDCARD) != CAM_REQ_CMP) { + xpt_bus_deregister(cam_sim_path(ahc->sim)); + cam_sim_free(ahc->sim, /*free_devq*/TRUE); + return (0); + } + + xpt_setup_ccb(&csa.ccb_h, ahc->path, /*priority*/5); + csa.ccb_h.func_code = XPT_SASYNC_CB; + csa.event_enable = AC_LOST_DEVICE; + csa.callback = ahc_async; + csa.callback_arg = ahc->sim; + xpt_action((union ccb *)&csa); + + if (ahc->features & AHC_TWIN) { + ahc->sim_b = cam_sim_alloc(ahc_action, ahc_poll, "ahc", + ahc, ahc->unit, 1, + ahc->scb_data->maxscbs, devq); + + if (ahc->sim_b == NULL) { + printf("ahc_attach: Unable to attach second " + "bus due to resource shortage"); + /* + * Must return success or the first bus + * won't get attached either. + */ + return (1); + } + + bus_id = (ahc->flags & AHC_CHANNEL_B_PRIMARY) ? 0 : 1; + if (xpt_bus_register(ahc->sim_b, bus_id) != CAM_SUCCESS) { + printf("ahc_attach: Unable to attach second " + "bus due to resource shortage"); + /* + * We do not want to destroy the device queue + * because the first bus is using it. + */ + cam_sim_free(ahc->sim_b, /*free_devq*/FALSE); + ahc->sim_b = NULL; + return (1); + } + + if (xpt_create_path(&ahc->path_b, /*periph*/NULL, + cam_sim_path(ahc->sim_b), + CAM_TARGET_WILDCARD, + CAM_LUN_WILDCARD) != CAM_REQ_CMP) { + xpt_bus_deregister(cam_sim_path(ahc->sim_b)); + cam_sim_free(ahc->sim_b, /*free_devq*/FALSE); + ahc->sim_b = NULL; + return (1); + } + xpt_setup_ccb(&csa.ccb_h, ahc->path_b, /*priority*/5); + csa.ccb_h.func_code = XPT_SASYNC_CB; + csa.event_enable = AC_LOST_DEVICE; + csa.callback = ahc_async; + csa.callback_arg = ahc->sim_b; + xpt_action((union ccb *)&csa); + } + return (1); +} + +static void +ahc_fetch_devinfo(struct ahc_softc *ahc, struct ahc_devinfo *devinfo) +{ + u_int8_t saved_tcl; + + saved_tcl = ahc_inb(ahc, SAVED_TCL); + ahc_compile_devinfo(devinfo, (saved_tcl >> 4) & 0x0f, + (saved_tcl & SELBUSB) ? 'B': 'A'); +} + +static void +ahc_compile_devinfo(struct ahc_devinfo *devinfo, u_int target, char channel) +{ + devinfo->target = target; + devinfo->target_offset = target; + devinfo->channel = channel; + if (channel == 'B') + devinfo->target_offset += 8; + devinfo->target_mask = (0x01 << devinfo->target_offset); +} + +/* + * Catch an interrupt from the adapter + */ +void +ahc_intr(void *arg) +{ + struct ahc_softc *ahc; + u_int8_t intstat; + + ahc = (struct ahc_softc *)arg; + + intstat = ahc_inb(ahc, INTSTAT); + + /* + * Any interrupts to process? + */ +#if NPCI > 0 + if ((intstat & INT_PEND) == 0) { + if ((ahc->chip & AHC_PCI) != 0 + && (ahc->unsolicited_ints > 500)) { + if ((ahc_inb(ahc, ERROR) & PCIERRSTAT) != 0) + ahc_pci_intr(ahc); + ahc->unsolicited_ints = 0; + } else { + ahc->unsolicited_ints++; + } + return; + } else { + ahc->unsolicited_ints = 0; + } +#else + if ((intstat & INT_PEND) == 0) + return; +#endif + + if (intstat & CMDCMPLT) { + struct scb *scb; + u_int8_t scb_index; + + ahc_outb(ahc, CLRINT, CLRCMDINT); + while (ahc->qoutfifo[ahc->qoutfifonext] != SCB_LIST_NULL) { + scb_index = ahc->qoutfifo[ahc->qoutfifonext]; + ahc->qoutfifo[ahc->qoutfifonext++] = SCB_LIST_NULL; + + if (scb_index == TARGET_CMD_CMPLT) { + ahc_handle_target_cmd(ahc); + continue; + } + + scb = ahc->scb_data->scbarray[scb_index]; + if (!scb || !(scb->flags & SCB_ACTIVE)) { + printf("%s: WARNING no command for scb %d " + "(cmdcmplt)\nQOUTPOS = %d\n", + ahc_name(ahc), scb_index, + ahc->qoutfifonext - 1); + continue; + } + + /* + * Save off the residual + * if there is one. + */ + if (scb->hscb->residual_SG_count != 0) + ahc_calc_residual(scb); + ahc_done(ahc, scb); + } + } + if (intstat & BRKADRINT) { + /* + * We upset the sequencer :-( + * Lookup the error message + */ + int i, error, num_errors; + + error = ahc_inb(ahc, ERROR); + num_errors = sizeof(hard_error)/sizeof(hard_error[0]); + for (i = 0; error != 1 && i < num_errors; i++) + error >>= 1; + panic("%s: brkadrint, %s at seqaddr = 0x%x\n", + ahc_name(ahc), hard_error[i].errmesg, + ahc_inb(ahc, SEQADDR0) | + (ahc_inb(ahc, SEQADDR1) << 8)); + + /* Tell everyone that this HBA is no longer availible */ + ahc_abort_scbs(ahc, ALL_TARGETS, ALL_CHANNELS, + ALL_LUNS, SCB_LIST_NULL, CAM_NO_HBA); + } + if (intstat & SEQINT) + ahc_handle_seqint(ahc, intstat); + + if (intstat & SCSIINT) + ahc_handle_scsiint(ahc, intstat); +} + +static void +ahc_handle_target_cmd(struct ahc_softc *ahc) +{ + struct tmode_tstate *tstate; + struct tmode_lstate *lstate; + struct ccb_accept_tio *atio; + struct target_cmd *cmd; + u_int8_t *byte; + int initiator; + int target; + int lun; + + cmd = &ahc->targetcmds[ahc->next_targetcmd]; + ahc->next_targetcmd++; + if (ahc->next_targetcmd >= ahc->num_targetcmds) + ahc->next_targetcmd = 0; + + initiator = cmd->icl >> 4; + target = cmd->targ_id; + lun = (cmd->identify & MSG_IDENTIFY_LUNMASK); + + xpt_print_path(ahc->path); + printf("Received Target Command (%d:%d:%d)\n", + initiator, target, lun); + ahc_dump_targcmd(cmd); + + byte = cmd->bytes; + tstate = ahc->enabled_targets[target]; + lstate = NULL; + if (tstate != NULL && lun < 8) + lstate = tstate->enabled_luns[lun]; + + /* + * XXX Need to have a default TMODE devce that attaches to luns + * that wouldn't otherwise be enabled and returns the proper + * inquiry information. After all, we don't want to duplicate + * this code in each driver. For now, simply drop it on the + * floor. + */ + if (lstate == NULL) { + printf("Incoming Command on disabled lun\n"); + return; + } + + atio = (struct ccb_accept_tio*)SLIST_FIRST(&lstate->accept_tios); + /* XXX Should reconnect and return BUSY status */ + if (atio == NULL) { + printf("No ATIOs for incoming command\n"); + return; + } + + /* + * Package it up and send it off to + * whomever has this lun enabled. + */ + atio->init_id = initiator; + if (byte[0] != 0xFF) { + /* Tag was included */ + atio->tag_action = *byte++; + atio->tag_id = *byte++; + atio->ccb_h.flags = CAM_TAG_ACTION_VALID; + } else { + byte++; + atio->ccb_h.flags = 0; + } + + /* Okay. Now determine the cdb size based on the command code */ + switch (*byte >> CMD_GROUP_CODE_SHIFT) { + case 0: + atio->cdb_len = 6; + break; + case 1: + case 2: + atio->cdb_len = 10; + break; + case 4: + atio->cdb_len = 16; + break; + case 5: + atio->cdb_len = 12; + break; + case 3: + default: + /* Only copy the opcode. */ + atio->cdb_len = 1; + printf("Reserved or VU command code type encountered\n"); + break; + } + bcopy(byte, atio->cdb_io.cdb_bytes, atio->cdb_len); + + SLIST_REMOVE_HEAD(&lstate->accept_tios, sim_links.sle); + atio->ccb_h.status |= CAM_CDB_RECVD; + + if ((cmd->identify & MSG_IDENTIFY_DISCFLAG) == 0) { + /* + * We weren't allowed to disconnect. + * We're hanging on the bus until a + * continue target I/O comes in response + * to this accept tio. + */ + xpt_print_path(atio->ccb_h.path); + printf("Incoming Command did not disconnect %x\n", lstate); + ahc->pending_device = lstate; + } + xpt_done((union ccb*)atio); +} + +static void +ahc_handle_seqint(struct ahc_softc *ahc, u_int8_t intstat) +{ + struct scb *scb; + struct ahc_devinfo devinfo; + + ahc_fetch_devinfo(ahc, &devinfo); + + /* + * Clear the upper byte that holds SEQINT status + * codes and clear the SEQINT bit. We will unpause + * the sequencer, if appropriate, after servicing + * the request. + */ + ahc_outb(ahc, CLRINT, CLRSEQINT); + switch (intstat & SEQINT_MASK) { + case NO_MATCH: + { + /* Ensure we don't leave the selection hardware on */ + ahc_outb(ahc, SCSISEQ, + ahc_inb(ahc, SCSISEQ) & (ENSELI|ENRSELI|ENAUTOATNP)); + printf("%s:%c:%d: no active SCB for reconnecting " + "target - issuing BUS DEVICE RESET\n", + ahc_name(ahc), devinfo.channel, devinfo.target); + printf("SAVED_TCL == 0x%x, ARG_1 == 0x%x, SEQ_FLAGS == 0x%x\n", + ahc_inb(ahc, SAVED_TCL), ahc_inb(ahc, ARG_1), + ahc_inb(ahc, SEQ_FLAGS)); + break; + } + case SEND_REJECT: + { + u_int8_t rejbyte = ahc_inb(ahc, ACCUM); + printf("%s:%c:%d: Warning - unknown message received from " + "target (0x%x). Rejecting\n", + ahc_name(ahc), devinfo.channel, devinfo.target, rejbyte); + break; + } + case NO_IDENT: + { + /* + * The reconnecting target either did not send an identify + * message, or did, but we didn't find and SCB to match and + * before it could respond to our ATN/abort, it hit a dataphase. + * The only safe thing to do is to blow it away with a bus + * reset. + */ + int found; + + printf("%s:%c:%d: Target did not send an IDENTIFY message. " + "LASTPHASE = 0x%x, SAVED_TCL == 0x%x\n", + ahc_name(ahc), devinfo.channel, devinfo.target, + ahc_inb(ahc, LASTPHASE), ahc_inb(ahc, SAVED_TCL)); + found = ahc_reset_channel(ahc, devinfo.channel, + /*initiate reset*/TRUE); + printf("%s: Issued Channel %c Bus Reset. " + "%d SCBs aborted\n", ahc_name(ahc), devinfo.channel, + found); + break; + } + case BAD_PHASE: + if (ahc_inb(ahc, LASTPHASE) == P_BUSFREE) { + printf("%s:%c:%d: Missed busfree.\n", ahc_name(ahc), + devinfo.channel, devinfo.target); + restart_sequencer(ahc); + return; + } else { + printf("%s:%c:%d: unknown scsi bus phase. Attempting " + "to continue\n", ahc_name(ahc), devinfo.channel, + devinfo.target); + } + break; + case EXTENDED_MSG: + { + ahc->msg_type = MSG_TYPE_INITIATOR_MSGIN; + ahc->msg_len = 0; + ahc->msg_index = 0; + + /* + * To actually receive the message, simply turn on + * REQINIT interrupts and let our interrupt handler + * do the rest (REQINIT should already be true). + */ + ahc_outb(ahc, SIMODE1, ahc_inb(ahc, SIMODE1) | ENREQINIT); + ahc->flags |= AHC_HANDLING_REQINITS; + return; + } + case REJECT_MSG: + { + /* + * What we care about here is if we had an + * outstanding SDTR or WDTR message for this + * target. If we did, this is a signal that + * the target is refusing negotiation. + */ + + u_int8_t scb_index; + u_int8_t last_msg; + + scb_index = ahc_inb(ahc, SCB_TAG); + scb = ahc->scb_data->scbarray[scb_index]; + + last_msg = ahc_inb(ahc, LAST_MSG); + + if ((last_msg == MSG_IDENTIFYFLAG) + && (scb->hscb->control & MSG_SIMPLE_Q_TAG) != 0) { + struct ccb_trans_settings neg; + + printf("%s:%c:%d: refuses tagged commands. Performing " + "non-tagged I/O\n", ahc_name(ahc), + devinfo.channel, devinfo.target); + + ahc->tagenable &= ~devinfo.target_mask; + neg.flags = 0; + neg.valid = CCB_TRANS_TQ_VALID; + xpt_setup_ccb(&neg.ccb_h, scb->ccb->ccb_h.path, + /*priority*/1); + xpt_async(AC_TRANSFER_NEG, scb->ccb->ccb_h.path, &neg); + /* + * Resend the identify for this CCB as the target + * may believe that the selection is invalid otherwise. + */ + ahc_outb(ahc, SCB_CONTROL, ahc_inb(ahc, SCB_CONTROL) + & ~MSG_SIMPLE_Q_TAG); + scb->hscb->control &= ~MSG_SIMPLE_Q_TAG; + scb->ccb->ccb_h.flags &= ~CAM_TAG_ACTION_VALID; + ahc_outb(ahc, MSG_OUT, MSG_IDENTIFYFLAG); + ahc_outb(ahc, SCSISIGO, ahc_inb(ahc, SCSISIGO) | ATNO); + + /* + * Requeue all tagged commands for this target + * currently in our posession so they can be + * converted to untagged commands. + */ + ahc_search_qinfifo(ahc, SCB_TARGET(scb), + SCB_CHANNEL(scb), + SCB_LUN(scb), + /*tag*/SCB_LIST_NULL, + CAM_REQUEUE_REQ, + SEARCH_COMPLETE); + } else if ((last_msg == MSG_IDENTIFYFLAG + || last_msg == HOST_MSG) + && (scb->flags & SCB_MSGOUT_WDTR) != 0) { + struct ahc_target_tinfo *tinfo; + + /* note 8bit xfers and clear flag */ + printf("%s:%c:%d: refuses WIDE negotiation. Using " + "8bit transfers\n", ahc_name(ahc), + devinfo.channel, devinfo.target); + scb->flags &= ~SCB_MSGOUT_BITS; + ahc->wdtrpending &= ~devinfo.target_mask; + ahc_set_width(ahc, &devinfo, scb->ccb->ccb_h.path, + MSG_EXT_WDTR_BUS_8_BIT, + AHC_TRANS_ACTIVE|AHC_TRANS_GOAL); + ahc_set_syncrate(ahc, &devinfo, scb->ccb->ccb_h.path, + /*syncrate*/NULL, /*period*/0, + /*offset*/0, AHC_TRANS_ACTIVE); + tinfo = &ahc->transinfo[devinfo.target_offset]; + if (tinfo->goal.period) { + /* Start the sync negotiation */ + ahc->sdtrpending |= devinfo.target_mask; + scb->flags |= SCB_MSGOUT_SDTR; + ahc_outb(ahc, MSG_OUT, HOST_MSG); + ahc_outb(ahc, SCSISIGO, + ahc_inb(ahc, SCSISIGO) | ATNO); + } + } else if ((last_msg == MSG_IDENTIFYFLAG + || last_msg == HOST_MSG) + && (scb->flags & SCB_MSGOUT_SDTR) != 0) { + + /* note asynch xfers and clear flag */ + ahc_set_syncrate(ahc, &devinfo, scb->ccb->ccb_h.path, + /*syncrate*/NULL, /*period*/0, + /*offset*/0, + AHC_TRANS_ACTIVE|AHC_TRANS_GOAL); + scb->flags &= ~SCB_MSGOUT_BITS; + ahc->sdtrpending &= ~devinfo.target_mask; + printf("%s:%c:%d: refuses synchronous negotiation. " + "Using asynchronous transfers\n", + ahc_name(ahc), + devinfo.channel, devinfo.target); + } else { + /* + * Otherwise, we ignore it. + */ +#ifdef AHC_DEBUG + if (ahc_debug & AHC_SHOWMISC) + printf("%s:%c:%d: Message reject -- ignored\n", + ahc_name(ahc), devinfo.channel, + devinfo.target); +#endif + break; + } + break; + } + case BAD_STATUS: + { + u_int8_t scb_index; + struct hardware_scb *hscb; + struct ccb_scsiio *csio; + /* + * The sequencer will notify us when a command + * has an error that would be of interest to + * the kernel. This allows us to leave the sequencer + * running in the common case of command completes + * without error. The sequencer will already have + * dma'd the SCB back up to us, so we can reference + * the in kernel copy directly. + */ + scb_index = ahc_inb(ahc, SCB_TAG); + scb = ahc->scb_data->scbarray[scb_index]; + hscb = scb->hscb; + + /* + * Set the default return value to 0 (don't + * send sense). The sense code will change + * this if needed. + */ + ahc_outb(ahc, RETURN_1, 0); + if (!(scb && (scb->flags & SCB_ACTIVE))) { + printf("%s:%c:%d: ahc_intr - referenced scb " + "not valid during seqint 0x%x scb(%d)\n", + ahc_name(ahc), devinfo.channel, + devinfo.target, intstat, scb_index); + goto unpause; + } + + /* Don't want to clobber the original sense code */ + if ((scb->flags & SCB_SENSE) != 0) { + /* + * Clear the SCB_SENSE Flag and have + * the sequencer do a normal command + * complete. + */ + scb->flags &= ~SCB_SENSE; + ahc_set_ccb_status(scb->ccb, CAM_AUTOSENSE_FAIL); + break; + } + ahc_set_ccb_status(scb->ccb, CAM_SCSI_STATUS_ERROR); + csio = &scb->ccb->csio; + csio->scsi_status = hscb->status; + switch (hscb->status) { + case SCSI_STATUS_OK: + printf("%s: Interrupted for staus of 0???\n", + ahc_name(ahc)); + break; + case SCSI_STATUS_CMD_TERMINATED: + case SCSI_STATUS_CHECK_COND: +#ifdef AHC_DEBUG + if (ahc_debug & AHC_SHOWSENSE) { + xpt_print_path(csio->ccb_h.path); + printf("SCB %d: requests Check Status\n", + scb->hscb->tag); + } +#endif + + if ((csio->ccb_h.flags & CAM_DIS_AUTOSENSE) == 0) { + struct ahc_dma_seg *sg = scb->ahc_dma; + struct scsi_sense *sc = + (struct scsi_sense *)(&hscb->cmdstore); + struct ahc_target_tinfo *tinfo; + + /* + * Save off the residual if there is one. + */ + if (hscb->residual_SG_count != 0) + ahc_calc_residual(scb); + +#ifdef AHC_DEBUG + if (ahc_debug & AHC_SHOWSENSE) { + xpt_print_path(csio->ccb_h.path); + printf("Sending Sense\n"); + } +#endif + /* + * bzero from the sense data before having + * the drive fill it. The SCSI spec mandates + * that any untransfered data should be + * assumed to be zero. + */ + bzero(&csio->sense_data, + sizeof(csio->sense_data)); + sc->opcode = REQUEST_SENSE; + sc->byte2 = SCB_LUN(scb) << 5; + sc->unused[0] = 0; + sc->unused[1] = 0; + sc->length = csio->sense_len; + sc->control = 0; + + sg->addr = vtophys(&csio->sense_data); + sg->len = csio->sense_len; + + /* + * Would be nice to preserve DISCENB here, + * but due to the way we page SCBs, we can't. + */ + hscb->control = 0; + /* + * This request sense could be because the + * the device lost power or in some other + * way has lost our transfer negotiations. + * Renegotiate if appropriate. + */ + ahc_set_width(ahc, &devinfo, + scb->ccb->ccb_h.path, + MSG_EXT_WDTR_BUS_8_BIT, + AHC_TRANS_CUR); + ahc_set_syncrate(ahc, &devinfo, + scb->ccb->ccb_h.path, + /*syncrate*/NULL, /*period*/0, + /*offset*/0, AHC_TRANS_CUR); + scb->flags &= ~SCB_MSGOUT_BITS; + tinfo = &ahc->transinfo[devinfo.target_offset]; + if (tinfo->goal.width) { + ahc->wdtrpending |= devinfo.target_mask; + hscb->control |= MK_MESSAGE; + scb->flags |= SCB_MSGOUT_WDTR; + } else if (tinfo->goal.period) { + ahc->sdtrpending |= devinfo.target_mask; + hscb->control |= MK_MESSAGE; + scb->flags |= SCB_MSGOUT_SDTR; + } + hscb->status = 0; + hscb->SG_count = 1; + hscb->SG_pointer = scb->ahc_dmaphys; + hscb->data = sg->addr; + hscb->datalen = sg->len; + hscb->cmdpointer = hscb->cmdstore_busaddr; + hscb->cmdlen = sizeof(*sc); + scb->sg_count = hscb->SG_count; + scb->flags |= SCB_SENSE; + /* + * Ensure the target is busy since this + * will be an untagged request. + */ + ahc_busy_tcl(ahc, scb); + ahc_outb(ahc, RETURN_1, SEND_SENSE); + + /* + * Ensure we have enough time to actually + * retrieve the sense. + */ + untimeout(ahc_timeout, (caddr_t)scb, + scb->ccb->ccb_h.timeout_ch); + scb->ccb->ccb_h.timeout_ch = + timeout(ahc_timeout, (caddr_t)scb, 5 * hz); + /* Freeze the queue while the sense occurs. */ + ahc_freeze_devq(ahc, scb->ccb->ccb_h.path); + ahc_freeze_ccb(scb->ccb); + break; + } + break; + case SCSI_STATUS_BUSY: + case SCSI_STATUS_QUEUE_FULL: + /* + * Requeue any transactions that haven't been + * sent yet. + */ + ahc_freeze_devq(ahc, scb->ccb->ccb_h.path); + ahc_freeze_ccb(scb->ccb); + break; + } + break; + } + case TARGET_SYNC_CMD: + { + /* + * We've already processed the command. If the command + * is still pending, don't unpause the sequencer until + * it returns. + */ + xpt_print_path(ahc->path); + printf("Saw a target sync cmd\n"); + if (ahc->pending_device != NULL) { + printf(" Pending device too.\n"); + return; + } + break; + } + case TARGET_MSG_HELP: + { + /* + * XXX Handle BDR, Abort, Abort Tag, and transfer negotiations. + */ + restart_sequencer(ahc); + return; + } + case AWAITING_MSG: + { + int scb_index; + + scb_index = ahc_inb(ahc, SCB_TAG); + scb = ahc->scb_data->scbarray[scb_index]; + + /* + * To facilitate adding multiple messages together, + * each routine should increment the index and len + * variables instead of setting them explicitly. + */ + ahc->msg_index = 0; + ahc->msg_len = 0; + + /* + * This SCB had MK_MESSAGE set in its control byte or + * we have explicitly set HOST_MSG in MSG_OUT, + * informing the sequencer that we want to send a + * special message to this target. + */ + if ((scb->flags & SCB_DEVICE_RESET) == 0 + && ahc_inb(ahc, MSG_OUT) == MSG_IDENTIFYFLAG + && (scb->hscb->control & TAG_ENB) != 0) { + ahc->msg_buf[ahc->msg_index++] = + scb->ccb->csio.tag_action; + ahc->msg_buf[ahc->msg_index++] = + scb->hscb->tag; + ahc->msg_len += 2; + } + + if (scb->flags & SCB_DEVICE_RESET) { + ahc->msg_buf[ahc->msg_index++] = MSG_BUS_DEV_RESET; + ahc->msg_len++; + xpt_print_path(scb->ccb->ccb_h.path); + printf("Bus Device Reset Message Sent\n"); + } else if (scb->flags & SCB_ABORT) { + if ((scb->hscb->control & TAG_ENB) != 0) + ahc->msg_buf[ahc->msg_index++] = MSG_ABORT_TAG; + else + ahc->msg_buf[ahc->msg_index++] = MSG_ABORT; + ahc->msg_len++; + xpt_print_path(scb->ccb->ccb_h.path); + printf("Abort Message Sent\n"); + } else if (scb->flags & SCB_MSGOUT_WDTR) { + struct ahc_target_tinfo *tinfo; + + tinfo = &ahc->transinfo[devinfo.target_offset]; + ahc_construct_wdtr(ahc, tinfo->goal.width); + } else if (scb->flags & SCB_MSGOUT_SDTR) { + struct ahc_target_tinfo *tinfo; + u_int period; + u_int maxsync; + + /* + * Now that the target is actually selected, we + * can further refine our sync rate based on the + * output transceiver mode. + */ + if ((ahc->features & AHC_ULTRA2) != 0) { + if ((ahc_inb(ahc, SBLKCTL) & ENAB40) != 0 + && (ahc_inb(ahc, SSTAT2) & EXP_ACTIVE) == 0) { + maxsync = AHC_SYNCRATE_ULTRA2; + } else { + maxsync = AHC_SYNCRATE_ULTRA; + } + } else if ((ahc->features & AHC_ULTRA) != 0) { + maxsync = AHC_SYNCRATE_ULTRA; + } else { + maxsync = AHC_SYNCRATE_FAST; + } + tinfo = &ahc->transinfo[devinfo.target_offset]; + period = tinfo->goal.period; + ahc_find_syncrate(ahc, &period, maxsync); + ahc_construct_sdtr(ahc, period, tinfo->goal.offset); + } else { + printf("ahc_intr: AWAITING_MSG for an SCB that " + "does not have a waiting message"); + panic("SCB = %d, SCB Control = %x, MSG_OUT = %x " + "SCB flags = %x", scb_index, scb->hscb->control, + ahc_inb(ahc, MSG_OUT), scb->flags); + } + + /* + * Record the fact that we attempted to send a message. + */ + scb->flags |= SCB_MSGOUT_SENT; + + /* + * To actually send the message, simply turn on + * REQINIT interrupts and let our interrupt handler + * do the rest (REQINIT should already be true). + */ + ahc->msg_index = 0; + ahc->msg_type = MSG_TYPE_INITIATOR_MSGOUT; + ahc->flags |= AHC_HANDLING_REQINITS; + ahc_outb(ahc, SIMODE1, ahc_inb(ahc, SIMODE1) | ENREQINIT); + + return; + } + case DATA_OVERRUN: + { + /* + * When the sequencer detects an overrun, it + * places the controller in "BITBUCKET" mode + * and allows the target to complete its transfer. + * Unfortunately, none of the counters get updated + * when the controller is in this mode, so we have + * no way of knowing how large the overrun was. + */ + u_int8_t scbindex = ahc_inb(ahc, SCB_TAG); + u_int8_t lastphase = ahc_inb(ahc, LASTPHASE); + int i; + + scb = ahc->scb_data->scbarray[scbindex]; + xpt_print_path(scb->ccb->ccb_h.path); + printf("data overrun detected in %s phase." + " Tag == 0x%x.\n", + lastphase == P_DATAIN ? "Data-In" : "Data-Out", + scb->hscb->tag); + xpt_print_path(scb->ccb->ccb_h.path); + printf("%s seen Data Phase. Length = %d. NumSGs = %d.\n", + ahc_inb(ahc, SEQ_FLAGS) & DPHASE ? "Have" : "Haven't", + scb->ccb->csio.dxfer_len, scb->sg_count); + for (i = 0; i < scb->sg_count - 1; i++) { + printf("sg[%d] - Addr 0x%x : Length %d\n", + i, + scb->ahc_dma[i].addr, + scb->ahc_dma[i].len); + } + /* + * Set this and it will take affect when the + * target does a command complete. + */ + ahc_freeze_devq(ahc, scb->ccb->ccb_h.path); + ahc_set_ccb_status(scb->ccb, CAM_DATA_RUN_ERR); + ahc_freeze_ccb(scb->ccb); + break; + } + case TRACEPOINT: + { + printf("TRACEPOINT: RETURN_2 = %d\n", ahc_inb(ahc, RETURN_2)); +#if 0 + printf("SSTAT1 == 0x%x\n", ahc_inb(ahc, SSTAT1)); + printf("SSTAT0 == 0x%x\n", ahc_inb(ahc, SSTAT0)); + printf(", SCSISIGI == 0x%x\n", ahc_inb(ahc, SCSISIGI)); + printf("TRACEPOINT: CCHCNT = %d, SG_COUNT = %d\n", + ahc_inb(ahc, CCHCNT), ahc_inb(ahc, SG_COUNT)); + printf("TRACEPOINT: SCB_TAG = %d\n", ahc_inb(ahc, SCB_TAG)); + printf("TRACEPOINT1: CCHADDR = %d, CCHCNT = %d, SCBPTR = %d\n", + ahc_inb(ahc, CCHADDR) + | (ahc_inb(ahc, CCHADDR+1) << 8) + | (ahc_inb(ahc, CCHADDR+2) << 16) + | (ahc_inb(ahc, CCHADDR+3) << 24), + ahc_inb(ahc, CCHCNT) + | (ahc_inb(ahc, CCHCNT+1) << 8) + | (ahc_inb(ahc, CCHCNT+2) << 16), + ahc_inb(ahc, SCBPTR)); + printf("TRACEPOINT: WAITING_SCBH = %d\n", ahc_inb(ahc, WAITING_SCBH)); + printf("TRACEPOINT: SCB_TAG = %d\n", ahc_inb(ahc, SCB_TAG)); +#endif + break; + } +#if NOT_YET + /* XXX Fill these in later */ + case MESG_BUFFER_BUSY: + break; + case MSGIN_PHASEMIS: + break; +#endif + default: + printf("ahc_intr: seqint, " + "intstat == 0x%x, scsisigi = 0x%x\n", + intstat, ahc_inb(ahc, SCSISIGI)); + break; + } + +unpause: + /* + * The sequencer is paused immediately on + * a SEQINT, so we should restart it when + * we're done. + */ + unpause_sequencer(ahc, /*unpause_always*/TRUE); +} + +static void +ahc_handle_scsiint(struct ahc_softc *ahc, u_int8_t intstat) +{ + u_int8_t scb_index; + u_int8_t status; + struct scb *scb; + + scb_index = ahc_inb(ahc, SCB_TAG); + status = ahc_inb(ahc, SSTAT1); + + if (scb_index < ahc->scb_data->numscbs) { + scb = ahc->scb_data->scbarray[scb_index]; + if ((scb->flags & SCB_ACTIVE) == 0) + scb = NULL; + } else + scb = NULL; + + if ((status & SCSIRSTI) != 0) { + char channel; + channel = 'A'; + if ((ahc->features & AHC_TWIN) != 0 + && ((ahc_inb(ahc, SBLKCTL) & SELBUSB) != 0)) + channel = 'B'; + printf("%s: Someone reset channel %c\n", + ahc_name(ahc), channel); + ahc_reset_channel(ahc, channel, /* Initiate Reset */FALSE); + } else if ((status & BUSFREE) != 0 && (status & SELTO) == 0) { + /* + * First look at what phase we were last in. + * If its message out, chances are pretty good + * that the busfree was in response to one of + * our abort requests. + */ + u_int8_t lastphase = ahc_inb(ahc, LASTPHASE); + u_int8_t saved_tcl = ahc_inb(ahc, SAVED_TCL); + u_int8_t target = (saved_tcl >> 4) & 0x0f; + char channel = saved_tcl & SELBUSB ? 'B': 'A'; + int printerror = 1; + + ahc_outb(ahc, SCSISEQ, + ahc_inb(ahc, SCSISEQ) & (ENSELI|ENRSELI|ENAUTOATNP)); + if (lastphase == P_MESGOUT) { + u_int message; + u_int tag; + + message = ahc_inb(ahc, SINDEX); + + tag = SCB_LIST_NULL; + switch (message) { + case MSG_ABORT_TAG: + tag = scb->hscb->tag; + /* FALLTRHOUGH */ + case MSG_ABORT: + xpt_print_path(scb->ccb->ccb_h.path); + printf("SCB %d - Abort %s Completed.\n", + tag == SCB_LIST_NULL ? "" : "Tag", + scb->hscb->tag); + if ((scb->flags & SCB_RECOVERY_SCB) != 0) { + ahc_set_ccb_status(scb->ccb, + CAM_REQ_ABORTED); + ahc_done(ahc, scb); + } + printerror = 0; + break; + case MSG_BUS_DEV_RESET: + ahc_handle_devreset(ahc, target, channel, + CAM_BDR_SENT, AC_SENT_BDR, + "Bus Device Reset Sent", + /*verbose_only*/FALSE); + printerror = 0; + break; + default: + break; + } + } + if (printerror != 0) { + if (scb != NULL) { + u_int8_t tag; + + if ((scb->hscb->control & TAG_ENB) != 0) + tag = scb->hscb->tag; + else + tag = SCB_LIST_NULL; + ahc_abort_scbs(ahc, target, channel, + SCB_LUN(scb), tag, + CAM_UNEXP_BUSFREE); + } else { + ahc_abort_scbs(ahc, target, channel, + ALL_LUNS, SCB_LIST_NULL, + CAM_UNEXP_BUSFREE); + printf("%s: ", ahc_name(ahc)); + } + printf("Unexpected busfree. LASTPHASE == 0x%x\n" + "SEQADDR == 0x%x\n", + lastphase, ahc_inb(ahc, SEQADDR0) + | (ahc_inb(ahc, SEQADDR1) << 8)); + } + ahc_outb(ahc, MSG_OUT, MSG_NOOP); + ahc_outb(ahc, SIMODE1, + ahc_inb(ahc, SIMODE1) & ~(ENBUSFREE|ENREQINIT)); + ahc->flags &= ~AHC_HANDLING_REQINITS; + ahc_outb(ahc, CLRSINT1, CLRBUSFREE); + ahc_outb(ahc, CLRINT, CLRSCSIINT); + restart_sequencer(ahc); + } else if ((status & SELTO) != 0) { + u_int8_t scbptr; + u_int8_t nextscb; + + scbptr = ahc_inb(ahc, WAITING_SCBH); + ahc_outb(ahc, SCBPTR, scbptr); + scb_index = ahc_inb(ahc, SCB_TAG); + + if (scb_index < ahc->scb_data->numscbs) { + scb = ahc->scb_data->scbarray[scb_index]; + if ((scb->flags & SCB_ACTIVE) == 0) + scb = NULL; + } else + scb = NULL; + + if (scb == NULL) { + printf("%s: ahc_intr - referenced scb not " + "valid during SELTO scb(%d, %d)\n", + ahc_name(ahc), scbptr, scb_index); + } else { + /* + * Clear any pending messages for the timed out + * target. + */ + ahc_outb(ahc, MSG_OUT, MSG_NOOP); + ahc_handle_devreset(ahc, SCB_TARGET(scb), + SCB_CHANNEL(scb), CAM_SEL_TIMEOUT, + /*ac_code*/0, "Selection Timeout", + /*verbose_only*/TRUE); + } + /* Stop the selection */ + ahc_outb(ahc, SCSISEQ, 0); + + ahc_outb(ahc, SIMODE1, + ahc_inb(ahc, SIMODE1) & ~ENREQINIT); + ahc->flags &= ~AHC_HANDLING_REQINITS; + + ahc_outb(ahc, CLRSINT1, CLRSELTIMEO|CLRBUSFREE); + + ahc_outb(ahc, CLRINT, CLRSCSIINT); + + restart_sequencer(ahc); + } else if (scb == NULL) { + printf("%s: ahc_intr - referenced scb not " + "valid during scsiint 0x%x scb(%d)\n" + "SIMODE0 = 0x%x, SIMODE1 = 0x%x, SSTAT0 = 0x%x\n" + "SEQADDR = 0x%x\n", ahc_name(ahc), + status, scb_index, ahc_inb(ahc, SIMODE0), + ahc_inb(ahc, SIMODE1), ahc_inb(ahc, SSTAT0), + ahc_inb(ahc, SEQADDR0) | (ahc_inb(ahc, SEQADDR1) << 8)); + ahc_outb(ahc, CLRSINT1, status); + ahc_outb(ahc, CLRINT, CLRSCSIINT); + unpause_sequencer(ahc, /*unpause_always*/TRUE); + scb = NULL; + } else if ((status & SCSIPERR) != 0) { + /* + * Determine the bus phase and + * queue an appropriate message + */ + char *phase; + u_int8_t mesg_out = MSG_NOOP; + u_int8_t lastphase = ahc_inb(ahc, LASTPHASE); + + xpt_print_path(scb->ccb->ccb_h.path); + + switch (lastphase) { + case P_DATAOUT: + phase = "Data-Out"; + break; + case P_DATAIN: + phase = "Data-In"; + mesg_out = MSG_INITIATOR_DET_ERR; + break; + case P_COMMAND: + phase = "Command"; + break; + case P_MESGOUT: + phase = "Message-Out"; + break; + case P_STATUS: + phase = "Status"; + mesg_out = MSG_INITIATOR_DET_ERR; + break; + case P_MESGIN: + phase = "Message-In"; + mesg_out = MSG_PARITY_ERROR; + break; + default: + phase = "unknown"; + break; + } + printf("parity error during %s phase.\n", phase); + + printf("SEQADDR == 0x%x\n", ahc_inb(ahc, SEQADDR0) + | (ahc_inb(ahc, SEQADDR1) << 8)); + + printf("SCSIRATE == 0x%x\n", ahc_inb(ahc, SCSIRATE)); + + /* + * We've set the hardware to assert ATN if we + * get a parity error on "in" phases, so all we + * need to do is stuff the message buffer with + * the appropriate message. "In" phases have set + * mesg_out to something other than MSG_NOP. + */ + if (mesg_out != MSG_NOOP) { + ahc_outb(ahc, MSG_OUT, mesg_out); + } + ahc_outb(ahc, CLRSINT1, CLRSCSIPERR); + ahc_outb(ahc, CLRINT, CLRSCSIINT); + unpause_sequencer(ahc, /*unpause_always*/TRUE); + } else if ((status & REQINIT) != 0 + && (ahc->flags & AHC_HANDLING_REQINITS) != 0) { + ahc_handle_reqinit(ahc, scb); + } else { + xpt_print_path(scb->ccb->ccb_h.path); + printf("Unknown SCSIINT. Status = 0x%x\n", status); + ahc_outb(ahc, CLRSINT1, status); + ahc_outb(ahc, CLRINT, CLRSCSIINT); + unpause_sequencer(ahc, /*unpause_always*/TRUE); + } +} + +static void +ahc_handle_reqinit(struct ahc_softc *ahc, struct scb *scb) +{ + struct ahc_devinfo devinfo; + u_int8_t simode1; + + ahc_fetch_devinfo(ahc, &devinfo); + + switch (ahc->msg_type) { + case MSG_TYPE_INITIATOR_MSGOUT: + { + int lastbyte; + int phasemis; + u_int8_t bus_phase; + + if (ahc->msg_len == 0) + panic("REQINIT interrupt with no active message"); + + lastbyte = (ahc->msg_index == ahc->msg_len - 1); + bus_phase = ahc_inb(ahc, SCSISIGI) & PHASE_MASK; + phasemis = bus_phase != P_MESGOUT; + + if (lastbyte || phasemis) { + /* Time to end our message session */ + ahc->msg_len = 0; + ahc->msg_type = MSG_TYPE_NONE; + simode1 = ahc_inb(ahc, SIMODE1) & ~ENREQINIT; + ahc_outb(ahc, SIMODE1, simode1); + ahc_outb(ahc, CLRINT, CLRSCSIINT); + ahc->flags &= ~AHC_HANDLING_REQINITS; + + if (phasemis == 0) { + ahc_outb(ahc, SINDEX, + ahc->msg_buf[ahc->msg_index]); + ahc_outb(ahc, RETURN_1, 0); + } else { + ahc_outb(ahc, RETURN_1, MSGOUT_PHASEMIS); + } + + unpause_sequencer(ahc, /* unpause_always */TRUE); + } else { + /* + * Clear our interrupt status and present the byte + * on the bus, but don't unpause the sequencer. + */ + ahc_outb(ahc, CLRSINT1, CLRREQINIT); + ahc_outb(ahc, CLRINT, CLRSCSIINT); + ahc_outb(ahc, SCSIDATL, ahc->msg_buf[ahc->msg_index++]); + } + break; + } + case MSG_TYPE_INITIATOR_MSGIN: + { + int phasemis; + int done; + + phasemis = (ahc_inb(ahc, SCSISIGI) & PHASE_MASK) != P_MESGIN; + + if (phasemis == 0) { + + ahc->msg_len++; + /* Pull the byte in without acking it */ + ahc->msg_buf[ahc->msg_index] = ahc_inb(ahc, SCSIBUSL); + done = ahc_parse_msg(ahc, scb, &devinfo); + /* Ack the byte */ + ahc_outb(ahc, CLRSINT1, CLRREQINIT); + ahc_outb(ahc, CLRINT, CLRSCSIINT); + ahc_inb(ahc, SCSIDATL); + ahc->msg_index++; + } + if (phasemis || done) { + /* Time to end our message session */ + ahc->msg_len = 0; + ahc->msg_type = MSG_TYPE_NONE; + simode1 = ahc_inb(ahc, SIMODE1) & ~ENREQINIT; + ahc->flags &= ~AHC_HANDLING_REQINITS; + ahc_outb(ahc, SIMODE1, simode1); + ahc_outb(ahc, CLRINT, CLRSCSIINT); + unpause_sequencer(ahc, /* unpause_always */TRUE); + } + break; + } + default: + panic("Unknown REQINIT message type"); + } +} + +static int +ahc_parse_msg(struct ahc_softc *ahc, struct scb *scb, + struct ahc_devinfo *devinfo) +{ + int reject; + int done; + u_int targ_scsirate; + + done = FALSE; + reject = FALSE; + targ_scsirate = ahc->transinfo[devinfo->target_offset].scsirate; + /* + * Parse as much of the message as is availible, + * rejecting it if we don't support it. When + * the entire message is availible and has been + * handled, return TRUE indicating that we have + * parsed an entire message. + */ + if (ahc->msg_buf[0] != MSG_EXTENDED) { + reject = TRUE; + } + + /* + * Just accept the length byte outright and perform + * more checking once we know the message type. + */ + if (!reject && (ahc->msg_len > 2)) { + switch (ahc->msg_buf[2]) { + case MSG_EXT_SDTR: + { + struct ahc_syncrate *syncrate; + u_int period; + u_int offset; + u_int saved_offset; + u_int maxsync; + + if (ahc->msg_buf[1] != MSG_EXT_SDTR_LEN) { + reject = TRUE; + break; + } + + /* + * Wait until we have both args before validating + * and acting on this message. + */ + if (ahc->msg_len < (MSG_EXT_SDTR_LEN + /*preamble*/2)) + break; + + period = ahc->msg_buf[3]; + saved_offset = offset = ahc->msg_buf[4]; + if ((ahc->features & AHC_ULTRA2) != 0) { + if ((ahc_inb(ahc, SBLKCTL) & ENAB40) != 0 + && (ahc_inb(ahc, SSTAT2) & EXP_ACTIVE) == 0) { + maxsync = AHC_SYNCRATE_ULTRA2; + } else { + maxsync = AHC_SYNCRATE_ULTRA; + } + } else if ((ahc->features & AHC_ULTRA) != 0) { + maxsync = AHC_SYNCRATE_ULTRA; + } else { + maxsync = AHC_SYNCRATE_FAST; + } + syncrate = ahc_find_syncrate(ahc, &period, maxsync); + ahc_validate_offset(ahc, syncrate, &offset, + targ_scsirate & WIDEXFER); + ahc_set_syncrate(ahc, devinfo, scb->ccb->ccb_h.path, + syncrate, period, offset, + AHC_TRANS_ACTIVE|AHC_TRANS_GOAL); + + /* + * See if we initiated Sync Negotiation + * and didn't have to fall down to async + * transfers. + */ + if ((scb->flags & (SCB_MSGOUT_SDTR|SCB_MSGOUT_SENT)) + == (SCB_MSGOUT_SDTR|SCB_MSGOUT_SENT)) { + /* We started it */ + if (saved_offset != offset) { + /* Went too low - force async */ + reject = TRUE; + } + scb->flags &= ~SCB_MSGOUT_BITS; + ahc->sdtrpending &= ~devinfo->target_mask; + } else { + /* + * Send our own SDTR in reply + */ + scb->flags &= ~SCB_MSGOUT_BITS; + scb->flags |= SCB_MSGOUT_SDTR; + ahc->sdtrpending |= devinfo->target_mask; + xpt_print_path(scb->ccb->ccb_h.path); + printf("Sending SDTR!!\n"); + ahc_outb(ahc, MSG_OUT, HOST_MSG); + ahc_outb(ahc, SCSISIGO, + ahc_inb(ahc, SCSISIGO) | ATNO); + } + done = TRUE; + break; + } + case MSG_EXT_WDTR: + { + struct ccb_trans_settings neg; + u_int8_t bus_width; + + if (ahc->msg_buf[1] != MSG_EXT_WDTR_LEN) { + reject = TRUE; + break; + } + + /* + * Wait until we have our arg before validating + * and acting on this message. + */ + if (ahc->msg_len < (MSG_EXT_WDTR_LEN + /*preamble*/2)) + break; + + bus_width = ahc->msg_buf[3]; + if ((scb->flags & (SCB_MSGOUT_WDTR|SCB_MSGOUT_SENT)) + == (SCB_MSGOUT_WDTR|SCB_MSGOUT_SENT)) { + /* + * Don't send a WDTR back to the + * target, since we asked first. + */ + switch (bus_width){ + default: + /* + * How can we do anything greater + * than 16bit transfers on a 16bit + * bus? + */ + reject = TRUE; + printf("%s: target %d requested %dBit " + "transfers. Rejecting...\n", + ahc_name(ahc), devinfo->target, + 8 * (0x01 << bus_width)); + /* FALLTHROUGH */ + case MSG_EXT_WDTR_BUS_8_BIT: + bus_width = MSG_EXT_WDTR_BUS_8_BIT; + break; + case MSG_EXT_WDTR_BUS_16_BIT: + break; + } + scb->flags &= ~SCB_MSGOUT_WDTR; + ahc->wdtrpending &= ~devinfo->target_mask; + } else { + /* + * Send our own WDTR in reply + */ + printf("Sending WDTR!\n"); + scb->flags &= ~SCB_MSGOUT_BITS; + scb->flags |= SCB_MSGOUT_WDTR; + switch (bus_width) { + default: + if (ahc->features & AHC_WIDE) { + /* Respond Wide */ + bus_width = + MSG_EXT_WDTR_BUS_16_BIT; + break; + } + /* FALLTHROUGH */ + case MSG_EXT_WDTR_BUS_8_BIT: + bus_width = MSG_EXT_WDTR_BUS_8_BIT; + break; + } + ahc_outb(ahc, MSG_OUT, HOST_MSG); + ahc_outb(ahc, SCSISIGO, + ahc_inb(ahc, SCSISIGO) | ATNO); + ahc->wdtrpending |= devinfo->target_mask; + } + ahc_set_width(ahc, devinfo, scb->ccb->ccb_h.path, + bus_width, + AHC_TRANS_ACTIVE|AHC_TRANS_GOAL); + + /* After a wide message, we are async */ + ahc_set_syncrate(ahc, devinfo, scb->ccb->ccb_h.path, + /*syncrate*/NULL, /*period*/0, + /*offset*/0, AHC_TRANS_ACTIVE); + if ((ahc->wdtrpending & devinfo->target_mask) == 0 + && (reject == 0)) { + struct ahc_target_tinfo *tinfo; + + scb->flags &= ~SCB_MSGOUT_WDTR; + tinfo = &ahc->transinfo[devinfo->target_offset]; + if (tinfo->goal.period) { + /* Start the sync negotiation */ + ahc->sdtrpending |= + devinfo->target_mask; + scb->flags |= SCB_MSGOUT_SDTR; + ahc_outb(ahc, MSG_OUT, HOST_MSG); + ahc_outb(ahc, SCSISIGO, + ahc_inb(ahc, SCSISIGO) | ATNO); + } + } + done = TRUE; + break; + } + default: + /* Unknown extended message. Reject it. */ + reject = TRUE; + break; + } + } + + if (reject) { + /* + * Assert attention and setup to + * reject the message. + */ + ahc_outb(ahc, MSG_OUT, MSG_MESSAGE_REJECT); + ahc_outb(ahc, SCSISIGO, ahc_inb(ahc, SCSISIGO) | ATNO); + done = TRUE; + } + return (done); +} + +static void +ahc_handle_devreset(struct ahc_softc *ahc, int target, char channel, + cam_status status, ac_code acode, char *message, + int verbose_only) +{ + struct ahc_devinfo devinfo; + struct cam_path *path; + path_id_t path_id; + u_int16_t targ_mask; + u_int8_t targ_scsirate; + int scratch_offset = target; + int found; + int error; + + ahc_compile_devinfo(&devinfo, target, channel); + + if (channel == 'B') + path_id = cam_sim_path(ahc->sim_b); + else + path_id = cam_sim_path(ahc->sim); + + error = xpt_create_path(&path, /*periph*/NULL, path_id, target, + CAM_LUN_WILDCARD); + /* + * Go back to async/narrow transfers and renegotiate. + */ + if (error == CAM_REQ_CMP) { + ahc_set_width(ahc, &devinfo, path, MSG_EXT_WDTR_BUS_8_BIT, + AHC_TRANS_CUR); + ahc_set_syncrate(ahc, &devinfo, path, /*syncrate*/NULL, + /*period*/0, /*offset*/0, AHC_TRANS_CUR); + } + found = ahc_abort_scbs(ahc, target, channel, ALL_LUNS, + SCB_LIST_NULL, status); + + if (error == CAM_REQ_CMP && acode != 0) + xpt_async(AC_SENT_BDR, path, NULL); + + if (error == CAM_REQ_CMP) + xpt_free_path(path); + + if (message != NULL + && (verbose_only == 0 || bootverbose != 0)) + printf("%s: %s on %c:%d. %d SCBs aborted\n", ahc_name(ahc), + message, channel, target, found); +} +/* + * We have an scb which has been processed by the + * adaptor, now we look to see how the operation + * went. + */ +static void +ahc_done(struct ahc_softc *ahc, struct scb *scb) +{ + union ccb *ccb; + + CAM_DEBUG(scb->ccb->ccb_h.path, CAM_DEBUG_TRACE, + ("ahc_done - scb %d\n", scb->hscb->tag)); + + ccb = scb->ccb; + LIST_REMOVE(&ccb->ccb_h, sim_links.le); + + untimeout(ahc_timeout, (caddr_t)scb, ccb->ccb_h.timeout_ch); + + if ((ccb->ccb_h.flags & CAM_DIR_MASK) != CAM_DIR_NONE) { + bus_dmasync_op_t op; + + if ((ccb->ccb_h.flags & CAM_DIR_MASK) == CAM_DIR_IN) + op = BUS_DMASYNC_POSTREAD; + else + op = BUS_DMASYNC_POSTWRITE; + bus_dmamap_sync(ahc->dmat, scb->dmamap, op); + bus_dmamap_unload(ahc->dmat, scb->dmamap); + } + + /* + * Unbusy this target/channel/lun. + * XXX if we are holding two commands per lun, + * send the next command. + */ + ahc_index_busy_tcl(ahc, scb->hscb->tcl, /*unbusy*/TRUE); + + if (ccb->ccb_h.func_code == XPT_CONT_TARGET_IO) { + xpt_print_path(ccb->ccb_h.path); + printf("CONT_TARGET_IO complete\n"); + ccb->ccb_h.status = CAM_REQ_CMP; + ahc_free_scb(ahc, scb); + xpt_done(ccb); + return; + } + + /* + * If the recovery SCB completes, we have to be + * out of our timeout. + */ + if ((scb->flags & SCB_RECOVERY_SCB) != 0) { + + struct ccb_hdr *ccbh; + + /* + * We were able to complete the command successfully, + * so reinstate the timeouts for all other pending + * commands. + */ + ccbh = ahc->pending_ccbs.lh_first; + while (ccbh != NULL) { + struct scb *pending_scb; + + pending_scb = (struct scb *)ccbh->ccb_scb_ptr; + ccbh->timeout_ch = + timeout(ahc_timeout, pending_scb, + (ccbh->timeout * hz)/1000); + ccbh = LIST_NEXT(ccbh, sim_links.le); + } + + /* + * Ensure that we didn't put a second instance of this + * SCB into the QINFIFO. + */ + ahc_search_qinfifo(ahc, SCB_TARGET(scb), SCB_CHANNEL(scb), + SCB_LUN(scb), scb->hscb->tag, /*status*/0, + SEARCH_REMOVE); + if (ahc_ccb_status(ccb) == CAM_BDR_SENT) + ahc_set_ccb_status(ccb, CAM_CMD_TIMEOUT); + xpt_print_path(ccb->ccb_h.path); + printf("no longer in timeout, status = %x\n", + ccb->ccb_h.status); + } + + if ((scb->flags & (SCB_MSGOUT_WDTR|SCB_MSGOUT_SDTR)) != 0) { + /* + * Turn off the pending flags for any DTR messages + * regardless of whether they completed successfully + * or not. This ensures that we don't have lingering + * state after we abort an SCB. + */ + u_int16_t mask; + + mask = (0x01 << (SCB_TARGET(scb) + | (SCB_IS_SCSIBUS_B(scb) ? SELBUSB : 0))); + if (scb->flags & SCB_MSGOUT_WDTR) + ahc->wdtrpending &= ~mask; + if (scb->flags & SCB_MSGOUT_SDTR) + ahc->sdtrpending &= ~mask; + } + /* Don't clobber any existing error state */ + if (ahc_ccb_status(ccb) == CAM_REQ_INPROG) { + ccb->ccb_h.status |= CAM_REQ_CMP; + } else if ((scb->flags & SCB_SENSE) != 0) { + /* We performed autosense retrieval */ + scb->ccb->ccb_h.status |= CAM_AUTOSNS_VALID; + } + ccb->ccb_h.status &= ~CAM_SIM_QUEUED; + ahc_free_scb(ahc, scb); + xpt_done(ccb); +} + +/* + * Determine the number of SCBs available on the controller + */ +int +ahc_probe_scbs(struct ahc_softc *ahc) { + int i; + + for (i = 0; i < AHC_SCB_MAX; i++) { + ahc_outb(ahc, SCBPTR, i); + ahc_outb(ahc, SCB_CONTROL, i); + if (ahc_inb(ahc, SCB_CONTROL) != i) + break; + ahc_outb(ahc, SCBPTR, 0); + if (ahc_inb(ahc, SCB_CONTROL) != 0) + break; + } + + return (i); +} + +/* + * Start the board, ready for normal operation + */ +int +ahc_init(struct ahc_softc *ahc) +{ + int max_targ = 15; + int i; + int term; + u_int8_t scsi_conf, sxfrctl1; + +#ifdef AHC_PRINT_SRAM + printf("Scratch Ram:"); + for (i = 0x20; i < 0x5f; i++) { + if (((i % 8) == 0) && (i != 0)) { + printf ("\n "); + } + printf (" 0x%x", ahc_inb(ahc, i)); + } + if ((ahc->features & AHC_MORE_SRAM) != 0) { + for (i = 0x70; i < 0x7f; i++) { + if (((i % 8) == 0) && (i != 0)) { + printf ("\n "); + } + printf (" 0x%x", ahc_inb(ahc, i)); + } + } + printf ("\n"); +#endif + + /* + * Assume we have a board at this stage and it has been reset. + */ + if ((ahc->flags & AHC_USEDEFAULTS) != 0) { + ahc->our_id = ahc->our_id_b = 7; + } + + /* + * XXX Would be better to use a per device flag, but PCI and EISA + * devices don't have them yet. + */ + if ((AHC_TMODE_ENABLE & (0x01 << ahc->unit)) != 0) + ahc->flags |= AHC_TARGETMODE; + + if ((ahc->features & AHC_TWIN) != 0) { + printf("Twin Channel, A SCSI Id=%d, B SCSI Id=%d, ", + ahc->our_id, ahc->our_id_b); + } else { + if ((ahc->features & AHC_WIDE) != 0) { + printf("Wide "); + } else { + printf("Single "); + } + printf("Channel %c, SCSI Id=%d, ", ahc->channel, ahc->our_id); + } + + ahc_outb(ahc, SEQ_FLAGS, 0); + + /* Determine the number of SCBs and initialize them */ + + if (ahc->scb_data->maxhscbs == 0) { + ahc->scb_data->maxhscbs = ahc_probe_scbs(ahc); + /* SCB 0 heads the free list */ + ahc_outb(ahc, FREE_SCBH, 0); + for (i = 0; i < ahc->scb_data->maxhscbs; i++) { + ahc_outb(ahc, SCBPTR, i); + + /* Clear the control byte. */ + ahc_outb(ahc, SCB_CONTROL, 0); + + /* Set the next pointer */ + ahc_outb(ahc, SCB_NEXT, i+1); + + /* Make the tag number invalid */ + ahc_outb(ahc, SCB_TAG, SCB_LIST_NULL); + } + + /* Make that the last SCB terminates the free list */ + ahc_outb(ahc, SCBPTR, i-1); + ahc_outb(ahc, SCB_NEXT, SCB_LIST_NULL); + + /* Ensure we clear the 0 SCB's control byte. */ + ahc_outb(ahc, SCBPTR, 0); + ahc_outb(ahc, SCB_CONTROL, 0); + + ahc->scb_data->maxhscbs = i; + } + + if (ahc->scb_data->maxhscbs == 0) + panic("%s: No SCB space found", ahc_name(ahc)); + + if (ahc->scb_data->maxhscbs < AHC_SCB_MAX) { + ahc->flags |= AHC_PAGESCBS; + ahc->scb_data->maxscbs = AHC_SCB_MAX; + if ((ahc->flags & AHC_TARGETMODE) != 0) { + /* Steal one slot for TMODE commands */ + ahc->scb_data->maxscbs--; + } + printf("%d/%d SCBs\n", ahc->scb_data->maxhscbs, + ahc->scb_data->maxscbs); + } else { + ahc->scb_data->maxscbs = ahc->scb_data->maxhscbs; + if ((ahc->flags & AHC_TARGETMODE) != 0) { + /* Steal one slot for TMODE commands */ + ahc->scb_data->maxscbs--; + } + ahc->flags &= ~AHC_PAGESCBS; + printf("%d SCBs\n", ahc->scb_data->maxhscbs); + } + +#ifdef AHC_DEBUG + if (ahc_debug & AHC_SHOWMISC) { + printf("%s: hardware scb %d bytes; kernel scb %d bytes; " + "ahc_dma %d bytes\n", + ahc_name(ahc), + sizeof(struct hardware_scb), + sizeof(struct scb), + sizeof(struct ahc_dma_seg)); + } +#endif /* AHC_DEBUG */ + + /* Set the SCSI Id, SXFRCTL0, SXFRCTL1, and SIMODE1, for both channels*/ + if (ahc->features & AHC_TWIN) { + + /* + * The device is gated to channel B after a chip reset, + * so set those values first + */ + term = (ahc->flags & AHC_TERM_ENB_B) != 0 ? STPWEN : 0; + if ((ahc->features & AHC_ULTRA2) != 0) + ahc_outb(ahc, SCSIID_ULTRA2, ahc->our_id_b); + else + ahc_outb(ahc, SCSIID, ahc->our_id_b); + scsi_conf = ahc_inb(ahc, SCSICONF + 1); + sxfrctl1 = ahc_inb(ahc, SXFRCTL1); + ahc_outb(ahc, SXFRCTL1, (scsi_conf & (ENSPCHK|STIMESEL)) + |term + |ENSTIMER|ACTNEGEN); + ahc_outb(ahc, SIMODE1, ENSELTIMO|ENSCSIRST|ENSCSIPERR); + ahc_outb(ahc, SXFRCTL0, DFON|SPIOEN); + + if (scsi_conf & RESET_SCSI) { + /* Reset the bus */ + if (bootverbose) + printf("%s: Resetting Channel B\n", + ahc_name(ahc)); + ahc_reset_current_bus(ahc); + } + + /* Select Channel A */ + ahc_outb(ahc, SBLKCTL, ahc_inb(ahc, SBLKCTL) & ~SELBUSB); + } + term = (ahc->flags & AHC_TERM_ENB_A) != 0 ? STPWEN : 0; + if ((ahc->features & AHC_ULTRA2) != 0) + ahc_outb(ahc, SCSIID_ULTRA2, ahc->our_id); + else + ahc_outb(ahc, SCSIID, ahc->our_id); + scsi_conf = ahc_inb(ahc, SCSICONF); + sxfrctl1 = ahc_inb(ahc, SXFRCTL1); + ahc_outb(ahc, SXFRCTL1, (scsi_conf & (ENSPCHK|STIMESEL)) + |term + |ENSTIMER|ACTNEGEN); + ahc_outb(ahc, SIMODE1, ENSELTIMO|ENSCSIRST|ENSCSIPERR); + ahc_outb(ahc, SXFRCTL0, DFON|SPIOEN); + + if ((ahc->features & AHC_ULTRA2) != 0) { + /* Wait for our transceiver status to settle */ + i = 1000000; + while (--i && ((ahc_inb(ahc, SBLKCTL) & (ENAB40|ENAB20)) == 0)) + DELAY(100); + + if (i == 0) + panic("%s: Transceiver state never settled\n", + ahc_name(ahc)); + } + + if (scsi_conf & RESET_SCSI) { + /* Reset the bus */ + if (bootverbose) + printf("%s: Resetting Channel %c\n", ahc_name(ahc), + ahc->channel); + + ahc_reset_current_bus(ahc); + } + + /* + * Look at the information that board initialization or + * the board bios has left us. In the lower four bits of each + * target's scratch space any value other than 0 indicates + * that we should initiate synchronous transfers. If it's zero, + * the user or the BIOS has decided to disable synchronous + * negotiation to that target so we don't activate the needsdtr + * flag. + */ + ahc->ultraenb = 0; + ahc->tagenable = ALL_TARGETS; + + /* Grab the disconnection disable table and invert it for our needs */ + if (ahc->flags & AHC_USEDEFAULTS) { + printf("%s: Host Adapter Bios disabled. Using default SCSI " + "device parameters\n", ahc_name(ahc)); + ahc->flags |= AHC_EXTENDED_TRANS_A|AHC_EXTENDED_TRANS_B; + ahc->discenable = ALL_TARGETS; + if ((ahc->features & AHC_ULTRA) != 0) + ahc->ultraenb = 0xffff; + } else { + ahc->discenable = ~((ahc_inb(ahc, DISC_DSB + 1) << 8) + | ahc_inb(ahc, DISC_DSB)); + if ((ahc->features & (AHC_ULTRA|AHC_ULTRA2)) != 0) + ahc->ultraenb = (ahc_inb(ahc, ULTRA_ENB + 1) << 8) + | ahc_inb(ahc, ULTRA_ENB); + } + + if ((ahc->features & (AHC_WIDE|AHC_TWIN)) == 0) + max_targ = 7; + + for (i = 0; i <= max_targ; i++) { + struct ahc_target_tinfo *transinfo; + + transinfo = &ahc->transinfo[i]; + /* Default to async narrow across the board */ + bzero(transinfo, sizeof(*transinfo)); + if (ahc->flags & AHC_USEDEFAULTS) { + if ((ahc->features & AHC_WIDE) != 0) + transinfo->user.width = MSG_EXT_WDTR_BUS_16_BIT; + + /* + * These will be truncated when we determine the + * connection type we have with the target. + */ + transinfo->user.period = ahc_syncrates->period; + transinfo->user.offset = ~0; + } else { + u_int scsirate; + u_int16_t mask; + + /* Take the settings leftover in scratch RAM. */ + scsirate = ahc_inb(ahc, TARG_SCSIRATE + i); + mask = (0x01 << i); + if ((ahc->features & AHC_ULTRA2) != 0) { + u_int offset; + + if ((scsirate & SOFS) == 0x0F) { + /* + * Haven't negotiated yet, + * so the format is different. + */ + scsirate = (scsirate & SXFR) >> 4 + | (ahc->ultraenb & mask) + ? 0x18 : 0x10 + | (scsirate & WIDEXFER); + offset = MAX_OFFSET_ULTRA2; + } else + offset = ahc_inb(ahc, TARG_OFFSET + i); + ahc_find_period(ahc, scsirate, + AHC_SYNCRATE_ULTRA2); + if (offset == 0) + transinfo->user.period = 0; + else + transinfo->user.offset = ~0; + } else if ((scsirate & SOFS) != 0) { + transinfo->user.period = + ahc_find_period(ahc, scsirate, + (ahc->ultraenb & mask) + ? AHC_SYNCRATE_ULTRA + : AHC_SYNCRATE_FAST); + if ((scsirate & SOFS) != 0 + && transinfo->user.period != 0) { + transinfo->user.offset = ~0; + } + } + if ((scsirate & WIDEXFER) != 0 + && (ahc->features & AHC_WIDE) != 0) { + transinfo->user.width = MSG_EXT_WDTR_BUS_16_BIT; + } + + } + } + ahc->sdtrpending = 0; + ahc->wdtrpending = 0; + +#ifdef AHC_DEBUG + if (ahc_debug & AHC_SHOWMISC) + printf("NEEDSDTR == 0x%x\nNEEDWDTR == 0x%x\n" + "DISCENABLE == 0x%x\nULTRAENB == 0x%x\n", + ahc->needsdtr_orig, ahc->needwdtr_orig, + ahc->discenable, ahc->ultraenb); +#endif + /* + * Allocate enough "hardware scbs" to handle + * the maximum number of concurrent transactions + * we can have active. We have to use contigmalloc + * if this array crosses a page boundary since the + * sequencer depends on this array being physically + * contiguous. + */ + if (ahc->scb_data->hscbs == NULL) { + size_t array_size; + + array_size = ahc->scb_data->maxscbs*sizeof(struct hardware_scb); + if (array_size > PAGE_SIZE) { + ahc->scb_data->hscbs = (struct hardware_scb *) + contigmalloc(array_size, M_DEVBUF, + M_NOWAIT, 0ul, 0xffffffff, + PAGE_SIZE, 0x10000); + } else { + ahc->scb_data->hscbs = (struct hardware_scb *) + malloc(array_size, M_DEVBUF, M_NOWAIT); + } + + if (ahc->scb_data->hscbs == NULL) { + printf("%s: unable to allocate hardware SCB array. " + "Failing attach\n", ahc_name(ahc)); + return (-1); + } + /* At least the control byte of each hscb needs to be zeroed */ + bzero(ahc->scb_data->hscbs, array_size); + } + + if ((ahc->flags & AHC_TARGETMODE) != 0) { + size_t array_size; + + ahc->num_targetcmds = 32; + array_size = ahc->num_targetcmds * sizeof(struct target_cmd); + ahc->targetcmds = malloc(array_size, M_DEVBUF, M_NOWAIT); + + if (ahc->targetcmds == NULL) { + printf("%s: unable to allocate targetcmd array. " + "Failing attach\n", ahc_name(ahc)); + return (-1); + } + + bzero(ahc->targetcmds, array_size); + ahc_outb(ahc, TMODE_CMDADDR_NEXT, 0); + } + + /* + * Tell the sequencer where it can find the our arrays in memory. + */ + { + u_int32_t physaddr; + + /* Tell the sequencer where it can find the hscb array. */ + physaddr = vtophys(ahc->scb_data->hscbs); + ahc_outb(ahc, HSCB_ADDR, physaddr & 0xFF); + ahc_outb(ahc, HSCB_ADDR + 1, (physaddr >> 8) & 0xFF); + ahc_outb(ahc, HSCB_ADDR + 2, (physaddr >> 16) & 0xFF); + ahc_outb(ahc, HSCB_ADDR + 3, (physaddr >> 24) & 0xFF); + ahc->hscb_busaddr = physaddr; + + physaddr = vtophys(ahc->qoutfifo); + ahc_outb(ahc, SCBID_ADDR, physaddr & 0xFF); + ahc_outb(ahc, SCBID_ADDR + 1, (physaddr >> 8) & 0xFF); + ahc_outb(ahc, SCBID_ADDR + 2, (physaddr >> 16) & 0xFF); + ahc_outb(ahc, SCBID_ADDR + 3, (physaddr >> 24) & 0xFF); + + if ((ahc->flags & AHC_TARGETMODE) != 0) { + physaddr = vtophys(ahc->targetcmds); + ahc_outb(ahc, TMODE_CMDADDR, physaddr & 0xFF); + ahc_outb(ahc, TMODE_CMDADDR + 1, + (physaddr >> 8) & 0xFF); + ahc_outb(ahc, TMODE_CMDADDR + 2, + (physaddr >> 16) & 0xFF); + ahc_outb(ahc, TMODE_CMDADDR + 3, + (physaddr >> 24) & 0xFF); + + ahc_outb(ahc, CMDSIZE_TABLE, 5); + ahc_outb(ahc, CMDSIZE_TABLE + 1, 9); + ahc_outb(ahc, CMDSIZE_TABLE + 2, 9); + ahc_outb(ahc, CMDSIZE_TABLE + 3, 0); + ahc_outb(ahc, CMDSIZE_TABLE + 4, 15); + ahc_outb(ahc, CMDSIZE_TABLE + 5, 11); + ahc_outb(ahc, CMDSIZE_TABLE + 6, 0); + ahc_outb(ahc, CMDSIZE_TABLE + 7, 0); + } + + /* There are no untagged SCBs active yet. */ + for (i = 0; i < sizeof(ahc->untagged_scbs); i++) { + ahc->untagged_scbs[i] = SCB_LIST_NULL; + } + for (i = 0; i < sizeof(ahc->qoutfifo); i++) { + ahc->qoutfifo[i] = SCB_LIST_NULL; + } + } + + /* Our Q FIFOs are empty. */ + ahc_outb(ahc, KERNEL_QINPOS, 0); + ahc_outb(ahc, QINPOS, 0); + ahc_outb(ahc, QOUTPOS, 0); + + /* + * Use the built in queue management registers + * if they are available. + */ + if ((ahc->features & AHC_QUEUE_REGS) != 0) { + ahc_outb(ahc, QOFF_CTLSTA, SCB_QSIZE_256); + ahc_outb(ahc, SDSCB_QOFF, 0); + ahc_outb(ahc, SNSCB_QOFF, 0); + ahc_outb(ahc, HNSCB_QOFF, 0); + } + + + /* We don't have any waiting selections */ + ahc_outb(ahc, WAITING_SCBH, SCB_LIST_NULL); + + /* Our disconnection list is empty too */ + ahc_outb(ahc, DISCONNECTED_SCBH, SCB_LIST_NULL); + + /* Message out buffer starts empty */ + ahc_outb(ahc, MSG_OUT, MSG_NOOP); + + /* + * Load the Sequencer program and Enable the adapter + * in "fast" mode. + */ + if (bootverbose) + printf("%s: Downloading Sequencer Program...", + ahc_name(ahc)); + + ahc_loadseq(ahc); + + /* We have to wait until after any system dumps... */ + at_shutdown(ahc_shutdown, ahc, SHUTDOWN_FINAL); + + return (0); +} + +static void +ahcminphys(struct buf *bp) +{ +/* + * Even though the card can transfer up to 16megs per command + * we are limited by the number of segments in the dma segment + * list that we can hold. The worst case is that all pages are + * discontinuous physically, hense the "page per segment" limit + * enforced here. + */ + if (bp->b_bcount > ((AHC_NSEG - 1) * PAGE_SIZE)) { + bp->b_bcount = ((AHC_NSEG - 1) * PAGE_SIZE); + } +} + +static cam_status +ahc_find_tmode_devs(struct ahc_softc *ahc, struct cam_sim *sim, union ccb *ccb, + struct tmode_tstate **tstate, struct tmode_lstate **lstate, + int notfound_failure) +{ + int our_id; + + /* + * If we are not configured for target mode, someone + * is really confused to be sending this to us. + */ + if ((ahc->flags & AHC_TARGETMODE) == 0) + return (CAM_REQ_INVALID); + + /* Range check target and lun */ + if (cam_sim_bus(sim) == 0) + our_id = ahc->our_id; + else + our_id = ahc->our_id_b; + if (ccb->ccb_h.target_id > ((ahc->features & AHC_WIDE) ? 15 : 7) + || ((ahc->features & AHC_MULTI_TID) == 0 + && (ccb->ccb_h.target_id != our_id))) + return (CAM_TID_INVALID); + + if (ccb->ccb_h.target_lun > 8) + return (CAM_LUN_INVALID); + + *tstate = ahc->enabled_targets[ccb->ccb_h.target_id]; + *lstate = NULL; + if (*tstate != NULL) + *lstate = (*tstate)->enabled_luns[ccb->ccb_h.target_lun]; + + if (notfound_failure != 0 && *lstate == NULL) + return (CAM_PATH_INVALID); + + return (CAM_REQ_CMP); +} + +static void +ahc_action(struct cam_sim *sim, union ccb *ccb) +{ + struct ahc_softc *ahc; + struct tmode_lstate *lstate; + int target_id; + int s; + + CAM_DEBUG(ccb->ccb_h.path, CAM_DEBUG_TRACE, ("ahc_action\n")); + + ahc = (struct ahc_softc *)cam_sim_softc(sim); + + target_id = ccb->ccb_h.target_id; + + switch (ccb->ccb_h.func_code) { + /* Common cases first */ + case XPT_ACCEPT_TARGET_IO: /* Accept Host Target Mode CDB */ + case XPT_CONT_TARGET_IO:/* Continue Host Target I/O Connection*/ + { + struct tmode_tstate *tstate; + cam_status status; + + status = ahc_find_tmode_devs(ahc, sim, ccb, &tstate, + &lstate, TRUE); + + if (status != CAM_REQ_CMP) { + ccb->ccb_h.status = status; + xpt_done(ccb); + break; + } + if (ccb->ccb_h.func_code == XPT_ACCEPT_TARGET_IO) { + SLIST_INSERT_HEAD(&lstate->accept_tios, &ccb->ccb_h, + sim_links.sle); + ccb->ccb_h.status = CAM_REQ_INPROG; + break; + } + + /* + * The target_id represents the target we attempt to + * select. In target mode, this is the initiator of + * the original command. + */ + target_id = ccb->csio.init_id; + xpt_print_path(ccb->ccb_h.path); + printf("Sending a continue TIO\n"); + /* FALLTHROUGH */ + } + case XPT_SCSI_IO: /* Execute the requested I/O operation */ + case XPT_RESET_DEV: /* Bus Device Reset the specified SCSI device */ + { + struct scb *scb; + struct hardware_scb *hscb; + struct ahc_target_tinfo *tinfo; + u_int16_t mask; + + /* + * get an scb to use. + */ + if ((scb = ahc_get_scb(ahc)) == NULL) { + int s; + + s = splcam(); + ahc->flags |= AHC_RESOURCE_SHORTAGE; + splx(s); + xpt_freeze_simq(ahc->sim, /*count*/1); + ahc_set_ccb_status(ccb, CAM_REQUEUE_REQ); + xpt_done(ccb); + return; + } + + hscb = scb->hscb; + + CAM_DEBUG(ccb->ccb_h.path, CAM_DEBUG_SUBTRACE, + ("start scb(%p)\n", scb)); + scb->ccb = ccb; + /* + * So we can find the SCB when an abort is requested + */ + ccb->ccb_h.ccb_scb_ptr = scb; + ccb->ccb_h.ccb_ahc_ptr = ahc; + + /* + * Put all the arguments for the xfer in the scb + */ + hscb->tcl = ((target_id << 4) & 0xF0) + | (SIM_IS_SCSIBUS_B(ahc, sim) ? SELBUSB : 0) + | (ccb->ccb_h.target_lun & 0x07); + + mask = SCB_TARGET_MASK(scb); + tinfo = &ahc->transinfo[SCB_TARGET_OFFSET(scb)]; + + hscb->scsirate = tinfo->scsirate; + hscb->scsioffset = tinfo->current.offset; + if ((ahc->ultraenb & mask) != 0) + hscb->control |= ULTRAENB; + + if ((ahc->discenable & mask) != 0 + && (ccb->ccb_h.flags & CAM_DIS_DISCONNECT) == 0) + hscb->control |= DISCENB; + + if (ccb->ccb_h.func_code == XPT_RESET_DEV) { + hscb->cmdpointer = NULL; + scb->flags |= SCB_DEVICE_RESET; + hscb->control |= MK_MESSAGE; + ahc_execute_scb(scb, NULL, 0, 0); + } else { + if (ccb->ccb_h.func_code == XPT_SCSI_IO) { + if (tinfo->current.width != tinfo->goal.width) { + if ((ahc->wdtrpending & mask) == 0) { + ahc->wdtrpending |= mask; + hscb->control |= MK_MESSAGE; + scb->flags |= SCB_MSGOUT_WDTR; + } + } else if ((tinfo->current.period + != tinfo->goal.period) + && (ahc->sdtrpending & mask) == 0) { + ahc->sdtrpending |= mask; + hscb->control |= MK_MESSAGE; + scb->flags |= SCB_MSGOUT_SDTR; + } + } else { + if (ahc->pending_device == lstate) { + scb->flags |= SCB_TARGET_IMMEDIATE; + ahc->pending_device = NULL; + } + hscb->control |= TARGET_SCB; + hscb->cmdpointer = IDENTIFY_SEEN; + if ((ccb->ccb_h.flags & CAM_SEND_STATUS) != 0) { + hscb->cmdpointer |= SPHASE_PENDING; + hscb->status = ccb->csio.scsi_status; + } + + /* Overloaded with tag ID */ + hscb->cmdlen = ccb->csio.tag_id; + /* + * Overloaded with our target ID to + * use for reselection. + */ + hscb->next = ccb->ccb_h.target_id; + } + if (ccb->ccb_h.flags & CAM_TAG_ACTION_VALID) + hscb->control |= ccb->csio.tag_action; + + ahc_setup_data(ahc, &ccb->csio, scb); + } + break; + } + case XPT_NOTIFY_ACK: + case XPT_IMMED_NOTIFY: + { + struct tmode_tstate *tstate; + struct tmode_lstate *lstate; + cam_status status; + + status = ahc_find_tmode_devs(ahc, sim, ccb, &tstate, + &lstate, TRUE); + + if (status != CAM_REQ_CMP) { + ccb->ccb_h.status = status; + xpt_done(ccb); + break; + } + if (ccb->ccb_h.func_code == XPT_NOTIFY_ACK) { + /* Clear notification state */ + } + SLIST_INSERT_HEAD(&lstate->immed_notifies, &ccb->ccb_h, + sim_links.sle); + ccb->ccb_h.status = CAM_REQ_INPROG; + break; + } + case XPT_EN_LUN: /* Enable LUN as a target */ + { + struct tmode_tstate *tstate; + struct tmode_lstate *lstate; + struct ccb_en_lun *cel; + cam_status status; + int target; + int lun; + + status = ahc_find_tmode_devs(ahc, sim, ccb, &tstate, &lstate, + /* notfound_failure*/FALSE); + + if (status != CAM_REQ_CMP) { + ccb->ccb_h.status = status; + xpt_done(ccb); + break; + } + + cel = &ccb->cel; + target = ccb->ccb_h.target_id; + lun = ccb->ccb_h.target_lun; + if (cel->enable != 0) { + /* Are we already enabled?? */ + if (lstate != NULL) { + ccb->ccb_h.status = CAM_LUN_ALRDY_ENA; + xpt_done(ccb); + break; + } + + if (cel->grp6_len != 0 + || cel->grp7_len != 0) { + /* + * Don't (yet?) support vendor + * specific commands. + */ + ccb->ccb_h.status = CAM_REQ_INVALID; + xpt_done(ccb); + break; + } + + /* + * Seems to be okay. + * Setup our data structures. + */ + if (tstate == NULL) { + tstate = malloc(sizeof(*tstate), + M_DEVBUF, M_NOWAIT); + if (tstate == NULL) { + ccb->ccb_h.status = CAM_RESRC_UNAVAIL; + xpt_done(ccb); + break; + } + bzero(tstate, sizeof(*tstate)); + ahc->enabled_targets[target] = tstate; + } + lstate = malloc(sizeof(*lstate), M_DEVBUF, M_NOWAIT); + if (lstate == NULL) { + ccb->ccb_h.status = CAM_RESRC_UNAVAIL; + xpt_done(ccb); + break; + } + bzero(lstate, sizeof(*lstate)); + SLIST_INIT(&lstate->accept_tios); + SLIST_INIT(&lstate->immed_notifies); + tstate->enabled_luns[lun] = lstate; + if ((ahc->features & AHC_MULTI_TID) != 0) { + u_int16_t targid_mask; + + pause_sequencer(ahc); + targid_mask = ahc_inb(ahc, TARGID) + | (ahc_inb(ahc, TARGID + 1) << 8); + + targid_mask |= (0x01 << target); + ahc_outb(ahc, TARGID, targid_mask); + ahc_outb(ahc, TARGID+1, (targid_mask >> 8)); + unpause_sequencer(ahc, /*always?*/FALSE); + } + ccb->ccb_h.status = CAM_REQ_CMP; + xpt_print_path(ccb->ccb_h.path); + printf("Lun now enabled for target mode\n"); + xpt_done(ccb); + break; + } else { + /* XXX Fully Implement Disable */ + if (lstate == NULL) { + ccb->ccb_h.status = CAM_LUN_INVALID; + xpt_done(ccb); + break; + } + ccb->ccb_h.status = CAM_REQ_CMP; + xpt_done(ccb); + break; + } + break; + } + case XPT_ABORT: /* Abort the specified CCB */ + /* XXX Implement */ + ccb->ccb_h.status = CAM_REQ_INVALID; + xpt_done(ccb); + break; + case XPT_SET_TRAN_SETTINGS: + { + struct ahc_devinfo devinfo; + struct ccb_trans_settings *cts; + struct ahc_target_tinfo *tinfo; + u_int update_type; + int s; + + cts = &ccb->cts; + ahc_compile_devinfo(&devinfo, cts->ccb_h.target_id, + SIM_IS_SCSIBUS_B(ahc, sim) ? 'B' : 'A'); + tinfo = &ahc->transinfo[devinfo.target_offset]; + update_type = 0; + if ((cts->flags & CCB_TRANS_CURRENT_SETTINGS) != 0) + update_type |= AHC_TRANS_GOAL; + if ((cts->flags & CCB_TRANS_USER_SETTINGS) != 0) + update_type |= AHC_TRANS_USER; + + s = splcam(); + + if ((cts->valid & CCB_TRANS_DISC_VALID) != 0) { + if ((cts->flags & CCB_TRANS_DISC_ENB) != 0) + ahc->discenable |= devinfo.target_mask; + else + ahc->discenable &= ~devinfo.target_mask; + } + + if ((cts->valid & CCB_TRANS_TQ_VALID) != 0) { + if ((cts->flags & CCB_TRANS_TAG_ENB) != 0) + ahc->tagenable |= devinfo.target_mask; + else + ahc->tagenable &= ~devinfo.target_mask; + } + + if ((cts->valid & CCB_TRANS_BUS_WIDTH_VALID) != 0) { + switch (cts->bus_width) { + case MSG_EXT_WDTR_BUS_16_BIT: + if ((ahc->features & AHC_WIDE) != 0) + break; + /* FALLTHROUGH to 8bit */ + case MSG_EXT_WDTR_BUS_32_BIT: + case MSG_EXT_WDTR_BUS_8_BIT: + default: + cts->bus_width = MSG_EXT_WDTR_BUS_8_BIT; + break; + } + if ((update_type & AHC_TRANS_GOAL) != 0) + tinfo->goal.width = cts->bus_width; + if ((update_type & AHC_TRANS_USER) != 0) + tinfo->user.width = cts->bus_width; + } + + if ((cts->valid & CCB_TRANS_SYNC_RATE_VALID) != 0) { + struct ahc_syncrate *syncrate; + u_int maxsync; + + if ((ahc->features & AHC_ULTRA2) != 0) + maxsync = AHC_SYNCRATE_ULTRA2; + else if ((ahc->features & AHC_ULTRA) != 0) + maxsync = AHC_SYNCRATE_ULTRA; + else + maxsync = AHC_SYNCRATE_FAST; + + if ((cts->valid & CCB_TRANS_SYNC_OFFSET_VALID) != 0) { + if (cts->sync_offset != 0) + cts->sync_offset = ~0; + } else { + cts->sync_offset = 0; + } + + syncrate = ahc_find_syncrate(ahc, &cts->sync_period, + maxsync); + ahc_validate_offset(ahc, syncrate, &cts->sync_offset, + tinfo->goal.width); + + /* We use a period of 0 to represent async */ + if (cts->sync_offset == 0) + cts->sync_period = 0; + + if ((update_type & AHC_TRANS_GOAL) != 0) { + tinfo->goal.period = cts->sync_period; + tinfo->goal.offset = cts->sync_offset; + } + if ((update_type & AHC_TRANS_USER) != 0) { + tinfo->user.period = cts->sync_period; + tinfo->user.offset = cts->sync_offset; + } + } + splx(s); + ccb->ccb_h.status = CAM_REQ_CMP; + xpt_done(ccb); + break; + } + case XPT_GET_TRAN_SETTINGS: + /* Get default/user set transfer settings for the target */ + { + struct ahc_devinfo devinfo; + struct ccb_trans_settings *cts; + struct ahc_target_tinfo *targ_info; + struct ahc_transinfo *tinfo; + int s; + + cts = &ccb->cts; + ahc_compile_devinfo(&devinfo, cts->ccb_h.target_id, + SIM_IS_SCSIBUS_B(ahc, sim) ? 'B' : 'A'); + targ_info = &ahc->transinfo[devinfo.target_offset]; + + if ((cts->flags & CCB_TRANS_CURRENT_SETTINGS) != 0) + tinfo = &targ_info->current; + else + tinfo = &targ_info->user; + + s = splcam(); + + cts->flags &= ~(CCB_TRANS_DISC_ENB|CCB_TRANS_TAG_ENB); + if ((ahc->discenable & devinfo.target_mask) != 0) + cts->flags |= CCB_TRANS_DISC_ENB; + + if ((ahc->tagenable & devinfo.target_mask) != 0) + cts->flags |= CCB_TRANS_TAG_ENB; + + cts->sync_period = tinfo->period; + cts->sync_offset = tinfo->offset; + cts->bus_width = tinfo->width; + + splx(s); + + cts->valid = CCB_TRANS_SYNC_RATE_VALID + | CCB_TRANS_SYNC_OFFSET_VALID + | CCB_TRANS_BUS_WIDTH_VALID + | CCB_TRANS_DISC_VALID + | CCB_TRANS_TQ_VALID; + + ccb->ccb_h.status = CAM_REQ_CMP; + xpt_done(ccb); + break; + } + case XPT_CALC_GEOMETRY: + { + struct ccb_calc_geometry *ccg; + u_int32_t size_mb; + u_int32_t secs_per_cylinder; + int extended; + + ccg = &ccb->ccg; + size_mb = ccg->volume_size + / ((1024L * 1024L) / ccg->block_size); + extended = SIM_IS_SCSIBUS_B(ahc, sim) + ? ahc->flags & AHC_EXTENDED_TRANS_B + : ahc->flags & AHC_EXTENDED_TRANS_A; + + if (size_mb > 1024 && extended) { + ccg->heads = 255; + ccg->secs_per_track = 63; + } else { + ccg->heads = 64; + ccg->secs_per_track = 32; + } + secs_per_cylinder = ccg->heads * ccg->secs_per_track; + ccg->cylinders = ccg->volume_size / secs_per_cylinder; + ccb->ccb_h.status = CAM_REQ_CMP; + xpt_done(ccb); + break; + } + case XPT_RESET_BUS: /* Reset the specified SCSI bus */ + { + struct cam_path *path; + char channel; + int found; + + s = splcam(); + if (SIM_IS_SCSIBUS_B(ahc, sim)) { + channel = 'B'; + path = ahc->path_b; + } else { + channel = 'A'; + path = ahc->path; + } + found = ahc_reset_channel(ahc, channel, /*initiate reset*/TRUE); + splx(s); + if (bootverbose) { + xpt_print_path(path); + printf("SCSI bus reset delivered. " + "%d SCBs aborted.\n", found); + } + ccb->ccb_h.status = CAM_REQ_CMP; + xpt_done(ccb); + break; + } + case XPT_TERM_IO: /* Terminate the I/O process */ + /* XXX Implement */ + ccb->ccb_h.status = CAM_REQ_INVALID; + xpt_done(ccb); + break; + case XPT_PATH_INQ: /* Path routing inquiry */ + { + struct ccb_pathinq *cpi = &ccb->cpi; + + cpi->version_num = 1; /* XXX??? */ + cpi->hba_inquiry = PI_SDTR_ABLE|PI_TAG_ABLE; + if ((ahc->features & AHC_WIDE) != 0) + cpi->hba_inquiry |= PI_WIDE_16; + if ((ahc->flags & AHC_TARGETMODE) != 0) { + cpi->target_sprt = PIT_PROCESSOR + | PIT_DISCONNECT + | PIT_TERM_IO; + } else { + cpi->target_sprt = 0; + } + cpi->hba_misc = 0; + cpi->hba_eng_cnt = 0; + cpi->max_target = (ahc->features & AHC_WIDE) ? 15 : 7; + cpi->max_lun = 7; + if (SIM_IS_SCSIBUS_B(ahc, sim)) + cpi->initiator_id = ahc->our_id_b; + else + cpi->initiator_id = ahc->our_id; + cpi->bus_id = cam_sim_bus(sim); + strncpy(cpi->sim_vid, "FreeBSD", SIM_IDLEN); + strncpy(cpi->hba_vid, "Adaptec", HBA_IDLEN); + strncpy(cpi->dev_name, cam_sim_name(sim), DEV_IDLEN); + cpi->unit_number = cam_sim_unit(sim); + cpi->ccb_h.status = CAM_REQ_CMP; + xpt_done(ccb); + break; + } + default: + ccb->ccb_h.status = CAM_REQ_INVALID; + xpt_done(ccb); + break; + } +} + +static void +ahc_async(void *callback_arg, u_int32_t code, struct cam_path *path, void *arg) +{ + struct ahc_softc *ahc; + struct cam_sim *sim; + + sim = (struct cam_sim *)callback_arg; + ahc = (struct ahc_softc *)cam_sim_softc(sim); + switch (code) { + case AC_LOST_DEVICE: + { + struct ahc_devinfo devinfo; + + ahc_compile_devinfo(&devinfo, xpt_path_target_id(path), + SIM_IS_SCSIBUS_B(ahc, sim) ? 'B' : 'A'); + + /* + * Revert to async/narrow transfers + * for the next device. + */ + pause_sequencer(ahc); + ahc_set_width(ahc, &devinfo, path, MSG_EXT_WDTR_BUS_8_BIT, + AHC_TRANS_GOAL|AHC_TRANS_CUR); + ahc_set_syncrate(ahc, &devinfo, path, /*syncrate*/NULL, + /*period*/0, /*offset*/0, + AHC_TRANS_GOAL|AHC_TRANS_CUR); + unpause_sequencer(ahc, /*unpause always*/FALSE); + break; + } + default: + break; + } +} + +static void +ahc_execute_scb(void *arg, bus_dma_segment_t *dm_segs, int nsegments, + int error) +{ + struct scb *scb; + union ccb *ccb; + struct ahc_softc *ahc; + int s; + + scb = (struct scb *)arg; + ccb = scb->ccb; + ahc = (struct ahc_softc *)ccb->ccb_h.ccb_ahc_ptr; + + if (nsegments != 0) { + struct ahc_dma_seg *sg; + bus_dma_segment_t *end_seg; + bus_dmasync_op_t op; + + end_seg = dm_segs + nsegments; + + /* Copy the first SG into the data pointer area */ + scb->hscb->SG_pointer = scb->ahc_dmaphys; + scb->hscb->data = dm_segs->ds_addr; + scb->hscb->datalen = dm_segs->ds_len; + dm_segs++; + + /* Copy the remaining segments into our SG list */ + sg = scb->ahc_dma; + while (dm_segs < end_seg) { + sg->addr = dm_segs->ds_addr; + sg->len = dm_segs->ds_len; + sg++; + dm_segs++; + } + + if ((ccb->ccb_h.flags & CAM_DIR_MASK) == CAM_DIR_IN) + op = BUS_DMASYNC_PREREAD; + else + op = BUS_DMASYNC_PREWRITE; + + bus_dmamap_sync(ahc->dmat, scb->dmamap, op); + } else { + scb->hscb->SG_pointer = 0; + scb->hscb->data = 0; + scb->hscb->datalen = 0; + } + + scb->sg_count = scb->hscb->SG_count = nsegments; + + s = splcam(); + + /* + * Last time we need to check if this SCB needs to + * be aborted. + */ + if (ahc_ccb_status(ccb) != CAM_REQ_INPROG) { + if (nsegments != 0) + bus_dmamap_unload(ahc->dmat, scb->dmamap); + ahc_free_scb(ahc, scb); + xpt_done(ccb); + splx(s); + return; + } + + /* Busy this tcl if we are untagged */ + if ((scb->hscb->control & TAG_ENB) == 0) + ahc_busy_tcl(ahc, scb); + + LIST_INSERT_HEAD(&ahc->pending_ccbs, &ccb->ccb_h, + sim_links.le); + + scb->flags |= SCB_ACTIVE; + ccb->ccb_h.status |= CAM_SIM_QUEUED; + + ccb->ccb_h.timeout_ch = + timeout(ahc_timeout, (caddr_t)scb, + (ccb->ccb_h.timeout * hz) / 1000); + + if ((scb->flags & SCB_TARGET_IMMEDIATE) != 0) { + xpt_print_path(ccb->ccb_h.path); + printf("Returning an immediate CTIO\n"); + if ((ahc->flags & AHC_PAGESCBS) == 0) + ahc_outb(ahc, SCBPTR, scb->hscb->tag); + ahc_outb(ahc, SCB_TAG, scb->hscb->tag); + unpause_sequencer(ahc, /*unpause_always*/TRUE); + } else { + + ahc->qinfifo[ahc->qinfifonext++] = scb->hscb->tag; + + if ((ahc->features & AHC_QUEUE_REGS) != 0) { + ahc_outb(ahc, HNSCB_QOFF, ahc->qinfifonext); + } else { + pause_sequencer(ahc); + ahc_outb(ahc, KERNEL_QINPOS, ahc->qinfifonext); + unpause_sequencer(ahc, /*unpause_always*/FALSE); + } + } + + splx(s); +} + +static void +ahc_poll(struct cam_sim *sim) +{ + ahc_intr(cam_sim_softc(sim)); +} + +static void +ahc_setup_data(struct ahc_softc *ahc, struct ccb_scsiio *csio, + struct scb *scb) +{ + struct hardware_scb *hscb; + struct ccb_hdr *ccb_h; + + hscb = scb->hscb; + ccb_h = &csio->ccb_h; + + if (ccb_h->func_code == XPT_SCSI_IO) { + hscb->cmdlen = csio->cdb_len; + if ((ccb_h->flags & CAM_CDB_POINTER) != 0) { + if ((ccb_h->flags & CAM_CDB_PHYS) == 0) + if (hscb->cmdlen <= 16) { + memcpy(hscb->cmdstore, + csio->cdb_io.cdb_ptr, + hscb->cmdlen); + hscb->cmdpointer = + hscb->cmdstore_busaddr; + } else + hscb->cmdpointer = + vtophys(csio->cdb_io.cdb_ptr); + else + hscb->cmdpointer = + (u_int32_t)csio->cdb_io.cdb_ptr; + } else { + /* + * CCB CDB Data Storage area is only 16 bytes + * so no additional testing is required + */ + memcpy(hscb->cmdstore, csio->cdb_io.cdb_bytes, + hscb->cmdlen); + hscb->cmdpointer = hscb->cmdstore_busaddr; + } + } + + /* Only use S/G if there is a transfer */ + if ((ccb_h->flags & CAM_DIR_MASK) != CAM_DIR_NONE) { + if ((ccb_h->flags & CAM_SCATTER_VALID) == 0) { + /* We've been given a pointer to a single buffer */ + if ((ccb_h->flags & CAM_DATA_PHYS) == 0) { + int s; + int error; + + s = splsoftvm(); + error = bus_dmamap_load(ahc->dmat, + scb->dmamap, + csio->data_ptr, + csio->dxfer_len, + ahc_execute_scb, + scb, /*flags*/0); + if (error == EINPROGRESS) { + /* + * So as to maintain ordering, + * freeze the controller queue + * until our mapping is + * returned. + */ + xpt_freeze_simq(ahc->sim, + /*count*/1); + scb->ccb->ccb_h.status |= + CAM_RELEASE_SIMQ; + } + splx(s); + } else { + struct bus_dma_segment seg; + + /* Pointer to physical buffer */ + if (csio->dxfer_len > AHC_MAXTRANSFER_SIZE) + panic("ahc_setup_data - Transfer size " + "larger than can device max"); + + seg.ds_addr = (bus_addr_t)csio->data_ptr; + seg.ds_len = csio->dxfer_len; + ahc_execute_scb(scb, &seg, 1, 0); + } + } else { + struct bus_dma_segment *segs; + + if ((ccb_h->flags & CAM_DATA_PHYS) != 0) + panic("ahc_setup_data - Physical segment " + "pointers unsupported"); + + if ((ccb_h->flags & CAM_SG_LIST_PHYS) == 0) + panic("ahc_setup_data - Virtual segment " + "addresses unsupported"); + + /* Just use the segments provided */ + segs = (struct bus_dma_segment *)csio->data_ptr; + ahc_execute_scb(scb, segs, csio->sglist_cnt, 0); + } + if (ccb_h->func_code == XPT_CONT_TARGET_IO) { + hscb->cmdpointer |= DPHASE_PENDING; + if ((ccb_h->flags & CAM_DIR_MASK) == CAM_DIR_IN) + hscb->cmdpointer |= (TARGET_DATA_IN << 8); + } + } else { + ahc_execute_scb(scb, NULL, 0, 0); + } +} + +static void +ahc_freeze_devq(struct ahc_softc *ahc, struct cam_path *path) +{ + int target; + char channel; + int lun; + + target = xpt_path_target_id(path); + lun = xpt_path_lun_id(path); + channel = xpt_path_sim(path)->bus_id == 0 ? 'A' : 'B'; + + ahc_search_qinfifo(ahc, target, channel, lun, + /*tag*/SCB_LIST_NULL, CAM_REQUEUE_REQ, + SEARCH_COMPLETE); +} + +/* + * An scb (and hence an scb entry on the board) is put onto the + * free list. + */ +static void +ahc_free_scb(struct ahc_softc *ahc, struct scb *scb) +{ + struct hardware_scb *hscb; + int opri; + + hscb = scb->hscb; + + opri = splcam(); + + if ((ahc->flags & AHC_RESOURCE_SHORTAGE) != 0 + && (scb->ccb->ccb_h.status & CAM_RELEASE_SIMQ) == 0) { + scb->ccb->ccb_h.status |= CAM_RELEASE_SIMQ; + ahc->flags &= ~AHC_RESOURCE_SHORTAGE; + } + + /* Clean up for the next user */ + scb->flags = SCB_FREE; + hscb->control = 0; + hscb->status = 0; + + STAILQ_INSERT_HEAD(&ahc->scb_data->free_scbs, scb, links); + splx(opri); +} + +/* + * Get a free scb, either one already assigned to a hardware slot + * on the adapter or one that will require an SCB to be paged out before + * use. If there are none, see if we can allocate a new SCB. Otherwise + * either return an error or sleep. + */ +static struct scb * +ahc_get_scb(struct ahc_softc *ahc) +{ + struct scb *scbp; + int opri; + + opri = splcam(); + if ((scbp = STAILQ_FIRST(&ahc->scb_data->free_scbs))) { + STAILQ_REMOVE_HEAD(&ahc->scb_data->free_scbs, links); + } else if (ahc->scb_data->numscbs < ahc->scb_data->maxscbs) { + scbp = ahc_alloc_scb(ahc); + if (scbp == NULL) + printf("%s: Can't malloc SCB\n", ahc_name(ahc)); + } + + splx(opri); + + return (scbp); +} + + +static struct scb * +ahc_alloc_scb(struct ahc_softc *ahc) +{ + static struct ahc_dma_seg *next_sg_array = NULL; + static int sg_arrays_free = 0; + struct scb *newscb; + int error; + + newscb = (struct scb *) malloc(sizeof(struct scb), M_DEVBUF, M_NOWAIT); + if (newscb != NULL) { + bzero(newscb, sizeof(struct scb)); + error = bus_dmamap_create(ahc->dmat, /*flags*/0, + &newscb->dmamap); + if (error != 0) + printf("%s: Unable to allocate SCB dmamap - error %d\n", + ahc_name(ahc), error); + + if (error == 0 && next_sg_array == NULL) { + size_t alloc_size = sizeof(struct ahc_dma_seg) + * AHC_NSEG; + sg_arrays_free = PAGE_SIZE / alloc_size; + alloc_size *= sg_arrays_free; + if (alloc_size == 0) + panic("%s: SG list doesn't fit in a page", + ahc_name(ahc)); + next_sg_array = (struct ahc_dma_seg *) + malloc(alloc_size, M_DEVBUF, M_NOWAIT); + } + if (error == 0 && next_sg_array != NULL) { + struct hardware_scb *hscb; + + newscb->ahc_dma = next_sg_array; + newscb->ahc_dmaphys = vtophys(next_sg_array); + sg_arrays_free--; + if (sg_arrays_free == 0) + next_sg_array = NULL; + else + next_sg_array = &next_sg_array[AHC_NSEG]; + hscb = &ahc->scb_data->hscbs[ahc->scb_data->numscbs]; + newscb->hscb = hscb; + hscb->control = 0; + hscb->status = 0; + hscb->tag = ahc->scb_data->numscbs; + hscb->residual_data_count[2] = 0; + hscb->residual_data_count[1] = 0; + hscb->residual_data_count[0] = 0; + hscb->residual_SG_count = 0; + hscb->cmdstore_busaddr = + ahc_hscb_busaddr(ahc, hscb->tag) + + offsetof(struct hardware_scb, cmdstore); + /* + * Place in the scbarray + * Never is removed. + */ + ahc->scb_data->scbarray[hscb->tag] = newscb; + ahc->scb_data->numscbs++; + } else { + free(newscb, M_DEVBUF); + newscb = NULL; + } + } + return newscb; +} + +static void +ahc_loadseq(struct ahc_softc *ahc) +{ + struct patch *cur_patch; + int i; + int downloaded; + int skip_addr; + u_int8_t download_consts[4]; + + /* Setup downloadable constant table */ + download_consts[TMODE_NUMCMDS] = ahc->num_targetcmds; + + cur_patch = patches; + downloaded = 0; + skip_addr = 0; + ahc_outb(ahc, SEQCTL, PERRORDIS|FAILDIS|FASTMODE|LOADRAM); + ahc_outb(ahc, SEQADDR0, 0); + ahc_outb(ahc, SEQADDR1, 0); + + for (i = 0; i < sizeof(seqprog)/4; i++) { + if (ahc_check_patch(ahc, &cur_patch, i, &skip_addr) == 0) { + /* + * Don't download this instruction as it + * is in a patch that was removed. + */ + continue; + } + ahc_download_instr(ahc, i, download_consts); + downloaded++; + } + ahc_outb(ahc, SEQCTL, PERRORDIS|FAILDIS|FASTMODE); + restart_sequencer(ahc); + + if (bootverbose) + printf(" %d instructions downloaded\n", downloaded); +} + +static int +ahc_check_patch(struct ahc_softc *ahc, struct patch **start_patch, + int start_instr, int *skip_addr) +{ + struct patch *cur_patch; + struct patch *last_patch; + int patch_index; + int num_patches; + + num_patches = sizeof(patches)/sizeof(struct patch); + last_patch = &patches[num_patches]; + cur_patch = *start_patch; + + while (cur_patch < last_patch && start_instr == cur_patch->begin) { + + if (cur_patch->patch_func(ahc) == 0) { + int skip; + + /* Start rejecting code */ + *skip_addr = start_instr + cur_patch->skip_instr; + cur_patch += cur_patch->skip_patch; + } else { + /* Accepted this patch. Advance to the next + * one and wait for our intruction pointer to + * hit this point. + */ + cur_patch++; + } + } + + *start_patch = cur_patch; + if (start_instr < *skip_addr) + /* Still skipping */ + return (0); + + return (1); +} + +static void +ahc_download_instr(struct ahc_softc *ahc, int instrptr, u_int8_t *dconsts) +{ + union ins_formats instr; + struct ins_format1 *fmt1_ins; + struct ins_format3 *fmt3_ins; + int fmt3; + u_int8_t opcode; + + /* Structure copy */ + instr = *(union ins_formats*)&seqprog[instrptr * 4]; + + fmt1_ins = &instr.format1; + fmt3_ins = NULL; + + /* Pull the opcode */ + opcode = instr.format1.opcode; + switch (opcode) { + case AIC_OP_JMP: + case AIC_OP_JC: + case AIC_OP_JNC: + case AIC_OP_CALL: + case AIC_OP_JNE: + case AIC_OP_JNZ: + case AIC_OP_JE: + case AIC_OP_JZ: + { + struct patch *cur_patch; + int address_offset; + u_int address; + int skip_addr; + int i; + + fmt3_ins = &instr.format3; + address_offset = 0; + address = fmt3_ins->address; + cur_patch = patches; + skip_addr = 0; + + for (i = 0; i < address;) { + + ahc_check_patch(ahc, &cur_patch, i, &skip_addr); + + if (skip_addr > i) { + int end_addr; + + end_addr = MIN(address, skip_addr); + address_offset += end_addr - i; + i = skip_addr; + } else { + i++; + } + } + address -= address_offset; + fmt3_ins->address = address; + /* FALLTHROUGH */ + } + case AIC_OP_OR: + case AIC_OP_AND: + case AIC_OP_XOR: + case AIC_OP_ADD: + case AIC_OP_ADC: + case AIC_OP_BMOV: + if (fmt1_ins->parity != 0) { + fmt1_ins->immediate = dconsts[fmt1_ins->immediate]; + } + fmt1_ins->parity = 0; + /* FALLTHROUGH */ + case AIC_OP_ROL: + if ((ahc->features & AHC_ULTRA2) != 0) { + int i, count; + + /* Calculate odd parity for the instruction */ + for (i = 0, count = 0; i < 31; i++) { + u_int32_t mask; + + mask = 0x01 << i; + if ((instr.integer & mask) != 0) + count++; + } + if ((count & 0x01) == 0) + instr.format1.parity = 1; + } else { + /* Compress the instruction for older sequencers */ + if (fmt3_ins != NULL) { + instr.integer = + fmt3_ins->immediate + | (fmt3_ins->source << 8) + | (fmt3_ins->address << 16) + | (fmt3_ins->opcode << 25); + } else { + instr.integer = + fmt1_ins->immediate + | (fmt1_ins->source << 8) + | (fmt1_ins->destination << 16) + | (fmt1_ins->ret << 24) + | (fmt1_ins->opcode << 25); + } + } + ahc_outsb(ahc, SEQRAM, instr.bytes, 4); + break; + default: + panic("Unknown opcode encountered in seq program"); + break; + } +} + +static void +ahc_set_recoveryscb(struct ahc_softc *ahc, struct scb *scb) { + + if ((scb->flags & SCB_RECOVERY_SCB) == 0) { + struct ccb_hdr *ccbh; + + scb->flags |= SCB_RECOVERY_SCB; + + /* + * Take all queued, but not sent SCBs out of the equation. + * Also ensure that no new CCBs are queued to us while we + * try to fix this problem. + */ + if ((scb->ccb->ccb_h.status & CAM_RELEASE_SIMQ) == 0) { + xpt_freeze_simq(ahc->sim, /*count*/1); + scb->ccb->ccb_h.status |= CAM_RELEASE_SIMQ; + } + + /* + * Go through all of our pending SCBs and remove + * any scheduled timeouts for them. We will reschedule + * them after we've successfully fixed this problem. + */ + ccbh = ahc->pending_ccbs.lh_first; + while (ccbh != NULL) { + struct scb *pending_scb; + + pending_scb = (struct scb *)ccbh->ccb_scb_ptr; + untimeout(ahc_timeout, pending_scb, ccbh->timeout_ch); + ccbh = ccbh->sim_links.le.le_next; + } + } +} + +static void +ahc_timeout(void *arg) +{ + struct scb *scb; + struct ahc_softc *ahc; + int s, found; + u_int8_t bus_state; + int target; + int lun; + char channel; + + scb = (struct scb *)arg; + ahc = (struct ahc_softc *)scb->ccb->ccb_h.ccb_ahc_ptr; + + s = splcam(); + + /* + * Ensure that the card doesn't do anything + * behind our back. Also make sure that we + * didn't "just" miss an interrupt that would + * affect this timeout. + */ + do { + ahc_intr(ahc); + pause_sequencer(ahc); + } while (ahc_inb(ahc, INTSTAT) & INT_PEND); + + if ((scb->flags & SCB_ACTIVE) == 0) { + /* Previous timeout took care of me already */ + printf("Timedout SCB handled by another timeout\n"); + unpause_sequencer(ahc, /*unpause_always*/TRUE); + splx(s); + return; + } + + target = SCB_TARGET(scb); + channel = SCB_CHANNEL(scb); + lun = SCB_LUN(scb); + + xpt_print_path(scb->ccb->ccb_h.path); + printf("SCB 0x%x - timed out ", scb->hscb->tag); + /* + * Take a snapshot of the bus state and print out + * some information so we can track down driver bugs. + */ + bus_state = ahc_inb(ahc, LASTPHASE); + + switch(bus_state) + { + case P_DATAOUT: + printf("in dataout phase"); + break; + case P_DATAIN: + printf("in datain phase"); + break; + case P_COMMAND: + printf("in command phase"); + break; + case P_MESGOUT: + printf("in message out phase"); + break; + case P_STATUS: + printf("in status phase"); + break; + case P_MESGIN: + printf("in message in phase"); + break; + case P_BUSFREE: + printf("while idle, LASTPHASE == 0x%x", + bus_state); + break; + default: + /* + * We aren't in a valid phase, so assume we're + * idle. + */ + printf("invalid phase, LASTPHASE == 0x%x", + bus_state); + bus_state = P_BUSFREE; + break; + } + + printf(", SCSISIGI == 0x%x\n", ahc_inb(ahc, SCSISIGI)); + + printf("SEQADDR == 0x%x\n", ahc_inb(ahc, SEQADDR0) + | (ahc_inb(ahc, SEQADDR1) << 8)); + printf("SSTAT1 == 0x%x\n", ahc_inb(ahc, SSTAT1)); +#if 0 + printf("SCSIRATE == 0x%x\n", ahc_inb(ahc, SCSIRATE)); + printf("CCSCBCTL == 0x%x\n", ahc_inb(ahc, CCSCBCTL)); + printf("CCSCBCNT == 0x%x\n", ahc_inb(ahc, CCSCBCNT)); + printf("DFCNTRL == 0x%x\n", ahc_inb(ahc, DFCNTRL)); + printf("DFSTATUS == 0x%x\n", ahc_inb(ahc, DFSTATUS)); + printf("CCHCNT == 0x%x\n", ahc_inb(ahc, CCHCNT)); +#endif + /* Decide our course of action */ + if (scb->flags & SCB_DEVICE_RESET) { + /* + * Been down this road before. + * Do a full bus reset. + */ +bus_reset: + ahc_set_ccb_status(scb->ccb, CAM_CMD_TIMEOUT); + found = ahc_reset_channel(ahc, channel, /*Initiate Reset*/TRUE); + printf("%s: Issued Channel %c Bus Reset. " + "%d SCBs aborted\n", ahc_name(ahc), channel, found); + } else { + /* + * Send a Bus Device Reset message: + * The target that is holding up the bus may not + * be the same as the one that triggered this timeout + * (different commands have different timeout lengths). + * Our strategy here is to queue a BDR message + * to the timed out target if the bus is idle. + * Otherwise, if we have an active target we stuff the + * message buffer with a BDR message and assert ATN + * in the hopes that the target will let go of the bus + * and go to the mesgout phase. If this fails, we'll + * get another timeout 2 seconds later which will attempt + * a bus reset. + */ + u_int8_t active_scb_index; + + active_scb_index = ahc_inb(ahc, SCB_TAG); + + if (bus_state != P_BUSFREE + && (active_scb_index < ahc->scb_data->numscbs)) { + struct scb *active_scb; + + /* + * If the active SCB is not from our device, + * assume that another device is hogging the bus + * and wait for it's timeout to expire before + * taking addition action. + */ + active_scb = ahc->scb_data->scbarray[active_scb_index]; + if (active_scb->hscb->tcl != scb->hscb->tcl + && (scb->flags & SCB_OTHERTCL_TIMEOUT) == 0) { + struct ccb_hdr *ccbh; + u_int newtimeout; + + scb->flags |= SCB_OTHERTCL_TIMEOUT; + newtimeout = MAX(active_scb->ccb->ccb_h.timeout, + scb->ccb->ccb_h.timeout); + ccbh = &scb->ccb->ccb_h; + scb->ccb->ccb_h.timeout_ch = + timeout(ahc_timeout, scb, + (newtimeout * hz) / 1000); + splx(s); + return; + } + ahc_set_recoveryscb(ahc, active_scb); + ahc_outb(ahc, MSG_OUT, MSG_BUS_DEV_RESET); + ahc_outb(ahc, SCSISIGO, bus_state|ATNO); + xpt_print_path(active_scb->ccb->ccb_h.path); + printf("BDR message in message buffer\n"); + active_scb->flags |= SCB_DEVICE_RESET; + active_scb->ccb->ccb_h.timeout_ch = + timeout(ahc_timeout, (caddr_t)active_scb, 2 * hz); + unpause_sequencer(ahc, /*unpause_always*/TRUE); + } else { + int disconnected; + + if (ahc_search_qinfifo(ahc, target, channel, lun, + scb->hscb->tag, /*status*/0, + SEARCH_COUNT) > 0) { + disconnected = FALSE; + } else { + disconnected = TRUE; + } + + if (disconnected) { + u_int8_t hscb_index; + + ahc_set_recoveryscb(ahc, scb); + /* + * Simply set the MK_MESSAGE control bit. + */ + scb->hscb->control |= MK_MESSAGE; + scb->flags |= SCB_QUEUED_MSG + | SCB_DEVICE_RESET; + hscb_index = ahc_find_scb(ahc, scb); + if (hscb_index != SCB_LIST_NULL) { + u_int8_t scb_control; + u_int8_t saved_scbptr; + + saved_scbptr = ahc_inb(ahc, SCBPTR); + ahc_outb(ahc, SCBPTR, hscb_index); + scb_control = ahc_inb(ahc, SCB_CONTROL); + ahc_outb(ahc, SCB_CONTROL, + scb_control | MK_MESSAGE); + ahc_outb(ahc, SCBPTR, saved_scbptr); + } + /* + * Actually re-queue this SCB in case we can + * select the device before it reconnects. + * Clear out any entries in the QINFIFO first + * so we are the next SCB for this target + * to run. + */ + ahc_search_qinfifo(ahc, SCB_TARGET(scb), + channel, SCB_LUN(scb), + SCB_LIST_NULL, + CAM_REQUEUE_REQ, + SEARCH_COMPLETE); + xpt_print_path(scb->ccb->ccb_h.path); + printf("Queuing a BDR SCB\n"); + ahc->qinfifo[ahc->qinfifonext++] = + scb->hscb->tag; + if ((ahc->features & AHC_QUEUE_REGS) != 0) { + ahc_outb(ahc, HNSCB_QOFF, + ahc->qinfifonext); + } else { + ahc_outb(ahc, KERNEL_QINPOS, + ahc->qinfifonext); + } + scb->ccb->ccb_h.timeout_ch = + timeout(ahc_timeout, (caddr_t)scb, 2 * hz); + unpause_sequencer(ahc, /*unpause_always*/FALSE); + } else { + /* Go "immediatly" to the bus reset */ + /* This shouldn't happen */ + ahc_set_recoveryscb(ahc, scb); + xpt_print_path(scb->ccb->ccb_h.path); + printf("SCB %d: Immediate reset. " + "Flags = 0x%x\n", scb->hscb->tag, + scb->flags); + goto bus_reset; + } + } + } + splx(s); +} + +/* + * Look through the SCB array of the card and attempt to find the + * hardware SCB that corresponds to the passed in SCB. Return + * SCB_LIST_NULL if unsuccessful. This routine assumes that the + * card is already paused. + */ +static u_int8_t +ahc_find_scb(struct ahc_softc *ahc, struct scb *scb) +{ + u_int8_t saved_scbptr; + u_int8_t curindex; + + saved_scbptr = ahc_inb(ahc, SCBPTR); + for (curindex = 0; curindex < ahc->scb_data->maxhscbs; curindex++) { + ahc_outb(ahc, SCBPTR, curindex); + if (ahc_inb(ahc, SCB_TAG) == scb->hscb->tag) + break; + } + ahc_outb(ahc, SCBPTR, saved_scbptr); + if (curindex >= ahc->scb_data->maxhscbs) + curindex = SCB_LIST_NULL; + + return curindex; +} + +static int +ahc_search_qinfifo(struct ahc_softc *ahc, int target, char channel, + int lun, u_int8_t tag, u_int32_t status, + ahc_search_action action) +{ + struct scb *scbp; + u_int8_t qinpos; + u_int8_t qintail; + int found; + + qinpos = ahc_inb(ahc, QINPOS); + qintail = ahc->qinfifonext; + found = 0; + + /* + * Start with an empty queue. Entries that are not chosen + * for removal will be re-added to the queue as we go. + */ + ahc->qinfifonext = qinpos; + + while (qinpos != qintail) { + scbp = ahc->scb_data->scbarray[ahc->qinfifo[qinpos]]; + if (ahc_match_scb(scbp, target, channel, lun, tag)) { + /* + * We found an scb that needs to be removed. + */ + switch (action) { + case SEARCH_COMPLETE: + if (ahc_ccb_status(scbp->ccb) == CAM_REQ_INPROG) + ahc_set_ccb_status(scbp->ccb, status); + ahc_freeze_ccb(scbp->ccb); + ahc_done(ahc, scbp); + break; + case SEARCH_COUNT: + ahc->qinfifo[ahc->qinfifonext++] = + scbp->hscb->tag; + break; + case SEARCH_REMOVE: + break; + } + found++; + } else { + ahc->qinfifo[ahc->qinfifonext++] = scbp->hscb->tag; + } + qinpos++; + } + + if ((ahc->features & AHC_QUEUE_REGS) != 0) { + ahc_outb(ahc, HNSCB_QOFF, ahc->qinfifonext); + } else { + ahc_outb(ahc, KERNEL_QINPOS, ahc->qinfifonext); + } + + return (found); +} + + +/* + * Abort all SCBs that match the given description (target/channel/lun/tag), + * setting their status to the passed in status if the status has not already + * been modified from CAM_REQ_INPROG. This routine assumes that the sequencer + * is paused before it is called. + */ +static int +ahc_abort_scbs(struct ahc_softc *ahc, int target, char channel, + int lun, u_int8_t tag, u_int32_t status) +{ + struct scb *scbp; + u_int8_t active_scb; + int i; + int found; + + /* restore this when we're done */ + active_scb = ahc_inb(ahc, SCBPTR); + + found = ahc_search_qinfifo(ahc, target, channel, lun, tag, + CAM_REQUEUE_REQ, SEARCH_COMPLETE); + + /* + * Search waiting for selection list. + */ + { + u_int8_t next, prev; + + next = ahc_inb(ahc, WAITING_SCBH); /* Start at head of list. */ + prev = SCB_LIST_NULL; + + while (next != SCB_LIST_NULL) { + u_int8_t scb_index; + + ahc_outb(ahc, SCBPTR, next); + scb_index = ahc_inb(ahc, SCB_TAG); + if (scb_index >= ahc->scb_data->numscbs) { + panic("Waiting List inconsistency. " + "SCB index == %d, yet numscbs == %d.", + scb_index, ahc->scb_data->numscbs); + } + scbp = ahc->scb_data->scbarray[scb_index]; + if (ahc_match_scb(scbp, target, channel, lun, tag)) { + + next = ahc_abort_wscb(ahc, next, prev); + } else { + + prev = next; + next = ahc_inb(ahc, SCB_NEXT); + } + } + } + /* + * Go through the disconnected list and remove any entries we + * have queued for completion, 0'ing their control byte too. + */ + { + u_int8_t next, prev; + + next = ahc_inb(ahc, DISCONNECTED_SCBH); + prev = SCB_LIST_NULL; + + while (next != SCB_LIST_NULL) { + u_int8_t scb_index; + + ahc_outb(ahc, SCBPTR, next); + scb_index = ahc_inb(ahc, SCB_TAG); + if (scb_index >= ahc->scb_data->numscbs) { + panic("Disconnected List inconsistency. " + "SCB index == %d, yet numscbs == %d.", + scb_index, ahc->scb_data->numscbs); + } + scbp = ahc->scb_data->scbarray[scb_index]; + if (ahc_match_scb(scbp, target, channel, lun, tag)) { + next = ahc_rem_scb_from_disc_list(ahc, prev, + next); + } else { + prev = next; + next = ahc_inb(ahc, SCB_NEXT); + } + } + } + /* + * Go through the hardware SCB array looking for commands that + * were active but not on any list. + */ + for(i = 0; i < ahc->scb_data->maxhscbs; i++) { + u_int8_t scbid; + + ahc_outb(ahc, SCBPTR, i); + scbid = ahc_inb(ahc, SCB_TAG); + if (scbid < ahc->scb_data->numscbs) { + scbp = ahc->scb_data->scbarray[scbid]; + if (ahc_match_scb(scbp, target, channel, lun, tag)) { + ahc_add_curscb_to_free_list(ahc); + } + } + } + /* + * Go through the pending CCB list and look for + * commands for this target that are still active. + * These are other tagged commands that were + * disconnected when the reset occured. + */ + { + struct ccb_hdr *ccb_h; + + + ccb_h = ahc->pending_ccbs.lh_first; + + while (ccb_h != NULL) { + scbp = (struct scb *)ccb_h->ccb_scb_ptr; + ccb_h = ccb_h->sim_links.le.le_next; + if (ahc_match_scb(scbp, target, channel, lun, tag)) { + if (ahc_ccb_status(scbp->ccb) == CAM_REQ_INPROG) + ahc_set_ccb_status(scbp->ccb, status); + ahc_freeze_ccb(scbp->ccb); + ahc_done(ahc, scbp); + found++; + } + } + } + ahc_outb(ahc, SCBPTR, active_scb); + return found; +} + +static u_int8_t +ahc_rem_scb_from_disc_list(struct ahc_softc *ahc, u_int8_t prev, u_int8_t scbptr) +{ + u_int8_t next; + + ahc_outb(ahc, SCBPTR, scbptr); + next = ahc_inb(ahc, SCB_NEXT); + + ahc_outb(ahc, SCB_CONTROL, 0); + + ahc_add_curscb_to_free_list(ahc); + + if (prev != SCB_LIST_NULL) { + ahc_outb(ahc, SCBPTR, prev); + ahc_outb(ahc, SCB_NEXT, next); + } else + ahc_outb(ahc, DISCONNECTED_SCBH, next); + + return next; +} + +static void +ahc_add_curscb_to_free_list(struct ahc_softc *ahc) +{ + /* Invalidate the tag so that ahc_find_scb doesn't think it's active */ + ahc_outb(ahc, SCB_TAG, SCB_LIST_NULL); + + ahc_outb(ahc, SCB_NEXT, ahc_inb(ahc, FREE_SCBH)); + ahc_outb(ahc, FREE_SCBH, ahc_inb(ahc, SCBPTR)); +} + +/* + * Manipulate the waiting for selection list and return the + * scb that follows the one that we remove. + */ +static u_int8_t +ahc_abort_wscb(struct ahc_softc *ahc, u_int8_t scbpos, u_int8_t prev) +{ + u_int8_t curscb, next; + + /* + * Select the SCB we want to abort and + * pull the next pointer out of it. + */ + curscb = ahc_inb(ahc, SCBPTR); + ahc_outb(ahc, SCBPTR, scbpos); + next = ahc_inb(ahc, SCB_NEXT); + + /* Clear the necessary fields */ + ahc_outb(ahc, SCB_CONTROL, 0); + + ahc_add_curscb_to_free_list(ahc); + + /* update the waiting list */ + if (prev == SCB_LIST_NULL) { + /* First in the list */ + ahc_outb(ahc, WAITING_SCBH, next); + + /* + * Ensure we aren't attempting to perform + * selection for this entry. + */ + ahc_outb(ahc, SCSISEQ, (ahc_inb(ahc, SCSISEQ) & ~ENSELO)); + } else { + /* + * Select the scb that pointed to us + * and update its next pointer. + */ + ahc_outb(ahc, SCBPTR, prev); + ahc_outb(ahc, SCB_NEXT, next); + } + + /* + * Point us back at the original scb position. + */ + ahc_outb(ahc, SCBPTR, curscb); + return next; +} + +static void +ahc_clear_intstat(struct ahc_softc *ahc) +{ + /* Clear any interrupt conditions this may have caused */ + ahc_outb(ahc, CLRSINT0, CLRSELDO|CLRSELDI|CLRSELINGO); + ahc_outb(ahc, CLRSINT1, CLRSELTIMEO|CLRATNO|CLRSCSIRSTI + |CLRBUSFREE|CLRSCSIPERR|CLRPHASECHG| + CLRREQINIT); + ahc_outb(ahc, CLRINT, CLRSCSIINT); +} + +static void +ahc_reset_current_bus(struct ahc_softc *ahc) +{ + u_int8_t scsiseq; + + ahc_outb(ahc, SIMODE1, ahc_inb(ahc, SIMODE1) & ~ENSCSIRST); + scsiseq = ahc_inb(ahc, SCSISEQ); + ahc_outb(ahc, SCSISEQ, scsiseq | SCSIRSTO); + DELAY(AHC_BUSRESET_DELAY); + /* Turn off the bus reset */ + ahc_outb(ahc, SCSISEQ, scsiseq & ~SCSIRSTO); + + ahc_clear_intstat(ahc); + + /* Re-enable reset interrupts */ + ahc_outb(ahc, SIMODE1, ahc_inb(ahc, SIMODE1) | ENSCSIRST); +} + +static int +ahc_reset_channel(struct ahc_softc *ahc, char channel, int initiate_reset) +{ + u_int target, max_target; + int found; + u_int8_t sblkctl; + char cur_channel; + struct cam_path *path; + + pause_sequencer(ahc); + /* + * Clean up all the state information for the + * pending transactions on this bus. + */ + found = ahc_abort_scbs(ahc, ALL_TARGETS, channel, ALL_LUNS, + SCB_LIST_NULL, CAM_SCSI_BUS_RESET); + path = channel == 'B' ? ahc->path_b : ahc->path; + + /* Notify the XPT that a bus reset occurred */ + xpt_async(AC_BUS_RESET, path, NULL); + + /* + * Revert to async/narrow transfers until we renegotiate. + */ + max_target = (ahc->features & AHC_WIDE) ? 15 : 7; + for (target = 0; target <= max_target; target++) { + struct ahc_devinfo devinfo; + + ahc_compile_devinfo(&devinfo, target, channel); + ahc_set_width(ahc, &devinfo, path, MSG_EXT_WDTR_BUS_8_BIT, + AHC_TRANS_CUR); + ahc_set_syncrate(ahc, &devinfo, path, /*syncrate*/NULL, + /*period*/0, /*offset*/0, AHC_TRANS_CUR); + } + + /* + * Reset the bus if we are initiating this reset and + * restart/unpause the sequencer + */ + sblkctl = ahc_inb(ahc, SBLKCTL); + cur_channel = 'A'; + if ((ahc->features & AHC_TWIN) != 0 + && ((sblkctl & SELBUSB) != 0)) + cur_channel = 'B'; + if (cur_channel != channel) { + /* Case 1: Command for another bus is active + * Stealthily reset the other bus without + * upsetting the current bus. + */ + ahc_outb(ahc, SBLKCTL, sblkctl ^ SELBUSB); + ahc_outb(ahc, SIMODE1, + ahc_inb(ahc, SIMODE1) & ~(ENBUSFREE|ENREQINIT)); + ahc_outb(ahc, SCSISEQ, + ahc_inb(ahc, SCSISEQ) & (ENSELI|ENRSELI|ENAUTOATNP)); + if (initiate_reset) + ahc_reset_current_bus(ahc); + ahc_clear_intstat(ahc); + ahc_outb(ahc, SBLKCTL, sblkctl); + unpause_sequencer(ahc, /*unpause_always*/FALSE); + } else { + /* Case 2: A command from this bus is active or we're idle */ + ahc_outb(ahc, SIMODE1, + ahc_inb(ahc, SIMODE1) & ~(ENBUSFREE|ENREQINIT)); + ahc->flags &= ~AHC_HANDLING_REQINITS; + ahc->msg_type = MSG_TYPE_NONE; + ahc_outb(ahc, SCSISEQ, + ahc_inb(ahc, SCSISEQ) & (ENSELI|ENRSELI|ENAUTOATNP)); + if (initiate_reset) + ahc_reset_current_bus(ahc); + ahc_clear_intstat(ahc); + restart_sequencer(ahc); + } + return found; +} + +static int +ahc_match_scb (struct scb *scb, int target, char channel, + int lun, u_int8_t tag) +{ + int targ = SCB_TARGET(scb); + char chan = SCB_CHANNEL(scb); + int slun = SCB_LUN(scb); + int match; + + match = ((chan == channel) || (channel == ALL_CHANNELS)); + if (match != 0) + match = ((targ == target) || (target == ALL_TARGETS)); + if (match != 0) + match = ((lun == slun) || (lun == ALL_LUNS)); + if (match != 0) + match = ((tag == scb->hscb->tag) || (tag == SCB_LIST_NULL)); + + return match; +} + +static void +ahc_construct_sdtr(struct ahc_softc *ahc, u_int8_t period, u_int8_t offset) +{ + ahc->msg_buf[ahc->msg_index++] = MSG_EXTENDED; + ahc->msg_buf[ahc->msg_index++] = MSG_EXT_SDTR_LEN; + ahc->msg_buf[ahc->msg_index++] = MSG_EXT_SDTR; + ahc->msg_buf[ahc->msg_index++] = period; + ahc->msg_buf[ahc->msg_index++] = offset; + ahc->msg_len += 5; +} + +static void +ahc_construct_wdtr(struct ahc_softc *ahc, u_int8_t bus_width) +{ + ahc->msg_buf[ahc->msg_index++] = MSG_EXTENDED; + ahc->msg_buf[ahc->msg_index++] = MSG_EXT_WDTR_LEN; + ahc->msg_buf[ahc->msg_index++] = MSG_EXT_WDTR; + ahc->msg_buf[ahc->msg_index++] = bus_width; + ahc->msg_len += 4; +} + +static void +ahc_calc_residual(struct scb *scb) +{ + struct hardware_scb *hscb; + + hscb = scb->hscb; + + /* + * If the disconnected flag is still set, this is bogus + * residual information left over from a sequencer + * pagin/pageout, so ignore this case. + */ + if ((scb->hscb->control & DISCONNECTED) == 0) { + u_int32_t resid; + int resid_sgs; + int sg; + + /* + * Remainder of the SG where the transfer + * stopped. + */ + resid = (hscb->residual_data_count[2] << 16) + | (hscb->residual_data_count[1] <<8) + | (hscb->residual_data_count[0]); + + /* + * Add up the contents of all residual + * SG segments that are after the SG where + * the transfer stopped. + */ + resid_sgs = scb->hscb->residual_SG_count - 1/*current*/; + sg = scb->sg_count - resid_sgs - 1/*first SG*/; + while (resid_sgs > 0) { + + resid += scb->ahc_dma[sg].len; + sg++; + resid_sgs--; + } + if ((scb->flags & SCB_SENSE) == 0) { + + scb->ccb->csio.resid = resid; + } else { + + scb->ccb->csio.sense_resid = resid; + } + } + + /* + * Clean out the residual information in this SCB for its + * next consumer. + */ + hscb->residual_data_count[0] = 0; + hscb->residual_data_count[1] = 0; + hscb->residual_data_count[2] = 0; + hscb->residual_SG_count = 0; + +#ifdef AHC_DEBUG + if (ahc_debug & AHC_SHOWMISC) { + sc_print_addr(xs->sc_link); + printf("Handled Residual of %ld bytes\n" ,xs->resid); + } +#endif +} + +static void +ahc_update_pending_syncrates(struct ahc_softc *ahc) +{ + /* + * Traverse the pending SCB list and ensure that all of the + * SCBs there have the proper settings. + */ + struct ccb_hdr *ccbh; + int pending_ccb_count; + int i; + u_int saved_scbptr; + + /* + * We were able to complete the command successfully, + * so reinstate the timeouts for all other pending + * commands. + */ + ccbh = LIST_FIRST(&ahc->pending_ccbs); + pending_ccb_count = 0; + while (ccbh != NULL) { + struct scb *pending_scb; + struct hardware_scb *pending_hscb; + struct ahc_target_tinfo *tinfo; + struct ahc_devinfo devinfo; + + pending_scb = (struct scb *)ccbh->ccb_scb_ptr; + pending_hscb = pending_scb->hscb; + ahc_compile_devinfo(&devinfo, SCB_TARGET(pending_scb), + SCB_CHANNEL(pending_scb)); + tinfo = &ahc->transinfo[devinfo.target_offset]; + pending_hscb->control &= ~ULTRAENB; + if ((ahc->ultraenb & devinfo.target_mask) != 0) + pending_hscb->control |= ULTRAENB; + pending_hscb->scsirate = tinfo->scsirate; + pending_hscb->scsioffset = tinfo->current.offset; + pending_ccb_count++; + ccbh = LIST_NEXT(ccbh, sim_links.le); + } + + if (pending_ccb_count == 0) + return; + + saved_scbptr = ahc_inb(ahc, SCBPTR); + /* Ensure that the hscbs down on the card match the new information */ + for (i = 0; i < ahc->scb_data->maxhscbs; i++) { + u_int scb_tag; + + ahc_outb(ahc, SCBPTR, i); + scb_tag = ahc_inb(ahc, SCB_TAG); + if (scb_tag != SCB_LIST_NULL) { + struct scb *pending_scb; + struct hardware_scb *pending_hscb; + struct ahc_target_tinfo *tinfo; + struct ahc_devinfo devinfo; + u_int control; + + pending_scb = ahc->scb_data->scbarray[scb_tag]; + pending_hscb = pending_scb->hscb; + ahc_compile_devinfo(&devinfo, SCB_TARGET(pending_scb), + SCB_CHANNEL(pending_scb)); + tinfo = &ahc->transinfo[devinfo.target_offset]; + control = ahc_inb(ahc, SCB_CONTROL); + control &= ~ULTRAENB; + if ((ahc->ultraenb & devinfo.target_mask) != 0) + control |= ULTRAENB; + ahc_outb(ahc, SCB_CONTROL, control); + ahc_outb(ahc, SCB_SCSIRATE, tinfo->scsirate); + ahc_outb(ahc, SCB_SCSIOFFSET, tinfo->current.offset); + } + } + ahc_outb(ahc, SCBPTR, saved_scbptr); +} + +static void +ahc_dump_targcmd(struct target_cmd *cmd) +{ + u_int8_t *byte; + u_int8_t *last_byte; + int initiator; + int target; + int lun; + int i; + + byte = &cmd->icl; + /* Debugging info for received commands */ + last_byte = &cmd[1].icl; + + i = 0; + while (byte < last_byte) { + if (i == 0) + printf("\t"); + printf("%#x", *byte++); + i++; + if (i == 8) { + printf("\n"); + i = 0; + } else { + printf(", "); + } + } +} + +static void +ahc_shutdown(int howto, void *arg) +{ + struct ahc_softc *ahc; + int i; + + ahc = (struct ahc_softc *)arg; + + ahc_reset(ahc); + ahc_outb(ahc, SCSISEQ, 0); + ahc_outb(ahc, SXFRCTL0, 0); + ahc_outb(ahc, DSPCISTATUS, 0); + + for (i = TARG_SCSIRATE; i < HA_274_BIOSCTRL; i++) + ahc_outb(ahc, i, 0); +} diff --git a/sys/dev/aic7xxx/aic7xxx.h b/sys/dev/aic7xxx/aic7xxx.h new file mode 100644 index 0000000..06f7d4a --- /dev/null +++ b/sys/dev/aic7xxx/aic7xxx.h @@ -0,0 +1,524 @@ +/* + * Interface to the generic driver for the aic7xxx based adaptec + * SCSI controllers. This is used to implement product specific + * probe and attach routines. + * + * Copyright (c) 1994, 1995, 1996, 1997, 1998 Justin T. Gibbs. + * All rights reserved. + * + * 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, + * without modification, immediately at the beginning of the file. + * 2. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * Where this Software is combined with software released under the terms of + * the GNU Public License ("GPL") and the terms of the GPL would require the + * combined work to also be released under the terms of the GPL, the terms + * and conditions of this License will apply in addition to those of the + * GPL with the exception of any terms or conditions of this License that + * conflict with, or are expressly prohibited by, the GPL. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR 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 AUTHOR 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. + * + * $Id: aic7xxx.h,v 1.40 1997/02/25 03:05:35 gibbs Exp $ + */ + +#ifndef _AIC7XXX_H_ +#define _AIC7XXX_H_ + +#include "ahc.h" /* for NAHC from config */ +#include "opt_aic7xxx.h" /* for config options */ + +#include <pci/pcivar.h> /* for pcici_t */ + +#define AHC_MAXTRANSFER_SIZE 0x00ffffff /* limited by 24bit counter */ +#define AHC_NSEG 32 /* The number of dma segments supported. + * AHC_NSEG can be maxed out at 256 entries, + * but the kernel will never need to transfer + * such a large (1MB) request. To reduce the + * driver's memory consumption, we reduce the + * max to 32. 16 would work if all transfers + * are paged alined since the kernel will only + * generate at most a 64k transfer, but to + * handle non-page aligned transfers, you need + * 17, so we round to the next power of two + * to make allocating SG space easy and + * efficient. + */ + +#define AHC_SCB_MAX 255 /* + * Up to 255 SCBs on some types of aic7xxx + * based boards. The aic7870 have 16 internal + * SCBs, but external SRAM bumps this to 255. + * The aic7770 family have only 4, and the + * aic7850 has only 3. + */ + + +#if defined(__FreeBSD__) +extern u_long ahc_unit; +#endif + +struct ahc_dma_seg { + u_int32_t addr; + u_int32_t len; +}; + +typedef enum { + AHC_NONE = 0x0000, + AHC_CHIPID_MASK = 0x00FF, + AHC_AIC7770 = 0x0001, + AHC_AIC7850 = 0x0002, + AHC_AIC7860 = 0x0003, + AHC_AIC7870 = 0x0004, + AHC_AIC7880 = 0x0005, + AHC_AIC7890 = 0x0006, + AHC_AIC7895 = 0x0007, + AHC_AIC7896 = 0x0008, + AHC_VL = 0x0100, /* Bus type VL */ + AHC_EISA = 0x0200, /* Bus type EISA */ + AHC_PCI = 0x0400, /* Bus type PCI */ +} ahc_chip; + +typedef enum { + AHC_FENONE = 0x0000, + AHC_ULTRA = 0x0001, /* Supports 20MHz Transfers */ + AHC_ULTRA2 = 0x0002, /* Supports 40MHz Transfers */ + AHC_WIDE = 0x0004, /* Wide Channel */ + AHC_TWIN = 0x0008, /* Twin Channel */ + AHC_MORE_SRAM = 0x0010, /* 80 bytes instead of 64 */ + AHC_CMD_CHAN = 0x0020, /* Has a Command DMA Channel */ + AHC_QUEUE_REGS = 0x0040, /* Has Queue management registers */ + AHC_SG_PRELOAD = 0x0080, /* Can perform auto-SG preload */ + AHC_SPIOCAP = 0x0100, /* Has a Serial Port I/O Cap Register */ + AHC_MULTI_TID = 0x0200, /* Has bitmask of TIDs for select-in */ + AHC_AIC7770_FE = AHC_FENONE, + AHC_AIC7850_FE = AHC_FENONE|AHC_SPIOCAP, + AHC_AIC7860_FE = AHC_ULTRA|AHC_SPIOCAP, + AHC_AIC7870_FE = AHC_FENONE, + AHC_AIC7880_FE = AHC_ULTRA, + AHC_AIC7890_FE = AHC_MORE_SRAM|AHC_CMD_CHAN|AHC_ULTRA2|AHC_QUEUE_REGS|AHC_SG_PRELOAD|AHC_MULTI_TID, + AHC_AIC7895_FE = AHC_MORE_SRAM|AHC_CMD_CHAN|AHC_ULTRA, + AHC_AIC7896_FE = AHC_MORE_SRAM|AHC_CMD_CHAN|AHC_ULTRA2|AHC_QUEUE_REGS|AHC_SG_PRELOAD|AHC_MULTI_TID, +} ahc_feature; + +typedef enum { + AHC_FNONE = 0x000, + AHC_PAGESCBS = 0x001,/* Enable SCB paging */ + AHC_CHANNEL_B_PRIMARY = 0x002,/* + * On twin channel adapters, probe + * channel B first since it is the + * primary bus. + */ + AHC_USEDEFAULTS = 0x004,/* + * For cards without an seeprom + * or a BIOS to initialize the chip's + * SRAM, we use the default target + * settings. + */ + AHC_INDIRECT_PAGING = 0x008, + AHC_SHARED_SRAM = 0x010, + AHC_LARGE_SEEPROM = 0x020,/* Uses C56_66 not C46 */ + AHC_EXTENDED_TRANS_A = 0x100, + AHC_EXTENDED_TRANS_B = 0x200, + AHC_TERM_ENB_A = 0x400, + AHC_TERM_ENB_B = 0x800, + AHC_HANDLING_REQINITS = 0x1000, + AHC_TARGETMODE = 0x2000,/* + * Allow target operations on this + * controller. + */ + AHC_NEWEEPROM_FMT = 0x4000, + AHC_RESOURCE_SHORTAGE = 0x8000 +} ahc_flag; + +typedef enum { + SCB_FREE = 0x0000, + SCB_OTHERTCL_TIMEOUT = 0x0002,/* + * Another device was active + * during the first timeout for + * this SCB so we gave ourselves + * an additional timeout period + * in case it was hogging the + * bus. + */ + SCB_DEVICE_RESET = 0x0004, + SCB_SENSE = 0x0008, + SCB_RECOVERY_SCB = 0x0040, + SCB_MSGOUT_SENT = 0x0200, + SCB_MSGOUT_SDTR = 0x0400, + SCB_MSGOUT_WDTR = 0x0800, + SCB_MSGOUT_BITS = (SCB_MSGOUT_SDTR|SCB_MSGOUT_WDTR + |SCB_MSGOUT_SENT), + SCB_ABORT = 0x1000, + SCB_QUEUED_MSG = 0x2000, + SCB_ACTIVE = 0x4000, + SCB_TARGET_IMMEDIATE = 0x8000 +} scb_flag; + +/* + * The driver keeps up to MAX_SCB scb structures per card in memory. The SCB + * consists of a "hardware SCB" mirroring the fields availible on the card + * and additional information the kernel stores for each transaction. + */ +struct hardware_scb { +/*0*/ u_int8_t control; +/*1*/ u_int8_t tcl; /* 4/1/3 bits */ +/*2*/ u_int8_t status; +/*3*/ u_int8_t SG_count; +/*4*/ u_int32_t SG_pointer; +/*8*/ u_int8_t residual_SG_count; +/*9*/ u_int8_t residual_data_count[3]; +/*12*/ u_int32_t data; +/*16*/ u_int32_t datalen; /* Really only three bytes, but its + * faster to treat it as a long on + * a quad boundary. + */ +/*20*/ u_int32_t cmdpointer; +/*24*/ u_int8_t cmdlen; +/*25*/ u_int8_t tag; /* Index into our kernel SCB array. + * Also used as the tag for tagged I/O + */ +/*26*/ u_int8_t next; /* Used for threading SCBs in the + * "Waiting for Selection" and + * "Disconnected SCB" lists down + * in the sequencer. + */ +/*27*/ u_int8_t scsirate; /* Value for SCSIRATE register */ +/*28*/ u_int8_t scsioffset; /* Value for SCSIOFFSET register */ +/*29*/ u_int8_t spare[3]; /* + * Spare space available on + * all controller types. + */ +/*32*/ u_int8_t cmdstore[16]; /* + * CDB storage for controllers + * supporting 64 byte SCBs. + */ +/*48*/ u_int32_t cmdstore_busaddr; /* + * Address of command store for + * 32byte SCB adapters + */ +/*48*/ u_int8_t spare_64[12]; /* + * Pad to 64 bytes. + */ +}; + +struct scb { + struct hardware_scb *hscb; + STAILQ_ENTRY(scb) links; /* for chaining */ + union ccb *ccb; /* the ccb for this cmd */ + scb_flag flags; + bus_dmamap_t dmamap; + struct ahc_dma_seg *ahc_dma;/* Pointer to SG segments */ + u_int32_t ahc_dmaphys;/* Phsical address of SG list */ + u_int sg_count;/* How full ahc_dma_seg is */ +}; + +struct scb_data { + struct hardware_scb *hscbs; /* Array of hardware SCBs */ + struct scb *scbarray[AHC_SCB_MAX]; /* Array of kernel SCBs */ + STAILQ_HEAD(, scb) free_scbs; /* + * Pool of SCBs ready to be assigned + * commands to execute. + */ + u_int8_t numscbs; + u_int8_t maxhscbs; /* Number of SCBs on the card */ + u_int8_t maxscbs; /* + * Max SCBs we allocate total including + * any that will force us to page SCBs + */ +}; + +/* + * Connection desciptor for select-in requests in target mode. + * The first byte is the connecting target, followed by identify + * message and optional tag information, terminated by 0xFF. The + * remainder is the command to execute. + */ +struct target_cmd { + u_int8_t icl; /* Really only holds Initiator ID */ + u_int8_t targ_id; /* Target ID we were selected at */ + u_int8_t identify; /* Identify message */ + u_int8_t bytes[29]; +}; + +/* + * Per lun target mode state including accept TIO CCB + * and immediate notify CCB pools. + */ +struct tmode_lstate { + SLIST_HEAD(, ccb_hdr) accept_tios; + SLIST_HEAD(, ccb_hdr) immed_notifies; +}; + +/* + * Per target mode enabled target state. Esentially just an array of + * pointers to lun target state. + */ +struct tmode_tstate { + struct tmode_lstate* enabled_luns[8]; +}; + +/* + * Define the format of the aic7XX0 SEEPROM registers (16 bits). + */ + +struct seeprom_config { +/* + * SCSI ID Configuration Flags + */ + u_int16_t device_flags[16]; /* words 0-15 */ +#define CFXFER 0x0007 /* synchronous transfer rate */ +#define CFSYNCH 0x0008 /* enable synchronous transfer */ +#define CFDISC 0x0010 /* enable disconnection */ +#define CFWIDEB 0x0020 /* wide bus device */ +#define CFSYNCHISULTRA 0x0040 /* CFSYNCH is an ultra offset (2940AU)*/ +/* UNUSED 0x0080 */ +#define CFSTART 0x0100 /* send start unit SCSI command */ +#define CFINCBIOS 0x0200 /* include in BIOS scan */ +#define CFRNFOUND 0x0400 /* report even if not found */ +#define CFMULTILUN 0x0800 /* Probe multiple luns in BIOS scan */ +/* UNUSED 0xf000 */ + +/* + * BIOS Control Bits + */ + u_int16_t bios_control; /* word 16 */ +#define CFSUPREM 0x0001 /* support all removeable drives */ +#define CFSUPREMB 0x0002 /* support removeable drives for boot only */ +#define CFBIOSEN 0x0004 /* BIOS enabled */ +/* UNUSED 0x0008 */ +#define CFSM2DRV 0x0010 /* support more than two drives */ +#define CF284XEXTEND 0x0020 /* extended translation (284x cards) */ +/* UNUSED 0x0060 */ +#define CFEXTEND 0x0080 /* extended translation enabled */ +/* UNUSED 0xff00 */ + +/* + * Host Adapter Control Bits + */ + u_int16_t adapter_control; /* word 17 */ +#define CFAUTOTERM 0x0001 /* Perform Auto termination */ +#define CFULTRAEN 0x0002 /* Ultra SCSI speed enable */ +#define CF284XSELTO 0x0003 /* Selection timeout (284x cards) */ +#define CF284XFIFO 0x000C /* FIFO Threshold (284x cards) */ +#define CFSTERM 0x0004 /* SCSI low byte termination */ +#define CFWSTERM 0x0008 /* SCSI high byte termination */ +#define CFSPARITY 0x0010 /* SCSI parity */ +#define CF284XSTERM 0x0020 /* SCSI low byte term (284x cards) */ +#define CFRESETB 0x0040 /* reset SCSI bus at boot */ +#define CFCHNLBPRIMARY 0x0100 /* aic7895 probe B channel first */ +#define CFSEAUTOTERM 0x0400 /* aic7890 Perform SE Auto Termination*/ +#define CFLVDSTERM 0x0800 /* aic7890 LVD Termination */ +/* UNUSED 0xf080 */ + +/* + * Bus Release, Host Adapter ID + */ + u_int16_t brtime_id; /* word 18 */ +#define CFSCSIID 0x000f /* host adapter SCSI ID */ +/* UNUSED 0x00f0 */ +#define CFBRTIME 0xff00 /* bus release time */ + +/* + * Maximum targets + */ + u_int16_t max_targets; /* word 19 */ +#define CFMAXTARG 0x00ff /* maximum targets */ +/* UNUSED 0xff00 */ + u_int16_t res_1[11]; /* words 20-30 */ + u_int16_t checksum; /* word 31 */ +}; + +#define AHC_TRANS_CUR 0x01 /* Modify current neogtiation status */ +#define AHC_TRANS_ACTIVE 0x03 /* Assume this is the active target */ +#define AHC_TRANS_GOAL 0x04 /* Modify negotiation goal */ +#define AHC_TRANS_USER 0x08 /* Modify user negotiation settings */ + +struct ahc_transinfo { + u_int8_t width; + u_int8_t period; + u_int8_t offset; +}; + +struct ahc_target_tinfo { + u_int8_t scsirate; + struct ahc_transinfo current; + struct ahc_transinfo goal; + struct ahc_transinfo user; +}; + +struct ahc_syncrate { + int sxfr_ultra2; + int sxfr; + /* Rates in Ultra mode have bit 8 of sxfr set */ +#define ULTRA_SXFR 0x100 + u_int8_t period; /* Period to send to SCSI target */ + char *rate; +}; + +typedef enum { + MSG_TYPE_NONE = 0x00, + MSG_TYPE_INITIATOR_MSGOUT = 0x01, + MSG_TYPE_INITIATOR_MSGIN = 0x02 +} ahc_msg_type; + +struct ahc_softc { + bus_space_tag_t tag; + bus_space_handle_t bsh; + bus_dma_tag_t dmat; + struct scb_data *scb_data; + + /* + * CCBs that have been send to the controller + */ + LIST_HEAD(, ccb_hdr) pending_ccbs; + + /* + * Target mode related state kept on a per enabled lun basis. + * Targets that are not enabled will have null entries. + */ + struct tmode_tstate* enabled_targets[16]; + + /* + * Device instance currently on the bus awaiting a continue TIO + * for a command that was not given the disconnect priveledge. + */ + struct tmode_lstate* pending_device; + + /* + * Card characteristics + */ + ahc_chip chip; + ahc_feature features; + ahc_flag flags; + + /* Values to store in the SEQCTL register for pause and unpause */ + u_int8_t unpause; + u_int8_t pause; + + /* Command Queues */ + u_int8_t qoutfifonext; + u_int8_t qinfifonext; + u_int8_t qoutfifo[256]; + u_int8_t qinfifo[256]; + + /* + * 256 byte array storing the SCBID of outstanding + * untagged SCBs indexed by TCL. + */ + u_int8_t untagged_scbs[256]; + + /* + * User/Current/Active Negotiation settings + */ + struct ahc_target_tinfo transinfo[16]; + + /* + * Per target state bitmasks. + */ + u_int16_t ultraenb; /* Using ultra sync rate */ + u_int16_t sdtrpending; /* Pending SDTR request */ + u_int16_t wdtrpending; /* Pending WDTR request */ + u_int16_t discenable; /* Disconnection allowed */ + u_int16_t tagenable; /* Tagged Queuing allowed */ + + /* + * Hooks into the XPT. + */ + struct cam_sim *sim; + struct cam_sim *sim_b; + struct cam_path *path; + struct cam_path *path_b; + + int unit; + + /* Channel Names ('A', 'B', etc.) */ + char channel; + char channel_b; + + /* Initiator Bus ID */ + u_int8_t our_id; + u_int8_t our_id_b; + + /* + * PCI error detection and data for running the + * PCI error interrupt handler. + */ + int unsolicited_ints; + pcici_t pci_config_id; + + /* Hmmm. */ + struct target_cmd *targetcmds; + int next_targetcmd; + int num_targetcmds; + + /* + * Incoming and outgoing message handling. + */ + ahc_msg_type msg_type; + u_int8_t msg_buf[8]; /* Message we are sending */ + u_int msg_len; /* Length of message to send */ + u_int msg_index; /* Current index in message */ + + /* + * "Bus" addresses of our data structures. + */ + u_int32_t hscb_busaddr; +}; + +struct full_ahc_softc { + struct ahc_softc softc; + struct scb_data scb_data_storage; +}; + +/* #define AHC_DEBUG */ +#ifdef AHC_DEBUG +/* Different debugging levels used when AHC_DEBUG is defined */ +#define AHC_SHOWMISC 0x0001 +#define AHC_SHOWCMDS 0x0002 +#define AHC_SHOWSCBS 0x0004 +#define AHC_SHOWABORTS 0x0008 +#define AHC_SHOWSENSE 0x0010 +#define AHC_SHOWSCBCNT 0x0020 + +extern int ahc_debug; /* Initialized in i386/scsi/aic7xxx.c */ +#endif + +char *ahc_name(struct ahc_softc *ahc); + +struct ahc_softc *ahc_alloc(int unit, u_int32_t io_base, + vm_offset_t maddr, ahc_chip chip, + ahc_feature features, ahc_flag flags, + struct scb_data *scb_data); +int ahc_reset(struct ahc_softc *ahc); +void ahc_free(struct ahc_softc *); +int ahc_probe_scbs(struct ahc_softc *); +int ahc_init(struct ahc_softc *); +int ahc_attach(struct ahc_softc *); +void ahc_intr(void *arg); + +#define ahc_inb(ahc, port) \ + bus_space_read_1((ahc)->tag, (ahc)->bsh, port) + +#define ahc_outb(ahc, port, value) \ + bus_space_write_1((ahc)->tag, (ahc)->bsh, port, value) + +#define ahc_outsb(ahc, port, valp, count) \ + bus_space_write_multi_1((ahc)->tag, (ahc)->bsh, port, valp, count) + +#endif /* _AIC7XXX_H_ */ diff --git a/sys/dev/aic7xxx/aic7xxx.reg b/sys/dev/aic7xxx/aic7xxx.reg index b166c33..fff2b6f 100644 --- a/sys/dev/aic7xxx/aic7xxx.reg +++ b/sys/dev/aic7xxx/aic7xxx.reg @@ -1,7 +1,7 @@ /* * Aic7xxx register and scratch ram definitions. * - * Copyright (c) 1994-1997 Justin Gibbs. + * Copyright (c) 1994-1998 Justin Gibbs. * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -10,10 +10,7 @@ * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions, and the following disclaimer, * without modification, immediately at the beginning of the file. - * 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. The name of the author may not be used to endorse or promote products + * 2. The name of the author may not be used to endorse or promote products * derived from this software without specific prior written permission. * * Where this Software is combined with software released under the terms of @@ -35,7 +32,7 @@ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * - * $Id: aic7xxx.reg,v 1.5 1997/08/13 17:02:24 gibbs Exp $ + * $Id: aic7xxx.reg,v 1.4 1997/06/27 19:38:39 gibbs Exp $ */ /* @@ -164,6 +161,7 @@ register SCSIRATE { access_mode RW bit WIDEXFER 0x80 /* Wide transfer control */ mask SXFR 0x70 /* Sync transfer rate */ + mask SXFR_ULTRA2 0x7f /* Sync transfer rate */ mask SOFS 0x0f /* Sync offset */ } @@ -177,6 +175,13 @@ register SCSIID { access_mode RW mask TID 0xf0 /* Target ID mask */ mask OID 0x0f /* Our ID mask */ + /* + * SCSI Maximum Offset (p. 4-61 aic7890/91 Data Book) + * The aic7890/91 allow an offset of up to 127 transfers in both wide + * and narrow mode. + */ + alias SCSIOFFSET + mask SOFS_ULTRA2 0x7f /* Sync offset U2 chips */ } /* @@ -230,14 +235,15 @@ register CLRSINT0 { register SSTAT0 { address 0x00b access_mode RO - bit TARGET 0x80 /* Board acting as target */ - bit SELDO 0x40 /* Selection Done */ - bit SELDI 0x20 /* Board has been selected */ - bit SELINGO 0x10 /* Selection In Progress */ - bit SWRAP 0x08 /* 24bit counter wrap */ - bit SDONE 0x04 /* STCNT = 0x000000 */ - bit SPIORDY 0x02 /* SCSI PIO Ready */ - bit DMADONE 0x01 /* DMA transfer completed */ + bit TARGET 0x80 /* Board acting as target */ + bit SELDO 0x40 /* Selection Done */ + bit SELDI 0x20 /* Board has been selected */ + bit SELINGO 0x10 /* Selection In Progress */ + bit SWRAP 0x08 /* 24bit counter wrap */ + bit IOERR 0x08 /* LVD Tranceiver mode changed */ + bit SDONE 0x04 /* STCNT = 0x000000 */ + bit SPIORDY 0x02 /* SCSI PIO Ready */ + bit DMADONE 0x01 /* DMA transfer completed */ } /* @@ -279,6 +285,7 @@ register SSTAT2 { address 0x00d access_mode RO bit OVERRUN 0x80 + bit EXP_ACTIVE 0x10 /* SCSI Expander Active */ mask SFCNT 0x1f } @@ -293,14 +300,13 @@ register SSTAT3 { } /* - * SCSI Test Control (p. 3-27) + * SCSI ID for the aic7890/91 chips */ -register SCSITEST { +register SCSIID_ULTRA2 { address 0x00f access_mode RW - bit RQAKCNT 0x04 - bit CNTRTEST 0x02 - bit CMODE 0x01 + mask TID 0xf0 /* Target ID mask */ + mask OID 0x0f /* Our ID mask */ } /* @@ -315,6 +321,7 @@ register SIMODE0 { bit ENSELDI 0x20 bit ENSELINGO 0x10 bit ENSWRAP 0x08 + bit ENIOERR 0x08 /* LVD Tranceiver mode changes */ bit ENSDONE 0x04 bit ENSPIORDY 0x02 bit ENDMADONE 0x01 @@ -378,6 +385,7 @@ register SELTIMER { bit STAGE3 0x04 bit STAGE2 0x02 bit STAGE1 0x01 + alias TARGIDIN } /* @@ -393,6 +401,36 @@ register SELID { } /* + * Target Mode Selecting in ID bitmask (aic7890/91/96/97) + */ +register TARGID { + address 0x01b + size 2 + access_mode RW +} + +/* + * Serial Port I/O Cabability register (p. 4-95 aic7860 Data Book) + * Indicates if external logic has been attached to the chip to + * perform the tasks of accessing a serial eeprom, testing termination + * strength, and performing cable detection. On the aic7860, most of + * these features are handled on chip, but on the aic7855 an attached + * aic3800 does the grunt work. + */ +register SPIOCAP { + address 0x01b + access_mode RW + bit SOFT1 0x80 + bit SOFT0 0x40 + bit SOFTCMDEN 0x20 + bit HAS_BRDCTL 0x10 /* External Board control */ + bit SEEPROM 0x08 /* External serial eeprom logic */ + bit EEPROM 0x04 /* Writable external BIOS ROM */ + bit ROM 0x02 /* Logic for accessing external ROM */ + bit SSPIOCPS 0x01 /* Termination and cable detection */ +} + +/* * SCSI Block Control (p. 3-32) * Controls Bus type and channel selection. In a twin channel configuration * addresses 0x00-0x1e are gated to the appropriate channel based on this @@ -406,7 +444,10 @@ register SBLKCTL { bit DIAGLEDON 0x40 /* Aic78X0 only */ bit AUTOFLUSHDIS 0x20 bit SELBUSB 0x08 + bit ENAB40 0x08 /* LVD transceiver active */ + bit ENAB20 0x04 /* SE/HVD transceiver active */ bit SELWIDE 0x02 + bit XCVR 0x01 /* External transceiver active */ } /* @@ -529,6 +570,19 @@ register BCTL { bit ENABLE 0x01 } +register DSCOMMAND0 { + address 0x084 + access_mode RW + bit CACHETHEN 0x80 + bit DPARCKEN 0x40 + bit MPARCKEN 0x20 + bit EXTREQLCK 0x10 + bit INTSCBRAMSEL 0x08 + bit RAMPS 0x04 + bit USCBSIZE32 0x02 + bit CIOPARCKEN 0x01 +} + /* * On the aic78X0 chips, Board Control is replaced by the DSCommand * register (p. 4-64) @@ -622,32 +676,26 @@ register INTSTAT { mask NO_IDENT 0x20|SEQINT /* no IDENTIFY after reconnect*/ mask NO_MATCH 0x30|SEQINT /* no cmd match for reconnect */ mask EXTENDED_MSG 0x40|SEQINT /* Extended message received */ - mask NO_MATCH_BUSY 0x50|SEQINT /* Couldn't find BUSY SCB */ + mask ABORT_REQUESTED 0x50|SEQINT /* Reconect of aborted SCB */ mask REJECT_MSG 0x60|SEQINT /* Reject message received */ mask BAD_STATUS 0x70|SEQINT /* Bad status from target */ mask RESIDUAL 0x80|SEQINT /* Residual byte count != 0 */ - mask ABORT_CMDCMPLT 0x91 /* - * Command tagged for abort - * completed successfully. - */ mask AWAITING_MSG 0xa0|SEQINT /* * Kernel requested to specify - * a message to this target - * (command was null), so tell - * it that it can fill the - * message buffer. - */ - mask MSG_BUFFER_BUSY 0xc0|SEQINT /* - * Sequencer wants to use the - * message buffer, but it - * already contains a message + * a message to this target + * (command was null), so tell + * it that it can fill the + * message buffer. */ - mask MSGIN_PHASEMIS 0xd0|SEQINT /* + mask TARGET_MSG_HELP 0xb0|SEQINT + mask TARGET_SYNC_CMD 0xc0|SEQINT + mask TRACEPOINT 0xd0|SEQINT + mask MSGIN_PHASEMIS 0xe0|SEQINT /* * Target changed phase on us * when we were expecting * another msgin byte. */ - mask DATA_OVERRUN 0xe0|SEQINT /* + mask DATA_OVERRUN 0xf0|SEQINT /* * Target attempted to write * beyond the bounds of its * command. @@ -665,7 +713,11 @@ register INTSTAT { register ERROR { address 0x092 access_mode RO - bit PARERR 0x08 + bit CIOPARERR 0x80 /* Ultra2 only */ + bit PCIERRSTAT 0x40 /* PCI only */ + bit MPARERR 0x20 /* PCI only */ + bit DPARERR 0x10 /* PCI only */ + bit SQPARERR 0x08 bit ILLOPCODE 0x04 bit ILLSADDR 0x02 bit ILLHADDR 0x01 @@ -677,6 +729,7 @@ register ERROR { register CLRINT { address 0x092 access_mode WO + bit CLRPARERR 0x10 /* PCI only */ bit CLRBRKADRINT 0x08 bit CLRSCSIINT 0x04 bit CLRCMDINT 0x02 @@ -686,6 +739,7 @@ register CLRINT { register DFCNTRL { address 0x093 access_mode RW + bit PRELOADEN 0x80 /* aic7890 only */ bit WIDEODD 0x40 bit SCSIEN 0x20 bit SDMAEN 0x10 @@ -700,6 +754,7 @@ register DFCNTRL { register DFSTATUS { address 0x094 access_mode RO + bit PRELOAD_AVAIL 0x80 bit DWORDEMP 0x20 bit MREQPEND 0x10 bit HDONE 0x08 @@ -762,17 +817,25 @@ register QOUTCNT { } /* + * Special Function + */ +register SFUNCT { + address 0x09f + access_mode RW +} + +/* * SCB Definition (p. 5-4) */ scb { address 0x0a0 SCB_CONTROL { size 1 - bit MK_MESSAGE 0x80 + bit TARGET_SCB 0x80 bit DISCENB 0x40 bit TAG_ENB 0x20 - bit MUST_DMAUP_SCB 0x10 - bit ABORT_SCB 0x08 + bit MK_MESSAGE 0x10 + bit ULTRAENB 0x08 bit DISCONNECTED 0x04 mask SCB_TAG_TYPE 0x03 } @@ -801,15 +864,20 @@ scb { size 4 } SCB_DATACNT { - size 3 - } - SCB_LINKED_NEXT { - size 1 + /* + * Really only 3 bytes, but padded to make + * the kernel's job easier. + */ + size 4 } SCB_CMDPTR { + alias SCB_TARGET_PHASES + alias SCB_TARGET_ID /* Byte 2 */ + bit TARGET_DATA_IN 0x1 /* In the second byte */ size 4 } SCB_CMDLEN { + alias SCB_INITIATOR_TAG size 1 } SCB_TAG { @@ -818,14 +886,29 @@ scb { SCB_NEXT { size 1 } - SCB_PREV { + SCB_SCSIRATE { + size 1 + } + SCB_SCSIOFFSET { size 1 } - SCB_BUSYTARGETS { + SCB_SPARE { + size 3 + } + SCB_CMDSTORE { + size 16 + } + SCB_CMDSTORE_BUSADDR { size 4 } + SCB_64BYTE_SPARE { + size 12 + } } +const SCB_32BYTE_SIZE 28 +const SCB_64BYTE_SIZE 48 + const SG_SIZEOF 0x08 /* sizeof(struct ahc_dma) */ /* --------------------- AHA-2840-only definitions -------------------- */ @@ -851,6 +934,117 @@ register STATUS_2840 { register DSPCISTATUS { address 0x086 + mask DFTHRSH_100 0xc0 +} + +register CCHADDR { + address 0x0E0 + size 8 +} + +register CCHCNT { + address 0x0E8 +} + +register CCSGRAM { + address 0x0E9 +} + +register CCSGADDR { + address 0x0EA +} + +register CCSGCTL { + address 0x0EB + bit CCSGDONE 0x80 + bit CCSGEN 0x08 + bit FLAG 0x02 + bit CCSGRESET 0x01 +} + +register CCSCBCNT { + address 0xEF +} + +register CCSCBCTL { + address 0x0EE + bit CCSCBDONE 0x80 + bit ARRDONE 0x40 /* SCB Array prefetch done */ + bit CCARREN 0x10 + bit CCSCBEN 0x08 + bit CCSCBDIR 0x04 + bit CCSCBRESET 0x01 +} + +register CCSCBADDR { + address 0x0ED +} + +register CCSCBRAM { + address 0xEC +} + +/* + * SCB bank address (7895/7896/97 only) + */ +register SCBBADDR { + address 0x0F0 + access_mode RW +} + +register CCSCBPTR { + address 0x0F1 +} + +register HNSCB_QOFF { + address 0x0F4 +} + +register SNSCB_QOFF { + address 0x0F6 +} + +register SDSCB_QOFF { + address 0x0F8 +} + +register QOFF_CTLSTA { + address 0x0FA + bit SCB_AVAIL 0x40 + bit SNSCB_ROLLOVER 0x20 + bit SDSCB_ROLLOVER 0x10 + mask SCB_QSIZE 0x07 + mask SCB_QSIZE_256 0x06 +} + +register DFF_THRSH { + address 0x0FB + mask WR_DFTHRSH 0x70 + mask RD_DFTHRSH 0x07 + mask RD_DFTHRSH_MIN 0x00 + mask RD_DFTHRSH_25 0x01 + mask RD_DFTHRSH_50 0x02 + mask RD_DFTHRSH_63 0x03 + mask RD_DFTHRSH_75 0x04 + mask RD_DFTHRSH_85 0x05 + mask RD_DFTHRSH_90 0x06 + mask RD_DFTHRSH_MAX 0x07 + mask WR_DFTHRSH_MIN 0x00 + mask WR_DFTHRSH_25 0x10 + mask WR_DFTHRSH_50 0x20 + mask WR_DFTHRSH_63 0x30 + mask WR_DFTHRSH_75 0x40 + mask WR_DFTHRSH_85 0x50 + mask WR_DFTHRSH_90 0x60 + mask WR_DFTHRSH_MAX 0x70 +} + +register SG_CACHEPTR { + access_mode RW + address 0x0fc + mask SG_USER_DATA 0xfc + bit LAST_SEG 0x02 + bit LAST_SEG_DONE 0x01 } register BRDCTL { @@ -863,6 +1057,12 @@ register BRDCTL { bit BRDRW 0x04 bit BRDCTL1 0x02 bit BRDCTL0 0x01 + /* 7890 Definitions */ + bit BRDDAT4 0x10 + bit BRDDAT3 0x08 + bit BRDDAT2 0x04 + bit BRDRW_ULTRA2 0x02 + bit BRDSTB_ULTRA2 0x01 } /* @@ -921,9 +1121,13 @@ scratch_ram { /* * 1 byte per target starting at this address for configuration values */ - TARG_SCRATCH { + TARG_SCSIRATE { + alias CMDSIZE_TABLE size 16 } + /* + * Bit vector of targets that have ULTRA enabled. + */ ULTRA_ENB { size 2 } @@ -934,18 +1138,16 @@ scratch_ram { size 2 } /* - * Length of pending message + * Single byte buffer used to designate the type or message + * to send to a target. */ - MSG_LEN { - size 1 - } - /* We reserve 8bytes to store outgoing messages */ MSG_OUT { - size 8 + size 1 } /* Parameters for DMA Logic */ DMAPARAMS { size 1 + bit PRELOADEN 0x80 bit WIDEODD 0x40 bit SCSIEN 0x20 bit SDMAEN 0x10 @@ -958,13 +1160,15 @@ scratch_ram { } SEQ_FLAGS { size 1 - bit RESELECTED 0x80 - bit IDENTIFY_SEEN 0x40 - bit TAGGED_SCB 0x20 - bit DPHASE 0x10 - bit PAGESCBS 0x04 - bit WIDE_BUS 0x02 - bit TWIN_BUS 0x01 + bit IDENTIFY_SEEN 0x80 + bit SCBPTR_VALID 0x40 + bit DPHASE 0x20 + /* Target flags */ + bit TARG_CMD_PENDING 0x10 + bit CMDPHASE_PENDING 0x08 + bit DPHASE_PENDING 0x04 + bit SPHASE_PENDING 0x02 + bit NO_DISCONNECT 0x01 } /* * Temporary storage for the @@ -974,27 +1178,15 @@ scratch_ram { SAVED_TCL { size 1 } + /* Working value of the number of SG segments left */ SG_COUNT { size 1 } - /* working value of SG pointer */ + /* Working value of SG pointer */ SG_NEXT { size 4 } /* - * head of list of SCBs awaiting - * selection - */ - WAITING_SCBH { - size 1 - } - SAVED_LINKPTR { - size 1 - } - SAVED_SCBPTR { - size 1 - } - /* * The last bus phase as seen by the sequencer. */ LASTPHASE { @@ -1011,23 +1203,12 @@ scratch_ram { mask P_MESGIN CDI|IOI|MSGI mask P_BUSFREE 0x01 } - MSGIN_EXT_LEN { - size 1 - } - MSGIN_EXT_OPCODE { - size 1 - } /* - * location 3, stores the last - * byte of an extended message if - * it passes the two bytes of space - * we allow now. This byte isn't - * used for anything, it just makes - * the code shorter for tossing - * extra bytes. + * head of list of SCBs awaiting + * selection */ - MSGIN_EXT_BYTES { - size 3 + WAITING_SCBH { + size 1 } /* * head of list of SCBs that are @@ -1044,18 +1225,40 @@ scratch_ram { FREE_SCBH { size 1 } + /* + * Address of the hardware scb array in the host. + */ HSCB_ADDR { size 4 } - CUR_SCBID { + /* + * Address of the 256 byte array storing the SCBID of outstanding + * untagged SCBs indexed by TCL. + */ + SCBID_ADDR { + size 4 + } + /* + * Address of the array of command descriptors used to store + * information about incoming selections. + */ + TMODE_CMDADDR { + size 4 + } + KERNEL_QINPOS { + size 1 + } + QINPOS { + size 1 + } + QOUTPOS { size 1 } /* - * Running count of commands placed in - * the QOUTFIFO. This is cleared by the - * kernel driver every FIFODEPTH commands. + * Offset into the command descriptor array for the next + * available desciptor to use. */ - CMDOUTCNT { + TMODE_CMDADDR_NEXT { size 1 } ARG_1 { @@ -1063,8 +1266,30 @@ scratch_ram { mask SEND_MSG 0x80 mask SEND_SENSE 0x40 mask SEND_REJ 0x20 + mask MSGOUT_PHASEMIS 0x10 alias RETURN_1 } + ARG_2 { + size 1 + alias RETURN_2 + } + + /* + * Snapshot of MSG_OUT taken after each message is sent. + */ + LAST_MSG { + size 1 + } + + /* + * Number of times we have filled the CCSGRAM with prefetched + * SG elements. + */ + PREFETCH_CNT { + size 1 + } + + /* * These are reserved registers in the card's scratch ram. Some of * the values are specified in the AHA2742 technical reference manual @@ -1073,7 +1298,10 @@ scratch_ram { SCSICONF { address 0x05a size 1 + bit TERM_ENB 0x80 bit RESET_SCSI 0x40 + mask HSCSIID 0x07 /* our SCSI ID */ + mask HWSCSIID 0x0f /* our SCSI ID if Wide Bus */ } HOSTCONF { address 0x05d @@ -1086,29 +1314,51 @@ scratch_ram { mask BIOSDISABLED 0x30 bit CHANNEL_B_PRIMARY 0x08 } + /* + * Per target SCSI offset values for Ultra2 controllers. + */ + TARG_OFFSET { + address 0x070 + size 16 + } } const SCB_LIST_NULL 0xff +const TARGET_CMD_CMPLT 0xfe + +const CCSGADDR_MAX 0x80 +const CCSGRAM_MAXSEGS 16 +/* Offsets into the SCBID array where different data is stored */ +const QOUTFIFO_OFFSET 0 +const QINFIFO_OFFSET 1 +const UNTAGGEDSCB_OFFSET 2 /* WDTR Message values */ -const BUS_8_BIT 0x00 +const BUS_8_BIT 0x00 const BUS_16_BIT 0x01 const BUS_32_BIT 0x02 + +/* Offset maximums */ const MAX_OFFSET_8BIT 0x0f -const MAX_OFFSET_16BIT 0x08 +const MAX_OFFSET_16BIT 0x08 +const MAX_OFFSET_ULTRA2 0x7f +const HOST_MSG 0xff + +/* Target mode command processing constants */ +const CMD_GROUP_CODE_SHIFT 0x05 +const CMD_GROUP0_BYTE_DELTA -4 +const CMD_GROUP2_BYTE_DELTA 9 +const CMD_GROUP3_BYTE_DELTA -15 +const CMD_GROUP4_BYTE_DELTA 4 +const CMD_GROUP5_BYTE_DELTA 11 + /* * Downloaded (kernel inserted) constants */ -const SCBCOUNT download /* The number of SCBs on this card */ -const COMP_SCBCOUNT download /* Two's complement of max SCBID */ -/* - * The maximum number of entries allowed in the QIN/OUTFIFO. - */ -const FIFODEPTH download /* Two's complement of SCBCOUNT */ + /* - * Mask of bits to test against when looking at the Queue Count - * registers. Works around a bug on aic7850 chips. + * Number of command descriptors in the command descriptor array. */ -const QCNTMASK download +const TMODE_NUMCMDS download diff --git a/sys/dev/aic7xxx/aic7xxx.seq b/sys/dev/aic7xxx/aic7xxx.seq index f3fad03..160f15a 100644 --- a/sys/dev/aic7xxx/aic7xxx.seq +++ b/sys/dev/aic7xxx/aic7xxx.seq @@ -1,7 +1,7 @@ /* * Adaptec 274x/284x/294x device driver firmware for Linux and FreeBSD. * - * Copyright (c) 1994-1997 Justin Gibbs. + * Copyright (c) 1994-1998 Justin Gibbs. * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -10,14 +10,11 @@ * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions, and the following disclaimer, * without modification, immediately at the beginning of the file. - * 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. The name of the author may not be used to endorse or promote products + * 2. The name of the author may not be used to endorse or promote products * derived from this software without specific prior written permission. * * Where this Software is combined with software released under the terms of - * the GNU Public License ("GPL") and the terms of the GPL would require the + * the GNU Public License (GPL) and the terms of the GPL would require the * combined work to also be released under the terms of the GPL, the terms * and conditions of this License will apply in addition to those of the * GPL with the exception of any terms or conditions of this License that @@ -35,11 +32,11 @@ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * - * $Id: aic7xxx.seq,v 1.76 1997/09/27 19:37:27 gibbs Exp $ + * $Id: aic7xxx.seq,v 1.77 1998/06/28 02:58:57 gibbs Exp $ */ #include <dev/aic7xxx/aic7xxx.reg> -#include <scsi/scsi_message.h> +#include <cam/scsi/scsi_message.h> /* * A few words on the waiting SCB list: @@ -59,36 +56,58 @@ * automatically consume the entries. */ -/* - * We assume that the kernel driver may reset us at any time, even in the - * middle of a DMA, so clear DFCNTRL too. - */ reset: clr SCSISIGO; /* De-assert BSY */ /* Always allow reselection */ - mvi SCSISEQ, ENRSELI|ENAUTOATNP; + if ((ahc->flags & AHC_TARGETMODE) != 0) { + mvi SCSISEQ, ENSELI|ENRSELI|ENAUTOATNP; + } else { + mvi SCSISEQ, ENRSELI|ENAUTOATNP; + } + + if ((ahc->features & AHC_CMD_CHAN) != 0) { + /* Ensure that no DMA operations are in progress */ + clr CCSGCTL; + clr CCSCBCTL; + } + call clear_target_state; poll_for_work: - test SSTAT0,SELDO jnz select; - test SSTAT0,SELDI jnz reselect; + and SXFRCTL0, ~SPIOEN; + if ((ahc->features & AHC_QUEUE_REGS) == 0) { + mov A, QINPOS; + } +poll_for_work_loop: + if ((ahc->features & AHC_QUEUE_REGS) == 0) { + and SEQCTL, ~PAUSEDIS; + } + test SSTAT0, SELDO|SELDI jnz selection; test SCSISEQ, ENSELO jnz poll_for_work; -.if ( TWIN_CHANNEL ) - /* - * Twin channel devices cannot handle things like SELTO - * interrupts on the "background" channel. So, if we - * are selecting, keep polling the current channel util - * either a selection or reselection occurs. - */ - xor SBLKCTL,SELBUSB; /* Toggle to the other bus */ - test SSTAT0,SELDO jnz select; - test SSTAT0,SELDI jnz reselect; - test SCSISEQ, ENSELO jnz poll_for_work; - xor SBLKCTL,SELBUSB; /* Toggle back */ -.endif + if ((ahc->features & AHC_TWIN) != 0) { + /* + * Twin channel devices cannot handle things like SELTO + * interrupts on the "background" channel. So, if we + * are selecting, keep polling the current channel util + * either a selection or reselection occurs. + */ + xor SBLKCTL,SELBUSB; /* Toggle to the other bus */ + test SSTAT0, SELDO|SELDI jnz selection; + test SCSISEQ, ENSELO jnz poll_for_work; + xor SBLKCTL,SELBUSB; /* Toggle back */ + } cmp WAITING_SCBH,SCB_LIST_NULL jne start_waiting; test_queue: /* Has the driver posted any work for us? */ - test QINCNT,QCNTMASK jz poll_for_work; + if ((ahc->features & AHC_QUEUE_REGS) != 0) { + test QOFF_CTLSTA, SCB_AVAIL jz poll_for_work_loop; + mov NONE, SNSCB_QOFF; + inc QINPOS; + } else { + or SEQCTL, PAUSEDIS; + cmp KERNEL_QINPOS, A je poll_for_work_loop; + inc QINPOS; + and SEQCTL, ~PAUSEDIS; + } /* * We have at least one queued SCB now and we don't have any @@ -96,78 +115,39 @@ test_queue: * any SCBs available for use, pull the tag from the QINFIFO * and get to work on it. */ -.if ( SCB_PAGING ) - mov ALLZEROS call get_free_or_disc_scb; - cmp SINDEX, SCB_LIST_NULL je poll_for_work; -.endif + if ((ahc->flags & AHC_PAGESCBS) != 0) { + mov ALLZEROS call get_free_or_disc_scb; + } + dequeue_scb: - mov CUR_SCBID,QINFIFO; -.if !( SCB_PAGING ) - /* In the non-paging case, the SCBID == hardware SCB index */ - mov SCBPTR, CUR_SCBID; -.endif + add A, -1, QINPOS; + mvi QINFIFO_OFFSET call fetch_byte; + + if ((ahc->flags & AHC_PAGESCBS) == 0) { + /* In the non-paging case, the SCBID == hardware SCB index */ + mov SCBPTR, RETURN_2; + } dma_queued_scb: /* * DMA the SCB from host ram into the current SCB location. */ mvi DMAPARAMS, HDMAEN|DIRECTION|FIFORESET; - mov CUR_SCBID call dma_scb; + mov RETURN_2 call dma_scb; /* - * See if there is not already an active SCB for this target. This code - * locks out on a per target basis instead of target/lun. Although this - * is not ideal for devices that have multiple luns active at the same - * time, it is faster than looping through all SCB's looking for active - * commands. We also don't have enough spare SCB space for us to store the - * SCBID of the currently busy transaction for each target/lun making it - * impossible to link up the SCBs. + * Preset the residual fields in case we never go through a data phase. + * This isn't done by the host so we can avoid a DMA to clear these + * fields for the normal case of I/O that completes without underrun + * or overrun conditions. */ -test_busy: - test SCB_CONTROL, TAG_ENB|ABORT_SCB jnz start_scb; - mvi SEQCTL, PAUSEDIS|FASTMODE; - mov SAVED_SCBPTR, SCBPTR; - mov SCB_TCL call index_untagged_scb; - mov ARG_1, SINDIR; /* - * ARG_1 should - * now have the SCB ID of - * any active, non-tagged, - * command for this target. - */ - cmp ARG_1, SCB_LIST_NULL je make_busy; -.if ( SCB_PAGING ) - /* - * Put this SCB back onto the free list. It - * may be necessary to satisfy the search for - * the active SCB. - */ - mov SCBPTR, SAVED_SCBPTR; - call add_scb_to_free_list; - /* Find the active SCB */ - mov ALLZEROS call findSCB; - /* - * If we couldn't find it, tell the kernel. This should - * never happen. - */ - cmp SINDEX, SCB_LIST_NULL jne paged_busy_link; - mvi INTSTAT, NO_MATCH_BUSY; -paged_busy_link: - /* Link us in */ - mov SCB_LINKED_NEXT, CUR_SCBID; - /* Put it back on the disconnected list */ - call add_scb_to_disc_list; - mvi SEQCTL, FASTMODE; - jmp poll_for_work; -.else -simple_busy_link: - mov SCBPTR, ARG_1; - mov SCB_LINKED_NEXT, CUR_SCBID; - mvi SEQCTL, FASTMODE; - jmp poll_for_work; -.endif -make_busy: - mov DINDIR, CUR_SCBID; - mov SCBPTR, SAVED_SCBPTR; - mvi SEQCTL, FASTMODE; + if ((ahc->features & AHC_CMD_CHAN) != 0) { + bmov SCB_RESID_DCNT, SCB_DATACNT, 3; + } else { + mov SCB_RESID_DCNT[0],SCB_DATACNT[0]; + mov SCB_RESID_DCNT[1],SCB_DATACNT[1]; + mov SCB_RESID_DCNT[2],SCB_DATACNT[2]; + } + mov SCB_RESID_SGCNT, SCB_SGCOUNT; start_scb: /* @@ -178,37 +158,243 @@ start_scb: mov WAITING_SCBH, SCBPTR; start_waiting: /* - * Pull the first entry off of the waiting SCB list - * We don't have to "test_busy" because only transactions that - * have passed that test can be in the WAITING_SCB list. + * Pull the first entry off of the waiting SCB list. */ mov SCBPTR, WAITING_SCBH; call start_selection; jmp poll_for_work; start_selection: -.if ( TWIN_CHANNEL ) - and SINDEX,~SELBUSB,SBLKCTL;/* Clear the channel select bit */ - and A,SELBUSB,SCB_TCL; /* Get new channel bit */ - or SINDEX,A; - mov SBLKCTL,SINDEX; /* select channel */ -.endif + if ((ahc->features & AHC_TWIN) != 0) { + and SINDEX,~SELBUSB,SBLKCTL;/* Clear channel select bit */ + and A,SELBUSB,SCB_TCL; /* Get new channel bit */ + or SINDEX,A; + mov SBLKCTL,SINDEX; /* select channel */ + } initialize_scsiid: - and A, TID, SCB_TCL; /* Get target ID */ - and SCSIID, OID; /* Clear old target */ - or SCSIID, A; - mvi SCSISEQ, ENSELO|ENAUTOATNO|ENRSELI|ENAUTOATNP ret; + if ((ahc->features & AHC_ULTRA2) != 0) { + and A, TID, SCB_TCL; /* Get target ID */ + and SCSIID_ULTRA2, OID; /* Clear old target */ + or SCSIID_ULTRA2, A; + } else { + and A, TID, SCB_TCL; /* Get target ID */ + and SCSIID, OID; /* Clear old target */ + or SCSIID, A; + } + if ((ahc->flags & AHC_TARGETMODE) != 0) { + and SINDEX, TARGET_SCB, SCB_CONTROL; + or SCSISEQ, ENSELO|ENAUTOATNO|ENSELI + |ENRSELI|ENAUTOATNP, SINDEX ret ; + } else { + mvi SCSISEQ, ENSELO|ENAUTOATNO|ENRSELI|ENAUTOATNP ret; + } + +/* + * Initialize transfer settings and clear the SCSI channel. + * SINDEX should contain any additional bit's the client wants + * set in SXFRCTL0. We also assume that the current SCB is + * a valid SCB for the target we wish to talk to. + */ +initialize_channel: + or SXFRCTL0, CLRSTCNT|CLRCHN, SINDEX; +set_transfer_settings: + if ((ahc->features & AHC_ULTRA) != 0) { + test SCB_CONTROL, ULTRAENB jz . + 2; + or SXFRCTL0, FAST20; + } +/* + * Initialize SCSIRATE with the appropriate value for this target. + */ + if ((ahc->features & AHC_ULTRA2) != 0) { + bmov SCSIRATE, SCB_SCSIRATE, 2 ret; + } else { + mov SCSIRATE, SCB_SCSIRATE ret; + } + +selection: + test SSTAT0,SELDO jnz select_out; + mvi CLRSINT0, CLRSELDI; +select_in: + if ((ahc->flags & AHC_TARGETMODE) != 0) { + test SSTAT0, TARGET jz initiator_reselect; + /* + * We've just been selected. Assert BSY and + * setup the phase for receiving messages + * from the target. + */ + mvi SCSISIGO, P_MESGOUT|BSYO; + + /* + * LAST_MSG gives an indication to the host of what + * went wrong should we need to terminate this selection + * before doing real work. Initialize it to SCB_LIST_NULL to + * indicate an improper initiator selection. + */ + mvi LAST_MSG, SCB_LIST_NULL; + + /* + * Setup the DMA for sending the identify and + * command information. We keep a count of the + * number of bytes to send to the host in ARG_2. + */ + or SEQ_FLAGS, CMDPHASE_PENDING; + mov A, TMODE_CMDADDR_NEXT; + if ((ahc->features & AHC_CMD_CHAN) != 0) { + mvi DINDEX, CCHADDR; + mvi TMODE_CMDADDR call set_32byte_addr; + mvi CCSCBCTL, CCSCBRESET; + } else { + mvi DINDEX, HADDR; + mvi TMODE_CMDADDR call set_32byte_addr; + mvi DFCNTRL, FIFORESET; + } + + /* Initiator that selected us */ + and SAVED_TCL, SELID_MASK, SELID; + if ((ahc->features & AHC_CMD_CHAN) != 0) { + mov CCSCBRAM, SAVED_TCL; + } else { + mov DFDAT, SAVED_TCL; + } + + /* The Target ID we were selected at */ + if ((ahc->features & AHC_MULTI_TID) != 0) { + if ((ahc->features & AHC_CMD_CHAN) != 0) { + and CCSCBRAM, 0x0f, TARGIDIN; + } else { + and DFDAT, 0x0f, TARGIDIN; + } + } else { + if ((ahc->features & AHC_CMD_CHAN) != 0) { + and CCSCBRAM, OID, SCSIID; + } else { + and DFDAT, OID, SCSIID; + } + } + + /* + * If ATN isn't asserted, the target isn't interested + * in talking to us. Go directly to bus free. + */ + test SCSISIGI, ATNI jz target_busfree; + + /* + * Watch ATN closely now as we pull in messages from the + * initiator. We follow the guidlines from section 6.5 + * of the SCSI-2 spec for what messages are allowed when. + */ + call targ_inb; + + /* + * Our first message must be one of IDENTIFY, ABORT, or + * BUS_DEVICE_RESET. + */ + test DINDEX, MSG_IDENTIFYFLAG jz more_first_messages; + /* Store for host */ + if ((ahc->features & AHC_CMD_CHAN) != 0) { + mov CCSCBRAM, DINDEX; + } else { + mov DFDAT, DINDEX; + } + mvi ARG_2, 3; + + /* Remember for disconnection decision */ + test DINDEX, MSG_IDENTIFY_DISCFLAG jnz . + 2; + /* XXX Honor per target settings too */ + or SEQ_FLAGS, NO_DISCONNECT; + + test SCSISIGI, ATNI jz ident_messages_done; + call targ_inb; + /* + * If this is a tagged request, the tagged message must + * immediately follow the identify. We test for a valid + * tag message by seeing if it is >= MSG_SIMPLE_Q_TAG and + * < MSG_IGN_WIDE_RESIDUE. + */ + add A, -MSG_SIMPLE_Q_TAG, DINDEX; + jnc ident_messages_done; + add A, -MSG_IGN_WIDE_RESIDUE, DINDEX; + jc ident_messages_done; + /* Store for host */ + if ((ahc->features & AHC_CMD_CHAN) != 0) { + mov CCSCBRAM, DINDEX; + } else { + mov DFDAT, DINDEX; + } + + /* + * If the initiator doesn't feel like providing a tag number, + * we've got a failed selection and must transition to bus + * free. + */ + test SCSISIGI, ATNI jz target_busfree; + /* + * Store the tag for the host. + */ + call targ_inb; + if ((ahc->features & AHC_CMD_CHAN) != 0) { + mov CCSCBRAM, DINDEX; + } else { + mov DFDAT, DINDEX; + } + add ARG_2, 2; + jmp ident_messages_done; + +more_first_messages: + /* + * Hmm. Now we're down to only accepting + * either an ABORT or BDR. + */ + cmp DINDEX, MSG_ABORT je . + 2; + cmp DINDEX, MSG_BUS_DEV_RESET jne target_busfree; + + /* Record the event and notify the host */ + mov LAST_MSG, DINDEX; + jmp target_busfree; + +ident_messages_done: + mvi LAST_MSG, MSG_NOOP; /* We are so far successful */ + /* Terminate the ident list */ + if ((ahc->features & AHC_CMD_CHAN) != 0) { + mvi CCSCBRAM, SCB_LIST_NULL; + } else { + mvi DFDAT, SCB_LIST_NULL; + } + or SEQ_FLAGS, TARG_CMD_PENDING; + jmp target_ITloop; + +/* + * We carefully toggle SPIOEN to allow us to return the + * message byte we receive so it can be checked prior to + * driving REQ on the bus for the next byte. + */ +targ_inb: + /* Drive REQ on the bus by enabling SCSI PIO */ + or SXFRCTL0, SPIOEN; + /* Wait for the byte */ + test SSTAT0, SPIORDY jz .; + /* Prevent our read from triggering another REQ */ + and SXFRCTL0, ~SPIOEN; + mov DINDEX, SCSIDATL ret; + } + /* * Reselection has been initiated by a target. Make a note that we've been * reselected, but haven't seen an IDENTIFY message from the target yet. */ -reselect: - clr MSG_LEN; /* Don't have anything in the mesg buffer */ - mvi CLRSINT0, CLRSELDI; +initiator_reselect: /* XXX test for and handle ONE BIT condition */ and SAVED_TCL, SELID_MASK, SELID; - or SEQ_FLAGS,RESELECTED; - jmp select2; + or SXFRCTL0, SPIOEN|CLRCHN; + mvi CLRSINT1,CLRBUSFREE; + or SIMODE1, ENBUSFREE; /* + * We aren't expecting a + * bus free, so interrupt + * the kernel driver if it + * happens. + */ + mvi MSG_OUT, MSG_NOOP; /* No message to send */ + jmp ITloop; /* * After the selection, remove this SCB from the "waiting SCB" @@ -216,106 +402,203 @@ reselect: * WAITING_SCBH. Our next pointer will be set to null the next time this * SCB is used, so don't bother with it now. */ -select: +select_out: /* Turn off the selection hardware */ - mvi SCSISEQ, ENRSELI|ENAUTOATNP; /* - * ATN on parity errors - * for "in" phases - */ + if ((ahc->flags & AHC_TARGETMODE) != 0) { + mvi SCSISEQ, ENSELI|ENRSELI|ENAUTOATNP; + } else { + mvi SCSISEQ, ENRSELI|ENAUTOATNP; + } mvi CLRSINT0, CLRSELDO; mov SCBPTR, WAITING_SCBH; mov WAITING_SCBH,SCB_NEXT; mov SAVED_TCL, SCB_TCL; + if ((ahc->flags & AHC_TARGETMODE) != 0) { + test SSTAT0, TARGET jz initiator_select; + + /* + * We've just re-selected an initiator. + * Assert BSY and setup the phase for + * sending our identify messages. + */ + mvi SCSISIGO, P_MESGIN|BSYO; + + /* + * Start out with a simple identify message. + */ + and A, LID, SCB_TCL; + or A, MSG_IDENTIFYFLAG call target_outb; + + /* + * If we are the result of a tagged command, send + * a simple Q tag and the tag id. + */ + test SCB_CONTROL, TAG_ENB jz . + 3; + mvi MSG_SIMPLE_Q_TAG call target_outb; + mov SCB_TAG call target_outb; +target_synccmd: + /* + * Now determine what phases the host wants us + * to go through. + */ + mov SEQ_FLAGS, SCB_TARGET_PHASES; + +target_ITloop: + /* + * XXX Start honoring ATN signals now that + * we properly identified ourself. + */ + test SEQ_FLAGS, CMDPHASE_PENDING jnz target_cmdphase; + test SEQ_FLAGS, DPHASE_PENDING jnz target_dphase; + test SEQ_FLAGS, SPHASE_PENDING jnz target_sphase; + + /* + * No more work to do. Either disconnect or not depending + * on the state of NO_DISCONNECT. + */ + test SEQ_FLAGS, NO_DISCONNECT jz target_disconnect; + if ((ahc->flags & AHC_PAGESCBS) != 0) { + mov ALLZEROS call get_free_or_disc_scb; + } + call complete_target_cmd; + mvi DMAPARAMS, HDMAEN|DIRECTION|FIFORESET; + mov SCB_TAG call dma_scb; + jmp target_synccmd; + +target_disconnect: + mvi SCSISIGO, P_MESGIN|BSYO; + mvi MSG_DISCONNECT call target_outb; + +target_busfree: + and SXFRCTL0, ~SPIOEN; + clr SCSISIGO; + call complete_target_cmd; + cmp LAST_MSG, MSG_NOOP je . + 2; + mvi INTSTAT, TARGET_MSG_HELP; + call clear_target_state; + jmp poll_for_work; + +target_cmdphase: + /* + * Add one for the terminating byte + * and one for the command code. + */ + if ((ahc->features & AHC_CMD_CHAN) != 0) { + add CCHCNT, 2, ARG_2; + } else { + add HCNT[0], 2, ARG_2; + clr HCNT[1]; + clr HCNT[2]; + } + mvi SCSISIGO, P_COMMAND|BSYO; + call targ_inb; + mov A, DINDEX; + /* Store for host */ + if ((ahc->features & AHC_CMD_CHAN) != 0) { + mov CCSCBRAM, A; + } else { + mov DFDAT, A; + } + + /* + * Determine the number of bytes to read + * based on the command group code using an adding + * jump table. Count is one less than the total + * since we've already fetched the first byte. + */ + shr A, CMD_GROUP_CODE_SHIFT; + add SINDEX, TARG_SCSIRATE, A; + mov A, SINDIR; + + if ((ahc->features & AHC_CMD_CHAN) != 0) { + add CCHCNT, A; + } else { + add HCNT[0], A; + } + + test A, 0xFF jz command_phase_done; +command_loop: + or SXFRCTL0, SPIOEN; + test SSTAT0, SPIORDY jz .; + cmp A, 1 jne . + 2; + and SXFRCTL0, ~SPIOEN; /* Last Byte */ + if ((ahc->features & AHC_CMD_CHAN) != 0) { + mov CCSCBRAM, SCSIDATL; + } else { + mov DFDAT, SCSIDATL; + } + dec A; + test A, 0xFF jnz command_loop; + +command_phase_done: + and SEQ_FLAGS, ~CMDPHASE_PENDING; + jmp target_ITloop; + +target_dphase: + /* + * Data direction flags are from the + * perspective of the initiator. + */ + and SXFRCTL0, ~SPIOEN; + or SXFRCTL0, CLRCHN; + test SCB_TARGET_PHASES[1], TARGET_DATA_IN jz . + 4; + mvi SCSISIGO, P_DATAIN|BSYO; + mvi LASTPHASE, P_DATAOUT; + jmp p_data; + mvi SCSISIGO, P_DATAOUT|BSYO; + mvi LASTPHASE, P_DATAIN; + jmp p_data; + +target_sphase: + mvi SCSISIGO, P_STATUS|BSYO; + mov SCB_TARGET_STATUS call target_outb; + /* XXX Watch for ATN for parity errors??? */ + mvi SCSISIGO, P_MESGIN|BSYO; + /* MSG_CMDCMPLT is 0, but we can't do an immediate of 0 */ + mov ALLZEROS call target_outb; + jmp target_busfree; + +complete_target_cmd: + test SEQ_FLAGS, TARG_CMD_PENDING jnz . + 2; + mov SCB_TAG jmp complete_post; + if ((ahc->features & AHC_CMD_CHAN) != 0) { + or CCSCBCTL, CCSCBEN|CCSCBRESET; + test CCSCBCTL, CCSCBDONE jz .; + clr CCSCBCTL; + } else { + or DFCNTRL, HDMAEN|FIFOFLUSH; + call dma_finish; + } + inc TMODE_CMDADDR_NEXT; + cmp TMODE_CMDADDR_NEXT, TMODE_NUMCMDS jne . + 2; + clr TMODE_CMDADDR_NEXT; + mvi TARGET_CMD_CMPLT jmp complete_post; + } +initiator_select: + mvi SPIOEN call initialize_channel; /* - * As soon as we get a successful selection, the target should go - * into the message out phase since we have ATN asserted. Prepare - * the message to send. - * - * Messages are stored in scratch RAM starting with a length byte - * followed by the message itself. - */ - -mk_identify: - and MSG_OUT,0x7,SCB_TCL; /* lun */ - and A,DISCENB,SCB_CONTROL; /* mask off disconnect privledge */ - or MSG_OUT,A; /* or in disconnect privledge */ - or MSG_OUT,MSG_IDENTIFYFLAG; - mvi MSG_LEN, 1; - -/* - * Send a tag message if TAG_ENB is set in the SCB control block. - * Use SCB_TAG (the position in the kernel's SCB array) as the tag value. - */ -mk_tag: - test SCB_CONTROL,TAG_ENB jz mk_message; - and MSG_OUT[1],TAG_ENB|SCB_TAG_TYPE,SCB_CONTROL; - mov MSG_OUT[2],SCB_TAG; - add MSG_LEN,2; /* update message length */ - -/* - * Interrupt the driver, and allow it to tweak the message buffer - * if it asks. + * We aren't expecting a bus free, so interrupt + * the kernel driver if it happens. */ -mk_message: - test SCB_CONTROL,MK_MESSAGE jz select2; - mvi INTSTAT,AWAITING_MSG; - -select2: mvi CLRSINT1,CLRBUSFREE; - or SIMODE1, ENBUSFREE; /* - * We aren't expecting a - * bus free, so interrupt - * the kernel driver if it - * happens. - */ -/* - * Initialize Ultra mode setting and clear the SCSI channel. - */ - or SXFRCTL0, CLRSTCNT|SPIOEN|CLRCHN; -.if ( ULTRA ) -ultra: - mvi SINDEX, ULTRA_ENB+1; - test SAVED_TCL, 0x80 jnz ultra_2; /* Target ID > 7 */ - dec SINDEX; -ultra_2: - mov FUNCTION1,SAVED_TCL; - mov A,FUNCTION1; - test SINDIR, A jz ndx_dtr; - or SXFRCTL0, FAST20; -.endif - + or SIMODE1, ENBUSFREE; /* - * Initialize SCSIRATE with the appropriate value for this target. - * The SCSIRATE settings for each target are stored in an array - * based at TARG_SCRATCH. + * As soon as we get a successful selection, the target should go + * into the message out phase since we have ATN asserted. */ -ndx_dtr: - shr A,4,SAVED_TCL; - test SBLKCTL,SELBUSB jz ndx_dtr_2; - or SAVED_TCL, SELBUSB; /* Add the channel bit while we're here */ - or A,0x08; /* Channel B entries add 8 */ -ndx_dtr_2: - add SINDEX,TARG_SCRATCH,A; - mov SCSIRATE,SINDIR; - + mvi MSG_OUT, MSG_IDENTIFYFLAG; + or SEQ_FLAGS, IDENTIFY_SEEN; /* - * Main loop for information transfer phases. If BSY is false, then - * we have a bus free condition, expected or not. Otherwise, wait - * for the target to assert REQ before checking MSG, C/D and I/O - * for the bus phase. - * + * Main loop for information transfer phases. Wait for the target + * to assert REQ before checking MSG, C/D and I/O for the bus phase. */ ITloop: - test SSTAT1,REQINIT jz ITloop; - test SSTAT1, SCSIPERR jnz ITloop; + call phase_lock; - and A,PHASE_MASK,SCSISIGI; - mov LASTPHASE,A; - mov SCSISIGO,A; + mov A, LASTPHASE; - cmp ALLZEROS,A je p_dataout; - cmp A,P_DATAIN je p_datain; + test A, ~P_DATAIN jz p_data; cmp A,P_COMMAND je p_command; cmp A,P_MESGOUT je p_mesgout; cmp A,P_STATUS je p_status; @@ -328,60 +611,88 @@ await_busfree: and SIMODE1, ~ENBUSFREE; call clear_target_state; mov NONE, SCSIDATL; /* Ack the last byte */ + and SXFRCTL0, ~SPIOEN; test SSTAT1,REQINIT|BUSFREE jz .; test SSTAT1, BUSFREE jnz poll_for_work; mvi INTSTAT, BAD_PHASE; clear_target_state: - clr DFCNTRL; + clr DFCNTRL; /* + * We assume that the kernel driver + * may reset us at any time, even + * in the middle of a DMA, so clear + * DFCNTRL too. + */ clr SCSIRATE; /* * We don't know the target we will * connect to, so default to narrow * transfers to avoid parity problems. */ - and SXFRCTL0, ~FAST20; + and SXFRCTL0, ~(FAST20); mvi LASTPHASE, P_BUSFREE; /* clear target specific flags */ - and SEQ_FLAGS,~(RESELECTED|IDENTIFY_SEEN|TAGGED_SCB|DPHASE) ret; - -p_dataout: - mvi DMAPARAMS, WIDEODD|SCSIEN|SDMAEN|HDMAEN|DIRECTION|FIFORESET; - jmp data_phase_init; + clr SEQ_FLAGS ret; /* * If we re-enter the data phase after going through another phase, the * STCNT may have been cleared, so restore it from the residual field. */ data_phase_reinit: - mvi DINDEX, STCNT; - mvi SCB_RESID_DCNT call bcopy_3; + if ((ahc->features & AHC_CMD_CHAN) != 0) { + bmov STCNT, SCB_RESID_DCNT, 3; + } else { + mvi DINDEX, STCNT; + mvi SCB_RESID_DCNT call bcopy_3; + } jmp data_phase_loop; -p_datain: - mvi DMAPARAMS, WIDEODD|SCSIEN|SDMAEN|HDMAEN|FIFORESET; -data_phase_init: +p_data: + if ((ahc->features & AHC_ULTRA2) != 0) { + mvi DMAPARAMS, PRELOADEN|SCSIEN|HDMAEN; + } else { + mvi DMAPARAMS, WIDEODD|SCSIEN|SDMAEN|HDMAEN|FIFORESET; + } + test LASTPHASE, IOI jnz . + 2; + or DMAPARAMS, DIRECTION; call assert; /* * Ensure entering a data * phase is okay - seen identify, etc. */ - + if ((ahc->features & AHC_CMD_CHAN) != 0) { + mvi CCSGADDR, CCSGADDR_MAX; + } test SEQ_FLAGS, DPHASE jnz data_phase_reinit; + /* We have seen a data phase */ + or SEQ_FLAGS, DPHASE; + /* * Initialize the DMA address and counter from the SCB. * Also set SG_COUNT and SG_NEXT in memory since we cannot * modify the values in the SCB itself until we see a * save data pointers message. */ - mvi DINDEX, HADDR; - mvi SCB_DATAPTR call bcopy_7; - - call set_stcnt_from_hcnt; - - mov SG_COUNT,SCB_SGCOUNT; - - mvi DINDEX, SG_NEXT; - mvi SCB_SGPTR call bcopy_4; + if ((ahc->features & AHC_CMD_CHAN) != 0) { + bmov HADDR, SCB_DATAPTR, 7; + } else { + mvi DINDEX, HADDR; + mvi SCB_DATAPTR call bcopy_7; + } + + if ((ahc->features & AHC_ULTRA2) == 0) { + if ((ahc->features & AHC_CMD_CHAN) != 0) { + bmov STCNT, HCNT, 3; + } else { + call set_stcnt_from_hcnt; + } + } + + if ((ahc->features & AHC_CMD_CHAN) != 0) { + bmov SG_COUNT, SCB_SGCOUNT, 5; + } else { + mvi DINDEX, SG_COUNT; + mvi SCB_SGCOUNT call bcopy_5; + } data_phase_loop: /* Guard against overruns */ @@ -393,18 +704,40 @@ data_phase_loop: * had an overrun. */ or SXFRCTL1,BITBUCKET; - mvi HCNT[0], 0xff; - mvi HCNT[1], 0xff; - mvi HCNT[2], 0xff; - call set_stcnt_from_hcnt; - + and DMAPARAMS, ~(HDMAEN|SDMAEN); + if ((ahc->features & AHC_ULTRA2) != 0) { + bmov HCNT, ALLONES, 3; + } else if ((ahc->features & AHC_CMD_CHAN) != 0) { + bmov STCNT, ALLONES, 3; + } else { + mvi STCNT[0], 0xFF; + mvi STCNT[1], 0xFF; + mvi STCNT[2], 0xFF; + } data_phase_inbounds: -/* If we are the last SG block, ensure wideodd is off. */ +/* If we are the last SG block, tell the hardware. */ cmp SG_COUNT,0x01 jne data_phase_wideodd; - and DMAPARAMS, ~WIDEODD; + if ((ahc->features & AHC_ULTRA2) != 0) { + or SG_CACHEPTR, LAST_SEG; + } else { + and DMAPARAMS, ~WIDEODD; + } data_phase_wideodd: - mov DMAPARAMS call dma; - + if ((ahc->features & AHC_ULTRA2) != 0) { + mov SINDEX, ALLONES; + mov DFCNTRL, DMAPARAMS; + test SSTAT0, SDONE jnz .;/* Wait for preload to complete */ +data_phase_dma_loop: + test SSTAT0, SDONE jnz data_phase_dma_done; + test SSTAT1,PHASEMIS jz data_phase_dma_loop; /* ie. underrun */ +data_phase_dma_phasemis: + test SSTAT0,SDONE jnz . + 2; + mov SINDEX,ALLZEROS; /* Remeber the phasemiss */ + } else { + mov DMAPARAMS call dma; + } + +data_phase_dma_done: /* Go tell the host about any overruns */ test SXFRCTL1,BITBUCKET jnz data_phase_overrun; @@ -418,11 +751,6 @@ sg_advance: dec SG_COUNT; /* one less segment to go */ test SG_COUNT, 0xff jz data_phase_finish; /* Are we done? */ - - clr A; /* add sizeof(struct scatter) */ - add SG_NEXT[0],SG_SIZEOF; - adc SG_NEXT[1],A; - /* * Load a struct scatter and set up the data address and length. * If the working value of the SG count is nonzero, then @@ -431,49 +759,105 @@ sg_advance: * This, like all DMA's, assumes little-endian host data storage. */ sg_load: - mvi DINDEX, HADDR; - mvi SG_NEXT call bcopy_4; - - mvi HCNT[0],SG_SIZEOF; - clr HCNT[1]; - clr HCNT[2]; - - or DFCNTRL, HDMAEN|DIRECTION|FIFORESET; - - call dma_finish; - -/* - * Copy data from FIFO into SCB data pointer and data count. This assumes - * that the SG segments are of the form: - * - * struct ahc_dma_seg { - * u_int32_t addr; four bytes, little-endian order - * u_int32_t len; four bytes, little endian order - * }; - */ - mvi HADDR call dfdat_in_7; + if ((ahc->features & AHC_CMD_CHAN) != 0) { + /* + * Do we have any prefetch left??? + */ + cmp CCSGADDR, CCSGADDR_MAX jne prefetched_segs_avail; + + /* + * Fetch MIN(CCSGADDR_MAX, (SG_COUNT * 8)) bytes. + */ + add A, -(CCSGRAM_MAXSEGS + 1), SG_COUNT; + mvi A, CCSGADDR_MAX; + jc . + 2; + shl A, 3, SG_COUNT; + mov CCHCNT, A; + bmov CCHADDR, SG_NEXT, 4; + mvi CCSGCTL, CCSGEN|CCSGRESET; + test CCSGCTL, CCSGDONE jz .; + and CCSGCTL, ~CCSGEN; + test CCSGCTL, CCSGEN jnz .; + mvi CCSGCTL, CCSGRESET; +prefetched_segs_avail: + bmov HADDR, CCSGRAM, 8; + } else { + mvi DINDEX, HADDR; + mvi SG_NEXT call bcopy_4; + + mvi HCNT[0],SG_SIZEOF; + clr HCNT[1]; + clr HCNT[2]; + + or DFCNTRL, HDMAEN|DIRECTION|FIFORESET; + + call dma_finish; + + /* + * Copy data from FIFO into SCB data pointer and data count. + * This assumes that the SG segments are of the form: + * struct ahc_dma_seg { + * u_int32_t addr; four bytes, little-endian order + * u_int32_t len; four bytes, little endian order + * }; + */ + mvi HADDR call dfdat_in_7; + } + + if ((ahc->features & AHC_ULTRA2) == 0) { + /* Load STCNT as well. It is a mirror of HCNT */ + if ((ahc->features & AHC_CMD_CHAN) != 0) { + bmov STCNT, HCNT, 3; + } else { + call set_stcnt_from_hcnt; + } + } + +/* Advance the SG pointer */ + clr A; /* add sizeof(struct scatter) */ + add SG_NEXT[0],SG_SIZEOF; + adc SG_NEXT[1],A; -/* Load STCNT as well. It is a mirror of HCNT */ - call set_stcnt_from_hcnt; test SSTAT1,PHASEMIS jz data_phase_loop; + /* Ensure the last seg is visable at the shaddow layer */ + if ((ahc->features & AHC_ULTRA2) != 0) { + or DFCNTRL, PRELOADEN; + } data_phase_finish: + if ((ahc->features & AHC_ULTRA2) != 0) { + call ultra2_dmafinish; + } /* * After a DMA finishes, save the SG and STCNT residuals back into the SCB * We use STCNT instead of HCNT, since it's a reflection of how many bytes * were transferred on the SCSI (as opposed to the host) bus. */ - mov SCB_RESID_DCNT[0],STCNT[0]; - mov SCB_RESID_DCNT[1],STCNT[1]; - mov SCB_RESID_DCNT[2],STCNT[2]; + if ((ahc->features & AHC_CMD_CHAN) != 0) { + bmov SCB_RESID_DCNT, STCNT, 3; + } else { + mov SCB_RESID_DCNT[0],STCNT[0]; + mov SCB_RESID_DCNT[1],STCNT[1]; + mov SCB_RESID_DCNT[2],STCNT[2]; + } mov SCB_RESID_SGCNT, SG_COUNT; - /* We have seen a data phase */ - or SEQ_FLAGS, DPHASE; + if ((ahc->features & AHC_ULTRA2) != 0) { + or SXFRCTL0, CLRSTCNT|CLRCHN; + } + if ((ahc->flags & AHC_TARGETMODE) != 0) { + test SEQ_FLAGS, DPHASE_PENDING jz . + 3; + and SEQ_FLAGS, ~DPHASE_PENDING; + jmp target_ITloop; + } jmp ITloop; data_phase_overrun: + if ((ahc->features & AHC_ULTRA2) != 0) { + call ultra2_dmafinish; + or SXFRCTL0, CLRSTCNT|CLRCHN; + } /* * Turn off BITBUCKET mode and notify the host */ @@ -481,23 +865,62 @@ data_phase_overrun: mvi INTSTAT,DATA_OVERRUN; jmp ITloop; +ultra2_dmafinish: + if ((ahc->features & AHC_ULTRA2) != 0) { + test DFCNTRL, DIRECTION jnz ultra2_dmahalt; + and DFCNTRL, ~SCSIEN; + test DFCNTRL, SCSIEN jnz .; + or DFCNTRL, FIFOFLUSH; + test DFSTATUS, FIFOEMP jz . - 1; +ultra2_dmahalt: + and DFCNTRL, ~(SCSIEN|HDMAEN); + test DFCNTRL, HDMAEN jnz .; + ret; + } + /* * Command phase. Set up the DMA registers and let 'er rip. */ p_command: call assert; -/* - * Load HADDR and HCNT. - */ - mvi DINDEX, HADDR; - mvi SCB_CMDPTR call bcopy_5; - clr HCNT[1]; - clr HCNT[2]; - - call set_stcnt_from_hcnt; - - mvi (SCSIEN|SDMAEN|HDMAEN|DIRECTION|FIFORESET) call dma; + if ((ahc->features & AHC_CMD_CHAN) != 0) { + mov HCNT[0], SCB_CMDLEN; + bmov HCNT[1], ALLZEROS, 2; + if ((ahc->features & AHC_ULTRA2) == 0) { + bmov STCNT, HCNT, 3; + } + add NONE, -17, SCB_CMDLEN; + jc dma_cmd_data; + if ((ahc->features & AHC_ULTRA2) != 0) { + mvi DFCNTRL, (PRELOADEN|SCSIEN|DIRECTION); + } else { + mvi DFCNTRL, (SCSIEN|SDMAEN|DIRECTION|FIFORESET); + } + bmov DFDAT, SCB_CMDSTORE, 16; + jmp cmd_loop; +dma_cmd_data: + bmov HADDR, SCB_CMDPTR, 4; + } else { + mvi DINDEX, HADDR; + mvi SCB_CMDPTR call bcopy_5; + clr HCNT[1]; + clr HCNT[2]; + } + + if ((ahc->features & AHC_ULTRA2) == 0) { + if ((ahc->features & AHC_CMD_CHAN) == 0) { + call set_stcnt_from_hcnt; + } + mvi DFCNTRL, (SCSIEN|SDMAEN|HDMAEN|DIRECTION|FIFORESET); + } else { + mvi DFCNTRL, (PRELOADEN|SCSIEN|HDMAEN|DIRECTION); + } +cmd_loop: + test SSTAT0, SDONE jnz . + 2; + test SSTAT1, PHASEMIS jz cmd_loop; + and DFCNTRL, ~(SCSIEN|HDMAEN|SDMAEN); + test DFCNTRL, (SCSIEN|SDMAEN|HDMAEN) jnz .; jmp ITloop; /* @@ -511,54 +934,85 @@ p_status: jmp ITloop; /* - * Message out phase. If there is not an active message, but the target - * took us into this phase anyway, build a no-op message and send it. + * Message out phase. If MSG_OUT is 0x80, build I full indentify message + * sequence and send it to the target. In addition, if the MK_MESSAGE bit + * is set in the SCB_CONTROL byte, interrupt the host and allow it to send + * it's own message. + * + * If MSG_OUT is == HOST_MSG, also interrupt the host and take a message. + * This is done to allow the hsot to send messages outside of an identify + * sequence while protecting the seqencer from testing the MK_MESSAGE bit + * on an SCB that might not be for the current nexus. (For example, a + * BDR message in responce to a bad reselection would leave us pointed to + * an SCB that doesn't have anything to do with the current target). + * Otherwise, treat MSG_OUT as a 1 byte message to send (abort, abort tag, + * bus device reset). + * + * When there are no messages to send, MSG_OUT should be set to MSG_NOOP, + * in case the target decides to put us in this phase for some strange + * reason. */ p_mesgout: - test MSG_LEN, 0xff jnz p_mesgout_start; - mvi MSG_NOOP call mk_mesg; /* build NOP message */ -p_mesgout_start: + mov SINDEX, MSG_OUT; + cmp SINDEX, MSG_IDENTIFYFLAG jne p_mesgout_from_host; +p_mesgout_identify: + if ((ahc->features & AHC_WIDE) != 0) { + and SINDEX,0xf,SCB_TCL; /* lun */ + } else { + and SINDEX,0x7,SCB_TCL; /* lun */ + } + and A,DISCENB,SCB_CONTROL; /* mask off disconnect privledge */ + or SINDEX,A; /* or in disconnect privledge */ + or SINDEX,MSG_IDENTIFYFLAG; +p_mesgout_mk_message: + test SCB_CONTROL,MK_MESSAGE jz p_mesgout_tag; + mov SCSIDATL, SINDEX; /* Send the last byte */ + jmp p_mesgout_from_host + 1;/* Skip HOST_MSG test */ /* - * Set up automatic PIO transfer from MSG_OUT. Bit 3 in - * SXFRCTL0 (SPIOEN) is already on. + * Send a tag message if TAG_ENB is set in the SCB control block. + * Use SCB_TAG (the position in the kernel's SCB array) as the tag value. */ - mvi SINDEX,MSG_OUT; - mov DINDEX,MSG_LEN; - +p_mesgout_tag: + test SCB_CONTROL,TAG_ENB jz p_mesgout_onebyte; + mov SCSIDATL, SINDEX; /* Send the identify message */ + call phase_lock; + cmp LASTPHASE, P_MESGOUT jne p_mesgout_done; + and SCSIDATL,TAG_ENB|SCB_TAG_TYPE,SCB_CONTROL; + call phase_lock; + cmp LASTPHASE, P_MESGOUT jne p_mesgout_done; + mov SCB_TAG jmp p_mesgout_onebyte; /* - * When target asks for a byte, drop ATN if it's the last one in - * the message. Otherwise, keep going until the message is exhausted. - * ATN must be dropped *at least* 90ns before we ack the last byte, so - * the code is aranged to execute two instructions before the byte is - * transferred to give a good margin of safety - * - * Keep an eye out for a phase change, in case the target issues - * a MESSAGE REJECT. + * Interrupt the driver, and allow it to send a message + * if it asks. */ -p_mesgout_loop: - test SSTAT1, REQINIT jz p_mesgout_loop; - test SSTAT1, SCSIPERR jnz p_mesgout_loop; - and LASTPHASE, PHASE_MASK, SCSISIGI; - cmp LASTPHASE, P_MESGOUT jne p_mesgout_done; -p_mesgout_testretry: - test DINDEX,0xff jnz p_mesgout_dropatn; - or SCSISIGO,ATNO,LASTPHASE;/* turn on ATN for the retry */ - jmp p_mesgout_start; +p_mesgout_from_host: + cmp SINDEX, HOST_MSG jne p_mesgout_onebyte; + mvi INTSTAT,AWAITING_MSG; + nop; + /* + * Did the host detect a phase change? + */ + cmp RETURN_1, MSGOUT_PHASEMIS je p_mesgout_done; + +p_mesgout_onebyte: + mvi CLRSINT1, CLRATNO; + mov SCSIDATL, SINDEX; + /* * If the next bus phase after ATN drops is a message out, it means * that the target is requesting that the last message(s) be resent. */ -p_mesgout_dropatn: - cmp DINDEX,1 jne p_mesgout_outb; /* last byte? */ - mvi CLRSINT1,CLRATNO; /* drop ATN */ -p_mesgout_outb: - dec DINDEX; - mov SCSIDATL,SINDIR; - jmp p_mesgout_loop; + call phase_lock; + cmp LASTPHASE, P_MESGOUT jne p_mesgout_done; + or SCSISIGO,ATNO,LASTPHASE;/* turn on ATN for the retry */ + jmp p_mesgout; p_mesgout_done: mvi CLRSINT1,CLRATNO; /* Be sure to turn ATNO off */ - clr MSG_LEN; /* no active msg */ + mov LAST_MSG, MSG_OUT; + cmp MSG_OUT, MSG_IDENTIFYFLAG jne . + 2; + and SCB_CONTROL, ~MK_MESSAGE; + mvi MSG_OUT, MSG_NOOP; /* No message left */ jmp ITloop; /* @@ -595,8 +1049,8 @@ mesgin_complete: /* * We got a "command complete" message, so put the SCB_TAG into the QOUTFIFO, * and trigger a completion interrupt. Before doing so, check to see if there - * is a residual or the status byte is something other than NO_ERROR (0). In - * either of these conditions, we upload the SCB back to the host so it can + * is a residual or the status byte is something other than STATUS_GOOD (0). + * In either of these conditions, we upload the SCB back to the host so it can * process this information. In the case of a non zero status byte, we * additionally interrupt the kernel driver synchronously, allowing it to * decide if sense should be retrieved. If the kernel driver wishes to request @@ -614,59 +1068,18 @@ mesgin_complete: * First check for residuals */ test SCB_RESID_SGCNT,0xff jnz upload_scb; - test SCB_TARGET_STATUS,0xff jz status_ok; /* Good Status? */ + test SCB_TARGET_STATUS,0xff jz complete; /* Good Status? */ upload_scb: mvi DMAPARAMS, FIFORESET; mov SCB_TAG call dma_scb; check_status: - test SCB_TARGET_STATUS,0xff jz status_ok; /* Just a residual? */ + test SCB_TARGET_STATUS,0xff jz complete; /* Just a residual? */ mvi INTSTAT,BAD_STATUS; /* let driver know */ - cmp RETURN_1, SEND_SENSE jne status_ok; + nop; + cmp RETURN_1, SEND_SENSE jne complete; /* This SCB becomes the next to execute as it will retrieve sense */ - mov SCB_LINKED_NEXT, SCB_TAG; - jmp dma_next_scb; - -status_ok: -/* First, mark this target as free. */ - test SCB_CONTROL,TAG_ENB jnz complete; /* - * Tagged commands - * don't busy the - * target. - */ - mov SAVED_SCBPTR, SCBPTR; - mov SAVED_LINKPTR, SCB_LINKED_NEXT; - mov SCB_TCL call index_untagged_scb; - mov DINDIR, SAVED_LINKPTR; - mov SCBPTR, SAVED_SCBPTR; - -complete: - /* Post the SCB and issue an interrupt */ -.if ( SCB_PAGING ) - /* - * Spin loop until there is space - * in the QOUTFIFO. - */ - cmp CMDOUTCNT, FIFODEPTH je .; - inc CMDOUTCNT; -.endif - mov QOUTFIFO,SCB_TAG; - mvi INTSTAT,CMDCMPLT; - test SCB_CONTROL, ABORT_SCB jz dma_next_scb; - mvi INTSTAT, ABORT_CMDCMPLT; - -dma_next_scb: - cmp SCB_LINKED_NEXT, SCB_LIST_NULL je add_to_free_list; -.if !( SCB_PAGING ) - /* Only DMA on top of ourselves if we are the SCB to download */ - mov A, SCB_LINKED_NEXT; - cmp SCB_TAG, A je dma_next_scb2; - call add_scb_to_free_list; - mov SCBPTR, A; - jmp add_to_waiting_list; -.endif -dma_next_scb2: mvi DMAPARAMS, HDMAEN|DIRECTION|FIFORESET; - mov SCB_LINKED_NEXT call dma_scb; + mov SCB_TAG call dma_scb; add_to_waiting_list: mov SCB_NEXT,WAITING_SCBH; mov WAITING_SCBH, SCBPTR; @@ -676,10 +1089,41 @@ add_to_waiting_list: */ call start_selection; jmp await_busfree; + +complete: + /* If we are untagged, clear our address up in host ram */ + test SCB_CONTROL, TAG_ENB jnz complete_queue; + mov A, SAVED_TCL; + mvi UNTAGGEDSCB_OFFSET call post_byte_setup; + mvi SCB_LIST_NULL call post_byte; + +complete_queue: + mov SCB_TAG call complete_post; + add_to_free_list: call add_scb_to_free_list; jmp await_busfree; +complete_post: + /* Post the SCBID in SINDEX and issue an interrupt */ + mov ARG_1, SINDEX; + if ((ahc->features & AHC_QUEUE_REGS) != 0) { + mov A, SDSCB_QOFF; + } else { + mov A, QOUTPOS; + } + mvi QOUTFIFO_OFFSET call post_byte_setup; + mov ARG_1 call post_byte; + if ((ahc->features & AHC_QUEUE_REGS) == 0) { + inc QOUTPOS; + } + if ((ahc->flags & AHC_TARGETMODE) != 0) { + test SEQ_FLAGS, NO_DISCONNECT jz . + 3; + mvi INTSTAT,TARGET_SYNC_CMD|CMDCMPLT; + ret; + } + mvi INTSTAT,CMDCMPLT ret; + /* * Is it an extended message? Copy the message to our message buffer and * notify the host. The host will tell us whether to reject this message, @@ -687,22 +1131,8 @@ add_to_free_list: * or simply to do nothing. */ mesgin_extended: - mvi MSGIN_EXT_LEN call inb_next; - mov A, MSGIN_EXT_LEN; -mesgin_extended_loop: - mov DINDEX call inb_next; - dec A; - cmp DINDEX, MSGIN_EXT_BYTES+3 jne mesgin_extended_loop_test; - dec DINDEX; /* dump by repeatedly filling the last byte */ -mesgin_extended_loop_test: - test A, 0xFF jnz mesgin_extended_loop; -mesgin_extended_intr: mvi INTSTAT,EXTENDED_MSG; /* let driver know */ - cmp RETURN_1,SEND_REJ je rej_mesgin; - cmp RETURN_1,SEND_MSG jne mesgin_done; -/* The kernel has setup a message to be sent */ - or SCSISIGO,ATNO,LASTPHASE; /* turn on ATNO */ - jmp mesgin_done; + jmp ITloop; /* * Is it a disconnect message? Set a flag in the SCB to remind us @@ -710,9 +1140,7 @@ mesgin_extended_intr: */ mesgin_disconnect: or SCB_CONTROL,DISCONNECTED; -.if ( SCB_PAGING ) call add_scb_to_disc_list; -.endif jmp await_busfree; /* @@ -724,21 +1152,25 @@ mesgin_disconnect: */ mesgin_sdptrs: test SEQ_FLAGS, DPHASE jz mesgin_done; - mov SCB_SGCOUNT,SG_COUNT; - /* The SCB SGPTR becomes the next one we'll download */ - mvi DINDEX, SCB_SGPTR; - mvi SG_NEXT call bcopy_4; + /* + * The SCB SGPTR becomes the next one we'll download, + * and the SCB DATAPTR becomes the current SHADDR. + * Use the residual number since STCNT is corrupted by + * any message transfer. + */ + if ((ahc->features & AHC_CMD_CHAN) != 0) { + bmov SCB_SGCOUNT, SG_COUNT, 5; + bmov SCB_DATAPTR, SHADDR, 4; + bmov SCB_DATACNT, SCB_RESID_DCNT, 3; + } else { + mvi DINDEX, SCB_SGCOUNT; + mvi SG_COUNT call bcopy_5; - /* The SCB DATAPTR0 becomes the current SHADDR */ - mvi DINDEX, SCB_DATAPTR; - mvi SHADDR call bcopy_4; - -/* - * Use the residual number since STCNT is corrupted by any message transfer. - */ - mvi SCB_RESID_DCNT call bcopy_3; - + mvi DINDEX, SCB_DATAPTR; + mvi SHADDR call bcopy_4; + mvi SCB_RESID_DCNT call bcopy_3; + } jmp mesgin_done; /* @@ -761,24 +1193,32 @@ mesgin_rdptrs: * clearing the "disconnected" bit so we don't "find" it by accident later. */ mesgin_identify: - test A,0x78 jnz rej_mesgin; /*!DiscPriv|!LUNTAR|!Reserved*/ - and A,0x07; /* lun in lower three bits */ + + if ((ahc->features & AHC_WIDE) != 0) { + and A,0x0f; /* lun in lower four bits */ + } else { + and A,0x07; /* lun in lower three bits */ + } or SAVED_TCL,A; /* SAVED_TCL should be complete now */ - mov SAVED_TCL call index_untagged_scb; - mov ARG_1, SINDIR; -.if ( SCB_PAGING ) - cmp ARG_1,SCB_LIST_NULL jne use_findSCB; -.else - cmp ARG_1,SCB_LIST_NULL je snoop_tag; - /* Directly index the SCB */ - mov SCBPTR,ARG_1; - test SCB_CONTROL,DISCONNECTED jz not_found; + + mvi ARG_2, SCB_LIST_NULL; /* SCBID of prev SCB in disc List */ + call get_untagged_SCBID; + cmp ARG_1, SCB_LIST_NULL je snoop_tag; + if ((ahc->flags & AHC_PAGESCBS) != 0) { + test SEQ_FLAGS, SCBPTR_VALID jz use_retrieveSCB; + } + /* + * If the SCB was found in the disconnected list (as is + * always the case in non-paging scenarios), SCBPTR is already + * set to the correct SCB. So, simply setup the SCB and get + * on with things. + */ + call rem_scb_from_disc_list; jmp setup_SCB; -.endif /* * Here we "snoop" the bus looking for a SIMPLE QUEUE TAG message. * If we get one, we use the tag returned to find the proper - * SCB. With SCB paging, this requires using findSCB for both tagged + * SCB. With SCB paging, this requires using search for both tagged * and non-tagged transactions since the SCB may exist in any slot. * If we're not using SCB paging, we can use the tag as the direct * index to the SCB. @@ -786,43 +1226,36 @@ mesgin_identify: snoop_tag: mov NONE,SCSIDATL; /* ACK Identify MSG */ snoop_tag_loop: - test SSTAT1,REQINIT jz snoop_tag_loop; - test SSTAT1, SCSIPERR jnz snoop_tag_loop; - and LASTPHASE, PHASE_MASK, SCSISIGI; + call phase_lock; cmp LASTPHASE, P_MESGIN jne not_found; cmp SCSIBUSL,MSG_SIMPLE_Q_TAG jne not_found; get_tag: - or SEQ_FLAGS, TAGGED_SCB; mvi ARG_1 call inb_next; /* tag value */ -/* - * See if the tag is in range. The tag is < SCBCOUNT if we add - * the complement of SCBCOUNT to the incomming tag and there is - * no carry. - */ - add SINDEX,COMP_SCBCOUNT,ARG_1; - jc not_found; -.if ! ( SCB_PAGING ) -index_by_tag: - mov SCBPTR,ARG_1; - mov A, SAVED_TCL; - cmp SCB_TCL,A jne not_found; - test SCB_CONTROL,TAG_ENB jz not_found; - test SCB_CONTROL,DISCONNECTED jz not_found; -.else -/* - * Ensure that the SCB the tag points to is for an SCB transaction - * to the reconnecting target. - */ -use_findSCB: - mov ALLZEROS call findSCB; /* Have to search */ - cmp SINDEX, SCB_LIST_NULL je not_found; -.endif + /* + * Ensure that the SCB the tag points to is for + * an SCB transaction to the reconnecting target. + */ +use_retrieveSCB: + call retrieveSCB; setup_SCB: + mov A, SAVED_TCL; + cmp SCB_TCL, A jne not_found_cleanup_scb; + test SCB_CONTROL,DISCONNECTED jz not_found_cleanup_scb; and SCB_CONTROL,~DISCONNECTED; or SEQ_FLAGS,IDENTIFY_SEEN; /* make note of IDENTIFY */ + call set_transfer_settings; + /* See if the host wants to send a message upon reconnection */ + test SCB_CONTROL, MK_MESSAGE jz mesgin_done; + and SCB_CONTROL, ~MK_MESSAGE; + mvi HOST_MSG call mk_mesg; jmp mesgin_done; +not_found_cleanup_scb: + test SCB_CONTROL, DISCONNECTED jz . + 3; + call add_scb_to_disc_list; + jmp not_found; + call add_scb_to_free_list; not_found: mvi INTSTAT, NO_MATCH; mvi MSG_BUS_DEV_RESET call mk_mesg; @@ -847,23 +1280,8 @@ mesgin_reject: * if there is no active message already. SINDEX is returned intact. */ mk_mesg: - mvi SEQCTL, PAUSEDIS|FASTMODE; - test MSG_LEN,0xff jz mk_mesg1; /* Should always succeed */ - - /* - * Hmmm. For some reason the mesg buffer is in use. - * Tell the driver. It should look at SINDEX to find - * out what we wanted to use the buffer for and resolve - * the conflict. - */ - mvi SEQCTL,FASTMODE; - mvi INTSTAT,MSG_BUFFER_BUSY; - -mk_mesg1: or SCSISIGO,ATNO,LASTPHASE;/* turn on ATNO */ - mvi MSG_LEN,1; /* length = 1 */ - mov MSG_OUT,SINDEX; /* 1-byte message */ - mvi SEQCTL,FASTMODE ret; + mov MSG_OUT,SINDEX ret; /* * Functions to read data in Automatic PIO mode. @@ -899,6 +1317,19 @@ inb_first: inb_last: mov NONE,SCSIDATL ret; /*dummy read from latch to ACK*/ +if ((ahc->flags & AHC_TARGETMODE) != 0) { + /* + * Send a byte to an initiator in Automatic PIO mode. + * SPIOEN must be on prior to calling this routine. + */ +target_outb: + or SXFRCTL0, SPIOEN; + test SSTAT0, SPIORDY jz .; + mov SCSIDATL, SINDEX; + test SSTAT0, SPIORDY jz .; + ret; +} + mesgin_phasemis: /* * We expected to receive another byte, but the target changed phase @@ -951,8 +1382,10 @@ dma_halt: * to drain the data fifo until there is space for the input * latch to drain and HDMAEN de-asserts. */ - mov NONE, DFDAT; - test DFCNTRL, HDMAEN jnz dma_halt; + if ((ahc->features & AHC_ULTRA2) == 0) { + mov NONE, DFDAT; + } + test DFCNTRL, (SCSIEN|SDMAEN|HDMAEN) jnz dma_halt; return: ret; @@ -961,60 +1394,162 @@ return: * message. */ assert: - test SEQ_FLAGS,RESELECTED jz return; /* reselected? */ test SEQ_FLAGS,IDENTIFY_SEEN jnz return; /* seen IDENTIFY? */ mvi INTSTAT,NO_IDENT ret; /* no - tell the kernel */ -.if ( SCB_PAGING ) /* * Locate a disconnected SCB either by SAVED_TCL (ARG_1 is SCB_LIST_NULL) - * or by the SCBIDn ARG_1. The search begins at the SCB index passed in - * via SINDEX. If the SCB cannot be found, SINDEX will be SCB_LIST_NULL, - * otherwise, SCBPTR is set to the proper SCB. + * or by the SCBID ARG_1. The search begins at the SCB index passed in + * via SINDEX which is an SCB that must be on the disconnected list. If + * the SCB cannot be found, SINDEX will be SCB_LIST_NULL, otherwise, SCBPTR + * is set to the proper SCB. */ findSCB: - mov SCBPTR,SINDEX; /* switch to next SCB */ + mov SCBPTR,SINDEX; /* Initialize SCBPTR */ + cmp ARG_1, SCB_LIST_NULL jne findSCB_by_SCBID; + mov A, SAVED_TCL; + mvi SCB_TCL jmp findSCB_loop; /* &SCB_TCL -> SINDEX */ +findSCB_by_SCBID: mov A, ARG_1; /* Tag passed in ARG_1 */ - cmp SCB_TAG,A jne findSCB_loop; - test SCB_CONTROL,DISCONNECTED jnz foundSCB;/*should be disconnected*/ + mvi SCB_TAG jmp findSCB_loop; /* &SCB_TAG -> SINDEX */ +findSCB_next: + mov ARG_2, SCBPTR; + cmp SCB_NEXT, SCB_LIST_NULL je notFound; + mov SCBPTR,SCB_NEXT; + dec SINDEX; /* Last comparison moved us too far */ findSCB_loop: - inc SINDEX; - cmp SINDEX,SCBCOUNT jne findSCB; + cmp SINDIR, A jne findSCB_next; + mov SINDEX, SCBPTR ret; +notFound: + mvi SINDEX, SCB_LIST_NULL ret; + /* - * We didn't find it. If we're paging, pull an SCB and DMA down the - * one we want. If we aren't paging or the SCB we dma down has the - * abort flag set, return not found. + * Retrieve an SCB by SCBID first searching the disconnected list falling + * back to DMA'ing the SCB down from the host. This routine assumes that + * ARG_1 is the SCBID of interrest and that SINDEX is the position in the + * disconnected list to start the search from. If SINDEX is SCB_LIST_NULL, + * we go directly to the host for the SCB. + */ +retrieveSCB: + test SEQ_FLAGS, SCBPTR_VALID jz retrieve_from_host; + mov SCBPTR call findSCB; /* Continue the search */ + cmp SINDEX, SCB_LIST_NULL je retrieve_from_host; + +/* + * This routine expects SINDEX to contain the index of the SCB to be + * removed, SCBPTR to be pointing to that SCB, and ARG_2 to be the + * SCBID of the SCB just previous to this one in the list or SCB_LIST_NULL + * if it is at the head. */ - mov ALLZEROS call get_free_or_disc_scb; - mvi DMAPARAMS, HDMAEN|DIRECTION|FIFORESET; - mov ARG_1 call dma_scb; - test SCB_RESID_SGCNT, 0xff jz . + 2; - or SCB_CONTROL, MUST_DMAUP_SCB; - test SCB_CONTROL, ABORT_SCB jz return; -find_error: - mvi SINDEX, SCB_LIST_NULL ret; -foundSCB: - test SCB_CONTROL, ABORT_SCB jnz find_error; rem_scb_from_disc_list: /* Remove this SCB from the disconnection list */ - cmp SCB_NEXT,SCB_LIST_NULL je unlink_prev; - mov SAVED_LINKPTR, SCB_PREV; - mov SCBPTR, SCB_NEXT; - mov SCB_PREV, SAVED_LINKPTR; - mov SCBPTR, SINDEX; -unlink_prev: - cmp SCB_PREV,SCB_LIST_NULL je rHead;/* At the head of the list */ - mov SAVED_LINKPTR, SCB_NEXT; - mov SCBPTR, SCB_PREV; - mov SCB_NEXT, SAVED_LINKPTR; + cmp ARG_2, SCB_LIST_NULL je rHead; + mov DINDEX, SCB_NEXT; + mov SCBPTR, ARG_2; + mov SCB_NEXT, DINDEX; mov SCBPTR, SINDEX ret; rHead: mov DISCONNECTED_SCBH,SCB_NEXT ret; -.else - ret; -.endif +retrieve_from_host: +/* + * We didn't find it. Pull an SCB and DMA down the one we want. + * We should never get here in the non-paging case. + */ + mov ALLZEROS call get_free_or_disc_scb; + mvi DMAPARAMS, HDMAEN|DIRECTION|FIFORESET; + /* Jump instead of call as we want to return anyway */ + mov ARG_1 jmp dma_scb; + +/* + * Determine whether a target is using tagged or non-tagged transactions + * by first looking for a matching transaction based on the TCL and if + * that fails, looking up this device in the host's untagged SCB array. + * The TCL to search for is assumed to be in SAVED_TCL. The value is + * returned in ARG_1 (SCB_LIST_NULL for tagged, SCBID for non-tagged). + * The SCBPTR_VALID bit is set in SEQ_FLAGS if we found the information + * in an SCB instead of having to go to the host. + */ +get_untagged_SCBID: + cmp DISCONNECTED_SCBH, SCB_LIST_NULL je get_SCBID_from_host; + mvi ARG_1, SCB_LIST_NULL; + mov DISCONNECTED_SCBH call findSCB; + cmp SINDEX, SCB_LIST_NULL je get_SCBID_from_host; + or SEQ_FLAGS, SCBPTR_VALID;/* Was in disconnected list */ + test SCB_CONTROL, TAG_ENB jnz . + 2; + mov ARG_1, SCB_TAG ret; + mvi ARG_1, SCB_LIST_NULL ret; + +/* + * Fetch a byte from host memory given an index of (A + (256 * SINDEX)) + * and a base address of SCBID_ADDR. The byte is returned in RETURN_2. + */ +fetch_byte: + mov ARG_2, SINDEX; + if ((ahc->features & AHC_CMD_CHAN) != 0) { + mvi DINDEX, CCHADDR; + mvi SCBID_ADDR call set_1byte_addr; + mvi CCHCNT, 1; + mvi CCSGCTL, CCSGEN|CCSGRESET; + test CCSGCTL, CCSGDONE jz .; + mvi CCSGCTL, CCSGRESET; + bmov RETURN_2, CCSGRAM, 1 ret; + } else { + mvi DINDEX, HADDR; + mvi SCBID_ADDR call set_1byte_addr; + mvi HCNT[0], 1; + clr HCNT[1]; + clr HCNT[2]; + mvi DFCNTRL, HDMAEN|DIRECTION|FIFORESET; + call dma_finish; + mov RETURN_2, DFDAT ret; + } + +/* + * Prepare the hardware to post a byte to host memory given an + * index of (A + (256 * SINDEX)) and a base address of SCBID_ADDR. + */ +post_byte_setup: + mov ARG_2, SINDEX; + if ((ahc->features & AHC_CMD_CHAN) != 0) { + mvi DINDEX, CCHADDR; + mvi SCBID_ADDR call set_1byte_addr; + mvi CCHCNT, 1; + mvi CCSCBCTL, CCSCBRESET ret; + } else { + mvi DINDEX, HADDR; + mvi SCBID_ADDR call set_1byte_addr; + mvi HCNT[0], 1; + clr HCNT[1]; + clr HCNT[2]; + mvi DFCNTRL, FIFORESET ret; + } + +post_byte: + if ((ahc->features & AHC_CMD_CHAN) != 0) { + bmov CCSCBRAM, SINDEX, 1; + or CCSCBCTL, CCSCBEN|CCSCBRESET; + test CCSCBCTL, CCSCBDONE jz .; + clr CCSCBCTL ret; + } else { + mov DFDAT, SINDEX; + or DFCNTRL, HDMAEN|FIFOFLUSH; + jmp dma_finish; + } + +get_SCBID_from_host: + mov A, SAVED_TCL; + mvi UNTAGGEDSCB_OFFSET call fetch_byte; + mov RETURN_1, RETURN_2 ret; + +phase_lock: + test SSTAT1, REQINIT jz phase_lock; + test SSTAT1, SCSIPERR jnz phase_lock; + and LASTPHASE, PHASE_MASK, SCSISIGI; + mov SCSISIGO, LASTPHASE ret; + +if ((ahc->features & AHC_CMD_CHAN) == 0) { set_stcnt_from_hcnt: mov STCNT[0], HCNT[0]; mov STCNT[1], HCNT[1]; @@ -1031,58 +1566,120 @@ bcopy_3: mov DINDIR, SINDIR; mov DINDIR, SINDIR; mov DINDIR, SINDIR ret; +} -dma_scb: - /* - * SCB index is in SINDEX. Determine the physical address in - * the host where this SCB is located and load HADDR with it. - */ - shr DINDEX, 3, SINDEX; - shl A, 5, SINDEX; - add HADDR[0], A, HSCB_ADDR[0]; - mov A, DINDEX; - adc HADDR[1], A, HSCB_ADDR[1]; +if ((ahc->flags & AHC_TARGETMODE) != 0) { +/* + * Setup addr assuming that A is an index into + * an array of 32byte objects, SINDEX contains + * the base address of that array, and DINDEX + * contains the base address of the location + * to store the indexed address. + */ +set_32byte_addr: + shr ARG_2, 3, A; + shl A, 5; + jmp set_1byte_addr; +} + +/* + * Setup addr assuming that A is an index into + * an array of 64byte objects, SINDEX contains + * the base address of that array, and DINDEX + * contains the base address of the location + * to store the indexed address. + */ +set_64byte_addr: + shr ARG_2, 2, A; + shl A, 6; + +/* + * Setup addr assuming that A + (ARG_1 * 256) is an + * index into an array of 1byte objects, SINDEX contains + * the base address of that array, and DINDEX contains + * the base address of the location to store the computed + * address. + */ +set_1byte_addr: + add DINDIR, A, SINDIR; + mov A, ARG_2; + adc DINDIR, A, SINDIR; clr A; - adc HADDR[2], A, HSCB_ADDR[2]; - adc HADDR[3], A, HSCB_ADDR[3]; - /* Setup Count */ - mvi HCNT[0], 28; - clr HCNT[1]; - clr HCNT[2]; - mov DFCNTRL, DMAPARAMS; - test DMAPARAMS, DIRECTION jnz dma_scb_fromhost; - /* Fill it with the SCB data */ + adc DINDIR, A, SINDIR; + adc DINDIR, A, SINDIR ret; + +/* + * Either post or fetch and SCB from host memory based on the + * DIRECTION bit in DMAPARAMS. The host SCB index is in SINDEX. + */ +dma_scb: + mov A, SINDEX; + if ((ahc->features & AHC_CMD_CHAN) != 0) { + mvi DINDEX, CCHADDR; + mvi HSCB_ADDR call set_64byte_addr; + mov CCSCBPTR, SCBPTR; + test DMAPARAMS, DIRECTION jz dma_scb_tohost; + mvi CCHCNT, SCB_64BYTE_SIZE; + mvi CCSCBCTL, CCARREN|CCSCBEN|CCSCBDIR|CCSCBRESET; + cmp CCSCBCTL, CCSCBDONE|ARRDONE|CCARREN|CCSCBEN|CCSCBDIR jne .; + jmp dma_scb_finish; +dma_scb_tohost: + mvi CCHCNT, SCB_32BYTE_SIZE; + if ((ahc->chip & AHC_CHIPID_MASK) == AHC_AIC7895) { + mvi CCSCBCTL, CCSCBRESET; + bmov CCSCBRAM, SCB_CONTROL, SCB_32BYTE_SIZE; + or CCSCBCTL, CCSCBEN|CCSCBRESET; + test CCSCBCTL, CCSCBDONE jz .; + } else { + mvi CCSCBCTL, CCARREN|CCSCBEN|CCSCBRESET; + cmp CCSCBCTL, CCSCBDONE|ARRDONE|CCARREN|CCSCBEN jne .; + } +dma_scb_finish: + clr CCSCBCTL; + test CCSCBCTL, CCARREN|CCSCBEN jnz .; + ret; + } else { + mvi DINDEX, HADDR; + mvi HSCB_ADDR call set_64byte_addr; + mvi HCNT[0], SCB_32BYTE_SIZE; + clr HCNT[1]; + clr HCNT[2]; + mov DFCNTRL, DMAPARAMS; + test DMAPARAMS, DIRECTION jnz dma_scb_fromhost; + /* Fill it with the SCB data */ copy_scb_tofifo: - mvi SINDEX, SCB_CONTROL; - add A, 28, SINDEX; + mvi SINDEX, SCB_CONTROL; + add A, SCB_32BYTE_SIZE, SINDEX; copy_scb_tofifo_loop: - mov DFDAT,SINDIR; - mov DFDAT,SINDIR; - mov DFDAT,SINDIR; - mov DFDAT,SINDIR; - mov DFDAT,SINDIR; - mov DFDAT,SINDIR; - mov DFDAT,SINDIR; - cmp SINDEX, A jne copy_scb_tofifo_loop; - or DFCNTRL, HDMAEN|FIFOFLUSH; + mov DFDAT,SINDIR; + mov DFDAT,SINDIR; + mov DFDAT,SINDIR; + mov DFDAT,SINDIR; + mov DFDAT,SINDIR; + mov DFDAT,SINDIR; + mov DFDAT,SINDIR; + cmp SINDEX, A jne copy_scb_tofifo_loop; + or DFCNTRL, HDMAEN|FIFOFLUSH; dma_scb_fromhost: - call dma_finish; - /* If we were putting the SCB, we are done */ - test DMAPARAMS, DIRECTION jz return; - mvi SCB_CONTROL call dfdat_in_7; - call dfdat_in_7_continued; - call dfdat_in_7_continued; - jmp dfdat_in_7_continued; + call dma_finish; + /* If we were putting the SCB, we are done */ + test DMAPARAMS, DIRECTION jz return; + mvi SCB_CONTROL call dfdat_in_7; + call dfdat_in_7_continued; + call dfdat_in_7_continued; + jmp dfdat_in_7_continued; dfdat_in_7: - mov DINDEX,SINDEX; + mov DINDEX,SINDEX; dfdat_in_7_continued: - mov DINDIR,DFDAT; - mov DINDIR,DFDAT; - mov DINDIR,DFDAT; - mov DINDIR,DFDAT; - mov DINDIR,DFDAT; - mov DINDIR,DFDAT; - mov DINDIR,DFDAT ret; + mov DINDIR,DFDAT; + mov DINDIR,DFDAT; + mov DINDIR,DFDAT; + mov DINDIR,DFDAT; + mov DINDIR,DFDAT; + mov DINDIR,DFDAT; + mov DINDIR,DFDAT ret; + } + /* * Wait for DMA from host memory to data FIFO to complete, then disable @@ -1095,27 +1692,14 @@ dma_finish: test DFCNTRL, HDMAEN jnz .; ret; -index_untagged_scb: - mov DINDEX, SINDEX; - shr DINDEX, 4; - and DINDEX, 0x03; /* Bottom two bits of tid */ - add DINDEX, SCB_BUSYTARGETS; - shr A, 6, SINDEX; /* Target ID divided by 4 */ - test SINDEX, SELBUSB jz index_untagged_scb2; - add A, 2; /* Add 2 positions */ -index_untagged_scb2: - mov SCBPTR, A; /* - * Select the SCB with this - * target's information. - */ - mov SINDEX, DINDEX ret; - add_scb_to_free_list: - mov SCB_NEXT, FREE_SCBH; - mvi SCB_TAG, SCB_LIST_NULL; - mov FREE_SCBH, SCBPTR ret; + if ((ahc->flags & AHC_PAGESCBS) != 0) { + mov SCB_NEXT, FREE_SCBH; + mov FREE_SCBH, SCBPTR; + } + mvi SCB_TAG, SCB_LIST_NULL ret; -.if ( SCB_PAGING ) +if ((ahc->flags & AHC_PAGESCBS) != 0) { get_free_or_disc_scb: cmp FREE_SCBH, SCB_LIST_NULL jne dequeue_free_scb; cmp DISCONNECTED_SCBH, SCB_LIST_NULL jne dequeue_disc_scb; @@ -1123,25 +1707,15 @@ return_error: mvi SINDEX, SCB_LIST_NULL ret; dequeue_disc_scb: mov SCBPTR, DISCONNECTED_SCBH; -/* - * If we have a residual, then we are in the middle of some I/O - * and we have to send this SCB back up to the kernel so that the - * saved data pointers and residual information isn't lost. - */ - test SCB_CONTROL, MUST_DMAUP_SCB jz . + 3; - and SCB_CONTROL, ~MUST_DMAUP_SCB; - jmp dma_up_scb; - test SCB_RESID_SGCNT,0xff jnz dma_up_scb; - cmp SCB_LINKED_NEXT, SCB_LIST_NULL je unlink_disc_scb; dma_up_scb: mvi DMAPARAMS, FIFORESET; mov SCB_TAG call dma_scb; unlink_disc_scb: - /* jmp instead of call since we want to return anyway */ - mov SCBPTR jmp rem_scb_from_disc_list; + mov DISCONNECTED_SCBH, SCB_NEXT ret; dequeue_free_scb: mov SCBPTR, FREE_SCBH; mov FREE_SCBH, SCB_NEXT ret; +} add_scb_to_disc_list: /* @@ -1149,11 +1723,5 @@ add_scb_to_disc_list: * candidates for paging out an SCB if one is needed for a new command. * Modifying the disconnected list is a critical(pause dissabled) section. */ - mvi SCB_PREV, SCB_LIST_NULL; mov SCB_NEXT, DISCONNECTED_SCBH; - mov DISCONNECTED_SCBH, SCBPTR; - cmp SCB_NEXT,SCB_LIST_NULL je return; - mov SCBPTR,SCB_NEXT; - mov SCB_PREV,DISCONNECTED_SCBH; - mov SCBPTR,DISCONNECTED_SCBH ret; -.endif + mov DISCONNECTED_SCBH, SCBPTR ret; diff --git a/sys/dev/aic7xxx/aic7xxx_93cx6.c b/sys/dev/aic7xxx/aic7xxx_93cx6.c new file mode 100644 index 0000000..d7f8ab5 --- /dev/null +++ b/sys/dev/aic7xxx/aic7xxx_93cx6.c @@ -0,0 +1,174 @@ +/* + * Interface for the 93C66/56/46/26/06 serial eeprom parts. + * + * Copyright (c) 1995, 1996 Daniel M. Eischen + * All rights reserved. + * + * 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 immediately at the beginning of the file, without modification, + * 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. Absolutely no warranty of function or purpose is made by the author + * Daniel M. Eischen. + * 4. Modifications may be freely made to this file if the above conditions + * are met. + * + * $Id: 93cx6.c,v 1.10 1997/02/22 09:38:36 peter Exp $ + */ + +/* + * The instruction set of the 93C66/56/46/26/06 chips are as follows: + * + * Start OP * + * Function Bit Code Address** Data Description + * ------------------------------------------------------------------- + * READ 1 10 A5 - A0 Reads data stored in memory, + * starting at specified address + * EWEN 1 00 11XXXX Write enable must preceed + * all programming modes + * ERASE 1 11 A5 - A0 Erase register A5A4A3A2A1A0 + * WRITE 1 01 A5 - A0 D15 - D0 Writes register + * ERAL 1 00 10XXXX Erase all registers + * WRAL 1 00 01XXXX D15 - D0 Writes to all registers + * EWDS 1 00 00XXXX Disables all programming + * instructions + * *Note: A value of X for address is a don't care condition. + * **Note: There are 8 address bits for the 93C56/66 chips unlike + * the 93C46/26/06 chips which have 6 address bits. + * + * The 93C46 has a four wire interface: clock, chip select, data in, and + * data out. In order to perform one of the above functions, you need + * to enable the chip select for a clock period (typically a minimum of + * 1 usec, with the clock high and low a minimum of 750 and 250 nsec + * respectively). While the chip select remains high, you can clock in + * the instructions (above) starting with the start bit, followed by the + * OP code, Address, and Data (if needed). For the READ instruction, the + * requested 16-bit register contents is read from the data out line but + * is preceded by an initial zero (leading 0, followed by 16-bits, MSB + * first). The clock cycling from low to high initiates the next data + * bit to be sent from the chip. + * + */ + +#include <sys/param.h> +#include <sys/systm.h> +#include <machine/bus_memio.h> +#include <machine/bus_pio.h> +#include <machine/bus.h> +#include <dev/aic7xxx/93cx6.h> + +/* + * Right now, we only have to read the SEEPROM. But we make it easier to + * add other 93Cx6 functions. + */ +static struct seeprom_cmd { + unsigned char len; + unsigned char bits[3]; +} seeprom_read = {3, {1, 1, 0}}; + +/* + * Wait for the SEERDY to go high; about 800 ns. + */ +#define CLOCK_PULSE(sd, rdy) \ + while ((SEEPROM_STATUS_INB(sd) & rdy) == 0) { \ + ; /* Do nothing */ \ + } \ + (void)SEEPROM_INB(sd); /* Clear clock */ + +/* + * Read the serial EEPROM and returns 1 if successful and 0 if + * not successful. + */ +int +read_seeprom(sd, buf, start_addr, count) + struct seeprom_descriptor *sd; + u_int16_t *buf; + bus_size_t start_addr; + bus_size_t count; +{ + int i = 0; + u_int k = 0; + u_int16_t v; + u_int8_t temp; + + /* + * Read the requested registers of the seeprom. The loop + * will range from 0 to count-1. + */ + for (k = start_addr; k < count + start_addr; k++) { + /* Send chip select for one clock cycle. */ + temp = sd->sd_MS ^ sd->sd_CS; + SEEPROM_OUTB(sd, temp ^ sd->sd_CK); + CLOCK_PULSE(sd, sd->sd_RDY); + + /* + * Now we're ready to send the read command followed by the + * address of the 16-bit register we want to read. + */ + for (i = 0; i < seeprom_read.len; i++) { + if (seeprom_read.bits[i] != 0) + temp ^= sd->sd_DO; + SEEPROM_OUTB(sd, temp); + CLOCK_PULSE(sd, sd->sd_RDY); + SEEPROM_OUTB(sd, temp ^ sd->sd_CK); + CLOCK_PULSE(sd, sd->sd_RDY); + if (seeprom_read.bits[i] != 0) + temp ^= sd->sd_DO; + } + /* Send the 6 or 8 bit address (MSB first, LSB last). */ + for (i = (sd->sd_chip - 1); i >= 0; i--) { + if ((k & (1 << i)) != 0) + temp ^= sd->sd_DO; + SEEPROM_OUTB(sd, temp); + CLOCK_PULSE(sd, sd->sd_RDY); + SEEPROM_OUTB(sd, temp ^ sd->sd_CK); + CLOCK_PULSE(sd, sd->sd_RDY); + if ((k & (1 << i)) != 0) + temp ^= sd->sd_DO; + } + + /* + * Now read the 16 bit register. An initial 0 precedes the + * register contents which begins with bit 15 (MSB) and ends + * with bit 0 (LSB). The initial 0 will be shifted off the + * top of our word as we let the loop run from 0 to 16. + */ + v = 0; + for (i = 16; i >= 0; i--) { + SEEPROM_OUTB(sd, temp); + CLOCK_PULSE(sd, sd->sd_RDY); + v <<= 1; + if (SEEPROM_DATA_INB(sd) & sd->sd_DI) + v |= 1; + SEEPROM_OUTB(sd, temp ^ sd->sd_CK); + CLOCK_PULSE(sd, sd->sd_RDY); + } + + buf[k - start_addr] = v; + + /* Reset the chip select for the next command cycle. */ + temp = sd->sd_MS; + SEEPROM_OUTB(sd, temp); + CLOCK_PULSE(sd, sd->sd_RDY); + SEEPROM_OUTB(sd, temp ^ sd->sd_CK); + CLOCK_PULSE(sd, sd->sd_RDY); + SEEPROM_OUTB(sd, temp); + CLOCK_PULSE(sd, sd->sd_RDY); + } +#ifdef 93CX6_DUMP_EEPROM + printf("\nSerial EEPROM:"); + for (k = 0; k < count; k = k + 1) { + if (((k % 8) == 0) && (k != 0)) { + printf ("\n "); + } + printf (" 0x%x", buf[k]); + } + printf ("\n"); +#endif + return (1); +} diff --git a/sys/dev/aic7xxx/aic7xxx_93cx6.h b/sys/dev/aic7xxx/aic7xxx_93cx6.h new file mode 100644 index 0000000..32645e1 --- /dev/null +++ b/sys/dev/aic7xxx/aic7xxx_93cx6.h @@ -0,0 +1,95 @@ +/* + * Interface to the 93C46 serial EEPROM that is used to store BIOS + * settings for the aic7xxx based adaptec SCSI controllers. It can + * also be used for 93C26 and 93C06 serial EEPROMS. + * + * Copyright (c) 1994, 1995 Justin T. Gibbs. + * All rights reserved. + * + * 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, + * without modification, immediately at the beginning of the file. + * 2. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * Where this Software is combined with software released under the terms of + * the GNU Public License ("GPL") and the terms of the GPL would require the + * combined work to also be released under the terms of the GPL, the terms + * and conditions of this License will apply in addition to those of the + * GPL with the exception of any terms or conditions of this License that + * conflict with, or are expressly prohibited by, the GPL. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR 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 AUTHOR 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. + * + * $Id$ + */ + +#include <sys/param.h> +#if !defined(__NetBSD__) +#include <sys/systm.h> +#endif + +#ifdef KERNEL + +typedef enum { + C46 = 6, + C56_66 = 8 +} seeprom_chip_t; + +struct seeprom_descriptor { + bus_space_tag_t sd_tag; + bus_space_handle_t sd_bsh; + bus_size_t sd_control_offset; + bus_size_t sd_status_offset; + bus_size_t sd_dataout_offset; + seeprom_chip_t sd_chip; + u_int16_t sd_MS; + u_int16_t sd_RDY; + u_int16_t sd_CS; + u_int16_t sd_CK; + u_int16_t sd_DO; + u_int16_t sd_DI; +}; + +/* + * This function will read count 16-bit words from the serial EEPROM and + * return their value in buf. The port address of the aic7xxx serial EEPROM + * control register is passed in as offset. The following parameters are + * also passed in: + * + * CS - Chip select + * CK - Clock + * DO - Data out + * DI - Data in + * RDY - SEEPROM ready + * MS - Memory port mode select + * + * A failed read attempt returns 0, and a successful read returns 1. + */ + +#define SEEPROM_INB(sd) \ + bus_space_read_1(sd->sd_tag, sd->sd_bsh, sd->sd_control_offset) +#define SEEPROM_OUTB(sd, value) \ + bus_space_write_1(sd->sd_tag, sd->sd_bsh, sd->sd_control_offset, value) +#define SEEPROM_STATUS_INB(sd) \ + bus_space_read_1(sd->sd_tag, sd->sd_bsh, sd->sd_status_offset) +#define SEEPROM_DATA_INB(sd) \ + bus_space_read_1(sd->sd_tag, sd->sd_bsh, sd->sd_dataout_offset) + +int read_seeprom(struct seeprom_descriptor *sd, u_int16_t *buf, + bus_size_t start_addr, bus_size_t count); + +#endif /* KERNEL */ diff --git a/sys/dev/aic7xxx/aicasm.c b/sys/dev/aic7xxx/aicasm.c index c05950d..66d30ce 100644 --- a/sys/dev/aic7xxx/aicasm.c +++ b/sys/dev/aic7xxx/aicasm.c @@ -1,7 +1,7 @@ /* * Aic7xxx SCSI host adapter firmware asssembler * - * Copyright (c) 1997 Justin T. Gibbs. + * Copyright (c) 1997, 1998 Justin T. Gibbs. * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -10,10 +10,7 @@ * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions, and the following disclaimer, * without modification, immediately at the beginning of the file. - * 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. The name of the author may not be used to endorse or promote products + * 2. The name of the author may not be used to endorse or promote products * derived from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND @@ -28,7 +25,7 @@ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * - * $Id: aicasm.c,v 1.18 1997/06/27 19:38:45 gibbs Exp $ + * $Id: aicasm.c,v 1.19 1997/09/03 03:44:38 gibbs Exp $ */ #include <sys/types.h> #include <sys/mman.h> @@ -43,13 +40,24 @@ #include "aicasm_symbol.h" #include "sequencer.h" -static void usage __P((void)); -static void back_patch __P((void)); -static void output_code __P((FILE *ofile)); -static void output_listing __P((FILE *listfile, char *ifilename, - char *options)); -static struct patch *next_patch __P((struct patch *cur_patch, int options, - int instrptr)); +typedef struct patch { + STAILQ_ENTRY(patch) links; + int patch_func; + u_int begin; + u_int skip_instr; + u_int skip_patch; +} patch_t; + +STAILQ_HEAD(patch_list, patch) patches; + +static void usage(void); +static void back_patch(void); +static void output_code(FILE *ofile); +static void output_listing(FILE *listfile, char *ifilename); +static int dump_scope(scope_t *scope); +static void emit_patch(scope_t *scope, int patch); +static int check_patch(patch_t **start_patch, int start_instr, + int *skip_addr, int *func_vals); struct path_list search_path; int includes_search_curdir; @@ -62,8 +70,8 @@ char *listfilename; FILE *listfile; static STAILQ_HEAD(,instruction) seq_program; -static STAILQ_HEAD(, patch) patch_list; -symlist_t patch_options; +struct scope_list scope_stack; +symlist_t patch_functions; #if DEBUG extern int yy_flex_debug; @@ -82,19 +90,24 @@ main(argc, argv) int ch; int retval; char *inputfilename; - char *options; + scope_t *sentinal; + STAILQ_INIT(&patches); SLIST_INIT(&search_path); STAILQ_INIT(&seq_program); - STAILQ_INIT(&patch_list); - SLIST_INIT(&patch_options); + SLIST_INIT(&scope_stack); + + /* Set Sentinal scope node */ + sentinal = scope_alloc(); + sentinal->type = SCOPE_ROOT; + includes_search_curdir = 1; appname = *argv; regfile = NULL; listfile = NULL; - options = NULL; #if DEBUG yy_flex_debug = 0; + yydebug = 0; #endif while ((ch = getopt(argc, argv, "d:l:n:o:r:I:O:")) != -1) { switch(ch) { @@ -138,10 +151,6 @@ main(argc, argv) } ofilename = optarg; break; - case 'O': - /* Patches to include in the listing */ - options = optarg; - break; case 'r': if ((regfile = fopen(optarg, "w")) == NULL) { perror(optarg); @@ -207,13 +216,33 @@ main(argc, argv) include_file(*argv, SOURCE_FILE); retval = yyparse(); if (retval == 0) { + if (SLIST_FIRST(&scope_stack) == NULL + || SLIST_FIRST(&scope_stack)->type != SCOPE_ROOT) { + stop("Unterminated conditional expression", + EX_DATAERR); + /* NOTREACHED */ + } + + /* Process outmost scope */ + process_scope(SLIST_FIRST(&scope_stack)); + /* + * Decend the tree of scopes and insert/emit + * patches as appropriate. We perform a depth first + * tranversal, recursively handling each scope. + */ + /* start at the root scope */ + dump_scope(SLIST_FIRST(&scope_stack)); + + /* Patch up forward jump addresses */ back_patch(); + if (ofile != NULL) output_code(ofile); - if (regfile != NULL) + if (regfile != NULL) { symtable_dump(regfile); + } if (listfile != NULL) - output_listing(listfile, inputfilename, options); + output_listing(listfile, inputfilename); } stop(NULL, 0); @@ -228,7 +257,7 @@ usage() (void)fprintf(stderr, "usage: %-16s [-nostdinc] [-I-] [-I directory] [-o output_file] [-r register_output_file] [-l program_list_file] - [-O option_name[|options_name2]] input_file\n", + input_file\n", appname); exit(EX_USAGE); } @@ -255,12 +284,9 @@ back_patch() /* NOTREACHED */ } f3_instr = &cur_instr->format.format3; - address = ((f3_instr->opcode_addr & ADDR_HIGH_BIT) << 8) - | f3_instr->address; + address = f3_instr->address; address += cur_instr->patch_label->info.linfo->address; - f3_instr->opcode_addr &= ~ADDR_HIGH_BIT; - f3_instr->opcode_addr |= (address >> 8) & ADDR_HIGH_BIT; - f3_instr->address = address & 0xFF; + f3_instr->address = address; } } } @@ -284,6 +310,7 @@ output_code(ofile) for(cur_instr = seq_program.stqh_first; cur_instr != NULL; cur_instr = cur_instr->links.stqe_next) { + fprintf(ofile, "\t0x%02x, 0x%02x, 0x%02x, 0x%02x,\n", cur_instr->format.bytes[0], cur_instr->format.bytes[1], @@ -291,60 +318,128 @@ output_code(ofile) cur_instr->format.bytes[3]); instrcount++; } - fprintf(ofile, "};\n"); + fprintf(ofile, "};\n\n"); /* - * Output the patch list, option definitions first. + * Output patch information. Patch functions first. */ - for(cur_node = patch_options.slh_first; + for(cur_node = SLIST_FIRST(&patch_functions); cur_node != NULL; - cur_node = cur_node->links.sle_next) { - fprintf(ofile, "#define\t%-16s\t0x%x\n", cur_node->symbol->name, - cur_node->symbol->info.condinfo->value); + cur_node = SLIST_NEXT(cur_node,links)) { + fprintf(ofile, +"static int ahc_patch%d_func(struct ahc_softc *ahc); + +static int +ahc_patch%d_func(struct ahc_softc *ahc) +{ + return (%s); +}\n\n", + cur_node->symbol->info.condinfo->func_num, + cur_node->symbol->info.condinfo->func_num, + cur_node->symbol->name); } fprintf(ofile, -"struct patch { - int options; - int negative; - int begin; - int end; +"typedef int patch_func_t __P((struct ahc_softc *)); +struct patch { + patch_func_t *patch_func; + u_int32_t begin :10, + skip_instr :10, + skip_patch :12; } patches[] = {\n"); - for(cur_patch = patch_list.stqh_first; + for(cur_patch = STAILQ_FIRST(&patches); cur_patch != NULL; - cur_patch = cur_patch->links.stqe_next) + cur_patch = STAILQ_NEXT(cur_patch,links)) { + fprintf(ofile, "\t{ ahc_patch%d_func, %d, %d, %d },\n", + cur_patch->patch_func, cur_patch->begin, + cur_patch->skip_instr, cur_patch->skip_patch); + } - fprintf(ofile, "\t{ 0x%08x, %d, 0x%03x, 0x%03x },\n", - cur_patch->options, cur_patch->negative, cur_patch->begin, - cur_patch->end); + fprintf(ofile, "\n};\n"); - fprintf(ofile, "\t{ 0x%08x, %d, 0x%03x, 0x%03x }\n};\n", - 0, 0, 0, 0); - fprintf(stderr, "%s: %d instructions used\n", appname, instrcount); } +static int +dump_scope(scope_t *scope) +{ + scope_t *cur_scope; + int patches_emitted = 0; + + /* + * Emit the first patch for this scope + */ + emit_patch(scope, 0); + + /* + * Dump each scope within this one. + */ + cur_scope = TAILQ_FIRST(&scope->inner_scope); + + while (cur_scope != NULL) { + + dump_scope(cur_scope); + + cur_scope = TAILQ_NEXT(cur_scope, scope_links); + } + + /* + * Emit the second, closing, patch for this scope + */ + emit_patch(scope, 1); +} + +void +emit_patch(scope_t *scope, int patch) +{ + patch_info_t *pinfo; + patch_t *new_patch; + + pinfo = &scope->patches[patch]; + + if (pinfo->skip_instr == 0) + /* No-Op patch */ + return; + + new_patch = (patch_t *)malloc(sizeof(*new_patch)); + + if (new_patch == NULL) + stop("Could not malloc patch structure", EX_OSERR); + + memset(new_patch, 0, sizeof(*new_patch)); + + if (patch == 0) { + new_patch->patch_func = scope->func_num; + new_patch->begin = scope->begin_addr; + } else { + new_patch->patch_func = 0; + new_patch->begin = scope->end_addr; + } + new_patch->skip_instr = pinfo->skip_instr; + new_patch->skip_patch = pinfo->skip_patch; + STAILQ_INSERT_TAIL(&patches, new_patch, links); +} + void -output_listing(listfile, ifilename, patches) - FILE *listfile; - char *ifilename; - char *patches; +output_listing(FILE *listfile, char *ifilename) { + char buf[1024]; FILE *ifile; - int line; struct instruction *cur_instr; + patch_t *cur_patch; + symbol_node_t *cur_func; + int *func_values; int instrcount; int instrptr; - char buf[1024]; - patch_t *cur_patch; - char *option_spec; - int options; + int line; + int func_count; + int skip_addr; instrcount = 0; instrptr = 0; line = 1; - options = 1; /* All code outside of patch blocks */ + skip_addr = 0; if ((ifile = fopen(ifilename, "r")) == NULL) { perror(ifilename); stop(NULL, EX_DATAERR); @@ -353,31 +448,66 @@ output_listing(listfile, ifilename, patches) /* * Determine which options to apply to this listing. */ - while ((option_spec = strsep(&patches, "|")) != NULL) { - symbol_t *symbol; - - symbol = symtable_get(option_spec); - if (symbol->type != CONDITIONAL) { - stop("Invalid option specified in patch list for " - "program listing", EX_USAGE); - /* NOTREACHED */ + for (func_count = 0, cur_func = SLIST_FIRST(&patch_functions); + cur_func != NULL; + cur_func = SLIST_NEXT(cur_func, links)) + func_count++; + + if (func_count != 0) { + func_values = (int *)malloc(func_count * sizeof(int)); + + if (func_values == NULL) + stop("Could not malloc", EX_OSERR); + + func_values[0] = 0; /* FALSE func */ + func_count--; + + /* + * Ask the user to fill in the return values for + * the rest of the functions. + */ + + + for (cur_func = SLIST_FIRST(&patch_functions); + cur_func != NULL && SLIST_NEXT(cur_func, links) != NULL; + cur_func = SLIST_NEXT(cur_func, links), func_count--) { + int input; + + fprintf(stdout, "\n(%s)\n", cur_func->symbol->name); + fprintf(stdout, + "Enter the return value for " + "this expression[T/F]:"); + + while (1) { + + input = getchar(); + input = toupper(input); + + if (input == 'T') { + func_values[func_count] = 1; + break; + } else if (input == 'F') { + func_values[func_count] = 0; + break; + } + } } - options |= symbol->info.condinfo->value; + fprintf(stdout, "\nThanks!\n"); } - cur_patch = patch_list.stqh_first; - for(cur_instr = seq_program.stqh_first; + /* Now output the listing */ + cur_patch = STAILQ_FIRST(&patches); + for(cur_instr = STAILQ_FIRST(&seq_program); cur_instr != NULL; - cur_instr = cur_instr->links.stqe_next,instrcount++) { + cur_instr = STAILQ_NEXT(cur_instr, links), instrcount++) { - cur_patch = next_patch(cur_patch, options, instrcount); - if (cur_patch - && cur_patch->begin <= instrcount - && cur_patch->end > instrcount) + if (check_patch(&cur_patch, instrcount, + &skip_addr, func_values) == 0) { /* Don't count this instruction as it is in a patch * that was removed. */ continue; + } while (line < cur_instr->srcline) { fgets(buf, sizeof(buf), ifile); @@ -401,29 +531,39 @@ output_listing(listfile, ifilename, patches) fclose(ifile); } -static struct patch * -next_patch(cur_patch, options, instrptr) - struct patch *cur_patch; - int options; - int instrptr; +static int +check_patch(patch_t **start_patch, int start_instr, + int *skip_addr, int *func_vals) { - while(cur_patch != NULL) { - if (((cur_patch->options & options) != 0 - && cur_patch->negative == FALSE) - || ((cur_patch->options & options) == 0 - && cur_patch->negative == TRUE) - || (instrptr >= cur_patch->end)) { - /* - * Either we want to keep this section of code, - * or we have consumed this patch. Skip to the - * next patch. + patch_t *cur_patch; + + cur_patch = *start_patch; + + while (cur_patch != NULL && start_instr == cur_patch->begin) { + if (func_vals[cur_patch->patch_func] == 0) { + int skip; + + /* Start rejecting code */ + *skip_addr = start_instr + cur_patch->skip_instr; + for (skip = cur_patch->skip_patch; + skip > 0 && cur_patch != NULL; + skip--) + cur_patch = STAILQ_NEXT(cur_patch, links); + } else { + /* Accepted this patch. Advance to the next + * one and wait for our intruction pointer to + * hit this point. */ - cur_patch = cur_patch->links.stqe_next; - } else - /* Found an okay patch */ - break; + cur_patch = STAILQ_NEXT(cur_patch, links); + } } - return (cur_patch); + + *start_patch = cur_patch; + if (start_instr < *skip_addr) + /* Still skipping */ + return (0); + + return (1); } /* @@ -471,7 +611,7 @@ stop(string, err_code) } } - symlist_free(&patch_options); + symlist_free(&patch_functions); symtable_close(); exit(err_code); @@ -491,15 +631,85 @@ seq_alloc() return new_instr; } -patch_t * -patch_alloc() +scope_t * +scope_alloc() { - patch_t *new_patch; + scope_t *new_scope; - new_patch = (patch_t *)malloc(sizeof(patch_t)); - if (new_patch == NULL) - stop("Unable to malloc patch object", EX_SOFTWARE); - memset(new_patch, 0, sizeof(*new_patch)); - STAILQ_INSERT_TAIL(&patch_list, new_patch, links); - return new_patch; + new_scope = (scope_t *)malloc(sizeof(scope_t)); + if (new_scope == NULL) + stop("Unable to malloc scope object", EX_SOFTWARE); + memset(new_scope, 0, sizeof(*new_scope)); + TAILQ_INIT(&new_scope->inner_scope); + + if (SLIST_FIRST(&scope_stack) != NULL) { + TAILQ_INSERT_TAIL(&SLIST_FIRST(&scope_stack)->inner_scope, + new_scope, scope_links); + } + /* This patch is now the current scope */ + SLIST_INSERT_HEAD(&scope_stack, new_scope, scope_stack_links); + return new_scope; +} + +void +process_scope(scope_t *scope) +{ + /* + * We are "leaving" this scope. We should now have + * enough information to process the lists of scopes + * we encapsulate. + */ + scope_t *cur_scope; + u_int skip_patch_count; + u_int skip_instr_count; + + cur_scope = TAILQ_LAST(&scope->inner_scope, scope_tailq); + skip_patch_count = 0; + skip_instr_count = 0; + while (cur_scope != NULL) { + u_int patch0_patch_skip; + + patch0_patch_skip = 0; + switch (cur_scope->type) { + case SCOPE_IF: + case SCOPE_ELSE_IF: + if (skip_instr_count != 0) { + /* Create a tail patch */ + patch0_patch_skip++; + cur_scope->patches[1].skip_patch = + skip_patch_count + 1; + cur_scope->patches[1].skip_instr = + skip_instr_count; + } + + /* Count Head patch */ + patch0_patch_skip++; + + /* Count any patches contained in our inner scope */ + patch0_patch_skip += cur_scope->inner_scope_patches; + + cur_scope->patches[0].skip_patch = patch0_patch_skip; + cur_scope->patches[0].skip_instr = + cur_scope->end_addr - cur_scope->begin_addr; + + skip_instr_count += cur_scope->patches[0].skip_instr; + + skip_patch_count += patch0_patch_skip; + if (cur_scope->type == SCOPE_IF) { + scope->inner_scope_patches += skip_patch_count; + skip_patch_count = 0; + skip_instr_count = 0; + } + break; + case SCOPE_ELSE: + /* Count any patches contained in our innter scope */ + skip_patch_count += cur_scope->inner_scope_patches; + + skip_instr_count += cur_scope->end_addr + - cur_scope->begin_addr; + break; + } + + cur_scope = TAILQ_PREV(cur_scope, scope_tailq, scope_links); + } } diff --git a/sys/dev/aic7xxx/aicasm.h b/sys/dev/aic7xxx/aicasm.h index a81afc1..5080675 100644 --- a/sys/dev/aic7xxx/aicasm.h +++ b/sys/dev/aic7xxx/aicasm.h @@ -10,10 +10,7 @@ * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions, and the following disclaimer, * without modification, immediately at the beginning of the file. - * 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. The name of the author may not be used to endorse or promote products + * 2. The name of the author may not be used to endorse or promote products * derived from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND @@ -28,7 +25,7 @@ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * - * $Id: aic7xxx_asm.h,v 1.2 1997/03/16 07:28:30 gibbs Exp $ + * $Id: aicasm.h,v 1.3 1997/06/27 19:38:47 gibbs Exp $ */ #include <sys/queue.h> @@ -56,13 +53,15 @@ typedef enum { SLIST_HEAD(path_list, path_entry); extern struct path_list search_path; -extern struct symlist patch_options; +extern struct scope_list scope_stack; +extern struct symlist patch_functions; extern int includes_search_curdir; /* False if we've seen -I- */ extern char *appname; extern int yylineno; extern char *yyfilename; -void stop __P((const char *errstring, int err_code)); -void include_file __P((char *file_name, include_type type)); -struct instruction *seq_alloc __P((void)); -struct patch *patch_alloc __P((void)); +void stop(const char *errstring, int err_code); +void include_file(char *file_name, include_type type); +struct instruction *seq_alloc(void); +struct scope *scope_alloc(void); +void process_scope(struct scope *); diff --git a/sys/dev/aic7xxx/aicasm/aicasm.c b/sys/dev/aic7xxx/aicasm/aicasm.c index c05950d..66d30ce 100644 --- a/sys/dev/aic7xxx/aicasm/aicasm.c +++ b/sys/dev/aic7xxx/aicasm/aicasm.c @@ -1,7 +1,7 @@ /* * Aic7xxx SCSI host adapter firmware asssembler * - * Copyright (c) 1997 Justin T. Gibbs. + * Copyright (c) 1997, 1998 Justin T. Gibbs. * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -10,10 +10,7 @@ * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions, and the following disclaimer, * without modification, immediately at the beginning of the file. - * 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. The name of the author may not be used to endorse or promote products + * 2. The name of the author may not be used to endorse or promote products * derived from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND @@ -28,7 +25,7 @@ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * - * $Id: aicasm.c,v 1.18 1997/06/27 19:38:45 gibbs Exp $ + * $Id: aicasm.c,v 1.19 1997/09/03 03:44:38 gibbs Exp $ */ #include <sys/types.h> #include <sys/mman.h> @@ -43,13 +40,24 @@ #include "aicasm_symbol.h" #include "sequencer.h" -static void usage __P((void)); -static void back_patch __P((void)); -static void output_code __P((FILE *ofile)); -static void output_listing __P((FILE *listfile, char *ifilename, - char *options)); -static struct patch *next_patch __P((struct patch *cur_patch, int options, - int instrptr)); +typedef struct patch { + STAILQ_ENTRY(patch) links; + int patch_func; + u_int begin; + u_int skip_instr; + u_int skip_patch; +} patch_t; + +STAILQ_HEAD(patch_list, patch) patches; + +static void usage(void); +static void back_patch(void); +static void output_code(FILE *ofile); +static void output_listing(FILE *listfile, char *ifilename); +static int dump_scope(scope_t *scope); +static void emit_patch(scope_t *scope, int patch); +static int check_patch(patch_t **start_patch, int start_instr, + int *skip_addr, int *func_vals); struct path_list search_path; int includes_search_curdir; @@ -62,8 +70,8 @@ char *listfilename; FILE *listfile; static STAILQ_HEAD(,instruction) seq_program; -static STAILQ_HEAD(, patch) patch_list; -symlist_t patch_options; +struct scope_list scope_stack; +symlist_t patch_functions; #if DEBUG extern int yy_flex_debug; @@ -82,19 +90,24 @@ main(argc, argv) int ch; int retval; char *inputfilename; - char *options; + scope_t *sentinal; + STAILQ_INIT(&patches); SLIST_INIT(&search_path); STAILQ_INIT(&seq_program); - STAILQ_INIT(&patch_list); - SLIST_INIT(&patch_options); + SLIST_INIT(&scope_stack); + + /* Set Sentinal scope node */ + sentinal = scope_alloc(); + sentinal->type = SCOPE_ROOT; + includes_search_curdir = 1; appname = *argv; regfile = NULL; listfile = NULL; - options = NULL; #if DEBUG yy_flex_debug = 0; + yydebug = 0; #endif while ((ch = getopt(argc, argv, "d:l:n:o:r:I:O:")) != -1) { switch(ch) { @@ -138,10 +151,6 @@ main(argc, argv) } ofilename = optarg; break; - case 'O': - /* Patches to include in the listing */ - options = optarg; - break; case 'r': if ((regfile = fopen(optarg, "w")) == NULL) { perror(optarg); @@ -207,13 +216,33 @@ main(argc, argv) include_file(*argv, SOURCE_FILE); retval = yyparse(); if (retval == 0) { + if (SLIST_FIRST(&scope_stack) == NULL + || SLIST_FIRST(&scope_stack)->type != SCOPE_ROOT) { + stop("Unterminated conditional expression", + EX_DATAERR); + /* NOTREACHED */ + } + + /* Process outmost scope */ + process_scope(SLIST_FIRST(&scope_stack)); + /* + * Decend the tree of scopes and insert/emit + * patches as appropriate. We perform a depth first + * tranversal, recursively handling each scope. + */ + /* start at the root scope */ + dump_scope(SLIST_FIRST(&scope_stack)); + + /* Patch up forward jump addresses */ back_patch(); + if (ofile != NULL) output_code(ofile); - if (regfile != NULL) + if (regfile != NULL) { symtable_dump(regfile); + } if (listfile != NULL) - output_listing(listfile, inputfilename, options); + output_listing(listfile, inputfilename); } stop(NULL, 0); @@ -228,7 +257,7 @@ usage() (void)fprintf(stderr, "usage: %-16s [-nostdinc] [-I-] [-I directory] [-o output_file] [-r register_output_file] [-l program_list_file] - [-O option_name[|options_name2]] input_file\n", + input_file\n", appname); exit(EX_USAGE); } @@ -255,12 +284,9 @@ back_patch() /* NOTREACHED */ } f3_instr = &cur_instr->format.format3; - address = ((f3_instr->opcode_addr & ADDR_HIGH_BIT) << 8) - | f3_instr->address; + address = f3_instr->address; address += cur_instr->patch_label->info.linfo->address; - f3_instr->opcode_addr &= ~ADDR_HIGH_BIT; - f3_instr->opcode_addr |= (address >> 8) & ADDR_HIGH_BIT; - f3_instr->address = address & 0xFF; + f3_instr->address = address; } } } @@ -284,6 +310,7 @@ output_code(ofile) for(cur_instr = seq_program.stqh_first; cur_instr != NULL; cur_instr = cur_instr->links.stqe_next) { + fprintf(ofile, "\t0x%02x, 0x%02x, 0x%02x, 0x%02x,\n", cur_instr->format.bytes[0], cur_instr->format.bytes[1], @@ -291,60 +318,128 @@ output_code(ofile) cur_instr->format.bytes[3]); instrcount++; } - fprintf(ofile, "};\n"); + fprintf(ofile, "};\n\n"); /* - * Output the patch list, option definitions first. + * Output patch information. Patch functions first. */ - for(cur_node = patch_options.slh_first; + for(cur_node = SLIST_FIRST(&patch_functions); cur_node != NULL; - cur_node = cur_node->links.sle_next) { - fprintf(ofile, "#define\t%-16s\t0x%x\n", cur_node->symbol->name, - cur_node->symbol->info.condinfo->value); + cur_node = SLIST_NEXT(cur_node,links)) { + fprintf(ofile, +"static int ahc_patch%d_func(struct ahc_softc *ahc); + +static int +ahc_patch%d_func(struct ahc_softc *ahc) +{ + return (%s); +}\n\n", + cur_node->symbol->info.condinfo->func_num, + cur_node->symbol->info.condinfo->func_num, + cur_node->symbol->name); } fprintf(ofile, -"struct patch { - int options; - int negative; - int begin; - int end; +"typedef int patch_func_t __P((struct ahc_softc *)); +struct patch { + patch_func_t *patch_func; + u_int32_t begin :10, + skip_instr :10, + skip_patch :12; } patches[] = {\n"); - for(cur_patch = patch_list.stqh_first; + for(cur_patch = STAILQ_FIRST(&patches); cur_patch != NULL; - cur_patch = cur_patch->links.stqe_next) + cur_patch = STAILQ_NEXT(cur_patch,links)) { + fprintf(ofile, "\t{ ahc_patch%d_func, %d, %d, %d },\n", + cur_patch->patch_func, cur_patch->begin, + cur_patch->skip_instr, cur_patch->skip_patch); + } - fprintf(ofile, "\t{ 0x%08x, %d, 0x%03x, 0x%03x },\n", - cur_patch->options, cur_patch->negative, cur_patch->begin, - cur_patch->end); + fprintf(ofile, "\n};\n"); - fprintf(ofile, "\t{ 0x%08x, %d, 0x%03x, 0x%03x }\n};\n", - 0, 0, 0, 0); - fprintf(stderr, "%s: %d instructions used\n", appname, instrcount); } +static int +dump_scope(scope_t *scope) +{ + scope_t *cur_scope; + int patches_emitted = 0; + + /* + * Emit the first patch for this scope + */ + emit_patch(scope, 0); + + /* + * Dump each scope within this one. + */ + cur_scope = TAILQ_FIRST(&scope->inner_scope); + + while (cur_scope != NULL) { + + dump_scope(cur_scope); + + cur_scope = TAILQ_NEXT(cur_scope, scope_links); + } + + /* + * Emit the second, closing, patch for this scope + */ + emit_patch(scope, 1); +} + +void +emit_patch(scope_t *scope, int patch) +{ + patch_info_t *pinfo; + patch_t *new_patch; + + pinfo = &scope->patches[patch]; + + if (pinfo->skip_instr == 0) + /* No-Op patch */ + return; + + new_patch = (patch_t *)malloc(sizeof(*new_patch)); + + if (new_patch == NULL) + stop("Could not malloc patch structure", EX_OSERR); + + memset(new_patch, 0, sizeof(*new_patch)); + + if (patch == 0) { + new_patch->patch_func = scope->func_num; + new_patch->begin = scope->begin_addr; + } else { + new_patch->patch_func = 0; + new_patch->begin = scope->end_addr; + } + new_patch->skip_instr = pinfo->skip_instr; + new_patch->skip_patch = pinfo->skip_patch; + STAILQ_INSERT_TAIL(&patches, new_patch, links); +} + void -output_listing(listfile, ifilename, patches) - FILE *listfile; - char *ifilename; - char *patches; +output_listing(FILE *listfile, char *ifilename) { + char buf[1024]; FILE *ifile; - int line; struct instruction *cur_instr; + patch_t *cur_patch; + symbol_node_t *cur_func; + int *func_values; int instrcount; int instrptr; - char buf[1024]; - patch_t *cur_patch; - char *option_spec; - int options; + int line; + int func_count; + int skip_addr; instrcount = 0; instrptr = 0; line = 1; - options = 1; /* All code outside of patch blocks */ + skip_addr = 0; if ((ifile = fopen(ifilename, "r")) == NULL) { perror(ifilename); stop(NULL, EX_DATAERR); @@ -353,31 +448,66 @@ output_listing(listfile, ifilename, patches) /* * Determine which options to apply to this listing. */ - while ((option_spec = strsep(&patches, "|")) != NULL) { - symbol_t *symbol; - - symbol = symtable_get(option_spec); - if (symbol->type != CONDITIONAL) { - stop("Invalid option specified in patch list for " - "program listing", EX_USAGE); - /* NOTREACHED */ + for (func_count = 0, cur_func = SLIST_FIRST(&patch_functions); + cur_func != NULL; + cur_func = SLIST_NEXT(cur_func, links)) + func_count++; + + if (func_count != 0) { + func_values = (int *)malloc(func_count * sizeof(int)); + + if (func_values == NULL) + stop("Could not malloc", EX_OSERR); + + func_values[0] = 0; /* FALSE func */ + func_count--; + + /* + * Ask the user to fill in the return values for + * the rest of the functions. + */ + + + for (cur_func = SLIST_FIRST(&patch_functions); + cur_func != NULL && SLIST_NEXT(cur_func, links) != NULL; + cur_func = SLIST_NEXT(cur_func, links), func_count--) { + int input; + + fprintf(stdout, "\n(%s)\n", cur_func->symbol->name); + fprintf(stdout, + "Enter the return value for " + "this expression[T/F]:"); + + while (1) { + + input = getchar(); + input = toupper(input); + + if (input == 'T') { + func_values[func_count] = 1; + break; + } else if (input == 'F') { + func_values[func_count] = 0; + break; + } + } } - options |= symbol->info.condinfo->value; + fprintf(stdout, "\nThanks!\n"); } - cur_patch = patch_list.stqh_first; - for(cur_instr = seq_program.stqh_first; + /* Now output the listing */ + cur_patch = STAILQ_FIRST(&patches); + for(cur_instr = STAILQ_FIRST(&seq_program); cur_instr != NULL; - cur_instr = cur_instr->links.stqe_next,instrcount++) { + cur_instr = STAILQ_NEXT(cur_instr, links), instrcount++) { - cur_patch = next_patch(cur_patch, options, instrcount); - if (cur_patch - && cur_patch->begin <= instrcount - && cur_patch->end > instrcount) + if (check_patch(&cur_patch, instrcount, + &skip_addr, func_values) == 0) { /* Don't count this instruction as it is in a patch * that was removed. */ continue; + } while (line < cur_instr->srcline) { fgets(buf, sizeof(buf), ifile); @@ -401,29 +531,39 @@ output_listing(listfile, ifilename, patches) fclose(ifile); } -static struct patch * -next_patch(cur_patch, options, instrptr) - struct patch *cur_patch; - int options; - int instrptr; +static int +check_patch(patch_t **start_patch, int start_instr, + int *skip_addr, int *func_vals) { - while(cur_patch != NULL) { - if (((cur_patch->options & options) != 0 - && cur_patch->negative == FALSE) - || ((cur_patch->options & options) == 0 - && cur_patch->negative == TRUE) - || (instrptr >= cur_patch->end)) { - /* - * Either we want to keep this section of code, - * or we have consumed this patch. Skip to the - * next patch. + patch_t *cur_patch; + + cur_patch = *start_patch; + + while (cur_patch != NULL && start_instr == cur_patch->begin) { + if (func_vals[cur_patch->patch_func] == 0) { + int skip; + + /* Start rejecting code */ + *skip_addr = start_instr + cur_patch->skip_instr; + for (skip = cur_patch->skip_patch; + skip > 0 && cur_patch != NULL; + skip--) + cur_patch = STAILQ_NEXT(cur_patch, links); + } else { + /* Accepted this patch. Advance to the next + * one and wait for our intruction pointer to + * hit this point. */ - cur_patch = cur_patch->links.stqe_next; - } else - /* Found an okay patch */ - break; + cur_patch = STAILQ_NEXT(cur_patch, links); + } } - return (cur_patch); + + *start_patch = cur_patch; + if (start_instr < *skip_addr) + /* Still skipping */ + return (0); + + return (1); } /* @@ -471,7 +611,7 @@ stop(string, err_code) } } - symlist_free(&patch_options); + symlist_free(&patch_functions); symtable_close(); exit(err_code); @@ -491,15 +631,85 @@ seq_alloc() return new_instr; } -patch_t * -patch_alloc() +scope_t * +scope_alloc() { - patch_t *new_patch; + scope_t *new_scope; - new_patch = (patch_t *)malloc(sizeof(patch_t)); - if (new_patch == NULL) - stop("Unable to malloc patch object", EX_SOFTWARE); - memset(new_patch, 0, sizeof(*new_patch)); - STAILQ_INSERT_TAIL(&patch_list, new_patch, links); - return new_patch; + new_scope = (scope_t *)malloc(sizeof(scope_t)); + if (new_scope == NULL) + stop("Unable to malloc scope object", EX_SOFTWARE); + memset(new_scope, 0, sizeof(*new_scope)); + TAILQ_INIT(&new_scope->inner_scope); + + if (SLIST_FIRST(&scope_stack) != NULL) { + TAILQ_INSERT_TAIL(&SLIST_FIRST(&scope_stack)->inner_scope, + new_scope, scope_links); + } + /* This patch is now the current scope */ + SLIST_INSERT_HEAD(&scope_stack, new_scope, scope_stack_links); + return new_scope; +} + +void +process_scope(scope_t *scope) +{ + /* + * We are "leaving" this scope. We should now have + * enough information to process the lists of scopes + * we encapsulate. + */ + scope_t *cur_scope; + u_int skip_patch_count; + u_int skip_instr_count; + + cur_scope = TAILQ_LAST(&scope->inner_scope, scope_tailq); + skip_patch_count = 0; + skip_instr_count = 0; + while (cur_scope != NULL) { + u_int patch0_patch_skip; + + patch0_patch_skip = 0; + switch (cur_scope->type) { + case SCOPE_IF: + case SCOPE_ELSE_IF: + if (skip_instr_count != 0) { + /* Create a tail patch */ + patch0_patch_skip++; + cur_scope->patches[1].skip_patch = + skip_patch_count + 1; + cur_scope->patches[1].skip_instr = + skip_instr_count; + } + + /* Count Head patch */ + patch0_patch_skip++; + + /* Count any patches contained in our inner scope */ + patch0_patch_skip += cur_scope->inner_scope_patches; + + cur_scope->patches[0].skip_patch = patch0_patch_skip; + cur_scope->patches[0].skip_instr = + cur_scope->end_addr - cur_scope->begin_addr; + + skip_instr_count += cur_scope->patches[0].skip_instr; + + skip_patch_count += patch0_patch_skip; + if (cur_scope->type == SCOPE_IF) { + scope->inner_scope_patches += skip_patch_count; + skip_patch_count = 0; + skip_instr_count = 0; + } + break; + case SCOPE_ELSE: + /* Count any patches contained in our innter scope */ + skip_patch_count += cur_scope->inner_scope_patches; + + skip_instr_count += cur_scope->end_addr + - cur_scope->begin_addr; + break; + } + + cur_scope = TAILQ_PREV(cur_scope, scope_tailq, scope_links); + } } diff --git a/sys/dev/aic7xxx/aicasm/aicasm.h b/sys/dev/aic7xxx/aicasm/aicasm.h index a81afc1..5080675 100644 --- a/sys/dev/aic7xxx/aicasm/aicasm.h +++ b/sys/dev/aic7xxx/aicasm/aicasm.h @@ -10,10 +10,7 @@ * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions, and the following disclaimer, * without modification, immediately at the beginning of the file. - * 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. The name of the author may not be used to endorse or promote products + * 2. The name of the author may not be used to endorse or promote products * derived from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND @@ -28,7 +25,7 @@ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * - * $Id: aic7xxx_asm.h,v 1.2 1997/03/16 07:28:30 gibbs Exp $ + * $Id: aicasm.h,v 1.3 1997/06/27 19:38:47 gibbs Exp $ */ #include <sys/queue.h> @@ -56,13 +53,15 @@ typedef enum { SLIST_HEAD(path_list, path_entry); extern struct path_list search_path; -extern struct symlist patch_options; +extern struct scope_list scope_stack; +extern struct symlist patch_functions; extern int includes_search_curdir; /* False if we've seen -I- */ extern char *appname; extern int yylineno; extern char *yyfilename; -void stop __P((const char *errstring, int err_code)); -void include_file __P((char *file_name, include_type type)); -struct instruction *seq_alloc __P((void)); -struct patch *patch_alloc __P((void)); +void stop(const char *errstring, int err_code); +void include_file(char *file_name, include_type type); +struct instruction *seq_alloc(void); +struct scope *scope_alloc(void); +void process_scope(struct scope *); diff --git a/sys/dev/aic7xxx/aicasm/aicasm_gram.y b/sys/dev/aic7xxx/aicasm/aicasm_gram.y index fa657c0..09a62b9 100644 --- a/sys/dev/aic7xxx/aicasm/aicasm_gram.y +++ b/sys/dev/aic7xxx/aicasm/aicasm_gram.y @@ -2,7 +2,7 @@ /* * Parser for the Aic7xxx SCSI Host adapter sequencer assembler. * - * Copyright (c) 1997 Justin T. Gibbs. + * Copyright (c) 1997-1998 Justin T. Gibbs. * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -11,10 +11,7 @@ * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions, and the following disclaimer, * without modification, immediately at the beginning of the file. - * 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. The name of the author may not be used to endorse or promote products + * 2. The name of the author may not be used to endorse or promote products * derived from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND @@ -29,7 +26,7 @@ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * - * $Id: aicasm_gram.y,v 1.3 1997/09/03 03:44:40 gibbs Exp $ + * $Id: aicasm_gram.y,v 1.4 1997/09/27 19:37:28 gibbs Exp $ */ #include <stdio.h> @@ -56,7 +53,6 @@ static symbol_ref_t sindex; static int instruction_ptr; static int sram_or_scb_offset; static int download_constant_count; -static patch_t *cur_patch; static void process_bitmask __P((int mask_type, symbol_t *sym, int mask)); static void initialize_symbol __P((symbol_t *symbol)); @@ -118,11 +114,13 @@ static int is_download_const __P((expression_t *immed)); %token <str> T_PATH +%token <sym> T_CEXPR + %token T_EOF T_INCLUDE %token <value> T_SHR T_SHL T_ROR T_ROL -%token <value> T_MVI T_MOV T_CLR +%token <value> T_MVI T_MOV T_CLR T_BMOV %token <value> T_JMP T_JC T_JNC T_JE T_JNE T_JNZ T_JZ T_CALL @@ -150,7 +148,7 @@ static int is_download_const __P((expression_t *immed)); %token T_NL -%token T_IF T_ELSE T_ENDIF +%token T_IF T_ELSE T_ELSE_IF T_ENDIF %type <sym_ref> reg_symbol address destination source opt_source @@ -670,64 +668,89 @@ address: ; conditional: - T_IF + T_IF T_CEXPR '{' { - if (cur_patch != NULL) { - stop("Nested .if directive", EX_DATAERR); - /* NOTREACHED */ - } - cur_patch = patch_alloc(); - cur_patch->begin = instruction_ptr; - } - option_list -; + scope_t *new_scope; -conditional: - T_ELSE + add_conditional($2); + new_scope = scope_alloc(); + new_scope->type = SCOPE_IF; + new_scope->begin_addr = instruction_ptr; + new_scope->func_num = $2->info.condinfo->func_num; + } +| T_ELSE T_IF T_CEXPR '{' { - patch_t *next_patch; + scope_t *new_scope; + scope_t *scope_context; + scope_t *last_scope; + + /* + * Ensure that the previous scope is either an + * if or and else if. + */ + scope_context = SLIST_FIRST(&scope_stack); + last_scope = TAILQ_LAST(&scope_context->inner_scope, + scope_tailq); + if (last_scope == NULL + || last_scope->type == T_ELSE) { - if (cur_patch == NULL) { - stop(".else outsize of .if", EX_DATAERR); + stop("'else if' without leading 'if'", EX_DATAERR); + /* NOTREACHED */ + } + add_conditional($3); + new_scope = scope_alloc(); + new_scope->type = SCOPE_ELSE_IF; + new_scope->begin_addr = instruction_ptr; + new_scope->func_num = $3->info.condinfo->func_num; + } +| T_ELSE '{' + { + scope_t *new_scope; + scope_t *scope_context; + scope_t *last_scope; + + /* + * Ensure that the previous scope is either an + * if or and else if. + */ + scope_context = SLIST_FIRST(&scope_stack); + last_scope = TAILQ_LAST(&scope_context->inner_scope, + scope_tailq); + if (last_scope == NULL + || last_scope->type == SCOPE_ELSE) { + + stop("'else' without leading 'if'", EX_DATAERR); /* NOTREACHED */ } - cur_patch->end = instruction_ptr; - next_patch = patch_alloc(); - next_patch->options = cur_patch->options; - next_patch->negative = cur_patch->negative ? FALSE : TRUE; - cur_patch = next_patch; - cur_patch->begin = instruction_ptr; + new_scope = scope_alloc(); + new_scope->type = SCOPE_ELSE; + new_scope->begin_addr = instruction_ptr; } ; conditional: - T_ENDIF + '}' { - if (cur_patch == NULL) { - stop(".endif outsize of .if", EX_DATAERR); + scope_t *scope_context; + scope_t *last_scope; + + scope_context = SLIST_FIRST(&scope_stack); + if (scope_context->type == SCOPE_ROOT) { + stop("Unexpected '}' encountered", EX_DATAERR); /* NOTREACHED */ } - cur_patch->end = instruction_ptr; - cur_patch = NULL; - } -; -option_list: - '(' option_symbol_list ')' -| '!' option_list - { - cur_patch->negative = cur_patch->negative ? FALSE : TRUE; - } -; + scope_context->end_addr = instruction_ptr; -option_symbol_list: - T_SYMBOL - { - add_conditional($1); - } -| option_list '|' T_SYMBOL - { - add_conditional($3); + /* Pop the scope */ + SLIST_REMOVE_HEAD(&scope_stack, scope_stack_links); + + process_scope(scope_context); + + if (SLIST_FIRST(&scope_stack) == NULL) { + stop("Unexpected '}' encountered", EX_DATAERR); + /* NOTREACHED */ + } } ; @@ -804,6 +827,13 @@ code: ; code: + T_BMOV destination ',' source ',' immediate ret ';' + { + format_1_instr(AIC_OP_BMOV, &$2, &$6, &$4, $7); + } +; + +code: T_MOV destination ',' source ret ';' { expression_t immed; @@ -1118,7 +1148,8 @@ format_1_instr(opcode, dest, immed, src, ret) /* Allocate sequencer space for the instruction and fill it out */ instr = seq_alloc(); f1_instr = &instr->format.format1; - f1_instr->opcode_ret = (opcode << 1) | (ret ? RETURN_BIT : 0); + f1_instr->ret = ret ? 1 : 0; + f1_instr->opcode = opcode; f1_instr->destination = dest->symbol->info.rinfo->address + dest->offset; f1_instr->source = src->symbol->info.rinfo->address @@ -1126,7 +1157,7 @@ format_1_instr(opcode, dest, immed, src, ret) f1_instr->immediate = immed->value; if (is_download_const(immed)) - f1_instr->opcode_ret |= DOWNLOAD_CONST_IMMEDIATE; + f1_instr->parity = 1; symlist_free(&immed->referenced_syms); instruction_ptr++; @@ -1154,7 +1185,8 @@ format_2_instr(opcode, dest, places, src, ret) /* Allocate sequencer space for the instruction and fill it out */ instr = seq_alloc(); f2_instr = &instr->format.format2; - f2_instr->opcode_ret = (AIC_OP_ROL << 1) | (ret ? RETURN_BIT : 0); + f2_instr->ret = ret ? 1 : 0; + f2_instr->opcode = AIC_OP_ROL; f2_instr->destination = dest->symbol->info.rinfo->address + dest->offset; f2_instr->source = src->symbol->info.rinfo->address @@ -1225,15 +1257,14 @@ format_3_instr(opcode, src, immed, address) instr->patch_label = address->symbol; } else addr = address->symbol->info.linfo->address + address->offset; - f3_instr->opcode_addr = (opcode << 1) - | ((addr >> 8) & 0x01); - f3_instr->address = addr & 0xff; + f3_instr->opcode = opcode; + f3_instr->address = addr; f3_instr->source = src->symbol->info.rinfo->address + src->offset; f3_instr->immediate = immed->value; if (is_download_const(immed)) - f3_instr->opcode_addr |= DOWNLOAD_CONST_IMMEDIATE; + f3_instr->parity = 1; symlist_free(&immed->referenced_syms); instruction_ptr++; @@ -1326,19 +1357,38 @@ static void add_conditional(symbol) symbol_t *symbol; { - static int numoptions = 1; + static int numfuncs; - if (symbol->type == UNINITIALIZED) { - symbol->type = CONDITIONAL; - initialize_symbol(symbol); - symbol->info.condinfo->value = 0x01 << numoptions++; - symlist_add(&patch_options, symbol, SYMLIST_INSERT_HEAD); - } else if (symbol->type != CONDITIONAL) { - stop("Conditional symbol mirrors other symbol", + if (numfuncs == 0) { + /* add a special conditional, "0" */ + symbol_t *false_func; + + false_func = symtable_get("0"); + if (false_func->type != UNINITIALIZED) { + stop("Conditional expression '0' " + "conflicts with a symbol", EX_DATAERR); + /* NOTREACHED */ + } + false_func->type = CONDITIONAL; + initialize_symbol(false_func); + false_func->info.condinfo->func_num = numfuncs++; + symlist_add(&patch_functions, false_func, SYMLIST_INSERT_HEAD); + } + + /* This condition has occurred before */ + if (symbol->type == CONDITIONAL) + return; + + if (symbol->type != UNINITIALIZED) { + stop("Conditional expression conflicts with a symbol", EX_DATAERR); /* NOTREACHED */ } - cur_patch->options |= symbol->info.condinfo->value; + + symbol->type = CONDITIONAL; + initialize_symbol(symbol); + symbol->info.condinfo->func_num = numfuncs++; + symlist_add(&patch_functions, symbol, SYMLIST_INSERT_HEAD); } void diff --git a/sys/dev/aic7xxx/aicasm/aicasm_scan.l b/sys/dev/aic7xxx/aicasm/aicasm_scan.l index 8446376..44bc834 100644 --- a/sys/dev/aic7xxx/aicasm/aicasm_scan.l +++ b/sys/dev/aic7xxx/aicasm/aicasm_scan.l @@ -2,7 +2,7 @@ /* * Lexical Analyzer for the Aic7xxx SCSI Host adapter sequencer assembler. * - * Copyright (c) 1997 Justin T. Gibbs. + * Copyright (c) 1997-1998 Justin T. Gibbs. * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -11,10 +11,7 @@ * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions, and the following disclaimer, * without modification, immediately at the beginning of the file. - * 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. The name of the author may not be used to endorse or promote products + * 2. The name of the author may not be used to endorse or promote products * derived from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND @@ -29,7 +26,7 @@ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * - * $Id: aicasm_scan.l,v 1.4 1997/09/03 03:44:42 gibbs Exp $ + * $Id: aicasm_scan.l,v 1.5 1997/09/27 19:37:29 gibbs Exp $ */ #include <sys/types.h> @@ -43,6 +40,11 @@ #include "aicasm.h" #include "aicasm_symbol.h" #include "y.tab.h" + +#define MAX_STR_CONST 256 +char string_buf[MAX_STR_CONST]; +char *string_buf_ptr; +int parren_count; %} PATH [-/A-Za-z0-9_.]*[./][-/A-Za-z0-9_.]* @@ -50,6 +52,8 @@ WORD [A-Za-z_][-A-Za-z_0-9]* SPACE [ \t]+ %x COMMENT +%x CEXPR +%x INCLUDE %% \n { ++yylineno; } @@ -60,6 +64,32 @@ SPACE [ \t]+ <COMMENT>"*"+[^*/\n]* ; <COMMENT>"/"+[^*/\n]* ; <COMMENT>"*"+"/" { BEGIN INITIAL; } +if[ \t]*\( { + string_buf_ptr = string_buf; + parren_count = 1; + BEGIN CEXPR; + return T_IF; + } +<CEXPR>\( { *string_buf_ptr++ = '('; parren_count++; } +<CEXPR>\) { + parren_count--; + if (parren_count == 0) { + /* All done */ + BEGIN INITIAL; + *string_buf_ptr = '\0'; + yylval.sym = symtable_get(string_buf); + return T_CEXPR; + } else { + *string_buf_ptr++ = ')'; + } + } +<CEXPR>\n { ++yylineno; } +<CEXPR>[^()\n]+ { + char *yptr = yytext; + + while (*yptr != '\0') + *string_buf_ptr++ = *yptr++; + } {SPACE} ; @@ -109,6 +139,7 @@ jnz { return T_JNZ; } call { return T_CALL; } add { return T_ADD; } adc { return T_ADC; } +bmov { return T_BMOV; } inc { return T_INC; } dec { return T_DEC; } stc { return T_STC; } @@ -120,12 +151,10 @@ and { return T_AND; } or { return T_OR; } ret { return T_RET; } nop { return T_NOP; } -.if { return T_IF; } -.else { return T_ELSE; } -.endif { return T_ENDIF; } +else { return T_ELSE; } /* Allowed Symbols */ -[-+,:()~|&."{};<>[\]!] { return yytext[0]; } +[-+,:()~|&."{};<>[\]!] { return yytext[0]; } /* Number processing */ 0[0-7]* { @@ -144,7 +173,11 @@ nop { return T_NOP; } } /* Include Files */ -#include { return T_INCLUDE; } +#include { return T_INCLUDE; BEGIN INCLUDE;} +<INCLUDE>[<>\"] { return yytext[0]; } +<INCLUDE>{PATH} { yylval.str = strdup(yytext); return T_PATH; } +<INCLUDE>; { BEGIN INITIAL; return yytext[0]; } +<INCLUDE>. { stop("Invalid include line", EX_DATAERR); } /* For parsing C include files with #define foo */ #define { yylval.value = TRUE; return T_CONST; } diff --git a/sys/dev/aic7xxx/aicasm/aicasm_symbol.c b/sys/dev/aic7xxx/aicasm/aicasm_symbol.c index c9f645a..f808e89 100644 --- a/sys/dev/aic7xxx/aicasm/aicasm_symbol.c +++ b/sys/dev/aic7xxx/aicasm/aicasm_symbol.c @@ -10,10 +10,7 @@ * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions, and the following disclaimer, * without modification, immediately at the beginning of the file. - * 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. The name of the author may not be used to endorse or promote products + * 2. The name of the author may not be used to endorse or promote products * derived from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND @@ -28,7 +25,7 @@ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * - * $Id: aicasm_symbol.c,v 1.3 1997/09/03 03:44:44 gibbs Exp $ + * $Id: aicasm_symbol.c,v 1.4 1997/09/27 19:37:30 gibbs Exp $ */ diff --git a/sys/dev/aic7xxx/aicasm/aicasm_symbol.h b/sys/dev/aic7xxx/aicasm/aicasm_symbol.h index 199e3ba..ba98b43 100644 --- a/sys/dev/aic7xxx/aicasm/aicasm_symbol.h +++ b/sys/dev/aic7xxx/aicasm/aicasm_symbol.h @@ -10,10 +10,7 @@ * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions, and the following disclaimer, * without modification, immediately at the beginning of the file. - * 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. The name of the author may not be used to endorse or promote products + * 2. The name of the author may not be used to endorse or promote products * derived from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND @@ -28,7 +25,7 @@ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * - * $Id: aicasm_symbol.h,v 1.2 1997/06/27 19:38:56 gibbs Exp $ + * $Id: aicasm_symbol.h,v 1.3 1997/09/27 19:37:30 gibbs Exp $ */ #include <sys/queue.h> @@ -82,7 +79,7 @@ struct label_info { }; struct cond_info { - int value; + int func_num; }; typedef struct expression_info { @@ -113,13 +110,32 @@ typedef struct symbol_node { symbol_t *symbol; }symbol_node_t; -typedef struct patch { - STAILQ_ENTRY(patch) links; - int negative; - int begin; - int end; - int options; -} patch_t; +typedef enum { + SCOPE_ROOT, + SCOPE_IF, + SCOPE_ELSE_IF, + SCOPE_ELSE +} scope_type; + +typedef struct patch_info { + int skip_patch; + int skip_instr; +} patch_info_t; + +typedef struct scope { + SLIST_ENTRY(scope) scope_stack_links; + TAILQ_ENTRY(scope) scope_links; + TAILQ_HEAD(, scope) inner_scope; + scope_type type; + int inner_scope_patches; + int begin_addr; + int end_addr; + patch_info_t patches[2]; + int func_num; +} scope_t; + +SLIST_HEAD(scope_list, scope); +TAILQ_HEAD(scope_tailq, scope); void symbol_delete __P((symbol_t *symbol)); diff --git a/sys/dev/aic7xxx/aicasm_gram.y b/sys/dev/aic7xxx/aicasm_gram.y index fa657c0..09a62b9 100644 --- a/sys/dev/aic7xxx/aicasm_gram.y +++ b/sys/dev/aic7xxx/aicasm_gram.y @@ -2,7 +2,7 @@ /* * Parser for the Aic7xxx SCSI Host adapter sequencer assembler. * - * Copyright (c) 1997 Justin T. Gibbs. + * Copyright (c) 1997-1998 Justin T. Gibbs. * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -11,10 +11,7 @@ * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions, and the following disclaimer, * without modification, immediately at the beginning of the file. - * 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. The name of the author may not be used to endorse or promote products + * 2. The name of the author may not be used to endorse or promote products * derived from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND @@ -29,7 +26,7 @@ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * - * $Id: aicasm_gram.y,v 1.3 1997/09/03 03:44:40 gibbs Exp $ + * $Id: aicasm_gram.y,v 1.4 1997/09/27 19:37:28 gibbs Exp $ */ #include <stdio.h> @@ -56,7 +53,6 @@ static symbol_ref_t sindex; static int instruction_ptr; static int sram_or_scb_offset; static int download_constant_count; -static patch_t *cur_patch; static void process_bitmask __P((int mask_type, symbol_t *sym, int mask)); static void initialize_symbol __P((symbol_t *symbol)); @@ -118,11 +114,13 @@ static int is_download_const __P((expression_t *immed)); %token <str> T_PATH +%token <sym> T_CEXPR + %token T_EOF T_INCLUDE %token <value> T_SHR T_SHL T_ROR T_ROL -%token <value> T_MVI T_MOV T_CLR +%token <value> T_MVI T_MOV T_CLR T_BMOV %token <value> T_JMP T_JC T_JNC T_JE T_JNE T_JNZ T_JZ T_CALL @@ -150,7 +148,7 @@ static int is_download_const __P((expression_t *immed)); %token T_NL -%token T_IF T_ELSE T_ENDIF +%token T_IF T_ELSE T_ELSE_IF T_ENDIF %type <sym_ref> reg_symbol address destination source opt_source @@ -670,64 +668,89 @@ address: ; conditional: - T_IF + T_IF T_CEXPR '{' { - if (cur_patch != NULL) { - stop("Nested .if directive", EX_DATAERR); - /* NOTREACHED */ - } - cur_patch = patch_alloc(); - cur_patch->begin = instruction_ptr; - } - option_list -; + scope_t *new_scope; -conditional: - T_ELSE + add_conditional($2); + new_scope = scope_alloc(); + new_scope->type = SCOPE_IF; + new_scope->begin_addr = instruction_ptr; + new_scope->func_num = $2->info.condinfo->func_num; + } +| T_ELSE T_IF T_CEXPR '{' { - patch_t *next_patch; + scope_t *new_scope; + scope_t *scope_context; + scope_t *last_scope; + + /* + * Ensure that the previous scope is either an + * if or and else if. + */ + scope_context = SLIST_FIRST(&scope_stack); + last_scope = TAILQ_LAST(&scope_context->inner_scope, + scope_tailq); + if (last_scope == NULL + || last_scope->type == T_ELSE) { - if (cur_patch == NULL) { - stop(".else outsize of .if", EX_DATAERR); + stop("'else if' without leading 'if'", EX_DATAERR); + /* NOTREACHED */ + } + add_conditional($3); + new_scope = scope_alloc(); + new_scope->type = SCOPE_ELSE_IF; + new_scope->begin_addr = instruction_ptr; + new_scope->func_num = $3->info.condinfo->func_num; + } +| T_ELSE '{' + { + scope_t *new_scope; + scope_t *scope_context; + scope_t *last_scope; + + /* + * Ensure that the previous scope is either an + * if or and else if. + */ + scope_context = SLIST_FIRST(&scope_stack); + last_scope = TAILQ_LAST(&scope_context->inner_scope, + scope_tailq); + if (last_scope == NULL + || last_scope->type == SCOPE_ELSE) { + + stop("'else' without leading 'if'", EX_DATAERR); /* NOTREACHED */ } - cur_patch->end = instruction_ptr; - next_patch = patch_alloc(); - next_patch->options = cur_patch->options; - next_patch->negative = cur_patch->negative ? FALSE : TRUE; - cur_patch = next_patch; - cur_patch->begin = instruction_ptr; + new_scope = scope_alloc(); + new_scope->type = SCOPE_ELSE; + new_scope->begin_addr = instruction_ptr; } ; conditional: - T_ENDIF + '}' { - if (cur_patch == NULL) { - stop(".endif outsize of .if", EX_DATAERR); + scope_t *scope_context; + scope_t *last_scope; + + scope_context = SLIST_FIRST(&scope_stack); + if (scope_context->type == SCOPE_ROOT) { + stop("Unexpected '}' encountered", EX_DATAERR); /* NOTREACHED */ } - cur_patch->end = instruction_ptr; - cur_patch = NULL; - } -; -option_list: - '(' option_symbol_list ')' -| '!' option_list - { - cur_patch->negative = cur_patch->negative ? FALSE : TRUE; - } -; + scope_context->end_addr = instruction_ptr; -option_symbol_list: - T_SYMBOL - { - add_conditional($1); - } -| option_list '|' T_SYMBOL - { - add_conditional($3); + /* Pop the scope */ + SLIST_REMOVE_HEAD(&scope_stack, scope_stack_links); + + process_scope(scope_context); + + if (SLIST_FIRST(&scope_stack) == NULL) { + stop("Unexpected '}' encountered", EX_DATAERR); + /* NOTREACHED */ + } } ; @@ -804,6 +827,13 @@ code: ; code: + T_BMOV destination ',' source ',' immediate ret ';' + { + format_1_instr(AIC_OP_BMOV, &$2, &$6, &$4, $7); + } +; + +code: T_MOV destination ',' source ret ';' { expression_t immed; @@ -1118,7 +1148,8 @@ format_1_instr(opcode, dest, immed, src, ret) /* Allocate sequencer space for the instruction and fill it out */ instr = seq_alloc(); f1_instr = &instr->format.format1; - f1_instr->opcode_ret = (opcode << 1) | (ret ? RETURN_BIT : 0); + f1_instr->ret = ret ? 1 : 0; + f1_instr->opcode = opcode; f1_instr->destination = dest->symbol->info.rinfo->address + dest->offset; f1_instr->source = src->symbol->info.rinfo->address @@ -1126,7 +1157,7 @@ format_1_instr(opcode, dest, immed, src, ret) f1_instr->immediate = immed->value; if (is_download_const(immed)) - f1_instr->opcode_ret |= DOWNLOAD_CONST_IMMEDIATE; + f1_instr->parity = 1; symlist_free(&immed->referenced_syms); instruction_ptr++; @@ -1154,7 +1185,8 @@ format_2_instr(opcode, dest, places, src, ret) /* Allocate sequencer space for the instruction and fill it out */ instr = seq_alloc(); f2_instr = &instr->format.format2; - f2_instr->opcode_ret = (AIC_OP_ROL << 1) | (ret ? RETURN_BIT : 0); + f2_instr->ret = ret ? 1 : 0; + f2_instr->opcode = AIC_OP_ROL; f2_instr->destination = dest->symbol->info.rinfo->address + dest->offset; f2_instr->source = src->symbol->info.rinfo->address @@ -1225,15 +1257,14 @@ format_3_instr(opcode, src, immed, address) instr->patch_label = address->symbol; } else addr = address->symbol->info.linfo->address + address->offset; - f3_instr->opcode_addr = (opcode << 1) - | ((addr >> 8) & 0x01); - f3_instr->address = addr & 0xff; + f3_instr->opcode = opcode; + f3_instr->address = addr; f3_instr->source = src->symbol->info.rinfo->address + src->offset; f3_instr->immediate = immed->value; if (is_download_const(immed)) - f3_instr->opcode_addr |= DOWNLOAD_CONST_IMMEDIATE; + f3_instr->parity = 1; symlist_free(&immed->referenced_syms); instruction_ptr++; @@ -1326,19 +1357,38 @@ static void add_conditional(symbol) symbol_t *symbol; { - static int numoptions = 1; + static int numfuncs; - if (symbol->type == UNINITIALIZED) { - symbol->type = CONDITIONAL; - initialize_symbol(symbol); - symbol->info.condinfo->value = 0x01 << numoptions++; - symlist_add(&patch_options, symbol, SYMLIST_INSERT_HEAD); - } else if (symbol->type != CONDITIONAL) { - stop("Conditional symbol mirrors other symbol", + if (numfuncs == 0) { + /* add a special conditional, "0" */ + symbol_t *false_func; + + false_func = symtable_get("0"); + if (false_func->type != UNINITIALIZED) { + stop("Conditional expression '0' " + "conflicts with a symbol", EX_DATAERR); + /* NOTREACHED */ + } + false_func->type = CONDITIONAL; + initialize_symbol(false_func); + false_func->info.condinfo->func_num = numfuncs++; + symlist_add(&patch_functions, false_func, SYMLIST_INSERT_HEAD); + } + + /* This condition has occurred before */ + if (symbol->type == CONDITIONAL) + return; + + if (symbol->type != UNINITIALIZED) { + stop("Conditional expression conflicts with a symbol", EX_DATAERR); /* NOTREACHED */ } - cur_patch->options |= symbol->info.condinfo->value; + + symbol->type = CONDITIONAL; + initialize_symbol(symbol); + symbol->info.condinfo->func_num = numfuncs++; + symlist_add(&patch_functions, symbol, SYMLIST_INSERT_HEAD); } void diff --git a/sys/dev/aic7xxx/aicasm_scan.l b/sys/dev/aic7xxx/aicasm_scan.l index 8446376..44bc834 100644 --- a/sys/dev/aic7xxx/aicasm_scan.l +++ b/sys/dev/aic7xxx/aicasm_scan.l @@ -2,7 +2,7 @@ /* * Lexical Analyzer for the Aic7xxx SCSI Host adapter sequencer assembler. * - * Copyright (c) 1997 Justin T. Gibbs. + * Copyright (c) 1997-1998 Justin T. Gibbs. * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -11,10 +11,7 @@ * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions, and the following disclaimer, * without modification, immediately at the beginning of the file. - * 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. The name of the author may not be used to endorse or promote products + * 2. The name of the author may not be used to endorse or promote products * derived from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND @@ -29,7 +26,7 @@ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * - * $Id: aicasm_scan.l,v 1.4 1997/09/03 03:44:42 gibbs Exp $ + * $Id: aicasm_scan.l,v 1.5 1997/09/27 19:37:29 gibbs Exp $ */ #include <sys/types.h> @@ -43,6 +40,11 @@ #include "aicasm.h" #include "aicasm_symbol.h" #include "y.tab.h" + +#define MAX_STR_CONST 256 +char string_buf[MAX_STR_CONST]; +char *string_buf_ptr; +int parren_count; %} PATH [-/A-Za-z0-9_.]*[./][-/A-Za-z0-9_.]* @@ -50,6 +52,8 @@ WORD [A-Za-z_][-A-Za-z_0-9]* SPACE [ \t]+ %x COMMENT +%x CEXPR +%x INCLUDE %% \n { ++yylineno; } @@ -60,6 +64,32 @@ SPACE [ \t]+ <COMMENT>"*"+[^*/\n]* ; <COMMENT>"/"+[^*/\n]* ; <COMMENT>"*"+"/" { BEGIN INITIAL; } +if[ \t]*\( { + string_buf_ptr = string_buf; + parren_count = 1; + BEGIN CEXPR; + return T_IF; + } +<CEXPR>\( { *string_buf_ptr++ = '('; parren_count++; } +<CEXPR>\) { + parren_count--; + if (parren_count == 0) { + /* All done */ + BEGIN INITIAL; + *string_buf_ptr = '\0'; + yylval.sym = symtable_get(string_buf); + return T_CEXPR; + } else { + *string_buf_ptr++ = ')'; + } + } +<CEXPR>\n { ++yylineno; } +<CEXPR>[^()\n]+ { + char *yptr = yytext; + + while (*yptr != '\0') + *string_buf_ptr++ = *yptr++; + } {SPACE} ; @@ -109,6 +139,7 @@ jnz { return T_JNZ; } call { return T_CALL; } add { return T_ADD; } adc { return T_ADC; } +bmov { return T_BMOV; } inc { return T_INC; } dec { return T_DEC; } stc { return T_STC; } @@ -120,12 +151,10 @@ and { return T_AND; } or { return T_OR; } ret { return T_RET; } nop { return T_NOP; } -.if { return T_IF; } -.else { return T_ELSE; } -.endif { return T_ENDIF; } +else { return T_ELSE; } /* Allowed Symbols */ -[-+,:()~|&."{};<>[\]!] { return yytext[0]; } +[-+,:()~|&."{};<>[\]!] { return yytext[0]; } /* Number processing */ 0[0-7]* { @@ -144,7 +173,11 @@ nop { return T_NOP; } } /* Include Files */ -#include { return T_INCLUDE; } +#include { return T_INCLUDE; BEGIN INCLUDE;} +<INCLUDE>[<>\"] { return yytext[0]; } +<INCLUDE>{PATH} { yylval.str = strdup(yytext); return T_PATH; } +<INCLUDE>; { BEGIN INITIAL; return yytext[0]; } +<INCLUDE>. { stop("Invalid include line", EX_DATAERR); } /* For parsing C include files with #define foo */ #define { yylval.value = TRUE; return T_CONST; } diff --git a/sys/dev/aic7xxx/aicasm_symbol.c b/sys/dev/aic7xxx/aicasm_symbol.c index c9f645a..f808e89 100644 --- a/sys/dev/aic7xxx/aicasm_symbol.c +++ b/sys/dev/aic7xxx/aicasm_symbol.c @@ -10,10 +10,7 @@ * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions, and the following disclaimer, * without modification, immediately at the beginning of the file. - * 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. The name of the author may not be used to endorse or promote products + * 2. The name of the author may not be used to endorse or promote products * derived from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND @@ -28,7 +25,7 @@ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * - * $Id: aicasm_symbol.c,v 1.3 1997/09/03 03:44:44 gibbs Exp $ + * $Id: aicasm_symbol.c,v 1.4 1997/09/27 19:37:30 gibbs Exp $ */ diff --git a/sys/dev/aic7xxx/aicasm_symbol.h b/sys/dev/aic7xxx/aicasm_symbol.h index 199e3ba..ba98b43 100644 --- a/sys/dev/aic7xxx/aicasm_symbol.h +++ b/sys/dev/aic7xxx/aicasm_symbol.h @@ -10,10 +10,7 @@ * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions, and the following disclaimer, * without modification, immediately at the beginning of the file. - * 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. The name of the author may not be used to endorse or promote products + * 2. The name of the author may not be used to endorse or promote products * derived from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND @@ -28,7 +25,7 @@ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * - * $Id: aicasm_symbol.h,v 1.2 1997/06/27 19:38:56 gibbs Exp $ + * $Id: aicasm_symbol.h,v 1.3 1997/09/27 19:37:30 gibbs Exp $ */ #include <sys/queue.h> @@ -82,7 +79,7 @@ struct label_info { }; struct cond_info { - int value; + int func_num; }; typedef struct expression_info { @@ -113,13 +110,32 @@ typedef struct symbol_node { symbol_t *symbol; }symbol_node_t; -typedef struct patch { - STAILQ_ENTRY(patch) links; - int negative; - int begin; - int end; - int options; -} patch_t; +typedef enum { + SCOPE_ROOT, + SCOPE_IF, + SCOPE_ELSE_IF, + SCOPE_ELSE +} scope_type; + +typedef struct patch_info { + int skip_patch; + int skip_instr; +} patch_info_t; + +typedef struct scope { + SLIST_ENTRY(scope) scope_stack_links; + TAILQ_ENTRY(scope) scope_links; + TAILQ_HEAD(, scope) inner_scope; + scope_type type; + int inner_scope_patches; + int begin_addr; + int end_addr; + patch_info_t patches[2]; + int func_num; +} scope_t; + +SLIST_HEAD(scope_list, scope); +TAILQ_HEAD(scope_tailq, scope); void symbol_delete __P((symbol_t *symbol)); diff --git a/sys/dev/aic7xxx/sequencer.h b/sys/dev/aic7xxx/sequencer.h index fd69435..0fe929b 100644 --- a/sys/dev/aic7xxx/sequencer.h +++ b/sys/dev/aic7xxx/sequencer.h @@ -2,7 +2,7 @@ * Instruction formats for the sequencer program downloaded to * Aic7xxx SCSI host adapters * - * Copyright (c) 1997 Justin T. Gibbs. + * Copyright (c) 1997, 1998 Justin T. Gibbs. * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -11,10 +11,7 @@ * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions, and the following disclaimer, * without modification, immediately at the beginning of the file. - * 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. The name of the author may not be used to endorse or promote products + * 2. The name of the author may not be used to endorse or promote products * derived from this software without specific prior written permission. * * Where this Software is combined with software released under the terms of @@ -36,40 +33,44 @@ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * - * $Id: sequencer.h,v 1.2 1997/06/27 19:38:52 gibbs Exp $ + * $Id: sequencer.h,v 1.3 1997/09/27 19:37:31 gibbs Exp $ */ struct ins_format1 { - u_int8_t immediate; - u_int8_t source; - u_int8_t destination; - u_int8_t opcode_ret; -#define DOWNLOAD_CONST_IMMEDIATE 0x80 + u_int32_t immediate : 8, + source : 9, + destination : 9, + ret : 1, + opcode : 4, + parity : 1; }; struct ins_format2 { - u_int8_t shift_control; - u_int8_t source; - u_int8_t destination; - u_int8_t opcode_ret; -#define RETURN_BIT 0x01 + u_int32_t shift_control : 8, + source : 9, + destination : 9, + ret : 1, + opcode : 4, + parity : 1; }; struct ins_format3 { - u_int8_t immediate; - u_int8_t source; - u_int8_t address; - u_int8_t opcode_addr; -#define ADDR_HIGH_BIT 0x01 + u_int32_t immediate : 8, + source : 9, + address : 10, + opcode : 4, + parity : 1; }; -struct instruction { - union { +union ins_formats { struct ins_format1 format1; struct ins_format2 format2; struct ins_format3 format3; u_int8_t bytes[4]; - } format; + u_int32_t integer; +}; +struct instruction { + union ins_formats format; u_int srcline; struct symbol *patch_label; STAILQ_ENTRY(instruction) links; @@ -81,6 +82,7 @@ struct instruction { #define AIC_OP_ADD 0x3 #define AIC_OP_ADC 0x4 #define AIC_OP_ROL 0x5 +#define AIC_OP_BMOV 0x6 #define AIC_OP_JMP 0x8 #define AIC_OP_JC 0x9 |