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