elks-enhanced

publicRead
Owner: themasterBranch: masterCommits: 6893Updated: 2026-04-19 00:15
Git CLI clone URL
git clone https://www.xt-emporium.com/git/elks-enhanced.git
Fullscreen desktop URL

Commit diff

Commit c0240aa95fa7a77e293cd9c3f9004c52cc93547
commit c0240aa95fa7a77e293cd9c3f9004c52cc93547e
Author: Greg Haerr <greg@censoft.com>
Date:   Sun Feb 1 21:26:33 2026 -0700

    [kernel] Rewrite C fast serial driver top half in ASM for speed and size
---
 elks/arch/i86/drivers/char/serfast.S     | 195 +++++++++++++++++--------------
 elks/arch/i86/drivers/char/serial-8250.c |  78 +++----------
 elks/include/linuxmt/chqueue.h           |  12 +-
 elks/include/linuxmt/ntty.h              |   8 +-
 4 files changed, 132 insertions(+), 161 deletions(-)

diff --git a/elks/arch/i86/drivers/char/serfast.S b/elks/arch/i86/drivers/char/serfast.S
index f05f8592..94fefe2f 100644
--- a/elks/arch/i86/drivers/char/serfast.S
+++ b/elks/arch/i86/drivers/char/serfast.S
@@ -1,114 +1,130 @@
-// First part of top half of fast serial interrupt handlers for ELKS
+// Top half of fast serial interrupt handlers for ELKS
 //
 // Runs on any stack and skips ELKS _irqit stack switching overhead.
 // Must run with interrupts disabled as could interrupt user, kernel or interrupt stack.
-// Calls the second part of the top half (rs_fast_com1) to read and queue received byte.
+// Reads received byte into tty input queue at interrupt time.
 // The bottom half (serial_bh) runs later, which processes queue and calls wake_up.
 //
 // 25 June 2020 Greg Haerr
+//  1 Feb 2026 Rewritten completely in assembly
 //
 #include <linuxmt/config.h>
-	.code16
-	.text
-
+        .code16
+        .text
 #ifdef CONFIG_ARCH_IBMPC
 
-	.global asm_fast_com1		// entry points
-	.global asm_fast_com2
-	.global asm_fast_com3
-	.global asm_fast_com4
-	.extern	rs_fast_com1		// 2nd parts of handlers
-	.extern	rs_fast_com2
-	.extern	rs_fast_com3
-	.extern	rs_fast_com4
+        .global ports                   // struct serial_info ports[]
+        .global asm_fast_com1           // entry points
+        .global asm_fast_com2
+        .global asm_fast_com3
+        .global asm_fast_com4
+
+// Original C handler rewritten in ASM:
+//
+// void rs_fast_com1(void)
+// {
+//     struct serial_info *sp = &ports[0];
+//     struct ch_queue *q = &sp->tty->inq;
+//     unsigned char c;
+//
+//     c = INB(sp->io + UART_RX);       // Read received data
+//     if (q->len < q->size) {
+//         q->base[q->head] = c;
+//         if (++q->head >= q->size)
+//             q->head = 0;
+//         q->len++;
+//     }
+//     if (c == 03)                     // assumes VINTR = ^C and byte queued anyways
+//         sp->intrchar = c;
+// }
+
 //
 // Entry for ttyS0 top half interrupt handler, called by CALLF within dynamic handler
 //
 asm_fast_com1:
-	push	%ax			// save regs, uses 18 bytes of current stack
-	push	%bx
-	push	%cx
-	push	%dx
-	push	%ds
-
-	// Recover kernel data segment
-	// Was pushed by the CALLF of the dynamic handler
-	mov	%sp,%bx
-	mov	%ss:12(%bx),%ds
-	call	rs_fast_com1		// call special 2nd part of top half C handler
-					// which doesn't use any SS/SP/BP addressing
-common_return:
-	mov	$0x20,%al		// EOI on primary controller
-	out	%al,$0x20
-
-	pop	%ds			// restore regs
-	pop	%dx
-	pop	%cx
-	pop	%bx
-	pop	%ax
-	add	$4,%sp			// skip the trampoline DS:*irq
-	iret
+        push    %cx
+        mov     $ports+0,%cx            // cx = &ports[0]
+common_entry:
+        push    %ax                     // save regs, uses 14 bytes of current stack
+        push    %bx
+        push    %dx
+        push    %ds
+
+        // Recover kernel data segment
+        // Was pushed by the CALLF of the dynamic handler
+        mov     %sp,%bx
+        mov     %ss:12(%bx),%ds
+
+        // translated C routine above
+        // NOTE: uses hard-coded offsets from struct serial_info, tty and ch_queue
+        push    %si
+        push    %di
+        mov     %cx,%bx                 // bx = sp = &ports[n]
+        mov     (%bx),%si               // si = q = &sp->tty->inq
+        mov     0x4(%bx),%dx            // dx = sp->io + UART_RX
+        in      (%dx),%al
+        mov     %al,%dl                 // dl = c = INB(sp->io+UART_RX)
+        mov     (%si),%ax               // ax = q->len
+        cmp     0x2(%si),%ax            // if (ax >= q->size)
+        jge     2f
+        mov     0x4(%si),%bx            // bx = q->head
+        mov     0x8(%si),%di            // di = q->base
+        mov     %dl,(%bx,%di)           // q->base[q->head] = c
+        mov     0x4(%si),%ax            // ax = q->head
+        inc     %ax
+        mov     %ax,0x4(%si)            // ++q->head
+        cmp     0x2(%si),%ax            // if (q->size < q->head)
+        jl      1f
+        movw    $0x0,0x4(%si)           // q->head = 0
+1:      incw    (%si)
+2:      cmp     $0x3,%dl                // if (c == 03)
+        jne     3f
+        mov     %cx,%bx                 // bx = sp
+        movw    $0x3,2(%bx)             // sp->intrchar = c
+3:      pop     %di
+        pop     %si
+
+        mov     $0x20,%al               // EOI on primary controller
+        out     %al,$0x20
+        pop     %ds                     // restore regs
+        pop     %dx
+        pop     %bx
+        pop     %ax
+        pop     %cx
+        add     $4,%sp                  // skip the trampoline DS:*irq
+        iret
 
 //
 // Entry for ttyS1 top half interrupt handler, called by CALLF within dynamic handler
 //
 asm_fast_com2:
-	push	%ax			// save regs, uses 18 bytes of current stack
-	push	%bx
-	push	%cx
-	push	%dx
-	push	%ds
-
-	// Recover kernel data segment
-	// Was pushed by the CALLF of the dynamic handler
-	mov	%sp,%bx
-	mov	%ss:12(%bx),%ds
-	call	rs_fast_com2		// call special 2nd part of top half C handler
-					// which doesn't use any SS/SP/BP addressing
-	jmp     common_return
+        push    %cx
+        mov     $ports+16,%cx           // cx = &ports[1]
+        jmp     common_entry
 
 //
 // Entry for ttyS2 top half interrupt handler, called by CALLF within dynamic handler
 //
 asm_fast_com3:
-	push	%ax			// save regs, uses 18 bytes of current stack
-	push	%bx
-	push	%cx
-	push	%dx
-	push	%ds
-
-	// Recover kernel data segment
-	// Was pushed by the CALLF of the dynamic handler
-	mov	%sp,%bx
-	mov	%ss:12(%bx),%ds
-	call	rs_fast_com3		// call special 2nd part of top half C handler
-					// which doesn't use any SS/SP/BP addressing
-	jmp     common_return
+        push    %cx
+        mov     $ports+32,%cx           // cx = &ports[2]
+        jmp     common_entry
 
 //
 // Entry for ttyS3 top half interrupt handler, called by CALLF within dynamic handler
 //
 asm_fast_com4:
-	push	%ax			// save regs, uses 18 bytes of current stack
-	push	%bx
-	push	%cx
-	push	%dx
-	push	%ds
-
-	// Recover kernel data segment
-	// Was pushed by the CALLF of the dynamic handler
-	mov	%sp,%bx
-	mov	%ss:12(%bx),%ds
-	call	rs_fast_com4		// call special 2nd part of top half C handler
-					// which doesn't use any SS/SP/BP addressing
-	jmp     common_return
+        push    %cx
+        mov     $ports+48,%cx           // cx = &ports[3]
+        jmp     common_entry
 #endif
 
 #ifdef CONFIG_FAST_IRQ1_NECV25
 //
-// Entry for console-serial-necv25 top half interrupt handler, called by CALLF within dynamic handler
+// Entry for console-serial-necv25 top half interrupt handler,
+//      called by CALLF within dynamic handler
 //
-    .extern	sercon_fast_irq1_necv25
+    .extern sercon_fast_irq1_necv25
     .global asm_fast_irq1_necv25
 asm_fast_irq1_necv25:
     push    %ax         // save regs, uses 18 bytes of current stack
@@ -119,15 +135,15 @@ asm_fast_irq1_necv25:
 
     // Recover kernel data segment
     // Was pushed by the CALLF of the dynamic handler
-    mov	%sp,%bx
-    mov	%ss:12(%bx),%ds
+    mov %sp,%bx
+    mov %ss:12(%bx),%ds
 
-    call	sercon_fast_irq1_necv25 // call special 2nd part of top half C handler
+    call    sercon_fast_irq1_necv25 // call special 2nd part of top half C handler
                                     // which doesn't use any SS/SP/BP addressing
 
-    .word   0x920f                  // NEC V25 specific FINT (End Of Interrupt) instruction
+    .word   0x920f      // NEC V25 specific FINT (End Of Interrupt) instruction
 
-    pop     %ds			// restore regs
+    pop     %ds         // restore regs
     pop     %dx
     pop     %cx
     pop     %bx
@@ -138,9 +154,10 @@ asm_fast_irq1_necv25:
 
 #ifdef CONFIG_FAST_IRQ2_NECV25
 //
-// Entry for console-serial-necv25 top half interrupt handler, called by CALLF within dynamic handler
+// Entry for console-serial-necv25 top half interrupt handler,
+//      called by CALLF within dynamic handler
 //
-    .extern	sercon_fast_irq2_necv25
+    .extern sercon_fast_irq2_necv25
     .global asm_fast_irq2_necv25
 asm_fast_irq2_necv25:
     push    %ax         // save regs, uses 18 bytes of current stack
@@ -151,15 +168,15 @@ asm_fast_irq2_necv25:
 
     // Recover kernel data segment
     // Was pushed by the CALLF of the dynamic handler
-    mov	%sp,%bx
-    mov	%ss:12(%bx),%ds
+    mov %sp,%bx
+    mov %ss:12(%bx),%ds
 
-    call	sercon_fast_irq2_necv25 // call special 2nd part of top half C handler
+    call    sercon_fast_irq2_necv25 // call special 2nd part of top half C handler
                                     // which doesn't use any SS/SP/BP addressing
 
-    .word   0x920f                  // NEC V25 specific FINT (End Of Interrupt) instruction
+    .word   0x920f      // NEC V25 specific FINT (End Of Interrupt) instruction
 
-    pop     %ds			// restore regs
+    pop     %ds         // restore regs
     pop     %dx
     pop     %cx
     pop     %bx
diff --git a/elks/arch/i86/drivers/char/serial-8250.c b/elks/arch/i86/drivers/char/serial-8250.c
index e00a619a..d8f42ee9 100644
--- a/elks/arch/i86/drivers/char/serial-8250.c
+++ b/elks/arch/i86/drivers/char/serial-8250.c
@@ -20,15 +20,15 @@
 #include <arch/serial-8250.h>
 #include <arch/ports.h>
 
-struct serial_info {
-             char *io;
+struct serial_info {        /* NOTE: first three members used in fastser.S driver */
+    struct tty *  tty;      /* 0 */
+    int           intrchar; /* 2 ^C SIGINT processing */
+    unsigned int  io;       /* 4 */
     unsigned char irq;
     unsigned char flags;
     unsigned char lcr;
     unsigned char mcr;
     unsigned int  divisor;
-    struct tty *  tty;
-    int           intrchar; /* used by fast handler for ^C SIGINT processing */
     int pad1, pad2;         /* round out to 16 bytes for faster addressing of ports[] */
 };
 
@@ -42,7 +42,6 @@ struct serial_info {
 #define ST_16750        4
 #define ST_UNKNOWN      15
 
-
 /* I/O delay settings*/
 #define INB             inb     // use inb_p for 1us delay
 #define OUTB            outb    // use outb_p for 1us delay
@@ -52,11 +51,11 @@ struct serial_info {
 #define DEFAULT_MCR             \
         ((unsigned char) (UART_MCR_DTR | UART_MCR_RTS | UART_MCR_OUT2))
 
-static struct serial_info ports[MAX_SERIAL] = {
-    {(char *)COM1_PORT, COM1_IRQ, 0, DEFAULT_LCR, DEFAULT_MCR, 0, NULL, 0,0,0},
-    {(char *)COM2_PORT, COM2_IRQ, 0, DEFAULT_LCR, DEFAULT_MCR, 0, NULL, 0,0,0},
-    {(char *)COM3_PORT, COM3_IRQ, 0, DEFAULT_LCR, DEFAULT_MCR, 0, NULL, 0,0,0},
-    {(char *)COM4_PORT, COM4_IRQ, 0, DEFAULT_LCR, DEFAULT_MCR, 0, NULL, 0,0,0},
+struct serial_info ports[MAX_SERIAL] = {
+    {NULL, 0, COM1_PORT, COM1_IRQ, 0, DEFAULT_LCR, DEFAULT_MCR, 0, 0,0},
+    {NULL, 0, COM2_PORT, COM2_IRQ, 0, DEFAULT_LCR, DEFAULT_MCR, 0, 0,0},
+    {NULL, 0, COM3_PORT, COM3_IRQ, 0, DEFAULT_LCR, DEFAULT_MCR, 0, 0,0},
+    {NULL, 0, COM4_PORT, COM4_IRQ, 0, DEFAULT_LCR, DEFAULT_MCR, 0, 0,0},
 };
 
 static unsigned int divisors[] = {
@@ -213,12 +212,15 @@ static int rs_write(struct tty *tty)
     return i;
 }
 
+#if UNUSED
 /*
+ * NOTE: This routine is no longer used, instead it was rewritten in serfast.S.
+ *
  * Serial interrupt top half. This top half actually consists of two parts.
- * The first part of the top half of the handler is asm_fast_irq4 in serfast.S,
+ * The first part of the top half of the handler is asm_fast_com1 in serfast.S,
  * and the function below is the second part of the top half, rs_fast_com1.
  *
- * The first part asm_fast_irq4 is called directly from the IRQ 4 interrupt
+ * The first part asm_fast_com1 is called directly from the IRQ 4 interrupt
  * vector, bypassing the normal kernel stack switch code in _irqit. That code
  * runs with interrupts disabled and saves registers AX,BX,CX,DX,DS, and
  * sets DS to the kernel data segment. The stack segment is not changed,
@@ -262,57 +264,7 @@ void rs_fast_com1(void)
      */
     //mark_bh(SERIAL_BH);
 }
-
-void rs_fast_com2(void)
-{
-    struct serial_info *sp = &ports[1];
-    struct ch_queue *q = &sp->tty->inq;
-    unsigned char c;
-
-    c = INB(sp->io + UART_RX);
-    if (q->len < q->size) {
-        q->base[q->head] = c;
-        if (++q->head >= q->size)
-            q->head = 0;
-        q->len++;
-    }
-    if (c == 03)
-        sp->intrchar = c;
-}
-
-void rs_fast_com3(void)
-{
-    struct serial_info *sp = &ports[2];
-    struct ch_queue *q = &sp->tty->inq;
-    unsigned char c;
-
-    c = INB(sp->io + UART_RX);
-    if (q->len < q->size) {
-        q->base[q->head] = c;
-        if (++q->head >= q->size)
-            q->head = 0;
-        q->len++;
-    }
-    if (c == 03)
-        sp->intrchar = c;
-}
-
-void rs_fast_com4(void)
-{
-    struct serial_info *sp = &ports[3];
-    struct ch_queue *q = &sp->tty->inq;
-    unsigned char c;
-
-    c = INB(sp->io + UART_RX);
-    if (q->len < q->size) {
-        q->base[q->head] = c;
-        if (++q->head >= q->size)
-            q->head = 0;
-        q->len++;
-    }
-    if (c == 03)
-        sp->intrchar = c;
-}
+#endif
 
 /* check for SIGINT and wakeup waiting processes */
 static void pump_port(struct serial_info *sp)
diff --git a/elks/include/linuxmt/chqueue.h b/elks/include/linuxmt/chqueue.h
index a671665b..7bbb91a5 100644
--- a/elks/include/linuxmt/chqueue.h
+++ b/elks/include/linuxmt/chqueue.h
@@ -3,11 +3,13 @@
 
 /* chqueue.h (C) 1997 Chad Page, rewritten Greg Haerr Oct 2020 */
 
-struct ch_queue {
-    unsigned char	*base;
-    int 		size;		/* doesn't have to be power of two*/
-    int			len, head, tail;
-    struct wait_queue	wait;
+struct ch_queue {               /* NOTE: members used in fastser.S driver */
+    int                 len;    /* 0 */
+    int                 size;   /* 2 doesn't have to be power of two */
+    int                 head;   /* 4 */
+    int                 tail;
+    unsigned char       *base;  /* 8 */
+    struct wait_queue   wait;
 };
 
 extern void chq_init(register struct ch_queue *,unsigned char *,int);
diff --git a/elks/include/linuxmt/ntty.h b/elks/include/linuxmt/ntty.h
index de347d5a..440d058b 100644
--- a/elks/include/linuxmt/ntty.h
+++ b/elks/include/linuxmt/ntty.h
@@ -93,15 +93,15 @@ struct tty_ops {
     void (*conout) (dev_t, int);
 };
 
-struct tty {
-    struct tty_ops *ops;
+struct tty {            /* NOTE: first member used in fastser.S driver */
+    struct ch_queue inq, outq;
     unsigned short minor;
     unsigned int flags;
-    struct ch_queue inq, outq;
-    struct termios termios;
     unsigned char ostate;
     unsigned char usecount;
     pid_t pgrp;
+    struct tty_ops *ops;
+    struct termios termios;
 };
 
 extern struct tty ttys[];