/* * Handle v9t9 modules. * * The module concept is used here to separate the guts of the emulator * from the particularities of the host executing environment. Without * modules, the emulator is a silent, invisible, noniteractive state machine. * Modules define the interface between the 99/4A state machine and the * display, sound, keyboard, and I/O devices of the host. * */ #include #include "v9t9_common.h" #include "command.h" #include "v9t9_module.h" /* These are lists of modules of the given type. Except (currently) for vmCPU and vmVideo, any of the types may have more than one active module at one time. For instance, there can be two separate sound devices [maybe one for MIDI output of the sound generator, and another for digitized data output]. */ vmModule *vmCPU, *vmVideo, *vmKeyboard, *vmSound, *vmDSR; /* Currently active main module */ vmModule *vmCPUMain, *vmVideoMain, *vmKeyboardMain, *vmSoundMain, *vmDSRMain; static char *vmErrors[] = { "no error", "not available", "configuration error", "module disabled", "internal error" }; static char *vmTypeNames[] = { "CPU", "video", "sound", "keyboard", "DSR" }; #define MAX_MODULES 64 static vmModule *modules[MAX_MODULES]; static int nmodules; #define _L LOG_MODULES | LOG_INFO #define _LL LOG_MODULES | LOG_ERROR static int AddModule(vmModule * module) { if (nmodules >= MAX_MODULES) { logger(_LL | 0, "AddModule: too many modules defined"); return 0; } if (module->name == NULL) { logger(_LL | 0, "AddModule: module has no name\n"); return 0; } if (module->version != VM_CURRENT_VERSION) { logger(_LL | 0, "AddModule: module '%s' has wrong module version (%d != %d)\n\n", module->name, module->version, VM_CURRENT_VERSION); return 0; } if (module->m.generic == NULL) { logger(_LL | 0, "AddModule: module '%s' has no specific module pointer\n", module->name); return 0; } switch (module->type) { case vmTypeCPU: if (module->m.cpu->version != VMCPU_CURRENT_VERSION) { logger(_LL | 0, "AddModule: CPU module '%s' has wrong version (%d)\n", module->name, module->m.cpu->version); return 0; } if (!module->m.cpu->execute || !module->m.cpu->stop) { logger(_LL | 0, "AddModule: CPU module '%s' has NULL callback(s)\n", module->name); return 0; } break; case vmTypeVideo: if (module->m.video->version != VMVIDEO_CURRENT_VERSION) { logger(_LL | 0, "AddModule: video module '%s' has wrong version (%d)\n", module->name, module->m.video->version); return 0; } if (!module->m.video->updatelist || !module->m.video->resize || !module->m.video->setfgbg || !module->m.video->setblank || !module->m.video->resetfromblank) { logger(_LL | 0, "AddModule: Video module '%s' has NULL callback(s)\n", module->name); return 0; } break; case vmTypeSound: if (module->m.sound->version != VMSOUND_CURRENT_VERSION) { logger(_LL | 0, "AddModule: sound module '%s' has wrong version (%d)\n", module->name, module->m.sound->version); return 0; } if (!(module->flags & vmFlagsExclusive) && (module->m.sound->update == NULL && module->m.sound->flush == NULL && module->m.sound->play == NULL && module->m.sound->read == NULL)) { logger(_LL | 0, "AddModule: '%s' is an invalid sound module (has no callbacks " "and is not exclusive), ignoring\n", module->name); return 0; } break; case vmTypeKeyboard: if (module->m.kbd->version != VMKEYBOARD_CURRENT_VERSION) { logger(_LL | 0, "AddModule: keyboard module '%s' has wrong version (%d)\n", module->name, module->m.kbd->version); return 0; } if (!module->m.kbd->scan || !module->m.kbd->getspecialkeys) { logger(_LL | 0, "AddModule: Keyboard module '%s' has NULL callback(s)\n", module->name); return 0; } break; case vmTypeDSR: if (module->m.dsr->version != VMDSR_CURRENT_VERSION) { logger(_LL | 0, "AddModule: DSR module '%s' has wrong version (%d)\n", module->name, module->m.dsr->version); return 0; } if (!module->m.dsr->getcrubase || !module->m.dsr->filehandler) { logger(_LL | 0, "AddModule: DSR module '%s' has NULL callback(s)\n", module->name); return 0; } break; logger(_LL | 0, "AddModule: module '%s' has unknown type (%d)\n", module->name, module->type); return 0; } if (module->detect == NULL || module->init == NULL || module->term == NULL || module->enable == NULL || module->disable == NULL || module->restart == NULL || module->restop == NULL) { logger(_LL | 0, "AddModule: callback(s) missing in '%s'\n", module->name); return 0; } modules[nmodules++] = module; return 1; } /*********************************/ int vmModulesInit(void) { nmodules = 0; return 1; } int vmModulesTerm(void) { vmTermModules(); nmodules = 0; return 1; } int vmRegisterModules(vmModule ** module) { vmModule **mptr; for (mptr = module; *mptr; mptr++) if (!AddModule(*mptr)) return 0; return 1; } /*********************************/ vmResult vmDetectModule(vmModule * module) { vmResult res; assert(module != NULL); logger(_L | L_1, "vmDetectModule: '%s'\n", module->name); res = module->detect(); if (res != vmOk) { // not _LL since a detection needn't succeed logger(_L | 0, "vmDetectModule: '%s': %s\n", module->name, vmErrors[res]); module->runtimeflags |= vmRTUnavailable; } return res; } vmResult vmInitModule(vmModule * module) { vmResult res; assert(module != NULL); if (module->runtimeflags & vmRTUnavailable) return vmOk; if (module->runtimeflags & vmRTUndoInit) return vmOk; logger(_L | L_1, "vmInitModule: '%s'\n", module->name); res = module->init(); if (res != vmOk) { logger(_LL | 0, "vmInitModule: '%s': %s\n", module->name, vmErrors[res]); module->runtimeflags |= vmRTUnavailable; } else { module->runtimeflags |= vmRTUndoInit; } return res; } vmResult vmTermModule(vmModule * module) { vmResult res; assert(module != NULL); if (!(module->flags & vmRTUndoInit)) return vmOk; module->runtimeflags &= ~vmRTUndoInit; logger(_L | L_1, "vmTermModule: '%s'\n", module->name); res = module->term(); if (res != vmOk) logger(_LL | 0, "vmTermModule: '%s': %s\n", module->name, vmErrors[res]); return res; } vmResult vmEnableModule(vmModule * module) { vmResult res; assert(module != NULL); if (module->runtimeflags & vmRTUnavailable) return vmOk; if ((module->flags & vmFlagsOneShotEnable) && (module->runtimeflags & vmRTEnabledOnce)) return vmOk; if (module->runtimeflags & vmRTUndoEnable) return vmOk; logger(_L | L_1, "vmEnableModule: '%s'\n", module->name); res = module->enable(); if (res != vmOk) logger(_LL | 0, "vmEnableModule: '%s': %s\n", module->name, vmErrors[res]); else { module->runtimeflags |= vmRTEnabledOnce; module->runtimeflags |= vmRTUndoEnable; } return res; } vmResult vmDisableModule(vmModule * module) { vmResult res; assert(module != NULL); if (!(module->runtimeflags & vmRTUndoEnable)) return vmOk; logger(_L | L_1, "vmDisableModule: '%s'\n", module->name); module->runtimeflags &= ~vmRTUndoEnable; res = module->disable(); if (res != vmOk) logger(_LL | 0, "vmDisableModule: '%s': %s\n", module->name, vmErrors[res]); return res; } vmResult vmRestartModule(vmModule * module) { vmResult res; assert(module != NULL); if (!(module->runtimeflags & vmRTInUse)) return vmOk; if (!(module->runtimeflags & vmRTEnabledOnce)) return vmOk; if (module->runtimeflags & vmRTUndoRestart) return vmOk; logger(_L | L_1, "vmRestartModule: '%s'\n", module->name); res = module->restart(); if (res != vmOk) logger(_LL | 0, "vmRestartModule: '%s': %s\n", module->name, vmErrors[res]); else module->runtimeflags |= vmRTUndoRestart; return res; } vmResult vmRestopModule(vmModule * module) { vmResult res; assert(module != NULL); if (!(module->runtimeflags & vmRTInUse)) return vmOk; if (!(module->runtimeflags & vmRTEnabledOnce)) return vmOk; if (!(module->runtimeflags & vmRTUndoRestart)) return vmOk; logger(_L | L_1, "vmRestopModule: '%s'\n", module->name); res = module->restop(); if (res != vmOk) logger(_LL | 0, "vmRestopModule: '%s': %s\n", module->name, vmErrors[res]); else module->runtimeflags &= ~vmRTUndoRestart; return res; } /********************/ static int CollectModules(vmModule ** list, vmType type, bool required) { int found = 0; int x; *list = NULL; for (x = 0; x < nmodules; x++) if (modules[x]->type == type && vmDetectModule(modules[x]) == vmOk) { *list = modules[x]; list = &(*list)->next; found++; } *list = NULL; /* terminate list */ if (!found && required) logger(_L | LOG_FATAL, "CollectModules: Could not find a %s module\n", vmTypeNames[type]); return found; } /* Detect all applicable modules */ int vmDetectModules(void) { logger(_L | L_1, "vmDetectModules"); /* CPU modules */ if (!CollectModules(&vmCPU, vmTypeCPU, true)) return 0; /* Video modules */ if (!CollectModules(&vmVideo, vmTypeVideo, true)) return 0; /* Sound modules */ if (!CollectModules(&vmSound, vmTypeSound, true)) return 0; /* Keyboard modules */ if (!CollectModules(&vmKeyboard, vmTypeKeyboard, true)) return 0; /* DSR modules */ CollectModules(&vmDSR, vmTypeDSR, false); return 1; } /* Select startup (or re-startup) modules. -- We are allowed to use more than one module of one type at once if none has the vmFlagsExclusive flag set. -- For sound modules, we also make the restriction that we only use as many modules as will satisfy both callbacks; if the first module selected defines one, that one will be ignored even if defined in a second selected one. -- For simplicity, we assume that the list of modules is ordered by priority and stop with the first workable combination. */ static void SelectModules(vmModule * list, vmModule ** main, vmType type, bool startup, bool exclusive) { vmModule *ptr, *prev; int chained = 0; /* already using a module? */ int termed = 0; /* not looking for new modules */ for (ptr = list, prev = NULL; ptr; ptr = ptr->next) { /* clear flag */ ptr->runtimeflags &= ~vmRTInUse; if (termed) { continue; } if (ptr->runtimeflags & vmRTUnavailable) { logger(_L | L_1, "SelectModules: '%s' is unavailable, skipping\n", ptr->name); continue; } if (ptr->runtimeflags & vmRTUnselected) { logger(_L | L_1, "SelectModules: '%s' is unselected, skipping\n", ptr->name); continue; } if (ptr->flags & vmFlagsExclusive) { if (chained) { /* already using non-exclusive one */ logger(_L | L_1, "SelectModules: not using '%s' since non-exclusive module already in use\n", ptr->name); continue; } /* else, select this one, and one-by-one ignore each of the rest */ } else /* sound has special semantics */ if (type == vmTypeSound && prev != NULL) { /* if we're here, then the 'prev' module defines one callback; only select a module that defines the other one */ if ((prev->m.sound->update == NULL && ptr->m.sound->update == NULL) || ((prev->m.sound->play == NULL) && (ptr->m.sound->play == NULL)) || ((prev->m.sound->read == NULL) && (ptr->m.sound->read == NULL))) { /* neither defines the missing callback */ logger(_L | L_1, "SelectModules: not using '%s' since it doesn't satisfy missing " "callbacks from module '%s'\n", ptr->name, prev->name); continue; } } /* just use it */ logger(_L|L_0, "SelectModules: using '%s'\n", ptr->name); if (!chained && main) *main = ptr; ptr->runtimeflags |= vmRTInUse; if (!startup) vmEnableModule(ptr); chained++; prev = ptr; /* did we get the only one we could use? */ if (exclusive) { logger(_L | L_1, "SelectModules: terminating search, only one %s module may be active\n", vmTypeNames[type]); termed = 1; continue; } if (type == vmTypeSound && ptr->m.sound->update != NULL && ptr->m.sound->play != NULL && ptr->m.sound->read != NULL) { logger(_L | L_1, "SelectModule: terminating search since '%s' defines " "all sound callbacks\n", ptr->name); termed = 1; continue; } } if (!chained && prev != NULL) logger(_L | LOG_FATAL, "SelectModules: did not find a single usable %s module?\n", vmTypeNames[type]); } /* The user has opted to select a custom list of modules. Validate each combination. If the combination fails, simply call SelectModules() again to return to the default state. Valid list: -- no two are vmTypeExclusive -- none has vmRTUnselected set -- for sound, no conflicting callbacks AND, an empty list is valid if "incremental" is set. */ static int ValidateModules(vmModule * list, vmType type, bool required, bool exclusive, bool incremental) { vmModule *ptr, *prev; int chained = 0; /* already using a module? */ ptr = list; prev = NULL; while (ptr) { if (!(ptr->runtimeflags & (vmRTUnselected | vmRTUnavailable))) { /* did we get the only one we could use? */ if (chained && exclusive) { logger(_LL | 0, "ValidateModules: '%s' must not be selected since " "only one %s module may be active at once\n", ptr->name, vmTypeNames[type]); return 0; } if (ptr->flags & vmFlagsExclusive) { if (chained) { /* already using non-exclusive one, fail */ logger(_LL | 0, "ValidateModules: '%s' must not be selected since " "other non-exclusive modules are already selected\n", ptr->name); return 0; } } else /* sound has special semantics */ if (type == vmTypeSound && prev != NULL) { if (((prev->m.sound->update != NULL) && (ptr->m.sound->update != NULL)) || ((prev->m.sound->play != NULL) && (ptr->m.sound->play != NULL)) || ((prev->m.sound->read != NULL) && (ptr->m.sound->read != NULL))) { /* both have the same callback defined */ logger(_LL | 0, "ValidateModules: '%s' must not be selected since module " "'%s' defines the same callbacks\n", ptr->name, prev->name); return 0; } } // success, so far // // allow selection of disabled module // ptr->runtimeflags &= ~vmRTUnselected; chained++; prev = ptr; } ptr = ptr->next; } if (!chained && !incremental && required) { logger(_LL | 0, "ValidateModules: no %s modules are selected; " "at least one is required\n", vmTypeNames[type]); return 0; } return 1; } static vmResult IterModuleList(vmModule * ptr, vmResult(*vmFunc) (vmModule *)) { vmResult res = vmOk, ret = vmOk; while (ptr) { if (!(ptr->runtimeflags & (vmRTUnavailable | vmRTUnselected))) { if ((res = vmFunc(ptr)) != vmOk) { if (res == vmNotAvailable) ptr->runtimeflags |= vmRTUnavailable; else ret = res; } } ptr = ptr->next; } return ret; } static vmResult IterModuleLists(vmModule ** list[], vmResult(*vmFunc) (vmModule *)) { vmResult res = vmOk, ret = vmOk; int step; step = 0; while (list && list[step]) { vmModule *ptr = *(list[step]); if ((res = IterModuleList(ptr, vmFunc)) != vmOk) ret = res; step++; } return ret; } int vmValidateModuleSelection(bool startup, bool incremental) { logger(_L | L_1, "vmValidateModuleSelection\n"); /* CPU modules */ SelectModules(vmCPU, &vmCPUMain, vmTypeCPU, startup, true /*exclusive*/); /* Video modules */ SelectModules(vmVideo, &vmVideoMain, vmTypeVideo, startup, false /*exclusive*/); /* Sound modules */ IterModuleList(vmSound, vmRestopModule); SelectModules(vmSound, &vmSoundMain, vmTypeSound, startup, false /*exclusive*/); IterModuleList(vmSound, vmRestartModule); /* Keyboard modules */ SelectModules(vmKeyboard, &vmKeyboardMain, vmTypeKeyboard, startup, false /*exclusive*/); /* DSR modules */ SelectModules(vmDSR, NULL, vmTypeDSR, startup, false /*exclusive*/); /* Reset our features mask. */ features &= ~(FE_VIDEO | FE_KEYBOARD | FE_SOUND | FE_SPEECH); /* These are always available. */ features |= FE_VIDEO | FE_KEYBOARD; /* These depend on the union of callbacks our sound modules define. */ if (vmSound->m.sound->update != NULL || (vmSound->next && vmSound->next->m.sound->update != NULL)) features |= FE_SOUND; if (vmSound->m.sound->play != NULL || (vmSound->next && vmSound->next->m.sound->play != NULL)) features |= FE_SPEECH | FE_CSXWRITE; if (vmSound->m.sound->read != NULL || (vmSound->next && vmSound->next->m.sound->read != NULL)) features |= FE_CSXREAD; return 1; } int vmSelectStartupModules(void) { return vmValidateModuleSelection(true, false); } int vmInitModules(void) { static vmModule **initList[] = { &vmKeyboard, &vmSound, &vmVideo, &vmCPU, &vmDSR, NULL }; logger(_L | L_1, "vmInitModules\n"); return IterModuleLists(initList, vmInitModule) == vmOk; } int vmTermModules(void) { static vmModule **termList[] = { &vmDSR, &vmVideo, &vmCPU, &vmKeyboard, &vmSound, NULL }; logger(_L | L_1, "vmTermModules\n"); return IterModuleLists(termList, vmTermModule) == vmOk; } int vmEnableModules(void) { static vmModule **enableList[] = { &vmVideo, &vmKeyboard, &vmSound, &vmDSR, &vmCPU, NULL }; logger(_L | L_1, "vmEnableModules\n"); return IterModuleLists(enableList, vmEnableModule) == vmOk; } int vmDisableModules(void) { static vmModule **disableList[] = { &vmCPU, &vmDSR, &vmSound, &vmKeyboard, &vmVideo, NULL }; logger(_L | L_1, "vmDisableModules\n"); return IterModuleLists(disableList, vmDisableModule) == vmOk; } int vmRestartModules(void) { static vmModule **restartList[] = { &vmDSR, &vmCPU, NULL }; int ret = 1; logger(_L | L_1, "vmRestartModules, features = %x\n", features); if (features & FE_VIDEO) ret &= (IterModuleList(vmVideo, vmRestartModule) == vmOk); if (features & FE_KEYBOARD) ret &= (IterModuleList(vmKeyboard, vmRestartModule) == vmOk); if (features & FE_SOUND) ret &= (IterModuleList(vmSound, vmRestartModule) == vmOk); return ret && IterModuleLists(restartList, vmRestartModule) == vmOk; } int vmRestopModules(void) { static vmModule **restopList[] = { &vmCPU, &vmDSR, NULL }; int ret = 1; logger(_L | L_1, "vmRestopModules, features = %x\n", features); if (features & FE_SOUND) ret &= (IterModuleList(vmSound, vmRestopModule) == vmOk); if (features & FE_KEYBOARD) ret &= (IterModuleList(vmKeyboard, vmRestopModule) == vmOk); if (features & FE_VIDEO) ret &= (IterModuleList(vmVideo, vmRestopModule) == vmOk); return ret && IterModuleLists(restopList, vmRestopModule) == vmOk; } int vmClearUserFlagsOnModules(void) { static vmModule **allList[] = { &vmCPU, &vmDSR, &vmSound, &vmVideo, &vmKeyboard, 0L }; int idx = 0; logger(_L | L_1, "vmClearUserFlagsOnModules\n"); while (allList[idx]) { vmModule *list = *allList[idx]; while (list) { list->runtimeflags &= ~vmRTUserModified; list = list->next; } idx++; } return 1; } /******************************/ vmModule * vmLookupModule(char *tag) { int x; for (x = 0; modules[x] && x < MAX_MODULES; x++) { if (strcasecmp(modules[x]->tag, tag) == 0) return modules[x]; } return NULL; } static DECL_SYMBOL_ACTION(vm_list_v9t9_modules) { int x; for (x = 0; modules[x] && x < MAX_MODULES; x++) { logger(LOG_USER, "Tag: '%s', name: '%s', type: '%s'\n", modules[x]->tag, modules[x]->name, vmTypeNames[modules[x]->type]); logger(LOG_USER, "\tFlags: "); if (modules[x]->flags & vmFlagsExclusive) logger(LOG_USER, "exclusive "); if (modules[x]->flags & vmFlagsOneShotEnable) if (modules[x]->runtimeflags & vmRTEnabledOnce) logger(LOG_USER, "[enable-once] "); else logger(LOG_USER, "enable-once "); if (modules[x]->flags & vmFlagsOneShotDisable) if (modules[x]->runtimeflags & vmRTDisabledOnce) logger(LOG_USER, "[disable-once] "); else logger(LOG_USER, "disable-once "); if (modules[x]->runtimeflags & vmRTInUse) logger( LOG_USER, "in-use "); if (modules[x]->runtimeflags & vmRTUnselected) logger( LOG_USER, "unselected "); logger(LOG_USER, "\n"); } return 1; } static DECL_SYMBOL_ACTION(vm_toggle_v9t9_module) { char *tag; int onoff; vmModule *mod; if (task == csa_READ) { static int idx; if (!iter) idx = 0; if (idx >= nmodules) return 0; while (idx < nmodules) { if (modules[idx]->runtimeflags & vmRTUserModified) { command_arg_set_string(sym->args, modules[idx]->tag); command_arg_set_num(sym->args->next, !(modules[idx]->runtimeflags & vmRTUnselected)); idx++; return 1; } else { idx++; } } return 0; } command_arg_get_string(sym->args, &tag); command_arg_get_num(sym->args->next, &onoff); mod = vmLookupModule(tag); if (mod == NULL) { logger(_LL | LOG_USER, "No such v9t9 module '%s'\n\n", tag); return 0; } if (!onoff) { vmRestopModule(mod); vmDisableModule(mod); } if (onoff) mod->runtimeflags &= ~vmRTUnselected; else mod->runtimeflags |= vmRTUnselected; mod->runtimeflags |= vmRTUserModified; if (!vmValidateModuleSelection(false, true)) return 0; if (onoff) { if (vmInitModule(mod) != vmOk) return 0; if (vmEnableModule(mod) != vmOk) return 0; if (vmRestartModule(mod) != vmOk) return 0; } return 1; } static DECL_SYMBOL_ACTION(vm_setup_v9t9_modules) { return vmValidateModuleSelection(false, false); } void vmAddModuleCommands(void) { command_symbol_table *modules = command_symbol_table_new("V9t9 Module Commands", "These options affect the modules used to emulate V9t9", command_symbol_new("ListV9t9Modules", "List available modules and current status", c_DONT_SAVE, vm_list_v9t9_modules, NULL /* ret */ , NULL /* args */ , command_symbol_new("ToggleV9t9Module", "Turn use of a module on or off", c_DYNAMIC, vm_toggle_v9t9_module, NULL /* ret */ , command_arg_new_string ("tag", "tag name for module", NULL, NEW_ARG_STR(64), command_arg_new_num ("on|off", "whether to use or not use module", NULL, NEW_ARG_NUM(bool), NULL /* next */ )) , command_symbol_new("SetupV9t9Modules", "Setup module gestalt", c_DONT_SAVE, vm_setup_v9t9_modules, NULL /* ret */ , NULL /* args */ , NULL /* next */ ))), NULL /* sub */ , NULL /* next */ ); command_symbol_table_add_subtable(universe, modules); }