Synchronize with trunk revision 59636 (just before Alex's CreateProcess revamp).
[reactos.git] / drivers / serial / serial / rw.c
1 /*
2 * COPYRIGHT: See COPYING in the top level directory
3 * PROJECT: Serial port driver
4 * FILE: drivers/dd/serial/create.c
5 * PURPOSE: Serial IRP_MJ_READ/IRP_MJ_WRITE operations
6 *
7 * PROGRAMMERS: Hervé Poussineau (hpoussin@reactos.org)
8 */
9
10 #include "serial.h"
11
12 static IO_WORKITEM_ROUTINE SerialReadWorkItem;
13
14 static PVOID
15 SerialGetUserBuffer(IN PIRP Irp)
16 {
17 ASSERT(Irp);
18
19 if (Irp->MdlAddress)
20 return Irp->MdlAddress;
21 else
22 return Irp->AssociatedIrp.SystemBuffer;
23 }
24
25 static VOID
26 ReadBytes(
27 IN PDEVICE_OBJECT DeviceObject,
28 IN PIRP Irp,
29 PWORKITEM_DATA WorkItemData)
30 {
31 PSERIAL_DEVICE_EXTENSION DeviceExtension;
32 ULONG Length;
33 PUCHAR Buffer;
34 UCHAR ReceivedByte;
35 KTIMER TotalTimeoutTimer;
36 KIRQL Irql;
37 ULONG ObjectCount;
38 PVOID ObjectsArray[2];
39 ULONG_PTR Information = 0;
40 NTSTATUS Status;
41
42 ASSERT(DeviceObject);
43 ASSERT(WorkItemData);
44
45 DeviceExtension = (PSERIAL_DEVICE_EXTENSION)DeviceObject->DeviceExtension;
46 Length = IoGetCurrentIrpStackLocation(Irp)->Parameters.Read.Length;
47 Buffer = SerialGetUserBuffer(Irp);
48
49 INFO_(SERIAL, "UseIntervalTimeout = %s, IntervalTimeout = %lu\n",
50 WorkItemData->UseIntervalTimeout ? "YES" : "NO",
51 WorkItemData->UseIntervalTimeout ? WorkItemData->IntervalTimeout.QuadPart : 0);
52 INFO_(SERIAL, "UseTotalTimeout = %s\n",
53 WorkItemData->UseTotalTimeout ? "YES" : "NO");
54
55 ObjectCount = 1;
56 ObjectsArray[0] = &DeviceExtension->InputBufferNotEmpty;
57 if (WorkItemData->UseTotalTimeout)
58 {
59 KeInitializeTimer(&TotalTimeoutTimer);
60 KeSetTimer(&TotalTimeoutTimer, WorkItemData->TotalTimeoutTime, NULL);
61 ObjectsArray[ObjectCount] = &TotalTimeoutTimer;
62 ObjectCount++;
63 }
64
65 /* while buffer is not fully filled */
66 while (Length > 0)
67 {
68 /* read already received bytes from buffer */
69 KeAcquireSpinLock(&DeviceExtension->InputBufferLock, &Irql);
70 while (!IsCircularBufferEmpty(&DeviceExtension->InputBuffer)
71 && Length > 0)
72 {
73 PopCircularBufferEntry(&DeviceExtension->InputBuffer, &ReceivedByte);
74 INFO_(SERIAL, "Reading byte from buffer: 0x%02x\n", ReceivedByte);
75
76 Buffer[Information++] = ReceivedByte;
77 Length--;
78 }
79 KeClearEvent(&DeviceExtension->InputBufferNotEmpty);
80 KeReleaseSpinLock(&DeviceExtension->InputBufferLock, Irql);
81
82 if (WorkItemData->DontWait
83 && !(WorkItemData->ReadAtLeastOneByte && Information == 0))
84 {
85 INFO_(SERIAL, "Buffer empty. Don't wait more bytes\n");
86 break;
87 }
88
89 Status = KeWaitForMultipleObjects(
90 ObjectCount,
91 ObjectsArray,
92 WaitAny,
93 Executive,
94 KernelMode,
95 FALSE,
96 (WorkItemData->UseIntervalTimeout && Information > 0) ? &WorkItemData->IntervalTimeout : NULL,
97 NULL);
98
99 if (Status == STATUS_TIMEOUT /* interval timeout */
100 || Status == STATUS_WAIT_1) /* total timeout */
101 {
102 TRACE_(SERIAL, "Timeout when reading bytes. Status = 0x%08lx\n", Status);
103 break;
104 }
105 }
106
107 /* stop total timeout timer */
108 if (WorkItemData->UseTotalTimeout)
109 KeCancelTimer(&TotalTimeoutTimer);
110
111 Irp->IoStatus.Information = Information;
112 if (Information == 0)
113 Irp->IoStatus.Status = STATUS_TIMEOUT;
114 else
115 Irp->IoStatus.Status = STATUS_SUCCESS;
116 }
117
118 static VOID NTAPI
119 SerialReadWorkItem(
120 IN PDEVICE_OBJECT DeviceObject,
121 IN PVOID pWorkItemData /* real type PWORKITEM_DATA */)
122 {
123 PWORKITEM_DATA WorkItemData;
124 PIRP Irp;
125
126 TRACE_(SERIAL, "SerialReadWorkItem() called\n");
127
128 WorkItemData = (PWORKITEM_DATA)pWorkItemData;
129 Irp = WorkItemData->Irp;
130
131 ReadBytes(DeviceObject, Irp, WorkItemData);
132
133 IoCompleteRequest(Irp, IO_NO_INCREMENT);
134
135 IoFreeWorkItem(WorkItemData->IoWorkItem);
136 ExFreePoolWithTag(pWorkItemData, SERIAL_TAG);
137 }
138
139 NTSTATUS NTAPI
140 SerialRead(
141 IN PDEVICE_OBJECT DeviceObject,
142 IN PIRP Irp)
143 {
144 PIO_STACK_LOCATION Stack;
145 PSERIAL_DEVICE_EXTENSION DeviceExtension;
146 ULONG Length;
147 PUCHAR Buffer;
148 PWORKITEM_DATA WorkItemData;
149 PIO_WORKITEM WorkItem;
150 NTSTATUS Status;
151
152 TRACE_(SERIAL, "IRP_MJ_READ\n");
153
154 Stack = IoGetCurrentIrpStackLocation(Irp);
155 Length = Stack->Parameters.Read.Length;
156 Buffer = SerialGetUserBuffer(Irp);
157 DeviceExtension = (PSERIAL_DEVICE_EXTENSION)DeviceObject->DeviceExtension;
158
159 if (Stack->Parameters.Read.ByteOffset.QuadPart != 0 || Buffer == NULL)
160 {
161 Status = STATUS_INVALID_PARAMETER;
162 goto ByeBye;
163 }
164
165 if (Length == 0)
166 {
167 Status = STATUS_SUCCESS;
168 goto ByeBye;
169 }
170
171 /* Allocate memory for parameters */
172 WorkItemData = ExAllocatePoolWithTag(PagedPool, sizeof(WORKITEM_DATA), SERIAL_TAG);
173 if (!WorkItemData)
174 {
175 Status = STATUS_INSUFFICIENT_RESOURCES;
176 goto ByeBye;
177 }
178 RtlZeroMemory(WorkItemData, sizeof(WORKITEM_DATA));
179 WorkItemData->Irp = Irp;
180
181 /* Calculate time outs */
182 if (DeviceExtension->SerialTimeOuts.ReadIntervalTimeout == INFINITE &&
183 DeviceExtension->SerialTimeOuts.ReadTotalTimeoutMultiplier == INFINITE &&
184 DeviceExtension->SerialTimeOuts.ReadTotalTimeoutConstant > 0 &&
185 DeviceExtension->SerialTimeOuts.ReadTotalTimeoutConstant < INFINITE)
186 {
187 /* read at least one byte, and at most bytes already received */
188 WorkItemData->DontWait = TRUE;
189 WorkItemData->ReadAtLeastOneByte = TRUE;
190 }
191 else if (DeviceExtension->SerialTimeOuts.ReadIntervalTimeout == INFINITE &&
192 DeviceExtension->SerialTimeOuts.ReadTotalTimeoutConstant == 0 &&
193 DeviceExtension->SerialTimeOuts.ReadTotalTimeoutMultiplier == 0)
194 {
195 /* read only bytes that are already in buffer */
196 WorkItemData->DontWait = TRUE;
197 }
198 else
199 {
200 /* use timeouts */
201 if (DeviceExtension->SerialTimeOuts.ReadIntervalTimeout != 0)
202 {
203 WorkItemData->UseIntervalTimeout = TRUE;
204 WorkItemData->IntervalTimeout.QuadPart = DeviceExtension->SerialTimeOuts.ReadIntervalTimeout;
205 }
206 if (DeviceExtension->SerialTimeOuts.ReadTotalTimeoutMultiplier != 0 ||
207 DeviceExtension->SerialTimeOuts.ReadTotalTimeoutConstant != 0)
208 {
209 ULONG TotalTimeout;
210 LARGE_INTEGER SystemTime;
211
212 WorkItemData->UseTotalTimeout = TRUE;
213 TotalTimeout = DeviceExtension->SerialTimeOuts.ReadTotalTimeoutConstant +
214 DeviceExtension->SerialTimeOuts.ReadTotalTimeoutMultiplier * Length;
215 KeQuerySystemTime(&SystemTime);
216 WorkItemData->TotalTimeoutTime.QuadPart = SystemTime.QuadPart +
217 TotalTimeout * 10000;
218 }
219 }
220
221 /* Pend IRP */
222 WorkItem = IoAllocateWorkItem(DeviceObject);
223 if (WorkItem)
224 {
225 WorkItemData->IoWorkItem = WorkItem;
226 IoMarkIrpPending(Irp);
227 IoQueueWorkItem(WorkItem, SerialReadWorkItem, DelayedWorkQueue, WorkItemData);
228 return STATUS_PENDING;
229 }
230
231 /* Insufficient resources, we can't pend the Irp */
232 INFO_(SERIAL, "Insufficient resources\n");
233 Status = IoAcquireRemoveLock(&DeviceExtension->RemoveLock, ULongToPtr(DeviceExtension->ComPort));
234 if (!NT_SUCCESS(Status))
235 {
236 ExFreePoolWithTag(WorkItemData, SERIAL_TAG);
237 goto ByeBye;
238 }
239 ReadBytes(DeviceObject, Irp, WorkItemData);
240 Status = Irp->IoStatus.Status;
241
242 IoReleaseRemoveLock(&DeviceExtension->RemoveLock, ULongToPtr(DeviceExtension->ComPort));
243
244 ByeBye:
245 Irp->IoStatus.Status = Status;
246 IoCompleteRequest(Irp, IO_NO_INCREMENT);
247 return Status;
248 }
249
250 NTSTATUS NTAPI
251 SerialWrite(
252 IN PDEVICE_OBJECT DeviceObject,
253 IN PIRP Irp)
254 {
255 PIO_STACK_LOCATION Stack;
256 PSERIAL_DEVICE_EXTENSION DeviceExtension;
257 ULONG Length;
258 ULONG_PTR Information = 0;
259 PUCHAR Buffer;
260 KIRQL Irql;
261 NTSTATUS Status = STATUS_SUCCESS;
262
263 TRACE_(SERIAL, "IRP_MJ_WRITE\n");
264
265 /* FIXME: pend operation if possible */
266 /* FIXME: use write timeouts */
267
268 Stack = IoGetCurrentIrpStackLocation(Irp);
269 Length = Stack->Parameters.Write.Length;
270 Buffer = SerialGetUserBuffer(Irp);
271 DeviceExtension = (PSERIAL_DEVICE_EXTENSION)DeviceObject->DeviceExtension;
272
273 if (Stack->Parameters.Write.ByteOffset.QuadPart != 0 || Buffer == NULL)
274 {
275 Status = STATUS_INVALID_PARAMETER;
276 goto ByeBye;
277 }
278
279 Status = IoAcquireRemoveLock(&DeviceExtension->RemoveLock, ULongToPtr(DeviceExtension->ComPort));
280 if (!NT_SUCCESS(Status))
281 goto ByeBye;
282
283 /* push bytes into output buffer */
284 KeAcquireSpinLock(&DeviceExtension->OutputBufferLock, &Irql);
285 while (Information < Length)
286 {
287 Status = PushCircularBufferEntry(&DeviceExtension->OutputBuffer, Buffer[Information]);
288 if (!NT_SUCCESS(Status))
289 {
290 if (Status == STATUS_BUFFER_TOO_SMALL)
291 {
292 KeReleaseSpinLock(&DeviceExtension->OutputBufferLock, Irql);
293 SerialSendByte(NULL, DeviceExtension, NULL, NULL);
294 KeAcquireSpinLock(&DeviceExtension->OutputBufferLock, &Irql);
295 continue;
296 }
297 else
298 {
299 WARN_(SERIAL, "Buffer overrun on COM%lu\n", DeviceExtension->ComPort);
300 DeviceExtension->SerialPerfStats.BufferOverrunErrorCount++;
301 break;
302 }
303 }
304 Information++;
305 }
306 KeReleaseSpinLock(&DeviceExtension->OutputBufferLock, Irql);
307 IoReleaseRemoveLock(&DeviceExtension->RemoveLock, ULongToPtr(DeviceExtension->ComPort));
308
309 /* send bytes */
310 SerialSendByte(NULL, DeviceExtension, NULL, NULL);
311
312 ByeBye:
313 Irp->IoStatus.Information = Information;
314 Irp->IoStatus.Status = Status;
315 IoCompleteRequest(Irp, IO_NO_INCREMENT);
316 return Status;
317 }