#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
{
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;
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;
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)
{
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. */
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;
}
}
+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)
{
select_device_wait (d);
execute_command (d, CMD_IDENTIFY);
- busy_wait (d);
+ wait_while_busy (d);
if (!input_sector (d->channel, id))
{
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
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;
/* 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]);
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);
+}