Hacking the Xbox 360 DVD drive
Too lazy/busy to read through all this? I've written a summary page conaining all of the how with a lot less of the why (because I'm just that nice).
Updates:
20th February 2006: WindowsXP and native SATA tests
21st February 2006: Possible firmware code support for bidirectional tray_status
2nd March 2006: A purely software method for inducing modeB (which, among other things, gets the drive working in Windows, Linux and others)
8th March 2006: Mode Select(10)
analysis, writing to anywhere in the MN103 address space in software
and executing custom code on the drive in software
9th March 2006: Confirmation of bidirectional tray_status line. At last.
15th March 2006: Windows ports of my
tools and a less annoying memdump utility for linux and windows (it now
accepts hexadecimal input instead of decimal)
I set out to investigate the communication between the DVD drive and CPU in my xbox 360. The first step was to reverse engineer the DVD power connector pinout and build an adapter that would allow me to power the 360 DVD drive from a PC while I probed it. This should have been an easy task, but it ended up leading me on something of an odyessy. The following notes document my actions and findings to date. Forgive the roughness, I wrote these notes as I went along and haven't had time to tidy them up a great deal, although I have been through and added links and tried to give credit where it is due. As well as adding some important warnings.
WARNING: If you are going to connect your 360 and PC together in *any* way, as I have done several times in the experients below, then you *must* provide the 360 with a path to true earth ground. This is because the 360 has a floating ground and horrible things happen if all connected systems do not agree on the reference voltage. I used a couple of croc clips from the chassis of the 360 to the chassis of my PC to achieve this.
My drive is the Hitachi-LG GDR-3120L (ROM version: 0046DH).
DVD power connector Pin numberings (from motherboard). These were obtained by observing the voltages on the DVD power connector pins in a running xbox while messing with the eject button.
01
23
45
67
89
X
X = unused (+3.3V)
0 = GND
1 = +12V supply
2 = GND
3 = +12V supply
4 = GND
5 = +5V supply
6 = GND
7 = ? (perhaps a +3.3V supply or perhaps unused?)
8a = output : eject (falling edge (0V) closes, rising edge (+3.3V) opens)
8b = output : eject (normally 0V, rising edge of a +3.3V pulse opens/closes tray)
9a = input : tray_status (0V = opening/closing, +3.3V open/closed)
9b = input : tray_status (+3.3V = open, 0V = closed)
Strange behaviour: 8a and 9a were observed with the scope during normal operation of the xbox. I designed a PC power supply to 360 DVD power connector adapter. Circuit diagram below. Using this adapter I observered behaviours 8b and 9b. Why the difference?

(The transistor U2, LED D1 and resistors R1 and R2 were to allow me to observe the tray_status output from the drive)
There is more strangeness. I wrote a simple program to send a couple of common ATAPI commands (technically the first command is an ATA command not ATAPI) to the 360 drive while it was connected to my PC. Here's the code. I am connecting my 360 drive to my PC using a PATA to SATA bridge board as I'm too poor to have SATA ports on my computer.
/*
* written to test the responsiveness of the xbox360
* DVD drive to some common ATA/ATAPI commands from
* issued from a PC.
*
* author: Kevin East (SeventhSon)
* email: kev@kev.nu
* web: http://www.kev.nu/360/
* date: 10th Febuary 2006
* platform: linux
*
*/
#include <stdio.h>
#include <linux/cdrom.h>
#include <sys/ioctl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#include <linux/hdreg.h>
void dump_hex(unsigned char *data, unsigned int len, unsigned int offset)
{
unsigned int i, j, mod;
char ascii[16];
for(i = 0; i < len; i++) {
mod = i % 16;
if(!(mod)) {
if(i) {
for(j = 0; j < 16; j++) {
if(ascii[j] >= 0x20 && ascii[j] <= 0x7E)
printf("%c", ascii[j]);
else
printf(".");
}
printf("\n");
}
printf("%08X: ", offset + i);
}
else if(i && !(i % 8))
printf("- ");
ascii[mod] = data[i];
printf("%02X ", data[i]);
}
if(!len)
printf("\n");
else {
len = 16 - (len % 16);
if(len != 16) {
if(len >= 8)
printf(" ");
for(mod++, i = 0; i < len; i++) {
ascii[mod + i] = ' ';
printf(" ");
}
}
for(j = 0; j < 16; j++) {
if(ascii[j] >= 0x20 && ascii[j] <= 0x7E)
printf("%c", ascii[j]);
else
printf(".");
}
printf("\n");
}
}
int main(int argc, char *argv[])
{
int fd;
struct cdrom_generic_command cgc;
struct request_sense sense;
struct hd_drive_cmd_hdr *ch;
unsigned char data[2048];
if(argc != 2) {
printf("usage: dvdprobe device\n");
return 1;
}
if((fd = open(argv[1], O_RDONLY)) == -1) {
perror("open");
return 1;
}
printf("\nWIN_PIDENTIFY\n=============\n"); // ATA IDENTIFY PACKET DEVICE
ch = (struct hd_drive_cmd_hdr *)data;
memset(ch, 0, sizeof(*ch));
ch->command = WIN_PIDENTIFY;
ch->sector_count = 1;
if(ioctl(fd, HDIO_DRIVE_CMD, ch) == -1)
perror("ioctl");
else
dump_hex(&data[sizeof(*ch)], 512, 0);
printf("\n\nGPCMD_INQUIRY\n=============\n"); // ATAPI INQUIRY
memset(&cgc, 0, sizeof(cgc));
cgc.cmd[0] = GPCMD_INQUIRY;
cgc.cmd[4] = 36;
cgc.buffer = data;
cgc.buflen = 36;
cgc.sense = &sense;
cgc.data_direction = CGC_DATA_READ;
cgc.timeout = 15000;
if(ioctl(fd, CDROM_SEND_PACKET, &cgc) == -1) {
perror("ioctl");
printf("sense: %02X/%02X/%02X\n", sense.sense_key, sense.asc, sense.ascq);
}
else
dump_hex(data, 36, 0);
printf("\n\nGPCMD_READ_CDVD_CAPACITY\n=============\n"); // ATAPI READ CAPACITY
memset(&cgc, 0, sizeof(cgc));
cgc.cmd[0] = GPCMD_READ_CDVD_CAPACITY;
cgc.buffer = data;
cgc.buflen = 8;
cgc.sense = &sense;
cgc.data_direction = CGC_DATA_READ;
cgc.timeout = 15000;
if(ioctl(fd, CDROM_SEND_PACKET, &cgc) == -1) {
perror("ioctl");
printf("sense: %02X/%02X/%02X\n", sense.sense_key, sense.asc, sense.ascq);
}
else
dump_hex(data, 8, 0);
printf("\n\nGPCMD_READ_12\n=============\n"); // ATAPI READ(12)
memset(&cgc, 0, sizeof(cgc));
cgc.cmd[0] = GPCMD_READ_12;
cgc.cmd[9] = 1;
cgc.buffer = data;
cgc.buflen = 2048;
cgc.sense = &sense;
cgc.data_direction = CGC_DATA_READ;
cgc.timeout = 15000;
if(ioctl(fd, CDROM_SEND_PACKET, &cgc) == -1) {
perror("ioctl");
printf("sense: %02X/%02X/%02X\n", sense.sense_key, sense.asc, sense.ascq);
}
else
dump_hex(data, 2048, 0);
printf("\n");
close(fd);
return 0;
}
When the DVD is powered from xbox:
* READ(12) fails with a vendor specific CHECK CONDITION - 05/81/00
* Inquiry fails with weird CHECK CONDITION 05/55/00 - System Resource Failure.
Here's the program output (powered from xbox)
WIN_PIDENTIFY ============= 00000000: C0 85 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 ................ 00000010: 00 00 00 00 20 20 20 20 - 20 20 20 20 20 20 20 20 .... 00000020: 20 20 20 20 20 20 20 20 - 00 00 00 00 00 00 30 30 ......00 00000030: 36 34 20 20 20 20 4C 48 - 44 2D 2D 54 54 53 56 44 64 LHD--TTSVD 00000040: 2D 44 4F 52 20 4D 44 47 - 33 52 32 31 4C 30 20 20 -DOR MDG3R21L0 00000050: 20 20 20 20 20 20 20 20 - 20 20 20 20 20 20 00 00 .. 00000060: 00 00 00 0F 00 00 00 02 - 00 02 06 00 00 00 00 00 ................ 00000070: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 07 00 ................ 00000080: 03 00 78 00 78 00 78 00 - 78 00 00 00 00 00 00 00 ..x.x.x.x....... 00000090: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 ................ 000000A0: 7C 00 19 00 18 42 00 40 - 00 40 18 42 00 00 00 40 |....B.@.@.B...@ 000000B0: 3F 20 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 ? .............. 000000C0: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 ................ 000000D0: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 ................ 000000E0: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 ................ 000000F0: 00 00 00 00 00 00 00 00 - 00 00 FE FF FE FF 00 00 ................ 00000100: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 ................ 00000110: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 ................ 00000120: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 ................ 00000130: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 ................ 00000140: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 ................ 00000150: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 ................ 00000160: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 ................ 00000170: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 ................ 00000180: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 ................ 00000190: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 ................ 000001A0: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 ................ 000001B0: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 ................ 000001C0: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 ................ 000001D0: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 ................ 000001E0: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 ................ 000001F0: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 ................ GPCMD_INQUIRY ============= ioctl: Input/output error sense: 05/55/00 GPCMD_READ_CDVD_CAPACITY ============= 00000000: 00 00 0D BE 00 00 08 00 ........ GPCMD_READ_12 ============= ioctl: Input/output error sense: 05/81/00
When the DVD is powered from my adapter
* both commands succeed.
* DVD drive spins loudly (with a game disc in) without spinning down.
Here's the program output (powered from PC)
WIN_PIDENTIFY ============= 00000000: C0 85 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 ................ 00000010: 00 00 00 00 20 20 20 20 - 20 20 20 20 20 20 20 20 .... 00000020: 20 20 20 20 20 20 20 20 - 00 00 00 00 00 00 30 30 ......00 00000030: 36 34 20 20 20 20 4C 48 - 44 2D 2D 54 54 53 56 44 64 LHD--TTSVD 00000040: 2D 44 4F 52 20 4D 44 47 - 33 52 32 31 4C 30 20 20 -DOR MDG3R21L0 00000050: 20 20 20 20 20 20 20 20 - 20 20 20 20 20 20 00 00 .. 00000060: 00 00 00 0F 00 00 00 02 - 00 02 06 00 00 00 00 00 ................ 00000070: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 07 04 ................ 00000080: 03 00 78 00 78 00 78 00 - 78 00 00 00 00 00 00 00 ..x.x.x.x....... 00000090: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 ................ 000000A0: 7C 00 19 00 18 42 00 40 - 00 40 18 42 00 00 00 40 |....B.@.@.B...@ 000000B0: 3F 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 ?............... 000000C0: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 ................ 000000D0: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 ................ 000000E0: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 ................ 000000F0: 00 00 00 00 00 00 00 00 - 00 00 FE FF FE FF 00 00 ................ 00000100: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 ................ 00000110: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 ................ 00000120: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 ................ 00000130: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 ................ 00000140: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 ................ 00000150: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 ................ 00000160: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 ................ 00000170: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 ................ 00000180: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 ................ 00000190: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 ................ 000001A0: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 ................ 000001B0: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 ................ 000001C0: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 ................ 000001D0: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 ................ 000001E0: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 ................ 000001F0: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 ................ GPCMD_INQUIRY ============= 00000000: 05 80 00 32 5B 00 00 00 - 48 4C 2D 44 54 2D 53 54 ...2[...HL-DT-ST 00000010: 44 56 44 2D 52 4F 4D 20 - 47 44 52 33 31 32 30 4C DVD-ROM GDR3120L 00000020: 30 30 34 36 0046 GPCMD_READ_CDVD_CAPACITY ============= 00000000: 00 00 0D BE 00 00 08 00 ........ GPCMD_READ_12 ============= 00000000: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 ................ 00000010: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 ................ 00000020: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 ................ 00000030: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 ................ 00000040: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 ................ 00000050: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 ................ 00000060: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 ................ 00000070: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 ................ 00000080: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 ................ 00000090: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 ................ 000000A0: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 ................ 000000B0: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 ................ 000000C0: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 ................ 000000D0: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 ................ 000000E0: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 ................ 000000F0: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 ................ 00000100: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 ................ 00000110: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 ................ 00000120: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 ................ 00000130: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 ................ 00000140: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 ................ 00000150: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 ................ 00000160: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 ................ 00000170: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 ................ 00000180: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 ................ 00000190: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 ................ 000001A0: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 ................ 000001B0: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 ................ 000001C0: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 ................ 000001D0: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 ................ 000001E0: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 ................ 000001F0: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 ................ 00000200: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 ................ 00000210: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 ................ 00000220: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 ................ 00000230: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 ................ 00000240: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 ................ 00000250: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 ................ 00000260: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 ................ 00000270: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 ................ 00000280: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 ................ 00000290: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 ................ 000002A0: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 ................ 000002B0: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 ................ 000002C0: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 ................ 000002D0: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 ................ 000002E0: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 ................ 000002F0: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 ................ 00000300: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 ................ 00000310: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 ................ 00000320: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 ................ 00000330: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 ................ 00000340: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 ................ 00000350: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 ................ 00000360: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 ................ 00000370: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 ................ 00000380: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 ................ 00000390: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 ................ 000003A0: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 ................ 000003B0: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 ................ 000003C0: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 ................ 000003D0: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 ................ 000003E0: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 ................ 000003F0: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 ................ 00000400: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 ................ 00000410: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 ................ 00000420: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 ................ 00000430: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 ................ 00000440: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 ................ 00000450: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 ................ 00000460: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 ................ 00000470: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 ................ 00000480: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 ................ 00000490: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 ................ 000004A0: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 ................ 000004B0: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 ................ 000004C0: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 ................ 000004D0: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 ................ 000004E0: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 ................ 000004F0: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 ................ 00000500: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 ................ 00000510: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 ................ 00000520: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 ................ 00000530: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 ................ 00000540: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 ................ 00000550: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 ................ 00000560: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 ................ 00000570: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 ................ 00000580: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 ................ 00000590: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 ................ 000005A0: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 ................ 000005B0: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 ................ 000005C0: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 ................ 000005D0: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 ................ 000005E0: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 ................ 000005F0: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 ................ 00000600: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 ................ 00000610: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 ................ 00000620: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 ................ 00000630: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 ................ 00000640: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 ................ 00000650: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 ................ 00000660: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 ................ 00000670: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 ................ 00000680: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 ................ 00000690: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 ................ 000006A0: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 ................ 000006B0: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 ................ 000006C0: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 ................ 000006D0: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 ................ 000006E0: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 ................ 000006F0: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 ................ 00000700: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 ................ 00000710: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 ................ 00000720: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 ................ 00000730: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 ................ 00000740: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 ................ 00000750: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 ................ 00000760: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 ................ 00000770: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 ................ 00000780: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 ................ 00000790: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 ................ 000007A0: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 ................ 000007B0: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 ................ 000007C0: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 ................ 000007D0: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 ................ 000007E0: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 ................ 000007F0: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 ................
I was able to mount and browse the small DVD structure that exists on 360 game discs in Linux (and windows) when using my adapter. Here are the directory listings.
# mount /dev/hdc /mnt/hdc mount: block device /dev/hdc is write-protected, mounting read-only # ls /mnt/hdc -alF total 10 dr-xr-xr-x 4 4294967295 4294967295 136 2005-10-07 20:18 ./ drwxr-xr-x 8 root root 4096 2006-02-09 19:39 ../ dr-xr-xr-x 65536 4294967295 4294967295 40 2005-10-07 20:17 AUDIO_TS/ dr-xr-xr-x 65536 4294967295 4294967295 352 2005-10-07 20:17 VIDEO_TS/ # ls /mnt/hdc/AUDIO_TS -alF total 4 dr-xr-xr-x 65536 4294967295 4294967295 40 2005-10-07 20:17 ./ dr-xr-xr-x 4 4294967295 4294967295 136 2005-10-07 20:18 ../ # ls /mnt/hdc/VIDEO_TS -alF total 6476 dr-xr-xr-x 65536 4294967295 4294967295 352 2005-10-07 20:17 ./ dr-xr-xr-x 4 4294967295 4294967295 136 2005-10-07 20:18 ../ -r--r--r-- 1 4294967295 4294967295 12288 2005-10-07 20:08 VIDEO_TS.BUP -r--r--r-- 1 4294967295 4294967295 12288 2005-10-07 20:08 VIDEO_TS.IFO -r--r--r-- 1 4294967295 4294967295 3289088 2005-10-07 20:08 VIDEO_TS.VOB -r--r--r-- 1 4294967295 4294967295 12288 2005-10-07 20:08 VTS_01_0.BUP -r--r--r-- 1 4294967295 4294967295 12288 2005-10-07 20:08 VTS_01_0.IFO -r--r--r-- 1 4294967295 4294967295 3289088 2005-10-07 20:08 VTS_01_1.VOB #
Let's call behaviours 8a and 9a with INQUIRY and READ(12) failing and the drive being quiet modeA and behaviours 8b and 9b with INQUIRY and READ(12) working and the drive spinning loudly modeB.
If I remove the connection between the DVD drive and the tray_status input (the input to the base of the transistor) on my adapter board, then the drive enters modeA.
If I remove the connection between the DVD drive and the tray_status input on my adapter board while the drive powers up (from the PC of course) and then connect it after power up, then the still drive behaves as modeA.
Discovery: shorting tray_status to GND during DVD drive power up causes modeB (tested in the 360 and the PC). The 360 works okay in modeB, the eject button doesn't work correctly but it loads a game okay. Leaving tray_status unconnected causes modeA.
There's something going on with tray_status at boot. I've written a very simple parallel port based logic analyser (PPLA) to observe the tray_status line during drive power up. Here's the code.
/*
* written to observe the tray_status signal of an
* xbox 360 DVD drive via a PC parallel port (PP).
*
* author: Kevin East (SeventhSon)
* email: kev@kev.nu
* web: http://www.kev.nu/360/
* date: 10th Febuary 2006
* platform: linux
*
*/
#include <stdio.h>
#include <unistd.h>
#include <sys/io.h>
#define PPBASE 0x378
#define PPSTAT_REG PPBASE + 1
#define PPSTAT_SEL 0x10 // SELECT input (PP pin 13) is connected to tray_status
int main(int argc, char *argv[])
{
unsigned int i, lasti = 0;
unsigned char val, lastval = 0xFF;
if(ioperm(PPSTAT_REG, 1, 1) == -1)
perror("ioperm");
else {
for(i = 0; ; i++) {
if((val = inb(PPSTAT_REG) & PPSTAT_SEL) != lastval) {
printf("val: %u cycle: %u diff: %u\n", val >> 4, i, i - lasti);
lastval = val;
lasti = i;
}
usleep(1); // note: precise timing is not possible in userspace, this delay is for rejecting short glitches and relative approximation only
}
}
}
Here are the results (powered from 360).
val: 1 cycle: 0 diff: 0 (note: 360 turned on here) val: 0 cycle: 55 diff: 55 val: 1 cycle: 58 diff: 3 val: 0 cycle: 62 diff: 4 val: 1 cycle: 71 diff: 9 val: 0 cycle: 80 diff: 9 val: 1 cycle: 84 diff: 4
This is confirmed by the scope (approximate pulse timigs aquired from scope)
~100ms ~100ms ~200ms ~200ms ~100ms
_____ _________ _________________________
______| |_________| |_____|
| | | | | | | | | | |
0 1 0 0 1 1 0 1 1 1 1
0 = 0V
1 = 3.3V
This signal is observed with and without a game disc in the drive and even when the tray_status wire is cut and the DVD end attached to the scope/LA and the 360 end tied to +3.3V (so that it doesn't get confused). The signal *is* coming from the drive.
Possibly the tray_status line is bidirectional and the high pulses are the drive releasing the line and sampling the input. If the line is driven low externally during these periods then modeB (a debug mode?) is entered, else modeA is entered. This is all just a hypothesis. The pulses could be nothing, just a side effect of drive initialisation.
Interesting LA results when powered from the PC with tray_status disconnected from my adater board. The sccope/LA reading is,
val: 1 cycle: 0 diff: 0 (note: 360 turned on here) val: 0 cycle: 63 diff: 63 val: 1 cycle: 66 diff: 3 val: 0 cycle: 70 diff: 4 val: 1 cycle: 79 diff: 9
~100ms ~200ms
____ ____________________________________________
____| |_________|
No second low pulse. Perhaps the difference is down to what the xbox does with eject during boot (pulsing eject could cause another tray opening/closing low status pulse)?
Confirmed. The 360 pulses the eject line for ~100ms during power up. I rewrote the PPLA to capture both signals (code below).
/*
* written to observe the tray_status and eject
* signals of an xbox 360 DVD drive via a PC
* parallel port (PP).
*
* author: Kevin East (SeventhSon)
* email: kev@kev.nu
* web: http://www.kev.nu/360/
* date: 10th Febuary 2006
* platform: linux
*
*/
#include <stdio.h>
#include <unistd.h>
#include <sys/io.h>
#define PPBASE 0x378
#define PPSTAT_REG PPBASE + 1
#define PPSTAT_SEL 0x10 // PP select input (PP pin 13) is connected to tray_status
#define PPSTAT_PAPER 0x20 // PP out of paper input (PP pin 12) is connected to eject
int main(int argc, char *argv[])
{
unsigned int i, lasti = 0, lasti2 = 0;
unsigned char val, lastval = 0xFF, lastval2 = 0xFF;
if(ioperm(PPSTAT_REG, 1, 1) == -1)
perror("ioperm");
else {
for(i = 0; ; i++) {
if((val = inb(PPSTAT_REG) & PPSTAT_SEL) != lastval) {
printf("tray_status> val: %u cycle: %u diff: %u\n", val >> 4, i, i - lasti);
lastval = val;
lasti = i;
}
if((val = inb(PPSTAT_REG) & PPSTAT_PAPER) != lastval2) {
printf("eject> val: %u cycle: %u diff: %u\n", val >> 5, i, i - lasti2);
lastval2 = val;
lasti2 = i;
}
usleep(1); // note: precise timing is not possible in userspace, this delay is for rejecting glitches and relative approximation only
}
}
}
Here are the results.
tray_status> val: 1 cycle: 0 diff: 0 eject> val: 0 cycle: 0 diff: 0 (note: xbox turned on here) tray_status> val: 0 cycle: 49 diff: 49 tray_status> val: 1 cycle: 52 diff: 3 tray_status> val: 0 cycle: 55 diff: 3 tray_status> val: 1 cycle: 65 diff: 10 tray_status> val: 0 cycle: 74 diff: 9 eject> val: 1 cycle: 74 diff: 74 tray_status> val: 1 cycle: 78 diff: 4 eject> val: 0 cycle: 78 diff: 4
This confirms that an eject pulse from the 360 during boot causes the second low pulse on the tray_status line (note the same cycles for rising/falling edge of eject pulse and falling/rising edge of tray_status pulse).
That explains the second low pulse but what about the first?
I disassembled the drive. IC501 (the 8 pin SOIC affair) appears to be a +3.3V output voltage regulator, which means that pin 7 on the DVD power connector is most likely unused and almost certainly not a +3.3V supply.
Voltmeter measurements on pins of IC501
1 = +5V 8 = +5V
2 = +3.3V 7 = 0V
3 = +5V 6 = +3.3V
4 = +5V 5 = +3.3V
tray_status is connected directly to pin ? (I can't be bothered to count) on the MN103 (IC201) which, as previously noted in various other fora, is a 32-bit panasonic microcontroller. This pin is pulled high (+3.3V) by a 10K resistor (R214) to pin 5 (which I am now guessing is the output pin) of IC501 (the voltage regulator). This explains why my adapter board caused modeB. The transistor base resistor (R1) on my adapter and the 10K resistor in the DVD drive (R214) form a potential divider which holds the tray_status line below the threshold for logic level 1.
This is certainly consistent with my hypothesis that the first high pulse is a tray_status release. R214 would pull tray_status high during any release of the mn103 pin which could be overidden by externally driving the tray_status line low.
I removed the microscopic resistor R214 (which I promptly lost :-\), cut the eject line and tied the DVD end to GND (never ejected), cut the tray_status line and tied the 360 end to +3.3V (always closed). I then tied tray_status low with a replacement 10K resistor. By comparing the two tray_status waveforms (tray_status pulled high and low) I can see exactly how the MN103 is driving/releasing the tray_status line. Any periods that differ beween the two waveforms are periods when tray_status is released. Any periods that are the same mean the tray_status line is being driven high/low.
Here are the two waveforms.
Tray status tied low:
~1S
______________________________
___________________| |____________________
tray_status tied high:
~100ms ~200ms
____ ___________________________________________________
____| |_________|
0 | 1 | 0 | 0 | 1 | 1 | 1 | 1 |
As suspected, it looks as though the tray_status line is released during that first high pulse. The waveforms differ after the ~1S pulse too, but that is most likely due to the different behaviours of modeA and modeB.
The sequence: (all times are rough, will tighten up later)
1. DVD drive powered on
2. drive drives tray_status low for 4ms
3. drive releases tray_status (which goes high because of R214) for 4ms
4. drive drives tray_status low for 8ms
5. drive drives tray_status high for 1s
6. drive drives tray_status as required by behaviour 9a/9b
The drive could be sampling the tray_status line between steps 3 and 4. If it's high (default because of R214) then it enters modeA, if it's low (driven externally) then it enters modeB.
WARNING: For my tests I have been shorting the tray_status line to the chassis of my PC briefly to initiate modeB (after croc clipping the floating chassis of the 360 to my PC chassis of course). This works but isn't a very clever idea and could damage the 360 DVD drive. It's fine for the short period while the tray_status pin is released but as soon as the MN103 starts to drive the pin again then you have the MN103 output connected directly to a supply rail, which is *never* good. I have now connected one end of a 10K resistor to the tray_status pin (after removing R214) and the other end of the resistor to a switch between GND and +3.3V. This allows me to safetly select modeA (switch to +3.3V) or modeB (switch to GND).
The firmware should confirm or deny my theory. I've been avoiding the disassembler so far, but it looks like I've gone as far as I can with the hardware.
As already discovered by the hackers working on the DVD firmware at the xboxhacker.net BBS, the firmware is scrambled. If I remember correctly, the scrambling algorithm xors the data with a 32-bit value and swaps the data bits around. Phantasm and Loser did some great work deciphering the bit swapping and Loser wrote a small utility to obfuscate/deobfuscate a firmware image.
Groepaz has written a great IDA module for the MN103 processor. Takes some work to get going with the IDA 4.9 kernel (it was written for 4.7), but nothing that's too much of a problem. Mainly a few functions that previously returned buffers now require the buffer (and it's length) as an argument.
The hackers (too many to name) involved in the xboxhacker.net "hacking DVD firmware" thread have discovered a lot of the mn103's address space mapping. The following quote from MacDennis is a nice summary.
------------------start quote-----------------------
Facts:
* The DVD-ROM in the X360 can contain a Hitachi-LG GDR-3120L DVD-ROM
* This model contains the MN103S94 CPU from Panasonic / Matsushita Electric Industrial
* The purpose of this CPU is being a DVD controller
* This CPU contains the AM32 core, a third-generation microcontroller core
* Instructions / code can be run from ROM, RAM, Flash memory and cache memory
* Data can be stored in RAM and cache memory
* The CPU is connected to external Flash memory, type: 39SF020A (SST)
* The CPU is NOT connected to any *external* RAM/DRAM memory (buffer/cache). There simply isn't any on the mainboard. The Philips XBOX1 drive has external DRAM and also the other X360 drive has it, but not this drive which is odd.
* "One assignment that is common throughout, however, is the location of the reset vector. It is always at 0x40000000."
* The instruction manual has a picture for the AM32 which shows external memory starting at 0x40000000, so it's not always 0x80000000!! See page 13 of the manual, processor mode (for program in external memory / with cache)
* In different mn103 firmwares from different dvd devices, the first 0x1C bytes are almost the same!
* The READ_BUFFER / WRITE_BUFFER commands are used in ATA / ATAPI devices to update the firmware by uploading it to the device by ATA commands, note that Takires mentions that these operations affect the 0x90000000 region. Did you find that out by disassembling the firmware?
* Noticed this on a forum: "However, FYI a custom version of the MN103S with a ROM mask (and not a flash) is commonly used in DVD drives (like the Pioneer 1x6 for instance) as a secondary controller for low level tasks."
With the above facts in mind, this is my theory, yes speculation but still ..
* The missing buffer/cache SRAM (which is odd for a high-speed DVD controller) is *in* the CPU package itself, next to the CPU die
* Reset vector starts at 0x40000000, internal boot rom. Might also contain functions common for a DVD controller. Jumps to 0x90000020. Why 0x20? Then you have room for a (cleartext) header in the firmware.
* The buffer/cache SRAM starts at 0x80000000
* The external firmware flash starts at 0x90000000
* I don't believe this is a custom MS / X360 ASIC, this because of the costs involved.
Opinions anyone?
------------------end quote-----------------------
So, as all the hard work has been done for me, I'll get started on the disassembly.
I've been staring at the disassembly for ages and I can't find anything that looks like it is controlling the tray_status pin. It's a bit of a needle in a haystack. It's also worth noting that any code that releases/reads the tray_status line is quite possibly in the internal memory at 0x40000000 and not in the flash. I'll need to look at that code too.
DaveX, a hacker also working on the firmware has discoved several vendor specific commands for the Hitachi-LG drive (see his post in the xboxhacker.net "firmware hacking" thread). One of these commands (ATAPI command code E7 with a subcommand code of 01) allows you to read data from the drive's address space.
Another hacker (djhuevo) reported success in using DaveX's command to read the internal memory at 0x40000000 (see his post in the xboxhacker.net "firmware hacking" thread). Hopefully I can repeat that success.
I can! Here's the code I used to dump the internal memory. It will dump any range you care to specify, which means it can dump other areas of the mn103 address space too.
/*
* dumps the a given portion of the address space
* of the MN103 microcontroller within the
* Hitachi-LG Xbox 360 DVD drive. Warning: it can
* take a while to dump a lot of data.
*
* example: dump the internal memory mapped
* from 0x40000000 to 0x40020000
* with the 360 drive set up as /dev/hdc
* to the file image.bin using a block size
* of 0x8000.
*
* # ./memdump /dev/hdc 32768 4 32768 ./image.bin
*
* author: Kevin East (SeventhSon)
* email: kev@kev.nu
* web: http://www.kev.nu/360/
* date: 12th Febuary 2006
* platform: linux
*
*/
#include <stdio.h>
#include <linux/cdrom.h>
#include <sys/ioctl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
int main(int argc, char *argv[])
{
int fd;
unsigned int i, cycles, block_size, block_off, block_len;
struct cdrom_generic_command cgc;
struct request_sense sense;
unsigned char *data;
FILE *fptr;
if(argc != 6) {
printf("usage: memdump device offset_in_blocks length_in_blocks block_size output_file\n");
return 1;
}
block_off = atoi(argv[2]);
block_len = atoi(argv[3]);
block_size = atoi(argv[4]);
if(!block_size || block_size > 65535) {
printf("invalid block_size (valid: 1 - 65535)\n");
return 1;
}
if(!(data = malloc(block_size))) {
printf("malloc() failed\n");
return 1;
}
if((fd = open(argv[1], O_RDONLY | O_NONBLOCK)) == -1) {
free(data);
perror("open");
return 1;
}
if(!(fptr = fopen(argv[5], "wb"))) {
free(data);
close(fd);
perror("fopen");
return 1;
}
for(i = block_off; i < block_off + block_len; i++) {
memset(data, '0', sizeof(data));
memset(cgc.cmd, 0, sizeof(cgc.cmd));
cgc.cmd[0] = 0xE7; // vendor specific command (discovered by DaveX)
cgc.cmd[1] = 0x48; // H
cgc.cmd[2] = 0x49; // I
cgc.cmd[3] = 0x54; // T
cgc.cmd[4] = 0x01; // read MCU memory sub-command
cgc.cmd[6] = (unsigned char)(((i * block_size) & 0xFF000000) >> 24); // address MSB
cgc.cmd[7] = (unsigned char)(((i * block_size) & 0x00FF0000) >> 16); // address
cgc.cmd[8] = (unsigned char)(((i * block_size) & 0x0000FF00) >> 8); // address
cgc.cmd[9] = (unsigned char)((i * block_size) & 0x000000FF); // address LSB
cgc.cmd[10] = (unsigned char)((block_size & 0xFF00) >> 8); // length MSB
cgc.cmd[11] = (unsigned char)(block_size & 0x00FF); // length LSB
cgc.buffer = data;
cgc.buflen = block_size;
cgc.sense = &sense;
cgc.data_direction = CGC_DATA_READ;
cgc.timeout = 15000;
if(ioctl(fd, CDROM_SEND_PACKET, &cgc) == -1) {
perror("ioctl");
printf("sense: %02X/%02X/%02X (offset 0x%08X)\n", sense.sense_key, sense.asc, sense.ascq, i * block_size);
}
else {
fwrite(data, block_size, 1, fptr);
if(ferror(fptr))
printf("fwrite() failed (offset 0x%08X)\n", i * block_size);
}
}
printf("\n");
free(data);
fclose(fptr);
close(fd);
return 0;
}
At first I dumped the entire address space from 0x40000000 to 0x80000000 (it took an age to complete). The data cycled around every 0x20000 bytes and so I conclude the same thing as djhuevo, that the internal memory at 0x40000000 is 128KB in size. I used the following command to dump only the first 128KB of memory at 0x40000000.
# ./memdump /dev/hdc 32768 4 32768 ./intmem.bin
According to the mn103 manual the reset vector is at 0x40000000 and the interrupt vector is at 0x40000008. So I expected to see valid code at the beginning of the image and a prompt jump (within the first 8 bytes) to hop over the interrupt service routine. The puzzling thing is that the first 0x10000 bytes are all 0xFF, which is obviously not valid mn103 code :-\ When DaveX presented the vendor specific memory dump command he alluded to range checks. Perhaps the command was blocking access to the boot code from 0x0 - 0x10000 and simply returning 0xFF values. I have decided to confirm/deny this hypothesis before attempting to disassembly the internal memory image.
Another hacker working on the dvd firmware (robinsod) discovered the ATAPI command handler tables (see see his post in the xboxhacker.net "firmware hacking" thread). Using these tables we can easily see that the address of the 0xE7 command handler is 0x90025BC8 (that's offset 0x25BC8 into the flash image).
Here's the disassembly starting at that address. It certainly looks the part (check out the cmp 0xE7 in the first few lines). The comments that I made for myself while running through the code appear along the execution path for the dump memory subcommand (remember that the 0xE7 command can do more than dump memory). Note that code blocks are in execution order, not address order. I've also deleted a lot of code that isn't executed or relevant.
ROM:00025BC8 add 0xFC, SP ! '³' ROM:00025BCB movbu (word_5B7+1), D0 ; get ATAPI command code (note: packet starts at 5B8) ROM:00025BCE cmp 0xE7, D0 ! 'þ' ; is it 0xE7? ROM:00025BD2 beq loc_25BD6 ; yes in our case, so branch to 25BD6 ROM:00025BD6 loc_25BD6: ! CODE XREF: ROM:00025BD2 j ROM:00025BD6 movbu (word_5B9), D0 ; get 2nd byte of ATAPI packet ROM:00025BD9 cmp 0xE, D0 ; is it 0xE? ROM:00025BDB bne loc_25BF3 ; not in our case, so branch to 25BF3 ROM:00025BF3 loc_25BF3: ! CODE XREF: ROM:00025BDB j ROM:00025BF3 ! ROM:00025BE2 j ... ROM:00025BF3 movbu (word_5B9+1), D0 ; get third byte of ATAPI packet ROM:00025BF6 cmp 0x52, D0 ! 'R' ; is it 'R'? ROM:00025BF8 bne loc_25C2C ; not in our case, so brach to 25C2C ROM:00025C2C loc_25C2C: ! CODE XREF: ROM:00025BF8 j ROM:00025C2C ! ROM:00025BFF j ... ROM:00025C2C movbu (word_5B9+1), D0 ; get third byte of ATAPI packet ROM:00025C2F cmp 0x49, D0 ! 'I' ; is it 'I'? ROM:00025C31 bne loc_25C3A ; yes in our case, so don't branch ROM:00025C33 movbu (word_5BB), D0 ; get fourth byte of ATAPI packet ROM:00025C36 cmp 0x54, D0 ! 'T' ; is it 'T'? ROM:00025C38 beq loc_25C42 ; yes in our case, so brach to 25C42 ROM:00025C42 loc_25C42: ! CODE XREF: ROM:00025C38 j ROM:00025C42 movbu (word_5BB+1), D0 ; get fifth byte of ATAPI packet (subcommand code) ROM:00025C45 cmp 1, D0 ; is it 1? ROM:00025C47 beq loc_25CA3 ; yes in our case, so branch to 25CA3 ROM:00025CA3 loc_25CA3: ! CODE XREF: ROM:00025C47 j ROM:00025CA3 call sub_25D27, [], 4 ; call command 0xE7 subcommand 0x01 (dump internal memory) handler ROM:00025CA8 bra loc_25D0A ROM:00025D27 sub_25D27: ! CODE XREF: ROM:loc_25CA3 p ROM:00025D27 call sub_25420, [D2,D3], 0xC ; off we go again ROM:00025D2C mov 1, D0 ROM:00025D2E ret [], 4 ROM:00025420 sub_25420: ! CODE XREF: sub_25D27 p ROM:00025420 mov 0x5C2, A0 ; A0 = address of last two bytes of ATAPI packet, the transfer length input ROM:00025423 call sub_33AD8, [], 8 ; 16bit big endian to little endian conversion ROM:0002542A movhu D0, (word_8C4) ; store transfer length input at 8C4 ROM:0002542D mov 0x5BE, A0 ; A0 = address of the 32-bit address input in the ATAPI packet ROM:00025430 call sub_33A84, [], 8 ; 32bit big endian to little endian conversion ROM:00025437 mov D0, A0 ; our target address goes into A0 ROM:00025439 mov D0, (word_62C) ; and gets stored at 62C ROM:0002543C mov 0xFF000000, D2 ; ROM:00025442 mov word_90000000, D3 ; D3 = 90000000 ROM:00025448 mov D0, D1 ; target address into D1 ROM:00025449 and D2, D1 ; zero all but MSB of target address ROM:0002544B cmp D3, D1 ; are we accessing range 90000000 to 90FFFFFF (flash address space)? ROM:0002544C beq loc_25493 ; yes, so branch to 25493 (this ends the handler) ROM:0002544E movhu (word_8C4), D0 ; get transfer length ROM:00025451 mov A0, D1 ; get target address ROM:00025453 add D0, D1 ; D1 = address of byte after last byte to be copied ROM:00025454 and D1, D2 ; check that target + length doesn't go into firmware ROM:00025456 cmp D3, D2 ; does it? ROM:00025457 beq loc_25493 ; yes, so bail ROM:00025459 mov 0x8002EC00, D2 ; RAM is believed to be mapped at 0x80000000 ROM:0002545F cmp A0, D2 ; are we trying to access above 0x8002EC00? ROM:00025461 bhi loc_2546B ; no, so branch to 2546B ROM:00025463 cmp 0x80037300, A0 ; are we trying to access range 0x8002EC00 - 0x80037300? ROM:00025469 bcs loc_25493 ; yes, so bail. ROM:0002546B ROM:0002546B loc_2546B: ! CODE XREF: sub_25420+41 j ROM:0002546B cmp D1, D2 ; does target address + transfer length take us above 0x8002EC00? ROM:0002546C bhi loc_25476 ; no, so branch to 25476 ROM:0002546E cmp 0x80037300, D1 ; does target address + transfer length take us into 0x8002EC00 - 0x80037300? ROM:00025474 bcs loc_25493 ; yes, so bail ROM:00025476 ROM:00025476 loc_25476: ! CODE XREF: sub_25420+4C j ROM:00025476 mov 0x8003A000, D2 ; Another check for range 0x8003A000 - 0x8003A300 ROM:0002547C cmp A0, D2 ROM:0002547E bhi loc_25488 ROM:00025480 cmp 0x8003A300, A0 ROM:00025486 bcs loc_25493 ROM:00025488 ROM:00025488 loc_25488: ! CODE XREF: sub_25420+5E j ROM:00025488 cmp D1, D2 ; continuing check for range 0x8003A000 - 0x8003A300 ROM:00025489 bhi loc_2549B ROM:0002548B cmp 0x8003A300, D1 ROM:00025491 bcc loc_2549B ; if we pass checks, branch to 2549B ROM:00025493 ROM:00025493 loc_25493: ! CODE XREF: sub_25420+2C j ROM:00025493 ! sub_25420+37 j ... ROM:00025493 mov 4, D0 ROM:00025495 movbu D0, (word_6D8+1) ROM:00025498 clr D0 ROM:00025499 bra locret_254F3 ; we failed checks so we're out ROM:00033AD8 sub_33AD8: ! CODE XREF: sub_2506C+3 p ROM:00033AD8 ! sub_250FE+3 p ... ROM:00033AD8 mov SP, A1 ; this function converts the big endian 16 bit ROM:00033AD9 mov A0, (SP) ; value at (A0) to a little endian 32 bit value ROM:00033ADB mov (A1), A0 ; and returns it in D0 ROM:00033ADD movbu (A0), D0 ; ROM:00033ADF movbu D0, (5,SP) ; ROM:00033AE2 mov (A1), D0 ; ROM:00033AE3 inc D0 ; ROM:00033AE4 mov D0, (A1) ; ROM:00033AE5 mov D0, A0 ; ROM:00033AE7 movbu (A0), D0 ; ROM:00033AE9 movbu D0, (4,SP) ; ROM:00033AEC movhu (4,SP), D0 ; ROM:00033AEF retf [], 8 ; return to 2542A ROM:00033A84 sub_33A84: ! CODE XREF: sub_1A1D5+32 p ROM:00033A84 ! sub_1A1D5+42 p ... ROM:00033A84 mov SP, A1 ; this function converts the big endian 32 bit ROM:00033A85 mov A0, (SP) ; value at (A0) to a little endian 32 bit value ROM:00033A87 mov (A1), A0 ; and returns it in D0 ROM:00033A89 movbu (A0), D0 ROM:00033A8B movbu D0, (7,SP) ROM:00033A8E mov (A1), D0 ROM:00033A8F inc D0 ROM:00033A90 mov D0, (A1) ROM:00033A91 mov D0, A0 ROM:00033A93 movbu (A0), D0 ROM:00033A95 movbu D0, (6,SP) ROM:00033A98 mov (A1), D0 ROM:00033A99 inc D0 ROM:00033A9A mov D0, (A1) ROM:00033A9B mov D0, A0 ROM:00033A9D movbu (A0), D0 ROM:00033A9F movbu D0, (5,SP) ROM:00033AA2 mov (A1), D0 ROM:00033AA3 inc D0 ROM:00033AA4 mov D0, (A1) ROM:00033AA5 mov D0, A0 ROM:00033AA7 movbu (A0), D0 ROM:00033AA9 movbu D0, (4,SP) ROM:00033AAC mov (4,SP), D0 ROM:00033AAE retf [], 8 ; return to 25437 ROM:0002549B loc_2549B: ! CODE XREF: sub_25420+69 j ROM:0002549B ! sub_25420+71 j ROM:0002549B movhu (word_8C4), D1 ; get transfer length ROM:0002549E mov 0x400, D2 ROM:000254A1 cmp D2, D1 ; is transfer length greater than 0x400? ROM:000254A2 bhi loc_254C3 ; yes, so branch to 254C3 ROM:000254A4 mov unk_3CC00, D0 ; D0 = 3CC00 ROM:000254AA call sub_33C12, [D2,D3,A2,A3], 0x18 ; transfer data to RAM ROM:000254B1 mov unk_3CC00, A0 ; A0 = 3CC00 (offset into RAM of our target data) ROM:000254B7 movhu (word_8C4), D0 ; D0 = transfer length ROM:000254BA call sub_1B5FE, [], 0 ; set up control variables for ATAPI data transfer? ROM:000254C1 bra loc_254F1 ; and away we go ROM:000254C3 ! --------------------------------------------------------------------------- ROM:000254C3 ROM:000254C3 loc_254C3: ! CODE XREF: sub_25420+82 j ROM:000254C3 mov D2, D1 ; D1 = 0x400 ROM:000254C4 sub D2, D0 ; D0 = 0x400 ROM:000254C6 movhu D0, (word_8C4) ; overwrite tranfer length with 0x400 ROM:000254C9 mov unk_3CC00, D0 ; D0 = 3CC00 ROM:000254CF call sub_33C12, [D2,D3,A2,A3], 0x18 ; transfer 0x400 bytes of target data to RAM ROM:000254D6 mov (word_62C), D0 ; D0 = some value in internal RAM ROM:000254D9 mov unk_3CC00, A0 ; A0 = 3CC00 (offset into RAM of our target data) ROM:000254DF add D2, D0 ROM:000254E0 mov D0, (word_62C) ROM:000254E3 mov D2, D0 ROM:000254E4 mov unk_900254F6, A1 ROM:000254EA call sub_1B621, [], 0 ; set up control variables for ATAPI data transfer? ROM:000254F1 ROM:000254F1 loc_254F1: ! CODE XREF: sub_25420+A1 j ROM:000254F1 mov 1, D0 ROM:000254F3 ROM:000254F3 locret_254F3: ! CODE XREF: sub_25420+79 j ROM:000254F3 ret [D2,D3], 0xC ; back to 25D2C (this concludes the 0xE7 handler) ROM:00033C12 sub_33C12: ! CODE XREF: sub_100FF+2C p ROM:00033C12 ! sub_100FF+51 p ... ROM:00033C12 mov D1, D3 ; D3 = transfer length ROM:00033C13 mov D0, D2 ; D2 = 3CC00 ROM:00033C14 mov A0, A2 ; A2 = target address ROM:00033C15 mov SP, A3 ; A3 points to top entry of stack ROM:00033C16 inc4 A3 ; A3 points to second to top entry of stack ROM:00033C17 mov A3, A0 ; now so does A0 ROM:00033C18 call sub_64E4, [], 0 ; save CPU state ROM:00033C1F and 0xFDFF, PSW ; clear interrupt mask 1 (IM1) ROM:00033C23 nop ROM:00033C24 nop ROM:00033C25 or 0x80000000, D2 ; D2 = 8003CC00 ROM:00033C2B mov D2, A0 ; so does A0 ROM:00033C2D exthu D3 ; D3 = transfer length ROM:00033C2E clr D0 ; zero D0 ROM:00033C2F cmp D0, D3 ; is transfer zero or negative? ROM:00033C30 ble loc_33C3D ; yep, so branch to 33C3D (exit function) ROM:00033C32 setlb ; setup loop registers ROM:00033C33 movbu (A2), D1 ; D1 = byte at target address ROM:00033C35 inc A2 ; advance source pointer ROM:00033C36 movbu D1, (A0) ; copy to address 8003CC00 (RAM) ROM:00033C38 inc A0 ; advance destination pointer ROM:00033C39 inc D0 ; advance counter ROM:00033C3A exthu D0 ROM:00033C3B cmp D3, D0 ; all bytes transfered? ROM:00033C3C llt ; yes, so branch back to 33C33 ROM:00033C3D ROM:00033C3D loc_33C3D: ! CODE XREF: sub_33C12+1E j ROM:00033C3D mov A3, A0 ; (A3) is where saved PSW is stored ROM:00033C3E call sub_64F0, [], 0 ; restore CPU state ROM:00033C45 ret [D2,D3,A2,A3], 0x18 ; return to 254B1 or 254D6 (depending on transfer length) ROM:000064E4 sub_64E4: ! CODE XREF: sub_1A1D5+3 p ROM:000064E4 ! sub_1A22C+3 p ... ROM:000064E4 movm [D0,D1,A0,A1,MDR,LIR,LAR], (SP) ; this function saves CPU state ROM:000064E6 mov PSW, D0 ROM:000064E8 nop ROM:000064E9 nop ROM:000064EA movhu D0, (A0) ROM:000064EC movm (SP), [D0,D1,A0,A1,MDR,LIR,LAR] ROM:000064EE rets ; return to 33C1F ROM:000064F0 sub_64F0: ! CODE XREF: sub_1A1D5+4D p ROM:000064F0 ! sub_1A22C+39 p ... ROM:000064F0 movm [D0,D1,A0,A1,MDR,LIR,LAR], (SP) ; this function restores CPU state ROM:000064F2 movhu (A0), D0 ROM:000064F4 mov D0, PSW ROM:000064F6 nop ROM:000064F7 nop ROM:000064F8 movm (SP), [D0,D1,A0,A1,MDR,LIR,LAR] ROM:000064FA rets ; return to 33C45 ROM:0001B5FE sub_1B5FE: ! CODE XREF: sub_25420+9A p ROM:0001B5FE ! sub_25781+4F p ... ROM:0001B5FE movhu D0, (word_634) ; store transfer length in internal RAM ROM:0001B601 mov A0, D0 ; D0 = 3CC00 ROM:0001B603 add 0x80000000, D0 ; D0 = 0x8003CC00 (address in RAM of our target data) ROM:0001B609 mov D0, (word_630) ; store address in internal RAM ROM:0001B60C bclr 2, (word_5A8+1) ; clear bit 2 of (5A9) in internal RAM ROM:0001B611 mov 5, D0 ROM:0001B613 movbu D0, (word_6D8+1) ; (6D9) in internal RAM = 5 ROM:0001B616 clr D0 ROM:0001B617 movbu D0, (word_6DA+1) ; (6DB) in internal RAM = 0 ROM:0001B61A bset 8, (word_5A5+1) ; set bit 8 of (5A6) in internal RAM ROM:0001B61F rets ; back to 254C1 ROM:0001B621 sub_1B621: ! CODE XREF: sub_25420+CA p ROM:0001B621 ! sub_25781+66 p ... ROM:0001B621 movhu D0, (word_634) ; this does the same job as sub_1B5FE for transfers over 0x400 bytes ROM:0001B624 mov A0, D0 ROM:0001B626 add 0x80000000, D0 ROM:0001B62C mov D0, (word_630) ROM:0001B62F bclr 2, (word_5A8+1) ROM:0001B634 mov 5, D0 ROM:0001B636 movbu D0, (word_6D8+1) ROM:0001B639 clr D0 ROM:0001B63A movbu D0, (word_6DA+1) ROM:0001B63D bclr 8, (word_5A5+1) ROM:0001B642 mov A1, (word_6DC) ; only difference between sub_1B5FE and this function ROM:0001B646 rets ; return to 254F1 ROM:0001B646 ! End of function sub_1B621
That's the handler pretty much completely traced. Nothing preventing/corrupting access to 0x40000000. So I'm assuming that the dump I made of the internal memory at 0x40000000 is correct. Which is annoying because there are 0xFF values at the previously assumed reset vector.
The range checks that DaveX mentioned turn out to be very interesting. The checks prevent access to the flash range 0x90000000 - 0x90FFFFFF. The firmware is only 0x40000 (256KB) big which requires 18 address lines (2^18 = 256KB). The external memory address space of the MN103 is much larger than that and so the flash memory may also be mapped into addresses 0x90040000, 0x90080000, etc. More importantly, it may well be mapped into 0x91000000, 0x91040000, etc. These latter addresses will make it through the range check.
Confirmed! You *can* dump the firmware in software using the following command.
# ./memdump /dev/hdc 74240 8 32768 ./firmware.bin
The other two checks are supposed to prevent access to the ranges 0x8002EC00 - 0x80037300 and 0x8003A000 - 0x8003A300. What's so special about those ranges? Perhaps the memory mapping trick will also work on these addresses. To try this I'll first dump way more than I'll need to try and determine the size of the memory.
# ./memdump /dev/hdc 65536 8192 32768 ./ram.bin ioctl: Input/output error sense: 00/00/00 (offset 0x80028000) ioctl: Input/output error sense: 00/00/00 (offset 0x80030000)
Those errors are the range checks kicking in. Looking at the dump in the hex editor it appears to cycle around every 0x40000 bytes (although it's harder to tell because the RAM can change between reads). So the RAM looks to be 256KB in size. So let's try dumping from address 0x81000000 to 0x81040000. If the mapping trick works this should give us the entire contents of the RAM.
# ./memdump /dev/hdc 66048 8 32768 ./ram.bin
No errors. A good sign. Looking at the image confirms that it has worked.
Deja vu? This trick is identical to the MIST trick that fooled the POKEPCI check on the original xbox.
There's another bug with the latter two checks too. The checks are as follows
if target address is in secret range, then do not allow.
if target address + requested transfer length is in secret range, then do not allow.
Those two rules do not cover all possible address and transfer lengh combinations that can return the secret ranges. If we use the maximum transfer length (0xFFFF) and specify an offset as close as possible to the start of a secret range, then our target address will be below the range and our target address + the transfer length (0xFFFF) will be above the range. The result is that somewhere in the middle of our dump we get the entire secret range. This works because the length of the latter two forbidden ranges are less than the maximum transfer length of 0xFFFF. Here are the commands I used. This won't work to dump the firmware at 0x90000000, that range is too large.
# ./memdump /dev/hdc 32771 1 65535 ./range1.bin
# ./memdump /dev/hdc 32772 1 65535 ./range2.bin
The checks are useless.
At this point I'm no closer to finding any code that selects modeA/modeB based on tray_status. Hopefully I'll have more to add later.
Shortly after posting the above notes to the xboxhacker.net forums, Tiros posted details of his own experiences using the drive under windows. Apparently it is only necessary to tie the eject input low during DVD drive power up to get the drive working under windows. It will be wise to look into this before investing any more time into my own ideas.
After some more experimentation on my machine:
a) I have not
been able to reproduce the success Tiros has had with grounding eject
at drive power up in win98 (the only version of windows that I have,
unfortunately).
b) Linux can mount/browse a game disc even in modeA. If I power the
drive up from the 360 (all connections intact) connected to the PC
through a PATA - SATA bridge board, then it works fine (although
Read(12) and Inquiry fail as usual with the usual check conditions for
modeA).
c) The drive works in win98/Linux every time in modeB (pulling tray_status low during power up)
After many false assurances that his computer could not be damaged I managed to convince my friend to allow me to run some tests on his windows XP machine with native SATA.
Results from XP:
c) In XP the drive works every time in modeB with both the bridge board
and native SATA. Although to get a drive letter assigned (and hence
have the drive show up under "my computer") with native SATA, I have to
go through "device manager > DVD/CDROM drives > right click on
HL-DT-ST DVD-ROM GDR3120L SCSI CdRom Device > Properties >
Volumes tab > Populate > OK" (screen shot). After that it works nicely.
d) I still can't reproduce the grounding eject method using a PATA - SATA bridge board or the native SATA.
I'm guessing (could be wrong) that Tiros grounded eject trick will leave the drive in modeA, just as Linux can access the drive in modeA. Still lots of questions. Answers to come hopefully. Although what I have been referring to as "modeB" isn't fully understood, the fact is that (as strange as it may seem) pulling tray_status low during power up gets my drive working in Linux/Win98 and WinXP (with and without native SATA). Hmmm...
Inquiry and Read(12) fail for modeA and succeed for modeB. So let's disassemble their handlers and see if we can spot some checks. Any checks can possibly be traced back to the inital modeA/B branch.
Here's the disassembly for the Inquiry handler.
ROM:00024F48 movm [D2,A2], (SP) ROM:00024F4A add 0xF8, SP ! '°' ROM:00024F4D btst 3, (word_5B9) ; check1: are bits 0 (EVPD) and 1 (CmdDt) of packet[1] clear? ROM:00024F52 beq loc_24F57 ; yes, so branch ROM:00024F54 jmp loc_25013 ; no, so bail out ROM:00024F57 ! --------------------------------------------------------------------------- ROM:00024F57 ROM:00024F57 loc_24F57: ! CODE XREF: ROM:00024F52j ROM:00024F57 movbu (word_5B9+1), D0 ROM:00024F5A cmp 0, D0 ; check2: is packet[2] (page/operation code) zero? ROM:00024F5C beq loc_24F61 ; yes, so branch ROM:00024F5E jmp loc_25013 ; no, so bail out ROM:00024F61 ! --------------------------------------------------------------------------- ROM:00024F61 ROM:00024F61 loc_24F61: ! CODE XREF: ROM:00024F5Cj ROM:00024F61 call sub_1B53B, [], 0 ; check3: get bit 6 of (598) (internal data RAM) into D0 ROM:00024F68 extb D0 ROM:00024F69 cmp 0, D0 ; was bit 6 zero? ROM:00024F6B bne loc_24F80 ; no, so branch ROM:00024F6D movbu (word_5BD), D0 ; yes, D0 = packet[5] ROM:00024F70 mov 0xC0, D1 ! '+' ROM:00024F73 and D1, D0 ; set bits 6 and 7 (both vendor specific) of packet[5] (EDIT 16th March 2006: this is a mistake, see below) ROM:00024F75 cmp D1, D0 ; check4: are all the other bits (NACA, flag, link) zero? ROM:00024F76 beq loc_24F80 ; yes, so branch ROM:00024F78 mov 0xD, D0 : no, so bail ROM:00024F7A movbu D0, (word_5D8) ROM:00024F7D jmp loc_25018 ROM:0001B53B sub_1B53B: ! CODE XREF: sub_1A99A+Cp ROM:0001B53B ! sub_1DE7Bp ... ROM:0001B53B btst 0x40, (word_598) ! '@' ; is bit 6 of (598) clear? ROM:0001B540 beq loc_1B546 ; yes so branch ROM:0001B542 mov 1, D0 ; no, so D0 = 1 ROM:0001B544 bra locret_1B547 ; return ROM:0001B546 ! --------------------------------------------------------------------------- ROM:0001B546 ROM:0001B546 loc_1B546: ! CODE XREF: sub_1B53B+5j ROM:0001B546 clr D0 ; D0 = 0 ROM:0001B547 ROM:0001B547 locret_1B547: ! CODE XREF: sub_1B53B+9j ROM:0001B547 ROM:0001B547 locret_1B547: ! CODE XREF: sub_1B53B+9j ROM:0001B547 rets ; return to 24F68 ROM:00024F80 movbu (word_5BB+1), D2 ; D2 = allocation length ROM:00024F83 mov D2, D1 ROM:00024F84 exthu D1 ; D1 = allocation length ROM:00024F85 cmp 0, D1 ; is alloc length zero? ROM:00024F87 bne loc_24F8C ; nope, so branch ROM:00024F89 jmp loc_25018 ; yes, bail ROM:00024F8C ! --------------------------------------------------------------------------- ROM:00024F8C ROM:00024F8C loc_24F8C: ! CODE XREF: ROM:00024F87j ROM:00024F8C mov D2, D1 ; D1 = alloc length ROM:00024F8D exthu D1 ROM:00024F8E cmp 0x60, D1 ! '`' ; is alloc length greater 0x60? ROM:00024F90 ble loc_24F94 ; no, so branch ROM:00024F92 mov 0x60, D2 ! '`' ; yes, so truncate it to 0x60 ROM:00024F94 ROM:00024F94 loc_24F94: ! CODE XREF: ROM:00024F90j ROM:00024F94 mov unk_3CC00, D0 ; D0 = 3CC00 ROM:00024F9A mov 0x60, D1 ! '`' ; D1 = 0x60 ROM:00024F9C mov unk_9003D47C, A0 ; A0 points to flash address 9003D47C (Inquiry response data) ROM:00024FA2 call sub_33C12, [D2,D3,A2,A3], 0x18 ; Transfer 0x60 byts of response data to RAM ROM:00024FA9 mov SP, A2 ROM:00024FAA inc4 A2 ; A2 = top of stack ROM:00024FAB mov A2, A1 ; A1 = top of stack ROM:00024FAC mov unk_9003D4A0, A0 ; points to flash address 9003D5A0 ROM:00024FB2 clr D0 ; D0 = 0 ROM:00024FB3 bra loc_24FBC ; branch ROM:00024FB5 ! --------------------------------------------------------------------------- ROM:00024FB5 ROM:00024FB5 loc_24FB5: ! CODE XREF: ROM:00024FBFj ROM:00024FB5 movbu (A0), D1 ; D1 = first byte of inquiry response (device type) ROM:00024FB7 inc A0 ; advance source ptr ROM:00024FB8 movbu D1, (A1) ; stick device type on stack ROM:00024FBA inc A1 ; advance destination ptr ROM:00024FBB inc D0 ; advance counter ROM:00024FBC ROM:00024FBC loc_24FBC: ! CODE XREF: ROM:00024FB3j ROM:00024FBC exthu D0 ROM:00024FBD cmp 4, D0 ; is D0 less than 4? ROM:00024FBF blt loc_24FB5 ; loop copies first for bytes of inquiry data to stack ROM:00024FC1 mov 0x41, D0 ! 'A' ; D0 = 0x41 ROM:00024FC3 movbu D0, (7,SP) ; put 41 on stack ROM:00024FC6 mov unk_3CC24, D0 ; D0 = 3CC24 ROM:00024FCC mov 4, D1 ; D1 = 4 ROM:00024FCE mov A2, A0 ; A0 = ptr to the 4 bytes we just put on the stack ROM:00024FCF call sub_33C12, [D2,D3,A2,A3], 0x18 ; copy those 4 bytes to 3CC24 in RAM (offset 36 into the inquiry response, vendor specific) ROM:00024FD6 mov 0x5FFC, A0 ; A0 = 5FFC (address in internal data RAM) ROM:00024FD9 call sub_33A84, [], 8 ; check5: D0 = make_litte_endian((5FFC)); ROM:00024FE0 cmp 0xEEF8EFFA, D0 ; is the result EEF8EFFA? ROM:00024FE6 bne loc_24FFD ; no, so branch (skips revision level overwrite) ROM:00024FE8 mov 0x5A, D0 ! 'Z' ; yes, D0 = 5A; ROM:00024FEA movbu D0, (4,SP) ; put 5A on stack ROM:00024FED mov unk_3CC20, D0 ; D0 = 3CC20 (offset 32 into inquiry response, product revision level) ROM:00024FF3 mov 1, D1 ; D1 = 1 (copy one byte) ROM:00024FF5 mov A2, A0 ; A0 = where we just put 0x5A ROM:00024FF6 call sub_33C12, [D2,D3,A2,A3], 0x18 ; overwrite product revision level in RAM ROM:00024FFD ROM:00024FFD loc_24FFD: ! CODE XREF: ROM:00024FE6j ROM:00024FFD bclr 0x80, (word_59E) ! 'Ç' ; clear bit 7 of (59E) ROM:00025002 mov unk_3CC00, A0 ; A0 = start of inquiry response in RAM ROM:00025008 exthu D2 ; D2 = alloc length ROM:00025009 mov D2, D0 ; D0 = alloc length ROM:0002500A call sub_1B5FE, [], 0 ; set up control variables for ATAPI transfer? ROM:00025011 bra locret_2501D ; return (this concludes the Inquiry handler) ROM:00025013 ! --------------------------------------------------------------------------- ROM:00025013 ROM:00025013 loc_25013: ! CODE XREF: ROM:00024F54j ROM:00025013 ! ROM:00024F5Ej ROM:00025013 mov 0xA, D0 ROM:00025015 movbu D0, (word_5D8) ROM:00025018 ROM:00025018 loc_25018: ! CODE XREF: ROM:00024F7Dj ROM:00025018 ! ROM:00024F89j ROM:00025018 mov 4, D0 ROM:0002501A movbu D0, (word_6D8+1) ROM:0002501D ROM:0002501D locret_2501D: ! CODE XREF: ROM:00025011j ROM:0002501D ret [D2,A2], 0x10 ; The concludes the Inquiry handler ROM:00033A84 sub_33A84: ! CODE XREF: sub_1A1D5+32p ROM:00033A84 ! sub_1A1D5+42p ... ROM:00033A84 mov SP, A1 ; A1 = top of stack ROM:00033A85 mov A0, (SP) ; put 5FFC on stack ROM:00033A87 mov (A1), A0 ; A0 = 5FFC ROM:00033A89 movbu (A0), D0 ; D0 = (5FFC) ROM:00033A8B movbu D0, (7,SP) ; store (5FFC) on stack ROM:00033A8E mov (A1), D0 ; D0 = 5FFC ROM:00033A8F inc D0 ; D0 = 5FFD ROM:00033A90 mov D0, (A1) ; store 5FFD on stack ROM:00033A91 mov D0, A0 ; A0 = 5FFD ROM:00033A93 movbu (A0), D0 ; D0 = (5FFD) ROM:00033A95 movbu D0, (6,SP) ; store (5FFD) on stack ROM:00033A98 mov (A1), D0 ; D0 = 5FFD ROM:00033A99 inc D0 : D0 = 5FFE ROM:00033A9A mov D0, (A1) ; blah ROM:00033A9B mov D0, A0 ; blah ROM:00033A9D movbu (A0), D0 ROM:00033A9F movbu D0, (5,SP) ; store (5FFE) on stack ROM:00033AA2 mov (A1), D0 ; blah ROM:00033AA3 inc D0 ROM:00033AA4 mov D0, (A1) ROM:00033AA5 mov D0, A0 ROM:00033AA7 movbu (A0), D0 ROM:00033AA9 movbu D0, (4,SP) ; store (5FFF) on stack ROM:00033AAC mov (4,SP), D0 ; D0 = ((5FFC) << 24) | ((5FFD) << 16) | ((5FFE) << 8) | (5FFF); ROM:00033AAE ROM:00033AAE locret_33AAE: ROM:00033AAE retf [], 8 ; return to 24FE0
checks 1,2 and 4 = uninteresting, standard ATAPI field checks.
check5 = interesting. Inquiry returns revision level 5A47 instead of 0047 for product revision level if the little endian version of the word at (5FFC) is EEF8EFFA? I don't want to get sidetracked again, I'll leave this as a mystery for now.
check3 = very interesting. If (598) bit 6 is set, then set a couple of vendor specific bits in packet[5] before continuing. Perhaps (598) bit 6 indicates modeA or modeB. (EDIT 16th March 2006: reading through this stuff again, it appears that I made a mistake here. The handler doesn't set any bits, it just skips over the check for those bits if (598) bit 6 is set. Fortunately this error didn't/doesn't affect any of my conclusions). This could explain why Inquiry works for modeB and not modeA. The first thing to try is sending and Inquiry command to the drive in modeA with these bits set and see if it works. Here's code to do that. From the disassembly, it looks like the drive has 60 bytes of Inquiry data. So there's some vendor specific stuff in the output for sure. My dvdprobe.c only dumped the first (mandatory) 36 bytes, this program dumps all 96 bytes.
/*
* sends an ATAPI Inquiry command with the
* vendor specific bits at offset 5 into
* the command packet set (as required by the
* Xbox360 Hitachi-LG DVD drive)
*
* author: Kevin East (SeventhSon)
* email: kev@kev.nu
* web: http://www.kev.nu/360/
* date: 21st february 2006
* platform: linux
*
*/
#include <stdio.h>
#include <linux/cdrom.h>
#include <sys/ioctl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
void dump_hex(unsigned char *data, unsigned int len, unsigned int offset)
{
unsigned int i, j, mod;
char ascii[16];
for(i = 0; i < len; i++) {
mod = i % 16;
if(!(mod)) {
if(i) {
for(j = 0; j < 16; j++) {
if(ascii[j] >= 0x20 && ascii[j] <= 0x7E)
printf("%c", ascii[j]);
else
printf(".");
}
printf("\n");
}
printf("%08X: ", offset + i);
}
else if(i && !(i % 8))
printf("- ");
ascii[mod] = data[i];
printf("%02X ", data[i]);
}
if(!len)
printf("\n");
else {
len = 16 - (len % 16);
if(len != 16) {
if(len >= 8)
printf(" ");
for(mod++, i = 0; i < len; i++) {
ascii[mod + i] = ' ';
printf(" ");
}
}
for(j = 0; j < 16; j++) {
if(ascii[j] >= 0x20 && ascii[j] <= 0x7E)
printf("%c", ascii[j]);
else
printf(".");
}
printf("\n");
}
}
int main(int argc, char *argv[])
{
int fd;
struct cdrom_generic_command cgc;
struct request_sense sense;
unsigned char data[0x60];
if(argc != 2) {
printf("usage: dvdprobe device\n");
return 1;
}
if((fd = open(argv[1], O_RDONLY | O_NONBLOCK)) == -1) {
perror("open");
return 1;
}
printf("\n\nGPCMD_INQUIRY\n=============\n"); // ATAPI INQUIRY
memset(&cgc, 0, sizeof(cgc));
cgc.cmd[0] = GPCMD_INQUIRY;
cgc.cmd[4] = 0x60;
cgc.cmd[5] = 0xC0; // set vendor specific bits 6 and 7
cgc.buffer = data;
cgc.buflen = 0x60;
cgc.sense = &sense;
cgc.data_direction = CGC_DATA_READ;
cgc.timeout = 15000;
if(ioctl(fd, CDROM_SEND_PACKET, &cgc) == -1) {
perror("ioctl");
printf("sense: %02X/%02X/%02X\n", sense.sense_key, sense.asc, sense.ascq);
}
else
dump_hex(data, 0x60, 0);
printf("\n");
close(fd);
return 0;
}
Output for modeA:
GPCMD_INQUIRY ============= 00000000: 05 80 00 32 5B 00 00 00 - 48 4C 2D 44 54 2D 53 54 ...2[...HL-DT-ST 00000010: 44 56 44 2D 52 4F 4D 20 - 47 44 52 33 31 32 30 4C DVD-ROM GDR3120L 00000020: 30 30 34 36 30 42 4D 41 - 42 20 20 20 30 35 2F 30 00460BMAB 05/0 00000030: 37 2F 32 37 20 20 20 00 - 01 00 00 00 00 00 00 00 7/27 ......... 00000040: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 ................ 00000050: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 ................
It worked! This is consistant with what Nayr discovered about the Hitachi-LG's inquiry command (long before I did). It's possible that a standard Inquiry works in modeB because the handler sets the required vendor specific bits for you (after check 3) (EDIT 16th March 2006: reading through this stuff again, it appears that I made a mistake here. The handler doesn't set any bits, it just skips over the check for those bits if (598) bit 6 is set. Fortunately this error didn't/doesn't affect any of my conclusions). If this is true, then it would seem that (598) bit 6 specifies modeA/ModeB.
The only writes to (598) bit 6 that I can find in the firmware are at the following locations:
90006662
90006669
Here's the disassembly listing in that general area.
ROM:0000665A movbu (word_D98C+1), D0 ; read (D98D) ROM:0000665D btst 0x10, D0 ; Is bit 4 set? ROM:00006660 beq loc_6669 ; No, branch ROM:00006662 bclr 0x40, (word_598) ; Yes, so (598) bit 6 = 0 (modeA?) ROM:00006667 bra loc_6680 ROM:00006669 ! --------------------------------------------------------------------------- ROM:00006669 ROM:00006669 loc_6669: ! CODE XREF: sub_654A+116j ROM:00006669 bset 0x40, (word_598) ; (598) bit 6 = 1 (modeB?) ROM:0000666E clr D0 ROM:0000666F movbu D0, (0x8003E6D8) ROM:00006675 mov 1, D0 ROM:00006677 mov 0x64, D1 ! 'd' ROM:00006679 call sub_1EEB3, [D2,D3,A2], 0x10 ROM:00006680 ROM:00006680 loc_6680: ROM:00006680 mov 0x20, D0 ! ' ' ROM:00006682 movbu D0, (A2) ROM:00006684 ret [D2,A2], 0xC ROM:00006684 ! End of function sub_654A
We can see that bit 6 of (598) is set or cleared after testing (D98D) bit 4. According to the MN103 manual, (D98D) can be internal data RAM or mapped externally depending on memory configuration.
Occurances of writes to (D98D) in the firmware (the ones I can find at least):
900000E9 (set to zero)
Here's the code around that location
ROM:000000E5 movbu D0, (word_D98C) ! D98C (internal/external data RAM) = 20 ROM:000000E8 clr D0 ROM:000000E9 movbu D0, (word_D98C+1) ! D98C (internal/external data RAM) = 0 ROM:000000EC rets
This is very near to the suspected flash entry point (offset 0x20), so this is likely code to initalise (D98C/D). I think (D98C) and (D98D) are related. Look at the code directly above the (D98D) bit 4 check (the check that I suspect selects modeA or modeB).
ROM:0000664E mov 0xD98C, A2 ; A2 = D98C ROM:00006651 mov 0x30, D0 ! '0' ; D0 = 0x30 ROM:00006653 movbu D0, (A2) ; set bit 4 and 5 at (D98C) ROM:00006655 call sub_628E, [D2], 4 ; call timer delay function ROM:0000665A movbu (word_D98C+1), D0 ; read (D98D) ROM:0000665D btst 0x10, D0 ; Is bit 4 set? ROM:00006660 beq loc_6669 ; No, branch ROM:00006662 bclr 0x40, (word_598) ; Yes, so (598) bit 6 = 0 (modeA?) ROM:00006667 bra loc_6680 ROM:00006669 ! --------------------------------------------------------------------------- ROM:00006669 ROM:00006669 loc_6669: ! CODE XREF: sub_654A+116j ROM:00006669 bset 0x40, (word_598) ; (598) bit 6 = 1 (modeB?) ROM:0000666E clr D0 ROM:0000666F movbu D0, (0x8003E6D8) ROM:00006675 mov 1, D0 ROM:00006677 mov 0x64, D1 ! 'd' ROM:00006679 call sub_1EEB3, [D2,D3,A2], 0x10 ROM:00006680 ROM:00006680 loc_6680: ROM:00006680 mov 0x20, D0 ! ' ' ; D0 = 0x20 ROM:00006682 movbu D0, (A2) ; clear bit 4 at (D98C) ROM:00006684 ret [D2,A2], 0xC ROM:00006684 ! End of function sub_654A ROM:0000628E sub_628E: ! CODE XREF: sub_654A+10Bp ROM:0000628E movhu (word_DC54), D1 ; (DC54) a timer? D1 = initial timer value ROM:00006291 setlb ; set up loop registers ROM:00006292 movhu (word_DC54), D0 ; read current timer value ROM:00006295 mov D1, D2 ROM:00006296 sub D0, D2 ; calc difference between inital and current timer values ROM:00006298 mov D2, D0 ROM:00006299 exthu D0 ROM:0000629A cmp 0x6A, D0 ! 'j' ROM:0000629C llt ; Loop for 0x6A timer increments ROM:0000629D retf [D2], 4 ROM:0000629D ! End of function sub_628E
If you trace the disassembly from 0x90000020 (flash entry point), then you can see that the above code is executed very to near to the begining of flash execution. It also appears to be executed only once. The subroutine at 9000628E looks suspiciously like a timer delay routine to me. I could be seeing things but (D98D) looks like an 8 bit I/O port's data register and (D98C) looks like the I/O port's corresponding control register (bits set to 0 for output and 1 for input). Here's my current speculation.
1) Drive powers up
execution hits 0x900000E5
2) Drive
initialises I/O control register (D98C) to 0x20 (all I/O lines outputs
except for line 5, which is an input, eject perhaps?)
3) Drive also initialises I/O data register (D98D) to 0x00 (send all output lines low)
execution hits 0x9000664E
4) Drive sets I/O control register (D98C) to 0x30 (line 4, tray_status,
is now an input). Resistor R214 pulls it high in an out of the box
drive.
5) Drive calls the timer delay function at 0x9000628E to give enough time for tray_status input to settle.
6) Drive reads I/O data register (D98D). If bit 4 is set (tray_status
high), then it clears (598) bit 6. This cements modeA. If bit 4 is
clear (tray_status low), then it sets (598) bit 6. This cements modeB.
execution hits 0x90006680
7) Drive sets I/O control register (D98C) back to 0x20 (line 4, tray_status, is now an output again).
If this speculation is true, then it explains the first part of the waveforms that I have been seeing on tray_status and the behaviour of the Inquiry command that I have been observing. It would also confirm my theory about a bidirectional tray_status line. Finally, it would explain why I have had so much success in Windows with modeB. A standard Inquiry command works in modeB (the handler fills in the vendor specific bits for you). The non-standard Inquiry seems to be the biggest stumbling block for windows.
The subroutine at 0x9001B53B that returns (598) bit 6 (the possible modeA/B indicator) is referenced a fair amount in the firmware. If the above speculation holds true, then tracing these references will highlight other differences between modeA and modeB. For example, I image we'd find a reference to this function in the READ(12) command handler (which also works in modeB, but not modeA).
Todo: confirm/deny the above speculation.
There exists a debug command in the Hitachi drive that will set (598) bit 6 (see SuperMario's post on xbh.net). Here's the handler code for this command.
ROM:00025FA7 loc_25FA7: ! CODE XREF: sub_25D86+C1 j ROM:00025FA7 movbu (word_5BF+1), D0 ROM:00025FAA cmp 1, D0 ROM:00025FAC bne loc_25FF4 ROM:00025FAE movbu (word_598), D0 ROM:00025FB1 mov 0x64, D1 ! 'd' ROM:00025FB3 or 0x40, D0 ! '@' ROM:00025FB6 or 0x10, D0 ROM:00025FB9 movbu D0, (word_598) ROM:00025FBC clr D0 ROM:00025FBD movbu D0, (0x8003E6D8) ROM:00025FC3 mov 1, D0 ROM:00025FC5 call sub_1EEB3, [D2,D3,A2], 0x10 ROM:00025FCA bra loc_25FF4 ROM:00025FF4 ROM:00025FF4 loc_25FF4: ! CODE XREF: sub_25D86+102 j ROM:00025FF4 ! sub_25D86+10A j ... ROM:00025FF4 clr D0 ROM:00025FF5 ret [D2], 8 ROM:00025FF5 ! End of function sub_25D86
This handler is doing all of the same things as the code at 0x90006669 that I posted about in my last update and that I suspected was setting up modeB after sampling the tray_status input. They both set (598) bit 6, clear the byte at 0x8003E6D8 in RAM and call sub_1EEB3 with parameters 0x64 (in D1) and 0x01 (in D0). It certainly looks the part. Here's some code that will send the drive this command.
/*
* Puts a Hitachi-LG Xbox 360 DVD drive into modeB.
* In modeB, the drive responds to standard ATAPI
* commands that it otherwise wouldn't (for example:
* Read(12), Inquiry, Mode Sense(10)). ModeB also
* changes, among other things, the behaviour of the
* drive's eject input and tray_status output.
*
* author: Kevin East (SeventhSon)
* email: kev@kev.nu
* web: http://www.kev.nu/360/
* date: 2nd March 2006
* platform: linux
*
*/
#include <stdio.h>
#include <linux/cdrom.h>
#include <sys/ioctl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
int main(int argc, char *argv[])
{
int fd;
struct cdrom_generic_command cgc;
struct request_sense sense;
if(argc != 2) {
printf("usage: modeb device\n");
return 1;
}
if((fd = open(argv[1], O_RDONLY | O_NONBLOCK)) == -1) {
perror("open");
return 1;
}
memset(&cgc, 0, sizeof(cgc));
cgc.cmd[0] = 0xE7; // Hitachi debug command 0xE7
cgc.cmd[1] = 0x48; // 'H'
cgc.cmd[2] = 0x49; // 'I'
cgc.cmd[3] = 0x54; // 'T'
cgc.cmd[4] = 0x30;
cgc.cmd[5] = 0x90;
cgc.cmd[6] = 0x90;
cgc.cmd[7] = 0xD0;
cgc.cmd[8] = 0x01;
cgc.sense = &sense;
cgc.data_direction = CGC_DATA_NONE;
cgc.timeout = 15000;
if(ioctl(fd, CDROM_SEND_PACKET, &cgc) == -1) {
perror("ioctl");
printf("sense: %02X/%02X/%02X\n", sense.sense_key, sense.asc, sense.ascq);
}
else
printf("done\n");
printf("\n");
close(fd);
return 0;
}
It works! The drive immediately spins up loudly in the usual modeB style, eject and tray_status adopt their usual modeB behaviours and the standard Inquiry and Read(12) commands succeed. I've also written a win32 version of the above program, code and binary below.
Be warned: This win32 program is the mother of all hacks. It does not use the windows API (DeviceIoControl(), IOCTL_SCSI_DIRECT_OR_WHATEVER_ITS_CALLED, etc.) and instead uses a userspace parallel ATA driver that I wrote back when I was just learning about the ATA spec. The userpace driver writes directly to the ATA controller's I/O ports (which requires Geek Hideout's I/O dll, which I have included in the binary zip). It also requires that you be using an ATA - SATA adapter to connect your 360 drive to your PC, or an SATA controller that has a legacy mode (try the BIOS setup options on your controller). Sorry :-\ If anybody feels like writing a proper win32 version, then please email me and let me know. I have zero patience for the win32 API.
/*
* Puts a Hitachi-LG Xbox 360 DVD drive into modeB.
* In modeB, the drive responds to standard ATAPI
* commands that it otherwise wouldn't (for example:
* Read(12), Inquiry, Mode Sense(10)). ModeB also
* changes, among other things, the behaviour of the
* drive's eject input and tray_status output.
*
* author: Kevin East (SeventhSon)
* email: kev@kev.nu
* web: http://www.kev.nu/360/
* date: 2nd March 2006
* platform: Windows
*
*/
#include "../ata/ata.h"
#include <windows.h>
#include <string.h>
#include <stdio.h>
int hex_atoi(void *dest, char *src, int dest_len)
{
int src_last, i, j;
unsigned char val;
memset(dest, 0, dest_len);
src_last = strlen(src) - 1;
for(i = src_last; i >= 0; i--) {
if(src[i] >= '0' && src[i] <= '9')
val = src[i] - 0x30;
else if(src[i] >= 'a' && src[i] <= 'f')
val = (src[i] - 0x60) + 9;
else if(src[i] >= 'A' && src[i] <= 'F')
val = (src[i] - 0x40) + 9;
else
return 1; // invalid hex digit
j = src_last - i;
if(j & 1)
val <<= 4;
#ifdef BIG_ENDIAN
j = dest_len - ((j >> 1) + 1);
#else
j >>= 1;
#endif
if(j >= dest_len || j < 0)
break;
((unsigned char *)dest)[j] |= val;
}
return 0;
}
int main(int argc, char *argv[])
{
struct ata_info ai;
unsigned short comm, ctrl;
unsigned char pio, packet[12];
LoadIODLL();
if(argc < 3) {
printf("usage: modeb_win command_base control_base [pio_mode]\n\ncommand_base: base register of ATA command block in hex (e.g. 1F0 or 170)\ncontrol_base: base register of ATA control block in hex (e.g. 3F6 or 376)\npio_mode: 16 or 32 (default: 32)\n\n");
return 1;
}
if(hex_atoi(&comm, argv[1], sizeof(comm))) {
printf("error: invalid ATA command block base register\n");
return 1;
}
if(hex_atoi(&ctrl, argv[2], sizeof(ctrl))) {
printf("error: invalid ATA control block base register\n");
return 1;
}
if(argc > 3) {
pio = atoi(argv[3]);
if(pio == 16)
pio = ATA_PIO_16;
else if(pio == 32)
pio = ATA_PIO_32;
else {
printf("error: invalid PIO mode (valid: 16 or 32)\n");
return 1;
}
}
else
pio = ATA_PIO_16;
ata_init_info(&ai, comm, ctrl, pio, ATA_DEFAULT_TIMEOUT);
memset(packet, 0, sizeof(packet));
packet[0] = 0xE7; // Hitachi debug command 0xE7
packet[1] = 0x48; // 'H'
packet[2] = 0x49; // 'I'
packet[3] = 0x54; // 'T'
packet[4] = 0x30;
packet[5] = 0x90;
packet[6] = 0x90;
packet[7] = 0xD0;
packet[8] = 0x01;
if(ata_packet(packet, sizeof(packet), ATA_MASTER, ATA_INTR_ON, &ai))
ata_dump_err(stdout, &ai.err);
else
printf("done\n");
printf("\n");
return 0;
}
download source (7th Mar 06: link now fixed)
download binary
usage instructions are in the binary zip. I've tested it only in win98 and I had to restart windows to get the drive recognised after running the program, but it worked and it should work in 2000/XP too. Although I really recommend using the Linux version if at all possible.
For the sake of completeness, I'd still like to confirm my theories about tray_status and the I/O port at (D98C)/(D98D). I've already confirmed that the register at (DC54) is a 16-bit counter by dumping it repeatedly (with the 0xE7 memory dump command) and observing that the 16-bit value decends with each read (cycling around to 0xFFFF as it hits 0). To test the I/O registers I'll need to be able to write to those registers as well as read from them.
It will almost certainly be useful to have a purely software method for writing to the MN103 address space for other hacks/investigations. As far as I know no such thing exists yet, or at least nobody is publishing it.
There is a debug command that allows you to poke bytes in memory above 0x80000000 (external RAM) (see djhuevo's post on xbh.net), but unfortunately I need to write to the address space below this limit. There is also a function that allows you to load bulk code/data into RAM starting at 0x80000000 (see SpenZerX's post on xbh.net). Either the poke RAM or bulk load to RAM commands could be used to upload our own MN103 code to RAM and then the Hitachi jump to RAM command (see DaveX's original post on the Hitachi vendor specific commands) can be used to execute this code (which could write to wherever we like).
The problem is that the jump to subroutine in RAM command (handler starts at 0x9002A725) requires (59E) bit 3 to be set or it fails.
ROM:0002A7A2 btst 8, (word_59E) ; is (59E) bit 3 set? ROM:0002A7A7 beq loc_2A7BA ; No, so bail
This bit isn't set by default in modeA or B. This bit is set within the bulk load to RAM command's handler (starting at 0x9002A5A1), perhaps to ensure that code has actually been written to RAM before the drive jumps.
ROM:0002A5DC bset 8, (word_59E) ; set (59E) bit 3
The petty annoyance is that the bulk write to RAM command is protected by a check against (5A5) bit 4 at the very start of the handler. If this bit is clear, then the command fails. again, this isn't set by default in ModeA or B.
ROM:0002A5A1 btst 0x10, (word_5A5) ; is (5A5) bit 4 set? ROM:0002A5A6 beq loc_2A5CC ; no, so branch (this exits the handler)
I can see one other place in the firmware where (5A5) bit 4 is set and one other place where (59E) bit 3 is set. Both of these exist within the ATAPI Mode Select(10) command handler. Which I have yet to trace.
Options:
* Exploit a bug in the firmware to de-rail a write or a call/jump/ret
to either write to our target location directly or execute our own code
that will write to our target location (the code can be uploaded
anywhere in RAM a byte at a time using the RAM poke command).
* Figure out the Mode Select(10) commands that will get either (5A5) bit 4 or (59E) bit 3 set.
A Mode Sense(10) dump will be helpful when tracing the Mode Select(10) handler. Here's code to dump the drive's complete Mode Parameter List.
/*
* dumps the Mode Parameter List containing all
* Mode Pages of the xbox 360 Hitachi DVD drive.
*
* author: Kevin East (SeventhSon)
* email: kev@kev.nu
* web: http://www.kev.nu/360/
* date: 3rd march 2006
* platform: linux
*
*/
#include <stdio.h>
#include <linux/cdrom.h>
#include <sys/ioctl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
void dump_hex(unsigned char *data, unsigned int len, unsigned int offset)
{
unsigned int i, j, mod;
char ascii[16];
for(i = 0; i < len; i++) {
mod = i % 16;
if(!(mod)) {
if(i) {
for(j = 0; j < 16; j++) {
if(ascii[j] >= 0x20 && ascii[j] <= 0x7E)
printf("%c", ascii[j]);
else
printf(".");
}
printf("\n");
}
printf("%08X: ", offset + i);
}
else if(i && !(i % 8))
printf("- ");
ascii[mod] = data[i];
printf("%02X ", data[i]);
}
if(!len)
printf("\n");
else {
len = 16 - (len % 16);
if(len != 16) {
if(len >= 8)
printf(" ");
for(mod++, i = 0; i < len; i++) {
ascii[mod + i] = ' ';
printf(" ");
}
}
for(j = 0; j < 16; j++) {
if(ascii[j] >= 0x20 && ascii[j] <= 0x7E)
printf("%c", ascii[j]);
else
printf(".");
}
printf("\n");
}
}
int main(int argc, char *argv[])
{
int fd;
struct cdrom_generic_command cgc;
struct request_sense sense;
unsigned char data[0x96 + 2];
if(argc != 2) {
printf("usage: msense device\n");
return 1;
}
if((fd = open(argv[1], O_RDONLY | O_NONBLOCK)) == -1) {
perror("open");
return 1;
}
memset(&cgc, 0, sizeof(cgc));
cgc.cmd[0] = 0x5A; // mode sense command
cgc.cmd[1] = 0x08; // DBD set to 1
cgc.cmd[2] = 0x3F; // return current current values for all mode pages
cgc.cmd[7] = 0xFF; // allocation length MSB
cgc.cmd[8] = 0xFF; // allocation length LSB
cgc.buffer = data;
cgc.buflen = sizeof(data);
cgc.sense = &sense;
cgc.data_direction = CGC_DATA_READ;
cgc.timeout = 15000;
if(ioctl(fd, CDROM_SEND_PACKET, &cgc) == -1) {
perror("ioctl");
printf("sense: %02X/%02X/%02X\n", sense.sense_key, sense.asc, sense.ascq);
}
else
dump_hex(data, sizeof(data), 0);
printf("\n");
close(fd);
return 0;
}
This program only works with the drive in modeB. The drive rejects a standard Mode Sense(10) command under modeA. There are probably vendor specific parts of the packet that need to be set in order for Mode Sense(10) to work in modeA, but I don't know what they are because I haven't looked at the Mode Sense(10) handler yet. Here's the ouput from my drive.
00000000: 00 96 70 00 00 00 00 00 - 01 0A 00 08 14 08 14 00 ..p............. 00000010: 00 00 00 00 20 0A 02 00 - 00 00 00 00 00 00 00 00 .... ........... 00000020: 3B 30 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 ;0.............. 00000030: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 ................ 00000040: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 ................ 00000050: 00 00 3C 16 00 00 00 00 - 00 00 00 00 00 00 00 00 ..<............. 00000060: 00 00 00 00 00 00 00 00 - 00 00 3D 0A 04 00 00 00 ..........=..... 00000070: 00 00 00 00 00 00 3E 20 - 00 00 00 E1 03 00 00 00 ......> ........ 00000080: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 ................ 00000090: 00 00 00 00 00 00 00 00 ........
Here's a breakdown of the above data.
mode param header: 00 96 : mode data length (big endian) 70 : medium type code (70 = no disc, 42 = 360 game) 00 00 00 : reserved 00 00 : block descriptor length mode pages: 01 : page code 1 = Read/Write Error Recovery Parameters, ps = false 0A : param length 00 08 14 08 14 00 00 00 00 00 : mode params (see inf-8090) 20 : page code 20 = vendor specific, ps = false 0A : param length 02 00 00 00 00 00 00 00 00 00 : mode params 3B : page code 3B = vendor specific, ps = false 30 : param length 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 : mode params 3C : page code 3C = vendor specific, ps = false 16 : param length 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 : mode params 3D : page code 3D = vendor specific, ps = false 0A : param length 04 00 00 00 00 00 00 00 00 00 : mode params 3E : page code 3E = vendor specific, ps = false 20 : param length 00 00 00 E1 03 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 : mode params
For the record, Mode Select(10) works in ModeA. As a test, I have successfully modified the Read Retry Count parameter of the Read/Write Error Recovery Parameters mode page in both modes (see inf-8090).
Okay, I have started tracing the Mode Select(10) handler (which starts at 0x9002643F). My comments for this handler are pretty indecipherable, so the following pseudo-code describes the top level of the handler.
if(!sub_1B130()) // not looked into this sub routine yet
exit;
if ((59E) bit 3 is set and packet[3] == 'H' and packet[4] == 'L') // [H]itachi [L]G?
call 0x80000000; // call subroutine in RAM
if ((59E) bit 4 is set (settable via a debug command)) {
if(PLL <= 0x400) // PLL = Parameter Length List (from the ATAPI Mode Select packet)
call sub_1B66D with D0 = PLL, A0 = 0x63C, A1 = 0x9003012A;
exit;
}
if (59E) bit 3 is set {
call sub_1B66D with D0 = PLL, A0 = 0x63C, A1 = 0x90026FC7;
exit;
}
else if(PLL <= 0x78) {
call sub_1B66D D0 = PLL, A0 = 0x63C, A1 = 0x900265C5;
enter a loop; // I haven't looked at this yet
exit;
}
There are 4 main paths execution can take. All of them end up with a call to sub_1B66D. The arguments to sub_1B66D are the PLL (parameter list length), 0x63C (address of input mode parameter list, page code is at (644)) and an address that differs for each of the 4 calls. The 4 locations contain sub-routines and it's a fair assumption that the specified subroutine gets called sometime after the Mode Select(10) handler returns.
Under normal conditions (and with a PLL <= 0x78), execution takes the call to sub_1B66D with A1 set to 0x900265C5 (the final call in the above pseudo code). So let's disassemble the subroutine at 0x900265C5. Right at the start of the routine is the following code.
ROM:000265C8 call sub_1A1D5, [A2], 0b1100 ; set (24AC) and (24B4) based on (5A4) bit 4 ROM:000265CF call sub_1A27D, [A2], 0xC ; set (626) based on (6BF) and set (6BF) to 1 ROM:000265D6 movbu (word_643+1), D0 ; D0 = page code ROM:000265D9 cmp 9, D0 ; is page code 9? ROM:000265DB bne loc_265F2 ; no, so branch ROM:000265DD btst 0x40, (word_598) ! '@' ; are we in modeB? ROM:000265E2 beq loc_26646 ; no, so bail ROM:000265E4 btst 0x10, (word_598) ; is (598) bit 4 clear? ROM:000265E9 bne loc_26646 ; no, so bail ROM:000265EB bset 0x10, (unk_5A5) ; if all requirements met, then it set (5A5) bit 4 ROM:000265F0 bra loc_2664B ; return
(5A5) bit 4 is the bit that restricts the bulk write to RAM command. Looks like a Mode Select(10) command with a page code 9 will set this bit for us. But there is a problem, we must be in modeB and (598) bit 4 must be clear. The software method that I wrote about in my last update can get us into modeB, but that handler also *sets* (598) bit 4, which will cause the above code to fail. If we go back and look at the code that I suspect sets modeB based on tray_status (0x90006669) then we can see that it *does not* set (598) bit 4. This is a crucial difference between the software and hardware methods of invoking modeB. Here's code that will send a Mode Select(10) command that should set (5A5) bit 4.
/*
* Sets bit 4 at address 0x5A5 within the address
* space of the MN103 microcontroller within the
* Hitachi-LG xbox 360 dvd drive
*
* author: Kevin East (SeventhSon)
* email: kev@kev.nu
* web: http://www.kev.nu/360/
* date: 7th March 2006
* platform: linux
*
*/
#include <stdio.h>
#include <linux/cdrom.h>
#include <sys/ioctl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
int main(int argc, char *argv[])
{
int fd;
struct cdrom_generic_command cgc;
struct request_sense sense;
unsigned char data[10];
if(argc != 2) {
printf("usage: set_5A5_4 device\n");
return 1;
}
if((fd = open(argv[1], O_RDONLY | O_NONBLOCK)) == -1) {
perror("open");
return 1;
}
memset(&cgc, 0, sizeof(cgc));
cgc.cmd[0] = 0x55; // Mode Select(10)
cgc.cmd[1] = 0x10;
cgc.cmd[8] = 0x0A; // Parameter List Length
memset(data, 0, sizeof(data));
data[1] = 0x08;
data[8] = 0x09; // page code
cgc.buffer = data;
cgc.buflen = 10;
cgc.sense = &sense;
cgc.data_direction = CGC_DATA_WRITE;
cgc.timeout = 15000;
if(ioctl(fd, CDROM_SEND_PACKET, &cgc) == -1) {
perror("ioctl");
printf("sense: %02X/%02X/%02X\n", sense.sense_key, sense.asc, sense.ascq);
}
else
printf("done\n");
printf("\n");
close(fd);
return 0;
}
After running the above program I examined (5A5) bit 4 using the Hitachi 0xE7 memory dump command. As suspected, it fails in ModeA, fails in software induced modeB and succeeds in hardware induced modeB. Which sucks because the whole idea is that no hardware tinkering should be required to write to the MN103 address space.
Continuing with the disassembly trace at 0x900265F2 (the branch if page code is not 9) we get the following.
ROM:000265F2 loc_265F2: ! CODE XREF: sub_265C8+13j ROM:000265F2 movbu (word_643+1), D0 ; D0 = page code ROM:000265F5 and 0x3F, D0 ! '?' ; zero bits 6 and 7 of (644) (PS and reserved bits) ROM:000265F8 cmp 1, D0 ; page code 1 = Read/Write Error Recovery Parameters ROM:000265FA beq loc_26612 ROM:000265FC cmp 0x20, D0 ! ' ' ; page code 20 = vendor specific ROM:000265FE beq loc_2661A ROM:00026600 cmp 0x3B, D0 ! ';' ; page code 3B = vendor specific ROM:00026602 beq loc_26622 ROM:00026604 cmp 0x3C, D0 ! '<' ; page code 3C = vendor specific ROM:00026606 beq loc_2662A ROM:00026608 cmp 0x3E, D0 ! '>' ; page code 3D = vendor specific ROM:0002660A beq loc_26632 ROM:0002660C cmp 0x3A, D0 ! ':' ; page code 3A = vendor specific ROM:0002660E beq loc_26646 ROM:00026610 bra loc_2663A ; page code does not match any of the above ROM:00026612 ! --------------------------------------------------------------------------- ROM:00026612 ROM:00026612 loc_26612: ! CODE XREF: sub_265C8+32j ROM:00026612 call sub_26B40, [D2], 8 ROM:00026617 ret [], 4 ROM:0002661A ! --------------------------------------------------------------------------- ROM:0002661A ROM:0002661A loc_2661A: ! CODE XREF: sub_265C8+36j ROM:0002661A call sub_269BE, [], 4 ROM:0002661F ret [], 4 ROM:00026622 ! --------------------------------------------------------------------------- ROM:00026622 ROM:00026622 loc_26622: ! CODE XREF: sub_265C8+3Aj ROM:00026622 call sub_28239, [D2,D3,A2], 0x10 ROM:00026627 ret [], 4 ROM:0002662A ! --------------------------------------------------------------------------- ROM:0002662A ROM:0002662A loc_2662A: ! CODE XREF: sub_265C8+3Ej ROM:0002662A call sub_281D6, [], 4 ROM:0002662F ret [], 4 ROM:00026632 ! --------------------------------------------------------------------------- ROM:00026632 ROM:00026632 loc_26632: ! CODE XREF: sub_265C8+42j ROM:00026632 call sub_2709B, [A2], 8 ROM:00026637 ret [], 4 ROM:0002663A ! --------------------------------------------------------------------------- ROM:0002663A ROM:0002663A loc_2663A: ! CODE XREF: sub_265C8+48j ROM:0002663A call sub_1B53B, [], 0 ! get modeA/B flag ROM:00026641 extb D0 ROM:00026642 cmp 0, D0 ! Are we in modeB? ROM:00026644 bne loc_26653 ! yes, so branch (modeA returns) ROM:00026646 ROM:00026646 loc_26646: ! CODE XREF: sub_265C8+1Aj ROM:00026646 ! sub_265C8+21j ... ROM:00026646 mov 0xB, D0 ROM:00026648 movbu D0, (word_5D8) ROM:0002664B ROM:0002664B loc_2664B: ! CODE XREF: sub_265C8+28j ROM:0002664B ! sub_265C8+DDj ... ROM:0002664B mov 4, D0 ROM:0002664D movbu D0, (word_6D8+1) ROM:00026650 ret [], 4
This code handles the page codes reported by Mode Sense(10) earlier (1, 20, 3A, 3B, 3C and 3E). If the page code does not match any of them, then the routine returns if we are in modeA. However, if we are in modeB then we branch to the following code.
ROM:00026653 loc_26653: ! CODE XREF: sub_265C8+7Cj ROM:00026653 movbu (word_5C0), D0 ; D0 = paramter list length ROM:00026656 cmp 8, D0 ; is the PLL 8? (i.e. mode parameter header only, no mode pages) ROM:00026658 beq loc_266A7 ; yes, so branch past the mode page transfer code ROM:0002665A movbu (word_5C0), D0 ROM:0002665D cmp 8, D0 ROM:0002665F blt loc_2669B ; bail out if PLL is less than 8 ROM:00026661 movbu (word_5C0), D0 ROM:00026664 add 0xF8, D0 ! '°' ROM:00026666 movbu D0, (word_5C0) ; PLL -= 2 (to account for length field) ROM:00026669 clr D0 ROM:0002666A movbu D0, (word_5F4) ROM:0002666D movbu D0, (word_5F4+1) ROM:00026670 bra loc_2667D ; branch into transfer loop ROM:00026672 ! --------------------------------------------------------------------------- ROM:00026672 ROM:00026672 loc_26672: ! CODE XREF: sub_265C8+BAj ROM:00026672 call sub_266AE, [D2,D3], 0xC ; calls one of nine functions based on the page code ROM:00026677 mov D0, D1 ROM:00026678 extbu D1 ROM:00026679 cmp 0, D1 ROM:0002667B bne loc_26684 ; break out of loop if error occured ROM:0002667D ROM:0002667D loc_2667D: ; transfer loop entry point ROM:0002667D movbu (word_5C0), D1 ! D1 = remaining PLL ROM:00026680 cmp 0, D1 ROM:00026682 bne loc_26672 ; loop until all data tranfered ROM:00026684 ROM:00026684 loc_26684: ; loop drops out here ROM:00026684 extbu D0 ROM:00026685 cmp 3, D0 ROM:00026687 beq loc_26693 ; bail if error ROM:00026689 cmp 1, D0 ROM:0002668B beq loc_26697 ; bail if error ROM:0002668D cmp 0, D0 ROM:0002668F beq loc_266A7 ; successful transfer, so branch ROM:00026691 bra loc_2669B ; bail if error ROM:00026693 ! --------------------------------------------------------------------------- ROM:00026693 ROM:00026693 loc_26693: ! CODE XREF: sub_265C8+BFj ROM:00026693 mov 0x10, D0 ROM:00026695 bra loc_2669D ROM:00026697 ! --------------------------------------------------------------------------- ROM:00026697 ROM:00026697 loc_26697: ! CODE XREF: sub_265C8+C3j ROM:00026697 mov 0xA, D0 ROM:00026699 bra loc_2669D ROM:0002669B ! --------------------------------------------------------------------------- ROM:0002669B ROM:0002669B loc_2669B: ! CODE XREF: sub_265C8+97j ROM:0002669B ! sub_265C8+C9j ROM:0002669B mov 0xB, D0 ROM:0002669D ROM:0002669D loc_2669D: ! CODE XREF: sub_265C8+CDj ROM:0002669D ! sub_265C8+D1j ROM:0002669D movbu D0, (word_5D8) ROM:000266A0 bclr 4, (word_59C+1) ROM:000266A5 bra loc_2664B ROM:000266A7 ! --------------------------------------------------------------------------- ROM:000266A7 ROM:000266A7 loc_266A7: ! CODE XREF: sub_265C8+90j ROM:000266A7 ! sub_265C8+C7j ROM:000266A7 call sub_26C3B, [D2], 8 ; this function sets (59E) bit 3 :-) ROM:000266AC bra loc_2664B ; this branch ends the routine
The subroutine sub_266AE() that gets called within each cycle of the transfer loop uses two tables both containing 9 entries. One (at 0x9003D5EC) contains 9 8bit page codes and the other (at 0x9003D5F8) contains 9 32bit subroutine addresses. Each subroutine corresponds to the page code at the same position in the other table. sub_266AE calls the correct handler routine for the specified page code using these tables.
page code table: ROM:0003D5EC .byte 0 ! vendor specific ROM:0003D5ED .byte 1 ! Read/Write error recovery params ROM:0003D5EE .byte 0xD ! reserved ROM:0003D5EF .byte 0xE ! CD Audio control ROM:0003D5F0 .byte 0x2A ! * ! C/DVD capabilities and mechanical status ROM:0003D5F1 .byte 0x18 ! reserved ROM:0003D5F2 .byte 0x1A ! power condition ROM:0003D5F3 .byte 0x1D ! Timeout and protect ROM:0003D5F4 .byte 0x21 ! ! ! vendor specific corresponding subroutine table: ROM:0003D5F8 .long 0x900266E6 ROM:0003D5FC .long 0x90026715 ROM:0003D600 .long 0x90026795 ROM:0003D604 .long 0x900267E9 ROM:0003D608 .long 0x90026875 ROM:0003D60C .long 0x90026894 ROM:0003D610 .long 0x900268B3 ROM:0003D614 .long 0x90026929 ROM:0003D618 .long 0x90026982
In modeB the drive supports a lot more page codes than Mode Sense(10) would have us believe. Including a lot of the standard ones that you'd expect on C/DVD drive. For future reference here's a list of page codes the Hitachi-LG drive seems to support via Mode Select(10). There may be more, I haven't traced all of the Mode Select(10) handler yet.
In modeA ======== 0x01 = Read/Write Error Recovery Parameters 0x20 = vendor specific 0x3A = vendor specific 0x3B = vendor specific 0x3C = vendor specific 0x3D = vendor specific In modeB only ============= 0x00 = vendor specific 0x09 = reserved (used by the 360 drive to set (5A5) bit 4) [hardware induced modeB only] 0x0D = reserved 0x0E = CD Audio control 0x18 = reserved 0x1A = power condition 0x1D = timeout and protect 0x21 = vendor specific 0x2A = C/DVD capabilities and mechanical status
You'll notice that at the end of the above code sub_26C3B() is called. This routine sets (59E) bit 3, which is the bit that we need to set in order to get the Hitachi jump to RAM command to working. sub26C3B() gets called after the successful transfer of any mode page that isn't 0x01, 0x20, 0x3A, 0x3B, 0x3C, 0x3E or 0x09. It even gets called if we do not supply any mode pages with the Mode Select(10) command. So, in theory, a Mode Select(10) command sent to the drive in modeB followed by an 8 byte mode parameter list (the header only) should set (59E) bit 3. This should even work with software induced modeB. Here's some code to do this.
/*
* Sets bit 3 at address 0x59E within the address
* space of the MN103 microcontroller within the
* Hitachi-LG xbox 360 dvd drive
*
* author: Kevin East (SeventhSon)
* email: kev@kev.nu
* web: http://www.kev.nu/360/
* date: 7th March 2006
* platform: linux
*
*/
#include <stdio.h>
#include <linux/cdrom.h>
#include <sys/ioctl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
int main(int argc, char *argv[])
{
int fd;
struct cdrom_generic_command cgc;
struct request_sense sense;
unsigned char data[8];
if(argc != 2) {
printf("usage: set_59E_3 device\n");
return 1;
}
if((fd = open(argv[1], O_RDONLY | O_NONBLOCK)) == -1) {
perror("open");
return 1;
}
memset(&cgc, 0, sizeof(cgc));
cgc.cmd[0] = 0x55; // Mode Select(10)
cgc.cmd[1] = 0x10;
cgc.cmd[8] = 0x08; // Paramete List Length
memset(data, 0, sizeof(data));
data[1] = 0x08;
cgc.buffer = data;
cgc.buflen = 8;
cgc.sense = &sense;
cgc.data_direction = CGC_DATA_WRITE;
cgc.timeout = 15000;
if(ioctl(fd, CDROM_SEND_PACKET, &cgc) == -1) {
perror("ioctl");
printf("sense: %02X/%02X/%02X\n", sense.sense_key, sense.asc, sense.ascq);
}
else
printf("done\n");
printf("\n");
close(fd);
return 0;
}
It works! Now there's nothing to stop us from writing to anywhere in the MN103 address space without so much as removing the DVD case. The follwing process should achieve this.
1. Put the drive into modeB using the Hitachi 0xE7 command that sets (598) bit 6.
2. Upload our code (which, when executed, will write to anywhere we like) one byte at a time using the 0xE7 RAM poke command.
3. Set (59E) bit 3 using Mode Select(10) as described above.
4. Force the drive to jump to our code using the 0xE7 jump to RAM command (which will succeed because we have set (59E) bit 3)
Hmmm. I'm having trouble with step 4. I know my code is getting uploaded correctly and executed, but the drive is hanging afterwards. I see that the Hitachi jump to RAM command handler expects a 32-bit value at offset 8 in the ATAPI command packet. I have no idea what this is supposed to be, the code that I have so far traced simply checks that this value is between 1 and 0x60000. Here's the dissasembly
ROM:0002A728 mov 0x5C0, A0 ; A0 = address of 32-bit value at packet[8] ... snip ... ROM:0002A794 call sub_33A84, [], 8 ; D0 = 32 bit value at packet[8] in little endian ROM:0002A79B mov D0, (word_924) ; store it at (924) ROM:0002A79E cmp 0, D0 ; is 32-bit value zero? ROM:0002A7A0 beq loc_2A7BF ; yes, exit (without error) ROM:0002A7A2 btst 8, (word_59E) ; no, so is (59E) bit 3 set? ROM:0002A7A7 beq loc_2A7BA ; no, so exit (with error) ROM:0002A7A9 cmp 0x60000, D0 ; yes, so is 32-bit value > 0x60000? ROM:0002A7AF bhi loc_2A7BA ; yes, so exit (with error) ROM:0002A7B1 call sub_2A7C3, [], 4 ; the call to RAM is within this short function ROM:0002A7B6 mov 1, D0 ROM:0002A7B8 bra locret_2A7C0 ; all done, return ROM:0002A7BA ! --------------------------------------------------------------------------- ROM:0002A7BA ROM:0002A7BA loc_2A7BA: ! CODE XREF: sub_2A725+82 j ROM:0002A7BA ! sub_2A725+8A j ROM:0002A7BA mov 0xA, D0 ROM:0002A7BC movbu D0, (word_5D8) ROM:0002A7BF ROM:0002A7BF loc_2A7BF: ! CODE XREF: sub_2A725+7B j ROM:0002A7BF clr D0 ROM:0002A7C0 ROM:0002A7C0 locret_2A7C0: ! CODE XREF: sub_2A725+93 j ROM:0002A7C0 ret [], 4
I think that it is a transfer length field of some kind. This is because the command fails with an error if it is > 0x60000 but completes with no error (although it doesn't jump to RAM) if this value is zero. This is the specified behaviour for many ATAPI transfer length fields, a zero length transfer request is not to be considered an error. But what is to be transfered? Perhaps the custom code uploaded to RAM is supposed to set up this transfer itself to provide completely custom debug data? Who knows? If the drive is expecting a transfer of data after jumping to RAM, then this could explain the hang.
Fortunately there's more than one way to skin this particular cat. If you take another look at my top level Mode Select(10) pseudo-code, then you'll see that it's possible to jump to a routine at 0x80000000 using Mode Select(10). This requires (59E) bit 3 to be set before issuing a Mode Select(10) command with a couple of reserved bytes set to 'H' and 'L' in the ATAPI packet. Both of these things are acheivable. Once our RAM routine has returned, then the Mode Select(10) handler continues to execute, but because (59E) bit 3 is set this time, the execution takes us to the subroutine at 0x90026FC7 instead of the normal one at 0x900265C5 (see the pseudo-code). Here's the start of the routine at 0x90026FC7
ROM:00026FC7 movm [D2,D3,A2,A3], (SP) ROM:00026FC9 add 0xF0, SP ! '' ROM:00026FCC btst 0x10, (unk_5A5) ; is (5A5) bit 4 set? ROM:00026FD1 bne loc_26FDB ; yes, so continue ROM:00026FD3 mov 0xB, D0 ; no, so bail out with error ROM:00026FD5 movbu D0, (word_5D8) ROM:00026FD8 jmp loc_27093 ... snip ... ROM:00027093 loc_27093: ! CODE XREF: ROM:00026FD8 j ROM:00027093 ! ROM:00026FF9 j ... ROM:00027093 mov 4, D0 ROM:00027095 movbu D0, (word_6D8+1) ROM:00027098 ret [D2,D3,A2,A3], 0x20 ! ' '
Because (5A5) bit 4 isn't set under normal conditions, the Mode Select(10) call will fail. Fortunately, this is after it has executed the routine in RAM, so it doesn't matter.
The new plan:
1. Put the drive into modeB using the Hitachi 0xE7 command that sets (598) bit 6.
2. Upload our code (which, when executed, will write to anywhere we like) one byte at a time using the 0xE7 RAM poke command.
3. Set (59E) bit 3 using Mode Select(10) as described above.
4. Force the drive to jump to our code using a Mode Select(10) command
with 'H' and 'L' set at packet[3] and packet[4]. This will execute the
routine in RAM and then fail as described above.
This time it worked! The following code is a Linux app that will peek or poke any byte in the MN103 address space.
/*
* Reads or writes a single byte from/to
* anywhere within the address space of
* the MN103 microcontroller inside of
* the Hitachi-LG Xbox360 drive.
*
* author: Kevin East (SeventhSon)
* email: kev@kev.nu
* web: http://www.kev.nu/360/
* date: 7th March 2006
* platform: linux
*
*/
#include <stdio.h>
#include <linux/cdrom.h>
#include <sys/ioctl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#define USAGE "usage: pp device address_in_hex peek\n pp device address_in_hex poke value_in_hex\n\n"
#define MODE_PEEK 0
#define MODE_POKE 1
/*
mov 0x000000??,D1
movbu D1,(0x????????)
rets
*/
unsigned char code[] = {0xFC,0xCD,0x00,0x00,0x00,0x00,0xFC,0x86,0x00,0x00,0x00,0x00,0xF0,0xFC};
int hex_atoi(void *dest, char *src, int dest_len)
{
int src_last, i, j;
unsigned char val;
memset(dest, 0, dest_len);
src_last = strlen(src) - 1;
for(i = src_last; i >= 0; i--) {
if(src[i] >= '0' && src[i] <= '9')
val = src[i] - 0x30;
else if(src[i] >= 'a' && src[i] <= 'f')
val = (src[i] - 0x60) + 9;
else if(src[i] >= 'A' && src[i] <= 'F')
val = (src[i] - 0x40) + 9;
else
return 1; // invalid hex digit
j = src_last - i;
if(j & 1)
val <<= 4;
j >>= 1;
if(j >= dest_len || j < 0)
break;
((unsigned char *)dest)[j] |= val;
}
return 0;
}
int main(int argc, char *argv[])
{
int fd;
struct cdrom_generic_command cgc;
struct request_sense sense;
unsigned int addr, mode, i;
unsigned char val, param_list[8];
if(argc < 4) {
printf(USAGE);
return 1;
}
if(hex_atoi(&addr, argv[2], sizeof(addr))) {
printf("error: invalid address %s\n", argv[2]);
printf(USAGE);
return 1;
}
if(!strcmp(argv[3], "peek")) {
mode = MODE_PEEK;
val = 0;
}
else if(!strcmp(argv[3], "poke")) {
if(argc < 5) {
printf(USAGE);
return 1;
}
mode = MODE_POKE;
if(hex_atoi(&val, argv[4], sizeof(val))) {
printf("error: invalid poke value %s\n", argv[4]);
printf(USAGE);
return 1;
}
}
else {
printf("error: invalid mode %s\n", argv[3]);
printf(USAGE);
return 1;
}
if((fd = open(argv[1], O_RDONLY | O_NONBLOCK)) == -1) {
perror("open");
return 1;
}
if(mode == MODE_PEEK) {
// Hitachi read memory command
memset(cgc.cmd, 0, sizeof(cgc.cmd));
cgc.cmd[0] = 0xE7;
cgc.cmd[1] = 0x48;
cgc.cmd[2] = 0x49;
cgc.cmd[3] = 0x54;
cgc.cmd[4] = 0x01;
cgc.cmd[6] = (unsigned char)((addr & 0xFF000000) >> 24); // address MSB
cgc.cmd[7] = (unsigned char)((addr & 0x00FF0000) >> 16); // address
cgc.cmd[8] = (unsigned char)((addr & 0x0000FF00) >> 8); // address
cgc.cmd[9] = (unsigned char)(addr & 0x000000FF); // address LSB
cgc.cmd[11] = 1; // length LSB
cgc.buffer = &val;
cgc.buflen = 1;
cgc.sense = &sense;
cgc.data_direction = CGC_DATA_READ;
cgc.timeout = 15000;
if(ioctl(fd, CDROM_SEND_PACKET, &cgc) == -1) {
perror("ioctl");
printf("Hitachi read memory command failed (sense: %02X/%02X/%02X)\n", sense.sense_key, sense.asc, sense.ascq);
}
else
printf("0x%02X\n", val);
}
else { // mode == MODE_POKE
// initiate modeB
memset(&cgc, 0, sizeof(cgc));
cgc.cmd[0] = 0xE7;
cgc.cmd[1] = 0x48;
cgc.cmd[2] = 0x49;
cgc.cmd[3] = 0x54;
cgc.cmd[4] = 0x30;
cgc.cmd[5] = 0x90;
cgc.cmd[6] = 0x90;
cgc.cmd[7] = 0xD0;
cgc.cmd[8] = 0x01;
cgc.sense = &sense;
cgc.data_direction = CGC_DATA_NONE;
cgc.timeout = 15000;
if(ioctl(fd, CDROM_SEND_PACKET, &cgc) == -1) {
perror("ioctl");
printf("failed to initiate modeB (sense: %02X/%02X/%02X)\n", sense.sense_key, sense.asc, sense.ascq);
}
else {
*((unsigned int *)&code[8]) = addr;
*((unsigned int *)&code[2]) = val;
// upload code a byte at a time using RAM poke command
for(i = 0; i < sizeof(code); i++) {
// Hitachi poke RAM command
memset(cgc.cmd, 0, sizeof(cgc.cmd));
cgc.cmd[0] = 0xE7;
cgc.cmd[1] = 0x48;
cgc.cmd[2] = 0x49;
cgc.cmd[3] = 0x54;
cgc.cmd[4] = 0xCC;
cgc.cmd[5] = code[i];
cgc.cmd[8] = (unsigned char)(((0x80000000 + i) & 0xFF000000) >> 24); // address MSB
cgc.cmd[9] = (unsigned char)(((0x80000000 + i) & 0x00FF0000) >> 16); // address
cgc.cmd[10] = (unsigned char)(((0x80000000 + i) & 0x0000FF00) >> 8); // address
cgc.cmd[11] = (unsigned char)((0x80000000 + i) & 0x000000FF); // address LSB
cgc.sense = &sense;
cgc.data_direction = CGC_DATA_NONE;
cgc.timeout = 15000;
if(ioctl(fd, CDROM_SEND_PACKET, &cgc) == -1) {
perror("ioctl");
printf("Hitachi poke RAM command failed on byte %u (sense: %02X/%02X/%02X)\n", i, sense.sense_key, sense.asc, sense.ascq);
close(fd);
return 1;
}
}
// set (59E) bit 3 via Mode Select(10)
memset(cgc.cmd, 0, sizeof(cgc.cmd));
cgc.cmd[0] = 0x55;
cgc.cmd[1] = 0x10;
cgc.cmd[8] = 0x08;
memset(param_list, 0, sizeof(param_list));
param_list[1] = 6;
cgc.buffer = param_list;
cgc.buflen = 8;
cgc.sense = &sense;
cgc.data_direction = CGC_DATA_WRITE;
cgc.timeout = 15000;
if(ioctl(fd, CDROM_SEND_PACKET, &cgc) == -1) {
perror("ioctl");
printf("set (59E) bit 3 via Mode Select(10) failed (sense: %02X/%02X/%02X)\n", sense.sense_key, sense.asc, sense.ascq);
}
else {
// jump to RAM routine via Mode Select(10)
memset(cgc.cmd, 0, sizeof(cgc.cmd));
cgc.cmd[0] = 0x55;
cgc.cmd[1] = 0x10;
cgc.cmd[3] = 0x48; // 'H'
cgc.cmd[4] = 0x4C; // 'L'
cgc.cmd[8] = 0x08;
memset(param_list, 0, sizeof(param_list));
param_list[1] = 6;
cgc.buffer = param_list;
cgc.buflen = 8;
cgc.sense = &sense;
cgc.data_direction = CGC_DATA_WRITE;
cgc.timeout = 15000;
if(ioctl(fd, CDROM_SEND_PACKET, &cgc) == -1)
printf("done\n");
else
printf("this shouldn't happen\n");
}
}
}
close(fd);
printf("\n");
return 0;
}
Another useful tool. The following program will upload and execute any piece of MN103 code you supply.
/*
* uploads any given MN103 code to
* the Hitachi-LG Xbox360 drive and
* causes the drive to execute it.
*
* author: Kevin East (SeventhSon)
* email: kev@kev.nu
* web: http://www.kev.nu/360/
* date: 7th March 2006
* platform: linux
*
*/
#include <stdio.h>
#include <linux/cdrom.h>
#include <sys/ioctl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#define USAGE "usage: execcode device binary_code_file\n\n"
int main(int argc, char *argv[])
{
int fd;
struct cdrom_generic_command cgc;
struct request_sense sense;
unsigned int i, len;
unsigned char param_list[8];
FILE *fptr;
if(argc < 3) {
printf(USAGE);
return 1;
}
if((fd = open(argv[1], O_RDONLY | O_NONBLOCK)) == -1) {
perror("open");
return 1;
}
if(!(fptr = fopen(argv[2], "rb"))) {
close(fd);
perror("fopen");
return 1;
}
// initiate modeB
memset(&cgc, 0, sizeof(cgc));
cgc.cmd[0] = 0xE7;
cgc.cmd[1] = 0x48;
cgc.cmd[2] = 0x49;
cgc.cmd[3] = 0x54;
cgc.cmd[4] = 0x30;
cgc.cmd[5] = 0x90;
cgc.cmd[6] = 0x90;
cgc.cmd[7] = 0xD0;
cgc.cmd[8] = 0x01;
cgc.sense = &sense;
cgc.data_direction = CGC_DATA_NONE;
cgc.timeout = 15000;
if(ioctl(fd, CDROM_SEND_PACKET, &cgc) == -1) {
perror("ioctl");
printf("failed to initiate modeB (sense: %02X/%02X/%02X)\n", sense.sense_key, sense.asc, sense.ascq);
}
else {
if(fseek(fptr, 0, SEEK_END) == -1) {
close(fd);
fclose(fptr);
perror("fseek");
return 1;
}
if((len = ftell(fptr)) == -1) {
close(fd);
fclose(fptr);
perror("ftell");
return 1;
}
if(fseek(fptr, 0, SEEK_SET) == -1) {
close(fd);
fclose(fptr);
perror("fseek");
return 1;
}
// upload code a byte at a time using RAM poke command
for(i = 0; i < len; i++) {
// Hitachi poke RAM command
memset(cgc.cmd, 0, sizeof(cgc.cmd));
cgc.cmd[0] = 0xE7;
cgc.cmd[1] = 0x48;
cgc.cmd[2] = 0x49;
cgc.cmd[3] = 0x54;
cgc.cmd[4] = 0xCC;
cgc.cmd[5] = (unsigned char)getc(fptr);
cgc.cmd[8] = (unsigned char)(((0x80000000 + i) & 0xFF000000) >> 24); // address MSB
cgc.cmd[9] = (unsigned char)(((0x80000000 + i) & 0x00FF0000) >> 16); // address
cgc.cmd[10] = (unsigned char)(((0x80000000 + i) & 0x0000FF00) >> 8); // address
cgc.cmd[11] = (unsigned char)((0x80000000 + i) & 0x000000FF); // address LSB
cgc.sense = &sense;
cgc.data_direction = CGC_DATA_NONE;
cgc.timeout = 15000;
if(ioctl(fd, CDROM_SEND_PACKET, &cgc) == -1) {
perror("ioctl");
printf("Hitachi poke RAM command failed on byte %u (sense: %02X/%02X/%02X)\n", i, sense.sense_key, sense.asc, sense.ascq);
close(fd);
fclose(fptr);
return 1;
}
}
// set (59E) bit 3 via Mode Select(10)
memset(cgc.cmd, 0, sizeof(cgc.cmd));
cgc.cmd[0] = 0x55;
cgc.cmd[1] = 0x10;
cgc.cmd[8] = 0x08;
memset(param_list, 0, sizeof(param_list));
param_list[1] = 6;
cgc.buffer = param_list;
cgc.buflen = 8;
cgc.sense = &sense;
cgc.data_direction = CGC_DATA_WRITE;
cgc.timeout = 15000;
if(ioctl(fd, CDROM_SEND_PACKET, &cgc) == -1) {
perror("ioctl");
printf("set (59E) bit 3 via Mode Select(10) failed (sense: %02X/%02X/%02X)\n", sense.sense_key, sense.asc, sense.ascq);
}
else {
// jump to RAM routine via Mode Select(10)
memset(cgc.cmd, 0, sizeof(cgc.cmd));
cgc.cmd[0] = 0x55;
cgc.cmd[1] = 0x10;
cgc.cmd[3] = 0x48; // 'H'
cgc.cmd[4] = 0x4C; // 'L'
cgc.cmd[8] = 0x08;
memset(param_list, 0, sizeof(param_list));
param_list[1] = 6;
cgc.buffer = param_list;
cgc.buflen = 8;
cgc.sense = &sense;
cgc.data_direction = CGC_DATA_WRITE;
cgc.timeout = 15000;
if(ioctl(fd, CDROM_SEND_PACKET, &cgc) == -1)
printf("done\n");
else
printf("this shouldn't happen\n");
}
}
close(fd);
fclose(fptr);
printf("\n");
return 0;
}
WARNING: At the time of writing, both of the above programs are criminally under-tested. The former has only been tested with a few pokes followed by peeks to RAM and a couple of lower memory locations like (5A5). The latter program has only been tested once with a very simple (14 byte) write-to-RAM routine. Also, I do not know enough about all of the drive's state flags to be sure that these programs will leave the drive in a stable state.
The next step is to use the above programs to confirm/deny my speculation regarding tray_status and potential I/O port registers (D98C) and (D98D)...
I was nearly right about registers (D98C) and (D98D).
(D98C) bit 0 = tray_status output (1 = +3.3V, 0 = 0V)
(D98C) bit 4 = tray_status direction (1 = input, 0 = output)
(D98D) bit 4 = tray_status input (1 = +3.3V, 0 = 0V)
Examples using the peek poke program that I posted in yesterday's update.
tray_status as an input: # ./pp /dev/hdc D98C peek 0x20 # ./pp /dev/hdc D98C poke 30 done # ./pp /dev/hdc D98C peek 0x30 (I pulled tray_status high here) # ./pp /dev/hdc D98D peek 0x30 (I pulled tray_status low here) # ./pp /dev/hdc D98D peek 0x20
tray_status as an output: # ./pp /dev/hdc D98C peek 0x30 # ./pp /dev/hdc D98C poke 20 done # ./pp /dev/hdc D98C peek 0x20 # ./pp /dev/hdc D98C poke 21 done (tray_status was measured at +3.3V here) # ./pp /dev/hdc D98C poke 20 done (tray_status was measured at 0V here)
So that's the tray_status/modeB mystery finally solved. Now I need a new project.
I've had a request from a fellow hacker to port my pp.c and codeexec.c tools to windows. So here they are. I've also ported my memdump.c tool and modified both the windows and linux versions to accept hexadecimal input parameters (the old decimal memdump was annoying).
Note that these tools work via your operating system's driver. So you'll need to get your OS to detect the drive before they will work (Linux users: simply ejecting and closing the tray during startup should get it detected. windows users: If you are using a PATA - SATA bridge board or SATA contoller with a legacy mode, then you can use my modeb_win tool to get your drive detected (see above)).
I can write versions of the following programs that do not require the OS to detect the drive. However, these will require a PATA - SATA bridge board or a legacy mode SATA controller. If demand is high enough or a hacker who has contributed significantly to the community wants them (as was the case with these Windows ports), then I'll code 'em. Otherwise I won't bother, I'm just that lazy.
memdump.c - hex memdump source for Linux
memdump_win.zip - hex memdump binary for Win2000/XP
memdump_win_src.zip - hex memdump source for Win2000/XP
pp_win.zip - peek/poke binary for Win2000/XP
pp_win_src.zip - peek/poke source for Win2000/XP
execcode_win.zip - execcode binary for Win2000/XP
execcode_win_src.zip - execcode source for Win2000/XP
And finally, I've written a new version of my modeb_win util. This version uses the windows driver instead of writing directly to hardware like the old version. This version is a safer and more stable way to put the drive into modeB. However, unlike the previous version, this one requires that windows has detected the drive (so it's not terribly useful).
modeb_win2.zip - modeb_win2 binary for Win2000/XP
modeb_win2_src.zip - modeb_win2 source for Win2000/XP
These apps are even less tested than their linux counterparts. Let me know if you find bugs (fixes would be nice too). Email is at the botom of the page, or catch me on the xbh.net forums and send me a PM.