superna9999 / pyamlboot

Amlogic USB Boot Protocol Library
https://pypi.org/project/pyamlboot/
Apache License 2.0
71 stars 27 forks source link

[FR] Implement `AML_LIBUSB_REQ_WRITE_MEDIA` and `AML_LIBUSB_REQ_READ_MEDIA` commands #21

Open fonix232 opened 5 months ago

fonix232 commented 5 months ago

Many have been tinkering with the Spotify CarThing (codename superbird), an Amlogic S905D2 based device. @bishopdynamics has created a cross-platform tool wrapping pyamlboot to simplify a lot of the processes (dumping and restoring devices to specific firmware versions, enabling ADB and custom ROMs, etc.), however this tool, at the moment is somewhat limited in speed due to the indirect approach needed - instead of being able to read the attached storage (media) directly, it needs to instruct the device to read chunks of the partitions into RAM, then transfer those chunks to the host. This has a very limited read and write bandwidth - a few hundred KBps for read, and around 1.5MBps for writing. Simply said, dumping a device can take hours, which is really suboptimal.

However, the semi-official update utility, which is sadly not cross platform, has a much faster option for both writing and reading, the mread and mwrite methods.

Fortunately, there's an older version of the update utility where the source code is available: https://github.com/yangfl/update/

This source code defines both mread and mwrite quite well:

By the looks of it, the process is quite straightforward - I'll do mread as I'm more familiar with it, still need to dig through mwrite properly.

  1. After sanitising the input of mread, the parameters (store or read into memory, partition name, file type (always normal), and read size) are assigned
  2. A bulkcmd is sent with the params bulkcmd update [storeOrMemory] [partition] [fileType] [readSize in hex]
  3. The method ReadMediaFile is called, with the optional output file name (empty if reading into memory) and size
  4. ReadMediaFile prepares the output file, then begins calling AmlReadMedia with 0x10000L chunks read into buffer]
  5. AmlReadMedia further sanitises its input, then calls ReadMediaCMD to send the instruction to the device to read the chunk
  6. ReadMediaCMD is a bit convoluted, but it generally sends a USB control message and checks the output (https://github.com/yangfl/update/blob/master/AmlLibusb/AmlLibusb.cpp#L168-L191)
  7. Once ReadMediaCMD is finished, a bulk USB read happens into the provided buffer (https://github.com/yangfl/update/blob/master/AmlLibusb/AmlLibusb.cpp#L282-L293)
  8. That buffer is then written into the output file, or prints the block into console

mwrite is mostly the same, with some intermittent bulkcmd's checking the status and verifying the download (I'm guessing some light checksums of blocks).

Would it be possible to implement this in pyamlboot? I'd gladly do it myself, however neither my C++ nor my Python knowledge is on a level where I'd feel certain my efforts aren't pointless or downright dangerous.

superna9999 commented 5 months ago

Hi I thinks this is already done in #20 see https://github.com/superna9999/pyamlboot/pull/20/commits/d8d772d89527c669d24db16b91c5228dae9e74a4

fonix232 commented 5 months ago

@superna9999 I agree, it looks okay on first blink. However, given how much data is being transferred, to avoid hanging, I think it would be better for both ReadMedia and WriteMedia if they did a batched approach for continuous progress update - basically, replacing epin.read(size, timeout).tobytes() and epout.write(data, 1000) with smaller entries, similarly to how the update utility does:

With the main difference being that we wouldn't need to re-issue the control messages (self.dev.ctrl_transfer calls in the PR you linked), but only the bulk read/write part would need to repeat.

But otherwise this marginal nitpick, the code looks good, and seems to match precisely what's happening in the update tool!