Implement read/write support.
authorBen Pfaff <blp@cs.stanford.edu>
Sun, 29 Aug 2004 21:34:16 +0000 (21:34 +0000)
committerBen Pfaff <blp@cs.stanford.edu>
Sun, 29 Aug 2004 21:34:16 +0000 (21:34 +0000)
src/devices/disk.c

index a742ee42d0cbbebd45e865410eeb42f791c5edb8..cc7e9a7e14cbd80436fef7b631dcd1256d973360 100644 (file)
 #define DEV_DEV 0x10            /* Select device: 0=master, 1=slave. */
 
 /* Commands. */
-#define CMD_IDENTIFY 0xec       /* IDENTIFY DEVICE. */
+#define CMD_IDENTIFY 0xec               /* IDENTIFY DEVICE. */
+#define CMD_READ_SECTOR_RETRY 0x20      /* READ SECTOR with retries. */
+#define CMD_READ_SECTOR_NORETRY 0x21    /* READ SECTOR without retries. */
+#define CMD_WRITE_SECTOR_RETRY 0x30     /* WRITE SECTOR with retries. */
+#define CMD_WRITE_SECTOR_NORETRY 0x31   /* WRITE SECTOR without retries. */
 
 struct disk 
   {
@@ -67,19 +71,7 @@ struct channel
 static struct channel channels[2];
 
 static void
-select_device (const struct disk *d)
-{
-  struct channel *c = d->channel;
-  uint8_t dev = DEV_MBS;
-  if (d->device == 1)
-    dev |= DEV_DEV;
-  outb (reg_device (c), dev);
-  inb (reg_alt_status (c));
-  timer_nsleep (400);
-}
-
-static void
-wait_idle (const struct disk *d) 
+wait_until_idle (const struct disk *d) 
 {
   int i;
 
@@ -93,17 +85,9 @@ wait_idle (const struct disk *d)
   printk ("%s: idle timeout\n", d->name);
 }
 
-static void
-select_device_wait (const struct disk *d) 
-{
-  wait_idle (d);
-  select_device (d);
-  wait_idle (d);
-}
-
 /* Wait up to 30 seconds for disk D to clear BSY. */
 static bool
-busy_wait (const struct disk *d) 
+wait_while_busy (const struct disk *d) 
 {
   struct channel *c = d->channel;
   int i;
@@ -125,6 +109,26 @@ busy_wait (const struct disk *d)
   return false;
 }
 
+static void
+select_device (const struct disk *d)
+{
+  struct channel *c = d->channel;
+  uint8_t dev = DEV_MBS;
+  if (d->device == 1)
+    dev |= DEV_DEV;
+  outb (reg_device (c), dev);
+  inb (reg_alt_status (c));
+  timer_nsleep (400);
+}
+
+static void
+select_device_wait (const struct disk *d) 
+{
+  wait_until_idle (d);
+  select_device (d);
+  wait_until_idle (d);
+}
+
 static void
 reset_channel (struct channel *c) 
 {
@@ -166,7 +170,7 @@ reset_channel (struct channel *c)
   if (present[0]) 
     {
       select_device (&c->dev[0]);
-      busy_wait (&c->dev[0]); 
+      wait_while_busy (&c->dev[0]); 
     }
 
   /* Wait for device 1 to clear BSY. */
@@ -181,12 +185,12 @@ reset_channel (struct channel *c)
             break;
           timer_msleep (10);
         }
-      busy_wait (&c->dev[1]);
+      wait_while_busy (&c->dev[1]);
     }
 }
 
 static bool
-check_ata_device (struct disk *d) 
+check_device_type (struct disk *d) 
 {
   struct channel *c = d->channel;
   uint8_t error, lbam, lbah;
@@ -250,6 +254,41 @@ input_sector (struct channel *c, void *sector)
     }
 }
 
+static bool
+output_sector (struct channel *c, const void *sector) 
+{
+  uint8_t status;
+
+  ASSERT (sector != NULL);
+
+  status = inb (reg_status (c));
+  if (status & STA_DRQ) 
+    {
+      /* Command was successful.  Write data into SECTOR. */
+      outsw (reg_data (c), sector, DISK_SECTOR_SIZE / 2);
+      return true; 
+    }
+  else 
+    {
+      /* Command failed. */
+      return false; 
+    }
+}
+
+static void
+printk_ata_string (char *string, size_t word_cnt) 
+{
+  int last;
+  int i;
+
+  for (last = word_cnt * 2 - 1; last >= 0; last--)
+    if (string[last ^ 1] != ' ')
+      break;
+
+  for (i = 0; i <= last; i++)
+    printk ("%c", string[i ^ 1]);
+}
+
 static void
 identify_ata_device (struct disk *d) 
 {
@@ -259,7 +298,7 @@ identify_ata_device (struct disk *d)
 
   select_device_wait (d);
   execute_command (d, CMD_IDENTIFY);
-  busy_wait (d);
+  wait_while_busy (d);
 
   if (!input_sector (d->channel, id)) 
     {
@@ -267,9 +306,13 @@ identify_ata_device (struct disk *d)
       return;
     }
 
-  d->capacity = id[57] | ((uint32_t) id[58] << 16);
-  printk ("%s: detected %"PRDSNu" sector (%d MB) disk\n",
+  d->capacity = id[60] | ((uint32_t) id[61] << 16);
+  printk ("%s: detected %'"PRDSNu" sector (%d MB) disk: ",
           d->name, d->capacity, d->capacity * DISK_SECTOR_SIZE / 1024 / 1024);
+  printk_ata_string ((char *) &id[27], 20);
+  printk (" ");
+  printk_ata_string ((char *) &id[10], 10);
+  printk ("\n");
 }
 
 static void
@@ -281,7 +324,13 @@ interrupt_handler (struct intr_frame *f)
     if (f->vec_no == c->irq)
       {
         if (c->expecting_interrupt) 
-          sema_up (&c->completion_wait);
+          {
+            /* Acknowledge interrupt. */
+            inb (reg_status (c));
+
+            /* Wake up waiter. */
+            sema_up (&c->completion_wait);
+          }
         else
           printk ("%s: unexpected interrupt\n", c->name);
         return;
@@ -336,11 +385,11 @@ disk_init (void)
       /* Reset hardware. */
       reset_channel (c);
 
-      /* Detect ATA devices. */
-      if (check_ata_device (&c->dev[0]))
-        check_ata_device (&c->dev[1]);
+      /* Distinguish ATA hard disks from other devices. */
+      if (check_device_type (&c->dev[0]))
+        check_device_type (&c->dev[1]);
 
-      /* Detect device properties. */
+      /* Read hard disk identity information. */
       for (device = 0; device < 2; device++)
         if (c->dev[device].is_ata)
           identify_ata_device (&c->dev[device]);
@@ -350,10 +399,60 @@ disk_init (void)
 struct disk *
 disk_get (int idx) 
 {
+  struct disk *d;
+  
   ASSERT (idx >= 0 && idx < 4);
-  return &channels[idx / 2].dev[idx % 2];
+  d = &channels[idx / 2].dev[idx % 2];
+  return d->is_ata ? d : NULL;
+}
+
+disk_sector_no
+disk_size (struct disk *d) 
+{
+  ASSERT (d != NULL);
+  
+  return d->capacity;
+}
+
+static void
+select_sector (struct disk *d, disk_sector_no sec_no) 
+{
+  struct channel *c = d->channel;
+
+  ASSERT (sec_no < d->capacity);
+  ASSERT (sec_no < (1UL << 28));
+  
+  select_device_wait (d);
+  outb (reg_nsect (c), 1);
+  outb (reg_lbal (c), sec_no);
+  outb (reg_lbam (c), sec_no >> 8);
+  outb (reg_lbah (c), (sec_no >> 16));
+  outb (reg_device (c),
+        DEV_MBS | DEV_LBA | (d->device == 1 ? DEV_DEV : 0) | (sec_no >> 24));
+}
+
+void
+disk_read (struct disk *d, disk_sector_no sec_no, void *buffer) 
+{
+  ASSERT (d != NULL);
+  ASSERT (buffer != NULL);
+
+  select_sector (d, sec_no);
+  execute_command (d, CMD_READ_SECTOR_RETRY);
+  wait_while_busy (d);
+  if (!input_sector (d->channel, buffer))
+    panic ("%s: disk read failed, sector=%"PRDSNu, d->name, sec_no);
 }
 
-disk_sector_no disk_size (struct disk *);
-void disk_read (struct disk *, disk_sector_no, void *);
-void disk_write (struct disk *, disk_sector_no, const void *);
+void
+disk_write (struct disk *d, disk_sector_no sec_no, const void *buffer)
+{
+  ASSERT (d != NULL);
+  ASSERT (buffer != NULL);
+
+  select_sector (d, sec_no);
+  execute_command (d, CMD_WRITE_SECTOR_RETRY);
+  wait_while_busy (d);
+  if (!output_sector (d->channel, buffer))
+    panic ("%s: disk write failed, sector=%"PRDSNu, d->name, sec_no);
+}