xref: /reactos/ntoskrnl/io/pnpmgr/pnpreport.c (revision 7eead935)
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     Status = IopCreateDeviceKeyPath(&DeviceNode->InstancePath, REG_OPTION_NON_VOLATILE, &InstanceKey);
288     if (!NT_SUCCESS(Status))
289         return Status;
290 
291     /* Save the driver name */
292     RtlInitUnicodeString(&ValueName, L"Service");
293     Status = ZwSetValueKey(InstanceKey, &ValueName, 0, REG_SZ, ServiceLongName.Buffer, ServiceLongName.Length + sizeof(UNICODE_NULL));
294     if (!NT_SUCCESS(Status))
295     {
296         DPRINT("Failed to write the Service name value: 0x%x\n", Status);
297     }
298 
299     /* Report as non-legacy driver */
300     RtlInitUnicodeString(&ValueName, L"Legacy");
301     LegacyValue = 0;
302     Status = ZwSetValueKey(InstanceKey, &ValueName, 0, REG_DWORD, &LegacyValue, sizeof(LegacyValue));
303     if (!NT_SUCCESS(Status))
304     {
305         DPRINT("Failed to write the Legacy value: 0x%x\n", Status);
306     }
307 
308     /* Add DETECTEDInterfaceType\DriverName */
309     IdLength = 0;
310     IdLength += swprintf(&HardwareId[IdLength],
311                          L"DETECTED%ls\\%wZ",
312                          IfString,
313                          &ServiceName);
314     IdLength++;
315 
316     /* Add DETECTED\DriverName */
317     IdLength += swprintf(&HardwareId[IdLength],
318                          L"DETECTED\\%wZ",
319                          &ServiceName);
320     IdLength++;
321 
322     /* Terminate the string with another null */
323     HardwareId[IdLength++] = UNICODE_NULL;
324 
325     /* Store the value for CompatibleIDs */
326     RtlInitUnicodeString(&ValueName, L"CompatibleIDs");
327     Status = ZwSetValueKey(InstanceKey, &ValueName, 0, REG_MULTI_SZ, HardwareId, IdLength * sizeof(WCHAR));
328     if (!NT_SUCCESS(Status))
329     {
330         DPRINT("Failed to write the compatible IDs: 0x%x\n", Status);
331         ZwClose(InstanceKey);
332         return Status;
333     }
334 
335     /* Add a hardware ID if the driver didn't report one */
336     RtlInitUnicodeString(&ValueName, L"HardwareID");
337     if (ZwQueryValueKey(InstanceKey, &ValueName, KeyValueBasicInformation, NULL, 0, &RequiredLength) == STATUS_OBJECT_NAME_NOT_FOUND)
338     {
339         /* Just use our most specific compatible ID */
340         IdLength = 0;
341         IdLength += swprintf(&HardwareId[IdLength],
342                              L"DETECTED%ls\\%wZ",
343                              IfString,
344                              &ServiceName);
345         IdLength++;
346 
347         HardwareId[IdLength++] = UNICODE_NULL;
348 
349         /* Write the value to the registry */
350         Status = ZwSetValueKey(InstanceKey, &ValueName, 0, REG_MULTI_SZ, HardwareId, IdLength * sizeof(WCHAR));
351         if (!NT_SUCCESS(Status))
352         {
353             DPRINT("Failed to write the hardware ID: 0x%x\n", Status);
354             ZwClose(InstanceKey);
355             return Status;
356         }
357     }
358 
359     /* Assign the resources to the device node */
360     DeviceNode->BootResources = ResourceList;
361     DeviceNode->ResourceRequirements = ResourceRequirements;
362 
363     /* Set appropriate flags */
364     if (DeviceNode->BootResources)
365         IopDeviceNodeSetFlag(DeviceNode, DNF_HAS_BOOT_CONFIG);
366 
367     if (!DeviceNode->ResourceRequirements && !DeviceNode->BootResources)
368         IopDeviceNodeSetFlag(DeviceNode, DNF_NO_RESOURCE_REQUIRED);
369 
370     /* Write the resource information to the registry */
371     IopSetDeviceInstanceData(InstanceKey, DeviceNode);
372 
373     /* If the caller didn't get the resources assigned for us, do it now */
374     if (!ResourceAssigned)
375     {
376         Status = IopAssignDeviceResources(DeviceNode);
377 
378         /* See if we failed */
379         if (!NT_SUCCESS(Status))
380         {
381             DPRINT("Assigning resources failed: 0x%x\n", Status);
382             ZwClose(InstanceKey);
383             return Status;
384         }
385     }
386 
387     /* Close the instance key handle */
388     ZwClose(InstanceKey);
389 
390     /* Register the given DO with PnP root if required */
391     if (DeviceObject && *DeviceObject)
392         PnpRootRegisterDevice(*DeviceObject);
393 
394     /* Report the device's enumeration to umpnpmgr */
395     IopQueueTargetDeviceEvent(&GUID_DEVICE_ENUMERATED,
396                               &DeviceNode->InstancePath);
397 
398     /* Report the device's arrival to umpnpmgr */
399     IopQueueTargetDeviceEvent(&GUID_DEVICE_ARRIVAL,
400                               &DeviceNode->InstancePath);
401 
402     DPRINT("Reported device: %S (%wZ)\n", HardwareId, &DeviceNode->InstancePath);
403 
404     /* Return the PDO */
405     if (DeviceObject) *DeviceObject = Pdo;
406 
407     return STATUS_SUCCESS;
408 }
409 
410 /*
411  * @halfplemented
412  */
413 NTSTATUS
414 NTAPI
415 IoReportResourceForDetection(IN PDRIVER_OBJECT DriverObject,
416                              IN PCM_RESOURCE_LIST DriverList OPTIONAL,
417                              IN ULONG DriverListSize OPTIONAL,
418                              IN PDEVICE_OBJECT DeviceObject OPTIONAL,
419                              IN PCM_RESOURCE_LIST DeviceList OPTIONAL,
420                              IN ULONG DeviceListSize OPTIONAL,
421                              OUT PBOOLEAN ConflictDetected)
422 {
423     PCM_RESOURCE_LIST ResourceList;
424     NTSTATUS Status;
425 
426     *ConflictDetected = FALSE;
427 
428     if (!DriverList && !DeviceList)
429         return STATUS_INVALID_PARAMETER;
430 
431     /* Find the real list */
432     if (!DriverList)
433         ResourceList = DeviceList;
434     else
435         ResourceList = DriverList;
436 
437     /* Look for a resource conflict */
438     Status = IopDetectResourceConflict(ResourceList, FALSE, NULL);
439     if (Status == STATUS_CONFLICTING_ADDRESSES)
440     {
441         /* Oh noes */
442         *ConflictDetected = TRUE;
443     }
444     else if (NT_SUCCESS(Status))
445     {
446         /* Looks like we're good to go */
447 
448         /* TODO: Claim the resources in the ResourceMap */
449     }
450 
451     return Status;
452 }
453 
454 VOID
455 NTAPI
456 IopSetEvent(IN PVOID Context)
457 {
458     PKEVENT Event = Context;
459 
460     /* Set the event */
461     KeSetEvent(Event, IO_NO_INCREMENT, FALSE);
462 }
463 
464 /*
465  * @implemented
466  */
467 NTSTATUS
468 NTAPI
469 IoReportTargetDeviceChange(IN PDEVICE_OBJECT PhysicalDeviceObject,
470                            IN PVOID NotificationStructure)
471 {
472     KEVENT NotifyEvent;
473     NTSTATUS Status, NotifyStatus;
474     PTARGET_DEVICE_CUSTOM_NOTIFICATION notifyStruct = (PTARGET_DEVICE_CUSTOM_NOTIFICATION)NotificationStructure;
475 
476     ASSERT(notifyStruct);
477 
478     /* Check for valid PDO */
479     if (!IopIsValidPhysicalDeviceObject(PhysicalDeviceObject))
480     {
481         KeBugCheckEx(PNP_DETECTED_FATAL_ERROR, 0x2, (ULONG_PTR)PhysicalDeviceObject, 0, 0);
482     }
483 
484     /* FileObject must be null. PnP will fill in it */
485     ASSERT(notifyStruct->FileObject == NULL);
486 
487     /* Do not handle system PnP events */
488     if ((RtlCompareMemory(&(notifyStruct->Event), &(GUID_TARGET_DEVICE_QUERY_REMOVE), sizeof(GUID)) != sizeof(GUID)) ||
489         (RtlCompareMemory(&(notifyStruct->Event), &(GUID_TARGET_DEVICE_REMOVE_CANCELLED), sizeof(GUID)) != sizeof(GUID)) ||
490         (RtlCompareMemory(&(notifyStruct->Event), &(GUID_TARGET_DEVICE_REMOVE_COMPLETE), sizeof(GUID)) != sizeof(GUID)))
491     {
492         return STATUS_INVALID_DEVICE_REQUEST;
493     }
494 
495     if (notifyStruct->Version != 1)
496     {
497         return STATUS_INVALID_DEVICE_REQUEST;
498     }
499 
500     /* Initialize even that will let us know when PnP will have finished notify */
501     KeInitializeEvent(&NotifyEvent, NotificationEvent, FALSE);
502 
503     Status = PpSetCustomTargetEvent(PhysicalDeviceObject, &NotifyEvent, &NotifyStatus, NULL, NULL, notifyStruct);
504     /* If no error, wait for the notify to end and return the status of the notify and not of the event */
505     if (NT_SUCCESS(Status))
506     {
507         KeWaitForSingleObject(&NotifyEvent, Executive, KernelMode, FALSE, NULL);
508         Status = NotifyStatus;
509     }
510 
511     return Status;
512 }
513 
514 /*
515  * @implemented
516  */
517 NTSTATUS
518 NTAPI
519 IoReportTargetDeviceChangeAsynchronous(IN PDEVICE_OBJECT PhysicalDeviceObject,
520                                        IN PVOID NotificationStructure,
521                                        IN PDEVICE_CHANGE_COMPLETE_CALLBACK Callback OPTIONAL,
522                                        IN PVOID Context OPTIONAL)
523 {
524     PINTERNAL_WORK_QUEUE_ITEM Item = NULL;
525     PTARGET_DEVICE_CUSTOM_NOTIFICATION notifyStruct = (PTARGET_DEVICE_CUSTOM_NOTIFICATION)NotificationStructure;
526 
527     ASSERT(notifyStruct);
528 
529     /* Check for valid PDO */
530     if (!IopIsValidPhysicalDeviceObject(PhysicalDeviceObject))
531     {
532         KeBugCheckEx(PNP_DETECTED_FATAL_ERROR, 0x2, (ULONG_PTR)PhysicalDeviceObject, 0, 0);
533     }
534 
535     /* FileObject must be null. PnP will fill in it */
536     ASSERT(notifyStruct->FileObject == NULL);
537 
538     /* Do not handle system PnP events */
539     if ((RtlCompareMemory(&(notifyStruct->Event), &(GUID_TARGET_DEVICE_QUERY_REMOVE), sizeof(GUID)) != sizeof(GUID)) ||
540         (RtlCompareMemory(&(notifyStruct->Event), &(GUID_TARGET_DEVICE_REMOVE_CANCELLED), sizeof(GUID)) != sizeof(GUID)) ||
541         (RtlCompareMemory(&(notifyStruct->Event), &(GUID_TARGET_DEVICE_REMOVE_COMPLETE), sizeof(GUID)) != sizeof(GUID)))
542     {
543         return STATUS_INVALID_DEVICE_REQUEST;
544     }
545 
546     if (notifyStruct->Version != 1)
547     {
548         return STATUS_INVALID_DEVICE_REQUEST;
549     }
550 
551     /* We need to store all the data given by the caller with the WorkItem, so use our own struct */
552     Item = ExAllocatePoolWithTag(NonPagedPool, sizeof(INTERNAL_WORK_QUEUE_ITEM), '  pP');
553     if (!Item) return STATUS_INSUFFICIENT_RESOURCES;
554 
555     /* Initialize all stuff */
556     ObReferenceObject(PhysicalDeviceObject);
557     Item->NotificationStructure = notifyStruct;
558     Item->PhysicalDeviceObject = PhysicalDeviceObject;
559     Item->Callback = Callback;
560     Item->Context = Context;
561     ExInitializeWorkItem(&(Item->WorkItem), IopReportTargetDeviceChangeAsyncWorker, Item);
562 
563     /* Finally, queue the item, our work here is done */
564     ExQueueWorkItem(&(Item->WorkItem), DelayedWorkQueue);
565 
566     return STATUS_PENDING;
567 }
568