text: /*++
  
  Copyright (c) 2000  Microsoft Corporation
  
  Module Name:
  
      vector.c
  
  Abstract:
  
      This module is how external drivers add / remove hooks to deal with
      ACPI Gpe Events
  
  Author:
  
      Stephane Plante
  
  Environment:
  
      NT Kernel Mode Driver Only
  
  --*/
  
  #include "pch.h"
  
  //
  // Table for installed GPE handlers
  //
  PGPE_VECTOR_ENTRY   GpeVectorTable      = NULL;
  UCHAR               GpeVectorFree       = 0;
  ULONG               GpeVectorTableSize  = 0;
  
  
  VOID
  ACPIVectorBuildVectorMasks(
      VOID
      )
  /*++
  
  Routine Description:
  
      This routine is called to walk the GPE Vector Table and properly
      enable all the events that we think should be enabled.
  
      This routine is typically called after we have loaded a new set
      of tables or we have unloaded an existing set of tables.
  
      We have to call this routine because at the start of the operation,
      we clear out all the knowledge of these additional vectors.
  
      This routine is called with GPEs disabled and the GPE Table locked
      acquired.
  
  Arguments:
  
      None
  
  Return Value:
  
      None
  
  --*/
  {
      BOOLEAN installed;
      ULONG   i;
      ULONG   mode;
  
      //
      // Walk all the elements in the table
      //
      for (i = 0; i < GpeVectorTableSize; i++) {
  
          //
          // Does this entry point to a vector object?
          //
          if (GpeVectorTable[i].GpeVectorObject == NULL) {
  
              continue;
  
          }
  
          if (GpeVectorTable[i].GpeVectorObject->Mode == LevelSensitive) {
  
              mode = ACPI_GPE_LEVEL_INSTALL;
  
          } else {
  
              mode = ACPI_GPE_EDGE_INSTALL;
  
          }
  
          //
          // Install the GPE into bit-maps.  This validates the GPE number.
          //
          installed = ACPIGpeInstallRemoveIndex(
              GpeVectorTable[i].GpeVectorObject->Vector,
              mode,
              ACPI_GPE_HANDLER,
              &(GpeVectorTable[i].GpeVectorObject->HasControlMethod)
              );
          if (!installed) {
  
              ACPIPrint( (
                  ACPI_PRINT_CRITICAL,
                  "ACPIVectorBuildVectorMasks: Could not reenable Vector Object %d\n",
                  i
                  ) );
  
          }
  
      }
  
  }
  
  NTSTATUS
  ACPIVectorClear(
      PDEVICE_OBJECT      AcpiDeviceObject,
      PVOID               GpeVectorObject
      )
  /*++
  
  Routine Description:
  
      Clear the GPE_STS (status) bit associated with a vector object
  
  Arguments:
  
      AcpiDeviceObject    - The ACPI device object
      GpeVectorObject     - Pointer to the vector object returned by
                            ACPIGpeConnectVector
  
  Return Value
  
      Returns status
  
  --*/
  {
      PGPE_VECTOR_OBJECT  localVectorObject = GpeVectorObject;
      ULONG               gpeIndex;
      ULONG               bitOffset;
      ULONG               i;
  
      ASSERT( localVectorObject );
  
      //
      // What is the GPE index for this vector?
      //
      gpeIndex = localVectorObject->Vector;
  
      //
      // Calculate the proper mask to use
      //
      bitOffset = gpeIndex % 8;
  
      //
      // Calculate the offset for the register
      //
      i = ACPIGpeIndexToGpeRegister (gpeIndex);
  
      //
      // Clear the register
      //
      ACPIWriteGpeStatusRegister (i, (UCHAR) (1 << bitOffset));
      return STATUS_SUCCESS;
  }
  
  NTSTATUS
  ACPIVectorConnect(
      PDEVICE_OBJECT          AcpiDeviceObject,
      ULONG                   GpeVector,
      KINTERRUPT_MODE         GpeMode,
      BOOLEAN                 Sharable,
      PGPE_SERVICE_ROUTINE    ServiceRoutine,
      PVOID                   ServiceContext,
      PVOID                   *GpeVectorObject
      )
  /*++
  
  Routine Description:
  
      Connects a handler to a general-purpose event.
  
  Arguments:
  
      AcpiDeviceObject    - The ACPI object
      GpeVector           - The event number to connect to
      GpeMode             - Level or edge interrupt
      Sharable            - Can this level be shared?
      ServiceRoutine      - Address of the handler
      ServiceContext      - Context object to be passed to the handler
      *GpeVectorObject    - Pointer to where the vector object is returned
  
  Return Value
  
      Returns status
  
  --*/
  {
      BOOLEAN                 installed;
      KIRQL                   oldIrql;
      NTSTATUS                status;
      PGPE_VECTOR_OBJECT      localVectorObject;
      ULONG                   mode;
  
      ASSERT( GpeVectorObject );
  
      ACPIPrint( (
          ACPI_PRINT_INFO,
          "ACPIVectorConnect: Attach GPE handler\n"
          ) );
  
      status = STATUS_SUCCESS;
      *GpeVectorObject = NULL;
  
      //
      // Do GPEs exist on this machine?
      //
      if (AcpiInformation->GpeSize == 0) {
  
          return STATUS_UNSUCCESSFUL;
  
      }
  
      //
      // Validate the vector number (GPE number)
      //
      if ( !ACPIGpeValidIndex(GpeVector) ) {
  
          return STATUS_INVALID_PARAMETER_2;
  
      }
  
      //
      // Create and initialize a vector object
      //
      localVectorObject = ExAllocatePoolWithTag (
          NonPagedPool,
          sizeof(GPE_VECTOR_OBJECT),
          ACPI_SHARED_GPE_POOLTAG
          );
      if (localVectorObject == NULL) {
  
          return STATUS_INSUFFICIENT_RESOURCES;
  
      }
      RtlZeroMemory( localVectorObject, sizeof(GPE_VECTOR_OBJECT) );
      localVectorObject->Vector   = GpeVector;
      localVectorObject->Handler  = ServiceRoutine;
      localVectorObject->Context  = ServiceContext;
      localVectorObject->Mode     = GpeMode;
  
      //
      // We don't implement anything other than sharable...
      //
      localVectorObject->Sharable = Sharable;
  
      //
      // Level/Edge mode for ACPIGpeInstallRemoveIndex()
      //
      if (GpeMode == LevelSensitive) {
  
          mode = ACPI_GPE_LEVEL_INSTALL;
  
      } else {
  
          mode = ACPI_GPE_EDGE_INSTALL;
  
      }
  
      //
      // Lock the global tables
      //
      KeAcquireSpinLock (&GpeTableLock, &oldIrql);
  
      //
      // Disable GPEs while we are installing the handler
      //
      ACPIGpeEnableDisableEvents(FALSE);
  
      //
      // Install the GPE into bit-maps.  This validates the GPE number.
      //
      installed = ACPIGpeInstallRemoveIndex(
          GpeVector,
          mode,
          ACPI_GPE_HANDLER,
          &(localVectorObject->HasControlMethod)
          );
      if (!installed) {
  
          status = STATUS_UNSUCCESSFUL;
  
      } else {
  
          //
          // Install GPE handler into vector table.
          //
          installed = ACPIVectorInstall(
              GpeVector,
              localVectorObject
              );
          if (!installed) {
  
              ACPIGpeInstallRemoveIndex(
                  GpeVector,
                  ACPI_GPE_REMOVE,
                  0,
                  &localVectorObject->HasControlMethod
                  );
              status = STATUS_UNSUCCESSFUL;
  
          }
  
      }
  
      if (!NT_SUCCESS(status)) {
  
          ExFreePool (localVectorObject);
  
      } else {
  
          *GpeVectorObject = localVectorObject;
  
      }
  
      //
      // Update hardware to match us
      //
      ACPIGpeEnableDisableEvents (TRUE);
  
      //
      // Unlock tables and return status
      //
      KeReleaseSpinLock (&GpeTableLock, oldIrql);
      return status;
  }
  
  NTSTATUS
  ACPIVectorDisable(
      PDEVICE_OBJECT      AcpiDeviceObject,
      PVOID               GpeVectorObject
      )
  /*++
  
  Routine Description:
  
      Temporarily disable a GPE that is already attached to a handler.
  
  Arguments:
  
      AcpiDeviceObject    - The ACPI device object
      GpeVectorObject     - Pointer to the vector object returned by ACPIGpeConnectVector
  
  Return Value
  
      Returns status
  
  --*/
  {
      PGPE_VECTOR_OBJECT  localVectorObject = GpeVectorObject;
      KIRQL               oldIrql;
      ULONG               gpeIndex;
      ULONG               bit;
      ULONG               i;
  
      //
      // The GPE index was validated when the handler was attached
      //
      gpeIndex = localVectorObject->Vector;
  
      //
      // Calculate the mask and index
      //
      bit = (1 << (gpeIndex % 8));
      i = ACPIGpeIndexToGpeRegister (gpeIndex);
  
      //
      // Lock the global tables
      //
      KeAcquireSpinLock (&GpeTableLock, &oldIrql);
  
      //
      // Disable GPEs while we are fussing with the enable bits
      //
      ACPIGpeEnableDisableEvents(FALSE);
  
      //
      // Remove the GPE from the enable bit-maps.  This event will be completely disabled,
      // but the handler has not been removed.
      //
      GpeEnable [i]      &= ~bit;
      GpeCurEnable [i]   &= ~bit;
      ASSERT(!(GpeWakeEnable[i] & bit));
  
      //
      // Update hardware to match us
      //
      ACPIGpeEnableDisableEvents (TRUE);
  
      //
      // Unlock tables and return status
      //
      KeReleaseSpinLock (&GpeTableLock, oldIrql);
      ACPIPrint( (
          ACPI_PRINT_RESOURCES_2,
          "ACPIVectorDisable: GPE %x disabled\n",
          gpeIndex
          ) );
      return STATUS_SUCCESS;
  }
  
  NTSTATUS
  ACPIVectorDisconnect(
      PVOID                   GpeVectorObject
      )
  /*++
  
  Routine Description:
  
      Disconnects a handler from a general-purpose event.
  
  Arguments:
  
      GpeVectorObject - Pointer to the vector object returned by
                        ACPIGpeConnectVector
  
  Return Value
  
      Returns status
  
  --*/
  {
      BOOLEAN                 removed;
      KIRQL                   oldIrql;
      NTSTATUS                status          = STATUS_SUCCESS;
      PGPE_VECTOR_OBJECT      gpeVectorObj    = GpeVectorObject;
  
      ACPIPrint( (
          ACPI_PRINT_INFO,
          "ACPIVectorDisconnect: Detach GPE handler\n"
          ) );
  
      //
      // Lock the global tables
      //
      KeAcquireSpinLock (&GpeTableLock, &oldIrql);
  
      //
      // Disable GPEs while we are removing the handler
      //
      ACPIGpeEnableDisableEvents (FALSE);
  
      //
      // Remove GPE handler From vector table.
      //
      ACPIVectorRemove(gpeVectorObj->Vector);
  
      //
      // Remove the GPE from the bit-maps.  Fall back to using control method
      // if available.
      //
      removed = ACPIGpeInstallRemoveIndex(
          gpeVectorObj->Vector,
          ACPI_GPE_REMOVE,
          0,
          &(gpeVectorObj->HasControlMethod)
          );
      if (!removed) {
  
          status = STATUS_UNSUCCESSFUL;
  
      }
  
      //
      // Update hardware to match us
      //
      ACPIGpeEnableDisableEvents(TRUE);
  
      //
      // Unlock tables and return status
      //
      KeReleaseSpinLock (&GpeTableLock, oldIrql);
  
      //
      // Free the vector object, it's purpose is done.
      //
      if (status == STATUS_SUCCESS) {
  
          ExFreePool (GpeVectorObject);
  
      }
      return status;
  }
  
  NTSTATUS
  ACPIVectorEnable(
      PDEVICE_OBJECT      AcpiDeviceObject,
      PVOID               GpeVectorObject
      )
  /*++
  
  Routine Description:
  
      Enable (a previously disabled) GPE that is already attached to a handler.
  
  Arguments:
  
      AcpiDeviceObject    - The ACPI device object
      GpeVectorObject     - Pointer to the vector object returned by ACPIGpeConnectVector
  
  Return Value
  
      Returns status
  
  --*/
  {
      KIRQL               oldIrql;
      PGPE_VECTOR_OBJECT  localVectorObject = GpeVectorObject;
      ULONG               bit;
      ULONG               gpeIndex;
      ULONG               gpeRegister;
  
      //
      // The GPE index was validated when the handler was attached
      //
      gpeIndex = localVectorObject->Vector;
      bit = (1 << (gpeIndex % 8));
      gpeRegister = ACPIGpeIndexToGpeRegister (gpeIndex);
  
      //
      // Lock the global tables
      //
      KeAcquireSpinLock (&GpeTableLock, &oldIrql);
  
      //
      // Disable GPEs while we are fussing with the enable bits
      //
      ACPIGpeEnableDisableEvents (FALSE);
  
      //
      // Enable the GPE in the bit maps.
      //
      GpeEnable [gpeRegister]      |= bit;
      GpeCurEnable [gpeRegister]   |= bit;
  
      //
      // Update hardware to match us
      //
      ACPIGpeEnableDisableEvents (TRUE);
  
      //
      // Unlock tables and return status
      //
      KeReleaseSpinLock (&GpeTableLock, oldIrql);
      ACPIPrint( (
          ACPI_PRINT_RESOURCES_2,
          "ACPIVectorEnable: GPE %x enabled\n",
          gpeIndex
          ) );
      return STATUS_SUCCESS;
  }
  
  VOID
  ACPIVectorFreeEntry (
      ULONG       TableIndex
      )
  /*++
  
  Routine Description:
  
      Free a GPE vector table entry.
      NOTE: Should be called with the global GpeVectorTable locked.
  
  Arguments:
  
      TableIndex  - Index into GPE vector table of entry to be freed
  
  Return Value:
  
      NONE
  
  --*/
  {
      //
      // Put onto free list
      //
      GpeVectorTable[TableIndex].Next = GpeVectorFree;
      GpeVectorFree = (UCHAR) TableIndex;
  }
  
  BOOLEAN
  ACPIVectorGetEntry (
      PULONG              TableIndex
      )
  /*++
  
  Routine Description:
  
      Get a new vector entry from the GPE vector table.
      NOTE: Should be called with the global GpeVectorTable locked.
  
  Arguments:
  
      TableIndex      - Pointer to where the vector table index of the entry is returned
  
  Return Value:
  
      TRUE - Success
      FALSE - Failure
  
  --*/
  {
      PGPE_VECTOR_ENTRY   Vector;
      ULONG               i, j;
  
  #define NEW_TABLE_ENTRIES       4
  
      if (!GpeVectorFree) {
  
          //
          // No free entries on vector table, make some
          //
          i = GpeVectorTableSize;
          Vector = ExAllocatePoolWithTag (
              NonPagedPool,
              sizeof (GPE_VECTOR_ENTRY) * (i + NEW_TABLE_ENTRIES),
              ACPI_SHARED_GPE_POOLTAG
              );
          if (Vector == NULL) {
  
              return FALSE;
  
          }
  
          //
          // Make sure that its in a known state
          //
          RtlZeroMemory(
              Vector,
              (sizeof(GPE_VECTOR_ENTRY) * (i + NEW_TABLE_ENTRIES) )
              );
  
          //
          // Copy old table to new
          //
          if (GpeVectorTable) {
  
              RtlCopyMemory(
                  Vector,
                  GpeVectorTable,
                  sizeof (GPE_VECTOR_ENTRY) * i
                  );
              ExFreePool (GpeVectorTable);
  
          }
  
          GpeVectorTableSize += NEW_TABLE_ENTRIES;
          GpeVectorTable = Vector;
  
          //
          // Link new entries
          //
          for (j=0; j < NEW_TABLE_ENTRIES; j++) {
  
              GpeVectorTable[i+j].Next = (UCHAR) (i+j+1);
  
          }
  
          //
          // The last entry in the list gets pointed to 0, because we then
          // want to grow this list again
          //
          GpeVectorTable[i+j-1].Next = 0;
  
          //
          // The next free vector the head of the list that we just allocated
          //
          GpeVectorFree = (UCHAR) i;
  
      }
  
      *TableIndex = GpeVectorFree;
      Vector = &GpeVectorTable[GpeVectorFree];
      GpeVectorFree = Vector->Next;
      return TRUE;
  }
  
  BOOLEAN
  ACPIVectorInstall(
      ULONG               GpeIndex,
      PGPE_VECTOR_OBJECT  GpeVectorObject
      )
  /*++
  
  Routine Description:
  
      Install a GPE handler into the Map and Vector tables
      NOTE: Should be called with the global GpeVectorTable locked, and GPEs disabled
  
  Arguments:
  
  
  Return Value:
  
      TRUE    - Success
      FALSE   - Failure
  
  --*/
  {
      ULONG               byteIndex;
      ULONG               tableIndex;
  
      //
      // Get an entry in the global vector table
      //
      if (ACPIVectorGetEntry (&tableIndex)) {
  
          //
          // Install the entry into the map table
          //
          byteIndex = ACPIGpeIndexToByteIndex (GpeIndex);
          GpeMap [byteIndex] = (UCHAR) tableIndex;
  
          //
          // Install the vector object in the vector table entry
          //
          GpeVectorTable [tableIndex].GpeVectorObject = GpeVectorObject;
          return TRUE;
  
      }
  
      return FALSE;
  }
  
  BOOLEAN
  ACPIVectorRemove(
      ULONG       GpeIndex
      )
  /*++
  
  Routine Description:
  
      Remove a GPE handler from the Map and Vector tables
      NOTE: Should be called with the global GpeVectorTable locked,
      and GPEs disabled
  
  Arguments:
  
  
  Return Value:
  
      TRUE    - Success
      FALSE   - Failure
  
  --*/
  {
      ULONG               byteIndex;
      ULONG               tableIndex;
  
      //
      // Get the table index from the map table
      //
      byteIndex = ACPIGpeIndexToByteIndex (GpeIndex);
      tableIndex = GpeMap [byteIndex];
  
      //
      // Bounds check
      //
      if (tableIndex >= GpeVectorTableSize) {
  
          return FALSE;
  
      }
  
      //
      // Remember that we don't have this GpeVectorObject anymore
      //
      GpeVectorTable[tableIndex].GpeVectorObject = NULL;
  
      //
      // Free the slot in the master vector table
      //
      ACPIVectorFreeEntry (tableIndex);
      return TRUE;
  }
  
  