<?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/en/feed.xml" rel="self" type="application/atom+xml" /><link href="https://0x705h.com/en/" 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="en"><title type="html">tOSh for x86 - i386+ | Part VI | Booting on Different Types of Machines</title><link href="https://0x705h.com/en/tOSh-running-on-different-machines" rel="alternate" type="text/html" title="tOSh for x86 - i386+ | Part VI | Booting on Different Types of Machines" /><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-running-on-different-machines"><![CDATA[<p>To download the <a href="/resources/">tOSh</a> Operating System, you can do so from the <a href="/resources/">Resources</a> section of this site.</p>

<h1 id="parts">Parts</h1>

<p>This series of articles has the following parts:</p>

<ul>
  <li><a href="/en/codeando-un-bootloader">Part I   - Bootloader</a></li>
  <li><a href="/en/modo-protegido">Part II  - Stage 1</a></li>
  <li><a href="/en/saltando-a-c">Part III - Jumping to C</a></li>
  <li><a href="/en/interrupciones">Part IV  - Interrupts</a></li>
  <li><a href="/en/sistema-de-archivos-y-disquetera">Part V   - File System and Floppy Drive Driver</a></li>
  <li><a href="/en/tOSh-corriendo-en-maquinas">Part VI - Booting on different types of machines</a>  <strong>&lt;— You’re here</strong></li>
</ul>

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

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

<p>This month (September, 2026) I managed to get the operating system to boot and run consistently on real hardware and emulators.</p>

<p>Thanks to friends for lending me hardware to test it on all kinds of machines.</p>

<p>The floppy drive fails on very old machines, like XTs and 286s, which is expected, because I switch to [Protected Mode] almost immediately, but on some 386+ machines the floppy drive sometimes fails to finish reading files and eventually crashes, as reported by the <code class="language-plaintext highlighter-rouge">Kernel</code>, which isn’t really that bad.</p>

<p>During testing, I got some suggestions that I could perhaps fall back to the BIOS and build a wrapper, but no, I don’t think I’m going that far.</p>

<p>At least for now.</p>

<p>On the other hand, loading <code class="language-plaintext highlighter-rouge">tOSh.img</code> into all kinds of emulators is a very easy task and even fun.</p>

<p>Just download the floppy image from <a href="/resources/">Resources</a> and mount it in the emulator you like.</p>

<p>If you want to run <code class="language-plaintext highlighter-rouge">tOSh.img</code> on real hardware, you’ll have to transfer the image to a hard drive on the hardware you’re using and use something like <code class="language-plaintext highlighter-rouge">RAWrite</code>, keeping in mind that it is a 1.44MB image for 3 1/2" floppy disks.</p>

<p>I also left the source code for the latest version in <a href="/resources/">Resources</a>.</p>

<p><img src="/assets/images/tOSh/tOSh-ola-ke-ase.jpeg" alt="tOSh waving at the camera" class="img-responsive" /><em>_tOSh waving at the camera_</em></p>

<p>If I ever continue with this project, I’m not going to provide any more support for old computers. Instead, I’ll jump directly to <a href="[https://wiki.osdev.org/Setting_Up_Long_Mode](https://wiki.osdev.org/Setting_Up_Long_Mode)">Long Mode</a>, enable a modern video mode, configure multiple CPUs, unlock everything the Intel architecture has to offer, and rewrite everything that is incomplete in <code class="language-plaintext highlighter-rouge">tOSh</code>.</p>

<h1 id="references">References</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="operating systems" /><summary type="html"><![CDATA[Well, here it is: the "tOSh Operating System, Huh?" Operating System, running on different machines and emulators]]></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="en"><title type="html">tOSh for x86 - i386+ | Part V | Filesystem and Floppy Drive Driver</title><link href="https://0x705h.com/en/filesystem-and-floppy-drive" rel="alternate" type="text/html" title="tOSh for x86 - i386+ | Part V | Filesystem and Floppy Drive Driver" /><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/filesystem-and-floppy-drive"><![CDATA[<p>To download the Operating System <a href="/resources/">tOSh</a>, you can do it from the <a href="/resources/">Resources</a> section of this same site.</p>

<h1 id="parts">Parts</h1>

<p>This series of articles will have the following parts:</p>

<ul>
  <li><a href="/en/codeando-un-bootloader">Part I   - Bootloader</a></li>
  <li><a href="/en/modo-protegido">Part II  - Stage 1</a></li>
  <li><a href="/en/saltando-a-c">Part III - Jumping to C</a></li>
  <li><a href="/en/interrupciones">Part IV  - Interrupts</a></li>
  <li><a href="/en/sistema-de-archivos-y-disquetera">Part V   - Filesystem and Floppy Drive Driver</a> <strong>&lt;— You’re here</strong></li>
  <li><a href="/en/tOSh-corriendo-en-maquinas">Part VI - Booting on different types of machines</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="programming-a-driver">Programming a Driver</h1>

<p>Have you ever wondered what the fuck a driver is?<br /></p>

<p>It’s a pain in the ass, that’s what it is. ;-( – But it’s a pain in the ass that we have to program, otherwise nothing fucking works. 3;-D !</p>

<p>A shitload of years went by and life put a stop to the development of <code class="language-plaintext highlighter-rouge">tOSh</code>, the revolutionary Operating System that’s going to change the life of I don’t know who, but it sure as hell changed mine.</p>

<p>We cleared the ground so we could finally start doing something useful –after years!– with our operating system. And one of the useful things a computer actually does is, indeed, store files.</p>

<p>In <code class="language-plaintext highlighter-rouge">tOSh</code>, for now, we don’t even have a hard drive. We can’t even save a file on the <strong>same floppy disk</strong> we boot from because, first of all, we’re not controlling the floppy drive, let alone do we have a filesystem to save anything!</p>

<p>At some point, I thought that controlling the floppy on x86 was going to be easier than putting together a hard-drive driver.</p>

<p>Since I decided to go with the floppy because <strong>I LIKE FLOPPY DISKS</strong>, I don’t really know yet whether it would’ve been better to deal with <strong>HDDs</strong> or the floppy drive, but getting a working floppy-drive driver up and running was surprisingly harder than I expected.</p>

<p>While developing <code class="language-plaintext highlighter-rouge">tOSh</code>, I generally use <code class="language-plaintext highlighter-rouge">qemu</code> and <code class="language-plaintext highlighter-rouge">bochs</code> to test that things are working, including the floppy drive. But when I test some version of the operating system on real hardware, like my [1997 Machine], or some of the machines at the hackerspace where I’m a member, or friends who have <strong>really old hardware</strong>, things get even more complicated.</p>

<p>Join me on this adventure to program, as generically as possible, a driver for floppy disk controllers, <code class="language-plaintext highlighter-rouge">FDC</code> (Floppy Disk Controller), and a <strong>filesystem</strong> for writing data non-stop, 70s style.</p>

<h2 id="floppy-drive-driver">Floppy Drive Driver</h2>

<p>Let’s start with some #dEfInEz:</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> turns the motor on and off, selects the active drive, and enables IRQ/DMA.</li>
  <li><strong>FDC_MSR</strong> is read-only and tells you what state the controller is in.</li>
  <li><strong>FDC_FIFO</strong> is the 1-byte buffer through which commands go in and results come out. The entire FDC protocol goes through this single port, one byte at a time.</li>
  <li><strong>FDC_CCR</strong> defines the "speed" of the transfer.</li>
</ul>

<h2 id="protocol">Protocol</h2>

<p>The protocol has three parts or phases.</p>
<ul>
  <li><strong>command phase</strong>, you send bytes.</li>
  <li><strong>execution phase</strong>, the controller does extremely important stuff.</li>
  <li><strong>result phase</strong>, the controller sends you back the status of the previous operation.</li>
</ul>

<p>The MSR tells you which part you’re in by looking at the <code class="language-plaintext highlighter-rouge">RQM</code> (Request For Master) and <code class="language-plaintext highlighter-rouge">DIO</code> (Data Input/Output) bits:</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> means <code class="language-plaintext highlighter-rouge">RQM=1, DIO=0</code>. The controller is waiting for a byte. To read a result, the condition changes to <code class="language-plaintext highlighter-rouge">(msr &amp; 0xD0) == 0xD0</code>, which adds <code class="language-plaintext highlighter-rouge">CB=1</code> (controller busy) to <code class="language-plaintext highlighter-rouge">RQM=1, DIO=1</code>: the FDC has a byte ready and is processing a command (hence the busy).</p>

<p>I do all of this with <strong>polling</strong> with a timeout. If the FDC doesn’t respond, the <a href="https://en.wikipedia.org/wiki/Kernel_%28operating_system%29">Kernel</a> will hang forever. Each <code class="language-plaintext highlighter-rouge">fdc_write</code> / <code class="language-plaintext highlighter-rouge">floppy_read_byte</code> has 100 ticks of leeway (at 100Hz, one second), and if it can’t do it, it reports it and aborts.</p>

<p>The <code class="language-plaintext highlighter-rouge">inb(0x80)</code> is needed –on real hardware– to perform an ISA I/O delay. The bus needs X amount of time between polls.</p>

<p><strong><em>*Coughs*</em></strong> <br /> Let’s talk about emulators.</p>

<p>Have you noticed that sometimes some people –fukken beautiful people in this fucking world– are obsessed with emulating entire architectures with <code class="language-plaintext highlighter-rouge">clock</code>-level accuracy? Well, <code class="language-plaintext highlighter-rouge">qemu</code> and <code class="language-plaintext highlighter-rouge">bochs</code> aren’t those.</p>

<p>Right now we’re talking about <code class="language-plaintext highlighter-rouge">x86</code>, but on other architectures or consoles, like <strong>Super Nintendo</strong> or I don’t know, <strong>Playstation</strong>, there are several ways of approaching emulation. Maybe with the <strong>SNES</strong>, since it’s a ROM, it’s not so obvious, but with <strong>CDROM</strong>, where there’s a motor involved (like with a floppy), things change.</p>

<p>If I program a controller for some piece of shit that’s emulated with <code class="language-plaintext highlighter-rouge">qemu</code>, I’m missing all the <strong>physical</strong> peculiarities that the motor and the RPMs of the CD or the magnetic platter of the floppy disk have. In this case, we’re talking about a floppy drive, where there’s a <strong>motor</strong> (!) spinning and taking a few milliseconds to reach a stable rotational speed.</p>

<p>When I was programming this driver and the Operating System in general, it worked in <code class="language-plaintext highlighter-rouge">qemu</code>, and when I tried to run it on real hardware, <strong>it fucking didn’t work at all</strong>.</p>

<p>That’s why it’s incredibly important to follow the datasheets for whatever the hell you’re programming, and if you’re lucky, to dig up information that other people have researched before, like <strong>Josh Cole</strong> from <a href="https://floppy.cafe/">floppy.cafe</a>, who collected practically 90% of everything I needed to develop the driver in a single place. What a fucking champ.</p>

<p>In the age of Artificial Intelligence, knowing where to find the documentation it was trained on may be even better than blindly asking it for an implementation.</p>

<p>I guarantee it.</p>

<h2 id="reset-and-initialization">Reset and Initialization</h2>

<p>The FDC startup sequence in <code class="language-plaintext highlighter-rouge">floppy_init()</code> follows the order from the datasheet:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>1. Unmask IRQ6 on the PIC
2. Configure data rate (CCR = 0x00)
3. DOR = enable + irq, motor off
4. Reset: DOR = 0x00
5. I/O delay (4x writes to 0x80)
6. DOR = enable + irq (end of reset)
7. Wait for IRQ6 (the FDC reset generates an IRQ)
8. 4x SENSE INTERRUPT
9. SPECIFY
10. CONFIGURE
11. Motor on -&gt; RECALIBRATE -&gt; motor off
12. Test read (sector 0, head 0, sector 1)
</code></pre></div></div>

<p>The only relevant thing here is why we do <strong>4 Sense Interrupt</strong> after the reset. This is because I left the controller in "drive polling" mode: when the FDC performs a reset with that mode active, it generates a pending status for each of the 4 drives it can have (A, B, C, or D floppies), even if you only have one connected. If you don’t drain those 4 results with <code class="language-plaintext highlighter-rouge">floppy_sense_interrupt()</code>, the controller is left with crap in the queue and you have to exhaust the data in it.</p>

<p>I spent quite some time trying to understand why the drive kept hanging on me, and one of the reasons was this.</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> has no result phase and doesn’t generate an IRQ, so once the 3 bytes have been sent, that’s it. The values:</p>

<ul>
  <li>
    <p><strong>Byte 1 (<code class="language-plaintext highlighter-rouge">0x8f</code>)</strong>: high nibble = SRT (Step Rate Time, how long each head step takes, in inverse units – higher values = faster steps), low nibble = HUT (Head Unload Time, how long it waits before "releasing" the head after an operation).</p>
  </li>
  <li>
    <p><strong>Byte 2 (<code class="language-plaintext highlighter-rouge">0x1e</code>)</strong>: high bits = HLT (Head Load Time, how long it takes to settle the head before reading/writing), low bit = NDMA. <code class="language-plaintext highlighter-rouge">NDMA=0</code> is the important one: it tells the FDC that we’re going to use DMA for data transfers, not PIO.</p>
  </li>
</ul>

<p>I use DMA transfers because (I think this is commented in <code class="language-plaintext highlighter-rouge">floppy.h</code>) because <code class="language-plaintext highlighter-rouge">bochs</code> doesn’t support PIO transfers, only DMA.</p>

<p>If you forget this bit, the FDC expects you to read each byte manually from the FIFO, and the whole DMA pipeline we build further down becomes completely useless.</p>

<p>Since the reset clears this configuration, we have to send it again on every <code class="language-plaintext highlighter-rouge">floppy_init()</code>.</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> enables three things: <strong>EIS</strong> (Enable Implied Seek, the FDC performs the seek automatically before a read/write without you explicitly asking for it), <strong>EFIFO=0</strong> (which, counterintuitively, <strong>enables</strong> the controller’s internal 16-byte FIFO instead of disabling it – the bit’s name is the opposite of what you’d expect) and <strong>FIFOTHR=7</strong>, the 8-byte threshold that triggers a DMA/memory access. This gives the system some leeway so it doesn’t lose bytes if there’s a bit of bus latency.</p>

<p><code class="language-plaintext highlighter-rouge">CONFIGURE</code>, like <code class="language-plaintext highlighter-rouge">SPECIFY</code>, also doesn’t generate an IRQ or have a result phase.</p>

<h2 id="motor-and-timing">Motor and 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 is more than what <code class="language-plaintext highlighter-rouge">osdev</code> documents (it suggests 300ms for 3.5" and even says 50ms is enough). In my real setup, 50ms wasn’t enough and I was getting intermittent seek failures. I preferred to play it safe with half a second instead of chasing the theoretical minimum – the cost of waiting a little longer is insignificant compared to having a failed read in the middle of boot.</p>

<p>For this I need a timer that doesn’t depend on counting CPU cycles by hand, so enter <code class="language-plaintext highlighter-rouge">pit.c</code>.</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>The <a href="https://wiki.osdev.org/Programmable_Interval_Timer">PIT</a> runs internally at 1193182 Hz (<code class="language-plaintext highlighter-rouge">PIT_FREQUENCY</code>). With <code class="language-plaintext highlighter-rouge">pit_init(100)</code>, I configure a divider that triggers an IRQ0 every 10ms, meaning 100 ticks per second. <code class="language-plaintext highlighter-rouge">pit_ticks</code> is a global counter incremented from <code class="language-plaintext highlighter-rouge">pit_irq_handler()</code>, and everything else in the system (<code class="language-plaintext highlighter-rouge">pit_wait_ms</code>, the FDC timeouts, <code class="language-plaintext highlighter-rouge">floppy_wait_irq</code>) relies on that counter instead of blindly busy-waiting.</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 at 100Hz means a 2-second timeout for the FDC’s IRQ6 to arrive – deliberately generous, because I don’t want a real floppy (which can take some time to spin up and seek) to trigger a false timeout.</p>

<p>Notice the <code class="language-plaintext highlighter-rouge">hlt</code> in the loop: instead of spin-waiting and burning CPU, the processor stops until the next interrupt (either from the PIT or the FDC), which is exactly what a real kernel should do when waiting for I/O.</p>

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

<p><code class="language-plaintext highlighter-rouge">RECALIBRATE</code> and <code class="language-plaintext highlighter-rouge">SEEK</code> generate an IRQ but <strong>don’t have their own result phase</strong>. The status has to be requested separately with <code class="language-plaintext highlighter-rouge">SENSE INTERRUPT</code>:</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> moves the head to track 0 (useful for "resynchronizing" the known physical position), while <code class="language-plaintext highlighter-rouge">SEEK</code> moves it to a specific cylinder. In both cases, after the IRQ, I do <code class="language-plaintext highlighter-rouge">SENSE INTERRUPT</code> and validate two things: the <code class="language-plaintext highlighter-rouge">Seek End</code> bit (<code class="language-plaintext highlighter-rouge">st0 &amp; 0x20</code>) and that the reported cylinder (<code class="language-plaintext highlighter-rouge">cyl</code>) matches the expected one (0 for recalibrate, the requested cylinder for seek). If anything goes wrong, I spit it out to the console.</p>

<h2 id="sector-read-and-write">Sector Read and Write</h2>

<p>The read/write command sends 9 bytes in total:</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> on the command enables <code class="language-plaintext highlighter-rouge">MT</code> (Multi-Track, the controller can automatically continue onto the next head if it crosses the EOT) and <a href="https://en.wikipedia.org/wiki/Modified_frequency_modulation">MFM</a> (the standard modulation for floppies and magnetic storage from double density upwards). <code class="language-plaintext highlighter-rouge">N=2</code> tells the FDC "each sector is 512 bytes" using the chip’s standard size table (0=128B, 1=256B, 2=512B…). <code class="language-plaintext highlighter-rouge">EOT=18</code> is the number of sectors per track in the 1.44MB geometry.</p>

<p>After the IRQ (which arrives when the DMA transfer is complete, not before), comes the <strong>result phase</strong> of 7 bytes (<code class="language-plaintext highlighter-rouge">ST0</code> through <code class="language-plaintext highlighter-rouge">ST2</code> plus C/H/R/N returned), of which I only check the first three:</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>Any bit set in <code class="language-plaintext highlighter-rouge">ST1</code>/<code class="language-plaintext highlighter-rouge">ST2</code> is treated as a total operation failure – I don’t try to distinguish "it’s a recoverable CRC error" from "the sector doesn’t exist"; I simply retry the whole thing.</p>

<p><code class="language-plaintext highlighter-rouge">floppy_write_sector_impl</code> is almost a mirror image of the read, changing the command (<code class="language-plaintext highlighter-rouge">FDC_CMD_WRITE_DATA | 0xC0</code>, MFM with no need for MT… although in the code I left the same <code class="language-plaintext highlighter-rouge">0xC0</code>) and the DMA direction.</p>

<h2 id="chs-and-lba-conversion">CHS and LBA Conversion</h2>

<p>So I don’t have to think about cylinder/head/sector anywhere else in the kernel, I expose <code class="language-plaintext highlighter-rouge">floppy_read_lba</code> / <code class="language-plaintext highlighter-rouge">floppy_write_lba</code>:</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>This assumes the fixed geometry of a 1.44MB floppy: 2 heads, 18 sectors per track, 80 cylinders (2×18×80 = 2880 total sectors, hence the <code class="language-plaintext highlighter-rouge">if (lba &gt;= 2880) return false;</code>). There’s no geometry detection from the media descriptor and no support for other formats – for <code class="language-plaintext highlighter-rouge">tOSh</code>, a single type of floppy is more than enough, and hardcoding the geometry saves an entire layer of abstraction that I don’t need.</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>Real floppies are prone to transient errors: dust, worn-out media, a head that didn’t settle properly on the first attempt, putting <strong>A FRIDGE MAGNET AS A PAPERWEIGHT ON TOP OF A STACK OF FLOPPIES</strong>, etc…</p>

<p>Instead of failing immediately on the first error, I retry 3 times, and between each attempt I do a full <code class="language-plaintext highlighter-rouge">RECALIBRATE</code> to force the head to resynchronize its physical position – if the error was caused precisely by the head being out of position, the recalibrate fixes it before the next attempt.</p>

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

<p>The FDC doesn’t deliver the read data byte by byte through the FIFO when <code class="language-plaintext highlighter-rouge">NDMA=0</code> (see Specify above) – it uses the 8237 DMA chip to write directly to memory while the CPU does something else (or, in our case, waits in a <code class="language-plaintext highlighter-rouge">hlt</code>).</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>A few points are worth highlighting here:</p>

<ul>
  <li>
    <p>The <strong>flip-flop</strong> is an internal bit in the 8237 that indicates whether the next byte written to an address/count port is the low byte or the high byte. Since this state is shared between channels, you have to explicitly reset it (<code class="language-plaintext highlighter-rouge">DMA_CLEAR_FF</code>) before writing both the address and the count – otherwise, you can end up writing the high byte where the low byte was supposed to go and build an address that is basically anything.</p>
  </li>
  <li>
    <p>The <strong>page register</strong> (<code class="language-plaintext highlighter-rouge">DMA_PAGE_CH2 = 0x81</code>) is a remnant of the fact that the original 8237 only handles 16-bit addresses for the offset. To reach 24-bit memory addresses (as a real PC needs), the high byte of the address is written separately, into the page register for the corresponding channel. This is also why there’s a restriction that <strong>the buffer cannot cross a 64KiB boundary</strong>: the 8237 increments the 16-bit offset for every byte transferred, but never touches the page register during the transfer. If the buffer starts near the end of a 64KB page and crosses it, the DMA doesn’t advance to the next page – it wraps the offset around and starts overwriting the beginning of the same 64KB page. That’s why I set up the floppy buffer like this:</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>Aligning it to 512 bytes (the size of a sector) guarantees that you’ll never have a 512-byte buffer that starts, for example, at address <code class="language-plaintext highlighter-rouge">0xFFF00</code> and ends up crossing into <code class="language-plaintext highlighter-rouge">0x10000</code> in the next 64KB block. The linker’s <code class="language-plaintext highlighter-rouge">.dma</code> section (see Part III) puts this buffer in its own 4KB-page-aligned region, away from any other data, so <strong>in practice, boundary-crossing never happens, which means the driver doesn’t need to perform any runtime checks</strong>.</p>

<ul>
  <li>
    <p>The <strong>mode</strong> (<code class="language-plaintext highlighter-rouge">0x46</code> for reading, <code class="language-plaintext highlighter-rouge">0x4A</code> for writing) follows the 8237’s naming convention from the peripheral’s perspective, not the memory’s: <code class="language-plaintext highlighter-rouge">dma_read()</code> means "the FDC reads from the disk and writes to memory" (which effectively fills your buffer in <code class="language-plaintext highlighter-rouge">floppy_read_sector_impl</code>), while <code class="language-plaintext highlighter-rouge">dma_write()</code> means "memory is read and written to the FDC" (to send data to the disk). It’s an easy name to get confused if you think in terms of the CPU instead of the peripheral – here <code class="language-plaintext highlighter-rouge">dma_read</code>/<code class="language-plaintext highlighter-rouge">dma_write</code> are named from the perspective of "what does the FDC do with the data?", which is consistent with how Intel documents the chip.</p>
  </li>
  <li>
    <p><code class="language-plaintext highlighter-rouge">count--</code> because the 8237 expects the count as <code class="language-plaintext highlighter-rouge">N-1</code> (you transfer <code class="language-plaintext highlighter-rouge">count</code> bytes, but the internal register counts down from <code class="language-plaintext highlighter-rouge">count-1</code> to 0).</p>
  </li>
</ul>

<p>So, roughly, <code class="language-plaintext highlighter-rouge">floppy_read_sector</code> with <code class="language-plaintext highlighter-rouge">dma_read()</code> sets up channel 2 pointing at the buffer for 512 bytes, the <code class="language-plaintext highlighter-rouge">READ DATA</code> command is sent to the FDC, the transfer happens in the background while the CPU waits with <code class="language-plaintext highlighter-rouge">hlt</code>, and when it finishes, the FDC generates IRQ6, which is handled by <code class="language-plaintext highlighter-rouge">floppy_wait_irq</code>.</p>

<p>There’s a lot going on here, and maybe some things are easier to understand by reading the source code.</p>

<p>As I say at the beginning of this whole series of articles, if you want, you can download the source from the <a href="/resources/">Resources</a> page.</p>

<h1 id="the-filesystem">The Filesystem</h1>

<p><img src="/assets/images/tOSh/tOSh-listing.jpeg" alt="Probando el Filesystem en mi máquina del 97'" class="img-responsive" /><em>Testing the Filesystem on my ‘97 Machine</em></p>

<h2 id="lets-get-it-workin">Let’s Get It Workin'</h2>

<p>For <code class="language-plaintext highlighter-rouge">tOSh</code> there are no blocks, no FAT, no directory tree, no journaling, no long names, and no paths.</p>

<p><code class="language-plaintext highlighter-rouge">tOSh</code> doesn’t give a shit about any of that, not even about the consistency of your data. <code class="language-plaintext highlighter-rouge">tOSh</code> is an Operating System <strong>ONLY FOR THE CRAZY AND THE BRAVE</strong>.</p>

<p>Having said that, and with that clarification out of the way, I’ll start telling you how I built it.</p>

<p>There’s a fixed-size directory with up to 256 files, each with a name, a starting sector, and a size. Period.</p>

<p>The reasoning behind this is the same as with the fixed floppy geometry: any feature from a real filesystem (FAT, ext2, whatever) adds a layer of indirection – blocks, linked lists of clusters, free-space bitmaps – that solves problems <code class="language-plaintext highlighter-rouge">tOSh</code> doesn’t have yet, such as real fragmentation on a disk that gets filled and emptied over and over. For what I need (persisting a handful of small files on a 1.44MB floppy during kernel development), a flat directory is enough.</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>The filesystem starts at sector 256, well after the kernel (which ends at 136). The gap I leave between 137 and 255 (119 sectors, ~59KB) is room for the kernel to grow while I keep developing it without colliding with the filesystem – remember that the <code class="language-plaintext highlighter-rouge">Makefile</code>/<code class="language-plaintext highlighter-rouge">build.sh</code> from Part III checks that the kernel doesn’t exceed the 128 sectors assigned to it, so this reserved range is the actual buffer before I have to move the whole layout.</p>

<p>The gap between 289 and 319 (31 sectors) is the same thing, but for the directory: <code class="language-plaintext highlighter-rouge">FS_DIRECTORY_SECTORS</code> is fixed at 32, but the actual size occupied by <code class="language-plaintext highlighter-rouge">struct fs_file[FS_MAX_FILES]</code> may end up using less. This margin saves me from having to recalculate <code class="language-plaintext highlighter-rouge">FS_DATA_START</code> every time I change <code class="language-plaintext highlighter-rouge">FS_MAX_FILES</code> or the size of <code class="language-plaintext highlighter-rouge">fs_file</code>.</p>

<h2 id="the-superblock">The 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> is <code class="language-plaintext highlighter-rouge">0x48534f54</code>, which read as little-endian ASCII bytes gives <code class="language-plaintext highlighter-rouge">TOSH</code>. <code class="language-plaintext highlighter-rouge">fs_is_initialized()</code> reads sector 256 and compares that magic – if it doesn’t match, it assumes the floppy was never formatted with this filesystem and that’s it; it doesn’t try to "recover" or interpret anything else from that sector. <code class="language-plaintext highlighter-rouge">__attribute__((packed))</code> is necessary so the struct occupies exactly the bytes I declare, without any padding the compiler would add for alignment.</p>

<h2 id="the-directory">The Directory</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>The entire directory is an array of 256 entries that lives in RAM while the kernel is running and is persisted in its entirety to disk with <code class="language-plaintext highlighter-rouge">fs_save_directory()</code> every time something changes (create, write, delete). There is no separate "free entries" bitmap – an entry is free if <code class="language-plaintext highlighter-rouge">used == 0</code>, and finding a file (<code class="language-plaintext highlighter-rouge">fs_find</code>) is simply a matter of walking the array and comparing names.</p>

<p>This means that every operation that modifies the filesystem rewrites all 32 sectors of the directory, even if only one entry changed. It’s clearly non-optimal in terms of I/O, but against a floppy that is already painfully slow, the difference between writing 1 sector or 32 doesn’t change the user experience, and I save myself from having to track which specific directory sector corresponds to which entry.</p>

<h2 id="not-managing-free-space">Not Managing Free Space</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>Space allocation is about as primitive as it gets: I go through all used files, find the highest <code class="language-plaintext highlighter-rouge">start_sector + sectors_occupied</code>, and the next file starts there. There is no free-sector bitmap, no free-list, no compression.</p>

<p>The direct consequence of this –and a conscious decision, <strong>IT’S NOT A BUG, IT’S A FEATURE</strong>– is that <code class="language-plaintext highlighter-rouge">fs_delete()</code> <strong>doesn’t free or recycle the space</strong>.</p>

<p>Deleting a file only clears its directory entry (<code class="language-plaintext highlighter-rouge">used = 0</code>); the sectors it occupied stay there, ignored forever by <code class="language-plaintext highlighter-rouge">fs_next_free_sector()</code>, because that function doesn’t look at entries with <code class="language-plaintext highlighter-rouge">used == 0</code>, it only looks at the furthest point occupied by <strong>currently</strong> live files… but that furthest point may have been left behind by a file that no longer exists. In other words: creating and deleting files leaves holes that will never be used again until you reformat the entire disk with <code class="language-plaintext highlighter-rouge">fs_format()</code>. This used to be called <strong>disk fragmentation</strong> and it’s what the "Windows Defragmenter" was supposed to fix. MS-DOS and Windows 9x also suffered from it a bit, for other reasons.</p>

<p>For the current state of <code class="language-plaintext highlighter-rouge">tOSh</code>, this is acceptable: the filesystem is not intended (yet, who knows) for intensive, prolonged write/delete usage, but rather to persist a handful of configuration files or system binaries. The day this becomes a real problem (kkjjj I mean never, long live <code class="language-plaintext highlighter-rouge">tOSh</code>), the simplest solution without redesigning everything would be to add a free-sector bitmap, or migrate to a block-based scheme with a free-list – but that would completely change the filesystem.</p>

<h2 id="operations">Operations</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> and <code class="language-plaintext highlighter-rouge">fs_delete</code> are pretty straightforward given the design I explained above:</p>

<ul>
  <li><strong><code class="language-plaintext highlighter-rouge">fs_create</code></strong> looks for a free entry, assigns it the next sector via <code class="language-plaintext highlighter-rouge">fs_next_free_sector()</code>, stores the name, and persists the directory. The size starts at 0 – there is no data yet, only the position reservation.</li>
  <li><strong><code class="language-plaintext highlighter-rouge">fs_write</code></strong> splits the data into 512-byte sectors (padding the last one with zeros if it’s not an exact multiple) and writes them sequentially starting from <code class="language-plaintext highlighter-rouge">start_sector</code>. It updates <code class="language-plaintext highlighter-rouge">size</code> and persists the entire directory again.</li>
  <li><strong><code class="language-plaintext highlighter-rouge">fs_read</code></strong> does the opposite: it calculates how many sectors the file occupies from <code class="language-plaintext highlighter-rouge">size</code>, reads them all, and copies only the valid bytes (not the zero padding) into the caller’s buffer.</li>
  <li><strong><code class="language-plaintext highlighter-rouge">fs_delete</code></strong> clears the entry without touching the data sectors (see just above).</li>
</ul>

<p>None of these four functions support files growing beyond the free space they had when they were created – there is no concept of "extending" a file beyond whatever space was available when it was first written contiguously, because <code class="language-plaintext highlighter-rouge">fs_write</code> always writes starting from <code class="language-plaintext highlighter-rouge">start_sector</code>, assuming that the space has already been reserved by <code class="language-plaintext highlighter-rouge">fs_create</code> + the creation order of the other files.</p>

<h2 id="known-limitations">Known Limitations</h2>

<p>So I have it documented and it doesn’t get lost over time when I come back to implementing things in <code class="language-plaintext highlighter-rouge">tOSh</code> three years from now:</p>

<ul>
  <li>Fixed maximum filesystem size of <code class="language-plaintext highlighter-rouge">FS_TOTAL_SECTORS = 2880</code> (the full 1.44MB of the floppy, without distinguishing that part of it is already used by the boot/kernel – making sure you don’t overwrite that is the responsibility of setting <code class="language-plaintext highlighter-rouge">FS_DATA_START</code> correctly, not something the filesystem checks at runtime). Still, maybe if I come back to touch or rewrite the filesystem, it would also be a good idea to implement support for hard drives and take that into account to make it a bit more serious. We’ll see.</li>
  <li>Non-hierarchical filenames: <code class="language-plaintext highlighter-rouge">FS_FILENAME_MAX = 32</code> bytes, with no path separator, everything lives in the same hardcoded namespace.</li>
  <li>No file fragmentation: each file occupies a contiguous range of sectors. This simplifies the code enormously (there’s no need to track chains of blocks), but it means that large files in a filesystem with lots of holes from previous deletes will eventually not fit, even if there is plenty of "total space" spread across gaps. Eventually, after the filesystem has created and deleted enough files, it’s going to crash :)</li>
  <li>No journaling or any kind of recovery mechanism for a power failure in the middle of an <code class="language-plaintext highlighter-rouge">fs_save_directory()</code>. If the directory write gets interrupted halfway through, you’re left with a partial version and you can even lose the entire filesystem because the superblock can get corrupted too.</li>
</ul>

<p>Next time, we’ll run <code class="language-plaintext highlighter-rouge">tOSh</code> on several machines, emulated and real, borrowed and/or donated for that purpose by dear nerd friends and fellow geeks whom I respect and love very much.</p>

<h1 id="references">References</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="operating systems" /><category term="floppies" /><summary type="html"><![CDATA[Let's make our files persistent, but only on a 3.5" floppy disk.]]></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/en/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/en/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="/en/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/en/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/en/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/en/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="en"><title type="html">tOSh for x86 - i386+ | Part IV | Interrupts</title><link href="https://0x705h.com/en/interrupts" rel="alternate" type="text/html" title="tOSh for x86 - i386+ | Part 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>To download the Operating System <a href="/resources/">tOSh</a>, you can do it from the <a href="/resources/">Resources</a> section of this same site.</p>

<h1 id="parts">Parts</h1>

<p>This series of articles will have the following parts:</p>

<ul>
  <li><a href="/en/codeando-un-bootloader">Part I   - Bootloader</a></li>
  <li><a href="/en/modo-protegido">Part II  - Stage 1</a></li>
  <li><a href="/en/saltando-a-c">Part III - Jumping to C</a></li>
  <li><a href="/en/interrupciones">Part IV  - Interrupts</a> <strong>&lt;— You’re here</strong></li>
  <li><a href="/en/sistema-de-archivos-y-disquetera">Part V   - Filesystem and Floppy Drive Driver</a></li>
  <li><a href="/en/tOSh-corriendo-en-maquinas">Part VI - Booting on different types of machines</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>The fact that both old-school and modern PC architectures are based on [Interrupts] is precisely because of this chip, Intel’s <a href="https://www.eeeguide.com/8259a-programmable-interrupt-controller/">Chip 8259a PIC</a>.</p>

<p>An interrupt is a signal received by the CPU that temporarily stops whatever the CPU is computing so it can <strong><em>handle (or manage)</em></strong> an event with a higher priority.</p>

<p>For a PC to interact with any sufficiently complex electronic device, there has to be some kind of interaction with a CPU, to a greater or lesser extent. One option was to simply go around asking each device <strong>"Hey!, what’s up?"</strong>.</p>

<p>However, constantly asking connected devices whether they need anything wastes time and resources on every single question. This strategy is called (<a href="https://en.wikipedia.org/wiki/Polling_(computer_science)">Polling</a>).</p>

<p>One of the solutions proposed to avoid <a href="https://en.wikipedia.org/wiki/Polling_(computer_science)">Polling</a> is simply to let the CPU do its thing and run its programs normally, and wait for a signal when it’s time to attend to some action from someone. The two most common types of CPU interrupts are, for example:</p>

<ul>
  <li>
    <p><strong>Hardware Interrupts</strong>: They are sent asynchronously by physical devices such as mice, keyboards, and hard drives, which need attention from time to time. For example, pressing a key on a PC keyboard triggers a hardware interrupt and <strong>INTERRUPTS EVERYTHING</strong>, and you have to let whatever it is executing, whether it’s a <a href="https://en.wikipedia.org/wiki/Kernel_%28operating_system%29">Kernel</a> or a program in "Userspace", stop and handle the interrupt immediately.</p>
  </li>
  <li>
    <p><strong>Software Interrupts</strong>: Exceptions or Traps, generated internally by the processor or by a running program due to an error (such as dividing by zero) or by a request to the operating system via a system call.</p>
  </li>
</ul>

<h2 id="how-do-you-handle-an-interrupt">How Do You Handle an Interrupt?</h2>

<p><strong>Diego Silicio</strong> reminds us in his "<strong>interrupted sextillas</strong>":</p>

<p>Original:
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>Translation:</p>

<p>here we come to sing to you<br />
in interrupted meter<br />
you, my dense friend<br />
but never 'IS' like a human<br />
PICante is the countryman<br />
on his line he has joined<br /></p>

<p><strong>Explanation</strong>: The poem is packed with technical and linguistic wordplay. “PICante” combines PIC (Programmable Interrupt Controller) with picante (“spicy”), while “paisano” keeps the gaucho/countryman voice of the poem.</p>

<p>There is also a layered joke in “usted, amigo tupido / pero nunca ‘ES’ como humano”. Tupido can mean dense/thick, but tupido + ES visually produces “EStupido”, evoking estúpido (“stupid”). At the same time, ES is literally the Spanish verb es (“is”), so “nunca ES como humano” means “it is never like a human.” The capitalization makes the hidden EStupido construction more apparent.</p>

<p>So the poem simultaneously talks about interrupts and PICs while disguising technical terminology inside deliberately gaucho-style verse and wordplay.</p>

<p>The cryptic gaucho has interrupted us, but once we finish this exasperating article, his song will be attended to by everyone.</p>

<p>When an interrupt occurs, the CPU <strong>should</strong> save the execution context somewhere. The stack is a good place.</p>

<p>So: Save context -&gt; Execute an Interrupt Service Routine <a href="https://wiki.osdev.org/Interrupt_Service_Routines">ISR</a> (we’re getting to this very soon) -&gt; Restore the context and keep working.</p>

<p>Before I can even start putting together the <strong>interrupt routines</strong>, which we’re only going to <strong>handle</strong> a couple of for now, I still have to explain even more things, because in the world of the <strong>PC</strong>, nothing is, unfortunately, simple.</p>

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

<p>In a traditional PC, the <em>Programmable Interrupt Controller</em> or <a href="https://www.eeeguide.com/8259a-programmable-interrupt-controller/">Chip 8259a PIC</a> is the intermediary between devices and the processor.</p>

<p>Devices don’t talk directly to the processor.</p>

<p>They are connected to the PIC through interrupt lines called <em>Interrupt Request</em> or <strong>IRQs</strong>.</p>

<p>The PIC receives those signals and takes care of telling the CPU that someone is asking for attention.</p>

<p>If we read the <a href="https://www.eeeguide.com/8259a-programmable-interrupt-controller/">Chip 8259a PIC</a> datasheet for a bit, we’ll find that it has 8 IRQ lines, from IRQ0 to IRQ7, and everything is just fine until we have 9 or more devices to connect simultaneously, because we run out of them.</p>

<p>At some point in the ’80s they effectively ran out of IRQs for devices, and like good engineers, they slapped another one in there, creating a <strong>master/slave</strong> setup with two <a href="https://www.eeeguide.com/8259a-programmable-interrupt-controller/">Chip 8259a PIC</a>s connected in cascade.</p>

<table>
  <thead>
    <tr>
      <th style="text-align: left">Hardware Line</th>
      <th style="text-align: left">8259a PIC Chip</th>
      <th style="text-align: left">Default Vector</th>
      <th style="text-align: left">Protected Mode Remapped</th>
      <th style="text-align: left">Assigned Device</th>
      <th style="text-align: left">I/O Port Used</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>By using IRQ2 as the <strong>Cascade Line</strong>, we lose one IRQ, but devices attached to the <code class="language-plaintext highlighter-rouge">slave</code> can access the CPU through it.</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>Additionally, the <a href="https://www.eeeguide.com/8259a-programmable-interrupt-controller/">Chip 8259a PIC</a> allows interrupts to be <strong>masked</strong>. If we’re handling the keyboard and don’t want the floppy drive sneaking in at that moment, we tell the PIC "this particular IRQ" don’t bother with it.</p>

<p>This is what I do when configuring the IRQs: tOSh only implements 3 of them:</p>

<ul>
  <li><strong>IRQ0</strong> (timer / pit)</li>
  <li><strong>IRQ1</strong> (keyboard)</li>
  <li><strong>IRQ6</strong> (floppy)</li>
</ul>

<p>All interrupts are masked except these three, which I specifically unmask so they can be handled:</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="the-idt---interrupt-descriptor-table">The IDT - Interrupt Descriptor Table</h2>

<p>The <code class="language-plaintext highlighter-rouge">IDT</code> is a binary data structure specific to the IA-32 and x86-64 architectures and is the counterpart to the [Real Mode] and <a href="https://wiki.osdev.org/Setting_Up_Long_Mode">Long Mode</a> <code class="language-plaintext highlighter-rouge">IVT</code>.</p>

<p>Configuring the <a href="https://wiki.osdev.org/Interrupt_Descriptor_Table">IDT</a> allows us to tell the CPU where our <a href="https://wiki.osdev.org/Interrupt_Service_Routines">ISR</a>s, or Interrupt Service Routines, are located. These are the functions we have to write ourselves in order to handle interrupts triggered by the processor or by devices. It is quite similar to the structure of the <a href="https://wiki.osdev.org/Global_Descriptor_Table">GDT</a>.</p>

<p>Below is Intel’s Protected Mode Interrupt Table:</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>A common mistake is confusing the IRQs of the <a href="https://www.eeeguide.com/8259a-programmable-interrupt-controller/">Chip 8259a PIC</a> with the INTerrupts. THEY ARE NOT THE SAME!!!!!1</strong></p>

<p>An IRQ is the physical hardware line. The interrupt number is the entry the CPU uses in the IDT.</p>

<p>They are two related things, but they are not the same, and the thing that connects them is precisely the PIC’s <strong>remapping</strong>.</p>

<p>The interrupt number works as an index into the table: the interrupt arrives, the CPU looks up the corresponding entry, and jumps straight to the code we configured to handle it.</p>

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

<p>You can already smell the problem, right? The IRQ values and the CPU Interrupts (WHICH ARE NOT THE SAME THING!!!!!!!!!!1) overlap.</p>

<p>This overlap means we have to move or <strong>remap</strong> the IRQ values to after the CPU’s INTerrupt table.</p>

<p>The first 32 entries of the <a href="https://wiki.osdev.org/Interrupt_Descriptor_Table">IDT</a> (vectors 0 through 31) are reserved for exceptions from the <code class="language-plaintext highlighter-rouge">x86</code> itself (<em>_divide by zero error, general protection fault, etc.._</em>) and the PIC’s IRQs also start at 0, overlapping with them.</p>

<p>That’s why when booting our <a href="https://en.wikipedia.org/wiki/Kernel_%28operating_system%29">Kernel</a>, we have to reconfigure the PIC so that it runs above the CPU’s <strong>INTerrupt</strong> table area, like this:</p>

<table>
  <thead>
    <tr>
      <th style="text-align: right">PIC</th>
      <th style="text-align: right">Original IRQ</th>
      <th style="text-align: right">Destination 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="press-any-key-to-continue">Press Any Key to Continue…</h2>

<p>Let’s use pressing the <code class="language-plaintext highlighter-rouge">any</code> key as an example.</p>

<p>The logic of any PC keyboard, for every key pressed, generates a <code class="language-plaintext highlighter-rouge">scancode</code> for the keyboard controller.</p>

<p>This controller generates an <code class="language-plaintext highlighter-rouge">IRQ</code>, which in our case is <code class="language-plaintext highlighter-rouge">IRQ1</code> (see the table above). The one receiving this <code class="language-plaintext highlighter-rouge">IRQ</code> is the <a href="https://www.eeeguide.com/8259a-programmable-interrupt-controller/">Chip 8259a PIC</a>, which checks that the interrupt is enabled (or unmasked), marks it as pending, and sends a signal to the CPU through the INTR line.</p>

<p>The CPU accepts the interrupt and the PIC delivers the corresponding vector. If IRQ1 is remapped to vector 0x21, the CPU uses that vector to look up the IDT and see at which memory address it will find the corresponding <strong>interrupt handler</strong>.</p>

<p>This is tOSh’s; we only support a Timer, the Keyboard, and the 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>And from the routines in <code class="language-plaintext highlighter-rouge">idt.asm</code>, where we declare how we’re going to load the handler, we create this function:</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>The CPU enters the corresponding handler through our <code class="language-plaintext highlighter-rouge">extern interrupt_handler</code>, which is called from <code class="language-plaintext highlighter-rouge">irq_common</code> and makes a call to our C code, which ends up executing the <code class="language-plaintext highlighter-rouge">kbd_handler()</code> function.</p>

<p>As you can see, we save the execution context with <code class="language-plaintext highlighter-rouge">pusha</code> because whatever the CPU had been doing, we <strong>have to save it</strong> in order to do whatever we need to do and, when returning from our <code class="language-plaintext highlighter-rouge">call interrupt_handler</code>, restore the context and return from our interrupt with <code class="language-plaintext highlighter-rouge">iret</code>.</p>

<p>We’ve got everything covered. The <code class="language-plaintext highlighter-rouge">kbd_handler()</code> and everything related to the keyboard is pretty damn boring because it’s just mapping <code class="language-plaintext highlighter-rouge">scancodes</code> to keyboard functions (CTRL+C does this, SHIFT + lowercase turns it into uppercase, etc…) and as <strong>phun phakt</strong>, this piece of <a href="https://en.wikipedia.org/wiki/Kernel_%28operating_system%29">Kernel</a> took me years to develop because when I looked at what I had to code, I couldn’t be arsed, and for a couple of years I felt weird about copying some cool implementations I found from other people on GitHub, but recently I got over it and got heavily inspired. Unfortunately, I don’t remember exactly who they were today, but hey, thanks.</p>

<p>As you can see in the C function <code class="language-plaintext highlighter-rouge">interrupt_handler()</code>, there’s a call to the <code class="language-plaintext highlighter-rouge">pic_eoi()</code> function. An <code class="language-plaintext highlighter-rouge">iret</code> alone isn’t enough to indicate that we’re returning from an interrupt; we also have to tell the PIC that we’re done handling the <code class="language-plaintext highlighter-rouge">IRQ</code>.</p>

<p>The CPU finally returns to the program that was interrupted, continuing exactly at the <code class="language-plaintext highlighter-rouge">EIP</code> where it had stopped.</p>

<p>And that’s it. We’re done.</p>

<h2 id="wh0a-youre-missing-the-apic-advanced-programmable-interrupt-controller">wh0a, you’re missing the APIC! (Advanced Programmable Interrupt Controller)</h2>

<p>Oh no! Wait! I still need to make one clarification. <a href="https://wiki.osdev.org/APIC">APIC</a> is the replacement for the <a href="https://www.eeeguide.com/8259a-programmable-interrupt-controller/">Chip 8259a PIC</a>, and to this day, <code class="language-plaintext highlighter-rouge">tOSh</code> doesn’t support it.</p>

<p>In some way, supporting <a href="https://wiki.osdev.org/APIC">APIC</a> would allow us to handle, among all the things it supports, more CPUs (dual-core and up), but at this point in development I’m busy fighting with interrupt masking and programming a keyboard.</p>

<p>So we’ll leave that for later. :)</p>

<h1 id="references">References</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="operating systems" /><summary type="html"><![CDATA[This CPU is being interrupted all the time!]]></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/en/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="/en/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/en/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>