Index: trunk/src/os2ahci/ahci.c
===================================================================
--- trunk/src/os2ahci/ahci.c	(revision 109)
+++ trunk/src/os2ahci/ahci.c	(revision 110)
@@ -1300,8 +1300,18 @@
 
   if (handled) {
-    /* trigger state machine to process next IORBs, if any */
-    spin_lock(drv_lock);
-    trigger_engine();
-    spin_unlock(drv_lock);
+    /* Trigger state machine to process next IORBs, if any. Due to excessive
+     * IORB requeue operations (e.g. when processing large unaligned reads or
+     * writes), we may be stacking interrupts on top of each other. If we
+     * detect this, we'll pass this on to the engine context hook.
+     */
+    if ((u16) (u32) (void _far *) &irq_stat < 0xf000) {
+      ddprintf("IRQ stack running low; arming engine context hook\n");
+      DevHelp_ArmCtxHook(0, engine_ctxhook_h);
+
+    } else {
+      spin_lock(drv_lock);
+      trigger_engine();
+      spin_unlock(drv_lock);
+    }
 
     /* complete the interrupt */
@@ -1328,5 +1338,6 @@
   u32 done_mask;
 
-  ddprintf("port interrupt for adapter #%d, port #%d\n", ad_no(ai), p);
+  ddprintf("port interrupt for adapter #%d, port #%d, stack frame %Fp\n", ad_no(ai),
+           p, (void _far *) &done_queue);
   memset(&done_queue, 0x00, sizeof(done_queue));
 
Index: trunk/src/os2ahci/ata.c
===================================================================
--- trunk/src/os2ahci/ata.c	(revision 109)
+++ trunk/src/os2ahci/ata.c	(revision 110)
@@ -33,4 +33,12 @@
 
 /* -------------------------- function prototypes -------------------------- */
+
+static int ata_cmd_read (IORBH _far *iorb, AD_INFO *ai, int p, int d, int slot,
+                         ULONG sector, ULONG count, SCATGATENTRY _far *sg_list,
+                         ULONG sg_cnt);
+
+static int ata_cmd_write(IORBH _far *iorb, AD_INFO *ai, int p, int d, int slot,
+                         ULONG sector, ULONG count, SCATGATENTRY _far *sg_list,
+                         ULONG sg_cnt, int write_through);
 
 /* ------------------------ global/static variables ------------------------ */
@@ -124,5 +132,5 @@
       if (ata_cmd.lba_l & 0xf0000000UL) {
         dprintf("error: LBA-28 address %ld has more than 28 bits\n", ata_cmd.lba_l);
-        return(-1);
+        return(ATA_CMD_INVALID_PARM);
       }
       /* add upper 4 bits to device field */
@@ -152,5 +160,5 @@
     case AP_VADDR:
       /* virtual buffer address in addr/len format (up to 4K) */
-      DevHelp_VirtToPhys(va_arg(va, void _far *), &sg_single.ppXferBuf);
+      sg_single.ppXferBuf = virt_to_phys(va_arg(va, void _far *));
       sg_single.XferBufLen = va_arg(va, u16);
       sg_list = &sg_single;
@@ -175,5 +183,5 @@
     default:
       dprintf("error: v_ata_cmd() called with invalid parameter type (%d)\n", (int) ap);
-      return(-1);
+      return(ATA_CMD_INVALID_PARM);
     }
 
@@ -254,4 +262,8 @@
         return(i - 1);
       }
+      if ((sg_addr & 1) || (chunk & 1)) {
+        ddprintf("error: ata_cmd() called with unaligned S/G element(s)\n");
+        return(ATA_CMD_UNALIGNED_ADDR);
+      }
       cmd_tbl->sg_list[n].addr = sg_addr;
       cmd_tbl->sg_list[n].size = chunk - 1;
@@ -277,5 +289,5 @@
   }
 
-  return(0);
+  return(ATA_CMD_SUCCESS);
 }
 
@@ -567,4 +579,14 @@
   int rc;
 
+  if (io->BlockCount == 0) {
+    /* NOP; return -1 without error in IORB to indicate success */
+    return(-1);
+  }
+
+  if (add_workspace(iorb)->unaligned) {
+    /* unaligned S/G addresses present; need to use double buffers */
+    return(ata_read_unaligned(iorb, slot));
+  }
+
   /* Kludge: some I/O commands during boot use excessive S/G buffer lengths
    * which cause NCQ commands to lock up. If there's only one S/G element
@@ -573,6 +595,6 @@
    */
   if (io->BlocksXferred == 0 && io->cSGList == 1 &&
-      io->pSGList[0].XferBufLen > io->BlockCount * io->BlockSize) {
-    io->pSGList[0].XferBufLen = io->BlockCount * io->BlockSize;
+      io->pSGList[0].XferBufLen > (ULONG) io->BlockCount * io->BlockSize) {
+    io->pSGList[0].XferBufLen = (ULONG) io->BlockCount * io->BlockSize;
   }
 
@@ -581,39 +603,6 @@
     sg_indx = ata_get_sg_indx(io);
     sg_cnt = io->cSGList - sg_indx;
-
-    if (sector >= (1UL << 28) || count > 256 || add_workspace(iorb)->is_ncq) {
-      /* need LBA48 for this command */
-      if (!ai->ports[p].devs[d].lba48) {
-        iorb_seterr(iorb, IOERR_RBA_LIMIT);
-        return(-1);
-      }
-      if (add_workspace(iorb)->is_ncq) {
-        /* use NCQ read; count goes into feature register, tag into count! */
-        rc = ata_cmd(ai, p, d, slot, ATA_CMD_FPDMA_READ,
-                     AP_SECTOR_48, (u32) sector, (u16) 0,
-                     AP_FEATURES,  (u16) count,
-                     AP_COUNT,     (u16) (slot << 3), /* tag = slot */
-                     AP_SGLIST,    io->pSGList + sg_indx, (u16) sg_cnt,
-                     AP_DEVICE,    0x40,
-                     AP_END);
-      } else {
-        rc = ata_cmd(ai, p, d, slot, ATA_CMD_READ_EXT,
-                     AP_SECTOR_48, (u32) sector, (u16) 0,
-                     AP_COUNT,     (u16) count,
-                     AP_SGLIST,    io->pSGList + sg_indx, (u16) sg_cnt,
-                     AP_DEVICE,    0x40,
-                     AP_END);
-      }
-
-    } else {
-      rc = ata_cmd(ai, p, d, slot, ATA_CMD_READ,
-                   AP_SECTOR_28, (u32) sector,
-                   AP_COUNT,     (u16) count & 0xffU,
-                   AP_SGLIST,    io->pSGList + sg_indx, (u16) sg_cnt,
-                   AP_DEVICE,    0x40,
-                   AP_END);
-    }
-
-    if (rc > 0) {
+    if ((rc = ata_cmd_read(iorb, ai, p, d, slot, sector, count,
+                           io->pSGList + sg_indx, sg_cnt)) > 0) {
       /* couldn't map all S/G elements */
       ata_max_sg_cnt(io, sg_indx, (USHORT) rc, &sg_cnt, &count);
@@ -628,4 +617,53 @@
     iorb_seterr(iorb, IOERR_CMD_SGLIST_BAD);
 
+  } else if (rc == ATA_CMD_UNALIGNED_ADDR) {
+    /* unaligned S/G addresses detected; need to use double buffers */
+    add_workspace(iorb)->unaligned = 1;
+    return(ata_read_unaligned(iorb, slot));
+
+  } else {
+    iorb_seterr(iorb, IOERR_CMD_ADD_SOFTWARE_FAILURE);
+  }
+
+  return(rc);
+}
+
+/******************************************************************************
+ * Read sectors from AHCI device with unaligned S/G element addresses. AHCI
+ * only allows aligned S/G addresses while OS/2 doesn't have these kind of
+ * restrictions. This doesn't happen very often but when it does, we need to
+ * use a transfer buffer and copy the data manually.
+ */
+int ata_read_unaligned(IORBH _far *iorb, int slot)
+{
+  IORB_EXECUTEIO _far *io = (IORB_EXECUTEIO _far *) iorb;
+  ADD_WORKSPACE _far *aws = add_workspace(iorb);
+  AD_INFO *ai = ad_infos + iorb_unit_adapter(iorb);
+  ULONG sector = io->RBA + io->BlocksXferred;
+  SCATGATENTRY sg_single;
+  int p = iorb_unit_port(iorb);
+  int d = iorb_unit_device(iorb);
+  int rc;
+
+  ddprintf("ata_read_unaligned(%d.%d.%d, %ld)\n", ad_no(ai), p, d, sector);
+
+  /* allocate transfer buffer */
+  if ((aws->buf = malloc(io->BlockSize)) == NULL) {
+    iorb_seterr(iorb, IOERR_CMD_SW_RESOURCE);
+    return(-1);
+  }
+
+  /* prepare read command using transfer buffer */
+  sg_single.ppXferBuf = virt_to_phys(aws->buf);
+  sg_single.XferBufLen = io->BlockSize;
+  rc = ata_cmd_read(iorb, ai, p, d, slot, sector, 1, &sg_single, 1);
+
+  if (rc == 0) {
+    add_workspace(iorb)->blocks = 1;
+    add_workspace(iorb)->ppfunc = ata_read_pp;
+
+  } else if (rc > 0) {
+    iorb_seterr(iorb, IOERR_CMD_SGLIST_BAD);
+
   } else {
     iorb_seterr(iorb, IOERR_CMD_ADD_SOFTWARE_FAILURE);
@@ -638,9 +676,18 @@
  * Post processing function for ata_read(); this function updates the
  * BlocksXferred counter in the IORB and, if not all blocks have been
- * transferred, requeues the IORB to process the remaining sectors.
+ * transferred, requeues the IORB to process the remaining sectors. It also
+ * takes care of copying data from the transfer buffer for unaligned reads.
  */
 void ata_read_pp(IORBH _far *iorb)
 {
   IORB_EXECUTEIO _far *io = (IORB_EXECUTEIO _far *) iorb;
+  ADD_WORKSPACE _far *aws = add_workspace(iorb);
+
+  if (aws->unaligned) {
+    /* copy transfer buffer to corresponding physical address in S/G list */
+    sg_memcpy(io->pSGList, io->cSGList,
+              (ULONG) io->BlocksXferred * (ULONG) io->BlockSize,
+              aws->buf, io->BlockSize, BUF_TO_SG);
+  }
 
   io->BlocksXferred += add_workspace(iorb)->blocks;
@@ -666,4 +713,9 @@
   int d = iorb_unit_device(iorb);
   int rc;
+
+  if (io->BlockCount == 0) {
+    /* NOP; return -1 without error in IORB to indicate success */
+    return(-1);
+  }
 
   /* prepare verify command */
@@ -704,48 +756,21 @@
   int rc;
 
+  if (io->BlockCount == 0) {
+    /* NOP; return -1 without error in IORB to indicate success */
+    return(-1);
+  }
+
+  if (add_workspace(iorb)->unaligned) {
+    /* unaligned S/G addresses present; need to use double buffers */
+    return(ata_write_unaligned(iorb, slot));
+  }
+
   /* prepare write command while keeping an eye on S/G count limitations */
   do {
     sg_indx = ata_get_sg_indx(io);
     sg_cnt = io->cSGList - sg_indx;
-
-    if (sector >= (1UL << 28) || count > 256 || add_workspace(iorb)->is_ncq) {
-      /* need LBA48 for this command */
-      if (!ai->ports[p].devs[d].lba48) {
-        iorb_seterr(iorb, IOERR_RBA_LIMIT);
-        return(-1);
-      }
-      if (add_workspace(iorb)->is_ncq) {
-        /* use NCQ write; count goes into feature register, tag into count! */
-        rc = ata_cmd(ai, p, d, slot, ATA_CMD_FPDMA_WRITE,
-                     AP_SECTOR_48, (u32) sector, (u16) 0,
-                     AP_FEATURES,  (u16) count,
-                     AP_COUNT,     (u16) (slot << 3), /* tag = slot */
-                     AP_SGLIST,    io->pSGList + sg_indx, (u16) sg_cnt,
-                     AP_DEVICE,    0x40,
-		     AP_DEVICE,    (io->Flags & XIO_DISABLE_HW_WRITE_CACHE) ?
-		                   0x80 : 0, /* force unit access */
-                     AP_WRITE,     1,
-                     AP_END);
-      } else {
-        rc = ata_cmd(ai, p, d, slot, ATA_CMD_WRITE_EXT,
-                     AP_SECTOR_48, (u32) sector, (u16) 0,
-                     AP_COUNT,     (u16) count,
-                     AP_SGLIST,    io->pSGList + sg_indx, (u16) sg_cnt,
-                     AP_DEVICE,    0x40,
-                     AP_WRITE,     1,
-                     AP_END);
-      }
-
-    } else {
-      rc = ata_cmd(ai, p, d, slot, ATA_CMD_WRITE,
-                   AP_SECTOR_28, (u32) sector,
-                   AP_COUNT,     (u16) count & 0xffU,
-                   AP_SGLIST,    io->pSGList + sg_indx, (u16) sg_cnt,
-                   AP_DEVICE,    0x40,
-                   AP_WRITE,     1,
-                   AP_END);
-    }
-
-    if (rc > 0) {
+    if ((rc = ata_cmd_write(iorb, ai, p, d, slot, sector, count,
+                            io->pSGList + sg_indx, sg_cnt,
+                            io->Flags & XIO_DISABLE_HW_WRITE_CACHE)) > 0) {
       /* couldn't map all S/G elements */
       ata_max_sg_cnt(io, sg_indx, (USHORT) rc, &sg_cnt, &count);
@@ -760,4 +785,9 @@
     iorb_seterr(iorb, IOERR_CMD_SGLIST_BAD);
 
+  } else if (rc == ATA_CMD_UNALIGNED_ADDR) {
+    /* unaligned S/G addresses detected; need to use double buffers */
+    add_workspace(iorb)->unaligned = 1;
+    return(ata_write_unaligned(iorb, slot));
+
   } else {
     iorb_seterr(iorb, IOERR_CMD_ADD_SOFTWARE_FAILURE);
@@ -766,4 +796,55 @@
   return(rc);
 }
+
+/******************************************************************************
+ * Write sectors from AHCI device with unaligned S/G element addresses. AHCI
+ * only allows aligned S/G addresses while OS/2 doesn't have these kind of
+ * restrictions. This doesn't happen very often but when it does, we need to
+ * use a transfer buffer and copy the data manually.
+ */
+int ata_write_unaligned(IORBH _far *iorb, int slot)
+{
+  IORB_EXECUTEIO _far *io = (IORB_EXECUTEIO _far *) iorb;
+  ADD_WORKSPACE _far *aws = add_workspace(iorb);
+  AD_INFO *ai = ad_infos + iorb_unit_adapter(iorb);
+  ULONG sector = io->RBA + io->BlocksXferred;
+  SCATGATENTRY sg_single;
+  int p = iorb_unit_port(iorb);
+  int d = iorb_unit_device(iorb);
+  int rc;
+
+  ddprintf("ata_write_unaligned(%d.%d.%d, %ld)\n", ad_no(ai), p, d, sector);
+
+  /* allocate transfer buffer */
+  if ((aws->buf = malloc(io->BlockSize)) == NULL) {
+    iorb_seterr(iorb, IOERR_CMD_SW_RESOURCE);
+    return(-1);
+  }
+
+  /* copy next sector from S/G list to transfer buffer */
+  sg_memcpy(io->pSGList, io->cSGList,
+            (ULONG) io->BlocksXferred * (ULONG) io->BlockSize,
+            aws->buf, io->BlockSize, SG_TO_BUF);
+
+  /* prepare write command using transfer buffer */
+  sg_single.ppXferBuf = virt_to_phys(aws->buf);
+  sg_single.XferBufLen = io->BlockSize;
+  rc = ata_cmd_write(iorb, ai, p, d, slot, sector, 1, &sg_single, 1,
+                     io->Flags & XIO_DISABLE_HW_WRITE_CACHE);
+
+  if (rc == 0) {
+    add_workspace(iorb)->blocks = 1;
+    add_workspace(iorb)->ppfunc = ata_write_pp;
+
+  } else if (rc > 0) {
+    iorb_seterr(iorb, IOERR_CMD_SGLIST_BAD);
+
+  } else {
+    iorb_seterr(iorb, IOERR_CMD_ADD_SOFTWARE_FAILURE);
+  }
+
+  return(rc);
+}
+
 
 /******************************************************************************
@@ -888,2 +969,97 @@
 }
 
+/******************************************************************************
+ * Fabricate ATA READ command based on the capabilities of the corresponding
+ * device and the paramters set from above (NCQ, etc).
+ */
+static int ata_cmd_read(IORBH _far *iorb, AD_INFO *ai, int p, int d, int slot,
+                        ULONG sector, ULONG count, SCATGATENTRY _far *sg_list,
+                        ULONG sg_cnt)
+{
+  int rc;
+
+  if (sector >= (1UL << 28) || count > 256 || add_workspace(iorb)->is_ncq) {
+    /* need LBA48 for this command */
+    if (!ai->ports[p].devs[d].lba48) {
+      iorb_seterr(iorb, IOERR_RBA_LIMIT);
+      return(-1);
+    }
+    if (add_workspace(iorb)->is_ncq) {
+      /* use NCQ read; count goes into feature register, tag into count! */
+      rc = ata_cmd(ai, p, d, slot, ATA_CMD_FPDMA_READ,
+                   AP_SECTOR_48, (u32) sector, (u16) 0,
+                   AP_FEATURES,  (u16) count,
+                   AP_COUNT,     (u16) (slot << 3), /* tag == slot */
+                   AP_SGLIST,    sg_list, (u16) sg_cnt,
+                   AP_DEVICE,    0x40,
+                   AP_END);
+    } else {
+      rc = ata_cmd(ai, p, d, slot, ATA_CMD_READ_EXT,
+                   AP_SECTOR_48, (u32) sector, (u16) 0,
+                   AP_COUNT,     (u16) count,
+                   AP_SGLIST,    sg_list, (u16) sg_cnt,
+                   AP_DEVICE,    0x40,
+                   AP_END);
+    }
+
+  } else {
+    rc = ata_cmd(ai, p, d, slot, ATA_CMD_READ,
+                 AP_SECTOR_28, (u32) sector,
+                 AP_COUNT,     (u16) count & 0xffU,
+                 AP_SGLIST,    sg_list, (u16) sg_cnt,
+                 AP_DEVICE,    0x40,
+                 AP_END);
+  }
+  
+  return(rc);
+}
+
+/******************************************************************************
+ * Fabricate ATA WRITE command based on the capabilities of the corresponding
+ * device and the paramters set from above (NCQ, etc)
+ */
+static int ata_cmd_write(IORBH _far *iorb, AD_INFO *ai, int p, int d, int slot,
+                         ULONG sector, ULONG count, SCATGATENTRY _far *sg_list,
+                         ULONG sg_cnt, int write_through)
+{
+  int rc;
+
+  if (sector >= (1UL << 28) || count > 256 || add_workspace(iorb)->is_ncq) {
+    /* need LBA48 for this command */
+    if (!ai->ports[p].devs[d].lba48) {
+      iorb_seterr(iorb, IOERR_RBA_LIMIT);
+      return(-1);
+    }
+    if (add_workspace(iorb)->is_ncq) {
+      /* use NCQ write; count goes into feature register, tag into count! */
+      rc = ata_cmd(ai, p, d, slot, ATA_CMD_FPDMA_WRITE,
+                   AP_SECTOR_48, (u32) sector, (u16) 0,
+                   AP_FEATURES,  (u16) count,
+                   AP_COUNT,     (u16) (slot << 3), /* tag = slot */
+                   AP_SGLIST,    sg_list, (u16) sg_cnt,
+                   AP_DEVICE,    0x40,
+                   AP_DEVICE,    (write_through) ? 0x80 : 0, /* force unit access */
+                   AP_WRITE,     1,
+                   AP_END);
+    } else {
+      rc = ata_cmd(ai, p, d, slot, ATA_CMD_WRITE_EXT,
+                   AP_SECTOR_48, (u32) sector, (u16) 0,
+                   AP_COUNT,     (u16) count,
+                   AP_SGLIST,    sg_list, (u16) sg_cnt,
+                   AP_DEVICE,    0x40,
+                   AP_WRITE,     1,
+                   AP_END);
+    }
+
+  } else {
+    rc = ata_cmd(ai, p, d, slot, ATA_CMD_WRITE,
+                 AP_SECTOR_28, (u32) sector,
+                 AP_COUNT,     (u16) count & 0xffU,
+                 AP_SGLIST,    sg_list, (u16) sg_cnt,
+                 AP_DEVICE,    0x40,
+                 AP_WRITE,     1,
+                 AP_END);
+  }
+
+  return(rc);
+}
Index: trunk/src/os2ahci/ata.h
===================================================================
--- trunk/src/os2ahci/ata.h	(revision 109)
+++ trunk/src/os2ahci/ata.h	(revision 110)
@@ -432,4 +432,13 @@
 } ATA_PARM;
 
+/******************************************************************************
+ * Return codes for ata_cmd(); please note that positive return codes indicate
+ * that not all S/G elements could be mapped, 0 means success and negative
+ * values indicate error conditions.
+ */
+#define ATA_CMD_SUCCESS             0
+#define ATA_CMD_INVALID_PARM       -1
+#define ATA_CMD_UNALIGNED_ADDR     -2
+
 /* ------------------------ typedefs and structures ------------------------ */
 
@@ -473,26 +482,29 @@
 /* -------------------------- function prototypes -------------------------- */
 
-extern int       ata_cmd             (AD_INFO *ai, int port, int device,
-                                      int slot, int cmd, ...);
-extern int       v_ata_cmd           (AD_INFO *ai, int port, int device,
-                                      int slot, int cmd, va_list va);
-extern void      ata_cmd_to_fis      (u8 _far *fis, ATA_CMD _far *cmd,
-                                      int device);
-extern USHORT    ata_get_sg_indx     (IORB_EXECUTEIO _far *io);
-extern void      ata_max_sg_cnt      (IORB_EXECUTEIO _far *io, USHORT sg_indx,
-                                      USHORT sg_max, USHORT _far *sg_cnt,
-                                      USHORT _far *sector_cnt);
-
-extern int       ata_get_geometry    (IORBH _far *iorb, int slot);
-extern void      ata_get_geometry_pp (IORBH _far *iorb);
-extern int       ata_unit_ready      (IORBH _far *iorb, int slot);
-extern int       ata_read            (IORBH _far *iorb, int slot);
-extern void      ata_read_pp         (IORBH _far *iorb);
-extern int       ata_verify          (IORBH _far *iorb, int slot);
-extern int       ata_write           (IORBH _far *iorb, int slot);
-extern void      ata_write_pp        (IORBH _far *iorb);
-extern int       ata_execute_ata     (IORBH _far *iorb, int slot);
-extern int       ata_req_sense       (IORBH _far *iorb, int slot);
-
-extern char     *ata_dev_name        (u16 *id_buf);
-
+extern int       ata_cmd                  (AD_INFO *ai, int port, int device,
+                                           int slot, int cmd, ...);
+extern int       v_ata_cmd                (AD_INFO *ai, int port, int device,
+                                           int slot, int cmd, va_list va);
+extern void      ata_cmd_to_fis           (u8 _far *fis, ATA_CMD _far *cmd,
+                                           int device);
+extern USHORT    ata_get_sg_indx          (IORB_EXECUTEIO _far *io);
+extern void      ata_max_sg_cnt           (IORB_EXECUTEIO _far *io,
+                                           USHORT sg_indx, USHORT sg_max,
+                                           USHORT _far *sg_cnt,
+                                           USHORT _far *sector_cnt);
+
+extern int       ata_get_geometry         (IORBH _far *iorb, int slot);
+extern void      ata_get_geometry_pp      (IORBH _far *iorb);
+extern int       ata_unit_ready           (IORBH _far *iorb, int slot);
+extern int       ata_read                 (IORBH _far *iorb, int slot);
+extern int       ata_read_unaligned       (IORBH _far *iorb, int slot);
+extern void      ata_read_pp              (IORBH _far *iorb);
+extern int       ata_verify               (IORBH _far *iorb, int slot);
+extern int       ata_write                (IORBH _far *iorb, int slot);
+extern int       ata_write_unaligned      (IORBH _far *iorb, int slot);
+extern void      ata_write_pp             (IORBH _far *iorb);
+extern int       ata_execute_ata          (IORBH _far *iorb, int slot);
+extern int       ata_req_sense            (IORBH _far *iorb, int slot);
+
+extern char     *ata_dev_name             (u16 *id_buf);
+
Index: trunk/src/os2ahci/atapi.c
===================================================================
--- trunk/src/os2ahci/atapi.c	(revision 109)
+++ trunk/src/os2ahci/atapi.c	(revision 110)
@@ -80,4 +80,14 @@
   int rc;
 
+  if (io->BlockCount == 0) {
+    /* NOP; return -1 without error in IORB to indicate success */
+    return(-1);
+  }
+
+  if (add_workspace(iorb)->unaligned) {
+    /* unaligned S/G addresses present; need to use double buffers */
+    return(atapi_read_unaligned(iorb, slot));
+  }
+
   /* translate read command to SCSI/ATAPI READ12 command.
    * READ12 seems to be the most supported READ variant - according to MMC,
@@ -112,4 +122,61 @@
   if (rc == 0) {
     add_workspace(iorb)->blocks = count;
+    add_workspace(iorb)->ppfunc = ata_read_pp;
+
+  } else if (rc > 0) {
+    iorb_seterr(iorb, IOERR_CMD_SGLIST_BAD);
+
+  } else if (rc == ATA_CMD_UNALIGNED_ADDR) {
+    /* unaligned S/G addresses detected; need to use double buffers */
+    add_workspace(iorb)->unaligned = 1;
+    return(atapi_read_unaligned(iorb, slot));
+
+  } else {
+    iorb_seterr(iorb, IOERR_CMD_ADD_SOFTWARE_FAILURE);
+  }
+
+  return(rc);
+}
+
+/******************************************************************************
+ * Read sectors from AHCI device with unaligned S/G element addresses. AHCI
+ * only allows aligned S/G addresses while OS/2 doesn't have these kind of
+ * restrictions. This doesn't happen very often but when it does, we need to
+ * use a transfer buffer and copy the data manually.
+ */
+int atapi_read_unaligned(IORBH _far *iorb, int slot)
+{
+  IORB_EXECUTEIO _far *io = (IORB_EXECUTEIO _far *) iorb;
+  ADD_WORKSPACE _far *aws = add_workspace(iorb);
+  ATAPI_CDB_12 cdb;
+  AD_INFO *ai = ad_infos + iorb_unit_adapter(iorb);
+  int p = iorb_unit_port(iorb);
+  int d = iorb_unit_device(iorb);
+  int rc;
+
+  /* translate read command to SCSI/ATAPI READ12 command.
+   * READ12 seems to be the most supported READ variant - according to MMC,
+   * and it's enough even for BluRay.
+   */
+  memset(&cdb, 0x00, sizeof(cdb));
+  cdb.cmd = ATAPI_CMD_READ_12;
+  SET_CDB_32(cdb.lba, io->RBA + io->BlocksXferred);
+  SET_CDB_32(cdb.trans_len, 1);
+
+  /* allocate transfer buffer */
+  if ((aws->buf = malloc(io->BlockSize)) == NULL) {
+    iorb_seterr(iorb, IOERR_CMD_SW_RESOURCE);
+    return(-1);
+  }
+
+  rc = ata_cmd(ai, p, d, slot, ATA_CMD_PACKET,
+               AP_ATAPI_CMD, (void _far *) &cdb, sizeof(cdb),
+               AP_VADDR,     (void _far *) aws->buf, (u16) io->BlockSize,
+               AP_DEVICE,    0x40,
+               AP_FEATURES,  ATAPI_FEAT_DMA | ATAPI_FEAT_DMA_TO_HOST,
+               AP_END);
+
+  if (rc == 0) {
+    add_workspace(iorb)->blocks = 1;
     add_workspace(iorb)->ppfunc = ata_read_pp;
 
Index: trunk/src/os2ahci/atapi.h
===================================================================
--- trunk/src/os2ahci/atapi.h	(revision 109)
+++ trunk/src/os2ahci/atapi.h	(revision 110)
@@ -169,4 +169,5 @@
 extern int     atapi_unit_ready       (IORBH _far *iorb, int slot);
 extern int     atapi_read             (IORBH _far *iorb, int slot);
+extern int     atapi_read_unaligned   (IORBH _far *iorb, int slot);
 extern int     atapi_verify           (IORBH _far *iorb, int slot);
 extern int     atapi_write            (IORBH _far *iorb, int slot);
Index: trunk/src/os2ahci/ioctl.c
===================================================================
--- trunk/src/os2ahci/ioctl.c	(revision 109)
+++ trunk/src/os2ahci/ioctl.c	(revision 110)
@@ -204,5 +204,5 @@
   ic->iorb.cSGList          = ic->sg_cnt;
   ic->iorb.pSGList          = ic->sg_lst;
-  DevHelp_VirtToPhys(ic->sg_lst, &ic->iorb.ppSGLIST);
+  ic->iorb.ppSGLIST         = virt_to_phys(ic->sg_lst);
 
   ic->iorb.ControllerCmdLen = req->cmdlen;
Index: trunk/src/os2ahci/libc.c
===================================================================
--- trunk/src/os2ahci/libc.c	(revision 109)
+++ trunk/src/os2ahci/libc.c	(revision 110)
@@ -96,4 +96,5 @@
 static u8 heap_buf[HEAP_SIZE];
 static u8 heap_units[HEAP_UNIT_CNT];
+static ULONG heap_phys_addr;
 
 /* ----------------------------- start of code ----------------------------- */
@@ -106,4 +107,6 @@
   DevHelp_CreateSpinLock(&mem_lock);
   DevHelp_CreateSpinLock(&com_lock);
+
+  DevHelp_VirtToPhys(heap_buf, &heap_phys_addr);
 }
 
@@ -347,5 +350,4 @@
   va_list va;
   const unsigned char _far *buf = p;
-  long pos = 0;
   int i;
 
@@ -378,5 +380,4 @@
     printf("\n");
 
-    pos += 16;
     buf += 16;
     len -= 16;
@@ -424,4 +425,44 @@
   }
   return(0);
+}
+
+/******************************************************************************
+ * Copy block from S/G list to virtual address or vice versa.
+ */
+void sg_memcpy(SCATGATENTRY _far *sg_list, USHORT sg_cnt, ULONG sg_off,
+               void _far *buf, USHORT len, SG_MEMCPY_DIRECTION dir)
+{
+  USHORT mode_flag;
+  USHORT i;
+  USHORT l;
+  ULONG phys_addr;
+  ULONG pos = 0;
+  char _far *p;
+
+  /* walk through S/G list to find the elements involved in the operation */
+  for (i = 0; i < sg_cnt && len > 0; i++) {
+    if (pos <= sg_off && pos + sg_list[i].XferBufLen > sg_off) {
+
+      /* this S/G element intersects with the block to be copied */
+      phys_addr = sg_list[i].ppXferBuf + (sg_off - pos);
+      if ((l = sg_list[i].XferBufLen - (sg_off - pos)) > len) {
+        l = len;
+      }
+
+      if (DevHelp_PhysToVirt(phys_addr, l, (PVOID) &p, &mode_flag)) {
+        panic("sg_memcpy(): DevHelp_PhysToVirt() failed");
+      }
+      if (dir == SG_TO_BUF) {
+        memcpy(buf, p, l);
+      } else {
+        memcpy(p, buf, l);
+      }
+      sg_off += l;
+      buf = (char _far *) buf + l;
+      len -= l;
+    }
+    
+    pos += sg_list[i].XferBufLen;
+  }
 }
 
@@ -550,4 +591,28 @@
 
 /******************************************************************************
+ * Return the physical address of a pointer inside the heap buffer. This is
+ * necessary because DevHelp_VirtToPhys() can't be called at interrupt time
+ * and we need physical addresses for heap objects when requeueing unaligned
+ * IORBs inside ahci_intr -> trigger_engine.
+ *
+ * If the pointer is not a heap pointer, this function falls back to calling
+ * DevHelp_VirtToPhys with all consequences (i.e. a trap when this is done
+ * at interrupt time).
+ */
+ULONG virt_to_phys(void _far *ptr)
+{
+  if (ptr < heap_buf || ptr > heap_buf + sizeof(heap_buf)) {
+    ULONG addr;
+
+    if (DevHelp_VirtToPhys(ptr, &addr) != 0) {
+      panic("virt_to_phys(): invalid pointer or execution mode");
+    }
+    return(addr);
+  }
+
+  return(heap_phys_addr + ((char _far *) ptr - (char _far *) heap_buf));
+}
+
+/******************************************************************************
  * Calibrate 'mdelay()' loop. This is done by setting up a 1 second timer
  * with a callback that sets 'mdelay_done' to MD_CALIBRATION_END. Then it
@@ -681,8 +746,6 @@
  * interrupts were already disabled or != 0, if not.
  *
- * NOTE: SMP systems must use spinlocks, thus this function will only be
- *       compiled on non-SMP builds.
- */
-#ifndef OS2AHCI_SMP
+ * NOTE: SMP systems should use spinlocks.
+ */
 int disable(void)
 {
@@ -699,5 +762,4 @@
   return(rc);
 }
-#endif
 
 /******************************************************************************
@@ -705,13 +767,10 @@
  * that the presence of _asm statements will disable compiler optimizations.
  *
- * NOTE: SMP systems must use spinlocks, thus this function will only be
- *       compiled on non-SMP builds.
- */
-#ifndef OS2AHCI_SMP
+ * NOTE: SMP systems should use spinlocks.
+ */
 void enable(void)
 {
   _asm sti;
 }
-#endif
 
 /******************************************************************************
Index: trunk/src/os2ahci/os2ahci.c
===================================================================
--- trunk/src/os2ahci/os2ahci.c	(revision 109)
+++ trunk/src/os2ahci/os2ahci.c	(revision 110)
@@ -307,6 +307,6 @@
 
       default:
-        cprintf("invalid option: /%c\n", *s);
-        goto init_fail;
+        cprintf("unknown option: /%c - ignored\n", *s);
+        break;
       }
     }
@@ -481,4 +481,10 @@
  * hand off to a context hook which will continue to trigger the engine until
  * all IORBs have been sent.
+ *
+ * NOTE: While initialization has not completed (or during APM suspend/resume
+ *       operations), this function will loop indefinitely because we can't
+ *       rely on interrupt handlers or context hooks and complex IORBs
+ *       requiring multiple requeues would eventually hang and time out if
+ *       we stopped triggering here.
  */
 void trigger_engine(void)
@@ -486,5 +492,5 @@
   int i;
 
-  for (i = 0; i < 3; i++) {
+  for (i = 0; i < 3 || !init_complete; i++) {
     if (trigger_engine_1() == 0) {
       /* done -- all IORBs have been sent on their way */
Index: trunk/src/os2ahci/os2ahci.h
===================================================================
--- trunk/src/os2ahci/os2ahci.h	(revision 109)
+++ trunk/src/os2ahci/os2ahci.h	(revision 110)
@@ -281,6 +281,6 @@
 typedef struct {
   IORB_QUEUE    iorb_queue;            /* IORB queue for this port */
-  unsigned      dev_max   : 4;         /* maximum device number on this port (0-15) */
-  unsigned      cmd_slot  : 5;         /* current command slot index (using round-
+  unsigned      dev_max     : 4;       /* maximum device number on this port (0-15) */
+  unsigned      cmd_slot    : 5;       /* current command slot index (using round-
                                         * robin indexes to prevent starvation) */
 
@@ -349,6 +349,13 @@
   unsigned      is_ncq        : 1;     /* should use native command queueing */
   unsigned      complete      : 1;     /* IORB has completed processing */
+  unsigned      unaligned     : 1;     /* unaligned S/G; need to use transfer buffer */
   unsigned      cmd_slot      : 5;     /* AHCI command slot for this IORB */
 } ADD_WORKSPACE;
+
+/* sg_memcpy() direction */
+typedef enum {
+  SG_TO_BUF,                           /* copy from S/G list to buffer */
+  BUF_TO_SG                            /* copy from buffer to S/G list */
+} SG_MEMCPY_DIRECTION;  
 
 /* -------------------------- function prototypes -------------------------- */
@@ -444,8 +451,12 @@
 extern char _far  *strcpy        (char _far *dst, const char _far *src);
 extern int         memcmp        (void _far *p1, void _far *p2, size_t len);
+extern void        sg_memcpy     (SCATGATENTRY _far *sg_list, USHORT sg_cnt,
+                                  ULONG sg_off, void _far *buf, USHORT len,
+                                  SG_MEMCPY_DIRECTION dir);
 extern long        strtol        (const char _far *buf,
                                   const char _far * _far *ep, int base);
 extern void       *malloc        (size_t len);
 extern void        free          (void *ptr);
+extern ULONG       virt_to_phys  (void _far *ptr);
 extern void        mdelay_cal    (void);
 extern void        mdelay        (u32 millies);
Index: trunk/src/os2ahci/version.h
===================================================================
--- trunk/src/os2ahci/version.h	(revision 109)
+++ trunk/src/os2ahci/version.h	(revision 110)
@@ -14,4 +14,4 @@
 
 
-#define VERSION            114       /* driver version (2 implied decimals) */
+#define VERSION            115       /* driver version (2 implied decimals) */
 
