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