prevent buffer overflow, LoadString accepts the size of the buffer in TCHARs, not...
[reactos.git] / reactos / drivers / dd / serial / pnp.c
1 /* $Id:
2 *
3 * COPYRIGHT: See COPYING in the top level directory
4 * PROJECT: ReactOS kernel
5 * FILE: drivers/dd/serial/pnp.c
6 * PURPOSE: Serial IRP_MJ_PNP operations
7 *
8 * PROGRAMMERS: Hervé Poussineau (poussine@freesurf.fr)
9 */
10 /* FIXME: call IoAcquireRemoveLock/IoReleaseRemoveLock around each I/O operation */
11
12 #define INITGUID
13 #define NDEBUG
14 #include "serial.h"
15
16 NTSTATUS STDCALL
17 SerialAddDeviceInternal(
18 IN PDRIVER_OBJECT DriverObject,
19 IN PDEVICE_OBJECT Pdo,
20 IN UART_TYPE UartType,
21 IN PULONG pComPortNumber OPTIONAL,
22 OUT PDEVICE_OBJECT* pFdo OPTIONAL)
23 {
24 PDEVICE_OBJECT Fdo = NULL;
25 PSERIAL_DEVICE_EXTENSION DeviceExtension = NULL;
26 NTSTATUS Status;
27 WCHAR DeviceNameBuffer[32];
28 UNICODE_STRING DeviceName;
29 static ULONG DeviceNumber = 0;
30 static ULONG ComPortNumber = 1;
31
32 DPRINT("Serial: SerialAddDeviceInternal called\n");
33
34 ASSERT(DeviceObject);
35 ASSERT(Pdo);
36
37 /* Create new device object */
38 swprintf(DeviceNameBuffer, L"\\Device\\Serial%lu", DeviceNumber);
39 RtlInitUnicodeString(&DeviceName, DeviceNameBuffer);
40 Status = IoCreateDevice(DriverObject,
41 sizeof(SERIAL_DEVICE_EXTENSION),
42 &DeviceName,
43 FILE_DEVICE_SERIAL_PORT,
44 FILE_DEVICE_SECURE_OPEN,
45 FALSE,
46 &Fdo);
47 if (!NT_SUCCESS(Status))
48 {
49 DPRINT("Serial: IoCreateDevice() failed with status 0x%08x\n", Status);
50 Fdo = NULL;
51 goto ByeBye;
52 }
53 DeviceExtension = (PSERIAL_DEVICE_EXTENSION)Fdo->DeviceExtension;
54 RtlZeroMemory(DeviceExtension, sizeof(SERIAL_DEVICE_EXTENSION));
55
56 /* Register device interface */
57 Status = IoRegisterDeviceInterface(Pdo, &GUID_DEVINTERFACE_COMPORT, NULL, &DeviceExtension->SerialInterfaceName);
58 if (!NT_SUCCESS(Status))
59 {
60 DPRINT("Serial: IoRegisterDeviceInterface() failed with status 0x%08x\n", Status);
61 goto ByeBye;
62 }
63
64 DeviceExtension->SerialPortNumber = DeviceNumber++;
65 if (pComPortNumber == NULL)
66 DeviceExtension->ComPort = ComPortNumber++;
67 else
68 DeviceExtension->ComPort = *pComPortNumber;
69 DeviceExtension->Pdo = Pdo;
70 DeviceExtension->PnpState = dsStopped;
71 DeviceExtension->UartType = UartType;
72 Status = InitializeCircularBuffer(&DeviceExtension->InputBuffer, 16);
73 if (!NT_SUCCESS(Status)) goto ByeBye;
74 Status = InitializeCircularBuffer(&DeviceExtension->OutputBuffer, 16);
75 if (!NT_SUCCESS(Status)) goto ByeBye;
76 IoInitializeRemoveLock(&DeviceExtension->RemoveLock, SERIAL_TAG, 0, 0);
77 KeInitializeSpinLock(&DeviceExtension->InputBufferLock);
78 KeInitializeSpinLock(&DeviceExtension->OutputBufferLock);
79 KeInitializeEvent(&DeviceExtension->InputBufferNotEmpty, NotificationEvent, FALSE);
80 KeInitializeDpc(&DeviceExtension->ReceivedByteDpc, SerialReceiveByte, DeviceExtension);
81 KeInitializeDpc(&DeviceExtension->SendByteDpc, SerialSendByte, DeviceExtension);
82 Fdo->Flags |= DO_POWER_PAGABLE;
83 Status = IoAttachDeviceToDeviceStackSafe(Fdo, Pdo, &DeviceExtension->LowerDevice);
84 if (!NT_SUCCESS(Status))
85 {
86 DPRINT("Serial: IoAttachDeviceToDeviceStackSafe() failed with status 0x%08x\n", Status);
87 goto ByeBye;
88 }
89 Fdo->Flags |= DO_BUFFERED_IO;
90 Fdo->Flags &= ~DO_DEVICE_INITIALIZING;
91 if (pFdo)
92 {
93 *pFdo = Fdo;
94 }
95
96 return STATUS_SUCCESS;
97
98 ByeBye:
99 if (Fdo)
100 {
101 FreeCircularBuffer(&DeviceExtension->InputBuffer);
102 FreeCircularBuffer(&DeviceExtension->OutputBuffer);
103 IoDeleteDevice(Fdo);
104 }
105 return Status;
106 }
107
108 NTSTATUS STDCALL
109 SerialAddDevice(
110 IN PDRIVER_OBJECT DriverObject,
111 IN PDEVICE_OBJECT Pdo)
112 {
113 /* Serial.sys is a legacy driver. AddDevice is called once
114 * with a NULL Pdo just after the driver initialization.
115 * Detect this case and return success.
116 */
117 if (Pdo == NULL)
118 return STATUS_SUCCESS;
119
120 /* We have here a PDO not null. It represents a real serial
121 * port. So call the internal AddDevice function.
122 */
123 return SerialAddDeviceInternal(DriverObject, Pdo, UartUnknown, NULL, NULL);
124 }
125
126 NTSTATUS STDCALL
127 SerialPnpStartDevice(
128 IN PDEVICE_OBJECT DeviceObject,
129 IN PCM_RESOURCE_LIST ResourceList)
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 | SERIAL_BAUD_USER;
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 switch (PartialDescriptor->Type)
166 {
167 case CmResourceTypePort:
168 if (PartialDescriptor->u.Port.Length < 8)
169 return STATUS_INSUFFICIENT_RESOURCES;
170 if (DeviceExtension->BaseAddress != 0)
171 return STATUS_UNSUCCESSFUL;
172 DeviceExtension->BaseAddress = PartialDescriptor->u.Port.Start.u.LowPart;
173 break;
174 case CmResourceTypeInterrupt:
175 if (Dirql != 0)
176 return STATUS_UNSUCCESSFUL;
177 Dirql = (KIRQL)PartialDescriptor->u.Interrupt.Level;
178 Vector = PartialDescriptor->u.Interrupt.Vector;
179 Affinity = PartialDescriptor->u.Interrupt.Affinity;
180 if (PartialDescriptor->Flags & CM_RESOURCE_INTERRUPT_LATCHED)
181 InterruptMode = Latched;
182 else
183 InterruptMode = LevelSensitive;
184 ShareInterrupt = (PartialDescriptor->ShareDisposition == CmResourceShareShared);
185 break;
186 }
187 }
188 }
189 DPRINT("Serial: New COM port. Base = 0x%lx, Irql = %u\n",
190 DeviceExtension->BaseAddress, Dirql);
191 if (!DeviceExtension->BaseAddress)
192 return STATUS_INSUFFICIENT_RESOURCES;
193 if (!Dirql)
194 return STATUS_INSUFFICIENT_RESOURCES;
195 ComPortBase = (PUCHAR)DeviceExtension->BaseAddress;
196
197 if (DeviceExtension->UartType == UartUnknown)
198 DeviceExtension->UartType = SerialDetectUartType(ComPortBase);
199
200 /* Get current settings */
201 DeviceExtension->MCR = READ_PORT_UCHAR(SER_MCR(ComPortBase));
202 DeviceExtension->MSR = READ_PORT_UCHAR(SER_MSR(ComPortBase));
203 DeviceExtension->WaitMask = 0;
204
205 /* Set baud rate */
206 Status = SerialSetBaudRate(DeviceExtension, DeviceExtension->BaudRate);
207 if (!NT_SUCCESS(Status))
208 {
209 DPRINT("Serial: SerialSetBaudRate() failed with status 0x%08x\n", Status);
210 return Status;
211 }
212
213 /* Set line control */
214 DeviceExtension->SerialLineControl.StopBits = STOP_BIT_1;
215 DeviceExtension->SerialLineControl.Parity = NO_PARITY;
216 DeviceExtension->SerialLineControl.WordLength = 8;
217 Status = SerialSetLineControl(DeviceExtension, &DeviceExtension->SerialLineControl);
218 if (!NT_SUCCESS(Status))
219 {
220 DPRINT("Serial: SerialSetLineControl() failed with status 0x%08x\n", Status);
221 return Status;
222 }
223
224 /* Clear receive/transmit buffers */
225 if (DeviceExtension->UartType >= Uart16550A)
226 {
227 /* 16550 UARTs also have FIFO queues, but they are unusable due to a bug */
228 WRITE_PORT_UCHAR(SER_FCR(ComPortBase),
229 SR_FCR_CLEAR_RCVR | SR_FCR_CLEAR_XMIT);
230 }
231
232 /* Create link \DosDevices\COMX -> \Device\SerialX */
233 swprintf(DeviceNameBuffer, L"\\Device\\Serial%lu", DeviceExtension->SerialPortNumber);
234 swprintf(LinkNameBuffer, L"\\DosDevices\\COM%lu", DeviceExtension->ComPort);
235 swprintf(ComPortBuffer, L"COM%lu", DeviceExtension->ComPort);
236 RtlInitUnicodeString(&DeviceName, DeviceNameBuffer);
237 RtlInitUnicodeString(&LinkName, LinkNameBuffer);
238 RtlInitUnicodeString(&ComPort, ComPortBuffer);
239 Status = IoCreateSymbolicLink(&LinkName, &DeviceName);
240 if (!NT_SUCCESS(Status))
241 {
242 DPRINT("Serial: IoCreateSymbolicLink() failed with status 0x%08x\n", Status);
243 return Status;
244 }
245
246 /* Activate serial interface */
247 Status = IoSetDeviceInterfaceState(&DeviceExtension->SerialInterfaceName, TRUE);
248 if (!NT_SUCCESS(Status))
249 {
250 DPRINT("Serial: IoSetDeviceInterfaceState() failed with status 0x%08x\n", Status);
251 IoDeleteSymbolicLink(&LinkName);
252 return Status;
253 }
254
255 /* Connect interrupt and enable them */
256 Status = IoConnectInterrupt(
257 &DeviceExtension->Interrupt, SerialInterruptService,
258 DeviceObject, NULL,
259 Vector, Dirql, Dirql,
260 InterruptMode, ShareInterrupt,
261 Affinity, FALSE);
262 if (!NT_SUCCESS(Status))
263 {
264 DPRINT("Serial: IoConnectInterrupt() failed with status 0x%08x\n", Status);
265 IoSetDeviceInterfaceState(&DeviceExtension->SerialInterfaceName, FALSE);
266 IoDeleteSymbolicLink(&LinkName);
267 return Status;
268 }
269
270 /* Write an entry value under HKLM\HARDWARE\DeviceMap\SERIALCOMM */
271 /* This step is not mandatory, so don't exit in case of error */
272 RtlInitUnicodeString(&KeyName, L"\\Registry\\Machine\\HARDWARE\\DeviceMap\\SERIALCOMM");
273 InitializeObjectAttributes(&objectAttributes, &KeyName, OBJ_CASE_INSENSITIVE, NULL, NULL);
274 Status = ZwCreateKey(&hKey, KEY_SET_VALUE, &objectAttributes, 0, NULL, REG_OPTION_VOLATILE, NULL);
275 if (NT_SUCCESS(Status))
276 {
277 /* Key = \Device\Serialx, Value = COMx */
278 ZwSetValueKey(hKey, &DeviceName, 0, REG_SZ, &ComPortBuffer, ComPort.Length + sizeof(WCHAR));
279 ZwClose(hKey);
280 }
281
282 DeviceExtension->PnpState = dsStarted;
283
284 /* Activate interrupt modes */
285 IER = READ_PORT_UCHAR(SER_IER(ComPortBase));
286 IER |= SR_IER_DATA_RECEIVED | SR_IER_THR_EMPTY | SR_IER_LSR_CHANGE | SR_IER_MSR_CHANGE;
287 WRITE_PORT_UCHAR(SER_IER(ComPortBase), IER);
288
289 /* Activate DTR, RTS */
290 DeviceExtension->MCR |= SR_MCR_DTR | SR_MCR_RTS;
291 WRITE_PORT_UCHAR(SER_MCR(ComPortBase), DeviceExtension->MCR);
292
293 return STATUS_SUCCESS;
294 }
295
296 NTSTATUS STDCALL
297 SerialPnp(
298 IN PDEVICE_OBJECT DeviceObject,
299 IN PIRP Irp)
300 {
301 ULONG MinorFunction;
302 PIO_STACK_LOCATION Stack;
303 ULONG_PTR Information = 0;
304 NTSTATUS Status;
305
306 Stack = IoGetCurrentIrpStackLocation(Irp);
307 MinorFunction = Stack->MinorFunction;
308
309 switch (MinorFunction)
310 {
311 case IRP_MN_START_DEVICE:
312 {
313 BOOLEAN ConflictDetected;
314 DPRINT("Serial: IRP_MJ_PNP / IRP_MN_START_DEVICE\n");
315
316 /* FIXME: first HACK: PnP manager can send multiple
317 * IRP_MN_START_DEVICE for one device
318 */
319 if (((PSERIAL_DEVICE_EXTENSION)DeviceObject->DeviceExtension)->PnpState != dsStopped)
320 {
321 DPRINT1("Serial: device already started. Ignoring this irp!\n");
322 Status = STATUS_SUCCESS;
323 break;
324 }
325 /* FIXME: second HACK: verify that we have some allocated resources.
326 * It seems not to be always the case on some hardware
327 */
328 if (Stack->Parameters.StartDevice.AllocatedResources == NULL)
329 {
330 DPRINT1("Serial: no allocated resources. Can't start COM%lu\n",
331 ((PSERIAL_DEVICE_EXTENSION)DeviceObject->DeviceExtension)->ComPort);
332 Status = STATUS_INSUFFICIENT_RESOURCES;
333 break;
334 }
335 /* FIXME: third HACK: verify that we don't have resource conflict,
336 * because PnP manager doesn't do it automatically
337 */
338 Status = IoReportResourceForDetection(
339 DeviceObject->DriverObject, Stack->Parameters.StartDevice.AllocatedResources, 0,
340 NULL, NULL, 0,
341 &ConflictDetected);
342 if (!NT_SUCCESS(Status))
343 {
344 Irp->IoStatus.Information = 0;
345 Irp->IoStatus.Status = Status;
346 IoCompleteRequest(Irp, IO_NO_INCREMENT);
347 return Status;
348 }
349
350 /* Call lower driver */
351 Status = ForwardIrpAndWait(DeviceObject, Irp);
352 if (NT_SUCCESS(Status))
353 Status = SerialPnpStartDevice(
354 DeviceObject,
355 Stack->Parameters.StartDevice.AllocatedResources);
356 break;
357 }
358 /* IRP_MN_QUERY_STOP_DEVICE (FIXME: required) */
359 /* IRP_MN_STOP_DEVICE (FIXME: required) */
360 /* IRP_MN_CANCEL_STOP_DEVICE (FIXME: required) */
361 /* IRP_MN_QUERY_REMOVE_DEVICE (FIXME: required) */
362 /* case IRP_MN_REMOVE_DEVICE (FIXME: required) */
363 /*{
364 DPRINT("Serial: IRP_MJ_PNP / IRP_MN_REMOVE_DEVICE\n");
365 IoAcquireRemoveLock
366 IoReleaseRemoveLockAndWait
367 pass request to DeviceExtension-LowerDriver
368 disable interface
369 IoDeleteDevice(Fdo) and/or IoDetachDevice
370 break;
371 }*/
372 /* IRP_MN_CANCEL_REMOVE_DEVICE (FIXME: required) */
373 /* IRP_MN_SURPRISE_REMOVAL (FIXME: required) */
374 /* IRP_MN_QUERY_CAPABILITIES (optional) */
375 /* IRP_MN_QUERY_PNP_DEVICE_STATE (optional) */
376 /* IRP_MN_FILTER_RESOURCE_REQUIREMENTS (optional) */
377 /* IRP_MN_DEVICE_USAGE_NOTIFICATION (FIXME: required or optional ???) */
378 /* IRP_MN_QUERY_DEVICE_RELATIONS / BusRelations (optional) */
379 /* IRP_MN_QUERY_DEVICE_RELATIONS / RemovalRelations (optional) */
380 /* IRP_MN_QUERY_INTERFACE (optional) */
381 default:
382 {
383 DPRINT1("Serial: unknown minor function 0x%x\n", MinorFunction);
384 return ForwardIrpAndForget(DeviceObject, Irp);
385 }
386 }
387
388 Irp->IoStatus.Information = Information;
389 Irp->IoStatus.Status = Status;
390 IoCompleteRequest(Irp, IO_NO_INCREMENT);
391 return Status;
392 }