========================================
MT1939 Firmware Update Protocol Analysis
========================================

The SE-506CB is a robot with a powerful 405nm laser, a fast ARM processor,
high speed USB 2.0, over 4 megabytes of RAM, and an extremely precise
positioning system combining a hyrid of high-speed unidirectional spiral
groove addressing and an ultra-precise high speed optical tracking system. It
is capable of writing 149 nanometer dots onto a metal foil and dye substrate
at a rate of over 200 million per second. All precisely positioned in a
microscopic spiral, winding with canyons 320 nanometers apart.

It has 2 megabytes of flash memory, programmable over straightforward commands
using unencrypted, uncompressed ARM code. The standard firmware implements the
USB Mass Storage protocol, and above that a suite of standard and unique SCSI
and MMC opcodes. It controls the two motors and voice coil mechanism that make
up the mechanical parts of the robot, and the undocumented Mediatek MT1939
System-on-Chip includes both the modern ARM processor and no doubt many
obscure and custom components dedicated to the tasks of routing specially
formatted data and controlling power-constrained hardware components as
efficiently as possible. It is slim, efficient, modern, a best seller in its
category. Manufactured by the truckload at Samsung, and available for one
click on Amazon for $80 today. And when you plug it into your computer, it
will let you store up to 50 gigabytes of data on a 120mm metal and plastic
Blu-Ray disc.

It's a truly amazing machine. Optical drive capabilities were steadily growing
more nuanced and expansive over the decades since the invention of the CD-ROM,
but people hardly noticed. Today's Blu-Ray burner might be the last generation
of optical drive that's really marketed widely enough to be this affordable. I
want to open it up as a platform for art and research. This is part of an
affort to understand the hardware well enough to start crafting completely new
open source firnwares from scratch.


Detailed Background
-------------------

This is an analysis of USB traffic observed during a firmware update from
version TS01 to version TS01, on the Samsung SE-506CB Blu-Ray drive based on
the Mediatek MT1939 System-on-Chip.

The logs here were captured using the debugging features in VMware Fusion
while running the Windows version of the official updater tool in a VM. There
was also a Mac version, but it did not appear to function on the latest
releases of the operating system.

In the shorthand below, packets aren't explicitly labelled but they're
presented in a consistent order:

   - First the USB Command Block Wrapper (CBW) packet that contains the SCSI
     Command Data Block (CDB). This begins with a characteristic "USBC"
     signature. It contains various fields of interest, like a command tag and
     the length of the expected response. To paraphrase the protocol spec in
     pseudocode:

        uint32_t dCBWSignature = 'USBC';
        uint32_t dCBWTag;
        uint32_t dCBWDataTransferLength;
        uint8_t bmCBWFlags;
        uint8_t bCBWLUN;
        uint8_t bCBWCBLength;
        uint8_t CBWCB[16];

    For details on this, consult the USB Mass Storage specification
    (usbmassbulk_10.pdf)

   - Next, just the SCSI CDB. This is redundant (it's part of the USB packet)
     but having it here makes the trace easier to read, since it can be
     displayed with the correct length and in a visually distinct place. This
     packet always begins with a 1-byte opcode that identifies the command.
     The format of the rest depends on this byte. The various SCSI
     specifications use a handful of formats that share some common parts but
     which aren't totally consistent.

   - Now the data for the transfer. Simple commands like INQUIRY for example
     will have just one response packet here. If the situation gets more
     complicated, I'll label the packets.

   - Finally the "USBS" signature on the USB Command Status Wrapper (CSW)
     packet:

        uint32_t dCSWSignature = 'USBS';
        uint32_t dCSWTag;
        uint32_t dCSWDataResidue;
        uint8_t dCSWStatus;

    And that all-important Status byte at the end will be 0 for success and 1
    for failure. There's also a response '2' which shouldn't happen unless the
    USB layer of the protocol gets confused. Other status codes are supposed
    to be reserved according to the official spec.

Enough introduction, on to the...


Analysis
--------

1. First thing after the app starts, windows ceases its usual MMC device
polling cycle. The updater has taken exclusive control of the SCSI device from
Windows.

2. Gathering detailed version information from the firmware, using a mix of
standard and vendor-specific commands.

- INQUIRY command

USBC INQUIRY datalen 36
 000: 55 53 42 43 00 b8 5c 06 24 00 00 00 80 00 0c 12 USBC..\.$.......
 010: 00 00 00 24 00 00 00 00 00 00 00 00 00 00 00    ...$........... 

 000: 12 00 00 00 24 00 00 00 00 00 00 00             ....$.......    

 For version TS01:
   000: 05 80 00 32 1f 00 00 00 54 53 53 54 63 6f 72 70 ...2....TSSTcorp
   010: 42 44 44 56 44 57 20 53 45 2d 35 30 36 43 42 20 BDDVDW SE-506CB 
   020: 54 53 30 31                                     TS01            

 For bootloader:
   000: 05 80 00 32 1f 00 00 00 54 53 53 54 63 6f 72 70 ...2....TSSTcorp
   010: 42 44 44 56 44 57 20 53 45 2d 35 30 36 43 42 20 BDDVDW SE-506CB 
   020: 42 4f 4f 54                                     BOOT            

 000: 55 53 42 53 00 b8 5c 06 00 00 00 00 00          USBS..\......   

- Undocumented (FF 00 FF) Read firmware version info

USBC UNKNOWN datalen 8
 000: 55 53 42 43 10 f0 18 06 08 00 00 00 80 00 0c ff USBC............
 010: 00 ff 00 00 00 00 00 00 00 00 aa 00 00 00 00    ............... 

 000: ff 00 ff 00 00 00 00 00 00 00 00 aa             ............    

 Same for all versions observed so far:
   000: 00 06 01 01 54 53 20 00                         ....TS .        

 000: 55 53 42 53 10 f0 18 06 00 00 00 00 00          USBS.........   

- Repeated INQUIRY, longer buffer length

USBC INQUIRY datalen 96
 000: 55 53 42 43 00 b8 5c 06 60 00 00 00 80 00 0c 12 USBC..\.`.......
 010: 00 00 00 60 00 00 00 00 00 00 00 00 00 00 00    ...`........... 

 000: 12 00 00 00 60 00 00 00 00 00 00 00             ....`.......    

 For version TS01:
   000: 05 80 00 32 1f 00 00 00 54 53 53 54 63 6f 72 70 ...2....TSSTcorp
   010: 42 44 44 56 44 57 20 53 45 2d 35 30 36 43 42 20 BDDVDW SE-506CB 
   020: 54 53 30 31 20 20 30 35 32 38 20 20 20 20 20 20 TS01  0528      
   030: 20 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ...............
   040: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
   050: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................

 For bootloader:
   000: 05 80 00 32 1f 00 00 00 54 53 53 54 63 6f 72 70 ...2....TSSTcorp
   010: 42 44 44 56 44 57 20 53 45 2d 35 30 36 43 42 20 BDDVDW SE-506CB 
   020: 42 4f 4f 54 00 00 00 00 00 00 00 00 00 00 00 00 BOOT............
   030: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
   040: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
   050: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................

 000: 55 53 42 53 00 b8 5c 06 00 00 00 00 00          USBS..\......   

3. At this point, the firmware updater app has everything it needs to display
its dialog box with drive and firmware info. Now it's waiting for the user to
pick a file and initiate the "Download" operation.

    It's also worth noting the warning labels on the updater app. There's the
    typical one where you shouldn't remove power during the update. That's
    pretty normal for any kind of reflasher tool. But then, below that,
    there's another warning. Quoted, underlined, all caps, and even blinking,
    warning me to run the firmware updater again if the drive stops working.

    Reading between the lines, this hints a few things to me:

        - This is corroborated by the delays we see below, but it seems like
          the drive is reflashing the firmware as the image is being received,
          rather than buffering the image then reflashing all at once. This
          means that the drive is probably (hopefully?) in no state to work
          properly if it's interrupted. It will probably crash, but maybe it
          glitches first. Hard to say until I understand the boot process and
          how much verification happens, if any.

        - For this promised recovery procedure to have any chance of
          recovering from a power fail during flash, there needs to be a
          bootloader capable of catching this fault somehow, then choosing to
          run a tiny stub firmware instead of the regular firmware. The stub
          firmware may use a different firmware update mechanism or it may be
          a subset of the usual firmware.

        - The way it catches this fault will be important to our reversing
          efforts. If it's checking a hash or a signature at boot, then we'll
          need to figure out how to update the hash or forge the signature
          when we create our own firmware images. If it's catching a runtime
          error, that's easy then. We just try not to trip over that handler,
          but if we do we have a built-in safety net. This is the most
          developer-friendly thing we could hope to find, so I'm skeptical.
          Worst case, it has no way of catching the fault, this warning
          message is actually a lie, and I'll brick my drive the moment I
          create a buggy firmware. If that happens, plan B is to open up the
          device and look for a JTAG port. It would be a good chance to try
          the JTAGulator. And plan C, if that fails, this unit will be
          sacrificed for PCB scans ^_^

        - Further experimentation reveals that there's a small "infonub"
          structure at the end of flash. The bootloader checks this before
          invoking the application image. It probably includes a simple
          checksum or CRC. If this CRC passes, it's unclear how one would
          intentionally enter the bootloader to replace a malfunctioning
          firmware image. This will require some care until we have a way of
          recovering.

4. When "Download" starts, a rapid sequence of status polls occur. It happens
five times in this case. Doesn't look important. It seems more like a side-
effect of some API they're using to open the SCSI device.

USBC TEST UNIT READY datalen 0
 000: 55 53 42 43 30 b5 06 06 00 00 00 00 00 00 0c 00 USBC0...........
 010: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00    ............... 

 000: 00 00 00 00 00 00 00 00 00 00 00 00             ............    

 000: 55 53 42 53 30 b5 06 06 00 00 00 00 01          USBS0........   

USBC REQUEST SENSE datalen 24
 000: 55 53 42 43 30 b5 06 06 18 00 00 00 80 00 0c 03 USBC0...........
 010: 00 00 00 18 00 00 00 00 00 00 00 00 00 00 00    ............... 

 000: 03 00 00 00 18 00 00 00 00 00 00 00             ............    

 000: 70 00 02 00 00 00 00 0a 00 00 00 00 3a 02 00 00 p...........:...
 010: 00 00 00 00 00 00 00 00                         ........        

 000: 55 53 42 53 30 b5 06 06 00 00 00 00 00          USBS0........   

5. Step (2) repeats. App is checking the status of the existing firmware again
before beginning the "Download".

6. Undocumented (FF 00 01) Begin writing firmware image

USBC UNKNOWN datalen 63488
 000: 55 53 42 43 b0 b4 93 05 00 f8 00 00 00 00 0c ff USBC............
 010: 00 01 00 00 00 00 00 00 00 00 00 00 00 00 00    ............... 

 000: ff 00 01 00 00 00 00 00 00 00 00 00             ............    

 Firmware data: 0xf800 (63488) bytes from address 0

    This is sent as four USB 2.0 packets, with lengths 0x4A00, 0x4000, 0x4000,
    and 0x2E00. This seems quite odd, but it likely reveals more about the
    structure of the memory buffers in the firmware updater app than it does
    about the device itself, since this pattern repeats identically for each
    of the many SCSI transfers that make up the firmware image.

    Why this weird size, 0xf800 bytes? It's the number you'd pick if your
    constraints were:

        - Less than 64 kiB
        - Multiple of 2 kiB (0x800)

        (Pure speculation, but these are exactly the constraints you'd have if
        (you believed that 2 kiB was a good buffer size, and wanted your
        (driver to work on Windows. I seem to remember some odd 64 kiB limits
        (from a past life of debugging USB drivers on Windows systems.)

    An alternate theory: This could be related to a flash error-correction
    structure implemented by the device. It could be that the physical flash
    device has an erase block size of exactly 64 kiB, but each of these blocks
    needs 2 kiB of reserved space for the ECC something something :) The total
    size of the image still adds up to exactly 2 MiB, but this means that the
    physical flash blocks are mapped a little bit askew from the blocks as
    seen by the ARM CPU core. Just a theory for now, we might be able to prove
    this later if we can figure out what the flash brand is and how it's
    attached.

    From looking at aligntments elsewhere in the firmware, it seems like usual
    binary multiples (0x10000 and such) are used and I don't see any evidence
    of the 0xf800 block size elsewhere. This is good news!

 000: 55 53 42 53 b0 b4 93 05 00 00 00 00 00          USBS.........   

7. Undocumented (FF 00 01) Continue writing firmware image

This includes a flag 0xFF in the CDB, indicating that this is a continuation
rather than the first block of the image.

After 16 continuations (17 total writes), totalling 1054 kiB and requiring
about 0.5 second of transmission time, the device stalls for about 10 seconds
before it will return a successful status code. It appears that the device
buffers about 1 megabyte (half the total) at a time before committing the
write.

After that pause, another 16 continuations of the same type for 32 total.

USBC UNKNOWN datalen 63488
 000: 55 53 42 43 b0 b4 93 05 00 f8 00 00 00 00 0c ff USBC............
 010: 00 01 00 00 ff 00 00 00 00 00 00 00 00 00 00    ............... 

 000: ff 00 01 00 00 ff 00 00 00 00 00 00             ............   
                     ^ New

 Firmware data: 0xf800-byte block.
 Repeated for firmware blocks 1 through 32 as described above.

 000: 55 53 42 53 b0 b4 93 05 00 00 00 00 00          USBS.........   

8. Undocumented (FF 00 01) Finish writing firmware image

The final block is smaller, only 2048 bytes. The total of (1 + 32) * 0xf800 +
2048 is exactly 2 MiB: 0x200000. The parameters for this final block are
different yet again.

USBC UNKNOWN datalen 2048
 000: 55 53 42 43 10 44 29 05 00 08 00 00 00 00 0c ff USBC.D).........
 010: 00 01 00 00 02 00 00 00 00 00 00 00 00 00 00    ............... 

 000: ff 00 01 00 00 02 00 00 00 00 00 00             ............    
                      ^ Again this byte changed

                        What does the value mean? Most logical would be this
                        is simply a state indicator and these were hardcoded
                        constants [0, -1, 2] for some reason. Maybe we'll find
                        out what 1 means later. Maybe it means "Error, try to
                        abort!" whereas 2 means "go ahead and reboot into the
                        new firmware image!"

    It occurs to me here that the footer data in the last 48 bytes of the
    firmware image probably includes some kind of entry point or checksum
    information in addition to the plaintext version string. That would make
    this otherwise seemingly haphasard firmware updater procedure make a bit
    more sense. This trailer would be the integrity check used by the
    bootloader. The idea would be that the bootloader is likely to stay
    intact, and if it notices the rest of the firmware has been damaged it
    will quarantine us into a tiny tiny firmware that just knows how to
    reflash again.

     Excerpt showing the very end of the last packet of the firmware image,
     for reference:

     790: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ................
     7a0: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ................
     7b0: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ................
     7c0: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ................
     7d0: 54 53 53 54 63 6f 72 70 42 44 44 56 44 57 20 53 TSSTcorpBDDVDW S
     7e0: 45 2d 35 30 36 43 42 20 54 53 30 31 3e 22 04 00 E-506CB TS01>"..
     7f0: 54 53 20 ff ff ff ff ff aa aa ff ff ff ff 59 18 TS ...........Y.

     And as another aside, the full INQUIRY response for reference:

     000: 05 80 00 32 1f 00 00 00 54 53 53 54 63 6f 72 70 ...2....TSSTcorp
     010: 42 44 44 56 44 57 20 53 45 2d 35 30 36 43 42 20 BDDVDW SE-506CB 
     020: 54 53 30 31 20 20 30 35 32 38 20 20 20 20 20 20 TS01  0528      
     030: 20 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ...............
     040: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
     050: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................

     A boring computer sciency type would probably call this 48-byte blob at
     address 0x1fffd0 something like a "Firmware Trailer", since it lives at
     the very end. But let's call it the "infonub", since it's a little but
     quite important thingie that indentifies a firmware image in some
     important and still somewhat mysterious ways. First we have a section of
     th INQUIRY response, the first 28 bytes. it's a little response, just
     barely enough to capture the firmware version. Reading between the lines
     again and looking at the fields in common between these two packet
     formats, it seems to me that the firmware has something like this as a
     data type:

         struct FirmwareVersionStrings {
            char vendor[8]      = "TSSTcorp";
            char product[16]    = "BDDVDW SE-506CB ";
            char version[4]     = "TS01";
         };

     The version code "TS01" is consistent with the format used in the
     filenames of firmware images included with the official updater tool.

     The remainder of the infonub after the string structure looks like this:

     1fffe0:                                     3e 22 04 00             >"..
     1ffff0: 54 53 20 ff ff ff ff ff aa aa ff ff ff ff 59 18 TS ...........Y.

    Or listed as 32-bit words:  (you know, for some inspiration)  
   
     0004223e   Really hoping for an entry point, but this doesn't make sense.
     ff205354   Feature bits? Orderly loader flags?
     ffffffff   -1, or anything really.
     ffffaaaa   Two small bit-fiddly things?
     1859ffff   An important maybe-BCD-formatted number & a 16-bit placeholder?

    Wowsers, when thes comes back note the nonzero residual code reported via
    SCSI! In this case it's 0x800. But we're supposed to be done! I wonder
    why? This happens to match the length of our non-maximum-length write
    payload. It's also worth noting that there's antother 10 second pause in
    delivering the USBS packet for this final write. This would be consistent
    with a hypothesis that the firmware writes 1 MiB at a time, and these
    writes are big chunky 10 second long operations that block everything.

     000: 55 53 42 53 10 44 29 05 00 08 00 00 00          USBS.D)......   
                                 ^-----------^
                  This is 2 kiB in the "residual" field. Huh!

8. To review the timeline so far, we had a bunch of rapid-fire things, then:

    - We write 1 MiB of firmware image, and suddenly the USB interface stalls
      for 10 seconds until a successful CSW packet comes back.

    - Then we write another 1 MiB of firmware (each write took only a half
      second even in a VM with lots of debug). Just prior to the CSW in the
      final packet, there's another similar 10 second stall. The CSW completes
      successfully.

    So! Now we've actually finished sending the firmware and the drive has
    told us everything's good. I see another 10 second pause in the USB logs
    here. I don't know if that's necessary (giving the drive time to finish
    doing something else) or whether it's because Windows takes some time to
    reload its usual MMC drivers. For whatever reason, Windows only resumes
    MMC polling about 10 seconds after the final firmware download CSW
    completes successfully.

    We won't know whether this additional 10 second delay is necessary until
    we try omitting it ^_^

9. And that's it! The drive starts running the new firmware automatically at
some point. In the normal operation of the Windows firmware updater in this
VM, the subsequent polling commands sent to the drive begin working
immediately, even though the software forces you to abruptly reboot windows. I
can see why; the state of the firmware could be out of sync with what the
Windows driver thinks, since the firmware has just been abruptly changed while
the driver's running. Of course, if we're hackers, it means we can probably
get a tolerably rapid development cycle. Just about 22 seconds to fully
reflash the chip.


Other Notes
-----------

The first 0x10000 bytes of flash are write protected. It appears that this
portion of the update image is ignored.


~MeS`14
