#include "synch.h"
#include "timer.h"
-#define reg_data(CHANNEL) ((CHANNEL)->reg_base + 0)
-#define reg_error(CHANNEL) ((CHANNEL)->reg_base + 1)
-#define reg_nsect(CHANNEL) ((CHANNEL)->reg_base + 2)
-#define reg_lbal(CHANNEL) ((CHANNEL)->reg_base + 3)
-#define reg_lbam(CHANNEL) ((CHANNEL)->reg_base + 4)
-#define reg_lbah(CHANNEL) ((CHANNEL)->reg_base + 5)
-#define reg_device(CHANNEL) ((CHANNEL)->reg_base + 6)
-#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_status(CHANNEL) reg_ctl (CHANNEL)
+/* ATA command block port addresses. */
+#define reg_data(CHANNEL) ((CHANNEL)->reg_base + 0) /* Data. */
+#define reg_error(CHANNEL) ((CHANNEL)->reg_base + 1) /* Error. */
+#define reg_nsect(CHANNEL) ((CHANNEL)->reg_base + 2) /* Sector Count. */
+#define reg_lbal(CHANNEL) ((CHANNEL)->reg_base + 3) /* LBA 0:7. */
+#define reg_lbam(CHANNEL) ((CHANNEL)->reg_base + 4) /* LBA 15:8. */
+#define reg_lbah(CHANNEL) ((CHANNEL)->reg_base + 5) /* LBA 23:16. */
+#define reg_device(CHANNEL) ((CHANNEL)->reg_base + 6) /* Device/LBA 27:24. */
+#define reg_status(CHANNEL) ((CHANNEL)->reg_base + 7) /* Status (r/o). */
+#define reg_command(CHANNEL) reg_status (CHANNEL) /* Command (w/o). */
+
+/* ATA control block port addresses.
+ (If we supported non-legacy ATA controllers this would not be
+ flexible enough, but it's fine for what we do.) */
+#define reg_ctl(CHANNEL) ((CHANNEL)->reg_base + 0x206) /* Control (w/o). */
+#define reg_alt_status(CHANNEL) reg_ctl (CHANNEL) /* Alt Status (r/o). */
/* Alternate Status Register bits. */
-#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
+#define STA_BSY 0x80 /* Busy. */
+#define STA_DRQ 0x08 /* Data Request. */
/* Control Register bits. */
-#define CTL_SRST 0x04
-#define CTL_NIEN 0x02
+#define CTL_SRST 0x04 /* Software Reset. */
/* Device Register bits. */
#define DEV_MBS 0xa0 /* Must be set. */
#define DEV_LBA 0x40 /* Linear based addressing. */
#define DEV_DEV 0x10 /* Select device: 0=master, 1=slave. */
-/* Commands. */
-#define CMD_IDENTIFY 0xec /* IDENTIFY DEVICE. */
+/* Commands.
+ Many more are defined but this is the small subset that we
+ use. */
+#define CMD_IDENTIFY_DEVICE 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. */
+/* An ATA device. */
struct disk
{
- char name[8];
- struct channel *channel;
- int device;
+ char name[8]; /* e.g. "hd0:1". */
+ struct channel *channel; /* Channel disk is on. */
+ int dev_no; /* Device 0 or 1 for master or slave. */
- bool is_ata;
- disk_sector_no capacity;
+ bool is_ata; /* 1=This device is an ATA disk. */
+ disk_sector_no capacity; /* Capacity in sectors (if is_ata is true). */
};
+/* An ATA channel (aka controller).
+ Each channel can control up to two disks. */
struct channel
{
- char name[8];
- uint16_t reg_base;
- uint8_t irq;
+ char name[8]; /* e.g. "hd0" */
+ uint16_t reg_base; /* 0x1f0 for hd0, 0x170 for hd1. */
+ uint8_t irq; /* Interrupt in use. */
- struct lock lock;
- bool expecting_interrupt;
- struct semaphore completion_wait;
+ struct lock lock; /* Must acquire to access the controller. */
+ bool expecting_interrupt; /* True if an interrupt is expected, false if
+ any interrupt would be spurious. */
+ struct semaphore completion_wait; /* Up'd by interrupt handler. */
- struct disk dev[2];
+ struct disk devices[2]; /* The devices on this channel. */
};
+/* We support the two "legacy" ATA channels found in a standard PC. */
#define CHANNEL_CNT (sizeof channels / sizeof *channels)
static struct channel channels[2];
-static void
-wait_until_idle (const struct disk *d)
+static void reset_channel (struct channel *);
+static bool check_device_type (struct disk *);
+static void identify_ata_device (struct disk *);
+
+static void select_sector (struct disk *, disk_sector_no);
+static void issue_pio_command (struct channel *, uint8_t command);
+static void input_sector (struct channel *, void *);
+static void output_sector (struct channel *, const void *);
+
+static void wait_until_idle (const struct disk *);
+static bool wait_while_busy (const struct disk *);
+static void select_device (const struct disk *);
+static void select_device_wait (const struct disk *);
+
+static void interrupt_handler (struct intr_frame *);
+
+/* Initialize the disk subsystem and detect disks. */
+void
+disk_init (void)
{
- int i;
+ size_t chan_no;
- for(i = 0; i < 1000; i++)
+ for (chan_no = 0; chan_no < CHANNEL_CNT; chan_no++)
{
- if ((inb (reg_status (d->channel)) & (STA_BSY | STA_DRQ)) == 0)
- return;
- timer_usleep (10);
- }
+ struct channel *c = &channels[chan_no];
+ int dev_no;
- printk ("%s: idle timeout\n", d->name);
+ /* Initialize channel. */
+ snprintf (c->name, sizeof c->name, "hd%d", chan_no);
+ switch (chan_no)
+ {
+ 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 (dev_no = 0; dev_no < 2; dev_no++)
+ {
+ struct disk *d = &c->devices[dev_no];
+ snprintf (d->name, sizeof d->name, "%s:%d", c->name, dev_no);
+ d->channel = c;
+ d->dev_no = dev_no;
+
+ 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);
+
+ /* Distinguish ATA hard disks from other devices. */
+ if (check_device_type (&c->devices[0]))
+ check_device_type (&c->devices[1]);
+
+ /* Read hard disk identity information. */
+ for (dev_no = 0; dev_no < 2; dev_no++)
+ if (c->devices[dev_no].is_ata)
+ identify_ata_device (&c->devices[dev_no]);
+ }
}
-/* Wait up to 30 seconds for disk D to clear BSY. */
-static bool
-wait_while_busy (const struct disk *d)
+/* Returns the disk numbered DEV_NO--either 0 or 1 for master or
+ slave, respectively--within the channel numbered CHAN_NO. */
+struct disk *
+disk_get (int chan_no, int dev_no)
{
- struct channel *c = d->channel;
- int i;
-
- for (i = 0; i < 3000; i++)
+ ASSERT (dev_no == 0 || dev_no == 1);
+
+ if (chan_no < (int) CHANNEL_CNT)
{
- if (i == 700)
- printk ("%s: busy, waiting...", d->name);
- if (!(inb (reg_alt_status (c)) & STA_BSY))
- {
- if (i >= 700)
- printk ("ok\n");
- return true;
- }
- timer_msleep (10);
+ struct disk *d = &channels[chan_no].devices[dev_no];
+ if (d->is_ata)
+ return d;
}
+ return NULL;
+}
- printk ("failed\n");
- return false;
+/* Returns the size of disk D, measured in DISK_SECTOR_SIZE-byte
+ sectors. */
+disk_sector_no
+disk_size (struct disk *d)
+{
+ ASSERT (d != NULL);
+
+ return d->capacity;
}
-static void
-select_device (const struct disk *d)
+/* Reads sector SEC_NO from disk D into BUFFER, which must have
+ room for DISK_SECTOR_SIZE bytes. */
+void
+disk_read (struct disk *d, disk_sector_no sec_no, void *buffer)
{
- 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);
+ struct channel *c;
+
+ ASSERT (d != NULL);
+ ASSERT (buffer != NULL);
+
+ c = d->channel;
+ lock_acquire (&c->lock);
+ select_sector (d, sec_no);
+ issue_pio_command (c, CMD_READ_SECTOR_RETRY);
+ sema_down (&c->completion_wait);
+ if (!wait_while_busy (d))
+ PANIC ("%s: disk read failed, sector=%"PRDSNu, d->name, sec_no);
+ input_sector (c, buffer);
+ lock_release (&c->lock);
}
-static void
-select_device_wait (const struct disk *d)
+/* Write sector SEC_NO to disk D from BUFFER, which must contain
+ DISK_SECTOR_SIZE bytes. Returns after the disk has
+ acknowledged receiving the data. */
+void
+disk_write (struct disk *d, disk_sector_no sec_no, const void *buffer)
{
- wait_until_idle (d);
- select_device (d);
- wait_until_idle (d);
+ struct channel *c;
+
+ ASSERT (d != NULL);
+ ASSERT (buffer != NULL);
+
+ c = d->channel;
+ lock_acquire (&c->lock);
+ select_sector (d, sec_no);
+ issue_pio_command (c, CMD_WRITE_SECTOR_RETRY);
+ if (!wait_while_busy (d))
+ PANIC ("%s: disk write failed, sector=%"PRDSNu, d->name, sec_no);
+ output_sector (c, buffer);
+ sema_down (&c->completion_wait);
+ lock_release (&c->lock);
}
+\f
+/* Disk detection and identification. */
+
+static void printk_ata_string (char *string, size_t size);
+/* Resets an ATA channel and waits for any devices present on it
+ to finish the reset. */
static void
reset_channel (struct channel *c)
{
bool present[2];
- int device;
+ int dev_no;
/* The ATA reset sequence depends on which devices are present,
so we start by detecting device presence. */
- for (device = 0; device < 2; device++)
+ for (dev_no = 0; dev_no < 2; dev_no++)
{
- struct disk *d = &c->dev[device];
+ struct disk *d = &c->devices[dev_no];
select_device (d);
outb (reg_nsect (c), 0x55);
outb (reg_lbal (c), 0xaa);
- present[device] = (inb (reg_nsect (c)) == 0x55
+ present[dev_no] = (inb (reg_nsect (c)) == 0x55
&& inb (reg_lbal (c)) == 0xaa);
}
/* Wait for device 0 to clear BSY. */
if (present[0])
{
- select_device (&c->dev[0]);
- wait_while_busy (&c->dev[0]);
+ select_device (&c->devices[0]);
+ wait_while_busy (&c->devices[0]);
}
/* Wait for device 1 to clear BSY. */
{
int i;
- select_device (&c->dev[1]);
+ select_device (&c->devices[1]);
for (i = 0; i < 3000; i++)
{
if (inb (reg_nsect (c)) == 1 && inb (reg_lbal (c)) == 1)
break;
timer_msleep (10);
}
- wait_while_busy (&c->dev[1]);
+ wait_while_busy (&c->devices[1]);
}
}
+/* Checks whether device D is an ATA disk and sets D's is_ata
+ member appropriately. If D is device 0 (master), returns true
+ if it's possible that a slave (device 1) exists on this
+ channel. If D is device 1 (slave), the return value is not
+ meaningful. */
static bool
check_device_type (struct disk *d)
{
lbam = inb (reg_lbam (c));
lbah = inb (reg_lbah (c));
- if (error != 1 && (error != 0x81 || d->device == 1))
+ if (error != 1 && (error != 0x81 || d->dev_no == 1))
{
d->is_ata = false;
return error != 0x81;
}
}
-static void
-issue_command (struct disk *d, uint8_t command)
-{
- struct channel *c = d->channel;
-
- /* Interrupts must be enabled or our semaphore will never be
- 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 ();
- c->expecting_interrupt = true;
- outb (reg_command (c), command);
- intr_enable ();
-}
-
-static bool
-input_sector (struct channel *c, void *sector)
-{
- uint8_t status;
-
- 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;
- }
-}
-
-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]);
-}
-
+/* Sends an IDENTIFY DEVICE command to disk D and reads the
+ response. Initializes D's capacity member based on the result
+ and prints a message describing the disk to the console. */
static void
identify_ata_device (struct disk *d)
{
+ struct channel *c = d->channel;
uint16_t id[DISK_SECTOR_SIZE / 2];
ASSERT (d->is_ata);
+ /* Send the IDENTIFY DEVICE command, wait for an interrupt
+ indicating the device's response is ready, and read the data
+ into our buffer. */
select_device_wait (d);
- issue_command (d, CMD_IDENTIFY);
- sema_down (&d->channel->completion_wait);
- wait_while_busy (d);
-
- if (!input_sector (d->channel, id))
+ issue_pio_command (c, CMD_IDENTIFY_DEVICE);
+ sema_down (&c->completion_wait);
+ if (!wait_while_busy (d))
{
d->is_ata = false;
return;
}
+ input_sector (c, id);
/* Calculate capacity. */
d->capacity = id[60] | ((uint32_t) id[61] << 16);
else
printk ("%"PRDSNu" byte", d->capacity * DISK_SECTOR_SIZE);
printk (") disk, model \"");
- printk_ata_string ((char *) &id[27], 20);
+ printk_ata_string ((char *) &id[27], 40);
printk ("\", serial \"");
- printk_ata_string ((char *) &id[10], 10);
+ printk_ata_string ((char *) &id[10], 20);
printk ("\"\n");
}
+/* Prints STRING, which consists of SIZE bytes in a funky format:
+ each pair of bytes is in reverse order. Does not print
+ trailing whitespace and/or nulls. */
static void
-interrupt_handler (struct intr_frame *f)
+printk_ata_string (char *string, size_t size)
{
- struct channel *c;
+ size_t i;
- for (c = channels; c < channels + CHANNEL_CNT; c++)
- if (f->vec_no == c->irq)
- {
- if (c->expecting_interrupt)
- {
- /* Acknowledge interrupt. */
- inb (reg_status (c));
+ /* Find the last non-white, non-null character. */
+ for (; size > 0; size--)
+ {
+ int c = string[(size - 1) ^ 1];
+ if (c != '\0' && !isspace (c))
+ break;
+ }
- /* Wake up waiter. */
- sema_up (&c->completion_wait);
- }
- else
- printk ("%s: unexpected interrupt\n", c->name);
- return;
- }
+ /* Print. */
+ for (i = 0; i < size; i++)
+ printk ("%c", string[i ^ 1]);
+}
+\f
+/* Selects device D, waiting for it to become ready, and then
+ writes SEC_NO to the disk's sector selection registers. (We
+ use LBA mode.) */
+static void
+select_sector (struct disk *d, disk_sector_no sec_no)
+{
+ struct channel *c = d->channel;
- NOT_REACHED ();
+ 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->dev_no == 1 ? DEV_DEV : 0) | (sec_no >> 24));
}
-void
-disk_init (void)
+/* Writes COMMAND to channel C and prepares for receiving a
+ completion interrupt. */
+static void
+issue_pio_command (struct channel *c, uint8_t command)
{
- size_t channel;
-
- for (channel = 0; channel < CHANNEL_CNT; channel++)
- {
- struct channel *c = &channels[channel];
- int device;
+ /* Interrupts must be enabled or our semaphore will never be
+ up'd by the completion handler. */
+ ASSERT (intr_get_level () == IF_ON);
- /* 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;
+ c->expecting_interrupt = true;
+ outb (reg_command (c), command);
+}
- d->is_ata = false;
- d->capacity = 0;
- }
+/* Reads a sector from channel C's data register in PIO mode into
+ SECTOR, which must have room for DISK_SECTOR_SIZE bytes. */
+static void
+input_sector (struct channel *c, void *sector)
+{
+ insw (reg_data (c), sector, DISK_SECTOR_SIZE / 2);
+}
- /* Register interrupt handler. */
- intr_register (c->irq, 0, IF_OFF, interrupt_handler, c->name);
+/* Writes SECTOR to channel C's data register in PIO mode.
+ SECTOR must contain DISK_SECTOR_SIZE bytes. */
+static void
+output_sector (struct channel *c, const void *sector)
+{
+ outsw (reg_data (c), sector, DISK_SECTOR_SIZE / 2);
+}
+\f
+/* Low-level ATA primitives. */
- /* Reset hardware. */
- reset_channel (c);
+/* Wait up to 10 seconds for the controller to become idle, that
+ is, for the BSY and DRQ bits to clear in the status register.
- /* Distinguish ATA hard disks from other devices. */
- if (check_device_type (&c->dev[0]))
- check_device_type (&c->dev[1]);
+ As a side effect, reading the status register clears any
+ pending interrupt. */
+static void
+wait_until_idle (const struct disk *d)
+{
+ int i;
- /* Read hard disk identity information. */
- for (device = 0; device < 2; device++)
- if (c->dev[device].is_ata)
- identify_ata_device (&c->dev[device]);
+ for (i = 0; i < 1000; i++)
+ {
+ if ((inb (reg_status (d->channel)) & (STA_BSY | STA_DRQ)) == 0)
+ return;
+ timer_usleep (10);
}
-}
-struct disk *
-disk_get (int idx)
-{
- struct disk *d;
-
- ASSERT (idx >= 0 && idx < 4);
- d = &channels[idx / 2].dev[idx % 2];
- return d->is_ata ? d : NULL;
+ printk ("%s: idle timeout\n", d->name);
}
-disk_sector_no
-disk_size (struct disk *d)
+/* Wait up to 30 seconds for disk D to clear BSY,
+ and then return the status of the DRQ bit.
+ The ATA standards say that a disk may take as long as that to
+ complete its reset. */
+static bool
+wait_while_busy (const struct disk *d)
{
- ASSERT (d != NULL);
+ struct channel *c = d->channel;
+ int i;
- return d->capacity;
+ for (i = 0; i < 3000; i++)
+ {
+ if (i == 700)
+ printk ("%s: busy, waiting...", d->name);
+ if (!(inb (reg_alt_status (c)) & STA_BSY))
+ {
+ if (i >= 700)
+ printk ("ok\n");
+ return (inb (reg_alt_status (c)) & STA_DRQ) != 0;
+ }
+ timer_msleep (10);
+ }
+
+ printk ("failed\n");
+ return false;
}
+/* Program D's channel so that D is now the selected disk. */
static void
-select_sector (struct disk *d, disk_sector_no sec_no)
+select_device (const struct disk *d)
{
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));
+ uint8_t dev = DEV_MBS;
+ if (d->dev_no == 1)
+ dev |= DEV_DEV;
+ outb (reg_device (c), dev);
+ inb (reg_alt_status (c));
+ timer_nsleep (400);
}
-void
-disk_read (struct disk *d, disk_sector_no sec_no, void *buffer)
+/* Select disk D in its channel, as select_device(), but wait for
+ the channel to become idle before and after. */
+static void
+select_device_wait (const struct disk *d)
{
- 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);
+ wait_until_idle (d);
+ select_device (d);
+ wait_until_idle (d);
}
-
-void
-disk_write (struct disk *d, disk_sector_no sec_no, const void *buffer)
+\f
+/* ATA interrupt handler. */
+static void
+interrupt_handler (struct intr_frame *f)
{
- ASSERT (d != NULL);
- ASSERT (buffer != NULL);
+ struct channel *c;
- 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);
+ for (c = channels; c < channels + CHANNEL_CNT; c++)
+ if (f->vec_no == c->irq)
+ {
+ if (c->expecting_interrupt)
+ {
+ inb (reg_status (c)); /* Acknowledge interrupt. */
+ sema_up (&c->completion_wait); /* Wake up waiter. */
+ }
+ else
+ printk ("%s: unexpected interrupt\n", c->name);
+ return;
+ }
+
+ NOT_REACHED ();
}
+
+