<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="4.4.1">Jekyll</generator><link href="https://0x705h.com/de/feed.xml" rel="self" type="application/atom+xml" /><link href="https://0x705h.com/de/" rel="alternate" type="text/html" /><updated>2026-09-25T14:25:39+00:00</updated><id>https://0x705h.com/feed.xml</id><title type="html">0x705h</title><author><name>toshi</name></author><entry xml:lang="de"><title type="html">tOSh für x86 - i386+ | Teil VI | Booten auf verschiedenen Maschinentypen</title><link href="https://0x705h.com/de/tOSh-booten-auf-verschiedenen-maschinentypen" rel="alternate" type="text/html" title="tOSh für x86 - i386+ | Teil VI | Booten auf verschiedenen Maschinentypen" /><published>2026-09-18T17:19:19+00:00</published><updated>2026-09-18T17:19:19+00:00</updated><id>https://0x705h.com/tOSh-corriendo-en-maquinas</id><content type="html" xml:base="https://0x705h.com/tOSh-booten-auf-verschiedenen-maschinentypen"><![CDATA[<p>Um das Betriebssystem <a href="/resources/">tOSh</a> herunterzuladen, kannst du es im Bereich <a href="/resources/">Resources</a> dieser Website finden.</p>

<h1 id="teile">Teile</h1>

<p>Diese Artikelserie besteht aus folgenden Teilen:</p>

<ul>
  <li><a href="/de/codeando-un-bootloader">Teil I   - Bootloader</a></li>
  <li><a href="/de/modo-protegido">Teil II  - Stage 1</a></li>
  <li><a href="{/saltando-a-c">Teil III - Sprung nach C</a></li>
  <li><a href="/de/interrupciones">Teil IV  - Interrupts</a></li>
  <li><a href="/de/sistema-de-archivos-y-disquetera">Teil V   - Dateisystem und Diskettenlaufwerk-Treiber</a></li>
  <li><a href="/de/tOSh-corriendo-en-maquinas">Teil VI - Booten auf verschiedenen Maschinentypen</a>  <strong>&lt;— Du bist hier</strong></li>
</ul>

<p><img src="/assets/images/tOSh/tOSh-running-86box.png" alt="tOSh läuft in 86Box" class="img-responsive" /><em>_tOSh läuft in 86Box_</em></p>

<h1 id="tosh-202609-release">tOSh 2026.09 Release</h1>

<p>Diesen Monat (September 2026) habe ich es geschafft, das Betriebssystem auf echter Hardware und in Emulatoren zuverlässig zu booten und auszuführen.</p>

<p>Danke an Freunde, die mir Hardware geliehen haben, damit ich es auf allen möglichen Maschinen testen konnte.</p>

<p>Bei sehr alten Maschinen, etwa XTs und 286ern, fällt das Diskettenlaufwerk aus, was zu erwarten ist, weil ich fast sofort in den [Protected Mode] wechsle. Auf einigen 386+-Maschinen schafft es das Diskettenlaufwerk jedoch manchmal nicht, Dateien vollständig zu lesen und fällt schließlich aus, was vom <code class="language-plaintext highlighter-rouge">Kernel</code> gemeldet wird. Das ist eigentlich gar nicht so schlimm.</p>

<p>Während der Tests gab es einige Vorschläge, dass ich vielleicht auf das BIOS zurückfallen und einen Wrapper dafür bauen könnte. Aber nein, ich glaube nicht, dass ich so weit gehen werde.</p>

<p>Zumindest vorerst nicht.</p>

<p><code class="language-plaintext highlighter-rouge">tOSh.img</code> in verschiedensten Emulatoren zu laden, ist dagegen eine sehr einfache und sogar unterhaltsame Aufgabe.</p>

<p>Lade einfach das Disketten-Image aus <a href="/resources/">Resources</a> herunter und mounte es in dem Emulator deiner Wahl.</p>

<p>Wenn du <code class="language-plaintext highlighter-rouge">tOSh.img</code> auf echter Hardware ausführen möchtest, musst du das Image auf eine Festplatte der verwendeten Hardware übertragen und ein Programm wie <code class="language-plaintext highlighter-rouge">RAWrite</code> verwenden. Beachte dabei, dass es sich um ein 1,44-MB-Image für 3,5-Zoll-Disketten handelt.</p>

<p>Den Quellcode der neuesten Version habe ich ebenfalls unter <a href="/resources/">Resources</a> abgelegt.</p>

<p><img src="/assets/images/tOSh/tOSh-ola-ke-ase.jpeg" alt="tOSh winkt in die Kamera" class="img-responsive" /><em>_tOSh winkt in die Kamera_</em></p>

<p>Falls ich dieses Projekt irgendwann weiterführe, werde ich keine alten Rechner mehr unterstützen. Stattdessen werde ich direkt auf <a href="[https://wiki.osdev.org/Setting_Up_Long_Mode](https://wiki.osdev.org/Setting_Up_Long_Mode)">Long Mode</a> wechseln, einen modernen Videomodus aktivieren, mehrere CPUs konfigurieren, alles freischalten, was die Intel-Architektur zu bieten hat, und alles neu schreiben, was bei <code class="language-plaintext highlighter-rouge">tOSh</code> noch unvollständig ist.</p>

<h1 id="referenzen">Referenzen</h1>

<ul>
  <li><a href="[https://wiki.osdev.org/Setting_Up_Long_Mode](https://wiki.osdev.org/Setting_Up_Long_Mode)">Long Mode</a></li>
</ul>]]></content><author><name>toshi</name></author><category term="coding" /><category term="msdos" /><category term="intel" /><category term="amd" /><category term="x64" /><category term="x86" /><category term="boot" /><category term="bootloader" /><category term="p.o.s.t." /><category term="osdev" /><category term="Betriebssysteme" /><summary type="html"><![CDATA[So, hier ist es: das "tOSh Operating System, Huh?" Betriebssystem, laufend auf verschiedenen Maschinen und Emulatoren]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://0x705h.com/assets/images/tOSh/tOSh-running-86box.png" /><media:content medium="image" url="https://0x705h.com/assets/images/tOSh/tOSh-running-86box.png" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry xml:lang="de"><title type="html">tOSh für x86 - i386+ | Teil V | Dateisystem und Floppy-Laufwerk-Treiber</title><link href="https://0x705h.com/de/dateisystem-und-floppy-laufwerk" rel="alternate" type="text/html" title="tOSh für x86 - i386+ | Teil V | Dateisystem und Floppy-Laufwerk-Treiber" /><published>2026-08-12T07:43:04+00:00</published><updated>2026-08-12T07:43:04+00:00</updated><id>https://0x705h.com/sistema-de-archivos-y-disquetera</id><content type="html" xml:base="https://0x705h.com/dateisystem-und-floppy-laufwerk"><![CDATA[<p>Um das Betriebssystem <a href="/resources/">tOSh</a> herunterzuladen, kannst du dies über den Bereich <a href="/resources/">Resources</a> dieser Website tun.</p>

<h1 id="teile">Teile</h1>

<p>Diese Artikelserie wird aus folgenden Teilen bestehen:</p>

<ul>
  <li><a href="/de/codeando-un-bootloader">Teil I   - Bootloader</a></li>
  <li><a href="/de/modo-protegido">Teil II  - Stage 1</a></li>
  <li><a href="/de/saltando-a-c">Teil III - Sprung zu C</a></li>
  <li><a href="/de/interrupciones">Teil IV  - Interrupts</a></li>
  <li><a href="/de/sistema-de-archivos-y-disquetera">Teil V   - Dateisystem und Diskettenlaufwerk-Treiber</a> <strong>&lt;— Du bist hier</strong></li>
  <li><a href="/de/tOSh-corriendo-en-maquinas">Teil VI - Booten auf verschiedenen Maschinentypen</a></li>
</ul>

<p><img src="/assets/images/tOSh/fuck-win-go-tOSh.jpeg" alt="fuck wINdOzE gO tOSh!" class="img-responsive" /><em>fuck wINdOzE gO tOSh!</em></p>

<h1 id="einen-treiber-programmieren">Einen Treiber programmieren</h1>

<p>Hast du dich schon mal gefragt, was zur Hölle ein Treiber ist?<br /></p>

<p>Es ist eine Scheiße, das ist es. ;-( – Aber es ist eine Scheiße, die wir programmieren müssen, sonst funktioniert hier gar keine Scheiße. 3;-D !</p>

<p>Eine Scheißmenge Jahre vergingen und das Leben hat die Entwicklung von <code class="language-plaintext highlighter-rouge">tOSh</code> gestoppt, dem revolutionären Betriebssystem, das das Leben von weiß der Geier wem verändern wird, aber meines ganz sicher verändert hat.</p>

<p>Wir haben den Boden bereitet, um nach –Jahren!– endlich etwas Nützliches mit unserem Betriebssystem machen zu können. Und eine der nützlichen Sachen, die ein Computer tatsächlich macht, ist, Dateien zu speichern.</p>

<p>Bei <code class="language-plaintext highlighter-rouge">tOSh</code> haben wir vorerst nicht einmal eine Festplatte. Nicht einmal auf der <strong>gleichen Diskette</strong>, von der wir booten, können wir eine Datei speichern, weil wir erstens die Diskette überhaupt nicht ansteuern und schon gar kein Dateisystem haben, um irgendetwas zu speichern!</p>

<p>Irgendwann dachte ich, dass es unter x86 einfacher sein würde, das Floppy zu kontrollieren, als einen Treiber für eine Festplatte zusammenzubauen.</p>

<p>Da ich mich für das Floppy entschieden habe, weil <strong>ICH MAG DISKETTEN</strong>, weiß ich momentan nicht einmal, ob es besser gewesen wäre, <strong>HDDs</strong> oder das Floppy zu handhaben. Aber einen funktionierenden Floppy-Treiber hinzubekommen war überraschend viel schwieriger, als ich erwartet hatte.</p>

<p>Während ich <code class="language-plaintext highlighter-rouge">tOSh</code> entwickle, benutze ich normalerweise <code class="language-plaintext highlighter-rouge">qemu</code> und <code class="language-plaintext highlighter-rouge">bochs</code>, um zu testen, ob die Sachen funktionieren, einschließlich des Floppy-Laufwerks. Wenn ich aber irgendeine Version des Betriebssystems auf echter Hardware teste, etwa auf meiner [Maschine von 1997], auf einigen anderen Rechnern aus dem Hackerspace, in dem ich Mitglied bin, oder bei Freunden mit <strong>wirklich alter Hardware</strong>, wird es noch komplizierter.</p>

<p>Begleitet mich auf diesem Abenteuer, einen Treiber für Floppy-Disk-Controller, <code class="language-plaintext highlighter-rouge">FDC</code> (Floppy Disk Controller), so generisch wie möglich zu programmieren und ein <strong>Dateisystem</strong> zu bauen, um nonstop Daten zu schreiben, 70er-Style.</p>

<h2 id="floppy-laufwerk-treiber">Floppy-Laufwerk-Treiber</h2>

<p>Fangen wir mit ein paar #dEfInEz an:</p>

<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cp">#define FDC_DOR   0x3F2   </span><span class="cm">/* Digital Output Register */</span><span class="cp">
#define FDC_MSR   0x3F4   </span><span class="cm">/* Main Status Register */</span><span class="cp">
#define FDC_FIFO  0x3F5   </span><span class="cm">/* Data FIFO */</span><span class="cp">
#define FDC_CCR   0x3F7   </span><span class="cm">/* Configuration Control Register */</span><span class="cp">
</span></code></pre></div></div>

<ul>
  <li><strong>FDC_DOR</strong> schaltet den Motor ein und aus, wählt das aktive Laufwerk aus und aktiviert IRQ/DMA.</li>
  <li><strong>FDC_MSR</strong> ist nur lesbar und sagt dir, in welchem Zustand sich der Controller befindet.</li>
  <li><strong>FDC_FIFO</strong> ist der 1-Byte-Buffer, über den Befehle reingehen und Ergebnisse rauskommen. Das gesamte FDC-Protokoll läuft über diesen einen Port, ein Byte nach dem anderen.</li>
  <li><strong>FDC_CCR</strong> definiert die "Geschwindigkeit" der Übertragung.</li>
</ul>

<h2 id="protokoll">Protokoll</h2>

<p>Das Protokoll besteht aus drei Teilen bzw. Phasen.</p>

<ul>
  <li><strong>command phase</strong>, du schickst Bytes.</li>
  <li><strong>execution phase</strong>, der Controller erledigt extrem wichtige Sachen.</li>
  <li><strong>result phase</strong>, der Controller gibt dir den Status der vorherigen Operation zurück.</li>
</ul>

<p>Das MSR sagt dir, in welchem Teil du gerade bist, indem du die Bits <code class="language-plaintext highlighter-rouge">RQM</code> (Request For Master) und <code class="language-plaintext highlighter-rouge">DIO</code> (Data Input/Output) anschaust:</p>

<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">static</span> <span class="n">bool</span> <span class="nf">fdc_wait_write_ready</span><span class="p">(</span><span class="kt">void</span><span class="p">)</span>
<span class="p">{</span>
    <span class="kt">uint32_t</span> <span class="n">start</span> <span class="o">=</span> <span class="n">pit_get_ticks</span><span class="p">();</span>

    <span class="k">while</span> <span class="p">((</span><span class="n">pit_get_ticks</span><span class="p">()</span> <span class="o">-</span> <span class="n">start</span><span class="p">)</span> <span class="o">&lt;</span> <span class="mi">100</span><span class="p">)</span> <span class="p">{</span>

        <span class="kt">uint8_t</span> <span class="n">msr</span> <span class="o">=</span> <span class="n">inb</span><span class="p">(</span><span class="n">FDC_MSR</span><span class="p">);</span>

        <span class="k">if</span> <span class="p">((</span><span class="n">msr</span> <span class="o">&amp;</span> <span class="mh">0xC0</span><span class="p">)</span> <span class="o">==</span> <span class="mh">0x80</span><span class="p">)</span>
            <span class="k">return</span> <span class="nb">true</span><span class="p">;</span>

        <span class="n">inb</span><span class="p">(</span><span class="mh">0x80</span><span class="p">);</span> <span class="cm">/* I/O Delay for ISA hardware */</span>
    <span class="p">}</span>

    <span class="n">terminal_writestring</span><span class="p">(</span><span class="s">"FLOPPY: FDC write timeout</span><span class="se">\n</span><span class="s">"</span><span class="p">);</span>
    <span class="k">return</span> <span class="nb">false</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>

<p><code class="language-plaintext highlighter-rouge">(msr &amp; 0xC0) == 0x80</code> bedeutet <code class="language-plaintext highlighter-rouge">RQM=1, DIO=0</code>. Der Controller wartet auf ein Byte. Um ein Ergebnis zu lesen, ändert sich die Bedingung zu <code class="language-plaintext highlighter-rouge">(msr &amp; 0xD0) == 0xD0</code>, wodurch zu <code class="language-plaintext highlighter-rouge">RQM=1, DIO=1</code> noch <code class="language-plaintext highlighter-rouge">CB=1</code> (controller busy) dazukommt: Der FDC hat ein Byte bereit und verarbeitet gerade einen Befehl (daher busy).</p>

<p>Das alles mache ich mit <strong>Polling</strong> und einem Timeout. Wenn der FDC nicht antwortet, hängt sich der <a href="https://en.wikipedia.org/wiki/Kernel_%28operating_system%29">Kernel</a> für immer auf. Jedes <code class="language-plaintext highlighter-rouge">fdc_write</code> / <code class="language-plaintext highlighter-rouge">floppy_read_byte</code> hat 100 Ticks Spielraum (bei 100Hz eine Sekunde), und wenn es nicht klappt, wird das gemeldet und abgebrochen.</p>

<p>Das <code class="language-plaintext highlighter-rouge">inb(0x80)</code> brauchen wir –auf echter Hardware– für ein ISA-I/O-Delay. Der Bus braucht zwischen den Polls eine gewisse Zeit X.</p>

<p><strong><em>*Hustet*</em></strong> <br /> Reden wir über Emulatoren.</p>

<p>Ist euch schon mal aufgefallen, dass manche Leute –fukken wunderschöne Menschen in dieser beschissenen Welt– davon besessen sind, ganze Architekturen mit <code class="language-plaintext highlighter-rouge">clock</code>-genauer Präzision zu emulieren? Nun, <code class="language-plaintext highlighter-rouge">qemu</code> und <code class="language-plaintext highlighter-rouge">bochs</code> gehören nicht dazu.</p>

<p>Wir reden gerade über <code class="language-plaintext highlighter-rouge">x86</code>, aber bei anderen Architekturen oder Konsolen, wie <strong>Super Nintendo</strong> oder was weiß ich, <strong>Playstation</strong>, gibt es verschiedene Ansätze für die Emulation. Vielleicht ist es beim <strong>SNES</strong>, weil es ein ROM ist, nicht ganz so offensichtlich, aber bei <strong>CDROM</strong>, wo ein Motor im Spiel ist (wie bei einem Floppy), ändern sich die Dinge.</p>

<p>Wenn ich einen Controller für irgendeine Scheiße programmiere, die mit <code class="language-plaintext highlighter-rouge">qemu</code> emuliert wird, entgehen mir all die <strong>physischen</strong> Besonderheiten, die der Motor und die RPMs der CD oder die magnetische Scheibe der Diskette mit sich bringen. In diesem Fall reden wir über ein Floppy-Laufwerk, bei dem ein <strong>Motor</strong> (!) rotiert und ein paar Millisekunden braucht, um eine stabile Drehgeschwindigkeit zu erreichen.</p>

<p>Als ich diesen Treiber und das Betriebssystem im Allgemeinen programmierte, lief es in <code class="language-plaintext highlighter-rouge">qemu</code>, und als ich es auf echter Hardware laufen lassen wollte, <strong>lief es verdammt nochmal überhaupt nicht</strong>.</p>

<p>Deshalb ist es unglaublich wichtig, die Datasheets von allem zu befolgen, was man gerade programmiert, und wenn man Glück hat, Informationen zusammenzutragen, die andere Leute bereits recherchiert haben. Zum Beispiel <strong>Josh Cole</strong> von <a href="https://floppy.cafe/">floppy.cafe</a>, der praktisch 90% von allem, was ich für die Entwicklung des Treibers brauchte, an einem einzigen Ort gesammelt hat. Ein verdammter Meister.</p>

<p>Im Zeitalter der Künstlichen Intelligenz ist es vielleicht sogar besser zu wissen, wo man die Dokumentation findet, aus der sie gefüttert wurde, als sie blind nach einer Implementierung zu fragen.</p>

<p>Ich versichere es euch.</p>

<h2 id="reset-und-initialisierung">Reset und Initialisierung</h2>

<p>Die FDC-Startsequenz in <code class="language-plaintext highlighter-rouge">floppy_init()</code> folgt der Reihenfolge aus dem Datasheet:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>1. IRQ6 am PIC unmasken
2. Datenrate konfigurieren (CCR = 0x00)
3. DOR = enable + irq, Motor aus
4. Reset: DOR = 0x00
5. I/O-Delay (4x Schreibzugriffe auf 0x80)
6. DOR = enable + irq (Ende des Resets)
7. Auf IRQ6 warten (der FDC-Reset erzeugt einen IRQ)
8. 4x SENSE INTERRUPT
9. SPECIFY
10. CONFIGURE
11. Motor an -&gt; RECALIBRATE -&gt; Motor aus
12. Testlesen (Sector 0, Head 0, Sector 1)
</code></pre></div></div>

<p>Das einzig Relevante hier ist, warum wir nach dem Reset <strong>4 Sense Interrupt</strong> machen. Das liegt daran, dass ich den Controller im Modus "Drive Polling" gelassen habe: Wenn der FDC in diesem Modus einen Reset durchführt, erzeugt er einen ausstehenden Status für jedes der 4 Laufwerke, die er haben kann (A, B, C oder D Floppys), selbst wenn nur eines davon angeschlossen ist. Wenn du diese 4 Ergebnisse nicht mit <code class="language-plaintext highlighter-rouge">floppy_sense_interrupt()</code> leerst, bleibt im Controller noch irgendein Scheiß in der Queue und du musst die Daten darin erst vollständig abarbeiten.</p>

<p>Ich habe ziemlich lange versucht herauszufinden, warum sich das Laufwerk bei mir ständig aufhängte, und einer der Gründe war genau das.</p>

<h2 id="specify">Specify</h2>

<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">static</span> <span class="n">bool</span> <span class="nf">floppy_specify</span><span class="p">(</span><span class="kt">void</span><span class="p">)</span>
<span class="p">{</span>
    <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">fdc_write</span><span class="p">(</span><span class="n">FDC_CMD_SPECIFY</span><span class="p">))</span>
        <span class="k">return</span> <span class="nb">false</span><span class="p">;</span>

    <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">fdc_write</span><span class="p">(</span><span class="mh">0x8f</span><span class="p">))</span>   <span class="cm">/* SRT=8, HUT=15 */</span>
        <span class="k">return</span> <span class="nb">false</span><span class="p">;</span>

    <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">fdc_write</span><span class="p">(</span><span class="mh">0x1e</span><span class="p">))</span>   <span class="cm">/* HLT=15, NDMA=0 */</span>
        <span class="k">return</span> <span class="nb">false</span><span class="p">;</span>

    <span class="k">return</span> <span class="nb">true</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>

<p><code class="language-plaintext highlighter-rouge">SPECIFY</code> hat keine Result-Phase und erzeugt keinen IRQ. Sobald die 3 Bytes gesendet wurden, war’s das. Die Werte:</p>

<ul>
  <li>
    <p><strong>Byte 1 (<code class="language-plaintext highlighter-rouge">0x8f</code>)</strong>: High-Nibble = SRT (Step Rate Time, wie lange jeder Schritt des Kopfes dauert, in umgekehrten Einheiten – höhere Werte = schnellere Schritte), Low-Nibble = HUT (Head Unload Time, wie lange gewartet wird, bevor der Kopf nach einer Operation "losgelassen" wird).</p>
  </li>
  <li>
    <p><strong>Byte 2 (<code class="language-plaintext highlighter-rouge">0x1e</code>)</strong>: obere Bits = HLT (Head Load Time, wie lange es dauert, bis sich der Kopf vor dem Lesen/Schreiben gesetzt hat), unterstes Bit = NDMA. <code class="language-plaintext highlighter-rouge">NDMA=0</code> ist das Wichtige: Damit sagen wir dem FDC, dass wir DMA für die Datenübertragung verwenden werden, nicht PIO.</p>
  </li>
</ul>

<p>Ich verwende DMA-Transfers, weil (ich glaube, das ist in <code class="language-plaintext highlighter-rouge">floppy.h</code> kommentiert) <code class="language-plaintext highlighter-rouge">bochs</code> keine PIO-Transfers unterstützt, sondern nur DMA.</p>

<p>Wenn du dieses Bit vergisst, erwartet der FDC, dass du jedes Byte manuell aus dem FIFO liest, und die ganze DMA-Pipeline, die wir weiter unten aufbauen, ist für die Katz.</p>

<p>Da der Reset diese Konfiguration löscht, müssen wir sie bei jedem <code class="language-plaintext highlighter-rouge">floppy_init()</code> erneut senden.</p>

<h2 id="configure">Configure</h2>

<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">static</span> <span class="n">bool</span> <span class="nf">floppy_configure</span><span class="p">(</span><span class="kt">void</span><span class="p">)</span>
<span class="p">{</span>
    <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">fdc_write</span><span class="p">(</span><span class="mh">0x13</span><span class="p">))</span>   <span class="cm">/* CONFIGURE */</span>
        <span class="k">return</span> <span class="nb">false</span><span class="p">;</span>
    <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">fdc_write</span><span class="p">(</span><span class="mh">0x00</span><span class="p">))</span>   <span class="cm">/* reserved byte */</span>
        <span class="k">return</span> <span class="nb">false</span><span class="p">;</span>
    <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">fdc_write</span><span class="p">(</span><span class="mh">0x57</span><span class="p">))</span>   <span class="cm">/* EIS=1, EFIFO=0, POLL=1, FIFOTHR=7 */</span>
        <span class="k">return</span> <span class="nb">false</span><span class="p">;</span>
    <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">fdc_write</span><span class="p">(</span><span class="mh">0x00</span><span class="p">))</span>   <span class="cm">/* PRETRK */</span>
        <span class="k">return</span> <span class="nb">false</span><span class="p">;</span>

    <span class="k">return</span> <span class="nb">true</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>

<p><code class="language-plaintext highlighter-rouge">0x57</code> aktiviert drei Dinge: <strong>EIS</strong> (Enable Implied Seek, der FDC führt den Seek automatisch vor einem Read/Write aus, ohne dass du ihn explizit dazu aufforderst), <strong>EFIFO=0</strong> (was kontraintuitiv den internen 16-Byte-FIFO des Controllers <strong>aktiviert</strong> statt ihn zu deaktivieren – der Name des Bits ist genau das Gegenteil von dem, was man erwarten würde) und <strong>FIFOTHR=7</strong>, den 8-Byte-Schwellenwert, der einen DMA-/Speicherzugriff auslöst. Das gibt dem System etwas Spielraum, damit es bei etwas Bus-Latenz keine Bytes verliert.</p>

<p><code class="language-plaintext highlighter-rouge">CONFIGURE</code> erzeugt wie <code class="language-plaintext highlighter-rouge">SPECIFY</code> ebenfalls keinen IRQ und hat keine Result-Phase.</p>

<h2 id="motor-und-timing">Motor und timing</h2>

<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">static</span> <span class="kt">void</span> <span class="nf">floppy_motor_on</span><span class="p">(</span><span class="kt">void</span><span class="p">)</span>
<span class="p">{</span>
    <span class="n">outb</span><span class="p">(</span><span class="n">FDC_DOR</span><span class="p">,</span> <span class="n">FDC_DOR_ENABLE</span> <span class="o">|</span> <span class="n">FDC_DOR_IRQ</span> <span class="o">|</span> <span class="n">FDC_DOR_MOTOR_A</span> <span class="o">|</span> <span class="n">FDC_DRIVE_A</span><span class="p">);</span>
    <span class="n">pit_wait_ms</span><span class="p">(</span><span class="mi">500</span><span class="p">);</span>
<span class="p">}</span>
</code></pre></div></div>

<p>500ms sind mehr als das, was <code class="language-plaintext highlighter-rouge">osdev</code> dokumentiert (dort werden 300ms für 3,5" vorgeschlagen und es heißt sogar, dass 50ms reichen). In meinem echten Setup waren 50ms nicht genug und ich hatte sporadische Seek-Fehler. Ich habe mich lieber für eine halbe Sekunde auf der sicheren Seite entschieden, statt dem theoretischen Minimum hinterherzujagen – die Kosten für ein etwas längeres Warten sind im Vergleich zu einem fehlgeschlagenen Read mitten im Bootvorgang völlig unbedeutend.</p>

<p>Dafür brauche ich einen Timer, der nicht davon abhängt, CPU-Zyklen von Hand zu zählen, also kommt <code class="language-plaintext highlighter-rouge">pit.c</code> ins Spiel.</p>

<h2 id="el-pit-programmable-interval-timer">El PIT (Programmable Interval Timer)</h2>

<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kt">void</span> <span class="nf">pit_init</span><span class="p">(</span><span class="kt">uint32_t</span> <span class="n">frequency</span><span class="p">)</span>
<span class="p">{</span>
    <span class="n">pit_hz</span> <span class="o">=</span> <span class="n">frequency</span><span class="p">;</span>
    <span class="kt">uint32_t</span> <span class="n">divisor</span> <span class="o">=</span> <span class="n">PIT_FREQUENCY</span> <span class="o">/</span> <span class="n">frequency</span><span class="p">;</span>

    <span class="n">outb</span><span class="p">(</span><span class="n">PIT_COMMAND</span><span class="p">,</span> <span class="mh">0x36</span><span class="p">);</span>   <span class="cm">/* Ch0, lo/hi, mode 3, binario */</span>
    <span class="n">outb</span><span class="p">(</span><span class="n">PIT_CH0</span><span class="p">,</span> <span class="n">divisor</span> <span class="o">&amp;</span> <span class="mh">0xFF</span><span class="p">);</span>
    <span class="n">outb</span><span class="p">(</span><span class="n">PIT_CH0</span><span class="p">,</span> <span class="p">(</span><span class="n">divisor</span> <span class="o">&gt;&gt;</span> <span class="mi">8</span><span class="p">)</span> <span class="o">&amp;</span> <span class="mh">0xFF</span><span class="p">);</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Der <a href="https://wiki.osdev.org/Programmable_Interval_Timer">PIT</a> läuft intern mit 1193182 Hz (PIT_FREQUENCY). Mit pit_init(100) konfiguriere ich einen Teiler, der alle 10ms einen IRQ0 auslöst, also 100 Ticks pro Sekunde. pit_ticks ist ein globaler Counter, der von pit_irq_handler() inkrementiert wird, und alles andere im System (pit_wait_ms, die FDC-Timeouts, floppy_wait_irq) stützt sich auf diesen Counter, statt blind Busy-Waiting zu machen.</p>

<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">static</span> <span class="n">bool</span> <span class="nf">floppy_wait_irq</span><span class="p">(</span><span class="kt">void</span><span class="p">)</span>
<span class="p">{</span>
    <span class="kt">uint32_t</span> <span class="n">start</span> <span class="o">=</span> <span class="n">pit_get_ticks</span><span class="p">();</span>

    <span class="k">while</span> <span class="p">(</span><span class="o">!</span><span class="n">floppy_irq</span><span class="p">)</span> <span class="p">{</span>
        <span class="k">if</span> <span class="p">((</span><span class="n">pit_get_ticks</span><span class="p">()</span> <span class="o">-</span> <span class="n">start</span><span class="p">)</span> <span class="o">&gt;=</span> <span class="mi">200</span><span class="p">)</span> <span class="p">{</span>
            <span class="n">terminal_writestring</span><span class="p">(</span><span class="s">"FLOPPY: IRQ timeout</span><span class="se">\n</span><span class="s">"</span><span class="p">);</span>
            <span class="k">return</span> <span class="nb">false</span><span class="p">;</span>
        <span class="p">}</span>
        <span class="n">__asm__</span> <span class="k">volatile</span> <span class="p">(</span><span class="s">"hlt"</span><span class="p">);</span>
    <span class="p">}</span>
    <span class="k">return</span> <span class="nb">true</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>

<p>200 Ticks bei 100Hz bedeuten ein 2-Sekunden-Timeout, damit der IRQ6 des FDC ankommt – absichtlich großzügig, weil ich nicht will, dass ein echtes Floppy-Laufwerk (das etwas Zeit für Spin-up und Seek brauchen kann) einen falschen Timeout auslöst.</p>

<p>Beachtet das <code class="language-plaintext highlighter-rouge">hlt</code> in der Schleife: Statt per Spin-Waiting CPU-Zeit zu verbraten, hält der Prozessor bis zum nächsten Interrupt an (entweder vom PIT oder vom FDC), genau das, was ein echter Kernel tun sollte, wenn er auf I/O wartet.</p>

<h2 id="sense-interrupt-recalibrate-y-seek">Sense Interrupt, Recalibrate y Seek</h2>

<p><code class="language-plaintext highlighter-rouge">RECALIBRATE</code> und <code class="language-plaintext highlighter-rouge">SEEK</code> erzeugen einen IRQ, haben aber <strong>keine eigene Result-Phase</strong>. Der Status muss separat mit <code class="language-plaintext highlighter-rouge">SENSE INTERRUPT</code> abgefragt werden:</p>

<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">static</span> <span class="n">bool</span> <span class="nf">floppy_sense_interrupt</span><span class="p">(</span><span class="kt">uint8_t</span> <span class="o">*</span><span class="n">st0</span><span class="p">,</span> <span class="kt">uint8_t</span> <span class="o">*</span><span class="n">cyl</span><span class="p">)</span>
<span class="p">{</span>
    <span class="n">inb</span><span class="p">(</span><span class="mh">0x80</span><span class="p">);</span>
    <span class="n">inb</span><span class="p">(</span><span class="mh">0x80</span><span class="p">);</span>

    <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">fdc_write</span><span class="p">(</span><span class="n">FDC_CMD_SENSE_INT</span><span class="p">))</span>
        <span class="k">return</span> <span class="nb">false</span><span class="p">;</span>
    <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">floppy_read_byte</span><span class="p">(</span><span class="n">st0</span><span class="p">))</span>
        <span class="k">return</span> <span class="nb">false</span><span class="p">;</span>
    <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">floppy_read_byte</span><span class="p">(</span><span class="n">cyl</span><span class="p">))</span>
        <span class="k">return</span> <span class="nb">false</span><span class="p">;</span>

    <span class="k">return</span> <span class="nb">true</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>

<p><code class="language-plaintext highlighter-rouge">RECALIBRATE</code> bewegt den Kopf auf Spur 0 (nützlich, um die bekannte physische Position zu "resynchronisieren"), während <code class="language-plaintext highlighter-rouge">SEEK</code> ihn auf einen bestimmten Zylinder bewegt. In beiden Fällen mache ich nach dem IRQ ein <code class="language-plaintext highlighter-rouge">SENSE INTERRUPT</code> und prüfe zwei Dinge: das <code class="language-plaintext highlighter-rouge">Seek End</code>-Bit (<code class="language-plaintext highlighter-rouge">st0 &amp; 0x20</code>) und ob der gemeldete Zylinder (<code class="language-plaintext highlighter-rouge">cyl</code>) mit dem erwarteten übereinstimmt (0 beim Recalibrate, der angeforderte Zylinder beim Seek). Wenn etwas schiefgeht, spucke ich es auf der Konsole aus.</p>

<h2 id="lesen-und-schreiben-von-sektoren">Lesen und Schreiben von Sektoren</h2>

<p>Der Read/Write-Befehl sendet insgesamt 9 Bytes:</p>

<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">fdc_write</span><span class="p">(</span><span class="n">FDC_CMD_READ_DATA</span> <span class="o">|</span> <span class="mh">0xC0</span><span class="p">))</span> <span class="p">{</span> <span class="p">[...]</span> <span class="p">}</span>  <span class="cm">/* MT=1, MFM=1 */</span>
<span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">fdc_write</span><span class="p">((</span><span class="n">head</span> <span class="o">&lt;&lt;</span> <span class="mi">2</span><span class="p">)</span> <span class="o">|</span> <span class="n">FDC_DRIVE_A</span><span class="p">))</span> <span class="p">{</span> <span class="p">[...]</span> <span class="p">}</span> <span class="cm">/* HD + DR */</span>
<span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">fdc_write</span><span class="p">(</span><span class="n">cylinder</span><span class="p">))</span> <span class="p">{</span> <span class="p">[...]</span> <span class="p">}</span>                  <span class="cm">/* C */</span>
<span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">fdc_write</span><span class="p">(</span><span class="n">head</span><span class="p">))</span> <span class="p">{</span> <span class="p">[...]</span> <span class="p">}</span>                      <span class="cm">/* H */</span>
<span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">fdc_write</span><span class="p">(</span><span class="n">sector</span><span class="p">))</span> <span class="p">{</span> <span class="p">[...]</span> <span class="p">}</span>                    <span class="cm">/* R */</span>
<span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">fdc_write</span><span class="p">(</span><span class="mi">2</span><span class="p">))</span> <span class="p">{</span> <span class="p">[...]</span> <span class="p">}</span>                         <span class="cm">/* N = 512 bytes */</span>
<span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">fdc_write</span><span class="p">(</span><span class="mi">18</span><span class="p">))</span> <span class="p">{</span> <span class="p">[...]</span> <span class="p">}</span>                        <span class="cm">/* EOT: último sector de la pista */</span>
<span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">fdc_write</span><span class="p">(</span><span class="mh">0x1B</span><span class="p">))</span> <span class="p">{</span> <span class="p">[...]</span> <span class="p">}</span>                      <span class="cm">/* GPL */</span>
<span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">fdc_write</span><span class="p">(</span><span class="mh">0xFF</span><span class="p">))</span> <span class="p">{</span> <span class="p">[...]</span> <span class="p">}</span>                      <span class="cm">/* DTL, ignorado cuando N != 0 */</span>
</code></pre></div></div>

<p><code class="language-plaintext highlighter-rouge">0xC0</code> im Befehl aktiviert <code class="language-plaintext highlighter-rouge">MT</code> (Multi-Track, der Controller kann automatisch mit dem nächsten Kopf weitermachen, wenn er EOT überschreitet) und <a href="https://en.wikipedia.org/wiki/Modified_frequency_modulation">MFM</a> (die Standardmodulation für Floppys und magnetische Speicher ab Double Density aufwärts). <code class="language-plaintext highlighter-rouge">N=2</code> sagt dem FDC "jeder Sektor ist 512 Bytes groß", anhand der Standard-Größentabelle des Chips (0=128B, 1=256B, 2=512B…). <code class="language-plaintext highlighter-rouge">EOT=18</code> ist die Anzahl der Sektoren pro Spur bei der 1,44MB-Geometrie.</p>

<p>Nach dem IRQ (der kommt, wenn der DMA-Transfer abgeschlossen ist, nicht vorher) folgt die <strong>Result-Phase</strong> mit 7 Bytes (<code class="language-plaintext highlighter-rouge">ST0</code> bis <code class="language-plaintext highlighter-rouge">ST2</code> plus C/H/R/N als Rückgabewerte), von denen ich nur die ersten drei prüfe:</p>

<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">if</span> <span class="p">(</span><span class="n">st</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span> <span class="o">&amp;</span> <span class="mh">0xC0</span><span class="p">)</span> <span class="p">{</span> <span class="p">[...]</span> <span class="p">}</span>   <span class="cm">/* error phase */</span>
<span class="k">if</span> <span class="p">(</span><span class="n">st</span><span class="p">[</span><span class="mi">1</span><span class="p">]</span> <span class="o">!=</span> <span class="mi">0</span><span class="p">)</span>   <span class="p">{</span> <span class="p">[...]</span> <span class="p">}</span>   <span class="cm">/* ST1: errores de datos, CRC, etc. */</span>
<span class="k">if</span> <span class="p">(</span><span class="n">st</span><span class="p">[</span><span class="mi">2</span><span class="p">]</span> <span class="o">!=</span> <span class="mi">0</span><span class="p">)</span>   <span class="p">{</span> <span class="p">[...]</span> <span class="p">}</span>   <span class="cm">/* ST2: errores específicos de la pista/sector */</span>
</code></pre></div></div>

<p>Jedes gesetzte Bit in <code class="language-plaintext highlighter-rouge">ST1</code>/<code class="language-plaintext highlighter-rouge">ST2</code> behandle ich als kompletten Fehler der Operation – ich versuche nicht zu unterscheiden, ob es sich um "einen behebbaren CRC-Fehler" oder "den Sektor gibt es nicht" handelt; ich wiederhole einfach die komplette Operation.</p>

<p><code class="language-plaintext highlighter-rouge">floppy_write_sector_impl</code> ist fast ein Spiegelbild des Reads. Dabei ändert sich der Befehl (<code class="language-plaintext highlighter-rouge">FDC_CMD_WRITE_DATA | 0xC0</code>, MFM ohne MT nötig zu haben… obwohl ich im Code dasselbe <code class="language-plaintext highlighter-rouge">0xC0</code> gelassen habe) und die DMA-Richtung.</p>

<h2 id="chs-und-lba-konvertierung">CHS und LBA-Konvertierung</h2>

<p>Damit ich im restlichen Kernel nicht über Zylinder/Kopf/Sektor nachdenken muss, stelle ich <code class="language-plaintext highlighter-rouge">floppy_read_lba</code> / <code class="language-plaintext highlighter-rouge">floppy_write_lba</code> bereit:</p>

<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kt">uint32_t</span> <span class="n">cylinder</span> <span class="o">=</span> <span class="n">lba</span> <span class="o">/</span> <span class="p">(</span><span class="mi">2</span> <span class="o">*</span> <span class="mi">18</span><span class="p">);</span>
<span class="kt">uint32_t</span> <span class="n">tmp</span> <span class="o">=</span> <span class="n">lba</span> <span class="o">%</span> <span class="p">(</span><span class="mi">2</span> <span class="o">*</span> <span class="mi">18</span><span class="p">);</span>
<span class="kt">uint32_t</span> <span class="n">head</span> <span class="o">=</span> <span class="n">tmp</span> <span class="o">/</span> <span class="mi">18</span><span class="p">;</span>
<span class="kt">uint32_t</span> <span class="n">sector</span> <span class="o">=</span> <span class="p">(</span><span class="n">tmp</span> <span class="o">%</span> <span class="mi">18</span><span class="p">)</span> <span class="o">+</span> <span class="mi">1</span><span class="p">;</span>
</code></pre></div></div>

<p>Das setzt die feste Geometrie einer 1,44MB-Diskette voraus: 2 Köpfe, 18 Sektoren pro Spur, 80 Zylinder (2×18×80 = 2880 Sektoren insgesamt, daher das <code class="language-plaintext highlighter-rouge">if (lba &gt;= 2880) return false;</code>). Es gibt keine Geometrie-Erkennung über den Media Descriptor und keine Unterstützung für andere Formate – für <code class="language-plaintext highlighter-rouge">tOSh</code> reicht ein einziger Diskettentyp völlig aus, und die fest codierte Geometrie spart eine ganze Abstraktionsebene, die ich nicht brauche.</p>

<h2 id="retry-logic">Retry logic</h2>

<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">bool</span> <span class="nf">floppy_read_sector</span><span class="p">(</span><span class="kt">uint8_t</span> <span class="n">cylinder</span><span class="p">,</span> <span class="kt">uint8_t</span> <span class="n">head</span><span class="p">,</span> <span class="kt">uint8_t</span> <span class="n">sector</span><span class="p">,</span> <span class="kt">uint8_t</span> <span class="o">*</span><span class="n">buffer</span><span class="p">)</span>
<span class="p">{</span>
    <span class="k">for</span> <span class="p">(</span><span class="kt">int</span> <span class="n">attempt</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="n">attempt</span> <span class="o">&lt;</span> <span class="mi">3</span><span class="p">;</span> <span class="n">attempt</span><span class="o">++</span><span class="p">)</span> <span class="p">{</span>
        <span class="k">if</span> <span class="p">(</span><span class="n">floppy_read_sector_impl</span><span class="p">(</span><span class="n">cylinder</span><span class="p">,</span> <span class="n">head</span><span class="p">,</span> <span class="n">sector</span><span class="p">,</span> <span class="n">buffer</span><span class="p">))</span>
            <span class="k">return</span> <span class="nb">true</span><span class="p">;</span>

        <span class="n">terminal_writestring</span><span class="p">(</span><span class="s">"FLOPPY: retrying read...</span><span class="se">\n</span><span class="s">"</span><span class="p">);</span>
        <span class="n">floppy_motor_on</span><span class="p">();</span>
        <span class="n">floppy_recalibrate</span><span class="p">();</span>
        <span class="n">floppy_motor_off</span><span class="p">();</span>
    <span class="p">}</span>
    <span class="k">return</span> <span class="nb">false</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Echte Disketten sind anfällig für vorübergehende Fehler: Staub, abgenutztes Medium, ein Kopf, der beim ersten Versuch nicht richtig aufgesetzt hat, einen <strong>KÜHLSCHRANKMAGNETEN ALS PAPIERBESCHWERER AUF EINEN STAPEL DISKETTEN ZU LEGEN</strong>, usw…</p>

<p>Statt beim ersten Fehler sofort aufzugeben, versuche ich es 3-mal erneut und mache zwischen jedem Versuch ein vollständiges <code class="language-plaintext highlighter-rouge">RECALIBRATE</code>, um den Kopf zu zwingen, seine physische Position neu zu synchronisieren – wenn der Fehler gerade dadurch verursacht wurde, dass der Kopf nicht korrekt positioniert war, behebt das Recalibrate das Problem vor dem nächsten Versuch.</p>

<h1 id="dma-8237-chip">DMA, 8237-Chip</h1>

<p>Der FDC liefert die gelesenen Daten bei <code class="language-plaintext highlighter-rouge">NDMA=0</code> (siehe Specify weiter oben) nicht Byte für Byte über den FIFO – er verwendet den 8237-DMA-Chip, um direkt in den Speicher zu schreiben, während die CPU etwas anderes macht (oder in unserem Fall in einem <code class="language-plaintext highlighter-rouge">hlt</code> wartet).</p>

<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">static</span> <span class="kt">void</span> <span class="nf">dma_transfer</span><span class="p">(</span><span class="kt">uint8_t</span> <span class="n">channel</span><span class="p">,</span> <span class="kt">uint32_t</span> <span class="n">address</span><span class="p">,</span> <span class="kt">uint16_t</span> <span class="n">count</span><span class="p">,</span> <span class="kt">uint8_t</span> <span class="n">mode</span><span class="p">)</span>
<span class="p">{</span>
    <span class="kt">uint8_t</span> <span class="n">page</span> <span class="o">=</span> <span class="p">(</span><span class="n">address</span> <span class="o">&gt;&gt;</span> <span class="mi">16</span><span class="p">)</span> <span class="o">&amp;</span> <span class="mh">0xff</span><span class="p">;</span>
    <span class="kt">uint16_t</span> <span class="n">offset</span> <span class="o">=</span> <span class="n">address</span> <span class="o">&amp;</span> <span class="mh">0xffff</span><span class="p">;</span>

    <span class="n">outb</span><span class="p">(</span><span class="n">DMA_MASK</span><span class="p">,</span> <span class="mh">0x04</span> <span class="o">|</span> <span class="n">channel</span><span class="p">);</span>   <span class="cm">/* deshabilita el canal */</span>
    <span class="n">outb</span><span class="p">(</span><span class="n">DMA_CLEAR_FF</span><span class="p">,</span> <span class="mi">0</span><span class="p">);</span>            <span class="cm">/* resetea el flip-flop de byte lo/hi */</span>

    <span class="n">outb</span><span class="p">(</span><span class="n">addr_port</span><span class="p">,</span> <span class="n">offset</span> <span class="o">&amp;</span> <span class="mh">0xff</span><span class="p">);</span>
    <span class="n">outb</span><span class="p">(</span><span class="n">addr_port</span><span class="p">,</span> <span class="p">(</span><span class="n">offset</span> <span class="o">&gt;&gt;</span> <span class="mi">8</span><span class="p">)</span> <span class="o">&amp;</span> <span class="mh">0xff</span><span class="p">);</span>
    <span class="n">outb</span><span class="p">(</span><span class="n">page_port</span><span class="p">,</span> <span class="n">page</span><span class="p">);</span>

    <span class="n">outb</span><span class="p">(</span><span class="n">DMA_CLEAR_FF</span><span class="p">,</span> <span class="mi">0</span><span class="p">);</span>

    <span class="n">count</span><span class="o">--</span><span class="p">;</span>                          <span class="cm">/* el 8237 cuenta N-1 */</span>
    <span class="n">outb</span><span class="p">(</span><span class="n">count_port</span><span class="p">,</span> <span class="n">count</span> <span class="o">&amp;</span> <span class="mh">0xff</span><span class="p">);</span>
    <span class="n">outb</span><span class="p">(</span><span class="n">count_port</span><span class="p">,</span> <span class="p">(</span><span class="n">count</span> <span class="o">&gt;&gt;</span> <span class="mi">8</span><span class="p">)</span> <span class="o">&amp;</span> <span class="mh">0xff</span><span class="p">);</span>

    <span class="n">outb</span><span class="p">(</span><span class="n">DMA_MODE</span><span class="p">,</span> <span class="n">mode</span> <span class="o">|</span> <span class="n">channel</span><span class="p">);</span>
    <span class="n">outb</span><span class="p">(</span><span class="n">DMA_MASK</span><span class="p">,</span> <span class="n">channel</span><span class="p">);</span>          <span class="cm">/* habilita el canal */</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Ein paar Punkte sind hier erwähnenswert:</p>

<ul>
  <li>
    <p>Das <strong>Flip-Flop</strong> ist ein internes Bit im 8237, das angibt, ob das nächste Byte, das an einen Adress-/Count-Port geschrieben wird, das Low-Byte oder das High-Byte ist. Da dieser Zustand zwischen den Kanälen geteilt wird, muss man es explizit zurücksetzen (<code class="language-plaintext highlighter-rouge">DMA_CLEAR_FF</code>), bevor man sowohl die Adresse als auch den Count schreibt – sonst kann es passieren, dass das High-Byte dort landet, wo eigentlich das Low-Byte hin sollte, und man am Ende eine Adresse zusammenbaut, die weiß der Geier wohin zeigt.</p>
  </li>
  <li>
    <p>Das <strong>Page Register</strong> (<code class="language-plaintext highlighter-rouge">DMA_PAGE_CH2 = 0x81</code>) ist ein Überbleibsel davon, dass der ursprüngliche 8237 für den Offset nur 16-Bit-Adressen verarbeitet. Um 24-Bit-Speicheradressen zu erreichen (wie sie ein echter PC braucht), wird das High-Byte der Adresse separat in das Page Register des entsprechenden Kanals geschrieben. Das ist auch der Grund für die Einschränkung, dass <strong>der Buffer keine 64KiB-Grenze überschreiten darf</strong>: Der 8237 erhöht den 16-Bit-Offset bei jedem übertragenen Byte, greift während der Übertragung aber nie auf das Page Register zu. Wenn der Buffer nahe am Ende einer 64-KB-Page beginnt und darüber hinausläuft, wechselt der DMA nicht auf die nächste Page – der Offset läuft über und beginnt, den Anfang derselben 64-KB-Page zu überschreiben. Deshalb habe ich den Floppy-Buffer so angelegt:</p>
  </li>
</ul>

<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">static</span> <span class="kt">uint8_t</span> <span class="n">floppy_dma_buffer</span><span class="p">[</span><span class="mi">512</span><span class="p">]</span>
    <span class="n">__attribute__</span><span class="p">((</span><span class="n">aligned</span><span class="p">(</span><span class="mi">512</span><span class="p">),</span> <span class="n">section</span><span class="p">(</span><span class="s">".dma"</span><span class="p">)));</span>
</code></pre></div></div>

<p>Die Ausrichtung auf 512 Bytes (die Größe eines Sektors) garantiert, dass du niemals einen 512-Byte-Buffer hast, der zum Beispiel bei Adresse <code class="language-plaintext highlighter-rouge">0xFFF00</code> beginnt und beim nächsten 64KB-Block über <code class="language-plaintext highlighter-rouge">0x10000</code> hinausläuft. Die <code class="language-plaintext highlighter-rouge">.dma</code>-Section des Linker-Scripts (siehe Teil III) legt diesen Buffer in einen eigenen, auf 4KB-Seiten ausgerichteten Bereich, weit weg von allen anderen Daten. Dadurch tritt <strong>in der Praxis nie ein Boundary-Crossing auf, sodass der Treiber dafür keine Runtime-Prüfung braucht</strong>.</p>

<ul>
  <li>
    <p>Der <strong>Modus</strong> (<code class="language-plaintext highlighter-rouge">0x46</code> zum Lesen, <code class="language-plaintext highlighter-rouge">0x4A</code> zum Schreiben) folgt der Namenskonvention des 8237 aus Sicht des Peripheriegeräts, nicht aus Sicht des Speichers: <code class="language-plaintext highlighter-rouge">dma_read()</code> bedeutet "der FDC liest von der Diskette und schreibt in den Speicher" (wodurch in <code class="language-plaintext highlighter-rouge">floppy_read_sector_impl</code> effektiv dein Buffer gefüllt wird), während <code class="language-plaintext highlighter-rouge">dma_write()</code> bedeutet "der Speicher wird gelesen und an den FDC geschrieben" (um Daten auf die Diskette zu schicken). Der Name ist leicht verwirrend, wenn man in Begriffen der CPU statt des Peripheriegeräts denkt – hier sind <code class="language-plaintext highlighter-rouge">dma_read</code>/<code class="language-plaintext highlighter-rouge">dma_write</code> aus der Perspektive "Was macht der FDC mit den Daten?" benannt, was mit der Dokumentation des Chips von Intel übereinstimmt.</p>
  </li>
  <li>
    <p><code class="language-plaintext highlighter-rouge">count--</code>, weil der 8237 den Count als <code class="language-plaintext highlighter-rouge">N-1</code> erwartet (du überträgst <code class="language-plaintext highlighter-rouge">count</code> Bytes, aber das interne Register zählt von <code class="language-plaintext highlighter-rouge">count-1</code> bis 0 herunter).</p>
  </li>
</ul>

<p>Grob gesagt richtet also <code class="language-plaintext highlighter-rouge">floppy_read_sector</code> mit <code class="language-plaintext highlighter-rouge">dma_read()</code> Kanal 2 für den Buffer und 512 Bytes ein, der <code class="language-plaintext highlighter-rouge">READ DATA</code>-Befehl wird an den FDC gesendet, die Übertragung läuft im Hintergrund, während die CPU mit <code class="language-plaintext highlighter-rouge">hlt</code> wartet, und wenn sie fertig ist, erzeugt der FDC IRQ6, den <code class="language-plaintext highlighter-rouge">floppy_wait_irq</code> behandelt.</p>

<p>Das ist eine ganze Menge, und vielleicht lassen sich manche Dinge besser verstehen, wenn man den Source-Code liest.</p>

<p>Wie ich am Anfang dieser ganzen Artikelserie schreibe, kannst du dir den Source von der Seite <a href="/resources/">Resources</a> herunterladen, wenn du möchtest.</p>

<h1 id="das-dateisystem">Das Dateisystem</h1>

<p><img src="/assets/images/tOSh/tOSh-listing.jpeg" alt="Probando el Filesystem en mi máquina del 97'" class="img-responsive" /><em>Das Dateisystem auf meiner ‘97er Maschine testen</em></p>

<h2 id="bringen-wir-es-zum-laufen">Bringen wir es zum Laufen'</h2>

<p>Für <code class="language-plaintext highlighter-rouge">tOSh</code> gibt es keine Blöcke, kein FAT, keinen Verzeichnisbaum, kein Journaling, keine langen Dateinamen und keine Pfade.</p>

<p><code class="language-plaintext highlighter-rouge">tOSh</code> scheißt auf den ganzen Kram, nicht einmal die Konsistenz deiner Daten interessiert es. <code class="language-plaintext highlighter-rouge">tOSh</code> ist ein Betriebssystem <strong>NUR FÜR DIE VERRÜCKTEN UND DIE MUTIGEN</strong>.</p>

<p>Nachdem das gesagt und diese Klarstellung gemacht ist, fange ich an, euch zu erzählen, wie ich das aufgebaut habe.</p>

<p>Es gibt ein Verzeichnis mit fester Größe für bis zu 256 Dateien, jede davon mit einem Namen, einem Startsektor und einer Größe. Punkt.</p>

<p>Der Grund dafür ist derselbe wie bei der festen Floppy-Geometrie: Jede Funktion eines echten Dateisystems (FAT, ext2, was auch immer) fügt eine Ebene der Indirektion hinzu – Blöcke, verkettete Cluster-Listen, Bitmaps für freien Speicher – die Probleme löst, die <code class="language-plaintext highlighter-rouge">tOSh</code> noch gar nicht hat, etwa echte Fragmentierung auf einer Diskette, die immer wieder gefüllt und geleert wird. Für das, was ich brauche (ein paar kleine Dateien während der Kernel-Entwicklung auf einer 1,44MB-Diskette persistent zu speichern), reicht ein flaches Verzeichnis völlig aus.</p>

<h2 id="disk-layout">Disk-Layout</h2>

<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cp">#define FS_SUPERBLOCK_SECTOR   256
</span>
<span class="cp">#define FS_DIRECTORY_START     257
#define FS_DIRECTORY_SECTORS   32
</span>
<span class="cp">#define FS_DATA_START          320
</span></code></pre></div></div>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>+--------------------------+
| 0    BOOT                |
+--------------------------+
| 1-8  STAGE1              |
+--------------------------+
| 9-136 KERNEL             | &lt;- 64 KiB
+--------------------------+
| 137-255 RESERVED         |
+--------------------------+
| 256     SUPERBLOCK       |
| 257-288 DIRECTORY        |
| 289-319 RESERVED         |
+--------------------------+
| 320-2879 FILE DATA       | &lt;- ~1.28 MB
+--------------------------+
</code></pre></div></div>

<p>Das Dateisystem beginnt bei Sektor 256, weit hinter dem Kernel (der bei 136 endet). Der Gap, den ich zwischen 137 und 255 lasse (119 Sektoren, ~59KB), ist Spielraum, damit der Kernel während der Entwicklung wachsen kann, ohne mit dem Dateisystem zu kollidieren – denkt daran, dass das <code class="language-plaintext highlighter-rouge">Makefile</code>/<code class="language-plaintext highlighter-rouge">build.sh</code> aus Teil III prüft, dass der Kernel die dafür vorgesehenen 128 Sektoren nicht überschreitet. Dieser reservierte Bereich ist also das tatsächliche Polster, bevor ich das gesamte Layout verschieben muss.</p>

<p>Der Gap zwischen 289 und 319 (31 Sektoren) ist dasselbe, aber für das Verzeichnis: <code class="language-plaintext highlighter-rouge">FS_DIRECTORY_SECTORS</code> ist fest auf 32 gesetzt, aber die tatsächliche Größe, die <code class="language-plaintext highlighter-rouge">struct fs_file[FS_MAX_FILES]</code> belegt, kann am Ende kleiner sein. Dieser Spielraum erspart mir, <code class="language-plaintext highlighter-rouge">FS_DATA_START</code> jedes Mal neu berechnen zu müssen, wenn ich <code class="language-plaintext highlighter-rouge">FS_MAX_FILES</code> oder die Größe von <code class="language-plaintext highlighter-rouge">fs_file</code> ändere.</p>

<h2 id="der-superblock">Der Superblock</h2>

<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">struct</span> <span class="n">fs_superblock</span> <span class="p">{</span>
    <span class="kt">uint32_t</span> <span class="n">magic</span><span class="p">;</span>
    <span class="kt">uint16_t</span> <span class="n">version</span><span class="p">;</span>
    <span class="kt">uint16_t</span> <span class="n">sector_size</span><span class="p">;</span>
    <span class="kt">uint32_t</span> <span class="n">total_sectors</span><span class="p">;</span>
    <span class="kt">uint32_t</span> <span class="n">data_start</span><span class="p">;</span>
<span class="p">}</span> <span class="n">__attribute__</span><span class="p">((</span><span class="n">packed</span><span class="p">));</span>
</code></pre></div></div>

<p><code class="language-plaintext highlighter-rouge">FS_MAGIC</code> ist <code class="language-plaintext highlighter-rouge">0x48534f54</code>, was als Little-Endian-ASCII-Bytes gelesen <code class="language-plaintext highlighter-rouge">TOSH</code> ergibt. <code class="language-plaintext highlighter-rouge">fs_is_initialized()</code> liest Sektor 256 und vergleicht diesen Magic-Wert – wenn er nicht übereinstimmt, geht es davon aus, dass die Diskette nie mit diesem Dateisystem formatiert wurde, und das war’s; es wird nicht versucht, irgendetwas von diesem Sektor zu "recovern" oder zu interpretieren. <code class="language-plaintext highlighter-rouge">__attribute__((packed))</code> ist notwendig, damit das Struct exakt die von mir angegebenen Bytes belegt, ohne Padding, das der Compiler zur Ausrichtung einfügen würde.</p>

<h2 id="das-verzeichnis">Das Verzeichnis</h2>

<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">struct</span> <span class="n">fs_file</span> <span class="p">{</span>
    <span class="kt">char</span>     <span class="n">name</span><span class="p">[</span><span class="n">FS_FILENAME_MAX</span><span class="p">];</span>
    <span class="kt">uint32_t</span> <span class="n">start_sector</span><span class="p">;</span>
    <span class="kt">uint32_t</span> <span class="n">size</span><span class="p">;</span>
    <span class="kt">uint8_t</span>  <span class="n">used</span><span class="p">;</span>
<span class="p">}</span> <span class="n">__attribute__</span><span class="p">((</span><span class="n">packed</span><span class="p">));</span>
</code></pre></div></div>

<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">static</span> <span class="k">struct</span> <span class="n">fs_file</span> <span class="n">directory</span><span class="p">[</span><span class="n">FS_MAX_FILES</span><span class="p">];</span>
</code></pre></div></div>

<p>Das gesamte Verzeichnis ist ein Array mit 256 Einträgen, das während der Laufzeit des Kernels im RAM liegt und bei jeder Änderung (create, write, delete) mit <code class="language-plaintext highlighter-rouge">fs_save_directory()</code> vollständig auf die Diskette geschrieben wird. Es gibt keine separate Bitmap für "freie Einträge" – ein Eintrag ist frei, wenn <code class="language-plaintext highlighter-rouge">used == 0</code> ist, und eine Datei zu suchen (<code class="language-plaintext highlighter-rouge">fs_find</code>) bedeutet einfach, das Array durchzugehen und die Namen zu vergleichen.</p>

<p>Das bedeutet, dass jede Operation, die das Dateisystem verändert, alle 32 Sektoren des Verzeichnisses neu schreibt, selbst wenn sich nur ein einziger Eintrag geändert hat. In Bezug auf I/O ist das offensichtlich nicht optimal, aber bei einer Diskette, die ohnehin schon quälend langsam ist, macht der Unterschied zwischen 1 und 32 geschriebenen Sektoren keinen Unterschied für die Benutzererfahrung, und ich erspare mir, nachverfolgen zu müssen, welcher bestimmte Verzeichnissektor zu welchem Eintrag gehört.</p>

<h2 id="keinen-freien-speicher-verwalten">Keinen freien Speicher verwalten</h2>

<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">static</span> <span class="kt">uint32_t</span> <span class="nf">fs_next_free_sector</span><span class="p">(</span><span class="kt">void</span><span class="p">)</span>
<span class="p">{</span>
    <span class="kt">uint32_t</span> <span class="n">sector</span> <span class="o">=</span> <span class="n">FS_DATA_START</span><span class="p">;</span>

    <span class="k">for</span> <span class="p">(</span><span class="kt">int</span> <span class="n">i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="n">i</span> <span class="o">&lt;</span> <span class="n">FS_MAX_FILES</span><span class="p">;</span> <span class="n">i</span><span class="o">++</span><span class="p">)</span> <span class="p">{</span>
        <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">directory</span><span class="p">[</span><span class="n">i</span><span class="p">].</span><span class="n">used</span><span class="p">)</span>
            <span class="k">continue</span><span class="p">;</span>

        <span class="kt">uint32_t</span> <span class="n">sectors</span> <span class="o">=</span> <span class="p">(</span><span class="n">directory</span><span class="p">[</span><span class="n">i</span><span class="p">].</span><span class="n">size</span> <span class="o">+</span> <span class="mi">511</span><span class="p">)</span> <span class="o">/</span> <span class="mi">512</span><span class="p">;</span>
        <span class="kt">uint32_t</span> <span class="n">end</span> <span class="o">=</span> <span class="n">directory</span><span class="p">[</span><span class="n">i</span><span class="p">].</span><span class="n">start_sector</span> <span class="o">+</span> <span class="n">sectors</span><span class="p">;</span>

        <span class="k">if</span> <span class="p">(</span><span class="n">end</span> <span class="o">&gt;</span> <span class="n">sector</span><span class="p">)</span>
            <span class="n">sector</span> <span class="o">=</span> <span class="n">end</span><span class="p">;</span>
    <span class="p">}</span>

    <span class="k">return</span> <span class="n">sector</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Die Speicherzuweisung ist so primitiv, wie es nur geht: Ich gehe alle belegten Dateien durch, finde das höchste <code class="language-plaintext highlighter-rouge">start_sector + sectors_occupied</code>, und dort beginnt die nächste Datei. Es gibt keine Bitmap für freie Sektoren, keine Free-List, keine Kompression.</p>

<p>Die direkte Konsequenz daraus –und eine bewusste Entscheidung, <strong>DAS IST KEIN BUG, DAS IST EIN FEATURE</strong>– ist, dass <code class="language-plaintext highlighter-rouge">fs_delete()</code> <strong>den Speicher weder freigibt noch recycelt</strong>.</p>

<p>Beim Löschen einer Datei wird nur ihr Verzeichniseintrag geleert (<code class="language-plaintext highlighter-rouge">used = 0</code>); die von ihr belegten Sektoren bleiben dort und werden von <code class="language-plaintext highlighter-rouge">fs_next_free_sector()</code> für immer ignoriert, weil diese Funktion keine Einträge mit <code class="language-plaintext highlighter-rouge">used == 0</code> berücksichtigt, sondern nur den am weitesten hinten liegenden Bereich, der von <strong>aktuell</strong> existierenden Dateien belegt ist… aber dieser am weitesten hinten liegende Punkt kann von einer Datei hinterlassen worden sein, die gar nicht mehr existiert. Anders gesagt: Dateien zu erstellen und zu löschen hinterlässt Lücken, die niemals wieder verwendet werden, bis man die gesamte Diskette mit <code class="language-plaintext highlighter-rouge">fs_format()</code> neu formatiert. Das nannte man <strong>Festplattenfragmentierung</strong> und genau das sollte der "Windows-Defragmentierer" beheben. Bei MS-DOS und Windows 9x trat das aus anderen Gründen ebenfalls ein bisschen auf.</p>

<p>Für den aktuellen Stand von <code class="language-plaintext highlighter-rouge">tOSh</code> ist das akzeptabel: Das Dateisystem ist (noch, wer weiß) nicht für intensives, lang andauerndes Schreiben und Löschen gedacht, sondern dafür, eine Handvoll Konfigurationsdateien oder System-Binaries persistent zu speichern. Wenn das eines Tages zu einem echten Problem wird (kkjjj, also niemals, lang lebe <code class="language-plaintext highlighter-rouge">tOSh</code>), wäre die einfachste Lösung ohne das Design komplett neu zu bauen, eine Bitmap für freie Sektoren hinzuzufügen oder auf ein Block-Schema mit Free-List umzusteigen – aber das würde das Dateisystem komplett verändern.</p>

<h2 id="operationen">Operationen</h2>

<p><code class="language-plaintext highlighter-rouge">fs_create</code>, <code class="language-plaintext highlighter-rouge">fs_write</code>, <code class="language-plaintext highlighter-rouge">fs_read</code> und <code class="language-plaintext highlighter-rouge">fs_delete</code> sind angesichts des oben erklärten Designs ziemlich direkt:</p>

<ul>
  <li>
    <p><strong><code class="language-plaintext highlighter-rouge">fs_create</code></strong> sucht einen freien Eintrag, weist ihm über <code class="language-plaintext highlighter-rouge">fs_next_free_sector()</code> den nächsten Sektor zu, speichert den Namen und persistiert das Verzeichnis. Die Größe startet bei 0 – es gibt noch keine Daten, nur die Reservierung der Position.</p>
  </li>
  <li>
    <p><strong><code class="language-plaintext highlighter-rouge">fs_write</code></strong> teilt die Daten in 512-Byte-Sektoren auf (und füllt den letzten mit Nullen auf, wenn die Größe kein exaktes Vielfaches ist) und schreibt sie ab <code class="language-plaintext highlighter-rouge">start_sector</code> sequentiell. Es aktualisiert <code class="language-plaintext highlighter-rouge">size</code> und persistiert erneut das gesamte Verzeichnis.</p>
  </li>
  <li>
    <p><strong><code class="language-plaintext highlighter-rouge">fs_read</code></strong> macht das Gegenteil: Es berechnet anhand von <code class="language-plaintext highlighter-rouge">size</code>, wie viele Sektoren die Datei belegt, liest sie alle und kopiert nur die gültigen Bytes (also nicht das Null-Padding) in den Buffer des Callers.</p>
  </li>
  <li>
    <p><strong><code class="language-plaintext highlighter-rouge">fs_delete</code></strong> leert den Eintrag, ohne die Datensektoren anzufassen (siehe direkt oben).</p>
  </li>
</ul>

<p>Keine dieser vier Funktionen unterstützt Dateien, die über den freien Platz hinauswachsen, den sie zum Zeitpunkt ihrer Erstellung hatten – es gibt kein Konzept, eine Datei über den Platz hinaus zu "erweitern", der beim ersten zusammenhängenden Schreiben verfügbar war, weil <code class="language-plaintext highlighter-rouge">fs_write</code> immer ab <code class="language-plaintext highlighter-rouge">start_sector</code> schreibt und davon ausgeht, dass der Platz bereits durch <code class="language-plaintext highlighter-rouge">fs_create</code> + die Erstellungsreihenfolge der anderen Dateien reserviert wurde.</p>

<h2 id="bekannte-einschränkungen">Bekannte Einschränkungen</h2>

<p>Damit das dokumentiert ist und nicht mit der Zeit verloren geht, wenn ich in drei Jahren wieder anfange, Dinge in <code class="language-plaintext highlighter-rouge">tOSh</code> zu implementieren:</p>

<ul>
  <li>Feste maximale Dateisystemgröße von <code class="language-plaintext highlighter-rouge">FS_TOTAL_SECTORS = 2880</code> (die kompletten 1,44MB der Diskette, ohne zu berücksichtigen, dass ein Teil davon bereits vom Bootloader/Kernel belegt ist – dafür zu sorgen, dass man das nicht überschreibt, ist Aufgabe eines korrekt gesetzten <code class="language-plaintext highlighter-rouge">FS_DATA_START</code> und nichts, was das Dateisystem zur Laufzeit überprüft). Trotzdem wäre es vielleicht ganz nett, wenn ich das Dateisystem noch einmal anfasse oder neu schreibe, auch Unterstützung für Festplatten einzubauen und das von Anfang an zu berücksichtigen, um das Ganze etwas ernsthafter zu machen. Mal sehen.</li>
  <li>Nicht-hierarchische Dateinamen: <code class="language-plaintext highlighter-rouge">FS_FILENAME_MAX = 32</code> Bytes, kein Path-Trenner, alles lebt im selben hardcodierten Namespace.</li>
  <li>Keine Dateifragmentierung: Jede Datei belegt einen zusammenhängenden Bereich von Sektoren. Das vereinfacht den Code enorm (man muss keine Blockketten verwalten), bedeutet aber auch, dass große Dateien in einem Dateisystem mit vielen Lücken durch vorherige Löschvorgänge irgendwann nicht mehr hineinpassen, obwohl insgesamt noch genug "freier Speicher" über die verschiedenen Lücken verteilt vorhanden ist. Irgendwann wird das Dateisystem, nachdem es genug Dateien erstellt und gelöscht hat, einfach crashen :)</li>
  <li>Kein Journaling und keinerlei Recovery-Mechanismus für einen Stromausfall mitten in einem <code class="language-plaintext highlighter-rouge">fs_save_directory()</code>. Wenn das Schreiben des Verzeichnisses mittendrin abgebrochen wird, bleibt eine teilweise geschriebene Version zurück und man kann sogar das gesamte Dateisystem verlieren, weil auch der Superblock beschädigt werden kann.</li>
</ul>

<p>Beim nächsten Mal lassen wir <code class="language-plaintext highlighter-rouge">tOSh</code> auf mehreren Maschinen laufen, emulierten und echten, geliehenen und/oder zu diesem Zweck von lieben Nerd-Freunden und Kollegen gespendeten, die ich sehr respektiere und ganz doll lieb habe.</p>

<h1 id="referenzen">Referenzen</h1>

<ul>
  <li><a href="https://en.wikipedia.org/wiki/Polling_(computer_science)">Polling</a></li>
  <li><a href="https://en.wikipedia.org/wiki/Kernel_%28operating_system%29">Kernel</a></li>
  <li><a href="https://wiki.osdev.org/Setting_Up_Long_Mode">Long Mode</a></li>
  <li><a href="https://wiki.osdev.org/Real_Mode">Modo Real</a></li>
  <li><a href="https://wiki.osdev.org/Protected_Mode">Modo Protegido</a></li>
  <li><a href="https://wiki.osdev.org/Global_Descriptor_Table">GDT</a></li>
  <li><a href="https://wiki.osdev.org/Interrupt_Descriptor_Table">IDT</a></li>
  <li><a href="https://wiki.osdev.org/Interrupt_Service_Routines">ISR</a></li>
  <li><a href="https://wiki.osdev.org/Linker_Scripts">Linker Scripts</a></li>
  <li><a href="https://www.eeeguide.com/8259a-programmable-interrupt-controller/">Chip 8259a PIC</a></li>
  <li><a href="https://wiki.osdev.org/8259_PIC">8259 PIC osdev</a></li>
  <li><a href="https://wiki.osdev.org/APIC">APIC</a></li>
  <li><a href="https://wiki.osdev.org/X86_Interrupts">Interrupciones</a></li>
  <li><a href="https://wiki.osdev.org/Floppy_Disk_Controller">FDC osdev</a></li>
  <li><a href="https://wiki.osdev.org/ISA_DMA">8237 DMA osdev</a></li>
  <li><a href="https://wiki.osdev.org/Programmable_Interval_Timer">PIT</a></li>
  <li><a href="https://en.wikipedia.org/wiki/Modified_frequency_modulation">MFM</a></li>
  <li><a href="https://floppy.cafe/">floppy.cafe</a></li>
</ul>]]></content><author><name>toshi</name></author><category term="coding" /><category term="msdos" /><category term="sistema de archivos" /><category term="intel" /><category term="amd" /><category term="x64" /><category term="x86" /><category term="osdev" /><category term="Betriebssysteme" /><category term="floppies" /><summary type="html"><![CDATA[Wir machen unsere Dateien persistent, aber nur auf einer 3,5"-Diskette.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://0x705h.com/assets/images/tOSh/fuck-win-go-tOSh.jpeg" /><media:content medium="image" url="https://0x705h.com/assets/images/tOSh/fuck-win-go-tOSh.jpeg" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry xml:lang="es"><title type="html">PuTTY-Color</title><link href="https://0x705h.com/de/PuTTY-color" rel="alternate" type="text/html" title="PuTTY-Color" /><published>2026-06-23T11:37:00+00:00</published><updated>2026-06-23T11:37:00+00:00</updated><id>https://0x705h.com/PuTTY-color</id><content type="html" xml:base="https://0x705h.com/PuTTY-color"><![CDATA[<blockquote>
  <p>¿Tu PuTTY tiene solamente 16 colores y estás usando tmux todo el día? Vamos a configurar todo como corresponde de una buena vez</p>
</blockquote>

<p>Miremos juntos estas dos hermosas capturas de pantalla: acá abajo podemos apreciar muy fácilmente la diferencia de colores
entre una terminal y la otra. Ambas coloridas pero también una con más detalle que la otra.</p>

<div class="d-flex justify-content-around">
    <div class="">

        <img src="/assets/images/misc/putty/xterm-16color.PNG" alt="Una Terminal con una Paleta Triste :(" class="img-responsive" />
        <em>Una Terminal con una Paleta Triste :(</em>

    </div>

    <div class="">


        <img src="/assets/images/misc/putty/xterm-256color.PNG" alt="Una Terminal con una Paleta Feliz :D" class="img-responsive" />
        <em>Una Terminal con una Paleta Feliz :D</em>

    </div>
</div>

<p>Bien.</p>

<p>Más abajo también podes encontrar un script en Python que al ejecutarlo,
te arma la paleta de colores. Si el resultado después de ejecutar el script
es como el screenshot de la izquierda, seguí leyendo. Pero si es como el de la 
derecha, entonces ya sabés que es lo que esta pasando y antes que te vayas
de este sitio copia el link y pasaselo a algún amigo que necesite tener 
más color en su vida &lt;3</p>

<p>¡Copate, dale! Si le das like a cualquier cosa. ¿Como no vas a pasar este artículo que esta buenísimo
y encima te pimpea el laburo a ese pibe que te cae tan bien y tiene que tener tantos colores en su terminal como vos?</p>

<p>.:: fin de anuncio berreta ::.</p>

<noscript><pre>404: Not Found</pre></noscript>
<script src="https://gist.github.com/036523669f428105ca81.js"> </script>

<h1 id="configuración-del-server">Configuración del Server</h1>

<p>La versión oficial dice que tenemos que exportar <code class="language-plaintext highlighter-rouge">$TERM</code> con el 
valor <code class="language-plaintext highlighter-rouge">screen-256color</code> en tu <code class="language-plaintext highlighter-rouge">.*rc</code> si estás usando <code class="language-plaintext highlighter-rouge">tmux</code> o 
<code class="language-plaintext highlighter-rouge">term-256color</code> si no.</p>

<p>Mi configuración <a href="https://github.com/0x705h/tmux-config">tmux-config</a> ya tiene por defecto configurado <code class="language-plaintext highlighter-rouge">screen-256color</code>:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>╭─tsh at verso <span class="k">in</span> ~ 26-02-11 - 8:12:26
╰─○ <span class="nb">grep</span> <span class="nt">-Rni</span> screen-256color .tmux
.tmux/tmux.conf:5:set <span class="nt">-g</span> default-terminal <span class="s2">"screen-256color"</span>
</code></pre></div></div>

<p>Pero en mi <code class="language-plaintext highlighter-rouge">.zshrc</code> lo configuré con xterm:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>╭─tsh at verso <span class="k">in</span> ~ 26-02-11 - 8:14:07
╰─○ <span class="nb">cat</span> ~/.zshrc |grep 256color
<span class="nb">export </span><span class="nv">TERM</span><span class="o">=</span>tmux-256color
</code></pre></div></div>

<p>Eso es todo del lado del server.</p>

<h1 id="configuración-de-putty">Configuración de PuTTY</h1>

<p>Configurá <code class="language-plaintext highlighter-rouge">Connection -&gt; Data -&gt; Terminal-type string</code> a <code class="language-plaintext highlighter-rouge">xterm-256color</code></p>

<div class="d-flex justify-content-around">
    <div class="">

        <img src="/assets/images/misc/putty/xterm-256color-putty-config.PNG" alt="" class="img-responsive" />
        <em>Es acá la configuración!!!!1</em>

    </div>
</div>

<p>Listo. Reiniciá la sesión de tu PuTTY y disfrutá tus comandos en 4K UHD 100% REAL NO FAKE.</p>

<p>De nada, ¡Titán! :)</p>

<h1 id="referencias">Referencias</h1>

<ul>
  <li><a href="https://www.chiark.greenend.org.uk/~sgtatham/putty/latest.html">PuTTY</a></li>
  <li><a href="https://gist.github.com/limingjie/036523669f428105ca81">terminalcolors.py</a></li>
</ul>]]></content><author><name>toshi</name></author><category term="misc" /><category term="term" /><category term="xterm" /><category term="screen" /><category term="tmux" /><category term="putty" /><summary type="html"><![CDATA[¿Tu PuTTY tiene solamente 16 colores y estás usando tmux todo el día? Vamos a configurar todo como corresponde de una buena vez]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://0x705h.com/assets/images/misc/putty/xterm-16color.PNG" /><media:content medium="image" url="https://0x705h.com/assets/images/misc/putty/xterm-16color.PNG" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry xml:lang="es"><title type="html">Construyendo un Datacenter</title><link href="https://0x705h.com/de/construyendo-un-datacenter" rel="alternate" type="text/html" title="Construyendo un Datacenter" /><published>2024-12-17T11:37:00+00:00</published><updated>2024-12-17T11:37:00+00:00</updated><id>https://0x705h.com/construyendo-un-datacenter</id><content type="html" xml:base="https://0x705h.com/construyendo-un-datacenter"><![CDATA[<!-- Empecé a escribir este artículo el 2024-04-12 13:37:00 +0200
y después fueron pasando algunas cosas laborales poco felices. 
Continué trabajando en este proyecto hasta hoy, que creció mucho más
allá de lo que tenía planeado en un principio.
-->

<p><strong>Armar un Datacenter</strong>. <code class="language-plaintext highlighter-rouge">Tuqui.</code></p>

<p>Una cosa que quise hacer desde siempre. ¿Desde <strong>cuándo siempre</strong>? 
Desde mi tierna adolescencia: cuando comprendí que un par de compus en red podían 
sentar las bases infraestructurales
para levantar servicios "de la interwebz". Lo interesante, es que en todas 
las iteraciones que lo intenté, a lo largo de todos estos años, siempre me faltó
algo, mayormente tiempo y dinero. Usé computadoras variopintas para estos proyectos 
pero siempre terminaba todo en la nada ya sea por falta de hardware, conocimiento,
tiempo o por pura paja.</p>

<p>También estoy escribiendo este artículo hace demasiado y en algún momento
tengo que frenar. Lo último que estaba haciendo antes de cortar este
artículo era configurar todo un entorno de desarrollo y deployment 
con <code class="language-plaintext highlighter-rouge">Flux</code>. Está muy bien, pero después de <code class="language-plaintext highlighter-rouge">Flux</code>: ¿Qué sigue?
Y… todo sigue. Esta profesión tiene la particularidad que pareciera
ser infinita y nunca terminara.</p>

<p>En algún momento del año pasado, se estaba cristalizando la idea de armar 
un pequeño datacenter (o HomeLab) para mi.
En Hetzner, tenía varios servicios funcionando para mis cosas, pero algunas de 
estas aplicaciones requerían mucha capacidad de almacenamiento y estaba volviéndose
muy caro mantener todo esto en "la nube".</p>

<p>A mitad del año pasado, reduzco la cantidad de servidores al mínimo: paso
de 7 servidores para mis servicios a solamente 2, con moderado éxito.
Tengo que remover y hacer backups de las aplicaciones que uso que requieren mucho
almacenamiento y ponerlas offline para reducir costos.</p>

<p>Por otro lado, consigo un NAS Synology DS923+ con varios discos 
y es suficiente para almacenar muchas de mis <strong>otras cosas</strong>, 
pero en algún momento, necesito volver a tener mis 
servicios andando nuevamente y el almacenamiento ya no es un problema.</p>

<p>Entonces con el aguinaldo en mano, hago unos presupuestos e investigo que 
opciones hay en mercados de usados/refurbished para conseguir algunas computadoras,
placas, cables, Switches y abandonar "la nube" completamente.</p>

<p>Antes de empezar, debo decir que hay una tendencia en la industria del software 
que está empezando a considerar cada vez más fuertemente alejarse de las soluciones
corporativas de "la nube" y hostear el software on-premise. 
Todavía no entiendo bien si esta tendencia está dada para abaratar costos 
en el largo plazo o simplemente porque armar soluciones en la nube, hoy,
es mucho más sencillo.</p>

<p>Y antaño es hace, no sé, ¿Dos, tres años? ;D</p>

<h1 id="el-hardware">El Hardware</h1>

<p>Empecemos por listar el hardware mínimo para correr mi Datacenter:</p>

<ul>
  <li>Synology DS923+, 4 Bay, 2 HDD 8TB y 2 HDD 16TB en SHR1. Todos los discos de tipo NAS menos uno que es un EXOS y que por ahora no esta integrado al volumen y lo tengo como hotspare. Capacidad total 14 TB. La configuración no es óptima asi como tampoco el precio de estos discos.</li>
  <li>Cuatro Lenovo ThinkCentre (m720q y m920q) Tiny, ~i5-9500T, 16/32 GB, 240 GB SSD</li>
  <li>Placa de red Intel i350-T4 4 port gigabit PCIe</li>
  <li>01AJ940 Raiser Para Lenovo Thinkcentre M920x</li>
  <li>TP-Link Archer AX55 Wi-Fi 6 WLAN Router, 2402 Mbps 5 GHz, 574 Mbps 2.4 GHz, 4 × Gigabit LAN Ports, 1 × USB 3.0 Port</li>
  <li>TP-Link TL-SG116E 16-Port Gigabit Switch</li>
  <li>Cables Cat 5.e ó mejores</li>
  <li>Netgear GS108E Managed Switch 8 Port Gigabit Ethernet LAN Switch Plus</li>
</ul>

<p>Muchos de estos componentes son relativamente baratos. Los más baratos son los switches,
los cables y el router wifi.
Los más caros, son las computadoras y la placa de red Intel, que es usada para servidores. 
Lo interesante de esto, es que todos los componentes caros <strong>son usados y 
refurbisheados</strong>, lo que los hacen increiblemente accessibles, sin contar que estamos
dándole vida a cosas que tiraron prácticamente a la basura previamente.
En el país en el que vivo, hay empresas que se dedican a restaurar y arreglar 
hardware viejo y roto para la reventa a precios ridículos.</p>

<p>Los switches parecen una exageración, pero tengo muchos dispositivos conectados a 
una red <a href="/de/conectandose-a-bbs-como-en-1997">una Pentium MMX</a>, una Commodore 64 con WIC64 y otras cosas divertidas las cuales escriba quisá en algún otro post.</p>

<p><img src="/assets/images/datacenter/thinkcentres-abiertas.jpg" alt="Dos ThinkCentre Abiertas" class="img-responsive" /><em>Desarmando dos ThinkCentres y preparando la instalación de una placa</em></p>

<p>Lo interesante de estas máquinas Lenovo ThinkCentre M920q Tiny, es que son muy 
pequeñas, y el consumo en IDLE ronda alrededor de 10W asi que la factura de luz no se incrementa demasiado 
según mis cálculos y sigue siendo una opción más barata 
que rentar máquinas virtuales en cualquier proveedor de "la nube". 
También es interesante que estas máquinas, particularmente soportan <a href="https://en.wikipedia.org/wiki/Input%E2%80%93output_memory_management_unit">IOMMU</a> y <a href="https://en.wikipedia.org/wiki/Single-root_input/output_virtualization">SR-IOV</a>
para poder hacer <a href="https://pve.proxmox.com/wiki/PCI(e)_Passthrough">PCIe Passthrough</a>, que es precisamente algo importantísimo en mi 
setup, ya que necesito armar un firewall y un router para mi datacenter.
Y es acá donde entra en acción la interacción entre las <strong>ThinkCentre y la placa 
de red Intel i350T4</strong>.</p>

<p><img src="/assets/images/datacenter/i350T4.jpg" alt="Intel i350T4" class="img-responsive" /><em>La placa de red que va a tener una de las ThinkCentre</em></p>

<p>El primer problema de todos, es como metemos esta placa en una máquina con 
espacio tan reducido y además, con un slot PCIe que tiene una orientación 
que simplemente no permite la instalación de ninguna placa.</p>

<p><img src="/assets/images/datacenter/thinkcentre-pcie.jpg" alt="Slot PCIe" class="img-responsive" /><em>Oops! La placa de red no entra ni a palos acá en el único slot PCIe</em></p>

<p>Ahi es donde viene una parte que solo pude conseguir en China, que es 
el <code class="language-plaintext highlighter-rouge">Riser 01AJ940</code>, específicamente diseñado para cambiar la dirección vertical del slot
a una horizontal.</p>

<p><img src="/assets/images/datacenter/riser-sin-placa.jpg" alt="Riser 01AJ940" class="img-responsive" /><em>Riser 01AJ940 conectado a una de las ThinkCentre. Se puede ver como cambia la dirección del slot</em></p>

<p>Esta parte, debería haber venido con un adaptador para la parte del chasis de
la ThinkCentre que lo que hace es sostener la placa en el aire para que los contactos
de la parte trasera de la placa no toquen ningún componente del motherboard ó el
chasis. 
Y no, solo me vino el Riser sin el adapador, asi que tuve que atarlo con alambre.</p>

<p><img src="/assets/images/datacenter/placa-carton-1.jpg" alt="Riser 01AJ940 con Carton" class="img-responsive" /><em>Cortamos unos pedazos de cartón…</em></p>

<p><img src="/assets/images/datacenter/placa-carton-2.jpg" alt="Riser 01AJ940 con Carton 2" class="img-responsive" /><em>… y se lo chantamos ahi masomeno’</em></p>

<p><img src="/assets/images/datacenter/placa-carton-3.jpg" alt="Riser 01AJ940 con Carton 3" class="img-responsive" /><em>No cierra bien el chasis de este servidor, y quedaron unos huecos a los costados de la placa, pero bueno :D</em></p>

<p>Con esta máquina ensamblada de nuevo, ya podemos empezar a armar en uno de nuestros
nodos del Datacenter, un router/firewall con pfSense, virtualizado obvio, 
pero permitiéndole a la VM capturar el hardware directamente vía <a href="https://pve.proxmox.com/wiki/PCI(e)_Passthrough">PCIe Passthrough</a>.</p>

<p>Es importante destacar que la elección de todo este hardware estuvo directamente
basada en las funcionalidades descriptas para poder permitir que las VMs puedan 
usar la placa de red directamente, sin que el sistema operativo del Host 
estuviera involucrado.</p>

<p>Interesantemente, antes de comprar este hardware estuve jugando muchísimo con 
<a href="https://libvirt.org/docs.html">libvirt</a> pensando inocentemente que podría emular todo el datacenter en mi 
PC de escritorio. En teoría es posible, pero el hardware de mi PC de escritorio
es muy malo y no solo no me permite capturar componentes conectados en los slots PCIe, 
sino que también mi placa de red <code class="language-plaintext highlighter-rouge">Realtek</code> es una reverenda mierda. 
Pude hacer andar todo, si, pero sin buen hardware, las redes virtualizadas en placas
de mierda, son tremendamente inestables.</p>

<p>Solo cuando me dí cuenta de esto, entendí que debía comprar hardware de todas maneras.
Además, inutilizar mi PC para correr un cluster, tampoco era una opción.</p>

<h1 id="proxmox">Proxmox</h1>

<p><img src="/assets/images/datacenter/proxmox-screen.jpg" alt="Instalando Proxmox" class="img-responsive" /><em>Instalando Proxmox</em></p>

<p>Habiendo ganado mucha experiencia (y peleas) con <a href="https://libvirt.org/docs.html">libvirt</a> tuve que tomar una decisión. 
¿Uso <a href="https://libvirt.org/docs.html">libvirt</a> o cambio a <a href="https://www.proxmox.com/en/">Proxmox</a>?</p>

<p>Por detalles en los que no voy a ahondar, prácticamente <a href="https://www.proxmox.com/en/">Proxmox</a> fue la 
decisión correcta. Integra el Hypervisor KVM, soporta Linux Containers (LXC) 
out-of-the-box y también funciona como cluster. Cada instalación de <a href="https://www.proxmox.com/en/">Proxmox</a>
puede funcionar como un nodo único o podés incluir el nodo en un cluster para 
organizar y distribuir el uso de recursos de containers o VMs.</p>

<p>Toda la explicación del hardware en este artículo converge exactamente en este 
mapeo de recursos. La <strong>VM "pfSense"</strong> tiene estos recursos asignados:</p>

<p><img src="/assets/images/datacenter/proxmox-iommu-mappings.png" alt="Proxmox Intel i350-IOMMU" class="img-responsive" /><em>Proxmox Intel i350-IOMMU - Dispositivo Mappeado a esta VM con PCI Passthrough</em></p>

<p>Ahora, al instalar <code class="language-plaintext highlighter-rouge">pfSense</code> en una VM hosteada por <a href="https://www.proxmox.com/en/">Proxmox</a>,<code class="language-plaintext highlighter-rouge">pfSense</code> detecta 
los cuatro puertos ethernet como si fueran propios, interactuando directamente 
con el hardware, sin intermediarios.</p>

<p><img src="/assets/images/datacenter/pfsense-interfaces.png" alt="pfSense Interfaces" class="img-responsive" /><em>Interfases detectadas por pfSense via a Proxmox VM con PCI Passthrough</em></p>

<p>En este punto, ya tenemos un router andando. Como configurar un router/firewall como pfSense amerita un artículo completamente distinto y mi setup es más complejo que 
lo que quiero explicar en este. 
Pero brevemente, tengo 4 interfaces, de los cuales salen 4 cables a el switch 
de 16 puertos, segmentado o aislados por <a href="https://en.wikipedia.org/wiki/VLAN">VLANs</a> para separar las compus viejas, el wifi, la tele y varios dispositivos electrónicos, asi como también no permitir 
el acceso a mi red a invitados.
¿Porqué? Porque tengo conectado un <code class="language-plaintext highlighter-rouge">Windows 3.11</code> en mi red con File Sharing a un <code class="language-plaintext highlighter-rouge">Windows 10</code>,
una máquina <code class="language-plaintext highlighter-rouge">MS-DOS</code> también con <code class="language-plaintext highlighter-rouge">Packet Driver</code> y una 3COM y cualquiera que tuviera
una PC conectada a mi red, <a href="https://0ut3r.space/2022/06/16/smb-hacking/">podría hacer desmanes</a>. 
Sin contar que la <code class="language-plaintext highlighter-rouge">Commodore 64</code> también está conectada a la red y <strong>a la internet</strong>.</p>

<p>Mismo procedimiento luego para incluir la otra ThinkCentre en el cluster <a href="https://www.proxmox.com/en/">Proxmox</a>,
joinearlo como nodo y conectando un drive NFS para tener almacenamiento suficiente
para las VMs.</p>

<p>Y ahi arrancó un nuevo problema.</p>

<h1 id="demasiado-acceso-a-disco">Demasiado Acceso a Disco</h1>

<p>Resulta que cuando empecé a conectar VMs al drive NFS del NAS, los discos empezaron
a hacer ruido, y <em>fuerte</em>. 
Aunque tenga redundancia en los discos a través de SHR, tampoco es que por solamente
tenerlo encendido para que las VMs estén disponibles, vayan desgastándolos 
lentamente acortando su vida útil, eventualmente fallando uno (o varios) de ellos,
y tener que reemplazar el disco roto. 
Y los discos no son algo necesariamente barato. 
Asi que me puse a ver como hacer para minimizar la escritura a disco de las VMs
de Linux que tenia corriendo.</p>

<p>Las distribuciones de Linux, o al menos las más comunes o importantes, tienen settings
por default que necesitamos cambiar.</p>

<p>Habiendo cambiado algunas propiedades en el <code class="language-plaintext highlighter-rouge">fstab</code> y midiendo el las escrituras
y lecturas pude minimizar las escrituras. Pero configurar VM por VM cuando tenés
más de diez corriendo, es totalmente impracticable. Especialmente si por cada nueva
instalación de una VM, tenés que pasar por los mismos tweaks una y otra vez.</p>

<p>Entonces, a la 3er VM que estaba configurando, pensé en armar un template "base"
con todas las cosas configuradas y chantársela a las nuevas VMs en el momento
de la creación. Asi, solo configuro una imagen con todos mis settings óptimos
y me olvido de cualquier configuración adicional.</p>

<p>Con <a href="https://www.packer.io/">Packer</a> de Hashicorp, preparar imágenes para VMs es "relativamente" sencillo.</p>

<h2 id="packer-y-cloud-init-un-solo-corazón">Packer y cloud-init, un solo corazón</h2>

<p>Con <a href="https://www.packer.io/">Packer</a> lo que vamos a hacer es crear una imagen exclusiva para mi cluster (o el tuyo), 
para que cada nueva VM tenga todo configurado, ahorrándonos un montón de tiempo.</p>

<p>Cuando dije anteriormente que <a href="https://www.packer.io/">Packer</a> es "relativamente" sencillo, es en principio
verdad. Definís una VM para buildear la imágen, conectas un ISO con <code class="language-plaintext highlighter-rouge">cloud-init</code> 
y le decís a la imagen como construirse con <code class="language-plaintext highlighter-rouge">subiquity</code> si usas Ubuntu Server o cualquier Debian based Distro.</p>

<p>El tema escomo los layers de abstracción; el orden de ejecución de <code class="language-plaintext highlighter-rouge">autoinstall</code>
de Ubuntu Server <code class="language-plaintext highlighter-rouge">subiquity</code>; y finalmente en que momento diferentes instancias de <code class="language-plaintext highlighter-rouge">cloud-init</code> son ejecutadas
es increíblemente confuso. 
<code class="language-plaintext highlighter-rouge">cloud-init</code> se ejecuta en varias instancias dependiendo de el contexto de ejecución:</p>

<ul>
  <li>La instancia de la creación de la imagen.</li>
  <li>La instancia del uso de esta imagen en la creación de la VM.</li>
</ul>

<p>La documentación tampoco ayuda mucho y ni que hablar de las diferencias
entre versiones. 
La parte menos importante de todo esto es el archivo de <a href="https://www.packer.io/">Packer</a> en si. 
Asi que lo pasamos rápido, mostrando las partes más importantes:</p>

<div class="language-hcl highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nx">packer</span> <span class="p">{</span>
  <span class="nx">required_plugins</span> <span class="p">{</span>
    <span class="nx">name</span> <span class="o">=</span> <span class="p">{</span>
      <span class="nx">version</span> <span class="o">=</span> <span class="s2">"~&gt; 1"</span>
      <span class="nx">source</span>  <span class="o">=</span> <span class="s2">"github.com/hashicorp/proxmox"</span>
    <span class="p">}</span>
  <span class="p">}</span>
<span class="p">}</span>

<span class="nx">source</span> <span class="s2">"proxmox-iso"</span> <span class="s2">"proxmox-ubuntu-server-jammy"</span> <span class="p">{</span>
  <span class="nx">proxmox_url</span> <span class="o">=</span> <span class="nx">var</span><span class="p">.</span><span class="nx">proxmox_api_url</span>
  <span class="nx">username</span>    <span class="o">=</span> <span class="nx">var</span><span class="p">.</span><span class="nx">proxmox_api_token_id</span>
  <span class="nx">token</span>       <span class="o">=</span> <span class="nx">var</span><span class="p">.</span><span class="nx">proxmox_api_token_secret</span>
  <span class="nx">node</span>        <span class="o">=</span> <span class="s2">"px-03"</span>
  <span class="nx">vm_id</span>       <span class="o">=</span> <span class="s2">"9666"</span>
  <span class="nx">vm_name</span>     <span class="o">=</span> <span class="s2">"packer-proxmox-ubuntu-server-jammy-1"</span>
  <span class="nx">iso_url</span>     <span class="o">=</span> <span class="s2">"https://old-releases.ubuntu.com/releases/jammy/ubuntu-22.04.4-live-server-amd64.iso"</span>


  <span class="c1"># get the checksum from https://releases.ubuntu.com/22.04/SHA256SUMS</span>
  <span class="nx">iso_checksum</span><span class="o">=</span> <span class="s2">"45f873de9f8cb637345d6e66a583762730bbea30277ef7b32c9c3bd6700a32b2"</span>
  <span class="nx">iso_storage_pool</span> <span class="o">=</span> <span class="nx">var</span><span class="err">.</span><span class="nx">proxmox_storage_pool</span>

  <span class="nx">ssh_username</span> <span class="o">=</span> <span class="s2">"ubuntu"</span>
  <span class="nx">ssh_password</span> <span class="o">=</span> <span class="s2">"ubuntu"</span>
  <span class="nx">ssh_timeout</span>  <span class="o">=</span> <span class="s2">"10m"</span>

  <span class="nx">cloud_init</span> <span class="o">=</span> <span class="kc">false</span>
  <span class="nx">cloud_init_storage_pool</span> <span class="o">=</span> <span class="nx">var</span><span class="err">.</span><span class="nx">proxmox_storage_pool</span>

  <span class="p">[...]</span>

  <span class="nx">template_name</span>        <span class="o">=</span> <span class="s2">"packer-ubuntu-server-jammy"</span>
  <span class="nx">template_description</span> <span class="o">=</span> <span class="s2">"Packer generated Ubuntu 22.04"</span>
  <span class="nx">unmount_iso</span>          <span class="o">=</span> <span class="kc">true</span>

  <span class="nx">additional_iso_files</span> <span class="p">{</span>
    <span class="nx">cd_files</span> <span class="o">=</span> <span class="p">[</span>
      <span class="s2">"./http/meta-data"</span><span class="p">,</span>
      <span class="s2">"./http/user-data"</span><span class="p">,</span>
    <span class="p">]</span>
    <span class="nx">cd_label</span>         <span class="o">=</span> <span class="s2">"cidata"</span>
    <span class="nx">unmount</span>          <span class="o">=</span> <span class="kc">true</span>
    <span class="nx">iso_storage_pool</span> <span class="o">=</span> <span class="nx">var</span><span class="p">.</span><span class="nx">proxmox_storage_pool</span>
  <span class="p">}</span>


  <span class="nx">memory</span>     <span class="o">=</span> <span class="s2">"4096"</span>
  <span class="nx">cores</span>      <span class="o">=</span> <span class="s2">"4"</span>
  <span class="nx">sockets</span>    <span class="o">=</span> <span class="s2">"1"</span>
  <span class="nx">os</span>         <span class="o">=</span> <span class="s2">"l26"</span>
  <span class="nx">machine</span>    <span class="o">=</span> <span class="s2">"q35"</span>
  <span class="nx">qemu_agent</span> <span class="o">=</span> <span class="kc">true</span>

  <span class="nx">scsi_controller</span> <span class="o">=</span> <span class="s2">"virtio-scsi-pci"</span>

  <span class="nx">disks</span> <span class="p">{</span>
    <span class="nx">disk_size</span>         <span class="o">=</span> <span class="s2">"8G"</span>
    <span class="c1"># qcow2 over raw, because NFS in my system</span>
    <span class="c1"># but it's not supported by terraform proxmox (???)</span>
    <span class="c1"># update: months after, it's supported. Todo piola!</span>
    <span class="c1"># https://forum.proxmox.com/threads/raw-vs-qcow2.34566/</span>
    <span class="nx">format</span>            <span class="o">=</span> <span class="s2">"qcow2"</span>
    <span class="nx">storage_pool</span>      <span class="o">=</span> <span class="nx">var</span><span class="p">.</span><span class="nx">proxmox_disks_nas_storage_pool</span> <span class="c1">#Specify your storage pool</span>
    <span class="c1">#storage_pool_type = "zfs"   #Specify pool type</span>
    <span class="nx">type</span>              <span class="o">=</span> <span class="s2">"virtio"</span>
    <span class="c1"># writeback increase of r/w speed</span>
    <span class="c1"># https://forum.proxmox.com/threads/virtio-vs-scsi.52893/</span>
    <span class="nx">cache_mode</span>        <span class="o">=</span> <span class="s2">"writeback"</span>
  <span class="p">}</span>

  <span class="p">[...]</span>
<span class="err">}</span>

<span class="c1"># Build Definition to create the VM Template</span>
<span class="nx">build</span> <span class="p">{</span>

  <span class="nx">name</span>    <span class="o">=</span> <span class="s2">"proxmox-ubuntu-server-jammy"</span>
  <span class="nx">sources</span> <span class="o">=</span> <span class="p">[</span><span class="s2">"source.proxmox-iso.proxmox-ubuntu-server-jammy"</span><span class="p">]</span>

  <span class="c1"># Provisioning the VM Template for Cloud-Init Integration in Proxmox #1</span>
  <span class="nx">provisioner</span> <span class="s2">"shell"</span> <span class="p">{</span>
    <span class="nx">inline</span> <span class="o">=</span> <span class="p">[</span>
      <span class="s2">"while [ ! -f /var/lib/cloud/instance/boot-finished ]; do echo 'Waiting for cloud-init...'; sleep 1; done"</span><span class="p">,</span>
      <span class="s2">"sudo rm /etc/ssh/ssh_host_*"</span><span class="p">,</span>
      <span class="s2">"sudo truncate -s 0 /etc/machine-id"</span><span class="p">,</span>
      <span class="s2">"sudo apt -y autoremove --purge"</span><span class="p">,</span>
      <span class="s2">"sudo apt -y clean"</span><span class="p">,</span>
      <span class="s2">"sudo apt -y autoclean"</span><span class="p">,</span>
      <span class="s2">"sudo cloud-init clean"</span><span class="p">,</span>
      <span class="s2">"sudo rm -f /etc/cloud/cloud.cfg.d/subiquity-disable-cloudinit-networking.cfg"</span><span class="p">,</span>
      <span class="s2">"sudo sync"</span>
    <span class="p">]</span>
  <span class="p">}</span>

  <span class="p">[...]</span>

<span class="p">}</span>



</code></pre></div></div>

<h2 id="filesystem-mount-options">Filesystem Mount Options</h2>

<p>Una de las cosas que queremos hacer para minimizar el acceso da disco,
es que en momento de provisionar la imagen o el template que estamos armando
con packer, se hacen configuraciones de algunos flags de varios componentes
del sistema operativo. Como por ejemplo, en el <code class="language-plaintext highlighter-rouge">fstab</code> generalmente 
tenemos algunas líneas como la siguiente:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>UUID=b22312e6-a495-4a5b-959f-4d73ec97e9bf /               ext4    errors=remount-ro 0       1
</code></pre></div></div>

<p>Como default, esta bien, pero nosotros necesitamos para nuestro escenario
particular, donde queremos minimizar las escrituras a disco, un <code class="language-plaintext highlighter-rouge">fstab</code> que 
se vea más o menos así:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>UUID=b22312e6-a495-4a5b-959f-4d73ec97e9bf /               ext4    discard,noatime,nodiratime,errors=remount-ro 0       1

</code></pre></div></div>

<p>Donde:</p>
<ul>
  <li><code class="language-plaintext highlighter-rouge">discard</code> - le dice al device que descarte <code class="language-plaintext highlighter-rouge">blocks</code>. Si el dispositivo no lo soporta, no tiene efecto</li>
  <li><code class="language-plaintext highlighter-rouge">noatime</code> - no guardes información sobre el tiempo de acceso a archivos</li>
  <li><code class="language-plaintext highlighter-rouge">noadate</code> - no guardes información sobre la fecha de acceso a archivos</li>
  <li><code class="language-plaintext highlighter-rouge">nodiratime</code>- no guardes el tiempo de acceso a directorios</li>
</ul>

<h2 id="reconfigurar-journald">Reconfigurar journald</h2>

<p>El <code class="language-plaintext highlighter-rouge">journald</code> en su configuración por defecto escribe una banda a disco, guardando
todos los logs posibles. 
En años de experiencia, debo decir también que esta es solo mi opinión,
que hay dos instancias donde se revisan los logs. La primera es 
cuando por colectarlos y procesarlos, salta alguna alerta 
para revisar. La segunda, es cuando ya es demasiado tarde. 
Si algún proceso no está funcionando más, esa instancia
de logs de systemd/journald va a dejar de producir logs.
Entonces es innecesario estar guardando logs sobre el estado
de cada una de las aplicaciones a disco si son consumidos
desde otros procesadores de logs. Por eso, desactivamos
y configuramos algunas cosas de <code class="language-plaintext highlighter-rouge">journald</code> para que los 
logs queden en memoria. Para esto, en nuestro <code class="language-plaintext highlighter-rouge">packer</code> 
debemos lograr escribir <code class="language-plaintext highlighter-rouge">/etc/systemd/journald.conf.d/50-local.conf</code> con la siguiente configuración:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>[Journal]
Storage=volatile
RuntimeMaxUse=128M
ForwardToSyslog=no
</code></pre></div></div>

<h2 id="sysctl-swappiness-a-1">sysctl swappiness a 1</h2>

<p>Hay un parámetro del Kernel de Linux que controla el swapping
de la información de una aplicación (como páginas anónimas)
de la memoria física a la memoria virtual al disco.
Se llama <code class="language-plaintext highlighter-rouge">vm.swappiness</code>. Este parámetro va de 0 a 100.</p>

<p>Mientras más alto este parámetro, más posibilidades hay que
procesos inactivos swappeen fuera de la memoria física;
mientras más bajo el valor, se swappea menos, forzando los 
bufferes del filesystem que se vacíen.</p>

<p>Entonces, bajemos la probabilidad al mínimo seteando este
valor a 1 para minimizar la escritura a disco de 
la siguiente manera:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">echo</span> <span class="s2">"vm.swappiness = 1"</span> <span class="o">&gt;&gt;</span> /target/etc/sysctl.conf
</code></pre></div></div>

<h2 id="clavándola-con-autoinstall-user-data-y-packer">Clavándola con autoinstall, user-data y Packer</h2>

<p>Todas estas configuraciones están barbaras, pero las tenemos
que cristalizar en un template de <code class="language-plaintext highlighter-rouge">proxmox</code> para que cuando
empecemos a crear VMs con <code class="language-plaintext highlighter-rouge">terraform</code>, no solo algunos
packetes esten en el <strong>template/imagen</strong> preconfiguradas,
pero también algunos settings en el sistema operativo.</p>

<p>Recordemos, esta configuración es para <strong>mi</strong> cluster. 
Cada datacenter, arquitectura, cableado, discos, servidores tendrá sus propias particularidades. En mi caso, escritura a disco
era un problema. Entonces, ¿Cómo armamos esa imagen para nuestro
<strong>"cluster super ecológico"</strong> ? ¿Cómo lo automatizamos?</p>

<p>En nuestro archivo <code class="language-plaintext highlighter-rouge">pkr</code> donde definimos nuestra imagen,
tenemos una sección donde podemos definir <code class="language-plaintext highlighter-rouge">user-data</code> donde
declaramos los pasos que <code class="language-plaintext highlighter-rouge">autoinstall</code> va a ejecutar en la 
creación del template:</p>

<div class="language-hcl highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nx">source</span> <span class="s2">"proxmox-iso"</span> <span class="s2">"proxmox-ubuntu-server-jammy"</span> <span class="p">{</span>
<span class="p">[...]</span>
  <span class="nx">additional_iso_files</span> <span class="p">{</span>
    <span class="nx">cd_files</span> <span class="o">=</span> <span class="p">[</span>
      <span class="s2">"./http/meta-data"</span><span class="p">,</span>
      <span class="s2">"./http/user-data"</span><span class="p">,</span>
    <span class="p">]</span>
    <span class="nx">cd_label</span>         <span class="o">=</span> <span class="s2">"cidata"</span>
    <span class="nx">unmount</span>          <span class="o">=</span> <span class="kc">true</span>
    <span class="nx">iso_storage_pool</span> <span class="o">=</span> <span class="nx">var</span><span class="p">.</span><span class="nx">proxmox_storage_pool</span>
  <span class="p">}</span>
<span class="p">[...]</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Las secciones relevantes de nuestro archivo <code class="language-plaintext highlighter-rouge">user-data</code>
son las siguientes:</p>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">#cloud-config</span>

<span class="c1"># autoinstall reference</span>
<span class="c1"># https://canonical-subiquity.readthedocs-hosted.com/en/latest/reference/autoinstall-reference.html#user-data</span>
<span class="na">autoinstall</span><span class="pi">:</span>
<span class="na">autoinstall</span><span class="pi">:</span>
  <span class="na">version</span><span class="pi">:</span> <span class="m">1</span>
  <span class="na">locale</span><span class="pi">:</span> <span class="s">en_US</span>
  <span class="na">keyboard</span><span class="pi">:</span>
    <span class="na">layout</span><span class="pi">:</span> <span class="s">es</span>
<span class="pi">[</span><span class="nv">...</span><span class="pi">]</span>
  <span class="na">storage</span><span class="pi">:</span>
    <span class="c1">#layout:</span>
    <span class="c1">#  name: direct</span>
    <span class="na">config</span><span class="pi">:</span>
      <span class="pi">-</span> <span class="na">id</span><span class="pi">:</span> <span class="s">disk0</span>
        <span class="na">type</span><span class="pi">:</span> <span class="s">disk</span>
        <span class="na">wipe</span><span class="pi">:</span> <span class="s">superblock-recursive</span>
        <span class="na">ptable</span><span class="pi">:</span> <span class="s">msdos</span>
        <span class="na">model</span><span class="pi">:</span> <span class="s">QEMU HARDDISK</span>
        <span class="na">path</span><span class="pi">:</span> <span class="s">/dev/vda</span>
        <span class="na">name</span><span class="pi">:</span> <span class="s">system-disk</span>
        <span class="na">preserve</span><span class="pi">:</span> <span class="kc">false</span>
        <span class="na">grub_device</span><span class="pi">:</span> <span class="kc">true</span>
      <span class="pi">-</span> <span class="na">id</span><span class="pi">:</span> <span class="s">disk0-part1</span>
        <span class="na">device</span><span class="pi">:</span> <span class="s">disk0</span>
        <span class="na">type</span><span class="pi">:</span> <span class="s">partition</span>
        <span class="c1">#partition_type: EF02</span>
        <span class="na">name</span><span class="pi">:</span> <span class="s">boot-partition</span>
        <span class="na">number</span><span class="pi">:</span> <span class="m">1</span>
        <span class="c1">#size: 512MB</span>
        <span class="na">size</span><span class="pi">:</span> <span class="s">1GB</span>
        <span class="na">flag</span><span class="pi">:</span> <span class="s">boot</span>
        <span class="na">preserve</span><span class="pi">:</span> <span class="kc">false</span>
      <span class="pi">-</span> <span class="na">id</span><span class="pi">:</span> <span class="s">disk0-part2</span>
        <span class="na">device</span><span class="pi">:</span> <span class="s">disk0</span>
        <span class="na">type</span><span class="pi">:</span> <span class="s">partition</span>
        <span class="na">preserve</span><span class="pi">:</span> <span class="kc">false</span>
        <span class="na">size</span><span class="pi">:</span> <span class="s">4GB</span>
        <span class="na">resize</span><span class="pi">:</span> <span class="kc">true</span>
        <span class="na">name</span><span class="pi">:</span> <span class="s">root-partition</span>
        <span class="na">number</span><span class="pi">:</span> <span class="m">2</span>
        <span class="c1">#wipe: superblock</span>
      <span class="pi">-</span> <span class="na">id</span><span class="pi">:</span> <span class="s">disk0-part1-format-boot</span>
        <span class="na">type</span><span class="pi">:</span> <span class="s">format</span>
        <span class="na">volume</span><span class="pi">:</span> <span class="s">disk0-part1</span>
        <span class="na">fstype</span><span class="pi">:</span> <span class="s">ext4</span>
        <span class="na">label</span><span class="pi">:</span> <span class="s">boot</span>
      <span class="pi">-</span> <span class="na">id</span><span class="pi">:</span> <span class="s">disk0-part2-format-root</span>
        <span class="na">volume</span><span class="pi">:</span> <span class="s">disk0-part2</span>
        <span class="na">type</span><span class="pi">:</span> <span class="s">format</span>
        <span class="na">fstype</span><span class="pi">:</span> <span class="s">ext4</span>
        <span class="na">label</span><span class="pi">:</span> <span class="s">root</span>
      <span class="pi">-</span> <span class="na">id</span><span class="pi">:</span> <span class="s">disk0-part1-mount-boot</span>
        <span class="na">type</span><span class="pi">:</span> <span class="s">mount</span>
        <span class="na">path</span><span class="pi">:</span> <span class="s">/boot</span>
        <span class="na">device</span><span class="pi">:</span> <span class="s">disk0-part1-format-boot</span>
        <span class="na">options</span><span class="pi">:</span> <span class="s1">'</span><span class="s">discard,noatime,nodiratime,errors=remount-ro'</span>        
      <span class="pi">-</span> <span class="na">id</span><span class="pi">:</span> <span class="s">disk0-part1-mount-root</span>
        <span class="na">type</span><span class="pi">:</span> <span class="s">mount</span>
        <span class="na">path</span><span class="pi">:</span> <span class="s">/</span>
        <span class="na">device</span><span class="pi">:</span> <span class="s">disk0-part2-format-root</span>
        <span class="na">options</span><span class="pi">:</span> <span class="s1">'</span><span class="s">discard,noatime,nodiratime,errors=remount-ro'</span>
    <span class="na">swap</span><span class="pi">:</span>
      <span class="na">size</span><span class="pi">:</span> <span class="m">0</span>

  <span class="na">late-commands</span><span class="pi">:</span>
  <span class="pi">-</span> <span class="pi">|</span>
    <span class="s">rm /target/etc/netplan/00-installer-config.yaml</span>
    <span class="s">cat &lt;&lt;EOF &gt; /target/etc/netplan/80-my-net.yaml</span>
    <span class="s">network:</span>
      <span class="s">version: 2</span>
      <span class="s">#renderer: networkd</span>
      <span class="s">ethernets:</span>
        <span class="s">enp18:</span>
          <span class="s">match:</span>
            <span class="s">name: enp*</span>
          <span class="s">set-name: eth0</span>
          <span class="s">dhcp4: true    </span>
    <span class="s">EOF</span>
  <span class="c1"># configure journald to minimize disk writes</span>
  <span class="pi">-</span> <span class="pi">|</span>
    <span class="s">mkdir /target/etc/systemd/journald.conf.d</span>
    <span class="s">cat &lt;&lt;EOF &gt; /target/etc/systemd/journald.conf.d/50-local.conf</span>
    <span class="s">[Journal]</span>
    <span class="s">Storage=volatile</span>
    <span class="s">RuntimeMaxUse=128M</span>
    <span class="s">ForwardToSyslog=no    </span>
    <span class="s">EOF</span>
  <span class="pi">-</span> <span class="s">echo "vm.swappiness = 1" &gt;&gt; /target/etc/sysctl.conf</span>
  


  <span class="na">user-data</span><span class="pi">:</span>
    <span class="na">package_upgrade</span><span class="pi">:</span> <span class="kc">false</span>
    <span class="na">hostname</span><span class="pi">:</span> <span class="s">packerimage</span>
    <span class="na">manage_etc_hosts</span><span class="pi">:</span> <span class="kc">true</span>
    <span class="na">timezone</span><span class="pi">:</span> <span class="s">Europe/Vienna</span>
    <span class="na">users</span><span class="pi">:</span>
      <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">ubuntu</span>
        <span class="na">groups</span><span class="pi">:</span> <span class="pi">[</span><span class="nv">adm</span><span class="pi">,</span> <span class="nv">sudo</span><span class="pi">]</span>
        <span class="na">lock-passwd</span><span class="pi">:</span> <span class="kc">false</span>
        <span class="na">sudo</span><span class="pi">:</span> <span class="s">ALL=(ALL) NOPASSWD:ALL</span>
        <span class="na">shell</span><span class="pi">:</span> <span class="s">/bin/bash</span>
        <span class="na">passwd</span><span class="pi">:</span> <span class="s2">"</span><span class="s">$6$exDY1mhS4KUYCE/2$zmn9ToZwTKLhCw.b4/b.ZRTIZM30JZ4QrOQ2aOXJ8yk96xpcCof0kxKwuX1kqLG/ygbJ1f8wxED22bTL4F46P0"</span> <span class="c1">#ubuntu</span>

</code></pre></div></div>

<p>En el id <code class="language-plaintext highlighter-rouge">disk0-part1-mount-root</code> y el id <code class="language-plaintext highlighter-rouge">disk0-part1-mount-boot</code>
declaramos las opciones de montaje de la unidad como habíamos mencionado en la sección anterior de este artículo:</p>
<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code>        <span class="na">options</span><span class="pi">:</span> <span class="s1">'</span><span class="s">discard,noatime,nodiratime,errors=remount-ro'</span>
<span class="err">    </span><span class="na">swap</span><span class="pi">:</span>
      <span class="na">size</span><span class="pi">:</span> <span class="m">0</span>
</code></pre></div></div>

<p>De yapa, también decimos en la sección <code class="language-plaintext highlighter-rouge">storage:</code> que no vamos
a tener <code class="language-plaintext highlighter-rouge">swap</code>.</p>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code>  <span class="na">late-commands</span><span class="pi">:</span>
  <span class="pi">-</span> <span class="pi">|</span>
    <span class="s">rm /target/etc/netplan/00-installer-config.yaml</span>
    <span class="s">cat &lt;&lt;EOF &gt; /target/etc/netplan/80-my-net.yaml</span>
    <span class="s">network:</span>
      <span class="s">version: 2</span>
      <span class="s">#renderer: networkd</span>
      <span class="s">ethernets:</span>
        <span class="s">enp18:</span>
          <span class="s">match:</span>
            <span class="s">name: enp*</span>
          <span class="s">set-name: eth0</span>
          <span class="s">dhcp4: true    </span>
    <span class="s">EOF</span>
  <span class="c1"># configure journald to minimize disk writes</span>
  <span class="pi">-</span> <span class="pi">|</span>
    <span class="s">mkdir /target/etc/systemd/journald.conf.d</span>
    <span class="s">cat &lt;&lt;EOF &gt; /target/etc/systemd/journald.conf.d/50-local.conf</span>
    <span class="s">[Journal]</span>
    <span class="s">Storage=volatile</span>
    <span class="s">RuntimeMaxUse=128M</span>
    <span class="s">ForwardToSyslog=no    </span>
    <span class="s">EOF</span>
  <span class="pi">-</span> <span class="s">echo "vm.swappiness = 1" &gt;&gt; /target/etc/sysctl.conf</span>
  
</code></pre></div></div>

<p>Luego, además de cambiar el nombre de las interfaces de red
de la que se usa en ubuntu (enp*) a la vieja y querida 
definición <code class="language-plaintext highlighter-rouge">eth0</code>. 
Lo que sigue luego es sencillo, configuramos <code class="language-plaintext highlighter-rouge">journald</code> 
con los settings que discutimos anteriormente y seteamos a 1
el <code class="language-plaintext highlighter-rouge">swappiness</code> en <code class="language-plaintext highlighter-rouge">/etc/sysctl.conf</code>. Es importante saber,
que en esta fase de configuración, el disco donde se está
instalando el sistema operativo esta montado en el mountpoint 
<code class="language-plaintext highlighter-rouge">"/target"</code></p>

<p>Una ves que tenemos todas nuestras definiciones armadas, ejecutamos:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span>packer build <span class="nt">-force</span> <span class="nt">-var-file</span><span class="o">=</span>vars.pkrvars.hcl <span class="nb">.</span> 
</code></pre></div></div>

<p>Luego que termine de ejecutar, un template va a estar disponible
en nuestro nodo <code class="language-plaintext highlighter-rouge">proxmox</code> para poder referenciarlo en <code class="language-plaintext highlighter-rouge">terraform</code>
y usarlo como template base para instalar nuestra propia 
imagen Linux en cada nueva Virtual Machine.</p>

<h1 id="terraform-con-proxmox">Terraform con Proxmox</h1>

<p>Primero lo primero. Definir nuestros <strong>providers</strong>:</p>

<div class="language-hcl highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nx">terraform</span> <span class="p">{</span>

  <span class="nx">required_version</span> <span class="o">=</span> <span class="s2">"&gt;= 1.7.0"</span>

  <span class="nx">required_providers</span> <span class="p">{</span>
    <span class="nx">proxmox</span> <span class="o">=</span> <span class="p">{</span>
      <span class="nx">source</span> <span class="o">=</span> <span class="s2">"bpg/proxmox"</span>
      <span class="nx">version</span> <span class="o">=</span> <span class="s2">"0.66.2"</span>
    <span class="p">}</span>
    <span class="nx">ssh</span> <span class="o">=</span> <span class="p">{</span>
      <span class="nx">source</span> <span class="o">=</span> <span class="s2">"loafoe/ssh"</span>
      <span class="nx">version</span> <span class="o">=</span> <span class="s2">"2.7.0"</span>
    <span class="p">}</span>
    <span class="nx">tls</span> <span class="o">=</span> <span class="p">{</span>
      <span class="nx">source</span>  <span class="o">=</span> <span class="s2">"hashicorp/tls"</span>
      <span class="nx">version</span> <span class="o">=</span> <span class="s2">"&gt;= 4.0"</span>
    <span class="p">}</span>
  <span class="p">}</span>
<span class="p">}</span>

<span class="nx">provider</span> <span class="s2">"proxmox"</span> <span class="p">{</span>
  <span class="nx">endpoint</span> <span class="o">=</span> <span class="nx">var</span><span class="p">.</span><span class="nx">proxmox_api_url</span>

  <span class="nx">username</span> <span class="o">=</span> <span class="s2">"root@pam"</span> <span class="c1"># for SSH</span>
  <span class="nx">password</span> <span class="o">=</span> <span class="nx">var</span><span class="p">.</span><span class="nx">proxmox_ssh_password</span>  <span class="c1"># SSH password</span>

  <span class="nx">insecure</span> <span class="o">=</span> <span class="kc">true</span>

  <span class="nx">ssh</span> <span class="p">{</span>
    <span class="nx">agent</span> <span class="o">=</span> <span class="kc">true</span>
  <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Tener en cuenta que este ambiente no es apto para un ambiente
de producción. En caso de querer autenticarnos con <strong>proxmox</strong>
a través de <strong>terraform</strong>, debemos crear una API Key 
de manera segura con la lista de permisos permitidos y mínimos
para que terraform pueda interactuar con el Proxmox cluster.</p>

<p>Después de configurar API Keys, usuarios, SSH y accesos de <strong>Proxmox</strong> 
para el <strong>provider</strong> de <strong>Terraform</strong> en mi máquina local 
y ya teniendo la imagen  construida con <strong>Packer</strong> podemos 
empezar a levantar con nuestros scripts de <code class="language-plaintext highlighter-rouge">Terraform</code>, un pequeño
cluster <code class="language-plaintext highlighter-rouge">Kubernetes</code>, como ejemplo.</p>

<p>Voy a crear con las siguientes definiciones dos VMs.
Una con un <code class="language-plaintext highlighter-rouge">controlplane</code> y la otra con un <code class="language-plaintext highlighter-rouge">agent</code>
de la siguiente manera:</p>

<div class="language-terraform highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">variable</span> <span class="nx">k3s_control_plane_instances</span> <span class="p">{</span>
  <span class="nx">description</span> <span class="o">=</span> <span class="s2">"Proxmox/k3s number of control planes for this cluster"</span>
  <span class="nx">type</span>        <span class="o">=</span> <span class="nx">number</span>
  <span class="nx">default</span>     <span class="o">=</span> <span class="mi">1</span>
<span class="p">}</span>

<span class="k">variable</span> <span class="nx">k3s_agent_instances</span> <span class="p">{</span>
  <span class="nx">description</span> <span class="o">=</span> <span class="s2">"Proxmox/k3s number of agents for this cluster"</span>
  <span class="nx">type</span>        <span class="o">=</span> <span class="nx">number</span>
  <span class="nx">default</span>     <span class="o">=</span> <span class="mi">1</span>
<span class="p">}</span>

</code></pre></div></div>

<p>Como ejemplo para levantar un <strong>Kubernetes Control-Plane</strong> y un <strong>Kubernetes Agent</strong>.
Todo parece muy complicado, pero escencialmente es armar unas VMs
comunes y corrientes y correrles un <code class="language-plaintext highlighter-rouge">cloudinit</code> dependiendo
si son control planes o agents.</p>

<p>Lo que si, hay un detalle. 
Para poder controlar todo el cluster <strong>Kubernetes</strong> es importante
poder recuperar su archivo <code class="language-plaintext highlighter-rouge">kubeconfig</code>. 
Con todo esto dicho, la configuración del <strong>primer control-plane</strong>:</p>

<div class="language-hcl highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nx">resource</span> <span class="s2">"proxmox_virtual_environment_file"</span> <span class="s2">"k3s_control_plane_cloudinit"</span> <span class="p">{</span>
    <span class="nx">count</span>        <span class="o">=</span> <span class="nx">var</span><span class="p">.</span><span class="nx">k3s_control_plane_instances</span>
    <span class="nx">node_name</span>    <span class="o">=</span> <span class="nx">var</span><span class="p">.</span><span class="nx">proxmox_main_node_name</span>
    <span class="nx">content_type</span> <span class="o">=</span> <span class="s2">"snippets"</span>
    <span class="c1"># it is needed to add a "snippet" storage in proxmox node</span>
    <span class="c1"># at Datacenter&gt;Storage section</span>
    <span class="nx">datastore_id</span> <span class="o">=</span> <span class="s2">"terraform_snippets"</span>
    <span class="p">[...]</span>
    <span class="nx">source_raw</span> <span class="p">{</span>
        <span class="nx">data</span> <span class="o">=</span> <span class="o">&lt;&lt;</span><span class="nx">EOF</span>
        <span class="p">[...]</span>

        <span class="nx">write_files</span><span class="o">:</span> 
            <span class="o">-</span> <span class="nx">path</span><span class="o">:</span> <span class="o">/</span><span class="nx">root</span><span class="o">/</span><span class="nx">control-plane-setup</span><span class="p">.</span><span class="nx">sh</span>
              <span class="nx">permissions</span><span class="o">:</span> <span class="mi">0744</span>
              <span class="nx">content</span><span class="o">:</span> <span class="o">|</span>
                <span class="c1">#!/bin/bash</span>
                <span class="nx">if</span> <span class="p">[[</span> <span class="err">`</span><span class="nx">hostname</span><span class="err">`</span> <span class="o">==</span> <span class="s2">"k3s-control-plane-0"</span> <span class="p">]]</span><span class="err">;</span> <span class="nx">then</span>
                 <span class="nx">echo</span> <span class="s2">"curl -fL https://get.k3s.io | sh -s - server --cluster-init --token </span><span class="se">\"</span><span class="s2">secret</span><span class="se">\"</span><span class="s2">  --write-kubeconfig-mode </span><span class="se">\"</span><span class="s2">0644</span><span class="se">\"</span><span class="s2"> --disable traefik --disable servicelb --bind-address </span><span class="err">\</span><span class="s2">`hostname -i</span><span class="err">\</span><span class="s2">` --tls-san </span><span class="se">\"</span><span class="s2">k3s-control-plane-${count.index}.tsh</span><span class="se">\"</span><span class="s2"> "</span>
                <span class="nx">else</span>
                 <span class="nx">echo</span> <span class="s2">"curl -fL https://get.k3s.io | sh -s - server --token </span><span class="se">\"</span><span class="s2">secret</span><span class="se">\"</span><span class="s2">  --write-kubeconfig-mode </span><span class="se">\"</span><span class="s2">0644</span><span class="se">\"</span><span class="s2"> --disable traefik --disable servicelb --bind-address </span><span class="err">\</span><span class="s2">`hostname -i</span><span class="err">\</span><span class="s2">`  --server https://k3s-control-plane-0.tsh:6443 --tls-san </span><span class="se">\"</span><span class="s2">k3s-control-plane-${count.index}.tsh</span><span class="se">\"</span><span class="s2"> "</span>
                <span class="nx">fi</span>    
        <span class="p">[...]</span>

        <span class="nx">EOF</span>
        <span class="nx">file_name</span> <span class="o">=</span> <span class="s2">"k3s-control-plane-${count.index}-cloud-config.yaml"</span>
    <span class="err">}</span>
<span class="p">}</span>

</code></pre></div></div>

<p>Que lindo tener mi propio <strong>TLD</strong> (top level domain) llamado <code class="language-plaintext highlighter-rouge">.tsh</code> &lt;3
Todo esto viene de un previamente configurado <strong>pfSense</strong>.
Como se construye toda la parte de red amerita otro artículo.</p>

<p><strong>IMPORTANTE:</strong> nótese el <code class="language-plaintext highlighter-rouge">--token \"secret\"</code>. El token del cluster, no está generado. Esta hardcodeado como <code class="language-plaintext highlighter-rouge">secret</code>. 
Guarda con eso, ¿Eh?. Está mal y el token tiene que ser generado, secreto y fuera de cualquier repositorio
y del alcance de los niños.</p>

<p>Cotinuamos con nuestra definición de <strong>Virtual Machine</strong> para el <code class="language-plaintext highlighter-rouge">control-plane</code>:</p>

<div class="language-terraform highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">resource</span> <span class="s2">"proxmox_virtual_environment_vm"</span> <span class="s2">"k3s_control_plane"</span> <span class="p">{</span>
    <span class="nx">count</span> <span class="o">=</span> <span class="kd">var</span><span class="p">.</span><span class="nx">k3s_control_plane_instances</span>
    <span class="nx">node_name</span> <span class="o">=</span> <span class="kd">var</span><span class="p">.</span><span class="nx">proxmox_main_node_name</span>
    <span class="nx">vm_id</span>     <span class="o">=</span> <span class="s2">"505</span><span class="p">${</span><span class="nx">count</span><span class="p">.</span><span class="nx">index</span><span class="p">}</span><span class="s2">"</span> 

    <span class="nx">name</span> <span class="o">=</span> <span class="s2">"</span><span class="p">${</span><span class="kd">var</span><span class="p">.</span><span class="nx">k3s_hostname_prefix</span><span class="p">}</span><span class="s2">-control-plane-</span><span class="p">${</span><span class="nx">count</span><span class="p">.</span><span class="nx">index</span><span class="p">}</span><span class="s2">"</span>
    <span class="nx">description</span> <span class="o">=</span> <span class="s2">"k3s cluster managed by Terraform"</span>
    <span class="nx">tags</span> <span class="o">=</span> <span class="p">[</span><span class="s2">"terraform"</span><span class="p">,</span> <span class="s2">"k3s"</span><span class="p">,</span> <span class="s2">"control-plane"</span><span class="p">]</span>

    <span class="nx">agent</span> <span class="p">{</span>
        <span class="nx">enabled</span> <span class="o">=</span> <span class="kc">true</span>
    <span class="p">}</span>

    <span class="nx">machine</span> <span class="o">=</span> <span class="s2">"q35"</span>
    <span class="nx">cpu</span> <span class="p">{</span>
        <span class="nx">architecture</span>    <span class="o">=</span> <span class="s2">"x86_64"</span>
        <span class="nx">cores</span>           <span class="o">=</span> <span class="mi">2</span>
        <span class="nx">sockets</span>         <span class="o">=</span> <span class="mi">1</span>
        <span class="nx">type</span>            <span class="o">=</span> <span class="s2">"x86-64-v2-AES"</span>
    <span class="p">}</span>

    <span class="nx">memory</span> <span class="p">{</span>
        <span class="nx">dedicated</span> <span class="o">=</span> <span class="mi">2048</span>
        <span class="nx">floating</span>  <span class="o">=</span> <span class="mi">2048</span> <span class="c1">// set equal to dedicated to enable ballooning</span>
    <span class="p">}</span>

    <span class="nx">initialization</span> <span class="p">{</span>
        <span class="nx">ip_config</span> <span class="p">{</span>
            <span class="nx">ipv4</span> <span class="p">{</span>
                <span class="nx">address</span> <span class="o">=</span> <span class="s2">"dhcp"</span>
            <span class="p">}</span>
        <span class="p">}</span>

        <span class="nx">user_data_file_id</span> <span class="o">=</span> <span class="nx">element</span><span class="p">(</span><span class="nx">proxmox_virtual_environment_file</span><span class="p">.</span><span class="nx">k3s_control_plane_cloudinit</span><span class="p">.*.</span><span class="nx">id</span><span class="p">,</span> <span class="nx">count</span><span class="p">.</span><span class="nx">index</span><span class="p">)</span>
    <span class="p">}</span>

    <span class="c1">// fake mac-addr for pfSense identification</span>
    <span class="nx">network_device</span> <span class="p">{</span>
      <span class="nx">bridge</span> <span class="o">=</span> <span class="s2">"vmbr0"</span>
      <span class="nx">mac_address</span> <span class="o">=</span> <span class="s2">"ee:ee:ee:00:00:1</span><span class="p">${</span><span class="nx">count</span><span class="p">.</span><span class="nx">index</span><span class="p">}</span><span class="s2">"</span>
    <span class="p">}</span>

    <span class="nx">operating_system</span> <span class="p">{</span>
      <span class="nx">type</span> <span class="o">=</span> <span class="s2">"l26"</span>
    <span class="p">}</span>

    <span class="nx">vga</span> <span class="p">{</span>
        <span class="nx">memory</span>  <span class="o">=</span> <span class="mi">16</span>
        <span class="nx">type</span>    <span class="o">=</span> <span class="s2">"std"</span>
    <span class="p">}</span>

    <span class="c1">//serial_device {}</span>

    <span class="nx">clone</span> <span class="p">{</span>
        <span class="nx">vm_id</span> <span class="o">=</span> <span class="mi">9666</span>
        <span class="nx">node_name</span> <span class="o">=</span> <span class="kd">var</span><span class="p">.</span><span class="nx">proxmox_main_node_name</span>
        <span class="nx">datastore_id</span> <span class="o">=</span> <span class="kd">var</span><span class="p">.</span><span class="nx">proxmox_disks_nas_storage_pool</span>
    <span class="p">}</span>

<span class="p">}</span>


<span class="k">output</span> <span class="s2">"control_plane_hostnames"</span> <span class="p">{</span>
    <span class="nx">description</span> <span class="o">=</span> <span class="s2">"Control Planes Hostnames"</span>
    <span class="nx">value</span> <span class="o">=</span> <span class="nx">proxmox_virtual_environment_vm</span><span class="p">.</span><span class="nx">k3s_control_plane</span><span class="p">.*.</span><span class="nx">name</span>
<span class="p">}</span>

<span class="k">resource</span> <span class="s2">"terraform_data"</span> <span class="s2">"clean_ssh_known_hosts"</span> <span class="p">{</span>
    <span class="nx">count</span> <span class="o">=</span> <span class="nx">length</span><span class="p">(</span><span class="nx">proxmox_virtual_environment_vm</span><span class="p">.</span><span class="nx">k3s_control_plane</span><span class="p">)</span> <span class="o">!</span><span class="p">=</span> <span class="mi">0</span> <span class="o">?</span> <span class="mi">1</span> <span class="o">:</span> <span class="mi">0</span>

    <span class="nx">depends_on</span> <span class="o">=</span> <span class="p">[</span> <span class="nx">proxmox_virtual_environment_vm</span><span class="p">.</span><span class="nx">k3s_control_plane</span> <span class="p">]</span>
    <span class="c1"># clean up the first control plane local ssh known_hosts</span>
    <span class="k">provisioner</span> <span class="s2">"local-exec"</span> <span class="p">{</span>
        <span class="nx">command</span> <span class="o">=</span> <span class="s2">"ssh-keygen -f </span><span class="se">\"</span><span class="s2">/home/</span><span class="err">$</span><span class="s2">USER/.ssh/known_hosts</span><span class="se">\"</span><span class="s2"> -R </span><span class="se">\"</span><span class="p">${</span><span class="nx">proxmox_virtual_environment_vm</span><span class="p">.</span><span class="nx">k3s_control_plane</span><span class="p">.</span><span class="mi">0</span><span class="p">.</span><span class="nx">name</span><span class="p">}</span><span class="s2">.tsh</span><span class="se">\"</span><span class="s2">"</span>
    <span class="p">}</span>
<span class="p">}</span>


<span class="k">resource</span> <span class="s2">"ssh_sensitive_resource"</span> <span class="s2">"grab_kubeconfig"</span> <span class="p">{</span>
  <span class="nx">count</span> <span class="o">=</span> <span class="nx">length</span><span class="p">(</span><span class="nx">proxmox_virtual_environment_vm</span><span class="p">.</span><span class="nx">k3s_control_plane</span><span class="p">)</span> <span class="o">!</span><span class="p">=</span> <span class="mi">0</span> <span class="o">?</span> <span class="mi">1</span> <span class="o">:</span> <span class="mi">0</span>

  <span class="c1"># The default behaviour is to run file blocks and commands at create time</span>
  <span class="c1"># You can also specify 'destroy' to run the commands at destroy time</span>
  <span class="nx">when</span> <span class="o">=</span> <span class="s2">"create"</span>

  <span class="nx">host</span>     <span class="o">=</span> <span class="s2">"</span><span class="p">${</span><span class="nx">proxmox_virtual_environment_vm</span><span class="p">.</span><span class="nx">k3s_control_plane</span><span class="p">.</span><span class="mi">0</span><span class="p">.</span><span class="nx">name</span><span class="p">}</span><span class="s2">.tsh"</span> 
  <span class="nx">user</span>         <span class="o">=</span> <span class="s2">"root"</span>
  <span class="nx">private_key</span> <span class="o">=</span> <span class="nx">tls_private_key</span><span class="p">.</span><span class="nx">ssh</span><span class="p">.</span><span class="nx">private_key_pem</span>

  <span class="nx">agent</span>        <span class="o">=</span> <span class="kc">false</span>
  <span class="c1"># An ssh-agent with your SSH private keys should be running</span>
  <span class="c1"># Use 'private_key' to set the SSH key otherwise</span>

  <span class="c1"># Try to complete in at most 15 minutes and wait 5 seconds between retries</span>
  <span class="nx">timeout</span>     <span class="o">=</span> <span class="s2">"5m"</span>
  <span class="nx">retry_delay</span> <span class="o">=</span> <span class="s2">"5s"</span>

  <span class="nx">commands</span> <span class="o">=</span> <span class="p">[</span>
     <span class="s2">"cat /etc/rancher/k3s/k3s.yaml"</span>
  <span class="p">]</span>

  <span class="nx">triggers</span> <span class="o">=</span> <span class="p">{</span>
    <span class="nx">always_run</span> <span class="o">=</span> <span class="s2">"</span><span class="p">${</span><span class="nx">timestamp</span><span class="p">()}</span><span class="s2">"</span>
  <span class="p">}</span>

  <span class="nx">depends_on</span> <span class="o">=</span> <span class="p">[</span> <span class="nx">terraform_data</span><span class="p">.</span><span class="nx">clean_ssh_known_hosts</span> <span class="p">]</span>

<span class="p">}</span>


<span class="k">output</span> <span class="s2">"kubeconfig"</span> <span class="p">{</span>
    <span class="c1"># grab this output from terraform with the following command</span>
    <span class="c1"># terraform output -raw kubeconfig &gt; ~/.kube/config</span>
    <span class="nx">value</span> <span class="o">=</span> <span class="nx">length</span><span class="p">(</span><span class="nx">ssh_sensitive_resource</span><span class="p">.</span><span class="nx">grab_kubeconfig</span><span class="p">)</span> <span class="o">!</span><span class="p">=</span> <span class="mi">0</span> <span class="o">?</span> <span class="nx">ssh_sensitive_resource</span><span class="p">.</span><span class="nx">grab_kubeconfig</span><span class="p">.</span><span class="mi">0</span><span class="p">.</span><span class="nx">result</span> <span class="o">:</span> <span class="mi">0</span>
    <span class="nx">sensitive</span> <span class="o">=</span> <span class="kc">true</span>
<span class="p">}</span>

<span class="c1"># finally write the content of kubeconfig to ~/.kube/config automatically</span>
<span class="k">resource</span> <span class="s2">"local_sensitive_file"</span> <span class="s2">"local_user_kubeconfig"</span> <span class="p">{</span>
    <span class="nx">count</span> <span class="o">=</span> <span class="nx">length</span><span class="p">(</span><span class="nx">proxmox_virtual_environment_vm</span><span class="p">.</span><span class="nx">k3s_control_plane</span><span class="p">)</span> <span class="o">!</span><span class="p">=</span> <span class="mi">0</span> <span class="o">?</span> <span class="mi">1</span> <span class="o">:</span> <span class="mi">0</span>

    <span class="nx">content</span>  <span class="o">=</span> <span class="nx">ssh_sensitive_resource</span><span class="p">.</span><span class="nx">grab_kubeconfig</span><span class="p">.</span><span class="mi">0</span><span class="p">.</span><span class="nx">result</span>
    <span class="nx">filename</span> <span class="o">=</span> <span class="nx">pathexpand</span><span class="p">(</span><span class="s2">"~/.kube/config"</span><span class="p">)</span>

    <span class="nx">depends_on</span> <span class="o">=</span> <span class="p">[</span> <span class="nx">ssh_sensitive_resource</span><span class="p">.</span><span class="nx">grab_kubeconfig</span> <span class="p">]</span>
<span class="p">}</span>

</code></pre></div></div>

<p>Creemos el control plane con terraform:</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>
<span class="o">[</span>  2:10PM <span class="o">]</span>  <span class="o">[</span> toshi@turmalina:~/dev/alberinfra/terraform<span class="o">(</span>master✗<span class="o">)</span> <span class="o">]</span>
 <span class="nv">$ </span>terraform apply  <span class="nt">-var-file</span><span class="o">=</span><span class="s2">"vars.tfvars"</span>
tls_private_key.ssh: Refreshing state... <span class="o">[</span><span class="nb">id</span><span class="o">=</span>fc8e10ca14b36c8be3389e57006877903259b17f]
proxmox_virtual_environment_file.cloud_config: Refreshing state... <span class="o">[</span><span class="nb">id</span><span class="o">=</span>terraform_snippets:snippets/cloud-config.yaml]
proxmox_virtual_environment_file.k3s_etcd_cloudinit: Refreshing state... <span class="o">[</span><span class="nb">id</span><span class="o">=</span>terraform_snippets:snippets/k3s-etcd-cloud-config.yaml]

Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols:
  + create

Terraform will perform the following actions:

  <span class="c"># local_sensitive_file.local_user_kubeconfig[0] will be created</span>
  + resource <span class="s2">"local_sensitive_file"</span> <span class="s2">"local_user_kubeconfig"</span> <span class="o">{</span>
      + content              <span class="o">=</span> <span class="o">(</span>sensitive value<span class="o">)</span>
      + content_base64sha256 <span class="o">=</span> <span class="o">(</span>known after apply<span class="o">)</span>
      + content_base64sha512 <span class="o">=</span> <span class="o">(</span>known after apply<span class="o">)</span>
      + content_md5          <span class="o">=</span> <span class="o">(</span>known after apply<span class="o">)</span>
      + content_sha1         <span class="o">=</span> <span class="o">(</span>known after apply<span class="o">)</span>
      + content_sha256       <span class="o">=</span> <span class="o">(</span>known after apply<span class="o">)</span>
      + content_sha512       <span class="o">=</span> <span class="o">(</span>known after apply<span class="o">)</span>
      + directory_permission <span class="o">=</span> <span class="s2">"0700"</span>
      + file_permission      <span class="o">=</span> <span class="s2">"0700"</span>
      + filename             <span class="o">=</span> <span class="s2">"/home/toshi/.kube/config"</span>
      + <span class="nb">id</span>                   <span class="o">=</span> <span class="o">(</span>known after apply<span class="o">)</span>
    <span class="o">}</span>

<span class="o">[</span>...]

  <span class="c"># proxmox_virtual_environment_vm.k3s_control_plane[0] will be created</span>
  + resource <span class="s2">"proxmox_virtual_environment_vm"</span> <span class="s2">"k3s_control_plane"</span> <span class="o">{</span>
      + acpi                    <span class="o">=</span> <span class="nb">true</span>
      + bios                    <span class="o">=</span> <span class="s2">"seabios"</span>
      + description             <span class="o">=</span> <span class="s2">"k3s cluster managed by Terraform"</span>
      + <span class="nb">id</span>                      <span class="o">=</span> <span class="o">(</span>known after apply<span class="o">)</span>
      + ipv4_addresses          <span class="o">=</span> <span class="o">(</span>known after apply<span class="o">)</span>
      + ipv6_addresses          <span class="o">=</span> <span class="o">(</span>known after apply<span class="o">)</span>
      + keyboard_layout         <span class="o">=</span> <span class="s2">"en-us"</span>
      + mac_addresses           <span class="o">=</span> <span class="o">(</span>known after apply<span class="o">)</span>
      + machine                 <span class="o">=</span> <span class="s2">"q35"</span>
      + migrate                 <span class="o">=</span> <span class="nb">false</span>
      + name                    <span class="o">=</span> <span class="s2">"k3s-control-plane-0"</span>
      + network_interface_names <span class="o">=</span> <span class="o">(</span>known after apply<span class="o">)</span>
      + node_name               <span class="o">=</span> <span class="s2">"px-03"</span>
      + on_boot                 <span class="o">=</span> <span class="nb">true</span>
      + protection              <span class="o">=</span> <span class="nb">false</span>
      + reboot                  <span class="o">=</span> <span class="nb">false</span>
      + scsi_hardware           <span class="o">=</span> <span class="s2">"virtio-scsi-pci"</span>
      + started                 <span class="o">=</span> <span class="nb">true</span>
      + stop_on_destroy         <span class="o">=</span> <span class="nb">false</span>
      + tablet_device           <span class="o">=</span> <span class="nb">true</span>
      + tags                    <span class="o">=</span> <span class="o">[</span>
          + <span class="s2">"terraform"</span>,
          + <span class="s2">"k3s"</span>,
          + <span class="s2">"control-plane"</span>,
        <span class="o">]</span>
      + template                <span class="o">=</span> <span class="nb">false</span>
      + timeout_clone           <span class="o">=</span> 1800
      + timeout_create          <span class="o">=</span> 1800
      + timeout_migrate         <span class="o">=</span> 1800
      + timeout_move_disk       <span class="o">=</span> 1800
      + timeout_reboot          <span class="o">=</span> 1800
      + timeout_shutdown_vm     <span class="o">=</span> 1800
      + timeout_start_vm        <span class="o">=</span> 1800
      + timeout_stop_vm         <span class="o">=</span> 300
      + vm_id                   <span class="o">=</span> 5050

      + agent <span class="o">{</span>
          + enabled <span class="o">=</span> <span class="nb">true</span>
          + <span class="nb">timeout</span> <span class="o">=</span> <span class="s2">"15m"</span>
          + trim    <span class="o">=</span> <span class="nb">false</span>
          + <span class="nb">type</span>    <span class="o">=</span> <span class="s2">"virtio"</span>
        <span class="o">}</span>

      + clone <span class="o">{</span>
          + datastore_id <span class="o">=</span> <span class="s2">"cluster_drives"</span>
          + full         <span class="o">=</span> <span class="nb">true</span>
          + node_name    <span class="o">=</span> <span class="s2">"px-03"</span>
          + retries      <span class="o">=</span> 1
          + vm_id        <span class="o">=</span> 9666
        <span class="o">}</span>

      + cpu <span class="o">{</span>
          + architecture <span class="o">=</span> <span class="s2">"x86_64"</span>
          + cores        <span class="o">=</span> 2
          + hotplugged   <span class="o">=</span> 0
          + limit        <span class="o">=</span> 0
          + numa         <span class="o">=</span> <span class="nb">false</span>
          + sockets      <span class="o">=</span> 1
          + <span class="nb">type</span>         <span class="o">=</span> <span class="s2">"x86-64-v2-AES"</span>
          + units        <span class="o">=</span> 1024
        <span class="o">}</span>

      + initialization <span class="o">{</span>
          + datastore_id      <span class="o">=</span> <span class="s2">"local-lvm"</span>
          + upgrade           <span class="o">=</span> <span class="o">(</span>known after apply<span class="o">)</span>
          + user_data_file_id <span class="o">=</span> <span class="o">(</span>known after apply<span class="o">)</span>

          + ip_config <span class="o">{</span>
              + ipv4 <span class="o">{</span>
                  + address <span class="o">=</span> <span class="s2">"dhcp"</span>
                <span class="o">}</span>
            <span class="o">}</span>
        <span class="o">}</span>

      + memory <span class="o">{</span>
          + dedicated      <span class="o">=</span> 2048
          + floating       <span class="o">=</span> 2048
          + keep_hugepages <span class="o">=</span> <span class="nb">false</span>
          + shared         <span class="o">=</span> 0
        <span class="o">}</span>

      + network_device <span class="o">{</span>
          + bridge      <span class="o">=</span> <span class="s2">"vmbr0"</span>
          + enabled     <span class="o">=</span> <span class="nb">true</span>
          + firewall    <span class="o">=</span> <span class="nb">false</span>
          + mac_address <span class="o">=</span> <span class="s2">"ee:ee:ee:00:00:10"</span>
          + model       <span class="o">=</span> <span class="s2">"virtio"</span>
          + mtu         <span class="o">=</span> 0
          + queues      <span class="o">=</span> 0
          + rate_limit  <span class="o">=</span> 0
          + vlan_id     <span class="o">=</span> 0
        <span class="o">}</span>

      + operating_system <span class="o">{</span>
          + <span class="nb">type</span> <span class="o">=</span> <span class="s2">"l26"</span>
        <span class="o">}</span>

      + vga <span class="o">{</span>
          + memory <span class="o">=</span> 16
          + <span class="nb">type</span>   <span class="o">=</span> <span class="s2">"std"</span>
        <span class="o">}</span>
    <span class="o">}</span>

  <span class="c"># ssh_sensitive_resource.grab_kubeconfig[0] will be created</span>
  + resource <span class="s2">"ssh_sensitive_resource"</span> <span class="s2">"grab_kubeconfig"</span> <span class="o">{</span>
      + agent                              <span class="o">=</span> <span class="nb">false</span>
      + bastion_port                       <span class="o">=</span> <span class="s2">"22"</span>
      + commands                           <span class="o">=</span> <span class="o">[</span>
          + <span class="s2">"cat /etc/rancher/k3s/k3s.yaml"</span>,
        <span class="o">]</span>
      + commands_after_file_changes        <span class="o">=</span> <span class="nb">true</span>
      + host                               <span class="o">=</span> <span class="s2">"k3s-control-plane-0.tsh"</span>
      + <span class="nb">id</span>                                 <span class="o">=</span> <span class="o">(</span>known after apply<span class="o">)</span>
      + ignore_no_supported_methods_remain <span class="o">=</span> <span class="nb">false</span>
      + port                               <span class="o">=</span> <span class="s2">"22"</span>
      + private_key                        <span class="o">=</span> <span class="o">(</span>sensitive value<span class="o">)</span>
      + result                             <span class="o">=</span> <span class="o">(</span>sensitive value<span class="o">)</span>
      + retry_delay                        <span class="o">=</span> <span class="s2">"5s"</span>
      + <span class="nb">timeout</span>                            <span class="o">=</span> <span class="s2">"5m"</span>
      + triggers                           <span class="o">=</span> <span class="o">(</span>known after apply<span class="o">)</span>
      + user                               <span class="o">=</span> <span class="s2">"root"</span>
      + when                               <span class="o">=</span> <span class="s2">"create"</span>
    <span class="o">}</span>

  <span class="c"># terraform_data.clean_ssh_known_hosts[0] will be created</span>
  + resource <span class="s2">"terraform_data"</span> <span class="s2">"clean_ssh_known_hosts"</span> <span class="o">{</span>
      + <span class="nb">id</span> <span class="o">=</span> <span class="o">(</span>known after apply<span class="o">)</span>
    <span class="o">}</span>

Plan: 5 to add, 0 to change, 0 to destroy.

Changes to Outputs:
  ~ control_plane_hostnames <span class="o">=</span> <span class="o">[</span>
      + <span class="s2">"k3s-control-plane-0"</span>,
    <span class="o">]</span>
  ~ kubeconfig              <span class="o">=</span> <span class="o">(</span>sensitive value<span class="o">)</span>

Do you want to perform these actions?
  Terraform will perform the actions described above.
  Only <span class="s1">'yes'</span> will be accepted to approve.

  Enter a value:  yES!


proxmox_virtual_environment_file.k3s_control_plane_cloudinit[0]: Creating...
proxmox_virtual_environment_file.k3s_control_plane_cloudinit[0]: Creation <span class="nb">complete </span>after 0s <span class="o">[</span><span class="nb">id</span><span class="o">=</span>terraform_snippets:snippets/k3s-control-plane-0-cloud-config.yaml]
proxmox_virtual_environment_vm.k3s_control_plane[0]: Creating...
proxmox_virtual_environment_vm.k3s_control_plane[0]: Still creating... <span class="o">[</span>10s elapsed]
proxmox_virtual_environment_vm.k3s_control_plane[0]: Still creating... <span class="o">[</span>20s elapsed]
proxmox_virtual_environment_vm.k3s_control_plane[0]: Still creating... <span class="o">[</span>30s elapsed]
proxmox_virtual_environment_vm.k3s_control_plane[0]: Still creating... <span class="o">[</span>40s elapsed]
proxmox_virtual_environment_vm.k3s_control_plane[0]: Still creating... <span class="o">[</span>50s elapsed]
proxmox_virtual_environment_vm.k3s_control_plane[0]: Still creating... <span class="o">[</span>1m0s elapsed]
proxmox_virtual_environment_vm.k3s_control_plane[0]: Still creating... <span class="o">[</span>1m10s elapsed]
proxmox_virtual_environment_vm.k3s_control_plane[0]: Still creating... <span class="o">[</span>1m20s elapsed]
proxmox_virtual_environment_vm.k3s_control_plane[0]: Still creating... <span class="o">[</span>1m30s elapsed]
proxmox_virtual_environment_vm.k3s_control_plane[0]: Still creating... <span class="o">[</span>1m40s elapsed]
proxmox_virtual_environment_vm.k3s_control_plane[0]: Still creating... <span class="o">[</span>1m50s elapsed]
proxmox_virtual_environment_vm.k3s_control_plane[0]: Still creating... <span class="o">[</span>2m0s elapsed]
proxmox_virtual_environment_vm.k3s_control_plane[0]: Creation <span class="nb">complete </span>after 2m5s <span class="o">[</span><span class="nb">id</span><span class="o">=</span>5050]
terraform_data.clean_ssh_known_hosts[0]: Creating...
terraform_data.clean_ssh_known_hosts[0]: Provisioning with <span class="s1">'local-exec'</span>...
terraform_data.clean_ssh_known_hosts[0] <span class="o">(</span>local-exec<span class="o">)</span>: Executing: <span class="o">[</span><span class="s2">"/bin/sh"</span> <span class="s2">"-c"</span> <span class="s2">"ssh-keygen -f </span><span class="se">\"</span><span class="s2">/home/</span><span class="nv">$USER</span><span class="s2">/.ssh/known_hosts</span><span class="se">\"</span><span class="s2"> -R </span><span class="se">\"</span><span class="s2">k3s-control-plane-0.tsh</span><span class="se">\"</span><span class="s2">"</span><span class="o">]</span>
terraform_data.clean_ssh_known_hosts[0] <span class="o">(</span>local-exec<span class="o">)</span>: <span class="c"># Host k3s-control-plane-0.tsh found: line 47</span>
terraform_data.clean_ssh_known_hosts[0] <span class="o">(</span>local-exec<span class="o">)</span>: /home/toshi/.ssh/known_hosts updated.
terraform_data.clean_ssh_known_hosts[0] <span class="o">(</span>local-exec<span class="o">)</span>: Original contents retained as /home/toshi/.ssh/known_hosts.old
terraform_data.clean_ssh_known_hosts[0]: Creation <span class="nb">complete </span>after 0s <span class="o">[</span><span class="nb">id</span><span class="o">=</span>31f50cec-46e2-b9c9-a343-b93ffce76b42]
ssh_sensitive_resource.grab_kubeconfig[0]: Creating...
ssh_sensitive_resource.grab_kubeconfig[0]: Still creating... <span class="o">[</span>10s elapsed]
ssh_sensitive_resource.grab_kubeconfig[0]: Still creating... <span class="o">[</span>20s elapsed]
ssh_sensitive_resource.grab_kubeconfig[0]: Still creating... <span class="o">[</span>30s elapsed]
ssh_sensitive_resource.grab_kubeconfig[0]: Creation <span class="nb">complete </span>after 32s <span class="o">[</span><span class="nb">id</span><span class="o">=</span>7996163503264882213]
local_sensitive_file.local_user_kubeconfig[0]: Creating...
local_sensitive_file.local_user_kubeconfig[0]: Creation <span class="nb">complete </span>after 0s <span class="o">[</span><span class="nb">id</span><span class="o">=</span>4f1a874c917b0ba076bf53abab934f6105f5e266]

Apply <span class="nb">complete</span><span class="o">!</span> Resources: 5 added, 0 changed, 0 destroyed.

Outputs:

control_plane_hostnames <span class="o">=</span> <span class="o">[</span>
  <span class="s2">"k3s-control-plane-0"</span>,
<span class="o">]</span>
kubeconfig <span class="o">=</span> &lt;sensitive&gt;
private_key <span class="o">=</span> &lt;sensitive&gt;    

</code></pre></div></div>

<p><code class="language-plaintext highlighter-rouge">Tuqui.</code> :)</p>

<p>Mientras ejecutamos <code class="language-plaintext highlighter-rouge">terraform</code>, esta preciosura empieza a ronronear:</p>

<p><img src="/assets/images/datacenter/datacenter1.jpg" alt="Datacenter_1" class="img-responsive" /><em>Vista frontal, con línea roja para urgencias conectada a la CCC EPVPN</em></p>

<p><img src="/assets/images/datacenter/datacenter2.jpg" alt="Datacenter_2" class="img-responsive" /><em>Vista d’cotè</em></p>

<p><img src="/assets/images/datacenter/datacenter3.jpg" alt="Datacenter_3" class="img-responsive" /><em>Switch principal</em></p>

<p><img src="/assets/images/datacenter/datacenter4.jpg" alt="Datacenter_4" class="img-responsive" /><em>Vista dorsal, la máquina inferior corre pfSense y distribuye la internet a todas las partes de la casa</em></p>

<p>Muy bueno el <code class="language-plaintext highlighter-rouge">rack</code> de madera ¿no? 
Heh, bueno. Seguimos.</p>

<p>Hacemos lo mismo, agregando un agente para que el control-plane no se coma toda la 
carga de lo que vamos a hacer más tarde:</p>

<div class="language-hcl highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nx">resource</span> <span class="s2">"proxmox_virtual_environment_vm"</span> <span class="s2">"k3s_agent"</span> <span class="p">{</span>
    <span class="nx">count</span> <span class="o">=</span> <span class="nx">var</span><span class="p">.</span><span class="nx">k3s_agent_instances</span>
    <span class="nx">node_name</span> <span class="o">=</span> <span class="nx">var</span><span class="p">.</span><span class="nx">proxmox_main_node_name</span>
    <span class="nx">vm_id</span>     <span class="o">=</span> <span class="s2">"507${count.index}"</span> 

    <span class="nx">name</span> <span class="o">=</span> <span class="s2">"${var.k3s_hostname_prefix}-agent-${count.index}"</span>
    <span class="nx">description</span> <span class="o">=</span> <span class="s2">"k3s cluster managed by Terraform"</span>
    <span class="nx">tags</span> <span class="o">=</span> <span class="p">[</span><span class="s2">"terraform"</span><span class="p">,</span> <span class="s2">"k3s"</span><span class="p">,</span> <span class="s2">"agent"</span><span class="p">]</span>

    <span class="nx">started</span> <span class="o">=</span> <span class="kc">true</span>

    <span class="nx">agent</span> <span class="p">{</span>
        <span class="nx">enabled</span> <span class="o">=</span> <span class="kc">true</span>
    <span class="p">}</span>

    <span class="nx">machine</span> <span class="o">=</span> <span class="s2">"q35"</span>
    <span class="nx">cpu</span> <span class="p">{</span>
        <span class="nx">architecture</span>    <span class="o">=</span> <span class="s2">"x86_64"</span>
        <span class="nx">cores</span>           <span class="o">=</span> <span class="mi">2</span>
        <span class="nx">sockets</span>         <span class="o">=</span> <span class="mi">1</span>
        <span class="nx">type</span>            <span class="o">=</span> <span class="s2">"x86-64-v2-AES"</span>
    <span class="p">}</span>

    <span class="nx">memory</span> <span class="p">{</span>
        <span class="nx">dedicated</span> <span class="o">=</span> <span class="mi">1024</span> <span class="o">*</span> <span class="mi">4</span>
        <span class="nx">floating</span>  <span class="o">=</span> <span class="mi">1024</span> <span class="o">*</span> <span class="mi">4</span> <span class="c1">// set equal to dedicated to enable ballooning</span>
    <span class="p">}</span>

    <span class="c1">// overlay the disk that comes with `packer`</span>
    <span class="c1">// this settings are the same as configured</span>
    <span class="c1">// in our ubuntu packer template for proxmox</span>
    <span class="nx">disk</span> <span class="p">{</span>
        <span class="nx">interface</span>    <span class="o">=</span> <span class="s2">"virtio0"</span> <span class="c1"># overlay the first disk</span>
        <span class="nx">datastore_id</span> <span class="o">=</span> <span class="nx">var</span><span class="p">.</span><span class="nx">proxmox_disks_nas_storage_pool</span>
        <span class="nx">size</span>         <span class="o">=</span> <span class="mi">8</span>  <span class="c1"># in gigabytes</span>

        <span class="nx">cache</span>        <span class="o">=</span> <span class="s2">"writeback"</span>
        <span class="nx">file_format</span>  <span class="o">=</span> <span class="s2">"qcow2"</span>
        <span class="nx">replicate</span>    <span class="o">=</span> <span class="kc">false</span>
    <span class="p">}</span>


    <span class="c1">// second disk -- this gives the agents more disk space </span>
    <span class="c1">// than the other VMs</span>
    <span class="nx">disk</span> <span class="p">{</span>
        <span class="nx">interface</span>    <span class="o">=</span> <span class="s2">"virtio1"</span> <span class="c1"># attach it as a second disk</span>
        <span class="nx">datastore_id</span> <span class="o">=</span> <span class="s2">"cluster_drives"</span>
        <span class="nx">size</span>         <span class="o">=</span> <span class="mi">200</span>  <span class="c1"># in gigabytes</span>

        <span class="nx">cache</span>        <span class="o">=</span> <span class="s2">"writeback"</span>
        <span class="nx">file_format</span>  <span class="o">=</span> <span class="s2">"qcow2"</span>
        <span class="nx">replicate</span>    <span class="o">=</span> <span class="kc">false</span>
    <span class="err">}</span>

    <span class="nx">initialization</span> <span class="p">{</span>
        <span class="nx">ip_config</span> <span class="p">{</span>
            <span class="nx">ipv4</span> <span class="p">{</span>
                <span class="nx">address</span> <span class="o">=</span> <span class="s2">"dhcp"</span>
            <span class="p">}</span>
        <span class="p">}</span>

        <span class="nx">user_data_file_id</span> <span class="o">=</span> <span class="nx">element</span><span class="p">(</span><span class="nx">proxmox_virtual_environment_file</span><span class="p">.</span><span class="nx">k3s_agent_cloudinit</span><span class="p">.*.</span><span class="nx">id</span><span class="p">,</span> <span class="nx">count</span><span class="p">.</span><span class="nx">index</span><span class="p">)</span>
    <span class="p">}</span>

    <span class="nx">network_device</span> <span class="p">{</span>
      <span class="nx">bridge</span> <span class="o">=</span> <span class="s2">"vmbr0"</span>
      <span class="nx">mac_address</span> <span class="o">=</span> <span class="s2">"ee:ee:ee:00:00:5${count.index}"</span>
    <span class="p">}</span>

    <span class="nx">operating_system</span> <span class="p">{</span>
      <span class="nx">type</span> <span class="o">=</span> <span class="s2">"l26"</span>
    <span class="p">}</span>

    <span class="nx">vga</span> <span class="p">{</span>
        <span class="nx">memory</span>  <span class="o">=</span> <span class="mi">16</span>
        <span class="nx">type</span>    <span class="o">=</span> <span class="s2">"std"</span>
    <span class="p">}</span>

    <span class="c1">//serial_device {}</span>

    <span class="nx">clone</span> <span class="p">{</span>
        <span class="nx">vm_id</span> <span class="o">=</span> <span class="mi">9666</span>
        <span class="nx">node_name</span> <span class="o">=</span> <span class="nx">var</span><span class="p">.</span><span class="nx">proxmox_main_node_name</span>
        <span class="nx">datastore_id</span> <span class="o">=</span> <span class="nx">var</span><span class="p">.</span><span class="nx">proxmox_disks_nas_storage_pool</span>
    <span class="p">}</span>

    <span class="nx">depends_on</span> <span class="o">=</span> <span class="p">[</span> <span class="nx">proxmox_virtual_environment_vm</span><span class="p">.</span><span class="nx">k3s_control_plane</span> <span class="p">]</span>

<span class="p">}</span>

</code></pre></div></div>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="o">[</span>  2:18PM <span class="o">]</span>  <span class="o">[</span> toshi@turmalina:~/dev/alberinfra/terraform<span class="o">(</span>master✗<span class="o">)</span> <span class="o">]</span>
 <span class="nv">$ </span>terraform apply  <span class="nt">-var-file</span><span class="o">=</span><span class="s2">"vars.tfvars"</span>
tls_private_key.ssh: Refreshing state... <span class="o">[</span><span class="nb">id</span><span class="o">=</span>fc8e10ca14b36c8be3389e57006877903259b17f]
proxmox_virtual_environment_file.cloud_config: Refreshing state... <span class="o">[</span><span class="nb">id</span><span class="o">=</span>terraform_snippets:snippets/cloud-config.yaml]
proxmox_virtual_environment_file.k3s_etcd_cloudinit: Refreshing state... <span class="o">[</span><span class="nb">id</span><span class="o">=</span>terraform_snippets:snippets/k3s-etcd-cloud-config.yaml]
proxmox_virtual_environment_file.k3s_control_plane_cloudinit[0]: Refreshing state... <span class="o">[</span><span class="nb">id</span><span class="o">=</span>terraform_snippets:snippets/k3s-control-plane-0-cloud-config.yaml]
proxmox_virtual_environment_vm.k3s_control_plane[0]: Refreshing state... <span class="o">[</span><span class="nb">id</span><span class="o">=</span>5050]
terraform_data.clean_ssh_known_hosts[0]: Refreshing state... <span class="o">[</span><span class="nb">id</span><span class="o">=</span>31f50cec-46e2-b9c9-a343-b93ffce76b42]
ssh_sensitive_resource.grab_kubeconfig[0]: Refreshing state... <span class="o">[</span><span class="nb">id</span><span class="o">=</span>7996163503264882213]
local_sensitive_file.local_user_kubeconfig[0]: Refreshing state... <span class="o">[</span><span class="nb">id</span><span class="o">=</span>4f1a874c917b0ba076bf53abab934f6105f5e266]

Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols:
  + create
-/+ destroy and <span class="k">then </span>create replacement

Terraform will perform the following actions:
<span class="o">[</span> ... <span class="o">]</span>

  <span class="c"># proxmox_virtual_environment_vm.k3s_agent[0] will be created</span>
  + resource <span class="s2">"proxmox_virtual_environment_vm"</span> <span class="s2">"k3s_agent"</span> <span class="o">{</span>
      + acpi                    <span class="o">=</span> <span class="nb">true</span>
      + bios                    <span class="o">=</span> <span class="s2">"seabios"</span>
      + description             <span class="o">=</span> <span class="s2">"k3s cluster managed by Terraform"</span>
      + <span class="nb">id</span>                      <span class="o">=</span> <span class="o">(</span>known after apply<span class="o">)</span>
      + ipv4_addresses          <span class="o">=</span> <span class="o">(</span>known after apply<span class="o">)</span>
      + ipv6_addresses          <span class="o">=</span> <span class="o">(</span>known after apply<span class="o">)</span>
      + keyboard_layout         <span class="o">=</span> <span class="s2">"en-us"</span>
      + mac_addresses           <span class="o">=</span> <span class="o">(</span>known after apply<span class="o">)</span>
      + machine                 <span class="o">=</span> <span class="s2">"q35"</span>
      + migrate                 <span class="o">=</span> <span class="nb">false</span>
      + name                    <span class="o">=</span> <span class="s2">"k3s-agent-0"</span>
      + network_interface_names <span class="o">=</span> <span class="o">(</span>known after apply<span class="o">)</span>
      + node_name               <span class="o">=</span> <span class="s2">"px-03"</span>
      + on_boot                 <span class="o">=</span> <span class="nb">true</span>
      + protection              <span class="o">=</span> <span class="nb">false</span>
      + reboot                  <span class="o">=</span> <span class="nb">false</span>
      + scsi_hardware           <span class="o">=</span> <span class="s2">"virtio-scsi-pci"</span>
      + started                 <span class="o">=</span> <span class="nb">true</span>
      + stop_on_destroy         <span class="o">=</span> <span class="nb">false</span>
      + tablet_device           <span class="o">=</span> <span class="nb">true</span>
      + tags                    <span class="o">=</span> <span class="o">[</span>
          + <span class="s2">"terraform"</span>,
          + <span class="s2">"k3s"</span>,
          + <span class="s2">"agent"</span>,
        <span class="o">]</span>
      + template                <span class="o">=</span> <span class="nb">false</span>
      + timeout_clone           <span class="o">=</span> 1800
      + timeout_create          <span class="o">=</span> 1800
      + timeout_migrate         <span class="o">=</span> 1800
      + timeout_move_disk       <span class="o">=</span> 1800
      + timeout_reboot          <span class="o">=</span> 1800
      + timeout_shutdown_vm     <span class="o">=</span> 1800
      + timeout_start_vm        <span class="o">=</span> 1800
      + timeout_stop_vm         <span class="o">=</span> 300
      + vm_id                   <span class="o">=</span> 5070

      + agent <span class="o">{</span>
          + enabled <span class="o">=</span> <span class="nb">true</span>
          + <span class="nb">timeout</span> <span class="o">=</span> <span class="s2">"15m"</span>
          + trim    <span class="o">=</span> <span class="nb">false</span>
          + <span class="nb">type</span>    <span class="o">=</span> <span class="s2">"virtio"</span>
        <span class="o">}</span>

      + clone <span class="o">{</span>
          + datastore_id <span class="o">=</span> <span class="s2">"cluster_drives"</span>
          + full         <span class="o">=</span> <span class="nb">true</span>
          + node_name    <span class="o">=</span> <span class="s2">"px-03"</span>
          + retries      <span class="o">=</span> 1
          + vm_id        <span class="o">=</span> 9666
        <span class="o">}</span>

      + cpu <span class="o">{</span>
          + architecture <span class="o">=</span> <span class="s2">"x86_64"</span>
          + cores        <span class="o">=</span> 2
          + hotplugged   <span class="o">=</span> 0
          + limit        <span class="o">=</span> 0
          + numa         <span class="o">=</span> <span class="nb">false</span>
          + sockets      <span class="o">=</span> 1
          + <span class="nb">type</span>         <span class="o">=</span> <span class="s2">"x86-64-v2-AES"</span>
          + units        <span class="o">=</span> 1024
        <span class="o">}</span>

      + disk <span class="o">{</span>
          + aio               <span class="o">=</span> <span class="s2">"io_uring"</span>
          + backup            <span class="o">=</span> <span class="nb">true</span>
          + cache             <span class="o">=</span> <span class="s2">"writeback"</span>
          + datastore_id      <span class="o">=</span> <span class="s2">"cluster_drives"</span>
          + discard           <span class="o">=</span> <span class="s2">"ignore"</span>
          + file_format       <span class="o">=</span> <span class="s2">"qcow2"</span>
          + interface         <span class="o">=</span> <span class="s2">"virtio0"</span>
          + iothread          <span class="o">=</span> <span class="nb">false</span>
          + path_in_datastore <span class="o">=</span> <span class="o">(</span>known after apply<span class="o">)</span>
          + replicate         <span class="o">=</span> <span class="nb">false</span>
          + size              <span class="o">=</span> 8
          + ssd               <span class="o">=</span> <span class="nb">false</span>
        <span class="o">}</span>
      + disk <span class="o">{</span>
          + aio               <span class="o">=</span> <span class="s2">"io_uring"</span>
          + backup            <span class="o">=</span> <span class="nb">true</span>
          + cache             <span class="o">=</span> <span class="s2">"writeback"</span>
          + datastore_id      <span class="o">=</span> <span class="s2">"cluster_drives"</span>
          + discard           <span class="o">=</span> <span class="s2">"ignore"</span>
          + file_format       <span class="o">=</span> <span class="s2">"qcow2"</span>
          + interface         <span class="o">=</span> <span class="s2">"virtio1"</span>
          + iothread          <span class="o">=</span> <span class="nb">false</span>
          + path_in_datastore <span class="o">=</span> <span class="o">(</span>known after apply<span class="o">)</span>
          + replicate         <span class="o">=</span> <span class="nb">false</span>
          + size              <span class="o">=</span> 200
          + ssd               <span class="o">=</span> <span class="nb">false</span>
        <span class="o">}</span>

      + initialization <span class="o">{</span>
          + datastore_id      <span class="o">=</span> <span class="s2">"local-lvm"</span>
          + upgrade           <span class="o">=</span> <span class="o">(</span>known after apply<span class="o">)</span>
          + user_data_file_id <span class="o">=</span> <span class="o">(</span>known after apply<span class="o">)</span>

          + ip_config <span class="o">{</span>
              + ipv4 <span class="o">{</span>
                  + address <span class="o">=</span> <span class="s2">"dhcp"</span>
                <span class="o">}</span>
            <span class="o">}</span>
        <span class="o">}</span>

      + memory <span class="o">{</span>
          + dedicated      <span class="o">=</span> 4096
          + floating       <span class="o">=</span> 4096
          + keep_hugepages <span class="o">=</span> <span class="nb">false</span>
          + shared         <span class="o">=</span> 0
        <span class="o">}</span>

      + network_device <span class="o">{</span>
          + bridge      <span class="o">=</span> <span class="s2">"vmbr0"</span>
          + enabled     <span class="o">=</span> <span class="nb">true</span>
          + firewall    <span class="o">=</span> <span class="nb">false</span>
          + mac_address <span class="o">=</span> <span class="s2">"ee:ee:ee:00:00:50"</span>
          + model       <span class="o">=</span> <span class="s2">"virtio"</span>
          + mtu         <span class="o">=</span> 0
          + queues      <span class="o">=</span> 0
          + rate_limit  <span class="o">=</span> 0
          + vlan_id     <span class="o">=</span> 0
        <span class="o">}</span>

      + operating_system <span class="o">{</span>
          + <span class="nb">type</span> <span class="o">=</span> <span class="s2">"l26"</span>
        <span class="o">}</span>

      + vga <span class="o">{</span>
          + memory <span class="o">=</span> 16
          + <span class="nb">type</span>   <span class="o">=</span> <span class="s2">"std"</span>
        <span class="o">}</span>
    <span class="o">}</span>

  <span class="c"># ssh_sensitive_resource.grab_kubeconfig[0] must be replaced</span>
-/+ resource <span class="s2">"ssh_sensitive_resource"</span> <span class="s2">"grab_kubeconfig"</span> <span class="o">{</span>
      ~ <span class="nb">id</span>                                 <span class="o">=</span> <span class="s2">"7996163503264882213"</span> -&gt; <span class="o">(</span>known after apply<span class="o">)</span>
      ~ result                             <span class="o">=</span> <span class="o">(</span>sensitive value<span class="o">)</span>
      ~ triggers                           <span class="o">=</span> <span class="o">{</span> <span class="c"># forces replacement</span>
          - <span class="s2">"always_run"</span> <span class="o">=</span> <span class="s2">"2024-12-17T13:14:11Z"</span>
        <span class="o">}</span> -&gt; <span class="o">(</span>known after apply<span class="o">)</span> <span class="c"># forces replacement</span>
        <span class="c"># (12 unchanged attributes hidden)</span>
    <span class="o">}</span>

Plan: 4 to add, 0 to change, 2 to destroy.

Changes to Outputs:
  ~ kubeconfig              <span class="o">=</span> <span class="o">(</span>sensitive value<span class="o">)</span>
Do you want to perform these actions?
  Terraform will perform the actions described above.
  Only <span class="s1">'yes'</span> will be accepted to approve.

  Enter a value: <span class="nb">yes

</span>local_sensitive_file.local_user_kubeconfig[0]: Destroying... <span class="o">[</span><span class="nb">id</span><span class="o">=</span>4f1a874c917b0ba076bf53abab934f6105f5e266]
local_sensitive_file.local_user_kubeconfig[0]: Destruction <span class="nb">complete </span>after 0s
proxmox_virtual_environment_file.k3s_agent_cloudinit[0]: Creating...
ssh_sensitive_resource.grab_kubeconfig[0]: Destroying... <span class="o">[</span><span class="nb">id</span><span class="o">=</span>7996163503264882213]
ssh_sensitive_resource.grab_kubeconfig[0]: Destruction <span class="nb">complete </span>after 0s
ssh_sensitive_resource.grab_kubeconfig[0]: Creating...
proxmox_virtual_environment_file.k3s_agent_cloudinit[0]: Creation <span class="nb">complete </span>after 0s <span class="o">[</span><span class="nb">id</span><span class="o">=</span>terraform_snippets:snippets/k3s-agent-0-cloud-config.yaml]
proxmox_virtual_environment_vm.k3s_agent[0]: Creating...
ssh_sensitive_resource.grab_kubeconfig[0]: Creation <span class="nb">complete </span>after 0s <span class="o">[</span><span class="nb">id</span><span class="o">=</span>7130346671968112612]
local_sensitive_file.local_user_kubeconfig[0]: Creating...
local_sensitive_file.local_user_kubeconfig[0]: Creation <span class="nb">complete </span>after 0s <span class="o">[</span><span class="nb">id</span><span class="o">=</span>4f1a874c917b0ba076bf53abab934f6105f5e266]
proxmox_virtual_environment_vm.k3s_agent[0]: Still creating... <span class="o">[</span>10s elapsed]
proxmox_virtual_environment_vm.k3s_agent[0]: Still creating... <span class="o">[</span>20s elapsed]
proxmox_virtual_environment_vm.k3s_agent[0]: Still creating... <span class="o">[</span>30s elapsed]
proxmox_virtual_environment_vm.k3s_agent[0]: Still creating... <span class="o">[</span>40s elapsed]
proxmox_virtual_environment_vm.k3s_agent[0]: Still creating... <span class="o">[</span>50s elapsed]
proxmox_virtual_environment_vm.k3s_agent[0]: Still creating... <span class="o">[</span>1m0s elapsed]
proxmox_virtual_environment_vm.k3s_agent[0]: Still creating... <span class="o">[</span>1m10s elapsed]
proxmox_virtual_environment_vm.k3s_agent[0]: Still creating... <span class="o">[</span>1m20s elapsed]
proxmox_virtual_environment_vm.k3s_agent[0]: Still creating... <span class="o">[</span>1m30s elapsed]
proxmox_virtual_environment_vm.k3s_agent[0]: Still creating... <span class="o">[</span>1m40s elapsed]
proxmox_virtual_environment_vm.k3s_agent[0]: Still creating... <span class="o">[</span>1m50s elapsed]
proxmox_virtual_environment_vm.k3s_agent[0]: Still creating... <span class="o">[</span>2m0s elapsed]
proxmox_virtual_environment_vm.k3s_agent[0]: Still creating... <span class="o">[</span>2m10s elapsed]
proxmox_virtual_environment_vm.k3s_agent[0]: Creation <span class="nb">complete </span>after 2m13s <span class="o">[</span><span class="nb">id</span><span class="o">=</span>5070]

Apply <span class="nb">complete</span><span class="o">!</span> Resources: 4 added, 0 changed, 2 destroyed.

Outputs:

control_plane_hostnames <span class="o">=</span> <span class="o">[</span>
  <span class="s2">"k3s-control-plane-0"</span>,
<span class="o">]</span>
kubeconfig <span class="o">=</span> &lt;sensitive&gt;
private_key <span class="o">=</span> &lt;sensitive&gt;
</code></pre></div></div>

<h1 id="próximos-pasos">Próximos Pasos</h1>

<p>Una vez que ya tenemos andando nuestro cluster Kubernetes, 
podemos hacer con él lo que queramos.
Lo que hice en mi caso fue instalar <code class="language-plaintext highlighter-rouge">GitLab</code> y <code class="language-plaintext highlighter-rouge">FluxCD</code>
para aplicar el framework <a href="https://about.gitlab.com/topics/gitops/">GitOps</a> y tener un <a href="https://en.wikipedia.org/wiki/Single_source_of_truth">Single Source of Truth</a>.</p>

<p><img src="/assets/images/datacenter/flux-running-reconcile.png" alt="Flux Bootstrap Reconcile" class="img-responsive" /><em>Flux Bootstrap Reconcile</em></p>

<p>En mi caso, toda la infraestructura, <code class="language-plaintext highlighter-rouge">kubernetes</code> y las
aplicaciones y containers que corren sobre el cluster, 
<strong>estan todas definidas en un único repositorio</strong> que <code class="language-plaintext highlighter-rouge">Flux</code>
está mirando constantemente cuando hay nuevos pushes en
el repositorio donde están todas las definiciones.
Cualquier cambio que se haga en mi repo, modifica alguna parte
del cluster.</p>

<p><img src="/assets/images/datacenter/k9s-corriendo-k8s-flux-y-podinfo.png" alt="Corriendo FluxCD" class="img-responsive" /><em>Flux y podinfo corriendo en mi cluster k8s</em></p>

<p>Y en caso de querer mandar todo a la mierda y volar el cluster, solo basta
con un dulce y tierno <strong>terraform destroy</strong> :D</p>

<p>Si llegaste hasta acá y te sirvió: <strong>¡Salud!</strong></p>]]></content><author><name>toshi</name></author><category term="coding" /><summary type="html"><![CDATA[Como armar un datacenter casero con Hardware viejo, Proxmox, Packer, Terraform y un NAS que vive escribiendo a disco. ¡Kubernetes incluido!]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://0x705h.com/assets/images/datacenter/datacenter1.jpg" /><media:content medium="image" url="https://0x705h.com/assets/images/datacenter/datacenter1.jpg" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry xml:lang="es"><title type="html">iSS 4K Intro - 1er Puesto - Flashparty 2024 4kb Compo</title><link href="https://0x705h.com/de/iSS-4k-intro" rel="alternate" type="text/html" title="iSS 4K Intro - 1er Puesto - Flashparty 2024 4kb Compo" /><published>2024-10-14T11:37:00+00:00</published><updated>2024-10-14T11:37:00+00:00</updated><id>https://0x705h.com/iSS-4k-intro</id><content type="html" xml:base="https://0x705h.com/iSS-4k-intro"><![CDATA[<p>Una nueva <a href="https://flashparty.rebelion.digital/index.php/es/">Flashparty</a>, una nueva <a href="https://flashparty.rebelion.digital/index.php/es/compos-reglas">Compo</a> y una <a href="https://files.scene.org/view/parties/2024/flashparty24/4k_and_256b_intro/behelit-iss.zip">Intro 4k</a> que supo
llegar al <a href="https://www.pouet.net/prod.php?which=98296">primer puesto</a> en la competición casi anual latinoamericana
de la <a href="https://en.wikipedia.org/wiki/Demoscene">demoscene</a>. 
La razón por la cual publicamos este artículo es para cebar a los actuales
y potenciales demosceners latinoamericanos a crear producciones 
como esta y al explicar el workflow, demistificar la complejidad, demostrando
que poniendo todas las piezas juntas del rompecabezas, es posible. No es fácil
claro, pero ni a palos imposible.</p>

<p>Nuestro grupo, <a href="https://behel.it">BEHELiT</a> somos:</p>

<ul>
  <li>Toshi (el que escribe)</li>
  <li><a href="https://linktr.ee/rowboto">Row</a></li>
  <li><a href="https://www.hopp.bio/madbit">Madbit</a></li>
</ul>

<p>Acá podes descargar la <code class="language-plaintext highlighter-rouge">Intro 4k</code> en este mismo link –&gt; <a href="https://files.scene.org/view/parties/2024/flashparty24/4k_and_256b_intro/behelit-iss.zip">Intro 4k</a>. 
Para que se vea más o menos rápido, necesitás una RTX.</p>

<h1 id="una-breve-introducción">Una Breve Introducción</h1>

<p>Generalizando una bocha, una <code class="language-plaintext highlighter-rouge">Demo</code> es un programa - que cuando es extremadamente pequeño - que se renombra a <code class="language-plaintext highlighter-rouge">intro</code> como la que vamos a discutir en este artículo - que produce presentaciones audiovisuales en tiempo real. El propósito de estas producciones es demostrar habilidades en programación, arte visual y música. Estas producciones u otras del mundo de la demoscene (gráficos, música, videos, videojuegos) son compartidas, votadas y publicadas online en festivales o conferencias llamados "demoparties" como por ejemplo, la <a href="https://flashparty.rebelion.digital/index.php/es/">Flashparty</a>.</p>

<p>En las categorías que hay en estas competiciones, esta producción participó en la categoría "Intro 4K" donde la limitación está en el tamaño del ejecutable, que en este caso es <code class="language-plaintext highlighter-rouge">4096 Bytes</code>.</p>

<p>Aquí, logramos meter en un programa de este tamaño (algo muchísimo más pequeño que el tamaño total de este texto que estás leyendo) gráficos en tiempo real, música y lo necesario para poder coordinar todo en un ejecutable <code class="language-plaintext highlighter-rouge">.EXE</code> de Windows.</p>

<p>Para ponerlo en perspectiva, este ejecutable de <code class="language-plaintext highlighter-rouge">4096 Bytes</code> que comentamos en este artículo, produjo <code class="language-plaintext highlighter-rouge">~1 Gigabyte</code> de data de audio y video en 2 minutos de ejecución. Esta captura luego fue subida a Youtube.</p>

<p>Aquí abajo, el video:</p>

<center>
<iframe width="560" height="315" src="https://www.youtube.com/embed/ywQAltQ2GVU?si=FkNq0OfDoioJUnky" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen=""></iframe>
</center>

<h1 id="el-framework">El Framework</h1>

<p>Nuestro framework está compuesto de las siguientes herramientas:</p>

<ul>
  <li>cmake</li>
  <li>nasm</li>
  <li>visual studio 2022 community edition (CRT SDK)</li>
  <li><a href="https://github.com/vsariola/sointu">sointu</a></li>
  <li><a href="https://github.com/runestubbe/Crinkler">crinkler</a></li>
  <li><a href="https://github.com/laurentlb/shader-minifier">Shader Minifier</a></li>
  <li><a href="https://github.com/Gargaj/Bonzomatic">bonzomatic</a></li>
</ul>

<h2 id="la-toolchain">La Toolchain</h2>

<p>El Visual Studio nos habilita a tener todos los headers necesarios 
para poder compilar un ejecutable y hablar con la API de Windows. 
Adicionalmente, necesitamos instalar el <code class="language-plaintext highlighter-rouge">Windows Universal CRT SDK</code> como
dependencia para el proyecto. Este SDK nos permite tener un runtime universal
para poder ejecutar programas hechos con C y C++ y nos habilita tener acceso 
a las funciones estandard a lo largo de diferentes versiones de Windows 
sin necesidad de recompilar programas dependiendo de cada una de las versiones
del sistema operativo.</p>

<p>Con <code class="language-plaintext highlighter-rouge">cmake</code> no solo unimos todo el proyecto sino que también los <code class="language-plaintext highlighter-rouge">stages</code> de compilación,
linking y compresión del ejecutable final. Esto nos permite tener diferentes 
<code class="language-plaintext highlighter-rouge">targets</code> donde en el proceso de desarrollo de la intro, podemos seleccionar 
tener una versión final con todas las técnicas de compresión y minificado o bien
una versión de desarrollo que compila rápido. <code class="language-plaintext highlighter-rouge">cmake</code> se integra con
Visual Studio relativamente fácil y además estoy más acostumbrado a laburar con <code class="language-plaintext highlighter-rouge">cmake</code> antes que con el sistema de <code class="language-plaintext highlighter-rouge">Makefiles</code> que provee Visual Studio que 
es mucho más visual (heh) que con código.</p>

<p>Necesitamos además <code class="language-plaintext highlighter-rouge">nasm</code> porque es una de las dependencias de <a href="https://github.com/vsariola/sointu">sointu</a>, que es el <strong>sintetizador modular sustractivo</strong> de audio, forkeado de <code class="language-plaintext highlighter-rouge">4klang</code> que usamos para esta producción. Más adelante en este artículo entro en detalles.</p>

<p>Luego viene <a href="https://github.com/runestubbe/Crinkler">crinkler</a> que es un compresor de ejecutables con una vuelta de tuerca: no solamente comprime el ejecutable sino también que es un <code class="language-plaintext highlighter-rouge">linker</code>. Esto significa que en vez de usar el linker que viene por defecto en Visual Studio, nos metemos en el medio del proceso de building, descartamos el linker de microsoft y usamos este, que antes de insertar los <code class="language-plaintext highlighter-rouge">.obj</code>, reorganiza su data, las comprime, crea hashtables y organiza las secciones, comprimiéndolas entre otras cosas.</p>

<p>Una de las partes del código, contiene una <code class="language-plaintext highlighter-rouge">shader</code> (en verdad, dos shaders, el principal y el de postprocessing) en un directorio donde lo vamos codeando. 
Pero no optimizamos (mucho) en el desarrollo el nombre de las variables o sus 
<code class="language-plaintext highlighter-rouge">#defines</code>, ya que reducir a mano el nombre de las variables (como se suele ver 
seguido en shadertoy) hace el código bastante ilegible. Entonces usamos
una herramienta que hace esto por nosotros en el proceso de compilación
que se llama <a href="https://github.com/laurentlb/shader-minifier">Shader Minifier</a>. No hace algo muy distinto a cualquier minificador
de código de Javascript o CSS, pero especializado en código HLSL y GLSL.
Yo uso GLSL como lenguaje para escribir nuestros shaders.</p>

<p>Aqui debajo una sección del <code class="language-plaintext highlighter-rouge">fragment shader</code> de la intro <strong>sin minificar</strong>:</p>

<div class="language-glsl highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">Hit</span> <span class="nf">march</span><span class="p">(</span><span class="n">Ray</span> <span class="n">ray</span><span class="p">)</span> <span class="p">{</span>
    <span class="n">Hit</span> <span class="n">hit</span> <span class="o">=</span> <span class="n">noHit</span><span class="p">();</span>

    <span class="k">for</span><span class="p">(</span><span class="kt">int</span> <span class="n">i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="n">i</span> <span class="o">&lt;</span> <span class="n">DMS</span><span class="p">;</span> <span class="n">i</span><span class="o">++</span><span class="p">)</span> <span class="p">{</span>
    	<span class="kt">vec3</span> <span class="n">pivot</span> <span class="o">=</span> <span class="n">ray</span><span class="p">.</span><span class="n">origin</span> <span class="o">+</span> <span class="n">ray</span><span class="p">.</span><span class="n">direction</span> <span class="o">*</span> <span class="n">hit</span><span class="p">.</span><span class="n">distance</span><span class="p">;</span> 
        <span class="kt">float</span> <span class="n">distance</span> <span class="o">=</span> <span class="n">sdfScene</span><span class="p">(</span><span class="n">pivot</span><span class="p">);</span>
        <span class="n">hit</span><span class="p">.</span><span class="n">distance</span> <span class="o">+=</span> <span class="n">distance</span><span class="p">;</span> 
        <span class="k">if</span><span class="p">(</span><span class="n">hit</span><span class="p">.</span><span class="n">distance</span> <span class="o">&gt;</span> <span class="n">MAXDISTANCE</span> 
            <span class="o">||</span> <span class="n">distance</span> <span class="o">&lt;</span> <span class="p">.</span><span class="mi">1</span> <span class="o">*</span> <span class="n">TENTH_EPSILON</span><span class="p">)</span> 
        <span class="k">break</span><span class="p">;</span>
    <span class="p">}</span>

    <span class="n">hit</span><span class="p">.</span><span class="n">point</span> <span class="o">+=</span> <span class="n">ray</span><span class="p">.</span><span class="n">origin</span> <span class="o">+</span> <span class="n">ray</span><span class="p">.</span><span class="n">direction</span> <span class="o">*</span> <span class="n">hit</span><span class="p">.</span><span class="n">distance</span><span class="p">;</span>
    <span class="n">hit</span><span class="p">.</span><span class="n">normal</span> <span class="o">=</span> <span class="n">computeNormal</span><span class="p">(</span><span class="n">hit</span><span class="p">.</span><span class="n">point</span><span class="p">);</span>
    <span class="n">hit</span><span class="p">.</span><span class="n">material</span> <span class="o">=</span> <span class="n">sdfTagMaterial</span><span class="p">(</span><span class="n">ray</span><span class="p">,</span> <span class="n">hit</span><span class="p">.</span><span class="n">distance</span><span class="p">,</span> <span class="n">hit</span><span class="p">.</span><span class="n">point</span><span class="p">);</span>

    <span class="k">return</span> <span class="n">hit</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Y la misma función <strong>minificada</strong>:</p>

<div class="language-glsl highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <span class="s">"Hit d(Ray v)"</span>
 <span class="s">"{"</span>
   <span class="s">"Hit m=s();"</span>
   <span class="s">"for(int f=0;f&lt;DMS;f++)"</span>
     <span class="s">"{"</span>
       <span class="s">"vec3 y=v.origin+v.direction*m.distance;"</span>
       <span class="s">"float n=D(y);"</span>
       <span class="s">"m.distance+=n;"</span>
       <span class="s">"if(m.distance&gt;MAXDISTANCE||n&lt;.1*TENTH_EPSILON)"</span>
         <span class="s">"break;"</span>
     <span class="s">"}"</span>
   <span class="s">"m.point+=v.origin+v.direction*m.distance;"</span>
   <span class="s">"m.normal=a(m.point);"</span>
   <span class="s">"m.material=D(v,m.distance,m.point);"</span>
   <span class="s">"return m;"</span>
 <span class="s">"}"</span>
</code></pre></div></div>

<p>Se puede ver también que en el <code class="language-plaintext highlighter-rouge">struct del HIT</code> <strong>todavía</strong> hay espacio para 
manualmente reducir los nombres de sus propiedades. También sus <code class="language-plaintext highlighter-rouge">#defines</code> que por algún motivo no son reemplazados.
Pero en esta iteración de desarrollo, preferimos legibilidad ante todo (!) :)</p>

<p>Como los ciclos de compilación y prueba del tamaño final de la producción 
dependiendo en que máquina compilemos lleva de 5 a 10 minutos, para probar
nuestros shaders usamos <a href="https://github.com/Gargaj/Bonzomatic">bonzomatic</a>. Cuando uno esta satisfecho con los cambios
del shader, luego se integra a la producción y se compila con varios flags
que tengo armados en <code class="language-plaintext highlighter-rouge">cmake</code> donde activo y desactivo por targets que van 
desde "no me comprimas nada, dejá todo como está asi a lo bestia" hasta
"sacále todo el jugo que puedas a la compresión". 
Este último tarda muchísimo, entonces para tener una compilación del shader GLSL rápida, uso <a href="https://github.com/Gargaj/Bonzomatic">bonzomatic</a> que me da feedback inmediato pulsando <code class="language-plaintext highlighter-rouge">F5</code> y 
después integro con pequeños cambios el shader escrito ahí a la intro.</p>

<h1 id="la-música">La Música</h1>

<p>Acá es donde entra <a href="https://www.hopp.bio/madbit">Madbit</a> o como le decimos en <a href="https://behel.it">BEHELiT</a>, <code class="language-plaintext highlighter-rouge">El Bitio</code>. 
<a href="https://github.com/vsariola/sointu">Sointu</a> es un <strong>sintetizador modular sustractivo</strong>, especializado para producciones como esta. 
<a href="https://github.com/vsariola/sointu">Sointu</a> viene un con un <a href="https://en.wikipedia.org/wiki/Music_tracker">Tracker</a>, con un sintetizador y un compilador del 
archivo trackeado, que lo transforma en bytecode que el sintetizador puede interpretar y
hacerlo sonar en un espacio ínfimo de código.</p>

<p><img src="/assets/images/iSS-4k-intro/madbit-sointu-track.PNG" alt="Sointu Track de Madbit" class="img-responsive" /><em>Sointu Track de Madbit</em></p>

<p>Cada instrumento es definido por sus propiedades para poder ser sintetizado,
como un filtro ADSR (attack, decay, sustain, release), el gain, filtros, delay, envelopes, osciladores, etc…
Después, se configuran las notas de cada uno de los instrumentos con sus notas en un 
patrón. Y esos patrones se componen entre ellos para conformar la canción. 
Una de las cosas que <a href="https://www.hopp.bio/madbit">Madbit</a> tuvo que tener en cuenta al armar instrumentos, patrones
y la canción en su totalidad, es la limitación de tamaño del tema. Entonces al componer (si mal no recuerdo solo tenía <strong>500 bytes disponibles</strong> para poder hacer <strong>TODO UN TEMA DE 2 MINUTOS</strong>) tuvo que diseñar una canción que fuera interesante y a la vez 
con una mínima cantidad de instrumentos. ¡Y lo optimizó de manera tal que le metio banda de instrumentos y patrones!</p>

<p>Una anécdota, durante toda la producción, estuve trabajando con un loop de un patrón con 4 instrumentos que no tenía variaciones que me mandó él como boilerplate. El mismo día que estabamos cerrando la intro, le pregunto al bitio si termina el tema (estuvo trabajando fuertemente en 
la música y diseño de <a href="https://www.pouet.net/prod.php?which=98277">Man in the Vox</a>, también ganador del primer puesto de la <a href="https://flashparty.rebelion.digital/index.php/es/">Flashparty</a> en la categoria <code class="language-plaintext highlighter-rouge">Demo PC</code>).
Solo tuvo 3 horas antes de la deadline para hacer el tema y mandarme el <code class="language-plaintext highlighter-rouge">yaml</code> que escupe <a href="https://github.com/vsariola/sointu">sointu</a> antes de enviar el ejecutable final a la <a href="https://flashparty.rebelion.digital/index.php/es/">Flashparty</a>. Y lo hizo. Hizo un temazo en 3 horitas nada más. Kudos <a href="https://www.hopp.bio/madbit">Madbit</a>, un capo y un músico de la concha de la lora.</p>

<h1 id="el-fragment-shader">El Fragment Shader</h1>

<h2 id="raymarching-y-pathtracing">Raymarching y Pathtracing</h2>

<p>Habiendo experimentado una banda con raymarching y viendo lo que viene pasando
en la <a href="https://en.wikipedia.org/wiki/Demoscene">Demoscene</a> últimamente en las competiciones me encuentro con una forma 
de procesar los rayos que hace que salga casi-gratis <code class="language-plaintext highlighter-rouge">global illumination</code> y 
la propiedad <code class="language-plaintext highlighter-rouge">emissive</code> de un material en un campo geométrico. 
Después de intentarlo sin éxito claro, le pedí ayuda para la implementación 
a <a href="https://linktr.ee/rowboto">Row</a>. Me explica que esto que quería lograr, era <strong>Pathtracing</strong> y que 
ya estaba implementado en su engine que valió el 1er puesto
también con <a href="https://www.pouet.net/prod.php?which=98277">Man in the Vox</a>, una excelente producción de <a href="https://behel.it">BEHELiT</a>.</p>

<p><img src="/assets/images/iSS-4k-intro/sdf-geometry-and-tag-material.PNG" alt="Geometría SDF, Pathtracing y SDF Tag Material" class="img-responsive" /><em>Geometría SDF, Pathtracing y SDF Tag Material</em></p>

<p>Después de varias charlas por discord a altísimas horas de la noche, el planteo
mio fue el siguiente:</p>

<p>"¿Es posible juntar estas dos cosas? ¿Raymarching (para tener las bondades de SDF)
y Pathtracing para poder asignarle propiedades a los objetos?"</p>

<p>Y me dijo, claro que si, <code class="language-plaintext highlighter-rouge">hold my fukken beer</code>. Le dije que yo se la sostengo,
y me la tomé. Después de varias explicaciones seguía sin entender como funcionaba
ese segmento del shader donde un objeto SDF era "taggeado" por cada una de sus
expresiones matemáticas, y que los rayos vayan rebotando dependiendo de 
la propiedad de la superficie (roughness, reflective, emissive) y ademas de la capacidad que
tenga cada objeto de emitir luz y que esos rayos rebotaran usándolos.</p>

<p>Como no entendí un carajo, aunque <a href="https://linktr.ee/rowboto">Row</a> me lo explicó varias veces, me tomé un vuelo 
a Buenos Aires y de ahí a Uruguay (donde <a href="https://linktr.ee/rowboto">Row</a> y <a href="https://www.hopp.bio/madbit">Madbit</a> viven) a exigir 
explicaciones y compartir unos guisos DELICIOSOS mientras la nerdeabamos y 
haciamos un cringe-gauntlet en el estudio del <code class="language-plaintext highlighter-rouge">bitio</code>; cosas que merecen otro post.</p>

<p>Calculamos la geometría y el "Tag Material":</p>

<div class="language-glsl highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">//////////</span>
<span class="c1">// Scene</span>

<span class="kt">float</span> <span class="nf">sdfScene</span><span class="p">(</span><span class="kt">vec3</span> <span class="n">point</span><span class="p">)</span> <span class="p">{</span>
    <span class="c1">//Tunnel</span>
    <span class="kt">float</span> <span class="n">tunnel</span> <span class="o">=</span> <span class="n">fBox</span><span class="p">(</span><span class="n">point</span> <span class="o">+</span> <span class="kt">vec3</span><span class="p">(</span><span class="mi">0</span><span class="p">.,</span> <span class="o">-</span><span class="mi">1</span><span class="p">.,</span> <span class="mi">0</span><span class="p">.),</span> <span class="kt">vec3</span><span class="p">(</span><span class="mi">1</span><span class="p">.</span><span class="mi">2</span><span class="p">,</span> <span class="mi">1</span><span class="p">.</span><span class="mi">2</span><span class="p">,</span> <span class="mi">99999</span><span class="p">.));</span>
    <span class="kt">float</span> <span class="n">carTunnelLeft</span> <span class="o">=</span> <span class="n">fBox</span><span class="p">(</span><span class="n">point</span> <span class="o">+</span> <span class="kt">vec3</span><span class="p">(</span><span class="mi">3</span><span class="p">.,</span> <span class="o">-</span><span class="mi">2</span><span class="p">.,</span> <span class="mi">0</span><span class="p">.),</span> <span class="kt">vec3</span><span class="p">(</span><span class="mi">1</span><span class="p">.</span><span class="mi">2</span><span class="p">,</span> <span class="mi">1</span><span class="p">.</span><span class="mi">2</span><span class="p">,</span> <span class="mi">99999</span><span class="p">.));</span>
    <span class="kt">float</span> <span class="n">carTunnelRight</span> <span class="o">=</span> <span class="n">fBox</span><span class="p">(</span><span class="n">point</span> <span class="o">+</span> <span class="kt">vec3</span><span class="p">(</span><span class="o">-</span><span class="mi">3</span><span class="p">.,</span> <span class="o">-</span><span class="mi">2</span><span class="p">.,</span> <span class="mi">0</span><span class="p">.),</span> <span class="kt">vec3</span><span class="p">(</span><span class="mi">1</span><span class="p">.</span><span class="mi">2</span><span class="p">,</span> <span class="mi">1</span><span class="p">.</span><span class="mi">2</span><span class="p">,</span> <span class="mi">99999</span><span class="p">.));</span>


    <span class="c1">//Walls</span>
    <span class="kt">vec3</span> <span class="n">wallPoint</span> <span class="o">=</span> <span class="n">kifs</span><span class="p">(</span><span class="n">point</span><span class="p">,</span> <span class="n">sin</span><span class="p">(</span><span class="n">fGlobalTime</span><span class="o">/</span><span class="mi">200</span><span class="p">)</span> <span class="o">+</span> <span class="mi">1</span><span class="p">.</span><span class="mi">3</span><span class="p">);</span>  
    <span class="c1">//wallPoint = point;</span>

    <span class="c1">// red carpet </span>
    <span class="kt">float</span> <span class="n">redCarpet</span> <span class="o">=</span> <span class="n">fBox</span><span class="p">(</span><span class="n">wallPoint</span> <span class="o">+</span> <span class="kt">vec3</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="mi">5</span><span class="p">,</span> <span class="mi">0</span><span class="p">),</span> <span class="kt">vec3</span><span class="p">(</span><span class="mi">100</span><span class="p">,</span> <span class="mi">0</span><span class="p">.</span><span class="mo">01</span><span class="p">,</span> <span class="mi">100</span><span class="p">));</span>


    <span class="kt">float</span> <span class="n">wall1</span> <span class="o">=</span> <span class="n">fBox</span><span class="p">(</span><span class="n">wallPoint</span> <span class="o">+</span> <span class="kt">vec3</span><span class="p">(</span><span class="mi">1</span><span class="p">.</span><span class="mi">2</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">),</span> <span class="kt">vec3</span><span class="p">(.</span><span class="mi">50</span><span class="p">,</span> <span class="mi">5</span><span class="p">.,</span> <span class="p">.</span><span class="mi">25</span><span class="p">));</span>
    <span class="kt">float</span> <span class="n">wall2</span> <span class="o">=</span> <span class="n">fBox</span><span class="p">(</span><span class="n">wallPoint</span> <span class="o">+</span> <span class="kt">vec3</span><span class="p">(</span><span class="o">-</span><span class="p">.</span><span class="mi">5</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">),</span> <span class="kt">vec3</span><span class="p">(.</span><span class="mi">25</span><span class="p">,</span> <span class="mi">3</span><span class="p">.,</span> <span class="p">.</span><span class="mi">35</span><span class="p">));</span>
    <span class="kt">float</span> <span class="n">wall3</span> <span class="o">=</span> <span class="n">fBox</span><span class="p">(</span><span class="n">wallPoint</span> <span class="o">+</span> <span class="kt">vec3</span><span class="p">(</span> <span class="p">.</span><span class="mi">5</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">),</span> <span class="kt">vec3</span><span class="p">(.</span><span class="mo">05</span><span class="p">,</span> <span class="mi">2</span><span class="p">.</span><span class="mi">5</span><span class="p">,</span> <span class="p">.</span><span class="mi">15</span><span class="p">));</span>
    <span class="kt">float</span> <span class="n">wall4</span> <span class="o">=</span> <span class="n">fBox</span><span class="p">(</span><span class="n">wallPoint</span> <span class="o">+</span> <span class="kt">vec3</span><span class="p">(</span> <span class="mi">0</span><span class="p">.</span><span class="mi">3</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">),</span> <span class="kt">vec3</span><span class="p">(</span><span class="mi">5</span><span class="p">.</span><span class="mo">05</span><span class="p">,</span> <span class="mi">1</span><span class="p">.</span><span class="mi">5</span><span class="p">,</span> <span class="p">.</span><span class="mi">15</span><span class="p">));</span>
    <span class="kt">float</span> <span class="n">walls</span> <span class="o">=</span> <span class="n">min</span><span class="p">(</span><span class="n">wall1</span><span class="p">,</span> <span class="n">min</span><span class="p">(</span><span class="n">wall2</span><span class="p">,</span> <span class="n">min</span><span class="p">(</span><span class="n">wall3</span><span class="p">,</span> <span class="n">wall4</span><span class="p">)));</span>

    <span class="c1">//Floor</span>
    <span class="kt">float</span> <span class="n">floorCube1</span> <span class="o">=</span> <span class="n">fBox</span><span class="p">(</span><span class="n">wallPoint</span> <span class="o">+</span> <span class="kt">vec3</span><span class="p">(</span><span class="o">-</span><span class="mi">2</span><span class="p">.,</span> <span class="mi">5</span><span class="p">.,</span> <span class="o">-</span><span class="p">.</span><span class="mi">33</span><span class="p">),</span> <span class="kt">vec3</span><span class="p">(</span><span class="mi">1</span><span class="p">.,</span> <span class="mi">1</span><span class="p">.,</span> <span class="p">.</span><span class="mi">33</span><span class="p">));</span>
    <span class="kt">float</span> <span class="n">floorCube2</span> <span class="o">=</span> <span class="n">fBox</span><span class="p">(</span><span class="n">wallPoint</span> <span class="o">+</span> <span class="kt">vec3</span><span class="p">(</span><span class="mi">0</span><span class="p">.,</span> <span class="mi">5</span><span class="p">.,</span> <span class="mi">0</span><span class="p">),</span> <span class="kt">vec3</span><span class="p">(</span><span class="mi">1</span><span class="p">.,</span> <span class="mi">1</span><span class="p">.,</span> <span class="p">.</span><span class="mi">33</span><span class="p">));</span>
    <span class="kt">float</span> <span class="n">floorCube3</span> <span class="o">=</span> <span class="n">fBox</span><span class="p">(</span><span class="n">wallPoint</span> <span class="o">+</span> <span class="kt">vec3</span><span class="p">(</span><span class="mi">2</span><span class="p">.,</span> <span class="mi">5</span><span class="p">.,</span> <span class="p">.</span><span class="mi">33</span><span class="p">),</span> <span class="kt">vec3</span><span class="p">(</span><span class="mi">1</span><span class="p">.,</span> <span class="mi">1</span><span class="p">.,</span> <span class="p">.</span><span class="mi">33</span><span class="p">));</span>
  
    <span class="kt">float</span> <span class="n">floorCube</span> <span class="o">=</span> <span class="n">min</span><span class="p">(</span><span class="n">floorCube3</span><span class="p">,</span> <span class="n">min</span><span class="p">(</span><span class="n">floorCube1</span><span class="p">,</span> <span class="n">floorCube2</span><span class="p">));</span>

    <span class="k">return</span> <span class="n">max</span><span class="p">(</span><span class="o">-</span><span class="n">carTunnelRight</span><span class="p">,</span> <span class="n">max</span><span class="p">(</span><span class="o">-</span><span class="n">carTunnelLeft</span><span class="p">,</span> <span class="n">max</span><span class="p">(</span><span class="o">-</span><span class="n">tunnel</span><span class="p">,</span> <span class="n">min</span><span class="p">(</span><span class="n">redCarpet</span><span class="p">,</span> <span class="n">min</span><span class="p">(</span><span class="n">walls</span><span class="p">,</span> <span class="n">floorCube</span><span class="p">)))));</span>
<span class="p">}</span>

<span class="n">Material</span> <span class="nf">sdfTagMaterial</span><span class="p">(</span><span class="n">Ray</span> <span class="n">ray</span><span class="p">,</span> <span class="kt">float</span> <span class="n">distance</span><span class="p">,</span> <span class="kt">vec3</span> <span class="n">point</span><span class="p">)</span> <span class="p">{</span>

    <span class="c1">//Walls</span>
    <span class="kt">vec3</span> <span class="n">wallPoint</span> <span class="o">=</span> <span class="n">kifs</span><span class="p">(</span><span class="n">point</span><span class="p">,</span> <span class="n">sin</span><span class="p">(</span><span class="n">fGlobalTime</span><span class="o">/</span><span class="mi">200</span><span class="p">)</span> <span class="o">+</span> <span class="mi">1</span><span class="p">.</span><span class="mi">3</span><span class="p">);</span>
    <span class="c1">//wallPoint = point;</span>

    <span class="c1">// red carpet </span>
    <span class="kt">float</span> <span class="n">redCarpet</span> <span class="o">=</span> <span class="n">fBox</span><span class="p">(</span><span class="n">wallPoint</span> <span class="o">+</span> <span class="kt">vec3</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="mi">5</span><span class="p">,</span> <span class="mi">0</span><span class="p">),</span> <span class="kt">vec3</span><span class="p">(</span><span class="mi">100</span><span class="p">,</span> <span class="mi">0</span><span class="p">.</span><span class="mo">01</span><span class="p">,</span> <span class="mi">100</span><span class="p">));</span>
    <span class="kt">float</span> <span class="n">wall1</span> <span class="o">=</span> <span class="n">fBox</span><span class="p">(</span><span class="n">wallPoint</span> <span class="o">+</span> <span class="kt">vec3</span><span class="p">(</span><span class="mi">1</span><span class="p">.</span><span class="mi">2</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">),</span> <span class="kt">vec3</span><span class="p">(.</span><span class="mi">50</span><span class="p">,</span> <span class="mi">5</span><span class="p">.,</span> <span class="p">.</span><span class="mi">25</span><span class="p">));</span>
    <span class="kt">float</span> <span class="n">wall2</span> <span class="o">=</span> <span class="n">fBox</span><span class="p">(</span><span class="n">wallPoint</span> <span class="o">+</span> <span class="kt">vec3</span><span class="p">(</span><span class="o">-</span><span class="p">.</span><span class="mi">5</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">),</span> <span class="kt">vec3</span><span class="p">(.</span><span class="mi">25</span><span class="p">,</span> <span class="mi">3</span><span class="p">.,</span> <span class="p">.</span><span class="mi">35</span><span class="p">));</span>
    <span class="kt">float</span> <span class="n">wall3</span> <span class="o">=</span> <span class="n">fBox</span><span class="p">(</span><span class="n">wallPoint</span> <span class="o">+</span> <span class="kt">vec3</span><span class="p">(</span> <span class="p">.</span><span class="mi">5</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">),</span> <span class="kt">vec3</span><span class="p">(.</span><span class="mo">05</span><span class="p">,</span> <span class="mi">2</span><span class="p">.</span><span class="mi">5</span><span class="p">,</span> <span class="p">.</span><span class="mi">15</span><span class="p">));</span>
    <span class="kt">float</span> <span class="n">wall4</span> <span class="o">=</span> <span class="n">fBox</span><span class="p">(</span><span class="n">wallPoint</span> <span class="o">+</span> <span class="kt">vec3</span><span class="p">(</span> <span class="p">.</span><span class="mi">9</span><span class="p">,</span> <span class="mi">2</span><span class="p">,</span> <span class="mi">0</span><span class="p">),</span> <span class="kt">vec3</span><span class="p">(.</span><span class="mo">05</span><span class="p">,</span> <span class="mi">5</span><span class="p">.</span><span class="mi">5</span><span class="p">,</span> <span class="p">.</span><span class="mi">15</span><span class="p">));</span>

    <span class="kt">float</span> <span class="n">floorCube1</span> <span class="o">=</span> <span class="n">fBox</span><span class="p">(</span><span class="n">wallPoint</span> <span class="o">+</span> <span class="kt">vec3</span><span class="p">(</span><span class="o">-</span><span class="mi">2</span><span class="p">.,</span> <span class="mi">5</span><span class="p">.,</span> <span class="o">-</span><span class="p">.</span><span class="mi">33</span><span class="p">),</span> <span class="kt">vec3</span><span class="p">(</span><span class="mi">1</span><span class="p">.,</span> <span class="mi">1</span><span class="p">.,</span> <span class="p">.</span><span class="mi">33</span><span class="p">));</span>
    <span class="kt">float</span> <span class="n">floorCube2</span> <span class="o">=</span> <span class="n">fBox</span><span class="p">(</span><span class="n">wallPoint</span> <span class="o">+</span> <span class="kt">vec3</span><span class="p">(</span><span class="mi">0</span><span class="p">.,</span> <span class="mi">5</span><span class="p">.,</span> <span class="mi">0</span><span class="p">),</span> <span class="kt">vec3</span><span class="p">(</span><span class="mi">1</span><span class="p">.,</span> <span class="mi">1</span><span class="p">.,</span> <span class="p">.</span><span class="mi">33</span><span class="p">));</span>
    <span class="kt">float</span> <span class="n">floorCube3</span> <span class="o">=</span> <span class="n">fBox</span><span class="p">(</span><span class="n">wallPoint</span> <span class="o">+</span> <span class="kt">vec3</span><span class="p">(</span><span class="mi">2</span><span class="p">.,</span> <span class="mi">5</span><span class="p">.,</span> <span class="p">.</span><span class="mi">33</span><span class="p">),</span> <span class="kt">vec3</span><span class="p">(</span><span class="mi">1</span><span class="p">.,</span> <span class="mi">1</span><span class="p">.,</span> <span class="p">.</span><span class="mi">33</span><span class="p">));</span>
  
    <span class="kt">float</span> <span class="n">floorCube</span> <span class="o">=</span> <span class="n">min</span><span class="p">(</span><span class="n">floorCube3</span><span class="p">,</span> <span class="n">min</span><span class="p">(</span><span class="n">floorCube1</span><span class="p">,</span> <span class="n">floorCube2</span><span class="p">));</span>
  
    <span class="n">Material</span> <span class="n">result</span><span class="p">;</span>

    <span class="k">if</span> <span class="p">(</span><span class="n">wall1</span> <span class="o">&lt;</span> <span class="n">EPSILON</span><span class="p">)</span>
        <span class="n">result</span> <span class="o">=</span> <span class="n">gold</span><span class="p">();</span>
    <span class="k">else</span> <span class="k">if</span> <span class="p">(</span><span class="n">wall2</span> <span class="o">&lt;</span> <span class="n">EPSILON</span><span class="p">)</span>
        <span class="n">result</span> <span class="o">=</span> <span class="n">concrete</span><span class="p">();</span>
    <span class="k">else</span> <span class="k">if</span> <span class="p">(</span><span class="n">wall3</span> <span class="o">&lt;</span> <span class="n">EPSILON</span><span class="p">)</span>
        <span class="n">result</span> <span class="o">=</span> <span class="n">light</span><span class="p">();</span>
    <span class="k">else</span> <span class="k">if</span> <span class="p">(</span><span class="n">wall4</span> <span class="o">&lt;</span> <span class="n">EPSILON</span><span class="p">)</span>
        <span class="n">result</span> <span class="o">=</span> <span class="n">light3</span><span class="p">();</span>
    <span class="k">else</span> <span class="k">if</span> <span class="p">(</span><span class="n">floorCube1</span> <span class="o">&lt;</span> <span class="n">EPSILON</span><span class="p">)</span>
        <span class="n">result</span> <span class="o">=</span> <span class="n">light2</span><span class="p">();</span>
    <span class="k">else</span> <span class="k">if</span> <span class="p">(</span><span class="n">floorCube2</span> <span class="o">&lt;</span> <span class="n">EPSILON</span><span class="p">)</span>
        <span class="n">result</span> <span class="o">=</span> <span class="n">light3</span><span class="p">();</span>
    <span class="k">else</span> <span class="k">if</span> <span class="p">(</span><span class="n">redCarpet</span> <span class="o">&lt;</span> <span class="n">EPSILON</span><span class="p">)</span>
        <span class="n">result</span> <span class="o">=</span> <span class="n">redCarpetMaterial</span><span class="p">();</span>
    <span class="k">else</span> <span class="k">if</span> <span class="p">(</span><span class="n">floorCube3</span> <span class="o">&lt;</span> <span class="n">EPSILON</span><span class="p">)</span>
        <span class="n">result</span> <span class="o">=</span> <span class="n">carLight</span><span class="p">();</span>
    <span class="k">else</span>
        <span class="n">result</span> <span class="o">=</span> <span class="n">skybox</span><span class="p">(</span><span class="n">fGlobalTime</span><span class="p">,</span> <span class="n">ray</span><span class="p">);</span>

    <span class="k">return</span> <span class="n">result</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>

<p>En la función <code class="language-plaintext highlighter-rouge">sdfTagMaterial()</code>, la idea es que cuando el rayo se este enviando,
poder "taggear" el objeto que está codeado en expresiones SDF y devolver en el
<code class="language-plaintext highlighter-rouge">HIT</code> sus propiedades, debajo un ejemplo de algunos "materiales":</p>

<div class="language-glsl highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">[...]</span>
<span class="c1">/////////////</span>
<span class="c1">// Material</span>
<span class="n">Material</span> <span class="nf">material</span><span class="p">(</span><span class="kt">vec3</span> <span class="n">color</span><span class="p">,</span> <span class="kt">vec3</span> <span class="n">emissive</span><span class="p">,</span> <span class="kt">float</span> <span class="n">roughness</span><span class="p">)</span> <span class="p">{</span>
    <span class="n">Material</span> <span class="n">material</span><span class="p">;</span>
    <span class="n">material</span><span class="p">.</span><span class="n">c</span> <span class="o">=</span> <span class="n">color</span><span class="p">;</span>
    <span class="n">material</span><span class="p">.</span><span class="n">e</span> <span class="o">=</span> <span class="n">emissive</span><span class="p">;</span>
    <span class="n">material</span><span class="p">.</span><span class="n">roughness</span> <span class="o">=</span> <span class="n">roughness</span><span class="p">;</span>
    <span class="k">return</span> <span class="n">material</span><span class="p">;</span>
<span class="p">}</span>
<span class="p">[...]</span>
<span class="n">Material</span> <span class="nf">light2</span><span class="p">()</span> <span class="p">{</span> <span class="c1">// esta la luz de la pija </span>
    <span class="k">return</span> <span class="n">material</span><span class="p">(</span>
        <span class="kt">vec3</span><span class="p">(.</span><span class="mi">5</span><span class="p">,</span> <span class="p">.</span><span class="mi">5</span><span class="p">,</span> <span class="p">.</span><span class="mi">5</span><span class="p">),</span>
        <span class="n">syncs</span><span class="p">[</span><span class="mi">4</span><span class="p">]</span> <span class="o">&gt;</span> <span class="mi">16</span><span class="o">*</span><span class="mi">8</span> <span class="o">?</span> <span class="kt">vec3</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="mi">1</span> <span class="o">-</span> <span class="n">mod</span><span class="p">(</span><span class="n">syncs</span><span class="p">[</span><span class="mi">4</span><span class="p">]</span> <span class="o">/</span> <span class="mi">4</span><span class="p">,</span> <span class="mi">1</span><span class="p">),</span> <span class="mi">0</span><span class="p">)</span> <span class="o">:</span> <span class="kt">vec3</span><span class="p">(</span><span class="mi">0</span><span class="p">),</span>
        <span class="mi">0</span><span class="p">.</span><span class="mi">55</span><span class="c1">// + sin(fGlobalTime*20.)</span>
    <span class="p">);</span>
<span class="p">}</span>

<span class="n">Material</span> <span class="nf">carLight</span><span class="p">()</span> <span class="p">{</span>
    <span class="k">return</span> <span class="n">material</span><span class="p">(</span>
        <span class="kt">vec3</span><span class="p">(</span><span class="mi">1</span><span class="p">,</span> <span class="mi">1</span><span class="p">,</span> <span class="mi">1</span><span class="p">),</span>
        <span class="n">syncs</span><span class="p">[</span><span class="mi">4</span><span class="p">]</span> <span class="o">&gt;</span> <span class="mi">32</span><span class="o">*</span><span class="mi">8</span> 
            <span class="o">?</span> <span class="kt">vec3</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">)</span> <span class="o">*</span> <span class="p">(.</span><span class="mi">5</span> <span class="o">+</span> <span class="p">.</span><span class="mi">5</span> <span class="o">*</span> <span class="n">sin</span><span class="p">(</span><span class="n">fGlobalTime</span><span class="o">*</span><span class="mi">2</span><span class="p">.))</span> <span class="o">+</span> <span class="kt">vec3</span><span class="p">(</span><span class="mi">0</span><span class="p">.</span><span class="mi">2</span><span class="p">,</span><span class="mi">0</span><span class="p">.</span><span class="mi">2</span><span class="p">,</span> <span class="mi">1</span> <span class="o">-</span> <span class="n">mod</span><span class="p">(</span><span class="n">syncs</span><span class="p">[</span><span class="mi">4</span><span class="p">]</span> <span class="o">/</span> <span class="mi">4</span><span class="p">,</span> <span class="mi">1</span><span class="p">))</span>
             <span class="o">:</span> <span class="kt">vec3</span><span class="p">(</span><span class="mi">0</span><span class="p">),</span>
        <span class="mi">0</span><span class="p">.</span><span class="mi">55</span>
    <span class="p">);</span>
<span class="p">}</span>

<span class="n">Material</span> <span class="nf">light3</span><span class="p">()</span> <span class="p">{</span>
    <span class="k">return</span> <span class="n">material</span><span class="p">(</span>
        <span class="kt">vec3</span><span class="p">(.</span><span class="mi">5</span><span class="p">,</span> <span class="p">.</span><span class="mi">5</span><span class="p">,</span> <span class="p">.</span><span class="mi">5</span><span class="p">),</span>
        <span class="n">syncs</span><span class="p">[</span><span class="mi">7</span><span class="p">]</span> <span class="o">&gt;</span> <span class="mi">16</span><span class="o">*</span><span class="mi">1</span> <span class="o">?</span> <span class="kt">vec3</span><span class="p">(</span><span class="mi">1</span><span class="p">,</span> <span class="mi">1</span><span class="p">,</span> <span class="mi">1</span><span class="p">)</span> <span class="o">*</span> <span class="p">(.</span><span class="mi">5</span> <span class="o">+</span> <span class="p">.</span><span class="mi">5</span> <span class="o">*</span> <span class="n">sin</span><span class="p">(</span><span class="n">fGlobalTime</span><span class="o">*</span><span class="mi">2</span><span class="p">.))</span> <span class="o">:</span> <span class="kt">vec3</span><span class="p">(</span><span class="mi">0</span><span class="p">),</span>
        <span class="mi">0</span><span class="p">.</span><span class="mi">95</span><span class="c1">// + sin(fGlobalTime*20.)</span>
    <span class="p">);</span>
<span class="p">}</span>
<span class="p">[...]</span>
</code></pre></div></div>

<h2 id="kifs">KIFS</h2>

<p>Un <code class="language-plaintext highlighter-rouge">KIFS</code> es un Kaleidoscopic <a href="https://en.wikipedia.org/wiki/Iterated_function_system">Iterated function system</a>.</p>

<div class="language-glsl highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">[...]</span>

<span class="kt">vec2</span> <span class="nf">repeat</span><span class="p">(</span><span class="kt">vec2</span> <span class="n">p</span><span class="p">,</span> <span class="kt">vec2</span> <span class="n">s</span><span class="p">)</span> <span class="p">{</span>
  <span class="k">return</span> <span class="p">(</span><span class="n">fract</span><span class="p">(</span><span class="n">p</span><span class="o">/</span><span class="n">s</span><span class="o">-</span><span class="mi">0</span><span class="p">.</span><span class="mi">5</span><span class="p">)</span><span class="o">-</span><span class="mi">0</span><span class="p">.</span><span class="mi">5</span><span class="p">)</span><span class="o">*</span><span class="n">s</span><span class="p">;</span>  
<span class="p">}</span>

<span class="p">[...]</span>

<span class="kt">vec3</span> <span class="nf">kifs</span><span class="p">(</span><span class="kt">vec3</span> <span class="n">p</span><span class="p">,</span> <span class="kt">float</span> <span class="n">t</span><span class="p">)</span> <span class="p">{</span>
  
  <span class="n">p</span><span class="p">.</span><span class="n">xz</span> <span class="o">=</span> <span class="n">repeat</span><span class="p">(</span><span class="n">p</span><span class="p">.</span><span class="n">xz</span><span class="p">,</span> <span class="kt">vec2</span><span class="p">(</span><span class="mi">28</span><span class="p">));</span>
  <span class="n">p</span><span class="p">.</span><span class="n">xz</span> <span class="o">=</span> <span class="n">abs</span><span class="p">(</span><span class="n">p</span><span class="p">.</span><span class="n">xz</span><span class="p">);</span>
  
  <span class="kt">vec2</span> <span class="n">s</span><span class="o">=</span><span class="kt">vec2</span><span class="p">(</span><span class="mi">10</span><span class="p">,</span><span class="mi">7</span><span class="p">)</span> <span class="o">*</span> <span class="mi">0</span><span class="p">.</span><span class="mi">6</span><span class="p">;</span>
  <span class="k">for</span><span class="p">(</span><span class="kt">int</span> <span class="n">i</span><span class="o">=</span><span class="mi">0</span><span class="p">;</span> <span class="n">i</span><span class="o">&lt;</span><span class="mi">5</span><span class="p">;</span> <span class="o">++</span><span class="n">i</span><span class="p">)</span> <span class="p">{</span>
    <span class="n">p</span><span class="p">.</span><span class="n">xz</span> <span class="o">*=</span> <span class="n">rot</span><span class="p">(</span><span class="n">t</span><span class="p">);</span>
    <span class="n">p</span><span class="p">.</span><span class="n">xz</span> <span class="o">=</span> <span class="n">abs</span><span class="p">(</span><span class="n">p</span><span class="p">.</span><span class="n">xz</span><span class="p">)</span> <span class="o">-</span> <span class="n">s</span><span class="p">;</span>
    <span class="n">p</span><span class="p">.</span><span class="n">y</span> <span class="o">+=</span> <span class="mi">0</span><span class="p">.</span><span class="mi">1</span><span class="o">*</span><span class="n">abs</span><span class="p">(</span><span class="n">p</span><span class="p">.</span><span class="n">z</span><span class="p">);</span>
    <span class="n">s</span><span class="o">*=</span><span class="kt">vec2</span><span class="p">(</span><span class="mi">0</span><span class="p">.</span><span class="mi">7</span><span class="p">,</span><span class="mi">0</span><span class="p">.</span><span class="mi">5</span><span class="p">);</span>
  <span class="p">}</span>
  
  <span class="k">return</span> <span class="n">p</span><span class="p">;</span>
<span class="p">}</span>

<span class="p">[...]</span>

<span class="kt">float</span> <span class="nf">sdfScene</span><span class="p">(</span><span class="kt">vec3</span> <span class="n">point</span><span class="p">)</span> <span class="p">{</span>
    <span class="p">[...]</span>
    <span class="c1">//Walls</span>
    <span class="kt">vec3</span> <span class="n">wallPoint</span> <span class="o">=</span> <span class="n">kifs</span><span class="p">(</span><span class="n">point</span><span class="p">,</span> <span class="n">sin</span><span class="p">(</span><span class="n">fGlobalTime</span><span class="o">/</span><span class="mi">200</span><span class="p">)</span> <span class="o">+</span> <span class="mi">1</span><span class="p">.</span><span class="mi">3</span><span class="p">);</span>  
    <span class="c1">//wallPoint = point;</span>

    <span class="c1">// red carpet </span>
    <span class="kt">float</span> <span class="n">redCarpet</span> <span class="o">=</span> <span class="n">fBox</span><span class="p">(</span><span class="n">wallPoint</span> <span class="o">+</span> <span class="kt">vec3</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="mi">5</span><span class="p">,</span> <span class="mi">0</span><span class="p">),</span> <span class="kt">vec3</span><span class="p">(</span><span class="mi">100</span><span class="p">,</span> <span class="mi">0</span><span class="p">.</span><span class="mo">01</span><span class="p">,</span> <span class="mi">100</span><span class="p">));</span>
    <span class="p">[...]</span>
<span class="p">}</span>


</code></pre></div></div>

<p>Como en toda escena <a href="https://en.wikipedia.org/wiki/Signed_distance_function">SDF</a>, en esta función llamada <code class="language-plaintext highlighter-rouge">sdfScene(vec3 point)</code> y que a veces en muchos shaders de shadertoy por ejemplo la llaman <code class="language-plaintext highlighter-rouge">map(vec3 point)</code> le pasamos un punto en el espacio y como se puede ver en la variable <code class="language-plaintext highlighter-rouge">wallPoint</code> foldeamos toda la escena sobre un espacio 3d en los <strong>ejes X y Z</strong> para agregar complejidad "caleidoscópica" a la geometría hecha con <a href="https://en.wikipedia.org/wiki/Signed_distance_function">SDF</a>.</p>

<p>Un ejemplo de esto se puede ver en el siguiente video que 
está aquí debajo.</p>

<center>
<iframe width="560" height="315" src="https://www.youtube.com/embed/KDhh2Mf2fGE?si=hRONH6BWMkIarTHY" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen=""></iframe>
</center>

<p>En el video, lo que hago es comentar la función <code class="language-plaintext highlighter-rouge">kifs</code> y pasarle 
directamente el punto que recibo de la escena SDF, donde se puede 
ver la geometría original. 
Después descomento esa línea para aplicarle la función <code class="language-plaintext highlighter-rouge">kifs</code> de nuevo.
Se puede ver como toda la geometría rota en el infinito. 
Los números y vectores usados dentro de la función <code class="language-plaintext highlighter-rouge">kifs</code> 
son a base de prueba y error y algunos valores son choreados
de otras implementaciones que encontre en shadertoy.</p>

<h2 id="strip-preventer-vs-error-120">Strip Preventer vs Error 120</h2>

<p>Cada tanto, cuando compilaba la intro y la ejecutaba saltaba
un errorlevel <code class="language-plaintext highlighter-rouge">120</code>.
Siendo que no había (o no supe encontrar en ese momento) información
sobre este código de error, <a href="https://linktr.ee/rowboto">Row</a> se quedó mirando un el código
del shader por largo rato hasta que se le ocurrió que quizás en 
algún momento del proceso de building algunas variables se 
estaban <code class="language-plaintext highlighter-rouge">strippeando</code> y que cuando el shader quería ser ejecutado
por la GPU, la intro volaba en mil pedazos.
¿La solución? Declarar las variables al principio del <code class="language-plaintext highlighter-rouge">main()</code> de
la siguiente manera:</p>

<div class="language-glsl highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kt">void</span> <span class="nf">main</span><span class="p">()</span>
<span class="p">{</span>
    <span class="p">[...]</span>
    <span class="kt">float</span> <span class="n">globalTime</span> <span class="o">=</span> <span class="n">fGlobalTime</span><span class="p">;</span>
    <span class="kt">float</span> <span class="n">stripPreventer</span> <span class="o">=</span> <span class="n">beat</span> <span class="o">+</span> <span class="n">pattern</span> <span class="o">+</span> <span class="n">part</span> <span class="o">+</span> <span class="n">partBeat</span> <span class="o">+</span> <span class="n">partIndex</span> <span class="o">+</span> <span class="n">globalTime</span><span class="p">;</span>
    <span class="p">[...]</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Como <code class="language-plaintext highlighter-rouge">fGlobalTime</code> y la declaración <code class="language-plaintext highlighter-rouge">globalTime</code> son variables 
globales que cambian todo el tiempo en la ejecución del shader,
lo que hacemos es forzar el cálculo de todas las variables de 
que se borraban en el proceso de build, aunque no usemos la 
variable <code class="language-plaintext highlighter-rouge">stripPreventer</code> para ningún otro propósito.
Me encontré con este problema en <code class="language-plaintext highlighter-rouge">GCC</code> varias veces, 
donde el compilador en este caso, decide que sirve y que no y me sacaba
pedazos de código enteros para optimizarlos, cuando en mi
caso de uso realmente necesitaba que no me optimice nada.
Que lo hayamos tenido en un shader, con ese errorlevel feo,
fue cuando menos sorprendente y desconcertante. :D</p>

<h2 id="uniforms-y-sointu">Uniforms y Sointu</h2>

<p>Finalmente, al usar <a href="https://github.com/vsariola/sointu">sointu</a> para hacer la música, tenemos 
que aprovechar una interfaz que nos da para transformar cada uno
de los tracks en información util que nos sirve para <strong>sincronizar
los efectos de la intro</strong>.
Por ejemplo, el kick o el hihat, al pasarlos a través de una
<code class="language-plaintext highlighter-rouge">uniform</code> nos da cierta informacion para modificar las propiedades
de del <code class="language-plaintext highlighter-rouge">emissive</code> de los materiales; en nuestro <code class="language-plaintext highlighter-rouge">main.c</code>:</p>

<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// main.c </span>
<span class="p">[...]</span>
<span class="cp">#pragma data_seg(".sointu_buffer")
</span><span class="n">SUsample</span> <span class="n">sointu_buffer</span><span class="p">[</span><span class="n">SU_BUFFER_LENGTH</span><span class="p">];</span>
<span class="kt">float</span> <span class="n">syncBuf</span><span class="p">[(</span><span class="n">SYNC_DELAY</span> <span class="o">&gt;&gt;</span> <span class="mi">8</span><span class="p">)</span> <span class="o">*</span> <span class="n">SU_NUMSYNCS</span> <span class="o">+</span> <span class="n">SU_SYNCBUFFER_LENGTH</span><span class="p">];</span>
<span class="n">HWAVEOUT</span> <span class="n">hWaveOut</span><span class="p">;</span>
<span class="p">[...]</span>

<span class="kt">void</span> <span class="nf">entrypoint</span><span class="p">(</span><span class="kt">void</span><span class="p">)</span>
<span class="p">{</span>
<span class="p">[...]</span>

    <span class="n">waveOutGetPosition</span><span class="p">(</span><span class="n">hWaveOut</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">MMTime</span><span class="p">,</span> <span class="k">sizeof</span><span class="p">(</span><span class="n">MMTIME</span><span class="p">));</span>

    <span class="p">((</span><span class="n">PFNGLUNIFORM1FVPROC</span><span class="p">)</span><span class="n">wglGetProcAddress</span><span class="p">(</span><span class="s">"glUniform1fv"</span><span class="p">))</span>
        <span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="mi">8</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">syncBuf</span><span class="p">[((</span><span class="n">MMTime</span><span class="p">.</span><span class="n">u</span><span class="p">.</span><span class="n">sample</span> <span class="o">+</span> <span class="n">SYNC_DELAY</span><span class="p">)</span> <span class="o">&gt;&gt;</span> <span class="mi">8</span><span class="p">)</span> <span class="o">*</span> <span class="n">SU_NUMSYNCS</span><span class="p">]);</span>

<span class="p">[...]</span>
<span class="p">}</span>

</code></pre></div></div>

<p>Y desde nuestro shader, recibimos las uniforms de la siguiente
manera:</p>

<div class="language-glsl highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">uniform</span> <span class="kt">float</span> <span class="n">syncs</span><span class="p">[</span><span class="mi">8</span><span class="p">];</span>

<span class="p">[</span> <span class="p">...</span> <span class="p">]</span>

<span class="n">Material</span> <span class="nf">light3</span><span class="p">()</span> <span class="p">{</span>
    <span class="k">return</span> <span class="n">material</span><span class="p">(</span>
        <span class="kt">vec3</span><span class="p">(.</span><span class="mi">5</span><span class="p">,</span> <span class="p">.</span><span class="mi">5</span><span class="p">,</span> <span class="p">.</span><span class="mi">5</span><span class="p">),</span>
        <span class="n">syncs</span><span class="p">[</span><span class="mi">7</span><span class="p">]</span> <span class="o">&gt;</span> <span class="mi">16</span><span class="o">*</span><span class="mi">1</span> <span class="o">?</span> <span class="kt">vec3</span><span class="p">(</span><span class="mi">1</span><span class="p">,</span> <span class="mi">1</span><span class="p">,</span> <span class="mi">1</span><span class="p">)</span> <span class="o">*</span> <span class="p">(.</span><span class="mi">5</span> <span class="o">+</span> <span class="p">.</span><span class="mi">5</span> <span class="o">*</span> <span class="n">sin</span><span class="p">(</span><span class="n">fGlobalTime</span><span class="o">*</span><span class="mi">2</span><span class="p">.))</span> <span class="o">:</span> <span class="kt">vec3</span><span class="p">(</span><span class="mi">0</span><span class="p">),</span>
        <span class="mi">0</span><span class="p">.</span><span class="mi">95</span><span class="c1">// + sin(fGlobalTime*20.)</span>
    <span class="p">);</span>
<span class="p">}</span>

<span class="p">[</span> <span class="p">...</span> <span class="p">]</span>

<span class="kt">void</span> <span class="nf">main</span><span class="p">()</span>
<span class="p">{</span>
    <span class="c1">// sointu sync variables</span>
    <span class="kt">float</span> <span class="n">beat</span> <span class="o">=</span> <span class="n">syncs</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span><span class="o">/</span><span class="mi">4</span><span class="p">;</span>
    <span class="kt">float</span> <span class="n">pattern</span> <span class="o">=</span> <span class="n">beat</span><span class="o">/</span><span class="mi">4</span><span class="p">;</span>
    <span class="kt">float</span> <span class="n">part</span> <span class="o">=</span> <span class="n">pattern</span><span class="o">/</span><span class="mi">2</span><span class="p">;</span>
    <span class="kt">float</span> <span class="n">partBeat</span> <span class="o">=</span> <span class="n">mod</span><span class="p">(</span><span class="n">beat</span><span class="p">,</span><span class="mi">8</span><span class="p">);</span>
    <span class="kt">float</span> <span class="n">partIndex</span> <span class="o">=</span> <span class="kt">int</span><span class="p">(</span><span class="n">part</span><span class="p">);</span>
    <span class="kt">float</span> <span class="n">globalTime</span> <span class="o">=</span> <span class="n">fGlobalTime</span><span class="p">;</span>
<span class="p">[...]</span>

</code></pre></div></div>

<h1 id="conclusión">Conclusión</h1>

<p>Hacía basante tiempo que no me divertía programando. 
En el proceso, logramos constuir un <strong>framework</strong> para 
futuras producciones y aún queda margen para mejoras 
y reducir el tamaño de las próximas intros todavía más,
y quisá meter más contenido, efectos o música.</p>

<h1 id="referencias">Referencias</h1>

<ul>
  <li><a href="https://behel.it">BEHELiT</a></li>
  <li><a href="https://files.scene.org/view/parties/2024/flashparty24/4k_and_256b_intro/behelit-iss.zip">Intro 4k</a></li>
  <li><a href="https://www.pouet.net/prod.php?which=98296">primer puesto</a></li>
  <li><a href="https://www.hopp.bio/madbit">Madbit</a></li>
  <li><a href="https://linktr.ee/rowboto">Row</a></li>
  <li><a href="https://www.pouet.net/prod.php?which=98277">Man in the Vox</a></li>
  <li><a href="https://flashparty.rebelion.digital/index.php/es/compos-reglas">Compo</a></li>
  <li><a href="https://flashparty.rebelion.digital/index.php/es/">Flashparty</a></li>
  <li><a href="https://en.wikipedia.org/wiki/Demoscene">Demoscene</a></li>
  <li><a href="https://github.com/runestubbe/Crinkler">Crinkler</a></li>
  <li><a href="https://github.com/laurentlb/shader-minifier">Shader Minifier</a></li>
  <li><a href="https://github.com/Gargaj/Bonzomatic">bonzomatic</a></li>
  <li><a href="https://github.com/vsariola/sointu">sointu</a></li>
  <li><a href="https://en.wikipedia.org/wiki/Music_tracker">Tracker</a></li>
  <li><a href="https://en.wikipedia.org/wiki/Iterated_function_system">Iterated function system</a></li>
  <li><a href="https://en.wikipedia.org/wiki/Signed_distance_function">SDF</a></li>
</ul>]]></content><author><name>toshi</name></author><category term="demoscene" /><summary type="html"><![CDATA[¿Cómo hicimos la intro ganadora de la categoría Intro 4k de la Flashparty 2024? Acá te lo explico.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://0x705h.com/assets/images/iSS-4k-intro/sdf-geometry-and-tag-material.PNG" /><media:content medium="image" url="https://0x705h.com/assets/images/iSS-4k-intro/sdf-geometry-and-tag-material.PNG" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry xml:lang="es"><title type="html">Compilando MS-DOS 4.0</title><link href="https://0x705h.com/de/compilando-msdos-4.0" rel="alternate" type="text/html" title="Compilando MS-DOS 4.0" /><published>2024-04-28T11:37:00+00:00</published><updated>2024-04-28T11:37:00+00:00</updated><id>https://0x705h.com/compilando-msdos-4.0</id><content type="html" xml:base="https://0x705h.com/compilando-msdos-4.0"><![CDATA[<p>¡El código fuente de <a href="https://en.wikipedia.org/wiki/MS-DOS#MS-DOS_4.0_/_MS-DOS_4.x">MS-DOS 4.0</a> fue liberado en <a href="https://github.com/microsoft/MS-DOS">su cuenta de GitHub</a>!
Ya había liberado anteriormente la versión 1.25 y la versión 2.0 y ahora le tocó a la versión 4.0.</p>

<h1 id="preparando-el-código">Preparando el Código</h1>

<p>Antes de escribir este artículo me encontré con algunos problemas.
Todo el proceso de compilación lo quise hacer con la versión <code class="language-plaintext highlighter-rouge">MSDOS 3.31</code>,
la más inmediatamente cercana a la 4.0, con la idea de tratar de hacerlo
lo más fiel posible a lo "que hubiera sido en esa época" pero con la última
version de DOS antes de la que fue liberada, simplemente no es posible 
compilarlo.
El problema que estuvo sucediendo hasta que me di cuenta es que las variables de entorno y los 
"paths" tienen un espacio limitado, y algunas rutas son demasiado largas para que 
el compilador pueda procesarlas a través del sistema operativo. Por esto mismo,
en la mitad de la compilación con la versión MSDOS 3.31 del código fuente, 
<code class="language-plaintext highlighter-rouge">nmake</code> y <code class="language-plaintext highlighter-rouge">cc</code> no encuentran los <code class="language-plaintext highlighter-rouge">INCludes</code> en el path. 
Por esto mismo, vamos a compilar MSDOS 4 con la version MSDOS 5 pelada.</p>

<p>También encontré problemas de formato. 
Si agarramos cualquier archivo, nos vamos a dar cuenta que hay caracteres 
UTF-8 y LF cuando en verdad deberían ser archivos ASCII CRLF 
Lo primero que tenemos que hacer con el código es hacer estas conversiones
de formato.</p>

<p><img src="/assets/images/compile-msdos4.0/crlf.png" alt="crlf" class="img-responsive" /></p>

<p>Como se puede ver en la imagen arriba, no hay un <code class="language-plaintext highlighter-rouge">0x0d</code> seguido de un <code class="language-plaintext highlighter-rouge">0x0a</code> (CRLF).
También hay caracteres en varios archivos que parecerian ser UTF-8 como 
la siguiente secuencia de bytes: <code class="language-plaintext highlighter-rouge">ef bf bd</code></p>

<p>Vamos a reemplazar estos caracteres con el caracter <code class="language-plaintext highlighter-rouge">*</code>.
Sin las siguientes conversiones que se pueden ver en los dos comandos de abajo
no es posible compilar el código fuente, ya que el compilador provisto por 
Microsoft en este release, no pueden interpretar estos caracteres.</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span>find <span class="nt">-iname</span> <span class="s1">'*.bat'</span> <span class="nt">-o</span> <span class="nt">-iname</span> <span class="s1">'*.asm'</span> <span class="nt">-o</span> <span class="nt">-iname</span> <span class="s1">'*.skl'</span> 
  <span class="nt">-o</span> <span class="nt">-iname</span> <span class="s1">'zero.dat'</span> <span class="nt">-o</span> <span class="nt">-iname</span> <span class="s1">'locscr'</span> | xargs unix2dos <span class="nt">-f</span>

<span class="nv">$ </span>find <span class="nb">.</span> <span class="nt">-type</span> f <span class="se">\(</span> <span class="nt">-name</span> <span class="s2">"GETMSG.ASM"</span> <span class="nt">-o</span> <span class="nt">-name</span> <span class="s2">"SELECT2.ASM"</span> 
  <span class="nt">-o</span> <span class="nt">-name</span> <span class="s2">"USA.INF"</span> <span class="se">\)</span> <span class="nt">-exec</span> <span class="nb">sed</span> <span class="nt">-i</span> <span class="nt">-re</span> <span class="s1">'s/\xef\xbf\xbd|\xc4\xbf|\xc4\xb4/\*/g'</span> <span class="o">{}</span> <span class="se">\;</span>
</code></pre></div></div>

<p><img src="/assets/images/compile-msdos4.0/crlf-sed.png" alt="crlf-sed" class="img-responsive" /></p>

<p>Opcionalmente, podemos modificar en el archivo <code class="language-plaintext highlighter-rouge">BIOS/MSLOAD.ASM</code>, la variable <code class="language-plaintext highlighter-rouge">Mystacks</code>
de 64 words a 128 words:</p>

<div class="language-nasm highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">[</span><span class="nf">...</span><span class="p">]</span>
<span class="nl">start:</span>
        <span class="nf">jmp</span> <span class="nv">Save_Input_Values</span>
<span class="nf">SYS_Version</span>     <span class="nv">dw</span>      <span class="nv">EXPECTED_VERSION</span> <span class="c1">;AN001; From VERSIONA.INC file</span>
<span class="nf">Mystacks</span>        <span class="nv">dw</span>      <span class="mi">128</span> <span class="nv">dup</span> <span class="p">(</span><span class="mi">0</span><span class="p">)</span>      <span class="c1">;AN000; local stack</span>
<span class="nf">MyStack_ptr</span>     <span class="nv">label</span>   <span class="kt">word</span>
<span class="p">[</span><span class="nf">...</span><span class="p">]</span>
</code></pre></div></div>

<p>De esta manera, vamos a permitir ejecutar MS-DOS en discos rígidos manejados 
por <code class="language-plaintext highlighter-rouge">QEMU</code> o similares.</p>

<p>Finalmente solo porque podemos, vamos a cambiar el mensaje de copyright 
del <code class="language-plaintext highlighter-rouge">command.com</code> en el archivo <code class="language-plaintext highlighter-rouge">MESSAGES/USA-MS.MSG:175</code>:</p>

<div class="language-nasm highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">[</span><span class="nf">...</span><span class="p">]</span>
<span class="err">0463</span> <span class="nf">U</span> <span class="mi">0000</span> <span class="err">"</span><span class="nv">Out</span> <span class="nv">of</span> <span class="nv">environment</span> <span class="nb">sp</span><span class="nv">ace</span><span class="s">",CR,LF
0464 U 0001 CR,LF,CR,LF,"</span><span class="nv">Toshisoft</span><span class="p">(</span><span class="nv">R</span><span class="p">)</span> <span class="nv">MS</span><span class="o">-</span><span class="nv">DOS</span><span class="p">(</span><span class="nv">R</span><span class="p">)</span> <span class="nv">Version</span> <span class="mf">6.66</span><span class="s">",CR,LF
        "</span>             <span class="p">(</span><span class="nv">C</span><span class="p">)</span><span class="nv">Copyleft</span> <span class="mh">0x705</span><span class="nv">h.com</span> <span class="mi">2024</span><span class="s">",CR,LF
0465 U 0000 "</span><span class="nb">Sp</span><span class="nv">ecified</span> <span class="nv">COMMAND</span> <span class="nv">search</span> <span class="nb">di</span><span class="nv">rectory</span> <span class="nv">bad</span><span class="s">",CR,LF
0466 U 0000 "</span><span class="nb">Sp</span><span class="nv">ecified</span> <span class="nv">COMMAND</span> <span class="nv">search</span> <span class="nb">di</span><span class="nv">rectory</span> <span class="nv">bad</span> <span class="nv">access</span> <span class="nv">denied</span><span class="err">"</span><span class="p">,</span><span class="nv">CR</span><span class="p">,</span><span class="nv">LF</span>
<span class="p">[</span><span class="nf">...</span><span class="p">]</span>
</code></pre></div></div>

<h1 id="metamos-todo-en-una-486-con-pcem">Metamos todo en una 486 (con PCem)</h1>

<p>La máquina en PCem que configuré para compilar <code class="language-plaintext highlighter-rouge">MSDOS 4.0</code> tiene las 
siguientes características:</p>

<table>
  <thead>
    <tr>
      <th> </th>
      <th> </th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>Machine</td>
      <td>486 Award SiS 496/497</td>
    </tr>
    <tr>
      <td>CPU</td>
      <td>Intel i486DX2/66</td>
    </tr>
    <tr>
      <td>Dynamic Recompiler</td>
      <td>On</td>
    </tr>
    <tr>
      <td>Memory</td>
      <td>16 MB</td>
    </tr>
    <tr>
      <td>Video</td>
      <td>S3 ViRGE/DX</td>
    </tr>
    <tr>
      <td>Storage</td>
      <td>250 MB</td>
    </tr>
    <tr>
      <td>Floppy Drives</td>
      <td>3.5" 1.44M</td>
    </tr>
  </tbody>
</table>

<p>Recordar configurar el disco rígido y el floppy en el BIOS así como también
la secuencia de booteo a <code class="language-plaintext highlighter-rouge">A,C</code> – primero el floppy, luego el HDD.</p>

<p>Luego de preparar la máquina, instalamos desde un floppy con el instalador de 
MSDOS 5 al disco C.
Adicionalmente también preparamos un floppy vacío:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <span class="nv">$ </span><span class="nb">dd </span><span class="k">if</span><span class="o">=</span>/dev/zero <span class="nv">of</span><span class="o">=</span>f144.img <span class="nv">count</span><span class="o">=</span>1440 <span class="nv">bs</span><span class="o">=</span>1k
1440+0 records <span class="k">in
</span>1440+0 records out
1474560 bytes <span class="o">(</span>1,5 MB, 1,4 MiB<span class="o">)</span> copied, 0,0028958 s, 509 MB/s
</code></pre></div></div>

<p>Ya con nuestro MSDOS 5 instalado, insertamos el floppy y hacemos que este floppy 
sea booteable. Después vamos a sobreescribir los archivos de sistema <code class="language-plaintext highlighter-rouge">IO.SYS</code>
y <code class="language-plaintext highlighter-rouge">MSDOS.SYS</code>, pero hacemos esto porque necesitamos tener un <code class="language-plaintext highlighter-rouge">Master Boot Record (MBR)</code> 
disponible en ese floppy, para hacerlo booteable.</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>C:<span class="se">\&gt;</span> format a: /s
</code></pre></div></div>

<p>Ahora que tenemos todo listo, lo que hacemos es <code class="language-plaintext highlighter-rouge">APAGAR</code> la máquina en PCem
completamente. Esto es importante porque montar una imagen de disco mientras
una VM en PCem está corriendo, tiene comportamiento indefinido.</p>

<p>Una vez la máquina está apagada, montamos el disco y copiamos todo el source
de MSDOS 4.0 a nuestra imagen:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span>mount <span class="nt">-o</span> loop,offset<span class="o">=</span><span class="si">$(</span><span class="nb">echo</span> <span class="s2">"63*512"</span> |bc<span class="si">)</span> <span class="nt">-t</span> msdos 486-compiling-msdos.img /mnt
<span class="nv">$ </span><span class="nb">cd</span> /mnt/src
<span class="nv">$ </span><span class="nb">cp</span> <span class="nt">-r</span> /home/toshi/dev/MS-DOS/v4.0/src/ <span class="nb">.</span>
<span class="nv">$ </span>umount /mnt
</code></pre></div></div>

<h1 id="compilando">Compilando!</h1>

<p>Arrancamos nuestra máquina en PCem de nuevo y editamos con el <code class="language-plaintext highlighter-rouge">EDIT</code>
el archivo <code class="language-plaintext highlighter-rouge">c:\src\setenv.bat</code> modificando <code class="language-plaintext highlighter-rouge">BAKROOT</code>, <code class="language-plaintext highlighter-rouge">LIB</code> y <code class="language-plaintext highlighter-rouge">INCLUDE</code> 
de la siguiente manera:</p>

<p><img src="/assets/images/compile-msdos4.0/setenv.bat.png" alt="setenv" class="img-responsive" /></p>

<p>Una vez que hicimos esta modificación, ejecutamos los siguientes 
comandos y esperamos que el sistema operativo termine de compilar.</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>c:<span class="se">\S</span>RC&gt; setenv
c:<span class="se">\S</span>RC&gt; nmake
</code></pre></div></div>

<p>Al terminar de compilar, hay un archivo .BAT que copia todos los ejecutables
de la compilación a un directorio, procedemos a ejecutarlo y dar 
por finalizada esta parte del proceso:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>C:<span class="se">\S</span>RC&gt; <span class="nb">mkdir </span>bin
C:<span class="se">\S</span>RC&gt; cpy.bat bin
</code></pre></div></div>

<h1 id="booteando-la-versión-compilada">Booteando la Versión Compilada</h1>

<p>Una vez que terminó de compilar, insertamos el floppy que habíamos preparado
anteriormente y eliminamos los archivos <code class="language-plaintext highlighter-rouge">MSDOS.SYS</code> e <code class="language-plaintext highlighter-rouge">IO.SYS</code> y copiamos
el sistema operativo compilado de la siguiente manera:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>A:<span class="se">\&gt;</span> attrib <span class="nt">-S</span> <span class="nt">-H</span> <span class="nt">-R</span> IO.SYS
A:<span class="se">\&gt;</span> attrib <span class="nt">-S</span> <span class="nt">-H</span> <span class="nt">-R</span> MSDOS.SYS
A:<span class="se">\&gt;</span> del IO.SYS
A:<span class="se">\&gt;</span> del MSDOS.SYS
A:<span class="se">\&gt;</span> C:
C:<span class="se">\&gt;</span> CD SRC<span class="se">\B</span>IN
C:<span class="se">\S</span>RC<span class="se">\B</span>IN&gt; copy <span class="k">*</span>.<span class="k">*</span> A:
</code></pre></div></div>

<p>Y finalmente, rebooteamos desde nuestro floppy:</p>

<p><img src="/assets/images/compile-msdos4.0/msdos-4.0-booted.png" alt="msdos-4.0-booted" class="img-responsive" /></p>

<p>Ahora, que me es posible compilar el sistema operativo, y potencialmente debuggearlo, quisá pueda avanzar más aún
en un proyecto que vengo laburando hace bastante, <a href="https://social.0x705h.com/@toshi/109897260564832809">Jakiar el Truco Arbiser</a>. :D</p>

<h1 id="referencias">Referencias</h1>

<ul>
  <li><a href="https://en.wikipedia.org/wiki/MS-DOS#MS-DOS_4.0_/_MS-DOS_4.x">MS-DOS 4.0</a></li>
  <li><a href="https://github.com/microsoft/MS-DOS">MS-DOS 4.0 Source Code</a></li>
</ul>]]></content><author><name>toshi</name></author><category term="retro" /><summary type="html"><![CDATA[Microsoft liberó el código del sistema operativo MS-DOS. ¡Vamos a compilarlo!]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://0x705h.com/assets/images/compile-msdos4.0/msdos-4.0-booted.png" /><media:content medium="image" url="https://0x705h.com/assets/images/compile-msdos4.0/msdos-4.0-booted.png" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry xml:lang="es"><title type="html">Conectándose a BBSs como en 1997</title><link href="https://0x705h.com/de/conectandose-a-bbs-como-en-1997" rel="alternate" type="text/html" title="Conectándose a BBSs como en 1997" /><published>2023-06-05T11:37:00+00:00</published><updated>2023-06-05T11:37:00+00:00</updated><id>https://0x705h.com/conectandose-a-bbs-como-en-1997</id><content type="html" xml:base="https://0x705h.com/conectandose-a-bbs-como-en-1997"><![CDATA[<h1 id="la-pc">La PC</h1>

<p>¿Tiene alguna PC que poseas un conector serial <code class="language-plaintext highlighter-rouge">RS232/DE-9</code>? 
Hace un tiempo vengo armando con la ayuda de algunos amigos y cybercirujeando la computadora que me acompañó
desde 1997 a alrededor del 2005. 
En mi caso, estas son las especificaciones de mi PC retro hasta hoy:</p>

<table>
  <thead>
    <tr>
      <th style="text-align: right">Especificaciones</th>
      <th style="text-align: left"> </th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td style="text-align: right">Tipo</td>
      <td style="text-align: left">Personal Computer</td>
    </tr>
    <tr>
      <td style="text-align: right">CPU</td>
      <td style="text-align: left">Penitum MMX 233mhz</td>
    </tr>
    <tr>
      <td style="text-align: right">RAM</td>
      <td style="text-align: left">256 MB (1997: 16MB)</td>
    </tr>
    <tr>
      <td style="text-align: right">HDD</td>
      <td style="text-align: left">Compact Flash 32GB (1997: 2GB)</td>
    </tr>
    <tr>
      <td style="text-align: right">RED</td>
      <td style="text-align: left">3COM, RJ45 - La genérica que anda en cualquier lado</td>
    </tr>
    <tr>
      <td style="text-align: right">Video</td>
      <td style="text-align: left">S3 ViRGE/DX 4MB + Voodoo 3DFX Linkeada<br /> (1997: Trident PCI TGUI9440 1MB/Oak 512kb)</td>
    </tr>
    <tr>
      <td style="text-align: right">Audio</td>
      <td style="text-align: left">SoundBlaster 16 (1997: SoundBlaster AWE32)</td>
    </tr>
    <tr>
      <td style="text-align: right">Media Removible</td>
      <td style="text-align: left">Disquetera 5 1/4, 3 1/2, CDRW, DVDRW</td>
    </tr>
    <tr>
      <td style="text-align: right">Puertos</td>
      <td style="text-align: left">1 Serial DE-9, 2 Paralelos DB-25, PS/2</td>
    </tr>
    <tr>
      <td style="text-align: right">Sistema Operativo</td>
      <td style="text-align: left">MS-DOS / Win 3.11</td>
    </tr>
  </tbody>
</table>

<p>En este caso, lo que queremos hacer es conectarnos a <code class="language-plaintext highlighter-rouge">BBSs</code> con terminales de la época, como 
<code class="language-plaintext highlighter-rouge">Terminate 5.0</code>. Lo que nos permitiría también, bajar archivos de estos sistemas siempre y cuando
soporten protocolos de transferencia de archivos como <code class="language-plaintext highlighter-rouge">zModem</code>. 
Los que ya se hayan conectado a una <code class="language-plaintext highlighter-rouge">BBS</code> con <code class="language-plaintext highlighter-rouge">telnet</code> a través de Internet, se habrán
dado cuenta ya que es posible bajar archivos de <code class="language-plaintext highlighter-rouge">BBSs</code> a menos que se usen terminales especializadas,
como <code class="language-plaintext highlighter-rouge">SyncTERM</code>.</p>

<p>En caso de usar <code class="language-plaintext highlighter-rouge">mTCP</code>, con <code class="language-plaintext highlighter-rouge">Packet Driver</code> en <code class="language-plaintext highlighter-rouge">MS-DOS</code>, <code class="language-plaintext highlighter-rouge">Telnet</code> también es una opción, pero tiene
la misma limitación.</p>

<h1 id="un-modem-un-puerto-cables-y-una-emulación">Un Modem, Un Puerto, Cables y Una Emulación</h1>

<p>Hablemos de <a href="https://en.wikipedia.org/wiki/RS-232">RS232</a>. 
Generalizando, este standard permite la comunicación y envío de datos entre equipos, a través del puerto serie de las computadoras,
bajo el conector <a href="https://en.wikipedia.org/wiki/D-subminiature#DE-9">DE-9</a> (coloquial e incorrectamente llamado <code class="language-plaintext highlighter-rouge">DB9</code>) que es el que se usaba normalmente, cuando conectábamos nuestro mouse serial a la PC. 
Este cable, en un extremo usa el tipo de conector <a href="https://en.wikipedia.org/wiki/D-subminiature#DE-9">DE-9</a> y en el otro un tipo de conector llamado <a href="https://en.wikipedia.org/wiki/Insulation-displacement_connector">IDC-10</a>.</p>

<p>Dicho esto, hay una cosa interesante cuando mencioné anteriormente sobre estos cables, son un <code class="language-plaintext highlighter-rouge">standard</code>.
Lamentablemente, no lo son. Si tenés el cableado del cable hecho correctamente, entonces todos los pasos que siguen 
van a ser muy fáciles. Pero si no, el intercambio de datos entre dos computadoras simplemente no va a suceder correctamente.</p>

<p><img src="/assets/images/bbs1997/12In-DB9-To-IDC10-Serial-Port-Everex-Cable-Bracket-Add-DB9-Port-AT-Motherboard.jpg" alt="DE-9 to IDC-10" class="img-responsive" /><em>IDC-10 a DE-9</em></p>

<p>Resulta ser que hay dos "standares" grandes en el mundo de los cables seriales:</p>
<ul>
  <li>AT/Everex</li>
  <li>DTK/Intel</li>
</ul>

<p>Cada "standard" tiene su propio <code class="language-plaintext highlighter-rouge">Pinout</code> particular. Primero tenes que saber cual standard usa tu motherboard. Luego de esto, tenes que chequear 
como está hecho el cableado en el cable que va del motherboard al panel trasero de la PC y finalmente hacer los tests pertinentes
teniendo en cuenta las siguientes tablas:</p>

<table>
  <thead>
    <tr>
      <th style="text-align: right">Número Pin (interno) AT/Everex</th>
      <th>Nombre de la Señal</th>
      <th>Descripción</th>
      <th>Número Pin RS232 DE-9</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td style="text-align: right">1</td>
      <td>DCD</td>
      <td>Data Carrier Detect</td>
      <td>1</td>
    </tr>
    <tr>
      <td style="text-align: right">2</td>
      <td>RXD</td>
      <td>Receive Data</td>
      <td>2</td>
    </tr>
    <tr>
      <td style="text-align: right">3</td>
      <td>TXD</td>
      <td>Transmit Data</td>
      <td>3</td>
    </tr>
    <tr>
      <td style="text-align: right">4</td>
      <td>DTR</td>
      <td>Data Terminal Ready</td>
      <td>4</td>
    </tr>
    <tr>
      <td style="text-align: right">5</td>
      <td>GND</td>
      <td>System Ground</td>
      <td>5</td>
    </tr>
    <tr>
      <td style="text-align: right">6</td>
      <td>DSR</td>
      <td>Data Set Ready</td>
      <td>6</td>
    </tr>
    <tr>
      <td style="text-align: right">7</td>
      <td>RTS</td>
      <td>Request to Send</td>
      <td>7</td>
    </tr>
    <tr>
      <td style="text-align: right">8</td>
      <td>CTS</td>
      <td>Clear to Send</td>
      <td>8</td>
    </tr>
    <tr>
      <td style="text-align: right">9</td>
      <td>RI</td>
      <td>Ring Indicator</td>
      <td>9</td>
    </tr>
  </tbody>
</table>

<table>
  <thead>
    <tr>
      <th style="text-align: right">Número Pin (interno) DTK/INTEL</th>
      <th>Nombre de la Señal</th>
      <th>Descripción</th>
      <th>Número Pin RS232 DE-9</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td style="text-align: right">1</td>
      <td>DCD</td>
      <td>Data Carrier Detect</td>
      <td>1</td>
    </tr>
    <tr>
      <td style="text-align: right">2</td>
      <td>DSR</td>
      <td>Data Set Ready</td>
      <td>6</td>
    </tr>
    <tr>
      <td style="text-align: right">3</td>
      <td>RXD</td>
      <td>Receive Data</td>
      <td>2</td>
    </tr>
    <tr>
      <td style="text-align: right">4</td>
      <td>RTS</td>
      <td>Request to Send</td>
      <td>7</td>
    </tr>
    <tr>
      <td style="text-align: right">5</td>
      <td>TXD</td>
      <td>Transmit Data</td>
      <td>3</td>
    </tr>
    <tr>
      <td style="text-align: right">6</td>
      <td>CTS</td>
      <td>Clear to Send</td>
      <td>8</td>
    </tr>
    <tr>
      <td style="text-align: right">7</td>
      <td>DTR</td>
      <td>Data Terminal Ready</td>
      <td>4</td>
    </tr>
    <tr>
      <td style="text-align: right">8</td>
      <td>RI</td>
      <td>Ring Indicator</td>
      <td>9</td>
    </tr>
    <tr>
      <td style="text-align: right">9</td>
      <td>GND</td>
      <td>System Ground</td>
      <td>5</td>
    </tr>
  </tbody>
</table>

<p><img src="/assets/images/bbs1997/IDC10-escondido.jpg" alt="IDC10 Escondido" class="img-responsive" /><em>Encontrá el COM1 en forma de conector IDC10</em></p>

<p>¿Como darse cuenta que pinout tenés? En el motherboard, buscando el numero de serie o buscando si la sigla 
<code class="language-plaintext highlighter-rouge">DTK</code> esta impresa en la plancha del motherboard cerca del número de serie. Si no encontrás nada, es posible que sea un <code class="language-plaintext highlighter-rouge">AT/Everex</code>.
En el cable, con un multímetro. Le chantás un alambre finito a cada uno de los pines en el IDC y 
sostenés la aguja del multímetro de un lado y del otro probas que PIN tiene continuidad. 
Tomás nota de cada uno y comparás el resultado con alguna de las dos tablas anteriores. 
Si corresponde, sabés que tipo de cable tenés. Si el cable es compatible con el motherboard, ya estás
en camino y tenés resuelta la parte más difícil. Todo lo demás, es tener los materiales nomás \o/ !</p>

<h1 id="cables">Cables</h1>

<p><img src="/assets/images/bbs1997/serial-to-usb.jpg" alt="USB to Serial Interface" class="img-responsive" /><em>Interfase o conector USB a Serial</em></p>

<p>En la imagen de arriba, se ven dos cosas:</p>
<ul>
  <li>Un cable RS232 Hembra-Hembra de tipo <code class="language-plaintext highlighter-rouge">Null Modem</code></li>
  <li>Un conversor USB a Serial, conectado a una Raspberry PI</li>
</ul>

<p>Sobre el cable serial RS232 de tipo Null Modem, lo que hace a grandes rasgos es cruzar la señal <code class="language-plaintext highlighter-rouge">RXD</code> y <code class="language-plaintext highlighter-rouge">TXD</code>.
Comunmente, este tipo de cables se pueden conseguir en casas de electrónica. Y muy probablemente se cruce
el PIN 2 con el PIN 3.
Si prestás atención a la tabla de más arriba, esto funciona bárbaro con un <code class="language-plaintext highlighter-rouge">AT/Everex</code>, pero en el caso de <code class="language-plaintext highlighter-rouge">DTK/INTEL</code> 
estos cables simplemente no sirven, ya que cruzan otros pines.</p>

<p>Este artículo prácticamente lo escribo por esta misma situación. Mi motherboard tiene un pinout <code class="language-plaintext highlighter-rouge">DTK/INTEL</code>. 
En un principio, pensaba que los cables que iba consiguiendo prestados no funcionaban porque eran viejos o estaban
fallados, pero lo que en realidad sucedia, es que el pinout de los cables que conseguia estaban armados
para <code class="language-plaintext highlighter-rouge">AT/Everex</code>.</p>

<p>Solo pude darme cuenta que estaba pasando en el momento en que me puse a testear con multímetro y un tester RS232
cable por cable, pin por pin, tratando de ver cuando los pines matcheaban al enviar y recibir datos.</p>

<p><img src="/assets/images/bbs1997/rs232-tester.jpg" alt="RS232 Tester" class="img-responsive" /><em>Tester RS232. Configurar 300 baudios está muy bien para VER data</em></p>

<p><img src="/assets/images/bbs1997/tester-rs232-usb-to-serial2.jpg" alt="Tester RS232 USB to Serial 2" class="img-responsive" /><em>El otro extremo del cable USB a Serial, conectado al tester y al puerto serie de la PC</em></p>

<p>El <code class="language-plaintext highlighter-rouge">null modem</code> y el conversor <code class="language-plaintext highlighter-rouge">USB a Serial</code> tenian un pinout <code class="language-plaintext highlighter-rouge">AT/Everex</code>, pero mi motherboard <em>y el cable</em> del motherboard al panel
trasero era incompatible con este "standard".
Soldar, no iba a soldar… 
Asi que esta fue la conversación que tuve con un amigo que tiene una gigantezca colección de componentes electronicos:</p>

<p>- Hey fg! Necesito unos cables seriales para enchufar al motherboard</p>

<p>- ¿Cuántos necesitás?</p>

<p>- ¡TODOS LOS QUE TENGAS!</p>

<p>Y aquí abajo el resultado:</p>

<p><img src="/assets/images/bbs1997/un-monton-de-cables-seriales.jpg" alt="Un montón de cables seriales" class="img-responsive" /><em>Un montón de cables seriales provistos por fg</em></p>

<p>Una vez que entendí cual era el problema de los pinouts y que tenia cables, motherboard y más cables todos más o menos incompatibles
entre sí, con un par de horas de buscar cable por cable y testear los pinouts con el multímetro, di con la combinación de cables 
correcta. Cada uno de estos cables, también pueden ser de varios tipos y algunos cruzan pines y otros dejan los pines con una relación
pin a pin directa.</p>

<p>Una vez todo resuelto y probando enviar y recibir data en <em>ambos sentidos</em> ya estaba listo para continuar con el proyecto.</p>

<h1 id="raspberry-pi-y-tcpser">Raspberry PI y TCPSER</h1>

<p><img src="/assets/images/bbs1997/rpi3b-tcpser.jpg" alt="Raspberry PI3 B+" class="img-responsive" /><em>Raspberry PI3 B+, conectado a mi red y también por USB al Serial-To-USB</em></p>

<p>En alguna que otra foto de este post se va a poder ver que la <code class="language-plaintext highlighter-rouge">raspi</code> esta colgando sostenida por unos cables no muy ordenados.
Esta <code class="language-plaintext highlighter-rouge">raspi</code> está conectada al conversor <code class="language-plaintext highlighter-rouge">USB a Serial</code>. Este conversor tiene un chipset <code class="language-plaintext highlighter-rouge">FTDI/FT232RL</code> y fue fabricado
por Digitus. 
Una vez conectado este conversor, chequeamos que la raspberry vea el dispositivo USB y cargado el módulo de kernel correspondiente:</p>

<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code>toshi@blackhole:~ <span class="nv">$ </span>lsusb |grep 232
Bus 001 Device 007: ID 0403:6001 Future Technology Devices International, Ltd FT232 Serial <span class="o">(</span>UART<span class="o">)</span> IC

toshi@blackhole:~ <span class="nv">$ </span>lsmod |grep ftdi
ftdi_sio               45056  1
usbserial              36864  3 ftdi_sio

toshi@blackhole:~ <span class="nv">$ </span><span class="nb">ls</span> <span class="nt">-lah</span> /dev/ttyU<span class="k">*</span>
crw-rw---- 1 root dialout 188, 0 jun  5 00:03 /dev/ttyUSB0
</code></pre></div></div>

<p>Ahora que tenemos el device <code class="language-plaintext highlighter-rouge">/dev/ttyUSB0</code> disponible para nosotros, solo nos falta bajarnos 
un programa llamado <a href="https://github.com/FozzTexx/tcpser">TCPSER</a>.
<a href="https://github.com/FozzTexx/tcpser">TCPSER</a> es un emulador <code class="language-plaintext highlighter-rouge">Serial a Modem IP</code> que soporta todos los comandos <a href="https://en.wikipedia.org/wiki/Hayes_AT_command_set">Hayes</a> standard.
Este programa es necesario porque vamos a engañar a nuestra PC retro y a <code class="language-plaintext highlighter-rouge">Terminate 5.0</code> - 
el programa para llamar a BBSs de esa época - haciéndole creer que en el <code class="language-plaintext highlighter-rouge">COM1 o COM2</code> hay un <code class="language-plaintext highlighter-rouge">Modem</code> del otro lado.</p>

<p>En mi caso, el comando <a href="https://github.com/FozzTexx/tcpser">TCPSER</a> que me funcionó para esto fue el siguiente:</p>

<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">sudo</span> ./tcpser <span class="nt">-d</span> /dev/ttyUSB0 <span class="nt">-I</span> <span class="nt">-s</span> 38400 <span class="nt">-p</span> 64128 <span class="nt">-l4</span> <span class="nt">-i</span> <span class="s2">"&amp;k0&amp;c0"</span>
2023-06-05 17:14:41:1995661424:INFO:Server socket bound to port
2023-06-05 17:14:41:1995661424:INFO:Server socket listening <span class="k">for </span>connections
2023-06-05 17:14:41:1995661424:INFO:Opening serial device
2023-06-05 17:14:41:1995661424:INFO:Opened serial device /dev/ttyUSB0 at speed 38400 as fd 8
2023-06-05 17:14:41:1995661424:INFO:serial device configured
2023-06-05 17:14:41:1993839712:INFO:Control Lines: DSR:1 DCD:1 CTS:1
2023-06-05 17:14:41:1993839712:INFO:Disconnecting modem
2023-06-05 17:14:41:1993839712:INFO:Disconnecting
2023-06-05 17:14:41:1993839712:INFO:Control Lines: DSR:1 DCD:1 CTS:1
2023-06-05 17:14:41:1993839712:INFO:Control Lines: DSR:1 DCD:0 CTS:1
<span class="o">[</span>...]
</code></pre></div></div>

<p>La velocidad máxima de transferencia que pude lograr fue 38400 baudios. Para más información de que son los otros flags,
en el <code class="language-plaintext highlighter-rouge">README.md</code> de <a href="https://github.com/FozzTexx/tcpser">TCPSER</a> se puede leer una descripción de algunos comandos <a href="https://en.wikipedia.org/wiki/Hayes_AT_command_set">Hayes</a> y algunas consideraciones 
para armar los cables necesarios en caso de que no los tengas.</p>

<h1 id="terminate-50">Terminate 5.0</h1>

<p><img src="/assets/images/bbs1997/mesa-pc-desarmada-de-cote.jpg" alt="Mesa desarmada de Coté" class="img-responsive" /><em>La PC en Terapia Intensiva de coté ;D</em></p>

<p>Finalmente, cuando tenemos todo conectado, los cables chequeados, verificado que podamos escribir en ambas computadoras
data de un lado al otro bidireccionalmente, estamos listos para configurar <code class="language-plaintext highlighter-rouge">Terminate 5.0</code>.
En la configuración de los dispositivos, seleccionamos nuestro <code class="language-plaintext highlighter-rouge">COM</code> y seteamos los <code class="language-plaintext highlighter-rouge">baudios</code> correspondientes.
Esto es muy importante: los baudios en los que corre <a href="https://github.com/FozzTexx/tcpser">TCPSER</a> tienen que ser iguales a los de <code class="language-plaintext highlighter-rouge">Terminate</code> o tu terminal
preferida. En caso que no funcione, probá bajando aún más la velocidad.
Una vez configurado correctamente, tenes que ver lo siguiente en tu pantalla:</p>

<p><img src="/assets/images/bbs1997/atz-ok.jpg" alt="ATZ, OK!" class="img-responsive" /><em>ATZ, Ok! :D</em></p>

<p>Luego configurás tu BBS favorito:</p>

<p><img src="/assets/images/bbs1997/vaultbbs-phonebook.jpg" alt="VaultBBS Phonebook" class="img-responsive" /><em>Podés poner tanto una IP como un nombre de dominio acá más el puerto</em></p>

<p>Y dejás un OneLiner como corresponde cada vez que visitás algún sistema:</p>

<p><img src="/assets/images/bbs1997/vaultbbs-oneliner.jpg" alt="VaultBBS Oneliner" class="img-responsive" /><em>¡Saludos desde el "cyberdespacio"!</em></p>

<p>Habiendo hecho funcionar esto, pude transferir archivos con el protocolo <code class="language-plaintext highlighter-rouge">zModem</code> 
desde <code class="language-plaintext highlighter-rouge">Terminate 5.0</code> en una PC retro. "Llamar" a varias <code class="language-plaintext highlighter-rouge">BBSs</code>, dejar mensajes y traer al presente esa 
nostalgia de mirar un fondo negro, iluminado con los colores y dibujos de artistas <code class="language-plaintext highlighter-rouge">ANSI</code> como muchas veces 
lo he hecho, allá por el año 199x, antes de la Internet, el IRC y el Internet Explorer 5.0.</p>

<h1 id="referencias">Referencias</h1>

<ul>
  <li><a href="https://en.wikipedia.org/wiki/RS-232">RS232</a></li>
  <li><a href="https://en.wikipedia.org/wiki/D-subminiature#DE-9">DE-9</a></li>
  <li><a href="https://en.wikipedia.org/wiki/Insulation-displacement_connector">IDC-10</a></li>
  <li><a href="https://github.com/FozzTexx/tcpser">TCPSER</a></li>
  <li><a href="https://en.wikipedia.org/wiki/Hayes_AT_command_set">Hayes</a></li>
</ul>]]></content><author><name>toshi</name></author><category term="retro" /><summary type="html"><![CDATA[Emulando un Modem, nos conectamos a BBSs a través del COM1/COM2 con el puerto serial de una computadora del año 1997.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://0x705h.com/assets/images/bbs1997/atz-ok.jpg" /><media:content medium="image" url="https://0x705h.com/assets/images/bbs1997/atz-ok.jpg" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry xml:lang="de"><title type="html">tOSh für x86 - i386+ | Teil IV | Interrupts</title><link href="https://0x705h.com/de/interrupts" rel="alternate" type="text/html" title="tOSh für x86 - i386+ | Teil IV | Interrupts" /><published>2023-02-06T07:43:04+00:00</published><updated>2023-02-06T07:43:04+00:00</updated><id>https://0x705h.com/interrupciones</id><content type="html" xml:base="https://0x705h.com/interrupts"><![CDATA[<p>Um das Betriebssystem <a href="/resources/">tOSh</a> herunterzuladen, kannst du dies über den Bereich <a href="/resources/">Resources</a> dieser Website tun.</p>

<h1 id="teile">Teile</h1>

<p>Diese Artikelserie wird aus folgenden Teilen bestehen:</p>

<ul>
  <li><a href="/de/codeando-un-bootloader">Teil I   - Bootloader</a></li>
  <li><a href="/de/modo-protegido">Teil II  - Stage 1</a></li>
  <li><a href="/de/saltando-a-c">Teil III - Sprung zu C</a></li>
  <li><a href="/de/interrupciones">Teil IV  - Interrupts</a> <strong>&lt;— Du bist hier</strong></li>
  <li><a href="/de/sistema-de-archivos-y-disquetera">Teil V   - Dateisystem und Diskettenlaufwerk-Treiber</a></li>
  <li><a href="/de/tOSh-corriendo-en-maquinas">Teil VI - Booten auf verschiedenen Maschinentypen</a></li>
</ul>

<p><img src="/assets/images/tOSh/tOSh-development.jpeg" alt="tOSh Kernel Panic!" class="img-responsive" /><em>tOSh Kernel Panic!</em></p>

<h1 id="interrupts">Interrupts</h1>

<p>Dass sowohl die alten als auch die modernen PC-Architekturen auf [Interrupts] basieren, liegt genau an diesem Chip, dem <a href="https://www.eeeguide.com/8259a-programmable-interrupt-controller/">Chip 8259a PIC</a> von Intel.</p>

<p>Ein Interrupt ist ein Signal, das die CPU empfängt und das vorübergehend alles stoppt, was die CPU gerade berechnet, damit sie ein Ereignis mit höherer Priorität <strong><em>handlen (oder verwalten)</em></strong> kann.</p>

<p>Damit ein PC mit einem ausreichend komplexen elektronischen Gerät interagieren kann, muss es in irgendeiner Form eine Interaktion mit einer CPU geben. Eine Möglichkeit war, einfach jedes Gerät direkt zu fragen: <strong>"Hey!, was geht?"</strong>.</p>

<p>Wenn man die angeschlossenen Geräte jedoch ständig fragt, ob sie etwas brauchen, gehen bei jeder einzelnen Frage Zeit und Ressourcen verloren. Diese Strategie nennt man (<a href="https://en.wikipedia.org/wiki/Polling_(computer_science)">Polling</a>).</p>

<p>Eine der Lösungen, um <a href="https://en.wikipedia.org/wiki/Polling_(computer_science)">Polling</a> zu vermeiden, besteht darin, die CPU einfach ihre Sachen machen und ihre Programme normal ausführen zu lassen und auf ein Signal zu warten, sobald jemand eine Aktion von ihr benötigt. Die beiden häufigsten Arten von CPU-Interrupts sind zum Beispiel:</p>

<ul>
  <li>
    <p><strong>Hardware Interrupts</strong>: Sie werden asynchron von physischen Geräten wie Maus, Tastatur und Festplatten gesendet, die von Zeit zu Zeit Aufmerksamkeit benötigen. Zum Beispiel löst das Drücken einer Taste auf der PC-Tastatur einen Hardware-Interrupt aus und <strong>UNTERBRICHT ALLES</strong>, und man muss das, was gerade ausgeführt wird, egal ob ein <a href="https://en.wikipedia.org/wiki/Kernel_%28operating_system%29">Kernel</a> oder ein Programm im "Userspace", stehen lassen und den Interrupt sofort handlen.</p>
  </li>
  <li>
    <p><strong>Software Interrupts</strong>: Exceptions oder Traps, die intern vom Prozessor oder von einem laufenden Programm aufgrund eines Fehlers erzeugt werden (zum Beispiel durch eine Division durch null) oder durch einen request an das Betriebssystem über einen system call.</p>
  </li>
</ul>

<h2 id="wie-wird-ein-interrupt-gehandlet-behandelt">Wie wird ein Interrupt gehandlet (behandelt)?</h2>

<p><strong>Diego Silicio</strong> erinnert uns in seinen "<strong>unterbrochenen Sextillen</strong>" daran:</p>

<p>Original:</p>

<p>acá le venimo' a canta'<br />
en compás he interrumpido<br />
usted, amigo tupido<br />
pero nunca 'ES' como humano<br />
PICante es el paisano<br />
en su línea se ha unido<br /></p>

<p>Übersetzung:</p>

<p>hier kommen wir, um euch zu singen<br />
im unterbrochenen Takt<br />
Sie, mein dichter Freund<br />
doch niemals 'IST' wie ein Mensch<br />
PICant ist der Landsmann<br />
in seiner Zeile hat er sich vereint<br /></p>

<p><strong>Erklärung</strong>: Das Gedicht steckt voller technischer und sprachlicher Wortspiele. „PICante“ kombiniert PIC (Programmable Interrupt Controller) mit picante („scharf“), während „paisano“ den Gaucho-/Landsmann-Ton des Gedichts beibehält.</p>

<p>Dazu kommt ein mehrschichtiges Wortspiel in „usted, amigo tupido / pero nunca ‘ES’ como humano“. Tupido bedeutet etwa „dicht“, „kompakt“ oder auch „begriffsstutzig“. Zusammen mit ES ergibt sich visuell „EStupido“, was auf estúpido („dumm“) anspielt. Gleichzeitig ist ES tatsächlich das spanische Verb es („ist“), sodass „nunca ES como humano“ wörtlich „ist niemals wie ein Mensch“ bedeutet. Die Großschreibung von ES macht die versteckte Konstruktion EStupido noch deutlicher.</p>

<p>Das Gedicht spricht also gleichzeitig über Interrupts und PICs, während es technische Begriffe in einen absichtlich gauchohaften Vers verpackt und mehrere Ebenen von Wortspielen übereinanderlegt.</p>

<p>Der kryptische Gaucho hat uns unterbrochen, aber nachdem wir diesen zermürbenden Artikel beendet haben, wird sein Gesang von allen behandelt.</p>

<p>Wenn ein Interrupt auftritt, <strong>sollte</strong> die CPU den Ausführungskontext irgendwo sichern. Der stack ist dafür ein guter Ort.</p>

<p>Also: Kontext sichern -&gt; Eine Interrupt Service Routine <a href="https://wiki.osdev.org/Interrupt_Service_Routines">ISR</a> ausführen (dazu kommen wir gleich) -&gt; Kontext wiederherstellen und weiterarbeiten.</p>

<p>Bevor ich überhaupt anfangen kann, die <strong>Interrupt-Routinen</strong> zusammenzubauen, von denen wir vorerst nur ein paar <strong>handlen</strong> werden und nicht alle, muss ich noch mehr Dinge erklären, denn in der Welt des <strong>PC</strong> ist leider nichts einfach.</p>

<h2 id="der-pic---programmable-interrupt-controller">Der PIC - Programmable Interrupt Controller</h2>

<p>In einem traditionellen PC ist der <em>Programmable Interrupt Controller</em> oder <a href="https://www.eeeguide.com/8259a-programmable-interrupt-controller/">Chip 8259a PIC</a> der Vermittler zwischen den Geräten und dem Prozessor.</p>

<p>Die Geräte sprechen nicht direkt mit dem Prozessor.</p>

<p>Sie sind über Interrupt-Leitungen, genannt <em>Interrupt Request</em> oder <strong>IRQs</strong>, mit dem PIC verbunden.</p>

<p>Der PIC empfängt diese Signale und kümmert sich darum, der CPU mitzuteilen, dass jemand Aufmerksamkeit verlangt.</p>

<p>Wenn wir ein bisschen im Datasheet des <a href="https://www.eeeguide.com/8259a-programmable-interrupt-controller/">Chip 8259a PIC</a> lesen, stellen wir fest, dass er 8 IRQ-Leitungen hat, von IRQ0 bis IRQ7, und alles ist wunderbar, bis wir 9 oder mehr Geräte gleichzeitig anschließen wollen, weil uns die Leitungen ausgehen.</p>

<p>Irgendwann in den 80ern gingen ihnen tatsächlich die IRQs für die ganzen Geräte aus, und wie es sich für gute Ingenieure gehört, haben sie einfach noch einen drangeklatscht und damit ein <strong>master/slave</strong>-Schema mit zwei kaskadierten <a href="https://www.eeeguide.com/8259a-programmable-interrupt-controller/">Chip 8259a PIC</a>s geschaffen.</p>

<table>
  <thead>
    <tr>
      <th style="text-align: left">Hardware-Leitung</th>
      <th style="text-align: left">8259a PIC-Chip</th>
      <th style="text-align: left">Standard-Vektor</th>
      <th style="text-align: left">Im Protected Mode remapped</th>
      <th style="text-align: left">Zugewiesenes Gerät</th>
      <th style="text-align: left">Verwendeter I/O-Port</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td style="text-align: left"><strong>IRQ 0</strong></td>
      <td style="text-align: left">Master</td>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">0x08</code></td>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">0x20</code></td>
      <td style="text-align: left">Programmable Interrupt Timer (PIT)</td>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">0x20</code> (Command), <code class="language-plaintext highlighter-rouge">0x21</code> (Data)</td>
    </tr>
    <tr>
      <td style="text-align: left"><strong>IRQ 1</strong></td>
      <td style="text-align: left">Master</td>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">0x09</code></td>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">0x21</code></td>
      <td style="text-align: left">PS/2 Keyboard</td>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">0x20</code> (Command), <code class="language-plaintext highlighter-rouge">0x21</code> (Data)</td>
    </tr>
    <tr>
      <td style="text-align: left"><strong>IRQ 2</strong></td>
      <td style="text-align: left">Master</td>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">0x0A</code></td>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">0x22</code></td>
      <td style="text-align: left">Cascade Line (Connects Slave PIC)</td>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">0x20</code> (Command), <code class="language-plaintext highlighter-rouge">0x21</code> (Data)</td>
    </tr>
    <tr>
      <td style="text-align: left"><strong>IRQ 3</strong></td>
      <td style="text-align: left">Master</td>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">0x0B</code></td>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">0x23</code></td>
      <td style="text-align: left">COM2 / COM4 Serial Ports</td>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">0x20</code> (Command), <code class="language-plaintext highlighter-rouge">0x21</code> (Data)</td>
    </tr>
    <tr>
      <td style="text-align: left"><strong>IRQ 4</strong></td>
      <td style="text-align: left">Master</td>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">0x0C</code></td>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">0x24</code></td>
      <td style="text-align: left">COM1 / COM3 Serial Ports</td>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">0x20</code> (Command), <code class="language-plaintext highlighter-rouge">0x21</code> (Data)</td>
    </tr>
    <tr>
      <td style="text-align: left"><strong>IRQ 5</strong></td>
      <td style="text-align: left">Master</td>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">0x0D</code></td>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">0x25</code></td>
      <td style="text-align: left">LPT2 Parallel Port / Sound Card</td>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">0x20</code> (Command), <code class="language-plaintext highlighter-rouge">0x21</code> (Data)</td>
    </tr>
    <tr>
      <td style="text-align: left"><strong>IRQ 6</strong></td>
      <td style="text-align: left">Master</td>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">0x0E</code></td>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">0x26</code></td>
      <td style="text-align: left">Floppy Disk Controller</td>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">0x20</code> (Command), <code class="language-plaintext highlighter-rouge">0x21</code> (Data)</td>
    </tr>
    <tr>
      <td style="text-align: left"><strong>IRQ 7</strong></td>
      <td style="text-align: left">Master</td>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">0x0F</code></td>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">0x27</code></td>
      <td style="text-align: left">LPT1 Parallel Port (Spurious IRQ Line)</td>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">0x20</code> (Command), <code class="language-plaintext highlighter-rouge">0x21</code> (Data)</td>
    </tr>
    <tr>
      <td style="text-align: left"><strong>IRQ 8</strong></td>
      <td style="text-align: left">Slave</td>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">0x70</code></td>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">0x28</code></td>
      <td style="text-align: left">CMOS Real-Time Clock (RTC)</td>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">0xA0</code> (Command), <code class="language-plaintext highlighter-rouge">0xA1</code> (Data)</td>
    </tr>
    <tr>
      <td style="text-align: left"><strong>IRQ 9</strong></td>
      <td style="text-align: left">Slave</td>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">0x71</code></td>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">0x29</code></td>
      <td style="text-align: left">ACPI / PCI Peripheral Extended</td>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">0xA0</code> (Command), <code class="language-plaintext highlighter-rouge">0xA1</code> (Data)</td>
    </tr>
    <tr>
      <td style="text-align: left"><strong>IRQ 10</strong></td>
      <td style="text-align: left">Slave</td>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">0x72</code></td>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">0x2A</code></td>
      <td style="text-align: left">Open PCI Peripheral Slot</td>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">0xA0</code> (Command), <code class="language-plaintext highlighter-rouge">0xA1</code> (Data)</td>
    </tr>
    <tr>
      <td style="text-align: left"><strong>IRQ 11</strong></td>
      <td style="text-align: left">Slave</td>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">0x73</code></td>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">0x2B</code></td>
      <td style="text-align: left">Open PCI Peripheral Slot</td>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">0xA0</code> (Command), <code class="language-plaintext highlighter-rouge">0xA1</code> (Data)</td>
    </tr>
    <tr>
      <td style="text-align: left"><strong>IRQ 12</strong></td>
      <td style="text-align: left">Slave</td>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">0x74</code></td>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">0x2C</code></td>
      <td style="text-align: left">PS/2 Mouse Port</td>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">0xA0</code> (Command), <code class="language-plaintext highlighter-rouge">0xA1</code> (Data)</td>
    </tr>
    <tr>
      <td style="text-align: left"><strong>IRQ 13</strong></td>
      <td style="text-align: left">Slave</td>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">0x75</code></td>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">0x2D</code></td>
      <td style="text-align: left">Floating Point Unit / Coprocessor</td>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">0xA0</code> (Command), <code class="language-plaintext highlighter-rouge">0xA1</code> (Data)</td>
    </tr>
    <tr>
      <td style="text-align: left"><strong>IRQ 14</strong></td>
      <td style="text-align: left">Slave</td>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">0x76</code></td>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">0x2E</code></td>
      <td style="text-align: left">Primary ATA / IDE Hard Drive</td>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">0xA0</code> (Command), <code class="language-plaintext highlighter-rouge">0xA1</code> (Data)</td>
    </tr>
    <tr>
      <td style="text-align: left"><strong>IRQ 15</strong></td>
      <td style="text-align: left">Slave</td>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">0x77</code></td>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">0x2F</code></td>
      <td style="text-align: left">Secondary ATA / IDE Hard Drive (Spurious)</td>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">0xA0</code> (Command), <code class="language-plaintext highlighter-rouge">0xA1</code> (Data)</td>
    </tr>
  </tbody>
</table>

<p>Indem wir IRQ2 als <strong>Cascade Line</strong> verwenden, verlieren wir einen IRQ, aber die Geräte, die am <code class="language-plaintext highlighter-rouge">slave</code> hängen, können darüber auf die CPU zugreifen.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Device -&gt; Slave -&gt; Master -&gt; CPU
Device -&gt; Master -&gt; CPU
</code></pre></div></div>

<p>Zusätzlich erlaubt der <a href="https://www.eeeguide.com/8259a-programmable-interrupt-controller/">Chip 8259a PIC</a>, Interrupts zu <strong>maskieren</strong>. Wenn wir gerade die Tastatur behandeln und nicht wollen, dass sich in diesem Moment das Diskettenlaufwerk einmischt, sagen wir dem PIC: "Diese eine IRQ" lässt du jetzt mal links liegen.</p>

<p>Das ist es, was ich beim Konfigurieren der IRQs mache: tOSh implementiert nur 3 davon:</p>

<ul>
  <li><strong>IRQ0</strong> (timer / pit)</li>
  <li><strong>IRQ1</strong> (Tastatur)</li>
  <li><strong>IRQ6</strong> (Floppy)</li>
</ul>

<p>Alle Interrupts sind maskiert, außer diesen drei, die ich ausdrücklich entmaskiere, damit sie behandelt werden können:</p>

<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// idt.c</span>

<span class="p">[...]</span>

<span class="kt">void</span> <span class="nf">interrupt_handler</span><span class="p">(</span><span class="kt">uint32_t</span> <span class="n">number</span><span class="p">)</span>
<span class="p">{</span>
    <span class="kt">uint32_t</span> <span class="n">irq</span> <span class="o">=</span> <span class="n">number</span> <span class="o">-</span> <span class="mi">32</span><span class="p">;</span>
    <span class="k">switch</span> <span class="p">(</span><span class="n">irq</span><span class="p">)</span> <span class="p">{</span>

        <span class="k">case</span> <span class="mi">0</span><span class="p">:</span>
            <span class="c1">// timer (IRQ 0)</span>
            <span class="n">pit_irq_handler</span><span class="p">();</span>
            <span class="k">break</span><span class="p">;</span>

        <span class="k">case</span> <span class="mi">1</span><span class="p">:</span>
            <span class="c1">// Keyboard (IRQ 1)</span>
            <span class="n">kbd_handler</span><span class="p">();</span>
            <span class="k">break</span><span class="p">;</span>

        <span class="k">case</span> <span class="mi">6</span><span class="p">:</span>
            <span class="c1">// Floppy (IRQ 6)</span>
            <span class="n">floppy_irq_handler</span><span class="p">();</span>
            <span class="k">break</span><span class="p">;</span>

        <span class="nl">default:</span>
            <span class="k">break</span><span class="p">;</span>
    <span class="p">}</span>

    <span class="n">pic_eoi</span><span class="p">(</span><span class="n">irq</span><span class="p">);</span>
<span class="p">}</span>

</code></pre></div></div>

<h2 id="die-idt---interrupt-descriptor-table">Die IDT - Interrupt Descriptor Table</h2>

<p>Die <code class="language-plaintext highlighter-rouge">IDT</code> ist eine binäre Datenstruktur, die spezifisch für die Architekturen IA-32 und x86-64 ist, und das Gegenstück zur <code class="language-plaintext highlighter-rouge">IVT</code> im [Real Mode] und <a href="https://wiki.osdev.org/Setting_Up_Long_Mode">Long Mode</a>.</p>

<p>Durch das Konfigurieren der <a href="https://wiki.osdev.org/Interrupt_Descriptor_Table">IDT</a> können wir der CPU sagen, wo sich unsere <a href="https://wiki.osdev.org/Interrupt_Service_Routines">ISR</a>s bzw. Interrupt Service Routines befinden. Das sind die Funktionen, die wir selbst schreiben müssen, um die Interrupts zu behandeln, die vom Prozessor oder von Geräten ausgelöst werden. Sie ist ziemlich ähnlich zur Struktur der <a href="https://wiki.osdev.org/Global_Descriptor_Table">GDT</a>.</p>

<p>Nachfolgend kommt Intels Interrupt-Tabelle für den [Protected Mode]:</p>

<table>
  <thead>
    <tr>
      <th>Int. Nº (hex)</th>
      <th style="text-align: right">Int. Nº (dec)</th>
      <th>Mnem.</th>
      <th>Type</th>
      <th>Err. code</th>
      <th>Name</th>
      <th>Source</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>0x00</td>
      <td style="text-align: right">0</td>
      <td>#DE</td>
      <td>Fault</td>
      <td>No</td>
      <td>Divide Error</td>
      <td>DIV and IDIV instructions.</td>
    </tr>
    <tr>
      <td>0x01</td>
      <td style="text-align: right">1</td>
      <td>#DB</td>
      <td>Trap</td>
      <td>No</td>
      <td>Debug Exception</td>
      <td>Instruction, data, and I/O breakpoints; single-step; and others.</td>
    </tr>
    <tr>
      <td>0x02</td>
      <td style="text-align: right">2</td>
      <td>NMI</td>
      <td>Interrupt</td>
      <td>No</td>
      <td>NMI Interrupt</td>
      <td>Nonmaskable external interrupt.</td>
    </tr>
    <tr>
      <td>0x03</td>
      <td style="text-align: right">3</td>
      <td>#BP</td>
      <td>Trap</td>
      <td>No</td>
      <td>Breakpoint</td>
      <td>INT3 instruction.</td>
    </tr>
    <tr>
      <td>0x04</td>
      <td style="text-align: right">4</td>
      <td>#OF</td>
      <td>Trap</td>
      <td>No</td>
      <td>Overflow</td>
      <td>INTO instruction.</td>
    </tr>
    <tr>
      <td>0x05</td>
      <td style="text-align: right">5</td>
      <td>#BR</td>
      <td>Fault</td>
      <td>No</td>
      <td>BOUND Range Exceeded</td>
      <td>BOUND instruction.</td>
    </tr>
    <tr>
      <td>0x06</td>
      <td style="text-align: right">6</td>
      <td>#UD</td>
      <td>Fault</td>
      <td>No</td>
      <td>Invalid Opcode (Undefined Opcode)</td>
      <td>UD instruction or reserved opcode.</td>
    </tr>
    <tr>
      <td>0x07</td>
      <td style="text-align: right">7</td>
      <td>#NM</td>
      <td>Fault</td>
      <td>No</td>
      <td>Device Not Available (No Math Coprocessor)</td>
      <td>Floating-point or WAIT/FWAIT instruction.</td>
    </tr>
    <tr>
      <td>0x08</td>
      <td style="text-align: right">8</td>
      <td>#DF</td>
      <td>Abort</td>
      <td>Yes (zero)</td>
      <td>Double Fault</td>
      <td>Any instruction that can generate an exception, an NMI (Non-maskable interrupt), or an INTR (external interrupt).</td>
    </tr>
    <tr>
      <td>0x09</td>
      <td style="text-align: right">9</td>
      <td>—</td>
      <td>Fault</td>
      <td>No</td>
      <td>Coprocessor Segment Overrun (reserved)</td>
      <td>Floating-point instruction.</td>
    </tr>
    <tr>
      <td>0x0A</td>
      <td style="text-align: right">10</td>
      <td>#TS</td>
      <td>Fault</td>
      <td>Yes</td>
      <td>Invalid TSS</td>
      <td>Task switch or TSS access.</td>
    </tr>
    <tr>
      <td>0x0B</td>
      <td style="text-align: right">11</td>
      <td>#NP</td>
      <td>Fault</td>
      <td>Yes</td>
      <td>Segment Not Present</td>
      <td>Loading segment registers or accessing system segments.</td>
    </tr>
    <tr>
      <td>0x0C</td>
      <td style="text-align: right">12</td>
      <td>#SS</td>
      <td>Fault</td>
      <td>Yes</td>
      <td>Stack-Segment Fault</td>
      <td>Stack operations and SS (stack segment) register loads.</td>
    </tr>
    <tr>
      <td>0x0D</td>
      <td style="text-align: right">13</td>
      <td>#GP</td>
      <td>Fault</td>
      <td>Yes</td>
      <td>General Protection</td>
      <td>Any memory reference and other protection checks.</td>
    </tr>
    <tr>
      <td>0x0E</td>
      <td style="text-align: right">14</td>
      <td>#PF</td>
      <td>Fault</td>
      <td>Yes</td>
      <td>Page Fault</td>
      <td>Any memory reference.</td>
    </tr>
    <tr>
      <td>0x0F</td>
      <td style="text-align: right">15</td>
      <td>—</td>
      <td>—</td>
      <td>No</td>
      <td>Intel reserved. Do not use.</td>
      <td>—</td>
    </tr>
    <tr>
      <td>0x10</td>
      <td style="text-align: right">16</td>
      <td>#MF</td>
      <td>Fault</td>
      <td>No</td>
      <td>x87 FPU Floating-Point Error (Math Fault)</td>
      <td>x87 FPU floating-point or WAIT/FWAIT instruction.</td>
    </tr>
    <tr>
      <td>0x11</td>
      <td style="text-align: right">17</td>
      <td>#AC</td>
      <td>Fault</td>
      <td>Yes (zero)</td>
      <td>Alignment Check</td>
      <td>Any data reference in memory.</td>
    </tr>
    <tr>
      <td>0x12</td>
      <td style="text-align: right">18</td>
      <td>#MC</td>
      <td>Abort</td>
      <td>No</td>
      <td>Machine Check</td>
      <td>Error codes (if any) and source are model dependent.</td>
    </tr>
    <tr>
      <td>0x13</td>
      <td style="text-align: right">19</td>
      <td>#XM</td>
      <td>Fault</td>
      <td>No</td>
      <td>SIMD Floating-Point Exception</td>
      <td>SSE/SSE2/SSE3 floating-point instructions.</td>
    </tr>
    <tr>
      <td>0x14</td>
      <td style="text-align: right">20</td>
      <td>#VE</td>
      <td>Fault</td>
      <td>No</td>
      <td>Virtualization Exception</td>
      <td>EPT violations.</td>
    </tr>
    <tr>
      <td>0x15</td>
      <td style="text-align: right">21</td>
      <td>#CP</td>
      <td>Fault</td>
      <td>Yes</td>
      <td>Control Protection Exception</td>
      <td>RET, IRET, RSTORSSP, and SETSSBSY instructions can generate this exception. When CET indirect branch tracking is enabled, this exception can be generated due to a missing ENDBRANCH instruction at target of an indirect call or jump.</td>
    </tr>
    <tr>
      <td>0x16–0x1F</td>
      <td style="text-align: right">22–31</td>
      <td>—</td>
      <td>—</td>
      <td>—</td>
      <td>Reserved for future use as CPU exception vectors.</td>
      <td>—</td>
    </tr>
    <tr>
      <td>0x20–0xFF</td>
      <td style="text-align: right">32–255</td>
      <td>—</td>
      <td>Interrupt</td>
      <td>No</td>
      <td>—</td>
      <td>External interrupts.</td>
    </tr>
  </tbody>
</table>

<p><strong>Ein häufiger Fehler ist, die IRQs des <a href="https://www.eeeguide.com/8259a-programmable-interrupt-controller/">Chip 8259a PIC</a> mit den INTerrupts zu verwechseln. DAS IST NICHT DASSELBE!!!!!1</strong></p>

<p>Eine IRQ ist die physische Hardware-Leitung. Die Interrupt-Nummer ist der Eintrag, den die CPU in der IDT verwendet.</p>

<p>Es sind zwei miteinander verbundene Dinge, aber nicht dasselbe, und was sie miteinander verbindet, ist genau das <strong>Remapping</strong> des PIC.</p>

<p>Die Interrupt-Nummer funktioniert als Index innerhalb der Tabelle: Der Interrupt kommt an, die CPU sucht den entsprechenden Eintrag und springt direkt zu dem Code, den wir dafür konfiguriert haben.</p>

<h2 id="remapping">Remapping</h2>

<p>Man kann das Problem schon riechen, oder? Die IRQ-Werte und die CPU-Interrupts (DIE NICHT DASSELBE SIND!!!!!!!!!!1) überlappen sich.</p>

<p>Durch diese Überschneidung müssen wir die IRQ-Werte verschieben bzw. <strong>remappen</strong>, sodass sie hinter der INTerrupt-Tabelle der CPU liegen.</p>

<p>Die ersten 32 Einträge der <a href="https://wiki.osdev.org/Interrupt_Descriptor_Table">IDT</a> (Vektoren 0 bis 31) sind für Exceptions des x86 selbst reserviert (<em>divide by zero error, general protection fault, etc..</em>) und die IRQs des PIC beginnen ebenfalls bei 0, wodurch sie sich überschneiden.</p>

<p>Deshalb müssen wir beim Start unseres <a href="https://en.wikipedia.org/wiki/Kernel_%28operating_system%29">Kernel</a> den PIC neu konfigurieren, damit er oberhalb des Bereichs der <strong>INTerrupt</strong>-Tabelle der CPU läuft, und zwar so:</p>

<table>
  <thead>
    <tr>
      <th style="text-align: right">PIC</th>
      <th style="text-align: right">Original IRQ</th>
      <th style="text-align: right">Ziel-IRQ</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td style="text-align: right">Master</td>
      <td style="text-align: right">IRQ0 - IRQ7</td>
      <td style="text-align: right">INT 32 - INT 39</td>
    </tr>
    <tr>
      <td style="text-align: right">Slave</td>
      <td style="text-align: right">IRQ8 - IRQ15</td>
      <td style="text-align: right">INT 40 - INT 47</td>
    </tr>
  </tbody>
</table>

<h2 id="drücken-sie-eine-beliebige-taste-um-fortzufahren">Drücken Sie eine beliebige Taste, um fortzufahren…</h2>

<p>Nehmen wir als Beispiel das Drücken der <code class="language-plaintext highlighter-rouge">beliebigen</code> Taste.</p>

<p>Die Logik jeder PC-Tastatur erzeugt für jede gedrückte Taste einen <code class="language-plaintext highlighter-rouge">scancode</code> für den Tastatur-Controller.</p>

<p>Dieser Controller erzeugt einen <code class="language-plaintext highlighter-rouge">IRQ</code>, in unserem Fall <code class="language-plaintext highlighter-rouge">IRQ1</code> (siehe Tabelle weiter oben). Empfänger dieses <code class="language-plaintext highlighter-rouge">IRQ</code> ist der <a href="https://www.eeeguide.com/8259a-programmable-interrupt-controller/">Chip 8259a PIC</a>, der prüft, ob der Interrupt aktiviert (bzw. entmaskiert) ist, ihn als ausstehend markiert und über die INTR-Leitung ein Signal an die CPU schickt.</p>

<p>Die CPU akzeptiert den Interrupt und der PIC liefert den entsprechenden Vektor. Wenn IRQ1 auf den Vektor 0x21 remapped wurde, verwendet die CPU diesen Vektor, um in der IDT nachzuschauen, an welcher Speicheradresse sie den entsprechenden <strong>Interrupt-Handler</strong> findet.</p>

<p>Das hier ist der von tOSh; wir unterstützen nur einen Timer, die Tastatur und das Floppy:</p>

<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cm">/* de idt.asm, declaramos esta y otras funciones como "extern" */</span>

<span class="kt">void</span> <span class="nf">interrupt_handler</span><span class="p">(</span><span class="kt">uint32_t</span> <span class="n">number</span><span class="p">)</span>
<span class="p">{</span>
    <span class="kt">uint32_t</span> <span class="n">irq</span> <span class="o">=</span> <span class="n">number</span> <span class="o">-</span> <span class="mi">32</span><span class="p">;</span>
    <span class="k">switch</span> <span class="p">(</span><span class="n">irq</span><span class="p">)</span> <span class="p">{</span>

        <span class="k">case</span> <span class="mi">0</span><span class="p">:</span>
            <span class="c1">// timer (IRQ 0)</span>
            <span class="n">pit_irq_handler</span><span class="p">();</span>
            <span class="k">break</span><span class="p">;</span>

        <span class="k">case</span> <span class="mi">1</span><span class="p">:</span>
            <span class="c1">// Keyboard (IRQ 1)</span>
            <span class="n">kbd_handler</span><span class="p">();</span>
            <span class="k">break</span><span class="p">;</span>

        <span class="k">case</span> <span class="mi">6</span><span class="p">:</span>
            <span class="c1">// Floppy (IRQ 6)</span>
            <span class="n">floppy_irq_handler</span><span class="p">();</span>
            <span class="k">break</span><span class="p">;</span>

        <span class="nl">default:</span>
            <span class="k">break</span><span class="p">;</span>
    <span class="p">}</span>

    <span class="n">pic_eoi</span><span class="p">(</span><span class="n">irq</span><span class="p">);</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Und aus den Routinen in <code class="language-plaintext highlighter-rouge">idt.asm</code>, wo wir deklarieren, wie wir den Handler laden werden, erstellen wir diese Funktion:</p>

<div class="language-nasm highlighter-rouge"><div class="highlight"><pre class="highlight"><code>
<span class="p">[</span><span class="nf">...</span><span class="p">]</span>

<span class="nf">extern</span> <span class="nv">interrupt_handler</span>

<span class="p">[</span><span class="nf">...</span><span class="p">]</span>

<span class="nl">irq_common:</span>
    <span class="nf">pusha</span>

    <span class="c1">; ESP apunta ahora al comienzo de pusha:</span>
    <span class="c1">;</span>
    <span class="c1">; [ESP + 0]  EDI</span>
    <span class="c1">; [ESP + 4]  ESI</span>
    <span class="c1">; [ESP + 8]  EBP</span>
    <span class="c1">; [ESP + 12] original ESP</span>
    <span class="c1">; [ESP + 16] EBX</span>
    <span class="c1">; [ESP + 20] EDX</span>
    <span class="c1">; [ESP + 24] ECX</span>
    <span class="c1">; [ESP + 28] EAX</span>
    <span class="c1">; [ESP + 32] interrupt number</span>
    <span class="c1">; [ESP + 36] error code</span>
    <span class="c1">; [ESP + 40] EIP</span>
    <span class="c1">; [ESP + 44] CS</span>
    <span class="c1">; [ESP + 48] EFLAGS</span>

    <span class="nf">mov</span> <span class="nb">eax</span><span class="p">,</span> <span class="p">[</span><span class="nb">esp</span> <span class="o">+</span> <span class="mi">32</span><span class="p">]</span>         <span class="c1">; +32 = interrupt number </span>
    <span class="nf">push</span> <span class="nb">eax</span>
    <span class="nf">call</span> <span class="nv">interrupt_handler</span>
    <span class="nf">add</span> <span class="nb">esp</span><span class="p">,</span> <span class="mi">4</span>

    <span class="nf">popa</span>
    <span class="c1">; remove interrupt number + fake error code</span>
    <span class="nf">add</span> <span class="nb">esp</span><span class="p">,</span> <span class="mi">8</span>
    <span class="nf">iret</span>

<span class="p">[</span><span class="nf">...</span><span class="p">]</span>

</code></pre></div></div>

<p>Die CPU gelangt über unser <code class="language-plaintext highlighter-rouge">extern interrupt_handler</code> in den entsprechenden Handler. Dieser wird in <code class="language-plaintext highlighter-rouge">irq_common</code> aufgerufen und macht einen Call in unseren C-Code, der schließlich die Funktion <code class="language-plaintext highlighter-rouge">kbd_handler()</code> ausführt.</p>

<p>Wie man sehen kann, sichern wir mit <code class="language-plaintext highlighter-rouge">pusha</code> den Ausführungskontext, denn egal, was die CPU gerade gemacht hat, wir <strong>müssen das sichern</strong>, um zu tun, was wir tun müssen, und nach unserem <code class="language-plaintext highlighter-rouge">call interrupt_handler</code> den Kontext wiederherzustellen und mit <code class="language-plaintext highlighter-rouge">iret</code> aus unserem Interrupt zurückzukehren.</p>

<p>Wir haben jetzt alles abgedeckt. Der <code class="language-plaintext highlighter-rouge">kbd_handler()</code> und alles, was mit der Tastatur zu tun hat, ist ziemlich öde, weil es im Grunde nur darum geht, <code class="language-plaintext highlighter-rouge">scancodes</code> auf Tastaturfunktionen zu mappen (CTRL+C macht dies, SHIFT + Kleinbuchstabe wird zum Großbuchstaben usw.) und als <strong>phun phakt</strong>: Dieser Teil des <a href="https://en.wikipedia.org/wiki/Kernel_%28operating_system%29">Kernel</a> hat Jahre gedauert, weil ich beim Anblick dessen, was ich da alles programmieren musste, keinen Bock hatte. Und ein paar Jahre lang hatte ich auch Skrupel, einige ziemlich coole Implementierungen anderer Leute von GitHub zu kopieren. Aber vor kurzem ist mir das dann vergangen und ich habe mich sehr stark inspirieren lassen. Leider weiß ich heute nicht mehr genau, von wem, aber hey, danke.</p>

<p>Wie man in der C-Funktion <code class="language-plaintext highlighter-rouge">interrupt_handler()</code> sehen kann, gibt es dort einen Aufruf der Funktion <code class="language-plaintext highlighter-rouge">pic_eoi()</code>. Ein <code class="language-plaintext highlighter-rouge">iret</code> allein reicht nicht aus, um anzuzeigen, dass wir von einem Interrupt zurückkehren; wir müssen dem PIC auch sagen, dass wir mit der Behandlung des <code class="language-plaintext highlighter-rouge">IRQ</code> fertig sind.</p>

<p>Die CPU kehrt schließlich zu dem unterbrochenen Programm zurück und setzt genau bei dem <code class="language-plaintext highlighter-rouge">EIP</code> fort, an dem sie angehalten wurde.</p>

<p>Und fertig. Wir sind durch.</p>

<h2 id="wh0a-dir-fehlt-der-apic-advanced-programmable-interrupt-controller">wh0a, dir fehlt der APIC! (Advanced Programmable Interrupt Controller)</h2>

<p>Oh nein! Moment! Ich muss noch eine Sache klarstellen. <a href="https://wiki.osdev.org/APIC">APIC</a> ist der Ersatz für den <a href="https://www.eeeguide.com/8259a-programmable-interrupt-controller/">Chip 8259a PIC</a>, und bis heute unterstützt <code class="language-plaintext highlighter-rouge">tOSh</code> ihn nicht.</p>

<p>Irgendwie würde uns die Unterstützung für <a href="https://wiki.osdev.org/APIC">APIC</a> unter anderem ermöglichen, mehr CPUs zu verwalten (Dual-Core aufwärts), aber an diesem Punkt der Entwicklung kämpfe ich gerade mit Interrupt-Maskierung und der Programmierung einer Tastatur.</p>

<p>Also lassen wir das für später. :)</p>

<h1 id="referenz">Referenz</h1>

<ul>
  <li><a href="https://en.wikipedia.org/wiki/Polling_(computer_science)">Polling</a></li>
  <li><a href="https://en.wikipedia.org/wiki/Kernel_%28operating_system%29">Kernel</a></li>
  <li><a href="https://wiki.osdev.org/Setting_Up_Long_Mode">Long Mode</a></li>
  <li><a href="https://wiki.osdev.org/Real_Mode">Modo Real</a></li>
  <li><a href="https://wiki.osdev.org/Protected_Mode">Modo Protegido</a></li>
  <li><a href="https://wiki.osdev.org/Global_Descriptor_Table">GDT</a></li>
  <li><a href="https://wiki.osdev.org/Interrupt_Descriptor_Table">IDT</a></li>
  <li><a href="https://wiki.osdev.org/Interrupt_Service_Routines">ISR</a></li>
  <li><a href="https://wiki.osdev.org/Linker_Scripts">Linker Scripts</a></li>
  <li><a href="https://www.eeeguide.com/8259a-programmable-interrupt-controller/">Chip 8259a PIC</a></li>
  <li><a href="https://wiki.osdev.org/8259_PIC">8259 PIC osdev</a></li>
  <li><a href="https://wiki.osdev.org/APIC">APIC</a></li>
  <li><a href="https://wiki.osdev.org/X86_Interrupts">Interrupciones</a></li>
</ul>]]></content><author><name>toshi</name></author><category term="coding" /><category term="msdos" /><category term="intel" /><category term="amd" /><category term="x64" /><category term="x86" /><category term="boot" /><category term="bootloader" /><category term="p.o.s.t." /><category term="osdev" /><category term="Betriebssysteme" /><summary type="html"><![CDATA[Diese CPU wird die ganze Zeit unterbrochen!]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://0x705h.com/assets/images/tOSh/tOSh-development.jpeg" /><media:content medium="image" url="https://0x705h.com/assets/images/tOSh/tOSh-development.jpeg" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry xml:lang="es"><title type="html">microtonal intro 256b | Flashparty 2021 | MSDOS</title><link href="https://0x705h.com/de/microtonal-flashparty-2021-256b-intro-msdos" rel="alternate" type="text/html" title="microtonal intro 256b | Flashparty 2021 | MSDOS" /><published>2021-09-07T11:37:00+00:00</published><updated>2021-09-07T11:37:00+00:00</updated><id>https://0x705h.com/microtonal-flashparty-2021-256b-intro-msdos</id><content type="html" xml:base="https://0x705h.com/microtonal-flashparty-2021-256b-intro-msdos"><![CDATA[<p>Llegó el momento de una nueva Flashparty. 
Muchas intros últimamente de 256 bytes, mucho MIDI para el audio
pero acá se juega fuerte: Adlib de posta, nada de los <a href="http://sizecoding.org/wiki/Output#MIDI_notes">trucos con MIDI</a> que hacen otras producciones.</p>

<p>Otres decían que no se podía porque setear los registros, inicializar y configurar <a href="https://es.wikipedia.org/wiki/Yamaha_YM3812">OPL2</a> (soportada por la SoundBlaster 16) ocupaba demasiado espacio para una produccion de 256b.</p>

<p>Con respecto a que es exactamente todo esto del <code class="language-plaintext highlighter-rouge">sizecoding</code> y la <code class="language-plaintext highlighter-rouge">demoscene</code> podés leer una introduccion <a href="/de/jaja-keH-27bytes-msdos-intro">en este post anterior</a>.</p>

<p>También podes bajar esta producción llamada <a href="https://files.scene.org/view/parties/2021/flashparty21/256b_intro/toshi_-_microtonal.zip">microtonal</a> con el código fuente completo de <code class="language-plaintext highlighter-rouge">scene.org</code>.</p>

<p>Acá la producción final. Recordá; Audio y Video en 256 bytes!:</p>

<center>
<iframe width="560" height="315" src="https://www.youtube.com/embed/aQiszPgXkXA" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen=""></iframe>
</center>

<h1 id="introducción">Introducción</h1>

<p>Desde cero, el primer objetivo era hacer <em>sonar algo</em> en <code class="language-plaintext highlighter-rouge">msdos</code> evitando usar los <a href="http://sizecoding.org/wiki/Output#MIDI_notes">trucos con MIDI</a> que se pueden leer en el sitio de <code class="language-plaintext highlighter-rouge">sizecoding.org</code>.
En esta producción de 256 bytes para <code class="language-plaintext highlighter-rouge">msdos</code> usé el <a href="https://es.wikipedia.org/wiki/Modo_13h">Modo 13</a>. El 
<a href="https://github.com/munshkr">munshkr</a> codeó el <a href="https://github.com/munshkr/fmtribe/">fmtribe</a>, que es una drum-machine/synth que hace uso extensivo de OPL. De su código, compilé una versión reducida con <code class="language-plaintext highlighter-rouge">djgpp</code> solamente configurando los IN/OUT y dándole play al metrónomo que <a href="https://github.com/munshkr/fmtribe/">fmtribe</a> tiene incorporado. Extrayendo el metrónomo para tener una configuración básica del <a href="https://es.wikipedia.org/wiki/Yamaha_YM3812">OPL2</a>, este fue mi punto de partida para tener algo que <em>suene</em>. En un principio, lo que hice fue compilar con <code class="language-plaintext highlighter-rouge">djgpp</code> esta sección de codigo "mínima" y fijarme el output del código en assembly x86. El programa completo para hacer andar el metrónomo de <a href="https://github.com/munshkr/fmtribe/">fmtribe</a> en un <code class="language-plaintext highlighter-rouge">.com</code> ocupaba si mal no recuerdo, alrededor de 1 Kilobyte.</p>

<p>Demasiado grande para una producción de 256 bytes, ¿no?
<code class="language-plaintext highlighter-rouge">AIGHT!, hold my beer</code>.</p>

<h1 id="escribiendo-registros">Escribiendo registros…</h1>

<p>Escencialmente, para hacer andar <a href="https://es.wikipedia.org/wiki/Yamaha_YM3812">OPL2</a> es necesario crear:</p>
<ul>
  <li>data de instrumentos en un solo operador</li>
  <li>un loop para ejecutarlos</li>
  <li>decirle a <a href="https://es.wikipedia.org/wiki/Yamaha_YM3812">OPL2</a> cuando una nota esta ON u OFF.</li>
</ul>

<p>Aquí debajo, está la subrutina para setear los instrumentos…</p>

<div class="language-nasm highlighter-rouge"><div class="highlight"><pre class="highlight"><code>
<span class="nl">adlib_set_instrument:</span>

    <span class="c1">; adlib_write requires: </span>
    <span class="c1">; al - reg</span>
    <span class="c1">; bl - val</span>
    <span class="c1">; from the previous loop, cx is 0</span>
    <span class="c1">; so we initialize the counter with cl, saving 1 byte!</span>
    <span class="nf">mov</span> <span class="nb">cl</span><span class="p">,</span> <span class="mi">10</span>  <span class="c1">; 10 values for carrier and modulator</span>
    <span class="nf">mov</span> <span class="nb">si</span><span class="p">,</span> <span class="nv">instrument_data</span>
        <span class="nl">.loop1:</span>
        <span class="nf">lodsw</span>
        <span class="nf">mov</span> <span class="nb">bl</span><span class="p">,</span> <span class="nb">ah</span>
        <span class="nf">call</span> <span class="nv">adlib_write</span>
        <span class="nf">loop</span> <span class="nv">.loop1</span>

</code></pre></div></div>

<p>… con su data necesaria:</p>

<div class="language-nasm highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nl">instrument_data:</span>
<span class="c1">; OP1 Modulator</span>
<span class="kd">db</span> <span class="mh">0x20</span><span class="p">,</span> <span class="mh">0x87</span>     <span class="c1">; m_am_vib_eg</span>
<span class="kd">db</span> <span class="mh">0x40</span><span class="p">,</span> <span class="mh">0x40</span>     <span class="c1">; m_ksl_volume</span>
<span class="kd">db</span> <span class="mh">0x60</span><span class="p">,</span> <span class="mh">0x81</span>     <span class="c1">; m_attack_decay</span>
<span class="kd">db</span> <span class="mh">0x80</span><span class="p">,</span> <span class="mh">0xf3</span>     <span class="c1">; m_sustain_release</span>
<span class="kd">db</span> <span class="mh">0xe0</span><span class="p">,</span> <span class="mh">0x00</span>     <span class="c1">; m_waveform</span>

<span class="c1">; OP1 Carrier</span>
<span class="kd">db</span> <span class="mh">0x23</span><span class="p">,</span> <span class="mh">0xa5</span>     <span class="c1">; c_am_vib_eg</span>
<span class="kd">db</span> <span class="mh">0x43</span><span class="p">,</span> <span class="mh">0x0b</span>     <span class="c1">; c_ksl_volume</span>
<span class="kd">db</span> <span class="mh">0x63</span><span class="p">,</span> <span class="mh">0xf1</span>     <span class="c1">; c_attack_decay</span>
<span class="kd">db</span> <span class="mh">0x83</span><span class="p">,</span> <span class="mh">0x1f</span>     <span class="c1">; c_sustain_release</span>
<span class="kd">db</span> <span class="mh">0xe3</span><span class="p">,</span> <span class="mh">0x00</span>     <span class="c1">; c_waveform</span>
</code></pre></div></div>

<p>Esto es lo primero que hacemos. Configurar los instrumentos. 
Para más información sobre como funciona FM y <a href="https://es.wikipedia.org/wiki/Yamaha_YM3812">OPL2</a> fijate en este mismo sitio la página de <a href="/resources/">resources</a> donde cuelgo documentación de todo lo que trata este blog.</p>

<h1 id="-eficientemente">… eficientemente?</h1>

<p>OPL necesita una tabla de "frecuencias" donde se define el <code class="language-plaintext highlighter-rouge">pitch</code> de cada una de las notas que el chip necesita para poder "afinarse".
El tema es que el programador tiene que escribir la tabla de los pitches perfectamente asi cada frecuencia corresponde con todas las notas que hay en la música.
El tema es que esta tabla ocupa demasiado espacio, entonces aproximamos con el siguiente algoritmo la tabla de frecuencias para acercarse lo mas posible a el DoReMiFaSolLaSi (y sus sostenidos/bemoles) haciendo que esta obra se llame <code class="language-plaintext highlighter-rouge">microtonal</code>. Siendo que no podemos pegarle exactamente a las frecuencias, las aproximamos.</p>

<div class="language-nasm highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nl">gen_notes_frequencies:</span>

    <span class="c1">; if you substract each note from the real frequency table</span>
    <span class="c1">; you'll notice that there is 16 to 19 elements of distance</span>
    <span class="c1">; between each element. </span>

    <span class="c1">; the notes generated by this algorithm </span>
    <span class="c1">; are going to generate very-near frequencies</span>
    <span class="c1">; from the correct ones, so the notes will be actually</span>
    <span class="c1">; a bit out of tune. But we can argue that this is actually</span>
    <span class="c1">; NOT out of tune but a microtonal OPL music approach ;D</span>

    <span class="nf">mov</span> <span class="nb">ax</span><span class="p">,</span> <span class="mh">0x16b</span>   <span class="c1">; (Note Db)</span>
    <span class="nf">mov</span> <span class="nb">cl</span><span class="p">,</span> <span class="mi">12</span>      <span class="c1">; 12 notes ; assuming cx = 0, saving 1 byte</span>
    <span class="nf">mov</span> <span class="nb">di</span><span class="p">,</span> <span class="nv">notes</span>
        <span class="nl">.loop1:</span>
        <span class="nf">stosw</span>
        <span class="nf">add</span> <span class="nb">al</span><span class="p">,</span> <span class="mi">16</span>  <span class="c1">; distance between notes</span>
        <span class="nf">loop</span> <span class="nv">.loop1</span>
</code></pre></div></div>

<p>La magia de este algoritmo es que genera una tabla de frecuencias cercana a las notas comunes, ahorrando un monton de espacio de una tabla de frecuencias que debería ser hardcodeada. 
¿Desafinamos el chip? ¡No! ¡Esto es música microtonal! ¡El último grito de la demoscene latinoamericana!</p>

<h1 id="otras-cosas">Otras Cosas</h1>

<p>También en esta producción hay unas rutinas que tratan de escribir en pantalla un mensaje y generar un efecto visual. Los detalles de estas rutinas son bastante irrelevantes porque esta producción se trataba más de el audio que de el video. El código fuente esta disponible pero no creo que la parte gráfica, al menos en esta producción, sea la parte central.</p>

<p>De todas maneras, usé un par de trucos inspirados en otras producciones de otros demosceners para poder escribir un poquito de texto y mostrar algo en la pantalla, y que no sea solamente audio.</p>

<p>Asi que, tenemos en 256 bytes para <code class="language-plaintext highlighter-rouge">msdos</code>, audio y gráficos con texto. 
¿Que tal? ;D</p>

<h1 id="mejoras-futuras">Mejoras futuras</h1>

<p>Hay algunas cosas donde se podría reducir aun más el tamaño de esta produccion, como usar <a href="http://www.sizecoding.org/wiki/Floating-point_Opcodes#Stack_addressing.2C_.22Rrrolas_Trick.22.2C_alignment_optimization.2C_Aspect_Ratio">stack addressing</a> o también hacer un secuanciador con el <code class="language-plaintext highlighter-rouge">Intel 8253 PIT</code> en vez de estar loopeando a mano, pero hubo un momento donde dije "bueno, esto yastá" y yastuvo. :D</p>

<h1 id="concluyendo">Concluyendo</h1>

<p>Quizás parece que esto que escribí lo hice al toque-roque, y algunas partes pueden ser que si, pero esta producción llevó varios meses de desarrollo en los momentitos libres que tenía.</p>

<p>Espero que este blogpost le sirva a los otros sizecoders latinoamericanos que andan pululando por ahi, y si tenés alguna pregunta sobre esto o alguna otra cosa relacionada con el <code class="language-plaintext highlighter-rouge">sizecoding</code>, escribime abajo en la caja de comentarios.</p>

<p>¡Salut!</p>

<h1 id="referencias">Referencias</h1>

<ul>
  <li>Código fuente de <a href="https://files.scene.org/view/parties/2021/flashparty21/256b_intro/toshi_-_microtonal.zip">microtonal</a> por 0x705h/tsh/toshi</li>
  <li>MSDOS/VGA <a href="https://es.wikipedia.org/wiki/Modo_13h">Modo 13</a></li>
  <li><a href="https://github.com/munshkr">munshkr</a>'s <a href="https://github.com/munshkr/fmtribe/">fmtribe</a></li>
  <li>Soundblaster 16 / Adlib <a href="https://es.wikipedia.org/wiki/Yamaha_YM3812">OPL2</a></li>
  <li><a href="http://www.sizecoding.org/wiki/Floating-point_Opcodes#Stack_addressing.2C_.22Rrrolas_Trick.22.2C_alignment_optimization.2C_Aspect_Ratio">stack addressing</a></li>
  <li><a href="http://sizecoding.org/wiki/Output#MIDI_notes">trucos con MIDI</a></li>
</ul>]]></content><author><name>toshi</name></author><category term="demoscene" /><summary type="html"><![CDATA[Desde el principio hasta el final, todo el proceso de desarrollo de esta entry que participó en la Flashparty 2021]]></summary></entry><entry xml:lang="es"><title type="html">27 bytes intro - jaja-keH - Flashparty 2020 entry</title><link href="https://0x705h.com/de/jaja-keH-27bytes-msdos-intro" rel="alternate" type="text/html" title="27 bytes intro - jaja-keH - Flashparty 2020 entry" /><published>2020-07-27T13:17:00+00:00</published><updated>2020-07-27T13:17:00+00:00</updated><id>https://0x705h.com/jaja-keH-27bytes-msdos-intro</id><content type="html" xml:base="https://0x705h.com/jaja-keH-27bytes-msdos-intro"><![CDATA[<p>En la <a href="https://flashparty.dx.am/index.php?lang=es">Flashparty</a> del año 2020, participé en la categoría 256 bytes combinados y mi programa o <code class="language-plaintext highlighter-rouge">entry</code>
compitió con otras producciones increíbles. En este artículo comento el código fuente en detalle
de la <code class="language-plaintext highlighter-rouge">entry</code> original que originalmente tenía <code class="language-plaintext highlighter-rouge">27 bytes</code>, y al final un par de cambios para reducir el tamaño del programa aún más y una pequeña 
modificación a la selección de colores para hacer el efecto más "dinámico" (?)</p>

<p>Aquí abajo está el video de la <code class="language-plaintext highlighter-rouge">versión final</code>, reducida a <code class="language-plaintext highlighter-rouge">24 bytes</code> y con <code class="language-plaintext highlighter-rouge">más colores locos</code>.</p>

<center>
<iframe width="560" height="315" src="https://www.youtube.com/embed/Qxe6A6tQaFw" frameborder="0" allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture" allowfullscreen=""></iframe>
</center>

<h1 id="sizecoding">Sizecoding</h1>

<p>Para dar una definición aproximada sobre que es el <code class="language-plaintext highlighter-rouge">sizecoding</code> vamos a decir que es el arte
de crear programas lo más pequeños posibles para los tipos más populares de CPUs.
En particular, nos referimos a que esta categoría de programas tienen <code class="language-plaintext highlighter-rouge">256 bytes</code> o menos, creados
típicamente por miembros de la <a href="https://es.wikipedia.org/wiki/Demoscene">Demoscene</a> como una manera de demostrar habilidad como programador.
El tamaño de estos pequeños programas se mide en el tamaño total de sus <a href="https://es.wikipedia.org/wiki/C%C3%B3digo_de_operaci%C3%B3n">opcodes</a> y generalmente 
son ejecutables de la arquitectura objetivo: <code class="language-plaintext highlighter-rouge">.COM</code> para <code class="language-plaintext highlighter-rouge">MS-DOS</code>, <code class="language-plaintext highlighter-rouge">PRGs</code> para <code class="language-plaintext highlighter-rouge">Commodore 64</code>, etc…</p>

<h1 id="flashparty">FlashParty</h1>

<p>Citando directamente al sitio de <a href="https://flashparty.dx.am/index.php?lang=es">Flashparty</a>: es un evento basado en el modelo <a href="https://en.wikipedia.org/wiki/Demoscene#Parties">Demoparty</a> que tiene su origen en la sub-cultura de la <a href="https://es.wikipedia.org/wiki/Demoscene">Demoscene</a>.
La <a href="https://es.wikipedia.org/wiki/Demoscene">Demoscene</a> es un movimiento artístico que usa tecnologías informáticas a nivel muy eficiente e ingenioso para la creación de piezas de arte: código, animaciones, música y gráficos.
Una <a href="https://en.wikipedia.org/wiki/Demoscene#Parties">Demoparty</a> es la reunión de artistas en un lugar físico para presentar sus obras y participar en competencias que se dirimen por votación del público presente.</p>

<h1 id="la-entry">La entry</h1>

<p>Para obtener el <code class="language-plaintext highlighter-rouge">zip</code> original enviado a la competencia, podés descargarlo de el siguiente link de <a href="https://files.scene.org/view/parties/2020/flashparty20/256b_retro_alternative_intro/h4nz-jaja-keh.zip">scene.org</a>.
A continuación, voy a mostarles el código fuente del mismo programa que envié, pero con muchísimos comentarios.</p>

<figure class="highlight"><pre><code class="language-nasm" data-lang="nasm"><span class="nf">mov</span> <span class="nb">al</span><span class="p">,</span><span class="mh">0x13</span> 
<span class="nf">int</span> <span class="mh">0x10</span></code></pre></figure>

<p>Con esto seteamos el modo de video en <code class="language-plaintext highlighter-rouge">MS-DOS</code> en el <code class="language-plaintext highlighter-rouge">Modo 13h</code>, de <code class="language-plaintext highlighter-rouge">320x200 con 256 colores</code>. 
O sea, pasamos del modo normal del sistema operativo que es el modo texto a un modo gráfico para
poder empezar a dibujar algo.</p>

<figure class="highlight"><pre><code class="language-nasm" data-lang="nasm"><span class="nf">les</span> <span class="nb">bp</span><span class="p">,</span> <span class="p">[</span><span class="nb">bx</span><span class="p">]</span> </code></pre></figure>

<p>Acá les dejo un link a la instrucción <a href="https://www.felixcloutier.com/x86/lds:les:lfs:lgs:lss">LES</a>. En resumen, modificamos el segmento
<code class="language-plaintext highlighter-rouge">ES</code> para que apunte a la memoria de video mapeada en <code class="language-plaintext highlighter-rouge">A000:0000</code>. Pero en verdad estamos
haciendo un truco. Tradicionalmente esto se hace con las instrucciones <code class="language-plaintext highlighter-rouge">push 0xa000</code> y <code class="language-plaintext highlighter-rouge">pop es</code>.
Pusheamos en el <code class="language-plaintext highlighter-rouge">stack</code> el segmento de memoria de la memoria de video y popeamos este valor
en el registro de segmento <code class="language-plaintext highlighter-rouge">ES</code>. Estas instrucciones ocupan en total 4 bytes.
<code class="language-plaintext highlighter-rouge">PUSH</code> ocupa un byte. El word <code class="language-plaintext highlighter-rouge">0xa000</code> ocupa 2 bytes, y <code class="language-plaintext highlighter-rouge">pop es</code> ocupa un byte más, haciendo
el total de 4 bytes.
Para ahorrarnos un byte, usamos la instrucción <a href="https://www.felixcloutier.com/x86/lds:les:lfs:lgs:lss">LES</a>, que escencialmente hace lo mismo, aprovechando
los valores de inicialización de registros en el momento que empieza a <a href="http://www.fysnet.net/yourhelp.htm">ejecutarse un programa</a> en
MS-DOS. <code class="language-plaintext highlighter-rouge">ES</code> en vez de valer <code class="language-plaintext highlighter-rouge">0xa000</code> va a valer un byte menos: <code class="language-plaintext highlighter-rouge">0x9fff</code> y todo lo que dibujemos
en la memoria de video va a estar offseteado por un byte. No es muy terrible, si tenemos esto 
siempre en cuenta. El tradeoff es este offset de un byte para ganar un byte de espacio 
en nuestro programa.</p>

<figure class="highlight"><pre><code class="language-nasm" data-lang="nasm"><span class="nl">loop:</span>
<span class="nf">cwd</span>                     <span class="c1">; "clear" DX (if AH &lt; 0x7F)</span>
<span class="nf">mov</span> <span class="nb">ax</span><span class="p">,</span><span class="nb">di</span>               <span class="c1">; get screen position into AX (Y)</span>
<span class="nf">mov</span> <span class="nb">bx</span><span class="p">,</span><span class="mi">320</span>              <span class="c1">; get screen width into BX</span>
<span class="nf">div</span> <span class="nb">bx</span>                  <span class="c1">; divide, to get row and column (DX = X) </span>
                        <span class="c1">; divide DX:AX por BX, resultado AX = Resultado, DX = Resto</span></code></pre></figure>

<p>Con estas instrucciones lo que hacemos es lograr obtener la posición <code class="language-plaintext highlighter-rouge">X</code> e <code class="language-plaintext highlighter-rouge">Y</code>
de la pantalla, avanzada por el puntero <code class="language-plaintext highlighter-rouge">DI</code> que se incrementa cuando más tarde
en el programa usamos la instrucción <code class="language-plaintext highlighter-rouge">stosb</code>.</p>

<figure class="highlight"><pre><code class="language-nasm" data-lang="nasm"><span class="nl">sierpinski:</span>
        <span class="nf">and</span> <span class="nb">ax</span><span class="p">,</span> <span class="nb">dx</span> <span class="c1">; AND method</span>
        <span class="nf">mov</span> <span class="nb">al</span><span class="p">,</span> <span class="kt">byte</span> <span class="p">[</span><span class="nb">fs</span><span class="p">:</span><span class="mh">046ch</span><span class="p">]</span> <span class="c1">; get color from timer</span>
        <span class="nf">jnz</span> <span class="nv">.not_draw</span>
        <span class="nf">add</span> <span class="nb">al</span><span class="p">,</span> <span class="mi">127</span> <span class="c1">; set a different color from timer</span>
        <span class="nl">.not_draw:</span>
        <span class="nf">stosb</span>       <span class="c1">; guardar AL en ES:[DI], DI++</span>
        <span class="nf">jmp</span> <span class="nv">short</span> <span class="nv">loop</span></code></pre></figure>

<p>De todas las cosas que se pueden dibujar en un espacio altamente reducido, hay un método llamado
<a href="https://lodev.org/cgtutor/sierpinski.html">el método AND</a> del <code class="language-plaintext highlighter-rouge">Triángulo de Sierpinski</code>. Prácticamente la instrucción que lo dibuja es <code class="language-plaintext highlighter-rouge">and ax, dx</code> dentro del loop que
itera X e Y en el espacio de memoria. 
Para obtener el dato del color a dibujar, usamos la posición de memora <code class="language-plaintext highlighter-rouge">fs:046ch</code> que es donde
está el timer de MS-DOS, que nos da un valor incremental en cada iteración del loop. Para darle
variedad y que se pueda ver la diferencia entre el <a href="https://es.wikipedia.org/wiki/Tri%C3%A1ngulo_de_Sierpinski">triángulo de Sierpinski</a> y el fondo,
cambiamos el color del fondo sumando 127 bytes al registro <code class="language-plaintext highlighter-rouge">AL</code> cuando <code class="language-plaintext highlighter-rouge">and ax, dx</code> es igual a cero.</p>

<h1 id="optimizando-la-entry-a-24-bytes-con-más-colores">Optimizando la entry: A 24 bytes, con más colores!</h1>

<p>Este programa es chiquito. Pero se puede hacer aún mas pequeño. 
Removí las instrucciones para calcular X e Y e hice uso de una técnica llamada <a href="http://www.sizecoding.org/wiki/General_Coding_Tricks#Obtaining_X_and_Y_without_DIV_.28The_Rrrola_Trick.29">rrrola trick</a>.
Para que haya más variación de colores, cambié la instrucción <code class="language-plaintext highlighter-rouge">add al, 127</code> por <code class="language-plaintext highlighter-rouge">xor al, dl</code>.</p>

<p>Es importante aclarar que al ahorrarnos estos 3 bytes con el <a href="http://www.sizecoding.org/wiki/General_Coding_Tricks#Obtaining_X_and_Y_without_DIV_.28The_Rrrola_Trick.29">rrrola trick</a>, las coordenadas X e Y 
son aproximadas y hay una deformación en el ploteo del <code class="language-plaintext highlighter-rouge">Triángulo de Sierpinski</code>, por lo que no se
ve exactamente como la entry original. Hay como una especie de <code class="language-plaintext highlighter-rouge">zoom in</code> medio feo, pero hey:
son 3 bytes menos! ;D</p>

<figure class="highlight"><pre><code class="language-nasm" data-lang="nasm"><span class="c1">; coded by h4nz or toshi or 0x705h</span>
<span class="c1">; fp2020 - 24 bytes.</span>
<span class="c1">; final version after flashparty 2020</span>
<span class="c1">; this is LESS shitty, but still ;D</span>
<span class="c1">; viva peron </span>

<span class="nf">mov</span> <span class="nb">al</span><span class="p">,</span><span class="mh">0x13</span>
<span class="nf">int</span> <span class="mh">0x10</span>

<span class="nf">les</span> <span class="nb">bp</span><span class="p">,</span> <span class="p">[</span><span class="nb">bx</span><span class="p">]</span> <span class="c1">; replaces push 0xa000 / pop es</span>
             <span class="c1">; but sets ES=9fff == A000 - 1</span>

<span class="nl">loop:</span>
<span class="nf">mov</span> <span class="nb">ax</span><span class="p">,</span> <span class="mh">0xcccd</span> <span class="c1">; rrrola trick</span>
<span class="nf">mul</span> <span class="nb">di</span>

<span class="nl">sierpinski:</span>
        <span class="nf">and</span> <span class="nb">dh</span><span class="p">,</span> <span class="nb">dl</span> <span class="c1">; AND method with rrrola trick</span>
        <span class="nf">mov</span> <span class="nb">al</span><span class="p">,</span> <span class="kt">byte</span> <span class="p">[</span><span class="nb">fs</span><span class="p">:</span><span class="mh">046ch</span><span class="p">]</span> <span class="c1">; get color from timer</span>
        <span class="nf">jnz</span> <span class="nv">.not_draw</span>
        <span class="nf">xor</span> <span class="nb">al</span><span class="p">,</span> <span class="nb">dl</span> <span class="c1">; make the sierpinski pixels</span>
                   <span class="c1">; go rainbow and crazy</span>
        <span class="nl">.not_draw:</span>
        <span class="nf">stosb</span>
        <span class="nf">jmp</span> <span class="nv">short</span> <span class="nv">loop</span></code></pre></figure>

<h1 id="referencias">Referencias</h1>

<ul>
  <li><a href="http://www.sizecoding.org/wiki/Main_Page">Sizecoding</a></li>
  <li><a href="https://flashparty.dx.am/index.php?lang=es">Flashparty</a></li>
  <li>Triángulo de Sierpinski: <a href="https://lodev.org/cgtutor/sierpinski.html">el método AND</a></li>
  <li><a href="https://es.wikipedia.org/wiki/Demoscene">Demoscene</a></li>
  <li>Valores por defecto de los registros al <a href="http://www.fysnet.net/yourhelp.htm">ejecutarse un programa</a> en <code class="language-plaintext highlighter-rouge">MS-DOS</code></li>
  <li><a href="http://www.sizecoding.org/wiki/General_Coding_Tricks#Obtaining_X_and_Y_without_DIV_.28The_Rrrola_Trick.29">rrrola trick</a></li>
</ul>]]></content><author><name>toshi</name></author><category term="demoscene" /><summary type="html"><![CDATA[Acá explico mi entry a la Flashparty 2020, que salió 4ta en la competencia de intros de 256 bytes combinado]]></summary></entry></feed>