blob: f119a46d309a66ecbdb691840c18610feb0d7f94 [file] [log] [blame] [raw]
/* pdp11_td.c: TU58 simulator
Copyright (c) 2015, Mark Pizzolato
Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the "Software"),
to deal in the Software without restriction, including without limitation
the rights to use, copy, modify, merge, publish, distribute, sublicense,
and/or sell copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
ROBERT M SUPNIK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
Except as contained in this notice, the name of Mark Pizzolato shall not be
used in advertising or otherwise to promote the sale, use or other dealings
in this Software without prior written authorization from Mark Pizzolato.
td TU58 DECtape
26-Jun-15 MP Initial Unibus/Qbus implemention merged from
vax730_stddev.c (done by Matt Burke) and pdp11_dl.c
Added support for multiple concurrent TU58 devices
This module implements the TU58 functionality for the
VAX730 and VAX750 console devices as well as
Unibus/Qbus connected dual drive TU58s.
PDP-11 TU58 DECtapes are represented in memory by fixed length buffer of 32b words.
16b 256 words per block [256 x 16b]
Extracted from the TU58 DECtape II User's Guide - Programming Chapter:
3.1 GENERAL PRINCIPLES
The TU58 is controlled by a microprocessor that frees the host computer from device-related operations,
such as tape positioning and error retry. Only one high-level command to the microprocessor is necessary to
initiate a complex operation. The host and ru58 communicate via strings of one or more bytes called
packets. One brief packet can contain a message which completely describes a high-level command. The
handshaking sequences between host and TU58 as well as packet format are defined by the radial serial
protocol (RSP), or the modified radial serial protocol (MRSP), and were designed to be suitable for transmission
by asynchronous interfaces.
3.1.1 Block Number, Byte Count, and Drive Number
The TU58 uses a drive number, block number, and byte count to write or read data. Figure 1-4 (Chapter 1)
shows the locations of blocks on the tape. If all of the desired data is contained within a single 512-byte
block, the byte count will be 512 or less. When the host asks for a particular block and a 512-or-Iess byte
count, the TU58 positions the specified drive (unit) at that block and transfers the number of bytes specified.
If the host asks for a block and also a byte count greater than that of the 512-byte boundary, the
TU58 reads as many sequential blocks as are needed to fulfill the byte count. The same process applies to
the write function. This means that the host software or an on-tape file directory need only store the number
of the first block in a file and the file's byte count to read or write all the data without having to know
the additional block numbers.
3.1.2 Special Handler Functions
Some device-related functions are not dealt with directly in the RSP, the MRSP, or in the ru58 firmware.
1. A short routine called Newtape (Appendix B) should be included in a TU58 handler to provide a
complete wind-rewind for new or environmentally stressed tape cartridges. This procedure
brings the tape to proper operating tension levels.
2. A TU58 handler should check the success code (byte 3 of the RSP or MRSP end message) for
the presence of soft errors. This enables action to be taken before hard errors (permanent data
losses) occur.
3.2 RADIAL SERIAL PROTOCOL (RSP) AND MODIFIED RSP (MRSP)
3.2.1 Packets
All communication between the host and the TU58 is accomplished via sequences of bytes called packets.
There are two types of multi-byte packets: Control (Command) and Data. Either RSP or MRSP may be
selected using the command packet switch byte. In addition, there are three single-byte packets used to
manage protocol and control the state of the system: INIT, Continue, and XOFF.
Control (Command) - A Control packet is sent to the TU58 to initiate all operations. The packet
contains a message completely describing the operation to be performed. In the case of a read or
write operation, for example, the message includes the function to be performed, unit (drive) number,
byte count and block number.
A special case of the Control packet, called an End packet, is sent from the TU58 to the host after
completion of an operation or on an error. The End packet includes the status of the completed or
aborted operation.
Data - The Data packet holds messages of between 1 and 128 bytes. This message is actually the
data transferred from or to the TU58 during a read or write operation. For transmissions of larger
than 128 bytes, the transfer is broken up and sent 128 bytes at a time.
INIT - This single-byte packet is sent to the TU58 to cause the power-up sequence. The TU58
returns Continue after completion, to indicate that the power-up sequence has occurred. When the
TU5S makes a protocol error or receives an invalid command, it reinitializes and sends INIT continuously
to the host. When the host recognizes INIT, it sends Break to the TU58 to restore the
protocol.
Bootstrap - A flag byte saying Bootstrap (octal 10), followed by a byte containing a drive number,
causes the TU58 to read block 0 of the selected drive. It returns the 512 bytes without radial serial
packaging. This simplifies bootstrap operations. Bootstrap may be sent by the host instead of a
second INIT as part of the initialization process described below.
Continue - Before the host sends a Data packet to the TU58, it must wait until the TUS8 sends
Continue. This permits the TU58 to control the rate that data packets are sent to it.
XON - An alternate term for Continue.
XOFF - Ordinarily, the TU58 does not have to wait between messages to the host. However, if the
host is unable to receive all of a message from the peripheral at once, it may send XOFF. The
TU58 stops transmitting immediately and waits until the host sends Continue to complete the
transfer when it is ready. (Two characters may be sent by the UART to the host after the TUS8
receives XOFF.)
3.2.1.1 Packet Usage - Position within the packet determines the meaning of each byte. All packets
begin with a flag byte, which announces the type of packet to follow. Flag byte numeric assignments
are as follows.
Packet Type Flag Byte Value
Octal Binary
Data 01 00001
Control (Command) 02 00010
INIT 04 00100
Bootstrap 10 01000
Continue 20 10000
XON 21 10001
XOFF 23 10011
(Bits 5 - 7 of the nag byte are reserved.)
Multiple-byte (Control and Data) packets also contain a byte count byte, message bytes, and two checksum
bytes. The byte count byte is the number of message bytes in the packet. The two checksum bytes
are a 16-bit checksum. The checksum is formed by summing successive byte-pairs taken as 16-bit words
while adding any carry back into the sum (end-around carry), The flag and byte count bytes are included
in the checksum. (See example in Appendix 8.)
3.2.1 Break and Initialization
Break is a unique logic entity that can be interpreted by the TU58 and the host regardless of the state
of the protocol. This is the logical equivalent of a bus init or a master reset. Break is transmitted when
the serial line, which normally switches between two logic states called mark and space, is kept in the
space condition for at least one character time. This causes the TU58's UART to set its framing error
bit. The TU58 interprets the framing error as Break.
If communications breakĀ· down, due to any transient problem, the host may restore order by sending
Break and IN IT as outlined above. The faulty operations are cancelled, and the TU58 reinitializes itself,
returns Continue, and waits for instructions.
With DIGITAL serial interfaces, the initialize sequence may be sent by the following sequence of operations.
Set the Break bit in the transmit control status register, then send two null characters. When the
transmit ready flag is set again, remove the Break bit. This times Break to be one character time long.
The second character is discarded by the TU58controller. Next, send two INIT characters. The first is
discarded by the TU58. The TU58 responds to the second INIT by sending Continue. When Continue
has been received, the initialize sequence is complete and any command packet may follow.
3.2.3 Command Packets
The command packet format is shown in Table 3-1. Bytes 0, 1, 12, and 13 are the message delivery
bytes. Their definitions follow.
Table 3-1 Command Packet Structure
Byte Byte Contents
o Flag = 0000 0010(028)
1 Message Byte Count = 0000 101 O( 128)
2 Op Code
3 Modifier
4 Unit Number
5 Switches
6 Sequence Number - Low
7 Sequence Number - High
8 Byte Count - Low
9 Byte Count - High
10 Block Number - Low
11 Block Number - High
12 Checksum - Low
13 Checksum - High
0 Flag This byte is set to 00000010 to indicate
that the packet is a Command packet.
1 Message Byte Count Number of bytes in the packet, excluding the four message delivery
bytes. This is decimal 10 for all command packets.
12, 13 Checksum The 16-bit checksum of bytes 0 through 11. The checksum is
formed by treating each pair of bytes as a word and summing
words with end-around carry.
The remaining bytes are defined below.
2 Op Code Operation being commanded. (See Table 34 and Paragraph 3.3
for definitions.)
3 Modifier Permits variations of commands.
4 Unit Number Selects drive 0 or I.
5 Switches Selects maintenance mode and specifies RSP or MRSP.
6,7 Sequence Number Always zero for TU58.
8,9 Byte Count Number of bytes to be transferred by a read or write command.
Ignored by other commands.
10,11 Block Numbet The block number to be used by commands requiring tape positioning.
3.1.3.1 Maintenance Mode - Setting bit 4 of the switches byte (byte 5) to I in a read command inhibits
retries on data errors. Instead, the incorrect data is delivered to the host followed by an end packet.
The success code in the end packet indicates a hard dt~.ta error. Since data is transmitted in 128-byte
packets, a multiple packet read progresses normally until a checksum mismatch occurs. Then the bad
data packet is transmitted, followed by the end packet, and the operation terminates.
3.1.3.1 Special Address Mode - Setting the most significant bit of the modifier byte (byte 3) to 1
selects special address mode. In this mode all tape positioning operations are addressed by 128-byte
records (0-2047) instead of 512-byte blocks (0-511). Zero-fill in a write operation only fills out to a 128-
byte boundary in this mode. To translate between normal addressing and special addressing, multiply
the normal address by 4. The result is the address of the first I 28-byte record of the block. Add I, 2, or
3 to get to the next three 128-byte records.
3.1.4 Data Packets
3.1.4.1 Radial Serial Protocol-A data transfer operation uses three or more message packets. The first
packet is the command packet from host to the TU58. Next, the data is transferred in 128-byte packets in
either direction (as required by read or write). After all data is transferred, the TU58 sends an end packet.
If the TUS8 encounters a failure before all data has been transferred, it sends the end packet as soon as the
failure occurs.
The data packet is shown in Table 3-2. The flag byte is set to 0018. The number of data bytes may be
between 1 and 128 bytes. For data transfers larger than 128 bytes, the transaction is broken up and sent
128 bytes at a time. The host is assumed to have enough buffer capacity to accept the entire transaction,
whereas the TU58 only has 128 bytes of buffer space. For write commands, the host must wait between
message packets for the TU58 to send the Continue flag 0208 before sending the next packet. Because the
host has enough buffer space, the TU58 does not wait for a Continue flag between message packets when it
sends back read data.
3.1.4.2 Modified Radial Serial Protocol- When the host does not have sufficient buffer space to accept
entire transactions at the hardware selected data transfer rate, modified radial serial protocol (MRSP) may
be specified using the command packet switch byte. Bit 3 of the switch byte is set to specify the MRSP. Bit
3 remains set until intentionally cleared or cleared during power up. A good practice is to set bit 3 in every
MRSP command packet.
MRSP is identical to RSP except during transmission to the host. When a command packet specifies
MRSP for the first time (that is, bit 3 of the switch byte was previously cleared or cleared during power
up), the ru58 will send one data or end packet byte (whichever occurs first). The subsequent bytes, up to
and including the last byte of the end packet, will not be transmitted until a Continue or an XON is
received from the host. To prevent a protocol error from occurring, it is necessary to transmit Continue or .
XON before transmitting any command packets. If a protocol error is detected, continuous INITs are sent
with the Continue handshake. If a bootstrap is being transmitted, however, no handshake is employed.
3.2.5 End Packets
The end packet is sent to the host by the ru58 after completion or termination of an operation or an error.
End packets are sent using RSP or MRSP as specified by the last command packet. The end packet is
shown in Table 3-3.
Table 3-1 Data Packets
Byte Byte Contents
0 Flag = 0000 0001
1 Byte Count = M
-----------------
2 First Data Byte
3 Data
M Data
M+1 Last Data Byte
-----------------
M+2 Checksum L
M+3 Checksum H
Table 3-3 End Packet
Byte Byte Contents
0 Flag = 0000 0010
1 Byte Count = 0000 1010
-----------------
2 Op Code - 0100 0000
3 Success Code
4 Unit
5 Not Used
6 Sequence No. L
7 Sequence No. H
8 Actual Byte Count L
9 Actual Byte Count H
10 Summary Status L
11 Summary Status H
-----------------
12 Checksum L
13 Checksum H
The definition of bytes 0, 1, 12, and 13 are the same as for the command packet. The remaining bytes
are defined as follows.
Byte 2 Op Code - 0100 0000 for end packet
Byte 3 Success Code
Octal Decimal
0 0 = Normal success
1 1 = Success but with retries
377 -1 = Failed self test
376 -2 = Partial operation (end of medium)
370 -8 = Bad unit number
367 -9 = No cartridge
365 -11 = Write protected
357 -17 = Data check error
340 -32 = Seek error (block not found)
337 -33 = Motor stopped
320 -48 = Bad opcode
311 -55 = Bad block number (> 511)
Byte 4 Unit Number 0 or 1 for drive number.
Byte 5 Always 0
Bytes 6,7 Sequence number - always 0 as in command packet.
Bytes 8,9 Actual byte count - number of bytes handled in transaction. In a <good operation,
this is the same as the data byte count in the command packet.
Bytes 10,11 Summary Status
Byte 10
Bit 0 Reserved
...
Bit 7 Reserved
Byte 11
Bit 0 Reserved
Bit 1 Reserved
Bit 2 Reserved
Bit 3 Reserved
Bit 4 Logic error
Bit 5 Motion error
Bit 6 Transfer error
Bit 7 Special condition (errors)
3.3 INSTRUCTION SET
The operation performed by the TU58 when it receives a Control (command) packet is determined by the
op code byte in the control packet message. Note that while any command can specify modified radial
serial protocol with the switch byte, the response will not be MRSP if a boot operation is being performed.
Instruction set op code byte assignments are listed in Table 3-4.
To allow for future development, certain op codes in the command set have been reserved. These commands
have unpredictable results and should not be used. Op codes not listed in the command set are illegal
and result in the return of an end packet with the "bad op code" success code.
Table 3-4 Instruction Set
OpCode OpCode
Decimal Octal Instuction Set
-----------------
0 0 NOP
1 1 INIT
2 2 Read
3 3 Write
4 4 (Reserved)
5 5 Position
6 6 (Reserved)
7 7 Diagnose
8 10 Gctstatus
9 11 Set status
10 12 (Reserved)
11 13 (Reserved)
The following is a brief description and usage example of each.
OP CODE`O NOP
This instruction causes the TU58 to return an end packet. There are no modifiers to NOP. The NOP
packet is shown below.
BYTE
0 0000 0010 FLAG
1 0000 1010 MESSAGE BYTE CNT
2 0000 0000 OPCODE
3 0000 0000 MODIFIER
4 0000 OOOX UNIT NUMBER (IGNORED)
5 0000 0000 SWITCHES (NOT USED)
6 0000 0000 SEQ NO. (NOT USED)
7 0000 0000 SEQ NO. (NOT USED)
8 0000 0000 BYTE COUNT L NO DATA
9 0000 0000 BYTE COUNT H INVOLVED
10 0000 0000 BLOCKNO L NO TAPE
11 0000 0000 BLOCKNO H POSITION
12 0000 00IX CHECKSUM L
13 0000 1010 CHECKSUM H
The TUS8 returns the following end packet.
0 0000 0010 FLAG
1 0000 1010 MESSAGE BYTE CNT
2 0100 0000 OPCOPE .
3 0000 0000 SUCCESS CODE
4 0000 OOOX UNIT (IGNORED)
S 0000 0000 NOT USED
6 0000 0000 SEQ L
7 0000 0000 SEQ H ....
8 0000 0000 .. ACTUAL BYTE CNT L NO DATA
9 0000 0000 ACTUAL BYTE CNT H INVOLVED
10 0000 0000 SUMMARY STATUS L
11 XXXX XXXX SUMMARY STATUS H
12 ooox XXXX CHECKSUM L
13 XXXX XXXX CHECKSUM H
OP CODE 1 INIT
This instruction causes the TU58 controller to reset itself toĀ· a ready state. No tape positioning results
from this operation. The command 'packet is tbe same as for NOP except for the op code and the resultant
change to the low order checksum byte. The TU58 sends the same end packet as for NOP after
reinitializing itself. There are no modifiers to IN IT.
OP CODE 2 Read, and Read with Decreased Sensitivity
This instruction causes the TU58 to position the tape in the drive selected by Unit Number to the block
designated by the block number bytes. It reads data starting at the designated block and continues
reading until the byte count (command bytes 8 and 9) is satisfied. After data has been sent, the TU58
sends an end packet. Byte 3 indicates success, success with retries, or failure of the operation. In the
event of failure, the end packet is sent at the time of failure without filling up the data count. The end
packet is recognized by the host by the flag byte. The host sees a command flag (0000 0010) instead of
a data flag (0000 0001).
There are two modifiers to the read command. Setting the least significant bit of byte 3 to 1 causes the
TU58 to read the tape with decreased sensitivity in the read amplifier. This makes the read amplifier
miss data if any weak spots are present. Thus, if the TU58 can read error-free in this mode, the data is
healthy. The read transaction between TU58 and host is shown for 510 bytes (just under a full block) in
Figure 3-1. Setting the most significant bit of byte 3 to 1 selects special address mode. See Paragraphs
3.2.3.1 and 3.2.3.2.
OP CODE 3 Write, and Write and Read Verify
This op code causes the TU58 to position the tape in the selected driveto the block specified by the
number in bytes 10,11 of the command packet and write data from the first data packet into that block.
It writes data from subsequent data packets into one or more. blocks until the byte count called out in
bytes 8, 9 of the command packet has been satisfied.
The controller automatically zero-fills any remaining bytes ina 512-byte tape block.
There are two modifiers pennitted with the write command. Setting the least significant bit of byte
3 to 1 causes the TU58 to write all of the data and then back up and read the data just written with
decreased sensitivity and test the checksum of each record. If all of the checksums are correct, the
TU58 sends an end packet with the success code set to 0 (or 1 if retries were necessary to read the
data). Failure to read correct data results in a success code of - 17 (3578 ) to indicate a hard read
error. Setting the most significant bit of byte 3 to 1 selects special address mode. See Paragraph
3.2.3.2. . .
The write operation has to cope with the fact that the TU58 only has 128 bytes of buffer space. It is
necessary for the host to send a data packet and wait for the TU58 to write it before sending the next data
packet. This is accomplished using the continue flag. The continue flag is a single byte response of 000 1
0000 from TU58 to host. The RSP write transaction for both write and write/verify operations is shown in
Figure 3Ā·2. The MRSP write transaction for both write and write/verify operations is shown in Figure 3Ā·3.
OP CODE 4 (Resened)
OP CODE 5 Position
This command causes the TU58 to position tape on the selected drive to the block designated by bytes 10,
11. After reaching the selected block, it sends an end packet. See Paragraph 3.2.3.2.
OP CODE 6 (Reserved)
OP CODE 7 Diagnose
This command causes the TU58 to run its internal diagnostic program which tests the processor, ROM,
and RAM. Upon completion, TU58 sends an end packet with appropriate success code (0 = Pass, -1 =
Fail). Note that if the bootstrap hardware option is selected, boot information will be transmitted without
handshaking even if the switch byte specifies MRSP.
OP CODE 8 Get Status
This command is treated as a NOP. The TU58 returns an end packet.
OP CODE 9 Set Status
This command is treated as a NOP because TU58 status cannot be set from the host. The TU58 returns
an end packet.
OP CODE 10 (Resened)
OP CODE 11 (Resened)
*/
#if defined (VM_VAX) /* VAX version */
#include "vax_defs.h"
extern int32 int_req[IPL_HLVL];
#else /* PDP-11 version */
#include "pdp11_defs.h"
extern int32 int_req[IPL_HLVL];
#endif
#include "pdp11_td.h"
/* DL Definitions */
/* registers */
#define DLICSR_RD (CSR_DONE|CSR_IE) /* DL11C */
#define DLICSR_WR (CSR_IE)
#define DLIBUF_ERR 0100000
#define DLIBUF_OVR 0040000
#define DLIBUF_RBRK 0020000
#define DLIBUF_RD (DLIBUF_ERR|DLIBUF_OVR|DLIBUF_RBRK|0377)
#define DLOCSR_XBR 0000001 /* xmit brk, RWNI */
#define DLOCSR_RD (CSR_DONE|CSR_IE|DLOCSR_XBR)
#define DLOCSR_WR (CSR_IE|DLOCSR_XBR)
static BITFIELD rx_csr_bits[] = {
BITNCF(6), /* unused */
BIT(IE), /* Interrupt Enable */
BIT(DONE), /* Xmit Ready */
BITNCF(8), /* unused */
ENDBITS
};
static BITFIELD rx_buf_bits[] = {
BITF(DAT,8), /* data buffer */
BITNCF(5), /* unused */
BIT(RBRK),
BIT(OVR),
BIT(ERR),
ENDBITS
};
static BITFIELD tx_csr_bits[] = {
BIT(XBR), /* Break */
BITNC, /* unused */
BIT(MAINT), /* Maint */
BITNCF(3), /* unused */
BIT(IE), /* Interrupt Enable */
BIT(DONE), /* Xmit Ready */
BITNCF(8), /* unused */
ENDBITS
};
static BITFIELD tx_buf_bits[] = {
BITF(DAT,8), /* data buffer */
BITNCF(8), /* unused */
ENDBITS
};
static BITFIELD *td_reg_bits[] = {
rx_csr_bits,
rx_buf_bits,
tx_csr_bits,
tx_buf_bits,
};
static const char *tdc_regnam[] =
{
"RX_CSR",
"RX_BUF",
"TX_CSR",
"TX_BUF"
};
/* TU58 definitions */
#define TD_NUMCTLR 16 /* #controllers */
#define UNIT_V_WLK (UNIT_V_UF) /* write locked */
#define UNIT_WLK (1u << UNIT_V_UF)
#define UNIT_WPRT (UNIT_WLK | UNIT_RO) /* write protect */
#define TD_NUMBLK 512 /* blocks/tape */
#define TD_NUMBY 512 /* bytes/block */
#define TD_SIZE (TD_NUMBLK * TD_NUMBY) /* bytes/tape */
#define TD_OPDAT 001 /* Data */
#define TD_OPCMD 002 /* Command */
#define TD_OPINI 004 /* INIT */
#define TD_OPBOO 010 /* Bootstrap */
#define TD_OPCNT 020 /* Continue */
#define TD_OPXOF 023 /* XOFF */
#define TD_CMDNOP 0000 /* NOP */
#define TD_CMDINI 0001 /* INIT */
#define TD_CMDRD 0002 /* Read */
#define TD_CMDWR 0003 /* Write */
#define TD_CMDPOS 0005 /* Position */
#define TD_CMDDIA 0007 /* Diagnose */
#define TD_CMDGST 0010 /* Get Status */
#define TD_CMDSST 0011 /* Set Status */
#define TD_CMDMRSP 0012 /* MRSP Request */
#define TD_CMDEND 0100 /* END */
#define TD_STSOK 0000 /* Normal success */
#define TD_STSRTY 0001 /* Success with retries */
#define TD_STSFAIL 0377 /* Failed selftest */
#define TD_STSPO 0376 /* Partial operation (end of medium) */
#define TD_STSBUN 0370 /* Bad unit number */
#define TD_STSNC 0367 /* No cartridge */
#define TD_STSWP 0365 /* Write protected */
#define TD_STSDCE 0357 /* Data check error */
#define TD_STSSE 0340 /* Seek error (block not found) */
#define TD_STSMS 0337 /* Motor stopped */
#define TD_STSBOP 0320 /* Bad opcode */
#define TD_STSBBN 0311 /* Bad block number (>511) */
#define TD_GETOPC 0 /* get opcode state */
#define TD_GETLEN 1 /* get length state */
#define TD_GETDATA 2 /* get data state */
#define TD_IDLE 0 /* idle state */
#define TD_READ 1 /* read */
#define TD_READ1 2 /* fill buffer */
#define TD_READ2 3 /* empty buffer */
#define TD_WRITE 4 /* write */
#define TD_WRITE1 5 /* write */
#define TD_WRITE2 6 /* write */
#define TD_END 7 /* empty buffer */
#define TD_END1 8 /* empty buffer */
#define TD_INIT 9 /* empty buffer */
static char *td_states[] = {
"IDLE", "READ", "READ1", "READ2", "WRITE", "WRITE1", "WRITE2", "END", "END1", "INIT"
};
static char *td_ops[] = {
"NOP", "INI", "RD", "WR", "004", "POS", "006", "DIA",
"GST", "SST", "MRSP", "013", "014", "015", "016", "017",
"020", "021", "022", "023", "024", "025", "026", "027",
"030", "031", "032", "033", "034", "035", "036", "037",
"040", "041", "042", "043", "044", "045", "046", "047",
"050", "051", "052", "053", "054", "055", "056", "057",
"060", "061", "062", "063", "064", "065", "066", "067",
"070", "071", "072", "073", "074", "075", "076", "077",
"END"
};
static char *td_csostates[] = {
"GETOPC", "GETLEN", "GETDATA"
};
static int32 td_stime = 100; /* seek, per block */
static int32 td_ctime = 150; /* command time */
static int32 td_xtime = 180; /* tr set time */
static int32 td_itime = 180; /* init time */
static int32 td_regval; /* temp location used in reg declarations */
static int32 td_ctrls = 1; /* number of enabled controllers */
static uint32 tdi_ireq = 0;
static uint32 tdo_ireq = 0;
struct CTLR {
DEVICE *dptr;
UNIT *uptr;
uint16 rx_csr;
uint16 rx_buf;
void (*rx_set_int) (int32 ctlr_num, t_bool val);
uint16 tx_csr;
uint16 tx_buf;
void (*tx_set_int) (int32 ctlr_num, t_bool val);
uint8 ibuf[TD_NUMBY+1]; /* input buffer */
int32 ibptr; /* input buffer pointer */
int32 ilen; /* input length */
uint8 obuf[TD_NUMBY+1]; /* output buffer */
int32 obptr; /* output buffer pointer */
int32 olen; /* output length */
int32 block; /* current block number */
int32 txsize; /* remaining transfer size */
int32 offset; /* offset into current transfer */
int32 p_state; /* protocol state */
int32 o_state; /* output state */
int32 unitno; /* active unit number */
int32 ecode; /* end packet success code */
};
static CTLR td_ctlr[TD_NUMCTLR+1]; /* one for each DL based TU58 plus console */
static t_stat td_rd (int32 *data, int32 PA, int32 access);
static t_stat td_wr (int32 data, int32 PA, int32 access);
static t_stat td_svc (UNIT *uptr);
static t_stat td_reset (DEVICE *dptr);
static t_stat td_set_ctrls (UNIT *uptr, int32 val, char *cptr, void *desc);
static t_stat td_show_ctlrs (FILE *st, UNIT *uptr, int32 val, void *desc);
static t_stat td_boot (int32 unitno, DEVICE *dptr);
static t_stat td_help (FILE *st, DEVICE *dptr, UNIT *uptr, int32 flag, const char *cptr);
static void tdi_set_int (int32 ctlr, t_bool val);
static int32 tdi_iack (void);
static void tdo_set_int (int32 ctlr, t_bool val);
static int32 tdo_iack (void);
static const char *td_description (DEVICE *dptr);
static void td_process_packet(CTLR *ctrl);
static t_bool td_test_xfr (UNIT *uptr, int32 state);
/* TU58 data structures
td_dev TD device descriptor
td_unit TD unit list
td_reg TD register list
td_mod TD modifier list
*/
#define IOLN_DL 010
static DIB td_dib = {
IOBA_AUTO, IOLN_DL, &td_rd, &td_wr,
2, IVCL (TDRX), VEC_AUTO, { &tdi_iack, &tdo_iack }, IOLN_DL,
};
static UNIT td_unit[2*TD_NUMCTLR];
static REG td_reg[] = {
{ DRDATAD (CTRLRS, td_ctrls, 4, "number of controllers"), REG_HRO },
{ DRDATAD (CTIME, td_ctime,24, "command time"), PV_LEFT },
{ DRDATAD (STIME, td_stime,24, "seek, per block"), PV_LEFT },
{ DRDATAD (XTIME, td_xtime,24, "tr set time"), PV_LEFT },
{ DRDATAD (ITIME, td_itime,24, "init time"), PV_LEFT },
#define RDATA(nm,loc,wd,desc) SRDATAD(nm,td_ctlr[0].loc,16,wd,0,TD_NUMCTLR+1,sizeof(CTLR),REG_RO,desc)
#define RDATAF(nm,loc,wd,desc,flds) SRDATADF(nm,td_ctlr[0].loc,16,wd,0,TD_NUMCTLR+1,sizeof(CTLR),REG_RO,desc,flds)
{ RDATA (ECODE, ecode, 16, "end packet success code") },
{ RDATA (BLOCK, block, 16, "current block number") },
{ RDATAF (RX_CSR, rx_csr, 16, "input control/status register", rx_csr_bits) },
{ RDATAF (RX_BUF, rx_buf, 16, "input buffer register", rx_buf_bits) },
{ RDATAF (TX_CSR, tx_csr, 16, "output control/status register", tx_csr_bits) },
{ RDATAF (TX_BUF, tx_buf, 16, "output buffer register", tx_buf_bits) },
{ RDATA (P_STATE,p_state, 4, "protocol state") },
{ RDATA (O_STATE,o_state, 4, "output state") },
{ RDATA (IBPTR, ibptr, 16, "input buffer pointer") },
{ RDATA (OBPTR, obptr, 16, "output buffer pointer") },
{ RDATA (ILEN, ilen, 16, "input length") },
{ RDATA (OLEN, olen, 16, "output length") },
{ RDATA (TXSIZE, txsize, 16, "remaining transfer size") },
{ RDATA (OFFSET, offset, 16, "offset into current transfer") },
{ RDATA (UNITNO, unitno, 16, "active unit number") },
{ BRDATAD (IBUF, td_ctlr[0].ibuf,16, 8, 512, "input buffer"), },
{ BRDATAD (OBUF, td_ctlr[0].obuf,16, 8, 512, "output buffer"), },
{ NULL }
};
static MTAB td_mod[] = {
{ UNIT_WLK, 0, "write enabled", "WRITEENABLED", NULL, NULL, NULL, "Write enable TU58 drive" },
{ UNIT_WLK, UNIT_WLK, "write locked", "LOCKED", NULL, NULL, NULL, "Write lock TU58 drive" },
{ MTAB_XTD | MTAB_VDV, 0, "CONTROLLERS", "CONTROLLERS", &td_set_ctrls, &td_show_ctlrs, NULL, "Number of Controllers" },
{ MTAB_XTD|MTAB_VDV, 0, "ADDRESS", NULL, &set_addr, &show_addr, NULL, "Bus address" },
{ MTAB_XTD|MTAB_VDV, 1, "VECTOR", NULL, &set_vec, &show_vec, NULL, "Interrupt vector" },
{ 0 }
};
DEVICE tdc_dev = {
"TDC", td_unit, td_reg, td_mod,
2*TD_NUMCTLR, DEV_RDX, 20, 1, DEV_RDX, 8,
NULL, NULL, &td_reset,
&td_boot, NULL, NULL,
&td_dib, DEV_DISABLE | DEV_DIS | DEV_UBUS | DEV_QBUS | DEV_DEBUG, 0,
td_deb, NULL, NULL, &td_help, NULL, NULL,
&td_description
};
#define CSI_CLR_INT ctlr->rx_set_int (ctlr-td_ctlr, 0)
#define CSI_SET_INT ctlr->rx_set_int (ctlr-td_ctlr, 1)
#define CSO_CLR_INT ctlr->tx_set_int (ctlr-td_ctlr, 0)
#define CSO_SET_INT ctlr->tx_set_int (ctlr-td_ctlr, 1)
t_stat td_rd_i_csr (CTLR *ctlr, int32 *data)
{
*data = ctlr->rx_csr & DLICSR_RD;
sim_debug_bits_hdr(TDDEB_IRD, ctlr->dptr, "RX_CSR", rx_csr_bits, *data, *data, 1);
return SCPE_OK;
}
t_stat td_wr_i_csr (CTLR *ctlr, int32 data)
{
if ((data & CSR_IE) == 0)
CSI_CLR_INT;
else {
if ((ctlr->rx_csr & (CSR_DONE | CSR_IE)) == CSR_DONE)
CSI_SET_INT;
}
sim_debug_bits_hdr(TDDEB_IWR, ctlr->dptr, "RX_CSR", rx_csr_bits, ctlr->rx_csr, data, 1);
ctlr->rx_csr = (ctlr->rx_csr & ~DLICSR_WR) | (data & DLICSR_WR);
return SCPE_OK;
}
t_stat td_rd_i_buf (CTLR *ctlr, int32 *data)
{
int32 t = ctlr->rx_buf;
ctlr->rx_csr &= ~CSR_DONE; /* clr done */
ctlr->rx_buf &= BMASK; /* clr errors */
sim_debug_bits_hdr(TDDEB_IRD, ctlr->dptr, "RX_BUF", rx_buf_bits, t, ctlr->rx_buf, 1);
CSI_CLR_INT;
*data = t;
return SCPE_OK;
}
t_stat td_wr_i_buf (CTLR *ctlr, int32 data)
{
sim_debug_bits_hdr(TDDEB_IWR, ctlr->dptr, "RX_BUF", rx_buf_bits, ctlr->rx_buf, ctlr->rx_buf, 1);
return SCPE_OK;
}
t_stat td_rd_o_csr (CTLR *ctlr, int32 *data)
{
sim_debug_bits_hdr(TDDEB_ORD, ctlr->dptr, "TX_CSR", tx_csr_bits, ctlr->tx_csr, ctlr->tx_csr, 1);
*data = ctlr->tx_csr & DLOCSR_RD;
return SCPE_OK;
}
t_stat td_wr_o_csr (CTLR *ctlr, int32 data)
{
sim_debug_bits_hdr(TDDEB_OWR, ctlr->dptr, "TX_CSR", tx_csr_bits, data, data, 1);
if ((ctlr->tx_csr & DLOCSR_XBR) && !(data & DLOCSR_XBR)) {
ctlr->ibptr = 0;
ctlr->ibuf[ctlr->ibptr++] = TD_OPINI;
td_process_packet(ctlr); /* check packet */
}
if ((data & CSR_IE) == 0)
CSO_CLR_INT;
else if ((ctlr->tx_csr & (CSR_DONE + CSR_IE)) == CSR_DONE)
CSO_SET_INT;
ctlr->tx_csr = (ctlr->tx_csr & ~DLOCSR_WR) | (data & DLOCSR_WR);
return SCPE_OK;
}
t_stat td_rd_o_buf (CTLR *ctlr, int32 *data)
{
*data = 0;
sim_debug_bits_hdr(TDDEB_ORD, ctlr->dptr, "TX_BUF", tx_buf_bits, *data, *data, 1);
return SCPE_OK;
}
t_stat td_wr_o_buf (CTLR *ctlr, int32 data)
{
sim_debug (TDDEB_OWR, ctlr->dptr, "td_wr_o_buf() %s o_state=%s, ibptr=%d, ilen=%d\n", (ctlr->tx_csr & DLOCSR_XBR) ? "XMT-BRK" : "", td_csostates[ctlr->o_state], ctlr->ibptr, ctlr->ilen);
sim_debug_bits_hdr(TDDEB_OWR, ctlr->dptr, "TX_BUF", tx_buf_bits, data, data, 1);
ctlr->tx_buf = data & BMASK; /* save data */
ctlr->tx_csr &= ~CSR_DONE; /* clear flag */
CSO_CLR_INT;
switch (ctlr->o_state) {
case TD_GETOPC:
ctlr->ibptr = 0;
ctlr->ibuf[ctlr->ibptr++] = ctlr->tx_buf & BMASK;
td_process_packet(ctlr); /* check packet */
break;
case TD_GETLEN:
ctlr->ibuf[ctlr->ibptr++] = ctlr->tx_buf & BMASK;
ctlr->ilen = ctlr->tx_buf + 4; /* packet length + header + checksum */
ctlr->o_state = TD_GETDATA;
break;
case TD_GETDATA:
ctlr->ibuf[ctlr->ibptr++] = ctlr->tx_buf & BMASK;
if (ctlr->ibptr >= ctlr->ilen) {
ctlr->o_state = TD_GETOPC;
td_process_packet(ctlr);
}
break;
}
ctlr->tx_csr |= CSR_DONE; /* set input flag */
if (ctlr->tx_csr & CSR_IE)
CSO_SET_INT;
return SCPE_OK;
}
static char *reg_access[] = {"Read", "ReadC", "Write", "WriteC", "WriteB"};
typedef t_stat (*reg_read_routine) (CTLR *ctlr, int32 *data);
static reg_read_routine td_rd_regs[] = {
td_rd_i_csr,
td_rd_i_buf,
td_rd_o_csr,
td_rd_o_buf
};
t_stat td_rd (int32 *data, int32 PA, int32 access)
{
int32 ctlr = ((PA - td_dib.ba) >> 3);
if (ctlr > td_ctrls) /* validate controller number */
return SCPE_IERR;
if (PA & 1) /* odd address reference? */
return SCPE_OK;
sim_debug (TDDEB_RRD, &tdc_dev, "td_rd(PA=%o(%s), access=%d-%s)\n", PA, tdc_regnam[(PA >> 1) & 03], access, reg_access[access]);
return (td_rd_regs[(PA >> 1) & 03])(&td_ctlr[ctlr], data);
}
typedef t_stat (*reg_write_routine) (CTLR *ctlr, int32 data);
static reg_write_routine td_wr_regs[] = {
td_wr_i_csr,
td_wr_i_buf,
td_wr_o_csr,
td_wr_o_buf
};
static t_stat td_wr (int32 data, int32 PA, int32 access)
{
int32 ctrl = ((PA - td_dib.ba) >> 3);
if (ctrl > td_ctrls) /* validate line number */
return SCPE_IERR;
sim_debug (TDDEB_RWR, &tdc_dev, "td_wr(PA=%o(%s), access=%d-%s, data=%X)\n", PA, tdc_regnam[(PA >> 1) & 03], access, reg_access[access], data);
if (PA & 1) /* odd address reference? */
return SCPE_OK;
sim_debug_bits_hdr (TDDEB_RWR, &tdc_dev, tdc_regnam[(PA >> 1) & 03], td_reg_bits[(PA >> 1) & 03], data, data, 1);
return td_wr_regs[(PA >> 1) & 03](&td_ctlr[ctrl], data);
}
static void td_process_packet(CTLR *ctlr)
{
uint32 unit;
int32 opcode = ctlr->ibuf[0];
char *opcode_name, *command_name;
switch (opcode) {
case TD_OPDAT:
opcode_name = "OPDAT";
break;
case TD_OPCMD:
opcode_name = "OPCMD";
break;
case TD_OPINI:
opcode_name = "OPINI";
break;
case TD_OPBOO:
opcode_name = "OPBOO";
break;
case TD_OPCNT:
opcode_name = "OPCNT";
break;
case TD_OPXOF:
opcode_name = "OPXOF";
break;
default:
opcode_name = "unknown";
}
sim_debug (TDDEB_TRC, ctlr->dptr, "td_process_packet() Opcode=%s(%d)\n", opcode_name, opcode);
switch (opcode) {
case TD_OPDAT:
if (ctlr->p_state != TD_WRITE1) { /* expecting data? */
sim_printf("TU58 protocol error 1\n");
return;
}
if (ctlr->ibptr < 2) { /* whole packet read? */
ctlr->o_state = TD_GETLEN; /* get rest of packet */
return;
}
ctlr->p_state = TD_WRITE2;
sim_activate (ctlr->uptr+ctlr->unitno, td_ctime); /* sched command */
break;
case TD_OPCMD:
if (ctlr->p_state != TD_IDLE) { /* expecting command? */
sim_printf("TU58 protocol error 2\n");
return;
}
if (ctlr->ibptr < 2) { /* whole packet read? */
ctlr->o_state = TD_GETLEN; /* get rest of packet */
return;
}
if (ctlr->ibuf[2] > TD_CMDEND)
command_name = "Unknown";
else
command_name = td_ops[ctlr->ibuf[2]];
sim_debug (TDDEB_OPS, ctlr->dptr, "strt: fnc=%d(%s), len=%d, unit=%d, block=%d, size=%d\n", ctlr->ibuf[2], command_name, ctlr->ibuf[1], ctlr->ibuf[4], ((ctlr->ibuf[11] << 8) | ctlr->ibuf[10]), ((ctlr->ibuf[9] << 8) | ctlr->ibuf[8]));
switch (ctlr->ibuf[2]) {
case TD_CMDNOP: /* NOP */
case TD_CMDGST: /* Get status */
case TD_CMDSST: /* Set status */
ctlr->unitno = ctlr->ibuf[4];
ctlr->p_state = TD_END; /* All treated as NOP */
ctlr->ecode = TD_STSOK;
ctlr->offset = 0;
sim_activate (ctlr->uptr+ctlr->unitno, td_ctime);/* sched command */
break;
case TD_CMDINI:
sim_printf("Warning: TU58 command 'INIT' not implemented\n");
break;
case TD_CMDRD:
ctlr->unitno = ctlr->ibuf[4];
ctlr->block = ((ctlr->ibuf[11] << 8) | ctlr->ibuf[10]);
ctlr->txsize = ((ctlr->ibuf[9] << 8) | ctlr->ibuf[8]);
ctlr->p_state = TD_READ;
ctlr->offset = 0;
sim_activate (ctlr->uptr+ctlr->unitno, td_ctime);/* sched command */
break;
case TD_CMDWR:
ctlr->unitno = ctlr->ibuf[4];
ctlr->block = ((ctlr->ibuf[11] << 8) | ctlr->ibuf[10]);
ctlr->txsize = ((ctlr->ibuf[9] << 8) | ctlr->ibuf[8]);
ctlr->p_state = TD_WRITE;
ctlr->offset = 0;
sim_activate (ctlr->uptr+ctlr->unitno, td_ctime);/* sched command */
break;
case TD_CMDPOS:
sim_printf("Warning: TU58 command 'Position' not implemented\n");
break;
case TD_CMDDIA:
sim_printf("Warning: TU58 command 'Diagnose' not implemented\n");
break;
case TD_CMDMRSP:
ctlr->rx_buf = TD_OPDAT;
ctlr->rx_csr |= CSR_DONE; /* set input flag */
if (ctlr->rx_csr & CSR_IE)
CSI_SET_INT;
break;
}
break;
case TD_OPINI:
for (unit=0; unit < ctlr->dptr->numunits; unit++)
sim_cancel (ctlr->uptr+unit);
ctlr->ibptr = 0;
ctlr->obptr = 0;
ctlr->olen = 0;
ctlr->offset = 0;
ctlr->txsize = 0;
ctlr->o_state = TD_GETOPC;
ctlr->p_state = TD_INIT;
sim_activate (ctlr->uptr, td_itime); /* sched command */
break;
case TD_OPBOO:
if (ctlr->ibptr < 2) { /* whole packet read? */
ctlr->ilen = 2;
ctlr->o_state = TD_GETDATA; /* get rest of packet */
return;
}
else {
int8 *fbuf;
int i;
sim_debug (TDDEB_TRC, ctlr->dptr, "td_process_packet(OPBOO) Unit=%d\n", ctlr->ibuf[4]);
ctlr->unitno = ctlr->ibuf[1];
fbuf = ctlr->uptr[ctlr->unitno].filebuf;
ctlr->block = 0;
ctlr->txsize = 0;
ctlr->p_state = TD_READ2;
ctlr->offset = 0;
ctlr->obptr = 0;
for (i=0; i < TD_NUMBY; i++)
ctlr->obuf[i] = fbuf[i];
ctlr->olen = TD_NUMBY;
ctlr->rx_buf = ctlr->obuf[ctlr->obptr++]; /* get first byte */
sim_data_trace(ctlr->dptr, &ctlr->uptr[ctlr->unitno], ctlr->obuf, "Boot Block Data", ctlr->olen, "", TDDEB_DAT);
sim_activate (ctlr->uptr+ctlr->unitno, td_ctime);/* sched command */
}
break;
case TD_OPCNT:
break;
default:
//sim_printf("TU58: Unknown opcode %d\n", opcode);
break;
}
}
static t_stat td_svc (UNIT *uptr)
{
int32 i, t, data_size;
uint16 c, w;
uint32 da;
int8 *fbuf = uptr->filebuf;
CTLR *ctlr = (CTLR *)uptr->up7;
sim_debug (TDDEB_TRC, ctlr->dptr, "td_svc(%s, p_state=%s)\n", sim_uname(uptr), td_states[ctlr->p_state]);
switch (ctlr->p_state) { /* case on state */
case TD_IDLE: /* idle */
return SCPE_IERR; /* done */
case TD_READ: case TD_WRITE: /* read, write */
if (td_test_xfr (uptr, ctlr->p_state)) { /* transfer ok? */
t = abs (ctlr->block - 0); /* # blocks to seek */
if (t == 0) /* minimum 1 */
t = 1;
ctlr->p_state++; /* set next state */
sim_activate (uptr, td_stime * t); /* schedule seek */
break;
}
else
ctlr->p_state = TD_END;
sim_activate (uptr, td_xtime); /* schedule next */
break;
case TD_READ1: /* build data packet */
da = (ctlr->block * 512) + ctlr->offset; /* get tape address */
if (ctlr->txsize > 128) /* Packet length */
data_size = 128;
else
data_size = ctlr->txsize;
ctlr->txsize = ctlr->txsize - data_size;
ctlr->offset = ctlr->offset + data_size;
ctlr->obptr = 0;
ctlr->obuf[ctlr->obptr++] = TD_OPDAT; /* Data packet */
ctlr->obuf[ctlr->obptr++] = data_size; /* Data length */
for (i = 0; i < data_size; i++) /* copy sector to buf */
ctlr->obuf[ctlr->obptr++] = fbuf[da + i];
c = 0;
for (i = 0; i < (data_size + 2); i++) { /* Calculate checksum */
w = (ctlr->obuf[i] << ((i & 0x1) ? 8 : 0));
c = c + w + ( (uint32)((uint32)c + (uint32)w) > 0xFFFF ? 1 : 0);
}
ctlr->obuf[ctlr->obptr++] = (c & 0xFF); /* Checksum L */
ctlr->obuf[ctlr->obptr++] = ((c >> 8) & 0xFF); /* Checksum H */
ctlr->olen = ctlr->obptr;
ctlr->obptr = 0;
ctlr->p_state = TD_READ2; /* go empty */
sim_data_trace(ctlr->dptr, &ctlr->uptr[ctlr->unitno], ctlr->obuf, "Sending Read Data Packet", ctlr->olen, "", TDDEB_DAT);
sim_activate (uptr, td_xtime); /* schedule next */
break;
case TD_READ2: /* send data packet to host */
if ((ctlr->rx_csr & CSR_DONE) == 0) { /* prev data taken? */
ctlr->rx_buf = ctlr->obuf[ctlr->obptr++]; /* get next byte */
ctlr->rx_csr |= CSR_DONE; /* set input flag */
if (ctlr->rx_csr & CSR_IE)
CSI_SET_INT;
if (ctlr->obptr >= ctlr->olen) { /* buffer empty? */
if (ctlr->txsize > 0)
ctlr->p_state = TD_READ1;
else
ctlr->p_state = TD_END;
}
}
sim_activate (uptr, td_xtime); /* schedule next */
break;
case TD_WRITE1: /* send continue */
if ((ctlr->rx_csr & CSR_DONE) == 0) { /* prev data taken? */
ctlr->rx_buf = TD_OPCNT;
ctlr->rx_csr |= CSR_DONE; /* set input flag */
if (ctlr->rx_csr & CSR_IE)
CSI_SET_INT;
break;
}
sim_activate (uptr, td_xtime); /* schedule next */
break;
case TD_WRITE2: /* write data to buffer */
da = (ctlr->block * 512) + ctlr->offset; /* get tape address */
ctlr->olen = ctlr->ibuf[1];
for (i = 0; i < ctlr->olen; i++) /* write data to buffer */
fbuf[da + i] = ctlr->ibuf[i + 2];
ctlr->offset += ctlr->olen;
ctlr->txsize -= ctlr->olen;
da = da + ctlr->olen;
if (da > uptr->hwmark) /* update hwmark */
uptr->hwmark = da;
if (ctlr->txsize > 0)
ctlr->p_state = TD_WRITE1;
else { /* check whole number of blocks written */
if ((ctlr->olen = (512 - (ctlr->offset % 512))) != 512) {
for (i = 0; i < ctlr->olen; i++)
fbuf[da + i] = 0; /* zero fill */
da = da + ctlr->olen;
if (da > uptr->hwmark) /* update hwmark */
uptr->hwmark = da;
}
ctlr->p_state = TD_END;
}
sim_activate (uptr, td_xtime); /* schedule next */
break;
case TD_END: /* build end packet */
ctlr->obptr = 0;
ctlr->obuf[ctlr->obptr++] = TD_OPCMD; /* Command packet */
ctlr->obuf[ctlr->obptr++] = 0xA; /* ** Need definition ** */
ctlr->obuf[ctlr->obptr++] = TD_CMDEND;
ctlr->obuf[ctlr->obptr++] = ctlr->ecode; /* Success code */
ctlr->obuf[ctlr->obptr++] = ctlr->unitno; /* Unit number */
ctlr->obuf[ctlr->obptr++] = 0; /* Not used */
ctlr->obuf[ctlr->obptr++] = 0; /* Sequence L (not used) */
ctlr->obuf[ctlr->obptr++] = 0; /* Sequence H (not used) */
ctlr->obuf[ctlr->obptr++] = (ctlr->offset & 0xFF);/* Byte count L */
ctlr->obuf[ctlr->obptr++] = ((ctlr->offset >> 8) & 0xFF);/* Byte count H */
ctlr->obuf[ctlr->obptr++] = 0; /* Summary status L */
ctlr->obuf[ctlr->obptr++] = 0; /* Summary status H */
c = 0;
for (i = 0; i < (0xA + 2); i++) { /* Calculate checksum */
w = (ctlr->obuf[i] << ((i & 0x1) ? 8 : 0));
c = c + w + ( (uint32)((uint32)c + (uint32)w) > 0xFFFF ? 1 : 0);
}
ctlr->obuf[ctlr->obptr++] = c & 0xFF; /* Checksum L */
ctlr->obuf[ctlr->obptr++] = (c >> 8) & 0xFF; /* Checksum H */
ctlr->olen = ctlr->obptr;
ctlr->obptr = 0;
ctlr->p_state = TD_END1; /* go empty */
sim_debug(TDDEB_PKT, ctlr->dptr, "END PKT: %s Generated - Unit: %d, Success Code: %X\n", sim_uname(uptr), ctlr->unitno, ctlr->ecode);
sim_activate (uptr, td_xtime); /* schedule next */
break;
case TD_END1: /* send end packet to host */
if ((ctlr->rx_csr & CSR_DONE) == 0) { /* prev data taken? */
ctlr->rx_buf = ctlr->obuf[ctlr->obptr++]; /* get next byte */
ctlr->rx_csr |= CSR_DONE; /* set input flag */
if (ctlr->rx_csr & CSR_IE)
CSI_SET_INT;
if (ctlr->obptr >= ctlr->olen) { /* buffer empty? */
sim_debug(TDDEB_PKT, ctlr->dptr, "END PKT: %s Sent. Unit=%d\n", sim_uname(uptr), ctlr->unitno);
ctlr->p_state = TD_IDLE;
break;
}
}
sim_activate (uptr, td_xtime); /* schedule next */
break;
case TD_INIT:
if ((ctlr->rx_csr & CSR_DONE) == 0) { /* prev data taken? */
ctlr->rx_buf = TD_OPCNT;
ctlr->rx_csr |= CSR_DONE; /* set input flag */
if (ctlr->rx_csr & CSR_IE)
CSI_SET_INT;
ctlr->p_state = TD_IDLE;
break;
}
sim_activate (uptr, td_xtime); /* schedule next */
break;
}
return SCPE_OK;
}
/* Test for data transfer okay */
static t_bool td_test_xfr (UNIT *uptr, int32 state)
{
CTLR *ctlr = (CTLR *)uptr->up7;
if ((uptr->flags & UNIT_BUF) == 0) /* not buffered? */
ctlr->ecode = TD_STSNC;
else if (ctlr->block >= TD_NUMBLK) /* bad block? */
ctlr->ecode = TD_STSBBN;
else if ((state == TD_WRITE) && (uptr->flags & UNIT_WPRT)) /* write and locked? */
ctlr->ecode = TD_STSWP;
else {
ctlr->ecode = TD_STSOK;
return TRUE;
}
return FALSE;
}
/* Interrupt routines */
static void tdi_set_int (int32 ctlr, t_bool val)
{
if ((tdi_ireq & (1 << ctlr)) ^ (val << ctlr)) {
sim_debug (TDDEB_INT, &tdc_dev, "tdi_set_int(%d, %d)\n", ctlr, val);
if (val)
tdi_ireq |= (1 << ctlr); /* set rcv int */
else
tdi_ireq &= ~(1 << ctlr); /* clear rcv int */
if (tdi_ireq == 0) /* all clr? */
CLR_INT (TDRX);
else
SET_INT (TDRX); /* no, set intr */
}
}
static int32 tdi_iack (void)
{
int32 ctlr;
sim_debug (TDDEB_INT, &tdc_dev, "tdi_iack()\n");
for (ctlr = 0; ctlr < TD_NUMCTLR; ctlr++) { /* find 1st line */
if (tdi_ireq & (1 << ctlr)) {
tdi_set_int (ctlr, 0); /* clr req */
return (td_dib.vec + (ctlr * 010)); /* return vector */
}
}
return 0;
}
static void tdo_set_int (int32 ctlr, t_bool val)
{
if ((tdo_ireq & (1 << ctlr)) ^ (val << ctlr)) {
sim_debug (TDDEB_INT, &tdc_dev, "tdo_set_int(%d, %d)\n", ctlr, val);
if (val)
tdo_ireq |= (1 << ctlr); /* set xmt int */
else
tdo_ireq &= ~(1 << ctlr); /* clear xmt int */
if (tdo_ireq == 0) /* all clr? */
CLR_INT (TDTX);
else
SET_INT (TDTX); /* no, set intr */
}
}
static int32 tdo_iack (void)
{
int32 ctlr;
sim_debug (TDDEB_INT, &tdc_dev, "tdo_iack()\n");
for (ctlr = 0; ctlr < TD_NUMCTLR; ctlr++) { /* find 1st line */
if (tdo_ireq & (1 << ctlr)) {
tdo_set_int (ctlr, 0); /* clear intr */
return (td_dib.vec + (ctlr * 010) + 4); /* return vector */
}
}
return 0;
}
static t_stat td_reset_ctlr (CTLR *ctlr)
{
REG *reg;
ctlr->tx_buf = 0;
ctlr->tx_csr = CSR_DONE;
CSI_CLR_INT;
ctlr->o_state = TD_GETOPC;
ctlr->ibptr = 0;
ctlr->obptr = 0;
ctlr->ilen = 0;
ctlr->olen = 0;
ctlr->offset = 0;
ctlr->txsize = 0;
ctlr->p_state = 0;
ctlr->ecode = 0;
/* fixup/connect registers to actual data */
reg = find_reg ("ECODE", NULL, ctlr->dptr);
if (reg)
reg->loc = (void *)&ctlr->ecode;
reg = find_reg ("BLOCK", NULL, ctlr->dptr);
if (reg)
reg->loc = (void *)&ctlr->block;
reg = find_reg ("P_STATE", NULL, ctlr->dptr);
if (reg)
reg->loc = (void *)&ctlr->p_state;
reg = find_reg ("O_STATE", NULL, ctlr->dptr);
if (reg)
reg->loc = (void *)&ctlr->o_state;
reg = find_reg ("IBPTR", NULL, ctlr->dptr);
if (reg)
reg->loc = (void *)&ctlr->ibptr;
reg = find_reg ("ILEN", NULL, ctlr->dptr);
if (reg)
reg->loc = (void *)&ctlr->ilen;
reg = find_reg ("OBPTR", NULL, ctlr->dptr);
if (reg)
reg->loc = (void *)&ctlr->obptr;
reg = find_reg ("OLEN", NULL, ctlr->dptr);
if (reg)
reg->loc = (void *)&ctlr->olen;
reg = find_reg ("TXSIZE", NULL, ctlr->dptr);
if (reg)
reg->loc = (void *)&ctlr->txsize;
reg = find_reg ("OFFSET", NULL, ctlr->dptr);
if (reg)
reg->loc = (void *)&ctlr->offset;
reg = find_reg ("IBUF", NULL, ctlr->dptr);
if (reg)
reg->loc = (void *)&ctlr->ibuf;
reg = find_reg ("OBUF", NULL, ctlr->dptr);
if (reg)
reg->loc = (void *)&ctlr->obuf;
reg = find_reg ("RX_CSR", NULL, ctlr->dptr);
if (reg)
reg->loc = (void *)&ctlr->rx_csr;
reg = find_reg ("RX_BUF", NULL, ctlr->dptr);
if (reg)
reg->loc = (void *)&ctlr->rx_buf;
reg = find_reg ("TX_CSR", NULL, ctlr->dptr);
if (reg)
reg->loc = (void *)&ctlr->tx_csr;
reg = find_reg ("TX_BUF", NULL, ctlr->dptr);
if (reg)
reg->loc = (void *)&ctlr->tx_buf;
reg = find_reg ("UNIT", NULL, ctlr->dptr);
if (reg)
reg->loc = (void *)&ctlr->unitno;
reg = find_reg ("CTIME", NULL, ctlr->dptr);
if (reg)
reg->loc = (void *)&td_ctime;
reg = find_reg ("STIME", NULL, ctlr->dptr);
if (reg)
reg->loc = (void *)&td_stime;
reg = find_reg ("XTIME", NULL, ctlr->dptr);
if (reg)
reg->loc = (void *)&td_xtime;
reg = find_reg ("ITIME", NULL, ctlr->dptr);
if (reg)
reg->loc = (void *)&td_itime;
return SCPE_OK;
}
/* Reset */
static t_stat td_reset (DEVICE *dptr)
{
CTLR *ctlr;
int ctl;
static t_bool td_enabled_reset = FALSE;
if (dptr->flags & DEV_DIS)
td_enabled_reset = FALSE;
else {
/* When the TDC device is just being enabled, */
if (!td_enabled_reset) {
char num[16];
td_enabled_reset = TRUE;
/* make sure to bound the number of DLI devices */
sprintf (num, "%d", td_ctrls);
td_set_ctrls (dptr->units, 0, num, NULL);
}
}
sim_debug (TDDEB_INT, dptr, "td_reset()\n");
for (ctl=0; ctl<TD_NUMCTLR; ctl++) {
ctlr = &td_ctlr[ctl];
ctlr->dptr = &tdc_dev;
ctlr->uptr = td_unit + 2*ctl;
ctlr->rx_set_int = tdi_set_int;
ctlr->tx_set_int = tdo_set_int;
td_unit[2*ctl+0].action = &td_svc;
td_unit[2*ctl+0].flags |= UNIT_FIX|UNIT_ATTABLE|UNIT_BUFABLE|UNIT_MUSTBUF|UNIT_DIS;
td_unit[2*ctl+0].capac = TD_SIZE;
td_unit[2*ctl+0].up7 = ctlr;
td_unit[2*ctl+1].action = &td_svc;
td_unit[2*ctl+1].flags |= UNIT_FIX|UNIT_ATTABLE|UNIT_BUFABLE|UNIT_MUSTBUF|UNIT_DIS;
td_unit[2*ctl+1].capac = TD_SIZE;
td_unit[2*ctl+1].up7 = ctlr;
td_reset_ctlr (ctlr);
sim_cancel (&td_unit[2*ctl]);
sim_cancel (&td_unit[2*ctl+1]);
}
for (ctl=0; ctl<td_ctrls; ctl++) {
td_unit[2*ctl+0].flags &= ~UNIT_DIS;
td_unit[2*ctl+1].flags &= ~UNIT_DIS;
}
return auto_config (tdc_dev.name, td_ctrls); /* auto config */
}
static const char *td_description (DEVICE *dptr)
{
return "TU58 cartridge";
}
/* Change number of controllers */
static t_stat td_set_ctrls (UNIT *uptr, int32 val, char *cptr, void *desc)
{
int32 newln, i;
t_stat r;
DEVICE *dli_dptr = find_dev ("DLI");
if (cptr == NULL)
return SCPE_ARG;
newln = (int32)get_uint (cptr, 10, TD_NUMCTLR, &r);
if (r != SCPE_OK)
return r;
if (newln == 0)
return SCPE_ARG;
if (newln < td_ctrls) {
for (i = newln; i < td_ctrls; i++)
if ((td_unit[2*i].flags & UNIT_ATT) ||
(td_unit[2*i+1].flags & UNIT_ATT))
return SCPE_ALATT;
}
td_ctrls = newln;
td_dib.lnt = td_ctrls * td_dib.ulnt; /* upd IO page lnt */
/* Make sure that the number of TU58 controllers plus DL devices is 16 or less */
if ((dli_dptr != NULL) && !(dli_dptr->flags & DEV_DIS) &&
((((DIB *)dli_dptr->ctxt)->numc + td_ctrls) > 16)) { /* Too many? */
dli_dptr->flags |= DEV_DIS; /* First disable DL devices */
dli_dptr->reset (dli_dptr); /* Notify of the disable */
if (td_ctrls < 16) { /* Room for some DL devices? */
dli_dptr->flags &= ~DEV_DIS; /* Re-Enable DL devices */
dli_dptr->reset (dli_dptr); /* Notify of the enable which forces sizing */
}
}
return td_reset (&tdc_dev);
}
/* Show number of controllers */
t_stat td_show_ctlrs (FILE *st, UNIT *uptr, int32 val, void *desc)
{
fprintf (st, "controllers=%d", td_ctrls);
return SCPE_OK;
}
static t_stat td_help (FILE *st, DEVICE *dptr, UNIT *uptr, int32 flag, const char *cptr)
{
fprintf (st, "%s (%s)\n\n", dptr->description (dptr), dptr->name);
fprintf (st, "DECtape TU58 Cartridge .\n\n");
fprint_set_help (st, dptr);
fprint_show_help (st, dptr);
fprint_reg_help (st, dptr);
return SCPE_OK;
}
t_stat td_connect_console_device (DEVICE *dptr,
void (*rx_set_int) (int32 ctlr_num, t_bool val),
void (*tx_set_int) (int32 ctlr_num, t_bool val))
{
uint32 i;
CTLR *ctlr = &td_ctlr[TD_NUMCTLR];
for (i=0; i<dptr->numunits; i++) {
dptr->units[i].capac = TD_SIZE;
dptr->units[i].action = td_svc;
dptr->units[i].flags |= UNIT_FIX|UNIT_ATTABLE|UNIT_BUFABLE|UNIT_MUSTBUF;
dptr->units[i].up7 = (void *)ctlr;
sim_cancel (&dptr->units[i]);
}
ctlr->dptr = dptr;
ctlr->uptr = dptr->units;
ctlr->rx_set_int = rx_set_int;
ctlr->tx_set_int = tx_set_int;
return td_reset_ctlr (ctlr);
}
/* Device bootstrap */
#if defined (VM_PDP11)
#define BOOT_START 02000 /* start */
#define BOOT_ENTRY (BOOT_START + 000) /* entry */
#define BOOT_CSR (BOOT_START + 002) /* CSR */
#define BOOT_UNIT (BOOT_START + 006) /* unit number */
#define BOOT_LEN (sizeof (boot_rom) / sizeof (int16))
/* PDP11 Bootstrap adapted from 23-76589.mac.txt */
static const uint16 boot_rom[] = {
/* RCSR = 0 offset from CSR in R1 */
/* RBUF = 2 offset from CSR in R1 */
/* TCSR = 4 offset from CSR in R1 */
/* TBUF = 6 offset from CSR in R1 */
/* BOOT_START: */
0012701, 0176500, /* MOV #176500,R1 ; Set CSR */
0012702, 0000000, /* MOV #0,R0 ; Set Unit Number */
0012706, BOOT_START,/* MOV #BOOT_START,SP ; Setup a Stack */
0005261, 0000004, /* INC TCSR(R1) ; Set BRK (Init) */
0005003, /* CLR R3 ; data 000, 000 */
0004767, 0000050, /* JSR PC,10$ ; transmit many NULs */
0005061, 0000004, /* CLR TCSR(R1) ; Clear BRK */
0105761, 0000002, /* TSTB RBUF(R1) ; Flush receive char */
0012703, 0004004, /* MOV #<010*400>+004,r3; data 010,004 */
0004767, 0000034, /* JSR PC,12$ ; xmit 004(init) & 010(boot)*/
0010003, /* MOV R0,R3 ; get unit number */
0004767, 0000030, /* JSR PC,13$ ; xmit unit number */
/* ; setup complete, read data bytes */
0005003, /* CLR R3 ; init load address */
0105711, /* 1$: TSTB RCSR(R1) ; next ready? */
0100376, /* BPL 1$ ; not yet? */
0116123, 0000002, /* MOVB RBUF(R1),(R3)+ ; read next byte int memory */
0022703, 0001000, /* CMP #1000,R3 ; all done? */
0101371, /* BHI 1$ ; no, continue */
0005007, /* CLR PC ; Jump to bootstrap at 0 */
/* ; character Output routine */
004717, /* 10$: JSR PC,(PC) ; Recurs call char replicate*/
004717, /* 11$: JSR PC,(PC) ; Recurs call char replicate*/
004717, /* 12$: JSR PC,(PC) ; Recurs call char replicate*/
0105761, 0000004, /* 13$: TSTB TCSR(R1) ; XMit Avail? */
0100375, /* BPL 13$ ; Wait for DONE */
0110361, 0000006, /* MOVB R3,TBUF(R1); Send Character */
0000303, /* SWAB R3 ; swap to other char */
0000207, /* RTS PC ; recurse or return */
};
static t_stat td_boot (int32 unitno, DEVICE *dptr)
{
size_t i;
extern uint16 *M; /* memory */
for (i = 0; i < BOOT_LEN; i++)
M[(BOOT_START >> 1) + i] = boot_rom[i];
M[BOOT_UNIT >> 1] = unitno & 1;
M[BOOT_CSR >> 1] = (td_dib.ba & DMASK) + 000;
cpu_set_boot (BOOT_ENTRY);
return SCPE_OK;
}
#else
static t_stat td_boot (int32 unitno, DEVICE *dptr)
{
return SCPE_NOFNC;
}
#endif