
/**
 * @file: driver.c
 *
 * Tento soubor zajistuje inicializaci finalizaci vsech soucasti ovladace
 * a implementuje mechanismus komunikace s okolim. Obsahuje take vsechny
 * funkce, ktere ovladacum diktuje jejcih architektura.
 */

#include <ntddk.h>
#include "version.h"
#include "ioctls.h"
#include "ssdtinfo.h"
#include "driver.h"

// TRUE - Shadow tabulka jiz byla nalezena. Jinak FALSE
static BOOLEAN ShadowTableInitialized = FALSE;

// Zamek pouzivany k zajisteni exkluzivity pri hledani Shadow tabulky
static KSPIN_LOCK CreateLock;


VOID DriverUnload(IN PDRIVER_OBJECT DriverObject);



/** Obsluhuje pozadavky IRP_MJ_CREATE.
 *
 *  Pokud jeste nebyla Shadow tabulka nalezena, rutina se pokusi naleyt
 *  jeji adresu. V pripade, ze uz tabulka nalezena byla, obsluha pozadavku
 *  vzdy uspeje.
 */
NTSTATUS DriverCreate(IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp)
{
  KIRQL OldIrql;
  NTSTATUS Status = STATUS_UNSUCCESSFUL;
  KdPrint(("driver.c: DriverCreate(DeviceObject=0x%p; Irp=0x%p)\n", DeviceObject, Irp));

  KeAcquireSpinLock(&CreateLock, &OldIrql);
  if (!ShadowTableInitialized) {
    Status = SSDTInfoInitShadowTable();
    ShadowTableInitialized = NT_SUCCESS(Status);
  } else Status = STATUS_SUCCESS;

  KeReleaseSpinLock(&CreateLock, OldIrql);
  Irp->IoStatus.Status = Status;
  IoCompleteRequest(Irp, IO_NO_INCREMENT);

  KdPrint(("driver.c: DriverCreate(-):0x%x\n", Status));
  return Status;
}


/** Obsluha pozadavku IRP_MJ_CLOSE.
 *
 *  Obsluha techto druhu pozadavku vzdy uspeje.
 */
NTSTATUS DriverClose(IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp)
{
  NTSTATUS Status = STATUS_UNSUCCESSFUL;
  KdPrint(("driver.c: DriverClose(DeviceObject=0x%p; Irp=0x%p)\n", DeviceObject, Irp));

  IoCompleteRequest(Irp, IO_NO_INCREMENT);
  Status = STATUS_SUCCESS;

  KdPrint(("driver.c: DriverClose(-):0x%x\n", Status));
  return Status;
}


/** Rutina obsluhuje pozadavky typu IRP_MJ_DEVICE_CONTROL.
 *
 *  Ovladac podporuje nasledujici kody IOCTL:
 *  + IOCTL_SSDTINFO_GENERAL - obecne informace o tabulce systemovych
 *    volani ci jeji stinove "kopii"
 *  + IOCTL_SSDTINFO_ADDRESSES - vraci pole adres systemovych sluzeb dane
 *    tabulky a podtabulky
 *  + IOCTL_SSDTINFO_COUNTERS - vraci tabulku citacu dane tabulky a
 *    podtabulky
 *  + IOCTL_SSDTINFO_ARGUMENTS - vraci tabulku argumentu dane tabulky a
 *    podtabulky
 */
NTSTATUS DriverDeviceControl(IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp)
{
  ULONG TableType = 0;
  PIO_STACK_LOCATION IrpStack = NULL;
  PVOID OutBuffer = NULL;
  ULONG OutBufferLength = 0;
  PVOID InBuffer = NULL;
  ULONG InBufferLength = 0;
  ULONG ControlCode = 0;
  PIN_GENERAL InGeneral = NULL;
  POUT_GENERAL OutGeneral = NULL;
  PIN_SPECIFIC InSpecific = NULL;
  POUT_SPECIFIC OutSpecific = NULL;
  NTSTATUS Status = STATUS_UNSUCCESSFUL;
  KdPrint(("driver.c: DrivercreateDeviceControl(DeviceObject=0x%p; Irp=0x%p)\n", DeviceObject, Irp));

  IrpStack = IoGetCurrentIrpStackLocation(Irp);
  OutBufferLength = IrpStack->Parameters.DeviceIoControl.OutputBufferLength;
  OutBuffer = Irp->AssociatedIrp.SystemBuffer;
  InBufferLength = IrpStack->Parameters.DeviceIoControl.InputBufferLength;
  InBuffer = Irp->AssociatedIrp.SystemBuffer;
  ControlCode = IrpStack->Parameters.DeviceIoControl.IoControlCode;
  Irp->IoStatus.Information = 0;
  switch (ControlCode) {
    case IOCTL_SSDTINFO_GENERAL:
      if (InBufferLength >= sizeof(IN_GENERAL)) {
        InGeneral = InBuffer;
        OutGeneral = OutBuffer;
        TableType = InGeneral->TableType;
        if (TableType == TABLE_TYPE_NORMAL)
          OutGeneral->TableAddress = SSDTInfoGetServiceTable();
        else OutGeneral->TableAddress = SSDTInfoGetServiceTableShadow();
        Status = SSDTInfoGetBasicInfo(TableType, &OutGeneral->ServiceTable, OutBufferLength - sizeof(PVOID));
        if (NT_SUCCESS(Status))
          Irp->IoStatus.Information = sizeof(OUT_GENERAL);
      } else Status = STATUS_BUFFER_TOO_SMALL;
      break;

    case IOCTL_SSDTINFO_ADDRESSES:
      if (InBufferLength >= sizeof(IN_SPECIFIC)) {
        InSpecific = InBuffer;
        OutSpecific = OutBuffer;
        Status = SSDTInfoGetAddressTable(InSpecific->TableType, InSpecific->Subtable, OutSpecific, OutBufferLength);
        if (NT_SUCCESS(Status))
          Irp->IoStatus.Information = OutBufferLength;
      } else Status = STATUS_BUFFER_TOO_SMALL;
      break;

    case IOCTL_SSDTINFO_COUNTERS:
      if (InBufferLength >= sizeof(IN_SPECIFIC)) {
        InSpecific = InBuffer;
        OutSpecific = OutBuffer;
        Status = SSDTInfoGetCounterTable(InSpecific->TableType, InSpecific->Subtable, OutSpecific, OutBufferLength);
        if (NT_SUCCESS(Status))
          Irp->IoStatus.Information = OutBufferLength;
      } else Status = STATUS_BUFFER_TOO_SMALL;
      break;

    case IOCTL_SSDTINFO_ARGUMENTS:
      if (InBufferLength >= sizeof(IN_SPECIFIC)) {
        InSpecific = InBuffer;
        OutSpecific = OutBuffer;
        Status = SSDTInfoGetArgumentTable(InSpecific->TableType, InSpecific->Subtable, OutSpecific, OutBufferLength);
        if (NT_SUCCESS(Status))
          Irp->IoStatus.Information = OutBufferLength;
      } else Status = STATUS_BUFFER_TOO_SMALL;
      break;
    default:
      Status = STATUS_NOT_IMPLEMENTED;
      break;
  }

  Irp->IoStatus.Status = Status;
  IoCompleteRequest(Irp, IO_NO_INCREMENT);

  KdPrint(("driver.c: DriverDeviceControl(-):0x%x\n", Status));
  return Status;
}



/** Inicializuje zarizeni pro komunikaci s okolnim svetem a nasmeruje
 *  na nej symbolicky odkaz, aby se na nej mohly pripojovat i aplikace
 *  bezici v uzivatelskem rezimu.
 *
 *  @param DriverObject Adresa struktury DRIVER_OBJECT reprezentujici
 *  ovladac ssdtinfo.sys.
 *
 *  @return Funkce vraci hodnotu NTSTATUS indikujici uspech ci neuspech
 *  operace.
 */
NTSTATUS DriverInit(IN PDRIVER_OBJECT DriverObject)
{
  UNICODE_STRING uDeviceName;
  UNICODE_STRING uLinkName;
  NTSTATUS Status = STATUS_UNSUCCESSFUL;
  KdPrint(("driver.c: DriverInit(DriverObject=0x%p)\n", DriverObject));

  RtlInitUnicodeString(&uDeviceName, DEVICE_NAME);
  Status = IoCreateDevice(DriverObject, 0, &uDeviceName, FILE_DEVICE_UNKNOWN, 0, FALSE, &DriverObject->DeviceObject);
  if (NT_SUCCESS(Status)) {
    RtlInitUnicodeString(&uLinkName, LINK_NAME);
    Status = IoCreateSymbolicLink(&uLinkName, &uDeviceName);
    if (NT_SUCCESS(Status)) {
      DriverObject->MajorFunction[IRP_MJ_CREATE] = DriverCreate;
      DriverObject->MajorFunction[IRP_MJ_CLOSE] = DriverClose;
      DriverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL] = DriverDeviceControl;
      DriverObject->DriverUnload = DriverUnload;
    } else IoDeleteDevice(DriverObject->DeviceObject);
  }

  KdPrint(("driver.c: DriverFinit(-):0x%x\n", Status));
  return Status;
}


/** Odstrani symbolicky odkaz a zariznei pro komunikaci s okolnim svetem.
 *
 *  @param DriverObject Adresa struktury DRIVER_OBJECT reprezentujici
 *  ovladac ssdtinfo.sys.
 */
VOID DriverFinit(IN PDRIVER_OBJECT DriverObject)
{
  UNICODE_STRING uLinkName;
  KdPrint(("driver.c: DriverFinit(DriverObject=0x%p)\n", DriverObject));

  RtlInitUnicodeString(&uLinkName, LINK_NAME);
  IoDeleteSymbolicLink(&uLinkName);
  IoDeleteDevice(DriverObject->DeviceObject);

  KdPrint(("driver.c: DriverFinit(-)\n"));
  return;
}



/** Tuto rutinu jadro vola tesne pred dynamickym uvolnenim ovladace z
 *  pameti jadra. Rutina provede uklid vsech soucasti ovladace.
 */
VOID DriverUnload(IN PDRIVER_OBJECT DriverObject)
{
  KdPrint(("driver.c: DriverUnload(DriverObject=0x%p)\n", DriverObject));

  DriverFinit(DriverObject);
  SSDTInfoFinit();
  VersionFinit();

  KdPrint(("ssdtinfo.c DriverUnload(-)\n"));
  return;
}


/** Tuto rutinu jadro vola tesne po namapovani souboru ovladace do jadra.
 *   rutina inicializuje vsechny soucasti ovladace. 
 */
NTSTATUS DriverEntry(IN PDRIVER_OBJECT DriverObject, IN PUNICODE_STRING RegPath)
{
  NTSTATUS Status = STATUS_UNSUCCESSFUL;
  KdPrint(("driver.c: DriverEntry(DriverObject=0x%p; RegPath=\"%S\")\n", DriverObject, RegPath->Buffer));

  Status = VersionInit();
  if (NT_SUCCESS(Status)) {
    Status = SSDTInfoInit();
    if (NT_SUCCESS(Status)) {
      Status = DriverInit(DriverObject);
      if (!NT_SUCCESS(Status)) {
        SSDTInfoFinit();
        VersionFinit();
      }
    } else VersionFinit();
  }

  KdPrint(("driver.c: DriverEntry(-):0x%x\n", Status));
  return Status;
}
