303 lines
12 KiB
C
303 lines
12 KiB
C
#include <ntlite/ddk/module.h>
|
|
// Intel High Definition Audio PCI attachment and controller lifetime management.
|
|
|
|
#include "hda.h"
|
|
|
|
#include <string.h>
|
|
|
|
static DRIVER_OBJECT gHdaDriver;
|
|
static const IO_DEVICE_ID gHdaIds[] = {
|
|
{ IoBusPci, { "PCI\\VEN_8086&DEV_2668", sizeof("PCI\\VEN_8086&DEV_2668") - 1u } },
|
|
{ IoBusPci, { "PCI\\VEN_15AD&DEV_1977", sizeof("PCI\\VEN_15AD&DEV_1977") - 1u } },
|
|
{ IoBusPci, { "PCI\\CC_040300", sizeof("PCI\\CC_040300") - 1u } },
|
|
};
|
|
|
|
static const IO_DRIVER_ID_TABLE gHdaIdTable = {
|
|
gHdaIds,
|
|
sizeof(gHdaIds) / sizeof(gHdaIds[0]),
|
|
};
|
|
|
|
// Handles the interrupt for its owning HDA controller.
|
|
static void HdaIsr(void* context) {
|
|
HDA_CONTROLLER* controller = (HDA_CONTROLLER*)context;
|
|
if (controller && controller->Magic == HDA_CONTROLLER_MAGIC)
|
|
HdaHandleInterrupt(controller);
|
|
}
|
|
|
|
|
|
|
|
// Maps BAR0 and validates the stream-descriptor aperture described by GCAP.
|
|
static KSTATUS HdaMapRegisters(HDA_CONTROLLER* controller) {
|
|
PCI_BAR bar;
|
|
KSTATUS status = PciGetBar(controller->Pdo, 0, &bar);
|
|
if (!KSUCCESS(status))
|
|
return status;
|
|
if (bar.Type != PciBarMemory32 && bar.Type != PciBarMemory64)
|
|
return NTLSTATUS_NOT_SUPPORTED;
|
|
|
|
status = PciMapBar(controller->Pdo,
|
|
0,
|
|
PAGE_PRESENT | PAGE_WRITE | PAGE_CACHE_UC,
|
|
(void**)&controller->Registers,
|
|
&controller->RegisterBytes);
|
|
if (!KSUCCESS(status))
|
|
return status;
|
|
if (controller->RegisterBytes < HDA_REG_STREAM_BASE)
|
|
return NTLSTATUS_BUFFER_TOO_SMALL;
|
|
|
|
controller->Gcap = HdaRead16(controller, HDA_REG_GCAP);
|
|
controller->InputStreamCount =
|
|
(uint8_t)((controller->Gcap & HDA_GCAP_ISS_MASK) >> HDA_GCAP_ISS_SHIFT);
|
|
controller->OutputStreamCount =
|
|
(uint8_t)((controller->Gcap & HDA_GCAP_OSS_MASK) >> HDA_GCAP_OSS_SHIFT);
|
|
controller->BidirectionalStreamCount =
|
|
(uint8_t)((controller->Gcap & HDA_GCAP_BSS_MASK) >> HDA_GCAP_BSS_SHIFT);
|
|
controller->TotalStreamCount =
|
|
(uint8_t)(controller->InputStreamCount +
|
|
controller->OutputStreamCount +
|
|
controller->BidirectionalStreamCount);
|
|
if ((controller->OutputStreamCount == 0 &&
|
|
controller->BidirectionalStreamCount == 0) ||
|
|
controller->TotalStreamCount == 0 ||
|
|
controller->TotalStreamCount > 30u)
|
|
return NTLSTATUS_NOT_SUPPORTED;
|
|
|
|
uint64_t requiredBytes = HDA_REG_STREAM_BASE +
|
|
(uint64_t)controller->TotalStreamCount *
|
|
HDA_REG_STREAM_STRIDE;
|
|
if (controller->RegisterBytes < requiredBytes)
|
|
return NTLSTATUS_BUFFER_TOO_SMALL;
|
|
|
|
// Dedicated output descriptors precede bidirectional descriptors. Use the
|
|
// first bidirectional descriptor when no output-only descriptor exists.
|
|
uint8_t outputDescriptorBase = controller->InputStreamCount;
|
|
uint8_t bidirectionalDescriptorBase =
|
|
(uint8_t)(controller->InputStreamCount + controller->OutputStreamCount);
|
|
controller->Output.DescriptorIndex = controller->OutputStreamCount
|
|
? outputDescriptorBase
|
|
: bidirectionalDescriptorBase;
|
|
controller->Output.StreamTag = HDA_OUTPUT_STREAM_TAG;
|
|
return NTLSTATUS_SUCCESS;
|
|
}
|
|
|
|
// Connects the controller interrupt using the PCI bus routing policy.
|
|
static KSTATUS HdaConfigureInterrupt(HDA_CONTROLLER* controller) {
|
|
controller->InterruptRequest.Routine = HdaIsr;
|
|
controller->InterruptRequest.Context = controller;
|
|
controller->InterruptRequest.Affinity = 0;
|
|
controller->InterruptRequest.Interrupt = (KE_INTERRUPT*)0;
|
|
return PciConnectInterrupts(controller->Pdo, &controller->InterruptRequest,
|
|
1u, &controller->UsingMsi);
|
|
}
|
|
|
|
// Waits until queued or active interrupt work no longer references the controller.
|
|
static KSTATUS HdaWaitForControllerInterruptWorkIdle(HDA_CONTROLLER* controller) {
|
|
if (!controller)
|
|
return NTLSTATUS_INVALID_PARAMETER;
|
|
for (uint32_t elapsed = 0; elapsed < 100000u; elapsed += 10u) {
|
|
if (controller->IsrActive == 0 &&
|
|
controller->Dpc.Inserted == 0 && controller->DpcActive == 0)
|
|
return NTLSTATUS_SUCCESS;
|
|
KeStallExecution(10);
|
|
}
|
|
return NTLSTATUS_TIMEOUT;
|
|
}
|
|
|
|
// Starts one PCI HDA controller and leaves endpoint registration to codec discovery.
|
|
static KSTATUS HdaStartDevice(HDA_CONTROLLER* controller) {
|
|
if (!controller || controller->Magic != HDA_CONTROLLER_MAGIC ||
|
|
!controller->Pdo || !controller->Fdo)
|
|
return NTLSTATUS_INVALID_PARAMETER;
|
|
if (controller->Started)
|
|
return NTLSTATUS_SUCCESS;
|
|
|
|
KSTATUS status = PciGetDeviceInfo(controller->Pdo, &controller->PciInfo);
|
|
if (!KSUCCESS(status))
|
|
return status;
|
|
if (controller->PciInfo.ClassCode != 0x04u ||
|
|
controller->PciInfo.Subclass != 0x03u ||
|
|
controller->PciInfo.ProgIf != 0x00u)
|
|
return NTLSTATUS_NOT_SUPPORTED;
|
|
|
|
status = PciEnableDevice(controller->Pdo,
|
|
PCI_ENABLE_MEMORY |
|
|
PCI_ENABLE_BUSMASTER |
|
|
PCI_DISABLE_INTX);
|
|
if (!KSUCCESS(status))
|
|
return status;
|
|
|
|
status = HdaMapRegisters(controller);
|
|
if (!KSUCCESS(status))
|
|
return status;
|
|
|
|
|
|
status = HdaAllocateDma(controller);
|
|
if (!KSUCCESS(status))
|
|
return status;
|
|
KeInitializeDpc(&controller->Dpc, HdaDpcRoutine, controller);
|
|
|
|
status = HdaStartRecoveryWorker(controller);
|
|
if (!KSUCCESS(status)) {
|
|
HdaReleaseDma(controller);
|
|
return status;
|
|
}
|
|
|
|
status = HdaInitializeController(controller);
|
|
if (!KSUCCESS(status)) {
|
|
HdaStopRecoveryWorker(controller);
|
|
HdaQuiesceController(controller);
|
|
HdaReleaseDma(controller);
|
|
return status;
|
|
}
|
|
|
|
status = HdaConfigureInterrupt(controller);
|
|
if (!KSUCCESS(status)) {
|
|
HdaStopRecoveryWorker(controller);
|
|
HdaQuiesceController(controller);
|
|
HdaReleaseDma(controller);
|
|
return status;
|
|
}
|
|
|
|
controller->Started = true;
|
|
controller->Fdo->PnpState = DevicePnpStarted;
|
|
|
|
status = HdaCodecInitialize(controller);
|
|
if (!KSUCCESS(status) && status != NTLSTATUS_NOT_SUPPORTED &&
|
|
status != NTLSTATUS_NOT_FOUND) {
|
|
controller->Started = false;
|
|
controller->Fdo->PnpState = DevicePnpNotStarted;
|
|
if (controller->InterruptRequest.Interrupt)
|
|
PciDisconnectInterrupts(controller->Pdo, &controller->InterruptRequest, 1u);
|
|
HdaStopRecoveryWorker(controller);
|
|
HdaQuiesceController(controller);
|
|
HdaReleaseDma(controller);
|
|
return status;
|
|
}
|
|
if (!KSUCCESS(status)) {
|
|
DbgPrint(
|
|
"HDA: no analog endpoint registered status=0x%x\n",
|
|
status);
|
|
}
|
|
|
|
HdaArmInterrupts(controller);
|
|
HdaHandleInterrupt(controller);
|
|
|
|
return NTLSTATUS_SUCCESS;
|
|
}
|
|
|
|
// Handles PnP lifetime transitions for one HDA function device object.
|
|
static KSTATUS HdaDispatchPnp(DEVICE_OBJECT* device, IRP* irp) {
|
|
if (!device || !irp || !device->DeviceExtension)
|
|
return NTLSTATUS_INVALID_PARAMETER;
|
|
HDA_CONTROLLER* controller = (HDA_CONTROLLER*)device->DeviceExtension;
|
|
if (controller->Magic != HDA_CONTROLLER_MAGIC)
|
|
return NTLSTATUS_INVALID_PARAMETER;
|
|
|
|
IO_STACK_LOCATION* stack = IoGetCurrentIrpStackLocation(irp);
|
|
if (!stack || stack->MajorFunction != IRP_MJ_PNP)
|
|
return NTLSTATUS_INVALID_PARAMETER;
|
|
|
|
switch ((IO_PNP_MINOR_FUNCTION)stack->MinorFunction) {
|
|
case IRP_MN_START_DEVICE:
|
|
return HdaStartDevice(controller);
|
|
|
|
case IRP_MN_QUERY_REMOVE_DEVICE:
|
|
case IRP_MN_CANCEL_REMOVE_DEVICE:
|
|
return NTLSTATUS_SUCCESS;
|
|
|
|
case IRP_MN_SURPRISE_REMOVAL:
|
|
controller->Started = false;
|
|
controller->SurpriseRemoved = true;
|
|
if (controller->InterruptRequest.Interrupt)
|
|
PciDisconnectInterrupts(controller->Pdo, &controller->InterruptRequest, 1u);
|
|
HdaStopRecoveryWorker(controller);
|
|
KSTATUS surpriseDpcStatus =
|
|
HdaWaitForControllerInterruptWorkIdle(controller);
|
|
if (!KSUCCESS(surpriseDpcStatus)) {
|
|
KePanic(KE_PANIC_CODE_DRIVER,
|
|
"HDA: interrupt work did not quiesce during surprise removal status=0x%x\n",
|
|
surpriseDpcStatus);
|
|
}
|
|
HdaCodecStop(controller, IoAudioSurpriseRemoval);
|
|
HdaCodecDestroy(controller);
|
|
device->PnpState = DevicePnpSurpriseRemoved;
|
|
return NTLSTATUS_SUCCESS;
|
|
|
|
case IRP_MN_REMOVE_DEVICE:
|
|
if (!controller->SurpriseRemoved && controller->Registers)
|
|
HdaWrite32(controller, HDA_REG_INTCTL, 0);
|
|
controller->Started = false;
|
|
if (controller->InterruptRequest.Interrupt)
|
|
PciDisconnectInterrupts(controller->Pdo, &controller->InterruptRequest, 1u);
|
|
HdaStopRecoveryWorker(controller);
|
|
KSTATUS dpcStatus = HdaWaitForControllerInterruptWorkIdle(controller);
|
|
if (!KSUCCESS(dpcStatus)) {
|
|
KePanic(KE_PANIC_CODE_DRIVER,
|
|
"HDA: interrupt work did not quiesce during removal status=0x%x\n",
|
|
dpcStatus);
|
|
}
|
|
HdaCodecStop(controller,
|
|
controller->SurpriseRemoved
|
|
? IoAudioSurpriseRemoval
|
|
: IoAudioOrderlyRemoval);
|
|
HdaCodecDestroy(controller);
|
|
if (!controller->SurpriseRemoved)
|
|
HdaQuiesceController(controller);
|
|
controller->Removed = true;
|
|
HdaReleaseDma(controller);
|
|
if (!controller->SurpriseRemoved)
|
|
(void)PciEnableDevice(controller->Pdo, PCI_DISABLE_INTX);
|
|
device->PnpState = DevicePnpDeleted;
|
|
IoDetachDevice(device);
|
|
IoDeleteDevice(device);
|
|
return NTLSTATUS_SUCCESS;
|
|
|
|
default:
|
|
return IoForwardIrpToLower(device, irp);
|
|
}
|
|
}
|
|
|
|
// Attaches one HDA function device object to a matching PCI PDO.
|
|
static KSTATUS HdaAddDevice(DRIVER_OBJECT* driver, DEVICE_OBJECT* physicalDeviceObject) {
|
|
if (!driver || !physicalDeviceObject ||
|
|
physicalDeviceObject->Type != DeviceObjectPhysical)
|
|
return NTLSTATUS_INVALID_PARAMETER;
|
|
|
|
DEVICE_OBJECT* fdo = (DEVICE_OBJECT*)0;
|
|
KSTATUS status = IoCreateDevice(driver, sizeof(HDA_CONTROLLER), &fdo);
|
|
if (!KSUCCESS(status))
|
|
return status;
|
|
(void)IoSetDeviceName(fdo, SYS_SV_LITERAL("hda"));
|
|
|
|
HDA_CONTROLLER* controller = (HDA_CONTROLLER*)fdo->DeviceExtension;
|
|
memset(controller, 0, sizeof(*controller));
|
|
controller->Magic = HDA_CONTROLLER_MAGIC;
|
|
controller->Fdo = fdo;
|
|
controller->Pdo = physicalDeviceObject;
|
|
controller->StateLock = (SPINLOCK)SPINLOCK_INIT;
|
|
controller->Output.StreamTag = HDA_OUTPUT_STREAM_TAG;
|
|
|
|
controller->LowerDevice = IoAttachDeviceToDeviceStack(fdo, physicalDeviceObject);
|
|
if (!controller->LowerDevice) {
|
|
IoDeleteDevice(fdo);
|
|
return NTLSTATUS_FAILURE;
|
|
}
|
|
return NTLSTATUS_SUCCESS;
|
|
}
|
|
|
|
// Registers the Intel HDA transport driver with the I/O manager.
|
|
static KSTATUS DriverInitialize(NTL_MODULE* module) {
|
|
(void)module;
|
|
|
|
|
|
memset(&gHdaDriver, 0, sizeof(gHdaDriver));
|
|
IoInitializeDriverObject(&gHdaDriver, SYS_SV_LITERAL("hda"));
|
|
gHdaDriver.IdTable = &gHdaIdTable;
|
|
gHdaDriver.DriverAddDevice = HdaAddDevice;
|
|
gHdaDriver.MajorFunction[IRP_MJ_PNP] = HdaDispatchPnp;
|
|
return NTLSTATUS_SUCCESS;
|
|
}
|
|
|
|
static DRIVER_OBJECT* const gModuleIoDrivers[] = { &gHdaDriver };
|
|
NTL_DECLARE_IO_MODULE(DriverInitialize, gModuleIoDrivers);
|