* Sync up to trunk head (r64716).
[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 IopReportTargetDeviceChangeAsyncWorker(PVOID Context)
112 {
113 PINTERNAL_WORK_QUEUE_ITEM Item;
114
115 Item = (PINTERNAL_WORK_QUEUE_ITEM)Context;
116 PpSetCustomTargetEvent(Item->PhysicalDeviceObject, NULL, NULL, Item->Callback, Item->Context, Item->NotificationStructure);
117 ObDereferenceObject(Item->PhysicalDeviceObject);
118 ExFreePoolWithTag(Context, ' pP');
119 }
120
121 NTSTATUS
122 PpSetCustomTargetEvent(IN PDEVICE_OBJECT DeviceObject,
123 IN OUT PKEVENT SyncEvent OPTIONAL,
124 IN OUT PNTSTATUS SyncStatus OPTIONAL,
125 IN PDEVICE_CHANGE_COMPLETE_CALLBACK Callback OPTIONAL,
126 IN PVOID Context OPTIONAL,
127 IN PTARGET_DEVICE_CUSTOM_NOTIFICATION NotificationStructure)
128 {
129 ASSERT(NotificationStructure != NULL);
130 ASSERT(DeviceObject != NULL);
131
132 if (SyncEvent)
133 {
134 ASSERT(SyncStatus);
135 *SyncStatus = STATUS_PENDING;
136 }
137
138 /* That call is totally wrong but notifications handler must be fixed first */
139 IopNotifyPlugPlayNotification(DeviceObject,
140 EventCategoryTargetDeviceChange,
141 &GUID_PNP_CUSTOM_NOTIFICATION,
142 NotificationStructure,
143 NULL);
144
145 if (SyncEvent)
146 {
147 KeSetEvent(SyncEvent, IO_NO_INCREMENT, FALSE);
148 *SyncStatus = STATUS_SUCCESS;
149 }
150
151 return STATUS_SUCCESS;
152 }
153
154 /* PUBLIC FUNCTIONS **********************************************************/
155
156 /*
157 * @implemented
158 */
159 NTSTATUS
160 NTAPI
161 IoReportDetectedDevice(IN PDRIVER_OBJECT DriverObject,
162 IN INTERFACE_TYPE LegacyBusType,
163 IN ULONG BusNumber,
164 IN ULONG SlotNumber,
165 IN PCM_RESOURCE_LIST ResourceList,
166 IN PIO_RESOURCE_REQUIREMENTS_LIST ResourceRequirements OPTIONAL,
167 IN BOOLEAN ResourceAssigned,
168 IN OUT PDEVICE_OBJECT *DeviceObject OPTIONAL)
169 {
170 PDEVICE_NODE DeviceNode;
171 PDEVICE_OBJECT Pdo;
172 NTSTATUS Status;
173 HANDLE InstanceKey;
174 ULONG RequiredLength;
175 UNICODE_STRING ValueName, ServiceName;
176 WCHAR HardwareId[256];
177 PWCHAR IfString;
178 ULONG IdLength;
179
180 DPRINT("IoReportDetectedDevice (DeviceObject %p, *DeviceObject %p)\n",
181 DeviceObject, DeviceObject ? *DeviceObject : NULL);
182
183 /* Create the service name (eg. ACPI_HAL) */
184 ServiceName.Buffer = DriverObject->DriverName.Buffer +
185 sizeof(DRIVER_ROOT_NAME) / sizeof(WCHAR) - 1;
186 ServiceName.Length = DriverObject->DriverName.Length -
187 sizeof(DRIVER_ROOT_NAME) + sizeof(WCHAR);
188 ServiceName.MaximumLength = ServiceName.Length;
189
190 /* If the interface type is unknown, treat it as internal */
191 if (LegacyBusType == InterfaceTypeUndefined)
192 LegacyBusType = Internal;
193
194 /* Get the string equivalent of the interface type */
195 IfString = IopGetInterfaceTypeString(LegacyBusType);
196
197 /* If NULL is returned then it's a bad type */
198 if (!IfString)
199 return STATUS_INVALID_PARAMETER;
200
201 /* We use the caller's PDO if they supplied one */
202 if (DeviceObject && *DeviceObject)
203 {
204 Pdo = *DeviceObject;
205 }
206 else
207 {
208 /* Create the PDO */
209 Status = PnpRootCreateDevice(&ServiceName,
210 NULL,
211 &Pdo,
212 NULL);
213 if (!NT_SUCCESS(Status))
214 {
215 DPRINT("PnpRootCreateDevice() failed (Status 0x%08lx)\n", Status);
216 return Status;
217 }
218 }
219
220 /* Create the device node for the new PDO */
221 Status = IopCreateDeviceNode(IopRootDeviceNode,
222 Pdo,
223 NULL,
224 &DeviceNode);
225
226 if (!NT_SUCCESS(Status))
227 {
228 DPRINT("IopCreateDeviceNode() failed (Status 0x%08lx)\n", Status);
229 return Status;
230 }
231
232 /* We're enumerated already */
233 IopDeviceNodeSetFlag(DeviceNode, DNF_ENUMERATED);
234
235 /* We don't call AddDevice for devices reported this way */
236 IopDeviceNodeSetFlag(DeviceNode, DNF_ADDED);
237
238 /* We don't send IRP_MN_START_DEVICE */
239 IopDeviceNodeSetFlag(DeviceNode, DNF_STARTED);
240
241 /* We need to get device IDs */
242 #if 0
243 IopDeviceNodeSetFlag(DeviceNode, DNF_NEED_QUERY_IDS);
244 #endif
245
246 /* This is a legacy driver for this device */
247 IopDeviceNodeSetFlag(DeviceNode, DNF_LEGACY_DRIVER);
248
249 /* Perform a manual configuration of our device */
250 IopActionInterrogateDeviceStack(DeviceNode, DeviceNode->Parent);
251 IopActionConfigureChildServices(DeviceNode, DeviceNode->Parent);
252
253 /* Open a handle to the instance path key */
254 /* REG_OPTION_VOLATILE is a HACK!!! */
255 Status = IopCreateDeviceKeyPath(&DeviceNode->InstancePath, REG_OPTION_VOLATILE, &InstanceKey);
256 if (!NT_SUCCESS(Status))
257 return Status;
258
259 /* Add DETECTEDInterfaceType\DriverName */
260 IdLength = 0;
261 IdLength += swprintf(&HardwareId[IdLength],
262 L"DETECTED%ls\\%wZ",
263 IfString,
264 &ServiceName);
265 IdLength++;
266
267 /* Add DETECTED\DriverName */
268 IdLength += swprintf(&HardwareId[IdLength],
269 L"DETECTED\\%wZ",
270 &ServiceName);
271 IdLength++;
272
273 /* Terminate the string with another null */
274 HardwareId[IdLength++] = UNICODE_NULL;
275
276 /* Store the value for CompatibleIDs */
277 RtlInitUnicodeString(&ValueName, L"CompatibleIDs");
278 Status = ZwSetValueKey(InstanceKey, &ValueName, 0, REG_MULTI_SZ, HardwareId, IdLength * sizeof(WCHAR));
279 if (!NT_SUCCESS(Status))
280 {
281 DPRINT("Failed to write the compatible IDs: 0x%x\n", Status);
282 ZwClose(InstanceKey);
283 return Status;
284 }
285
286 /* Add a hardware ID if the driver didn't report one */
287 RtlInitUnicodeString(&ValueName, L"HardwareID");
288 if (ZwQueryValueKey(InstanceKey, &ValueName, KeyValueBasicInformation, NULL, 0, &RequiredLength) == STATUS_OBJECT_NAME_NOT_FOUND)
289 {
290 /* Just use our most specific compatible ID */
291 IdLength = 0;
292 IdLength += swprintf(&HardwareId[IdLength],
293 L"DETECTED%ls\\%wZ",
294 IfString,
295 &ServiceName);
296 IdLength++;
297
298 HardwareId[IdLength++] = UNICODE_NULL;
299
300 /* Write the value to the registry */
301 Status = ZwSetValueKey(InstanceKey, &ValueName, 0, REG_MULTI_SZ, HardwareId, IdLength * sizeof(WCHAR));
302 if (!NT_SUCCESS(Status))
303 {
304 DPRINT("Failed to write the hardware ID: 0x%x\n", Status);
305 ZwClose(InstanceKey);
306 return Status;
307 }
308 }
309
310 /* Assign the resources to the device node */
311 DeviceNode->BootResources = ResourceList;
312 DeviceNode->ResourceRequirements = ResourceRequirements;
313
314 /* Set appropriate flags */
315 if (DeviceNode->BootResources)
316 IopDeviceNodeSetFlag(DeviceNode, DNF_HAS_BOOT_CONFIG);
317
318 if (!DeviceNode->ResourceRequirements && !DeviceNode->BootResources)
319 IopDeviceNodeSetFlag(DeviceNode, DNF_NO_RESOURCE_REQUIRED);
320
321 /* Write the resource information to the registry */
322 IopSetDeviceInstanceData(InstanceKey, DeviceNode);
323
324 /* If the caller didn't get the resources assigned for us, do it now */
325 if (!ResourceAssigned)
326 {
327 Status = IopAssignDeviceResources(DeviceNode);
328
329 /* See if we failed */
330 if (!NT_SUCCESS(Status))
331 {
332 DPRINT("Assigning resources failed: 0x%x\n", Status);
333 ZwClose(InstanceKey);
334 return Status;
335 }
336 }
337
338 /* Close the instance key handle */
339 ZwClose(InstanceKey);
340
341 /* Register the given DO with PnP root if required */
342 if (DeviceObject && *DeviceObject)
343 PnpRootRegisterDevice(*DeviceObject);
344
345 /* Report the device's enumeration to umpnpmgr */
346 IopQueueTargetDeviceEvent(&GUID_DEVICE_ENUMERATED,
347 &DeviceNode->InstancePath);
348
349 /* Report the device's arrival to umpnpmgr */
350 IopQueueTargetDeviceEvent(&GUID_DEVICE_ARRIVAL,
351 &DeviceNode->InstancePath);
352
353 DPRINT1("Reported device: %S (%wZ)\n", HardwareId, &DeviceNode->InstancePath);
354
355 /* Return the PDO */
356 if (DeviceObject) *DeviceObject = Pdo;
357
358 return STATUS_SUCCESS;
359 }
360
361 /*
362 * @halfplemented
363 */
364 NTSTATUS
365 NTAPI
366 IoReportResourceForDetection(IN PDRIVER_OBJECT DriverObject,
367 IN PCM_RESOURCE_LIST DriverList OPTIONAL,
368 IN ULONG DriverListSize OPTIONAL,
369 IN PDEVICE_OBJECT DeviceObject OPTIONAL,
370 IN PCM_RESOURCE_LIST DeviceList OPTIONAL,
371 IN ULONG DeviceListSize OPTIONAL,
372 OUT PBOOLEAN ConflictDetected)
373 {
374 PCM_RESOURCE_LIST ResourceList;
375 NTSTATUS Status;
376
377 *ConflictDetected = FALSE;
378
379 if (!DriverList && !DeviceList)
380 return STATUS_INVALID_PARAMETER;
381
382 /* Find the real list */
383 if (!DriverList)
384 ResourceList = DeviceList;
385 else
386 ResourceList = DriverList;
387
388 /* Look for a resource conflict */
389 Status = IopDetectResourceConflict(ResourceList, FALSE, NULL);
390 if (Status == STATUS_CONFLICTING_ADDRESSES)
391 {
392 /* Oh noes */
393 *ConflictDetected = TRUE;
394 }
395 else if (NT_SUCCESS(Status))
396 {
397 /* Looks like we're good to go */
398
399 /* TODO: Claim the resources in the ResourceMap */
400 }
401
402 return Status;
403 }
404
405 VOID
406 NTAPI
407 IopSetEvent(IN PVOID Context)
408 {
409 PKEVENT Event = Context;
410
411 /* Set the event */
412 KeSetEvent(Event, IO_NO_INCREMENT, FALSE);
413 }
414
415 /*
416 * @implemented
417 */
418 NTSTATUS
419 NTAPI
420 IoReportTargetDeviceChange(IN PDEVICE_OBJECT PhysicalDeviceObject,
421 IN PVOID NotificationStructure)
422 {
423 KEVENT NotifyEvent;
424 NTSTATUS Status, NotifyStatus;
425 PTARGET_DEVICE_CUSTOM_NOTIFICATION notifyStruct = (PTARGET_DEVICE_CUSTOM_NOTIFICATION)NotificationStructure;
426
427 ASSERT(notifyStruct);
428
429 /* Check for valid PDO */
430 if (!IopIsValidPhysicalDeviceObject(PhysicalDeviceObject))
431 {
432 KeBugCheckEx(PNP_DETECTED_FATAL_ERROR, 0x2, (ULONG)PhysicalDeviceObject, 0, 0);
433 }
434
435 /* FileObject must be null. PnP will fill in it */
436 ASSERT(notifyStruct->FileObject == NULL);
437
438 /* Do not handle system PnP events */
439 if ((RtlCompareMemory(&(notifyStruct->Event), &(GUID_TARGET_DEVICE_QUERY_REMOVE), sizeof(GUID)) != sizeof(GUID)) ||
440 (RtlCompareMemory(&(notifyStruct->Event), &(GUID_TARGET_DEVICE_REMOVE_CANCELLED), sizeof(GUID)) != sizeof(GUID)) ||
441 (RtlCompareMemory(&(notifyStruct->Event), &(GUID_TARGET_DEVICE_REMOVE_COMPLETE), sizeof(GUID)) != sizeof(GUID)))
442 {
443 return STATUS_INVALID_DEVICE_REQUEST;
444 }
445
446 if (notifyStruct->Version != 1)
447 {
448 return STATUS_INVALID_DEVICE_REQUEST;
449 }
450
451 /* Initialize even that will let us know when PnP will have finished notify */
452 KeInitializeEvent(&NotifyEvent, NotificationEvent, FALSE);
453
454 Status = PpSetCustomTargetEvent(PhysicalDeviceObject, &NotifyEvent, &NotifyStatus, NULL, NULL, notifyStruct);
455 /* If no error, wait for the notify to end and return the status of the notify and not of the event */
456 if (NT_SUCCESS(Status))
457 {
458 KeWaitForSingleObject(&NotifyEvent, Executive, KernelMode, FALSE, NULL);
459 Status = NotifyStatus;
460 }
461
462 return Status;
463 }
464
465 /*
466 * @implemented
467 */
468 NTSTATUS
469 NTAPI
470 IoReportTargetDeviceChangeAsynchronous(IN PDEVICE_OBJECT PhysicalDeviceObject,
471 IN PVOID NotificationStructure,
472 IN PDEVICE_CHANGE_COMPLETE_CALLBACK Callback OPTIONAL,
473 IN PVOID Context OPTIONAL)
474 {
475 PINTERNAL_WORK_QUEUE_ITEM Item = NULL;
476 PTARGET_DEVICE_CUSTOM_NOTIFICATION notifyStruct = (PTARGET_DEVICE_CUSTOM_NOTIFICATION)NotificationStructure;
477
478 ASSERT(notifyStruct);
479
480 /* Check for valid PDO */
481 if (!IopIsValidPhysicalDeviceObject(PhysicalDeviceObject))
482 {
483 KeBugCheckEx(PNP_DETECTED_FATAL_ERROR, 0x2, (ULONG)PhysicalDeviceObject, 0, 0);
484 }
485
486 /* FileObject must be null. PnP will fill in it */
487 ASSERT(notifyStruct->FileObject == NULL);
488
489 /* Do not handle system PnP events */
490 if ((RtlCompareMemory(&(notifyStruct->Event), &(GUID_TARGET_DEVICE_QUERY_REMOVE), sizeof(GUID)) != sizeof(GUID)) ||
491 (RtlCompareMemory(&(notifyStruct->Event), &(GUID_TARGET_DEVICE_REMOVE_CANCELLED), sizeof(GUID)) != sizeof(GUID)) ||
492 (RtlCompareMemory(&(notifyStruct->Event), &(GUID_TARGET_DEVICE_REMOVE_COMPLETE), sizeof(GUID)) != sizeof(GUID)))
493 {
494 return STATUS_INVALID_DEVICE_REQUEST;
495 }
496
497 if (notifyStruct->Version != 1)
498 {
499 return STATUS_INVALID_DEVICE_REQUEST;
500 }
501
502 /* We need to store all the data given by the caller with the WorkItem, so use our own struct */
503 Item = ExAllocatePoolWithTag(NonPagedPool, sizeof(INTERNAL_WORK_QUEUE_ITEM), ' pP');
504 if (!Item) return STATUS_INSUFFICIENT_RESOURCES;
505
506 /* Initialize all stuff */
507 ObReferenceObject(PhysicalDeviceObject);
508 Item->NotificationStructure = notifyStruct;
509 Item->PhysicalDeviceObject = PhysicalDeviceObject;
510 Item->Callback = Callback;
511 Item->Context = Context;
512 ExInitializeWorkItem(&(Item->WorkItem), (PWORKER_THREAD_ROUTINE)IopReportTargetDeviceChangeAsyncWorker, Item);
513
514 /* Finally, queue the item, our work here is done */
515 ExQueueWorkItem(&(Item->WorkItem), DelayedWorkQueue);
516
517 return STATUS_PENDING;
518 }