/* 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 |