shithub: choc

Download patch

ref: 06b97d2d116b622bc067b245f81b2857767d598e
parent: cdacf59acecd944f4a573b3e112c0c43b052f975
author: Simon Howard <fraggle@gmail.com>
date: Fri Feb 26 16:07:59 EST 2010

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

Subversion-branch: /branches/opl-branch
Subversion-revision: 1871

--- a/OPL-TODO
+++ b/OPL-TODO
@@ -15,5 +15,6 @@
 
 Other tasks:
 
+ * Get a better software OPL emulator
  * DMXOPTIONS opl3/phase option support.
 
--- a/README.OPL
+++ b/README.OPL
@@ -64,11 +64,13 @@
 
 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 ===
 
--- a/opl/Makefile.am
+++ b/opl/Makefile.am
@@ -13,6 +13,7 @@
         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
 
--- /dev/null
+++ b/opl/ioperm_sys.c
@@ -1,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 */
+
--- /dev/null
+++ b/opl/ioperm_sys.h
@@ -1,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 */
+
--- a/opl/opl.c
+++ b/opl/opl.c
@@ -46,7 +46,7 @@
 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 @@
     &opl_openbsd_driver,
 #endif
 #ifdef _WIN32
-    &opl_win9x_driver,
+    &opl_win32_driver,
 #endif
     &opl_sdl_driver,
     NULL
@@ -197,6 +197,7 @@
     {
 #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 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;
--- /dev/null
+++ b/opl/opl_win32.c
@@ -1,0 +1,172 @@
+// Emacs style mode select   -*- C++ -*- 
+//-----------------------------------------------------------------------------
+//
+// 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:
+//     OPL Win32 native interface.
+//
+//-----------------------------------------------------------------------------
+
+#include "config.h"
+
+#ifdef _WIN32
+
+#include <stdio.h>
+
+#define WIN32_LEAN_AND_MEAN
+#include <windows.h>
+
+#include "opl.h"
+#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_Win32_PortRead(opl_port_t port)
+{
+    unsigned char result;
+
+    __asm__ volatile (
+       "movl %1, %%edx\n"
+       "inb  %%dx, %%al\n"
+       "movb %%al, %0"
+       :   "=m" (result)
+       :   "r" (opl_port_base + port)
+       :   "edx", "al", "memory"
+    );
+
+    return result;
+}
+
+static void OPL_Win32_PortWrite(opl_port_t port, unsigned int value)
+{
+    __asm__ volatile (
+       "movl %0, %%edx\n"
+       "movb %1, %%al\n"
+       "outb %%al, %%dx"
+       :
+       :   "r" (opl_port_base + port), "r" ((unsigned char) value)
+       :   "edx", "al"
+    );
+}
+
+// TODO: MSVC version
+// #elif defined(_MSC_VER) && defined(_M_IX6) ...
+
+#else
+
+// Not x86, or don't know how to do port R/W on this compiler.
+
+#define NO_PORT_RW
+
+static unsigned int OPL_Win32_PortRead(opl_port_t port)
+{
+    return 0;
+}
+
+static void OPL_Win32_PortWrite(opl_port_t port, unsigned int value)
+{
+}
+
+#endif
+
+static int OPL_Win32_Init(unsigned int port_base)
+{
+#ifndef NO_PORT_RW
+
+    OSVERSIONINFO version_info;
+
+    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);
+
+    // On NT-based systems, we must acquire I/O port permissions
+    // using the ioperm.sys driver.
+
+    if (version_info.dwPlatformId == VER_PLATFORM_WIN32_NT)
+    {
+        // Install driver.
+
+        if (!IOperm_InstallDriver())
+        {
+            return 0;
+        }
+
+        // Open port range.
+
+        if (!IOperm_EnablePortRange(opl_port_base, 2, 1))
+        {
+            IOperm_UninstallDriver();
+            return 0;
+        }
+    }
+
+    // Start callback thread
+
+    if (!OPL_Timer_StartThread())
+    {
+        IOperm_UninstallDriver();
+        return 0;
+    }
+
+    return 1;
+
+#endif
+
+    return 0;
+}
+
+static void OPL_Win32_Shutdown(void)
+{
+    // Stop callback thread
+
+    OPL_Timer_StopThread();
+
+    // Unload IOperm library.
+
+    IOperm_UninstallDriver();
+}
+
+opl_driver_t opl_win32_driver =
+{
+    "Win32",
+    OPL_Win32_Init,
+    OPL_Win32_Shutdown,
+    OPL_Win32_PortRead,
+    OPL_Win32_PortWrite,
+    OPL_Timer_SetCallback,
+    OPL_Timer_ClearCallbacks,
+    OPL_Timer_Lock,
+    OPL_Timer_Unlock,
+    OPL_Timer_SetPaused
+};
+
+#endif /* #ifdef _WIN32 */
+
--- a/opl/opl_win9x.c
+++ /dev/null
@@ -1,140 +1,0 @@
-// Emacs style mode select   -*- C++ -*- 
-//-----------------------------------------------------------------------------
-//
-// 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:
-//     OPL Win9x native interface.
-//
-//-----------------------------------------------------------------------------
-
-#include "config.h"
-
-#ifdef _WIN32
-
-#define WIN32_LEAN_AND_MEAN
-#include <windows.h>
-
-#include "opl.h"
-#include "opl_internal.h"
-#include "opl_timer.h"
-
-static unsigned int opl_port_base;
-
-// MingW?
-
-#if defined(__GNUC__) && defined(__i386__)
-
-static unsigned int OPL_Win9x_PortRead(opl_port_t port)
-{
-    unsigned char result;
-
-    __asm__ volatile (
-       "movl %1, %%edx\n"
-       "inb  %%dx, %%al\n"
-       "movb %%al, %0"
-       :   "=m" (result)
-       :   "r" (opl_port_base + port)
-       :   "edx", "al", "memory"
-    );
-
-    return result;
-}
-
-static void OPL_Win9x_PortWrite(opl_port_t port, unsigned int value)
-{
-    __asm__ volatile (
-       "movl %0, %%edx\n"
-       "movb %1, %%al\n"
-       "outb %%al, %%dx"
-       :
-       :   "r" (opl_port_base + port), "r" ((unsigned char) value)
-       :   "edx", "al"
-    );
-}
-
-// TODO: MSVC version
-// #elif defined(_MSC_VER) && defined(_M_IX6) ...
-
-#else
-
-// Not x86, or don't know how to do port R/W on this compiler.
-
-#define NO_PORT_RW
-
-static unsigned int OPL_Win9x_PortRead(opl_port_t port)
-{
-    return 0;
-}
-
-static void OPL_Win9x_PortWrite(opl_port_t port, unsigned int value)
-{
-}
-
-#endif
-
-static int OPL_Win9x_Init(unsigned int port_base)
-{
-#ifndef NO_PORT_RW
-
-    OSVERSIONINFO version_info;
-
-    // Check that this is a Windows 9x series OS:
-
-    memset(&version_info, 0, sizeof(version_info));
-    version_info.dwOSVersionInfoSize = sizeof(version_info);
-
-    GetVersionEx(&version_info);
-
-    if (version_info.dwPlatformId == 1)
-    {
-        opl_port_base = port_base;
-
-        // Start callback thread
-
-        return OPL_Timer_StartThread();
-    }
-
-#endif
-
-    return 0;
-}
-
-static void OPL_Win9x_Shutdown(void)
-{
-    // Stop callback thread
-
-    OPL_Timer_StopThread();
-}
-
-opl_driver_t opl_win9x_driver =
-{
-    "Win9x",
-    OPL_Win9x_Init,
-    OPL_Win9x_Shutdown,
-    OPL_Win9x_PortRead,
-    OPL_Win9x_PortWrite,
-    OPL_Timer_SetCallback,
-    OPL_Timer_ClearCallbacks,
-    OPL_Timer_Lock,
-    OPL_Timer_Unlock,
-    OPL_Timer_SetPaused
-};
-
-#endif /* #ifdef _WIN32 */
-