- Add synchronization on input and output buffers
- Respect timeouts on IRP_MJ_READ
- Get right buffer in read/write routines
Modified: trunk/reactos/drivers/dd/serial/devctrl.c
Modified: trunk/reactos/drivers/dd/serial/misc.c
Modified: trunk/reactos/drivers/dd/serial/pnp.c
Modified: trunk/reactos/drivers/dd/serial/rw.c
Modified: trunk/reactos/drivers/dd/serial/serial.h

Modified: trunk/reactos/drivers/dd/serial/devctrl.c
--- trunk/reactos/drivers/dd/serial/devctrl.c	2005-03-23 22:11:20 UTC (rev 14296)
+++ trunk/reactos/drivers/dd/serial/devctrl.c	2005-03-24 07:50:41 UTC (rev 14297)
@@ -215,14 +215,23 @@
 	OUT PSERIAL_STATUS pSerialStatus,
 	IN PSERIAL_DEVICE_EXTENSION DeviceExtension)
 {
+	KIRQL Irql;
+	
 	RtlZeroMemory(pSerialStatus, sizeof(SERIAL_STATUS));
 	
 	pSerialStatus->Errors = 0; /* FIXME */
 	pSerialStatus->HoldReasons = 0; /* FIXME */
+	
+	KeAcquireSpinLock(&DeviceExtension->InputBufferLock, &Irql);
 	pSerialStatus->AmountInInQueue = (DeviceExtension->InputBuffer.WritePosition + DeviceExtension->InputBuffer.Length
 		- DeviceExtension->InputBuffer.ReadPosition) % DeviceExtension->InputBuffer.Length;
+	KeReleaseSpinLock(&DeviceExtension->InputBufferLock, Irql);
+	
+	KeAcquireSpinLock(&DeviceExtension->OutputBufferLock, &Irql);
 	pSerialStatus->AmountInOutQueue = (DeviceExtension->OutputBuffer.WritePosition + DeviceExtension->OutputBuffer.Length
 		- DeviceExtension->OutputBuffer.ReadPosition) % DeviceExtension->OutputBuffer.Length;
+	KeReleaseSpinLock(&DeviceExtension->OutputBufferLock, Irql);
+	
 	pSerialStatus->EofReceived = FALSE; /* FIXME */
 	pSerialStatus->WaitForImmediate = FALSE; /* FIXME */
 	
@@ -497,8 +506,10 @@
 		}
 		case IOCTL_SERIAL_PURGE:
 		{
+			KIRQL Irql1, Irql2;
 			DPRINT("Serial: IOCTL_SERIAL_PURGE\n");
-			/* FIXME: lock input and output queues */
+			KeAcquireSpinLock(&DeviceExtension->InputBufferLock, &Irql1);
+			KeAcquireSpinLock(&DeviceExtension->OutputBufferLock, &Irql2);
 			DeviceExtension->InputBuffer.ReadPosition = DeviceExtension->InputBuffer.WritePosition = 0;
 			DeviceExtension->OutputBuffer.ReadPosition = DeviceExtension->OutputBuffer.WritePosition = 0;
 			/* Clear receive/transmit buffers */
@@ -507,7 +518,8 @@
 				WRITE_PORT_UCHAR(SER_FCR(ComPortBase),
 					SR_FCR_CLEAR_RCVR | SR_FCR_CLEAR_XMIT);
 			}
-			/* FIXME: unlock input and output queues */
+			KeReleaseSpinLock(&DeviceExtension->OutputBufferLock, Irql2);
+			KeReleaseSpinLock(&DeviceExtension->InputBufferLock, Irql1);
 			Status = STATUS_SUCCESS;
 			break;
 		}
@@ -618,18 +630,19 @@
 				return STATUS_INVALID_PARAMETER;
 			else
 			{
+				KIRQL Irql;
 				Status = STATUS_SUCCESS;
 				if (((PSERIAL_QUEUE_SIZE)Buffer)->InSize > DeviceExtension->InputBuffer.Length)
 				{
-					/* FIXME: lock input queue */
+					KeAcquireSpinLock(&DeviceExtension->InputBufferLock, &Irql);
 					Status = IncreaseCircularBufferSize(&DeviceExtension->InputBuffer, ((PSERIAL_QUEUE_SIZE)Buffer)->InSize);
-					/* FIXME: unlock input queue */
+					KeReleaseSpinLock(&DeviceExtension->InputBufferLock, Irql);
 				}
 				if (NT_SUCCESS(Status) && ((PSERIAL_QUEUE_SIZE)Buffer)->OutSize > DeviceExtension->OutputBuffer.Length)
 				{
-					/* FIXME: lock output queue */
+					KeAcquireSpinLock(&DeviceExtension->OutputBufferLock, &Irql);
 					Status = IncreaseCircularBufferSize(&DeviceExtension->OutputBuffer, ((PSERIAL_QUEUE_SIZE)Buffer)->OutSize);
-					/* FIXME: unlock output queue */
+					KeReleaseSpinLock(&DeviceExtension->OutputBufferLock, Irql);
 				}
 			}
 			break;

Modified: trunk/reactos/drivers/dd/serial/misc.c
--- trunk/reactos/drivers/dd/serial/misc.c	2005-03-23 22:11:20 UTC (rev 14296)
+++ trunk/reactos/drivers/dd/serial/misc.c	2005-03-24 07:50:41 UTC (rev 14297)
@@ -60,6 +60,67 @@
 	return IoCallDriver(LowerDevice, Irp);
 }
 
+VOID STDCALL
+SerialReceiveByte(
+	IN PKDPC Dpc,
+	IN PVOID pDeviceExtension, // real type PSERIAL_DEVICE_EXTENSION
+	IN PVOID pByte,            // real type UCHAR
+	IN PVOID Unused)
+{
+	PSERIAL_DEVICE_EXTENSION DeviceExtension;
+	UCHAR Byte;
+	KIRQL Irql;
+	NTSTATUS Status;
+	
+	DeviceExtension = (PSERIAL_DEVICE_EXTENSION)pDeviceExtension;
+	Byte = (UCHAR)(ULONG_PTR)pByte;
+	DPRINT1("Serial: received byte on COM%lu: 0x%02x (%c)\n",
+		DeviceExtension->ComPort, Byte, Byte);
+	
+	KeAcquireSpinLock(&DeviceExtension->InputBufferLock, &Irql);
+	Status = PushCircularBufferEntry(&DeviceExtension->InputBuffer, Byte);
+	if (!NT_SUCCESS(Status))
+	{
+		/* FIXME: count buffer overflow */
+		return;
+	}
+	DPRINT1("Serial: push to buffer done\n");
+	KeReleaseSpinLock(&DeviceExtension->InputBufferLock, Irql);
+	InterlockedIncrement(&DeviceExtension->SerialPerfStats.ReceivedCount);
+}
+
+VOID STDCALL
+SerialSendByte(
+	IN PKDPC Dpc,
+	IN PVOID pDeviceExtension, // real type PSERIAL_DEVICE_EXTENSION
+	IN PVOID Unused1,
+	IN PVOID Unused2)
+{
+	PSERIAL_DEVICE_EXTENSION DeviceExtension;
+	PUCHAR ComPortBase;
+	UCHAR Byte;
+	KIRQL Irql;
+	NTSTATUS Status;
+	
+	DeviceExtension = (PSERIAL_DEVICE_EXTENSION)pDeviceExtension;
+	ComPortBase = (PUCHAR)DeviceExtension->BaseAddress;
+	
+	DPRINT1("Serial: sending bytes (if any) on COM%lu\n",
+		DeviceExtension->ComPort);
+	
+	KeAcquireSpinLock(&DeviceExtension->OutputBufferLock, &Irql);
+	while (!IsCircularBufferEmpty(&DeviceExtension->OutputBuffer)
+		&& READ_PORT_UCHAR(SER_LSR(ComPortBase)) & SR_LSR_TBE)
+	{
+		Status = PopCircularBufferEntry(&DeviceExtension->OutputBuffer, &Byte);
+		if (!NT_SUCCESS(Status))
+			break;
+		WRITE_PORT_UCHAR(SER_THR(ComPortBase), Byte);
+		DeviceExtension->SerialPerfStats.TransmittedCount++;
+	}	
+	KeReleaseSpinLock(&DeviceExtension->OutputBufferLock, Irql);
+}
+
 BOOLEAN STDCALL
 SerialInterruptService(
 	IN PKINTERRUPT Interrupt,
@@ -70,7 +131,6 @@
 	UCHAR Byte;
 	PUCHAR ComPortBase;
 	UCHAR Iir;
-	NTSTATUS Status;
 	
 	DeviceObject = (PDEVICE_OBJECT)ServiceContext;
 	DeviceExtension = (PSERIAL_DEVICE_EXTENSION)DeviceObject->DeviceExtension;
@@ -96,18 +156,7 @@
 		case SR_IIR_THR_EMPTY:
 		{
 			DPRINT("Serial: SR_IIR_THR_EMPTY\n");
-			/* FIXME: lock OutputBuffer */
-			while (!IsCircularBufferEmpty(&DeviceExtension->OutputBuffer)
-				&& READ_PORT_UCHAR(SER_LSR(ComPortBase)) & SR_LSR_TBE)
-			{
-				Status = PopCircularBufferEntry(&DeviceExtension->OutputBuffer, &Byte);
-				if (!NT_SUCCESS(Status))
-					break;
-				WRITE_PORT_UCHAR(SER_THR(ComPortBase), Byte);
-				DeviceExtension->SerialPerfStats.TransmittedCount++;
-			}	
-			/* FIXME: unlock OutputBuffer */
-			return TRUE;
+			return KeInsertQueueDpc(&DeviceExtension->SendByteDpc, NULL, NULL);
 		}
 		case SR_IIR_DATA_RECEIVED:
 		{
@@ -116,19 +165,10 @@
 			{
 				Byte = READ_PORT_UCHAR(SER_RBR(ComPortBase));
 				DPRINT1("Serial: Byte received: 0x%02x (%c)\n", Byte, Byte);
-				/* FIXME: lock InputBuffer */
-				Status = PushCircularBufferEntry(&DeviceExtension->InputBuffer, Byte);
-				if (!NT_SUCCESS(Status))
-				{
-					/* FIXME: count buffer overflow */
+				if (!KeInsertQueueDpc(&DeviceExtension->ReceivedByteDpc, (PVOID)(ULONG_PTR)Byte, NULL))
 					break;
-				}
-				DPRINT1("Serial: push to buffer done\n");
-				/* FIXME: unlock InputBuffer */
-				DeviceExtension->SerialPerfStats.ReceivedCount++;
 			}
 			return TRUE;
-			break;
 		}
 		case SR_IIR_ERROR:
 		{

Modified: trunk/reactos/drivers/dd/serial/pnp.c
--- trunk/reactos/drivers/dd/serial/pnp.c	2005-03-23 22:11:20 UTC (rev 14296)
+++ trunk/reactos/drivers/dd/serial/pnp.c	2005-03-24 07:50:41 UTC (rev 14297)
@@ -76,6 +76,10 @@
 	Status = InitializeCircularBuffer(&DeviceExtension->OutputBuffer, 16);
 	if (!NT_SUCCESS(Status)) goto ByeBye;
 	IoInitializeRemoveLock(&DeviceExtension->RemoveLock, SERIAL_TAG, 0, 0);
+	KeInitializeSpinLock(&DeviceExtension->InputBufferLock);
+	KeInitializeSpinLock(&DeviceExtension->OutputBufferLock);
+	KeInitializeDpc(&DeviceExtension->ReceivedByteDpc, SerialReceiveByte, DeviceExtension);
+	KeInitializeDpc(&DeviceExtension->SendByteDpc, SerialSendByte, DeviceExtension);
 	//Fdo->Flags |= DO_POWER_PAGEABLE (or DO_POWER_INRUSH?)
 	Status = IoAttachDeviceToDeviceStackSafe(Fdo, Pdo, &DeviceExtension->LowerDevice);
 	if (!NT_SUCCESS(Status))
@@ -152,13 +156,7 @@
 	
 	DeviceExtension = (PSERIAL_DEVICE_EXTENSION)DeviceObject->DeviceExtension;
 	
-	/* FIXME: actually, IRP_MN_START_DEVICE is sent twice to each serial device:
-	 * - one when loading serial.sys
-	 * - one when loading attached upper filter serenum.sys
-	 * This behaviour MUST NOT exist.
-	 * As PnP handling isn't right anyway, I didn't search how to correct this.
-	 */
-	if (DeviceExtension->PnpState == dsStarted) return STATUS_SUCCESS;
+	ASSERT(DeviceExtension->PnpState == dsStopped);
 	
 	DeviceExtension->ComPort = DeviceExtension->SerialPortNumber + 1;
 	DeviceExtension->BaudRate = 19200 | SERIAL_BAUD_USER;

Modified: trunk/reactos/drivers/dd/serial/rw.c
--- trunk/reactos/drivers/dd/serial/rw.c	2005-03-23 22:11:20 UTC (rev 14296)
+++ trunk/reactos/drivers/dd/serial/rw.c	2005-03-24 07:50:41 UTC (rev 14297)
@@ -16,12 +16,8 @@
 SerialGetUserBuffer(IN PIRP Irp)
 {
    ASSERT(Irp);
-
-   if (Irp->MdlAddress)
-      return MmGetSystemAddressForMdlSafe(Irp->MdlAddress, NormalPagePriority);
-   else
-   	/* FIXME: try buffer */
-      return Irp->UserBuffer;
+   
+   return Irp->AssociatedIrp.SystemBuffer;
 }
 
 NTSTATUS STDCALL
@@ -36,6 +32,7 @@
 	PUCHAR Buffer;
 	PUCHAR ComPortBase;
 	UCHAR ReceivedByte;
+	KIRQL Irql;
 	NTSTATUS Status = STATUS_SUCCESS;
 	
 	DPRINT("Serial: IRP_MJ_READ\n");
@@ -64,8 +61,8 @@
 	if (!NT_SUCCESS(Status))
 		goto ByeBye;
 	
-	/* FIXME: lock InputBuffer */
-	while (Length-- > 0 && !IsCircularBufferEmpty(&DeviceExtension->InputBuffer))
+	KeAcquireSpinLock(&DeviceExtension->InputBufferLock, &Irql);
+	while (Length-- > 0)
 	{
 		Status = PopCircularBufferEntry(&DeviceExtension->InputBuffer, &ReceivedByte);
 		if (!NT_SUCCESS(Status))
@@ -73,54 +70,117 @@
 		DPRINT("Serial: read from buffer 0x%x (%c)\n", ReceivedByte, ReceivedByte);
 		Buffer[Information++] = ReceivedByte;
 	}
+	KeReleaseSpinLock(&DeviceExtension->InputBufferLock, Irql);
 	if (Length > 0 &&
 		!(DeviceExtension->SerialTimeOuts.ReadIntervalTimeout == INFINITE &&
 		  DeviceExtension->SerialTimeOuts.ReadTotalTimeoutConstant == 0 &&
 		  DeviceExtension->SerialTimeOuts.ReadTotalTimeoutMultiplier == 0))
 	{
-		if (DeviceExtension->SerialTimeOuts.ReadIntervalTimeout == 0
-			|| DeviceExtension->SerialTimeOuts.ReadTotalTimeoutMultiplier == 0)
+		ULONG IntervalTimeout;
+		ULONG TotalTimeout;
+		BOOLEAN UseIntervalTimeout = FALSE;
+		BOOLEAN UseTotalTimeout = FALSE;
+		ULONG ThisByteTimeout;
+		BOOLEAN IsByteReceived;
+		ULONG i;
+		/* Extract timeouts informations */
+		if (DeviceExtension->SerialTimeOuts.ReadIntervalTimeout == INFINITE &&
+			DeviceExtension->SerialTimeOuts.ReadTotalTimeoutMultiplier == INFINITE &&
+			DeviceExtension->SerialTimeOuts.ReadTotalTimeoutConstant > 0 &&
+			DeviceExtension->SerialTimeOuts.ReadTotalTimeoutConstant < INFINITE)
 		{
-			DPRINT("Serial: we must wait for %lu characters!\n", Length);
-#if 1
-			/* Disable interrupts */
-			WRITE_PORT_UCHAR(SER_IER((PUCHAR)DeviceExtension->BaseAddress), DeviceExtension->IER & ~1);
-			
-			/* Polling code */
-			while (Length > 0)
+			if (Information > 0)
 			{
-				while ((READ_PORT_UCHAR(SER_LSR(ComPortBase)) & SR_LSR_DR) == 0)
-					;
-				ReceivedByte = READ_PORT_UCHAR(SER_RBR(ComPortBase));
-				Buffer[Information++] = ReceivedByte;
-				Length--;
+				/* don't read mode bytes */
+				Length = 0;
 			}
-			/* Enable interrupts */
-			WRITE_PORT_UCHAR(SER_IER((PUCHAR)DeviceExtension->BaseAddress), DeviceExtension->IER);
-#else
-			while (Length > 0)
+			else
 			{
-				if (!IsCircularBufferEmpty(&DeviceExtension->InputBuffer))
+				/* read only one byte */
+				UseTotalTimeout = TRUE;
+				TotalTimeout = DeviceExtension->SerialTimeOuts.ReadTotalTimeoutConstant;
+				Length = 1;
+			}
+		}
+		else
+		{
+			if (DeviceExtension->SerialTimeOuts.ReadIntervalTimeout != 0)
+			{
+				UseIntervalTimeout = TRUE;
+				IntervalTimeout = DeviceExtension->SerialTimeOuts.ReadIntervalTimeout;
+			}
+			if (DeviceExtension->SerialTimeOuts.ReadTotalTimeoutConstant != 0 ||
+				DeviceExtension->SerialTimeOuts.ReadTotalTimeoutMultiplier != 0)
+			{
+				UseTotalTimeout = TRUE;
+				TotalTimeout = DeviceExtension->SerialTimeOuts.ReadTotalTimeoutConstant +
+					DeviceExtension->SerialTimeOuts.ReadTotalTimeoutMultiplier * Length;
+			}
+		}
+		DPRINT("Serial: UseIntervalTimeout = %ws, IntervalTimeout = %lu\n",
+			UseIntervalTimeout ? L"YES" : L"NO",
+			UseIntervalTimeout ? IntervalTimeout : 0);
+		DPRINT("Serial: UseTotalTimeout = %ws, TotalTimeout = %lu\n",
+			UseTotalTimeout ? L"YES" : L"NO",
+			UseTotalTimeout ? TotalTimeout : 0);
+		
+		/* FIXME: it should be better to use input buffer instead of
+		 * disabling interrupts, and try to directly read for port! */
+		
+		/* FIXME: NtQueryPerformanceCounter gives a more accurate
+		 * timer, but it is not available on all computers. First try
+		 * NtQueryPerformanceCounter, and current method if it is not
+		 * implemented. */
+		
+		/* FIXME: remove disabling interrupts */
+		WRITE_PORT_UCHAR(SER_IER(ComPortBase), DeviceExtension->IER & ~1);
+		while (Length > 0)
+		{
+			ThisByteTimeout = IntervalTimeout;
+			IsByteReceived = FALSE;
+			while (TRUE)
+			{
+				for (i = 0; i < 1000; i++)
 				{
-					Status = PopCircularBufferEntry(&DeviceExtension->InputBuffer, &ReceivedByte);
-					if (!NT_SUCCESS(Status))
+#if 1
+					if ((READ_PORT_UCHAR(SER_LSR(ComPortBase)) & SR_LSR_DR) != 0)
+					{
+						ReceivedByte = READ_PORT_UCHAR(ComPortBase);
+						DPRINT("Serial: received byte 0x%02x (%c)\n", ReceivedByte, ReceivedByte);
+						IsByteReceived = TRUE;
 						break;
-					DPRINT1("Serial: read from buffer 0x%x (%c)\n", ReceivedByte, ReceivedByte);
-					Buffer[Information++] = ReceivedByte;
-					Length--;
+					}
+#else
+					KeAcquireSpinLock(&DeviceExtension->InputBufferLock, &Irql);
+					if (!IsCircularBufferEmpty(&DeviceExtension->InputBuffer))
+					{
+						PopCircularBufferEntry(&DeviceExtension->InputBuffer, &ReceivedByte);
+						KeReleaseSpinLock(&DeviceExtension->InputBufferLock, Irql);
+						DPRINT("Serial: reading byte from buffer 0x%02x (%c)\n", ReceivedByte, ReceivedByte);
+						IsByteReceived = TRUE;
+						break;
+					}
+					KeReleaseSpinLock(&DeviceExtension->InputBufferLock, Irql);
+#endif
+					KeStallExecutionProcessor(1);
 				}
+				if (IsByteReceived) break;
+				if (UseIntervalTimeout)
+				{
+					if (ThisByteTimeout == 0) break; else ThisByteTimeout--;
+				}
+				if (UseTotalTimeout)
+				{
+					if (TotalTimeout == 0) break; else TotalTimeout--;
+				}
 			}
-#endif
+			if (!IsByteReceived) break;
+			Buffer[Information++] = ReceivedByte;
+			Length--;
 		}
-		else
-		{
-			/* FIXME: use ReadTotalTimeoutMultiplier and ReadTotalTimeoutConstant */
-			DPRINT1("Serial: we must wait for %lu characters at maximum within %lu milliseconds! UNIMPLEMENTED\n",
-				Length,
-				Stack->Parameters.Read.Length * DeviceExtension->SerialTimeOuts.ReadTotalTimeoutMultiplier + DeviceExtension->SerialTimeOuts.ReadTotalTimeoutConstant);
-		}
+		/* FIXME: remove enabling interrupts */
+		WRITE_PORT_UCHAR(SER_IER(ComPortBase), DeviceExtension->IER);
 	}
-	/* FIXME: unlock InputBuffer */
 	Status = STATUS_SUCCESS;
 	
 	IoReleaseRemoveLock(&DeviceExtension->RemoveLock, (PVOID)DeviceExtension->ComPort);
@@ -143,6 +203,7 @@
 	ULONG Information = 0;
 	PUCHAR Buffer;
 	PUCHAR ComPortBase;
+	KIRQL Irql;
 	NTSTATUS Status = STATUS_SUCCESS;
 	
 	DPRINT("Serial: IRP_MJ_WRITE\n");
@@ -166,7 +227,7 @@
 	if (!NT_SUCCESS(Status))
 		goto ByeBye;
 	
-	/* FIXME: lock OutputBuffer */
+	KeAcquireSpinLock(&DeviceExtension->OutputBufferLock, &Irql);
 	if (IsCircularBufferEmpty(&DeviceExtension->OutputBuffer))
 	{
 		/* Put the maximum amount of data in UART output buffer */
@@ -197,7 +258,7 @@
 		DPRINT1("Serial: write to buffer 0x%02x\n", Buffer[Information]);
 		Information++;
 	}
-	/* FIXME: unlock OutputBuffer */
+	KeReleaseSpinLock(&DeviceExtension->OutputBufferLock, Irql);
 	IoReleaseRemoveLock(&DeviceExtension->RemoveLock, (PVOID)DeviceExtension->ComPort);
 
 ByeBye:

Modified: trunk/reactos/drivers/dd/serial/serial.h
--- trunk/reactos/drivers/dd/serial/serial.h	2005-03-23 22:11:20 UTC (rev 14296)
+++ trunk/reactos/drivers/dd/serial/serial.h	2005-03-24 07:50:41 UTC (rev 14297)
@@ -81,6 +81,8 @@
 	ULONG BaudRate;
 	ULONG BaseAddress;
 	PKINTERRUPT Interrupt;
+	KDPC ReceivedByteDpc;
+	KDPC SendByteDpc;
 	
 	SERIAL_LINE_CONTROL SerialLineControl;
 	UART_TYPE UartType;
@@ -90,7 +92,9 @@
 	SERIAL_TIMEOUTS SerialTimeOuts;
 	BOOLEAN IsOpened;	
 	CIRCULAR_BUFFER InputBuffer;
+	KSPIN_LOCK InputBufferLock;
 	CIRCULAR_BUFFER OutputBuffer;
+	KSPIN_LOCK OutputBufferLock;
 	
 	/* Current values */
 	UCHAR IER; /* Base+1, Interrupt Enable Register */
@@ -250,6 +254,20 @@
 	IN PDEVICE_OBJECT DeviceObject,
 	IN PIRP Irp);
 
+VOID STDCALL
+SerialReceiveByte(
+	IN PKDPC Dpc,
+	IN PVOID pDeviceExtension, // real type PSERIAL_DEVICE_EXTENSION
+	IN PVOID pByte,            // real type UCHAR
+	IN PVOID Unused);
+
+VOID STDCALL
+SerialSendByte(
+	IN PKDPC Dpc,
+	IN PVOID pDeviceExtension, // real type PSERIAL_DEVICE_EXTENSION
+	IN PVOID Unused1,
+	IN PVOID Unused2);
+
 BOOLEAN STDCALL
 SerialInterruptService(
 	IN PKINTERRUPT Interrupt,