Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[kernel] Add kernel and user mode divide by zero handling #1969

Merged
merged 1 commit into from
Aug 20, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion elks/arch/i86/kernel/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ OBJS = strace.o system.o irq.o irqtab.o process.o \
entry.o signal.o timer.o

ifeq ($(CONFIG_ARCH_IBMPC), y)
OBJS += irq-8259.o timer-8254.o
OBJS += irq-8259.o timer-8254.o divzero.o
endif

ifeq ($(CONFIG_ARCH_PC98), y)
Expand Down
44 changes: 44 additions & 0 deletions elks/arch/i86/kernel/divzero.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
#include <linuxmt/config.h>
#include <linuxmt/mm.h>
#include <linuxmt/sched.h>

/*
* Divide by zero and divide overflow exception handler
*
* 19 Aug 24 Greg Haerr
*/

static char div0msg[] = { "Divide by zero\n" };

void div0_handler(int i, struct pt_regs *regs)
{
/* divide by 0 from nested interrupt or idle task means kernel code was executing */
if (_gint_count > 1 /*|| current->t_regs.ss == kernel_ds*/) {
/*
* Trap from kernel code.
*
* Not panicing at this point involves determining the CS:IP of the
* faulting instruction, then doing two different things depending on
* whether the CPU is 8088/8086/V20/V30 or 286/386+: the former trap
* pushes the CS:IP following the DIV, while the latter pushes CS:IP of
* the DIV instruction. Incrementing IP on 286+ would involve decoding
* all forms of DIV, while allowing either would introduce undefined
* behaviour as a result of an incorrect calcuation. So we panic.
*/
#if 0
struct uregs __far *sys_stack;
sys_stack = _MK_FP(regs->ss, regs->sp);
printk("Div0 at CS:IP %x:%x\n", sys_stack->cs, sys_stack->ip);
#endif
panic(div0msg);
} else {
/* For user mode faults, display error message and kill the process */
printk(div0msg);
#if 0
struct uregs __far *user_stack;
user_stack = _MK_FP(current->t_regs.ss, current->t_regs.sp);
printk("Div0 at CS:IP %x:%x\n", user_stack->cs, user_stack->ip);
#endif
sys_kill(current->pid, SIGABRT); /* no SIGFPE so send SIGABRT for now */
}
}
74 changes: 27 additions & 47 deletions elks/arch/i86/kernel/irq.c
Original file line number Diff line number Diff line change
Expand Up @@ -23,94 +23,68 @@
#include <linuxmt/types.h>
#include <linuxmt/heap.h>

#include <arch/irq.h>
#include <arch/ports.h>
#include <arch/segment.h>
#include <arch/irq.h>

// TODO: simplify the whole by replacing IRQ by INT number
// Would also allow to handle any of the 0..255 interrupts
// including the 0..7 processor exceptions & traps

/*
* Irq index numbers >= 16 are used for hardware exceptions or syscall.
* This allows handling of any of the 0..255 interrupts
* including the 0..7 processor exceptions & traps.
*/
struct int_handler {
byte_t call; /* CALLF opcode (9Ah) */
int_proc proc;
word_t proc;
word_t seg;
byte_t irq;
byte_t irq; /* actually irq index number */
} __attribute__ ((packed));

#define NR_TRAMPOLINES 12
static struct int_handler trampoline [NR_TRAMPOLINES];
static struct int_handler *irq_trampoline [16];
static irq_handler irq_action [16];
static struct int_handler trampoline[NR_IRQS];
static irq_handler irq_action[NR_IRQS];

/* called by _irqit assembler hook after saving registers */
void do_IRQ(int i,void *regs)
void do_IRQ(int i, struct pt_regs *regs)
{
irq_handler ih = irq_action [i];
if (!ih)
printk("Unexpected interrupt: %u\n", i);
else (*ih)(i, regs);
}

static struct int_handler *handler_alloc(void)
/* install interrupt vector to point to handler trampoline */
static void int_handler_add(int irq, int vect, int_proc proc)
{
struct int_handler *h;

for (h = trampoline; h < &trampoline[NR_TRAMPOLINES]; h++) {
if (h->call == 0)
return h;
}
return NULL;
}

static void handler_free(struct int_handler *h)
{
h->call = 0;
}

/* install interrupt vector to point to allocated handler trampoline */
static int int_handler_add (int irq, int vect, int_proc proc, struct int_handler *h)
{
if (!h) h = handler_alloc();
if (!h) return -ENOMEM;

h = &trampoline[irq];
h->call = 0x9A; /* CALLF opcode */
h->proc = proc;
h->proc = (word_t)proc;
h->seg = kernel_cs; /* resident kernel code segment */
h->irq = irq;

int_vector_set (vect, (int_proc) h, kernel_ds);

return 0;
int_vector_set(vect, (word_t)h, kernel_ds);
}

/* request an IRQ from 0 to 15 */
int request_irq(int irq, irq_handler handler, int hflag)
{
struct int_handler *h;
int_proc proc;
flag_t flags;

irq = remap_irq(irq);
if (irq < 0 || !handler) return -EINVAL;

if (irq_action [irq]) return -EBUSY;
h = handler_alloc();
if (!h) return -ENOMEM;

save_flags(flags);
clr_irq();

irq_action [irq] = handler;
irq_trampoline [irq] = h;

if (hflag == INT_SPECIFIC)
proc = (int_proc) handler;
else
proc = _irqit;

// TODO: IRQ number has no meaning for an INT handler
// see above simplification TODO
int_handler_add (irq, irq_vector (irq), proc, h);
int_handler_add(irq, irq_vector(irq), proc);

enable_irq(irq);
restore_flags(flags);
Expand All @@ -133,11 +107,11 @@ int free_irq(int irq)
clr_irq();

disable_irq(irq);
int_vector_set(irq_vector(irq), 0, 0); /* reset vector to 0:0 */
/* don't reset vector to 0:0, instead allow "unexpected interrupt" above */
/*int_vector_set(irq_vector(irq), 0, 0);*/
irq_action[irq] = NULL;
restore_flags(flags);

handler_free(irq_trampoline[irq]);
return 0;
}

Expand All @@ -147,7 +121,13 @@ int free_irq(int irq)
void INITPROC irq_init(void)
{
/* use INT 0x80h for system calls */
int_handler_add(0x80, 0x80, _irqit, NULL);
int_handler_add(IDX_SYSCALL, 0x80, _irqit);

#ifdef CONFIG_ARCH_IBMPC
/* catch INT 0x00h divide by zero trap */
irq_action[IDX_DIVZERO] = div0_handler;
int_handler_add(IDX_DIVZERO, 0x00, _irqit);
#endif

#if defined(CONFIG_TIMER_INT0F) || defined(CONFIG_TIMER_INT1C)
/* Use IRQ 7 vector (simulated by INT 0Fh) for timer interrupt handler */
Expand Down
25 changes: 5 additions & 20 deletions elks/arch/i86/kernel/irqtab.S
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
#include <linuxmt/trace.h>
#include <arch/asm-offsets.h>
#include <arch/ports.h>
#include <arch/irq.h>

.arch i8086, nojumps
.code16
Expand Down Expand Up @@ -71,7 +72,7 @@ int_vector_set:
!
! There are three possible cases to cope with
!
! Interrupted user mode or syscall (_gint_count == 0)
! Syscall or Interrupted user mode (_gint_count == 0)
! Switch to process's kernel stack
! Optionally, check (SS == current->t_regs.ss)
! and panic on failure
Expand Down Expand Up @@ -190,7 +191,7 @@ save_regs:
// ds:[di] has IRQ number
//
movb (%di),%al
cmpb $0x80,%al
cmpb $IDX_SYSCALL,%al
jne updct
//
// ----------PROCESS SYSCALL----------
Expand Down Expand Up @@ -236,14 +237,8 @@ ret_from_syscall:
/*
!
! ----------PROCESS INTERRUPT----------
!
! Update intr_count
!
*/
updct:
#ifdef CHECK_SCHED
incw intr_count // only needed for schedule during interrupt warning
#endif
//
// Call the C code
//
Expand Down Expand Up @@ -319,13 +314,7 @@ a6: out %al,$PIC1_CMD // Ack on primary controller
//
was_trap:
//
// Restore intr_count
//
#ifdef CHECK_SCHED
decw intr_count
#endif
//
// Now look at rescheduling
// Look at rescheduling
//
cmpw $1,_gint_count
jne restore_regs // No
Expand Down Expand Up @@ -404,7 +393,7 @@ idle_halt:
ret

.data
.global intr_count
.global _gint_count
.global endistack
.global istack
.extern current
Expand All @@ -416,10 +405,6 @@ org_irq0: // original BIOS IRQ 0 vector
.long 0
_gint_count: // General interrupts count. Start with 1
.word 1 // because init_task() is in kernel mode
#ifdef CHECK_SCHED
intr_count: // Hardware interrupts count
.word 0
#endif
#ifdef CHECK_SS
pmsg: .ascii "Running unknown code"
.byte 0
Expand Down
1 change: 1 addition & 0 deletions elks/arch/i86/kernel/process.c
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@ void kfork_proc(void (*addr)())
t = find_empty_process();

t->t_xregs.cs = kernel_cs; /* Run in kernel space */
/* All other t_regs values invalid for idle task or handlers interrupting idle task */
t->t_regs.ds = t->t_regs.es = t->t_regs.ss = kernel_ds;
if (addr)
arch_build_stack(t, addr);
Expand Down
22 changes: 16 additions & 6 deletions elks/include/arch/irq.h
Original file line number Diff line number Diff line change
@@ -1,22 +1,30 @@
#ifndef __ARCH_8086_IRQ_H
#define __ARCH_8086_IRQ_H

#include <linuxmt/types.h>

/* irq.c*/
#ifdef __KERNEL__
/* irq numbers >= 16 are hardware exceptions/traps or syscall */
#define IDX_SYSCALL 16
#define IDX_DIVZERO 17
#define NR_IRQS 18 /* = # IRQs plus special indexes above */

#define INT_GENERIC 0 // use the generic interrupt handler (aka '_irqit')
#define INT_SPECIFIC 1 // use a specific interrupt handler

#ifndef __ASSEMBLER__
#include <linuxmt/types.h>

/* irq.c*/
typedef void (* int_proc) (void); // any INT handler
typedef void (* irq_handler) (int,struct pt_regs *); // IRQ handler

void do_IRQ(int,void *);
void do_IRQ(int,struct pt_regs *);
void div0_handler(int, struct pt_regs *);
int request_irq(int,irq_handler,int hflag);
int free_irq(int irq);
void int_vector_set (int vect, int_proc proc, int seg);
void _irqit (void);

/* irqtab.S */
void int_vector_set (int vect, word_t proc, word_t seg);
void _irqit (void);

/* irq-8259.c, irq-8018x.c*/
void initialize_irq(void);
Expand All @@ -26,6 +34,8 @@ int remap_irq(int);
int irq_vector(int irq);

void idle_halt(void);
#endif /* __ASSEMBLER__ */
#endif /* __KERNEL__ */

#ifdef __ia16__
#define save_flags(x) \
Expand Down
2 changes: 1 addition & 1 deletion elks/include/linuxmt/sched.h
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@ extern int task_slots_unused;

extern volatile jiff_t jiffies; /* ticks updated by the timer interrupt*/
extern pid_t last_pid;
extern int intr_count;
extern int _gint_count;

extern struct timeval xtime;
extern jiff_t xtime_jiffies;
Expand Down
1 change: 1 addition & 0 deletions elks/include/linuxmt/signal.h
Original file line number Diff line number Diff line change
Expand Up @@ -238,6 +238,7 @@ extern int send_sig(sig_t,struct task_struct *,int);
extern void arch_setup_sighandler_stack(register struct task_struct *,
__kern_sighandler_t,unsigned);
extern void ctrl_alt_del(void);
extern int sys_kill(pid_t, sig_t);
#endif /* __KERNEL__*/

#endif
1 change: 1 addition & 0 deletions elks/init/main.c
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,7 @@ void start_kernel(void)

/*
* We are now the idle task. We won't run unless no other process can run.
* The idle task always runs with _gint_count == 1 (switched from user mode syscall)
*/
while (1) {
#if TIMER_TEST
Expand Down
1 change: 1 addition & 0 deletions elks/kernel/printk.c
Original file line number Diff line number Diff line change
Expand Up @@ -286,6 +286,7 @@ void halt(void)
/* Lock up with infinite loop */
kputs("\nSYSTEM HALTED - Press CTRL-ALT-DEL to reboot:");

/*clr_irq();*/ /* uncomment to halt interrupt handlers, but then CAD won't work */
while (1)
idle_halt();
}
Expand Down
4 changes: 2 additions & 2 deletions elks/kernel/sched.c
Original file line number Diff line number Diff line change
Expand Up @@ -67,10 +67,10 @@ void schedule(void)
prev = current;

#ifdef CHECK_SCHED
if (intr_count) {
if (_gint_count > 1) { /* neither user nor idle task was running */
/* Taking a timer IRQ during another IRQ or while in kernel space is
* quite legal. We just dont switch then */
panic("SCHED: schedule() called from interrupt, intr_count %d", intr_count);
panic("schedule from interrupt\n");
}
#endif

Expand Down
2 changes: 0 additions & 2 deletions elks/kernel/sys.c
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,6 @@

static int C_A_D = 1;

extern int sys_kill(pid_t, sig_t);

/*
* Reboot system call: for obvious reasons only root may call it, and even
* root needs to set up some magic numbers in the registers so that some
Expand Down
Loading