From 06b97d2d116b622bc067b245f81b2857767d598e Mon Sep 17 00:00:00 2001 From: Simon Howard Date: Fri, 26 Feb 2010 21:07:59 +0000 Subject: 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_win32.c | 172 +++++++++++++++++++++++++++++++++++++ opl/opl_win9x.c | 140 ------------------------------ 8 files changed, 480 insertions(+), 146 deletions(-) create mode 100644 opl/ioperm_sys.c create mode 100644 opl/ioperm_sys.h create mode 100644 opl/opl_win32.c delete mode 100644 opl/opl_win9x.c 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 + +#define WIN32_LEAN_AND_MEAN +#include +#include + +#include + +#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_win32.c b/opl/opl_win32.c new file mode 100644 index 00000000..29df3643 --- /dev/null +++ b/opl/opl_win32.c @@ -0,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 + +#define WIN32_LEAN_AND_MEAN +#include + +#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 */ + diff --git a/opl/opl_win9x.c b/opl/opl_win9x.c deleted file mode 100644 index ff527b3e..00000000 --- a/opl/opl_win9x.c +++ /dev/null @@ -1,140 +0,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 - -#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 */ - -- cgit v1.2.3