<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="3.10.0">Jekyll</generator><link href="https://divinehawk.github.io/feed.xml" rel="self" type="application/atom+xml" /><link href="https://divinehawk.github.io/" rel="alternate" type="text/html" /><updated>2026-09-28T02:03:41+00:00</updated><id>https://divinehawk.github.io/feed.xml</id><title type="html">DivineHawk</title><subtitle>Insights, research, and engineering notes from DivineHawk AI — machine learning, systems, and applied research.</subtitle><author><name>DivineHawk</name></author><entry><title type="html">Adding ADB keys to the Pastigio Android Frame</title><link href="https://divinehawk.github.io/2026/07/10/u-boot-adb-setup.html" rel="alternate" type="text/html" title="Adding ADB keys to the Pastigio Android Frame" /><published>2026-07-10T00:00:00+00:00</published><updated>2026-07-10T00:00:00+00:00</updated><id>https://divinehawk.github.io/2026/07/10/u-boot-adb-setup</id><content type="html" xml:base="https://divinehawk.github.io/2026/07/10/u-boot-adb-setup.html"><![CDATA[<h2 id="overview">Overview</h2>

<p>This post documents the custom U-Boot binary used in the <a href="//2026/07/08/pastigio-frame-adb-fel-mode.html">previous FEL mode guide</a> — the bootloader that exposes the frame’s eMMC as a USB mass storage device on your host machine, allowing you to mount the Android filesystem and install ADB keys.</p>

<p><strong>Build and install sunxi-fel</strong> <a href="https://github.com/linux-sunxi/sunxi-tools"><code>sunxi-tools</code></a></p>

<p><strong>Download the binary:</strong> <a href="/assets/u-boot-sunxi-with-spl.bin"><code>u-boot-sunxi-with-spl.bin</code></a></p>

<h2 id="what-this-u-boot-does">What This U-Boot Does</h2>

<p>When booted via FEL mode, this custom U-Boot:</p>
<ul>
  <li>Initializes the Allwinner A33 SoC and DRAM</li>
  <li>Boots directly into USB mass storage mode (<code>ums 0 mmc 2</code>)</li>
  <li>Exposes the frame’s internal eMMC as a plain block device on the host</li>
  <li>Requires <strong>no serial cable, no fastboot, no manual interaction</strong></li>
</ul>

<h2 id="how-to-use-it">How to Use It</h2>

<p>See the <a href="/2026-07-08-pastigio-frame-adb-fel-mode.html">previous post</a> for the full procedure, but in brief:</p>

<ol>
  <li><strong>Short the UPDATE pads</strong> on the MS33-V2.0 board while powering on to force FEL mode</li>
  <li><strong>Boot this U-Boot via FEL</strong> — no flashing required, it runs entirely in SRAM/DRAM:
    <pre><code class="language-bash">$ sunxi-fel -v uboot u-boot-sunxi-with-spl.bin
</code></pre>
  </li>
  <li><strong>The frame’s eMMC appears on your host</strong> as a USB mass storage device</li>
</ol>

<h2 id="mounting-the-userdata-partition-and-adding-adb-keys">Mounting the Userdata Partition and Adding ADB Keys</h2>

<p>Once the eMMC is exposed as USB mass storage, you can mount the Android userdata partition and plant your ADB public key:</p>

<h3 id="mount-the-partition">Mount the Partition</h3>

<p>Based on the partition layout from the previous post, the userdata partition is typically <code>/dev/sda1</code>. Create a mount point and mount it:</p>

<pre><code class="language-bash">$ sudo mkdir -p /mnt/frame_userdata
$ sudo mount /dev/sda1 /mnt/frame_userdata
</code></pre>

<h3 id="plant-your-adb-public-key">Plant Your ADB Public Key</h3>

<p>First, generate an ADB keypair if you don’t have one:</p>

<pre><code class="language-bash">$ mkdir -p ~/.android
$ adb keygen ~/.android/adbkey
</code></pre>

<p>Create the ADB keys directory and copy your public key:</p>

<pre><code class="language-bash">$ sudo mkdir -p /mnt/frame_userdata/misc/adb
$ sudo cp ~/.android/adbkey.pub /mnt/frame_userdata/misc/adb/adb_keys
</code></pre>

<h3 id="unmount-and-reboot">Unmount and Reboot</h3>

<p>Safely unmount the partition:</p>

<pre><code class="language-bash">$ sudo umount /mnt/frame_userdata
</code></pre>

<p><strong>Power cycle the frame normally</strong> — no UPDATE pad shorting needed on reboot. The frame will boot Android and automatically trust your ADB public key.</p>

<h3 id="verify-adb-connection">Verify ADB Connection</h3>

<p>After the frame boots, verify that <code>adb devices</code> shows the frame as authorized:</p>

<pre><code class="language-bash">$ adb devices
List of attached devices
&lt;frame-serial&gt;    device
</code></pre>

<p>No prompt or fingerprint prompt should appear.</p>

<h2 id="binary-details">Binary Details</h2>

<p>This prebuilt U-Boot binary is based on mainline U-Boot configured for the Allwinner A33 with these key settings:</p>

<ul>
  <li><strong>Boot command</strong>: <code>ums 0 mmc 2</code> — boots straight into USB mass storage mode with zero delay</li>
  <li><strong>No PMIC</strong>: Configured for boards without the AXP PMIC</li>
  <li><strong>USB gadget</strong>: Supports USB mass storage gadget mode for block device access</li>
  <li><strong>Device tree</strong>: Customized for the MS33-V2.0 board to properly initialize the eMMC and USB OTG controller</li>
</ul>

<h2 id="why-this-works-without-fastboot-or-serial">Why This Works Without Fastboot or Serial</h2>

<ul>
  <li><strong>FEL mode</strong> is baked into the Allwinner A33 boot ROM and cannot be disabled by vendor firmware</li>
  <li><strong>USB mass storage mode</strong> in U-Boot gives direct block-device access to the eMMC without needing to boot Android</li>
  <li><strong>No manufacturer restrictions</strong> apply below the Android layer — you’re talking directly to the SoC</li>
</ul>

<h2 id="next-steps">Next Steps</h2>

<p>With ADB now enabled, you can follow the <a href="https://immichframe.dev/docs/getting-started/apps#frameo">ImmichFrame Frameo setup guide</a> to sideload the app or continue with any other development workflows.</p>]]></content><author><name>DivineHawk</name></author><category term="embedded" /><category term="u-boot" /><category term="adb" /><category term="frame" /><category term="allwinner" /><category term="fel" /><summary type="html"><![CDATA[Overview]]></summary></entry><entry><title type="html">Enabling ADB on a Pastigio Frameo Photo Frame via Allwinner FEL Mode</title><link href="https://divinehawk.github.io/2026/07/08/pastigio-frame-adb-fel-mode.html" rel="alternate" type="text/html" title="Enabling ADB on a Pastigio Frameo Photo Frame via Allwinner FEL Mode" /><published>2026-07-08T00:00:00+00:00</published><updated>2026-07-08T00:00:00+00:00</updated><id>https://divinehawk.github.io/2026/07/08/pastigio-frame-adb-fel-mode</id><content type="html" xml:base="https://divinehawk.github.io/2026/07/08/pastigio-frame-adb-fel-mode.html"><![CDATA[<p>I bought a <a href="https://www.amazon.com/dp/B0D41ZMYB2">Pastigio 10.1” Frameo digital picture frame</a> (model <strong>ZC002</strong>) with one goal: run <a href="https://github.com/immichFrame/ImmichFrame">ImmichFrame</a> on it instead of relying on Frameo’s cloud infrastructure.</p>

<p>This is the story of how a “we’ve disabled this for your safety” support ticket turned into a teardown, an Allwinner FEL session, and a hand-planted ADB key.</p>

<h2 id="the-dead-end-a-toggle-that-does-nothing">The dead end: a toggle that does nothing</h2>

<p>Frameo’s on-device settings menu actually has a “Enable ADB debugging” option. Flip it, and normally you’d expect the familiar Android “Allow USB debugging?” dialog with the RSA key fingerprint to pop up. Instead: nothing. No dialog, no connection possible, just silence.</p>

<p>I opened a support ticket. The reply, verbatim:</p>

<blockquote>
  <p>“We’ve thoroughly discussed your request with the manufacturer. They indicated that since the ADB debugging interface is subject to strict system-level permission controls, enabling it could potentially cause irreversible damage to the system programs. Therefore, this setting is currently unavailable. We appreciate your understanding.”</p>
</blockquote>

<p>That’s not really an explanation — ADB debugging doesn’t cause “irreversible damage” any more than any other Android feature — but it did confirm this wasn’t a bug I could fix from the settings menu. So I opened the case.</p>

<h2 id="whats-actually-inside">What’s actually inside</h2>

<p>Popping the back off the frame revealed a board silkscreened <strong>MS33-V2.0</strong>, built around an <strong>Allwinner A33</strong> SoC — a quad-core Cortex-A7 part that Allwinner has shipped in a huge number of budget tablets and, apparently, photo frames.</p>

<p>More interestingly, the board has an unpopulated set of pads clearly labeled <strong>UPDATE</strong>, footprinted for a tactile switch that was never soldered on. On Allwinner designs this is almost always the FEL/recovery strap: a GPIO sampled by the boot ROM at power-on that, when pulled to the active state, forces the SoC to skip its normal boot media (eMMC/NAND/SD) and drop into <strong>FEL mode</strong> instead.</p>

<p>There’s also an RX/TX header on the board — 3.3V logic levels, 115200 baud, the usual Allwinner debug UART setup. Hooking up a USB-to-serial adapter and powering the frame on normally, though, got nothing readable: either silence or garbage that didn’t resolve at any of the usual rates. Whatever console the stock firmware uses, it isn’t coming out on those pins during a normal boot.</p>

<p><img src="/assets/ms33-v2-board.jpg" alt="MS33-V2.0 board, showing the Allwinner R6383BA (A33) SoC, the unpopulated UPDATE button footprint, and the RX/TX serial header" />
<em>The MS33-V2.0 board out of the frame — Allwinner SoC dead center, <code>UPDATE</code> footprint and <code>RX</code>/<code>TX</code> pads on the left edge, WiFi/BT module bottom-left.</em></p>

<h2 id="fel-mode-the-backdoor-allwinner-cant-disable">FEL mode: the backdoor Allwinner can’t disable</h2>

<p>FEL (Firmware Loader) is a first-stage USB recovery protocol baked into the boot ROM (BROM) of every Allwinner SoC — it’s mask-programmed into silicon, so no amount of vendor software lockdown on top of it can remove it. When a chip enters FEL, it enumerates over USB with a fixed identity:</p>

<pre><code>Bus 001 Device 004: ID 1f3a:efe8 Allwinner Technology sunxi SoC OTG connector in FEL/flashing mode
</code></pre>

<p>At this stage there’s no OS, no filesystem, no Android permission model — just the BROM waiting for commands to read/write SRAM and DRAM and to hand off execution. The standard tool for talking to it is <a href="https://github.com/linux-sunxi/sunxi-tools"><code>sunxi-fel</code></a>, part of the <code>sunxi-tools</code> package from the linux-sunxi project.</p>

<h2 id="forcing-fel-and-talking-to-the-chip">Forcing FEL and talking to the chip</h2>

<p>With the UPDATE pads shorted (a pair of tweezers works fine — no soldering needed since the footprint is exposed) while applying power, the frame came up silent with a blank screen and enumerated as <code>1f3a:efe8</code> on the host. From there:</p>

<pre><code class="language-bash">$ sunxi-fel version
AWUSBFEX soc=00186300(A33) ver=0001 44 08 scratchpad=00007e00 00000000 00000000
</code></pre>

<p>That confirms the A33 identification and that the BROM is alive and listening.</p>

<h2 id="booting-a-custom-u-boot-with-usb-mass-storage">Booting a custom U-Boot with USB Mass Storage</h2>

<p>FEL by itself only gives you raw memory read/write and the ability to execute code — it doesn’t understand partitions or filesystems. To actually get at the frame’s internal storage, I cloned <a href="https://github.com/u-boot/u-boot">mainline U-Boot</a> and built a custom binary with USB gadget mass storage enabled.</p>

<p>The stock <code>sun8i-a33-q8-tablet</code> device tree needed two changes: enabling the eMMC controller (the frame’s storage lives on <code>mmc2</code>, wired for 8-bit bus width and non-removable, unlike the Q8 tablet it started as) and switching USB OTG into gadget mode:</p>

<pre><code class="language-dts">&amp;mmc2 {
    pinctrl-names = "default";
    pinctrl-0 = &lt;&amp;mmc2_8bit_pins&gt;;
    bus-width = &lt;8&gt;;
    non-removable;
    status = "okay";
};

&amp;usb_otg {
    dr_mode = "peripheral";
    status = "okay";
};
</code></pre>

<p>For the defconfig, I started from <code>configs/q8_a33_tablet_1024x600_defconfig</code> — mainline U-Boot already has this for the generic Q8-format A33 tablets, and the panel timings and GPIO wiring (<code>PH7</code> for power, <code>PH6</code> for backlight enable, <code>PH0</code> for PWM) turned out to be an exact match.</p>

<pre><code>CONFIG_ARM=y
CONFIG_ARCH_SUNXI=y
CONFIG_DEFAULT_DEVICE_TREE="sun8i-a33-q8-tablet"
CONFIG_DRAM_CLK=456
CONFIG_SPL=y
CONFIG_MACH_SUN8I_A33=y
CONFIG_DRAM_ZQ=15291
CONFIG_AXP_GPIO=y
CONFIG_VIDEO_LCD_MODE="x:1024,y:600,depth:18,pclk_khz:51000,le:159,ri:160,up:22,lo:12,hs:1,vs:1,sync:3,vmode:0"
CONFIG_VIDEO_LCD_DCLK_PHASE=0
CONFIG_VIDEO_LCD_POWER="PH7"
CONFIG_VIDEO_LCD_BL_EN="PH6"
CONFIG_VIDEO_LCD_BL_PWM="PH0"
# CONFIG_SYS_MALLOC_CLEAR_ON_INIT is not set
CONFIG_REGULATOR_AXP_DRIVEVBUS=y
CONFIG_REGULATOR_AXP_USB_POWER=y
CONFIG_AXP_DLDO1_VOLT=3300
CONFIG_CONS_INDEX=5
CONFIG_USB_MUSB_HOST=y
</code></pre>

<p>Two things about it didn’t fit this board. First, it assumes an AXP-series PMIC handling power sequencing and VBUS/USB regulator control (<code>CONFIG_AXP_GPIO</code>, the <code>REGULATOR_AXP_*</code> options, <code>CONFIG_AXP_DLDO1_VOLT</code>) — this board doesn’t have that chip. Second, it boots into a U-Boot interactive prompt, which is fine if you have a serial cable, but doesn’t help on a headless device.</p>

<p>The minimal changes:</p>

<pre><code>CONFIG_ARM=y
CONFIG_ARCH_SUNXI=y
CONFIG_DEFAULT_DEVICE_TREE="sun8i-a33-q8-tablet"
CONFIG_DRAM_CLK=456
CONFIG_SPL=y
CONFIG_MACH_SUN8I_A33=y
CONFIG_DRAM_ZQ=15291
CONFIG_SUNXI_NO_PMIC=y
CONFIG_VIDEO_LCD_MODE="x:1024,y:600,depth:18,pclk_khz:51000,le:159,ri:160,up:22,lo:12,hs:1,vs:1,sync:3,vmode:0"
CONFIG_VIDEO_LCD_DCLK_PHASE=0
CONFIG_VIDEO_LCD_POWER="PH7"
CONFIG_VIDEO_LCD_BL_EN="PH6"
CONFIG_VIDEO_LCD_BL_PWM="PH0"
# CONFIG_SYS_MALLOC_CLEAR_ON_INIT is not set
CONFIG_CONS_INDEX=5
CONFIG_CMD_USB_MASS_STORAGE=y
CONFIG_USB_GADGET=y
CONFIG_USB_GADGET_DOWNLOAD=y
CONFIG_USB_MUSB_GADGET=y
CONFIG_BOOTDELAY=0
CONFIG_BOOTCOMMAND="ums 0 mmc 2"
</code></pre>

<p><code>BOOTDELAY=0</code> plus <code>BOOTCOMMAND="ums 0 mmc 2"</code> means there’s no U-Boot prompt to babysit — it boots straight into exposing <code>mmc2</code> (the eMMC) as a USB mass-storage device the moment SPL hands off control.</p>

<p>Built with:</p>

<pre><code class="language-bash">$ make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- q8_a33_tablet_1024x600_defconfig
$ make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- -j$(nproc)
</code></pre>

<p>and injected the resulting image straight into SRAM/DRAM over FEL, with no write to the frame’s actual storage yet:</p>

<pre><code class="language-bash">$ sunxi-fel -v uboot u-boot-sunxi-with-spl.bin
</code></pre>

<p>If the DTS and defconfig are right, this is the whole show: SPL brings up DRAM, U-Boot loads, <code>BOOTCOMMAND</code> fires automatically, and the eMMC shows up as a plain block device on the host — no device tree blobs, no kernel, nothing but a bootloader doing exactly what you asked.</p>

<p>One side effect of all this: <code>CONFIG_CONS_INDEX=5</code> carried over untouched from the base Q8 defconfig, and it turned out to be exactly right for this board too — the moment this custom U-Boot started booting, the UART sprang to life at 115200 and began outputting logs. Score one for the linux-sunxi community’s generalized hardware templates.</p>

<h2 id="mounting-the-frames-storage-on-my-pc">Mounting the frame’s storage on my PC</h2>

<p>Back on the host, the frame now shows up as an ordinary disk. The full partition table:</p>

<pre><code>Disk /dev/sda: 28.91 GiB, 31037849600 bytes, 60620800 sectors
Disk model: UMS disk 0
Disklabel type: dos

Device      Boot    Start      End  Sectors  Size Id Type
/dev/sda1   *     4268032 60719103 56451072 26.9G  b W95 FAT32
/dev/sda2         73728    139263    65536   32M  6 FAT16
/dev/sda3         1       4128768  4128768    2G  5 Extended
/dev/sda5         139264   172031    32768   16M 83 Linux
/dev/sda6         172032   204799    32768   16M 83 Linux
/dev/sda7         204800  2301951  2097152    1G 83 Linux
/dev/sda8        2301952  2334719    32768   16M 83 Linux
/dev/sda9        2334720  2400255    65536   32M 83 Linux
/dev/sda10       2400256  3973119  1572864  768M 83 Linux
/dev/sda11       3973120  4005887    32768   16M 83 Linux
/dev/sda12       4005888  4038655    32768   16M 83 Linux
/dev/sda13       4038656  4039679     1024  512K 83 Linux
/dev/sda14       4039680  4071423    31744 15.5M 83 Linux
/dev/sda15       4071424  4235263   163840   80M 83 Linux
/dev/sda16       4235264  4268031    32768   16M 83 Linux
</code></pre>

<p>Twelve small <code>Linux</code>-type (id <code>83</code>) partitions and one tiny FAT16 partition sit behind the extended container — none of those needed touching for this. The one that mattered is <code>/dev/sda1</code>: a standard FAT32, about 27GB, mounted on <code>/data</code> by the Android runtime.</p>

<pre><code class="language-bash">$ sudo mount /dev/sda1 /mnt/frame
</code></pre>

<p>A stock recovery log later confirmed exactly what each of those partitions is for. Its kernel command line includes a <code>partitions=</code> argument mapping every one of them by name to an <code>mmcblk0pN</code> device node:</p>

<pre><code>partitions=bootloader@mmcblk0p2:env@mmcblk0p5:boot@mmcblk0p6:system@mmcblk0p7:misc@mmcblk0p8:recovery@mmcblk0p9:cache@mmcblk0p10:metadata@mmcblk0p11:private@mmcblk0p12:frp@mmcblk0p13:empty@mmcblk0p14:userdata@mmcblk0p1
</code></pre>

<p>Matched up against the sizes from <code>fdisk</code>, it’s a completely conventional Allwinner/Android layout — <code>bootloader</code>, <code>env</code>, <code>boot</code>, <code>system</code> (the 1G partition), <code>misc</code>, <code>recovery</code>, <code>cache</code> (768M, big because this is a media device), <code>metadata</code>, <code>private</code>, <code>frp</code>, and finally <code>userdata</code> at the front.</p>

<h2 id="planting-the-adb-key-by-hand">Planting the ADB key by hand</h2>

<p>Android’s ADB authorization dialog isn’t magic — it’s backed by a plain text file. When you accept the “Allow USB debugging?” prompt, <code>adbd</code> appends the connecting host’s public key (the content of <code>~/.android/adbkey.pub</code> on your machine) to a file on the device:</p>

<pre><code>/data/misc/adb/adb_keys
</code></pre>

<p>On every subsequent connection, <code>adbd</code> checks incoming keys against that file and skips the dialog entirely if there’s a match. Since the frame’s launcher apparently never implements or shows that dialog, the manufacturer probably expects no one will ever need to use it.</p>

<p>With the userdata partition mounted, the fix was direct — write the host key straight into the file <code>adbd</code> already checks:</p>

<pre><code class="language-bash">$ mkdir -p /mnt/frame/misc/adb
$ cat ~/.android/adbkey.pub &gt;&gt; /mnt/frame/misc/adb/adb_keys
</code></pre>

<p>No <code>chmod</code>/<code>chown</code> here — the partition turned out to be <strong>FAT32</strong>, which doesn’t store per-file Unix ownership or permission bits at all, so those calls are meaningless on this filesystem (and the permissions Android cares about are enforced at the SELinux policy level anyway).</p>

<p>Then, cleanly unmount before pulling power — this is a raw block device, not a live filesystem, so no lazy writes should be left pending:</p>

<pre><code class="language-bash">$ sudo umount /mnt/frame
</code></pre>

<h2 id="the-payoff">The payoff</h2>

<p>Power cycled the frame normally (no more shorting the UPDATE pads — this boots from eMMC as usual), plugged it into the host over its regular USB-C port, and:</p>

<pre><code class="language-bash">$ adb devices
List of devices attached
&lt;serial&gt;    device
</code></pre>

<p><code>device</code>, not <code>unauthorized</code> — no prompt ever appeared, because none needed to. The key was already in the trusted list before Android’s ADB stack even started.</p>

<h2 id="whats-next">What’s next</h2>

<p>With a working, authorized <code>adb</code> connection, follow the <a href="https://immichframe.dev/docs/getting-started/apps#frameo">ImmichFrame setup instructions for Frameo devices</a> to sideload the app and configure it.</p>

<h2 id="if-youre-trying-this-yourself">If you’re trying this yourself</h2>

<p>A few honest caveats:</p>

<ul>
  <li>You’re writing to a raw partition from outside Android’s normal write path. Dump the partition (or at least the parts you’re touching) before you write anything, so you have something to restore if the write messes things up.</li>
  <li>The UPDATE-pad-as-FEL-strap trick is common on Allwinner designs but not guaranteed — confirm your SoC and strap behavior before assuming it maps the same way.</li>
  <li>This process only works because you have physical access to hardware you own. It’s not a remote exploit and shouldn’t be treated as one.</li>
</ul>

<p>Manufacturer software policies can lock a menu toggle. They can’t touch what’s burned into the SoC’s boot ROM.</p>]]></content><author><name>DivineHawk</name></author><category term="AI" /><summary type="html"><![CDATA[I bought a Pastigio 10.1” Frameo digital picture frame (model ZC002) with one goal: run ImmichFrame on it instead of relying on Frameo’s cloud infrastructure.]]></summary></entry></feed>