Keyboard/char input buffering and irq handling
authorBarret Rhoden <brho@cs.berkeley.edu>
Sat, 24 Mar 2012 01:08:44 +0000 (18:08 -0700)
committerBarret Rhoden <brho@cs.berkeley.edu>
Sat, 24 Mar 2012 01:08:44 +0000 (18:08 -0700)
Each arch needs to have interrupt handlers for their console input that
call the appropriate routine kmsgs, which will handle putting the char
in the console buffer.  Take a look at x86's.  If this is somehow
fundamentally incompatible with RISCV, let me know.

This techincally adds to a kernel header, but no one should need to
rebuild anything.

kern/arch/i686/init.c
kern/include/console.h [new file with mode: 0644]
kern/include/ros/ring_buffer.h
kern/src/Makefrag
kern/src/console.c [new file with mode: 0644]
kern/src/init.c
kern/src/manager.c

index 69c93a9..47d7c28 100644 (file)
 #include <arch/ioapic.h>
 #include <arch/console.h>
 #include <arch/perfmon.h>
+#include <console.h>
 
-#include <monitor.h>
+/* irq handler for the console (kb, serial, etc) */
+static void irq_console(struct trapframe *tf, void *data)
+{
+       int c = cons_getc();
+       if (!c)
+               return;
+       /* Do our work in an RKM, instead of interrupt context */
+       if (c == 'G')
+               send_kernel_message(core_id(), __run_mon, 0, 0, 0, KMSG_ROUTINE);
+       else
+               send_kernel_message(core_id(), __cons_add_char, (long)&cons_buf,
+                                   (long)c, 0, KMSG_ROUTINE);
+}
+
+static void cons_irq_init(void)
+{
+       register_interrupt_handler(interrupt_handlers, 1 + PIC1_OFFSET, irq_console,
+                                  0);
+       register_interrupt_handler(interrupt_handlers, 3 + PIC1_OFFSET, irq_console,
+                                  0);
+       register_interrupt_handler(interrupt_handlers, 4 + PIC1_OFFSET, irq_console,
+                                  0);
+       /* route kb and both serial interrupts to core 0 */
+#ifdef __CONFIG_ENABLE_MPTABLES__
+       ioapic_route_irq(1, 0);
+       ioapic_route_irq(3, 0);
+       ioapic_route_irq(4, 0);
+#else 
+       pic_unmask_irq(1);      /* keyboard */
+       pic_unmask_irq(3);      /* serial 2 or 4 */
+       pic_unmask_irq(4);      /* serial 1 or 3 */
+       unmask_lapic_lvt(LAPIC_LVT_LINT0);
+#endif /* __CONFIG_ENABLE_MPTABLES__ */
+}
 
 void arch_init()
 {
@@ -56,34 +90,5 @@ void arch_init()
        #endif // __CONFIG_NETWORKING__
 
        perfmon_init();
-               
-#ifdef __CONFIG_MONITOR_ON_INT__
-       /* Handler to read a char from the interrupt source and call the monitor.
-        * Need to read the character so the device will send another interrupt.
-        * Note this will read from both the serial and the keyboard, and throw away
-        * the result.  We condition, since we don't want to trigger on a keyboard
-        * up interrupt */
-       void mon_int(struct trapframe *tf, void *data)
-       {
-               // Enable interrupts here so that we can receive 
-               // other interrupts (e.g. from the NIC)
-               enable_irq();
-               if (cons_getc())
-                       monitor(0);
-       }
-       register_interrupt_handler(interrupt_handlers, 1 + PIC1_OFFSET, mon_int, 0);
-       register_interrupt_handler(interrupt_handlers, 3 + PIC1_OFFSET, mon_int, 0);
-       register_interrupt_handler(interrupt_handlers, 4 + PIC1_OFFSET, mon_int, 0);
-# ifdef __CONFIG_ENABLE_MPTABLES__
-       ioapic_route_irq(1, 0);
-       ioapic_route_irq(3, 0);
-       ioapic_route_irq(4, 0);
-# else 
-       pic_unmask_irq(1);      /* keyboard */
-       pic_unmask_irq(3);      /* serial 2 or 4 */
-       pic_unmask_irq(4);      /* serial 1 or 3 */
-       unmask_lapic_lvt(LAPIC_LVT_LINT0);
-# endif /* __CONFIG_ENABLE_MPTABLES__ */
-       enable_irq(); /* we want these interrupts to work in the kernel. */
-#endif /* __CONFIG_MONITOR_ON_INT__ */
+       cons_irq_init();
 }
diff --git a/kern/include/console.h b/kern/include/console.h
new file mode 100644 (file)
index 0000000..08d80e9
--- /dev/null
@@ -0,0 +1,40 @@
+/* Copyright (c) 2012 The Regents of the University of California
+ * Barret Rhoden <brho@cs.berkeley.edu>
+ * See LICENSE for details.
+ *
+ * Console (Keyboard/serial/whatever) related functions. */
+
+#ifndef ROS_KERN_CONSOLE_H
+#define ROS_KERN_CONSOLE_H
+
+#include <atomic.h>
+#include <kthread.h>
+#include <trap.h>
+
+#define KB_BUF_SIZE 256        /* Make sure this is a power of 2 */
+
+/* Ring buffer for keyboard/character devices.  Might make a more generic
+ * version in the future (allowing both sides to block, etc).  Adding will drop
+ * any overflow, and getting will block til the full amount is read. */
+struct kb_buffer {
+       unsigned int prod_idx;
+       unsigned int cons_idx;
+       spinlock_t                                                      buf_lock;
+       struct semaphore                                        buf_sem;
+       char                                                            buf[KB_BUF_SIZE];
+};
+extern struct kb_buffer cons_buf;      /* kernel's console buffer */
+
+void kb_buf_init(struct kb_buffer *kb);
+/* These are not irq-safe.  and get will block. */
+void kb_add_to_buf(struct kb_buffer *kb, char c);
+void kb_get_from_buf(struct kb_buffer *kb, char *dst, size_t cnt);
+
+/* Kernel messages associated with the console.  Arch-specific interrupt
+ * handlers need to call these.  For add char, a0 = &cons_buf and a1 = the char
+ * you read.  Call __run_mon on your 'magic' input.  */
+void __cons_add_char(struct trapframe *tf, uint32_t srcid, long a0, long a1,
+                     long a2);
+void __run_mon(struct trapframe *tf, uint32_t srcid, long a0, long a1, long a2);
+
+#endif /* ROS_KERN_CONSOLE_H */
index 1a114b3..540a673 100644 (file)
 #define xen_rmb() rmb()
 #define xen_wmb() wmb()
 
+/* Helpers for generic power-of-2 ring buffers */
+#define __ring_nr_empty(sz, prod, cons) ((sz) - ((prod) - (cons)))
+#define __ring_empty(prod, cons) ((prod) == (cons))
+#define __ring_nr_full(prod, cons) ((prod) - (cons))
+#define __ring_full(sz, prod, cons) (__ring_nr_empty((sz), (prod), (cons)) == 0)
+
 typedef unsigned int RING_IDX;
 
 /* Round a 32-bit unsigned constant down to the nearest power of two. */
index 35dda58..b7b735f 100644 (file)
@@ -58,6 +58,7 @@ KERN_SRCFILES := $(KERN_ARCH_SRCFILES) \
                  $(KERN_SRC_DIR)/alarm.c \
                  $(KERN_SRC_DIR)/kdebug.c \
                  $(KERN_SRC_DIR)/ucq.c \
+                 $(KERN_SRC_DIR)/console.c \
                  $(KERN_SRC_DIR)/arsc.c
 
 # Only build files if they exist.
diff --git a/kern/src/console.c b/kern/src/console.c
new file mode 100644 (file)
index 0000000..20d7d3e
--- /dev/null
@@ -0,0 +1,107 @@
+/* Copyright (c) 2012 The Regents of the University of California
+ * Barret Rhoden <brho@cs.berkeley.edu>
+ * See LICENSE for details.
+ *
+ * Console (Keyboard/serial/whatever) related functions. */
+
+#include <console.h>
+#include <ros/ring_buffer.h>
+#include <monitor.h>
+#include <stdio.h>
+
+struct kb_buffer cons_buf;
+
+void kb_buf_init(struct kb_buffer *kb)
+{
+       kb->prod_idx = 0;
+       kb->cons_idx = 0;
+       spinlock_init(&kb->buf_lock);
+       init_sem(&kb->buf_sem, 0);
+       /* no need to memset the buffer - we only read something that is written */
+}
+
+/* Producers don't block - their input is dropped.  Should be rare for now; if
+ * it happens, it's probably a bug. */
+void kb_add_to_buf(struct kb_buffer *kb, char c)
+{
+       /* make sure we're a power of 2 */
+       static_assert(KB_BUF_SIZE == __RD32(KB_BUF_SIZE));
+       struct kthread *sleeper;
+       bool was_empty = FALSE;
+       spin_lock(&kb->buf_lock);
+       if (!__ring_full(KB_BUF_SIZE, kb->prod_idx, kb->cons_idx)) {
+               if (__ring_empty(kb->prod_idx, kb->cons_idx))
+                       was_empty = TRUE;
+               kb->buf[kb->prod_idx % KB_BUF_SIZE] = c;        // compiler should shift
+               kb->prod_idx++;
+       } else {
+               /* else drop the char */
+               printk("[kernel] KB buffer overflowed %c\n", c);
+       }
+       spin_unlock(&kb->buf_lock);
+       /* up the sem when it goes from empty->non_empty.   rule for syncing with
+        * blockers: if there are any items in the buffer, either the sem is upped,
+        * or there is an active consumer.  consumers immediately down (to become an
+        * active consumer). */
+       if (was_empty) {
+               sleeper = __up_sem(&kb->buf_sem, FALSE);
+               if (sleeper)
+                       kthread_runnable(sleeper);
+       }
+       /* also note that multiple readers on the console/serial are going to fight
+        * for input and it is going to get interleaved - broader issue */
+}
+
+/* Will read cnt chars from the KB buf into dst.  Will block until complete. */
+void kb_get_from_buf(struct kb_buffer *kb, char *dst, size_t cnt)
+{
+       struct kthread *sleeper;
+       unsigned int dst_idx = 0; /* aka, amt copied so far */
+       bool need_an_up = FALSE;
+
+       /* so long as we still need items, we'll sleep til there is activity, then
+        * grab everything we can til the kb buf is empty (or we're done).  If we
+        * didn't empty the buf, we'll need to up the sem later. */
+       while (dst_idx < cnt) {
+               /* this will return immediately if some data is already there, o/w we
+                * block til there is some activity */
+               sleep_on(&kb->buf_sem);
+               spin_lock(&kb->buf_lock);
+               /* under the current scheme, we should only have one active consumer at
+                * a time, so if we woke up, the ring must not be empty. */
+               assert(!__ring_empty(kb->prod_idx, kb->cons_idx));
+               while (!__ring_empty(kb->prod_idx, kb->cons_idx)) {
+                       if (dst_idx == cnt) {
+                               /* we're done, and it's not empty yet */
+                               need_an_up = TRUE;
+                               break;
+                       }
+                       dst[dst_idx] = kb->buf[kb->cons_idx % KB_BUF_SIZE];
+                       kb->cons_idx++;
+                       dst_idx++;
+               }
+               spin_unlock(&kb->buf_lock);
+       }
+       /* Remember: if the buf is non empty, there is either an active consumer or
+        * the sem is upped. */
+       if (need_an_up) {
+               sleeper = __up_sem(&kb->buf_sem, FALSE);
+               if (sleeper)
+                       kthread_runnable(sleeper);
+       }
+}
+
+/* Kernel messages associated with the console.  Arch-specific interrupt
+ * handlers need to call these.  For add char, a0 = &cons_buf and a1 = the char
+ * you read.  Call __run_mon on your 'magic' input.  */
+void __cons_add_char(struct trapframe *tf, uint32_t srcid, long a0, long a1,
+                     long a2)
+{
+       kb_add_to_buf((struct kb_buffer*)a0, (char)a1);
+}
+
+void __run_mon(struct trapframe *tf, uint32_t srcid, long a0, long a1,
+               long a2)
+{
+       monitor(0);
+}
index 5cdbaf4..d744b11 100644 (file)
@@ -44,6 +44,7 @@
 #include <kthread.h>
 #include <net.h>
 #include <eth_audio.h>
+#include <console.h>
 
 // zra: flag for Ivy
 int booting = 1;
@@ -94,6 +95,7 @@ void kernel_init(multiboot_info_t *mboot_info)
        sysenter_init();
        timer_init();
        train_timing();
+       kb_buf_init(&cons_buf);
        
        arch_init();
        block_init();
index 04304b4..1e1fece 100644 (file)
@@ -118,11 +118,6 @@ void manager_brho(void)
        }
        while (1) {
                enable_irq();
-               /* one time keyboard / serial check for some magic, then monitor */
-               if (cons_getc() == 'G') {
-                       printk("Heard the call of the giraffe!\n");
-                       monitor(0);
-               }
                process_routine_kmsg(0);
                /* would like to idle here, but without reset stacks, people will run
                 * off the kstack.  so just idle if we have an owning proc (which we