[TESTS] Add a test for spec2def
[reactos.git] / ntoskrnl / io / pnpmgr / pnpreport.c
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 }