/*
 *	ctpci Windows Kernel driver 32/64 Bit for PCI (and MSR)   
 *
 *	Copyright (c) c't 2010 Andreas Stiller 
 *
 *	Can be freely distributed and used under the terms of the GNU GPL.
 
For compilation please include pathes to WDK or DDK and SDK in 
Tools/Options/Projects and Solutions/VC++-Directories
for instance for WDK 7600.16385.0 and SDK 7.0 

Includes: 
C:\Program Files\Microsoft SDKs\Windows\v7.0\Include
$(VCInstallDir)include
C:\WINDDK\7600.16385.0\inc\ddk
C:\WINDDK\7600.16385.0\inc\api
...

Libs (64 Bit)
C:\Program Files\Microsoft SDKs\Windows\v7.0\Lib\x64
C:\WINDDK\7600.16385.0\lib\win7\amd64
... 

Libs (32 Bit)
C:\Program Files\Microsoft SDKs\Windows\v7.0\Lib\i386
C:\WINDDK\7600.16385.0\lib\win7\i386

if you want to use intrinsics like __rdmsr or __wrmsr, you have to patch intrin.h in order 
to run with ntddk.h (see below) 

 */


#ifndef STD_CALL
#define STD_CALL
#endif

#define CONDITION_HANDLING 1
#define WIN32_LEAN_AND_MEAN 1 
#define NT_UP 1
#define NT_INST 0
#define _NT1X_ 100
#define DEVL 1
#define FPO 1
#ifndef _IDWBUILD
#define _IDWBUILD
#endif

#ifdef _DEBUG 
#define DBG 0
#else 
#define DBG 0
#endif 

//#define with_HalGetBusData  
/*
 use of HalGetBusData (for Find_PCI) 
 needs patch of ntddk.h  (no NO_LEGACY_DRIVER for HalGetBusData)
*/

#define with_MSRs  
/* 
use of rdmsr and wrmsr, 
needs patch of intrin.h (Kollision of interlock intrinsics in ntddk.h)


*/

extern "C"
{ 
#include <ntddk.h>
#include <string.h>
#include <stdlib.h>
#include <devioctl.h>
#include <conio.h>
#include "ctpci.h"
}

#ifdef with_MSRs
#define skipddkintrinsics
#include <intrin.h> // !!!!patched Version!!! 


/*  WDK 
__MACHINEIA32(long _InterlockedAddLargeStatistic(__int64 volatile *, long))
__MACHINEI(__int64 _InterlockedCompareExchange64(__int64 volatile *, __int64, __int64))
*/

/*  DDK
__MACHINEI(void __movsb(unsigned char *, unsigned char const *, size_t))
__MACHINEI(void __movsw(unsigned short *, unsigned short const *, size_t))
__MACHINEI(void __movsd(unsigned long *, unsigned long const *, size_t))
__MACHINEX64(void __movsq(unsigned long long *, unsigned long long const *, size_t))

__MACHINEI(long _InterlockedExchange(long volatile *, long))
__MACHINEI(long _InterlockedExchangeAdd(long volatile *, long))
__MACHINEI(long _InterlockedCompareExchange (long volatile *, long, long))
__MACHINEIW64(long _InterlockedOr(long volatile *, long))
__MACHINEIW64(long _InterlockedXor(long volatile *, long))
*/

#pragma intrinsic (__readmsr,__writemsr)
#endif



#define ctpci_DEVICE_NAME L"\\Device\\ctpci"
#define DOS_DEVICE_NAME      L"\\DosDevices\\Dev_ctpci"


#define   ctpci_Major		     1L
#define   ctpci_Minor            0L

long magic[3]={0x12345678,ctpci_Major,ctpci_Minor};


typedef ULONG  DWORD;
typedef USHORT WORD;

//#define with_HalGetBusData

#define IOPM_SIZE 0x2000

typedef UCHAR IOPMTYP[IOPM_SIZE];

typedef struct {
  ULONG Dummy;
  IOPMTYP iopm;
} TLocalDevInfo,* PLocalDevInfo;


#define IOCTL_MAPMEM_MAP_USER_PHYSICAL_MEMORY CTL_CODE(FILE_DEVICE_MAPMEM,MAPMEM_IOCTL_INDEX, METHOD_BUFFERED,FILE_ANY_ACCESS)

#ifdef with_HalGetBusData // erfordert Patch von ntddk.h 

static NTSTATUS
ProbePCI(ULONG VendorID, ULONG DeviceID, UCHAR *Irq, ULONG *BaseAdr)
{

PCI_SLOT_NUMBER     slotNumber;
PPCI_COMMON_CONFIG  pciData;
UCHAR               buf[PCI_COMMON_HDR_LENGTH];
ULONG               i, f, j, bus;
BOOLEAN             flag;

  pciData = (PPCI_COMMON_CONFIG) buf;
  slotNumber.u.bits.Reserved = 0;

  flag = TRUE;
  for (bus = 0; flag; bus++)
    {
    for (i = 0; i < PCI_MAX_DEVICES  &&  flag; i++)
      {
      slotNumber.u.bits.DeviceNumber = i;

      for (f = 0; f < PCI_MAX_FUNCTION; f++)
        {
        slotNumber.u.bits.FunctionNumber = f;

        j = HalGetBusData(PCIConfiguration, bus, slotNumber.u.AsULONG,
                          pciData, PCI_COMMON_HDR_LENGTH);

        if (j == 0)
          {
          /* out of buses */
			  DbgPrint ("ctpci: Out of Busses\n"); 
          flag = FALSE;
          break;
          }
       
        if (pciData->VendorID == PCI_INVALID_VENDORID)
          {
          /* skip to next slot */
          break;
          }
#ifdef _DEBUG
		DbgPrint("ctpci: Vendor: %x, Device: %x\n",pciData->VendorID,pciData->DeviceID);
#endif
        /* check for vendor and device id, return base address */
        if (pciData->VendorID == VendorID && pciData->DeviceID == DeviceID)
          {
          *Irq       = pciData->u.type0.InterruptLine;
          BaseAdr[0] = pciData->u.type0.BaseAddresses[0];
          BaseAdr[1] = pciData->u.type0.BaseAddresses[1];
          BaseAdr[2] = pciData->u.type0.BaseAddresses[2];
          BaseAdr[3] = pciData->u.type0.BaseAddresses[3];
          BaseAdr[4] = pciData->u.type0.BaseAddresses[4];
          BaseAdr[5] = pciData->u.type0.BaseAddresses[5];
          return STATUS_SUCCESS;
          }
        }
      }
    }

  return STATUS_NO_SUCH_DEVICE;
}
#endif

static NTSTATUS
ReadPCIDword(ULONG index, ULONG bdf, ULONG* Value){
	ULONG bus,res;
	PCI_SLOT_NUMBER     slotNumber;
	if ((index >> 31) &1) { 
		//Index mit Bit 31 gesetzt: 
		//Index ist CF8-Configuration-Entry 
		bus = (index >>16) & 0xff;  
		slotNumber.u.bits.DeviceNumber  = index >> 11;
		slotNumber.u.bits.FunctionNumber= index >> 8;
		index=((index >> 2) & 0x3F) | ((index >> 19) & 0x3FF);
	}
	else {
		bus=(bdf >> 8) & 0xff;
		slotNumber.u.bits.DeviceNumber = bdf >>3; 
		slotNumber.u.bits.FunctionNumber = bdf;
	}
	*Value=0;
	slotNumber.u.bits.Reserved = 0;
	res=HalGetBusDataByOffset(PCIConfiguration, bus,
		slotNumber.u.AsULONG,Value, index & 0xFFC,sizeof(ULONG));
#ifdef _DEBUG 
		DbgPrint ("ctpci: ReadPCIDword, index: %x BDF: %x Bus %x, Dev: %x fun: %x val: %x res: %x commonlen=%x\n",
			index,bdf,bus, slotNumber.u.bits.DeviceNumber,slotNumber.u.bits.FunctionNumber, *Value, res,PCI_COMMON_HDR_LENGTH);
#endif 
	 
	 
 

	if (res==0){/* out of buses */
#ifdef _DEBUG 
 DbgPrint ("ctpci: Out of Buses\n"); 
#endif
	return STATUS_END_OF_FILE;       
	}
	if (res==2) { /* no Device */
#ifdef _DEBUG 
  DbgPrint ("ctpci: No Device\n"); 
#endif
	return STATUS_NO_SUCH_DEVICE;    
	}
return STATUS_SUCCESS;
}

static NTSTATUS
WritePCIDword(ULONG index, ULONG bdf, ULONG Value)
{
ULONG bus,res;
PCI_SLOT_NUMBER     slotNumber;

	if ((index >> 31) &1) //Index mit Bit 31 gesetzt: Index ist CF8-Configuration-Entry 
		{ 
			bus = (index >>16) & 0xff;  
			slotNumber.u.bits.DeviceNumber  = index >> 11;
			slotNumber.u.bits.FunctionNumber= index >> 8;
			index=((index >> 2) & 0x3F) | ((index >> 19) & 0x3FF);
	
		}
		else 
		{
        bus=(bdf >> 8) & 0xff;
	    slotNumber.u.bits.DeviceNumber = bdf >>3; 
	    slotNumber.u.bits.FunctionNumber = bdf;
		}
		
        slotNumber.u.bits.Reserved = 0;

		res=HalSetBusDataByOffset(PCIConfiguration, bus,slotNumber.u.AsULONG,
                          &Value, index & 0xFFC,sizeof(ULONG));
#ifdef _DEBUG 
		DbgPrint ("ctpci: WritePCIDword, index: %x BDF: %x Bus %x, Dev: %x fun: %x val: %x res: %x\n",index,bdf,bus, slotNumber.u.bits.DeviceNumber,slotNumber.u.bits.FunctionNumber, Value, res);
#endif 

        if (res==0)
        {
          /* out of buses */
			DbgPrint ("ctpci: Out of Buses\n"); 
          return STATUS_END_OF_FILE;       
        }
		if (res==2)
		{ /* no device */  
          return STATUS_NO_SUCH_DEVICE;    
		}
        return STATUS_SUCCESS;
}


NTSTATUS ctpci_Control(IN PIRP pIrp,
					   IN PIO_STACK_LOCATION IrpStack, IN ULONG IoctlCode)
{
	PctpciInfo InBuf=(PctpciInfo)pIrp->AssociatedIrp.SystemBuffer;
	ULONG InBufSize =IrpStack->Parameters.DeviceIoControl.InputBufferLength;
	ULONG OutBufSize=IrpStack->Parameters.DeviceIoControl.OutputBufferLength;

	void*  OutBuf = pIrp->AssociatedIrp.SystemBuffer; 
	ULONG* retlen = (ULONG*) &(pIrp->IoStatus.Information);

	ULONG OpCode  = InBuf->OpCode;
	ULONG Index   = InBuf->Par1; 
	ULONG Valuelo = InBuf->Par2;
	ULONG Valuehi = InBuf->Par3;

#ifdef _DEBUG 
	DbgPrint("ctpci: OpCode=%2x, %8x %8x %8x, ",OpCode,Index, Valuelo, Valuehi);
#endif

	OpCode &=0xffff;

	*retlen =0; 


	__try {
		switch(OpCode) {
  case OP_Check:
	  memcpy(OutBuf,magic,sizeof(long)*3);
	  *retlen = sizeof(long)*3;
	  return(STATUS_SUCCESS);

  case OP_ReadPCIDword: 
	  *retlen = sizeof(ULONG);
	  return ReadPCIDword(Index,Valuelo,(PULONG) OutBuf );

  case OP_WritePCIDword: 
	  return WritePCIDword(Index,Valuelo,Valuehi);

  case OP_ReadPhysMemDword:{
	  PHYSICAL_ADDRESS PADDR;
	  PADDR.LowPart=Index;
	  PADDR.HighPart=Valuelo;	// Reserve für > 4GByte
	  PVOID Linadr=MmMapIoSpace (PADDR,4,MmNonCached);
	  if (Linadr==0) return STATUS_ACCESS_VIOLATION;
	  *((PULONG)(OutBuf))= *(ULONG*) Linadr;
	  MmUnmapIoSpace (Linadr,4); 
	  *retlen = sizeof(ULONG);
	  return STATUS_SUCCESS;
						   }

#ifdef with_MSRs 	   
  case OP_GetMR:       
	  *((PULONGLONG)OutBuf)= __readmsr (Index &0xFFF); 
	  *retlen=sizeof (ULONGLONG);
	  return STATUS_SUCCESS;

  case OP_SetMR:
	  ULONGLONG v=((ULONGLONG) Valuehi) <<32 | (ULONGLONG) Valuelo;
	  __writemsr (Index,v); 
	  return STATUS_SUCCESS;
#endif


#ifdef with_HalGetBusData 
  case OP_FindPCI:
	  Status = ProbePCI(InBuf->Par1, InBuf->Par2, &Irq, BaseAdr);
	  if (Status == STATUS_NO_SUCH_DEVICE)
		  pIrp->IoStatus.Information = 0;
	  else
	  {
		  DbgPrint("ctpci: BaseAdr0 = %X\n", BaseAdr[0]);
		  DbgPrint("ctpci: BaseAdr1 = %X\n", BaseAdr[1]);
		  *(PULONG)OutBuf = (ULONG)Irq;
		  *((PULONG)OutBuf+1) = BaseAdr[0];
		  *((PULONG)OutBuf+2) = BaseAdr[1];
		  *((PULONG)OutBuf+3) = BaseAdr[2];
		  *((PULONG)OutBuf+4) = BaseAdr[3];
		  *((PULONG)OutBuf+5) = BaseAdr[4];
		  *((PULONG)OutBuf+6) = BaseAdr[5];
		  pIrp->IoStatus.Information = 7*sizeof(ULONG);
	  }

	  return STATUS_SUCCESS;
		
#endif 
	}
		return STATUS_INVALID_PARAMETER; 
	}

	__except (1)
	{
		return STATUS_NONCONTINUABLE_EXCEPTION; 
	}
}


NTSTATUS ctpci_CreateDevice(IN  PWSTR PrototypeName, 
    IN  DEVICE_TYPE DeviceType, IN  PDRIVER_OBJECT DriverObject,
    OUT PDEVICE_OBJECT *ppDevObj)
{
  UNICODE_STRING NtDeviceName;
  UNICODE_STRING Win32DeviceName;  
  RtlInitUnicodeString(&NtDeviceName,PrototypeName);
  NTSTATUS Status = IoCreateDevice(DriverObject,sizeof(TLocalDevInfo),
                            &NtDeviceName,DeviceType,0,FALSE,ppDevObj);
  if (!NT_SUCCESS(Status)) return Status;
  RtlZeroMemory((*ppDevObj)->DeviceExtension,sizeof(TLocalDevInfo));
  RtlInitUnicodeString(&Win32DeviceName,DOS_DEVICE_NAME);
  Status = IoCreateSymbolicLink(&Win32DeviceName,&NtDeviceName);
  if (!NT_SUCCESS(Status))
    IoDeleteDevice(*ppDevObj);
  return Status;
}

NTSTATUS ctpci_Dispatch(IN PDEVICE_OBJECT pDO,IN PIRP pIrp)
{
	pIrp->IoStatus.Information = 0;  // Anzahl Rückgabe-Bytes
	
	PIO_STACK_LOCATION pIrpStack = IoGetCurrentIrpStackLocation(pIrp);
	NTSTATUS           Status    = STATUS_NOT_IMPLEMENTED;
  	
	switch(pIrpStack->MajorFunction) {
	case IRP_MJ_CREATE:
	case IRP_MJ_CLOSE:
	  Status = STATUS_SUCCESS;
	  break;
	 case IRP_MJ_DEVICE_CONTROL: 
		  Status = ctpci_Control(pIrp,pIrpStack,
		  pIrpStack->Parameters.DeviceIoControl.IoControlCode);

	}
#ifdef _DEBUG_
	DbgPrint ("ctcpi: Status= %0x, Retlen=%d\n",Status,pIrp->IoStatus.Information);  
#endif
	pIrp->IoStatus.Status = Status;
	IoCompleteRequest(pIrp,IO_NO_INCREMENT);
	return Status;
}

VOID ctpci_Unload(PDRIVER_OBJECT DriverObject)
{ // Symbolischen Link wieder auflösen
  UNICODE_STRING Win32DeviceName;
  RtlInitUnicodeString(&Win32DeviceName,DOS_DEVICE_NAME);
  IoDeleteSymbolicLink(&Win32DeviceName);
  IoDeleteDevice(DriverObject->DeviceObject);
}

extern "C" NTSTATUS DriverEntry(IN PDRIVER_OBJECT DriverObject,
                                IN PUNICODE_STRING RegistryPath)
{ // Symbolischen Link erzeugen, so dass Win darauf zugreifen kann
  DriverObject->MajorFunction[IRP_MJ_CREATE]         = ctpci_Dispatch;
  DriverObject->MajorFunction[IRP_MJ_CLOSE]          = ctpci_Dispatch;
  DriverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL] = ctpci_Dispatch;
  DriverObject->DriverUnload                         = ctpci_Unload;
  PDEVICE_OBJECT DeviceObject;
  return ctpci_CreateDevice(ctpci_DEVICE_NAME,DIRECTNT_TYPE,
                               DriverObject,&DeviceObject);
}

