diff --git a/CHANGES b/CHANGES
index 940ee0f65..c8f691b77 100644
--- a/CHANGES
+++ b/CHANGES
@@ -2,6 +2,7 @@
 Emulation fixes:
  - GB Video: Clear VRAM on reset (fixes mgba.io/i/2152)
  - GBA SIO: Add missing NORMAL8 implementation bits (fixes mgba.io/i/2172)
+ - GBA SIO: Fix missing interrupt on an unattached NORMAL transfer
  - GBA Video: Revert scanline latching changes (fixes mgba.io/i/2153, mgba.io/i/2149)
 Other fixes:
  - 3DS: Fix disabling "wide" mode on 2DS (fixes mgba.io/i/2167)
diff --git a/src/gba/sio/lockstep.c b/src/gba/sio/lockstep.c
index 5cac226a1..e243e5f86 100644
--- a/src/gba/sio/lockstep.c
+++ b/src/gba/sio/lockstep.c
@@ -442,11 +442,22 @@ static void _GBASIOLockstepNodeProcessEvents(struct mTiming* timing, void* user,
 	struct GBASIOLockstepNode* node = user;
 	mLockstepLock(&node->p->d);
 
-	int32_t cycles = 0;
+	int32_t cycles = cycles = node->nextEvent;
 	node->nextEvent -= cyclesLate;
 	node->eventDiff += cyclesLate;
 	if (node->p->d.attached < 2) {
-		cycles = GBASIOCyclesPerTransfer[GBASIOMultiplayerGetBaud(node->d.p->siocnt)][0];
+		switch (node->mode) {
+		case SIO_MULTI:
+			cycles = GBASIOCyclesPerTransfer[GBASIOMultiplayerGetBaud(node->d.p->siocnt)][0];
+			break;
+		case SIO_NORMAL_8:
+		case SIO_NORMAL_32:
+			if (node->nextEvent <= 0) {
+				cycles = _masterUpdate(node);
+				node->eventDiff = 0;
+			}
+			break;
+		}
 	} else if (node->nextEvent <= 0) {
 		if (!node->id) {
 			cycles = _masterUpdate(node);
@@ -455,8 +466,6 @@ static void _GBASIOLockstepNodeProcessEvents(struct mTiming* timing, void* user,
 			cycles += node->p->d.useCycles(&node->p->d, node->id, node->eventDiff);
 		}
 		node->eventDiff = 0;
-	} else {
-		cycles = node->nextEvent;
 	}
 	if (cycles > 0) {
 		node->nextEvent = 0;