#include // Intel High Definition Audio PCI attachment and controller lifetime management. #include "hda.h" #include 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);