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