/* 
 * This software is licensed under terms of the GNU GENERAL PUBLIC LICENSE
 *
 * This software is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This software  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 software.  If not, see <http://www.gnu.org/licenses/>.
 *
 * Source code is available at http://ppcenter.free.fr
 */

// Please use this file as a Port access wrapper for
// your OS (win64, windows vista, linux, etc).

#include "portlib.h"



// Use the phymem.sys / pmdll.dll couple.
// http://www.codeproject.com/KB/system/phymem.aspx
#include <windows.h>
#include "pmdll.h"


#ifdef  __cplusplus
extern "C" {
#endif


/* Enumerates the port access library available. */
#define LIB_PHYMEM 0
#define LIB_DIRECT 1
int _plib = LIB_PHYMEM;




/*-----------------------------------------------------*/
/* OS version detection stuff. Taken from libticables. */
#include <windows.h>
#define WIN_9X	1
#define WIN_NT	2
#define WIN_64	4

// Note: the kernel version of XP x64 is 5.2, like Windows 2003, not 5.1 as 
// Windows XP 32-Bit.
int win32_check_os(void)
{
    OSVERSIONINFO os;
    SYSTEM_INFO si;

    GetSystemInfo(&si);	// should use GetNativeSystemInfo(&si); & si.wProcessorArchitecture but MSVC too old
    if(si.dwProcessorType > 586)
        return WIN_64;

    memset(&os, 0, sizeof(OSVERSIONINFO));
    os.dwOSVersionInfoSize = sizeof(OSVERSIONINFO);
    GetVersionEx(&os);

    if (os.dwPlatformId == VER_PLATFORM_WIN32_WINDOWS)
    return WIN_9X;
    else if (os.dwPlatformId == VER_PLATFORM_WIN32_NT)
        return WIN_NT;
    else
        return WIN_NT;

    return 0;
}
/*-----------------------------------------------------*/


/* DLL dynamic load stuff. */
typedef int           (*Fct_LoadPhyMemDriver  )(void);
typedef void          (*Fct_UnloadPhyMemDriver)(void);
typedef unsigned char (*Fct_ReadPortByte      )(unsigned short);
typedef void          (*Fct_WritePortByte     )(unsigned short, unsigned char);

HINSTANCE pmdll_h = 0;
Fct_LoadPhyMemDriver   pmdll_LoadPhyMemDriver   = 0;
Fct_UnloadPhyMemDriver pmdll_UnloadPhyMemDriver = 0;
Fct_ReadPortByte       pmdll_ReadPortByte       = 0;
Fct_WritePortByte      pmdll_WritePortByte      = 0;


int port_init(void)
{
    int ret = 0;

    /* Decide which port access library to use. */
    int ver = win32_check_os();
    switch(ver)
    {
    default:
    case(WIN_NT):
        scl_log(SCLT_DLLPG, 1, "[portlib]Use phymem.sys/pm.dll.");
        _plib = LIB_PHYMEM;
        break;

    case(WIN_9X):
        scl_log(SCLT_DLLPG, 1, "[portlib]Use direct port access.");
        _plib = LIB_DIRECT;
        break;

    case(WIN_64):
        scl_log(SCLT_DLLPG, 0, "[portlib]Warning: unsupported operating system !");
        _plib = LIB_PHYMEM;
        break;
    }


    /* Init the ports access. */
    switch(_plib)
    {
    default:
    case(LIB_PHYMEM):
        /* Open pmdll.dll. */
        if(pmdll_h != 0)
        {
            FreeLibrary(pmdll_h); pmdll_h = 0;
        }
        pmdll_h = LoadLibrary("pmdll.dll");
        if(pmdll_h)
        {
            pmdll_LoadPhyMemDriver   = (Fct_LoadPhyMemDriver  )GetProcAddress(pmdll_h, "LoadPhyMemDriver"  );
            pmdll_UnloadPhyMemDriver = (Fct_UnloadPhyMemDriver)GetProcAddress(pmdll_h, "UnloadPhyMemDriver");
            pmdll_ReadPortByte       = (Fct_ReadPortByte      )GetProcAddress(pmdll_h, "ReadPortByte"      );
            pmdll_WritePortByte      = (Fct_WritePortByte     )GetProcAddress(pmdll_h, "WritePortByte"     );
        }

        if(pmdll_h != 0)
        {
            ret = (pmdll_LoadPhyMemDriver() ? 0 : 1);
        }
        else
        {
            ret = 1;
        }
        break;

    case(LIB_DIRECT):
        /* Nothing special to do in this case. */
        ret = 0;
        break;
    }

    return ret;
}
int port_close(void)
{
    int ret = 0;
    switch(_plib)
    {
    default:
    case(LIB_PHYMEM):
        if(pmdll_h != 0)
        {
            pmdll_UnloadPhyMemDriver();
        }

        /* Close pmdll.dll */
        if(pmdll_h != 0)
        {
            FreeLibrary(pmdll_h); pmdll_h = 0;
        }
        ret = 0;
        break;

    case(LIB_DIRECT):
        /* Nothing special to do in this case. */
        break;
    }

    return ret;
}

int port_read_byte(unsigned int addr)
{
    int ret = 0;
    int c;

    switch(_plib)
    {
    default:
    case(LIB_PHYMEM):
        if(pmdll_h != 0)
        {
            ret = pmdll_ReadPortByte(addr);
        }
        else
        {
            ret = 0x42;
        }
        break;

    case(LIB_DIRECT):
        /* I/O thru assembly code (Taken from libticables). */
#ifdef __GNUC__
asm("movl $0,%%eax \n movw %1,%%dx \n inb %%dx,%%al \n movl %%eax,%0": "=g"(c): "g"(addr):"eax",
      "dx");
#else
            __asm {
         mov eax, 0 
         mov edx, addr 
         in al, dx 
         mov c, eax}
#endif
        ret = c;
    break;
    }

    return ret;
}
void port_write_byte(unsigned int addr, int data)
{
    switch(_plib)
    {
    default:
    case(LIB_PHYMEM):
        if(pmdll_h != 0)
        {
            pmdll_WritePortByte(addr, data);
        }
        break;

    case(LIB_DIRECT):
        /* I/O thru assembly code (Taken from libticables). */
#ifdef __GNUC__
asm("movw %0,%%dx \n movw %1,%%ax \n outb %%al,%%dx"::"g"(addr), "g"(data):"ax",
      "dx");
#else
            __asm {
          mov edx, addr 
          mov eax, data 
          out dx, al}
#endif
        break;
    }
}


#ifdef  __cplusplus
}
#endif

