
/**
 * @file: sync.c
 *
 * Tento soubor implementuje rutiny pro zablokovani procesoru, ktere take
 * dovoluji vykonat urcity kod bud na jednom zadanem, nebo na vsech procesorech.
 * 
 * Blokovani funguje tak, ze se uroven IRQL na aktualnim procesoru zvedne na
 * DISPATCH_LEVEL (pokud uz takova je, nebo je vyssi, neprovadi se nic). Aktualni
 * procesor nasledne rozesle na ostatni procesory objekty DPC reprezentujici blokovaci
 * rutinu. Aktualni procesor pocka, nez vsechny ostatni procesory zacnou vykonvat tuto
 * rutinu, ktera v cyklu ceka na prikaz k odblokovani. 
 *
 * Odblokovani se provadi shozenim priznaku, coz blokovaci rutine signalizuje ukonceni
 * smycky. Pokud je to potreba, IRQL aktualniho procesoru se nasledne snizi na hodnotu
 * pred zablokovanim.
 *
 * Donuceni procesoru vykonat urcity kod je realizovano zaslanim objektu DPC na prislusny
 * procesor. Aktualni procesor neceka na vykonani tohoto kodu.
 *
 * Vykonani urciteho kodu na vsech procesorech je realizovano rozeslanim objektu DPC na kazdy
 * procesor (vcetne aktualniho). Aktualni procesor opet neceka, az vsichni (vcetne neho) vykonaji
 * zadany kod.
 */

#include <ntddk.h>
#include "driver.h"
#include "sync.h"

// Promenna uchovava pocet aktivnich procesoru
static ULONG CPUCount = 0;



/** Inicializuje strukturu BLOCK_CONTEXT.
 *
 * Rutina nastavi jednotlive polozky struktury BLOCK_CONTEXT tak, aby
 * tato struktura mohla byt pouzita pri volani rutiny BlockProcessors a
 * nasledne rutiny UnblockProcessors. Rutina take alokuje pole objektu DPC,
 * pro kazdy procesor rezervuje jeden objekt. Objekct DPC pro aktualni procesor
 * vsak nebude pri blokovani vyuzit.
 *
 * @param Context Adresa struktury BLOCK_CONTEXT, kterou si volajici
 * preje inicializovat.
 *
 * @return Funkce vraci hodnotu NTSTATUS indikujici uspech ci neuspech
 * operace. Chybovy kod funkce muze vratit pouze v pripade, ze se nepodari
 * alokovat pole objektu DPC.
 */
NTSTATUS BlockContextInit(PBLOCK_CONTEXT Context)
{
  NTSTATUS Status = STATUS_UNSUCCESSFUL;
  KdPrint(("sync.c: BlockContextInit(Context=0x%p)\n", Context));

  // Nula zablokovanych procesoru
  Context->CPUBlocked = 0;
  // Procesory nejsou zablokovany
  Context->Blocked = 0;
  // Dosazeni urcite hodnoty do polozky OldIrql. Tato hodnota bude v
  // rutine BlockProcessors prepsana.
  Context->OldIrql = PASSIVE_LEVEL;
  // Neprobiha proces blokovani procesoru
  Context->Block = 0;
  // alokace pole DPC (pouze v pripade, ze stroj disponuje vice nez jednim
  // aktivnim logickym procesorem).
  Context->Dpc = NULL;
  if (CPUCount > 1) {
    Context->Dpc = ExAllocatePoolWithTag(NonPagedPool, CPUCount*sizeof(KDPC), POOL_TAG);
    if (Context->Dpc != NULL) 
      Status = STATUS_SUCCESS;
    else Status = STATUS_INSUFFICIENT_RESOURCES;
  } else Status = STATUS_SUCCESS;

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


/** Uvolni pamet zabranou strukturou BLOCK_CONTEXT.
 *
 * Rutina neuvolni pamet zabranou samotnou strukturou BLOCK_CONTEXT, ale
 * pouze blok, ktery zabira pole objektu DPC. Chova se tedy, co se tyce
 * operaci s pameti, reverzne k funkci BlockContextInit.
 *
 * @param Context Struktura BLOCK_CONTEXT, nad kterou si volajici preje
 * provest uklid.
 */
VOID BlockContextDestroy(PBLOCK_CONTEXT Context)
{
  KdPrint(("sync.c: BlockContextDestroy(Context=0x%p)\n", Context));

  // "Uklizet" nelze v pripade, ze je tato struktura prave pouzivana
  // pri blokovani procesoru.
  ASSERT(Context->CPUBlocked == 0);
  ASSERT(Context->Blocked == 0);
  ASSERT(Context->Block == 0);

  // Dealokaci pole objektu DPC je nutne provest pouze v pripade, 
  // ze stroj disponuje vice nez jednim aktivnim logickym procesorem.
  if (CPUCount > 1) {
    ExFreePoolWithTag(Context->Dpc, POOL_TAG);
    Context->Dpc = NULL;
  }

  KdPrint(("sync.c: BlockContextDestroy(-)\n"));
  return;
}


/** Blokovaci rutina.
 *
 * Tato rutina zpusobuje zablokovani procesoru, ktery ji vykonava. Procesor
 * vykonava kod v "nekonecne" smycce, dokud rutina UnblockProcessors neshodi
 * priznak Block v prislusne strukture BLOCK_CONTEXT.
 *
 * @param Dpc Objekt DPC, ktery reprezentuje tuto instanci blokujici rutiny.
 * @param Context Adresa struktury BLOCK_CONTEXT, ktera je pouzita pri tomto
 * blokovani procesoru.
 * @param Arg1 Tento parametr ma vzdy hodnotu NULL a rutina jej nepouziva. Je
 * zde pouze z duvodu dodrzeni deklarace odlozenych procedur pro objekty DPC.
 * @param Arg2 Tento parametr ma vzdy hodnotu NULL a rutina jej nepouziva. Je
 * zde pouze z duvodu dodrzeni deklarace odlozenych procedur pro objekty DPC.
 */
static VOID BlockingRoutine(PKDPC Dpc, PBLOCK_CONTEXT Context, PVOID Arg1, PVOID Arg2) 
{
  KdPrint(("sync.c: BlockingRoutine(Dpc=%p;Context=0x%p; Arg1=0x%p; Arg2=0x%p)\n", Dpc, Context, Arg1, Arg2));

  // Zvyseni poctu zablokovanych procesoru (o aktualni procesor)
  InterlockedIncrement(&Context->CPUBlocked);
  if (InterlockedCompareExchange(&Context->CPUBlocked, Context->CPUBlocked, Context->CPUBlocked) == CPUCount)
    // Pokud jiz pocet zablokovanych procesoru dosahl poctu aktivnich
    // logickych procesoru stroje bez jednoho, je treba signalizovat nastavenim
    // priznaku Blocked ve strukture BLOCK_CONTEXT, ze se podarilo vsechny
    // potrebne procesory zablokovat.
    InterlockedIncrement(&Context->Blocked);

  // Nekonecna smycka. Procesor v ni uvizne, dokud rutina UnblockProcessors
  // neshodi priznak Block ve strukture BLOCK_CONTEXT, cimz signalizuje
  // ukonceni operace blokovani.
  while (InterlockedCompareExchange(&Context->Block, Context->Block, 1) == 1)
    __asm nop

  // Blokovani skoncilo. Je treba snizit pocet zablokovanych procesoru
  // o aktualni procesor.
  InterlockedDecrement(&Context->CPUBlocked);

  KdPrint(("sync.c: BlockingRoutine(-)\n"));
  return;
}



/** Zablokuje vsechny aktivni logicke procesory az na aktualni.
 *
 * Pokud se IRQL aktualniho procesoru nachazi pod urovni DISPATCH_LEVEL,
 * je na tuto uroven zvednuta. Tim dojde k vyrazeni planovace na aktualnim
 * procesoru. Rutina nasledne na vsechny ostatni aktivni logicke procesory
 * rozesle objekty DPC a pocka, nez zacnou provadet smycku v blokovaci rutine
 * (BlockingRoutine).
 */
VOID BlockProcessors(PBLOCK_CONTEXT Context)
{
  ULONG	i = 0;
  ULONG CurrentCPU = 0;
  KdPrint(("sync.c: BlockProcessors(Context=0x%p)\n", Context));

  // Ulozeni hodnoty IRQL pred zablokovanim a jeji pripadne zvyseni na
  // DISPATCH_LEVEL.
  Context->OldIrql = KeGetCurrentIrql();
  if (Context->OldIrql < DISPATCH_LEVEL)
    KeRaiseIrql(DISPATCH_LEVEL, &Context->OldIrql);

  // Zjisteni cisla aktualniho procesoru (ten enbude blokovan).Tuto
  // operaci lze bezpecne provest, protoze planovani vlaken na aktualnim
  // procesoru je maskovano hardwarovou prioritou.
  CurrentCPU = KeGetCurrentProcessorNumber();

  // Volajici chce blokovat procesory
  Context->Block = 1;
  // Jiz je jeden procesor zablokovany (aktualni procesor). Ve skutecnosti
  // neni, ale bude provadet kod, ktery urci volajici, takze vlastne
  // "zablokovany" je.
  Context->CPUBlocked = 1;
  // Vsechny potrebne procesory ale zablokovany jeste nejsou.
  Context->Blocked = 0;
  if (CPUCount > 1) {
    // Stroj disponuje vice aktivnimi logickymi procesory. Je nutne
    // rozeslat objekty DPC.
    for (i = 0; i < CPUCount; i++) {
      if (i != CurrentCPU) {
        KeInitializeDpc(&Context->Dpc[i], (PKDEFERRED_ROUTINE)BlockingRoutine, NULL);
        KeSetTargetProcessorDpc(&Context->Dpc[i], (CCHAR)i);
        KeInsertQueueDpc(&Context->Dpc[i], NULL, NULL);
      }
    }

	// Cekani na nastaveni priznaku Blocked ve strukture BLOCK_CONTEXT.
    // Blokovaci rutina jej nastavi, az do ni dorazi vsechny potrebne
    // logicke procesory
    while (InterlockedCompareExchange(&Context->Blocked, Context->Blocked, 1) == 0)
      __asm nop

  } else InterlockedIncrement(&Context->Blocked);

  KdPrint(("sync.c: BlockProcessors(-)\n"));
  return;
}


/** Odblokuje procesory zablokovane rutinou BlockProcessors.
 *
 * @param Context Adresa struktury BLOCK_CONTEXT s informacemi o
 * zablokovanych procesorech. Volajici tuto strukturu musel predtim
 * zadat jako parametr do rutiny BlockProcessors.
 */
VOID UnblockProcessors(PBLOCK_CONTEXT Context)
{
  KdPrint(("sync.c: UnblockProcessors(Context=0x%p)\n", Context));

  // Shozeni priznaku Blocked.
  InterlockedDecrement(&Context->Blocked);
  // Shozeni priznaku Block. Tim dojde ke konci "nekonecnych cyklu" ve
  // vsech instanich blokovaci rutiny.
  InterlockedDecrement(&Context->Block);
  // Cekani, dokud vsechny potrebne logicke procesory neopusti blokovaci
  // rutinu.
  while (InterlockedCompareExchange(&Context->CPUBlocked, Context->CPUBlocked, 1) > 1)
    __asm nop

  // Snizeni IRQL aktualniho procesoru na puvodni hodnotu, je-li to treba.
  if (Context->OldIrql < DISPATCH_LEVEL)
    KeLowerIrql(Context->OldIrql);

  KdPrint(("sync.c: UnblockProcessors(-)\n"));
  return;
}



/** Rutina donuti zadany procesor vykonat prislusny kod.
 *
 * Rutina procesor donuti k vykonani kodu pomoci objektu DPC. Rutina
 * neceka, az bude kod na cilovem procesoru skutecne vykonat. Cilovym
 * procesorem muze byt i aktualni procesor.
 *
 * @param Processor Cislo ciloveho logickeho procesoru.
 * @param Routine Adresa rutiny, kterou ma cilovy procesor vykonat. 
 * Hlavicka rutiny musi odpovidat hlavicce odlozene volane procedury
 * (KDEFFERED_ROUTINE).
 * @param Context Prvni parametr cilove rutiny.
 * @param Arg1 Druhy parametr cilove rutiny.
 * @param Arg2 Treti parametr cilove rutiny.
 * @param Dpc Adresa struktury DPC, kterou funkce vyuzije k donuceni procesoru
 * vykonat zadany kod.
 */
VOID ExecuteAtProcessor(IN ULONG Processor, IN PKDEFERRED_ROUTINE Routine, IN PVOID Context, IN PVOID Arg1, IN PVOID Arg2, IN PKDPC Dpc)
{
  KdPrint(("sync.c: VykonejNaProcesoru(Procesor=%u; Rutina=0x%p; Context=0x%p; Arg1=0x%p; Arg2=0x%p; Dpc=0x%p)\n", Processor, Routine, Context, Arg1, Arg2, Dpc));

  KeInitializeDpc(Dpc, Routine, Context);
  KeSetTargetProcessorDpc(Dpc, (CCHAR)Processor);
  KeInsertQueueDpc(Dpc, Arg1, Arg2);

  KdPrint(("sync.c: ExecuteAtProcessor(-)\n"));
  return;
}


/** Donuti vsechny procesory (vcetne aktualniho) vykonat zadany kod.
 *
 * Rutina k tomuto ucely pouzije mechanismu odlozeneho volani procedur
 * (DPC); na kazdy aktivni logicky procesor zasle jeden objekt DPC reprezentujici
 * zadany kod. 
 *
 * Rutina neceka, az zadany kod vsechny procesory vykonaji. Synchronizace
 * je veci volajiciho.
 *
 * @param Routine Rutina, kterou maji vsechny procesory vykonat. Jeji hlavicka
 * musi byt kompatibilni s odlozene volanou procedurou (KDEFFERED_ROUTINE).
 * @param Context Prvni parametr cilove rutiny.
 * @param Arg1 Druhy parametr cilove rutiny.
 * @param Arg2 Treti parametr cilove rutiny.
 * @param Dpc Adresa promenne, kterou funkce v pripade uspechu vyplni adresou pole
 * struktur DPC. Volajici toto pole musi odalokovat, az dobehnou vsechny instance
 * cilove rutiny.
 *
 * @return Funkce vraci hodnotu NTSTATUS indikujici uspech ci neuspech
 * operace.
 */
NTSTATUS ExecuteAtEveryProcessor(IN PKDEFERRED_ROUTINE Routine, IN PVOID Context, IN PVOID Arg1, IN PVOID Arg2, OUT PVOID *Dpc)
{
  ULONG i;
  KIRQL OldIrql = KeGetCurrentIrql();
  NTSTATUS Status = STATUS_UNSUCCESSFUL;
  KdPrint(("sync.c: ExecuteAtEveryProcessor(Routine=0x%p; Context=0x%p; Arg1=0x%p; Arg2=0x%p; Dpc=0x%p)\n", Routine, Context, Arg1, Arg2, Dpc));

  *Dpc = ExAllocatePoolWithTag(NonPagedPool, CPUCount*sizeof(KDPC), POOL_TAG);
  if (*Dpc != NULL) {
    Status = STATUS_SUCCESS;
    if (OldIrql < DISPATCH_LEVEL)
      KeRaiseIrql(DISPATCH_LEVEL, &OldIrql);

    for (i = 0; i < CPUCount; ++i) {
      PKDPC TmpDpc = *Dpc;
      ExecuteAtProcessor(i, Routine, Context, Arg1, Arg2, &TmpDpc[i]);
    }

    if (OldIrql < DISPATCH_LEVEL)
      KeLowerIrql(OldIrql);
  }

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


/** Inicializuje modul pro blokovani procesoru.
 *
 * Rutina pouze zjisti pocet aktivnich logickych procesoru. Cely modul
 * pak predpoklada, ze se toto cislo dale nebude menit.
 *
 * @return Funkce vraci hodnotu NTSTATUS indikujici uspech ci neuspech
 * operace.
 */
NTSTATUS SyncInit(VOID)
{
  NTSTATUS Status = STATUS_UNSUCCESSFUL;
  KdPrint(("sync.c: SyncInit()\n"));

  CPUCount = KeNumberProcessors;
  Status = STATUS_SUCCESS;

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


/** Provede uklid po modulu pro blokovani procesoru.
 *
 * Protoze tento modul nijak nezasahuje do datovych struktur jadra a pouziva
 * pouze dokumentovana rozhrani a behem sve inicializace zjistuje pouze pocet
 * aktivnich logickych procesoru, tato rutina nedela nic.
 */
VOID SyncFinit(VOID)
{
  KdPrint(("sync.c: SyncFinit()\n"));

  KdPrint(("sync.c: SyncFinit(-)\n"));
  return;
}
