Thanks, that's much easier to read than the godmode9 stuff, which has the cart functions scattered across at least 4 different source files.
I have tested some hardware behaviour, and found some additional details (and some questions):
Code: Select all
#define CTRCARD_PAGESIZE_16K (8u<<16)
#define CTRCARD_PAGESIZE_64K (9u<<16)
Those two are wrong, value 8 is only 8Kbytes, not 16Kbytes.
And everything in range 9..15 is only 8Kbytes, too.
Everyone says so, but I really can't reproduce that.
I think that the CRC error flag is in bit8, not bit4. And CRC error checking is enabled in bit9. And, the error flag can be cleared/acknowledged by writing 0 to bit8.
Or is there anything indicating that bit4 would be CRC related?
I don't fully understand the CRC stuff...
Some commands do always trigger the error flag in bit8 (if enabled in bit9). I guess nintendo just didn't (properly) implement CRC's for all commands.
For command 83h (with 0 byte response), the crc error flag remains zero (unless specifying a response size bigger than 0 bytes, which triggers error).
Some of the old 8-byte NTRCARD commands seem to be also appending a 32bit CRC to the response data (only if there is any response). That value doesn't seem to be regular CRC32 though... I don't know if it's supposed to be calculated on CMD+Data, or only on Data, or something... and maybe it's garbage, or it's something different than CRC32, or the checksum is encrypted, too...?
So far, I haven't spotted a case there I could reproduce the checksum. Something like, if the data is "this", then the checksum must be "that".
Going by the wiki, that bit(s) should be transfer speed. I haven't verified that yet though.
Code: Select all
ctrcardCommand(cmdBuf, 4, 1, 0x701002C, &chipIdTest);
The LSBs set to 2Ch in that command (and other commands)... what is that good for? Is that just what nintendo is doing?
Writing 00h to LSBs does work, too. Maybe there is some difference related to timings, or maybe DMA requests.
Bit5 isn't R/W, so writing 2Ch looks a bit weird. Unless bit5 is some write-only flag used to reset or apply something?
EDIT: Bit5 seems to get set on timeout, maybe writing 1 does reset the bit, and maybe some of the other bits select the timeout duration?
Code: Select all
if(chipIdTest == chipId && cardTypeTest == cardType)
{
cmdBuf[0] = 0xC5000000; // Check status cmd
ctrcardCommand(cmdBuf, 0, 1, 0x100002C, NULL);
}
Why is that called "check status cmd"? It doesn't seem to check (=test) anything. Or is it mean to check (=confirm) something?
As far as I understand, command C5h must be sent once every 10,000 data read commands... and otherwise the cart returns wrong data?
Then I would call it "reset watchdog" or "reset crypto timebomb" or so.
Executing command C5h only if chipId/cardType do match... I guess that isn't really required for normal operation (assuming that chipId/cardType should always match).
That two writes would look nicer if they were called "REG_CTRCARDCNT = CTRCARD_nRESET;"
But that bit should be already set anyways, so the writes could be removed. Unless they supposed to clear the other bits (which normally shouldn't be required either). Anyways, doing the writes shouldn't disturb either.
Code: Select all
// make zero read and 4 byte read a little special for timing optimization(and 512 too)
What is that optimization (or is it a planned optimization on the todo list)?
Code: Select all
// if read is not finished, ds will not pull ROM CS to high, we pull it high manually
Is there a situation where that could happen for NTRCARD, or CTRCARD registers?
I guess the transfer should always transfer the expected number of words, even if the cartridge is ejected mid-transfer.
Or is there some "abort upon timeout" feature, or some "abort further blocks(s) upon crc-error" feature?
Hmmm, I guess NDS couldn't even detect cart eject at all... but DSi/3DS do have a eject switch... is the code related to that switch?
Code: Select all
static u32 readCount = 0;
u32 cmdBuf[4] = {0};
if(readCount++ >= 10000)
{
cmdBuf[0] = 0xC5000000; // Check status cmd
When resetting/ejecting/inserting cartridges, readCount should be probably reset to zero, too.
Or well, if it is bigger than zero, then command C5h would be thrown a be earlier, which might be no problem at all(?)
Code: Select all
if(flag) (*(vu32*)0x1000400C) = 0x00000001; // Enable cart command encryption?
No, that's something else: Writing 1 does disable writes to CTRCARD_CNT.bit28 (reset), and CTRCARD_SECCNT.bit0,1,2 (some of the encryption bits).
For now, I've called 1000400C register CTRCARD_LOCK. Once when set, the bit cannot be changed back from 1 to 0.
The main purpose seems to be to
prevent disabling the encryption (assuming that encryption was enabled in SECCNT.bit2).
Code: Select all
// TODO: Do we need cmdRand* here?
const u32 uniqueIdCmd[4] = {0xC6000000, 0x00000000, 0x00000000, 0x00000000};
ctrcardCommand(uniqueIdCmd, 0x40, 1, 0x701002C, buf);
What is that unique ID used for?
And how unique is it? Different for each game? Or even different for each cartridge, ie. containing some PROM with serial number?
PS.
The wiki says that there is also an 8-byte command with value 71C93FE9BB0A3B18 being sent to 3DS carts? Is that really done?
It seems to have no function, and seems to work without sending that command.
PPS.
And there are those CTRCARD1 registers at 10005000h. My current theory would be that they might be related to "NAND" carts with "CARD2" entry in the NCSD header? Could that be right?
The CTRCARD_SECCNT registers are different for CTRCARD0 and CTRCARD1, so the latter might be encrypted differently (or maybe completely unencrypted) (though I wouldn't know how the cartridge could distinguish between differently encrypted commands).
PPPS.
I think I have solved the meaning of the CGC and CGC_DET interrupt bits in REG_IE/IF registers.
CGC seems to be short for change-gamecart, CGC=Eject, and CGC_DET=Insert(ed).