Your Title - sistema de archivos 2026-09-25T14:25:39+00:00 http://example.com/tag/sistema de archivos.html Author Here tOSh para x86 - i386+ | Parte V | Sistema de Archivos y Driver de Disquetera 2026-08-12T07:43:04+00:00 http://example.com/sistema-de-archivos-y-disquetera <p>Para descargar el Sistema Operativo <a href="/resources/">tOSh</a>, podés hacerlo desde la sección <a href="/resources/">Resources</a> de este mismo sitio.</p> <h1 id="partes">Partes</h1> <p>Esta serie de artículos tiene las siguientes partes:</p> <ul> <li><a href="/codeando-un-bootloader">Parte I - Bootloader</a></li> <li><a href="/modo-protegido">Parte II - Stage 1</a></li> <li><a href="/saltando-a-c">Parte III - Saltando a C</a></li> <li><a href="/interrupciones">Parte IV - Interrupciones</a></li> <li><a href="/sistema-de-archivos-y-disquetera">Parte V - Sistema de Archivos y Driver de Disquetera</a> <strong>&lt;— Estás acá</strong></li> <li><a href="/tOSh-corriendo-en-maquinas">Parte VI - Booteando en diferentes tipos de máquinas</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="programar-un-driver">Programar un Driver</h1> <p>¿Alguna vez te preguntaste que verga es un driver?<br /> Es una verga, eso es lo que es. ;-( – Pero es una verga que tenemos que programar, sino, no funciona una verga nada. 3;-D !</p> <p>Pasaron una banda de años y la vida detuvo el desarrollo de <code class="language-plaintext highlighter-rouge">tOSh</code>, el Sistema Operativo revolucionario que le va a cambiar la vida a no se quien, pero la mia, por seguro que lo hizo.</p> <p>Barrenamos el terreno para poder empezar a hacer algo útil –¡después de años!– con nuestro sistema operativo. Y una de las cosas útiles que una computadora hace, es efectivamente, guardar archivos. En <code class="language-plaintext highlighter-rouge">tOSh</code> por ahora ni tenemos disco rígido. Ni en el <strong>mismo disquette</strong> en el que booteamos podemos guardar un archivo porque primero, no estamos manipulando la disquetera ni mucho menos tenemos un sistema de archivos para guardar nada!</p> <p>En x86, en algún momento pensé que controlar el floppy iba a ser mas fácil que armar un driver de disco rígido. Como tomé la decision de ir por el floppy porque <strong>ME GUSTAN LOS DISQUETTES</strong> no se bien por ahora si hubiera sido mejor manejar <strong>HDDs</strong> o la disquetera, pero hacer un driver funcionando para disquetera fue soprendentemente más difícil de lo que esperaba.</p> <p>Mientras desarrollo <code class="language-plaintext highlighter-rouge">tOSh</code> generalmente uso <code class="language-plaintext highlighter-rouge">qemu</code> y <code class="language-plaintext highlighter-rouge">bochs</code> para probar que las cosas estén funcionando, incluyendo la disquetera. Pero cuando pruebo alguna versión del sistema operativo en hardware real como mi [Máquina de 1997] o algunas otras del hackerspace en el que soy miembro o amigos que tienen hardware <strong>realmente viejo</strong> las cosas se van complicando aún más.</p> <p>Acompañenmé en esta aventura para programar lo más genéricamente posible, un driver para controladoras de disquetteras, <code class="language-plaintext highlighter-rouge">FDC</code> (Floppy Disk Controller) y un <strong>sistema de archivos</strong> para escribir datita sin parar, 70s style.</p> <h2 id="driver-de-disquetera">Driver de Disquetera</h2> <p>Arranquemos con unos #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> prende y apaga el motor, selecciona el drive activo y habilita IRQ/DMA.</li> <li><strong>FDC_MSR</strong> es de solo lectura y te dice en qué estado está el controlador.</li> <li><strong>FDC_FIFO</strong> es el buffer de 1 byte por donde entran los comandos y salen los resultados. Todo el protocolo del FDC pasa por este único puerto, un byte a la vez.</li> <li><strong>FDC_CCR</strong> define la "velocidad" de transferencia.</li> </ul> <h2 id="protocolo">Protocolo</h2> <p>El protocolo tiene tres partes o fases.</p> <ul> <li><strong>command phase</strong>, mandás bytes.</li> <li><strong>execution phase</strong> el controlador ejecuta cosas importantísimas</li> <li><strong>result phase</strong> el controlador te devuelve status de la operación anterior.</li> </ul> <p>El MSR te dice en qué parte estás mirando los bits <code class="language-plaintext highlighter-rouge">RQM</code> (Request For Master) y <code class="language-plaintext highlighter-rouge">DIO</code> (Data Input/Output):</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> es <code class="language-plaintext highlighter-rouge">RQM=1, DIO=0</code>. El controlador espera un byte. Para leer un resultado, la condición cambia a <code class="language-plaintext highlighter-rouge">(msr &amp; 0xD0) == 0xD0</code>, que agrega <code class="language-plaintext highlighter-rouge">CB=1</code> (controller busy) a <code class="language-plaintext highlighter-rouge">RQM=1, DIO=1</code>: el FDC tiene un byte listo y procesando un comando (por eso el busy).</p> <p>Todo esto lo hago con <strong>polling</strong> con timeout. Si el FDC no responde el <a href="https://en.wikipedia.org/wiki/Kernel_%28operating_system%29">Kernel</a> va a colgarse para siempre. Cada <code class="language-plaintext highlighter-rouge">fdc_write</code> / <code class="language-plaintext highlighter-rouge">floppy_read_byte</code> tiene 100 ticks de margen (a 100Hz, un segundo), y si no lo logra, lo reporta y aborta.</p> <p>El <code class="language-plaintext highlighter-rouge">inb(0x80)</code> lo necesitamos para –en hardware real– hacer un I/O delay en ISA. El bus necesita tiempo X entre polls.</p> <p><strong><em>*Tose*</em></strong> <br /> Hablemos de emuladores.</p> <p>¿Vieron que a veces algunas personas –fukken hermosas personas en el puto mundo– están obsesionadas con emular arquitecturas enteras con precisión de <code class="language-plaintext highlighter-rouge">clock</code>?. Bueno, <code class="language-plaintext highlighter-rouge">qemu</code> y <code class="language-plaintext highlighter-rouge">bochs</code> no son esos. Ahora justo estamos hablando de <code class="language-plaintext highlighter-rouge">x86</code>, pero en otras arquitecturas o consolas, como <strong>Super Nintendo</strong> o no se, <strong>Playstation</strong> hay varias formas de encarar la emulación. Quizás con la <strong>SNES</strong>, como es una ROM no es tan claro pero con <strong>CDROM</strong> donde hay un motor involucrado (como con un floppy) las cosas cambian.</p> <p>Si yo programo un controlador de cualquier verga que esta emulada con <code class="language-plaintext highlighter-rouge">qemu</code>, me estoy perdiendo todas las particularidades <strong>físicas</strong> que el motor y los RPM del CD o la lámina magnética del disquette tienen. En este caso, estamos hablando de una disquetera, donde hay un <strong>motor</strong> (!) spinneando y tardando algunos milisegundos en alcanzar una velocidad de revoluciones estable. Cuando estuve programando este driver y el Sistema Operativo en general, en <code class="language-plaintext highlighter-rouge">qemu</code> andaba y cuando lo quise correr en hardware real, <strong>no andaba ni a palos</strong>.</p> <p>Por esto es increiblemente importante seguir los datasheets de lo que sea que estés programando, y si tenés suerte, poder recabar en información que otras personas hayan investigado anteriormente, como por ejemplo <strong>Josh Cole</strong> de <a href="https://floppy.cafe/">floppy.cafe</a> que recopiló practicamente un 90% de todo lo que necesité para poder desarrollar el driver, en un solo lugar. Un capo.</p> <p>En tiempos de Inteligencia Artificial, saber donde encontrar la documentación de la cual se alimentó, es quizás hasta mejor que pedirle ciegamente la implementación. Se los aseguro.</p> <h2 id="reset-e-inicialización">Reset e inicialización</h2> <p>La secuencia de arranque del FDC en <code class="language-plaintext highlighter-rouge">floppy_init()</code> sigue el orden del datasheet:</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>1. Unmask IRQ6 en el PIC 2. Configurar data rate (CCR = 0x00) 3. DOR = enable + irq, motor off 4. Reset: DOR = 0x00 5. Delay de I/O (4x escrituras a 0x80) 6. DOR = enable + irq (fin del reset) 7. Esperar IRQ6 (el reset del FDC genera una IRQ) 8. 4x SENSE INTERRUPT 9. SPECIFY 10. CONFIGURE 11. Motor on -&gt; RECALIBRATE -&gt; motor off 12. Lectura de prueba (sector 0, head 0, sector 1) </code></pre></div></div> <p>Lo único relevante es porque hacer <strong>4 Sense Interrupt</strong> después del reset. Esto es porque dejé el controlador en modo de "polling de drives": cuando el FDC hace un reset con ese modo activo, genera un status pendiente por cada uno de los 4 drives que puede llegar a tener (A, B, C ó D floppies), aunque solo tengas uno conectado. Si no drenás esos 4 resultados con <code class="language-plaintext highlighter-rouge">floppy_sense_interrupt()</code>, el controlador queda con porquería en la cola y tenés que agotar los datos de la misma. Estuve bastante tiempo tratando de entender por qué se me colgaba el drive, y una de las razones era por esto.</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> no tiene result phase ni genera IRQ, así que una vez mandados los 3 bytes, listo. Los valores:</p> <ul> <li><strong>Byte 1 (<code class="language-plaintext highlighter-rouge">0x8f</code>)</strong>: nibble alto = SRT (Step Rate Time, cuánto tarda cada paso del cabezal, en unidades inversas – valores más altos = pasos más rápidos), nibble bajo = HUT (Head Unload Time, cuánto espera antes de "soltar" el cabezal tras una operación).</li> <li><strong>Byte 2 (<code class="language-plaintext highlighter-rouge">0x1e</code>)</strong>: bits altos = HLT (Head Load Time, cuánto tarda en asentar el cabezal antes de leer/escribir), bit bajo = NDMA. <code class="language-plaintext highlighter-rouge">NDMA=0</code> es el que importa: le dice al FDC que vamos a usar DMA para la transferencia de datos, no PIO.</li> </ul> <p>Uso transferencias DMA porque (creo que esto está comentado en <code class="language-plaintext highlighter-rouge">floppy.h</code>) porque <code class="language-plaintext highlighter-rouge">bochs</code> no soporta PIO transfers, solo DMA. Si te olvidás este bit, el FDC espera que vos leas cada byte manualmente del FIFO y todo el pipeline de DMA que armamos más abajo no sirve para nada.</p> <p>Como el reset limpia esta configuración, hay que volver a mandarla en cada <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> activa tres cosas: <strong>EIS</strong> (Enable Implied Seek, el FDC hace el seek solo antes de un read/write sin que se lo pidas explícitamente), <strong>EFIFO=0</strong> (que, contraintuitivamente, <em>activa</em> el FIFO interno de 16 bytes del controlador en vez de desactivarlo – el nombre del bit es al revés de lo que uno esperaría) y <strong>FIFOTHR=7</strong>, el umbral de 8 bytes que dispara un acceso a DMA/memoria. Esto le da margen al sistema para no perder bytes si hay un poco de latencia en el bus.</p> <p><code class="language-plaintext highlighter-rouge">CONFIGURE</code>, como <code class="language-plaintext highlighter-rouge">SPECIFY</code>, tampoco genera IRQ ni result phase.</p> <h2 id="motor-y-timing">Motor y 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 es más de lo que documenta <code class="language-plaintext highlighter-rouge">osdev</code> (que sugiere 300ms para 3.5" y hasta dice que 50ms alcanza). En mi setup real, 50ms no era suficiente y tenía fallos intermitentes de seek. Preferí ir a lo seguro con medio segundo en vez de perseguir el mínimo teórico – el costo de esperar de más es insignificante comparado con el de tener una lectura fallida en medio del boot.</p> <p>Para esto necesito un timer que no dependa de contar ciclos de CPU a mano, así que entra <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>El <a href="https://wiki.osdev.org/Programmable_Interval_Timer">PIT</a> corre a 1193182 Hz internamente (<code class="language-plaintext highlighter-rouge">PIT_FREQUENCY</code>). Con <code class="language-plaintext highlighter-rouge">pit_init(100)</code> configuro un divisor que dispara una IRQ0 cada 10ms, o sea 100 ticks por segundo. <code class="language-plaintext highlighter-rouge">pit_ticks</code> es un contador global incrementado desde <code class="language-plaintext highlighter-rouge">pit_irq_handler()</code>, y todo lo demás en el sistema (<code class="language-plaintext highlighter-rouge">pit_wait_ms</code>, los timeouts del FDC, <code class="language-plaintext highlighter-rouge">floppy_wait_irq</code>) se apoya en ese contador en vez de hacer busy-waiting a ciegas.</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 a 100Hz son 2 segundos de timeout para que llegue la IRQ6 del FDC – generoso a propósito, porque no quiero que un floppy real (que puede tardar en spin-up y seek) dispare un falso timeout.</p> <p>Noten el <code class="language-plaintext highlighter-rouge">hlt</code> en el loop: en vez de spin-waiting quemando CPU, el procesador se detiene hasta la próxima interrupción (sea la del PIT o la del FDC), que es lo que corresponde hacer en un kernel real cuando estás esperando 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> y <code class="language-plaintext highlighter-rouge">SEEK</code> generan una IRQ pero <strong>no tienen result phase propia</strong>. El status hay que pedirlo aparte con <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> manda el cabezal a la pista 0 (útil para "resincronizar" la posición física conocida) y <code class="language-plaintext highlighter-rouge">SEEK</code> lo mueve a un cilindro específico. En ambos casos, después de la IRQ, hago <code class="language-plaintext highlighter-rouge">SENSE INTERRUPT</code> y valido dos cosas: el bit <code class="language-plaintext highlighter-rouge">Seek End</code> (<code class="language-plaintext highlighter-rouge">st0 &amp; 0x20</code>) y que el cilindro reportado (<code class="language-plaintext highlighter-rouge">cyl</code>) coincida con el esperado (0 para recalibrate, el cilindro pedido para seek). Si algo sale mal, lo escupo a consola.</p> <h2 id="lectura-y-escritura-de-sectores">Lectura y escritura de sectores</h2> <p>El comando de lectura/escritura manda 9 bytes en 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> sobre el comando activa <code class="language-plaintext highlighter-rouge">MT</code> (Multi-Track, el controlador puede seguir a la próxima cabeza automáticamente si cruza el EOT) y <a href="https://en.wikipedia.org/wiki/Modified_frequency_modulation">MFM</a> (modulación estándar para floppies y storage magnético de doble densidad para arriba). <code class="language-plaintext highlighter-rouge">N=2</code> le dice al FDC "cada sector son 512 bytes" usando la tabla estándar de tamaños del chip (0=128B, 1=256B, 2=512B…). <code class="language-plaintext highlighter-rouge">EOT=18</code> es el número de sectores por pista en la geometría 1.44MB.</p> <p>Después de la IRQ (que llega cuando termina la transferencia por DMA, no antes), viene el <strong>result phase</strong> de 7 bytes (<code class="language-plaintext highlighter-rouge">ST0</code> a <code class="language-plaintext highlighter-rouge">ST2</code> más C/H/R/N de vuelta), de los cuales solo chequeo los primeros tres:</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>Cualquier bit prendido en <code class="language-plaintext highlighter-rouge">ST1</code>/<code class="language-plaintext highlighter-rouge">ST2</code> lo trato como fallo total de la operación – no intento distinguir "es un CRC error recuperable" de "el sector no existe", simplemente lo reintento entero.</p> <p><code class="language-plaintext highlighter-rouge">floppy_write_sector_impl</code> es casi un espejo de la lectura, cambiando el comando (<code class="language-plaintext highlighter-rouge">FDC_CMD_WRITE_DATA | 0xC0</code>, MFM sin necesidad de MT… aunque en el código dejé el mismo <code class="language-plaintext highlighter-rouge">0xC0</code>) y el sentido del DMA.</p> <h2 id="chs-y-la-conversión-desde-lba">CHS y la conversión desde LBA</h2> <p>Para no tener que pensar en cilindro/cabeza/sector en el resto del kernel, expongo <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>Esto asume la geometría fija de un floppy 1.44MB: 2 cabezas, 18 sectores por pista, 80 cilindros (2×18×80 = 2880 sectores totales, de ahí el <code class="language-plaintext highlighter-rouge">if (lba &gt;= 2880) return false;</code>). No hay lectura de geometría desde el media descriptor ni soporte para otros formatos – para <code class="language-plaintext highlighter-rouge">tOSh</code>, un solo tipo de floppy alcanza y sobra, y hardcodear la geometría ahorra una capa entera de abstracción que no necesito.</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>Los floppies reales son propensos a errores transitorios: polvo, desgaste del medio, un cabezal que no asentó bien en el primer intento, poner <strong>UN IMAN DE HELADERA COMO PISAPAPELES SOBRE UNA PILA DE FLOPPIES</strong>, etc… En vez de fallar de una ante el primer error, reintento 3 veces, y entre cada intento hago un <code class="language-plaintext highlighter-rouge">RECALIBRATE</code> completo para forzar al cabezal a resincronizar su posición física – si el error fue justamente por una posición de cabezal corrida, el recalibrate lo arregla antes del siguiente intento.</p> <h1 id="dma-chip-8237">DMA, Chip 8237</h1> <p>El FDC no te entrega los datos leídos byte a byte por el FIFO cuando <code class="language-plaintext highlighter-rouge">NDMA=0</code> (ver Specify más arriba) – usa el chip DMA 8237 para escribir directamente a memoria mientras la CPU hace otra cosa (o, en nuestro caso, espera en un <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>Algunos puntos que vale la pena remarcar acá:</p> <ul> <li>El <strong>flip-flop</strong> es un bit interno del 8237 que indica si el próximo byte escrito a un puerto de dirección/cuenta es el byte bajo o el alto. Como es estado compartido entre canales, hay que resetearlo explícitamente (<code class="language-plaintext highlighter-rouge">DMA_CLEAR_FF</code>) antes de escribir tanto la dirección como el conteo – si no lo hacés, podés terminar escribiendo el byte alto donde iba el bajo y armar una dirección que sea cualquier cosa.</li> <li>El <strong>page register</strong> (<code class="language-plaintext highlighter-rouge">DMA_PAGE_CH2 = 0x81</code>) es un resabio de que el 8237 original solo maneja direcciones de 16 bits para offset. Para llegar a memoria de 24 bits (como necesita un PC real), el byte alto de la dirección se escribe aparte, en el page register del canal correspondiente. Esto es también la razón de la restricción de que <strong>el buffer no puede cruzar un límite de 64KiB</strong>: el 8237 incrementa el offset de 16 bits en cada byte transferido, pero nunca toca el page register durante la transferencia. Si el buffer arranca cerca del final de una página de 64KB y se pasa, el DMA no avanza de página – le da la vuelta al offset y empieza a pisar el principio de la misma página de 64KB. Por eso el buffer del floppy lo escribí así:</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>Alinearlo a 512 bytes (el tamaño de un sector) garantiza que nunca vas a tener un buffer de 512 bytes que empiece, por ejemplo, en la dirección <code class="language-plaintext highlighter-rouge">0xFFF00</code> y termine cruzando a <code class="language-plaintext highlighter-rouge">0x10000</code> en el próximo bloque de 64KB. La sección <code class="language-plaintext highlighter-rouge">.dma</code> del linker script (ver Parte III) mete este buffer en su propia región alineada a página de 4KB, lejos de cualquier otro dato, así que <strong>en la práctica el boundary-crossing nunca ocurre, evitando que el driver haga ningún chequeo en runtime</strong>.</p> <ul> <li> <p>El <strong>modo</strong> (<code class="language-plaintext highlighter-rouge">0x46</code> para lectura, <code class="language-plaintext highlighter-rouge">0x4A</code> para escritura) sigue la nomenclatura del 8237 mirada desde la óptica del periférico, no de la memoria: <code class="language-plaintext highlighter-rouge">dma_read()</code> es "el FDC lee del disco y escribe a memoria" (lo que en <code class="language-plaintext highlighter-rouge">floppy_read_sector_impl</code> efectivamente llena tu buffer), mientras que <code class="language-plaintext highlighter-rouge">dma_write()</code> es "la memoria se lee y se escribe al FDC" (para mandar datos al disco). Es un nombre fácil de confundir si pensás en términos de CPU en vez de en términos del periférico – acá <code class="language-plaintext highlighter-rouge">dma_read</code>/<code class="language-plaintext highlighter-rouge">dma_write</code> están nombrados desde la perspectiva de "¿qué hace la FDC con el dato?", que es consistente con cómo Intel documenta el chip.</p> </li> <li> <p><code class="language-plaintext highlighter-rouge">count--</code> porque el 8237 espera el conteo como <code class="language-plaintext highlighter-rouge">N-1</code> (transferís <code class="language-plaintext highlighter-rouge">count</code> bytes, pero el registro interno cuenta de <code class="language-plaintext highlighter-rouge">count-1</code> hasta 0).</p> </li> </ul> <p>Masomenos nos queda que <code class="language-plaintext highlighter-rouge">floppy_read_sector</code> con <code class="language-plaintext highlighter-rouge">dma_read()</code> arma el canal 2 apuntando al buffer y por 512 bytes, el comando <code class="language-plaintext highlighter-rouge">READ DATA</code> se manda al FDC, la transferencia ocurre en el fondo mientras la CPU espera con <code class="language-plaintext highlighter-rouge">hlt</code>, y cuando termina, el FDC genera la IRQ6 que atiende <code class="language-plaintext highlighter-rouge">floppy_wait_irq</code>.</p> <p>Es un montón todo esto y quizás algunas cosas se pueden ver mejor leyendo el código fuente. Como pongo al principio de toda esta serie de artículos, si querés, podes bajarte el source desde la pagina de <a href="/resources/">Resources</a>.</p> <h1 id="el-sistema-de-archivos">El Sistema de Archivos</h1> <p><img src="/assets/images/tOSh/tOSh-listing.jpeg" alt="Probando el Filesystem en mi máquina del 97'" class="img-responsive" /><em>Probando el Filesystem en mi máquina del 97’</em></p> <h2 id="hagámoslon-andá">Hagámoslon andá'</h2> <p>Para <code class="language-plaintext highlighter-rouge">tOSh</code> no hay bloques, no hay FAT, no hay árbol de directorios, no hay journaling, no hay nombres largos ni paths. A <code class="language-plaintext highlighter-rouge">tOSh</code> no le importan todas esas mierdas, ni siquiera la consistencia de tus datos. <code class="language-plaintext highlighter-rouge">tOSh</code> es un Sistema Operativo <strong>SOLO PARA LOCOS Y VALIENTES</strong>.</p> <p>Dicho esto y hecha la aclaración, empiezo a contarles como lo armé. Hay un directorio de tamaño fijo con hasta 256 archivos, cada uno con un nombre, un sector de inicio, y un tamaño. Punto.</p> <p>La razón detrás de esto es la misma que con la geometría fija del floppy: cualquier feature de un filesystem real (FAT, ext2, lo que sea) agrega una capa de indirección – bloques, listas enlazadas de clusters, bitmaps de espacio libre – que resuelve problemas que <code class="language-plaintext highlighter-rouge">tOSh</code> todavía no tiene, como fragmentación real de un disco que se llena y vacía muchas veces. Para lo que necesito (persistir un puñado de archivos chicos en un floppy de 1.44MB durante el desarrollo del kernel), un directorio plano alcanza.</p> <h2 id="layout-de-disco">Layout de disco</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>El filesystem arranca en el sector 256, bien después del kernel (que termina en el 136). El gap que hago entre 137 y 255 (119 sectores, ~59KB) es margen para que el kernel vaya creciendo mientras lo voy desarrollando y que no choque contra el filesystem – recordá que el <code class="language-plaintext highlighter-rouge">Makefile</code>/<code class="language-plaintext highlighter-rouge">build.sh</code> de la Parte III valida que el kernel no supere los 128 sectores asignados, así que ese rango reservado es el colchón real antes de tener que mover todo el layout.</p> <p>El gap entre 289 y 319 (31 sectores) es lo mismo pero para el directorio: <code class="language-plaintext highlighter-rouge">FS_DIRECTORY_SECTORS</code> está fijado en 32, pero el tamaño real que ocupa <code class="language-plaintext highlighter-rouge">struct fs_file[FS_MAX_FILES]</code> puede terminar usando menos. Este margen me evita recalcular <code class="language-plaintext highlighter-rouge">FS_DATA_START</code> cada vez que cambio <code class="language-plaintext highlighter-rouge">FS_MAX_FILES</code> o el tamaño de <code class="language-plaintext highlighter-rouge">fs_file</code>.</p> <h2 id="el-superblock">El 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> es <code class="language-plaintext highlighter-rouge">0x48534f54</code>, que leído como bytes ASCII little-endian da <code class="language-plaintext highlighter-rouge">TOSH</code>. <code class="language-plaintext highlighter-rouge">fs_is_initialized()</code> lee el sector 256 y compara ese magic – si no matchea, asume que el floppy nunca fue formateado con este filesystem y listo, no intenta "recuperar" ni interpretar nada más de ese sector. <code class="language-plaintext highlighter-rouge">__attribute__((packed))</code> es necesario para que el struct ocupe exactamente los bytes que declaro, sin padding que el compilador metería por alineación.</p> <h2 id="el-directorio">El directorio</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>Todo el directorio es un array de 256 entradas que vive en RAM durante la ejecución del kernel y se persiste entero al disco con <code class="language-plaintext highlighter-rouge">fs_save_directory()</code> cada vez que cambia algo (create, write, delete). No hay bitmap de "entradas libres" separado – una entrada está libre si <code class="language-plaintext highlighter-rouge">used == 0</code>, y buscar un archivo (<code class="language-plaintext highlighter-rouge">fs_find</code>) es simplemente recorrer el array comparando nombres.</p> <p>Esto significa que cada operación que modifica el filesystem reescribe los 32 sectores completos del directorio, incluso si solo cambió una entrada. Es claramente no-óptimo en términos de I/O, pero contra un floppy que ya de por sí es lentísimo, la diferencia entre escribir 1 sector o 32 no cambia la experiencia de uso, y me ahorro tener que trackear qué sector específico del directorio corresponde a qué entrada.</p> <h2 id="no-manejando-el-espacio-libre">No Manejando el Espacio Libre</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>La asignación de espacio es lo más primitivo que se puede hacer: recorro todos los archivos usados, encuentro el <code class="language-plaintext highlighter-rouge">start_sector + sectores_ocupados</code> más alto, y el próximo archivo arranca ahí. No hay bitmap de sectores libres, no hay free-list, no hay compresión.</p> <p>La consecuencia directa de esto –y una decisión consciente, <strong>NO ES UN BUG, ES UN FEATURE</strong>– es que <code class="language-plaintext highlighter-rouge">fs_delete()</code> <strong>no libera ni recicla el espacio</strong>. Borrar un archivo solo limpia su entrada de directorio (<code class="language-plaintext highlighter-rouge">used = 0</code>); los sectores que ocupaba quedan ahí, ignorados para siempre por <code class="language-plaintext highlighter-rouge">fs_next_free_sector()</code>, porque esa función no mira entradas <code class="language-plaintext highlighter-rouge">used == 0</code>, solo mira el punto más lejano ocupado por archivos <em>actualmente</em> vivos… pero ese punto más lejano puede haber sido dejado por un archivo que ya no existe. En otras palabras: crear y borrar archivos deja huecos que jamás se vuelven a usar hasta que reformateás el disco entero con <code class="language-plaintext highlighter-rouge">fs_format()</code>. A esto se le llamaba <strong>fragmentacion de disco</strong> y es lo que el "Defragmentador de Windows" solucionaba. Un poco también le pasaba, por otros motivos, a MS-DOS y Windows 9x.</p> <p>Para el estado actual de <code class="language-plaintext highlighter-rouge">tOSh</code> esto es aceptable: el filesystem no está pensado (todavía, quien sabe) para un uso de escritura/borrado intensivo y prolongado, sino para persistir un puñado de archivos de configuración o binarios del sistema. El día que esto sea un problema real (kkjjj osea nunca, aguante <code class="language-plaintext highlighter-rouge">tOSh</code>), la solución más simple sin rehacer el diseño sería agregar un bitmap de sectores libres, o migrar a un esquema de bloques con free-list – pero eso es haría cambiar completamente el filesystem.</p> <h2 id="operaciones">Operaciones</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> y <code class="language-plaintext highlighter-rouge">fs_delete</code> son bastante directas dado el diseño que expliqué arriba:</p> <ul> <li><strong><code class="language-plaintext highlighter-rouge">fs_create</code></strong> busca una entrada libre, le asigna el próximo sector vía <code class="language-plaintext highlighter-rouge">fs_next_free_sector()</code>, guarda el nombre, y persiste el directorio. El tamaño arranca en 0 – todavía no hay datos, solo la reserva de posición.</li> <li><strong><code class="language-plaintext highlighter-rouge">fs_write</code></strong> parte los datos en sectores de 512 bytes (rellenando el último con ceros si no es múltiplo exacto) y los escribe secuencialmente desde <code class="language-plaintext highlighter-rouge">start_sector</code>. Actualiza <code class="language-plaintext highlighter-rouge">size</code> y vuelve a persistir el directorio entero.</li> <li><strong><code class="language-plaintext highlighter-rouge">fs_read</code></strong> hace lo inverso: calcula cuántos sectores ocupa el archivo a partir de <code class="language-plaintext highlighter-rouge">size</code>, los lee todos, y copia solo los bytes válidos (no el padding de ceros) al buffer del caller.</li> <li><strong><code class="language-plaintext highlighter-rouge">fs_delete</code></strong> limpia la entrada, sin tocar los sectores de datos (ver justito arriba).</li> </ul> <p>Ninguna de estas cuatro funciones soporta archivos que crezcan más allá del hueco libre que tenían al momento de crearse – no hay concepto de "extender" un archivo más allá de lo que había disponible cuando se escribió por primera vez de forma contigua, porque <code class="language-plaintext highlighter-rouge">fs_write</code> siempre escribe a partir de <code class="language-plaintext highlighter-rouge">start_sector</code>, asumiendo que el espacio ya fue reservado por <code class="language-plaintext highlighter-rouge">fs_create</code> + el orden de creación de los demás archivos.</p> <h2 id="limitaciones-conocidas">Limitaciones conocidas</h2> <p>Para que me quede documentado y no se pierda con el tiempo cuando en 3 años vuelva a implementarle cosas a <code class="language-plaintext highlighter-rouge">tOSh</code>:</p> <ul> <li>Tamaño máximo de filesystem fijo a <code class="language-plaintext highlighter-rouge">FS_TOTAL_SECTORS = 2880</code> (los 1.44MB completos del floppy, sin distinguir que una parte ya está usada por boot/kernel – la validación de que no te pisés con eso es responsabilidad de dejar bien puesto <code class="language-plaintext highlighter-rouge">FS_DATA_START</code>, no algo que el filesystem chequee en runtime). Igual, quizás estaria bueno si vuelvo a tocar el sistema de archivos o reescribirlos, también implementar soporte para discos rígidos, y tener eso en cuenta para hacerlo un poco más serio. Veremos.</li> <li>Nombres de archivo sin jerarquía: <code class="language-plaintext highlighter-rouge">FS_FILENAME_MAX = 32</code> bytes, sin separador de path, todo vive en el mismo namespace jarcodeado.</li> <li>Sin fragmentación de archivos: cada archivo ocupa un rango contiguo de sectores. Esto simplifica muchísimo el código (no hay que trackear cadenas de bloques) pero significa que archivos grandes en un filesystem con muchos huecos por deletes previos eventualmente no van a entrar, aunque haya "espacio total" de sobra repartido en gaps. Eventualmente, el sistema de archivos creando y borrando archivos suficientes veces, va a crashear :)</li> <li>Sin journaling ni ningún mecanismo de recuperación ante un corte de luz a mitad de un <code class="language-plaintext highlighter-rouge">fs_save_directory()</code>. Si la escritura del directorio se corta en el medio, quedás con una versión parcial y hasta inclusive te quedás sin sistema de archivos porque el superbloque se puede corromper también.</li> </ul> <p>Para la próxima, corremos <code class="language-plaintext highlighter-rouge">tOSh</code> en varias máquinas, emuladas y reales, prestadas y/o donadas para tal fin por queridos amigos y compañeros nerds que respeto y amo fuerte.</p> <h1 id="referencias">Referencias</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>