<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0" xmlns:media="http://search.yahoo.com/mrss/"><channel><title><![CDATA[Lorenz Brun]]></title><description><![CDATA[Random bits from a systems person]]></description><link>https://lorenz.brun.one/</link><image><url>https://lorenz.brun.one/favicon.png</url><title>Lorenz Brun</title><link>https://lorenz.brun.one/</link></image><generator>Ghost 4.48</generator><lastBuildDate>Wed, 12 Feb 2025 14:48:40 GMT</lastBuildDate><atom:link href="https://lorenz.brun.one/rss/" rel="self" type="application/rss+xml"/><ttl>60</ttl><item><title><![CDATA[Mass-erasing TI CC13xx/CC26xx chips with OpenOCD]]></title><description><![CDATA[<p>I recently had some custom PCBs made to be able to integrate a ZigBee coordinator into the Banana Pi R3 router. The currently best-supported SoC for running ZigBee coordinators is widely considered to be TI&apos;s CC2652P2 due to its generous RAM and Flash as well as the integrated</p>]]></description><link>https://lorenz.brun.one/mass-erasing-ti-cc26xx-13xx/</link><guid isPermaLink="false">63a8d778dfc7d50001c5d842</guid><dc:creator><![CDATA[Lorenz Brun]]></dc:creator><pubDate>Mon, 26 Dec 2022 00:07:53 GMT</pubDate><content:encoded><![CDATA[<p>I recently had some custom PCBs made to be able to integrate a ZigBee coordinator into the Banana Pi R3 router. The currently best-supported SoC for running ZigBee coordinators is widely considered to be TI&apos;s CC2652P2 due to its generous RAM and Flash as well as the integrated 20dBm power amplifier. To minimize the BOM I went with a <a href="https://www.szrfstar.com/product/237-en.html">RF-BM-2652P2</a> module from RF-Star containing just that SoC and only two additional connectors, a female header to plug into the R3&apos;s GPIO and a 10-pin ARM-style JTAG connector for initial programming.</p><p>When I got the boards I soldered the module as well as the two connectors and hooked the JTAG up to my FT2232H breakout board as the official XDS110 probes from TI are currently unavailable due to a chip shortage. Theoretically this should work as OpenOCD has support for both the probe as well as the SoC. But with neither OpenOCD 0.11 nor 0.12-rc3 the JTAG chain even probed correctly. Here I should note that TI has made the (questionable) design decision to only support the relatively-uncommon cJTAG (a 2-wire variant of JTAG) by default, requiring a set of specific scan codes sent into the cJTAG interface to actually switch into normal 4-wire JTAG operation. This evidently didn&apos;t work as I didn&apos;t see any activity on the TDI wire. After lots of googling I eventually stumbled upon <a href="https://sourceforge.net/p/openocd/tickets/375/">issue #375</a> in the OpenOCD issue tracker which recommended adding <code>runtest 20</code> before the cJTAG to JTAG transition sequence. This just runs 20 idle cycles on the JTAG interface before starting the transition, essentially JTAG&apos;s equivalent of putting a sleep into code. Patching this into my OpenOCD configuration made the JTAG chain work immediately.</p><p>But I wasn&apos;t done yet. While the SoC now identified correctly on JTAG, the ARM debug adapter (DAP), needed to access the flash, failed to initialize. I reliably got <code>Error: Invalid ACK (4) in DAP response</code> which is a rather unhelpful error, even googling results in no useful results. At this point I tried to use this setup with a different CC2652P2 on another board I own, which worked perfectly. So at this point it&apos;s not an issue with my JTAG adapter or OpenOCD configuration, but the SoC itself seems to either be blocking access to the ARM debug adapter or the core together with the debug adapter is powered down. This was a bit of a suprise considering that this is a brand-new module and thus should theoretically be clean and have no firmware on it. My best guess is that this firmware is left over from factory testing. Now, most SoCs have a feature where one can essentially factory-reset the entire chip, disabling any debug protection but also deleting all firmware on the chip. </p><p>These TI chips do in fact have a mass erase feature, though one needs to look in the <a href="https://www.ti.com/lit/ug/swcu117i/swcu117i.pdf">1700-page reference manual</a> to find it. Under section <em>5.8 Debug Features Supported Through WUC TAP </em>there is the <em>CHIP_ERASE_REQ</em> operation which does erase everything from the chip. Before I can use this functionality however I first need to be able to access the <strong>W</strong>ake <strong>U</strong>p <strong>C</strong>ontroller <strong>T</strong>est <strong>A</strong>ccess <strong>P</strong>oint, which is a dedicated JTAG TAP. TI has their own JTAG router on these chips, which they call ICEPick. In Table 5-5 of the reference manual there is an overview of the various TAPs available to be enabled in the JTAG router. The TAP in question is called &quot;AON WUC&quot; here, available on the 5th test bank. OpenOCD <a href="https://review.openocd.org/c/openocd/+/5715">recently</a> (in unreleased verison 0.12) got support for not just enabling the debug banks but also the test banks through ICEPick. Enabling TAPs is done with <code>icepick_c_tapenable</code> followed by the TAP for the ICEPick itself, followed by the bank index. Debug banks start from 0, test banks from 16. So test bank 5 is at index 21. I guessed the instruction register length (irlen) based on the documented commands. The complete WUC TAP definition looks like this:</p><pre><code class="language-openocd">jtag newtap $_CHIPNAME wuc -irlen 4 -ircapture 0x1 -irmask 0xf -disable
jtag configure $_CHIPNAME.wuc -event tap-enable &quot;icepick_c_tapenable $_CHIPNAME.jrc 21&quot;
</code></pre><p>Now I have a JTAG TAP for the Wake Up Controller and can finally issue the documented commands. The IR value (0x01) needs to be shifted into the instruction register using irscan, the data register needs to be filled with the documented command bit and shifted in using drscan. After each command I run another 20 idle cycles just to make sure that the previous command is executed. This might not be necessary, but it takes negligible time. So issuing the mass erase looks like this:</p><pre><code class="language-tcl"># CHIP_ERASE_REQ
irscan $_CHIPNAME.wuc 0x01 -endstate IRPAUSE
drscan $_CHIPNAME.wuc 8 0x2 -endstate DRPAUSE
runtest 20

# MCU_VD_RESET_REQ
irscan $_CHIPNAME.wuc 0x01 -endstate IRPAUSE
drscan $_CHIPNAME.wuc 8 0x20 -endstate DRPAUSE
runtest 20
</code></pre><p>After running this, I tried connecting with my regular debug configuration again and the DAP came up immediately. So the chips were indeed not clean and that was the reason the DAP didn&apos;t connect.</p><hr><p>If you want a complete solution for the CC2652, here is a OpenOCD script combining everything:</p><figure class="kg-card kg-code-card"><pre><code class="language-tcl">source [find target/icepick.cfg]
source [find target/ti-cjtag.cfg]

if { [info exists CHIPNAME] } {
        set _CHIPNAME $CHIPNAME
} else {
        set _CHIPNAME cc26x2
}

#
# WUC TAP
#
jtag newtap $_CHIPNAME wuc -irlen 4 -ircapture 0x1 -irmask 0xf -disable
jtag configure $_CHIPNAME.wuc -event tap-enable &quot;icepick_c_tapenable $_CHIPNAME.jrc 21&quot;

#
# ICEpick-C (JTAG route controller)
#
if { [info exists JRC_TAPID] } {
        set _JRC_TAPID $JRC_TAPID
} else {
        set _JRC_TAPID 0x3bb4102f
}
jtag newtap $_CHIPNAME jrc -irlen 6 -ircapture 0x1 -irmask 0x3f -expected-id $_JRC_TAPID -ignore-version
jtag configure $_CHIPNAME.jrc -event setup &quot;jtag tapenable $_CHIPNAME.wuc&quot;
# A start sequence is needed to change from 2-pin cJTAG to 4-pin JTAG
jtag configure $_CHIPNAME.jrc -event post-reset &quot;ti_cjtag_to_4pin_jtag $_CHIPNAME.jrc&quot;

init

# CHIP_ERASE_REQ
irscan $_CHIPNAME.wuc 0x01 -endstate IRPAUSE
set _res [drscan $_CHIPNAME.wuc 8 0x2 -endstate DRPAUSE]
echo [format %x $_res]
runtest 20

# MCU_VD_RESET_REQ
irscan $_CHIPNAME.wuc 0x01 -endstate IRPAUSE
set _res [drscan $_CHIPNAME.wuc 8 0x20 -endstate DRPAUSE]
echo [format %x $_res]
runtest 20

reset_config srst_once
adapter srst delay 100

exit</code></pre><figcaption>cc26x2-mass-erase.cfg</figcaption></figure><p>This can then be used from either the command line or another script loading this one. This is mine:</p><pre><code class="language-tcl">source [find interface/ftdi/minimodule.cfg]
transport select jtag
adapter speed 100
source ./cc26x2-mass-erase.cfg
</code></pre>]]></content:encoded></item><item><title><![CDATA[Bypassing Lenovo's WiFi Module Authorization]]></title><description><![CDATA[<p>I recently resurrected an old Lenovo notebook (a Lenovo Yoga 2 Pro specifically) as a test machine for various things. For being such an old machine (first released in 2014) it still performs relatively well, the display, SSD, CPU and memory are still fine for smaller tasks. But the integrated</p>]]></description><link>https://lorenz.brun.one/bypassing-lenovos-wifi-authorization/</link><guid isPermaLink="false">639cfb74dfc7d50001c5d672</guid><category><![CDATA[Hardware]]></category><category><![CDATA[Reverse-Engineering]]></category><dc:creator><![CDATA[Lorenz Brun]]></dc:creator><pubDate>Mon, 19 Dec 2022 19:00:00 GMT</pubDate><content:encoded><![CDATA[<p>I recently resurrected an old Lenovo notebook (a Lenovo Yoga 2 Pro specifically) as a test machine for various things. For being such an old machine (first released in 2014) it still performs relatively well, the display, SSD, CPU and memory are still fine for smaller tasks. But the integrated WiFi module is terrible, especially in newer networks. I run my main network only in the 5GHz band as the 2.4GHz band is very congested here and it can&apos;t even connect as it does not have any support for the 5GHz band at all. As WiFi modules are relatively inexpensive (&lt;20$) I decided to replace it with a modern Intel AX210 as it is cheap and known to work well on Linux.</p><p>I went ahead and replaced the module in my notebook and started it. Upon booting, the firmware printed &quot;Unauthorized Wireless network card is plugged in. Power off and remove it&quot; and refused to do anything after that. This is because Lenovo has a module built into the firmware which checks if the WiFi module has the exact same PCI ID as the one originally shipped and aborts the boot if it detects any other ID.</p><p>A workaround (at least on Linux) is to boot with the original module installed (otherwise the PCIe port doesn&apos;t get initialized by the firmware), delete the PCI device for the module (<code>echo 1 &gt; /sys/class/pci_bus/[your_bus_num]/device/delete</code>), suspend the notebook, replace the module while in suspend, resume and rescan the bus (<code>echo 1 &gt; /sys/class/pci_bus/0000:00/rescan</code>). You have to do this every time you reboot though, which makes this rather tedious. On Linux you can use kexec to soft-reboot without going through the firmware, but there are still things which need a hard reboot.</p><hr><p>To actually fix the problem at the root, I need to patch the firmware which is stored on a flash chip on the motherboard. There are two ways to get to it, via the chipset SPI controller while the machine is booted or from an external clip probe while the machine is without power. The first method does not require opening the notebook nor additional hardware but is risky. If the modified firmware you flash has any issues booting you essentially have a brick unless you have the external clip probe and flasher for the second method.</p><p>I have the necessary hardware, so I decided to go with the safer method. I disassembled the notebook until I got access to the SPI flash chip containing the firmware. This is in 99% of cases a single SOIC-8 chip with <em>25</em> somewhere in the part number. For the Lenovo Yoga 2 Pro this is a W25Q64FVSSIG, a 3V 64Mbit flash chip made by Winbond. I used a CH341a-based flasher set to 3.3V (make sure that you&apos;re not feeding too much voltage into your IC) with a SOIC-8 clip probe to access it. On the software side I used flashrom, which has support for tons of SPI flash chips including this one. I first read the existing contents with <code>flashrom -p ch341a_spi --read=stock.rom</code>, then did the same thing again and compared checksums to make sure I have a valid copy of the stock contents should anything go wrong. With the flash contents in hand, I needed to hack out the check.</p><p>Since this notebook, same as most since ~2011 has UEFI-based firmware, I used <a href="https://github.com/LongSoft/UEFITool">UEFITool</a> to open the flash contents. It successfully detects a bunch of data structures, including two UEFI FFSv2 filesystems. I extracted the complete body of both filesystems and called <code>strings</code> on it to find the one containing the messge I was seeing. Since UEFI contains both single-byte-based ASCII/UTF-8 text as well as UCS2/UTF-16 text I needed to call it with <code>strings -e l</code> as well (note UEFI is always little-endian). I spotted the error message string in the second <code>strings</code> call of the first FFSv2 volume in close proximity to the OneKeyRecovery executable as well as the UEFIL05BIOSLock executable. I decided to extract the PE body of both (in UEFITool) and called strings on both again, determining that UEFIL05BIOSLock is the executable containing the WiFi module authorization code. Since this executable is just a special Windows PE file, I loaded it into Ghidra to figure out how it operates.</p><p>I searched Ghidra for the error message (Search -&gt; For strings) and jumped to the sole function it is referenced from, which I called checkHandler:</p><pre><code class="language-c++">
/* WARNING: Globals starting with &apos;_&apos; overlap smaller symbols at the same address */

void checkHandler(EFI_EVENT *Event,void *context)

{
  EFI_STATUS status;
  bool consoleCleared;
  bool stopBoot;
  
  stopBoot = false;
  consoleCleared = false;
  if ((enableL05WWANLock) &amp;&amp; (_wwanState == 2)) {
    clearConsole();
    consoleCleared = true;
    print(L&quot;\nUnauthorized WWAN network card is plugged in. Power off and remove it&quot;);
    stopBoot = true;
  }
  if ((((enableL05WLANLock != false) &amp;&amp; (wlan_pci_buses != (wlan_pci_bus_desc *)0x0)) &amp;&amp;
      (authorized_wlan_devices != (authorized_wlan_device *)0x0)) &amp;&amp;
     (status = checkWLANAuthorized(), status == 0x8000000000000003)) {
    if (!consoleCleared) {
      clearConsole();
    }
    print(L&quot;\nUnauthorized Wireless network card is plugged in. Power off and remove it&quot;);
    stopBoot = true;
  }
  if (stopBoot) {
    do {
    } while( true );
  }
  return;
}</code></pre><p>Note that this is the fully reverse-engineered function, after I&apos;ve given everything names and fixed any issues with data structures. This is very different from what you get when you&apos;re just opening this executable in Ghidra. If you find yourself needing to reverse-engineer (U)EFI executables, I can highly recommend <a href="https://github.com/al3xtjames/ghidra-firmware-utils">uefi-firmware-tools</a> by Alex James which I also used to assist to reverse-engineer this module.</p><p>In a nutshell what this function does is it checks if the WWAN (mobile network) or WLAN lock global variables are set, if they are it checks if the authorized device global variables aren&apos;t null pointers and if that succeeds it tries to check if the device is authorized. In case of the WWAN device the check has already been performed prior to this function being called and its result is stored in <code>_wwanState</code>. If either the WWAN or WLAN device are not in the authorized list it sets <code>stopBoot</code> to true and prints the aformentioned message to the screen. Finally it just enters an infinite loop to prevent the system from booting.</p><p>The actual check is in checkWLANAuthorized which is a relatively lengthy function which I&apos;m not going to show here reading the PCI device config space for each bus in a list of buses, reading the vendor and product IDs from each of those devices and comparing it against a list of authorized devices.</p><p><code>checkHandler</code> is referenced from the executable entry point (the low-level equivalent of a main function), where it gets passed to a function (<code>efiRegisterNotify</code>) which registers it as an EFI event handler to be executed just before the boot. In this function one can also see where most of the data in global variables is coming from: A custom Lenovo EFI protocol with GUID a98e0db6-796e-4c19-b3a23036ad5f02a7 containing a pointer to a rather large data structure with the list of approved devices as well as the bus identifiers (PCIe for WiFi, USB for WWAN) which need to be checked.</p><pre><code class="language-c">EFI_STATUS _ModuleEntryPoint(EFI_HANDLE ImageHandle,EFI_SYSTEM_TABLE *SystemTable)

{
  EFI_STATUS status;
  undefined local_res18 [16];
  
  setHandles(ImageHandle,SystemTable);
  enableL05WLANLock = enableL05WLANLock;
  enableL05WWANLock = enableL05WWANLock;
  if ((lenovo_ext_con_proto == 0) &amp;&amp;
     (status = (*bootServices-&gt;LocateProtocol)
                         (&amp;EFI_GUID_180001a50,(void *)0x0,(void **)&amp;lenovo_ext_con_proto),
     (longlong)status &lt; 0)) {
    return status;
  }
  // Lenovo&apos;s authorized devices protocol
  // GUID a98e0db6-796e-4c19-b3a23036ad5f02a7
  status = (*bootServices-&gt;LocateProtocol)(&amp;lenovo_auth_proto_guid,(void *)0x0,&amp;lenovo_auth_proto);
  if ((longlong)status &lt; 0) {
    status = 0;
  }
  else {
    if (enableL05WLANLock != false) {
      wlan_pci_buses = lenovo_auth_proto-&gt;auth_data-&gt;wlan_pci_buses;
      authorized_wlan_devices = lenovo_auth_proto-&gt;auth_data-&gt;authorized_wlan_devices;
    }
    if (enableL05WWANLock != false) {
      wwan_usb_buses = lenovo_auth_proto-&gt;auth_data-&gt;wwan_usb_buses;
      authorized_wwan_devices = lenovo_auth_proto-&gt;auth_data-&gt;authorized_wwan_devices;
      if ((wwan_usb_buses != (undefined **)0x0) &amp;&amp;
         (authorized_wwan_devices != (authorized_wwan_device *)0x0)) {
        registerWWANCheck();
      }
    }
    status = efiRegisterNotify(8,checkHandler,0,local_res18);
  }
  return status;
}

void efiRegisterNotify(EFI_TPL template,EFI_EVENT_NOTIFY notify_func,void *ctx,EFI_EVENT *event)

{
  if (notify_func == (EFI_EVENT_NOTIFY)0x0) {
    notify_func = (EFI_EVENT_NOTIFY)&amp;DAT_180000934;
  }
  (*bootServices-&gt;CreateEventEx)(0x200,template,notify_func,ctx,&amp;EfiEventReadyToBootGuid,event);
  return;
}</code></pre><p>So now that we understand how this executable works, I needed to find a way to bypass it. The logically simplest way would be to just delete it, but it creates custom events in functions not shown here, the absence of these events might cause unknown problems and I don&apos;t have the time to reverse-engineer the rest of the firmware to make sure it doesn&apos;t. Another way would be to add my new WiFi module&apos;s IDs to the list of approved devices, but that list is not in here and this would require further reverse-engineering. Or I could just use my knowledge of this executable to hack it so it never triggers the infinite loop.</p><p>In this case I&apos;m actually quite lucky as the global variables <code>enableL05WLANLockStatic</code> and <code>enableL05WWANLockStatic</code> are statically defined in this executable&apos;s data section which I can modify. Setting both of those to zero would essentially bypass all of this module&apos;s harmful features as none of the checkHandler&apos;s conditions would evaluate to true.</p><figure class="kg-card kg-image-card"><img src="https://lorenz.brun.one/content/images/2022/12/lenovo_l05lock_re_data.png" class="kg-image" alt loading="lazy" width="840" height="374" srcset="https://lorenz.brun.one/content/images/size/w600/2022/12/lenovo_l05lock_re_data.png 600w, https://lorenz.brun.one/content/images/2022/12/lenovo_l05lock_re_data.png 840w" sizes="(min-width: 720px) 720px"></figure><p>They are also in a neat OEM Switch section, meaning they are likely intended to be toggled by the OEM while testing and toggling them thus should not cause any adverse effects. Because they have a nice string description just in front of the values (be aware of the trailing null bytes also in front of them though) I chose to just open the executable in a hex editor, find the two relevant strings and changed the two bytes from 0x01 to 0x00, thus disabling the lock. To confirm the mod, one could load the resulting binary back into Ghidra but I was quite confident in my change, so I just went ahead and used UEFITool again to replace the PE body of the executable in question with the modded one. Note that if the menu entry to do that is greyed out, make sure you use the <em>old engine</em> version of UEFITool to perform the mod as the new engine cannot yet replace parts of the firmware.</p><p>After saving the resulting firmware file from UEFITool I used flashrom again to write back my changes to the SPI flash chip, reassembled the notebook and booted it. And indeed, it booted straight into the operating system, even with the &quot;unauthorized&quot; WiFi module installed!</p><p>Now, could I have guessed how to patch this just from the <code>strings</code> output and a bit of trial &amp; error? Most likely, but to me it&apos;s a lot more fun and educational to solve the problem a bit more thoroughly.</p><hr><p>A few notes for last: </p><ol><li> I am in no way responsible for anything you do to your devices as a result of this blog post. This is explicitly unsupported by the vendor and you do it on your own risk.</li><li>This post has been written specifically using the Lenovo Yoga 2 Pro&apos;s firmware. Please exercise caution and don&apos;t blindly apply it to other models.</li></ol>]]></content:encoded></item><item><title><![CDATA[Making SPI work on the ROCK64 (and similar Linux SBCs)]]></title><description><![CDATA[<p>Recently I needed to connect a SPI device to a ROCK64 SBC running mainline Linux (5.19 at the time of writing) for use with a userspace SPI driver (<a href="https://www.kernel.org/doc/html/latest/spi/spidev.html">spidev</a>) as Linux doesn&apos;t have a device class nor a driver for it. I initially thought this would be</p>]]></description><link>https://lorenz.brun.one/spi-on-rock64/</link><guid isPermaLink="false">632479ffdfc7d50001c5d473</guid><dc:creator><![CDATA[Lorenz Brun]]></dc:creator><pubDate>Fri, 23 Sep 2022 16:54:09 GMT</pubDate><content:encoded><![CDATA[<p>Recently I needed to connect a SPI device to a ROCK64 SBC running mainline Linux (5.19 at the time of writing) for use with a userspace SPI driver (<a href="https://www.kernel.org/doc/html/latest/spi/spidev.html">spidev</a>) as Linux doesn&apos;t have a device class nor a driver for it. I initially thought this would be quite simple, but it turns out there are a surprising amount of quirks, so I decided to document the process here. If you&apos;re just here for the device tree overlay, it is at the very end of the post.</p><p>The way Linux knows what devices are present and should be exposed on a given SBC is through a <a href="https://www.kernel.org/doc/html/latest/devicetree/usage-model.html">Device Tree</a>, a piece of data which describes all hardware exposed on a given SBC. For a lot of the more popular SBCs (like the ROCK64) these are maintained by the Linux kernel developers in the main source tree. By convention there are usually at least two device tree source files which describe a board: One describing the peripherals the SoC (System on Chip) has and one describing which ones are wired up on a particular board and to what. Linux&apos;s device trees have a policy of not enabling peripherals if nothing is connected to them on a board, so if you wire up hardware to your SBC, you most likely need to extend the device tree to cover that extra hardware.</p><p>Looking at the <a href="https://github.com/torvalds/linux/blob/v5.19/arch/arm64/boot/dts/rockchip/rk3328-rock64.dts">ROCK64 Device Tree</a> we can see that on it the <code>spi0</code> controller being enabled and configured:</p><pre><code> &amp;spi0 {
    // Enable the spi0 controller (remember the SoC device tree disables all peripherals just connected to the outside by convention)
	status = &quot;okay&quot;;

    // Define a flash device on Chip Select 0 (the number after the @)
	flash@0 {
        // What driver to load for this device
		compatible = &quot;jedec,spi-nor&quot;;
        // reg is the Chip Select again and must match the number after the @ of the definition.
		reg = &lt;0&gt;;
        
        // ...
	};
};
</code></pre><p>So this tells us that the ROCK64 board uses the <a href="https://github.com/torvalds/linux/blob/v5.19/arch/arm64/boot/dts/rockchip/rk3328.dtsi#L429"><code>spi0</code> controller from the SoC</a> and has a SPI NOR flash fitted which occupies Chip Select 0. So to connect my device I either need to use a different SPI controller (spi1) or a different Chip Select on spi0. As it turns out <code>spi1</code> is not available on the ROCK64 because the RK3328 doesn&apos;t have enough pins (there&apos;s also the 3368 and 3399 with more), so my only option is to use a second chip select on <code>spi0</code>.</p><p>In device tree form this looks like this:</p><pre><code>&amp;spi0 {
	status = &quot;okay&quot;;
	// Tell the SPI controller that the second CS is used
	num-cs = 2;
    
	// Pins for the SPI controller, documented below
	pinctrl-0 = &lt;&amp;spi0m2_clk &amp;spi0m2_tx &amp;spi0m2_rx &amp;spi0m2_cs0 &amp;spi0_cs1&gt;;
    
	flash@0 {
        // ...
	};
	spidev@1 {
		compatible = &quot;linux,spidev&quot;;
		// Enable our spidev
		status = &quot;okay&quot;;
		// Again should match the CS line (@1 in this case)
		reg = &lt;1&gt;; 
		// The maximum frequency the SPI slave supports
		// (and your wiring too, floating wires respond poorly to high frequencies).
		spi-max-frequency = &lt;10000000&gt;;
	};
};
</code></pre><p>I told the SPI controller that I now need two of its SPI chip select lines and defined my spidev on chip select 1. There is one complicated part about this, and that is <code>pinctrl-0</code>. This tells the SPI controller which pins to use and tells the pin controller to switch them to the correct mode. The Linux <code>spi0</code> definition is actually the same, except that I added <code>spi0_cs1</code>, which is the additional chip select that I&apos;m using for my device. Now here it gets weird. The RK3328 has three GPIO map modes (m0, m1 and m2) for the SPI peripheral. I need to run spi0 in m2 because otherwise the SPI flash doesn&apos;t work because it is wired to the pins as mapped in M2 mode. Also for both other modes not all pins are on the physical connector. But there is no <code>spi0m2_cs1</code> on the chip. So there aren&apos;t actually two chip selects available on the board. So I&apos;m stuck.</p><p>Or am I? Linux has this nice feature called <code>cs-gpio</code>, which is essentially just using GPIOs as chip selects. Instead of having the hardware SPI controller control the chip select line, Linux does it via GPIO. This is obviously slower, but the chip select line is very infrequently toggled so in practice this doesn&apos;t really slow things down. Now according to the <a href="https://github.com/torvalds/linux/blob/v5.19/Documentation/devicetree/bindings/spi/spi-controller.yaml">documentation</a> for this feature, the resulting device tree should look something like this:</p><pre><code>&amp;spi0 {
	pinctrl-0 = &lt;&amp;spi0m2_clk &amp;spi0m2_tx &amp;spi0m2_rx &amp;spi0m2_cs0 &amp;spi0_cs1&gt;;
	// Use the native CS and as a second one GPIO3 pin 7 (GPIO3_A7) as as ACTIVE_LOW (1)
	cs-gpios = &lt;0&gt;, &lt;&amp;gpio3 7 1&gt;;

	spidev@1 {
		compatible = &quot;linux,spidev&quot;;
		status = &quot;okay&quot;;
		reg = &lt;1&gt;;
		spi-max-frequency = &lt;10000000&gt;;
	};
}
// ...
&amp;pinctrl {
	// ...
	spidev1 {
		spi0_cs1: spi0-cs1 {
        	// GPIO 3 Pin 7 Mode GPIO (0), pull up
 			rockchip,pins = &lt;3 7 0 &amp;pcfg_pull_up&gt;;
 		};
 	};
};</code></pre><p>One more thing I need to change is that I&apos;ve been using the <code>linux,spidev</code> compatible on my spidev. This is however no longer supported on recent Linux versions due to a questionable design decision that each userspace device needs its own compatible string upstreamed into Linux. As this is a hobby project, I&apos;m just going to hijack one already on the list, like <code>cisco,spi-petra</code>. If you&apos;re building something that&apos;s going to ship to millions you might want to actually get your own compatible string into Linux.</p><p>On most SPI controllers I&apos;d now be done. But as it turns out the Rockchip SPI controller is kind of broken. It accepts this and happily gives me <code>/dev/spidev0.1</code>. But there is one big problem: If I&apos;m talking to my spidev, both chip select lines go low. This is very bad and could damage hardware because the bus can be occupied by two devices at the same time. From looking at the code this is a hardware limitation of the Rockchip SPI controller. It needs to always toggle its native chip select, otherwise it doesn&apos;t make progress. So I&apos;m stuck again.</p><p>Luckily there is an (admittedly kind of hacky) solution to this as well. Let&apos;s just use two GPIO chip selects and disconnect the native one. Then the SPI controller can happily toggle the native chip select, it is not connected to anything anymore.</p><pre><code>&amp;spi0 {
	cs-gpios = &lt;&amp;gpio3 8 1&gt;, &lt;&amp;gpio3 7 1&gt;;
};
&amp;pinctrl {
	// ...
    spi0-2 {
		// ...
		// Override definition of spi0m2_cs0
		spi0m2_cs0: spi0m2-cs0 {
			// GPIO3 Pin 8/B0 Mode GPIO (0), pull up
			// Note the mode change from 4 to 0, this switches the pin from being controlled by the SPI controller to a GPIO
			rockchip,pins = &lt;3 8 0 &amp;pcfg_pull_up&gt;;
		};
	};
	spidev1 {
		spi0_cs1: spi0-cs1 {
        	// GPIO 3 Pin 7 Mode GPIO (0), pull up
 			rockchip,pins = &lt;3 7 0 &amp;pcfg_pull_up&gt;;
 		};
 	};
};</code></pre><p>And with that, I can talk to both devices without issues!</p><p>Now for the last part, making this composable. Until now I&apos;ve used excerpts of the full device tree to document what I did. This is however a relatively poor way of implementing something like this as I&apos;d have to update my device tree for every change the Linux developers make. Let&apos;s instead use a device tree overlay where I just <em>overlay</em> my device tree changes on top of the normal device tree.</p><p>And this is it:</p><pre><code class="language-dts">/dts-v1/;
/plugin/;
/ {
	compatible = &quot;pine64,rock64&quot;, &quot;rockchip,rk3328&quot;;
	fragment@0 {
		target = &lt;&amp;spi0&gt;;
		__overlay__ {
			#address-cells = &lt;0x1&gt;;
			#size-cells = &lt;0&gt;;

			pinctrl-0 = &lt;&amp;spi0m2_clk &amp;spi0m2_tx &amp;spi0m2_rx &amp;spi0m2_cs0 &amp;spi0_cs1&gt;;
			
			cs-gpios = &lt;&amp;gpio3 8 1&gt;, &lt;&amp;gpio3 7 1&gt;;

			spidev@1 {
				// Hijack random compatible as I have no desire to rebuild Linux to
				// include a custom device ID in spidev&apos;s compatible list.
				compatible = &quot;cisco,spi-petra&quot;;
				status = &quot;okay&quot;;
				reg = &lt;1&gt;;
				spi-max-frequency = &lt;10000000&gt;;
			};
		};
	};
	fragment@1 {
		target = &lt;&amp;pinctrl&gt;;
		__overlay__ {
			spidev1 {
				spi0_cs1: spi0-cs1 {
					rockchip,pins = &lt;3 7 0 &amp;pcfg_pull_up&gt;;
				};
			};
		};
	};
	fragment@2 {
		target = &lt;&amp;spi0m2_cs0&gt;;
		__overlay__ {
			rockchip,pins = &lt;3 8 0 &amp;pcfg_pull_up&gt;;
		};
	};
};
</code></pre><p>It looks very similar to the last device tree, except that the changes are encapsulated in fragments, a compatible is added to indicate for which boards this overlay is and cell geometry (<code>#address-cells</code>, <code>#size-cells</code>) is added because it cannot currently be inherited from the base device tree. Applying this overlay is highly bootloader- and distro-specific, so I&apos;m leaving that part out of this post.</p>]]></content:encoded></item><item><title><![CDATA[Repairing Git commit graphs]]></title><description><![CDATA[<p>Recently a GitLab instance I maintain suddenly alerted me to a repository which didn&apos;t pass a <code>git fsck</code>. While investigating I saw that git pretty much refused to operate on this repository completely. Even a <code>git log</code> quickly resulted in <code>fatal: commit-graph requires overflow generation data but has</code></p>]]></description><link>https://lorenz.brun.one/repairing-git-commit-graphs/</link><guid isPermaLink="false">629d4c6f9352a90001051b86</guid><dc:creator><![CDATA[Lorenz Brun]]></dc:creator><pubDate>Mon, 06 Jun 2022 12:13:22 GMT</pubDate><content:encoded><![CDATA[<p>Recently a GitLab instance I maintain suddenly alerted me to a repository which didn&apos;t pass a <code>git fsck</code>. While investigating I saw that git pretty much refused to operate on this repository completely. Even a <code>git log</code> quickly resulted in <code>fatal: commit-graph requires overflow generation data but has none</code>. So something in the commit graph is broken. What is the commit graph? Its <a href="https://git-scm.com/docs/commit-graph">documentation</a> describes it as follows:</p><blockquote>The commit-graph file is a supplemental data structure that accelerates commit graph walks. If a user downgrades or disables the <em><strong>core.commitGraph</strong></em> config setting, then the existing ODB is sufficient.<br>...<br>The commit-graph file stores the commit graph structure along with some extra metadata to speed up graph walks.</blockquote><p>So it is basically a persisted cache to speed up certain operations. There is even a command to write a new commit graph, &#xA0;<code>git commit-graph write</code>. Theoretically this should write a new, valid commit graph. But sadly it fails with the exact same error about the existing commit graph being broken. It appears that we need to get rid of the broken commit graph before generating a new one as the broken one interferes with the generation of the new one.</p><p>After some investigation into how Git stores its commit graphs there are two ways they can be stored in a repository (the <code>.git</code> directory unless the repository is bare). There can either be a single commit graph stored at <code>objects/info/commit-graph</code> or a newline-separated list of commit graph hashes at <code>objects/info/commit-graphs/commit-graph-chain</code>. By removing both of those files (most likely only one of them will exist) Git will no longer have a commit graph cache and should work normally again. Optionally you can now write a new commit graph with <code>git commit-graph write</code>, but for example GitLab automatically creates a new commit graph for you when running housekeeping for a repository.</p><h3 id="annex-investigating-the-broken-commit-graph">Annex: Investigating the broken commit graph</h3><p>The affected repository was stored on ZFS so filesystem corruption is very unlikely. When looking at <a href="https://github.com/git/git/blob/v2.36.1/commit-graph.c#L806">where the error is triggered in Git source code</a>, it appears that Git is hitting a commit date offset with the <code>CORRECTED_COMMIT_DATE_OFFSET_OVERFLOW</code> flag set, which according to <a href="https://github.com/git/git/commit/e8b63005c48696a26f976f5f9b0ccaf1983e439d">its commit message</a> means that the commit date offset exceeds 2&#xB3;&#xB9; seconds (~68 years). These date offsets are stored in a special block (GDOV) which is not present in the broken commit graph. This means that Git cannot recover the original commit date offset as it would be stored in the GDOV block and that&apos;s why it aborts.</p><p>Considering that this is a fairly normal repository it seems unlikely that such a date offset would be present and a quick check of all commits confirms that indeed no such offset is present. Also the new commit graph has no GDOV block and works just fine. So what happened? A binary diff of the broken and the new commit graph shows little differences except for a section which is empty (all zero bytes) in the new commit graph and just counts up from 0x800000 bytes (2^31 in binary) to 0x8000009D with a few 0x00000000 sprinkled in between. Sadly the binary format is relatively compact and we cannot get Git to decode it easily because it&apos;s broken so I do not know exactly what that section was supposed to be. But it seems clear that Git was interpreting the 31st bit as the offset overflow flag. </p><p>I could investigate further and parse the file fully, but I think I&apos;ll end it here. It seems likely that this was either caused by a freak accident (bit flip, ...) or a bug in an older version of Git when writing the commit graph.</p><p>If anyone else wants to parse the files I&apos;ve attached both the newly-written and the broken graph files.</p>
        <div class="kg-card kg-file-card kg-file-card-medium">
            <a class="kg-file-card-container" href="https://lorenz.brun.one/content/files/2022/06/fixed.graph" title="Download" download>
                <div class="kg-file-card-contents">
                    <div class="kg-file-card-title">Newly-written graph</div>
                    
                    <div class="kg-file-card-metadata">
                        <div class="kg-file-card-filename">fixed.graph</div>
                        <div class="kg-file-card-filesize">12 KB</div>
                    </div>
                </div>
                <div class="kg-file-card-icon">
                    <svg xmlns="http://www.w3.org/2000/svg" viewbox="0 0 24 24"><defs><style>.a{fill:none;stroke:currentColor;stroke-linecap:round;stroke-linejoin:round;stroke-width:1.5px;}</style></defs><title>download-circle</title><polyline class="a" points="8.25 14.25 12 18 15.75 14.25"/><line class="a" x1="12" y1="6.75" x2="12" y2="18"/><circle class="a" cx="12" cy="12" r="11.25"/></svg>
                </div>
            </a>
        </div>
        
        <div class="kg-card kg-file-card kg-file-card-medium">
            <a class="kg-file-card-container" href="https://lorenz.brun.one/content/files/2022/06/broken.graph" title="Download" download>
                <div class="kg-file-card-contents">
                    <div class="kg-file-card-title">Broken graph</div>
                    
                    <div class="kg-file-card-metadata">
                        <div class="kg-file-card-filename">broken.graph</div>
                        <div class="kg-file-card-filesize">12 KB</div>
                    </div>
                </div>
                <div class="kg-file-card-icon">
                    <svg xmlns="http://www.w3.org/2000/svg" viewbox="0 0 24 24"><defs><style>.a{fill:none;stroke:currentColor;stroke-linecap:round;stroke-linejoin:round;stroke-width:1.5px;}</style></defs><title>download-circle</title><polyline class="a" points="8.25 14.25 12 18 15.75 14.25"/><line class="a" x1="12" y1="6.75" x2="12" y2="18"/><circle class="a" cx="12" cy="12" r="11.25"/></svg>
                </div>
            </a>
        </div>
        <h3 id="update-root-cause-found">Update: Root Cause found</h3><p>Shortly after this article was written, Will Chandler on the Git mailing list figured out the actual cause of this issue. See <a href="https://public-inbox.org/git/DD88D523-0ECA-4474-9AA5-1D4A431E532A@wfchandler.org/">https://public-inbox.org/git/DD88D523-0ECA-4474-9AA5-1D4A431E532A@wfchandler.org/</a> for his write-up. It turned out to be a bug in Git where an upgrade of the commit graph data from v1 to v2 caused an underflow, which flips the overflow flag making the commit graph unreadable.</p>]]></content:encoded></item><item><title><![CDATA[UniFi AP DHCP adoption with URL]]></title><description><![CDATA[<p>UniFi APs can automatically annonce themselves to a controller for adoption via either a DHCP option (43) or the <em>unifi</em> DNS name. But sadly both options don&apos;t <a href="https://help.ui.com/hc/en-us/articles/204909754-UniFi-Layer-3-Adoption-for-Remote-UniFi-Controllers#7">officially</a> allow you to configure a URL instead of just an IP address. If the controller is not hosted locally this</p>]]></description><link>https://lorenz.brun.one/unifi-ap-dhcp-adoption-with-url/</link><guid isPermaLink="false">6288cb6d0281b70001156fda</guid><dc:creator><![CDATA[Lorenz Brun]]></dc:creator><pubDate>Sat, 21 May 2022 11:42:39 GMT</pubDate><content:encoded><![CDATA[<p>UniFi APs can automatically annonce themselves to a controller for adoption via either a DHCP option (43) or the <em>unifi</em> DNS name. But sadly both options don&apos;t <a href="https://help.ui.com/hc/en-us/articles/204909754-UniFi-Layer-3-Adoption-for-Remote-UniFi-Controllers#7">officially</a> allow you to configure a URL instead of just an IP address. If the controller is not hosted locally this is quite annoying as its IP might change and you cannot use non-standard ports.</p><p>Luckily there is an undocumented DHCP option code hidden in UniFi&apos;s firmware which allows passing the full URL to the inform endpoint. The standard IP-based provisioning uses option 43 code 1 containing an IP address in binary format. But there is also code 2 which takes a full URL in text format. Sadly configuration of these vendor-specific options is very dependant on the used DHCP server, I can only give an example for ISC dhcpd.</p><figure class="kg-card kg-code-card"><pre><code class="language-dhcpd">option space ubnt;
option ubnt.unifi-address code 1 = ip-address;
# The undocumented URL option
option ubnt.unifi-url code 2 = text;

# Define Ubiquiti vendor class with option space
class &quot;ubnt&quot; {
	match if substring (option vendor-class-identifier , 0, 4) = &quot;ubnt&quot;;
	option vendor-class-identifier &quot;ubnt&quot;;
	vendor-option-space ubnt;
}

shared-network testing {
	subnet 192.0.2.0 netmask 255.255.255.0 {
		option ubnt.unifi-url &quot;http://unifi.example.com/inform&quot;;
        # ...
	}
}
</code></pre><figcaption>Example configuration for ISC dhcpd</figcaption></figure><p>With this configured, all unconfigured UniFi APs in the network will send an adoption request to the given inform endpoint.</p><p>Since this option is undocumented by Ubiquiti, it could theoretically go away at any time, but it has been there for at least a few years and three major firmware revisions (4, 5 and 6) so it seems like Ubiquiti has no interest in removing it.</p>]]></content:encoded></item><item><title><![CDATA[Enabling S3 sleep on a DeskMini X300]]></title><description><![CDATA[<p><strong>TL;DR:</strong> Follow the README in <a href="https://github.com/lorenz/asrock-x300-s3-fix">https://github.com/lorenz/asrock-x300-s3-fix</a></p><p>The ASRock DeskMini X300 is a very affordable barebone computer for everything that doesn&apos;t require more than 8C/16T or a necessitates an external GPU. Equipped with a 5700G, 32 GiB of RAM and a 512GiB of</p>]]></description><link>https://lorenz.brun.one/enabling-s3-sleep-on-x300/</link><guid isPermaLink="false">61f70d238bca8b00013c4a6c</guid><dc:creator><![CDATA[Lorenz Brun]]></dc:creator><pubDate>Mon, 31 Jan 2022 00:35:52 GMT</pubDate><content:encoded><![CDATA[<p><strong>TL;DR:</strong> Follow the README in <a href="https://github.com/lorenz/asrock-x300-s3-fix">https://github.com/lorenz/asrock-x300-s3-fix</a></p><p>The ASRock DeskMini X300 is a very affordable barebone computer for everything that doesn&apos;t require more than 8C/16T or a necessitates an external GPU. Equipped with a 5700G, 32 GiB of RAM and a 512GiB of fast NVMe flash they are fairly powerful machines at a very affordable price point (~800$) while also being space-efficient at around 2l volume, power-efficient (measured at around 7W in idle on Linux) and quiet (at least with a Noctua L9a fitted).</p><p>They do however have one giant flaw: Standby (ACPI S3) is just straight-up unavailable out-of-the-box with CPUs starting from the 4000 series. Windows won&apos;t even show you the button, Linux attempts to use a S2idle which doesn&apos;t do much because the hardware has no S2idle controllers available. It turns the screen off and lowers power consumption by around 1W, but that&apos;s about it. The power LED and fan keep running. This is now documented by ASRock, but only on their <a href="https://www.asrock.com/nettop/AMD/DeskMini%20X300%20Series/index.asp#Specification">detailed specifications tab</a> and literally on the last row.</p><p>When I got my first batch of them I thought about sending them back because Desktop PCs without ACPI S3 aren&apos;t really useable. But first I wanted to try and see if I could enable ACPI S3 on these boxes. Because fundamentally there really isn&apos;t much on there that could screw with S3. The X300 chipset isn&apos;t really a chipset, but pretty much denotes the lack of a chipset. The only portion of it that&apos;s actually physical is a marker chip telling the CPU/SoC that there is no actual chipset present. So basically the only things that are even involved are the CPU/SoC itself, memory, the SuperIO chip and the firmware/BIOS. But all of these components individually support S3, at least if you&apos;re not running the CPU with TSME (Transparent System Memory Encryption) enabled. And since the sister platform Jupiter X300 (also by ASRock) supports S3 it&apos;s extremely unlikely that the hardware physically can&apos;t do it. The reason for the issue is in the firmware/BIOS.</p><p>What&apos;s interesting about the issue is that the operating systems are aware of the fact that S3 is unsupported. This means that the firmware explicitly chose to pass an ACPI table with S3 disabled. So I dumped the ACPI table responsible for most of the original ACPI power handling, the <em>Differentiated System Description Table (</em>DSDT). I tried decompiling the table with acpica, but the decompiler refused because the table contained broken definitions. I patched the decompiler to accept the broken declarations and saw something very interesting</p><pre><code class="language-ASL">    Name (_S0, Package (0x04)  // _S0_: S0 System State
    {
        Zero, 
        Zero, 
        Zero, 
        Zero
    })
    Name (XS3, Package (0x04)
    {
        0x03, 
        Zero, 
        Zero, 
        Zero
    })
    Name (_S4, Package (0x04)  // _S4_: S4 System State
    {
        0x04, 
        Zero, 
        Zero, 
        Zero
    })
    Name (_S5, Package (0x04)  // _S5_: S5 System State
    {
        0x05, 
        Zero, 
        Zero, 
        Zero
    })</code></pre><p>For the S0 (powered on), S4 (suspend-to-disk) and S5 (powered off) power states, there is a valid entry (see the <a href="https://uefi.org/specs/ACPI/6.4/16_Waking_and_Sleeping/sleeping-states.html?highlight=_s3">ACPI sleeping state docs</a>), but for S3 there is an X in front of it, which is not a defined ACPI resource name. Because this platform supports S3 on older CPUs and the entry still exists I wrote a patch which replaced the XS3 resource name with _S3 which is the proper one and recompiled the DSDT table using acpica&apos;s iasl. But ACPI tables are provided by firmware, so just having a fixed one doesn&apos;t really get you far. Luckily Linux provides a facility to overwrite firmware ACPI tables using a special type of CPIO archive / initramfs. The compiled ACPI table needs to be located in the CPIO archive at kernel/firmware/acpi/dsdt.aml. Then this CPIO archive needs to be passed to your bootloader of choice before the normal initramfs. There&apos;s one more caviat which bit me the first time I tried this: ACPI tables have a version number. Linux will only load the override table if its version is <em>higher </em>than the one provided by firmware. Another small patch to the <code>DefinitionBlock</code> of the table source later, Linux loaded my modified DSDT table:</p><pre><code>ACPI: Table Upgrade: override [DSDT-ALASKA-  A M I ]
ACPI: DSDT 0x00000000BB235000 Physical table override, new table: 0x00000000BD632000</code></pre><p>And unsurprisingly it now showed S3 as supported:</p><pre><code>ACPI: PM: (supports S0 S3 S4 S5)</code></pre><p>Now I needed to actually test it since I only forced ACPI to expose the capability and didn&apos;t yet fix any possible bugs with S3. But to my surprise just running <code>systemctl suspend</code> suspended the machine and pressing the power button resumed it again without any obvious bugs. A stress test with 100 sleep/wake cycles didn&apos;t reveal any issues at least on my Cezanne-based Ryzen 5700G and with TSME disabled in firmware. <em>Success!</em></p><p>I also looked into modifying the firmware itself to correct the ACPI table there but I didn&apos;t find the responsible EFI module. Modern EFI firmware is one massive pile of code and at least for AMD firmware there isn&apos;t much public research/documentation available yet. So for now I&apos;m injecting my fix via Linux&apos;s ACPI table override feature. If someone wants to have a go at it, the firmware is <a href="https://download.asrock.com/BIOS/AM4/X300M-STX(1.70)ROM.zip">here</a>.</p><p>A few notes on the constraints of this hack:</p><ol><li>This is unsupported by both the vendor (ASRock) and me. I am not responsible for dead hardware or eaten cats. And depending on your jurisdiction it might void your warranty.</li><li>Any change to firmware settings can change the underlying ACPI tables. Since the patched one is not taken from the firmware you then have an inconsistent set of ACPI tables loaded which can lead to unpredictable behavior and in extreme cases even hardware damage. The only &quot;safe&quot; way to do this is to never change firmware settings or update the firmware after you&apos;ve injected the patch. Otherwise you need to remove the hack first, change the settings and then redo the whole procedure.</li><li>This is only confirmed to work on Cezanne-based APUs (so the Ryzen 5x00G series) and Renoir-based APUs (Ryzen 4x50G, <a href="https://github.com/lorenz/asrock-x300-s3-fix/issues/1">thanks Z3NOX</a>) with TSME disabled. It might very well not work with other APUs.</li><li>It only works if your operating system supports overriding ACPI tables. I don&apos;t know how to do that on Windows and have never tested it.</li></ol><p>If you have one of those boxes and you want to apply my fix, there is a repo at <a href="https://github.com/lorenz/asrock-x300-s3-fix">https://github.com/lorenz/asrock-x300-s3-fix</a> which contains instructions on how to use it.</p>]]></content:encoded></item><item><title><![CDATA[Using Go Modules with GitLab repos]]></title><description><![CDATA[<!--kg-card-begin: markdown--><p>I wanted to share a small trick for using Go 1.11+ modules with GitLab repos. If you just try to import another repo and run a build or test using GitLab CI, this happens:</p>
<pre><code>fatal: could not read Username for &apos;https://git.dolansoft.org&apos;: terminal prompts disabled</code></pre>]]></description><link>https://lorenz.brun.one/using-go-modules-with-gitlab-repos/</link><guid isPermaLink="false">61f479693b720e0001f7344c</guid><dc:creator><![CDATA[Lorenz Brun]]></dc:creator><pubDate>Thu, 07 Mar 2019 02:09:26 GMT</pubDate><content:encoded><![CDATA[<!--kg-card-begin: markdown--><p>I wanted to share a small trick for using Go 1.11+ modules with GitLab repos. If you just try to import another repo and run a build or test using GitLab CI, this happens:</p>
<pre><code>fatal: could not read Username for &apos;https://git.dolansoft.org&apos;: terminal prompts disabled
</code></pre>
<p>Go (calling Git) complains because it doesn&apos;t know how to authenticate itself. GitLab has a cool feature as of 9.0 which grants the GitLab CI token the same access rights as the user that pushed the commit. Now we just need to make Git use that. We cannot pass these credentials in the URL since the call is controlled by Go. But there exists <a href="https://git-scm.com/docs/git-config#git-config-urlltbasegtinsteadOf">a Git feature</a> which allows us to replace the URL by one of our choosing, including one that contains credentials. The end result looks something like this:</p>
<pre><code class="language-yaml">test:
  stage: test
  image: golang:1.12
  script:
    - git config --global url.&quot;https://gitlab-ci-token:$CI_BUILD_TOKEN@git.dolansoft.org/&quot;.insteadOf &quot;https://git.dolansoft.org/&quot;
    - go test
</code></pre>
<p>If you have multiple build steps, you can also put that command into <code>before_script</code>.</p>
<!--kg-card-end: markdown-->]]></content:encoded></item><item><title><![CDATA[Dealing with bad RAM on Linux]]></title><description><![CDATA[<!--kg-card-begin: markdown--><p>I have a server that has  a single byte of RAM which is defective. Usually you just RMA the affected sticks, but I felt it was kind of wasteful to do that for just a single byte of a 8GiB stick that was otherwise still perfectly fine.</p>
<p>Under Linux you</p>]]></description><link>https://lorenz.brun.one/dealing-with-bad-ram-on-linux/</link><guid isPermaLink="false">61f479693b720e0001f7344a</guid><category><![CDATA[CoreOS]]></category><category><![CDATA[Server]]></category><dc:creator><![CDATA[Lorenz Brun]]></dc:creator><pubDate>Fri, 23 Dec 2016 20:14:30 GMT</pubDate><media:content url="https://lorenz.brun.one/content/images/2016/09/memtest.png" medium="image"/><content:encoded><![CDATA[<!--kg-card-begin: markdown--><img src="https://lorenz.brun.one/content/images/2016/09/memtest.png" alt="Dealing with bad RAM on Linux"><p>I have a server that has  a single byte of RAM which is defective. Usually you just RMA the affected sticks, but I felt it was kind of wasteful to do that for just a single byte of a 8GiB stick that was otherwise still perfectly fine.</p>
<p>Under Linux you have basically three methods of telling the kernel that you don&apos;t want to use the defective memory anymore: The <a href="http://rick.vanrein.org/linux/badram/">BadRAM-Patch</a>, the <a href="https://www.kernel.org/doc/Documentation/kernel-parameters.txt"><code>memmap</code> kernel parameter</a> and Grub 2s <code>badram</code> command.<br>
As the first option would require patching the kernel, I wanted to stay away from that one if possible. The second one is a bit flaky, the addresses are in MiB-increments and most places where you use the exclusion syntax where you exclude a specific region mention that is unstable.<br>
Telling Grub would obviously be the easiest variant. It turns out that using that is actually pretty simple, but there are a few caveats, especially if you run a 64-bit system (as most people currently do).</p>
<p>At first you have to have run Memtest86+ and grab the addresses that are defective. Generally people recommend to use the badram output option to print out the addresses, but that output option cuts off addresses larger than 2<sup>32</sup>. So what you actually need to do is take the defective address from the screen (in the example below <code>003ba0b5e20</code>)<br>
<img src="https://lorenz.brun.one/content/images/2016/09/memtest-1.png" alt="Dealing with bad RAM on Linux" loading="lazy"></p>
<p>That address (or more addresses) now needs to be stripped of excess zeroes and formatted like this: <code>0x00000003ba0b5e24</code>. Together with a mask, for example <code>0xffffffffffffff00</code>, we can put this address into the Grub config.</p>
<pre><code>badram 0x00000003ba0b5e24,0xffffffffffffff00
</code></pre>
<p>Further addresses could be added after a second comma.</p>
<p>After rebooting the system we can see the RAM utilization table changed:</p>
<pre><code>[    0.000000] BIOS-e820: [mem 0x0000000000000000-0x000000000009d3ff] usable
[    0.000000] BIOS-e820: [mem 0x000000000009d400-0x000000000009ffff] reserved
[    0.000000] BIOS-e820: [mem 0x00000000000e0000-0x00000000000fffff] reserved
[    0.000000] BIOS-e820: [mem 0x0000000000100000-0x00000000b82f4fff] usable
[    0.000000] BIOS-e820: [mem 0x00000000b82f5000-0x00000000b82fbfff] ACPI NVS
[    0.000000] BIOS-e820: [mem 0x00000000b82fc000-0x00000000b8748fff] usable
[    0.000000] BIOS-e820: [mem 0x00000000b8749000-0x00000000b8b98fff] reserved
[    0.000000] BIOS-e820: [mem 0x00000000b8b99000-0x00000000cc6a9fff] usable
[    0.000000] BIOS-e820: [mem 0x00000000cc6aa000-0x00000000cc8b1fff] reserved
[    0.000000] BIOS-e820: [mem 0x00000000cc8b2000-0x00000000cc8c8fff] ACPI data
[    0.000000] BIOS-e820: [mem 0x00000000cc8c9000-0x00000000cce0afff] ACPI NVS
[    0.000000] BIOS-e820: [mem 0x00000000cce0b000-0x00000000cdffefff] reserved
[    0.000000] BIOS-e820: [mem 0x00000000cdfff000-0x00000000cdffffff] usable
[    0.000000] BIOS-e820: [mem 0x00000000cf000000-0x00000000df1fffff] reserved
[    0.000000] BIOS-e820: [mem 0x00000000f8000000-0x00000000fbffffff] reserved
[    0.000000] BIOS-e820: [mem 0x00000000fec00000-0x00000000fec00fff] reserved
[    0.000000] BIOS-e820: [mem 0x00000000fed00000-0x00000000fed03fff] reserved
[    0.000000] BIOS-e820: [mem 0x00000000fed1c000-0x00000000fed1ffff] reserved
[    0.000000] BIOS-e820: [mem 0x00000000fee00000-0x00000000fee00fff] reserved
[    0.000000] BIOS-e820: [mem 0x00000000ff000000-0x00000000ffffffff] reserved
[    0.000000] BIOS-e820: [mem 0x0000000100000000-0x00000003ba0b5bff] usable
[    0.000000] BIOS-e820: [mem 0x00000003ba0b6000-0x000000041fdfffff] usable
</code></pre>
<p>The two last entries are proof that our Grub parameter indeed had the effect of disabling the defective memory. The kernel blocked 1KiB of memory in between the two large usable blocks which contains the defective RAM.</p>
<!--kg-card-end: markdown-->]]></content:encoded></item><item><title><![CDATA[Setting up ZFS on CoreOS]]></title><description><![CDATA[<!--kg-card-begin: markdown--><p>Recently I needed to expand my disk storage in my server. I previously had 2x2TB of old WD Green disks in a BTRFS RAID1. Now I upgraded to 8x3TB and a dedicated SAS2008-based controller.</p>
<p>As a filesystem I opted for ZFS because I wanted parity-based RAID and <a href="https://btrfs.wiki.kernel.org/index.php/RAID56">Btrfs&apos;s</a></p>]]></description><link>https://lorenz.brun.one/setting-up-zfs/</link><guid isPermaLink="false">61f479693b720e0001f73449</guid><category><![CDATA[Storage]]></category><category><![CDATA[CoreOS]]></category><category><![CDATA[Server]]></category><dc:creator><![CDATA[Lorenz Brun]]></dc:creator><pubDate>Mon, 05 Sep 2016 22:42:31 GMT</pubDate><content:encoded><![CDATA[<!--kg-card-begin: markdown--><p>Recently I needed to expand my disk storage in my server. I previously had 2x2TB of old WD Green disks in a BTRFS RAID1. Now I upgraded to 8x3TB and a dedicated SAS2008-based controller.</p>
<p>As a filesystem I opted for ZFS because I wanted parity-based RAID and <a href="https://btrfs.wiki.kernel.org/index.php/RAID56">Btrfs&apos;s implementation is considered broken at the moment</a>. Because my server (just like all servers at DolanSoft) run on CoreOS I needed to compile ZFS for CoreOS.</p>
<p>That is not easily done though, because there is no kernel module compilation environment in CoreOS and because it is an immutable operating system, there is not even the possibility of installing one.</p>
<p><em>Edit 01/18:</em> I now published <a href="https://github.com/lorenz/torcx-zfs">torcx-zfs</a> which is a much cleaner and quicker way of installing ZFS on CoreOS</p>
<p>After a lot of digging, I found <a href="https://groups.google.com/forum/#!msg/coreos-user/uVdiq64oAIM/eJS1d7apJAoJ">this</a> mail by a CoreOS dev which linked to a container that is automatically built for every CoreOS version and has the needed tools to compile the ZFS kernel module and userspace tools.</p>
<p>So I pulled the right container for my CoreOS version (stable)</p>
<pre><code class="language-sh">wget http://stable.release.core-os.net/amd64-usr/current/coreos_developer_container.bin.bz2
bunzip2 coreos_developer_container.bin.bz2
</code></pre>
<p>and started the resulting container using <code>sudo systemd-nspawn -i coreos_developer_container.bin --share-system</code>.</p>
<p>The mail above also had instructions on how to prepare that container for kernel module development, which I&apos;m repeating here:</p>
<pre><code class="language-sh">emerge-gitclone
emerge -gKav coreos-sources
cd /usr/src/linux
zcat /proc/config.gz &gt;.config
make modules_prepare
</code></pre>
<p>After that, we&apos;re ready to build SPI and ZFS:</p>
<pre><code class="language-sh">wget -O - https://github.com/zfsonlinux/zfs/releases/download/zfs-0.6.5.7/zfs-0.6.5.7.tar.gz | tar -xzf -
wget -O - https://github.com/zfsonlinux/spl/archive/spl-0.6.5.7.tar.gz | tar -xzf -
cd spl &amp;&amp; make &amp;&amp; make install
cd zfs &amp;&amp; make &amp;&amp; make install
</code></pre>
<p>The resulting <code>coreos_developer_container.bin</code> can now be used anywhere to install the ZFS kernel module and userspace.</p>
<pre><code class="language-sh">sudo systemd-nspawn -i coreos_developer_container.bin --bind /:/target --capability=CAP_SYS_MODULE --share-system
modprobe zfs
cp /usr/local/sbin/* /target/opt/bin/
cp -r /usr/local/lib64 /target/usr/share/oem/
chown -R 755 /target/usr/share/oem
</code></pre>
<p>Now we need to run <code>ldconfig -v</code> to update the library path and restart the session.</p>
<p>We can now finally create a ZFS pool:</p>
<pre><code class="language-sh">sudo zpool create data raidz2 /dev/sdb /dev/sdc /dev/sdd /dev/sdf
</code></pre>
<p>Have fun with your new ZFS!</p>
<!--kg-card-end: markdown-->]]></content:encoded></item></channel></rss>