Friday, March 29, 2013

Mercury ME-DPF24MG digital photo frame reverse engineering

I got two Mercury ME-DPF24MG digital photo frames at the Leamington Zellers liquidation sale at 70% and 80% off the yellow tag price. They're nice 2.4" photo frames with a 320x240 LCD, rechargeable battery, a magnetic back, and a leg for standing the frame up on a desk. Some reverse engineering has already been done by others, but it's incomplete and the frame cannot be used as USB controlled display yet. The frame is also similar to the Technaxx Magno, which has been investigated more thoroughly but still cannot be used as a display.

Basic information

The frame has 4 megabytes of flash memory. The manual claims "32 MB", but I guess they mean 32 megabits. Phack from st2205tool-1.4.3 does a check for the amount of memory, and quits with "Expected response 8 on cmd 1, got 0x1f!" because the frame reports more memory than expected. Here is the external memory map, in terms of 32 kilobyte (0x8000 size) pages:

0x000-0x07F flash, starting with firmware in pages 0 and 1.
0x080-0x2FF unused
0x300-0x37F LCD
0x380-0x3FF 4 pages repeating, similar to firmware
0x400- address space repeats, probably because bank register ignores bits

The area at 0x380 seems to contain a firmware for a different photo frame. The menus are smaller than in the true firmware, implying it is for a lower resolution. Maybe it is ROM inside the chip? I did not investigate that area further.

The read command adds two to the low byte of the page number. As a result, a normal read starts right after the firmware. I dumped the firmware by reading in page 0xFE, because when firmware adds 2 to that number, it gets 0. I used 0xFE, not 0xFFFFFFFE because the firmware only adds 2 to the least significant byte of the page number.

The chip is probably a Sitronix ST2203U. It is definitely not a ST2205U because the DMA controller is different. I wasn't able to find a full User's Manual for that chip, but the ST2205U and ST2202U manuals are helpful. RAM is at 0x80-0x880 internal addresses, meaning there is only 2 kilobytes.

The LCD controller is unknown, and probably similar to Ilitek ILI9325C. The command number is 16 bit, with the most significant byte first, and coordinates are input the same way.  To talk to the LCD, set DRR to $300, send commands to $8000 and send data to $C000. Here is a sequence for setting a rectangle: C=$20, D=Y1, C=$51, D=Y1, C=$51, D=Y2, C=$21, D=X1, C=$52 D=X1, C=$53, D=X2, C=$22, followed by data with 3 bytes per pixel. This is untested.

USB Commands

Like other photo frames using Sitronix chips, the frame acts as a USB mass storage device but only responds to reads and writes to specific locations. The locations are the same as with other frames:

Write commands at 0x6200, which is SCSI sector $31, or '1'.
Write data to 0x6600, which is SCSI sector $33 or '3'.
Read data from 0xb000, which is SCSI sector $58 or 'X'

Reads from other locations start from a location in flash with "SITRONIX CORP.", which phack uses to verify the presence of a photo frame. Data writes only matter when a command has been sent, and until that command has consumed the amount of data it expects. It should be possible to split data reads and writes into multiple accesses all starting at that location. They must not be split into multiple sequential accesses because only the first chunk would go to the right location.

Commands use the first 16 bytes written to 0x6200. The first byte is the command number and the rest can be parameters. I will call the command number P0, with the parameters starting at P1. Not all commands have parameters. In some cases P1-P4 can be thought of as a page address in big endian format, with 0x8000 byte pages and the high bit being a flag used for firmware updates. Some commands return data which can be read from 0xb000 afterwards.

Here are the commands:

1: Get flash size

No parameters

Return size: 1
R0 = flash size, 0x1F with this photo frame

2: Return 32 bit checksum of flash bank

P1 : If bit 7 is high: add 6 to P4,but don't carry over to P3
P3 : for DRRH
P4 : for DRRL
2 is always added to P4 and carried over to P3. As a result 0x80000000 will checksum page 8, when it probably should be working on page 6. A firmware upgrade can be checksummed via pages 4 and

Return size: 4
R0-R3 = big endian 32 bit sum of all bytes in page

3: Write to flash

P1 : If bit 7 is high DRRL=P4+6, DRRH=0 ignoring P3. Bit 0 of byte $97 is also set. When USB is disconnected, code running in RAM copies pages 6, 7 to pages 0, 1, and then restarts the photo frame. If bit 7 is low, DRRL=P4+2 and DRRH=P3
P3 : for DRRH
P4 : for DRRL
P5 to P8 = amount to write in big endian format

No data is returned.

This function should be followed by a write of the data to 0x6600.

This function can be used to directly write to the firmware pages, but that would probably be dangerous because it could erase code that was running. The firmware update function via bit 7 of P1 should be used instead. It overwrites most of the second photo with the firmware. Note that writes to pages 6 and 7 done this way don't immediately accomplish anything special. One can read or checksum the pages to verify they were written correctly, and otherwise continue to use the USB interface. This gives you a chance to recover if there was a problem with the write. USB disconnection is the point of no return. I always did it with the power switch in the on position, so the update can continue on battery power. I don't know if having the switch in the off position might allow one to prevent an update. That or running out of battery power might be dangerous, if the update is begun but not completed.

The first 0x4000 bytes of the flash are protected on both frames. I see no code preventing writes there, so the protection must be set up in the flash chip. This is probably an attempt to avoid bricking the device, but it does not actually protect you.

4: Set read pointer

P4 + 2 = DRRL. The add isn't carried over to DRRH.

Data is returned from $8000, according to the selected bank.

Note that there is no length parameter here. Read length comes from transfer length in READ(10) SCSI commands. This only sets a pointer which is then incremented by following reads. However, it does not wrap properly, so the second page will actually read from $0000-$7FFF, dumping the RAM, which is useful. For flash dumping, use this function before every page.

The same read pointer and data sending code is used for other commands which return data to the host. Those commands set the pointer to a location in RAM where they wrote their reply.

5: Get LCD size and BPP

No parameters

Return size: 5
R0 = high byte of width
R1 = low byte of width
R2 = high byte of height
R3 = low byte of height
R4 = bits per pixel + $80

6: Set clock

P1 = high byte of year
P2 = low byte of year
P3 = month
P4 = day in month
P5 = hours
P6 = minutes

There seems to be no way to set seconds via this interface, and the resulting seconds value is unpredictable. The user interface on the device allows setting of seconds.

Return size: 1
R0 = $5A

7: Get image format?

No parameters

Return size: 2
R0 = 9
R1 = 0

8: Get version

No parameters.

Return size: 3
R0 = $5A
R1 = 1
R2 = 5

Maybe the $5A just indicates success like after command 6, and the version is 1.5?

9: Display message

No parameters

No return data

This command uses the next data write to 0x6600 as the parameter. The first 9 characters are displayed on the LCD. Exactly the first 9 characters are displayed, so a shorter message should be padded with spaces. The function displaying the text displays a larger rectangle of text, but only 9 characters can be set via USB because the rest are overwritten.

Firmware structure and hacking possibilities

The firmware is in flash, from 0 to 0xFFFF. It is divided into 4 pages which get mapped into 0x4000-0x7FFF via PRRH and PRRL. Here, I will be talking about firmware pages, which are 0x4000 bytes in size.

Interrupt handlers, basic USB functionality and initialization except for the LCD is all in page 0. This page appears to be write protected on the flash chip. Everything that's needed to write to flash seems to be on page 0. The code checks for a valid signature at $100 in page 1, but it jumps to other pages anyways, so this doesn't protect you. It also doesn't protect you if calls to other pages hang or don't provide a way to get to USB mode.

The USB interrupt handler takes care of USB, the mass storage protocol and SCSI commands. The USB loop in main code deals with written commands and data, and sends data in response to reads. The code used to hack other photo frames goes in the USB interrupt handler. Both the USB interrupt handler and USB loop are on page 0, meaning they cannot be altered unless the page is somehow unlocked. Some commands run code on other pages, so they offer a way for running some new code.

Interrupt hooks

Interrupt handlers including the USB interrupt handler call subroutines in memory at $780 to $80C, with 14 bytes available for each routine. That is sufficient to set IRR to remap $4000-$4FFF, call a subroutine in a different firmware page, and jump to code $81A which resets IRR to 0. The calling of these routines are enabled by bit 7 of the byte at $8F. The RAM is set up to call subroutines at $4200 to $42A0 in firmware page 2, but those are only RTS instructions. Hooking might not ever be enabled normally, but it seems ready to enable. The USB interrupt hook at $78E is called after the main USB interrupt code

Hacking options

Due to page 0 being locked, there are several options:
  1. Add code to one of the commands using another page.
  2. Write code which interacts with the USB interrupt handler instead of the main USB loop.
  3. Disable interrupts and interact with the USB hardware directly.
  4. Use the USB interrupt hook.
  5. Use IRR to handle interrupts in other pages.
  6. Unlock page 0. This may involve uploading code and running it while applying a high voltage to some pin(s) of the flash chip.
I think option 1 is a good start, but other options are better eventual solutions.

Bank switching

For understanding the firmware, it is important to understand the function at $820. It is used for calling a function in another firmware bank. The function runs from RAM so it can continue through the bank switch. The call is followed by 4 bytes of parameters, and the function returns to the instruction following those two bytes. The bytes are: DRRL, DRRH, low byte of address, high byte of address. The address is the function address minus one, because the function is accessed via an RTS instruction.

There is another similar function which instead copies $200 bytes of code code to $520 and jumps there. It is used to call functions which write to flash. It uses the byte at $99 as the low byte of the source address and $9A as the high byte. There are no parameter bytes after the function call.

The DMA controller

It is also important to understand the DMA controller, because it is used often. It is more like the ST2202U than the ST2205U. Registers are:

$36 DMRL : source bank low byte
$37 DMRH : source bank high byte
$28 DMSL : source address low byte
$29 DMSH : source address high byte

$34 DRRL : destination bank low byte
$35 DRRH : destination bank high byte
$2A DMDL : destination address low byte
$2B DMDH : destination address high byte

$2C DCNTL : counter low byte, DMA trigger
$2D DCNTH : counter high byte

When reading for DMA, the DMR registers determine mapping of $8000-$FFFF. When writing, that mapping is determined by the DRR registers, like when the CPU accesses those locations. DMA can also access internal RAM. The bank registers are irrelevant then, because that address range is not banked. After a write to DCNTL, DMA starts and the CPU waits until it completes. DMA is used by interrupt code for some USB transfers and only DRRL and DRRH are saved, so interrupts need to be disabled before setting other registers. They can be re-enabled immediately after the write to DCNTL.


Arvid S said...

Great work! I'm trying to reverse engineer a similar DPF also based on ST2203u. Have a look at this source code: it's for another chip it seems, but parts of the code is built on st2203u and the reset vector code in main.asm matches the disassembly on my frame at least!

Boris Gjenero said...

Thanks for the firmware source link. I've already fully hacked my photo frame. The hack source is now on GitHub. I added code to another firmware page, triggered by one of the commands on that page. Once activated, it interacts with the USB hardware directly for better performance.

Arvid S said...

Thanks for the link to your sources. Your solution seems really ellegant and efficient. I'll try to translate it to my Digiview v240 frames next.

Boris Gjenero said...

There was a serious bug in the USB code running on the photo frame. I forgot a # so it was reading memory location 4 instead of using the USBBFS_BKO constant. I fixed it here.

Muhammad Hassam said...

A very awesome blog post. We are really grateful for your blog post. You will find a lot of approaches after visiting your post. large digital picture frame