Files
2026-10-27 09:28:31 +01:00

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);