mfld-fr / emu86

Intel IA16 emulator for embedded development
35 stars 6 forks source link

Add keyboard queue to SDL console #46

Closed mfld-fr closed 3 years ago

mfld-fr commented 3 years ago

Insert a keyboard queue between the SDL event queue and the generic console, in order to simplify the SDL event processing and to keep the SDL queue empty after processing. Also reorder the functions to avoid forward declarations for statics, and drop the commented mouse related code.

ghaerr commented 3 years ago

Nice simplification, no more peeping and pumping, which were constraints required in the MIcrowindows system where I originally wrote this.

However, lots of changes, I presume you've tested it fully? I haven't read the SDL docs lately but I assume SDL_PollEvent pumps its own event queue.

Now that you're fully integrating the SDL backend, a problem area in INT 16h needs to be looked at in con_get_key: INT 16h function 1 is poll char, while function 0 is get (and wait for) char. ELKS will never call the get function without having previously called poll char, so it works. However, MON86 may call get char first, which needs to block until a character is ready. Stdio backend handles this (although not in a particularly great way) but SDL doesn't. This problem may require more reorganization when/if support for reading from stdio (for commands) while waiting on SDL con_get_key is occurring.

mfld-fr commented 3 years ago

You are right, con_get_char() is not blocking with SDL console. That is not compliant with the BIOS specification with a blocking call for INT 16H AH=0. Fortunately, MON86 is targetting Advantech SBC, with no console at all. I will put a big WARNING in the code to remind that non-compliance.

ghaerr commented 3 years ago

My point is that, there are many programs that likely could call only INT 16H AH=0. Rather than a warning, the time is probably now that we should create a design that will properly handle this. Perhaps your upcoming generic console will?

The code handling this in stdio is a system-blocking kluge, and we still have the problem of simultaneous multiple input devices to consider, if we want the now-possible utility of EMU86 controlling the emulation using stdio and emulated input/output using SDL.

ghaerr commented 3 years ago

That is not compliant with the BIOS specification with a blocking call for INT 16H AH=0.

The real problem is not SDL or the BIOS specification, its the API specification for con_get_key : does it wait, or not?

IMO, the low-level backends should not be in the business of blocking, ever. We already have con_poll_key. Thus, the callers of these functions, i.e. rom-elks.c should deal with possibly not having a character ready from the underlying subsystem.

[EDIT: I probably jumped to a conclusion on the above statement about non-blocking. I'm aware that the current stdio implementation has double-duty, blocking and non-blocking with cooked and raw mode, as well as the con_get_key hack. So, the design isn't quite as simple as everything non-blocking. A grander design encompassing future requirements may be needed, depending on the degree to which enhancements to EMU86 are taken.]

mfld-fr commented 3 years ago

The INT16H AH=0, if emulated with a BIOS stub as for ELKS target, cannot really block within EMU86, as it would also block the main loop.

For more advanced emulation like for the Advantech SBC (so for MON86 and SYS86), there is no emulated INT 16H, but the real BIOS image from the original EEPROM (ADVTECH.BIN). That way, even if that real BIOS blocks within a short loop, the EMU86 main loop does not, as it still executes the instructions in that short loop.

The correct design for ELKS target would be the same : run a real BIOS on emulated devices.

ghaerr commented 3 years ago

The correct design for ELKS target would be the same : run a real BIOS on emulated devices.

I see what you mean. Let me think about this some more.

ghaerr commented 3 years ago

@mfld-fr : Looks good as far as the scope of this commit is concerned.

The INT16H AH=0, if emulated with a BIOS stub as for ELKS target, cannot really block within EMU86, as it would also block the main loop.

As you can see from your last cleanup commit, both stdio and pty backends block the main loop when con_get_key is called. So our design/implementation problem isn't really limited to the SDL implementation; instead, perhaps SDL should call SDL_GetEvent in con_get_key and block just like the other backends do if no character available ? This would at least provide compatibility in emulation regardless of backend selected.

mfld-fr commented 3 years ago

As you can see from your last cleanup commit, both stdio and pty backends block the main loop when con_get_key is called.

Agree, still some work to remove any blocking in the character console. I would like, after maturing the current step about SDL, to try to insert the keyboard queue also into that console, and feed it from con_proc() as for SDL.

That way, any user expecting INT 16H AH=0 to block on an empty queue will have an error in the current fake BIOS, alerting him that there is something wrong, because con_get_key() will return non-zero if the queue is empty.

perhaps SDL should call SDL_GetEvent in con_get_key and block just like the other backends do if no character available ?

We cannot block at all in con_get_key(), otherwise any device emulation other than the keyboard would be hanged. For example, even if the real code is blocked in a BIOS call, an interruption can occurs from the timer.

ghaerr commented 3 years ago

I would like, after maturing the current step about SDL, to try to insert the keyboard queue also into that console, and feed it from con_proc() as for SDL.

If the main loop were changed such that the stdio backend was polled for input just like SDL is, and characters added to an input queue, that could solve the multiple-input problem discussed earlier, and simplify things. The stdio backend would always be in raw mode (except for ISIG/^C) and then the debug console could be rewritten to run based on input from non-blocking stdio. Then, it would be possible to run SDL emulation input/output at the same time as debug console input/output, all automatically handled from the main loop!

any user expecting INT 16H AH=0 to block on an empty queue will have an error in the current fake BIOS, alerting him that there is something wrong, because con_get_key() will return non-zero if the queue is empty.

I don't really like this solution, as that the BIOS keyboard wait function is likely frequently called by simple programs. Our design shouldn't necessarily be fixed upon only known Advantech and ELKS cases. But agreed we don't have a better answer yet.

We cannot block at all in con_get_key(), otherwise any device emulation other than the keyboard would be hanged. For example, even if the real code is blocked in a BIOS call, an interruption can occurs from the timer.

Very good point. Perhaps the INT 16H AH=0 case should be partially coded in EMU86 "ROM" so that an 8086 piece of code executes calling INT 16H AH=1 "poll" while waiting for a character. These instructions would be emulated by EMU86 while waiting for a character. That would work nicely!

mfld-fr commented 3 years ago

Very good point. Perhaps the INT 16H AH=0 case should be partially coded in EMU86 "ROM" so that an 8086 piece of code executes calling INT 16H AH=1 "poll" while waiting for a character. These instructions would be emulated by EMU86 while waiting for a character. That would work nicely!

Yes, one could try to code a minimal piece of real BIOS to block in the case the queue is empty, for example using a fake I/O port that gives the status of the queue and allows to dequeue. Hey, wait a minute, isn't that the keyboard port :smile: ?

ghaerr commented 3 years ago

for example using a fake I/O port that gives the status of the queue and allows to dequeue.

Or just call the C based INT 16h AH=1 poll code using a reserved INT. Emulating the keyboard port has it's own set of problems, but I do see your point! :)

ghaerr commented 3 years ago

The stdio backend would always be in raw mode (except for ISIG/^C) and then the debug console could be rewritten to run based on input from non-blocking stdio. Then, it would be possible to run SDL emulation input/output at the same time as debug console input/output, all automatically handled from the main loop!

Future thought: In order for the above to work, instead of using compile-time bound functions of a single console to a single backend, C++/vtable-style pointers to backend object(s) would be opened at startup. Then, the main loop (for the emulated console as well as debug console) would use a passed object pointer, e.g. console->con_proc() and debug->con_proc() etc. This would eliminate the need for mappings like con-stdio.c, and use dynamic function pointers instead.

mfld-fr commented 3 years ago

Yes, switching to C++ would help when dealing with multiple instances of backends. But that's beyond the scope of this PR. Are you OK to merge this one ?

ghaerr commented 3 years ago

Ok with merge, thanks! Don't switch to C++!