Goto sanos source index

//
// hd.c
//
// IDE driver
//
// Copyright (C) 2002 Michael Ringgaard. All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions
// are met:
// 
// 1. Redistributions of source code must retain the above copyright 
//    notice, this list of conditions and the following disclaimer.  
// 2. Redistributions in binary form must reproduce the above copyright
//    notice, this list of conditions and the following disclaimer in the
//    documentation and/or other materials provided with the distribution.  
// 3. Neither the name of the project nor the names of its contributors
//    may be used to endorse or promote products derived from this software
//    without specific prior written permission. 
// 
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
// ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
// OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
// HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
// LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
// OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 
// SUCH DAMAGE.
// 

#include <os/krnl.h>

#define CDSECTORSIZE            2048

#define HD_CONTROLLERS          2
#define HD_DRIVES               4
#define HD_PARTITIONS           4

#define MAX_PRDS                (PAGESIZE / 8)
#define MAX_DMA_XFER_SIZE       ((MAX_PRDS - 1) * PAGESIZE)

#define HDC0_IOBASE             0x01F0
#define HDC1_IOBASE             0x0170

#define HDC0_IRQ                14
#define HDC1_IRQ                15

#define HD0_DRVSEL              0x00 // was:0xA0
#define HD1_DRVSEL              0x10 // was:0xB0
#define HD_LBA                  0x40

#define idedelay() udelay(25)

//
// Controller registers
//

#define HDC_DATA                0x0000
#define HDC_ERR                 0x0001
#define HDC_FEATURE             0x0001
#define HDC_SECTORCNT           0x0002
#define HDC_SECTOR              0x0003
#define HDC_TRACKLSB            0x0004
#define HDC_TRACKMSB            0x0005
#define HDC_DRVHD               0x0006
#define HDC_STATUS              0x0007
#define HDC_COMMAND             0x0007
#define HDC_DEVCTRL             0x0008
#define HDC_ALT_STATUS          0x0206
#define HDC_CONTROL             0x0206

//
// Drive commands
//

#define HDCMD_NULL              0x00
#define HDCMD_IDENTIFY          0xEC
#define HDCMD_RESET             0x10
#define HDCMD_DIAG              0x90
#define HDCMD_READ              0x20
#define HDCMD_WRITE             0x30
#define HDCMD_PACKET            0xA0
#define HDCMD_PIDENTIFY         0xA1
#define HDCMD_MULTREAD          0xC4
#define HDCMD_MULTWRITE         0xC5
#define HDCMD_SETMULT           0xC6
#define HDCMD_READDMA           0xC8
#define HDCMD_WRITEDMA          0xCA
#define HDCMD_SETFEATURES       0xEF
#define HDCMD_FLUSHCACHE        0xE7

//
// Controller status
//

#define HDCS_ERR                0x01   // Error
#define HDCS_IDX                0x02   // Index
#define HDCS_CORR               0x04   // Corrected data
#define HDCS_DRQ                0x08   // Data request
#define HDCS_DSC                0x10   // Drive seek complete
#define HDCS_DWF                0x20   // Drive write fault
#define HDCS_DRDY               0x40   // Drive ready
#define HDCS_BSY                0x80   // Controller busy

//
// Device control 
//

#define HDDC_HD15               0x00  // Use 4 bits for head (not used, was 0x08)
#define HDDC_SRST               0x04  // Soft reset
#define HDDC_NIEN               0x02  // Disable interrupts

//
// Feature commands
//

#define HDFEAT_ENABLE_WCACHE    0x02  // Enable write caching
#define HDFEAT_XFER_MODE        0x03  // Set transfer mode
#define HDFEAT_DISABLE_RLA      0x55  // Disable read-lookahead
#define HDFEAT_DISABLE_WCACHE   0x82  // Disable write caching
#define HDFEAT_ENABLE_RLA       0xAA  // Enable read-lookahead

//
// Transfer modes
//

#define HDXFER_MODE_PIO         0x00
#define HDXFER_MODE_WDMA        0x20
#define HDXFER_MODE_UDMA        0x40

//
// Controller error conditions 
//

#define HDCE_AMNF               0x01   // Address mark not found
#define HDCE_TK0NF              0x02   // Track 0 not found
#define HDCE_ABRT               0x04   // Abort
#define HDCE_MCR                0x08   // Media change requested
#define HDCE_IDNF               0x10   // Sector id not found
#define HDCE_MC                 0x20   // Media change
#define HDCE_UNC                0x40   // Uncorrectable data error
#define HDCE_BBK                0x80   // Bad block

//
// Timeouts (in ms)
//

#define HDTIMEOUT_DRDY          5000
#define HDTIMEOUT_DRQ           5000
#define HDTIMEOUT_CMD           1000
#define HDTIMEOUT_BUSY          60000
#define HDTIMEOUT_XFER          10000

//
// Drive interface types
//

#define HDIF_NONE               0
#define HDIF_PRESENT            1
#define HDIF_UNKNOWN            2
#define HDIF_ATA                3
#define HDIF_ATAPI              4

//
// IDE media types
//

#define IDE_FLOPPY              0x00
#define IDE_TAPE                0x01
#define IDE_CDROM               0x05
#define IDE_OPTICAL             0x07
#define IDE_SCSI                0x21
#define IDE_DISK                0x20

//
// ATAPI commands
//

#define ATAPI_CMD_REQUESTSENSE  0x03
#define ATAPI_CMD_READCAPICITY  0x25
#define ATAPI_CMD_READ10        0x28

//
// Transfer type
//

#define HD_XFER_IDLE            0
#define HD_XFER_READ            1
#define HD_XFER_WRITE           2
#define HD_XFER_DMA             3
#define HD_XFER_IGNORE          4

//
// Bus master registers
//

#define BM_COMMAND_REG          0      // Offset to command reg
#define BM_STATUS_REG           2      // Offset to status reg
#define BM_PRD_ADDR             4      // Offset to PRD addr reg

//
// Bus master command register flags
//

#define BM_CR_STOP              0x00   // Stop transfer
#define BM_CR_START             0x01   // Start transfer
#define BM_CR_READ              0x00   // Read from memory
#define BM_CR_WRITE             0x08   // Write to memory

//
// Bus master status register flags
//

#define BM_SR_ACT               0x01   // Active
#define BM_SR_ERR               0x02   // Error
#define BM_SR_INT               0x04   // INTRQ signal asserted
#define BM_SR_DRV0              0x20   // Drive 0 can do dma
#define BM_SR_DRV1              0x40   // Drive 1 can do dma
#define BM_SR_SIMPLEX           0x80   // Simplex only

//
// Parameters returned by read drive parameters command
//

struct hdparam  {
  unsigned short config;               // General configuration bits
  unsigned short cylinders;            // Cylinders
  unsigned short reserved;
  unsigned short heads;                // Heads
  unsigned short unfbytespertrk;       // Unformatted bytes/track
  unsigned short unfbytes;             // Unformatted bytes/sector
  unsigned short sectors;              // Sectors per track
  unsigned short vendorunique[3];
  char serial[20];                     // Serial number
  unsigned short buffertype;           // Buffer type
  unsigned short buffersize;           // Buffer size, in 512-byte units
  unsigned short necc;                 // ECC bytes appended
  char rev[8];                         // Firmware revision
  char model[40];                      // Model name
  unsigned char nsecperint;            // Sectors per interrupt
  unsigned char resv0;                 // Reserved
  unsigned short usedmovsd;            // Can use double word read/write?
  unsigned short caps;                 // Capabilities
  unsigned short resv1;                // Reserved
  unsigned short pio;                  // PIO data transfer cycle timing (0=slow, 1=medium, 2=fast)
  unsigned short dma;                  // DMA data transfer cycle timing (0=slow, 1=medium, 2=fast)
  unsigned short valid;                // Flag valid fields to follow
  unsigned short logcyl;               // Logical cylinders
  unsigned short loghead;              // Logical heads
  unsigned short logspt;               // Logical sector/track
  unsigned short cap0;                 // Capacity in sectors (32-bit)
  unsigned short cap1;
  unsigned short multisec;             // Multiple sector values
  unsigned short totalsec0;            // Total number of user sectors
  unsigned short totalsec1;            //  (LBA; 32-bit value)
  unsigned short dmasingle;            // Single-word DMA info
  unsigned short dmamulti;             // Multi-word DMA info
  unsigned short piomode;              // Advanced PIO modes
  unsigned short minmulti;             // Minimum multiword xfer
  unsigned short multitime;            // Recommended cycle time
  unsigned short minpio;               // Min PIO without flow ctl
  unsigned short miniodry;             // Min with IORDY flow
  unsigned short resv2[6];             // Reserved
  unsigned short queue_depth;          // Queue depth
  unsigned short resv3[4];             // Reserved
  unsigned short vermajor;             // Major version number
  unsigned short verminor;             // Minor version number
  unsigned short cmdset1;              // Command set supported
  unsigned short cmdset2;
  unsigned short cfsse;                // Command set-feature supported extensions
  unsigned short cfs_enable_1;         // Command set-feature enabled
  unsigned short cfs_enable_2;         // Command set-feature enabled
  unsigned short csf_default;          // Command set-feature default
  unsigned short dmaultra;             // UltraDMA mode (0:5 = supported mode, 8:13 = selected mode)

  unsigned short word89;               // Reserved (word 89)
  unsigned short word90;               // Reserved (word 90)
  unsigned short curapmvalues;         // Current APM values
  unsigned short word92;               // Reserved (word 92)
  unsigned short hw_config;            // Hardware config
  unsigned short words94_125[32];      // Reserved words 94-125
  unsigned short last_lun;             // Reserved (word 126)
  unsigned short word127;              // Reserved (word 127)
  unsigned short dlf;                  // Device lock function
                                       // 15:9  reserved
                                       // 8     security level 1:max 0:high
                                       // 7:6   reserved
                                       // 5     enhanced erase
                                       // 4     expire
                                       // 3     frozen
                                       // 2     locked
                                       // 1     en/disabled
                                       // 0     capability
                                        
  unsigned short csfo;                 // Current set features options
                                       // 15:4 reserved
                                       // 3      auto reassign
                                       // 2      reverting
                                       // 1      read-look-ahead
                                       // 0      write cache
                                         
  unsigned short words130_155[26];     // Reserved vendor words 130-155
  unsigned short word156;
  unsigned short words157_159[3];      // Reserved vendor words 157-159
  unsigned short words160_255[96];     // Reserved words 160-255
};

struct hd;

struct prd {
  unsigned long addr;
  int len;
};

struct hdc  {
  struct mutex lock;                   // Controller mutex
  struct event ready;                  // Controller interrupt event
  struct interrupt intr;               // Interrupt object
  struct dpc xfer_dpc;                 // DPC for data transfer
  
  int status;                          // Controller status

  int iobase;                          // I/O port registers base address
  int irq;                             // IRQ for controller
  int bmregbase;                       // Busmaster register base

  char *bufp;                          // Buffer pointer for next transfer
  int nsects;                          // Number of sectors left to transfer
  int result;                          // Result of transfer
  int dir;                             // Transfer direction
  struct hd *active;                   // Active drive for transfer

  struct prd *prds;                    // PRD list for DMA transfer
  unsigned long prds_phys;             // Physical address of PRD list
};

struct partition {
  dev_t dev;
  unsigned int start;
  unsigned int len;
  unsigned short bootid;
  unsigned short systid;
};

struct hd {
  struct hdc *hdc;                      // Controller
  struct hdparam param;                 // Drive parameter block
  int drvsel;                           // Drive select on controller
  int use32bits;                        // Use 32 bit transfers
  int sectbufs;                         // Number of sector buffers
  int lba;                              // LBA mode
  int iftype;                           // IDE interface type (ATA/ATAPI)
  int media;                            // Device media type (hd, cdrom, ...)
  int multsect;                         // Sectors per interrupt
  int udmamode;                         // UltraDMA mode
  dev_t devno;                          // Device number

  // Geometry
  unsigned int blks;                    // Number of blocks on drive
  unsigned int size;                    // Size in MB

  unsigned int cyls;                    // Number of cylinders
  unsigned int heads;                   // Number of heads
  unsigned int sectors;                 // Sectors per track

  struct partition parts[HD_PARTITIONS]; // Partition info
};

int ideprobe = 0;
static struct hdc hdctab[HD_CONTROLLERS];
static struct hd hdtab[HD_DRIVES];

static int create_partitions(struct hd *hd);

static void hd_fixstring(unsigned char *s, int len) {
  unsigned char *p = s;
  unsigned char *end = s + len;

  // Convert from big-endian to host byte order
  for (p = end ; p != s;) {
     unsigned short *pp = (unsigned short *) (p -= 2);
    *pp = ((*pp & 0x00FF) << 8) | ((*pp & 0xFF00) >> 8);
  }

  // Strip leading blanks
  while (s != end && *s == ' ') ++s;

  // Compress internal blanks and strip trailing blanks
  while (s != end && *s) {
    if (*s++ != ' ' || (s != end && *s && *s != ' ')) *p++ = *(s - 1);
  }

  // Wipe out trailing garbage
  while (p != end) *p++ = '\0';
}

static void hd_error(char *func, unsigned char error) {
  kprintf(KERN_ERR "%s: ", func);
  if (error & HDCE_BBK)   kprintf("bad block  ");
  if (error & HDCE_UNC)   kprintf("uncorrectable data  ");
  if (error & HDCE_MC)    kprintf("media change  ");
  if (error & HDCE_IDNF)  kprintf("id not found  ");
  if (error & HDCE_MCR)   kprintf("media change requested  ");
  if (error & HDCE_ABRT)  kprintf("abort  ");
  if (error & HDCE_TK0NF) kprintf("track 0 not found  ");
  if (error & HDCE_AMNF)  kprintf("address mark not found  ");
  kprintf("\n");
}

static int hd_wait(struct hdc *hdc, unsigned char mask, unsigned int timeout) {
  unsigned int start;
  unsigned char status;

  start = clocks;
  while (1) {
    status = inp(hdc->iobase + HDC_ALT_STATUS);
    if (status & HDCS_ERR) {
      unsigned char error;
 
      error = inp(hdc->iobase + HDC_ERR);
      hd_error("hdwait", error);

      return error;
    }

    if (!(status & HDCS_BSY) && ((status & mask) == mask)) return 0;
    if (time_before(start + timeout, clocks)) return -ETIMEOUT;

    yield();
  }
}

static void hd_select_drive(struct hd *hd) {
  outp(hd->hdc->iobase + HDC_DRVHD, hd->drvsel);
}

static void hd_setup_transfer(struct hd *hd, blkno_t blkno, int nsects) {
  unsigned int track;
  unsigned int head;
  unsigned int sector;

  if (hd->lba) {
    track = (blkno >> 8) & 0xFFFF;
    head = ((blkno >> 24) & 0xF) | HD_LBA;
    sector = blkno & 0xFF;
  } else {
    track = blkno / (hd->heads * hd->sectors);
    head = (blkno / hd->sectors) % hd->heads;
    sector = blkno % hd->sectors + 1;
  }

  outp(hd->hdc->iobase + HDC_FEATURE, 0);
  outp(hd->hdc->iobase + HDC_SECTORCNT, nsects);
  outp(hd->hdc->iobase + HDC_SECTOR, (unsigned char) sector);
  outp(hd->hdc->iobase + HDC_TRACKLSB, (unsigned char) track);
  outp(hd->hdc->iobase + HDC_TRACKMSB, (unsigned char) (track >> 8));
  outp(hd->hdc->iobase + HDC_DRVHD, (unsigned char) (head & 0xFF | hd->drvsel));
}

static void pio_read_buffer(struct hd *hd, char *buffer, int size) {
  struct hdc *hdc = hd->hdc;

  if (hd->use32bits) {
    insd(hdc->iobase + HDC_DATA, buffer, size / 4);
  } else {
    insw(hdc->iobase + HDC_DATA, buffer, size / 2);
  }
}

static void pio_write_buffer(struct hd *hd, char *buffer, int size) {
  struct hdc *hdc = hd->hdc;

  if (hd->use32bits) {
    outsd(hdc->iobase + HDC_DATA, buffer, size / 4);
  } else {
    outsw(hdc->iobase + HDC_DATA, buffer, size / 2);
  }
}

static void setup_dma(struct hdc *hdc, char *buffer, int count, int cmd) {
  int i;
  int len;
  char *next;

  i = 0;
  next = (char *) ((unsigned long) buffer & ~(PAGESIZE - 1)) + PAGESIZE;
  while (1) {
    if (i == MAX_PRDS) panic("hd dma transfer too large");

    hdc->prds[i].addr = virt2phys(buffer);
    len = next - buffer;
    if (count > len) {
      hdc->prds[i].len = len;
      count -= len;
      buffer = next;
      next += PAGESIZE;
      i++;
    } else {
      hdc->prds[i].len = count | 0x80000000;
      break;
    }
  }

  // Setup PRD table
  outpd(hdc->bmregbase + BM_PRD_ADDR, hdc->prds_phys);
  
  // Specify read/write
  outp(hdc->bmregbase + BM_COMMAND_REG, cmd | BM_CR_STOP);

  // Clear INTR & ERROR flags
  outp(hdc->bmregbase + BM_STATUS_REG, inp(hdc->bmregbase + BM_STATUS_REG) | BM_SR_INT | BM_SR_ERR);
}

static void start_dma(struct hdc *hdc) {
  // Start DMA operation
  outp(hdc->bmregbase + BM_COMMAND_REG, inp(hdc->bmregbase + BM_COMMAND_REG) | BM_CR_START);
}

static int stop_dma(struct hdc *hdc) {
  int dmastat;

  // Stop DMA channel and check DMA status
  outp(hdc->bmregbase + BM_COMMAND_REG, inp(hdc->bmregbase + BM_COMMAND_REG) & ~BM_CR_START);
  
  // Get DMA status
  dmastat = inp(hdc->bmregbase + BM_STATUS_REG);

  // Clear INTR && ERROR flags
  outp(hdc->bmregbase + BM_STATUS_REG, dmastat | BM_SR_INT | BM_SR_ERR);

  // Check for DMA errors
  if (dmastat & BM_SR_ERR) {
    kprintf(KERN_ERR "hd: dma error %02X\n", dmastat);
    return -EIO;
  }

  return 0;
}

static int hd_identify(struct hd *hd) {
  // Ignore interrupt for identify command
  hd->hdc->dir = HD_XFER_IGNORE;
  reset_event(&hd->hdc->ready);

  // Issue read drive parameters command
  outp(hd->hdc->iobase + HDC_FEATURE, 0);
  outp(hd->hdc->iobase + HDC_DRVHD, hd->drvsel);
  outp(hd->hdc->iobase + HDC_COMMAND, hd->iftype == HDIF_ATAPI ? HDCMD_PIDENTIFY : HDCMD_IDENTIFY);

  // Wait for data ready
  if (wait_for_object(&hd->hdc->ready, HDTIMEOUT_CMD) < 0) return -ETIMEOUT;
  
  // Some controllers issues the interrupt before data is ready to be read
  // Make sure data is ready by waiting for DRQ to be set
  if (hd_wait(hd->hdc, HDCS_DRQ, HDTIMEOUT_DRQ) < 0) return -EIO;

  // Read parameter data
  insw(hd->hdc->iobase + HDC_DATA, &(hd->param), SECTORSIZE / 2);

  // Fill in drive parameters
  hd->cyls = hd->param.cylinders;
  hd->heads = hd->param.heads;
  hd->sectors = hd->param.sectors;
  hd->use32bits = hd->param.usedmovsd != 0;
  hd->sectbufs = hd->param.buffersize;
  hd->multsect = hd->param.nsecperint;
  if (hd->multsect == 0) hd->multsect = 1;

  hd_fixstring(hd->param.model, sizeof(hd->param.model));
  hd_fixstring(hd->param.rev, sizeof(hd->param.rev));
  hd_fixstring(hd->param.serial, sizeof(hd->param.serial));

  if (hd->iftype == HDIF_ATA) {
    hd->media = IDE_DISK;
  } else {
    hd->media = (hd->param.config >> 8) & 0x1f;
  }

  // Determine LBA or CHS mode
  if ((hd->param.caps & 0x0200) == 0) {
    hd->lba = 0;
    hd->blks = hd->cyls * hd->heads * hd->sectors;
    if (hd->cyls == 0 && hd->heads == 0 && hd->sectors == 0) return -EIO;
    if (hd->cyls == 0xFFFF && hd->heads == 0xFFFF && hd->sectors == 0xFFFF) return -EIO;
  } else {
    hd->lba = 1;
    hd->blks = (hd->param.totalsec1 << 16) | hd->param.totalsec0;
    if (hd->media == IDE_DISK && (hd->blks == 0 || hd->blks == 0xFFFFFFFF)) return -EIO;
  }
  hd->size = hd->blks / (1024 * 1024 / SECTORSIZE);

  return 0;
}

static int hd_cmd(struct hd *hd, unsigned int cmd, unsigned int feat, unsigned int nsects) {
  // Ignore interrupt for command
  hd->hdc->dir = HD_XFER_IGNORE;
  reset_event(&hd->hdc->ready);

  // Issue command
  outp(hd->hdc->iobase + HDC_FEATURE, feat);
  outp(hd->hdc->iobase + HDC_SECTORCNT, nsects);
  outp(hd->hdc->iobase + HDC_DRVHD, hd->drvsel);
  outp(hd->hdc->iobase + HDC_COMMAND, cmd);

  // Wait for data ready
  if (wait_for_object(&hd->hdc->ready, HDTIMEOUT_CMD) < 0) return -ETIMEOUT;

  // Check status
  if (hd->hdc->result < 0) return -EIO;

  return 0;
}

static int atapi_packet_read(struct hd *hd, unsigned char *pkt, int pktlen, void *buffer, size_t bufsize) {
  struct hdc *hdc;
  int result;
  char *bufp;
  int bufleft;
  unsigned short bytes;
  
  //kprintf("atapi_read_packet(0x%x) %d bytes, buflen=%d\n", pkt[0], pktlen, bufsize);

  hdc = hd->hdc;
  if (wait_for_object(&hdc->lock, HDTIMEOUT_BUSY) < 0) return -EBUSY;

  bufp = (char *) buffer;
  bufleft = bufsize;
  hdc->dir = HD_XFER_IGNORE;
  hdc->active = hd;
  reset_event(&hdc->ready);

  // Setup registers
  outp(hdc->iobase + HDC_FEATURE, 0);
  outp(hdc->iobase + HDC_SECTORCNT, 0);
  outp(hdc->iobase + HDC_SECTOR, 0);
  outp(hdc->iobase + HDC_TRACKLSB, (unsigned char) (bufsize & 0xFF));
  outp(hdc->iobase + HDC_TRACKMSB, (unsigned char) (bufsize >> 8));
  outp(hdc->iobase + HDC_DRVHD, (unsigned char) hd->drvsel);
  
  // Send packet command
  outp(hdc->iobase + HDC_COMMAND, HDCMD_PACKET);

  // Wait for drive ready to receive packet
  result = hd_wait(hdc, HDCS_DRDY, HDTIMEOUT_DRDY);
  if (result != 0) {
    kprintf(KERN_WARNING "atapi_packet_read: busy waiting for packet ready (0x%02x)\n", result);

    hdc->dir = HD_XFER_IDLE;
    hdc->active = NULL;
    release_mutex(&hdc->lock);

    return -EIO;
  }

  // Command packet transfer
  pio_write_buffer(hd, pkt, pktlen);

  // Data transfer
  while (1) {
    // Wait until data ready
    //kprintf("wait for data\n");
    if (wait_for_object(&hdc->ready, HDTIMEOUT_XFER) < 0) {
      kprintf(KERN_WARNING "hd_read: timeout waiting for interrupt\n");
      hdc->result = -EIO;
      break;
    }
    reset_event(&hdc->ready);

    // Check for errors
    if (hdc->status & HDCS_ERR) {
      unsigned char error;

      error = inp(hdc->iobase + HDC_ERR);
      //kprintf(KERN_ERR "hd: atapi packet read error (status=0x%02x,error=0x%02x)\n", hdc->status, error);

      hdc->result = -EIO;
      break;
    }

    // Exit the read data loop if the device indicates this is the end of the command
    //kprintf("stat 0x%02x\n", hdc->status);
    if ((hdc->status & (HDCS_BSY | HDCS_DRQ)) == 0) break;

    // Get the byte count
    bytes = (inp(hdc->iobase + HDC_TRACKMSB) << 8) | inp(hdc->iobase + HDC_TRACKLSB);
    //kprintf("%d bytes\n", bytes);
    if (bytes == 0) break;
    if (bytes > bufleft) {
      kprintf(KERN_ERR "%s: buffer overrun\n", device(hd->devno)->name);
      hdc->result = -EBUF;
      break;
    }

    // Read the bytes
    pio_read_buffer(hd, bufp, bytes);
    bufp += bytes;
    bufleft -= bytes;
  }

  // Cleanup
  hdc->dir = HD_XFER_IDLE;
  hdc->active = NULL;
  result = hdc->result;
  release_mutex(&hdc->lock);
  return result == 0 ? bufsize - bufleft : result;
}

static int atapi_read_capacity(struct hd *hd) {
  unsigned char pkt[12];
  unsigned long buf[2];
  unsigned long blks;
  unsigned long blksize;
  int rc;

  memset(pkt, 0, 12);
  pkt[0] = ATAPI_CMD_READCAPICITY;

  rc = atapi_packet_read(hd, pkt, 12, buf, sizeof buf);
  if (rc < 0) return rc;
  if (rc != sizeof buf) return -EBUF;

  blks = ntohl(buf[0]);
  blksize = ntohl(buf[1]);
  if (blksize != CDSECTORSIZE) kprintf("%s: unexpected block size (%d)\n", device(hd->devno)->name, blksize);
  return blks;
}

static int atapi_request_sense(struct hd *hd) {
  unsigned char pkt[12];
  unsigned char buf[18];
  int rc;

  memset(pkt, 0, 12);
  pkt[0] = ATAPI_CMD_REQUESTSENSE;
  pkt[4] = sizeof buf;

  rc = atapi_packet_read(hd, pkt, 12, buf, sizeof buf);
  if (rc < 0) return rc;

  return 0;
}

static int hd_ioctl(struct dev *dev, int cmd, void *args, size_t size) {
  struct hd *hd = (struct hd *) dev->privdata;
  struct geometry *geom;

  switch (cmd) {
    case IOCTL_GETDEVSIZE:
      return hd->blks;

    case IOCTL_GETBLKSIZE:
      return SECTORSIZE;

    case IOCTL_GETGEOMETRY:
      if (!args || size != sizeof(struct geometry)) return -EINVAL;
      geom = (struct geometry *) args;
      geom->cyls = hd->cyls;
      geom->heads = hd->heads;
      geom->spt = hd->sectors;
      geom->sectorsize = SECTORSIZE;
      geom->sectors = hd->blks;

      return 0;

    case IOCTL_REVALIDATE:
      return create_partitions(hd);
  }

  return -ENOSYS;
}

static int hd_read_pio(struct dev *dev, void *buffer, size_t count, blkno_t blkno, int flags) {
  struct hd *hd;
  struct hdc *hdc;
  int sectsleft;
  int nsects;
  int result;
  char *bufp;

//kprintf("hdread: block %d\n", blkno);

  if (count == 0) return 0;
  bufp = (char *) buffer;
  hd = (struct hd *) dev->privdata;
  hdc = hd->hdc;
  sectsleft = count / SECTORSIZE;
  if (wait_for_object(&hdc->lock, HDTIMEOUT_BUSY) < 0) return -EBUSY;

  while (sectsleft > 0) {
    // Select drive
    hd_select_drive(hd);

    // Wait for controller ready
    result = hd_wait(hdc, HDCS_DRDY, HDTIMEOUT_DRDY);
    if (result != 0) {
      kprintf(KERN_ERR "hd_read: no drdy (0x%02x)\n", result);
      hdc->result = -EIO;
      break;
    }

    // Calculate maximum number of sectors we can transfer
//kprintf("%d sects left\n", sectsleft);
    if (sectsleft > 256) {
      nsects = 256;
    } else {
      nsects = sectsleft;
    }

    // Prepare transfer
//kprintf("read %d sects\n", nsects);
    hdc->bufp = bufp;
    hdc->nsects = nsects;
    hdc->result = 0;
    hdc->dir = HD_XFER_READ;
    hdc->active = hd;
    reset_event(&hdc->ready);

    hd_setup_transfer(hd, blkno, nsects);
    outp(hdc->iobase + HDC_COMMAND, hd->multsect > 1 ? HDCMD_MULTREAD : HDCMD_READ);

    // Wait until data read
    if (wait_for_object(&hdc->ready, HDTIMEOUT_XFER) < 0) {
      kprintf(KERN_WARNING "hd_read: timeout waiting for interrupt\n");
      hdc->result = -EIO;
      break;
    }
    if (hdc->result < 0) break;

    // Advance to next
    sectsleft -= nsects;
    bufp += nsects * SECTORSIZE;
  }

//kprintf("finito\n");

  // Cleanup
  hdc->dir = HD_XFER_IDLE;
  hdc->active = NULL;
  result = hdc->result;
  release_mutex(&hdc->lock);
  return result == 0 ? count : result;
}

static int hd_write_pio(struct dev *dev, void *buffer, size_t count, blkno_t blkno, int flags) {
  struct hd *hd;
  struct hdc *hdc;
  int sectsleft;
  int nsects;
  int n;
  int result;
  char *bufp;

//kprintf("hdwrite: block %d\n", blkno);

  if (count == 0) return 0;
  bufp = (char *) buffer;
  hd = (struct hd *) dev->privdata;
  hdc = hd->hdc;
  sectsleft = count / SECTORSIZE;
  if (wait_for_object(&hdc->lock, HDTIMEOUT_BUSY) < 0) return -EBUSY;

  while (sectsleft > 0) {
//kprintf("%d sects left\n", sectsleft);
    // Select drive
    hd_select_drive(hd);

    // Wait for controller ready
    result = hd_wait(hdc, HDCS_DRDY, HDTIMEOUT_DRDY);
    if (result != 0) {
      kprintf(KERN_ERR "hd_write: no drdy (0x%02x)\n", result);
      hdc->result = -EIO;
      break;
    }

    // Calculate maximum number of sectors we can transfer
    if (sectsleft > 256) {
      nsects = 256;
    } else {
      nsects = sectsleft;
    }

//kprintf("write %d sects\n", nsects);
    // Prepare transfer
    hdc->bufp = bufp;
    hdc->nsects = nsects;
    hdc->result = 0;
    hdc->dir = HD_XFER_WRITE;
    hdc->active = hd;
    reset_event(&hdc->ready);

    hd_setup_transfer(hd, blkno, nsects);
    outp(hdc->iobase + HDC_COMMAND, hd->multsect > 1 ? HDCMD_MULTWRITE : HDCMD_WRITE);

    // Wait for data ready
    if (!(inp(hdc->iobase + HDC_ALT_STATUS) & HDCS_DRQ)) {
      result = hd_wait(hdc, HDCS_DRQ, HDTIMEOUT_DRQ);
      if (result != 0) {
        kprintf("hd_write: no drq (0x%02x)\n", result);
        hdc->result = -EIO;
        break;
      }
    }
    
    // Write first sector(s)
    n = hd->multsect;
    if (n > nsects) n = nsects;
    while (n-- > 0) {
      pio_write_buffer(hd, hdc->bufp, SECTORSIZE);
      hdc->bufp += SECTORSIZE;
    }

//kprintf("wait\n");
    // Wait until data written
    if (wait_for_object(&hdc->ready, HDTIMEOUT_XFER) < 0) {
      kprintf(KERN_ERR "hd_write: timeout waiting for interrupt\n");
      hdc->result = -EIO;
      break;
    }
    if (hdc->result < 0) break;

//kprintf("ready\n");

    // Advance to next
    sectsleft -= nsects;
    bufp += nsects * SECTORSIZE;
  }

//kprintf("finito\n");

  // Cleanup
  hdc->dir = HD_XFER_IDLE;
  hdc->active = NULL;
  result = hdc->result;
  release_mutex(&hdc->lock);
  return result == 0 ? count : result;
}

static int hd_read_udma(struct dev *dev, void *buffer, size_t count, blkno_t blkno, int flags) {
  struct hd *hd;
  struct hdc *hdc;
  int sectsleft;
  int nsects;
  int result;
  char *bufp;

  if (count == 0) return 0;
  bufp = (char *) buffer;

  hd = (struct hd *) dev->privdata;
  hdc = hd->hdc;
  sectsleft = count / SECTORSIZE;
  if (wait_for_object(&hdc->lock, HDTIMEOUT_BUSY) < 0) return -EBUSY;

  while (sectsleft > 0) {
    // Select drive
    hd_select_drive(hd);

    // Wait for controller ready
    result = hd_wait(hdc, HDCS_DRDY, HDTIMEOUT_DRDY);
    if (result != 0) {
      kprintf(KERN_ERR "hd_read: no drdy (0x%02x)\n", result);
      result = -EIO;
      break;
    }

    // Calculate maximum number of sectors we can transfer
    if (sectsleft > 256) {
      nsects = 256;
    } else {
      nsects = sectsleft;
    }

    if (nsects > MAX_DMA_XFER_SIZE / SECTORSIZE) nsects = MAX_DMA_XFER_SIZE / SECTORSIZE;

    // Prepare transfer
    result = 0;
    hdc->dir = HD_XFER_DMA;
    hdc->active = hd;
    reset_event(&hdc->ready);

    hd_setup_transfer(hd, blkno, nsects);
    
    // Setup DMA
    setup_dma(hdc, bufp, nsects * SECTORSIZE, BM_CR_WRITE);

    // Start read
    outp(hdc->iobase + HDC_COMMAND, HDCMD_READDMA);
    start_dma(hdc);

    // Wait for interrupt
    if (wait_for_object(&hdc->ready, HDTIMEOUT_XFER) < 0) {
      kprintf(KERN_WARNING "hd: timeout waiting for read to complete\n");
      stop_dma(hdc);
      result = -EIO;
      break;
    }

    // Stop DMA channel and check DMA status
    result = stop_dma(hdc);
    if (result < 0) break;

    // Check controller status
    if (hdc->status & HDCS_ERR) {
      unsigned char error;

      error = inp(hdc->iobase + HDC_ERR);
      hd_error("hdread", error);

      kprintf(KERN_ERR "hd: read error (0x%02x)\n", hdc->status);
      result = -EIO;
      break;
    }

    // Advance to next
    sectsleft -= nsects;
    bufp += nsects * SECTORSIZE;
  }

  // Cleanup
  hdc->dir = HD_XFER_IDLE;
  hdc->active = NULL;
  release_mutex(&hdc->lock);
  return result == 0 ? count : result;
}

static int hd_write_udma(struct dev *dev, void *buffer, size_t count, blkno_t blkno, int flags) {
  struct hd *hd;
  struct hdc *hdc;
  int sectsleft;
  int nsects;
  int result;
  char *bufp;

  if (count == 0) return 0;
  bufp = (char *) buffer;

  hd = (struct hd *) dev->privdata;
  hdc = hd->hdc;
  sectsleft = count / SECTORSIZE;
  if (wait_for_object(&hdc->lock, HDTIMEOUT_BUSY) < 0) return -EBUSY;

  //kprintf("hdwrite block %d size %d buffer %p\n", blkno, count, buffer);

  while (sectsleft > 0) {
    // Select drive
    hd_select_drive(hd);

    // Wait for controller ready
    result = hd_wait(hdc, HDCS_DRDY, HDTIMEOUT_DRDY);
    if (result != 0) {
      kprintf(KERN_ERR "hd_write: no drdy (0x%02x)\n", result);
      result = -EIO;
      break;
    }

    // Calculate maximum number of sectors we can transfer
    if (sectsleft > 256) {
      nsects = 256;
    } else {
      nsects = sectsleft;
    }

    if (nsects > MAX_DMA_XFER_SIZE / SECTORSIZE) nsects = MAX_DMA_XFER_SIZE / SECTORSIZE;

    // Prepare transfer
    result = 0;
    hdc->dir = HD_XFER_DMA;
    hdc->active = hd;
    reset_event(&hdc->ready);

    hd_setup_transfer(hd, blkno, nsects);

    // Setup DMA
    setup_dma(hdc, bufp, nsects * SECTORSIZE, BM_CR_READ);
    
    // Start write
    outp(hdc->iobase + HDC_COMMAND, HDCMD_WRITEDMA);
    start_dma(hdc);

    // Wait for interrupt
    if (wait_for_object(&hdc->ready, HDTIMEOUT_XFER) < 0) {
      kprintf(KERN_WARNING "hd: timeout waiting for write to complete\n");
      stop_dma(hdc);
      result = -EIO;
      break;
    }

    // Stop DMA channel and check DMA status
    result = stop_dma(hdc);
    if (result < 0) break;

    // Check controller status
    if (hdc->status & HDCS_ERR) {
      unsigned char error;

      error = inp(hdc->iobase + HDC_ERR);
      hd_error("hdwrite", error);

      kprintf(KERN_ERR "hd: write error (0x%02x)\n", hdc->status);
      result = -EIO;
      break;
    }

    // Advance to next
    sectsleft -= nsects;
    bufp += nsects * SECTORSIZE;
  }

  // Cleanup
  hdc->dir = HD_XFER_IDLE;
  hdc->active = NULL;
  release_mutex(&hdc->lock);
  return result == 0 ? count : result;
}

static int cd_read(struct dev *dev, void *buffer, size_t count, blkno_t blkno, int flags) {
  struct hd *hd = (struct hd *) dev->privdata;
  unsigned char pkt[12];
  unsigned int blks;

  //kprintf("cd_read: blk %d %d bytes\n", blkno, count);

  blks = count / CDSECTORSIZE;
  if (blks > 0xFFFF) return -EINVAL;

  memset(pkt, 0, 12);
  pkt[0] = ATAPI_CMD_READ10;
  pkt[2] = blkno >> 24;
  pkt[3] = (blkno >> 16) & 0xFF;
  pkt[4] = (blkno >> 8) & 0xFF;
  pkt[5] = blkno & 0xFF;
  pkt[7] = (blks >> 8) & 0xFF;
  pkt[8] = blks & 0xFF;

  return atapi_packet_read(hd, pkt, 12, buffer, count);
}

static int cd_write(struct dev *dev, void *buffer, size_t count, blkno_t blkno, int flags) {
  return -ENODEV;
}

static int cd_ioctl(struct dev *dev, int cmd, void *args, size_t size) {
  struct hd *hd = (struct hd *) dev->privdata;
  int rc;

  switch (cmd) {
    case IOCTL_GETDEVSIZE:
      if (hd->blks <= 0) hd->blks = atapi_read_capacity(hd);
      return hd->blks < 0 ? 0 : hd->blks;

    case IOCTL_GETBLKSIZE:
      return CDSECTORSIZE;

    case IOCTL_REVALIDATE:
      rc = atapi_request_sense(hd);
      if (rc < 0) return rc;

      rc = hd->blks = atapi_read_capacity(hd);
      if (rc < 0) return rc;
      
      return 0;
  }

  return -ENOSYS;
}

void hd_dpc(void *arg) {
  struct hdc *hdc = (struct hdc *) arg;
  int nsects;
  int n;

  //kprintf("[hddpc]");
  switch (hdc->dir) {
    case HD_XFER_READ:
      // Check status
      hdc->status = inp(hdc->iobase + HDC_STATUS);
      if (hdc->status & HDCS_ERR) {
        unsigned char error;

        error = inp(hdc->iobase + HDC_ERR);
        hd_error("hdread", error);

        kprintf(KERN_ERR "hd: read error (0x%02x)\n", hdc->status);
        hdc->result = -EIO;
        set_event(&hdc->ready);
      } else {
        // Read sector data
        nsects = hdc->active->multsect;
        if (nsects > hdc->nsects) nsects = hdc->nsects;
        for (n = 0; n < nsects; n++) {
          pio_read_buffer(hdc->active, hdc->bufp, SECTORSIZE);
          hdc->bufp += SECTORSIZE;
        }

        // Signal event if we have read all sectors
        hdc->nsects -= nsects;
        if (hdc->nsects == 0) set_event(&hdc->ready);
      }
      
      break;

    case HD_XFER_WRITE:
      // Check status
      hdc->status = inp(hdc->iobase + HDC_STATUS);
      if (hdc->status & HDCS_ERR) {
        unsigned char error;

        error = inp(hdc->iobase + HDC_ERR);
        hd_error("hdwrite", error);

        kprintf(KERN_ERR "hd: write error (0x%02x)\n", hdc->status);
        hdc->result = -EIO;
        set_event(&hdc->ready);
      } else {
        // Transfer next sector(s) or signal end of transfer
        nsects = hdc->active->multsect;
        if (nsects > hdc->nsects) nsects = hdc->nsects;
        hdc->nsects -= nsects;

        if (hdc->nsects > 0) {
          nsects = hdc->active->multsect;
          if (nsects > hdc->nsects) nsects = hdc->nsects;

          for (n = 0; n < nsects; n++) {
            pio_write_buffer(hdc->active, hdc->bufp, SECTORSIZE);
            hdc->bufp += SECTORSIZE;
          }
        } else {
          set_event(&hdc->ready);
        }
      }

      break;

    case HD_XFER_DMA:
      outp(hdc->bmregbase + BM_STATUS_REG, inp(hdc->bmregbase + BM_STATUS_REG));
      hdc->status = inp(hdc->iobase + HDC_STATUS);
      set_event(&hdc->ready);
      break;

    case HD_XFER_IGNORE:
      // Read status to acknowledge interrupt
      hdc->status = inp(hdc->iobase + HDC_STATUS);
      set_event(&hdc->ready);
      break;

    case HD_XFER_IDLE:
    default:
      // Read status to acknowledge interrupt
      hdc->status = inp(hdc->iobase + HDC_STATUS);
      kprintf("unexpected intr from hdc\n");
  }
}

int hdc_handler(struct context *ctxt, void *arg) {
  struct hdc *hdc = (struct hdc *) arg;

  if (hdc->xfer_dpc.flags & DPC_QUEUED) kprintf("hd: intr lost\n");
  queue_irq_dpc(&hdc->xfer_dpc, hd_dpc, hdc);
  eoi(hdc->irq);
  return 0;
}

static int part_ioctl(struct dev *dev, int cmd, void *args, size_t size) {
  struct partition *part = (struct partition *) dev->privdata;

  switch (cmd) {
    case IOCTL_GETDEVSIZE:
      return part->len;

    case IOCTL_GETBLKSIZE:
      return dev_ioctl(part->dev, IOCTL_GETBLKSIZE, NULL, 0);
  }

  return -ENOSYS;
}

static int part_read(struct dev *dev, void *buffer, size_t count, blkno_t blkno, int flags) {
  struct partition *part = (struct partition *) dev->privdata;
  if (blkno + count / SECTORSIZE > part->len) return -EFAULT;
  return dev_read(part->dev, buffer, count, blkno + part->start, 0);
}

static int part_write(struct dev *dev, void *buffer, size_t count, blkno_t blkno, int flags) {
  struct partition *part = (struct partition *) dev->privdata;
  if (blkno + count / SECTORSIZE > part->len) return -EFAULT;
  return dev_write(part->dev, buffer, count, blkno + part->start, 0);
}

struct driver harddisk_udma_driver = {
  "idedisk/udma",
  DEV_TYPE_BLOCK,
  hd_ioctl,
  hd_read_udma,
  hd_write_udma
};

struct driver harddisk_pio_driver = {
  "idedisk/pio",
  DEV_TYPE_BLOCK,
  hd_ioctl,
  hd_read_pio,
  hd_write_pio
};

struct driver cdrom_pio_driver = {
  "idecd/pio",
  DEV_TYPE_BLOCK,
  cd_ioctl,
  cd_read,
  cd_write
};

struct driver partition_driver = {
  "partition", 
  DEV_TYPE_BLOCK,
  part_ioctl,
  part_read,
  part_write
};

static int create_partitions(struct hd *hd) {
  struct master_boot_record mbrdata;
  struct master_boot_record *mbr = &mbrdata;
  dev_t devno;
  int rc;
  int i;
  char devname[DEVNAMELEN];

  // Read partition table
  rc = dev_read(hd->devno, mbr, SECTORSIZE, 0, 0);
  if (rc < 0) {
    kprintf(KERN_ERR "%s: error %d reading partition table\n", device(hd->devno)->name, rc);
    return rc;
  }

  // Create partition devices
  if (mbr->signature != MBR_SIGNATURE) {
    kprintf(KERN_ERR "%s: illegal boot sector signature\n", device(hd->devno)->name);
    return -EIO;
  }

  for (i = 0; i < HD_PARTITIONS; i++) {
    hd->parts[i].dev = hd->devno;
    hd->parts[i].bootid = mbr->parttab[i].bootid;
    hd->parts[i].systid = mbr->parttab[i].systid;
    hd->parts[i].start = mbr->parttab[i].relsect;
    hd->parts[i].len = mbr->parttab[i].numsect;

    if (mbr->parttab[i].systid != 0) {
      sprintf(devname, "%s%c", device(hd->devno)->name, 'a' + i);
      devno = dev_open(devname);
      if (devno == NODEV) {
        devno = dev_make(devname, &partition_driver, NULL, &hd->parts[i]);
        kprintf(KERN_INFO "%s: partition %d on %s, %dMB (type %02x)\n", devname, i, device(hd->devno)->name, mbr->parttab[i].numsect / ((1024 * 1024) / SECTORSIZE), mbr->parttab[i].systid);
      } else {
        dev_close(devno);
      }
    }
  }

  return 0;
}

static int probe_device(struct hdc *hdc, int drvsel) {
  unsigned char sc, sn;

  // Probe for device on controller
  outp(hdc->iobase + HDC_DRVHD, drvsel);
  idedelay();

  outp(hdc->iobase + HDC_SECTORCNT, 0x55);
  outp(hdc->iobase + HDC_SECTOR, 0xAA);

  outp(hdc->iobase + HDC_SECTORCNT, 0xAA);
  outp(hdc->iobase + HDC_SECTOR, 0x55);

  outp(hdc->iobase + HDC_SECTORCNT, 0x55);
  outp(hdc->iobase + HDC_SECTOR, 0xAA);

  sc = inp(hdc->iobase + HDC_SECTORCNT);
  sn = inp(hdc->iobase + HDC_SECTOR);

  if (sc == 0x55 && sn == 0xAA) {
    return 1;
  } else {
    return -EIO;
  }
}

static int wait_reset_done(struct hdc *hdc, int drvsel) {
  unsigned int tmo;

  outp(hdc->iobase + HDC_DRVHD, drvsel);
  idedelay();

  tmo = ticks + 5*HZ;
  while (time_after(tmo, ticks)) {
    hdc->status = inp(hdc->iobase + HDC_STATUS);
    if ((hdc->status & HDCS_BSY) == 0) return 0;
  }

  return -EBUSY;
}

static int get_interface_type(struct hdc *hdc, int drvsel) {
  unsigned char sc, sn, cl, ch, st;

  outp(hdc->iobase + HDC_DRVHD, drvsel);
  idedelay();

  sc = inp(hdc->iobase + HDC_SECTORCNT);
  sn = inp(hdc->iobase + HDC_SECTOR);
  //kprintf("%x: sc=0x%02x sn=0x%02x\n", hdc->iobase, sc, sn);

  if (sc == 0x01 && sn == 0x01) {
    cl = inp(hdc->iobase + HDC_TRACKLSB);
    ch = inp(hdc->iobase + HDC_TRACKMSB);
    st = inp(hdc->iobase + HDC_STATUS);

    //kprintf("%x: cl=0x%02x ch=0x%02x st=0x%02x\n", hdc->iobase, cl, ch, st);

    if (cl == 0x14 && ch == 0xeb) return HDIF_ATAPI;
    if (cl == 0x00 && ch == 0x00 && st != 0x00) return HDIF_ATA;
  }

  return HDIF_UNKNOWN;
}

static int setup_hdc(struct hdc *hdc, int iobase, int irq, int bmregbase, int *masterif, int *slaveif) {
  memset(hdc, 0, sizeof(struct hdc));
  hdc->iobase = iobase;
  hdc->irq = irq;
  hdc->bmregbase = bmregbase;
  hdc->dir = HD_XFER_IGNORE;

  if (hdc->bmregbase) {
    // Allocate one page for PRD list
    hdc->prds = (struct prd *) kmalloc(PAGESIZE);
    hdc->prds_phys = virt2phys(hdc->prds);
  }

  init_dpc(&hdc->xfer_dpc);
  init_mutex(&hdc->lock, 0);
  init_event(&hdc->ready, 0, 0);

  if (ideprobe) {
    // Assume no devices connected to controller
    *masterif = HDIF_NONE;
    *slaveif = HDIF_NONE;

    // Setup device control register
    outp(hdc->iobase + HDC_CONTROL, HDDC_HD15 | HDDC_NIEN);

    // Probe for master and slave device on controller
    if (probe_device(hdc, HD0_DRVSEL) >= 0) *masterif = HDIF_PRESENT;
    if (probe_device(hdc, HD1_DRVSEL) >= 0) *slaveif = HDIF_PRESENT;

    // Reset controller
    outp(hdc->iobase + HDC_CONTROL, HDDC_HD15 | HDDC_SRST | HDDC_NIEN);
    idedelay();
    outp(hdc->iobase + HDC_CONTROL, HDDC_HD15 | HDDC_NIEN);
    idedelay();

    // Wait for reset to finish on all present devices
    if (*masterif != HDIF_NONE) {
      int rc = wait_reset_done(hdc, HD0_DRVSEL);
      if (rc < 0) {
        kprintf(KERN_ERR "hd: error %d waiting for reset to complete on master device\n");
        *masterif = HDIF_NONE;
      }
    }

    if (*slaveif != HDIF_NONE) {
      int rc = wait_reset_done(hdc, HD1_DRVSEL);
      if (rc < 0) {
        kprintf(KERN_ERR "hd: error %d waiting for reset to complete on slave device\n");
        *slaveif = HDIF_NONE;
      }
    }

    // Determine interface types
    if (*masterif != HDIF_NONE) *masterif = get_interface_type(hdc, HD0_DRVSEL);
    if (*slaveif != HDIF_NONE) *slaveif = get_interface_type(hdc, HD1_DRVSEL);
  } else {
    // No IDE probing, assume both devices connected to force selection by BIOS settings
    *masterif = HDIF_ATA;
    *slaveif = HDIF_ATA;
  }

  // Enable interrupts
  register_interrupt(&hdc->intr, IRQ2INTR(irq), hdc_handler, hdc);
  enable_irq(irq);

  outp(hdc->iobase + HDC_CONTROL, HDDC_HD15);
  idedelay();

  return 0;
}

static void setup_hd(struct hd *hd, struct hdc *hdc, char *devname, int drvsel, int udmasel, int iftype) {
  static int udma_speed[] = {16, 25, 33, 44, 66, 100};

  int rc;

  // Initialize drive block
  memset(hd, 0, sizeof(struct hd));
  hd->hdc = hdc;
  hd->drvsel = drvsel;
  hd->iftype = iftype;

  // Get info block from device
  rc = hd_identify(hd);
  if (rc < 0) {
    // Try other interface type
    if (hd->iftype == HDIF_ATA) {
      hd->iftype = HDIF_ATAPI;
    } else if (hd->iftype == HDIF_ATAPI) {
      hd->iftype = HDIF_ATA;
    }
    rc = hd_identify(hd);
    if (rc < 0) {
      kprintf("hd: device %s not responding, ignored.\n", devname);
      return;
    }
  }

  // Determine UDMA mode
  if (!hdc->bmregbase) {
    hd->udmamode = -1;
  } else if ((hd->param.valid & 4) &&  (hd->param.dmaultra & (hd->param.dmaultra >> 8) & 0x3F)) {
    if ((hd->param.dmaultra >> 13) & 1) {
      hd->udmamode = 5; // UDMA 100
    } else if ((hd->param.dmaultra >> 12) & 1) {
      hd->udmamode = 4; // UDMA 66
    } else if ((hd->param.dmaultra >> 11) & 1) {
      hd->udmamode = 3; // UDMA 44
    } else if ((hd->param.dmaultra >> 10) & 1) {
      hd->udmamode = 2; // UDMA 33
    } else if ((hd->param.dmaultra >> 9) & 1) {
      hd->udmamode = 1; // UDMA 25
    } else {
      hd->udmamode = 0; // UDMA 16
    }
  } else {
    hd->udmamode = -1;
  }

  // Set multi-sector mode if drive supports it
  if (hd->multsect > 1) {
    rc = hd_cmd(hd, HDCMD_SETMULT, 0, hd->multsect);
    if (rc < 0) {
      kprintf(KERN_WARNING "hd: unable to set multi sector mode\n");
      hd->multsect = 1;
    }
  }

  // Enable UDMA for drive if it supports it.
  if (hd->udmamode != -1) {
    // Enable drive in bus master status register
    int dmastat = inp(hdc->bmregbase + BM_STATUS_REG);
    outp(hdc->bmregbase + BM_STATUS_REG,  dmastat | udmasel);

    // Set feature in IDE controller
    rc = hd_cmd(hd, HDCMD_SETFEATURES, HDFEAT_XFER_MODE, HDXFER_MODE_UDMA | hd->udmamode);
    if (rc < 0) kprintf(KERN_WARNING "hd: unable to enable UDMA mode\n");
  }

  // Enable read ahead and write caching if supported
  if (hd->param.csfo & 2) hd_cmd(hd, HDCMD_SETFEATURES, HDFEAT_ENABLE_RLA, 0);
  if (hd->param.csfo & 1) hd_cmd(hd, HDCMD_SETFEATURES, HDFEAT_ENABLE_WCACHE, 0);

  // Make new device
  if (hd->media == IDE_DISK) {
    if (hd->udmamode != -1) {
      hd->devno = dev_make(devname, &harddisk_udma_driver, NULL, hd);
    } else {
      hd->devno = dev_make(devname, &harddisk_pio_driver, NULL, hd);
    }
  } else if (hd->media == IDE_CDROM) {
    hd->devno = dev_make("cd#", &cdrom_pio_driver, NULL, hd);
  } else {
    kprintf(KERN_ERR "%s: unknown media type 0x%02x (iftype %d, config 0x%04x)\n", devname, hd->media, hd->iftype, hd->param.config);
    return;
  }

  kprintf(KERN_INFO "%s: %s", device(hd->devno)->name, hd->param.model);
  if (hd->size > 0) kprintf(" (%d MB)", hd->size);
  if (hd->lba) kprintf(", LBA");
  if (hd->udmamode != -1) kprintf(", UDMA%d", udma_speed[hd->udmamode]);
  if (hd->param.csfo & 2) kprintf(", read ahead");
  if (hd->param.csfo & 1) kprintf(", write cache");
  if (hd->udmamode == -1 && hd->multsect > 1) kprintf(", %d sects/intr", hd->multsect);
  if (!hd->use32bits) kprintf(", word I/O");
  //if (hd->hdc->bmregbase) kprintf(", bmregbase=0x%x", hd->hdc->bmregbase);
  kprintf("\n");

  if (hd->media == IDE_DISK) create_partitions(hd);
}

void init_hd() {
  int bmiba;
  int numhd;
  struct unit *ide;
  int rc;
  int masterif;
  int slaveif;

  numhd = 4;
  ideprobe = get_num_option(krnlopts, "ideprobe", 1);

  if (!ideprobe) {
    numhd = syspage->biosdata[0x75];
    kprintf("hd: %d IDE device(s) reported by BIOS\n", numhd);
  }

  ide = lookup_unit_by_class(NULL, PCI_CLASS_STORAGE_IDE, PCI_SUBCLASS_MASK);
  if (ide) {
    bmiba = pci_read_config_dword(ide, PCI_CONFIG_BASE_ADDR_4) & 0xFFF0;
    pci_enable_busmastering(ide);
  }

  if (numhd >= 1)  {
    rc = setup_hdc(&hdctab[0], HDC0_IOBASE, HDC0_IRQ, ide ? bmiba : 0, &masterif, &slaveif);
    if (rc < 0) {
      kprintf(KERN_ERR "hd: error %d initializing primary IDE controller\n", rc);
    } else {
      if (numhd >= 1 && masterif > HDIF_UNKNOWN) setup_hd(&hdtab[0], &hdctab[0], "hd0", HD0_DRVSEL, BM_SR_DRV0, masterif);
      if (numhd >= 2 && slaveif > HDIF_UNKNOWN) setup_hd(&hdtab[1], &hdctab[0], "hd1", HD1_DRVSEL, BM_SR_DRV1, slaveif);
    }
  }

  if (numhd >= 3) {
    rc = setup_hdc(&hdctab[1], HDC1_IOBASE, HDC1_IRQ, ide ? bmiba + 8 : 0, &masterif, &slaveif);
    if (rc < 0) {
      kprintf(KERN_ERR "hd: error %d initializing secondary IDE controller\n", rc);
    } else {
      if (numhd >= 3 && masterif > HDIF_UNKNOWN) setup_hd(&hdtab[2], &hdctab[1], "hd2", HD0_DRVSEL, BM_SR_DRV0, masterif);
      if (numhd >= 4 && slaveif > HDIF_UNKNOWN) setup_hd(&hdtab[3], &hdctab[1], "hd3", HD1_DRVSEL, BM_SR_DRV1, slaveif);
    }
  }
}