xref: /reactos/ntoskrnl/io/pnpmgr/pnpreport.c (revision 176de8bd)
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     TARGET_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
IopGetInterfaceTypeString(INTERFACE_TYPE IfType)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
IopReportTargetDeviceChangeAsyncWorker(PVOID Context)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
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)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
IoReportDetectedDevice(_In_ PDRIVER_OBJECT DriverObject,_In_ INTERFACE_TYPE LegacyBusType,_In_ ULONG BusNumber,_In_ ULONG SlotNumber,_In_opt_ PCM_RESOURCE_LIST ResourceList,_In_opt_ PIO_RESOURCE_REQUIREMENTS_LIST ResourceRequirements,_In_ BOOLEAN ResourceAssigned,_Inout_ PDEVICE_OBJECT * DeviceObject)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     UNICODE_STRING Control = RTL_CONSTANT_STRING(L"Control");
159     UNICODE_STRING DeviceReportedName = RTL_CONSTANT_STRING(L"DeviceReported");
160     OBJECT_ATTRIBUTES ObjectAttributes;
161     PDEVICE_NODE DeviceNode;
162     PDEVICE_OBJECT Pdo;
163     NTSTATUS Status;
164     HANDLE InstanceKey, ControlKey;
165     UNICODE_STRING ValueName, ServiceLongName, ServiceName;
166     WCHAR HardwareId[256];
167     PWCHAR IfString;
168     ULONG IdLength;
169     ULONG LegacyValue;
170     ULONG DeviceReported = 1;
171 
172     DPRINT("IoReportDetectedDevice (DeviceObject %p, *DeviceObject %p)\n",
173            DeviceObject, DeviceObject ? *DeviceObject : NULL);
174 
175     ServiceLongName = DriverObject->DriverExtension->ServiceKeyName;
176     ServiceName = ServiceLongName;
177 
178     /* If the interface type is unknown, treat it as internal */
179     if (LegacyBusType == InterfaceTypeUndefined)
180         LegacyBusType = Internal;
181 
182     /* Get the string equivalent of the interface type */
183     IfString = IopGetInterfaceTypeString(LegacyBusType);
184 
185     /* If NULL is returned then it's a bad type */
186     if (!IfString)
187         return STATUS_INVALID_PARAMETER;
188 
189     /*
190      * Drivers that have been created via a direct IoCreateDriver() call
191      * have their ServiceKeyName set to \Driver\DriverName. We need to
192      * strip everything up to the last path separator and keep what remains.
193      */
194     if (DriverObject->Flags & DRVO_BUILTIN_DRIVER)
195     {
196         /*
197          * Find the last path separator.
198          * NOTE: Since ServiceName is not necessarily NULL-terminated,
199          * we cannot use wcsrchr().
200          */
201         if (ServiceName.Buffer && ServiceName.Length >= sizeof(WCHAR))
202         {
203             ValueName.Length = 1;
204             ValueName.Buffer = ServiceName.Buffer + (ServiceName.Length / sizeof(WCHAR)) - 1;
205 
206             while ((ValueName.Buffer > ServiceName.Buffer) && (*ValueName.Buffer != L'\\'))
207             {
208                 --ValueName.Buffer;
209                 ++ValueName.Length;
210             }
211             if (*ValueName.Buffer == L'\\')
212             {
213                 ++ValueName.Buffer;
214                 --ValueName.Length;
215             }
216             ValueName.Length *= sizeof(WCHAR);
217 
218             /* Shorten the string */
219             ServiceName.MaximumLength -= (ServiceName.Length - ValueName.Length);
220             ServiceName.Length = ValueName.Length;
221             ServiceName.Buffer = ValueName.Buffer;
222         }
223     }
224 
225     /* We use the caller's PDO if they supplied one */
226     UNICODE_STRING instancePath = {0};
227     if (DeviceObject && *DeviceObject)
228     {
229         Pdo = *DeviceObject;
230     }
231     else
232     {
233         /* Create the PDO */
234         Status = PnpRootCreateDevice(&ServiceName, &Pdo, &instancePath);
235         if (!NT_SUCCESS(Status))
236         {
237             DPRINT("PnpRootCreateDevice() failed (Status 0x%08lx)\n", Status);
238             return Status;
239         }
240     }
241 
242     /* Create the device node for the new PDO */
243     DeviceNode = PipAllocateDeviceNode(Pdo);
244     if (!DeviceNode)
245     {
246         DPRINT("PipAllocateDeviceNode() failed\n");
247         return STATUS_INSUFFICIENT_RESOURCES;
248     }
249 
250     // The string comes from PnpRootCreateDevice, so it can be used right away
251     DeviceNode->InstancePath = instancePath;
252 
253     /* Open a handle to the instance path key */
254     Status = IopCreateDeviceKeyPath(&DeviceNode->InstancePath, REG_OPTION_NON_VOLATILE, &InstanceKey);
255     if (!NT_SUCCESS(Status))
256         return Status;
257 
258     /* Save the driver name */
259     RtlInitUnicodeString(&ValueName, L"Service");
260     Status = ZwSetValueKey(InstanceKey, &ValueName, 0, REG_SZ, ServiceLongName.Buffer, ServiceLongName.Length + sizeof(UNICODE_NULL));
261     if (!NT_SUCCESS(Status))
262     {
263         DPRINT("Failed to write the Service name value: 0x%x\n", Status);
264     }
265 
266     /* Report as non-legacy driver */
267     RtlInitUnicodeString(&ValueName, L"Legacy");
268     LegacyValue = 0;
269     Status = ZwSetValueKey(InstanceKey, &ValueName, 0, REG_DWORD, &LegacyValue, sizeof(LegacyValue));
270     if (!NT_SUCCESS(Status))
271     {
272         DPRINT("Failed to write the Legacy value: 0x%x\n", Status);
273     }
274     Status = ZwSetValueKey(InstanceKey, &DeviceReportedName, 0, REG_DWORD, &DeviceReported, sizeof(DeviceReported));
275     if (!NT_SUCCESS(Status))
276     {
277         DPRINT("Failed to write the DeviceReported value: 0x%x\n", Status);
278     }
279 
280     /* Set DeviceReported=1 in Control subkey */
281     InitializeObjectAttributes(&ObjectAttributes,
282                                &Control,
283                                OBJ_CASE_INSENSITIVE | OBJ_KERNEL_HANDLE,
284                                InstanceKey,
285                                NULL);
286     Status = ZwCreateKey(&ControlKey,
287                          KEY_SET_VALUE,
288                          &ObjectAttributes,
289                          0,
290                          NULL,
291                          REG_OPTION_VOLATILE,
292                          NULL);
293     if (NT_SUCCESS(Status))
294     {
295         Status = ZwSetValueKey(ControlKey,
296                                &DeviceReportedName,
297                                0,
298                                REG_DWORD,
299                                &DeviceReported,
300                                sizeof(DeviceReported));
301         ZwClose(ControlKey);
302     }
303     if (!NT_SUCCESS(Status))
304     {
305         DPRINT1("Failed to set ReportedDevice=1 for device %wZ (status 0x%08lx)\n", &instancePath, 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     // Set the device's DeviceDesc and LocationInformation fields
336     PiSetDevNodeText(DeviceNode, InstanceKey);
337 
338     /* Assign the resources to the device node */
339     DeviceNode->BootResources = ResourceList;
340     DeviceNode->ResourceRequirements = ResourceRequirements;
341 
342     /* Set appropriate flags */
343     if (DeviceNode->BootResources)
344         IopDeviceNodeSetFlag(DeviceNode, DNF_HAS_BOOT_CONFIG);
345 
346     if (!DeviceNode->ResourceRequirements && !DeviceNode->BootResources)
347         IopDeviceNodeSetFlag(DeviceNode, DNF_NO_RESOURCE_REQUIRED);
348 
349     /* Write the resource information to the registry */
350     IopSetDeviceInstanceData(InstanceKey, DeviceNode);
351 
352     /* If the caller didn't get the resources assigned for us, do it now */
353     if (!ResourceAssigned)
354     {
355         Status = IopAssignDeviceResources(DeviceNode);
356 
357         /* See if we failed */
358         if (!NT_SUCCESS(Status))
359         {
360             DPRINT("Assigning resources failed: 0x%x\n", Status);
361             ZwClose(InstanceKey);
362             return Status;
363         }
364     }
365 
366     /* Close the instance key handle */
367     ZwClose(InstanceKey);
368 
369     /* Register the given DO with PnP root if required */
370     if (DeviceObject && *DeviceObject)
371         PnpRootRegisterDevice(*DeviceObject);
372 
373     PiInsertDevNode(DeviceNode, IopRootDeviceNode);
374     DeviceNode->Flags |= DNF_MADEUP | DNF_ENUMERATED;
375 
376     // we still need to query IDs, send events and reenumerate this node
377     PiSetDevNodeState(DeviceNode, DeviceNodeStartPostWork);
378 
379     DPRINT("Reported device: %S (%wZ)\n", HardwareId, &DeviceNode->InstancePath);
380 
381     PiQueueDeviceAction(Pdo, PiActionEnumDeviceTree, NULL, NULL);
382 
383     /* Return the PDO */
384     if (DeviceObject) *DeviceObject = Pdo;
385 
386     return STATUS_SUCCESS;
387 }
388 
389 /*
390  * @halfplemented
391  */
392 NTSTATUS
393 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)394 IoReportResourceForDetection(IN PDRIVER_OBJECT DriverObject,
395                              IN PCM_RESOURCE_LIST DriverList OPTIONAL,
396                              IN ULONG DriverListSize OPTIONAL,
397                              IN PDEVICE_OBJECT DeviceObject OPTIONAL,
398                              IN PCM_RESOURCE_LIST DeviceList OPTIONAL,
399                              IN ULONG DeviceListSize OPTIONAL,
400                              OUT PBOOLEAN ConflictDetected)
401 {
402     PCM_RESOURCE_LIST ResourceList;
403     NTSTATUS Status;
404 
405     *ConflictDetected = FALSE;
406 
407     if (!DriverList && !DeviceList)
408         return STATUS_INVALID_PARAMETER;
409 
410     /* Find the real list */
411     if (!DriverList)
412         ResourceList = DeviceList;
413     else
414         ResourceList = DriverList;
415 
416     /* Look for a resource conflict */
417     Status = IopDetectResourceConflict(ResourceList, TRUE, NULL);
418     if (Status == STATUS_CONFLICTING_ADDRESSES)
419     {
420         /* Oh noes */
421         *ConflictDetected = TRUE;
422     }
423     else if (NT_SUCCESS(Status))
424     {
425         /* Looks like we're good to go */
426 
427         /* TODO: Claim the resources in the ResourceMap */
428     }
429 
430     return Status;
431 }
432 
433 VOID
434 NTAPI
IopSetEvent(IN PVOID Context)435 IopSetEvent(IN PVOID Context)
436 {
437     PKEVENT Event = Context;
438 
439     /* Set the event */
440     KeSetEvent(Event, IO_NO_INCREMENT, FALSE);
441 }
442 
443 /*
444  * @implemented
445  */
446 NTSTATUS
447 NTAPI
IoReportTargetDeviceChange(IN PDEVICE_OBJECT PhysicalDeviceObject,IN PVOID NotificationStructure)448 IoReportTargetDeviceChange(IN PDEVICE_OBJECT PhysicalDeviceObject,
449                            IN PVOID NotificationStructure)
450 {
451     KEVENT NotifyEvent;
452     NTSTATUS Status, NotifyStatus;
453     PTARGET_DEVICE_CUSTOM_NOTIFICATION notifyStruct = (PTARGET_DEVICE_CUSTOM_NOTIFICATION)NotificationStructure;
454 
455     ASSERT(notifyStruct);
456 
457     /* Check for valid PDO */
458     if (!IopIsValidPhysicalDeviceObject(PhysicalDeviceObject))
459     {
460         KeBugCheckEx(PNP_DETECTED_FATAL_ERROR, 0x2, (ULONG_PTR)PhysicalDeviceObject, 0, 0);
461     }
462 
463     /* FileObject must be null. PnP will fill in it */
464     ASSERT(notifyStruct->FileObject == NULL);
465 
466     /* Do not handle system PnP events */
467     if (IsEqualGUID(&(notifyStruct->Event), &GUID_TARGET_DEVICE_QUERY_REMOVE) ||
468         IsEqualGUID(&(notifyStruct->Event), &GUID_TARGET_DEVICE_REMOVE_CANCELLED) ||
469         IsEqualGUID(&(notifyStruct->Event), &GUID_TARGET_DEVICE_REMOVE_COMPLETE))
470     {
471         return STATUS_INVALID_DEVICE_REQUEST;
472     }
473 
474     if (notifyStruct->Version != 1)
475     {
476         return STATUS_INVALID_DEVICE_REQUEST;
477     }
478 
479     /* Initialize even that will let us know when PnP will have finished notify */
480     KeInitializeEvent(&NotifyEvent, NotificationEvent, FALSE);
481 
482     Status = PpSetCustomTargetEvent(PhysicalDeviceObject, &NotifyEvent, &NotifyStatus, NULL, NULL, notifyStruct);
483     /* If no error, wait for the notify to end and return the status of the notify and not of the event */
484     if (NT_SUCCESS(Status))
485     {
486         KeWaitForSingleObject(&NotifyEvent, Executive, KernelMode, FALSE, NULL);
487         Status = NotifyStatus;
488     }
489 
490     return Status;
491 }
492 
493 /*
494  * @implemented
495  */
496 NTSTATUS
497 NTAPI
IoReportTargetDeviceChangeAsynchronous(IN PDEVICE_OBJECT PhysicalDeviceObject,IN PVOID NotificationStructure,IN PDEVICE_CHANGE_COMPLETE_CALLBACK Callback OPTIONAL,IN PVOID Context OPTIONAL)498 IoReportTargetDeviceChangeAsynchronous(IN PDEVICE_OBJECT PhysicalDeviceObject,
499                                        IN PVOID NotificationStructure,
500                                        IN PDEVICE_CHANGE_COMPLETE_CALLBACK Callback OPTIONAL,
501                                        IN PVOID Context OPTIONAL)
502 {
503     PINTERNAL_WORK_QUEUE_ITEM Item = NULL;
504     PTARGET_DEVICE_CUSTOM_NOTIFICATION notifyStruct = (PTARGET_DEVICE_CUSTOM_NOTIFICATION)NotificationStructure;
505     SIZE_T WorkItemSize;
506 
507     ASSERT(notifyStruct);
508 
509     /* Check for valid PDO */
510     if (!IopIsValidPhysicalDeviceObject(PhysicalDeviceObject))
511     {
512         KeBugCheckEx(PNP_DETECTED_FATAL_ERROR, 0x2, (ULONG_PTR)PhysicalDeviceObject, 0, 0);
513     }
514 
515     /* FileObject must be null. PnP will fill in it */
516     ASSERT(notifyStruct->FileObject == NULL);
517 
518     /* Do not handle system PnP events */
519     if (IsEqualGUID(&(notifyStruct->Event), &GUID_TARGET_DEVICE_QUERY_REMOVE) ||
520         IsEqualGUID(&(notifyStruct->Event), &GUID_TARGET_DEVICE_REMOVE_CANCELLED) ||
521         IsEqualGUID(&(notifyStruct->Event), &GUID_TARGET_DEVICE_REMOVE_COMPLETE))
522     {
523         return STATUS_INVALID_DEVICE_REQUEST;
524     }
525 
526     if (notifyStruct->Version != 1)
527     {
528         return STATUS_INVALID_DEVICE_REQUEST;
529     }
530 
531     /* Calculate the required size for the work item and notification structure */
532     WorkItemSize = FIELD_OFFSET(INTERNAL_WORK_QUEUE_ITEM, NotificationStructure)
533                    + notifyStruct->Size;
534 
535     /* We need to store all the data given by the caller with the WorkItem, so use our own struct */
536     Item = ExAllocatePoolWithTag(NonPagedPool, WorkItemSize, '  pP');
537     if (!Item) return STATUS_INSUFFICIENT_RESOURCES;
538 
539     /* Initialize all stuff */
540     ObReferenceObject(PhysicalDeviceObject);
541     Item->PhysicalDeviceObject = PhysicalDeviceObject;
542     Item->Callback = Callback;
543     Item->Context = Context;
544     RtlCopyMemory(&Item->NotificationStructure, notifyStruct, notifyStruct->Size);
545     ExInitializeWorkItem(&(Item->WorkItem), IopReportTargetDeviceChangeAsyncWorker, Item);
546 
547     /* Finally, queue the item, our work here is done */
548     ExQueueWorkItem(&(Item->WorkItem), DelayedWorkQueue);
549 
550     return STATUS_PENDING;
551 }
552