Acceso LBA de HDD vía PIO

Todos los sistemas operativos eventualmente se verán en la necesidad de un medio de almacenamiento confiable de larga duración. Solamente existe una cantidad breve de dispositivos de almacenamiento comúnmente usados:

  • Floppy

  • Unidades Flash

  • CD-ROM/DVD

  • Discos duros

Los discos duros son por mucho, el mecanismo más ampliamente usado para el almacenamiento de datos, y este tutorial te familiarizará con un método práctico para accederlos. En el pasado, un método conocido como CHS era empleado. Con CHS, especificabas el Cilindro (C), la Cabeza (H) y el Sector (S) en donde tus datos estababan ubicados.

El problema con este método es que el número de cilindros que podían direccionarse era bastante limitado. Para resolver este problema, se creó un nuevo método para acceder los discos duros: Direccionamiento Linear de Bloques (Linear Block Addressing, o LBA). Con LBA, simplemente especificas la dirección del bloque que deseas acceder.

Los bloques son (usualmente) porciones de datos de 512 bytes, así que los primeros 512 bytes de datos en el disco están en el bloque 0, los siguientes 512 bytes están en el bloque 1, etc. Claramente, esto es más fácil que tener que calcular y especificar tres bits separados de información, como con CHS. Sin embargo, solo hay un problema con LBA. Hay dos formas de LBA, que son ligeramente diferentes: LBA28 y LBA48.

LBA28 usa 28 bits para especificar la dirección del bloque, y LBA48 usa 48 bits. La mayoría de discos soportan LBA28, pero no todos los discos soportan LBA48. En particular, versiones antiguas de Bochs soportan LBA28 pero no LBA48. Este no es un problema serio, pero algo de lo que hay que estar enterado. Ahora que sabes cómo funciona el LBA, es hora de ver los métodos involucrados en cuestión.

Para leer un sector usando LBA28:

  1. Envía un byte nulo al puerto 0x1F1: outb(0x1F1, 0x00);

  2. Envía una cuenta de sector al puerto 0x1F2: outb(0x1F2, 0x01);

  3. Envía los 8 bis de menor peso de la dirección del bloque al puerto 0x1F3:
    outb(0x1F3, (unsigned char)addr);

  4. Envía los siguientes 8 bits de la dirección del bloque al puerto 0x1F4:
    outb(0x1F4, (unsigned char)(addr >> 8);

  5. Envía los siguientes 8 bits de la dirección del bloque al puerto 0x1F5:
    outb(0x1F5, (unsigned char)(addr >> 16);

  6. Envía el indicador del drive, algunos bits mágicos y los 4 bits de mayor peso de la dirección al puerto 0x1F6:
    outb(0x1F6, 0xE0 | (drive << 4) | ((addr >> 24) & 0x0F));

  7. Envía el comando (0x20) al puerto 0x1F7:
    outb(0x1F7, 0x20);

Para escribir un sector usando LBA28:

Haz exactamente todo lo anterior, pero en el paso 7 envía el comando 0x30 en lugar de 0x20:
outb(0x1F7, 0x30);

Para leer un sector usando LBA48:

  1. Envía dos bytes nulos al puerto 0x1F1:
    outb(0x1F1,0x00);
    outb(0x1F1, 0x00);


  2. Envía una cuenta de sector de 16 bits al puerto 0x1F2:
    outb(0x1F2, 0x00);
    outb(0x1F2, 0x01);


  3. Envía los bits 24-31 al puerto 0x1F3:
    outb(0x1F3, (unsigned char)(addr >> 24));

  4. Envía los bits 0-7 al puerto 0x1F3:
    outb(0x1F3, (unsigned char)addr);

  5. Envía los bits 32-39 al puerto 0x1F4:
    outb(0x1F4, (unsigned char)(addr >> 32));

  6. Envía los bits 8-15 al puerto 0x1F4:
    outb(0x1F4, (unsigned char)(addr >> 8));

  7. Envía los bits 40-47 al puerto 0x1F5:
    outb(0x1F5, (unsigned char)(addr >> 40));

  8. Envía los bits 16-23 al puerto 0x1F5:
    outb(0x1F5, (unsigned char)(addr >> 16));

  9. Envía el indicador del drive y algunos bits mágicos al puerto 0x1F6:
    outb(0x1F6, 0x40 | (drive << 4));

  10. Envía el comando (0x24) al puerto 0x1F7:
    outb(0x1F7, 0x24);

Para escribir un sector usando LBA48:

Haz exactamente todo lo anterior, pero en el paso 10, envía el byte de comando 0x34 en lugar de 0x24:
outb(0x1F7, 0x34);



Una vez que hayas hecho todo esto, simplemente tienes que esperar a que el drive señale que está listo:

while (!(inb(0x1F7) & 0x08));


Y luego lee/escribe los datos desde/hacia el puerto 0x1F0:

//Para la lectura:
///
for (idx = 0; idx < 256; idx++)
{
 tmpword = inw(0x1F0);
 buffer[idx * 2] = (unsigned char)tmpword;
 buffer[idx * 2 + 1] = (unsigned char)(tmpword >> 8);
}

////////////////////////////////////////////////////////
////////////////////////////////////////////////////////
////////////////////////////////////////////////////////
////////////////////////////////////////////////////////

//Para la escritura:
///
for (idx = 0; idx < 256; idx++)

{

tmpword = buffer[8 + idx * 2] | (buffer[8 + idx * 2 + 1] << 8);

outw(0x1F0, tmpword);

}

Por supuesto, todo esto es inútil si no sabes qué drives están conectados. Cada controlador IDE puede manejar 2 drives, y la mayoría de computadoras tienen 2 controladores IDE. El controlador primerio, que es el que hemos estado programando hasta ahora, tiene sus registros ubicados desde el puerto 0x1F0 hasta el puerto 0x1F7. El controlador secundario tiene sus registros en los puertos 0x170-0x177.

Detectar si los controladores están presentes es bastante fácil:

  1. Escribe un valor mágico al puerto bajo LBA para ese controlador (0x1F3 para el controlador primario, y 0x173 para el controlador secundario):
    outb(0x1F3, 0x88);

  2. Leer de vuelta del mismo puerto, y ver si lo que lees es lo que escribiste. Si es así, ese controlador existe.

Ahora tienes que detectar qué drives están presentes en cada controlador. Para hacer esto, simplemente seleccionas el drive apropiado con el registro de selección de drive/cabeza (0x1F6 para el controlador primario, y 0x176 para el controlador secundario), esperas un pequeño instante (yo espero 1/250 de un segundo, es decir la cuarta parte de un segundo), y luego lees el registro de estado y ves si el bit de ocupado está activado:

//Usa 0xB0 en lugar de 0xA0
//para comprobar el segundo drive
//en el controlador:
///
  outb(0x1F6, 0xA0);

//Esperar 1/250 de un segundo:
///
 sleep(1);


//Leer el puerto de estado:
///
 tmpword = inb(0x1F7);


//Ver si el bit de "ocupado" está activado:
///
 if(tmpword & 0x40)
 {
  printf("Primary master exists\n");
 }

Y eso prácticamente encierra todo esto. Nota que no he probado realmente mi código de 48 bits, porque estoy atrapado con Bochs, que solamente soporta LBA28. Debería funcionar, de acuerdo a la especificación ATA.

Si hay cualquier error, solo envíame un email a marsdragon88@gmail.com.



--Dragoniz3r

© Todos los Derechos Reservados Bona Fide OS development 2001-2006. Renunciamos a la responsabilidad por todas las cosas malas; las cosas buenas están bien.