<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
	<channel>
		<title>Hardware on tsvinc</title>
		<link>https://tsvinc.dev/tags/hardware/</link>
		<description>Recent content in Hardware on tsvinc</description>
		<generator>Hugo</generator>
		<language>en-us</language>
		
		
		
		
			<lastBuildDate>Mon, 26 Oct 2026 00:00:00 +0000</lastBuildDate>
		
			<atom:link href="https://tsvinc.dev/tags/hardware/index.xml" rel="self" type="application/rss+xml" />
			<item>
				<title>The 3090 unlock is real. Where it lands is the catch</title>
				<link>https://tsvinc.dev/posts/rtx-3090-unlock-what-it-buys/</link>
				<pubDate>Mon, 26 Oct 2026 00:00:00 +0000</pubDate>
				<guid>https://tsvinc.dev/posts/rtx-3090-unlock-what-it-buys/</guid>
				<description>&lt;p&gt;NVIDIA sells the GeForce RTX 3090 with one kind of tensor math running at half speed: FP16 and BF16 multiplies that accumulate in FP32, and TF32, issue at half the rate of the professional cards built on the same silicon. &lt;code&gt;gpu_unlock&lt;/code&gt;, an open-source kernel module, puts the full rate back at every boot. My 3090 runs headless at home as a local LLM box, mostly serving one 27-billion-parameter coding model through llama.cpp, so the question was what doubling that math buys the work I actually do. I approach every free speedup the same way: I assume the bill is somewhere, and I go looking for it.&lt;/p&gt;</description>
			</item>
			<item>
				<title>The pin template was for a different plug</title>
				<link>https://tsvinc.dev/posts/tuya-plug-openbeken-grid-canary/</link>
				<pubDate>Fri, 16 Oct 2026 00:00:00 +0000</pubDate>
				<guid>https://tsvinc.dev/posts/tuya-plug-openbeken-grid-canary/</guid>
				<description>&lt;p&gt;The smart plug&#39;s vendor app put its energy monitoring behind a subscription. The metering chip inside measures voltage alongside current and power all the same, whatever the app agrees to show. I wanted that voltage. Read off a plug on wall power, it says whether the grid is up, and my &lt;a href=&#34;https://tsvinc.dev/posts/bluetti-ac180-ups-over-bluetooth/&#34;&gt;power-station UPS&lt;/a&gt; needed exactly that: a second opinion on the grid from something not behind a battery.&lt;/p&gt;&#xA;&lt;p&gt;So the plug had to leave Tuya&#39;s firmware for &lt;a href=&#34;https://github.com/openshwprojects/OpenBK7231T_App&#34;&gt;OpenBeken&lt;/a&gt;, an open firmware for the Beken chips in most Tuya plugs. That took one evening. Getting the pins right took the next day, because the template I borrowed was for a different plug, and the plug&#39;s own leftover settings were the only thing that knew.&lt;/p&gt;</description>
			</item>
			<item>
				<title>Installing Fedora on a computer that hadn&#39;t shipped yet</title>
				<link>https://tsvinc.dev/posts/installed-before-it-shipped/</link>
				<pubDate>Sat, 10 Oct 2026 00:00:00 +0000</pubDate>
				<guid>https://tsvinc.dev/posts/installed-before-it-shipped/</guid>
				<description>&lt;p&gt;The oldest boot in this machine&#39;s journal is from 5 October 2026. The machine first ran Fedora on 9 October. The twelve boots before that happened on a different computer.&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-console&#34; data-lang=&#34;console&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;gp&#34;&gt;$&lt;/span&gt; journalctl --list-boots --no-pager &lt;span class=&#34;p&#34;&gt;|&lt;/span&gt; head -14&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;go&#34;&gt;IDX BOOT ID                          FIRST ENTRY                  LAST ENTRY&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;go&#34;&gt;-29 f657a145f7974ea18abc160d1e2be8d6 Mon 2026-10-05 16:58:39 EEST Mon 2026-10-05 16:59:24 EEST&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;go&#34;&gt;-28 cfcb877ad4144b119517fa1c642346da Mon 2026-10-05 16:59:48 EEST Mon 2026-10-05 17:00:52 EEST&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;go&#34;&gt;-27 36916dd2ff404daca7a8d530c48cbb5c Mon 2026-10-05 17:01:17 EEST Mon 2026-10-05 17:02:00 EEST&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;go&#34;&gt;-26 b91bc2749b424de590c0be29c42c55ef Tue 2026-10-06 02:59:57 EEST Tue 2026-10-06 00:06:08 EEST&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;go&#34;&gt;-25 2f3c0e624c9149dcb68e48f5fb76f584 Tue 2026-10-06 00:16:04 EEST Tue 2026-10-06 00:18:53 EEST&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;go&#34;&gt;-24 bc4795b434bb42e9b639371238174409 Tue 2026-10-06 09:43:36 EEST Tue 2026-10-06 11:06:32 EEST&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;go&#34;&gt;-23 9f2f8229067a414080a2b3c7246ecabd Tue 2026-10-06 11:07:03 EEST Tue 2026-10-06 11:08:09 EEST&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;go&#34;&gt;-22 d756260ad69a48e484907b5f6851e066 Tue 2026-10-06 15:20:22 EEST Tue 2026-10-06 15:22:12 EEST&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;go&#34;&gt;-21 83d51b23ac24402b84dcac40d041cfc9 Tue 2026-10-06 15:22:36 EEST Tue 2026-10-06 15:23:03 EEST&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;go&#34;&gt;-20 25132501db5f4ec8a01c52d7e5d8ec88 Tue 2026-10-06 15:58:15 EEST Tue 2026-10-06 16:35:01 EEST&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;go&#34;&gt;-19 52412be57f7546849748767f08cc6791 Tue 2026-10-06 19:12:31 EEST Tue 2026-10-06 22:31:30 EEST&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;go&#34;&gt;-18 9101d8c8bc204e8a8ff190db670a7bc8 Tue 2026-10-06 22:31:55 EEST Tue 2026-10-06 22:33:03 EEST&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;go&#34;&gt;-17 4e6687cdb2084df0885005a1dec46dce Fri 2026-10-09 13:29:55 EEST Fri 2026-10-09 11:06:18 EEST&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Boots −29 through −18 all ran on a GEEKOM AE8, booting a USB disk the A9 Mega had never seen: eleven under QEMU, one (−26) on the AE8&#39;s own hardware. Boot −17 is the A9 Mega itself, for the first time. By its own clock it starts at 13:29 and ends at 11:06 the same day.&lt;/p&gt;</description>
			</item>
			<item>
				<title>All four drives at once, and why that was the good news</title>
				<link>https://tsvinc.dev/posts/all-four-drives-at-once/</link>
				<pubDate>Wed, 08 Jul 2026 00:00:00 +0000</pubDate>
				<guid>https://tsvinc.dev/posts/all-four-drives-at-once/</guid>
				<description>&lt;div class=&#34;tldr&#34;&gt;&#xA;  &lt;span class=&#34;tldr-label&#34;&gt;TL;DR&lt;/span&gt;&#xA;  N drives failing &lt;em&gt;at the same instant&lt;/em&gt; is never N drive failures. It&#39;s a shared path: driver, cable, or power. Here it was two of them stacked — a partial kernel update that installed &lt;code&gt;kernel-core&lt;/code&gt; without &lt;code&gt;kernel-modules&lt;/code&gt;, stranding &lt;code&gt;mpt3sas&lt;/code&gt;, and underneath it a loose SFF-8087 cable with every PHY negotiating at unknown link rate. The boot SSD was on a different controller and never blinked, which was the clue. No data was lost.&#xA;&lt;/div&gt;&#xA;&#xA;&lt;p&gt;I asked in four words, flat, no punctuation to speak of, with the calm of someone who hasn&#39;t decided yet how bad it is:&lt;/p&gt;</description>
			</item>
			<item>
				<title>Salvaging a dying disk by partitioning around the wound</title>
				<link>https://tsvinc.dev/posts/partition-around-the-wound/</link>
				<pubDate>Wed, 08 Jul 2026 00:00:00 +0000</pubDate>
				<guid>https://tsvinc.dev/posts/partition-around-the-wound/</guid>
				<description>&lt;div class=&#34;tldr&#34;&gt;&#xA;  &lt;span class=&#34;tldr-label&#34;&gt;TL;DR&lt;/span&gt;&#xA;  &lt;code&gt;badblocks&lt;/code&gt; tells you &lt;em&gt;how many&lt;/em&gt; bad blocks; its &lt;code&gt;-o&lt;/code&gt; logfile tells you &lt;em&gt;where&lt;/em&gt;, and that changes the verdict. All 58 failures on this 8 TB drive sat in the first 5.23% of the platter. Partition past the wound, leave the damaged region unallocated, and the healthy 94.8% — about 6.7 TiB — goes back to work as disposable scratch space. Not for anything you&#39;d miss.&#xA;&lt;/div&gt;&#xA;&#xA;&lt;p&gt;Every &lt;code&gt;badblocks&lt;/code&gt; run leaves two artifacts, and the useful one is the file almost nobody opens. There&#39;s the summary line everyone reads:&lt;/p&gt;</description>
			</item>
			<item>
				<title>Stop chasing a sensor that doesn&#39;t exist</title>
				<link>https://tsvinc.dev/posts/stop-chasing-a-sensor-that-doesnt-exist/</link>
				<pubDate>Wed, 08 Jul 2026 00:00:00 +0000</pubDate>
				<guid>https://tsvinc.dev/posts/stop-chasing-a-sensor-that-doesnt-exist/</guid>
				<description>&lt;div class=&#34;tldr&#34;&gt;&#xA;  &lt;span class=&#34;tldr-label&#34;&gt;TL;DR&lt;/span&gt;&#xA;  The LSI SAS2008 has no temperature sensor. Not an unsupported one, not one behind a proprietary tool — the silicon was never fabricated with it. No driver, &lt;code&gt;lsiutil&lt;/code&gt; option, or sysfs path will summon it. Once you can tell &amp;quot;hard to monitor&amp;quot; from &amp;quot;impossible to monitor,&amp;quot; the answer stops being another tool and starts being airflow. The same night&#39;s fan experiment made the point twice: the null result &lt;em&gt;was&lt;/em&gt; the diagnosis.&#xA;&lt;/div&gt;&#xA;&#xA;&lt;p&gt;The storage box runs every HDD through one LSI SAS2008 HBA -- a 9211-8i-class card in IT mode, &lt;code&gt;mpt3sas&lt;/code&gt;, sitting at PCI &lt;code&gt;05:00.0&lt;/code&gt;. These things are fanless, run hot, and are famous for slow-cooking themselves in dead-air cases. So the instinct is obvious: monitor the chip temperature. Graph it, alert on it, normal ops hygiene.&lt;/p&gt;</description>
			</item>
			<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>HTC Desire Z screenshots on Android 2.3, the long way round</title>
				<link>https://tsvinc.dev/posts/screenshot-toast-war/</link>
				<pubDate>Sun, 09 Jun 2024 00:00:00 +0000</pubDate>
				<guid>https://tsvinc.dev/posts/screenshot-toast-war/</guid>
				<description>&lt;p&gt;How hard is it to get a picture of a phone&#39;s screen when you have root on the phone, its bootloader unlocked and a keyboard attached? I needed a few screenshots of MeshLauncher, the home-screen app I had just written for my HTC Desire Z, a 2010 Android slider with a four-row keyboard that I&#39;m rebuilding around software of my own. It runs CyanogenMod 7, the community build of Android 2.3, and on that ROM the honest answer is: hard enough that I ended up signing my app with Google&#39;s published platform key. Phones that refuse simple requests get my full attention, which has never once been good news for the phone or for my evening.&lt;/p&gt;</description>
			</item>
			<item>
				<title>HTC Desire Z launcher keys: ADW&#39;s Back trick and two wrong fixes</title>
				<link>https://tsvinc.dev/posts/25kb-launcher-part-2/</link>
				<pubDate>Tue, 02 Apr 2024 00:00:00 +0000</pubDate>
				<guid>https://tsvinc.dev/posts/25kb-launcher-part-2/</guid>
				<description>&lt;p&gt;Android&#39;s documentation for &lt;code&gt;KeyEvent.FLAG_CANCELED&lt;/code&gt; says, in one paragraph, that an app should not act on a key until the key&#39;s up event arrives, because the press can still be canceled. I learned that rule the expensive way: my launcher acted on Back too early, I went looking in someone else&#39;s launcher for the fix, found it, and only afterwards read the paragraph that had been sitting in the class I was calling all along.&lt;/p&gt;</description>
			</item>
			<item>
				<title>A 22 KB home screen for the HTC Desire Z, built without Gradle</title>
				<link>https://tsvinc.dev/posts/22kb-launcher/</link>
				<pubDate>Fri, 15 Mar 2024 00:00:00 +0000</pubDate>
				<guid>https://tsvinc.dev/posts/22kb-launcher/</guid>
				<description>&lt;p&gt;I assumed the cheap way to get a small launcher was to take an existing one and cut. The phone is an HTC Desire Z, the 2010 Android slider with a full keyboard, which I&#39;m turning into a field device for a mesh network, on CyanogenMod 7, the community build of Android 2.3. Its home screen app is ADWLauncher, and I had its source open on my desk with every intention of carving it down to three screens.&lt;/p&gt;</description>
			</item>
			<item>
				<title>HTC Desire Z Home button dead on CM7: nine suspects, no culprit</title>
				<link>https://tsvinc.dev/posts/home-key-archaeology/</link>
				<pubDate>Mon, 08 Jan 2024 00:00:00 +0000</pubDate>
				<guid>https://tsvinc.dev/posts/home-key-archaeology/</guid>
				<description>&lt;p&gt;Above the Home-key code in Android 2.3&#39;s window manager sits a comment promising that the system handles Home itself, &amp;quot;so applications can never break it&amp;quot;. I have read that comment many times now, and every time I agree with it a little less. On my HTC Desire Z, a 2010 Android slider I keep around for its keyboard, running CyanogenMod 7 (the community build of Android 2.3), Home did nothing at all. No crash, no wrong app, no delay. A tap on the leftmost button below the screen, a little feedback from the phone, and then the same app still sitting there.&lt;/p&gt;</description>
			</item>
			<item>
				<title>Android test-keys: system UID with AOSP&#39;s public platform key</title>
				<link>https://tsvinc.dev/posts/aosp-test-keys/</link>
				<pubDate>Fri, 22 Dec 2023 00:00:00 +0000</pubDate>
				<guid>https://tsvinc.dev/posts/aosp-test-keys/</guid>
				<description>&lt;p&gt;My HTC Desire Z, a 2010 slider phone, runs CyanogenMod 7, a custom build of Android 2.3, and I wanted my own small apps on it to do system things: toggle GPS from a background service, take screenshots without the su toast popping up each time. The usual route is root, and root on this ROM announces itself every time, like a butler who insists on reading your name out at your own front door. This part is the other route, which the ROM leaves open by default.&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>HTC Desire Z: three black screens and a brightness of 30</title>
				<link>https://tsvinc.dev/posts/brightness-30/</link>
				<pubDate>Wed, 27 Sep 2023 00:00:00 +0000</pubDate>
				<guid>https://tsvinc.dev/posts/brightness-30/</guid>
				<description>&lt;p&gt;I keep a ledger for nights like this one. The entry reads: three black screens in twenty-two minutes, three different exits, and a number attached to exactly one of them. The phone is an HTC Desire Z, the 2010 Android slider with a keyboard that I&#39;m slimming down into a mesh-network node, running CyanogenMod 7, the community build of Android 2.3. I had just pulled most of its system apps and was writing my own home screen for it, and three times the screen came back lit and empty: backlight on, nothing drawn.&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>Debloating Android 2.3 on the HTC Desire Z: use pm disable</title>
				<link>https://tsvinc.dev/posts/cm7-bloat-dependency-map/</link>
				<pubDate>Thu, 03 Aug 2023 00:00:00 +0000</pubDate>
				<guid>https://tsvinc.dev/posts/cm7-bloat-dependency-map/</guid>
				<description>&lt;p&gt;The careful way to strip a ROM is to delete nothing, and that is how I set out to strip CyanogenMod 7.2.0, the community build of Android 2.3, on an HTC Desire Z: a 2010 phone with a slide-out keyboard that I&#39;m turning into a pocket terminal for a mesh network. Every unwanted system APK would move out of &lt;code&gt;/system/app/&lt;/code&gt; into &lt;code&gt;/system/app-disabled/&lt;/code&gt;. Reversible, nothing deleted, every step undone with one &lt;code&gt;mv&lt;/code&gt;.&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>HTC Desire Z bought for parts: S-OFF, SuperCID and 4202 mV</title>
				<link>https://tsvinc.dev/posts/4202-millivolts/</link>
				<pubDate>Fri, 14 Jul 2023 00:00:00 +0000</pubDate>
				<guid>https://tsvinc.dev/posts/4202-millivolts/</guid>
				<description>&lt;p&gt;It arrived as a parts phone: an HTC Desire Z, the 2010 Android slider with a four-row hardware keyboard, bought as a donor for whatever still worked. I expected a cracked digitizer and a battery swollen into a pillow. Then I plugged it in, and every question I asked it over USB came back with better news than the last. When a phone from 2010 starts doing that, I stop enjoying it and start keeping notes.&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>
