blocksds / sdk

Main BlocksDS SDK repository
https://blocksds.github.io/docs/
164 stars 10 forks source link

Decide how to detect DSi mode #104

Closed AntonioND closed 11 months ago

AntonioND commented 11 months ago

At the moment, the _dsi_mode variable is set during in the crt0.s during the startup of the program. However, this is ignored by the MPU setup code, which reads SCFG_EXT9 instead. Most likely, the MPU code needs to migrate to using the _dsi_mode variable.

  1. The _dsi_mode variable is set based on reading SCFG_A9ROM. The crt0 of the ARM7 sends this value to the ARM9: https://github.com/blocksds/sdk/blob/e0e13e376323cd285e593384cf54df41fd517e44/sys/crts/ds_arm7_crt0.s#L140-L141

  2. The MPU setup code reads register SCFG_EXT9 and checks what's the total available RAM: https://github.com/blocksds/libnds/blob/83436e70c0934debb1237198e934e64de405e267/source/arm9/system/mpu_setup.s#L138-L140

GBATEK: https://problemkaputt.de/gbatek.htm#dsicontrolregistersscfg

It looks to me like option 1 may be more robust.

AntonioND commented 11 months ago

Actually, after some discussion in the gbadev discord server, what I understand is:

DSi mode doesn't exist as such. You simply run on a DSi, and you have access to features depending on the value of the SCFG registers. For example, some features can be used from loaders that setup an "incorrect" "DS mode" because the features simply aren't locked down: https://github.com/devkitPro/libnds/pull/32 https://github.com/devkitPro/libnds/issues/23

Even if "DSi mode" doesn't exist, I still believe it is a useful abstraction. Essentially, as a homebrew developer, your ROM will normally get loaded in either a DS or a DSi. If it gets loaded in a DSi, it will either get loaded in a mode that only allows DS access (like from a slot 1 flashcard) or a mode that allows full DSi access (like from unlaunch).

Do we want to support modes in between? If we do, we need to start adding checks for specific features in a few places (not that many, to be honest). The main issue, however, is that all DSi features are hidden in twl code/data RAM sections. So, theoretically, could you run in a DSi where you have access to everything except to the extended RAM? It would make no sense. Or we could load all the code to regular non-twl sections, and force any regular DS to load code for DSi features that it can't use.

I think that assuming that "full DS" and "full DSi" modes exist makes sense.

I could still be convinced that this is a bad idea.

NightScript370 commented 11 months ago

I hope it's fine if I throw my two cents into the ring; I suggested to @RocketRobz regarding this matter for TWiLight in particular.

Our apps have a feature where loading our app from the flashcart could still access the DSi battery, DSi SD card amongst many others. The use cases for flexibility exists, but it is NOT beneficial for most homebrew. One thing seriously hurting us, however, is the duplicated code that we have to maintain because we add those checks and avoid loading it to the twl code/data RAM sections.

In my opinion, this level of flexibility should be behind a flag. Simply put, without this flag will load the extended functions into twl ram and there would be no need for checks (because the code would otherwise not get loaded). However, with the flag, it would then start implementing checks but now developers could use it for our weird apps that would use it.

You might want to also take a look at https://github.com/DS-Homebrew/nds-bootstrap/issues/34, where @ahezard first argued for it.

AntonioND commented 11 months ago

Our apps have a feature where loading our app from the flashcart could still access the DSi battery, DSi SD card amongst many others. The use cases for flexibility exists, but it is NOT beneficial for most homebrew. One thing seriously hurting us, however, is the duplicated code that we have to maintain because we add those checks and avoid loading it to the twl code/data RAM sections.

Couldn't that be fixed by having a different linker script where you remove all the contents of the twl sections? Unfortunately, a build flag for this isn't easy because the way to place things into twl sections is to name the files twl.c.

You could remove the EXCLUDE_FILE statements from here:

https://github.com/blocksds/sdk/blob/c46b22ea780bdfdddfaf3ca1a70d53f0c647f340/sys/crts/ds_arm9.ld#L171-L179

And empty the sections here (don't remove them or the crt0 may crash?):

https://github.com/blocksds/sdk/blob/c46b22ea780bdfdddfaf3ca1a70d53f0c647f340/sys/crts/ds_arm9.ld#L267-L280

If this is a real issue, another option would be for BlocksDS to offer the modified linkerscripts alongside the normal ones, I guess.

RocketRobz commented 11 months ago

Option 1 sounds like the best one to me. I'd do option 2 as fallback. Option 2 would be used for an almost-DSi-mode, where the DSi hardware is available, but the BIOS functions are still the DS-mode ones. I'd also have the TWL binaries loaded in that mode. TWiLight Menu++ currently doesn't load the TWL binaries in it's almost-DSi-mode, but nds-bootstrap loads them (along with patching the DSi mode check to return true) when it attempts to boot a game in DSi-mode with the DS-mode BIOS set in SCFG.

Speaking of BIOS, since the console cannot switch back to the DSi BIOS once the DS one is set, a nice SDK addition would be to load a dumped DSi BIOS file, and switch to it via low vectors (you'd need to find a way for arm7 to use it though). nds-bootstrap does so, in order for DSi mode games to work properly. You'd need to apply relocation patches from there though: https://github.com/DS-Homebrew/nds-bootstrap/blob/master/retail/arm9/source/conf_sd.cpp#L1484

asiekierka commented 11 months ago

Okay. Let's summarize.

First, some assumptions:

What paths do the execution vectors provide?

First of all, SCFG is accessible on ARM9 in all execution modes. As such, using SCFG_A9ROM to detect the fundamental of "NDS/DSi mode" is appropriate - any mode which makes SCFG_A9ROM inaccessible falls under the second assumption (unreasonably high cost). In addition, using SCFG_EXT9 to detect and control the state of ARM9-side functionality, or shared functionality, is appropriate. This primarily means NDMA, camera and DSP access, as well as the main memory limit.

SCFG is not accessible on ARM7 in all user-level execution modes (that is, cartridge and DSiWare), with the exception of bit 25 (access to NWRAM) which can be queried via ARM9. However, we can make assumptions based on all possible execution code paths:

SCFG_EXT7 bit Description Value
0 Revised ARM7 DMA game-configurable, typically 0
1 Revised Sound DMA game-configurable, typically 1
2 Revised Sound game-configurable, typically 1
8 Extended ARM7 Interrupts 1
9 Extended SPI Clock (8MHz) 1
10 ?? game-configurable, typically 0
11 ?? 1
16 NDMA 1
17 AES 1
18 SD/MMC game-configurable, varies
19 SDIO WiFi 1
20 Microphone 1
21 SNDEXCNT 1
22 I2C 1
23 GPIO 1
25 NWRAM 1
31 SCFG/MBK registers game-configurable, typically 0

Out of these, the only varying bits that matter to homebrew are:

In total, I would leave the SCFG_A9ROM general and SCFG_EXT9 memory checks alone. They are reasonable, and should work under all known cases.

Reading the nds-bootstrap issue was a major exercise in frustration as it didn't answer the fundamental question of "what, exactly, is DSi header location 0x1BC". It just felt like "oh, it works if I do this" without any show of curiosity as to why that is and what other effects could it have or not have. This may be somewhat understandable given the 2017-era understanding of the device, but it's 2023, and we have enough people researching the remaining edge cases of the platform (including two emulators with at least partial DSi mode support, one of them open source) to get a proper answer.

Is there anything else we want to resolve?

asiekierka commented 11 months ago

Our apps have a feature where loading our app from the flashcart could still access the DSi battery, DSi SD card amongst many others. The use cases for flexibility exists, but it is NOT beneficial for most homebrew.

BlocksDS is a homebrew SDK and I don't think it should be interested in use-cases that do not have a benefit for homebrew projects. Tools for playing backups or modding games should maintain their own codepaths, which can then be further optimized for their particular scenarios. (For example, minimizing the size of the SD/MMC driver may be valuable if what is being done is a patch on top of an existing program, whereas in our case we may be willing to add some additional code to support edge cases or improve performance.)

The feature of placing DSi-mode code in ARM9i/ARM7i sections separately is important, as it allows saving valuable RAM space in DS-mode. Modifying the link script should be sufficient to make them nonetheless available in ARM9/ARM7, though, and should not be a big burden to maintain on the interested project's side.

AntonioND commented 11 months ago

I guess that then what we have to do is to modify the code that the crt0 uses to detect DSi mode (right now the detection is done by the ARM7, not the ARM9), and add a few helpers to detect the individual features that can be blocked potentially. We already have some checks in the camera functions, for example, so it wouldn't be unreasonable to add more checks to all DSi features that may require them.

EDIT: This assumes that there won't be any DSi mode in which we don't have the extended main RAM, though, but that's reasonable.

asiekierka commented 11 months ago

It's being done by the ARM9 in the current crt0s, though. It is correct.

It may be a good idea to add checks for DSP/NWRAM in particular.

AntonioND commented 11 months ago

Ah, you're right, I misremembered it (even though I wrote the comments that document it...).

asiekierka commented 11 months ago

I've looked again, and apparently nocash did document the 1BCh value - or rather, 1BFh, as a flag byte.

asiekierka commented 11 months ago

... which was upstreamed regardless, so we're good: https://github.com/devkitPro/libnds/blob/master/include/nds/arm7/codec.h#L44-L46

NightScript370 commented 11 months ago

BlocksDS is a homebrew SDK and I don't think it should be interested in use-cases that do not have a benefit for homebrew projects.

TWiLight isn't a game player. It's an independent file launcher.

asiekierka commented 11 months ago

TWiLight isn't a game player. It's an independent file launcher.

TWiLight's main advertised feature, and based on all I've seen its feature of primary interest in production, is being able to run "DS, DSi and DSiWare ROMs", so I believe it is not entirely unfair to call it a "game player". We've literally had a court debate such arguments in the EU, in the context of flashcarts which similarly advertised themselves as "development devices" - see paragraph 36 in particular.

Either way, that's not even what I did - I quoted Robz himself as saying that the use case is "not beneficial to most homebrew". Homebrew which wants to make use of DSi hardware has to be recompiled anyway, generally speaking - which means it can support a fully native DSi mode. As such, it is reasonable for me to conclude that the use-cases which could benefit from a "hybrid DS/DSi mode" are ones in which the executable's code is not in full control of the software developer - so modding games, playing "game backups", running legacy flashcart kernels (which is something lifehackerhansol's projects are taking care of for even the most obscure devices, so it's likewise not a big deal IMO), et cetera. If there's a different scenario, I'd love to hear more about it.

AntonioND commented 11 months ago

Well, so then the idea is to leave the isDSiMode() function alone, as that one uses SCFG_A9ROM, which is what commercial games use to detect DSi, and it seems sensible overall.

Then, specific parts of code that rely on DSi features will need to check if they are actually available in the SCFG_EXT register (only if there is the possibility of the feature being disabled, like NWRAM, or if there are multiple settings, like the size of the available main RAM). For example, I've added this check to the DSP execute functions: https://github.com/blocksds/libnds/commit/91b739e80251994e154a7b3d218cdd5fb26ecb96

Regarding hybrid modes like DS mode with some DSi features... I think that's too hacky to support. It's possible to provide your own linker scripts that remove all twl sections (and then all the twl code will be loaded even in DS mode) so there are workarounds that the user can do without adding build flags to libnds. Because, honestly, providing multiple versions of the same library isn't user-friendly.

AntonioND commented 9 months ago

Related: https://github.com/blocksds/libnds/pull/85