Fix disk_write(): interrupt fires *after* write complete, not before.
[pintos-anon] / src / devices / disk.c
index b40a8e8f4ab407a6d6d366bcd763d16cc880ebf5..6206e78cedb9aec9df14146cae25704ee7f5e2d3 100644 (file)
@@ -1,4 +1,11 @@
 #include "disk.h"
+#include <stdbool.h>
+#include "debug.h"
+#include "io.h"
+#include "interrupt.h"
+#include "lib.h"
+#include "synch.h"
+#include "timer.h"
 
 #define reg_data(CHANNEL) ((CHANNEL)->reg_base + 0)
 #define reg_error(CHANNEL) ((CHANNEL)->reg_base + 1)
 #define reg_status(CHANNEL) ((CHANNEL)->reg_base + 7)
 #define reg_command(CHANNEL) reg_status (CHANNEL)
 #define reg_ctl(CHANNEL) ((CHANNEL)->reg_base + 0x206)
-#define reg_alt(CHANNEL) reg_ctl (CHANNEL)
+#define reg_alt_status(CHANNEL) reg_ctl (CHANNEL)
 
 /* Alternate Status Register bits. */
-#define ALT_BSY 0x80
-#define ALT_DRDY 0x40
-#define ALT_DF 0x20
-#define ALT_DSC 0x10
-#define ALT_DRQ 0x08
-#define ALT_CORR 0x04
-#define ALT_IDX 0x02
-#define ALT_ERR 0x01
+#define STA_BSY 0x80
+#define STA_DRDY 0x40
+#define STA_DF 0x20
+#define STA_DSC 0x10
+#define STA_DRQ 0x08
+#define STA_CORR 0x04
+#define STA_IDX 0x02
+#define STA_ERR 0x01
 
 /* Control Register bits. */
 #define CTL_SRST 0x04
 #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 
   {
-    const char *name;
+    char name[8];
     struct channel *channel;
     int device;
 
@@ -46,86 +57,91 @@ struct disk
 
 struct channel 
   {
-    const char *name;
+    char name[8];
     uint16_t reg_base;
-    int irq;
+    uint8_t irq;
+
+    struct lock lock;
+    bool expecting_interrupt;
+    struct semaphore completion_wait;
+
     struct disk dev[2];
   };
 
 #define CHANNEL_CNT (sizeof channels / sizeof *channels)
-static struct channel channels[2] = {
-  {"ide0", 0x1f0, 14},
-  {"ide1", 0x170, 15},
-};
+static struct channel channels[2];
 
 static void
-select_device (struct device *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 (c));
-  ndelay (400);
-}
-
-static void
-wait_idle (struct device *d) 
+wait_until_idle (const struct disk *d) 
 {
   int i;
 
   for(i = 0; i < 1000; i++) 
     {
-      if ((inb (reg_status (d->channel)) & (STA_BUSY | STA_DRQ)) == 0)
+      if ((inb (reg_status (d->channel)) & (STA_BSY | STA_DRQ)) == 0)
         return;
-      udelay (10);
+      timer_usleep (10);
     }
 
   printk ("%s: idle timeout\n", d->name);
 }
 
-static void
-select_device_wait (struct device *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 (struct channel *c
+wait_while_busy (const struct disk *d
 {
+  struct channel *c = d->channel;
   int i;
   
   for (i = 0; i < 3000; i++)
     {
       if (i == 700)
-        printk ("%s: busy, waiting...");
-      if (!(inb (reg_alt (c)) & ALT_BSY)) 
+        printk ("%s: busy, waiting...", d->name);
+      if (!(inb (reg_alt_status (c)) & STA_BSY)) 
         {
           if (i >= 700)
             printk ("ok\n");
           return true; 
         }
-      mdelay (10);
+      timer_msleep (10);
     }
 
-  printk ("failed\n", c->name);
+  printk ("failed\n");
   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) 
 {
   bool present[2];
+  int device;
 
   /* The ATA reset sequence depends on which devices are present,
      so we start by detecting device presence. */
   for (device = 0; device < 2; device++)
     {
       struct disk *d = &c->dev[device];
-      
+
       select_device (d);
 
       outb (reg_nsect (c), 0x55);
@@ -144,38 +160,40 @@ reset_channel (struct channel *c)
   /* Issue soft reset sequence, which selects device 0 as a side effect.
      Also enable interrupts. */
   outb (reg_ctl (c), 0);
-  delay (1);
+  timer_usleep (10);
   outb (reg_ctl (c), CTL_SRST);
-  delay (1);
+  timer_usleep (10);
   outb (reg_ctl (c), 0);
-  delay (150);
+
+  timer_msleep (150);
 
   /* Wait for device 0 to clear BSY. */
   if (present[0]) 
     {
-      select_device (c->dev[0]);
-      busy_wait (c); 
+      select_device (&c->dev[0]);
+      wait_while_busy (&c->dev[0]); 
     }
 
   /* Wait for device 1 to clear BSY. */
   if (present[1])
     {
-      select_device (c->dev[1]);
+      int i;
+
+      select_device (&c->dev[1]);
       for (i = 0; i < 3000; i++) 
         {
           if (inb (reg_nsect (c)) == 1 && inb (reg_lbal (c)) == 1)
             break;
-          delay (10);
+          timer_msleep (10);
         }
-      busy_wait (c);
+      wait_while_busy (&c->dev[1]);
     }
 }
 
 static bool
-check_ata_device (const struct device *d) 
+check_device_type (struct disk *d) 
 {
   struct channel *c = d->channel;
-  bool maybe_slave;     /* If D is a master, could a slave exist? */
   uint8_t error, lbam, lbah;
 
   select_device (d);
@@ -197,7 +215,7 @@ check_ata_device (const struct device *d)
 }
 
 static void
-execute_command (struct disk *d, uint8_t command) 
+issue_command (struct disk *d, uint8_t command) 
 {
   struct channel *c = d->channel;
 
@@ -205,57 +223,188 @@ execute_command (struct disk *d, uint8_t command)
      up'd by the completion handler. */
   ASSERT (intr_get_level () == IF_ON);
 
+  /* Atomically note that we expect an interrupt and send the
+     command to the device. */
   intr_disable ();
-  sema_init (&completion_sema, 0, "disk");
-  intr_enable ();
-  
+  c->expecting_interrupt = true;
   outb (reg_command (c), command);
+  intr_enable ();
+}
+
+static bool
+input_sector (struct channel *c, void *sector) 
+{
+  uint8_t status;
 
-  sema_down ();
+  ASSERT (sector != NULL);
+
+  status = inb (reg_status (c));
+  if (status & STA_DRQ) 
+    {
+      /* Command was successful.  Read data into SECTOR. */
+      insw (reg_data (c), sector, DISK_SECTOR_SIZE / 2);
+      return true; 
+    }
+  else 
+    {
+      /* Command failed. */
+      return false; 
+    }
 }
 
-void
+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--) 
+    {
+      int c = string[last ^ 1];
+      if (c != '\0' && !isspace (c))
+        break; 
+    }
+
+  for (i = 0; i <= last; i++)
+    printk ("%c", string[i ^ 1]);
+}
+
+static void
 identify_ata_device (struct disk *d) 
 {
-  struct channel *c = d->channel;
+  uint16_t id[DISK_SECTOR_SIZE / 2];
 
   ASSERT (d->is_ata);
 
   select_device_wait (d);
-  execute_command (CMD_IDENTIFY);
-  
-  
+  issue_command (d, CMD_IDENTIFY);
+  sema_down (&d->channel->completion_wait);
+  wait_while_busy (d);
 
+  if (!input_sector (d->channel, id)) 
+    {
+      d->is_ata = false;
+      return;
+    }
+
+  /* Calculate capacity. */
+  d->capacity = id[60] | ((uint32_t) id[61] << 16);
+
+  /* Print identification message. */
+  printk ("%s: detected %'"PRDSNu" sector (", d->name, d->capacity);
+  if (d->capacity > 1024 / DISK_SECTOR_SIZE * 1024 * 1024)
+    printk ("%"PRDSNu" GB",
+            d->capacity / (1024 / DISK_SECTOR_SIZE * 1024 * 1024));
+  else if (d->capacity > 1024 / DISK_SECTOR_SIZE * 1024)
+    printk ("%"PRDSNu" MB", d->capacity / (1024 / DISK_SECTOR_SIZE * 1024));
+  else if (d->capacity > 1024 / DISK_SECTOR_SIZE)
+    printk ("%"PRDSNu" kB", d->capacity / (1024 / DISK_SECTOR_SIZE));
+  else
+    printk ("%"PRDSNu" byte", d->capacity * DISK_SECTOR_SIZE);
+  printk (") disk, model \"");
+  printk_ata_string ((char *) &id[27], 20);
+  printk ("\", serial \"");
+  printk_ata_string ((char *) &id[10], 10);
+  printk ("\"\n");
 }
 
-void
-disk_init (void
+static void
+interrupt_handler (struct intr_frame *f
 {
   struct channel *c;
 
   for (c = channels; c < channels + CHANNEL_CNT; c++)
+    if (f->vec_no == c->irq)
+      {
+        if (c->expecting_interrupt) 
+          {
+            /* Acknowledge interrupt. */
+            inb (reg_status (c));
+
+            /* Wake up waiter. */
+            sema_up (&c->completion_wait);
+          }
+        else
+          printk ("%s: unexpected interrupt\n", c->name);
+        return;
+      }
+
+  NOT_REACHED ();
+}
+
+void
+disk_init (void) 
+{
+  size_t channel;
+
+  for (channel = 0; channel < CHANNEL_CNT; channel++)
     {
+      struct channel *c = &channels[channel];
       int device;
 
-      /* Register interrupt handler. */
-      register_irq (c);
-
-      /* Initialize device state. */
+      /* Initialize channel. */
+      snprintf (c->name, sizeof c->name, "ide%d", channel);
+      switch (channel) 
+        {
+        case 0:
+          c->reg_base = 0x1f0;
+          c->irq = 14 + 0x20;
+          break;
+        case 1:
+          c->reg_base = 0x170;
+          c->irq = 15 + 0x20;
+          break;
+        default:
+          NOT_REACHED ();
+        }
+      lock_init (&c->lock, c->name);
+      c->expecting_interrupt = false;
+      sema_init (&c->completion_wait, 0, c->name);
+      /* Initialize devices. */
       for (device = 0; device < 2; device++)
         {
           struct disk *d = &c->dev[device];
+          snprintf (d->name, sizeof d->name, "%s:%d", c->name, device);
           d->channel = c;
           d->device = device;
+
+          d->is_ata = false;
+          d->capacity = 0;
         }
 
+      /* Register interrupt handler. */
+      intr_register (c->irq, 0, IF_OFF, interrupt_handler, c->name);
+
       /* 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]);
@@ -265,10 +414,66 @@ disk_init (void)
 struct disk *
 disk_get (int idx) 
 {
+  struct disk *d;
+  
   ASSERT (idx >= 0 && idx < 4);
-  return &channel[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 *);
-void disk_read (struct disk *, disk_sector_no, void *);
-void disk_write (struct disk *, disk_sector_no, const void *);
+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);
+
+  lock_acquire (&d->channel->lock);
+  select_sector (d, sec_no);
+  issue_command (d, CMD_READ_SECTOR_RETRY);
+  sema_down (&d->channel->completion_wait);
+  wait_while_busy (d);
+  if (!input_sector (d->channel, buffer))
+    panic ("%s: disk read failed, sector=%"PRDSNu, d->name, sec_no);
+  lock_release (&d->channel->lock);
+}
+
+void
+disk_write (struct disk *d, disk_sector_no sec_no, const void *buffer)
+{
+  ASSERT (d != NULL);
+  ASSERT (buffer != NULL);
+
+  lock_acquire (&d->channel->lock);
+  select_sector (d, sec_no);
+  issue_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);
+  sema_down (&d->channel->completion_wait);
+  lock_release (&d->channel->lock);
+}