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