Index: /tags/1.12/src/include/devhelp.h
===================================================================
--- /tags/1.12/src/include/devhelp.h	(revision 97)
+++ /tags/1.12/src/include/devhelp.h	(revision 97)
@@ -0,0 +1,1089 @@
+/* DEVHELP.H - DevHelp header file for Watcom-based device drivers
+ *
+ * Markus Thielen 09/2010:
+ *    some modifications for OpenWatcom/os2ahci.add
+ *
+ * TODO: the Watcom build is broken - the resulting ADD does not work.
+ *
+*/
+
+#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;
+
+extern PFN Device_Help;
+
+#define DEVHELP_CALL \
+   "call dword ptr [Device_Help]" \
+   "jc   error" \
+   "sub  ax,ax" \
+   "error:" \
+   value [ax]
+
+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];
+
+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_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_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_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_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 far 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           0x8003
+#define WAIT_TIMED_OUT             0x8001
+
+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]" \
+   "jc   error" \
+   "mov  ax,0"       /* doesn't affect zero flag like sub ax,ax does */ \
+   "error:" \
+   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 DevHlp_ResetEventSem(ULONG hEvent, PULONG 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 = \
+   "int 3" \
+   "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_ProcessToGlobal = \
+   "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
+#endif
Index: /tags/1.12/src/lbatest/lbatest.c
===================================================================
--- /tags/1.12/src/lbatest/lbatest.c	(revision 97)
+++ /tags/1.12/src/lbatest/lbatest.c	(revision 97)
@@ -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/1.12/src/lbatest/readtest.c
===================================================================
--- /tags/1.12/src/lbatest/readtest.c	(revision 97)
+++ /tags/1.12/src/lbatest/readtest.c	(revision 97)
@@ -0,0 +1,233 @@
+/******************************************************************************
+
+  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 */
+  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/1.12/src/os2ahci/Makefile
===================================================================
--- /tags/1.12/src/os2ahci/Makefile	(revision 97)
+++ /tags/1.12/src/os2ahci/Makefile	(revision 97)
@@ -0,0 +1,137 @@
+###############################################################################
+# Makefile - makefile for os2ahci 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$(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   = os2ahci.add
+
+LIBS     = addcalls dhcalls doscalls slibcep rmcalls apmcalls
+
+SRCS     = init.asm libc.c os2ahci.c pci.c ahci.c ata.c atapi.c ctxhook.c \
+           apm.c ioctl.c
+
+OBJS     = init.obj libc.obj os2ahci.obj pci.obj ahci.obj ata.obj atapi.obj \
+           ctxhook.obj apm.obj ioctl.obj
+
+INCS     = os2ahci.h ahci.h version.h
+
+
+all: $(TARGET)
+
+clean:
+	rm -f $(OBJS) $(TARGET)
+
+
+###############################################################################
+# Object/source dependencies
+
+init.obj:     init.asm   Makefile
+
+libc.obj:     libc.c     Makefile $(INCS)
+
+os2ahci.obj:  os2ahci.c  Makefile $(INCS) bldday.h ioctl.h
+
+pci.obj:      pci.c      Makefile $(INCS)
+
+ahci.obj:     ahci.c     Makefile $(INCS) ata.h atapi.h
+
+ata.obj:      ata.c      Makefile $(INCS) ata.h
+
+atapi.obj:    atapi.c    Makefile $(INCS) ata.h atapi.h
+
+ctxhook.obj:  ctxhook.c  Makefile $(INCS) ata.h atapi.h
+
+apm.obj:      apm.c      Makefile $(INCS)
+
+ioctl.obj:    ioctl.c    Makefile $(INCS) atapi.h ioctl.h
+
+###############################################################################
+# 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)
+	etags.exe $(SRCS) $(INCS)
+
+bldday.h:
+	$(BLDDATE) > bldday.h
+
+.asm.obj:
+	$(AS) $(AFLAGS) $(AS_INCLUDE) $*.asm
+
+.c.obj:
+	$(CC) $(CFLAGS) $(CC_INCLUDE) $*.c
+
+os2ahci.def:
+	$(BLDLEVEL) os2ahci.def.template os2ahci.def version.h
+
+
+$(TARGET): $(OBJS) os2ahci.def Makefile
+	$(LD) $(LFLAGS) $(OBJS),$(TARGET),$*.map,$(LIB_DIRS) $(LIBS),$*.def
+	$(MAPSYM) os2ahci
+
+
Index: /tags/1.12/src/os2ahci/README
===================================================================
--- /tags/1.12/src/os2ahci/README	(revision 97)
+++ /tags/1.12/src/os2ahci/README	(revision 97)
@@ -0,0 +1,272 @@
+====================================================================
+                      eComStation AHCI Driver
+====================================================================
+
+
+Introduction
+============
+
+OS2AHCI is an AHCI driver for eComStation. It supports both ATA and
+ATAPI devices in a single driver, thus there's no need for an
+ATAPI/CDROM filter driver.
+
+
+Copyrights
+==========
+
+Copyright (c) 2011 thi.guten Software Development
+Copyright (c) 2011 Mensys B.V.
+
+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
+
+
+Licensing and Source Code
+=========================
+
+The *binary* delivery of OS2AHCI.ADD as obtained from ecomstation.nl,
+or as part of packaged eComStation releases, is licensed to run with
+eComStation, only.
+
+The source code can be retrieved from http://svn.ecomstation.nl;
+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 GPL2.
+
+The toolchain required for compilation consists of:
+
+ - IBM OS/2 DDK version 9.23 or later (see ddk\base\h\version.mak)
+ - Microsoft C600 
+ - ALP Assembler (part of DDK)
+ - link.exe (part of DDK)
+
+
+Watcom Build
+------------
+
+The source tree contains several files that were created during an
+attempt to use the Watcom compiler (e.g. wmakefile); however, the
+Watcom build is broken and produces a non-working driver.
+
+
+Installation
+============
+
+- Copy the driver file, OS2AHCI.ADD, to C:\OS2\BOOT
+
+- Add the following line to CONFIG.SYS:
+  BASEDEV=OS2AHCI.ADD
+
+
+Driver Command Line Options
+===========================
+
+Global Options
+
+Option                 Description
+------------------------------------------------------------------------------
+/c:<addr>              Set debug COM port base address in hex (default = 3f8);
+                       if used, this option should come before any /d options.
+/d                     Debug output on COM port; multiple "/d" options will
+                       increase verbosity:
+                         1 = requests
+                         2 = detailed
+                         3 = verbose, including MMIO operations
+/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
+/r                     Reset ports during initialization (default = off
+                       unless the [Intel] AHCI controller was found to be
+                       initialized by the BIOS to SATA mode)
+/a                     Set adapter for adapter-specific options
+                       (default = -1, all adapters)
+/p                     Set port for port-specific options
+                       (default = -1, all ports)
+
+Adapter-specific Options
+
+Option                 Description
+------------------------------------------------------------------------------
+/i                     Ignore current adapter
+
+Port-specific Options
+
+Option                 Description
+------------------------------------------------------------------------------
+/s                     Enable SCSI emulation for ATAPI units (default = off)
+                       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 eComStation users. In order to turn on NCQ, just add the command line
+option "/n" to OS2AHCI.ADD.
+
+
+Interoperability With IDE Drivers
+=================================
+
+There are three kinds of IDE/ATA/SATA controllers:
+
+ 1. Legacy 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 legacy 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 DANI1S506.ADD, the options "/A:x /I"
+      will be required to tell DANI1S506.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
+legacy 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 DANI1S506.ADD
+
+OS2AHCI.ADD can't drive the CDROM because it's attached to a legacy 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 DANI1S506 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.
+
Index: /tags/1.12/src/os2ahci/ahci.c
===================================================================
--- /tags/1.12/src/os2ahci/ahci.c	(revision 97)
+++ /tags/1.12/src/os2ahci/ahci.c	(revision 97)
@@ -0,0 +1,1655 @@
+/******************************************************************************
+ * ahci.c - ahci hardware access functions
+ *
+ * Copyright (c) 2011 thi.guten Software Development
+ * Copyright (c) 2011 Mensys B.V.
+ *
+ * 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 */
+};
+
+/* IRQ levels for stub interrupt handlers. OS/2 calls interrupt handlers
+ * without passing the IRQ level, yet it expects the interrupt handler to
+ * know the IRQ level for EOI processing. Thus we need multiple interrupt
+ * handlers, one for each IRQ, and some mapping from the interrupt handler
+ * index to the corresponding IRQ.
+ */
+static u16      irq_map[MAX_AD];   /* IRQ level for each stub IRQ func */
+static int      irq_map_cnt;       /* number of IRQ stub funcs used */
+
+/* ----------------------------- start of code ----------------------------- */
+
+/******************************************************************************
+ * Interrupt handlers. Those are stubs which call the real interrupt handler
+ * with the IRQ level as parameter. This mapping is required because OS/2
+ * calls interrupt handlers without any parameters, yet expects them to know
+ * which IRQ level to complete when calling DevHelp_EOI().
+ *
+ * This array of functions needs to be extended when increasing MAX_AD.
+ */
+#if MAX_AD > 8
+#error must extend irq_handler_xx and irq_handlers[] when increasing MAX_AD
+#endif
+
+/* Macro to call AHCI interrupt handler and set/clear carry flag accordingly.
+ * We need to set the carry flag if the interrupt was not handled. This is
+ * done by shifting the return value of ahci_intr() to the right, implying
+ * bit 0 will be set when the interrupt was not handled.
+ */
+#define call_ahci_intr(i)   return(ahci_intr(irq_map[i]) >> 1)
+
+static USHORT _cdecl _far irq_handler_00(void)  { call_ahci_intr(0); }
+static USHORT _cdecl _far irq_handler_01(void)  { call_ahci_intr(1); }
+static USHORT _cdecl _far irq_handler_02(void)  { call_ahci_intr(2); }
+static USHORT _cdecl _far irq_handler_03(void)  { call_ahci_intr(3); }
+static USHORT _cdecl _far irq_handler_04(void)  { call_ahci_intr(4); }
+static USHORT _cdecl _far irq_handler_05(void)  { call_ahci_intr(5); }
+static USHORT _cdecl _far irq_handler_06(void)  { call_ahci_intr(6); }
+static USHORT _cdecl _far irq_handler_07(void)  { call_ahci_intr(7); }
+
+PFN irq_handlers[] = {
+  (PFN) irq_handler_00, (PFN) irq_handler_01, (PFN) irq_handler_02,
+  (PFN) irq_handler_03, (PFN) irq_handler_04, (PFN) irq_handler_05,
+  (PFN) irq_handler_06, (PFN) irq_handler_07
+};
+
+/******************************************************************************
+ * 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.
+ *
+ * NOTE: This function also 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_save_bios_config(AD_INFO *ai)
+{
+  int ports;
+  int i;
+
+  /* save BIOS configuration */
+  for (i = 0; i < HOST_CAP2; i += sizeof(u32)) {
+    ai->bios_config[i / sizeof(u32)] = readl(ai->mmio + i);
+  }
+
+  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;
+  }
+
+  /* HOST_CAP2 only exists for AHCI V1.2 and later */
+  if (ai->bios_config[HOST_VERSION / sizeof(u32)] >= 0x00010200L) {
+    ai->bios_config[HOST_CAP2 / sizeof(u32)] = readl(ai->mmio + HOST_CAP2);
+  } else {
+    ai->bios_config[HOST_CAP2 / sizeof(u32)] = 0;
+  }
+
+  /* print AHCI register debug information */
+  if (debug) {
+    printf("AHCI global controller registers:\n");
+    for (i = 0; i <= HOST_CAP2 / sizeof(u32); i++) {
+      u32 val = ai->bios_config[i];
+      printf(" %02x: %08lx", i, val);
+
+      if (i == HOST_CAP) {
+        printf(" -");
+        if (val & HOST_CAP_64)         printf(" 64bit");
+        if (val & HOST_CAP_NCQ)        printf(" ncq");
+        if (val & HOST_CAP_SNTF)       printf(" sntf");
+        if (val & HOST_CAP_MPS)        printf(" mps");
+        if (val & HOST_CAP_SSS)        printf(" sss");
+        if (val & HOST_CAP_ALPM)       printf(" alpm");
+        if (val & HOST_CAP_LED)        printf(" led");
+        if (val & HOST_CAP_CLO)        printf(" clo");
+        if (val & HOST_CAP_ONLY)       printf(" ahci_only");
+        if (val & HOST_CAP_PMP)        printf(" pmp");
+        if (val & HOST_CAP_FBS)        printf(" fbs");
+        if (val & HOST_CAP_PIO_MULTI)  printf(" pio_multi");
+        if (val & HOST_CAP_SSC)        printf(" ssc");
+        if (val & HOST_CAP_PART)       printf(" part");
+        if (val & HOST_CAP_CCC)        printf(" ccc");
+        if (val & HOST_CAP_EMS)        printf(" ems");
+        if (val & HOST_CAP_SXS)        printf(" sxs");
+        printf(" cmd_slots:%d", (u16) ((val >> 8) & 0x1f) + 1);
+        printf(" ports:%d",     (u16) (val & 0x1f) + 1);
+
+      } else if (i == HOST_CTL) {
+        printf(" -");
+        if (val & HOST_AHCI_EN)        printf(" ahci_enabled");
+        if (val & HOST_IRQ_EN)         printf(" irq_enabled");
+        if (val & HOST_RESET)          printf(" resetting");
+
+      } else if (i == HOST_CAP2) {
+        printf(" -");
+        if (val & HOST_CAP2_BOH)       printf(" boh");
+        if (val & HOST_CAP2_NVMHCI)    printf(" nvmhci");
+        if (val & HOST_CAP2_APST)      printf(" apst");
+      }
+      printf("\n");
+    }
+  }
+
+  /* Save working copies of CAP, CAP2 and port_map and 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...
+   */
+  ai->cap      = ai->bios_config[HOST_CAP        / sizeof(u32)];
+  ai->cap2     = ai->bios_config[HOST_CAP2       / sizeof(u32)];
+  ai->port_map = ai->bios_config[HOST_PORTS_IMPL / sizeof(u32)];
+
+  if (ai->pci->board >= sizeof(initial_flags) / sizeof(*initial_flags)) {
+    dprintf("error: invalid board index in PCI info\n");
+    return(-1);
+  }
+  ai->flags = initial_flags[ai->pci->board];
+
+  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;
+  }
+
+  if ((ai->cap & HOST_CAP_NCQ) && (ai->flags & AHCI_HFLAG_NO_NCQ)) {
+    dprintf("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("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("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("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("JMB361 has only one port, port_map 0x%lx -> 0x%lx\n", ai->port_map, 1);
+    ai->port_map = 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->cap & 0x1f) + 1;
+  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->cap & 0x1f) + 1;
+    dprintf("implemented port map (0x%lx) contains more "
+            "ports than nr_ports (%d), using nr_ports\n",
+            ai->port_map, ports);
+    ai->port_map = (1UL << ports) - 1UL;
+  }
+
+  /* set maximum command slot number */
+  ai->cmd_max = (u16) ((ai->cap >> 8) & 0x1f);
+
+  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)
+{
+  ddprintf("restoring AHCI BIOS configuration\n");
+
+  /* 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 < AHCI_MAX_PORTS; p++) {
+      if (ai->port_map & (1UL << p)) {
+        u8 _far *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 */
+        mdelay(2);
+
+        writel(port_mmio + PORT_SCR_CTL, tmp);
+        readl(port_mmio + PORT_SCR_CTL);  /* flush */
+      }
+    }
+
+    /* Wait some time to give the COMRESET a chance to complete (usually, at
+     * least hard disks complete the reset within a few milliseonds)
+     */
+    mdelay(20);
+  }
+
+  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)
+{
+  ddprintf("restoring initial configuration\n");
+
+  /* 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)]);
+
+  /* flush PCI MMIO delayed write buffers */
+  readl(ai->mmio + HOST_CTL);
+
+  /* (re-)enable AHCI mode */
+  ahci_enable_ahci(ai);
+
+  return(0);
+}
+
+/******************************************************************************
+ * 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 _far *port_mmio = port_base(ai, p);
+
+  if ((pc = malloc(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 _far *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 */
+  }
+
+  free(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);
+    }
+    mdelay(10);
+  }
+
+  /* couldn't enable AHCI mode */
+  dprintf("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;
+
+  if ((id_buf = malloc(ATA_ID_WORDS * sizeof(u16))) == NULL) {
+    return(-1);
+  }
+
+  if (ai->bios_config[0] == 0) {
+    /* first call */
+    ahci_save_bios_config(ai);
+  }
+
+  if (ahci_enable_ahci(ai)) {
+    goto exit_port_scan;
+  }
+
+  /* perform port scan */
+  dprintf("scanning ports on adapter #%d\n", ad_no(ai));
+  for (p = 0; p < AHCI_MAX_PORTS; p++) {
+    if (ai->port_map & (1UL << p)) {
+
+      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 {
+        ddprintf("(re)starting port #%d\n", p);
+        ahci_stop_port(ai, p);
+        rc = ahci_start_port(ai, p, 0);
+      }
+      if (rc) {
+        /* no device attached to this port */
+        ai->port_map &= ~(1UL << p);
+        goto restore_port_config;
+      }
+
+      /* this port seems to have a device attached and ready for commands */
+      ddprintf("port #%d seems to be attached to a device; probing...\n", p);
+
+      /* 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 _far *) id_buf, 512,
+                                  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 */
+        ddphex(id_buf, 512, "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);
+      }
+
+    restore_port_config:
+      if (pc != NULL) {
+        ahci_restore_port_config(ai, p, pc);
+      }
+    }
+  }
+
+exit_port_scan:
+  if (!init_complete) {
+    ahci_restore_bios_config(ai);
+  }
+  free(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;
+  int p;
+  int i;
+
+  dprintf("completing initialization of adapter #%d\n", ad_no(ai));
+
+  /* register IRQ handlers; each IRQ level is registered only once */
+  for (i = 0; i < irq_map_cnt; i++) {
+    if (irq_map[i] == ai->irq) {
+      /* we already have this IRQ registered */
+      break;
+    }
+  }
+  if (i >= irq_map_cnt) {
+    dprintf("registering interrupt #%d\n", ai->irq);
+    if (DevHelp_SetIRQ(mk_NPFN(irq_handlers[irq_map_cnt]), ai->irq, 1) != 0) {
+      dprintf("failed to register shared interrupt\n");
+      if (DevHelp_SetIRQ(mk_NPFN(irq_handlers[irq_map_cnt]), ai->irq, 0) != 0) {
+        dprintf("failed to register exclusive interrupt\n");
+        return(-1);
+      }
+    }
+    irq_map[irq_map_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 < AHCI_MAX_PORTS; p++) {
+    if (ai->port_map & (1UL << p)) {
+      if (init_reset) {
+        ahci_reset_port(ai, p, 1);
+      } else {
+        dprintf("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, 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); */
+
+  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 _far *port_mmio = port_base(ai, p);
+  u32 tmp;
+  int timeout = 5000;
+
+  dprintf("resetting port %d.%d\n", ad_no(ai), p);
+  if (debug > 1) {
+    printf("command engine status:\n");
+    printf(" PORT_SCR_ACT   = 0x%lx\n", readl(port_mmio + PORT_SCR_ACT));
+    printf(" PORT_CMD_ISSUE = 0x%lx\n", readl(port_mmio + PORT_CMD_ISSUE));
+    printf("link/device status:\n");
+    printf(" PORT_SCR_STAT  = 0x%lx\n", readl(port_mmio + PORT_SCR_STAT));
+    printf(" PORT_SCR_CTL   = 0x%lx\n", readl(port_mmio + PORT_SCR_CTL));
+    printf(" PORT_SCR_ERR   = 0x%lx\n", readl(port_mmio + PORT_SCR_ERR));
+    printf(" PORT_TFDATA    = 0x%lx\n", readl(port_mmio + PORT_TFDATA));
+    printf("interrupt status:\n");
+    printf(" PORT_IRQ_STAT  = 0x%lx\n", readl(port_mmio + PORT_IRQ_STAT));
+    printf(" PORT_IRQ_MASK  = 0x%lx\n", readl(port_mmio + PORT_IRQ_MASK));
+    printf(" HOST_IRQ_STAT  = 0x%lx\n", readl(ai->mmio + HOST_IRQ_STAT));
+  }
+
+  /* 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);
+
+  /* set link speed and power management options */
+  ddprintf("setting link speed and power management options\n");
+  tmp = readl(port_mmio + PORT_SCR_CTL) & ~0x00000ff0UL;
+  tmp |= ((u32) link_speed[ad_no(ai)][p] & 0x0f) << 4;
+  tmp |= ((u32) link_power[ad_no(ai)][p] & 0x0f) << 8;
+  writel(port_mmio + PORT_SCR_CTL, tmp);
+
+  /* issue COMRESET on the port */
+  ddprintf("issuing COMRESET on port\n");
+  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 */
+  mdelay(2);
+
+  writel(port_mmio + PORT_SCR_CTL, tmp);
+  readl(port_mmio + PORT_SCR_CTL);  /* flush */
+
+  /* wait for communication to be re-established after port reset */
+  while (((tmp = readl(port_mmio + PORT_SCR_STAT)) & 3) != 3) {
+    mdelay(10);
+    timeout -= 10;
+    if (timeout <= 0) {
+      dprintf("no device present after resetting port #%d "
+              "(PORT_SCR_STAT = 0x%lx)\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 */
+  ddprintf("starting port again\n");
+  ahci_start_port(ai, p, ei);
+
+  /* wait for device to be ready ((PxTFD & (BSY | DRQ | ERR)) == 0) */
+  while (((tmp = readl(port_mmio + PORT_TFDATA)) & 0x89) != 0) {
+    mdelay(10);
+    timeout -= 10;
+    if (timeout <= 0) {
+      dprintf("device not ready on port #%d "
+              "(PORT_TFDATA = 0x%lx)\n", p, tmp);
+      ahci_stop_port(ai, p);
+      return(-1);
+    }
+  }
+  ddprintf(" PORT_TFDATA   = 0x%lx\n", readl(port_mmio + PORT_TFDATA));
+
+  return(0);
+}
+
+/******************************************************************************
+ * Start specified port.
+ */
+int ahci_start_port(AD_INFO *ai, int p, int ei)
+{
+  u8 _far *port_mmio = port_base(ai, p);
+  u32 status;
+
+  /* check whether device presence is detected and link established */
+  status = readl(port_mmio + PORT_SCR_STAT);
+  ddprintf(" PORT_SCR_STAT = 0x%lx\n", status);
+  if ((status & 0xf) != 3) {
+    return(-1);
+  }
+
+  /* clear SError, if any */
+  status = readl(port_mmio + PORT_SCR_ERR);
+  ddprintf(" PORT_SCR_ERR  = 0x%lx\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 _far *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 _far *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 _far *port_mmio = port_base(ai, p);
+  u32 tmp;
+  int rc;
+
+  /* disable port interrupts */
+  writel(port_mmio + PORT_IRQ_MASK, 0);
+
+  /* disable FIS reception */
+  if ((rc = ahci_stop_fis_rx(ai, p)) != 0) {
+    dprintf("error: failed to stop FIS receive (%d)\n", rc);
+    return(rc);
+  }
+
+  /* disable command engine */
+  if ((rc = ahci_stop_engine(ai, p)) != 0) {
+    dprintf("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 _far *port_mmio = port_base(ai, p);
+  int timeout = 1000;
+  u32 tmp;
+
+  /* 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 1000 */
+  while (timeout > 0 && (readl(port_mmio + PORT_CMD) & PORT_CMD_FIS_ON)) {
+    mdelay(10);
+    timeout -= 10;
+  }
+
+  return((timeout <= 0) ? -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 _far *port_mmio = port_base(ai, p);
+  int timeout = 500;
+  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 */
+  while (timeout > 0 && (readl(port_mmio + PORT_CMD) & PORT_CMD_LIST_ON)) {
+    mdelay(10);
+    timeout -= 10;
+  }
+
+  return((timeout <= 0) ? -1 : 0);
+}
+
+/******************************************************************************
+ * Determine whether a port is busy executing commands.
+ */
+ahci_port_busy(AD_INFO *ai, int p)
+{
+  u8 _far *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 _far *iorb, int ncq_capable,
+                    int (*func)(IORBH _far *, int))
+{
+  volatile u32 *cmds;
+  ADD_WORKSPACE _far *aws = add_workspace(iorb);
+  AD_INFO *ai = ad_infos + iorb_unit_adapter(iorb);
+  P_INFO *port = ai->ports + iorb_unit_port(iorb);
+  ULONG timeout;
+  u8 _far *port_mmio = port_base(ai, iorb_unit_port(iorb));
+  u16 cmd_max = ai->cmd_max;
+  int i;
+
+  /* determine timeout in milliseconds */
+  switch (iorb->Timeout) {
+  case 0:
+    timeout = DEFAULT_TIMEOUT;
+    break;
+  case 0xffffffffUL:
+    timeout = 0xffffffffUL;
+    break;
+  default:
+    timeout = iorb->Timeout * 1000;
+    break;
+  }
+
+  /* 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(iorb)].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(iorb)].ncq_max - 1) > ai->cmd_max) {
+      cmd_max = ai->cmd_max;
+    }
+    ddprintf("NCQ command; cmd_max = %d->%d\n", (u16) 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(iorb, 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(iorb, 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(iorb);
+          return;
+        }
+
+        /* start timer for this IORB */
+        ADD_StartTimerMS(&aws->timer, timeout, (PFN) timeout_callback, iorb, 0);
+
+        /* issue command to hardware */
+        *cmds |= (1UL << port->cmd_slot);
+        aws->queued_hw = 1;
+        aws->cmd_slot = port->cmd_slot;
+
+        ddprintf("issuing command on slot %d\n", port->cmd_slot);
+        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 _far *iorb, int (*func)(IORBH _far *, int),
+                           ULONG timeout)
+{
+  AHCI_PORT_CFG *pc = NULL;
+  AD_INFO *ai = ad_infos + iorb_unit_adapter(iorb);
+  int p = iorb_unit_port(iorb);
+  u8 _far *port_mmio = port_base(ai, p);
+
+  /* enable AHCI mode */
+  if (ahci_enable_ahci(ai) != 0) {
+    iorb_seterr(iorb, 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(iorb, IOERR_DEVICE_BUSY);
+    goto restore_bios_config;
+  }
+
+  /* save port configuration */
+  if ((pc = ahci_save_port_config(ai, p)) == NULL) {
+    iorb_seterr(iorb, IOERR_CMD_SW_RESOURCE);
+    goto restore_bios_config;
+  }
+
+  /* restart/reset port (includes the necessary port configuration) */
+  if ((ai->bios_config[HOST_CTL / sizeof(u32)] & HOST_AHCI_EN) == 0 &&
+      ai->pci->vendor == PCI_VENDOR_ID_INTEL) {
+    /* 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(iorb, IOERR_ADAPTER_NONSPECIFIC);
+      goto restore_bios_config;
+    }
+
+  } else if (ahci_stop_port(ai, p) || ahci_start_port(ai, p, 0)) {
+    iorb_seterr(iorb, IOERR_ADAPTER_NONSPECIFIC);
+    goto restore_bios_config;
+  }
+
+  /* prepare command */
+  if (func(iorb, 0) == 0) {
+    /* successfully prepared cmd; issue cmd and wait for completion */
+    ddprintf("executing polled cmd...");
+    writel(port_mmio + PORT_CMD_ISSUE, 1);
+    timeout /= 10;
+    while (timeout > 0 && (readl(port_mmio + PORT_CMD_ISSUE) & 1)) {
+      mdelay(10);
+      timeout--;
+    }
+    ddprintf(" done (time left = %ld)\n", timeout * 10);
+
+    if (timeout == 0) {
+      dprintf("timeout for IORB %Fp\n", iorb);
+      iorb_seterr(iorb, IOERR_ADAPTER_TIMEOUT);
+
+    } else if (readl(port_mmio + PORT_SCR_ERR) != 0 ||
+               readl(port_mmio + PORT_TFDATA) & 0x89) {
+      dprintf("polled cmd error for IORB %Fp\n", iorb);
+      iorb_seterr(iorb, IOERR_DEVICE_NONSPECIFIC);
+      ahci_reset_port(ai, iorb_unit_port(iorb), 0);
+
+    } else {
+      /* successfully executed command */
+      if (add_workspace(iorb)->ppfunc != NULL) {
+        add_workspace(iorb)->ppfunc(iorb);
+      } else {
+        add_workspace(iorb)->complete = 1;
+      }
+    }
+  }
+
+restore_bios_config:
+  /* restore BIOS configuration */
+  if (pc != NULL) {
+    ahci_restore_port_config(ai, p, pc);
+  }
+  ahci_restore_bios_config(ai);
+
+  if (add_workspace(iorb)->complete | (iorb->Status | IORB_ERROR)) {
+    iorb_done(iorb);
+  }
+  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 _far *port_mmio = port_base(ai, p);
+  u32 tmp;
+  int rc;
+
+  /* verify that command slot 0 is idle */
+  if (readl(port_mmio + PORT_CMD_ISSUE) & 1) {
+    ddprintf("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 */
+  ddprintf("executing polled cmd...");
+  writel(port_mmio + PORT_CMD_ISSUE, 1);
+
+  /* wait until command has completed */
+  while (timeout > 0 && (readl(port_mmio + PORT_CMD_ISSUE) & 1)) {
+    mdelay(10);
+    timeout -= 10;
+  }
+  ddprintf(" done (time left = %d)\n", timeout);
+
+  /* check error condition */
+  if ((tmp = readl(port_mmio + PORT_SCR_ERR)) != 0) {
+    dprintf("SERR = 0x%08lx\n", tmp);
+    timeout = 0;
+  }
+  if (((tmp = readl(port_mmio + PORT_TFDATA)) & 0x89) != 0) {
+    dprintf("TFDATA = 0x%08lx\n", tmp);
+    timeout = 0;
+  }
+
+  if (timeout <= 0) {
+    ahci_reset_port(ai, p, 0);
+    return(-1);
+  }
+  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("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));
+  }
+}
+
+/******************************************************************************
+ * 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)
+{
+  ddprintf("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, ...)
+ *
+ * NOTE: OS/2 expects the carry flag set upon return from an interrupt
+ *       handler if the interrupt has not been handled. We do this by
+ *       shifting the return code from this function one bit to the right,
+ *       thus the return code must set bit 0 in this case.
+ */
+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 */
+    spin_lock(drv_lock);
+    trigger_engine();
+    spin_unlock(drv_lock);
+
+    /* complete the interrupt */
+    DevHelp_EOI(irq);
+    return(0);
+  } else {
+    return(1);
+  }
+}
+
+/******************************************************************************
+ * 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 _far *iorb;
+  IORBH _far *next = NULL;
+  u8 _far *port_mmio = port_base(ai, p);
+  u32 irq_stat;
+  u32 active_cmds;
+  u32 done_mask;
+
+  ddprintf("port interrupt for adapter #%d, port #%d\n", ad_no(ai), p);
+  memset(&done_queue, 0x00, sizeof(done_queue));
+
+  /* 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 */
+
+  if (irq_stat & PORT_IRQ_ERROR) {
+    /* this is an error interrupt */
+    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;
+    ddprintf("[ncq_cmds]: active_cmds = 0x%08lx, done_mask = 0x%08lx\n",
+             active_cmds, done_mask);
+  } else {
+    active_cmds = readl(port_mmio + PORT_CMD_ISSUE);
+    done_mask = ai->ports[p].reg_cmds ^ active_cmds;
+    ddprintf("[reg_cmds]: active_cmds = 0x%08lx, done_mask = 0x%08lx\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 (iorb = ai->ports[p].iorb_queue.root; iorb != NULL; iorb = next) {
+    ADD_WORKSPACE _far *aws = (ADD_WORKSPACE _far *) &iorb->ADDWorkSpace;
+    next = iorb->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(iorb);
+      } 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, iorb);
+        iorb_queue_add(&done_queue, iorb);
+        aws_free(add_workspace(iorb));
+      }
+    }
+  }
+
+  spin_unlock(drv_lock);
+
+  /* complete all IORBs in the done queue */
+  for (iorb = done_queue.root; iorb != NULL; iorb = next) {
+    next = iorb->pNxtIORB;
+    iorb_complete(iorb);
+  }
+}
+
+/******************************************************************************
+ * 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.
+   */
+  if (irq_stat & PORT_IRQ_UNK_FIS) {
+    u32 _far *unk = (u32 _far *) (port_dma_base(ai, p)->rx_fis + RX_FIS_UNK);
+    dprintf("warning: unknown FIS %08lx %08lx %08lx %08lx\n",
+            unk[0], unk[1], unk[2], unk[3]);
+    reset_port = 1;
+  }
+  if (irq_stat & (PORT_IRQ_HBUS_ERR | PORT_IRQ_HBUS_DATA_ERR)) {
+    dprintf("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("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;
+    DevHelp_ArmCtxHook(0, reset_ctxhook_h);
+
+    /* no point analyzing device errors after a reset... */
+    return;
+  }
+
+  /* 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.
+   */
+  if (irq_stat & PORT_IRQ_TF_ERR) {
+    ports_to_restart[ad_no(ai)] |= 1UL << p;
+    DevHelp_ArmCtxHook(0, restart_ctxhook_h);
+  }
+}
+
+/******************************************************************************
+ * Get device or media geometry. Device and media geometry are expected to be
+ * the same for non-removable devices.
+ */
+void ahci_get_geometry(IORBH _far *iorb)
+{
+  dprintf("ahci_get_geometry(%d.%d.%d)\n", (int) iorb_unit_adapter(iorb),
+          (int) iorb_unit_port(iorb), (int) iorb_unit_device(iorb));
+
+  ahci_exec_iorb(iorb, 0, cmd_func(iorb, get_geometry));
+}
+
+/******************************************************************************
+ * Test whether unit is ready.
+ */
+void ahci_unit_ready(IORBH _far *iorb)
+{
+  dprintf("ahci_unit_ready(%d.%d.%d)\n", (int) iorb_unit_adapter(iorb),
+          (int) iorb_unit_port(iorb), (int) iorb_unit_device(iorb));
+
+  ahci_exec_iorb(iorb, 0, cmd_func(iorb, unit_ready));
+}
+
+/******************************************************************************
+ * Read sectors from AHCI device.
+ */
+void ahci_read(IORBH _far *iorb)
+{
+  dprintf("ahci_read(%d.%d.%d, %ld, %ld)\n", (int) iorb_unit_adapter(iorb),
+          (int) iorb_unit_port(iorb), (int) iorb_unit_device(iorb),
+          (long) ((IORB_EXECUTEIO _far *) iorb)->RBA,
+          (long) ((IORB_EXECUTEIO _far *) iorb)->BlockCount);
+
+  ahci_exec_iorb(iorb, 1, cmd_func(iorb, read));
+}
+
+/******************************************************************************
+ * Verify readability of sectors on AHCI device.
+ */
+void ahci_verify(IORBH _far *iorb)
+{
+  dprintf("ahci_verify(%d.%d.%d, %ld, %ld)\n", (int) iorb_unit_adapter(iorb),
+          (int) iorb_unit_port(iorb), (int) iorb_unit_device(iorb),
+          (long) ((IORB_EXECUTEIO _far *) iorb)->RBA,
+          (long) ((IORB_EXECUTEIO _far *) iorb)->BlockCount);
+
+  ahci_exec_iorb(iorb, 0, cmd_func(iorb, verify));
+}
+
+/******************************************************************************
+ * Write sectors to AHCI device.
+ */
+void ahci_write(IORBH _far *iorb)
+{
+  dprintf("ahci_write(%d.%d.%d, %ld, %ld)\n", (int) iorb_unit_adapter(iorb),
+          (int) iorb_unit_port(iorb), (int) iorb_unit_device(iorb),
+          (long) ((IORB_EXECUTEIO _far *) iorb)->RBA,
+          (long) ((IORB_EXECUTEIO _far *) iorb)->BlockCount);
+
+  ahci_exec_iorb(iorb, 1, cmd_func(iorb, write));
+}
+
+/******************************************************************************
+ * Execute SCSI (ATAPI) command.
+ */
+void ahci_execute_cdb(IORBH _far *iorb)
+{
+  int a = iorb_unit_adapter(iorb);
+  int p = iorb_unit_port(iorb);
+  int d = iorb_unit_device(iorb);
+
+  dphex(((IORB_ADAPTER_PASSTHRU _far *) iorb)->pControllerCmd,
+        ((IORB_ADAPTER_PASSTHRU _far *) iorb)->ControllerCmdLen,
+        "ahci_execute_cdb(%d.%d.%d): ", a, p, d);
+
+  if (ad_infos[a].ports[p].devs[d].atapi) {
+    ahci_exec_iorb(iorb, 0, atapi_execute_cdb);
+  } else {
+    iorb_seterr(iorb, IOERR_CMD_NOT_SUPPORTED);
+    iorb_done(iorb);
+  }
+}
+
+/******************************************************************************
+ * 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 _far *iorb)
+{
+  int a = iorb_unit_adapter(iorb);
+  int p = iorb_unit_port(iorb);
+  int d = iorb_unit_device(iorb);
+
+  dphex(((IORB_ADAPTER_PASSTHRU _far *) iorb)->pControllerCmd,
+        ((IORB_ADAPTER_PASSTHRU _far *) iorb)->ControllerCmdLen,
+        "ahci_execute_ata(%d.%d.%d): ", a, p, d);
+
+  ahci_exec_iorb(iorb, 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];
+  static u8 total_dev_cnt;
+
+  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;
+
+  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("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, ata_dev_name(id_buf));
+  } else {
+    sprintf(dev_name, RM_HD_PREFIX "%s", p, d, ata_dev_name(id_buf));
+  }
+
+  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(ata_dev_name(id_buf), "VBOX HARDDISK", 13)) {
+    /* running inside virtualbox */
+    pci_hack_virtualbox();
+  }
+}
+
+
Index: /tags/1.12/src/os2ahci/ahci.h
===================================================================
--- /tags/1.12/src/os2ahci/ahci.h	(revision 97)
+++ /tags/1.12/src/os2ahci/ahci.h	(revision 97)
@@ -0,0 +1,285 @@
+/******************************************************************************
+ * ahci.h - AHCI-specific constants for os2ahci.h
+ *
+ * Copyright (c) 2011 thi.guten Software Development
+ * Copyright (c) 2011 Mensys B.V.
+ *
+ * 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          32
+#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
+
+/* port-specific DMA scratch buffer aligned to 1024 bytes */
+#define AHCI_PORT_PRIV_DMA_SZ   (((sizeof(AHCI_PORT_DMA) + 1023U) / 1024U) * 1024U)
+
+#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_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)
+
+/* 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/1.12/src/os2ahci/apm.c
===================================================================
--- /tags/1.12/src/os2ahci/apm.c	(revision 97)
+++ /tags/1.12/src/os2ahci/apm.c	(revision 97)
@@ -0,0 +1,175 @@
+/******************************************************************************
+ * apm.c - Functions to interface with the APM driver.
+ *
+ * Copyright (c) 2011 thi.guten Software Development
+ * Copyright (c) 2011 Mensys B.V.
+ *
+ * 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 <apmcalls.h>
+
+/* -------------------------- macros and constants ------------------------- */
+
+/* ------------------------ typedefs and structures ------------------------ */
+
+/* -------------------------- function prototypes -------------------------- */
+
+/* ------------------------ global/static variables ------------------------ */
+
+USHORT _far _cdecl apm_event    (APMEVENT _far *evt);
+
+/* ----------------------------- start of code ----------------------------- */
+
+/******************************************************************************
+ * 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("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("couldn't register for power event notificatins (rc = %d)\n", rc);
+    return;
+  }
+}
+
+/******************************************************************************
+ * APM event handler
+ */
+USHORT _far _cdecl apm_event(APMEVENT _far *evt)
+{
+  USHORT msg = (USHORT) evt->ulParm1;
+
+  dprintf("received APM event: 0x%lx/0x%lx\n");
+
+  switch (msg) {
+
+  case APM_SETPWRSTATE:
+    if (evt->ulParm2 >> 16 != APM_PWRSTATEREADY) {
+      /* we're suspending */
+      apm_suspend();
+    }
+    break;
+
+  case APM_NORMRESUMEEVENT:
+  case APM_CRITRESUMEEVENT:
+    /* we're resuming */
+    apm_resume();
+    break;
+
+  default:
+    dprintf("unknown APM event; ignoring...\n");
+    break;
+  }
+
+  return(0);
+}
+
+/******************************************************************************
+ * APM suspend handler. In a nutshell, it'll turn of interrupts and flush all
+ * write caches.
+ */
+void apm_suspend(void)
+{
+  int a;
+  int p;
+  int d;
+
+  dprintf("apm_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 */
+      while (ahci_port_busy(ai, p)) {
+        msleep(250);
+      }
+
+      /* 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);
+	}
+      }
+    }
+  }
+
+  /* reset init_complete so that we can process IORBs without interrupts */
+  init_complete = 0;
+
+  /* restore BIOS configuration for each adapter and release the adapter */
+  for (a = 0; a < ad_info_cnt; a++) {
+    ahci_restore_bios_config(ad_infos + a);
+    unlock_adapter(ad_infos + a);
+  }
+
+  dprintf("apm_suspend() finished\n");
+}
+
+/******************************************************************************
+ * APM resume handler. All ports are restarted with interrupts enabled using
+ * the same function as the IOCM_COMPLETE_INIT handler does.
+ */
+void apm_resume(void)
+{
+  int a;
+
+  dprintf("apm_resume()\n");
+
+  for (a = 0; a < ad_info_cnt; a++) {
+    AD_INFO *ai = ad_infos + a;
+
+    /* 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.
+     */
+    lock_adapter(ai);
+    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++) {
+    unlock_adapter(ad_infos + a);
+  }
+
+  dprintf("apm_resume() finished\n");
+}
Index: /tags/1.12/src/os2ahci/ata.c
===================================================================
--- /tags/1.12/src/os2ahci/ata.c	(revision 97)
+++ /tags/1.12/src/os2ahci/ata.c	(revision 97)
@@ -0,0 +1,889 @@
+/******************************************************************************
+ * ata.c - ATA command processing
+ *
+ * Copyright (c) 2011 thi.guten Software Development
+ * Copyright (c) 2011 Mensys B.V.
+ *
+ * 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 -------------------------- */
+
+/* ------------------------ 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 _far *dma_base_virt;
+  AHCI_CMD_HDR _far *cmd_hdr;
+  AHCI_CMD_TBL _far *cmd_tbl;
+  SCATGATENTRY _far *sg_list = NULL;
+  SCATGATENTRY sg_single;
+  ATA_PARM ap;
+  ATA_CMD ata_cmd;
+  void _far *atapi_cmd = NULL;
+  u32 dma_base_phys;
+  u16 atapi_cmd_len = 0;
+  u16 ahci_flags = 0;
+  u16 sg_cnt = 0;
+  int i;
+  int 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 = (u8) cmd;
+
+  /* parse variable arguments */
+  do {
+    switch ((ap = va_arg(va, ATA_PARM))) {
+
+    case AP_AHCI_FLAGS:
+      ahci_flags |= va_arg(va, u16);
+      break;
+
+    case AP_WRITE:
+      if (va_arg(va, u16) != 0) {
+        ahci_flags |= AHCI_CMD_WRITE;
+      }
+      break;
+
+    case AP_FEATURES:
+      /* ATA features word */
+      ata_cmd.features |= va_arg(va, u16);
+      break;
+
+    case AP_COUNT:
+      /* transfer count */
+      ata_cmd.count = va_arg(va, u16);
+      break;
+
+    case AP_SECTOR_28:
+      /* 28-bit sector address */
+      ata_cmd.lba_l = va_arg(va, u32);
+      if (ata_cmd.lba_l & 0xf0000000UL) {
+        dprintf("error: LBA-28 address %ld has more than 28 bits\n", ata_cmd.lba_l);
+        return(-1);
+      }
+      /* 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, u16);
+      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, u16) & 0xf0U;
+      break;
+
+    case AP_SGLIST:
+      /* scatter/gather list in SCATGATENTRY/count format */
+      sg_list = va_arg(va, void _far *);
+      sg_cnt = va_arg(va, u16);
+      break;
+
+    case AP_VADDR:
+      /* virtual buffer address in addr/len format (up to 4K) */
+      DevHelp_VirtToPhys(va_arg(va, void _far *), &sg_single.ppXferBuf);
+      sg_single.XferBufLen = va_arg(va, u16);
+      sg_list = &sg_single;
+      sg_cnt = 1;
+      break;
+
+    case AP_ATAPI_CMD:
+      /* ATAPI command */
+      atapi_cmd = va_arg(va, void _far *);
+      atapi_cmd_len = va_arg(va, u16);
+      ahci_flags |= AHCI_CMD_ATAPI;
+      break;
+
+    case AP_ATA_CMD:
+      /* ATA command "pass-through" */
+      memcpy(&ata_cmd, va_arg(va, void _far *), sizeof(ATA_CMD));
+      break;
+
+    case AP_END:
+      break;
+
+    default:
+      dprintf("error: v_ata_cmd() called with invalid parameter type (%d)\n", (int) ap);
+      return(-1);
+    }
+
+  } 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]);
+
+  /* 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 */
+        ddprintf("ata_cmd(): too many S/G elements\n");
+        return(i - 1);
+      }
+      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 |= (u32) n << 16;
+
+  if (debug >= 2) {
+    printf("ATA command for %d.%d.%d:\n", ad_no(ai), p, d);
+    phex(cmd_hdr, offsetof(AHCI_CMD_HDR, reserved), "cmd_hdr:   ");
+    phex(&ata_cmd, sizeof(ata_cmd), "ata_cmd:   ");
+    if (atapi_cmd != NULL) {
+      phex(atapi_cmd, atapi_cmd_len, "atapi_cmd: ");
+    }
+    if (n > 0) {
+      phex(cmd_tbl->sg_list, sizeof(*cmd_tbl->sg_list) * n, "sg_list:   ");
+    }
+  }
+
+  return(0);
+}
+
+/******************************************************************************
+ * 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 _far *fis, ATA_CMD _far *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 _far *io)
+{
+  ULONG offset = io->BlocksXferred * io->BlockSize;
+  USHORT i;
+
+  for (i = 0; i < io->cSGList && offset > 0; i++) {
+    offset -= io->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 _far *io, USHORT sg_indx, USHORT sg_max,
+                    USHORT _far *sg_cnt, USHORT _far *sector_cnt)
+{
+  ULONG max_sector_cnt = 0;
+  USHORT max_sg_cnt = 0;
+  ULONG offset = 0;
+  USHORT i;
+
+  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 += io->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 _far *iorb, int slot)
+{
+  ADD_WORKSPACE _far *aws = add_workspace(iorb);
+  int rc;
+
+  /* allocate buffer for ATA identify information */
+  if ((aws->buf = malloc(ATA_ID_WORDS * sizeof(u16))) == NULL) {
+    iorb_seterr(iorb, IOERR_CMD_SW_RESOURCE);
+    return(-1);
+  }
+
+  /* request ATA identify information */
+  aws->ppfunc = ata_get_geometry_pp;
+  rc = ata_cmd(ad_infos + iorb_unit_adapter(iorb),
+               iorb_unit_port(iorb),
+               iorb_unit_device(iorb),
+               slot,
+               ATA_CMD_ID_ATA,
+               AP_VADDR, (void _far *) aws->buf, ATA_ID_WORDS * sizeof(u16),
+               AP_END);
+
+  if (rc != 0) {
+    iorb_seterr(iorb, IOERR_CMD_ADD_SOFTWARE_FAILURE);
+  }
+
+  return(rc);
+}
+
+/******************************************************************************
+ * Post processing function for ata_get_geometry(): convert the ATA identify
+ * information to OS/2 IOCC_GEOMETRY information.
+ */
+void ata_get_geometry_pp(IORBH _far *iorb)
+{
+  GEOMETRY _far *geometry = ((IORB_GEOMETRY _far *) iorb)->pGeometry;
+  USHORT geometry_len =  ((IORB_GEOMETRY _far *) iorb)->GeometryLen;
+  u16 *id_buf = add_workspace(iorb)->buf;
+  int a = iorb_unit_adapter(iorb);
+  int p = iorb_unit_port(iorb);
+
+  /* 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("warning: limiting disk %d.%d.%d to 2TB\n",
+              iorb_unit_adapter(iorb), iorb_unit_port(iorb),
+              iorb_unit_device(iorb));
+      geometry->TotalSectors = 0xffffffffUL;
+    } else {
+      geometry->TotalSectors = ATA_CAPACITY48_L(id_buf);
+    }
+  } else {
+    /* 28-bit LBA */
+    geometry->TotalSectors = ATA_CAPACITY(id_buf) & 0x0fffffffUL;
+  }
+
+  /* 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);
+
+  } else if (CUR_HEADS(id_buf) > 0 && CUR_CYLS(id_buf) > 0 &&
+             CUR_SECTORS(id_buf) > 0 &&
+             CUR_CAPACITY(id_buf) == CUR_HEADS(id_buf) *
+                                     CUR_CYLS(id_buf) *
+                                     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);
+
+  } 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);
+
+  } else {
+    /* use typical SCSI geometry */
+    geometry->NumHeads        = 255;
+    geometry->SectorsPerTrack = 63;
+    geometry->TotalCylinders  = geometry->TotalSectors /
+				 ((u32) geometry->NumHeads *
+				  (u32) geometry->SectorsPerTrack);
+  }
+
+  if (debug) {
+    printf("geometry information:\n");
+    printf("  heads:     %d\n", (u16) geometry->NumHeads);
+    printf("  sectors:   %d\n", (u16) geometry->SectorsPerTrack);
+    printf("  cylinders: %d\n", (u16) geometry->TotalCylinders);
+    printf("  capacity:  %ldMB\n", (u32) (geometry->TotalSectors / 2048));
+  }
+
+  /* tell interrupt handler that this IORB is complete */
+  add_workspace(iorb)->complete = 1;
+}
+
+/******************************************************************************
+ * Test whether unit is ready.
+ */
+int ata_unit_ready(IORBH _far *iorb, 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 _far *) iorb)->UnitStatus = US_READY | US_POWER;
+  return(-1);
+}
+
+/******************************************************************************
+ * Read sectors from AHCI device.
+ */
+int ata_read(IORBH _far *iorb, int slot)
+{
+  IORB_EXECUTEIO _far *io = (IORB_EXECUTEIO _far *) iorb;
+  AD_INFO *ai = ad_infos + iorb_unit_adapter(iorb);
+  ULONG sector = io->RBA + io->BlocksXferred;
+  USHORT count = io->BlockCount - io->BlocksXferred;
+  USHORT sg_indx;
+  USHORT sg_cnt;
+  int p = iorb_unit_port(iorb);
+  int d = iorb_unit_device(iorb);
+  int rc;
+
+  /* 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 &&
+      io->pSGList[0].XferBufLen > io->BlockCount * io->BlockSize) {
+    io->pSGList[0].XferBufLen = 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 (sector >= (1UL << 28) || count > 256 || add_workspace(iorb)->is_ncq) {
+      /* need LBA48 for this command */
+      if (!ai->ports[p].devs[d].lba48) {
+        iorb_seterr(iorb, IOERR_RBA_LIMIT);
+        return(-1);
+      }
+      if (add_workspace(iorb)->is_ncq) {
+        /* use NCQ read; count goes into feature register, tag into count! */
+        rc = ata_cmd(ai, p, d, slot, ATA_CMD_FPDMA_READ,
+                     AP_SECTOR_48, (u32) sector, (u16) 0,
+                     AP_FEATURES,  (u16) count,
+                     AP_COUNT,     (u16) (slot << 3), /* tag = slot */
+                     AP_SGLIST,    io->pSGList + sg_indx, (u16) sg_cnt,
+                     AP_DEVICE,    0x40,
+                     AP_END);
+      } else {
+        rc = ata_cmd(ai, p, d, slot, ATA_CMD_READ_EXT,
+                     AP_SECTOR_48, (u32) sector, (u16) 0,
+                     AP_COUNT,     (u16) count,
+                     AP_SGLIST,    io->pSGList + sg_indx, (u16) sg_cnt,
+                     AP_DEVICE,    0x40,
+                     AP_END);
+      }
+
+    } else {
+      rc = ata_cmd(ai, p, d, slot, ATA_CMD_READ,
+                   AP_SECTOR_28, (u32) sector,
+                   AP_COUNT,     (u16) count & 0xffU,
+                   AP_SGLIST,    io->pSGList + sg_indx, (u16) sg_cnt,
+                   AP_DEVICE,    0x40,
+                   AP_END);
+    }
+
+    if (rc > 0) {
+      /* 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(iorb)->blocks = count;
+    add_workspace(iorb)->ppfunc = ata_read_pp;
+
+  } else if (rc > 0) {
+    iorb_seterr(iorb, IOERR_CMD_SGLIST_BAD);
+
+  } else {
+    iorb_seterr(iorb, IOERR_CMD_ADD_SOFTWARE_FAILURE);
+  }
+
+  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.
+ */
+void ata_read_pp(IORBH _far *iorb)
+{
+  IORB_EXECUTEIO _far *io = (IORB_EXECUTEIO _far *) iorb;
+
+  io->BlocksXferred += add_workspace(iorb)->blocks;
+  dprintf("ata_read_pp(): blocks transferred = %d\n", (int) io->BlocksXferred);
+
+  if (io->BlocksXferred >= io->BlockCount) {
+    /* we're done; tell IRQ handler the IORB is complete */
+    add_workspace(iorb)->complete = 1;
+  } else {
+    /* requeue this IORB for next iteration */
+    iorb_requeue(iorb);
+  }
+}
+
+/******************************************************************************
+ * Verify readability of sectors on ATA device.
+ */
+int ata_verify(IORBH _far *iorb, int slot)
+{
+  IORB_EXECUTEIO _far *io = (IORB_EXECUTEIO _far *) iorb;
+  AD_INFO *ai = ad_infos + iorb_unit_adapter(iorb);
+  int p = iorb_unit_port(iorb);
+  int d = iorb_unit_device(iorb);
+  int rc;
+
+  /* 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(iorb, IOERR_RBA_LIMIT);
+      return(-1);
+    }
+    rc = ata_cmd(ai, p, d, slot, ATA_CMD_VERIFY_EXT,
+                 AP_SECTOR_48, (u32) io->RBA, (u16) 0,
+                 AP_COUNT,     (u16) io->BlockCount,
+                 AP_DEVICE,    0x40,
+                 AP_END);
+  } else {
+    rc = ata_cmd(ai, p, d, slot, ATA_CMD_VERIFY,
+                 AP_SECTOR_28, (u32) io->RBA,
+                 AP_COUNT,     (u16) io->BlockCount & 0xffU,
+                 AP_END);
+  }
+
+  return(rc);
+}
+
+/******************************************************************************
+ * Write sectors to AHCI device.
+ */
+int ata_write(IORBH _far *iorb, int slot)
+{
+  IORB_EXECUTEIO _far *io = (IORB_EXECUTEIO _far *) iorb;
+  AD_INFO *ai = ad_infos + iorb_unit_adapter(iorb);
+  ULONG sector = io->RBA + io->BlocksXferred;
+  USHORT count = io->BlockCount - io->BlocksXferred;
+  USHORT sg_indx;
+  USHORT sg_cnt;
+  int p = iorb_unit_port(iorb);
+  int d = iorb_unit_device(iorb);
+  int rc;
+
+  /* prepare write command while keeping an eye on S/G count limitations */
+  do {
+    sg_indx = ata_get_sg_indx(io);
+    sg_cnt = io->cSGList - sg_indx;
+
+    if (sector >= (1UL << 28) || count > 256 || add_workspace(iorb)->is_ncq) {
+      /* need LBA48 for this command */
+      if (!ai->ports[p].devs[d].lba48) {
+        iorb_seterr(iorb, IOERR_RBA_LIMIT);
+        return(-1);
+      }
+      if (add_workspace(iorb)->is_ncq) {
+        /* use NCQ write; count goes into feature register, tag into count! */
+        rc = ata_cmd(ai, p, d, slot, ATA_CMD_FPDMA_WRITE,
+                     AP_SECTOR_48, (u32) sector, (u16) 0,
+                     AP_FEATURES,  (u16) count,
+                     AP_COUNT,     (u16) (slot << 3), /* tag = slot */
+                     AP_SGLIST,    io->pSGList + sg_indx, (u16) sg_cnt,
+                     AP_DEVICE,    0x40,
+		     AP_DEVICE,    (io->Flags & XIO_DISABLE_HW_WRITE_CACHE) ?
+		                   0x80 : 0, /* force unit access */
+                     AP_WRITE,     1,
+                     AP_END);
+      } else {
+        rc = ata_cmd(ai, p, d, slot, ATA_CMD_WRITE_EXT,
+                     AP_SECTOR_48, (u32) sector, (u16) 0,
+                     AP_COUNT,     (u16) count,
+                     AP_SGLIST,    io->pSGList + sg_indx, (u16) sg_cnt,
+                     AP_DEVICE,    0x40,
+                     AP_WRITE,     1,
+                     AP_END);
+      }
+
+    } else {
+      rc = ata_cmd(ai, p, d, slot, ATA_CMD_WRITE,
+                   AP_SECTOR_28, (u32) sector,
+                   AP_COUNT,     (u16) count & 0xffU,
+                   AP_SGLIST,    io->pSGList + sg_indx, (u16) sg_cnt,
+                   AP_DEVICE,    0x40,
+                   AP_WRITE,     1,
+                   AP_END);
+    }
+
+    if (rc > 0) {
+      /* 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(iorb)->blocks = count;
+    add_workspace(iorb)->ppfunc = ata_write_pp;
+
+  } else if (rc > 0) {
+    iorb_seterr(iorb, IOERR_CMD_SGLIST_BAD);
+
+  } else {
+    iorb_seterr(iorb, IOERR_CMD_ADD_SOFTWARE_FAILURE);
+  }
+
+  return(rc);
+}
+
+/******************************************************************************
+ * 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 _far *iorb)
+{
+  IORB_EXECUTEIO _far *io = (IORB_EXECUTEIO _far *) iorb;
+
+  io->BlocksXferred += add_workspace(iorb)->blocks;
+  dprintf("ata_write_pp(): blocks transferred = %d\n", (int) io->BlocksXferred);
+
+  if (io->BlocksXferred >= io->BlockCount) {
+    /* we're done; tell IRQ handler the IORB is complete */
+    add_workspace(iorb)->complete = 1;
+  } else {
+    /* requeue this IORB for next iteration */
+    iorb_requeue(iorb);
+  }
+}
+
+/******************************************************************************
+ * Execute ATA command.
+ */
+int ata_execute_ata(IORBH _far *iorb, int slot)
+{
+  IORB_ADAPTER_PASSTHRU _far *apt = (IORB_ADAPTER_PASSTHRU _far *) iorb;
+  AD_INFO *ai = ad_infos + iorb_unit_adapter(iorb);
+  int p = iorb_unit_port(iorb);
+  int d = iorb_unit_device(iorb);
+  int rc;
+
+  if (apt->ControllerCmdLen != sizeof(ATA_CMD)) {
+    iorb_seterr(iorb, IOERR_CMD_SYNTAX);
+    return(-1);
+  }
+
+  rc = ata_cmd(ai, p, d, slot, 0,
+               AP_SGLIST,   apt->pSGList, apt->cSGList,
+               AP_ATA_CMD,  apt->pControllerCmd,
+               AP_WRITE,    !(apt->Flags & PT_DIRECTION_IN),
+               AP_END);
+
+  return(rc);
+}
+
+/******************************************************************************
+ * 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 _far *iorb, int slot)
+{
+  AD_INFO *ai = ad_infos + iorb_unit_adapter(iorb);
+  u8 _far *port_mmio = port_base(ai, iorb_unit_port(iorb));
+  u32 tf_data = readl(port_mmio + PORT_TFDATA);
+  u8 err = (u8) (tf_data >> 8);
+  u8 sts = (u8) (tf_data);
+
+  if (sts & ATA_ERR) {
+    if (sts & ATA_DF) {
+      /* there is a device-specific error condition */
+      if (err & ATA_ICRC) {
+        iorb_seterr(iorb, IOERR_ADAPTER_DEVICEBUSCHECK);
+      } else if (err & ATA_UNC) {
+        iorb_seterr(iorb, IOERR_MEDIA);
+      } else if (err & ATA_IDNF) {
+        iorb_seterr(iorb, IOERR_RBA_ADDRESSING_ERROR);
+      } else {
+        iorb_seterr(iorb, IOERR_DEVICE_NONSPECIFIC);
+      }
+
+    } else {
+      iorb_seterr(iorb, IOERR_DEVICE_NONSPECIFIC);
+    }
+  } else {
+    /* this function only gets called when we received an error interrupt */
+    iorb_seterr(iorb, 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);
+}
+
Index: /tags/1.12/src/os2ahci/ata.h
===================================================================
--- /tags/1.12/src/os2ahci/ata.h	(revision 97)
+++ /tags/1.12/src/os2ahci/ata.h	(revision 97)
@@ -0,0 +1,498 @@
+/******************************************************************************
+ * ata.h - ATA structures and macros for os2ahci driver
+ *
+ * Copyright (c) 2011 thi.guten Software Development
+ * Copyright (c) 2011 Mensys B.V.
+ *
+ * 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_ENABLE        0xD8
+#define ATA_SMART_READ_VALUES   0xD0
+#define ATA_SMART_READ_THRESHOLDS 0xD1
+
+/* 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 _far *, u16]  buffer S/G (SCATGATENTRY/count)    */
+  AP_VADDR,        /* [void _far *, 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 _far *, u16]  ATAPI command (CDB) and length     */
+  AP_ATA_CMD,      /* [void _far *]       ATA command (fixed len)            */
+  AP_END           /* []                  end of variable argument list      */
+} ATA_PARM;
+
+/* ------------------------ 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 _far *fis, ATA_CMD _far *cmd,
+                                      int device);
+extern USHORT    ata_get_sg_indx     (IORB_EXECUTEIO _far *io);
+extern void      ata_max_sg_cnt      (IORB_EXECUTEIO _far *io, USHORT sg_indx,
+                                      USHORT sg_max, USHORT _far *sg_cnt,
+                                      USHORT _far *sector_cnt);
+
+extern int       ata_get_geometry    (IORBH _far *iorb, int slot);
+extern void      ata_get_geometry_pp (IORBH _far *iorb);
+extern int       ata_unit_ready      (IORBH _far *iorb, int slot);
+extern int       ata_read            (IORBH _far *iorb, int slot);
+extern void      ata_read_pp         (IORBH _far *iorb);
+extern int       ata_verify          (IORBH _far *iorb, int slot);
+extern int       ata_write           (IORBH _far *iorb, int slot);
+extern void      ata_write_pp        (IORBH _far *iorb);
+extern int       ata_execute_ata     (IORBH _far *iorb, int slot);
+extern int       ata_req_sense       (IORBH _far *iorb, int slot);
+
+extern char     *ata_dev_name        (u16 *id_buf);
+
Index: /tags/1.12/src/os2ahci/atapi.c
===================================================================
--- /tags/1.12/src/os2ahci/atapi.c	(revision 97)
+++ /tags/1.12/src/os2ahci/atapi.c	(revision 97)
@@ -0,0 +1,402 @@
+/******************************************************************************
+ * atapi.c - ATAPI command processing
+ *
+ * Copyright (c) 2011 thi.guten Software Development
+ * Copyright (c) 2011 Mensys B.V.
+ *
+ * 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 <scsi.h>
+
+/* -------------------------- macros and constants ------------------------- */
+
+/* ------------------------ typedefs and structures ------------------------ */
+
+/* -------------------------- function prototypes -------------------------- */
+
+static void atapi_req_sense_pp      (IORBH _far *iorb);
+static int atapi_pad_cdb            (u8 _far *cmd_in, u16 cmd_in_len,
+                                     u8 _far *cmd_out, u16 _far *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 _far *iorb, int slot)
+{
+  iorb_seterr(iorb, IOERR_CMD_NOT_SUPPORTED);
+  return(-1);
+}
+
+/******************************************************************************
+ * Test whether unit is ready. This function is not expected to be called.
+ */
+int atapi_unit_ready(IORBH _far *iorb, int slot)
+{
+  ddprintf("atapi_unit_ready called\n");
+  iorb_seterr(iorb, IOERR_CMD_NOT_SUPPORTED);
+  return(-1);
+}
+
+/******************************************************************************
+ * Read sectors from AHCI device.
+ */
+int atapi_read(IORBH _far *iorb, int slot)
+{
+  IORB_EXECUTEIO _far *io = (IORB_EXECUTEIO _far *) iorb;
+  ATAPI_CDB_12 cdb;
+  AD_INFO *ai = ad_infos + iorb_unit_adapter(iorb);
+  USHORT count = io->BlockCount - io->BlocksXferred;
+  USHORT sg_indx;
+  USHORT sg_cnt;
+  int p = iorb_unit_port(iorb);
+  int d = iorb_unit_device(iorb);
+  int rc;
+
+  /* translate read command to SCSI/ATAPI READ12 command.
+   * READ12 seems to be the most supported READ variant - according to MMC,
+   * and it's enough even for BluRay.
+   */
+  memset(&cdb, 0x00, sizeof(cdb));
+  cdb.cmd = ATAPI_CMD_READ_12;
+  SET_CDB_32(cdb.lba, io->RBA + io->BlocksXferred);
+
+  do {
+    /* update sector count (might have been updated due to S/G limitations) */
+    SET_CDB_32(cdb.trans_len, (u32) 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 _far *) &cdb, sizeof(cdb),
+                 AP_SGLIST,    io->pSGList + sg_indx, (u16) 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(iorb)->blocks = count;
+    add_workspace(iorb)->ppfunc = ata_read_pp;
+
+  } else if (rc > 0) {
+    iorb_seterr(iorb, IOERR_CMD_SGLIST_BAD);
+
+  } else {
+    iorb_seterr(iorb, IOERR_CMD_ADD_SOFTWARE_FAILURE);
+  }
+
+  return(rc);
+}
+
+/******************************************************************************
+ * Verify readability of sectors on AHCI device. This function is not expected
+ * to be called.
+ */
+int atapi_verify(IORBH _far *iorb, int slot)
+{
+  ddprintf("atapi_verify called\n");
+  iorb_seterr(iorb, IOERR_CMD_NOT_SUPPORTED);
+  return(-1);
+}
+
+/******************************************************************************
+ * Write sectors to AHCI device. This function is not expected to be called.
+ */
+int atapi_write(IORBH _far *iorb, int slot)
+{
+  ddprintf("atapi_write called\n");
+  iorb_seterr(iorb, IOERR_CMD_NOT_SUPPORTED);
+  return(-1);
+}
+
+/******************************************************************************
+ * Execute ATAPI command.
+ */
+int atapi_execute_cdb(IORBH _far *iorb, int slot)
+{
+  IORB_ADAPTER_PASSTHRU _far *pt = (IORB_ADAPTER_PASSTHRU _far *) iorb;
+  int rc;
+  u8 cdb[ATAPI_MAX_CDB_LEN];
+  u16 cdb_len;
+
+  if (pt->ControllerCmdLen > ATAPI_MAX_CDB_LEN) {
+    iorb_seterr(iorb, IOERR_CMD_SYNTAX);
+    return -1;
+  }
+  /* AHCI requires 12 or 16 byte commands */
+  atapi_pad_cdb(pt->pControllerCmd, pt->ControllerCmdLen,
+                (u8 _far *) cdb, (u16 _far *) &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(iorb), iorb_unit_port(iorb),
+               iorb_unit_device(iorb), slot, ATA_CMD_PACKET,
+               AP_ATAPI_CMD, (void _far *) cdb, cdb_len,
+               AP_SGLIST, pt->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(iorb, 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 _far *iorb, int slot)
+{
+  SCSI_STATUS_BLOCK _far *ssb;
+  ADD_WORKSPACE _far *aws = add_workspace(iorb);
+  int rc;
+  u8 cdb[ATAPI_MIN_CDB_LEN];
+  ATAPI_CDB_6 _far *pcdb = (ATAPI_CDB_6 _far *) cdb;
+  size_t sense_buf_len = ATAPI_SENSE_LEN;
+
+  if ((iorb->RequestControl & IORB_REQ_STATUSBLOCK) &&
+      iorb->StatusBlockLen >= sizeof(*ssb) && iorb->pStatusBlock != 0) {
+
+    /* 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) */
+    ssb = (SCSI_STATUS_BLOCK _far *) (((u32) iorb & 0xffff0000U) +
+                                       (u16) iorb->pStatusBlock);
+    if (ssb->Flags & 0x0008U) {
+      iorb_seterr(iorb, 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 = malloc(sense_buf_len)) == NULL) {
+    iorb_seterr(iorb, 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(iorb),
+               iorb_unit_port(iorb),
+               iorb_unit_device(iorb),
+               slot,
+               ATA_CMD_PACKET,
+               AP_ATAPI_CMD, (void _far*) cdb, sizeof(cdb),
+               AP_VADDR, (void _far *) aws->buf, sense_buf_len,
+               AP_FEATURES,  ATAPI_FEAT_DMA,
+               AP_END);
+
+  if (rc > 0) {
+    iorb_seterr(iorb, IOERR_CMD_SGLIST_BAD);
+
+  } else if (rc < 0) {
+    /* we failed to get info about an error -> return
+     * non specific device error
+     */
+    iorb_seterr(iorb, 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 _far *iorb)
+{
+  SCSI_STATUS_BLOCK _far *ssb;
+  ADD_WORKSPACE _far *aws = add_workspace(iorb);
+  ATAPI_SENSE_DATA *psd = (ATAPI_SENSE_DATA *) aws->buf;
+
+  dphex(psd, sizeof(*psd), "sense buffer:\n");
+
+  if ((iorb->RequestControl & IORB_REQ_STATUSBLOCK) &&
+      iorb->StatusBlockLen >= sizeof(*ssb) && iorb->pStatusBlock != 0) {
+
+    /* copy sense data to IORB */
+    ssb = (SCSI_STATUS_BLOCK _far *) (((u32) iorb & 0xffff0000U) +
+                                       (u16) iorb->pStatusBlock);
+    ssb->AdapterErrorCode = 0;
+    ssb->TargetStatus = SCSI_STAT_CHECKCOND;
+    ssb->ResidualLength = 0;
+    memset(ssb->AdapterDiagInfo, 0x00, sizeof(ssb->AdapterDiagInfo));
+
+    if (ssb->SenseData != NULL) {
+      memcpy(ssb->SenseData, psd, ssb->ReqSenseLen);
+      ssb->Flags |= STATUS_SENSEDATA_VALID;
+    }
+    iorb->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(iorb, IOERR_DEVICE_NONSPECIFIC);
+    break;
+
+  case ASENSE_NOT_READY:
+    iorb_seterr(iorb, IOERR_UNIT_NOT_READY);
+    break;
+
+  case ASENSE_UNIT_ATTENTION:
+    iorb_seterr(iorb, IOERR_MEDIA_CHANGED);
+    break;
+
+  case ASENSE_MEDIUM_ERROR:
+    iorb_seterr(iorb, IOERR_MEDIA);
+    break;
+
+  case ASENSE_ILLEGAL_REQUEST:
+    iorb_seterr(iorb, IOERR_CMD_SYNTAX);
+    break;
+
+  case ASENSE_DATA_PROTECT:
+    iorb_seterr(iorb, IOERR_MEDIA_WRITE_PROTECT);
+    break;
+
+  case ASENSE_BLANK_CHECK:
+    iorb_seterr(iorb, IOERR_MEDIA_NOT_FORMATTED);
+    break;
+
+  case ASENSE_ABORTED_COMMAND:
+  case ASENSE_COPY_ABORTED:
+    iorb_seterr(iorb, IOERR_CMD_ABORTED);
+    break;
+
+  default:
+    iorb_seterr(iorb, 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 _far *cmd_in, u16 cmd_in_len,
+                  u8 _far *cmd_out, u16 _far *cmd_out_len)
+{
+  ATAPI_CDB_12 _far *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 _far *) 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/1.12/src/os2ahci/atapi.h
===================================================================
--- /tags/1.12/src/os2ahci/atapi.h	(revision 97)
+++ /tags/1.12/src/os2ahci/atapi.h	(revision 97)
@@ -0,0 +1,175 @@
+/******************************************************************************
+ * atapi.h - ATAPI structures and macros for os2ahci driver
+ *
+ * Copyright (c) 2011 thi.guten Software Development
+ * Copyright (c) 2011 Mensys B.V.
+ *
+ * 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 _far *iorb, int slot);
+extern int     atapi_unit_ready       (IORBH _far *iorb, int slot);
+extern int     atapi_read             (IORBH _far *iorb, int slot);
+extern int     atapi_verify           (IORBH _far *iorb, int slot);
+extern int     atapi_write            (IORBH _far *iorb, int slot);
+extern int     atapi_execute_cdb      (IORBH _far *iorb, int slot);
+extern int     atapi_req_sense        (IORBH _far *iorb, int slot);
+
Index: /tags/1.12/src/os2ahci/ctxhook.c
===================================================================
--- /tags/1.12/src/os2ahci/ctxhook.c	(revision 97)
+++ /tags/1.12/src/os2ahci/ctxhook.c	(revision 97)
@@ -0,0 +1,434 @@
+/******************************************************************************
+ * ctxhook.c - context hooks (kernel thread functions) for os2ahci
+ *
+ * Copyright (c) 2011 thi.guten Software Development
+ * Copyright (c) 2011 Mensys B.V.
+ *
+ * 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 restart_ctxhook(ULONG parm)
+{
+  IORB_QUEUE done_queue;
+  AD_INFO *ai;
+  IORBH _far *problem_iorb;
+  IORBH _far *iorb;
+  IORBH _far *next = NULL;
+  u8 _far *port_mmio;
+  int rearm_ctx_hook = 0;
+  int need_reset;
+  int ccs;
+  int a;
+  int p;
+
+  dprintf("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);
+        problem_iorb = NULL;
+        need_reset = 0;
+
+        dprintf("port %d, TF_DATA: 0x%lx\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) & 0x0f);
+
+        for (iorb = ai->ports[p].iorb_queue.root; iorb != NULL; iorb = next) {
+          ADD_WORKSPACE _far *aws = add_workspace(iorb);
+          next = iorb->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 comand that failed */
+                problem_iorb = iorb;
+              }
+            }
+            /* we can requeue all IORBs unconditionally (see function comment) */
+            iorb_requeue(iorb);
+          }
+        }
+
+        /* sanity check: issued command bitmaps should be 0 now */
+        if (ai->ports[p].ncq_cmds != 0 || ai->ports[p].reg_cmds != 0) {
+          dprintf("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 (problem_iorb != NULL) {
+          /* 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(problem_iorb, IOERR_DEVICE_NONSPECIFIC);
+            iorb_queue_del(&ai->ports[p].iorb_queue, problem_iorb);
+            iorb_queue_add(&done_queue, problem_iorb);
+
+          } else {
+            /* get sense information */
+            ADD_WORKSPACE _far *aws = add_workspace(problem_iorb);
+            int d = iorb_unit_device(problem_iorb);
+            int (*req_sense)(IORBH _far *, int) = (ai->ports[p].devs[d].atapi) ?
+                                                   atapi_req_sense : ata_req_sense;
+
+            aws->processing = 1;
+            aws->queued_hw = 1;
+
+            if (req_sense(problem_iorb, 0) == 0) {
+              /* execute request sense on slot #0 before anything else comes along */
+              ADD_StartTimerMS(&aws->timer, 5000, (PFN) timeout_callback, iorb, 0);
+              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, problem_iorb);
+              iorb_queue_add(&done_queue, problem_iorb);
+            }
+          }
+        }
+      }
+    }
+  }
+
+  spin_unlock(drv_lock);
+
+  /* call notification routine on all IORBs which have completed */
+  for (iorb = done_queue.root; iorb != NULL; iorb = next) {
+    next = iorb->pNxtIORB;
+
+    spin_lock(drv_lock);
+    aws_free(add_workspace(iorb));
+    spin_unlock(drv_lock);
+
+    iorb_complete(iorb);
+  }
+
+  /* restart engine to resume IORB processing */
+  spin_lock(drv_lock);
+  trigger_engine();
+  spin_unlock(drv_lock);
+
+  dprintf("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);
+    DevHelp_ArmCtxHook(0, restart_ctxhook_h);
+  }
+}
+
+/******************************************************************************
+ * 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 reset_ctxhook(ULONG parm)
+{
+  IORB_QUEUE done_queue;
+  AD_INFO *ai;
+  IORBH _far *iorb;
+  IORBH _far *next = NULL;
+  int rearm_ctx_hook = 0;
+  int a;
+  int p;
+
+  dprintf("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 */
+    ADD_CancelTimer(th_reset_watchdog);
+    th_reset_watchdog = 0;
+  }
+
+  /* add ports of active IORBs from the abort queue to ports_to_reset[] */
+  for (iorb = abort_queue.root; iorb != NULL; iorb = next) {
+    next = iorb->pNxtIORB;
+    a = iorb_unit_adapter(iorb);
+    p = iorb_unit_port(iorb);
+    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, iorb);
+    iorb_queue_add(&done_queue, iorb);
+
+    /* reset port if the IORB has already been queued to hardware */
+    if (add_workspace(iorb)->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 (iorb = ai->ports[p].iorb_queue.root; iorb != NULL; iorb = next) {
+          next = iorb->pNxtIORB;
+          if (add_workspace(iorb)->queued_hw) {
+            /* this IORB had already been queued to HW when we reset the port */
+            if (add_workspace(iorb)->idempotent) {
+              /* We can retry this IORB; just reset its status and it will be
+               * picked up by subsequent trigger_engine() calls.
+               */
+              iorb_requeue(iorb);
+
+            } else {
+              /* we cannot retry this IORB; consider it aborted */
+              iorb->ErrorCode = IOERR_CMD_ABORTED;
+              iorb_queue_del(&ai->ports[p].iorb_queue, iorb);
+              iorb_queue_add(&done_queue, iorb);
+            }
+          }
+        }
+      }
+    }
+  }
+
+  spin_unlock(drv_lock);
+
+  /* complete all aborted IORBs */
+  for (iorb = done_queue.root; iorb != NULL; iorb = next) {
+    next = iorb->pNxtIORB;
+
+    spin_lock(drv_lock);
+    aws_free(add_workspace(iorb));
+    spin_unlock(drv_lock);
+
+    iorb->Status |= IORB_ERROR;
+    iorb_complete(iorb);
+  }
+
+  /* restart engine to resume IORB processing */
+  spin_lock(drv_lock);
+  trigger_engine();
+  spin_unlock(drv_lock);
+
+  dprintf("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);
+    DevHelp_ArmCtxHook(0, reset_ctxhook_h);
+  }
+}
+
+/******************************************************************************
+ * 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 engine_ctxhook(ULONG parm)
+{
+  int iorbs_sent;
+  int i;
+
+  dprintf("engine_ctxhook() started\n");
+
+  spin_lock(drv_lock);
+  for (i = 0; i < 10; i++) {
+    if ((iorbs_sent = trigger_engine_1()) == 0) {
+      break;
+    }
+  }
+  spin_unlock(drv_lock);
+
+  dprintf("engine_ctxhook() completed\n");
+
+  if (iorbs_sent != 0) {
+    /* need to rearm ourselves for another run */
+    msleep(250);
+    DevHelp_ArmCtxHook(0, engine_ctxhook_h);
+  }
+}
+
Index: /tags/1.12/src/os2ahci/init.asm
===================================================================
--- /tags/1.12/src/os2ahci/init.asm	(revision 97)
+++ /tags/1.12/src/os2ahci/init.asm	(revision 97)
@@ -0,0 +1,384 @@
+; ----------------------------------------------------------------------------
+; Initialization routines and segment mappings for os2ahci driver, plus
+; some low-level functions that were implemented in assembler
+
+include devhdr.inc
+
+; -----------------------------------------------------------------------------
+; Public symbols
+
+                PUBLIC  _asm_strat           ; low-level strategy routine
+                PUBLIC  _readl               ; MMIO read (32 bits)
+                PUBLIC  _writel              ; MMIO write (32 bits)
+                PUBLIC  _memset              ; C memset() implementation
+                PUBLIC  _memcpy              ; C memcpy() implementation
+                PUBLIC  _restart_hook        ; port restart context hook
+                PUBLIC  _reset_hook          ; port reset context hook
+                PUBLIC  _engine_hook         ; engine trigger context hook
+                PUBLIC  _dev_hdr             ; device driver header
+                PUBLIC  __I4M                ; 32bit signed multiply routine
+                PUBLIC  __U4M                ; 32bit unsigned multiply routine
+                PUBLIC  __U4D                ; 32bit unsigned divide routine
+                PUBLIC  __I4D                ; 32bit signed divide 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                      ; no headers after this one
+                dw      DEVLEV_3 + DEV_CHAR_DEV ; flags for ADD drivers
+                dw      _asm_strat              ; strategy routine
+                dw      0                       ; no IDC routine
+                db      "OS2AHCI$"              ; name of character device
+                dq      0                       ; 8 reserved bytes
+                dd      DEV_IOCTL2 + DEV_ADAPTER_DD + DEV_INITCOMPLETE + 20h ; 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'
+readl_dbg_fmt   db      "readl(%04x:%04x) = 0x%08lx"
+                db      10, 0
+writel_dbg_fmt  db      "writel(%04x:%04x, 0x%08lx)"
+                db      10, 0
+_DATA           ENDS
+
+CONST           SEGMENT WORD PUBLIC 'CONST'
+CONST           ENDS
+
+_BSS            SEGMENT WORD PUBLIC 'BSS'
+_BSS            ENDS
+
+c_common        SEGMENT WORD PUBLIC 'BSS'
+                EXTRN   _debug : WORD        ; global debug flag
+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 : NEAR           ; C strategy routine
+                EXTRN   _printf : NEAR            ; C printf routine
+                EXTRN   _restart_ctxhook : NEAR   ; C restart context hook
+                EXTRN   _reset_ctxhook : NEAR     ; C reset context hook
+                EXTRN   _engine_ctxhook : NEAR    ; C engine context hook
+_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
+
+                POP     BX
+                POP     ES
+                MOV     WORD PTR ES:[BX+3], AX
+                RET
+_asm_strat      ENDP
+
+                .386
+
+
+; Read long value from MMIO address; need to do this here to get real
+; 32-bit operations because at least the AHCI device in VirtualBox doesn't
+; seem to support reading 32-bit MMIO registers in two 16-bit steps.
+;
+; C prototype:  u32 readl(void _far *addr);
+_readl          PROC    NEAR
+                ENTER   0, 0
+
+                ; load 'addr' into ES:EAX
+                LES     AX, [BP+4]
+                AND     EAX, 0000FFFFh
+
+                ; read MMIO register into EDX and return it in DX:AX
+                MOV     EDX, ES:[EAX]
+
+                ; print debug message if debug level is 3+
+                CMP     _debug, 3
+                JB      no_debug
+                PUSH    EDX             ; save value read from MMIO port
+                PUSH    EDX             ; value read from MMIO address
+                PUSH    AX              ; offset of MMIO address
+                PUSH    ES              ; segment of MMIO address
+                PUSH    OFFSET readl_dbg_fmt
+                CALL    _printf
+                ADD     SP, 10
+                POP     EDX             ; restore value read from MMIO port
+
+no_debug:       MOV     EAX, EDX
+                SHR     EDX, 16
+
+                LEAVE
+                RET
+_readl          ENDP
+
+
+; Write long value to MMIO address; need to do this here to get real
+; 32-bit operations because at least the AHCI device in VirtualBox doesn't
+; seem to support reading 32-bit MMIO registers in two 16-bit steps and the
+; assumption is that this is the same with writing 32-bit MMIO registers.
+;
+; C prototype:  void writel(void _far *addr, u32 val);
+_writel         PROC    NEAR
+                ENTER   0, 0
+
+                ; load 'addr' into ES:EAX
+                LES     AX, [BP+4]
+                AND     EAX, 0000FFFFh
+
+                ; load 'val' into EDX, preserving the upper 16 bits of EBP
+                MOV     EBX, EBP
+                AND     EBP, 0000FFFFh
+                MOV     EDX, [EBP+8]
+                MOV     EBP, EBX
+
+                ; store 'val' at MMIO address in ES:EAX
+                MOV     DWORD PTR ES:[EAX], EDX
+
+                ; print debug message if debug level is 3+
+                CMP     _debug, 3
+                JB      no_debug2
+                PUSH    EDX             ; value written to MMIO address
+                PUSH    AX              ; offset of MMIO address
+                PUSH    ES              ; segment of MMIO address
+                PUSH    OFFSET writel_dbg_fmt
+                CALL    _printf
+                ADD     SP, 10
+
+no_debug2:      LEAVE
+                RET
+_writel         ENDP
+
+
+; Halfway-decent 32-bit implementation of memset().
+;
+; C prototype: void *memset(void _far *s, int c, size_t n);
+_memset         PROC    NEAR
+                ENTER   0, 0
+
+                PUSH    DI
+
+                ; load 's' into ES:EDI
+                LES     DI, [BP+4]
+                AND     EDI, 0000FFFFh
+
+                ; load 'c' into EAX, replicating the low 8 bits all over
+                MOV     BL, [BP+8]
+                MOV     BH, BL
+                MOV     AX, BX
+                SHL     EAX, 16
+                MOV     AX, BX
+
+                ; load 'n / 4' into ECX
+                MOV     CX, [BP+10]
+                SHR     CX, 2
+                AND     ECX, 0000FFFFh
+
+                ; fill 's' with 32-bit chunks of EAX
+                REP     STOSD
+
+                ; set remaining bytes (n % 4)'
+                MOV     CX, [BP+10]
+                AND     CX, 0003h
+                REP     STOSB
+
+                ; return address of 's'
+                LES     AX, [BP+4]
+                PUSH    ES
+                POP     DX
+
+                POP     DI
+
+                LEAVE
+                RET
+_memset         ENDP
+
+
+; Halfway-decent 32-bit implementation of memcpy(). 
+;
+; C prototype: void *memcpy(void _far *d, void _far *s, size_t n);
+_memcpy         PROC    NEAR
+                ENTER   0, 0
+
+                PUSH    SI
+                PUSH    DI
+                PUSH    DS
+
+                ; load 'd' into ES:EDI
+                LES     DI, [BP+4]
+                AND     EDI, 0000FFFFh
+
+                ; load 's' into DS:ESI
+                LDS     SI, [BP+8]
+                AND     ESI, 0000FFFFh
+
+                ; load 'n / 4' into ECX
+                MOV     CX, [BP+12]
+                SHR     CX, 2
+                AND     ECX, 0000FFFFh
+
+                ; copy 's' to 'd' in 32-bit chunks
+                REP     MOVSD
+
+                ; copy remaining bytes (n % 4)'
+                MOV     CX, [BP+12]
+                AND     CX, 0003h
+                REP     MOVSB
+
+                ; return address of 'd'
+                LES     AX, [BP+4]
+                PUSH    ES
+                POP     DX
+
+                POP     DS
+                POP     DI
+                POP     SI
+
+                LEAVE
+                RET
+_memcpy         ENDP
+
+
+; Port restart context hook; context hooks need to save all registers
+; and get the parameter passed to DevHelp_ArmCtxHook() in EAX, thus
+; we need a stub which calls the real context hook.
+_restart_hook   PROC    FAR
+                PUSHAD
+                PUSH    EAX
+                CALL    _restart_ctxhook
+                POP     EAX
+                POPAD
+                RET
+_restart_hook   ENDP
+
+
+; Port reset context hook; context hooks need to save all registers
+; and get the parameter passed to DevHelp_ArmCtxHook() in EAX, thus
+; we need a stub which calls the real context hook.
+_reset_hook     PROC    FAR
+                PUSHAD
+                PUSH    EAX
+                CALL    _reset_ctxhook
+                POP     EAX
+                POPAD
+                RET
+_reset_hook     ENDP
+
+
+; Engine trigger context hook; context hooks need to save all registers
+; and get the parameter passed to DevHelp_ArmCtxHook() in EAX, thus
+; we need a stub which calls the real context hook.
+_engine_hook    PROC    FAR
+                PUSHAD
+                PUSH    EAX
+                CALL    _engine_ctxhook
+                POP     EAX
+                POPAD
+                RET
+_engine_hook    ENDP
+
+
+; Unsigned long divide routine; 
+; taken from OS/2 Uniaud project, original author: Timur Tabi
+__U4D           proc    near
+                shl     edx,10h            ;; Load dx:ax into eax
+                mov     dx,ax
+                mov     eax,edx
+                xor     edx,edx            ;; Zero extend eax into edx
+                shl     ecx,10h            ;; Load cx:bx into ecx
+                mov     cx,bx
+                div     ecx                ;; Divide eax/ecx into eax
+                mov     ecx,edx            ;; Load edx into cx:bx
+                shr     ecx,10h
+                mov     bx,dx
+                mov     edx,eax            ;; Load eax into dx:ax
+                shr     edx,10h
+                ret
+__U4D           endp
+        
+; Long multiply routine; 
+; taken from OS/2 Uniaud project, original author: Timur Tabi
+__U4M           proc    near
+__I4M           label   near
+                shl     edx,10h            ;; Load dx:ax into eax
+                mov     dx,ax
+                mov     eax,edx
+                mov     dx,cx              ;; Load cx:bx into edx
+                shl     edx,10h
+                mov     dx,bx
+                mul     edx                ;; Multiply eax*edx into edx:eax
+                mov     edx,eax            ;; Load eax into dx:ax
+                shr     edx,10h
+                ret
+__U4M           endp
+
+
+; Signed long divide routine; 
+; taken from OS/2 Uniaud project, original author: Timur Tabi
+__I4D           proc    near
+                shl     edx,10h            ;; Load dx:ax into eax
+                mov     dx,ax
+                mov     eax,edx
+                cdq                        ;; Sign extend eax into edx
+                shl     ecx,10h            ;; Load cx:bx into ecx
+                mov     cx,bx
+                idiv    ecx                ;; Divide eax/ecx into eax
+                mov     ecx,edx            ;; Load edx into cx:bx
+                shr     ecx,10h
+                mov     bx,dx
+                mov     edx,eax            ;; Load eax into dx:ax
+                shr     edx,10h
+                ret
+__I4D           endp
+        
+                .286
+
+_TEXT           ENDS
+
+                END
+
Index: /tags/1.12/src/os2ahci/install.cmd
===================================================================
--- /tags/1.12/src/os2ahci/install.cmd	(revision 97)
+++ /tags/1.12/src/os2ahci/install.cmd	(revision 97)
@@ -0,0 +1,2 @@
+copy os2ahci.add c:\os2\boot\*
+copy os2ahci.sym c:\os2\boot\*
Index: /tags/1.12/src/os2ahci/ioctl.c
===================================================================
--- /tags/1.12/src/os2ahci/ioctl.c	(revision 97)
+++ /tags/1.12/src/os2ahci/ioctl.c	(revision 97)
@@ -0,0 +1,341 @@
+/******************************************************************************
+ * ioctl.c - Generic IOCTL command processing
+ *
+ * Copyright (c) 2011 thi.guten Software Development
+ * Copyright (c) 2011 Mensys B.V.
+ *
+ * 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 "ioctl.h"
+
+#include <scsi.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.
+ */
+typedef struct {
+  IORB_ADAPTER_PASSTHRU iorb;                  /* IORB */
+  SCSI_STATUS_BLOCK   ssb;                     /* SCSI status block */
+  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 */
+  UCHAR               lh[16];                  /* lock handle for VMLock() */
+} IOCTL_CONTEXT;
+
+/* -------------------------- function prototypes -------------------------- */
+
+static LIN lin(void _far *p);
+
+IORBH _far * _far _cdecl ioctl_wakeup(IORBH _far *iorb);
+
+/* ------------------------ 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(RP_GENIOCTL _far *ioctl)
+{
+  OS2AHCI_DEVLIST _far *devlst = (OS2AHCI_DEVLIST _far *) ioctl->DataPacket;
+  USHORT maxcnt = 0;
+  USHORT cnt = 0;
+  USHORT a;
+  USHORT p;
+  USHORT d;
+
+  /* verify addressability of parm buffer (number of devlst elements) */
+  if (DevHelp_VerifyAccess((SEL) ((ULONG) ioctl->ParmPacket >> 16),
+                            sizeof(USHORT),
+                            (USHORT) (ULONG) ioctl->ParmPacket,
+                            VERIFY_READONLY) != 0) {
+    return(STDON | STERR | 0x05);
+  }
+
+  maxcnt = *((USHORT _far *) ioctl->ParmPacket);
+
+  /* verify addressability of return buffer (OS2AHCI_DEVLIST) */
+  if (DevHelp_VerifyAccess((SEL) ((ULONG) devlst >> 16),
+                            offsetof(OS2AHCI_DEVLIST, devs) +
+                              sizeof(devlst->devs) * maxcnt,
+                            (USHORT) (ULONG) devlst,
+                            VERIFY_READWRITE) != 0) {
+    return(STDON | STERR | 0x05);
+  }
+
+  /* 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(STDON);
+}
+
+/******************************************************************************
+ * Adapter passthrough IOCTL. This IOCTL covers both ATA and ATAPI passthrough
+ * requests.
+ */
+USHORT ioctl_passthrough(RP_GENIOCTL _far *ioctl)
+{
+  OS2AHCI_PASSTHROUGH _far *req = (OS2AHCI_PASSTHROUGH _far *) ioctl->ParmPacket;
+  char _far *sense_buf = (char _far *) ioctl->DataPacket;
+  IOCTL_CONTEXT *ic;
+  USHORT ret;
+  USHORT a;
+  USHORT p;
+  USHORT d;
+
+  /* verify addressability of parm buffer (OS2AHCI_PASSTHROUGH) */
+  if (DevHelp_VerifyAccess((SEL) ((ULONG) req >> 16),
+                            sizeof(OS2AHCI_PASSTHROUGH),
+                            (USHORT) (ULONG) req,
+                            VERIFY_READWRITE) != 0) {
+    return(STDON | STERR | 0x05);
+  }
+
+  /* verify addressability of data buffer (sense data) */
+  if (req->sense_len > 0) {
+    if (DevHelp_VerifyAccess((SEL) ((ULONG) sense_buf >> 16),
+                              req->sense_len,
+                              (USHORT) (ULONG) sense_buf,
+                              VERIFY_READWRITE) != 0) {
+      return(STDON | STERR | 0x05);
+    }
+  }
+
+  /* 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(STDON | STERR | ERROR_I24_BAD_UNIT);
+  }
+  if ((req->buflen + 4095) / 4096 + 1 > AHCI_MAX_SG / 2) {
+    return(STDON | STERR | ERROR_I24_INVALID_PARAMETER);
+  }
+
+  /* allocate IOCTL context data */
+  if ((ic = malloc(sizeof(*ic))) == NULL) {
+    return(STDON | STERR | ERROR_I24_GEN_FAILURE);
+  }
+  memset(ic, 0x00, sizeof(*ic));
+
+  /* lock DMA transfer buffer into memory and construct S/G list */
+  if (req->buflen > 0) {
+    if (DevHelp_VMLock(VMDHL_LONG | !((req->flags & PT_WRITE) ? VMDHL_WRITE : 0),
+                       req->buf, req->buflen, lin(ic->sg_lst), lin(&ic->lh),
+                       &ic->sg_cnt) != 0) {
+      /* couldn't lock buffer and/or produce a S/G list */
+      free(ic);
+      return(STDON | STERR | ERROR_I24_INVALID_PARAMETER);
+    }
+  }
+
+  /* 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    = ioctl_wakeup;
+
+  ic->iorb.cSGList          = ic->sg_cnt;
+  ic->iorb.pSGList          = ic->sg_lst;
+  DevHelp_VirtToPhys(ic->sg_lst, &ic->iorb.ppSGLIST);
+
+  ic->iorb.ControllerCmdLen = req->cmdlen;
+  ic->iorb.pControllerCmd   = req->cmd.cdb;
+  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     = (BYTE *) &ic->ssb;
+    ic->iorb.iorbh.StatusBlockLen   = sizeof(ic->ssb);
+    ic->ssb.SenseData               = (SCSI_REQSENSE_DATA _far *) ic->sense;
+    ic->ssb.ReqSenseLen             = sizeof(ic->sense);
+    ic->iorb.iorbh.RequestControl  |= IORB_REQ_STATUSBLOCK;
+  }
+
+  /* send IORB on its way */
+  add_entry(&ic->iorb.iorbh);
+
+  /* Wait for IORB completion. On SMP kernels, ProcBlock will release
+   * all spinlocks currently held, in addition to re-enabling interrupts,
+   * so we can use spinlocks to protect the time between checking the
+   * IORB_DONE flag and calling ProcBlock.
+   *
+   * However, if OS2AHCI_SMP is not defined, we're emulating spinlocks
+   * via disabling interrupts and there's a sanity check in the emulation
+   * code to catch recursive spinlocks, thus we need to reset the emulated
+   * spinlock status before calling ProcBlock. Otherwise, the driver will
+   * will get caught in the sanity checks when processing the next IORB or
+   * interrupt while we're waiting.
+   */
+  spin_lock(drv_lock);
+  while (!(ic->iorb.iorbh.Status & IORB_DONE)) {
+#   ifndef OS2AHCI_SMP
+      drv_lock = 0;
+#   endif
+    DevHelp_ProcBlock((ULONG) (void _far *) &ic->iorb.iorbh, 30000, 1);
+    spin_lock(drv_lock);
+  }
+  spin_unlock(drv_lock);
+
+  ret = STDON;
+
+  /* map IORB error codes to device driver error codes */
+  if (ic->iorb.iorbh.Status & IORB_ERROR) {
+    ret |= STERR;
+
+    switch (ic->iorb.iorbh.ErrorCode) {
+
+    case IOERR_UNIT_NOT_READY:
+      ret |= ERROR_I24_NOT_READY;
+      break;
+
+    case IOERR_MEDIA_CHANGED:
+      ret |= ERROR_I24_DISK_CHANGE;
+      break;
+
+    case IOERR_MEDIA:
+    case IOERR_MEDIA_NOT_FORMATTED:
+      ret |= ERROR_I24_CRC;
+      break;
+
+    case IOERR_CMD_SYNTAX:
+    case IOERR_CMD_NOT_SUPPORTED:
+      ret |= ERROR_I24_BAD_COMMAND;
+      break;
+
+    case IOERR_MEDIA_WRITE_PROTECT:
+      ret |= ERROR_I24_WRITE_PROTECT;
+      break;
+
+    case IOERR_CMD_ABORTED:
+      ret |= ERROR_I24_CHAR_CALL_INTERRUPTED;
+      break;
+
+    case IOERR_RBA_ADDRESSING_ERROR:
+      ret |= ERROR_I24_SEEK;
+      break;
+
+    case IOERR_RBA_LIMIT:
+      ret |= ERROR_I24_SECTOR_NOT_FOUND;
+      break;
+
+    case IOERR_CMD_SGLIST_BAD:
+      ret |= ERROR_I24_INVALID_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 |= ERROR_I24_GEN_FAILURE;
+      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->ssb.SenseData,
+             min(ic->ssb.ReqSenseLen, req->sense_len));
+    }
+  }
+
+  free(ic);
+  if (req->buflen > 0) {
+    DevHelp_VMUnLock(lin(ic->lh));
+  }
+  return(ret);
+}
+
+/******************************************************************************
+ * Get linear address for specified virtual address.
+ */
+static LIN lin(void _far *p)
+{
+  LIN l;
+
+  if (DevHelp_VirtToLin((SEL) ((ULONG) p >> 16), (USHORT) (ULONG) p, &l) != 0) {
+    return(0);
+  }
+
+  return(l);
+}
+
+/******************************************************************************
+ * IORB notification routine; used to wake up the sleeping application thread
+ * when the IOCTL IORB is complete.
+ */
+IORBH _far * _far _cdecl ioctl_wakeup(IORBH _far *iorb)
+{
+  USHORT awake_count;
+
+  DevHelp_ProcRun((ULONG) iorb, &awake_count);
+}
+
Index: /tags/1.12/src/os2ahci/ioctl.h
===================================================================
--- /tags/1.12/src/os2ahci/ioctl.h	(revision 97)
+++ /tags/1.12/src/os2ahci/ioctl.h	(revision 97)
@@ -0,0 +1,115 @@
+/******************************************************************************
+ * ioctl.h - IOCTL structures and constants for os2ahci driver
+ *
+ * Copyright (c) 2011 thi.guten Software Development
+ * Copyright (c) 2011 Mensys B.V.
+ *
+ * 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         0x80
+#define OS2AHCI_IOCTL_GET_DEVLIST      0x01
+#define OS2AHCI_IOCTL_PASSTHROUGH      0x02
+
+/* 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 */
+  LIN     buf;            /* buffer for data transfers (32-bit linear address) */
+  USHORT  sense_len;      /* length of sense data in IOCTL DataPacket */
+} OS2AHCI_PASSTHROUGH;
+
+#pragma pack()
+
Index: /tags/1.12/src/os2ahci/libc.c
===================================================================
--- /tags/1.12/src/os2ahci/libc.c	(revision 97)
+++ /tags/1.12/src/os2ahci/libc.c	(revision 97)
@@ -0,0 +1,776 @@
+/******************************************************************************
+ * libc.c - minimal subset of C runtime library for os2ahci
+ *
+ * Copyright (c) 2011 thi.guten Software Development
+ * Copyright (c) 2011 Mensys B.V.
+ *
+ * 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 ------------------------- */
+
+#define MSG_REPLACEMENT_STRING 1178   /* empty message with a single %; used
+                                       * for printing custom messages via
+                                       * DevHelp_Save_Message() */
+
+/* heap management constants */
+#define HEAP_SIZE             8192
+#define HEAP_UNIT              128
+#define HEAP_UNIT_CNT         (HEAP_SIZE / HEAP_UNIT)
+
+/* ------------------------ typedefs and structures ------------------------ */
+
+/* mdelay() calibration status */
+typedef enum {
+  MD_NOT_CALIBRATED,                  /* delay loop not calibrated */
+  MD_CALIBRATION_START,               /* calibration run started */
+  MD_CALIBRATION_END,                 /* calibration run ended */
+  MD_CALIBRATION_DONE                 /* calibration complete */
+} MDCAL;
+
+/* -------------------------- function prototypes -------------------------- */
+
+static void             long_to_asc            (long val,
+                                                char _far *buf,
+                                                int base,
+                                                int zero, int flen);
+static void _cdecl _far mdelay_timer_callback  (ULONG timer_handle,
+                                                ULONG parm1,
+                                                ULONG parm2);
+static int              mdelay_cal_end         (void);
+
+/* ------------------------ global/static variables ------------------------ */
+
+/* debug COM port base address */
+u16 com_base = 0x03f8;
+
+static char  hex_digits[] = "0123456789abcdef";
+static ULONG mem_lock;
+static ULONG com_lock;
+
+/* message table for DosHelp_Save_Message() which prints the first string */
+static MSGTABLE init_msgtbl = {
+  MSG_REPLACEMENT_STRING,
+  1,
+  0
+};
+
+/* COM port initialization sequence */
+static struct {
+  int reg;
+  u8  data;
+} com_init_sequence[] = {
+  3, 0x80,   /* overlay divisor latch register at 0x3f8 and 0x3f9 */
+  0, 0x01,   /* set low byte of divisor to 1 (115200 baud) */
+  1, 0x00,   /* set high byte of divisor to 0 */
+  3, 0x03,   /* reset divisor latch register overlay and set 8,n,1 */
+  1, 0x00,   /* disable interrupts */
+  4, 0x0f,   /* modem control register */
+  -1, 0x00
+};
+
+/* delay loop calibration data */
+volatile MDCAL mdelay_cal_status = 0;     /* delay loop calibration status */
+volatile u32   mdelay_loops_per_ms = 0;   /* delay loop counter */
+
+/* very small heap for dynamic memory management */
+static u8 heap_buf[HEAP_SIZE];
+static u8 heap_units[HEAP_UNIT_CNT];
+
+/* ----------------------------- start of code ----------------------------- */
+
+/******************************************************************************
+ * Initialize libc components
+ */
+void init_libc(void)
+{
+  DevHelp_CreateSpinLock(&mem_lock);
+  DevHelp_CreateSpinLock(&com_lock);
+}
+
+/******************************************************************************
+ * Initialize COM port to 115200,n,8,1
+ *
+ * NOTE: Something is wrong with this code, or the init sequence, but we never
+ *       got around to fixing it because it works fine on Virtualbox, and on
+ *       physical machines we tend to have the kernel debugger running on the
+ *       same port, thus KDB will set port parameters for us. This is going
+ *       to be fixed eventually...
+ */
+void init_com(void)
+{
+  int i;
+
+  spin_lock(com_lock);
+
+  for (i = 0; com_init_sequence[i].reg != -1; i++) {
+    u16 port = com_base + com_init_sequence[i].reg;
+    u8  data = com_init_sequence[i].data;
+    _asm {
+      mov  dx, port;
+      mov  al, data;
+      out  dx, al;
+    }
+  }
+
+  spin_unlock(com_lock);
+}
+
+/******************************************************************************
+ * Print a formatted message into a string buffer. This is very basic,
+ * supporting only strings and integers (16 and 32 bits (l), decimal (d)
+ * and hex (x)). Formatting length modifiers are only supported with a single
+ * digit -- 32-bit numbers don't need more than 9 characters -- and an
+ * optional '0' in front.
+ */
+int vsprintf(char _far *buf, const char *fmt, va_list va)
+{
+  char _far *orig = buf;
+  char _far *s;
+  int lmod;
+  int fptr;
+  int zero;
+  int flen;
+
+  for (; *fmt != '\0'; fmt++) {
+    switch (*fmt) {
+
+    case '%':
+      fmt++;
+      zero = flen = 0;
+      if (*fmt >= '0' && *fmt <= '9') {
+        /* formatting length modifiers */
+        zero = (*fmt == '0') ? 1 : 0;
+        fmt += zero;
+        if ((flen = *fmt - '0') >= 1 && flen <= 9) {
+          fmt++;
+        }
+      }
+
+      /* data type modifiers */
+      lmod = (*fmt == 'l') ? 1 : 0;
+      fptr = (*fmt == 'F') ? 1 : 0;
+      fmt += lmod + fptr;
+
+      switch (*fmt) {
+
+      case 's':
+        if (fptr) {
+          char _far *p = va_arg(va, char _far *);
+          s = (p == 0) ? "[null]" : p;
+        } else {
+          char *p = va_arg(va, char *);
+          s = (p == 0) ? "[null]" : p;
+        }
+        while ((*buf = *(s++)) != '\0')
+          buf++;
+        break;
+
+      case 'c':
+        *(buf++) = (char) va_arg(va, int);
+        break;
+
+      case 'd':
+        long_to_asc((lmod) ? va_arg(va, long)
+                           : va_arg(va, int), buf, 10, zero, flen);
+        buf += strlen(buf);
+        break;
+
+      case 'x':
+        long_to_asc((lmod) ? va_arg(va, u32)
+                           : va_arg(va, u16), buf, 16, zero, flen);
+        buf += strlen(buf);
+        break;
+
+      case 'p':
+        if (fptr || lmod) {
+          u16 off = va_arg(va, u16);
+          u16 seg = va_arg(va, u16);
+          long_to_asc(seg, buf, 16, 1, 4);
+          buf += strlen(buf);
+          *(buf++) = ':';
+          long_to_asc(off, buf, 16, 1, 4);
+          buf += strlen(buf);
+        } else {
+          long_to_asc(va_arg(va, u16), buf, 16, 1, 4);
+          buf += strlen(buf);
+        }
+        break;
+
+      default:
+        *(buf++) = *fmt;
+        break;
+      }
+    break;
+
+    case '\n':
+      *(buf++) = '\r';
+      *(buf++) = '\n';
+      break;
+
+    default:
+      *(buf++) = *fmt;
+      break;
+
+    }
+  }
+
+  *buf = '\0';
+  return((int) (buf - orig));
+}
+
+/*******************************************************************************
+ * Print a formatted message into a string buffer. Relies on vsprintf()
+ */
+int sprintf(char _far *buf, const char *fmt, ...)
+{
+  va_list va;
+
+  va_start(va, fmt);
+  return(vsprintf(buf, fmt, va));
+}
+
+/******************************************************************************
+ * Print messages to serial port
+ *
+ * NOTES: This function uses a 1K buffer for the resulting message. Thus,
+ *        messages should never exceed 1024 bytes.
+ */
+void vprintf(const char *fmt, va_list va)
+{
+  static char buf[1024];
+  char *s;
+
+  spin_lock(com_lock);
+
+  vsprintf(buf, fmt, va);
+
+  /* write debug message to serial port */
+  for (s = buf; *s != '\0'; s++) {
+
+    /* inp() and outp() are redefined by the DDK in an incompatible
+     * way (only words). Instead of messing around with those
+     * definitions, it's safer and easier to put the whole thing
+     * into an _asm block.
+     *
+     * The C equivalent would look like this:
+     *
+     *   while (!(inp(com_base + 5) & 0x20));
+     *   outp(com_base, *s);
+     */
+
+    _asm {
+      /* wait until COM transmitter is idle */
+      mov  dx, com_base;
+      add  dx, 5;
+    transmitter_not_idle:
+      in   al, dx;
+      and  al, 0x20;
+      jz   transmitter_not_idle;
+
+      /* output character to be sent */
+      mov  dx, com_base;
+      mov  bx, s;
+      mov  al, [bx];
+      out  dx, al;
+    };
+  }
+
+  spin_unlock(com_lock);
+}
+
+/******************************************************************************
+ * Print messages to COM port
+ */
+void printf(const char *fmt, ...)
+{
+  va_list va;
+
+  va_start(va, fmt);
+  vprintf(fmt, va);
+}
+
+/******************************************************************************
+ * Print a message to the system console. This works only during device driver
+ * initialization.
+ *
+ * NOTE: This function uses a 1K buffer for the resulting message. Thus,
+ *       messages should never exceed 1024 bytes...
+ */
+void cprintf(const char *fmt, ...)
+{
+  static char buf[1024];
+  va_list va;
+  size_t len;
+
+  va_start(va, fmt);
+  vsprintf(buf, fmt, va);
+
+  if (debug) {
+    /* print the same message to COM1 as well */
+    printf("%s", buf);
+  }
+
+  /* remove trailing CR/LF (DevHelp_Save_Message() will add it again) */
+  if ((len = strlen(buf)) >= 2 && buf[len-1] == '\n' && buf[len-2] == '\r') {
+    buf[len-2] = '\0';
+  }
+
+  init_msgtbl.MsgStrings[0] = buf;
+  DevHelp_Save_Message((NPBYTE) &init_msgtbl);
+}
+
+/******************************************************************************
+ * Print hex buffer to COM port.
+ */
+void phex(const void _far *p, int len, const char *fmt, ...)
+{
+  va_list va;
+  const unsigned char _far *buf = p;
+  long pos = 0;
+  int i;
+
+  if (!debug) {
+    return;
+  }
+
+  /* print header */
+  va_start(va, fmt);
+  vprintf(fmt, va);
+
+  /* print hex block */
+  while (len > 0) {
+    printf("%Fp ", buf);
+
+    /* 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;
+  }
+}
+
+/******************************************************************************
+ * Return length of zero-terminated string
+ */
+size_t strlen(const char _far *s)
+{
+  int len = 0;
+
+  while (*(s++) != '\0') {
+    len++;
+  }
+  return(len);
+}
+
+/******************************************************************************
+ * Copy zero-terminated string
+ */
+char _far *strcpy(char _far *dst, const char _far *src)
+{
+  char _far *orig = dst;
+
+  while ((*(dst++) = *(src++)) != '\0');
+  return(orig);
+}
+
+/******************************************************************************
+ * Compare blocks of memory
+ */
+int memcmp(void _far *p1, void _far *p2, size_t len)
+{
+  register char _far *s1 = p1;
+  register char _far *s2 = p2;
+  int n = 0;
+
+  while (len > 0) {
+    if ((n = *(s1++) - *(s2++)) != 0) {
+      return(n);
+    }
+    len--;
+  }
+  return(0);
+}
+
+/******************************************************************************
+ * Convert a string to a long value using the specified base
+ */
+long strtol(const char _far *buf, const char _far * _far *ep, int base)
+{
+  register const char _far *s = buf;
+  long val = 0;
+  int negative = 0;
+
+  /* skip leading whitespace */
+  while (*s == ' ' || *s == '\t') {
+    s++;
+  }
+
+  /* positive or negative */
+  if (*s == '-') {
+    negative = 1;
+    s++;
+  } else if (*s == '+') {
+    s++;
+  }
+
+  /* convert string to long integer */
+  for (;; s++) {
+    int digit = (*s <= '9') ? (*s - '0') : (tolower(*s) - 'a' + 10);
+    if (digit < 0 || digit >= base) {
+      break;
+    }
+    val *= base;
+    val += digit;
+  }
+
+  if (ep != NULL) {
+    *ep = s;
+  }
+  if (negative) {
+    val = -val;
+  }
+  return(val);
+}
+
+/******************************************************************************
+ * Extremely simple and stupid implementation of malloc(). The heap is very
+ * small, only 8K at the moment, and the memory blocks are managed using a
+ * simple array of "number of heap units allocated", zero meaning this unit is
+ * available. Each heap unit is currently 128 bytes.
+ *
+ * Dynamic memory is primarily used for things like ATA identify, ATAPI
+ * sense buffers, etc. and should be freed as soon as possible, otherwise
+ * we'll quickly run out of memory.
+ */
+void *malloc(size_t len)
+{
+  u16 units = (len + HEAP_UNIT - 1) / HEAP_UNIT;
+  u16 i;
+  u16 n;
+
+  spin_lock(mem_lock);
+
+  /* find a sequence of free heap units big enough for the requested length */
+  for (i = 0; i < HEAP_UNIT_CNT; i++) {
+    if (heap_units[i] == 0) {
+      for (n = i; n < i + units && n < HEAP_UNIT_CNT; n++) {
+        if (heap_units[n] != 0) {
+          break;
+        }
+      }
+      if (n == i + units) {
+        /* found a chunk large enough; update 'heap_units[]' */
+        for (; i < n; i++) {
+          heap_units[i] = (u8) (n - i);
+        }
+        spin_unlock(mem_lock);
+        return(heap_buf + (n - units) * HEAP_UNIT);
+      }
+
+      /* keep searching... */
+      i = n;
+    } else {
+      /* skip occupied heap units */
+      i += heap_units[i] - 1;
+    }
+  }
+
+  /* out of memory */
+  spin_unlock(mem_lock);
+  dprintf("malloc(%d): out of memory\n", len);
+  return(NULL);
+}
+
+/******************************************************************************
+ * Free block of memory allocted by malloc().
+ *
+ * NOTE: This function is not reentrant, thus must be called with the driver-
+ *       level spinlock held. The main reason for this design is that most
+ *       functions that need dynamic memory are already holding the spinlock.
+ */
+void free(void *ptr)
+{
+  u8 *p = (u8 *) ptr;
+  u16 first_unit;
+  u16 units;
+  u16 i;
+
+  if (p < heap_buf || p >= heap_buf + sizeof(heap_buf) ||
+      (u16) (p - heap_buf) % HEAP_UNIT != 0) {
+    dprintf("free(0x%p): invalid pointer (heap_buf = 0x%p)\n",
+            (u16) p, (u16) heap_buf);
+    return;
+  }
+
+  /* clear unit allocation counters in heap_units[] */
+  spin_lock(mem_lock);
+
+  first_unit = (u16) (p - heap_buf) / HEAP_UNIT;
+  units = heap_units[first_unit];
+  for (i = first_unit; i < first_unit + units; i++) {
+    heap_units[i] = 0;
+  }
+
+  spin_unlock(mem_lock);
+}
+
+/******************************************************************************
+ * Calibrate 'mdelay()' loop. This is done by setting up a 1 second timer
+ * with a callback that sets 'mdelay_done' to MD_CALIBRATION_END. Then it
+ * calls mdelay() with a large milliseond value as initial delay loop counter.
+ * When the timer triggers, 'mdelay()' will stop and update the delay loop
+ * counter.
+ *
+ * This function needs to be called at device driver init time. Since it uses
+ * ADD timers, it must be called with interrupts enabled. All this is not very
+ * precise (we should wait for a clock tick before starting, ...) but we don't
+ * really need precise timers.
+ */
+void mdelay_cal(void)
+{
+  ULONG timer_handle;
+
+  dprintf("calibrating delay loop... ");
+
+  mdelay_loops_per_ms = 100000;
+  mdelay_cal_status = MD_CALIBRATION_START;
+
+  ADD_StartTimerMS(&timer_handle, 1000, (PFN) mdelay_timer_callback, 0, 0);
+  mdelay(999999999);
+  ADD_CancelTimer(timer_handle);
+
+  dprintf("done (loops per ms = %ld)\n", mdelay_loops_per_ms);
+}
+
+/******************************************************************************
+ * Wait specified number of milliseconds. This is implemented using a busy
+ * loop and is only good for delays in the millisecond range but never for more
+ * than a few milliseconds and only in situations where a proper timer won't do.
+ * As a rule of thumb, don't call this function and use ADD timers, instead.
+ *
+ * NOTES:
+ *
+ *  - Timers are problematic on x86 platforms because there's no reliable
+ *    hardware timer on all architectures and the CPU clock speed may change
+ *    while executing delay loops (AMD Cool&Quiet and Intel SpeedStep), thus
+ *    calibration routines won't really be sufficient. But this usually only
+ *    extends the delay and we don't really need a high precision timer. The
+ *    exception are things like notebooks that are clocked slower when on
+ *    battery and which got booted while on battery. Should still be OK,
+ *    though, because our requirements are not that strict.
+ *
+ *  - The code in this function is inefficient by design to make sure it
+ *    will work with future CPUs which might otherwise be too fast for
+ *    our loop counters. Part of this design is using volatile variables to
+ *    force memory operations.
+ *
+ *  - Before using this function, call mdelay_calibrate() to determine the
+ *    number of inner loops required per millisecond.
+ */
+void mdelay(u32 millies)
+{
+  volatile u32 i;
+  volatile u32 n;
+
+  for (i = 0; i < millies; i++) {
+    for (n = 0; n < mdelay_loops_per_ms; n++) {
+      if (mdelay_cal_end()) {
+        /* this is a calibration run that just ended */
+        goto complete_calibration;
+      }
+    }
+  }
+  return;
+
+complete_calibration:
+  /* complete calibration cycle */
+  if (i < 1000) {
+    /* Initial value for delay loop was too high; interpolate results for
+     * an assumed initial delay loop divided by 1000.
+     */
+    i = i * 1000 + mdelay_loops_per_ms % 1000;
+    mdelay_loops_per_ms /= 1000;
+  }
+  mdelay_loops_per_ms = (mdelay_loops_per_ms * i) / 1000;
+  mdelay_cal_status = MD_CALIBRATION_DONE;
+}
+
+/******************************************************************************
+ * Sleep specified number of milliseonds. This is implemented by yielding the
+ * CPU until the system timer value indicates we're done. This function can
+ * only be called at task time, or from a context hook.
+ *
+ * NOTE: The accuracy is limited by the OS/2 timer interrupt frequency which
+ *       can lead to intervals up to 55ms (18.2 timer interrupts per second).
+ */
+void msleep(u32 millies)
+{
+  volatile PGINFOSEG gis;
+  ULONG start;
+  ULONG end;
+  PSEL p;
+
+  if (DevHelp_GetDOSVar(DHGETDOSV_SYSINFOSEG, 0, (PPVOID) &p)) {
+    /* no global info segment; use mdelay() */
+    mdelay(millies);
+    return;
+  }
+  gis = (PGINFOSEG) ((u32) *p << 16);
+  start = gis->msecs;
+  end = start + millies;
+
+  if (end < start) {
+    /* wrap-around; wait until 'msecs' has wrapped, too */
+    while (gis->msecs >= start) {
+      DevHelp_Yield();
+    }
+  }
+
+  while (gis->msecs <= end) {
+    DevHelp_Yield();
+  }
+}
+
+/******************************************************************************
+ * 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)
+{
+  DevHelp_InternalError(msg, strlen(msg));
+}
+
+/******************************************************************************
+ * Disable interrupts. The reason for using a separate function for this is
+ * that the presence of _asm statements will disable compiler optimizations.
+ * In order to support nested calls, this function will return 0 if the
+ * interrupts were already disabled or != 0, if not.
+ *
+ * NOTE: SMP systems must use spinlocks, thus this function will only be
+ *       compiled on non-SMP builds.
+ */
+#ifndef OS2AHCI_SMP
+int disable(void)
+{
+  int rc = 0;
+
+  _asm {
+    pushf
+    pop   ax
+    and   ax, 0x0200;  /* "interrupts enabled" bit */
+    mov   rc, ax;
+    cli
+  }
+
+  return(rc);
+}
+#endif
+
+/******************************************************************************
+ * Enable interrupts. The reason for using a separate function for this is
+ * that the presence of _asm statements will disable compiler optimizations.
+ *
+ * NOTE: SMP systems must use spinlocks, thus this function will only be
+ *       compiled on non-SMP builds.
+ */
+#ifndef OS2AHCI_SMP
+void enable(void)
+{
+  _asm sti;
+}
+#endif
+
+/******************************************************************************
+ * Convert 'long' to ASCII with the specified base
+ */
+static void long_to_asc(long val, char _far *buf, int base, int zero, int flen)
+{
+  register unsigned long abs_val;
+  char tmp[80];
+  char _far *ptmp = tmp;
+  char _far *s;
+
+  if (base > 16) {
+    sprintf(buf, "[EVAL]");
+    return;
+  }
+
+  abs_val = (unsigned long) ((val < 0 && base <= 10) ? -val : val);
+  tmp[sizeof(tmp) - 1] = '\0';
+
+  for (s = ptmp + sizeof(tmp) - 2; s > ptmp; s--) {
+    *s = hex_digits[abs_val % base];
+    flen--;
+    if ((abs_val /= base) == 0) {
+      break;
+    }
+  }
+
+  /* left-pad the resulting number with zeros or spaces up to 'flen' */
+  while (flen > 0) {
+    *(--s) = (zero) ? '0' : ' ';
+    flen--;
+  }
+
+  /* prepend minus sign if val was negative and base is decimal or less */
+  if (val < 0 && base <= 0) {
+    *(--s) = '-';
+    flen--;
+  }
+
+  strcpy(buf, s);
+}
+
+/******************************************************************************
+ * Timer callback handler for 'mdelay_calibrate()'
+ */
+static void _cdecl _far mdelay_timer_callback(ULONG timer_handle,
+                                              ULONG parm1,
+                                              ULONG parm2)
+{
+  mdelay_cal_status = MD_CALIBRATION_END;
+}
+
+/******************************************************************************
+ * Determine whether an mdelay calibration run has just ended. This is in a
+ * function to prevent overzealous optimizers from removing the whole delay
+ * loop in mdelay().
+ */
+static int mdelay_cal_end(void)
+{
+  return(mdelay_cal_status == MD_CALIBRATION_END);
+}
Index: /tags/1.12/src/os2ahci/os2ahci.c
===================================================================
--- /tags/1.12/src/os2ahci/os2ahci.c	(revision 97)
+++ /tags/1.12/src/os2ahci/os2ahci.c	(revision 97)
@@ -0,0 +1,1388 @@
+/******************************************************************************
+ * os2ahci.c - main file for os2ahci driver
+ *
+ * Copyright (c) 2011 thi.guten Software Development
+ * Copyright (c) 2011 Mensys B.V.
+ *
+ * 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 "bldday.h"
+
+#include "ioctl.h"
+
+/* -------------------------- macros and constants ------------------------- */
+
+/* parse integer command line parameter */
+#define drv_parm_int(s, value, type, radix)               \
+  {                                                       \
+    char _far *_ep;                                       \
+    if ((s)[1] != ':') {                                  \
+      cprintf("missing colon (:) after /%c\n", *(s));     \
+      goto init_fail;                                     \
+    }                                                     \
+    value = (type) strtol((s) + 2,                        \
+                          (const char _far* _far*) &_ep,  \
+                          radix);                         \
+    s = _ep;                                              \
+  }
+
+/* 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;                 \
+  }
+
+/* ------------------------ typedefs and structures ------------------------ */
+
+/* -------------------------- function prototypes -------------------------- */
+
+void _cdecl  small_code_      (void);
+
+static int   add_unit_info    (IORB_CONFIGURATION _far *iorb_conf, int dt_ai,
+                               int a, int p, int d, int scsi_id);
+
+/* ------------------------ global/static variables ------------------------ */
+
+int             debug = 0;         /* if > 0, print debug messages to COM1 */
+int             thorough_scan = 1; /* if != 0, perform thorough PCI scan */
+int             init_reset;        /* if != 0, reset ports during init */
+
+PFN             Device_Help = 0;   /* pointer to device helper entry point */
+ULONG           RMFlags = 0;       /* required by resource manager library */
+PFN             RM_Help0 = NULL;   /* required by resource manager library */
+PFN             RM_Help3 = NULL;   /* required by resource manager library */
+HDRIVER         rm_drvh;           /* resource manager driver handle */
+char            rm_drvname[80];    /* driver name as returned by RM */
+USHORT          add_handle;        /* driver handle (RegisterDeviceClass) */
+UCHAR           timer_pool[TIMER_POOL_SIZE]; /* timer pool */
+
+/* resource manager driver information structure */
+DRIVERSTRUCT rm_drvinfo = {
+  "OS2AHCI",                       /* driver name */
+  "AHCI SATA Driver",              /* driver description */
+  "GNU",                           /* vendor name */
+  CMVERSION_MAJOR,                 /* RM interface version major */
+  CMVERSION_MINOR,                 /* RM interface version minor */
+  BLD_YEAR, BLD_MONTH, BLD_DAY,    /* date */
+  0,                               /* driver flags */
+  DRT_ADDDM,                       /* driver type */
+  DRS_ADD,                         /* driver sub type */
+  NULL                             /* driver callback */
+};
+
+ULONG           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 */
+
+/* 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];
+
+static char     init_msg[] = "OS2AHCI driver version %d.%02d\n";
+static char     exit_msg[] = "OS2AHCI driver *not* installed\n";
+
+/* ----------------------------- start of code ----------------------------- */
+
+/******************************************************************************
+ * OS/2 device driver main strategy function. This function is only used
+ * for initialization purposes; all other calls go directly to the adapter
+ * device driver's strategy function.
+ */
+USHORT _cdecl c_strat(RPH _far *req)
+{
+  u16 rc;
+
+  switch (req->Cmd) {
+
+  case CMDInitBase:
+    rc = init_drv((RPINITIN _far *) req);
+    break;
+
+  case CMDShutdown:
+    rc = exit_drv(((RPSAVERESTORE _far *) req)->FuncCode);
+    break;
+
+  case CMDGenIOCTL:
+    rc = gen_ioctl((RP_GENIOCTL _far *) req);
+    break;
+
+  default:
+    rc = STDON | STATUS_ERR_UNKCMD;
+    break;
+  }
+
+  return(rc);
+}
+
+/******************************************************************************
+ * Intialize the os2ahci driver. This includes command line parsing, scanning
+ * the PCI bus for supported AHCI adapters, etc.
+ */
+USHORT init_drv(RPINITIN _far *req)
+{
+  RPINITOUT _far *rsp = (RPINITOUT _far *) req;
+  DDD_PARM_LIST _far *ddd_pl = (DDD_PARM_LIST _far *) req->InitArgs;
+  APIRET rmrc;
+  char _far *cmd_line;
+  char _far *s;
+  int adapter_index = -1;
+  int port_index = -1;
+  int invert_option;
+  int optval;
+  u16 vendor;
+  u16 device;
+
+  /* set device helper entry point */
+  Device_Help = req->DevHlpEP;
+
+  /* create driver-level spinlock */
+  DevHelp_CreateSpinLock(&drv_lock);
+
+  /* initialize libc code */
+  init_libc();
+
+  /* print initialization message */
+  cprintf(init_msg, VERSION / 100, VERSION % 100);
+
+#ifdef ECS_BUILD
+  cprintf("This driver is licensed for use only in conjunction with eComStation.");
+#endif
+
+  /* register driver with resource manager */
+  if ((rmrc = RMCreateDriver(&rm_drvinfo, &rm_drvh)) != RMRC_SUCCESS) {
+    cprintf("failed to register driver with resource manager (rc = %d)\n", rmrc);
+    goto init_fail;
+  }
+
+  /* parse command line parameters */
+  cmd_line = (char _far *) ((u32) ddd_pl & 0xffff0000l) + ddd_pl->cmd_line_args;
+
+  for (s = cmd_line; *s != 0; s++) {
+    if (*s == '/') {
+      if ((invert_option = (s[1] == '!')) != 0) {
+        s++;
+      }
+      s++;
+      switch (tolower(*s)) {
+
+      case '\0':
+        /* end of command line; can only happen if command line is incorrect */
+        cprintf("incomplete command line option\n");
+        goto init_fail;
+
+      case 'c':
+        /* set COM port base address for debug messages */
+        drv_parm_int(s, com_base, u16, 16);
+        break;
+
+      case 'd':
+        /* increase debug level */
+        if (debug++ == 0) {
+          init_com();
+        }
+        break;
+
+      case 'g':
+        /* add specfied PCI ID as a supported generic AHCI adapter  */
+        drv_parm_int(s, vendor, u16, 16);
+        drv_parm_int(s, device, u16, 16);
+        if (add_pci_id(vendor, device)) {
+          cprintf("failed to add PCI ID %04x:%04x\n", vendor, device);
+          goto init_fail;
+        }
+        thorough_scan = 1;
+        break;
+
+      case 't':
+        /* perform thorough PCI scan (i.e. look for individual supported PCI IDs) */
+        thorough_scan = !invert_option;
+        break;
+
+      case 'r':
+        /* reset ports during initialization */
+	init_reset = 1;
+        break;
+
+      case 'a':
+        /* set adapter index for adapter and port-related options */
+        drv_parm_int(s, adapter_index, int, 10);
+        if (adapter_index < 0 || adapter_index >= MAX_AD) {
+          cprintf("invalid adapter index (%d)\n", adapter_index);
+          goto init_fail;
+        }
+        break;
+
+      case 'p':
+        /* set port index for port-related options */
+        drv_parm_int(s, port_index, int, 10);
+        if (port_index < 0 || port_index >= AHCI_MAX_PORTS) {
+          cprintf("invalid port index (%d)\n", port_index);
+          goto init_fail;
+        }
+        break;
+
+      case 'i':
+        /* ignore current adapter index */
+        if (adapter_index >= 0) {
+          ad_ignore |= 1U << adapter_index;
+        }
+        break;
+
+      case 's':
+        /* enable SCSI emulation for ATAPI devices */
+        set_port_option(emulate_scsi, !invert_option);
+        break;
+
+      case 'n':
+        /* enable NCQ */
+        set_port_option(enable_ncq, !invert_option);
+        break;
+
+      case 'l':
+        /* set link speed or power savings */
+        s++;
+        switch (tolower(*s)) {
+        case 's':
+          /* set link speed */
+          drv_parm_int(s, optval, int, 10);
+          set_port_option(link_speed, optval);
+          break;
+        case 'p':
+          /* set power management */
+          drv_parm_int(s, optval, int, 10);
+          set_port_option(link_power, optval);
+          break;
+        default:
+          cprintf("invalid link parameter (%c)\n", *s);
+          goto init_fail;
+        }
+        /* need to reset the port in order to establish link settings */
+        init_reset = 1;
+        break;
+
+      case '4':
+        /* enable 4K sector geometry enhancement (track size = 56) */
+        if (!invert_option) {
+          set_port_option(track_size, 56);
+        }
+        break;
+
+      default:
+        cprintf("invalid option: /%c\n", *s);
+        goto init_fail;
+      }
+    }
+  }
+
+  /* scan PCI bus for supported devices */
+  scan_pci_bus();
+
+  if (ad_info_cnt > 0) {
+    /* initialization succeeded and we found at least one AHCI adapter */
+    ADD_InitTimer(timer_pool, sizeof(timer_pool));
+    mdelay_cal();
+
+    if (DevHelp_RegisterDeviceClass("OS2AHCI", (PFN) add_entry, 0, 1,
+                                    &add_handle)) {
+      cprintf("error: couldn't register device class\n");
+      goto init_fail;
+    }
+
+    /* allocate context hooks */
+    if (DevHelp_AllocateCtxHook(mk_NPFN(restart_hook), &restart_ctxhook_h) != 0 ||
+        DevHelp_AllocateCtxHook(mk_NPFN(reset_hook), &reset_ctxhook_h) != 0 ||
+        DevHelp_AllocateCtxHook(mk_NPFN(engine_hook), &engine_ctxhook_h)) {
+      cprintf("failed to allocate task-time context hooks\n");
+      goto init_fail;
+    }
+
+    rsp->CodeEnd = (u16) end_of_code;
+    rsp->DataEnd = (u16) &end_of_data;
+    return(STDON);
+
+  } else {
+    /* no adapters found */
+    cprintf(" No adapters found.\n");
+  }
+
+init_fail:
+  /* initialization failed; set segment sizes to 0 and return error */
+  rsp->CodeEnd = 0;
+  rsp->DataEnd = 0;
+
+  /* free context hooks */
+  if (engine_ctxhook_h != 0)  DevHelp_FreeCtxHook(engine_ctxhook_h);
+  if (reset_ctxhook_h != 0)   DevHelp_FreeCtxHook(reset_ctxhook_h);
+  if (restart_ctxhook_h != 0) DevHelp_FreeCtxHook(restart_ctxhook_h);
+
+  if (rm_drvh != 0) {
+    /* remove driver from resource manager */
+    RMDestroyDriver(rm_drvh);
+  }
+
+  cprintf(exit_msg);
+  return(STDON | ERROR_I24_QUIET_INIT_FAIL);
+}
+
+/******************************************************************************
+ * 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.
+ */
+USHORT gen_ioctl(RP_GENIOCTL _far *ioctl)
+{
+  switch (ioctl->Category) {
+
+  case OS2AHCI_IOCTL_CATEGORY:
+
+    switch (ioctl->Function) {
+
+    case OS2AHCI_IOCTL_GET_DEVLIST:
+      return(ioctl_get_devlist(ioctl));
+
+    case OS2AHCI_IOCTL_PASSTHROUGH:
+      return(ioctl_passthrough(ioctl));
+
+    }
+  }
+  return(STDON | STATUS_ERR_UNKCMD);
+}
+
+/******************************************************************************
+ * 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 APM
+ * 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("exit_drv(%d) called\n", func);
+
+  if (func == 0) {
+    /* we're only interested in the second phase of the shutdown */
+    return(STDON);
+  }
+
+  apm_suspend();
+  return(STDON);
+}
+
+/******************************************************************************
+ * 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 _cdecl _far _loadds add_entry(IORBH _far *first_iorb)
+{
+  IORBH _far *iorb;
+  IORBH _far *next = NULL;
+
+  spin_lock(drv_lock);
+
+  for (iorb = first_iorb; iorb != NULL; iorb = next) {
+    /* 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 'next'.
+     */
+    next = (iorb->RequestControl | IORB_CHAIN) ? iorb->pNxtIORB : 0;
+
+    iorb->Status = 0;
+    iorb->ErrorCode = 0;
+    memset(&iorb->ADDWorkSpace, 0x00, sizeof(ADD_WORKSPACE));
+
+    if (iorb_driver_level(iorb)) {
+      /* driver-level IORB */
+      iorb->UnitHandle = 0;
+      iorb_queue_add(&driver_queue, iorb);
+
+    } else {
+      /* port-level IORB */
+      int a = iorb_unit_adapter(iorb);
+      int p = iorb_unit_port(iorb);
+      int d = iorb_unit_device(iorb);
+
+      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("warning: IORB for %d.%d.%d out of range\n", a, p, d);
+        iorb->Status = IORB_ERROR;
+        iorb->ErrorCode = IOERR_CMD_SYNTAX;
+        iorb_complete(iorb);
+        continue;
+      }
+
+      iorb_queue_add(&ad_infos[a].ports[p].iorb_queue, iorb);
+    }
+  }
+
+  /* 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.
+ */
+void trigger_engine(void)
+{
+  int i;
+
+  for (i = 0; i < 3; 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.
+   */
+  DevHelp_ArmCtxHook(0, engine_ctxhook_h);
+}
+
+/******************************************************************************
+ * 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 _far *iorb;
+  IORBH _far *next;
+  int iorbs_sent = 0;
+  int a;
+  int p;
+
+  iorbs_sent = 0;
+
+  /* process driver-level IORBs */
+  if ((iorb = driver_queue.root) != NULL && !add_workspace(iorb)->processing) {
+    send_iorb(iorb);
+    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 */
+      next = NULL;
+      for (iorb = ai->ports[p].iorb_queue.root; iorb != NULL; iorb = next) {
+        next = iorb->pNxtIORB;
+        if (!add_workspace(iorb)->processing) {
+          send_iorb(iorb);
+          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 _far *iorb)
+{
+  /* 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(iorb)->processing = 1;
+  spin_unlock(drv_lock);
+
+  switch (iorb->CommandCode) {
+
+  case IOCC_CONFIGURATION:
+    iocc_configuration(iorb);
+    break;
+
+  case IOCC_DEVICE_CONTROL:
+    iocc_device_control(iorb);
+    break;
+
+  case IOCC_UNIT_CONTROL:
+    iocc_unit_control(iorb);
+    break;
+
+  case IOCC_GEOMETRY:
+    iocc_geometry(iorb);
+    break;
+
+  case IOCC_EXECUTE_IO:
+    iocc_execute_io(iorb);
+    break;
+
+  case IOCC_UNIT_STATUS:
+    iocc_unit_status(iorb);
+    break;
+
+  case IOCC_ADAPTER_PASSTHRU:
+    iocc_adapter_passthru(iorb);
+    break;
+
+  default:
+    /* unsupported call */
+    iorb_seterr(iorb, IOERR_CMD_NOT_SUPPORTED);
+    iorb_done(iorb);
+    break;
+  }
+
+  /* re-aquire spinlock before returning to trigger_engine() */
+  spin_lock(drv_lock);
+}
+
+/******************************************************************************
+ * Handle IOCC_CONFIGURATION requests.
+ */
+void iocc_configuration(IORBH _far *iorb)
+{
+  int a;
+
+  switch (iorb->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("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);
+      }
+
+      /* register APM hook */
+      apm_init();
+    }
+    iorb_done(iorb);
+    break;
+
+  case IOCM_GET_DEVICE_TABLE:
+    /* construct a device table */
+    iocm_device_table(iorb);
+    break;
+
+  default:
+    iorb_seterr(iorb, IOERR_CMD_NOT_SUPPORTED);
+    iorb_done(iorb);
+    break;
+  }
+}
+
+/******************************************************************************
+ * Handle IOCC_DEVICE_CONTROL requests.
+ */
+void iocc_device_control(IORBH _far *iorb)
+{
+  AD_INFO *ai = ad_infos + iorb_unit_adapter(iorb);
+  IORBH _far *ptr;
+  IORBH _far *next = NULL;
+  int p = iorb_unit_port(iorb);
+  int d = iorb_unit_device(iorb);
+
+  switch (iorb->CommandModifier) {
+
+  case IOCM_ABORT:
+    /* abort all pending commands on specified port and device */
+    spin_lock(drv_lock);
+    for (ptr = ai->ports[p].iorb_queue.root; ptr != NULL; ptr = next) {
+      next = ptr->pNxtIORB;
+      /* move all matching IORBs to the abort queue */
+      if (ptr != iorb && iorb_unit_device(ptr) == d) {
+        iorb_queue_del(&ai->ports[p].iorb_queue, ptr);
+        iorb_queue_add(&abort_queue, ptr);
+        ptr->ErrorCode = IOERR_CMD_ABORTED;
+      }
+    }
+    spin_unlock(drv_lock);
+
+    /* trigger reset context hook which will finish the abort processing */
+    DevHelp_ArmCtxHook(0, reset_ctxhook_h);
+    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(iorb, 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(iorb, IOERR_CMD_NOT_SUPPORTED);
+    break;
+
+  default:
+    iorb_seterr(iorb, IOERR_CMD_NOT_SUPPORTED);
+    break;
+  }
+
+  iorb_done(iorb);
+}
+
+/******************************************************************************
+ * Handle IOCC_UNIT_CONTROL requests.
+ */
+void iocc_unit_control(IORBH _far *iorb)
+{
+  IORB_UNIT_CONTROL _far *iorb_uc = (IORB_UNIT_CONTROL _far *) iorb;
+  int a = iorb_unit_adapter(iorb);
+  int p = iorb_unit_port(iorb);
+  int d = iorb_unit_device(iorb);
+
+  spin_lock(drv_lock);
+  switch (iorb->CommandModifier) {
+
+  case IOCM_ALLOCATE_UNIT:
+    /* allocate unit for exclusive access */
+    if (ad_infos[a].ports[p].devs[d].allocated) {
+      iorb_seterr(iorb, 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(iorb, 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(iorb, IOERR_UNIT_NOT_ALLOCATED);
+      break;
+    }
+    ad_infos[a].ports[p].devs[d].unit_info = iorb_uc->pUnitInfo;
+    break;
+
+  default:
+    iorb_seterr(iorb, IOERR_CMD_NOT_SUPPORTED);
+    break;
+  }
+
+  spin_unlock(drv_lock);
+  iorb_done(iorb);
+}
+
+/******************************************************************************
+ * 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 _far *iorb)
+{
+  IORB_CONFIGURATION _far *iorb_conf;
+  DEVICETABLE _far *dt;
+  char _far *pos;
+  int scsi_units = 0;
+  int scsi_id = 1;
+  int rc;
+  int dta;
+  int a;
+  int p;
+  int d;
+
+  iorb_conf = (IORB_CONFIGURATION _far *) iorb;
+  dt = iorb_conf->pDeviceTable;
+
+  spin_lock(drv_lock);
+
+  /* initialize device table header */
+  dt->ADDLevelMajor = ADD_LEVEL_MAJOR;
+  dt->ADDLevelMinor = ADD_LEVEL_MINOR;
+  dt->ADDHandle     = add_handle;
+  dt->TotalAdapters = ad_info_cnt + 1;
+
+  /* set start of adapter and device information tables */
+  pos = (char _far *) (dt->pAdapter + dt->TotalAdapters);
+
+  /* go through all adapters, including the virtual SCSI adapter */
+  for (dta = 0; dta < dt->TotalAdapters; dta++) {
+    ADAPTERINFO _far *ptr = (ADAPTERINFO _far *) pos;
+
+    /* sanity check for sufficient space in device table */
+    if ((u32) (ptr + 1) - (u32) dt > iorb_conf->DeviceTableLen) {
+      dprintf("error: device table provided by DASD too small\n");
+      iorb_seterr(iorb, IOERR_CMD_SW_RESOURCE);
+      goto iocm_device_table_done;
+    }
+
+    dt->pAdapter[dta] = (ADAPTERINFO _near *) ((u32) ptr & 0xffff);
+    memset(ptr, 0x00, sizeof(*ptr));
+
+    ptr->AdapterIOAccess = AI_IOACCESS_BUS_MASTER;
+    ptr->AdapterHostBus  = AI_HOSTBUS_OTHER | AI_BUSWIDTH_32BIT;
+    ptr->AdapterFlags    = AF_16M | AF_HW_SCATGAT;
+    ptr->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;
+
+      ptr->AdapterDevBus = AI_DEVBUS_ST506 | AI_DEVBUS_32BIT;
+      sprintf(ptr->AdapterName, "AHCI_%d", dta);
+
+      if (!ad_info->port_scan_done) {
+        /* first call; need to scan AHCI hardware for devices */
+        if (ad_info->busy) {
+          dprintf("error: port scan requested while adapter was busy\n");
+          iorb_seterr(iorb, 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("error: port scan failed on adapter #%d\n", dta);
+          iorb_seterr(iorb, 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]) {
+              /* only report this unit as SCSI unit */
+              scsi_units++;
+              continue;
+            }
+            if (add_unit_info(iorb_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 */
+        dt->TotalAdapters--;
+        break;
+      }
+
+      /* set adapter name and bus type to mimic a SCSI controller */
+      ptr->AdapterDevBus = AI_DEVBUS_SCSI_2 | AI_DEVBUS_16BIT;
+      sprintf(ptr->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(iorb_conf, dta, a, p, d, scsi_id++)) {
+                goto iocm_device_table_done;
+              }
+            }
+          }
+        }
+      }
+    }
+
+    /* calculate offset for next adapter */
+    pos = (char _far *) (ptr->UnitInfo + ptr->AdapterUnits);
+  }
+
+iocm_device_table_done:
+  spin_unlock(drv_lock);
+  iorb_done(iorb);
+}
+
+/******************************************************************************
+ * Handle IOCC_GEOMETRY requests.
+ */
+void iocc_geometry(IORBH _far *iorb)
+{
+  switch (iorb->CommandModifier) {
+
+  case IOCM_GET_MEDIA_GEOMETRY:
+  case IOCM_GET_DEVICE_GEOMETRY:
+    add_workspace(iorb)->idempotent = 1;
+    ahci_get_geometry(iorb);
+    break;
+
+  default:
+    iorb_seterr(iorb, IOERR_CMD_NOT_SUPPORTED);
+    iorb_done(iorb);
+  }
+}
+
+/******************************************************************************
+ * Handle IOCC_EXECUTE_IO requests.
+ */
+void iocc_execute_io(IORBH _far *iorb)
+{
+  switch (iorb->CommandModifier) {
+
+  case IOCM_READ:
+    add_workspace(iorb)->idempotent = 1;
+    ahci_read(iorb);
+    break;
+
+  case IOCM_READ_VERIFY:
+    add_workspace(iorb)->idempotent = 1;
+    ahci_verify(iorb);
+    break;
+
+  case IOCM_WRITE:
+    add_workspace(iorb)->idempotent = 1;
+    ahci_write(iorb);
+    break;
+
+  case IOCM_WRITE_VERIFY:
+    add_workspace(iorb)->idempotent = 1;
+    ahci_write(iorb);
+    break;
+
+  default:
+    iorb_seterr(iorb, IOERR_CMD_NOT_SUPPORTED);
+    iorb_done(iorb);
+  }
+}
+
+/******************************************************************************
+ * Handle IOCC_UNIT_STATUS requests.
+ */
+void iocc_unit_status(IORBH _far *iorb)
+{
+  switch (iorb->CommandModifier) {
+
+  case IOCM_GET_UNIT_STATUS:
+    add_workspace(iorb)->idempotent = 1;
+    ahci_unit_ready(iorb);
+    break;
+
+  default:
+    iorb_seterr(iorb, IOERR_CMD_NOT_SUPPORTED);
+    iorb_done(iorb);
+  }
+}
+
+/******************************************************************************
+ * Handle IOCC_ADAPTER_PASSTHROUGH requests.
+ */
+void iocc_adapter_passthru(IORBH _far *iorb)
+{
+  switch (iorb->CommandModifier) {
+
+  case IOCM_EXECUTE_CDB:
+    add_workspace(iorb)->idempotent = 0;
+    ahci_execute_cdb(iorb);
+    break;
+
+  case IOCM_EXECUTE_ATA:
+    add_workspace(iorb)->idempotent = 0;
+    ahci_execute_ata(iorb);
+    break;
+
+  default:
+    iorb_seterr(iorb, IOERR_CMD_NOT_SUPPORTED);
+    iorb_done(iorb);
+  }
+}
+
+/******************************************************************************
+ * Add an IORB to the specified queue. This function must be called with the
+ * adapter-level spinlock aquired.
+ */
+void iorb_queue_add(IORB_QUEUE _far *queue, IORBH _far *iorb)
+{
+  if (iorb_priority(iorb) {
+    /* priority IORB; insert at first position */
+    iorb->pNxtIORB = queue->root;
+    queue->root = iorb;
+
+  } else {
+    /* append IORB to end of queue */
+    iorb->pNxtIORB = NULL;
+
+    if (queue->root == NULL) {
+      queue->root = iorb;
+    } else {
+      queue->tail->pNxtIORB = iorb;
+    }
+    queue->tail = iorb;
+  }
+
+  if (debug) {
+    /* 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 = 1;
+
+    if ((u32) queue >> 16 == (u32) (void _far *) &queue >> 16) {
+      /* this queue is on the stack */
+      queue_type = "local";
+      min_debug = 2;
+
+    } else if (queue == &driver_queue) {
+      queue_type = "driver";
+
+    } else if (queue == &abort_queue) {
+      queue_type = "abort";
+      min_debug = 2;
+
+    } else {
+      queue_type = "port";
+    }
+
+    if (debug >= min_debug) {
+      printf("IORB %Fp queued (cmd = %d/%d, queue = %Fp [%s], timeout = %ld)\n",
+             iorb, iorb->CommandCode, iorb->CommandModifier, queue, queue_type,
+             iorb->Timeout);
+    }
+  }
+}
+
+/******************************************************************************
+ * Remove an IORB from the specified queue. This function must be called with
+ * the adapter-level spinlock aquired.
+ */
+int iorb_queue_del(IORB_QUEUE _far *queue, IORBH _far *iorb)
+{
+  IORBH _far *_iorb;
+  IORBH _far *_prev = NULL;
+  int found = 0;
+
+  for (_iorb = queue->root; _iorb != NULL; _iorb = _iorb->pNxtIORB) {
+    if (_iorb == iorb) {
+      /* found the IORB to be removed */
+      if (_prev != NULL) {
+        _prev->pNxtIORB = _iorb->pNxtIORB;
+      } else {
+        queue->root = _iorb->pNxtIORB;
+      }
+      if (_iorb == queue->tail) {
+        queue->tail = _prev;
+      }
+      found = 1;
+      break;
+    }
+    _prev = _iorb;
+  }
+
+  if (found) {
+    ddprintf("IORB %Fp removed (queue = %Fp)\n", iorb, queue);
+  } else {
+    dprintf("IORB %Fp not found in queue %Fp\n", iorb, queue);
+  }
+
+  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 _far *iorb, USHORT error_code)
+{
+  iorb->ErrorCode = error_code;
+  iorb->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 _far *iorb)
+{
+  int a = iorb_unit_adapter(iorb);
+  int p = iorb_unit_port(iorb);
+
+  /* remove IORB from corresponding queue */
+  spin_lock(drv_lock);
+  if (iorb_driver_level(iorb)) {
+    iorb_queue_del(&driver_queue, iorb);
+  } else {
+    iorb_queue_del(&ad_infos[a].ports[p].iorb_queue, iorb);
+  }
+  aws_free(add_workspace(iorb));
+  spin_unlock(drv_lock);
+
+  iorb_complete(iorb);
+}
+
+/******************************************************************************
+ * 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 _far *iorb)
+{
+  iorb->Status |= IORB_DONE;
+
+  dprintf("IORB %Fp complete (status = 0x%04x, error = 0x%04x)\n",
+          iorb, iorb->Status, iorb->ErrorCode);
+
+  if (iorb->RequestControl & IORB_ASYNC_POST) {
+    iorb->NotifyAddress(iorb);
+  }
+}
+
+/******************************************************************************
+ * 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 _far *iorb)
+{
+  ADD_WORKSPACE _far *aws = add_workspace(iorb);
+  u16 no_ncq = aws->no_ncq;
+
+  aws_free(aws);
+  memset(aws, 0x00, sizeof(*aws));
+  aws->no_ncq = no_ncq;
+}
+
+/******************************************************************************
+ * 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 _far *aws)
+{
+  if (aws->timer != 0) {
+    ADD_CancelTimer(aws->timer);
+    aws->timer = 0;
+  }
+
+  if (aws->buf != NULL) {
+    free(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)
+{
+  spin_lock(drv_lock);
+  while (ai->busy) {
+    spin_unlock(drv_lock);
+    msleep(250);
+    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 _cdecl _far timeout_callback(ULONG timer_handle, ULONG p1,
+                                  ULONG p2)
+{
+  IORBH _far *iorb = (IORBH _far *) p1;
+  int a = iorb_unit_adapter(iorb);
+  int p = iorb_unit_port(iorb);
+
+  ADD_CancelTimer(timer_handle);
+  dprintf("timeout for IORB %Fp\n", iorb);
+
+  /* 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, iorb) == 0) {
+    iorb_queue_add(&abort_queue, iorb);
+    iorb->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.
+   */
+  DevHelp_ArmCtxHook(0, reset_ctxhook_h);
+
+  /* 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.
+   */
+  ADD_StartTimerMS(&th_reset_watchdog, 5000, (PFN) reset_watchdog, 0, 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 _cdecl _far reset_watchdog(ULONG timer_handle, ULONG p1,
+                                ULONG p2)
+{
+  /* reset watchdog timer */
+  ADD_CancelTimer(timer_handle);
+  dprintf("reset watchdog invoked\n");
+
+  /* call context hook manually */
+  reset_ctxhook(0);
+}
+
+/******************************************************************************
+ * small_code_ - this dummy func resolves the undefined reference linker
+ * error that occurrs when linking WATCOM objects with DDK's link.exe
+ */
+void _cdecl small_code_(void)
+{
+}
+
+/******************************************************************************
+ * 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 _far *iorb_conf, int dta,
+                         int a, int p, int d, int scsi_id)
+{
+  DEVICETABLE _far *dt = iorb_conf->pDeviceTable;
+  ADAPTERINFO _far *ptr = (ADAPTERINFO _far *) (((u32) dt & 0xffff0000U) +
+                                                 (u16) dt->pAdapter[dta]);
+  UNITINFO _far *ui = ptr->UnitInfo + ptr->AdapterUnits;
+  AD_INFO *ai = ad_infos + a;
+
+  if ((u32) (ui + 1) - (u32) dt > iorb_conf->DeviceTableLen) {
+    dprintf("error: device table provided by DASD too small\n");
+    iorb_seterr(&iorb_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(ui, 0x00, sizeof(*ui));
+    ui->AdapterIndex = dta;                 /* device table adapter index */
+    ui->UnitHandle   = iorb_unit(a, p, d);  /* physical adapter index */
+    ui->UnitIndex    = ptr->AdapterUnits;
+    ui->UnitType     = ai->ports[p].devs[d].dev_type;
+    ui->QueuingCount = ai->ports[p].devs[d].ncq_max;;
+    if (ai->ports[p].devs[d].removable) {
+      ui->UnitFlags |= UF_REMOVABLE;
+    }
+    if (scsi_id > 0) {
+      /* set fake SCSI ID for this unit */
+      ui->UnitSCSITargetID = scsi_id;
+    }
+  } else {
+    /* copy updated device (unit) information (IOCM_CHANGE_UNITINFO) */
+    memcpy(ui, ai->ports[p].devs[d].unit_info, sizeof(*ui));
+  }
+
+  ptr->AdapterUnits++;
+  return(0);
+}
+
Index: /tags/1.12/src/os2ahci/os2ahci.def.template
===================================================================
--- /tags/1.12/src/os2ahci/os2ahci.def.template	(revision 97)
+++ /tags/1.12/src/os2ahci/os2ahci.def.template	(revision 97)
@@ -0,0 +1,17 @@
+library os2ahci
+Description '$@#Mensys:0.0.0#@##1## 2011/05/02 11:41:00      ECSDEVEL::::00::@@AHCI Driver. (c) Mensys BV 2011'
+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/1.12/src/os2ahci/os2ahci.h
===================================================================
--- /tags/1.12/src/os2ahci/os2ahci.h	(revision 97)
+++ /tags/1.12/src/os2ahci/os2ahci.h	(revision 97)
@@ -0,0 +1,521 @@
+/******************************************************************************
+ * os2ahci.h - main header file for os2ahci driver
+ *
+ * Copyright (c) 2011 thi.guten Software Development
+ * Copyright (c) 2011 Mensys B.V.
+ *
+ * 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.sys
+ * are expected to be byte-aligned without the need of explicit pragma pack()
+ * directives. Where possible, the structures are layed out such that words
+ * and dwords are aligned at least on 2-byte boundaries.
+ */
+
+#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>
+
+#ifdef __WATCOMC__
+/* include WATCOM specific DEVHELP stubs */
+#include <devhelp.h>
+#else
+#include <dhcalls.h>
+#endif
+
+#include <addcalls.h>
+#include <rmcalls.h>
+#include <devclass.h>
+#include <devcmd.h>
+#include <rmbase.h>
+
+#include "ahci.h"
+#include "version.h"
+
+/* -------------------------- macros and constants ------------------------- */
+
+#define MAX_AD               8       /* maximum number of adapters */
+
+/* Timer pool size. In theory, we need one timer per outstanding command plus
+ * a few miscellaneous timers but it's unlikely we'll ever have outstanding
+ * commands on all devices on all ports on all apapters -- this would be
+ * 8 * 32 * 32 = 8192 outstanding commands on a maximum of 8 * 32 * 15 = 3840
+ * devices and that's a bit of an exaggeration. It should be more than enough
+ * to have 128 timers.
+ */
+#define TIMER_COUNT        128
+#define TIMER_POOL_SIZE    (sizeof(ADD_TIMER_POOL) + \
+                            TIMER_COUNT * sizeof(ADD_TIMER_DATA))
+
+/* default command timeout (can be overwritten in the IORB) */
+#define DEFAULT_TIMEOUT  30000
+
+/* max/min macros */
+#define max(a, b)  (a) > (b) ? (a) : (b)
+#define min(a, b)  (a) < (b) ? (a) : (b)
+
+/* debug output macros */
+#define dprintf   if (debug > 0) printf
+#define dphex     if (debug > 0) phex
+#define ddprintf  if (debug > 1) printf
+#define ddphex    if (debug > 1) phex
+#define dddprintf if (debug > 2) printf
+#define dddphex   if (debug > 2) phex
+
+/* adapter number from AD_INFO pointer; mainly for dprintf() purposes */
+#define ad_no(ai) (((u16) ai - (u16) ad_infos) / sizeof(*ai))
+
+/* Convert far function address into NPFN (the DDK needs this all over the
+ * place and just casting to NPFN will produce a "segment lost in conversion"
+ * warning. Since casting to a u32 is a bit nasty for function pointers and
+ * might have to be revised for different compilers, we'll use a central
+ * macro for this crap.
+ */
+#define mk_NPFN(func)      (NPFN) (u32) (func)
+
+/* stdarg.h macros with explicit far pointers
+ *
+ * NOTE: The compiler pushes fixed arguments with 16 bits minimum, thus
+ *       the last fixed argument (i.e. the one passed to va_start) must
+ *       have at least 16 bits. Otherwise, the address calculation in
+ *       va_start() will fail.
+ */
+typedef char _far *va_list;
+#define va_start(va, last)    va = (va_list) (&last + 1)
+#define va_arg(va, type)      ((type _far *) (va += sizeof(type)))[-1]
+#define va_end(va)            va = 0
+
+/* ctype macros */
+#define isupper(ch)           ((ch) >= 'A' && (ch) <= 'Z')
+#define tolower(ch)           (isupper(ch) ? (ch) + ('a' - 'A') : (ch))
+
+/* stddef macros */
+#define offsetof(s, e)        ((u16) &((s *) 0)->e)
+
+/* SMP spinlock compatibility macros for older DDKs using CLI/STI */
+#ifndef OS2AHCI_SMP
+#define DevHelp_CreateSpinLock(p_sph)  *(p_sph) = 0
+#define DevHelp_FreeSpinLock(sph)      0
+
+#define DevHelp_AquireSpinLock(sph)    if ((sph) != 0)                \
+                                         panic("recursive spinlock"); \
+                                       (sph) = disable()
+
+#define DevHelp_ReleaseSpinLock(sph)   if (sph) {                     \
+                                         (sph) = 0;                   \
+                                         enable();                    \
+                                       }
+#endif
+
+/* shortcut macros */
+#define spin_lock(sl)     DevHelp_AquireSpinLock(sl)
+#define spin_unlock(sl)   DevHelp_ReleaseSpinLock(sl)
+
+/* Get AHCI port MMIO base from AD_INFO and port number. For the time being,
+ * MMIO addresses are assumed to be valid 16:16 pointers which implies
+ * that one GDT selector is allocated per adapter.
+ */
+#define port_base(ai, p)   ((u8 _far *) (ai)->mmio + 0x100 + (p) * 0x80)
+
+/* Get address of port-specific DMA scratch buffer. The total size of all DMA
+ * buffers required for 32 ports exceeds 65536 bytes, thus we need multiple
+ * GDT selectors to access all port DMA scratch buffers and some logic to map
+ * a port number to the corresponding DMA scratch buffer address.
+ */
+#define PORT_DMA_BUFS_PER_SEG  ((size_t) (65536UL / AHCI_PORT_PRIV_DMA_SZ))
+#define PORT_DMA_BUF_SEGS      ((AHCI_MAX_PORTS + PORT_DMA_BUFS_PER_SEG - 1) \
+                                / PORT_DMA_BUFS_PER_SEG)
+#define PORT_DMA_SEG_SIZE      ((u32) PORT_DMA_BUFS_PER_SEG * \
+                                (u32) AHCI_PORT_PRIV_DMA_SZ)
+
+#define port_dma_base(ai, p) \
+  ((AHCI_PORT_DMA _far *) ((ai)->dma_buf[(p) / PORT_DMA_BUFS_PER_SEG] + \
+                           ((p) % PORT_DMA_BUFS_PER_SEG) * AHCI_PORT_PRIV_DMA_SZ))
+
+#define port_dma_base_phys(ai, p) \
+  ((ai)->dma_buf_phys + (u32) (p) * AHCI_PORT_PRIV_DMA_SZ)
+
+/* 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)  (((u16) (iorb)->UnitHandle >> 8) & 0x07U)
+#define iorb_unit_port(iorb)     (((u16) (iorb)->UnitHandle >> 4) & 0x0fU)
+#define iorb_unit_device(iorb)   ((u16)  (iorb)->UnitHandle       & 0x0fU)
+
+/*******************************************************************************
+ * 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 _far *) &(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 ------------------------ */
+
+typedef unsigned int     size_t;
+
+/* 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 _far *volatile root;           /* root of request list */
+  IORBH _far *volatile tail;           /* tail of request list */
+} IORB_QUEUE;
+
+/* port information structure */
+typedef struct {
+  IORB_QUEUE    iorb_queue;            /* IORB queue for this port */
+  unsigned      dev_max   : 4;         /* maximum device number on this port (0-15) */
+  unsigned      cmd_slot  : 5;         /* current command slot index (using round-
+                                        * robin indexes to prevent starvation) */
+
+  volatile u32  ncq_cmds;              /* bitmap for NCQ commands issued */
+  volatile u32  reg_cmds;              /* bitmap for regular commands issued */
+
+  struct {
+    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 _far *unit_info;          /* pointer to modified unit info */
+  } devs[15];
+} P_INFO;
+
+/* adapter information structure */
+typedef struct {
+  PCI_ID       *pci;                   /* pointer to corresponding PCI ID */
+
+  unsigned      port_max : 5;          /* maximum port number (0-31) */
+  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 */
+
+  u32           port_map;              /* bitmap of active ports */
+
+  /* initial adapter configuration from BIOS */
+  u32           bios_config[HOST_CAP2 / sizeof(u32) + 1];
+
+  u32           cap;                   /* working copy of CAP register */
+  u32           cap2;                  /* working copy of CAP2 register */
+  u32           flags;                 /* adapter flags */
+
+  HRESOURCE     rm_adh;                /* resource handle for adapter */
+  HRESOURCE     rm_bars[6];            /* resource handle for MMIO and I/O BARs */
+  HRESOURCE     rm_irq;                /* resource handle for IRQ */
+
+  u8            bus;                   /* PCI bus number */
+  u8            dev_func;              /* PCI device and function number */
+  u16           irq;                   /* interrupt number */
+
+  u32           mmio_phys;             /* physical address of MMIO region */
+  u32           mmio_size;             /* size of MMIO region */
+  u8      _far *mmio;                  /* pointer to this adapter's MMIO region */
+
+  u32           dma_buf_phys;          /* physical address of DMA scratch buffer */
+  u8      _far *dma_buf[PORT_DMA_BUF_SEGS]; /* DMA scatch buffer */
+
+  P_INFO        ports[AHCI_MAX_PORTS]; /* SATA ports on this adapter */
+} AD_INFO;
+
+/* ADD workspace in IORB (must not exceed 16 bytes) */
+typedef struct {
+  void (*ppfunc)(IORBH _far *iorb);    /* post-processing function */
+  void         *buf;                   /* response buffer (e.g. for identify cmds) */
+  ULONG         timer;                 /* timer for timeout procesing */
+  USHORT        blocks;                /* number of blocks to be transferred */
+  unsigned      processing    : 1;     /* IORB is being processd */
+  unsigned      idempotent    : 1;     /* IORB is idempotent (can be retried) */
+  unsigned      queued_hw     : 1;     /* IORB has been queued to hardware */
+  unsigned      no_ncq        : 1;     /* must not use native command queuing */
+  unsigned      is_ncq        : 1;     /* should use native command queueing */
+  unsigned      complete      : 1;     /* IORB has completed processing */
+  unsigned      cmd_slot      : 5;     /* AHCI command slot for this IORB */
+} ADD_WORKSPACE;
+
+/* -------------------------- function prototypes -------------------------- */
+
+/* init.asm */
+extern u32  _cdecl        readl       (void _far *addr);
+extern u32  _cdecl        writel      (void _far *addr, u32 val);
+extern void _far * _cdecl memcpy      (void _far *v_dst, void _far *v_src, int len);
+extern void _far * _cdecl memset      (void _far *p, int ch, size_t len);
+extern void _cdecl _far restart_hook  (void);
+extern void _cdecl _far reset_hook    (void);
+extern void _cdecl _far engine_hook   (void);
+
+/* os2ahci.c */
+extern USHORT  init_drv               (RPINITIN _far *req);
+extern USHORT  gen_ioctl              (RP_GENIOCTL _far *ioctl);
+extern USHORT  exit_drv               (int func);
+extern void _cdecl _far _loadds add_entry    (IORBH _far *iorb);
+extern void    trigger_engine         (void);
+extern int     trigger_engine_1       (void);
+extern void    send_iorb              (IORBH _far *iorb);
+extern void    iocc_configuration     (IORBH _far *iorb);
+extern void    iocc_device_control    (IORBH _far *iorb);
+extern void    iocc_unit_control      (IORBH _far *iorb);
+extern void    iocm_device_table      (IORBH _far *iorb);
+extern void    iocc_geometry          (IORBH _far *iorb);
+extern void    iocc_execute_io        (IORBH _far *iorb);
+extern void    iocc_unit_status       (IORBH _far *iorb);
+extern void    iocc_adapter_passthru  (IORBH _far *iorb);
+extern void    iorb_queue_add         (IORB_QUEUE _far *queue, IORBH _far *iorb);
+extern int     iorb_queue_del         (IORB_QUEUE _far *queue, IORBH _far *iorb);
+extern void    iorb_seterr            (IORBH _far *iorb, USHORT error_code);
+extern void    iorb_done              (IORBH _far *iorb);
+extern void    iorb_complete          (IORBH _far *iorb);
+extern void    iorb_requeue           (IORBH _far *iorb);
+extern void    aws_free               (ADD_WORKSPACE _far *aws);
+extern void    lock_adapter           (AD_INFO *ai);
+extern void    unlock_adapter         (AD_INFO *ai);
+extern void _cdecl _far timeout_callback    (ULONG timer_handle, ULONG p1, ULONG p2);
+extern void _cdecl _far reset_watchdog      (ULONG timer_handle, ULONG p1, ULONG p2);
+
+/* ahci.c */
+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 _far *iorb, int ncq_capable,
+                                             int (*func)(IORBH _far *, int));
+extern void    ahci_exec_polled_iorb        (IORBH _far *iorb,
+                                             int (*func)(IORBH _far *, 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 _far *iorb);
+extern void    ahci_unit_ready              (IORBH _far *iorb);
+extern void    ahci_read                    (IORBH _far *iorb);
+extern void    ahci_verify                  (IORBH _far *iorb);
+extern void    ahci_write                   (IORBH _far *iorb);
+extern void    ahci_execute_cdb             (IORBH _far *iorb);
+extern void    ahci_execute_ata             (IORBH _far *iorb);
+
+/* libc.c */
+extern void        init_libc     (void);
+extern void        init_com      (void);
+extern int         vsprintf      (char _far *buf, const char *fmt, va_list va);
+extern int         sprintf       (char _far *buf, const char *fmt, ...);
+extern void        vfprintf      (const char *fmt, va_list va);
+extern void _cdecl printf        (const char *fmt, ...);
+extern void        cprintf       (const char *fmt, ...);
+extern void        phex          (const void _far *p, int len,
+                                  const char *fmt, ...);
+extern size_t      strlen        (const char _far *s);
+extern char _far  *strcpy        (char _far *dst, const char _far *src);
+extern int         memcmp        (void _far *p1, void _far *p2, size_t len);
+extern long        strtol        (const char _far *buf,
+                                  const char _far * _far *ep, int base);
+extern void       *malloc        (size_t len);
+extern void        free          (void *ptr);
+extern void        mdelay_cal    (void);
+extern void        mdelay        (u32 millies);
+extern void        msleep        (u32 millies);
+extern void        panic         (char *msg);
+extern int         disable       (void);
+extern void        enable        (void);
+
+/* pci.c */
+extern int       add_pci_id     (u16 vendor, u16 device);
+extern void      scan_pci_bus   (void);
+extern int       pci_enable_int (UCHAR bus, UCHAR dev_func);
+extern void      pci_hack_virtualbox(void);
+extern char     *vendor_from_id (u16 vendor);
+extern char     *device_from_id (u16 device);
+
+/* ctxhook.c */
+extern void _cdecl restart_ctxhook    (ULONG parm);
+extern void _cdecl reset_ctxhook      (ULONG parm);
+extern void _cdecl engine_ctxhook     (ULONG parm);
+
+/* apm.c */
+extern void        apm_init           (void);
+extern void        apm_suspend        (void);
+extern void        apm_resume         (void);
+
+/* ioctl.c */
+extern USHORT      ioctl_get_devlist  (RP_GENIOCTL _far *ioctl);
+extern USHORT      ioctl_passthrough  (RP_GENIOCTL _far *ioctl);
+
+/* ---------------------------- global variables --------------------------- */
+
+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 */
+
+extern int           debug;         /* if != 0, print debug messages to COM1 */
+extern int           thorough_scan; /* if != 0, perform thorough PCI scan */
+extern int           init_reset;    /* if != 0, reset ports during init */
+
+extern HDRIVER       rm_drvh;       /* resource manager driver handle */
+extern USHORT        add_handle;    /* adapter device driver handle */
+extern UCHAR         timer_pool[];  /* timer pool */
+
+extern PCI_ID        pci_ids[];     /* SATA adapter PCI IDs */
+extern ULONG         drv_lock;      /* driver-level 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 u16           com_base;      /* debug COM port base address */
+
+/* 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];
+
Index: /tags/1.12/src/os2ahci/pci.c
===================================================================
--- /tags/1.12/src/os2ahci/pci.c	(revision 97)
+++ /tags/1.12/src/os2ahci/pci.c	(revision 97)
@@ -0,0 +1,1145 @@
+/******************************************************************************
+ * PCI.c - PCI constants and detection code for os2ahci driver
+ *
+ * Copyright (c) 2011 thi.guten Software Development
+ * Copyright (c) 2011 Mensys B.V.
+ *
+ * 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))
+
+/******************************************************************************
+ * OEMHLP constants for PCI access
+ */
+#define GENERIC_IOCTL       0x10
+#define OH_CATEGORY         0x00
+#define OH_FUNC_PCI         0x0b
+
+/* subfunctions */
+#define OH_BIOS_INFO        0x00
+#define OH_FIND_DEVICE      0x01
+#define OH_FIND_CLASS       0x02
+#define OH_READ_CONFIG      0x03
+#define OH_WRITE_CONFIG     0x04
+
+/* return codes */
+#define OH_SUCCESS          0x00
+#define OH_NOT_SUPPORTED    0x81
+#define OH_BAD_VENDOR       0x83
+#define OH_NOT_FOUND        0x86
+#define OH_BAD_REGISTER     0x87
+
+/* ------------------------ typedefs and structures ------------------------ */
+
+/******************************************************************************
+ * OEMHLP IOCtl parameter union. The parameter area is generally used as input
+ * to the OEMHLP IOCtl calls.
+ */
+typedef union {
+
+  /* query PCI BIOS information" */
+  struct {
+    UCHAR    subfunction;
+  } bios_info;
+
+  /* find PCI device */
+  struct {
+    UCHAR    subfunction;
+    USHORT   device;
+    USHORT   vendor;
+    UCHAR    index;
+  } find_device;
+
+  /* find PCI class code */
+  struct {
+    UCHAR    subfunction;
+    ULONG    class;
+    UCHAR    index;
+  } find_class;
+
+  /* read PCI configuration space */
+  struct {
+    UCHAR    subfunction;
+    UCHAR    bus;
+    UCHAR    dev_func;
+    UCHAR    reg;
+    UCHAR    size;
+  } read_config;
+
+  /* write PCI configuration space */
+  struct {
+    UCHAR    subfunction;
+    UCHAR    bus;
+    UCHAR    dev_func;
+    UCHAR    reg;
+    UCHAR    size;
+    ULONG    data;
+  } write_config;
+
+} OH_PARM;
+
+/******************************************************************************
+ * OEMHLP IOCtl data union. The data area is generally used as output from the
+ * OEMHLP IOCtl calls.
+ */
+typedef union {
+
+  /* query PCI BIOS information" */
+  struct {
+    UCHAR    rc;
+    UCHAR    hw_mech;
+    UCHAR    major_version;
+    UCHAR    minor_version;
+    UCHAR    last_bus;
+  } bios_info;
+
+  /* find PCI device */
+  struct {
+    UCHAR    rc;
+    UCHAR    bus;
+    UCHAR    dev_func;
+  } find_device;
+
+  /* find PCI class code */
+  struct {
+    UCHAR    rc;
+    UCHAR    bus;
+    UCHAR    dev_func;
+  } find_class;
+
+  /* read PCI confguration space */
+  struct {
+    UCHAR    rc;
+    ULONG    data;
+  } read_config;
+
+  /* write PCI confguration space */
+  struct {
+    UCHAR    rc;
+  } write_config;
+
+} OH_DATA;
+
+/* -------------------------- function prototypes -------------------------- */
+
+static void  add_pci_device   (PCI_ID *pci_id, OH_DATA _far *data);
+static UCHAR pci_read_conf    (UCHAR bus, UCHAR dev_func, UCHAR indx,
+                               UCHAR size, ULONG _far *val);
+static UCHAR pci_write_conf   (UCHAR bus, UCHAR dev_func, UCHAR indx, UCHAR size,
+                               ULONG val);
+static int   oemhlp_call      (UCHAR subfunction, OH_PARM _far *parm,
+                               OH_DATA _far *data);
+static long  bar_resource     (UCHAR bus, UCHAR dev_func,
+                               RESOURCESTRUCT _far *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";
+
+/******************************************************************************
+ * other strings
+ */
+static char s_already_claimed[] = "Warning: device already claimed by another driver.\n";
+
+
+
+/******************************************************************************
+ * 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 }
+};
+
+/******************************************************************************
+ * OEMHLP$ is used by OS/2 to provide access to OEM-specific machine resources
+ * like PCI BIOS access. We're using this to enumerate the PCI bus. Due to
+ * BIOS bugs, it may be necessary to use I/O operations for this purpose but
+ * so far I think this is only relevant for rather old PCs and SATA is not
+ * expected to be a priority on those machines.
+ */
+static IDCTABLE     oemhlp;            /* OEMHLP$ IDC entry point */
+
+/* ----------------------------- 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) {
+    /* all slots in use */
+    return(-1);
+  }
+
+  /* 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)
+{
+  OH_PARM parm;
+  OH_DATA data;
+  UCHAR index;
+  UCHAR rc;
+  int ad_indx = 0;
+  int i;
+  int n;
+
+  ddprintf("scanning PCI bus...\n");
+
+  /* verify that we have a PCI system */
+  memset(&parm, 0x00, sizeof(parm));
+  if (oemhlp_call(OH_BIOS_INFO, &parm, &data) != OH_SUCCESS) {
+    cprintf("couldn't get PCI BIOS information\n");
+    return;
+  }
+
+  /* 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 */
+        memset(&parm, 0x00, sizeof(parm));
+        parm.find_class.class = pci_ids[i].class;
+        parm.find_class.index = index;
+        rc = oemhlp_call(OH_FIND_CLASS, &parm, &data);
+
+      } else if (thorough_scan) {
+        /* look for this specific vendor and device ID */
+        memset(&parm, 0x00, sizeof(parm));
+        parm.find_device.device = pci_ids[i].device;
+        parm.find_device.vendor = pci_ids[i].vendor;
+        parm.find_device.index = index;
+        rc = oemhlp_call(OH_FIND_DEVICE, &parm, &data);
+
+      } else {
+        rc = OH_NOT_FOUND;
+      }
+
+      if (rc == OH_SUCCESS) {
+        /* found a device */
+        int already_found = 0;
+
+        /* increment index for next loop */
+        if (++index > 180) {
+          /* something's wrong here... */
+          return;
+        }
+
+        /* check whether we already found this device */
+        for (n = 0; n < ad_info_cnt; n++) {
+          if (ad_infos[n].bus == data.find_device.bus &&
+              ad_infos[n].dev_func == data.find_device.dev_func) {
+            /* 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, &data);
+      }
+
+    } while (rc == OH_SUCCESS);
+  }
+}
+
+/******************************************************************************
+ * 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(UCHAR bus, UCHAR dev_func)
+{
+  ULONG tmp;
+
+  if (pci_read_conf (bus, dev_func, 4, sizeof(u32), &tmp) != OH_SUCCESS ||
+      pci_write_conf(bus, dev_func, 4, sizeof(u32), tmp & ~(1UL << 10)) != OH_SUCCESS) {
+    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)
+{
+  ULONG irq = 0;
+
+  if (pci_read_conf(0, 0x08, 0x60, 1, &irq) == OH_SUCCESS && irq == 0x80) {
+    /* set IRQ for first device/func to 11 */
+    dprintf("hacking virtualbox PIIX3 PCI to ISA bridge IRQ mapping\n");
+    irq = ad_infos[0].irq;
+    pci_write_conf(0, 0x08, 0x60, 1, irq);
+  }
+}
+
+/******************************************************************************
+ * Add a single PCI device to the list of adapters.
+ */
+static void add_pci_device(PCI_ID *pci_id, OH_DATA _far *data)
+{
+  char rc_list_buf[sizeof(AHRESOURCE) + sizeof(HRESOURCE) * 15];
+  AHRESOURCE _far *rc_list = (AHRESOURCE _far *) rc_list_buf;
+  RESOURCESTRUCT resource;
+  ADAPTERSTRUCT adapter;
+  ADJUNCT adj;
+  AD_INFO *ad_info;
+  APIRET ret;
+  UCHAR bus       = data->find_class.bus;
+  UCHAR dev_func  = data->find_class.dev_func;
+  ULONG val;
+  SEL gdt[PORT_DMA_BUF_SEGS + 1];
+  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 (pci_read_conf(bus, dev_func, 0x00, sizeof(ULONG), &val) != OH_SUCCESS) {
+    return;
+  }
+  device = (u16) (val >> 16);
+  vendor = (u16) (val & 0xffff);
+
+  /* ... and class code */
+  if (pci_read_conf(bus, dev_func, 0x08, sizeof(ULONG), &val) != OH_SUCCESS) {
+    return;
+  }
+  class = (u32) (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) {
+        /* vendor doesn't match */
+        return;
+      }
+
+    } 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 */
+  cprintf("found AHCI device: %s %s (%04x:%04x)\n"
+          " class:0x%06lx bus:%d devfunc:0x%02x\n",
+          vendor_from_id(vendor), device_from_id(device),
+          vendor, device,
+          class, bus, dev_func);
+
+  /* make sure we got room in the adapter information array */
+  if (ad_info_cnt >= MAX_AD - 1) {
+    cprintf("error: too many AHCI devices\n");
+    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
+   *
+   * NOTE: We rely on the IRQ number saved in the PCI config space by the PCI
+   *       BIOS. There's no reliable way to find out the IRQ number in any
+   *       other way unless we start using message-driven interrupts (which
+   *       is out of scope for the time being).
+   */
+  if (pci_read_conf(bus, dev_func, 0x3c, sizeof(u32), &val) != OH_SUCCESS) {
+    return;
+  }
+  irq = (int) (val & 0xff);
+  pin = (int) ((val >> 8) & 0xff);
+
+  memset(&resource, 0x00, sizeof(resource));
+  resource.ResourceType          = RS_TYPE_IRQ;
+  resource.IRQResource.IRQLevel  = irq;
+  resource.IRQResource.PCIIrqPin = pin;
+  resource.IRQResource.IRQFlags  = RS_IRQ_SHARED;
+
+  ret = RMAllocResource(rm_drvh, &ad_info->rm_irq, &resource);
+  if (ret != RMRC_SUCCESS) {
+    cprintf("error: couldn't register IRQ %d (rc = %s)\n", 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.
+   */
+  for (i = 0; i < sizeof(ad_info->rm_bars) / sizeof(*ad_info->rm_bars); i++) {
+    long len = bar_resource(bus, dev_func, &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) {
+        cprintf("error: BAR #5 must be an MMIO region\n");
+        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) {
+      cprintf("error: couldn't register [MM]IO region (rc = %s)\n", rmerr(ret));
+      goto add_pci_fail;
+    }
+    rc_list->hResource[rc_list->NumResource++] = ad_info->rm_bars[i];
+  }
+
+  if (ad_info->mmio_phys == 0) {
+    cprintf("error: couldn't determine MMIO base address\n");
+    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_ids + i;
+  ad_info->bus      = bus;
+  ad_info->dev_func = dev_func;
+  ad_info->irq      = irq;
+
+  /* allocate memory for port-specific DMA scratch buffers */
+  if (DevHelp_AllocPhys((long) AHCI_PORT_PRIV_DMA_SZ * AHCI_MAX_PORTS,
+                        MEMTYPE_ABOVE_1M, &ad_info->dma_buf_phys) != 0) {
+    cprintf("error: couldn't allocate DMA scratch buffers for AHCI ports\n");
+    ad_info->dma_buf_phys = 0;
+    goto add_pci_fail;
+  }
+
+  /* allocate GDT selectors for memory-mapped I/O and DMA scratch buffers */
+  if (DevHelp_AllocGDTSelector(gdt, PORT_DMA_BUF_SEGS + 1) != 0) {
+    cprintf("error: couldn't allocate GDT selectors\n");
+    memset(gdt, 0x00, sizeof(gdt));
+    goto add_pci_fail;
+  }
+
+  /* map MMIO address to first GDT selector */
+  if (DevHelp_PhysToGDTSelector(ad_info->mmio_phys,
+                                (USHORT) ad_info->mmio_size, gdt[0]) != 0) {
+    cprintf("error: couldn't map MMIO address to GDT selector\n");
+    goto add_pci_fail;
+  }
+
+  /* map DMA scratch buffers to remaining GDT selectors */
+  for (i = 0; i < PORT_DMA_BUF_SEGS; i++) {
+    ULONG addr = ad_info->dma_buf_phys + i * PORT_DMA_SEG_SIZE;
+    USHORT len = AHCI_PORT_PRIV_DMA_SZ * PORT_DMA_BUFS_PER_SEG;
+
+    if (DevHelp_PhysToGDTSelector(addr, len, gdt[i+1]) != 0) {
+      cprintf("error: couldn't map DMA scratch buffer to GDT selector\n");
+      goto add_pci_fail;
+    }
+  }
+
+  /* fill in MMIO and DMA scratch buffer addresses in adapter info */
+  ad_info->mmio = (u8 _far *) ((u32) gdt[0] << 16);
+  for (i = 0; i < PORT_DMA_BUF_SEGS; i++) {
+    ad_info->dma_buf[i] = (u8 _far *) ((u32) gdt[i+1] << 16);
+  }
+
+  /* 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) {
+    cprintf("error: couldn't register adapter (rc = %s)\n", rmerr(ret));
+    goto add_pci_fail;
+  }
+
+  /* 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);
+  }
+  for (i = 0; i < sizeof(gdt) / sizeof(*gdt); i++) {
+    if (gdt[i] != 0) {
+      DevHelp_FreeGDTSelector(gdt[i]);
+    }
+  }
+  if (ad_info->dma_buf_phys != 0) {
+    DevHelp_FreePhys(ad_info->dma_buf_phys);
+  }
+}
+
+/******************************************************************************
+ * Read PCI configuration space register
+ */
+static UCHAR pci_read_conf(UCHAR bus, UCHAR dev_func, UCHAR indx, UCHAR size,
+                           ULONG _far *val)
+{
+  OH_PARM parm;
+  OH_DATA data;
+  UCHAR rc;
+
+  memset(&parm, 0x00, sizeof(parm));
+  parm.read_config.bus      = bus;
+  parm.read_config.dev_func = dev_func;
+  parm.read_config.reg      = indx;
+  parm.read_config.size     = size;
+  if ((rc = oemhlp_call(OH_READ_CONFIG, &parm, &data) != OH_SUCCESS)) {
+    cprintf("error: couldn't read config space (bus = %d, dev_func = 0x%02x, indx = 0x%02x, rc = %d)\n",
+            bus, dev_func, indx,  rc);
+    return(rc);
+  }
+
+  *val = data.read_config.data;
+  return(OH_SUCCESS);
+}
+
+/******************************************************************************
+ * Write PCI configuration space register
+ */
+static UCHAR pci_write_conf(UCHAR bus, UCHAR dev_func, UCHAR indx, UCHAR size,
+                            ULONG val)
+{
+  OH_PARM parm;
+  OH_DATA data;
+  UCHAR rc;
+
+  memset(&parm, 0x00, sizeof(parm));
+  parm.write_config.bus      = bus;
+  parm.write_config.dev_func = dev_func;
+  parm.write_config.reg      = indx;
+  parm.write_config.size     = size;
+  parm.write_config.data     = val;
+
+  if ((rc = oemhlp_call(OH_WRITE_CONFIG, &parm, &data) != OH_SUCCESS)) {
+    cprintf("error: couldn't write config space (bus = %d, dev_func = 0x%02x, indx = 0x%02x, rc = %d)\n",
+            bus, dev_func, indx, rc);
+    return(rc);
+  }
+
+  return(OH_SUCCESS);
+}
+/******************************************************************************
+ * Call OEMHLP$ IDC entry point with the specified IOCtl parameter and data
+ * packets.
+ */
+static int oemhlp_call(UCHAR subfunction, OH_PARM _far *parm,
+                       OH_DATA _far *data)
+{
+  void (_far *func)(void);
+  RP_GENIOCTL ioctl;
+  unsigned short prot_idc_ds;
+
+  if (oemhlp.ProtIDCEntry == NULL || oemhlp.ProtIDC_DS == 0) {
+    /* attach to OEMHLP$ device driver */
+    if (DevHelp_AttachDD("OEMHLP$ ", (NPBYTE) &oemhlp) ||
+        oemhlp.ProtIDCEntry == NULL ||
+        oemhlp.ProtIDC_DS == 0) {
+      cprintf("couldn't attach to OEMHLP$\n");
+      return(OH_NOT_SUPPORTED);
+    }
+  }
+
+  /* store subfuntion in first byte of pararameter packet */
+  parm->bios_info.subfunction = subfunction;
+  memset(data, 0x00, sizeof(*data));
+
+  /* assemble IOCtl request */
+  memset(&ioctl, 0x00, sizeof(ioctl));
+  ioctl.rph.Len     = sizeof(ioctl);
+  ioctl.rph.Unit    = 0;
+  ioctl.rph.Cmd     = GENERIC_IOCTL;
+  ioctl.rph.Status  = 0;
+
+  ioctl.Category    = OH_CATEGORY;
+  ioctl.Function    = OH_FUNC_PCI;
+  ioctl.ParmPacket  = (PUCHAR) parm;
+  ioctl.DataPacket  = (PUCHAR) data;
+  ioctl.ParmLen     = sizeof(*parm);
+  ioctl.DataLen     = sizeof(*data);
+
+  /* Call OEMHLP's IDC routine. Before doing so, we need to assign the address
+   * to be called to a stack variable because the inter-device driver calling
+   * convention forces us to set DS to the device driver's data segment and ES
+   * to the segment of the request packet.
+   */
+  func = oemhlp.ProtIDCEntry;
+
+  /* The WATCOM compiler does not support struct references in inline
+   * assembler code, so we pass it in a stack variable
+   */
+  prot_idc_ds = oemhlp.ProtIDC_DS;
+
+  _asm {
+    push ds;
+    push es;
+    push bx;
+    push si;
+    push di;
+
+    push ss
+    pop  es
+    lea  bx, ioctl;
+    mov  ds, prot_idc_ds;
+    call dword ptr [func];
+
+    pop  di;
+    pop  si;
+    pop  bx;
+    pop  es;
+    pop  ds;
+  }
+
+  dddphex(parm, sizeof(*parm), "oemhlp_parm: ");
+  dddphex(data, sizeof(*data), "oemhlp_data: ");
+
+  if (ioctl.rph.Status & STERR) {
+    return(OH_NOT_SUPPORTED);
+  }
+  return(data->bios_info.rc);
+}
+
+/******************************************************************************
+ * 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(UCHAR bus, UCHAR dev_func,
+                         RESOURCESTRUCT _far *resource, int i)
+{
+  u32 bar_addr = 0;
+  u32 bar_size = 0;
+
+  /* temporarily write 1s to this BAR to determine the address range */
+  if (pci_read_conf (bus, dev_func, PCI_BAR(i), sizeof(u32), &bar_addr) != OH_SUCCESS ||
+      pci_write_conf(bus, dev_func, PCI_BAR(i), sizeof(u32), ~(0UL))    != OH_SUCCESS ||
+      pci_read_conf (bus, dev_func, PCI_BAR(i), sizeof(u32), &bar_size) != OH_SUCCESS ||
+      pci_write_conf(bus, dev_func, PCI_BAR(i), sizeof(u32), bar_addr)  != OH_SUCCESS) {
+
+    cprintf("error: couldn't determine [MM]IO size\n");
+    if (bar_addr != 0) {
+      pci_write_conf(bus, dev_func, PCI_BAR(i), sizeof(u32), bar_addr);
+    }
+    return(-1);
+  }
+
+  if (bar_size == 0 || bar_size == 0xffffffffUL) {
+    /* bar not implemented or device not working properly */
+    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;
+  }
+
+  ddprintf("BAR #%d: type = %s, addr = 0x%08lx, size = %ld\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/1.12/src/os2ahci/test/Makefile
===================================================================
--- /tags/1.12/src/os2ahci/test/Makefile	(revision 97)
+++ /tags/1.12/src/os2ahci/test/Makefile	(revision 97)
@@ -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/1.12/src/os2ahci/test/ioctl_test.c
===================================================================
--- /tags/1.12/src/os2ahci/test/ioctl_test.c	(revision 97)
+++ /tags/1.12/src/os2ahci/test/ioctl_test.c	(revision 97)
@@ -0,0 +1,187 @@
+#include <stdlib.h>
+#include <stdio.h>
+#include <stddef.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/1.12/src/os2ahci/version.h
===================================================================
--- /tags/1.12/src/os2ahci/version.h	(revision 97)
+++ /tags/1.12/src/os2ahci/version.h	(revision 97)
@@ -0,0 +1,17 @@
+/******************************************************************************
+ * version.h - define version numbers and strings for OS2AHCI.ADD
+ *
+ * Copyright (c) 2011 thi.guten Software Development
+ * Copyright (c) 2011 Mensys B.V.
+ *
+ * Authors: Christian Mueller, Markus Thielen
+ *
+ *
+ * NOTE: this is kept in a separate file for easier parsing by
+ *       bldlvl.cmd (which creates the bldlevel signature)
+ *
+ */
+
+
+#define VERSION            112       /* driver version (2 implied decimals) */
+
Index: /tags/1.12/src/os2ahci/wmakefile
===================================================================
--- /tags/1.12/src/os2ahci/wmakefile	(revision 97)
+++ /tags/1.12/src/os2ahci/wmakefile	(revision 97)
@@ -0,0 +1,131 @@
+###############################################################################
+# wmakefile - makefile for os2ahci driver using WATCOM compiler
+#
+# Copyright (c) 2010 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
+
+
+###############################################################################
+# 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 WATCOM installation
+!ifndef WATCOM
+WATCOM       = i:\watcom
+!endif
+
+CC_INCLUDE   = -I..\include \
+               -I$(DDK)\base\h \
+               -I$(DDK)\base\ibmh \
+	       -I$(DDK)\base\src\dev\dasd\diskh \
+               -I$(WATCOM)\h
+
+AS_INCLUDE   = -I:$(DDK)\base\inc \
+               -I:$(DDK)\base\src\dev\dasd\diskinc
+
+LIB_DIRS     = $(DDK)\base\lib\ \
+	       $(DDK)\base\src\dev\dasd\devhlp\ \
+
+
+###############################################################################
+# Tool Chain
+#
+# This makefile uses the Watcom 16bit compiler. 
+# Linker and assembler are link.exe and alp.exe that ship with the OS/2 DDK
+#
+
+AS         = $(DDK)\tools\alp.exe
+CC         = wcc
+LD         = $(DDK)\base\tools\link.exe
+BLDLEVEL   = cmd.exe /c ..\..\tools\bldlvl.cmd
+BLDDATE    = cmd.exe /c ..\..\tools\blddate.cmd
+MAPSYM     = $(DDK)\base\tools\mapsym.exe
+
+AFLAGS        = -Mb
+CFLAGS        = -d3 -hc -bt=os2 -ms -zu -5 -w2 -wcd=138 -zp1 -q -s -zgp -zfp -oi
+CFLAGS_DEBUG  = -d3 -hc
+LFLAGS        = /noe /nod /packd /a:16 /batch /map /line 
+
+###############################################################################
+# Main dependencies
+
+TARGET   = os2ahci.add
+
+LIBS     = addcalls doscalls rmcalls # dhcalls not needed, see local devhelp.h
+
+SRCS     = init.asm math.asm libc.c os2ahci.c pci.c ahci.c ata.c atapi.c \
+	   ctxhook.c
+
+OBJS     = init.obj libc.obj os2ahci.obj pci.obj ahci.obj ata.obj atapi.obj \
+           ctxhook.obj
+
+INCS     = os2ahci.h ahci.h version.h
+
+
+all: $(TARGET)
+
+clean:
+	rm -f $(OBJS) $(TARGET)
+
+###############################################################################
+# Object/source dependencies
+
+init.obj:     init.asm   wmakefile
+
+libc.obj:     libc.c     wmakefile $(INCS)
+
+os2ahci.obj:  os2ahci.c  wmakefile $(INCS) bldday.h
+
+pci.obj:      pci.c      wmakefile $(INCS)
+
+ahci.obj:     ahci.c     wmakefile $(INCS) ata.h atapi.h
+
+ata.obj:      ata.c      wmakefile $(INCS) ata.h
+
+atapi.obj:    atapi.c    wmakefile $(INCS) ata.h atapi.h
+
+ctxhook.obj:  ctxhook.c  wmakefile $(INCS) ata.h atapi.h
+
+###############################################################################
+# 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)
+	etags.exe $(SRCS) $(INCS)
+
+bldday.h:
+	$(BLDDATE) > bldday.h
+
+.asm.obj:
+	$(AS) $(AFLAGS) $(AS_INCLUDE) $*.asm
+
+.c.obj:
+	$(CC) $(CFLAGS) $(CC_INCLUDE) $*.c
+	wdis -l $*.obj
+
+$(TARGET): $(OBJS) os2ahci.def wmakefile
+	$(BLDLEVEL) os2ahci.def version.h
+	$(LD) $(LFLAGS) $(OBJS),$(TARGET),$*.map,$(LIB_DIRS) $(LIBS),$*.def
+	mapsym os2ahci
+
+
Index: /tags/1.12/tools/blddate.cmd
===================================================================
--- /tags/1.12/tools/blddate.cmd	(revision 97)
+++ /tags/1.12/tools/blddate.cmd	(revision 97)
@@ -0,0 +1,17 @@
+/*
+ * blddate.cmd - REXX script to spit out a header file containing
+ * #defines for the current day, month and year
+ *
+ * Used by os2ahci.add
+ *
+ * MT 08/2010
+ */
+
+d = DATE('S')
+
+say '/* auto generated by blddate.cmd */'
+say '#define BLD_YEAR  ' FORMAT(SUBSTR(d, 1, 4))
+say '#define BLD_MONTH ' FORMAT(SUBSTR(d, 5, 2))
+say '#define BLD_DAY   ' FORMAT(SUBSTR(d, 7, 2))
+
+
Index: /tags/1.12/tools/bldlvl.cmd
===================================================================
--- /tags/1.12/tools/bldlvl.cmd	(revision 97)
+++ /tags/1.12/tools/bldlvl.cmd	(revision 97)
@@ -0,0 +1,260 @@
+/***********************************************************************\
+ *                           BuildLevel.cmd                            *
+ *             Copyright (C) by Stangl Roman, 1996, 1997               *
+ *                                                                     *
+ * This Code may be freely distributed, provided the Copyright isn't   *
+ * removed.                                                            *
+ *                                                                     *
+ * BuildLevel.cmd                                                      *
+ *              Batch file to replace the DESCRIPTION statement in a   *
+ *              module's (EXE or DLL) with one the OS/2 BLDLEVEL       *
+ *              command is able to read.                               *
+ *              Syntax: BuildLevel ModuleDefinitionFile Headerfile     *
+ *              e.g. BuildLevel PC2.def PC2.h                          *
+ 
+ 
+ NOTE: this has been heavily modified to better suit the OS2AHCI
+       environment...
+ 
+ 
+ *                                                                     *
+\***********************************************************************/
+
+/* static char     _VERSION_[]="BuildLevel.cmd - V2.00"; */
+
+
+Call RxFuncAdd 'SysLoadFuncs', 'RexxUtil', 'SysLoadFuncs'
+Call SysLoadFuncs
+                                        /* Trap CTRL+BRK processing to avoid data corruption */
+Signal On HALT NAME SignalHandler
+
+                                        /* OS/2 returncode */
+ReturnCode=1
+                                        /* 1st commandline argument (1 *.DEF file) */
+DefinitionFile=""
+                                        /* As the contents of the 1st commandline argument
+                                           file get changed, we make a temporary copy to read
+                                           from and write the new contents to the original file */
+DefinitionCopy="$Temp$"
+                                        /* 2nd commandline argument (1 *.H* file) */
+HeaderFile=""
+                                        /* Name of the binary module which gets filled from
+                                           the NAME (for *.EXE) or LIBRARY (for *.DLL) statement */
+ModuleName=""
+                                        /* Description statement which gets constructed from the
+                                           header (*.H*) file to replace/add the DESCRIPTION
+                                           statement in the *.DEF file */
+Description=""
+Description_Header="Description '"
+Description_Trailer="'"
+                                        /* The build level part of the DESCRIPTION statement is built
+                                           from the following substrings */
+BldLevel=""
+BldLevel_Header="$@#"
+BldLevel_Vendor=""
+BldLevel_Version=""
+BldLevel_Info=""
+BldLevel_Trailer="@#"
+
+/*--------------------------------------------------------------------------------------*\
+ * Main entry point.                                                                    *
+ * Req:                                                                                 *
+ *      none                                                                            *
+ * Returns:                                                                             *
+ *      ReturnCode .... Error code (0 if no error)                                      *
+\*--------------------------------------------------------------------------------------*/
+                                        /* Get the DefinitionFile (*.DEF) we want to check
+                                           the BLDLEVEL and the Headerfile (*.H*) where
+                                           the required BLDLEVEL data is defined in */
+Parse Arg TemplateFile DefinitionFile HeaderFile .
+Do While (TemplateFile\="" & DefinitionFile\="" & HeaderFile\="")
+  /* We have valid commandline arguments, so first see
+   if the Headerfile contents are ok for us */
+  ReturnCode=ParseHeaderFile()
+  If ReturnCode\=0 Then Leave
+  /* Parse the template DEF file for the module
+   name, to see if we got a valid file */
+  ReturnCode=ParseTemplateFile()
+  If ReturnCode\=0 Then Leave
+  
+  /* Build DESCRIPTION statement for *.DEF file */
+  
+  /* MT: build date string as expected by bldlevel (26 chars fixed) */
+  x = date('s')
+  dat = " " || substr(x, 1, 4) || "/" || substr(x, 5, 2) || "/" || substr(x, 7, 2)
+  dat = dat || " " || time() || "      "
+  BldLevel=BldLevel_Header||"Mensys:"||BldLevel_Version||".0#@##1##"||dat||value('hostname',,'OS2ENVIRONMENT')||"::::00::@@AHCI Driver. (c) Mensys BV 2011"
+  Description=Description_Header||BldLevel||Description_Trailer
+  
+  ReturnCode=ModifyDefinitionFile()
+  If ReturnCode\=0 Then Do
+    Command="@del "||DefinitionCopy||" >NUL 2>&1"
+    Command
+    Leave
+    End
+
+  /* Exit with success */
+  Command="@copy "||DefinitionCopy||" "||DefinitionFile||" /v >NUL"
+  Command
+  Command="@del "||DefinitionCopy||" >NUL 2>&1"
+  Command
+  ReturnCode=0
+  Leave
+  End
+/* Exit to commandline */
+If (ReturnCode\=0) Then Do
+  Say ""
+  Say "Error "||ReturnCode||" modifying "||DefinitionFile||", no changes made"
+  Say ""
+  End
+Else Do
+  Say "Module definition file "||DefinitionFile||" successfully modified"
+  End
+Return ReturnCode
+
+/*--------------------------------------------------------------------------------------*\
+ * Parse a C/C++ header file to for a VERSION (#define VERSION xxx)                    *
+ \*--------------------------------------------------------------------------------------*/
+ParseHeaderFile:
+  LinesLeft=2
+/* Read ahead 1st line */
+  CurrentLine=LineIn(HeaderFile, 1, 1)
+/* Do while there are lines in the file */
+  Do While(LinesLeft>0)
+    /* Parse for constructs of the form:
+     #define BLDLEVEL_Macro "Value" Comment */
+    /* Kill that ... tabs */
+    CurrentLine=Translate(CurrentLine, ' ', X2C(9))
+    Parse Var CurrentLine Define Macro Value .
+    Parse Upper Var Define Define
+    Define=Space(Define, 0)
+    Parse Upper Var Macro Macro
+    Macro=Space(Macro, 0)
+    /* Test for the macros we expect */
+    /* MT: BLDLEVEL_VERSION is ignored so we do not have to change more than one
+     *     symbol for each new version */
+    If (Define="#DEFINE") Then Do
+      Select
+        When (Macro="VERSION") Then Do
+          /* VERSION is a numeric value with 2 implied decimals */
+          BldLevel_Version = substr(Value, 1, length(Value) - 2) || "." || substr(Value, length(Value) - 1, 2)
+          End
+        Otherwise
+        End
+      End
+    CurrentLine=LineIn(HeaderFile)
+    /* As Lines() returns 0 after having read the last line
+     we have to ensure that this last line will be processed too */
+    If (Lines(HeaderFile)=0) Then
+      LinesLeft=LinesLeft-1
+    End
+  If (BldLevel_Version\="") Then
+    Return 0
+  Else
+    Return 10
+
+/*--------------------------------------------------------------------------------------*\
+ * Parse a C/C++ module definition file to test if this file is used to build a OS/2    *
+ * EXE or DLL file and if it contains a DESCRIPTION line.                               *
+ * Req:                                                                                 *
+ *      none                                                                            *
+ * Returns:                                                                             *
+ *      ReturnCode .... Error code (0 if no error)                                      *
+ \*--------------------------------------------------------------------------------------*/
+ParseTemplateFile:
+/* Read ahead 1st line */
+  Command=Stream(TemplateFile, 'C', 'OPEN READ')
+  LinesLeft=2
+  CurrentLine=LineIn(TemplateFile)
+/* Do while there are lines in the file */
+  Do While(LinesLeft>0)
+    /* Parse for constructs of the form:
+     NAME/LIBRARY ModuleName Options */
+    /* Kill that ... tabs */
+    CurrentLine=Translate(CurrentLine, ' ', X2C(9))
+    Parse Var CurrentLine Statement Module Options
+    Parse Upper Var Statement Statement
+    Statement=Space(Statement, 0)
+    Module=Space(Module, 0)
+    If (Module\="") Then Do
+      If (Statement="NAME") Then
+        ModuleName=Module||".exe"
+      If (Statement="LIBRARY") Then
+        ModuleName=Module||".add" /* MT: we're an ADD after all */
+      End
+    /* See if there is a DESCRIPTION statement */
+    Parse Var CurrentLine Statement Options
+    Parse Upper Var Statement Statement
+    Statement=Space(Statement, 0)
+    If (Statement="DESCRIPTION") Then
+      DescriptionStatement=Options
+    CurrentLine=LineIn(TemplateFile)
+    /* As Lines() returns 0 after having read the last line
+     we have to ensure that this last line will be processed too */
+    If (Lines(TemplateFile)=0) Then
+      LinesLeft=LinesLeft-1
+    End
+/* Close the stream */
+  Command=Stream(TemplateFile, 'C', 'CLOSE')
+  If (ModuleName\="" & DescriptionStatement\="") Then
+    Return 0
+  Else
+    Return 20
+
+/*--------------------------------------------------------------------------------------*\
+ * Parse a C/C++ module definition file to modify the DESCRIPTION statement into a form *
+ * that is readable by BLDLEVEL (the original gets discarded).                          *
+ * Req:                                                                                 *
+ *      none                                                                            *
+ * Returns:                                                                             *
+ *      ReturnCode .... Error code (0 if no error)                                      *
+ \*--------------------------------------------------------------------------------------*/
+ModifyDefinitionFile:
+/* Read and write ahead 1st line */
+  Command=Stream(DefinitionCopy, 'C', 'OPEN WRITE')
+  If (Command="ERROR") Then
+    Return 30
+  Command=Stream(TemplateFile, 'C', 'OPEN READ')
+  If (Command="ERROR") Then
+    Return 31
+  LinesLeft=2
+  CurrentLine=LineIn(TemplateFile)
+/* Do while there are lines in the file */
+  Do While(LinesLeft>0)
+    /* Parse for constructs of the form:
+     DESCRIPTION Options */
+    /* Kill that ... tabs */
+    CurrentLine=Translate(CurrentLine, ' ', X2C(9))
+    Parse Var CurrentLine Statement Options
+    Parse Upper Var Statement Statement
+    Statement=Space(Statement, 0)
+    If (Statement="DESCRIPTION") Then Do
+      Command=LineOut(DefinitionCopy, Description)
+      End
+    Else Do
+      Command=LineOut(DefinitionCopy, CurrentLine)
+      End
+    CurrentLine=LineIn(TemplateFile)
+    /* As Lines() returns 0 after having read the last line
+     we have to ensure that this last line will be processed too */
+    If (Lines(TemplateFile)=0) Then
+      LinesLeft=LinesLeft-1
+    End
+/* Close the streams */
+  Command=Stream(TemplateFile, 'C', 'CLOSE')
+  Command=Stream(DefinitionCopy, 'C', 'CLOSE')
+Return 0
+
+/*--------------------------------------------------------------------------------------*\
+ * Signal Handler that avoids interruption and just tells user that CTRL+BRK is not     *
+ * allowed currently.                                                                   *
+ * Req:                                                                                 *
+ *      none                                                                            *
+ * Returns:                                                                             *
+ *      none                                                                            *
+ \*--------------------------------------------------------------------------------------*/
+SignalHandler:
+  Say "    CTRL+BRK currently disabled"
+Return 0
+
+
