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