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