From f07a4c844da9f0ecae5bbee1ab94be56505f26f7 Mon Sep 17 00:00:00 2001 From: Thomas Bushnell Date: Tue, 25 Feb 1997 21:28:37 +0000 Subject: Initial source --- scsi/adapters/README | 290 ++++ scsi/adapters/scsi_33C93.h | 396 ++++++ scsi/adapters/scsi_33C93_hdw.c | 2078 ++++++++++++++++++++++++++++ scsi/adapters/scsi_5380.h | 126 ++ scsi/adapters/scsi_5380_hdw.c | 2423 +++++++++++++++++++++++++++++++++ scsi/adapters/scsi_53C700.h | 327 +++++ scsi/adapters/scsi_53C700_hdw.c | 696 ++++++++++ scsi/adapters/scsi_53C94.h | 253 ++++ scsi/adapters/scsi_53C94_hdw.c | 2840 +++++++++++++++++++++++++++++++++++++++ scsi/adapters/scsi_7061.h | 230 ++++ scsi/adapters/scsi_7061_hdw.c | 2603 +++++++++++++++++++++++++++++++++++ scsi/adapters/scsi_89352.h | 231 ++++ scsi/adapters/scsi_89352_hdw.c | 2192 ++++++++++++++++++++++++++++++ scsi/adapters/scsi_aha15.h | 347 +++++ scsi/adapters/scsi_aha15_hdw.c | 1467 ++++++++++++++++++++ scsi/adapters/scsi_aha17_hdw.c | 1371 +++++++++++++++++++ scsi/adapters/scsi_dma.h | 150 +++ scsi/adapters/scsi_user_dma.c | 171 +++ scsi/adapters/scsi_user_dma.h | 47 + 19 files changed, 18238 insertions(+) create mode 100644 scsi/adapters/README create mode 100644 scsi/adapters/scsi_33C93.h create mode 100644 scsi/adapters/scsi_33C93_hdw.c create mode 100644 scsi/adapters/scsi_5380.h create mode 100644 scsi/adapters/scsi_5380_hdw.c create mode 100644 scsi/adapters/scsi_53C700.h create mode 100644 scsi/adapters/scsi_53C700_hdw.c create mode 100644 scsi/adapters/scsi_53C94.h create mode 100644 scsi/adapters/scsi_53C94_hdw.c create mode 100644 scsi/adapters/scsi_7061.h create mode 100644 scsi/adapters/scsi_7061_hdw.c create mode 100644 scsi/adapters/scsi_89352.h create mode 100644 scsi/adapters/scsi_89352_hdw.c create mode 100644 scsi/adapters/scsi_aha15.h create mode 100644 scsi/adapters/scsi_aha15_hdw.c create mode 100644 scsi/adapters/scsi_aha17_hdw.c create mode 100644 scsi/adapters/scsi_dma.h create mode 100644 scsi/adapters/scsi_user_dma.c create mode 100644 scsi/adapters/scsi_user_dma.h (limited to 'scsi/adapters') diff --git a/scsi/adapters/README b/scsi/adapters/README new file mode 100644 index 0000000..1bc7e7c --- /dev/null +++ b/scsi/adapters/README @@ -0,0 +1,290 @@ + +This directory contains various examples of HBA support code, +among them: + + Chip/Board File Machine tested By + + NCR 53C94 scsi_53C94 DecStation 5000 af@cmu + DEC 7061 scsi_7061 DecStation 3100/2100 af@cmu + NCR 5380 scsi_5380 VaxStation 3100 af@cmu + Fujitsu 89352 scsi_89352 Omron Luna88k danner@cmu + Adaptec 1542B scsi_aha15 AT/PC af@cmu + +It should be trivial to modify them for some other machine that uses +the same SCSI chips, hopefully by properly conditionalizing and macroizing +the existing code. + +There are various rules and assumptions to keep in mind when designing/coding +the support code for a new HBA, here is a list. Pls extend this with +anything you find is not openly stated here and made your life miserable +during the port, and send it back to CMU. + + +AUTOCONF + +We assume the structures and procedures defined in chips/busses.*, +e.g. someone will call configure_bus_master to get to our foo_probe() +routine. This should make up its mind on how many targets it sees +[see later for dynamic reconfig], allocate a descriptor for each +one and leave the driver ready to accept commands (via foo_go()). + + On raw chips you should use a test_unit_ready command, + selecting without ATN, and timing out on non-existant + devices. Use LUN 0. + On boards, there probably is a command to let the board do + it (see Adaptec), if not do as above. + +The typical autoconf descriptor might look like + + caddr_t foo_std[NFOO] = { 0 }; + struct bus_device *foo_dinfo[NFOO*8]; + struct bus_ctlr *foo_minfo[NFOO]; + struct bus_driver foo_driver = + { foo_probe, scsi_slave, scsi_attach, foo_go, foo_std, "rz", + foo_dinfo, "foo", foo_minfo, BUS_INTR_B4_PROBE}; + +which indicates that foo_probe() and foo_go() are our interface functions, +and we use the generic scsi_slave() and scsi_attach() for the rest. +Their definition is + + foo_probe(reg, ui) + vm_offset_t reg; + struct bus_ctlr *ui; + +[the "reg" argument might actually be something else on architectures that + do not use memory mapped I/O] + + aha_go(tgt, cmd_count, in_count, cmd_only) + target_info_t *tgt; + boolean_t cmd_only; + +The foo_go() routine is fairly common across chips, look at any example to +see how to structure it. Basically, the arguments tell us how much data +to expect in either direction, and whether (cmd_only) we think we should +be selecting with ATN (cmd_only==FALSE) or not. The only gotcha is cmd_count +actually includes the size of any parameters. + +The "go" field of the scsi_softc structure describing your HBA should be +set to your foo_go() routine, by the foo_probe() routine. + +DATA DEPENDENCIES + +The upper layer assumes that tgt->cmd_ptr is a pointer to good memory +[e.g. no funny padding] where it places the scsi command control blocks +AND small (less than 255 bytes) parameters. It also expects small results +in there (things like read_capacity, for instance). I think I cleaned +up all the places that used to assume tgt->cmd_ptr was aligned, but do not +be surprised if I missed one. + +It does NOT use the dma_ptr, or any of the transient_state fields. + +WATCHDOG + +There is an optional MI watchdog facility, which can be used quite simply by +filling in the "watchdog" field of the scsi_softc structure describing +your HBA. To disable it, leave the field zero (or, dynamically, zero the +timeout value). You can use a watchdog of your own if you like, or more +likely set this field to point to the MI scsi_watchdog(). +This requires that your foo_softc descriptor starts off with a watchdog_t +structure, with the "reset" field pointing to a function that will +reset the SCSI bus should the watchdog expire. + +When a new SCSI command is initiated you should + if (foo->wd.nactive++ == 0) + foo->wd.watchdog_state = SCSI_WD_ACTIVE; +to activate the watchdog, on each interrupt [or other signal that all +is proceeding well for the command and it is making progress] you should + if (foo->wd.nactive) + foo->wd.watchdog_state = SCSI_WD_ACTIVE; +bump down the watchdog 'trigger level', and when the command terminates + if (aha->wd.nactive-- == 1) + aha->wd.watchdog_state = SCSI_WD_INACTIVE; + +When you detect a SCSI bus reset (possibly we initiated it) you should + aha->wd.nactive = 0; +and after cleaning up properly invoke + scsi_bus_was_reset(sc) + scsi_softc_t sc; + +The functiona that is invoked on watchdog expiry is + foo_reset_scsibus(foo) + register foo_softc_t foo; + +Note that this can be used for dumb chips that do not support select timeouts +in hardware [see the 5380 or 7061 code], but its primary use is to detect +instances where a target is holding on the SCSI bus way too long. + +The one drawback of resetting the bus is that some devices (like tapes) +lose status in case of a reset, and the MI code does not (yet?) try to +keep enough information around to be able to recover. If you want to +add something very useful you might change the rz_tape.c code to do just +that, e.g. on SCSI_RET_ABORTs wait a while for the tape to do whatever, +then rewind, and seek forward where the tape should have been positioned at +the beginning of the command that failed, then reissue the command. +None of the examples so far tries to be 'smart' like making an attempt +to get the bus unstuck without resetting it, send us ideas if you have +some. + + +DYNAMIC RECONFIG + +Your code should be ready to add/remove targets on the fly. To do so, +notify the upper layer that a target went offline returning +SCSI_RET_DEVICE_DOWN when e.g. the select timed out, and clear out +the tgt->flags field. +To find new devices, define a function + + boolean_t + foo_probe_target(tgt, ior) + target_info_t *tgt; + io_req_t ior; + +and install it in the "probe" field of the scsi_softc_t structure describing +the HBA to the upper layer. This function should finalize all HBA-specific +info in the target_info structure, then do a scsi_inquiry and check the +return code. If this is not SCSI_RET_DEVICE_DOWN the target should be +marked TGT_ALIVE. + + +COMMAND TERMINATION + +Generally, command termination should be reported to the upper layers +by setting the tgt->done field to the proper value [it should remain +SCSI_RET_IN_PROGRESS while the command is executing] and invoking the +target's completion routine, like: + if (tgt->ior) { + LOG(0xA,"ops->restart"); + (*tgt->dev_ops->restart)( tgt, TRUE); + } +Note that early on some commands will actually wait for completion +by spinning on the tgt->done value, because autoconf happens when +threads and the scheduler are not working. + +Return SCSI_RET_RETRY if the target was busy, the command will be retried +as appropriate. + +Check the completion routines [in rz_disk.c and rz_tape.c for instance] +if you are not sure what to return in a troubled case. + +HBA CHIPS GOTCHAS + +All of the examples so far use the idea of 'scripts': the interrupt routine +matches the chip state with what is expected and if this is ok (it is +in the common important case) it just calls a prepackaged function. +We have found this to be _extremely_ simpler than using a state machine +of various ridiculous and erroneous sorts, and much more handy for debugging +also. Not to mention the saving on code. +Nonetheless, there really are no restrictions on how to structure the HBA +code, if you prefer state machines go ahead and use them! + +Scheduling of the bus among competing targets is one of the major missing +pieces for simple HBAs. A winning strategy used so far is as follows. +Allocate a queue_head_t of waiting_targets in your foo_softc, and two +target_info_t pointers next_target and active_target. A three-valued +state field is also needed. If you enter the foo_go() routine +and find the state&BUSY simply enqueue_tail() your tgt on the waiting_targets +queue. Otherwise mark the bus BUSY, set next_target to tgt, and proceed +to a selection attempt. +Note that the attempt might fail and a reselection win over it, therefore +the attempt_selection() routine should just retrieve the next_target +and install it in active_target, start the selection and let the interrupt +routine take care of the rest [see scsi_5380 for a different setup]. +If a reselection wins we should note that we had a COLLISION in the state +field, install the reconecting target and proceed to completion. +When either a command is complete or a target disconnects you should invoke +a foo_release_bus() routine, which might look like: + +boolean_t +foo_release_bus(foo) + register foo_softc_t foo; +{ + boolean_t ret = TRUE; + + LOG(9,"release"); + if (foo->state & FOO_STATE_COLLISION) { + + LOG(0xB,"collided"); + foo->state &= ~FOO_STATE_COLLISION; + foo_attempt_selection(foo); + + } else if (queue_empty(&foo->waiting_targets)) { + + foo->state &= ~FOO_STATE_BUSY; + foo->active_target = 0; + foo->script = 0; + ret = FALSE; + + } else { + + LOG(0xC,"dequeue"); + foo->next_target = (target_info_t *) + dequeue_head(&foo->waiting_targets); + foo_attempt_selection(foo); + } + return ret; +} + +which indicates whether the bus has been handed off to a new target or not. +This provides the desired FIFO scheduling of the bus and gives maximum +parallelism when targets are allowed to (and infact do) disconnect. + +An area where there are problems most times is how to minimize the +interaction of selections and reselections in, e.g. foo_attempt_selection(). +This is very much chip specific, but sneaking on the SCSI bus should +be a viable alternative in most cases. Check in the specs what happens +if you send a command while there is already a reselection pending: +a well behaved chip would ignore the command and not screwup its status. +[Keep in mind that even if _now_ there is no reselection indication + on the next cycle there might be and you won't see it!] + +RANDOM GOTCHAS + +A number of workstations do not provide real DMA support [do not ask me why] +but rather a special 'buffer' more or less wide where you have to copy +data to and from. This has been handled, see esp the 52C94 code which has +even the extreme optimization of issuing the send command even before +the data has been copied into the buffer! We have handled even machines +where no DMA at all was provided. + +Speaking of DMA.. many of these chips 'prefetch' data, or have a FIFO +on board (they have to if they do synch xfers), and when the target +disconnects it is always a pain to find out how many bytes exactly did we +xfer. Be advised that this hurdle exists, and that the best way to +debug your code here is to use a tape. A safe way is to initially +disable disconnects [so that you can get the system up from disk] +and enable them only on the tape unit that you are using for testing. +Later on enable disks but make sure you have some way to recover from +a zapped disk ! + +MOVING TO USER SPACE + +All examples have hooks for user-space versions of the driver, the +ones for 54C94 and 7061 actually do work. Look in mapped_scsi.c +for how this is done, it is fairly simple as far as the kernel is +concerned. To keep the option of mapping to user space open you +should structure your interrupt routine such that it does all the +state gathering and clearing of the interrupt right away. This +scheme gives you some assurance that your code will keep on working +when the interrupt processing is actually delayed and you recover +the interrupt state from the saved structure in the mapped area. + + +IMPROVEMENTS + +There are a variety of things to be done still, for instance: + +- rewrite scsi_slave() and scsi_attach() to be fully SCSI-II compliant. + There are only comments right now as to how that should be done. + +- add enough machinery to the tape code to be able to recover from + bus resets. Do so in such a way that other devices might use the ideas. + +- add more devices, like printers scanners modems etc that are currently + missing + +- add a 'generic' set_status flavor which simply executes a scsi command + passed in from user. This seems the way many vendors and other people + have strutured their drivers, it would make it possible to have a common + user-level program to do special maintainance work like, for instance, + reformatting of disks. + diff --git a/scsi/adapters/scsi_33C93.h b/scsi/adapters/scsi_33C93.h new file mode 100644 index 0000000..454a8eb --- /dev/null +++ b/scsi/adapters/scsi_33C93.h @@ -0,0 +1,396 @@ +/* + * Mach Operating System + * Copyright (c) 1991,1990,1989 Carnegie Mellon University + * All Rights Reserved. + * + * Permission to use, copy, modify and distribute this software and its + * documentation is hereby granted, provided that both the copyright + * notice and this permission notice appear in all copies of the + * software, derivative works or modified versions, and any portions + * thereof, and that both notices appear in supporting documentation. + * + * CARNEGIE MELLON ALLOWS FREE USE OF THIS SOFTWARE IN ITS "AS IS" + * CONDITION. CARNEGIE MELLON DISCLAIMS ANY LIABILITY OF ANY KIND FOR + * ANY DAMAGES WHATSOEVER RESULTING FROM THE USE OF THIS SOFTWARE. + * + * Carnegie Mellon requests users of this software to return to + * + * Software Distribution Coordinator or Software.Distribution@CS.CMU.EDU + * School of Computer Science + * Carnegie Mellon University + * Pittsburgh PA 15213-3890 + * + * any improvements or extensions that they make and grant Carnegie Mellon + * the rights to redistribute these changes. + */ +/* + * File: scsi_33C93.h + * Author: Alessandro Forin, Carnegie Mellon University + * Date: 8/91 + * + * Defines for the WD/AMD 33C93 SBIC (SCSI Bus Interface Controller) + */ + +/* + * Register map, w mux addressing + */ + +typedef struct { + + volatile unsigned char sbic_myid; /* rw: My SCSI id */ +#define sbic_cdbsize sbic_myid /* w : size of CDB */ + + volatile unsigned char sbic_control; /* rw: Control register */ + + volatile unsigned char sbic_timeo; /* rw: Timeout period */ + + volatile unsigned char sbic_cdb1; /* rw: CDB, 1st byte */ +#define sbic_tsecs sbic_cdb1 /* rw: Xlate: nsectors */ + + volatile unsigned char sbic_cdb2; /* rw: CDB, 2nd byte */ +#define sbic_theads sbic_cdb2 /* rw: Xlate: nheads */ + + volatile unsigned char sbic_cdb3; /* rw: CDB, 3rd byte */ +#define sbic_tcyl_hi sbic_cdb3 /* rw: Xlate: ncyls, hi */ + + volatile unsigned char sbic_cdb4; /* rw: CDB, 4th byte */ +#define sbic_tcyl_lo sbic_cdb4 /* rw: Xlate: ncyls, lo */ + + volatile unsigned char sbic_cdb5; /* rw: CDB, 5th byte */ +#define sbic_addr_hi sbic_cdb5 /* rw: Xlate: address, hi */ + + volatile unsigned char sbic_cdb6; /* rw: CDB, 6th byte */ +#define sbic_addr_2 sbic_cdb6 /* rw: Xlate: address */ + + volatile unsigned char sbic_cdb7; /* rw: CDB, 7th byte */ +#define sbic_addr_3 sbic_cdb7 /* rw: Xlate: address */ + + volatile unsigned char sbic_cdb8; /* rw: CDB, 8th byte */ +#define sbic_addr_lo sbic_cdb8 /* rw: Xlate: address, lo */ + + volatile unsigned char sbic_cdb9; /* rw: CDB, 9th byte */ +#define sbic_secno sbic_cdb9 /* rw: Xlate: sector no */ + + volatile unsigned char sbic_cdb10; /* rw: CDB, 10th byte */ +#define sbic_headno sbic_cdb10 /* rw: Xlate: head no */ + + volatile unsigned char sbic_cdb11; /* rw: CDB, 11th byte */ +#define sbic_cylno_hi sbic_cdb11/* rw: Xlate: cyl no, hi */ + + volatile unsigned char sbic_cdb12; /* rw: CDB, 12th byte */ +#define sbic_cylno_lo sbic_cdb12/* rw: Xlate: cyl no, lo */ + + volatile unsigned char sbic_tlun; /* rw: Target LUN */ + + volatile unsigned char sbic_cmd_phase; /* rw: Command phase */ + + volatile unsigned char sbic_syn; /* rw: Synch xfer params */ + + volatile unsigned char sbic_count_hi; /* rw: Xfer count, hi */ + + volatile unsigned char sbic_count_med; /* rw: Xfer count, med */ + + volatile unsigned char sbic_count_lo; /* rw: Xfer count, lo */ + + volatile unsigned char sbic_selid; /* rw: Target ID (select) */ + + volatile unsigned char sbic_rselid; /* rw: Target ID (reselect) */ + + volatile unsigned char sbic_csr; /* r : Status register */ + + volatile unsigned char sbic_cmd; /* rw: Command register */ + + volatile unsigned char sbic_data; /* rw: FIFO top */ + + char u0; /* unused, padding */ + char u1; /* unused, padding */ + char u2; /* unused, padding */ + char u3; /* unused, padding */ + char u4; /* unused, padding */ + + volatile unsigned char sbic_asr; /* r : Aux Status Register */ + +} sbic_mux_regmap_t; + +/* + * Register map, non mux (indirect) addressing + */ +typedef struct { + volatile unsigned char sbic_asr; /* r : Aux Status Register */ +#define sbic_address sbic_asr /* w : desired register no */ + + volatile unsigned char sbic_value; /* rw: register value */ +} sbic_ind_regmap_t; + +#define sbic_read_reg(regs,regno,val) { \ + (regs)->sbic_address = (regno); \ + wbflush(); \ + (val) = (regs)->sbic_value; \ + } + +#define sbic_write_reg(regs,regno,val) { \ + (regs)->sbic_address = (regno); \ + wbflush(); \ + (regs)->sbic_value = (val); \ + } + +#define SBIC_myid 0 +#define SBIC_cdbsize 0 +#define SBIC_control 1 +#define SBIC_timeo 2 +#define SBIC_cdb1 3 +#define SBIC_tsecs 3 +#define SBIC_cdb2 4 +#define SBIC_theads 4 +#define SBIC_cdb3 5 +#define SBIC_tcyl_hi 5 +#define SBIC_cdb4 6 +#define SBIC_tcyl_lo 6 +#define SBIC_cdb5 7 +#define SBIC_addr_hi 7 +#define SBIC_cdb6 8 +#define SBIC_addr_2 8 +#define SBIC_cdb7 9 +#define SBIC_addr_3 9 +#define SBIC_cdb8 10 +#define SBIC_addr_lo 10 +#define SBIC_cdb9 11 +#define SBIC_secno 11 +#define SBIC_cdb10 12 +#define SBIC_headno 12 +#define SBIC_cdb11 13 +#define SBIC_cylno_hi 13 +#define SBIC_cdb12 14 +#define SBIC_cylno_lo 14 +#define SBIC_tlun 15 +#define SBIC_cmd_phase 16 +#define SBIC_syn 17 +#define SBIC_count_hi 18 +#define SBIC_count_med 19 +#define SBIC_count_lo 20 +#define SBIC_selid 21 +#define SBIC_rselid 22 +#define SBIC_csr 23 +#define SBIC_cmd 24 +#define SBIC_data 25 +/* sbic_asr is addressed directly */ + +/* + * Register defines + */ + +/* + * Auxiliary Status Register + */ + +#define SBIC_ASR_INT 0x80 /* Interrupt pending */ +#define SBIC_ASR_LCI 0x40 /* Last command ignored */ +#define SBIC_ASR_BSY 0x20 /* Busy, only cmd/data/asr readable */ +#define SBIC_ASR_CIP 0x10 /* Busy, cmd unavail also */ +#define SBIC_ASR_xxx 0x0c +#define SBIC_ASR_PE 0x02 /* Parity error (even) */ +#define SBIC_ASR_DBR 0x01 /* Data Buffer Ready */ + +/* + * My ID register, and/or CDB Size + */ + +#define SBIC_ID_FS_8_10 0x00 /* Input clock is 8-10 Mhz */ + /* 11 Mhz is invalid */ +#define SBIC_ID_FS_12_15 0x40 /* Input clock is 12-15 Mhz */ +#define SBIC_ID_FS_16_20 0x80 /* Input clock is 16-20 Mhz */ +#define SBIC_ID_EHP 0x10 /* Enable host parity */ +#define SBIC_ID_EAF 0x08 /* Enable Advanced Features */ +#define SBIC_ID_MASK 0x07 +#define SBIC_ID_CBDSIZE_MASK 0x0f /* if unk SCSI cmd group */ + +/* + * Control register + */ + +#define SBIC_CTL_DMA 0x80 /* Single byte dma */ +#define SBIC_CTL_DBA_DMA 0x40 /* direct buffer acces (bus master)*/ +#define SBIC_CTL_BURST_DMA 0x20 /* continuous mode (8237) */ +#define SBIC_CTL_NO_DMA 0x00 /* Programmed I/O */ +#define SBIC_CTL_HHP 0x10 /* Halt on host parity error */ +#define SBIC_CTL_EDI 0x08 /* Ending disconnect interrupt */ +#define SBIC_CTL_IDI 0x04 /* Intermediate disconnect interrupt*/ +#define SBIC_CTL_HA 0x02 /* Halt on ATN */ +#define SBIC_CTL_HSP 0x01 /* Halt on SCSI parity error */ + +/* + * Timeout period register + * [val in msecs, input clk in Mhz] + */ + +#define SBIC_TIMEOUT(val,clk) ((((val)*(clk))/80)+1) + +/* + * CDBn registers, note that + * cdb11 is used for status byte in target mode (send-status-and-cc) + * cdb12 sez if linked command complete, and w/flag if so + */ + +/* + * Target LUN register + * [holds target status when select-and-xfer] + */ + +#define SBIC_TLUN_VALID 0x80 /* did we receive an Identify msg */ +#define SBIC_TLUN_DOK 0x40 /* Disconnect OK */ +#define SBIC_TLUN_xxx 0x38 +#define SBIC_TLUN_MASK 0x07 + +/* + * Command Phase register + */ + +#define SBIC_CPH_MASK 0x7f /* values/restarts are cmd specific */ +#define SBIC_CPH(p) ((p)&SBIC_CPH_MASK) + +/* + * FIFO register + */ + +#define SBIC_FIFO_DEEP 12 + +/* + * Synchronous xfer register + */ + +#define SBIC_SYN_OFF_MASK 0x0f +#define SBIC_SYN_MAX_OFFSET (SBIC_FIFO_DEEP-1) +#define SBIC_SYN_PER_MASK 0x70 +#define SBIC_SYN_MIN_PERIOD 2 /* upto 8, encoded as 0 */ + +#define SBIC_SYN(o,p) (((o)&SBIC_SYN_OFF_MASK)|(((p)<<4)&SBIC_SYN_PER_MASK)) + +/* + * Transfer count register + * optimal access macros depend on addressing + */ + +/* + * Destination ID (selid) register + */ + +#define SBIC_SID_SCC 0x80 /* Select command chaining (tgt) */ +#define SBIC_SID_DPD 0x40 /* Data phase direction (inittor) */ +# define SBIC_SID_FROM_SCSI 0x40 +# define SBIC_SID_TO_SCSI 0x00 +#define SBIC_SID_xxx 0x38 +#define SBIC_SID_IDMASK 0x07 + +/* + * Source ID (rselid) register + */ + +#define SBIC_RID_ER 0x80 /* Enable reselection */ +#define SBIC_RID_ES 0x40 /* Enable selection */ +#define SBIC_RID_DSP 0x20 /* Disable select parity */ +#define SBIC_RID_SIV 0x08 /* Source ID valid */ +#define SBIC_RID_MASK 0x07 + +/* + * Status register + */ + +#define SBIC_CSR_CAUSE 0xf0 +# define SBIC_CSR_RESET 0x00 /* chip was reset */ +# define SBIC_CSR_CMD_DONE 0x10 /* cmd completed */ +# define SBIC_CSR_CMD_STOPPED 0x20 /* interrupted or abrted*/ +# define SBIC_CSR_CMD_ERR 0x40 /* end with error */ +# define SBIC_CSR_BUS_SERVICE 0x80 /* REQ pending on the bus */ + +#define SBIC_CSR_QUALIFIER 0x0f + + /* Reset State Interrupts */ +# define SBIC_CSR_RESET 0x00 /* reset w/advanced features*/ +# define SBIC_CSR_RESET_AM 0x01 /* reset w/advanced features*/ + + /* Successful Completion Interrupts */ +# define SBIC_CSR_TARGET 0x10 /* reselect complete */ +# define SBIC_CSR_INITIATOR 0x11 /* select complete */ +# define SBIC_CSR_WO_ATN 0x13 /* tgt mode completion */ +# define SBIC_CSR_W_ATN 0x14 /* ditto */ +# define SBIC_CSR_XLATED 0x15 /* translate address cmd */ +# define SBIC_CSR_S_XFERRED 0x16 /* initiator mode completion*/ +# define SBIC_CSR_XFERRED 0x18 /* phase in low bits */ + + /* Paused or Aborted Interrupts */ +# define SBIC_CSR_MSGIN_W_ACK 0x20 /* (I) msgin, ACK asserted*/ +# define SBIC_CSR_SDP 0x21 /* (I) SDP msg received */ +# define SBIC_CSR_SEL_ABRT 0x22 /* sel/resel aborted */ +# define SBIC_CSR_XFR_PAUSED 0x23 /* (T) no ATN */ +# define SBIC_CSR_XFR_PAUSED_ATN 0x24 /* (T) ATN is asserted */ +# define SBIC_CSR_RSLT_AM 0x27 /* (I) lost selection (AM) */ +# define SBIC_CSR_MIS 0x28 /* (I) xfer aborted, ph mis */ + + /* Terminated Interrupts */ +# define SBIC_CSR_CMD_INVALID 0x40 +# define SBIC_CSR_DISC 0x41 /* (I) tgt disconnected */ +# define SBIC_CSR_SEL_TIMEO 0x42 +# define SBIC_CSR_PE 0x43 /* parity error */ +# define SBIC_CSR_PE_ATN 0x44 /* ditto, ATN is asserted */ +# define SBIC_CSR_XLATE_TOOBIG 0x45 +# define SBIC_CSR_RSLT_NOAM 0x46 /* (I) lost sel, no AM mode */ +# define SBIC_CSR_BAD_STATUS 0x47 /* status byte was nok */ +# define SBIC_CSR_MIS_1 0x48 /* ph mis, see low bits */ + + /* Service Required Interrupts */ +# define SBIC_CSR_RSLT_NI 0x80 /* reselected, no ify msg */ +# define SBIC_CSR_RSLT_IFY 0x81 /* ditto, AM mode, got ify */ +# define SBIC_CSR_SLT 0x82 /* selected, no ATN */ +# define SBIC_CSR_SLT_ATN 0x83 /* selected with ATN */ +# define SBIC_CSR_ATN 0x84 /* (T) ATN asserted */ +# define SBIC_CSR_DISC_1 0x85 /* (I) bus is free */ +# define SBIC_CSR_UNK_GROUP 0x87 /* strange CDB1 */ +# define SBIC_CSR_MIS_2 0x88 /* (I) ph mis, see low bits */ + +#define SBIC_PHASE(csr) SCSI_PHASE(csr) + +/* + * Command register (command codes) + */ + +#define SBIC_CMD_SBT 0x80 /* Single byte xfer qualifier */ +#define SBIC_CMD_MASK 0x7f + + /* Miscellaneous */ +#define SBIC_CMD_RESET 0x00 /* (DTI) lev I */ +#define SBIC_CMD_ABORT 0x01 /* (DTI) lev I */ +#define SBIC_CMD_DISC 0x04 /* ( TI) lev I */ +#define SBIC_CMD_SSCC 0x0d /* ( TI) lev I */ +#define SBIC_CMD_SET_IDI 0x0f /* (DTI) lev I */ +#define SBIC_CMD_XLATE 0x18 /* (DT ) lev II */ + + /* Initiator state */ +#define SBIC_CMD_SET_ATN 0x02 /* ( I) lev I */ +#define SBIC_CMD_CLR_ACK 0x03 /* ( I) lev I */ +#define SBIC_CMD_XFER_INFO 0x20 /* ( I) lev II */ + + /* Target state */ +#define SBIC_CMD_SND_DISC 0x0e /* ( T ) lev II */ +#define SBIC_CMD_RCV_CMD 0x10 /* ( T ) lev II */ +#define SBIC_CMD_RCV_DATA 0x11 /* ( T ) lev II */ +#define SBIC_CMD_RCV_MSG_OUT 0x12 /* ( T ) lev II */ +#define SBIC_CMD_RCV 0x13 /* ( T ) lev II */ +#define SBIC_CMD_SND_STATUS 0x14 /* ( T ) lev II */ +#define SBIC_CMD_SND_DATA 0x15 /* ( T ) lev II */ +#define SBIC_CMD_SND_MSG_IN 0x16 /* ( T ) lev II */ +#define SBIC_CMD_SND 0x17 /* ( T ) lev II */ + + /* Disconnected state */ +#define SBIC_CMD_RESELECT 0x05 /* (D ) lev II */ +#define SBIC_CMD_SEL_ATN 0x06 /* (D ) lev II */ +#define SBIC_CMD_SEL 0x07 /* (D ) lev II */ +#define SBIC_CMD_SEL_ATN_XFER 0x08 /* (D I) lev II */ +#define SBIC_CMD_SEL_XFER 0x09 /* (D I) lev II */ +#define SBIC_CMD_RESELECT_RECV 0x0a /* (DT ) lev II */ +#define SBIC_CMD_RESELECT_SEND 0x0b /* (DT ) lev II */ +#define SBIC_CMD_WAIT_SEL_RECV 0x0c /* (DT ) lev II */ + + +/* approximate, but we won't do SBT on selects */ +#define sbic_isa_select(cmd) (((cmd)>0x5)&&((cmd)<0xa)) + diff --git a/scsi/adapters/scsi_33C93_hdw.c b/scsi/adapters/scsi_33C93_hdw.c new file mode 100644 index 0000000..169ccbf --- /dev/null +++ b/scsi/adapters/scsi_33C93_hdw.c @@ -0,0 +1,2078 @@ +/* + * Mach Operating System + * Copyright (c) 1991,1990,1989 Carnegie Mellon University + * All Rights Reserved. + * + * Permission to use, copy, modify and distribute this software and its + * documentation is hereby granted, provided that both the copyright + * notice and this permission notice appear in all copies of the + * software, derivative works or modified versions, and any portions + * thereof, and that both notices appear in supporting documentation. + * + * CARNEGIE MELLON ALLOWS FREE USE OF THIS SOFTWARE IN ITS AS-IS + * CONDITION. CARNEGIE MELLON DISCLAIMS ANY LIABILITY OF ANY KIND FOR + * ANY DAMAGES WHATSOEVER RESULTING FROM THE USE OF THIS SOFTWARE. + * + * Carnegie Mellon requests users of this software to return to + * + * Software Distribution Coordinator or Software.Distribution@CS.CMU.EDU + * School of Computer Science + * Carnegie Mellon University + * Pittsburgh PA 15213-3890 + * + * any improvements or extensions that they make and grant Carnegie Mellon + * the rights to redistribute these changes. + */ +/* + * File: scsi_33C93_hdw.h + * Author: Alessandro Forin, Carnegie Mellon University + * Date: 8/91 + * + * Bottom layer of the SCSI driver: chip-dependent functions + * + * This file contains the code that is specific to the WD/AMD 33C93 + * SCSI chip (Host Bus Adapter in SCSI parlance): probing, start + * operation, and interrupt routine. + */ + +#if 0 +DISCLAIMER: THIS DOES NOT EVEN COMPILE YET, it went in by mistake. +Code that probably makes some sense is from here to "TILL HERE" + +/* + * This layer works based on small simple 'scripts' that are installed + * at the start of the command and drive the chip to completion. + * The idea comes from the specs of the NCR 53C700 'script' processor. + * + * There are various reasons for this, mainly + * - Performance: identify the common (successful) path, and follow it; + * at interrupt time no code is needed to find the current status + * - Code size: it should be easy to compact common operations + * - Adaptability: the code skeleton should adapt to different chips without + * terrible complications. + * - Error handling: and it is easy to modify the actions performed + * by the scripts to cope with strange but well identified sequences + * + */ + +#include +#if NSBIC > 0 +#include + +#ifdef IRIS +#define PAD(n) char n[3] /* or whatever */ +#define SBIC_MUX_ADDRESSING /* comment out if wrong */ +#define SBIC_CLOCK_FREQUENCY 20 /* FIXME FIXME FIXME */ +#define SBIC_MACHINE_DMA_MODE SBIC_CTL_DMA /* FIXME FIXME FIXME */ + +#define SBIC_SET_RST_ADDR /*SCSI_INIT_ADDR*/ +#define SBIC_CLR_RST_ADDR /*SCSI_RDY_ADDR*/ +#define SBIC_MACHINE_RESET_SCSIBUS(regs,per) \ + { int temp; \ + temp = *(volatile unsigned int *)SBIC_SET_RST_ADDR; \ + delay(per); \ + temp = *(volatile unsigned int *)SBIC_CLR_RST_ADDR; \ + } + +#endif + +#include /* spl definitions */ +#include +#include +#include +#include + +#include +#include + +#include +#include +#include + +/* + * Spell out all combinations of padded/nopadded and mux/nomux + */ +#ifdef PAD +typedef struct { + + volatile unsigned char sbic_myid; /* rw: My SCSI id */ +/*#define sbic_cdbsize sbic_myid /* w : size of CDB */ + PAD(pad0) + volatile unsigned char sbic_control; /* rw: Control register */ + PAD(pad1) + volatile unsigned char sbic_timeo; /* rw: Timeout period */ + PAD(pad2) + volatile unsigned char sbic_cdb1; /* rw: CDB, 1st byte */ + PAD(pad3) + volatile unsigned char sbic_cdb2; /* rw: CDB, 2nd byte */ + PAD(pad4) + volatile unsigned char sbic_cdb3; /* rw: CDB, 3rd byte */ + PAD(pad5) + volatile unsigned char sbic_cdb4; /* rw: CDB, 4th byte */ + PAD(pad6) + volatile unsigned char sbic_cdb5; /* rw: CDB, 5th byte */ + PAD(pad7) + volatile unsigned char sbic_cdb6; /* rw: CDB, 6th byte */ + PAD(pad8) + volatile unsigned char sbic_cdb7; /* rw: CDB, 7th byte */ + PAD(pad9) + volatile unsigned char sbic_cdb8; /* rw: CDB, 8th byte */ + PAD(pad10) + volatile unsigned char sbic_cdb9; /* rw: CDB, 9th byte */ + PAD(pad11) + volatile unsigned char sbic_cdb10; /* rw: CDB, 10th byte */ + PAD(pad12) + volatile unsigned char sbic_cdb11; /* rw: CDB, 11th byte */ + PAD(pad13) + volatile unsigned char sbic_cdb12; /* rw: CDB, 12th byte */ + PAD(pad14) + volatile unsigned char sbic_tlun; /* rw: Target LUN */ + PAD(pad15) + volatile unsigned char sbic_cmd_phase; /* rw: Command phase */ + PAD(pad16) + volatile unsigned char sbic_syn; /* rw: Synch xfer params */ + PAD(pad17) + volatile unsigned char sbic_count_hi; /* rw: Xfer count, hi */ + PAD(pad18) + volatile unsigned char sbic_count_med; /* rw: Xfer count, med */ + PAD(pad19) + volatile unsigned char sbic_count_lo; /* rw: Xfer count, lo */ + PAD(pad20) + volatile unsigned char sbic_selid; /* rw: Target ID (select) */ + PAD(pad21) + volatile unsigned char sbic_rselid; /* rw: Target ID (reselect) */ + PAD(pad22) + volatile unsigned char sbic_csr; /* r : Status register */ + PAD(pad23) + volatile unsigned char sbic_cmd; /* rw: Command register */ + PAD(pad24) + volatile unsigned char sbic_data; /* rw: FIFO top */ + PAD(pad25) + char u0; /* unused, padding */ + PAD(pad26) + char u1; /* unused, padding */ + PAD(pad27) + char u2; /* unused, padding */ + PAD(pad28) + char u3; /* unused, padding */ + PAD(pad29) + char u4; /* unused, padding */ + PAD(pad30) + volatile unsigned char sbic_asr; /* r : Aux Status Register */ + PAD(pad31) + +} sbic_padded_mux_regmap_t; + +typedef struct { + volatile unsigned char sbic_asr; /* r : Aux Status Register */ +/*#define sbic_address sbic_asr /* w : desired register no */ + PAD(pad0); + volatile unsigned char sbic_value; /* rw: register value */ + PAD(pad1); +} sbic_padded_ind_regmap_t; + +#else /* !PAD */ + +typedef sbic_mux_regmap_t sbic_padded_mux_regmap_t; +typedef sbic_ind_regmap_t sbic_padded_ind_regmap_t; + +#endif /* !PAD */ + +/* + * Could have used some non-ANSIsm in the following :-)) + */ +#ifdef SBIC_MUX_ADDRESSING + +typedef sbic_padded_mux_regmap_t sbic_padded_regmap_t; + +#define SET_SBIC_myid(regs,val) (regs)->sbic_myid = (val) +#define GET_SBIC_myid(regs,val) (val) = (regs)->sbic_myid +#define SET_SBIC_cdbsize(regs,val) (regs)->sbic_cdbsize = (val) +#define GET_SBIC_cdbsize(regs,val) (val) = (regs)->sbic_cdbsize +#define SET_SBIC_control(regs,val) (regs)->sbic_control = (val) +#define GET_SBIC_control(regs,val) (val) = (regs)->sbic_control +#define SET_SBIC_timeo(regs,val) (regs)->sbic_timeo = (val) +#define GET_SBIC_timeo(regs,val) (val) = (regs)->sbic_timeo +#define SET_SBIC_cdb1(regs,val) (regs)->sbic_cdb1 = (val) +#define GET_SBIC_cdb1(regs,val) (val) = (regs)->sbic_cdb1 +#define SET_SBIC_cdb2(regs,val) (regs)->sbic_cdb2 = (val) +#define GET_SBIC_cdb2(regs,val) (val) = (regs)->sbic_cdb2 +#define SET_SBIC_cdb3(regs,val) (regs)->sbic_cdb3 = (val) +#define GET_SBIC_cdb3(regs,val) (val) = (regs)->sbic_cdb3 +#define SET_SBIC_cdb4(regs,val) (regs)->sbic_cdb4 = (val) +#define GET_SBIC_cdb4(regs,val) (val) = (regs)->sbic_cdb4 +#define SET_SBIC_cdb5(regs,val) (regs)->sbic_cdb5 = (val) +#define GET_SBIC_cdb5(regs,val) (val) = (regs)->sbic_cdb5 +#define SET_SBIC_cdb6(regs,val) (regs)->sbic_cdb6 = (val) +#define GET_SBIC_cdb6(regs,val) (val) = (regs)->sbic_cdb6 +#define SET_SBIC_cdb7(regs,val) (regs)->sbic_cdb7 = (val) +#define GET_SBIC_cdb7(regs,val) (val) = (regs)->sbic_cdb7 +#define SET_SBIC_cdb8(regs,val) (regs)->sbic_cdb8 = (val) +#define GET_SBIC_cdb8(regs,val) (val) = (regs)->sbic_cdb8 +#define SET_SBIC_cdb9(regs,val) (regs)->sbic_cdb9 = (val) +#define GET_SBIC_cdb9(regs,val) (val) = (regs)->sbic_cdb9 +#define SET_SBIC_cdb10(regs,val) (regs)->sbic_cdb10 = (val) +#define GET_SBIC_cdb10(regs,val) (val) = (regs)->sbic_cdb10 +#define SET_SBIC_cdb11(regs,val) (regs)->sbic_cdb11 = (val) +#define GET_SBIC_cdb11(regs,val) (val) = (regs)->sbic_cdb11 +#define SET_SBIC_cdb12(regs,val) (regs)->sbic_cdb12 = (val) +#define GET_SBIC_cdb12(regs,val) (val) = (regs)->sbic_cdb12 +#define SET_SBIC_tlun(regs,val) (regs)->sbic_tlun = (val) +#define GET_SBIC_tlun(regs,val) (val) = (regs)->sbic_tlun +#define SET_SBIC_cmd_phase(regs,val) (regs)->sbic_cmd_phase = (val) +#define GET_SBIC_cmd_phase(regs,val) (val) = (regs)->sbic_cmd_phase +#define SET_SBIC_syn(regs,val) (regs)->sbic_syn = (val) +#define GET_SBIC_syn(regs,val) (val) = (regs)->sbic_syn +#define SET_SBIC_count_hi(regs,val) (regs)->sbic_count_hi = (val) +#define GET_SBIC_count_hi(regs,val) (val) = (regs)->sbic_count_hi +#define SET_SBIC_count_med(regs,val) (regs)->sbic_count_med = (val) +#define GET_SBIC_count_med(regs,val) (val) = (regs)->sbic_count_med +#define SET_SBIC_count_lo(regs,val) (regs)->sbic_count_lo = (val) +#define GET_SBIC_count_lo(regs,val) (val) = (regs)->sbic_count_lo +#define SET_SBIC_selid(regs,val) (regs)->sbic_selid = (val) +#define GET_SBIC_selid(regs,val) (val) = (regs)->sbic_selid +#define SET_SBIC_rselid(regs,val) (regs)->sbic_rselid = (val) +#define GET_SBIC_rselid(regs,val) (val) = (regs)->sbic_rselid +#define SET_SBIC_csr(regs,val) (regs)->sbic_csr = (val) +#define GET_SBIC_csr(regs,val) (val) = (regs)->sbic_csr +#define SET_SBIC_cmd(regs,val) (regs)->sbic_cmd = (val) +#define GET_SBIC_cmd(regs,val) (val) = (regs)->sbic_cmd +#define SET_SBIC_data(regs,val) (regs)->sbic_data = (val) +#define GET_SBIC_data(regs,val) (val) = (regs)->sbic_data + +#define SBIC_TC_SET(regs,val) { \ + (regs)->sbic_count_hi = ((val)>>16)); \ + (regs)->sbic_count_med = (val)>>8; \ + (regs)->sbic_count_lo = (val); \ + } +#define SBIC_TC_GET(regs,val) { \ + (val) = ((regs)->sbic_count_hi << 16) | \ + ((regs)->sbic_count_med << 8) | \ + ((regs)->sbic_count_lo); \ + } + +#define SBIC_LOAD_COMMAND(regs,cmd,cmdsize) { \ + register char *ptr = (char*)(cmd); \ + (regs)->cis_cdb1 = *ptr++; \ + (regs)->cis_cdb2 = *ptr++; \ + (regs)->cis_cdb3 = *ptr++; \ + (regs)->cis_cdb4 = *ptr++; \ + (regs)->cis_cdb5 = *ptr++; \ + (regs)->cis_cdb6 = *ptr++; \ + if (cmdsize > 6) { \ + (regs)->cis_cdb7 = *ptr++; \ + (regs)->cis_cdb8 = *ptr++; \ + (regs)->cis_cdb9 = *ptr++; \ + (regs)->cis_cdb10 = *ptr++; \ + } \ + if (cmdsize > 10) { \ + (regs)->cis_cdb11 = *ptr++; \ + (regs)->cis_cdb12 = *ptr; \ + } \ + } + +#else /*SBIC_MUX_ADDRESSING*/ + +typedef sbic_padded_ind_regmap_t sbic_padded_regmap_t; + +#define SET_SBIC_myid(regs,val) sbic_write_reg(regs,SBIC_myid,val) +#define GET_SBIC_myid(regs,val) sbic_read_reg(regs,SBIC_myid,val) +#define SET_SBIC_cdbsize(regs,val) sbic_write_reg(regs,SBIC_cdbsize,val) +#define GET_SBIC_cdbsize(regs,val) sbic_read_reg(regs,SBIC_cdbsize,val) +#define SET_SBIC_control(regs,val) sbic_write_reg(regs,SBIC_control,val) +#define GET_SBIC_control(regs,val) sbic_read_reg(regs,SBIC_control,val) +#define SET_SBIC_timeo(regs,val) sbic_write_reg(regs,SBIC_timeo,val) +#define GET_SBIC_timeo(regs,val) sbic_read_reg(regs,SBIC_timeo,val) +#define SET_SBIC_cdb1(regs,val) sbic_write_reg(regs,SBIC_cdb1,val) +#define GET_SBIC_cdb1(regs,val) sbic_read_reg(regs,SBIC_cdb1,val) +#define SET_SBIC_cdb2(regs,val) sbic_write_reg(regs,SBIC_cdb2,val) +#define GET_SBIC_cdb2(regs,val) sbic_read_reg(regs,SBIC_cdb2,val) +#define SET_SBIC_cdb3(regs,val) sbic_write_reg(regs,SBIC_cdb3,val) +#define GET_SBIC_cdb3(regs,val) sbic_read_reg(regs,SBIC_cdb3,val) +#define SET_SBIC_cdb4(regs,val) sbic_write_reg(regs,SBIC_cdb4,val) +#define GET_SBIC_cdb4(regs,val) sbic_read_reg(regs,SBIC_cdb4,val) +#define SET_SBIC_cdb5(regs,val) sbic_write_reg(regs,SBIC_cdb5,val) +#define GET_SBIC_cdb5(regs,val) sbic_read_reg(regs,SBIC_cdb5,val) +#define SET_SBIC_cdb6(regs,val) sbic_write_reg(regs,SBIC_cdb6,val) +#define GET_SBIC_cdb6(regs,val) sbic_read_reg(regs,SBIC_cdb6,val) +#define SET_SBIC_cdb7(regs,val) sbic_write_reg(regs,SBIC_cdb7,val) +#define GET_SBIC_cdb7(regs,val) sbic_read_reg(regs,SBIC_cdb7,val) +#define SET_SBIC_cdb8(regs,val) sbic_write_reg(regs,SBIC_cdb8,val) +#define GET_SBIC_cdb8(regs,val) sbic_read_reg(regs,SBIC_cdb8,val) +#define SET_SBIC_cdb9(regs,val) sbic_write_reg(regs,SBIC_cdb9,val) +#define GET_SBIC_cdb9(regs,val) sbic_read_reg(regs,SBIC_cdb9,val) +#define SET_SBIC_cdb10(regs,val) sbic_write_reg(regs,SBIC_cdb10,val) +#define GET_SBIC_cdb10(regs,val) sbic_read_reg(regs,SBIC_cdb10,val) +#define SET_SBIC_cdb11(regs,val) sbic_write_reg(regs,SBIC_cdb11,val) +#define GET_SBIC_cdb11(regs,val) sbic_read_reg(regs,SBIC_cdb11,val) +#define SET_SBIC_cdb12(regs,val) sbic_write_reg(regs,SBIC_cdb12,val) +#define GET_SBIC_cdb12(regs,val) sbic_read_reg(regs,SBIC_cdb12,val) +#define SET_SBIC_tlun(regs,val) sbic_write_reg(regs,SBIC_tlun,val) +#define GET_SBIC_tlun(regs,val) sbic_read_reg(regs,SBIC_tlun,val) +#define SET_SBIC_cmd_phase(regs,val) sbic_write_reg(regs,SBIC_cmd_phase,val) +#define GET_SBIC_cmd_phase(regs,val) sbic_read_reg(regs,SBIC_cmd_phase,val) +#define SET_SBIC_syn(regs,val) sbic_write_reg(regs,SBIC_syn,val) +#define GET_SBIC_syn(regs,val) sbic_read_reg(regs,SBIC_syn,val) +#define SET_SBIC_count_hi(regs,val) sbic_write_reg(regs,SBIC_count_hi,val) +#define GET_SBIC_count_hi(regs,val) sbic_read_reg(regs,SBIC_count_hi,val) +#define SET_SBIC_count_med(regs,val) sbic_write_reg(regs,SBIC_count_med,val) +#define GET_SBIC_count_med(regs,val) sbic_read_reg(regs,SBIC_count_med,val) +#define SET_SBIC_count_lo(regs,val) sbic_write_reg(regs,SBIC_count_lo,val) +#define GET_SBIC_count_lo(regs,val) sbic_read_reg(regs,SBIC_count_lo,val) +#define SET_SBIC_selid(regs,val) sbic_write_reg(regs,SBIC_selid,val) +#define GET_SBIC_selid(regs,val) sbic_read_reg(regs,SBIC_selid,val) +#define SET_SBIC_rselid(regs,val) sbic_write_reg(regs,SBIC_rselid,val) +#define GET_SBIC_rselid(regs,val) sbic_read_reg(regs,SBIC_rselid,val) +#define SET_SBIC_csr(regs,val) sbic_write_reg(regs,SBIC_csr,val) +#define GET_SBIC_csr(regs,val) sbic_read_reg(regs,SBIC_csr,val) +#define SET_SBIC_cmd(regs,val) sbic_write_reg(regs,SBIC_cmd,val) +#define GET_SBIC_cmd(regs,val) sbic_read_reg(regs,SBIC_cmd,val) +#define SET_SBIC_data(regs,val) sbic_write_reg(regs,SBIC_data,val) +#define GET_SBIC_data(regs,val) sbic_read_reg(regs,SBIC_data,val) + +#define SBIC_TC_SET(regs,val) { \ + sbic_write_reg(regs,SBIC_count_hi,((val)>>16)); \ + (regs)->sbic_value = (val)>>8; wbflush(); \ + (regs)->sbic_value = (val); \ + } +#define SBIC_TC_GET(regs,val) { \ + sbic_read_reg(regs,SBIC_count_hi,(val)); \ + (val) = ((val)<<8) | (regs)->sbic_value; \ + (val) = ((val)<<8) | (regs)->sbic_value; \ + } + +#define SBIC_LOAD_COMMAND(regs,cmd,cmdsize) { + register int n=cmdsize-1; \ + register char *ptr = (char*)(cmd); \ + sbic_write_reg(regs,SBIC_cdb1,*ptr++); \ + while (n-- > 0) (regs)->sbic_value = *ptr++; \ + } + +#endif /*SBIC_MUX_ADDRESSING*/ + +#define GET_SBIC_asr(regs,val) (val) = (regs)->sbic_asr + + +/* + * If all goes well (cross fingers) the typical read/write operation + * should complete in just one interrupt. Therefore our scripts + * have only two parts: a pre-condition and an action. The first + * triggers error handling if not satisfied and in our case it is a match + * of .... + * The action part is just a function pointer, invoked in a standard way. + * The script proceeds only if the action routine returns TRUE. + * See sbic_intr() for how and where this is all done. + */ + +typedef struct script { + struct { /* expected state at interrupt: */ + unsigned char csr; /* interrupt cause */ + unsigned char pha; /* command phase */ + } condition; +/* unsigned char unused[2]; /* unused padding */ + boolean_t (*action)(); /* extra operations */ +} *script_t; + +/* Matching on the condition value */ +#define ANY 0xff +#define SCRIPT_MATCH(csr,pha,cond) \ + (((cond).csr == (csr)) && \ + (((cond).pha == (pha)) || ((cond).pha==ANY))) + + +/* forward decls of script actions */ +boolean_t + sbic_end(), /* all come to an end */ + sbic_get_status(), /* get status from target */ + sbic_dma_in(), /* get data from target via dma */ + sbic_dma_in_r(), /* get data from target via dma (restartable)*/ + sbic_dma_out(), /* send data to target via dma */ + sbic_dma_out_r(), /* send data to target via dma (restartable) */ + sbic_dosynch(), /* negotiate synch xfer */ + sbic_msg_in(), /* receive the disconenct message */ + sbic_disconnected(), /* target has disconnected */ + sbic_reconnect(); /* target reconnected */ + +/* forward decls of error handlers */ +boolean_t + sbic_err_generic(), /* generic handler */ + sbic_err_disconn(), /* target disconnects amidst */ + gimmeabreak(); /* drop into the debugger */ + +int sbic_reset_scsibus(); +boolean_t sbic_probe_target(); +static sbic_wait(); + +/* + * State descriptor for this layer. There is one such structure + * per (enabled) SCSI-33c93 interface + */ +struct sbic_softc { + watchdog_t wd; + sbic_padded_regmap_t *regs; /* 33c93 registers */ + + scsi_dma_ops_t *dma_ops; /* DMA operations and state */ + opaque_t dma_state; + + script_t script; /* what should happen next */ + boolean_t (*error_handler)();/* what if something is wrong */ + int in_count; /* amnt we expect to receive */ + int out_count; /* amnt we are going to ship */ + + volatile char state; +#define SBIC_STATE_BUSY 0x01 /* selecting or currently connected */ +#define SBIC_STATE_TARGET 0x04 /* currently selected as target */ +#define SBIC_STATE_COLLISION 0x08 /* lost selection attempt */ +#define SBIC_STATE_DMA_IN 0x10 /* tgt --> initiator xfer */ +#define SBIC_STATE_AM_MODE 0x20 /* 33c93A with advanced mode (AM) */ + + unsigned char ntargets; /* how many alive on this scsibus */ + unsigned char done; + unsigned char unused; + + scsi_softc_t *sc; /* HBA-indep info */ + target_info_t *active_target; /* the current one */ + + target_info_t *next_target; /* trying to seize bus */ + queue_head_t waiting_targets;/* other targets competing for bus */ + +} sbic_softc_data[NSBIC]; + +typedef struct sbic_softc *sbic_softc_t; + +sbic_softc_t sbic_softc[NSBIC]; + +/* + * Synch xfer parameters, and timing conversions + */ +int sbic_min_period = SBIC_SYN_MIN_PERIOD; /* in cycles = f(ICLK,FSn) */ +int sbic_max_offset = SBIC_SYN_MAX_OFFSET; /* pure number */ + +int sbic_to_scsi_period(regs,a) +{ + unsigned int fs; + + /* cycle = DIV / (2*CLK) */ + /* DIV = FS+2 */ + /* best we can do is 200ns at 20Mhz, 2 cycles */ + + GET_SBIC_myid(regs,fs); + fs = (fs >>6) + 2; /* DIV */ + fs = (fs * 1000) / (SBIC_CLOCK_FREQUENCY<<1); /* Cycle, in ns */ + if (a < 2) a = 8; /* map to Cycles */ + return ((fs*a)>>2); /* in 4 ns units */ +} + +int scsi_period_to_sbic(regs,p) +{ + register unsigned int fs; + + /* Just the inverse of the above */ + + GET_SBIC_myid(regs,fs); + fs = (fs >>6) + 2; /* DIV */ + fs = (fs * 1000) / (SBIC_CLOCK_FREQUENCY<<1); /* Cycle, in ns */ + + ret = p << 2; /* in ns units */ + ret = ret / fs; /* in Cycles */ + if (ret < sbic_min_period) + return sbic_min_period; + /* verify rounding */ + if (sbic_to_scsi_period(regs,ret) < p) + ret++; + return (ret >= 8) ? 0 : ret; +} + +#define u_min(a,b) (((a) < (b)) ? (a) : (b)) + +/* + * Definition of the controller for the auto-configuration program. + */ + +int sbic_probe(), scsi_slave(), scsi_attach(), sbic_go(), sbic_intr(); + +caddr_t sbic_std[NSBIC] = { 0 }; +struct bus_device *sbic_dinfo[NSBIC*8]; +struct bus_ctlr *sbic_minfo[NSBIC]; +struct bus_driver sbic_driver = + { sbic_probe, scsi_slave, scsi_attach, sbic_go, sbic_std, "rz", sbic_dinfo, + "sbic", sbic_minfo, BUS_INTR_B4_PROBE}; + + +sbic_set_dmaops(unit, dmaops) + unsigned int unit; + scsi_dma_ops_t *dmaops; +{ + if (unit < NSBIC) + sbic_std[unit] = (caddr_t)dmaops; +} + +/* + * Scripts + */ +struct script +sbic_script_any_cmd[] = { /* started with SEL & XFER */ + {{SBIC_CSR_S_XFERRED, 0x60}, sbic_get_status}, +}, + +sbic_script_try_synch[] = { /* started with SEL */ + {{SBIC_CSR_INITIATOR, ANY}, sbic_dosynch}, + {{SBIC_CSR_S_XFERRED, 0x60}, sbic_get_status}, +}; + + +#define DEBUG +#ifdef DEBUG + +#define PRINT(x) if (scsi_debug) printf x + +sbic_state(regs, overrule) + sbic_padded_regmap_t *regs; +{ + register unsigned char asr,tmp; + + if (regs == 0) { + if (sbic_softc[0]) + regs = sbic_softc[0]->regs; + else + regs = (sbic_padded_regmap_t*)0xXXXXXXXX; + } + + GET_SBIC_asr(regs,asr); + + if ((asr & SBIC_ASR_BSY) && !overrule) + db_printf("-BUSY- "); + else { + unsigned char tlun,pha,selid,rselid; + unsigned int cnt; + GET_SBIC_tlun(regs,tlun); + GET_SBIC_cmd_phase(regs,pha); + GET_SBIC_selid(regs,selid); + GET_SBIC_rselid(regs,rselid); + SBIC_TC_GET(regs,cnt); + db_printf("tc %x tlun %x sel %x rsel %x pha %x ", + cnt, tlun, selid, rselid, pha); + } + + if (asr & SBIC_ASR_INT) + db_printf("-INT- "); + else { + GET_SBIC_csr(regs,tmp); + db_printf("csr %x ", tmp); + } + + if (asr & SBIC_ASR_CIP) + db_printf("-CIP-\n"); + else { + GET_SBIC_cmd(regs,tmp); + db_printf("cmd %x\n", tmp); + } + return 0; +} + +sbic_target_state(tgt) + target_info_t *tgt; +{ + if (tgt == 0) + tgt = sbic_softc[0]->active_target; + if (tgt == 0) + return 0; + db_printf("@x%x: fl %x dma %X+%x cmd %x@%X id %x per %x off %x ior %X ret %X\n", + tgt, + tgt->flags, tgt->dma_ptr, tgt->transient_state.dma_offset, tgt->cur_cmd, + tgt->cmd_ptr, tgt->target_id, tgt->sync_period, tgt->sync_offset, + tgt->ior, tgt->done); + if (tgt->flags & TGT_DISCONNECTED){ + script_t spt; + + spt = tgt->transient_state.script; + db_printf("disconnected at "); + db_printsym(spt,1); + db_printf(": %x %x ", spt->condition.csr, spt->condition.pha); + db_printsym(spt->action,1); + db_printf(", "); + db_printsym(tgt->transient_state.handler, 1); + db_printf("\n"); + } + + return 0; +} + +sbic_all_targets(unit) +{ + int i; + target_info_t *tgt; + for (i = 0; i < 8; i++) { + tgt = sbic_softc[unit]->sc->target[i]; + if (tgt) + sbic_target_state(tgt); + } +} + +sbic_script_state(unit) +{ + script_t spt = sbic_softc[unit]->script; + + if (spt == 0) return 0; + db_printsym(spt,1); + db_printf(": %x %x ", spt->condition.csr, spt->condition.pha); + db_printsym(spt->action,1); + db_printf(", "); + db_printsym(sbic_softc[unit]->error_handler, 1); + return 0; +} + +#define TRMAX 200 +int tr[TRMAX+3]; +int trpt, trpthi; +#define TR(x) tr[trpt++] = x +#define TRWRAP trpthi = trpt; trpt = 0; +#define TRCHECK if (trpt > TRMAX) {TRWRAP} + +#define TRACE + +#ifdef TRACE + +#define LOGSIZE 256 +int sbic_logpt; +char sbic_log[LOGSIZE]; + +#define MAXLOG_VALUE 0x1e +struct { + char *name; + unsigned int count; +} logtbl[MAXLOG_VALUE]; + +static LOG(e,f) + char *f; +{ + sbic_log[sbic_logpt++] = (e); + if (sbic_logpt == LOGSIZE) sbic_logpt = 0; + if ((e) < MAXLOG_VALUE) { + logtbl[(e)].name = (f); + logtbl[(e)].count++; + } +} + +sbic_print_log(skip) + int skip; +{ + register int i, j; + register unsigned char c; + + for (i = 0, j = sbic_logpt; i < LOGSIZE; i++) { + c = sbic_log[j]; + if (++j == LOGSIZE) j = 0; + if (skip-- > 0) + continue; + if (c < MAXLOG_VALUE) + db_printf(" %s", logtbl[c].name); + else + db_printf("-x%x", c & 0x7f); + } +} + +sbic_print_stat() +{ + register int i; + register char *p; + for (i = 0; i < MAXLOG_VALUE; i++) { + if (p = logtbl[i].name) + printf("%d %s\n", logtbl[i].count, p); + } +} + +#else /*TRACE*/ +#define LOG(e,f) +#define LOGSIZE +#endif /*TRACE*/ + +#else /*DEBUG*/ +#define PRINT(x) +#define LOG(e,f) +#define LOGSIZE + +#endif /*DEBUG*/ + + +/* + * Probe/Slave/Attach functions + */ + +/* + * Probe routine: + * Should find out (a) if the controller is + * present and (b) which/where slaves are present. + * + * Implementation: + * Send a test-unit-ready to each possible target on the bus + * except of course ourselves. + */ +sbic_probe(reg, ui) + unsigned reg; + struct bus_ctlr *ui; +{ + int unit = ui->unit; + sbic_softc_t sbic = &sbic_softc_data[unit]; + int target_id; + scsi_softc_t *sc; + register sbic_padded_regmap_t *regs; + spl_t s; + boolean_t did_banner = FALSE; + + /* + * We are only called if the right board is there, + * but make sure anyways.. + */ + if (check_memory(reg, 0)) + return 0; + +#if MAPPABLE + /* Mappable version side */ + SBIC_probe(reg, ui); +#endif /*MAPPABLE*/ + + /* + * Initialize hw descriptor, cache some pointers + */ + sbic_softc[unit] = sbic; + sbic->regs = (sbic_padded_regmap_t *) (reg); + + if ((sbic->dma_ops = (scsi_dma_ops_t *)sbic_std[unit]) == 0) + /* use same as unit 0 if undefined */ + sbic->dma_ops = (scsi_dma_ops_t *)sbic_std[0]; + + sbic->dma_state = (*sbic->dma_ops->init)(unit, reg); + + queue_init(&sbic->waiting_targets); + + sc = scsi_master_alloc(unit, sbic); + sbic->sc = sc; + + sc->go = sbic_go; + sc->watchdog = scsi_watchdog; + sc->probe = sbic_probe_target; + sbic->wd.reset = sbic_reset_scsibus; + +#ifdef MACH_KERNEL + sc->max_dma_data = -1; +#else + sc->max_dma_data = scsi_per_target_virtual; +#endif + + regs = sbic->regs; + + /* + * Reset chip, fully. Note that interrupts are already enabled. + */ + s = splbio(); + if (sbic_reset(regs, TRUE)) + sbic->state |= SBIC_STATE_AM_MODE; + + /* + * Our SCSI id on the bus. + * The user can probably set this via the prom. + * If not, it is easy to fix: make a default that + * can be changed as boot arg. Otherwise we keep + * what the prom used. + */ +#ifdef unneeded + SET_SBIC_myid(regs, (scsi_initiator_id[unit] & 0x7)); + sbic_reset(regs, TRUE); +#endif + GET_SBIC_myid(regs,sc->initiator_id); + sc->initiator_id &= 0x7; + printf("%s%d: SCSI id %d", ui->name, unit, sc->initiator_id); + + /* + * For all possible targets, see if there is one and allocate + * a descriptor for it if it is there. + */ + for (target_id = 0; target_id < 8; target_id++) { + register unsigned char asr, csr, pha; + register scsi_status_byte_t status; + + /* except of course ourselves */ + if (target_id == sc->initiator_id) + continue; + + SBIC_TC_SET(regs,0); + SET_SBIC_selid(regs,target_id); + SET_SBIC_timo(regs,SBIC_TIMEOUT(250,SBIC_CLOCK_FREQUENCY)); + + /* + * See if the unit is ready. + * XXX SHOULD inquiry LUN 0 instead !!! + */ + { + scsi_command_test_unit_ready_y c; + bzero(&c, sizeof(c)); + c.scsi_cmd_code = SCSI_CMD_TEST_UNIT_READY; + SBIC_LOAD_COMMAND(regs,&c,sizeof(c)); + } + + /* select and send it */ + SET_SBIC_cmd(regs,SBIC_CMD_SEL_XFER); + + /* wait for the chip to complete, or timeout */ + asr = sbic_wait(regs, SBIC_ASR_INT); + GET_SBIC_csr(regs,csr); + + /* + * Check if the select timed out + */ + GET_SBIC_cmd_phase(regs,pha); + if ((SBIC_CPH(pha) == 0) && (csr & SBIC_CSR_CMD_ERR)) { + /* noone out there */ +#if notsure + SET_SBIC_cmd(regs,SBIC_CMD_DISC); + asr = sbic_wait(regs, SBIC_ASR_INT); + GET_SBIC_csr(regs,csr); +#endif + continue; + } + + printf(",%s%d", did_banner++ ? " " : " target(s) at ", + target_id); + + if (SBIC_CPH(pha) < 0x60) + /* XXX recover by hand XXX */ + panic(" target acts weirdo"); + + GET_SBIC_tlun(regs,status.bits); + + if (status.st.scsi_status_code != SCSI_ST_GOOD) + scsi_error( 0, SCSI_ERR_STATUS, status.bits, 0); + + /* + * Found a target + */ + sbic->ntargets++; + { + register target_info_t *tgt; + tgt = scsi_slave_alloc(sc->masterno, target_id, sbic); + + (*sbic->dma_ops->new_target)(sbic->dma_state, tgt); + } + } + + printf(".\n"); + + splx(s); + return 1; +} + +boolean_t +sbic_probe_target(tgt, ior) + target_info_t *tgt; + io_req_t ior; +{ + sbic_softc_t sbic = sbic_softc[tgt->masterno]; + boolean_t newlywed; + + newlywed = (tgt->cmd_ptr == 0); + if (newlywed) { + (*sbic->dma_ops->new_target)(sbic->dma_state, tgt); + } + + if (scsi_inquiry(tgt, SCSI_INQ_STD_DATA) == SCSI_RET_DEVICE_DOWN) + return FALSE; + + tgt->flags = TGT_ALIVE; + return TRUE; +} + +static sbic_wait(regs, until) + sbic_padded_regmap_t *regs; + char until; +{ + register unsigned char val; + int timeo = 1000000; + + GET_SBIC_asr(regs,val); + while ((val & until) == 0) { + if (!timeo--) { + printf("sbic_wait TIMEO with x%x\n", regs->sbic_csr); + break; + } + delay(1); + GET_SBIC_asr(regs,val); + } + return val; +} + +boolean_t +sbic_reset(regs, quick) + sbic_padded_regmap_t *regs; +{ + char my_id, csr; + + /* preserve our ID for now */ + GET_SBIC_myid(regs,my_id); + my_id &= SBIC_ID_MASK; + + if (SBIC_CLOCK_FREQUENCY < 11) + my_id |= SBIC_ID_FS_8_10; + else if (SBIC_CLOCK_FREQUENCY < 16) + my_id |= SBIC_ID_FS_12_15; + else if (SBIC_CLOCK_FREQUENCY < 21) + my_id |= SBIC_ID_FS_16_20; + + my_id |= SBIC_ID_EAF|SBIC_ID_EHP; + + SET_SBIC_myid(regs,myid); + wbflush(); + + /* + * Reset chip and wait till done + */ + SET_SBIC_cmd(regs,SBIC_CMD_RESET); + delay(25); + + (void) sbic_wait(regs, SBIC_ASR_INT); + GET_SBIC_csr(regs,csr); /* clears interrupt also */ + + /* + * Set up various chip parameters + */ + SET_SBIC_control(regs, SBIC_CTL_HHP|SBIC_CTL_EDI|SBIC_CTL_HSP| + SBIC_MACHINE_DMA_MODE); + /* will do IDI on the fly */ + SET_SBIC_rselid(regs, SBIC_RID_ER|SBIC_RID_ES|SBIC_RID_DSP); + SET_SBIC_syn(regs,SBIC_SYN(0,sbic_min_period)); /* asynch for now */ + + /* anything else was zeroed by reset */ + + if (quick) + return (csr & SBIC_CSR_RESET_AM); + + /* + * reset the scsi bus, the interrupt routine does the rest + * or you can call sbic_bus_reset(). + */ + /* + * Now HOW do I do this ? I just want to drive the SCSI "RST" + * signal true for about 25 usecs; But the chip has no notion + * of such a signal at all. The spec suggest that the chip's + * reset pin be connected to the RST signal, which makes this + * operation a machdep one. + */ + SBIC_MACHINE_RESET_SCSIBUS(regs, 30); + + return (csr & SBIC_CSR_RESET_AM); +} + +/* + * Operational functions + */ + +/* + * Start a SCSI command on a target + */ +sbic_go(tgt, cmd_count, in_count, cmd_only) + target_info_t *tgt; + boolean_t cmd_only; +{ + sbic_softc_t sbic; + register spl_t s; + boolean_t disconn; + script_t scp; + boolean_t (*handler)(); + + LOG(1,"go"); + + sbic = (sbic_softc_t)tgt->hw_state; + + tgt->transient_state.cmd_count = cmd_count; /* keep it here */ + + (*sbic->dma_ops->map)(sbic->dma_state, tgt); + + disconn = BGET(scsi_might_disconnect,tgt->masterno,tgt->target_id); + disconn = disconn && (sbic->ntargets > 1); + disconn |= BGET(scsi_should_disconnect,tgt->masterno,tgt->target_id); + + /* + * Setup target state + */ + tgt->done = SCSI_RET_IN_PROGRESS; + + handler = (disconn) ? sbic_err_disconn : sbic_err_generic; + scp = sbic_script_any_cmd; + + switch (tgt->cur_cmd) { + case SCSI_CMD_READ: + case SCSI_CMD_LONG_READ: + LOG(2,"readop"); + break; + case SCSI_CMD_WRITE: + case SCSI_CMD_LONG_WRITE: + LOG(0x1a,"writeop"); + break; + case SCSI_CMD_INQUIRY: + /* This is likely the first thing out: + do the synch neg if so */ + if (!cmd_only && ((tgt->flags&TGT_DID_SYNCH)==0)) { + scp = sbic_script_try_synch; + tgt->flags |= TGT_TRY_SYNCH; + break; + } + case SCSI_CMD_MODE_SELECT: + case SCSI_CMD_REASSIGN_BLOCKS: + case SCSI_CMD_FORMAT_UNIT: + tgt->transient_state.cmd_count = sizeof(scsi_command_group_0); + tgt->transient_state.out_count = cmd_count - sizeof(scsi_command_group_0); + /* fall through */ + case SCSI_CMD_REQUEST_SENSE: + case SCSI_CMD_MODE_SENSE: + case SCSI_CMD_RECEIVE_DIAG_RESULTS: + case SCSI_CMD_READ_CAPACITY: + case SCSI_CMD_READ_BLOCK_LIMITS: + case SCSI_CMD_READ_TOC: + case SCSI_CMD_READ_SUBCH: + case SCSI_CMD_READ_HEADER: + LOG(0x1c,"cmdop"); + LOG(0x80+tgt->cur_cmd,0); + break; + case SCSI_CMD_TEST_UNIT_READY: + /* + * Do the synch negotiation here, unless prohibited + * or done already + */ + if ( ! (tgt->flags & TGT_DID_SYNCH)) { + scp = sbic_script_try_synch; + tgt->flags |= TGT_TRY_SYNCH; + cmd_only = FALSE; + } + /* fall through */ + default: + LOG(0x1c,"cmdop"); + LOG(0x80+tgt->cur_cmd,0); + break; + } + + tgt->transient_state.script = scp; + tgt->transient_state.handler = handler; + tgt->transient_state.identify = (cmd_only) ? 0xff : + (disconn ? SCSI_IDENTIFY|SCSI_IFY_ENABLE_DISCONNECT : + SCSI_IDENTIFY); + + if (in_count) + tgt->transient_state.in_count = + (in_count < tgt->block_size) ? tgt->block_size : in_count; + else + tgt->transient_state.in_count = 0; + + /* + * See if another target is currently selected on + * this SCSI bus, e.g. lock the sbic structure. + * Note that it is the strategy routine's job + * to serialize ops on the same target as appropriate. + * XXX here and everywhere, locks! + */ + /* + * Protection viz reconnections makes it tricky. + */ + s = splbio(); + + if (sbic->wd.nactive++ == 0) + sbic->wd.watchdog_state = SCSI_WD_ACTIVE; + + if (sbic->state & SBIC_STATE_BUSY) { + /* + * Queue up this target, note that this takes care + * of proper FIFO scheduling of the scsi-bus. + */ + LOG(3,"enqueue"); + enqueue_tail(&sbic->waiting_targets, (queue_entry_t) tgt); + } else { + /* + * It is down to at most two contenders now, + * we will treat reconnections same as selections + * and let the scsi-bus arbitration process decide. + */ + sbic->state |= SBIC_STATE_BUSY; + sbic->next_target = tgt; + sbic_attempt_selection(sbic); + /* + * Note that we might still lose arbitration.. + */ + } + splx(s); +} + +sbic_attempt_selection(sbic) + sbic_softc_t sbic; +{ + target_info_t *tgt; + sbic_padded_regmap_t *regs; + register unsigned char val; + register int out_count; + + regs = sbic->regs; + tgt = sbic->next_target; + + LOG(4,"select"); + LOG(0x80+tgt->target_id,0); + + /* + * We own the bus now.. unless we lose arbitration + */ + sbic->active_target = tgt; + + /* Try to avoid reselect collisions */ + GET_SBIC_asr(regs,val); + if (val & SBIC_ASR_INT) + return; + + /* + * Init bus state variables + */ + sbic->script = tgt->transient_state.script; + sbic->error_handler = tgt->transient_state.handler; + sbic->done = SCSI_RET_IN_PROGRESS; + + sbic->out_count = 0; + sbic->in_count = 0; + + /* Define how the identify msg should be built */ + GET_SBIC_rselid(regs, val); + val &= ~(SBIC_RID_MASK|SBIC_RID_ER); + /* the enable reselection bit is used to build the identify msg */ + if (tgt->transient_state.identify != 0xff) + val |= (tgt->transient_state.identify & SCSI_IFY_ENABLE_DISCONNECT) << 1; + SET_SBIC_rselid(regs, val); + SET_SBIC_tlun(regs, tgt->lun); + + /* + * Start the chip going + */ + out_count = (*sbic->dma_ops->start_cmd)(sbic->dma_state, tgt); + SBIC_TC_PUT(regs, out_count); + + val = tgt->target_id; + if (tgt->transient_state.in_count) + val |= SBIC_SID_FROM_SCSI; + SET_SBIC_selid(regs, val); + + SET_SBIC_timo(regs,SBIC_TIMEOUT(250,SBIC_CLOCK_FREQUENCY)); + + SET_SBIC_syn(regs,SBIC_SYN(tgt->sync_offset,tgt->sync_period)); + + /* ugly little help for compiler */ +#define command out_count + if (tgt->flags & TGT_DID_SYNCH) { + command = (tgt->transient_state.identify == 0xff) ? + SBIC_CMD_SEL_XFER : + SBIC_CMD_SEL_ATN_XFER; /*preferred*/ + } else if (tgt->flags & TGT_TRY_SYNCH) + command = SBIC_CMD_SEL_ATN; + else + command = SBIC_CMD_SEL_XFER; + + /* load 10 bytes anyways, the chip knows how much to use */ + SBIC_LOAD_COMMAND(regs, tgt->cmd_ptr, 10); + + /* Try to avoid reselect collisions */ + GET_SBIC_asr(regs,val); + if (val & SBIC_ASR_INT) + return; + + SET_SBIC_cmd_phase(regs, 0); /* not a resume */ + SET_SBIC_cmd(regs, command); +#undef command +} + +/* + * Interrupt routine + * Take interrupts from the chip + * + * Implementation: + * Move along the current command's script if + * all is well, invoke error handler if not. + */ +sbic_intr(unit, spllevel) + spl_t spllevel; +{ + register sbic_softc_t sbic; + register script_t scp; + register int asr, csr, pha; + register sbic_padded_regmap_t *regs; +#if MAPPABLE + extern boolean_t rz_use_mapped_interface; + + if (rz_use_mapped_interface) + return SBIC_intr(unit,spllevel); +#endif /*MAPPABLE*/ + + sbic = sbic_softc[unit]; + regs = sbic->regs; + + LOG(5,"\n\tintr"); + + /* drop spurious interrupts */ + GET_SBIC_asr(regs, asr); + if ((asr & SBIC_ASR_INT) == 0) + return; + + /* collect ephemeral information */ + GET_SBIC_cmd_phase(regs, pha); + GET_SBIC_csr(regs, csr); + +TR(csr);TR(asr);TR(pha);TRCHECK + + /* XXX verify this is indeed the case for a SCSI RST asserted */ + if ((csr & SBIC_CSR_CAUSE) == SBIC_CSR_RESET) + return sbic_bus_reset(sbic); + + /* we got an interrupt allright */ + if (sbic->active_target) + sbic->wd.watchdog_state = SCSI_WD_ACTIVE; + + splx(spllevel); /* drop priority */ + + if ((sbic->state & SBIC_STATE_TARGET) || + (csr == SBIC_CSR_RSLT_AM) || (csr == SBIC_CSR_RSLT_NOAM) || + (csr == SBIC_CSR_SLT) || (csr == SBIC_CSR_SLT_ATN)) + return sbic_target_intr(sbic); + + /* + * In attempt_selection() we gave the select command even if + * the chip might have been reconnected already. + */ + if ((csr == SBIC_CSR_RSLT_NI) || (csr == SBIC_CSR_RSLT_IFY)) + return sbic_reconnect(sbic, csr, pha); + + /* + * Check for parity errors + */ + if (asr & SBIC_ASR_PE) { + char *msg; +printf("{PE %x,%x}", asr, pha); + + msg = "SCSI bus parity error"; + /* all we can do is to throw a reset on the bus */ + printf( "sbic%d: %s%s", sbic - sbic_softc_data, msg, + ", attempting recovery.\n"); + sbic_reset(regs, FALSE); + return; + } + + if ((scp = sbic->script) == 0) /* sanity */ + return; + + LOG(6,"match"); + if (SCRIPT_MATCH(csr,pha,scp->condition)) { + /* + * Perform the appropriate operation, + * then proceed + */ + if ((*scp->action)(sbic, csr, pha)) { + sbic->script = scp + 1; + } + } else + return (*sbic->error_handler)(sbic, csr, pha); +} + +sbic_target_intr() +{ + panic("SBIC: TARGET MODE !!!\n"); +} + +/* + * Routines that the interrupt code might switch to + */ + +boolean_t +sbic_end(sbic, csr, pha) + register sbic_softc_t sbic; +{ + register target_info_t *tgt; + register io_req_t ior; + + LOG(8,"end"); + + tgt = sbic->active_target; + if ((tgt->done = sbic->done) == SCSI_RET_IN_PROGRESS) + tgt->done = SCSI_RET_SUCCESS; + + sbic->script = 0; + + if (sbic->wd.nactive-- == 1) + sbic->wd.watchdog_state = SCSI_WD_INACTIVE; + + sbic_release_bus(sbic); + + if (ior = tgt->ior) { + (*sbic->dma_ops->end_cmd)(sbic->dma_state, tgt, ior); + LOG(0xA,"ops->restart"); + (*tgt->dev_ops->restart)( tgt, TRUE); + } + + return FALSE; +} + +boolean_t +sbic_release_bus(sbic) + register sbic_softc_t sbic; +{ + boolean_t ret = TRUE; + + LOG(9,"release"); + if (sbic->state & SBIC_STATE_COLLISION) { + + LOG(0xB,"collided"); + sbic->state &= ~SBIC_STATE_COLLISION; + sbic_attempt_selection(sbic); + + } else if (queue_empty(&sbic->waiting_targets)) { + + sbic->state &= ~SBIC_STATE_BUSY; + sbic->active_target = 0; + sbic->script = 0; + ret = FALSE; + + } else { + + LOG(0xC,"dequeue"); + sbic->next_target = (target_info_t *) + dequeue_head(&sbic->waiting_targets); + sbic_attempt_selection(sbic); + } + return ret; +} + +boolean_t +sbic_get_status(sbic, csr, pha) + register sbic_softc_t sbic; +{ + register sbic_padded_regmap_t *regs = sbic->regs; + register scsi2_status_byte_t status; + int len; + io_req_t ior; + register target_info_t *tgt = sbic->active_target; + + LOG(0xD,"get_status"); +TRWRAP + + sbic->state &= ~SBIC_STATE_DMA_IN; + + /* + * Get the status byte + */ + GET_SBIC_tlun(regs, status.bits); + + if (status.st.scsi_status_code != SCSI_ST_GOOD) { + scsi_error(sbic->active_target, SCSI_ERR_STATUS, status.bits, 0); + sbic->done = (status.st.scsi_status_code == SCSI_ST_BUSY) ? + SCSI_RET_RETRY : SCSI_RET_NEED_SENSE; + } else + sbic->done = SCSI_RET_SUCCESS; + + /* Tell DMA engine we are done */ + (*sbic->dma_ops->end_xfer)(sbic->dma_state, tgt, tgt->transient_state.in_count); + + return sbic_end(sbic, csr, pha); + +} + +#if 0 + +boolean_t +sbic_dma_in(sbic, csr, ir) + register sbic_softc_t sbic; +{ + register target_info_t *tgt; + register sbic_padded_regmap_t *regs = sbic->regs; + register int count; + unsigned char ff = regs->sbic_flags; + + LOG(0xE,"dma_in"); + tgt = sbic->active_target; + + sbic->state |= SBIC_STATE_DMA_IN; + + count = (*sbic->dma_ops->start_datain)(sbic->dma_state, tgt); + SBIC_TC_PUT(regs, count); + + if ((sbic->in_count = count) == tgt->transient_state.in_count) + return TRUE; + regs->sbic_cmd = sbic->script->command; + sbic->script = sbic_script_restart_data_in; + return FALSE; +} + +sbic_dma_in_r(sbic, csr, ir) + register sbic_softc_t sbic; +{ + register target_info_t *tgt; + register sbic_padded_regmap_t *regs = sbic->regs; + register int count; + boolean_t advance_script = TRUE; + + + LOG(0xE,"dma_in"); + tgt = sbic->active_target; + + sbic->state |= SBIC_STATE_DMA_IN; + + if (sbic->in_count == 0) { + /* + * Got nothing yet, we just reconnected. + */ + register int avail; + + /* + * Rather than using the messy RFB bit in cnfg2 + * (which only works for synch xfer anyways) + * we just bump up the dma offset. We might + * endup with one more interrupt at the end, + * so what. + * This is done in sbic_err_disconn(), this + * way dma (of msg bytes too) is always aligned + */ + + count = (*sbic->dma_ops->restart_datain_1) + (sbic->dma_state, tgt); + + /* common case of 8k-or-less read ? */ + advance_script = (tgt->transient_state.in_count == count); + + } else { + + /* + * We received some data. + */ + register int offset, xferred; + + /* + * Problem: sometimes we get a 'spurious' interrupt + * right after a reconnect. It goes like disconnect, + * reconnect, dma_in_r, here but DMA is still rolling. + * Since there is no good reason we got here to begin with + * we just check for the case and dismiss it: we should + * get another interrupt when the TC goes to zero or the + * target disconnects. + */ + SBIC_TC_GET(regs,xferred); + if (xferred != 0) + return FALSE; + + xferred = sbic->in_count - xferred; + assert(xferred > 0); + + tgt->transient_state.in_count -= xferred; + assert(tgt->transient_state.in_count > 0); + + count = (*sbic->dma_ops->restart_datain_2) + (sbic->dma_state, tgt, xferred); + + sbic->in_count = count; + SBIC_TC_PUT(regs, count); + regs->sbic_cmd = sbic->script->command; + + (*sbic->dma_ops->restart_datain_3) + (sbic->dma_state, tgt); + + /* last chunk ? */ + if (count == tgt->transient_state.in_count) + sbic->script++; + + return FALSE; + } + + sbic->in_count = count; + SBIC_TC_PUT(regs, count); + + if (!advance_script) { + regs->sbic_cmd = sbic->script->command; + } + return advance_script; +} + + +/* send data to target. Only called to start the xfer */ + +boolean_t +sbic_dma_out(sbic, csr, ir) + register sbic_softc_t sbic; +{ + register sbic_padded_regmap_t *regs = sbic->regs; + register int reload_count; + register target_info_t *tgt; + int command; + + LOG(0xF,"dma_out"); + + SBIC_TC_GET(regs, reload_count); + sbic->extra_count = regs->sbic_flags & SBIC_FLAGS_FIFO_CNT; + reload_count += sbic->extra_count; + SBIC_TC_PUT(regs, reload_count); + sbic->state &= ~SBIC_STATE_DMA_IN; + + tgt = sbic->active_target; + + command = sbic->script->command; + + if ((sbic->out_count = reload_count) >= + tgt->transient_state.out_count) + sbic->script++; + else + sbic->script = sbic_script_restart_data_out; + + if ((*sbic->dma_ops->start_dataout) + (sbic->dma_state, tgt, ®s->sbic_cmd, command)) { + regs->sbic_cmd = command; + } + + return FALSE; +} + +/* send data to target. Called in two different ways: + (a) to restart a big transfer and + (b) after reconnection + */ +boolean_t +sbic_dma_out_r(sbic, csr, ir) + register sbic_softc_t sbic; +{ + register sbic_padded_regmap_t *regs = sbic->regs; + register target_info_t *tgt; + boolean_t advance_script = TRUE; + int count; + + + LOG(0xF,"dma_out"); + + tgt = sbic->active_target; + sbic->state &= ~SBIC_STATE_DMA_IN; + + if (sbic->out_count == 0) { + /* + * Nothing committed: we just got reconnected + */ + count = (*sbic->dma_ops->restart_dataout_1) + (sbic->dma_state, tgt); + + /* is this the last chunk ? */ + advance_script = (tgt->transient_state.out_count == count); + } else { + /* + * We sent some data. + */ + register int offset, xferred; + + SBIC_TC_GET(regs,count); + + /* see comment above */ + if (count) { + return FALSE; + } + + count += (regs->sbic_flags & SBIC_FLAGS_FIFO_CNT); + count -= sbic->extra_count; + xferred = sbic->out_count - count; + assert(xferred > 0); + + tgt->transient_state.out_count -= xferred; + assert(tgt->transient_state.out_count > 0); + + count = (*sbic->dma_ops->restart_dataout_2) + (sbic->dma_state, tgt, xferred); + + /* last chunk ? */ + if (tgt->transient_state.out_count == count) + goto quickie; + + sbic->out_count = count; + + sbic->extra_count = (*sbic->dma_ops->restart_dataout_3) + (sbic->dma_state, tgt, ®s->sbic_fifo); + SBIC_TC_PUT(regs, count); + regs->sbic_cmd = sbic->script->command; + + (*sbic->dma_ops->restart_dataout_4)(sbic->dma_state, tgt); + + return FALSE; + } + +quickie: + sbic->extra_count = (*sbic->dma_ops->restart_dataout_3) + (sbic->dma_state, tgt, ®s->sbic_fifo); + + sbic->out_count = count; + + SBIC_TC_PUT(regs, count); + + if (!advance_script) { + regs->sbic_cmd = sbic->script->command; + } + return advance_script; +} +#endif /*0*/ + +boolean_t +sbic_dosynch(sbic, csr, pha) + register sbic_softc_t sbic; + register unsigned char csr, pha; +{ + register sbic_padded_regmap_t *regs = sbic->regs; + register unsigned char c; + int i, per, offs; + register target_info_t *tgt; + + /* + * Try synch negotiation + * Phase is MSG_OUT here. + */ + tgt = sbic->active_target; + +#if 0 + regs->sbic_cmd = SBIC_CMD_FLUSH; + delay(2); + + per = sbic_min_period; + if (BGET(scsi_no_synchronous_xfer,sbic->sc->masterno,tgt->target_id)) + offs = 0; + else + offs = sbic_max_offset; + + tgt->flags |= TGT_DID_SYNCH; /* only one chance */ + tgt->flags &= ~TGT_TRY_SYNCH; + + regs->sbic_fifo = SCSI_EXTENDED_MESSAGE; + regs->sbic_fifo = 3; + regs->sbic_fifo = SCSI_SYNC_XFER_REQUEST; + regs->sbic_fifo = sbic_to_scsi_period(regs,sbic_min_period); + regs->sbic_fifo = offs; + regs->sbic_cmd = SBIC_CMD_XFER_INFO; + csr = sbic_wait(regs, SBIC_CSR_INT); + ir = regs->sbic_intr; + + if (SCSI_PHASE(csr) != SCSI_PHASE_MSG_IN) + gimmeabreak(); + + regs->sbic_cmd = SBIC_CMD_XFER_INFO; + csr = sbic_wait(regs, SBIC_CSR_INT); + ir = regs->sbic_intr; + + while ((regs->sbic_flags & SBIC_FLAGS_FIFO_CNT) > 0) + c = regs->sbic_fifo; /* see what it says */ + + if (c == SCSI_MESSAGE_REJECT) { + printf(" did not like SYNCH xfer "); + + /* Tk50s get in trouble with ATN, sigh. */ + regs->sbic_cmd = SBIC_CMD_CLR_ATN; + + goto cmd; + } + + /* + * Receive the rest of the message + */ + regs->sbic_cmd = SBIC_CMD_MSG_ACPT; + sbic_wait(regs, SBIC_CSR_INT); + ir = regs->sbic_intr; + + if (c != SCSI_EXTENDED_MESSAGE) + gimmeabreak(); + + regs->sbic_cmd = SBIC_CMD_XFER_INFO; + sbic_wait(regs, SBIC_CSR_INT); + c = regs->sbic_intr; + if (regs->sbic_fifo != 3) + panic("sbic_dosynch"); + + for (i = 0; i < 3; i++) { + regs->sbic_cmd = SBIC_CMD_MSG_ACPT; + sbic_wait(regs, SBIC_CSR_INT); + c = regs->sbic_intr; + + regs->sbic_cmd = SBIC_CMD_XFER_INFO; + sbic_wait(regs, SBIC_CSR_INT); + c = regs->sbic_intr;/*ack*/ + c = regs->sbic_fifo; + + if (i == 1) tgt->sync_period = scsi_period_to_sbic(regs,c); + if (i == 2) tgt->sync_offset = c; + } + +cmd: + regs->sbic_cmd = SBIC_CMD_MSG_ACPT; + csr = sbic_wait(regs, SBIC_CSR_INT); + c = regs->sbic_intr; + + /* phase should normally be command here */ + if (SCSI_PHASE(csr) == SCSI_PHASE_CMD) { + /* test unit ready or what ? */ + regs->sbic_fifo = 0; + regs->sbic_fifo = 0; + regs->sbic_fifo = 0; + regs->sbic_fifo = 0; + regs->sbic_fifo = 0; + regs->sbic_fifo = 0; + SBIC_TC_PUT(regs,0xff); + regs->sbic_cmd = SBIC_CMD_XFER_PAD; /*0x98*/ + csr = sbic_wait(regs, SBIC_CSR_INT); + ir = regs->sbic_intr;/*ack*/ + } + +status: + if (SCSI_PHASE(csr) != SCSI_PHASE_STATUS) + gimmeabreak(); + +#endif + return TRUE; +} + +/* + * The bus was reset + */ +sbic_bus_reset(sbic) + register sbic_softc_t sbic; +{ + register sbic_padded_regmap_t *regs = sbic->regs; + + LOG(0x1d,"bus_reset"); + + /* + * Clear bus descriptor + */ + sbic->script = 0; + sbic->error_handler = 0; + sbic->active_target = 0; + sbic->next_target = 0; + sbic->state &= SBIC_STATE_AM_MODE; /* save this one bit only */ + queue_init(&sbic->waiting_targets); + sbic->wd.nactive = 0; + (void) sbic_reset(regs, TRUE); + + printf("sbic: (%d) bus reset ", ++sbic->wd.reset_count); + delay(scsi_delay_after_reset); /* some targets take long to reset */ + + if (sbic->sc == 0) /* sanity */ + return; + + scsi_bus_was_reset(sbic->sc); +} + +/* + * Disconnect/reconnect mode ops + */ + +/* save all relevant data, free the BUS */ +boolean_t +sbic_disconnected(sbic, csr, pha) + register sbic_softc_t sbic; + register unsigned char csr, pha; + +{ + register target_info_t *tgt; + + LOG(0x11,"disconnected"); + + tgt = sbic->active_target; + tgt->flags |= TGT_DISCONNECTED; + tgt->transient_state.handler = sbic->error_handler; + /* anything else was saved in sbic_err_disconn() */ + + PRINT(("{D%d}", tgt->target_id)); + + sbic_release_bus(sbic); + + return FALSE; +} + +/* See who reconnected, restore BUS */ +boolean_t +sbic_reconnect(sbic, csr, ir) + register sbic_softc_t sbic; + register unsigned char csr, ir; + +{ + register target_info_t *tgt; + sbic_padded_regmap_t *regs; + int id, pha; + + LOG(0x12,"reconnect"); + /* + * See if this reconnection collided with a selection attempt + */ + if (sbic->state & SBIC_STATE_BUSY) + sbic->state |= SBIC_STATE_COLLISION; + + sbic->state |= SBIC_STATE_BUSY; + + /* find tgt */ + regs = sbic->regs; + GET_SBIC_rselid(regs,id); + + id &= 0x7; + + if ((sbic->state & SBIC_STATE_AM) == 0) { + /* Must pick the identify */ + pha = 0x44; + } else + pha = 0x45; + + tgt = sbic->sc->target[id]; + if (id > 7 || tgt == 0) panic("sbic_reconnect"); + + /* synch things*/ + SET_SBIC_syn(regs,SBIC_SYN(tgt->sync_offset,tgt->sync_period)); + + PRINT(("{R%d}", id)); + if (sbic->state & SBIC_STATE_COLLISION) + PRINT(("[B %d-%d]", sbic->active_target->target_id, id)); + + LOG(0x80+id,0); + + sbic->active_target = tgt; + tgt->flags &= ~TGT_DISCONNECTED; + + sbic->script = tgt->transient_state.script; + sbic->error_handler = tgt->transient_state.handler; + sbic->in_count = 0; + sbic->out_count = 0; + +set counter and setup dma, then + + /* Resume the command now */ + SET_SBIC_cmd_phase(regs, pha); + SET_SBIC_cmd(regs, SBIC_CMD_SEL_XFER); + + return FALSE; +} + +TILL HERE + +/* + * Error handlers + */ + +/* + * Fall-back error handler. + */ +sbic_err_generic(sbic, csr, ir) + register sbic_softc_t sbic; +{ + LOG(0x13,"err_generic"); + + /* handle non-existant or powered off devices here */ + if ((ir == SBIC_INT_DISC) && + (sbic_isa_select(sbic->cmd_was)) && + (SBIC_SS(sbic->ss_was) == 0)) { + /* Powered off ? */ + if (sbic->active_target->flags & TGT_FULLY_PROBED) + sbic->active_target->flags = 0; + sbic->done = SCSI_RET_DEVICE_DOWN; + sbic_end(sbic, csr, ir); + return; + } + + switch (SCSI_PHASE(csr)) { + case SCSI_PHASE_STATUS: + if (sbic->script[-1].condition == SCSI_PHASE_STATUS) { + /* some are just slow to get out.. */ + } else + sbic_err_to_status(sbic, csr, ir); + return; + break; + case SCSI_PHASE_DATAI: + if (sbic->script->condition == SCSI_PHASE_STATUS) { +/* printf("{P}");*/ + return; + } + break; + case SCSI_PHASE_DATAO: + if (sbic->script->condition == SCSI_PHASE_STATUS) { + /* + * See comment above. Actually seen on hitachis. + */ +/* printf("{P}");*/ + return; + } + } + gimmeabreak(); +} + +/* + * Handle disconnections as exceptions + */ +sbic_err_disconn(sbic, csr, ir) + register sbic_softc_t sbic; + register unsigned char csr, ir; +{ + register sbic_padded_regmap_t *regs; + register target_info_t *tgt; + int count; + boolean_t callback = FALSE; + + LOG(0x16,"err_disconn"); + + if (SCSI_PHASE(csr) != SCSI_PHASE_MSG_IN) + return sbic_err_generic(sbic, csr, ir); + + regs = sbic->regs; + tgt = sbic->active_target; + + switch (sbic->script->condition) { + case SCSI_PHASE_DATAO: + LOG(0x1b,"+DATAO"); + if (sbic->out_count) { + register int xferred, offset; + + SBIC_TC_GET(regs,xferred); /* temporary misnomer */ + xferred += regs->sbic_flags & SBIC_FLAGS_FIFO_CNT; + xferred -= sbic->extra_count; + xferred = sbic->out_count - xferred; /* ok now */ + tgt->transient_state.out_count -= xferred; + assert(tgt->transient_state.out_count > 0); + + callback = (*sbic->dma_ops->disconn_1) + (sbic->dma_state, tgt, xferred); + + } else { + + callback = (*sbic->dma_ops->disconn_2) + (sbic->dma_state, tgt); + + } + sbic->extra_count = 0; + tgt->transient_state.script = sbic_script_restart_data_out; + break; + + + case SCSI_PHASE_DATAI: + LOG(0x17,"+DATAI"); + if (sbic->in_count) { + register int offset, xferred; + + SBIC_TC_GET(regs,count); + xferred = sbic->in_count - count; + assert(xferred > 0); + +if (regs->sbic_flags & 0xf) +printf("{Xf %x,%x,%x}", xferred, sbic->in_count, regs->sbic_flags & SBIC_FLAGS_FIFO_CNT); + tgt->transient_state.in_count -= xferred; + assert(tgt->transient_state.in_count > 0); + + callback = (*sbic->dma_ops->disconn_3) + (sbic->dma_state, tgt, xferred); + + tgt->transient_state.script = sbic_script_restart_data_in; + if (tgt->transient_state.in_count == 0) + tgt->transient_state.script++; + + } + tgt->transient_state.script = sbic->script; + break; + + case SCSI_PHASE_STATUS: + /* will have to restart dma */ + SBIC_TC_GET(regs,count); + if (sbic->state & SBIC_STATE_DMA_IN) { + register int offset, xferred; + + LOG(0x1a,"+STATUS+R"); + + xferred = sbic->in_count - count; + assert(xferred > 0); + +if (regs->sbic_flags & 0xf) +printf("{Xf %x,%x,%x}", xferred, sbic->in_count, regs->sbic_flags & SBIC_FLAGS_FIFO_CNT); + tgt->transient_state.in_count -= xferred; +/* assert(tgt->transient_state.in_count > 0);*/ + + callback = (*sbic->dma_ops->disconn_4) + (sbic->dma_state, tgt, xferred); + + tgt->transient_state.script = sbic_script_restart_data_in; + if (tgt->transient_state.in_count == 0) + tgt->transient_state.script++; + + } else { + + /* add what's left in the fifo */ + count += (regs->sbic_flags & SBIC_FLAGS_FIFO_CNT); + /* take back the extra we might have added */ + count -= sbic->extra_count; + /* ..and drop that idea */ + sbic->extra_count = 0; + + LOG(0x19,"+STATUS+W"); + + + if ((count == 0) && (tgt->transient_state.out_count == sbic->out_count)) { + /* all done */ + tgt->transient_state.script = sbic->script; + tgt->transient_state.out_count = 0; + } else { + register int xferred, offset; + + /* how much we xferred */ + xferred = sbic->out_count - count; + + tgt->transient_state.out_count -= xferred; + assert(tgt->transient_state.out_count > 0); + + callback = (*sbic->dma_ops->disconn_5) + (sbic->dma_state,tgt,xferred); + + tgt->transient_state.script = sbic_script_restart_data_out; + } + sbic->out_count = 0; + } + break; + default: + gimmeabreak(); + return; + } + sbic_msg_in(sbic,csr,ir); + sbic->script = sbic_script_disconnect; + regs->sbic_cmd = SBIC_CMD_XFER_INFO|SBIC_CMD_DMA; + if (callback) + (*sbic->dma_ops->disconn_callback)(sbic->dma_state, tgt); +} + +/* + * Watchdog + * + * We know that some (name withdrawn) disks get + * stuck in the middle of dma phases... + */ +sbic_reset_scsibus(sbic) + register sbic_softc_t sbic; +{ + register target_info_t *tgt = sbic->active_target; + register sbic_padded_regmap_t *regs = sbic->regs; + register int ir; + + if (scsi_debug && tgt) { + int dmalen; + SBIC_TC_GET(sbic->regs,dmalen); + printf("Target %d was active, cmd x%x in x%x out x%x Sin x%x Sou x%x dmalen x%x\n", + tgt->target_id, tgt->cur_cmd, + tgt->transient_state.in_count, tgt->transient_state.out_count, + sbic->in_count, sbic->out_count, + dmalen); + } + ir = regs->sbic_intr; + if ((ir & SBIC_INT_RESEL) && (SCSI_PHASE(regs->sbic_csr) == SCSI_PHASE_MSG_IN)) { + /* getting it out of the woods is a bit tricky */ + spl_t s = splbio(); + + (void) sbic_reconnect(sbic, regs->sbic_csr, ir); + sbic_wait(regs, SBIC_CSR_INT); + ir = regs->sbic_intr; + regs->sbic_cmd = SBIC_CMD_MSG_ACPT; + splx(s); + } else { + regs->sbic_cmd = SBIC_CMD_BUS_RESET; + delay(35); + } +} + +#endif NSBIC > 0 + +#endif 0 diff --git a/scsi/adapters/scsi_5380.h b/scsi/adapters/scsi_5380.h new file mode 100644 index 0000000..12be922 --- /dev/null +++ b/scsi/adapters/scsi_5380.h @@ -0,0 +1,126 @@ +/* + * Mach Operating System + * Copyright (c) 1991,1990,1989 Carnegie Mellon University + * All Rights Reserved. + * + * Permission to use, copy, modify and distribute this software and its + * documentation is hereby granted, provided that both the copyright + * notice and this permission notice appear in all copies of the + * software, derivative works or modified versions, and any portions + * thereof, and that both notices appear in supporting documentation. + * + * CARNEGIE MELLON ALLOWS FREE USE OF THIS SOFTWARE IN ITS + * CONDITION. CARNEGIE MELLON DISCLAIMS ANY LIABILITY OF ANY KIND FOR + * ANY DAMAGES WHATSOEVER RESULTING FROM THE USE OF THIS SOFTWARE. + * + * Carnegie Mellon requests users of this software to return to + * + * Software Distribution Coordinator or Software.Distribution@CS.CMU.EDU + * School of Computer Science + * Carnegie Mellon University + * Pittsburgh PA 15213-3890 + * + * any improvements or extensions that they make and grant Carnegie the + * rights to redistribute these changes. + */ +/* + * File: scsi_5380.h + * Author: Alessandro Forin, Carnegie Mellon University + * Date: 5/91 + * + * Defines for the NCR 5380 (SCSI chip), aka Am5380 + */ + +/* + * Register map + */ + +typedef struct { + volatile unsigned char sci_data; /* r: Current data */ +#define sci_odata sci_data /* w: Out data */ + volatile unsigned char sci_icmd; /* rw: Initiator command */ + volatile unsigned char sci_mode; /* rw: Mode */ + volatile unsigned char sci_tcmd; /* rw: Target command */ + volatile unsigned char sci_bus_csr; /* r: Bus Status */ +#define sci_sel_enb sci_bus_csr /* w: Select enable */ + volatile unsigned char sci_csr; /* r: Status */ +#define sci_dma_send sci_csr /* w: Start dma send data */ + volatile unsigned char sci_idata; /* r: Input data */ +#define sci_trecv sci_idata /* w: Start dma receive, target */ + volatile unsigned char sci_iack; /* r: Interrupt Acknowledge */ +#define sci_irecv sci_iack /* w: Start dma receive, initiator */ +} sci_regmap_t; + + +/* + * Initiator command register + */ + +#define SCI_ICMD_DATA 0x01 /* rw: Assert data bus */ +#define SCI_ICMD_ATN 0x02 /* rw: Assert ATN signal */ +#define SCI_ICMD_SEL 0x04 /* rw: Assert SEL signal */ +#define SCI_ICMD_BSY 0x08 /* rw: Assert BSY signal */ +#define SCI_ICMD_ACK 0x10 /* rw: Assert ACK signal */ +#define SCI_ICMD_LST 0x20 /* r: Lost arbitration */ +#define SCI_ICMD_DIFF SCI_ICMD_LST /* w: Differential cable */ +#define SCI_ICMD_AIP 0x40 /* r: Arbitration in progress */ +#define SCI_ICMD_TEST SCI_ICMD_AIP /* w: Test mode */ +#define SCI_ICMD_RST 0x80 /* rw: Assert RST signal */ + + +/* + * Mode register + */ + +#define SCI_MODE_ARB 0x01 /* rw: Start arbitration */ +#define SCI_MODE_DMA 0x02 /* rw: Enable DMA xfers */ +#define SCI_MODE_MONBSY 0x04 /* rw: Monitor BSY signal */ +#define SCI_MODE_DMA_IE 0x08 /* rw: Enable DMA complete interrupt */ +#define SCI_MODE_PERR_IE 0x10 /* rw: Interrupt on parity errors */ +#define SCI_MODE_PAR_CHK 0x20 /* rw: Check parity */ +#define SCI_MODE_TARGET 0x40 /* rw: Target mode (Initiator if 0) */ +#define SCI_MODE_BLOCKDMA 0x80 /* rw: Block-mode DMA handshake (MBZ) */ + + +/* + * Target command register + */ + +#define SCI_TCMD_IO 0x01 /* rw: Assert I/O signal */ +#define SCI_TCMD_CD 0x02 /* rw: Assert C/D signal */ +#define SCI_TCMD_MSG 0x04 /* rw: Assert MSG signal */ +#define SCI_TCMD_PHASE_MASK 0x07 /* r: Mask for current bus phase */ +#define SCI_TCMD_REQ 0x08 /* rw: Assert REQ signal */ +#define SCI_TCMD_LAST_SENT 0x80 /* ro: Last byte was xferred + * (not on 5380/1) */ + +#define SCI_PHASE(x) SCSI_PHASE(x) + +/* + * Current (SCSI) Bus status + */ + +#define SCI_BUS_DBP 0x01 /* r: Data Bus parity */ +#define SCI_BUS_SEL 0x02 /* r: SEL signal */ +#define SCI_BUS_IO 0x04 /* r: I/O signal */ +#define SCI_BUS_CD 0x08 /* r: C/D signal */ +#define SCI_BUS_MSG 0x10 /* r: MSG signal */ +#define SCI_BUS_REQ 0x20 /* r: REQ signal */ +#define SCI_BUS_BSY 0x40 /* r: BSY signal */ +#define SCI_BUS_RST 0x80 /* r: RST signal */ + +#define SCI_CUR_PHASE(x) SCSI_PHASE((x)>>2) + +/* + * Bus and Status register + */ + +#define SCI_CSR_ACK 0x01 /* r: ACK signal */ +#define SCI_CSR_ATN 0x02 /* r: ATN signal */ +#define SCI_CSR_DISC 0x04 /* r: Disconnected (BSY==0) */ +#define SCI_CSR_PHASE_MATCH 0x08 /* r: Bus and SCI_TCMD match */ +#define SCI_CSR_INT 0x10 /* r: Interrupt request */ +#define SCI_CSR_PERR 0x20 /* r: Parity error */ +#define SCI_CSR_DREQ 0x40 /* r: DMA request */ +#define SCI_CSR_DONE 0x80 /* r: DMA count is zero */ + diff --git a/scsi/adapters/scsi_5380_hdw.c b/scsi/adapters/scsi_5380_hdw.c new file mode 100644 index 0000000..2fc7d89 --- /dev/null +++ b/scsi/adapters/scsi_5380_hdw.c @@ -0,0 +1,2423 @@ +/* + * Mach Operating System + * Copyright (c) 1991,1990,1989 Carnegie Mellon University + * All Rights Reserved. + * + * Permission to use, copy, modify and distribute this software and its + * documentation is hereby granted, provided that both the copyright + * notice and this permission notice appear in all copies of the + * software, derivative works or modified versions, and any portions + * thereof, and that both notices appear in supporting documentation. + * + * CARNEGIE MELLON ALLOWS FREE USE OF THIS SOFTWARE IN ITS + * CONDITION. CARNEGIE MELLON DISCLAIMS ANY LIABILITY OF ANY KIND FOR + * ANY DAMAGES WHATSOEVER RESULTING FROM THE USE OF THIS SOFTWARE. + * + * Carnegie Mellon requests users of this software to return to + * + * Software Distribution Coordinator or Software.Distribution@CS.CMU.EDU + * School of Computer Science + * Carnegie Mellon University + * Pittsburgh PA 15213-3890 + * + * any improvements or extensions that they make and grant Carnegie the + * rights to redistribute these changes. + */ +/* + * File: scsi_5380_hdw.c + * Author: Alessandro Forin, Carnegie Mellon University + * Date: 4/91 + * + * Bottom layer of the SCSI driver: chip-dependent functions + * + * This file contains the code that is specific to the NCR 5380 + * SCSI chip (Host Bus Adapter in SCSI parlance): probing, start + * operation, and interrupt routine. + */ + +/* + * This layer works based on small simple 'scripts' that are installed + * at the start of the command and drive the chip to completion. + * The idea comes from the specs of the NCR 53C700 'script' processor. + * + * There are various reasons for this, mainly + * - Performance: identify the common (successful) path, and follow it; + * at interrupt time no code is needed to find the current status + * - Code size: it should be easy to compact common operations + * - Adaptability: the code skeleton should adapt to different chips without + * terrible complications. + * - Error handling: and it is easy to modify the actions performed + * by the scripts to cope with strange but well identified sequences + * + */ + +#include +#if NSCI > 0 +#include + +#include +#include +#include +#include +#include + +#include + +#include +#include +#include + +#ifdef VAXSTATION +#define PAD(n) char n[3] +#endif + +#include + +#ifdef PAD +typedef struct { + volatile unsigned char sci_data; /* r: Current data */ +/*#define sci_odata sci_data /* w: Out data */ + PAD(pad0); + + volatile unsigned char sci_icmd; /* rw: Initiator command */ + PAD(pad1); + + volatile unsigned char sci_mode; /* rw: Mode */ + PAD(pad2); + + volatile unsigned char sci_tcmd; /* rw: Target command */ + PAD(pad3); + + volatile unsigned char sci_bus_csr; /* r: Bus Status */ +/*#define sci_sel_enb sci_bus_csr /* w: Select enable */ + PAD(pad4); + + volatile unsigned char sci_csr; /* r: Status */ +/*#define sci_dma_send sci_csr /* w: Start dma send data */ + PAD(pad5); + + volatile unsigned char sci_idata; /* r: Input data */ +/*#define sci_trecv sci_idata /* w: Start dma receive, target */ + PAD(pad6); + + volatile unsigned char sci_iack; /* r: Interrupt Acknowledge */ +/*#define sci_irecv sci_iack /* w: Start dma receive, initiator */ + PAD(pad7); + +} sci_padded_regmap_t; +#else +typedef sci_regmap_t sci_padded_regmap_t; +#endif + +#ifdef VAXSTATION +#define check_memory(addr,dow) ((dow) ? wbadaddr(addr,4) : badaddr(addr,4)) + +/* vax3100 */ +#include +#define STC_5380_A VAX3100_STC_5380_A +#define STC_5380_B VAX3100_STC_5380_B +#define STC_DMAREG_OFF VAX3100_STC_DMAREG_OFF + +static int mem; /* mem++ seems to take approx 0.34 usecs */ +#define delay_1p2_us() {mem++;mem++;mem++;mem++;} +#define my_scsi_id(ctlr) (ka3100_scsi_id((ctlr))) +#endif /* VAXSTATION */ + + +#ifndef STC_5380_A /* cross compile check */ +typedef struct { + int sci_dma_dir, sci_dma_adr; +} *sci_dmaregs_t; +#define STC_DMAREG_OFF 0 +#define SCI_DMA_DIR_WRITE 0 +#define SCI_DMA_DIR_READ 1 +#define STC_5380_A 0 +#define STC_5380_B 0x100 +#define SCI_RAM_SIZE 0x10000 +#endif + +/* + * The 5380 can't tell you the scsi ID it uses, so + * unless there is another way use the defaults + */ +#ifndef my_scsi_id +#define my_scsi_id(ctlr) (scsi_initiator_id[(ctlr)]) +#endif + +/* + * Statically partition the DMA buffer between targets. + * This way we will eventually be able to attach/detach + * drives on-fly. And 18k/target is enough. + */ +#define PER_TGT_DMA_SIZE ((SCI_RAM_SIZE/7) & ~(sizeof(int)-1)) + +/* + * Round to 4k to make debug easier + */ +#define PER_TGT_BUFF_SIZE ((PER_TGT_DMA_SIZE >> 12) << 12) +#define PER_TGT_BURST_SIZE (PER_TGT_BUFF_SIZE>>1) + +/* + * Macros to make certain things a little more readable + */ + +#define SCI_ACK(ptr,phase) (ptr)->sci_tcmd = (phase) +#define SCI_CLR_INTR(regs) {register int temp = regs->sci_iack;} + + +/* + * A script has a two parts: a pre-condition and an action. + * The first triggers error handling if not satisfied and in + * our case it is formed by the current bus phase and connected + * condition as per bus status bits. The action part is just a + * function pointer, invoked in a standard way. The script + * pointer is advanced only if the action routine returns TRUE. + * See sci_intr() for how and where this is all done. + */ + +typedef struct script { + int condition; /* expected state at interrupt */ + int (*action)(); /* action routine */ +} *script_t; + +#define SCRIPT_MATCH(cs,bs) (((bs)&SCI_BUS_BSY)|SCI_CUR_PHASE((bs))) + +#define SCI_PHASE_DISC 0x0 /* sort of .. */ + + +/* forward decls of script actions */ +boolean_t + sci_dosynch(), /* negotiate synch xfer */ + sci_dma_in(), /* get data from target via dma */ + sci_dma_out(), /* send data to target via dma */ + sci_get_status(), /* get status from target */ + sci_end_transaction(), /* all come to an end */ + sci_msg_in(), /* get disconnect message(s) */ + sci_disconnected(); /* current target disconnected */ +/* forward decls of error handlers */ +boolean_t + sci_err_generic(), /* generic error handler */ + sci_err_disconn(), /* when a target disconnects */ + gimmeabreak(); /* drop into the debugger */ + +int sci_reset_scsibus(); +boolean_t sci_probe_target(); + +scsi_ret_t sci_select_target(); + +#ifdef VAXSTATION +/* + * This should be somewhere else, and it was a + * mistake to share this buffer across SCSIs. + */ +struct dmabuffer { + volatile char *base; + char *sbrk; +} dmab[1]; + +volatile char * +sci_buffer_base(unit) +{ + return dmab[unit].base; +} + +sci_buffer_init(dmar, ram) + sci_dmaregs_t dmar; + volatile char *ram; +{ + dmar->sci_dma_rammode = SCI_RAM_EXPMODE; + dmab[0].base = dmab[0].sbrk = (char *) ram; + blkclr((char *) ram, SCI_RAM_SIZE); +} +char * +sci_buffer_sbrk(size) +{ + char *ret = dmab[0].sbrk; + + dmab[0].sbrk += size; + if ((dmab[0].sbrk - dmab[0].base) > SCI_RAM_SIZE) + panic("scialloc"); + return ret; +} + +#endif /* VAXSTATION */ + +/* + * State descriptor for this layer. There is one such structure + * per (enabled) 5380 interface + */ +struct sci_softc { + watchdog_t wd; + sci_padded_regmap_t *regs; /* 5380 registers */ + sci_dmaregs_t dmar; /* DMA controller registers */ + volatile char *buff; /* DMA buffer memory (I/O space) */ + script_t script; + int (*error_handler)(); + int in_count; /* amnt we expect to receive */ + int out_count; /* amnt we are going to ship */ + + volatile char state; +#define SCI_STATE_BUSY 0x01 /* selecting or currently connected */ +#define SCI_STATE_TARGET 0x04 /* currently selected as target */ +#define SCI_STATE_COLLISION 0x08 /* lost selection attempt */ +#define SCI_STATE_DMA_IN 0x10 /* tgt --> initiator xfer */ + + unsigned char ntargets; /* how many alive on this scsibus */ + unsigned char done; + unsigned char extra_byte; + + scsi_softc_t *sc; + target_info_t *active_target; + + target_info_t *next_target; /* trying to seize bus */ + queue_head_t waiting_targets;/* other targets competing for bus */ + +} sci_softc_data[NSCI]; + +typedef struct sci_softc *sci_softc_t; + +sci_softc_t sci_softc[NSCI]; + +/* + * Definition of the controller for the auto-configuration program. + */ + +int sci_probe(), scsi_slave(), sci_go(), sci_intr(); +void scsi_attach(); + +vm_offset_t sci_std[NSCI] = { 0 }; +struct bus_device *sci_dinfo[NSCI*8]; +struct bus_ctlr *sci_minfo[NSCI]; +struct bus_driver sci_driver = + { sci_probe, scsi_slave, scsi_attach, sci_go, sci_std, "rz", sci_dinfo, + "sci", sci_minfo, BUS_INTR_B4_PROBE}; + +/* + * Scripts + */ +struct script +sci_script_data_in[] = { + { SCSI_PHASE_DATAI|SCI_BUS_BSY, sci_dma_in}, + { SCSI_PHASE_STATUS|SCI_BUS_BSY, sci_get_status}, + { SCSI_PHASE_MSG_IN|SCI_BUS_BSY, sci_end_transaction} +}, + +sci_script_data_out[] = { + { SCSI_PHASE_DATAO|SCI_BUS_BSY, sci_dma_out}, + { SCSI_PHASE_STATUS|SCI_BUS_BSY, sci_get_status}, + { SCSI_PHASE_MSG_IN|SCI_BUS_BSY, sci_end_transaction} +}, + +sci_script_cmd[] = { + { SCSI_PHASE_STATUS|SCI_BUS_BSY, sci_get_status}, + { SCSI_PHASE_MSG_IN|SCI_BUS_BSY, sci_end_transaction} +}, + +/* Synchronous transfer neg(oti)ation */ + +sci_script_try_synch[] = { + { SCSI_PHASE_MSG_OUT|SCI_BUS_BSY, sci_dosynch} +}, + +/* Disconnect sequence */ + +sci_script_disconnect[] = { + { SCI_PHASE_DISC, sci_disconnected} +}; + + + +#define u_min(a,b) (((a) < (b)) ? (a) : (b)) + + +#define DEBUG +#ifdef DEBUG + +sci_state(base) + vm_offset_t base; +{ + sci_padded_regmap_t *regs; + sci_dmaregs_t dmar; + extern char *sci; + unsigned dmadr; + int cnt, i; + + if (base == 0) + base = (vm_offset_t)sci; + + for (i = 0; i < 2; i++) { + regs = (sci_padded_regmap_t*) (base + + (i ? STC_5380_B : STC_5380_A)); + dmar = (sci_dmaregs_t) ((char*)regs + STC_DMAREG_OFF); + SCI_DMADR_GET(dmar,dmadr); + SCI_TC_GET(dmar,cnt); + + db_printf("scsi%d: ph %x (sb %x), mode %x, tph %x, csr %x, cmd %x, ", + i, + (unsigned) SCI_CUR_PHASE(regs->sci_bus_csr), + (unsigned) regs->sci_bus_csr, + (unsigned) regs->sci_mode, + (unsigned) regs->sci_tcmd, + (unsigned) regs->sci_csr, + (unsigned) regs->sci_icmd); + db_printf("dma%c %x @ %x\n", + (dmar->sci_dma_dir) ? 'I' : 'O', cnt, dmadr); + } + return 0; +} +sci_target_state(tgt) + target_info_t *tgt; +{ + if (tgt == 0) + tgt = sci_softc[0]->active_target; + if (tgt == 0) + return 0; + db_printf("fl %x dma %x+%x cmd %x id %x per %x off %x ior %x ret %x\n", + tgt->flags, tgt->dma_ptr, tgt->transient_state.dma_offset, + tgt->cmd_ptr, tgt->target_id, tgt->sync_period, tgt->sync_offset, + tgt->ior, tgt->done); + if (tgt->flags & TGT_DISCONNECTED){ + script_t spt; + + spt = tgt->transient_state.script; + db_printf("disconnected at "); + db_printsym(spt,1); + db_printf(": %x ", spt->condition); + db_printsym(spt->action,1); + db_printf(", "); + db_printsym(tgt->transient_state.handler, 1); + db_printf("\n"); + } + + return 0; +} + +sci_all_targets(unit) +{ + int i; + target_info_t *tgt; + for (i = 0; i < 8; i++) { + tgt = sci_softc[unit]->sc->target[i]; + if (tgt) + sci_target_state(tgt); + } +} + +sci_script_state(unit) +{ + script_t spt = sci_softc[unit]->script; + + if (spt == 0) return 0; + db_printsym(spt,1); + db_printf(": %x ", spt->condition); + db_printsym(spt->action,1); + db_printf(", "); + db_printsym(sci_softc[unit]->error_handler, 1); + return 0; + +} + +#define PRINT(x) if (scsi_debug) printf x + +#define TRMAX 200 +int tr[TRMAX+3]; +int trpt, trpthi; +#define TR(x) tr[trpt++] = x +#define TRWRAP trpthi = trpt; trpt = 0; +#define TRCHECK if (trpt > TRMAX) {TRWRAP} + +#define TRACE + +#ifdef TRACE + +#define LOGSIZE 256 +int sci_logpt; +char sci_log[LOGSIZE]; + +#define MAXLOG_VALUE 0x24 +struct { + char *name; + unsigned int count; +} logtbl[MAXLOG_VALUE]; + +static LOG(e,f) + char *f; +{ + sci_log[sci_logpt++] = (e); + if (sci_logpt == LOGSIZE) sci_logpt = 0; + if ((e) < MAXLOG_VALUE) { + logtbl[(e)].name = (f); + logtbl[(e)].count++; + } +} + +sci_print_log(skip) + int skip; +{ + register int i, j; + register unsigned char c; + + for (i = 0, j = sci_logpt; i < LOGSIZE; i++) { + c = sci_log[j]; + if (++j == LOGSIZE) j = 0; + if (skip-- > 0) + continue; + if (c < MAXLOG_VALUE) + db_printf(" %s", logtbl[c].name); + else + db_printf("-%d", c & 0x7f); + } + db_printf("\n"); + return 0; +} + +sci_print_stat() +{ + register int i; + register char *p; + for (i = 0; i < MAXLOG_VALUE; i++) { + if (p = logtbl[i].name) + printf("%d %s\n", logtbl[i].count, p); + } +} + +#else /* TRACE */ +#define LOG(e,f) +#endif /* TRACE */ + +#else /* DEBUG */ +#define PRINT(x) +#define LOG(e,f) +#define TR(x) +#define TRCHECK +#define TRWRAP +#endif /* DEBUG */ + + +/* + * Probe/Slave/Attach functions + */ + +/* + * Probe routine: + * Should find out (a) if the controller is + * present and (b) which/where slaves are present. + * + * Implementation: + * Send an identify msg to each possible target on the bus + * except of course ourselves. + */ +sci_probe(reg, ui) + char *reg; + struct bus_ctlr *ui; +{ + int unit = ui->unit; + sci_softc_t sci = &sci_softc_data[unit]; + int target_id, i; + scsi_softc_t *sc; + register sci_padded_regmap_t *regs; + spl_t s; + boolean_t did_banner = FALSE; + char *cmd_ptr; + static char *here = "sci_probe"; + + /* + * We are only called if the chip is there, + * but make sure anyways.. + */ + regs = (sci_padded_regmap_t *) (reg); + if (check_memory(regs, 0)) + return 0; + +#if notyet + /* Mappable version side */ + SCI_probe(reg, ui); +#endif + + /* + * Initialize hw descriptor + */ + sci_softc[unit] = sci; + sci->regs = regs; + sci->dmar = (sci_dmaregs_t)(reg + STC_DMAREG_OFF); + sci->buff = sci_buffer_base(0); + + queue_init(&sci->waiting_targets); + + sc = scsi_master_alloc(unit, sci); + sci->sc = sc; + + sc->go = sci_go; + sc->probe = sci_probe_target; + sc->watchdog = scsi_watchdog; + sci->wd.reset = sci_reset_scsibus; + +#ifdef MACH_KERNEL + sc->max_dma_data = -1; /* unlimited */ +#else + sc->max_dma_data = scsi_per_target_virtual; +#endif + + scsi_might_disconnect[unit] = 0; /* still true */ + + /* + * Reset chip + */ + s = splbio(); + sci_reset(sci, TRUE); + SCI_CLR_INTR(regs); + + /* + * Our SCSI id on the bus. + */ + + sc->initiator_id = my_scsi_id(unit); + printf("%s%d: my SCSI id is %d", ui->name, unit, sc->initiator_id); + + /* + * For all possible targets, see if there is one and allocate + * a descriptor for it if it is there. + */ + cmd_ptr = sci_buffer_sbrk(0); + for (target_id = 0; target_id < 8; target_id++) { + + register unsigned csr, dsr; + scsi_status_byte_t status; + + /* except of course ourselves */ + if (target_id == sc->initiator_id) + continue; + + if (sci_select_target( regs, sc->initiator_id, target_id, FALSE) == SCSI_RET_DEVICE_DOWN) { + SCI_CLR_INTR(regs); + continue; + } + + printf(",%s%d", did_banner++ ? " " : " target(s) at ", + target_id); + + /* should be command phase here: we selected wo ATN! */ + while (SCI_CUR_PHASE(regs->sci_bus_csr) != SCSI_PHASE_CMD) + ; + + SCI_ACK(regs,SCSI_PHASE_CMD); + + /* build command in dma area */ + { + unsigned char *p = (unsigned char*) cmd_ptr; + + p[0] = SCSI_CMD_TEST_UNIT_READY; + p[1] = + p[2] = + p[3] = + p[4] = + p[5] = 0; + } + + sci_data_out(regs, SCSI_PHASE_CMD, 6, cmd_ptr); + + while (SCI_CUR_PHASE(regs->sci_bus_csr) != SCSI_PHASE_STATUS) + ; + + SCI_ACK(regs,SCSI_PHASE_STATUS); + + sci_data_in(regs, SCSI_PHASE_STATUS, 1, &status.bits); + + if (status.st.scsi_status_code != SCSI_ST_GOOD) + scsi_error( 0, SCSI_ERR_STATUS, status.bits, 0); + + /* get cmd_complete message */ + while (SCI_CUR_PHASE(regs->sci_bus_csr) != SCSI_PHASE_MSG_IN) + ; + + SCI_ACK(regs,SCSI_PHASE_MSG_IN); + + sci_data_in(regs, SCSI_PHASE_MSG_IN, 1, &i); + + /* check disconnected, clear all intr bits */ + while (regs->sci_bus_csr & SCI_BUS_BSY) + ; + SCI_ACK(regs,SCI_PHASE_DISC); + + SCI_CLR_INTR(regs); + + /* ... */ + + /* + * Found a target + */ + sci->ntargets++; + { + register target_info_t *tgt; + + tgt = scsi_slave_alloc(unit, target_id, sci); + + /* "virtual" address for our use */ + tgt->cmd_ptr = sci_buffer_sbrk(PER_TGT_DMA_SIZE); + /* "physical" address for dma engine */ + tgt->dma_ptr = (char*)(tgt->cmd_ptr - sci->buff); +#ifdef MACH_KERNEL +#else /*MACH_KERNEL*/ + fdma_init(&tgt->fdma, scsi_per_target_virtual); +#endif /*MACH_KERNEL*/ + } + } + printf(".\n"); + + splx(s); + return 1; +} + +boolean_t +sci_probe_target(tgt, ior) + target_info_t *tgt; + io_req_t ior; +{ + sci_softc_t sci = sci_softc[tgt->masterno]; + boolean_t newlywed; + + newlywed = (tgt->cmd_ptr == 0); + if (newlywed) { + /* desc was allocated afresh */ + + /* "virtual" address for our use */ + tgt->cmd_ptr = sci_buffer_sbrk(PER_TGT_DMA_SIZE); + /* "physical" address for dma engine */ + tgt->dma_ptr = (char*)(tgt->cmd_ptr - sci->buff); +#ifdef MACH_KERNEL +#else /*MACH_KERNEL*/ + fdma_init(&tgt->fdma, scsi_per_target_virtual); +#endif /*MACH_KERNEL*/ + + } + + if (scsi_inquiry(tgt, SCSI_INQ_STD_DATA) == SCSI_RET_DEVICE_DOWN) + return FALSE; + + tgt->flags = TGT_ALIVE; + return TRUE; +} + + +static sci_wait(preg, until) + volatile unsigned char *preg; +{ + int timeo = 1000000; + /* read it over to avoid bus glitches */ + while ( ((*preg & until) != until) || + ((*preg & until) != until) || + ((*preg & until) != until)) { + delay(1); + if (!timeo--) { + printf("sci_wait TIMEO with x%x\n", *preg); + break; + } + } + return *preg; +} + +scsi_ret_t +sci_select_target(regs, myid, id, with_atn) + register sci_padded_regmap_t *regs; + unsigned char myid, id; + boolean_t with_atn; +{ + register unsigned char bid, icmd; + scsi_ret_t ret = SCSI_RET_RETRY; + + if ((regs->sci_bus_csr & (SCI_BUS_BSY|SCI_BUS_SEL)) && + (regs->sci_bus_csr & (SCI_BUS_BSY|SCI_BUS_SEL)) && + (regs->sci_bus_csr & (SCI_BUS_BSY|SCI_BUS_SEL))) + return ret; + + /* for our purposes.. */ + myid = 1 << myid; + id = 1 << id; + + regs->sci_sel_enb = myid; /* if not there already */ + + regs->sci_odata = myid; + regs->sci_mode |= SCI_MODE_ARB; + /* AIP might not set if BSY went true after we checked */ + for (bid = 0; bid < 20; bid++) /* 20usec circa */ + if (regs->sci_icmd & SCI_ICMD_AIP) + break; + if ((regs->sci_icmd & SCI_ICMD_AIP) == 0) { + goto lost; + } + + delay(2); /* 2.2us arb delay */ + + if (regs->sci_icmd & SCI_ICMD_LST) { + goto lost; + } + + regs->sci_mode &= ~SCI_MODE_PAR_CHK; + bid = regs->sci_data; + + if ((bid & ~myid) > myid) { + goto lost; + } + if (regs->sci_icmd & SCI_ICMD_LST) { + goto lost; + } + + /* Won arbitration, enter selection phase now */ + icmd = regs->sci_icmd & ~(SCI_ICMD_DIFF|SCI_ICMD_TEST); + icmd |= (with_atn ? (SCI_ICMD_SEL|SCI_ICMD_ATN) : SCI_ICMD_SEL); + regs->sci_icmd = icmd; + + if (regs->sci_icmd & SCI_ICMD_LST) { + goto nosel; + } + + /* XXX a target that violates specs might still drive the bus XXX */ + /* XXX should put our id out, and after the delay check nothi XXX */ + /* XXX ng else is out there. XXX */ + + delay_1p2_us(); + + regs->sci_sel_enb = 0; + + regs->sci_odata = myid | id; + + icmd |= SCI_ICMD_BSY|SCI_ICMD_DATA; + regs->sci_icmd = icmd; + + regs->sci_mode &= ~SCI_MODE_ARB; /* 2 deskew delays, too */ + + icmd &= ~SCI_ICMD_BSY; + regs->sci_icmd = icmd; + + /* bus settle delay, 400ns */ + delay(0); /* too much ? */ + + regs->sci_mode |= SCI_MODE_PAR_CHK; + + { + register int timeo = 2500;/* 250 msecs in 100 usecs chunks */ + while ((regs->sci_bus_csr & SCI_BUS_BSY) == 0) + if (--timeo > 0) + delay(100); + else { + goto nodev; + } + } + + icmd &= ~(SCI_ICMD_DATA|SCI_ICMD_SEL); + regs->sci_icmd = icmd; +/* regs->sci_sel_enb = myid;*/ /* looks like we should NOT have it */ + return SCSI_RET_SUCCESS; +nodev: + ret = SCSI_RET_DEVICE_DOWN; + regs->sci_sel_enb = myid; +nosel: + icmd &= ~(SCI_ICMD_DATA|SCI_ICMD_SEL|SCI_ICMD_ATN); + regs->sci_icmd = icmd; +lost: + bid = regs->sci_mode; + bid &= ~SCI_MODE_ARB; + bid |= SCI_MODE_PAR_CHK; + regs->sci_mode = bid; + + return ret; +} + +sci_data_out(regs, phase, count, data) + register sci_padded_regmap_t *regs; + unsigned char *data; +{ + register unsigned char icmd; + + /* ..checks.. */ + + icmd = regs->sci_icmd & ~(SCI_ICMD_DIFF|SCI_ICMD_TEST); +loop: + if (SCI_CUR_PHASE(regs->sci_bus_csr) != phase) + return count; + + while ( ((regs->sci_bus_csr & SCI_BUS_REQ) == 0) && + ((regs->sci_bus_csr & SCI_BUS_REQ) == 0) && + ((regs->sci_bus_csr & SCI_BUS_REQ) == 0)) + ; + icmd |= SCI_ICMD_DATA; + regs->sci_icmd = icmd; + regs->sci_odata = *data++; + icmd |= SCI_ICMD_ACK; + regs->sci_icmd = icmd; + + icmd &= ~(SCI_ICMD_DATA|SCI_ICMD_ACK); + while ( (regs->sci_bus_csr & SCI_BUS_REQ) && + (regs->sci_bus_csr & SCI_BUS_REQ) && + (regs->sci_bus_csr & SCI_BUS_REQ)) + ; + regs->sci_icmd = icmd; + if (--count > 0) + goto loop; + return 0; +} + +sci_data_in(regs, phase, count, data) + register sci_padded_regmap_t *regs; + unsigned char *data; +{ + register unsigned char icmd; + + /* ..checks.. */ + + icmd = regs->sci_icmd & ~(SCI_ICMD_DIFF|SCI_ICMD_TEST); +loop: + if (SCI_CUR_PHASE(regs->sci_bus_csr) != phase) + return count; + + while ( ((regs->sci_bus_csr & SCI_BUS_REQ) == 0) && + ((regs->sci_bus_csr & SCI_BUS_REQ) == 0) && + ((regs->sci_bus_csr & SCI_BUS_REQ) == 0)) + ; + *data++ = regs->sci_data; + icmd |= SCI_ICMD_ACK; + regs->sci_icmd = icmd; + + icmd &= ~SCI_ICMD_ACK; + while ( (regs->sci_bus_csr & SCI_BUS_REQ) && + (regs->sci_bus_csr & SCI_BUS_REQ) && + (regs->sci_bus_csr & SCI_BUS_REQ)) + ; + regs->sci_icmd = icmd; + if (--count > 0) + goto loop; + return 0; + +} + +sci_reset(sci, quickly) + sci_softc_t sci; + boolean_t quickly; +{ + register sci_padded_regmap_t *regs = sci->regs; + register sci_dmaregs_t dma = sci->dmar; + int dummy; + + regs->sci_icmd = SCI_ICMD_TEST; /* don't drive outputs */ + regs->sci_icmd = SCI_ICMD_TEST|SCI_ICMD_RST; + delay(25); + regs->sci_icmd = 0; + + regs->sci_mode = SCI_MODE_PAR_CHK|SCI_MODE_PERR_IE; + regs->sci_tcmd = SCI_PHASE_DISC; /* make sure we do not miss transition */ + regs->sci_sel_enb = 0; + + /* idle the dma controller */ + dma->sci_dma_adr = 0; + dma->sci_dma_dir = SCI_DMA_DIR_WRITE; + SCI_TC_PUT(dma,0); + + /* clear interrupt (two might be queued?) */ + SCI_CLR_INTR(regs); + SCI_CLR_INTR(regs); + + if (quickly) + return; + + /* + * reset the scsi bus, the interrupt routine does the rest + * or you can call sci_bus_reset(). + */ + regs->sci_icmd = SCI_ICMD_RST; + +} + +/* + * Operational functions + */ + +/* + * Start a SCSI command on a target + */ +sci_go(tgt, cmd_count, in_count, cmd_only) + target_info_t *tgt; + boolean_t cmd_only; +{ + sci_softc_t sci; + register spl_t s; + boolean_t disconn; + script_t scp; + boolean_t (*handler)(); + + LOG(1,"go"); + + sci = (sci_softc_t)tgt->hw_state; + + /* + * We cannot do real DMA. + */ +#ifdef MACH_KERNEL +#else /*MACH_KERNEL*/ + if (tgt->ior) + fdma_map(&tgt->fdma, tgt->ior); +#endif /*MACH_KERNEL*/ + + if ((tgt->cur_cmd == SCSI_CMD_WRITE) || + (tgt->cur_cmd == SCSI_CMD_LONG_WRITE)){ + io_req_t ior = tgt->ior; + register int len = ior->io_count; + + tgt->transient_state.out_count = len; + + if (len > PER_TGT_BUFF_SIZE) + len = PER_TGT_BUFF_SIZE; + bcopy( ior->io_data, + tgt->cmd_ptr + cmd_count, + len); + tgt->transient_state.copy_count = len; + + /* avoid leaks */ + if (len < tgt->block_size) { + bzero( tgt->cmd_ptr + cmd_count + len, + tgt->block_size - len); + tgt->transient_state.out_count = tgt->block_size; + } + } else { + tgt->transient_state.out_count = 0; + tgt->transient_state.copy_count = 0; + } + + tgt->transient_state.cmd_count = cmd_count; + + disconn = BGET(scsi_might_disconnect,tgt->masterno,tgt->target_id); + disconn = disconn && (sci->ntargets > 1); + disconn |= BGET(scsi_should_disconnect,tgt->masterno,tgt->target_id); + + /* + * Setup target state + */ + tgt->done = SCSI_RET_IN_PROGRESS; + + handler = (disconn) ? sci_err_disconn : sci_err_generic; + + switch (tgt->cur_cmd) { + case SCSI_CMD_READ: + case SCSI_CMD_LONG_READ: + LOG(0x13,"readop"); + scp = sci_script_data_in; + break; + case SCSI_CMD_WRITE: + case SCSI_CMD_LONG_WRITE: + LOG(0x14,"writeop"); + scp = sci_script_data_out; + break; + case SCSI_CMD_INQUIRY: + /* This is likely the first thing out: + do the synch neg if so */ + if (!cmd_only && ((tgt->flags&TGT_DID_SYNCH)==0)) { + scp = sci_script_try_synch; + tgt->flags |= TGT_TRY_SYNCH; + break; + } + case SCSI_CMD_REQUEST_SENSE: + case SCSI_CMD_MODE_SENSE: + case SCSI_CMD_RECEIVE_DIAG_RESULTS: + case SCSI_CMD_READ_CAPACITY: + case SCSI_CMD_READ_BLOCK_LIMITS: + case SCSI_CMD_READ_TOC: + case SCSI_CMD_READ_SUBCH: + case SCSI_CMD_READ_HEADER: + case 0xc4: /* despised: SCSI_CMD_DEC_PLAYBACK_STATUS */ + case 0xdd: /* despised: SCSI_CMD_NEC_READ_SUBCH_Q */ + case 0xde: /* despised: SCSI_CMD_NEC_READ_TOC */ + scp = sci_script_data_in; + LOG(0x1c,"cmdop"); + LOG(0x80+tgt->cur_cmd,0); + break; + case SCSI_CMD_MODE_SELECT: + case SCSI_CMD_REASSIGN_BLOCKS: + case SCSI_CMD_FORMAT_UNIT: + case 0xc9: /* vendor-spec: SCSI_CMD_DEC_PLAYBACK_CONTROL */ + tgt->transient_state.cmd_count = sizeof_scsi_command(tgt->cur_cmd); + tgt->transient_state.out_count = + cmd_count - tgt->transient_state.cmd_count; + scp = sci_script_data_out; + LOG(0x1c,"cmdop"); + LOG(0x80+tgt->cur_cmd,0); + break; + case SCSI_CMD_TEST_UNIT_READY: + /* + * Do the synch negotiation here, unless prohibited + * or done already + */ + if (tgt->flags & TGT_DID_SYNCH) { + scp = sci_script_cmd; + } else { + scp = sci_script_try_synch; + tgt->flags |= TGT_TRY_SYNCH; + cmd_only = FALSE; + } + LOG(0x1c,"cmdop"); + LOG(0x80+tgt->cur_cmd,0); + break; + default: + LOG(0x1c,"cmdop"); + LOG(0x80+tgt->cur_cmd,0); + scp = sci_script_cmd; + } + + tgt->transient_state.script = scp; + tgt->transient_state.handler = handler; + tgt->transient_state.identify = (cmd_only) ? 0xff : + (disconn ? SCSI_IDENTIFY|SCSI_IFY_ENABLE_DISCONNECT : + SCSI_IDENTIFY); + + if (in_count) + tgt->transient_state.in_count = + (in_count < tgt->block_size) ? tgt->block_size : in_count; + else + tgt->transient_state.in_count = 0; + tgt->transient_state.dma_offset = 0; + + /* + * See if another target is currently selected on + * this SCSI bus, e.g. lock the sci structure. + * Note that it is the strategy routine's job + * to serialize ops on the same target as appropriate. + * XXX here and everywhere, locks! + */ + /* + * Protection viz reconnections makes it tricky. + */ +/* s = splbio();*/ + s = splhigh(); + + if (sci->wd.nactive++ == 0) + sci->wd.watchdog_state = SCSI_WD_ACTIVE; + + if (sci->state & SCI_STATE_BUSY) { + /* + * Queue up this target, note that this takes care + * of proper FIFO scheduling of the scsi-bus. + */ + LOG(3,"enqueue"); + enqueue_tail(&sci->waiting_targets, (queue_entry_t) tgt); + } else { + /* + * It is down to at most two contenders now, + * we will treat reconnections same as selections + * and let the scsi-bus arbitration process decide. + */ + sci->state |= SCI_STATE_BUSY; + sci->next_target = tgt; + sci_attempt_selection(sci); + /* + * Note that we might still lose arbitration.. + */ + } + splx(s); +} + +sci_attempt_selection(sci) + sci_softc_t sci; +{ + target_info_t *tgt; + register int out_count; + sci_padded_regmap_t *regs; + sci_dmaregs_t dmar; + register int cmd; + boolean_t ok; + scsi_ret_t ret; + + regs = sci->regs; + dmar = sci->dmar; + tgt = sci->next_target; + + LOG(4,"select"); + LOG(0x80+tgt->target_id,0); + + /* + * Init bus state variables and set registers. + */ + sci->active_target = tgt; + + /* reselection pending ? */ + if ((regs->sci_bus_csr & (SCI_BUS_BSY|SCI_BUS_SEL)) && + (regs->sci_bus_csr & (SCI_BUS_BSY|SCI_BUS_SEL)) && + (regs->sci_bus_csr & (SCI_BUS_BSY|SCI_BUS_SEL))) + return; + + sci->script = tgt->transient_state.script; + sci->error_handler = tgt->transient_state.handler; + sci->done = SCSI_RET_IN_PROGRESS; + + sci->in_count = 0; + sci->out_count = 0; + sci->extra_byte = 0; + + /* + * This is a bit involved, but the bottom line is we want to + * know after we selected with or w/o ATN if the selection + * went well (ret) and if it is (ok) to send the command. + */ + ok = TRUE; + if (tgt->flags & TGT_DID_SYNCH) { + if (tgt->transient_state.identify == 0xff) { + /* Select w/o ATN */ + ret = sci_select_target(regs, sci->sc->initiator_id, + tgt->target_id, FALSE); + } else { + /* Select with ATN */ + ret = sci_select_target(regs, sci->sc->initiator_id, + tgt->target_id, TRUE); + if (ret == SCSI_RET_SUCCESS) { + register unsigned char icmd; + + while (SCI_CUR_PHASE(regs->sci_bus_csr) != SCSI_PHASE_MSG_OUT) + ; + icmd = regs->sci_icmd & ~(SCI_ICMD_DIFF|SCI_ICMD_TEST); + icmd &= ~SCI_ICMD_ATN; + regs->sci_icmd = icmd; + SCI_ACK(regs,SCSI_PHASE_MSG_OUT); + ok = (sci_data_out(regs, SCSI_PHASE_MSG_OUT, + 1, &tgt->transient_state.identify) == 0); + } + } + } else if (tgt->flags & TGT_TRY_SYNCH) { + /* Select with ATN, do the synch xfer neg */ + ret = sci_select_target(regs, sci->sc->initiator_id, + tgt->target_id, TRUE); + if (ret == SCSI_RET_SUCCESS) { + while (SCI_CUR_PHASE(regs->sci_bus_csr) != SCSI_PHASE_MSG_OUT) + ; + ok = sci_dosynch( sci, regs->sci_csr, regs->sci_bus_csr); + } + } else { + ret = sci_select_target(regs, sci->sc->initiator_id, + tgt->target_id, FALSE); + } + + if (ret == SCSI_RET_DEVICE_DOWN) { + sci->done = ret; + sci_end(sci, regs->sci_csr, regs->sci_bus_csr); + return; + } + if ((ret != SCSI_RET_SUCCESS) || !ok) + return; + +/* time this out or do it via dma !! */ + while (SCI_CUR_PHASE(regs->sci_bus_csr) != SCSI_PHASE_CMD) + ; + + /* set dma pointer and counter to xfer command */ + out_count = tgt->transient_state.cmd_count; +#if 0 + SCI_ACK(regs,SCSI_PHASE_CMD); + sci_data_out(regs,SCSI_PHASE_CMD,out_count,tgt->cmd_ptr); + regs->sci_mode = SCI_MODE_PAR_CHK|SCI_MODE_DMA|SCI_MODE_MONBSY; +#else + SCI_DMADR_PUT(dmar,tgt->dma_ptr); + delay_1p2_us(); + SCI_TC_PUT(dmar,out_count); + dmar->sci_dma_dir = SCI_DMA_DIR_WRITE; + SCI_ACK(regs,SCSI_PHASE_CMD); + SCI_CLR_INTR(regs); + regs->sci_mode = SCI_MODE_PAR_CHK|SCI_MODE_DMA|SCI_MODE_MONBSY; + regs->sci_icmd = SCI_ICMD_DATA; + regs->sci_dma_send = 1; +#endif +} + +/* + * Interrupt routine + * Take interrupts from the chip + * + * Implementation: + * Move along the current command's script if + * all is well, invoke error handler if not. + */ +sci_intr(unit) +{ + register sci_softc_t sci; + register script_t scp; + register unsigned csr, bs, cmd; + register sci_padded_regmap_t *regs; + boolean_t try_match; +#if notyet + extern boolean_t rz_use_mapped_interface; + + if (rz_use_mapped_interface) + return SCI_intr(unit); +#endif + + LOG(5,"\n\tintr"); + + sci = sci_softc[unit]; + regs = sci->regs; + + /* ack interrupt */ + csr = regs->sci_csr; + bs = regs->sci_bus_csr; + cmd = regs->sci_icmd; +TR(regs->sci_mode); + SCI_CLR_INTR(regs); + +TR(csr); +TR(bs); +TR(cmd); +TRCHECK; + + if (cmd & SCI_ICMD_RST){ + sci_bus_reset(sci); + return; + } + + /* we got an interrupt allright */ + if (sci->active_target) + sci->wd.watchdog_state = SCSI_WD_ACTIVE; + + /* drop spurious calls */ + if ((csr & SCI_CSR_INT) == 0) { + LOG(2,"SPURIOUS"); + return; + } + + /* Note: reselect has I/O asserted, select has not */ + if ((sci->state & SCI_STATE_TARGET) || + ((bs & (SCI_BUS_BSY|SCI_BUS_SEL|SCI_BUS_IO)) == SCI_BUS_SEL)) { + sci_target_intr(sci,csr,bs); + return; + } + + scp = sci->script; + + /* Race: disconnecting, we get the disconnected notification + (csr sez BSY dropped) at the same time a reselect is active */ + if ((csr & SCI_CSR_DISC) && + scp && (scp->condition == SCI_PHASE_DISC)) { + (void) (*scp->action)(sci, csr, bs); + /* takes care of calling reconnect if necessary */ + return; + } + + /* check who got the bus */ + if ((scp == 0) || (cmd & SCI_ICMD_LST) || + ((bs & (SCI_BUS_BSY|SCI_BUS_SEL|SCI_BUS_IO)) == (SCI_BUS_SEL|SCI_BUS_IO))) { + sci_reconnect(sci, csr, bs); + return; + } + + if (SCRIPT_MATCH(csr,bs) != scp->condition) { + if (try_match = (*sci->error_handler)(sci, csr, bs)) { + csr = regs->sci_csr; + bs = regs->sci_bus_csr; + } + } else + try_match = TRUE; + + /* might have been side effected */ + scp = sci->script; + + if (try_match && (SCRIPT_MATCH(csr,bs) == scp->condition)) { + /* + * Perform the appropriate operation, + * then proceed + */ + if ((*scp->action)(sci, csr, bs)) { + /* might have been side effected */ + scp = sci->script; + sci->script = scp + 1; + } + } +} + + +sci_target_intr(sci) + register sci_softc_t sci; +{ + panic("SCI: TARGET MODE !!!\n"); +} + +/* + * All the many little things that the interrupt + * routine might switch to + */ +boolean_t +sci_end_transaction( sci, csr, bs) + register sci_softc_t sci; +{ + register sci_padded_regmap_t *regs = sci->regs; + char cmc; + + LOG(0x1f,"end_t"); + + /* Stop dma, no interrupt on disconnect */ + regs->sci_icmd = 0; + regs->sci_mode &= ~(SCI_MODE_DMA|SCI_MODE_MONBSY|SCI_MODE_DMA_IE); +/* dmar->sci_dma_dir = SCI_DMA_DIR_WRITE;/* make sure we steal not */ + + SCI_ACK(regs,SCSI_PHASE_MSG_IN); + + regs->sci_sel_enb = (1 << sci->sc->initiator_id); + + sci_data_in(regs, SCSI_PHASE_MSG_IN, 1, &cmc); + + if (cmc != SCSI_COMMAND_COMPLETE) + printf("{T%x}", cmc); + + /* check disconnected, clear all intr bits */ + while (regs->sci_bus_csr & SCI_BUS_BSY) + ; + SCI_CLR_INTR(regs); + SCI_ACK(regs,SCI_PHASE_DISC); + + if (!sci_end(sci, csr, bs)) { + SCI_CLR_INTR(regs); + (void) sci_reconnect(sci, csr, bs); + } + return FALSE; +} + +boolean_t +sci_end( sci, csr, bs) + register sci_softc_t sci; +{ + register target_info_t *tgt; + register io_req_t ior; + register sci_padded_regmap_t *regs = sci->regs; + boolean_t reconn_pending; + + LOG(6,"end"); + + tgt = sci->active_target; + + if ((tgt->done = sci->done) == SCSI_RET_IN_PROGRESS) + tgt->done = SCSI_RET_SUCCESS; + + sci->script = 0; + + if (sci->wd.nactive-- == 1) + sci->wd.watchdog_state = SCSI_WD_INACTIVE; + + /* check reconnection not pending */ + bs = regs->sci_bus_csr; + reconn_pending = ((bs & (SCI_BUS_BSY|SCI_BUS_SEL|SCI_BUS_IO)) == (SCI_BUS_SEL|SCI_BUS_IO)); + if (!reconn_pending) { + sci_release_bus(sci); + } else { + sci->active_target = 0; +/* sci->state &= ~SCI_STATE_BUSY; later */ + } + + if (ior = tgt->ior) { +#ifdef MACH_KERNEL +#else /*MACH_KERNEL*/ + fdma_unmap(&tgt->fdma, ior); +#endif /*MACH_KERNEL*/ + LOG(0xA,"ops->restart"); + (*tgt->dev_ops->restart)( tgt, TRUE); + if (reconn_pending) + sci->state &= ~SCI_STATE_BUSY; + } + + return (!reconn_pending); +} + +boolean_t +sci_release_bus(sci) + register sci_softc_t sci; +{ + boolean_t ret = FALSE; + + LOG(9,"release"); + + sci->script = 0; + + if (sci->state & SCI_STATE_COLLISION) { + + LOG(0xB,"collided"); + sci->state &= ~SCI_STATE_COLLISION; + sci_attempt_selection(sci); + + } else if (queue_empty(&sci->waiting_targets)) { + + sci->state &= ~SCI_STATE_BUSY; + sci->active_target = 0; + ret = TRUE; + + } else { + + LOG(0xC,"dequeue"); + sci->next_target = (target_info_t *) + dequeue_head(&sci->waiting_targets); + sci_attempt_selection(sci); + } + return ret; +} + +boolean_t +sci_get_status( sci, csr, bs) + register sci_softc_t sci; +{ + register sci_padded_regmap_t *regs = sci->regs; + register sci_dmaregs_t dmar = sci->dmar; + scsi2_status_byte_t status; + register target_info_t *tgt; + unsigned int len, mode; + + LOG(0xD,"get_status"); +TRWRAP; + + /* Stop dma */ + regs->sci_icmd = 0; + mode = regs->sci_mode; + regs->sci_mode = (mode & ~(SCI_MODE_DMA|SCI_MODE_DMA_IE)); + dmar->sci_dma_dir = SCI_DMA_DIR_WRITE;/* make sure we steal not */ + + sci->state &= ~SCI_STATE_DMA_IN; + + tgt = sci->active_target; + + if (len = sci->in_count) { + register int count; + SCI_TC_GET(dmar,count); + if ((tgt->cur_cmd != SCSI_CMD_READ) && + (tgt->cur_cmd != SCSI_CMD_LONG_READ)){ + len -= count; + } else { + if (count) { +#if 0 + this is incorrect and besides.. + tgt->ior->io_residual = count; +#endif + len -= count; + } + sci_copyin( tgt, tgt->transient_state.dma_offset, + len, 0, 0); + } + } + + /* to get the phase mismatch intr */ + regs->sci_mode = mode; + + SCI_ACK(regs,SCSI_PHASE_STATUS); + + sci_data_in(regs, SCSI_PHASE_STATUS, 1, &status.bits); + + SCI_TC_PUT(dmar,0); + + if (status.st.scsi_status_code != SCSI_ST_GOOD) { + scsi_error(sci->active_target, SCSI_ERR_STATUS, status.bits, 0); + sci->done = (status.st.scsi_status_code == SCSI_ST_BUSY) ? + SCSI_RET_RETRY : SCSI_RET_NEED_SENSE; + } else + sci->done = SCSI_RET_SUCCESS; + + return TRUE; +} + +boolean_t +sci_dma_in( sci, csr, bs) + register sci_softc_t sci; +{ + register target_info_t *tgt; + register sci_padded_regmap_t *regs = sci->regs; + register sci_dmaregs_t dmar = sci->dmar; + char *dma_ptr; + register int count; + boolean_t advance_script = TRUE; + + LOG(0xE,"dma_in"); + + /* + * Problem: the 5380 pipelines xfers between the scsibus and + * itself and between itself and the DMA engine --> halting ? + * In the dmaout direction all is ok, except that (see NCR notes) + * the EOP interrupt is generated before the pipe is empty. + * In the dmain direction (here) the interrupt comes when + * one too many bytes have been xferred on chip! + * + * More specifically, IF we asked for count blindly and we had + * more than count bytes coming (double buffering) we would endup + * actually xferring count+1 from the scsibus, but only count + * to memory [hopefully the last byte sits in the sci_datai reg]. + * This could be helped, except most times count is an exact multiple + * of the sector size which is where disks disconnect.... + * + * INSTEAD, we recognize here that we expect more than count bytes + * coming and set the DMA count to count-1 but keep sci->in_count + * above to count. This will be wrong if the target disconnects + * amidst, but we can cure it. + * + * The places where this has an effect are marked by "EXTRA_BYTE" + */ + + tgt = sci->active_target; + sci->state |= SCI_STATE_DMA_IN; + + /* ought to stop dma to start another */ + regs->sci_mode &= ~ (SCI_MODE_DMA|SCI_MODE_DMA_IE); + regs->sci_icmd = 0; + + if (sci->in_count == 0) { + /* + * Got nothing yet: either just sent the command + * or just reconnected + */ + register int avail; + + count = tgt->transient_state.in_count; + count = u_min(count, (PER_TGT_BURST_SIZE)); + avail = PER_TGT_BUFF_SIZE - tgt->transient_state.dma_offset; + count = u_min(count, avail); + + /* common case of 8k-or-less read ? */ + advance_script = (tgt->transient_state.in_count == count); + + } else { + + /* + * We received some data. + */ + register int offset, xferred, eb; + unsigned char extrab = regs->sci_idata; /* EXTRA_BYTE */ + + SCI_TC_GET(dmar,xferred); + assert(xferred == 0); +if (scsi_debug) { +printf("{B %x %x %x (%x)}", + sci->in_count, xferred, sci->extra_byte, extrab); +} + /* ++EXTRA_BYTE */ + xferred = sci->in_count - xferred; + eb = sci->extra_byte; + /* --EXTRA_BYTE */ + assert(xferred > 0); + tgt->transient_state.in_count -= xferred; + assert(tgt->transient_state.in_count > 0); + offset = tgt->transient_state.dma_offset; + tgt->transient_state.dma_offset += xferred; + count = u_min(tgt->transient_state.in_count, (PER_TGT_BURST_SIZE)); + if (tgt->transient_state.dma_offset == PER_TGT_BUFF_SIZE) { + tgt->transient_state.dma_offset = 0; + } else { + register int avail; + avail = PER_TGT_BUFF_SIZE - tgt->transient_state.dma_offset; + count = u_min(count, avail); + } + advance_script = (tgt->transient_state.in_count == count); + + /* get some more */ + dma_ptr = tgt->dma_ptr + tgt->transient_state.dma_offset; + sci->in_count = count; + /* ++EXTRA_BYTE */ + if (!advance_script) { + sci->extra_byte = 1; /* that's the cure.. */ + count--; + } else + sci->extra_byte = 0; + /* --EXTRA_BYTE */ + SCI_TC_PUT(dmar,count); +/* regs->sci_icmd = 0;*/ + SCI_DMADR_PUT(dmar,dma_ptr); + delay_1p2_us(); + SCI_ACK(regs,SCSI_PHASE_DATAI); + SCI_CLR_INTR(regs); + regs->sci_mode |= (advance_script ? SCI_MODE_DMA + : (SCI_MODE_DMA|SCI_MODE_DMA_IE)); + dmar->sci_dma_dir = SCI_DMA_DIR_READ; + regs->sci_irecv = 1; + + /* copy what we got */ + sci_copyin( tgt, offset, xferred, eb, extrab); + + /* last chunk ? */ + return advance_script; + } + + sci->in_count = count; + dma_ptr = tgt->dma_ptr + tgt->transient_state.dma_offset; + + /* ++EXTRA_BYTE */ + if (!advance_script) { + sci->extra_byte = 1; /* that's the cure.. */ + count--; + } else + sci->extra_byte = 0; + /* --EXTRA_BYTE */ + + SCI_TC_PUT(dmar,count); +/* regs->sci_icmd = 0;*/ + SCI_DMADR_PUT(dmar,dma_ptr); + delay_1p2_us(); + SCI_ACK(regs,SCSI_PHASE_DATAI); + SCI_CLR_INTR(regs); + regs->sci_mode |= (advance_script ? SCI_MODE_DMA + : (SCI_MODE_DMA|SCI_MODE_DMA_IE)); + dmar->sci_dma_dir = SCI_DMA_DIR_READ; + regs->sci_irecv = 1; + + return advance_script; +} + +/* send data to target. Called in three different ways: + (a) to start transfer (b) to restart a bigger-than-8k + transfer (c) after reconnection + */ +int sci_delay = 1; + +boolean_t +sci_dma_out( sci, csr, bs) + register sci_softc_t sci; +{ + register sci_padded_regmap_t *regs = sci->regs; + register sci_dmaregs_t dmar = sci->dmar; + register char *dma_ptr; + register target_info_t *tgt; + boolean_t advance_script = TRUE; + int count = sci->out_count; + spl_t s; + register int tmp; + + LOG(0xF,"dma_out"); + + tgt = sci->active_target; + sci->state &= ~SCI_STATE_DMA_IN; + + if (sci->out_count == 0) { + /* + * Nothing committed: either just sent the + * command or reconnected + */ + register int remains; + + /* ought to stop dma to start another */ + regs->sci_mode &= ~ (SCI_MODE_DMA|SCI_MODE_DMA_IE); + dmar->sci_dma_dir = SCI_DMA_DIR_READ;/*hold it */ + + regs->sci_icmd = SCI_ICMD_DATA; + + SCI_ACK(regs,SCSI_PHASE_DATAO); + + count = tgt->transient_state.out_count; + count = u_min(count, (PER_TGT_BURST_SIZE)); + remains = PER_TGT_BUFF_SIZE - tgt->transient_state.dma_offset; + count = u_min(count, remains); + + /* common case of 8k-or-less write ? */ + advance_script = (tgt->transient_state.out_count == count); + } else { + /* + * We sent some data. + * Also, take care of bogus interrupts + */ + register int offset, xferred; + +if (sci_delay & 1) delay(1000); + /* ought to stop dma to start another */ + regs->sci_mode &= ~ (SCI_MODE_DMA|SCI_MODE_DMA_IE); + dmar->sci_dma_dir = SCI_DMA_DIR_READ;/*hold it */ +/* regs->sci_icmd = SCI_ICMD_DATA; */ + + SCI_TC_GET(dmar,xferred); +if (xferred) printf("{A %x}", xferred); + xferred = sci->out_count - xferred; + assert(xferred > 0); + tgt->transient_state.out_count -= xferred; + assert(tgt->transient_state.out_count > 0); + offset = tgt->transient_state.dma_offset; + tgt->transient_state.dma_offset += xferred; + count = u_min(tgt->transient_state.out_count, (PER_TGT_BURST_SIZE)); + if (tgt->transient_state.dma_offset == PER_TGT_BUFF_SIZE) { + tgt->transient_state.dma_offset = 0; + } else { + register int remains; + remains = PER_TGT_BUFF_SIZE - tgt->transient_state.dma_offset; + count = u_min(count, remains); + } + /* last chunk ? */ + if (tgt->transient_state.out_count == count) + goto quickie; + + /* ship some more */ + dma_ptr = tgt->dma_ptr + + tgt->transient_state.cmd_count + tgt->transient_state.dma_offset; + sci->out_count = count; + /* + * Mistery: sometimes the first byte + * of an 8k chunk is missing from the tape, it must + * be that somehow touching the 5380 registers + * after the dma engine is ready screws up: false DRQ? + */ +s = splhigh(); + SCI_TC_PUT(dmar,count); +/* SCI_CLR_INTR(regs);*/ + regs->sci_mode = SCI_MODE_PAR_CHK | SCI_MODE_DMA | + SCI_MODE_MONBSY | SCI_MODE_DMA_IE; +/* regs->sci_icmd = SCI_ICMD_DATA;*/ + dmar->sci_dma_dir = SCI_DMA_DIR_WRITE; + SCI_DMADR_PUT(dmar,dma_ptr); + delay_1p2_us(); + + regs->sci_dma_send = 1; +splx(s); + /* copy some more data */ + sci_copyout(tgt, offset, xferred); + return FALSE; + } + +quickie: + sci->out_count = count; + dma_ptr = tgt->dma_ptr + + tgt->transient_state.cmd_count + tgt->transient_state.dma_offset; + tmp = (advance_script ? + SCI_MODE_PAR_CHK|SCI_MODE_DMA|SCI_MODE_MONBSY: + SCI_MODE_PAR_CHK|SCI_MODE_DMA|SCI_MODE_MONBSY|SCI_MODE_DMA_IE); +s = splhigh(); + SCI_TC_PUT(dmar,count); +/* SCI_CLR_INTR(regs);*/ + regs->sci_mode = tmp; +/* regs->sci_icmd = SCI_ICMD_DATA;*/ + SCI_DMADR_PUT(dmar,dma_ptr); + delay_1p2_us(); + dmar->sci_dma_dir = SCI_DMA_DIR_WRITE; + regs->sci_dma_send = 1; +splx(s); + + return advance_script; +} + +/* disconnect-reconnect ops */ + +/* get the message in via dma */ +boolean_t +sci_msg_in(sci, csr, bs) + register sci_softc_t sci; +{ + register target_info_t *tgt; + char *dma_ptr; + register sci_padded_regmap_t *regs = sci->regs; + register sci_dmaregs_t dmar = sci->dmar; + + LOG(0x15,"msg_in"); + + tgt = sci->active_target; + + dma_ptr = tgt->dma_ptr; + /* We would clobber the data for READs */ + if (sci->state & SCI_STATE_DMA_IN) { + register int offset; + offset = tgt->transient_state.cmd_count + tgt->transient_state.dma_offset; + dma_ptr += offset; + } + + /* ought to stop dma to start another */ + regs->sci_mode &= ~ (SCI_MODE_DMA|SCI_MODE_DMA_IE); + regs->sci_icmd = 0; + + /* We only really expect two bytes */ + SCI_TC_PUT(dmar,sizeof(scsi_command_group_0)); +/* regs->sci_icmd = 0*/ + SCI_DMADR_PUT(dmar,dma_ptr); + delay_1p2_us(); + SCI_ACK(regs,SCSI_PHASE_MSG_IN); + SCI_CLR_INTR(regs); + regs->sci_mode |= SCI_MODE_DMA; + dmar->sci_dma_dir = SCI_DMA_DIR_READ; + regs->sci_irecv = 1; + + return TRUE; +} + +/* check the message is indeed a DISCONNECT */ +boolean_t +sci_disconnect(sci, csr, bs) + register sci_softc_t sci; +{ + register int len; + boolean_t ok = FALSE; + register sci_dmaregs_t dmar = sci->dmar; + register char *msgs; + unsigned int offset; + + + SCI_TC_GET(dmar,len); + len = sizeof(scsi_command_group_0) - len; + PRINT(("{G%d}",len)); + + /* wherever it was, take it from there */ + SCI_DMADR_GET(dmar,offset); + msgs = (char*)sci->buff + offset - len; + + if ((len == 0) || (len > 2)) + ok = FALSE; + else { + /* A SDP message preceeds it in non-completed READs */ + ok = ((msgs[0] == SCSI_DISCONNECT) || /* completed op */ + ((msgs[0] == SCSI_SAVE_DATA_POINTER) && /* incomplete */ + (msgs[1] == SCSI_DISCONNECT))); + } + if (!ok) + printf("[tgt %d bad msg (%d): %x]", + sci->active_target->target_id, len, *msgs); + + return TRUE; +} + +/* save all relevant data, free the BUS */ +boolean_t +sci_disconnected(sci, csr, bs) + register sci_softc_t sci; +{ + register target_info_t *tgt; + sci_padded_regmap_t *regs = sci->regs; + + regs->sci_mode &= ~(SCI_MODE_MONBSY|SCI_MODE_DMA); + SCI_CLR_INTR(regs);/*retriggered by MONBSY cuz intr routine did CLR */ + SCI_ACK(regs,SCI_PHASE_DISC); + + LOG(0x16,"disconnected"); + + sci_disconnect(sci,csr,bs); + + tgt = sci->active_target; + tgt->flags |= TGT_DISCONNECTED; + tgt->transient_state.handler = sci->error_handler; + /* the rest has been saved in sci_err_disconn() */ + + PRINT(("{D%d}", tgt->target_id)); + + sci_release_bus(sci); + + return FALSE; +} + +/* get reconnect message, restore BUS */ +boolean_t +sci_reconnect(sci, csr, bs) + register sci_softc_t sci; +{ + register target_info_t *tgt; + sci_padded_regmap_t *regs; + register int id; + int msg; + + LOG(0x17,"reconnect"); + + if (sci->wd.nactive == 0) { + LOG(2,"SPURIOUS"); + return FALSE; + } + + regs = sci->regs; + + regs->sci_mode &= ~SCI_MODE_PAR_CHK; + id = regs->sci_data;/*parity!*/ + regs->sci_mode |= SCI_MODE_PAR_CHK; + + /* xxx check our id is in there */ + + id &= ~(1 << sci->sc->initiator_id); + { + register int i; + for (i = 0; i < 8; i++) + if (id & (1 << i)) break; +if (i == 8) {printf("{P%x}", id);return;} + id = i; + } + regs->sci_icmd = SCI_ICMD_BSY; + while (regs->sci_bus_csr & SCI_BUS_SEL) + ; + regs->sci_icmd = 0; + delay_1p2_us(); + while ( ((regs->sci_bus_csr & SCI_BUS_BSY) == 0) && + ((regs->sci_bus_csr & SCI_BUS_BSY) == 0) && + ((regs->sci_bus_csr & SCI_BUS_BSY) == 0)) + ; + + regs->sci_mode |= SCI_MODE_MONBSY; + + /* Now should wait for correct phase: REQ signals it */ + while ( ((regs->sci_bus_csr & SCI_BUS_REQ) == 0) && + ((regs->sci_bus_csr & SCI_BUS_REQ) == 0) && + ((regs->sci_bus_csr & SCI_BUS_REQ) == 0)) + ; + + /* + * See if this reconnection collided with a selection attempt + */ + if (sci->state & SCI_STATE_BUSY) + sci->state |= SCI_STATE_COLLISION; + + sci->state |= SCI_STATE_BUSY; + + /* Get identify msg */ + bs = regs->sci_bus_csr; +if (SCI_CUR_PHASE(bs) != SCSI_PHASE_MSG_IN) gimmeabreak(); + SCI_ACK(regs,SCSI_PHASE_MSG_IN); + msg = 0; + sci_data_in(regs, SCSI_PHASE_MSG_IN, 1, &msg); + regs->sci_mode = SCI_MODE_PAR_CHK|SCI_MODE_DMA|SCI_MODE_MONBSY; + regs->sci_sel_enb = 0; + + if (msg != SCSI_IDENTIFY) + printf("{I%x %x}", id, msg); + + tgt = sci->sc->target[id]; + if (id > 7 || tgt == 0) panic("sci_reconnect"); + + PRINT(("{R%d}", id)); + if (sci->state & SCI_STATE_COLLISION) + PRINT(("[B %d-%d]", sci->active_target->target_id, id)); + + LOG(0x80+id,0); + + sci->active_target = tgt; + tgt->flags &= ~TGT_DISCONNECTED; + + sci->script = tgt->transient_state.script; + sci->error_handler = tgt->transient_state.handler; + sci->in_count = 0; + sci->out_count = 0; + + /* Should get a phase mismatch when tgt changes phase */ + + return TRUE; +} + + + +/* do the synch negotiation */ +boolean_t +sci_dosynch( sci, csr, bs) + register sci_softc_t sci; +{ + /* + * Phase is MSG_OUT here, cmd has not been xferred + */ + int len; + register target_info_t *tgt; + register sci_padded_regmap_t *regs = sci->regs; + unsigned char off, icmd; + register unsigned char *p; + + regs->sci_mode |= SCI_MODE_MONBSY; + + LOG(0x11,"dosync"); + + /* ATN still asserted */ + SCI_ACK(regs,SCSI_PHASE_MSG_OUT); + + tgt = sci->active_target; + + tgt->flags |= TGT_DID_SYNCH; /* only one chance */ + tgt->flags &= ~TGT_TRY_SYNCH; + + p = (unsigned char *)tgt->cmd_ptr + tgt->transient_state.cmd_count + + tgt->transient_state.dma_offset; + p[0] = SCSI_IDENTIFY; + p[1] = SCSI_EXTENDED_MESSAGE; + p[2] = 3; + p[3] = SCSI_SYNC_XFER_REQUEST; + /* We cannot run synchronous */ +#define sci_to_scsi_period(x) 0xff +#define scsi_period_to_sci(x) (x) + off = 0; + p[4] = sci_to_scsi_period(sci_min_period); + p[5] = off; + + /* xfer all but last byte with ATN set */ + sci_data_out(regs, SCSI_PHASE_MSG_OUT, + sizeof(scsi_synch_xfer_req_t), p); + icmd = regs->sci_icmd & ~(SCI_ICMD_DIFF|SCI_ICMD_TEST); + icmd &= ~SCI_ICMD_ATN; + regs->sci_icmd = icmd; + sci_data_out(regs, SCSI_PHASE_MSG_OUT, 1, + &p[sizeof(scsi_synch_xfer_req_t)]); + + /* wait for phase change */ + while (regs->sci_csr & SCI_CSR_PHASE_MATCH) + ; + bs = regs->sci_bus_csr; + + /* The standard sez there nothing else the target can do but.. */ + if (SCI_CUR_PHASE(bs) != SCSI_PHASE_MSG_IN) + panic("sci_dosync");/* XXX put offline */ + +msgin: + /* ack */ + SCI_ACK(regs,SCSI_PHASE_MSG_IN); + + /* get answer */ + len = sizeof(scsi_synch_xfer_req_t); + len = sci_data_in(regs, SCSI_PHASE_MSG_IN, len, p); + + /* do not cancel the phase mismatch interrupt ! */ + + /* look at the answer and see if we like it */ + if (len || (p[0] != SCSI_EXTENDED_MESSAGE)) { + /* did not like it at all */ + printf(" did not like SYNCH xfer "); + } else { + /* will NOT do synch */ + printf(" but we cannot do SYNCH xfer "); + tgt->sync_period = scsi_period_to_sci(p[3]); + tgt->sync_offset = p[4]; + /* sanity */ + if (tgt->sync_offset != 0) + printf(" ?OFFSET %x? ", tgt->sync_offset); + } + + /* wait for phase change */ + while (regs->sci_csr & SCI_CSR_PHASE_MATCH) + ; + bs = regs->sci_bus_csr; + + /* phase should be command now */ + /* continue with simple command script */ + sci->error_handler = sci_err_generic; + sci->script = sci_script_cmd; + + if (SCI_CUR_PHASE(bs) == SCSI_PHASE_CMD ) + return TRUE; + +/* sci->script++;*/ + if (SCI_CUR_PHASE(bs) == SCSI_PHASE_STATUS ) + return TRUE; /* intr is pending */ + + sci->script++; + if (SCI_CUR_PHASE(bs) == SCSI_PHASE_MSG_IN ) + return TRUE; + + if ((bs & SCI_BUS_BSY) == 0) /* uhu? disconnected */ + return sci_end_transaction(sci, regs->sci_csr, regs->sci_bus_csr); + + panic("sci_dosynch"); + return FALSE; +} + +/* + * The bus was reset + */ +sci_bus_reset(sci) + register sci_softc_t sci; +{ + register target_info_t *tgt; + register sci_padded_regmap_t *regs = sci->regs; + int i; + + LOG(0x21,"bus_reset"); + + /* + * Clear bus descriptor + */ + sci->script = 0; + sci->error_handler = 0; + sci->active_target = 0; + sci->next_target = 0; + sci->state = 0; + queue_init(&sci->waiting_targets); + sci->wd.nactive = 0; + sci_reset(sci, TRUE); + + printf("sci%d: (%d) bus reset ", sci->sc->masterno, ++sci->wd.reset_count); + delay(scsi_delay_after_reset); /* some targets take long to reset */ + + if (sci->sc == 0) /* sanity */ + return; + + scsi_bus_was_reset(sci->sc); +} + +/* + * Error handlers + */ + +/* + * Generic, default handler + */ +boolean_t +sci_err_generic(sci, csr, bs) + register sci_softc_t sci; +{ + register int cond = sci->script->condition; + + LOG(0x10,"err_generic"); + + if (SCI_CUR_PHASE(bs) == SCSI_PHASE_STATUS) + return sci_err_to_status(sci, csr, bs); + gimmeabreak(); + return FALSE; +} + +/* + * Handle generic errors that are reported as + * an unexpected change to STATUS phase + */ +sci_err_to_status(sci, csr, bs) + register sci_softc_t sci; +{ + script_t scp = sci->script; + + LOG(0x20,"err_tostatus"); + while (SCSI_PHASE(scp->condition) != SCSI_PHASE_STATUS) + scp++; + sci->script = scp; +#if 0 + /* + * Normally, we would already be able to say the command + * is in error, e.g. the tape had a filemark or something. + * But in case we do disconnected mode WRITEs, it is quite + * common that the following happens: + * dma_out -> disconnect -> reconnect + * and our script might expect at this point that the dma + * had to be restarted (it didn't know it was completed + * because the tape record is shorter than we asked for). + * And in any event.. it is both correct and cleaner to + * declare error iff the STATUS byte says so. + */ + sci->done = SCSI_RET_NEED_SENSE; +#endif + return TRUE; +} + +/* + * Watch for a disconnection + */ +boolean_t +sci_err_disconn(sci, csr, bs) + register sci_softc_t sci; +{ + register sci_padded_regmap_t *regs; + register sci_dmaregs_t dmar = sci->dmar; + register target_info_t *tgt; + int count; + + LOG(0x18,"err_disconn"); + + if (SCI_CUR_PHASE(bs) != SCSI_PHASE_MSG_IN) + return sci_err_generic(sci, csr, bs); + + regs = sci->regs; + + tgt = sci->active_target; + + switch (SCSI_PHASE(sci->script->condition)) { + case SCSI_PHASE_DATAO: + LOG(0x1b,"+DATAO"); + +if (sci_delay & 1) delay(1000); + /* Stop dma */ + regs->sci_icmd = 0; + regs->sci_mode &= ~(SCI_MODE_DMA|SCI_MODE_DMA_IE); + dmar->sci_dma_dir = SCI_DMA_DIR_READ;/* make sure we steal not */ + + if (sci->out_count) { + register int xferred, offset; + + SCI_TC_GET(dmar,xferred); +if (scsi_debug) +printf("{O %x %x}", xferred, sci->out_count); + /* 5380 prefetches */ + xferred = sci->out_count - xferred - 1; +/* assert(xferred > 0);*/ + tgt->transient_state.out_count -= xferred; + assert(tgt->transient_state.out_count > 0); + offset = tgt->transient_state.dma_offset; + tgt->transient_state.dma_offset += xferred; + if (tgt->transient_state.dma_offset >= PER_TGT_BUFF_SIZE) + tgt->transient_state.dma_offset = 0; + + sci_copyout( tgt, offset, xferred); + + } + tgt->transient_state.script = sci_script_data_out; + break; + + case SCSI_PHASE_DATAI: + LOG(0x19,"+DATAI"); + + /* Stop dma */ + regs->sci_icmd = 0; + regs->sci_mode &= ~(SCI_MODE_DMA|SCI_MODE_DMA_IE); + dmar->sci_dma_dir = SCI_DMA_DIR_WRITE;/* make sure we steal not */ + + if (sci->in_count) { + register int offset, xferred; +/* unsigned char extrab = regs->sci_idata;*/ + + SCI_TC_GET(dmar,xferred); + /* ++EXTRA_BYTE */ +if (scsi_debug) +printf("{A %x %x %x}", xferred, sci->in_count, sci->extra_byte); + xferred = sci->in_count - xferred - sci->extra_byte; + /* ++EXTRA_BYTE */ + assert(xferred > 0); + tgt->transient_state.in_count -= xferred; + assert(tgt->transient_state.in_count > 0); + offset = tgt->transient_state.dma_offset; + tgt->transient_state.dma_offset += xferred; + if (tgt->transient_state.dma_offset >= PER_TGT_BUFF_SIZE) + tgt->transient_state.dma_offset = 0; + + /* copy what we got */ + sci_copyin( tgt, offset, xferred, 0, 0/*extrab*/); + } + tgt->transient_state.script = sci_script_data_in; + break; + + case SCSI_PHASE_STATUS: + /* will have to restart dma */ + SCI_TC_GET(dmar,count); + if (sci->state & SCI_STATE_DMA_IN) { + register int offset, xferred; +/* unsigned char extrab = regs->sci_idata;*/ + + LOG(0x1a,"+STATUS+R"); + + + /* Stop dma */ + regs->sci_icmd = 0; + regs->sci_mode &= ~(SCI_MODE_DMA|SCI_MODE_DMA_IE); + dmar->sci_dma_dir = SCI_DMA_DIR_WRITE;/* make sure we steal not */ + + /* ++EXTRA_BYTE */ +if (scsi_debug) +printf("{A %x %x %x}", count, sci->in_count, sci->extra_byte); + xferred = sci->in_count - count - sci->extra_byte; + /* ++EXTRA_BYTE */ + assert(xferred > 0); + tgt->transient_state.in_count -= xferred; +/* assert(tgt->transient_state.in_count > 0);*/ + offset = tgt->transient_state.dma_offset; + tgt->transient_state.dma_offset += xferred; + if (tgt->transient_state.dma_offset >= PER_TGT_BUFF_SIZE) + tgt->transient_state.dma_offset = 0; + + /* copy what we got */ + sci_copyin( tgt, offset, xferred, 0, 0/*/extrab*/); + + tgt->transient_state.script = sci_script_data_in; + if (tgt->transient_state.in_count == 0) + tgt->transient_state.script++; + + } else { + + LOG(0x1d,"+STATUS+W"); + +if (sci_delay & 1) delay(1000); + /* Stop dma */ + regs->sci_icmd = 0; + regs->sci_mode &= ~(SCI_MODE_DMA|SCI_MODE_DMA_IE); + dmar->sci_dma_dir = SCI_DMA_DIR_READ;/* make sure we steal not */ + +if (scsi_debug) +printf("{O %x %x}", count, sci->out_count); + if ((count == 0) && (tgt->transient_state.out_count == sci->out_count)) { + /* all done */ + tgt->transient_state.script = &sci_script_data_out[1]; + tgt->transient_state.out_count = 0; + } else { + register int xferred, offset; + + /* how much we xferred */ + xferred = sci->out_count - count - 1;/*prefetch*/ + + tgt->transient_state.out_count -= xferred; + assert(tgt->transient_state.out_count > 0); + offset = tgt->transient_state.dma_offset; + tgt->transient_state.dma_offset += xferred; + if (tgt->transient_state.dma_offset >= PER_TGT_BUFF_SIZE) + tgt->transient_state.dma_offset = 0; + + sci_copyout( tgt, offset, xferred); + + tgt->transient_state.script = sci_script_data_out; + } + sci->out_count = 0; + } + break; + default: + gimmeabreak(); + } + sci->extra_byte = 0; + +/* SCI_ACK(regs,SCSI_PHASE_MSG_IN); later */ + (void) sci_msg_in(sci,csr,bs); + + regs->sci_sel_enb = (1 << sci->sc->initiator_id); + + sci->script = sci_script_disconnect; + + return FALSE; +} + +/* + * Watchdog + * + */ +sci_reset_scsibus(sci) + register sci_softc_t sci; +{ + register target_info_t *tgt = sci->active_target; + if (tgt) { + int cnt; + SCI_TC_GET(sci->dmar,cnt); + log( LOG_KERN, + "Target %d was active, cmd x%x in x%x out x%x Sin x%x Sou x%x dmalen x%x\n", + tgt->target_id, tgt->cur_cmd, + tgt->transient_state.in_count, tgt->transient_state.out_count, + sci->in_count, sci->out_count, cnt); + } + sci->regs->sci_icmd = SCI_ICMD_RST; + delay(25); +} + +/* + * Copy routines + */ +/*static*/ +sci_copyin(tgt, offset, len, isaobb, obb) + register target_info_t *tgt; + unsigned char obb; +{ + register char *from, *to; + register int count; + + count = tgt->transient_state.copy_count; + + from = tgt->cmd_ptr + offset; + to = tgt->ior->io_data + count; + tgt->transient_state.copy_count = count + len; + + bcopy( from, to, len); + /* check for last, poor little odd byte */ + if (isaobb) { + to += len; + to[-1] = obb; + } +} + +/*static*/ +sci_copyout( tgt, offset, len) + register target_info_t *tgt; +{ + register char *from, *to; + register int count, olen; + unsigned char c; + char *p; + + count = tgt->ior->io_count - tgt->transient_state.copy_count; + if (count > 0) { + + len = u_min(count, len); + offset += tgt->transient_state.cmd_count; + + count = tgt->transient_state.copy_count; + tgt->transient_state.copy_count = count + len; + + from = tgt->ior->io_data + count; + to = tgt->cmd_ptr + offset; + + bcopy(from, to, len); + + } +} + +#endif /*NSCI > 0*/ + diff --git a/scsi/adapters/scsi_53C700.h b/scsi/adapters/scsi_53C700.h new file mode 100644 index 0000000..224fc5b --- /dev/null +++ b/scsi/adapters/scsi_53C700.h @@ -0,0 +1,327 @@ +/* + * Mach Operating System + * Copyright (c) 1991,1990,1989 Carnegie Mellon University + * All Rights Reserved. + * + * Permission to use, copy, modify and distribute this software and its + * documentation is hereby granted, provided that both the copyright + * notice and this permission notice appear in all copies of the + * software, derivative works or modified versions, and any portions + * thereof, and that both notices appear in supporting documentation. + * + * CARNEGIE MELLON ALLOWS FREE USE OF THIS SOFTWARE IN ITS + * CONDITION. CARNEGIE MELLON DISCLAIMS ANY LIABILITY OF ANY KIND FOR + * ANY DAMAGES WHATSOEVER RESULTING FROM THE USE OF THIS SOFTWARE. + * + * Carnegie Mellon requests users of this software to return to + * + * Software Distribution Coordinator or Software.Distribution@CS.CMU.EDU + * School of Computer Science + * Carnegie Mellon University + * Pittsburgh PA 15213-3890 + * + * any improvements or extensions that they make and grant Carnegie the + * rights to redistribute these changes. + */ +/* + * File: scsi_53C700.h + * Author: Alessandro Forin, Carnegie Mellon University + * Date: 8/91 + * + * Defines for the NCR 53C700 (SCSI I/O Processor) + */ + +/* + * Register map + */ + +typedef struct { + volatile unsigned char siop_scntl0; /* rw: SCSI control reg 0 */ + volatile unsigned char siop_scntl1; /* rw: SCSI control reg 1 */ + volatile unsigned char siop_sdid; /* rw: SCSI Destination ID */ + volatile unsigned char siop_sien; /* rw: SCSI Interrupt Enable */ + volatile unsigned char siop_scid; /* rw: SCSI Chip ID reg */ + volatile unsigned char siop_sxfer; /* rw: SCSI Transfer reg */ + volatile unsigned char siop_sodl; /* rw: SCSI Output Data Latch */ + volatile unsigned char siop_socl; /* rw: SCSI Output Control Latch */ + volatile unsigned char siop_sfbr; /* ro: SCSI First Byte Received */ + volatile unsigned char siop_sidl; /* ro: SCSI Input Data Latch */ + volatile unsigned char siop_sbdl; /* ro: SCSI Bus Data Lines */ + volatile unsigned char siop_sbcl; /* ro: SCSI Bus Control Lines */ + volatile unsigned char siop_dstat; /* ro: DMA status */ + volatile unsigned char siop_sstat0; /* ro: SCSI status reg 0 */ + volatile unsigned char siop_sstat1; /* ro: SCSI status reg 1 */ + volatile unsigned char siop_sstat2; /* ro: SCSI status reg 2 */ + volatile unsigned char siop_res1; + volatile unsigned char siop_res2; + volatile unsigned char siop_res3; + volatile unsigned char siop_res4; + volatile unsigned char siop_ctest0; /* ro: Chip test register 0 */ + volatile unsigned char siop_ctest1; /* ro: Chip test register 1 */ + volatile unsigned char siop_ctest2; /* ro: Chip test register 2 */ + volatile unsigned char siop_ctest3; /* ro: Chip test register 3 */ + volatile unsigned char siop_ctest4; /* rw: Chip test register 4 */ + volatile unsigned char siop_ctest5; /* rw: Chip test register 5 */ + volatile unsigned char siop_ctest6; /* rw: Chip test register 6 */ + volatile unsigned char siop_ctest7; /* rw: Chip test register 7 */ + volatile unsigned char siop_temp0; /* rw: Temporary Stack reg */ + volatile unsigned char siop_temp1; + volatile unsigned char siop_temp2; + volatile unsigned char siop_temp3; + volatile unsigned char siop_dfifo; /* rw: DMA FIFO */ + volatile unsigned char siop_istat; /* rw: Interrupt Status reg */ + volatile unsigned char siop_res5; + volatile unsigned char siop_res6; + volatile unsigned char siop_dbc0; /* rw: DMA Byte Counter reg */ + volatile unsigned char siop_dbc1; + volatile unsigned char siop_dbc2; + volatile unsigned char siop_dcmd; /* rw: DMA Command Register */ + volatile unsigned char siop_dnad0; /* rw: DMA Next Address */ + volatile unsigned char siop_dnad1; + volatile unsigned char siop_dnad2; + volatile unsigned char siop_dnad3; + volatile unsigned char siop_dsp0; /* rw: DMA SCRIPTS Pointer reg */ + volatile unsigned char siop_dsp1; + volatile unsigned char siop_dsp2; + volatile unsigned char siop_dsp3; + volatile unsigned char siop_dsps0; /* rw: DMA SCRIPTS Pointer Save reg */ + volatile unsigned char siop_dsps1; + volatile unsigned char siop_dsps2; + volatile unsigned char siop_dsps3; + volatile unsigned char siop_dmode; /* rw: DMA Mode reg */ + volatile unsigned char siop_res7; + volatile unsigned char siop_res8; + volatile unsigned char siop_res9; + volatile unsigned char siop_res10; + volatile unsigned char siop_dien; /* rw: DMA Interrupt Enable */ + volatile unsigned char siop_dwt; /* rw: DMA Watchdog Timer */ + volatile unsigned char siop_dcntl; /* rw: DMA Control reg */ + volatile unsigned char siop_res11; + volatile unsigned char siop_res12; + volatile unsigned char siop_res13; + volatile unsigned char siop_res14; + +} siop_regmap_t; + +/* + * Register defines + */ + +/* Scsi control register 0 (scntl0) */ + +#define SIOP_SCNTL0_ARB 0xc0 /* Arbitration mode */ +# define SIOP_ARB_SIMPLE 0x00 +# define SIOP_ARB_FULL 0xc0 +#define SIOP_SCNTL0_START 0x20 /* Start Sequence */ +#define SIOP_SCNTL0_WATN 0x10 /* (Select) With ATN */ +#define SIOP_SCNTL0_EPC 0x08 /* Enable Parity Checking */ +#define SIOP_SCNTL0_EPG 0x04 /* Enable Parity Generation */ +#define SIOP_SCNTL0_AAP 0x02 /* Assert ATN on Parity Error */ +#define SIOP_SCNTL0_TRG 0x01 /* Target Mode */ + +/* Scsi control register 1 (scntl1) */ + +#define SIOP_SCNTL1_EXC 0x80 /* Extra Clock Cycle of data setup */ +#define SIOP_SCNTL1_ADB 0x40 /* Assert Data Bus */ +#define SIOP_SCNTL1_ESR 0x20 /* Enable Selection/Reselection */ +#define SIOP_SCNTL1_CON 0x10 /* Connected */ +#define SIOP_SCNTL1_RST 0x08 /* Assert RST */ +#define SIOP_SCNTL1_PAR 0x04 /* Force bad Parity */ +#define SIOP_SCNTL1_SND 0x02 /* Start Send operation */ +#define SIOP_SCNTL1_RCV 0x01 /* Start Receive operation */ + +/* Scsi interrupt enable register (sien) */ + +#define SIOP_SIEN_M_A 0x80 /* Phase Mismatch or ATN active */ +#define SIOP_SIEN_FC 0x40 /* Function Complete */ +#define SIOP_SIEN_STO 0x20 /* (Re)Selection timeout */ +#define SIOP_SIEN_SEL 0x10 /* (Re)Selected */ +#define SIOP_SIEN_SGE 0x08 /* SCSI Gross Error */ +#define SIOP_SIEN_UDC 0x04 /* Unexpected Disconnect */ +#define SIOP_SIEN_RST 0x02 /* RST asserted */ +#define SIOP_SIEN_PAR 0x01 /* Parity Error */ + +/* Scsi chip ID (scid) */ + +#define SIOP_SCID_VALUE(i) (1< asserted */ +#define SIOP_ACK 0x40 +#define SIOP_BSY 0x20 +#define SIOP_SEL 0x10 +#define SIOP_ATN 0x08 +#define SIOP_MSG 0x04 +#define SIOP_CD 0x02 +#define SIOP_IO 0x01 + +#define SIOP_PHASE(socl) SCSI_PHASE(socl) + +/* Scsi first byte received register (sfbr) */ + +/* Scsi input data latch register (sidl) */ + +/* Scsi bus data lines register (sbdl) */ + +/* Scsi bus control lines register (sbcl). Same as socl */ + +/* DMA status register (dstat) */ + +#define SIOP_DSTAT_DFE 0x80 /* DMA FIFO empty */ +#define SIOP_DSTAT_RES 0x60 +#define SIOP_DSTAT_ABRT 0x10 /* Aborted */ +#define SIOP_DSTAT_SSI 0x08 /* SCRIPT Single Step */ +#define SIOP_DSTAT_SIR 0x04 /* SCRIPT Interrupt Instruction */ +#define SIOP_DSTAT_WTD 0x02 /* Watchdog Timeout Detected */ +#define SIOP_DSTAT_OPC 0x01 /* Invalid SCRIPTS Opcode */ + +/* Scsi status register 0 (sstat0) */ + +#define SIOP_SSTAT0_M_A 0x80 /* Phase Mismatch or ATN active */ +#define SIOP_SSTAT0_FC 0x40 /* Function Complete */ +#define SIOP_SSTAT0_STO 0x20 /* (Re)Selection timeout */ +#define SIOP_SSTAT0_SEL 0x10 /* (Re)Selected */ +#define SIOP_SSTAT0_SGE 0x08 /* SCSI Gross Error */ +#define SIOP_SSTAT0_UDC 0x04 /* Unexpected Disconnect */ +#define SIOP_SSTAT0_RST 0x02 /* RST asserted */ +#define SIOP_SSTAT0_PAR 0x01 /* Parity Error */ + +/* Scsi status register 1 (sstat1) */ + +#define SIOP_SSTAT1_ILF 0x80 /* Input latch (sidl) full */ +#define SIOP_SSTAT1_ORF 0x40 /* output reg (sodr) full */ +#define SIOP_SSTAT1_OLF 0x20 /* output latch (sodl) full */ +#define SIOP_SSTAT1_AIP 0x10 /* Arbitration in progress */ +#define SIOP_SSTAT1_LOA 0x08 /* Lost arbitration */ +#define SIOP_SSTAT1_WOA 0x04 /* Won arbitration */ +#define SIOP_SSTAT1_RST 0x02 /* SCSI RST current value */ +#define SIOP_SSTAT1_SDP 0x01 /* SCSI SDP current value */ + +/* Scsi status register 2 (sstat2) */ + +#define SIOP_SSTAT2_FF 0xf0 /* SCSI FIFO flags (bytecount) */ +# define SIOP_SCSI_FIFO_DEEP 8 +#define SIOP_SSTAT2_SDP 0x08 /* Latched (on REQ) SCSI SDP */ +#define SIOP_SSTAT2_MSG 0x04 /* Latched SCSI phase */ +#define SIOP_SSTAT2_CD 0x02 +#define SIOP_SSTAT2_IO 0x01 + +/* Chip test register 0 (ctest0) */ + +#define SIOP_CTEST0_RES 0xfc +#define SIOP_CTEST0_RTRG 0x02 /* Real Target mode */ +#define SIOP_CTEST0_DDIR 0x01 /* Xfer direction (1-> from SCSI bus) */ + +/* Chip test register 1 (ctest1) */ + +#define SIOP_CTEST1_FMT 0xf0 /* Byte empty in DMA FIFO bottom (high->byte3) */ +#define SIOP_CTEST1_FFL 0x0f /* Byte full in DMA FIFO top, same */ + +/* Chip test register 2 (ctest2) */ + +#define SIOP_CTEST2_RES 0xc0 +#define SIOP_CTEST2_SOFF 0x20 /* Synch Offset compare (1-> zero Init, max Tgt */ +#define SIOP_CTEST2_SFP 0x10 /* SCSI FIFO Parity */ +#define SIOP_CTEST2_DFP 0x08 /* DMA FIFO Parity */ +#define SIOP_CTEST2_TEOP 0x04 /* True EOP (a-la 5380) */ +#define SIOP_CTEST2_DREQ 0x02 /* DREQ status */ +#define SIOP_CTEST2_DACK 0x01 /* DACK status */ + +/* Chip test register 3 (ctest3) read-only, top of SCSI FIFO */ + +/* Chip test register 4 (ctest4) */ + +#define SIOP_CTEST4_RES 0x80 +#define SIOP_CTEST4_ZMOD 0x40 /* High-impedance outputs */ +#define SIOP_CTEST4_SZM 0x20 /* ditto, SCSI "outputs" */ +#define SIOP_CTEST4_SLBE 0x10 /* SCSI loobpack enable */ +#define SIOP_CTEST4_SFWR 0x08 /* SCSI FIFO write enable (from sodl) */ +#define SIOP_CTEST4_FBL 0x07 /* DMA FIFO Byte Lane select (from ctest6) + 4->0, .. 7->3 */ + +/* Chip test register 5 (ctest5) */ + +#define SIOP_CTEST5_ADCK 0x80 /* Clock Address Incrementor */ +#define SIOP_CTEST5_BBCK 0x40 /* Clock Byte counter */ +#define SIOP_CTEST5_ROFF 0x20 /* Reset SCSI offset */ +#define SIOP_CTEST5_MASR 0x10 /* Master set/reset pulses (of bits 3-0) */ +#define SIOP_CTEST5_DDIR 0x08 /* (re)set internal DMA direction */ +#define SIOP_CTEST5_EOP 0x04 /* (re)set internal EOP */ +#define SIOP_CTEST5_DREQ 0x02 /* (re)set internal REQ */ +#define SIOP_CTEST5_DACK 0x01 /* (re)set internal ACK */ + +/* Chip test register 6 (ctest6) DMA FIFO access */ + +/* Chip test register 7 (ctest7) */ + +#define SIOP_CTEST7_RES 0xe0 +#define SIOP_CTEST7_STD 0x10 /* Disable selection timeout */ +#define SIOP_CTEST7_DFP 0x08 /* DMA FIFO parity bit */ +#define SIOP_CTEST7_EVP 0x04 /* Even parity (to host bus) */ +#define SIOP_CTEST7_DC 0x02 /* Drive DC pin low on SCRIPT fetches */ +#define SIOP_CTEST7_DIFF 0x01 /* Differential mode */ + +/* DMA FIFO register (dfifo) */ + +#define SIOP_DFIFO_FLF 0x80 /* Flush (spill) DMA FIFO */ +#define SIOP_DFIFO_CLF 0x40 /* Clear DMA and SCSI FIFOs */ +#define SIOP_DFIFO_BO 0x3f /* FIFO byte offset counter */ + +/* Interrupt status register (istat) */ + +#define SIOP_ISTAT_ABRT 0x80 /* Abort operation */ +#define SIOP_ISTAT_RES 0x70 +#define SIOP_ISTAT_CON 0x08 /* Connected */ +#define SIOP_ISTAT_PRE 0x04 /* Pointer register empty */ +#define SIOP_ISTAT_SIP 0x02 /* SCSI Interrupt pending */ +#define SIOP_ISTAT_DIP 0x01 /* DMA Interrupt pending */ + + +/* DMA Mode register (dmode) */ + +#define SIOP_DMODE_BL_MASK 0xc0 /* 0->1 1->2 2->4 3->8 */ +#define SIOP_DMODE_BW16 0x20 /* Bus Width is 16 bits */ +#define SIOP_DMODE_286 0x10 /* 286 mode */ +#define SIOP_DMODE_IO_M 0x08 /* xfer data to memory or I/O space */ +#define SIOP_DMODE_FAM 0x04 /* fixed address mode */ +#define SIOP_DMODE_PIPE 0x02 /* SCRIPTS in Pipeline mode */ +#define SIOP_DMODE_MAN 0x01 /* SCRIPTS in Manual start mode */ + +/* DMA interrupt enable register (dien) */ + +#define SIOP_DIEN_RES 0xe0 +#define SIOP_DIEN_ABRT 0x10 /* On Abort */ +#define SIOP_DIEN_SSI 0x08 /* On SCRIPTS sstep */ +#define SIOP_DIEN_SIR 0x04 /* On SCRIPTS intr instruction */ +#define SIOP_DIEN_WTD 0x02 /* On watchdog timeout */ +#define SIOP_DIEN_OPC 0x01 /* On SCRIPTS illegal opcode */ + +/* DMA control register (dcntl) */ + +#define SIOP_DCNTL_CF_MASK 0xc0 /* Clock frequency dividers: + 0 --> 37.51..50.00 Mhz, div=2 + 1 --> 25.01..37.50 Mhz, div=1.5 + 2 --> 16.67..25.00 Mhz, div=1 + 3 --> reserved + */ +#define SIOP_DCNTL_S16 0x20 /* SCRIPTS fetches 16bits at a time */ +#define SIOP_DCNTL_SSM 0x10 /* Single step mode */ +#define SIOP_DCNTL_LLM 0x08 /* Enable Low-level mode */ +#define SIOP_DCNTL_STD 0x04 /* Start SCRIPTS operation */ +#define SIOP_DCNTL_RES 0x02 +#define SIOP_DCNTL_RST 0x01 /* Software reset */ + diff --git a/scsi/adapters/scsi_53C700_hdw.c b/scsi/adapters/scsi_53C700_hdw.c new file mode 100644 index 0000000..61b5a3b --- /dev/null +++ b/scsi/adapters/scsi_53C700_hdw.c @@ -0,0 +1,696 @@ +/* + * Mach Operating System + * Copyright (c) 1991,1990,1989 Carnegie Mellon University + * All Rights Reserved. + * + * Permission to use, copy, modify and distribute this software and its + * documentation is hereby granted, provided that both the copyright + * notice and this permission notice appear in all copies of the + * software, derivative works or modified versions, and any portions + * thereof, and that both notices appear in supporting documentation. + * + * CARNEGIE MELLON ALLOWS FREE USE OF THIS SOFTWARE IN ITS + * CONDITION. CARNEGIE MELLON DISCLAIMS ANY LIABILITY OF ANY KIND FOR + * ANY DAMAGES WHATSOEVER RESULTING FROM THE USE OF THIS SOFTWARE. + * + * Carnegie Mellon requests users of this software to return to + * + * Software Distribution Coordinator or Software.Distribution@CS.CMU.EDU + * School of Computer Science + * Carnegie Mellon University + * Pittsburgh PA 15213-3890 + * + * any improvements or extensions that they make and grant Carnegie the + * rights to redistribute these changes. + */ +/* + * File: scsi_53C700_hdw.c + * Author: Alessandro Forin, Carnegie Mellon University + * Date: 8/91 + * + * Bottom layer of the SCSI driver: chip-dependent functions + * + * This file contains the code that is specific to the NCR 53C700 + * SCSI chip (Host Bus Adapter in SCSI parlance): probing, start + * operation, and interrupt routine. + */ + + +#include +#if NSIOP > 0 +#include + +#include +#include +#include +#include +#include + +#include + +#include +#include +#include + +#include + +#ifdef PAD +typedef struct { + volatile unsigned char siop_scntl0; /* rw: SCSI control reg 0 */ + PAD(pad0); + volatile unsigned char siop_scntl1; /* rw: SCSI control reg 1 */ + PAD(pad1); + volatile unsigned char siop_sdid; /* rw: SCSI Destination ID */ + PAD(pad2); + volatile unsigned char siop_sien; /* rw: SCSI Interrupt Enable */ + PAD(pad3); + volatile unsigned char siop_scid; /* rw: SCSI Chip ID reg */ + PAD(pad4); + volatile unsigned char siop_sxfer; /* rw: SCSI Transfer reg */ + PAD(pad5); + volatile unsigned char siop_sodl; /* rw: SCSI Output Data Latch */ + PAD(pad6); + volatile unsigned char siop_socl; /* rw: SCSI Output Control Latch */ + PAD(pad7); + volatile unsigned char siop_sfbr; /* ro: SCSI First Byte Received */ + PAD(pad8); + volatile unsigned char siop_sidl; /* ro: SCSI Input Data Latch */ + PAD(pad9); + volatile unsigned char siop_sbdl; /* ro: SCSI Bus Data Lines */ + PAD(pad10); + volatile unsigned char siop_sbcl; /* ro: SCSI Bus Control Lines */ + PAD(pad11); + volatile unsigned char siop_dstat; /* ro: DMA status */ + PAD(pad12); + volatile unsigned char siop_sstat0; /* ro: SCSI status reg 0 */ + PAD(pad13); + volatile unsigned char siop_sstat1; /* ro: SCSI status reg 1 */ + PAD(pad14); + volatile unsigned char siop_sstat2; /* ro: SCSI status reg 2 */ + PAD(pad15); + volatile unsigned char siop_res1; + PAD(pad16); + volatile unsigned char siop_res2; + PAD(pad17); + volatile unsigned char siop_res3; + PAD(pad18); + volatile unsigned char siop_res4; + PAD(pad19); + volatile unsigned char siop_ctest0; /* ro: Chip test register 0 */ + PAD(pad20); + volatile unsigned char siop_ctest1; /* ro: Chip test register 1 */ + PAD(pad21); + volatile unsigned char siop_ctest2; /* ro: Chip test register 2 */ + PAD(pad22); + volatile unsigned char siop_ctest3; /* ro: Chip test register 3 */ + PAD(pad23); + volatile unsigned char siop_ctest4; /* rw: Chip test register 4 */ + PAD(pad24); + volatile unsigned char siop_ctest5; /* rw: Chip test register 5 */ + PAD(pad25); + volatile unsigned char siop_ctest6; /* rw: Chip test register 6 */ + PAD(pad26); + volatile unsigned char siop_ctest7; /* rw: Chip test register 7 */ + PAD(pad27); + volatile unsigned char siop_temp0; /* rw: Temporary Stack reg */ + PAD(pad28); + volatile unsigned char siop_temp1; + PAD(pad29); + volatile unsigned char siop_temp2; + PAD(pad30); + volatile unsigned char siop_temp3; + PAD(pad31); + volatile unsigned char siop_dfifo; /* rw: DMA FIFO */ + PAD(pad32); + volatile unsigned char siop_istat; /* rw: Interrupt Status reg */ + PAD(pad33); + volatile unsigned char siop_res5; + PAD(pad34); + volatile unsigned char siop_res6; + PAD(pad35); + volatile unsigned char siop_dbc0; /* rw: DMA Byte Counter reg */ + PAD(pad36); + volatile unsigned char siop_dbc1; + PAD(pad37); + volatile unsigned char siop_dbc2; + PAD(pad38); + volatile unsigned char siop_dcmd; /* rw: DMA Command Register */ + PAD(pad39); + volatile unsigned char siop_dnad0; /* rw: DMA Next Address */ + PAD(pad40); + volatile unsigned char siop_dnad1; + PAD(pad41); + volatile unsigned char siop_dnad2; + PAD(pad42); + volatile unsigned char siop_dnad3; + PAD(pad43); + volatile unsigned char siop_dsp0; /* rw: DMA SCRIPTS Pointer reg */ + PAD(pad44); + volatile unsigned char siop_dsp1; + PAD(pad45); + volatile unsigned char siop_dsp2; + PAD(pad46); + volatile unsigned char siop_dsp3; + PAD(pad47); + volatile unsigned char siop_dsps0; /* rw: DMA SCRIPTS Pointer Save reg */ + PAD(pad48); + volatile unsigned char siop_dsps1; + PAD(pad49); + volatile unsigned char siop_dsps2; + PAD(pad50); + volatile unsigned char siop_dsps3; + PAD(pad51); + volatile unsigned char siop_dmode; /* rw: DMA Mode reg */ + PAD(pad52); + volatile unsigned char siop_res7; + PAD(pad53); + volatile unsigned char siop_res8; + PAD(pad54); + volatile unsigned char siop_res9; + PAD(pad55); + volatile unsigned char siop_res10; + PAD(pad56); + volatile unsigned char siop_dien; /* rw: DMA Interrupt Enable */ + PAD(pad57); + volatile unsigned char siop_dwt; /* rw: DMA Watchdog Timer */ + PAD(pad58); + volatile unsigned char siop_dcntl; /* rw: DMA Control reg */ + PAD(pad59); + volatile unsigned char siop_res11; + PAD(pad60); + volatile unsigned char siop_res12; + PAD(pad61); + volatile unsigned char siop_res13; + PAD(pad62); + volatile unsigned char siop_res14; + PAD(pad63); +} siop_padded_regmap_t; +#else +typedef siop_regmap_t siop_padded_regmap_t; +#endif + +/* + * Macros to make certain things a little more readable + */ + +/* forward decls */ + +int siop_reset_scsibus(); +boolean_t siop_probe_target(); + +/* + * State descriptor for this layer. There is one such structure + * per (enabled) 53C700 interface + */ +struct siop_softc { + watchdog_t wd; + siop_padded_regmap_t *regs; /* 53C700 registers */ + scsi_dma_ops_t *dma_ops; /* DMA operations and state */ + opaque_t dma_state; + + script_t script; + int (*error_handler)(); + int in_count; /* amnt we expect to receive */ + int out_count; /* amnt we are going to ship */ + + volatile char state; +#define SIOP_STATE_BUSY 0x01 /* selecting or currently connected */ +#define SIOP_STATE_TARGET 0x04 /* currently selected as target */ +#define SIOP_STATE_COLLISION 0x08 /* lost selection attempt */ +#define SIOP_STATE_DMA_IN 0x10 /* tgt --> initiator xfer */ + + unsigned char ntargets; /* how many alive on this scsibus */ + unsigned char done; + + scsi_softc_t *sc; + target_info_t *active_target; + + target_info_t *next_target; /* trying to seize bus */ + queue_head_t waiting_targets;/* other targets competing for bus */ + +} siop_softc_data[NSIOP]; + +typedef struct siop_softc *siop_softc_t; + +siop_softc_t siop_softc[NSIOP]; + +/* + * Definition of the controller for the auto-configuration program. + */ + +int siop_probe(), scsi_slave(), scsi_attach(), siop_go(), siop_intr(); + +caddr_t siop_std[NSIOP] = { 0 }; +struct bus_device *siop_dinfo[NSIOP*8]; +struct bus_ctlr *siop_minfo[NSIOP]; +struct bus_driver siop_driver = + { siop_probe, scsi_slave, scsi_attach, siop_go, siop_std, "rz", siop_dinfo, + "siop", siop_minfo, BUS_INTR_B4_PROBE}; + +/* + * Scripts + */ +struct script +siop_script_data_in[] = { +}, + +siop_script_data_out[] = { +}, + +siop_script_cmd[] = { +}, + +/* Synchronous transfer neg(oti)ation */ + +siop_script_try_synch[] = { +}, + +/* Disconnect sequence */ + +siop_script_disconnect[] = { +}; + + +#define DEBUG +#ifdef DEBUG + +siop_state(base) + vm_offset_t base; +{ + siop_padded_regmap_t *regs; +.... + return 0; +} +siop_target_state(tgt) + target_info_t *tgt; +{ + if (tgt == 0) + tgt = siop_softc[0]->active_target; + if (tgt == 0) + return 0; + db_printf("@x%x: fl %x dma %X+%x cmd %x@%X id %x per %x off %x ior %X ret %X\n", + tgt, + tgt->flags, tgt->dma_ptr, tgt->transient_state.dma_offset, tgt->cur_cmd, + tgt->cmd_ptr, tgt->target_id, tgt->sync_period, tgt->sync_offset, + tgt->ior, tgt->done); + if (tgt->flags & TGT_DISCONNECTED){ + script_t spt; + + spt = tgt->transient_state.script; + db_printf("disconnected at "); + db_printsym(spt,1); + db_printf(": %x ", spt->condition); + db_printsym(spt->action,1); + db_printf(", "); + db_printsym(tgt->transient_state.handler, 1); + db_printf("\n"); + } + + return 0; +} + +siop_all_targets(unit) +{ + int i; + target_info_t *tgt; + for (i = 0; i < 8; i++) { + tgt = siop_softc[unit]->sc->target[i]; + if (tgt) + siop_target_state(tgt); + } +} + +siop_script_state(unit) +{ + script_t spt = siop_softc[unit]->script; + + if (spt == 0) return 0; + db_printsym(spt,1); + db_printf(": %x ", spt->condition); + db_printsym(spt->action,1); + db_printf(", "); + db_printsym(siop_softc[unit]->error_handler, 1); + return 0; + +} + +#define PRINT(x) if (scsi_debug) printf x + +#define TRMAX 200 +int tr[TRMAX+3]; +int trpt, trpthi; +#define TR(x) tr[trpt++] = x +#define TRWRAP trpthi = trpt; trpt = 0; +#define TRCHECK if (trpt > TRMAX) {TRWRAP} + +#define TRACE + +#ifdef TRACE + +#define LOGSIZE 256 +int siop_logpt; +char siop_log[LOGSIZE]; + +#define MAXLOG_VALUE 0x24 +struct { + char *name; + unsigned int count; +} logtbl[MAXLOG_VALUE]; + +static LOG(e,f) + char *f; +{ + siop_log[siop_logpt++] = (e); + if (siop_logpt == LOGSIZE) siop_logpt = 0; + if ((e) < MAXLOG_VALUE) { + logtbl[(e)].name = (f); + logtbl[(e)].count++; + } +} + +siop_print_log(skip) + int skip; +{ + register int i, j; + register unsigned char c; + + for (i = 0, j = siop_logpt; i < LOGSIZE; i++) { + c = siop_log[j]; + if (++j == LOGSIZE) j = 0; + if (skip-- > 0) + continue; + if (c < MAXLOG_VALUE) + db_printf(" %s", logtbl[c].name); + else + db_printf("-%d", c & 0x7f); + } + db_printf("\n"); + return 0; +} + +siop_print_stat() +{ + register int i; + register char *p; + for (i = 0; i < MAXLOG_VALUE; i++) { + if (p = logtbl[i].name) + printf("%d %s\n", logtbl[i].count, p); + } +} + +#else /* TRACE */ +#define LOG(e,f) +#endif /* TRACE */ + +#else /* DEBUG */ +#define PRINT(x) +#define LOG(e,f) +#define TR(x) +#define TRCHECK +#define TRWRAP +#endif /* DEBUG */ + + +/* + * Probe/Slave/Attach functions + */ + +/* + * Probe routine: + * Should find out (a) if the controller is + * present and (b) which/where slaves are present. + * + * Implementation: + * Send an identify msg to each possible target on the bus + * except of course ourselves. + */ +siop_probe(reg, ui) + char *reg; + struct bus_ctlr *ui; +{ + int unit = ui->unit; + siop_softc_t siop = &siop_softc_data[unit]; + int target_id, i; + scsi_softc_t *sc; + register siop_padded_regmap_t *regs; + int s; + boolean_t did_banner = FALSE; + char *cmd_ptr; + static char *here = "siop_probe"; + + /* + * We are only called if the chip is there, + * but make sure anyways.. + */ + regs = (siop_padded_regmap_t *) (reg); + if (check_memory(regs, 0)) + return 0; + +#if notyet + /* Mappable version side */ + SIOP_probe(reg, ui); +#endif + + /* + * Initialize hw descriptor + */ + siop_softc[unit] = siop; + siop->regs = regs; + + if ((siop->dma_ops = (scsi_dma_ops_t *)siop_std[unit]) == 0) + /* use same as unit 0 if undefined */ + siop->dma_ops = (scsi_dma_ops_t *)siop_std[0]; + siop->dma_state = (*siop->dma_ops->init)(unit, reg); + + queue_init(&siop->waiting_targets); + + sc = scsi_master_alloc(unit, siop); + siop->sc = sc; + + sc->go = siop_go; + sc->probe = siop_probe_target; + sc->watchdog = scsi_watchdog; + siop->wd.reset = siop_reset_scsibus; + +#ifdef MACH_KERNEL + sc->max_dma_data = -1; /* unlimited */ +#else + sc->max_dma_data = scsi_per_target_virtual; +#endif + + /* + * Reset chip + */ + s = splbio(); + siop_reset(siop, TRUE); + + /* + * Our SCSI id on the bus. + */ + + sc->initiator_id = my_scsi_id(unit); + printf("%s%d: my SCSI id is %d", ui->name, unit, sc->initiator_id); + + /* + * For all possible targets, see if there is one and allocate + * a descriptor for it if it is there. + */ + for (target_id = 0; target_id < 8; target_id++) { + + register unsigned csr, dsr; + scsi_status_byte_t status; + + /* except of course ourselves */ + if (target_id == sc->initiator_id) + continue; + + ..... + + printf(",%s%d", did_banner++ ? " " : " target(s) at ", + target_id); + + ..... + + + /* + * Found a target + */ + siop->ntargets++; + { + register target_info_t *tgt; + + tgt = scsi_slave_alloc(unit, target_id, siop); + + tgt->cmd_ptr = ... + tgt->dma_ptr = ... +#ifdef MACH_KERNEL +#else /*MACH_KERNEL*/ + fdma_init(&tgt->fdma, scsi_per_target_virtual); +#endif /*MACH_KERNEL*/ + } + } + printf(".\n"); + + splx(s); + return 1; +} + +boolean_t +siop_probe_target(sc, tgt, ior) + scsi_softc_t *sc; + target_info_t *tgt; + io_req_t ior; +{ + siop_softc_t siop = siop_softc[sc->masterno]; + boolean_t newlywed; + + newlywed = (tgt->cmd_ptr == 0); + if (newlywed) { + /* desc was allocated afresh */ + + tgt->cmd_ptr = ... + tgt->dma_ptr = ... +#ifdef MACH_KERNEL +#else /*MACH_KERNEL*/ + fdma_init(&tgt->fdma, scsi_per_target_virtual); +#endif /*MACH_KERNEL*/ + + } + + if (scsi_inquiry(sc, tgt, SCSI_INQ_STD_DATA) == SCSI_RET_DEVICE_DOWN) + return FALSE; + + tgt->flags = TGT_ALIVE; + return TRUE; +} + + +static siop_wait(preg, until) + volatile unsigned char *preg; +{ + int timeo = 1000000; + while ((*preg & until) != until) { + delay(1); + if (!timeo--) { + printf("siop_wait TIMEO with x%x\n", *preg); + break; + } + } + return *preg; +} + + +siop_reset(siop, quickly) + siop_softc_t siop; + boolean_t quickly; +{ + register siop_padded_regmap_t *regs = siop->regs; + + .... + + if (quickly) + return; + + /* + * reset the scsi bus, the interrupt routine does the rest + * or you can call siop_bus_reset(). + */ + .... + +} + +/* + * Operational functions + */ + +/* + * Start a SCSI command on a target + */ +siop_go(sc, tgt, cmd_count, in_count, cmd_only) + scsi_softc_t *sc; + target_info_t *tgt; + boolean_t cmd_only; +{ + siop_softc_t siop; + register int s; + boolean_t disconn; + script_t scp; + boolean_t (*handler)(); + + LOG(1,"go"); + + siop = (siop_softc_t)tgt->hw_state; + + .... +} + +siop_attempt_selection(siop) + siop_softc_t siop; +{ + target_info_t *tgt; + register int out_count; + siop_padded_regmap_t *regs; + register int cmd; + boolean_t ok; + scsi_ret_t ret; + + regs = siop->regs; + tgt = siop->next_target; + + LOG(4,"select"); + LOG(0x80+tgt->target_id,0); + + /* + * Init bus state variables and set registers. + */ + siop->active_target = tgt; + + /* reselection pending ? */ + ...... +} + +/* + * Interrupt routine + * Take interrupts from the chip + * + * Implementation: + * Move along the current command's script if + * all is well, invoke error handler if not. + */ +siop_intr(unit) +{ + register siop_softc_t siop; + register script_t scp; + register unsigned csr, bs, cmd; + register siop_padded_regmap_t *regs; + boolean_t try_match; +#if notyet + extern boolean_t rz_use_mapped_interface; + + if (rz_use_mapped_interface) + return SIOP_intr(unit); +#endif + + LOG(5,"\n\tintr"); + + siop = siop_softc[unit]; + regs = siop->regs; + + /* ack interrupt */ + .... +} + + +siop_target_intr(siop) + register siop_softc_t siop; +{ + panic("SIOP: TARGET MODE !!!\n"); +} + +/* + * All the many little things that the interrupt + * routine might switch to + */ + +#endif /*NSIOP > 0*/ + diff --git a/scsi/adapters/scsi_53C94.h b/scsi/adapters/scsi_53C94.h new file mode 100644 index 0000000..82891f3 --- /dev/null +++ b/scsi/adapters/scsi_53C94.h @@ -0,0 +1,253 @@ +/* + * Mach Operating System + * Copyright (c) 1991,1990,1989 Carnegie Mellon University + * All Rights Reserved. + * + * Permission to use, copy, modify and distribute this software and its + * documentation is hereby granted, provided that both the copyright + * notice and this permission notice appear in all copies of the + * software, derivative works or modified versions, and any portions + * thereof, and that both notices appear in supporting documentation. + * + * CARNEGIE MELLON ALLOWS FREE USE OF THIS SOFTWARE IN ITS "AS IS" + * CONDITION. CARNEGIE MELLON DISCLAIMS ANY LIABILITY OF ANY KIND FOR + * ANY DAMAGES WHATSOEVER RESULTING FROM THE USE OF THIS SOFTWARE. + * + * Carnegie Mellon requests users of this software to return to + * + * Software Distribution Coordinator or Software.Distribution@CS.CMU.EDU + * School of Computer Science + * Carnegie Mellon University + * Pittsburgh PA 15213-3890 + * + * any improvements or extensions that they make and grant Carnegie Mellon + * the rights to redistribute these changes. + */ +/* + * File: scsi_53C94.h + * Author: Alessandro Forin, Carnegie Mellon University + * Date: 9/90 + * + * Defines for the NCR 53C94 ASC (SCSI interface) + * Some gotcha came from the "86C01/53C94 DMA lab work" written + * by Ken Stewart (NCR MED Logic Products Applications Engineer) + * courtesy of NCR. Thanks Ken ! + */ + +/* + * Register map + */ + +typedef struct { + volatile unsigned char asc_tc_lsb; /* rw: Transfer Counter LSB */ + volatile unsigned char asc_tc_msb; /* rw: Transfer Counter MSB */ + volatile unsigned char asc_fifo; /* rw: FIFO top */ + volatile unsigned char asc_cmd; /* rw: Command */ + volatile unsigned char asc_csr; /* r: Status */ +#define asc_dbus_id asc_csr /* w: Destination Bus ID */ + volatile unsigned char asc_intr; /* r: Interrupt */ +#define asc_sel_timo asc_intr /* w: (re)select timeout */ + volatile unsigned char asc_ss; /* r: Sequence Step */ +#define asc_syn_p asc_ss /* w: synchronous period */ + volatile unsigned char asc_flags; /* r: FIFO flags + seq step */ +#define asc_syn_o asc_flags /* w: synchronous offset */ + volatile unsigned char asc_cnfg1; /* rw: Configuration 1 */ + volatile unsigned char asc_ccf; /* w: Clock Conv. Factor */ + volatile unsigned char asc_test; /* w: Test Mode */ + volatile unsigned char asc_cnfg2; /* rw: Configuration 2 */ + volatile unsigned char asc_cnfg3; /* rw: Configuration 3 */ + volatile unsigned char asc_rfb; /* w: Reserve FIFO byte */ +} asc_regmap_t; + + +/* + * Transfer Count: access macros + * That a NOP is required after loading the dma counter + * I learned on the NCR test code. Sic. + */ + +#define ASC_TC_MAX 0x10000 + +#define ASC_TC_GET(ptr,val) \ + val = ((ptr)->asc_tc_lsb&0xff)|(((ptr)->asc_tc_msb&0xff)<<8) +#define ASC_TC_PUT(ptr,val) \ + (ptr)->asc_tc_lsb=(val); \ + (ptr)->asc_tc_msb=(val)>>8; mb(); \ + (ptr)->asc_cmd = ASC_CMD_NOP|ASC_CMD_DMA; + +/* + * FIFO register + */ + +#define ASC_FIFO_DEEP 16 + + +/* + * Command register (command codes) + */ + +#define ASC_CMD_DMA 0x80 + /* Miscellaneous */ +#define ASC_CMD_NOP 0x00 +#define ASC_CMD_FLUSH 0x01 +#define ASC_CMD_RESET 0x02 +#define ASC_CMD_BUS_RESET 0x03 + /* Initiator state */ +#define ASC_CMD_XFER_INFO 0x10 +#define ASC_CMD_I_COMPLETE 0x11 +#define ASC_CMD_MSG_ACPT 0x12 +#define ASC_CMD_XFER_PAD 0x18 +#define ASC_CMD_SET_ATN 0x1a +#define ASC_CMD_CLR_ATN 0x1b + /* Target state */ +#define ASC_CMD_SND_MSG 0x20 +#define ASC_CMD_SND_STATUS 0x21 +#define ASC_CMD_SND_DATA 0x22 +#define ASC_CMD_DISC_SEQ 0x23 +#define ASC_CMD_TERM 0x24 +#define ASC_CMD_T_COMPLETE 0x25 +#define ASC_CMD_DISC 0x27 +#define ASC_CMD_RCV_MSG 0x28 +#define ASC_CMD_RCV_CDB 0x29 +#define ASC_CMD_RCV_DATA 0x2a +#define ASC_CMD_RCV_CMD 0x2b +#define ASC_CMD_ABRT_DMA 0x04 + /* Disconnected state */ +#define ASC_CMD_RESELECT 0x40 +#define ASC_CMD_SEL 0x41 +#define ASC_CMD_SEL_ATN 0x42 +#define ASC_CMD_SEL_ATN_STOP 0x43 +#define ASC_CMD_ENABLE_SEL 0x44 +#define ASC_CMD_DISABLE_SEL 0x45 +#define ASC_CMD_SEL_ATN3 0x46 + +/* this is approximate (no ATN3) but good enough */ +#define asc_isa_select(cmd) (((cmd)&0x7c)==0x40) + +/* + * Status register, and phase encoding + */ + +#define ASC_CSR_INT 0x80 +#define ASC_CSR_GE 0x40 +#define ASC_CSR_PE 0x20 +#define ASC_CSR_TC 0x10 +#define ASC_CSR_VGC 0x08 +#define ASC_CSR_MSG 0x04 +#define ASC_CSR_CD 0x02 +#define ASC_CSR_IO 0x01 + +#define ASC_PHASE(csr) SCSI_PHASE(csr) + +/* + * Destination Bus ID + */ + +#define ASC_DEST_ID_MASK 0x07 + + +/* + * Interrupt register + */ + +#define ASC_INT_RESET 0x80 +#define ASC_INT_ILL 0x40 +#define ASC_INT_DISC 0x20 +#define ASC_INT_BS 0x10 +#define ASC_INT_FC 0x08 +#define ASC_INT_RESEL 0x04 +#define ASC_INT_SEL_ATN 0x02 +#define ASC_INT_SEL 0x01 + + +/* + * Timeout register: + * + * val = (timeout * CLK_freq) / (8192 * CCF); + */ + +#define asc_timeout_250(clk,ccf) ((31*clk)/ccf) + +/* + * Sequence Step register + */ + +#define ASC_SS_XXXX 0xf0 +#define ASC_SS_SOM 0x80 +#define ASC_SS_MASK 0x07 +#define ASC_SS(ss) ((ss)&ASC_SS_MASK) + +/* + * Synchronous Transfer Period + */ + +#define ASC_STP_MASK 0x1f +#define ASC_STP_MIN 0x05 /* 5 clk per byte */ +#define ASC_STP_MAX 0x04 /* after ovfl, 35 clk/byte */ + +/* + * FIFO flags + */ + +#define ASC_FLAGS_SEQ_STEP 0xe0 +#define ASC_FLAGS_FIFO_CNT 0x1f + +/* + * Synchronous offset + */ + +#define ASC_SYNO_MASK 0x0f /* 0 -> asyn */ + +/* + * Configuration 1 + */ + +#define ASC_CNFG1_SLOW 0x80 +#define ASC_CNFG1_SRD 0x40 +#define ASC_CNFG1_P_TEST 0x20 +#define ASC_CNFG1_P_CHECK 0x10 +#define ASC_CNFG1_TEST 0x08 +#define ASC_CNFG1_MY_BUS_ID 0x07 + +/* + * CCF register + */ + +#define ASC_CCF_10MHz 0x2 +#define ASC_CCF_15MHz 0x3 +#define ASC_CCF_20MHz 0x4 +#define ASC_CCF_25MHz 0x5 + +#define mhz_to_ccf(x) (((x-1)/5)+1) /* see specs for limits */ + +/* + * Test register + */ + +#define ASC_TEST_XXXX 0xf8 +#define ASC_TEST_HI_Z 0x04 +#define ASC_TEST_I 0x02 +#define ASC_TEST_T 0x01 + +/* + * Configuration 2 + */ + +#define ASC_CNFG2_RFB 0x80 +#define ASC_CNFG2_EPL 0x40 +#define ASC_CNFG2_EBC 0x20 +#define ASC_CNFG2_DREQ_HIZ 0x10 +#define ASC_CNFG2_SCSI2 0x08 +#define ASC_CNFG2_BPA 0x04 +#define ASC_CNFG2_RPE 0x02 +#define ASC_CNFG2_DPE 0x01 + +/* + * Configuration 3 + */ + +#define ASC_CNFG3_XXXX 0xf8 +#define ASC_CNFG3_SRB 0x04 +#define ASC_CNFG3_ALT_DMA 0x02 +#define ASC_CNFG3_T8 0x01 + diff --git a/scsi/adapters/scsi_53C94_hdw.c b/scsi/adapters/scsi_53C94_hdw.c new file mode 100644 index 0000000..dad9b22 --- /dev/null +++ b/scsi/adapters/scsi_53C94_hdw.c @@ -0,0 +1,2840 @@ +/* + * Mach Operating System + * Copyright (c) 1993,1992,1991,1990,1989 Carnegie Mellon University + * All Rights Reserved. + * + * Permission to use, copy, modify and distribute this software and its + * documentation is hereby granted, provided that both the copyright + * notice and this permission notice appear in all copies of the + * software, derivative works or modified versions, and any portions + * thereof, and that both notices appear in supporting documentation. + * + * CARNEGIE MELLON ALLOWS FREE USE OF THIS SOFTWARE IN ITS AS-IS + * CONDITION. CARNEGIE MELLON DISCLAIMS ANY LIABILITY OF ANY KIND FOR + * ANY DAMAGES WHATSOEVER RESULTING FROM THE USE OF THIS SOFTWARE. + * + * Carnegie Mellon requests users of this software to return to + * + * Software Distribution Coordinator or Software.Distribution@CS.CMU.EDU + * School of Computer Science + * Carnegie Mellon University + * Pittsburgh PA 15213-3890 + * + * any improvements or extensions that they make and grant Carnegie Mellon + * the rights to redistribute these changes. + */ +/* + * File: scsi_53C94_hdw.h + * Author: Alessandro Forin, Carnegie Mellon University + * Date: 9/90 + * + * Bottom layer of the SCSI driver: chip-dependent functions + * + * This file contains the code that is specific to the NCR 53C94 + * SCSI chip (Host Bus Adapter in SCSI parlance): probing, start + * operation, and interrupt routine. + */ + +/* + * This layer works based on small simple 'scripts' that are installed + * at the start of the command and drive the chip to completion. + * The idea comes from the specs of the NCR 53C700 'script' processor. + * + * There are various reasons for this, mainly + * - Performance: identify the common (successful) path, and follow it; + * at interrupt time no code is needed to find the current status + * - Code size: it should be easy to compact common operations + * - Adaptability: the code skeleton should adapt to different chips without + * terrible complications. + * - Error handling: and it is easy to modify the actions performed + * by the scripts to cope with strange but well identified sequences + * + */ + +#include +#if NASC > 0 +#include + +#ifdef DECSTATION +typedef unsigned char asc_register_t; +#define PAD(n) char n[3]; +#define mb() +#ifdef MACH_KERNEL +#define HAS_MAPPED_SCSI +#endif +#define ASC_PROBE_DYNAMICALLY FALSE /* established custom */ +#define DEBUG 1 +#define TRACE 1 +#endif + +#ifdef FLAMINGO +typedef unsigned int asc_register_t; +#define PAD(n) int n; /* sparse ! */ +#define mb() wbflush() /* memory barrier */ +#define ASC_PROBE_DYNAMICALLY TRUE +#define DEBUG 1 +#define TRACE 1 +#endif + +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include + +#define private static + +#ifdef PAD +typedef struct { + volatile asc_register_t asc_tc_lsb; /* rw: Transfer Counter LSB */ + PAD(pad0) + volatile asc_register_t asc_tc_msb; /* rw: Transfer Counter MSB */ + PAD(pad1) + volatile asc_register_t asc_fifo; /* rw: FIFO top */ + PAD(pad2) + volatile asc_register_t asc_cmd; /* rw: Command */ + PAD(pad3) + volatile asc_register_t asc_csr; /* r: Status */ +/*#define asc_dbus_id asc_csr /* w: Destination Bus ID */ + PAD(pad4) + volatile asc_register_t asc_intr; /* r: Interrupt */ +/*#define asc_sel_timo asc_intr /* w: (re)select timeout */ + PAD(pad5) + volatile asc_register_t asc_ss; /* r: Sequence Step */ +/*#define asc_syn_p asc_ss /* w: synchronous period */ + PAD(pad6) + volatile asc_register_t asc_flags; /* r: FIFO flags + seq step */ +/*#define asc_syn_o asc_flags /* w: synchronous offset */ + PAD(pad7) + volatile asc_register_t asc_cnfg1; /* rw: Configuration 1 */ + PAD(pad8) + volatile asc_register_t asc_ccf; /* w: Clock Conv. Factor */ + PAD(pad9) + volatile asc_register_t asc_test; /* w: Test Mode */ + PAD(pad10) + volatile asc_register_t asc_cnfg2; /* rw: Configuration 2 */ + PAD(pad11) + volatile asc_register_t asc_cnfg3; /* rw: Configuration 3 */ + PAD(pad12) + volatile asc_register_t asc_rfb; /* w: Reserve FIFO byte */ + PAD(pad13) +} asc_padded_regmap_t; + +#else /* !PAD */ + +typedef asc_regmap_t asc_padded_regmap_t; + +#endif /* !PAD */ + +#define get_reg(r,x) ((unsigned char)((r)->x)) + +#define fifo_count(r) ((r)->asc_flags & ASC_FLAGS_FIFO_CNT) +#define get_fifo(r) get_reg(r,asc_fifo) + +boolean_t asc_probe_dynamically = ASC_PROBE_DYNAMICALLY; + +/* + * We might need to use some fields usually + * handled by the DMA engine, if asked to. + * These are "dma_ptr" and "hba_dep". + */ +#define has_oddb hba_dep[0] +#define the_oddb hba_dep[1] + +/* + * A script has a three parts: a pre-condition, an action, and + * an optional command to the chip. The first triggers error + * handling if not satisfied and in our case it is a match + * of the expected and actual scsi-bus phases. + * The action part is just a function pointer, and the + * command is what the 53c90 should be told to do at the end + * of the action processing. This command is only issued and the + * script proceeds if the action routine returns TRUE. + * See asc_intr() for how and where this is all done. + */ + +typedef struct script { + unsigned char condition; /* expected state at interrupt */ + unsigned char command; /* command to the chip */ + unsigned short flags; /* unused padding */ + boolean_t (*action)(); /* extra operations */ +} *script_t; + +/* Matching on the condition value */ +#define ANY 0xff +#define SCRIPT_MATCH(csr,ir,value) ((SCSI_PHASE(csr)==(value)) || \ + (((value)==ANY) && \ + ((ir)&(ASC_INT_DISC|ASC_INT_FC)))) + +/* When no command is needed */ +#define SCRIPT_END -1 + +/* forward decls of script actions */ +boolean_t + asc_end(), /* all come to an end */ + asc_clean_fifo(), /* .. in preparation for status byte */ + asc_get_status(), /* get status from target */ + asc_put_status(), /* send status to initiator */ + asc_dma_in(), /* get data from target via dma */ + asc_dma_in_r(), /* get data from target via dma (restartable)*/ + asc_dma_out(), /* send data to target via dma */ + asc_dma_out_r(), /* send data to target via dma (restartable) */ + asc_dosynch(), /* negotiate synch xfer */ + asc_msg_in(), /* receive the disconenct message */ + asc_disconnected(), /* target has disconnected */ + asc_reconnect(); /* target reconnected */ + +/* forward decls of error handlers */ +boolean_t + asc_err_generic(), /* generic handler */ + asc_err_disconn(), /* target disconnects amidst */ + gimmeabreak(); /* drop into the debugger */ + +int asc_reset_scsibus(); +boolean_t asc_probe_target(); +private asc_wait(); + +/* + * State descriptor for this layer. There is one such structure + * per (enabled) SCSI-53c90 interface + */ +struct asc_softc { + watchdog_t wd; + asc_padded_regmap_t *regs; /* 53c90 registers */ + + scsi_dma_ops_t *dma_ops; /* DMA operations and state */ + opaque_t dma_state; + + script_t script; /* what should happen next */ + boolean_t (*error_handler)();/* what if something is wrong */ + int in_count; /* amnt we expect to receive */ + int out_count; /* amnt we are going to ship */ + + volatile char state; +#define ASC_STATE_BUSY 0x01 /* selecting or currently connected */ +#define ASC_STATE_TARGET 0x04 /* currently selected as target */ +#define ASC_STATE_COLLISION 0x08 /* lost selection attempt */ +#define ASC_STATE_DMA_IN 0x10 /* tgt --> initiator xfer */ +#define ASC_STATE_SPEC_DMA 0x20 /* special, 8 byte threshold dma */ +#define ASC_STATE_DO_RFB 0x40 /* DMA engine cannot handle odd bytes */ + + unsigned char ntargets; /* how many alive on this scsibus */ + unsigned char done; + unsigned char extra_count; /* sleazy trick to spare an interrupt */ + int dmacnt_at_end; + + scsi_softc_t *sc; /* HBA-indep info */ + target_info_t *active_target; /* the current one */ + + target_info_t *next_target; /* trying to seize bus */ + queue_head_t waiting_targets;/* other targets competing for bus */ + + unsigned char ss_was; /* districate powered on/off devices */ + unsigned char cmd_was; + + unsigned char timeout; /* cache a couple numbers */ + unsigned char ccf; + unsigned char clk; + +} asc_softc_data[NASC]; + +typedef struct asc_softc *asc_softc_t; + +asc_softc_t asc_softc[NASC]; + +/* + * Synch xfer parameters, and timing conversions + */ +int asc_min_period = 5; /* in CLKS/BYTE, e.g. 1 CLK = 40nsecs @25 Mhz */ +int asc_max_offset = 15; /* pure number */ + +int asc_to_scsi_period(a,clk) +{ + /* Note: the SCSI unit is 4ns, hence + A_P * 1,000,000,000 + ------------------- = S_P + C_Mhz * 4 + */ + return a * (250 / clk); + +} + +int scsi_period_to_asc(p,clk) +{ + register int ret; + + ret = (p * clk) / 250; + if (ret < asc_min_period) + return asc_min_period; + if ((asc_to_scsi_period(ret,clk)) < p) + return ret + 1; + return ret; +} + +#define readback(a) {register int foo; foo = a; mb();} + +#define u_min(a,b) (((a) < (b)) ? (a) : (b)) + +/* + * Definition of the controller for the auto-configuration program. + */ + +int asc_probe(), scsi_slave(), asc_go(), asc_intr(); +void scsi_attach(); + +vm_offset_t asc_std[NASC] = { 0 }; +struct bus_device *asc_dinfo[NASC*8]; +struct bus_ctlr *asc_minfo[NASC]; +struct bus_driver asc_driver = + { asc_probe, scsi_slave, scsi_attach, asc_go, asc_std, "rz", asc_dinfo, + "asc", asc_minfo, BUS_INTR_B4_PROBE}; + + +int asc_clock_speed_in_mhz[NASC] = {25,25,25,25}; /* original 3max */ + +asc_set_dmaops(unit, dmaops) + unsigned int unit; + scsi_dma_ops_t *dmaops; +{ + if (unit < NASC) + asc_std[unit] = (vm_offset_t)dmaops; +} + +/* + * Scripts + */ +struct script +asc_script_data_in[] = { /* started with SEL & DMA */ + {SCSI_PHASE_DATAI, ASC_CMD_XFER_INFO|ASC_CMD_DMA, 0, asc_dma_in}, + {SCSI_PHASE_STATUS, ASC_CMD_I_COMPLETE, 0, asc_clean_fifo}, + {SCSI_PHASE_MSG_IN, ASC_CMD_MSG_ACPT, 0, asc_get_status}, + {ANY, SCRIPT_END, 0, asc_end} +}, + +asc_script_data_out[] = { /* started with SEL & DMA */ + {SCSI_PHASE_DATAO, ASC_CMD_XFER_INFO|ASC_CMD_DMA, 0, asc_dma_out}, + {SCSI_PHASE_STATUS, ASC_CMD_I_COMPLETE, 0, asc_clean_fifo}, + {SCSI_PHASE_MSG_IN, ASC_CMD_MSG_ACPT, 0, asc_get_status}, + {ANY, SCRIPT_END, 0, asc_end} +}, + +asc_script_try_synch[] = { + {SCSI_PHASE_MSG_OUT, ASC_CMD_I_COMPLETE,0, asc_dosynch}, + {SCSI_PHASE_MSG_IN, ASC_CMD_MSG_ACPT, 0, asc_get_status}, + {ANY, SCRIPT_END, 0, asc_end} +}, + +asc_script_simple_cmd[] = { + {SCSI_PHASE_STATUS, ASC_CMD_I_COMPLETE, 0, asc_clean_fifo}, + {SCSI_PHASE_MSG_IN, ASC_CMD_MSG_ACPT, 0, asc_get_status}, + {ANY, SCRIPT_END, 0, asc_end} +}, + +asc_script_disconnect[] = { + {ANY, ASC_CMD_ENABLE_SEL, 0, asc_disconnected}, +/**/ {SCSI_PHASE_MSG_IN, ASC_CMD_MSG_ACPT, 0, asc_reconnect} +}, + +asc_script_restart_data_in[] = { /* starts after disconnect */ + {SCSI_PHASE_DATAI, ASC_CMD_XFER_INFO|ASC_CMD_DMA, 0, asc_dma_in_r}, + {SCSI_PHASE_STATUS, ASC_CMD_I_COMPLETE, 0, asc_clean_fifo}, + {SCSI_PHASE_MSG_IN, ASC_CMD_MSG_ACPT, 0, asc_get_status}, + {ANY, SCRIPT_END, 0, asc_end} +}, + +asc_script_restart_data_out[] = { /* starts after disconnect */ + {SCSI_PHASE_DATAO, ASC_CMD_XFER_INFO|ASC_CMD_DMA, 0, asc_dma_out_r}, + {SCSI_PHASE_STATUS, ASC_CMD_I_COMPLETE, 0, asc_clean_fifo}, + {SCSI_PHASE_MSG_IN, ASC_CMD_MSG_ACPT, 0, asc_get_status}, + {ANY, SCRIPT_END, 0, asc_end} +}, + +#if documentation +/* + * This is what might happen during a read + * that disconnects + */ +asc_script_data_in_wd[] = { /* started with SEL & DMA & allow disconnect */ + {SCSI_PHASE_MSG_IN, ASC_CMD_XFER_INFO|ASC_CMD_DMA, 0, asc_msg_in}, + {ANY, ASC_CMD_ENABLE_SEL, 0, asc_disconnected}, + {SCSI_PHASE_MSG_IN, ASC_CMD_MSG_ACPT, 0, asc_reconnect}, + {SCSI_PHASE_DATAI, ASC_CMD_XFER_INFO|ASC_CMD_DMA, 0, asc_dma_in}, + {SCSI_PHASE_STATUS, ASC_CMD_I_COMPLETE, 0, asc_clean_fifo}, + {SCSI_PHASE_MSG_IN, ASC_CMD_MSG_ACPT, 0, asc_get_status}, + {ANY, SCRIPT_END, 0, asc_end} +}, +#endif + +/* + * Target mode scripts + */ +asc_script_t_data_in[] = { + {SCSI_PHASE_CMD, ASC_CMD_RCV_DATA|ASC_CMD_DMA, 0, asc_dma_in_r}, + {SCSI_PHASE_DATAO, ASC_CMD_TERM, 0, asc_put_status}, + {ANY, SCRIPT_END, 0, asc_end} +}, + +asc_script_t_data_out[] = { + {SCSI_PHASE_CMD, ASC_CMD_SND_DATA|ASC_CMD_DMA, 0, asc_dma_out_r}, + {SCSI_PHASE_DATAI, ASC_CMD_TERM, 0, asc_put_status}, + {ANY, SCRIPT_END, 0, asc_end} +}; + + +#ifdef DEBUG + +#define PRINT(x) if (scsi_debug) printf x + +asc_state(regs) + asc_padded_regmap_t *regs; +{ + register unsigned char ff,csr,ir,d0,d1,cmd; + + if (regs == 0) { + if (asc_softc[0]) + regs = asc_softc[0]->regs; + else + regs = (asc_padded_regmap_t*)0xbf400000; + } + ff = get_reg(regs,asc_flags); + csr = get_reg(regs,asc_csr); +/* ir = get_reg(regs,asc_intr); nope, clears interrupt */ + d0 = get_reg(regs,asc_tc_lsb); + d1 = get_reg(regs,asc_tc_msb); + cmd = get_reg(regs,asc_cmd); + printf("dma %x ff %x csr %x cmd %x\n", + (d1 << 8) | d0, ff, csr, cmd); + return 0; +} + +asc_target_state(tgt) + target_info_t *tgt; +{ + if (tgt == 0) + tgt = asc_softc[0]->active_target; + if (tgt == 0) + return 0; + db_printf("@x%x: fl %x dma %X+%x cmd %x@%X id %x per %x off %x ior %X ret %X\n", + tgt, + tgt->flags, tgt->dma_ptr, tgt->transient_state.dma_offset, tgt->cur_cmd, + tgt->cmd_ptr, (long)tgt->target_id, + (long)tgt->sync_period, (long)tgt->sync_offset, + tgt->ior, (long)tgt->done); + if (tgt->flags & TGT_DISCONNECTED){ + script_t spt; + + spt = tgt->transient_state.script; + db_printf("disconnected at "); + db_printsym(spt,1); + db_printf(": %x %x ", spt->condition, spt->command); + db_printsym(spt->action,1); + db_printf(", "); + db_printsym(tgt->transient_state.handler, 1); + db_printf("\n"); + } + + return 0; +} + +asc_all_targets(unit) +{ + int i; + target_info_t *tgt; + for (i = 0; i < 8; i++) { + tgt = asc_softc[unit]->sc->target[i]; + if (tgt) + asc_target_state(tgt); + } +} + +asc_script_state(unit) +{ + script_t spt = asc_softc[unit]->script; + + if (spt == 0) return 0; + db_printsym(spt,1); + db_printf(": %x %x ", spt->condition, spt->command); + db_printsym(spt->action,1); + db_printf(", "); + db_printsym(asc_softc[unit]->error_handler, 1); + return 0; +} + +#define TRMAX 200 +int tr[TRMAX+3]; +int trpt, trpthi; +#define TR(x) tr[trpt++] = x +#define TRWRAP trpthi = trpt; trpt = 0; +#define TRCHECK if (trpt > TRMAX) {TRWRAP} + + +#ifdef TRACE + +#define LOGSIZE 256 +int asc_logpt; +char asc_log[LOGSIZE]; + +#define MAXLOG_VALUE 0x42 +struct { + char *name; + unsigned int count; +} logtbl[MAXLOG_VALUE]; + +/* private */ LOG(e,f) + char *f; +{ + asc_log[asc_logpt++] = (e); + if (asc_logpt == LOGSIZE) asc_logpt = 0; + if ((e) < MAXLOG_VALUE) { + logtbl[(e)].name = (f); + logtbl[(e)].count++; + } +} + +asc_print_log(skip) + int skip; +{ + register int i, j; + register unsigned char c; + + for (i = 0, j = asc_logpt; i < LOGSIZE; i++) { + c = asc_log[j]; + if (++j == LOGSIZE) j = 0; + if (skip-- > 0) + continue; + if (c < MAXLOG_VALUE) + db_printf(" %s", logtbl[c].name); + else + db_printf("-x%x", c & 0x7f); + } +} + +asc_print_stat() +{ + register int i; + register char *p; + for (i = 0; i < MAXLOG_VALUE; i++) { + if (p = logtbl[i].name) + printf("%d %s\n", logtbl[i].count, p); + } +} + +#else /*TRACE*/ +#define LOG(e,f) +#define LOGSIZE +#endif /*TRACE*/ + +#else /*DEBUG*/ +#define PRINT(x) +#define LOG(e,f) +#define LOGSIZE +#define TR(x) +#define TRCHECK +#define TRWRAP + +#endif /*DEBUG*/ + + +/* + * Probe/Slave/Attach functions + */ + +/* + * Probe routine: + * Should find out (a) if the controller is + * present and (b) which/where slaves are present. + * + * Implementation: + * Send a test-unit-ready to each possible target on the bus + * except of course ourselves. + */ +asc_probe(reg, ui) + vm_offset_t reg; + struct bus_ctlr *ui; +{ + int unit = ui->unit; + asc_softc_t asc = &asc_softc_data[unit]; + int target_id; + scsi_softc_t *sc; + register asc_padded_regmap_t *regs; + spl_t s; + boolean_t did_banner = FALSE; + + /* + * We are only called if the right board is there, + * but make sure anyways.. + */ + if (check_memory(reg, 0)) + return 0; + +#if defined(HAS_MAPPED_SCSI) + /* Mappable version side */ + ASC_probe(reg, ui); +#endif + + /* + * Initialize hw descriptor, cache some pointers + */ + asc_softc[unit] = asc; + asc->regs = (asc_padded_regmap_t *) (reg); + + if ((asc->dma_ops = (scsi_dma_ops_t *)asc_std[unit]) == 0) + /* use same as unit 0 if undefined */ + asc->dma_ops = (scsi_dma_ops_t *)asc_std[0]; + { + int dma_bsize = 16; /* bits, preferred */ + boolean_t do_rfb = FALSE; + + asc->dma_state = (*asc->dma_ops->init)(unit, reg, &dma_bsize, &do_rfb); + if (dma_bsize > 16) + asc->state |= ASC_STATE_SPEC_DMA; + if (do_rfb) + asc->state |= ASC_STATE_DO_RFB; + } + + queue_init(&asc->waiting_targets); + + asc->clk = asc_clock_speed_in_mhz[unit]; + asc->ccf = mhz_to_ccf(asc->clk); /* see .h file */ + asc->timeout = asc_timeout_250(asc->clk,asc->ccf); + + sc = scsi_master_alloc(unit, asc); + asc->sc = sc; + + sc->go = asc_go; + sc->watchdog = scsi_watchdog; + sc->probe = asc_probe_target; + asc->wd.reset = asc_reset_scsibus; + +#ifdef MACH_KERNEL + sc->max_dma_data = -1; +#else + sc->max_dma_data = scsi_per_target_virtual; +#endif + + regs = asc->regs; + + /* + * Our SCSI id on the bus. + * The user can set this via the prom on 3maxen/pmaxen. + * If this changes it is easy to fix: make a default that + * can be changed as boot arg. + */ + { + register unsigned char my_id; + + my_id = scsi_initiator_id[unit] & 0x7; + if (my_id != 7) + regs->asc_cnfg1 = my_id; mb(); + } + + /* + * Reset chip, fully. Note that interrupts are already enabled. + */ + s = splbio(); + asc_reset(asc, TRUE, asc->state & ASC_STATE_SPEC_DMA); + + sc->initiator_id = regs->asc_cnfg1 & ASC_CNFG1_MY_BUS_ID; + printf("%s%d: SCSI id %d", ui->name, unit, sc->initiator_id); + + { + register target_info_t *tgt; + + tgt = scsi_slave_alloc(sc->masterno, sc->initiator_id, asc); + (*asc->dma_ops->new_target)(asc->dma_state, tgt); + sccpu_new_initiator(tgt, tgt); + } + + if (asc_probe_dynamically) + printf("%s", ", will probe targets on demand"); + else { + + /* + * For all possible targets, see if there is one and allocate + * a descriptor for it if it is there. + */ + for (target_id = 0; target_id < 8; target_id++) { + register unsigned char csr, ss, ir, ff; + register scsi_status_byte_t status; + + /* except of course ourselves */ + if (target_id == sc->initiator_id) + continue; + + regs->asc_cmd = ASC_CMD_FLUSH; /* empty fifo */ + mb(); + delay(2); + + regs->asc_dbus_id = target_id; mb(); + regs->asc_sel_timo = asc->timeout; mb(); + + /* + * See if the unit is ready. + * XXX SHOULD inquiry LUN 0 instead !!! + */ + regs->asc_fifo = SCSI_CMD_TEST_UNIT_READY; mb(); + regs->asc_fifo = 0; mb(); + regs->asc_fifo = 0; mb(); + regs->asc_fifo = 0; mb(); + regs->asc_fifo = 0; mb(); + regs->asc_fifo = 0; mb(); + + /* select and send it */ + regs->asc_cmd = ASC_CMD_SEL; mb(); + + /* wait for the chip to complete, or timeout */ + csr = asc_wait(regs, ASC_CSR_INT, 1); + ss = get_reg(regs,asc_ss); + ir = get_reg(regs,asc_intr); + + /* empty fifo, there is garbage in it if timeout */ + regs->asc_cmd = ASC_CMD_FLUSH; mb(); + delay(2); + + /* + * Check if the select timed out + */ + if ((ASC_SS(ss) == 0) && (ir == ASC_INT_DISC)) + /* noone out there */ + continue; + + if (SCSI_PHASE(csr) != SCSI_PHASE_STATUS) { + printf( " %s%d%s", "ignoring target at ", target_id, + " cuz it acts weirdo"); + continue; + } + + printf(",%s%d", did_banner++ ? " " : " target(s) at ", + target_id); + + regs->asc_cmd = ASC_CMD_I_COMPLETE; + wbflush(); + csr = asc_wait(regs, ASC_CSR_INT, 1); + ir = get_reg(regs,asc_intr); /* ack intr */ + mb(); + + status.bits = get_fifo(regs); /* empty fifo */ + mb(); + ff = get_fifo(regs); + + if (status.st.scsi_status_code != SCSI_ST_GOOD) + scsi_error( 0, SCSI_ERR_STATUS, status.bits, 0); + + regs->asc_cmd = ASC_CMD_MSG_ACPT; mb(); + csr = asc_wait(regs, ASC_CSR_INT, 1); + ir = get_reg(regs,asc_intr); /* ack intr */ + mb(); + + /* + * Found a target + */ + asc->ntargets++; + { + register target_info_t *tgt; + tgt = scsi_slave_alloc(sc->masterno, target_id, asc); + + (*asc->dma_ops->new_target)(asc->dma_state, tgt); + } + } + } /* asc_probe_dynamically */ + + regs->asc_cmd = ASC_CMD_ENABLE_SEL; mb(); + + printf(".\n"); + + splx(s); + return 1; +} + +boolean_t +asc_probe_target(tgt, ior) + target_info_t *tgt; + io_req_t ior; +{ + asc_softc_t asc = asc_softc[tgt->masterno]; + boolean_t newlywed; + + newlywed = (tgt->cmd_ptr == 0); + if (newlywed) { + (*asc->dma_ops->new_target)(asc->dma_state, tgt); + } + + if (scsi_inquiry(tgt, SCSI_INQ_STD_DATA) == SCSI_RET_DEVICE_DOWN) + return FALSE; + + asc->ntargets++; + tgt->flags = TGT_ALIVE; + return TRUE; +} + +private asc_wait(regs, until, complain) + asc_padded_regmap_t *regs; +{ + int timeo = 1000000; + while ((regs->asc_csr & until) == 0) { + mb(); + delay(1); + if (!timeo--) { + if (complain) + printf("asc_wait TIMEO with x%x\n", get_reg(regs,asc_csr)); + break; + } + } + return get_reg(regs,asc_csr); +} + +asc_reset(asc, quick, special_dma) + asc_softc_t asc; +{ + char my_id; + int ccf; + asc_padded_regmap_t *regs; + + regs = asc->regs; + + /* preserve our ID for now */ + my_id = (regs->asc_cnfg1 & ASC_CNFG1_MY_BUS_ID); + + /* + * Reset chip and wait till done + */ + regs->asc_cmd = ASC_CMD_RESET; + wbflush(); delay(25); + + /* spec says this is needed after reset */ + regs->asc_cmd = ASC_CMD_NOP; + wbflush(); delay(25); + + /* + * Set up various chip parameters + */ + regs->asc_ccf = asc->ccf; + wbflush(); + delay(25); + regs->asc_sel_timo = asc->timeout; mb(); + /* restore our ID */ + regs->asc_cnfg1 = my_id | ASC_CNFG1_P_CHECK; mb(); + regs->asc_cnfg2 = ASC_CNFG2_SCSI2; + mb(); + regs->asc_cnfg3 = special_dma ? (ASC_CNFG3_T8|ASC_CNFG3_ALT_DMA) : 0; + mb(); + /* zero anything else */ + ASC_TC_PUT(regs, 0); mb(); + regs->asc_syn_p = asc_min_period; mb(); + regs->asc_syn_o = 0; mb(); /* asynch for now */ + + regs->asc_cmd = ASC_CMD_ENABLE_SEL; mb(); + + if (quick) return; + + /* + * reset the scsi bus, the interrupt routine does the rest + * or you can call asc_bus_reset(). + */ + regs->asc_cmd = ASC_CMD_BUS_RESET; mb(); +} + + +/* + * Operational functions + */ + +/* + * Start a SCSI command on a target + */ +asc_go(tgt, cmd_count, in_count, cmd_only) + target_info_t *tgt; + boolean_t cmd_only; +{ + asc_softc_t asc; + register spl_t s; + boolean_t disconn; + script_t scp; + boolean_t (*handler)(); + + LOG(1,"go"); + + asc = (asc_softc_t)tgt->hw_state; + + tgt->transient_state.cmd_count = cmd_count; /* keep it here */ + tgt->transient_state.out_count = 0; /* default */ + + (*asc->dma_ops->map)(asc->dma_state, tgt); + + disconn = BGET(scsi_might_disconnect,tgt->masterno,tgt->target_id); + disconn = disconn && (asc->ntargets > 1); + disconn |= BGET(scsi_should_disconnect,tgt->masterno,tgt->target_id); + + /* + * Setup target state + */ + tgt->done = SCSI_RET_IN_PROGRESS; + + handler = (disconn) ? asc_err_disconn : asc_err_generic; + + switch (tgt->cur_cmd) { + case SCSI_CMD_READ: + case SCSI_CMD_LONG_READ: + LOG(2,"readop"); + scp = asc_script_data_in; + break; + case SCSI_CMD_WRITE: + case SCSI_CMD_LONG_WRITE: + LOG(0x18,"writeop"); + scp = asc_script_data_out; + break; + case SCSI_CMD_INQUIRY: + /* This is likely the first thing out: + do the synch neg if so */ + if (!cmd_only && ((tgt->flags&TGT_DID_SYNCH)==0)) { + scp = asc_script_try_synch; + tgt->flags |= TGT_TRY_SYNCH; + disconn = FALSE; + break; + } + case SCSI_CMD_REQUEST_SENSE: + case SCSI_CMD_MODE_SENSE: + case SCSI_CMD_RECEIVE_DIAG_RESULTS: + case SCSI_CMD_READ_CAPACITY: + case SCSI_CMD_READ_BLOCK_LIMITS: + case SCSI_CMD_READ_TOC: + case SCSI_CMD_READ_SUBCH: + case SCSI_CMD_READ_HEADER: + case 0xc4: /* despised: SCSI_CMD_DEC_PLAYBACK_STATUS */ + case 0xdd: /* despised: SCSI_CMD_NEC_READ_SUBCH_Q */ + case 0xde: /* despised: SCSI_CMD_NEC_READ_TOC */ + scp = asc_script_data_in; + LOG(0x1c,"cmdop"); + LOG(0x80+tgt->cur_cmd,0); + break; + case SCSI_CMD_MODE_SELECT: + case SCSI_CMD_REASSIGN_BLOCKS: + case SCSI_CMD_FORMAT_UNIT: + case 0xc9: /* vendor-spec: SCSI_CMD_DEC_PLAYBACK_CONTROL */ + tgt->transient_state.cmd_count = sizeof_scsi_command(tgt->cur_cmd); + tgt->transient_state.out_count = + cmd_count - tgt->transient_state.cmd_count; + scp = asc_script_data_out; + LOG(0x1c,"cmdop"); + LOG(0x80+tgt->cur_cmd,0); + break; + case SCSI_CMD_TEST_UNIT_READY: + /* + * Do the synch negotiation here, unless prohibited + * or done already + */ + if (tgt->flags & TGT_DID_SYNCH) { + scp = asc_script_simple_cmd; + } else { + scp = asc_script_try_synch; + tgt->flags |= TGT_TRY_SYNCH; + cmd_only = FALSE; + disconn = FALSE; + } + LOG(0x1c,"cmdop"); + LOG(0x80+tgt->cur_cmd,0); + break; + default: + LOG(0x1c,"cmdop"); + LOG(0x80+tgt->cur_cmd,0); + scp = asc_script_simple_cmd; + } + + tgt->transient_state.script = scp; + tgt->transient_state.handler = handler; + tgt->transient_state.identify = (cmd_only) ? 0xff : + (disconn ? SCSI_IDENTIFY|SCSI_IFY_ENABLE_DISCONNECT : + SCSI_IDENTIFY); + + if (in_count) + tgt->transient_state.in_count = + (in_count < tgt->block_size) ? tgt->block_size : in_count; + else + tgt->transient_state.in_count = 0; + + /* + * See if another target is currently selected on + * this SCSI bus, e.g. lock the asc structure. + * Note that it is the strategy routine's job + * to serialize ops on the same target as appropriate. + * XXX here and everywhere, locks! + */ + /* + * Protection viz reconnections makes it tricky. + */ + s = splbio(); + + if (asc->wd.nactive++ == 0) + asc->wd.watchdog_state = SCSI_WD_ACTIVE; + + if (asc->state & ASC_STATE_BUSY) { + /* + * Queue up this target, note that this takes care + * of proper FIFO scheduling of the scsi-bus. + */ + LOG(3,"enqueue"); + enqueue_tail(&asc->waiting_targets, (queue_entry_t) tgt); + } else { + /* + * It is down to at most two contenders now, + * we will treat reconnections same as selections + * and let the scsi-bus arbitration process decide. + */ + asc->state |= ASC_STATE_BUSY; + asc->next_target = tgt; + asc_attempt_selection(asc); + /* + * Note that we might still lose arbitration.. + */ + } + splx(s); +} + +asc_attempt_selection(asc) + asc_softc_t asc; +{ + target_info_t *tgt; + asc_padded_regmap_t *regs; + register int out_count; + + regs = asc->regs; + tgt = asc->next_target; + + LOG(4,"select"); + LOG(0x80+tgt->target_id,0); + + /* + * We own the bus now.. unless we lose arbitration + */ + asc->active_target = tgt; + + /* Try to avoid reselect collisions */ + if ((regs->asc_csr & (ASC_CSR_INT|SCSI_PHASE_MSG_IN)) == + (ASC_CSR_INT|SCSI_PHASE_MSG_IN)) + return; + + /* + * Cleanup the FIFO + */ + regs->asc_cmd = ASC_CMD_FLUSH; + readback(regs->asc_cmd); + /* + * This value is not from spec, I have seen it failing + * without this delay and with logging disabled. That had + * about 42 extra instructions @25Mhz. + */ + delay(2);/* XXX time & move later */ + + + /* + * Init bus state variables + */ + asc->script = tgt->transient_state.script; + asc->error_handler = tgt->transient_state.handler; + asc->done = SCSI_RET_IN_PROGRESS; + + asc->out_count = 0; + asc->in_count = 0; + asc->extra_count = 0; + + /* + * Start the chip going + */ + out_count = (*asc->dma_ops->start_cmd)(asc->dma_state, tgt); + if (tgt->transient_state.identify != 0xff) { + regs->asc_fifo = tgt->transient_state.identify | tgt->lun; + mb(); + } + ASC_TC_PUT(regs, out_count); + readback(regs->asc_cmd); + + regs->asc_dbus_id = tgt->target_id; + readback(regs->asc_dbus_id); + + regs->asc_sel_timo = asc->timeout; + readback(regs->asc_sel_timo); + + regs->asc_syn_p = tgt->sync_period; + readback(regs->asc_syn_p); + + regs->asc_syn_o = tgt->sync_offset; + readback(regs->asc_syn_o); + + /* ugly little help for compiler */ +#define command out_count + if (tgt->flags & TGT_DID_SYNCH) { + command = (tgt->transient_state.identify == 0xff) ? + ASC_CMD_SEL | ASC_CMD_DMA : + ASC_CMD_SEL_ATN | ASC_CMD_DMA; /*preferred*/ + } else if (tgt->flags & TGT_TRY_SYNCH) + command = ASC_CMD_SEL_ATN_STOP; + else + command = ASC_CMD_SEL | ASC_CMD_DMA; + + /* Try to avoid reselect collisions */ + if ((regs->asc_csr & (ASC_CSR_INT|SCSI_PHASE_MSG_IN)) != + (ASC_CSR_INT|SCSI_PHASE_MSG_IN)) { + register int tmp; + + regs->asc_cmd = command; mb(); + /* + * Very nasty but infrequent problem here. We got/will get + * reconnected but the chip did not interrupt. The watchdog would + * fix it allright, but it stops the machine before it expires! + * Too bad we cannot just look at the interrupt register, sigh. + */ + tmp = get_reg(regs,asc_cmd); + if ((tmp != command) && (tmp == (ASC_CMD_NOP|ASC_CMD_DMA))) { + if ((regs->asc_csr & ASC_CSR_INT) == 0) { + delay(250); /* increase if in trouble */ + + if (get_reg(regs,asc_csr) == SCSI_PHASE_MSG_IN) { + /* ok, take the risk of reading the ir */ + tmp = get_reg(regs,asc_intr); mb(); + if (tmp & ASC_INT_RESEL) { + (void) asc_reconnect(asc, get_reg(regs,asc_csr), tmp); + asc_wait(regs, ASC_CSR_INT, 1); + tmp = get_reg(regs,asc_intr); mb(); + regs->asc_cmd = ASC_CMD_MSG_ACPT; + readback(regs->asc_cmd); + } else /* does not happen, but who knows.. */ + asc_reset(asc,FALSE,asc->state & ASC_STATE_SPEC_DMA); + } + } + } + } +#undef command +} + +/* + * Interrupt routine + * Take interrupts from the chip + * + * Implementation: + * Move along the current command's script if + * all is well, invoke error handler if not. + */ +asc_intr(unit, spllevel) + spl_t spllevel; +{ + register asc_softc_t asc; + register script_t scp; + register int ir, csr; + register asc_padded_regmap_t *regs; +#if defined(HAS_MAPPED_SCSI) + extern boolean_t rz_use_mapped_interface; + + if (rz_use_mapped_interface) + return ASC_intr(unit,spllevel); +#endif + + asc = asc_softc[unit]; + + LOG(5,"\n\tintr"); + /* collect ephemeral information */ + regs = asc->regs; + csr = get_reg(regs,asc_csr); mb(); + asc->ss_was = get_reg(regs,asc_ss); mb(); + asc->cmd_was = get_reg(regs,asc_cmd); mb(); + + /* drop spurious interrupts */ + if ((csr & ASC_CSR_INT) == 0) + return; + + ir = get_reg(regs,asc_intr); /* this re-latches CSR (and SSTEP) */ + mb(); + +TR(csr);TR(ir);TR(get_reg(regs,asc_cmd));TRCHECK + + /* this must be done within 250msec of disconnect */ + if (ir & ASC_INT_DISC) { + regs->asc_cmd = ASC_CMD_ENABLE_SEL; + readback(regs->asc_cmd); + } + + if (ir & ASC_INT_RESET) + return asc_bus_reset(asc); + + /* we got an interrupt allright */ + if (asc->active_target) + asc->wd.watchdog_state = SCSI_WD_ACTIVE; + +#if mips + splx(spllevel); /* drop priority */ +#endif + + if ((asc->state & ASC_STATE_TARGET) || + (ir & (ASC_INT_SEL|ASC_INT_SEL_ATN))) + return asc_target_intr(asc, csr, ir); + + /* + * In attempt_selection() we could not check the asc_intr + * register to see if a reselection was in progress [else + * we would cancel the interrupt, and it would not be safe + * anyways]. So we gave the select command even if sometimes + * the chip might have been reconnected already. What + * happens then is that we get an illegal command interrupt, + * which is why the second clause. Sorry, I'd do it better + * if I knew of a better way. + */ + if ((ir & ASC_INT_RESEL) || + ((ir & ASC_INT_ILL) && (regs->asc_cmd & ASC_CMD_SEL_ATN))) + return asc_reconnect(asc, csr, ir); + + /* + * Check for various errors + */ + if ((csr & (ASC_CSR_GE|ASC_CSR_PE)) || (ir & ASC_INT_ILL)) { + char *msg; +printf("{E%x,%x}", csr, ir); + if (csr & ASC_CSR_GE) + return;/* sit and prey? */ + + if (csr & ASC_CSR_PE) + msg = "SCSI bus parity error"; + if (ir & ASC_INT_ILL) + msg = "Chip sez Illegal Command"; + /* all we can do is to throw a reset on the bus */ + printf( "asc%d: %s%s", asc - asc_softc_data, msg, + ", attempting recovery.\n"); + asc_reset(asc, FALSE, asc->state & ASC_STATE_SPEC_DMA); + return; + } + + if ((scp = asc->script) == 0) /* sanity */ + return; + + LOG(6,"match"); + if (SCRIPT_MATCH(csr,ir,scp->condition)) { + /* + * Perform the appropriate operation, + * then proceed + */ + if ((*scp->action)(asc, csr, ir)) { + asc->script = scp + 1; + regs->asc_cmd = scp->command; mb(); + } + } else + (void) (*asc->error_handler)(asc, csr, ir); +} + +asc_target_intr(asc, csr, ir) + register asc_softc_t asc; + +{ + register script_t scp; + + LOG(0x1e,"tmode"); + + if ((asc->state & ASC_STATE_TARGET) == 0) { + + /* + * We just got selected + */ + asc->state |= ASC_STATE_TARGET; + + /* + * See if this selection collided with our selection attempt + */ + if (asc->state & ASC_STATE_BUSY) + asc->state |= ASC_STATE_COLLISION; + asc->state |= ASC_STATE_BUSY; + + return asc_selected(asc, csr, ir); + + } + /* We must be executing a script here */ + scp = asc->script; + assert(scp != 0); + + LOG(6,"match"); + if (SCRIPT_MATCH(csr,ir,scp->condition)) { + /* + * Perform the appropriate operation, + * then proceed + */ + if ((*scp->action)(asc, csr, ir)) { + asc->script = scp + 1; + asc->regs->asc_cmd = scp->command; mb(); + } + } else + (void) (*asc->error_handler)(asc, csr, ir); + +} + +/* + * All the many little things that the interrupt + * routine might switch to + */ +boolean_t +asc_clean_fifo(asc, csr, ir) + register asc_softc_t asc; + +{ + register asc_padded_regmap_t *regs = asc->regs; + register char ff; + + ASC_TC_GET(regs,asc->dmacnt_at_end); + + ASC_TC_PUT(regs,0); /* stop dma engine */ + readback(regs->asc_cmd); + + LOG(7,"clean_fifo"); + + while (fifo_count(regs)) { + ff = get_fifo(regs); + mb(); + } + return TRUE; +} + +boolean_t +asc_end(asc, csr, ir) + register asc_softc_t asc; +{ + register target_info_t *tgt; + register io_req_t ior; + + LOG(8,"end"); + + asc->state &= ~ASC_STATE_TARGET; + asc->regs->asc_syn_p = 0; mb(); + asc->regs->asc_syn_o = 0; mb(); + + tgt = asc->active_target; + if ((tgt->done = asc->done) == SCSI_RET_IN_PROGRESS) + tgt->done = SCSI_RET_SUCCESS; + + asc->script = 0; + + if (asc->wd.nactive-- == 1) + asc->wd.watchdog_state = SCSI_WD_INACTIVE; + + asc_release_bus(asc); + + if (ior = tgt->ior) { + /* + * WARNING: the above might have scheduled the + * DMA engine off to someone else. Keep it in + * mind in the following code + */ + (*asc->dma_ops->end_cmd)(asc->dma_state, tgt, ior); + + LOG(0xA,"ops->restart"); + (*tgt->dev_ops->restart)( tgt, TRUE); + } + + return FALSE; +} + +boolean_t +asc_release_bus(asc) + register asc_softc_t asc; +{ + boolean_t ret = TRUE; + + LOG(9,"release"); + if (asc->state & ASC_STATE_COLLISION) { + + LOG(0xB,"collided"); + asc->state &= ~ASC_STATE_COLLISION; + asc_attempt_selection(asc); + + } else if (queue_empty(&asc->waiting_targets)) { + + asc->state &= ~ASC_STATE_BUSY; + asc->active_target = 0; + asc->script = 0; + ret = FALSE; + + } else { + + LOG(0xC,"dequeue"); + asc->next_target = (target_info_t *) + dequeue_head(&asc->waiting_targets); + asc_attempt_selection(asc); + } + return ret; +} + +boolean_t +asc_get_status(asc, csr, ir) + register asc_softc_t asc; +{ + register asc_padded_regmap_t *regs = asc->regs; + register scsi2_status_byte_t status; + int len; + boolean_t ret; + io_req_t ior; + register target_info_t *tgt = asc->active_target; + + LOG(0xD,"get_status"); +TRWRAP + + asc->state &= ~ASC_STATE_DMA_IN; + + if (asc->state & ASC_STATE_DO_RFB) { + tgt->transient_state.has_oddb = FALSE; + regs->asc_cnfg2 = ASC_CNFG2_SCSI2; + } + + /* + * Get the last two bytes in FIFO + */ + while (fifo_count(regs) > 2) { + status.bits = get_fifo(regs); mb(); + } + + status.bits = get_fifo(regs); mb(); + + if (status.st.scsi_status_code != SCSI_ST_GOOD) { + scsi_error(asc->active_target, SCSI_ERR_STATUS, status.bits, 0); + asc->done = (status.st.scsi_status_code == SCSI_ST_BUSY) ? + SCSI_RET_RETRY : SCSI_RET_NEED_SENSE; + } else + asc->done = SCSI_RET_SUCCESS; + + status.bits = get_fifo(regs); /* just pop the command_complete */ + mb(); + + /* if reading, move the last piece of data in main memory */ + if (len = asc->in_count) { + register int count; + + count = asc->dmacnt_at_end; + if (count) { +#if 0 + this is incorrect and besides.. + tgt->ior->io_residual = count; +#endif + len -= count; + } + regs->asc_cmd = asc->script->command; + readback(regs->asc_cmd); + + ret = FALSE; + } else + ret = TRUE; + + asc->dmacnt_at_end = 0; + (*asc->dma_ops->end_xfer)(asc->dma_state, tgt, len); + if (!ret) + asc->script++; + return ret; +} + +boolean_t +asc_put_status(asc, csr, ir) + register asc_softc_t asc; +{ + register asc_padded_regmap_t *regs = asc->regs; + register scsi2_status_byte_t status; + register target_info_t *tgt = asc->active_target; + int len; + + LOG(0x21,"put_status"); + + asc->state &= ~ASC_STATE_DMA_IN; + + if (len = asc->in_count) { + register int count; + + ASC_TC_GET(regs,count); mb(); + if (count) + len -= count; + } + (*asc->dma_ops->end_xfer)(asc->dma_state, tgt, len); + +/* status.st.scsi_status_code = SCSI_ST_GOOD; */ + regs->asc_fifo = 0; mb(); + regs->asc_fifo = SCSI_COMMAND_COMPLETE; mb(); + + return TRUE; +} + + +boolean_t +asc_dma_in(asc, csr, ir) + register asc_softc_t asc; +{ + register target_info_t *tgt; + register asc_padded_regmap_t *regs = asc->regs; + register int count; + unsigned char ff = get_reg(regs,asc_flags); mb(); + + LOG(0xE,"dma_in"); + tgt = asc->active_target; + + /* + * This seems to be needed on certain rudimentary targets + * (such as the DEC TK50 tape) which apparently only pick + * up 6 initial bytes: when you add the initial IDENTIFY + * you are left with 1 pending byte, which is left in the + * FIFO and would otherwise show up atop the data we are + * really requesting. + * + * This is only speculation, though, based on the fact the + * sequence step value of 3 out of select means the target + * changed phase too quick and some bytes have not been + * xferred (see NCR manuals). Counter to this theory goes + * the fact that the extra byte that shows up is not alwyas + * zero, and appears to be pretty random. + * Note that asc_flags say there is one byte in the FIFO + * even in the ok case, but the sstep value is the right one. + * Note finally that this might all be a sync/async issue: + * I have only checked the ok case on synch disks so far. + * + * Indeed it seems to be an asynch issue: exabytes do it too. + */ + if ((tgt->sync_offset == 0) && ((ff & ASC_FLAGS_SEQ_STEP) != 0x80)) { + regs->asc_cmd = ASC_CMD_NOP; + wbflush(); + PRINT(("[tgt %d: %x while %d]", tgt->target_id, ff, tgt->cur_cmd)); + while ((ff & ASC_FLAGS_FIFO_CNT) != 0) { + ff = get_fifo(regs); mb(); + ff = get_reg(regs,asc_flags); mb(); + } + } + + asc->state |= ASC_STATE_DMA_IN; + + count = (*asc->dma_ops->start_datain)(asc->dma_state, tgt); + ASC_TC_PUT(regs, count); + readback(regs->asc_cmd); + + if ((asc->in_count = count) == tgt->transient_state.in_count) + return TRUE; + regs->asc_cmd = asc->script->command; mb(); + asc->script = asc_script_restart_data_in; + return FALSE; +} + +asc_dma_in_r(asc, csr, ir) + register asc_softc_t asc; +{ + register target_info_t *tgt; + register asc_padded_regmap_t *regs = asc->regs; + register int count; + boolean_t advance_script = TRUE; + + + LOG(0x1f,"dma_in_r"); + tgt = asc->active_target; + + asc->state |= ASC_STATE_DMA_IN; + + if (asc->in_count == 0) { + /* + * Got nothing yet, we just reconnected. + */ + register int avail; + + /* + * NOTE: if we have to handle the RFB (obb), + * the odd byte has been installed at reconnect + * time, before switching to data-in phase. Now + * we are already in data-in phase. + * It is up to the DMA engine to trim the dma_ptr + * down one byte. + */ + + count = (*asc->dma_ops->restart_datain_1) + (asc->dma_state, tgt); + + /* common case of 8k-or-less read ? */ + advance_script = (tgt->transient_state.in_count == count); + + } else { + + /* + * We received some data. + */ + register int offset, xferred; + + /* + * Problem: sometimes we get a 'spurious' interrupt + * right after a reconnect. It goes like disconnect, + * reconnect, dma_in_r, here but DMA is still rolling. + * Since there is no good reason we got here to begin with + * we just check for the case and dismiss it: we should + * get another interrupt when the TC goes to zero or the + * target disconnects. + */ + ASC_TC_GET(regs,xferred); mb(); + if (xferred != 0) + return FALSE; + + xferred = asc->in_count - xferred; + assert(xferred > 0); + + tgt->transient_state.in_count -= xferred; + assert(tgt->transient_state.in_count > 0); + + /* + * There should NOT be any obb issues here, + * we would have no control anyways. + */ + count = (*asc->dma_ops->restart_datain_2) + (asc->dma_state, tgt, xferred); + + asc->in_count = count; + ASC_TC_PUT(regs, count); + readback(regs->asc_cmd); + regs->asc_cmd = asc->script->command; mb(); + + (*asc->dma_ops->restart_datain_3) + (asc->dma_state, tgt); + + /* last chunk ? */ + if (count == tgt->transient_state.in_count) + asc->script++; + + return FALSE; + } + + asc->in_count = count; + ASC_TC_PUT(regs, count); + readback(regs->asc_cmd); + + if (!advance_script) { + regs->asc_cmd = asc->script->command; + readback(regs->asc_cmd); + } + return advance_script; +} + + +/* send data to target. Only called to start the xfer */ + +boolean_t +asc_dma_out(asc, csr, ir) + register asc_softc_t asc; +{ + register asc_padded_regmap_t *regs = asc->regs; + register int reload_count; + register target_info_t *tgt; + int command; + + LOG(0xF,"dma_out"); + + ASC_TC_GET(regs, reload_count); mb(); + asc->extra_count = fifo_count(regs); + reload_count += asc->extra_count; + ASC_TC_PUT(regs, reload_count); + asc->state &= ~ASC_STATE_DMA_IN; + readback(regs->asc_cmd); + + tgt = asc->active_target; + + command = asc->script->command; + + if (reload_count == 0) reload_count = ASC_TC_MAX; + asc->out_count = reload_count; + + if (reload_count >= tgt->transient_state.out_count) + asc->script++; + else + asc->script = asc_script_restart_data_out; + + if ((*asc->dma_ops->start_dataout) + (asc->dma_state, tgt, (volatile unsigned *)®s->asc_cmd, + command, &asc->extra_count)) { + regs->asc_cmd = command; + readback(regs->asc_cmd); + } + + return FALSE; +} + +/* send data to target. Called in two different ways: + (a) to restart a big transfer and + (b) after reconnection + */ +boolean_t +asc_dma_out_r(asc, csr, ir) + register asc_softc_t asc; +{ + register asc_padded_regmap_t *regs = asc->regs; + register target_info_t *tgt; + boolean_t advance_script = TRUE; + int count; + + + LOG(0x20,"dma_out_r"); + + tgt = asc->active_target; + asc->state &= ~ASC_STATE_DMA_IN; + + if (asc->out_count == 0) { + /* + * Nothing committed: we just got reconnected + */ + count = (*asc->dma_ops->restart_dataout_1) + (asc->dma_state, tgt); + + /* is this the last chunk ? */ + advance_script = (tgt->transient_state.out_count == count); + } else { + /* + * We sent some data. + */ + register int offset, xferred; + + ASC_TC_GET(regs,count); mb(); + + /* see comment above */ + if (count) { + return FALSE; + } + + count += fifo_count(regs); + count -= asc->extra_count; + xferred = asc->out_count - count; + assert(xferred > 0); + + tgt->transient_state.out_count -= xferred; + assert(tgt->transient_state.out_count > 0); + + count = (*asc->dma_ops->restart_dataout_2) + (asc->dma_state, tgt, xferred); + + /* last chunk ? */ + if (tgt->transient_state.out_count == count) + goto quickie; + + asc->out_count = count; + + asc->extra_count = (*asc->dma_ops->restart_dataout_3) + (asc->dma_state, tgt, + (volatile unsigned *)®s->asc_fifo); + ASC_TC_PUT(regs, count); + readback(regs->asc_cmd); + regs->asc_cmd = asc->script->command; mb(); + + (*asc->dma_ops->restart_dataout_4)(asc->dma_state, tgt); + + return FALSE; + } + +quickie: + asc->extra_count = (*asc->dma_ops->restart_dataout_3) + (asc->dma_state, tgt, + (volatile unsigned *)®s->asc_fifo); + + asc->out_count = count; + + ASC_TC_PUT(regs, count); + readback(regs->asc_cmd); + + if (!advance_script) { + regs->asc_cmd = asc->script->command; + } + return advance_script; +} + +boolean_t +asc_dosynch(asc, csr, ir) + register asc_softc_t asc; + register unsigned char csr, ir; +{ + register asc_padded_regmap_t *regs = asc->regs; + register unsigned char c; + int i, per, offs; + register target_info_t *tgt; + + /* + * Phase is MSG_OUT here. + * Try to go synchronous, unless prohibited + */ + tgt = asc->active_target; + regs->asc_cmd = ASC_CMD_FLUSH; + readback(regs->asc_cmd); + delay(1); + + per = asc_min_period; + if (BGET(scsi_no_synchronous_xfer,asc->sc->masterno,tgt->target_id)) + offs = 0; + else + offs = asc_max_offset; + + tgt->flags |= TGT_DID_SYNCH; /* only one chance */ + tgt->flags &= ~TGT_TRY_SYNCH; + + regs->asc_fifo = SCSI_EXTENDED_MESSAGE; mb(); + regs->asc_fifo = 3; mb(); + regs->asc_fifo = SCSI_SYNC_XFER_REQUEST; mb(); + regs->asc_fifo = asc_to_scsi_period(asc_min_period,asc->clk); mb(); + regs->asc_fifo = offs; mb(); + regs->asc_cmd = ASC_CMD_XFER_INFO; + readback(regs->asc_cmd); + csr = asc_wait(regs, ASC_CSR_INT, 1); + ir = get_reg(regs,asc_intr); mb(); + + /* some targets might be slow to move to msg-in */ + + if (SCSI_PHASE(csr) != SCSI_PHASE_MSG_IN) { + + /* wait for direction bit to flip */ + csr = asc_wait(regs, SCSI_IO, 0); + ir = get_reg(regs,asc_intr); mb(); + /* Some ugly targets go stright to command phase. + "You could at least say goodbye" */ + if (SCSI_PHASE(csr) == SCSI_PHASE_CMD) + goto did_not; + if (SCSI_PHASE(csr) != SCSI_PHASE_MSG_IN) + gimmeabreak(); + } + + regs->asc_cmd = ASC_CMD_XFER_INFO; mb(); + csr = asc_wait(regs, ASC_CSR_INT, 1); + ir = get_reg(regs,asc_intr); mb(); + + /* some targets do not even take all the bytes out */ + while (fifo_count(regs) > 0) { + c = get_fifo(regs); /* see what it says */ + mb(); + } + + if (c == SCSI_MESSAGE_REJECT) { +did_not: + printf(" did not like SYNCH xfer "); + + /* Tk50s get in trouble with ATN, sigh. */ + regs->asc_cmd = ASC_CMD_CLR_ATN; + readback(regs->asc_cmd); + + goto cmd; + } + + /* + * Receive the rest of the message + */ + regs->asc_cmd = ASC_CMD_MSG_ACPT; mb(); + asc_wait(regs, ASC_CSR_INT, 1); + ir = get_reg(regs,asc_intr); mb(); + + if (c != SCSI_EXTENDED_MESSAGE) + gimmeabreak(); + + regs->asc_cmd = ASC_CMD_XFER_INFO; mb(); + asc_wait(regs, ASC_CSR_INT, 1); + c = get_reg(regs,asc_intr); mb(); + if (get_fifo(regs) != 3) + panic("asc_dosynch"); + + for (i = 0; i < 3; i++) { + regs->asc_cmd = ASC_CMD_MSG_ACPT; mb(); + asc_wait(regs, ASC_CSR_INT, 1); + c = get_reg(regs,asc_intr); mb(); + + regs->asc_cmd = ASC_CMD_XFER_INFO; mb(); + asc_wait(regs, ASC_CSR_INT, 1); + c = get_reg(regs,asc_intr);/*ack*/ mb(); + c = get_fifo(regs); mb(); + + if (i == 1) tgt->sync_period = scsi_period_to_asc(c,asc->clk); + if (i == 2) tgt->sync_offset = c; + } + +cmd: + regs->asc_cmd = ASC_CMD_MSG_ACPT; mb(); + csr = asc_wait(regs, ASC_CSR_INT, 1); + c = get_reg(regs,asc_intr); mb(); + + /* Might have to wait a bit longer for slow targets */ + for (c = 0; SCSI_PHASE(get_reg(regs,asc_csr)) == SCSI_PHASE_MSG_IN; c++) { + mb(); + delay(2); + if (c & 0x80) break; /* waited too long */ + } + csr = get_reg(regs,asc_csr); mb(); + + /* phase should normally be command here */ + if (SCSI_PHASE(csr) == SCSI_PHASE_CMD) { + register char *cmd = tgt->cmd_ptr; + + /* test unit ready or inquiry */ + for (i = 0; i < sizeof(scsi_command_group_0); i++) { + regs->asc_fifo = *cmd++; mb(); + } + ASC_TC_PUT(regs,0xff); mb(); + regs->asc_cmd = ASC_CMD_XFER_PAD; /*0x18*/ mb(); + + if (tgt->cur_cmd == SCSI_CMD_INQUIRY) { + tgt->transient_state.script = asc_script_data_in; + asc->script = tgt->transient_state.script; + regs->asc_syn_p = tgt->sync_period; + regs->asc_syn_o = tgt->sync_offset; mb(); + return FALSE; + } + + csr = asc_wait(regs, ASC_CSR_INT, 1); + ir = get_reg(regs,asc_intr);/*ack*/ mb(); + } + + if (SCSI_PHASE(csr) != SCSI_PHASE_STATUS) + csr = asc_wait(regs, SCSI_IO, 1); /* direction flip */ + +status: + if (SCSI_PHASE(csr) != SCSI_PHASE_STATUS) + gimmeabreak(); + + return TRUE; +} + +/* The other side of the coin.. */ +asc_odsynch(asc, initiator) + register asc_softc_t asc; + target_info_t *initiator; +{ + register asc_padded_regmap_t *regs = asc->regs; + register unsigned char c; + int len, per, offs; + + /* + * Phase is MSG_OUT, we are the target and we have control. + * Any IDENTIFY messages have been handled already. + */ + initiator->flags |= TGT_DID_SYNCH; + initiator->flags &= ~TGT_TRY_SYNCH; + + /* + * We only understand synch negotiations + */ + c = get_fifo(regs); mb(); + if (c != SCSI_EXTENDED_MESSAGE) goto bad; + + /* + * This is not in the specs, but apparently the chip knows + * enough about scsi to receive the length automatically. + * So there were two bytes in the fifo at function call. + */ + len = get_fifo(regs); mb(); + if (len != 3) goto bad; + while (len) { + if (fifo_count(regs) == 0) { + regs->asc_cmd = ASC_CMD_RCV_MSG; + readback(regs->asc_cmd); + asc_wait(regs, ASC_CSR_INT, 1); + c = get_reg(regs,asc_intr); mb(); + } + c = get_fifo(regs); mb(); + if (len == 1) offs = c; + if (len == 2) per = c; + len--; + } + + /* + * Adjust the proposed parameters + */ + c = scsi_period_to_asc(per,asc->clk); + initiator->sync_period = c; + per = asc_to_scsi_period(c,asc->clk); + + if (offs > asc_max_offset) offs = asc_max_offset; + initiator->sync_offset = offs; + + /* + * Tell him what the deal is + */ + regs->asc_fifo = SCSI_EXTENDED_MESSAGE; mb(); + regs->asc_fifo = 3; mb(); + regs->asc_fifo = SCSI_SYNC_XFER_REQUEST; mb(); + regs->asc_fifo = per; mb(); + regs->asc_fifo = offs; mb(); + regs->asc_cmd = ASC_CMD_SND_MSG; + readback(regs->asc_cmd); + asc_wait(regs, ASC_CSR_INT, 1); + c = get_reg(regs,asc_intr); mb(); + + /* + * Exit conditions: fifo empty, phase undefined but non-command + */ + return; + + /* + * Something wrong, reject the message + */ +bad: + while (fifo_count(regs)) { + c = get_fifo(regs); mb(); + } + regs->asc_fifo = SCSI_MESSAGE_REJECT; mb(); + regs->asc_cmd = ASC_CMD_SND_MSG; + readback(regs->asc_cmd); + asc_wait(regs, ASC_CSR_INT, 1); + c = get_reg(regs,asc_intr); mb(); +} + +/* + * The bus was reset + */ +asc_bus_reset(asc) + register asc_softc_t asc; +{ + register asc_padded_regmap_t *regs = asc->regs; + + LOG(0x1d,"bus_reset"); + + /* + * Clear bus descriptor + */ + asc->script = 0; + asc->error_handler = 0; + asc->active_target = 0; + asc->next_target = 0; + asc->state &= ASC_STATE_SPEC_DMA | ASC_STATE_DO_RFB; + queue_init(&asc->waiting_targets); + asc->wd.nactive = 0; + asc_reset(asc, TRUE, asc->state & ASC_STATE_SPEC_DMA); + + printf("asc: (%d) bus reset ", ++asc->wd.reset_count); + + /* some targets take long to reset */ + delay( scsi_delay_after_reset + + asc->sc->initiator_id * 500000); /* if multiple initiators */ + + if (asc->sc == 0) /* sanity */ + return; + + scsi_bus_was_reset(asc->sc); +} + +/* + * Disconnect/reconnect mode ops + */ + +/* get the message in via dma */ +boolean_t +asc_msg_in(asc, csr, ir) + register asc_softc_t asc; + register unsigned char csr, ir; +{ + register target_info_t *tgt; + register asc_padded_regmap_t *regs = asc->regs; + unsigned char ff; + + LOG(0x10,"msg_in"); + /* must clean FIFO as in asc_dma_in, sigh */ + while (fifo_count(regs) != 0) { + ff = get_fifo(regs); mb(); + } + + (void) (*asc->dma_ops->start_msgin)(asc->dma_state, asc->active_target); + /* We only really expect two bytes, at tgt->cmd_ptr */ + ASC_TC_PUT(regs, sizeof(scsi_command_group_0)); + readback(regs->asc_cmd); + + return TRUE; +} + +/* check the message is indeed a DISCONNECT */ +boolean_t +asc_disconnect(asc, csr, ir) + register asc_softc_t asc; + register unsigned char csr, ir; + +{ + register char *msgs, ff; + register target_info_t *tgt; + asc_padded_regmap_t *regs; + + tgt = asc->active_target; + + (*asc->dma_ops->end_msgin)(asc->dma_state, tgt); + + /* + * Do not do this. It is most likely a reconnection + * message that sits there already by the time we + * get here. The chip is smart enough to only dma + * the bytes that correctly came in as msg_in proper, + * the identify and selection bytes are not dma-ed. + * Yes, sometimes the hardware does the right thing. + */ +#if 0 + /* First check message got out of the fifo */ + regs = asc->regs; + while (fifo_count(regs) != 0) { + *msgs++ = get_fifo(regs); + } +#endif + msgs = tgt->cmd_ptr; + + /* A SDP message preceeds it in non-completed READs */ + if ((msgs[0] == SCSI_DISCONNECT) || /* completed */ + ((msgs[0] == SCSI_SAVE_DATA_POINTER) && /* non complete */ + (msgs[1] == SCSI_DISCONNECT))) { + /* that's the ok case */ + } else + printf("[tgt %d bad SDP: x%x]", + tgt->target_id, *((unsigned short *)msgs)); + + return TRUE; +} + +/* save all relevant data, free the BUS */ +boolean_t +asc_disconnected(asc, csr, ir) + register asc_softc_t asc; + register unsigned char csr, ir; + +{ + register target_info_t *tgt; + + LOG(0x11,"disconnected"); + asc_disconnect(asc,csr,ir); + + tgt = asc->active_target; + tgt->flags |= TGT_DISCONNECTED; + tgt->transient_state.handler = asc->error_handler; + /* anything else was saved in asc_err_disconn() */ + + PRINT(("{D%d}", tgt->target_id)); + + asc_release_bus(asc); + + return FALSE; +} + +int asc_restore_ptr = 1; + +/* get reconnect message out of fifo, restore BUS */ +boolean_t +asc_reconnect(asc, csr, ir) + register asc_softc_t asc; + register unsigned char csr, ir; + +{ + register target_info_t *tgt; + asc_padded_regmap_t *regs; + unsigned int id; + + LOG(0x12,"reconnect"); + /* + * See if this reconnection collided with a selection attempt + */ + if (asc->state & ASC_STATE_BUSY) + asc->state |= ASC_STATE_COLLISION; + + asc->state |= ASC_STATE_BUSY; + + /* find tgt: first byte in fifo is (tgt_id|our_id) */ + regs = asc->regs; + while (fifo_count(regs) > 2) /* sanity */ { + id = get_fifo(regs); mb(); + } + if (fifo_count(regs) != 2) + gimmeabreak(); + + id = get_fifo(regs); /* must decode this now */ + mb(); + id &= ~(1 << asc->sc->initiator_id); + { + register int i; + for (i = 0; id > 1; i++) + id >>= 1; + id = i; + } + + tgt = asc->sc->target[id]; + if (tgt == 0) panic("asc_reconnect"); + + /* synch things*/ + regs->asc_syn_p = tgt->sync_period; + regs->asc_syn_o = tgt->sync_offset; + readback(regs->asc_syn_o); + + /* Get IDENTIFY message */ + { + register int i = get_fifo(regs); + if ((i & SCSI_IDENTIFY) == 0) + gimmeabreak(); + i &= 0x7; + /* If not LUN 0 see which target it is */ + if (i) { + target_info_t *tgt1; + for (tgt1 = tgt->next_lun; + tgt1 && tgt1 != tgt; + tgt1 = tgt1->next_lun) + if (tgt1->lun == i) { + tgt = tgt1; + break; + } + } + } + + if (asc->state & ASC_STATE_DO_RFB) { + if (tgt->transient_state.has_oddb) { + if (tgt->sync_period) { + regs->asc_cnfg2 = ASC_CNFG2_SCSI2 | ASC_CNFG2_RFB; + wbflush(); + regs->asc_rfb = tgt->transient_state.the_oddb; + } else { + regs->asc_fifo = tgt->transient_state.the_oddb; + } + tgt->transient_state.has_oddb = FALSE; + } else { + regs->asc_cnfg2 = ASC_CNFG2_SCSI2; + } + wbflush(); + } + + PRINT(("{R%d}", id)); + if (asc->state & ASC_STATE_COLLISION) + PRINT(("[B %d-%d]", asc->active_target->target_id, id)); + + LOG(0x80+id,0); + + asc->active_target = tgt; + tgt->flags &= ~TGT_DISCONNECTED; + + asc->script = tgt->transient_state.script; + asc->error_handler = tgt->transient_state.handler; + asc->in_count = 0; + asc->out_count = 0; + + regs->asc_cmd = ASC_CMD_MSG_ACPT; + readback(regs->asc_cmd); + + /* What if there is a RESTORE_PTR msgin ? */ + if (asc_restore_ptr) { +more_msgin: + csr = asc_wait(regs, ASC_CSR_INT, 1); + + if (SCSI_PHASE(csr) == SCSI_PHASE_MSG_IN) { + /* ack intr */ + id = get_reg(regs,asc_intr); mb(); + + /* Ok, get msg */ + regs->asc_cmd = ASC_CMD_XFER_INFO; + readback(regs->asc_cmd); + /* wait for xfer done */ + csr = asc_wait(regs, ASC_CSR_INT, 1); + + /* look at what we got */ + id = get_fifo(regs); + + if (id != SCSI_RESTORE_POINTERS) + printf("asc%d: uhu msg %x\n", asc->sc->masterno, id); + /* ack intr */ + id = get_reg(regs,asc_intr); mb(); + + /* move on */ + regs->asc_cmd = ASC_CMD_MSG_ACPT; + readback(regs->asc_cmd); + goto more_msgin; + } + } + + return FALSE; +} + + +/* We have been selected as target */ + +boolean_t +asc_selected(asc, csr, ir) + register asc_softc_t asc; + register unsigned csr, ir; +{ + register asc_padded_regmap_t *regs = asc->regs; + register unsigned char id; + target_info_t *self, *initiator; + unsigned int len; + + /* + * Find initiator's id: the head of the fifo is (init_id|our_id) + */ + + id = get_fifo(regs); /* must decode this now */ + mb(); + id &= ~(1 << asc->sc->initiator_id); + { + register int i; + for (i = 0; id > 1; i++) + id >>= 1; + id = i; + } + + /* + * See if we have seen him before + */ + self = asc->sc->target[asc->sc->initiator_id]; + initiator = asc->sc->target[id]; + if (initiator == 0) { + + initiator = scsi_slave_alloc(asc->sc->masterno, id, asc); + (*asc->dma_ops->new_target)(asc->dma_state, initiator); + + } + + if (! (initiator->flags & TGT_ONLINE) ) + sccpu_new_initiator(self, initiator); + + /* + * If selected with ATN the chip did the msg-out + * phase already, let us look at the message(s) + */ + if (ir & ASC_INT_SEL_ATN) { + register unsigned char m; + + m = get_fifo(regs); mb(); + if ((m & SCSI_IDENTIFY) == 0) + gimmeabreak(); + + csr = get_reg(regs,asc_csr); mb(); + if ((SCSI_PHASE(csr) == SCSI_PHASE_MSG_OUT) && + fifo_count(regs)) + asc_odsynch(asc, initiator); + + /* Get the command now, unless we have it already */ + mb(); + if (fifo_count(regs) < sizeof(scsi_command_group_0)) { + regs->asc_cmd = ASC_CMD_RCV_CMD; + readback(regs->asc_cmd); + asc_wait(regs, ASC_CSR_INT, 1); + ir = get_reg(regs,asc_intr); mb(); + csr = get_reg(regs,asc_csr); mb(); + } + } else { + /* + * Pop away the null byte that follows the id + */ + if (get_fifo(regs) != 0) + gimmeabreak(); + + } + + /* + * Take rest of command out of fifo + */ + self->dev_info.cpu.req_pending = TRUE; + self->dev_info.cpu.req_id = id; + self->dev_info.cpu.req_cmd = get_fifo(regs); + self->dev_info.cpu.req_lun = get_fifo(regs); + + LOG(0x80+self->dev_info.cpu.req_cmd, 0); + + switch ((self->dev_info.cpu.req_cmd & SCSI_CODE_GROUP) >> 5) { + case 0: + len = get_fifo(regs) << 16; mb(); + len |= get_fifo(regs) << 8; mb(); + len |= get_fifo(regs); mb(); + break; + case 1: + case 2: + len = get_fifo(regs); /* xxx lba1 */ mb(); + len = get_fifo(regs); /* xxx lba2 */ mb(); + len = get_fifo(regs); /* xxx lba3 */ mb(); + len = get_fifo(regs); /* xxx lba4 */ mb(); + len = get_fifo(regs); /* xxx xxx */ mb(); + len = get_fifo(regs) << 8; mb(); + len |= get_fifo(regs); mb(); + break; + case 5: + len = get_fifo(regs); /* xxx lba1 */ mb(); + len = get_fifo(regs); /* xxx lba2 */ mb(); + len = get_fifo(regs); /* xxx lba3 */ mb(); + len = get_fifo(regs); /* xxx lba4 */ mb(); + len = get_fifo(regs) << 24; mb(); + len |= get_fifo(regs) << 16; mb(); + len |= get_fifo(regs) << 8; mb(); + len |= get_fifo(regs); mb(); + if (get_fifo(regs) != 0) ; + break; + default: + panic("asc_selected"); + } + self->dev_info.cpu.req_len = len; +/*if (scsi_debug) printf("[L x%x]", len);*/ + + /* xxx pop the cntrl byte away */ + if (get_fifo(regs) != 0) + gimmeabreak(); + mb(); + + /* + * Setup state + */ + asc->active_target = self; + asc->done = SCSI_RET_IN_PROGRESS; + asc->out_count = 0; + asc->in_count = 0; + asc->extra_count = 0; + + /* + * Sync params. + */ + regs->asc_syn_p = initiator->sync_period; + regs->asc_syn_o = initiator->sync_offset; + readback(regs->asc_syn_o); + + /* + * Do the up-call + */ + LOG(0xB,"tgt-mode-restart"); + (*self->dev_ops->restart)( self, FALSE); + + /* The call above has either prepared the data, + placing an ior on self, or it handled it some + other way */ + if (self->ior == 0) + return FALSE; + + /* sanity */ + if (fifo_count(regs)) { + regs->asc_cmd = ASC_CMD_FLUSH; + readback(regs->asc_cmd); + } + + /* needed by some dma code to align things properly */ + self->transient_state.cmd_count = sizeof(scsi_command_group_0); + self->transient_state.in_count = len; /* XXX */ + + (*asc->dma_ops->map)(asc->dma_state, self); + + if (asc->wd.nactive++ == 0) + asc->wd.watchdog_state = SCSI_WD_ACTIVE; + + + { + register script_t scp; + unsigned char command; + + switch (self->dev_info.cpu.req_cmd) { + case SCSI_CMD_TEST_UNIT_READY: + scp = asc_script_t_data_in+1; break; + case SCSI_CMD_SEND: + scp = asc_script_t_data_in; break; + default: + scp = asc_script_t_data_out; break; + } + + asc->script = scp; + command = scp->command; + if (!(*scp->action)(asc, csr, ir)) + return FALSE; + +/*if (scsi_debug) printf("[F%x]", fifo_count(regs));*/ + + asc->script++; + regs->asc_cmd = command; mb(); + + } + + return FALSE; +} + + +/* + * Other utilities + */ +private void +pick_up_oddb( + target_info_t *tgt) +{ + register char *lastp; + + /* State should have been updated before we get here */ + lastp = tgt->dma_ptr + tgt->transient_state.dma_offset; + + if ((vm_offset_t) lastp & 1) { + tgt->transient_state.has_oddb = TRUE; + tgt->transient_state.the_oddb = lastp[-1]; + } else + tgt->transient_state.has_oddb = FALSE; +} + + +/* + * Error handlers + */ + +/* + * Fall-back error handler. + */ +asc_err_generic(asc, csr, ir) + register asc_softc_t asc; +{ + LOG(0x13,"err_generic"); + + /* handle non-existant or powered off devices here */ + if ((ir == ASC_INT_DISC) && + (asc_isa_select(asc->cmd_was)) && + (ASC_SS(asc->ss_was) == 0)) { + /* Powered off ? */ + target_info_t *tgt = asc->active_target; + + tgt->flags = 0; + tgt->sync_period = 0; + tgt->sync_offset = 0; mb(); + asc->done = SCSI_RET_DEVICE_DOWN; + asc_end(asc, csr, ir); + return; + } + + switch (SCSI_PHASE(csr)) { + case SCSI_PHASE_STATUS: + if (asc->script[-1].condition == SCSI_PHASE_STATUS) { + /* some are just slow to get out.. */ + } else + asc_err_to_status(asc, csr, ir); + return; + break; + case SCSI_PHASE_DATAI: + if (asc->script->condition == SCSI_PHASE_STATUS) { + /* + * Here is what I know about it. We reconnect to + * the target (csr 87, ir 0C, cmd 44), start dma in + * (81 10 12) and then get here with (81 10 90). + * Dma is rolling and keeps on rolling happily, + * the value in the counter indicates the interrupt + * was signalled right away: by the time we get + * here only 80-90 bytes have been shipped to an + * rz56 running synchronous at 3.57 Mb/sec. + */ +/* printf("{P}");*/ + return; + } + break; + case SCSI_PHASE_DATAO: + if (asc->script->condition == SCSI_PHASE_STATUS) { + /* + * See comment above. Actually seen on hitachis. + */ +/* printf("{P}");*/ + return; + } + break; + case SCSI_PHASE_CMD: + /* This can be the case with drives that are not + even scsi-1 compliant and do not like to be + selected with ATN (to do the synch negot) and + go stright to command phase, regardless */ + + if (asc->script == asc_script_try_synch) { + + target_info_t *tgt = asc->active_target; + register asc_padded_regmap_t *regs = asc->regs; + + tgt->flags |= TGT_DID_SYNCH; /* only one chance */ + tgt->flags &= ~TGT_TRY_SYNCH; + + if (tgt->cur_cmd == SCSI_CMD_INQUIRY) + tgt->transient_state.script = asc_script_data_in; + else + tgt->transient_state.script = asc_script_simple_cmd; + asc->script = tgt->transient_state.script; + regs->asc_cmd = ASC_CMD_CLR_ATN; + readback(regs->asc_cmd); + asc->regs->asc_cmd = ASC_CMD_XFER_PAD|ASC_CMD_DMA; /*0x98*/ mb(); + printf(" did not like SYNCH xfer "); + return; + } + /* fall through */ + } + gimmeabreak(); +} + +/* + * Handle generic errors that are reported as + * an unexpected change to STATUS phase + */ +asc_err_to_status(asc, csr, ir) + register asc_softc_t asc; +{ + script_t scp = asc->script; + + LOG(0x14,"err_tostatus"); + while (scp->condition != SCSI_PHASE_STATUS) + scp++; + (*scp->action)(asc, csr, ir); + asc->script = scp + 1; + asc->regs->asc_cmd = scp->command; mb(); +#if 0 + /* + * Normally, we would already be able to say the command + * is in error, e.g. the tape had a filemark or something. + * But in case we do disconnected mode WRITEs, it is quite + * common that the following happens: + * dma_out -> disconnect (xfer complete) -> reconnect + * and our script might expect at this point that the dma + * had to be restarted (it didn't notice it was completed). + * And in any event.. it is both correct and cleaner to + * declare error iff the STATUS byte says so. + */ + asc->done = SCSI_RET_NEED_SENSE; +#endif +} + +/* + * Handle disconnections as exceptions + */ +asc_err_disconn(asc, csr, ir) + register asc_softc_t asc; + register unsigned char csr, ir; +{ + register asc_padded_regmap_t *regs; + register target_info_t *tgt; + int count; + boolean_t callback = FALSE; + + LOG(0x16,"err_disconn"); + + /* + * We only do msg-in cases here + */ + if (SCSI_PHASE(csr) != SCSI_PHASE_MSG_IN) + return asc_err_generic(asc, csr, ir); + + regs = asc->regs; + tgt = asc->active_target; + + /* + * What did we expect to happen, and what did happen. + */ + switch (asc->script->condition) { + case SCSI_PHASE_DATAO: + /* + * A data out phase was either about to be started, + * or it was in progress but more had to go out later + * [e.g. a big xfer for instance, or more than the + * DMA engine can take in one shot]. + */ + LOG(0x1b,"+DATAO"); + if (asc->out_count) { + register int xferred, offset; + + /* + * Xfer in progress. See where we stopped. + */ + ASC_TC_GET(regs,xferred); /* temporary misnomer */ + /* + * Account for prefetching, in its various forms + */ + xferred += fifo_count(regs); + xferred -= asc->extra_count; + /* + * See how much went out, how much to go. + */ + xferred = asc->out_count - xferred; /* ok now */ + tgt->transient_state.out_count -= xferred; + assert(tgt->transient_state.out_count > 0); + + callback = (*asc->dma_ops->disconn_1) + (asc->dma_state, tgt, xferred); + + } else { + + /* + * A disconnection before DMA was (re)started. + */ + callback = (*asc->dma_ops->disconn_2) + (asc->dma_state, tgt); + + } + asc->extra_count = 0; + tgt->transient_state.script = asc_script_restart_data_out; + break; + + + case SCSI_PHASE_DATAI: + /* + * Same as above, the other way around + */ + LOG(0x17,"+DATAI"); + if (asc->in_count) { + register int offset, xferred; + + /* + * How much did we expect, how much did we get + */ + ASC_TC_GET(regs,count); mb(); + xferred = asc->in_count - count; + assert(xferred > 0); + +if (regs->asc_flags & 0xf) +printf("{Xf %x,%x,%x}", xferred, asc->in_count, fifo_count(regs)); + tgt->transient_state.in_count -= xferred; + assert(tgt->transient_state.in_count > 0); + + callback = (*asc->dma_ops->disconn_3) + (asc->dma_state, tgt, xferred); + + /* + * Handle obb if we have to. DMA code has + * updated pointers and flushed buffers. + */ + if (asc->state & ASC_STATE_DO_RFB) + pick_up_oddb(tgt); + + tgt->transient_state.script = asc_script_restart_data_in; + /* + * Some silly targets disconnect after they + * have given us all the data. + */ + if (tgt->transient_state.in_count == 0) + tgt->transient_state.script++; + + } else + tgt->transient_state.script = asc_script_restart_data_in; + break; + + case SCSI_PHASE_STATUS: + /* + * Either one of the above cases here. Only diff + * the DMA engine was setup to run to completion + * and (most likely) did not. + */ + ASC_TC_GET(regs,count); mb(); + if (asc->state & ASC_STATE_DMA_IN) { + register int offset, xferred; + + LOG(0x1a,"+STATUS+R"); + + + /* + * Handle brain-dead sequence: + * 1-xfer all data, disconnect + * 2-reconnect, disconnect immediately ?? + * 3-rept 2 + * 4-reconnect,complete + */ + if (asc->in_count) { + + xferred = asc->in_count - count; + assert(xferred > 0); +if (regs->asc_flags & 0xf) +printf("{Xf %x,%x,%x}", xferred, asc->in_count, fifo_count(regs)); + + tgt->transient_state.in_count -= xferred; + + callback = (*asc->dma_ops->disconn_4) + (asc->dma_state, tgt, xferred); + } + /* + * Handle obb if we have to. DMA code has + * updated pointers and flushed buffers. + */ + if (asc->state & ASC_STATE_DO_RFB) + pick_up_oddb(tgt); + + tgt->transient_state.script = asc_script_restart_data_in; + + /* see previous note */ + if (tgt->transient_state.in_count == 0) + tgt->transient_state.script++; + + } else { + + /* + * Outgoing xfer, take care of prefetching. + */ + /* add what's left in the fifo */ + count += fifo_count(regs); + /* take back the extra we might have added */ + count -= asc->extra_count; + /* ..and drop that idea */ + asc->extra_count = 0; + + LOG(0x19,"+STATUS+W"); + + /* + * All done ? This is less silly than with + * READs: some disks will only say "done" when + * the data is down on the platter, and some + * people like it much better that way. + */ + if ((count == 0) && (tgt->transient_state.out_count == asc->out_count)) { + /* all done */ + tgt->transient_state.script = asc->script; + tgt->transient_state.out_count = 0; + } else { + register int xferred, offset; + + /* how much we xferred */ + xferred = asc->out_count - count; + + /* how much to go */ + tgt->transient_state.out_count -= xferred; + assert(tgt->transient_state.out_count > 0); + + callback = (*asc->dma_ops->disconn_5) + (asc->dma_state,tgt,xferred); + + tgt->transient_state.script = asc_script_restart_data_out; + } + asc->out_count = 0; + } + break; + default: + gimmeabreak(); + return; + } + asc_msg_in(asc,csr,ir); + asc->script = asc_script_disconnect; + regs->asc_cmd = ASC_CMD_XFER_INFO|ASC_CMD_DMA; + wbflush(); + /* + * Prevent a race, now. If the reselection comes quickly + * the chip will prefetch and reload the transfer counter + * register. Make sure it will stop, by reloading a zero. + */ + regs->asc_tc_lsb = 0; + regs->asc_tc_msb = 0; + if (callback) + (*asc->dma_ops->disconn_callback)(asc->dma_state, tgt); +} + +/* + * Watchdog + * + * So far I have only seen the chip get stuck in a + * select/reselect conflict: the reselection did + * win and the interrupt register showed it but.. + * no interrupt was generated. + * But we know that some (name withdrawn) disks get + * stuck in the middle of dma phases... + */ +asc_reset_scsibus(asc) + register asc_softc_t asc; +{ + register target_info_t *tgt = asc->active_target; + register asc_padded_regmap_t *regs = asc->regs; + register int ir; + + if (scsi_debug && tgt) { + int dmalen; + ASC_TC_GET(asc->regs,dmalen); mb(); + printf("Target %d was active, cmd x%x in x%x out x%x Sin x%x Sou x%x dmalen x%x\n", + tgt->target_id, tgt->cur_cmd, + tgt->transient_state.in_count, tgt->transient_state.out_count, + asc->in_count, asc->out_count, + dmalen); + } + ir = get_reg(regs,asc_intr); mb(); + if ((ir & ASC_INT_RESEL) && (SCSI_PHASE(regs->asc_csr) == SCSI_PHASE_MSG_IN)) { + /* getting it out of the woods is a bit tricky */ + spl_t s = splbio(); + + (void) asc_reconnect(asc, get_reg(regs,asc_csr), ir); + asc_wait(regs, ASC_CSR_INT, 1); + ir = get_reg(regs,asc_intr); mb(); + regs->asc_cmd = ASC_CMD_MSG_ACPT; + readback(regs->asc_cmd); + splx(s); + } else { + regs->asc_cmd = ASC_CMD_BUS_RESET; mb(); + delay(35); + } +} + +#endif NASC > 0 + diff --git a/scsi/adapters/scsi_7061.h b/scsi/adapters/scsi_7061.h new file mode 100644 index 0000000..8969f8b --- /dev/null +++ b/scsi/adapters/scsi_7061.h @@ -0,0 +1,230 @@ +/* + * Mach Operating System + * Copyright (c) 1991,1990,1989 Carnegie Mellon University + * All Rights Reserved. + * + * Permission to use, copy, modify and distribute this software and its + * documentation is hereby granted, provided that both the copyright + * notice and this permission notice appear in all copies of the + * software, derivative works or modified versions, and any portions + * thereof, and that both notices appear in supporting documentation. + * + * CARNEGIE MELLON ALLOWS FREE USE OF THIS SOFTWARE IN ITS "AS IS" + * CONDITION. CARNEGIE MELLON DISCLAIMS ANY LIABILITY OF ANY KIND FOR + * ANY DAMAGES WHATSOEVER RESULTING FROM THE USE OF THIS SOFTWARE. + * + * Carnegie Mellon requests users of this software to return to + * + * Software Distribution Coordinator or Software.Distribution@CS.CMU.EDU + * School of Computer Science + * Carnegie Mellon University + * Pittsburgh PA 15213-3890 + * + * any improvements or extensions that they make and grant Carnegie Mellon + * the rights to redistribute these changes. + */ +/* + * File: scsi_7061.h + * Author: Alessandro Forin, Carnegie Mellon University + * Date: 9/90 + * + * Defines for the DEC DC7061 SII gate array (SCSI interface) + */ + +/* + * Register map + */ + +typedef struct { + volatile unsigned short sii_sdb; /* rw: Data bus and parity */ + volatile unsigned short sii_sc1; /* rw: scsi signals 1 */ + volatile unsigned short sii_sc2; /* rw: scsi signals 2 */ + volatile unsigned short sii_csr; /* rw: control and status */ + volatile unsigned short sii_id; /* rw: scsi bus ID */ + volatile unsigned short sii_sel_csr; /* rw: selection status */ + volatile unsigned short sii_destat; /* ro: selection detector status */ + volatile unsigned short sii_dstmo; /* unsupp: dssi timeout */ + volatile unsigned short sii_data; /* rw: data register */ + volatile unsigned short sii_dma_ctrl; /* rw: dma control reg */ + volatile unsigned short sii_dma_len; /* rw: length of transfer */ + volatile unsigned short sii_dma_adr_low;/* rw: low address */ + volatile unsigned short sii_dma_adr_hi; /* rw: high address */ + volatile unsigned short sii_dma_1st_byte;/* rw: initial byte */ + volatile unsigned short sii_stlp; /* unsupp: dssi short trgt list ptr */ + volatile unsigned short sii_ltlp; /* unsupp: dssi long " " " */ + volatile unsigned short sii_ilp; /* unsupp: dssi initiator list ptr */ + volatile unsigned short sii_dssi_csr; /* unsupp: dssi control */ + volatile unsigned short sii_conn_csr; /* rc: connection interrupt control */ + volatile unsigned short sii_data_csr; /* rc: data interrupt control */ + volatile unsigned short sii_cmd; /* rw: command register */ + volatile unsigned short sii_diag_csr; /* rw: disgnostic status */ +} sii_regmap_t; + +/* + * Data bus register (diag) + */ + +#define SII_SDB_DATA 0x00ff /* data bits, assert high */ +#define SII_SDB_PARITY 0x0100 /* parity bit */ + +/* + * Control signals one (diag) + */ + +#define SII_CS1_IO 0x0001 /* I/O bit */ +#define SII_CS1_CD 0x0002 /* Control/Data bit */ +#define SII_CS1_MSG 0x0004 /* Message bit */ +#define SII_CS1_ATN 0x0008 /* Attention bit */ +#define SII_CS1_REQ 0x0010 /* Request bit */ +#define SII_CS1_ACK 0x0020 /* Acknowledge bit */ +#define SII_CS1_RST 0x0040 /* Reset bit */ +#define SII_CS1_SEL 0x0080 /* Selection bit */ +#define SII_CS1_BSY 0x0100 /* Busy bit */ + +/* + * Control signals two (diag) + */ + +#define SII_CS2_SBE 0x0001 /* Bus enable */ +#define SII_CS2_ARB 0x0002 /* arbitration enable */ +#define SII_CS2_TGS 0x0004 /* Target role steer */ +#define SII_CS2_IGS 0x0008 /* Initiator role steer */ + +/* + * Control and status register + */ + +#define SII_CSR_IE 0x0001 /* Interrupt enable */ +#define SII_CSR_PCE 0x0002 /* Parity check enable */ +#define SII_CSR_SLE 0x0004 /* Select enable */ +#define SII_CSR_RSE 0x0008 /* Reselect enable */ +#define SII_CSR_HPM 0x0010 /* Arbitration enable */ + +/* + * SCSI bus ID register + */ + +#define SII_ID_MASK 0x0007 /* The scsi ID */ +#define SII_ID_IO 0x8000 /* ID pins are in/out */ + +/* + * Selector control and status register + */ + +#define SII_SEL_ID 0x0003 /* Destination ID */ + +/* + * Selection detector status register + */ + +#define SII_DET_ID 0x0003 /* Selector's ID */ + +/* + * Data register (silo) + */ + +#define SII_DATA_VAL 0x00ff /* Lower byte */ + +/* + * DMA control register + */ + +#define SII_DMA_SYN_OFFSET 0x0003 /* 0 -> asynch */ + +/* + * DMA counter + */ + +#define SII_DMA_COUNT_MASK 0x1fff /* in bytes */ + +/* + * DMA address registers + */ + +#define SII_DMA_LOW_MASK 0xffff /* all bits */ +#define SII_DMA_HIGH_MASK 0x0003 /* unused ones mbz */ + +/* + * DMA initial byte + */ + +#define SII_DMA_IBYTE 0x00ff /* for odd address DMAs */ + +/* + * Connection status register + */ + +#define SII_CON_LST 0x0002 /* ro: lost arbitration */ +#define SII_CON_SIP 0x0004 /* ro: selection InProgress */ +#define SII_CON_SWA 0x0008 /* rc: selected with ATN */ +#define SII_CON_TGT 0x0010 /* ro: target role */ +#define SII_CON_DST 0x0020 /* ro: sii is destination */ +#define SII_CON_CON 0x0040 /* ro: sii is connected */ +#define SII_CON_SCH 0x0080 /* rci: state change */ +#define SII_CON_LDN 0x0100 /* ??i: dssi list elm done */ +#define SII_CON_BUF 0x0200 /* ??i: dssi buffer service */ +#define SII_CON_TZ 0x0400 /* ??: dssi target zero */ +#define SII_CON_OBC 0x0800 /* ??i: dssi outen bit clr */ +#define SII_CON_BERR 0x1000 /* rci: bus error */ +#define SII_CON_RST 0x2000 /* rci: RST asserted */ +#define SII_CON_DI 0x4000 /* ro: data_csr intr */ +#define SII_CON_CI 0x8000 /* ro: con_csr intr */ + +/* + * Data transfer status register + */ + +#define SII_DTR_IO 0x0001 /* ro: I/O asserted */ +#define SII_DTR_CD 0x0002 /* ro: CD asserted */ +#define SII_DTR_MSG 0x0004 /* ro: MSG asserted */ +#define SII_DTR_ATN 0x0008 /* rc: ATN found asserted */ +#define SII_DTR_MIS 0x0010 /* roi: phase mismatch */ +#define SII_DTR_OBB 0x0100 /* ro: odd byte boundry */ +#define SII_DTR_IPE 0x0200 /* ro: incoming parity err */ +#define SII_DTR_IBF 0x0400 /* roi: input buffer full */ +#define SII_DTR_TBE 0x0800 /* roi: xmt buffer empty */ +#define SII_DTR_TCZ 0x1000 /* ro: xfer counter zero */ +#define SII_DTR_DONE 0x2000 /* rci: xfer complete */ +#define SII_DTR_DI 0x4000 /* ro: data_csr intr */ +#define SII_DTR_CI 0x8000 /* ro: con_csr intr */ + +#define SII_PHASE(dtr) SCSI_PHASE(dtr) + + +/* + * Command register + * + * Certain bits are only valid in certain roles: + * I - Initiator D - Destination T - Target + * Bits 0-3 give the 'expected phase' + * Bits 4-6 give the 'expected state' + * Bits 7-11 are the 'command' proper + */ + +#define SII_CMD_IO 0x0001 /* rw: (T) assert I/O */ +#define SII_CMD_CD 0x0002 /* rw: (T) assert CD */ +#define SII_CMD_MSG 0x0004 /* rw: (T) assert MSG */ +#define SII_CMD_ATN 0x0008 /* rw: (I) assert ATN */ + +#define SII_CMD_TGT 0x0010 /* rw: (DIT) target */ +#define SII_CMD_DST 0x0020 /* rw: (DIT) destination */ +#define SII_CMD_CON 0x0040 /* rw: (DIT) connected */ + +#define SII_CMD_RESET 0x0080 /* rw: (DIT) reset */ +#define SII_CMD_DIS 0x0100 /* rw: (DIT) disconnect */ +#define SII_CMD_REQ 0x0200 /* rw: (T) request data */ +#define SII_CMD_SEL 0x0400 /* rw: (D) select */ +#define SII_CMD_XFER 0x0800 /* rw: (IT) xfer information */ + +#define SII_CMD_RSL 0x1000 /* rw: reselect target */ +#define SII_CMD_RST 0x4000 /* zw: assert RST */ +#define SII_CMD_DMA 0x8000 /* rw: command uses DMA */ + +/* + * Diagnostic control register + */ + +#define SII_DIAG_TEST 0x0001 /* rw: test mode */ +#define SII_DIAG_DIA 0x0002 /* rw: ext loopback mode */ +#define SII_DIAG_PORT_ENB 0x0004 /* rw: enable drivers */ +#define SII_DIAG_LPB 0x0008 /* rw: loopback reg writes */ diff --git a/scsi/adapters/scsi_7061_hdw.c b/scsi/adapters/scsi_7061_hdw.c new file mode 100644 index 0000000..674e892 --- /dev/null +++ b/scsi/adapters/scsi_7061_hdw.c @@ -0,0 +1,2603 @@ +/* + * Mach Operating System + * Copyright (c) 1991,1990,1989 Carnegie Mellon University + * All Rights Reserved. + * + * Permission to use, copy, modify and distribute this software and its + * documentation is hereby granted, provided that both the copyright + * notice and this permission notice appear in all copies of the + * software, derivative works or modified versions, and any portions + * thereof, and that both notices appear in supporting documentation. + * + * CARNEGIE MELLON ALLOWS FREE USE OF THIS SOFTWARE IN ITS "AS IS" + * CONDITION. CARNEGIE MELLON DISCLAIMS ANY LIABILITY OF ANY KIND FOR + * ANY DAMAGES WHATSOEVER RESULTING FROM THE USE OF THIS SOFTWARE. + * + * Carnegie Mellon requests users of this software to return to + * + * Software Distribution Coordinator or Software.Distribution@CS.CMU.EDU + * School of Computer Science + * Carnegie Mellon University + * Pittsburgh PA 15213-3890 + * + * any improvements or extensions that they make and grant Carnegie Mellon + * the rights to redistribute these changes. + */ +/* + * File: scsi_7061_hdw.c + * Author: Alessandro Forin, Carnegie Mellon University + * Date: 10/90 + * + * Bottom layer of the SCSI driver: chip-dependent functions + * + * This file contains the code that is specific to the DEC DC7061 + * SCSI chip (Host Bus Adapter in SCSI parlance): probing, start + * operation, and interrupt routine. + */ + +/* + * This layer works based on small simple 'scripts' that are installed + * at the start of the command and drive the chip to completion. + * The idea comes from the specs of the NCR 53C700 'script' processor. + * + * There are various reasons for this, mainly + * - Performance: identify the common (successful) path, and follow it; + * at interrupt time no code is needed to find the current status + * - Code size: it should be easy to compact common operations + * - Adaptability: the code skeleton should adapt to different chips without + * terrible complications. + * - Error handling: and it is easy to modify the actions performed + * by the scripts to cope with strange but well identified sequences + * + */ + +#include +#if NSII > 0 + +#include + +#ifdef DECSTATION +#define PAD(n) short n +#endif + +#include /* spl definitions */ +#include +#include +#include +#include + +#include + +#include +#include +#include + +#define isa_oddbb hba_dep[0] +#define oddbb hba_dep[1] + +#include + +#ifdef PAD + +typedef struct { + volatile unsigned short sii_sdb; /* rw: Data bus and parity */ + PAD(pad0); + volatile unsigned short sii_sc1; /* rw: scsi signals 1 */ + PAD(pad1); + volatile unsigned short sii_sc2; /* rw: scsi signals 2 */ + PAD(pad2); + volatile unsigned short sii_csr; /* rw: control and status */ + PAD(pad3); + volatile unsigned short sii_id; /* rw: scsi bus ID */ + PAD(pad4); + volatile unsigned short sii_sel_csr; /* rw: selection status */ + PAD(pad5); + volatile unsigned short sii_destat; /* ro: selection detector status */ + PAD(pad6); + volatile unsigned short sii_dstmo; /* unsupp: dssi timeout */ + PAD(pad7); + volatile unsigned short sii_data; /* rw: data register */ + PAD(pad8); + volatile unsigned short sii_dma_ctrl; /* rw: dma control reg */ + PAD(pad9); + volatile unsigned short sii_dma_len; /* rw: length of transfer */ + PAD(pad10); + volatile unsigned short sii_dma_adr_low;/* rw: low address */ + PAD(pad11); + volatile unsigned short sii_dma_adr_hi; /* rw: high address */ + PAD(pad12); + volatile unsigned short sii_dma_1st_byte;/* rw: initial byte */ + PAD(pad13); + volatile unsigned short sii_stlp; /* unsupp: dssi short trgt list ptr */ + PAD(pad14); + volatile unsigned short sii_ltlp; /* unsupp: dssi long " " " */ + PAD(pad15); + volatile unsigned short sii_ilp; /* unsupp: dssi initiator list ptr */ + PAD(pad16); + volatile unsigned short sii_dssi_csr; /* unsupp: dssi control */ + PAD(pad17); + volatile unsigned short sii_conn_csr; /* rc: connection interrupt control */ + PAD(pad18); + volatile unsigned short sii_data_csr; /* rc: data interrupt control */ + PAD(pad19); + volatile unsigned short sii_cmd; /* rw: command register */ + PAD(pad20); + volatile unsigned short sii_diag_csr; /* rw: disgnostic status */ + PAD(pad21); +} sii_padded_regmap_t; + +#else /*!PAD*/ + +typedef sii_regmap_t sii_padded_regmap_t; + +#endif /*!PAD*/ + + +#undef SII_CSR_SLE +#define SII_CSR_SLE 0 /* for now */ + +#ifdef DECSTATION +#include +#define SII_OFFSET_RAM (KN01_SYS_SII_B_START-KN01_SYS_SII) +#define SII_RAM_SIZE (KN01_SYS_SII_B_END-KN01_SYS_SII_B_START) +/* 16 bits in 32 bit envelopes */ +#define SII_DMADR_LO(ptr) ((((unsigned)ptr)>>1)&SII_DMA_LOW_MASK) +#define SII_DMADR_HI(ptr) ((((unsigned)ptr)>>(16+1))&SII_DMA_HIGH_MASK) +#endif /* DECSTATION */ + +#ifndef SII_OFFSET_RAM /* cross compile check */ +#define SII_OFFSET_RAM 0 +#define SII_RAM_SIZE 0x10000 +#define SII_DMADR_LO(ptr) (((unsigned)ptr)>>16) +#define SII_DMADR_HI(ptr) (((unsigned)ptr)&0xffff) +#endif + +/* + * Statically partition the DMA buffer between targets. + * This way we will eventually be able to attach/detach + * drives on-fly. And 18k/target is enough. + */ +#define PER_TGT_DMA_SIZE ((SII_RAM_SIZE/7) & ~(sizeof(int)-1)) + +/* + * Round to 4k to make debug easier + */ +#define PER_TGT_BUFF_SIZE ((PER_TGT_DMA_SIZE >> 12) << 12) + +/* + * Macros to make certain things a little more readable + */ +#define SII_COMMAND(regs,cs,ds,cmd) \ + { \ + (regs)->sii_cmd = ((cs) & 0x70) | \ + ((ds) & 0x07) | (cmd); \ + wbflush(); \ + } +#define SII_ACK(regs,cs,ds,cmd) \ + { \ + SII_COMMAND(regs,cs,ds,cmd); \ + (regs)->sii_conn_csr = (cs); \ + (regs)->sii_data_csr = (ds); \ + } + +/* + * Deal with bogus pmax dma buffer + */ + +static char decent_buffer[NSII*8][256]; + +/* + * A script has a three parts: a pre-condition, an action, and + * an optional command to the chip. The first triggers error + * handling if not satisfied and in our case it is formed by the + * values of the sii_conn_csr and sii_data_csr register + * bits. The action part is just a function pointer, and the + * command is what the 7061 should be told to do at the end + * of the action processing. This command is only issued and the + * script proceeds if the action routine returns TRUE. + * See sii_intr() for how and where this is all done. + */ + +typedef struct script { + int condition; /* expected state at interrupt */ + int (*action)(); /* extra operations */ + int command; /* command to the chip */ +} *script_t; + +#define SCRIPT_MATCH(cs,ds) ((cs)&0x70|SCSI_PHASE((ds))) + +#define SII_PHASE_DISC 0x4 /* sort of .. */ + +/* When no command is needed */ +#define SCRIPT_END -1 + +/* forward decls of script actions */ +boolean_t + sii_script_true(), /* when nothing needed */ + sii_identify(), /* send identify msg */ + sii_dosynch(), /* negotiate synch xfer */ + sii_dma_in(), /* get data from target via dma */ + sii_dma_out(), /* send data to target via dma */ + sii_get_status(), /* get status from target */ + sii_end_transaction(), /* all come to an end */ + sii_msg_in(), /* get disconnect message(s) */ + sii_disconnected(); /* current target disconnected */ +/* forward decls of error handlers */ +boolean_t + sii_err_generic(), /* generic error handler */ + sii_err_disconn(), /* when a target disconnects */ + sii_err_rdp(), /* in reconn, handle rdp mgs */ + gimmeabreak(); /* drop into the debugger */ + +int sii_reset_scsibus(); +boolean_t sii_probe_target(); +static sii_wait(); + +/* + * State descriptor for this layer. There is one such structure + * per (enabled) SCSI-7061 interface + */ +struct sii_softc { + watchdog_t wd; + sii_padded_regmap_t *regs; + volatile char *buff; + script_t script; + int (*error_handler)(); + int in_count; /* amnt we expect to receive */ + int out_count; /* amnt we are going to ship */ + + volatile char state; +#define SII_STATE_BUSY 0x01 /* selecting or currently connected */ +#define SII_STATE_TARGET 0x04 /* currently selected as target */ +#define SII_STATE_COLLISION 0x08 /* lost selection attempt */ +#define SII_STATE_DMA_IN 0x10 /* tgt --> initiator xfer */ + + unsigned char ntargets; /* how many alive on this scsibus */ + unsigned char done; + unsigned char cmd_count; + + scsi_softc_t *sc; + target_info_t *active_target; + + target_info_t *next_target; /* trying to seize bus */ + queue_head_t waiting_targets;/* other targets competing for bus */ + +} sii_softc_data[NSII]; + +typedef struct sii_softc *sii_softc_t; + +sii_softc_t sii_softc[NSII]; + +/* + * Synch xfer parameters, and timing conversions + */ +int sii_min_period = 63; /* in 4 ns units */ +int sii_max_offset = 3; /* pure number */ + +#define sii_to_scsi_period(a) (a) +#define scsi_period_to_sii(p) (((p) < sii_min_period) ? sii_min_period : (p)) + +/* + * Definition of the controller for the auto-configuration program. + */ + +int sii_probe(), scsi_slave(), sii_go(), sii_intr(); +extern void scsi_attach(); + +vm_offset_t sii_std[NSII] = { 0 }; +struct bus_device *sii_dinfo[NSII*8]; +struct bus_ctlr *sii_minfo[NSII]; +struct bus_driver sii_driver = + { sii_probe, scsi_slave, scsi_attach, sii_go, sii_std, "rz", sii_dinfo, + "sii", sii_minfo, /*BUS_INTR_B4_PROBE?*/}; + +/* + * Scripts + */ +struct script +sii_script_data_in[] = { + { SCSI_PHASE_CMD|SII_CON_CON, sii_script_true, + (SII_CMD_XFER|SII_CMD_DMA)|SII_CON_CON|SCSI_PHASE_CMD}, + { SCSI_PHASE_DATAI|SII_CON_CON, sii_dma_in, + (SII_CMD_XFER|SII_CMD_DMA)|SII_CON_CON|SCSI_PHASE_DATAI}, + { SCSI_PHASE_STATUS|SII_CON_CON, sii_get_status, + SII_CMD_XFER|SII_CON_CON|SCSI_PHASE_STATUS}, + { SCSI_PHASE_MSG_IN|SII_CON_CON, sii_end_transaction, SCRIPT_END} +}, + +sii_script_data_out[] = { + { SCSI_PHASE_CMD|SII_CON_CON, sii_script_true, + (SII_CMD_XFER|SII_CMD_DMA)|SII_CON_CON|SCSI_PHASE_CMD}, + { SCSI_PHASE_DATAO|SII_CON_CON, sii_dma_out, + (SII_CMD_XFER|SII_CMD_DMA)|SII_CON_CON|SCSI_PHASE_DATAO}, + { SCSI_PHASE_STATUS|SII_CON_CON, sii_get_status, + SII_CMD_XFER|SII_CON_CON|SCSI_PHASE_STATUS}, + { SCSI_PHASE_MSG_IN|SII_CON_CON, sii_end_transaction, SCRIPT_END} +}, + +sii_script_cmd[] = { + { SCSI_PHASE_CMD|SII_CON_CON, sii_script_true, + (SII_CMD_XFER|SII_CMD_DMA)|SII_CON_CON|SCSI_PHASE_CMD}, + { SCSI_PHASE_STATUS|SII_CON_CON, sii_get_status, + SII_CMD_XFER|SII_CON_CON|SCSI_PHASE_STATUS}, + { SCSI_PHASE_MSG_IN|SII_CON_CON, sii_end_transaction, SCRIPT_END} +}, + +/* Same, after a disconnect */ + +sii_script_restart_data_in[] = { + { SCSI_PHASE_DATAI|SII_CON_CON|SII_CON_DST, sii_dma_in, + (SII_CMD_XFER|SII_CMD_DMA)|SII_CON_CON|SII_CON_DST|SCSI_PHASE_DATAI}, + { SCSI_PHASE_STATUS|SII_CON_CON|SII_CON_DST, sii_get_status, + SII_CMD_XFER|SII_CON_CON|SII_CON_DST|SCSI_PHASE_STATUS}, + { SCSI_PHASE_MSG_IN|SII_CON_CON|SII_CON_DST, sii_end_transaction, SCRIPT_END} +}, + +sii_script_restart_data_out[] = { + { SCSI_PHASE_DATAO|SII_CON_CON|SII_CON_DST, sii_dma_out, + (SII_CMD_XFER|SII_CMD_DMA)|SII_CON_CON|SII_CON_DST|SCSI_PHASE_DATAO}, + { SCSI_PHASE_STATUS|SII_CON_CON|SII_CON_DST, sii_get_status, + SII_CMD_XFER|SII_CON_CON|SII_CON_DST|SCSI_PHASE_STATUS}, + { SCSI_PHASE_MSG_IN|SII_CON_CON|SII_CON_DST, sii_end_transaction, SCRIPT_END} +}, + +sii_script_restart_cmd[] = { + { SCSI_PHASE_STATUS|SII_CON_CON|SII_CON_DST, sii_get_status, + SII_CMD_XFER|SII_CON_CON|SII_CON_DST|SCSI_PHASE_STATUS}, + { SCSI_PHASE_MSG_IN|SII_CON_CON|SII_CON_DST, sii_end_transaction, SCRIPT_END} +}, + +/* Synchronous transfer negotiation */ + +sii_script_try_synch[] = { + { SCSI_PHASE_MSG_OUT|SII_CON_CON, sii_dosynch, SCRIPT_END} +}, + +/* Disconnect sequence */ + +sii_script_disconnect[] = { + { SII_PHASE_DISC, sii_disconnected, SCRIPT_END} +}; + + + +#define u_min(a,b) (((a) < (b)) ? (a) : (b)) + + +#define DEBUG +#ifdef DEBUG + +sii_state(regs) + sii_padded_regmap_t *regs; +{ + unsigned dmadr; + + if (regs == 0) + regs = (sii_padded_regmap_t*) 0xba000000; + + dmadr = regs->sii_dma_adr_low | (regs->sii_dma_adr_hi << 16); + db_printf("sc %x, dma %x @ x%X, cs %x, ds %x, cmd %x\n", + (unsigned) regs->sii_sc1, + (unsigned) regs->sii_dma_len, dmadr, + (unsigned) regs->sii_conn_csr, + (unsigned) regs->sii_data_csr, + (unsigned) regs->sii_cmd); + +} +sii_target_state(tgt) + target_info_t *tgt; +{ + if (tgt == 0) + tgt = sii_softc[0]->active_target; + if (tgt == 0) + return 0; + db_printf("@x%x: fl %X dma %X+%X cmd %x@%X id %X per %X off %X ior %X ret %X\n", + tgt, + tgt->flags, tgt->dma_ptr, tgt->transient_state.dma_offset, tgt->cur_cmd, + tgt->cmd_ptr, tgt->target_id, tgt->sync_period, tgt->sync_offset, + tgt->ior, tgt->done); + if (tgt->flags & TGT_DISCONNECTED){ + script_t spt; + + spt = tgt->transient_state.script; + db_printf("disconnected at "); + db_printsym(spt,1); + db_printf(": %X %X ", spt->condition, spt->command); + db_printsym(spt->action,1); + db_printf(", "); + db_printsym(tgt->transient_state.handler, 1); + db_printf("\n"); + } + + return 0; +} + +sii_all_targets(unit) +{ + int i; + target_info_t *tgt; + for (i = 0; i < 8; i++) { + tgt = sii_softc[unit]->sc->target[i]; + if (tgt) + sii_target_state(tgt); + } +} + +sii_script_state(unit) +{ + script_t spt = sii_softc[unit]->script; + + if (spt == 0) return 0; + db_printsym(spt,1); + db_printf(": %X %X ", spt->condition, spt->command); + db_printsym(spt->action,1); + db_printf(", "); + db_printsym(sii_softc[unit]->error_handler, 1); + return 0; + +} + +#define PRINT(x) if (scsi_debug) printf x + +#define TRMAX 200 +int tr[TRMAX+3]; +int trpt, trpthi; +#define TR(x) tr[trpt++] = x +#define TRWRAP trpthi = trpt; trpt = 0; +#define TRCHECK if (trpt > TRMAX) {TRWRAP} + +#define TRACE + +#ifdef TRACE + +#define LOGSIZE 256 +int sii_logpt; +char sii_log[LOGSIZE]; + +#define MAXLOG_VALUE 0x25 +struct { + char *name; + unsigned int count; +} logtbl[MAXLOG_VALUE]; + +static LOG(e,f) + char *f; +{ + sii_log[sii_logpt++] = (e); + if (sii_logpt == LOGSIZE) sii_logpt = 0; + if ((e) < MAXLOG_VALUE) { + logtbl[(e)].name = (f); + logtbl[(e)].count++; + } +} + +sii_print_log(skip) + int skip; +{ + register int i, j; + register unsigned char c; + + for (i = 0, j = sii_logpt; i < LOGSIZE; i++) { + c = sii_log[j]; + if (++j == LOGSIZE) j = 0; + if (skip-- > 0) + continue; + if (c < MAXLOG_VALUE) + db_printf(" %s", logtbl[c].name); + else + db_printf("-x%x", c & 0x7f); + } + db_printf("\n"); + return 0; +} + +sii_print_stat() +{ + register int i; + register char *p; + for (i = 0; i < MAXLOG_VALUE; i++) { + if (p = logtbl[i].name) + printf("%d %s\n", logtbl[i].count, p); + } +} + +#else /* TRACE */ +#define LOG(e,f) +#endif /* TRACE */ + +struct cnt { + unsigned int zeroes; + unsigned int usage; + unsigned int avg; + unsigned int min; + unsigned int max; +}; + +static bump(counter, value) + register struct cnt *counter; + register unsigned int value; +{ + register unsigned int n; + + if (value == 0) { + counter->zeroes++; + return; + } + n = counter->usage + 1; + counter->usage = n; + if (n == 0) { + printf("{Counter at x%x overflowed with avg x%x}", + counter, counter->avg); + return; + } else + if (n == 1) + counter->min = 0xffffffff; + + counter->avg = ((counter->avg * (n - 1)) + value) / n; + if (counter->min > value) + counter->min = value; + if (counter->max < value) + counter->max = value; +} + +struct cnt + s_cnt; + +#else /* DEBUG */ +#define PRINT(x) +#define LOG(e,f) +#define TR(x) +#define TRCHECK +#define TRWRAP +#endif /* DEBUG */ + + +/* + * Probe/Slave/Attach functions + */ + +/* + * Probe routine: + * Should find out (a) if the controller is + * present and (b) which/where slaves are present. + * + * Implementation: + * Send an identify msg to each possible target on the bus + * except of course ourselves. + */ +sii_probe(reg, ui) + unsigned reg; + struct bus_ctlr *ui; +{ + int unit = ui->unit; + sii_softc_t sii = &sii_softc_data[unit]; + int target_id, i; + scsi_softc_t *sc; + register sii_padded_regmap_t *regs; + spl_t s; + boolean_t did_banner = FALSE; + char *dma_ptr; + static char *here = "sii_probe"; + + /* + * We are only called if the chip is there, + * but make sure anyways.. + */ + if (check_memory(reg, 0)) + return 0; + +#ifdef MACH_KERNEL + /* Mappable version side */ + SII_probe(reg, ui); +#endif /*MACH_KERNEL*/ + + /* + * Initialize hw descriptor + */ + sii_softc[unit] = sii; + sii->regs = (sii_padded_regmap_t *) (reg); + sii->buff = (volatile char*) (reg + SII_OFFSET_RAM); + + queue_init(&sii->waiting_targets); + + sc = scsi_master_alloc(unit, sii); + sii->sc = sc; + + sc->go = sii_go; + sc->watchdog = scsi_watchdog; + sc->probe = sii_probe_target; + + sii->wd.reset = sii_reset_scsibus; + +#ifdef MACH_KERNEL + sc->max_dma_data = -1; /* unlimited */ +#else + sc->max_dma_data = scsi_per_target_virtual; +#endif + + regs = sii->regs; + + /* + * Clear out dma buffer + */ + blkclr(sii->buff, SII_RAM_SIZE); + + /* + * Reset chip, fully. + */ + s = splbio(); + sii_reset(regs, TRUE); + + /* + * Our SCSI id on the bus. + * The user can set this via the prom on pmaxen/3maxen. + * If this changes it is easy to fix: make a default that + * can be changed as boot arg. + */ +#ifdef unneeded + regs->sii_id = (scsi_initiator_id[unit] & SII_ID_MASK)|SII_ID_IO; +#endif + sc->initiator_id = regs->sii_id & SII_ID_MASK; + printf("%s%d: my SCSI id is %d", ui->name, unit, sc->initiator_id); + + /* + * For all possible targets, see if there is one and allocate + * a descriptor for it if it is there. + */ + for (target_id = 0, dma_ptr = (char*)sii->buff; + target_id < 8; + target_id++, dma_ptr += (PER_TGT_DMA_SIZE*2)) { + + register unsigned csr, dsr; + register scsi_status_byte_t status; + + /* except of course ourselves */ + if (target_id == sc->initiator_id) + continue; + + regs->sii_sel_csr = target_id; + wbflush(); + + /* select */ + regs->sii_cmd = SII_CMD_SEL; + wbflush(); + + /* wait for a selection timeout delay, and some more */ + delay(251000); + + dsr = regs->sii_data_csr; + csr = regs->sii_conn_csr; + if ((csr & SII_CON_CON) == 0) { + + regs->sii_conn_csr = csr;/*wtc bits*/ + + /* abort sel in progress */ + if (csr & SII_CON_SIP) { + regs->sii_cmd = SII_CMD_DIS; + wbflush(); + csr = sii_wait(®s->sii_conn_csr, SII_CON_SCH,1); + regs->sii_conn_csr = 0xffff;/*wtc bits */ + regs->sii_data_csr = 0xffff; + regs->sii_cmd = 0; + wbflush(); + } + continue; + } + + printf(",%s%d", did_banner++ ? " " : " target(s) at ", + target_id); + + /* should be command phase here */ + if (SCSI_PHASE(dsr) != SCSI_PHASE_CMD) + panic(here); + + /* acknowledge state change */ + SII_ACK(regs,csr,dsr,0); + + /* build command in (bogus) dma area */ + { + unsigned int *p = (unsigned int*) dma_ptr; + + p[0] = SCSI_CMD_TEST_UNIT_READY | (0 << 8); + p[1] = 0 | (0 << 8); + p[2] = 0 | (0 << 8); + } + + /* set up dma xfer parameters */ + regs->sii_dma_len = 6; + regs->sii_dma_adr_low = SII_DMADR_LO(dma_ptr); + regs->sii_dma_adr_hi = SII_DMADR_HI(dma_ptr); + wbflush(); + + /* issue dma command */ + SII_COMMAND(regs,csr,dsr,SII_CMD_XFER|SII_CMD_DMA); + + /* wait till done */ + dsr = sii_wait(®s->sii_data_csr, SII_DTR_DONE,1); + regs->sii_cmd &= ~(SII_CMD_XFER|SII_CMD_DMA); + regs->sii_data_csr = SII_DTR_DONE;/* clear */ + regs->sii_dma_len = 0; + + /* move on to status phase */ + dsr = sii_wait(®s->sii_data_csr, SCSI_PHASE_STATUS,1); + csr = regs->sii_conn_csr; + SII_ACK(regs,csr,dsr,0); + + if (SCSI_PHASE(dsr) != SCSI_PHASE_STATUS) + panic(here); + + /* get status byte */ + dsr = sii_wait(®s->sii_data_csr, SII_DTR_IBF,1); + csr = regs->sii_conn_csr; + + status.bits = regs->sii_data; + if (status.st.scsi_status_code != SCSI_ST_GOOD) + scsi_error( 0, SCSI_ERR_STATUS, status.bits, 0); + + /* get cmd_complete message */ + SII_ACK(regs,csr,dsr,0); + SII_COMMAND(regs,csr,dsr,SII_CMD_XFER); + dsr = sii_wait(®s->sii_data_csr, SII_DTR_DONE,1); + + dsr = sii_wait(®s->sii_data_csr, SCSI_PHASE_MSG_IN,1); + csr = regs->sii_conn_csr; + + + SII_ACK(regs,csr,dsr,0); + i = regs->sii_data; + SII_COMMAND(regs,csr,dsr,SII_CMD_XFER); + + /* check disconnected, clear all intr bits */ + csr = sii_wait(®s->sii_conn_csr, SII_CON_SCH,1); + if (regs->sii_conn_csr & SII_CON_CON) + panic(here); + + regs->sii_data_csr = 0xffff; + regs->sii_conn_csr = 0xffff; + regs->sii_cmd = 0; + + /* + * Found a target + */ + sii->ntargets++; + { + register target_info_t *tgt; + tgt = scsi_slave_alloc(sc->masterno, target_id, sii); + + tgt->dma_ptr = dma_ptr; + tgt->cmd_ptr = decent_buffer[unit*8 + target_id]; +#ifdef MACH_KERNEL +#else /*MACH_KERNEL*/ + fdma_init(&tgt->fdma, scsi_per_target_virtual); +#endif /*MACH_KERNEL*/ + } + } + printf(".\n"); + + splx(s); + return 1; +} + +boolean_t +sii_probe_target(tgt, ior) + target_info_t *tgt; + io_req_t ior; +{ + sii_softc_t sii = sii_softc[tgt->masterno]; + boolean_t newlywed; + int sii_probe_timeout(); + + newlywed = (tgt->cmd_ptr == 0); + if (newlywed) { + /* desc was allocated afresh */ + char *dma_ptr = (char*)sii->buff; + + dma_ptr += (PER_TGT_DMA_SIZE * tgt->target_id)*2; + tgt->dma_ptr = dma_ptr; + tgt->cmd_ptr = decent_buffer[tgt->masterno*8 + tgt->target_id]; +#ifdef MACH_KERNEL +#else /*MACH_KERNEL*/ + fdma_init(&tgt->fdma, scsi_per_target_virtual); +#endif /*MACH_KERNEL*/ + + } + + /* Unfortunately, the SII chip does not have timeout support + for selection */ + timeout(sii_probe_timeout, tgt, hz); + + if (scsi_inquiry(tgt, SCSI_INQ_STD_DATA) == SCSI_RET_DEVICE_DOWN) + return FALSE; + + untimeout(sii_probe_timeout, tgt); + tgt->flags = TGT_ALIVE; + return TRUE; +} + +sii_probe_timeout(tgt) + target_info_t *tgt; +{ + sii_softc_t sii = (sii_softc_t)tgt->hw_state; + register sii_padded_regmap_t *regs = sii->regs; + int cs, ds; + spl_t s; + + /* cancelled ? */ + if (tgt->done != SCSI_RET_IN_PROGRESS) + return; + + s = splbio(); + + /* Someone else might be using the bus (rare) */ + switch (regs->sii_conn_csr & (SII_CON_LST|SII_CON_SIP)) { + case SII_CON_SIP: + /* We really timed out */ + break; + case SII_CON_SIP|SII_CON_LST: + /* Someone else is (still) using the bus */ + sii->wd.watchdog_state = SCSI_WD_ACTIVE; + /* fall-through */ + default: + /* Did not get a chance to the bus yet */ + timeout(sii_probe_timeout, tgt, hz); + goto ret; + } + regs->sii_cmd = SII_CMD_DIS; + wbflush(); + regs->sii_csr |= SII_CSR_RSE; + regs->sii_cmd = 0; + wbflush(); + + sii->done = SCSI_RET_DEVICE_DOWN; + cs = regs->sii_conn_csr; + ds = regs->sii_data_csr; + if (!sii_end(sii, cs, ds)) + (void) sii_reconnect(sii, cs, ds); +ret: + splx(s); +} + + +static sii_wait(preg, until, complain) + volatile unsigned short *preg; +{ + int timeo = 1000000; + while ((*preg & until) != until) { + delay(1); + if (!timeo--) { + if (complain) { + gimmeabreak(); + printf("sii_wait TIMEO with x%x\n", *preg); + } + break; + } + } +#ifdef DEBUG + bump(&s_cnt, 1000000-timeo); +#endif + return *preg; +} + +sii_reset(regs, quickly) + register sii_padded_regmap_t *regs; + boolean_t quickly; +{ + int my_id; + + my_id = regs->sii_id & SII_ID_MASK; + + regs->sii_cmd = SII_CMD_RESET; + wbflush(); + delay(30); + + /* clear them all random bitsy */ + regs->sii_conn_csr = SII_CON_SWA|SII_CON_SCH|SII_CON_BERR|SII_CON_RST; + regs->sii_data_csr = SII_DTR_ATN|SII_DTR_DONE; + + regs->sii_id = my_id | SII_ID_IO; + + regs->sii_dma_ctrl = 0; /* asynch */ + + regs->sii_dma_len = 0; + regs->sii_dma_adr_low = 0; + regs->sii_dma_adr_hi = 0; + + regs->sii_csr = SII_CSR_IE|SII_CSR_PCE|SII_CSR_SLE|SII_CSR_HPM; + /* later: SII_CSR_RSE */ + + regs->sii_diag_csr = SII_DIAG_PORT_ENB; + wbflush(); + + if (quickly) + return; + + /* + * reset the scsi bus, the interrupt routine does the rest + * or you can call sii_bus_reset(). + */ + regs->sii_cmd = SII_CMD_RST; + +} + +/* + * Operational functions + */ + +/* + * Start a SCSI command on a target + */ +sii_go(tgt, cmd_count, in_count, cmd_only) + target_info_t *tgt; + boolean_t cmd_only; +{ + sii_softc_t sii; + register spl_t s; + boolean_t disconn; + script_t scp; + boolean_t (*handler)(); + + LOG(1,"go"); + + sii = (sii_softc_t)tgt->hw_state; + + /* + * We cannot do real DMA. + */ +#ifdef MACH_KERNEL +#else /*MACH_KERNEL*/ + if (tgt->ior) + fdma_map(&tgt->fdma, tgt->ior); +#endif /*MACH_KERNEL*/ + + copyout_gap16(tgt->cmd_ptr, tgt->dma_ptr, cmd_count); + + if ((tgt->cur_cmd == SCSI_CMD_WRITE) || + (tgt->cur_cmd == SCSI_CMD_LONG_WRITE)){ + io_req_t ior = tgt->ior; + register int len = ior->io_count; + + tgt->transient_state.out_count = len; + + if (len > PER_TGT_BUFF_SIZE) + len = PER_TGT_BUFF_SIZE; + copyout_gap16( ior->io_data, + tgt->dma_ptr + (cmd_count<<1), + len); + tgt->transient_state.copy_count = len; + + /* avoid leaks */ + if (len < tgt->block_size) { + bzero_gap16(tgt->dma_ptr + ((cmd_count + len)<<1), + len - tgt->block_size); + len = tgt->block_size; + tgt->transient_state.copy_count = len; + } + + } else { + tgt->transient_state.out_count = 0; + tgt->transient_state.copy_count = 0; + } + + tgt->transient_state.cmd_count = cmd_count; + tgt->transient_state.isa_oddbb = FALSE; + + disconn = BGET(scsi_might_disconnect,tgt->masterno,tgt->target_id); + disconn = disconn && (sii->ntargets > 1); + disconn |= BGET(scsi_should_disconnect,tgt->masterno,tgt->target_id); + + /* + * Setup target state + */ + tgt->done = SCSI_RET_IN_PROGRESS; + + handler = (disconn) ? sii_err_disconn : sii_err_generic; + + switch (tgt->cur_cmd) { + case SCSI_CMD_READ: + case SCSI_CMD_LONG_READ: + LOG(0x13,"readop"); + scp = sii_script_data_in; + break; + case SCSI_CMD_WRITE: + case SCSI_CMD_LONG_WRITE: + LOG(0x14,"writeop"); + scp = sii_script_data_out; + break; + case SCSI_CMD_INQUIRY: + /* This is likely the first thing out: + do the synch neg if so */ + if (!cmd_only && ((tgt->flags&TGT_DID_SYNCH)==0)) { + scp = sii_script_try_synch; + tgt->flags |= TGT_TRY_SYNCH; + break; + } + case SCSI_CMD_REQUEST_SENSE: + case SCSI_CMD_MODE_SENSE: + case SCSI_CMD_RECEIVE_DIAG_RESULTS: + case SCSI_CMD_READ_CAPACITY: + case SCSI_CMD_READ_BLOCK_LIMITS: + case SCSI_CMD_READ_TOC: + case SCSI_CMD_READ_SUBCH: + case SCSI_CMD_READ_HEADER: + case 0xc4: /* despised: SCSI_CMD_DEC_PLAYBACK_STATUS */ + case 0xdd: /* despised: SCSI_CMD_NEC_READ_SUBCH_Q */ + case 0xde: /* despised: SCSI_CMD_NEC_READ_TOC */ + scp = sii_script_data_in; + LOG(0x1c,"cmdop"); + LOG(0x80+tgt->cur_cmd,0); + break; + case SCSI_CMD_MODE_SELECT: + case SCSI_CMD_REASSIGN_BLOCKS: + case SCSI_CMD_FORMAT_UNIT: + case 0xc9: /* vendor-spec: SCSI_CMD_DEC_PLAYBACK_CONTROL */ + tgt->transient_state.cmd_count = sizeof_scsi_command(tgt->cur_cmd); + tgt->transient_state.out_count = + cmd_count - tgt->transient_state.cmd_count; + scp = sii_script_data_out; + LOG(0x1c,"cmdop"); + LOG(0x80+tgt->cur_cmd,0); + break; + case SCSI_CMD_TEST_UNIT_READY: + /* + * Do the synch negotiation here, unless done already + */ + if (tgt->flags & TGT_DID_SYNCH) { + scp = sii_script_cmd; + } else { + scp = sii_script_try_synch; + tgt->flags |= TGT_TRY_SYNCH; + } + LOG(0x1c,"cmdop"); + LOG(0x80+tgt->cur_cmd,0); + break; + default: + LOG(0x1c,"cmdop"); + LOG(0x80+tgt->cur_cmd,0); + scp = sii_script_cmd; + } + + tgt->transient_state.script = scp; + tgt->transient_state.handler = handler; + tgt->transient_state.identify = (cmd_only) ? 0xff : + (disconn ? SCSI_IDENTIFY|SCSI_IFY_ENABLE_DISCONNECT : + SCSI_IDENTIFY); + + if (in_count) + tgt->transient_state.in_count = + (in_count < tgt->block_size) ? tgt->block_size : in_count; + else + tgt->transient_state.in_count = 0; + + tgt->transient_state.dma_offset = 0; + + /* + * See if another target is currently selected on + * this SCSI bus, e.g. lock the sii structure. + * Note that it is the strategy routine's job + * to serialize ops on the same target as appropriate. + * XXX here and everywhere, locks! + */ + /* + * Protection viz reconnections makes it tricky. + */ +/* s = splbio();*/ + s = splhigh(); + + if (sii->wd.nactive++ == 0) + sii->wd.watchdog_state = SCSI_WD_ACTIVE; + + if (sii->state & SII_STATE_BUSY) { + /* + * Queue up this target, note that this takes care + * of proper FIFO scheduling of the scsi-bus. + */ + LOG(3,"enqueue"); + LOG(0x80+tgt->target_id,0); + enqueue_tail(&sii->waiting_targets, (queue_entry_t) tgt); + } else { + /* + * It is down to at most two contenders now, + * we will treat reconnections same as selections + * and let the scsi-bus arbitration process decide. + */ + sii->state |= SII_STATE_BUSY; + sii->next_target = tgt; + sii_attempt_selection(sii); + /* + * Note that we might still lose arbitration.. + */ + } + splx(s); +} + +sii_attempt_selection(sii) + sii_softc_t sii; +{ + target_info_t *tgt; + register int out_count; + sii_padded_regmap_t *regs; + register int cmd; + + regs = sii->regs; + tgt = sii->next_target; + + LOG(4,"select"); + LOG(0x80+tgt->target_id,0); + + /* + * Init bus state variables and set registers. + * [They are intermixed to avoid wbflush()es] + */ + sii->active_target = tgt; + + out_count = tgt->transient_state.cmd_count; + + /* set dma pointer and counter */ + regs->sii_dma_len = out_count; + regs->sii_dma_adr_low = SII_DMADR_LO(tgt->dma_ptr); + regs->sii_dma_adr_hi = SII_DMADR_HI(tgt->dma_ptr); + + sii->error_handler = tgt->transient_state.handler; + + regs->sii_sel_csr = tgt->target_id; + + sii->done = SCSI_RET_IN_PROGRESS; + + regs->sii_dma_ctrl = tgt->sync_offset; + + sii->cmd_count = out_count; + +/* if (regs->sii_conn_csr & (SII_CON_CON|SII_CON_DST))*/ + if (regs->sii_sc1 & (SII_CS1_BSY|SII_CS1_SEL)) + return; + regs->sii_csr = SII_CSR_IE|SII_CSR_PCE|SII_CSR_SLE|SII_CSR_HPM; + + sii->script = tgt->transient_state.script; + sii->in_count = 0; + sii->out_count = 0; + + if (tgt->flags & TGT_DID_SYNCH) { + if (tgt->transient_state.identify == 0xff) + cmd = SII_CMD_SEL; + else { + cmd = SII_CMD_SEL | SII_CMD_ATN | + SII_CMD_CON | SII_CMD_XFER | SCSI_PHASE_MSG_OUT; + /* chain select and message out */ +/*??*/ regs->sii_dma_1st_byte = tgt->transient_state.identify; + } + } else if (tgt->flags & TGT_TRY_SYNCH) + cmd = SII_CMD_SEL | SII_CMD_ATN; + else + cmd = SII_CMD_SEL; + +/* if (regs->sii_conn_csr & (SII_CON_CON|SII_CON_DST)) { */ + if (regs->sii_sc1 & (SII_CS1_BSY|SII_CS1_SEL)) { + /* let the reconnection attempt proceed */ + regs->sii_csr = SII_CSR_IE|SII_CSR_PCE|SII_CSR_SLE| + SII_CSR_HPM|SII_CSR_RSE; + sii->script = 0; + LOG(0x8c,0); + } else { + regs->sii_cmd = cmd; + wbflush(); + } +} + +/* + * Interrupt routine + * Take interrupts from the chip + * + * Implementation: + * Move along the current command's script if + * all is well, invoke error handler if not. + */ +boolean_t sii_inside_sii_intr = FALSE; + +sii_intr(unit,spllevel) +{ + register sii_softc_t sii; + register script_t scp; + register int cs, ds; + register sii_padded_regmap_t *regs; + boolean_t try_match; +#ifdef MACH_KERNEL + extern boolean_t rz_use_mapped_interface; + + if (rz_use_mapped_interface) + return SII_intr(unit,spllevel); +#endif /*MACH_KERNEL*/ + + /* interrupt code is NOT reentrant */ + if (sii_inside_sii_intr) { + LOG(0x22,"!!attempted to reenter sii_intr!!"); + return; + } + sii_inside_sii_intr = TRUE; + + LOG(5,"\n\tintr"); + + sii = sii_softc[unit]; + + /* collect status information */ + regs = sii->regs; + cs = regs->sii_conn_csr; + ds = regs->sii_data_csr; + +TR(cs); +TR(ds); +TR(regs->sii_cmd); +TRCHECK; + + if (cs & SII_CON_RST){ + sii_bus_reset(sii); + goto getout; + } + + /* we got an interrupt allright */ + if (sii->active_target) + sii->wd.watchdog_state = SCSI_WD_ACTIVE; + + /* rid of DONEs */ + if (ds & SII_DTR_DONE) { + regs->sii_data_csr = SII_DTR_DONE; + LOG(0x1e,"done"); + ds = regs->sii_data_csr; + cs = regs->sii_conn_csr; + } + + /* drop spurious calls, note that sometimes + * ds and cs get out-of-sync */ + if (((cs & SII_CON_CI) | (ds & SII_DTR_DI)) == 0) { + LOG(2,"SPURIOUS"); + goto getout; + } + + /* clear interrupt flags */ + + regs->sii_conn_csr = cs; + regs->sii_data_csr = cs; + + /* drop priority */ + splx(spllevel); + + if ((sii->state & SII_STATE_TARGET) || + (cs & SII_CON_TGT)) { + sii_target_intr(sii,cs,ds); + goto getout; + } + + scp = sii->script; + + /* check who got the bus */ + if ((scp == 0) || (cs & SII_CON_LST)) { + if (cs & SII_CON_DST) { + sii_reconnect(sii, cs, ds); + goto getout; + } + LOG(0x12,"no-script"); + goto getout; + } + + if (SCRIPT_MATCH(cs,ds) != scp->condition) { + if (try_match = (*sii->error_handler)(sii, cs, ds)) { + cs = regs->sii_conn_csr; + ds = regs->sii_data_csr; + } + } else + try_match = TRUE; + + /* might have been side effected */ + scp = sii->script; + + if (try_match && (SCRIPT_MATCH(cs,ds) == scp->condition)) { + /* + * Perform the appropriate operation, + * then proceed + */ + if ((*scp->action)(sii, cs, ds)) { + /* might have been side effected */ + scp = sii->script; + sii->script = scp + 1; + regs->sii_cmd = scp->command; + wbflush(); + } + } +getout: + sii_inside_sii_intr = FALSE; +} + + +sii_target_intr(sii) + register sii_softc_t sii; +{ + panic("SII: TARGET MODE !!!\n"); +} + +/* + * All the many little things that the interrupt + * routine might switch to + */ +boolean_t +sii_script_true(sii, cs, ds) + register sii_softc_t sii; + +{ + SII_COMMAND(sii->regs,cs,ds,SII_CON_CON/*sanity*/); + LOG(7,"nop"); + return TRUE; +} + +boolean_t +sii_end_transaction( sii, cs, ds) + register sii_softc_t sii; +{ + register sii_padded_regmap_t *regs = sii->regs; + + SII_COMMAND(sii->regs,cs,ds,0); + + LOG(0x1f,"end_t"); + + regs->sii_csr &= ~SII_CSR_RSE; + + /* is the fifo really clean here ? */ + ds = sii_wait(®s->sii_data_csr, SII_DTR_IBF,1); + + if (regs->sii_data != SCSI_COMMAND_COMPLETE) + printf("{T%x}", regs->sii_data); + + regs->sii_cmd = SII_CMD_XFER | SII_CON_CON | SCSI_PHASE_MSG_IN | + (cs & SII_CON_DST); + wbflush(); + + ds = sii_wait(®s->sii_data_csr, SII_DTR_DONE,1); + regs->sii_data_csr = SII_DTR_DONE; + + regs->sii_cmd = 0/*SII_PHASE_DISC*/; + wbflush(); + + cs = regs->sii_conn_csr; + + if ((cs & SII_CON_SCH) == 0) + cs = sii_wait(®s->sii_conn_csr, SII_CON_SCH,1); + regs->sii_conn_csr = SII_CON_SCH; + + regs->sii_csr |= SII_CSR_RSE; + + cs = regs->sii_conn_csr; + + if (!sii_end(sii, cs, ds)) + (void) sii_reconnect(sii, cs, ds); + return FALSE; +} + +boolean_t +sii_end( sii, cs, ds) + register sii_softc_t sii; +{ + register target_info_t *tgt; + register io_req_t ior; + register sii_padded_regmap_t *regs = sii->regs; + + LOG(6,"end"); + + tgt = sii->active_target; + + if ((tgt->done = sii->done) == SCSI_RET_IN_PROGRESS) + tgt->done = SCSI_RET_SUCCESS; + + sii->script = 0; + + if (sii->wd.nactive-- == 1) + sii->wd.watchdog_state = SCSI_WD_INACTIVE; + + /* check reconnection not pending */ + cs = regs->sii_conn_csr; + if ((cs & SII_CON_DST) == 0) + sii_release_bus(sii); + else { + sii->active_target = 0; +/* sii->state &= ~SII_STATE_BUSY; later */ + } + + if (ior = tgt->ior) { + LOG(0xA,"ops->restart"); +#ifdef MACH_KERNEL +#else /*MACH_KERNEL*/ + fdma_unmap(&tgt->fdma, ior); +#endif /*MACH_KERNEL*/ + (*tgt->dev_ops->restart)(tgt, TRUE); + if (cs & SII_CON_DST) + sii->state &= ~SII_STATE_BUSY; + } + + return ((cs & SII_CON_DST) == 0); +} + +boolean_t +sii_release_bus(sii) + register sii_softc_t sii; +{ + boolean_t ret = FALSE; + + LOG(9,"release"); + + sii->script = 0; + + if (sii->state & SII_STATE_COLLISION) { + + LOG(0xB,"collided"); + sii->state &= ~SII_STATE_COLLISION; + sii_attempt_selection(sii); + + } else if (queue_empty(&sii->waiting_targets)) { + + sii->state &= ~SII_STATE_BUSY; + sii->active_target = 0; + ret = TRUE; + + } else { + + LOG(0xC,"dequeue"); + sii->next_target = (target_info_t *) + dequeue_head(&sii->waiting_targets); + sii_attempt_selection(sii); + } + return ret; +} + +boolean_t +sii_get_status( sii, cs, ds) + register sii_softc_t sii; +{ + register sii_padded_regmap_t *regs = sii->regs; + register scsi2_status_byte_t status; + register target_info_t *tgt; + unsigned int len; + unsigned short cmd; + + LOG(0xD,"get_status"); +TRWRAP; + + sii->state &= ~SII_STATE_DMA_IN; + + tgt = sii->active_target; + sii->error_handler = tgt->transient_state.handler; + + if (len = sii->in_count) { + if ((tgt->cur_cmd != SCSI_CMD_READ) && + (tgt->cur_cmd != SCSI_CMD_LONG_READ)){ + len -= regs->sii_dma_len; + copyin_gap16(tgt->dma_ptr, tgt->cmd_ptr, len); + if (len & 0x1) /* odd byte, left in silo */ + tgt->cmd_ptr[len - 1] = regs->sii_data; + } else { + if (regs->sii_dma_len) { +#if 0 + this is incorrect and besides.. + tgt->ior->io_residual = regs->sii_dma_len; +#endif + len -= regs->sii_dma_len; + } + careful_copyin_gap16( tgt, tgt->transient_state.dma_offset, + len, ds & SII_DTR_OBB, + regs->sii_dma_1st_byte); + } + sii->in_count = 0; + } + + len = regs->sii_dma_len; + regs->sii_dma_len = 0;/*later?*/ + + /* if dma is still in progress we have to quiet it down */ + cmd = regs->sii_cmd; + if (cmd & SII_CMD_DMA) { + regs->sii_cmd = cmd & ~(SII_CMD_DMA|SII_CMD_XFER); + wbflush(); + /* DONE might NOT pop up. Sigh. */ + delay(10); + regs->sii_data_csr = regs->sii_data_csr; + } + + regs->sii_cmd = SCSI_PHASE_STATUS|SII_CON_CON|(cs & SII_CON_DST); + wbflush(); + + ds = sii_wait(®s->sii_data_csr, SII_DTR_IBF,1); + status.bits = regs->sii_data; + + if (status.st.scsi_status_code != SCSI_ST_GOOD) { + scsi_error(sii->active_target, SCSI_ERR_STATUS, status.bits, 0); + sii->done = (status.st.scsi_status_code == SCSI_ST_BUSY) ? + SCSI_RET_RETRY : SCSI_RET_NEED_SENSE; + } else + sii->done = SCSI_RET_SUCCESS; + + return TRUE; +} + +boolean_t +sii_dma_in( sii, cs, ds) + register sii_softc_t sii; +{ + register target_info_t *tgt; + register sii_padded_regmap_t *regs = sii->regs; + char *dma_ptr; + register int count; + boolean_t advance_script = TRUE; + + SII_COMMAND(regs,cs,ds,0); + LOG(0xE,"dma_in"); + + tgt = sii->active_target; + sii->error_handler = tgt->transient_state.handler; + sii->state |= SII_STATE_DMA_IN; + + if (sii->in_count == 0) { + /* + * Got nothing yet: either just sent the command + * or just reconnected + */ + register int avail; + + if (tgt->transient_state.isa_oddbb) { + regs->sii_dma_1st_byte = tgt->transient_state.oddbb; + tgt->transient_state.isa_oddbb = FALSE; + } + + count = tgt->transient_state.in_count; + count = u_min(count, (SII_DMA_COUNT_MASK+1)); + avail = PER_TGT_BUFF_SIZE - tgt->transient_state.dma_offset; + count = u_min(count, avail); + + /* common case of 8k-or-less read ? */ + advance_script = (tgt->transient_state.in_count == count); + + } else { + + /* + * We received some data. + * Also, take care of bogus interrupts + */ + register int offset, xferred; + unsigned char obb = regs->sii_data; + + xferred = sii->in_count - regs->sii_dma_len; + assert(xferred > 0); + tgt->transient_state.in_count -= xferred; + assert(tgt->transient_state.in_count > 0); + offset = tgt->transient_state.dma_offset; + tgt->transient_state.dma_offset += xferred; + count = u_min(tgt->transient_state.in_count, (SII_DMA_COUNT_MASK+1)); + if (tgt->transient_state.dma_offset == PER_TGT_BUFF_SIZE) { + tgt->transient_state.dma_offset = 0; + } else { + register int avail; + avail = PER_TGT_BUFF_SIZE - tgt->transient_state.dma_offset; + count = u_min(count, avail); + } + + /* get some more */ + dma_ptr = tgt->dma_ptr + (tgt->transient_state.dma_offset << 1); + sii->in_count = count; + regs->sii_dma_len = count; + regs->sii_dma_adr_low = SII_DMADR_LO(dma_ptr); + regs->sii_dma_adr_hi = SII_DMADR_HI(dma_ptr); + wbflush(); + regs->sii_cmd = sii->script->command; + wbflush(); + + /* copy what we got */ + careful_copyin_gap16( tgt, offset, xferred, ds & SII_DTR_OBB, obb); + + /* last chunk ? */ + if (count == tgt->transient_state.in_count) { + sii->script++; + } + return FALSE; + } +quickie: + sii->in_count = count; + dma_ptr = tgt->dma_ptr + (tgt->transient_state.dma_offset << 1); + regs->sii_dma_len = count; + regs->sii_dma_adr_low = SII_DMADR_LO(dma_ptr); + regs->sii_dma_adr_hi = SII_DMADR_HI(dma_ptr); + wbflush(); + + if (!advance_script) { + regs->sii_cmd = sii->script->command; + wbflush(); + } + return advance_script; +} + +/* send data to target. Called in three different ways: + (a) to start transfer (b) to restart a bigger-than-8k + transfer (c) after reconnection + */ +boolean_t +sii_dma_out( sii, cs, ds) + register sii_softc_t sii; +{ + register sii_padded_regmap_t *regs = sii->regs; + register char *dma_ptr; + register target_info_t *tgt; + boolean_t advance_script = TRUE; + int count = sii->out_count; + + SII_COMMAND(regs,cs,ds,0); + LOG(0xF,"dma_out"); + + tgt = sii->active_target; + sii->error_handler = tgt->transient_state.handler; + sii->state &= ~SII_STATE_DMA_IN; + + if (sii->out_count == 0) { + /* + * Nothing committed: either just sent the + * command or reconnected + */ + register int remains; + + count = tgt->transient_state.out_count; + count = u_min(count, (SII_DMA_COUNT_MASK+1)); + remains = PER_TGT_BUFF_SIZE - tgt->transient_state.dma_offset; + count = u_min(count, remains); + + /* common case of 8k-or-less write ? */ + advance_script = (tgt->transient_state.out_count == count); + } else { + /* + * We sent some data. + * Also, take care of bogus interrupts + */ + register int offset, xferred; + + xferred = sii->out_count - regs->sii_dma_len; + assert(xferred > 0); + tgt->transient_state.out_count -= xferred; + assert(tgt->transient_state.out_count > 0); + offset = tgt->transient_state.dma_offset; + tgt->transient_state.dma_offset += xferred; + count = u_min(tgt->transient_state.out_count, (SII_DMA_COUNT_MASK+1)); + if (tgt->transient_state.dma_offset == PER_TGT_BUFF_SIZE) { + tgt->transient_state.dma_offset = 0; + } else { + register int remains; + remains = PER_TGT_BUFF_SIZE - tgt->transient_state.dma_offset; + count = u_min(count, remains); + } + /* last chunk ? */ + if (tgt->transient_state.out_count == count) + goto quickie; + + /* ship some more */ + dma_ptr = tgt->dma_ptr + + ((tgt->transient_state.cmd_count + tgt->transient_state.dma_offset) << 1); + sii->out_count = count; + regs->sii_dma_len = count; + regs->sii_dma_adr_low = SII_DMADR_LO(dma_ptr); + regs->sii_dma_adr_hi = SII_DMADR_HI(dma_ptr); + wbflush(); + regs->sii_cmd = sii->script->command; + + /* copy some more data */ + careful_copyout_gap16(tgt, offset, xferred); + return FALSE; + } + +quickie: + sii->out_count = count; + dma_ptr = tgt->dma_ptr + + ((tgt->transient_state.cmd_count + tgt->transient_state.dma_offset) << 1); + regs->sii_dma_len = count; + regs->sii_dma_adr_low = SII_DMADR_LO(dma_ptr); + regs->sii_dma_adr_hi = SII_DMADR_HI(dma_ptr); + wbflush(); + + if (!advance_script) { + regs->sii_cmd = sii->script->command; + wbflush(); + } + return advance_script; +} + +/* disconnect-reconnect ops */ + +/* get the message in via dma */ +boolean_t +sii_msg_in(sii, cs, ds) + register sii_softc_t sii; + register unsigned char cs, ds; +{ + register target_info_t *tgt; + char *dma_ptr; + register sii_padded_regmap_t *regs = sii->regs; + + LOG(0x15,"msg_in"); + + tgt = sii->active_target; + + dma_ptr = tgt->dma_ptr; + /* We would clobber the data for READs */ + if (sii->state & SII_STATE_DMA_IN) { + register int offset; + offset = tgt->transient_state.cmd_count + tgt->transient_state.dma_offset; + if (offset & 1) offset++; + dma_ptr += (offset << 1); + } + + regs->sii_dma_adr_low = SII_DMADR_LO(dma_ptr); + regs->sii_dma_adr_hi = SII_DMADR_HI(dma_ptr); + /* We only really expect two bytes */ + regs->sii_dma_len = sizeof(scsi_command_group_0); + wbflush(); + + return TRUE; +} + +/* check the message is indeed a DISCONNECT */ +boolean_t +sii_disconnect(sii, cs, ds) + register sii_softc_t sii; + register unsigned char cs, ds; + +{ + register target_info_t *tgt; + register int len; + boolean_t ok = FALSE; + unsigned int dmsg = 0; + + tgt = sii->active_target; + + len = sizeof(scsi_command_group_0) - sii->regs->sii_dma_len; + PRINT(("{G%d}",len)); + +/* if (len == 0) ok = FALSE; */ + if (len == 1) { + dmsg = sii->regs->sii_dma_1st_byte; + ok = (dmsg == SCSI_DISCONNECT); + } else if (len == 2) { + register char *msgs; + register unsigned int offset; + register sii_padded_regmap_t *regs = sii->regs; + + /* wherever it was, take it from there */ + offset = regs->sii_dma_adr_low | ((regs->sii_dma_adr_hi&3)<<16); + msgs = (char*)sii->buff + (offset << 1); + dmsg = *((unsigned short *)msgs); + + /* A SDP message preceeds it in non-completed READs */ + ok = (((dmsg & 0xff) == SCSI_DISCONNECT) || + (dmsg == ((SCSI_DISCONNECT<<8)|SCSI_SAVE_DATA_POINTER))); + } + if (!ok) + printf("[tgt %d bad msg (%d): %x]", tgt->target_id, len, dmsg); + + return TRUE; +} + +/* save all relevant data, free the BUS */ +boolean_t +sii_disconnected(sii, cs, ds) + register sii_softc_t sii; + register unsigned char cs, ds; + +{ + register target_info_t *tgt; + + SII_COMMAND(sii->regs,cs,ds,0); + + sii->regs->sii_csr = SII_CSR_IE|SII_CSR_PCE|SII_CSR_SLE| + SII_CSR_HPM|SII_CSR_RSE; + + LOG(0x16,"disconnected"); + + sii_disconnect(sii,cs,ds); + + tgt = sii->active_target; + tgt->flags |= TGT_DISCONNECTED; + tgt->transient_state.handler = sii->error_handler; + /* the rest has been saved in sii_err_disconn() */ + + PRINT(("{D%d}", tgt->target_id)); + + sii_release_bus(sii); + + return FALSE; +} + +/* get reconnect message, restore BUS */ +boolean_t +sii_reconnect(sii, cs, ds) + register sii_softc_t sii; + register unsigned char cs, ds; + +{ + register target_info_t *tgt; + sii_padded_regmap_t *regs; + int id; + + regs = sii->regs; + regs->sii_conn_csr = SII_CON_SCH; + regs->sii_cmd = SII_CON_CON|SII_CON_DST|SCSI_PHASE_MSG_IN; + wbflush(); + + LOG(0x17,"reconnect"); + + /* + * See if this reconnection collided with a selection attempt + */ + if (sii->state & SII_STATE_BUSY) + sii->state |= SII_STATE_COLLISION; + + sii->state |= SII_STATE_BUSY; + + cs = regs->sii_conn_csr; + + /* tk50s are slow */ + if ((cs & SII_CON_CON) == 0) + cs = sii_wait(®s->sii_conn_csr, SII_CON_CON,1); + + /* ?? */ + if (regs->sii_conn_csr & SII_CON_BERR) + regs->sii_conn_csr = SII_CON_BERR; + + if ((ds & SII_DTR_IBF) == 0) + ds = sii_wait(®s->sii_data_csr, SII_DTR_IBF,1); + + if (regs->sii_data != SCSI_IDENTIFY) + printf("{I%x %x}", regs->sii_data, regs->sii_dma_1st_byte); + + /* find tgt: id is in sii_destat */ + id = regs->sii_destat; + + tgt = sii->sc->target[id]; + if (id > 7 || tgt == 0) panic("sii_reconnect"); + + PRINT(("{R%d}", id)); + if (sii->state & SII_STATE_COLLISION) + PRINT(("[B %d-%d]", sii->active_target->target_id, id)); + + LOG(0x80+id,0); + + sii->active_target = tgt; + tgt->flags &= ~TGT_DISCONNECTED; + + /* synch params */ + regs->sii_dma_ctrl = tgt->sync_offset; + regs->sii_dma_len = 0; + + sii->script = tgt->transient_state.script; + sii->error_handler = sii_err_rdp; + sii->in_count = 0; + sii->out_count = 0; + + regs->sii_cmd = SII_CMD_XFER|SII_CMD_CON|SII_CMD_DST|SCSI_PHASE_MSG_IN; + wbflush(); + + (void) sii_wait(®s->sii_data_csr, SII_DTR_DONE,1); + regs->sii_data_csr = SII_DTR_DONE; + + return TRUE; +} + + +/* do the synch negotiation */ +boolean_t +sii_dosynch( sii, cs, ds) + register sii_softc_t sii; +{ + /* + * Phase is MSG_OUT here, cmd has not been xferred + */ + int *p, len; + unsigned short dmalo, dmahi, dmalen; + register target_info_t *tgt; + register sii_padded_regmap_t *regs = sii->regs; + unsigned char off; + + regs->sii_cmd = SCSI_PHASE_MSG_OUT|SII_CMD_ATN|SII_CON_CON; + wbflush(); + + LOG(0x11,"dosync"); + + tgt = sii->active_target; + + tgt->flags |= TGT_DID_SYNCH; /* only one chance */ + tgt->flags &= ~TGT_TRY_SYNCH; + + p = (int*) (tgt->dma_ptr + (((regs->sii_dma_len<<1) + 2) & ~3)); + p[0] = SCSI_IDENTIFY | (SCSI_EXTENDED_MESSAGE<<8); + p[1] = 3 | (SCSI_SYNC_XFER_REQUEST<<8); + if (BGET(scsi_no_synchronous_xfer,tgt->masterno,tgt->target_id)) + off = 0; + else + off = sii_max_offset; + /* but we'll ship "off" manually */ + p[2] = sii_to_scsi_period(sii_min_period) |(off << 8); + + dmalen = regs->sii_dma_len; + dmalo = regs->sii_dma_adr_low; + dmahi = regs->sii_dma_adr_hi; + regs->sii_dma_len = sizeof(scsi_synch_xfer_req_t) /* + 1 */; + regs->sii_dma_adr_low = SII_DMADR_LO(p); + regs->sii_dma_adr_hi = SII_DMADR_HI(p); + wbflush(); + + regs->sii_cmd = SII_CMD_DMA|SII_CMD_XFER|SII_CMD_ATN| + SII_CON_CON|SCSI_PHASE_MSG_OUT; + wbflush(); + + /* wait for either DONE or MIS */ + ds = sii_wait(®s->sii_data_csr, SII_DTR_DI,1); + + /* TK50s do not like xtended messages */ + /* and some others just ignore the standard */ + if (SCSI_PHASE(ds) != SCSI_PHASE_MSG_OUT) { + /* disentangle FIFO */ + regs->sii_cmd = SII_CON_CON|SCSI_PHASE_MSG_OUT; + ds = sii_wait(®s->sii_data_csr, SII_DTR_DONE,1); + if (SCSI_PHASE(ds) == SCSI_PHASE_MSG_IN) + goto msgin; + goto got_answer; + } + + /* ack and stop dma */ + regs->sii_cmd = SII_CON_CON|SCSI_PHASE_MSG_OUT|SII_CMD_ATN; + wbflush(); + ds = sii_wait(®s->sii_data_csr, SII_DTR_DONE,1); + regs->sii_data_csr = SII_DTR_DONE; + wbflush(); + + /* last byte of message */ + regs->sii_data = off; + wbflush(); + regs->sii_cmd = SII_CMD_XFER|SII_CON_CON|SCSI_PHASE_MSG_OUT; + wbflush(); + + /* Race here: who will interrupt first, the DMA + controller or the status watching machine ? */ + delay(1000); + regs->sii_cmd = SII_CON_CON|SCSI_PHASE_MSG_OUT; + wbflush(); + + ds = sii_wait(®s->sii_data_csr, SII_DTR_DONE,1); + regs->sii_data_csr = SII_DTR_DONE; + + /* The standard sez there nothing else the target can do but.. */ + ds = sii_wait(®s->sii_data_csr, SCSI_PHASE_MSG_IN,0); + + /* Of course, what are standards for ? */ + if (SCSI_PHASE(ds) == SCSI_PHASE_CMD) + goto cmdp; +msgin: + /* ack */ + regs->sii_cmd = SII_CON_CON|SCSI_PHASE_MSG_IN; + wbflush(); + + /* set up dma to receive answer */ + regs->sii_dma_adr_low = SII_DMADR_LO(p); + regs->sii_dma_adr_hi = SII_DMADR_HI(p); + regs->sii_dma_len = sizeof(scsi_synch_xfer_req_t); + wbflush(); + regs->sii_cmd = SII_CMD_DMA|SII_CMD_XFER|SII_CON_CON|SCSI_PHASE_MSG_IN; + wbflush(); + + /* wait for the answer, and look at it */ + ds = sii_wait(®s->sii_data_csr, SII_DTR_MIS,1); + + regs->sii_cmd = SII_CON_CON|SCSI_PHASE_MSG_IN; + wbflush(); + ds = sii_wait(®s->sii_data_csr, SII_DTR_DONE,1); + +got_answer: + /* do not cancel the phase mismatch */ + regs->sii_data_csr = SII_DTR_DONE; + + if (regs->sii_dma_len || ((p[0] & 0xff) != SCSI_EXTENDED_MESSAGE)) { + /* did not like it */ + printf(" did not like SYNCH xfer "); + } else { + /* will do synch */ + tgt->sync_period = scsi_period_to_sii((p[1]>>8)&0xff); + tgt->sync_offset = regs->sii_data; /* odd xfer, in silo */ + /* sanity */ + if (tgt->sync_offset > sii_max_offset) + tgt->sync_offset = sii_max_offset; + regs->sii_dma_ctrl = tgt->sync_offset; + } + +cmdp: + /* phase should be command now */ + regs->sii_dma_len = dmalen; + regs->sii_dma_adr_low = dmalo; + regs->sii_dma_adr_hi = dmahi; + wbflush(); + + /* continue with simple command script */ + sii->error_handler = sii_err_generic; + + sii->script = (tgt->cur_cmd == SCSI_CMD_INQUIRY) ? + sii_script_data_in : sii_script_cmd; + if (SCSI_PHASE(ds) == SCSI_PHASE_CMD ) + return TRUE; + + sii->script++; + if (SCSI_PHASE(ds) == SCSI_PHASE_STATUS ) + return TRUE; + + sii->script++; /* msgin? */ + sii->script++; + if (SCSI_PHASE(ds) == SII_PHASE_DISC) + return TRUE; + +gimmeabreak(); + panic("sii_dosynch"); + return FALSE; +} + +/* + * The bus was reset + */ +sii_bus_reset(sii) + register sii_softc_t sii; +{ + register sii_padded_regmap_t *regs = sii->regs; + + LOG(0x21,"bus_reset"); + + /* + * Clear interrupt bits + */ + regs->sii_conn_csr = 0xffff; + regs->sii_data_csr = 0xffff; + + /* + * Clear bus descriptor + */ + sii->script = 0; + sii->error_handler = 0; + sii->active_target = 0; + sii->next_target = 0; + sii->state = 0; + queue_init(&sii->waiting_targets); + sii->wd.nactive = 0; + sii_reset(regs, TRUE); + + log(LOG_KERN, "sii: (%d) bus reset ", ++sii->wd.reset_count); + delay(scsi_delay_after_reset); /* some targets take long to reset */ + + if (sii->sc == 0) /* sanity */ + return; + + sii_inside_sii_intr = FALSE; + + scsi_bus_was_reset(sii->sc); +} + +/* + * Error handlers + */ + +/* + * Generic, default handler + */ +boolean_t +sii_err_generic(sii, cs, ds) + register sii_softc_t sii; +{ + register int cond = sii->script->condition; + + LOG(0x10,"err_generic"); + + /* + * Note to DEC hardware people. + * Dropping the notion of interrupting on + * DMA completions (or at least make it optional) + * would save TWO interrupts out of the SEVEN that + * are currently requested for a non-disconnecting + * READ or WRITE operation. + */ + if (ds & SII_DTR_DONE) + return TRUE; + + /* this is a band-aid */ + if ((SCSI_PHASE(cond) == SII_PHASE_DISC) && + (cs & SII_CON_SCH)) { + ds &= ~7; + ds |= SII_PHASE_DISC; + (void) (*sii->script->action)(sii,cs,ds); + return FALSE; + } + + /* TK50s are slow to connect, forgive em */ + if ((SCSI_PHASE(ds) == SCSI_PHASE_MSG_OUT) || + (SCSI_PHASE(cond) == SCSI_PHASE_MSG_OUT)) + return TRUE; + if ((SCSI_PHASE(cond) == SCSI_PHASE_CMD) && + ((SCSI_PHASE(ds) == 0) || (SCSI_PHASE(ds) == 4) || (SCSI_PHASE(ds) == 5))) + return TRUE; + + /* transition to status ? */ + if (SCSI_PHASE(ds) == SCSI_PHASE_STATUS) + return sii_err_to_status(sii, cs, ds); + + return sii_err_phase_mismatch(sii,cs,ds); +} + +/* + * Handle generic errors that are reported as + * an unexpected change to STATUS phase + */ +sii_err_to_status(sii, cs, ds) + register sii_softc_t sii; +{ + script_t scp = sii->script; + + LOG(0x20,"err_tostatus"); + while (SCSI_PHASE(scp->condition) != SCSI_PHASE_STATUS) + scp++; + sii->script = scp; +#if 0 + /* + * Normally, we would already be able to say the command + * is in error, e.g. the tape had a filemark or something. + * But in case we do disconnected mode WRITEs, it is quite + * common that the following happens: + * dma_out -> disconnect -> reconnect + * and our script might expect at this point that the dma + * had to be restarted (it didn't know it was completed + * because the tape record is shorter than we asked for). + * And in any event.. it is both correct and cleaner to + * declare error iff the STATUS byte says so. + */ + sii->done = SCSI_RET_NEED_SENSE; +#endif + return TRUE; +} + +/* + * Watch for a disconnection + */ +boolean_t +sii_err_disconn(sii, cs, ds) + register sii_softc_t sii; + register unsigned cs, ds; +{ + register sii_padded_regmap_t *regs; + register target_info_t *tgt; + int count; + int from; + unsigned char obb; + int delayed_copy = 0; + + LOG(0x18,"err_disconn"); + + if (SCSI_PHASE(ds) != SCSI_PHASE_MSG_IN) + return sii_err_generic(sii, cs, ds); + + regs = sii->regs; + + if ((regs->sii_cmd & (SII_CMD_DMA|SII_CMD_XFER)) == + (SII_CMD_DMA|SII_CMD_XFER)) { + /* stop dma and wait */ + regs->sii_cmd &= ~(SII_CMD_DMA|SII_CMD_XFER); + (void) sii_wait(®s->sii_data_csr, SII_DTR_DONE,1); +/* later: regs->sii_data_csr = SII_DTR_DONE; */ + } + + SII_COMMAND(regs,cs,ds,0); + + tgt = sii->active_target; + switch (SCSI_PHASE(sii->script->condition)) { + case SCSI_PHASE_DATAO: + LOG(0x1b,"+DATAO"); + if (sii->out_count) { + register int xferred, offset; + + xferred = sii->out_count - regs->sii_dma_len; + tgt->transient_state.out_count -= xferred; + assert(tgt->transient_state.out_count > 0); + offset = tgt->transient_state.dma_offset; + tgt->transient_state.dma_offset += xferred; + if (tgt->transient_state.dma_offset == PER_TGT_BUFF_SIZE) + tgt->transient_state.dma_offset = 0; + + delayed_copy = 1; + from = offset; + count = xferred; + + } + tgt->transient_state.script = sii_script_restart_data_out; + break; + + case SCSI_PHASE_DATAI: + LOG(0x19,"+DATAI"); + if (sii->in_count) { + register int offset, xferred; + + obb = regs->sii_dma_1st_byte; + + xferred = sii->in_count - regs->sii_dma_len; + assert(xferred > 0); + if (ds & SII_DTR_OBB) { + tgt->transient_state.isa_oddbb = TRUE; + tgt->transient_state.oddbb = obb; + } + tgt->transient_state.in_count -= xferred; + assert(tgt->transient_state.in_count > 0); + offset = tgt->transient_state.dma_offset; + tgt->transient_state.dma_offset += xferred; + if (tgt->transient_state.dma_offset == PER_TGT_BUFF_SIZE) + tgt->transient_state.dma_offset = 0; + + /* copy what we got */ + + delayed_copy = 2; + from = offset; + count = xferred; + + } + tgt->transient_state.script = sii_script_restart_data_in; + break; + + case SCSI_PHASE_STATUS: + /* will have to restart dma */ + if (count = regs->sii_dma_len) { + (void) sii_wait(®s->sii_data_csr, SII_DTR_DONE,1); + regs->sii_data_csr = SII_DTR_DONE; + } + if (sii->state & SII_STATE_DMA_IN) { + register int offset, xferred; + + obb = regs->sii_dma_1st_byte; + + LOG(0x1a,"+STATUS+R"); + + xferred = sii->in_count - count; + assert(xferred > 0); + if (ds & SII_DTR_OBB) { + tgt->transient_state.isa_oddbb = TRUE; + tgt->transient_state.oddbb = obb; + } + tgt->transient_state.in_count -= xferred; +/* assert(tgt->transient_state.in_count > 0);*/ + offset = tgt->transient_state.dma_offset; + tgt->transient_state.dma_offset += xferred; + if (tgt->transient_state.dma_offset == PER_TGT_BUFF_SIZE) + tgt->transient_state.dma_offset = 0; + + /* copy what we got */ + + delayed_copy = 2; + from = offset; + count = xferred; + + tgt->transient_state.script = sii_script_restart_data_in; + if (tgt->transient_state.in_count == 0) + tgt->transient_state.script++; + + } else { + + LOG(0x1d,"+STATUS+W"); + + if ((count == 0) && (tgt->transient_state.out_count == sii->out_count)) { + /* all done */ + tgt->transient_state.script = &sii_script_restart_data_out[1]; + tgt->transient_state.out_count = 0; + } else { + register int xferred, offset; + + /* how much we xferred */ + xferred = sii->out_count - count; + + tgt->transient_state.out_count -= xferred; + assert(tgt->transient_state.out_count > 0); + offset = tgt->transient_state.dma_offset; + tgt->transient_state.dma_offset += xferred; + if (tgt->transient_state.dma_offset == PER_TGT_BUFF_SIZE) + tgt->transient_state.dma_offset = 0; + + delayed_copy = 1; + from = offset; + count = xferred; + + tgt->transient_state.script = sii_script_restart_data_out; + } + sii->out_count = 0; + } + break; + case SII_PHASE_DISC: /* sometimes disconnects and phase remains */ + return sii_err_generic(sii, cs, ds); + default: + gimmeabreak(); + } + regs->sii_csr &= ~SII_CSR_RSE; + sii_msg_in(sii,cs,ds); + sii->script = sii_script_disconnect; + regs->sii_cmd = SII_CMD_DMA|SII_CMD_XFER|SCSI_PHASE_MSG_IN| + SII_CON_CON|(regs->sii_conn_csr & SII_CON_DST); + wbflush(); + if (delayed_copy == 2) + careful_copyin_gap16( tgt, from, count, ds & SII_DTR_OBB, obb); + else if (delayed_copy == 1) + careful_copyout_gap16( tgt, from, count); + + return FALSE; +} + +/* + * Suppose someone reads the specs as they read the Bible. + * They would send these unnecessary restore-pointer msgs + * in reconnect phases. If this was a SCSI-2 modify-pointer + * I could understand, but. Oh well. + */ +sii_err_rdp(sii, cs, ds) + register sii_softc_t sii; +{ + register sii_padded_regmap_t *regs; + register target_info_t *tgt; + + LOG(0x24,"err_drp"); + + /* One chance */ + sii->error_handler = sii->active_target->transient_state.handler; + + if (SCSI_PHASE(ds) != SCSI_PHASE_MSG_IN) + return sii_err_generic(sii, cs, ds); + + regs = sii->regs; + + if ((ds & SII_DTR_IBF) == 0) + ds = sii_wait(®s->sii_data_csr, SII_DTR_IBF,1); + + if (regs->sii_data != SCSI_RESTORE_POINTERS) + return sii_err_disconn(sii, cs, ds); + + regs->sii_cmd = SII_CMD_XFER|SII_CMD_CON|SII_CMD_DST|SCSI_PHASE_MSG_IN; + wbflush(); + + (void) sii_wait(®s->sii_data_csr, SII_DTR_DONE,1); + regs->sii_data_csr = SII_DTR_DONE; + + return FALSE; +} + +/* + * Handle strange, yet unexplained interrupts and error + * situations which eventually I will be old and wise + * enough to deal with properly with preventive care. + */ +sii_err_phase_mismatch(sii, cs, ds) + register sii_softc_t sii; +{ + register sii_padded_regmap_t *regs = sii->regs; + register int match; + + LOG(0x23,"err_mismatch"); + + match = SCSI_PHASE(sii->script->condition); + + /* dmain interrupted */ + if ((match == SCSI_PHASE_STATUS) && (SCSI_PHASE(ds) == SCSI_PHASE_DATAI)) { + register int xferred; + register char *p; + + if (regs->sii_dma_len <= 1) { +/*if (scsi_debug)*/ +printf("[DMAINZERO %x %x %x]", cs, ds, regs->sii_dma_len); + if (regs->sii_dma_len == 0) { + regs->sii_dma_len = sii->in_count; + wbflush(); + regs->sii_cmd = sii->script[-1].command; + } + return FALSE; + } + + /* This happens when you do not "init" the prom + and the fifo is screwed up */ + xferred = sii->in_count - regs->sii_dma_len; + p = (char*)( regs->sii_dma_adr_low | ((regs->sii_dma_adr_hi&3)<<16) ); + p += xferred; +if (scsi_debug) +printf("[DMAIN %x %x %x]", cs, ds, xferred); + /* odd bytes are not xferred */ + if (((unsigned)p) & 0x1){ + register short *oddb; + oddb = (short*)(sii->buff) + ((unsigned)p-1);/*shifts*/ + *oddb = regs->sii_dma_1st_byte; + } + regs->sii_dma_adr_low = ((unsigned)p); + regs->sii_dma_adr_hi = ((unsigned)p) << 16; + wbflush(); + regs->sii_cmd = sii->script[-1].command; + wbflush(); + return FALSE; + } else + /* dmaout interrupted */ + if ((match == SCSI_PHASE_STATUS) && (SCSI_PHASE(ds) == SCSI_PHASE_DATAO)) { + register int xferred; + register char *p; + + if (regs->sii_dma_len <= 1) { +/*if (scsi_debug)*/ +printf("[DMAOUTZERO %x %x %x]", cs, ds, regs->sii_dma_len); +gimmeabreak(); + if (regs->sii_dma_len == 0) { + regs->sii_dma_len = sii->out_count; + wbflush(); + regs->sii_cmd = sii->script[-1].command; + } + return FALSE; + } + + xferred = sii->out_count - regs->sii_dma_len; +/*if (scsi_debug)*/ +printf("[DMAOUT %x %x %x %x]", cs, ds, regs->sii_dma_len, sii->out_count); + sii->out_count -= xferred; + p = (char*)( regs->sii_dma_adr_low | ((regs->sii_dma_adr_hi&3)<<16) ); + p += xferred; + regs->sii_dma_adr_low = ((unsigned)p); + regs->sii_dma_adr_hi = ((unsigned)p) << 16; + wbflush(); + regs->sii_cmd = sii->script[-1].command; + wbflush(); + return FALSE; + } +#if 1 /* ?? */ + /* stuck in cmd phase */ + else if ((SCSI_PHASE(ds) == SCSI_PHASE_CMD) && + ((match == SCSI_PHASE_DATAI) || (match == SCSI_PHASE_DATAO))) { +/*if (scsi_debug)*/ +printf("[CMD %x %x %x %x]", cs, ds, sii->cmd_count, regs->sii_dma_len); + if (regs->sii_dma_len != 0) { + /* ouch, this hurts */ + register int xferred; + register char *p; + + xferred = sii->cmd_count - regs->sii_dma_len; + sii->cmd_count -= xferred; + p = (char*)( regs->sii_dma_adr_low | ((regs->sii_dma_adr_hi&3)<<16) ); + p += xferred; + regs->sii_dma_adr_low = ((unsigned)p); + regs->sii_dma_adr_hi = ((unsigned)p) << 16; + wbflush(); + regs->sii_cmd = 0x8842; + wbflush(); + return FALSE;; + + } + SII_ACK(regs,cs,ds,0/*match*/); + wbflush(); + return FALSE;; + } +#endif + else { + printf("{D%x %x}", cs, ds); +/* if (scsi_debug)*/ gimmeabreak(); + } + return FALSE; +} + +/* + * Watchdog + * + * There are two ways in which I have seen the chip + * get stuck: a target never reconnected, or the + * selection deadlocked. Both cases involved a tk50, + * but elsewhere it showed up with hitachi disks too. + */ +sii_reset_scsibus(sii) + register sii_softc_t sii; +{ + register target_info_t *tgt = sii->active_target; + register sii_padded_regmap_t *regs = sii->regs; + + /* see if SIP still --> device down or non-existant */ + if ((regs->sii_conn_csr & (SII_CON_LST|SII_CON_SIP)) == SII_CON_SIP){ + if (tgt) { + log(LOG_KERN, "Target %d went offline\n", + tgt->target_id); + tgt->flags = 0; + return sii_probe_timeout(tgt); + } + /* else fall through */ + } + + if (tgt) + log(LOG_KERN, "Target %d was active, cmd x%x in x%x out x%x Sin x%x Sou x%x dmalen x%x\n", + tgt->target_id, tgt->cur_cmd, + tgt->transient_state.in_count, tgt->transient_state.out_count, + sii->in_count, sii->out_count, + sii->regs->sii_dma_len); + + sii->regs->sii_cmd = SII_CMD_RST; + delay(25); +} + +/* + * Copy routines that avoid odd pointers + */ +boolean_t nocopyin = FALSE; +careful_copyin_gap16(tgt, offset, len, isaobb, obb) + register target_info_t *tgt; + unsigned char obb; +{ + register char *from, *to; + register int count; + + count = tgt->transient_state.copy_count; + + from = tgt->dma_ptr + (offset << 1); + to = tgt->ior->io_data + count; + tgt->transient_state.copy_count = count + len; + if (count & 1) { + from -= (1 << 1); + to -= 1; + len += 1; + } +if (nocopyin) return;/*timing*/ + copyin_gap16( from, to, len); + /* check for last, poor little odd byte */ + if (isaobb) { + to += len; + to[-1] = obb; + } +} + +careful_copyout_gap16( tgt, offset, len) + register target_info_t *tgt; +{ + register char *from, *to; + register int count, olen; + unsigned char c; + char *p; + + count = tgt->ior->io_count - tgt->transient_state.copy_count; + if (count > 0) { + + len = u_min(count, len); + offset += tgt->transient_state.cmd_count; + + count = tgt->transient_state.copy_count; + tgt->transient_state.copy_count = count + len; + + from = tgt->ior->io_data + count; + to = tgt->dma_ptr + (offset << 1); + + /* the scsi buffer acts weirdo at times */ + if ((olen=len) & 1) { + p = tgt->dma_ptr + ((offset + olen - 1)<<1); + c = (*(unsigned short*)p) >> 8;/*!MSF*/ + } + + if (count & 1) { + from -= 1; + to -= (1 << 1); + len += 1; + } + + count = copyout_gap16(from, to, len); + + /* the scsi buffer acts weirdo at times */ + if (olen & 1) { + unsigned char cv; + cv = (*(unsigned short*)p) >> 8;/*!MSF*/ + if (c != cv) { + /* + * Scott Fahlman would say + * "Use a big plier!" + */ + unsigned short s; + volatile unsigned short *pp; + pp = (volatile unsigned short*)p; + s = (c << 8) | (from[len-1] & 0xff); + do { + *pp = s; + } while (*pp != s); + } + } + } +} + +#endif NSII > 0 + diff --git a/scsi/adapters/scsi_89352.h b/scsi/adapters/scsi_89352.h new file mode 100644 index 0000000..85c579f --- /dev/null +++ b/scsi/adapters/scsi_89352.h @@ -0,0 +1,231 @@ +/* + * Mach Operating System + * Copyright (c) 1991 Carnegie Mellon University + * All Rights Reserved. + * + * Permission to use, copy, modify and distribute this software and its + * documentation is hereby granted, provided that both the copyright + * notice and this permission notice appear in all copies of the + * software, derivative works or modified versions, and any portions + * thereof, and that both notices appear in supporting documentation. + * + * CARNEGIE MELLON ALLOWS FREE USE OF THIS SOFTWARE IN ITS "AS IS" + * CONDITION. CARNEGIE MELLON DISCLAIMS ANY LIABILITY OF ANY KIND FOR + * ANY DAMAGES WHATSOEVER RESULTING FROM THE USE OF THIS SOFTWARE. + * + * Carnegie Mellon requests users of this software to return to + * + * Software Distribution Coordinator or Software.Distribution@CS.CMU.EDU + * School of Computer Science + * Carnegie Mellon University + * Pittsburgh PA 15213-3890 + * + * any improvements or extensions that they make and grant Carnegie Mellon + * the rights to redistribute these changes. + */ +/* + * File: scsi_89352.h + * Author: Daniel Stodolsky, Carnegie Mellon University + * Date: 06/91 + * + * Defines for the Fujitsu MB89352 SCSI Protocol Controller (HBA) + * The definitions herein also cover the 89351, 87035/36, 87033B + */ + +/* + * Register map, padded as needed. Matches Hardware, do not screw up. + */ + +#define vuc volatile unsigned char + +typedef struct +{ + vuc spc_bdid; /* Bus Device ID (R/W) */ +#define spc_myid spc_bdid + char pad0[3]; + vuc spc_sctl; /* SPC Control register (R/W) */ + char pad1[3]; + vuc spc_scmd; /* Command Register (R/W) */ + char pad2[3]; + vuc spc_tmod; /* Transmit Mode Register (synch models) */ + char pad3[3]; + vuc spc_ints; /* Interrupt sense (R); Interrupt Reset (w) */ + char pad4[3]; + vuc spc_psns; /* Phase Sense (R); SPC Diagnostic Control (w) */ +#define spc_phase spc_psns + char pad5[3]; + vuc spc_ssts; /* SPC status (R/O) */ + char pad6[3]; + vuc spc_serr; /* SPC error status (R/O) */ + char pad7[3]; + vuc spc_pctl; /* Phase Control (R/W) */ + char pad8[3]; + vuc spc_mbc; /* Modifed Byte Counter (R/O) */ + char pad9[3]; + vuc spc_dreg; /* Data Register (R/W) */ + char pad10[3]; + vuc spc_temp; /* Temporary Register (R/W) */ + char pad11[3]; + vuc spc_tch; /* Transfer Counter High (R/W) */ + char pad12[3]; + vuc spc_tcm; /* Transfer Counter Middle (R/W) */ + char pad13[3]; + vuc spc_tcl; /* Transfer Counter Low (R/W) */ + char pad14[3]; + vuc spc_exbf; /* External Buffer (synch models) */ + char pad15[3]; +} spc_regmap_t; + +#undef vuc + +/* + * Control register + */ + +#define SPC_SCTL_DISABLE 0x80 +#define SPC_SCTL_RESET 0x40 +#define SPC_SCTL_DIAGMODE 0x20 +#define SPC_SCTL_ARB_EBL 0x10 +#define SPC_SCTL_PAR_EBL 0x08 +#define SPC_SCTL_SEL_EBL 0x04 +#define SPC_SCTL_RSEL_EBL 0x02 +#define SPC_SCTL_IE 0x01 + +/* + * Command register + */ + +#define SPC_SCMD_CMDMASK 0xe0 +# define SPC_SCMD_C_ACKREQ_S 0xe0 +# define SPC_SCMD_C_ACKREQ_C 0xc0 +# define SPC_SCMD_C_STOP_X 0xa0 +# define SPC_SCMD_C_XFER 0x80 +# define SPC_SCMD_C_ATN_S 0x60 +# define SPC_SCMD_C_ATN_C 0x40 +# define SPC_SCMD_C_SELECT 0x20 +# define SPC_SCMD_C_BUS_RLSE 0x00 +#define SPC_SCMD_BUSRST 0x10 +#define SPC_SCMD_INTERCEPT_X 0x08 +#define SPC_SCMD_PROGRAMMED_X 0x04 +#define SPC_SCMD_PAD_X 0x01 + +/* + * Transfer mode register (MB87033B/35/36) + */ + +#define SPC_TMOD_SYNC_X 0x80 +#define SPC_TMOD_OFFSET_MASK 0x70 +# define SPC_OFFSET(x) (((x)<<4)&SPC_TMOD_OFFSET_MASK) +#define SPC_TMOD_PERIOD_MASK 0xc0 +# define SPC_PERIOD(x) (((x)<<2)&SPC_TMOD_PERIOD_MASK) +#define SPC_TMOD_EXP_COUNTER 0x01 + +/* + * Interrupt cause register + */ + +#define SPC_INTS_SELECTED 0x80 +#define SPC_INTS_RESELECTED 0x40 +#define SPC_INTS_DISC 0x20 +#define SPC_INTS_DONE 0x10 +#define SPC_INTS_BUSREQ 0x08 +#define SPC_INTS_TIMEOUT 0x04 +#define SPC_INTS_ERROR 0x02 +#define SPC_INTS_RESET 0x01 + +/* + * SCSI Bus signals ("phase") + */ + +#define SPC_BUS_REQ 0x80 /* rw */ +#define SPC_BUS_ACK 0x40 /* rw */ +#define SPC_BUS_ATN 0x20 /* ro */ +# define SPC_DIAG_ENBL_XFER 0x20 /* wo */ +#define SPC_BUS_SEL 0x10 /* ro */ +#define SPC_BUS_BSY 0x08 /* rw */ +#define SPC_BUS_MSG 0x04 /* rw */ +#define SPC_BUS_CD 0x02 /* rw */ +#define SPC_BUS_IO 0x01 /* rw */ + +#define SPC_CUR_PHASE(x) SCSI_PHASE(x) + +#define SPC_BSY(r) (r->spc_phase & SPC_BUS_BSY) + +/* + * Chip status register + */ + +#define SPC_SSTS_INI_CON 0x80 +#define SPC_SSTS_TGT_CON 0x40 +#define SPC_SSTS_BUSY 0x20 +#define SPC_SSTS_XIP 0x10 +#define SPC_SSTS_RST 0x08 +#define SPC_SSTS_TC0 0x04 +#define SPC_SSTS_FIFO_FULL 0x02 +#define SPC_SSTS_FIFO_EMPTY 0x01 + +/* + * Error register + */ + +#define SPC_SERR_SEL 0x80 /* Selected */ +#define SPC_SERR_RSEL 0x40 /* Reselected */ +#define SPC_SERR_DISC 0x20 /* Disconnected */ +#define SPC_SERR_CMDC 0x10 /* Command Complete */ +#define SPC_SERR_SRVQ 0x08 /* Service Required */ +#define SPC_SERR_TIMO 0x04 /* Timeout */ +#define SPC_SERR_HARDERR 0x02 /* SPC Hard Error */ +#define SPC_SERR_RSTC 0x01 /* Reset Condition */ + +/* + * Phase control register + * + * [use SPC_CUR_PHASE() here too] + */ + +#define SPC_PCTL_BFREE_IE 0x80 /* Bus free (disconnected) */ +#define SPC_PCTL_LST_IE 0x40 /* lost arbit (87033) */ +#define SPC_PCTL_ATN_IE 0x20 /* ATN set (87033) */ +#define SPC_PCTL_RST_DIS 0x10 /* RST asserted */ + +/* + * Modified byte counter register + */ + +#define SPC_MBC_ECNT_MASK 0xf0 /* 87033 only */ +# define SPC_MBC_ECNT_GET(x) (((x)&SPC_MBC_ECNT_MASK)>>4) +# define SPC_MBC_ECNT_PUT(x) (((x)<<4)&SPC_MBC_ECNT_MASK) +#define SPC_MBC_MBC_MASK 0x0f +# define SPC_MBC_GET(x) ((x)&SPC_MBC_MBC_MASK) +# define SPC_MBC_PUT(x) ((x)&SPC_MBC_MBC_MASK) + +/* + * Transfer counter register(s) + */ + +#define SPC_TC_PUT(ptr,val) { \ + (ptr)->spc_tch = (((val)>>16)&0xff); \ + (ptr)->spc_tcm = (((val)>> 8)&0xff); \ + (ptr)->spc_tcl = (((val) )&0xff); \ + } + +#define SPC_TC_GET(ptr,val) { \ + (val) = (((ptr)->spc_tch & 0xff )<<16) |\ + (((ptr)->spc_tcm 0 0xff )<<8) |\ + ((ptr)->spc_tcl 0xff);\ + } + +/* 87033 in expanded mode */ +#define SPC_XTC_PUT(ptr,val) { \ + (ptr)->spc_mbc = SPC_MBC_ECNT_PUT(((val)>>24));\ + (ptr)->spc_tch = (((val)>>16)&0xff); \ + (ptr)->spc_tcm = (((val)>> 8)&0xff); \ + (ptr)->spc_tcl = (((val) )&0xff); \ + } + +#define SPC_XTC_GET(ptr,val) { \ + (val) = (SPC_MBC_ECNT_GET((ptr)->spc_mbc)<<24)|\ + (((ptr)->spc_tch)<<16)|(((ptr)->spc_tcm)<<8)|\ + ((ptr)->spc_tcl);\ + } + diff --git a/scsi/adapters/scsi_89352_hdw.c b/scsi/adapters/scsi_89352_hdw.c new file mode 100644 index 0000000..5672cb6 --- /dev/null +++ b/scsi/adapters/scsi_89352_hdw.c @@ -0,0 +1,2192 @@ +/* + * Mach Operating System + * Copyright (c) 1992,1991 Carnegie Mellon University + * All Rights Reserved. + * + * Permission to use, copy, modify and distribute this software and its + * documentation is hereby granted, provided that both the copyright + * notice and this permission notice appear in all copies of the + * software, derivative works or modified versions, and any portions + * thereof, and that both notices appear in supporting documentation. + * + * CARNEGIE MELLON ALLOWS FREE USE OF THIS SOFTWARE IN ITS "AS IS" + * CONDITION. CARNEGIE MELLON DISCLAIMS ANY LIABILITY OF ANY KIND FOR + * ANY DAMAGES WHATSOEVER RESULTING FROM THE USE OF THIS SOFTWARE. + * + * Carnegie Mellon requests users of this software to return to + * + * Software Distribution Coordinator or Software.Distribution@CS.CMU.EDU + * School of Computer Science + * Carnegie Mellon University + * Pittsburgh PA 15213-3890 + * + * any improvements or extensions that they make and grant Carnegie Mellon + * the rights to redistribute these changes. + */ +/* + * File: scsi_89352_hdw.c + * Author: Daniel Stodolsky, Carnegie Mellon University + * Date: 06/91 + * + * Bottom layer of the SCSI driver: chip-dependent functions + * + * This file contains the code that is specific to the Fujitsu MB89352 + * SCSI chip (Host Bus Adapter in SCSI parlance): probing, start + * operation, and interrupt routine. + */ + +/* + * This layer works based on small simple 'scripts' that are installed + * at the start of the command and drive the chip to completion. + * The idea comes from the specs of the NCR 53C700 'script' processor. + * + * There are various reasons for this, mainly + * - Performance: identify the common (successful) path, and follow it; + * at interrupt time no code is needed to find the current status + * - Code size: it should be easy to compact common operations + * - Adaptability: the code skeleton should adapt to different chips without + * terrible complications. + * - Error handling: and it is easy to modify the actions performed + * by the scripts to cope with strange but well identified sequences + * + */ + +/* + * + * + * Known Headaches/Features with this chip. + * + * (1) After the interrupt raised by select, the phase sense (psns) + * and SPC status (ssts) registers do not display the correct values + * until the REQ line (via psns) is high. (danner@cs.cmu.edu 6/11/91) + * + * (2) After a data in phase, the command complete interrupt may be raised + * before the psns, ssts, and transfer counter registers settle. The reset + * acknowledge or request command should not be issued until they settle. + * (danner@cs.cmu.edu 6/14/91) + * + * (3) In general, an interrupt can be raised before the psns and ssts have + * meaningful values. One should wait for the psns to show the REQ bit (0x80) + * set before expecting meaningful values, with the exception of (2) above. + * Currently this is handled by spc_err_generic ("Late REQ"). (This problem + * is really a refinement of (1)). (danner@cs.cmu.edu 6/14/91) + * + * (4) When issuing a multibyte command after a select with attention, + * The chip will automatically drop ATN before sending the last byte of the + * message, in accordance with the ANSI SCSI standard. This requires, of course, + * the transfer counter be an accurate representation of the amount of data to be + * transfered. (danner@cs.cmu.edu 6/14/91) + * + */ + +#if 0 + +#include + +#include + +#if NSCSI > 0 + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include /*4proto*/ +#include /*4proto*/ + +#ifdef LUNA88K +#include +#define SPC_DEFAULT_ADDRESS (caddr_t) SCSI_ADDR +#endif + +#ifndef SPC_DEFAULT_ADDRESS /* cross compile check */ +#define SPC_DEFAULT_ADDRESS (caddr_t) 0 +#endif + + +/* external/forward declarations */ +int spc_probe(), spc_slave(), spc_attach(), scsi_go(); +void spc_reset(), spc_attempt_selection(), spc_target_intr(), spc_bus_reset(); +/* + * Statically allocated command & temp buffers + * This way we can attach/detach drives on-fly + */ +#define PER_TGT_BUFF_DATA 256 + +static char spc_buffer[NSCSI * 8 * PER_TGT_BUFF_DATA]; + +/* + * Macros to make certain things a little more readable + */ + +/* + wait for the desired phase to appear, but make sure the REQ bit set in the psns + (otherwise the values tend to float/be garbage. +*/ + +#define SPC_WAIT_PHASE(p) while(((regs->spc_psns & (SPC_BUS_REQ|SCSI_PHASE_MASK))) \ + != (SPC_BUS_REQ|(p))) + +/* + wait until a phase different than p appears in the psns. Since it is only valid + when the REQ bit is set, don't test unless REQ bit is set. So spin until + REQ is high or the phase is not p. +*/ + +#define SPC_WAIT_PHASE_VANISH(p) while(1) { int _psns_ = regs->spc_psns; \ + if ((_psns_ & SPC_BUS_REQ) && (_psns_ & SCSI_PHASE_MASK)!=p) break; } + + + +/* ?? */ +/* #define SPC_ACK(ptr,phase) (ptr)->spc_pctl = (phase) */ + +/* + * A script has a two parts: a pre-condition and an action. + * The first triggers error handling if not satisfied and in + * our case it is formed by the current bus phase and connected + * condition as per bus status bits. The action part is just a + * function pointer, invoked in a standard way. The script + * pointer is advanced only if the action routine returns TRUE. + * See spc_intr() for how and where this is all done. + */ + +typedef struct script { + char condition; /* expected state at interrupt */ + int (*action)(); /* action routine */ +} *script_t; + +#define SCRIPT_MATCH(psns) (SPC_CUR_PHASE((psns))|((psns) & SPC_BUS_BSY)) + +/* ?? */ +#define SPC_PHASE_DISC 0x0 /* sort of .. */ + +/* The active script is in the state expected right after the issue of a select */ + +#define SCRIPT_SELECT(scp) (scp->action == spc_issue_command || \ + scp->action == spc_issue_ident_and_command) + +/* forward decls of script actions */ +boolean_t + spc_dosynch(), /* negotiate synch xfer */ + spc_xfer_in(), /* get data from target via dma */ + spc_xfer_out(), /* send data to target via dma */ + spc_get_status(), /* get status from target */ + spc_end_transaction(), /* all come to an end */ + spc_msg_in(), /* get disconnect message(s) */ + spc_issue_command(), /* spit on the bus */ + spc_issue_ident_and_command(), /* spit on the bus (with ATN) */ + spc_disconnected(); /* current target disconnected */ +/* forward decls of error handlers */ +boolean_t + spc_err_generic(), /* generic error handler */ + spc_err_disconn(); /* when a target disconnects */ +void gimmeabreak(); /* drop into the debugger */ + +void spc_reset_scsibus(); +boolean_t spc_probe_target(); + +scsi_ret_t spc_select_target(); + +/* + * State descriptor for this layer. There is one such structure + * per (enabled) 89352 chip + */ +struct spc_softc { + watchdog_t wd; + spc_regmap_t *regs; /* 5380 registers */ + char *buff; /* scratch buffer memory */ + char *data_ptr; /* orig/dest memory */ + script_t script; + int (*error_handler)(); + int in_count; /* amnt we expect to receive */ + int out_count; /* amnt we are going to ship */ + + volatile char state; +#define SPC_STATE_BUSY 0x01 /* selecting or currently connected */ +#define SPC_STATE_TARGET 0x04 /* currently selected as target */ +#define SPC_STATE_COLLISION 0x08 /* lost selection attempt */ +#define SPC_STATE_DMA_IN 0x10 /* tgt --> initiator xfer */ + + unsigned char ntargets; /* how many alive on this scsibus */ + unsigned char done; + unsigned char xxxx; + + scsi_softc_t *sc; + target_info_t *active_target; + + target_info_t *next_target; /* trying to seize bus */ + queue_head_t waiting_targets;/* other targets competing for bus */ + decl_simple_lock_data(,chiplock) /* Interlock */ +} spc_softc_data[NSCSI]; + +typedef struct spc_softc *spc_softc_t; + +spc_softc_t spc_softc[NSCSI]; + +/* + * Definition of the controller for the auto-configuration program. + */ + +int spc_probe(), scsi_slave(), spc_go(); +void spc_intr(); +void scsi_attach(); + +vm_offset_t spc_std[NSCSI] = { SPC_DEFAULT_ADDRESS }; + +struct bus_device *spc_dinfo[NSCSI*8]; +struct bus_ctlr *spc_minfo[NSCSI]; +struct bus_driver spc_driver = + { spc_probe, scsi_slave, scsi_attach, spc_go, spc_std, "rz", spc_dinfo, + "spc", spc_minfo, BUS_INTR_B4_PROBE}; + +/* + * Scripts + */ + +struct script +spc_script_data_in[] = { + { SCSI_PHASE_CMD|SPC_BUS_BSY, spc_issue_command}, + { SCSI_PHASE_DATAI|SPC_BUS_BSY, spc_xfer_in}, + { SCSI_PHASE_STATUS|SPC_BUS_BSY, spc_get_status}, + { SCSI_PHASE_MSG_IN|SPC_BUS_BSY, spc_end_transaction} +}, + +spc_script_late_data_in[] = { + { SCSI_PHASE_MSG_OUT|SPC_BUS_BSY, spc_issue_ident_and_command}, + { SCSI_PHASE_DATAI|SPC_BUS_BSY, spc_xfer_in}, + { SCSI_PHASE_STATUS|SPC_BUS_BSY, spc_get_status}, + { SCSI_PHASE_MSG_IN|SPC_BUS_BSY, spc_end_transaction} +}, + +spc_script_data_out[] = { + { SCSI_PHASE_CMD|SPC_BUS_BSY, spc_issue_command}, + { SCSI_PHASE_DATAO|SPC_BUS_BSY, spc_xfer_out}, + { SCSI_PHASE_STATUS|SPC_BUS_BSY, spc_get_status}, + { SCSI_PHASE_MSG_IN|SPC_BUS_BSY, spc_end_transaction} +}, + + +spc_script_late_data_out[] = { + { SCSI_PHASE_MSG_OUT|SPC_BUS_BSY, spc_issue_ident_and_command}, + { SCSI_PHASE_DATAO|SPC_BUS_BSY, spc_xfer_out}, + { SCSI_PHASE_STATUS|SPC_BUS_BSY, spc_get_status}, + { SCSI_PHASE_MSG_IN|SPC_BUS_BSY, spc_end_transaction} +}, + + +spc_script_cmd[] = { + { SCSI_PHASE_CMD|SPC_BUS_BSY, spc_issue_command}, + { SCSI_PHASE_STATUS|SPC_BUS_BSY, spc_get_status}, + { SCSI_PHASE_MSG_IN|SPC_BUS_BSY, spc_end_transaction} +}, + +spc_script_late_cmd[] = { + { SCSI_PHASE_MSG_OUT|SPC_BUS_BSY, spc_issue_ident_and_command}, + { SCSI_PHASE_STATUS|SPC_BUS_BSY, spc_get_status}, + { SCSI_PHASE_MSG_IN|SPC_BUS_BSY, spc_end_transaction} +}, + +/* Synchronous transfer neg(oti)ation */ + +spc_script_try_synch[] = { + { SCSI_PHASE_MSG_OUT|SPC_BUS_BSY, spc_dosynch} +}, + +/* Disconnect sequence */ + +spc_script_disconnect[] = { + { SPC_PHASE_DISC, spc_disconnected} +}; + + + +#define u_min(a,b) (((a) < (b)) ? (a) : (b)) + + +#define DEBUG +#ifdef DEBUG + +int spc_state(base) + vm_offset_t base; +{ + register spc_regmap_t *regs; + + if (base == 0) + base = (vm_offset_t) SPC_DEFAULT_ADDRESS; + + regs = (spc_regmap_t*) (base); + + db_printf("spc_bdid (bus device #): %x\n",regs->spc_bdid); + db_printf("spc_sctl (spc internal control): %x\n",regs->spc_sctl); + db_printf("spc_scmd (scp command): %x\n",regs->spc_scmd); + db_printf("spc_ints (spc interrupt): %x\n",regs->spc_ints); + db_printf("spc_psns (scsi bus phase): %x\n",regs->spc_psns); + db_printf("spc_ssts (spc internal status): %x\n",regs->spc_ssts); + db_printf("spc_serr (spc internal err stat): %x\n",regs->spc_serr); + db_printf("spc_pctl (scsi transfer phase): %x\n",regs->spc_pctl); + db_printf("spc_mbc (spc transfer data ct): %x\n",regs->spc_mbc); +/* db_printf("spc_dreg (spc transfer data r/w): %x\n",regs->spc_dreg);*/ + db_printf("spc_temp (scsi data bus control): %x\n",regs->spc_temp); + db_printf("spc_tch (transfer byte ct (MSB): %x\n",regs->spc_tch); + db_printf("spc_tcm (transfer byte ct (2nd): %x\n",regs->spc_tcm); + db_printf("spc_tcl (transfer byte ct (LSB): %x\n",regs->spc_tcl); + + return 0; +} + +int spc_target_state(tgt) + target_info_t *tgt; +{ + if (tgt == 0) + tgt = spc_softc[0]->active_target; + if (tgt == 0) + return 0; + db_printf("fl %x dma %x+%x cmd %x id %x per %x off %x ior %x ret %x\n", + tgt->flags, tgt->dma_ptr, tgt->transient_state.dma_offset, + tgt->cmd_ptr, tgt->target_id, tgt->sync_period, tgt->sync_offset, + tgt->ior, tgt->done); + if (tgt->flags & TGT_DISCONNECTED){ + script_t spt; + + spt = tgt->transient_state.script; + db_printf("disconnected at "); + db_printsym((db_expr_t)spt,1); + db_printf(": %x ", spt->condition); + db_printsym((db_expr_t)spt->action,1); + db_printf(", "); + db_printsym((db_expr_t)tgt->transient_state.handler, 1); + db_printf("\n"); + } + + return 0; +} + +void spc_all_targets(unit) +int unit; +{ + int i; + target_info_t *tgt; + for (i = 0; i < 8; i++) { + tgt = spc_softc[unit]->sc->target[i]; + if (tgt) + spc_target_state(tgt); + } +} + +int spc_script_state(unit) +int unit; +{ + script_t spt = spc_softc[unit]->script; + + if (spt == 0) return 0; + db_printsym((db_expr_t)spt,1); + db_printf(": %x ", spt->condition); + db_printsym((db_expr_t)spt->action,1); + db_printf(", "); + db_printsym((db_expr_t)spc_softc[unit]->error_handler, 1); + return 0; +} + +#define PRINT(x) if (scsi_debug) printf x + +#define TRMAX 200 +int tr[TRMAX+3]; +int trpt, trpthi; +#define TR(x) tr[trpt++] = x +#define TRWRAP trpthi = trpt; trpt = 0; +#define TRCHECK if (trpt > TRMAX) {TRWRAP} + +#define TRACE + +#ifdef TRACE + +#define LOGSIZE 256 +int spc_logpt; +int spc_log[LOGSIZE]; + +#define MAXLOG_VALUE 0x30 +struct { + char *name; + unsigned int count; +} logtbl[MAXLOG_VALUE]; + +static void LOG(e,f) + int e; + char *f; +{ + spc_log[spc_logpt++] = (e); + if (spc_logpt == LOGSIZE) spc_logpt = 0; + if ((e) < MAXLOG_VALUE) { + logtbl[(e)].name = (f); + logtbl[(e)].count++; + } +} + +int spc_print_log(skip) + int skip; +{ + register int i, j; + register unsigned int c; + + for (i = 0, j = spc_logpt; i < LOGSIZE; i++) { + c = spc_log[j]; + if (++j == LOGSIZE) j = 0; + if (skip-- > 0) + continue; + if (c < MAXLOG_VALUE) + db_printf(" %s", logtbl[c].name); + else + db_printf("-0x%x", c - 0x80); + } + db_printf("\n"); + return 0; +} + +void spc_print_stat() +{ + register int i; + register char *p; + for (i = 0; i < MAXLOG_VALUE; i++) { + if (p = logtbl[i].name) + printf("%d %s\n", logtbl[i].count, p); + } +} + +#else /* TRACE */ +#define LOG(e,f) +#endif /* TRACE */ + +#else /* DEBUG */ +#define PRINT(x) +#define LOG(e,f) +#define TR(x) +#define TRCHECK +#define TRWRAP +#endif /* DEBUG */ + + +/* + * Probe/Slave/Attach functions + */ + +/* + * Probe routine: + * Should find out (a) if the controller is + * present and (b) which/where slaves are present. + * + * Implementation: + * Send an identify msg to each possible target on the bus + * except of course ourselves. + */ +int spc_probe(reg, ui) + char *reg; + struct bus_ctlr *ui; +{ + int tmp; + int unit = ui->unit; + spc_softc_t spc = &spc_softc_data[unit]; + int target_id, i; + scsi_softc_t *sc; + register spc_regmap_t *regs; + int s; + boolean_t did_banner = FALSE; + char *cmd_ptr; + + /* + * We are only called if the chip is there, + * but make sure anyways.. + */ + regs = (spc_regmap_t *) (reg); + if (check_memory((unsigned)regs, 0)) + return 0; + +#if notyet + /* Mappable version side */ + SPC_probe(reg, ui); +#endif + + /* + * Initialize hw descriptor + */ + spc_softc[unit] = spc; + spc->regs = regs; + spc->buff = spc_buffer; + + queue_init(&spc->waiting_targets); + + simple_lock_init(&spc->chiplock); + + sc = scsi_master_alloc(unit, (char*)spc); + spc->sc = sc; + + sc->go = spc_go; + sc->probe = spc_probe_target; + sc->watchdog = scsi_watchdog; + spc->wd.reset = spc_reset_scsibus; + +#ifdef MACH_KERNEL + sc->max_dma_data = -1; /* unlimited */ +#else + sc->max_dma_data = scsi_per_target_virtual; +#endif + + scsi_might_disconnect[unit] = 0; /* XXX for now */ + + /* + * Reset chip + */ + s = splbio(); + spc_reset(regs, TRUE); + tmp = regs->spc_ints = regs->spc_ints; + + /* + * Our SCSI id on the bus. + */ + + sc->initiator_id = bdid_to_id(regs->spc_bdid); + printf("%s%d: my SCSI id is %d", ui->name, unit, sc->initiator_id); + + /* + * For all possible targets, see if there is one and allocate + * a descriptor for it if it is there. + */ + cmd_ptr = spc_buffer; + for (target_id = 0; target_id < 8; target_id++, cmd_ptr += PER_TGT_BUFF_DATA) { + + register unsigned csr, ints; + scsi_status_byte_t status; + + /* except of course ourselves */ + if (target_id == sc->initiator_id) + continue; + + if (spc_select_target( regs, sc->initiator_id, target_id, FALSE) + == SCSI_RET_DEVICE_DOWN) { + tmp = regs->spc_ints = regs->spc_ints; + continue; + } + + printf(",%s%d", did_banner++ ? " " : " target(s) at ", + target_id); + + /* should be command phase here: we selected wo ATN! */ + SPC_WAIT_PHASE(SCSI_PHASE_CMD); + + SPC_ACK(regs,SCSI_PHASE_CMD); + + /* build command in buffer */ + { + unsigned char *p = (unsigned char*) cmd_ptr; + + p[0] = SCSI_CMD_TEST_UNIT_READY; + p[1] = + p[2] = + p[3] = + p[4] = + p[5] = 0; + } + + spc_data_out(regs, SCSI_PHASE_CMD, 6, cmd_ptr); + + SPC_WAIT_PHASE(SCSI_PHASE_STATUS); + + /* should have recieved a Command Complete Interrupt */ + while (!(regs->spc_ints)) + delay(1); + ints = regs->spc_ints; + if (ints != (SPC_INTS_DONE)) + gimmeabreak(); + regs->spc_ints = ints; + + SPC_ACK(regs,SCSI_PHASE_STATUS); + + csr = spc_data_in(regs, SCSI_PHASE_STATUS, 1, &status.bits); + LOG(0x25,"din_count"); + LOG(0x80+csr,0); + + if (status.st.scsi_status_code != SCSI_ST_GOOD) + scsi_error( 0, SCSI_ERR_STATUS, status.bits, 0); + + /* expect command complete interupt */ + while (!(regs->spc_ints & SPC_INTS_DONE)) + delay(1); + + /* clear all intr bits */ + tmp = regs->spc_ints; + LOG(0x26,"ints"); + LOG(0x80+tmp,0); + regs->spc_ints = SPC_INTS_DONE; + + /* get cmd_complete message */ + SPC_WAIT_PHASE(SCSI_PHASE_MSG_IN); + + SPC_ACK(regs,SCSI_PHASE_MSG_IN); + + csr = spc_data_in(regs,SCSI_PHASE_MSG_IN, 1,(unsigned char*)&i); + LOG(0x25,"din_count"); + LOG(0x80+csr,0); + + while (!(regs->spc_ints & SPC_INTS_DONE)) + delay(1); + + /* clear all done intr */ + tmp = regs->spc_ints; + LOG(0x26,"ints"); + LOG(0x80+tmp,0); + regs->spc_ints = SPC_INTS_DONE; + + SPC_ACK(regs,SPC_PHASE_DISC); + + /* release the bus */ + regs->spc_pctl = ~SPC_PCTL_BFREE_IE & SPC_PHASE_DISC; + /* regs->spc_scmd = 0; only in TARGET mode */ + + /* wait for disconnected interrupt */ + while (!(regs->spc_ints & SPC_INTS_DISC)) + delay(1); + + tmp = regs->spc_ints; + LOG(0x26,"ints"); + LOG(0x80+tmp,0); + regs->spc_ints = tmp; + LOG(0x29,"Probed\n"); + + /* + * Found a target + */ + spc->ntargets++; + { + register target_info_t *tgt; + + tgt = scsi_slave_alloc(unit, target_id, (char*)spc); + + /* "virtual" address for our use */ + tgt->cmd_ptr = cmd_ptr; + /* "physical" address for dma engine (??) */ + tgt->dma_ptr = 0; +#ifdef MACH_KERNEL +#else /*MACH_KERNEL*/ + fdma_init(&tgt->fdma, scsi_per_target_virtual); +#endif /*MACH_KERNEL*/ + } + } + printf(".\n"); + + splx(s); + return 1; +} + +boolean_t +spc_probe_target(tgt, ior) + target_info_t *tgt; + io_req_t ior; +{ + boolean_t newlywed; + + newlywed = (tgt->cmd_ptr == 0); + if (newlywed) { + /* desc was allocated afresh */ + + /* "virtual" address for our use */ + tgt->cmd_ptr = &spc_buffer[PER_TGT_BUFF_DATA*tgt->target_id + + (tgt->masterno*8*PER_TGT_BUFF_DATA) ]; + /* "physical" address for dma engine */ + tgt->dma_ptr = 0; +#ifdef MACH_KERNEL +#else /*MACH_KERNEL*/ + fdma_init(&tgt->fdma, scsi_per_target_virtual); +#endif /*MACH_KERNEL*/ + + } + + if (scsi_inquiry(tgt, SCSI_INQ_STD_DATA) == SCSI_RET_DEVICE_DOWN) + return FALSE; + + tgt->flags = TGT_ALIVE; + return TRUE; +} + +int bdid_to_id(bdid) + register int bdid; +{ + register int i; + for (i = 0; i < 8; i++) + if (bdid == (1 << i)) break; + return i; +} + +scsi_ret_t +spc_select_target(regs, myid, id, with_atn) + register spc_regmap_t *regs; + unsigned myid, id; + boolean_t with_atn; +{ + scsi_ret_t ret = SCSI_RET_RETRY; + int mask; + + if ((regs->spc_phase & (SPC_BUS_BSY|SPC_BUS_SEL)) +#ifdef MIPS + && (regs->spc_phase & (SPC_BUS_BSY|SPC_BUS_SEL)) + && (regs->spc_phase & (SPC_BUS_BSY|SPC_BUS_SEL)) +#endif + ) + return ret; + + /* setup for for select: + +#if 0 + (1) Toggle the Enable transfer bit (turning on the chips + SCSI bus drivers). +#endif + (2) Enable arbitration, parity, reselect display, but + disable interrupt generation to the CPU (we are polling). + (3) Disable the bus free interrupt and set I/O direction + (4) If doing a select with attention, write the Set attention command. + Then delay 1 microsecond to avoid command races. + + (5) Temp register gets 1<spc_psns = SPC_DIAG_ENBL_XFER; /* (1) */ +#endif + + regs->spc_sctl = SPC_SCTL_ARB_EBL| + SPC_SCTL_PAR_EBL| + SPC_SCTL_RSEL_EBL; /* (2) */ + + + + mask = ~SPC_PCTL_BFREE_IE & regs->spc_pctl; + mask &= ~1; /* set I/O direction to be out */ + + regs->spc_pctl = mask; /* (3) */ + + if (with_atn) + { + regs->spc_scmd = SPC_SCMD_C_ATN_S; /* (4) */ + delay(1); + } + + regs->spc_temp = (1<spc_scmd = (SPC_SCMD_C_SELECT | SPC_SCMD_PROGRAMMED_X); /* (7) */ + + { + int count = 2500; + + /* wait for an interrupt */ + while ((regs->spc_ints)==0) + { + if (--count > 0) + delay(100); + else + { + goto nodev; + } + } + + count = regs->spc_ints; + if (count & SPC_INTS_TIMEOUT) + { + /* sanity check. The ssts should have the busy bit set */ + if (regs->spc_ssts & SPC_SSTS_BUSY) + goto nodev; + else + panic("spc_select_target: timeout"); + } + + /* otherwise, we should have received a + command complete interrupt */ + + if (count & ~SPC_INTS_DONE) + panic("spc_select_target"); + + } /* (8) */ + + /* we got a response - now connected; bus is in COMMAND phase */ + + regs->spc_ints = regs->spc_ints; + /* regs->spc_scmd = 0; target only */ + return SCSI_RET_SUCCESS; +nodev: + SPC_TC_PUT(regs,0); /* play it safe */ + regs->spc_ints = regs->spc_ints; + /* regs->spc_scmd = 0; target only */ + ret = SCSI_RET_DEVICE_DOWN; + return ret; +} + +int spc_data_out(regs, phase, count, data) + int phase, count; + register spc_regmap_t *regs; + unsigned char *data; +{ + /* This is the one that sends data out. returns how many + bytes it did NOT xfer: */ + + if (SPC_CUR_PHASE(regs->spc_phase) != phase) + return count; + + /* check that the fifo is empty. If not, cry */ + if (!(regs->spc_ssts & SPC_SSTS_FIFO_EMPTY)) + panic("spc_data_out: junk in fifo\n"); + + SPC_TC_PUT(regs,count); + regs->spc_scmd = SPC_SCMD_C_XFER | SPC_SCMD_PROGRAMMED_X; + + /* wait for the SPC to start processing the command */ + while ((regs->spc_ssts & (SPC_SSTS_INI_CON|SPC_SSTS_TGT_CON|SPC_SSTS_BUSY|SPC_SSTS_XIP)) + != (SPC_SSTS_INI_CON|SPC_SSTS_BUSY|SPC_SSTS_XIP)) + delay(1); + + /* shovel out the data */ + + while (count) + { + /* check if interrupt is pending */ + int ints = regs->spc_ints; + int ssts; + + if (ints) /* something has gone wrong */ + break; + + ssts = regs->spc_ssts; + if (ssts & SPC_SSTS_FIFO_FULL) /* full fifo - can't write */ + delay(1); + else + { /* spit out a byte */ + regs->spc_dreg = *data; + data++; + count--; + } + } + + + if (count != 0) + { + /* need some sort of fifo cleanup if failed */ + gimmeabreak(); /* Bytes stranded in the fifo */ + } + + return count; +} + +int spc_data_in(regs, phase, count, data) + int phase, count; + register spc_regmap_t *regs; + unsigned char *data; +{ + if (SPC_CUR_PHASE(regs->spc_phase) != phase) + return count; + + SPC_TC_PUT(regs,count); + regs->spc_scmd = SPC_SCMD_C_XFER | SPC_SCMD_PROGRAMMED_X; + + /* The Fujistu code sample suggests waiting for the top nibble of the SSTS to + become 0xb (ssts & 0xf0) = 0xb. This state, however is transient. If the + message is short (say , 1 byte), it can get sucked into the fifo before + we ever get to look at the state. So instead, we are going to wait for + the fifo to become nonempty. + */ + + while ((regs->spc_ssts & SPC_SSTS_FIFO_EMPTY)) + delay(1); + + while (count) + { + int ints = regs->spc_ints; + int ssts; + + /* If there is an interrupt pending besides command complete or + phase mismatch, give up */ + + if (ints & ~(SPC_INTS_DONE|SPC_INTS_BUSREQ)) + break; + + /* see if there is any data in the fifo */ + ssts = regs->spc_ssts; + if ((ssts & SPC_SSTS_FIFO_EMPTY) == 0) + { + *data = regs->spc_dreg; + data++; + count--; + continue; + } + + /* if empty, check if phase has changed */ + if (SPC_CUR_PHASE(regs->spc_phase) != phase) + break; + + } + + if ((count==0) && (phase == SCSI_PHASE_MSG_IN)) + { + while (!(regs->spc_ints & SPC_INTS_DONE)) + delay(1); + + /* + So the command complete interrupt has arrived. Now check that the + other two conditions we expect - The psns to be in ack|busy|message_in phase + and ssts to indicate connected|xfer in progress|busy|xfer counter 0|empty fifo + are true. + */ + while (1) + { + register int psns = regs->spc_psns; + register int ssts = regs->spc_ssts; + register int sscon = ssts & (SPC_SSTS_INI_CON | SPC_SSTS_TGT_CON); + register int ssncon = ssts & ~(SPC_SSTS_INI_CON | SPC_SSTS_TGT_CON); + + if (psns == (SPC_BUS_ACK | SPC_BUS_BSY | SCSI_PHASE_MSG_IN) && + ssncon == (SPC_SSTS_BUSY | SPC_SSTS_XIP | SPC_SSTS_TC0 | SPC_SSTS_FIFO_EMPTY) && + sscon) + break; + } + + regs->spc_scmd = SPC_SCMD_C_ACKREQ_C; + } + + return count; +} + +void spc_reset(regs, quickly) + register spc_regmap_t *regs; + boolean_t quickly; +{ + register char myid; + + /* save our id across reset */ + myid = bdid_to_id(regs->spc_bdid); + + /* wait for Reset In signal to go low */ + while (regs->spc_ssts & SPC_SSTS_RST) + delay(1); + + /* reset chip */ + regs->spc_sctl = SPC_SCTL_RESET; + delay(25); + + regs->spc_myid = myid; + regs->spc_sctl = SPC_SCTL_ARB_EBL|SPC_SCTL_PAR_EBL|SPC_SCTL_SEL_EBL| + SPC_SCTL_RSEL_EBL|SPC_SCTL_IE; + regs->spc_scmd = SPC_SCMD_C_BUS_RLSE; + /* regs->spc_tmod = 0; - SANDRO ? */ + regs->spc_ints = 0xff;/* clear off any pending */ +#if 0 + regs->spc_pctl = SPC_PCTL_LST_IE; /* useful only on 87033 */ +#else + regs->spc_pctl = 0; +#endif + regs->spc_mbc = 0; + SPC_TC_PUT(regs,0); + + if (quickly) + return; + + /* + * reset the scsi bus, the interrupt routine does the rest + * or you can call spc_bus_reset(). + */ + regs->spc_scmd = SPC_SCMD_BUSRST|SPC_SCMD_C_STOP_X;/*?*/ +} + +/* + * Operational functions + */ + +/* + * Start a SCSI command on a target + */ +spc_go(tgt, cmd_count, in_count, cmd_only) + int cmd_count, in_count; + target_info_t *tgt; + boolean_t cmd_only; +{ + spc_softc_t spc; + register int s; + boolean_t disconn; + script_t scp; + boolean_t (*handler)(); + int late; + + LOG(1,"\n\tgo"); + + spc = (spc_softc_t)tgt->hw_state; + + /* + * We cannot do real DMA. + */ +#ifdef MACH_KERNEL +#else /*MACH_KERNEL*/ + if (tgt->ior) + fdma_map(&tgt->fdma, tgt->ior); +#endif /*MACH_KERNEL*/ + + if ((tgt->cur_cmd == SCSI_CMD_WRITE) || + (tgt->cur_cmd == SCSI_CMD_LONG_WRITE)){ + io_req_t ior = tgt->ior; + register int len = ior->io_count; + + tgt->transient_state.out_count = len; + tgt->transient_state.copy_count = 0; + + if (len < tgt->block_size) { + gimmeabreak(); + + /* avoid leaks */ +#if 0 +you`ll have to special case this +#endif + tgt->transient_state.out_count = tgt->block_size; + } + } else { + tgt->transient_state.out_count = 0; + tgt->transient_state.copy_count = 0; + } + + tgt->transient_state.cmd_count = cmd_count; + + disconn = + BGET(scsi_might_disconnect,(unsigned)tgt->masterno, tgt->target_id); + disconn = disconn && (spc->ntargets > 1); + disconn |= + BGET(scsi_should_disconnect,(unsigned)tgt->masterno, tgt->target_id); + + /* + * Setup target state + */ + tgt->done = SCSI_RET_IN_PROGRESS; + + handler = (disconn) ? spc_err_disconn : spc_err_generic; + + /* determine wether or not to use the late forms of the scripts */ + late = cmd_only ? FALSE : (tgt->flags & TGT_DID_SYNCH); + + switch (tgt->cur_cmd) { + case SCSI_CMD_READ: + case SCSI_CMD_LONG_READ: + LOG(0x13,"readop"); + scp = late ? spc_script_late_data_in : spc_script_data_in; + break; + case SCSI_CMD_WRITE: + case SCSI_CMD_LONG_WRITE: + LOG(0x14,"writeop"); + scp = late ? spc_script_late_data_out : spc_script_data_out; + break; + case SCSI_CMD_INQUIRY: + /* This is likely the first thing out: + do the synch neg if so */ + if (!cmd_only && ((tgt->flags&TGT_DID_SYNCH)==0)) { + scp = spc_script_try_synch; + tgt->flags |= TGT_TRY_SYNCH; + break; + } + case SCSI_CMD_REQUEST_SENSE: + case SCSI_CMD_MODE_SENSE: + case SCSI_CMD_RECEIVE_DIAG_RESULTS: + case SCSI_CMD_READ_CAPACITY: + case SCSI_CMD_READ_BLOCK_LIMITS: + case SCSI_CMD_READ_TOC: + case SCSI_CMD_READ_SUBCH: + case SCSI_CMD_READ_HEADER: + case 0xc4: /* despised: SCSI_CMD_DEC_PLAYBACK_STATUS */ + case 0xc6: /* despised: SCSI_CMD_TOSHIBA_READ_SUBCH_Q */ + case 0xc7: /* despised: SCSI_CMD_TOSHIBA_READ_TOC_ENTRY */ + case 0xdd: /* despised: SCSI_CMD_NEC_READ_SUBCH_Q */ + case 0xde: /* despised: SCSI_CMD_NEC_READ_TOC */ + scp = late ? spc_script_late_data_in : spc_script_data_in; + LOG(0x1c,"cmdop"); + LOG(0x80+tgt->cur_cmd,0); + break; + case SCSI_CMD_MODE_SELECT: + case SCSI_CMD_REASSIGN_BLOCKS: + case SCSI_CMD_FORMAT_UNIT: + case 0xc9: /* vendor-spec: SCSI_CMD_DEC_PLAYBACK_CONTROL */ + tgt->transient_state.cmd_count = sizeof_scsi_command(tgt->cur_cmd); + tgt->transient_state.out_count = + cmd_count - tgt->transient_state.cmd_count; + scp = late ? spc_script_late_data_out : spc_script_data_out; + LOG(0x1c,"cmdop"); + LOG(0x80+tgt->cur_cmd,0); + break; + case SCSI_CMD_TEST_UNIT_READY: + /* + * Do the synch negotiation here, unless prohibited + * or done already + */ + if (tgt->flags & TGT_DID_SYNCH) { + scp = late ? spc_script_late_cmd : spc_script_cmd; + } else { + scp = spc_script_try_synch; + tgt->flags |= TGT_TRY_SYNCH; + cmd_only = FALSE; + } + LOG(0x1c,"cmdop"); + LOG(0x80+tgt->cur_cmd,0); + break; + default: + LOG(0x1c,"cmdop"); + LOG(0x80+tgt->cur_cmd,0); + scp = late ? spc_script_late_cmd : spc_script_cmd; + } + + tgt->transient_state.script = scp; + tgt->transient_state.handler = handler; + tgt->transient_state.identify = (cmd_only) ? 0xff : + (disconn ? SCSI_IDENTIFY|SCSI_IFY_ENABLE_DISCONNECT : + SCSI_IDENTIFY); + + if (in_count) + tgt->transient_state.in_count = + (in_count < tgt->block_size) ? tgt->block_size : in_count; + else + tgt->transient_state.in_count = 0; + tgt->transient_state.dma_offset = 0; + + /* + * See if another target is currently selected on + * this SCSI bus, e.g. lock the spc structure. + * Note that it is the strategy routine's job + * to serialize ops on the same target as appropriate. + */ +#if 0 +locking code here +#endif + s = splbio(); + + if (spc->wd.nactive++ == 0) + spc->wd.watchdog_state = SCSI_WD_ACTIVE; + + if (spc->state & SPC_STATE_BUSY) { + /* + * Queue up this target, note that this takes care + * of proper FIFO scheduling of the scsi-bus. + */ + LOG(3,"enqueue"); + enqueue_tail(&spc->waiting_targets, (queue_entry_t) tgt); + } else { + /* + * It is down to at most two contenders now, + * we will treat reconnections same as selections + * and let the scsi-bus arbitration process decide. + */ + spc->state |= SPC_STATE_BUSY; + spc->next_target = tgt; + spc_attempt_selection(spc); + /* + * Note that we might still lose arbitration.. + */ + } + splx(s); +} + +void spc_attempt_selection(spc) + spc_softc_t spc; +{ + target_info_t *tgt; + spc_regmap_t *regs; + register int cmd; + int atn=0; + + /* This is about your select code */ + + regs = spc->regs; + tgt = spc->next_target; + + LOG(4,"select"); + LOG(0x80+tgt->target_id,0); + + /* + * Init bus state variables and set registers. + */ + spc->active_target = tgt; + + /* reselection pending ? */ + if ((regs->spc_phase & (SPC_BUS_BSY|SPC_BUS_SEL)) +#ifdef MIPS + && (regs->spc_phase & (SPC_BUS_BSY|SPC_BUS_SEL)) + && (regs->spc_phase & (SPC_BUS_BSY|SPC_BUS_SEL)) +#endif + ) + return; + + spc->script = tgt->transient_state.script; + spc->error_handler = tgt->transient_state.handler; + spc->done = SCSI_RET_IN_PROGRESS; + + spc->in_count = 0; + spc->out_count = 0; + + cmd = SPC_SCMD_C_SELECT | SPC_SCMD_PROGRAMMED_X; + if (tgt->flags & TGT_DID_SYNCH) + { + if (tgt->transient_state.identify != 0xff) + atn = 1; + } + else + if (tgt->flags & TGT_TRY_SYNCH) + atn = 1; + +#if 0 + regs->spc_psns = SPC_DIAG_ENBL_XFER; +#endif + + regs->spc_sctl = SPC_SCTL_ARB_EBL | SPC_SCTL_PAR_EBL | + SPC_SCTL_RSEL_EBL | SPC_SCTL_IE; + + + { int mask; + mask = ~SPC_PCTL_BFREE_IE & regs->spc_pctl; + regs->spc_pctl = mask; + } + + regs->spc_temp = (1<<(spc->sc->initiator_id)) | (1<<(tgt->target_id)); + + SPC_TC_PUT(regs,0xfa004); + + if (atn) + { + regs->spc_scmd = SPC_SCMD_C_ATN_S; + /* delay 1us to avoid races */ + delay(1); + } + + regs->spc_scmd = cmd; + return; +} + +/* + * Interrupt routine + * Take interrupts from the chip + * + * Implementation: + * Move along the current command's script if + * all is well, invoke error handler if not. + */ +void spc_intr(unit) +int unit; +{ + register spc_softc_t spc; + register script_t scp; + register unsigned ints, psns, ssts; + register spc_regmap_t *regs; + boolean_t try_match; +#if notyet + extern boolean_t rz_use_mapped_interface; + + if (rz_use_mapped_interface) + { + SPC_intr(unit); + return; + } +#endif + + spc = spc_softc[unit]; + regs = spc->regs; + + /* read the interrupt status register */ + ints = regs->spc_ints; + + LOG(5,"\n\tintr"); + LOG(0x80+ints,0); + +TR(ints); +TRCHECK; + + if (ints & SPC_INTS_RESET) + { + /* does its own interrupt reset when ready */ + spc_bus_reset(spc); + return; + } + + /* we got an interrupt allright */ + if (spc->active_target) + spc->wd.watchdog_state = SCSI_WD_ACTIVE; + + + if (ints == 0) + { /* no obvious cause */ + LOG(2,"SPURIOUS"); + gimmeabreak(); + return; + } + + + /* reset the interrupt */ + regs->spc_ints = ints; + + /* go get the phase, and status. We can't trust the + phase until REQ is asserted in the psns. Only do + this is we received a command complete or service + required interrupt. Otherwise, just read them once + and trust. */ + + + + if (ints & (SPC_INTS_DONE|SPC_INTS_BUSREQ)) + while(1) + { + psns = regs->spc_psns; + if (psns & SPC_BUS_REQ) + break; + delay(1); /* don't hog the bus */ + } + else + psns = regs->spc_psns; + + ssts = regs->spc_psns; + +TR(psns); +TR(ssts); +TRCHECK; + + if ((spc->state & SPC_STATE_TARGET) || + (ints & SPC_INTS_SELECTED)) + spc_target_intr(spc /**, ints, psns, ssts **/); + + scp = spc->script; + + if ((scp == 0) || (ints & SPC_INTS_RESELECTED)) + { + gimmeabreak(); + spc_reconnect(spc, ints, psns, ssts); + return; + } + + if (SCRIPT_MATCH(psns) != scp->condition) { + if (try_match = (*spc->error_handler)(spc, ints, psns, ssts)) { + psns = regs->spc_psns; + ssts = regs->spc_ssts; + } + } else + try_match = TRUE; + + + /* might have been side effected */ + scp = spc->script; + + if (try_match && (SCRIPT_MATCH(psns) == scp->condition)) { + /* + * Perform the appropriate operation, + * then proceed + */ + if ((*scp->action)(spc, ints, psns, ssts)) { + /* might have been side effected */ + scp = spc->script; + spc->script = scp + 1; + } + } +} + +void spc_target_intr(spc) + register spc_softc_t spc; +{ + panic("SPC: TARGET MODE !!!\n"); +} + +/* + * All the many little things that the interrupt + * routine might switch to + */ +boolean_t +spc_issue_command(spc, ints, psns, ssts) + spc_softc_t spc; + int ints, psns, ssts; +{ + register spc_regmap_t *regs = spc->regs; + + LOG(0x12, "cmd_issue"); + /* we have just done a select; + Bus is in CMD phase; + need to phase match */ + SPC_ACK(regs, SCSI_PHASE_CMD); + + return spc_data_out(regs, SCSI_PHASE_CMD, + spc->active_target->transient_state.cmd_count, + spc->active_target->cmd_ptr) ? FALSE : TRUE; +} + +boolean_t +spc_issue_ident_and_command(spc, ints, psns, ssts) + spc_softc_t spc; + int ints, psns, ssts; +{ + register spc_regmap_t *regs = spc->regs; + + LOG(0x22, "ident_and_cmd"); + /* we have just done a select with atn Bus is in MSG_OUT phase; + need to phase match */ + SPC_ACK(regs, SCSI_PHASE_MSG_OUT); + + spc_data_out(regs, SCSI_PHASE_MSG_OUT, 1, + &spc->active_target->transient_state.identify); + + /* wait to go to command phase */ + SPC_WAIT_PHASE(SCSI_PHASE_CMD); + + /* ack */ + SPC_ACK(regs, SCSI_PHASE_CMD); + + /* should be a command complete intr pending. Eat it */ + if (regs->spc_ints != SPC_INTS_DONE) + gimmeabreak(); + regs->spc_ints = SPC_INTS_DONE; + + /* spit */ + return spc_data_out(regs, SCSI_PHASE_CMD, + spc->active_target->transient_state.cmd_count, + spc->active_target->cmd_ptr) ? FALSE : TRUE; +} + + +boolean_t +spc_end_transaction( spc, ints, psns, serr) + register spc_softc_t spc; + int ints, psns, serr; +{ + register spc_regmap_t *regs = spc->regs; + char cmc; + int tmp; + + LOG(0x1f,"end_t"); + + SPC_ACK(regs,SCSI_PHASE_MSG_IN /*,1*/); + + spc_data_in(regs, SCSI_PHASE_MSG_IN, 1, &cmc); + + if (cmc != SCSI_COMMAND_COMPLETE) + printf("{T%x}", cmc); + + while (regs->spc_ints != (SPC_INTS_DONE|SPC_INTS_DISC)); + + SPC_ACK(regs,SPC_PHASE_DISC); + + /* going to disconnect */ + regs->spc_pctl = ~SPC_PCTL_BFREE_IE & SPC_PHASE_DISC; + /* regs->spc_scmd = 0; */ + + /* clear all intr bits? */ + tmp = regs->spc_ints; + regs->spc_ints = tmp; + + + if (!spc_end(spc, ints, psns, serr)) + (void) spc_reconnect(spc, ints, psns, serr); + return FALSE; +} + +boolean_t +spc_end( spc, ints, psns, serr) + register spc_softc_t spc; + int ints, psns, serr; +{ + register target_info_t *tgt; + register io_req_t ior; + register spc_regmap_t *regs = spc->regs; + int csr; + + LOG(6,"end"); + + tgt = spc->active_target; + + if ((tgt->done = spc->done) == SCSI_RET_IN_PROGRESS) + tgt->done = SCSI_RET_SUCCESS; + + spc->script = 0; + + if (spc->wd.nactive-- == 1) + spc->wd.watchdog_state = SCSI_WD_INACTIVE; + + /* check reconnection not pending */ + csr = SPC_INTS_RESELECTED & regs->spc_ints; + if (!csr) + spc_release_bus(spc); + else + { + spc->active_target = 0; + /* spc->state &= ~SPC_STATE_BUSY; later */ + } + if (ior = tgt->ior) { +#ifdef MACH_KERNEL +#else /*MACH_KERNEL*/ + fdma_unmap(&tgt->fdma, ior); +#endif /*MACH_KERNEL*/ + LOG(0xA,"ops->restart"); + (*tgt->dev_ops->restart)( tgt, TRUE); + if (csr) + spc->state &= ~SPC_STATE_BUSY; + } + + /* return not reselected */ + return (csr & SPC_INTS_RESELECTED) ? 0 : 1; +} + +boolean_t +spc_release_bus(spc) + register spc_softc_t spc; +{ + boolean_t ret = FALSE; + + LOG(9,"release"); + + spc->script = 0; + + if (spc->state & SPC_STATE_COLLISION) { + + LOG(0xB,"collided"); + spc->state &= ~SPC_STATE_COLLISION; + spc_attempt_selection(spc); + + } else if (queue_empty(&spc->waiting_targets)) { + + spc->state &= ~SPC_STATE_BUSY; + spc->active_target = 0; + ret = TRUE; + + } else { + + LOG(0xC,"dequeue"); + spc->next_target = (target_info_t *) + dequeue_head(&spc->waiting_targets); + spc_attempt_selection(spc); + } + return ret; +} + +boolean_t +spc_get_status( spc, ints, psns, serr) + register spc_softc_t spc; + int ints, psns, serr; +{ + register spc_regmap_t *regs = spc->regs; + scsi2_status_byte_t status; + register target_info_t *tgt; + + LOG(0xD,"get_status"); +TRWRAP; + + spc->state &= ~SPC_STATE_DMA_IN; + + tgt = spc->active_target; + + SPC_ACK(regs,SCSI_PHASE_STATUS /*,1*/); + + spc_data_in(regs, SCSI_PHASE_STATUS, 1, &status.bits); + + if (status.st.scsi_status_code != SCSI_ST_GOOD) { + scsi_error(spc->active_target, SCSI_ERR_STATUS, status.bits, 0); + spc->done = (status.st.scsi_status_code == SCSI_ST_BUSY) ? + SCSI_RET_RETRY : SCSI_RET_NEED_SENSE; + } else + spc->done = SCSI_RET_SUCCESS; + + return TRUE; +} + +boolean_t +spc_xfer_in( spc, ints, psns, ssts) + register spc_softc_t spc; + int ints, psns, ssts; +{ + register target_info_t *tgt; + register spc_regmap_t *regs = spc->regs; + register int count; + boolean_t advance_script = TRUE; + + LOG(0xE,"xfer_in"); + + tgt = spc->active_target; + spc->state |= SPC_STATE_DMA_IN; + + count = tgt->transient_state.in_count; + + SPC_ACK(regs, SCSI_PHASE_DATAI); + + if ((tgt->cur_cmd != SCSI_CMD_READ) && + (tgt->cur_cmd != SCSI_CMD_LONG_READ)) + spc_data_in(regs, SCSI_PHASE_DATAI, count, tgt->cmd_ptr); + else + { + spc_data_in(regs, SCSI_PHASE_DATAI, count, tgt->ior->io_data); + } + + return advance_script; +} + +boolean_t +spc_xfer_out( spc, ints, psns, ssts) + register spc_softc_t spc; + int ints, psns, ssts; +{ + register spc_regmap_t *regs = spc->regs; + register target_info_t *tgt; + boolean_t advance_script = TRUE; + int count = spc->out_count; + + LOG(0xF,"xfer_out"); + + tgt = spc->active_target; + spc->state &= ~SPC_STATE_DMA_IN; + + count = tgt->transient_state.out_count; + + SPC_ACK(regs, SCSI_PHASE_DATAO); + + if ((tgt->cur_cmd != SCSI_CMD_WRITE) && + (tgt->cur_cmd != SCSI_CMD_LONG_WRITE)) + spc_data_out(regs, SCSI_PHASE_DATAO, count, + tgt->cmd_ptr + tgt->transient_state.cmd_count); + else + spc_data_out(regs, SCSI_PHASE_DATAO, count, tgt->ior->io_data); + + return advance_script; +} + +/* disconnect-reconnect ops */ + +/* get the message in via dma ?? */ +boolean_t +spc_msg_in(spc, ints, psns, ssts) + register spc_softc_t spc; + int ints, psns, ssts; +{ + register target_info_t *tgt; + + LOG(0x15,"msg_in"); + gimmeabreak(); + + tgt = spc->active_target; + +#if 0 +You can do this by hand, just leave an interrupt pending at the end +#endif + + /* We only really expect two bytes */ +#if 0 + SPC_PUT(dmar,sizeof(scsi_command_group_0)); + .... +#endif + return TRUE; +} + +/* check the message is indeed a DISCONNECT */ +boolean_t +spc_disconnect(spc, ints, psns, ssts) + register spc_softc_t spc; + int ints, psns, ssts; +{ + register int len = 0; + boolean_t ok = FALSE; + register char *msgs = 0; + + +/* SPC_TC_GET(dmar,len); */ + len = sizeof(scsi_command_group_0) - len; + +/* msgs = tgt->cmd_ptr; */ /* I think */ + + if ((len == 0) || (len > 2) || msgs == 0) + ok = FALSE; + else { + /* A SDP message preceeds it in non-completed READs */ + ok = ((msgs[0] == SCSI_DISCONNECT) || /* completed op */ + ((msgs[0] == SCSI_SAVE_DATA_POINTER) && /* incomplete */ + (msgs[1] == SCSI_DISCONNECT))); + } + if (!ok) + printf("[tgt %d bad msg (%d): %x]", + spc->active_target->target_id, len, *msgs); + + return TRUE; +} + +/* save all relevant data, free the BUS */ +boolean_t +spc_disconnected(spc, ints, psns, ssts) + register spc_softc_t spc; + int ints, psns, ssts; +{ + register target_info_t *tgt; + +/* make sure reselects will work */ + + LOG(0x16,"disconnected"); + + spc_disconnect(spc,ints, psns, ssts); + + tgt = spc->active_target; + tgt->flags |= TGT_DISCONNECTED; + tgt->transient_state.handler = spc->error_handler; + /* the rest has been saved in spc_err_disconn() */ + + PRINT(("{D%d}", tgt->target_id)); + + spc_release_bus(spc); + + return FALSE; +} + +/* get reconnect message, restore BUS */ +boolean_t +spc_reconnect(spc, ints, psns, ssts) + register spc_softc_t spc; + int ints, psns, ssts; +{ + + LOG(0x17,"reconnect"); + + if (spc->wd.nactive == 0) { + LOG(2,"SPURIOUS"); + return FALSE; + } + +#if 0 +This is the 5380 code, for reference: + spc_regmap_t *regs = spc->regs; + register target_info_t *tgt; + register int id; + int msg; + + + id = regs->spc_data;/*parity?*/ + /* xxx check our id is in there */ + + id &= ~(1 << spc->sc->initiator_id); + { + register int i; + for (i = 0; i < 8; i++) + if (id & (1 << i)) break; +if (i == 8) {printf("{P%x}", id);return;} + id = i; + } + regs->spc_icmd = SPC_ICMD_BSY; + while (regs->spc_bus_csr & SPC_BUS_SEL) + ; + regs->spc_icmd = 0; + delay_1p2_us(); + while ( ((regs->spc_bus_csr & SPC_BUS_BSY) == 0) && + ((regs->spc_bus_csr & SPC_BUS_BSY) == 0) && + ((regs->spc_bus_csr & SPC_BUS_BSY) == 0)) + ; + + /* Now should wait for correct phase: REQ signals it */ + while ( ((regs->spc_bus_csr & SPC_BUS_REQ) == 0) && + ((regs->spc_bus_csr & SPC_BUS_REQ) == 0) && + ((regs->spc_bus_csr & SPC_BUS_REQ) == 0)) + ; + + regs->spc_mode |= SPC_MODE_MONBSY; + + /* + * See if this reconnection collided with a selection attempt + */ + if (spc->state & SPC_STATE_BUSY) + spc->state |= SPC_STATE_COLLISION; + + spc->state |= SPC_STATE_BUSY; + + /* Get identify msg */ + bs = regs->spc_phase; +if (SPC_CUR_PHASE(bs) != SCSI_PHASE_MSG_IN) gimmeabreak(); + SPC_ACK(regs,SCSI_PHASE_MSG_IN /*,1*/); + msg = 0; + spc_data_in(regs, SCSI_PHASE_MSG_IN, 1, &msg); + regs->spc_mode = SPC_MODE_PAR_CHK|SPC_MODE_DMA|SPC_MODE_MONBSY; + + if (msg != SCSI_IDENTIFY) + printf("{I%x %x}", id, msg); + + tgt = spc->sc->target[id]; + if (id > 7 || tgt == 0) panic("spc_reconnect"); + + PRINT(("{R%d}", id)); + if (spc->state & SPC_STATE_COLLISION) + PRINT(("[B %d-%d]", spc->active_target->target_id, id)); + + LOG(0x80+id,0); + + spc->active_target = tgt; + tgt->flags &= ~TGT_DISCONNECTED; + + spc->script = tgt->transient_state.script; + spc->error_handler = tgt->transient_state.handler; + spc->in_count = 0; + spc->out_count = 0; + + /* Should get a phase mismatch when tgt changes phase */ +#endif + return TRUE; +} + + + +/* do the synch negotiation */ +boolean_t +spc_dosynch( spc, ints, psns, ssts) + register spc_softc_t spc; + int ints, psns, ssts; +{ + /* + * Phase is MSG_OUT here, cmd has not been xferred + */ + int len; + register target_info_t *tgt; + register spc_regmap_t *regs = spc->regs; + unsigned char off; + unsigned char p[6]; + + LOG(0x11,"dosync"); + + /* ATN still asserted */ + SPC_ACK(regs,SCSI_PHASE_MSG_OUT); + + tgt = spc->active_target; + + tgt->flags |= TGT_DID_SYNCH; /* only one chance */ + tgt->flags &= ~TGT_TRY_SYNCH; + + /*p = some scratch buffer, on the stack */ + + p[0] = SCSI_IDENTIFY; + p[1] = SCSI_EXTENDED_MESSAGE; + p[2] = 3; + p[3] = SCSI_SYNC_XFER_REQUEST; + /* We cannot run synchronous */ +#define spc_to_scsi_period(x) 0x7 +#define scsi_period_to_spc(x) (x) + off = 0; + p[4] = spc_to_scsi_period(spc_min_period); + p[5] = off; + + /* The transfer is started with ATN still set. The + chip will automagically drop ATN before it transfers the + last byte. Pretty neat. */ + spc_data_out(regs, SCSI_PHASE_MSG_OUT, + sizeof(scsi_synch_xfer_req_t)+1, p); + + /* wait for phase change to status phase */ + SPC_WAIT_PHASE_VANISH(SCSI_PHASE_MSG_OUT); + + + psns = regs->spc_phase; + + /* The standard sez there nothing else the target can do but.. */ + if (SPC_CUR_PHASE(psns) != SCSI_PHASE_MSG_IN) + panic("spc_dosync");/* XXX put offline */ + + /* + msgin: + */ + /* ack */ + SPC_ACK(regs,SCSI_PHASE_MSG_IN); + + /* clear any pending interrupts */ + regs->spc_ints = regs->spc_ints; + + /* get answer */ + len = sizeof(scsi_synch_xfer_req_t); + len = spc_data_in(regs, SCSI_PHASE_MSG_IN, len, p); + + /* do not cancel the phase mismatch interrupt ! */ + + /* look at the answer and see if we like it */ + if (len || (p[0] != SCSI_EXTENDED_MESSAGE)) { + /* did not like it at all */ + printf(" did not like SYNCH xfer "); + } else { + /* will NOT do synch */ + printf(" but we cannot do SYNCH xfer "); + tgt->sync_period = scsi_period_to_spc(p[3]); + tgt->sync_offset = p[4]; + /* sanity */ + if (tgt->sync_offset != 0) + printf(" ?OFFSET %x? ", tgt->sync_offset); + } + + /* wait for phase change */ + SPC_WAIT_PHASE_VANISH(SCSI_PHASE_MSG_IN); + + psns = regs->spc_phase; + + /* phase should be command now */ + /* continue with simple command script */ + spc->error_handler = spc_err_generic; + spc->script = spc_script_cmd; + +/* Make sure you get out right here, esp the script pointer and/or pending intr */ + + if (SPC_CUR_PHASE(psns) == SCSI_PHASE_CMD ) + return FALSE; + + if (SPC_CUR_PHASE(psns) == SCSI_PHASE_STATUS ) /* jump to get_status */ + return TRUE; /* intr is pending */ + + spc->script++; + if (SPC_CUR_PHASE(psns) == SCSI_PHASE_MSG_IN ) + return TRUE; + + if ((psns & SPC_BUS_BSY) == 0) /* uhu? disconnected */ + return TRUE; + + gimmeabreak(); + return FALSE; +} + +/* + * The bus was reset + */ +void spc_bus_reset(spc) + register spc_softc_t spc; +{ + register spc_regmap_t *regs = spc->regs; + + LOG(0x21,"bus_reset"); + + /* + * Clear bus descriptor + */ + spc->script = 0; + spc->error_handler = 0; + spc->active_target = 0; + spc->next_target = 0; + spc->state = 0; + queue_init(&spc->waiting_targets); + spc->wd.nactive = 0; + spc_reset(regs, TRUE); + + printf("spc%d: (%d) bus reset ", spc->sc->masterno, ++spc->wd.reset_count); + delay(scsi_delay_after_reset); /* some targets take long to reset */ + + if (spc->sc == 0) /* sanity */ + return; + + scsi_bus_was_reset(spc->sc); +} + +/* + * Error handlers + */ + +/* + * Generic, default handler + */ +boolean_t +spc_err_generic(spc, ints, psns, ssts) + register spc_softc_t spc; + int ints, psns, ssts; +{ + register spc_regmap_t *regs = spc->regs; + LOG(0x10,"err_generic"); + + if (ints & SPC_INTS_TIMEOUT) /* we timed out */ + if ((regs->spc_scmd & SPC_SCMD_CMDMASK) == SPC_SCMD_C_SELECT) + { + /* Powered off ? */ + if (spc->active_target->flags & TGT_FULLY_PROBED) + { + spc->active_target->flags = 0; + LOG(0x1e,"Device Down"); + } + spc->done = SCSI_RET_DEVICE_DOWN; + spc_end(spc, ints, psns, ssts); + return FALSE; /* don't retry - just report missing device */ + } + else + { /* timed out - but not on a select. What is going on? */ + gimmeabreak(); + } + + if (SPC_CUR_PHASE(psns) == SCSI_PHASE_STATUS) + return spc_err_to_status(spc, ints, psns, ssts); + gimmeabreak(); + return FALSE; +} + +/* + * Handle generic errors that are reported as + * an unexpected change to STATUS phase + */ +boolean_t +spc_err_to_status(spc, ints, psns, ssts) + register spc_softc_t spc; + int ints, psns, ssts; +{ + script_t scp = spc->script; + + LOG(0x20,"err_tostatus"); + while (SCSI_PHASE(scp->condition) != SCSI_PHASE_STATUS) + scp++; + spc->script = scp; +#if 0 + /* + * Normally, we would already be able to say the command + * is in error, e.g. the tape had a filemark or something. + * But in case we do disconnected mode WRITEs, it is quite + * common that the following happens: + * dma_out -> disconnect -> reconnect + * and our script might expect at this point that the dma + * had to be restarted (it didn't know it was completed + * because the tape record is shorter than we asked for). + * And in any event.. it is both correct and cleaner to + * declare error iff the STATUS byte says so. + */ + spc->done = SCSI_RET_NEED_SENSE; +#endif + return TRUE; +} + +/* + * Watch for a disconnection + */ +boolean_t +spc_err_disconn(spc, ints, psns, ssts) + register spc_softc_t spc; + int ints, psns, ssts; +{ +#if 1 +/* + * THIS ROUTINE CAN'T POSSIBLY WORK... + * FOR EXAMPLE, THE VARIABLE 'xferred' IS NEVER INITIALIZED. + */ + return FALSE; +#else + register spc_regmap_t *regs; + register target_info_t *tgt; + int xferred; + + LOG(0x18,"err_disconn"); + + if (SPC_CUR_PHASE(ints) != SCSI_PHASE_MSG_IN) + return spc_err_generic(spc, ints, psns, ssts); + + regs = spc->regs; + + tgt = spc->active_target; + + switch (SCSI_PHASE(spc->script->condition)) { + case SCSI_PHASE_DATAO: + LOG(0x1b,"+DATAO"); +/*updatecounters:*/ + tgt->transient_state.out_count -= xferred; + assert(tgt->transient_state.out_count > 0); + tgt->transient_state.dma_offset += xferred; + + tgt->transient_state.script = spc_script_data_out; + break; + + case SCSI_PHASE_DATAI: + LOG(0x19,"+DATAI"); + +/*update counters: */ + assert(xferred > 0); + tgt->transient_state.in_count -= xferred; + assert(tgt->transient_state.in_count > 0); + tgt->transient_state.dma_offset += xferred; + + tgt->transient_state.script = spc_script_data_in; + break; + + case SCSI_PHASE_STATUS: + + if (spc->state & SPC_STATE_DMA_IN) { + + LOG(0x1a,"+STATUS+R"); + +/*same as above.. */ + assert(xferred > 0); + tgt->transient_state.in_count -= xferred; +/* assert(tgt->transient_state.in_count > 0);*/ + tgt->transient_state.dma_offset += xferred; + + tgt->transient_state.script = spc_script_data_in; + if (tgt->transient_state.in_count == 0) + tgt->transient_state.script++; + + } else { + + LOG(0x1d,"+STATUS+W"); + + if ((tgt->transient_state.out_count == spc->out_count)) { + /* all done */ + tgt->transient_state.script = &spc_script_data_out[1]; + tgt->transient_state.out_count = 0; + } else { + +/*.. */ + tgt->transient_state.out_count -= xferred; + assert(tgt->transient_state.out_count > 0); + tgt->transient_state.dma_offset += xferred; + + tgt->transient_state.script = spc_script_data_out; + } + spc->out_count = 0; + } + break; + default: + gimmeabreak(); + } + /* spc->xxx = 0; */ + +/* SPC_ACK(regs,SCSI_PHASE_MSG_IN); later */ + (void) spc_msg_in(spc, ints, psns, ssts); + + spc->script = spc_script_disconnect; + + return FALSE; +#endif +} + +/* + * Watchdog + * + */ +void spc_reset_scsibus(spc) + register spc_softc_t spc; +{ + register target_info_t *tgt = spc->active_target; + if (tgt) { + int cnt = 0; + /* SPC_TC_GET(spc->dmar,cnt); */ + log( LOG_KERN, + "Target %d was active, cmd x%x in x%x out x%x Sin x%x Sou x%x dmalen x%x\n", + tgt->target_id, tgt->cur_cmd, + tgt->transient_state.in_count, tgt->transient_state.out_count, + spc->in_count, spc->out_count, cnt); + } +#if 0 + spc->regs->..... +#endif + delay(25); +} + +int SPC_ACK(regs, phase) +register spc_regmap_t *regs; +unsigned phase; +{ + /* we want to switch into the specified phase - + + The calling routine should already dismissed + any pending interrupts (spc_ints) + */ + + regs->spc_psns = 0; + regs->spc_pctl = phase | SPC_PCTL_BFREE_IE; + return 0; +} +#endif /*NSCSI > 0*/ + +#endif 0 diff --git a/scsi/adapters/scsi_aha15.h b/scsi/adapters/scsi_aha15.h new file mode 100644 index 0000000..52cd936 --- /dev/null +++ b/scsi/adapters/scsi_aha15.h @@ -0,0 +1,347 @@ +/* + * Mach Operating System + * Copyright (c) 1991,1990 Carnegie Mellon University + * All Rights Reserved. + * + * Permission to use, copy, modify and distribute this software and its + * documentation is hereby granted, provided that both the copyright + * notice and this permission notice appear in all copies of the + * software, derivative works or modified versions, and any portions + * thereof, and that both notices appear in supporting documentation. + * + * CARNEGIE MELLON ALLOWS FREE USE OF THIS SOFTWARE IN ITS "AS IS" + * CONDITION. CARNEGIE MELLON DISCLAIMS ANY LIABILITY OF ANY KIND FOR + * ANY DAMAGES WHATSOEVER RESULTING FROM THE USE OF THIS SOFTWARE. + * + * Carnegie Mellon requests users of this software to return to + * + * Software Distribution Coordinator or Software.Distribution@CS.CMU.EDU + * School of Computer Science + * Carnegie Mellon University + * Pittsburgh PA 15213-3890 + * + * any improvements or extensions that they make and grant Carnegie Mellon + * the rights to redistribute these changes. + */ +/* + * File: scsi_aha15.h + * Author: Alessandro Forin, Carnegie Mellon University + * Date: 6/91 + * + * Definitions for the Adaptec AHA-15xx family + * of Intelligent SCSI Host Adapter boards + */ + +#ifndef _SCSI_AHA15_H_ +#define _SCSI_AHA15_H_ + +/* + * Addresses/length in 24 bits + * + * BEWARE: your compiler must pack these correctly, + * e.g. without gaps between two such contiguous structs + * (GCC does) + */ +typedef struct { + unsigned char msb; + unsigned char mid; + unsigned char lsb; +} aha_address_t; + +#define AHA_ADDRESS_SET(addr,val) {\ + (addr).msb = ((val) >> 16);\ + (addr).mid = ((val) >> 8);\ + (addr).lsb = (val) ;\ + } +#define AHA_ADDRESS_GET(addr,val) {\ + (val) = ((addr).msb << 16) |\ + ((addr).mid << 8) |\ + ((addr).lsb ) ;\ + } + +#define aha_length_t aha_address_t +#define AHA_LENGTH_SET AHA_ADDRESS_SET +#define AHA_LENGTH_GET AHA_ADDRESS_GET + +/* + * Register map + */ + +typedef struct { + volatile unsigned char aha_status; /* r: Status Register */ +#define aha_ctl aha_status /* w: Control Register */ + + volatile unsigned char aha_data; /* rw: Data Port */ +#define aha_cmd aha_data /* w: Command register */ + + volatile unsigned char aha_intr; /* ro: Interrupt Register */ +} aha_regmap_t; + +/* ..but on the 386 I/O is not memory mapped */ +#define AHA_STATUS_PORT(port) ((port)) +#define AHA_CONTROL_PORT(port) ((port)) +#define AHA_COMMAND_PORT(port) ((port)+1) +#define AHA_DATA_PORT(port) ((port)+1) +#define AHA_INTR_PORT(port) ((port)+2) + +/* Status Register */ +#define AHA_CSR_CMD_ERR 0x01 /* Invalid command */ +#define AHA_CSR_xxx 0x02 /* undefined */ +#define AHA_CSR_DATAI_FULL 0x04 /* In-port full */ +#define AHA_CSR_DATAO_FULL 0x08 /* Out-port full */ +#define AHA_CSR_IDLE 0x10 /* doin nuthin */ +#define AHA_CSR_INIT_REQ 0x20 /* initialization required */ +#define AHA_CSR_DIAG_FAIL 0x40 /* selftest failed */ +#define AHA_CSR_SELF_TEST 0x80 /* selftesting */ + +/* Control Register */ +#define AHA_CTL_xxx 0x0f /* undefined */ +#define AHA_CTL_SCSI_RST 0x10 /* reset SCSIbus */ +#define AHA_CTL_INTR_CLR 0x20 /* Clear interrupt reg */ +#define AHA_CTL_SOFT_RESET 0x40 /* Board only, no selftest */ +#define AHA_CTL_HARD_RESET 0x80 /* Full reset, and SCSIbus */ + +/* Interrupt Flags register */ +#define AHA_INTR_MBI_FULL 0x01 /* scan the In mboxes */ +#define AHA_INTR_MBO_AVAIL 0x02 /* scan the Out mboxes */ +#define AHA_INTR_DONE 0x04 /* command complete */ +#define AHA_INTR_RST 0x08 /* saw a SCSIbus reset */ +#define AHA_INTR_xxx 0x70 /* undefined */ +#define AHA_INTR_PENDING 0x80 /* Any interrupt bit set */ + +/* + * Command register + */ +#define AHA_CMD_NOP 0x00 /* */ +#define AHA_CMD_INIT 0x01 /* mbox initialization */ + /* 4 bytes follow: # of Out mboxes (x2->total), and + msb, mid, lsb of mbox address */ +struct aha_init { + unsigned char mb_count; + aha_address_t mb_ptr; +}; +#define AHA_CMD_START 0x02 /* start SCSI cmd */ +#define AHA_CMD_BIOS 0x03 +#define AHA_CMD_INQUIRY 0x04 + /* returns 4 bytes: */ +struct aha_inq { + unsigned char board_id; +# define AHA_BID_1540_B16 0x00 +# define AHA_BID_1540_B64 0x30 +# define AHA_BID_1540B 0x41 +# define AHA_BID_1640 0x42 +# define AHA_BID_1740 0x43 +# define AHA_BID_1542C 0x44 +# define AHA_BID_1542CF 0x45 /* BIOS v2.0x */ + + unsigned char options; +# define AHA_BOPT_STD 0x41 /* in 154x, standard model */ + + unsigned char frl_1; /* rev level */ + unsigned char frl_2; +}; +#define AHA_CMD_MBO_IE 0x05 + /* 1 byte follows: */ +# define AHA_MBO_DISABLE 0x00 +# define AHA_MBO_ENABLE 0x01 + +#define AHA_CMD_SET_SELTO 0x06 /* select timeout */ + /* 4 bytes follow: */ +struct aha_selto { + unsigned char enable; + char xxx; + unsigned char timeo_msb; + unsigned char timeo_lsb; +}; +#define AHA_CMD_SET_BUSON 0x07 + /* 1 byte value follows: 2..15 default 11 usecs */ +#define AHA_CMD_SET_BUSOFF 0x08 + /* 1 byte value follows: 1..64 default 4 usecs */ +#define AHA_CMD_SET_XSPEED 0x09 + /* 1 byte value follows: */ +# define AHA_DMASPEED_5Mb 0x00 +# define AHA_DMASPEED_7Mb 0x01 +# define AHA_DMASPEED_8Mb 0x02 +# define AHA_DMASPEED_10Mb 0x03 +# define AHA_DMASPEED_6Mb 0x04 + /* values in the range 80..ff encoded as follows: + bit 7 on --> custom speed + bits 6..4 read pulse width + 0 100ns + 1 150 + 2 200 + 3 250 + 4 300 + 5 350 + 6 400 + 7 450 + bit 3 strobe off time + 0 100ns + 1 150ns + bits 2..0 write pulse width + + */ +#define AHA_CMD_FIND_DEVICES 0x0a + /* returns 8 bytes, each one is a bitmask of the LUNs + available for the given target ID */ +struct aha_devs { + unsigned char tgt_luns[8]; +}; +#define AHA_CMD_GET_CONFIG 0x0b + /* returns 3 bytes: */ +struct aha_conf { + unsigned char dma_arbitration;/* bit N -> channel N */ + unsigned char intr_ch;/* bit N -> intr 9+N (but 13,16)*/ + unsigned char my_scsi_id; /* both of I and T role */ +}; +#define AHA_CMD_ENB_TGT_MODE 0x0c + /* 2 bytes follow: */ +struct aha_tgt { + unsigned char enable; + unsigned char luns; /* bitmask */ +}; + +#define AHA_CMD_GET_SETUP 0x0d + /* 1 byte follows: allocation len (N) */ + /* returns N bytes, 17 significant: */ +struct aha_setup { + BITFIELD_3( unsigned char, + initiate_SDT:1, + enable_parity:1, + res:6); + unsigned char xspeed; /* see above */ + unsigned char buson; + unsigned char busoff; + unsigned char n_mboxes;/* 0 if not initialized */ + aha_address_t mb_ptr; /* garbage if not inited */ + struct { + BITFIELD_3( unsigned char, + offset: 4, + period: 3, /* 200 + 50 * N */ + negotiated: 1); + } SDT_params[8]; + unsigned char no_disconnect; /* bitmask */ +}; + +#define AHA_CMD_WRITE_CH2 0x1a + /* 3 bytes (aha_address_t) follow for the buffer pointer */ +#define AHA_CMD_READ_CH2 0x1b + /* 3 bytes (aha_address_t) follow for the buffer pointer */ +#define AHA_CMD_WRITE_FIFO 0x1c + /* 3 bytes (aha_address_t) follow for the buffer pointer */ +#define AHA_CMD_READ_FIFO 0x1d + /* 3 bytes (aha_address_t) follow for the buffer pointer */ +#define AHA_CMD_ECHO 0x1f + /* 1 byte follows, which should then be read back */ +#define AHA_CMD_DIAG 0x20 +#define AHA_CMD_SET_OPT 0x21 + /* 2+ bytes follow: */ +struct aha_diag { + unsigned char parmlen; /* bytes to follow */ + unsigned char no_disconnect; /* bitmask */ + /* rest is undefined */ +}; + +#define AHA_EXT_BIOS 0x28 /* return extended bios info */ +#define AHA_MBX_ENABLE 0x29 /* enable mail box interface */ +struct aha_extbios { + unsigned char flags; /* Bit 3 == 1 extended bios enabled */ + unsigned char mailboxlock; /* mail box lock code to unlock it */ +}; + +/* + * Command Control Block + */ +typedef struct { + unsigned char ccb_code; +# define AHA_CCB_I_CMD 0x00 +# define AHA_CCB_T_CMD 0x01 +# define AHA_CCB_I_CMD_SG 0x02 +# define AHA_CCB_ICMD_R 0x03 +# define AHA_CCB_ICMD_SG_R 0x04 +# define AHA_CCB_BDEV_RST 0x81 + BITFIELD_4( unsigned char, + ccb_lun:3, + ccb_in:1, + ccb_out:1, + ccb_scsi_id:3); + unsigned char ccb_cmd_len; + unsigned char ccb_reqsns_len; /* if 1 no automatic reqsns*/ + aha_length_t ccb_datalen; + aha_address_t ccb_dataptr; + aha_address_t ccb_linkptr; + unsigned char ccb_linkid; + unsigned char ccb_hstatus; +# define AHA_HST_SUCCESS 0x00 +# define AHA_HST_SEL_TIMEO 0x11 +# define AHA_HST_DATA_OVRUN 0x12 +# define AHA_HST_BAD_DISCONN 0x13 +# define AHA_HST_BAD_PHASE_SEQ 0x14 +# define AHA_HST_BAD_OPCODE 0x16 +# define AHA_HST_BAD_LINK_LUN 0x17 +# define AHA_HST_INVALID_TDIR 0x18 +# define AHA_HST_DUPLICATED_CCB 0x19 +# define AHA_HST_BAD_PARAM 0x1a + + scsi2_status_byte_t ccb_status; + unsigned char ccb_xxx; + unsigned char ccb_xxx1; + scsi_command_group_5 ccb_scsi_cmd; /* cast as needed */ +} aha_ccb_t; + +/* For scatter/gather use a list of (len,ptr) segments, each field + is 3 bytes (aha_address_t) long. Max 17 segments, min 1 */ + +/* + * Ring descriptor, aka Mailbox + */ +typedef union { + + struct { + volatile unsigned char mb_cmd; /* Out mbox */ +# define mb_status mb_cmd /* In mbox */ + + aha_address_t mb_ptr; +#define AHA_MB_SET_PTR(mbx,val) AHA_ADDRESS_SET((mbx)->mb.mb_ptr,(val)) +#define AHA_MB_GET_PTR(mbx,val) AHA_ADDRESS_GET((mbx)->mb.mb_ptr,(val)) + + } mb; + + struct { /* ccb required In mbox */ + volatile unsigned char mb_cmd; + BITFIELD_4( unsigned char, + mb_lun : 3, + mb_isa_send : 1, + mb_isa_recv : 1, + mb_initiator_id : 3); + unsigned char mb_data_len_msb; + unsigned char mb_data_len_mid; + } mbt; + + unsigned int bits; /* quick access */ + +} aha_mbox_t; + +/* Out mbox, values for the mb_cmd field */ +#define AHA_MBO_FREE 0x00 +#define AHA_MBO_START 0x01 +#define AHA_MBO_ABORT 0x02 + +/* In mbox, values for the mb_status field */ +#define AHA_MBI_FREE 0x00 +#define AHA_MBI_SUCCESS 0x01 +#define AHA_MBI_ABORTED 0x02 +#define AHA_MBI_NOT_FOUND 0x03 +#define AHA_MBI_ERROR 0x04 +#define AHA_MBI_NEED_CCB 0x10 + +/* + * Scatter/gather segment lists + */ +typedef struct { + aha_length_t len; + aha_address_t ptr; +} aha_seglist_t; + +#define AHA_MAX_SEGLIST 17 /* which means max 64Kb */ +#endif /*_SCSI_AHA15_H_*/ diff --git a/scsi/adapters/scsi_aha15_hdw.c b/scsi/adapters/scsi_aha15_hdw.c new file mode 100644 index 0000000..5514bc5 --- /dev/null +++ b/scsi/adapters/scsi_aha15_hdw.c @@ -0,0 +1,1467 @@ +/* + * Mach Operating System + * Copyright (c) 1993,1992,1991,1990 Carnegie Mellon University + * All Rights Reserved. + * + * Permission to use, copy, modify and distribute this software and its + * documentation is hereby granted, provided that both the copyright + * notice and this permission notice appear in all copies of the + * software, derivative works or modified versions, and any portions + * thereof, and that both notices appear in supporting documentation. + * + * CARNEGIE MELLON ALLOWS FREE USE OF THIS SOFTWARE IN ITS "AS IS" + * CONDITION. CARNEGIE MELLON DISCLAIMS ANY LIABILITY OF ANY KIND FOR + * ANY DAMAGES WHATSOEVER RESULTING FROM THE USE OF THIS SOFTWARE. + * + * Carnegie Mellon requests users of this software to return to + * + * Software Distribution Coordinator or Software.Distribution@CS.CMU.EDU + * School of Computer Science + * Carnegie Mellon University + * Pittsburgh PA 15213-3890 + * + * any improvements or extensions that they make and grant Carnegie Mellon + * the rights to redistribute these changes. + */ +/* + * File: scsi_aha15_hdw.c + * Author: Alessandro Forin, Carnegie Mellon University + * Date: 6/91 + * + * Bottom layer of the SCSI driver: chip-dependent functions + * + * This file contains the code that is specific to the Adaptec + * AHA-15xx family of Intelligent SCSI Host Adapter boards: + * probing, start operation, and interrupt routine. + */ + +/* + * Since the board is "Intelligent" we do not need scripts like + * other simpler HBAs. Maybe. + */ +#include +#include + +#include +#if NAHA > 0 + +#include +#include +#include +#include +#include + +/* #include */ + +#include +#include +#include + +#include + +#ifdef AT386 +#define MACHINE_PGBYTES I386_PGBYTES +#define MAPPABLE 0 +#define gimmeabreak() asm("int3") +#include /* inlining of outb and inb */ +#endif /*AT386*/ + +#ifdef CBUS /* For the Corollary machine, physical */ +#include +#include + +#define aha_cbus_window transient_state.hba_dep[0] + /* must use windows for phys addresses */ + /* greater than 16 megs */ + +#define kvtoAT cbus_kvtoAT +#else /* CBUS */ +#define kvtoAT kvtophys +#endif /* CBUS */ + +#ifndef MACHINE_PGBYTES /* cross compile check */ +#define MACHINE_PGBYTES 0x1000 +#define MAPPABLE 1 +#define gimmeabreak() Debugger("gimmeabreak"); +#endif + +/* + * Data structures: ring, ccbs, a per target buffer + */ + +#define AHA_NMBOXES 2 /* no need for more, I think */ +struct aha_mb_ctl { + aha_mbox_t omb[AHA_NMBOXES]; + aha_mbox_t imb[AHA_NMBOXES]; + unsigned char iidx, oidx; /* roving ptrs into */ +}; +#define next_mbx_idx(i) ((((i)+1)==AHA_NMBOXES)?0:((i)+1)) + +#define AHA_NCCB 8 /* for now */ +struct aha_ccb_raw { + target_info_t *active_target; + aha_ccb_t ccb; + char buffer[256]; /* separate out this ? */ +}; +#define rccb_to_cmdptr(rccb) ((char*)&((rccb)->ccb.ccb_scsi_cmd)) + +/* forward decls */ +int aha_reset_scsibus(); +boolean_t aha_probe_target(); + +/* + * State descriptor for this layer. There is one such structure + * per (enabled) board + */ +struct aha_softc { + watchdog_t wd; + decl_simple_lock_data(, aha_lock) + unsigned int port; /* I/O port */ + + int ntargets; /* how many alive on this scsibus */ + + scsi_softc_t *sc; /* HBA-indep info */ + + struct aha_mb_ctl mb; /* mailbox structures */ + + /* This chicanery is for mapping back the phys address + of a CCB (which we get in an MBI) to its virtual */ + /* [we could use phystokv(), but it isn't standard] */ + vm_offset_t I_hold_my_phys_address; + struct aha_ccb_raw aha_ccbs[AHA_NCCB]; + +} aha_softc_data[NAHA]; + +typedef struct aha_softc *aha_softc_t; + +aha_softc_t aha_softc[NAHA]; + +struct aha_ccb_raw * +mb_to_rccb(aha, mbi) + aha_softc_t aha; + aha_mbox_t mbi; +{ + vm_offset_t addr; + + AHA_MB_GET_PTR(&mbi,addr); /* phys address of ccb */ + + /* make virtual */ + addr = ((vm_offset_t)&aha->I_hold_my_phys_address) + + (addr - aha->I_hold_my_phys_address); + + /* adjust by proper offset to get base */ + addr -= (vm_offset_t)&(((struct aha_ccb_raw *)0)->ccb); + + return (struct aha_ccb_raw *)addr; +} + +target_info_t * +aha_tgt_alloc(aha, id, sns_len, tgt) + aha_softc_t aha; + target_info_t *tgt; +{ + struct aha_ccb_raw *rccb; + + aha->ntargets++; + + if (tgt == 0) + tgt = scsi_slave_alloc(aha - aha_softc_data, id, aha); + + rccb = &(aha->aha_ccbs[id]); + rccb->ccb.ccb_reqsns_len = sns_len; + tgt->cmd_ptr = rccb_to_cmdptr(rccb); + tgt->dma_ptr = 0; +#ifdef CBUS + tgt->aha_cbus_window = 0; +#endif /* CBUS */ + return tgt; +} + +/* + * Synch xfer timing conversions + */ +#define aha_to_scsi_period(a) ((200 + ((a) * 50)) >> 2) +#define scsi_period_to_aha(p) ((((p) << 2) - 200) / 50) + +/* + * Definition of the controller for the auto-configuration program. + */ + +/* DOCUMENTATION */ +/* base ports can be: + 0x334, 0x330 (default), 0x234, 0x230, 0x134, 0x130 + possible interrupt channels are: + 9, 10, 11 (default), 12, 14, 15 + DMA channels can be: + 7, 6, 5 (default), 0 +/* DOCUMENTATION */ + +int aha_probe(), scsi_slave(), aha_go(), aha_intr(); +void scsi_attach(); + +vm_offset_t aha_std[NAHA] = { 0 }; +struct bus_device *aha_dinfo[NAHA*8]; +struct bus_ctlr *aha_minfo[NAHA]; +struct bus_driver aha_driver = + { aha_probe, scsi_slave, scsi_attach, aha_go, aha_std, "rz", aha_dinfo, + "ahac", aha_minfo, BUS_INTR_B4_PROBE}; + +#define DEBUG 1 +#if DEBUG + +#define PRINT(x) if (scsi_debug) printf x + +aha_state(port) +{ + register unsigned char st, intr; + + if (port == 0) + port = 0x330; + st = inb(AHA_STATUS_PORT(port)); + intr = inb(AHA_INTR_PORT(port)); + + printf("status %x intr %x\n", st, intr); + return 0; +} + +aha_target_state(tgt) + target_info_t *tgt; +{ + if (tgt == 0) + tgt = aha_softc[0]->sc->target[0]; + if (tgt == 0) + return 0; + printf("fl %x dma %X+%x cmd %x@%X id %x per %x off %x ior %X ret %X\n", + tgt->flags, tgt->dma_ptr, tgt->transient_state.dma_offset, tgt->cur_cmd, + tgt->cmd_ptr, tgt->target_id, tgt->sync_period, tgt->sync_offset, + tgt->ior, tgt->done); + + return 0; +} + +aha_all_targets(unit) +{ + int i; + target_info_t *tgt; + for (i = 0; i < 8; i++) { + tgt = aha_softc[unit]->sc->target[i]; + if (tgt) + aha_target_state(tgt); + } +} + +#define TRMAX 200 +int tr[TRMAX+3]; +int trpt, trpthi; +#define TR(x) tr[trpt++] = x +#define TRWRAP trpthi = trpt; trpt = 0; +#define TRCHECK if (trpt > TRMAX) {TRWRAP} + +#define TRACE + +#ifdef TRACE + +#define LOGSIZE 256 +#define LOG_KERN 0<<3 /* from syslog.h */ + +int aha_logpt; +char aha_log[LOGSIZE]; + +#define MAXLOG_VALUE 0x1e +struct { + char *name; + unsigned int count; +} logtbl[MAXLOG_VALUE]; + +static LOG(e,f) + char *f; +{ + aha_log[aha_logpt++] = (e); + if (aha_logpt == LOGSIZE) aha_logpt = 0; + if ((e) < MAXLOG_VALUE) { + logtbl[(e)].name = (f); + logtbl[(e)].count++; + } +} + +aha_print_log(skip) + int skip; +{ + register int i, j; + register unsigned char c; + + for (i = 0, j = aha_logpt; i < LOGSIZE; i++) { + c = aha_log[j]; + if (++j == LOGSIZE) j = 0; + if (skip-- > 0) + continue; + if (c < MAXLOG_VALUE) + printf(" %s", logtbl[c].name); + else + printf("-%x", c & 0x7f); + } + return 0; +} + +aha_print_stat() +{ + register int i; + register char *p; + for (i = 0; i < MAXLOG_VALUE; i++) { + if (p = logtbl[i].name) + printf("%d %s\n", logtbl[i].count, p); + } +} + +#else /*TRACE*/ +#define LOG(e,f) +#define LOGSIZE +#endif /*TRACE*/ + +#else /*DEBUG*/ +#define PRINT(x) +#define LOG(e,f) +#define LOGSIZE +#define TRCHECK +#define TR(a) + +#endif /*DEBUG*/ + +/* Utility functions at end */ + + +/* + * Probe/Slave/Attach functions + */ + +int aha_dotarget = 1; /* somehow on some boards this is trouble */ + +/* + * Probe routine: + * Should find out (a) if the controller is + * present and (b) which/where slaves are present. + * + * Implementation: + * Just ask the board to do it + */ +aha_probe(port, ui) + register port; + struct bus_ctlr *ui; +{ + int unit = ui->unit; + aha_softc_t aha = &aha_softc_data[unit]; + int target_id; + scsi_softc_t *sc; + spl_t s; + boolean_t did_banner = FALSE; + struct aha_devs installed; + struct aha_conf conf; + + /* No interrupts yet */ + s = splbio(); + + /* + * We should be called with a sensible port, but you never know. + * Send an echo command and see that we get it back properly + */ + { + register unsigned char st; + + st = inb(AHA_STATUS_PORT(port)); + + /* + * There is no board reset in case of reboot with + * no power-on/power-off sequence. Test it and do + * the reset if necessary. + */ + + if (!(st & AHA_CSR_INIT_REQ)) { + outb(AHA_CONTROL_PORT(port), + AHA_CTL_SOFT_RESET|AHA_CTL_HARD_RESET); + while ((st = inb(AHA_STATUS_PORT(port))) & + AHA_CSR_SELF_TEST); + } + if ((st & AHA_CSR_DATAO_FULL) || + !(st & AHA_CSR_INIT_REQ)) + goto fail; + + outb(AHA_COMMAND_PORT(port), AHA_CMD_ECHO); + delay(1000);/*?*/ + st = inb(AHA_STATUS_PORT(port)); + if (st & (AHA_CSR_CMD_ERR|AHA_CSR_DATAO_FULL)) + goto fail; + + outb(AHA_COMMAND_PORT(port), 0x5e); + delay(1000); + + st = inb(AHA_STATUS_PORT(port)); + if ((st & AHA_CSR_CMD_ERR) || + ((st & AHA_CSR_DATAI_FULL) == 0)) + goto fail; + + st = inb(AHA_DATA_PORT(port)); + if (st != 0x5e) { +fail: splx(s); + return 0; + } + /* + * augment test with check for echoing inverse and with + * test for enhanced adapter with standard ports enabled. + */ + + /* Check that 0xa1 echoed as well as 0x5e */ + + outb(AHA_COMMAND_PORT(port), AHA_CMD_ECHO); + delay(1000);/*?*/ + st = inb(AHA_STATUS_PORT(port)); + if (st & (AHA_CSR_CMD_ERR|AHA_CSR_DATAO_FULL)) + goto fail; + + outb(AHA_COMMAND_PORT(port), 0xa1); + delay(1000); + + st = inb(AHA_STATUS_PORT(port)); + if ((st & AHA_CSR_CMD_ERR) || + ((st & AHA_CSR_DATAI_FULL) == 0)) + goto fail; + + st = inb(AHA_DATA_PORT(port)); + if (st != 0xa1) + goto fail ; + + { /* Check that port isn't 174x in enhanced mode + with standard mode ports enabled. This should be + ignored because it will be caught and correctly + handled by eaha_probe(). See TRM4-11..13. + dph + */ + unsigned z ; + static unsigned port_table[] = + {0,0,0x130,0x134,0x230,0x234,0x330,0x334}; + for (z= 0x1000; z<= 0xF000; z+= 0x1000) + if (inb(z+0xC80) == 0x04 && + inb(z+0xC81) == 0x90 && + inb(z+0xCC0) & 0x80 == 0x80 && + port_table [inb(z+0xCC0) & 0x07] == port) + goto fail ; + } + outb(AHA_CONTROL_PORT(port), AHA_CTL_INTR_CLR); + } + +#if MAPPABLE + /* Mappable version side */ + AHA_probe(port, ui); +#endif /*MAPPABLE*/ + + /* + * Initialize hw descriptor, cache some pointers + */ + aha_softc[unit] = aha; + aha->port = port; + + sc = scsi_master_alloc(unit, aha); + aha->sc = sc; + + simple_lock_init(&aha->aha_lock); + sc->go = aha_go; + sc->watchdog = scsi_watchdog; + sc->probe = aha_probe_target; + aha->wd.reset = aha_reset_scsibus; + + /* Stupid limitation, no way around it */ + sc->max_dma_data = (AHA_MAX_SEGLIST-1) * MACHINE_PGBYTES; + + + /* XXX + * I'm not sure how much use this bit of code is really. + * On the 1542CF we don't really want to try and initialize + * the mailboxes before unlocking them in any case, and + * resetting the card is done above. + */ +#if 0 +#if 0 + /* + * Reset board. + */ + aha_reset(port, TRUE); +#else + /* + * Initialize mailboxes + */ + aha_init_1(aha); +#endif +#endif + + /* + * Who are we ? + */ + { + struct aha_inq inq; + struct aha_extbios extbios; + char *id; + + aha_command(port, AHA_CMD_INQUIRY, 0, 0, &inq, sizeof(inq), TRUE); + + switch (inq.board_id) { + case AHA_BID_1540_B16: + case AHA_BID_1540_B64: + id = "1540"; break; + case AHA_BID_1540B: + id = "1540B/1542B"; break; + case AHA_BID_1640: + id = "1640"; break; + case AHA_BID_1740: + id = "1740 Unsupported!!"; break; + case AHA_BID_1542C: + id = "1542C"; aha_dotarget = 0; break; + case AHA_BID_1542CF: + id = "1542CF"; break; + default: + id = 0; break; + } + + printf("Adaptec %s [id %x], rev %c%c, options x%x\n", + id ? id : "Board", + inq.board_id, inq.frl_1, inq.frl_2, inq.options); + + /* + * If we are a 1542C or 1542CF disable the extended bios + * so that the mailbox interface is unlocked. + * No need to check the extended bios flags as some of the + * extensions that cause us problems are not flagged in + * that byte. + */ + if (inq.board_id == 0x44 || inq.board_id == 0x45) { + aha_command(port, AHA_EXT_BIOS, 0, 0, &extbios, + sizeof(extbios), TRUE); +#ifdef AHADEBUG + printf("aha: extended bios flags 0x%x\n", extbios.flags); + printf("aha: mailboxlock 0x%x\n", extbios.mblock); +#endif /* AHADEBUG */ + + printf("aha: 1542C/CF detected, unlocking mailbox\n"); + + /* XXX - This sends the mailboxlock code out to the + * controller. We need to output a 0, then the + * code...so since we don't care about the flags + * anyway, we just zero out that field and re-use + * the struct. + */ + extbios.flags = 0; + aha_command(port, AHA_MBX_ENABLE, &extbios, + sizeof(extbios), 0, 0, TRUE); + } + + } +doconf: + /* + * Readin conf data + */ + aha_command(port, AHA_CMD_GET_CONFIG, 0, 0, &conf, sizeof(conf), TRUE); + + { + unsigned char args; + + /* + * Change the bus on/off times to not clash with + * other dma users. + */ + args = 7; + aha_command(port, AHA_CMD_SET_BUSON, &args, 1, 0, 0, TRUE); + args = 5; + aha_command(port, AHA_CMD_SET_BUSOFF, &args, 1, 0, 0, TRUE); + } + + /* XXX - This is _REALLY_ sickening. */ + /* + * Set up the DMA channel we'll be using. + */ + { + register int d, i; + static struct { + unsigned char port; + unsigned char init_data; + } aha_dma_init[8][2] = { + {{0x0b,0x0c}, {0x0a,0x00}}, /* channel 0 */ + {{0,0},{0,0}}, + {{0,0},{0,0}}, + {{0,0},{0,0}}, + {{0,0},{0,0}}, + {{0xd6,0xc1}, {0xd4,0x01}}, /* channel 5 (def) */ + {{0xd6,0xc2}, {0xd4,0x02}}, /* channel 6 */ + {{0xd6,0xc3}, {0xd4,0x03}} /* channel 7 */ + }; + + + for (i = 0; i < 8; i++) + if ((1 << i) & conf.intr_ch) break; + i += 9; + +#if there_was_a_way + /* + * On second unit, avoid clashes with first + */ + if ((unit > 0) && (ui->sysdep1 != i)) { + printf("Reprogramming irq and dma ch..\n"); + .... + goto doconf; + } +#endif + + /* + * Initialize the DMA controller viz the channel we'll use + */ + for (d = 0; d < 8; d++) + if ((1 << d) & conf.dma_arbitration) break; + + outb(aha_dma_init[d][0].port, aha_dma_init[d][0].init_data); + outb(aha_dma_init[d][1].port, aha_dma_init[d][1].init_data); + + /* make mapping phys->virt possible for CCBs */ + aha->I_hold_my_phys_address = + kvtoAT((vm_offset_t)&aha->I_hold_my_phys_address); + + /* + * Our SCSI ID. (xxx) On some boards this is SW programmable. + */ + sc->initiator_id = conf.my_scsi_id; + + printf("%s%d: [dma ch %d intr ch %d] my SCSI id is %d", + ui->name, unit, d, i, sc->initiator_id); + + /* Interrupt vector setup */ + ui->sysdep1 = i; + take_ctlr_irq(ui); + } + + /* + * More initializations + */ + { + register target_info_t *tgt; + + aha_init(aha); + + /* allocate a desc for tgt mode role */ + tgt = aha_tgt_alloc(aha, sc->initiator_id, 1, 0); + sccpu_new_initiator(tgt, tgt); /* self */ + + } + + /* Now we could take interrupts, BUT we do not want to + be selected as targets by some other host just yet */ + + /* + * For all possible targets, see if there is one and allocate + * a descriptor for it if it is there. + * This includes ourselves, when acting as target + */ + aha_command( port, AHA_CMD_FIND_DEVICES, 0, 0, &installed, sizeof(installed), TRUE); + for (target_id = 0; target_id < 8; target_id++) { + + if (target_id == sc->initiator_id) /* done already */ + continue; + + if (installed.tgt_luns[target_id] == 0) + continue; + + printf(",%s%d", did_banner++ ? " " : " target(s) at ", + target_id); + + /* Normally, only LUN 0 */ + if (installed.tgt_luns[target_id] != 1) + printf("(%x)", installed.tgt_luns[target_id]); + /* + * Found a target + */ + (void) aha_tgt_alloc(aha, target_id, 1/*no REQSNS*/, 0); + + } + printf(".\n"); + splx(s); + + return 1; +} + +boolean_t +aha_probe_target(tgt, ior) + target_info_t *tgt; + io_req_t ior; +{ + aha_softc_t aha = aha_softc[tgt->masterno]; + boolean_t newlywed; + + newlywed = (tgt->cmd_ptr == 0); + if (newlywed) { + /* desc was allocated afresh */ + (void) aha_tgt_alloc(aha,tgt->target_id, 1/*no REQSNS*/, tgt); + } + + if (scsi_inquiry(tgt, SCSI_INQ_STD_DATA) == SCSI_RET_DEVICE_DOWN) + return FALSE; + + tgt->flags = TGT_ALIVE; + return TRUE; +} + +aha_reset(port, quick) +{ + register unsigned char st; + + /* + * Reset board and wait till done + */ + outb(AHA_CONTROL_PORT(port), AHA_CTL_SOFT_RESET); + do { + delay(25); + st = inb(AHA_STATUS_PORT(port)); + } while ((st & (AHA_CSR_IDLE|AHA_CSR_INIT_REQ)) == 0); + + if (quick) return; + + /* + * reset the scsi bus. Does NOT generate an interrupt (bozos) + */ + outb(AHA_CONTROL_PORT(port), AHA_CTL_SCSI_RST); +} + +aha_init_1(aha) + aha_softc_t aha; +{ + struct aha_init a; + vm_offset_t phys; + + bzero(&aha->mb, sizeof(aha->mb)); /* also means all free */ + a.mb_count = AHA_NMBOXES; + phys = kvtoAT((vm_offset_t)&aha->mb); + AHA_ADDRESS_SET(a.mb_ptr, phys); + aha_command(aha->port, AHA_CMD_INIT, &a, sizeof(a), 0, 0, TRUE); +} + +aha_init_2(port) +{ + unsigned char disable = AHA_MBO_DISABLE; + struct aha_tgt role; + + /* Disable MBO available interrupt */ + aha_command(port, AHA_CMD_MBO_IE, &disable, 1, 0,0, FALSE); + + if (aha_dotarget) { + /* Enable target mode role */ + role.enable = 1; + role.luns = 1; /* only LUN 0 */ + aha_command(port, AHA_CMD_ENB_TGT_MODE, &role, sizeof(role), 0, 0, TRUE); + } +} + +aha_init(aha) + aha_softc_t aha; +{ + aha_init_1(aha); + aha_init_2(aha->port); +} + +/* + * Operational functions + */ + +/* + * Start a SCSI command on a target + */ +aha_go(tgt, cmd_count, in_count, cmd_only) + target_info_t *tgt; + boolean_t cmd_only; +{ + aha_softc_t aha; + spl_t s; + struct aha_ccb_raw *rccb; + int len; + vm_offset_t virt, phys; + +#if CBUS + at386_io_lock_state(); +#endif + + LOG(1,"go"); + + aha = (aha_softc_t)tgt->hw_state; + +/* XXX delay the handling of the ccb till later */ + rccb = &(aha->aha_ccbs[tgt->target_id]); + rccb->active_target = tgt; + + /* + * We can do real DMA. + */ +/* tgt->transient_state.copy_count = 0; unused */ +/* tgt->transient_state.dma_offset = 0; unused */ + + tgt->transient_state.cmd_count = cmd_count; + + if ((tgt->cur_cmd == SCSI_CMD_WRITE) || + (tgt->cur_cmd == SCSI_CMD_LONG_WRITE)){ + io_req_t ior = tgt->ior; + register int len = ior->io_count; + + tgt->transient_state.out_count = len; + + /* How do we avoid leaks here ? Trust the board + will do zero-padding, for now. XXX CHECKME */ +#if 0 + if (len < tgt->block_size) { + bzero(to + len, tgt->block_size - len); + len = tgt->block_size; + tgt->transient_state.out_count = len; + } +#endif + } else { + tgt->transient_state.out_count = 0; + } + + /* See above for in_count < block_size */ + tgt->transient_state.in_count = in_count; + + /* + * Setup CCB state + */ + tgt->done = SCSI_RET_IN_PROGRESS; + + switch (tgt->cur_cmd) { + case SCSI_CMD_READ: + case SCSI_CMD_LONG_READ: + LOG(9,"readop"); + virt = (vm_offset_t)tgt->ior->io_data; + len = tgt->transient_state.in_count; + rccb->ccb.ccb_in = 1; rccb->ccb.ccb_out = 0; + break; + case SCSI_CMD_WRITE: + case SCSI_CMD_LONG_WRITE: + LOG(0x1a,"writeop"); + virt = (vm_offset_t)tgt->ior->io_data; + len = tgt->transient_state.out_count; + rccb->ccb.ccb_in = 0; rccb->ccb.ccb_out = 1; + break; + case SCSI_CMD_INQUIRY: + case SCSI_CMD_REQUEST_SENSE: + case SCSI_CMD_MODE_SENSE: + case SCSI_CMD_RECEIVE_DIAG_RESULTS: + case SCSI_CMD_READ_CAPACITY: + case SCSI_CMD_READ_BLOCK_LIMITS: + case SCSI_CMD_READ_TOC: + case SCSI_CMD_READ_SUBCH: + case SCSI_CMD_READ_HEADER: + case 0xc4: /* despised: SCSI_CMD_DEC_PLAYBACK_STATUS */ + case 0xc6: /* despised: SCSI_CMD_TOSHIBA_READ_SUBCH_Q */ + case 0xc7: /* despised: SCSI_CMD_TOSHIBA_READ_TOC_ENTRY */ + case 0xdd: /* despised: SCSI_CMD_NEC_READ_SUBCH_Q */ + case 0xde: /* despised: SCSI_CMD_NEC_READ_TOC */ + LOG(0x1c,"cmdop"); + LOG(0x80+tgt->cur_cmd,0); + virt = (vm_offset_t)tgt->cmd_ptr; + len = tgt->transient_state.in_count; + rccb->ccb.ccb_in = 1; rccb->ccb.ccb_out = 0; + break; + case SCSI_CMD_MODE_SELECT: + case SCSI_CMD_REASSIGN_BLOCKS: + case SCSI_CMD_FORMAT_UNIT: + case 0xc9: /* vendor-spec: SCSI_CMD_DEC_PLAYBACK_CONTROL */ + { register int cs = sizeof_scsi_command(tgt->cur_cmd); + tgt->transient_state.cmd_count = cs; + len = + tgt->transient_state.out_count = cmd_count - cs; + virt = (vm_offset_t)tgt->cmd_ptr + cs; + rccb->ccb.ccb_in = 0; rccb->ccb.ccb_out = 1; + LOG(0x1c,"cmdop"); + LOG(0x80+tgt->cur_cmd,0); + } + break; + default: + LOG(0x1c,"cmdop"); + LOG(0x80+tgt->cur_cmd,0); + virt = 0; + len = 0; + rccb->ccb.ccb_in = 0; rccb->ccb.ccb_out = 0; + } + +#if CBUS + at386_io_lock(MP_DEV_WAIT); +#endif + aha_prepare_rccb(tgt, rccb, virt, len); + + rccb->ccb.ccb_lun = tgt->lun; + rccb->ccb.ccb_scsi_id = tgt->target_id; + +/* AHA_LENGTH_SET(rccb->ccb.ccb_linkptr, 0); unused */ +/* rccb->ccb.ccb_linkid = 0; unused */ + +#if !CBUS + s = splbio(); +#endif + + LOG(3,"enqueue"); + + aha_start_scsi(aha, &rccb->ccb); + +#if CBUS + at386_io_unlock(); +#else + splx(s); +#endif +} + +aha_prepare_rccb(tgt, rccb, virt, len) + target_info_t *tgt; + struct aha_ccb_raw *rccb; + vm_offset_t virt; + vm_size_t len; +{ + vm_offset_t phys; +#ifdef CBUS + int cbus_window; +#endif /* CBUS */ + + rccb->ccb.ccb_cmd_len = tgt->transient_state.cmd_count; + + /* this opcode is refused, grrrr. */ +/* rccb->ccb.ccb_code = AHA_CCB_I_CMD_R; /* default common case */ + rccb->ccb.ccb_code = AHA_CCB_I_CMD; /* default common case */ + AHA_LENGTH_SET(rccb->ccb.ccb_datalen, len);/* default common case */ + +#ifdef CBUS + if (tgt->aha_cbus_window == 0) + tgt->aha_cbus_window = cbus_alloc_win(AHA_MAX_SEGLIST+1); + cbus_window = tgt->aha_cbus_window; +#endif /* CBUS */ + + if (virt == 0) { + /* no xfers */ + AHA_ADDRESS_SET(rccb->ccb.ccb_dataptr, 0); + } else if (len <= MACHINE_PGBYTES) { +/* INCORRECT: what if across two pages :INCORRECT */ + /* simple xfer */ +#ifdef CBUS + phys = cbus_kvtoAT_ww(virt, cbus_window); +#else /* CBUS */ + phys = kvtophys(virt); +#endif /* CBUS */ + AHA_ADDRESS_SET(rccb->ccb.ccb_dataptr, phys); + } else { + /* messy xfer */ + aha_seglist_t *seglist; + vm_offset_t ph1, off; + vm_size_t l1; + + /* this opcode does not work, grrrrr */ +/* rccb->ccb.ccb_code = AHA_CCB_I_CMD_SG_R;*/ + rccb->ccb.ccb_code = AHA_CCB_I_CMD_SG; + + if (tgt->dma_ptr == 0) + aha_alloc_segment_list(tgt); + seglist = (aha_seglist_t *) tgt->dma_ptr; +#ifdef CBUS + phys = cbus_kvtoAT_ww(seglist, cbus_window); + cbus_window++; +#else /* CBUS */ + phys = kvtophys((vm_offset_t) seglist); +#endif /* CBUS */ + AHA_ADDRESS_SET(rccb->ccb.ccb_dataptr, phys); + + ph1 = /*i386_trunc_page*/ virt & ~(MACHINE_PGBYTES - 1); + off = virt & (MACHINE_PGBYTES - 1); +#ifdef CBUS + ph1 = cbus_kvtoAT_ww(ph1, cbus_window) + off; + cbus_window++; +#else /* CBUS */ + ph1 = kvtophys(ph1) + off; +#endif /* CBUS */ + l1 = MACHINE_PGBYTES - off; + + off = 1;/* now #pages */ + while (1) { + AHA_ADDRESS_SET(seglist->ptr, ph1); + AHA_LENGTH_SET(seglist->len, l1); + seglist++; + + if ((len -= l1) <= 0) + break; + virt += l1; off++; + +#ifdef CBUS + ph1 = cbus_kvtoAT_ww(virt, cbus_window); + cbus_window++; +#else /* CBUS */ + ph1 = kvtophys(virt); +#endif /* CBUS */ + l1 = (len > MACHINE_PGBYTES) ? MACHINE_PGBYTES : len; + } + l1 = off * sizeof(*seglist); + AHA_LENGTH_SET(rccb->ccb.ccb_datalen, l1); + } +} + +aha_start_scsi(aha, ccb) + aha_softc_t aha; + aha_ccb_t *ccb; +{ + register aha_mbox_t *mb; + register idx; + vm_offset_t phys; + aha_mbox_t mbo; + spl_t s; + + LOG(4,"start"); + LOG(0x80+ccb->ccb_scsi_id,0); + + /* + * Get an MBO, spin if necessary (takes little time) + */ + s = splbio(); + phys = kvtoAT((vm_offset_t)ccb); + /* might cross pages, but should be ok (kernel is contig) */ + AHA_MB_SET_PTR(&mbo,phys); + mbo.mb.mb_cmd = AHA_MBO_START; + + simple_lock(&aha->aha_lock); + if (aha->wd.nactive++ == 0) + aha->wd.watchdog_state = SCSI_WD_ACTIVE; + idx = aha->mb.oidx; + aha->mb.oidx = next_mbx_idx(idx); + mb = &aha->mb.omb[idx]; + while (mb->mb.mb_status != AHA_MBO_FREE) + delay(1); + mb->bits = mbo.bits; + simple_unlock(&aha->aha_lock); + + /* + * Start the board going + */ + aha_command(aha->port, AHA_CMD_START, 0, 0, 0, 0, FALSE); + splx(s); +} + +/* + * Interrupt routine + * Take interrupts from the board + * + * Implementation: + * TBD + */ +aha_intr(unit) +{ + register aha_softc_t aha; + register port; + register csr, intr; +#if MAPPABLE + extern boolean_t rz_use_mapped_interface; + + if (rz_use_mapped_interface) + return AHA_intr(unit); +#endif /*MAPPABLE*/ + + aha = aha_softc[unit]; + port = aha->port; + + LOG(5,"\n\tintr"); +gotintr: + /* collect ephemeral information */ + csr = inb(AHA_STATUS_PORT(port)); + intr = inb(AHA_INTR_PORT(port)); + + /* + * Check for errors + */ + if (csr & (AHA_CSR_DIAG_FAIL|AHA_CSR_CMD_ERR)) { +/* XXX */ gimmeabreak(); + } + + /* drop spurious interrupts */ + if ((intr & AHA_INTR_PENDING) == 0) { + LOG(2,"SPURIOUS"); + return; + } + outb(AHA_CONTROL_PORT(port), AHA_CTL_INTR_CLR); + +TR(csr);TR(intr);TRCHECK + + if (intr & AHA_INTR_RST) + return aha_bus_reset(aha); + + /* we got an interrupt allright */ + if (aha->wd.nactive) + aha->wd.watchdog_state = SCSI_WD_ACTIVE; + + if (intr == AHA_INTR_DONE) { + /* csr & AHA_CSR_CMD_ERR --> with error */ + LOG(6,"done"); + return; + } + +/* if (intr & AHA_INTR_MBO_AVAIL) will not happen */ + + /* Some real work today ? */ + if (intr & AHA_INTR_MBI_FULL) { + register int idx; + register aha_mbox_t *mb; + int nscan = 0; + aha_mbox_t mbi; +rescan: + simple_lock(&aha->aha_lock); + idx = aha->mb.iidx; + aha->mb.iidx = next_mbx_idx(idx); + mb = &aha->mb.imb[idx]; + mbi.bits = mb->bits; + mb->mb.mb_status = AHA_MBI_FREE; + simple_unlock(&aha->aha_lock); + + nscan++; + + switch (mbi.mb.mb_status) { + + case AHA_MBI_FREE: + if (nscan >= AHA_NMBOXES) + return; + goto rescan; + break; + + case AHA_MBI_SUCCESS: + case AHA_MBI_ERROR: + aha_initiator_intr(aha, mbi); + break; + + case AHA_MBI_NEED_CCB: + aha_target_intr(aha, mbi); + break; + +/* case AHA_MBI_ABORTED: /* this we wont see */ +/* case AHA_MBI_NOT_FOUND: /* this we wont see */ + default: + log( LOG_KERN, + "aha%d: Bogus status (x%x) in MBI\n", + unit, mbi.mb.mb_status); + break; + } + + /* peek ahead */ + if (aha->mb.imb[aha->mb.iidx].mb.mb_status != AHA_MBI_FREE) + goto rescan; + } + + /* See if more work ready */ + if (inb(AHA_INTR_PORT(port)) & AHA_INTR_PENDING) { + LOG(7,"\n\tre-intr"); + goto gotintr; + } +} + +/* + * The interrupt routine turns to one of these two + * functions, depending on the incoming mbi's role + */ +aha_target_intr(aha, mbi) + aha_softc_t aha; + aha_mbox_t mbi; +{ + target_info_t *initiator; /* this is the caller */ + target_info_t *self; /* this is us */ + int len; + + if (mbi.mbt.mb_cmd != AHA_MBI_NEED_CCB) + gimmeabreak(); + + /* If we got here this is not zero .. */ + self = aha->sc->target[aha->sc->initiator_id]; + + initiator = aha->sc->target[mbi.mbt.mb_initiator_id]; + /* ..but initiators are not required to answer to our inquiry */ + if (initiator == 0) { + /* allocate */ + initiator = aha_tgt_alloc(aha, mbi.mbt.mb_initiator_id, + sizeof(scsi_sense_data_t) + 5, 0); + + /* We do not know here wether the host was down when + we inquired, or it refused the connection. Leave + the decision on how we will talk to it to higher + level code */ + LOG(0xC, "new_initiator"); + sccpu_new_initiator(self, initiator); + } + + /* The right thing to do would be build an ior + and call the self->dev_ops->strategy routine, + but we cannot allocate it at interrupt level. + Also note that we are now disconnected from the + initiator, no way to do anything else with it + but reconnect and do what it wants us to do */ + + /* obviously, this needs both spl and MP protection */ + self->dev_info.cpu.req_pending = TRUE; + self->dev_info.cpu.req_id = mbi.mbt.mb_initiator_id; + self->dev_info.cpu.req_lun = mbi.mbt.mb_lun; + self->dev_info.cpu.req_cmd = + mbi.mbt.mb_isa_send ? SCSI_CMD_SEND: SCSI_CMD_RECEIVE; + len = (mbi.mbt.mb_data_len_msb << 16) | + (mbi.mbt.mb_data_len_mid << 8 ); + len += 0x100;/* truncation problem */ + self->dev_info.cpu.req_len = len; + + LOG(0xB,"tgt-mode-restart"); + (*self->dev_ops->restart)( self, FALSE); + + /* The call above has either prepared the data, + placing an ior on self, or it handled it some + other way */ + if (self->ior == 0) + return; /* I guess we'll do it later */ + + { + struct aha_ccb_raw *rccb; + + rccb = &(aha->aha_ccbs[initiator->target_id]); + rccb->active_target = initiator; + if (self->dev_info.cpu.req_cmd == SCSI_CMD_SEND) { + rccb->ccb.ccb_in = 1; + rccb->ccb.ccb_out = 0; + } else { + rccb->ccb.ccb_in = 0; + rccb->ccb.ccb_out = 1; + } + + aha_prepare_rccb(initiator, rccb, + (vm_offset_t)self->ior->io_data, self->ior->io_count); + rccb->ccb.ccb_code = AHA_CCB_T_CMD; + rccb->ccb.ccb_lun = initiator->lun; + rccb->ccb.ccb_scsi_id = initiator->target_id; + + simple_lock(&aha->aha_lock); + if (aha->wd.nactive++ == 0) + aha->wd.watchdog_state = SCSI_WD_ACTIVE; + simple_unlock(&aha->aha_lock); + + aha_start_scsi(aha, &rccb->ccb); + } +} + +aha_initiator_intr(aha, mbi) + aha_softc_t aha; + aha_mbox_t mbi; +{ + struct aha_ccb_raw *rccb; + scsi2_status_byte_t status; + target_info_t *tgt; + + rccb = mb_to_rccb(aha,mbi); + tgt = rccb->active_target; + rccb->active_target = 0; + + /* shortcut (sic!) */ + if (mbi.mb.mb_status == AHA_MBI_SUCCESS) + goto allok; + + switch (rccb->ccb.ccb_hstatus) { + case AHA_HST_SUCCESS: +allok: + status = rccb->ccb.ccb_status; + if (status.st.scsi_status_code != SCSI_ST_GOOD) { + scsi_error(tgt, SCSI_ERR_STATUS, status.bits, 0); + tgt->done = (status.st.scsi_status_code == SCSI_ST_BUSY) ? + SCSI_RET_RETRY : SCSI_RET_NEED_SENSE; + } else + tgt->done = SCSI_RET_SUCCESS; + break; + case AHA_HST_SEL_TIMEO: + if (tgt->flags & TGT_FULLY_PROBED) + tgt->flags = 0; /* went offline */ + tgt->done = SCSI_RET_DEVICE_DOWN; + break; + case AHA_HST_DATA_OVRUN: + /* BUT we don't know if this is an underrun. + It is ok if we get less data than we asked + for, in a number of cases. Most boards do not + seem to generate this anyways, but some do. */ + { register int cmd = tgt->cur_cmd; + switch (cmd) { + case SCSI_CMD_INQUIRY: + case SCSI_CMD_REQUEST_SENSE: + break; + default: + printf("%sx%x\n", + "aha: U/OVRUN on scsi command x%x\n", + cmd); + gimmeabreak(); + } + } + goto allok; + case AHA_HST_BAD_DISCONN: + printf("aha: bad disconnect\n"); + tgt->done = SCSI_RET_ABORTED; + break; + case AHA_HST_BAD_PHASE_SEQ: + /* we'll get an interrupt soon */ + printf("aha: bad PHASE sequencing\n"); + tgt->done = SCSI_RET_ABORTED; + break; + case AHA_HST_BAD_OPCODE: /* fall through */ + case AHA_HST_BAD_PARAM: +printf("aha: BADCCB\n");gimmeabreak(); + tgt->done = SCSI_RET_RETRY; + break; + case AHA_HST_BAD_LINK_LUN: /* these should not happen */ + case AHA_HST_INVALID_TDIR: + case AHA_HST_DUPLICATED_CCB: + printf("aha: bad hstatus (x%x)\n", rccb->ccb.ccb_hstatus); + tgt->done = SCSI_RET_ABORTED; + break; + } + + LOG(8,"end"); + + simple_lock(&aha->aha_lock); + if (aha->wd.nactive-- == 1) + aha->wd.watchdog_state = SCSI_WD_INACTIVE; + simple_unlock(&aha->aha_lock); + + if (tgt->ior) { + LOG(0xA,"ops->restart"); + (*tgt->dev_ops->restart)( tgt, TRUE); + } + + return FALSE; +} + +/* + * The bus was reset + */ +aha_bus_reset(aha) + register aha_softc_t aha; +{ + register port = aha->port; + + LOG(0x1d,"bus_reset"); + + /* + * Clear bus descriptor + */ + aha->wd.nactive = 0; + aha_reset(port, TRUE); + aha_init(aha); + + printf("aha: (%d) bus reset ", ++aha->wd.reset_count); + delay(scsi_delay_after_reset); /* some targets take long to reset */ + + if (aha->sc == 0) /* sanity */ + return; + + scsi_bus_was_reset(aha->sc); +} + +/* + * Watchdog + * + * We know that some (name withdrawn) disks get + * stuck in the middle of dma phases... + */ +aha_reset_scsibus(aha) + register aha_softc_t aha; +{ + register target_info_t *tgt; + register port = aha->port; + register int i; + + for (i = 0; i < AHA_NCCB; i++) { + tgt = aha->aha_ccbs[i].active_target; + if (/*scsi_debug &&*/ tgt) + printf("Target %d was active, cmd x%x in x%x out x%x\n", + tgt->target_id, tgt->cur_cmd, + tgt->transient_state.in_count, + tgt->transient_state.out_count); + } + aha_reset(port, FALSE); + delay(35); + /* no interrupt will come */ + aha_bus_reset(aha); +} + +/* + * Utilities + */ + +/* + * Send a command to the board along with some + * optional parameters, optionally receive the + * results at command completion, returns how + * many bytes we did NOT get back. + */ +aha_command(port, cmd, outp, outc, inp, inc, clear_interrupt) + unsigned char *outp, *inp; +{ + register unsigned char st; + boolean_t failed = TRUE; + + do { + st = inb(AHA_STATUS_PORT(port)); + } while (st & AHA_CSR_DATAO_FULL); + + /* Output command and any data */ + outb(AHA_COMMAND_PORT(port), cmd); + while (outc--) { + do { + st = inb(AHA_STATUS_PORT(port)); + if (st & AHA_CSR_CMD_ERR) goto out; + } while (st & AHA_CSR_DATAO_FULL); + + outb(AHA_COMMAND_PORT(port), *outp++); + } + + /* get any data */ + while (inc--) { + do { + st = inb(AHA_STATUS_PORT(port)); + if (st & AHA_CSR_CMD_ERR) goto out; + } while ((st & AHA_CSR_DATAI_FULL) == 0); + + *inp++ = inb(AHA_DATA_PORT(port)); + } + ++inc; + failed = FALSE; + + /* wait command complete */ + if (clear_interrupt) do { + delay(1); + st = inb(AHA_INTR_PORT(port)); + } while ((st & AHA_INTR_DONE) == 0); + +out: + if (clear_interrupt) + outb(AHA_CONTROL_PORT(port), AHA_CTL_INTR_CLR); + if (failed) + printf("aha_command: error on (%x %x %x %x %x %x), status %x\n", + port, cmd, outp, outc, inp, inc, st); + return inc; +} + +#include + +/* + * Allocate dynamically segment lists to + * targets (for scatter/gather) + * Its a max of 17*6=102 bytes per target. + */ +vm_offset_t aha_seglist_next, aha_seglist_end; + +aha_alloc_segment_list(tgt) + target_info_t *tgt; +{ +#define ALLOC_SIZE (AHA_MAX_SEGLIST * sizeof(aha_seglist_t)) + +/* XXX locking */ + if ((aha_seglist_next + ALLOC_SIZE) > aha_seglist_end) { + (void) kmem_alloc_wired(kernel_map, &aha_seglist_next, PAGE_SIZE); + aha_seglist_end = aha_seglist_next + PAGE_SIZE; + } + tgt->dma_ptr = (char *)aha_seglist_next; + aha_seglist_next += ALLOC_SIZE; +/* XXX locking */ +} + +#endif /* NAHA > 0 */ + diff --git a/scsi/adapters/scsi_aha17_hdw.c b/scsi/adapters/scsi_aha17_hdw.c new file mode 100644 index 0000000..d8afe6a --- /dev/null +++ b/scsi/adapters/scsi_aha17_hdw.c @@ -0,0 +1,1371 @@ +/* + * Mach Operating System + * Copyright (c) 1993 Carnegie Mellon University + * Copyright (c) 1993 University of Dublin + * All Rights Reserved. + * + * Permission to use, copy, modify and distribute this software and its + * documentation is hereby granted, provided that both the copyright + * notice and the following permission notice appear in all copies of the + * software, derivative works or modified versions, and any portions + * thereof, and that both notices appear in supporting documentation. + * + * CARNEGIE MELLON AND THE UNIVERSITY OF DUBLIN ALLOW FREE USE OF + * THIS SOFTWARE IN ITS "AS IS" CONDITION. CARNEGIE MELLON AND THE + * UNIVERSITY OF DUBLIN DISCLAIM ANY LIABILITY OF ANY KIND FOR + * ANY DAMAGES WHATSOEVER RESULTING FROM THE USE OF THIS SOFTWARE. + * + * Carnegie Mellon requests users of this software to return to + * + * Software Distribution Coordinator or Software.Distribution@CS.CMU.EDU + * School of Computer Science + * Carnegie Mellon University + * Pittsburgh PA 15213-3890 + * + * any improvements or extensions that they make and grant Carnegie Mellon + * the rights to redistribute these changes. + */ +/* + * Support for AHA-174x in enhanced mode. Dominic Herity (dherity@cs.tcd.ie) + * Will refer to "Adaptec AHA-1740A/1742A/1744 Technical Reference Manual" + * page x-y as TRMx-y in comments below. + */ + +#include +#if NEAHA > 0 + +#define db_printf printf + +#include +#include +#include + +#ifdef OSF +#include +#else +#include +#endif + +#include +#include +#include +#include + +#include +#include +#include + +#include +#include + +#ifdef AT386 +#define MACHINE_PGBYTES I386_PGBYTES +#define MAPPABLE 0 +#define gimmeabreak() asm("int3") + + +#include /* inlining of outb and inb */ +#ifdef OSF +#include +#endif +#endif /*AT386*/ + +#ifdef CBUS +#include +#endif + + +#ifndef MACHINE_PGBYTES /* cross compile check */ +#define MACHINE_PGBYTES 0x1000 +#define MAPPABLE 1 +#define gimmeabreak() Debugger("gimmeabreak"); +#endif + +int eaha_probe(), scsi_slave(), eaha_go(), eaha_intr(); +void scsi_attach(); + +vm_offset_t eaha_std[NEAHA] = { 0 }; +struct bus_device *eaha_dinfo[NEAHA*8]; +struct bus_ctlr *eaha_minfo[NEAHA]; +struct bus_driver eaha_driver = + { eaha_probe, scsi_slave, scsi_attach, eaha_go, eaha_std, "rz", + eaha_dinfo, "eahac", eaha_minfo, BUS_INTR_B4_PROBE}; + + +#define TRACE +#ifdef TRACE + +#define LOGSIZE 256 +int eaha_logpt; +char eaha_log[LOGSIZE]; + +#define MAXLOG_VALUE 0x1e +struct { + char *name; + unsigned int count; +} logtbl[MAXLOG_VALUE]; + +static LOG( + int e, + char *f) +{ + eaha_log[eaha_logpt++] = (e); + if (eaha_logpt == LOGSIZE) eaha_logpt = 0; + if ((e) < MAXLOG_VALUE) { + logtbl[(e)].name = (f); + logtbl[(e)].count++; + } +} + +eaha_print_log( + int skip) +{ + register int i, j; + register unsigned char c; + + for (i = 0, j = eaha_logpt; i < LOGSIZE; i++) { + c = eaha_log[j]; + if (++j == LOGSIZE) j = 0; + if (skip-- > 0) + continue; + if (c < MAXLOG_VALUE) + db_printf(" %s", logtbl[c].name); + else + db_printf("-%x", c & 0x7f); + } + return 0; +} + +eaha_print_stat() +{ + register int i; + register char *p; + for (i = 0; i < MAXLOG_VALUE; i++) { + if (p = logtbl[i].name) + printf("%d %s\n", logtbl[i].count, p); + } +} + +#else /*TRACE*/ +#define LOG(e,f) +#define LOGSIZE +#endif /*TRACE*/ + +#ifdef DEBUG +#define ASSERT(x) { if (!(x)) gimmeabreak() ; } +#define MARK() gimmeabreak() +#else +#define ASSERT(x) +#define MARK() +#endif + +/* + * Notes : + * + * do each host command TRM6-4 + * find targets in probe + * disable SCSI writes + * matching port with structs, eaha_go with port, eaha_intr with port + * + */ + +/* eaha registers. See TRM4-11..23. dph */ + +#define HID0(z) ((z)+0xC80) +#define HID1(z) ((z)+0xC81) +#define HID2(z) ((z)+0xC82) +#define HID3(z) ((z)+0xC83) +#define EBCTRL(z) ((z)+0xC84) +#define PORTADDR(z) ((z)+0xCC0) +#define BIOSADDR(z) ((z)+0xCC1) +#define INTDEF(z) ((z)+0xCC2) +#define SCSIDEF(z) ((z)+0xCC3) +#define MBOXOUT0(z) ((z)+0xCD0) +#define MBOXOUT1(z) ((z)+0xCD1) +#define MBOXOUT2(z) ((z)+0xCD2) +#define MBOXOUT3(z) ((z)+0xCD3) +#define MBOXIN0(z) ((z)+0xCD8) +#define MBOXIN1(z) ((z)+0xCD9) +#define MBOXIN2(z) ((z)+0xCDA) +#define MBOXIN3(z) ((z)+0xCDB) +#define ATTN(z) ((z)+0xCD4) +#define G2CNTRL(z) ((z)+0xCD5) +#define G2INTST(z) ((z)+0xCD6) +#define G2STAT(z) ((z)+0xCD7) +#define G2STAT2(z) ((z)+0xCDC) + +/* + * Enhanced mode data structures: ring, enhanced ccbs, a per target buffer + */ + +#define SCSI_TARGETS 8 /* Allow for SCSI-2 */ + + +/* Extended Command Control Block Format. See TRM6-3..12. */ + +typedef struct { + unsigned short command ; +# define EAHA_CMD_NOP 0 +# define EAHA_CMD_INIT_CMD 1 +# define EAHA_CMD_DIAG 5 +# define EAHA_CMD_INIT_SCSI 6 +# define EAHA_CMD_READ_SENS 8 +# define EAHA_CMD_DOWNLOAD 9 +# define EAHA_CMD_HOST_INQ 0x0a +# define EAHA_CMD_TARG_CMD 0x10 + + /* + * It appears to be customary to tackle the endian-ness of + * bit fields as follows, so I won't deviate. However, nothing in + * K&R implies that bit fields are implemented so that the fields + * of an unsigned char are allocated lsb first. Indeed, K&R _warns_ + * _against_ using bit fields to describe storage allocation. + * This issue is separate from endian-ness. dph + * And this is exactly the reason macros are used. If your compiler + * is weird just override the macros and we will all be happy. af + */ + BITFIELD_3(unsigned char, + cne:1, + xxx0:6, + di:1) ; + BITFIELD_7(unsigned char, + xxx1:2, + ses:1, + xxx2:1, + sg:1, + xxx3:1, + dsb:1, + ars:1) ; + + BITFIELD_5(unsigned char, + lun:3, + tag:1, + tt:2, + nd:1, + xxx4:1) ; + BITFIELD_7(unsigned char, + dat:1, + dir:1, + st:1, + chk:1, + xxx5:2, + rec:1, + nbr:1) ; + + unsigned short xxx6 ; + + vm_offset_t scather ; /* scatter/gather */ + unsigned scathlen ; + vm_offset_t status ; + vm_offset_t chain ; + int xxx7 ; + + vm_offset_t sense_p ; + unsigned char sense_len ; + unsigned char cdb_len ; + unsigned short checksum ; + scsi_command_group_5 cdb ; + unsigned char buffer[256] ; /* space for data returned. */ + +} eccb ; + +#define NTARGETS (8) +#define NECCBS (NTARGETS+2) /* Targets + 2 to allow for temporaries. */ + /* Can be up to 64 (TRM6-2), but that entails lots of bss usage */ + +typedef struct { /* Status Block Format. See TRM6-13..19. */ + BITFIELD_8(unsigned char, + don:1, + du:1, + xxx0:1, + qf:1, + sc:1, + dover:1, + ch:1, + inti:1) ; + BITFIELD_8(unsigned char, + asa:1, /* Error in TRM6-15..16 says both asa and sns */ + sns:1, /* bit 9. Bits 8 and 10 are not mentioned. */ + xxx1:1, + ini:1, + me:1, + xxx2:1, + eca:1, + xxx3:1) ; + + unsigned char ha_status ; +# define HA_STATUS_SUCCESS 0x00 +# define HA_STATUS_HOST_ABORTED 0x04 +# define HA_STATUS_ADP_ABORTED 0x05 +# define HA_STATUS_NO_FIRM 0x08 +# define HA_STATUS_NOT_TARGET 0x0a +# define HA_STATUS_SEL_TIMEOUT 0x11 +# define HA_STATUS_OVRUN 0x12 +# define HA_STATUS_BUS_FREE 0x13 +# define HA_STATUS_PHASE_ERROR 0x14 +# define HA_STATUS_BAD_OPCODE 0x16 +# define HA_STATUS_INVALID_LINK 0x17 +# define HA_STATUS_BAD_CBLOCK 0x18 +# define HA_STATUS_DUP_CBLOCK 0x19 +# define HA_STATUS_BAD_SCATHER 0x1a +# define HA_STATUS_RSENSE_FAIL 0x1b +# define HA_STATUS_TAG_REJECT 0x1c +# define HA_STATUS_HARD_ERROR 0x20 +# define HA_STATUS_TARGET_NOATTN 0x21 +# define HA_STATUS_HOST_RESET 0x22 +# define HA_STATUS_OTHER_RESET 0x23 +# define HA_STATUS_PROG_BAD_SUM 0x80 + + scsi2_status_byte_t target_status ; + + unsigned residue ; + vm_offset_t residue_buffer ; + unsigned short add_stat_len ; + unsigned char sense_len ; + char xxx4[9] ; + unsigned char cdb[6] ; + +} status_block ; + +typedef struct { + vm_offset_t ptr ; + unsigned len ; +} scather_entry ; + +#define SCATHER_ENTRIES 128 /* TRM 6-11 */ + +struct erccbx { + target_info_t *active_target; + eccb _eccb; + status_block status ; + struct erccbx *next ; +} ; + +typedef struct erccbx erccb ; + +/* forward decls */ +int eaha_reset_scsibus(); +boolean_t eaha_probe_target(); + +/* + * State descriptor for this layer. There is one such structure + * per (enabled) board + */ +typedef struct { + watchdog_t wd; + decl_simple_lock_data(, aha_lock) + int port; /* I/O port */ + + int has_sense_info [NTARGETS]; + int sense_info_lun [NTARGETS]; + /* 1742 enhanced mode will hang if target has + * sense info and host doesn't request it (TRM6-34). + * This sometimes happens in the scsi driver. + * These flags indicate when a target has sense + * info to disgorge. + * If set, eaha_go reads and discards sense info + * before running any command except request sense. + * dph + */ + + scsi_softc_t *sc; /* HBA-indep info */ + + erccb _erccbs[NECCBS] ; /* mailboxes */ + erccb *toperccb ; + + /* This chicanery is for mapping back the phys address + of a CCB (which we get in an MBI) to its virtual */ + /* [we could use phystokv(), but it isn't standard] */ + vm_offset_t I_hold_my_phys_address; + + char host_inquiry_data[256] ; /* Check out ../scsi2.h */ + +} eaha_softc ; + +eaha_softc eaha_softc_data[NEAHA]; + +typedef eaha_softc *eaha_softc_t; + +eaha_softc_t eaha_softc_pool[NEAHA]; + +int eaha_quiet ; + +erccb *erccb_alloc( + eaha_softc *eaha) +{ + erccb *e ; + int x ; + + do { + while (eaha->toperccb == 0) ;/* Shouldn't be often or long, */ + /* BUT should use a semaphore */ + x = splbio() ; + e = eaha->toperccb ; + if (e == 0) + splx(x) ; + } while (!e) ; + eaha->toperccb = e->next ; + splx(x) ; + bzero(e,sizeof(*e)) ; + e->_eccb.status = kvtophys((vm_offset_t)&e->status) ; + return e ; +} + +void erccb_free( + eaha_softc *eaha, + erccb *e) +{ + int x ; + ASSERT ( e >= eaha->_erccbs && e < eaha->_erccbs+NECCBS) ; + x = splbio() ; + e->next = eaha->toperccb ; + eaha->toperccb = e ; + splx(x) ; +} + +void eaha_mboxout( + int port, + vm_offset_t phys) +{ + outb(MBOXOUT0(port),phys) ; + outb(MBOXOUT1(port),phys>>8) ; + outb(MBOXOUT2(port),phys>>16) ; + outb(MBOXOUT3(port),phys>>24) ; +} + +void eaha_command( /* start a command */ + int port, + erccb *_erccb) +{ + int s ; + vm_offset_t phys = kvtophys((vm_offset_t) &_erccb->_eccb) ; + while ((inb(G2STAT(port)) & 0x04)==0); /*While MBO busy. TRM6-1 */ + s = splbio() ; + eaha_mboxout(port,phys) ; + while (inb(G2STAT(port)) & 1) ; /* While adapter busy. TRM6-2 */ + outb(ATTN(port),0x40 | _erccb->active_target->target_id) ; /* TRM6-20 */ + /* (Should use target id for intitiator command) */ + splx(s) ; +} + +eaha_reset( + eaha_softc_t eaha, + boolean_t quick) +{ + /* + * Reset board and wait till done + */ + unsigned st ; + int target_id ; + int port = eaha->port ; + + /* Reset adapter, maybe with SCSIbus */ + eaha_mboxout(port, quick ? 0x00080080 : 0x00000080 ) ; /* TRM 6-43..45 */ + outb(ATTN(port), 0x10 | inb(SCSIDEF(port)) & 0x0f) ; + outb(G2CNTRL(port),0x20) ; /* TRM 4-22 */ + + do { + st = inb(G2INTST(port)) >> 4 ; + } while (st == 0) ; + /* TRM 4-22 implies that 1 should not be returned in G2INTST, but + in practise, it is. So this code takes 0 to mean non-completion. */ + + for (target_id = 0 ; target_id < NTARGETS; target_id++) + eaha->has_sense_info[target_id] = FALSE ; + +} + +void eaha_init( + eaha_softc_t eaha) +{ + /* Do nothing - I guess */ +} + +void eaha_bus_reset( + eaha_softc_t eaha) + +{ + LOG(0x1d,"bus_reset"); + + /* + * Clear bus descriptor + */ + eaha->wd.nactive = 0; + eaha_reset(eaha, TRUE); + eaha_init(eaha); + + printf("eaha: (%d) bus reset ", ++eaha->wd.reset_count); + delay(scsi_delay_after_reset); /* some targets take long to reset */ + + if (eaha->sc == 0) /* sanity */ + return; + + scsi_bus_was_reset(eaha->sc); +} + +#ifdef notdef + /* functions added to complete 1742 support, but not used. Untested. */ + + void eaha_download(port, data, len) + int port ; + char *data ; + unsigned len ; + { + /* 1744 firmware download. Not implemented. TRM6-21 */ + } + + void eaha_initscsi(data, len) + char *data ; + unsigned len ; + { + /* initialize SCSI subsystem. Presume BIOS does it. + Not implemented. TRM6-23 */ + } + + void eaha_noop() + { + /* Not implemented. TRM6-27 */ + } + + erccb *eaha_host_adapter_inquiry(eaha) /* Returns a promise */ + eaha_softc *eaha ; /* TRM6-31..33 */ + { + erccb *_erccb = erccb_alloc(eaha) ; + _erccb->_eccb.scather = (vm_offset_t) kvtophys(eaha->host_inquiry_data) ; + _erccb->_eccb.scathlen = sizeof(eaha->host_inquiry_data) ; + _erccb->_eccb.ses = 1 ; + _erccb->_eccb.command = EAHA_CMD_HOST_INQ ; + eaha_command(eaha->port,_erccb->_eccb,0) ; /* Is scsi_id used */ + return _erccb ; + } + + erccb *eaha_read_sense_info(eaha, target, lun) /* TRM 6-33..35 */ + eaha_softc *eaha ; + unsigned target, lun ; + { /* Don't think we need this because its done in scsi_alldevs.c */ + #ifdef notdef + erccb *_erccb = erccb_alloc(eaha) ; + _erccb->_eccb.command = EAHA_CMD_READ_SENS ; + _erccb->_eccb.lun = lun ; + eaha_command(eaha->port,_erccb->_eccb, target) ;/*Wrong # args*/ + return _erccb ; + #else + return 0 ; + #endif + } + + void eaha_diagnostic(eaha) + eaha_softc *eaha ; + { + /* Not implemented. TRM6-36..37 */ + } + + erccb *eaha_target_cmd(eaha, target, lun, data, len) /* TRM6-38..39 */ + eaha_softc *eaha ; + unsigned target, lun ; + char *data ; + unsigned len ; + { + erccb *_erccb = erccb_alloc(eaha) ; + _erccb->_eccb.command = EAHA_CMD_TARG_CMD ; + _erccb->_eccb.lun = lun ; + eaha_command(eaha->port,_erccb->_eccb,target);/*Wrong # args*/ + return _erccb ; + } + + erccb *eaha_init_cmd(port) /* SHOULD RETURN TOKEN. i.e. ptr to eccb */ + /* Need list of free eccbs */ + { /* to be continued,. possibly. */ + } + +#endif /* notdef */ + +target_info_t * +eaha_tgt_alloc( + eaha_softc_t eaha, + int id, + target_info_t *tgt) +{ + erccb *_erccb; + + if (tgt == 0) + tgt = scsi_slave_alloc(eaha - eaha_softc_data, id, eaha); + + _erccb = erccb_alloc(eaha) ; /* This is very dodgy */ + tgt->cmd_ptr = (char *)& _erccb->_eccb.cdb ; + tgt->dma_ptr = 0; + return tgt; +} + + +struct { + scsi_sense_data_t sns ; + unsigned char extra + [254-sizeof(scsi_sense_data_t)] ; +} eaha_xsns [NTARGETS] ;/*must be bss to be contiguous*/ + + +/* Enhanced adapter probe routine */ + +eaha_probe( + register int port, + struct bus_ctlr *ui) +{ + int unit = ui->unit; + eaha_softc_t eaha = &eaha_softc_data[unit] ; + int target_id ; + scsi_softc_t *sc ; + int s; + boolean_t did_banner = FALSE ; + struct aha_devs installed; + unsigned char my_scsi_id, my_interrupt ; + + if (unit >= NEAHA) + return(0); + + /* No interrupts yet */ + s = splbio(); + + /* + * Detect prescence of 174x in enhanced mode. Ignore HID2 and HID3 + * on the assumption that compatibility will be preserved. dph + */ + if (inb(HID0(port)) != 0x04 || inb(HID1(port)) != 0x90 || + (inb(PORTADDR(port)) & 0x80) != 0x80) { + splx(s); + return 0 ; + } + + /* Issue RESET in case this is a reboot */ + + outb(EBCTRL(port),0x04) ; /* Disable board. TRM4-12 */ + outb(PORTADDR(port),0x80) ; /* Disable standard mode ports. TRM4-13. */ + my_interrupt = inb(INTDEF(port)) & 0x07 ; + outb(INTDEF(port), my_interrupt | 0x00) ; + /* Disable interrupts. TRM4-15 */ + my_scsi_id = inb(SCSIDEF(port)) & 0x0f ; + outb(SCSIDEF(port), my_scsi_id | 0x10) ; + /* Force SCSI reset on hard reset. TRM4-16 */ + outb(G2CNTRL(port),0xe0) ; /* Reset board, clear interrupt */ + /* and set 'host ready'. */ + delay(10*10) ; /* HRST must remain set for 10us. TRM4-22 */ + /* (I don't believe the delay loop is slow enough.) */ + outb(G2CNTRL(port),0x60);/*Un-reset board, set 'host ready'. TRM4-22*/ + + printf("Adaptec 1740A/1742A/1744 enhanced mode\n"); + + /* Get host inquiry data */ + + eaha_softc_pool[unit] = eaha ; + bzero(eaha,sizeof(*eaha)) ; + eaha->port = port ; + + sc = scsi_master_alloc(unit, eaha) ; + eaha->sc = sc ; + sc->go = eaha_go ; + sc->watchdog = scsi_watchdog ; + sc->probe = eaha_probe_target ; + eaha->wd.reset = eaha_reset_scsibus ; + sc->max_dma_data = -1 ; /* Lets be optimistic */ + sc->initiator_id = my_scsi_id ; + eaha_reset(eaha,TRUE) ; + eaha->I_hold_my_phys_address = + kvtophys((vm_offset_t)&eaha->I_hold_my_phys_address) ; + { + erccb *e ; + eaha->toperccb = eaha->_erccbs ; + for (e=eaha->_erccbs; e < eaha->_erccbs+NECCBS; e++) { + e->next = e+1 ; + e->_eccb.status = + kvtophys((vm_offset_t) &e->status) ; + } + eaha->_erccbs[NECCBS-1].next = 0 ; + + } + + ui->sysdep1 = my_interrupt + 9 ; + take_ctlr_irq(ui) ; + + printf("%s%d: [port 0x%x intr ch %d] my SCSI id is %d", + ui->name, unit, port, my_interrupt + 9, my_scsi_id) ; + + outb(INTDEF(port), my_interrupt | 0x10) ; + /* Enable interrupts. TRM4-15 */ + outb(EBCTRL(port),0x01) ; /* Enable board. TRM4-12 */ + + { target_info_t *t = eaha_tgt_alloc(eaha, my_scsi_id, 0) ; + /* Haven't enabled target mode a la standard mode, because */ + /* it doesn't seem to be necessary. */ + sccpu_new_initiator(t, t) ; + } + + /* Find targets, incl. ourselves. */ + + for (target_id=0; target_id < SCSI_TARGETS; target_id++) + if (target_id != sc->initiator_id) { + scsi_cmd_test_unit_ready_t *cmd; + erccb *_erccb = erccb_alloc(eaha) ; + unsigned attempts = 0 ; +#define MAX_ATTEMPTS 2 + target_info_t temp_targ ; + + temp_targ.ior = 0 ; + temp_targ.hw_state = (char *) eaha ; + temp_targ.cmd_ptr = (char *) &_erccb->_eccb.cdb ; + temp_targ.target_id = target_id ; + temp_targ.lun = 0 ; + temp_targ.cur_cmd = SCSI_CMD_TEST_UNIT_READY; + + cmd = (scsi_cmd_test_unit_ready_t *) temp_targ.cmd_ptr; + + do { + cmd->scsi_cmd_code = SCSI_CMD_TEST_UNIT_READY; + cmd->scsi_cmd_lun_and_lba1 = 0; /*assume 1 lun?*/ + cmd->scsi_cmd_lba2 = 0; + cmd->scsi_cmd_lba3 = 0; + cmd->scsi_cmd_ss_flags = 0; + cmd->scsi_cmd_ctrl_byte = 0; /* not linked */ + + eaha_go( &temp_targ, + sizeof(scsi_cmd_test_unit_ready_t),0,0); + /* ints disabled, so call isr yourself. */ + while (temp_targ.done == SCSI_RET_IN_PROGRESS) + if (inb(G2STAT(eaha->port)) & 0x02) { + eaha_quiet = 1 ; + eaha_intr(unit) ; + eaha_quiet = 0 ; + } + if (temp_targ.done == SCSI_RET_NEED_SENSE) { + /* MUST get sense info : TRM6-34 */ + if (eaha_retrieve_sense_info( + eaha, temp_targ.target_id, + temp_targ.lun) && + attempts == MAX_ATTEMPTS-1) { + + printf( + "\nTarget %d Check Condition : " + ,temp_targ.target_id) ; + scsi_print_sense_data(&eaha_xsns + [temp_targ.target_id]); + printf("\n") ; + } + } + } while (temp_targ.done != SCSI_RET_SUCCESS && + temp_targ.done != SCSI_RET_ABORTED && + ++attempts < MAX_ATTEMPTS) ; + + /* + * Recognize target which is present, whether or not + * it is ready, e.g. drive with removable media. + */ + if (temp_targ.done == SCSI_RET_SUCCESS || + temp_targ.done == SCSI_RET_NEED_SENSE && + _erccb->status.target_status.bits != 0) { /* Eureka */ + installed.tgt_luns[target_id]=1;/*Assume 1 lun?*/ + printf(", %s%d", + did_banner++ ? "" : "target(s) at ", + target_id); + + erccb_free(eaha, _erccb) ; + + /* Normally, only LUN 0 */ + if (installed.tgt_luns[target_id] != 1) + printf("(%x)", installed.tgt_luns[target_id]); + /* + * Found a target + */ + (void) eaha_tgt_alloc(eaha, target_id, 0); + /* Why discard ? */ + } else + installed.tgt_luns[target_id]=0; + } + + printf(".\n") ; + splx(s); + return 1 ; +} + +int eaha_retrieve_sense_info ( + eaha_softc_t eaha, + int tid, + int lun) +{ + int result ; + int s ; + target_info_t dummy_target ; /* Keeps eaha_command() happy. HACK */ + erccb *_erccb1 = erccb_alloc(eaha) ; + + _erccb1->active_target = &dummy_target ; + dummy_target.target_id = tid ; + _erccb1->_eccb.command = + EAHA_CMD_READ_SENS ; + _erccb1->_eccb.lun = lun ; + _erccb1->_eccb.sense_p = kvtophys((vm_offset_t) &eaha_xsns [tid]); + _erccb1->_eccb.sense_len = sizeof(eaha_xsns [tid]); + _erccb1->_eccb.ses = 1 ; + s = splbio() ; + eaha_command(eaha->port,_erccb1) ; + while ((inb(G2STAT(eaha->port)) & 0x02) == 0) ; + outb(G2CNTRL(eaha->port),0x40);/* Clear int */ + splx(s) ; + result = _erccb1->status.target_status.bits != 0 ; + erccb_free(eaha,_erccb1) ; + return result ; +} + +/* + * Start a SCSI command on a target (enhanced mode) + */ +eaha_go( + target_info_t *tgt, + int cmd_count, + int in_count, + boolean_t cmd_only)/*lint: unused*/ +{ + eaha_softc_t eaha; + int s; + erccb *_erccb; + int len; + vm_offset_t virt; + int tid = tgt->target_id ; + +#ifdef CBUS + at386_io_lock_state(); +#endif + LOG(1,"go"); + +#ifdef CBUS + at386_io_lock(MP_DEV_WAIT); +#endif + eaha = (eaha_softc_t)tgt->hw_state; + + if(eaha->has_sense_info[tid]) { + (void) eaha_retrieve_sense_info + (eaha, tid, eaha->sense_info_lun[tid]) ; + eaha->has_sense_info[tid] = FALSE ; + if (tgt->cur_cmd == SCSI_CMD_REQUEST_SENSE) { + bcopy(&eaha_xsns[tid],tgt->cmd_ptr,in_count) ; + tgt->done = SCSI_RET_SUCCESS; + tgt->transient_state.cmd_count = cmd_count; + tgt->transient_state.out_count = 0; + tgt->transient_state.in_count = in_count; + /* Fake up interrupt */ + /* Highlights from eaha_initiator_intr(), */ + /* ignoring errors */ + if (tgt->ior) + (*tgt->dev_ops->restart)( tgt, TRUE); +#ifdef CBUS + at386_io_unlock(); +#endif + return ; + } + } + +/* XXX delay the handling of the ccb till later */ + _erccb = (erccb *) + ((unsigned)tgt->cmd_ptr - (unsigned) &((erccb *) 0)->_eccb.cdb); + /* Tell *rccb about target, eg. id ? */ + _erccb->active_target = tgt; + + /* + * We can do real DMA. + */ +/* tgt->transient_state.copy_count = 0; unused */ +/* tgt->transient_state.dma_offset = 0; unused */ + + tgt->transient_state.cmd_count = cmd_count; + + if ((tgt->cur_cmd == SCSI_CMD_WRITE) || + (tgt->cur_cmd == SCSI_CMD_LONG_WRITE)){ + io_req_t ior = tgt->ior; + register int len = ior->io_count; + + tgt->transient_state.out_count = len; + + /* How do we avoid leaks here ? Trust the board + will do zero-padding, for now. XXX CHECKME */ +#if 0 + if (len < tgt->block_size) { + bzero(to + len, tgt->block_size - len); + len = tgt->block_size; + tgt->transient_state.out_count = len; + } +#endif + } else { + tgt->transient_state.out_count = 0; + } + + /* See above for in_count < block_size */ + tgt->transient_state.in_count = in_count; + + /* + * Setup CCB state + */ + tgt->done = SCSI_RET_IN_PROGRESS; + + switch (tgt->cur_cmd) { + case SCSI_CMD_READ: + case SCSI_CMD_LONG_READ: + LOG(9,"readop"); + virt = (vm_offset_t)tgt->ior->io_data; + len = tgt->transient_state.in_count; + break; + case SCSI_CMD_WRITE: + case SCSI_CMD_LONG_WRITE: + LOG(0x1a,"writeop"); + virt = (vm_offset_t)tgt->ior->io_data; + len = tgt->transient_state.out_count; + break; + case SCSI_CMD_INQUIRY: + case SCSI_CMD_REQUEST_SENSE: + case SCSI_CMD_MODE_SENSE: + case SCSI_CMD_RECEIVE_DIAG_RESULTS: + case SCSI_CMD_READ_CAPACITY: + case SCSI_CMD_READ_BLOCK_LIMITS: + LOG(0x1c,"cmdop"); + LOG(0x80+tgt->cur_cmd,0); + virt = (vm_offset_t)tgt->cmd_ptr; + len = tgt->transient_state.in_count; + break; + case SCSI_CMD_MODE_SELECT: + case SCSI_CMD_REASSIGN_BLOCKS: + case SCSI_CMD_FORMAT_UNIT: + tgt->transient_state.cmd_count = sizeof(scsi_command_group_0); + len = + tgt->transient_state.out_count = cmd_count - sizeof(scsi_command_group_0); + virt = (vm_offset_t)tgt->cmd_ptr+sizeof(scsi_command_group_0); + LOG(0x1c,"cmdop"); + LOG(0x80+tgt->cur_cmd,0); + break; + default: + LOG(0x1c,"cmdop"); + LOG(0x80+tgt->cur_cmd,0); + virt = 0; + len = 0; + } + + eaha_prepare_rccb(tgt, _erccb, virt, len); + + _erccb->_eccb.lun = tgt->lun; + + /* + * XXX here and everywhere, locks! + */ + s = splbio(); + + simple_lock(&eaha->aha_lock); + if (eaha->wd.nactive++ == 0) + eaha->wd.watchdog_state = SCSI_WD_ACTIVE; + simple_unlock(&eaha->aha_lock); + + LOG(3,"enqueue"); + + eaha_command(eaha->port, _erccb) ; + + splx(s); +#ifdef CBUS + at386_io_unlock(); +#endif +} + +eaha_prepare_rccb( + target_info_t *tgt, + erccb *_erccb, + vm_offset_t virt, + vm_size_t len) +{ + _erccb->_eccb.cdb_len = tgt->transient_state.cmd_count; + + _erccb->_eccb.command = EAHA_CMD_INIT_CMD;/* default common case */ + + if (virt == 0) { + /* no xfers */ + _erccb->_eccb.scather = 0 ; + _erccb->_eccb.scathlen = 0 ; + _erccb->_eccb.sg = 0 ; + } else { + /* messy xfer */ + scather_entry *seglist; + vm_size_t l1, off; + + _erccb->_eccb.sg = 1 ; + + if (tgt->dma_ptr == 0) + eaha_alloc_segment_list(tgt); + seglist = (scather_entry *) tgt->dma_ptr; + + _erccb->_eccb.scather = kvtophys((vm_offset_t) seglist); + + l1 = MACHINE_PGBYTES - (virt & (MACHINE_PGBYTES - 1)); + if (l1 > len) + l1 = len ; + + off = 1;/* now #pages */ + while (1) { + seglist->ptr = kvtophys(virt) ; + seglist->len = l1 ; + seglist++; + + if (len <= l1) + break ; + len-= l1 ; + virt += l1; off++; + + l1 = (len > MACHINE_PGBYTES) ? MACHINE_PGBYTES : len; + } + _erccb->_eccb.scathlen = off * sizeof(*seglist); + } +} + +/* + * Allocate dynamically segment lists to + * targets (for scatter/gather) + */ +vm_offset_t eaha_seglist_next = 0, eaha_seglist_end = 0 ; +#define EALLOC_SIZE (SCATHER_ENTRIES * sizeof(scather_entry)) + +eaha_alloc_segment_list( + target_info_t *tgt) +{ + +/* XXX locking */ +/* ? Can't spl() for unknown duration */ + if ((eaha_seglist_next + EALLOC_SIZE) > eaha_seglist_end) { + (void)kmem_alloc_wired(kernel_map,&eaha_seglist_next,PAGE_SIZE); + eaha_seglist_end = eaha_seglist_next + PAGE_SIZE; + } + tgt->dma_ptr = (char *)eaha_seglist_next; + eaha_seglist_next += EALLOC_SIZE; +/* XXX locking */ +} + +/* + * + * shameless copy from above + */ +eaha_reset_scsibus( + register eaha_softc_t eaha) +{ + register target_info_t *tgt; + register port = eaha->port; + register int i; + + for (i = 0; i < NECCBS; i++) { + tgt = eaha->_erccbs[i].active_target; + if (/*scsi_debug &&*/ tgt) + printf("Target %d was active, cmd x%x in x%x out x%x\n", + tgt->target_id, tgt->cur_cmd, + tgt->transient_state.in_count, + tgt->transient_state.out_count); + } + eaha_reset(eaha, FALSE); + delay(35); + /* no interrupt will come */ + eaha_bus_reset(eaha); +} + +boolean_t +eaha_probe_target( + target_info_t *tgt, + io_req_t ior) +{ + eaha_softc_t eaha = eaha_softc_pool[tgt->masterno]; + boolean_t newlywed; + + newlywed = (tgt->cmd_ptr == 0); + if (newlywed) { + /* desc was allocated afresh */ + (void) eaha_tgt_alloc(eaha,tgt->target_id, tgt); + } + + if (scsi_inquiry(tgt, SCSI_INQ_STD_DATA) == SCSI_RET_DEVICE_DOWN) + return FALSE; + + tgt->flags = TGT_ALIVE; + return TRUE; +} + + +/* + * Interrupt routine (enhanced mode) + * Take interrupts from the board + * + * Implementation: + * TBD + */ +eaha_intr( + int unit) +{ + register eaha_softc_t eaha; + register port; + unsigned g2intst, g2stat, g2stat2 ; + vm_offset_t mbi ; + erccb *_erccb ; + status_block *status ; + +#if MAPPABLE + extern boolean_t rz_use_mapped_interface; + + if (rz_use_mapped_interface) { + EAHA_intr(unit); + return ; + } +#endif /*MAPPABLE*/ + + eaha = eaha_softc_pool[unit]; + port = eaha->port; + + LOG(5,"\n\tintr"); +gotintr: + /* collect ephemeral information */ + + g2intst = inb(G2INTST(port)) ; /* See TRM4-22..23 */ + g2stat = inb(G2STAT(port)) ; /*lint:set,not used*/ + g2stat2 = inb(G2STAT2(port)) ; /*lint:set,not used*/ + mbi = (vm_offset_t) inb(MBOXIN0(port)) + (inb(MBOXIN1(port))<<8) + + (inb(MBOXIN2(port))<<16) + (inb(MBOXIN3(port))<<24) ; + + /* we got an interrupt allright */ + if (eaha->wd.nactive) + eaha->wd.watchdog_state = SCSI_WD_ACTIVE; + + outb(G2CNTRL(port),0x40) ; /* Clear EISA interrupt */ + + switch(g2intst>>4) { + case 0x07 : /* hardware error ? */ + case 0x0a : /* immediate command complete - don't expect */ + case 0x0e : /* ditto with failure */ + default : + printf( "aha%d: Bogus status (x%x) in MBI\n", + unit, mbi); + gimmeabreak() ; /* Any of above is disaster */ + break; + + case 0x0d : /* Asynchronous event TRM6-41 */ + if ((g2intst & 0x0f) == (inb(SCSIDEF(eaha->port)) & 0x0f)) + eaha_reset_scsibus(eaha) ; + else + eaha_target_intr(eaha, mbi, g2intst & 0x0f); + break; + + case 0x0c : /* ccb complete with error */ + case 0x01 : /* ccb completed with success */ + case 0x05 : /* ccb complete with success after retry */ + + _erccb = (erccb *) + ( ((vm_offset_t)&eaha->I_hold_my_phys_address) + + (mbi - eaha->I_hold_my_phys_address) - + (vm_offset_t)&(((erccb *)0)->_eccb) ) ; + /* That ain't necessary. As kernel (must be) */ + /* contiguous, only need delta to translate */ + + status = &_erccb->status ; + +#ifdef NOTDEF + if (!eaha_quiet && (!status->don || status->qf || + status->sc || status->dover || + status->ini || status->me)) { + printf("\nccb complete error G2INTST=%02X\n", + g2intst) ; + DUMP(*_erccb) ; + gimmeabreak() ; + } +#endif + + eaha_initiator_intr(eaha, _erccb); + break; + } + + /* See if more work ready */ + if (inb(G2STAT(port)) & 0x02) { + LOG(7,"\n\tre-intr"); + goto gotintr; + } +} + +/* + * The interrupt routine turns to one of these two + * functions, depending on the incoming mbi's role + */ +eaha_target_intr( + eaha_softc_t eaha, + unsigned int mbi, + unsigned int peer) +{ + target_info_t *initiator; /* this is the caller */ + target_info_t *self; /* this is us */ + int len; + + self = eaha->sc->target[eaha->sc->initiator_id]; + + initiator = eaha->sc->target[peer]; + + /* ..but initiators are not required to answer to our inquiry */ + if (initiator == 0) { + /* allocate */ + initiator = eaha_tgt_alloc(eaha, peer, 0); + + /* We do not know here wether the host was down when + we inquired, or it refused the connection. Leave + the decision on how we will talk to it to higher + level code */ + LOG(0xC, "new_initiator"); + sccpu_new_initiator(self, initiator); + /* Bug fix: was (aha->sc, self, initiator); dph */ + } + + /* The right thing to do would be build an ior + and call the self->dev_ops->strategy routine, + but we cannot allocate it at interrupt level. + Also note that we are now disconnected from the + initiator, no way to do anything else with it + but reconnect and do what it wants us to do */ + + /* obviously, this needs both spl and MP protection */ + self->dev_info.cpu.req_pending = TRUE; + self->dev_info.cpu.req_id = peer ; + self->dev_info.cpu.req_lun = (mbi>>24) & 0x07 ; + self->dev_info.cpu.req_cmd = + (mbi & 0x80000000) ? SCSI_CMD_SEND: SCSI_CMD_RECEIVE; + len = mbi & 0x00ffffff ; + + self->dev_info.cpu.req_len = len; + + LOG(0xB,"tgt-mode-restart"); + (*self->dev_ops->restart)( self, FALSE); + + /* The call above has either prepared the data, + placing an ior on self, or it handled it some + other way */ + if (self->ior == 0) + return; /* I guess we'll do it later */ + + { + erccb *_erccb ; + + _erccb = erccb_alloc(eaha) ; + _erccb->active_target = initiator; + _erccb->_eccb.command = EAHA_CMD_TARG_CMD ; + _erccb->_eccb.ses = 1 ; + _erccb->_eccb.dir = (self->cur_cmd == SCSI_CMD_SEND) ? 1 : 0 ; + + eaha_prepare_rccb(initiator, _erccb, + (vm_offset_t)self->ior->io_data, self->ior->io_count); + _erccb->_eccb.lun = initiator->lun; + + simple_lock(&eaha->aha_lock); + if (eaha->wd.nactive++ == 0) + eaha->wd.watchdog_state = SCSI_WD_ACTIVE; + simple_unlock(&eaha->aha_lock); + + eaha_command(eaha->port, _erccb); + } +} + +eaha_initiator_intr( + eaha_softc_t eaha, + erccb *_erccb) +{ + scsi2_status_byte_t status; + target_info_t *tgt; + + tgt = _erccb->active_target; + _erccb->active_target = 0; + + /* shortcut (sic!) */ + if (_erccb->status.ha_status == HA_STATUS_SUCCESS) + goto allok; + + switch (_erccb->status.ha_status) { /* TRM6-17 */ + case HA_STATUS_SUCCESS : +allok: + status = _erccb->status.target_status ; + if (status.st.scsi_status_code != SCSI_ST_GOOD) { + scsi_error(tgt, SCSI_ERR_STATUS, status.bits, 0); + tgt->done = (status.st.scsi_status_code == SCSI_ST_BUSY) ? + SCSI_RET_RETRY : SCSI_RET_NEED_SENSE; + } else + tgt->done = SCSI_RET_SUCCESS; + break; + + case HA_STATUS_SEL_TIMEOUT : + if (tgt->flags & TGT_FULLY_PROBED) + tgt->flags = 0; /* went offline */ + tgt->done = SCSI_RET_DEVICE_DOWN; + break; + + case HA_STATUS_OVRUN : + /* BUT we don't know if this is an underrun. + It is ok if we get less data than we asked + for, in a number of cases. Most boards do not + seem to generate this anyways, but some do. */ + { register int cmd = tgt->cur_cmd; + switch (cmd) { + case SCSI_CMD_INQUIRY: + case SCSI_CMD_REQUEST_SENSE: + case SCSI_CMD_RECEIVE_DIAG_RESULTS: + case SCSI_CMD_MODE_SENSE: + if (_erccb->status.du) /*Ignore underrun only*/ + break; + default: + printf("eaha: U/OVRUN on scsi command x%x\n",cmd); + gimmeabreak(); + } + } + goto allok; + case HA_STATUS_BUS_FREE : + printf("aha: bad disconnect\n"); + tgt->done = SCSI_RET_ABORTED; + break; + case HA_STATUS_PHASE_ERROR : + /* we'll get an interrupt soon */ + printf("aha: bad PHASE sequencing\n"); + tgt->done = SCSI_RET_ABORTED; + break; + case HA_STATUS_BAD_OPCODE : +printf("aha: BADCCB\n");gimmeabreak(); + tgt->done = SCSI_RET_RETRY; + break; + + case HA_STATUS_HOST_ABORTED : + case HA_STATUS_ADP_ABORTED : + case HA_STATUS_NO_FIRM : + case HA_STATUS_NOT_TARGET : + case HA_STATUS_INVALID_LINK : /* These aren't expected. */ + case HA_STATUS_BAD_CBLOCK : + case HA_STATUS_DUP_CBLOCK : + case HA_STATUS_BAD_SCATHER : + case HA_STATUS_RSENSE_FAIL : + case HA_STATUS_TAG_REJECT : + case HA_STATUS_HARD_ERROR : + case HA_STATUS_TARGET_NOATTN : + case HA_STATUS_HOST_RESET : + case HA_STATUS_OTHER_RESET : + case HA_STATUS_PROG_BAD_SUM : + default : + printf("aha: bad ha_status (x%x)\n", _erccb->status.ha_status); + tgt->done = SCSI_RET_ABORTED; + break; + } + + eaha->has_sense_info [tgt->target_id] = + (tgt->done == SCSI_RET_NEED_SENSE) ; + if (eaha->has_sense_info [tgt->target_id]) + eaha->sense_info_lun [tgt->target_id] = tgt->lun ; + + LOG(8,"end"); + + simple_lock(&eaha->aha_lock); + if (eaha->wd.nactive-- == 1) + eaha->wd.watchdog_state = SCSI_WD_INACTIVE; + simple_unlock(&eaha->aha_lock); + + if (tgt->ior) { + LOG(0xA,"ops->restart"); + (*tgt->dev_ops->restart)( tgt, TRUE); + } + + return FALSE;/*lint: Always returns FALSE. ignored. */ +} + +#endif /* NEAHA > 0 */ diff --git a/scsi/adapters/scsi_dma.h b/scsi/adapters/scsi_dma.h new file mode 100644 index 0000000..9401a16 --- /dev/null +++ b/scsi/adapters/scsi_dma.h @@ -0,0 +1,150 @@ +/* + * Mach Operating System + * Copyright (c) 1991,1990,1989 Carnegie Mellon University + * All Rights Reserved. + * + * Permission to use, copy, modify and distribute this software and its + * documentation is hereby granted, provided that both the copyright + * notice and this permission notice appear in all copies of the + * software, derivative works or modified versions, and any portions + * thereof, and that both notices appear in supporting documentation. + * + * CARNEGIE MELLON ALLOWS FREE USE OF THIS SOFTWARE IN ITS AS-IS + * CONDITION. CARNEGIE MELLON DISCLAIMS ANY LIABILITY OF ANY KIND FOR + * ANY DAMAGES WHATSOEVER RESULTING FROM THE USE OF THIS SOFTWARE. + * + * Carnegie Mellon requests users of this software to return to + * + * Software Distribution Coordinator or Software.Distribution@CS.CMU.EDU + * School of Computer Science + * Carnegie Mellon University + * Pittsburgh PA 15213-3890 + * + * any improvements or extensions that they make and grant Carnegie Mellon + * the rights to redistribute these changes. + */ +/* + * File: scsi_dma.h + * Author: Alessandro Forin, Carnegie Mellon University + * Date: 7/91 + * + * DMA operations that an HBA driver might invoke. + * + */ + +/* + * This defines much more than usually needed, mainly + * to cover for the case of no DMA at all and/or only + * DMA from/to a specialized buffer ( which means the + * CPU has to copy data into/outof it ). + */ + +typedef struct { + opaque_t (*init)( + int dev_unit, + vm_offset_t base, + int *dma_bsizep, + boolean_t *oddbp); + + void (*new_target)( + opaque_t dma_state, + target_info_t *tgt); + + void (*map)( + opaque_t dma_state, + target_info_t *tgt); + + int (*start_cmd)( + opaque_t dma_state, + target_info_t *tgt); + + void (*end_xfer)( + opaque_t dma_state, + target_info_t *tgt, + int xferred); + + void (*end_cmd)( + opaque_t dma_state, + target_info_t *tgt, + io_req_t ior); + + int (*start_datain)( + opaque_t dma_state, + target_info_t *tgt); + + int (*start_msgin)( + opaque_t dma_state, + target_info_t *tgt); + + void (*end_msgin)( + opaque_t dma_state, + target_info_t *tgt); + + boolean_t (*start_dataout)( + opaque_t dma_state, + target_info_t *tgt, + volatile unsigned *regp, + unsigned value, + unsigned char *prefetch_count); + + int (*restart_datain_1)( + opaque_t dma_state, + target_info_t *tgt); + + int (*restart_datain_2)( + opaque_t dma_state, + target_info_t *tgt, + int xferred); + + void (*restart_datain_3)( + opaque_t dma_state, + target_info_t *tgt); + + int (*restart_dataout_1)( + opaque_t dma_state, + target_info_t *tgt); + + int (*restart_dataout_2)( + opaque_t dma_state, + target_info_t *tgt, + int xferred); + + int (*restart_dataout_3)( + opaque_t dma_state, + target_info_t *tgt, + volatile unsigned *regp); + + void (*restart_dataout_4)( + opaque_t dma_state, + target_info_t *tgt); + + boolean_t (*disconn_1)( + opaque_t dma_state, + target_info_t *tgt, + int xferred); + + boolean_t (*disconn_2)( + opaque_t dma_state, + target_info_t *tgt); + + boolean_t (*disconn_3)( + opaque_t dma_state, + target_info_t *tgt, + int xferred); + + boolean_t (*disconn_4)( + opaque_t dma_state, + target_info_t *tgt, + int xferred); + + boolean_t (*disconn_5)( + opaque_t dma_state, + target_info_t *tgt, + int xferred); + + void (*disconn_callback)( + opaque_t dma_state, + target_info_t *tgt); + +} scsi_dma_ops_t; + diff --git a/scsi/adapters/scsi_user_dma.c b/scsi/adapters/scsi_user_dma.c new file mode 100644 index 0000000..5fb98d6 --- /dev/null +++ b/scsi/adapters/scsi_user_dma.c @@ -0,0 +1,171 @@ +/* + * Mach Operating System + * Copyright (c) 1991,1990 Carnegie Mellon University + * All Rights Reserved. + * + * Permission to use, copy, modify and distribute this software and its + * documentation is hereby granted, provided that both the copyright + * notice and this permission notice appear in all copies of the + * software, derivative works or modified versions, and any portions + * thereof, and that both notices appear in supporting documentation. + * + * CARNEGIE MELLON ALLOWS FREE USE OF THIS SOFTWARE IN ITS + * CONDITION. CARNEGIE MELLON DISCLAIMS ANY LIABILITY OF ANY KIND FOR + * ANY DAMAGES WHATSOEVER RESULTING FROM THE USE OF THIS SOFTWARE. + * + * Carnegie Mellon requests users of this software to return to + * + * Software Distribution Coordinator or Software.Distribution@CS.CMU.EDU + * School of Computer Science + * Carnegie Mellon University + * Pittsburgh PA 15213-3890 + * + * any improvements or extensions that they make and grant Carnegie the + * rights to redistribute these changes. + */ +/* + * File: scsi_user_dma.c + * Author: Alessandro Forin, Carnegie Mellon University + * Date: 4/91 + * + * Mach 2.5 compat file, to handle case of DMA to user space + * [e.g. fsck and other raw device accesses] + */ + +#ifdef MACH_KERNEL +/* We do not need this in 3.0 */ +#else /*MACH_KERNEL*/ + +#include +#include + +#include + +#include +#include /* round_page() */ + +/* bp -> pmap */ +#include +#include + +/* + * Initialization, called once per device + */ +fdma_init(fdma, size) + fdma_t fdma; + vm_size_t size; +{ + vm_offset_t addr; + + size = round_page(size); + addr = kmem_alloc_pageable(kernel_map, size); + if (addr == 0) panic("fdma_init"); + + fdma->kernel_virtual = addr; + fdma->max_data = size; + fdma->user_virtual = -1; + +} + +/* + * Remap a buffer from user space to kernel space. + * Note that physio() has already validated + * and wired the user's address range. + */ +fdma_map(fdma, bp) + fdma_t fdma; + struct buf *bp; +{ + pmap_t pmap; + vm_offset_t user_addr; + vm_size_t size; + vm_offset_t kernel_addr; + vm_offset_t off; + vm_prot_t prot; + + /* + * If this is not to user space, or no data xfer is + * involved, no need to do anything. + */ + user_addr = (vm_offset_t)bp->b_un.b_addr; + if (!(bp->b_flags & B_PHYS) || (user_addr == 0)) { + fdma->user_virtual = -1; + return; + } + /* + * We are going to clobber the buffer pointer, so + * remember what it was to restore it later. + */ + fdma->user_virtual = user_addr; + + /* + * Account for initial offset into phys page + */ + off = user_addr - trunc_page(user_addr); + + /* + * Check xfer size makes sense, note how many pages we'll remap + */ + size = bp->b_bcount + off; + assert((size <= fdma->max_data)); + fdma->xfer_size_rnd = round_page(size); + + pmap = bp->b_proc->task->map->pmap; + + /* + * Use minimal protection possible + */ + prot = VM_PROT_READ; + if (bp->b_flags & B_READ) + prot |= VM_PROT_WRITE; + + /* + * Loop through all phys pages, taking them from the + * user pmap (they are wired) and inserting them into + * the kernel pmap. + */ + user_addr -= off; + kernel_addr = fdma->kernel_virtual; + bp->b_un.b_addr = (char *)kernel_addr + off; + + for (size = fdma->xfer_size_rnd; size; size -= PAGE_SIZE) { + register vm_offset_t phys; + + phys = pmap_extract(pmap, user_addr); + pmap_enter(kernel_pmap, kernel_addr, phys, prot, TRUE); + user_addr += PAGE_SIZE; + kernel_addr += PAGE_SIZE; + } +} + +/* + * Called at end of xfer, to restore the buffer + */ +fdma_unmap(fdma, bp) + fdma_t fdma; + struct buf *bp; +{ + register vm_offset_t end_addr; + + /* + * Check we actually did remap it + */ + if (fdma->user_virtual == -1) + return; + + /* + * Restore the buffer + */ + bp->b_un.b_addr = (char *)fdma->user_virtual; + fdma->user_virtual = -1; + + /* + * Eliminate the mapping, pmap module might mess up + * the pv list otherwise. Some might actually tolerate it. + */ + end_addr = fdma->kernel_virtual + fdma->xfer_size_rnd; + pmap_remove(kernel_pmap, fdma->kernel_virtual, end_addr); + +} + +#endif /*MACH_KERNEL*/ diff --git a/scsi/adapters/scsi_user_dma.h b/scsi/adapters/scsi_user_dma.h new file mode 100644 index 0000000..ff2682c --- /dev/null +++ b/scsi/adapters/scsi_user_dma.h @@ -0,0 +1,47 @@ +/* + * Mach Operating System + * Copyright (c) 1991,1990 Carnegie Mellon University + * All Rights Reserved. + * + * Permission to use, copy, modify and distribute this software and its + * documentation is hereby granted, provided that both the copyright + * notice and this permission notice appear in all copies of the + * software, derivative works or modified versions, and any portions + * thereof, and that both notices appear in supporting documentation. + * + * CARNEGIE MELLON ALLOWS FREE USE OF THIS SOFTWARE IN ITS + * CONDITION. CARNEGIE MELLON DISCLAIMS ANY LIABILITY OF ANY KIND FOR + * ANY DAMAGES WHATSOEVER RESULTING FROM THE USE OF THIS SOFTWARE. + * + * Carnegie Mellon requests users of this software to return to + * + * Software Distribution Coordinator or Software.Distribution@CS.CMU.EDU + * School of Computer Science + * Carnegie Mellon University + * Pittsburgh PA 15213-3890 + * + * any improvements or extensions that they make and grant Carnegie the + * rights to redistribute these changes. + */ +/* + * File: scsi_user_dma.h + * Author: Alessandro Forin, Carnegie Mellon University + * Date: 4/91 + * + * Defines for Mach 2.5 compat, user-space DMA routines + */ + +/* There is one such structure per I/O device + that needs to xfer data to/from user space */ + +typedef struct fdma { + vm_offset_t kernel_virtual; + vm_size_t max_data; + vm_offset_t user_virtual; + int xfer_size_rnd; +} *fdma_t; + +extern int + fdma_init(/* fdma_t, vm_size_t */), + fdma_map(/* fdma_t, struct buf* */), + fdma_unmap(/* fdma_t, struct buf* */); -- cgit v1.2.3