Index: /tags/2.02/src/idctest/Makefile
===================================================================
--- /tags/2.02/src/idctest/Makefile	(revision 189)
+++ /tags/2.02/src/idctest/Makefile	(revision 189)
@@ -0,0 +1,118 @@
+###############################################################################
+# Makefile - makefile for IDCTEST driver, c600 tool chain
+#
+# Copyright (c) 2011 thi.guten Software Development
+# Copyright (c) 2011 Mensys B.V.
+#
+# Authors: Christian Mueller, Markus Thielen
+#
+#  This program is free software; you can redistribute it and/or modify
+#  it under the terms of the GNU General Public License as published by
+#  the Free Software Foundation; either version 2 of the License, or
+#  (at your option) any later version.
+#
+#  This program is distributed in the hope that it will be useful,
+#  but WITHOUT ANY WARRANTY; without even the implied warranty of
+#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#  GNU General Public License for more details.
+#
+#  You should have received a copy of the GNU General Public License
+#  along with this program; if not, write to the Free Software
+#  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+#
+
+###############################################################################
+# Tool Chain
+#
+
+AS         = $(DDK)\tools\alp.exe
+CC         = cl
+LD         = $(DDK)\base\tools\link.exe
+MAPSYM     = $(DDK)\base\tools\mapsym.exe
+BLDLEVEL   = cmd.exe /c ..\..\tools\bldlvl.cmd
+BLDDATE    = cmd.exe /c ..\..\tools\blddate.cmd
+
+AFLAGS     = -Mb
+CFLAGS     = -c -nologo -Asnw -W2 -G2ms -Zlpd -Fc -Zi -DECS_BUILD
+LFLAGS     = /noe /nod /packd /a:16 /batch /map /line
+
+###############################################################################
+# Environment
+
+# main path to OS/2 DDK; this needs to be set before this makefile will work
+!ifndef DDK
+DDK          = i:\ddk
+!endif
+
+# main path to 16 bit C compiler
+!ifndef CC16
+CC16         = i:\c600
+!endif
+
+CC_INCLUDE   = -I..\include \
+               -I$(DDK)\base\h \
+               -I$(DDK)\base\ibmh \
+	       -I$(DDK)\base\src\dev\dasd\diskh \
+               -I$(DDK)\base\src\dev\thinkpad\dockii\apmcalls \
+               -I$(CC16)\include
+
+AS_INCLUDE   = -I:$(DDK)\base\inc \
+               -I:$(DDK)\base\src\dev\dasd\diskinc
+
+LIB_DIRS     = $(DDK)\base\lib\ \
+	       $(DDK)\base\src\dev\dasd\devhlp\ \
+               $(DDK)\base\src\dev\thinkpad\dockii\apmcalls\ \
+               $(CC16)\lib\ \
+
+
+###############################################################################
+# Main dependencies
+
+TARGET   = idctest.sys
+
+LIBS     = dhcalls doscalls slibcep
+
+SRCS     = init.asm idctest.c
+
+OBJS     = init.obj idctest.obj
+
+INCS     = ..\include\ahci-idc.h
+
+
+all: $(TARGET)
+
+clean:
+	rm -f $(OBJS) $(TARGET) *.cod *.lst
+
+
+###############################################################################
+# Object/source dependencies
+
+init.obj:     init.asm   Makefile
+
+idctest.obj:  idctest.c  Makefile $(INCS)
+
+
+###############################################################################
+# Action definitions (compile/link commands)
+
+# emacs TAGS file creation
+# NOTE: OS/2 emacs etags.exe expects an empty file named c:\dev\null...
+tags:	$(SRCS) $(INCS) Makefile
+	etags.exe $(SRCS) $(INCS) $(DDK)\base\src\dev\dasd\diskh\dhcalls.h \
+	$(DDK)\base\src\dev\dasd\diskh\iorb.h \
+	$(DDK)\base\h\bsedos16.h \
+	$(DDK)\base\h\devcmd.h \
+	$(DDK)\base\h\reqpkt.h
+
+.asm.obj:
+	$(AS) $(AFLAGS) $(AS_INCLUDE) $*.asm
+
+.c.obj:
+	$(CC) $(CFLAGS) $(CC_INCLUDE) $*.c
+
+$(TARGET): $(OBJS) idctest.def Makefile
+	$(LD) $(LFLAGS) $(OBJS),$(TARGET),$*.map,$(LIB_DIRS) $(LIBS),$*.def
+	$(MAPSYM) idctest
+
+
Index: /tags/2.02/src/idctest/idctest.c
===================================================================
--- /tags/2.02/src/idctest/idctest.c	(revision 189)
+++ /tags/2.02/src/idctest/idctest.c	(revision 189)
@@ -0,0 +1,224 @@
+/******************************************************************************
+ * idctest.c - main and only source file for the OS2AHCI IDC test driver
+ *
+ * This driver is used to test the ring 0 IDC interface of OS2AHCI.
+ * When this driver is opened by a Ring 3 process, it uses OS2AHCI's
+ * IDC interface to send it a test IOCTL that does nothing but beep.
+ *
+ * Authors: Christian Mueller, Markus Thielen
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+/*---- includes --------------------------------------------------------------*/
+
+#define INCL_NOPMAPI
+#define INCL_DOSINFOSEG
+#define INCL_NO_SCB
+#define INCL_DOSERRORS
+#include <os2.h>
+#include <dos.h>
+#include <bseerr.h>
+#include <dskinit.h>
+#include <scb.h>
+
+#include <devhdr.h>
+#include <iorb.h>
+#include <strat2.h>
+#include <reqpkt.h>
+
+#include <dhcalls.h>
+
+#include <addcalls.h>
+#include <rmcalls.h>
+#include <devclass.h>
+#include <devcmd.h>
+#include <rmbase.h>
+#include "ahci-idc.h"
+
+
+/*---- function proEtotypes ---------------------------------------------------*/
+
+extern APIRET APIENTRY DosPutMessage           (USHORT hf,
+                                                USHORT cbMsg,
+                                                PCHAR pchMsg);
+
+static unsigned int    strlen                  (PSZ s);
+
+static void            memset                  (void _far *p,
+                                                unsigned int cb,
+                                                unsigned char byte);
+
+int                    init                    (RPINITIN _far *req);
+
+int                    c_strat                 (RPH _far *req);
+
+void                   drv_open                (void);
+
+
+/*---- global data -----------------------------------------------------------*/
+
+char init_msg[] = "\r\nOS2AHCI IDC Test Driver\r\n";
+char init_fail[] = "Failed to attach to OS2AHCI$";
+char init_ok[] = "up and running...\r\n";
+
+PFN Device_Help = 0;
+
+AHCI_ATTACH_AREA attach_data;
+int last_cmd;
+
+extern char _cdecl       end_of_data;   /* label at the end of all data segments */
+extern void _cdecl _near end_of_code(); /* label at the end of all code segments */
+
+/******************************************************************************
+ * c_strat_idc - IDC test driver entry point
+ */
+int c_strat_idc(RPH _far *req)
+
+{
+  USHORT result = STDON;
+  last_cmd = req->Cmd;
+
+  switch (last_cmd) {
+
+  case CMDInit:
+    return (init((RPINITIN _far *) req));
+
+  case CMDOpen:
+    drv_open();
+    return(STDON);
+
+  case CMDGenIOCTL:
+    drv_open();
+    return(STDON);
+
+  case CMDClose:
+    return(STDON);
+
+  default:
+    break;
+
+  }
+
+  return  STDON | STERR | ERROR_BAD_COMMAND;
+}
+
+/******************************************************************************
+ * init
+ */
+int init(RPINITIN _far *req)
+{
+
+  char *ahci_name = "OS2AHCI$ ";
+  RPINITOUT _far *rsp = (RPINITOUT _far *) req;
+  int ret = STDON;
+
+  /* store DevHelp entry point */
+  Device_Help = req->DevHlpEP;
+
+  DosPutMessage(1, strlen(init_msg), init_msg);
+
+  /* attach to OS2AHCI.ADD */
+  if (DevHelp_AttachDD(ahci_name, (NPBYTE) &attach_data)) {
+    /* failed to attach */
+    DosPutMessage(1, strlen(init_fail), init_fail);
+    rsp->CodeEnd = 0;
+    rsp->DataEnd = 0;
+    ret = STDON | ERROR_I24_QUIET_INIT_FAIL; 
+
+  } else {
+    /* successfully attached */
+    DosPutMessage(1, strlen(init_ok), init_ok);
+    rsp->CodeEnd = (USHORT) end_of_code;
+    rsp->DataEnd = (USHORT) &end_of_data;
+    ret = STDON;
+  }
+
+  return ret;
+}
+
+/******************************************************************************
+ * drv_open() - this is called when a ring 3 process opens us with DosOpen.
+ * We then call into OS2AHCI's IDC interface with a test IOCTL that causes
+ * OS2AHCI to beep.
+ */
+void drv_open(void)
+{
+  RP_GENIOCTL rp_ioctl;
+  unsigned short target_ds = attach_data.prot_ds;
+  PFN_AHCI_IDC_ENTRY func = attach_data.prot_entry_point;
+  
+  memset((void _far*) &rp_ioctl, sizeof(rp_ioctl), 0x00);
+  
+  /* prepare request packet */
+  rp_ioctl.rph.Len  = (UCHAR) sizeof(rp_ioctl);
+  rp_ioctl.rph.Cmd  = CMDGenIOCTL;
+  rp_ioctl.Category = OS2AHCI_IDC_CATEGORY;
+  rp_ioctl.Function = OS2AHCI_IDC_BEEP;
+
+  /* call IDC entry point in Assembler;
+   * IDC calling convention is to set DS to the data segment of
+   * the target driver and ES to the segment of the request packet.
+   */
+  _asm {
+    push ds
+    push es
+    push bx
+    push si
+    push di
+
+    push ss
+    pop  es
+    lea  bx, rp_ioctl
+    mov  ds, target_ds
+    call dword ptr [func]
+
+    pop  di
+    pop  si
+    pop  bx
+    pop  es
+    pop  ds
+  }
+
+  
+}
+
+
+/******************************************************************************
+ * strlen
+ */
+static unsigned int strlen(PSZ s)
+{
+  int i = 0;
+  while (*s++) {
+    i++;
+  }
+  
+  return (i);
+}
+
+/******************************************************************************
+ * memset
+ */
+static void memset(void _far *d, unsigned int cb, unsigned char byte)
+{
+  unsigned int i;
+  unsigned char _far *p = (unsigned char _far*) d;
+
+  for (i = 0; i < cb; i++) {
+    p[i] = byte;
+  }
+
+}
Index: /tags/2.02/src/idctest/idctest.def
===================================================================
--- /tags/2.02/src/idctest/idctest.def	(revision 189)
+++ /tags/2.02/src/idctest/idctest.def	(revision 189)
@@ -0,0 +1,17 @@
+library idctest
+Description '$@#thi.guten:1.0.0#@##1## 2012/05/09 13:48:27      ECSINTEL::::00::@@AHCI IDC Test Driver'
+protmode
+
+segments
+  DEVHDR        CLASS 'DATA'
+  _DATA         CLASS 'DATA'
+  CONST         CLASS 'CONST'
+  _BSS          CLASS 'BSS'
+  c_common      CLASS 'BSS'
+  _z_data       CLASS 'BSS'
+  _TEXT         CLASS 'CODE' IOPL
+  'CODE'        CLASS 'CODE' IOPL
+  RMCode        CLASS 'CODE' IOPL
+  LIBCODE       CLASS 'CODE' IOPL
+  _z_text       CLASS 'CODE' IOPL
+
Index: /tags/2.02/src/idctest/init.asm
===================================================================
--- /tags/2.02/src/idctest/init.asm	(revision 189)
+++ /tags/2.02/src/idctest/init.asm	(revision 189)
@@ -0,0 +1,107 @@
+; ----------------------------------------------------------------------------
+; Initialization routines and segment mappings for idctest driver.
+;
+; This is a stripped down version of os2ahci's init.asm.
+
+include devhdr.inc
+
+; -----------------------------------------------------------------------------
+; Public symbols
+
+                PUBLIC  _asm_strat           ; low-level strategy routine
+                PUBLIC  _end_of_data         ; end of all data (label)
+                PUBLIC  _end_of_code         ; end of all code (label)
+        
+; ----------------------------------------------------------------------------
+; Device Driver Header
+
+DEVHDR          SEGMENT WORD PUBLIC 'DATA'
+_dev_hdr        dd      -1                      ; next device header
+                dw      DEVLEV_3 + DEV_CHAR_DEV ; flags for ADD drivers
+                dw      OFFSET _asm_strat       ; strategy routine
+                dw      0                       ; IDC entry point
+                db      "IDCTEST$"              ; name of character device
+                dq      0                       ; 8 reserved bytes
+                dd      0                       ; ADD flags
+                dw      0
+DEVHDR          ENDS
+
+; ----------------------------------------------------------------------------
+; Segment Definitions. We need to reference all segments here, even if they
+; are not used, to allow proper grouping of all segments into one group for
+; data and one group for code as well as proper ordering of the segments
+; within each group to make sure the end of segment markers are at the end
+; of each group.
+
+; On top of that, we need to make sure that the end of segment marker is in
+; a data segment of class 'BSS' because BSS segments are always put towards
+; the end of the executable when linking with high-level language objects
+; such as C.
+
+_DATA           SEGMENT WORD PUBLIC 'DATA'
+_DATA           ENDS
+
+CONST           SEGMENT WORD PUBLIC 'CONST'
+CONST           ENDS
+
+_BSS            SEGMENT WORD PUBLIC 'BSS'
+_BSS            ENDS
+
+c_common        SEGMENT WORD PUBLIC 'BSS'
+c_common        ENDS
+
+_z_data         SEGMENT WORD PUBLIC 'BSS'
+_end_of_data    db      0
+_z_data         ENDS
+
+_TEXT           SEGMENT WORD PUBLIC 'CODE'
+                EXTRN   _c_strat_idc : NEAR           ; C strategy routine
+_TEXT           ENDS
+
+CODE            SEGMENT WORD PUBLIC 'CODE'
+CODE            ENDS
+
+RMCode          SEGMENT WORD PUBLIC 'CODE'
+RMCode          ENDS
+
+LIBCODE         SEGMENT WORD PUBLIC 'CODE'
+LIBCODE         ENDS
+
+_z_text         SEGMENT WORD PUBLIC 'CODE'
+_end_of_code    LABEL NEAR
+_z_text         ENDS
+
+DGROUP          GROUP   DEVHDR, _DATA, CONST, _BSS, c_common, _z_data
+TGROUP          GROUP   _TEXT, CODE, _z_text
+
+; ----------------------------------------------------------------------------
+; Start of code
+
+_TEXT           SEGMENT WORD PUBLIC 'CODE'
+                ASSUME  DS:DGROUP
+                ASSUME  ES:NOTHING
+                ASSUME  SS:NOTHING
+
+
+; Device driver main entry point (strategy routine)
+_asm_strat      PROC    FAR
+
+                ; push request packet address
+                PUSH    ES
+                PUSH    BX
+                CLD
+
+                ; call C strategy routine
+                CALL    _c_strat_idc
+
+                POP     BX
+                POP     ES
+                MOV     WORD PTR ES:[BX+3], AX
+                RET
+_asm_strat      ENDP
+
+
+_TEXT           ENDS
+
+                END
+
Index: /tags/2.02/src/idctest/testprog.c
===================================================================
--- /tags/2.02/src/idctest/testprog.c	(revision 189)
+++ /tags/2.02/src/idctest/testprog.c	(revision 189)
@@ -0,0 +1,60 @@
+/*************************************************************************
+ *
+ *  testprog.c - ring 3 test program for AHCI IDC function.
+ *  This program opens the IDCTEST.SYS driver, which will call OS2AHCI's
+ *  IDC entry point with a TEST IOCTL that does nothing but beep.
+ * 
+ *  Author: Markus Thielen, thi.guten Software Development.
+ *
+ *  Compilation (Watcom): wcl386 -bt=os2 test.c
+ *
+ * Copyright (c) 2010 by thi.guten Software development, www.thiguten.de
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+/*---- includes --------------------------------------------------------------*/
+
+#define INCL_DOSFILEMGR
+#define INCL_DOSPROCESS
+#define INCL_DOSERRORS
+#include <os2.h>
+#include <stdio.h>
+#include <string.h>
+
+
+/******************************************************************************
+ * main()
+ */
+void main(void)
+{
+  ULONG rc;
+  ULONG action;
+  HFILE hf;
+  char *drv_name = "IDCTEST$";
+
+  printf("OS2AHCI IDC Test Program. Do you hear a beep??\n");
+  rc = DosOpen(drv_name, &hf, &action, 0,
+               FILE_NORMAL, FILE_OPEN, OPEN_SHARE_DENYNONE, NULL);
+  if (rc) {
+    /* open failed */
+    fprintf(stderr, "DosOpen failed with code %d ", rc);
+    fprintf(stderr, "Is IDCTEST.SYS really loaded?\n");
+    return;
+  }
+
+  DosClose(hf);
+
+}
Index: /tags/2.02/src/include/ahci-idc.h
===================================================================
--- /tags/2.02/src/include/ahci-idc.h	(revision 189)
+++ /tags/2.02/src/include/ahci-idc.h	(revision 189)
@@ -0,0 +1,71 @@
+/******************************************************************************
+ * ahciidc.h - definitions for the OS2AHCI IDC interface
+ *
+ * Copyright (c) 2011 thi.guten Software Development
+ * Copyright (c) 2011 Mensys B.V.
+ * Copyright (c) 2013-2016 David Azarewicz
+ *
+ * Authors: Christian Mueller, Markus Thielen
+ *
+ * Parts copied from/inspired by the Linux AHCI driver;
+ * those parts are (c) Linux AHCI/ATA maintainers
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+
+/* -------------------------- macros and constants ------------------------- */
+
+/* IDC category */
+#define OS2AHCI_IDC_CATEGORY           0x88
+
+/* enter BIOS mode (e.g. to generate trap dumps)
+ * Request packet parameters: None
+ */
+#define OS2AHCI_IDC_BIOSMODE           0x66
+
+/* test IOCTL; does nothing but BEEP
+ * Request packet parameters: None
+ */
+#define OS2AHCI_IDC_BEEP               0x67
+
+
+/* ------------------------ typedefs and structures ------------------------ */
+
+/* AHCI_IDC_ENTRY defines a function pointer to the OS2AHCI IDC entry point */
+typedef void (_cdecl *PFN_AHCI_IDC_ENTRY) (unsigned short real_ds, REQPACKET *ioctl);
+
+#pragma pack(1)
+
+/*
+ * AHCI_ATTACH_AREA defines the device driver attach table as required
+ * to attach to os2ahci.add using DevHelp_AttachDD
+ */
+typedef struct
+{
+  PFN_AHCI_IDC_ENTRY  real_entry_point;   /* real mode entry point address */
+  unsigned short      real_ds;            /* real mode DS */
+
+  PFN_AHCI_IDC_ENTRY  prot_entry_point;   /* protected mode entry point address */
+  unsigned short      prot_ds;            /* protected mode DS */
+} AHCI_ATTACH_AREA;
+
+#pragma pack()
+
+/* -------------------------- function prototypes -------------------------- */
+
+/* ------------------------ global/static variables ------------------------ */
+
+
Index: /tags/2.02/src/include/devhelp.h
===================================================================
--- /tags/2.02/src/include/devhelp.h	(revision 189)
+++ /tags/2.02/src/include/devhelp.h	(revision 189)
@@ -0,0 +1,1145 @@
+/*
+ *  devhelp.h  OS/2 device help calls for DD 16-bit development.
+ *
+ *                          Open Watcom Project
+ *
+ * Copyright (c) 2004-2007 The Open Watcom Contributors. All Rights Reserved.
+ *
+ * This file was copied from the updated DDK.
+ *
+ * =========================================================================
+ */
+
+#ifndef DEVHELP_INCLUDED
+#define DEVHELP_INCLUDED
+
+#ifdef __WATCOMC__
+
+#ifndef OS2_INCLUDED
+#define INCL_NOPMAPI
+#include <os2.h>
+#endif
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+typedef ULONG   LIN;               // 32-Bit Linear Addess
+typedef ULONG   __far *PLIN;       // 16:16 Ptr to a 32-Bit Linear Addess
+
+typedef struct _PAGELIST {
+    ULONG    PhysAddr;
+    ULONG    Size;
+} PAGELIST, __near *NPPAGELIST, __far *PPAGELIST;
+
+typedef ULONG HSpinLock;
+
+// Device_Help: dev help offset from INIT
+extern PFN Device_Help;
+
+// 11 Jun 07 SHL change from far ptr to dword ptr
+// MKG - Due to Lars Erdmann comment:
+// Explanation: "CALL DWORD PTR" calls a far routine indirectly through the
+// function pointer saved in the address passed to the call (which is
+// correct since the devhelps use a function pointer) while "CALL FAR PTR"
+// calls a far routine directly, that is, the address passed to the call is
+// the far routine address (which is wrong for the devhelp calls).
+
+#define DEVHELP_CALL \
+   "call dword ptr [Device_Help]" \
+   "jc   error" \
+   "sub  ax,ax" \
+   "error:" \
+   value [ax]
+
+/* This service starts an ABIOS function for the Operating System Transfer
+ * Convention.
+ */
+USHORT DevHelp_ABIOSCall(USHORT Lid, NPBYTE ReqBlk, USHORT Entry_Type);
+#pragma aux DevHelp_ABIOSCall = \
+   "mov  dl,36h" \
+   DEVHELP_CALL \
+   parm caller [ax] [si] [dh] \
+   modify exact [ax dl];
+
+/* This service starts an ABIOS common entry point according to the Advanced
+ * BIOS Transfer Convention.
+ */
+USHORT DevHelp_ABIOSCommonEntry(NPBYTE ReqBlk, USHORT Entry_Type);
+#pragma aux DevHelp_ABIOSCommonEntry = \
+   "mov  dl,37h" \
+   DEVHELP_CALL \
+   parm caller [si] [dh] \
+   modify exact [ax dl];
+
+USHORT DevHelp_AcquireSpinLock(HSpinLock spl);
+#pragma aux DevHelp_AcquireSpinLock = \
+   "mov  ax,dx" \
+   "mov  dl,71h" \
+   DEVHELP_CALL \
+   parm caller nomemory [dx bx] \
+   modify nomemory exact [ax bx cx dx si di];
+
+USHORT DevHelp_AllocateCtxHook(NPFN HookHandler, PULONG HookHandle);
+#pragma aux DevHelp_AllocateCtxHook = \
+   "push bx" \
+   "movzx   eax,ax" \
+   "mov  ebx,-1" \
+   "mov  dl,63h" \
+   "call dword ptr [Device_Help]" \
+   "pop  bx" \
+   "jc   error" \
+   "mov  es:[bx],eax" \
+   "sub  ax,ax" \
+   "error:" \
+   value [ax] \
+   parm caller nomemory [ax] [es bx] \
+   modify exact [ax bx dl es];
+
+USHORT DevHelp_AllocGDTSelector(PSEL Selectors, USHORT Count);
+#pragma aux DevHelp_AllocGDTSelector = \
+   "mov  dl,2Dh" \
+   DEVHELP_CALL \
+   parm caller [es di] [cx] \
+   modify nomemory exact [ax dl];
+
+#define MEMTYPE_ABOVE_1M   0
+#define MEMTYPE_BELOW_1M   1
+
+USHORT DevHelp_AllocPhys(ULONG lSize, USHORT MemType, PULONG PhysAddr);
+#pragma aux DevHelp_AllocPhys = \
+   "xchg ax,bx" \
+   "mov  dl,18h" \
+   "call dword ptr [Device_Help]" \
+   "jc   error" \
+   "mov  es:[di],bx" \
+   "mov  es:[di+2],ax" \
+   "sub  ax,ax" \
+   "error:" \
+   value [ax] \
+   parm caller nomemory [ax bx] [dh] [es di] \
+   modify exact [ax bx dx];
+
+#define WAIT_NOT_ALLOWED 0
+#define WAIT_IS_ALLOWED  1
+
+USHORT DevHelp_AllocReqPacket(USHORT WaitFlag, PBYTE __far *ReqPktAddr);
+#pragma aux DevHelp_AllocReqPacket = \
+   "mov  dl,0Dh", \
+   "call dword ptr [Device_Help]" \
+   "jc   error" \
+   "push es" \
+   "push bx" \
+   "mov  bx,sp" \
+   "les  bx,ss:[bx]" \
+   "pop  es:[bx]" \
+   "pop  es:[bx+2]" \
+   "error:" \
+   "mov  ax,0" \
+   "sbb  ax,0" \
+   value [ax] \
+   parm caller [dh] [] \
+   modify exact [ax bx dl es];
+
+USHORT DevHelp_ArmCtxHook(ULONG HookData, ULONG HookHandle);
+#pragma aux DevHelp_ArmCtxHook = \
+   "mov  bx,sp" \
+   "mov  dl,65h", \
+   "mov  eax,ss:[bx]" \
+   "mov  ecx,-1" \
+   "mov  ebx,ss:[bx+4]" \
+   DEVHELP_CALL \
+   parm caller nomemory [] \
+   modify nomemory exact [ax bx cx dl];
+
+typedef struct _IDCTABLE {
+  USHORT   Reserved[3];
+  VOID     (__far *ProtIDCEntry) (VOID);
+  USHORT   ProtIDC_DS;
+} IDCTABLE, __near *NPIDCTABLE;
+
+USHORT DevHelp_AttachDD(NPSZ DDName, NPBYTE DDTable);
+#pragma aux DevHelp_AttachDD = \
+   "mov  dl,2Ah" \
+   "call dword ptr [Device_Help]" \
+   "mov  ax,0" \
+   "sbb  ax,0" \
+   value [ax] \
+   parm caller [bx] [di] \
+   modify exact [ax dl];
+
+USHORT DevHelp_Beep(USHORT Frequency, USHORT DurationMS);
+#pragma aux DevHelp_Beep = \
+   "mov  dl,52h" \
+   DEVHELP_CALL \
+   parm caller nomemory [bx] [cx] \
+   modify nomemory exact [ax dx];
+
+USHORT DevHelp_CloseEventSem(ULONG hEvent);
+#pragma aux DevHelp_CloseEventSem = \
+   "movzx   esp,sp" \
+   "mov  dl,68h" \
+   "mov  eax,[esp]" \
+   DEVHELP_CALL \
+   parm caller nomemory [] \
+   modify nomemory exact [ax dl];
+
+USHORT DevHelp_CreateSpinLock(HSpinLock FAR * pSpl);
+#pragma aux DevHelp_CreateSpinLock = \
+   "mov  ax,dx" \
+   "mov  dl,79h" \
+   DEVHELP_CALL \
+   parm caller [dx bx] \
+   modify exact [ax bx cx dx si di];
+
+USHORT DevHelp_DeRegister(USHORT MonitorPID, USHORT MonitorHandle, PUSHORT MonitorsLeft);
+#pragma aux DevHelp_DeRegister = \
+   "mov  dl,21h" \
+   "call dword ptr [Device_Help]" \
+   "jc   error" \
+   "mov  es:[di],ax" \
+   "sub  ax,ax" \
+   "error:" \
+   value [ax] \
+   parm caller nomemory [bx] [ax] [es di] \
+   modify exact [ax dl];
+
+USHORT DevHelp_DevDone(PBYTE ReqPktAddr);
+#pragma aux DevHelp_DevDone = \
+   "mov  dl,1" \
+   DEVHELP_CALL \
+   parm caller [es bx] \
+   modify exact [ax dl];
+
+#define DYNAPI_CALLGATE16       0x0001   /* 16:16 CallGate     */
+#define DYNAPI_CALLGATE32       0x0000   /*  0:32 CallGate     */
+
+#define DYNAPI_ROUTINE16        0x0002   /* 16:16 Routine Addr */
+#define DYNAPI_ROUTINE32        0x0000   /*  0:32 Routine Addr */
+
+USHORT DevHelp_DynamicAPI(PVOID  RoutineAddress, USHORT ParmCount, USHORT Flags, PSEL CallGateSel);
+#pragma aux DevHelp_DynamicAPI = \
+   "mov  dl,6Ch" \
+   "xchg ax,bx" \
+   "call dword ptr [Device_Help]" \
+   "jc   error" \
+   "mov  es:[si],di" \
+   "sub  ax,ax" \
+   "error:" \
+   value [ax] \
+   parm caller [ax bx] [cx] [dh] [es si] \
+   modify exact [ax bx di dl];
+
+USHORT DevHelp_EOI(USHORT IRQLevel);
+#pragma aux DevHelp_EOI = \
+   "mov  dl,31h" \
+   "call dword ptr [Device_Help]" \
+   "sub  ax,ax" \
+   value [ax] \
+   parm caller nomemory [al] \
+   modify nomemory exact [ax dl];
+
+USHORT DevHelp_FreeCtxHook(ULONG HookHandle);
+#pragma aux DevHelp_FreeCtxHook = \
+   "movzx   esp,sp" \
+   "mov  dl,64h", \
+   "mov  eax,[esp]" \
+   DEVHELP_CALL \
+   parm caller nomemory [] \
+   modify nomemory exact [ax dl];
+
+USHORT DevHelp_FreeGDTSelector(SEL Selector);
+#pragma aux DevHelp_FreeGDTSelector = \
+   "mov  dl,53h" \
+   DEVHELP_CALL \
+   parm caller nomemory [ax] \
+   modify nomemory exact [ax dl];
+
+USHORT DevHelp_FreeLIDEntry(USHORT LIDNumber);
+#pragma aux DevHelp_FreeLIDEntry = \
+   "mov  dl,35h" \
+   DEVHELP_CALL \
+   parm caller nomemory [ax] \
+   modify nomemory exact [ax dl];
+
+USHORT DevHelp_FreeSpinLock(HSpinLock spl);
+#pragma aux DevHelp_FreeSpinLock = \
+   "mov  ax,dx" \
+   "mov  dl,7ah" \
+   DEVHELP_CALL \
+   parm caller nomemory [dx bx] \
+   modify nomemory exact [ax bx cx dx si di];
+
+USHORT DevHelp_FreePhys(ULONG PhysAddr);
+#pragma aux DevHelp_FreePhys = \
+   "xchg ax,bx" \
+   "mov  dl,19h" \
+   "call dword ptr [Device_Help]" \
+   "mov  ax,0" \
+   "sbb  ax,0" \
+   value [ax] \
+   parm caller nomemory [ax bx] \
+   modify exact [ax bx dl];
+
+USHORT DevHelp_FreeReqPacket(PBYTE ReqPktAddr);
+#pragma aux DevHelp_FreeReqPacket = \
+   "mov  dl,0Eh", \
+   "call dword ptr [Device_Help]" \
+   "sub  ax,ax" \
+   value [ax] \
+   parm caller [es bx] \
+   modify exact [ax dl];
+
+typedef struct _SELDESCINFO {
+  UCHAR    Type;
+  UCHAR    Granularity;
+  LIN      BaseAddr;
+  ULONG    Limit;
+} SELDESCINFO, __far *PSELDESCINFO;
+
+typedef struct _GATEDESCINFO {
+  UCHAR    Type;
+  UCHAR    ParmCount;
+  SEL      Selector;
+  USHORT   Reserved_1;
+  ULONG    Offset;
+} GATEDESCINFO, __far *PGATEDESCINFO;
+
+USHORT DevHelp_GetDescInfo(SEL Selector, PBYTE SelInfo);
+#pragma aux DevHelp_GetDescInfo = \
+   "mov  dl,5Dh" \
+   "call dword ptr [Device_Help]" \
+   "jc   error" \
+   "mov  es:[bx],ax" \
+   "mov  es:[bx+2],ecx" \
+   "mov  es:[bx+6],edx" \
+   "sub  ax,ax" \
+   "error:" \
+   value [ax] \
+   parm caller nomemory [ax] [es bx] \
+   modify exact [ax cx dx];
+
+USHORT DevHelp_GetDeviceBlock(USHORT Lid, PPVOID DeviceBlockPtr);
+#pragma aux DevHelp_GetDeviceBlock = \
+   "mov  dl,38h" \
+   "call dword ptr [Device_Help]" \
+   "jc   error" \
+   "mov  es:[si],dx" \
+   "mov  es:[si+2],cx" \
+   "sub  ax,ax" \
+   "error:" \
+   value [ax] \
+   parm caller nomemory [ax] [es si] \
+   modify exact [ax bx cx dx];
+
+#define DHGETDOSV_SYSINFOSEG            1
+#define DHGETDOSV_LOCINFOSEG            2
+#define DHGETDOSV_VECTORSDF             4
+#define DHGETDOSV_VECTORREBOOT          5
+#define DHGETDOSV_YIELDFLAG             7                            /*@V76282*/
+#define DHGETDOSV_TCYIELDFLAG           8                            /*@V76282*/
+#define DHGETDOSV_DOSTABLES             9  // Undocumented by IBM, gets the dostables.
+#define DHGETDOSV_DOSCODEPAGE           11                           /*@V76282*/
+#define DHGETDOSV_INTERRUPTLEV          13
+#define DHGETDOSV_DEVICECLASSTABLE      14                           /*@V76282*/
+#define DHGETDOSV_DMQSSELECTOR          15                           /*@V76282*/
+#define DHGETDOSV_APMINFO               16                           /*@V76282*/
+
+USHORT DevHelp_GetDOSVar(USHORT VarNumber, USHORT VarMember, PPVOID KernalVar);
+#pragma aux DevHelp_GetDOSVar = \
+   "mov  dl,24h" \
+   "call dword ptr [Device_Help]" \
+   "mov  es:[di],bx" \
+   "mov  es:[di+2],ax" \
+   "sub  ax,ax" \
+   value [ax] \
+   parm caller nomemory [al] [cx] [es di] \
+   modify exact [ax bx dl];
+
+USHORT DevHelp_GetLIDEntry(USHORT DeviceType, SHORT LIDIndex, USHORT Type, PUSHORT LID);
+#pragma aux DevHelp_GetLIDEntry = \
+   "mov  dl,34h" \
+   "call dword ptr [Device_Help]" \
+   "jc   error" \
+   "mov  es:[di],ax" \
+   "sub  ax,ax" \
+   "error:" \
+   value [ax] \
+   parm caller nomemory [al] [bl] [dh] [es di] \
+   modify exact [ax dl];
+
+USHORT DevHelp_InternalError(PSZ MsgText, USHORT MsgLength );
+#pragma aux DevHelp_InternalError aborts = \
+   "push ds" \
+   "push es" \
+   "pop  ds" \
+   "pop  es" \
+   "mov  dl,2Bh" \
+   "jmp dword ptr es:[Device_Help]" \
+   parm [es si] [di] \
+   modify nomemory exact [];
+
+USHORT DevHelp_LinToGDTSelector(SEL Selector, LIN LinearAddr, ULONG Size);
+#pragma aux DevHelp_LinToGDTSelector = \
+   "mov  bx,sp" \
+   "mov  dl,5Ch" \
+   "mov  ecx,ss:[bx+4]" \
+   "mov  ebx,ss:[bx]" \
+   "call dword ptr [Device_Help]" \
+   "jc   error" \
+   "sub  ax,ax" \
+   "error:" \
+   value [ax] \
+   parm caller nomemory [ax] [] \
+   modify nomemory exact [ax bx cx dl];
+
+#define LOCKTYPE_SHORT_ANYMEM    0x00
+#define LOCKTYPE_LONG_ANYMEM     0x01
+#define LOCKTYPE_LONG_HIGHMEM    0x03
+#define LOCKTYPE_SHORT_VERIFY    0x04
+
+USHORT DevHelp_Lock(SEL Segment, USHORT LockType, USHORT WaitFlag, PULONG LockHandle);
+#pragma aux DevHelp_Lock = \
+   "mov  dl,13h" \
+   "call dword ptr [Device_Help]" \
+   "jc   error" \
+   "mov  es:[di],bx" \
+   "mov  es:[di+2],ax" \
+   "sub  ax,ax" \
+   "error:" \
+   value [ax] \
+   parm caller [ax] [bh] [bl] [es di] \
+   modify exact [ax dl];
+
+USHORT DevHelp_MonFlush(USHORT MonitorHandle);
+#pragma aux DevHelp_MonFlush = \
+   "mov  dl,23h" \
+   DEVHELP_CALL \
+   parm caller [ax] \
+   modify exact [ax dl];
+
+USHORT DevHelp_MonitorCreate(USHORT MonitorHandle, PBYTE FinalBuffer, NPFN NotifyRtn, PUSHORT MonitorChainHandle);
+#pragma aux DevHelp_MonitorCreate = \
+   "mov  dl,1Fh" \
+   "call dword ptr [Device_Help]" \
+   "jc   error" \
+   "mov  si,[esp]" \
+   "mov  es,[esp+2]" \
+   "mov  es:[si],ax" \
+   "sub  ax,ax" \
+   "error:" \
+   value [ax] \
+   parm caller [ax] [es si] [di] [] \
+   modify exact [ax dl es si];
+
+/* DevHelp_MonWrite has one change compared to the original specification.
+   The DataRecord parameter was originally a far (16:16) pointer.  It has
+   been changed to a near pointer because DS must point to the default data
+   segment and therefore the DataRecord parameter must be near.  This
+   allows the compiler to catch the error.
+*/
+
+USHORT DevHelp_MonWrite(USHORT MonitorHandle, NPBYTE DataRecord, USHORT Count, ULONG TimeStampMS, USHORT WaitFlag);
+#pragma aux DevHelp_MonWrite = \
+   "mov  dl,22h" \
+   DEVHELP_CALL \
+   parm caller [ax] [si] [cx] [di bx] [dh] \
+   modify exact [ax dl];
+
+USHORT DevHelp_OpenEventSem(ULONG hEvent);
+#pragma aux DevHelp_OpenEventSem = \
+   "mov  eax,[esp]" \
+   "mov  dl,67h" \
+   DEVHELP_CALL \
+   parm caller nomemory [] \
+   modify nomemory exact [ax dl];
+
+#define GDTSEL_R3CODE           0x0000                               /*@V76282*/
+#define GDTSEL_R3DATA           0x0001                               /*@V76282*/
+#define GDTSEL_R2CODE           0x0003                               /*@V76282*/
+#define GDTSEL_R2DATA           0x0004                               /*@V76282*/
+#define GDTSEL_R0CODE           0x0005                               /*@V76282*/
+#define GDTSEL_R0DATA           0x0006                               /*@V76282*/
+
+/* GDTSEL_ADDR32 may be OR'd with above defines */                   /*@V76282*/
+#define GDTSEL_ADDR32           0x0080                               /*@V76282*/
+
+USHORT DevHelp_PageListToGDTSelector(SEL Selector, ULONG Size, USHORT Access, LIN pPageList);
+#pragma aux DevHelp_PageListToGDTSelector = \
+   "mov  ecx,[esp]" \
+   "mov  dh,[esp+4]" \
+   "mov  edi,[esp+6]" \
+   "mov  dl,60h" \
+   "call dword ptr [Device_Help]" \
+   "jc   error" \
+   "sub  ax,ax" \
+   "error:" \
+   value [ax] \
+   parm caller [] \
+   modify nomemory exact [ax cx dx di];
+
+USHORT DevHelp_PageListToLin(ULONG Size, LIN pPageList, PLIN LinearAddr);
+#pragma aux DevHelp_PageListToLin = \
+   "mov  ecx,[esp]" \
+   "mov  edi,[esp+4]" \
+   "mov  dl,5Fh" \
+   "call dword ptr [Device_Help]" \
+   "jc   error" \
+   "les  di,[esp+8]" \
+   "mov  es:[di],eax" \
+   "sub  ax,ax" \
+   "error:" \
+   value [ax] \
+   parm caller [] \
+   modify exact [ax cx dx di];
+
+/* NOTE: After the DevHelp call, SI contains the modified selector. However,
+   the C interface has no provisions for returning this value to the caller.
+   You can, however, use the _SI() inline function defined in include.h.
+*/
+
+USHORT DevHelp_PhysToGDTSel(ULONG PhysAddr, ULONG Count, SEL Selector, UCHAR Access);
+#pragma aux DevHelp_PhysToGDTSel = \
+   "push bp" \
+   "mov  dl,54h" \
+   "mov  bp,sp" \
+   "mov  eax,[bp+2]" \
+   "mov  ecx,[bp+6]" \
+   "mov  si,[bp+10]" \
+   "mov  dh,[bp+12]" \
+   "call dword ptr [Device_Help]" \
+   "jc   error" \
+   "sub  ax,ax" \
+   "error:" \
+   "pop  bp" \
+   value [ax] \
+   parm caller nomemory [] \
+   modify nomemory exact [ax cx dx si];
+
+USHORT DevHelp_PhysToGDTSelector(ULONG PhysAddr, USHORT Count, SEL Selector);
+#pragma aux DevHelp_PhysToGDTSelector = \
+   "xchg ax,bx" \
+   "mov  dl,2Eh" \
+   DEVHELP_CALL \
+   parm nomemory [ax bx] [cx] [si] \
+   modify nomemory exact [ax bx dl];
+
+#define SELTYPE_R3CODE  0
+#define SELTYPE_R3DATA  1
+#define SELTYPE_FREE    2
+#define SELTYPE_R2CODE  3
+#define SELTYPE_R2DATA  4
+#define SELTYPE_R3VIDEO 5
+
+USHORT DevHelp_PhysToUVirt(ULONG PhysAddr, USHORT Length, USHORT flags, USHORT TagType, PVOID SelOffset);
+#pragma aux DevHelp_PhysToUVirt = \
+   "xchg ax,bx" \
+   "mov  dl,17h" \
+   "call dword ptr [Device_Help]" \
+   "jc   error" \
+   "push es" \
+   "push bx" \
+   "mov  bx,sp" \
+   "les  bx,ss:[bx+4]" \
+   "pop  es:[bx]" \
+   "pop  es:[bx+2]" \
+   "sub  ax,ax" \
+   "error:" \
+   value [ax] \
+   parm caller nomemory [bx ax] [cx] [dh] [si] [] \
+   modify exact [ax bx dl es];
+
+USHORT DevHelp_PhysToVirt(ULONG PhysAddr, USHORT usLength, PVOID SelOffset, PUSHORT ModeFlag);
+#pragma aux DevHelp_PhysToVirt = \
+   "xchg ax,bx" \
+   "mov  dx,15h" \
+   "push ds" \
+   "call dword ptr [Device_Help]" \
+   "jc   error" \
+   "sub  ax,ax" \
+   "mov  es:[di],si" \
+   "mov  es:[di+2],ds" \
+   "error:" \
+   "pop  ds" \
+   value [ax] \
+   parm caller nomemory [bx ax] [cx] [es di] [] \
+   modify exact [ax bx dx si];
+
+USHORT DevHelp_PostEventSem(ULONG hEvent);
+#pragma aux DevHelp_PostEventSem = \
+   "mov  eax,[esp]" \
+   "mov  dl,69h" \
+   DEVHELP_CALL \
+   parm caller nomemory [] \
+   modify nomemory exact [ax dl];
+
+#define WAIT_IS_INTERRUPTABLE      0
+#define WAIT_IS_NOT_INTERRUPTABLE  1
+
+#define WAIT_INTERRUPTED           0x2
+#define WAIT_TIMED_OUT             0x1
+
+USHORT DevHelp_ProcBlock(ULONG EventId, ULONG WaitTime, USHORT IntWaitFlag);
+#pragma aux DevHelp_ProcBlock = \
+   "mov  dl,4" \
+   "xchg ax,bx" \
+   "xchg cx,di" \
+   "call dword ptr [Device_Help]" \
+   "setnz al"                  \
+   "setc bl"                   \
+   "inc al"                    \
+   "neg bl"                    \
+   "and al,bl"                 \
+   "xor ah,ah"                 \
+   value [ax] \
+   parm caller nomemory [ax bx] [di cx] [dh] \
+   modify nomemory exact [ax bx cx dl di];
+
+USHORT DevHelp_ProcRun(ULONG EventId, PUSHORT AwakeCount);
+#pragma aux DevHelp_ProcRun = \
+   "mov  dl,5" \
+   "xchg ax,bx" \
+   "call dword ptr [Device_Help]" \
+   "mov  es:[si],ax" \
+   "sub  ax,ax" \
+   value [ax] \
+   parm caller nomemory [ax bx] [es si] \
+   modify exact [ax bx dl];
+
+USHORT DevHelp_PullParticular(NPBYTE Queue, PBYTE ReqPktAddr);
+#pragma aux DevHelp_PullParticular= \
+   "mov  dl,0Bh" \
+   DEVHELP_CALL \
+   parm [si] [es bx] \
+   modify exact [ax dl];
+
+USHORT DevHelp_PullRequest(NPBYTE Queue, PBYTE __far *ReqPktAddr);
+#pragma aux DevHelp_PullRequest = \
+   "push es" \
+   "mov  dl,0Ah" \
+   "call dword ptr [Device_Help]" \
+   "jc   error" \
+   "movzx   esp,sp" \
+   "push es" \
+   "push bx" \
+   "mov  bx,sp" \
+   "les  bx,[esp]" \
+   "pop  es:[bx]" \
+   "pop  es:[bx+2]" \
+   "sub  ax,ax" \
+   "error:" \
+   value [ax] \
+   parm [si] [] \
+   modify exact [ax bx dl es];
+
+USHORT DevHelp_PushRequest(NPBYTE Queue, PBYTE ReqPktddr);
+#pragma aux DevHelp_PushRequest = \
+   "mov  dl,09h" \
+   "call dword ptr [Device_Help]" \
+   "sub  ax,ax" \
+   value [ax] \
+   parm [si] [es bx] \
+   modify exact [ax dl];
+
+USHORT DevHelp_QueueFlush(NPBYTE Queue);
+#pragma aux DevHelp_QueueFlush = \
+   "mov  dl,10h" \
+   "call dword ptr [Device_Help]" \
+   "sub  ax,ax" \
+   value [ax] \
+   parm [bx] \
+   modify exact [ax dl];
+
+typedef struct _QUEUEHDR  {
+  USHORT   QSize;
+  USHORT   QChrOut;
+  USHORT   QCount;
+  BYTE     Queue[1];
+} QUEUEHDR, __far *PQUEUEHDR;
+
+USHORT DevHelp_QueueInit(NPBYTE Queue);
+#pragma aux DevHelp_QueueInit = \
+   "mov  dl,0Fh" \
+   "call dword ptr [Device_Help]" \
+   "sub  ax,ax" \
+   value [ax] \
+   parm [bx] \
+   modify exact [ax dl];
+
+USHORT DevHelp_QueueRead(NPBYTE Queue, PBYTE Char);
+#pragma aux DevHelp_QueueRead = \
+   "mov  dl,12h" \
+   "call dword ptr [Device_Help]" \
+   "jc   error" \
+   "mov  es:[di],al" \
+   "sub  ax,ax" \
+   "error:" \
+   value [ax] \
+   parm [bx] [es di] \
+   modify exact [ax dl];
+
+USHORT DevHelp_QueueWrite(NPBYTE Queue, UCHAR Char);
+#pragma aux DevHelp_QueueWrite = \
+   "mov  dl,11h" \
+   DEVHELP_CALL \
+   parm [bx] [al] \
+   modify exact [ax dl];
+
+USHORT DevHelp_RAS(USHORT Major, USHORT Minor, USHORT Size, PBYTE Data);
+#pragma aux DevHelp_RAS = \
+   "push ds" \
+   "push es" \
+   "pop  ds" \
+   "pop  es" \
+   "mov  dl,28h" \
+   "call dword ptr es:[Device_Help]" \
+   "push es" \
+   "pop  ds" \
+   "mov  ax,0" \
+   "sbb  ax,0" \
+   value [ax] \
+   parm [ax] [cx] [bx] [es si] \
+   modify nomemory exact [ax dl es];
+
+#define CHAIN_AT_TOP    0
+#define CHAIN_AT_BOTTOM 1
+
+USHORT DevHelp_Register(USHORT MonitorHandle, USHORT MonitorPID, PBYTE InputBuffer, NPBYTE OutputBuffer, USHORT ChainFlag);
+#pragma aux DevHelp_Register = \
+   "mov  dl,20h" \
+   DEVHELP_CALL \
+   parm caller nomemory [ax] [cx] [es si] [di] [dh] \
+   modify nomemory exact [ax dl];
+
+USHORT DevHelp_RegisterBeep(PFN BeepHandler);
+#pragma aux DevHelp_RegisterBeep = \
+   "mov  dl,51h" \
+   "call dword ptr [Device_Help]" \
+   "sub  ax,ax" \
+   value [ax] \
+   parm caller nomemory [cx di] \
+   modify nomemory exact [ax dl];
+
+#define DEVICECLASS_ADDDM       1                                    /*@V74979*/
+#define DEVICECLASS_MOUSE       2                                    /*@V74979*/
+
+USHORT DevHelp_RegisterDeviceClass(NPSZ DeviceString, PFN DriverEP, USHORT DeviceFlags, USHORT DeviceClass, PUSHORT DeviceHandle);
+#pragma aux DevHelp_RegisterDeviceClass = \
+   "mov  dl,43h" \
+   "xchg ax,bx" \
+   "call dword ptr [Device_Help]" \
+   "jc   error" \
+   "les  bx,[esp]" \
+   "mov  es:[bx],ax" \
+   "sub  ax,ax" \
+   "error:" \
+   value [ax] \
+   parm [si] [ax bx] [di] [cx] [] \
+   modify exact [ax bx dl es];
+
+USHORT DevHelp_RegisterPDD(NPSZ PhysDevName, PFN HandlerRoutine);
+#pragma aux DevHelp_RegisterPDD = \
+   "mov  dl,50h" \
+   DEVHELP_CALL \
+   parm caller [si] [es di] \
+   modify nomemory exact [ax dl];
+
+typedef struct _STACKUSAGEDATA {
+   USHORT Size;
+   USHORT Flags;
+   USHORT IRQLevel;
+   USHORT CLIStack;
+   USHORT STIStack;
+   USHORT EOIStack;
+   USHORT NestingLevel;
+} STACKUSAGEDATA;
+
+USHORT DevHelp_RegisterStackUsage(PVOID StackUsageData);
+#pragma aux DevHelp_RegisterStackUsage = \
+   "mov  dl,3Ah" \
+   "call dword ptr [Device_Help]" \
+   "mov  ax,0" \
+   "sbb  ax,0" \
+   value [ax] \
+   parm caller [bx] \
+   modify nomemory exact [ax dl];
+
+USHORT DevHelp_RegisterTmrDD(NPFN TimerEntry, PULONG TmrRollover, PULONG Tmr);
+#pragma aux DevHelp_RegisterTmrDD = \
+   "mov  dl,61h" \
+   "call dword ptr [Device_Help]" \
+   "mov  ax,bx" \
+   "les  bx,[esp]" \
+   "mov  es:[bx],ax" \
+   "mov  es:[bx+2],di" \
+   "les  bx,[esp+4]" \
+   "mov  es:[bx],cx" \
+   "mov  es:[bx+2],di" \
+   "sub  ax,ax" \
+   value [ax] \
+   parm caller nomemory [di] [] \
+   modify exact [ax bx cx di dl es];
+
+USHORT DevHelp_ReleaseSpinLock(HSpinLock spl);
+#pragma aux DevHelp_ReleaseSpinLock = \
+   "mov  ax,dx" \
+   "mov  dl,72h" \
+   DEVHELP_CALL \
+   parm caller nomemory [dx bx] \
+   modify nomemory exact [ax bx cx dx si di];
+
+USHORT DevHelp_ResetEventSem(ULONG hEvent, LIN pNumPosts);
+#pragma aux DevHelp_ResetEventSem = \
+   "mov  eax,[esp]" \
+   "mov  edi,[esp+4]" \
+   "mov  dl,6Ah" \
+   DEVHELP_CALL \
+   parm caller nomemory [] \
+   modify exact [ax di dl];
+
+USHORT DevHelp_ResetTimer(NPFN TimerHandler);
+#pragma aux DevHelp_ResetTimer = \
+   "mov  dl,1Eh" \
+   DEVHELP_CALL \
+   parm caller nomemory [ax] \
+   modify nomemory exact [ax dl];
+
+typedef struct _MSGTABLE {
+  USHORT   MsgId;                       /* Message Id #                  */
+  USHORT   cMsgStrings;                 /* # of (%) substitution strings */
+  PSZ      MsgStrings[1];               /* Substitution string pointers  */
+} MSGTABLE, __near *NPMSGTABLE;
+
+USHORT DevHelp_Save_Message(NPBYTE MsgTable);
+#pragma aux DevHelp_Save_Message = \
+   "sub  bx,bx" \
+   "mov  dl,3Dh" \
+   DEVHELP_CALL \
+   parm caller [si] \
+   modify nomemory exact [ax bx dl];
+
+USHORT DevHelp_SchedClock(PFN NEAR *SchedRoutineAddr);
+#pragma aux DevHelp_SchedClock = \
+   "mov  dl,0h" \
+   "call dword ptr [Device_Help]" \
+   "sub  ax,ax" \
+   value [ax] \
+   parm caller [ax] \
+   modify nomemory exact [ax dl];
+
+USHORT DevHelp_SemClear(ULONG SemHandle);
+#pragma aux DevHelp_SemClear = \
+   "xchg ax,bx" \
+   "mov  dl,7h" \
+   DEVHELP_CALL \
+   parm nomemory [ax bx] \
+   modify nomemory exact [ax bx dl];
+
+#define SEMUSEFLAG_IN_USE       0
+#define SEMUSEFLAG_NOT_IN_USE   1
+
+USHORT DevHelp_SemHandle(ULONG SemKey, USHORT SemUseFlag, PULONG SemHandle);
+#pragma aux DevHelp_SemHandle = \
+   "xchg ax,bx" \
+   "mov  dl,8h" \
+   "call dword ptr [Device_Help]" \
+   "jc   error" \
+   "mov  es:[si],bx" \
+   "mov  es:[si+2],ax" \
+   "sub  ax,ax" \
+   "error:" \
+   value [ax] \
+   parm nomemory [ax bx] [dh] [es si] \
+   modify exact [ax bx dl];
+
+USHORT DevHelp_SemRequest(ULONG SemHandle, ULONG SemTimeout);
+#pragma aux DevHelp_SemRequest = \
+   "xchg ax,bx" \
+   "xchg di,cx" \
+   "mov  dl,06h" \
+   DEVHELP_CALL \
+   parm nomemory [ax bx] [cx di] \
+   modify nomemory exact [ax bx cx di dl];
+
+#define EVENT_MOUSEHOTKEY   0
+#define EVENT_CTRLBREAK     1
+#define EVENT_CTRLC         2
+#define EVENT_CTRLNUMLOCK   3
+#define EVENT_CTRLPRTSC     4
+#define EVENT_SHIFTPRTSC    5
+#define EVENT_KBDHOTKEY     6
+#define EVENT_KBDREBOOT     7
+
+USHORT DevHelp_SendEvent(USHORT EventType, USHORT Parm);
+#pragma aux DevHelp_SendEvent = \
+   "mov  dl,25h" \
+   "call dword ptr [Device_Help]" \
+   "mov  ax,0" \
+   "sbb  ax,0" \
+   value [ax] \
+   parm nomemory [ah] [bx] \
+   modify nomemory exact [ax dl];
+
+USHORT DevHelp_SetIRQ(NPFN IRQHandler, USHORT IRQLevel, USHORT SharedFlag);
+#pragma aux DevHelp_SetIRQ = \
+   "mov  dl,1Bh" \
+   DEVHELP_CALL \
+   parm caller nomemory [ax] [bx] [dh] \
+   modify nomemory exact [ax dl];
+
+USHORT DevHelp_SetTimer(NPFN TimerHandler);
+#pragma aux DevHelp_SetTimer = \
+   "mov  dl,1Dh" \
+   DEVHELP_CALL \
+   parm caller nomemory [ax] \
+   modify nomemory exact [ax dl];
+
+USHORT DevHelp_SortRequest(NPBYTE Queue, PBYTE ReqPktAddr);
+#pragma aux DevHelp_SortRequest = \
+   "mov  dl,0Ch" \
+   "call dword ptr [Device_Help]" \
+   "sub  ax,ax" \
+   value [ax] \
+   parm [si] [es bx] \
+   modify exact [ax dl];
+
+USHORT DevHelp_TCYield(void);
+#pragma aux DevHelp_TCYield = \
+   "mov  dl,3" \
+   "call dword ptr [Device_Help]" \
+   "sub  ax,ax" \
+   value [ax] \
+   parm caller nomemory [] \
+   modify nomemory exact [ax dl];
+
+USHORT DevHelp_TickCount(NPFN TimerHandler, USHORT TickCount);
+#pragma aux DevHelp_TickCount = \
+   "mov  dl,33h" \
+   DEVHELP_CALL \
+   parm caller nomemory [ax] [bx] \
+   modify nomemory exact [ax dl];
+
+USHORT DevHelp_UnLock(ULONG LockHandle);
+#pragma aux DevHelp_UnLock = \
+   "xchg ax,bx" \
+   "mov  dl,14h" \
+   DEVHELP_CALL \
+   parm nomemory [ax bx] \
+   modify nomemory exact [ax bx dl];
+
+USHORT DevHelp_UnSetIRQ(USHORT IRQLevel);
+#pragma aux DevHelp_UnSetIRQ = \
+   "mov  dl,1Ch" \
+   DEVHELP_CALL \
+   parm caller nomemory [bx] \
+   modify nomemory exact [ax dl];
+
+#define VERIFY_READONLY    0
+#define VERIFY_READWRITE   1
+
+USHORT DevHelp_VerifyAccess(SEL MemSelector, USHORT Length, USHORT MemOffset, UCHAR AccessFlag);
+#pragma aux DevHelp_VerifyAccess = \
+   "mov  dl,27h" \
+   DEVHELP_CALL \
+   parm caller nomemory [ax] [cx] [di] [dh] \
+   modify nomemory exact [ax dl];
+
+#define VIDEO_PAUSE_OFF            0
+#define VIDEO_PAUSE_ON             1
+
+USHORT DevHelp_VideoPause(USHORT OnOff);
+#pragma aux DevHelp_VideoPause = \
+   "mov  dl,3Ch" \
+   "call dword ptr [Device_Help]" \
+   "mov  ax,0" \
+   "sbb  ax,0" \
+   value [ax] \
+   parm nomemory [al] \
+   modify nomemory exact [ax dl];
+
+USHORT DevHelp_VirtToLin(SEL Selector, ULONG Offset, PLIN LinearAddr);
+#pragma aux DevHelp_VirtToLin = \
+   "movzx   esp,sp" \
+   "mov  esi,[esp]" \
+   "mov  dl,5bh" \
+   "call dword ptr [Device_Help]" \
+   "jc   error" \
+   "les  bx,[esp+4]" \
+   "mov  es:[bx],eax" \
+   "sub  ax,ax" \
+   "error:" \
+   value [ax] \
+   parm caller nomemory [ax] [] \
+   modify exact [ax si es bx dl];
+
+USHORT DevHelp_VirtToPhys(PVOID SelOffset, PULONG PhysAddr);
+#pragma aux DevHelp_VirtToPhys = \
+   "push ds" \
+   "mov  dl,16h" \
+   "push es" \
+   "mov  si,ds" \
+   "mov  es,si" \
+   "mov  ds,bx" \
+   "mov  si,ax" \
+   "call dword ptr es:[Device_Help]" \
+   "pop  es" \
+   "mov  es:[di],bx" \
+   "mov  es:[di+2],ax" \
+   "pop  ds" \
+   "sub  ax,ax" \
+   value [ax] \
+   parm caller nomemory [ax bx] [es di] \
+   modify exact [ax bx dl es si];
+
+#define VMDHA_16M               0x0001                               /*@V76282*/
+#define VMDHA_FIXED             0x0002                               /*@V76282*/
+#define VMDHA_SWAP              0x0004                               /*@V76282*/
+#define VMDHA_CONTIG            0x0008                               /*@V76282*/
+#define VMDHA_PHYS              0x0010                               /*@V76282*/
+#define VMDHA_PROCESS           0x0020                               /*@V76282*/
+#define VMDHA_SGSCONT           0x0040                               /*@V76282*/
+#define VMDHA_RESERVE           0x0100                               /*@V76282*/
+#define VMDHA_USEHIGHMEM        0x0800                               /*@V76282*/
+
+USHORT DevHelp_VMAlloc(ULONG Flags, ULONG Size, ULONG PhysAddr, PLIN LinearAddr, PPVOID SelOffset);
+#pragma aux DevHelp_VMAlloc = \
+   "movzx   esp,sp" \
+   "mov  eax,[esp]" \
+   "mov  ecx,[esp+4]" \
+   "mov  edi,[esp+8]" \
+   "mov  dl,57h" \
+   "call dword ptr [Device_Help]" \
+   "jc   error" \
+   "les  di,[esp+12]" \
+   "mov  es:[di],eax" \
+   "les  di,[esp+16]" \
+   "mov  es:[di],ecx" \
+   "sub  ax,ax" \
+   "error:" \
+   value [ax] \
+   parm caller nomemory [] \
+   modify exact [ax cx di dl es];
+
+USHORT DevHelp_VMFree(LIN LinearAddr);
+#pragma aux DevHelp_VMFree = \
+   "movzx   esp,sp" \
+   "mov  dl,58h" \
+   "mov  eax,[esp]" \
+   DEVHELP_CALL \
+   parm caller nomemory [] \
+   modify nomemory exact [ax dl];
+
+#define VMDHGP_WRITE            0x0001                               /*@V76282*/
+#define VMDHGP_SELMAP           0x0002                               /*@V76282*/
+#define VMDHGP_SGSCONTROL       0x0004                               /*@V76282*/
+#define VMDHGP_4MEG             0x0008                               /*@V76282*/
+
+USHORT DevHelp_VMGlobalToProcess(ULONG Flags, LIN LinearAddr, ULONG Length, PLIN ProcessLinearAddr);
+#pragma aux DevHelp_VMGlobalToProcess = \
+   "mov  dl,5Ah" \
+   "mov  eax,[esp]" \
+   "mov  ebx,[esp+4]" \
+   "mov  ecx,[esp+8]" \
+   "call dword ptr [Device_Help]" \
+   "jc   error" \
+   "les  bx,[esp+12]" \
+   "mov  es:[bx],eax" \
+   "sub  ax,ax" \
+   "error:" \
+   value [ax] \
+   parm caller nomemory [] \
+   modify exact [ax bx cx dl es];
+
+#define VMDHL_NOBLOCK           0x0001
+#define VMDHL_CONTIGUOUS        0x0002
+#define VMDHL_16M               0x0004
+#define VMDHL_WRITE             0x0008
+#define VMDHL_LONG              0x0010
+#define VMDHL_VERIFY            0x0020
+
+USHORT DevHelp_VMLock(ULONG Flags, LIN LinearAddr, ULONG Length, LIN pPagelist, LIN pLockHandle, PULONG PageListCount);
+#pragma aux DevHelp_VMLock = \
+   "mov  dl,55h" \
+   "mov  eax,[esp]" \
+   "mov  ebx,[esp+4]" \
+   "mov  ecx,[esp+8]" \
+   "mov  edi,[esp+12]" \
+   "mov  esi,[esp+16]" \
+   "call dword ptr [Device_Help]" \
+   "jc   error" \
+   "les  bx,[esp+20]" \
+   "mov  es:[bx],eax" \
+   "sub  ax,ax" \
+   "error:" \
+   value [ax] \
+   parm caller nomemory [] \
+   modify exact [ax bx cx dl si di es];
+
+#define VMDHPG_READONLY         0x0000                               /*@V76282*/
+#define VMDHPG_WRITE            0x0001                               /*@V76282*/
+
+USHORT DevHelp_VMProcessToGlobal(ULONG Flags, LIN LinearAddr, ULONG Length, PLIN GlobalLinearAddr);
+#pragma aux DevHelp_VMProcessToGlobal = \
+   "mov  dl,59h" \
+   "mov  eax,[esp]" \
+   "mov  ebx,[esp+4]" \
+   "mov  ecx,[esp+8]" \
+   "call dword ptr [Device_Help]" \
+   "jc   error" \
+   "les  bx,[esp+12]" \
+   "mov  es:[bx],eax" \
+   "sub  ax,ax" \
+   "error:" \
+   value [ax] \
+   parm caller nomemory [] \
+   modify exact [ax bx cx dl es];
+
+#define VMDHS_DECOMMIT          0x0001                               /*@V76282*/
+#define VMDHS_RESIDENT          0x0002                               /*@V76282*/
+#define VMDHS_SWAP              0x0004                               /*@V76282*/
+
+USHORT DevHelp_VMSetMem(LIN LinearAddr, ULONG Size, ULONG Flags);
+#pragma aux DevHelp_VMSetMem = \
+   "mov  dl,66h" \
+   "mov  ebx,[esp]" \
+   "mov  ecx,[esp+4]" \
+   "mov  eax,[esp+8]" \
+   DEVHELP_CALL \
+   parm caller nomemory [] \
+   modify nomemory exact [ax bx cx dl];
+
+USHORT DevHelp_VMUnLock(LIN pLockHandle);
+#pragma aux DevHelp_VMUnLock = \
+   "mov  dl,56h" \
+   "mov  esi,[esp]" \
+   DEVHELP_CALL \
+   parm caller [] \
+   modify exact [ax si dl];
+
+USHORT DevHelp_Yield(void);
+#pragma aux DevHelp_Yield = \
+   "mov  dl,2" \
+   "call dword ptr [Device_Help]" \
+   "sub  ax,ax" \
+   value [ax] \
+   parm caller nomemory [] \
+   modify nomemory exact [ax dl];
+
+/*
+** DevHlp Error Codes  (from ABERROR.INC)
+*/
+
+#define MSG_MEMORY_ALLOCATION_FAILED    0x00
+#define ERROR_LID_ALREADY_OWNED         0x01
+#define ERROR_LID_DOES_NOT_EXIST        0x02
+#define ERROR_ABIOS_NOT_PRESENT         0x03
+#define ERROR_NOT_YOUR_LID              0x04
+#define ERROR_INVALID_ENTRY_POINT       0x05
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif // end __WATCOMC__
+#endif // end DEVHELP_INCLUDED
+
Index: /tags/2.02/src/ioctltest/Makefile
===================================================================
--- /tags/2.02/src/ioctltest/Makefile	(revision 189)
+++ /tags/2.02/src/ioctltest/Makefile	(revision 189)
@@ -0,0 +1,75 @@
+###############################################################################
+# Makefile - makefile for os2ahci test program, gcc tool chain
+#
+# Copyright (c) 2010 Christian Mueller, Markus Thielen.
+#
+#  This program is free software; you can redistribute it and/or modify
+#  it under the terms of the GNU General Public License as published by
+#  the Free Software Foundation; either version 2 of the License, or
+#  (at your option) any later version.
+#
+#  This program is distributed in the hope that it will be useful,
+#  but WITHOUT ANY WARRANTY; without even the implied warranty of
+#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#  GNU General Public License for more details.
+#
+#  You should have received a copy of the GNU General Public License
+#  along with this program; if not, write to the Free Software
+#  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+#
+
+###############################################################################
+# Tool Chain
+#
+
+CC         = gcc
+LD         = gcc
+
+CFLAGS     = -c
+LFLAGS     =
+
+###############################################################################
+# Environment
+
+CC_INCLUDE   =
+
+AS_INCLUDE   =
+
+LIB_DIRS     =
+
+
+###############################################################################
+# Main dependencies
+
+TARGET   = ioctl_test.exe
+
+LIBS     = -los2
+
+SRCS     = ioctl_test.c
+
+OBJS     = ioctl_test.o
+
+INCS     =
+
+
+all: $(TARGET)
+
+clean:
+	rm -f $(OBJS) $(TARGET)
+
+
+###############################################################################
+# Object/source dependencies
+
+ioctl_test.o: ioctl_test.c Makefile
+
+###############################################################################
+# Action definitions (compile/link commands)
+
+.c.o:
+	$(CC) $(CFLAGS) $(CC_INCLUDE) $*.c
+
+$(TARGET): $(OBJS) Makefile
+	$(LD) -o $(TARGET) $(OBJS) $(LIB_DIRS) $(LIBS)
+
+
Index: /tags/2.02/src/ioctltest/ioctl_test.c
===================================================================
--- /tags/2.02/src/ioctltest/ioctl_test.c	(revision 189)
+++ /tags/2.02/src/ioctltest/ioctl_test.c	(revision 189)
@@ -0,0 +1,188 @@
+#include <stdlib.h>
+#include <stdio.h>
+#include <stddef.h>
+#include <memory.h>
+
+#define INCL_DOS
+#include <os2.h>
+#include "../ioctl.h"
+
+void phex(const void *p, int len);
+
+int main(int arhc, char *argv[])
+{
+  OS2AHCI_DEVLIST *dl;
+  OS2AHCI_PASSTHROUGH pt;
+  APIRET rc;
+  HFILE fh;
+  ULONG at;
+  ULONG cnt;
+  ULONG plen;
+  ULONG dlen;
+  char sense_buf[64];
+  char id_buf[512];
+  int i;
+  int n;
+
+  /* open OS2AHCI character device driver */
+  rc = DosOpen("\\DEV\\OS2AHCI$", &fh, &at, 0, FILE_SYSTEM,
+               OPEN_ACTION_OPEN_IF_EXISTS, OPEN_SHARE_DENYNONE |
+               OPEN_FLAGS_NOINHERIT | OPEN_ACCESS_READONLY, NULL);
+
+  if (rc != 0) {
+    printf("couldn't open OS2AHCI$; rc = %ld\n", rc);
+    return(1);
+  }
+
+  /* get device information */
+  cnt = 20;
+  plen = sizeof(cnt);
+  dlen = offsetof(OS2AHCI_DEVLIST, devs) + cnt * sizeof(dl->devs);
+  dl = malloc(dlen);
+  rc = DosDevIOCtl(fh, OS2AHCI_IOCTL_CATEGORY, OS2AHCI_IOCTL_GET_DEVLIST,
+                   &cnt, plen, &plen, dl, dlen, &dlen);
+
+  if (rc != 0) {
+    printf("couldn't get device list; rc = %ld\n", rc);
+    return(1);
+  }
+
+  for (i = 0; i < dl->cnt; i++) {
+    printf("connection: %d.%d.%d, type: %d, ncq_max: %d, flags: 0x%04x\n",
+           dl->devs[i].adapter, dl->devs[i].port, dl->devs[i].device,
+           dl->devs[i].type, dl->devs[i].ncq_max, dl->devs[i].flags);
+  }
+
+  printf("\n");
+
+  /* get ATA ID buffer for each device */
+  for (i = 0; i < dl->cnt; i++) {
+    memset(&pt, 0x00, sizeof(pt));
+    memset(sense_buf, 0x00, sizeof(sense_buf));
+    pt.adapter   = dl->devs[i].adapter;
+    pt.port      = dl->devs[i].port;
+    pt.device    = dl->devs[i].device;
+    pt.flags     = 0;
+    pt.timeout   = 0;
+
+    pt.cmdlen    = sizeof(pt.cmd.ata);
+    pt.cmd.ata.cmd = (dl->devs[i].flags & DF_ATAPI) ? 0xa1 : 0xec;
+
+    pt.buflen    = sizeof(id_buf);
+    pt.buf       = id_buf;
+    pt.sense_len = sizeof(sense_buf);
+
+    plen = sizeof(pt);
+    dlen = sizeof(sense_buf);
+    rc = DosDevIOCtl(fh, OS2AHCI_IOCTL_CATEGORY, OS2AHCI_IOCTL_PASSTHROUGH,
+                     &pt, plen, &plen, sense_buf, dlen, &dlen);
+
+    if (rc == 0) {
+      printf("ID for %d.%d.%d: ", dl->devs[i].adapter, dl->devs[i].port,
+             dl->devs[i].device);
+      for (n = 54; n < 94; n += 2) {
+        printf("%c%c", id_buf[n+1], id_buf[n]);
+      }
+      printf("\n");
+
+    } else {
+      printf("\nID command failed (rc = %ld); sense code:\n", rc);
+      phex(sense_buf, sizeof(sense_buf));
+    }
+
+    if (dl->devs[i].type == 5) {
+      /* Test sense buffer handling by ejecting the drive tray, then
+       * checking whether the unit is ready.
+       */
+      printf("ejecting drive tray\n");
+      memset(&pt, 0x00, sizeof(pt));
+      memset(sense_buf, 0x00, sizeof(sense_buf));
+      pt.adapter    = dl->devs[i].adapter;
+      pt.port       = dl->devs[i].port;
+      pt.device     = dl->devs[i].device;
+      pt.flags      = PT_ATAPI;
+      pt.timeout    = 0;
+
+      pt.cmdlen     = 6;
+      pt.cmd.cdb[0] = 0x1b;
+      pt.cmd.cdb[4] = 0x02;
+
+      pt.buflen     = 0;
+      pt.buf        = NULL;
+      pt.sense_len  = sizeof(sense_buf);
+
+      plen = sizeof(pt);
+      dlen = sizeof(sense_buf);
+      rc = DosDevIOCtl(fh, OS2AHCI_IOCTL_CATEGORY, OS2AHCI_IOCTL_PASSTHROUGH,
+                       &pt, plen, &plen, sense_buf, dlen, &dlen);
+
+      if (rc != 0) {
+        printf("\neject command failed (rc = %ld); sense code:\n", rc);
+        phex(sense_buf, sizeof(sense_buf));
+        break;
+      }
+
+      printf("test unit ready\n");
+      memset(&pt, 0x00, sizeof(pt));
+      memset(sense_buf, 0x00, sizeof(sense_buf));
+      pt.adapter    = dl->devs[i].adapter;
+      pt.port       = dl->devs[i].port;
+      pt.device     = dl->devs[i].device;
+      pt.flags      = PT_ATAPI;
+      pt.timeout    = 0;
+
+      pt.cmdlen     = 6;
+      pt.cmd.cdb[0] = 0x00;
+
+      pt.buflen     = 0;
+      pt.buf        = NULL;
+      pt.sense_len  = sizeof(sense_buf);
+
+      plen = sizeof(pt);
+      dlen = sizeof(sense_buf);
+      rc = DosDevIOCtl(fh, OS2AHCI_IOCTL_CATEGORY, OS2AHCI_IOCTL_PASSTHROUGH,
+                       &pt, plen, &plen, sense_buf, dlen, &dlen);
+
+      if (rc != 0) {
+        printf("\ntest unit ready command failed as expected (rc = %ld); sense code:\n", rc);
+        phex(sense_buf, sizeof(sense_buf));
+        break;
+      }
+    }
+  }
+
+  return(0);
+}
+
+void phex(const void *p, int len)
+{
+  const unsigned char *buf = p;
+  long pos = 0;
+  int i;
+
+  /* print hex block */
+  while (len > 0) {
+    printf("%08X ", pos);
+
+    /* print hex block */
+    for (i = 0; i < 16; i++) {
+      if (i < len) {
+        printf("%c%02x", ((i == 8) ? '-' : ' '), buf[i]);
+      } else {
+        printf("   ");
+      }
+    }
+
+    /* print ASCII block */
+    printf("   ");
+    for (i = 0; i < ((len > 16) ? 16 : len); i++) {
+      printf("%c", (buf[i] >= 32 && buf[i] < 128) ? buf[i] : '.');
+    }
+    printf("\n");
+
+    pos += 16;
+    buf += 16;
+    len -= 16;
+  }
+}
+
Index: /tags/2.02/src/lbatest/lbatest.c
===================================================================
--- /tags/2.02/src/lbatest/lbatest.c	(revision 189)
+++ /tags/2.02/src/lbatest/lbatest.c	(revision 189)
@@ -0,0 +1,147 @@
+/******************************************************************************
+
+  lbatest.c - simple (dumb) test program for os2ahci
+
+  Writes the actual LBA to each sector on a hard disk. The purpose is to
+  find out if the sector mapping of os2ahci.add is correct.
+
+  THe program was written to run under Linux, since OS/2 physical disk
+  access IOCTLs support CHS addressing, only.
+
+  The os2ahci.add driver provides a special operating mode that does nothing
+  but verifying that the tests drive's sector's are addressed in the correct
+  manner by verifying the sector numbers written under Linux.
+
+  Author: Markus Thielen
+
+  Compilation: gcc -o lbatest lbatest.c
+
+  Copyright (c) 2010 by thi.guten Software development, www.thiguten.de
+
+   This program is free software; you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 2 of the License, or
+   (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program; if not, write to the Free Software
+   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+
+******************************************************************************/
+
+/*---- includes -------------------------------------------------------------*/
+
+#include <stddef.h>
+#include <stdlib.h>
+#include <ctype.h>
+#include <string.h>
+#include <stdio.h>
+
+#define SECTOR_SIZE        512
+#define SECTORS_PER_WRITE  256
+
+
+/*--- function prototypes ---------------------------------------------------*/
+
+void          usage            (void);
+
+void          write_test       (char *dev);
+
+/*--- global data -----------------------------------------------------------*/
+
+
+/*--- start of code ---------------------------------------------------------*/
+
+/******************************************************************************
+ * main()
+ */
+int main(int argc, char **argv)
+{
+
+  if (argc < 2) {
+    usage();
+    return -1;
+  }
+
+  write_test(argv[1]);
+
+  return 0;
+}
+
+
+
+/******************************************************************************
+ * write_test() - write each sector's address to the sector itself
+ */
+void write_test(char *dev)
+{
+  unsigned long lba;
+  float gbf = 1024.0 * 1024.0 * 1024.0 / 512.0;
+  size_t i;
+  unsigned char *wbuf;
+  char buf[100];
+  size_t cbtake = SECTOR_SIZE * SECTORS_PER_WRITE;
+  size_t cb_written;
+  FILE *fp;
+
+  /* ask for confirmation, we destroy the drive's contents */
+  printf("I'm going to destroy ALL DATA on drive %s now.\n"
+	 "type 'DESTROY' to continue, anything else to bail out.\n", dev);
+  if (fgets(buf, sizeof(buf), stdin) == NULL || strcmp(buf, "DESTROY\n")) {
+    return;
+  }
+
+  /* open drive */
+  if ((fp = fopen(dev, "wb")) == NULL) {
+    perror("Failed to open device");
+    return;
+  }
+
+  /* allocate big write buffer */
+  wbuf = calloc(SECTOR_SIZE, SECTORS_PER_WRITE);
+
+  /* go... */
+  for (lba = 0; ; lba += SECTORS_PER_WRITE) {
+
+    /* prepare buffer; set the first 4 bytes of each sector
+     * to its LBA */
+    for (i = 0; i < SECTORS_PER_WRITE; i++) {
+      *((unsigned long *)(wbuf + i * SECTOR_SIZE)) = lba + i;
+    }
+
+    /* write buffer to disk */
+    cb_written = fwrite(wbuf, 1, cbtake, fp);
+    if (cb_written < cbtake) {
+      if (!feof(fp)) {
+        perror("Failed to write to target device");
+      }
+      break;
+    }
+
+    /* write progress */
+    printf("\r%uk sectors written (%0.02f GB)",
+           (size_t)(lba / 1000), (float)lba / gbf);
+
+  }
+
+  /* cleanup */
+  fclose(fp);
+  free(wbuf);
+
+}
+
+/******************************************************************************
+ * usage() - print usage summary to STDOUT
+ */
+void usage(void)
+{
+  printf("lbatest for os2ahci.add\n"
+	 "Usage:\n\n"
+	 "lbatest <device>\n\n");
+}
+
Index: /tags/2.02/src/lbatest/readsect.c
===================================================================
--- /tags/2.02/src/lbatest/readsect.c	(revision 189)
+++ /tags/2.02/src/lbatest/readsect.c	(revision 189)
@@ -0,0 +1,298 @@
+/******************************************************************************
+
+  readsect.c - simple (dumb) HD sector read and test program
+
+  Author: Markus Thielen
+
+  Compilation (Watcom): wcl386 -bt=os2 readsect.c
+                        wcl386 -bt=os2 -d3 readsect.c (for debug)
+
+  Copyright (c) 2010 by thi.guten Software development, www.thiguten.de
+
+   This program is free software; you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 2 of the License, or
+   (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program; if not, write to the Free Software
+   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+
+******************************************************************************/
+
+/*---- includes -------------------------------------------------------------*/
+
+#define INCL_DOS
+#define INCL_ERRORS
+#define INCL_DOSDEVICES
+#define INCL_DOSDEVIOCTL
+#include <os2.h>
+#include <stdio.h>
+#include <io.h>
+#include <stddef.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <ctype.h>
+#include <string.h>
+#include <errno.h>
+
+#define SECTOR_SIZE        512
+#define SECTORS_PER_READ   4
+
+
+/*--- function prototypes ---------------------------------------------------*/
+
+void          usage            (void);
+
+int           read_sectors     (char *drv, unsigned long sectors_to_read, 
+                                unsigned long sectors_per_read, FILE *fpo);
+
+void          log              (char *fmt, ...);
+
+
+/*--- global data -----------------------------------------------------------*/
+
+char *log_fn = NULL;
+unsigned long buffer_offset = 0;
+
+
+/*--- start of code ---------------------------------------------------------*/
+
+/******************************************************************************
+ * main()
+ */
+int main(int argc, char **argv)
+{
+  char drv[50];
+  char *output;
+  FILE *fpo = NULL; /* output file */
+  int ret;
+  unsigned long sectors_to_read = 1024;
+  unsigned long i;
+  unsigned long sectors_per_read = SECTORS_PER_READ;
+  unsigned long loop_sectors_start = sectors_per_read;
+  int a;
+
+  if (argc < 4) {
+    usage();
+    return -1;
+  }
+
+  /* parse args */
+  for (a = 1; a < argc; a++) {
+
+    if (argv[a][0] != '-' && argv[a][0] != '/') {
+      fprintf(stderr, "unknown argument %s - aborting\n", argv[a]);
+      return (-1);
+    }
+
+    switch (tolower(argv[a][1])) {
+
+    case 'd':
+      /* drive spec */
+      if (isalpha(argv[++a][0])) {
+        sprintf(drv, "\\\\.\\%c", toupper(argv[a][0]));
+      } else if (isdigit(argv[a][0])) {
+        sprintf(drv, "\\\\.\\Physical_Disk%c", argv[a][0]);
+      } else {
+        usage();
+        return -1;
+      }
+      break;
+
+    case 's':
+      /* get sectors to read */
+      sectors_to_read = strtoul(argv[++a], NULL, 10);
+      break;
+
+    case 'o':
+      /* get output file */
+      output = argv[++a];
+      if (*output == '-' && output[1] == 0) {
+        /* use stdout */
+        fpo = stdout;
+      } else {
+        fpo = fopen(output, "wb");
+        if (!fpo) {
+          perror("Failed to open output file");
+          return(-1);
+        }
+      }
+      break;
+
+    case 'b':
+      /* optional buffer size */
+      if (*argv[++a] == '-') {
+        /* use a loop from 1 sectors to number specfied */
+        loop_sectors_start = 1;
+        sectors_per_read = (unsigned long) atoi(argv[a] + 1);
+        log("Looping sector count from 1 to %u; not using output file\n",
+            sectors_per_read);
+        if (fpo && fpo != stdin) {
+          fclose(fpo);
+          fpo = NULL;
+        }
+        
+      } else {
+        /* just one run */
+        sectors_per_read = (unsigned long) atoi(argv[a]);
+        loop_sectors_start = sectors_per_read;
+      }
+      if (sectors_per_read == 0) {
+        sectors_per_read = SECTORS_PER_READ;
+      }
+      break;
+
+    case 'l':
+      /* optional log file name */
+      log_fn = argv[++a];
+      unlink(log_fn);
+      break;
+
+    case 'u':
+      /* use unaligned read buffer */
+      buffer_offset = 1;
+      break;
+    }
+  }
+
+  /* go */
+  for (i = loop_sectors_start; i <= sectors_per_read; i++) {
+    ret = read_sectors(drv, sectors_to_read, i, fpo);
+  }
+
+  if (fpo && fpo != stdout) {
+    fclose(fpo);
+  }
+
+  log("\nDone\n");
+  return ret;
+
+  }
+
+/******************************************************************************
+ * read_sectors() - read sectors and dump to output file
+ */
+int read_sectors(char *drv, unsigned long sectors_to_read, 
+                 unsigned long sectors_per_read, FILE *fpo)
+{
+  unsigned long ret;
+  HFILE hf_disk;
+  unsigned long action;
+  unsigned long cb_read;
+  char *buf;
+  int rc = 0;
+  unsigned long cb_take;
+  unsigned long sectors_take;
+  unsigned long sectors_read = 0;
+
+  buf = malloc(SECTOR_SIZE * sectors_per_read + buffer_offset);
+  log("\nusing %u sectors per read\n", sectors_per_read);
+  log("read buffer address: 0x%u %s\n", buf + buffer_offset, 
+      buffer_offset ? "(unaligned)" : "");
+
+  /* open drive */
+  log("Getting handle for drive %s\n", drv);
+  ret = DosOpen(drv, &hf_disk, &action, 0, 0, 
+                OPEN_ACTION_OPEN_IF_EXISTS,
+                OPEN_SHARE_DENYNONE | OPEN_ACCESS_READONLY,
+                NULL);
+  if (ret != NO_ERROR) {
+    fprintf(stderr, "Failed to open disk %s for reading: %d\n", drv, ret);
+    return -1;
+  }
+
+  /* go... */
+  while (sectors_read < sectors_to_read) {
+
+    sectors_take = min(sectors_to_read - sectors_read, sectors_per_read);
+    cb_take = SECTOR_SIZE * sectors_take;
+
+    log("\rReading %u sector(s) this run; (%u sectors read so far)...",
+           sectors_take, sectors_read);
+    ret = DosRead(hf_disk, buf + buffer_offset, cb_take, &cb_read);
+    if (ret != NO_ERROR) {
+      fprintf(stderr, "\nFailed to read from disk %s, code %d\n", drv, ret);
+      rc = -1;
+      break;
+    }
+
+    if (cb_read == 0) {
+      break;
+    }
+
+    if (cb_read != cb_take) {
+      log("\n\nRead only %u instead of %u bytes...\n\n", cb_read, cb_take);
+      break;
+    }
+
+    sectors_read += cb_read / SECTOR_SIZE;
+    if (fpo && fwrite(buf + buffer_offset, 1, cb_read, fpo) != cb_read) {
+      perror("Failed to write to output file");
+      rc = -1;
+      break;
+    }
+
+  }
+
+  log("\n%u sectors read.\n", sectors_read);
+
+  DosClose(hf_disk);
+  free(buf);
+
+  return rc;
+}
+
+/******************************************************************************
+ * usage() - print usage summary to STDOUT
+ */
+void usage(void)
+{
+  printf("readsect.exe - read HD sectors and store them to an output file.\n"
+         "Call with a drive letter (for logical drive) or 1-based disk number (for\n"
+         "physical drive)\n\n"
+	 "Usage:\n\n"
+	 "readsect <drive letter|number> <number of sectors> <outfile>\n"
+         "         [<read buffer size>] [-lf <log file>] [-unaligned]\n"
+         "where:\n"
+         "-d drive letter       drive letter of logical drive to read from\n"
+         "   drive number       1-based physical disk number to read from\n"
+         "-s number of sectors  number of sectors to read (e.g. 1024)\n"
+         "-o outfile            path and filename of output file\n"
+         "-b read buffer size   optional buffer size in number of sectors (512 byte)\n"
+         "                      (default is %d sectors)\n"
+         "-l log file           optional log file name\n"
+         "-u                    use unaligned buffer\n",
+         SECTORS_PER_READ);
+}
+
+
+/*******************************************************************************
+ * log() - log a string to the log file (if any)
+ */
+void log(char *fmt, ...)
+{
+  va_list arglist;
+  FILE *fp;
+  char buf[500];
+
+  /* assemble arguments */
+  va_start(arglist, fmt);
+  vsprintf(buf, fmt, arglist);
+
+  if (log_fn && (fp = fopen(log_fn, "a")) != NULL) {
+    /* write string to log file */
+    fprintf(fp, buf);
+    fflush(fp);
+    fclose(fp);
+  }
+
+  /* write to stdout */
+  printf(buf);
+
+}
Index: /tags/2.02/src/lbatest/readtest.c
===================================================================
--- /tags/2.02/src/lbatest/readtest.c	(revision 189)
+++ /tags/2.02/src/lbatest/readtest.c	(revision 189)
@@ -0,0 +1,234 @@
+/******************************************************************************
+
+  readtest.c - simple (dumb) read test program for os2ahci
+
+  Reads each sector of a logical drive under OS/2 and verifies that the
+  sector number (LBA) written (by lbatest) in the first 4 bytes of each sector 
+  matches its real LBA.
+
+  To run the test, run lbatest under Linux, then attach the drive to an OS/2
+  system an create a single primary partition that stretches the entire drive.
+  Then run readtest.exe with the drive letter as a parameter.
+ 
+
+  Author: Markus Thielen
+
+  Compilation (Watcom): wcl386 -bt=os2 readtest.c
+
+  Copyright (c) 2010 by thi.guten Software development, www.thiguten.de
+
+   This program is free software; you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 2 of the License, or
+   (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program; if not, write to the Free Software
+   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+
+******************************************************************************/
+
+/*---- includes -------------------------------------------------------------*/
+
+#define INCL_DOS
+#define INCL_ERRORS
+#define INCL_DOSDEVICES
+#define INCL_DOSDEVIOCTL
+#include <os2.h>
+#include <stdio.h>
+#include <stddef.h>
+#include <stdlib.h>
+#include <ctype.h>
+#include <string.h>
+#include <signal.h>
+
+#define SECTOR_SIZE        512
+#define SECTORS_PER_READ   200
+
+
+/*--- function prototypes ---------------------------------------------------*/
+
+void          usage            (void);
+
+int           read_test        (char *drv);
+
+void          signal_handler   (int sig_no);
+
+/*--- global data -----------------------------------------------------------*/
+
+unsigned long lba_pos;
+unsigned long first_wrong_sector = 0xffffffff;
+
+
+/*--- start of code ---------------------------------------------------------*/
+
+/******************************************************************************
+ * main()
+ */
+int main(int argc, char **argv)
+{
+  HFILE hf;
+  char drv[50];
+
+  if (argc < 2) {
+    usage();
+    return -1;
+  }
+
+  if (isalpha(argv[1][0])) {
+    sprintf(drv, "\\\\.\\%c", toupper(argv[1][0]));
+  } else if (isdigit(argv[1][0])) {
+    sprintf(drv, "\\\\.\\Physical_Disk%c", argv[1][0]);
+  } else {
+    usage();
+    return -1;
+  }
+
+  return read_test(drv);
+
+}
+
+/******************************************************************************
+ * read_test() - read each sector's first 4 bytes and compare to its LBA
+ */
+int read_test(char *drv)
+{
+  unsigned long ret;
+  HFILE hf_disk;
+  unsigned long action;
+  unsigned long cb_read;
+  char *buf;
+  int rc = 0;
+  unsigned long cb_take = SECTOR_SIZE * SECTORS_PER_READ;
+  unsigned long err_cnt = 0;
+  float gbf = 1024.0 * 1024.0 * 1024.0 / 512.0;
+  LONGLONG cbfile = {0};
+  int s;
+  unsigned long sectors;
+  unsigned long val;
+
+  buf = calloc(SECTOR_SIZE, SECTORS_PER_READ);
+
+  /* open drive */
+  printf("Getting handle for drive %s\n", drv);
+  ret = DosOpen(drv, &hf_disk, &action, 0, 0, 
+                OPEN_ACTION_OPEN_IF_EXISTS,
+/*                   OPEN_FLAGS_DASD | OPEN_FLAGS_FAIL_ON_ERROR | */
+/*                   OPEN_FLAGS_WRITE_THROUGH | OPEN_FLAGS_NO_CACHE | */
+/*                   OPEN_FLAGS_SEQUENTIAL | OPEN_SHARE_DENYREADWRITE | */
+                OPEN_SHARE_DENYNONE | OPEN_ACCESS_READONLY,
+                NULL);
+  if (ret != NO_ERROR) {
+    fprintf(stderr, "Failed to open disk %s for reading: %d\n", drv, ret);
+    return -1;
+  }
+
+  /* lock disk */
+/*    ret = DosDevIOCtl(hf_disk, IOCTL_DISK, DSK_LOCKDRIVE, NULL, 0, */
+/*  		    &action, NULL, 0, &cb_read); */
+/*    if (ret != NO_ERROR) { */
+/*      fprintf(stderr, "Failed to lock drive, code %d\n", ret); */
+/*      rc = -1; */
+/*      goto cleanup; */
+/*    } */
+
+  /* catch Ctrl+C */
+  signal(SIGINT, signal_handler);
+
+  /* go... */
+  memset(buf, 0x00, sizeof(buf));
+  for (lba_pos = 0; ; lba_pos += SECTORS_PER_READ) {
+    ret = DosRead(hf_disk, buf, cb_take, &cb_read);
+    if (ret != NO_ERROR) {
+      fprintf(stderr, "\nFailed to read from disk %s, code %d\n", drv, ret);
+      rc = -1;
+      goto cleanup;
+    }
+
+    if (cb_read == 0) {
+      goto cleanup;
+    }
+
+    sectors = cb_read / SECTOR_SIZE;
+
+    for (s = 0; s < sectors; s++) {
+
+      if (lba_pos + s < 64) {
+        continue;
+      }
+
+      val = *((unsigned long*) (buf + s * SECTOR_SIZE));
+      if (val == 0xf6f6f6f6) {
+        /* appearantly, this is the funny first partition sector 
+         * created by LVM - skip it */
+        continue;
+      }
+
+      if (val != lba_pos + s) {
+        printf("\nWrong sector number: read 0x%08x from sector 0x%08x",
+               val, lba_pos + s);
+        err_cnt++;
+        if (first_wrong_sector == 0xffffffff) {
+          first_wrong_sector = lba_pos + s;
+        }
+      }
+    }
+
+    /* progress */
+    printf("\r%dk sectors read (%0.02f GB)", lba_pos / 1000, 
+           (float) lba_pos / gbf);
+  }
+
+cleanup:
+  /* unlock drive */
+  DosDevIOCtl(hf_disk, IOCTL_DISK, DSK_UNLOCKDRIVE, NULL, 0, &action,
+              NULL, 0, &cb_read);
+  DosClose(hf_disk);
+  free(buf);
+
+  /* print summary */
+  printf("Found %d logical errors\n", err_cnt);
+  if (first_wrong_sector != 0xffffffff) {
+    printf("First wrong sector was %u.\n", err_cnt, first_wrong_sector);
+  }
+
+  return rc;
+}
+
+/******************************************************************************
+ * usage() - print usage summary to STDOUT
+ */
+void usage(void)
+{
+  printf("LBA (read sector number) test for os2ahci.add\n"
+         "Call with a drive letter (for logical drive) or 1-based disk number (for\n"
+         "physical drive)\n\n"
+	 "Usage:\n\n"
+	 "lbatest <drive letter|drive number>\n\n");
+}
+
+
+/******************************************************************************
+ * signal_handler for SIGINT - prints summary to STDOUT
+ */
+void signal_handler(int sig_no)
+{
+
+  if (sig_no == SIGINT) {
+    /* read mode interrupted; show summary */
+    printf("\n\nLast block read:    0x%08x\n", lba_pos);
+    if (first_wrong_sector != 0xffffffff) {
+      printf("First wrong sector: 0x%08x\n", first_wrong_sector);
+    } else {
+      printf("All sectors read ok\n");
+    }
+  }
+
+  exit(0);
+
+}
Index: /tags/2.02/src/os2ahci/Makefile
===================================================================
--- /tags/2.02/src/os2ahci/Makefile	(revision 189)
+++ /tags/2.02/src/os2ahci/Makefile	(revision 189)
@@ -0,0 +1,190 @@
+###############################################################################
+# Makefile - makefile for os2ahci driver
+# This is an Open Watcom makefile
+#
+# Copyright (c) 2010 Christian Mueller, Markus Thielen.
+# Copyright (c) 2013-2016 David Azarewicz
+#
+.ERASE
+.SUFFIXES
+.SUFFIXES: .lst .obj .c .asm .lib .def
+
+# Define default build version if not specified in environment
+BLD_MAJOR=2
+BLD_MINOR=02 # must be 2 digits
+BLD_REV=0 # not used at this time
+
+!ifndef %ROOT
+!error ROOT must be set in the environment.
+!endif
+ROOT=$(%ROOT)
+
+!ifndef %WATCOM # if not defined in the environment
+!error WATCOM must be set in the environment.
+!endif
+WATCOM=$(%WATCOM)
+
+!ifndef %DDK # if not defined in the environment
+!error DDK must be set in the environment.
+!endif
+DDK=$(%DDK)
+
+!ifndef %DRV32KIT
+!error DRV32KIT must be defined in environment
+!endif
+DRV32KIT=$(%DRV32KIT)
+
+!ifdef %VENDOR
+VENDOR=$(%VENDOR)
+!else
+VENDOR=Unknown
+!endif
+
+!ifdef %BLD_MAJOR
+BLD_MAJOR=$(%BLD_MAJOR)
+!endif
+
+!ifdef %BLD_MINOR
+BLD_MINOR=$(%BLD_MINOR)
+!endif
+
+!ifdef %FIXPACK
+FIXPACK=$(%FIXPACK)
+!endif
+
+VERSION=$(BLD_MAJOR).$(BLD_MINOR)
+ZIPDIR=$(ROOT)\tmp
+
+!if "$(FIXPACK)"!=""
+!ifndef %BLD_DATE
+!error BLD_DATE must be defined for test versions
+!endif
+WPIFILE=$(ROOT)\AHCI-$(VERSION)-$(%BLD_DATE)-WPI.zip
+!else
+WPIFILE=$(ROOT)\AHCI-$(VERSION)-WPI.zip
+!endif
+
+AS_INCLUDE = -I=$(DDK)\base\inc
+
+%INCLUDE=.;$(ROOT)\src\include;$(DRV32KIT);$(WATCOM)\H;$(DDK)\base\h;$(DDK)\base32\h;
+%PATH=$(ROOT)\Tools;$(TOOLKIT)\bin;$(DDK)\base\tools;$(WATCOM)\BINP;$(WATCOM)\BINW;$(%PATH)
+
+AS=wasm
+CC=wcc386
+MAPSYM=mapsym.exe
+
+!ifdef __LOADDLL__
+!loaddll wcc386   $(WATCOM)\BINP\DLL\wccd386.dll
+!loaddll wlink $(WATCOM)\BINP\DLL\wlinkd.dll
+!endif
+
+#DAZ need to find which structures need pack(1) so we can reset this back to -zp4
+AFLAGS=-q -6p -bt=os2 -wx -d1
+CFLAGS =-q -bt=os2 -6s -olinar -s -ze -zl -zq -zfp -zgp -ms -wx -zp1 -ecs -ei -za99
+
+!ifdef DEBUG
+CDEFS=-DDEBUG
+O=Debug
+!else
+CDEFS=
+O=Retail
+!endif
+
+!ifdef FIXPACK
+CDEFS+= -DTESTVER
+!endif
+
+LIBS = $(DRV32KIT)\Drv32.lib $(DDK)\base32\lib\kee.lib
+
+OBJS = $(O)\os2ahci.obj $(O)\pci.obj $(O)\ahci.obj $(O)\ata.obj $(O)\atapi.obj &
+       $(O)\ctxhook.obj $(O)\trace.obj $(O)\ioctl.obj $(O)\apm.obj $(O)\thunk.obj
+
+.asm.obj: .autodepend
+  $(AS) $(AFLAGS) -fo=$^@ $[@
+  #wdis -l $^@
+
+.c.obj: .autodepend
+  $(CC) $(CFLAGS) $(CDEFS) -fo=$^@ $[@
+  wdis -l $^@
+
+all: directory $(O)\os2ahci.add $(O)\os2ahci.sym
+
+directory: .symbolic
+  $(ROOT)\Tools\createpath.cmd $(O)
+
+version.h: Makefile .always
+  @%create $^@
+  @%append $^@ /* Autogenerated by Makefile */
+  @%append $^@ $#define DMAJOR $(BLD_MAJOR)
+  @%append $^@ $#define DMINOR $(BLD_MINOR)
+  @%append $^@ $#define DVENDOR "$(VENDOR)"
+  @AddToFile $^@,$#define BLD_YEAR,DATEYEAR
+  @AddToFile $^@,$#define BLD_MONTH,DATEMONTH
+  @AddToFile $^@,$#define BLD_DAY,DATEDAY
+  @AddToFile $^@,$#define BLDLEVEL,BLDLEVEL2,$(VENDOR),$(VERSION),AHCI Driver (c) %Y $(VENDOR),$(FIXPACK)
+
+$(O)\os2ahci.add: version.h $(OBJS) Makefile
+  @%create $^&.lrf
+  @%append $^&.lrf format os2 lx phys
+  @%append $^&.lrf option mixed1632,nostub,align=4
+  @%append $^&.lrf option quiet,int,verbose,nocaseexact,eliminate
+  @%append $^&.lrf option map=$^*.map
+  @%append $^&.lrf option stack=0
+  #  @%append $*.lnk option protmode
+  @%append $^&.lrf sort global
+  @%append $^&.lrf option osname='ArcaOS'
+  @%append $^&.lrf name $^@
+  @%append $^&.lrf file $(DRV32KIT)\Drv32.lib(header)
+  @for %f in ($(OBJS)) do @%append $^&.lrf file %f
+  @for %f in ($(LIBS)) do @%append $^&.lrf library %f
+  #  @%append $*.lnk segment type DATA SHARED
+  #  @%append $*.lnk segment type CODE IOPL
+  #  # Order segments by class
+  #  @%append $*.lnk order
+  #  @%append $*.lnk clname 'DATA'
+  #  @%append $*.lnk clname 'CONST'
+  #  @%append $*.lnk clname 'BSS'
+  #  @%append $*.lnk clname 'CODE'
+  @wlink @$^&.lrf
+  @%erase $^&.lrf
+
+$(O)\os2ahci.sym: $(O)\os2ahci.map
+  wat2map.cmd $[@ $^*.ma1
+  cd $(O)
+  $(MAPSYM) $^&.ma1
+  @%erase $^&.ma1
+  cd ..
+
+release: .symbolic
+  @if exist $(WPIFILE) @del $(WPIFILE)
+  @!rm -rf $(ZIPDIR)
+
+  @md $(ZIPDIR)
+
+  @md $(ZIPDIR)\pkg1
+  copy /b $(ROOT)\src\os2ahci\$(O)\os2ahci.add $(ZIPDIR)\pkg1 >NUL
+  copy /b $(ROOT)\src\os2ahci\$(O)\os2ahci.sym $(ZIPDIR)\pkg1 >NUL
+
+  @md $(ZIPDIR)\pkg2
+  @copy /b $(ROOT)\Tools\smartahci.exe $(ZIPDIR)\pkg2 >NUL
+
+  @md $(ZIPDIR)\pkg20
+  @copy $(ROOT)\src\os2ahci\ReadMe.txt $(ZIPDIR)\pkg20 >NUL
+  @copy $(ROOT)\Tools\LICENSE $(ZIPDIR)\pkg20 >NUL
+
+  @md $(ZIPDIR)\wpi
+  @sed -e "s/\\X\\X\\X/\\0\\$(VERSION:.=\\)/" $(ROOT)\Tools\ahci.wis >$(ZIPDIR)\wpi\ahci.wis
+  #@copy $(ROOT)\tools\stub.exe $(ZIPDIR)\wpi >NUL
+
+  cd $(ZIPDIR)
+  @zip -r -X $(WPIFILE) *
+  @cd $(ROOT)
+  @!rm -rf $(ZIPDIR)
+
+clean: .symbolic
+  rm -f *.def *.err *.lnk version.h
+  rm $(ROOT)\AHCI-*-WPI.zip
+  @if exist Debug @!rm -r Debug
+  @if exist Retail @!rm -r Retail
+
+
Index: /tags/2.02/src/os2ahci/ReadMe.txt
===================================================================
--- /tags/2.02/src/os2ahci/ReadMe.txt	(revision 189)
+++ /tags/2.02/src/os2ahci/ReadMe.txt	(revision 189)
@@ -0,0 +1,447 @@
+AHCI Driver for OS/2 v2.01
+
+
+Introduction
+============
+
+OS2AHCI is an AHCI driver for OS/2. It supports both ATA and
+ATAPI devices in a single driver. An ATAPI/CDROM filter driver is
+only required if you want to read/write CD-DA (audio) format CDs.
+
+
+Copyrights and License
+======================
+
+(c) Copyright IBM Corporation 1990,2000.
+All rights reserved.
+Copyright (c) 2011 thi.guten Software Development
+Copyright (c) 2011 Mensys B.V.
+Copyright (c) 2013-2017 David Azarewicz
+
+Authors: Christian Mueller, Markus Thielen
+
+Parts copied from/inspired by the Linux AHCI driver;
+those parts are (c) Linux AHCI/ATA maintainers.
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston,
+ MA  02111-1307  USA
+
+The OS2AHCI.ADD Driver Software is a derivative work of the IBM DDK.
+Binary programs and documentation for the OS2AHCI.ADD Driver
+Software are licensed to and distributed by Arca Noae, LLC.
+
+The source code can be retrieved from http://svn.netlabs.org.
+In compliance to the GNU General Public License, the source code
+can of course be modified/compiled to run on other products as long
+as modifications will also be published as outlined in the GNU GPL.
+
+Please note that builds other than the official binary delivered by
+the Arca Noae web site are not officially supported.
+
+
+Getting Support and Reporting Problems
+======================================
+
+For more information and to report problems please visit:
+  https://www.arcanoae.com
+and click on SUPPORT.
+
+Or go directly to the AHCI support wiki at:
+  https://www.arcanoae.com/wiki/ahci/
+
+
+Driver Command Line Options
+===========================
+
+Global Options
+
+Option                 Description
+------------------------------------------------------------------------------
+/B:<baud>              Initialize the COM port to the specified baud rate. Allowable
+                       baud values are: 300, 600, 1200, 2400, 4800, 9600, 19200,
+                       38400, 57600, and 115200. /B has no effect if /C is not also
+                       specified. If /B is not specified, the COM port is not
+                       initialized. For example, if you are using the kernel debugger,
+                       the kernel debugger initializes the COM port so you should not
+                       use this switch.
+
+/C:<n>                 Set debug COM port base address. Values for n can be:
+                         1 = COM1
+                         2 = COM2
+                         a hex value (COM port base address) COM1=3f8, COM2=2f8
+                       The default is 0. If set to 0 then no output goes to the COM port.
+
+/D[:n]                 Debug output to COM port/debug buffer. Values for n can be:
+                         1 = requests
+                         2 = detailed
+                         3 = verbose
+                       If :n is not specified the debug level is incremented for
+                       each /D specified.
+
+/W                     Allows the debug buffer to wrap when full.
+
+/V[:n]                 Display informational messages during boot. Values for n can be:
+                         1 = Display sign on banner
+                         2 = Display adapter information
+                       If :n is not specified the verbosity level is incremented for
+                       each /V specified.
+
+/G:<vendor>:<device>   Add generic PCI ID to list of supported AHCI adapters
+                       (e.g. /G:8086:2829)
+
+/T                     Perform thorough PCI ID scan; default = on, can be
+                       turned off with /!T to perform only a PCI class scan
+
+/F                     Force the use of the HW write cache when using NCQ
+                       commands; see "Native Command Queuing" below for
+                       further explanation (default = off)
+
+/R                     Reset ports during initialization (default = on)
+                       Can be turned off with /!R, however, when the
+                       [Intel] AHCI controller was found to be
+                       initialized by the BIOS in SATA mode, ports will
+                       always be reset even when /!R is specified
+
+/A:n                   Set adapter to n for adapter-specific options
+                       (default = -1, all adapters)
+
+/P:n                   Set port to n for port-specific options
+                       (default = -1, all ports)
+
+/I                     Ignore current adapter if no port has been specified.
+                       Otherwise, ignore the current port on the current adapter.
+
+Port-specific Options
+
+Option                 Description
+------------------------------------------------------------------------------
+/S                     Enable SCSI emulation for ATAPI units (default = on)
+                       SCSI emulation is required for tools like cdrecord.
+
+/N                     Enable NCQ (Native Command Queuing) for hard disks
+                       (default = off)
+
+/LS                    Set link speed (default = 0):
+                         0 = maximum,
+                         1 = limit to generation 1
+                         2 = limit to generation 2
+                         3 = limit to generation 3
+
+/LP                    Set link power management (default = 0):
+                         0 = full power management,
+                         1 = transitions to "partial slumber state" disabled,
+                         2 = transitions to "slumber state" disabled,
+                         3 = transitions to both partial and slumber states disabled
+
+/4                     Force track size to be 56 sectors regardless of the
+                       reported disk geometry to optimize partition boundaries
+                       for hard disks with 4096 byte sectors.
+
+Port-specific options depend on the currently active adapter
+and port selector (/A and /P). Those selectors are -1 per default
+which means "all" adapters/ports. The scope can be reduced by limiting
+it to an adapter (/A) or an adapter and a port (/A and /P). The scope
+can be reset by setting the corresponding option back to -1.
+
+For example:
+
+  BASEDEV=OS2AHCI.ADD /N /A:0 /P:5 /!N /A:1 /P:-1 /!N
+
+This has the following effect:
+
+  - Enable NCQ for all hard disks
+  - Disable NCQ for hard disk on adapter #0, port #5
+  - Disable NCQ for all hard disks on adapter #1
+
+
+Native Command Queuing
+======================
+
+Native Command Queuing (NCQ) is a feature which allows sending multiple I/O
+requests to hard disks before waiting for any of the requests to complete,
+much like Tagged Command Queuing for SCSI devices. This allows the disks
+to reorder I/O requests to minimize head movements, resulting in improved
+performance when executing random I/Os. In practice, this will be most
+noticable when multiple programs request I/O services to different parts
+of the disk -- a single program typically won't queue up I/O's but instead
+will wait for each I/O to complete (with the exception of programs like
+database servers).
+
+While we believe NCQ will work with the majority of controllers and hard
+disks, it's currently turned off by default until we have more feedback
+from OS/2 users. In order to turn on NCQ, just add the command line
+option "/N" to OS2AHCI.ADD.
+
+NCQ and HW Caches
+-----------------
+
+In NCQ mode, OS2AHCI supports a request flag which allows upstream code
+(e.g. file systems) to force writes to go directly to the disk instead
+of being buffered in the HW disk cache. However, at least JFS doesn't
+support this flag properly which effectively disables the HW disk cache
+for write operations across the board, resulting in a substantial
+performance loss. In order to prevent OS2AHCI from disabling the HW
+cache when so requested by upstream code, please use the command line
+option "/F".
+
+This may, of course, result in data loss in case of power failures but
+apparently this was the situation with previous IDE drivers as well thus
+shouldn't make much difference in the field. The JFS code also seems to
+imply that this flag has never been widely supported by [IDE] drivers;
+otherwise, the JFS developers should have stumbled over the performance
+loss a long time ago and fixed the code.
+
+NOTES:
+
+ - Without NCQ, OS2AHCI behaves like former IDE drivers, i.e. the HW
+   cache will always be enabled (on modern disks).
+
+ - When suspending, rebooting or shutting down, OS2AHCI always flushes
+   the HW disk cache regardless of the "/F" or "/N" command line options.
+
+
+Interoperability With IDE Drivers
+=================================
+
+There are three kinds of IDE/ATA/SATA controllers:
+
+ 1. Older controllers (IDE or SATA) without AHCI support
+    This kind of controller will only be recognized by IDE drivers
+    (IBM1S506.ADD or DANIS506.ADD).
+
+ 2. AHCI-capable controllers which supports IDE/SATA interfaces
+    This kind of controller will work with IDE or AHCI drivers and it's
+    up to the user to decide which driver to use.
+
+ 3. AHCI-only controllers
+    This kind of controller will only be recognized by OS2AHCI.
+
+If there's a mix of controllers of types 1 and 3, both an IDE and an AHCI
+driver will be required.
+
+If type 2 controllers are involved, it's up to the user to decide which
+driver to use. Both DANIS506.ADD and OS2AHCI.ADD will verify whether another
+driver has already allocated the corresponding adapter, thus the only
+decision to take for mixed configurations is whether type-2 controllers
+should be handled by DANIS506.ADD or OS2AHCI.ADD and this can be done by
+having the desired driver's BASEDEV statement coming first in CONFIG.SYS.
+
+NOTE: Older versions of DANIS506.ADD did not verify whether the resources
+      of a particular adapter were already allocated by another driver.
+      DANIS506.ADD 1.8.8 or later is required for this to work.
+
+      When using earlier versions of DANIS506.ADD, the options "/A:x /I"
+      will be required to tell DANIS506.ADD to ignore adapters to be
+      driven by OS2AHCI.ADD. The same applies to IBM1S506.ADD
+
+Mixed Controller Example
+------------------------
+
+Assume a DELL D630 or a Thinkpad T60. The hard disk is attached to the
+SATA/AHCI controller of the ICH-7 hub while the CDROM is attached to the
+PATA IDE controller. This allows two different configurations:
+
+ 1. Drive HDD and CDROM via DANIS506.ADD
+ 2. Drive HDD via OS2AHCI.ADD and CDROM via DANIS506.ADD
+
+OS2AHCI.ADD can't drive the CDROM because it's attached to a PATA
+IDE controller which doesn't support AHCI.
+
+ - If OS2AHCI.ADD comes first in CONFIG.SYS, it will take over the SATA/AHCI
+   controller and drive the HDD. DANIS506.ADD will take care of the PATA/IDE
+   controller for the CDROM.
+ - If DANIS506.ADD comes first in CONFIG.SYS, it will take over both the
+   SATA/AHCI and the PATA/IDE controller and OS2AHCI.ADD will silently exit.
+
+Advantages of AHCI
+------------------
+
+The interfaces provided by the various [Intel] controllers could be
+summarized like this (the term ATA as driver interface being a bit of our
+own invention):
+
+ - Intel PIIX: IDE (I/O registers) and ATA (taskfile)
+ - Intel ICH6: IDE (I/O registers), ATA (taskfile) and SATA
+   (FIS, vendor-specific)
+ - Intel ICH7: IDE (I/O registers), ATA (taskfile), SATA
+   (FIS, vendor-specific) and AHCI (FIS)
+ - Intel PCH: AHCI (FIS)
+
+Taskfiles are regions in memory with ATA commands which the IDE/ATA
+controller can read and process autonomously. FIS (Frame Information
+Structures) are pretty much the same but they are specific to the SATA
+communication protocol on the serial link. The most important FIS type
+for AHCI drivers is the H2D (host to device) FIS which basically contains
+the ATA command to be executed.
+
+The big advantage of AHCI controllers, apart from being vendor-neutral,
+is that they take care of a lot of things which previous-generation
+drivers like DANIS506 would have to do step by step. For example, in
+order to send an ATAPI command, DANIS506 would have to do the following:
+
+  * Send ATA "PACKET" command to device (via IDE registers, ATA taskfiles
+    or SATA FIS)
+  * Wait until device signals via interrupt it's ready for the ATAPI command
+  * Send ATAPI command to device via PIO
+  * Wait until device signals via interrupt it's ready to transfer data
+  * Send/Receive any data that might come along with the ATAPI command via
+    PIO, or wait for DMA transfer to complete
+  * Wait until device signals via interrupt that command and data transfer
+    have completed
+
+For OS2AHCI, the same operation looks like this:
+
+  * Fill in AHCI command header, FIS with ATA "PACKET" command and the ATAPI
+    command
+  * Tell port engine to process the command
+  * Wait until controller signals via interrupt that command and data
+    transfer have completed
+
+The AHCI controller automatically takes care of all underlying bits and
+pieces. OS2AHCI doesn't even have to know whether a particular message is
+sent via PIO or DMA because this is handled by the AHCI controller, too.
+And the whole concept of PIO and DMA is only relevant between AHCI controller
+and the device -- all transfers between OS2AHCI and the AHCI controller are
+always done via DMA.
+
+
+SMART Support
+=============
+
+Starting with version 1.22, OS2AHCI supports the IOCTL interface required by
+existing SMART monitoring tools. Since those tools are hard-coded to open
+the character device named "IBMS506$", they will not work with OS2AHCI unless
+modified to open the OS2AHCI$ device. Previous versions of OS2AHCI used to
+register a duplicate device with the IBMS506$ name, however since that caused
+so many unacceptable problems, that is no longer done. You must have a SMART
+utility that opens the OS2AHCI$ device to access the AHCI driver.
+
+A patched version of the smartctl.exe program is included in the OS2AHCI
+distribution. This patched version simply has the name "IBMS506$" changed
+to "OS2AHCI$" and is otherwise identical. This patched program has been
+renamed to smartahci.exe to distinguish it from the unpatched version.
+The patched smartahci.exe program is provided AS-IS and is completely
+UNSUPPORTED. Use of this program is at your own risk.
+
+NOTES:
+
+ - The IOCTL interface for SMART is based on the idea of IDE controllers
+   with a master and a slave drive. OS2AHCI maps all devices (ATA or ATAPI)
+   sequentially to this pattern. If, for example, you have 4 hard disks and
+   one CDROM attached to a single controller on ports 1, 2, 5, 7, and 11,
+   SMART tools will see 3 controllers as follows:
+
+    - controller 0, master: HD on port 1
+    - controller 0, slave:  HD on port 2
+    - controller 1, master: HD on port 5
+    - controller 1, slave:  HD on port 7
+    - controller 2, master: CDROM on port 11
+
+ - The DSKSP_GEN_GET_COUNTERS interface is currently unsupported; calls to
+   the corresponding IOCTL will return 0 for all counters. SMART counters
+   are not affected by this limitation, i.e. SMART tools will be able to
+   report counters from the physical disk; this limitation only affects
+   the software counters maintained by ADD drivers which do support the
+   DSKSP_GEN_GET_COUNTERS IOCTL request.
+
+
+Manual Installation
+===================
+
+- Run the included update.cmd,
+  or copy the driver file, OS2AHCI.ADD, to \OS2\BOOT on your boot disk.
+
+- Add the following line to CONFIG.SYS:
+  BASEDEV=OS2AHCI.ADD
+
+
+Building The Driver
+-------------------
+
+The toolchain required for compilation consists of:
+
+ - The MiniDDK or an updated DDK (You must have a DDK license
+   to build this driver.)
+ - Open Watcom version 1.9 or later
+ - The Drv32 kit.
+
+Define DDK, WATCOM, and DRV32KIT in the environment.
+Use "wmake" or "wmake -a" to build the driver. See _build.cmd.
+
+
+Change Log
+==========
+
+v.2.01 01-Oct-2016 - David Azarewicz
+  Major reorganization of the entire driver.
+  Enhanced debugging support.
+
+v.1.32 09-Nov-2013 - David Azarewicz
+  Fix for some hardware that reports incorrect status
+  Report real device in addition to fake SCSI device when SCSI emulation
+    is enabled.
+
+v.1.31 21-Aug-2013 - David Azarewicz
+  Enhanced debug output.
+  Added code to check for bad geometries reported by the BIOS and fix them.
+  Fixed a PCI ID coding error that has been there since version 1.01.
+
+v.1.30 29-Jun-2013 - David Azarewicz
+  Enhanced debug log output
+  Removed the IBMS506 header that was causing problems and shouldn't
+    be there anyway.
+  Fixed a defect in the SMART IOCtl.
+  Added ability to ignore individual ports.
+
+v.1.29 12-Jun-2013 - David Azarewicz
+  Changed scsi emulation to be on by default.
+
+v.1.28 01-Jun-2013 - David Azarewicz
+  Reworked trap dump kernel exit
+  Removed unused IDC entry point.
+  Reworked suspend/resume routines.
+  Implemented a temporary hack to make resume work reasonably well.
+  Suspend/resume is only supported on OS/2 systems with ACPI.
+  Suspend/resume is known to not work reliably and cannot be further
+    addressed in this driver.
+
+v.1.27 23-Apr-2013 - David Azarewicz
+  Added LVM aware disk geometry reporting.
+  Begin to add disk information report - not finished yet.
+  Removed undocumented /Q switch and made the driver quiet by default.
+  Debug output improvements.
+  Added /B switch for setting debug baud rate.
+  Fixed up time delay functions
+
+v.1.26 26-Mar-2013 - David Azarewicz
+  Fix spin-up / power-up issue on some hardware
+  Reorganized and improved debug output.
+
+v.1.26 21-Feb-2013 - rousseau
+  Virtual box fix
+  Some SMP fixes
+  Changed default for port reset to always
+
+v.1.25 02-Oct-2012 - markus.thi
+  Added support for trap dumps
+
+v.1.24 21-May-2012 - markus.thi
+  Fixed JFS long format hang (ticket 16)
+
+V.1.23 16-May-2012 - markus.thi
+  added IDC entry point to allow switching back to BIOS mode
+
+v.1.22 17-Oct-2011 - markus.thi
+  Added "IBMS506" header to accomodate broken SMART tools.
Index: /tags/2.02/src/os2ahci/_build.cmd
===================================================================
--- /tags/2.02/src/os2ahci/_build.cmd	(revision 189)
+++ /tags/2.02/src/os2ahci/_build.cmd	(revision 189)
@@ -0,0 +1,13 @@
+@echo off
+rem ROOT must be set the the base of the AHCI sources.
+rem  ie. Where you checked out the trunk, not the os2ahci directory.
+rem set ROOT=f:\src\ahci
+rem set VENDOR=Your Name
+rem set BLD_MAJOR=2
+rem set BLD_MINOR=01
+rem set WATCOM=e:\Watcom
+rem set BLD_DATE=20161008
+rem set DDK=f:\ddk
+rem set DRV32KIT=f:\Drv32
+wmake -a
+
Index: /tags/2.02/src/os2ahci/_clean.cmd
===================================================================
--- /tags/2.02/src/os2ahci/_clean.cmd	(revision 189)
+++ /tags/2.02/src/os2ahci/_clean.cmd	(revision 189)
@@ -0,0 +1,2 @@
+@echo off
+wmake clean
Index: /tags/2.02/src/os2ahci/ahci.c
===================================================================
--- /tags/2.02/src/os2ahci/ahci.c	(revision 189)
+++ /tags/2.02/src/os2ahci/ahci.c	(revision 189)
@@ -0,0 +1,1846 @@
+/******************************************************************************
+ * ahci.c - ahci hardware access functions
+ *
+ * Copyright (c) 2011 thi.guten Software Development
+ * Copyright (c) 2011 Mensys B.V.
+ * Copyright (c) 2013-2016 David Azarewicz
+ *
+ * Authors: Christian Mueller, Markus Thielen
+ *
+ * Parts copied from/inspired by the Linux AHCI driver;
+ * those parts are (c) Linux AHCI/ATA maintainers
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+#include "os2ahci.h"
+#include "ata.h"
+#include "atapi.h"
+
+/* -------------------------- macros and constants ------------------------- */
+
+/* produce ata/atapi function pointer with the given func name */
+#define cmd_func(iorb, func)  ad_infos[iorb_unit_adapter(iorb)].     \
+                                 ports[iorb_unit_port(iorb)].        \
+                                  devs[iorb_unit_device(iorb)].atapi \
+                              ? atapi_##func : ata_##func
+
+
+/* ------------------------ typedefs and structures ------------------------ */
+
+/* -------------------------- function prototypes -------------------------- */
+
+static void ahci_setup_device(AD_INFO *ai, int p, int d, u16 *id_buf);
+
+/* ------------------------ global/static variables ------------------------ */
+
+/* Initial driver status flags indexed by the board_* constants in os2ahci.h
+ *
+ * NOTE: The Linux AHCI driver uses a combination of board-specific quirk
+ *       flags and overriding certain libata service functions to handle
+ *       adapter flaws. However, there were only three overrides at the time
+ *       os2ahci was written, one for hard adapter resets and two for port
+ *       resets, and we can easily implement those within the corresponding
+ *       reset handlers. If this becomes more complex, this array of flags
+ *       should be converted into a structure array which contains function
+ *       pointers to all handler functions which may need to be overridden.
+ */
+u16 initial_flags[] =
+{
+  0,                                        /* board_ahci */
+  AHCI_HFLAG_NO_NCQ |                       /* board_ahci_vt8251 */
+    AHCI_HFLAG_NO_PMP,
+  AHCI_HFLAG_IGN_IRQ_IF_ERR,                /* board_ahci_ign_iferr */
+  AHCI_HFLAG_IGN_SERR_INTERNAL |            /* board_ahci_sb600 */
+    AHCI_HFLAG_NO_MSI |
+    AHCI_HFLAG_SECT255 |
+    AHCI_HFLAG_32BIT_ONLY,
+  AHCI_HFLAG_NO_NCQ |                       /* board_ahci_mv */
+    AHCI_HFLAG_NO_MSI |
+    AHCI_HFLAG_MV_PATA |
+    AHCI_HFLAG_NO_PMP,
+  AHCI_HFLAG_IGN_SERR_INTERNAL,             /* board_ahci_sb700 */
+  AHCI_HFLAG_YES_NCQ,                       /* board_ahci_mcp65 */
+  AHCI_HFLAG_NO_PMP,                        /* board_ahci_nopmp */
+  AHCI_HFLAG_YES_NCQ,                       /* board_ahci_yesncq */
+  AHCI_HFLAG_NO_SNTF,                       /* board_ahci_nosntf */
+};
+
+#define MAX_IRQ_HANDLERS 8 /* This is the maximum number of handlers that Dev32Help_SetIRQ can register */
+static u16 irq_used[MAX_IRQ_HANDLERS]; /* IRQ level for each used IRQ */
+static int irq_used_cnt; /* number of IRQs used */
+
+/* ----------------------------- start of code ----------------------------- */
+
+#ifdef DEBUG
+void ahci_dump_host_regs(AD_INFO *ai, int bios_regs)
+{
+  int i;
+  u32 version;
+
+  DPRINTF(2,"AHCI global registers for adapter %d %d:%d:%d irq=%d addr=0x%x\n",
+      ad_no(ai),
+      PCI_BUS_FROM_BDF(ai->bus_dev_func), PCI_DEV_FROM_BDF(ai->bus_dev_func),
+      PCI_FUNC_FROM_BDF(ai->bus_dev_func), ai->irq, ai->mmio_phys);
+
+  for (i = 0; i <= HOST_CAP2; i += sizeof(u32)) {
+    u32 val;
+
+    if (bios_regs) val = ai->bios_config[i/sizeof(u32)];
+    else
+    {
+      /* HOST_CAP2 only exists for AHCI V1.2 and later */
+      if ((i == HOST_CAP2) && (version < 0x00010200L)) val = 0;
+      else val = readl(ai->mmio + i);
+    }
+    if (i == HOST_VERSION) version = val;
+
+    dprintf(0," %02x: %08lx", i, val);
+
+    if (i == HOST_CAP) {
+      dprintf(0," -");
+      if (val & HOST_CAP_64)         dprintf(0," 64bit");
+      if (val & HOST_CAP_NCQ)        dprintf(0," ncq");
+      if (val & HOST_CAP_SNTF)       dprintf(0," sntf");
+      if (val & HOST_CAP_MPS)        dprintf(0," mps");
+      if (val & HOST_CAP_SSS)        dprintf(0," sss");
+      if (val & HOST_CAP_ALPM)       dprintf(0," alpm");
+      if (val & HOST_CAP_LED)        dprintf(0," led");
+      if (val & HOST_CAP_CLO)        dprintf(0," clo");
+      if (val & HOST_CAP_ONLY)       dprintf(0," ahci_only");
+      if (val & HOST_CAP_PMP)        dprintf(0," pmp");
+      if (val & HOST_CAP_FBS)        dprintf(0," fbs");
+      if (val & HOST_CAP_PIO_MULTI)  dprintf(0," pio_multi");
+      if (val & HOST_CAP_SSC)        dprintf(0," ssc");
+      if (val & HOST_CAP_PART)       dprintf(0," part");
+      if (val & HOST_CAP_CCC)        dprintf(0," ccc");
+      if (val & HOST_CAP_EMS)        dprintf(0," ems");
+      if (val & HOST_CAP_SXS)        dprintf(0," sxs");
+      dprintf(0," cmd_slots:%d", ((val >> 8) & 0x1f) + 1);
+      dprintf(0," ports:%d",     (val & 0x1f) + 1);
+    } else if (i == HOST_CTL) {
+      dprintf(0," -");
+      if (val & HOST_AHCI_EN)        dprintf(0," ahci_enabled");
+      if (val & HOST_IRQ_EN)         dprintf(0," irq_enabled");
+      if (val & HOST_RESET)          dprintf(0," resetting");
+    } else if (i == HOST_CAP2) {
+      dprintf(0," -");
+      if (val & HOST_CAP2_BOH)       dprintf(0," boh");
+      if (val & HOST_CAP2_NVMHCI)    dprintf(0," nvmhci");
+      if (val & HOST_CAP2_APST)      dprintf(0," apst");
+    }
+    dprintf(0,"\n");
+  }
+}
+
+void ahci_dump_port_regs(AD_INFO *ai, int p)
+{
+  u8 *port_mmio = port_base(ai, p);
+
+  dprintf(0,"AHCI port %d registers:\n", p);
+  dprintf(0,"  PORT_CMD       = 0x%x\n", readl(port_mmio + PORT_CMD));
+  dprintf(0," command engine status:\n");
+  dprintf(0,"  PORT_SCR_ACT   = 0x%x\n", readl(port_mmio + PORT_SCR_ACT));
+  dprintf(0,"  PORT_CMD_ISSUE = 0x%x\n", readl(port_mmio + PORT_CMD_ISSUE));
+  dprintf(0," link/device status:\n");
+  dprintf(0,"  PORT_SCR_STAT  = 0x%x\n", readl(port_mmio + PORT_SCR_STAT));
+  dprintf(0,"  PORT_SCR_CTL   = 0x%x\n", readl(port_mmio + PORT_SCR_CTL));
+  dprintf(0,"  PORT_SCR_ERR   = 0x%x\n", readl(port_mmio + PORT_SCR_ERR));
+  dprintf(0,"  PORT_TFDATA    = 0x%x\n", readl(port_mmio + PORT_TFDATA));
+  dprintf(0," interrupt status:\n");
+  dprintf(0,"  PORT_IRQ_STAT  = 0x%x\n", readl(port_mmio + PORT_IRQ_STAT));
+  dprintf(0,"  PORT_IRQ_MASK  = 0x%x\n", readl(port_mmio + PORT_IRQ_MASK));
+  dprintf(0,"  HOST_IRQ_STAT  = 0x%x\n", readl(ai->mmio + HOST_IRQ_STAT));
+}
+#endif
+
+/******************************************************************************
+ * setup the CAPS and other adapter information for this adapter.
+ *
+ * This function saves working copies of the CAP and CAP2 registers
+ * as well as the initial port map in the AD_INFO structure after
+ * removing features which are known to cause trouble on this specific
+ * piece of hardware.
+ */
+int ahci_config_caps(AD_INFO *ai)
+{
+  int ports;
+  int i;
+
+  ai->cap = readl(ai->mmio + HOST_CAP);
+  ai->port_map = readl(ai->mmio + HOST_PORTS_IMPL);
+
+  /* HOST_CAP2 only exists for AHCI V1.2 and later */
+  if (readl(ai->mmio + HOST_VERSION) >= 0x00010200L) ai->cap2 = readl(ai->mmio + HOST_CAP2);
+
+  if (ai->pci->board >= sizeof(initial_flags) / sizeof(*initial_flags))
+  {
+    dprintf(0,"error: invalid board index in PCI info\n");
+    return(-1);
+  }
+  ai->flags = initial_flags[ai->pci->board];
+  ai->hw_ports = (ai->cap & 0x1f) + 1;
+
+  if ((ai->cap & HOST_CAP_64) && (ai->flags & AHCI_HFLAG_32BIT_ONLY))
+  {
+    /* disable 64-bit support for faulty controllers; OS/2 can't do 64 bits at
+     * this point, of course, but who knows where all this will be in a few
+     * years...
+     */
+    ai->cap &= ~HOST_CAP_64;
+  }
+
+  /* Remove broken feature bits. This is largely copied from the Linux AHCI driver -- the wisdom
+   * around quirks and faulty hardware is hard to come by...
+   */
+  if ((ai->cap & HOST_CAP_NCQ) && (ai->flags & AHCI_HFLAG_NO_NCQ))
+  {
+    DPRINTF(1,"controller can't do NCQ, turning off CAP_NCQ\n");
+    ai->cap &= ~HOST_CAP_NCQ;
+  }
+
+  if (!(ai->cap & HOST_CAP_NCQ) && (ai->flags & AHCI_HFLAG_YES_NCQ))
+  {
+    DPRINTF(1,"controller can do NCQ, turning on CAP_NCQ\n");
+    ai->cap |= HOST_CAP_NCQ;
+  }
+
+  if ((ai->cap & HOST_CAP_PMP) && (ai->flags & AHCI_HFLAG_NO_PMP))
+  {
+    DPRINTF(1,"controller can't do PMP, turning off CAP_PMP\n");
+    ai->cap |= HOST_CAP_PMP;
+  }
+
+  if ((ai->cap & HOST_CAP_SNTF) && (ai->flags & AHCI_HFLAG_NO_SNTF))
+  {
+    DPRINTF(1,"controller can't do SNTF, turning off CAP_SNTF\n");
+    ai->cap &= ~HOST_CAP_SNTF;
+  }
+
+  if (ai->pci_vendor == PCI_VENDOR_ID_JMICRON && ai->pci_device == 0x2361 && ai->port_map != 1)
+  {
+    DPRINTF(1,"JMB361 has only one port, port_map 0x%x -> 0x%x\n", ai->port_map, 1);
+    ai->port_map = 1;
+    ai->hw_ports = 1;
+  }
+
+  /* Correlate port map to number of ports reported in HOST_CAP
+   *
+   * NOTE: Port map and number of ports handling differs a bit from the
+   *       Linux AHCI driver because we're storing both in AI_INFO. As in the
+   *       Linux driver, the port map is the main driver for port scanning but
+   *       we're also saving a maximum port number in AI_INFO to reduce the
+   *       number of IORB queues to look at in trigger_engine(). This is done
+   *       in ahci_scan_ports().
+   */
+  ports = ai->hw_ports;
+  for (i = 0; i < AHCI_MAX_PORTS; i++)
+  {
+    if (ai->port_map & (1UL << i)) ports--;
+  }
+  if (ports < 0)
+  {
+    /* more ports in port_map than in HOST_CAP & 0x1f */
+    ports = ai->hw_ports;
+    DPRINTF(1,"implemented port map (0x%x) contains more ports than hw_ports (%d), using hw_ports\n", ai->port_map, ports);
+    ai->port_map = (1UL << ports) - 1UL;
+  }
+
+  /* set maximum command slot number */
+  ai->cmd_max = ((ai->cap >> 8) & 0x1f);
+
+  return(0);
+}
+
+/******************************************************************************
+ * Save BIOS configuration of AHCI adapter. As a side effect, this also saves
+ * generic configuration information which we may have to restore after an
+ * adapter reset.
+ */
+int ahci_save_bios_config(AD_INFO *ai)
+{
+  int i;
+
+  /* save BIOS configuration */
+  for (i = 0; i < HOST_CAP2; i += sizeof(u32))
+  {
+    ai->bios_config[i / sizeof(u32)] = readl(ai->mmio + i);
+  }
+
+  DPRINTF(3,"ahci_save_bios_config: BIOS AHCI mode is %d\n", ai->bios_config[HOST_CTL / sizeof(u32)] & HOST_AHCI_EN);
+
+  if ((ai->bios_config[HOST_CTL / sizeof(u32)] & HOST_AHCI_EN) == 0 && ai->pci_vendor == PCI_VENDOR_ID_INTEL)
+  {
+    /* Adapter is not in AHCI mode and the spec says a COMRESET is
+     * required when switching from SATA to AHCI mode and vice versa.
+     */
+    init_reset = 1;
+  }
+
+  DUMP_HOST_REGS(2,ai,1);
+
+  return(0);
+}
+
+/******************************************************************************
+ * Restore BIOS configuration of AHCI adapter. This is needed after scanning
+ * for devices because we still need the BIOS until the initial boot sequence
+ * has completed.
+ */
+int ahci_restore_bios_config(AD_INFO *ai)
+{
+  DPRINTF(3,"ahci_restore_bios_config: restoring AHCI BIOS configuration on adapter %d\n", ad_no(ai));
+
+  /* Restore saved BIOS configuration; please note that HOST_CTL is restored
+   * last because it may cause AHCI mode to be turned off again.
+   */
+  writel(ai->mmio + HOST_CCC,       ai->bios_config[HOST_CCC / sizeof(u32)]);
+  writel(ai->mmio + HOST_CCC_PORTS, ai->bios_config[HOST_CCC_PORTS / sizeof(u32)]);
+  writel(ai->mmio + HOST_EM_CTL,    ai->bios_config[HOST_EM_CTL / sizeof(u32)]);
+  writel(ai->mmio + HOST_CTL,       ai->bios_config[HOST_CTL / sizeof(u32)]);
+
+  /* flush PCI MMIO delayed write buffers */
+  readl(ai->mmio + HOST_CTL);
+
+  if ((ai->bios_config[HOST_CTL / sizeof(u32)] & HOST_AHCI_EN) == 0 && ai->pci_vendor == PCI_VENDOR_ID_INTEL)
+  {
+    /* This BIOS apparently accesses the controller via SATA registers and
+     * the AHCI spec says that we should issue a COMRESET on each port after
+     * disabling AHCI mode to allow the SATA controller to re-recognize attached
+     * devices. How to do this depends on the controller, of course, but so
+     * far I've only seen Dell notebook BIOSs with Intel chipsets to behave
+     * like this; all other BIOS implementations I've seen so far seem to take
+     * AHCI mode literally and operate the controller in AHCI mode from the
+     * beginning.
+     *
+     * We'll use a feature on Intel ICH7/8 controllers which provides MMIO
+     * mappings for the AHCI SCR registers even when not in AHCI mode.
+     */
+    int p;
+
+    for (p = 0; p <= ai->port_max; p++)
+    {
+      if (ai->port_map & (1UL << p))
+      {
+        u8 *port_mmio = port_base(ai, p);
+        u32 tmp;
+
+        tmp = readl(port_mmio + PORT_SCR_CTL) & ~0x0000000fUL;
+        writel(port_mmio + PORT_SCR_CTL, tmp | 1);
+        readl(port_mmio + PORT_SCR_CTL);  /* flush */
+
+        /* spec says "leave reset bit on for at least 1ms"; make it 2ms */
+        udelay(2000);
+
+        writel(port_mmio + PORT_SCR_CTL, tmp);
+        readl(port_mmio + PORT_SCR_CTL);  /* flush */
+      }
+    }
+  }
+
+  return(0);
+}
+
+/******************************************************************************
+ * Restore initial configuration (e.g. after an adapter reset). This relies
+ * on information saved by 'ahci_save_bios_config()'.
+ */
+int ahci_restore_initial_config(AD_INFO *ai)
+{
+  DPRINTF(3,"ahci_restore_initial_config: restoring initial configuration on adapter %d\n", ad_no(ai));
+
+  /* restore saved BIOS configuration */
+  //writel(ai->mmio + HOST_CCC,       ai->bios_config[HOST_CCC / sizeof(u32)]);
+  //writel(ai->mmio + HOST_CCC_PORTS, ai->bios_config[HOST_CCC_PORTS / sizeof(u32)]);
+  //writel(ai->mmio + HOST_EM_CTL,    ai->bios_config[HOST_EM_CTL / sizeof(u32)]);
+  //writel(ai->mmio + HOST_CTL,       ai->bios_config[HOST_CTL / sizeof(u32)]);
+
+  writel(ai->mmio + HOST_CAP, ai->bios_config[HOST_CAP / sizeof(u32)]);
+  if (ai->bios_config[HOST_CAP2 / sizeof(u32)])
+    writel(ai->mmio + HOST_CAP2, ai->bios_config[HOST_CAP2 / sizeof(u32)]);
+  writel(ai->mmio + HOST_PORTS_IMPL, ai->bios_config[HOST_PORTS_IMPL / sizeof(u32)]);
+
+  /* flush PCI MMIO delayed write buffers */
+  readl(ai->mmio + HOST_PORTS_IMPL);
+
+  return(0);
+}
+
+#ifdef NOT_USED
+int ahci_reset_controller(AD_INFO *ai)
+{
+    u32 tmp;
+    TIMER Timer;
+
+    DPRINTF(2,"controller reset starting on adapter %d\n", ad_no(ai));
+
+    /* we must be in AHCI mode, before using anything AHCI-specific, such as HOST_RESET. */
+    ahci_enable_ahci(ai);
+
+    /* global controller reset */
+    tmp = readl(ai->mmio + HOST_CTL);
+    if ((tmp & HOST_RESET) == 0) {
+        writel(ai->mmio + HOST_CTL, tmp | HOST_RESET);
+        readl(ai->mmio + HOST_CTL); /* flush */
+    }
+
+    /*
+     * to perform host reset, OS should set HOST_RESET
+     * and poll until this bit is read to be "0".
+     * reset must complete within 1 second, or
+     * the hardware should be considered fried.
+     */
+    TimerInit(&Timer, 1000);
+    while (((tmp = readl(ai->mmio + HOST_CTL)) & HOST_RESET) != 0) {
+      if (TimerCheckAndBlock(&Timer)) {
+        dprintf(0,"controller reset failed (0x%x)\n", tmp);
+        return(-1);
+      }
+    }
+
+    /* turn on AHCI mode */
+    ahci_enable_ahci(ai);
+
+    /* Some registers might be cleared on reset.  Restore initial values. */
+    ahci_restore_initial_config(ai);
+
+    if (ai->pci_vendor == PCI_VENDOR_ID_INTEL) {
+      u32 tmp16 = 0;
+
+      DPRINTF(1,"ahci_reset_controller: intel detected\n");
+      /* configure PCS */
+      PciReadConfig(ai->bus, ai->dev_func, 0x92, sizeof(u16), &tmp16);
+      if ((tmp16 & ai->port_map) != ai->port_map) {
+        DPRINTF(3,"ahci_reset_controller: updating PCS %x/%x\n", tmp16, ai->port_map);
+        tmp16 |= ai->port_map;
+        PciWriteConfig(ai->bus, ai->dev_func, 0x92, sizeof(u16), tmp16);
+      }
+    }
+
+    return 0;
+}
+#endif
+
+/******************************************************************************
+ * Save port configuration. This is primarily used to save the BIOS port
+ * configuration (command list and FIS buffers and the IRQ mask).
+ *
+ * The port configuration returned by this function is dynamically allocated
+ * and automatically freed when calling ahci_restore_port_config().
+ */
+AHCI_PORT_CFG *ahci_save_port_config(AD_INFO *ai, int p)
+{
+  AHCI_PORT_CFG *pc;
+  u8 *port_mmio = port_base(ai, p);
+
+  if ((pc = MemAlloc(sizeof(*pc))) == NULL) return(NULL);
+
+  pc->cmd_list   = readl(port_mmio + PORT_LST_ADDR);
+  pc->cmd_list_h = readl(port_mmio + PORT_LST_ADDR_HI);
+  pc->fis_rx     = readl(port_mmio + PORT_FIS_ADDR);
+  pc->fis_rx_h   = readl(port_mmio + PORT_FIS_ADDR_HI);
+  pc->irq_mask   = readl(port_mmio + PORT_IRQ_MASK);
+  pc->port_cmd   = readl(port_mmio + PORT_CMD);
+
+  return(pc);
+}
+
+/******************************************************************************
+ * Restore port configuration. This is primarily used to restore the BIOS port
+ * configuration (command list and FIS buffers and the IRQ mask).
+ *
+ * The port configuration is automatically freed.
+ */
+void ahci_restore_port_config(AD_INFO *ai, int p, AHCI_PORT_CFG *pc)
+{
+  u8 *port_mmio = port_base(ai, p);
+
+  /* stop the port, first */
+  ahci_stop_port(ai, p);
+
+  if (ai->bios_config[HOST_CTL / sizeof(u32)] & HOST_AHCI_EN)
+  {
+    /* BIOS uses AHCI, too, so we need to restore the port settings;
+     * restoring PORT_CMD may well start the port again but that's what
+     * this function is all about.
+     */
+    writel(port_mmio + PORT_LST_ADDR,    pc->cmd_list);
+    writel(port_mmio + PORT_LST_ADDR_HI, pc->cmd_list_h);
+    writel(port_mmio + PORT_FIS_ADDR,    pc->fis_rx);
+    writel(port_mmio + PORT_FIS_ADDR_HI, pc->fis_rx_h);
+    writel(port_mmio + PORT_IRQ_MASK,    pc->irq_mask);
+    writel(port_mmio + PORT_CMD,         pc->port_cmd);
+
+    readl(port_base(ai, p) + PORT_IRQ_MASK); /* flush */
+  }
+
+  MemFree(pc);
+}
+
+/******************************************************************************
+ * Enable AHCI mode on this controller.
+ */
+int ahci_enable_ahci(AD_INFO *ai)
+{
+  u32 ctl = readl(ai->mmio + HOST_CTL);
+  int i;
+
+  if (ctl & HOST_AHCI_EN)
+  {
+    /* AHCI mode already enabled */
+    return(0);
+  }
+
+  /* some controllers need AHCI_EN to be written multiple times */
+  for (i = 0; i < 5; i++)
+  {
+    ctl |= HOST_AHCI_EN;
+    writel(ai->mmio + HOST_CTL, ctl);
+    ctl = readl(ai->mmio + HOST_CTL);   /* flush && sanity check */
+    if (ctl & HOST_AHCI_EN)
+    {
+      return(0);
+    }
+    msleep(10);
+  }
+
+  /* couldn't enable AHCI mode */
+  dprintf(0,"failed to enable AHCI mode on adapter %d\n", ad_no(ai));
+  return(1);
+}
+
+/******************************************************************************
+ * Scan all ports for connected devices and fill in the corresponding device
+ * information.
+ *
+ * NOTES:
+ *
+ *  - The adapter is temporarily configured for os2ahci but the original BIOS
+ *    configuration will be restored when done. This happens only until we
+ *    have received the IOCC_COMPLETE_INIT command.
+ *
+ *  - Subsequent calls are currently not planned but may be required for
+ *    suspend/resume handling, hot swap functionality, etc.
+ *
+ *  - This function is expected to be called with the spinlock released but
+ *    the corresponding adapter's busy flag set. It will aquire the spinlock
+ *    temporarily to allocate/free memory for the ATA identify buffer.
+ */
+int ahci_scan_ports(AD_INFO *ai)
+{
+  AHCI_PORT_CFG *pc = NULL;
+  u16 *id_buf;
+  int is_ata;
+  int rc;
+  int p;
+  int i;
+  TIMER Timer;
+
+  if ((id_buf = MemAlloc(ATA_ID_WORDS * sizeof(u16))) == NULL) return(-1);
+
+  if (ai->bios_config[0] == 0) ahci_save_bios_config(ai); /* first call */
+
+  if (ahci_enable_ahci(ai)) goto exit_port_scan;
+
+  /* perform port scan */
+  DPRINTF(1,"ahci_scan_ports: scanning ports on adapter %d\n", ad_no(ai));
+  for (p = 0; p < AHCI_MAX_PORTS; p++)
+  {
+    if (!(ai->port_map & (1UL << p))) continue;
+    if (port_ignore[ad_no(ai)][p]) continue;
+
+    // DAZ allocate port structure here
+
+    DPRINTF(3,"ahci_scan_ports: Wait till not busy on port %d\n", p);
+    /* wait until all active commands have completed on this port */
+    TimerInit(&Timer, 250);
+    while (ahci_port_busy(ai, p))
+    {
+      if (TimerCheckAndBlock(&Timer)) break;
+    }
+
+    if (!init_complete)
+    {
+      if ((pc = ahci_save_port_config(ai, p)) == NULL) goto exit_port_scan;
+    }
+
+    /* start/reset port; if no device is attached, this is expected to fail */
+    if (init_reset)
+    {
+      rc = ahci_reset_port(ai, p, 0);
+    }
+    else
+    {
+      DPRINTF(3,"ahci_scan_ports: (re)starting port %d\n", p);
+      ahci_stop_port(ai, p);
+      rc = ahci_start_port(ai, p, 0);
+    }
+
+    if (rc == 0)
+    {
+      /* this port seems to have a device attached and ready for commands */
+      DPRINTF(1,"ahci_scan_ports: port %d seems to be attached to a device; probing...\n", p);
+
+      #ifdef DAZ_NEW_CODE
+      ai->ports[p].dma_buf = MemAllocAlign(AHCI_PORT_PRIV_DMA_SZ, 1024);
+      ai->ports[p].dma_buf_phys = MemPhysAdr(ai->ports[p].dma_buf);
+      #endif
+
+      /* Get ATA(PI) identity. The so-called signature gives us a hint whether
+       * this is an ATA or an ATAPI device but we'll try both in either case;
+       * the signature will merely determine whether we're going to probe for
+       * an ATA or ATAPI device, first, in order to reduce the chance of sending
+       * the wrong command (which would result in a port reset given the way
+       * ahci_exec_polled_cmd() was implemented).
+       */
+      is_ata = readl(port_base(ai, p) + PORT_SIG) == 0x00000101UL;
+      for (i = 0; i < 2; i++)
+      {
+        rc = ahci_exec_polled_cmd(ai, p, 0, 500,
+                                  (is_ata) ? ATA_CMD_ID_ATA : ATA_CMD_ID_ATAPI,
+                                  AP_VADDR, (void *) id_buf, ATA_ID_WORDS * sizeof(u16),
+                                  AP_END);
+        if (rc == 0) break;
+
+        /* try again with ATA/ATAPI swapped */
+        is_ata = !is_ata;
+      }
+    }
+
+    if (rc == 0)
+    {
+      /* we have a valid IDENTIFY or IDENTIFY_PACKET response */
+      DHEXDUMP(2,id_buf, ATA_ID_WORDS * sizeof(u16), "ATA_IDENTIFY%s results:\n", (is_ata) ? "" : "_PACKET");
+      ahci_setup_device(ai, p, 0, id_buf);
+    }
+    else
+    {
+      /* no device attached to this port */
+      ai->port_map &= ~(1UL << p);
+      #ifdef DAZ_NEW_CODE
+      if (ai->ports[p].dma_buf) MemFree(ai->ports[p].dma_buf);
+      ai->ports[p].dma_buf = NULL;
+      #endif
+    }
+
+    if (pc != NULL) ahci_restore_port_config(ai, p, pc);
+  }
+
+exit_port_scan:
+  if (!init_complete)
+  {
+    ahci_restore_bios_config(ai);
+  }
+  MemFree(id_buf);
+  return(0);
+}
+
+/******************************************************************************
+ * Complete initialization of adapter. This includes restarting all active
+ * ports and initializing interrupt processing. This is called when receiving
+ * the IOCM_COMPLETE_INIT request.
+ */
+int ahci_complete_init(AD_INFO *ai)
+{
+  int rc;
+  u32 p;
+  int i;
+
+  DPRINTF(1,"ahci_complete_init: completing initialization of adapter #%d\n", ad_no(ai));
+
+  /* register IRQ handlers; each IRQ level is registered only once */
+  for (i = 0; i < irq_used_cnt; i++)
+  {
+    if (irq_used[i] == ai->irq) break; /* we already have this IRQ registered */
+  }
+  if (i >= irq_used_cnt)
+  {
+    if (i >= MAX_IRQ_HANDLERS) return -1; /* no more handlers available */
+    DPRINTF(2,"registering interrupt #%d\n", ai->irq);
+    if (Dev32Help_SetIRQ(ahci_intr, ai->irq, 1, ai->irq) != 0)
+    {
+      DPRINTF(0,"failed to register shared interrupt\n");
+      if (Dev32Help_SetIRQ(ahci_intr, ai->irq, 0, ai->irq) != 0)
+      {
+        dprintf(0,"failed to register interrupt %d\n", ai->irq);
+        return(-1);
+      }
+    }
+    irq_used[irq_used_cnt++] = ai->irq;
+  }
+
+  /* enable AHCI mode */
+  if ((rc = ahci_enable_ahci(ai)) != 0) return(rc);
+
+  /* Start all ports. The main purpose is to set the command list and FIS
+   * receive area addresses properly and to enable port-level interrupts; we
+   * don't really care about the return status because we'll find out soon
+   * enough if a previously detected device has problems.
+   */
+  for (p = 0; p <= ai->port_max; p++)
+  {
+    if (ai->port_map & (1UL << p))
+    {
+      if (init_reset)
+      {
+        DPRINTF(3,"ahci_complete_init: resetting port %d\n", p);
+        ahci_reset_port(ai, p, 1);
+      }
+      else
+      {
+        DPRINTF(3,"ahci_complete_init: restarting port #%d\n", p);
+        ahci_stop_port(ai, p);
+        ahci_start_port(ai, p, 1);
+      }
+    }
+  }
+
+  /* clear pending interrupt status */
+  writel(ai->mmio + HOST_IRQ_STAT, readl(ai->mmio + HOST_IRQ_STAT));
+  readl(ai->mmio + HOST_IRQ_STAT); /* flush */
+
+  /* enable adapter-level interrupts */
+  writel(ai->mmio + HOST_CTL, readl(ai->mmio + HOST_CTL) | HOST_IRQ_EN);
+  readl(ai->mmio + HOST_CTL); /* flush */
+
+  /* enable interrupts on PCI-level (PCI 2.3 added a feature to disable INTs) */
+  /* pci_enable_int(ai->bus, ai->dev_func); */
+
+  DPRINTF(1,"ahci_complete_init: done\n");
+  return(0);
+}
+
+/******************************************************************************
+ * Reset specified port. This function is typically called during adapter
+ * initialization and first gets the port into a defined status, then resets
+ * the port by sending a COMRESET signal.
+ *
+ * This function is also the location of the link speed initialization (link
+ * needs to be restablished after changing link speed, anyway).
+ *
+ * NOTE: This function uses a busy loop to wait for DMA engines to stop and
+ *       the COMRESET to complete. It should only be called at task time
+ *       during initialization or in a context hook.
+ */
+int ahci_reset_port(AD_INFO *ai, int p, int ei)
+{
+  u8 *port_mmio = port_base(ai, p);
+  u32 tmp;
+  TIMER Timer;
+
+  DPRINTF(2,"ahci_reset_port: resetting port %d.%d\n", ad_no(ai), p);
+  DUMP_PORT_REGS(2,ai,p);
+
+  /* stop port engines (we don't care whether there is an error doing so) */
+  ahci_stop_port(ai, p);
+
+  /* clear SError */
+  tmp = readl(port_mmio + PORT_SCR_ERR);
+  writel(port_mmio + PORT_SCR_ERR, tmp);
+
+  /* Some hardware reports incorrect status so just set these bits unconditionally */
+  tmp = readl(port_mmio + PORT_CMD);
+  tmp &= ~PORT_CMD_ALPE; /* turn off agressive power management */
+  tmp |= (PORT_CMD_SPIN_UP | PORT_CMD_POWER_ON); /* power up and spin up the drive */
+  writel(port_mmio + PORT_CMD, tmp);
+
+  /* set link speed and power management options */
+  DPRINTF(3,"ahci_reset_port: setting link speed and power management options\n");
+  tmp = readl(port_mmio + PORT_SCR_CTL) & ~0x00000fffUL;
+  tmp |= (link_speed[ad_no(ai)][p] & 0x0f) << 4;
+  tmp |= (link_power[ad_no(ai)][p] & 0x0f) << 8;
+  writel(port_mmio + PORT_SCR_CTL, tmp);
+
+  /* issue COMRESET on the port */
+  DPRINTF(3,"ahci_reset_port: issuing COMRESET on port %d\n", p);
+  writel(port_mmio + PORT_SCR_CTL, tmp | 1);
+  readl(port_mmio + PORT_SCR_CTL);  /* flush */
+
+  /* spec says "leave reset bit on for at least 1ms"; make it 2ms */
+  udelay(2000);
+
+  writel(port_mmio + PORT_SCR_CTL, tmp);
+  readl(port_mmio + PORT_SCR_CTL);  /* flush */
+
+  /* wait for communication to be re-established after port reset */
+  DPRINTF(2,"Wait for communication...\n");
+  TimerInit(&Timer, 500);
+  while (((tmp = readl(port_mmio + PORT_SCR_STAT)) & 3) != 3)
+  {
+    if (TimerCheckAndBlock(&Timer))
+    {
+      DPRINTF(0,"no device present after resetting port #%d (PORT_SCR_STAT = 0x%x)\n", p, tmp);
+      return(-1);
+    }
+  }
+
+  /* clear SError again (recommended by AHCI spec) */
+  tmp = readl(port_mmio + PORT_SCR_ERR);
+  writel(port_mmio + PORT_SCR_ERR, tmp);
+
+  /* start port so we can receive the COMRESET FIS */
+  DPRINTF(2,"ahci_reset_port: starting port %d again\n", p);
+  ahci_start_port(ai, p, ei);
+
+  /* wait for device to be ready ((PxTFD & (BSY | DRQ | ERR)) == 0) */
+  TimerInit(&Timer, 1000);
+  while (((tmp = readl(port_mmio + PORT_TFDATA)) & 0x89) != 0)
+  {
+    if (TimerCheckAndBlock(&Timer))
+    {
+      DPRINTF(0,"device not ready on port #%d (PORT_TFDATA = 0x%x)\n", p, tmp);
+      ahci_stop_port(ai, p);
+      return(-1);
+    }
+  }
+  DPRINTF(3,"ahci_reset_port: PORT_TFDATA = 0x%x\n", readl(port_mmio + PORT_TFDATA));
+
+  return(0);
+}
+
+/******************************************************************************
+ * Start specified port.
+ */
+int ahci_start_port(AD_INFO *ai, int p, int ei)
+{
+  u8 *port_mmio = port_base(ai, p);
+  u32 status;
+
+  DPRINTF(3,"ahci_start_port %d.%d\n", ad_no(ai), p);
+  /* check whether device presence is detected and link established */
+
+  status = readl(port_mmio + PORT_SCR_STAT);
+  DPRINTF(3,"ahci_start_port: PORT_SCR_STAT = 0x%x\n", status);
+  if ((status & 0xf) != 3) return(-1);
+
+  /* clear SError, if any */
+  status = readl(port_mmio + PORT_SCR_ERR);
+  DPRINTF(3,"ahci_start_port: PORT_SCR_ERR  = 0x%x\n", status);
+  writel(port_mmio + PORT_SCR_ERR, status);
+
+  /* enable FIS reception */
+  ahci_start_fis_rx(ai, p);
+
+  /* enable command engine */
+  ahci_start_engine(ai, p);
+
+  if (ei)
+  {
+    /* clear any pending interrupts on this port */
+    if ((status = readl(port_mmio + PORT_IRQ_STAT)) != 0)
+    {
+      writel(port_mmio + PORT_IRQ_STAT, status);
+    }
+
+    /* enable port interrupts */
+    writel(port_mmio + PORT_IRQ_MASK, PORT_IRQ_TF_ERR |
+                                      PORT_IRQ_HBUS_ERR |
+                                      PORT_IRQ_HBUS_DATA_ERR |
+                                      PORT_IRQ_IF_ERR |
+                                      PORT_IRQ_OVERFLOW |
+                                      PORT_IRQ_BAD_PMP |
+                                      PORT_IRQ_UNK_FIS |
+                                      PORT_IRQ_SDB_FIS |
+                                      PORT_IRQ_DMAS_FIS |
+                                      PORT_IRQ_PIOS_FIS |
+                                      PORT_IRQ_D2H_REG_FIS);
+  }
+  else
+  {
+    writel(port_mmio + PORT_IRQ_MASK, 0);
+  }
+  readl(port_mmio + PORT_IRQ_MASK);  /* flush */
+
+  return(0);
+}
+
+/******************************************************************************
+ * Start port FIS reception. Copied from Linux AHCI driver and adopted to
+ * OS2AHCI.
+ */
+void ahci_start_fis_rx(AD_INFO *ai, int p)
+{
+  u8 *port_mmio = port_base(ai, p);
+  u32 port_dma = port_dma_base_phys(ai, p);
+  u32 tmp;
+
+  /* set command header and FIS address registers */
+  writel(port_mmio + PORT_LST_ADDR, port_dma + offsetof(AHCI_PORT_DMA, cmd_hdr));
+  writel(port_mmio + PORT_LST_ADDR_HI, 0);
+  writel(port_mmio + PORT_FIS_ADDR, port_dma + offsetof(AHCI_PORT_DMA, rx_fis));
+  writel(port_mmio + PORT_FIS_ADDR_HI, 0);
+
+  /* enable FIS reception */
+  tmp = readl(port_mmio + PORT_CMD);
+  tmp |= PORT_CMD_FIS_RX;
+  writel(port_mmio + PORT_CMD, tmp);
+
+  /* flush */
+  readl(port_mmio + PORT_CMD);
+}
+
+/******************************************************************************
+ * Start port HW engine. Copied from Linux AHCI driver and adopted to OS2AHCI.
+ */
+void ahci_start_engine(AD_INFO *ai, int p)
+{
+  u8 *port_mmio = port_base(ai, p);
+  u32 tmp;
+
+  /* start DMA */
+  tmp = readl(port_mmio + PORT_CMD);
+  tmp |= PORT_CMD_START;
+  writel(port_mmio + PORT_CMD, tmp);
+  readl(port_mmio + PORT_CMD); /* flush */
+}
+
+/******************************************************************************
+ * Stop specified port
+ */
+int ahci_stop_port(AD_INFO *ai, int p)
+{
+  u8 *port_mmio = port_base(ai, p);
+  u32 tmp;
+  int rc;
+
+  DPRINTF(3,"ahci_stop_port %d.%d\n", ad_no(ai), p);
+
+  /* disable port interrupts */
+  writel(port_mmio + PORT_IRQ_MASK, 0);
+
+  /* disable FIS reception */
+  if ((rc = ahci_stop_fis_rx(ai, p)) != 0)
+  {
+    dprintf(0,"error: failed to stop FIS receive (%d)\n", rc);
+    return(rc);
+  }
+
+  /* disable command engine */
+  if ((rc = ahci_stop_engine(ai, p)) != 0)
+  {
+    dprintf(0,"error: failed to stop port HW engine (%d)\n", rc);
+    return(rc);
+  }
+
+  /* clear any pending port IRQs */
+  tmp = readl(port_mmio + PORT_IRQ_STAT);
+  if (tmp) writel(port_mmio + PORT_IRQ_STAT, tmp);
+  writel(ai->mmio + HOST_IRQ_STAT, 1UL << p);
+
+  /* reset PxSACT register (tagged command queues, not reset by COMRESET) */
+  writel(port_mmio + PORT_SCR_ACT, 0);
+  readl(port_mmio + PORT_SCR_ACT);  /* flush */
+
+  return(0);
+}
+
+/******************************************************************************
+ * Stop port FIS reception. Copied from Linux AHCI driver and adopted to
+ * OS2AHCI.
+ *
+ * NOTE: This function uses a busy loop to wait for the DMA engine to stop. It
+ *       should only be called at task time during initialization or in a
+ *       context hook (e.g. when resetting a port).
+ */
+int ahci_stop_fis_rx(AD_INFO *ai, int p)
+{
+  u8 *port_mmio = port_base(ai, p);
+  TIMER Timer;
+  u32 tmp;
+  int status;
+
+  /* disable FIS reception */
+  tmp = readl(port_mmio + PORT_CMD);
+  tmp &= ~PORT_CMD_FIS_RX;
+  writel(port_mmio + PORT_CMD, tmp);
+
+  /* wait for completion, spec says 500ms, give it 1000ms */
+  status = 0;
+  TimerInit(&Timer, 1000);
+  while (readl(port_mmio + PORT_CMD) & PORT_CMD_FIS_ON)
+  {
+    status = TimerCheckAndBlock(&Timer);
+    if (status) break;
+  }
+
+  return(status ? -1 : 0);
+}
+
+/******************************************************************************
+ * Stop port HW engine. Copied from Linux AHCI driver and adopted to OS2AHCI.
+ *
+ * NOTE: This function uses a busy loop to wait for the DMA engine to stop. It
+ *       should only be called at task time during initialization or in a
+ *       context hook (e.g. when resetting a port).
+ */
+int ahci_stop_engine(AD_INFO *ai, int p)
+{
+  u8 *port_mmio = port_base(ai, p);
+  TIMER Timer;
+  int status;
+  u32 tmp;
+
+  tmp = readl(port_mmio + PORT_CMD);
+
+  /* check if the port is already stopped */
+  if ((tmp & (PORT_CMD_START | PORT_CMD_LIST_ON)) == 0) return 0;
+
+  /* set port to idle */
+  tmp &= ~PORT_CMD_START;
+  writel(port_mmio + PORT_CMD, tmp);
+
+  /* wait for engine to stop. This could be as long as 500 msec */
+  status = 0;
+  TimerInit(&Timer, 500);
+  while (readl(port_mmio + PORT_CMD) & PORT_CMD_LIST_ON)
+  {
+    status = TimerCheckAndBlock(&Timer);
+    if (status) break;
+  }
+
+  return(status ? -1 : 0);
+}
+
+/******************************************************************************
+ * Determine whether a port is busy executing commands.
+ */
+int ahci_port_busy(AD_INFO *ai, int p)
+{
+  u8 *port_mmio = port_base(ai, p);
+
+  return(readl(port_mmio + PORT_SCR_ACT) != 0 || readl(port_mmio + PORT_CMD_ISSUE) != 0);
+}
+
+/******************************************************************************
+ * Execute AHCI command for given IORB. This includes all steps typically
+ * required by any of the ahci_*() IORB processing functions.
+ *
+ * NOTE: In order to prevent race conditions with port restart and reset
+ *       handlers, we either need to keep the spinlock during the whole
+ *       operation or set the adapter's busy flag. Since the expectation
+ *       is that command preparation will be quick (it certainly doesn't
+ *       involve delays), we're going with the spinlock for the time being.
+ */
+void ahci_exec_iorb(IORBH FAR16DATA *vIorb, IORBH *pIorb, int ncq_capable, int (*func)(IORBH FAR16DATA *, IORBH *pIorb, int))
+{
+  volatile u32 *cmds;
+  ADD_WORKSPACE *aws = add_workspace(pIorb);
+  AD_INFO *ai = &ad_infos[iorb_unit_adapter(pIorb)];
+  P_INFO *port = &ai->ports[iorb_unit_port(pIorb)];
+  ULONG timeout;
+  u8 *port_mmio = port_base(ai, iorb_unit_port(pIorb));
+  u16 cmd_max = ai->cmd_max;
+  int i;
+
+  /* determine timeout in milliseconds */
+  switch (pIorb->Timeout)
+  {
+  case 0:
+    timeout = DEFAULT_TIMEOUT;
+    break;
+  case 0xffffffffUL:
+    timeout = 0xffffffffUL;
+    break;
+  default:
+    timeout = pIorb->Timeout * 1000;
+    break;
+  }
+
+  DPRINTF(7,"---------- ahci_exec_iorb: iorb=%x\n", vIorb);
+
+  /* Enable AHCI mode; apparently, the AHCI mode may end up becoming
+   * disabled, either during the boot sequence (by the BIOS) or by
+   * something else. The Linux AHCI drivers have this call in the
+   * command processing chain, and apparently for a good reason because
+   * without this, commands won't be executed.
+   */
+  ahci_enable_ahci(ai);
+
+  /* determine whether this will be an NCQ request */
+  aws->is_ncq = 0;
+  if (ncq_capable && port->devs[iorb_unit_device(pIorb)].ncq_max > 1 &&
+      (ai->cap & HOST_CAP_NCQ) && !aws->no_ncq && init_complete)
+  {
+
+    /* We can make this an NCQ request; limit command slots to the maximum
+     * NCQ tag number reported by the device - 1. Why "minus one"? I seem to
+     * recall an issue related to using all 32 tag numbers but can't quite
+     * pinpoint it right now. One less won't make much of a difference...
+     */
+    aws->is_ncq = 1;
+    if ((cmd_max = port->devs[iorb_unit_device(pIorb)].ncq_max - 1) > ai->cmd_max)
+    {
+      cmd_max = ai->cmd_max;
+    }
+    DPRINTF(8,"NCQ command; cmd_max = %d->%d\n", ai->cmd_max, cmd_max);
+  }
+
+  /* make sure adapter is available */
+  spin_lock(drv_lock);
+  if (!ai->busy)
+  {
+
+    if (!init_complete)
+    {
+      /* no IRQ handlers or context hooks availabe at this point */
+      ai->busy = 1;
+      spin_unlock(drv_lock);
+      ahci_exec_polled_iorb(vIorb, pIorb, func, timeout);
+      ai->busy = 0;
+      return;
+    }
+
+    /* make sure we don't mix NCQ and regular commands */
+    if (aws->is_ncq && port->reg_cmds == 0 || !aws->is_ncq && port->ncq_cmds == 0)
+    {
+      /* Find next available command slot. We use a simple round-robin
+       * algorithm for this to prevent commands with higher slot indexes
+       * from stalling when new commands are coming in frequently.
+       */
+      cmds = (aws->is_ncq) ? &port->ncq_cmds : &port->reg_cmds;
+      for (i = 0; i <= cmd_max; i++)
+      {
+        if (++(port->cmd_slot) > cmd_max) port->cmd_slot = 0;
+        if ((*cmds & (1UL << port->cmd_slot)) == 0) break;
+      }
+
+      if ((*cmds & (1UL << port->cmd_slot)) == 0)
+      {
+        /* found idle command slot; prepare command */
+        if (func(vIorb, pIorb, port->cmd_slot))
+        {
+          /* Command preparation failed, or no HW command required; IORB
+           * will already have the error code if there was an error.
+           */
+          spin_unlock(drv_lock);
+          iorb_done(vIorb, pIorb);
+          return;
+        }
+
+        /* start timer for this IORB */
+        Timer_StartTimerMS(&aws->timer, timeout, timeout_callback, CastFar16ToULONG(vIorb));
+
+        /* issue command to hardware */
+        *cmds |= (1UL << port->cmd_slot);
+        aws->queued_hw = 1;
+        aws->cmd_slot = port->cmd_slot;
+
+        DPRINTF(7,"Issuing command Slot=%d cmds=%x\n", port->cmd_slot, *cmds);
+        if (aws->is_ncq)
+        {
+          writel(port_mmio + PORT_SCR_ACT, (1UL << port->cmd_slot));
+          readl(port_mmio + PORT_SCR_ACT); /* flush */
+        }
+        writel(port_mmio + PORT_CMD_ISSUE, (1UL << port->cmd_slot));
+        readl(port_mmio + PORT_CMD_ISSUE); /* flush */
+
+        spin_unlock(drv_lock);
+        return;
+      }
+    }
+  }
+
+  /* requeue this IORB; it will be picked up again in trigger_engine() */
+  aws->processing = 0;
+  spin_unlock(drv_lock);
+}
+
+/******************************************************************************
+ * Execute polled IORB command. This function is called by ahci_exec_iorb()
+ * when the initialization has not yet completed. The reasons for polling until
+ * initialization has completed are:
+ *
+ *  - We need to restore the BIOS configuration after we're done with this
+ *    command because someone might still call int 13h routines; sending
+ *    asynchronous commands and waiting for interrupts to indicate completion
+ *    won't work in such a scenario.
+ *  - Our context hooks won't work while the device managers are initializing
+ *    (they can't yield at init time).
+ *  - The device managers typically poll for command completion during
+ *    initialization so it won't make much of a difference, anyway.
+ *
+ * NOTE: This function must be called with the adapter-level busy flag set but
+ *       without the driver-level spinlock held.
+ */
+void ahci_exec_polled_iorb(IORBH FAR16DATA *vIorb, IORBH *pIorb, int (*func)(IORBH FAR16DATA *, IORBH *pIorb, int), ULONG timeout)
+{
+  AHCI_PORT_CFG *pc = NULL;
+  AD_INFO *ai = ad_infos + iorb_unit_adapter(vIorb);
+  int p = iorb_unit_port(pIorb);
+  u8 *port_mmio = port_base(ai, p);
+  TIMER Timer;
+  int rc;
+
+  /* enable AHCI mode */
+  if (ahci_enable_ahci(ai) != 0)
+  {
+    iorb_seterr(pIorb, IOERR_ADAPTER_NONSPECIFIC);
+    goto restore_bios_config;
+  }
+
+  /* check whether command slot 0 is available */
+  if ((readl(port_mmio + PORT_CMD_ISSUE) & 1) != 0)
+  {
+    iorb_seterr(pIorb, IOERR_DEVICE_BUSY);
+    goto restore_bios_config;
+  }
+
+  /* save port configuration */
+  if ((pc = ahci_save_port_config(ai, p)) == NULL)
+  {
+    iorb_seterr(pIorb, IOERR_CMD_SW_RESOURCE);
+    goto restore_bios_config;
+  }
+
+  /* restart/reset port (includes the necessary port configuration) */
+  if (init_reset)
+  {
+    /* As outlined in ahci_restore_bios_config(), switching back and
+     * forth between SATA and AHCI mode requires a COMRESET to force
+     * the corresponding controller subsystem to rediscover attached
+     * devices. Thus, we'll reset the port instead of stopping and
+     * starting it.
+     */
+    if (ahci_reset_port(ai, p, 0))
+    {
+      iorb_seterr(pIorb, IOERR_ADAPTER_NONSPECIFIC);
+      goto restore_bios_config;
+    }
+
+  }
+  else if (ahci_stop_port(ai, p) || ahci_start_port(ai, p, 0))
+  {
+    iorb_seterr(pIorb, IOERR_ADAPTER_NONSPECIFIC);
+    goto restore_bios_config;
+  }
+
+  /* prepare command */
+  if (func(vIorb, pIorb, 0) == 0)
+  {
+    /* successfully prepared cmd; issue cmd and wait for completion */
+    DPRINTF(3,"---------- executing polled cmd on slot 0...");
+    writel(port_mmio + PORT_CMD_ISSUE, 1);
+    TimerInit(&Timer, timeout);
+    while (readl(port_mmio + PORT_CMD_ISSUE) & 1)
+    {
+      rc = TimerCheckAndBlock(&Timer);
+      if (rc) break;
+    }
+
+    /* 0x89 = BSY(0x80) | DRQ(0x08) | ERR(0x01) */
+    if (rc)
+    {
+      DPRINTF(3," timeout for IORB %x", vIorb);
+      iorb_seterr(pIorb, IOERR_ADAPTER_TIMEOUT);
+    }
+    else if (readl(port_mmio + PORT_SCR_ERR) != 0 || readl(port_mmio + PORT_TFDATA) & 0x89)
+    {
+      DPRINTF(3," polled cmd error for IORB %x", vIorb);
+      iorb_seterr(pIorb, IOERR_DEVICE_NONSPECIFIC);
+      ahci_reset_port(ai, iorb_unit_port(pIorb), 0);
+    }
+    else
+    {
+      /* successfully executed command */
+      if (add_workspace(pIorb)->ppfunc != NULL)
+      {
+        add_workspace(pIorb)->ppfunc(vIorb, pIorb);
+      }
+      else
+      {
+        add_workspace(pIorb)->complete = 1;
+      }
+    }
+    DPRINTF(3,"\n");
+  }
+
+restore_bios_config:
+  /* restore BIOS configuration */
+  if (pc != NULL)
+  {
+    ahci_restore_port_config(ai, p, pc);
+  }
+  ahci_restore_bios_config(ai);
+
+  if (add_workspace(pIorb)->complete | (pIorb->Status | IORB_ERROR))
+  {
+    iorb_done(vIorb, pIorb);
+  }
+  return;
+}
+
+/******************************************************************************
+ * Execute polled ATA/ATAPI command. This function will block until the command
+ * has completed or the timeout has expired, thus it should only be used during
+ * initialization. Furthermore, it will always use command slot zero.
+ *
+ * The difference to ahci_exec_polled_iorb() is that this function executes
+ * arbitrary ATA/ATAPI commands outside the context of an IORB. It's typically
+ * used when scanning for devices during initialization.
+ */
+int ahci_exec_polled_cmd(AD_INFO *ai, int p, int d, int timeout, int cmd, ...)
+{
+  va_list va;
+  u8 *port_mmio = port_base(ai, p);
+  u32 tmp;
+  int rc;
+  TIMER Timer;
+
+  /* verify that command slot 0 is idle */
+  if (readl(port_mmio + PORT_CMD_ISSUE) & 1)
+  {
+    DPRINTF(3,"port %d slot 0 is not idle; not executing polled cmd\n", p);
+    return(-1);
+  }
+
+  /* fill in command slot 0 */
+  va_start(va, cmd);
+  if ((rc = v_ata_cmd(ai, p, d, 0, cmd, va)) != 0) return(rc);
+
+  /* start command execution for slot 0 */
+  DPRINTF(3,"---------- executing polled cmd...");
+  writel(port_mmio + PORT_CMD_ISSUE, 1);
+
+  /* wait until command has completed */
+  TimerInit(&Timer, timeout);
+  rc = 0;
+  while (readl(port_mmio + PORT_CMD_ISSUE) & 1)
+  {
+    rc = TimerCheckAndBlock(&Timer);
+    if (rc)
+    {
+      DPRINTF(2," Timeout");
+      break;
+    }
+  }
+
+  tmp = readl(port_mmio + PORT_SCR_ERR);
+  if (tmp & PORT_ERR_FAIL_BITS)
+  {
+    DPRINTF(2," SERR = 0x%08lx", tmp);
+    rc = 1;
+  }
+  /* 0x89 = BSY(0x80) | DRQ(0x08) | ERR(0x01) */
+  if (((tmp = readl(port_mmio + PORT_TFDATA)) & 0x89) != 0)
+  {
+    DPRINTF(2," TFDATA = 0x%08lx", tmp);
+    rc = 1;
+  }
+
+  if (rc)
+  {
+    DPRINTF(3,"failed\n");
+    ahci_reset_port(ai, p, 0);
+    return(-1);
+  }
+  DPRINTF(3,"success\n");
+  return(0);
+}
+
+/******************************************************************************
+ * Flush write cache of the specified device. Since there's no equivalent IORB
+ * command, we'll execute this command directly using polling. Otherwise, we
+ * would have to create a fake IORB, add it to the port's IORB queue, ...
+ *
+ * Besides, this function is only called when shutting down and the code there
+ * would have to wait for the flush cache command to complete as well, using
+ * polling just the same...
+ */
+int ahci_flush_cache(AD_INFO *ai, int p, int d)
+{
+  if (!ai->ports[p].devs[d].atapi)
+  {
+    DPRINTF(2,"flushing cache on %d.%d.%d\n", ad_no(ai), p, d);
+    return(ahci_exec_polled_cmd(ai, p, d, 30000,
+           ai->ports[p].devs[d].lba48 ? ATA_CMD_FLUSH_EXT : ATA_CMD_FLUSH, AP_END));
+  }
+  return 0;
+}
+
+/******************************************************************************
+ * Set device into IDLE mode (spin down); this was used during
+ * debugging/testing and is now unused; it's still there in case we need it
+ * again...
+ *
+ * If 'idle' is != 0, the idle timeout is set to 5 seconds, otherwise it
+ * is turned off.
+ */
+int ahci_set_dev_idle(AD_INFO *ai, int p, int d, int idle)
+{
+  DPRINTF(3,"sending IDLE=%d command to port %d\n", idle, p);
+  return ahci_exec_polled_cmd(ai, p, d, 500, ATA_CMD_IDLE, AP_COUNT, idle ? 1 : 0, AP_END);
+}
+
+/******************************************************************************
+ * AHCI top-level hardware interrupt handler. This handler finds the adapters
+ * and ports which have issued the interrupt and calls the corresponding
+ * port interrupt handler.
+ *
+ * On entry, OS/2 will have processor interrupts enabled because we're using
+ * shared IRQs but we won't be preempted by another interrupt on the same
+ * IRQ level until we indicated EOI. We'll keep it this way, only requesting
+ * the driver-level spinlock when actually changing the driver state (IORB
+ * queues, ...)
+ */
+#pragma aux ahci_intr parm [eax]
+int ahci_intr(u16 irq)
+{
+  u32 irq_stat;
+  int handled = 0;
+  int a;
+  int p;
+
+  /* find adapter(s) with pending interrupts */
+  for (a = 0; a < ad_info_cnt; a++)
+  {
+    AD_INFO *ai = ad_infos + a;
+
+    if (ai->irq == irq && (irq_stat = readl(ai->mmio + HOST_IRQ_STAT)) != 0)
+    {
+      /* this adapter has interrupts pending */
+      u32 irq_masked = irq_stat & ai->port_map;
+
+      for (p = 0; p <= ai->port_max; p++)
+      {
+        if (irq_masked & (1UL << p))
+        {
+          ahci_port_intr(ai, p);
+        }
+      }
+
+      /* clear interrupt condition on the adapter */
+      writel(ai->mmio + HOST_IRQ_STAT, irq_stat);
+      readl(ai->mmio + HOST_IRQ_STAT); /* flush */
+      handled = 1;
+    }
+  }
+
+  if (handled)
+  {
+    /* 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 0
+    if ((u32)&irq_stat < 0xf000)
+    {
+      DPRINTF(0,"IRQ stack running low; arming engine context hook\n");
+      /* Rousseau:
+       * A context hook cannot be re-armed before it has completed.
+       * (?:\IBMDDK\DOCS\PDDREF.INF->Device Helper (DevHlp) Services)->ArmCtxHook)
+       * Also, it is executed at task-time, thus in the context of some
+       * application thread. Stacked interrupts with a stack below the
+       * threshold specified above, (0xf000), will repeatly try to arm the
+       * context hook, but since we are in an interrupted interrupt handler,
+       * it's highly unlikely the hook has completed.
+       * So, possibly only the first arming is succesful and subsequent armings
+       * will fail because no task-time thread has run between the stacked
+       * interrupts. One hint would be that if the dispatching truely worked,
+       * excessive stacked interrupts in VBox would not be a problem.
+       * This needs some more investigation.
+       */
+      KernArmHook(engine_ctxhook_h, 0, 0);
+    }
+    else
+    #endif
+    {
+      spin_lock(drv_lock);
+      trigger_engine();
+      spin_unlock(drv_lock);
+    }
+    DevCli();
+    Dev32Help_EOI(irq);
+    return(1); /* handled */
+  }
+
+  return(0); /* not handled */
+}
+
+/******************************************************************************
+ * AHCI port-level interrupt handler. As described above, processor interrupts
+ * are enabled on entry thus we have to protect shared resources with a
+ * spinlock.
+ */
+void ahci_port_intr(AD_INFO *ai, int p)
+{
+  IORB_QUEUE done_queue;
+  IORBH FAR16DATA *vIorb;
+  IORBH FAR16DATA *vNext = NULL;
+  u8 *port_mmio = port_base(ai, p);
+  u32 irq_stat;
+  u32 active_cmds;
+  u32 done_mask;
+
+  /* get interrupt status and clear it right away */
+  irq_stat = readl(port_mmio + PORT_IRQ_STAT);
+  writel(port_mmio + PORT_IRQ_STAT, irq_stat);
+  readl(port_mmio + PORT_IRQ_STAT); /* flush */
+
+  memset(&done_queue, 0x00, sizeof(done_queue));
+
+  if (irq_stat & PORT_IRQ_ERROR)
+  {
+    /* this is an error interrupt;
+     * disable port interrupts to avoid IRQ storm until error condition
+     * has been cleared by the restart handler
+     */
+    writel(port_mmio + PORT_IRQ_MASK, 0);
+    ahci_error_intr(ai, p, irq_stat);
+    return;
+  }
+
+  spin_lock(drv_lock);
+
+  /* Find out which command slots have completed. Since error recovery for
+   * NCQ commands interfers with non-NCQ commands, the upper layers will
+   * make sure there's never a mixture of NCQ and non-NCQ commands active
+   * on any port at any given time. This makes it easier to find out which
+   * commands have completed, too.
+   */
+  if (ai->ports[p].ncq_cmds != 0)
+  {
+    active_cmds = readl(port_mmio + PORT_SCR_ACT);
+    done_mask = ai->ports[p].ncq_cmds ^ active_cmds;
+    DPRINTF(7,"[ncq_cmds]: active_cmds=0x%08x done_mask=0x%08x\n", active_cmds, done_mask);
+  }
+  else
+  {
+    active_cmds = readl(port_mmio + PORT_CMD_ISSUE);
+    done_mask = ai->ports[p].reg_cmds ^ active_cmds;
+    DPRINTF(7,"[reg_cmds]: active_cmds=0x%08x  done_mask=0x%08x\n", active_cmds, done_mask);
+  }
+
+  /* Find the IORBs related to the completed commands and complete them.
+   *
+   * NOTES: The spinlock must not be released while in this loop to prevent
+   *        race conditions with timeout handlers or other threads in SMP
+   *        systems.
+   *
+   *        Since we hold the spinlock when IORBs complete, we can't call the
+   *        IORB notification routine right away because this routine might
+   *        schedule another IORB which could cause a deadlock. Thus, we'll
+   *        add all IORBs to be completed to a temporary queue which will be
+   *        processed after releasing the spinlock.
+   */
+  for (vIorb = ai->ports[p].iorb_queue.vRoot; vIorb != NULL; vIorb = vNext)
+  {
+    IORBH *pIorb = Far16ToFlat(vIorb);
+    ADD_WORKSPACE *aws = (ADD_WORKSPACE *) &pIorb->ADDWorkSpace;
+
+    vNext = pIorb->pNxtIORB;
+    if (aws->queued_hw && (done_mask & (1UL << aws->cmd_slot)))
+    {
+      /* this hardware command has completed */
+      ai->ports[p].ncq_cmds &= ~(1UL << aws->cmd_slot);
+      ai->ports[p].reg_cmds &= ~(1UL << aws->cmd_slot);
+
+      /* call post-processing function, if any */
+      if (aws->ppfunc != NULL) aws->ppfunc(vIorb, pIorb);
+      else aws->complete = 1;
+
+      if (aws->complete)
+      {
+        /* this IORB is complete; move IORB to our temporary done queue */
+        iorb_queue_del(&ai->ports[p].iorb_queue, vIorb);
+        iorb_queue_add(&done_queue, vIorb, pIorb);
+        aws_free(add_workspace(pIorb));
+      }
+    }
+  }
+
+  spin_unlock(drv_lock);
+
+  /* complete all IORBs in the done queue */
+  for (vIorb = done_queue.vRoot; vIorb != NULL; vIorb = vNext)
+  {
+    IORBH *pIorb = Far16ToFlat(vIorb);
+
+    vNext = pIorb->pNxtIORB;
+    iorb_complete(vIorb, pIorb);
+  }
+}
+
+/******************************************************************************
+ * AHCI error interrupt handler. Errors include interface errors and device
+ * errors (usually triggered by the error bit in the AHCI task file register).
+ *
+ * Since this involves long-running operations such as restarting or even
+ * resetting a port, this function is invoked at task time via a context
+ * hook.
+ *
+ * NOTE: AHCI controllers stop all processing when encountering an error
+ *       condition in order to give the driver time to find out what exactly
+ *       went wrong. This means no new commands will be processed until we
+ *       clear the error register and restore the "commands issued" register.
+ */
+void ahci_error_intr(AD_INFO *ai, int p, u32 irq_stat)
+{
+  int reset_port = 0;
+
+  /* Handle adapter and interface errors. Those typically require a port
+   * reset, or worse.
+   */
+  ai->ports[p].error_count++;
+
+  if (irq_stat & PORT_IRQ_UNK_FIS)
+  {
+    #ifdef DEBUG
+    u32 *unk = (u32 *) (port_dma_base(ai, p)->rx_fis + RX_FIS_UNK);
+    DPRINTF(0,"warning: unknown FIS %08lx %08lx %08lx %08lx\n", unk[0], unk[1], unk[2], unk[3]);
+    #endif
+    reset_port = 1;
+  }
+  if (irq_stat & (PORT_IRQ_HBUS_ERR | PORT_IRQ_HBUS_DATA_ERR))
+  {
+    dprintf(0,"warning: host bus [data] error for port #%d\n", p);
+    reset_port = 1;
+  }
+  if (irq_stat & PORT_IRQ_IF_ERR && !(ai->flags & AHCI_HFLAG_IGN_IRQ_IF_ERR))
+  {
+    dprintf(0,"warning: interface fatal error for port #%d\n", p);
+    reset_port = 1;
+  }
+  if (reset_port)
+  {
+    /* need to reset the port; leave this to the reset context hook */
+
+    ports_to_reset[ad_no(ai)] |= 1UL << p;
+    KernArmHook(reset_ctxhook_h, 0, 0);
+
+    /* no point analyzing device errors after a reset... */
+    return;
+  }
+
+  dprintf(0,"port #%d interrupt error status: 0x%08lx; restarting port\n", p, irq_stat);
+
+  /* Handle device-specific errors. Those errors typically involve restarting
+   * the corresponding port to resume operations which can take some time,
+   * thus we need to offload this functionality to the restart context hook.
+   */
+  ports_to_restart[ad_no(ai)] |= 1UL << p;
+  KernArmHook(restart_ctxhook_h, 0, 0);
+}
+
+/******************************************************************************
+ * Get device or media geometry. Device and media geometry are expected to be
+ * the same for non-removable devices.
+ */
+void ahci_get_geometry(IORBH FAR16DATA *vIorb, IORBH *pIorb)
+{
+  DPRINTF(7,"ahci_get_geometry(%d.%d.%d)\n", iorb_unit_adapter(pIorb),
+          iorb_unit_port(pIorb), iorb_unit_device(pIorb));
+
+  ahci_exec_iorb(vIorb, pIorb, 0, cmd_func(pIorb, get_geometry));
+}
+
+/******************************************************************************
+ * Test whether unit is ready.
+ */
+void ahci_unit_ready(IORBH FAR16DATA *vIorb, IORBH *pIorb)
+{
+  DPRINTF(7,"ahci_unit_ready(%d.%d.%d)\n", iorb_unit_adapter(pIorb),
+          iorb_unit_port(pIorb), iorb_unit_device(pIorb));
+
+  ahci_exec_iorb(vIorb, pIorb, 0, cmd_func(pIorb, unit_ready));
+}
+
+/******************************************************************************
+ * Read sectors from AHCI device.
+ */
+void ahci_read(IORBH FAR16DATA *vIorb, IORBH *pIorb)
+{
+  DPRINTF(7,"ahci_read(%d.%d.%d, %d, %d)\n", iorb_unit_adapter(vIorb),
+          iorb_unit_port(pIorb), iorb_unit_device(pIorb),
+          ((IORB_EXECUTEIO *) pIorb)->RBA,
+          ((IORB_EXECUTEIO *) pIorb)->BlockCount);
+
+  ahci_exec_iorb(vIorb, pIorb, 1, cmd_func(pIorb, read));
+}
+
+/******************************************************************************
+ * Verify readability of sectors on AHCI device.
+ */
+void ahci_verify(IORBH FAR16DATA *vIorb, IORBH *pIorb)
+{
+  DPRINTF(7,"ahci_verify(%d.%d.%d, %d, %d)\n", iorb_unit_adapter(pIorb),
+          iorb_unit_port(pIorb), iorb_unit_device(pIorb),
+          ((IORB_EXECUTEIO *)pIorb)->RBA,
+          ((IORB_EXECUTEIO *)pIorb)->BlockCount);
+
+  ahci_exec_iorb(vIorb, pIorb, 0, cmd_func(pIorb, verify));
+}
+
+/******************************************************************************
+ * Write sectors to AHCI device.
+ */
+void ahci_write(IORBH FAR16DATA *vIorb, IORBH *pIorb)
+{
+  DPRINTF(7,"ahci_write(%d.%d.%d, %d, %d)\n", iorb_unit_adapter(pIorb),
+          iorb_unit_port(pIorb), iorb_unit_device(pIorb),
+          ((IORB_EXECUTEIO *)pIorb)->RBA,
+          ((IORB_EXECUTEIO *)pIorb)->BlockCount);
+
+  ahci_exec_iorb(vIorb, pIorb, 1, cmd_func(pIorb, write));
+}
+
+/******************************************************************************
+ * Execute SCSI (ATAPI) command.
+ */
+void ahci_execute_cdb(IORBH FAR16DATA *vIorb, IORBH *pIorb)
+{
+  int a = iorb_unit_adapter(pIorb);
+  int p = iorb_unit_port(pIorb);
+  int d = iorb_unit_device(pIorb);
+
+  DHEXDUMP(0,Far16ToFlat(((IORB_ADAPTER_PASSTHRU *)pIorb)->pControllerCmd),
+        ((IORB_ADAPTER_PASSTHRU *)pIorb)->ControllerCmdLen,
+        "ahci_execute_cdb(%d.%d.%d): ", a, p, d);
+
+  if (ad_infos[a].ports[p].devs[d].atapi)
+  {
+    ahci_exec_iorb(vIorb, pIorb, 0, atapi_execute_cdb);
+  }
+  else
+  {
+    iorb_seterr(pIorb, IOERR_CMD_NOT_SUPPORTED);
+    iorb_done(vIorb, pIorb);
+  }
+}
+
+/******************************************************************************
+ * Execute ATA command. Please note that this is allowed for both ATA and
+ * ATAPI devices because ATAPI devices will process some ATA commands as well.
+ */
+void ahci_execute_ata(IORBH FAR16DATA *vIorb, IORBH *pIorb)
+{
+  #ifdef DEBUG
+  int a = iorb_unit_adapter(pIorb);
+  int p = iorb_unit_port(pIorb);
+  int d = iorb_unit_device(pIorb);
+
+  DHEXDUMP(0,Far16ToFlat(((IORB_ADAPTER_PASSTHRU *)pIorb)->pControllerCmd),
+        ((IORB_ADAPTER_PASSTHRU *)pIorb)->ControllerCmdLen,
+        "ahci_execute_ata(%d.%d.%d): ", a, p, d);
+  #endif
+
+  ahci_exec_iorb(vIorb, pIorb, 0, ata_execute_ata);
+}
+
+/******************************************************************************
+ * Set up device attached to the specified port based on ATA_IDENTFY_DEVICE or
+ * ATA_IDENTFY_PACKET_DEVICE data.
+ *
+ * NOTE: Port multipliers are not supported, yet, thus the device number is
+ *       expected to be 0 for the time being.
+ */
+static void ahci_setup_device(AD_INFO *ai, int p, int d, u16 *id_buf)
+{
+  DEVICESTRUCT ds;
+  ADJUNCT adj;
+  HDEVICE dh;
+  char dev_name[RM_MAX_PREFIX_LEN+ATA_ID_PROD_LEN+1];
+  char *pDevName;
+  static u8 total_dev_cnt;
+
+  if (p >= AHCI_MAX_PORTS) return;
+  if (d >= AHCI_MAX_DEVS) return;
+
+  if (ai->port_max < p) ai->port_max = p;
+  if (ai->ports[p].dev_max < d) ai->ports[p].dev_max = d;
+  memset(ai->ports[p].devs + d, 0x00, sizeof(*ai->ports[p].devs));
+
+  /* set generic device information (assuming an ATA disk device for now) */
+  ai->ports[p].devs[d].present   = 1;
+  ai->ports[p].devs[d].removable = (id_buf[ATA_ID_CONFIG] & 0x0080U) != 0;
+  ai->ports[p].devs[d].dev_type  = UIB_TYPE_DISK;
+  pDevName = ai->ports[p].devs[d].dev_name;
+  strncpy(pDevName, ata_dev_name(id_buf), sizeof(ai->ports[0].devs[0].dev_name));
+
+  if (id_buf[ATA_ID_CONFIG] & 0x8000U)
+  {
+    /* this is an ATAPI device; augment device information */
+    ai->ports[p].devs[d].atapi     = 1;
+    ai->ports[p].devs[d].atapi_16  = (id_buf[ATA_ID_CONFIG] & 0x0001U) != 0;
+    ai->ports[p].devs[d].dev_type  = (id_buf[ATA_ID_CONFIG] & 0x1f00U) >> 8;
+    ai->ports[p].devs[d].ncq_max   = 1;
+
+  }
+  else
+  {
+    /* complete ATA-specific device information */
+    if (enable_ncq[ad_no(ai)][p])
+    {
+      ai->ports[p].devs[d].ncq_max = id_buf[ATA_ID_QUEUE_DEPTH] & 0x001fU;
+    }
+    if (ai->ports[p].devs[d].ncq_max < 1)
+    {
+      /* NCQ not enabled for this device, or device doesn't support NCQ */
+      ai->ports[p].devs[d].ncq_max = 1;
+    }
+    if (id_buf[ATA_ID_CFS_ENABLE_2] & 0x0400U)
+    {
+      ai->ports[p].devs[d].lba48   = 1;
+    }
+  }
+
+  DPRINTF(2,"found device %d.%d.%d: removable = %d, dev_type = %d, atapi = %d, "
+          "ncq_max = %d\n", ad_no(ai), p, d,
+          ai->ports[p].devs[d].removable,
+          ai->ports[p].devs[d].dev_type,
+          ai->ports[p].devs[d].atapi,
+          ai->ports[p].devs[d].ncq_max);
+
+  /* add device to resource manager; we don't really care about errors here */
+  memset(&ds, 0x00, sizeof(ds));
+  memset(&adj, 0x00, sizeof(adj));
+
+  adj.pNextAdj            = NULL;
+  adj.AdjLength           = sizeof(adj);
+  adj.AdjType             = ADJ_ADD_UNIT;
+  adj.Add_Unit.ADDHandle  = rm_drvh;
+  adj.Add_Unit.UnitHandle = (USHORT)total_dev_cnt;
+
+  /* create Resource Manager device key string;
+   * we distinguish only HDs and CD drives for now
+   */
+  if (ai->ports[p].devs[d].removable)
+  {
+    sprintf(dev_name, RM_CD_PREFIX "%s", p, d, pDevName);
+  }
+  else
+  {
+    sprintf(dev_name, RM_HD_PREFIX "%s", p, d, pDevName);
+  }
+
+  ds.DevDescriptName = dev_name;
+  ds.DevFlags        = (ai->ports[p].devs[d].removable) ? DS_REMOVEABLE_MEDIA
+                                                        : DS_FIXED_LOGICALNAME;
+  ds.DevType         = ai->ports[p].devs[d].dev_type;
+  ds.pAdjunctList    = &adj;
+
+  RMCreateDevice(rm_drvh, &dh, &ds, ai->rm_adh, NULL);
+
+  total_dev_cnt++;
+
+  /* try to detect virtualbox environment to enable a hack for IRQ routing */
+  if (ai == ad_infos && ai->pci_vendor == 0x8086 && ai->pci_device == 0x2829 &&
+      !memcmp(pDevName, "VBOX HARDDISK", 13))
+  {
+    /* running inside virtualbox */
+    pci_hack_virtualbox();
+  }
+}
+
Index: /tags/2.02/src/os2ahci/ahci.h
===================================================================
--- /tags/2.02/src/os2ahci/ahci.h	(revision 189)
+++ /tags/2.02/src/os2ahci/ahci.h	(revision 189)
@@ -0,0 +1,310 @@
+/******************************************************************************
+ * ahci.h - AHCI-specific constants for os2ahci.h
+ *
+ * Copyright (c) 2011 thi.guten Software Development
+ * Copyright (c) 2011 Mensys B.V.
+ * Copyright (c) 2013-2016 David Azarewicz
+ *
+ * Authors: Christian Mueller, Markus Thielen
+ *
+ * Parts copied from/inspired by the Linux AHCI driver;
+ * those parts are (c) Linux AHCI/ATA maintainers
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+/* ----------------------------- include files ----------------------------- */
+
+/* -------------------------- macros and constants ------------------------- */
+
+/******************************************************************************
+ *  device prefix strings for Resource Manager
+ */
+#define RM_HD_PREFIX         "HD_(%d,%d) "
+#define RM_HD_PREFIX_LEN     (sizeof(RM_HD_PREFIX) - 1)
+#define RM_CD_PREFIX         "CD_(%d,%d) "
+#define RM_CD_PREFIX_LEN     (sizeof(RM_CD_PREFIX) - 1)
+#define RM_TAPE_PREFIX       "TAPE_(%d,%d) "
+#define RM_TAPE_PREFIX_LEN   (sizeof(RM_TAPE_PREFIX) - 1)
+#define RM_MAX_PREFIX_LEN    RM_TAPE_PREFIX_LEN
+
+/******************************************************************************
+ * AHCI flags and constants; those were initially copied from the Linux AHCI
+ * driver but converted to macros because enums are 16 bits for OS/2 drivers
+ * (unless we use KEE and a 32-bit compiler, which we don't)
+ *
+ * Changes from the Linux source:
+ *
+ *  - reduced AHCI_MAX_SG from 168 to 48 because the port-specific DMA scratch
+ *    buffer needs to be less than 64K to allow mapping the whole DMA area to a
+ *    16-bit memory segment
+ *
+ *  - added AHCI_MAX_SG_ELEMENT_LEN constant
+ *
+ *  - replaced much of the top-level size/offset math with real structs and
+ *    corresponding sizeof() directives.
+ */
+#define AHCI_PCI_BAR            5
+#define AHCI_MAX_PORTS          16 /* Spec says 32, but we only support 16 */
+#define AHCI_MAX_DEVS           1 /* was 8. we only support 1 drive per port */
+#define AHCI_MAX_SG             48 /* hardware max is 64K */
+#define AHCI_MAX_SG_ELEMENT_LEN (1UL << 22)
+#define AHCI_MAX_CMDS           32
+#define AHCI_RX_FIS_SZ          256
+#define AHCI_DEV_NAME_LEN       40
+
+/* port-specific DMA scratch buffer aligned to 1024 bytes */
+//DAZ #define AHCI_PORT_PRIV_DMA_SZ   (((sizeof(AHCI_PORT_DMA) + 1023U) / 1024U) * 1024U)
+#define AHCI_PORT_PRIV_DMA_SZ   sizeof(AHCI_PORT_DMA)
+
+#define AHCI_IRQ_ON_SG          (1UL << 31)
+#define AHCI_CMD_ATAPI          (1UL << 5)
+#define AHCI_CMD_WRITE          (1UL << 6)
+#define AHCI_CMD_PREFETCH       (1UL << 7)
+#define AHCI_CMD_RESET          (1UL << 8)
+#define AHCI_CMD_CLR_BUSY       (1UL << 10)
+
+#define RX_FIS_D2H_REG          0x40 /* offset of D2H Register FIS data */
+#define RX_FIS_SDB              0x58 /* offset of SDB FIS data */
+#define RX_FIS_UNK              0x60 /* offset of Unknown FIS data */
+
+#define board_ahci              0
+#define board_ahci_vt8251       1
+#define board_ahci_ign_iferr    2
+#define board_ahci_sb600        3
+#define board_ahci_mv           4
+#define board_ahci_sb700        5 /* for SB700 and SB800 */
+#define board_ahci_mcp65        6
+#define board_ahci_nopmp        7
+#define board_ahci_yesncq       8
+#define board_ahci_nosntf       9
+
+/* global controller registers */
+#define HOST_CAP                0x00 /* host capabilities */
+#define HOST_CTL                0x04 /* global host control */
+#define HOST_IRQ_STAT           0x08 /* interrupt status */
+#define HOST_PORTS_IMPL         0x0c /* bitmap of implemented ports */
+#define HOST_VERSION            0x10 /* AHCI spec. version compliancy */
+#define HOST_CCC                0x14 /* Command Completion Coalescing Control */
+#define HOST_CCC_PORTS          0x18 /* CCC ports */
+#define HOST_EM_LOC             0x1c /* Enclosure Management location */
+#define HOST_EM_CTL             0x20 /* Enclosure Management Control */
+#define HOST_CAP2               0x24 /* host capabilities, extended */
+#define HOST_BOHC               0x28 /* BIOS hand off control and status */
+
+/* HOST_CTL bits */
+#define HOST_RESET              (1UL << 0)  /* reset controller; self-clear */
+#define HOST_IRQ_EN             (1UL << 1)  /* global IRQ enable */
+#define HOST_AHCI_EN            (1UL << 31) /* AHCI enabled */
+
+/* HOST_CAP bits */
+#define HOST_CAP_SXS            (1UL << 5)  /* Supports External SATA */
+#define HOST_CAP_EMS            (1UL << 6)  /* Enclosure Management support */
+#define HOST_CAP_CCC            (1UL << 7)  /* Command Completion Coalescing */
+#define HOST_CAP_PART           (1UL << 13) /* Partial state capable */
+#define HOST_CAP_SSC            (1UL << 14) /* Slumber state capable */
+#define HOST_CAP_PIO_MULTI      (1UL << 15) /* PIO multiple DRQ support */
+#define HOST_CAP_FBS            (1UL << 16) /* FIS-based switching support */
+#define HOST_CAP_PMP            (1UL << 17) /* Port Multiplier support */
+#define HOST_CAP_ONLY           (1UL << 18) /* Supports AHCI mode only */
+#define HOST_CAP_CLO            (1UL << 24) /* Command List Override support */
+#define HOST_CAP_LED            (1UL << 25) /* Supports activity LED */
+#define HOST_CAP_ALPM           (1UL << 26) /* Aggressive Link PM support */
+#define HOST_CAP_SSS            (1UL << 27) /* Staggered Spin-up */
+#define HOST_CAP_MPS            (1UL << 28) /* Mechanical presence switch */
+#define HOST_CAP_SNTF           (1UL << 29) /* SNotification register */
+#define HOST_CAP_NCQ            (1UL << 30) /* Native Command Queueing */
+#define HOST_CAP_64             (1UL << 31) /* PCI DAC (64-bit DMA) support */
+
+/* HOST_CAP2 bits */
+#define HOST_CAP2_BOH           (1UL << 0)  /* BIOS/OS handoff supported */
+#define HOST_CAP2_NVMHCI        (1UL << 1)  /* NVMHCI supported */
+#define HOST_CAP2_APST          (1UL << 2)  /* Automatic partial to slumber */
+
+/* HOST_BOHC bits */
+#define HOST_BOHC_BOS           (1UL << 0)  /* BIOS owned (semaphore bit) */
+#define HOST_BOHC_OOS           (1UL << 1)  /* OS owned (semaphore bit) */
+#define HOST_BOHC_SOOE          (1UL << 2)  /* SMI on ownership change enable */
+#define HOST_BOHC_OOC           (1UL << 3)  /* OS ownership change */
+#define HOST_BOHC_BB            (1UL << 4)  /* BIOS is busy changing ownership */
+
+/* registers for each SATA port */
+#define PORT_LST_ADDR           0x00 /* command list DMA addr */
+#define PORT_LST_ADDR_HI        0x04 /* command list DMA addr hi */
+#define PORT_FIS_ADDR           0x08 /* FIS rx buf addr */
+#define PORT_FIS_ADDR_HI        0x0c /* FIS rx buf addr hi */
+#define PORT_IRQ_STAT           0x10 /* interrupt status */
+#define PORT_IRQ_MASK           0x14 /* interrupt enable/disable mask */
+#define PORT_CMD                0x18 /* port command */
+#define PORT_TFDATA             0x20 /* taskfile data */
+#define PORT_SIG                0x24 /* device TF signature */
+#define PORT_CMD_ISSUE          0x38 /* command issue */
+#define PORT_SCR_STAT           0x28 /* SATA phy register: SStatus */
+#define PORT_SCR_CTL            0x2c /* SATA phy register: SControl */
+#define PORT_SCR_ERR            0x30 /* SATA phy register: SError */
+#define PORT_SCR_ACT            0x34 /* SATA phy register: SActive */
+#define PORT_SCR_NTF            0x3c /* SATA phy register: SNotification */
+
+/* PORT_IRQ_{STAT,MASK} bits */
+#define PORT_IRQ_COLD_PRES      (1UL << 31) /* cold presence detect */
+#define PORT_IRQ_TF_ERR         (1UL << 30) /* task file error */
+#define PORT_IRQ_HBUS_ERR       (1UL << 29) /* host bus fatal error */
+#define PORT_IRQ_HBUS_DATA_ERR  (1UL << 28) /* host bus data error */
+#define PORT_IRQ_IF_ERR         (1UL << 27) /* interface fatal error */
+#define PORT_IRQ_IF_NONFATAL    (1UL << 26) /* interface non-fatal error */
+#define PORT_IRQ_OVERFLOW       (1UL << 24) /* xfer exhausted available S/G */
+#define PORT_IRQ_BAD_PMP        (1UL << 23) /* incorrect port multiplier */
+#define PORT_IRQ_PHYRDY         (1UL << 22) /* PhyRdy changed */
+#define PORT_IRQ_DEV_ILCK       (1UL << 7)  /* device interlock */
+#define PORT_IRQ_CONNECT        (1UL << 6)  /* port connect change status */
+#define PORT_IRQ_SG_DONE        (1UL << 5)  /* descriptor processed */
+#define PORT_IRQ_UNK_FIS        (1UL << 4)  /* unknown FIS rx'd */
+#define PORT_IRQ_SDB_FIS        (1UL << 3)  /* Set Device Bits FIS rx'd */
+#define PORT_IRQ_DMAS_FIS       (1UL << 2)  /* DMA Setup FIS rx'd */
+#define PORT_IRQ_PIOS_FIS       (1UL << 1)  /* PIO Setup FIS rx'd */
+#define PORT_IRQ_D2H_REG_FIS    (1UL << 0)  /* D2H Register FIS rx'd */
+#define PORT_IRQ_FREEZE         (PORT_IRQ_HBUS_ERR | PORT_IRQ_IF_ERR   | \
+                                 PORT_IRQ_CONNECT  | PORT_IRQ_PHYRDY   | \
+                                 PORT_IRQ_UNK_FIS  | PORT_IRQ_BAD_PMP)
+#define PORT_IRQ_ERROR          (PORT_IRQ_FREEZE   | PORT_IRQ_TF_ERR   | \
+                                 PORT_IRQ_OVERFLOW | PORT_IRQ_HBUS_DATA_ERR)
+#define DEF_PORT_IRQ            (PORT_IRQ_ERROR | PORT_IRQ_SG_DONE     | \
+                                 PORT_IRQ_SDB_FIS | PORT_IRQ_DMAS_FIS  | \
+                                 PORT_IRQ_PIOS_FIS | PORT_IRQ_D2H_REG_FIS)
+
+#define PORT_ERR_X              (1UL << 26) /* Exchanged */
+#define PORT_ERR_FIS            (1UL << 25) /* Unknown FIS type */
+#define PORT_ERR_TP             (1UL << 24) /* Transport State Transition Error */
+#define PORT_ERR_S              (1UL << 23) /* Link Sequence Error */
+#define PORT_ERR_H              (1UL << 22) /* Handshake Error */
+#define PORT_ERR_CRC            (1UL << 21) /* CRC Error */
+#define PORT_ERR_D              (1UL << 20) /* Disparity Error */
+#define PORT_ERR_B              (1UL << 19) /* 10B to 8B Decode Error */
+#define PORT_ERR_W              (1UL << 18) /* Comm Wake */
+#define PORT_ERR_PI             (1UL << 17) /* Phy Internal Error */
+#define PORT_ERR_N              (1UL << 16) /* PhyRdy Change */
+#define PORT_ERR_IE             (1UL << 11) /* Internal Error */
+#define PORT_ERR_P              (1UL << 10) /* Protocol Error */
+#define PORT_ERR_C              (1UL << 9)  /* Persistent Communication or Data Integrity Error */
+#define PORT_ERR_TD             (1UL << 8)  /* Transient Data Integrity Error */
+#define PORT_ERR_M              (1UL << 1)  /* Recovered Communications Error */
+#define PORT_ERR_DI             (1UL << 0)  /* Recoverred Data Integrity Error */
+#define PORT_ERR_FAIL_BITS      (PORT_ERR_X | PORT_ERR_FIS | PORT_ERR_TP | PORT_ERR_S | PORT_ERR_H | PORT_ERR_CRC | \
+                                 PORT_ERR_D | PORT_ERR_B | PORT_ERR_W | PORT_ERR_PI | PORT_ERR_IE | PORT_ERR_P | \
+                                 PORT_ERR_C | PORT_ERR_TD)
+
+/* PORT_CMD bits */
+#define PORT_CMD_ASP            (1UL << 27) /* Aggressive Slumber/Partial */
+#define PORT_CMD_ALPE           (1UL << 26) /* Aggressive Link PM enable */
+#define PORT_CMD_ATAPI          (1UL << 24) /* Device is ATAPI */
+#define PORT_CMD_PMP            (1UL << 17) /* PMP attached */
+#define PORT_CMD_LIST_ON        (1UL << 15) /* cmd list DMA engine running */
+#define PORT_CMD_FIS_ON         (1UL << 14) /* FIS DMA engine running */
+#define PORT_CMD_FIS_RX         (1UL << 4)  /* Enable FIS receive DMA engine */
+#define PORT_CMD_CLO            (1UL << 3)  /* Command list override */
+#define PORT_CMD_POWER_ON       (1UL << 2)  /* Power up device */
+#define PORT_CMD_SPIN_UP        (1UL << 1)  /* Spin up device */
+#define PORT_CMD_START          (1UL << 0)  /* Enable port DMA engine */
+
+#define PORT_CMD_ICC_MASK       (0xfUL << 28) /* i/f ICC state mask */
+#define PORT_CMD_ICC_ACTIVE     (0x1UL << 28) /* Put i/f in active state */
+#define PORT_CMD_ICC_PARTIAL    (0x2UL << 28) /* Put i/f in partial state */
+#define PORT_CMD_ICC_SLUMBER    (0x6UL << 28) /* Put i/f in slumber state */
+
+/* driver status bits */
+#define AHCI_HFLAG_NO_NCQ               (1UL << 0)  /* no native cmd queuing */
+#define AHCI_HFLAG_IGN_IRQ_IF_ERR       (1UL << 1)  /* ignore IRQ_IF_ERR */
+#define AHCI_HFLAG_IGN_SERR_INTERNAL    (1UL << 2)  /* ignore SERR_INTERNAL */
+#define AHCI_HFLAG_32BIT_ONLY           (1UL << 3)  /* force 32bit */
+#define AHCI_HFLAG_MV_PATA              (1UL << 4)  /* PATA port */
+#define AHCI_HFLAG_NO_MSI               (1UL << 5)  /* no PCI MSI */
+#define AHCI_HFLAG_NO_PMP               (1UL << 6)  /* no PMP */
+#define AHCI_HFLAG_NO_HOTPLUG           (1UL << 7)  /* ignore PxSERR.DIAG.N */
+#define AHCI_HFLAG_SECT255              (1UL << 8)  /* max 255 sectors */
+#define AHCI_HFLAG_YES_NCQ              (1UL << 9)  /* force NCQ cap on */
+#define AHCI_HFLAG_NO_SUSPEND           (1UL << 10) /* don't suspend */
+#define AHCI_HFLAG_SRST_TOUT_IS_OFFLINE (1UL << 11) /* treat SRST timeout as
+                                                      link offline */
+#define AHCI_HFLAG_NO_SNTF              (1UL << 12) /* no sntf */
+
+#define ICH_MAP                         0x90 /* ICH MAP register */
+
+/* em constants */
+#define EM_MAX_SLOTS                    8
+#define EM_MAX_RETRY                    5
+
+/* em_ctl bits */
+#define EM_CTL_RST                      (1UL << 9)  /* Reset */
+#define EM_CTL_TM                       (1UL << 8)  /* Transmit Message */
+#define EM_CTL_ALHD                     (1UL << 26) /* Activity LED */
+
+/* ------------------------ typedefs and structures ------------------------ */
+
+/* Primitive types
+ *
+ * Note: Since OS/2 is essentially an x86 OS and this driver, as well as the
+ *       interface it's developed for, is based on x86 design patterns, we're
+ *       not even going to start making a difference between little and big
+ *       endian architectures. PCI is little endian, AHCI is little endian,
+ *       x86 is little endian, and that's it.
+ */
+typedef unsigned char    u8;
+typedef unsigned short   u16;
+typedef unsigned long    u32;
+
+/* AHCI S/G structure */
+typedef struct {
+  u32   addr;            /* address of S/G element */
+  u32   addr_hi;         /* address of S/G element (upper 32 bits) */
+  u32   reserved;
+  u32   size;            /* size of S/G element - 1; the high 10 bits are flags:
+                          *  31    : interrupt on completion of this S/G
+                          *  30-22 : reserved */
+} AHCI_SG;
+
+/* AHCI command header */
+typedef struct {
+  u32   options;         /* command options */
+  u32   status;          /* command status */
+  u32   tbl_addr;        /* command table address */
+  u32   tbl_addr_high;   /* command table address (upper 32 bits) */
+  u32   reserved[4];
+} AHCI_CMD_HDR;
+
+/* AHCI command table */
+typedef struct {
+  u8           cmd_fis[64];                /* ATA command FIS */
+  u8           atapi_cmd[16];              /* ATAPI command */
+  u8           reserved[48];
+  AHCI_SG      sg_list[AHCI_MAX_SG];       /* AHCI S/G list */
+} AHCI_CMD_TBL;
+
+/* AHCI port DMA scratch area */
+typedef struct {
+  AHCI_CMD_HDR   cmd_hdr[AHCI_MAX_CMDS];   /* command headers */
+  u8             rx_fis[AHCI_RX_FIS_SZ];   /* FIS RX area */
+  AHCI_CMD_TBL   cmd_tbl[AHCI_MAX_CMDS];   /* command table */
+} AHCI_PORT_DMA;
+
+/* AHCI port BIOS configuration save area */
+typedef struct {
+  u32   cmd_list;                          /* cmd list base address */
+  u32   cmd_list_h;                        /* cmd list base address high */
+  u32   fis_rx;                            /* FIS receive buffer */
+  u32   fis_rx_h;                          /* FIS receive bufffer high */
+  u32   irq_mask;                          /* IRQ mask */
+  u32   port_cmd;                          /* port engine status */
+} AHCI_PORT_CFG;
+
Index: /tags/2.02/src/os2ahci/apm.c
===================================================================
--- /tags/2.02/src/os2ahci/apm.c	(revision 189)
+++ /tags/2.02/src/os2ahci/apm.c	(revision 189)
@@ -0,0 +1,271 @@
+/******************************************************************************
+ * apm.c - Functions to interface with the legacy APM driver, and suspend / resume functions.
+ *
+ * Copyright (c) 2011 thi.guten Software Development
+ * Copyright (c) 2011 Mensys B.V.
+ * Portions copyright (c) 2013-2016 David Azarewicz
+ *
+ * Authors: Christian Mueller, Markus Thielen
+ *
+ * Parts copied from/inspired by the Linux AHCI driver;
+ * those parts are (c) Linux AHCI/ATA maintainers
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+#include "os2ahci.h"
+
+/* Legacy APM support is not needed on eCS systems with ACPI and is more
+ * reliable without it enabled.
+ */
+#ifdef LEGACY_APM
+
+#include <apmcalls.h>
+USHORT _cdecl apm_event    (APMEVENT *evt);
+
+/******************************************************************************
+ * Connect to APM driver and register for power state change events.
+ */
+void apm_init(void)
+{
+  USHORT rc;
+
+  /* connect to APM driver */
+  if ((rc = APMAttach()) != 0) {
+    DPRINTF(2,"couldn't connect to APM driver (rc = %d)\n", rc);
+    return;
+  }
+
+  /* register for suspend/resume events */
+  if ((rc = APMRegister(apm_event, APM_NOTIFYSETPWR |
+                                   APM_NOTIFYNORMRESUME |
+                                   APM_NOTIFYCRITRESUME, 0)) != 0) {
+    DPRINTF(2,"couldn't register for power event notificatins (rc = %d)\n", rc);
+    return;
+  }
+}
+
+/******************************************************************************
+ * APM event handler
+ */
+USHORT _cdecl apm_event(APMEVENT *evt)
+{
+  USHORT msg = (USHORT) evt->ulParm1;
+
+  DPRINTF(2,"received APM event: 0x%x/0x%x\n");
+
+  switch (msg) {
+
+  case APM_SETPWRSTATE:
+    if (evt->ulParm2 >> 16 != APM_PWRSTATEREADY) {
+      /* we're suspending */
+      suspend();
+    }
+    break;
+
+  case APM_NORMRESUMEEVENT:
+  case APM_CRITRESUMEEVENT:
+    /* we're resuming */
+    resume();
+    break;
+
+  default:
+    DPRINTF(2,"unknown APM event; ignoring...\n");
+    break;
+  }
+
+  return(0);
+}
+#endif /* LEGACY_APM */
+
+/******************************************************************************
+ * Suspend handler. In a nutshell, it'll turn off interrupts and flush all
+ * write caches.
+ */
+void suspend(void)
+{
+  int a;
+  int p;
+  int d;
+  TIMER Timer;
+
+  if (suspended) return;
+  DPRINTF(2,"suspend()\n");
+
+  /* restart all ports with interrupts disabled */
+  for (a = 0; a < ad_info_cnt; a++) {
+    AD_INFO *ai = ad_infos + a;
+
+    lock_adapter(ai);
+    for (p = 0; p <= ai->port_max; p++) {
+      /* wait until all active commands have completed on this port */
+      TimerInit(&Timer, 250);
+      while (ahci_port_busy(ai, p)) {
+        if (TimerCheckAndBlock(&Timer)) break;
+      }
+
+      /* restart port with interrupts disabled */
+      ahci_stop_port(ai, p);
+      ahci_start_port(ai, p, 0);
+
+      /* flush cache on all attached devices */
+      for (d = 0; d <= ai->ports[p].dev_max; d++) {
+        if (ai->ports[p].devs[d].present) {
+          ahci_flush_cache(ai, p, d);
+        }
+      }
+    }
+
+    /* AHCI spec rev1.1 section 8.3.3:
+     * Software must disable interrupts prior to requesting a transition of the HBA to D3 state.
+     */
+    writel(ai->mmio + HOST_CTL, readl(ai->mmio + HOST_CTL) & ~HOST_IRQ_EN);
+    readl(ai->mmio + HOST_CTL); /* flush */
+
+    /* TODO: put the device into the D3 state */
+  }
+
+  /* reset init_complete so that we can process IORBs without interrupts */
+  init_complete = 0;
+
+  suspended = 1;
+  DPRINTF(2,"suspend() finished\n");
+}
+
+/******************************************************************************
+ * Resume handler. All ports are restarted with interrupts enabled using
+ * the same function as the IOCM_COMPLETE_INIT handler does.
+ */
+void resume(void)
+{
+  int a;
+
+  if (!suspended) return;
+  DPRINTF(2,"resume()\n");
+
+  for (a = 0; a < ad_info_cnt; a++) {
+    AD_INFO *ai = ad_infos + a;
+
+    /* TODO: put the device into the D0 state */
+
+    //ahci_reset_controller(ai);
+
+    /* Complete initialization of this adapter; this will restart the ports
+     * with interrupts enabled and take care of whatever else needs to be
+     * done to get the adapter and its ports up and running.
+     */
+    ahci_complete_init(ai);
+  }
+
+  /* tell the driver we're again fully operational */
+  init_complete = 1;
+
+  /* unlock all adapters now that we have set the init_complete flag */
+  for (a = 0; a < ad_info_cnt; a++) {
+    AD_INFO *ai = ad_infos + a;
+    unlock_adapter(ai);
+  }
+
+  suspended = 0;
+
+  /* restart engine to resume IORB processing */
+  /* The resume_sleep_flag and probably rearming the ctx hook is a temporary hack
+   * to make resume kind of work when I/O operations are outstanding or started
+   * during the suspend operation. This behavior may change with future versions
+   * of the ACPI software which will make this hack unnecessary.
+   */
+  resume_sleep_flag = 5000;
+  KernArmHook(engine_ctxhook_h, 0, 0);
+
+  DPRINTF(2,"resume() finished\n");
+}
+
+/******************************************************************************
+ * This is the kernel exit handler for panics and traps.
+ * Assume the system is trashed and do the absolute minimum necessary
+ * to put the adapters into a state so that the BIOS can operate the
+ * adapters. We never need to recover from this as the system will be rebooted.
+ */
+void shutdown_driver(void)
+{
+  int a;
+  int p;
+  u16 i;
+  u32 tmp;
+  //int d;
+
+  DPRINTF(1,"shutdown_driver() enter\n");
+
+  for (a = 0; a < ad_info_cnt; a++)
+  {
+    AD_INFO *ai = ad_infos + a;
+
+    /* Try to be nice. Wait 50ms for adapter to go not busy.
+     * If it doesn't go not busy in that time, too bad. Stop it anyway.
+     */
+    for (i=0; i<50000 && ai->busy; i++) udelay(1000);
+
+    for (p = 0; p <= ai->port_max; p++)
+    {
+      u8 *port_mmio = port_base(ai, p);
+
+      /* Wait up to 50ms for port to go not busy. Again stop it
+       * anyway if it doesn't go not busy in that time.
+       */
+      for (i=0; i<50000 && ahci_port_busy(ai, p); i++) udelay(1000);
+
+      /* stop port */
+      writel(port_mmio + PORT_IRQ_MASK, 0); /* disable port interrupts */
+      writel(port_mmio + PORT_CMD, readl(port_mmio + PORT_CMD) & ~PORT_CMD_FIS_RX); /* disable FIS reception */
+      while (readl(port_mmio + PORT_CMD) & PORT_CMD_FIS_ON); /* wait for it to stop */
+      writel(port_mmio + PORT_CMD, readl(port_mmio + PORT_CMD) & ~PORT_CMD_START); /* set port to idle */
+      while (readl(port_mmio + PORT_CMD) & PORT_CMD_LIST_ON); /* wait for it to stop */
+
+      /* clear any pending port IRQs */
+      tmp = readl(port_mmio + PORT_IRQ_STAT);
+      if (tmp) writel(port_mmio + PORT_IRQ_STAT, tmp);
+      writel(ai->mmio + HOST_IRQ_STAT, 1UL << p);
+
+      /* reset PxSACT register (tagged command queues, not reset by COMRESET) */
+      writel(port_mmio + PORT_SCR_ACT, 0);
+      readl(port_mmio + PORT_SCR_ACT);  /* flush */
+
+      #if 0
+      /* cannot flush caches this way */
+      ahci_start_port(ai, p, 0);
+
+      /* flush cache on all attached devices */
+      for (d = 0; d <= ai->ports[p].dev_max; d++)
+      {
+        if (ai->ports[p].devs[d].present)
+        {
+          ahci_flush_cache(ai, p, d);
+        }
+      }
+      #endif
+    }
+  }
+
+  init_complete = 0;
+
+  /* restore BIOS configuration for each adapter */
+  for (a = 0; a < ad_info_cnt; a++)
+  {
+    ahci_restore_bios_config(ad_infos + a);
+  }
+
+  DPRINTF(1,"shutdown_driver() finished\n");
+}
+
Index: /tags/2.02/src/os2ahci/ata.c
===================================================================
--- /tags/2.02/src/os2ahci/ata.c	(revision 189)
+++ /tags/2.02/src/os2ahci/ata.c	(revision 189)
@@ -0,0 +1,1421 @@
+/******************************************************************************
+ * ata.c - ATA command processing
+ *
+ * Copyright (c) 2011 thi.guten Software Development
+ * Copyright (c) 2011 Mensys B.V.
+ * Copyright (c) 2013-2016 David Azarewicz
+ *
+ * Authors: Christian Mueller, Markus Thielen
+ *
+ * Parts copied from/inspired by the Linux AHCI driver;
+ * those parts are (c) Linux AHCI/ATA maintainers
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+#include "os2ahci.h"
+#include "ata.h"
+
+/* -------------------------- macros and constants ------------------------- */
+
+/* ------------------------ typedefs and structures ------------------------ */
+
+/* -------------------------- function prototypes -------------------------- */
+
+static int ata_cmd_read(IORBH *pIorb, AD_INFO *ai, int p, int d, int slot,
+                        ULONG sector, ULONG count, SCATGATENTRY *sg_list,
+                        ULONG sg_cnt);
+
+static int ata_cmd_write(IORBH *pIorb, AD_INFO *ai, int p, int d, int slot,
+                         ULONG sector, ULONG count, SCATGATENTRY *sg_list,
+                         ULONG sg_cnt, int write_through);
+
+/* ------------------------ global/static variables ------------------------ */
+
+/* ----------------------------- start of code ----------------------------- */
+
+/******************************************************************************
+ * Initialize AHCI command slot, FIS and S/G list for the specified ATA
+ * command. The command parameters are passed as a variable argument list
+ * of type and value(s). The list is terminated by AP_END.
+ *
+ * Notes:
+ *
+ *  - The specified command slot is expected to be idle; no checks are
+ *    performed to prevent messing with a busy port.
+ *
+ *  - Port multipliers are not supported, yet, thus 'd' should always
+ *    be 0 for the time being.
+ *
+ *  - 'cmd' is passed as 16-bit integer because the compiler would push
+ *    a 'u8' as 16-bit value (it's a fixed argument) and the stdarg
+ *    macros would screw up the address of the first variable argument
+ *    if the size of the last fixed argument wouldn't match what the
+ *    compiler pushed on the stack.
+ *
+ * Return values:
+ *    0 : success
+ *  > 0 : could not map all S/G entries; the return value is the number of
+ *        S/G entries that could be mapped.
+ *  < 0 : other error
+ */
+int ata_cmd(AD_INFO *ai, int p, int d, int slot, int cmd, ...)
+{
+  va_list va;
+  va_start(va, cmd);
+  return(v_ata_cmd(ai, p, d, slot, cmd, va));
+}
+
+int v_ata_cmd(AD_INFO *ai, int p, int d, int slot, int cmd, va_list va)
+{
+  AHCI_PORT_DMA *dma_base_virt;
+  AHCI_CMD_HDR *cmd_hdr;
+  AHCI_CMD_TBL *cmd_tbl;
+  SCATGATENTRY *sg_list = NULL;
+  SCATGATENTRY sg_single;
+  ATA_PARM ap;
+  ATA_CMD ata_cmd;
+  void *atapi_cmd = NULL;
+  u32 dma_base_phys;
+  u32 atapi_cmd_len = 0;
+  u32 ahci_flags = 0;
+  u32 sg_cnt = 0;
+  u32 i;
+  u32 n;
+
+  /* --------------------------------------------------------------------------
+   * Initialize ATA command. The ATA command is set up with the main command
+   * value and a variable list of additional parameters such as the sector
+   * address, transfer count, ...
+   */
+  memset(&ata_cmd, 0x00, sizeof(ata_cmd));
+  ata_cmd.cmd = cmd;
+
+  /* parse variable arguments */
+  do
+  {
+    switch ((ap = va_arg(va, ATA_PARM)))
+    {
+
+    case AP_AHCI_FLAGS:
+      ahci_flags |= va_arg(va, u32);
+      break;
+
+    case AP_WRITE:
+      if (va_arg(va, u32) != 0)
+      {
+        ahci_flags |= AHCI_CMD_WRITE;
+      }
+      break;
+
+    case AP_FEATURES:
+      /* ATA features word */
+      ata_cmd.features |= va_arg(va, u32);
+      break;
+
+    case AP_COUNT:
+      /* transfer count */
+      ata_cmd.count = va_arg(va, u32);
+      break;
+
+    case AP_SECTOR_28:
+      /* 28-bit sector address */
+      ata_cmd.lba_l = va_arg(va, u32);
+      if (ata_cmd.lba_l & 0xf0000000UL)
+      {
+        dprintf(0,"error: LBA-28 address %d has more than 28 bits\n", ata_cmd.lba_l);
+        return(ATA_CMD_INVALID_PARM);
+      }
+      /* add upper 4 bits to device field */
+      ata_cmd.device |= (ata_cmd.lba_l >> 24) & 0x0fU;
+      /* only lower 24 bits come into lba_l */
+      ata_cmd.lba_l &= 0x00ffffffUL;
+      break;
+
+    case AP_SECTOR_48:
+      /* 48-bit sector address */
+      ata_cmd.lba_l = va_arg(va, u32);
+      ata_cmd.lba_h = va_arg(va, u32);
+      break;
+
+    case AP_DEVICE:
+      /* ATA device byte; note that this byte contains the highest
+       * 4 bits of LBA-28 address; we have to leave them alone here. */
+      ata_cmd.device |= va_arg(va, u32) & 0xf0;
+      break;
+
+    case AP_SGLIST:
+      /* scatter/gather list in SCATGATENTRY/count format */
+      sg_list = va_arg(va, void *);
+      sg_cnt = va_arg(va, u32);
+      break;
+
+    case AP_VADDR:
+      /* virtual buffer address in addr/len format (up to 4K) */
+      sg_single.ppXferBuf = MemPhysAdr(va_arg(va, void *));
+      sg_single.XferBufLen = va_arg(va, u32);
+      sg_list = &sg_single;
+      sg_cnt = 1;
+      break;
+
+    case AP_ATAPI_CMD:
+      /* ATAPI command */
+      atapi_cmd = va_arg(va, void *);
+      atapi_cmd_len = va_arg(va, u32);
+      ahci_flags |= AHCI_CMD_ATAPI;
+      break;
+
+    case AP_ATA_CMD:
+      /* ATA command "pass-through" */
+      memcpy(&ata_cmd, va_arg(va, void *), sizeof(ATA_CMD));
+      break;
+
+    case AP_END:
+      break;
+
+    default:
+      dprintf(0,"error: v_ata_cmd() called with invalid parameter type (%d)\n", (int) ap);
+      return(ATA_CMD_INVALID_PARM);
+    }
+
+  } while (ap != AP_END);
+
+  /* --------------------------------------------------------------------------
+   * Fill in AHCI ATA command information. This includes the port command slot,
+   * the corresponding command FIS and the S/G list. The layout of the AHCI
+   * port DMA region is based on the Linux AHCI driver and looks like this:
+   *
+   *  - 32 AHCI command headers (AHCI_CMD_HDR) with 32 bytes, each
+   *  -  1 FIS receive area with 256 bytes (AHCI_RX_FIS_SZ)
+   *  - 32 AHCI command tables, each consisting of
+   *    -  64 bytes for command FIS
+   *    -  16 bytes for ATAPI comands
+   *    -  48 bytes reserved
+   *    -  48 S/G entries (AHCI_SG) with 32 bytes, each
+   *
+   * Since the whole DMA buffer for all ports is larger than 64KB and we need
+   * multiple segments to address all of them, there are no virtual pointers
+   * to the individual elements in AD_INFO. Instead, we're relying on macros
+   * for getting the base address of a particular port's DMA region, then
+   * map a structure on top of that for convenience (AHCI_PORT_DMA).
+   */
+  dma_base_virt = port_dma_base(ai, p);
+  dma_base_phys = port_dma_base_phys(ai, p);
+
+  /* AHCI command header */
+  cmd_hdr = &dma_base_virt->cmd_hdr[slot];
+  memset(cmd_hdr, 0x00, sizeof(*cmd_hdr));
+  cmd_hdr->options  = ((d & 0x0f) << 12);
+  cmd_hdr->options |= ahci_flags;  /* AHCI command flags */
+  cmd_hdr->options |= 5;           /* length of command FIS in 32-bit words */
+  cmd_hdr->tbl_addr = dma_base_phys + offsetof(AHCI_PORT_DMA, cmd_tbl[slot]);
+  /* DAZ can use MemPhysAdr(&dma_base_virt->cmd_tbl[slot]), but is probably slower. */
+
+  /* AHCI command table */
+  cmd_tbl = &dma_base_virt->cmd_tbl[slot];
+  memset(cmd_tbl, 0x00, sizeof(*cmd_tbl));
+  ata_cmd_to_fis(cmd_tbl->cmd_fis, &ata_cmd, d);
+
+  if (atapi_cmd != NULL)
+  {
+    /* copy ATAPI command */
+    memcpy(cmd_tbl->atapi_cmd, atapi_cmd, atapi_cmd_len);
+  }
+
+  /* PRDT (S/G list)
+   *
+   *  - The S/G list for AHCI adapters is limited to 22 bits for the transfer
+   *    size of each element, thus we need to split S/G elements larger than
+   *    22 bits into 2 AHCI_SG elements.
+   *
+   *  - The S/G element size for AHCI is what the spec calls '0'-based
+   *    (i.e. 0 means 1 bytes). On top of that, the spec requires S/G transfer
+   *    sizes to be even in the context of 16-bit transfers, thus bit '1'
+   *    always needs to be set.
+   *
+   *  - AHCI_MAX_SG_ELEMENT_LEN defines the maximum size of an AHCI S/G
+   *    element in bytes, ignoring the '0'-based methodology (i.e. 1 << 22).
+   *
+   *  - There's a limit on the maximum number of S/G elements in the port DMA
+   *    buffer (AHCI_MAX_SG) which is lower than the HW maximum. It's beyond
+   *    the control of this function to split commands which require more
+   *    than AHCI_MAX_SG entries. In order to help the caller, the return value
+   *    of this function will indicate how many OS/2 S/G entries were
+   *    successfully mapped.
+   */
+  for (i = n = 0; i < sg_cnt; i++)
+  {
+    u32 sg_addr = sg_list[i].ppXferBuf;
+    u32 sg_size = sg_list[i].XferBufLen;
+
+    do
+    {
+      u32 chunk = (sg_size > AHCI_MAX_SG_ELEMENT_LEN) ? AHCI_MAX_SG_ELEMENT_LEN : sg_size;
+      if (n >= AHCI_MAX_SG)
+      {
+        /* couldn't store all S/G elements in our DMA buffer */
+        dprintf(0,"ata_cmd(): too many S/G elements\n");
+        return(i - 1);
+      }
+      if ((sg_addr & 1) || (chunk & 1))
+      {
+        dprintf(1,"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;
+      sg_addr += chunk;
+      sg_size -= chunk;
+      n++;
+    } while (sg_size > 0);
+  }
+
+  /* set final S/G count in AHCI command header */
+  cmd_hdr->options |= n << 16;
+
+  #ifdef DEBUG
+  if ((D32g_DbgLevel >= 7) || (atapi_cmd != NULL))
+  {
+    DPRINTF(0,"ATA command for %d.%d.%d, slot %d:\n", ad_no(ai), p, d, slot);
+    dHexDump(0,cmd_hdr, offsetof(AHCI_CMD_HDR, reserved), "cmd_hdr: ");
+    dHexDump(0,&ata_cmd, sizeof(ata_cmd), "ata_cmd: ");
+    if (atapi_cmd != NULL)
+    {
+      dHexDump(0,atapi_cmd, atapi_cmd_len, "atapi_cmd: ");
+    }
+    if (n > 0)
+    {
+      dHexDump(0,cmd_tbl->sg_list, sizeof(*cmd_tbl->sg_list) * n, "sg_list: ");
+    }
+  }
+  #endif
+
+  return(ATA_CMD_SUCCESS);
+}
+
+/******************************************************************************
+ * Fill SATA command FIS with values extracted from an ATA command structure.
+ * The command FIS buffer (fis) is expected to be initialized to 0s. The
+ * structure of the FIS maps to the ATA shadow register block, including
+ * registers which can be written twice to store 16 bits (called 'exp').
+ *
+ * The FIS structure looks like this (using LSB notation):
+ *
+ *       +----------------+----------------+----------------+----------------+
+ *  00   | FIS type (27h) |   C|R|R|R|PMP  |   Command      |  Features      |
+ *       +----------------+----------------+----------------+----------------+
+ *  04   |   LBA 7:0      |   LBA 15:8     |   LBA 23:16    |  R|R|R|D|Head  |
+ *       +----------------+----------------+----------------+----------------+
+ *  08   |   LBA 31:24    |   LBA 40:32    |   LBA 47:40    |  Features exp  |
+ *       +----------------+----------------+----------------+----------------+
+ *  12   |   Count 7:0    |   Count 15:8   |   Reserved     |  Control       |
+ *       +----------------+----------------+----------------+----------------+
+ *  16   |   Reserved     |   Reserved     |   Reserved     |  Reserved      |
+ *       +----------------+----------------+----------------+----------------+
+ */
+void ata_cmd_to_fis(u8 *fis, ATA_CMD *ata_cmd, int d)
+{
+  fis[0]  = 0x27;                  /* register - host to device FIS */
+  fis[1]  = (u8) (d & 0xf);        /* port multiplier number */
+  fis[1] |= 0x80;                  /* bit 7 indicates Command FIS */
+  fis[2]  = (u8) ata_cmd->cmd;
+  fis[3]  = (u8) ata_cmd->features;
+
+  fis[4]  = (u8) ata_cmd->lba_l;
+  fis[5]  = (u8) (ata_cmd->lba_l >> 8);
+  fis[6]  = (u8) (ata_cmd->lba_l >> 16);
+  fis[7]  = (u8) ata_cmd->device;
+
+  fis[8]  = (u8) (ata_cmd->lba_l >> 24);
+  fis[9]  = (u8) ata_cmd->lba_h;
+  fis[10] = (u8) (ata_cmd->lba_h >> 8);
+  fis[11] = (u8) (ata_cmd->features >> 8);
+
+  fis[12] = (u8) ata_cmd->count;
+  fis[13] = (u8) (ata_cmd->count >> 8);
+}
+
+/******************************************************************************
+ * Get index in S/G list for the number of transferred sectors in the IORB.
+ *
+ * Returning io->cSGList indicates an error.
+ *
+ * NOTE: OS/2 makes sure S/G lists are set up such that entries at the HW
+ *       limit will never cross sector boundaries. This means that splitting
+ *       S/G lists into multiple commands can be done without editing the S/G
+ *       lists.
+ */
+u16 ata_get_sg_indx(IORB_EXECUTEIO *io)
+{
+  ULONG offset = io->BlocksXferred * io->BlockSize;
+  SCATGATENTRY *pSGList = (SCATGATENTRY*)Far16ToFlat(io->pSGList);
+  USHORT i;
+
+  for (i = 0; i < io->cSGList && offset > 0; i++)
+  {
+    offset -= pSGList[i].XferBufLen;
+  }
+
+  return(i);
+}
+
+/******************************************************************************
+ * Get max S/G count which will fit into our HW S/G buffers. This function is
+ * called when the S/G list is too long and we need to split the IORB into
+ * multiple commands. It returns both the number of sectors and S/G list
+ * elements that we can handle in a single command.
+ *
+ * The parameter 'sg_indx' indicates the current start index in the S/G list
+ * (0 if this is the first command iteration).
+ *
+ * The parameter 'sg_max' is the return value of v_ata_cmd() and indicates
+ * how many S/G elements were successfully mapped. Whatever we return needs to
+ * be less or equal to this value.
+ *
+ * Returning 0 in *sg_cnt indicates an error.
+ *
+ * NOTE: OS/2 makes sure S/G lists are set up such that entries at HW limits
+ *       will never cross sector boundaries. This means that splitting S/G
+ *       lists into multiple commands can be done without editing S/G list
+ *       elements. Since AHCI only allows 22 bits for each S/G element, the
+ *       hardware limits are reported as AHCI_MAX_SG / 2 but will vary based
+ *       on the actual length of S/G elements. This function looks for the
+ *       maximum number of S/G elements that can be mapped on sector
+ *       boundaries which will still fit into our HW S/G list.
+ */
+void ata_max_sg_cnt(IORB_EXECUTEIO *io, USHORT sg_indx, USHORT sg_max,
+                    USHORT *sg_cnt, USHORT *sector_cnt)
+{
+  ULONG max_sector_cnt = 0;
+  USHORT max_sg_cnt = 0;
+  ULONG offset = 0;
+  USHORT i;
+  SCATGATENTRY *pSGList = (SCATGATENTRY*)Far16ToFlat(io->pSGList);
+
+  for (i = sg_indx; i < io->cSGList; i++)
+  {
+    if (i - sg_indx >= sg_max)
+    {
+      /* we're beyond the number of S/G elements we can map */
+      break;
+    }
+
+    offset += pSGList[i].XferBufLen;
+    if (offset % io->BlockSize == 0)
+    {
+      /* this S/G element ends on a sector boundary */
+      max_sector_cnt = offset / io->BlockSize;
+      max_sg_cnt = i + 1;
+    }
+  }
+
+  /* return the best match we found (0 indicating failure) */
+  *sector_cnt = max_sector_cnt;
+  *sg_cnt = max_sg_cnt;
+}
+
+
+/******************************************************************************
+ * Get device or media geometry. Device and media geometry are expected to be
+ * the same for non-removable devices, which will always be the case for the
+ * ATA devices we're dealing with (hard disks). ATAPI is a different story
+ * and handled by atapi_get_geometry().
+ */
+int ata_get_geometry(IORBH FAR16DATA *vIorb, IORBH *pIorb, int slot)
+{
+  ADD_WORKSPACE *aws = add_workspace(pIorb);
+  int rc;
+
+  /* allocate buffer for ATA identify information */
+  if ((aws->buf = MemAlloc(ATA_ID_WORDS * sizeof(u16))) == NULL)
+  {
+    iorb_seterr(pIorb, IOERR_CMD_SW_RESOURCE);
+    return(-1);
+  }
+
+  /* request ATA identify information */
+  aws->ppfunc = ata_get_geometry_pp;
+  rc = ata_cmd(ad_infos + iorb_unit_adapter(pIorb),
+               iorb_unit_port(pIorb),
+               iorb_unit_device(pIorb),
+               slot,
+               ATA_CMD_ID_ATA,
+               AP_VADDR, (void *) aws->buf, ATA_ID_WORDS * sizeof(u16),
+               AP_END);
+
+  if (rc != 0)
+  {
+    iorb_seterr(pIorb, IOERR_CMD_ADD_SOFTWARE_FAILURE);
+  }
+
+  return(rc);
+}
+
+/* Adjust the cylinder count in the physical
+ * geometry to the last full cylinder.
+ */
+int adjust_cylinders(GEOMETRY *geometry, ULONG TotalSectors)
+{
+  USHORT SecPerCyl;
+  int rc = FALSE;
+
+  geometry->TotalSectors = TotalSectors;
+  SecPerCyl = geometry->SectorsPerTrack * geometry->NumHeads;
+  if (SecPerCyl > 0)
+  {
+    ULONG TotalCylinders  = TotalSectors / SecPerCyl;
+
+    geometry->TotalSectors   = TotalCylinders * SecPerCyl;
+    geometry->TotalCylinders = TotalCylinders;
+    if (TotalCylinders >> 16)
+    {
+      geometry->TotalCylinders = 65535;
+      rc = TRUE;
+    }
+  }
+  return (rc);
+}
+
+/* Calculate the logical geometry based on the input physcial geometry
+ * using the LBA Assist Translation algorithm.
+ */
+#define BIOS_MAX_CYLINDERS       1024l
+#define BIOS_MAX_NUMHEADS         255
+#define BIOS_MAX_SECTORSPERTRACK   63
+void log_geom_calculate_LBA_assist(GEOMETRY *geometry, ULONG TotalSectors)
+{
+  UCHAR numSpT   = BIOS_MAX_SECTORSPERTRACK;
+  UCHAR numHeads = BIOS_MAX_NUMHEADS;
+  ULONG Cylinders;
+
+  if (TotalSectors <= (BIOS_MAX_CYLINDERS * 128 * BIOS_MAX_SECTORSPERTRACK))
+  {
+    USHORT temp = (TotalSectors - 1) / (BIOS_MAX_CYLINDERS * BIOS_MAX_SECTORSPERTRACK);
+
+    if (temp < 16)      numHeads = 16;
+    else if (temp < 32) numHeads = 32;
+    else if (temp < 64) numHeads = 64;
+    else                numHeads = 128;
+  }
+
+  do
+  {
+    Cylinders = TotalSectors / (USHORT)(numHeads * numSpT);
+    if (Cylinders >> 16)
+    {
+      if (numSpT < 128)
+        numSpT = (numSpT << 1) | 1;
+      else
+        Cylinders = 65535; // overflow !
+    }
+  } while (Cylinders >> 16);
+
+  geometry->TotalCylinders  = Cylinders;
+  geometry->NumHeads        = numHeads;
+  geometry->SectorsPerTrack = numSpT;
+}
+
+int check_lvm(IORBH *pIorb, ULONG sector)
+{
+  DLA_Table_Sector *pDLA = (DLA_Table_Sector*)add_workspace(pIorb)->buf;
+  AD_INFO *ai = ad_infos + iorb_unit_adapter(pIorb);
+  GEOMETRY *geometry = ((IORB_GEOMETRY*)pIorb)->pGeometry;
+  int p = iorb_unit_port(pIorb);
+  int rc;
+
+  rc = ahci_exec_polled_cmd(ai, p, 0, 500, ATA_CMD_READ,
+         AP_SECTOR_28, sector-1,
+         AP_COUNT, 1,
+         AP_VADDR, (void *)pDLA, 512,
+         AP_DEVICE, 0x40,
+         AP_END);
+  if (rc) return 0;
+
+  DHEXDUMP(3,pDLA, sizeof(DLA_Table_Sector), "DLA sector %d:\n", sector-1);
+
+  if ((pDLA->DLA_Signature1 == DLA_TABLE_SIGNATURE1) && (pDLA->DLA_Signature2 == DLA_TABLE_SIGNATURE2)) {
+    DPRINTF(3,"is_lvm_geometry found at sector %d\n", sector-1);
+    geometry->TotalCylinders = pDLA->Cylinders;
+    geometry->NumHeads = pDLA->Heads_Per_Cylinder;
+    geometry->SectorsPerTrack = pDLA->Sectors_Per_Track;
+    geometry->TotalSectors = pDLA->Cylinders * pDLA->Heads_Per_Cylinder * pDLA->Sectors_Per_Track;
+    return 1;
+  }
+
+  return 0;
+}
+
+/******************************************************************************
+ * Try to read LVM information from the disk. If found, use the LVM geometry.
+ * This function will only work at init time. A better strategy would be to
+ * calculate the geometry during ahci_scan_ports and save it away and then just
+ * return the saved values when ata_get_geometry() is called.
+ */
+int is_lvm_geometry(IORBH *pIorb)
+{
+  GEOMETRY *geometry = ((IORB_GEOMETRY*)pIorb)->pGeometry;
+  ULONG sector;
+
+  if (init_complete) return 0; /* We cannot use ahci_exec_polled_cmd() after init_complete */
+
+  if (use_lvm_info)
+  {
+    #ifdef DEBUG
+    AD_INFO *ai = ad_infos + iorb_unit_adapter(pIorb);
+    int p = iorb_unit_port(pIorb);
+    int d = iorb_unit_device(pIorb);
+    DPRINTF(3,"is_lvm_geometry (%d.%d.%d)\n", ad_no(ai), p, d);
+    #endif
+
+    /* First check the sector reported by the hardware */
+    if (check_lvm(pIorb, geometry->SectorsPerTrack)) return 1;
+
+    for (sector = 255; sector >= 63; sector >>= 1)
+    {
+      if (sector == geometry->SectorsPerTrack) continue;
+      if (check_lvm(pIorb, sector)) return 1;
+    }
+  }
+
+  return 0;
+}
+
+/******************************************************************************
+ * Post processing function for ata_get_geometry(): convert the ATA identify
+ * information to OS/2 IOCC_GEOMETRY information.
+ */
+void ata_get_geometry_pp(IORBH FAR16DATA *vIorb, IORBH *pIorb)
+{
+  GEOMETRY *geometry = ((IORB_GEOMETRY*)pIorb)->pGeometry;
+  USHORT geometry_len =  ((IORB_GEOMETRY *)pIorb)->GeometryLen;
+  u16 *id_buf = add_workspace(pIorb)->buf;
+  int a = iorb_unit_adapter(pIorb);
+  int p = iorb_unit_port(pIorb);
+  char *Method;
+
+  /* Fill-in geometry information; the ATA-8 spec declares the geometry
+   * fields in the ATA ID buffer as obsolete but it's still the best
+   * guess in most cases. If the information stored in the geometry
+   * fields is apparently incorrect, we'll use the algorithm typically
+   * used by SCSI adapters and modern PC BIOS versions:
+   *
+   *  - 512 bytes per sector
+   *  - 255 heads
+   *  -  63 sectors per track (or 56 with the parameter "/4")
+   *  -   x cylinders (calculated)
+   *
+   * Please note that os2ahci currently does not natively support ATA sectors
+   * larger than 512 bytes, therefore relies on the translation logic built
+   * into the corresponding ATA disks. In order to prevent file systems that
+   * use block sizes larger than 512 bytes (FAT, JFS, ...) from ending up on
+   * incorrectly aligned physical sector accesses, hence using more physical
+   * I/Os than necessary, the command line parameter "/4" can be used to force
+   * a track size of 56 sectors. This way, partitions will start on 4K
+   * boundaries.
+   *
+   * Another limitation is that OS/2 has a 32-bit variable for the total number
+   * of sectors, limiting the maximum capacity to roughly 2TB. This is another
+   * issue that needs to be addressed sooner or later; large sectors could
+   * raise this limit to something like 8TB but this is not really much of a
+   * difference. Maybe there's something in later DDKs that allows more than
+   * 32 bits?
+   */
+  memset(geometry, 0x00, geometry_len);
+  geometry->BytesPerSector = ATA_SECTOR_SIZE;
+
+  /* extract total number of sectors */
+  if (id_buf[ATA_ID_CFS_ENABLE_2] & 0x400)
+  {
+    /* 48-bit LBA supported */
+    if (ATA_CAPACITY48_H(id_buf) != 0)
+    {
+      /* more than 32 bits for number of sectors */
+      dprintf(0,"warning: limiting disk %d.%d.%d to 2TB\n",
+              iorb_unit_adapter(pIorb), iorb_unit_port(pIorb),
+              iorb_unit_device(pIorb));
+      geometry->TotalSectors = 0xffffffffUL;
+    }
+    else
+    {
+      geometry->TotalSectors = ATA_CAPACITY48_L(id_buf);
+    }
+  }
+  else
+  {
+    /* 28-bit LBA */
+    geometry->TotalSectors = ATA_CAPACITY(id_buf) & 0x0fffffffUL;
+  }
+
+  Method = "None";
+  /* fabricate the remaining geometry fields */
+  if (track_size[a][p] != 0)
+  {
+    /* A specific track size has been requested for this port; this is
+     * typically done for disks with 4K sectors to make sure partitions
+     * start on 8-sector boundaries (parameter "/4").
+     */
+    geometry->NumHeads        = 255;
+    geometry->SectorsPerTrack = track_size[a][p];
+    geometry->TotalCylinders  = geometry->TotalSectors / ((u32) geometry->NumHeads * (u32) geometry->SectorsPerTrack);
+    Method = "Custom";
+  }
+  else if (CUR_HEADS(id_buf) > 0 && CUR_CYLS(id_buf) > 0 && CUR_SECTORS(id_buf) > 0 &&
+             CUR_CAPACITY(id_buf) == (u32) CUR_HEADS(id_buf) * (u32) CUR_CYLS(id_buf) * (u32) CUR_SECTORS(id_buf))
+  {
+    /* BIOS-supplied (aka "current") geometry values look valid */
+    geometry->NumHeads        = CUR_HEADS(id_buf);
+    geometry->SectorsPerTrack = CUR_SECTORS(id_buf);
+    geometry->TotalCylinders  = CUR_CYLS(id_buf);
+    Method = "BIOS";
+  }
+  else if (ATA_HEADS(id_buf) > 0 && ATA_CYLS(id_buf) > 0 && ATA_SECTORS(id_buf) > 0)
+  {
+    /* ATA-supplied values for geometry look valid */
+    geometry->NumHeads        = ATA_HEADS(id_buf);
+    geometry->SectorsPerTrack = ATA_SECTORS(id_buf);
+    geometry->TotalCylinders  = ATA_CYLS(id_buf);
+    Method = "ATA";
+  }
+  else
+  {
+    /* use typical SCSI geometry */
+    geometry->NumHeads        = 255;
+    geometry->SectorsPerTrack = 63;
+    geometry->TotalCylinders  = geometry->TotalSectors / ((u32) geometry->NumHeads * (u32) geometry->SectorsPerTrack);
+    Method = "SCSI";
+  }
+
+  DPRINTF(2,"Physical geometry: %d cylinders, %d heads, %d sectors per track (%dMB) (%s)\n",
+      geometry->TotalCylinders, geometry->NumHeads, geometry->SectorsPerTrack,
+      (geometry->TotalSectors / 2048), Method);
+
+  /* Fixup the geometry in case the geometry reported by the BIOS is bad */
+  if (adjust_cylinders(geometry, geometry->TotalSectors))
+  { // cylinder overflow
+    log_geom_calculate_LBA_assist(geometry, geometry->TotalSectors);
+    geometry->TotalSectors = (USHORT)(geometry->NumHeads * geometry->SectorsPerTrack) * (ULONG)geometry->TotalCylinders;
+  }
+  adjust_cylinders(geometry, geometry->TotalSectors);
+
+  DPRINTF(2,"Logical geometry: %d cylinders, %d heads, %d sectors per track (%dMB) (%s)\n",
+      geometry->TotalCylinders, geometry->NumHeads, geometry->SectorsPerTrack,
+      (geometry->TotalSectors / 2048), Method);
+
+  if (is_lvm_geometry(pIorb)) Method = "LVM";
+  ad_infos[a].ports[p].devs[0].dev_info.Cylinders = geometry->TotalCylinders;
+  ad_infos[a].ports[p].devs[0].dev_info.HeadsPerCylinder = geometry->NumHeads;
+  ad_infos[a].ports[p].devs[0].dev_info.SectorsPerTrack = geometry->SectorsPerTrack;
+  ad_infos[a].ports[p].devs[0].dev_info.TotalSectors = geometry->TotalSectors;
+  ad_infos[a].ports[p].devs[0].dev_info.Method = Method;
+
+  DPRINTF(2,"Reported geometry: %d cylinders, %d heads, %d sectors per track (%dMB) (%s)\n",
+      geometry->TotalCylinders, geometry->NumHeads, geometry->SectorsPerTrack,
+      (geometry->TotalSectors / 2048), Method);
+
+  /* tell interrupt handler that this IORB is complete */
+  add_workspace(pIorb)->complete = 1;
+}
+
+/******************************************************************************
+ * Test whether unit is ready.
+ */
+int ata_unit_ready(IORBH FAR16DATA *vIorb, IORBH *pIorb, int slot)
+{
+  /* This is a NOP for ATA devices (at least right now); returning an error
+   * without setting an error code means ahci_exec_iorb() will not queue any
+   * HW command and the IORB will complete successfully.
+   */
+  ((IORB_UNIT_STATUS *)pIorb)->UnitStatus = US_READY | US_POWER;
+  return(-1);
+}
+
+/******************************************************************************
+ * Read sectors from AHCI device.
+ */
+int ata_read(IORBH FAR16DATA *vIorb, IORBH *pIorb, int slot)
+{
+  IORB_EXECUTEIO *io = (IORB_EXECUTEIO *)pIorb;
+  SCATGATENTRY *pSGList = (SCATGATENTRY*)Far16ToFlat(io->pSGList);
+  AD_INFO *ai = ad_infos + iorb_unit_adapter(pIorb);
+  ULONG sector = io->RBA + io->BlocksXferred;
+  USHORT count = io->BlockCount - io->BlocksXferred;
+  USHORT sg_indx;
+  USHORT sg_cnt;
+  int p = iorb_unit_port(pIorb);
+  int d = iorb_unit_device(pIorb);
+  int rc;
+
+  if (io->BlockCount == 0)
+  {
+    /* NOP; return -1 without error in IORB to indicate success */
+    return(-1);
+  }
+
+  if (add_workspace(pIorb)->unaligned)
+  {
+    /* unaligned S/G addresses present; need to use double buffers */
+    return(ata_read_unaligned(pIorb, 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
+   * and this element is already larger than what we can derive from the sector
+   * count, we'll adjust that element.
+   */
+  if (io->BlocksXferred == 0 && io->cSGList == 1 &&
+      pSGList[0].XferBufLen > (ULONG) io->BlockCount * io->BlockSize)
+  {
+    pSGList[0].XferBufLen = (ULONG) io->BlockCount * io->BlockSize;
+  }
+
+  /* prepare read 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 ((rc = ata_cmd_read(pIorb, ai, p, d, slot, sector, count,
+                           pSGList + sg_indx, sg_cnt)) > 0)
+    {
+      /* couldn't map all S/G elements */
+      ata_max_sg_cnt(io, sg_indx, rc, &sg_cnt, &count);
+    }
+  } while (rc > 0 && sg_cnt > 0);
+
+  if (rc == 0)
+  {
+    add_workspace(pIorb)->blocks = count;
+    add_workspace(pIorb)->ppfunc = ata_read_pp;
+  }
+  else if (rc > 0)
+  {
+    iorb_seterr(pIorb, IOERR_CMD_SGLIST_BAD);
+  }
+  else if (rc == ATA_CMD_UNALIGNED_ADDR)
+  {
+    /* unaligned S/G addresses detected; need to use double buffers */
+    add_workspace(pIorb)->unaligned = 1;
+    return(ata_read_unaligned(pIorb, slot));
+
+  }
+  else
+  {
+    iorb_seterr(pIorb, 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 *pIorb, int slot)
+{
+  IORB_EXECUTEIO *io = (IORB_EXECUTEIO *)pIorb;
+  ADD_WORKSPACE *aws = add_workspace(pIorb);
+  AD_INFO *ai = ad_infos + iorb_unit_adapter(pIorb);
+  ULONG sector = io->RBA + io->BlocksXferred;
+  SCATGATENTRY sg_single;
+  int p = iorb_unit_port(pIorb);
+  int d = iorb_unit_device(pIorb);
+  int rc;
+
+  DPRINTF(7,"ata_read_unaligned(%d.%d.%d, %d)\n", ad_no(ai), p, d, sector);
+  ai->ports[p].unaligned_read_count++;
+
+  /* allocate transfer buffer */
+  if ((aws->buf = MemAlloc(io->BlockSize)) == NULL)
+  {
+    iorb_seterr(pIorb, IOERR_CMD_SW_RESOURCE);
+    return(-1);
+  }
+
+  /* prepare read command using transfer buffer */
+  sg_single.ppXferBuf = MemPhysAdr(aws->buf);
+  sg_single.XferBufLen = io->BlockSize;
+  rc = ata_cmd_read(pIorb, ai, p, d, slot, sector, 1, &sg_single, 1);
+
+  if (rc == 0) {
+    add_workspace(pIorb)->blocks = 1;
+    add_workspace(pIorb)->ppfunc = ata_read_pp;
+
+  } else if (rc > 0) {
+    iorb_seterr(pIorb, IOERR_CMD_SGLIST_BAD);
+
+  } else {
+    iorb_seterr(pIorb, IOERR_CMD_ADD_SOFTWARE_FAILURE);
+  }
+
+  return(rc);
+}
+
+/******************************************************************************
+ * 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. It also
+ * takes care of copying data from the transfer buffer for unaligned reads.
+ */
+void ata_read_pp(IORBH FAR16DATA *vIorb, IORBH *pIorb)
+{
+  IORB_EXECUTEIO *io = (IORB_EXECUTEIO *)pIorb;
+  SCATGATENTRY *pSGList = (SCATGATENTRY*)Far16ToFlat(io->pSGList);
+  ADD_WORKSPACE *aws = add_workspace(pIorb);
+
+  if (aws->unaligned)
+  {
+    /* copy transfer buffer to corresponding physical address in S/G list */
+    sg_memcpy(pSGList, io->cSGList,
+              (ULONG) io->BlocksXferred * (ULONG) io->BlockSize,
+              aws->buf, io->BlockSize, BUF_TO_SG);
+  }
+
+  io->BlocksXferred += add_workspace(pIorb)->blocks;
+  DPRINTF(7,"ata_read_pp(): blocks transferred = %d\n", io->BlocksXferred);
+
+  if (io->BlocksXferred >= io->BlockCount)
+  {
+    /* we're done; tell IRQ handler the IORB is complete */
+    add_workspace(pIorb)->complete = 1;
+  }
+  else
+  {
+    /* requeue this IORB for next iteration */
+    iorb_requeue(pIorb);
+  }
+}
+
+/******************************************************************************
+ * Verify readability of sectors on ATA device.
+ */
+int ata_verify(IORBH FAR16DATA *vIorb, IORBH *pIorb, int slot)
+{
+  IORB_EXECUTEIO *io = (IORB_EXECUTEIO *)pIorb;
+  AD_INFO *ai = ad_infos + iorb_unit_adapter(pIorb);
+  int p = iorb_unit_port(pIorb);
+  int d = iorb_unit_device(pIorb);
+  int rc;
+
+  if (io->BlockCount == 0)
+  {
+    /* NOP; return -1 without error in IORB to indicate success */
+    return(-1);
+  }
+
+  /* prepare verify command */
+  if (io->RBA >= (1UL << 28) || io->BlockCount > 256)
+  {
+    /* need LBA48 for this command */
+    if (!ai->ports[p].devs[d].lba48) {
+      iorb_seterr(pIorb, IOERR_RBA_LIMIT);
+      return(-1);
+    }
+    rc = ata_cmd(ai, p, d, slot, ATA_CMD_VERIFY_EXT,
+                 AP_SECTOR_48, io->RBA, 0,
+                 AP_COUNT,     io->BlockCount,
+                 AP_DEVICE,    0x40,
+                 AP_END);
+  } else {
+    rc = ata_cmd(ai, p, d, slot, ATA_CMD_VERIFY,
+                 AP_SECTOR_28, io->RBA,
+                 AP_COUNT,     io->BlockCount & 0xffU,
+                 AP_DEVICE,    0x40,
+                 AP_END);
+  }
+
+  return(rc);
+}
+
+/******************************************************************************
+ * Write sectors to AHCI device.
+ */
+int ata_write(IORBH FAR16DATA *vIorb, IORBH *pIorb, int slot)
+{
+  IORB_EXECUTEIO *io = (IORB_EXECUTEIO *)pIorb;
+  SCATGATENTRY *pSGList = (SCATGATENTRY*)Far16ToFlat(io->pSGList);
+  AD_INFO *ai = ad_infos + iorb_unit_adapter(pIorb);
+  ULONG sector = io->RBA + io->BlocksXferred;
+  USHORT count = io->BlockCount - io->BlocksXferred;
+  USHORT sg_indx;
+  USHORT sg_cnt;
+  int p = iorb_unit_port(pIorb);
+  int d = iorb_unit_device(pIorb);
+  int rc;
+
+  if (io->BlockCount == 0)
+  {
+    /* NOP; return -1 without error in IORB to indicate success */
+    return(-1);
+  }
+
+  if (add_workspace(pIorb)->unaligned)
+  {
+    /* unaligned S/G addresses present; need to use double buffers */
+    return(ata_write_unaligned(pIorb, 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 ((rc = ata_cmd_write(pIorb, ai, p, d, slot, sector, count,
+                            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);
+    }
+  } while (rc > 0 && sg_cnt > 0);
+
+  if (rc == 0)
+  {
+    add_workspace(pIorb)->blocks = count;
+    add_workspace(pIorb)->ppfunc = ata_write_pp;
+  }
+  else if (rc > 0)
+  {
+    iorb_seterr(pIorb, IOERR_CMD_SGLIST_BAD);
+  }
+  else if (rc == ATA_CMD_UNALIGNED_ADDR)
+  {
+    /* unaligned S/G addresses detected; need to use double buffers */
+    add_workspace(pIorb)->unaligned = 1;
+    return(ata_write_unaligned(pIorb, slot));
+  }
+  else
+  {
+    iorb_seterr(pIorb, IOERR_CMD_ADD_SOFTWARE_FAILURE);
+  }
+
+  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 *pIorb, int slot)
+{
+  IORB_EXECUTEIO *io = (IORB_EXECUTEIO *)pIorb;
+  SCATGATENTRY *pSGList = (SCATGATENTRY*)Far16ToFlat(io->pSGList);
+  ADD_WORKSPACE *aws = add_workspace(pIorb);
+  AD_INFO *ai = ad_infos + iorb_unit_adapter(pIorb);
+  ULONG sector = io->RBA + io->BlocksXferred;
+  SCATGATENTRY sg_single;
+  int p = iorb_unit_port(pIorb);
+  int d = iorb_unit_device(pIorb);
+  int rc;
+
+  DPRINTF(7,"ata_write_unaligned(%d.%d.%d, %d)\n", ad_no(ai), p, d, sector);
+
+  /* allocate transfer buffer */
+  if ((aws->buf = MemAlloc(io->BlockSize)) == NULL)
+  {
+    iorb_seterr(pIorb, IOERR_CMD_SW_RESOURCE);
+    return(-1);
+  }
+
+  /* copy next sector from S/G list to transfer buffer */
+  sg_memcpy(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 = MemPhysAdr(aws->buf);
+  sg_single.XferBufLen = io->BlockSize;
+  rc = ata_cmd_write(pIorb, ai, p, d, slot, sector, 1, &sg_single, 1,
+                     io->Flags & XIO_DISABLE_HW_WRITE_CACHE);
+
+  if (rc == 0)
+  {
+    add_workspace(pIorb)->blocks = 1;
+    add_workspace(pIorb)->ppfunc = ata_write_pp;
+  }
+  else if (rc > 0)
+  {
+    iorb_seterr(pIorb, IOERR_CMD_SGLIST_BAD);
+  }
+  else
+  {
+    iorb_seterr(pIorb, IOERR_CMD_ADD_SOFTWARE_FAILURE);
+  }
+
+  return(rc);
+}
+
+
+/******************************************************************************
+ * Post processing function for ata_write(); 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.
+ */
+void ata_write_pp(IORBH FAR16DATA *vIorb, IORBH *pIorb)
+{
+  IORB_EXECUTEIO *io = (IORB_EXECUTEIO *)pIorb;
+
+  io->BlocksXferred += add_workspace(pIorb)->blocks;
+  DPRINTF(7,"ata_write_pp(): blocks transferred = %d\n", io->BlocksXferred);
+
+  if (io->BlocksXferred >= io->BlockCount)
+  {
+    /* we're done; tell IRQ handler the IORB is complete */
+    add_workspace(pIorb)->complete = 1;
+  }
+  else
+  {
+    /* requeue this IORB for next iteration */
+    iorb_requeue(pIorb);
+  }
+}
+
+/******************************************************************************
+ * Execute ATA command.
+ */
+int ata_execute_ata(IORBH FAR16DATA *vIorb, IORBH *pIorb, int slot)
+{
+  IORB_ADAPTER_PASSTHRU *apt = (IORB_ADAPTER_PASSTHRU *)pIorb;
+  SCATGATENTRY *pSGList = (SCATGATENTRY*)Far16ToFlat(apt->pSGList);
+  AD_INFO *ai = ad_infos + iorb_unit_adapter(pIorb);
+  int p = iorb_unit_port(pIorb);
+  int d = iorb_unit_device(pIorb);
+  int rc;
+
+  if (apt->ControllerCmdLen != sizeof(ATA_CMD))
+  {
+    iorb_seterr(pIorb, IOERR_CMD_SYNTAX);
+    return(-1);
+  }
+
+  rc = ata_cmd(ai, p, d, slot, 0,
+               AP_SGLIST,   pSGList, apt->cSGList,
+               AP_ATA_CMD,  Far16ToFlat(apt->pControllerCmd),
+               AP_WRITE,    !(apt->Flags & PT_DIRECTION_IN),
+               AP_END);
+
+  if (rc == 0)
+  {
+    add_workspace(pIorb)->ppfunc = ata_execute_ata_pp;
+  }
+
+  return(rc);
+}
+
+/******************************************************************************
+ * Post processing function for ata_execute_ata(); the main purpose of this
+ * function is to copy the received D2H FIS (i.e. the device registers after
+ * command completion) back to the ATA command structure.
+ *
+ * See ata_cmd_to_fis() for an explanation of the mapping.
+ */
+void ata_execute_ata_pp(IORBH FAR16DATA *vIorb, IORBH *pIorb)
+{
+  AHCI_PORT_DMA *dma_base;
+  ATA_CMD *cmd;
+  AD_INFO *ai;
+  u8 *fis;
+  int p;
+
+  /* get address of D2H FIS */
+  ai = ad_infos + iorb_unit_adapter(pIorb);
+  p = iorb_unit_port(pIorb);
+  dma_base = port_dma_base(ai, p);
+  fis = dma_base->rx_fis + 0x40;
+
+  if (fis[0] != 0x34)
+  {
+    /* this is not a D2H FIS - give up silently */
+    DPRINTF(3,"ata_execute_ata_pp(): D2H FIS type incorrect: %d\n", fis[0]);
+    add_workspace(pIorb)->complete = 1;
+    return;
+  }
+
+  /* map D2H FIS to the original ATA controller command structure */
+  cmd = (ATA_CMD *)Far16ToFlat(((IORB_ADAPTER_PASSTHRU*)pIorb)->pControllerCmd);
+
+  cmd->cmd      = fis[2];
+  cmd->device   = fis[7];
+  cmd->features = ((u16) fis[3])
+                | ((u16) fis[11]);
+  cmd->lba_l    = ((u32) fis[4])
+                | ((u32) fis[5] << 8)
+                | ((u32) fis[6] << 16)
+                | ((u32) fis[8] << 24);
+  cmd->lba_h    = ((u16) fis[9])
+                | ((u16) fis[10] << 8);
+  cmd->count    = ((u16) fis[12])
+                | ((u16) fis[13] << 8);
+
+  DHEXDUMP(0,cmd, sizeof(*cmd), "ahci_execute_ata_pp(): cmd after completion:\n");
+
+  /* signal completion to interrupt handler */
+  add_workspace(pIorb)->complete = 1;
+}
+
+/******************************************************************************
+ * Request sense information for a failed command. Since there is no "request
+ * sense" command for ATA devices, we need to read the current error code from
+ * the AHCI task file register and fabricate the sense information.
+ *
+ * NOTES:
+ *
+ *   - This function must be called right after an ATA command has failed and
+ *     before any other commands are queued on the corresponding port. This
+ *     function is typically called in the port restart context hook which is
+ *     triggered by an AHCI error interrupt.
+ *
+ *   - The ATA error bits are a complete mess. We'll try and catch the most
+ *     interesting error codes (such as medium errors) and report everything
+ *     else with a generic error code.
+ */
+int ata_req_sense(IORBH FAR16DATA *vIorb, IORBH *pIorb, int slot)
+{
+  AD_INFO *ai = ad_infos + iorb_unit_adapter(pIorb);
+  u8 *port_mmio = port_base(ai, iorb_unit_port(pIorb));
+  u32 tf_data = readl(port_mmio + PORT_TFDATA);
+  u8 err = (tf_data >> 8);
+  u8 sts = (tf_data);
+
+  if (sts & ATA_ERR)
+  {
+    if (sts & ATA_DF)
+    {
+      /* there is a device-specific error condition */
+      if (err & ATA_ICRC)
+      {
+        iorb_seterr(pIorb, IOERR_ADAPTER_DEVICEBUSCHECK);
+      }
+      else if (err & ATA_UNC)
+      {
+        iorb_seterr(pIorb, IOERR_MEDIA);
+      }
+      else if (err & ATA_IDNF)
+      {
+        iorb_seterr(pIorb, IOERR_RBA_ADDRESSING_ERROR);
+      }
+      else
+      {
+        iorb_seterr(pIorb, IOERR_DEVICE_NONSPECIFIC);
+      }
+
+    }
+    else
+    {
+      iorb_seterr(pIorb, IOERR_DEVICE_NONSPECIFIC);
+    }
+  }
+  else
+  {
+    /* this function only gets called when we received an error interrupt */
+    iorb_seterr(pIorb, IOERR_DEVICE_NONSPECIFIC);
+  }
+
+  /* Return an error to indicate there's no HW command to be submitted and
+   * that the IORB can be completed "as is" (the upstream code expects the
+   * IORB error code, if any, to be set when this happens and this is exactly
+   * what this function is all about).
+   */
+  return(-1);
+}
+
+/******************************************************************************
+ * Extract vendor and device name from an ATA INDENTIFY buffer. Since strings
+ * in the indentify buffer are byte-swapped, we need to swap them back.
+ */
+char *ata_dev_name(u16 *id_buf)
+{
+  static char dev_name[ATA_ID_PROD_LEN + 1];
+  char *t = dev_name;
+  char *s = (char *) (id_buf + ATA_ID_PROD);
+  int i;
+
+  dev_name[sizeof(dev_name)-1] = '\0';
+
+  for (i = 0; i < ATA_ID_PROD_LEN / 2; i++) {
+    *(t++) = s[1];
+    *(t++) = s[0];
+    s += 2;
+  }
+
+  return(dev_name);
+}
+
+/******************************************************************************
+ * 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 *pIorb, AD_INFO *ai, int p, int d, int slot,
+                        ULONG sector, ULONG count, SCATGATENTRY *sg_list,
+                        ULONG sg_cnt)
+{
+  int rc;
+
+  if (sector >= (1UL << 28) || count > 256 || add_workspace(pIorb)->is_ncq)
+  {
+    /* need LBA48 for this command */
+    if (!ai->ports[p].devs[d].lba48)
+    {
+      iorb_seterr(pIorb, IOERR_RBA_LIMIT);
+      return(-1);
+    }
+    if (add_workspace(pIorb)->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, sector, 0,
+                   AP_FEATURES,  count,
+                   AP_COUNT,     (slot << 3), /* tag == slot */
+                   AP_SGLIST,    sg_list, sg_cnt,
+                   AP_DEVICE,    0x40,
+                   AP_END);
+    }
+    else
+    {
+      rc = ata_cmd(ai, p, d, slot, ATA_CMD_READ_EXT,
+                   AP_SECTOR_48, sector, 0,
+                   AP_COUNT,     count,
+                   AP_SGLIST,    sg_list, sg_cnt,
+                   AP_DEVICE,    0x40,
+                   AP_END);
+    }
+
+  }
+  else
+  {
+    rc = ata_cmd(ai, p, d, slot, ATA_CMD_READ,
+                 AP_SECTOR_28, sector,
+                 AP_COUNT,     count & 0xffU,
+                 AP_SGLIST,    sg_list, 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 *pIorb, AD_INFO *ai, int p, int d, int slot,
+                         ULONG sector, ULONG count, SCATGATENTRY *sg_list,
+                         ULONG sg_cnt, int write_through)
+{
+  int rc;
+
+  if (sector >= (1UL << 28) || count > 256 || add_workspace(pIorb)->is_ncq)
+  {
+    /* need LBA48 for this command */
+    if (!ai->ports[p].devs[d].lba48)
+    {
+      iorb_seterr(pIorb, IOERR_RBA_LIMIT);
+      return(-1);
+    }
+    if (add_workspace(pIorb)->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, sector, 0,
+                   AP_FEATURES,  count,
+                   /* tag = slot */
+                   AP_COUNT,     (slot << 3),
+                   AP_SGLIST,    sg_list, sg_cnt,
+                   AP_DEVICE,    0x40,
+                   /* force unit access */
+                   AP_DEVICE,    (write_through && !force_write_cache) ? 0x80 : 0,
+                   AP_WRITE,     1,
+                   AP_END);
+    }
+    else
+    {
+      rc = ata_cmd(ai, p, d, slot, ATA_CMD_WRITE_EXT,
+                   AP_SECTOR_48, sector, 0,
+                   AP_COUNT,     count,
+                   AP_SGLIST,    sg_list, sg_cnt,
+                   AP_DEVICE,    0x40,
+                   AP_WRITE,     1,
+                   AP_END);
+    }
+  }
+  else
+  {
+    rc = ata_cmd(ai, p, d, slot, ATA_CMD_WRITE,
+                 AP_SECTOR_28, sector,
+                 AP_COUNT,     count & 0xffU,
+                 AP_SGLIST,    sg_list, sg_cnt,
+                 AP_DEVICE,    0x40,
+                 AP_WRITE,     1,
+                 AP_END);
+  }
+
+  return(rc);
+}
+
+/******************************************************************************
+ * Copy block from S/G list to virtual address or vice versa.
+ */
+void sg_memcpy(SCATGATENTRY *sg_list, USHORT sg_cnt, ULONG sg_off,
+               void *buf, USHORT len, SG_MEMCPY_DIRECTION dir)
+{
+  USHORT i;
+  USHORT l;
+  ULONG phys_addr;
+  ULONG pos = 0;
+  char *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 (Dev32Help_PhysToLin(phys_addr, l, (PVOID) &p))
+      {
+        panic("sg_memcpy(): DevHelp_PhysToLin() failed");
+      }
+      if (dir == SG_TO_BUF)
+      {
+        memcpy(buf, p, l);
+      }
+      else
+      {
+        memcpy(p, buf, l);
+      }
+      sg_off += l;
+      buf = (char *) buf + l;
+      len -= l;
+    }
+
+    pos += sg_list[i].XferBufLen;
+  }
+}
+
+/******************************************************************************
+ * Halt processing by submitting an internal error. This is a last resort and
+ * should only be called when the system state is corrupt.
+ */
+void panic(char *msg)
+{
+  Dev32Help_InternalError(msg, strlen(msg));
+}
+
Index: /tags/2.02/src/os2ahci/ata.h
===================================================================
--- /tags/2.02/src/os2ahci/ata.h	(revision 189)
+++ /tags/2.02/src/os2ahci/ata.h	(revision 189)
@@ -0,0 +1,514 @@
+/******************************************************************************
+ * ata.h - ATA structures and macros for os2ahci driver
+ *
+ * Copyright (c) 2011 thi.guten Software Development
+ * Copyright (c) 2011 Mensys B.V.
+ * Copyright (c) 2013-2016 David Azarewicz
+ *
+ * Authors: Christian Mueller, Markus Thielen
+ *
+ * Parts copied from/inspired by the Linux AHCI driver;
+ * those parts are (c) Linux AHCI/ATA maintainers
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+/* -------------------------- macros and constants ------------------------- */
+
+/******************************************************************************
+ * Right now, os2ahci uses a fixed sector size of 512 bytes for hard disks.
+ * This may change in the future...
+ */
+#define ATA_SECTOR_SIZE    512
+
+/******************************************************************************
+ * Macros to access geometry values in the ATA ID buffer
+ */
+#define ATA_CYLS(id_buf)            *((u16 *) (id_buf + ATA_ID_CYLS))
+#define ATA_HEADS(id_buf)           *((u16 *) (id_buf + ATA_ID_HEADS))
+#define ATA_SECTORS(id_buf)         *((u16 *) (id_buf + ATA_ID_SECTORS))
+#define ATA_CAPACITY(id_buf)        *((u32 *) (id_buf + ATA_ID_LBA_CAPACITY))
+
+#define ATA_CAPACITY48_L(id_buf)    *((u32 *) (id_buf + ATA_ID_LBA_CAPACITY_2))
+#define ATA_CAPACITY48_H(id_buf)    *((u32 *) (id_buf + ATA_ID_LBA_CAPACITY_2 + 2))
+
+#define CUR_CYLS(id_buf)            *((u16 *) (id_buf + ATA_ID_CUR_CYLS))
+#define CUR_HEADS(id_buf)           *((u16 *) (id_buf + ATA_ID_CUR_HEADS))
+#define CUR_SECTORS(id_buf)         *((u16 *) (id_buf + ATA_ID_CUR_SECTORS))
+#define CUR_CAPACITY(id_buf)        *((u32 *) (id_buf + ATA_ID_CUR_CAPACITY))
+
+
+/******************************************************************************
+ * ATA IDENTIFY offsets and constants. The ATA IDENTIFY response is a bit of a
+ * mess and after struggling to put this into some structure, I discovered the
+ * Linux folks simply defined offsets and constants to those fields which are
+ * actually needed. Thus, I copied the relevant bits from ata.h, replacing
+ * the enum with precompiler definitions because our enums are signed 16 bits.
+ *
+ * Please note that strings such as the product name are stored using byte-
+ * swapped 16-bit words.
+ */
+#define ATA_ID_WORDS            256
+#define ATA_ID_CONFIG           0
+#define ATA_ID_CYLS             1
+#define ATA_ID_HEADS            3
+#define ATA_ID_SECTORS          6
+#define ATA_ID_SERNO            10
+#define ATA_ID_BUF_SIZE         21
+#define ATA_ID_FW_REV           23
+#define ATA_ID_PROD             27
+#define ATA_ID_MAX_MULTSECT     47
+#define ATA_ID_DWORD_IO         48
+#define ATA_ID_CAPABILITY       49
+#define ATA_ID_OLD_PIO_MODES    51
+#define ATA_ID_OLD_DMA_MODES    52
+#define ATA_ID_FIELD_VALID      53
+#define ATA_ID_CUR_CYLS         54
+#define ATA_ID_CUR_HEADS        55
+#define ATA_ID_CUR_SECTORS      56
+#define ATA_ID_CUR_CAPACITY     57
+#define ATA_ID_MULTSECT         59
+#define ATA_ID_LBA_CAPACITY     60
+#define ATA_ID_SWDMA_MODES      62
+#define ATA_ID_MWDMA_MODES      63
+#define ATA_ID_PIO_MODES        64
+#define ATA_ID_EIDE_DMA_MIN     65
+#define ATA_ID_EIDE_DMA_TIME    66
+#define ATA_ID_EIDE_PIO         67
+#define ATA_ID_EIDE_PIO_IORDY   68
+#define ATA_ID_ADDITIONAL_SUPP  69
+#define ATA_ID_QUEUE_DEPTH      75
+#define ATA_ID_MAJOR_VER        80
+#define ATA_ID_COMMAND_SET_1    82
+#define ATA_ID_COMMAND_SET_2    83
+#define ATA_ID_CFSSE            84
+#define ATA_ID_CFSSE            84
+#define ATA_ID_CFS_ENABLE_1     85
+#define ATA_ID_CFS_ENABLE_2     86
+#define ATA_ID_CSF_DEFAULT      87
+#define ATA_ID_UDMA_MODES       88
+#define ATA_ID_HW_CONFIG        93
+#define ATA_ID_SPG              98
+#define ATA_ID_LBA_CAPACITY_2   100
+#define ATA_ID_SECTOR_SIZE      106
+#define ATA_ID_LAST_LUN         126
+#define ATA_ID_DLF              128
+#define ATA_ID_CSFO             129
+#define ATA_ID_CFA_POWER        160
+#define ATA_ID_CFA_KEY_MGMT     162
+#define ATA_ID_CFA_MODES        163
+#define ATA_ID_DATA_SET_MGMT    169
+#define ATA_ID_ROT_SPEED        217
+#define ATA_ID_PIO4             (1U << 1)
+
+#define ATA_ID_SERNO_LEN        20
+#define ATA_ID_FW_REV_LEN       8
+#define ATA_ID_PROD_LEN         40
+
+#define ATA_PCI_CTL_OFS         2
+
+#define ATA_PIO0                (1U << 0)
+#define ATA_PIO1                ATA_PIO0 | (1U << 1)
+#define ATA_PIO2                ATA_PIO1 | (1U << 2)
+#define ATA_PIO3                ATA_PIO2 | (1U << 3)
+#define ATA_PIO4                ATA_PIO3 | (1U << 4)
+#define ATA_PIO5                ATA_PIO4 | (1U << 5)
+#define ATA_PIO6                ATA_PIO5 | (1U << 6)
+
+#define ATA_PIO4_ONLY           (1U << 4)
+
+#define ATA_SWDMA0              (1U << 0)
+#define ATA_SWDMA1              ATA_SWDMA0 | (1U << 1)
+#define ATA_SWDMA2              ATA_SWDMA1 | (1U << 2)
+
+#define ATA_SWDMA2_ONLY         (1U << 2)
+
+#define ATA_MWDMA0              (1U << 0)
+#define ATA_MWDMA1              ATA_MWDMA0 | (1U << 1)
+#define ATA_MWDMA2              ATA_MWDMA1 | (1U << 2)
+#define ATA_MWDMA3              ATA_MWDMA2 | (1U << 3)
+#define ATA_MWDMA4              ATA_MWDMA3 | (1U << 4)
+
+#define ATA_MWDMA12_ONLY        (1U << 1) | (1U << 2)
+#define ATA_MWDMA2_ONLY         (1U << 2)
+
+#define ATA_UDMA0               (1U << 0)
+#define ATA_UDMA1               ATA_UDMA0 | (1U << 1)
+#define ATA_UDMA2               ATA_UDMA1 | (1U << 2)
+#define ATA_UDMA3               ATA_UDMA2 | (1U << 3)
+#define ATA_UDMA4               ATA_UDMA3 | (1U << 4)
+#define ATA_UDMA5               ATA_UDMA4 | (1U << 5)
+#define ATA_UDMA6               ATA_UDMA5 | (1U << 6)
+
+/******************************************************************************
+ * Miscellanous constants copied from the Linux LIBATA code. Again, the enums
+ * have been converted to preprocessor definitions to avoid problems with
+ * signed 16-bit integers.
+ *
+ * Please note that Linux supports all kinds of IDE/EIDE/ATA/SATA/... devices
+ * in LIBATA while we only need the ATA-8 bits, and those only as far as they
+ * are relevant to AHCI.
+ */
+
+/* bits in ATA command block registers */
+#define ATA_HOB                 (1U << 7)     /* LBA48 selector */
+#define ATA_NIEN                (1U << 1)     /* disable-irq flag */
+#define ATA_LBA                 (1U << 6)     /* LBA28 selector */
+#define ATA_DEV1                (1U << 4)     /* Select Device 1 (slave) */
+#define ATA_DEVICE_OBS          (1U << 7) | (1U << 5) /* obs bits in dev reg */
+#define ATA_DEVCTL_OBS          (1U << 3)     /* obsolete bit in devctl reg */
+#define ATA_BUSY                (1U << 7)     /* BSY status bit */
+#define ATA_DRDY                (1U << 6)     /* device ready */
+#define ATA_DF                  (1U << 5)     /* device fault */
+#define ATA_DSC                 (1U << 4)     /* drive seek complete */
+#define ATA_DRQ                 (1U << 3)     /* data request i/o */
+#define ATA_CORR                (1U << 2)     /* corrected data error */
+#define ATA_IDX                 (1U << 1)     /* index */
+#define ATA_ERR                 (1U << 0)     /* have an error */
+#define ATA_SRST                (1U << 2)     /* software reset */
+#define ATA_ICRC                (1U << 7)     /* interface CRC error */
+#define ATA_BBK                 ATA_ICRC      /* pre-EIDE: block marked bad */
+#define ATA_UNC                 (1U << 6)     /* uncorrectable media error */
+#define ATA_MC                  (1U << 5)     /* media changed */
+#define ATA_IDNF                (1U << 4)     /* ID not found */
+#define ATA_MCR                 (1U << 3)     /* media change requested */
+#define ATA_ABORTED             (1U << 2)     /* command aborted */
+#define ATA_TRK0NF              (1U << 1)     /* track 0 not found */
+#define ATA_AMNF                (1U << 0)     /* address mark not found */
+#define ATAPI_LFS               0xF0U         /* last failed sense */
+#define ATAPI_EOM               ATA_TRK0NF    /* end of media */
+#define ATAPI_ILI               ATA_AMNF      /* illegal length indication */
+#define ATAPI_IO                (1U << 1)
+#define ATAPI_COD               (1U << 0)
+
+/* ATA device commands */
+#define ATA_CMD_DEV_RESET               0x08  /* ATAPI device reset */
+#define ATA_CMD_CHK_POWER               0xE5  /* check power mode */
+#define ATA_CMD_STANDBY                 0xE2  /* place in standby power mode */
+#define ATA_CMD_IDLE                    0xE3  /* place in idle power mode */
+#define ATA_CMD_EDD                     0x90  /* execute device diagnostic */
+#define ATA_CMD_DOWNLOAD_MICRO          0x92
+#define ATA_CMD_NOP                     0x00
+#define ATA_CMD_FLUSH                   0xE7
+#define ATA_CMD_FLUSH_EXT               0xEA
+#define ATA_CMD_ID_ATA                  0xEC
+#define ATA_CMD_ID_ATAPI                0xA1
+#define ATA_CMD_SERVICE                 0xA2
+#define ATA_CMD_READ                    0xC8
+#define ATA_CMD_READ_EXT                0x25
+#define ATA_CMD_READ_QUEUED             0x26
+#define ATA_CMD_READ_STREAM_EXT         0x2B
+#define ATA_CMD_READ_STREAM_DMA_EXT     0x2A
+#define ATA_CMD_WRITE                   0xCA
+#define ATA_CMD_WRITE_EXT               0x35
+#define ATA_CMD_WRITE_QUEUED            0x36
+#define ATA_CMD_WRITE_STREAM_EXT        0x3B
+#define ATA_CMD_WRITE_STREAM_DMA_EXT    0x3A
+#define ATA_CMD_WRITE_FUA_EXT           0x3D
+#define ATA_CMD_WRITE_QUEUED_FUA_EXT    0x3E
+#define ATA_CMD_FPDMA_READ              0x60
+#define ATA_CMD_FPDMA_WRITE             0x61
+#define ATA_CMD_PIO_READ                0x20
+#define ATA_CMD_PIO_READ_EXT            0x24
+#define ATA_CMD_PIO_WRITE               0x30
+#define ATA_CMD_PIO_WRITE_EXT           0x34
+#define ATA_CMD_READ_MULTI              0xC4
+#define ATA_CMD_READ_MULTI_EXT          0x29
+#define ATA_CMD_WRITE_MULTI             0xC5
+#define ATA_CMD_WRITE_MULTI_EXT         0x39
+#define ATA_CMD_WRITE_MULTI_FUA_EXT     0xCE
+#define ATA_CMD_SET_FEATURES            0xEF
+#define ATA_CMD_SET_MULTI               0xC6
+#define ATA_CMD_PACKET                  0xA0
+#define ATA_CMD_VERIFY                  0x40
+#define ATA_CMD_VERIFY_EXT              0x42
+#define ATA_CMD_WRITE_UNCORR_EXT        0x45
+#define ATA_CMD_STANDBYNOW1             0xE0
+#define ATA_CMD_IDLEIMMEDIATE           0xE1
+#define ATA_CMD_SLEEP                   0xE6
+#define ATA_CMD_INIT_DEV_PARAMS         0x91
+#define ATA_CMD_READ_NATIVE_MAX         0xF8
+#define ATA_CMD_READ_NATIVE_MAX_EXT     0x27
+#define ATA_CMD_SET_MAX                 0xF9
+#define ATA_CMD_SET_MAX_EXT             0x37
+#define ATA_CMD_READ_LOG_EXT            0x2F
+#define ATA_CMD_WRITE_LOG_EXT           0x3F
+#define ATA_CMD_READ_LOG_DMA_EXT        0x47
+#define ATA_CMD_WRITE_LOG_DMA_EXT       0x57
+#define ATA_CMD_TRUSTED_RCV             0x5C
+#define ATA_CMD_TRUSTED_RCV_DMA         0x5D
+#define ATA_CMD_TRUSTED_SND             0x5E
+#define ATA_CMD_TRUSTED_SND_DMA         0x5F
+#define ATA_CMD_PMP_READ                0xE4
+#define ATA_CMD_PMP_WRITE               0xE8
+#define ATA_CMD_CONF_OVERLAY            0xB1
+#define ATA_CMD_SEC_SET_PASS            0xF1
+#define ATA_CMD_SEC_UNLOCK              0xF2
+#define ATA_CMD_SEC_ERASE_PREP          0xF3
+#define ATA_CMD_SEC_ERASE_UNIT          0xF4
+#define ATA_CMD_SEC_FREEZE_LOCK         0xF5
+#define ATA_CMD_SEC_DISABLE_PASS        0xF6
+#define ATA_CMD_CONFIG_STREAM           0x51
+#define ATA_CMD_SMART                   0xB0
+#define ATA_CMD_MEDIA_LOCK              0xDE
+#define ATA_CMD_MEDIA_UNLOCK            0xDF
+#define ATA_CMD_DSM                     0x06
+#define ATA_CMD_CHK_MED_CRD_TYP         0xD1
+#define ATA_CMD_CFA_REQ_EXT_ERR         0x03
+#define ATA_CMD_CFA_WRITE_NE            0x38
+#define ATA_CMD_CFA_TRANS_SECT          0x87
+#define ATA_CMD_CFA_ERASE               0xC0
+#define ATA_CMD_CFA_WRITE_MULT_NE       0xCD
+/* marked obsolete in the ATA/ATAPI-7 spec */
+#define ATA_CMD_RESTORE                 0x10
+
+/* READ_LOG_EXT pages */
+#define ATA_LOG_SATA_NCQ                0x10
+
+/* READ/WRITE LONG (obsolete) */
+#define ATA_CMD_READ_LONG               0x22
+#define ATA_CMD_READ_LONG_ONCE          0x23
+#define ATA_CMD_WRITE_LONG              0x32
+#define ATA_CMD_WRITE_LONG_ONCE         0x33
+
+/* SETFEATURES stuff */
+#define SETFEATURES_XFER        0x03
+#define XFER_UDMA_7             0x47
+#define XFER_UDMA_6             0x46
+#define XFER_UDMA_5             0x45
+#define XFER_UDMA_4             0x44
+#define XFER_UDMA_3             0x43
+#define XFER_UDMA_2             0x42
+#define XFER_UDMA_1             0x41
+#define XFER_UDMA_0             0x40
+#define XFER_MW_DMA_4           0x24  /* CFA only */
+#define XFER_MW_DMA_3           0x23  /* CFA only */
+#define XFER_MW_DMA_2           0x22
+#define XFER_MW_DMA_1           0x21
+#define XFER_MW_DMA_0           0x20
+#define XFER_SW_DMA_2           0x12
+#define XFER_SW_DMA_1           0x11
+#define XFER_SW_DMA_0           0x10
+#define XFER_PIO_6              0x0E  /* CFA only */
+#define XFER_PIO_5              0x0D  /* CFA only */
+#define XFER_PIO_4              0x0C
+#define XFER_PIO_3              0x0B
+#define XFER_PIO_2              0x0A
+#define XFER_PIO_1              0x09
+#define XFER_PIO_0              0x08
+#define XFER_PIO_SLOW           0x00
+
+#define SETFEATURES_WC_ON       0x02  /* Enable write cache */
+#define SETFEATURES_WC_OFF      0x82  /* Disable write cache */
+
+/* Enable/Disable Automatic Acoustic Management */
+#define SETFEATURES_AAM_ON      0x42
+#define SETFEATURES_AAM_OFF     0xC2
+
+#define SETFEATURES_SPINUP      0x07  /* Spin-up drive */
+
+#define SETFEATURES_SATA_ENABLE  0x10  /* Enable use of SATA feature */
+#define SETFEATURES_SATA_DISABLE 0x90  /* Disable use of SATA feature */
+
+/* SETFEATURE Sector counts for SATA features */
+#define SATA_FPDMA_OFFSET       0x01  /* FPDMA non-zero buffer offsets */
+#define SATA_FPDMA_AA           0x02  /* FPDMA Setup FIS Auto-Activate */
+#define SATA_DIPM               0x03  /* Device Initiated Power Management */
+#define SATA_FPDMA_IN_ORDER     0x04  /* FPDMA in-order data delivery */
+#define SATA_AN                 0x05  /* Asynchronous Notification */
+#define SATA_SSP                0x06  /* Software Settings Preservation */
+
+/* feature values for SET_MAX */
+#define ATA_SET_MAX_ADDR        0x00
+#define ATA_SET_MAX_PASSWD      0x01
+#define ATA_SET_MAX_LOCK        0x02
+#define ATA_SET_MAX_UNLOCK      0x03
+#define ATA_SET_MAX_FREEZE_LOCK 0x04
+
+/* feature values for DEVICE CONFIGURATION OVERLAY */
+#define ATA_DCO_RESTORE         0xC0
+#define ATA_DCO_FREEZE_LOCK     0xC1
+#define ATA_DCO_IDENTIFY        0xC2
+#define ATA_DCO_SET             0xC3
+
+/* feature values for SMART */
+#define ATA_SMART_READ_VALUES       0xD0
+#define ATA_SMART_READ_THRESHOLDS   0xD1
+#define ATA_SMART_AUTOSAVE          0xD2
+#define ATA_SMART_SAVE              0xD3
+#define ATA_SMART_IMMEDIATE_OFFLINE 0xD4
+#define ATA_SMART_READ_LOG          0xD5
+#define ATA_SMART_WRITE_LOG         0xD6
+#define ATA_SMART_ENABLE            0xD8
+#define ATA_SMART_DISABLE           0xD9
+#define ATA_SMART_STATUS            0xDA
+#define ATA_SMART_AUTO_OFFLINE      0xDB
+
+/* feature values for Data Set Management */
+#define ATA_DSM_TRIM            0x01
+
+/* password used in LBA Mid / LBA High for executing SMART commands */
+#define ATA_SMART_LBAM_PASS     0x4F
+#define ATA_SMART_LBAH_PASS     0xC2
+
+/* ATAPI stuff */
+#define ATAPI_PKT_DMA           (1U << 0)
+#define ATAPI_DMADIR            (1U << 2)      /* ATAPI data dir:
+                                                  0=to device, 1=to host */
+#define ATAPI_CDB_LEN           16
+
+/* PMP stuff */
+#define SATA_PMP_MAX_PORTS      15
+#define SATA_PMP_CTRL_PORT      15
+
+#define SATA_PMP_GSCR_DWORDS    128
+#define SATA_PMP_GSCR_PROD_ID   0
+#define SATA_PMP_GSCR_REV       1
+#define SATA_PMP_GSCR_PORT_INFO 2
+#define SATA_PMP_GSCR_ERROR     32
+#define SATA_PMP_GSCR_ERROR_EN  33
+#define SATA_PMP_GSCR_FEAT      64
+#define SATA_PMP_GSCR_FEAT_EN   96
+
+#define SATA_PMP_PSCR_STATUS    0
+#define SATA_PMP_PSCR_ERROR     1
+#define SATA_PMP_PSCR_CONTROL   2
+
+#define SATA_PMP_FEAT_BIST      (1U << 0)
+#define SATA_PMP_FEAT_PMREQ     (1U << 1)
+#define SATA_PMP_FEAT_DYNSSC    (1U << 2)
+#define SATA_PMP_FEAT_NOTIFY    (1U << 3)
+
+/* SATA Status and Control Registers */
+#define SCR_STATUS              0
+#define SCR_ERROR               1
+#define SCR_CONTROL             2
+#define SCR_ACTIVE              3
+#define SCR_NOTIFICATION        4
+
+/* SError bits */
+#define SERR_DATA_RECOVERED     (1UL << 0)  /* recovered data error */
+#define SERR_COMM_RECOVERED     (1UL << 1)  /* recovered comm failure */
+#define SERR_DATA               (1UL << 8)  /* unrecovered data error */
+#define SERR_PERSISTENT         (1UL << 9)  /* persistent data/comm error */
+#define SERR_PROTOCOL           (1UL << 10)  /* protocol violation */
+#define SERR_INTERNAL           (1UL << 11)  /* host internal error */
+#define SERR_PHYRDY_CHG         (1UL << 16)  /* PHY RDY changed */
+#define SERR_PHY_INT_ERR        (1UL << 17)  /* PHY internal error */
+#define SERR_COMM_WAKE          (1UL << 18)  /* Comm wake */
+#define SERR_10B_8B_ERR         (1UL << 19)  /* 10b to 8b decode error */
+#define SERR_DISPARITY          (1UL << 20)  /* Disparity */
+#define SERR_CRC                (1UL << 21)  /* CRC error */
+#define SERR_HANDSHAKE          (1UL << 22)  /* Handshake error */
+#define SERR_LINK_SEQ_ERR       (1UL << 23)  /* Link sequence error */
+#define SERR_TRANS_ST_ERROR     (1UL << 24)  /* Transport state trans. error */
+#define SERR_UNRECOG_FIS        (1UL << 25)  /* Unrecognized FIS */
+#define SERR_DEV_XCHG           (1UL << 26)  /* device exchanged */
+
+/******************************************************************************
+ * Parameters for ATA commands. Those parameters are passed in a variable
+ * argument list in the format:
+ *
+ *   AP_xxx, [val, ...], AP_xxx, [val, ...]
+ *
+ * The values expected by each parameter are indicated within square brackets.
+ */
+typedef enum {
+  AP_FEATURES,     /* [u16]               ATA command features (read: flags) */
+  AP_COUNT,        /* [u16]               number of sectors (0 = 65536)      */
+  AP_SECTOR_28,    /* [u32]               28-bit sector address              */
+  AP_SECTOR_48,    /* [u32, u16]          48-bit sector address              */
+  AP_DEVICE,       /* [u16]               ATA cmd "device" field             */
+  AP_SGLIST,       /* [void *, u16]  buffer S/G (SCATGATENTRY/count)    */
+  AP_VADDR,        /* [void *, u16]  buffer virtual address (buf/len)   */
+  AP_WRITE,        /* [u16]               if != 0, data is written to device */
+  AP_AHCI_FLAGS,   /* [u16]               AHCI command header flags          */
+  AP_ATAPI_CMD,    /* [void *, u16]  ATAPI command (CDB) and length     */
+  AP_ATA_CMD,      /* [void *]       ATA command (fixed len)            */
+  AP_END           /* []                  end of variable argument list      */
+} 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 ------------------------ */
+
+/******************************************************************************
+ * Generic ATA-8 command structure. This is loosely based on ATA-8, i.e. we
+ * don't have to deal with shadow registers and map them to the low bytes of
+ * adjecent words, etc. but there are still some oddities which need to be
+ * taken into consideration. For example, ATA-8 says LBA-28 sector addresses
+ * are simply the lower 28 bits in the LBA field. However, the underlying
+ * transport (SATA in our case) only looks at the three lower bytes of the
+ * LBA field, much like an IDE device would do. This means that only 3 bytes
+ * of the LBA field are processed and this yields only 24 bits. The remaining
+ * 4 bits need to be stored in the "device" register....
+ *
+ * Everything else seems to behave normally, as far as this term can be
+ * applied to IDE/ATA. Further details can be found in section 7.1.5.1 of the
+ * ATA-8 spec (Inputs for 28-bit Read/Write Commands).
+ */
+typedef struct {
+  u16  features;          /* feature bits */
+  u16  count;             /* count register (e.g. number of sectors) */
+  u32  lba_l;             /* low 24 bits of LBA-28 (32 bits for 48-bit devices) */
+  u16  lba_h;             /* high 16 bits of LBA for 48-bit devices */
+  u8   cmd;               /* ATA command field */
+  u8   device;            /* ATA device field and upper 4 bits of LBA-28 */
+} ATA_CMD;
+
+/* generic ATA-8 response structure */
+typedef struct {
+  u16  error;             /* error code, if any */
+  u16  count;             /* count register (e.g. number of sectors) */
+  u32  lba_l;             /* low 28 bits of LBA (32 bits for 48-bit devices) */
+  u16  lba_h;             /* high 16 bits of LBA for 48-bit devices */
+  u16  status;            /* response status bits */
+} ATA_RSP;
+
+/******************************************************************************
+ * Command-specific ATA-8 structures.
+ */
+
+/* -------------------------- 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 *fis, ATA_CMD *cmd, int device);
+extern USHORT ata_get_sg_indx(IORB_EXECUTEIO *io);
+extern void ata_max_sg_cnt(IORB_EXECUTEIO *io, USHORT sg_indx, USHORT sg_max, USHORT *sg_cnt, USHORT *sector_cnt);
+
+extern int ata_get_geometry(IORBH FAR16DATA *iorb, IORBH *pIorb, int slot);
+extern void ata_get_geometry_pp(IORBH FAR16DATA *vIorb, IORBH *pIorb);
+extern int ata_unit_ready(IORBH FAR16DATA *vIorb, IORBH *pIorb, int slot);
+extern int ata_read(IORBH FAR16DATA *vIorb, IORBH *pIorb, int slot);
+extern int ata_read_unaligned(IORBH *pIorb, int slot);
+extern void ata_read_pp(IORBH FAR16DATA *vIorb, IORBH *pIorb);
+extern int ata_verify(IORBH FAR16DATA *vIorb, IORBH *pIorb, int slot);
+extern int ata_write(IORBH FAR16DATA *vIorb, IORBH *pIorb, int slot);
+extern int ata_write_unaligned(IORBH *pIorb, int slot);
+extern void ata_write_pp(IORBH FAR16DATA *vIorb, IORBH *pIorb);
+extern int ata_execute_ata(IORBH FAR16DATA *vIorb, IORBH *pIorb, int slot);
+extern void ata_execute_ata_pp(IORBH FAR16DATA *vIorb, IORBH *pIorb);
+extern int ata_req_sense(IORBH FAR16DATA *vIorb, IORBH *pIorb, int slot);
+
+extern char *ata_dev_name(u16 *id_buf);
+
Index: /tags/2.02/src/os2ahci/atapi.c
===================================================================
--- /tags/2.02/src/os2ahci/atapi.c	(revision 189)
+++ /tags/2.02/src/os2ahci/atapi.c	(revision 189)
@@ -0,0 +1,505 @@
+/******************************************************************************
+ * atapi.c - ATAPI command processing
+ *
+ * Copyright (c) 2011 thi.guten Software Development
+ * Copyright (c) 2011 Mensys B.V.
+ * Copyright (c) 2013-2016 David Azarewicz
+ *
+ * Authors: Christian Mueller, Markus Thielen
+ *
+ * Parts copied from/inspired by the Linux AHCI driver;
+ * those parts are (c) Linux AHCI/ATA maintainers
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+#include "os2ahci.h"
+#include "ata.h"
+#include "atapi.h"
+
+/* need this for the SCSI status block */
+#include <Dev32scsi.h>
+
+/* -------------------------- macros and constants ------------------------- */
+
+/* ------------------------ typedefs and structures ------------------------ */
+
+/* -------------------------- function prototypes -------------------------- */
+
+static void atapi_req_sense_pp(IORBH FAR16DATA *vIorb, IORBH *pIorb);
+static int atapi_pad_cdb(u8 *cmd_in, u16 cmd_in_len, u8 *cmd_out, u16 *cmd_out_len);
+
+/* ------------------------ global/static variables ------------------------ */
+
+/* ----------------------------- start of code ----------------------------- */
+
+/******************************************************************************
+ * Get device or media geometry. This function is not expected to be called.
+ */
+int atapi_get_geometry(IORBH FAR16DATA *vIorb, IORBH *pIorb, int slot)
+{
+  DPRINTF(4,"atapi_get_geometry called\n");
+  iorb_seterr(pIorb, IOERR_CMD_NOT_SUPPORTED);
+  return(-1);
+}
+
+/******************************************************************************
+ * Test whether unit is ready. This function is not expected to be called.
+ */
+int atapi_unit_ready(IORBH FAR16DATA *vIorb, IORBH *pIorb, int slot)
+{
+  DPRINTF(4,"atapi_unit_ready called\n");
+  iorb_seterr(pIorb, IOERR_CMD_NOT_SUPPORTED);
+  return(-1);
+}
+
+/******************************************************************************
+ * Read sectors from AHCI device.
+ */
+int atapi_read(IORBH FAR16DATA *vIorb, IORBH *pIorb, int slot)
+{
+  IORB_EXECUTEIO *io = (IORB_EXECUTEIO *)pIorb;
+  SCATGATENTRY *pSGList = (SCATGATENTRY*)Far16ToFlat(io->pSGList);
+  ATAPI_CDB_12 cdb;
+  AD_INFO *ai = ad_infos + iorb_unit_adapter(pIorb);
+  USHORT count = io->BlockCount - io->BlocksXferred;
+  USHORT sg_indx;
+  USHORT sg_cnt;
+  int p = iorb_unit_port(pIorb);
+  int d = iorb_unit_device(pIorb);
+  int rc;
+
+  if (io->BlockCount == 0)
+  {
+    /* NOP; return -1 without error in IORB to indicate success */
+    return(-1);
+  }
+
+  if (add_workspace(pIorb)->unaligned)
+  {
+    /* unaligned S/G addresses present; need to use double buffers */
+    return(atapi_read_unaligned(pIorb, slot));
+  }
+
+  /* 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);
+
+  DPRINTF(4, "atapi_read\n");
+
+  do {
+    /* update sector count (might have been updated due to S/G limitations) */
+    SET_CDB_32(cdb.trans_len, count);
+
+    /* update S/G count and index */
+    sg_indx = ata_get_sg_indx(io);
+    sg_cnt = io->cSGList - sg_indx;
+
+    /* issue command */
+    rc = ata_cmd(ai, p, d, slot, ATA_CMD_PACKET,
+                 AP_ATAPI_CMD, (void *) &cdb, sizeof(cdb),
+                 AP_SGLIST,    pSGList + sg_indx, sg_cnt,
+                 AP_DEVICE,    0x40,
+                 AP_FEATURES,  ATAPI_FEAT_DMA | ATAPI_FEAT_DMA_TO_HOST,
+                 AP_END);
+
+    if (rc > 0)
+    {
+      /* couldn't map all S/G elements */
+      ata_max_sg_cnt(io, sg_indx, (USHORT) rc, &sg_cnt, &count);
+    }
+  } while (rc > 0 && sg_cnt > 0);
+
+  if (rc == 0)
+  {
+    add_workspace(pIorb)->blocks = count;
+    add_workspace(pIorb)->ppfunc = ata_read_pp;
+  }
+  else if (rc > 0)
+  {
+    iorb_seterr(pIorb, IOERR_CMD_SGLIST_BAD);
+  }
+  else if (rc == ATA_CMD_UNALIGNED_ADDR)
+  {
+    /* unaligned S/G addresses detected; need to use double buffers */
+    add_workspace(pIorb)->unaligned = 1;
+    return(atapi_read_unaligned(pIorb, slot));
+  }
+  else
+  {
+    iorb_seterr(pIorb, 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 *pIorb, int slot)
+{
+  IORB_EXECUTEIO *io = (IORB_EXECUTEIO *)pIorb;
+  ADD_WORKSPACE *aws = add_workspace(pIorb);
+  ATAPI_CDB_12 cdb;
+  AD_INFO *ai = ad_infos + iorb_unit_adapter(pIorb);
+  int p = iorb_unit_port(pIorb);
+  int d = iorb_unit_device(pIorb);
+  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, 1UL);
+
+  ai->ports[p].unaligned_read_count++;
+  DPRINTF(4, "atapi_read_unaligned\n");
+
+  /* allocate transfer buffer */
+  if ((aws->buf = MemAlloc(io->BlockSize)) == NULL)
+  {
+    iorb_seterr(pIorb, IOERR_CMD_SW_RESOURCE);
+    return(-1);
+  }
+
+  rc = ata_cmd(ai, p, d, slot, ATA_CMD_PACKET,
+               AP_ATAPI_CMD, (void *) &cdb, sizeof(cdb),
+               AP_VADDR,     (void *) aws->buf, io->BlockSize,
+               AP_DEVICE,    0x40,
+               AP_FEATURES,  ATAPI_FEAT_DMA | ATAPI_FEAT_DMA_TO_HOST,
+               AP_END);
+
+  if (rc == 0)
+  {
+    add_workspace(pIorb)->blocks = 1;
+    add_workspace(pIorb)->ppfunc = ata_read_pp;
+
+  }
+  else if (rc > 0)
+  {
+    iorb_seterr(pIorb, IOERR_CMD_SGLIST_BAD);
+
+  }
+  else
+  {
+    iorb_seterr(pIorb, IOERR_CMD_ADD_SOFTWARE_FAILURE);
+  }
+
+  return(rc);
+}
+
+/******************************************************************************
+ * Verify readability of sectors on AHCI device. This function is not expected
+ * to be called.
+ */
+int atapi_verify(IORBH FAR16DATA *vIorb, IORBH *pIorb, int slot)
+{
+  DPRINTF(4,"atapi_verify called\n");
+  iorb_seterr(pIorb, IOERR_CMD_NOT_SUPPORTED);
+  return(-1);
+}
+
+/******************************************************************************
+ * Write sectors to AHCI device. This function is not expected to be called.
+ */
+int atapi_write(IORBH FAR16DATA *vIorb, IORBH *pIorb, int slot)
+{
+  DPRINTF(4,"atapi_write called\n");
+  iorb_seterr(pIorb, IOERR_CMD_NOT_SUPPORTED);
+  return(-1);
+}
+
+/******************************************************************************
+ * Execute ATAPI command.
+ */
+int atapi_execute_cdb(IORBH FAR16DATA *vIorb, IORBH *pIorb, int slot)
+{
+  IORB_ADAPTER_PASSTHRU *pt = (IORB_ADAPTER_PASSTHRU *)pIorb;
+  SCATGATENTRY *pSGList = (SCATGATENTRY*)Far16ToFlat(pt->pSGList);
+  int rc;
+  u8 cdb[ATAPI_MAX_CDB_LEN];
+  u16 cdb_len;
+
+  if (pt->ControllerCmdLen > ATAPI_MAX_CDB_LEN)
+  {
+    iorb_seterr(pIorb, IOERR_CMD_SYNTAX);
+    return -1;
+  }
+  /* AHCI requires 12 or 16 byte commands */
+  atapi_pad_cdb(Far16ToFlat(pt->pControllerCmd), pt->ControllerCmdLen, cdb, &cdb_len);
+
+  if (cdb[0] == 0x12 || cdb[0] == 0x5a)
+  {
+    /* somebody sets the direction flag incorrectly for those commands */
+    pt->Flags |= PT_DIRECTION_IN;
+  }
+
+  /* we do not perform the S/G limitation recovery loop here:
+   * "ADDs are not required to iterate commands through the CDB PassThru
+   * mechanism:" -- Storage Device Driver Reference, Scatter/Gather Lists
+   */
+  rc = ata_cmd(ad_infos + iorb_unit_adapter(pIorb), iorb_unit_port(pIorb),
+               iorb_unit_device(pIorb), slot, ATA_CMD_PACKET,
+               AP_ATAPI_CMD, (void *)cdb, cdb_len,
+               AP_SGLIST, pSGList, pt->cSGList,
+               AP_WRITE, !(pt->Flags & PT_DIRECTION_IN),
+               AP_FEATURES, ATAPI_FEAT_DMA,
+               AP_FEATURES, (pt->Flags & PT_DIRECTION_IN) ? ATAPI_FEAT_DMA_TO_HOST : 0,
+               AP_END);
+
+  if (rc)
+  {
+    iorb_seterr(pIorb, IOERR_DEVICE_NONSPECIFIC);
+  }
+
+  return(rc);
+}
+
+
+/******************************************************************************
+ * Request sense information for a failed command.
+ *
+ * NOTE: This function must be called right after an ATAPI command has failed
+ *       and before any other commands are queued on the corresponding device.
+ *       This function is typically called in the port restart context hook
+ *       which is triggered by an AHCI error interrupt.
+ *
+ */
+int atapi_req_sense(IORBH FAR16DATA *vIorb, IORBH *pIorb, int slot)
+{
+  SCSI_STATUS_BLOCK *ssb;
+  ADD_WORKSPACE *aws = add_workspace(pIorb);
+  int rc;
+  u8 cdb[ATAPI_MIN_CDB_LEN];
+  ATAPI_CDB_6 *pcdb = (ATAPI_CDB_6 *) cdb;
+  size_t sense_buf_len = ATAPI_SENSE_LEN;
+
+  DPRINTF(4,"atapi_req_sense\n");
+
+  if ((pIorb->RequestControl & IORB_REQ_STATUSBLOCK) &&
+      pIorb->StatusBlockLen >= sizeof(*ssb) && pIorb->pStatusBlock != 0)
+  {
+    ULONG ulTmp;
+
+    ulTmp = (CastFar16ToULONG(vIorb) & 0xffff0000) + pIorb->pStatusBlock;
+    ssb = (SCSI_STATUS_BLOCK *)Far16ToFlat(CastULONGToFar16(ulTmp));
+
+    /* don't request sense data if caller asked us not to; the flag
+     * STATUS_DISABLE_REQEST_SENSE is not defined in the old DDK we've been
+     * using so we'll use the hard-coded value (0x0008) */
+    if (ssb->Flags & 0x0008U)
+    {
+      iorb_seterr(pIorb, IOERR_DEVICE_NONSPECIFIC);
+      return(-1);
+    }
+
+    /* if the sense buffer requested is larger than our default, adjust
+     * the length accordingly to satisfy the caller's requirements. */
+    if (ssb->SenseData != NULL && ssb->ReqSenseLen > sense_buf_len)
+    {
+      sense_buf_len = ssb->ReqSenseLen;
+    }
+  }
+
+  /* allocate sense buffer in ADD workspace */
+  if ((aws->buf = MemAlloc(sense_buf_len)) == NULL)
+  {
+    iorb_seterr(pIorb, IOERR_CMD_SW_RESOURCE);
+    return(-1);
+  }
+  memset(aws->buf, 0x00, sense_buf_len);
+
+  /* prepare request sense command */
+  memset(cdb, 0x00, sizeof(cdb));
+  pcdb->cmd = ATAPI_CMD_REQUEST_SENSE;
+  pcdb->trans_len = (u8) sense_buf_len;
+
+  aws->ppfunc = atapi_req_sense_pp;
+  rc = ata_cmd(ad_infos + iorb_unit_adapter(pIorb),
+               iorb_unit_port(pIorb),
+               iorb_unit_device(pIorb),
+               slot,
+               ATA_CMD_PACKET,
+               AP_ATAPI_CMD, (void *)cdb, sizeof(cdb),
+               AP_VADDR, (void *)aws->buf, sense_buf_len,
+               AP_FEATURES,  ATAPI_FEAT_DMA,
+               AP_END);
+
+  if (rc > 0)
+  {
+    iorb_seterr(pIorb, IOERR_CMD_SGLIST_BAD);
+  }
+  else if (rc < 0)
+  {
+    /* we failed to get info about an error -> return
+     * non specific device error
+     */
+    iorb_seterr(pIorb, IOERR_DEVICE_NONSPECIFIC);
+  }
+
+  return(rc);
+}
+
+/******************************************************************************
+ * Post processing function for ATAPI request sense; examines the sense
+ * data returned and maps sense info to IORB error info.
+ */
+static void atapi_req_sense_pp(IORBH FAR16DATA *vIorb, IORBH *pIorb)
+{
+  SCSI_STATUS_BLOCK *ssb;
+  ADD_WORKSPACE *aws = add_workspace(pIorb);
+  ATAPI_SENSE_DATA *psd = (ATAPI_SENSE_DATA *) aws->buf;
+
+  DHEXDUMP(0,psd, sizeof(*psd), "sense buffer:\n");
+
+  if ((pIorb->RequestControl & IORB_REQ_STATUSBLOCK) &&
+      pIorb->StatusBlockLen >= sizeof(*ssb) && pIorb->pStatusBlock != 0)
+  {
+    ULONG ulTmp;
+
+    /* copy sense data to IORB */
+    ulTmp = (CastFar16ToULONG(vIorb) & 0xffff0000) + pIorb->pStatusBlock;
+    ssb = (SCSI_STATUS_BLOCK *)Far16ToFlat(CastULONGToFar16(ulTmp));
+    ssb->AdapterErrorCode = 0;
+    ssb->TargetStatus = SCSI_STAT_CHECKCOND;
+    ssb->ResidualLength = 0;
+    memset(ssb->AdapterDiagInfo, 0x00, sizeof(ssb->AdapterDiagInfo));
+
+    if (ssb->SenseData != NULL)
+    {
+      memcpy(Far16ToFlat(ssb->SenseData), psd, ssb->ReqSenseLen);
+      ssb->Flags |= STATUS_SENSEDATA_VALID;
+    }
+    pIorb->Status |= IORB_STATUSBLOCK_AVAIL;
+  }
+
+  /* map sense data to some IOERR_ value */
+  switch (ATAPI_GET_SENSE(psd))
+  {
+  case ASENSE_NO_SENSE:
+  case ASENSE_RECOVERED_ERROR:
+    /* no error; this shouldn't happen because we'll only call
+     * atapi_req_sense() if we received an error interrupt */
+    iorb_seterr(pIorb, IOERR_DEVICE_NONSPECIFIC);
+    break;
+
+  case ASENSE_NOT_READY:
+    iorb_seterr(pIorb, IOERR_UNIT_NOT_READY);
+    break;
+
+  case ASENSE_UNIT_ATTENTION:
+    iorb_seterr(pIorb, IOERR_MEDIA_CHANGED);
+    break;
+
+  case ASENSE_MEDIUM_ERROR:
+    iorb_seterr(pIorb, IOERR_MEDIA);
+    break;
+
+  case ASENSE_ILLEGAL_REQUEST:
+    iorb_seterr(pIorb, IOERR_CMD_SYNTAX);
+    break;
+
+  case ASENSE_DATA_PROTECT:
+    iorb_seterr(pIorb, IOERR_MEDIA_WRITE_PROTECT);
+    break;
+
+  case ASENSE_BLANK_CHECK:
+    iorb_seterr(pIorb, IOERR_MEDIA_NOT_FORMATTED);
+    break;
+
+  case ASENSE_ABORTED_COMMAND:
+  case ASENSE_COPY_ABORTED:
+    iorb_seterr(pIorb, IOERR_CMD_ABORTED);
+    break;
+
+  default:
+    iorb_seterr(pIorb, IOERR_DEVICE_NONSPECIFIC);
+    break;
+  }
+
+  /* mark IORB as complete */
+  aws->complete = 1;
+}
+
+/******************************************************************************
+ * Pad ATAPI commands; AHCI requires ATAPI commands to be either 12 or
+ * 16 bytes in length. This func converts commands that have a 12 byte
+ * equivalent, and pads the others to 12 bytes.
+ * cmd_out buffer is expected to be ATAPI_MAX_CDB_LEN in size.
+ * returns 0 on success, != 0 if the command can't be converted.
+ */
+int atapi_pad_cdb(u8 *cmd_in, u16 cmd_in_len, u8 *cmd_out, u16 *cmd_out_len)
+{
+  ATAPI_CDB_12 *p12;
+  u32 tmp;
+
+  if (cmd_in_len == ATAPI_MIN_CDB_LEN || cmd_in_len == ATAPI_MAX_CDB_LEN)
+  {
+    /* command does not need to be converted */
+    memcpy(cmd_out, cmd_in, cmd_in_len);
+    *cmd_out_len = cmd_in_len;
+    return 0;
+  }
+
+  memset(cmd_out, 0x00, ATAPI_MAX_CDB_LEN);
+  p12 = (ATAPI_CDB_12 *) cmd_out;
+  /* we always convert to 12 byte CDBs */
+  *cmd_out_len = ATAPI_MIN_CDB_LEN;
+
+  /* check if command can be converted */
+  switch (cmd_in[0])
+  {
+  case ATAPI_CMD_READ_6:
+  case ATAPI_CMD_WRITE_6:
+    /* convert from 6 to 12 byte equivalent */
+    p12->cmd = 0xa0 | (cmd_in[0] & 0x0f);
+    p12->flags = cmd_in[1] & 0xc0; /* 6byte cmds have no flags (FUA etc.) */
+    tmp = GET_CDB_24(cmd_in + 1) & 0x1fffffUL;
+    SET_CDB_32(p12->lba, tmp);
+    SET_CDB_32(p12->trans_len, (cmd_in[4]));
+    p12->control = cmd_in[5];
+    break;
+
+  case ATAPI_CMD_READ_10:
+  case ATAPI_CMD_WRITE_10:
+    /* convert from 10 byte to 12 byte equivalent */
+    p12->cmd = 0xa0 | (cmd_in[0] & 0x0f);
+    p12->flags = cmd_in[1];
+    p12->control = cmd_in[9];
+    memcpy(p12->lba, cmd_in + 2, 4);
+    tmp = GET_CDB_16(cmd_in + 7);
+    SET_CDB_32(p12->trans_len, tmp);
+    break;
+
+  default:
+    /* pad with zeroes to 12 bytes */
+    memset(cmd_out, 0x00, ATAPI_MIN_CDB_LEN);
+    memcpy(cmd_out, cmd_in, cmd_in_len);
+    break;
+  }
+
+  return 0;
+}
+
Index: /tags/2.02/src/os2ahci/atapi.h
===================================================================
--- /tags/2.02/src/os2ahci/atapi.h	(revision 189)
+++ /tags/2.02/src/os2ahci/atapi.h	(revision 189)
@@ -0,0 +1,177 @@
+/******************************************************************************
+ * atapi.h - ATAPI structures and macros for os2ahci driver
+ *
+ * Copyright (c) 2011 thi.guten Software Development
+ * Copyright (c) 2011 Mensys B.V.
+ * Copyright (c) 2013-2016 David Azarewicz
+ *
+ * Authors: Christian Mueller, Markus Thielen
+ *
+ * Parts copied from/inspired by the Linux AHCI driver;
+ * those parts are (c) Linux AHCI/ATA maintainers
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+/* -------------------------- macros and constants ------------------------- */
+
+#define ATAPI_MIN_CDB_LEN   12   /* minimum ATAPI CDB len acc to AHCI spec */
+#define ATAPI_MAX_CDB_LEN   16
+
+/******************************************************************************
+ * macros to fill in ATAPI CDB values
+ */
+#define SET_CDB_16(_t, _v)  (_t)[0] = (u8) ((_v) >> 8); \
+                            (_t)[1] = (u8)  (_v)
+#define SET_CDB_24(_t, _v)  (_t)[0] = (u8) ((_v) >> 16) \
+                            (_t)[1] = (u8) ((_v) >> 8); \
+                            (_t)[2] = (u8)  (_v)
+#define SET_CDB_32(_t, _v)  (_t)[0] = (u8) ((_v) >> 24);\
+                            (_t)[1] = (u8) ((_v) >> 16);\
+                            (_t)[2] = (u8) ((_v) >> 8); \
+                            (_t)[3] = (u8)  (_v)
+
+#define GET_CDB_16(_f)      ((u16) (_f)[0] << 8  |            \
+                             (u16) (_f)[1])
+#define GET_CDB_24(_f)      ((u32) (_f)[0] << 16 |            \
+                             (u32) (_f)[1] << 8   |           \
+                             (u32) (_f)[2])
+#define GET_CDB_32(_f)      ((u32) (_f)[0] << 24  |           \
+			     (u32) (_f)[1] << 16  |           \
+                             (u32) (_f)[2] << 24  |           \
+                             (u32) (_f)[3])
+
+
+/******************************************************************************
+ * ATAPI/MMC command codes (as far as relevant for us)
+ */
+#define ATAPI_CMD_READ_6           0x08
+#define ATAPI_CMD_READ_10          0x28
+#define ATAPI_CMD_READ_12          0xa8
+#define ATAPI_CMD_READ_16          0x88
+#define ATAPI_CMD_WRITE_6          0x0a
+#define ATAPI_CMD_WRITE_10         0x2a
+#define ATAPI_CMD_WRITE_12         0xaa
+#define ATAPI_CMD_WRITE_16         0x8a
+#define ATAPI_CMD_REQUEST_SENSE    0x03
+
+/******************************************************************************
+ * ATAPI command flag bits
+ */
+#define ATAPI_FLAG_FUA             0x80
+#define ATAPI_FLAG_DPO             0x10
+
+#define ATAPI_FEAT_DMA             0x0001
+#define ATAPI_FEAT_DMA_TO_HOST     0x0004
+
+/******************************************************************************
+ * ATAPI sense data
+ */
+#define ATAPI_SENSE_LEN            96
+
+#define ASENSE_NO_SENSE            0x00  /* no sense -> success */
+#define ASENSE_RECOVERED_ERROR     0x01  /* recovered error -> success */
+#define ASENSE_NOT_READY           0x02  /* device not ready */
+#define ASENSE_MEDIUM_ERROR        0x03  /* medium/CRC error */
+#define ASENSE_HARDWARE_ERROR      0x04  /* device error */
+#define ASENSE_ILLEGAL_REQUEST     0x05  /* invalid command/parameters issued */
+#define ASENSE_UNIT_ATTENTION      0x06  /* new medium */
+#define ASENSE_DATA_PROTECT        0x07  /* protected LBA */
+#define ASENSE_BLANK_CHECK         0x08  /* unformatted or write protected */
+#define ASENSE_VENDOR_SPECIFIC     0x09  /* vendor specific sense data */
+#define ASENSE_COPY_ABORTED        0x0a  /* copy, ...command aborted */
+#define ASENSE_ABORTED_COMMAND     0x0b  /* command has been aborted */
+#define ASENSE_EQUAL               0x0c
+#define ASENSE_VOLUME_OVERFLOW     0x0d  /* out of space */
+#define ASENSE_MISCOMPARE          0x0e  /* verification failed */
+#define ASENSE_RESERVED            0x0f
+
+/******************************************************************************
+ * macro to get sense key from ATAPI_SENSE_DATA pointer
+ */
+#define ATAPI_GET_SENSE(p_) (u8)(p_->sense.sense_key & 0x0f)
+
+/* ------------------------ typedefs and structures ------------------------ */
+
+/******************************************************************************
+ * ATAPI_SENSE_DATA - define layout of ATAPI sense data
+ */
+typedef union _ATAPI_SENSE_DATA {
+  struct {
+    u8 valid_respc;      /* valid bit (bit 7), response code (bits 6:0) */
+    u8 segment;          /* segment number (obsolete) */
+    u8 sense_key;        /* some flags (bits 7:4), sense key (bits 3:0) */
+    u8 info[4];          /* information (?) */
+    u8 adl_len;          /* additional sense info length */
+    u8 cmd_specific[4];  /* command specific stuff (ignored) */
+    u8 asc;              /* additional sense code */
+    u8 ascq;             /* additional sense code qualifier */
+    u8 fruc;             /* field replaceable unit code */
+    u8 flags;            /* vendor-specific flags */
+    u8 field_off[2];     /* offset to invalid field in parm list */
+  } sense;
+
+  u8 padding[ATAPI_SENSE_LEN]; /* pad to 64 bytes */
+
+} ATAPI_SENSE_DATA;
+
+/******************************************************************************Ã
+ * ATAPI_CDB_6 - describes 6 byte ATAPI command
+ */
+typedef struct _ATAPI_CDB_6 {
+  u8   cmd;            /* command code */
+  u8   lun;            /* SCSI lun (ignored) */
+  u8   resvd[2];
+  u8   trans_len;      /* transfer length */
+  u8   control;
+} ATAPI_CDB_6;
+
+/******************************************************************************
+ * ATAPI_CDB_10 - describes layout of generic 10 byte ATAPI command
+ */
+typedef struct _ATAPI_CDB_10 {
+  u8   cmd;            /* SCSI/ATAPI command code */
+  u8   flags;          /* flags (DPO, FUA) */
+  u8   lba[4];         /* logical block address */
+  u8   trans_len[2];   /* number of blocks to transfer */
+  u8   control;        /* (nothing much) */
+} ATAPI_CDB_10;
+
+/******************************************************************************
+ * ATAPI_CDB_12 - describes layout of generic 12 byte ATAPI command
+ */
+typedef struct _ATAPI_CDB_12 {
+  u8   cmd;            /* SCSI/ATAPI command code */
+  u8   flags;          /* flags (DPO, FUA) */
+  u8   lba[4];         /* logical block address */
+  u8   trans_len[4];   /* number of blocks to transfer */
+  u8   reserved;
+  u8   control;        /* (nothing much) */
+} ATAPI_CDB_12;
+
+
+/* ---------------------------- global variables --------------------------- */
+
+/* -------------------------- function prototypes -------------------------- */
+
+extern int atapi_get_geometry(IORBH FAR16DATA *vIorb, IORBH *pIorb, int slot);
+extern int atapi_unit_ready(IORBH FAR16DATA *vIorb, IORBH *pIorb, int slot);
+extern int atapi_read(IORBH FAR16DATA *vIorb, IORBH *pIorb, int slot);
+extern int atapi_read_unaligned(IORBH *pIorb, int slot);
+extern int atapi_verify(IORBH FAR16DATA *vIorb, IORBH *pIorb, int slot);
+extern int atapi_write(IORBH FAR16DATA *vIorb, IORBH *pIorb, int slot);
+extern int atapi_execute_cdb(IORBH FAR16DATA *vIorb, IORBH *pIorb, int slot);
+extern int atapi_req_sense(IORBH FAR16DATA *vIorb, IORBH *pIorb, int slot);
+
Index: /tags/2.02/src/os2ahci/ctxhook.c
===================================================================
--- /tags/2.02/src/os2ahci/ctxhook.c	(revision 189)
+++ /tags/2.02/src/os2ahci/ctxhook.c	(revision 189)
@@ -0,0 +1,520 @@
+/******************************************************************************
+ * ctxhook.c - context hooks (kernel thread functions) for os2ahci
+ *
+ * Copyright (c) 2011 thi.guten Software Development
+ * Copyright (c) 2011 Mensys B.V.
+ * Copyright (c) 2013-2016 David Azarewicz
+ *
+ * Authors: Christian Mueller, Markus Thielen
+ *
+ * Parts copied from/inspired by the Linux AHCI driver;
+ * those parts are (c) Linux AHCI/ATA maintainers
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+#include "os2ahci.h"
+#include "ata.h"
+#include "atapi.h"
+
+/* -------------------------- macros and constants ------------------------- */
+
+/* ------------------------ typedefs and structures ------------------------ */
+
+/* -------------------------- function prototypes -------------------------- */
+
+/* ------------------------ global/static variables ------------------------ */
+
+/* port restart context hook and input data */
+ULONG           restart_ctxhook_h;
+volatile u32    ports_to_restart[MAX_AD];
+
+/* port reset context hook and input data */
+ULONG           reset_ctxhook_h;
+ULONG           th_reset_watchdog;
+volatile u32    ports_to_reset[MAX_AD];
+IORB_QUEUE      abort_queue;
+
+/* trigger engine context hook and input data */
+ULONG           engine_ctxhook_h;
+
+/* ----------------------------- start of code ----------------------------- */
+
+/******************************************************************************
+ * Port restart context hook. This context hook is executed at task time and
+ * will handle ports which are stopped due to a device error condition.
+ *
+ * The following conditions may exist:
+ *
+ *  - Only a single non-NCQ command is executed by the AHCI adapter at any
+ *    given time (even if more are outstanding). This is the case for single
+ *    devices or port multipliers without FIS-based command switching. Error
+ *    recovery is simple because we know which command has failed and that
+ *    all other commands have not yet started executing. Thus, we can requeue
+ *    all of them, replacing the failing command with a "request sense"
+ *    command to get error details.
+ *
+ *  - Multiple non-NCQ commands are executed on different devices behind a
+ *    port multiplier which supports FIS-based command switching. This is
+ *    more difficult to recover from but currently not an issue because we
+ *    don't yet support FIS-based command switching (the FIS receive areas
+ *    would become too large for the current data model).
+ *
+ *  - One or more NCQ commands were active at the time of the error, with or
+ *    without FIS-based command switching. We would have to interrogate the
+ *    corresponding devices to find out which command has failed but if this
+ *    is combined with FIS-based command switching, even the AHCI spec
+ *    recommends to reset the port. This leads to a much simpler approach:
+ *    requeue all NCQ commands (they are idempotent per definition, otherwise
+ *    they couldn't be reordered by the device) with the 'no_ncq' flag set
+ *    in the IORB and reset the port. Then those comands will be executed as
+ *    regular commands. The error, if it reoccurs, can then be handled by
+ *    one of the above cases.
+ *
+ * The upstream code will guarantee that we will never have a mix of NCQ and
+ * non-NCQ commands active at the same time in order to reduce complexity
+ * in the interrupt and error handlers.
+ */
+void _Syscall restart_ctxhook(ULONG parm)
+{
+  IORB_QUEUE done_queue;
+  AD_INFO *ai;
+  IORBH FAR16DATA *vProblemIorb;
+  IORBH FAR16DATA *vIorb;
+  IORBH FAR16DATA *vNext;
+  u8 *port_mmio;
+  int rearm_ctx_hook;
+  int need_reset;
+  int ccs;
+  int a;
+  int p;
+
+  D32ThunkStackTo32();
+
+  vNext = NULL;
+  rearm_ctx_hook = 0;
+
+  DPRINTF(8,"restart_ctxhook() started\n");
+  memset(&done_queue, 0x00, sizeof(done_queue));
+
+  spin_lock(drv_lock);
+
+  for (a = 0; a < ad_info_cnt; a++)
+  {
+    ai = ad_infos + a;
+
+    if (ai->busy)
+    {
+      /* this adapter is busy; leave it alone for now */
+      rearm_ctx_hook = 1;
+      continue;
+    }
+
+    for (p = 0; p <= ai->port_max; p++)
+    {
+      if (ports_to_restart[a] & (1UL << p))
+      {
+        ports_to_restart[a] &= ~(1UL << p);
+
+        /* restart this port */
+        port_mmio = port_base(ai, p);
+        vProblemIorb = NULL;
+        need_reset = 0;
+
+        DPRINTF(8,"port %d, TF_DATA: 0x%x\n", p, readl(port_mmio + PORT_TFDATA));
+
+        /* get "current command slot"; only valid if there are no NCQ cmds */
+        ccs = (int) ((readl(port_mmio + PORT_CMD) >> 8) & 0x1f);
+        DPRINTF(8," PORT_CMD      = 0x%x\n", ccs);
+
+        for (vIorb = ai->ports[p].iorb_queue.vRoot; vIorb != NULL; vIorb = vNext)
+        {
+          IORBH *pIorb = Far16ToFlat(vIorb);
+          ADD_WORKSPACE *aws = add_workspace(pIorb);
+          vNext = pIorb->pNxtIORB;
+
+          if (aws->queued_hw)
+          {
+            if (ai->ports[p].ncq_cmds & (1UL << aws->cmd_slot))
+            {
+              /* NCQ command; force non-NCQ mode and trigger port reset */
+              ai->ports[p].ncq_cmds &= ~(1UL << aws->cmd_slot);
+              aws->no_ncq = 1;
+              need_reset = 1;
+            }
+            else
+            {
+              /* regular command; clear cmd bit and identify problem IORB */
+              ai->ports[p].reg_cmds &= ~(1UL << aws->cmd_slot);
+              if (aws->cmd_slot == ccs)
+              {
+                /* this is the non-NCQ command that failed */
+                DPRINTF(0,"failing IORB: %x\n", vIorb);
+                vProblemIorb = vIorb;
+              }
+            }
+            /* we can requeue all IORBs unconditionally (see function comment) */
+            if (aws->retries++ < MAX_RETRIES)
+            {
+              iorb_requeue(pIorb);
+            }
+            else
+            {
+              /* retry count exceeded; consider IORB aborted */
+              iorb_seterr(pIorb, IOERR_CMD_ABORTED);
+              iorb_queue_del(&ai->ports[p].iorb_queue, vIorb);
+              iorb_queue_add(&done_queue, vIorb, pIorb);
+              if (vIorb == vProblemIorb)
+              {
+                /* no further analysis -- we're done with this one */
+                vProblemIorb = NULL;
+              }
+            }
+          }
+        }
+
+        /* sanity check: issued command bitmaps should be 0 now */
+        if (ai->ports[p].ncq_cmds != 0 || ai->ports[p].reg_cmds != 0)
+        {
+          DPRINTF(0,"warning: commands issued not 0 (%08lx/%08lx); resetting...\n",
+                  ai->ports[p].ncq_cmds, ai->ports[p].reg_cmds);
+          need_reset = 1;
+        }
+
+        if (!need_reset)
+        {
+          if ((readl(port_mmio + PORT_TFDATA) & 0x88) != 0)
+          {
+            /* device is not in an idle state */
+            need_reset = 1;
+          }
+        }
+
+        /* restart/reset port */
+        ai->busy = 1;
+        spin_unlock(drv_lock);
+        if (need_reset)
+        {
+          ahci_reset_port(ai, p, 1);
+        }
+        else
+        {
+          ahci_stop_port(ai, p);
+          ahci_start_port(ai, p, 1);
+        }
+        spin_lock(drv_lock);
+        ai->busy = 0;
+
+        /* reset internal port status */
+        ai->ports[p].ncq_cmds = 0;
+        ai->ports[p].reg_cmds = 0;
+        ai->ports[p].cmd_slot = 0;
+
+        if (vProblemIorb != NULL)
+        {
+          IORBH *pProblemIorb = Far16ToFlat(vProblemIorb);
+          /* get details about the error that caused this IORB to fail */
+          if (need_reset)
+          {
+            /* no way to retrieve error details after a reset */
+            iorb_seterr(pProblemIorb, IOERR_DEVICE_NONSPECIFIC);
+            iorb_queue_del(&ai->ports[p].iorb_queue, vProblemIorb);
+            iorb_queue_add(&done_queue, vProblemIorb, pProblemIorb);
+
+          }
+          else
+          {
+            /* get sense information */
+            ADD_WORKSPACE *aws = add_workspace(pProblemIorb);
+            int d = iorb_unit_device(pProblemIorb);
+            int (*req_sense)(IORBH FAR16DATA *, IORBH *, int) = (ai->ports[p].devs[d].atapi) ?
+                                                   atapi_req_sense : ata_req_sense;
+
+            aws->processing = 1;
+            aws->queued_hw = 1;
+
+            if (req_sense(vProblemIorb, pProblemIorb, 0) == 0)
+            {
+              /* execute request sense on slot #0 before anything else comes along */
+              Timer_StartTimerMS(&aws->timer, 5000, timeout_callback, CastFar16ToULONG(vProblemIorb));
+              aws->cmd_slot = 0;
+              ai->ports[p].reg_cmds = 1;
+              writel(port_mmio + PORT_CMD_ISSUE, 1);
+              readl(port_mmio); /* flush */
+
+            }
+            else
+            {
+              /* IORB is expected to contain the error code; just move to done queue */
+              iorb_queue_del(&ai->ports[p].iorb_queue, vProblemIorb);
+              iorb_queue_add(&done_queue, vProblemIorb, pProblemIorb);
+            }
+          }
+        }
+      }
+    }
+  }
+
+  spin_unlock(drv_lock);
+
+  /* call notification routine on all IORBs which have completed */
+  for (vIorb = done_queue.vRoot; vIorb != NULL; vIorb = vNext)
+  {
+    IORBH *pIorb = Far16ToFlat(vIorb);
+    vNext = pIorb->pNxtIORB;
+
+    spin_lock(drv_lock);
+    aws_free(add_workspace(pIorb));
+    spin_unlock(drv_lock);
+
+    iorb_complete(vIorb, pIorb);
+  }
+
+  /* restart engine to resume IORB processing */
+  spin_lock(drv_lock);
+  trigger_engine();
+  spin_unlock(drv_lock);
+
+  DPRINTF(8,"restart_ctxhook() completed\n");
+
+  /* Check whether we have to rearm ourselves because some adapters were busy
+   * when we wanted to restart ports on them.
+   */
+  if (rearm_ctx_hook)
+  {
+    msleep(250);
+    KernArmHook(restart_ctxhook_h, 0, 0);
+  }
+  KernThunkStackTo16();
+}
+
+/******************************************************************************
+ * Reset and abort context hook. This function runs at task time and takes
+ * care of port resets and their side effects. Input to this function are:
+ *
+ *  ports_to_reset[]   - array of port bitmaps, each bit indicating which port
+ *                       should be reset unconditionally. This is primarily
+ *                       used by the error interrupt handler.
+ *
+ *  abort_queue        - queue with IORBs to be arborted (timed-out, ...) If
+ *                       any of these commands have reached the hardware, the
+ *                       corresponding port is reset to interrupt command
+ *                       execution. This is primarily used for timeout
+ *                       handling and when IORBs are requested to be aborted.
+ *
+ * After resetting the requested ports, all remaining active IORBs on those
+ * ports have to be retried or aborted. Whether a retry is attempted depends
+ * on the kind of IORB -- those which are idempotent are retried, all others
+ * are aborted. This is different from the port restart hook because the
+ * restart hook can assume it is called with the port in error state, thus
+ * the controller will have stopped executing commands. The reset handler can
+ * be called at any time and we can't tell what's going on in the controller.
+ *
+ * The IORBs in the global abort_queue are expected to have their error code
+ * set (aborted, timeout, ...) but must not be marked as 'done'; otherwise,
+ * the upstream code might reuse the IORBs before we're done with them.
+ */
+void _Syscall reset_ctxhook(ULONG parm)
+{
+  IORB_QUEUE done_queue;
+  AD_INFO *ai;
+  IORBH FAR16DATA *vIorb;
+  IORBH FAR16DATA *vNext;
+  int rearm_ctx_hook;
+  int a;
+  int p;
+
+  D32ThunkStackTo32();
+
+  vNext = NULL;
+  rearm_ctx_hook = 0;
+
+  DPRINTF(8,"reset_ctxhook() started\n");
+  memset(&done_queue, 0x00, sizeof(done_queue));
+
+  spin_lock(drv_lock);
+
+  if (th_reset_watchdog != 0)
+  {
+    /* watchdog timer still active -- just reset it */
+    Timer_CancelTimer(th_reset_watchdog);
+    th_reset_watchdog = 0;
+  }
+
+  /* add ports of active IORBs from the abort queue to ports_to_reset[] */
+  for (vIorb = abort_queue.vRoot; vIorb != NULL; vIorb = vNext)
+  {
+    IORBH *pIorb = Far16ToFlat(vIorb);
+    vNext = pIorb->pNxtIORB;
+    a = iorb_unit_adapter(pIorb);
+    p = iorb_unit_port(pIorb);
+    ai = ad_infos + a;
+
+    if (ai->busy)
+    {
+      /* this adapter is busy; leave it alone for now */
+      rearm_ctx_hook = 1;
+      continue;
+    }
+
+    /* move IORB to the local 'done' queue */
+    iorb_queue_del(&abort_queue, vIorb);
+    iorb_queue_add(&done_queue, vIorb, pIorb);
+
+    /* reset port if the IORB has already been queued to hardware */
+    if (add_workspace(pIorb)->queued_hw)
+    {
+      /* prepare port reset */
+      ports_to_reset[a] |= (1UL << p);
+    }
+  }
+
+  /* reset all ports in 'ports_to_reset[]' */
+  for (a = 0; a < ad_info_cnt; a++)
+  {
+    ai = ad_infos + a;
+
+    if (ai->busy)
+    {
+      /* this adapter is busy; leave it alone for now */
+      rearm_ctx_hook = 1;
+      continue;
+    }
+
+    for (p = 0; p <= ai->port_max; p++)
+    {
+      if (ports_to_reset[a] & (1UL << p))
+      {
+        ports_to_reset[a] &= ~(1UL << p);
+
+        /* Reset this port. Since this is a rather slow operation, we'll
+         * release the spinlock while doing so. The adapter is marked as
+         * 'busy' to prevent similar routines (e.g. an ahci port scan) from
+         * interfering.
+         */
+        ai->busy = 1;
+        spin_unlock(drv_lock);
+        ahci_reset_port(ai, p, 1);
+        spin_lock(drv_lock);
+        ai->busy = 0;
+
+        /* reset port status */
+        ai->ports[p].ncq_cmds = 0;
+        ai->ports[p].reg_cmds = 0;
+        ai->ports[p].cmd_slot = 0;
+
+        /* retry or abort all remaining active commands on this port */
+        for (vIorb = ai->ports[p].iorb_queue.vRoot; vIorb != NULL; vIorb = vNext)
+        {
+          IORBH *pIorb = Far16ToFlat(vIorb);
+          ADD_WORKSPACE *aws = add_workspace(pIorb);
+          vNext = pIorb->pNxtIORB;
+
+          if (aws->queued_hw)
+          {
+            /* this IORB had already been queued to HW when we reset the port */
+            if (aws->idempotent && aws->retries++ < MAX_RETRIES)
+            {
+              /* we can retry this IORB */
+              iorb_requeue(pIorb);
+
+            }
+            else
+            {
+              /* we cannot retry this IORB; consider it aborted */
+              pIorb->ErrorCode = IOERR_CMD_ABORTED;
+              iorb_queue_del(&ai->ports[p].iorb_queue, vIorb);
+              iorb_queue_add(&done_queue, vIorb, pIorb);
+            }
+          }
+        }
+      }
+    }
+  }
+
+  spin_unlock(drv_lock);
+
+  /* complete all aborted IORBs */
+  for (vIorb = done_queue.vRoot; vIorb != NULL; vIorb = vNext)
+  {
+    IORBH *pIorb = Far16ToFlat(vIorb);
+    vNext = pIorb->pNxtIORB;
+
+    spin_lock(drv_lock);
+    aws_free(add_workspace(pIorb));
+    spin_unlock(drv_lock);
+
+    pIorb->Status |= IORB_ERROR;
+    iorb_complete(vIorb, pIorb);
+  }
+
+  /* restart engine to resume IORB processing */
+  spin_lock(drv_lock);
+  trigger_engine();
+  spin_unlock(drv_lock);
+
+  DPRINTF(8,"reset_ctxhook() completed\n");
+
+  /* Check whether we have to rearm ourselves because some adapters were busy
+   * when we wanted to reset ports on them.
+   */
+  if (rearm_ctx_hook)
+  {
+    msleep(250);
+    KernArmHook(reset_ctxhook_h, 0, 0);
+  }
+
+  KernThunkStackTo16();
+}
+
+/******************************************************************************
+ * IORB Engine context hook. This hook is executed if trigger_engine() came
+ * to the conclusion that some of the IORBs keep bouncing, most likely due to
+ * some condition on the adapter such as being busy. It could also be a very
+ * busy system. Either way, this requires some task-time help.
+ */
+void _Syscall engine_ctxhook(ULONG parm)
+{
+  int iorbs_sent;
+  int i;
+
+  D32ThunkStackTo32();
+
+  DPRINTF(8,"engine_ctxhook() started\n");
+  if (resume_sleep_flag)
+  {
+    msleep(resume_sleep_flag);
+    resume_sleep_flag = 0;
+  }
+
+  spin_lock(drv_lock);
+  for (i = 0; i < 10; i++)
+  {
+    if ((iorbs_sent = trigger_engine_1()) == 0) break;
+  }
+  spin_unlock(drv_lock);
+
+  DPRINTF(8,"engine_ctxhook() completed\n");
+
+  if (iorbs_sent != 0)
+  {
+    /* need to rearm ourselves for another run */
+    msleep(250);
+    KernArmHook(engine_ctxhook_h, 0, 0);
+  }
+
+  KernThunkStackTo16();
+}
+
Index: /tags/2.02/src/os2ahci/ioctl.c
===================================================================
--- /tags/2.02/src/os2ahci/ioctl.c	(revision 189)
+++ /tags/2.02/src/os2ahci/ioctl.c	(revision 189)
@@ -0,0 +1,578 @@
+/******************************************************************************
+ * ioctl.c - Generic IOCTL command processing
+ *
+ * Copyright (c) 2011 thi.guten Software Development
+ * Copyright (c) 2011 Mensys B.V.
+ * Copyright (c) 2013-2016 David Azarewicz
+ *
+ * Authors: Christian Mueller, Markus Thielen
+ *
+ * Parts copied from/inspired by the Linux AHCI driver;
+ * those parts are (c) Linux AHCI/ATA maintainers
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+#include "os2ahci.h"
+#include "atapi.h"
+#include "ata.h"
+#include "ioctl.h"
+
+#include <Dev32scsi.h>
+
+#pragma pack(1)
+
+/* -------------------------- macros and constants ------------------------- */
+
+/* ------------------------ typedefs and structures ------------------------ */
+
+/* Memory area for IOCTLs which send IORBs downstream; currently only
+ * OS2AHCI_IOCTL_PASSTHROUGH falls into this category, thus we're simply
+ * reusing the IORB_ADAPTER_PASSTHRU structure for now. If this ever changes,
+ * we'll need to define a union to cover all IORB types in question.
+ */
+#define CMDLEN sizeof(((OS2AHCI_PASSTHROUGH *) 0)->cmd)
+typedef struct {
+  IORB_ADAPTER_PASSTHRU iorb;                  /* IORB */
+  SCSI_STATUS_BLOCK   ssb;                     /* SCSI status block */
+  UCHAR               cmd[CMDLEN];             /* copy of passthrough cmd
+                                                * (need to fill ATA return
+                                                * registers at interrupt time)
+                                                */
+  UCHAR               sense[ATAPI_SENSE_LEN];  /* sense buffer */
+  SCATGATENTRY        sg_lst[AHCI_MAX_SG / 2]; /* scatter/gather list */
+  ULONG               sg_cnt;                  /* number of S/G elements */
+  struct _KernVMLock_t *lh;                  /* lock handle for VMLock() */
+} IOCTL_CONTEXT;
+
+/* -------------------------- function prototypes -------------------------- */
+
+static USHORT do_smart  (BYTE unit, BYTE sub_func, BYTE cnt, BYTE lba_l,
+                         void *buf);
+static int    map_unit  (BYTE unit, USHORT *a, USHORT *p,
+                         USHORT *d);
+
+extern IORBH FAR16DATA * __far16 IoctlWakeup16(IORBH FAR16DATA*);
+
+/* ------------------------ global/static variables ------------------------ */
+
+/* ----------------------------- start of code ----------------------------- */
+
+/******************************************************************************
+ * Return device list to allow the ring 3 application to figure out which
+ * adapter/port/device combinations are available.
+ */
+USHORT ioctl_get_devlist(REQPACKET *ioctl)
+{
+  OS2AHCI_DEVLIST *devlst = (OS2AHCI_DEVLIST*)Far16ToFlat(ioctl->ioctl.pvData);
+  USHORT maxcnt = 0;
+  USHORT cnt = 0;
+  USHORT a;
+  USHORT p;
+  USHORT d;
+
+  if (KernCopyIn(&maxcnt, ioctl->ioctl.pvParm, sizeof(maxcnt)))
+  {
+    return(RPDONE | RPERR_PARAMETER);
+  }
+
+  /* fill-in device list */
+  for (a = 0; a < ad_info_cnt; a++)
+  {
+    AD_INFO *ai = ad_infos + a;
+
+    for (p = 0; p <= ai->port_max; p++)
+    {
+      P_INFO *pi = ai->ports + p;
+
+      for (d = 0; d <= pi->dev_max; d++)
+      {
+        if (pi->devs[d].present)
+        {
+          /* add this device to the device list */
+          if (cnt >= maxcnt)
+          {
+            /* not enough room in devlst */
+            goto ioctl_get_device_done;
+          }
+
+          devlst->devs[cnt].adapter  = a;
+          devlst->devs[cnt].port     = p;
+          devlst->devs[cnt].device   = d;
+          devlst->devs[cnt].type     = pi->devs[d].dev_type;
+          devlst->devs[cnt].ncq_max  = pi->devs[d].ncq_max;
+
+          if (pi->devs[d].lba48)      devlst->devs[cnt].flags |= DF_LBA48;
+          if (pi->devs[d].atapi)      devlst->devs[cnt].flags |= DF_ATAPI;
+          if (pi->devs[d].atapi_16)   devlst->devs[cnt].flags |= DF_ATAPI_16;
+          if (pi->devs[d].removable)  devlst->devs[cnt].flags |= DF_REMOVABLE;
+          cnt++;
+        }
+      }
+    }
+  }
+
+ioctl_get_device_done:
+  devlst->cnt = cnt;
+  return(RPDONE);
+}
+
+/******************************************************************************
+ * Adapter passthrough IOCTL. This IOCTL covers both ATA and ATAPI passthrough
+ * requests.
+ */
+USHORT ioctl_passthrough(REQPACKET *ioctl)
+{
+  OS2AHCI_PASSTHROUGH *req = (OS2AHCI_PASSTHROUGH *)Far16ToFlat(ioctl->ioctl.pvParm);
+  char *sense_buf = (char *)Far16ToFlat(ioctl->ioctl.pvData);
+  IOCTL_CONTEXT *ic;
+  USHORT ret;
+  USHORT a;
+  USHORT p;
+  USHORT d;
+
+  /* Verify basic request parameters such as adapter/port/device, size of
+   * DMA buffer (the S/G list can't have more than AHCI_MAX_SG / 2 entries), ...
+   */
+  a = req->adapter;
+  p = req->port;
+  d = req->device;
+  if (a >= ad_info_cnt || p > ad_infos[a].port_max ||
+      d > ad_infos[a].ports[p].dev_max || !ad_infos[a].ports[p].devs[d].present)
+  {
+    return(RPDONE | RPERR_UNIT);
+  }
+  if ((req->buflen + 4095) / 4096 + 1 > AHCI_MAX_SG / 2 ||
+      req->cmdlen < 6 || req->cmdlen > sizeof(req->cmd))
+  {
+    return(RPDONE | RPERR_PARAMETER);
+  }
+
+  /* allocate IOCTL context data */
+  if ((ic = MemAlloc(sizeof(*ic))) == NULL)
+  {
+    return(RPDONE | RPERR_GENERAL);
+  }
+  memset(ic, 0x00, sizeof(*ic));
+
+  /* fill in adapter passthrough fields */
+  ic->iorb.iorbh.Length           = sizeof(ic->iorb);
+  ic->iorb.iorbh.UnitHandle       = iorb_unit(a, p, d);
+  ic->iorb.iorbh.CommandCode      = IOCC_ADAPTER_PASSTHRU;
+  ic->iorb.iorbh.CommandModifier  = (req->flags & PT_ATAPI) ? IOCM_EXECUTE_CDB : IOCM_EXECUTE_ATA;
+  ic->iorb.iorbh.RequestControl   = IORB_ASYNC_POST;
+  ic->iorb.iorbh.Timeout          = req->timeout;
+  ic->iorb.iorbh.NotifyAddress    = IoctlWakeup16;
+
+  ic->iorb.cSGList = ic->sg_cnt;
+  ic->iorb.pSGList = MemFar16Adr(ic->sg_lst);
+  ic->iorb.ppSGLIST = MemPhysAdr(ic->sg_lst);
+
+  memcpy(ic->cmd, req->cmd.cdb, sizeof(ic->cmd));
+  ic->iorb.ControllerCmdLen = req->cmdlen;
+  ic->iorb.pControllerCmd   = MemFar16Adr(ic->cmd);
+  ic->iorb.Flags            = (req->flags & PT_WRITE) ? 0 : PT_DIRECTION_IN;
+
+  if (req->sense_len > 0)
+  {
+    /* initialize SCSI status block to allow getting sense data */
+    ic->iorb.iorbh.pStatusBlock     = CastFar16ToULONG(MemFar16Adr(&ic->ssb));
+    ic->iorb.iorbh.StatusBlockLen   = sizeof(ic->ssb);
+    ic->ssb.SenseData               = MemFar16Adr(ic->sense);
+    ic->ssb.ReqSenseLen             = sizeof(ic->sense);
+    ic->iorb.iorbh.RequestControl  |= IORB_REQ_STATUSBLOCK;
+  }
+
+  /* send IORB on its way */
+  add_entry(MemFar16Adr(&ic->iorb.iorbh));
+
+  /* Wait for IORB completion. */
+  spin_lock(drv_lock);
+  while (!(ic->iorb.iorbh.Status & IORB_DONE))
+  {
+    KernBlock((ULONG)&ic->iorb.iorbh, 30000, 0, NULL, NULL);
+  }
+  spin_unlock(drv_lock);
+
+  ret = RPDONE;
+
+  /* map IORB error codes to device driver error codes */
+  if (ic->iorb.iorbh.Status & IORB_ERROR)
+  {
+    ret |= RPERR;
+
+    switch (ic->iorb.iorbh.ErrorCode)
+    {
+    case IOERR_UNIT_NOT_READY:
+      ret |= RPERR_NOTREADY;
+      break;
+
+    case IOERR_MEDIA_CHANGED:
+      ret |= RPERR_DISK;
+      break;
+
+    case IOERR_MEDIA:
+    case IOERR_MEDIA_NOT_FORMATTED:
+      ret |= RPERR_CRC;
+      break;
+
+    case IOERR_CMD_SYNTAX:
+    case IOERR_CMD_NOT_SUPPORTED:
+      ret |= RPERR_BADCOMMAND;
+      break;
+
+    case IOERR_MEDIA_WRITE_PROTECT:
+      ret |= RPERR_PROTECT;
+      break;
+
+    case IOERR_CMD_ABORTED:
+      ret |= RPERR_INTERRUPTED;
+      break;
+
+    case IOERR_RBA_ADDRESSING_ERROR:
+      ret |= RPERR_SEEK;
+      break;
+
+    case IOERR_RBA_LIMIT:
+      ret |= RPERR_SECTOR;
+      break;
+
+    case IOERR_CMD_SGLIST_BAD:
+      ret |= RPERR_PARAMETER;
+      break;
+
+    case IOERR_DEVICE_NONSPECIFIC:
+    case IOERR_ADAPTER_TIMEOUT:
+    case IOERR_ADAPTER_DEVICEBUSCHECK:
+    case IOERR_CMD_ADD_SOFTWARE_FAILURE:
+    case IOERR_CMD_SW_RESOURCE:
+    default:
+      ret |= RPERR_GENERAL;
+      break;
+    }
+
+    /* copy sense information, if there is any */
+    if ((ic->iorb.iorbh.Status & IORB_STATUSBLOCK_AVAIL) &&
+        (ic->ssb.Flags | STATUS_SENSEDATA_VALID))
+    {
+      memcpy(sense_buf, ic->sense, min(ic->ssb.ReqSenseLen, req->sense_len));
+    }
+
+  }
+  else if ((req->flags & PT_ATAPI) == 0)
+  {
+    /* Copy ATA cmd back to IOCTL request (ATA commands are effectively
+     * registers which are sometimes used to indicate return conditions,
+     * e.g. when requesting the smart status)
+     */
+    memcpy(&req->cmd.ata, ic->cmd, sizeof(req->cmd.ata));
+  }
+
+  MemFree(ic);
+  return(ret);
+}
+
+/******************************************************************************
+ * Generic disk IOCTL handler; this IOCTL category has originally been defined
+ * in IBM1S506; the code has been more or less copied from DANIS506.
+ *
+ * NOTE: Only a subset of the IOCTL calls are implemented in OS2AHCI at this
+ *       point, basically those calls required to get HDMON working.
+ */
+USHORT ioctl_gen_dsk(REQPACKET *ioctl)
+{
+  DSKSP_CommandParameters *cp = (DSKSP_CommandParameters*)Far16ToFlat(ioctl->ioctl.pvParm);
+  UnitInformationData *ui;
+  OS2AHCI_PASSTHROUGH pt;
+  REQPACKET tmp_ioctl;
+  USHORT size = 0;
+  USHORT ret;
+  USHORT a;
+  USHORT p;
+  USHORT d;
+  UCHAR unit;
+
+  unit = cp->byPhysicalUnit;
+
+  /* verify addressability of data buffer (depends on function code) */
+  switch (ioctl->ioctl.bFunction)
+  {
+  case DSKSP_GEN_GET_COUNTERS:
+    size = sizeof(DeviceCountersData);
+    break;
+
+  case DSKSP_GET_UNIT_INFORMATION:
+    size = sizeof(UnitInformationData);
+    break;
+
+  case DSKSP_GET_INQUIRY_DATA:
+    size = ATA_ID_WORDS * sizeof(u16);
+    break;
+  }
+
+  if (map_unit(unit, &a, &p, &d))
+  {
+    return(RPDONE | RPERR | RPERR_UNIT);
+  }
+
+  /* execute generic disk request */
+  switch (ioctl->ioctl.bFunction)
+  {
+  case DSKSP_GEN_GET_COUNTERS:
+    /* Not supported, yet; we would need dynamically allocated device
+     * structures to cope with the memory requirements of the corresponding
+     * statistics buffer. For the time being, we'll return an empty buffer.
+     */
+    memset(Far16ToFlat(ioctl->ioctl.pvData), 0x00, sizeof(DeviceCountersData));
+    ret = RPDONE;
+    break;
+
+  case DSKSP_GET_UNIT_INFORMATION:
+    /* get unit information; things like port addresses won't fit so we don't
+     * even bother returning those.
+     */
+    ui = (UnitInformationData*)Far16ToFlat(ioctl->ioctl.pvData);
+    memset(ui, 0x00, sizeof(*ui));
+
+    ui->wRevisionNumber = 1;
+    ui->wIRQ            = ad_infos[a].irq;
+    ui->wFlags          = UIF_VALID;
+    ui->wFlags         |= UIF_RUNNING_BMDMA;
+    ui->wFlags         |= (unit & 0x0001) ? UIF_SLAVE : 0;
+    ui->wFlags         |= (ad_infos[a].ports[p].devs[d].atapi) ? UIF_ATAPI : 0;
+    ui->wFlags         |= UIF_SATA;
+
+    ret = RPDONE;
+    break;
+
+  case DSKSP_GET_INQUIRY_DATA:
+    /* return ATA ID buffer */
+    memset(&tmp_ioctl, 0x00, sizeof(tmp_ioctl));
+    tmp_ioctl.ioctl.bCategory   = OS2AHCI_IOCTL_CATEGORY;
+    tmp_ioctl.ioctl.bFunction   = OS2AHCI_IOCTL_PASSTHROUGH;
+    tmp_ioctl.ioctl.pvParm = FlatToFar16(&pt);
+
+    memset(&pt, 0x00, sizeof(pt));
+    pt.adapter          = a;
+    pt.port             = p;
+    pt.device           = d;
+    pt.cmdlen           = sizeof(pt.cmd.ata);
+    pt.cmd.ata.cmd      = (ad_infos[a].ports[p].devs[d].atapi) ?
+                           ATA_CMD_ID_ATAPI : ATA_CMD_ID_ATA;
+    pt.buflen           = size;
+    pt.buf              = Far16ToFlat(ioctl->ioctl.pvData);
+
+    ret = gen_ioctl(&tmp_ioctl);
+    break;
+
+  default:
+    ret = RPDONE | RPERR_BADCOMMAND;
+    break;
+  }
+
+  return(ret);
+}
+
+/******************************************************************************
+ * SMART IOCTL handler; this IOCTL category has originally been defined in
+ * IBM1S506; the code has been more or less copied from DANIS506.
+ */
+USHORT ioctl_smart(REQPACKET *ioctl)
+{
+  DSKSP_CommandParameters *cp = (DSKSP_CommandParameters *)Far16ToFlat(ioctl->ioctl.pvParm);
+  USHORT size = 0;
+  USHORT ret;
+  UCHAR unit;
+  UCHAR parm;
+
+  unit = cp->byPhysicalUnit;
+
+  /* verify addressability of data buffer (depends on SMART function) */
+  switch (ioctl->ioctl.bFunction)
+  {
+
+  case DSKSP_SMART_GETSTATUS:
+    size = sizeof(ULONG);
+    break;
+
+  case DSKSP_SMART_GET_ATTRIBUTES:
+  case DSKSP_SMART_GET_THRESHOLDS:
+  case DSKSP_SMART_GET_LOG:
+    size = 512;
+    break;
+  }
+
+  if (size > 0)
+  {
+    parm = *(UCHAR*)Far16ToFlat(ioctl->ioctl.pvData);
+  }
+
+  /* execute SMART request */
+  switch (ioctl->ioctl.bFunction)
+  {
+  case DSKSP_SMART_ONOFF:
+    ret = do_smart(unit, (BYTE) ((parm) ? ATA_SMART_ENABLE : ATA_SMART_DISABLE), 0, 0, NULL);
+    break;
+
+  case DSKSP_SMART_AUTOSAVE_ONOFF:
+    ret = do_smart(unit, ATA_SMART_AUTOSAVE, (BYTE) ((parm) ? (BYTE) 0xf1 : 0), 0, NULL);
+    break;
+
+  case DSKSP_SMART_AUTO_OFFLINE:
+    ret = do_smart(unit, ATA_SMART_AUTO_OFFLINE, parm, 0, NULL);
+    break;
+
+  case DSKSP_SMART_EXEC_OFFLINE:
+    ret = do_smart(unit, ATA_SMART_IMMEDIATE_OFFLINE, 0, parm, NULL);
+    break;
+
+  case DSKSP_SMART_SAVE:
+    ret = do_smart(unit, ATA_SMART_SAVE, 0, 0, NULL);
+    break;
+
+  case DSKSP_SMART_GETSTATUS:
+    ret = do_smart(unit, ATA_SMART_STATUS, 0, 0, Far16ToFlat(ioctl->ioctl.pvData));
+    break;
+
+  case DSKSP_SMART_GET_ATTRIBUTES:
+    ret = do_smart(unit, ATA_SMART_READ_VALUES, 0, 0, Far16ToFlat(ioctl->ioctl.pvData));
+    break;
+
+  case DSKSP_SMART_GET_THRESHOLDS:
+    ret = do_smart(unit, ATA_SMART_READ_THRESHOLDS, 0, 0, Far16ToFlat(ioctl->ioctl.pvData));
+    break;
+
+  case DSKSP_SMART_GET_LOG:
+    ret = do_smart(unit, ATA_SMART_READ_LOG, 1, parm, Far16ToFlat(ioctl->ioctl.pvData));
+    break;
+
+  default:
+    ret = RPDONE | RPERR_BADCOMMAND;
+  }
+
+  return(ret);
+}
+
+/******************************************************************************
+ * Perform SMART request. The code has been more or less copied from DANIS506.
+ */
+static USHORT do_smart(BYTE unit, BYTE sub_func, BYTE cnt, BYTE lba_l, void *buf)
+{
+  OS2AHCI_PASSTHROUGH pt;
+  REQPACKET ioctl;
+  USHORT ret;
+  USHORT a;
+  USHORT p;
+  USHORT d;
+
+  if (map_unit(unit, &a, &p, &d))
+  {
+    return(RPDONE | RPERR_UNIT);
+  }
+
+  /* Perform SMART request using the existing OS2AHCI_IOTCL_PASSTHROUGH IOCTL
+   * interface which already takes care of allocating an IORB, s/g lists, etc.
+   */
+  memset(&ioctl, 0x00, sizeof(ioctl));
+  ioctl.ioctl.bCategory   = OS2AHCI_IOCTL_CATEGORY;
+  ioctl.ioctl.bFunction   = OS2AHCI_IOCTL_PASSTHROUGH;
+  ioctl.ioctl.pvParm = FlatToFar16(&pt);
+
+  memset(&pt, 0x00, sizeof(pt));
+  pt.adapter          = a;
+  pt.port             = p;
+  pt.device           = d;
+  pt.cmdlen           = sizeof(pt.cmd.ata);
+  pt.cmd.ata.features = sub_func;
+  pt.cmd.ata.count    = cnt;
+  pt.cmd.ata.lba_l    = (0xc24fL << 8) | lba_l;
+  pt.cmd.ata.cmd      = ATA_CMD_SMART;
+
+  if (buf != NULL && sub_func != ATA_SMART_STATUS)
+  {
+    pt.buflen         = 512;
+    pt.buf            = buf;
+  }
+
+  if (((ret = gen_ioctl(&ioctl)) & RPERR) == 0 && sub_func == ATA_SMART_STATUS)
+  {
+    /* ATA_SMART_STATUS doesn't transfer anything but instead relies on the
+     * returned D2H FIS, mapped to the ATA CMD, to have a certain value
+     * (0xf42c); the IOCTL result is expected to be returned as a ULONG in
+     * the data buffer.
+     */
+    if (((pt.cmd.ata.lba_l >> 8) & 0xffff) == 0xf42c)
+    {
+      *((ULONG *) buf) = 1;
+    } else {
+      *((ULONG *) buf) = 0;
+    }
+  }
+
+  return(ret);
+}
+
+/******************************************************************************
+ * Map DSKSP unit number to corresponding adapter/port/device number. Units
+ * are identified by an 8-bit adapter/device number with the lowest bit
+ * selecting between master (0) and slave (1). This number is mapped to our
+ * ATA/ATAPI units sequentially.
+ */
+static int map_unit(BYTE unit, USHORT *a, USHORT *p, USHORT *d)
+{
+  USHORT _a;
+  USHORT _p;
+  USHORT _d;
+
+  /* map unit to adapter/port/device */
+  for (_a = 0; _a < ad_info_cnt; _a++)
+  {
+    AD_INFO *ai = ad_infos + _a;
+
+    for (_p = 0; _p <= ai->port_max; _p++)
+    {
+      P_INFO *pi = ai->ports + _p;
+
+      for (_d = 0; _d <= pi->dev_max; _d++)
+      {
+        if (pi->devs[_d].present)
+        {
+          if (unit-- == 0)
+          {
+            /* found the device */
+            *a = _a;
+            *p = _p;
+            *d = _d;
+            return(0);
+          }
+        }
+      }
+    }
+  }
+
+  /* unit not found */
+  return(-1);
+}
+
+/******************************************************************************
+ * IORB notification routine; used to wake up the sleeping application thread
+ * when the IOCTL IORB is complete.
+ */
+IORBH *IoctlWakeup(ULONG ulArg)
+{
+  KernWakeup(ulArg, 0, NULL, 0);
+  return(NULL);
+}
+
Index: /tags/2.02/src/os2ahci/ioctl.h
===================================================================
--- /tags/2.02/src/os2ahci/ioctl.h	(revision 189)
+++ /tags/2.02/src/os2ahci/ioctl.h	(revision 189)
@@ -0,0 +1,207 @@
+/******************************************************************************
+ * ioctl.h - IOCTL structures and constants for os2ahci driver
+ *
+ * Copyright (c) 2011 thi.guten Software Development
+ * Copyright (c) 2011 Mensys B.V.
+ * Copyright (c) 2013-2016 David Azarewicz
+ *
+ * Authors: Christian Mueller, Markus Thielen
+ *
+ * Parts copied from/inspired by the Linux AHCI driver;
+ * those parts are (c) Linux AHCI/ATA maintainers
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+/* -------------------------- macros and constants ------------------------- */
+
+/* IOCTL categories and functions */
+#define OS2AHCI_IOCTL_CATEGORY         0x87
+#define OS2AHCI_IOCTL_GET_DEVLIST      0x01
+#define OS2AHCI_IOCTL_PASSTHROUGH      0x02
+
+/* IOCTL definitions from s506oem.h (primarily required for SMART calls) */
+#define DSKSP_CAT_SMART             0x80  /* SMART IOCTL category */
+#define DSKSP_SMART_ONOFF           0x20  /* turn SMART on or off */
+#define DSKSP_SMART_AUTOSAVE_ONOFF  0x21  /* turn SMART autosave on or off */
+#define DSKSP_SMART_SAVE            0x22  /* force save of SMART data */
+#define DSKSP_SMART_GETSTATUS       0x23  /* get SMART status (pass/fail) */
+#define DSKSP_SMART_GET_ATTRIBUTES  0x24  /* get SMART attributes table */
+#define DSKSP_SMART_GET_THRESHOLDS  0x25  /* get SMART thresholds table */
+#define DSKSP_SMART_GET_LOG         0x26  /* get SMART log        table */
+#define DSKSP_SMART_AUTO_OFFLINE    0x27  /* set SMART offline autosave timer */
+#define DSKSP_SMART_EXEC_OFFLINE    0x28  /* execute SMART immediate offline */
+
+#define SMART_CMD_ON                   1  /* on value for related SMART functions */
+#define SMART_CMD_OFF                  0  /* off value for related SMART functions */
+
+#define DSKSP_CAT_GENERIC           0x90  /* generic IOCTL category */
+#define DSKSP_GEN_GET_COUNTERS      0x40  /* get general counter values table */
+#define DSKSP_GET_UNIT_INFORMATION  0x41  /* get unit configuration and BM DMA counters */
+#define DSKSP_GET_INQUIRY_DATA      0x42  /* get ATA/ATAPI inquiry data */
+
+/* unit information structure flags from s506oem.h */
+#define UIF_VALID           0x8000U       /* unit information valid */
+#define UIF_TIMINGS_VALID   0x4000U       /* timing information valid */
+#define UIF_RUNNING_BMDMA   0x2000U       /* running Bus Master DMA on unit */
+#define UIF_RUNNING_DMA     0x1000U       /* running slave DMA on unit */
+#define UIF_SATA            0x0004U       /* SATA             */
+#define UIF_SLAVE           0x0002U       /* slave on channel */
+#define UIF_ATAPI           0x0001U       /* ATAPI device if 1, ATA otherwise */
+
+/* device flags */
+#define DF_LBA48            0x0001U
+#define DF_ATAPI            0x0002U
+#define DF_ATAPI_16         0x0004U
+#define DF_REMOVABLE        0x0008U
+
+/* passthrough flags */
+#define PT_WRITE            0x0001U    /* transfer direction host -> device */
+#define PT_ATAPI            0x0002U    /* ATAPI command (ATA if not set) */
+
+/* ------------------------ typedefs and structures ------------------------ */
+
+#ifndef VMDHL_WRITE
+typedef void *LIN;
+#endif
+
+#pragma pack(1)
+
+/******************************************************************************
+ * Generic ATA-8 command structure. This is loosely based on ATA-8, i.e. we
+ * don't have to deal with shadow registers and map them to the low bytes of
+ * adjecent words, etc. but there are still some oddities which need to be
+ * taken into consideration. For example, ATA-8 says LBA-28 sector addresses
+ * are simply the lower 28 bits in the LBA field. However, the underlying
+ * transport (SATA in our case) only looks at the three lower bytes of the
+ * LBA field, much like an IDE device would do. This means that only 3 bytes
+ * of the LBA field are processed and this yields only 24 bits. The remaining
+ * 4 bits need to be stored in the "device" register....
+ *
+ * Everything else seems to behave normally, as far as this term can be
+ * applied to IDE/ATA. Further details can be found in section 7.1.5.1 of the
+ * ATA-8 spec (Inputs for 28-bit Read/Write Commands).
+ */
+typedef struct {
+  USHORT  features;       /* feature bits */
+  USHORT  count;          /* count register (e.g. number of sectors) */
+  ULONG   lba_l;          /* low 24 bits of LBA-28 (32 bits for 48-bit devices) */
+  USHORT  lba_h;          /* high 16 bits of LBA for 48-bit devices */
+  UCHAR   cmd;            /* ATA command field */
+  UCHAR   device;         /* ATA device field and upper 4 bits of LBA-28 */
+} ATACMD;
+
+/******************************************************************************
+ * Data structure for OS2AHCI_IOCTL_GET_DEVLIST; the parameter for this IOCTL
+ * is a USHORT with the number of entries in 'devs'.
+ */
+typedef struct {
+  USHORT  cnt;            /* number of entries in 'devs' */
+  struct {
+    USHORT  adapter;      /* adapter */
+    USHORT  port;         /* port */
+    USHORT  device;       /* device (port multiplier) */
+    USHORT  type;         /* device type; see UIB_TYPE_* in iorb.h */
+    USHORT  ncq_max;      /* maximum number of queued commands */
+    USHORT  flags;        /* device flags; see DF_* above */
+  } devs[1];
+} OS2AHCI_DEVLIST;
+
+/******************************************************************************
+ * Parameter structure for OS2AHCI_IOCTL_PASSTHROUGH; the data structure for
+ * this IOCTL is a buffer for sense data in case of errors.
+ */
+typedef struct {
+  USHORT  adapter;        /* adapter */
+  USHORT  port;           /* port */
+  USHORT  device;         /* device (port multiplier) */
+  USHORT  flags;          /* request flags; see PT_* above */
+  ULONG   timeout;        /* timeout in seconds (0 = default of 30s) */
+
+  USHORT  cmdlen;         /* length of ATA or ATAPI command */
+  union {
+    ATACMD ata;           /* ATA command */
+    UCHAR  cdb[20];       /* ATAPI command */
+  } cmd;
+
+  ULONG   buflen;         /* length of buffer for data transfers */
+  void *buf;              /* buffer for data transfers (32-bit linear address) */
+  USHORT  sense_len;      /* length of sense data in IOCTL DataPacket */
+} OS2AHCI_PASSTHROUGH;
+
+/******************************************************************************
+ * DSKSP command parameters; copied from s506oem.h
+ */
+typedef struct _DSKSP_CommandParameters
+{
+  BYTE        byPhysicalUnit;             /* physical unit number 0-n */
+                                          /* 0 = Pri/Mas, 1=Pri/Sla, 2=Sec/Mas, etc. */
+} DSKSP_CommandParameters;
+
+/******************************************************************************
+ * DSKSP device counters data; copied from s506oem.h
+ */
+typedef struct _DeviceCountersData
+{
+  USHORT      wRevisionNumber;            /* counter structure revision */
+  ULONG       TotalReadOperations;        /* total read operations performed */
+  ULONG       TotalWriteOperations;       /* total write operations performed */
+  ULONG       TotalWriteErrors;           /* total write errors encountered */
+  ULONG       TotalReadErrors;            /* total read errors encountered */
+  ULONG       TotalSeekErrors;            /* total seek errors encountered */
+  ULONG       TotalSectorsRead;           /* total number of sectors read */
+  ULONG       TotalSectorsWritten;        /* total number of sectors written */
+
+  ULONG       TotalBMReadOperations;      /* total bus master DMA read operations */
+  ULONG       TotalBMWriteOperations;     /* total bus master DMA write operations */
+  ULONG       ByteMisalignedBuffers;      /* total buffers on odd byte boundary */
+  ULONG       TransfersAcross64K;         /* total buffers crossing a 64K page boundary */
+  USHORT      TotalBMStatus;              /* total bad busmaster status */
+  USHORT      TotalBMErrors;              /* total bad busmaster error */
+  ULONG       TotalIRQsLost;              /* total lost interrupts */
+  USHORT      TotalDRQsLost;              /* total lost data transfer requests */
+  USHORT      TotalBusyErrors;            /* total device busy timeouts        */
+  USHORT      TotalBMStatus2;             /* total bad busmaster status */
+  USHORT      TotalChipStatus;            /* total bad chip status */
+  USHORT      ReadErrors[4];
+  USHORT      WriteErrors[2];
+  USHORT      SeekErrors[2];
+  USHORT      SATAErrors;
+} DeviceCountersData;
+
+/******************************************************************************
+ * DSKSP unit information data; copied from s506oem.h
+ */
+typedef struct _UnitInformationData
+{
+  USHORT      wRevisionNumber;            /* structure revision number */
+  union {
+    struct {
+      USHORT  wTFBase;                    /* task file register base addr */
+      USHORT  wDevCtl;                    /* device control register addr */
+    } rev0;
+    ULONG     dTFBase;                    /* task file register base addr */
+  };
+  USHORT      wIRQ;                       /* interrupt request level */
+  USHORT      wFlags;                     /* flags */
+  UCHAR       byPIO_Mode;                 /* PIO transfer mode programmed */
+  UCHAR       byDMA_Mode;                 /* DMA transfer mode programmed */
+  ULONG       UnitFlags1;
+  USHORT      UnitFlags2;
+} UnitInformationData;
+
+
+#pragma pack()
+
Index: /tags/2.02/src/os2ahci/os2ahci.c
===================================================================
--- /tags/2.02/src/os2ahci/os2ahci.c	(revision 189)
+++ /tags/2.02/src/os2ahci/os2ahci.c	(revision 189)
@@ -0,0 +1,1680 @@
+/******************************************************************************
+ * os2ahci.c - main file for os2ahci driver
+ *
+ * Copyright (c) 2011 thi.guten Software Development
+ * Copyright (c) 2011 Mensys B.V.
+ * Copyright (c) 2013-2016 David Azarewicz
+ *
+ * Authors: Christian Mueller, Markus Thielen
+ *
+ * Parts copied from/inspired by the Linux AHCI driver;
+ * those parts are (c) Linux AHCI/ATA maintainers
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+#include "os2ahci.h"
+#include "ioctl.h"
+#include "version.h"
+#include "devhdr.h"
+
+/* -------------------------- macros and constants ------------------------- */
+
+/* set two-dimensional array of port options */
+#define set_port_option(opt, val)                         \
+  if (adapter_index == -1) {                              \
+    /* set option for all adapters and ports */           \
+    memset(opt, val, sizeof(opt));                        \
+  } else if (port_index == -1) {                          \
+    /* set option for all ports on current adapter */     \
+    memset(opt[adapter_index], val, sizeof(*opt));        \
+  } else {                                                \
+    /* set option for specific port */                    \
+    opt[adapter_index][port_index] = val;                 \
+  }
+
+#define FLAG_KRNL_EXIT_ADD        0x1000
+#define FLAG_KRNL_EXIT_REMOVE     0x2000
+
+#define TYPE_KRNL_EXIT_NMI        0x0000  /* non masked interrupts */
+#define TYPE_KRNL_EXIT_SFF        0x0001  /* system fatal faults */
+#define TYPE_KRNL_EXIT_PROCDUMP   0x0002
+#define TYPE_KRNL_EXIT_DYN        0x0003
+#define TYPE_KRNL_EXIT_INT13      0x0004  /* enable int13 IO */
+
+/* ------------------------ typedefs and structures ------------------------ */
+
+/* -------------------------- function prototypes -------------------------- */
+
+extern int SetPsdPutc(void);
+static int add_unit_info(IORB_CONFIGURATION *iorb_conf, int dt_ai, int a, int p, int d, int scsi_id);
+
+/* ------------------------ global/static variables ------------------------ */
+int thorough_scan = 1; /* if != 0, perform thorough PCI scan */
+int init_reset = 1;    /* if != 0, reset ports during init */
+int force_write_cache; /* if != 0, force write cache */
+int verbosity = 0;     /* default is quiet. 1=show sign on banner, >1=show adapter info during boot */
+int use_lvm_info = 1;
+long com_baud = 0;
+
+HDRIVER rm_drvh;           /* resource manager driver handle */
+USHORT add_handle;        /* driver handle (RegisterDeviceClass) */
+char drv_name[] = "OS2AHCI"; /* driver name as string */
+
+/* resource manager driver information structure */
+static DRIVERSTRUCT rm_drvinfo =
+{
+  NULL, /* We cannot do Flat to Far16 conversion at compile time */
+  NULL, /* so we put NULLs in all the Far16 fields and then fill */
+  NULL, /* them in at run time                                   */
+  DMAJOR,
+  DMINOR,
+  BLD_YEAR, BLD_MONTH, BLD_DAY,
+  0,
+  DRT_ADDDM,
+  DRS_ADD,
+  NULL
+};
+
+SpinLock_t drv_lock;          /* driver-level spinlock */
+IORB_QUEUE driver_queue;      /* driver-level IORB queue */
+AD_INFO ad_infos[MAX_AD];  /* adapter information list */
+int ad_info_cnt;       /* number of entries in ad_infos[] */
+u16 ad_ignore;         /* bitmap with adapter indexes to ignore */
+int init_complete;     /* if != 0, initialization has completed */
+int suspended;
+int resume_sleep_flag;
+
+/* apapter/port-specific options saved when parsing the command line */
+u8 emulate_scsi[MAX_AD][AHCI_MAX_PORTS];
+u8 enable_ncq[MAX_AD][AHCI_MAX_PORTS];
+u8 link_speed[MAX_AD][AHCI_MAX_PORTS];
+u8 link_power[MAX_AD][AHCI_MAX_PORTS];
+u8 track_size[MAX_AD][AHCI_MAX_PORTS];
+u8 port_ignore[MAX_AD][AHCI_MAX_PORTS];
+
+char BldLevel[] = BLDLEVEL;
+
+/* ----------------------------- start of code ----------------------------- */
+
+/******************************************************************************
+ * OS/2 device driver main strategy function.
+ *
+ * NOTE: this is also used as the IDC entry point. We expect an IOCTL request
+ *       packet for IDC calls, so they can be handled by gen_ioctl.
+ */
+void StrategyHandler(REQPACKET *prp)
+{
+  u16 rc;
+
+  switch (prp->bCommand)
+  {
+  case STRATEGY_BASEDEVINIT:
+    rc = init_drv(prp);
+    break;
+
+  case STRATEGY_SHUTDOWN:
+    rc = exit_drv(prp->save_restore.Function);
+    break;
+
+  case STRATEGY_GENIOCTL:
+    rc = gen_ioctl(prp);
+    break;
+
+  case STRATEGY_OPEN:
+    build_user_info();
+    rc = RPDONE;
+    break;
+
+  case STRATEGY_READ:
+    rc = char_dev_input(prp);
+    break;
+
+  case STRATEGY_SAVERESTORE:
+    rc = sr_drv(prp->save_restore.Function);
+    break;
+
+  case STRATEGY_INITCOMPLETE:
+  case STRATEGY_CLOSE:
+  case STRATEGY_INPUTSTATUS:
+  case STRATEGY_FLUSHINPUT:
+    /* noop */
+    rc = RPDONE;
+    break;
+
+  default:
+    rc = RPDONE | RPERR_BADCOMMAND;
+    break;
+  }
+
+  prp->usStatus = rc;
+}
+
+void IdcHandler(REQPACKET *prp)
+{
+  StrategyHandler(prp);
+}
+
+/******************************************************************************
+ * Intialize the os2ahci driver. This includes command line parsing, scanning
+ * the PCI bus for supported AHCI adapters, etc.
+ */
+USHORT init_drv(REQPACKET *req)
+{
+  static int init_drv_called;
+  static int init_drv_failed;
+  APIRET rmrc;
+  const char *pszCmdLine, *cmd_line;
+  int adapter_index = -1;
+  int port_index = -1;
+  int iInvertOption;
+  int iStatus;
+
+  if (init_drv_called)
+  {
+    /* This is the init call for the second (IBMS506$) character
+     * device driver. If the main driver failed initialization, fail this
+     * one as well.
+     */
+    return(RPDONE | ((init_drv_failed) ? RPERR_INITFAIL : 0));
+  }
+  D32g_DbgLevel = 0;
+  init_drv_called = 1;
+  suspended = 0;
+  resume_sleep_flag = 0;
+  memset(ad_infos, 0, sizeof(ad_infos));
+  memset(emulate_scsi, 1, sizeof(emulate_scsi)); /* set default enabled */
+  UtSetDriverName("OS2AHCI$");
+  Header.ulCaps |= DEV_ADAPTER_DD; /* DAZ This flag is not really needed. */
+
+  /* create driver-level spinlock */
+  KernAllocSpinLock(&drv_lock);
+
+  /* register driver with resource manager */
+  rm_drvinfo.DrvrName = drv_name;
+  rm_drvinfo.DrvrDescript = "AHCI SATA Driver";
+  rm_drvinfo.VendorName = DVENDOR;
+  if ((rmrc = RMCreateDriver(&rm_drvinfo, &rm_drvh)) != RMRC_SUCCESS)
+  {
+    iprintf("%s: failed to register driver with resource manager (rc = %d)", drv_name, rmrc);
+    goto init_fail;
+  }
+
+  pszCmdLine = cmd_line = req->init_in.szArgs;
+  iStatus = 0;
+  while (*pszCmdLine)
+  {
+    if (*pszCmdLine++ != '/') continue; /* Ignore anything that doesn't start with '/' */
+    /* pszCmdLine now points to first char of argument */
+
+    if ((iInvertOption = (*pszCmdLine == '!')) != 0) pszCmdLine++;
+
+    if (ArgCmp(pszCmdLine, "B:"))
+    {
+      pszCmdLine += 2;
+      com_baud = strtol(pszCmdLine, &pszCmdLine, 0);
+      continue;
+    }
+
+    if (ArgCmp(pszCmdLine, "C:"))
+    {
+      pszCmdLine += 2;
+      /* set COM port base address for debug messages */
+      D32g_ComBase = strtol(pszCmdLine, &pszCmdLine, 0);
+      if (D32g_ComBase == 1) D32g_ComBase = 0x3f8;
+      if (D32g_ComBase == 2) D32g_ComBase = 0x2f8;
+      continue;
+    }
+
+    if (ArgCmp(pszCmdLine, "D"))
+    {
+      pszCmdLine++;
+      if (*pszCmdLine == ':')
+      {
+        pszCmdLine++;
+        D32g_DbgLevel = strtol(pszCmdLine, &pszCmdLine, 0);
+      }
+      else D32g_DbgLevel++; /* increase debug level */
+      continue;
+    }
+
+    if (ArgCmp(pszCmdLine, "G:"))
+    {
+      u16 usVendor;
+      u16 usDevice;
+
+      pszCmdLine += 2;
+      /* add specfied PCI ID as a supported generic AHCI adapter  */
+      usVendor = strtol(pszCmdLine, &pszCmdLine, 16);
+      if (*pszCmdLine != ':') break;
+      pszCmdLine++;
+      usDevice = strtol(pszCmdLine, &pszCmdLine, 16);
+      if (add_pci_id(usVendor, usDevice))
+      {
+        iprintf("%s: failed to add PCI ID %04x:%04x", drv_name, usVendor, usDevice);
+        iStatus = 1;
+      }
+      thorough_scan = 1;
+      continue;
+    }
+
+    if (ArgCmp(pszCmdLine, "T"))
+    {
+      pszCmdLine++;
+      /* perform thorough PCI scan (i.e. look for individual supported PCI IDs) */
+      thorough_scan = !iInvertOption;
+      continue;
+    }
+
+    if (ArgCmp(pszCmdLine, "R"))
+    {
+      pszCmdLine++;
+      /* reset ports during initialization */
+      init_reset = !iInvertOption;
+      continue;
+    }
+
+    if (ArgCmp(pszCmdLine, "F"))
+    {
+      pszCmdLine++;
+      /* force write cache regardless of IORB flags */
+      force_write_cache = 1;
+      continue;
+    }
+
+    if (ArgCmp(pszCmdLine, "A:"))
+    {
+      pszCmdLine += 2;
+      /* set adapter index for adapter and port-related options */
+      adapter_index = strtol(pszCmdLine, &pszCmdLine, 0);
+      if (adapter_index < 0 || adapter_index >= MAX_AD)
+      {
+        iprintf("%s: invalid adapter index (%d)", drv_name, adapter_index);
+        iStatus = 1;
+      }
+      continue;
+    }
+
+    if (ArgCmp(pszCmdLine, "P:"))
+    {
+      pszCmdLine += 2;
+      /* set port index for port-related options */
+      port_index = strtol(pszCmdLine, &pszCmdLine, 0);
+      if (port_index < 0 || port_index >= AHCI_MAX_PORTS)
+      {
+        iprintf("%s: invalid port index (%d)", drv_name, port_index);
+        iStatus = 1;
+      }
+      continue;
+    }
+
+    if (ArgCmp(pszCmdLine, "I"))
+    {
+      pszCmdLine++;
+      /* ignore current adapter index */
+      if (adapter_index >= 0)
+      {
+        if (port_index >= 0) port_ignore[adapter_index][port_index] = !iInvertOption;
+        else ad_ignore |= 1U << adapter_index;
+      }
+      continue;
+    }
+
+    if (ArgCmp(pszCmdLine, "S"))
+    {
+      pszCmdLine++;
+      /* enable SCSI emulation for ATAPI devices */
+      set_port_option(emulate_scsi, !iInvertOption);
+      continue;
+    }
+
+    if (ArgCmp(pszCmdLine, "N"))
+    {
+      pszCmdLine++;
+      /* enable NCQ */
+      set_port_option(enable_ncq, !iInvertOption);
+      continue;
+    }
+
+    if (ArgCmp(pszCmdLine, "LS:"))
+    {
+      int optval;
+
+      pszCmdLine += 3;
+      /* set link speed */
+      optval = strtol(pszCmdLine, &pszCmdLine, 0);
+      set_port_option(link_speed, optval);
+      /* need to reset the port in order to establish link settings */
+      init_reset = 1;
+      continue;
+    }
+
+    if (ArgCmp(pszCmdLine, "LP:"))
+    {
+      int optval;
+
+      pszCmdLine += 3;
+      /* set power management */
+      optval = strtol(pszCmdLine, &pszCmdLine, 0);
+      set_port_option(link_power, optval);
+      /* need to reset the port in order to establish link settings */
+      init_reset = 1;
+      continue;
+    }
+
+    if (ArgCmp(pszCmdLine, "4"))
+    {
+      pszCmdLine++;
+      /* enable 4K sector geometry enhancement (track size = 56) */
+      if (!iInvertOption) set_port_option(track_size, 56);
+      continue;
+    }
+
+    if (ArgCmp(pszCmdLine, "Z"))
+    {
+      pszCmdLine++;
+      /* Specify to not use the LVM information. There is no reason why anyone would
+       * want to do this, but previous versions of this driver did not have LVM capability,
+       * so this switch is here temporarily just in case.
+       */
+      use_lvm_info = !iInvertOption;
+      continue;
+    }
+
+    if (ArgCmp(pszCmdLine, "V"))
+    {
+      pszCmdLine++;
+      if (*pszCmdLine == ':')
+      {
+        pszCmdLine++;
+        verbosity = strtol(pszCmdLine, &pszCmdLine, 0);
+      }
+      else verbosity++; /* increase verbosity level */
+      continue;
+    }
+
+    if (ArgCmp(pszCmdLine, "W"))
+    {
+      pszCmdLine++;
+      /* Specify to allow the trace buffer to wrap when full. */
+      D32g_DbgBufWrap = !iInvertOption;
+      continue;
+    }
+
+    iprintf("Unrecognized switch: %s", pszCmdLine-1);
+    iStatus = 1; /* unrecognized argument */
+  }
+
+  if (iStatus) goto init_fail;
+
+  if (com_baud) InitComPort(com_baud);
+
+  dprintf(0,"BldLevel: %s\n", BldLevel);
+  dprintf(0,"CmdLine: %s\n", cmd_line);
+  /*
+  if (sizeof(ADD_WORKSPACE) > ADD_WORKSPACE_SIZE)
+  {
+    dprintf(0,"ADD_WORKSPACE size is too big! %d>16\n", sizeof(ADD_WORKSPACE));
+    goto init_fail;
+  }
+  */
+
+  /* print initialization message */
+  ciprintf("%s driver version %d.%02d", drv_name, DMAJOR, DMINOR);
+
+  #ifdef TESTVER
+  #include "testver.c"
+  #endif
+
+  /* scan PCI bus for supported devices */
+  scan_pci_bus();
+
+  if (ad_info_cnt > 0)
+  {
+    /* initialization succeeded and we found at least one AHCI adapter */
+
+    if (Dev32Help_RegisterDeviceClass(drv_name, add_entry, 0, 1, &add_handle))
+    {
+      iprintf("%s: couldn't register device class", drv_name);
+      goto init_fail;
+    }
+
+    Timer_InitTimer(TIMER_COUNT);
+
+    /* allocate context hooks */
+    KernAllocateContextHook(restart_ctxhook, 0, &restart_ctxhook_h);
+    KernAllocateContextHook(reset_ctxhook, 0, &reset_ctxhook_h);
+    KernAllocateContextHook(engine_ctxhook, 0, &engine_ctxhook_h);
+
+    /* register kernel exit routine for trap dumps */
+    Dev32Help_RegisterKrnlExit(shutdown_driver, FLAG_KRNL_EXIT_ADD, TYPE_KRNL_EXIT_INT13);
+
+    return(RPDONE);
+
+  }
+  else
+  {
+    /* no adapters found */
+    ciprintf("%s: No adapters found.", drv_name);
+  }
+
+init_fail:
+  /* initialization failed; set segment sizes to 0 and return error */
+  init_drv_failed = 1;
+
+  if (rm_drvh != 0)
+  {
+    /* remove driver from resource manager */
+    RMDestroyDriver(rm_drvh);
+  }
+
+  ciprintf("%s driver *not* installed", drv_name);
+  return(RPDONE | RPERR_INITFAIL);
+}
+
+/******************************************************************************
+ * Generic IOCTL via character device driver. IOCTLs are used to control the
+ * driver operation and to execute native ATA and ATAPI (SCSI) commands from
+ * ring 3 applications. On top of that, some predefined IOCTLs (e.g. SMART
+ * commands for ATA disks) are implemented here.
+ */
+USHORT gen_ioctl(REQPACKET *ioctl)
+{
+  DPRINTF(2,"IOCTL 0x%x/0x%x\n", ioctl->ioctl.bCategory, ioctl->ioctl.bFunction);
+
+  switch (ioctl->ioctl.bCategory)
+  {
+  case OS2AHCI_IOCTL_CATEGORY:
+    switch (ioctl->ioctl.bFunction)
+    {
+
+    case OS2AHCI_IOCTL_GET_DEVLIST:
+      return(ioctl_get_devlist(ioctl));
+
+    case OS2AHCI_IOCTL_PASSTHROUGH:
+      return(ioctl_passthrough(ioctl));
+
+    }
+    break;
+
+  case DSKSP_CAT_GENERIC:
+    return(ioctl_gen_dsk(ioctl));
+
+  case DSKSP_CAT_SMART:
+    return(ioctl_smart(ioctl));
+  }
+
+  return(RPDONE | RPERR_BADCOMMAND);
+}
+
+/******************************************************************************
+ * Read from character device. If tracing is on (internal ring buffer trace),
+ * we return data from the trace buffer; if not, we might return a device
+ * dump similar to IBM1S506.ADD/DANIS506.ADD (TODO).
+ */
+USHORT char_dev_input(REQPACKET *pPacket)
+{
+  void *LinAdr;
+
+  if (Dev32Help_PhysToLin(pPacket->io.ulAddress, pPacket->io.usCount, &LinAdr))
+  {
+    pPacket->io.usCount = 0;
+    return RPDONE | RPERR_GENERAL;
+  }
+
+  pPacket->io.usCount = dCopyToUser(LinAdr, pPacket->io.usCount);
+
+  return RPDONE;
+}
+
+/******************************************************************************
+ * Device driver exit handler. This handler is called when OS/2 shuts down and
+ * flushes the write caches of all attached devices. Since this is effectively
+ * the same we do when suspending, we'll call out to the corresponding suspend
+ * function.
+ *
+ * NOTE: Errors are ignored because there's no way we could stop the shutdown
+ *       or do something about the error, unless retrying endlessly is
+ *       considered an option.
+ */
+USHORT exit_drv(int func)
+{
+  DPRINTF(2,"exit_drv(%d) called\n", func);
+
+  if (func == 0)
+  {
+    /* we're only interested in the second phase of the shutdown */
+    return(RPDONE);
+  }
+
+  suspend();
+  return(RPDONE);
+}
+
+/******************************************************************************
+ * Device driver suspend/resume handler. This handler is called when ACPI is
+ * executing a suspend or resume.
+ */
+USHORT sr_drv(int func)
+{
+  DPRINTF(2,"sr_drv(%d) called\n", func);
+
+  if (func) resume();
+  else suspend();
+
+  return(RPDONE);
+}
+
+/******************************************************************************
+ * ADD entry point. This is the main entry point for all ADD requests. Due to
+ * the asynchronous nature of ADD drivers, this function primarily queues the
+ * IORB(s) to the corresponding adapter or port queues, then triggers the
+ * state machine to initiate processing queued IORBs.
+ *
+ * NOTE: In order to prevent race conditions or engine stalls, certain rules
+ *       around locking, unlocking and IORB handling in general have been
+ *       established. Refer to the comments in "trigger_engine()" for
+ *       details.
+ */
+void add_entry(IORBH FAR16DATA *vFirstIorb)
+{
+  IORBH FAR16DATA *vIorb;
+  IORBH FAR16DATA *vNext = NULL;
+
+  spin_lock(drv_lock);
+
+  for (vIorb=vFirstIorb; vIorb!=NULL; vIorb=vNext)
+  {
+    IORBH *pIorb = Far16ToFlat(vIorb);
+
+    /* Queue this IORB. Queues primarily exist on port level but there are
+     * some requests which affect the whole driver, most notably
+     * IOCC_CONFIGURATION. In either case, adding the IORB to the driver or
+     * port queue will change the links, thus we need to save the original
+     * link in 'vNext'.
+     */
+    if (pIorb->RequestControl & IORB_CHAIN) vNext = pIorb->pNxtIORB;
+    else vNext = (IORBH FAR16DATA *)0;
+
+    pIorb->Status = 0;
+    pIorb->ErrorCode = 0;
+    memset(&pIorb->ADDWorkSpace, 0x00, sizeof(ADD_WORKSPACE));
+
+    #ifdef DEBUG
+    DumpIorb(pIorb); /* DAZ TESTING */
+    #endif
+
+    if (iorb_driver_level(pIorb))
+    {
+      /* driver-level IORB */
+      pIorb->UnitHandle = 0;
+      iorb_queue_add(&driver_queue, vIorb, pIorb);
+
+    }
+    else
+    {
+      /* port-level IORB */
+      int a = iorb_unit_adapter(pIorb);
+      int p = iorb_unit_port(pIorb);
+      int d = iorb_unit_device(pIorb);
+
+      if (a >= ad_info_cnt ||
+          p > ad_infos[a].port_max ||
+          d > ad_infos[a].ports[p].dev_max ||
+          (ad_infos[a].port_map & (1UL << p)) == 0)
+      {
+
+        /* unit handle outside of the allowed range */
+        dprintf(0,"warning: IORB for %d.%d.%d out of range\n", a, p, d);
+        pIorb->Status = IORB_ERROR;
+        pIorb->ErrorCode = IOERR_CMD_SYNTAX;
+        iorb_complete(vIorb, pIorb);
+        continue;
+      }
+
+      iorb_queue_add(&ad_infos[a].ports[p].iorb_queue, vIorb, pIorb);
+    }
+  }
+
+  /* trigger state machine */
+  trigger_engine();
+
+  spin_unlock(drv_lock);
+}
+
+/******************************************************************************
+ * Trigger IORB queue engine. This is a wrapper function for trigger_engine_1()
+ * which will try to get all IORBs sent on their way a couple of times. If
+ * there are still IORBs ready for processing after this, this function will
+ * 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 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)
+{
+  int i;
+
+  for (i = 0; i < 3 || !init_complete; i++)
+  {
+    if (trigger_engine_1() == 0)
+    {
+      /* done -- all IORBs have been sent on their way */
+      return;
+    }
+  }
+
+  /* Something keeps bouncing; hand off to the engine context hook which will
+   * keep trying in the background.
+   */
+  KernArmHook(engine_ctxhook_h, 0, 0);
+}
+
+/******************************************************************************
+ * Trigger IORB queue engine in order to send commands in the driver/port IORB
+ * queues to the AHCI hardware. This function will return the number of IORBs
+ * sent. Keep in mind that IORBs might "bounce" if the adapter/port is not in
+ * a state to accept the command, thus it might take quite a few calls to get
+ * all IORBs on their way. This is why there's a wrapper function which tries
+ * it a few times, then hands off to a context hook which will keep trying in
+ * the background.
+ *
+ * IORBs might complete before send_iorb() has returned, at any time during
+ * interrupt processing or on another CPU on SMP systems. IORB completion
+ * means modifications to the corresponding IORB queue (the completed IORB
+ * is removed from the queue) thus we need to protect the IORB queues from
+ * race conditions. The safest approach short of keeping the driver-level
+ * spinlock aquired permanently is to keep it throughout this function and
+ * release it temporarily in send_iorb().
+ *
+ * This implies that the handler functions are fully responsible for aquiring
+ * the driver-level spinlock when they need it, and for releasing it again.
+ *
+ * As a rule of thumb, get the driver-level spinlock whenever accessing
+ * volatile variables (IORB queues, values in ad_info[], ...).
+ *
+ * Additional Notes:
+ *
+ * - This function is expected to be called with the spinlock aquired
+ *
+ * - Adapters can be flagged as 'busy' which means no new IORBs are sent (they
+ *   just remain in the queue). This can be used to release the driver-level
+ *   spinlock while making sure no new IORBs are going to hit the hardware.
+ *   In order to prevent engine stalls, all handlers using this functionality
+ *   need to invoke trigger_engine() after resetting the busy flag.
+ *
+ * - Driver-level IORBs are not synchronized by adapter-level 'busy' flags.
+ *   However, the driver-level queue is worked "one entry at a time" which
+ *   means that no new IORBs will be queued on the driver-level queue until
+ *   the head element has completed processing. This means that driver-
+ *   level IORB handlers don't need to protect against each other. But they
+ *   they do need to keep in mind interference with port-level IORBs:
+ *
+ *   - Driver-level IORB handlers must obtain the spinlock and/or flag all
+ *     adapters as 'busy' which are affected by the driver-level IORB
+ *
+ *   - Driver-level IORB handlers must not access the hardware of a
+ *     particular adapter if it's flagged as 'busy' by another IORB.
+ */
+int trigger_engine_1(void)
+{
+  IORBH FAR16DATA *vIorb;
+  IORBH *pIorb;
+  IORBH FAR16DATA *vNext;
+  int iorbs_sent = 0;
+  int a;
+  int p;
+
+  iorbs_sent = 0;
+
+  /* process driver-level IORBs */
+  if ((vIorb = driver_queue.vRoot) != NULL)
+  {
+    pIorb = Far16ToFlat(vIorb);
+
+    if (!add_workspace(pIorb)->processing)
+    {
+      send_iorb(vIorb, pIorb);
+      iorbs_sent++;
+    }
+  }
+
+  /* process port-level IORBs */
+  for (a = 0; a < ad_info_cnt; a++)
+  {
+    AD_INFO *ai = ad_infos + a;
+    if (ai->busy)
+    {
+      /* adapter is busy; don't process any IORBs */
+      continue;
+    }
+    for (p = 0; p <= ai->port_max; p++)
+    {
+      /* send all queued IORBs on this port */
+      vNext = NULL;
+      for (vIorb = ai->ports[p].iorb_queue.vRoot; vIorb != NULL; vIorb = vNext)
+      {
+        pIorb = Far16ToFlat(vIorb);
+
+        vNext = pIorb->pNxtIORB;
+        if (!add_workspace(pIorb)->processing)
+        {
+          send_iorb(vIorb, pIorb);
+          iorbs_sent++;
+        }
+      }
+    }
+  }
+
+  return(iorbs_sent);
+}
+
+/******************************************************************************
+ * Send a single IORB to the corresponding AHCI adapter/port. This is just a
+ * switch board for calling the corresponding iocc_*() handler function.
+ *
+ * NOTE: This function is expected to be called with the driver-level spinlock
+ *       aquired. It will release it before calling any of the handler
+ *       functions and re-aquire it when done.
+ */
+void send_iorb(IORBH FAR16DATA *vIorb, IORBH *pIorb)
+{
+  /* Mark IORB as "processing" before doing anything else. Once the IORB is
+   * marked as "processing", we can release the spinlock because subsequent
+   * invocations of trigger_engine() (e.g. at interrupt time) will ignore this
+   * IORB.
+   */
+  add_workspace(pIorb)->processing = 1;
+  spin_unlock(drv_lock);
+
+  switch (pIorb->CommandCode)
+  {
+  case IOCC_CONFIGURATION:
+    iocc_configuration(vIorb, pIorb);
+    break;
+
+  case IOCC_DEVICE_CONTROL:
+    iocc_device_control(vIorb, pIorb);
+    break;
+
+  case IOCC_UNIT_CONTROL:
+    iocc_unit_control(vIorb, pIorb);
+    break;
+
+  case IOCC_GEOMETRY:
+    iocc_geometry(vIorb, pIorb);
+    break;
+
+  case IOCC_EXECUTE_IO:
+    iocc_execute_io(vIorb, pIorb);
+    break;
+
+  case IOCC_UNIT_STATUS:
+    iocc_unit_status(vIorb, pIorb);
+    break;
+
+  case IOCC_ADAPTER_PASSTHRU:
+    iocc_adapter_passthru(vIorb, pIorb);
+    break;
+
+  default:
+    /* unsupported call */
+    iorb_seterr(pIorb, IOERR_CMD_NOT_SUPPORTED);
+    iorb_done(vIorb, pIorb);
+    break;
+  }
+
+  /* re-aquire spinlock before returning to trigger_engine() */
+  spin_lock(drv_lock);
+}
+
+/******************************************************************************
+ * Handle IOCC_CONFIGURATION requests.
+ */
+void iocc_configuration(IORBH FAR16DATA *vIorb, IORBH *pIorb)
+{
+  int a;
+
+  switch (pIorb->CommandModifier)
+  {
+
+  case IOCM_COMPLETE_INIT:
+    /* Complete initialization. From now on, we won't have to restore the BIOS
+     * configuration after each command and we're fully operational (i.e. will
+     * use interrupts, timers and context hooks instead of polling).
+     */
+    if (!init_complete)
+    {
+      DPRINTF(1,"leaving initialization mode\n");
+      for (a = 0; a < ad_info_cnt; a++)
+      {
+        lock_adapter(ad_infos + a);
+        ahci_complete_init(ad_infos + a);
+      }
+      init_complete = 1;
+
+      /* release all adapters */
+      for (a = 0; a < ad_info_cnt; a++)
+      {
+        unlock_adapter(ad_infos + a);
+      }
+      DPRINTF(1,"leaving initialization mode 2\n");
+
+      #ifdef LEGACY_APM
+      /* register APM hook */
+      apm_init();
+      #endif
+    }
+    iorb_done(vIorb, pIorb);
+    break;
+
+  case IOCM_GET_DEVICE_TABLE:
+    /* construct a device table */
+    iocm_device_table(vIorb, pIorb);
+    break;
+
+  default:
+    iorb_seterr(pIorb, IOERR_CMD_NOT_SUPPORTED);
+    iorb_done(vIorb, pIorb);
+    break;
+  }
+}
+
+/******************************************************************************
+ * Handle IOCC_DEVICE_CONTROL requests.
+ */
+void iocc_device_control(IORBH FAR16DATA *vIorb, IORBH *pIorb)
+{
+  AD_INFO *ai = ad_infos + iorb_unit_adapter(pIorb);
+  IORBH FAR16DATA *vPtr;
+  IORBH FAR16DATA *vNext = NULL;
+  int p = iorb_unit_port(pIorb);
+  int d = iorb_unit_device(pIorb);
+
+  switch (pIorb->CommandModifier)
+  {
+
+  case IOCM_ABORT:
+    /* abort all pending commands on specified port and device */
+    spin_lock(drv_lock);
+    for (vPtr = ai->ports[p].iorb_queue.vRoot; vPtr != NULL; vPtr = vNext)
+    {
+      IORBH *pPtr = Far16ToFlat(vPtr);
+
+      vNext = pPtr->pNxtIORB;
+      /* move all matching IORBs to the abort queue */
+      if (vPtr != vIorb && iorb_unit_device(pPtr) == d)
+      {
+        iorb_queue_del(&ai->ports[p].iorb_queue, vPtr);
+        iorb_queue_add(&abort_queue, vPtr, pPtr);
+        pPtr->ErrorCode = IOERR_CMD_ABORTED;
+      }
+    }
+    spin_unlock(drv_lock);
+
+    /* trigger reset context hook which will finish the abort processing */
+    KernArmHook(reset_ctxhook_h, 0, 0);
+    break;
+
+  case IOCM_SUSPEND:
+  case IOCM_RESUME:
+  case IOCM_GET_QUEUE_STATUS:
+    /* Suspend/resume operations allow access to the hardware for other
+     * entities such as IBMIDECD.FLT. Since os2ahci implements both ATA
+     * and ATAPI in the same driver, this won't be required.
+     */
+    iorb_seterr(pIorb, IOERR_CMD_NOT_SUPPORTED);
+    break;
+
+  case IOCM_LOCK_MEDIA:
+  case IOCM_UNLOCK_MEDIA:
+  case IOCM_EJECT_MEDIA:
+    /* unit control commands to lock, unlock and eject media */
+    /* will be supported later... */
+    iorb_seterr(pIorb, IOERR_CMD_NOT_SUPPORTED);
+    break;
+
+  default:
+    iorb_seterr(pIorb, IOERR_CMD_NOT_SUPPORTED);
+    break;
+  }
+
+  iorb_done(vIorb, pIorb);
+}
+
+/******************************************************************************
+ * Handle IOCC_UNIT_CONTROL requests.
+ */
+void iocc_unit_control(IORBH FAR16DATA *vIorb, IORBH *pIorb)
+{
+  IORB_UNIT_CONTROL *pIorb_uc = (IORB_UNIT_CONTROL *)pIorb;
+  int a = iorb_unit_adapter(pIorb);
+  int p = iorb_unit_port(pIorb);
+  int d = iorb_unit_device(pIorb);
+
+  spin_lock(drv_lock);
+  switch (pIorb->CommandModifier)
+  {
+  case IOCM_ALLOCATE_UNIT:
+    /* allocate unit for exclusive access */
+    if (ad_infos[a].ports[p].devs[d].allocated)
+    {
+      iorb_seterr(pIorb, IOERR_UNIT_ALLOCATED);
+    }
+    else
+    {
+      ad_infos[a].ports[p].devs[d].allocated = 1;
+    }
+    break;
+
+  case IOCM_DEALLOCATE_UNIT:
+    /* deallocate exclusive access to unit */
+    if (!ad_infos[a].ports[p].devs[d].allocated)
+    {
+      iorb_seterr(pIorb, IOERR_UNIT_NOT_ALLOCATED);
+    }
+    else
+    {
+      ad_infos[a].ports[p].devs[d].allocated = 0;
+    }
+    break;
+
+  case IOCM_CHANGE_UNITINFO:
+    /* Change unit (device) information. One reason for this IOCM is the
+     * interface for filter device drivers: a filter device driver can
+     * either change existing UNITINFOs or permanently allocate units
+     * and fabricate new [logical] units; the former is the reason why we
+     * must store the pointer to the updated UNITNIFO for subsequent
+     * IOCC_CONFIGURATION/IOCM_GET_DEVICE_TABLE calls.
+     */
+    if (!ad_infos[a].ports[p].devs[d].allocated)
+    {
+      iorb_seterr(pIorb, IOERR_UNIT_NOT_ALLOCATED);
+      break;
+    }
+    ad_infos[a].ports[p].devs[d].unit_info = pIorb_uc->pUnitInfo;
+    break;
+
+  default:
+    iorb_seterr(pIorb, IOERR_CMD_NOT_SUPPORTED);
+    break;
+  }
+
+  spin_unlock(drv_lock);
+  iorb_done(vIorb, pIorb);
+}
+
+/******************************************************************************
+ * Scan all ports for AHCI devices and construct a DASD device table.
+ *
+ * NOTES: This function may be called multiple times. Only the first
+ *        invocation will actually scan for devices; all subsequent calls will
+ *        merely return the results of the initial scan, potentially augmented
+ *        by modified unit infos after IOCC_CONFIGURATION/IOCM_CHANGE_UNITINFO
+ *        requests.
+ *
+ *        In order to support applications that can't deal with ATAPI devices
+ *        (i.e. need a SCSI adapter) os2ahci will optionally report ATAPI
+ *        dvices as SCSI devices. The corresponding SCSI adapter doesn't
+ *        really exist and is only reported here for the IOCM_GET_DEVICETABLE
+ *        request. The units attached to this adapter will use the real HW
+ *        unit IDs, thus we'll never receive a command specific to the
+ *        emulated SCSI adapter and won't need to set up any sort of entity
+ *        for it; the only purpose of the emulated SCSI adapter is to pass the
+ *        bus type "AI_DEVBUS_SCSI_2" upstream, and the emulated units, of
+ *        course. The emulated SCSI target IDs are allocated as follows:
+ *
+ *         0     the virtual adapter
+ *         1..n  emulated devices; SCSI target ID increments sequentially
+ */
+void iocm_device_table(IORBH FAR16DATA *vIorb, IORBH *pIorb)
+{
+  IORB_CONFIGURATION *pIorb_conf;
+  DEVICETABLE FAR16DATA *vDt;
+  DEVICETABLE *pDt;
+  char *pPos;
+  int scsi_units = 0;
+  int scsi_id = 1;
+  int rc;
+  int dta;
+  int a;
+  int p;
+  int d;
+
+  pIorb_conf = (IORB_CONFIGURATION *)pIorb;
+  vDt = pIorb_conf->pDeviceTable;
+  pDt = Far16ToFlat(vDt);
+
+  spin_lock(drv_lock);
+
+  /* initialize device table header */
+  pDt->ADDLevelMajor = ADD_LEVEL_MAJOR;
+  pDt->ADDLevelMinor = ADD_LEVEL_MINOR;
+  pDt->ADDHandle     = add_handle;
+  pDt->TotalAdapters = ad_info_cnt + 1;
+
+  /* set start of adapter and device information tables */
+  pPos = (char*)&pDt->pAdapter[pDt->TotalAdapters];
+
+  /* go through all adapters, including the virtual SCSI adapter */
+  for (dta = 0; dta < pDt->TotalAdapters; dta++)
+  {
+    ADAPTERINFO *pPtr = (ADAPTERINFO *)pPos;
+
+    /* sanity check for sufficient space in device table */
+    if ((u32)(pPtr + 1) - (u32)pDt > pIorb_conf->DeviceTableLen)
+    {
+      dprintf(0,"error: device table provided by DASD too small\n");
+      iorb_seterr(pIorb, IOERR_CMD_SW_RESOURCE);
+      goto iocm_device_table_done;
+    }
+
+    pDt->pAdapter[dta] = MakeNear16PtrFromDiff(pIorb_conf->pDeviceTable, pDt, pPtr);
+
+    //DPRINTF(2,"iocm_device_table: ptr=%x dta=%x pAdapter[dta]=%x pDeviceTable=%x\n",
+    //  ptr, dta, dt->pAdapter[dta], iorb_conf->pDeviceTable);
+    memset(pPtr, 0x00, sizeof(*pPtr));
+
+    pPtr->AdapterIOAccess = AI_IOACCESS_BUS_MASTER;
+    pPtr->AdapterHostBus  = AI_HOSTBUS_OTHER | AI_BUSWIDTH_32BIT;
+    pPtr->AdapterFlags    = AF_16M | AF_HW_SCATGAT;
+    pPtr->MaxHWSGList     = AHCI_MAX_SG / 2;   /* AHCI S/G elements are 22 bits */
+
+    if (dta < ad_info_cnt)
+    {
+      /* this is a physical AHCI adapter */
+      AD_INFO *ad_info = ad_infos + dta;
+
+      pPtr->AdapterDevBus = AI_DEVBUS_ST506 | AI_DEVBUS_32BIT;
+      sprintf(pPtr->AdapterName, "AHCI_%d", dta);
+
+      if (!ad_info->port_scan_done)
+      {
+        /* first call; need to scan AHCI hardware for devices */
+        if (ad_info->busy)
+        {
+          dprintf(0,"error: port scan requested while adapter was busy\n");
+          iorb_seterr(pIorb, IOERR_CMD_SW_RESOURCE);
+          goto iocm_device_table_done;
+        }
+        ad_info->busy = 1;
+        spin_unlock(drv_lock);
+        rc = ahci_scan_ports(ad_info);
+        spin_lock(drv_lock);
+        ad_info->busy = 0;
+
+        if (rc != 0)
+        {
+          dprintf(0,"error: port scan failed on adapter #%d\n", dta);
+          iorb_seterr(pIorb, IOERR_CMD_SW_RESOURCE);
+          goto iocm_device_table_done;
+        }
+        ad_info->port_scan_done = 1;
+      }
+
+      /* insert physical (i.e. AHCI) devices into the device table */
+      for (p = 0; p <= ad_info->port_max; p++)
+      {
+        for (d = 0; d <= ad_info->ports[p].dev_max; d++)
+        {
+          if (ad_info->ports[p].devs[d].present)
+          {
+            if (ad_info->ports[p].devs[d].atapi && emulate_scsi[dta][p])
+            {
+              /* report this unit as SCSI unit */
+              scsi_units++;
+              //continue;
+            }
+            if (add_unit_info(pIorb_conf, dta, dta, p, d, 0))
+            {
+              goto iocm_device_table_done;
+            }
+          }
+        }
+      }
+    }
+    else
+    {
+      /* this is the virtual SCSI adapter */
+      if (scsi_units == 0)
+      {
+        /* not a single unit to be emulated via SCSI */
+        pDt->TotalAdapters--;
+        break;
+      }
+
+      /* set adapter name and bus type to mimic a SCSI controller */
+      pPtr->AdapterDevBus = AI_DEVBUS_SCSI_2 | AI_DEVBUS_16BIT;
+      sprintf(pPtr->AdapterName, "AHCI_SCSI_0");
+
+      /* add all ATAPI units to be emulated by this virtual adaper */
+      for (a = 0; a < ad_info_cnt; a++)
+      {
+        AD_INFO *ad_info = ad_infos + a;
+
+        for (p = 0; p <= ad_info->port_max; p++)
+        {
+          for (d = 0; d <= ad_info->ports[p].dev_max; d++)
+          {
+            if (ad_info->ports[p].devs[d].present && ad_info->ports[p].devs[d].atapi && emulate_scsi[a][p])
+            {
+              if (add_unit_info(pIorb_conf, dta, a, p, d, scsi_id++))
+              {
+                goto iocm_device_table_done;
+              }
+            }
+          }
+        }
+      }
+    }
+
+    /* calculate offset for next adapter */
+    pPos = (char *)(pPtr->UnitInfo + pPtr->AdapterUnits);
+  }
+
+iocm_device_table_done:
+  spin_unlock(drv_lock);
+  iorb_done(vIorb, pIorb);
+}
+
+/******************************************************************************
+ * Handle IOCC_GEOMETRY requests.
+ */
+void iocc_geometry(IORBH FAR16DATA *vIorb, IORBH *pIorb)
+{
+  switch (pIorb->CommandModifier)
+  {
+  case IOCM_GET_MEDIA_GEOMETRY:
+  case IOCM_GET_DEVICE_GEOMETRY:
+    add_workspace(pIorb)->idempotent = 1;
+    ahci_get_geometry(vIorb, pIorb);
+    break;
+
+  default:
+    iorb_seterr(pIorb, IOERR_CMD_NOT_SUPPORTED);
+    iorb_done(vIorb, pIorb);
+  }
+}
+
+/******************************************************************************
+ * Handle IOCC_EXECUTE_IO requests.
+ */
+void iocc_execute_io(IORBH FAR16DATA *vIorb, IORBH *pIorb)
+{
+  switch (pIorb->CommandModifier)
+  {
+  case IOCM_READ:
+    add_workspace(pIorb)->idempotent = 1;
+    ahci_read(vIorb, pIorb);
+    break;
+
+  case IOCM_READ_VERIFY:
+    add_workspace(pIorb)->idempotent = 1;
+    ahci_verify(vIorb, pIorb);
+    break;
+
+  case IOCM_WRITE:
+    add_workspace(pIorb)->idempotent = 1;
+    ahci_write(vIorb, pIorb);
+    break;
+
+  case IOCM_WRITE_VERIFY:
+    add_workspace(pIorb)->idempotent = 1;
+    ahci_write(vIorb, pIorb);
+    break;
+
+  default:
+    iorb_seterr(pIorb, IOERR_CMD_NOT_SUPPORTED);
+    iorb_done(vIorb, pIorb);
+  }
+}
+
+/******************************************************************************
+ * Handle IOCC_UNIT_STATUS requests.
+ */
+void iocc_unit_status(IORBH FAR16DATA *vIorb, IORBH *pIorb)
+{
+  switch (pIorb->CommandModifier)
+  {
+  case IOCM_GET_UNIT_STATUS:
+    add_workspace(pIorb)->idempotent = 1;
+    ahci_unit_ready(vIorb, pIorb);
+    break;
+
+  default:
+    iorb_seterr(pIorb, IOERR_CMD_NOT_SUPPORTED);
+    iorb_done(vIorb, pIorb);
+  }
+}
+
+/******************************************************************************
+ * Handle IOCC_ADAPTER_PASSTHROUGH requests.
+ */
+void iocc_adapter_passthru(IORBH FAR16DATA *vIorb, IORBH *pIorb)
+{
+  switch (pIorb->CommandModifier)
+  {
+
+  case IOCM_EXECUTE_CDB:
+    add_workspace(pIorb)->idempotent = 0;
+    ahci_execute_cdb(vIorb, pIorb);
+    break;
+
+  case IOCM_EXECUTE_ATA:
+    add_workspace(pIorb)->idempotent = 0;
+    ahci_execute_ata(vIorb, pIorb);
+    break;
+
+  default:
+    iorb_seterr(pIorb, IOERR_CMD_NOT_SUPPORTED);
+    iorb_done(vIorb, pIorb);
+  }
+}
+
+/******************************************************************************
+ * Add an IORB to the specified queue. This function must be called with the
+ * adapter-level spinlock aquired.
+ */
+void iorb_queue_add(IORB_QUEUE *queue, IORBH FAR16DATA *vIorb, IORBH *pIorb)
+{
+  if (iorb_priority(pIorb)
+  {
+    /* priority IORB; insert at first position */
+    pIorb->pNxtIORB = queue->vRoot;
+    queue->vRoot = vIorb;
+  }
+  else
+  {
+    /* append IORB to end of queue */
+    pIorb->pNxtIORB = NULL;
+
+    if (queue->vRoot == NULL)
+    {
+      queue->vRoot = vIorb;
+    }
+    else
+    {
+      ((IORBH *)Far16ToFlat(queue->vTail))->pNxtIORB = vIorb;
+    }
+    queue->vTail = vIorb;
+  }
+
+  #ifdef DEBUG
+  if (D32g_DbgLevel)
+  {
+    /* determine queue type (local, driver, abort or port) and minimum debug
+     * level; otherwise, queue debug prints can become really confusing.
+     */
+    char *queue_type;
+    int min_debug = 7;
+
+    if ((u32)queue >> 16 == (u32)&queue >> 16) /* DAZ this is bogus */
+    {
+      /* this queue is on the stack */
+      queue_type = "local";
+      min_debug = 8;
+
+    }
+    else if (queue == &driver_queue)
+    {
+      queue_type = "driver";
+
+    }
+    else if (queue == &abort_queue)
+    {
+      queue_type = "abort";
+      min_debug = 8;
+
+    }
+    else
+    {
+      queue_type = "port";
+    }
+
+    DPRINTF(min_debug,"IORB %x queued (cmd=%d/%d queue=%x [%s], timeout=%d)\n",
+           vIorb, pIorb->CommandCode, pIorb->CommandModifier, queue, queue_type,
+           pIorb->Timeout);
+  }
+  #endif
+}
+
+/******************************************************************************
+ * Remove an IORB from the specified queue. This function must be called with
+ * the adapter-level spinlock aquired.
+ */
+int iorb_queue_del(IORB_QUEUE *queue, IORBH FAR16DATA *vIorb)
+{
+  IORBH FAR16DATA *_vIorb;
+  IORBH FAR16DATA *_vPrev = NULL;
+  int found = 0;
+
+  for (_vIorb = queue->vRoot; _vIorb != NULL; )
+  {
+    IORBH *_pIorb = Far16ToFlat(_vIorb);
+    if (_vIorb == vIorb)
+    {
+      /* found the IORB to be removed */
+      if (_vPrev != NULL)
+      {
+        ((IORBH*)Far16ToFlat(_vPrev))->pNxtIORB = _pIorb->pNxtIORB;
+      }
+      else
+      {
+        queue->vRoot = _pIorb->pNxtIORB;
+      }
+      if (_vIorb == queue->vTail)
+      {
+        queue->vTail = _vPrev;
+      }
+      found = 1;
+      break;
+    }
+    _vPrev = _vIorb;
+    _vIorb = _pIorb->pNxtIORB;
+  }
+
+  #ifdef DEBUG
+  if (found)
+  {
+    DPRINTF(8,"IORB %x removed (queue = %x)\n", vIorb, queue);
+  }
+  else
+  {
+    DPRINTF(2,"IORB %x not found in queue %x\n", vIorb, queue);
+  }
+  #endif
+
+  return(!found);
+}
+
+/******************************************************************************
+ * Set the error code in the specified IORB
+ *
+ * NOTE: This function does *not* call iorb_done(). It merely sets the IORB
+ *       status to the specified error code.
+ */
+void iorb_seterr(IORBH *pIorb, USHORT error_code)
+{
+  pIorb->ErrorCode = error_code;
+  pIorb->Status |= IORB_ERROR;
+}
+
+/******************************************************************************
+ * Mark the specified IORB as done and notify the asynchronous post function,
+ * if any. The IORB is also removed from the corresponding IORB queue.
+ *
+ * NOTES: This function does not clear the Status field; it merely adds the
+ *        IORB_DONE flag.
+ *
+ *        This function is expected to be called *without* the corresponding
+ *        driver-level drv_lock aquired. It will aquire the spinlock before
+ *        updating the IORB queue and release it before notifying the upstream
+ *        code in order to prevent deadlocks.
+ *
+ *        Due to this logic, this function is only good for simple task-time
+ *        completions. Functions working on lists of IORBs (such as interrupt
+ *        handlers or context hooks) should call iorb_complete() directly and
+ *        implement their own logic for removing the IORB from the port queue.
+ *        See abort_ctxhook() for an example.
+ */
+void iorb_done(IORBH FAR16DATA *vIorb, IORBH *pIorb)
+{
+  int a = iorb_unit_adapter(pIorb);
+  int p = iorb_unit_port(pIorb);
+
+  /* remove IORB from corresponding queue */
+  spin_lock(drv_lock);
+  if (iorb_driver_level(pIorb))
+  {
+    iorb_queue_del(&driver_queue, vIorb);
+  }
+  else
+  {
+    iorb_queue_del(&ad_infos[a].ports[p].iorb_queue, vIorb);
+  }
+  aws_free(add_workspace(pIorb));
+  spin_unlock(drv_lock);
+
+  iorb_complete(vIorb, pIorb);
+}
+
+/******************************************************************************
+ * Complete an IORB. This should be called without the adapter-level spinlock
+ * to allow the IORB completion routine to perform whatever processing it
+ * requires. This implies that the IORB should no longer be in any global
+ * queue because the IORB completion routine may well reuse the IORB and send
+ * the next request to us before even returning from this function.
+ */
+void iorb_complete(IORBH FAR16DATA *vIorb, IORBH *pIorb)
+{
+  pIorb->Status |= IORB_DONE;
+
+  DPRINTF(7,"IORB %x complete status=0x%04x error=0x%04x\n",
+          vIorb, pIorb->Status, pIorb->ErrorCode);
+
+  if (pIorb->RequestControl & IORB_ASYNC_POST)
+  {
+    Dev32Help_CallFar16((PFNFAR16)pIorb->NotifyAddress, vIorb);
+  }
+}
+
+/******************************************************************************
+ * Requeue the specified IORB such that it will be sent downstream for
+ * processing again. This includes freeing all resources currently allocated
+ * (timer, buffer, ...) and resetting the flags to 0. The driver-level
+ * spinlock must be aquired when calling this function.
+ *
+ * The following flags are preserved:
+ *  - no_ncq
+ */
+void iorb_requeue(IORBH *pIorb)
+{
+  ADD_WORKSPACE *aws = add_workspace(pIorb);
+  u16 no_ncq = aws->no_ncq;
+  u16 unaligned = aws->unaligned;
+  u16 retries = aws->retries;
+
+  aws_free(aws);
+  memset(aws, 0x00, sizeof(*aws));
+
+  aws->no_ncq = no_ncq;
+  aws->unaligned = unaligned;
+  aws->retries = retries;
+}
+
+/******************************************************************************
+ * Free resources in ADD workspace (timer, buffer, ...). This function should
+ * be called with the spinlock held to prevent race conditions.
+ */
+void aws_free(ADD_WORKSPACE *aws)
+{
+  if (aws->timer != 0)
+  {
+    Timer_CancelTimer(aws->timer);
+    aws->timer = 0;
+  }
+
+  if (aws->buf != NULL)
+  {
+    MemFree(aws->buf);
+    aws->buf = NULL;
+  }
+}
+
+/******************************************************************************
+ * Lock the adapter, waiting for availability if necessary. This is expected
+ * to be called at task/request time without the driver-level spinlock
+ * aquired. Don't call at interrupt time.
+ */
+void lock_adapter(AD_INFO *ai)
+{
+  TIMER Timer;
+
+  spin_lock(drv_lock);
+  while (ai->busy)
+  {
+    spin_unlock(drv_lock);
+    TimerInit(&Timer, 250);
+    while (!TimerCheckAndBlock(&Timer));
+    spin_lock(drv_lock);
+  }
+  ai->busy = 1;
+  spin_unlock(drv_lock);
+}
+
+/******************************************************************************
+ * Unlock adapter (i.e. reset busy flag)
+ */
+void unlock_adapter(AD_INFO *ai)
+{
+  ai->busy = 0;
+}
+
+/******************************************************************************
+ * Timeout handler for I/O commands. Since timeout handling can involve
+ * lengthy operations like port resets, the main code is located in a
+ * separate function which is invoked via a context hook.
+ */
+void __syscall timeout_callback(ULONG timer_handle, ULONG p1)
+{
+  IORBH FAR16DATA *vIorb = (IORBH FAR16DATA *)CastULONGToFar16(p1);
+  IORBH *pIorb = Far16ToFlat(vIorb);
+  int a = iorb_unit_adapter(pIorb);
+  int p = iorb_unit_port(pIorb);
+
+  Timer_CancelTimer(timer_handle);
+  dprintf(0,"timeout for IORB %x\n", vIorb);
+
+  /* Move the timed-out IORB to the abort queue. Since it's possible that the
+   * IORB has completed after the timeout has expired but before we got to
+   * this line of code, we'll check the return code of iorb_queue_del(): If it
+   * returns an error, the IORB must have completed a few microseconds ago and
+   * there is no timeout.
+   */
+  spin_lock(drv_lock);
+  if (iorb_queue_del(&ad_infos[a].ports[p].iorb_queue, vIorb) == 0)
+  {
+    iorb_queue_add(&abort_queue, vIorb, pIorb);
+    pIorb->ErrorCode = IOERR_ADAPTER_TIMEOUT;
+  }
+  spin_unlock(drv_lock);
+
+  /* Trigger abort processing function. We don't really care whether this
+   * succeeds because the only reason why it would fail should be multiple
+   * calls to DevHelp_ArmCtxHook() before the context hook had a chance to
+   * start executing, which leaves two scenarios:
+   *
+   *  - We succeded in arming the context hook. Fine.
+   *
+   *  - We armed the context hook a second time before it had a chance to
+   *    start executing. In this case, the already scheduled context hook
+   *    will process our IORB as well.
+   */
+  KernArmHook(reset_ctxhook_h, 0, 0);
+
+  /* Set up a watchdog timer which calls the context hook manually in case
+   * some kernel thread is looping around the IORB_COMPLETE status bit
+   * without yielding the CPU (kernel threads don't preempt). This shouldn't
+   * happen per design because kernel threads are supposed to yield but it
+   * does in the early boot phase.
+   */
+  Timer_StartTimerMS(&th_reset_watchdog, 5000, reset_watchdog, 0);
+}
+
+/******************************************************************************
+ * Reset handler watchdog. If a timeout occurs, a context hook is armed which
+ * will execute as soon as a kernel thread yields the CPU. However, some
+ * kernel components won't yield the CPU during the early boot phase and the
+ * only way to kick some sense into those components is to run the context
+ * hook right inside this timer callback. Not exactly pretty, especially
+ * considering the fact that context hooks were implemented to prevent running
+ * lengthy operations like a port reset at interrupt time, but without this
+ * watchdog mechanism we run the risk of getting completely stalled by device
+ * problems during the early boot phase.
+ */
+void __syscall reset_watchdog(ULONG timer_handle, ULONG p1)
+{
+  /* reset watchdog timer */
+  Timer_CancelTimer(timer_handle);
+  dprintf(0,"reset watchdog invoked\n");
+
+  /* call context hook manually */
+  reset_ctxhook(0);
+}
+
+/******************************************************************************
+ * Add unit info to ADAPTERINFO array (IOCC_GET_DEVICE_TABLE requests). The
+ * adapter info array in the device table, dt->pAdapter[], is expected to be
+ * initialized for the specified index (dt_ai).
+ *
+ * Please note that the device table adapter index, dta, is not always equal
+ * to the physical adapter index, a: if SCSI emulation has been activated, the
+ * last reported adapter is a virtual SCSI adapter and the physical adapter
+ * indexes for those units are, of course, different from the device table
+ * index of the virtual SCSI adapter.
+ */
+static int add_unit_info(IORB_CONFIGURATION *pIorb_conf, int dta,
+                         int a, int p, int d, int scsi_id)
+{
+  DEVICETABLE *pDt = Far16ToFlat(pIorb_conf->pDeviceTable);
+  ADAPTERINFO *pPtr;
+  UNITINFO *pUi;
+  AD_INFO *ai = ad_infos + a;
+
+  pPtr = (ADAPTERINFO *)MakeFlatFromNear16(pIorb_conf->pDeviceTable, pDt->pAdapter[dta]);
+  //DPRINTF(2,"add_unit_info: ptr=%x dta=%x pAdapter[dta]=%x pDeviceTable=%x\n",
+  //    ptr, dta, dt->pAdapter[dta], iorb_conf->pDeviceTable);
+
+  pUi = &pPtr->UnitInfo[pPtr->AdapterUnits];
+
+  if ((u32)(pUi + 1) - (u32)pDt > pIorb_conf->DeviceTableLen)
+  {
+    dprintf(0,"error: device table provided by DASD too small\n");
+    iorb_seterr(&pIorb_conf->iorbh, IOERR_CMD_SW_RESOURCE);
+    return(-1);
+  }
+
+  if (ai->ports[p].devs[d].unit_info == NULL)
+  {
+    /* provide original information about this device (unit) */
+    memset(pUi, 0x00, sizeof(*pUi));
+    pUi->AdapterIndex = dta;                 /* device table adapter index */
+    pUi->UnitHandle   = iorb_unit(a, p, d);  /* physical adapter index */
+    pUi->UnitIndex    = pPtr->AdapterUnits;
+    pUi->UnitType     = ai->ports[p].devs[d].dev_type;
+    pUi->QueuingCount = ai->ports[p].devs[d].ncq_max;
+    if (ai->ports[p].devs[d].removable)
+    {
+      pUi->UnitFlags |= UF_REMOVABLE;
+    }
+    if (scsi_id > 0) {
+      /* set fake SCSI ID for this unit */
+      pUi->UnitSCSITargetID = scsi_id;
+    }
+  }
+  else
+  {
+    /* copy updated device (unit) information (IOCM_CHANGE_UNITINFO) */
+    memcpy(pUi, ai->ports[p].devs[d].unit_info, sizeof(*pUi));
+  }
+
+  pPtr->AdapterUnits++;
+  return(0);
+}
+
Index: /tags/2.02/src/os2ahci/os2ahci.def.template
===================================================================
--- /tags/2.02/src/os2ahci/os2ahci.def.template	(revision 189)
+++ /tags/2.02/src/os2ahci/os2ahci.def.template	(revision 189)
@@ -0,0 +1,16 @@
+library os2ahci
+protmode
+
+segments
+  'DEVHDR'      CLASS 'DATA'
+  '_DATA'       CLASS 'DATA'
+  'LIBDATA'     CLASS 'DATA'
+  'CONST'       CLASS 'CONST'
+  '_BSS'        CLASS 'BSS'
+  'c_common'    CLASS 'BSS'
+  '_z_data'     CLASS 'BSS'
+  '_TEXT'       CLASS 'CODE' IOPL
+  'CODE'        CLASS 'CODE' IOPL
+  'LIBCODE'     CLASS 'CODE' IOPL
+  'RMCode'      CLASS 'CODE' IOPL
+  '_z_text'     CLASS 'CODE' IOPL
Index: /tags/2.02/src/os2ahci/os2ahci.h
===================================================================
--- /tags/2.02/src/os2ahci/os2ahci.h	(revision 189)
+++ /tags/2.02/src/os2ahci/os2ahci.h	(revision 189)
@@ -0,0 +1,519 @@
+/******************************************************************************
+ * os2ahci.h - main header file for os2ahci driver
+ *
+ * Copyright (c) 2011 thi.guten Software Development
+ * Copyright (c) 2011 Mensys B.V.
+ * Copyright (c) 2013-2016 David Azarewicz
+ *
+ * Authors: Christian Mueller, Markus Thielen
+ *
+ * Parts copied from/inspired by the Linux AHCI driver;
+ * those parts are (c) Linux AHCI/ATA maintainers
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+/* ----------------------------- include files ----------------------------- */
+
+/* IMPORTANT NOTE: The DDK headers require tight structure packing and this
+ * is controlled via compiler parameters. Thus, all stuctures in os2ahci.add
+ * are expected to be byte-aligned without the need of explicit pragma pack()
+ * directives. Where possible, the structures are laid out such that words
+ * and dwords are aligned at least on 2-byte boundaries.
+ */
+
+/* Global feature defines
+ * DEBUG = enable debug logging routines to be compled in.
+ * LEGACY_APM = enable the legacy APM interface to be compiled in.
+ *   Legacy APM support is not needed on eCS systems with ACPI and is more reliable without it enabled.
+ */
+//#define LEGACY_APM
+//#define DAZ_NEW_CODE
+
+#include "Dev32lib.h"
+#include "Dev32rmcalls.h"
+#include <Dev32iorb.h>
+#include "ahci.h"
+#include "ahci-idc.h"
+
+/* -------------------------- macros and constants ------------------------- */
+
+#define MAX_AD 8 /* maximum number of adapters */
+
+#define TIMER_COUNT 128
+
+/* default command timeout (can be overwritten in the IORB) */
+#define DEFAULT_TIMEOUT 30000
+
+/* Maximum number of retries for commands in the restart/reset context hooks.
+ *
+ * Please note that the corresponding variable in the ADD workspace is a bit
+ * field, thus increasing this value means increasing the size of the bit
+ * field. At the time of writing this comment the 'retries' variable was 2
+ * bits wide (i.e. a maximum number of 3 retries) and there was exactly one
+ * bit left before the ADD workspace structure would become too large...
+ */
+#define MAX_RETRIES 3
+
+/* debug output macros */
+#ifdef DEBUG
+#define DPRINTF(a,b,...) dprintf(a, b, ##__VA_ARGS__)
+#define DHEXDUMP(a,b,c,d,...) dHexDump(a, b, c, d, ##__VA_ARGS__)
+#define DUMP_HOST_REGS(l,a,b) {if (D32g_DbgLevel>=l) ahci_dump_host_regs(a,b);}
+#define DUMP_PORT_REGS(l,a,b) {if (D32g_DbgLevel>=l) ahci_dump_port_regs(a,b);}
+#else
+#define DPRINTF(a,b,...)
+#define DHEXDUMP(a,b,c,d,...)
+#define DUMP_HOST_REGS(l,a,b)
+#define DUMP_PORT_REGS(l,a,b)
+#endif
+
+/* verbosity console print macros
+ * (we use 'i' in ciprintf here to avoid name clash
+ * with vprintf-like funcs)
+ */
+#define ciprintf(a,...)  {if (verbosity > 0) iprintf(a, ##__VA_ARGS__);}
+#define ciiprintf(a,...) {if (verbosity > 1) iprintf(a, ##__VA_ARGS__);}
+
+/* adapter number from AD_INFO pointer; mainly for dprintf() purposes */
+#define ad_no(ai) ( ( (u32)ai - (u32)ad_infos ) / sizeof(*ai))
+
+#define MakeNear16PtrFromDiff(Base16, Base32, New32) \
+  ( ( CastFar16ToULONG(Base16) + ( (ULONG)(New32) - (ULONG)(Base32) ) ) & 0xffff)
+
+#define MakeFar16PtrFromDiff(Base16, Base32, New32) \
+  CastULONGToFar16(CastFar16ToULONG(Base16) + ((ULONG)(New32) - (ULONG)(Base32))))
+
+/* Takes the selector from the first parameter, and the offset specified
+ * in the second parameter, and returns a flat pointer
+ */
+extern void *MakeFlatFromNear16(void __far16 *, USHORT);
+#pragma aux MakeFlatFromNear16 = \
+  "mov ax, bx" \
+  "call Far16ToFlat" \
+  parm nomemory [eax] [bx] value [eax] modify nomemory exact [eax];
+
+/* stdarg.h macros with explicit far pointers */
+typedef char *va_list;
+#define va_start(va, last)    va = (va_list) (&last + 1)
+#define va_arg(va, type)      ((type *) (va += sizeof(type)))[-1]
+#define va_end(va)            va = 0
+
+/* stddef macros */
+#define offsetof(s, e) ((u32)&((s *)0)->e)
+
+/* shortcut macros */
+#define spin_lock(sl)     KernAcquireSpinLock(&sl)
+#define spin_unlock(sl)   KernReleaseSpinLock(&sl)
+
+/* Get AHCI port MMIO base from AD_INFO and port number. */
+#define port_base(ai, p) ((u8 *) (ai)->mmio + 0x100 + (p) * 0x80)
+#define port_dma_base(ai, p) ((AHCI_PORT_DMA *) ((ai)->ports[(p)].dma_buf))
+#define port_dma_base_phys(ai, p) ((ai)->ports[p].dma_buf_phys)
+
+/* Convert an SATA adapter/port/device address into a 16-bit IORB unit handle
+ * (and the other way round). The mapping looks like this:
+ *
+ *  mapping                   comment
+ *  -----------------------------------------------------------------------
+ *  4 bits for the adapter    current max is 8 adapters
+ *  4 bits for the port       AHCI spec defines up to 32 ports
+ *  4 bits for the device     SATA spec defines up to 15 devices behind PMP
+ */
+#define iorb_unit(a, p, d)       ((((u16) (a) & 0x0fU) << 8) | \
+                                  (((u16) (p) & 0x0fU) << 4) | \
+                                  (((u16) (d) & 0x0fU)))
+#define iorb_unit_adapter(iorb)  (((iorb)->UnitHandle >> 8) & 0x07)
+#define iorb_unit_port(iorb)     (((iorb)->UnitHandle >> 4) & 0x0f)
+#define iorb_unit_device(iorb)   ((iorb)->UnitHandle & 0x0f)
+
+/*******************************************************************************
+ * Convenience macros for IORB processing functions
+ */
+/* is this IORB on driver or port level? */
+#define iorb_driver_level(iorb) ((iorb)->CommandCode == IOCC_CONFIGURATION)
+
+/* is this IORB to be inserted at the beginnig of the IORB queue? */
+#define iorb_priority(iorb)     ((iorb)->CommandCode == IOCC_DEVICE_CONTROL && \
+                                 (iorb)->CommandModifier == IOCM_ABORT))
+
+/* access IORB ADD workspace */
+#define add_workspace(iorb)     ((ADD_WORKSPACE *) &(iorb)->ADDWorkSpace)
+
+
+
+/******************************************************************************
+ * PCI generic IDs and macros
+ */
+#define PCI_ANY_ID                     0xffffU
+#define PCI_VDEVICE(vendor, device)    PCI_VENDOR_ID_##vendor, (device), \
+                                       PCI_ANY_ID, PCI_ANY_ID, 0, 0
+
+/******************************************************************************
+ * PCI vendor IDs for AHCI adapters known to this driver (copied from Linux
+ * pci_ids.h)
+ */
+#define PCI_VENDOR_ID_AL       0x10b9
+#define PCI_VENDOR_ID_AMD      0x1022
+#define PCI_VENDOR_ID_AT       0x1259
+#define PCI_VENDOR_ID_ATI      0x1002
+#define PCI_VENDOR_ID_ATT      0x11c1
+#define PCI_VENDOR_ID_CMD      0x1095
+#define PCI_VENDOR_ID_CT       0x102c
+#define PCI_VENDOR_ID_INTEL    0x8086
+#define PCI_VENDOR_ID_INITIO   0x1101
+#define PCI_VENDOR_ID_JMICRON  0x197B
+#define PCI_VENDOR_ID_MARVELL  0x11ab
+#define PCI_VENDOR_ID_NVIDIA   0x10de
+#define PCI_VENDOR_ID_PROMISE  0x105a
+#define PCI_VENDOR_ID_SI       0x1039
+#define PCI_VENDOR_ID_VIA      0x1106
+
+/******************************************************************************
+ * PCI class IDs we're interested in (copied from Linux pci_ids.h)
+ */
+#define PCI_BASE_CLASS_STORAGE          0x01
+#define PCI_CLASS_STORAGE_SCSI          0x0100
+#define PCI_CLASS_STORAGE_IDE           0x0101
+#define PCI_CLASS_STORAGE_FLOPPY        0x0102
+#define PCI_CLASS_STORAGE_IPI           0x0103
+#define PCI_CLASS_STORAGE_RAID          0x0104
+#define PCI_CLASS_STORAGE_SATA          0x0106
+#define PCI_CLASS_STORAGE_SATA_AHCI     0x010601
+#define PCI_CLASS_STORAGE_SAS           0x0107
+#define PCI_CLASS_STORAGE_OTHER         0x0180
+
+/******************************************************************************
+ * ANSI color code constants
+ */
+#define ANSI_CLR_BRIGHT "\x1b[1m"
+#define ANSI_CLR_RED    "\x1b[31m"
+#define ANSI_CLR_GREEN  "\x1b[32m"
+#define ANSI_CLR_BLUE   "\x1b[34m"
+#define ANSI_CLR_CYAN   "\x1b[36m"
+#define ANSI_CLR_WHITE  "\x1b[37m"
+#define ANSI_RESET      "\x1b[0m"
+
+
+/* ------------------------ typedefs and structures ------------------------ */
+
+/* PCI device information structure; this is used both for scanning and for
+ * identification purposes in 'AD_INFO'; based on the Linux pci_device_id
+ * structure but hard-wired to use board_* constants for 'driver_data'
+ */
+typedef struct {
+  u16   vendor;          /* PCI device vendor/manufacturer */
+  u16   device;          /* PCI device ID inside vendor scope */
+  u16   subvendor;       /* subsystem vendor (unused so far) */
+  u16   subdevice;       /* subsystem device (unused so far) */
+  u32   class;           /* PCI device class */
+  u32   class_mask;      /* bits to match when scanning for 'class' */
+  u32   board;           /* AHCI controller board type (board_* constants) */
+  char  *chipname;       /* human readable chip ID string */
+} PCI_ID;
+
+/* IORB queue; since IORB queues are updated at interrupt time, the
+ * corresponding pointers (not the data they point to) need to be volatile.
+ */
+typedef struct {
+  IORBH FAR16DATA *volatile vRoot;           /* root of request list */
+  IORBH FAR16DATA *volatile vTail;           /* tail of request list */
+} IORB_QUEUE;
+
+typedef struct {
+    USHORT Cylinders;
+    USHORT HeadsPerCylinder;
+    USHORT SectorsPerTrack;
+    ULONG  TotalSectors;
+    char *Method;
+} DEV_INFO;
+
+/* port information structure */
+typedef struct {
+  IORB_QUEUE    iorb_queue;            /* 00 IORB queue for this port */
+  unsigned      dev_max     : 4;       /* 08 maximum device number on this port (0..AHCI_MAX_DEVS-1) */
+  unsigned      cmd_slot    : 5;       /*    current command slot index (using round-
+                                        *    robin indexes to prevent starvation) */
+
+  volatile u32  ncq_cmds;              /* 0c bitmap for NCQ commands issued */
+  volatile u32  reg_cmds;              /* 10 bitmap for regular commands issued */
+  u32           dma_buf_phys;          /* 14 physical address of DMA scratch buffer */
+  u8           *dma_buf;               /* 18 DMA scatch buffers */
+
+  struct {                             /* 1c */
+    unsigned allocated :1;        /* if != 0, device is allocated */
+    unsigned present   :1;        /* if != 0, device is present */
+    unsigned lba48     :1;        /* if != 0, device supports 48-bit LBA */
+    unsigned atapi     :1;        /* if != 0, this is an ATAPI device */
+    unsigned atapi_16  :1;        /* if != 0, device suports 16-byte cmds */
+    unsigned removable :1;        /* if != 0, device has removable media */
+    unsigned dev_type  :5;        /* device type (UIB_TYPE_* in iorb.h) */
+    unsigned ncq_max   :5;        /* maximum tag number for queued commands */
+    UNITINFO *unit_info;          /* pointer to modified unit info */
+    DEV_INFO dev_info;
+    char dev_name[AHCI_DEV_NAME_LEN];
+  } devs[AHCI_MAX_DEVS];
+
+  u32 unaligned_read_count;
+  u32 error_count;
+} P_INFO;
+
+/* adapter information structure */
+typedef struct {
+  PCI_ID       *pci;                   /* 00 pointer to corresponding PCI ID */
+
+  unsigned      port_max : 5;          /* 04 maximum port number (0..AHCI_MAX_PORTS-1) */
+  unsigned      cmd_max : 5;           /*    maximum cmd slot number (0-31) */
+  unsigned      port_scan_done : 1;    /*    if != 0, port scan already done */
+  unsigned      busy : 1;              /*    if != 0, adapter is busy */
+  unsigned      hw_ports : 6;          /*    number of ports as reported by the hardware */
+
+  u32           port_map;              /* 08   bitmap of active ports */
+  u16           pci_vendor;            /* 0c */
+  u16           pci_device;            /* 0e */
+
+  /* initial adapter configuration from BIOS */
+  u32           bios_config[HOST_CAP2 / sizeof(u32) + 1];  /* 10  0x24 / 4 + 1 = 0x0a dwords = 0x28 bytes*/
+
+  u32           cap;                   /* 38 working copy of CAP register */
+  u32           cap2;                  /* 3c working copy of CAP2 register */
+  u32           flags;                 /* 40 adapter flags */
+
+  HRESOURCE     rm_adh;                /* 44 resource handle for adapter */
+  HRESOURCE     rm_bars[6];            /* 48 resource handle for MMIO and I/O BARs */
+  HRESOURCE     rm_irq;                /* 60 resource handle for IRQ */
+
+  u16           bus_dev_func;          /* 64 PCI bus number PCI device and function number */
+  u16           irq;                   /* 66 interrupt number */
+
+  u32           mmio_phys;             /* 68 physical address of MMIO region */
+  u32           mmio_size;             /* 6c size of MMIO region */
+  u8           *mmio;                  /* 70 pointer to this adapter's MMIO region */
+
+  P_INFO        ports[AHCI_MAX_PORTS]; /* 74 SATA ports on this adapter */
+} AD_INFO;
+
+/* ADD workspace in IORB (must not exceed 16 bytes) */
+typedef struct {
+  void (*ppfunc)(IORBH FAR16DATA *vIorb, IORBH *pIorb);         /* 00 post-processing function */
+  void         *buf;                   /* 04 response buffer (e.g. for identify cmds) */
+  ULONG         timer;                 /* 08 timer for timeout procesing */
+  USHORT        blocks;                /* 0c number of blocks to be transferred */
+  unsigned short processing    :1;     /* 0e IORB is being processd */
+  unsigned short idempotent    :1;     /*    IORB is idempotent (can be retried) */
+  unsigned short queued_hw     :1;     /*    IORB has been queued to hardware */
+  unsigned short no_ncq        :1;     /*    must not use native command queuing */
+  unsigned short is_ncq        :1;     /*    should use native command queueing */
+  unsigned short complete      :1;     /*    IORB has completed processing */
+  unsigned short unaligned     :1;     /*    unaligned S/G; need to use transfer buffer */
+  unsigned short retries       :2;     /*    number of retries for this command */
+  unsigned short cmd_slot      :5;     /*    AHCI command slot for this IORB */
+} ADD_WORKSPACE;                       /* 10 */
+
+/* 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;
+
+/* Define the size of a disk name.  Disk Names are user defined names given to physical disk drives in the system. */
+#define DLA_TABLE_SIGNATURE1  0x424D5202L
+#define DLA_TABLE_SIGNATURE2  0x44464D50L
+#define DISK_NAME_SIZE	  20
+
+typedef struct _DLA_Table_Sector { /* DTS */
+     ULONG DLA_Signature1;         /* The magic signature (part 1) of a Drive Letter Assignment Table. */
+     ULONG DLA_Signature2;         /* The magic signature (part 2) of a Drive Letter Assignment Table. */
+     ULONG DLA_CRC;                /* The 32 bit CRC for this sector.  Calculated assuming that this field and all unused space in the sector is 0. */
+     ULONG Disk_Serial_Number;     /* The serial number assigned to this disk. */
+     ULONG Boot_Disk_Serial_Number;/* The serial number of the disk used to boot the system.  This is for conflict resolution when multiple volumes
+                                      want the same drive letter.  Since LVM.EXE will not let this situation happen, the only way to get this situation
+                                      is for the disk to have been altered by something other than LVM.EXE, or if a disk drive has been moved from one
+                                      machine to another.  If the drive has been moved, then it should have a different Boot_Disk_Serial_Number.  Thus,
+                                      we can tell which disk drive is the "foreign" drive and therefore reject its claim for the drive letter in question.
+                                      If we find that all of the claimaints have the same Boot_Disk_Serial_Number, then we must assign drive letters on
+                                      a first come, first serve basis.                                          */
+     ULONG Install_Flags;          /* Used by the Install program. */
+     ULONG Cylinders;
+     ULONG Heads_Per_Cylinder;
+     ULONG Sectors_Per_Track;
+     char  Disk_Name[DISK_NAME_SIZE];  /* The name assigned to the disk containing this sector. */
+     UCHAR Reboot;                 /* For use by Install.  Used to keep track of reboots initiated by install. */
+     BYTE  Reserved[3];            /* Alignment. */
+                                   /* These are the four entries which correspond to the entries in the partition table. */
+} DLA_Table_Sector, *PDLA_Table_Sector;
+
+/* -------------------------- function prototypes -------------------------- */
+
+static inline unsigned long readl(void *a)
+{
+  return *(volatile unsigned long*)a;
+}
+
+static inline void writel(void *a, unsigned long v)
+{
+  *(volatile unsigned long*)a = v;
+}
+
+extern void shutdown_driver(void);
+
+/* os2ahci.c */
+extern USHORT init_drv(REQPACKET *req);
+extern USHORT gen_ioctl(REQPACKET *ioctl);
+extern USHORT char_dev_input(REQPACKET *rwrb);
+extern USHORT exit_drv(int func);
+extern USHORT sr_drv(int func);
+extern void add_entry(IORBH FAR16DATA *vIorb);
+extern void trigger_engine(void);
+extern int trigger_engine_1(void);
+extern void send_iorb(IORBH FAR16DATA *vIorb, IORBH *pIorb);
+extern void iocc_configuration (IORBH FAR16DATA *vIorb, IORBH *pIorb);
+extern void iocc_device_control(IORBH FAR16DATA *vIorb, IORBH *pIorb);
+extern void iocc_unit_control(IORBH FAR16DATA *vIorb, IORBH *pIorb);
+extern void iocm_device_table(IORBH FAR16DATA *vIorb, IORBH *pIorb);
+extern void iocc_geometry(IORBH FAR16DATA *vIorb, IORBH *pIorb);
+extern void iocc_execute_io(IORBH FAR16DATA *vIorb, IORBH *pIorb);
+extern void iocc_unit_status(IORBH FAR16DATA *vIorb, IORBH *pIorb);
+extern void iocc_adapter_passthru(IORBH FAR16DATA *vIorb, IORBH *pIorb);
+extern void iorb_queue_add(IORB_QUEUE *queue, IORBH FAR16DATA *vIorb, IORBH *pIorb);
+extern int iorb_queue_del(IORB_QUEUE *queue, IORBH FAR16DATA *vIorb);
+extern void iorb_seterr(IORBH *pIorb, USHORT error_code);
+extern void iorb_done(IORBH FAR16DATA *vIorb, IORBH *pIorb);
+extern void iorb_complete(IORBH FAR16DATA *vIorb, IORBH *pIorb);
+extern void iorb_requeue(IORBH *pIorb);
+extern void aws_free(ADD_WORKSPACE *aws);
+extern void lock_adapter(AD_INFO *ai);
+extern void unlock_adapter(AD_INFO *ai);
+extern void __syscall timeout_callback(ULONG timer_handle, ULONG p1);
+extern void __syscall reset_watchdog(ULONG timer_handle, ULONG p1);
+
+/* ahci.c */
+extern int ahci_config_caps(AD_INFO *ai);
+extern int ahci_save_bios_config(AD_INFO *ai);
+extern int ahci_restore_bios_config(AD_INFO *ai);
+extern int ahci_restore_initial_config(AD_INFO *ai);
+extern AHCI_PORT_CFG *ahci_save_port_config(AD_INFO *ai, int p);
+extern void ahci_restore_port_config(AD_INFO *ai, int p, AHCI_PORT_CFG *pc);
+extern int ahci_enable_ahci(AD_INFO *ai);
+extern int ahci_scan_ports(AD_INFO *ai);
+extern int ahci_complete_init(AD_INFO *ai);
+extern int ahci_reset_port(AD_INFO *ai, int p, int ei);
+extern int ahci_start_port(AD_INFO *ai, int p, int ei);
+extern void ahci_start_fis_rx(AD_INFO *ai, int p);
+extern void ahci_start_engine(AD_INFO *ai, int p);
+extern int ahci_stop_port(AD_INFO *ai, int p);
+extern int ahci_stop_fis_rx(AD_INFO *ai, int p);
+extern int ahci_stop_engine(AD_INFO *ai, int p);
+extern int ahci_port_busy(AD_INFO *ai, int p);
+extern void ahci_exec_iorb(IORBH FAR16DATA *vIorb, IORBH *pIorb, int ncq_capable, int (*func)(IORBH FAR16DATA *, IORBH *pIorb, int));
+extern void ahci_exec_polled_iorb(IORBH FAR16DATA *vIorb, IORBH *pIorb, int (*func)(IORBH FAR16DATA *, IORBH *pIorb, int), ULONG timeout);
+extern int ahci_exec_polled_cmd(AD_INFO *ai, int p, int d, int timeout, int cmd, ...);
+extern int ahci_set_dev_idle(AD_INFO *ai, int p, int d, int idle);
+extern int ahci_flush_cache(AD_INFO *ai, int p, int d);
+
+extern int ahci_intr(u16 irq);
+extern void ahci_port_intr(AD_INFO *ai, int p);
+extern void ahci_error_intr(AD_INFO *ai, int p, u32 irq_stat);
+
+extern void ahci_get_geometry(IORBH FAR16DATA *vIorb, IORBH *pIorb);
+extern void ahci_unit_ready(IORBH FAR16DATA *vIorb, IORBH *pIorb);
+extern void ahci_read(IORBH FAR16DATA *vIorb, IORBH *pIorb);
+extern void ahci_verify(IORBH FAR16DATA *vIorb, IORBH *pIorb);
+extern void ahci_write(IORBH FAR16DATA *vIorb, IORBH *pIorb);
+extern void ahci_execute_cdb(IORBH FAR16DATA *vIorb, IORBH *pIorb);
+extern void ahci_execute_ata(IORBH FAR16DATA *vIorb, IORBH *pIorb);
+extern void ahci_dump_host_regs(AD_INFO *ai, int bios_regs);
+extern void ahci_dump_port_regs(AD_INFO *ai, int p);
+extern int ahci_reset_controller(AD_INFO *ai);
+
+extern void sg_memcpy(SCATGATENTRY *sg_list, USHORT sg_cnt, ULONG sg_off, void *buf, USHORT len, SG_MEMCPY_DIRECTION dir);
+extern void panic(char *msg);
+
+/* trace.c */
+extern void build_user_info(void);
+
+/* pci.c */
+extern int add_pci_id(u16 vendor, u16 device);
+extern void scan_pci_bus(void);
+extern int pci_enable_int(USHORT BusDevFunc);
+extern void pci_hack_virtualbox(void);
+extern char *vendor_from_id(u16 vendor);
+extern char *device_from_id(u16 device);
+
+/* ctxhook.c */
+extern void _Syscall restart_ctxhook(ULONG parm);
+extern void _Syscall reset_ctxhook(ULONG parm);
+extern void _Syscall engine_ctxhook(ULONG parm);
+
+/* apm.c */
+extern void apm_init(void);
+extern void suspend(void);
+extern void resume(void);
+
+/* ioctl.c */
+extern USHORT ioctl_get_devlist(REQPACKET *ioctl);
+extern USHORT ioctl_passthrough(REQPACKET *ioctl);
+extern USHORT ioctl_gen_dsk(REQPACKET *ioctl);
+extern USHORT ioctl_smart(REQPACKET *ioctl);
+
+
+/* ---------------------------- global variables --------------------------- */
+
+extern int thorough_scan; /* if != 0, perform thorough PCI scan */
+extern int init_reset; /* if != 0, reset ports during init */
+extern int force_write_cache; /* if != 0, force write cache */
+extern int verbosity; /* if != 0, show some info during boot */
+extern int use_lvm_info;
+
+extern HDRIVER rm_drvh; /* resource manager driver handle */
+extern USHORT add_handle; /* adapter device driver handle */
+extern char drv_name[]; /* driver name as string ("OS2AHCI") */
+
+extern PCI_ID pci_ids[]; /* SATA adapter PCI IDs */
+extern SpinLock_t drv_lock; /* driver-level spinlock */
+extern ULONG com_lock; /* debug log spinlock */
+extern IORB_QUEUE driver_queue; /* driver-level IORB queue */
+extern AD_INFO ad_infos[]; /* adapter information list */
+extern int ad_info_cnt; /* number of entries in ad_infos[] */
+extern u16 ad_ignore; /* bitmap with adapters to be ignored */
+extern int init_complete; /* if != 0, initialization has completed */
+extern int suspended; /* indicates if the driver is suspended */
+extern int resume_sleep_flag;
+
+/* port restart context hook and input data */
+extern ULONG restart_ctxhook_h;
+extern volatile u32 ports_to_restart[MAX_AD];
+
+/* port reset context hook and input data */
+extern ULONG reset_ctxhook_h;
+extern ULONG th_reset_watchdog;
+extern volatile u32 ports_to_reset[MAX_AD];
+extern IORB_QUEUE abort_queue;
+
+/* trigger engine context hook and input data */
+extern ULONG engine_ctxhook_h;
+
+/* apapter/port-specific options saved when parsing the command line */
+extern u8 emulate_scsi[MAX_AD][AHCI_MAX_PORTS];
+extern u8 enable_ncq[MAX_AD][AHCI_MAX_PORTS];
+extern u8 link_speed[MAX_AD][AHCI_MAX_PORTS];
+extern u8 link_power[MAX_AD][AHCI_MAX_PORTS];
+extern u8 track_size[MAX_AD][AHCI_MAX_PORTS];
+extern u8 port_ignore[MAX_AD][AHCI_MAX_PORTS];
+
+#ifdef DEBUG
+extern void DumpIorb(IORBH *pIorb);
+#endif
+
Index: /tags/2.02/src/os2ahci/pci.c
===================================================================
--- /tags/2.02/src/os2ahci/pci.c	(revision 189)
+++ /tags/2.02/src/os2ahci/pci.c	(revision 189)
@@ -0,0 +1,861 @@
+/******************************************************************************
+ * PCI.c - PCI constants and detection code for os2ahci driver
+ *
+ * Copyright (c) 2011 thi.guten Software Development
+ * Copyright (c) 2011 Mensys B.V.
+ * Copyright (c) 2013-2016 David Azarewicz
+ *
+ * Authors: Christian Mueller, Markus Thielen
+ *
+ * Parts copied from/inspired by the Linux AHCI driver;
+ * those parts are (c) Linux AHCI/ATA maintainers
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+#include "os2ahci.h"
+
+/* -------------------------- macros and constants ------------------------- */
+
+/* offset of PCI base address register (BAR) in the PCI config space */
+#define PCI_BAR(reg)   (UCHAR) (0x10 + (reg) * sizeof(u32))
+
+/* ------------------------ typedefs and structures ------------------------ */
+
+/* -------------------------- function prototypes -------------------------- */
+
+static void  add_pci_device(PCI_ID *pci_id, USHORT BusDevFunc);
+static long  bar_resource(USHORT BusDevFunc, RESOURCESTRUCT *resource, int i);
+static char *rmerr(APIRET ret);
+
+/* ------------------------ global/static variables ------------------------ */
+
+/******************************************************************************
+ * chipset/controller name strings
+ */
+static char chip_esb2[]    = "ESB2";
+static char chip_ich8[]    = "ICH8";
+static char chip_ich8m[]   = "ICH8M";
+static char chip_ich9[]    = "ICH9";
+static char chip_ich9m[]   = "ICH9M";
+static char chip_ich10[]   = "ICH10";
+static char chip_pchahci[] = "PCH AHCI";
+static char chip_pchraid[] = "PCH RAID";
+static char chip_tolapai[] = "Tolapai";
+static char chip_sb600[]   = "SB600";
+static char chip_sb700[]   = "SB700/800";
+static char chip_vt8251[]  = "VT8251";
+static char chip_mcp65[]   = "MCP65";
+static char chip_mcp67[]   = "MCP67";
+static char chip_mcp73[]   = "MCP73";
+static char chip_mcp77[]   = "MCP77";
+static char chip_mcp79[]   = "MCP79";
+static char chip_mcp89[]   = "MCP689";
+static char chip_sis968[]  = "968";
+
+static char s_generic[]    = "Generic";
+
+
+/******************************************************************************
+ * PCI vendor and device IDs for known AHCI adapters. Copied from the Linux
+ * AHCI driver.
+ */
+
+PCI_ID pci_ids[] =
+{
+  /* Intel
+   * NOTE: ICH5 controller does NOT support AHCI, so we do
+   *       not add it here! */
+  { PCI_VDEVICE(INTEL, 0x2652), board_ahci,            "ICH6"       }, /* ICH6 */
+  { PCI_VDEVICE(INTEL, 0x2653), board_ahci,            "ICH6M"      }, /* ICH6M */
+  { PCI_VDEVICE(INTEL, 0x27c1), board_ahci,            "ICH7"       }, /* ICH7 */
+  { PCI_VDEVICE(INTEL, 0x27c5), board_ahci,            "ICH7M"      }, /* ICH7M */
+  { PCI_VDEVICE(INTEL, 0x27c3), board_ahci,            "ICH7R"      }, /* ICH7R */
+  { PCI_VDEVICE(AL,    0x5288), board_ahci_ign_iferr,  "ULiM5288"   }, /* ULi M5288 */
+  { PCI_VDEVICE(INTEL, 0x2681), board_ahci,            chip_esb2    }, /* ESB2 */
+  { PCI_VDEVICE(INTEL, 0x2682), board_ahci,            chip_esb2    }, /* ESB2 */
+  { PCI_VDEVICE(INTEL, 0x2683), board_ahci,            chip_esb2    }, /* ESB2 */
+  { PCI_VDEVICE(INTEL, 0x27c6), board_ahci,            "ICH7MDH"    }, /* ICH7-M DH */
+  { PCI_VDEVICE(INTEL, 0x2821), board_ahci,            chip_ich8    }, /* ICH8 */
+  { PCI_VDEVICE(INTEL, 0x2822), board_ahci_nosntf,     chip_ich8    }, /* ICH8 */
+  { PCI_VDEVICE(INTEL, 0x2824), board_ahci,            chip_ich8    }, /* ICH8 */
+  { PCI_VDEVICE(INTEL, 0x2829), board_ahci,            chip_ich8m   }, /* ICH8M */
+  { PCI_VDEVICE(INTEL, 0x282a), board_ahci,            chip_ich8m   }, /* ICH8M */
+  { PCI_VDEVICE(INTEL, 0x2922), board_ahci,            chip_ich9    }, /* ICH9 */
+  { PCI_VDEVICE(INTEL, 0x2923), board_ahci,            chip_ich9    }, /* ICH9 */
+  { PCI_VDEVICE(INTEL, 0x2924), board_ahci,            chip_ich9    }, /* ICH9 */
+  { PCI_VDEVICE(INTEL, 0x2925), board_ahci,            chip_ich9    }, /* ICH9 */
+  { PCI_VDEVICE(INTEL, 0x2927), board_ahci,            chip_ich9    }, /* ICH9 */
+  { PCI_VDEVICE(INTEL, 0x2929), board_ahci,            chip_ich9m   }, /* ICH9M */
+  { PCI_VDEVICE(INTEL, 0x292a), board_ahci,            chip_ich9m   }, /* ICH9M */
+  { PCI_VDEVICE(INTEL, 0x292b), board_ahci,            chip_ich9m   }, /* ICH9M */
+  { PCI_VDEVICE(INTEL, 0x292c), board_ahci,            chip_ich9m   }, /* ICH9M */
+  { PCI_VDEVICE(INTEL, 0x292f), board_ahci,            chip_ich9m   }, /* ICH9M */
+  { PCI_VDEVICE(INTEL, 0x294d), board_ahci,            chip_ich9    }, /* ICH9 */
+  { PCI_VDEVICE(INTEL, 0x294e), board_ahci,            chip_ich9m   }, /* ICH9M */
+  { PCI_VDEVICE(INTEL, 0x502a), board_ahci,            chip_tolapai }, /* Tolapai */
+  { PCI_VDEVICE(INTEL, 0x502b), board_ahci,            chip_tolapai }, /* Tolapai */
+  { PCI_VDEVICE(INTEL, 0x3a05), board_ahci,            chip_ich10   }, /* ICH10 */
+  { PCI_VDEVICE(INTEL, 0x3a22), board_ahci,            chip_ich10   }, /* ICH10 */
+  { PCI_VDEVICE(INTEL, 0x3a25), board_ahci,            chip_ich10   }, /* ICH10 */
+  { PCI_VDEVICE(INTEL, 0x3b22), board_ahci,            chip_pchahci }, /* PCH AHCI */
+  { PCI_VDEVICE(INTEL, 0x3b23), board_ahci,            chip_pchahci }, /* PCH AHCI */
+  { PCI_VDEVICE(INTEL, 0x3b24), board_ahci,            chip_pchraid }, /* PCH RAID */
+  { PCI_VDEVICE(INTEL, 0x3b25), board_ahci,            chip_pchraid }, /* PCH RAID */
+  { PCI_VDEVICE(INTEL, 0x3b29), board_ahci,            chip_pchahci }, /* PCH AHCI */
+  { PCI_VDEVICE(INTEL, 0x3b2b), board_ahci,            chip_pchraid }, /* PCH RAID */
+  { PCI_VDEVICE(INTEL, 0x3b2c), board_ahci,            chip_pchraid }, /* PCH RAID */
+  { PCI_VDEVICE(INTEL, 0x3b2f), board_ahci,            chip_pchahci }, /* PCH AHCI */
+
+  /* JMicron 360/1/3/5/6, match class to avoid IDE function */
+  { PCI_VENDOR_ID_JMICRON, PCI_ANY_ID, PCI_ANY_ID, PCI_ANY_ID,
+    PCI_CLASS_STORAGE_SATA_AHCI, 0xffffffL, board_ahci_ign_iferr, "360" },
+
+  /* ATI */
+  { PCI_VDEVICE(ATI, 0x4380), board_ahci_sb600,        chip_sb600   }, /* ATI SB600 */
+  { PCI_VDEVICE(ATI, 0x4390), board_ahci_sb700,        chip_sb700   }, /* ATI SB700/800 */
+  { PCI_VDEVICE(ATI, 0x4391), board_ahci_sb700,        chip_sb700   }, /* ATI SB700/800 */
+  { PCI_VDEVICE(ATI, 0x4392), board_ahci_sb700,        chip_sb700   }, /* ATI SB700/800 */
+  { PCI_VDEVICE(ATI, 0x4393), board_ahci_sb700,        chip_sb700   }, /* ATI SB700/800 */
+  { PCI_VDEVICE(ATI, 0x4394), board_ahci_sb700,        chip_sb700   }, /* ATI SB700/800 */
+  { PCI_VDEVICE(ATI, 0x4395), board_ahci_sb700,        chip_sb700   }, /* ATI SB700/800 */
+
+  /* AMD */
+  { PCI_VDEVICE(AMD, 0x7800), board_ahci }, /* AMD Hudson-2 */
+  /* AMD is using RAID class only for ahci controllers */
+  { PCI_VENDOR_ID_AMD, PCI_ANY_ID, PCI_ANY_ID, PCI_ANY_ID,
+    PCI_CLASS_STORAGE_RAID << 8, 0xffffffL, board_ahci, "Hudson2" },
+
+  /* VIA */
+  { PCI_VDEVICE(VIA, 0x3349), board_ahci_vt8251,       chip_vt8251  }, /* VIA VT8251 */
+  { PCI_VDEVICE(VIA, 0x6287), board_ahci_vt8251,       chip_vt8251  }, /* VIA VT8251 */
+
+  /* NVIDIA */
+  { PCI_VDEVICE(NVIDIA, 0x044c), board_ahci_mcp65,     chip_mcp65   }, /* MCP65 */
+  { PCI_VDEVICE(NVIDIA, 0x044d), board_ahci_mcp65,     chip_mcp65   }, /* MCP65 */
+  { PCI_VDEVICE(NVIDIA, 0x044e), board_ahci_mcp65,     chip_mcp65   }, /* MCP65 */
+  { PCI_VDEVICE(NVIDIA, 0x044f), board_ahci_mcp65,     chip_mcp65   }, /* MCP65 */
+  { PCI_VDEVICE(NVIDIA, 0x045c), board_ahci_mcp65,     chip_mcp65   }, /* MCP65 */
+  { PCI_VDEVICE(NVIDIA, 0x045d), board_ahci_mcp65,     chip_mcp65   }, /* MCP65 */
+  { PCI_VDEVICE(NVIDIA, 0x045e), board_ahci_mcp65,     chip_mcp65   }, /* MCP65 */
+  { PCI_VDEVICE(NVIDIA, 0x045f), board_ahci_mcp65,     chip_mcp65   }, /* MCP65 */
+  { PCI_VDEVICE(NVIDIA, 0x0550), board_ahci_yesncq,    chip_mcp67   }, /* MCP67 */
+  { PCI_VDEVICE(NVIDIA, 0x0551), board_ahci_yesncq,    chip_mcp67   }, /* MCP67 */
+  { PCI_VDEVICE(NVIDIA, 0x0552), board_ahci_yesncq,    chip_mcp67   }, /* MCP67 */
+  { PCI_VDEVICE(NVIDIA, 0x0553), board_ahci_yesncq,    chip_mcp67   }, /* MCP67 */
+  { PCI_VDEVICE(NVIDIA, 0x0554), board_ahci_yesncq,    chip_mcp67   }, /* MCP67 */
+  { PCI_VDEVICE(NVIDIA, 0x0555), board_ahci_yesncq,    chip_mcp67   }, /* MCP67 */
+  { PCI_VDEVICE(NVIDIA, 0x0556), board_ahci_yesncq,    chip_mcp67   }, /* MCP67 */
+  { PCI_VDEVICE(NVIDIA, 0x0557), board_ahci_yesncq,    chip_mcp67   }, /* MCP67 */
+  { PCI_VDEVICE(NVIDIA, 0x0558), board_ahci_yesncq,    chip_mcp67   }, /* MCP67 */
+  { PCI_VDEVICE(NVIDIA, 0x0559), board_ahci_yesncq,    chip_mcp67   }, /* MCP67 */
+  { PCI_VDEVICE(NVIDIA, 0x055a), board_ahci_yesncq,    chip_mcp67   }, /* MCP67 */
+  { PCI_VDEVICE(NVIDIA, 0x055b), board_ahci_yesncq,    chip_mcp67   }, /* MCP67 */
+  { PCI_VDEVICE(NVIDIA, 0x0580), board_ahci_yesncq,    chip_mcp67   }, /* Linux ID */
+  { PCI_VDEVICE(NVIDIA, 0x07f0), board_ahci_yesncq,    chip_mcp73   }, /* MCP73 */
+  { PCI_VDEVICE(NVIDIA, 0x07f1), board_ahci_yesncq,    chip_mcp73   }, /* MCP73 */
+  { PCI_VDEVICE(NVIDIA, 0x07f2), board_ahci_yesncq,    chip_mcp73   }, /* MCP73 */
+  { PCI_VDEVICE(NVIDIA, 0x07f3), board_ahci_yesncq,    chip_mcp73   }, /* MCP73 */
+  { PCI_VDEVICE(NVIDIA, 0x07f4), board_ahci_yesncq,    chip_mcp73   }, /* MCP73 */
+  { PCI_VDEVICE(NVIDIA, 0x07f5), board_ahci_yesncq,    chip_mcp73   }, /* MCP73 */
+  { PCI_VDEVICE(NVIDIA, 0x07f6), board_ahci_yesncq,    chip_mcp73   }, /* MCP73 */
+  { PCI_VDEVICE(NVIDIA, 0x07f7), board_ahci_yesncq,    chip_mcp73   }, /* MCP73 */
+  { PCI_VDEVICE(NVIDIA, 0x07f8), board_ahci_yesncq,    chip_mcp73   }, /* MCP73 */
+  { PCI_VDEVICE(NVIDIA, 0x07f9), board_ahci_yesncq,    chip_mcp73   }, /* MCP73 */
+  { PCI_VDEVICE(NVIDIA, 0x07fa), board_ahci_yesncq,    chip_mcp73   }, /* MCP73 */
+  { PCI_VDEVICE(NVIDIA, 0x07fb), board_ahci_yesncq,    chip_mcp73   }, /* MCP73 */
+  { PCI_VDEVICE(NVIDIA, 0x0ad0), board_ahci,           chip_mcp77   }, /* MCP77 */
+  { PCI_VDEVICE(NVIDIA, 0x0ad1), board_ahci,           chip_mcp77   }, /* MCP77 */
+  { PCI_VDEVICE(NVIDIA, 0x0ad2), board_ahci,           chip_mcp77   }, /* MCP77 */
+  { PCI_VDEVICE(NVIDIA, 0x0ad3), board_ahci,           chip_mcp77   }, /* MCP77 */
+  { PCI_VDEVICE(NVIDIA, 0x0ad4), board_ahci,           chip_mcp77   }, /* MCP77 */
+  { PCI_VDEVICE(NVIDIA, 0x0ad5), board_ahci,           chip_mcp77   }, /* MCP77 */
+  { PCI_VDEVICE(NVIDIA, 0x0ad6), board_ahci,           chip_mcp77   }, /* MCP77 */
+  { PCI_VDEVICE(NVIDIA, 0x0ad7), board_ahci,           chip_mcp77   }, /* MCP77 */
+  { PCI_VDEVICE(NVIDIA, 0x0ad8), board_ahci,           chip_mcp77   }, /* MCP77 */
+  { PCI_VDEVICE(NVIDIA, 0x0ad9), board_ahci,           chip_mcp77   }, /* MCP77 */
+  { PCI_VDEVICE(NVIDIA, 0x0ada), board_ahci,           chip_mcp77   }, /* MCP77 */
+  { PCI_VDEVICE(NVIDIA, 0x0adb), board_ahci,           chip_mcp77   }, /* MCP77 */
+  { PCI_VDEVICE(NVIDIA, 0x0ab4), board_ahci,           chip_mcp79   }, /* MCP79 */
+  { PCI_VDEVICE(NVIDIA, 0x0ab5), board_ahci,           chip_mcp79   }, /* MCP79 */
+  { PCI_VDEVICE(NVIDIA, 0x0ab6), board_ahci,           chip_mcp79   }, /* MCP79 */
+  { PCI_VDEVICE(NVIDIA, 0x0ab7), board_ahci,           chip_mcp79   }, /* MCP79 */
+  { PCI_VDEVICE(NVIDIA, 0x0ab8), board_ahci,           chip_mcp79   }, /* MCP79 */
+  { PCI_VDEVICE(NVIDIA, 0x0ab9), board_ahci,           chip_mcp79   }, /* MCP79 */
+  { PCI_VDEVICE(NVIDIA, 0x0aba), board_ahci,           chip_mcp79   }, /* MCP79 */
+  { PCI_VDEVICE(NVIDIA, 0x0abb), board_ahci,           chip_mcp79   }, /* MCP79 */
+  { PCI_VDEVICE(NVIDIA, 0x0abc), board_ahci,           chip_mcp79   }, /* MCP79 */
+  { PCI_VDEVICE(NVIDIA, 0x0abd), board_ahci,           chip_mcp79   }, /* MCP79 */
+  { PCI_VDEVICE(NVIDIA, 0x0abe), board_ahci,           chip_mcp79   }, /* MCP79 */
+  { PCI_VDEVICE(NVIDIA, 0x0abf), board_ahci,           chip_mcp79   }, /* MCP79 */
+  { PCI_VDEVICE(NVIDIA, 0x0d84), board_ahci,           chip_mcp89   }, /* MCP89 */
+  { PCI_VDEVICE(NVIDIA, 0x0d85), board_ahci,           chip_mcp89   }, /* MCP89 */
+  { PCI_VDEVICE(NVIDIA, 0x0d86), board_ahci,           chip_mcp89   }, /* MCP89 */
+  { PCI_VDEVICE(NVIDIA, 0x0d87), board_ahci,           chip_mcp89   }, /* MCP89 */
+  { PCI_VDEVICE(NVIDIA, 0x0d88), board_ahci,           chip_mcp89   }, /* MCP89 */
+  { PCI_VDEVICE(NVIDIA, 0x0d89), board_ahci,           chip_mcp89   }, /* MCP89 */
+  { PCI_VDEVICE(NVIDIA, 0x0d8a), board_ahci,           chip_mcp89   }, /* MCP89 */
+  { PCI_VDEVICE(NVIDIA, 0x0d8b), board_ahci,           chip_mcp89   }, /* MCP89 */
+  { PCI_VDEVICE(NVIDIA, 0x0d8c), board_ahci,           chip_mcp89   }, /* MCP89 */
+  { PCI_VDEVICE(NVIDIA, 0x0d8d), board_ahci,           chip_mcp89   }, /* MCP89 */
+  { PCI_VDEVICE(NVIDIA, 0x0d8e), board_ahci,           chip_mcp89   }, /* MCP89 */
+  { PCI_VDEVICE(NVIDIA, 0x0d8f), board_ahci,           chip_mcp89   }, /* MCP89 */
+
+  /* SiS */
+  { PCI_VDEVICE(SI, 0x1184), board_ahci,               "966"        }, /* SiS 966 */
+  { PCI_VDEVICE(SI, 0x1185), board_ahci,               chip_sis968  }, /* SiS 968 */
+  { PCI_VDEVICE(SI, 0x0186), board_ahci,               chip_sis968  }, /* SiS 968 */
+
+  /* Marvell */
+  { PCI_VDEVICE(MARVELL, 0x6145), board_ahci_mv,       "6145"       }, /* 6145 */
+  { PCI_VDEVICE(MARVELL, 0x6121), board_ahci_mv,       "6121"       }, /* 6121 */
+
+  /* Promise */
+  { PCI_VDEVICE(PROMISE, 0x3f20), board_ahci,          "PDC42819"   }, /* PDC42819 */
+
+  /* Generic, PCI class code for AHCI */
+  { PCI_ANY_ID, PCI_ANY_ID, PCI_ANY_ID, PCI_ANY_ID,
+    PCI_CLASS_STORAGE_SATA_AHCI, 0xffffffL, board_ahci, s_generic   },
+
+  /* end of list, including a few slots to define custom adapters (10) */
+  { 0, 0, 0, 0, 0, 0, 0, NULL },
+  { 0, 0, 0, 0, 0, 0, 0, NULL },
+  { 0, 0, 0, 0, 0, 0, 0, NULL },
+  { 0, 0, 0, 0, 0, 0, 0, NULL },
+  { 0, 0, 0, 0, 0, 0, 0, NULL },
+  { 0, 0, 0, 0, 0, 0, 0, NULL },
+  { 0, 0, 0, 0, 0, 0, 0, NULL },
+  { 0, 0, 0, 0, 0, 0, 0, NULL },
+  { 0, 0, 0, 0, 0, 0, 0, NULL },
+  { 0, 0, 0, 0, 0, 0, 0, NULL },
+
+  { 0, 0, 0, 0, 0, 0, 0, NULL }
+};
+
+/* ----------------------------- start of code ----------------------------- */
+
+/******************************************************************************
+ * Add specified PCI vendor and device ID to the list of supported AHCI
+ * controllers. Please note that the last slot in pci_ids needs to remain
+ * empty because it's used as end marker.
+ */
+int add_pci_id(u16 vendor, u16 device)
+{
+  int max_slot = sizeof(pci_ids) / sizeof(*pci_ids) - 2;
+  int i;
+
+  /* search for last used slot in 'pci_ids' */
+  for (i = max_slot; i >= 0 && pci_ids[i].vendor == 0; i--);
+  if (i >= max_slot) return(-1); /* all slots in use */
+
+  /* use slot after the last used slot */
+  i++;
+  pci_ids[i].vendor = vendor;
+  pci_ids[i].device = device;
+  pci_ids[i].board = board_ahci;
+  pci_ids[i].chipname = s_generic;
+  return(0);
+}
+
+/******************************************************************************
+ * Scan PCI bus using OEMHLP$ IOCTLs and build adapter list.
+ */
+void scan_pci_bus(void)
+{
+  UCHAR index;
+  int ad_indx = 0;
+  int i;
+  int n;
+  USHORT BusDevFunc;
+
+  DPRINTF(3,"scanning PCI bus...\n");
+
+  /* Go through the list of PCI IDs and search for each device
+   *
+   * NOTES:
+   *
+   *  - When searching via class code, the OEMHLP$ interface doesn't allow
+   *    setting a bitmask to look for individual portions of class code,
+   *    subclass code and programming interface. However, all bitmasks in the
+   *    PCI list currently use 0xffffff, thus this should not be a problem at
+   *    this point in time.
+   *
+   *  - Scanning via OEMHLP$ seems rather slow, at least in the virtual
+   *    machine I'm currenly using to test this driver. Thus, class code
+   *    scans are preferred unless the option "-t" (thorough_scan) has been
+   *    specified. The assumption is that most, if not all, modern AHCI
+   *    adapters have the correct class code (PCI_CLASS_STORAGE_SATA_AHCI).
+   */
+  for (i = 0; pci_ids[i].vendor != 0; i++)
+  {
+    index = 0;
+    do
+    {
+      if (pci_ids[i].device == PCI_ANY_ID || pci_ids[i].vendor == PCI_ANY_ID)
+      {
+        /* look for class code */
+        BusDevFunc = PciFindClass(pci_ids[i].class, index);
+      }
+      else if (thorough_scan)
+      {
+        /* look for this specific vendor and device ID */
+        BusDevFunc = PciFindDevice( pci_ids[i].vendor, pci_ids[i].device, index);
+
+      }
+      else
+      {
+        BusDevFunc = 0xffff;
+      }
+
+      if (BusDevFunc != 0xffff)
+      {
+        /* found a device */
+        int already_found = 0;
+
+        /* increment index for next loop */
+        if (++index > 180) return; /* something's wrong here... */
+
+        /* check whether we already found this device */
+        for (n = 0; n < ad_info_cnt; n++)
+        {
+          if (ad_infos[n].bus_dev_func == BusDevFunc)
+          {
+            /* this device has already been found (e.g. via thorough scan) */
+            already_found = 1;
+            break;
+          }
+        }
+
+        if (already_found || (ad_ignore & (1U << ad_indx++)))
+        {
+          /* ignore this device; it has either already been found via a
+           * thorough scan or has been specified to be ignored via command
+           * line option */
+          continue;
+        }
+
+        /* add this PCI device to ad_infos[] */
+        add_pci_device(pci_ids + i, BusDevFunc);
+      }
+
+    } while (BusDevFunc != 0xffff);
+  }
+}
+
+/******************************************************************************
+ * Enable interrupt generation. PCI 2.3 added a bit which allows disabling
+ * interrupt generation for a device. This function clears the corresponding
+ * bit in the configuration space command register.
+ */
+int pci_enable_int(USHORT BusDevFunc)
+{
+  ULONG tmp;
+
+  if (PciReadConfig(BusDevFunc, 4, sizeof(tmp), &tmp) ||
+      PciWriteConfig(BusDevFunc, 4, sizeof(tmp), tmp & ~(1UL << 10)))
+  {
+    return(-1);
+  }
+  return(0);
+}
+
+/******************************************************************************
+ * Hack to set up proper IRQ mappings in the emulated PIIX3 ISA bridge in
+ * VirtualBox (for some reason, the first mapped IRQ is 0x80 without this
+ * hack).
+ */
+void pci_hack_virtualbox(void)
+{
+  UCHAR irq = 0;
+
+  if (!PciReadConfig(0x0008, 0x60, sizeof(irq), &irq) && irq == 0x80)
+  {
+    /* set IRQ for first device/func to 11 */
+    DPRINTF(1,"hacking virtualbox PIIX3 PCI to ISA bridge IRQ mapping\n");
+    irq = ad_infos[0].irq;
+    PciWriteConfig(0x0008, 0x60, sizeof(irq), irq);
+  }
+}
+
+/******************************************************************************
+ * Add a single PCI device to the list of adapters.
+ */
+static void add_pci_device(PCI_ID *pci_id, USHORT BusDevFunc)
+{
+  char rc_list_buf[sizeof(AHRESOURCE) + sizeof(HRESOURCE) * 15];
+  AHRESOURCE *rc_list = (AHRESOURCE *) rc_list_buf;
+  RESOURCESTRUCT resource;
+  ADAPTERSTRUCT adapter;
+  ADJUNCT adj;
+  AD_INFO *ad_info;
+  APIRET ret;
+  ULONG val;
+  char tmp[40];
+  u16 device;
+  u16 vendor;
+  u32 class;
+  int irq;
+  int pin;
+  int i;
+
+  /*****************************************************************************
+   * Part 1: Get further information about the device to be added; PCI ID...
+   */
+  if (PciReadConfig(BusDevFunc, 0x00, sizeof(ULONG), &val)) return;
+  device = (val >> 16);
+  vendor = (val & 0xffff);
+
+  /* ... and class code */
+  if (PciReadConfig(BusDevFunc, 0x08, sizeof(ULONG), &val)) return;
+  class = (val >> 8);
+
+  if (pci_id->device == PCI_ANY_ID)
+  {
+    /* We found this device in a wildcard search. There are two possible
+     * reasons which require a different handling:
+     *
+     *  1) This device uses a non-standard PCI class and has been identified
+     *     with the corresponding class in pci_ids[] (e.g. the entry
+     *     PCI_VENDOR_ID_JMICRON), but there is a vendor ID in pci_ids[]. In
+     *     this case, we need to verify that the vendor is correct (see
+     *     comments regarding OEMHLP limitations in 'scan_pci_bus()')
+     *
+     *  2) This device was identified using a generic PCI class for AHCI
+     *     adapters such as PCI_CLASS_STORAGE_SATA_AHCI and we need to map
+     *     the device and vendor ID to the corresponding index in pci_ids[]
+     *     if there is such an entry; the index passed to this function will
+     *     be the generic class-based index which is fine as long as there's
+     *     not special treatment required as indicated by the board_*
+     *     constants in pci_ids[]...
+     *
+     *     The main reason for this kludge is that it seems as if OEMHLP$
+     *     is rather slow searching for PCI devices, adding around 30s
+     *     to the boot time when scanning for individual AHCI PCI IDs. Thus,
+     *     the OS2AHCI driver avoids this kind of scan in favor of a class-
+     *     based scan (unless overridden with the "/T" option).
+     */
+    if (pci_id->vendor != PCI_ANY_ID)
+    {
+      /* case 1: the vendor is known but we found the PCI device using a class
+       * search; verify vendor matches the one in pci_ids[]
+       */
+      if (pci_id->vendor != vendor) return; /* vendor doesn't match */
+    }
+    else
+    {
+      /* case 2: we found this device using a generic class search; if the
+       * device/vendor is listed in pci_ids[], use this entry in favor of the
+       * one passed in 'pci_id'
+       */
+      for (i = 0; pci_ids[i].vendor != 0; i++)
+      {
+        if (pci_ids[i].device == device && pci_ids[i].vendor == vendor)
+        {
+          pci_id = pci_ids + i;
+          break;
+        }
+      }
+    }
+  }
+
+  /* found a supported AHCI device */
+
+  if (PciReadConfig(BusDevFunc, 0x3c, sizeof(u32), &val)) return;
+  irq = (int) (val & 0xff);
+  pin = (int) ((val >> 8) & 0xff);
+
+  i = 1;
+  if (irq==0 || irq==255) i = 0;
+
+  if (verbosity > i)
+  {
+    iprintf("%s AHCI device %s %s (%d:%d:%d %04x:%04x) class:0x%06x", i?"Found":"Ignoring",
+     vendor_from_id(vendor), device_from_id(device),
+     PCI_BUS_FROM_BDF(BusDevFunc), PCI_DEV_FROM_BDF(BusDevFunc), PCI_FUNC_FROM_BDF(BusDevFunc),
+     vendor, device, class);
+    if (i==0) iprintf("Invalid interrupt (IRQ=%d).", irq);
+  }
+  if (i==0) return;
+
+  /* make sure we got room in the adapter information array */
+  if (ad_info_cnt >= MAX_AD - 1)
+  {
+    iprintf("%s: too many AHCI devices", drv_name);
+    return;
+  }
+
+  /****************************************************************************
+   * Part 2: Determine resource requirements and allocate resources with the
+   *         OS/2 resource manager. While doing so, some of the entries of the
+   *         corresponding slot in the AD_INFO array, namely resource manager
+   *         handles, are initialized so we need prepare the slot.
+   *
+   * NOTE: While registering resources with the resource manager, each new
+   *       resource is added to the corresponding rc_list.hResource[] slot.
+   *       rc_list is used further down to associate resources to adapters
+   *       when the adapter itself is registered with the OS/2 resource
+   *       manager.
+   */
+  ad_info = ad_infos + ad_info_cnt;
+  memset(ad_info, 0x00, sizeof(*ad_info));
+  rc_list->NumResource = 0;
+
+  /* Register IRQ with resource manager */
+  ret = RmAddIrq(rm_drvh, &ad_info->rm_irq, irq, pin);
+  if (ret)
+  {
+    if (ret == RMRC_RES_ALREADY_CLAIMED)
+    {
+      ciiprintf("Device already claimed.");
+    }
+    else
+    {
+      iprintf("%s: couldn't register IRQ %d (rc = %s)", drv_name, irq, rmerr(ret));
+    }
+    return;
+  }
+  rc_list->hResource[rc_list->NumResource++] = ad_info->rm_irq;
+
+  /* Allocate all I/O and MMIO addresses offered by this device. In theory,
+   * we need only BAR #5, the AHCI MMIO BAR, but in order to prevent any
+   * other driver from hijacking our device and accessing it via legacy
+   * registers we'll reserve anything we can find.
+   */
+
+  ciprintf("Adapter %d PCI=%d:%d:%d ID=%04x:%04x\n", ad_info_cnt, PCI_BUS_FROM_BDF(BusDevFunc),
+    PCI_DEV_FROM_BDF(BusDevFunc), PCI_FUNC_FROM_BDF(BusDevFunc), vendor, device);
+  DPRINTF(1,"Adapter %d PCI=%d:%d:%d ID=%04x:%04x\n", ad_info_cnt, PCI_BUS_FROM_BDF(BusDevFunc),
+    PCI_DEV_FROM_BDF(BusDevFunc), PCI_FUNC_FROM_BDF(BusDevFunc), vendor, device);
+
+  for (i = 0; i < sizeof(ad_info->rm_bars) / sizeof(*ad_info->rm_bars); i++)
+  {
+    long len = bar_resource(BusDevFunc, &resource, i);
+
+    if (len < 0)
+    {
+      /* something went wrong */
+      goto add_pci_fail;
+    }
+    if (len == 0)
+    {
+      /* this BAR is unused */
+      continue;
+    }
+
+    if (i == AHCI_PCI_BAR)
+    {
+      if (resource.ResourceType != RS_TYPE_MEM)
+      {
+        iprintf("%s: BAR #5 must be an MMIO region", drv_name);
+        goto add_pci_fail;
+      }
+      /* save this BAR's address as MMIO address */
+      ad_info->mmio_phys = resource.MEMResource.MemBase;
+      ad_info->mmio_size = resource.MEMResource.MemSize;
+    }
+
+    /* register [MM]IO region with resource manager */
+    ret = RMAllocResource(rm_drvh, ad_info->rm_bars + i, &resource);
+    if (ret != RMRC_SUCCESS)
+    {
+      if (ret == RMRC_RES_ALREADY_CLAIMED)
+      {
+        ciiprintf("Device already claimed.");
+      }
+      else
+      {
+        iprintf("%s: couldn't register [MM]IO region (rc = %s)", drv_name, rmerr(ret));
+      }
+      goto add_pci_fail;
+    }
+    rc_list->hResource[rc_list->NumResource++] = ad_info->rm_bars[i];
+  }
+
+  if (ad_info->mmio_phys == 0)
+  {
+    iprintf("%s: couldn't determine MMIO base address", drv_name);
+    goto add_pci_fail;
+  }
+
+  /****************************************************************************
+   * Part 3: Fill in the remaining fields in the AD_INFO slot and allocate
+   * memory and GDT selectors for the adapter. Finally, register the adapter
+   * itself with the OS/2 resource manager
+   */
+  ad_info->pci = pci_id;
+  ad_info->pci_vendor = vendor;
+  ad_info->pci_device = device;
+  ad_info->bus_dev_func = BusDevFunc;
+  ad_info->irq = irq;
+
+  ad_info->mmio = MapPhysToLin(ad_info->mmio_phys, ad_info->mmio_size);
+  if (!ad_info->mmio) goto add_pci_fail;
+
+  /* register adapter with resource manager */
+  memset(&adj, 0x00, sizeof(adj));
+  adj.pNextAdj       = NULL;
+  adj.AdjLength      = sizeof(adj);
+  adj.AdjType        = ADJ_ADAPTER_NUMBER;
+  adj.Adapter_Number = ad_info_cnt;
+
+  memset(&adapter, 0x00, sizeof(adapter));
+  sprintf(tmp, "AHCI_%d Controller", ad_info_cnt);
+  adapter.AdaptDescriptName = tmp;
+  adapter.AdaptFlags        = 0;
+  adapter.BaseType          = AS_BASE_MSD;
+  adapter.SubType           = AS_SUB_IDE;
+  adapter.InterfaceType     = AS_INTF_GENERIC;
+  adapter.HostBusType       = AS_HOSTBUS_PCI;
+  adapter.HostBusWidth      = AS_BUSWIDTH_32BIT;
+  adapter.pAdjunctList      = &adj;
+
+  ret = RMCreateAdapter(rm_drvh, &ad_info->rm_adh, &adapter, NULL, rc_list);
+  if (ret != RMRC_SUCCESS)
+  {
+    iprintf("%s: couldn't register adapter (rc = %s)", drv_name, rmerr(ret));
+    goto add_pci_fail;
+  }
+
+  if (ahci_config_caps(ad_info)) goto add_pci_fail;
+
+  #ifndef DAZ_NEW_CODE
+  /* fill in DMA scratch buffer addresses in adapter info */
+  for (i = 0; i < AHCI_MAX_PORTS; i++)
+  {
+    if (!(ad_info->port_map & (1UL << i))) continue;
+
+    ad_info->ports[i].dma_buf = MemAllocAlign(AHCI_PORT_PRIV_DMA_SZ, 1024);
+    ad_info->ports[i].dma_buf_phys = MemPhysAdr(ad_info->ports[i].dma_buf);
+  }
+  #endif
+
+  /* Successfully added the adapter and reserved its resources; the adapter
+   * is still under BIOS control so we're not going to do anything else at
+   * this point.
+   */
+
+  ad_info_cnt++;
+  return;
+
+
+add_pci_fail:
+  /* something went wrong; try to clean up as far as possible */
+  for (i = 0; i < sizeof(ad_info->rm_bars) / sizeof(*ad_info->rm_bars); i++)
+  {
+    if (ad_info->rm_bars[i] != 0) RMDeallocResource(rm_drvh, ad_info->rm_bars[i]);
+  }
+  if (ad_info->rm_irq != 0) RMDeallocResource(rm_drvh, ad_info->rm_irq);
+}
+
+/******************************************************************************
+ * Prepare a resource structure for a PCI Base Address Register (BAR). This
+ * basically means the type, address and range of the I/O address space. It
+ * returns the length of the address range as a signed long to allow the caller
+ * to differentiate between error conditions (< 0), unused BARs (0) or valid
+ * bars (> 0).
+ *
+ * NOTE: In order to do this, we need to temporarily write 0xffffffff to
+ *       the MMIO base address register (BAR), read back the resulting value
+ *       and check the 0 bits from the right end, masking the lower 2 (I/O) or
+ *       4 (MMIO) bits. After doing this, we must restore the original value
+ *       set up by the BIOS.
+ *
+ *  31                                                        4 3 2 1 0
+ *  -------------------------------------------------------------------
+ *                        base address                          P T T I
+ *  P = prefetchable
+ *  T = type (0 = any 32 bit, 1 = <1M, 2 = 64 bit)
+ *  I = I/O (1) or memory (0)
+ */
+static long bar_resource(USHORT BusDevFunc, RESOURCESTRUCT *resource, int i)
+{
+  u32 bar_addr = 0;
+  u32 bar_size = 0;
+
+  /* temporarily write 1s to this BAR to determine the address range */
+  if (PciReadConfig (BusDevFunc, PCI_BAR(i), sizeof(u32), &bar_addr) ||
+      PciWriteConfig(BusDevFunc, PCI_BAR(i), sizeof(u32), ~(0UL))    ||
+      PciReadConfig (BusDevFunc, PCI_BAR(i), sizeof(u32), &bar_size) ||
+      PciWriteConfig(BusDevFunc, PCI_BAR(i), sizeof(u32), bar_addr) )
+  {
+    iprintf("%s: couldn't determine [MM]IO size", drv_name);
+    if (bar_addr != 0)
+    {
+      PciWriteConfig(BusDevFunc, PCI_BAR(i), sizeof(u32), bar_addr);
+    }
+    return(-1);
+  }
+
+  /* bar not implemented or device not working properly */
+  if (bar_size == 0 || bar_size == 0xffffffffUL) return(0);
+
+  /* prepare resource allocation structure */
+  memset(resource, 0x00, sizeof(*resource));
+  if (bar_addr & 1)
+  {
+    bar_size = ~(bar_size & 0xfffffffcUL) + 1;
+    bar_size &= 0xffffUL;  /* I/O address space is 16 bits on x86 */
+    bar_addr &= 0xfffcUL;
+
+    resource->ResourceType              = RS_TYPE_IO;
+    resource->IOResource.BaseIOPort     = bar_addr;
+    resource->IOResource.NumIOPorts     = bar_size;
+    resource->IOResource.IOFlags        = RS_IO_EXCLUSIVE;
+    resource->IOResource.IOAddressLines = 16;
+
+  }
+  else
+  {
+    bar_size = ~(bar_size & 0xfffffff0UL) + 1;
+    bar_addr &= 0xfffffff0UL;
+
+    resource->ResourceType         = RS_TYPE_MEM;
+    resource->MEMResource.MemBase  = bar_addr;
+    resource->MEMResource.MemSize  = bar_size;
+    resource->MEMResource.MemFlags = RS_MEM_EXCLUSIVE;
+  }
+
+  DPRINTF(3,"BAR #%d: type = %s, addr = 0x%08lx, size = %d\n", i,
+           (resource->ResourceType == RS_TYPE_IO) ? "I/O" : "MEM",
+           bar_addr, bar_size);
+
+  return((long) bar_size);
+}
+
+/******************************************************************************
+ * return vendor name for PCI vendor ID
+ */
+char *vendor_from_id(u16 id)
+{
+
+  switch(id)
+  {
+    case PCI_VENDOR_ID_AL:
+      return "Ali";
+    case PCI_VENDOR_ID_AMD:
+    case PCI_VENDOR_ID_ATI:
+      return "AMD";
+    case PCI_VENDOR_ID_AT:
+      return "Allied Telesyn";
+    case PCI_VENDOR_ID_ATT:
+      return "ATT";
+    case PCI_VENDOR_ID_CMD:
+      return "CMD";
+    case PCI_VENDOR_ID_CT:
+      return "CT";
+    case PCI_VENDOR_ID_INTEL:
+      return "Intel";
+    case PCI_VENDOR_ID_INITIO:
+      return "Initio";
+    case PCI_VENDOR_ID_JMICRON:
+      return "JMicron";
+    case PCI_VENDOR_ID_MARVELL:
+      return "Marvell";
+    case PCI_VENDOR_ID_NVIDIA:
+      return "NVIDIA";
+    case PCI_VENDOR_ID_PROMISE:
+      return "PROMISE";
+    case PCI_VENDOR_ID_SI:
+      return "SiS";
+    case PCI_VENDOR_ID_VIA:
+      return "VIA";
+    default:
+      break;
+    }
+
+  return "Generic";
+}
+
+/******************************************************************************
+ * return a device name for a PCI device id
+ * NOTE: this is as simple as can be, so don't call it twice in one statement.
+ */
+char *device_from_id(u16 device)
+{
+  int i;
+
+  for (i = 0; pci_ids[i].vendor != 0; i++)
+  {
+    if (pci_ids[i].device == device)
+    {
+      return pci_ids[i].chipname;
+    }
+  }
+
+  return s_generic;
+}
+
+/******************************************************************************
+ * Return textual version of a resource manager error
+ */
+static char *rmerr(APIRET ret)
+{
+  switch (ret) {
+  case RMRC_SUCCESS:
+    return("RMRC_SUCCESS");
+  case RMRC_NOTINITIALIZED:
+    return("RMRC_NOTINITIALIZED");
+  case RMRC_BAD_DRIVERHANDLE:
+    return("RMRC_BAD_DRIVERHANDLE");
+  case RMRC_BAD_ADAPTERHANDLE:
+    return("RMRC_BAD_ADAPTERHANDLE");
+  case RMRC_BAD_DEVICEHANDLE:
+    return("RMRC_BAD_DEVICEHANDLE");
+  case RMRC_BAD_RESOURCEHANDLE:
+    return("RMRC_BAD_RESOURCEHANDLE");
+  case RMRC_BAD_LDEVHANDLE:
+    return("RMRC_BAD_LDEVHANDLE");
+  case RMRC_BAD_SYSNAMEHANDLE:
+    return("RMRC_BAD_SYSNAMEHANDLE");
+  case RMRC_BAD_DEVHELP:
+    return("RMRC_BAD_DEVHELP");
+  case RMRC_NULL_POINTER:
+    return("RMRC_NULL_POINTER");
+  case RMRC_NULL_STRINGS:
+    return("RMRC_NULL_STRINGS");
+  case RMRC_BAD_VERSION:
+    return("RMRC_BAD_VERSION");
+  case RMRC_RES_ALREADY_CLAIMED:
+    return("RMRC_RES_ALREADY_CLAIMED");
+  case RMRC_DEV_ALREADY_CLAIMED:
+    return("RMRC_DEV_ALREADY_CLAIMED");
+  case RMRC_INVALID_PARM_VALUE:
+    return("RMRC_INVALID_PARM_VALUE");
+  case RMRC_OUT_OF_MEMORY:
+    return("RMRC_OUT_OF_MEMORY");
+  case RMRC_SEARCH_FAILED:
+    return("RMRC_SEARCH_FAILED");
+  case RMRC_BUFFER_TOO_SMALL:
+    return("RMRC_BUFFER_TOO_SMALL");
+  case RMRC_GENERAL_FAILURE:
+    return("RMRC_GENERAL_FAILURE");
+  case RMRC_IRQ_ENTRY_ILLEGAL:
+    return("RMRC_IRQ_ENTRY_ILLEGAL");
+  case RMRC_NOT_IMPLEMENTED:
+    return("RMRC_NOT_IMPLEMENTED");
+  case RMRC_NOT_INSTALLED:
+    return("RMRC_NOT_INSTALLED");
+  case RMRC_BAD_DETECTHANDLE:
+    return("RMRC_BAD_DETECTHANDLE");
+  case RMRC_BAD_RMHANDLE:
+    return("RMRC_BAD_RMHANDLE");
+  case RMRC_BAD_FLAGS:
+    return("RMRC_BAD_FLAGS");
+  case RMRC_NO_DETECTED_DATA:
+    return("RMRC_NO_DETECTED_DATA");
+  default:
+    return("RMRC_UNKOWN");
+  }
+}
Index: /tags/2.02/src/os2ahci/trace.c
===================================================================
--- /tags/2.02/src/os2ahci/trace.c	(revision 189)
+++ /tags/2.02/src/os2ahci/trace.c	(revision 189)
@@ -0,0 +1,83 @@
+/******************************************************************************
+ *
+ * Copyright (c) 2013-2016 David Azarewicz
+ *
+ */
+
+#include "os2ahci.h"
+
+/* -------------------------- macros and constants ------------------------- */
+
+/* ------------------------ typedefs and structures ------------------------ */
+
+/* -------------------------- function prototypes -------------------------- */
+
+/* ------------------------ global/static variables ------------------------ */
+
+/* ----------------------------- start of code ----------------------------- */
+
+/******************************************************************************
+ * Create adapter/port/device list for user output.
+ */
+void build_user_info(void)
+{
+  int a;
+  int p;
+  int d;
+
+  for (a = 0; a < ad_info_cnt; a++)
+  {
+    AD_INFO *ai = ad_infos + a;
+
+    dprintf(0,"Adapter %d: PCI=%d:%d:%d ID=%04x:%04x %s %s irq=%d addr=0x%x version=%x\n", a,
+      PCI_BUS_FROM_BDF(ai->bus_dev_func), PCI_DEV_FROM_BDF(ai->bus_dev_func),
+      PCI_FUNC_FROM_BDF(ai->bus_dev_func),
+      ai->pci_vendor, ai->pci_device, vendor_from_id(ai->pci_vendor), ai->pci->chipname,
+      ai->irq, ai->mmio_phys,
+      ai->bios_config[HOST_VERSION / sizeof(u32)]);
+
+    for (p = 0; p <= ai->port_max; p++)
+    {
+      P_INFO *pi = &ai->ports[p];
+
+      dprintf(0,"  Port %d:\n", p);
+
+      for (d = 0; d <= pi->dev_max; d++)
+      {
+        if (!pi->devs[d].present)
+        {
+          dprintf(0,"    No drive present\n");
+        } else {
+          dprintf(0,"    Drive %d:", d);
+          if (pi->devs[d].atapi) dprintf(0," atapi");
+          if (pi->devs[d].removable) dprintf(0," removable");
+          if (pi->devs[d].dev_info.Method != NULL)
+          {
+            dprintf(0," %d cylinders, %d heads, %d sectors per track (%dMB) (%s)",
+              pi->devs[d].dev_info.Cylinders, pi->devs[d].dev_info.HeadsPerCylinder, pi->devs[d].dev_info.SectorsPerTrack,
+              pi->devs[d].dev_info.TotalSectors/2048, pi->devs[d].dev_info.Method);
+          }
+          dprintf(0,"\n");
+          dprintf(0,"             Model: %s\n", pi->devs[d].dev_name);
+        } /* if */
+      } /* for d */
+    } /* for p */
+  } /* for a */
+}
+
+#ifdef DEBUG
+void DumpIorb(IORBH *pIorb)
+{
+  if (D32g_DbgLevel < 2) return;
+  if (!ad_infos[iorb_unit_adapter(pIorb)].ports[iorb_unit_port(pIorb)].devs[iorb_unit_device(pIorb)].atapi) return;
+
+  dprintf(0,"IORB %x: Size=%x Len=%x Handle=%x CmdCode=%x\n",
+      pIorb, sizeof(IORBH), pIorb->Length, pIorb->UnitHandle, pIorb->CommandCode);
+  dprintf(0,"  CmdMod=%x ReqCtrl=%x Status=%x ErrorCode=%x\n",
+      pIorb->CommandModifier, pIorb->RequestControl, pIorb->Status, pIorb->ErrorCode);
+  dprintf(0,"  Timeout=%x StatusBlkLen=%x pStatusBlk=%x Res=%x pNxtIORB=%x\n",
+      pIorb->Timeout, pIorb->StatusBlockLen, pIorb->pStatusBlock, pIorb->Reserved_1,
+      pIorb->pNxtIORB);
+}
+#endif
+
Index: /tags/2.02/tools/AddToFile.cmd
===================================================================
--- /tags/2.02/tools/AddToFile.cmd	(revision 189)
+++ /tags/2.02/tools/AddToFile.cmd	(revision 189)
@@ -0,0 +1,219 @@
+/** AddToFile.cmd
+ * Adds the specified line to the end of the specified file.
+ * Written by and Copyright (c) 2010-2016 David Azarewicz http://88watts.net
+ *
+ * @#D Azarewicz:1.01#@##1## 16 Sep 2016              DAZAR1    ::::::@@AddToFile.cmd (c) David Azarewicz 2016
+ * V1.01 16-Sep-2016 First official release
+ *
+ * The following line is for the help sample code for the VAR function:
+EXAMPLEVAR=Example String
+ */
+call RxFuncAdd 'SysLoadFuncs', 'RexxUtil', 'SysLoadFuncs'
+call SysLoadFuncs
+parse arg OutFile','String','Function','Parm1','Parm2','Parm3','Parm4;
+
+if (OutFile='') then do
+  Say 'Usage:';
+  Say '  AddToFile.cmd FileName,String,Function[,Parameters...]';
+  Say 'Functions:';
+  Say '  DATEL - Adds the date as a long number.';
+  Say '  BLDLEVEL - Adds a formatted BLDLEVEL string.';
+  Say '  DATEYEAR - Adds the current year.';
+  Say '  DATEMONTH - Adds the current month.';
+  Say '  DATEDAY - Adds the current day.';
+  Say '  VERSIONMAJOR - Adds the major portion of the provided version number.';
+  Say '  VERSIONMINOR - Adds the minor portion of the provided version number.';
+  Say '  VERSIONREVISION - Adds the revision portion of the provided version number.';
+  Say '  VAR - Adds the value of the specified variable from a specified file.';
+  Say '  FILE - Adds the contents of a file.';
+  Say 'Examples:';
+  MyFile='AddToFile.tmp';
+  rc=SysFileDelete(MyFile);
+  MyCmd=MyFile||',#define DDATE,DATEL';
+  rc=LineOut(MyFile, '--- AddToFile.cmd '||MyCmd);
+  call 'AddToFile.cmd' MyCmd;
+  MyCmd=MyFile||',option description,BLDLEVEL,Vendor,1.2.3,Description,Fixpack';
+  rc=LineOut(MyFile, '--- AddToFile.cmd '||MyCmd);
+  call 'AddToFile.cmd' MyCmd;
+  MyCmd=MyFile||',char *bl = "%A";,BLDLEVEL,Vendor,1.2.3,Description,Fixpack';
+  rc=LineOut(MyFile, '--- AddToFile.cmd '||MyCmd);
+  call 'AddToFile.cmd' MyCmd;
+  MyCmd=MyFile||',#define DYEAR,DATEYEAR';
+  rc=LineOut(MyFile, '--- AddToFile.cmd '||MyCmd);
+  call 'AddToFile.cmd' MyCmd;
+  MyCmd=MyFile||',#define DMONTH,DATEMONTH';
+  rc=LineOut(MyFile, '--- AddToFile.cmd '||MyCmd);
+  call 'AddToFile.cmd' MyCmd;
+  MyCmd=MyFile||',#define DDAY,DATEDAY';
+  rc=LineOut(MyFile, '--- AddToFile.cmd '||MyCmd);
+  call 'AddToFile.cmd' MyCmd;
+  MyCmd=MyFile||',#define DVMAJ,VERSIONMAJOR,1.2.3';
+  rc=LineOut(MyFile, '--- AddToFile.cmd '||MyCmd);
+  call 'AddToFile.cmd' MyCmd;
+  MyCmd=MyFile||',#define DVMIN,VERSIONMINOR,1.2.3';
+  rc=LineOut(MyFile, '--- AddToFile.cmd '||MyCmd);
+  call 'AddToFile.cmd' MyCmd;
+  MyCmd=MyFile||',#define DVREV,VERSIONREVISION,1.2.3';
+  rc=LineOut(MyFile, '--- AddToFile.cmd '||MyCmd);
+  call 'AddToFile.cmd' MyCmd;
+  MyCmd=MyFile||',#define XYZ "%A",VAR,EXAMPLEVAR=,AddToFile.cmd';
+  rc=LineOut(MyFile, '--- AddToFile.cmd '||MyCmd);
+  call 'AddToFile.cmd' MyCmd;
+  MyCmd=MyFile||',InFileName,FILE';
+  rc=LineOut(MyFile, '--- AddToFile.cmd '||MyCmd);
+  call 'AddToFile.cmd' MyCmd;
+  do while LINES(MyFile) <> 0;
+    Say LINEIN(MyFile);
+  end;
+  rc=stream(MyFile,'c','close');
+  rc=SysFileDelete(MyFile);
+  exit;
+end
+
+select
+  when (Function='DATEL') then do
+    NewStr=DATE('S')||'L'
+    RepLoc=Pos('%A', String);
+    if (RepLoc>0) then OutStr=Substr(String,1,RepLoc-1)||NewStr||Substr(String,RepLoc+2);
+    else OutStr=String||' '||NewStr;
+    rc=lineout(OutFile, OutStr);
+    rc=lineout(OutFile);
+  end
+
+  when (LEFT(Function,8)='BLDLEVEL') then do
+    Type=SUBSTR(Function,9,1);
+
+    /* get hostname for build system */
+    ProjHost = VALUE('HOSTNAME',,'OS2ENVIRONMENT');
+
+    /* projString = projDate" "projTime"       "projHost */
+    ProjString = left(Date()||' '||TIME(),25)||left(ProjHost,10);
+
+    /* get fixpack from SVN version */
+    if (Parm4='SVN') then Parm4='SVN'||SvnVersion();
+
+    parse var Parm2 major'.'minor'.'ProjVersion;
+
+    RepLoc=Pos('%Y', Parm3);
+    if (RepLoc>0) then do
+      Parm3=Substr(Parm3,1,RepLoc-1)||FORMAT(SUBSTR(DATE('S'), 1, 4))||Substr(Parm3,RepLoc+2);
+    end
+
+    NewStr='@#'||Parm1||':'||Parm2||'#@##1## '||ProjString||'::::'||ProjVersion||'::'||Parm4||'@@'||Parm3;
+    if (Type='2') then do
+      /*OutStr=String||' "@#'||Parm1||':'||Parm2||'#@##1## '||ProjString||'::::'||ProjVersion||'::'||Parm4||'@@'||Parm3||'"';*/
+      OutStr=String||' "'||NewStr||'"';
+    end
+    else do
+      RepLoc=Pos('%A', String);
+      if (RepLoc>0) then OutStr=Substr(String,1,RepLoc-1)||NewStr||Substr(String,RepLoc+2);
+      else OutStr=String||" '"||NewStr||"'";
+    end
+    rc=lineout(OutFile, OutStr);
+    rc=lineout(OutFile);
+  end
+
+  when (Function='DATEYEAR') then do
+    NewStr=FORMAT(SUBSTR(DATE('S'), 1, 4));
+    RepLoc=Pos('%A', String);
+    if (RepLoc>0) then OutStr=Substr(String,1,RepLoc-1)||NewStr||Substr(String,RepLoc+2);
+    else OutStr=String||' '||NewStr;
+    rc=lineout(OutFile, OutStr);
+    rc=lineout(OutFile);
+  end
+
+  when (Function='DATEMONTH') then do
+    NewStr=FORMAT(SUBSTR(DATE('S'), 5, 2));
+    RepLoc=Pos('%A', String);
+    if (RepLoc>0) then OutStr=Substr(String,1,RepLoc-1)||NewStr||Substr(String,RepLoc+2);
+    else OutStr=String||' '||NewStr;
+    rc=lineout(OutFile, OutStr);
+    rc=lineout(OutFile);
+  end
+
+  when (Function='DATEDAY') then do
+    NewStr=FORMAT(SUBSTR(DATE('S'), 7, 2));
+    RepLoc=Pos('%A', String);
+    if (RepLoc>0) then OutStr=Substr(String,1,RepLoc-1)||NewStr||Substr(String,RepLoc+2);
+    else OutStr=String||' '||NewStr;
+    rc=lineout(OutFile, OutStr);
+    rc=lineout(OutFile);
+  end
+
+  when (Function="VERSIONMAJOR") then do
+    parse var Parm1 Major'.'Minor'.'Revision;
+    NewStr=FORMAT(Major);
+    RepLoc=Pos('%A', String);
+    if (RepLoc>0) then OutStr=Substr(String,1,RepLoc-1)||NewStr||Substr(String,RepLoc+2);
+    else OutStr=String||' '||NewStr;
+    rc=lineout(OutFile, OutStr);
+    rc=lineout(OutFile);
+  end
+
+  when (Function="VERSIONMINOR") then do
+    parse var Parm1 Major'.'Minor'.'Revision;
+    NewStr=FORMAT(Minor);
+    RepLoc=Pos('%A', String);
+    if (RepLoc>0) then OutStr=Substr(String,1,RepLoc-1)||NewStr||Substr(String,RepLoc+2);
+    else OutStr=String||' '||NewStr;
+    rc=lineout(OutFile, OutStr);
+    rc=lineout(OutFile);
+  end
+
+  when (Function="VERSIONREVISION") then do
+    parse var Parm1 Major'.'Minor'.'Revision;
+    NewStr=FORMAT(Revision);
+    RepLoc=Pos('%A', String);
+    if (RepLoc>0) then OutStr=Substr(String,1,RepLoc-1)||NewStr||Substr(String,RepLoc+2);
+    else OutStr=String||' '||NewStr;
+    rc=lineout(OutFile, OutStr);
+    rc=lineout(OutFile);
+  end
+
+  when (Function="FILE") then do
+    do while LINES(String) <> 0;
+      rc=LINEOUT(OutFile, LINEIN(String));
+    end;
+    rc=stream(String,'c','close');
+    rc=lineout(OutFile);
+  end
+
+  when (Function="VAR") then do
+    Type=SUBSTR(Function,4,1);
+    NewStr = '';
+    Len=LENGTH(Parm1);
+    do while LINES(Parm2) <> 0;
+      Line1=LINEIN(Parm2);
+      if (LEFT(Line1, Len) = Parm1) then do
+        NewStr=SUBSTR(Line1, Len+1);
+        LEAVE;
+      end
+    end;
+    rc=stream(Parm2,'c','close');
+
+    RepLoc=Pos('%A', String);
+    if (RepLoc>0) then OutStr=Substr(String,1,RepLoc-1)||NewStr||Substr(String,RepLoc+2);
+    else OutStr=String||' '||NewStr;
+    rc=LINEOUT(OutFile, OutStr);
+    rc=lineout(OutFile);
+  end
+
+  otherwise do
+    say "AddToFile: Unrecognized command: "||Function;
+    exit 1;
+  end
+end
+
+exit 0;
+
+SvnVersion:
+  File = ARG(1);
+  '@svn info 2>&1 | rxqueue'
+  do while (QUEUED() > 0)
+    PARSE PULL Line1':'Line2
+    if (Line1 = "Last Changed Rev") then leave;
+    Line2 = "";
+  end
+  do while (QUEUED() <> 0); PULL; end;
+  Return(STRIP(Line2,'B'));
+
Index: /tags/2.02/tools/LICENSE
===================================================================
--- /tags/2.02/tools/LICENSE	(revision 189)
+++ /tags/2.02/tools/LICENSE	(revision 189)
@@ -0,0 +1,674 @@
+                    GNU GENERAL PUBLIC LICENSE
+                       Version 3, 29 June 2007
+
+ Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+                            Preamble
+
+  The GNU General Public License is a free, copyleft license for
+software and other kinds of works.
+
+  The licenses for most software and other practical works are designed
+to take away your freedom to share and change the works.  By contrast,
+the GNU General Public License is intended to guarantee your freedom to
+share and change all versions of a program--to make sure it remains free
+software for all its users.  We, the Free Software Foundation, use the
+GNU General Public License for most of our software; it applies also to
+any other work released this way by its authors.  You can apply it to
+your programs, too.
+
+  When we speak of free software, we are referring to freedom, not
+price.  Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+them if you wish), that you receive source code or can get it if you
+want it, that you can change the software or use pieces of it in new
+free programs, and that you know you can do these things.
+
+  To protect your rights, we need to prevent others from denying you
+these rights or asking you to surrender the rights.  Therefore, you have
+certain responsibilities if you distribute copies of the software, or if
+you modify it: responsibilities to respect the freedom of others.
+
+  For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must pass on to the recipients the same
+freedoms that you received.  You must make sure that they, too, receive
+or can get the source code.  And you must show them these terms so they
+know their rights.
+
+  Developers that use the GNU GPL protect your rights with two steps:
+(1) assert copyright on the software, and (2) offer you this License
+giving you legal permission to copy, distribute and/or modify it.
+
+  For the developers' and authors' protection, the GPL clearly explains
+that there is no warranty for this free software.  For both users' and
+authors' sake, the GPL requires that modified versions be marked as
+changed, so that their problems will not be attributed erroneously to
+authors of previous versions.
+
+  Some devices are designed to deny users access to install or run
+modified versions of the software inside them, although the manufacturer
+can do so.  This is fundamentally incompatible with the aim of
+protecting users' freedom to change the software.  The systematic
+pattern of such abuse occurs in the area of products for individuals to
+use, which is precisely where it is most unacceptable.  Therefore, we
+have designed this version of the GPL to prohibit the practice for those
+products.  If such problems arise substantially in other domains, we
+stand ready to extend this provision to those domains in future versions
+of the GPL, as needed to protect the freedom of users.
+
+  Finally, every program is threatened constantly by software patents.
+States should not allow patents to restrict development and use of
+software on general-purpose computers, but in those that do, we wish to
+avoid the special danger that patents applied to a free program could
+make it effectively proprietary.  To prevent this, the GPL assures that
+patents cannot be used to render the program non-free.
+
+  The precise terms and conditions for copying, distribution and
+modification follow.
+
+                       TERMS AND CONDITIONS
+
+  0. Definitions.
+
+  "This License" refers to version 3 of the GNU General Public License.
+
+  "Copyright" also means copyright-like laws that apply to other kinds of
+works, such as semiconductor masks.
+
+  "The Program" refers to any copyrightable work licensed under this
+License.  Each licensee is addressed as "you".  "Licensees" and
+"recipients" may be individuals or organizations.
+
+  To "modify" a work means to copy from or adapt all or part of the work
+in a fashion requiring copyright permission, other than the making of an
+exact copy.  The resulting work is called a "modified version" of the
+earlier work or a work "based on" the earlier work.
+
+  A "covered work" means either the unmodified Program or a work based
+on the Program.
+
+  To "propagate" a work means to do anything with it that, without
+permission, would make you directly or secondarily liable for
+infringement under applicable copyright law, except executing it on a
+computer or modifying a private copy.  Propagation includes copying,
+distribution (with or without modification), making available to the
+public, and in some countries other activities as well.
+
+  To "convey" a work means any kind of propagation that enables other
+parties to make or receive copies.  Mere interaction with a user through
+a computer network, with no transfer of a copy, is not conveying.
+
+  An interactive user interface displays "Appropriate Legal Notices"
+to the extent that it includes a convenient and prominently visible
+feature that (1) displays an appropriate copyright notice, and (2)
+tells the user that there is no warranty for the work (except to the
+extent that warranties are provided), that licensees may convey the
+work under this License, and how to view a copy of this License.  If
+the interface presents a list of user commands or options, such as a
+menu, a prominent item in the list meets this criterion.
+
+  1. Source Code.
+
+  The "source code" for a work means the preferred form of the work
+for making modifications to it.  "Object code" means any non-source
+form of a work.
+
+  A "Standard Interface" means an interface that either is an official
+standard defined by a recognized standards body, or, in the case of
+interfaces specified for a particular programming language, one that
+is widely used among developers working in that language.
+
+  The "System Libraries" of an executable work include anything, other
+than the work as a whole, that (a) is included in the normal form of
+packaging a Major Component, but which is not part of that Major
+Component, and (b) serves only to enable use of the work with that
+Major Component, or to implement a Standard Interface for which an
+implementation is available to the public in source code form.  A
+"Major Component", in this context, means a major essential component
+(kernel, window system, and so on) of the specific operating system
+(if any) on which the executable work runs, or a compiler used to
+produce the work, or an object code interpreter used to run it.
+
+  The "Corresponding Source" for a work in object code form means all
+the source code needed to generate, install, and (for an executable
+work) run the object code and to modify the work, including scripts to
+control those activities.  However, it does not include the work's
+System Libraries, or general-purpose tools or generally available free
+programs which are used unmodified in performing those activities but
+which are not part of the work.  For example, Corresponding Source
+includes interface definition files associated with source files for
+the work, and the source code for shared libraries and dynamically
+linked subprograms that the work is specifically designed to require,
+such as by intimate data communication or control flow between those
+subprograms and other parts of the work.
+
+  The Corresponding Source need not include anything that users
+can regenerate automatically from other parts of the Corresponding
+Source.
+
+  The Corresponding Source for a work in source code form is that
+same work.
+
+  2. Basic Permissions.
+
+  All rights granted under this License are granted for the term of
+copyright on the Program, and are irrevocable provided the stated
+conditions are met.  This License explicitly affirms your unlimited
+permission to run the unmodified Program.  The output from running a
+covered work is covered by this License only if the output, given its
+content, constitutes a covered work.  This License acknowledges your
+rights of fair use or other equivalent, as provided by copyright law.
+
+  You may make, run and propagate covered works that you do not
+convey, without conditions so long as your license otherwise remains
+in force.  You may convey covered works to others for the sole purpose
+of having them make modifications exclusively for you, or provide you
+with facilities for running those works, provided that you comply with
+the terms of this License in conveying all material for which you do
+not control copyright.  Those thus making or running the covered works
+for you must do so exclusively on your behalf, under your direction
+and control, on terms that prohibit them from making any copies of
+your copyrighted material outside their relationship with you.
+
+  Conveying under any other circumstances is permitted solely under
+the conditions stated below.  Sublicensing is not allowed; section 10
+makes it unnecessary.
+
+  3. Protecting Users' Legal Rights From Anti-Circumvention Law.
+
+  No covered work shall be deemed part of an effective technological
+measure under any applicable law fulfilling obligations under article
+11 of the WIPO copyright treaty adopted on 20 December 1996, or
+similar laws prohibiting or restricting circumvention of such
+measures.
+
+  When you convey a covered work, you waive any legal power to forbid
+circumvention of technological measures to the extent such circumvention
+is effected by exercising rights under this License with respect to
+the covered work, and you disclaim any intention to limit operation or
+modification of the work as a means of enforcing, against the work's
+users, your or third parties' legal rights to forbid circumvention of
+technological measures.
+
+  4. Conveying Verbatim Copies.
+
+  You may convey verbatim copies of the Program's source code as you
+receive it, in any medium, provided that you conspicuously and
+appropriately publish on each copy an appropriate copyright notice;
+keep intact all notices stating that this License and any
+non-permissive terms added in accord with section 7 apply to the code;
+keep intact all notices of the absence of any warranty; and give all
+recipients a copy of this License along with the Program.
+
+  You may charge any price or no price for each copy that you convey,
+and you may offer support or warranty protection for a fee.
+
+  5. Conveying Modified Source Versions.
+
+  You may convey a work based on the Program, or the modifications to
+produce it from the Program, in the form of source code under the
+terms of section 4, provided that you also meet all of these conditions:
+
+    a) The work must carry prominent notices stating that you modified
+    it, and giving a relevant date.
+
+    b) The work must carry prominent notices stating that it is
+    released under this License and any conditions added under section
+    7.  This requirement modifies the requirement in section 4 to
+    "keep intact all notices".
+
+    c) You must license the entire work, as a whole, under this
+    License to anyone who comes into possession of a copy.  This
+    License will therefore apply, along with any applicable section 7
+    additional terms, to the whole of the work, and all its parts,
+    regardless of how they are packaged.  This License gives no
+    permission to license the work in any other way, but it does not
+    invalidate such permission if you have separately received it.
+
+    d) If the work has interactive user interfaces, each must display
+    Appropriate Legal Notices; however, if the Program has interactive
+    interfaces that do not display Appropriate Legal Notices, your
+    work need not make them do so.
+
+  A compilation of a covered work with other separate and independent
+works, which are not by their nature extensions of the covered work,
+and which are not combined with it such as to form a larger program,
+in or on a volume of a storage or distribution medium, is called an
+"aggregate" if the compilation and its resulting copyright are not
+used to limit the access or legal rights of the compilation's users
+beyond what the individual works permit.  Inclusion of a covered work
+in an aggregate does not cause this License to apply to the other
+parts of the aggregate.
+
+  6. Conveying Non-Source Forms.
+
+  You may convey a covered work in object code form under the terms
+of sections 4 and 5, provided that you also convey the
+machine-readable Corresponding Source under the terms of this License,
+in one of these ways:
+
+    a) Convey the object code in, or embodied in, a physical product
+    (including a physical distribution medium), accompanied by the
+    Corresponding Source fixed on a durable physical medium
+    customarily used for software interchange.
+
+    b) Convey the object code in, or embodied in, a physical product
+    (including a physical distribution medium), accompanied by a
+    written offer, valid for at least three years and valid for as
+    long as you offer spare parts or customer support for that product
+    model, to give anyone who possesses the object code either (1) a
+    copy of the Corresponding Source for all the software in the
+    product that is covered by this License, on a durable physical
+    medium customarily used for software interchange, for a price no
+    more than your reasonable cost of physically performing this
+    conveying of source, or (2) access to copy the
+    Corresponding Source from a network server at no charge.
+
+    c) Convey individual copies of the object code with a copy of the
+    written offer to provide the Corresponding Source.  This
+    alternative is allowed only occasionally and noncommercially, and
+    only if you received the object code with such an offer, in accord
+    with subsection 6b.
+
+    d) Convey the object code by offering access from a designated
+    place (gratis or for a charge), and offer equivalent access to the
+    Corresponding Source in the same way through the same place at no
+    further charge.  You need not require recipients to copy the
+    Corresponding Source along with the object code.  If the place to
+    copy the object code is a network server, the Corresponding Source
+    may be on a different server (operated by you or a third party)
+    that supports equivalent copying facilities, provided you maintain
+    clear directions next to the object code saying where to find the
+    Corresponding Source.  Regardless of what server hosts the
+    Corresponding Source, you remain obligated to ensure that it is
+    available for as long as needed to satisfy these requirements.
+
+    e) Convey the object code using peer-to-peer transmission, provided
+    you inform other peers where the object code and Corresponding
+    Source of the work are being offered to the general public at no
+    charge under subsection 6d.
+
+  A separable portion of the object code, whose source code is excluded
+from the Corresponding Source as a System Library, need not be
+included in conveying the object code work.
+
+  A "User Product" is either (1) a "consumer product", which means any
+tangible personal property which is normally used for personal, family,
+or household purposes, or (2) anything designed or sold for incorporation
+into a dwelling.  In determining whether a product is a consumer product,
+doubtful cases shall be resolved in favor of coverage.  For a particular
+product received by a particular user, "normally used" refers to a
+typical or common use of that class of product, regardless of the status
+of the particular user or of the way in which the particular user
+actually uses, or expects or is expected to use, the product.  A product
+is a consumer product regardless of whether the product has substantial
+commercial, industrial or non-consumer uses, unless such uses represent
+the only significant mode of use of the product.
+
+  "Installation Information" for a User Product means any methods,
+procedures, authorization keys, or other information required to install
+and execute modified versions of a covered work in that User Product from
+a modified version of its Corresponding Source.  The information must
+suffice to ensure that the continued functioning of the modified object
+code is in no case prevented or interfered with solely because
+modification has been made.
+
+  If you convey an object code work under this section in, or with, or
+specifically for use in, a User Product, and the conveying occurs as
+part of a transaction in which the right of possession and use of the
+User Product is transferred to the recipient in perpetuity or for a
+fixed term (regardless of how the transaction is characterized), the
+Corresponding Source conveyed under this section must be accompanied
+by the Installation Information.  But this requirement does not apply
+if neither you nor any third party retains the ability to install
+modified object code on the User Product (for example, the work has
+been installed in ROM).
+
+  The requirement to provide Installation Information does not include a
+requirement to continue to provide support service, warranty, or updates
+for a work that has been modified or installed by the recipient, or for
+the User Product in which it has been modified or installed.  Access to a
+network may be denied when the modification itself materially and
+adversely affects the operation of the network or violates the rules and
+protocols for communication across the network.
+
+  Corresponding Source conveyed, and Installation Information provided,
+in accord with this section must be in a format that is publicly
+documented (and with an implementation available to the public in
+source code form), and must require no special password or key for
+unpacking, reading or copying.
+
+  7. Additional Terms.
+
+  "Additional permissions" are terms that supplement the terms of this
+License by making exceptions from one or more of its conditions.
+Additional permissions that are applicable to the entire Program shall
+be treated as though they were included in this License, to the extent
+that they are valid under applicable law.  If additional permissions
+apply only to part of the Program, that part may be used separately
+under those permissions, but the entire Program remains governed by
+this License without regard to the additional permissions.
+
+  When you convey a copy of a covered work, you may at your option
+remove any additional permissions from that copy, or from any part of
+it.  (Additional permissions may be written to require their own
+removal in certain cases when you modify the work.)  You may place
+additional permissions on material, added by you to a covered work,
+for which you have or can give appropriate copyright permission.
+
+  Notwithstanding any other provision of this License, for material you
+add to a covered work, you may (if authorized by the copyright holders of
+that material) supplement the terms of this License with terms:
+
+    a) Disclaiming warranty or limiting liability differently from the
+    terms of sections 15 and 16 of this License; or
+
+    b) Requiring preservation of specified reasonable legal notices or
+    author attributions in that material or in the Appropriate Legal
+    Notices displayed by works containing it; or
+
+    c) Prohibiting misrepresentation of the origin of that material, or
+    requiring that modified versions of such material be marked in
+    reasonable ways as different from the original version; or
+
+    d) Limiting the use for publicity purposes of names of licensors or
+    authors of the material; or
+
+    e) Declining to grant rights under trademark law for use of some
+    trade names, trademarks, or service marks; or
+
+    f) Requiring indemnification of licensors and authors of that
+    material by anyone who conveys the material (or modified versions of
+    it) with contractual assumptions of liability to the recipient, for
+    any liability that these contractual assumptions directly impose on
+    those licensors and authors.
+
+  All other non-permissive additional terms are considered "further
+restrictions" within the meaning of section 10.  If the Program as you
+received it, or any part of it, contains a notice stating that it is
+governed by this License along with a term that is a further
+restriction, you may remove that term.  If a license document contains
+a further restriction but permits relicensing or conveying under this
+License, you may add to a covered work material governed by the terms
+of that license document, provided that the further restriction does
+not survive such relicensing or conveying.
+
+  If you add terms to a covered work in accord with this section, you
+must place, in the relevant source files, a statement of the
+additional terms that apply to those files, or a notice indicating
+where to find the applicable terms.
+
+  Additional terms, permissive or non-permissive, may be stated in the
+form of a separately written license, or stated as exceptions;
+the above requirements apply either way.
+
+  8. Termination.
+
+  You may not propagate or modify a covered work except as expressly
+provided under this License.  Any attempt otherwise to propagate or
+modify it is void, and will automatically terminate your rights under
+this License (including any patent licenses granted under the third
+paragraph of section 11).
+
+  However, if you cease all violation of this License, then your
+license from a particular copyright holder is reinstated (a)
+provisionally, unless and until the copyright holder explicitly and
+finally terminates your license, and (b) permanently, if the copyright
+holder fails to notify you of the violation by some reasonable means
+prior to 60 days after the cessation.
+
+  Moreover, your license from a particular copyright holder is
+reinstated permanently if the copyright holder notifies you of the
+violation by some reasonable means, this is the first time you have
+received notice of violation of this License (for any work) from that
+copyright holder, and you cure the violation prior to 30 days after
+your receipt of the notice.
+
+  Termination of your rights under this section does not terminate the
+licenses of parties who have received copies or rights from you under
+this License.  If your rights have been terminated and not permanently
+reinstated, you do not qualify to receive new licenses for the same
+material under section 10.
+
+  9. Acceptance Not Required for Having Copies.
+
+  You are not required to accept this License in order to receive or
+run a copy of the Program.  Ancillary propagation of a covered work
+occurring solely as a consequence of using peer-to-peer transmission
+to receive a copy likewise does not require acceptance.  However,
+nothing other than this License grants you permission to propagate or
+modify any covered work.  These actions infringe copyright if you do
+not accept this License.  Therefore, by modifying or propagating a
+covered work, you indicate your acceptance of this License to do so.
+
+  10. Automatic Licensing of Downstream Recipients.
+
+  Each time you convey a covered work, the recipient automatically
+receives a license from the original licensors, to run, modify and
+propagate that work, subject to this License.  You are not responsible
+for enforcing compliance by third parties with this License.
+
+  An "entity transaction" is a transaction transferring control of an
+organization, or substantially all assets of one, or subdividing an
+organization, or merging organizations.  If propagation of a covered
+work results from an entity transaction, each party to that
+transaction who receives a copy of the work also receives whatever
+licenses to the work the party's predecessor in interest had or could
+give under the previous paragraph, plus a right to possession of the
+Corresponding Source of the work from the predecessor in interest, if
+the predecessor has it or can get it with reasonable efforts.
+
+  You may not impose any further restrictions on the exercise of the
+rights granted or affirmed under this License.  For example, you may
+not impose a license fee, royalty, or other charge for exercise of
+rights granted under this License, and you may not initiate litigation
+(including a cross-claim or counterclaim in a lawsuit) alleging that
+any patent claim is infringed by making, using, selling, offering for
+sale, or importing the Program or any portion of it.
+
+  11. Patents.
+
+  A "contributor" is a copyright holder who authorizes use under this
+License of the Program or a work on which the Program is based.  The
+work thus licensed is called the contributor's "contributor version".
+
+  A contributor's "essential patent claims" are all patent claims
+owned or controlled by the contributor, whether already acquired or
+hereafter acquired, that would be infringed by some manner, permitted
+by this License, of making, using, or selling its contributor version,
+but do not include claims that would be infringed only as a
+consequence of further modification of the contributor version.  For
+purposes of this definition, "control" includes the right to grant
+patent sublicenses in a manner consistent with the requirements of
+this License.
+
+  Each contributor grants you a non-exclusive, worldwide, royalty-free
+patent license under the contributor's essential patent claims, to
+make, use, sell, offer for sale, import and otherwise run, modify and
+propagate the contents of its contributor version.
+
+  In the following three paragraphs, a "patent license" is any express
+agreement or commitment, however denominated, not to enforce a patent
+(such as an express permission to practice a patent or covenant not to
+sue for patent infringement).  To "grant" such a patent license to a
+party means to make such an agreement or commitment not to enforce a
+patent against the party.
+
+  If you convey a covered work, knowingly relying on a patent license,
+and the Corresponding Source of the work is not available for anyone
+to copy, free of charge and under the terms of this License, through a
+publicly available network server or other readily accessible means,
+then you must either (1) cause the Corresponding Source to be so
+available, or (2) arrange to deprive yourself of the benefit of the
+patent license for this particular work, or (3) arrange, in a manner
+consistent with the requirements of this License, to extend the patent
+license to downstream recipients.  "Knowingly relying" means you have
+actual knowledge that, but for the patent license, your conveying the
+covered work in a country, or your recipient's use of the covered work
+in a country, would infringe one or more identifiable patents in that
+country that you have reason to believe are valid.
+
+  If, pursuant to or in connection with a single transaction or
+arrangement, you convey, or propagate by procuring conveyance of, a
+covered work, and grant a patent license to some of the parties
+receiving the covered work authorizing them to use, propagate, modify
+or convey a specific copy of the covered work, then the patent license
+you grant is automatically extended to all recipients of the covered
+work and works based on it.
+
+  A patent license is "discriminatory" if it does not include within
+the scope of its coverage, prohibits the exercise of, or is
+conditioned on the non-exercise of one or more of the rights that are
+specifically granted under this License.  You may not convey a covered
+work if you are a party to an arrangement with a third party that is
+in the business of distributing software, under which you make payment
+to the third party based on the extent of your activity of conveying
+the work, and under which the third party grants, to any of the
+parties who would receive the covered work from you, a discriminatory
+patent license (a) in connection with copies of the covered work
+conveyed by you (or copies made from those copies), or (b) primarily
+for and in connection with specific products or compilations that
+contain the covered work, unless you entered into that arrangement,
+or that patent license was granted, prior to 28 March 2007.
+
+  Nothing in this License shall be construed as excluding or limiting
+any implied license or other defenses to infringement that may
+otherwise be available to you under applicable patent law.
+
+  12. No Surrender of Others' Freedom.
+
+  If conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License.  If you cannot convey a
+covered work so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you may
+not convey it at all.  For example, if you agree to terms that obligate you
+to collect a royalty for further conveying from those to whom you convey
+the Program, the only way you could satisfy both those terms and this
+License would be to refrain entirely from conveying the Program.
+
+  13. Use with the GNU Affero General Public License.
+
+  Notwithstanding any other provision of this License, you have
+permission to link or combine any covered work with a work licensed
+under version 3 of the GNU Affero General Public License into a single
+combined work, and to convey the resulting work.  The terms of this
+License will continue to apply to the part which is the covered work,
+but the special requirements of the GNU Affero General Public License,
+section 13, concerning interaction through a network will apply to the
+combination as such.
+
+  14. Revised Versions of this License.
+
+  The Free Software Foundation may publish revised and/or new versions of
+the GNU General Public License from time to time.  Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+  Each version is given a distinguishing version number.  If the
+Program specifies that a certain numbered version of the GNU General
+Public License "or any later version" applies to it, you have the
+option of following the terms and conditions either of that numbered
+version or of any later version published by the Free Software
+Foundation.  If the Program does not specify a version number of the
+GNU General Public License, you may choose any version ever published
+by the Free Software Foundation.
+
+  If the Program specifies that a proxy can decide which future
+versions of the GNU General Public License can be used, that proxy's
+public statement of acceptance of a version permanently authorizes you
+to choose that version for the Program.
+
+  Later license versions may give you additional or different
+permissions.  However, no additional obligations are imposed on any
+author or copyright holder as a result of your choosing to follow a
+later version.
+
+  15. Disclaimer of Warranty.
+
+  THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
+APPLICABLE LAW.  EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
+HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
+OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
+THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE.  THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
+IS WITH YOU.  SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
+ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
+
+  16. Limitation of Liability.
+
+  IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
+THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
+GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
+USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
+DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
+PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
+EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
+SUCH DAMAGES.
+
+  17. Interpretation of Sections 15 and 16.
+
+  If the disclaimer of warranty and limitation of liability provided
+above cannot be given local legal effect according to their terms,
+reviewing courts shall apply local law that most closely approximates
+an absolute waiver of all civil liability in connection with the
+Program, unless a warranty or assumption of liability accompanies a
+copy of the Program in return for a fee.
+
+                     END OF TERMS AND CONDITIONS
+
+            How to Apply These Terms to Your New Programs
+
+  If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+  To do so, attach the following notices to the program.  It is safest
+to attach them to the start of each source file to most effectively
+state the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+    <one line to give the program's name and a brief idea of what it does.>
+    Copyright (C) <year>  <name of author>
+
+    This program is free software: you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+Also add information on how to contact you by electronic and paper mail.
+
+  If the program does terminal interaction, make it output a short
+notice like this when it starts in an interactive mode:
+
+    <program>  Copyright (C) <year>  <name of author>
+    This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+    This is free software, and you are welcome to redistribute it
+    under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License.  Of course, your program's commands
+might be different; for a GUI interface, you would use an "about box".
+
+  You should also get your employer (if you work as a programmer) or school,
+if any, to sign a "copyright disclaimer" for the program, if necessary.
+For more information on this, and how to apply and follow the GNU GPL, see
+<http://www.gnu.org/licenses/>.
+
+  The GNU General Public License does not permit incorporating your program
+into proprietary programs.  If your program is a subroutine library, you
+may consider it more useful to permit linking proprietary applications with
+the library.  If this is what you want to do, use the GNU Lesser General
+Public License instead of this License.  But first, please read
+<http://www.gnu.org/philosophy/why-not-lgpl.html>.
Index: /tags/2.02/tools/ahci.wis
===================================================================
--- /tags/2.02/tools/ahci.wis	(revision 189)
+++ /tags/2.02/tools/ahci.wis	(revision 189)
@@ -0,0 +1,95 @@
+<WARPIN VERSION="1.0.15" OS="os2_45x">
+ <HEAD>
+  <TITLE>=("TITLE")</TITLE>
+  <REXX NAME=TITLE>
+   MyTitle="AHCI Driver for OS/2";
+
+   BootDrive = SysBootDrive();
+   ConfigSys = BootDrive||'\config.sys';
+   OsDir=VALUE('OSDIR',,'OS2ENVIRONMENT');
+   if (OsDir='') then OsDir='?:\OS2';
+   OsDocTarget='TARGET="'||OsDir||'\DOC\AHCI" LONGFILENAMES SELECT';
+   if (TRANSLATE(RIGHT(OsDir,3))='OS2') then do
+     OsAppTarget='TARGET="'||OsDir||'\APPS" LONGFILENAMES SELECT';
+   end
+   else do
+     OsAppTarget='TARGET="'||OsDir||'\BIN" LONGFILENAMES SELECT';
+   end
+
+   /* find AHCI line, if any */
+   SearchString='BASEDEV=OS2AHCI.ADD';
+   If (SysFileSearch(SearchString,ConfigSys,'test.') \= 0) then test.0 = 0;
+   ahci_ln = 0;
+   do i = 1 to test.0
+     If (TRANSLATE(WORD(test.i, 1)) \= SearchString) then iterate;
+     ahci_ln = i;
+     leave;
+   end
+
+   /* find Dani line, if any */
+   SearchString='BASEDEV=DANIS506.ADD';
+   If (SysFileSearch(SearchString,ConfigSys,'test.') \= 0) then test.0 = 0;
+   dani_ln = 0;
+   do i = 1 to test.0
+     If (TRANSLATE(WORD(test.i, 1)) \= SearchString) then iterate;
+     dani_ln = i;
+     leave;
+   end
+
+   ConfigSysModify = '';
+   if (ahci_ln=0) then do
+    if (dani_ln=0) then do
+     ConfigSysModify='CONFIGSYS="BASEDEV=OS2AHCI.ADD | UNIQUE(OS2AHCI.ADD) ADDTOP"';
+    end
+    else do
+     ConfigSysModify='CONFIGSYS="BASEDEV=OS2AHCI.ADD | UNIQUE(OS2AHCI.ADD) ADDBEFORE(DANIS506.ADD)"';
+    end
+   end
+
+   rc = WirexxPutEnv('MyTitle',MyTitle);
+   rc = WirexxPutEnv('ConfigSysModify',ConfigSysModify);
+   rc = WirexxPutEnv('OsAppTarget',OsAppTarget);
+   rc = WirexxPutEnv('OsDocTarget',OsDocTarget);
+
+   Return MyTitle;
+  </REXX>
+  <REXX NAME=GetEnv>
+   Parse Arg EnvVar .
+   Return WirexxGetEnv(EnvVar);
+  </REXX>
+
+  <PCK INDEX=1 PACKAGEID="Arca Noae\AHCI\Driver\X\X\X"
+   TITLE="AHCI Driver"
+   TARGET="?:\OS2\BOOT" FIXED LONGFILENAMES SELECT
+   =("GetEnv ConfigSysModify")
+   >The AHCI Driver
+  </PCK>
+
+  <PCK INDEX=2 PACKAGEID="Arca Noae\AHCI\Tools\X\X\X"
+   TITLE="AHCI Tools"
+   =("GetEnv OsAppTarget")
+   REQUIRES=1
+   >The SmartAHCI tool
+  </PCK>
+
+  <PCK INDEX=20 PACKAGEID="Arca Noae\AHCI\Documentation\X\X\X"
+   TITLE="AHCI Documentation"
+   =("GetEnv OsDocTarget")
+   >ReadMe and License
+  </PCK>
+</HEAD>
+ <BODY>
+  <PAGE INDEX=1 TYPE=README>
+   <TEXT>Welcome to the =("GetEnv MyTitle")</TEXT>
+   <README EXTRACTFROMPCK="20" FORMAT="PLAIN">ReadMe.txt</README>
+  </PAGE>
+  <PAGE INDEX=2 TYPE=CONTAINER>
+   <TEXT>Select the packages you want to install.
+   </TEXT>
+  </PAGE>
+  <PAGE INDEX=3 TYPE=CONFIGURE>
+    <TEXT>Press "Install" to begin installing this archive.</TEXT>
+    <NEXTBUTTON TARGET=0>I~nstall</NEXTBUTTON>
+  </PAGE>
+ </BODY>
+</WARPIN>
Index: /tags/2.02/tools/update.cmd
===================================================================
--- /tags/2.02/tools/update.cmd	(revision 189)
+++ /tags/2.02/tools/update.cmd	(revision 189)
@@ -0,0 +1,10 @@
+/* Update the driver */
+call RxFuncAdd 'SysLoadFuncs', 'RexxUtil', 'SysLoadFuncs';
+call SysLoadFuncs;
+parse arg BootDrive;
+
+if (BootDrive='') then BootDrive=SysBootDrive();
+
+address CMD 'copy /b OS2AHCI.ADD '||BootDrive||'\OS2\BOOT';
+address CMD 'copy /b OS2AHCI.SYM '||BootDrive||'\OS2\BOOT';
+
Index: /tags/2.02/tools/wat2map.cmd
===================================================================
--- /tags/2.02/tools/wat2map.cmd	(revision 189)
+++ /tags/2.02/tools/wat2map.cmd	(revision 189)
@@ -0,0 +1,297 @@
+/* $Id: wat2map.cmd,v 1.2 2002/04/26 23:09:44 smilcke Exp $ */
+
+/* SCCSID = src/dev/mme/pciaudio/wat2map.cmd, pciaudio, c.basedd 99/08/20 */
+/****************************************************************************
+ *                                                                          *
+ * Copyright (c) IBM Corporation 1994 - 1997.                               *
+ *                                                                          *
+ * The following IBM OS/2 source code is provided to you solely for the     *
+ * the purpose of assisting you in your development of OS/2 device drivers. *
+ * You may use this code in accordance with the IBM License Agreement       *
+ * provided in the IBM Device Driver Source Kit for OS/2.                   *
+ *                                                                          *
+ ****************************************************************************/
+
+/* 05 Jul 05 SHL - Correct do/end errors, close files
+   05 Jul 05 SHL - Partial 16/32 bit support (see f32bit)
+*/
+
+/**@internal src/dev/mme/pciaudio/wat2map.cmd, pciaudio, c.basedd
+ * WAT2MAP - translate symbol map from Watcom format to MS format.
+ * @version 1.1
+ * @context
+ *  Unless otherwise noted, all interfaces are Ring-0, 16-bit, kernel stack.
+ * @notes
+ *  Usage:  WAT2MAP <watcom_mapfile >ms_mapfile
+ *               - or -
+ *          type watcom_mapfile | WAT2MAP >ms_mapfile
+ *
+ *          Reads from stdin, writes to stdout.  Will accept the Watcom map filename
+ *          as an argument (in place of reading from stdin).  Eg.,
+ *
+ *          WAT2MAP watcom_mapfile >ms_mapfile
+ *
+ *  Notes:
+ *    1.)  The symbol handling in the debug kernel won't work for some of the
+ *         characters used in the C++ name space.  WAT2MAP handles these symbols
+ *         as follows.
+ *             Scoping operator symbol '::' is translated to '__'.
+ *             Destructor symbol '~' is translated to 'd'.
+ *             Symbols for operators '::operator' are not provided.
+ *
+ *         Eg., for user defined class 'A', the symbol for constructor A::A is
+ *         translated to A__A, and the destructor symbol A::~A becomes A__dA, and
+ *         the assignment operator, 'A::operator =', is not translated.
+ *
+ *    2.)  Bug - C++ provides for defining multiple functions with same fn name
+ *         but different function signatures (different parameter lists).  This
+ *         utility just translates all the address / symbol combinations it finds,
+ *         so you can end up with several addresses for a given fn name.
+ * @history
+*/
+/* <End of helpText> - don't modify this string - used to flag end of help. */
+/****************************************************************************/
+
+CALL RXFUNCADD 'sysloadfuncs','rexxutil','sysloadfuncs'
+call sysloadfuncs
+
+f32bit=0;
+
+Parse Arg arg1 arg2 rest
+If (Length( arg1 ) = 0) | (Verify( arg1, '/-?' ) = 0) Then Do;
+   Do i = 1 to 1000
+      helpText = Sourceline(i)
+      If Pos( '<End of helpText>', helpText ) <> 0 Then Leave;       /* quit loop */
+      Say helpText
+   End;
+   Return
+End;
+If Length( arg2 ) = 0 Then Do;
+   Say " Way to go Beaver... How about an out-put file name ?"
+   Return
+End;
+mapFile = arg1          /* Can be Null, in which case we pull from stdin. */
+outFile = arg2
+
+/* erase outfile */  /* kill the old map file */
+rc=SysFileDelete(outfile)
+
+
+/*--- 1.  Find & translate module name.  ---*/
+Do While Lines( mapFile ) <> 0
+   watcomText = LineIn( mapFile )
+   Parse Value watcomText With "Executable Image: " fileName
+   If fileName <> "" Then Do;   /* Found match */
+      fileName = FILESPEC('name', fileName);
+      i = POS('.', fileName);
+      if (i>0) then i=i-1;
+      fileName = SUBSTR(fileName, 1, i);
+      call lineout outfile ,' '
+      call lineout outfile ,' ' || fileName;
+      call lineout outfile ,' '
+      Leave;                    /* Break from loop. */
+   End;
+End
+If Lines( mapFile ) = 0 Then Do;        /* If end of file ... */
+   Say "Error:  Expected to find line with text 'Executable Image:' "
+   Return
+End
+
+/*--- 2.  Skip the group definitions - Rob's notes say we don't need them. -*/
+
+/*--- 3.  Skip to the start of the segment table.  ---*/
+Do While Lines( mapFile ) <> 0
+   watcomText = LineIn( mapFile )
+   Parse Value watcomText With "Segment" header_2_3 "Address" header_5
+   If Strip( header_5 ) = "Size" Then Leave;      /* Found header line for Segment table. */
+End
+If Lines( mapFile ) = 0 Then Do;        /* If end of file ... */
+   Say "Error:  Expected to find line with text 'Segments ... Size' "
+   Return
+End
+junk = LineIn( mapFile )       /* Discard a couple lines of formatting. */
+junk = LineIn( mapFile )
+
+/*--- 4.  Translate segment table.  ---*/
+NeedHeader=1;
+
+Do While Lines( mapFile ) <> 0
+   watcomText = LineIn( mapFile )
+   Parse Value watcomText With segName className groupName address size .
+   If segName = "" Then Leave;          /* Empty line, break from loop. */
+   if (length(address)>9) then f32bit=1;
+   if (NeedHeader) then do;
+     if f32bit then call lineout outfile , " Start         Length     Name                   Class"
+     else call lineout outfile , " Start     Length     Name                   Class"
+     NeedHeader=0;
+   end;
+   if f32bit then
+     length = size || 'H  '
+   else
+     length = Substr( size, 4, 5 ) || 'H     '
+   segName = Left( segName, 23 )
+   call lineout outfile ,' ' || address || ' ' || length || segName || className
+End
+call lineout outfile ,' '     /* Extra line feed. */
+
+
+/*--- 5.  For all remaining lines in the input file:  if the line starts
+   with a 16:16 address, assume it's a symbol declaration and translate
+   it into MS format.  ---*/
+
+call lineout outfile ,'  Address         Publics by Value'
+/* call lineout outfile ,' '*/
+
+LineTbl.0=0;
+LineIX = 0;
+Segments.0=0;
+
+Do While Lines( mapFile ) <> 0
+   watcomText = LineIn( mapFile )
+   Parse Value watcomText With seg ':' ofs declaration
+   if (seg=0) then iterate;
+   if length(ofs) = 5 then
+     ofs = left(ofs, 4) || strip(translate(right(ofs, 1),, '+*'))
+   if length(ofs) = 9 then
+     ofs = left(ofs, 8) || strip(translate(right(ofs, 1),, '+*'))
+
+   is_Adress = (is_Hex(seg) = 1) & (is_Hex(ofs) = 1)
+   If (is_Adress = 1) Then Do;
+
+      SegIX = X2D(seg);
+      if (SegIX>Segments.0) then do;
+        do i=Segments.0 to SegIX;
+           Segments.i=0;
+        end;
+        Segments.0=SegIX;
+      end;
+      if (X2D(ofs)>Segments.SegIX) then Segments.SegIX=X2D(ofs);
+
+      if \ f32bit then
+        ofs = right(ofs, 4)
+
+      /*--- Haven't done the work to xlate operator symbols - skip the line. */
+      If Pos( '::operator', declaration ) <> 0 Then Iterate;
+
+      /*--- Strip any arguement list if this is a function prototype.  */
+      declaration = StripMatchedParen( declaration )
+
+      /*--- Strip array brackets if this is an array. */
+      sqBracket = Pos( '[', declaration );
+      If sqBracket <> 0
+         Then declaration = Substr(declaration, 1, sqBracket-1);
+
+      /*--- Strip leading tokens from the function name.
+         Eg., remove function return type, near/far, etc.  */
+      declaration = Word( declaration, Words(declaration) );
+
+      /*--- Strip any remaining parens around function name. ---*/
+      declaration = ReplaceSubstr( '(', ' ', declaration );
+      declaration = ReplaceSubstr( ')', ' ', declaration );
+
+      /*--- Debug kernel doesn't like symbol for scoping operator "::"
+         in symbol names.  Replace :: with double underscore "__". ---*/
+      declaration = ReplaceSubstr( '::', '__', declaration );
+
+      /*--- Debug kernel doesn't like symbol for destructor "~"
+         in symbol names.  Replace ~ with character "d" for "destructor.
+         Note destructor for a class will translate "A::~A" -> "A__dA". ---*/
+      declaration = ReplaceSubstr( '~', 'd', declaration );
+
+      /* remove any trailing underscore */
+      declaration = STRIP(declaration, 't', '_');
+
+      /* remove the first underscore (if any) */
+      if (LEFT(declaration,1) = '_') then do;
+        declaration = RIGHT(declaration, LENGTH(declaration)-1);
+      end;
+
+      fill = copies(' ', 7)
+      LineIX = LineIX + 1;
+      LineTbl.LineIX = seg || ':' || ofs || fill || declaration;
+      LineTbl.0 = LineIX;
+   End;
+End; /* End While through symbol section, end of input file. */
+
+/* add dummy symbols for empty segments */
+do i=1 to Segments.0;
+  if (Segments.i>0) then iterate;
+  LineIX = LineIX + 1;
+  if (f32bit) then LineTbl.LineIX = D2X(i,4)||':00000000       SEGMENT'||i||'_DUMMY';
+  else LineTbl.LineIX = D2X(i,4)||':0000       SEGMENT'||i||'_DUMMY';
+  LineTbl.0 = LineIX;
+end;
+
+/* add dummy end segment symbols for 32 bit symbols */
+if (f32bit) then do;
+  do i=1 to Segments.0;
+    if (Segments.i > X2D('10000')) then iterate;
+    LineIX = LineIX + 1;
+    LineTbl.LineIX = D2X(i,4)||':00010000       SEGMENT'||i||'_END';
+    LineTbl.0 = LineIX;
+  end;
+end;
+
+/* Sort the data */
+do i=2 to LineTbl.0;
+   t=LineTbl.i;
+   do j=i-1 to 1 by -1 while t<LineTbl.j;
+      k=j+1;
+      LineTbl.k=LineTbl.j;
+   end;
+   j=j+1;
+   LineTbl.j=t;
+end;
+
+/* output the data */
+do LineIX = 1 to LineTbl.0;
+  call lineout outfile , ' ' || LineTbl.LineIX;
+end;
+
+call stream mapfile, 'C', 'CLOSE'
+call lineout outfile
+
+Return;  /* End of program.  */
+
+/*--- Helper subroutines. ---*/
+
+StripMatchedParen:
+/* Strips matched "( )" from end of string.  Returns
+   a substring with the trailing, matched parens deleted. */
+
+   Parse Arg string
+
+   ixOpenParen = LastPos( "(", string );
+   ixCloseParen = LastPos( ")", Substr( string, 1, Length(string)-1 ));
+
+   If (ixOpenParen = 0)                     /* No match. */
+      Then Return string
+   Else If ixCloseParen < ixOpenParen       /* Found match, no imbedded "()". */
+      Then Return Substr( string, 1, ixOpenParen-1 )
+   Else Do;                                 /* Imbedded (), must skip over them. */
+      /* Parse Value string With first ixCloseParen+1 rest */
+      first = Substr( string, 1, ixCloseParen)
+      rest = Substr( string, ixCloseParen+1 )
+      string = StripMatchedParen( first ) || rest
+      Return StripMatchedParen( string )
+   End;
+
+
+ReplaceSubstr:
+/* Replaces oldPat (old pattern) with newPat (new pattern) in string. */
+
+   Parse Arg oldPat , newPat , string
+
+   ix = Pos( oldPat, string )
+   if ix <> 0 Then Do;
+      first = Substr( string, 1, ix-1 )
+      rest  = Substr( string, ix + Length( oldPat ) )
+      string = first || newPat || rest
+   End;
+   Return string
+
+
+is_Hex:
+/* Returns 1 if String is valid hex number, 0 otherwise. */
+   Parse Arg string
+   Return (Length(string) > 0) &  (Verify( string, '0123456789abcdefABCDEF' ) = 0)
