- Update to r53061
[reactos.git] / drivers / serial / serial / pnp.c
1 /*
2 * COPYRIGHT: See COPYING in the top level directory
3 * PROJECT: Serial port driver
4 * FILE: drivers/dd/serial/pnp.c
5 * PURPOSE: Serial IRP_MJ_PNP operations
6 *
7 * PROGRAMMERS: Hervé Poussineau (hpoussin@reactos.org)
8 */
9 /* FIXME: call IoAcquireRemoveLock/IoReleaseRemoveLock around each I/O operation */
10
11 #define INITGUID
12 #include "serial.h"
13
14 NTSTATUS NTAPI
15 SerialAddDeviceInternal(
16 IN PDRIVER_OBJECT DriverObject,
17 IN PDEVICE_OBJECT Pdo,
18 IN UART_TYPE UartType,
19 IN PULONG pComPortNumber OPTIONAL,
20 OUT PDEVICE_OBJECT* pFdo OPTIONAL)
21 {
22 PDEVICE_OBJECT Fdo = NULL;
23 PSERIAL_DEVICE_EXTENSION DeviceExtension = NULL;
24 NTSTATUS Status;
25 WCHAR DeviceNameBuffer[32];
26 UNICODE_STRING DeviceName;
27
28 TRACE_(SERIAL, "SerialAddDeviceInternal()\n");
29
30 ASSERT(DriverObject);
31 ASSERT(Pdo);
32
33 /* Create new device object */
34 swprintf(DeviceNameBuffer, L"\\Device\\Serial%lu", IoGetConfigurationInformation()->SerialCount);
35 RtlInitUnicodeString(&DeviceName, DeviceNameBuffer);
36 Status = IoCreateDevice(DriverObject,
37 sizeof(SERIAL_DEVICE_EXTENSION),
38 &DeviceName,
39 FILE_DEVICE_SERIAL_PORT,
40 FILE_DEVICE_SECURE_OPEN,
41 FALSE,
42 &Fdo);
43 if (!NT_SUCCESS(Status))
44 {
45 WARN_(SERIAL, "IoCreateDevice() failed with status 0x%08x\n", Status);
46 Fdo = NULL;
47 goto ByeBye;
48 }
49 DeviceExtension = (PSERIAL_DEVICE_EXTENSION)Fdo->DeviceExtension;
50 RtlZeroMemory(DeviceExtension, sizeof(SERIAL_DEVICE_EXTENSION));
51
52 /* Register device interface */
53 Status = IoRegisterDeviceInterface(Pdo, &GUID_DEVINTERFACE_COMPORT, NULL, &DeviceExtension->SerialInterfaceName);
54 if (!NT_SUCCESS(Status))
55 {
56 WARN_(SERIAL, "IoRegisterDeviceInterface() failed with status 0x%08x\n", Status);
57 goto ByeBye;
58 }
59
60 DeviceExtension->SerialPortNumber = IoGetConfigurationInformation()->SerialCount++;
61 if (pComPortNumber == NULL)
62 DeviceExtension->ComPort = DeviceExtension->SerialPortNumber + 1;
63 else
64 DeviceExtension->ComPort = *pComPortNumber;
65 DeviceExtension->Pdo = Pdo;
66 DeviceExtension->PnpState = dsStopped;
67 DeviceExtension->UartType = UartType;
68 Status = InitializeCircularBuffer(&DeviceExtension->InputBuffer, 16);
69 if (!NT_SUCCESS(Status)) goto ByeBye;
70 Status = InitializeCircularBuffer(&DeviceExtension->OutputBuffer, 16);
71 if (!NT_SUCCESS(Status)) goto ByeBye;
72 IoInitializeRemoveLock(&DeviceExtension->RemoveLock, SERIAL_TAG, 0, 0);
73 KeInitializeSpinLock(&DeviceExtension->InputBufferLock);
74 KeInitializeSpinLock(&DeviceExtension->OutputBufferLock);
75 KeInitializeEvent(&DeviceExtension->InputBufferNotEmpty, NotificationEvent, FALSE);
76 KeInitializeDpc(&DeviceExtension->ReceivedByteDpc, SerialReceiveByte, DeviceExtension);
77 KeInitializeDpc(&DeviceExtension->SendByteDpc, SerialSendByte, DeviceExtension);
78 KeInitializeDpc(&DeviceExtension->CompleteIrpDpc, SerialCompleteIrp, DeviceExtension);
79 Status = IoAttachDeviceToDeviceStackSafe(Fdo, Pdo, &DeviceExtension->LowerDevice);
80 if (!NT_SUCCESS(Status))
81 {
82 WARN_(SERIAL, "IoAttachDeviceToDeviceStackSafe() failed with status 0x%08x\n", Status);
83 goto ByeBye;
84 }
85 if (DeviceExtension->LowerDevice->Flags & DO_POWER_PAGABLE)
86 Fdo->Flags |= DO_POWER_PAGABLE;
87 if (DeviceExtension->LowerDevice->Flags & DO_BUFFERED_IO)
88 Fdo->Flags |= DO_BUFFERED_IO;
89 if (DeviceExtension->LowerDevice->Flags & DO_DIRECT_IO)
90 Fdo->Flags |= DO_DIRECT_IO;
91
92 /* Choose default strategy */
93 if ((Fdo->Flags & (DO_BUFFERED_IO | DO_DIRECT_IO)) == 0)
94 Fdo->Flags |= DO_BUFFERED_IO;
95
96 Fdo->Flags &= ~DO_DEVICE_INITIALIZING;
97 if (pFdo)
98 {
99 *pFdo = Fdo;
100 }
101
102 return STATUS_SUCCESS;
103
104 ByeBye:
105 if (Fdo)
106 {
107 FreeCircularBuffer(&DeviceExtension->InputBuffer);
108 FreeCircularBuffer(&DeviceExtension->OutputBuffer);
109 IoDeleteDevice(Fdo);
110 }
111 return Status;
112 }
113
114 NTSTATUS NTAPI
115 SerialAddDevice(
116 IN PDRIVER_OBJECT DriverObject,
117 IN PDEVICE_OBJECT Pdo)
118 {
119 /* Serial.sys is a legacy driver. AddDevice is called once
120 * with a NULL Pdo just after the driver initialization.
121 * Detect this case and return success.
122 */
123 if (Pdo == NULL)
124 return STATUS_SUCCESS;
125
126 /* We have here a PDO not null. It represents a real serial
127 * port. So call the internal AddDevice function.
128 */
129 return SerialAddDeviceInternal(DriverObject, Pdo, UartUnknown, NULL, NULL);
130 }
131
132 NTSTATUS NTAPI
133 SerialPnpStartDevice(
134 IN PDEVICE_OBJECT DeviceObject,
135 IN PCM_RESOURCE_LIST ResourceList,
136 IN PCM_RESOURCE_LIST ResourceListTranslated)
137 {
138 PSERIAL_DEVICE_EXTENSION DeviceExtension;
139 WCHAR DeviceNameBuffer[32];
140 UNICODE_STRING DeviceName;
141 WCHAR LinkNameBuffer[32];
142 UNICODE_STRING LinkName;
143 WCHAR ComPortBuffer[32];
144 UNICODE_STRING ComPort;
145 ULONG Vector = 0;
146 ULONG i;
147 UCHAR IER;
148 KIRQL Dirql;
149 KAFFINITY Affinity = 0;
150 KINTERRUPT_MODE InterruptMode = Latched;
151 BOOLEAN ShareInterrupt = TRUE;
152 OBJECT_ATTRIBUTES objectAttributes;
153 PUCHAR ComPortBase;
154 UNICODE_STRING KeyName;
155 HANDLE hKey;
156 NTSTATUS Status;
157
158 DeviceExtension = (PSERIAL_DEVICE_EXTENSION)DeviceObject->DeviceExtension;
159
160 ASSERT(ResourceList);
161 ASSERT(DeviceExtension);
162 ASSERT(DeviceExtension->PnpState == dsStopped);
163
164 if (!ResourceList)
165 {
166 WARN_(SERIAL, "No allocated resources sent to driver\n");
167 return STATUS_INSUFFICIENT_RESOURCES;
168 }
169 if (ResourceList->Count != 1)
170 {
171 WARN_(SERIAL, "Wrong number of allocated resources sent to driver\n");
172 return STATUS_INSUFFICIENT_RESOURCES;
173 }
174 if (ResourceList->List[0].PartialResourceList.Version != 1
175 || ResourceList->List[0].PartialResourceList.Revision != 1
176 || ResourceListTranslated->List[0].PartialResourceList.Version != 1
177 || ResourceListTranslated->List[0].PartialResourceList.Revision != 1)
178 {
179 WARN_(SERIAL, "Revision mismatch: %u.%u != 1.1 or %u.%u != 1.1\n",
180 ResourceList->List[0].PartialResourceList.Version,
181 ResourceList->List[0].PartialResourceList.Revision,
182 ResourceListTranslated->List[0].PartialResourceList.Version,
183 ResourceListTranslated->List[0].PartialResourceList.Revision);
184 return STATUS_REVISION_MISMATCH;
185 }
186
187 DeviceExtension->BaudRate = 19200;
188 DeviceExtension->BaseAddress = 0;
189 Dirql = 0;
190 for (i = 0; i < ResourceList->List[0].PartialResourceList.Count; i++)
191 {
192 PCM_PARTIAL_RESOURCE_DESCRIPTOR PartialDescriptor = &ResourceList->List[0].PartialResourceList.PartialDescriptors[i];
193 PCM_PARTIAL_RESOURCE_DESCRIPTOR PartialDescriptorTranslated = &ResourceListTranslated->List[0].PartialResourceList.PartialDescriptors[i];
194 switch (PartialDescriptor->Type)
195 {
196 case CmResourceTypePort:
197 if (PartialDescriptor->u.Port.Length < 7)
198 return STATUS_INSUFFICIENT_RESOURCES;
199 if (DeviceExtension->BaseAddress != 0)
200 return STATUS_UNSUCCESSFUL;
201 DeviceExtension->BaseAddress = PartialDescriptor->u.Port.Start.u.LowPart;
202 break;
203 case CmResourceTypeInterrupt:
204 Dirql = (KIRQL)PartialDescriptorTranslated->u.Interrupt.Level;
205 Vector = PartialDescriptorTranslated->u.Interrupt.Vector;
206 Affinity = PartialDescriptorTranslated->u.Interrupt.Affinity;
207 if (PartialDescriptorTranslated->Flags & CM_RESOURCE_INTERRUPT_LATCHED)
208 InterruptMode = Latched;
209 else
210 InterruptMode = LevelSensitive;
211 ShareInterrupt = (PartialDescriptorTranslated->ShareDisposition == CmResourceShareShared);
212 break;
213 }
214 }
215 INFO_(SERIAL, "New COM port. Base = 0x%lx, Irql = %u\n",
216 DeviceExtension->BaseAddress, Dirql);
217 if (!DeviceExtension->BaseAddress)
218 return STATUS_INSUFFICIENT_RESOURCES;
219 if (!Dirql)
220 return STATUS_INSUFFICIENT_RESOURCES;
221 ComPortBase = ULongToPtr(DeviceExtension->BaseAddress);
222
223 /* Test if we are trying to start the serial port used for debugging */
224 INFO_(SERIAL, "Comparing addresses: KdComPortInUse: %p, ComPortBase: %p\n", KdComPortInUse, ComPortBase);
225 if (KdComPortInUse == ComPortBase)
226 {
227 INFO_(SERIAL, "Failing IRP_MN_START_DEVICE as this serial port is used for debugging\n");
228 return STATUS_INSUFFICIENT_RESOURCES;
229 }
230
231 if (DeviceExtension->UartType == UartUnknown)
232 DeviceExtension->UartType = SerialDetectUartType(ComPortBase);
233
234 /* Get current settings */
235 DeviceExtension->MCR = READ_PORT_UCHAR(SER_MCR(ComPortBase));
236 DeviceExtension->MSR = READ_PORT_UCHAR(SER_MSR(ComPortBase));
237 DeviceExtension->WaitMask = 0;
238
239 /* Set baud rate */
240 Status = SerialSetBaudRate(DeviceExtension, DeviceExtension->BaudRate);
241 if (!NT_SUCCESS(Status))
242 {
243 WARN_(SERIAL, "SerialSetBaudRate() failed with status 0x%08x\n", Status);
244 return Status;
245 }
246
247 /* Set line control */
248 DeviceExtension->SerialLineControl.StopBits = STOP_BIT_1;
249 DeviceExtension->SerialLineControl.Parity = NO_PARITY;
250 DeviceExtension->SerialLineControl.WordLength = 8;
251 Status = SerialSetLineControl(DeviceExtension, &DeviceExtension->SerialLineControl);
252 if (!NT_SUCCESS(Status))
253 {
254 WARN_(SERIAL, "SerialSetLineControl() failed with status 0x%08x\n", Status);
255 return Status;
256 }
257
258 /* Clear receive/transmit buffers */
259 if (DeviceExtension->UartType >= Uart16550A)
260 {
261 /* 16550 UARTs also have FIFO queues, but they are unusable due to a bug */
262 WRITE_PORT_UCHAR(SER_FCR(ComPortBase),
263 SR_FCR_CLEAR_RCVR | SR_FCR_CLEAR_XMIT);
264 }
265
266 /* Create link \DosDevices\COMX -> \Device\SerialX */
267 swprintf(DeviceNameBuffer, L"\\Device\\Serial%lu", DeviceExtension->SerialPortNumber);
268 swprintf(LinkNameBuffer, L"\\DosDevices\\COM%lu", DeviceExtension->ComPort);
269 swprintf(ComPortBuffer, L"COM%lu", DeviceExtension->ComPort);
270 RtlInitUnicodeString(&DeviceName, DeviceNameBuffer);
271 RtlInitUnicodeString(&LinkName, LinkNameBuffer);
272 RtlInitUnicodeString(&ComPort, ComPortBuffer);
273 Status = IoCreateSymbolicLink(&LinkName, &DeviceName);
274 if (!NT_SUCCESS(Status))
275 {
276 WARN_(SERIAL, "IoCreateSymbolicLink() failed with status 0x%08x\n", Status);
277 return Status;
278 }
279
280 /* Connect interrupt and enable them */
281 Status = IoConnectInterrupt(
282 &DeviceExtension->Interrupt, SerialInterruptService,
283 DeviceObject, NULL,
284 Vector, Dirql, Dirql,
285 InterruptMode, ShareInterrupt,
286 Affinity, FALSE);
287 if (!NT_SUCCESS(Status))
288 {
289 WARN_(SERIAL, "IoConnectInterrupt() failed with status 0x%08x\n", Status);
290 IoSetDeviceInterfaceState(&DeviceExtension->SerialInterfaceName, FALSE);
291 IoDeleteSymbolicLink(&LinkName);
292 return Status;
293 }
294
295 /* Write an entry value under HKLM\HARDWARE\DeviceMap\SERIALCOMM */
296 /* This step is not mandatory, so don't exit in case of error */
297 RtlInitUnicodeString(&KeyName, L"\\Registry\\Machine\\HARDWARE\\DeviceMap\\SERIALCOMM");
298 InitializeObjectAttributes(&objectAttributes, &KeyName, OBJ_CASE_INSENSITIVE, NULL, NULL);
299 Status = ZwCreateKey(&hKey, KEY_SET_VALUE, &objectAttributes, 0, NULL, REG_OPTION_VOLATILE, NULL);
300 if (NT_SUCCESS(Status))
301 {
302 /* Key = \Device\Serialx, Value = COMx */
303 ZwSetValueKey(hKey, &DeviceName, 0, REG_SZ, ComPortBuffer, ComPort.Length + sizeof(WCHAR));
304 ZwClose(hKey);
305 }
306
307 DeviceExtension->PnpState = dsStarted;
308
309 /* Activate interrupt modes */
310 IER = READ_PORT_UCHAR(SER_IER(ComPortBase));
311 IER |= SR_IER_DATA_RECEIVED | SR_IER_THR_EMPTY | SR_IER_LSR_CHANGE | SR_IER_MSR_CHANGE;
312 WRITE_PORT_UCHAR(SER_IER(ComPortBase), IER);
313
314 /* Activate DTR, RTS */
315 DeviceExtension->MCR |= SR_MCR_DTR | SR_MCR_RTS;
316 WRITE_PORT_UCHAR(SER_MCR(ComPortBase), DeviceExtension->MCR);
317
318 /* Activate serial interface */
319 IoSetDeviceInterfaceState(&DeviceExtension->SerialInterfaceName, TRUE);
320 /* We don't really care if the call succeeded or not... */
321
322 return STATUS_SUCCESS;
323 }
324
325 NTSTATUS NTAPI
326 SerialPnp(
327 IN PDEVICE_OBJECT DeviceObject,
328 IN PIRP Irp)
329 {
330 ULONG MinorFunction;
331 PIO_STACK_LOCATION Stack;
332 ULONG_PTR Information = 0;
333 NTSTATUS Status;
334
335 Stack = IoGetCurrentIrpStackLocation(Irp);
336 MinorFunction = Stack->MinorFunction;
337
338 switch (MinorFunction)
339 {
340 /* FIXME: do all these minor functions
341 IRP_MN_QUERY_REMOVE_DEVICE 0x1
342 IRP_MN_REMOVE_DEVICE 0x2
343 {
344 TRACE_(SERIAL, "IRP_MJ_PNP / IRP_MN_REMOVE_DEVICE\n");
345 IoAcquireRemoveLock
346 IoReleaseRemoveLockAndWait
347 pass request to DeviceExtension-LowerDriver
348 disable interface
349 IoDeleteDevice(Fdo) and/or IoDetachDevice
350 break;
351 }
352 IRP_MN_CANCEL_REMOVE_DEVICE 0x3
353 IRP_MN_STOP_DEVICE 0x4
354 IRP_MN_QUERY_STOP_DEVICE 0x5
355 IRP_MN_CANCEL_STOP_DEVICE 0x6
356 IRP_MN_QUERY_DEVICE_RELATIONS / BusRelations (optional) 0x7
357 IRP_MN_QUERY_DEVICE_RELATIONS / RemovalRelations (optional) 0x7
358 IRP_MN_QUERY_INTERFACE (optional) 0x8
359 IRP_MN_QUERY_CAPABILITIES (optional) 0x9
360 IRP_MN_FILTER_RESOURCE_REQUIREMENTS (optional) 0xd
361 IRP_MN_QUERY_PNP_DEVICE_STATE (optional) 0x14
362 IRP_MN_DEVICE_USAGE_NOTIFICATION (required or optional) 0x16
363 IRP_MN_SURPRISE_REMOVAL 0x17
364 */
365 case IRP_MN_START_DEVICE: /* 0x0 */
366 {
367 TRACE_(SERIAL, "IRP_MJ_PNP / IRP_MN_START_DEVICE\n");
368
369 ASSERT(((PSERIAL_DEVICE_EXTENSION)DeviceObject->DeviceExtension)->PnpState == dsStopped);
370
371 /* Call lower driver */
372 Status = ForwardIrpAndWait(DeviceObject, Irp);
373 if (NT_SUCCESS(Status))
374 Status = SerialPnpStartDevice(
375 DeviceObject,
376 Stack->Parameters.StartDevice.AllocatedResources,
377 Stack->Parameters.StartDevice.AllocatedResourcesTranslated);
378 break;
379 }
380 case IRP_MN_QUERY_DEVICE_RELATIONS: /* (optional) 0x7 */
381 {
382 switch (Stack->Parameters.QueryDeviceRelations.Type)
383 {
384 case BusRelations:
385 {
386 TRACE_(SERIAL, "IRP_MJ_PNP / IRP_MN_QUERY_DEVICE_RELATIONS / BusRelations\n");
387 return ForwardIrpAndForget(DeviceObject, Irp);
388 }
389 case RemovalRelations:
390 {
391 TRACE_(SERIAL, "IRP_MJ_PNP / IRP_MN_QUERY_DEVICE_RELATIONS / RemovalRelations\n");
392 return ForwardIrpAndForget(DeviceObject, Irp);
393 }
394 default:
395 TRACE_(SERIAL, "IRP_MJ_PNP / IRP_MN_QUERY_DEVICE_RELATIONS / Unknown type 0x%lx\n",
396 Stack->Parameters.QueryDeviceRelations.Type);
397 return ForwardIrpAndForget(DeviceObject, Irp);
398 }
399 break;
400 }
401 case IRP_MN_FILTER_RESOURCE_REQUIREMENTS: /* (optional) 0xd */
402 {
403 TRACE_(SERIAL, "IRP_MJ_PNP / IRP_MN_FILTER_RESOURCE_REQUIREMENTS\n");
404 return ForwardIrpAndForget(DeviceObject, Irp);
405 }
406 default:
407 {
408 TRACE_(SERIAL, "Unknown minor function 0x%x\n", MinorFunction);
409 return ForwardIrpAndForget(DeviceObject, Irp);
410 }
411 }
412
413 Irp->IoStatus.Information = Information;
414 Irp->IoStatus.Status = Status;
415 IoCompleteRequest(Irp, IO_NO_INCREMENT);
416 return Status;
417 }