mirror of
https://github.com/rcornwell/sims.git
synced 2026-04-25 19:51:57 +00:00
1080 lines
35 KiB
C
1080 lines
35 KiB
C
/* ibm360_chan.c: IBM 360 Channel functions.
|
||
|
||
Copyright (c) 2017, Richard Cornwell
|
||
|
||
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
|
||
RICHARD CORNWELL 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.
|
||
|
||
|
||
*/
|
||
|
||
#include "ibm360_defs.h"
|
||
#include "sim_defs.h"
|
||
|
||
|
||
#define CCMDMSK 0xff000000 /* Mask for command */
|
||
#define CDADRMSK 0x00ffffff /* Mask for data address */
|
||
#define CCNTMSK 0x0000ffff /* Mask for data count */
|
||
#define CD 0x80000000 /* Chain data */
|
||
#define CC 0x40000000 /* Chain command */
|
||
#define SLI 0x20000000 /* Suppress length indication */
|
||
#define SKIP 0x10000000 /* Skip flag */
|
||
#define PCI 0x08000000 /* Program controlled interuption */
|
||
|
||
/* Command masks */
|
||
#define CMD_TYPE 0x3 /* Type mask */
|
||
#define CMD_CHAN 0x0 /* Channel command */
|
||
#define CMD_WRITE 0x1 /* Write command */
|
||
#define CMD_READ 0x2 /* Read command */
|
||
#define CMD_CTL 0x3 /* Control command */
|
||
#define CMD_SENSE 0x4 /* Sense channel command */
|
||
#define CMD_TIC 0x8 /* Transfer in channel */
|
||
#define CMD_RDBWD 0xc /* Read backward */
|
||
|
||
#define STATUS_ATTN 0x8000 /* Device raised attention */
|
||
#define STATUS_MOD 0x4000 /* Status modifier */
|
||
#define STATUS_CTLEND 0x2000 /* Control end */
|
||
#define STATUS_BUSY 0x1000 /* Device busy */
|
||
#define STATUS_CEND 0x0800 /* Channel end */
|
||
#define STATUS_DEND 0x0400 /* Device end */
|
||
#define STATUS_CHECK 0x0200 /* Unit check */
|
||
#define STATUS_EXPT 0x0100 /* Unit excpetion */
|
||
#define STATUS_PCI 0x0080 /* Program interupt */
|
||
#define STATUS_LENGTH 0x0040 /* Incorrect lenght */
|
||
#define STATUS_PCHK 0x0020 /* Program check */
|
||
#define STATUS_PROT 0x0010 /* Protection check */
|
||
#define STATUS_CDATA 0x0008 /* Channel data check */
|
||
#define STATUS_CCNTL 0x0004 /* Channel control check */
|
||
#define STATUS_INTER 0x0002 /* Channel interface check */
|
||
#define STATUS_CHAIN 0x0001 /* Channel chain check */
|
||
|
||
#define FLAG_CD 0x8000 /* Chain data */
|
||
#define FLAG_CC 0x4000 /* Chain command */
|
||
#define FLAG_SLI 0x2000 /* Suppress length indicator */
|
||
#define FLAG_SKIP 0x1000 /* Suppress memory write */
|
||
#define FLAG_PCI 0x0800 /* Program controled interrupt */
|
||
|
||
#define BUFF_EMPTY 0x4 /* Buffer is empty */
|
||
#define BUFF_DIRTY 0x8 /* Buffer is dirty flag */
|
||
#define BUFF_NEWCMD 0x10 /* Channel ready for new command */
|
||
#define BUFF_CHNEND 0x20 /* Channel end */
|
||
|
||
#define AMASK 0x00ffffff
|
||
#define PMASK 0xf0000000 /* Storage protection mask */
|
||
extern uint32 *M;
|
||
extern uint8 key[MAXMEMSIZE / 2048];
|
||
|
||
#define MAX_DEV (MAX_CHAN * 256)
|
||
int channels = MAX_CHAN;
|
||
int subchannels = SUB_CHANS; /* Number of subchannels */
|
||
int irq_pend = 0;
|
||
uint32 caw[256]; /* Channel command address word */
|
||
uint32 ccw_addr[256]; /* Channel address */
|
||
uint16 ccw_count[256]; /* Channel count */
|
||
uint8 ccw_cmd[256]; /* Channel command and flags */
|
||
uint16 ccw_flags[256]; /* Channel flags */
|
||
uint16 chan_status[256]; /* Channel status */
|
||
uint16 chan_dev[256]; /* Device on channel */
|
||
uint32 chan_buf[256]; /* Channel data buffer */
|
||
uint8 chan_byte[256]; /* Current byte, dirty/full */
|
||
DIB *dev_unit[MAX_DEV]; /* Pointer to Device info block */
|
||
uint8 dev_status[MAX_DEV]; /* last device status flags */
|
||
|
||
/* Find unit pointer for given device */
|
||
UNIT *
|
||
find_chan_dev(uint16 addr) {
|
||
struct dib *dibp;
|
||
UNIT *uptr;
|
||
int i;
|
||
|
||
dibp = dev_unit[addr];
|
||
if (dibp == 0)
|
||
return NULL;
|
||
uptr = dibp->units;
|
||
if (dibp->mask == 0) {
|
||
for (i = 0; i < dibp->numunits; i++) {
|
||
if (addr == GET_UADDR(uptr->u3))
|
||
return uptr;
|
||
uptr++;
|
||
}
|
||
} else {
|
||
return uptr + (addr & ~dibp->mask & 0xff);
|
||
}
|
||
return NULL;
|
||
}
|
||
|
||
/* channel:
|
||
subchannels = 128
|
||
0 - 7 0x80-0xff
|
||
8 - 127 0x00-0x7f
|
||
128 - +6 0x1xx - 0x6xx
|
||
*/
|
||
|
||
/* look up device to find subchannel device is on */
|
||
int
|
||
find_subchan(uint16 device) {
|
||
int chan;
|
||
if (device > MAX_DEV)
|
||
return -1;
|
||
if (device > 0xff) {
|
||
chan = (device >> 8) & 0x7;
|
||
if (chan > channels)
|
||
return -1;
|
||
return subchannels + chan;
|
||
}
|
||
if (device < subchannels)
|
||
return device;
|
||
return ((device - subchannels)>>4) & 0xf;
|
||
}
|
||
|
||
/* Read a full word into memory.
|
||
* Return 1 if fail.
|
||
* Return 0 if success.
|
||
*/
|
||
int
|
||
readfull(int chan, uint32 addr, uint32 *word) {
|
||
int sk, k;
|
||
if ((addr & AMASK) > MEMSIZE) {
|
||
chan_status[chan] |= STATUS_PCHK;
|
||
irq_pend = 1;
|
||
return 1;
|
||
}
|
||
sk = (addr >> 24) & 0xf0;
|
||
addr &= AMASK;
|
||
addr >>= 2;
|
||
if (sk != 0) {
|
||
if ((cpu_unit.flags & FEAT_PROT) == 0) {
|
||
chan_status[chan] |= STATUS_PROT;
|
||
irq_pend = 1;
|
||
return 1;
|
||
}
|
||
k = key[addr >> 9];
|
||
if ((k & 0x8) != 0 && (k & 0xf0) != sk) {
|
||
//fprintf(stderr, "Prot %08x %x %x\n\r", addr << 2, k, sk);
|
||
chan_status[chan] |= STATUS_PROT;
|
||
irq_pend = 1;
|
||
return 1;
|
||
}
|
||
}
|
||
*word = M[addr];
|
||
return 0;
|
||
}
|
||
|
||
/* Read a word into the channel buffer.
|
||
* Return 1 if fail.
|
||
* Return 0 if success.
|
||
*/
|
||
int
|
||
readbuff(int chan) {
|
||
int sk, k;
|
||
uint32 addr = ccw_addr[chan];
|
||
if ((addr & AMASK) > MEMSIZE) {
|
||
chan_status[chan] |= STATUS_PCHK;
|
||
chan_byte[chan] = BUFF_CHNEND;
|
||
irq_pend = 1;
|
||
return 1;
|
||
}
|
||
sk = (addr >> 24) & 0xf0;
|
||
addr &= AMASK;
|
||
addr >>= 2;
|
||
if (sk != 0) {
|
||
if ((cpu_unit.flags & FEAT_PROT) == 0) {
|
||
chan_status[chan] |= STATUS_PROT;
|
||
chan_byte[chan] = BUFF_CHNEND;
|
||
irq_pend = 1;
|
||
return 1;
|
||
}
|
||
k = key[addr >> 9];
|
||
if ((k & 0x8) != 0 && (k & 0xf0) != sk) {
|
||
//fprintf(stderr, "Prot %08x %x %x\n\r", addr << 2, k, sk);
|
||
chan_status[chan] |= STATUS_PROT;
|
||
chan_byte[chan] = BUFF_CHNEND;
|
||
irq_pend = 1;
|
||
return 1;
|
||
}
|
||
}
|
||
chan_buf[chan] = M[addr];
|
||
if ((ccw_cmd[chan] & CMD_TYPE) == CMD_WRITE) {
|
||
sim_debug(DEBUG_CDATA, &cpu_dev, "Channel write %02x %06x %08x %08x '",
|
||
chan, ccw_addr[chan] & 0xFFFFFC, chan_buf[chan], ccw_count[chan]);
|
||
for(k = 24; k >= 0; k -= 8) {
|
||
char ch = ebcdic_to_ascii[(chan_buf[chan] >> k) & 0xFF];
|
||
if (ch < 0x20 || ch == 0xff)
|
||
ch = '.';
|
||
sim_debug(DEBUG_CDATA, &cpu_dev, "%c", ch);
|
||
}
|
||
|
||
sim_debug(DEBUG_CDATA, & cpu_dev, "'\n");
|
||
}
|
||
return 0;
|
||
}
|
||
|
||
/* Write channel buffer to memory.
|
||
* Return 1 if fail.
|
||
* Return 0 if success.
|
||
*/
|
||
int
|
||
writebuff(int chan) {
|
||
int sk, k;
|
||
uint32 addr = ccw_addr[chan];
|
||
if ((addr & AMASK) > MEMSIZE) {
|
||
chan_status[chan] |= STATUS_PCHK;
|
||
chan_byte[chan] = BUFF_CHNEND;
|
||
irq_pend = 1;
|
||
return 1;
|
||
}
|
||
sk = (addr >> 24) & 0xff;
|
||
addr &= AMASK;
|
||
addr >>= 2;
|
||
if (sk != 0) {
|
||
if ((cpu_unit.flags & FEAT_PROT) == 0) {
|
||
chan_status[chan] |= STATUS_PROT;
|
||
chan_byte[chan] = BUFF_CHNEND;
|
||
irq_pend = 1;
|
||
return 1;
|
||
}
|
||
k = key[addr >> 9];
|
||
if ((k & 0xf0) != sk) {
|
||
//fprintf(stderr, "Prot %08x %x %x\n\r", addr << 2, k, sk);
|
||
chan_status[chan] |= STATUS_PROT;
|
||
chan_byte[chan] = BUFF_CHNEND;
|
||
irq_pend = 1;
|
||
return 1;
|
||
}
|
||
}
|
||
M[addr] = chan_buf[chan];
|
||
sim_debug(DEBUG_CDATA, &cpu_dev, "Channel readf %02x %06x %08x %08x '",
|
||
chan, ccw_addr[chan] & 0xFFFFFC, chan_buf[chan], ccw_count[chan]);
|
||
for(k = 24; k >= 0; k -= 8) {
|
||
char ch = ebcdic_to_ascii[(chan_buf[chan] >> k) & 0xFF];
|
||
if (ch < 0x20 || ch == 0xff)
|
||
ch = '.';
|
||
sim_debug(DEBUG_CDATA, &cpu_dev, "%c", ch);
|
||
}
|
||
sim_debug(DEBUG_CDATA, &cpu_dev, "'\n");
|
||
return 0;
|
||
}
|
||
|
||
/*
|
||
* Load in the next CCW, return 1 if failure, 0 if success.
|
||
*/
|
||
int
|
||
load_ccw(uint16 chan, int tic_ok) {
|
||
uint32 word;
|
||
int cmd = 0;
|
||
UNIT *uptr;
|
||
|
||
loop:
|
||
/* Abort if channel not on double boundry */
|
||
if ((caw[chan] & 0x7) != 0) {
|
||
chan_status[chan] |= STATUS_PCHK;
|
||
return 1;
|
||
}
|
||
/* Abort if we have any errors */
|
||
if (chan_status[chan] & 0x7f)
|
||
return 1;
|
||
/* Check if we have status modifier set */
|
||
if (chan_status[chan] & STATUS_MOD) {
|
||
caw[chan]+=8;
|
||
caw[chan] &= PMASK|AMASK; /* Mask overflow bits */
|
||
chan_status[chan] &= ~STATUS_MOD;
|
||
}
|
||
/* Read in next CCW */
|
||
readfull(chan, caw[chan], &word);
|
||
sim_debug(DEBUG_CMD, &cpu_dev, "Channel read ccw %02x %06x %08x\n",
|
||
chan, caw[chan], word);
|
||
/* TIC can't follow TIC nor be first in chain */
|
||
if (((word >> 24) & 0xf) == CMD_TIC) {
|
||
if (tic_ok) {
|
||
caw[chan] = (caw[chan] & PMASK) | (word & AMASK);
|
||
tic_ok = 0;
|
||
goto loop;
|
||
}
|
||
chan_status[chan] |= STATUS_PCHK;
|
||
irq_pend = 1;
|
||
return 1;
|
||
}
|
||
caw[chan] += 4;
|
||
caw[chan] &= PMASK|AMASK; /* Mask overflow bits */
|
||
/* Check if not chaining data */
|
||
if ((ccw_flags[chan] & FLAG_CD) == 0) {
|
||
ccw_cmd[chan] = (word >> 24) & 0xff;
|
||
cmd = 1;
|
||
}
|
||
/* Set up for this command */
|
||
ccw_addr[chan] = word & AMASK;
|
||
ccw_addr[chan] |= caw[chan] & PMASK; /* Copy key */
|
||
readfull(chan, caw[chan], &word);
|
||
sim_debug(DEBUG_CMD, &cpu_dev, "Channel read ccw2 %02x %06x %08x\n",
|
||
chan, caw[chan], word);
|
||
caw[chan]+=4;
|
||
caw[chan] &= PMASK|AMASK; /* Mask overflow bits */
|
||
ccw_count[chan] = word & 0xffff;
|
||
/* Copy SLI indicator on CD command */
|
||
if ((ccw_flags[chan] & (FLAG_CD|FLAG_SLI)) == (FLAG_CD|FLAG_SLI))
|
||
word |= (FLAG_SLI<<16);
|
||
ccw_flags[chan] = (word >> 16) & 0xffff;
|
||
chan_byte[chan] = BUFF_EMPTY;
|
||
/* Check invalid count */
|
||
if (ccw_count[chan] == 0) {
|
||
chan_status[chan] |= STATUS_PCHK;
|
||
irq_pend = 1;
|
||
return 1;
|
||
}
|
||
if (cmd) {
|
||
DIB *dibp = dev_unit[chan_dev[chan]];
|
||
/* Check if invalid command */
|
||
if ((ccw_cmd[chan] & 0xF) == 0) {
|
||
chan_status[chan] |= STATUS_PCHK;
|
||
irq_pend = 1;
|
||
return 1;
|
||
}
|
||
uptr = find_chan_dev(chan_dev[chan]);
|
||
if (uptr == 0)
|
||
return 1;
|
||
chan_status[chan] &= 0xff;
|
||
chan_status[chan] |= dibp->start_cmd(uptr, chan, ccw_cmd[chan]) << 8;
|
||
if (chan_status[chan] & (STATUS_ATTN|STATUS_CHECK|STATUS_EXPT)) {
|
||
chan_status[chan] |= STATUS_CEND;
|
||
ccw_flags[chan] = 0;
|
||
ccw_cmd[chan] = 0;
|
||
irq_pend = 1;
|
||
return 1;
|
||
}
|
||
if (chan_status[chan] & (STATUS_DEND|STATUS_CEND)) {
|
||
chan_status[chan] |= STATUS_CEND;
|
||
chan_byte[chan] = BUFF_NEWCMD;
|
||
ccw_cmd[chan] = 0;
|
||
irq_pend = 1;
|
||
}
|
||
}
|
||
if (ccw_flags[chan] & FLAG_PCI) {
|
||
chan_status[chan] |= STATUS_PCI;
|
||
ccw_flags[chan] &= ~FLAG_PCI;
|
||
irq_pend = 1;
|
||
sim_debug(DEBUG_CMD, &cpu_dev, "Set PCI %02x\n", chan);
|
||
}
|
||
return 0;
|
||
}
|
||
|
||
|
||
/* read byte from memory */
|
||
int
|
||
chan_read_byte(uint16 addr, uint8 *data) {
|
||
int chan = find_subchan(addr);
|
||
int byte;
|
||
int k;
|
||
|
||
/* Abort if we have any errors */
|
||
if (chan < 0)
|
||
return 1;
|
||
if (chan_status[chan] & 0x7f)
|
||
return 1;
|
||
if ((ccw_cmd[chan] & 0x1) == 0) {
|
||
return 1;
|
||
}
|
||
/* Check if finished transfer */
|
||
if (chan_byte[chan] == BUFF_CHNEND)
|
||
return 1;
|
||
/* Check if count is zero */
|
||
if (ccw_count[chan] == 0) {
|
||
/* If not data channing, let device know there will be no
|
||
* more data to come
|
||
*/
|
||
if ((ccw_flags[chan] & FLAG_CD) == 0) {
|
||
chan_status[chan] |= STATUS_CEND;
|
||
chan_byte[chan] = BUFF_CHNEND;
|
||
sim_debug(DEBUG_DETAIL, &cpu_dev, "chan_read_end\n");
|
||
return 1;
|
||
} else {
|
||
/* If chaining try and start next CCW */
|
||
if (load_ccw(chan, 1))
|
||
return 1;
|
||
}
|
||
}
|
||
/* Read in next work if buffer is in empty status */
|
||
if (chan_byte[chan] == BUFF_EMPTY) {
|
||
if (readbuff(chan))
|
||
return 1;
|
||
chan_byte[chan] = ccw_addr[chan] & 0x3;
|
||
ccw_addr[chan] += 4 - chan_byte[chan];
|
||
}
|
||
/* Return current byte */
|
||
ccw_count[chan]--;
|
||
byte = (chan_buf[chan] >> (8 * (3 - (chan_byte[chan] & 0x3)))) & 0xff;
|
||
chan_byte[chan]++;
|
||
*data = byte;
|
||
return 0;
|
||
}
|
||
|
||
/* write byte to memory */
|
||
int
|
||
chan_write_byte(uint16 addr, uint8 *data) {
|
||
int chan = find_subchan(addr);
|
||
int byte;
|
||
int offset;
|
||
int k;
|
||
uint32 mask;
|
||
|
||
/* Abort if we have any errors */
|
||
if (chan < 0)
|
||
return 1;
|
||
if (chan_status[chan] & 0x7f)
|
||
return 1;
|
||
if ((ccw_cmd[chan] & 0x1) != 0) {
|
||
return 1;
|
||
}
|
||
/* Check if at end of transfer */
|
||
if (chan_byte[chan] == BUFF_CHNEND) {
|
||
if ((ccw_flags[chan] & FLAG_SLI) == 0) {
|
||
chan_status[chan] |= STATUS_LENGTH;
|
||
}
|
||
return 1;
|
||
}
|
||
/* Check if count is zero */
|
||
if (ccw_count[chan] == 0) {
|
||
/* Flush the buffer if we got anything back. */
|
||
if (chan_byte[chan] & BUFF_DIRTY) {
|
||
if (writebuff(chan))
|
||
return 1;
|
||
}
|
||
/* If not data channing, let device know there will be no
|
||
* more data to come
|
||
*/
|
||
if ((ccw_flags[chan] & FLAG_CD) == 0) {
|
||
chan_byte[chan] = BUFF_CHNEND;
|
||
if ((ccw_flags[chan] & FLAG_SLI) == 0) {
|
||
sim_debug(DEBUG_DETAIL, &cpu_dev, "chan_write_ length\n");
|
||
chan_status[chan] |= STATUS_LENGTH;
|
||
}
|
||
sim_debug(DEBUG_DETAIL, &cpu_dev, "chan_write_end\n");
|
||
return 1;
|
||
}
|
||
/* Otherwise try and grab next CCW */
|
||
if (load_ccw(chan, 1))
|
||
return 1;
|
||
}
|
||
/* If we are skipping, just adjust count */
|
||
if (ccw_flags[chan] & FLAG_SKIP) {
|
||
ccw_count[chan]--;
|
||
chan_byte[chan] = BUFF_EMPTY;
|
||
if ((ccw_cmd[chan] & 0xf) == CMD_RDBWD)
|
||
ccw_addr[chan]--;
|
||
else
|
||
ccw_addr[chan]++;
|
||
return 0;
|
||
}
|
||
/* Check if we need to save what we have */
|
||
if (chan_byte[chan] == (BUFF_EMPTY|BUFF_DIRTY)) {
|
||
if (writebuff(chan))
|
||
return 1;
|
||
if ((ccw_cmd[chan] & 0xf) == CMD_RDBWD)
|
||
ccw_addr[chan] -= 1 + (ccw_addr[chan] & 0x3);
|
||
else
|
||
ccw_addr[chan] += 4 - (ccw_addr[chan] & 0x3);
|
||
chan_byte[chan] = BUFF_EMPTY;
|
||
}
|
||
if (chan_byte[chan] == BUFF_EMPTY) {
|
||
if (readbuff(chan))
|
||
return 1;
|
||
chan_byte[chan] = ccw_addr[chan] & 0x3;
|
||
}
|
||
/* Store it in buffer and adjust pointer */
|
||
ccw_count[chan]--;
|
||
offset = 8 * (chan_byte[chan] & 0x3);
|
||
mask = 0xff000000 >> offset;
|
||
chan_buf[chan] &= ~mask;
|
||
chan_buf[chan] |= ((uint32)(*data)) << (24 - offset);
|
||
if ((ccw_cmd[chan] & 0xf) == CMD_RDBWD) {
|
||
if (chan_byte[chan] & 0x3)
|
||
chan_byte[chan]--;
|
||
else
|
||
chan_byte[chan] = BUFF_EMPTY;
|
||
} else
|
||
chan_byte[chan]++;
|
||
chan_byte[chan] |= BUFF_DIRTY;
|
||
return 0;
|
||
}
|
||
|
||
/*
|
||
* A device wishes to inform the CPU it needs some service.
|
||
*/
|
||
void
|
||
set_devattn(uint16 addr, uint8 flags) {
|
||
int chan = find_subchan(addr);
|
||
|
||
if (chan < 0)
|
||
return;
|
||
if (chan_dev[chan] == addr && (chan_status[chan] & STATUS_CEND) != 0 &&
|
||
(flags & SNS_DEVEND) != 0) {
|
||
chan_status[chan] |= ((uint16)flags) << 8;
|
||
} else
|
||
dev_status[addr] = flags;
|
||
sim_debug(DEBUG_EXP, &cpu_dev, "set_devattn(%x, %x) %x\n",
|
||
addr, flags, chan_dev[chan]);
|
||
irq_pend = 1;
|
||
}
|
||
|
||
/*
|
||
* Signal end of transfer by device.
|
||
*/
|
||
void
|
||
chan_end(uint16 addr, uint8 flags) {
|
||
int chan = find_subchan(addr);
|
||
|
||
if (chan < 0)
|
||
return;
|
||
|
||
sim_debug(DEBUG_DETAIL, &cpu_dev, "chan_end(%x, %x) %x\n", addr, flags, ccw_count[chan]);
|
||
/* If PCI flag set, trigger interrupt */
|
||
if (ccw_flags[chan] & FLAG_PCI) {
|
||
chan_status[chan] |= STATUS_PCI;
|
||
ccw_flags[chan] &= ~FLAG_PCI;
|
||
irq_pend = 1;
|
||
}
|
||
/* Flush buffer if there was any change */
|
||
if (chan_byte[chan] & BUFF_DIRTY) {
|
||
if (writebuff(chan))
|
||
return;
|
||
chan_byte[chan] = BUFF_EMPTY;
|
||
}
|
||
chan_status[chan] |= STATUS_CEND;
|
||
chan_status[chan] |= ((uint16)flags) << 8;
|
||
ccw_cmd[chan] = 0;
|
||
|
||
/* If count not zero and not suppressing length, report error */
|
||
if (ccw_count[chan] != 0 && (ccw_flags[chan] & FLAG_SLI) == 0) {
|
||
sim_debug(DEBUG_DETAIL, &cpu_dev, "chan_end length\n");
|
||
chan_status[chan] |= STATUS_LENGTH;
|
||
ccw_flags[chan] = 0;
|
||
}
|
||
if (flags & (SNS_ATTN|SNS_UNITCHK|SNS_UNITEXP))
|
||
ccw_flags[chan] = 0;
|
||
|
||
/* If channel is also finished, then skip any more data commands. */
|
||
if (chan_status[chan] & (STATUS_DEND|STATUS_CEND)) {
|
||
chan_byte[chan] = BUFF_NEWCMD;
|
||
|
||
/* While command has chain data set, continue to skip */
|
||
while ((ccw_flags[chan] & FLAG_CD)) {
|
||
if (load_ccw(chan, 1))
|
||
break;
|
||
if ((ccw_flags[chan] & FLAG_SLI) == 0) {
|
||
sim_debug(DEBUG_DETAIL, &cpu_dev, "chan_end length\n");
|
||
chan_status[chan] |= STATUS_LENGTH;
|
||
ccw_flags[chan] = 0;
|
||
}
|
||
}
|
||
}
|
||
|
||
irq_pend = 1;
|
||
}
|
||
|
||
/*
|
||
* Save full csw.
|
||
*/
|
||
int
|
||
store_csw(uint16 chan) {
|
||
M[0x40 >> 2] = caw[chan];
|
||
M[0x44 >> 2] = (((uint32)ccw_count[chan])) | ((uint32)chan_status[chan]<<16);
|
||
if (chan_status[chan] & STATUS_PCI) {
|
||
chan_status[chan] &= ~STATUS_PCI;
|
||
ccw_flags[chan] &= ~FLAG_PCI;
|
||
} else {
|
||
chan_status[chan] = 0;
|
||
ccw_flags[chan] &= ~FLAG_PCI;
|
||
}
|
||
sim_debug(DEBUG_EXP, &cpu_dev, "Channel store csw %02x %06x %08x\n",
|
||
chan, M[0x40>>2], M[0x44 >> 2]);
|
||
return chan_dev[chan];
|
||
}
|
||
|
||
/*
|
||
* Handle SIO instruction.
|
||
*/
|
||
int
|
||
startio(uint16 addr) {
|
||
int chan = find_subchan(addr);
|
||
DIB *dibp = dev_unit[addr];
|
||
UNIT *uptr;
|
||
uint8 status;
|
||
|
||
/* Find channel this device is on, if no none return cc=3 */
|
||
if (chan < 0 || dibp == 0)
|
||
return 3;
|
||
uptr = find_chan_dev(addr);
|
||
if (uptr == 0)
|
||
return 3;
|
||
|
||
/* If channel is active return cc=2 */
|
||
if (ccw_cmd[chan] != 0 || (ccw_flags[chan] & (FLAG_CD|FLAG_CC)) != 0 || chan_status[chan] != 0)
|
||
return 2;
|
||
|
||
sim_debug(DEBUG_CMD, &cpu_dev, "SIO %x %x %x %x\n", addr, chan,
|
||
ccw_cmd[chan], ccw_flags[chan]);
|
||
|
||
/* All ok, get caw address */
|
||
chan_status[chan] = 0;
|
||
dev_status[addr] = 0;
|
||
caw[chan] = M[0x48>>2];
|
||
chan_dev[chan] = addr;
|
||
|
||
/* If device has start_io function run it */
|
||
if (dibp->start_io != NULL) {
|
||
chan_status[chan] = dibp->start_io(uptr, chan) << 8;
|
||
if (chan_status[chan] != 0) {
|
||
M[0x44 >> 2] = ((uint32)chan_status[chan]<<16) | M[0x44 >> 2] & 0xffff;
|
||
sim_debug(DEBUG_EXP, &cpu_dev, "Channel store csw %02x %08x\n",
|
||
chan, M[0x44 >> 2]);
|
||
chan_status[chan] = 0;
|
||
return 1;
|
||
}
|
||
}
|
||
|
||
/* Try to load first command */
|
||
if (load_ccw(chan, 0)) {
|
||
if (chan_status[chan] &
|
||
(STATUS_ATTN|STATUS_CHECK|STATUS_PROT|STATUS_PCHK|STATUS_EXPT)) {
|
||
M[0x44 >> 2] = ((uint32)chan_status[chan]<<16) | M[0x44 >> 2] & 0xffff;
|
||
sim_debug(DEBUG_CMD, &cpu_dev, "SIO %x %x %x %x cc=2\n", addr, chan,
|
||
ccw_cmd[chan], ccw_flags[chan]);
|
||
chan_status[chan] = 0;
|
||
dev_status[addr] = 0;
|
||
ccw_cmd[chan] = 0;
|
||
return 1;
|
||
}
|
||
if (chan_status[chan] & (STATUS_PCI)) {
|
||
M[0x44 >> 2] = ((uint32)chan_status[chan]<<16) | M[0x44 >> 2] & 0xffff;
|
||
sim_debug(DEBUG_EXP, &cpu_dev, "Channel store csw %02x %08x\n",
|
||
chan, M[0x44 >> 2]);
|
||
chan_status[chan] &= ~STATUS_PCI;
|
||
dev_status[addr] = 0;
|
||
return 1;
|
||
}
|
||
}
|
||
|
||
/* If channel returned busy save CSW and return cc=1 */
|
||
if (chan_status[chan] & STATUS_BUSY) {
|
||
M[0x40 >> 2] = 0;
|
||
M[0x44 >> 2] = ((uint32)chan_status[chan]<<16);
|
||
sim_debug(DEBUG_EXP, &cpu_dev, "Channel store csw %02x %08x\n",
|
||
chan, M[0x44 >> 2]);
|
||
chan_status[chan] = 0;
|
||
dev_status[addr] = 0;
|
||
chan_dev[chan] = 0;
|
||
ccw_cmd[chan] = 0;
|
||
return 1;
|
||
}
|
||
return 0;
|
||
}
|
||
|
||
/*
|
||
* Handle TIO instruction.
|
||
*/
|
||
int testio(uint16 addr) {
|
||
int chan = find_subchan(addr);
|
||
DIB *dibp = dev_unit[addr];
|
||
UNIT *uptr;
|
||
uint16 status;
|
||
|
||
/* Find channel this device is on, if no none return cc=3 */
|
||
if (chan < 0 || dibp == 0)
|
||
return 3;
|
||
uptr = find_chan_dev(addr);
|
||
if (uptr == 0)
|
||
return 3;
|
||
|
||
/* If any error pending save csw and return cc=1 */
|
||
if (chan_status[chan] & (STATUS_PCI|STATUS_ATTN|STATUS_CHECK|\
|
||
STATUS_PROT|STATUS_PCHK|STATUS_EXPT)) {
|
||
sim_debug(DEBUG_CMD, &cpu_dev, "TIO %x %x %x %x cc=1\n", addr, chan,
|
||
ccw_cmd[chan], ccw_flags[chan]);
|
||
store_csw(chan);
|
||
return 1;
|
||
}
|
||
|
||
/* If channel active, return cc=2 */
|
||
if (ccw_cmd[chan] != 0 || (ccw_flags[chan] & (FLAG_CD|FLAG_CC)) != 0) {
|
||
sim_debug(DEBUG_CMD, &cpu_dev, "TIO %x %x %x %x cc=2\n", addr, chan,
|
||
ccw_cmd[chan], ccw_flags[chan]);
|
||
return 2;
|
||
}
|
||
|
||
/* If error pending for another device, return cc=2 */
|
||
if (chan_dev[chan] != 0 && chan_dev[chan] != addr) {
|
||
sim_debug(DEBUG_CMD, &cpu_dev, "TIO %x %x %x %x cc=2a\n", addr, chan,
|
||
ccw_cmd[chan], ccw_flags[chan]);
|
||
return 2;
|
||
}
|
||
|
||
/* Device finished and channel status pending return it and cc=1 */
|
||
if (ccw_cmd[chan] == 0 && chan_status[chan] != 0) {
|
||
sim_debug(DEBUG_CMD, &cpu_dev, "TIO %x %x %x %x cc=1a\n", addr, chan,
|
||
ccw_cmd[chan], ccw_flags[chan]);
|
||
store_csw(chan);
|
||
chan_dev[chan] = 0;
|
||
return 1;
|
||
}
|
||
|
||
/* Device has returned a status, store the csw and return cc=1 */
|
||
if (dev_status[addr] != 0) {
|
||
sim_debug(DEBUG_CMD, &cpu_dev, "TIO %x %x %x %x cc=1b\n", addr, chan,
|
||
ccw_cmd[chan], ccw_flags[chan]);
|
||
M[0x40 >> 2] = 0;
|
||
M[0x44 >> 2] = ((uint32)dev_status[addr]) << 24;
|
||
dev_status[addr] = 0;
|
||
return 1;
|
||
}
|
||
|
||
/* Nothing pending, send a 0 command to device to get status */
|
||
status = dibp->start_cmd(uptr, chan, 0) << 8;
|
||
|
||
/* If no status and unattached device return cc=3 */
|
||
if (status == 0 && (uptr->flags & UNIT_ATT) == 0) {
|
||
sim_debug(DEBUG_CMD, &cpu_dev, "TIO %x %x %x %x cc=1c\n", addr, chan,
|
||
ccw_cmd[chan], ccw_flags[chan]);
|
||
return 3;
|
||
}
|
||
|
||
/* If we get a error, save csw and return cc=1 */
|
||
if (status & (STATUS_ATTN|STATUS_CHECK|STATUS_EXPT)) {
|
||
M[0x44 >> 2] = ((uint32)status<<24) | M[0x44 >> 2] & 0xffff;
|
||
sim_debug(DEBUG_CMD, &cpu_dev, "TIO %x %x %x %x cc=1d\n", addr, chan,
|
||
ccw_cmd[chan], ccw_flags[chan]);
|
||
return 1;
|
||
}
|
||
|
||
/* Everything ok, return cc=0 */
|
||
sim_debug(DEBUG_CMD, &cpu_dev, "TIO %x %x %x %x cc=0\n", addr, chan,
|
||
ccw_cmd[chan], ccw_flags[chan]);
|
||
return 0;
|
||
}
|
||
|
||
/*
|
||
* Handle HIO instruction.
|
||
*/
|
||
int haltio(uint16 addr) {
|
||
int chan = find_subchan(addr);
|
||
DIB *dibp = dev_unit[addr];
|
||
UNIT *uptr;
|
||
uint8 status;
|
||
|
||
/* Find channel this device is on, if no none return cc=3 */
|
||
if (chan < 0 || dibp == 0)
|
||
return 3;
|
||
uptr = find_chan_dev(chan_dev[chan]);
|
||
if (uptr == 0)
|
||
return 3;
|
||
sim_debug(DEBUG_CMD, &cpu_dev, "HIO %x %x %x %x\n", addr, chan,
|
||
ccw_cmd[chan], ccw_flags[chan]);
|
||
|
||
/* If channel busy, set end of buffer, return cc=2 */
|
||
if (ccw_cmd[chan]) {
|
||
chan_byte[chan] = BUFF_CHNEND;
|
||
return 2;
|
||
}
|
||
|
||
/* Not executing a command, issue halt if available */
|
||
if (dibp->halt_io != NULL)
|
||
chan_status[chan] = dibp->halt_io(uptr) << 8;
|
||
return 0;
|
||
}
|
||
|
||
/*
|
||
* Handle TCH instruction.
|
||
*/
|
||
int testchan(uint16 channel) {
|
||
uint16 st = 0;
|
||
channel >>= 8;
|
||
if (channel == 0)
|
||
return 0;
|
||
if (channel > channels)
|
||
return 3;
|
||
st = chan_status[subchannels + channel];
|
||
if (st & STATUS_BUSY)
|
||
return 2;
|
||
if (st & (STATUS_ATTN|STATUS_PCI|STATUS_EXPT|STATUS_CHECK|
|
||
STATUS_PROT|STATUS_CDATA|STATUS_CCNTL|STATUS_INTER|
|
||
STATUS_CHAIN))
|
||
return 1;
|
||
return 0;
|
||
}
|
||
|
||
/*
|
||
* Bootstrap a device. Set command to READ IPL, length 24 bytes, suppress
|
||
* length warning, and chain command.
|
||
*/
|
||
t_stat chan_boot(uint16 addr, DEVICE *dptyr) {
|
||
int chan = find_subchan(addr);
|
||
DIB *dibp = dev_unit[addr];
|
||
UNIT *uptr;
|
||
uint8 status;
|
||
int i;
|
||
|
||
if (chan < 0 || dibp == 0)
|
||
return SCPE_IOERR;
|
||
for (i = 0; i < MAX_DEV; i++) {
|
||
dev_status[i] = 0;
|
||
}
|
||
for (i = 0; i < 256; i++) {
|
||
ccw_cmd[i] = 0;
|
||
ccw_flags[i] = 0;
|
||
}
|
||
uptr = find_chan_dev(addr);
|
||
chan_status[chan] = 0;
|
||
dev_status[addr] = 0;
|
||
caw[chan] = 0x8;
|
||
chan_dev[chan] = addr;
|
||
ccw_count[chan] = 24;
|
||
ccw_flags[chan] = FLAG_CC|FLAG_SLI;
|
||
ccw_addr[chan] = 0;
|
||
chan_byte[chan] = BUFF_EMPTY;
|
||
ccw_cmd[chan] = 0x2;
|
||
chan_status[chan] &= 0xff;
|
||
chan_status[chan] |= dibp->start_cmd(uptr, chan, ccw_cmd[chan]) << 8;
|
||
if (chan_status[chan] & (STATUS_ATTN|STATUS_CHECK|STATUS_EXPT)) {
|
||
ccw_flags[chan] = 0;
|
||
return SCPE_IOERR;
|
||
}
|
||
loading = addr;
|
||
return SCPE_OK;
|
||
}
|
||
|
||
/*
|
||
* Scan all channels and see if one is ready to start or has
|
||
* interrupt pending.
|
||
*/
|
||
uint16
|
||
scan_chan(uint16 mask) {
|
||
int i;
|
||
int ch;
|
||
int pend = 0; /* No device */
|
||
int imask = 0x8000;
|
||
|
||
/* Quick exit if no pending IRQ's */
|
||
if (irq_pend == 0)
|
||
return 0;
|
||
irq_pend = 0;
|
||
/* Start with channel 0 and work through all channels */
|
||
for (i = 0; i < subchannels + channels; i++) {
|
||
/* If onto channel 1 or above shift mask */
|
||
if (i >= subchannels)
|
||
imask = imask / 2;
|
||
|
||
/* Check if PCI pending */
|
||
if (chan_status[i] & STATUS_PCI) {
|
||
sim_debug(DEBUG_EXP, &cpu_dev, "Scan PCI(%x %x %x %x) end\n", i,
|
||
chan_status[i], imask, mask);
|
||
if ((imask & mask) != 0) {
|
||
pend = chan_dev[i];
|
||
break;
|
||
}
|
||
}
|
||
|
||
/* If channel end, check if we should continue */
|
||
if (chan_status[i] & STATUS_CEND) {
|
||
if (ccw_flags[i] & FLAG_CC) {
|
||
if (chan_status[i] & STATUS_DEND)
|
||
(void)load_ccw(i, 1);
|
||
else
|
||
irq_pend = 1;
|
||
} else {
|
||
sim_debug(DEBUG_EXP, &cpu_dev, "Scan(%x %x %x %x) end\n", i,
|
||
chan_status[i], imask, mask);
|
||
if ((imask & mask) != 0 || loading != 0) {
|
||
pend = chan_dev[i];
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
/* Only return loading unit on loading */
|
||
if (loading != 0 && loading != pend)
|
||
return 0;
|
||
/* See if we can post an IRQ */
|
||
if (pend) {
|
||
irq_pend = 1;
|
||
ch = find_subchan(pend);
|
||
if (ch < 0)
|
||
return 0;
|
||
if (loading && loading == pend) {
|
||
chan_status[ch] = 0;
|
||
return pend;
|
||
}
|
||
if (ch >= 0 && loading == 0) {
|
||
sim_debug(DEBUG_EXP, &cpu_dev, "Scan end (%x %x)\n", chan_dev[ch], pend);
|
||
store_csw(ch);
|
||
return pend;
|
||
}
|
||
} else {
|
||
for (pend = 0; pend < MAX_DEV; pend++) {
|
||
if (dev_status[pend] != 0) {
|
||
ch = find_subchan(pend);
|
||
if (ch >= 0 && ccw_cmd[ch] == 0 &&
|
||
(mask & (0x8000 >> (pend >> 8))) != 0) {
|
||
irq_pend = 1;
|
||
M[0x44 >> 2] = (((uint32)dev_status[pend]) << 24);
|
||
M[0x40>>2] = 0;
|
||
sim_debug(DEBUG_EXP, &cpu_dev,
|
||
"Set atten %03x %02x [%08x] %08x\n",
|
||
ch, dev_status[pend], M[0x40 >> 2], M[0x44 >> 2]);
|
||
dev_status[pend] = 0;
|
||
return pend;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
return 0;
|
||
}
|
||
|
||
|
||
t_stat
|
||
chan_set_devs()
|
||
{
|
||
int i, j;
|
||
|
||
for(i = 0; i < MAX_DEV; i++) {
|
||
dev_unit[i] = NULL; /* Device pointer */
|
||
}
|
||
/* Build channel array */
|
||
for (i = 0; sim_devices[i] != NULL; i++) {
|
||
DEVICE *dptr = sim_devices[i];
|
||
UNIT *uptr = dptr->units;
|
||
DIB *dibp = (DIB *) dptr->ctxt;
|
||
int addr;
|
||
int chan;
|
||
|
||
/* If no DIB, not channel device */
|
||
if (dibp == NULL)
|
||
continue;
|
||
/* Skip disabled devices */
|
||
if (dptr->flags & DEV_DIS)
|
||
continue;
|
||
/* Check if address is in unit or dev entry */
|
||
for (j = 0; j < dptr->numunits; j++) {
|
||
addr = GET_UADDR(uptr->u3);
|
||
if ((uptr->flags & UNIT_DIS) == 0)
|
||
dev_unit[addr] = dibp;
|
||
if (dibp->dev_ini != NULL)
|
||
dibp->dev_ini(uptr, 1);
|
||
uptr++;
|
||
|
||
}
|
||
}
|
||
return SCPE_OK;
|
||
}
|
||
|
||
/* Sets the device onto a given channel */
|
||
t_stat
|
||
set_dev_addr(UNIT * uptr, int32 val, CONST char *cptr, void *desc)
|
||
{
|
||
DEVICE *dptr;
|
||
DIB *dibp;
|
||
t_value newdev;
|
||
t_stat r;
|
||
int num;
|
||
int type;
|
||
int i;
|
||
int devaddr;
|
||
|
||
if (cptr == NULL)
|
||
return SCPE_ARG;
|
||
if (uptr == NULL)
|
||
return SCPE_IERR;
|
||
dptr = find_dev_from_unit(uptr);
|
||
if (dptr == NULL)
|
||
return SCPE_IERR;
|
||
|
||
dibp = (DIB *) dptr->ctxt;
|
||
if (dibp == NULL)
|
||
return SCPE_IERR;
|
||
|
||
newdev = get_uint (cptr, 16, 0xfff, &r);
|
||
|
||
if (r != SCPE_OK)
|
||
return r;
|
||
|
||
if ((newdev >> 8) > channels)
|
||
return SCPE_ARG;
|
||
|
||
if (newdev >= MAX_DEV)
|
||
return SCPE_ARG;
|
||
|
||
devaddr = GET_UADDR(uptr->u3);
|
||
|
||
/* Clear out existing entry */
|
||
if (dptr->flags & DEV_UADDR) {
|
||
dev_unit[devaddr] = NULL;
|
||
} else {
|
||
devaddr &= dibp->mask | 0x700;
|
||
for (i = 0; i < dibp->numunits; i++)
|
||
dev_unit[devaddr + i] = NULL;
|
||
}
|
||
|
||
/* Check if device already at newdev */
|
||
if (dptr->flags & DEV_UADDR) {
|
||
if (dev_unit[newdev] != NULL)
|
||
r = SCPE_ARG;
|
||
} else {
|
||
newdev &= dibp->mask | 0x700;
|
||
for (i = 0; i < dibp->numunits; i++) {
|
||
if (dev_unit[newdev + i] != NULL)
|
||
r = SCPE_ARG;
|
||
}
|
||
}
|
||
|
||
/* If not, point to new dev, else restore old */
|
||
if (r == SCPE_OK)
|
||
devaddr = newdev;
|
||
|
||
/* Update device entry */
|
||
if (dptr->flags & DEV_UADDR) {
|
||
dev_unit[devaddr] = dibp;
|
||
uptr->u3 &= ~UNIT_ADDR(0x7ff);
|
||
uptr->u3 |= UNIT_ADDR(devaddr);
|
||
fprintf(stderr, "Set dev %x\n\r", GET_UADDR(uptr->u3));
|
||
} else {
|
||
for (i = 0; i < dibp->numunits; i++) {
|
||
dev_unit[devaddr + i] = dibp;
|
||
uptr = &((dibp->units)[i]);
|
||
uptr->u3 &= ~UNIT_ADDR(0x7ff);
|
||
uptr->u3 |= UNIT_ADDR(devaddr + i);
|
||
fprintf(stderr, "Set dev %x\n\r", GET_UADDR(uptr->u3));
|
||
}
|
||
}
|
||
return r;
|
||
}
|
||
|
||
t_stat
|
||
show_dev_addr(FILE * st, UNIT * uptr, int32 v, CONST void *desc)
|
||
{
|
||
DEVICE *dptr;
|
||
DIB *dibp;
|
||
int addr;
|
||
|
||
|
||
if (uptr == NULL)
|
||
return SCPE_IERR;
|
||
dptr = find_dev_from_unit(uptr);
|
||
if (dptr == NULL)
|
||
return SCPE_IERR;
|
||
addr = GET_UADDR(uptr->u3);
|
||
fprintf(st, "%03x", addr);
|
||
return SCPE_OK;
|
||
}
|
||
|