Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Error Handler: Mega CD Mode 1 (and possibly Mode 2) support #19

Open
OrionNavattan opened this issue Apr 27, 2023 · 6 comments
Open

Error Handler: Mega CD Mode 1 (and possibly Mode 2) support #19

OrionNavattan opened this issue Apr 27, 2023 · 6 comments
Labels
enhancement New feature or request

Comments

@OrionNavattan
Copy link
Contributor

Out of curiosity, have you examined the possibility of a branch with Mega CD support? I've been studying the code of the handler and have a few good ideas as to how this could be implemented in Mode 1, and possibly even Mode 2, albeit without symbol table support (which would not be practical at all in Mode 2, and of limited usefulness in Mode 1 given ROM and PRGRAM sizes).

@vladikcomper
Copy link
Owner

vladikcomper commented Apr 27, 2023

Good suggestion.

I'm not sure about Mode 2 (don't have any experience, but you're right about the symbol table), but Mode 1 shouldn't be any different from an ordinary ROM and I had no issues with using old generation of my debugger in Sonic Winter Adventures, which had Mode 1 support.

I'll see what can be done about Mode 2, although it's currently not the priority and it'll probably take a long while. After all, I'm still trying to finish a proper 32X support first :)

@vladikcomper vladikcomper added the enhancement New feature or request label Apr 27, 2023
@OrionNavattan
Copy link
Contributor Author

I guess I should have clarified that I meant adding the ability to handle sub CPU exceptions in addition to main CPU ones. Not exactly trivial, but not impossible; I do have a good idea as to how this can be implemented.

Assuming Mode 1, the handling of main CPU exceptions would not change much (aside from modify the message to clarify that the error occurred on the main CPU, and perhaps stopping the sub CPU to prevent any issues). To handle sub CPU exceptions in either mode is another matter entirely.

The biggest problem I see is how to let the main CPU know that the sub CPU has crashed. Since there is no way for the sub CPU to interrupt the main CPU, the only real option is for the communication protocol to have some means to signal the sub CPU has crashed, probably by having the sub CPU write a specific value to one of the communication command registers. The main CPU would need to check for this value at VBlank, before sending any commands to the sub CPU, and as part of any wait loops when waiting on a reply from the sub CPU or waiting for word RAM access. It's ugly, inefficient, and fraught with race conditions, but there's simply no other way to do it.

As for how to handle the exceptions themselves; there would need to be a separate exception handler module in the sub CPU program, incorporated into the CD system program (i.e., the initial user program on the sub CPU, whether loaded from disc or copied from cartridge). The BIOS' jump table provides entries for all of the error exceptions and user traps starting at $5F40; these would need to be set to point to the handler's entry points during the SP's init routine (by default, they reboot the sub CPU by resetting the stack pointer and falling through to the start vector). Upon an exception, this handler would disable interrupts, signal the main CPU that it has crashed, and wait for the main CPU to notice. Once acknowledged, gather any information that the main CPU will need that can't be determined from examining the stack and exception frame (e.g., the type of exception), write all of it to the top of the stack, calculate the offset from the end of the stack to its head and write it to one of the communication registers, signal the main CPU it's done, then halt. This handler would not have any of the console features or anything; it'd only have what is needed to gather information and prepare it for the main CPU, and would behave the same in both Mode 1 and 2.

On the main CPU side in both Mode 1 and 2, the handler would, in addition to handling errors on that side, have additional code for processing sub-CPU exceptions. The main CPU program would check the sub CPU status as described above, and if the sub CPU is crashed, enter the exception handler via one of the user trap vectors. Once in, it will disable interrupts, acknowledge the sub CPU, and wait for it to process the exception. Once the sub CPU is done, the main CPU will stop and gain access to the sub CPU's bus and program RAM, find the end of the stack using the BIOS' vector table and the value passed by the sub CPU, process the exception, and display it, with the error screen clarifying that it's a sub CPU exception.

For Mode 2, the main CPU handler would need to be incorporated into the workram-resident code, either the initial program or its extension. Much like on the sub CPU, the BIOS' jumptable in workram includes entries for the error and user trap vectors starting at $FFFFFD18; these would need to be set to point to the handler's entry points by the IP during initialization.

A bit of a doozy, but I hope this helps. :>

@OrionNavattan
Copy link
Contributor Author

Actually went ahead and wrote some code for handling sub CPU exceptions following the ideas I laid out in my previous comment. Haven't attempted to assemble it, but hopefully it should illustrate how this could be implemented. https://gist.github.com/OrionNavattan/ca1c451587cb3c9f16fb8650101bb7df

@vladikcomper
Copy link
Owner

@OrionNavattan You really nailed the concept of Main and Sub CPU communication for error handling, impressive! This may work in some simpler projects when symbol tables aren't involved.

Looking at the Mega CD memory layout, I can see quite a lot of ways different software may organize memory, so it'll incredibly hard to come up with a universal solution of sorts. In your example for one, you always use bank 0 of Sub CPU's PROG-RAM, but what if someone uses a different bank or buffers happen to be in-between banks? To avoid that, we need to set limitations on how the underlying software uses memory, which makes error handler implementation quite picky~

Here's another implementation idea that just popped in my head and could make it a little more flexible/universal:

Make Sub-CPU actually execute error handler's code (have its own version), but use Main-CPU to handle rendering. Error handler's rendering is well-abstracted: it entirely relies on the Console module (modules/core/Console.asm). We can re-implement it for Sub-CPU to utilize Main-CPU to passthrough VDP registers and data.

It's not nearly as memory and space efficient as your solution, and Console re-implementation may take some work, but we'll have full access to Sub-CPU's address space, and we can natively support symbols tables. But this is likely how modern-day kernels and SDKs would implement this despite inefficiency, because it allows to leave higher-level code intact (instead of hardcoding buffers and checks).

@OrionNavattan
Copy link
Contributor Author

OrionNavattan commented May 3, 2023

Hey, glad you approve of my concept! You're right, it would work for small Mode 1 projects with only a bit of sub CPU code (e.g., code that simply manages playing CDDA music), although I actually had the near opposite in mind when writing it, namely, large Mode 1 projects where there would simply be no room in the ROM for a symbol table. I'm actually doing research for a mode 1 port of Sonic CD, and from my rough estimates, everything minus the FMVs and CDDA audio will just fit into 2.9 MB with plentiful use of compression, leaving 1.1 MB for code, so I doubt there'd be room for symbol tables for either CPU.

The code I wrote was written with my plans for Sonic CD Mode 1 in mind, and consequently only covers what may well be the simplest use case: the sub CPU program is using the BIOS, and is using the stack that was set up by the BIOS code, whose head (at least in the North American Model 2) is located at $5E80. You are right however in that a universal handler can't make any assumptions on how the sub CPU code is structured, given that the options are almost limitless. It could all be in a contiguous "ROM" immediately after the BIOS; it could be scattered across the program RAM with blocks of RAM variables in between; there could be additional code loaded from disc or cartridge, the stack could be relocated away from the default location, and there could even be code that doesn't use the BIOS at all. And that's just in Mode 1.

Regardless of all that, symbol table support for the sub CPU program in Mode 1 would nevertheless be a good feature to have in the handful of circumstances where it is feasible. I haven't looked at your console system in depth yet, but a stripped down version of it running on the sub CPU that relies on the main CPU to handle the actual rendering sounds like the way to go. Could also transfer the processed exception data via wordram to eliminate the hassles of dealing with program RAM banks.

@OrionNavattan
Copy link
Contributor Author

Well, I think I outdid myself. I put together a version of your main handler that assembles entirely from source, inserted it and my sub CPU exception code into an unfinished Mode 1 demo made by Devon, and after several hours of debugging, it works! Only thing I haven't tested is the handling of address errors, but that should be simple enough. Anyhow, the gist in my earlier comment has been updated with the working version.

Also sharing the hacked demo I used to test the handler (set up to crash the main CPU once init is finished):
https://drive.google.com/file/d/155pURaYjaLeJuTunZRste0FZ7f5bdmCK/view

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
None yet
Development

No branches or pull requests

2 participants