xref: /reactos/ntoskrnl/io/pnpmgr/pnpreport.c (revision 7b1049c8)
1 /*
2  * PROJECT:         ReactOS Kernel
3  * COPYRIGHT:       GPL - See COPYING in the top level directory
4  * FILE:            ntoskrnl/io/pnpmgr/pnpreport.c
5  * PURPOSE:         Device Changes Reporting Functions
6  * PROGRAMMERS:     Cameron Gutman (cameron.gutman@reactos.org)
7  *                  Pierre Schweitzer
8  */
9 
10 /* INCLUDES ******************************************************************/
11 
12 #include <ntoskrnl.h>
13 #define NDEBUG
14 #include <debug.h>
15 
16 /* TYPES *******************************************************************/
17 
18 typedef struct _INTERNAL_WORK_QUEUE_ITEM
19 {
20     WORK_QUEUE_ITEM WorkItem;
21     PDEVICE_OBJECT PhysicalDeviceObject;
22     PDEVICE_CHANGE_COMPLETE_CALLBACK Callback;
23     PVOID Context;
24     PTARGET_DEVICE_CUSTOM_NOTIFICATION NotificationStructure;
25 } INTERNAL_WORK_QUEUE_ITEM, *PINTERNAL_WORK_QUEUE_ITEM;
26 
27 NTSTATUS
28 IopSetDeviceInstanceData(HANDLE InstanceKey,
29                          PDEVICE_NODE DeviceNode);
30 
31 NTSTATUS
32 PpSetCustomTargetEvent(IN PDEVICE_OBJECT DeviceObject,
33                        IN OUT PKEVENT SyncEvent OPTIONAL,
34                        IN OUT PNTSTATUS SyncStatus OPTIONAL,
35                        IN PDEVICE_CHANGE_COMPLETE_CALLBACK Callback OPTIONAL,
36                        IN PVOID Context OPTIONAL,
37                        IN PTARGET_DEVICE_CUSTOM_NOTIFICATION NotificationStructure);
38 
39 /* PRIVATE FUNCTIONS *********************************************************/
40 
41 PWCHAR
42 IopGetInterfaceTypeString(INTERFACE_TYPE IfType)
43 {
44     switch (IfType)
45     {
46         case Internal:
47             return L"Internal";
48 
49         case Isa:
50             return L"Isa";
51 
52         case Eisa:
53             return L"Eisa";
54 
55         case MicroChannel:
56             return L"MicroChannel";
57 
58         case TurboChannel:
59             return L"TurboChannel";
60 
61         case PCIBus:
62             return L"PCIBus";
63 
64         case VMEBus:
65             return L"VMEBus";
66 
67         case NuBus:
68             return L"NuBus";
69 
70         case PCMCIABus:
71             return L"PCMCIABus";
72 
73         case CBus:
74             return L"CBus";
75 
76         case MPIBus:
77             return L"MPIBus";
78 
79         case MPSABus:
80             return L"MPSABus";
81 
82         case ProcessorInternal:
83             return L"ProcessorInternal";
84 
85         case PNPISABus:
86             return L"PNPISABus";
87 
88         case PNPBus:
89             return L"PNPBus";
90 
91         case Vmcs:
92             return L"Vmcs";
93 
94         default:
95             DPRINT1("Invalid bus type: %d\n", IfType);
96             return NULL;
97     }
98 }
99 
100 VOID
101 NTAPI
102 IopReportTargetDeviceChangeAsyncWorker(PVOID Context)
103 {
104     PINTERNAL_WORK_QUEUE_ITEM Item;
105 
106     Item = (PINTERNAL_WORK_QUEUE_ITEM)Context;
107     PpSetCustomTargetEvent(Item->PhysicalDeviceObject, NULL, NULL, Item->Callback, Item->Context, Item->NotificationStructure);
108     ObDereferenceObject(Item->PhysicalDeviceObject);
109     ExFreePoolWithTag(Context, '  pP');
110 }
111 
112 NTSTATUS
113 PpSetCustomTargetEvent(IN PDEVICE_OBJECT DeviceObject,
114                        IN OUT PKEVENT SyncEvent OPTIONAL,
115                        IN OUT PNTSTATUS SyncStatus OPTIONAL,
116                        IN PDEVICE_CHANGE_COMPLETE_CALLBACK Callback OPTIONAL,
117                        IN PVOID Context OPTIONAL,
118                        IN PTARGET_DEVICE_CUSTOM_NOTIFICATION NotificationStructure)
119 {
120     ASSERT(NotificationStructure != NULL);
121     ASSERT(DeviceObject != NULL);
122 
123     if (SyncEvent)
124     {
125         ASSERT(SyncStatus);
126         *SyncStatus = STATUS_PENDING;
127     }
128 
129     /* That call is totally wrong but notifications handler must be fixed first */
130     PiNotifyTargetDeviceChange(&GUID_PNP_CUSTOM_NOTIFICATION, DeviceObject, NotificationStructure);
131 
132     if (SyncEvent)
133     {
134         KeSetEvent(SyncEvent, IO_NO_INCREMENT, FALSE);
135         *SyncStatus = STATUS_SUCCESS;
136     }
137 
138     return STATUS_SUCCESS;
139 }
140 
141 /* PUBLIC FUNCTIONS **********************************************************/
142 
143 /*
144  * @implemented
145  */
146 NTSTATUS
147 NTAPI
148 IoReportDetectedDevice(
149     _In_ PDRIVER_OBJECT DriverObject,
150     _In_ INTERFACE_TYPE LegacyBusType,
151     _In_ ULONG BusNumber,
152     _In_ ULONG SlotNumber,
153     _In_opt_ PCM_RESOURCE_LIST ResourceList,
154     _In_opt_ PIO_RESOURCE_REQUIREMENTS_LIST ResourceRequirements,
155     _In_ BOOLEAN ResourceAssigned,
156     _Inout_ PDEVICE_OBJECT *DeviceObject)
157 {
158     PDEVICE_NODE DeviceNode;
159     PDEVICE_OBJECT Pdo;
160     NTSTATUS Status;
161     HANDLE InstanceKey;
162     UNICODE_STRING ValueName, ServiceLongName, ServiceName;
163     WCHAR HardwareId[256];
164     PWCHAR IfString;
165     ULONG IdLength;
166     ULONG LegacyValue;
167 
168     DPRINT("IoReportDetectedDevice (DeviceObject %p, *DeviceObject %p)\n",
169            DeviceObject, DeviceObject ? *DeviceObject : NULL);
170 
171     ServiceLongName = DriverObject->DriverExtension->ServiceKeyName;
172     ServiceName = ServiceLongName;
173 
174     /* If the interface type is unknown, treat it as internal */
175     if (LegacyBusType == InterfaceTypeUndefined)
176         LegacyBusType = Internal;
177 
178     /* Get the string equivalent of the interface type */
179     IfString = IopGetInterfaceTypeString(LegacyBusType);
180 
181     /* If NULL is returned then it's a bad type */
182     if (!IfString)
183         return STATUS_INVALID_PARAMETER;
184 
185     /*
186      * Drivers that have been created via a direct IoCreateDriver() call
187      * have their ServiceKeyName set to \Driver\DriverName. We need to
188      * strip everything up to the last path separator and keep what remains.
189      */
190     if (DriverObject->Flags & DRVO_BUILTIN_DRIVER)
191     {
192         /*
193          * Find the last path separator.
194          * NOTE: Since ServiceName is not necessarily NULL-terminated,
195          * we cannot use wcsrchr().
196          */
197         if (ServiceName.Buffer && ServiceName.Length >= sizeof(WCHAR))
198         {
199             ValueName.Length = 1;
200             ValueName.Buffer = ServiceName.Buffer + (ServiceName.Length / sizeof(WCHAR)) - 1;
201 
202             while ((ValueName.Buffer > ServiceName.Buffer) && (*ValueName.Buffer != L'\\'))
203             {
204                 --ValueName.Buffer;
205                 ++ValueName.Length;
206             }
207             if (*ValueName.Buffer == L'\\')
208             {
209                 ++ValueName.Buffer;
210                 --ValueName.Length;
211             }
212             ValueName.Length *= sizeof(WCHAR);
213 
214             /* Shorten the string */
215             ServiceName.MaximumLength -= (ServiceName.Length - ValueName.Length);
216             ServiceName.Length = ValueName.Length;
217             ServiceName.Buffer = ValueName.Buffer;
218         }
219     }
220 
221     /* We use the caller's PDO if they supplied one */
222     UNICODE_STRING instancePath;
223     if (DeviceObject && *DeviceObject)
224     {
225         Pdo = *DeviceObject;
226     }
227     else
228     {
229         /* Create the PDO */
230         Status = PnpRootCreateDevice(&ServiceName, NULL, &Pdo, &instancePath);
231         if (!NT_SUCCESS(Status))
232         {
233             DPRINT("PnpRootCreateDevice() failed (Status 0x%08lx)\n", Status);
234             return Status;
235         }
236     }
237 
238     /* Create the device node for the new PDO */
239     DeviceNode = PipAllocateDeviceNode(Pdo);
240     if (!DeviceNode)
241     {
242         DPRINT("PipAllocateDeviceNode() failed\n");
243         return STATUS_INSUFFICIENT_RESOURCES;
244     }
245 
246     Status = RtlDuplicateUnicodeString(0, &instancePath, &DeviceNode->InstancePath);
247 
248     /* Open a handle to the instance path key */
249     Status = IopCreateDeviceKeyPath(&DeviceNode->InstancePath, REG_OPTION_NON_VOLATILE, &InstanceKey);
250     if (!NT_SUCCESS(Status))
251         return Status;
252 
253     /* Save the driver name */
254     RtlInitUnicodeString(&ValueName, L"Service");
255     Status = ZwSetValueKey(InstanceKey, &ValueName, 0, REG_SZ, ServiceLongName.Buffer, ServiceLongName.Length + sizeof(UNICODE_NULL));
256     if (!NT_SUCCESS(Status))
257     {
258         DPRINT("Failed to write the Service name value: 0x%x\n", Status);
259     }
260 
261     /* Report as non-legacy driver */
262     RtlInitUnicodeString(&ValueName, L"Legacy");
263     LegacyValue = 0;
264     Status = ZwSetValueKey(InstanceKey, &ValueName, 0, REG_DWORD, &LegacyValue, sizeof(LegacyValue));
265     if (!NT_SUCCESS(Status))
266     {
267         DPRINT("Failed to write the Legacy value: 0x%x\n", Status);
268     }
269 
270     /* Add DETECTEDInterfaceType\DriverName */
271     IdLength = 0;
272     IdLength += swprintf(&HardwareId[IdLength],
273                          L"DETECTED%ls\\%wZ",
274                          IfString,
275                          &ServiceName);
276     IdLength++;
277 
278     /* Add DETECTED\DriverName */
279     IdLength += swprintf(&HardwareId[IdLength],
280                          L"DETECTED\\%wZ",
281                          &ServiceName);
282     IdLength++;
283 
284     /* Terminate the string with another null */
285     HardwareId[IdLength++] = UNICODE_NULL;
286 
287     /* Store the value for CompatibleIDs */
288     RtlInitUnicodeString(&ValueName, L"CompatibleIDs");
289     Status = ZwSetValueKey(InstanceKey, &ValueName, 0, REG_MULTI_SZ, HardwareId, IdLength * sizeof(WCHAR));
290     if (!NT_SUCCESS(Status))
291     {
292         DPRINT("Failed to write the compatible IDs: 0x%x\n", Status);
293         ZwClose(InstanceKey);
294         return Status;
295     }
296 
297     /* Assign the resources to the device node */
298     DeviceNode->BootResources = ResourceList;
299     DeviceNode->ResourceRequirements = ResourceRequirements;
300 
301     /* Set appropriate flags */
302     if (DeviceNode->BootResources)
303         IopDeviceNodeSetFlag(DeviceNode, DNF_HAS_BOOT_CONFIG);
304 
305     if (!DeviceNode->ResourceRequirements && !DeviceNode->BootResources)
306         IopDeviceNodeSetFlag(DeviceNode, DNF_NO_RESOURCE_REQUIRED);
307 
308     /* Write the resource information to the registry */
309     IopSetDeviceInstanceData(InstanceKey, DeviceNode);
310 
311     /* If the caller didn't get the resources assigned for us, do it now */
312     if (!ResourceAssigned)
313     {
314         Status = IopAssignDeviceResources(DeviceNode);
315 
316         /* See if we failed */
317         if (!NT_SUCCESS(Status))
318         {
319             DPRINT("Assigning resources failed: 0x%x\n", Status);
320             ZwClose(InstanceKey);
321             return Status;
322         }
323     }
324 
325     /* Close the instance key handle */
326     ZwClose(InstanceKey);
327 
328     /* Register the given DO with PnP root if required */
329     if (DeviceObject && *DeviceObject)
330         PnpRootRegisterDevice(*DeviceObject);
331 
332     PiInsertDevNode(DeviceNode, IopRootDeviceNode);
333     DeviceNode->Flags |= DNF_MADEUP | DNF_ENUMERATED;
334 
335     // we still need to query IDs, send events and reenumerate this node
336     PiSetDevNodeState(DeviceNode, DeviceNodeStartPostWork);
337 
338     DPRINT("Reported device: %S (%wZ)\n", HardwareId, &DeviceNode->InstancePath);
339 
340     PiQueueDeviceAction(Pdo, PiActionEnumDeviceTree, NULL, NULL);
341 
342     /* Return the PDO */
343     if (DeviceObject) *DeviceObject = Pdo;
344 
345     return STATUS_SUCCESS;
346 }
347 
348 /*
349  * @halfplemented
350  */
351 NTSTATUS
352 NTAPI
353 IoReportResourceForDetection(IN PDRIVER_OBJECT DriverObject,
354                              IN PCM_RESOURCE_LIST DriverList OPTIONAL,
355                              IN ULONG DriverListSize OPTIONAL,
356                              IN PDEVICE_OBJECT DeviceObject OPTIONAL,
357                              IN PCM_RESOURCE_LIST DeviceList OPTIONAL,
358                              IN ULONG DeviceListSize OPTIONAL,
359                              OUT PBOOLEAN ConflictDetected)
360 {
361     PCM_RESOURCE_LIST ResourceList;
362     NTSTATUS Status;
363 
364     *ConflictDetected = FALSE;
365 
366     if (!DriverList && !DeviceList)
367         return STATUS_INVALID_PARAMETER;
368 
369     /* Find the real list */
370     if (!DriverList)
371         ResourceList = DeviceList;
372     else
373         ResourceList = DriverList;
374 
375     /* Look for a resource conflict */
376     Status = IopDetectResourceConflict(ResourceList, FALSE, NULL);
377     if (Status == STATUS_CONFLICTING_ADDRESSES)
378     {
379         /* Oh noes */
380         *ConflictDetected = TRUE;
381     }
382     else if (NT_SUCCESS(Status))
383     {
384         /* Looks like we're good to go */
385 
386         /* TODO: Claim the resources in the ResourceMap */
387     }
388 
389     return Status;
390 }
391 
392 VOID
393 NTAPI
394 IopSetEvent(IN PVOID Context)
395 {
396     PKEVENT Event = Context;
397 
398     /* Set the event */
399     KeSetEvent(Event, IO_NO_INCREMENT, FALSE);
400 }
401 
402 /*
403  * @implemented
404  */
405 NTSTATUS
406 NTAPI
407 IoReportTargetDeviceChange(IN PDEVICE_OBJECT PhysicalDeviceObject,
408                            IN PVOID NotificationStructure)
409 {
410     KEVENT NotifyEvent;
411     NTSTATUS Status, NotifyStatus;
412     PTARGET_DEVICE_CUSTOM_NOTIFICATION notifyStruct = (PTARGET_DEVICE_CUSTOM_NOTIFICATION)NotificationStructure;
413 
414     ASSERT(notifyStruct);
415 
416     /* Check for valid PDO */
417     if (!IopIsValidPhysicalDeviceObject(PhysicalDeviceObject))
418     {
419         KeBugCheckEx(PNP_DETECTED_FATAL_ERROR, 0x2, (ULONG_PTR)PhysicalDeviceObject, 0, 0);
420     }
421 
422     /* FileObject must be null. PnP will fill in it */
423     ASSERT(notifyStruct->FileObject == NULL);
424 
425     /* Do not handle system PnP events */
426     if ((RtlCompareMemory(&(notifyStruct->Event), &(GUID_TARGET_DEVICE_QUERY_REMOVE), sizeof(GUID)) != sizeof(GUID)) ||
427         (RtlCompareMemory(&(notifyStruct->Event), &(GUID_TARGET_DEVICE_REMOVE_CANCELLED), sizeof(GUID)) != sizeof(GUID)) ||
428         (RtlCompareMemory(&(notifyStruct->Event), &(GUID_TARGET_DEVICE_REMOVE_COMPLETE), sizeof(GUID)) != sizeof(GUID)))
429     {
430         return STATUS_INVALID_DEVICE_REQUEST;
431     }
432 
433     if (notifyStruct->Version != 1)
434     {
435         return STATUS_INVALID_DEVICE_REQUEST;
436     }
437 
438     /* Initialize even that will let us know when PnP will have finished notify */
439     KeInitializeEvent(&NotifyEvent, NotificationEvent, FALSE);
440 
441     Status = PpSetCustomTargetEvent(PhysicalDeviceObject, &NotifyEvent, &NotifyStatus, NULL, NULL, notifyStruct);
442     /* If no error, wait for the notify to end and return the status of the notify and not of the event */
443     if (NT_SUCCESS(Status))
444     {
445         KeWaitForSingleObject(&NotifyEvent, Executive, KernelMode, FALSE, NULL);
446         Status = NotifyStatus;
447     }
448 
449     return Status;
450 }
451 
452 /*
453  * @implemented
454  */
455 NTSTATUS
456 NTAPI
457 IoReportTargetDeviceChangeAsynchronous(IN PDEVICE_OBJECT PhysicalDeviceObject,
458                                        IN PVOID NotificationStructure,
459                                        IN PDEVICE_CHANGE_COMPLETE_CALLBACK Callback OPTIONAL,
460                                        IN PVOID Context OPTIONAL)
461 {
462     PINTERNAL_WORK_QUEUE_ITEM Item = NULL;
463     PTARGET_DEVICE_CUSTOM_NOTIFICATION notifyStruct = (PTARGET_DEVICE_CUSTOM_NOTIFICATION)NotificationStructure;
464 
465     ASSERT(notifyStruct);
466 
467     /* Check for valid PDO */
468     if (!IopIsValidPhysicalDeviceObject(PhysicalDeviceObject))
469     {
470         KeBugCheckEx(PNP_DETECTED_FATAL_ERROR, 0x2, (ULONG_PTR)PhysicalDeviceObject, 0, 0);
471     }
472 
473     /* FileObject must be null. PnP will fill in it */
474     ASSERT(notifyStruct->FileObject == NULL);
475 
476     /* Do not handle system PnP events */
477     if ((RtlCompareMemory(&(notifyStruct->Event), &(GUID_TARGET_DEVICE_QUERY_REMOVE), sizeof(GUID)) != sizeof(GUID)) ||
478         (RtlCompareMemory(&(notifyStruct->Event), &(GUID_TARGET_DEVICE_REMOVE_CANCELLED), sizeof(GUID)) != sizeof(GUID)) ||
479         (RtlCompareMemory(&(notifyStruct->Event), &(GUID_TARGET_DEVICE_REMOVE_COMPLETE), sizeof(GUID)) != sizeof(GUID)))
480     {
481         return STATUS_INVALID_DEVICE_REQUEST;
482     }
483 
484     if (notifyStruct->Version != 1)
485     {
486         return STATUS_INVALID_DEVICE_REQUEST;
487     }
488 
489     /* We need to store all the data given by the caller with the WorkItem, so use our own struct */
490     Item = ExAllocatePoolWithTag(NonPagedPool, sizeof(INTERNAL_WORK_QUEUE_ITEM), '  pP');
491     if (!Item) return STATUS_INSUFFICIENT_RESOURCES;
492 
493     /* Initialize all stuff */
494     ObReferenceObject(PhysicalDeviceObject);
495     Item->NotificationStructure = notifyStruct;
496     Item->PhysicalDeviceObject = PhysicalDeviceObject;
497     Item->Callback = Callback;
498     Item->Context = Context;
499     ExInitializeWorkItem(&(Item->WorkItem), IopReportTargetDeviceChangeAsyncWorker, Item);
500 
501     /* Finally, queue the item, our work here is done */
502     ExQueueWorkItem(&(Item->WorkItem), DelayedWorkQueue);
503 
504     return STATUS_PENDING;
505 }
506