foxygit / doom Log in
commit 06b97d2d116b622bc067b245f81b2857767d598e
Author:     Simon Howard <fraggle@gmail.com>
AuthorDate: Fri Feb 26 21:07:59 2010 +0000
Commit:     Simon Howard <fraggle@gmail.com>
CommitDate: Fri Feb 26 21:07:59 2010 +0000

    Add OPL hardware playback support for Windows NT-based systems.

    Subversion-branch: /branches/opl-branch
    Subversion-revision: 1871
---
 OPL-TODO                         |   1 +
 README.OPL                       |   8 +-
 opl/Makefile.am                  |   3 +-
 opl/ioperm_sys.c                 | 255 +++++++++++++++++++++++++++++++++++++++
 opl/ioperm_sys.h                 |  36 ++++++
 opl/opl.c                        |  11 +-
 opl/{opl_win9x.c => opl_win32.c} |  68 ++++++++---
 7 files changed, 358 insertions(+), 24 deletions(-)

diff --git a/OPL-TODO b/OPL-TODO
index 57d315a0..6252bc2a 100644
--- a/OPL-TODO
+++ b/OPL-TODO
@@ -15,5 +15,6 @@ Bad MIDIs:

 Other tasks:

+ * Get a better software OPL emulator
  * DMXOPTIONS opl3/phase option support.

diff --git a/README.OPL b/README.OPL
index 2b837c4c..1746dd88 100644
--- a/README.OPL
+++ b/README.OPL
@@ -64,11 +64,13 @@ stdout.txt:

 If you're running an NT-based system, it is not possible to directly
 access the OPL chip, even when running as Administrator.  Fortunately,
-it is possible to use the third-party "PortTalk" driver:
+it is possible to use the "ioperm.sys" driver developed for Cygwin:

-    http://www.beyondlogic.org/porttalk/porttalk.htm
+    http://openwince.sourceforge.net/ioperm/

-TODO - the NT driver hasn't actually been written yet..
+It is not necessary to have Cygwin installed to use this.  Copy the
+ioperm.sys file into the same directory as the Chocolate Doom
+executable and it should be automatically loaded.

 === Linux ===

diff --git a/opl/Makefile.am b/opl/Makefile.am
index 8bbed9f0..d099b875 100644
--- a/opl/Makefile.am
+++ b/opl/Makefile.am
@@ -13,6 +13,7 @@ libopl_a_SOURCES =                                \
         opl_queue.c         opl_queue.h           \
         opl_sdl.c                                 \
         opl_timer.c         opl_timer.h           \
-        opl_win9x.c                               \
+        opl_win32.c                               \
+        ioperm_sys.c        ioperm_sys.h          \
         fmopl.c             fmopl.h

diff --git a/opl/ioperm_sys.c b/opl/ioperm_sys.c
new file mode 100644
index 00000000..37512b63
--- /dev/null
+++ b/opl/ioperm_sys.c
@@ -0,0 +1,255 @@
+// Emacs style mode select   -*- C++ -*-
+//-----------------------------------------------------------------------------
+//
+// Copyright(C) 2002, 2003 Marcel Telka
+// Copyright(C) 2009 Simon Howard
+//
+// 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.
+//
+// DESCRIPTION:
+//     Interface to the ioperm.sys driver, based on code from the
+//     Cygwin ioperm library.
+//
+//-----------------------------------------------------------------------------
+
+#ifdef _WIN32
+
+#include <stdio.h>
+
+#define WIN32_LEAN_AND_MEAN
+#include <windows.h>
+#include <winioctl.h>
+
+#include <errno.h>
+
+#define IOPERM_FILE "\\\\.\\ioperm"
+
+#define IOCTL_IOPERM               \
+    CTL_CODE(FILE_DEVICE_UNKNOWN, 0xA00, METHOD_BUFFERED, FILE_ANY_ACCESS)
+
+struct ioperm_data
+{
+    unsigned long from;
+    unsigned long num;
+    int turn_on;
+};
+
+static SC_HANDLE scm = NULL;
+static SC_HANDLE svc = NULL;
+static int service_was_created = 0;
+static int service_was_started = 0;
+
+int IOperm_EnablePortRange(unsigned int from, unsigned int num, int turn_on)
+{
+    HANDLE h;
+    struct ioperm_data ioperm_data;
+    DWORD BytesReturned;
+    BOOL r;
+
+    h = CreateFile(IOPERM_FILE, GENERIC_READ, 0, NULL,
+                   OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
+
+    if (h == INVALID_HANDLE_VALUE)
+    {
+        errno = ENODEV;
+        return -1;
+    }
+
+    ioperm_data.from = from;
+    ioperm_data.num = num;
+    ioperm_data.turn_on = turn_on;
+
+    r = DeviceIoControl(h, IOCTL_IOPERM,
+                        &ioperm_data, sizeof ioperm_data,
+                        NULL, 0,
+                        &BytesReturned, NULL);
+
+    if (!r)
+    {
+        errno = EPERM;
+    }
+
+    CloseHandle(h);
+
+    return r != 0;
+}
+
+// Load ioperm.sys driver.
+// Returns 1 for success, 0 for failure.
+// Remember to call IOperm_UninstallDriver to uninstall the driver later.
+
+int IOperm_InstallDriver(void)
+{
+    int error;
+    int result = 1;
+
+    scm = OpenSCManager(NULL, NULL, SC_MANAGER_ALL_ACCESS);
+
+    if (scm == NULL)
+    {
+        error = GetLastError();
+        fprintf(stderr, "IOperm_InstallDriver: OpenSCManager failed (%i)\n",
+                        error);
+        return 0;
+    }
+
+    svc = CreateService(scm,
+                        TEXT("ioperm"),
+                        TEXT("I/O port access driver"),
+                        SERVICE_ALL_ACCESS,
+                        SERVICE_KERNEL_DRIVER,
+                        SERVICE_AUTO_START,
+                        SERVICE_ERROR_NORMAL,
+                        "ioperm.sys",
+                        NULL,
+                        NULL,
+                        NULL,
+                        NULL,
+                        NULL);
+
+    if (svc == NULL)
+    {
+        error = GetLastError();
+
+        if (error != ERROR_SERVICE_EXISTS)
+        {
+            fprintf(stderr,
+                    "IOperm_InstallDriver: Failed to create service (%i)\n",
+                    error);
+        }
+        else
+        {
+            svc = OpenService(scm, TEXT("ioperm"), SERVICE_ALL_ACCESS);
+
+            if (svc == NULL)
+            {
+                error = GetLastError();
+
+                fprintf(stderr,
+                        "IOperm_InstallDriver: Failed to open service (%i)\n",
+                        error);
+            }
+        }
+
+        if (svc == NULL)
+        {
+            CloseServiceHandle(scm);
+            return 0;
+        }
+    }
+    else
+    {
+        service_was_created = 1;
+    }
+
+    if (!StartService(svc, 0, NULL))
+    {
+        error = GetLastError();
+
+        if (error != ERROR_SERVICE_ALREADY_RUNNING)
+        {
+            fprintf(stderr, "IOperm_InstallDriver: Failed to start service (%i)\n",
+                            error);
+            result = 0;
+        }
+        else
+        {
+            printf("IOperm_InstallDriver: ioperm driver already running\n");
+        }
+    }
+    else
+    {
+        printf("IOperm_InstallDriver: ioperm driver installed\n");
+        service_was_started = 1;
+    }
+
+    if (result == 0)
+    {
+        CloseServiceHandle(svc);
+        CloseServiceHandle(scm);
+    }
+
+    return result;
+}
+
+int IOperm_UninstallDriver(void)
+{
+    SERVICE_STATUS stat;
+    int result = 1;
+    int error;
+
+    // If we started the service, stop it.
+
+    if (service_was_started)
+    {
+        if (!ControlService(svc, SERVICE_CONTROL_STOP, &stat))
+        {
+            error = GetLastError();
+
+            if (error == ERROR_SERVICE_NOT_ACTIVE)
+            {
+                fprintf(stderr,
+                        "IOperm_UninstallDriver: Service not active? (%i)\n",
+                        error);
+            }
+            else
+            {
+                fprintf(stderr,
+                        "IOperm_UninstallDriver: Failed to stop service (%i)\n",
+                        error);
+                result = 0;
+            }
+        }
+    }
+
+    // If we created the service, delete it.
+
+    if (service_was_created)
+    {
+        if (!DeleteService(svc))
+        {
+            error = GetLastError();
+
+            fprintf(stderr,
+                    "IOperm_UninstallDriver: DeleteService failed (%i)\n",
+                    error);
+
+            result = 0;
+        }
+    }
+
+    // Close handles.
+
+    if (svc != NULL)
+    {
+        CloseServiceHandle(svc);
+        svc = NULL;
+    }
+
+    if (scm != NULL)
+    {
+        CloseServiceHandle(scm);
+        scm = NULL;
+    }
+
+    service_was_created = 0;
+    service_was_started = 0;
+
+    return result;
+}
+
+#endif /* #ifndef _WIN32 */
+
diff --git a/opl/ioperm_sys.h b/opl/ioperm_sys.h
new file mode 100644
index 00000000..faf17bf3
--- /dev/null
+++ b/opl/ioperm_sys.h
@@ -0,0 +1,36 @@
+// Emacs style mode select   -*- C++ -*-
+//-----------------------------------------------------------------------------
+//
+// Copyright(C) 2002, 2003 Marcel Telka
+// Copyright(C) 2009 Simon Howard
+//
+// 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.
+//
+// DESCRIPTION:
+//     Interface to the ioperm.sys driver, based on code from the
+//     Cygwin ioperm library.
+//
+//-----------------------------------------------------------------------------
+
+#ifndef IOPERM_SYS_H
+#define IOPERM_SYS_H
+
+int IOperm_EnablePortRange(unsigned int from, unsigned int num, int turn_on);
+int IOperm_InstallDriver(void);
+int IOperm_UninstallDriver(void);
+
+#endif /* #ifndef IOPERM_SYS_H */
+
diff --git a/opl/opl.c b/opl/opl.c
index 2c8fd692..9e674530 100644
--- a/opl/opl.c
+++ b/opl/opl.c
@@ -46,7 +46,7 @@ extern opl_driver_t opl_linux_driver;
 extern opl_driver_t opl_openbsd_driver;
 #endif
 #ifdef _WIN32
-extern opl_driver_t opl_win9x_driver;
+extern opl_driver_t opl_win32_driver;
 #endif
 extern opl_driver_t opl_sdl_driver;

@@ -59,7 +59,7 @@ static opl_driver_t *drivers[] =
     &opl_openbsd_driver,
 #endif
 #ifdef _WIN32
-    &opl_win9x_driver,
+    &opl_win32_driver,
 #endif
     &opl_sdl_driver,
     NULL
@@ -197,6 +197,7 @@ void OPL_WritePort(opl_port_t port, unsigned int value)
     {
 #ifdef OPL_DEBUG_TRACE
         printf("OPL_write: %i, %x\n", port, value);
+        fflush(stdout);
 #endif
         driver->write_port_func(port, value);
     }
@@ -208,10 +209,16 @@ unsigned int OPL_ReadPort(opl_port_t port)
     {
         unsigned int result;

+#ifdef OPL_DEBUG_TRACE
+        printf("OPL_read: %i...\n", port);
+        fflush(stdout);
+#endif
+
         result = driver->read_port_func(port);

 #ifdef OPL_DEBUG_TRACE
         printf("OPL_read: %i -> %x\n", port, result);
+        fflush(stdout);
 #endif

         return result;
diff --git a/opl/opl_win9x.c b/opl/opl_win32.c
similarity index 65%
rename from opl/opl_win9x.c
rename to opl/opl_win32.c
index ff527b3e..29df3643 100644
--- a/opl/opl_win9x.c
+++ b/opl/opl_win32.c
@@ -19,7 +19,7 @@
 // 02111-1307, USA.
 //
 // DESCRIPTION:
-//     OPL Win9x native interface.
+//     OPL Win32 native interface.
 //
 //-----------------------------------------------------------------------------

@@ -27,6 +27,8 @@

 #ifdef _WIN32

+#include <stdio.h>
+
 #define WIN32_LEAN_AND_MEAN
 #include <windows.h>

@@ -34,13 +36,15 @@
 #include "opl_internal.h"
 #include "opl_timer.h"

+#include "ioperm_sys.h"
+
 static unsigned int opl_port_base;

 // MingW?

 #if defined(__GNUC__) && defined(__i386__)

-static unsigned int OPL_Win9x_PortRead(opl_port_t port)
+static unsigned int OPL_Win32_PortRead(opl_port_t port)
 {
     unsigned char result;

@@ -56,7 +60,7 @@ static unsigned int OPL_Win9x_PortRead(opl_port_t port)
     return result;
 }

-static void OPL_Win9x_PortWrite(opl_port_t port, unsigned int value)
+static void OPL_Win32_PortWrite(opl_port_t port, unsigned int value)
 {
     __asm__ volatile (
        "movl %0, %%edx\n"
@@ -77,58 +81,86 @@ static void OPL_Win9x_PortWrite(opl_port_t port, unsigned int value)

 #define NO_PORT_RW

-static unsigned int OPL_Win9x_PortRead(opl_port_t port)
+static unsigned int OPL_Win32_PortRead(opl_port_t port)
 {
     return 0;
 }

-static void OPL_Win9x_PortWrite(opl_port_t port, unsigned int value)
+static void OPL_Win32_PortWrite(opl_port_t port, unsigned int value)
 {
 }

 #endif

-static int OPL_Win9x_Init(unsigned int port_base)
+static int OPL_Win32_Init(unsigned int port_base)
 {
 #ifndef NO_PORT_RW

     OSVERSIONINFO version_info;

-    // Check that this is a Windows 9x series OS:
+    opl_port_base = port_base;
+
+    // Check the OS version.

     memset(&version_info, 0, sizeof(version_info));
     version_info.dwOSVersionInfoSize = sizeof(version_info);

     GetVersionEx(&version_info);

-    if (version_info.dwPlatformId == 1)
+    // On NT-based systems, we must acquire I/O port permissions
+    // using the ioperm.sys driver.
+
+    if (version_info.dwPlatformId == VER_PLATFORM_WIN32_NT)
     {
-        opl_port_base = port_base;
+        // Install driver.
+
+        if (!IOperm_InstallDriver())
+        {
+            return 0;
+        }
+
+        // Open port range.

-        // Start callback thread
+        if (!IOperm_EnablePortRange(opl_port_base, 2, 1))
+        {
+            IOperm_UninstallDriver();
+            return 0;
+        }
+    }
+
+    // Start callback thread

-        return OPL_Timer_StartThread();
+    if (!OPL_Timer_StartThread())
+    {
+        IOperm_UninstallDriver();
+        return 0;
     }

+    return 1;
+
 #endif

     return 0;
 }

-static void OPL_Win9x_Shutdown(void)
+static void OPL_Win32_Shutdown(void)
 {
     // Stop callback thread

     OPL_Timer_StopThread();
+
+    // Unload IOperm library.
+
+    IOperm_UninstallDriver();
 }

-opl_driver_t opl_win9x_driver =
+opl_driver_t opl_win32_driver =
 {
-    "Win9x",
-    OPL_Win9x_Init,
-    OPL_Win9x_Shutdown,
-    OPL_Win9x_PortRead,
-    OPL_Win9x_PortWrite,
+    "Win32",
+    OPL_Win32_Init,
+    OPL_Win32_Shutdown,
+    OPL_Win32_PortRead,
+    OPL_Win32_PortWrite,
     OPL_Timer_SetCallback,
     OPL_Timer_ClearCallbacks,
     OPL_Timer_Lock,