/* * PROJECT: ReactOS Kernel * COPYRIGHT: GPL - See COPYING in the top level directory * FILE: ntoskrnl/io/pnpmgr/pnpreport.c * PURPOSE: Device Changes Reporting Functions * PROGRAMMERS: Cameron Gutman (cameron.gutman@reactos.org) * Pierre Schweitzer */ /* INCLUDES ******************************************************************/ #include #define NDEBUG #include /* TYPES *******************************************************************/ typedef struct _INTERNAL_WORK_QUEUE_ITEM { WORK_QUEUE_ITEM WorkItem; PDEVICE_OBJECT PhysicalDeviceObject; PDEVICE_CHANGE_COMPLETE_CALLBACK Callback; PVOID Context; PTARGET_DEVICE_CUSTOM_NOTIFICATION NotificationStructure; } INTERNAL_WORK_QUEUE_ITEM, *PINTERNAL_WORK_QUEUE_ITEM; NTSTATUS NTAPI IopCreateDeviceKeyPath(IN PCUNICODE_STRING RegistryPath, IN ULONG CreateOptions, OUT PHANDLE Handle); NTSTATUS IopSetDeviceInstanceData(HANDLE InstanceKey, PDEVICE_NODE DeviceNode); NTSTATUS IopActionInterrogateDeviceStack(PDEVICE_NODE DeviceNode, PVOID Context); NTSTATUS PpSetCustomTargetEvent(IN PDEVICE_OBJECT DeviceObject, IN OUT PKEVENT SyncEvent OPTIONAL, IN OUT PNTSTATUS SyncStatus OPTIONAL, IN PDEVICE_CHANGE_COMPLETE_CALLBACK Callback OPTIONAL, IN PVOID Context OPTIONAL, IN PTARGET_DEVICE_CUSTOM_NOTIFICATION NotificationStructure); /* PRIVATE FUNCTIONS *********************************************************/ PWCHAR IopGetInterfaceTypeString(INTERFACE_TYPE IfType) { switch (IfType) { case Internal: return L"Internal"; case Isa: return L"Isa"; case Eisa: return L"Eisa"; case MicroChannel: return L"MicroChannel"; case TurboChannel: return L"TurboChannel"; case PCIBus: return L"PCIBus"; case VMEBus: return L"VMEBus"; case NuBus: return L"NuBus"; case PCMCIABus: return L"PCMCIABus"; case CBus: return L"CBus"; case MPIBus: return L"MPIBus"; case MPSABus: return L"MPSABus"; case ProcessorInternal: return L"ProcessorInternal"; case PNPISABus: return L"PNPISABus"; case PNPBus: return L"PNPBus"; case Vmcs: return L"Vmcs"; default: DPRINT1("Invalid bus type: %d\n", IfType); return NULL; } } VOID NTAPI IopReportTargetDeviceChangeAsyncWorker(PVOID Context) { PINTERNAL_WORK_QUEUE_ITEM Item; Item = (PINTERNAL_WORK_QUEUE_ITEM)Context; PpSetCustomTargetEvent(Item->PhysicalDeviceObject, NULL, NULL, Item->Callback, Item->Context, Item->NotificationStructure); ObDereferenceObject(Item->PhysicalDeviceObject); ExFreePoolWithTag(Context, ' pP'); } NTSTATUS PpSetCustomTargetEvent(IN PDEVICE_OBJECT DeviceObject, IN OUT PKEVENT SyncEvent OPTIONAL, IN OUT PNTSTATUS SyncStatus OPTIONAL, IN PDEVICE_CHANGE_COMPLETE_CALLBACK Callback OPTIONAL, IN PVOID Context OPTIONAL, IN PTARGET_DEVICE_CUSTOM_NOTIFICATION NotificationStructure) { ASSERT(NotificationStructure != NULL); ASSERT(DeviceObject != NULL); if (SyncEvent) { ASSERT(SyncStatus); *SyncStatus = STATUS_PENDING; } /* That call is totally wrong but notifications handler must be fixed first */ IopNotifyPlugPlayNotification(DeviceObject, EventCategoryTargetDeviceChange, &GUID_PNP_CUSTOM_NOTIFICATION, NotificationStructure, NULL); if (SyncEvent) { KeSetEvent(SyncEvent, IO_NO_INCREMENT, FALSE); *SyncStatus = STATUS_SUCCESS; } return STATUS_SUCCESS; } /* PUBLIC FUNCTIONS **********************************************************/ /* * @implemented */ NTSTATUS NTAPI IoReportDetectedDevice(IN PDRIVER_OBJECT DriverObject, IN INTERFACE_TYPE LegacyBusType, IN ULONG BusNumber, IN ULONG SlotNumber, IN PCM_RESOURCE_LIST ResourceList, IN PIO_RESOURCE_REQUIREMENTS_LIST ResourceRequirements OPTIONAL, IN BOOLEAN ResourceAssigned, IN OUT PDEVICE_OBJECT *DeviceObject OPTIONAL) { PDEVICE_NODE DeviceNode; PDEVICE_OBJECT Pdo; NTSTATUS Status; HANDLE InstanceKey; ULONG RequiredLength; UNICODE_STRING ValueName, ServiceLongName, ServiceName; WCHAR HardwareId[256]; PWCHAR IfString; ULONG IdLength; ULONG LegacyValue; DPRINT("IoReportDetectedDevice (DeviceObject %p, *DeviceObject %p)\n", DeviceObject, DeviceObject ? *DeviceObject : NULL); ServiceLongName = DriverObject->DriverExtension->ServiceKeyName; ServiceName = ServiceLongName; /* If the interface type is unknown, treat it as internal */ if (LegacyBusType == InterfaceTypeUndefined) LegacyBusType = Internal; /* Get the string equivalent of the interface type */ IfString = IopGetInterfaceTypeString(LegacyBusType); /* If NULL is returned then it's a bad type */ if (!IfString) return STATUS_INVALID_PARAMETER; /* * Drivers that have been created via a direct IoCreateDriver() call * have their ServiceKeyName set to \Driver\DriverName. We need to * strip everything up to the last path separator and keep what remains. */ if (DriverObject->Flags & DRVO_BUILTIN_DRIVER) { /* * Find the last path separator. * NOTE: Since ServiceName is not necessarily NULL-terminated, * we cannot use wcsrchr(). */ if (ServiceName.Buffer && ServiceName.Length >= sizeof(WCHAR)) { ValueName.Length = 1; ValueName.Buffer = ServiceName.Buffer + (ServiceName.Length / sizeof(WCHAR)) - 1; while ((ValueName.Buffer > ServiceName.Buffer) && (*ValueName.Buffer != L'\\')) { --ValueName.Buffer; ++ValueName.Length; } if (*ValueName.Buffer == L'\\') { ++ValueName.Buffer; --ValueName.Length; } ValueName.Length *= sizeof(WCHAR); /* Shorten the string */ ServiceName.MaximumLength -= (ServiceName.Length - ValueName.Length); ServiceName.Length = ValueName.Length; ServiceName.Buffer = ValueName.Buffer; } } /* We use the caller's PDO if they supplied one */ if (DeviceObject && *DeviceObject) { Pdo = *DeviceObject; } else { /* Create the PDO */ Status = PnpRootCreateDevice(&ServiceName, NULL, &Pdo, NULL); if (!NT_SUCCESS(Status)) { DPRINT("PnpRootCreateDevice() failed (Status 0x%08lx)\n", Status); return Status; } } /* Create the device node for the new PDO */ Status = IopCreateDeviceNode(IopRootDeviceNode, Pdo, NULL, &DeviceNode); if (!NT_SUCCESS(Status)) { DPRINT("IopCreateDeviceNode() failed (Status 0x%08lx)\n", Status); return Status; } /* We're enumerated already */ IopDeviceNodeSetFlag(DeviceNode, DNF_ENUMERATED); /* We don't call AddDevice for devices reported this way */ IopDeviceNodeSetFlag(DeviceNode, DNF_ADDED); /* We don't send IRP_MN_START_DEVICE */ IopDeviceNodeSetFlag(DeviceNode, DNF_STARTED); /* We need to get device IDs */ #if 0 IopDeviceNodeSetFlag(DeviceNode, DNF_NEED_QUERY_IDS); #endif /* This is a legacy driver for this device */ IopDeviceNodeSetFlag(DeviceNode, DNF_LEGACY_DRIVER); /* Perform a manual configuration of our device */ IopActionInterrogateDeviceStack(DeviceNode, DeviceNode->Parent); IopActionConfigureChildServices(DeviceNode, DeviceNode->Parent); /* Open a handle to the instance path key */ Status = IopCreateDeviceKeyPath(&DeviceNode->InstancePath, REG_OPTION_NON_VOLATILE, &InstanceKey); if (!NT_SUCCESS(Status)) return Status; /* Save the driver name */ RtlInitUnicodeString(&ValueName, L"Service"); Status = ZwSetValueKey(InstanceKey, &ValueName, 0, REG_SZ, ServiceLongName.Buffer, ServiceLongName.Length + sizeof(UNICODE_NULL)); if (!NT_SUCCESS(Status)) { DPRINT("Failed to write the Service name value: 0x%x\n", Status); } /* Report as non-legacy driver */ RtlInitUnicodeString(&ValueName, L"Legacy"); LegacyValue = 0; Status = ZwSetValueKey(InstanceKey, &ValueName, 0, REG_DWORD, &LegacyValue, sizeof(LegacyValue)); if (!NT_SUCCESS(Status)) { DPRINT("Failed to write the Legacy value: 0x%x\n", Status); } /* Add DETECTEDInterfaceType\DriverName */ IdLength = 0; IdLength += swprintf(&HardwareId[IdLength], L"DETECTED%ls\\%wZ", IfString, &ServiceName); IdLength++; /* Add DETECTED\DriverName */ IdLength += swprintf(&HardwareId[IdLength], L"DETECTED\\%wZ", &ServiceName); IdLength++; /* Terminate the string with another null */ HardwareId[IdLength++] = UNICODE_NULL; /* Store the value for CompatibleIDs */ RtlInitUnicodeString(&ValueName, L"CompatibleIDs"); Status = ZwSetValueKey(InstanceKey, &ValueName, 0, REG_MULTI_SZ, HardwareId, IdLength * sizeof(WCHAR)); if (!NT_SUCCESS(Status)) { DPRINT("Failed to write the compatible IDs: 0x%x\n", Status); ZwClose(InstanceKey); return Status; } /* Add a hardware ID if the driver didn't report one */ RtlInitUnicodeString(&ValueName, L"HardwareID"); if (ZwQueryValueKey(InstanceKey, &ValueName, KeyValueBasicInformation, NULL, 0, &RequiredLength) == STATUS_OBJECT_NAME_NOT_FOUND) { /* Just use our most specific compatible ID */ IdLength = 0; IdLength += swprintf(&HardwareId[IdLength], L"DETECTED%ls\\%wZ", IfString, &ServiceName); IdLength++; HardwareId[IdLength++] = UNICODE_NULL; /* Write the value to the registry */ Status = ZwSetValueKey(InstanceKey, &ValueName, 0, REG_MULTI_SZ, HardwareId, IdLength * sizeof(WCHAR)); if (!NT_SUCCESS(Status)) { DPRINT("Failed to write the hardware ID: 0x%x\n", Status); ZwClose(InstanceKey); return Status; } } /* Assign the resources to the device node */ DeviceNode->BootResources = ResourceList; DeviceNode->ResourceRequirements = ResourceRequirements; /* Set appropriate flags */ if (DeviceNode->BootResources) IopDeviceNodeSetFlag(DeviceNode, DNF_HAS_BOOT_CONFIG); if (!DeviceNode->ResourceRequirements && !DeviceNode->BootResources) IopDeviceNodeSetFlag(DeviceNode, DNF_NO_RESOURCE_REQUIRED); /* Write the resource information to the registry */ IopSetDeviceInstanceData(InstanceKey, DeviceNode); /* If the caller didn't get the resources assigned for us, do it now */ if (!ResourceAssigned) { Status = IopAssignDeviceResources(DeviceNode); /* See if we failed */ if (!NT_SUCCESS(Status)) { DPRINT("Assigning resources failed: 0x%x\n", Status); ZwClose(InstanceKey); return Status; } } /* Close the instance key handle */ ZwClose(InstanceKey); /* Register the given DO with PnP root if required */ if (DeviceObject && *DeviceObject) PnpRootRegisterDevice(*DeviceObject); /* Report the device's enumeration to umpnpmgr */ IopQueueTargetDeviceEvent(&GUID_DEVICE_ENUMERATED, &DeviceNode->InstancePath); /* Report the device's arrival to umpnpmgr */ IopQueueTargetDeviceEvent(&GUID_DEVICE_ARRIVAL, &DeviceNode->InstancePath); DPRINT("Reported device: %S (%wZ)\n", HardwareId, &DeviceNode->InstancePath); /* Return the PDO */ if (DeviceObject) *DeviceObject = Pdo; return STATUS_SUCCESS; } /* * @halfplemented */ NTSTATUS NTAPI IoReportResourceForDetection(IN PDRIVER_OBJECT DriverObject, IN PCM_RESOURCE_LIST DriverList OPTIONAL, IN ULONG DriverListSize OPTIONAL, IN PDEVICE_OBJECT DeviceObject OPTIONAL, IN PCM_RESOURCE_LIST DeviceList OPTIONAL, IN ULONG DeviceListSize OPTIONAL, OUT PBOOLEAN ConflictDetected) { PCM_RESOURCE_LIST ResourceList; NTSTATUS Status; *ConflictDetected = FALSE; if (!DriverList && !DeviceList) return STATUS_INVALID_PARAMETER; /* Find the real list */ if (!DriverList) ResourceList = DeviceList; else ResourceList = DriverList; /* Look for a resource conflict */ Status = IopDetectResourceConflict(ResourceList, FALSE, NULL); if (Status == STATUS_CONFLICTING_ADDRESSES) { /* Oh noes */ *ConflictDetected = TRUE; } else if (NT_SUCCESS(Status)) { /* Looks like we're good to go */ /* TODO: Claim the resources in the ResourceMap */ } return Status; } VOID NTAPI IopSetEvent(IN PVOID Context) { PKEVENT Event = Context; /* Set the event */ KeSetEvent(Event, IO_NO_INCREMENT, FALSE); } /* * @implemented */ NTSTATUS NTAPI IoReportTargetDeviceChange(IN PDEVICE_OBJECT PhysicalDeviceObject, IN PVOID NotificationStructure) { KEVENT NotifyEvent; NTSTATUS Status, NotifyStatus; PTARGET_DEVICE_CUSTOM_NOTIFICATION notifyStruct = (PTARGET_DEVICE_CUSTOM_NOTIFICATION)NotificationStructure; ASSERT(notifyStruct); /* Check for valid PDO */ if (!IopIsValidPhysicalDeviceObject(PhysicalDeviceObject)) { KeBugCheckEx(PNP_DETECTED_FATAL_ERROR, 0x2, (ULONG_PTR)PhysicalDeviceObject, 0, 0); } /* FileObject must be null. PnP will fill in it */ ASSERT(notifyStruct->FileObject == NULL); /* Do not handle system PnP events */ if ((RtlCompareMemory(&(notifyStruct->Event), &(GUID_TARGET_DEVICE_QUERY_REMOVE), sizeof(GUID)) != sizeof(GUID)) || (RtlCompareMemory(&(notifyStruct->Event), &(GUID_TARGET_DEVICE_REMOVE_CANCELLED), sizeof(GUID)) != sizeof(GUID)) || (RtlCompareMemory(&(notifyStruct->Event), &(GUID_TARGET_DEVICE_REMOVE_COMPLETE), sizeof(GUID)) != sizeof(GUID))) { return STATUS_INVALID_DEVICE_REQUEST; } if (notifyStruct->Version != 1) { return STATUS_INVALID_DEVICE_REQUEST; } /* Initialize even that will let us know when PnP will have finished notify */ KeInitializeEvent(&NotifyEvent, NotificationEvent, FALSE); Status = PpSetCustomTargetEvent(PhysicalDeviceObject, &NotifyEvent, &NotifyStatus, NULL, NULL, notifyStruct); /* If no error, wait for the notify to end and return the status of the notify and not of the event */ if (NT_SUCCESS(Status)) { KeWaitForSingleObject(&NotifyEvent, Executive, KernelMode, FALSE, NULL); Status = NotifyStatus; } return Status; } /* * @implemented */ NTSTATUS NTAPI IoReportTargetDeviceChangeAsynchronous(IN PDEVICE_OBJECT PhysicalDeviceObject, IN PVOID NotificationStructure, IN PDEVICE_CHANGE_COMPLETE_CALLBACK Callback OPTIONAL, IN PVOID Context OPTIONAL) { PINTERNAL_WORK_QUEUE_ITEM Item = NULL; PTARGET_DEVICE_CUSTOM_NOTIFICATION notifyStruct = (PTARGET_DEVICE_CUSTOM_NOTIFICATION)NotificationStructure; ASSERT(notifyStruct); /* Check for valid PDO */ if (!IopIsValidPhysicalDeviceObject(PhysicalDeviceObject)) { KeBugCheckEx(PNP_DETECTED_FATAL_ERROR, 0x2, (ULONG_PTR)PhysicalDeviceObject, 0, 0); } /* FileObject must be null. PnP will fill in it */ ASSERT(notifyStruct->FileObject == NULL); /* Do not handle system PnP events */ if ((RtlCompareMemory(&(notifyStruct->Event), &(GUID_TARGET_DEVICE_QUERY_REMOVE), sizeof(GUID)) != sizeof(GUID)) || (RtlCompareMemory(&(notifyStruct->Event), &(GUID_TARGET_DEVICE_REMOVE_CANCELLED), sizeof(GUID)) != sizeof(GUID)) || (RtlCompareMemory(&(notifyStruct->Event), &(GUID_TARGET_DEVICE_REMOVE_COMPLETE), sizeof(GUID)) != sizeof(GUID))) { return STATUS_INVALID_DEVICE_REQUEST; } if (notifyStruct->Version != 1) { return STATUS_INVALID_DEVICE_REQUEST; } /* We need to store all the data given by the caller with the WorkItem, so use our own struct */ Item = ExAllocatePoolWithTag(NonPagedPool, sizeof(INTERNAL_WORK_QUEUE_ITEM), ' pP'); if (!Item) return STATUS_INSUFFICIENT_RESOURCES; /* Initialize all stuff */ ObReferenceObject(PhysicalDeviceObject); Item->NotificationStructure = notifyStruct; Item->PhysicalDeviceObject = PhysicalDeviceObject; Item->Callback = Callback; Item->Context = Context; ExInitializeWorkItem(&(Item->WorkItem), IopReportTargetDeviceChangeAsyncWorker, Item); /* Finally, queue the item, our work here is done */ ExQueueWorkItem(&(Item->WorkItem), DelayedWorkQueue); return STATUS_PENDING; }