Adding An I/O Device To A SIMH Virtual Machine Updated 15 -Oct-2002 for SIMH V2.10 This memo provides more detail on adding I/O device simulators to the various virtual machines supported by SIMH. 1. SCP and I/O Device Interactions 1.1 The SCP Interface The simulator control package (SCP) finds devices through the device list, DEVICE *sim_devices. This list, defined in_sys.c, must be modified to add the DEVICE data structure(s) of the new device to sim_devices: extern DEVICE new_device; : DEVICE *sim_devices[] = { &cpu_dev, : &new_device, NULL }; The device then defines data structures for UNITs, REGISTERs, and, if required, options. 1.2 I/O Interface Requirements SCP provides interfaces to attach files to, and detach them from, I/O devices, and to examine and modify the contents of attached files. SCP expects devices to store individual data words right aligned in container words. The container words should be the next largest power of 2 in width: Data word Container word 1b to 8b 8b 9b to 16b 16b 17b to 32b 32b 33b to 64b 64b (requires compile flag -DUSE_INT64) 1.3 Save/Restore Interactions The Save/Restore capability allows simulations to be stopped, saved, resumed, and repeated. For save and restore to work properly, I/O devices must save and restore all state required for operation. This includes control registers, working registers, intermediate buffers, and mode flags. Save and restore automatically handle the following state items: * Content of declared registers. * Content of memo ry-like structures. * Device user -specific flags and DEV_DIS. * Whether each unit is attached to a file and, if so, the file name. * Whether each unit is active, and, if so, the unit time out. * Unit U3 and U4 words. * Unit user -specific flags and UNIT_DIS. There are two methods for handling intermediate buffers. First, the buffer can be made accessible as unit memory. This requires buffer -specific examine and deposit routines. Alternately, the buffer can be declared as an arrayed register. 2. PDP-8 2.1 CPU and I/O De vice Structures Simulated memory is kept in array int16 M[MAXMEMSIZE]. 12b words are right justified in each array entry; the high order 4b must be zero. The interrupt structure is implemented in three parallel variables: * int32 int_req: interrupt requests. The two high order bits are the interrupt enable flag and the interrupts-not-deferred flag * int32 dev_done: device done flags * int32 int_enable: device interrupt enable flags A device without interrupt control keeps its interrupt request, which is als o the device done flag, in int_req. A device with interrupt control keeps its interrupt request in dev_done and its interrupt enable flag in int_enable. Pictorially, +----+----+...+----+----+...+----+----+----+ |ion |indf| |irq1|irq2| |irqx|irqy|irqz| irq_req +----+----+...+----+----+...+----+----+----+ +----+----+...+----+----+...+----+----+----+ | 0 | 0 | | 0 | 0 | |donx|dony|donz| dev_done +----+----+...+----+----+...+----+----+----+ +----+----+...+----+----+...+----+----+----+ | 0 | 0 | | 0 | 0 | |enbx|enby|enbz| int_enable +----+----+...+----+----+...+----+----+----+ <- fixed -> <-no enbl-> <- with enable-> Logically, the relationship is int_req = (int_req & (OVHD+NOENB)) | (dev_done & dev_enable); Macro INT_UPDATE maintains this relationship a fter a change to any of the three variables. Device enable flags are kept in dev_enb. The device enable flag, by convention, is the same bit position as device interrupt flag. I/O dispatching is done by explicit case decoding in the IOT instruction flow for CPU IOT's, and dispatch through table dev_tab[64] for devices. Each entry in dev_tab is a pointer to a device IOT processing routine. The calling sequence for the IOT routine is: new_data = iot_routine (IOT instruction, current AC); where new_data<11:0> = new contents of AC new_data = 1 if skip, 0 if not new_data<31:IOT_V_REASON> = stop code, if non-zero 2.2 DEVICE Context and Flags The DEVICE ctxt (context) field must point to the device information block (DIB), if one exists. The DEVICE flags field must specify whether the device supports the "SET ENABLED/SET DISABLED" commands (DEV_DISABLE). If a device can be disabled, the state of the device flag must be declared as a register for SAVE/RESTORE. 2.3 Adding A New I/O Devi c e 2.3.1 Defining The Device Number and Done/Interrupt Flag Module pdp8_defs.h must be modified to add the device number definitions and the device interrupt flag definitions. The device number is the lower device number that the device responds to (e.g, 060 for the RL8A): #define DEV_NEW 0nn /* not 0,010,020-027 */ If the device has a separate interrupt enable, the interrupt flag must be added above INT_V_DIRECT, and the latter increased accordingly: #define INT_V_TTI4 (INT_V_START+13) /* clock */ #define INT_V_NEW (INT_V_START+14) /* new */ #define INT_V_DIRECT (INT_V_START+15) /* direct start */ : #define INT_NEW (1 << INT_V_NEW) If the device has only an interrupt/done flag, it must be added between INT_V_DIRECT and INT_V_OVHD, and the latter increased accordingly: #define INT_V_UF (INT_V_DIRECT+8) /* user int */ #define INT_V_NEW (INT_V_DIRECT+9) /* new */ #define INT_V_OVHD (INT_V_DIRECT+10) /* overhead start */ : #define INT_NEW (1 << INT_V_NEW) 2.3.2 Adding The Device Information Block The device information block is declared in the device module, as follows: int32 iotrtn1 (int32 instruction, int32 AC); int32 iotrtn2 (int32 instruction, int32 AC); : DIB dev_dib = { DEV_NEW, num_iot_routines, { &iotrtn1, &iotrn2, ... } }; DEV_NEW is the device number, and num_iot_routines is the number of IOT dispatch routines (allocated contiguously starting at DEV_NEW). If a device number in the range defined by [DEV_NEW, DEV_NEW + num_iot_routines - 1] is not needed, the corresponding dispatch address should be NULL. 3. PDP-11, VAX, and PDP-10 3.1 CPU and I/O Device Structures For the PDP -11, simulated memory is kept in array uint16 *M, dynamically allocated. For the VAX, simulated memory is kept in array uint32 *M, dynamically allocated. For the PDP -10, simulated memory is kept in array uint64_t *M, dynamically allocated. Because the three systems use different memory widths and different I/O mapping schemes, DMA peripherals that are shared among them use interface routines to access memory. The interrupt structure is implemented by array int_req, indexed by priority level (except on the PDP-10, where all levels are kept in one word). Each device is assigned a request flag in int_req[device_IPL], according to its priority, with highest priority at the rig ht (low order bit). To facilitate access to int_req across the three systems, each device dev defines three variables: INT_V_dev - the bit number of the device's interrupt request flag INT_dev - the mask of the device's interrupt request flag IPL_dev - the index into int_req for the device's priority level (PDP -11, VAX only) Three macros allow simulated devides to access and manipulate interrupt structures independent of the underlying VM: IVCL (dev) - vector locator for DIB (IPL * 32 + bit number) IREQ (dev) - resolves to int_req[device_IPL] CLR_INT (dev) - clears the device's interrupt request flag SET_INT (dev) - sets the device's interrupt request flag I/O dispatching is done by table -driven address decoding in the I/O page read and write routin es. Interrupt handling is done by table driven processing of vector and interrupt handling tables. These tables are constructed at run time from device information blocks (DIB's). Each I/O device has a DIB with the following information: { IO page base address, IO page length, read_routine, write_routine, num_vectors, vector_locator, vector, { &iack_rtn1, &iack_rtn2, ... } } The calling sequence for an I/O read is: t_stat read_routine (int32 *data, int32 pa, int32 access) The calling sequence for an I/O write is: t_stat write_routine (int32 data, int32 pa, int32 access) For both, the access parameter can have one of the following values: READ normal read READC console read (PDP -11 only) WRITE word write WRITEC console word write (PDP-11 only) WRITEB byte write I/O read and I/O word write use word (even) addresses; the low order bit of the address should be ignored. I/O byte write uses byte addresses, and the data byte to be written is right -justified in the calling argument. If the device has vectors, the vector_locator field specifies the position of the vector in the interrupt tables, using macro IVLC (dev). If the device has static interrupt vectors, they are specified by the DIB vector field and by the DIB num_vectors fi eld. The device is assumed to have vectors at vector, ..., vector + ((num_vectors -1) * 4). If the device has dynamic interrupt acknowledge routines, they are specified by the DIB interrupt acknowledge routines. An calling sequence for an interrupt acknowledge routine is: int32 iack_rtn (void) It returns the interrupt vector for the device, or 0 if there is no interrupt (passive release). 3.2 DEVICE Context and Flags For the PDP -11, VAX, and PDP -10, the DEVICE ctxt (context) field must point to the device information block (DIB), if one exists. The DEVICE flags field must specify whether the device is a Unibus device (DEV_UBUS), a Qbus device (DEV_QBUS), both, or neither. The DEVICE flags field must also specify whether the device supports the "SET ENABL ED/SET DISABLED" commands (DEV_DISABLE). If a device can be disabled, the state of the device flag must be declared as a register for SAVE/RESTORE. Lastly, the DEVICE flags field specifies whether the device addresses and vectors are autoconfigu red (DEV_FLTA). 3.3 Memory Access Routines DMA devices access memory through four interface routines: int32 Map_ReadB (t_addr ba, int32 bc, uint8 *buf, t_bool map); int32 Map_ReadW (t_addr ba, int32 bc, uint16 *buf, t_bool map); int32 Map_WriteB (t_addr ba, int32 bc, uint8 *buf, t_bool map); int32 Map_WriteW (t_addr ba, int32 bc, uint16 *buf, t_bool map); The arguments to these routines are: ba starting memory address bc byte count *buf pointer to device buffer map PDP-11: mapped (1) or physical (0) transfer VAX: ignored PDP-10: Unibus 3 (1) or Unibus 1 (0) transfer For the PDP -11, map is 1 for devices which use the I/O map in a Unibus configuration (it is ignored for a Qbus configuration), 0 otherwise. For the VAX, map is ignored (all trans fers are mapped). For the PDP -10, map specifies whether to use Unibus 1 map (0) or Unibus 3 map (1). The value returned is the number of bytes not transferred; a return value of 0 indicates a successful transfer. Note that the PDP -10 can only share a sm all number of PDP -11 peripherals, because of its dependence on 18b transfers on the Unibus. The routines return the number of bytes not transferred: 0 indicates a successful transfer. Transfer failures can occur if the mapped address uses an invalid mapp ing register or maps to non-existent memory. 3.4 Adding A New I/O Device 3.4.1 Defining The I/O Page Region I/O page regions are defined by a base address and a byte length. The base address is defined as an offset against the I/O page base address (IOPAGEBASE). These definitions are kept in pdp11_defs.h (vaxmod_defs.h). For example, if a new IPL 4 device has I/O addresses 17777700-17777707: #define IOBA_NEWIPL4 (IOPAGEBASE + 017700) /* base addr */ #define IOLN_NEWIPL4 010 /* length = 8 bytes */ Note that the offsets are always the low order 13b of the I/O address, because the I/O page is only 8KB long. 3.4.2 Defining The Device Parameters If the device can interrupt, pdp11_defs.h (vaxmod_defs.h, pdp10_defs.h) must be modified to add the device interrupt flag(s) and priority level. The device flag(s) should be inserted using a spare bit (or bits) at the appropriate priority level. On the PDP -11, the PIRQ interrupt flags (PIR) must always be the last (lower priority) device in the level. /* IPL 4 devices */ #define INT_V_LPT 4 #define INT_V_NEW 5 /* new IPL 4 dev */ #define INT_V_PIR4 6 /* used to be 4 */ : #define INT_NEW (1u << INT_V_NEW) : #define IPL_NEW 4 The device vector(s) must also be defined: #define VEC_NEW 0360 If the device participates in autoconfiguration, its rank must be specified as well: #define RANK_DEV 17 /* rank 17 */ 3.4.3 Adding The Device Information Block The device information block is declared in the device module, as follows: t_stat new_rd (int32 *data, int32 addr, int32 access); t_stat new_wr (int32 data, int32 addr, int32 access); int32 new_iack1 (void); int32 new_iack2 (void); : DIB new_dib = { IOBA_NEW, IOLN_NEW, &new_rd, &new_wr, num_vectors, IVLC (NEW), VEC_NEW, { &new_iack1, &new_iack2, ... }; 3.4.4 Adding The Device To Autoconfiguration (PDP -11, VAX only) If the device needs to be autoconfigured, and it is not presently included in the autoconfiguration table, it must be added to table auto_tab in pdp11_io.c (vax_io.c). Entry `n' in auto_tab corresponds to autoconfiguration rank n + 1; the first two fields of the entry are filled in. The fields are: uint32 amod address modulus uint32 vmod vector modulus uint32 flags flags uint32 num number of controllers if determined statically uint32 fix CSR address if first controller has fixed address char *dnam[4] list of controller names in this rank, maximum 4 Currently defined flags are AUTO_DYN (number of controllers is determined dynamically) and AUTO_VEC (autoconfiguration determines the device vectors as well as the device addresses). 4. Nova 4.1 CPU and I/O Device Structures Simulated memory is kept in array uint16 M[MAXMEMSIZE]. The interrupt structure is implemented in three parallel variables: * int32 int_req: interrupt requests. The two high order bi ts are the interrupt enable flag and the interrupts-not-deferred flag * int32 dev_done: device done flags * int32 dev_disable: device interrupt disable flags Pictorially, +----+----+...+----+----+...+----+----+----+ |ion |indf| |irqa|irqb| |irqx|irqy|irqz| irq_req +----+----+...+----+----+...+----+----+----+ +----+----+...+----+----+...+----+----+----+ | 0 | 0 | |dona|donb| |donx|dony|donz| dev_done +----+----+...+----+----+...+----+----+----+ +----+----+...+----+----+...+----+----+----+ | 0 | 0 | |disa|disb| |disx|disy|disz| dev_disable +----+----+...+----+----+...+----+----+----+ <- fixed -> <------- I/O devices ------> Logically, the relationship is int_req = (int_req & ~INT_DEV) | (dev_done & ~dev_disable); Device enable flags are kept in iot_enb. The d evice enable flag, by convention, is the same bit position as device interrupt flag. I/O dispatching is indirectly through dispatch table dev_table, which has one entry for each possible I/O device. Each entry is a structure of the form: int32 mask; /* interrupt/done mask bit */ int32 pi; /* PI out mask bit */ t_stat (*iot_routine)(); /* addr of I/O routine */ The I/O routine is called by new_data = iot_routine (IOT pulse, IOT subopcode, AC value); where new_data<15:0> = new contents of AC, if DIA/DIB/DIC new_data = 1 if skip, 0 if not new_data<31:IOT_V_REASON> = stop code, if non-zero 4.2 DEVICE Context and Flags The DEVICE ctxt (context) field must point to the device information block (DIB), if one exists. The DEVICE flags field must specify whether the device supports the "SET ENABLED/SET DISABLED" commands (DEV_DISABLE). If a device can be disabled, the state of the device flag must be declared as a register for SAVE/RESTORE. 4.3 Memory Mapping On mapped Nova's and on Eclipse's, DMA transfers use a memory map to translate 15b virtual addresses to physical addresses. The mapping function is called by: int32 MapAddr(int32 map, int32 addr) with the following arguments: map map number, usually 0 addr virtual address The routine returns the physical address to be used for the transfer. 4.4 Adding A New I/O Device 4.4.1 Defining The Device Number And The Done/Interrupt Flag Module nova_defs.h must be modified to add the device number definitions and the device interrupt flag definitions. #define DEV_NEW 0nn /* can't be 00, 01 */ Device flags are kept as a bit vector. If priority is unimportant, the device flag can be defined as one of the currently unused bits: #define INT_V_NEW 1 /* new */ : #define INT_NEW (1 << INT_V_NEW) If the device requires a specific priority with respect to existing devices, it must be assigned the appropriate flag bit, and the other device flag bits moved up or down. The device's PI mask bit must also be defined: #define PI_NEW 000200 4.4.2 Adding The Device Information Block The device information block is declared in the device module, as follows: int32 iot (int32 pulse, int32 code, int32 AC); : DIB new_dib = { DEV_NEW, INT_new, PI_new, &iot };