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