------------------------- Multiple Device Instances ------------------------- Computer systems often employ multiple interface cards of a particular type. Common examples are terminal interfaces and multiplexers, line printer interfaces, and disc and tape drive interfaces. In some extreme cases, e.g., the classic HP 3000 machines, there are only two interfaces: one for terminals and one for HP-IB parallel devices. Simulators for these machines must, of necessity, provide multiple instances of the devices. Currently, these must be implemented at the VM level, as SCP provides no facilities to duplicate devices. This is a proposal to add SCP assistance to allow a VM to designate "clonable" devices and to allow the user to specify the number of instances of each of those devices. That is, if a given device permits it, the user may increase or decrease the current number of instances of the device. Such devices would start out with one instance (the original device), and the user may increase (or subsequently decrease) the number of "clones" of that device to fit the system configuration requirements. The SCP alterations to support multiple device instances are constructed to require no changes to any simulator that does not support cloning. So all existing simulators will work "as-is" without changes. Overview ~~~~~~~~ The SCP additions needed to support multiple device instances are: - One new user command to change the number of device instances. - One new entry for the command in "set_dev_tab". - One new command handler function. - One new DEVICE flag to indicate that a device is a copy. - One new DEVICE structure field to point at a VM support routine. The existing "sim_devices" array is enlarged to allow for additional device instances, but the structure (i.e., a series of device pointers ending with a NULL pointer) is unchanged, so all of the SCP and VM routines that scan this array are not affected. A device simulator that supports multiple instances must be constructed in a specific way: - The global "sim_devices" array defined by the VM must be larger than the number of initialized elements. The additional NULL elements provide the space for the creation of additional device instances. The total number of dynamically created clones for all devices is limited by the number of additional array elements provided by the static "sim_devices" array. This limit should be set to reflect the expansion limitations of the simulated hardware, where the number of device select codes, channel numbers, interrupt priorities, etc. determines maximum the number of additional I/O cards that may be added to the system. Clones for a given device will be inserted into the array immediately after the original device, so a device and its clones will always be contiguous. - All state variables pertaining to a given instance must be collected in a structure that can be easily duplicated. Typically, a single structure is used with possible sub-structures, although several independent structures could be used at the expense of more complex duplication. This structure is statically allocated for the initial ("original") instance. It will be dynamically allocated by SCP for additional ("cloned") instances. - All references to the state structure must be through pointers. Each instance will have its own pointer that points to its own state structure. - The device must maintain an instance identifier that is unique for each instance. A typical instance ID is simply the copy number, with zero identifying the original instance. If the device defines a Device Information Block (DIB) as the device context, the instance ID may be stored as a field of the DIB. Another possible location might be within the user flags in the DEVICE structure. The instance ID is used by the device's various internal routines to obtain the pointer to the current instance's state structure. - The device must provide an instance adjustment function and place a pointer to the function in the new "adjust" field of the DEVICE structure. When this field is non-NULL, SCP considers the device to be clonable. The adjustment routine is responsible for adjusting the copy of the operating state created by SCP to fit the requirements of the cloned device. The responsibilities of this routine are detailed below. Implementation ~~~~~~~~~~~~~~ The required SCP changes follow. 1. The new SCP global command is: SET COUNT= It sets the total number of devices to the specified value, so if count = 3, there will two copies created plus the original device. A subsequent "SET COUNT=4" would add one more copy, while "SET COUNT=1" would remove all copies, leaving only the original device. Neither SCP nor any existing VM uses the COUNT modifier, so it should be safe to use it here. 2. The new entry in "set_dev_tab" is: { "COUNT", &set_dev_count, 0 }, This provides the linkage from the COUNT keyword to the command handler. 3. The new command handler declaration is: t_stat set_dev_count (DEVICE *dptr, UNIT *uptr, int32 flag, char *cptr); The routine is called when the user enters the new command, with "dptr" pointing at the DEVICE structure to clone, "uptr" pointing at the device's UNIT array, "flag" set to 0, and "cptr" pointing at the character string denoting the new device count. After validating that cloning of the device is permissible, the routine allocates new DEVICE structures and inserts the device pointers into the "sim_devices" array after the original device and any preexisting clones. The original DEVICE field values are copied into each new device. A new name array is allocated for each device and is initialized to a permutation of the original device name using one of two specified algorithms (the VM adjustment routine can change the names if it desires). Each device is also allocated a new UNIT array and a new REG array; these are initialized to the values in the corresponding arrays in the original device. The MTAB array is not duplicated, as local copies are not needed unless one or more of the "desc" fields point at local state variables. The VM adjustment routine handles this if needed. All devices also have their "ctxt" fields set to NULL; the VM routine is responsible for allocating new device contexts if needed. If clones are to be deleted, the VM routine is called to free any allocations it made. Then the command handler frees the device names, UNIT and REG arrays, and the DEVICE structures. It then removes the corresponding "sim_devices" entries. 4. The new DEVICE field appended to the existing structure is: uint32 (*adjust) (struct sim_device **dvptr, int32 delta); The field is initialized by the VM to point to a per-device support routine that adjusts the newly allocated DEVICE, UNIT, and REG structures, relocates any state-specific pointers in these structures, and performs whatever other work is needed to coordinate the new devices with their dedicated state variables. The field defaults to NULL for existing VMs that do not explicitly support dynamic allocation. The adjust routine is called in one of two ways. If, on entry, the "dvptr" parameter is NULL, then the routine returns the number of elements defined for the "sim_devices" array in bits 15-0, the maximum count of instances allowed for the device in bits 23-16, and an indicator of the device naming algorithm (described below) to use in bits 31-24. The COUNT command handler uses the count values to ensure that additional copies of the device do not overflow the static "sim_devices" array and do not exceed the limit of devices of the specified type. If, on entry, the "dvptr" parameter is not NULL, then it points at the first of potentially several device pointer array entries that have been inserted or will be removed. The "delta" parameter indicates the number of device pointers that have been added (if positive) or that will be deleted (if negative). The routine is responsible for adjusting the initialization of newly cloned DEVICE structures, including additional allocations for subsidiary structures as needed, and for deallocating any structures from clones to be deleted that were added during a prior call. It is called by the command handler after the device structures have been allocated or before the device structures are to be freed. The routine returns SCPE_OK if all allocations or deallocations succeed. If the routine returns an SCP error code, any newly allocated devices are freed before the error message is printed. Not all devices will be clonable, e.g., the CPU or the system clock. If the VM does not set this new DEVICE field, SCP will reject clone commands for the device with a "Command not allowed" error. As existing simulators will default this field to NULL, none of their devices will be clonable. Each clonable device will have some upper limit on the number of possible instances. For example, an I/O card that has jumpers to select a channel number from 0-15 cannot have more than 16 instances (the original plus fifteen clones). Another example is a bus controller that can accommodate no more than four hardware line printers due to bus loading. Individual instance count limits can be imposed, and the number of "extra" elements in the "sim_devices" array determined by summing the various instance counts. 5. The new device flag is: #define DEV_V_CLONE 7 #define DEV_CLONE (1 << DEV_V_CLONE) It is set by SCP on new instances of DEVICE structures. When scanning the device list to add or reduce the number of copies of a given device, SCP uses this flag to determine how many copies of the original device currently exist. Only devices with DEV_CLONE set will be freed if the number of copies is reduced. Because pointers to clones appear in the device pointer array immediately following the original device pointer, a scan forward from that point counting DEV_CLONE flags yields the number of clones without having to keep a separate count of each device's clones. The flag also prevents the user from cloning a clone. State Variables and Registers ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ The current state of a simulated device may reside in four places: in a DEVICE structure, in a device context structure, in one or more UNIT structures, and in a collection of state variables. During device cloning, the DEVICE and UNIT structures are duplicated by SCP. The device's "adjust" routine is responsible for duplicating the device context and the set of state variables. Commonly, the latter are implemented as global scalars in a device simulator, but collecting them into a single STATE structure allows for easier duplication. A related issue is the initialization of the duplicate REG arrays. Registers reference many if not all of the state variables, and these too must be duplicated and accessed per-device. The register collection typically will reference fields in more than one structure, e.g., the state held in the DEVICE, state held within device UNITs, state within the device context such as I/O address and interrupt priority number, and state within the STATE structure. When the original REG array is duplicated, the copy still refers to the field locations in the original state structure. These must be changed to refer to the same fields in the duplicate of that structure. The "loc" field of each register element is an address that is some fixed offset from the origin of the state structure itself. Subtracting the two addresses yields the offset. Adding that offset to the start of the duplicated state structure yields a reference into the same field of the duplicate. If fields from several structures are present in the REG array, then this process must be repeated for each duplicate structure. Expanded MTAB "user reserved" field values might be used to indicate the base structure used by each register to the "adjust" routine. Or the "adjust" routine could be aware directly of which register "loc" values apply to which structures. The various routines present in a device simulator must ensure that they use the per-device copies of each of these structures. The DEVICE structure contains a pointer to the UNITs, so gaining access to the former grants access to the latter. Access to the local STATE structure is also necessary, so some means of associating a DEVICE with its dynamically allocated state is required. Several mapping arrangements are possible. The simplest is to define a static array of structures, each consisting of a DEVICE pointer and a STATE pointer, and sized to hold the maximum number of instances of the specific device that is reported by the "adjust" routine. This array is indexed by an instance number from 0 to N, where 0 is the original instance. When clones are created, the "adjust" routine sets a field in each new device's context to the instance number. This number is then used to uniquely identify a DEVICE and its associated state. The first entry in the mapping array is statically initialized to point at the static DEVICE and STATE structures of the original device. Indirect Access to State Variables ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ A complication arises when a state structure must be accessed via an indirect reference, e.g., in a routine that is passed a DEVICE or UNIT pointer or a channel address, rather than the instance number. In these cases, the passed parameter must allow a lookup either of the state pointer directly, or of the DEVICE pointer that can be used to obtain the state pointer. For DEVICE pointer translation, the device context can be examined to obtain the instance number that was set by the "adjust" routine, and the instance number is used to index into the mapping array to get the state pointer. For UNIT pointer translation, a straightforward solution is to call "find_dev_from_unit" to get the DEVICE pointer, use that to get the DIB pointer, and use that to get the state pointer. However, that routine does a linear search of the "sim_devices" array each time, so it's poorly suited if the service routine is called frequently, e.g., to transfer each data byte of a fast device. A better solution is to store the instance number in the UNIT structure, e.g., in one of the user fields (e.g., "u3") or as a field in the user portion of the "flags" field. This can be done during the initial power-on reset that occurs at simulator startup or when a new clone is "adjusted." For channel address (e.g.) translation, a separate static mapping table from channel numbers to instance number can be used. The table can be populated by the same mechanism as above for setting the UNIT fields. Additional Issues ~~~~~~~~~~~~~~~~~ SAVE and RESTORE will need some sort of accommodation to handle cloned devices, as the saved pointers to dynamic allocations will not be valid upon restoration. I have not looked into this, however, as I am unfamiliar with the operation of the commands or ramifications of the changes. Summary ~~~~~~~ By providing support within SCP for device cloning, most of the work of creating, initializing, and destroying cloned devices is done on behalf of the device simulators, with relatively little left to the "adjust" routine. Adding clone support to existing devices is mostly a matter of collecting all state variables into a single structure to make copying easy and modifying internal utility routines to access those variables through a state pointer obtained from a mapping array that is indexed by the instance number. An existing simulator's static DEVICE, UNIT array, and DIB are all retained. As the "sim_devices" array retains its current structure, all routines in SCP and the various simulators that access it remain unchanged. Extensions ~~~~~~~~~~ Once dynamic DEVICE allocation is implemented, it would be relatively simple to extend this to dynamic UNIT allocation. A cloned device is allocated a UNIT array whose size is determined by the "numunits" field of the DEVICE structure. It would be a simple matter to change the unit count by dynamically allocating a larger or smaller UNIT array and adjusting "numunits" appropriately. Restrictions on the maximum number of units would be necessary, as typically a device controller supports only a limited number of attached peripherals.