Skip to content

Pokechu22/ghidra-mn102-lang

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

43 Commits
 
 
 
 
 
 
 
 
 
 

Repository files navigation

A SLEIGH processor spec for Ghidra for the Matsushita (Panasonic) MN102 processor. The MN102 is used by the Nintendo GameCube and Wii for the disc drive.

Installation

This repository should be placed within the Ghidra install as the folder Ghidra/Processors/MN102, such that Ghidra/Processors/MN102/data is the path to the data folder. Ghidra should automatically add it to the available processor list on its next start, and compile the files when it is first used.

Manuals

Various processor manuals were once available on Panasonic's website. Unfortunately, they no longer are; archive.org has partial listings of hardware manuals and tool manuals, but not the manuals themselves.

The MN102L instruction manual is readily available, but it is for the L variant; per YAGCD the GameCube uses MN102H60GFA. According to some product advertising (Pub.No.A000130E, DSA0038294), the H version features a hardware multiplier; it actually has some other instructions as well. Unfortunately, I couldn't find the MN102H instruction manual, but several versions of the LSI User's Manual are available, and they include a table of instructions at the end (along with other useful hardware information). There are at least 3 relevant versions: a Japanese-language MN10200 series manual (Pub.No.22399-031, DSA00163540), the English 1st edition, 1st printing MN102H60G/60K/F60G/F60K LSI User's Manual (Pub.No.22360-011E, DSA00163538), and the English 1st edition, 3rd printing MN102H60G/60K/F60G/F60K LSI User's Manual (Pub.No.22360-013E, DSAH00424716).

Notes

MOVB Dm,(An)

The MOVB Dm,(An) instruction has incorrect information in the manual. It claims that it's encoded as 10+Dm<<2+An, which would result in 0x12 being MOVB D0,(A2). However, it should be 10+An<<2+Dm, which would result in 0x12 being MOVB D2,(A0). Most other instructions have the first register unshifted and the second shifted by 2; for instance, MOVB Dm,(d8,An) is encoded as F5 : 10+An<<2+Dm : d8 (for instance, MOVB D2,(2,A0) is F5 12 02). This is the case in all manuals I've checked.

A relevant code snippet (located at around 82F50 in all versions of the GameCube drive firmware):

; void memset(byte * dst, byte value, uint2 count)
; byte *            A0:3           dst
; byte              D0:1           value
; uint2             D1:3           count
memset:
	ADD        -0x4,SP        ; d3 fc
	; Save the value of D2 (3 bytes)
	MOVX       D2,(0x0,SP)    ; f5 5e 00
	; Move value into D2
	MOV        D0,D2          ; 82
	; Clear D0, to use as a counter
	SUB        D0,D0          ; a0
	; This serves no real purpose since MOVB is used when writing...
	; It might be caused by value being an int according to the C spec
	; (but this version of memset does not return the passed dst pointer,
	; so I'm not sure if value is also kept unnecessarily large)
	EXTXBU     D2             ; be
	BRA        .test          ; ea 05
.loop
	; Write value to dst (*dst = value)
	; The manual would have this as MOVB D0,(A2)
	; which writes the counter to a register not used anywhere else here
	MOVB       D2,(A0)        ; 12
	; Increment counter
	ADD        0x1,D0         ; d4 01
	; Increment dst
	ADD        0x1,A0         ; d0 01
.test
	; Check if the counter is at count
	CMP        D1,D0          ; f3 94
	; If D0-D1 would carry (i.e. D0-D1 < 0, or D0 < D1),
	; then jump back to the loop.  Otherwise continue.
	; Note that this checks CF, so this is a 16-bit comparison.
	BCS        .loop          ; e4 f7
	; Restore contents of D2
	MOVX       (0x0,SP),D2    ; f5 7e 00
	ADD        0x4,SP         ; d3 04
	RTS                       ; fe

This is obviously supposed to be something like this:

void memset(byte * dst, byte value, uint2 count) {
	uint2 counter = 0;
	while (counter < count) {
		*dst = value;
		count++;
		dst++;
	}
}

but would be this with the manual's MOVB D0,(A2):

void memset(byte * dst, byte value, uint2 count) {
	uint2 counter = 0;
	while (counter < count) {
		*whateverisinA2 = counter;
		count++;
		dst++;
	}
}

MOV (Di, An), Am and MOV Am, (Di, An)

These instructions aren't listed in the 3rd printing of the MN102H60GFA manual, only in the 1st printing. They have opcodes F1 00-F1 3F and F1 80-F1 BF. Similar MOV (Di, An), Dm) and MOV Dm (Di, An) instructions take up the rest of F1, and are listed in both printings. (These instructions are listed in the "Differences between 1st Edition 1st Printing and 1st Edition 3rd Printing" section, but it doesn't elaborate on why they were deleted).

The first form shows up in the GameCube drive code as F1 00 (MOV (D0, A0), A0), in code related to the state machines (and thus to multi-dimensional arrays) where it seems plausible. The second form does not show up at all. If an implementation bug or something caused it to be removed, it probably does not affect the GameCube, since the relevant function is pretty frequently hit based on my understanding.

Interestingly, these instructions are actually present in the MN102L manual (but invisible; text can be copied out of it and pasted into notepad though). They can be found on pages 35 (51 in the PDF) and 42 (58 in the PDF).

32-bit pointers

Ghidra handles 3-byte values surprisingly well. Most actual pointers are 3 bytes, but generally arrays of pointers are 4 bytes with the 4th byte set to 0 due to alignment concerns (along with it being faster to multiply by 4 by adding a variable to itself twice). The type for an array of 56 such pointers is pointer32[56] or type *32[56], and an array of 8 pointers to such arrays would have the type pointer32 *32[8] or type *32 *32[8].

Known issues

Registers show up in decompilation

For some reason, registers show up in the decompilation, instead of using local variables. For instance, the above memset example actually decompiles to this:

void memset(byte value,uint2 count,byte *dst)
{
  D0 = 0;
  while( true ) {
    if ((ushort)D1 <= (ushort)D0) break;
    *dst = value;
    D0 = D0 + 1;
    dst = dst + 1;
  }
  return;
}

Without the current separate processor status variable hackery, it instead looks like this:

void memset(byte value,uint2 count,byte *dst)
{
  ushort uVar1;
  ushort uVar2;
  ushort uVar3;
  ushort uVar4;
  ushort uVar5;

  D0 = 0;
  uVar1 = PSW & 0xff00 | 0x10;
  while( true ) {
    uVar2 = (ushort)(D0 < count) << 6;
    uVar3 = (ushort)SBORROW3(D0,count) << 7;
    uVar4 = (ushort)((ushort)D0 < (ushort)D1) << 2;
    uVar5 = (ushort)SBORROW2((ushort)D0,(ushort)D1) << 3;
    if (((byte)((uVar4 | uVar5) >> 2) & 1) != 1) break;
    *buf = value;
    D0 = D0 + 1;
    buf = buf + 1;
    uVar1 = uVar1 & 0xff00 | uVar2 | uVar3 | uVar4 | uVar5;
  }
  PSW = uVar1 & 0xff00 | uVar2 | uVar3 | uVar4 | uVar5 | (ushort)((int)register0x15 < 0) << 5 |
        (ushort)((undefined *)register0x15 == (undefined *)0x0) << 4 | (ushort)((short)SP < 0) << 1
        | (ushort)((short)SP == 0);
  return;
}

This doesn't make sense to me, since other processors don't have this problem (though, I think the 8051 one does, and I based mine off of it).

Parameter ordering

The MN102 has both data registers (D0, D1, D2, D3) and address registers (A0, A1, A2, SP). Parameters can be in either of them, with pointers usually going into address registers. Thus, the original order of the parameters can't be determined, and things will often be a bit weird (I manually adjusted them in the original memset example).

I'm also not 100% sure what the actual calling convention is and whether D0 is always the return register, or A0 sometimes is. The current specification is in MN102.cspec.

About

A SLEIGH processor spec for Ghidra for the Matsushita (Panasonic) MN102 processor

Topics

Resources

License

Stars

Watchers

Forks

Releases

No releases published