<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
	<channel>
		<title>Embedded on tsvinc</title>
		<link>https://tsvinc.dev/tags/embedded/</link>
		<description>Recent content in Embedded on tsvinc</description>
		<generator>Hugo</generator>
		<language>en-us</language>
		
		
		
		
			<lastBuildDate>Sun, 31 May 2026 00:00:00 +0000</lastBuildDate>
		
			<atom:link href="https://tsvinc.dev/tags/embedded/index.xml" rel="self" type="application/rss+xml" />
			<item>
				<title>Carving a SQLite database from a FiiO X1&#39;s raw NAND</title>
				<link>https://tsvinc.dev/posts/fiio-x1-twelve-thousand-songs-part-16/</link>
				<pubDate>Sun, 31 May 2026 00:00:00 +0000</pubDate>
				<guid>https://tsvinc.dev/posts/fiio-x1-twelve-thousand-songs-part-16/</guid>
				<description>&lt;p&gt;I went into the flash chip of a second-hand music player looking for the previous owner&#39;s music database, and the one intact database I found was mine. Six tracks, one album, from my own SD card. Stored inside it, as if nothing had happened, were ten favourites I had never set.&lt;/p&gt;&#xA;&lt;p&gt;The player is a FiiO X1, bought used, and its 128 MB NAND flash chip holds both the firmware and the player&#39;s own files. &lt;a href=&#34;https://tsvinc.dev/posts/fiio-x1-128mb-one-page-part-6/&#34;&gt;Part 6&lt;/a&gt; copied the whole chip over USB into one raw image; &lt;a href=&#34;https://tsvinc.dev/posts/fiio-x1-nand-archaeology-part-7/&#34;&gt;Part 7&lt;/a&gt; mapped it and found thousands of file paths from the old owner&#39;s library in its user-data blocks. The X1 runs HiBy&#39;s firmware on the µC/OS-II real-time kernel, and this part finds out what keeps those paths: which database engine, which tables, and how much of it can be read back.&lt;/p&gt;</description>
			</item>
			<item>
				<title>FiiO X1 EQ table: seven register bytes my generator never read</title>
				<link>https://tsvinc.dev/posts/fiio-x1-seven-wrong-bytes-part-15/</link>
				<pubDate>Sun, 31 May 2026 00:00:00 +0000</pubDate>
				<guid>https://tsvinc.dev/posts/fiio-x1-seven-wrong-bytes-part-15/</guid>
				<description>&lt;p&gt;My project notes for the FiiO X1 have a gotchas section, and for a while one line in it was wrong in a way that sounded careful. It said that the EQ coefficients I had generated in Q23, a 24-bit fixed-point format, broke the boot, so the PCM5142, the player&#39;s DAC chip, probably had constraints beyond the math of the Audio EQ Cookbook, the standard recipe for these filters.&lt;/p&gt;&#xA;&lt;p&gt;It reads like a finding. It was a shrug with a datasheet attached. I distrust a root cause the way I distrust a datasheet, by how much it explains without having been tested, and this one explained everything and had been tested against nothing.&lt;/p&gt;</description>
			</item>
			<item>
				<title>FiiO X1 FTL reversed: the map was in the OOB bytes I never dumped</title>
				<link>https://tsvinc.dev/posts/fiio-x1-margins-we-threw-away-part-18/</link>
				<pubDate>Sun, 31 May 2026 00:00:00 +0000</pubDate>
				<guid>https://tsvinc.dev/posts/fiio-x1-margins-we-threw-away-part-18/</guid>
				<description>&lt;p&gt;Rockbox&#39;s hwstub tools have a Lua function for NAND flash called &lt;code&gt;read_page&lt;/code&gt;, and it does exactly what it says. You give it a page and a length, it gives you that many bytes of the page. I gave it 2,048, the size of a page&#39;s data area, 65,536 times, and it never once complained. Good tool. The complaint is about the length.&lt;/p&gt;&#xA;&lt;p&gt;The flash belongs to a FiiO X1, a second-hand music player whose 128 MB NAND chip I copied in full over USB in &lt;a href=&#34;https://tsvinc.dev/posts/fiio-x1-128mb-one-page-part-6/&#34;&gt;Part 6&lt;/a&gt;, using hwstub, Rockbox&#39;s debug stub, and that function. Each page on this chip is 2,112 bytes: 2,048 of data and 64 of spare area, the OOB (&amp;quot;out of band&amp;quot;) bytes where flash keeps error-correction codes and bookkeeping. &lt;a href=&#34;https://tsvinc.dev/posts/fiio-x1-twelve-thousand-songs-part-16/&#34;&gt;Part 16&lt;/a&gt; carved a SQLite database out of that copy, found pages from different versions of the file side by side in it, and blamed the flash translation layer (FTL), the firmware code that decides which physical block holds which logical one. &amp;quot;The FTL did it&amp;quot; is the flash-storage version of &amp;quot;works on my machine&amp;quot;. This part reads the X1&#39;s FTL in the firmware to see what it actually does, and finds out where it keeps its map.&lt;/p&gt;</description>
			</item>
			<item>
				<title>FiiO X1 PCM5142 DSP blocks: the flat head I believed</title>
				<link>https://tsvinc.dev/posts/fiio-x1-flat-that-lied-part-17/</link>
				<pubDate>Sun, 31 May 2026 00:00:00 +0000</pubDate>
				<guid>https://tsvinc.dev/posts/fiio-x1-flat-that-lied-part-17/</guid>
				<description>&lt;p&gt;For the length of one hex dump, three blocks of DAC coefficients in the FiiO X1&#39;s firmware were solved. I had read the first 96 bytes of each, seen nothing but unity filters, which pass the signal through unchanged, and written down that all three were a flat, do-nothing initialization. Then the decoder read the rest.&lt;/p&gt;&#xA;&lt;p&gt;The X1 is a pocket music player from 2014 whose sound comes out of a TI PCM5142, a DAC with a small programmable DSP inside it. The firmware, HiBy&#39;s, on the µC/OS-II kernel, talks to the DAC over a three-wire serial line it drives by toggling GPIO pins; &lt;a href=&#34;https://tsvinc.dev/posts/fiio-x1-hidden-dac-part-9/&#34;&gt;Part 9&lt;/a&gt; found that line and the equalizer table behind it, and &lt;a href=&#34;https://tsvinc.dev/posts/fiio-x1-seven-wrong-bytes-part-15/&#34;&gt;Part 15&lt;/a&gt; decoded that table. My notes on the audio code still listed three other blocks as undecoded: 2,708 bytes each, sitting just below the equalizer table in memory, streamed to the DAC every time the firmware opens an audio stream. Nobody puts 8 KB of nothing in a firmware image and then sends it to a chip on every stream, which should have been my first clue.&lt;/p&gt;</description>
			</item>
			<item>
				<title>FiiO X1 bare-metal audio: four GPIO offsets, all wrong</title>
				<link>https://tsvinc.dev/posts/fiio-x1-three-wrong-numbers-part-14/</link>
				<pubDate>Tue, 26 May 2026 00:00:00 +0000</pubDate>
				<guid>https://tsvinc.dev/posts/fiio-x1-three-wrong-numbers-part-14/</guid>
				<description>&lt;p&gt;The build was clean: 18,672 bytes, nothing worse than unused-function warnings. A clean build is the most reassuring thing a compiler can say and the least informative, and this one was vouching for an audio driver that, as written, could not have made a sound.&lt;/p&gt;&#xA;&lt;p&gt;The driver belongs to a bootloader for the FiiO X1, a $99 music player on an Ingenic JZ4760B MIPS chip. &lt;a href=&#34;https://tsvinc.dev/posts/fiio-x1-twelve-kilobytes-part-13/&#34;&gt;Part 13&lt;/a&gt; wrote that bootloader: it runs from the SD card as &lt;code&gt;factory.x1&lt;/code&gt;, a file the stock loader executes without checking, and it can load an image of Rockbox, the open-source player firmware. The next step toward Rockbox is sound, and the smallest honest test of sound is a sine wave from bare-metal code: no codecs, no DMA, no mixer. As far as I knew then, that meant three things the stock loader, which doesn&#39;t play music, has no reason to set up: an I2C controller to configure the TI PCM5142 DAC, the AIC (the chip&#39;s audio interface) to send it I2S samples, and the GPIO pins both of them need.&lt;/p&gt;</description>
			</item>
			<item>
				<title>FiiO X1 Rockbox bootloader: 12 KB of SD, FAT32 and a menu</title>
				<link>https://tsvinc.dev/posts/fiio-x1-twelve-kilobytes-part-13/</link>
				<pubDate>Mon, 25 May 2026 00:00:00 +0000</pubDate>
				<guid>https://tsvinc.dev/posts/fiio-x1-twelve-kilobytes-part-13/</guid>
				<description>&lt;p&gt;The first critical finding in the code review said my bootloader would jump into the firmware it had just loaded and execute whatever the instruction cache happened to remember instead. The code compiled. It had a boot menu, an SD card driver and a FAT32 reader, and it would have crashed on the one line that mattered, silently, on a device with no debugger.&lt;/p&gt;&#xA;&lt;p&gt;The device is the FiiO X1, a $99 music player on an Ingenic JZ4760B MIPS chip. &lt;a href=&#34;https://tsvinc.dev/posts/fiio-x1-ten-bands-part-12/&#34;&gt;Part 12&lt;/a&gt; replaced its equalizer presets by patching floats inside the stock firmware, and it worked, for exactly nine presets, because the firmware has nine slots. More slots would mean finding the menu code in Ghidra and patching loop bounds and arrays, and every feature after that would cost the same again. The alternative is Rockbox, the open-source player firmware, and Rockbox needs a way onto the device. This part is that way in: a small bootloader that runs from the SD card, offers a menu, and loads a Rockbox image from the card into RAM.&lt;/p&gt;</description>
			</item>
			<item>
				<title>FiiO X1 EQ presets: floats in .data, not a NAND cache</title>
				<link>https://tsvinc.dev/posts/fiio-x1-ten-bands-part-12/</link>
				<pubDate>Sun, 25 Jan 2026 00:00:00 +0000</pubDate>
				<guid>https://tsvinc.dev/posts/fiio-x1-ten-bands-part-12/</guid>
				<description>&lt;p&gt;Where does a music player keep its equalizer presets? On the FiiO X1 I had a confident answer, a cache on the flash chip that the firmware writes once and trusts forever, and the answer had cost me an evening of flashing.&lt;/p&gt;&#xA;&lt;p&gt;The X1 is a $99 player on an Ingenic JZ4760B MIPS chip, running firmware that HiBy wrote on the µC/OS-II real-time kernel. In &lt;a href=&#34;https://tsvinc.dev/posts/fiio-x1-the-patcher-part-10/&#34;&gt;Part 10&lt;/a&gt; I built a patcher that rewrites &lt;code&gt;sys.bin&lt;/code&gt;, the firmware binary inside the official update file &lt;code&gt;x1.fw&lt;/code&gt;, and the one change I wanted most showed up once, one slot off, and never again after I fixed the slot: I patched the preset gains, flashed, opened the EQ screen, and Rock still read all zeros. Meanwhile my custom EQ setting and my theme choice had survived the flash. So I wrote down a theory: the presets live in a cache on the NAND&#39;s &lt;code&gt;userdata&lt;/code&gt; partition, and the values in the binary only matter the first time. This part reads the firmware&#39;s EQ code in Ghidra, a disassembler and decompiler, instead of guessing at it.&lt;/p&gt;</description>
			</item>
			<item>
				<title>FiiO X1 bootloader in Ghidra: 94 functions at the wrong base</title>
				<link>https://tsvinc.dev/posts/fiio-x1-source-part-11/</link>
				<pubDate>Sun, 07 Sep 2025 00:00:00 +0000</pubDate>
				<guid>https://tsvinc.dev/posts/fiio-x1-source-part-11/</guid>
				<description>&lt;p&gt;I believed Ghidra, the disassembler and decompiler I read firmware with, when it said the bootloader had 94 functions. In this series, believing a tool is how most mistakes start, and this one started early.&lt;/p&gt;&#xA;&lt;p&gt;The bootloader is the SPL, the second-stage loader of the FiiO X1, a $99 music player built on an Ingenic JZ4760B MIPS chip. The boot ROM runs the IPL, an 8 KB stage that wakes the RAM (&lt;a href=&#34;https://tsvinc.dev/posts/fiio-x1-8kb-gold-part-4/&#34;&gt;Part 4&lt;/a&gt;); the IPL loads the SPL, 1 MB of code; the SPL loads &lt;code&gt;sys.bin&lt;/code&gt;, the 4.1 MB firmware that plays the music. I had a copy of the SPL from the flash chip, and after &lt;a href=&#34;https://tsvinc.dev/posts/fiio-x1-the-patcher-part-10/&#34;&gt;Part 10&lt;/a&gt;, where my equalizer builds stopped at the boot screen, broke playback, or showed up one slot off and then not at all, I wanted the boot chain mapped: what the SPL initializes, what it checks before it runs the firmware, and what it leaves behind for whatever runs next. A port of Rockbox, the open-source firmware for music players, would need all three answers.&lt;/p&gt;</description>
			</item>
			<item>
				<title>FiiO X1 firmware patcher: everything but the EQ</title>
				<link>https://tsvinc.dev/posts/fiio-x1-the-patcher-part-10/</link>
				<pubDate>Fri, 23 May 2025 00:00:00 +0000</pubDate>
				<guid>https://tsvinc.dev/posts/fiio-x1-the-patcher-part-10/</guid>
				<description>&lt;p&gt;Two of the things I changed in the FiiO X1&#39;s firmware showed up on the device. The one I cared about most, the equalizer, made it onto the screen exactly once, one slot off, and never again.&lt;/p&gt;&#xA;&lt;p&gt;The X1 is a $99 music player whose firmware, written by HiBy, runs on an Ingenic JZ4760B MIPS chip and drives a TI PCM5142 DAC. &lt;a href=&#34;https://tsvinc.dev/posts/fiio-x1-hidden-dac-part-9/&#34;&gt;Part 9&lt;/a&gt; found switches the factory left off: gapless playback, a USB DAC mode. The obvious next step was a tool that takes the official update file, &lt;code&gt;x1.fw&lt;/code&gt;, changes it, and puts it back together so the player&#39;s own bootloader will flash it. This is that tool, and the list of what it could and could not do.&lt;/p&gt;</description>
			</item>
			<item>
				<title>FiiO X1 firmware: a hidden USB DAC mode and a debug shell</title>
				<link>https://tsvinc.dev/posts/fiio-x1-hidden-dac-part-9/</link>
				<pubDate>Sat, 15 Mar 2025 00:00:00 +0000</pubDate>
				<guid>https://tsvinc.dev/posts/fiio-x1-hidden-dac-part-9/</guid>
				<description>&lt;p&gt;FiiO&#39;s manual for the first-generation X1 and the 2014 Headfonics review agree on one thing about its USB port: it is a disk. Plug the $99 music player into a computer and it mounts as storage. If you want a USB DAC, the player working as your computer&#39;s sound card, the review says to look elsewhere, because &amp;quot;FiiO stripped that right out&amp;quot;.&lt;/p&gt;&#xA;&lt;p&gt;I read spec sheets the way I read a boot ROM&#39;s promises: by what they leave out. A missing feature on a cheap model of a product line is often still in the code, just not on the box. I had the X1&#39;s firmware decrypted, and &lt;a href=&#34;https://tsvinc.dev/posts/fiio-x1-nand-archaeology-part-7/&#34;&gt;Part 7&lt;/a&gt; had matched it byte for byte against the copy on the chip, so it was time to check.&lt;/p&gt;</description>
			</item>
			<item>
				<title>FiiO X1 NAND: 44 KB of high-entropy data, identified</title>
				<link>https://tsvinc.dev/posts/fiio-x1-last-44-kilobytes-part-8/</link>
				<pubDate>Thu, 12 Oct 2023 00:00:00 +0000</pubDate>
				<guid>https://tsvinc.dev/posts/fiio-x1-last-44-kilobytes-part-8/</guid>
				<description>&lt;p&gt;The last unidentified thing on the flash chip was 43,535 bytes long, and it looked exactly like encryption.&lt;/p&gt;&#xA;&lt;p&gt;The chip is the 128MB NAND of a FiiO X1, a $99 music player on an Ingenic JZ4760B, and by now I had a raw dump of all of it and a map that named almost every block: boot chain, firmware code (&lt;code&gt;sys.bin&lt;/code&gt;), fonts, the previous owner&#39;s music library (&lt;a href=&#34;https://tsvinc.dev/posts/fiio-x1-nand-archaeology-part-7/&#34;&gt;Part 7&lt;/a&gt; has the tour). One gap was left on the legend. The first 43,535 bytes of block 64, pages 0 through 20, where the boot resource partition begins.&lt;/p&gt;</description>
			</item>
			<item>
				<title>FiiO X1 NAND map: fonts, plaintext and a stranger&#39;s music</title>
				<link>https://tsvinc.dev/posts/fiio-x1-nand-archaeology-part-7/</link>
				<pubDate>Wed, 30 Aug 2023 00:00:00 +0000</pubDate>
				<guid>https://tsvinc.dev/posts/fiio-x1-nand-archaeology-part-7/</guid>
				<description>&lt;p&gt;422 of the 1,024 blocks on the flash chip were holding data. My map said 128.&lt;/p&gt;&#xA;&lt;p&gt;The chip belongs to a FiiO X1, a $99 portable music player whose Ingenic JZ4760B MIPS chip runs FiiO&#39;s firmware on the µC/OS-II real-time kernel, and the map was mine: the first pass over a raw 128MB dump of its NAND flash, taken over USB with Rockbox&#39;s hwstub debug stub in &lt;a href=&#34;https://tsvinc.dev/posts/fiio-x1-128mb-one-page-part-6/&#34;&gt;Part 6&lt;/a&gt;. The goal is the firmware code itself, &lt;code&gt;sys.bin&lt;/code&gt;, which I hadn&#39;t found. That map marked everything past block 215 as erased userdata. I had not opened those blocks. I had looked at where they sat and written down what I expected.&lt;/p&gt;</description>
			</item>
			<item>
				<title>FiiO X1 NAND dump: 128 MB over USB, one block at a time</title>
				<link>https://tsvinc.dev/posts/fiio-x1-128mb-one-page-part-6/</link>
				<pubDate>Wed, 19 Jul 2023 00:00:00 +0000</pubDate>
				<guid>https://tsvinc.dev/posts/fiio-x1-128mb-one-page-part-6/</guid>
				<description>&lt;p&gt;Two programs set out to copy the whole flash chip of a FiiO X1, a $99 music player built around an Ingenic JZ4760B MIPS chip, so its firmware could be studied off the device. The first was 4,384 bytes of bare-metal C that would run from the player&#39;s SD card, read every NAND page and write it straight back to the card, with an estimated finish in a few minutes. The second was a shell script that asks Rockbox&#39;s hwstub debug stub for one block at a time over USB.&lt;/p&gt;</description>
			</item>
			<item>
				<title>FiiO X1 factory.x1: unsigned code execution from the SD card</title>
				<link>https://tsvinc.dev/posts/fiio-x1-door-always-open-part-5/</link>
				<pubDate>Mon, 08 May 2023 00:00:00 +0000</pubDate>
				<guid>https://tsvinc.dev/posts/fiio-x1-door-always-open-part-5/</guid>
				<description>&lt;p&gt;Put a file named &lt;code&gt;factory.x1&lt;/code&gt; on the SD card of a FiiO X1, a portable music player built on an Ingenic JZ4760B MIPS chip, boot it down the recovery path (the button-combination boot that also installs firmware updates from the card), and its bootloader jumps into that file without asking for a signature. I read that in a decompiled bootloader after four parts of getting in the hard way: the chip&#39;s boot ROM over USB, a RAM (DDR) capture from live registers, a NAND flash dump with the hwstub debug stub from Rockbox (the open-source player firmware project), and a decompiled IPL, the first-stage loader that sets up RAM. The stage after the IPL had been willing all along.&lt;/p&gt;</description>
			</item>
			<item>
				<title>FiiO X1 IPL decompiled: 8 KB of JZ4760B DDR init code</title>
				<link>https://tsvinc.dev/posts/fiio-x1-8kb-gold-part-4/</link>
				<pubDate>Wed, 22 Mar 2023 00:00:00 +0000</pubDate>
				<guid>https://tsvinc.dev/posts/fiio-x1-8kb-gold-part-4/</guid>
				<description>&lt;p&gt;Every time a FiiO X1 boots cold, the first code it reads from flash prints its own build date to a serial port that nothing is connected to. That code is the IPL, the initial program loader of this Ingenic JZ4760B-based music player, and it holds the RAM setup that the developer of the Rockbox port (open-source music-player firmware) wrote on its forum he couldn&#39;t find. &lt;a href=&#34;https://tsvinc.dev/posts/fiio-x1-fee1dead-part-3/&#34;&gt;Part 3&lt;/a&gt; dumped it from NAND flash with Rockbox&#39;s hwstub debug stub. Now it goes into Ghidra, the NSA&#39;s open-source decompiler, function by function.&lt;/p&gt;</description>
			</item>
			<item>
				<title>FiiO X1 NAND dump: hwstub, fee1:dead and a 64-bit compiler</title>
				<link>https://tsvinc.dev/posts/fiio-x1-fee1dead-part-3/</link>
				<pubDate>Sat, 14 Jan 2023 00:00:00 +0000</pubDate>
				<guid>https://tsvinc.dev/posts/fiio-x1-fee1dead-part-3/</guid>
				<description>&lt;p&gt;The tool that finally dumped the flash is 12.4KB of someone else&#39;s firmware, built with a compiler meant for a different target, after the canonical route had failed at every step I tried. The flash belongs to a FiiO X1, a portable music player on an Ingenic JZ4760B MIPS chip, and it holds the IPL: the initial program loader that sets up the player&#39;s RAM at power-on, and the piece the Rockbox port (open-source music-player firmware) has been missing.&lt;/p&gt;</description>
			</item>
			<item>
				<title>FiiO X1 DDR deadlock: reading RAM config from live registers</title>
				<link>https://tsvinc.dev/posts/fiio-x1-ddr-deadlock-part-2/</link>
				<pubDate>Sat, 05 Nov 2022 00:00:00 +0000</pubDate>
				<guid>https://tsvinc.dev/posts/fiio-x1-ddr-deadlock-part-2/</guid>
				<description>&lt;p&gt;I assumed the DDR parameters would be in the firmware update file. Firmware has to get RAM working somehow, and the update file was the one thing I already had. Those parameters, the timings the memory controller needs before RAM works, are the one thing keeping Rockbox, the open-source music-player firmware, off the FiiO X1, a portable music player built on an Ingenic JZ4760B MIPS32 chip. The Rockbox port has wanted them for years, and they are in no public datasheet.&lt;/p&gt;</description>
			</item>
			<item>
				<title>FiiO X1 boot ROM: running code on a JZ4760B with no RAM</title>
				<link>https://tsvinc.dev/posts/fiio-x1-boot-rom-part-1/</link>
				<pubDate>Sat, 17 Sep 2022 00:00:00 +0000</pubDate>
				<guid>https://tsvinc.dev/posts/fiio-x1-boot-rom-part-1/</guid>
				<description>&lt;p&gt;&lt;em&gt;Assumes familiarity with memory-mapped I/O and register-level hardware programming. MIPS knowledge helps but isn&#39;t required — the constraints are the story, not the instruction set.&lt;/em&gt;&lt;/p&gt;&#xA;&lt;p&gt;How do you read the code that wakes up a machine&#39;s RAM, when that code sits on flash you can&#39;t read until the RAM is awake? That question is the whole problem with the FiiO X1, a portable music player built on an Ingenic JZ4760B (a MIPS32 chip, which the player&#39;s own boot loader sets to 384MHz) that runs FiiO&#39;s firmware on the µC/OS-II real-time kernel. Rockbox, the open-source firmware that replaces the stock software on music players, has had an unfinished X1 port for years. What stalled it is the DDR initialization parameters: the timing values the chip needs before its RAM works at all, which nobody outside FiiO knows.&lt;/p&gt;</description>
			</item>
	</channel>
</rss>
