xref: /reactos/drivers/serial/serial/rw.c (revision bef39dd6)
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