/* 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. 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 breakdown, 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 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 */ #define TD_BOOTSTRAP 10 /* bootstrap read */ #define TD_POSITION 11 /* position */ static const char *td_states[] = { "IDLE", "READ", "READ1", "READ2", "WRITE", "WRITE1", "WRITE2", "END", "END1", "INIT", "BOOTSTRAP","POSITION" }; static const 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 const 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_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, CONST char *cptr, void *desc); static t_stat td_show_ctlrs (FILE *st, UNIT *uptr, int32 val, CONST 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) STRDATAD(nm,td_ctlr[0].loc,16,wd,0,TD_NUMCTLR+1,sizeof(CTLR),REG_RO,desc) #define RDATAF(nm,loc,wd,desc,flds) STRDATADF(nm,td_ctlr[0].loc,16,wd,0,TD_NUMCTLR+1,sizeof(CTLR),REG_RO,desc,flds) { RDATA (ECODE, ecode, 32, "end packet success code") }, { RDATA (BLOCK, block, 32, "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,32, "protocol state") }, { RDATA (O_STATE,o_state,32, "output state") }, { RDATA (IBPTR, ibptr, 32, "input buffer pointer") }, { RDATA (OBPTR, obptr, 32, "output buffer pointer") }, { RDATA (ILEN, ilen, 32, "input length") }, { RDATA (OLEN, olen, 32, "output length") }, { RDATA (TXSIZE, txsize, 32, "remaining transfer size") }, { RDATA (OFFSET, offset, 32, "offset into current transfer") }, { RDATA (UNITNO, unitno, 32, "active unit number") }, /* REG entries for each controller's IBUF and OBUF are dynamically established on first call to td_reset. */ { NULL } }; static MTAB td_mod[] = { { MTAB_XTD|MTAB_VUN, 0, "write enabled", "WRITEENABLED", &set_writelock, &show_writelock, NULL, "Write enable TU58 drive" }, { MTAB_XTD|MTAB_VUN, 1, NULL, "LOCKED", &set_writelock, 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 const 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]; const 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_debug (TDDEB_ERR, ctlr->dptr, "td_process_packet() Opcode=%s(%d) - TU58 protocol error 1 - Not Expecting Data\n", opcode_name, opcode); 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_debug (TDDEB_ERR, ctlr->dptr, "td_process_packet() Opcode=%s(%d) - TU58 protocol error 2 - Not Expecting Command\n", opcode_name, opcode); 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 */ case TD_CMDINI: /* INIT */ case TD_CMDDIA: /* Diagnose */ 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_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: ctlr->unitno = ctlr->ibuf[4]; ctlr->block = ((ctlr->ibuf[11] << 8) | ctlr->ibuf[10]); ctlr->txsize = 0; ctlr->p_state = TD_POSITION; ctlr->offset = 0; sim_activate (ctlr->uptr+ctlr->unitno, td_ctime);/* sched command */ 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 < MIN(ctlr->dptr->numunits, 2); 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 = (int8 *)ctlr->uptr[ctlr->unitno].filebuf; if (fbuf == NULL) { /* attached? */ sim_debug (TDDEB_ERR, ctlr->dptr, "td_process_packet(OPBOO) Unit=%d - NOT ATTACHED\n", ctlr->ibuf[4]); break; } ctlr->block = 0; ctlr->txsize = 0; ctlr->p_state = TD_BOOTSTRAP; 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 */ ctlr->rx_csr |= CSR_DONE; /* set input flag */ if (ctlr->rx_csr & CSR_IE) /* interrupt if enabled */ CSI_SET_INT; 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_debug (TDDEB_TRC, ctlr->dptr, "td_process_packet(%s) Unit=%d Unknown Opcode: %d\n", opcode_name, ctlr->ibuf[4], opcode); break; } } static t_stat td_svc (UNIT *uptr) { int32 i, t, data_size; uint16 c, w; uint32 da; int8 *fbuf = (int8 *)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_POSITION: /* position */ 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 = TD_END; /* 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_BOOTSTRAP: /* send data 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? */ ctlr->p_state = TD_IDLE; break; } } 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) { 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; return SCPE_OK; } /* Reset */ static t_stat td_reset (DEVICE *dptr) { CTLR *ctlr; int ctl; static t_bool td_enabled_reset = FALSE; static t_bool td_regs_inited = FALSE; if (!td_regs_inited) { int regs; int reg; REG *registers; /* Count initial register array */ for (regs = 0; dptr->registers [regs].name != NULL; regs++) ; /* Allocate new register array with room for input and output buffer registers */ registers = (REG *)calloc (regs + 2 * (TD_NUMCTLR + 1) + 1, sizeof (*registers)); if (registers == NULL) return SCPE_MEM; /* Copy initial register array */ for (reg = 0; reg < regs; reg++) registers[reg] = dptr->registers[reg]; /* For each controller add input and output buffer register entries */ for (ctl = 0; ctl < TD_NUMCTLR + 1; ctl++) { char reg_name[32]; char reg_desc[64]; static REG reg_template[] = { { BRDATAD(TBUF, td_ctlr[0].ibuf, 16, 8, TD_NUMBY + 1, "input buffer") }, { NULL } }; snprintf(reg_name, sizeof(reg_name), "IBUF_%d", ctl); registers[reg] = reg_template[0]; registers[reg].name = (char *)calloc (strlen (reg_name) + 1, sizeof (char)); strcpy ((char *)registers[reg].name, reg_name); snprintf(reg_desc, sizeof(reg_desc), "input buffer for %s%d", dptr->name, ctl); registers[reg].desc = (char*)calloc(strlen(reg_desc) + 1, sizeof(char)); strcpy((char*)registers[reg].desc, reg_desc); registers[reg].loc = td_ctlr[ctl].ibuf; snprintf(reg_name, sizeof(reg_name), "OBUF_%d", ctl); registers[reg + 1] = reg_template[0]; registers[reg + 1].name = (char*)calloc(strlen(reg_name) + 1, sizeof(char)); strcpy((char*)registers[reg + 1].name, reg_name); snprintf(reg_desc, sizeof(reg_desc), "output buffer for %s%d", dptr->name, ctl); registers[reg + 1].desc = (char*)calloc(strlen(reg_desc) + 1, sizeof(char)); strcpy((char*)registers[reg + 1].desc, reg_desc); registers[reg + 1].loc = td_ctlr[ctl].obuf; reg += 2; } dptr->registers = registers; td_regs_inited = TRUE; } 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; ctldptr = &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; ctlflags & 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, CONST 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; inumunits; 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 */ 0012700, 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; for (i = 0; i < BOOT_LEN; i++) WrMemW (BOOT_START + (2 * i), boot_rom[i]); WrMemW (BOOT_UNIT, unitno & 1); WrMemW (BOOT_CSR, (td_dib.ba & DMASK) + (unitno >> 1) * 010); cpu_set_boot (BOOT_ENTRY); return SCPE_OK; } #else static t_stat td_boot (int32 unitno, DEVICE *dptr) { return SCPE_NOFNC; } #endif