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