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[];