kernel - Fix live lock in vfs_conf.c mountroot>
authorMatthew Dillon <dillon@apollo.backplane.com>
Tue, 14 Jul 2015 20:33:49 +0000 (13:33 -0700)
committerMatthew Dillon <dillon@apollo.backplane.com>
Tue, 14 Jul 2015 20:46:12 +0000 (13:46 -0700)
* The mountroot> prompt calls cngetc() to process user input.  However, this
  function hard loops and can prevent other kernel threads from running on
  the current cpu.

* Rearrange the code to use cncheckc() and a 1/25 second tsleep().

* Fix a bug in the syscons code where NOKEY was not being properly returned
  as documented.  Modify all use cases to handle NOKEY.  This allows us to
  differentiate between a keyboard present but not key pressed and a keyboard
  not present.

* Pull the automatic polling mode code out of cncheckc() (or more precisely,
  out of sccncheckc()) and add a new cnpoll() API function to set it manually.

  This fixes issues in vfs_conf when normal keyboard processing interrupts
  are operational and cncheckc() is used with a tsleep() delay.  The normal
  processing interrupt wound up eating the keystrokes so the cncheckc()
  basically always failed.

  cncheckc() in general also always had a small window of opportunity where
  a keystroke could be lost due loops on it.

* Call cnpoll() in various places, such as when entering the debugger,
  asking for input in vfs_conf, and a few other places.

14 files changed:
sys/ddb/db_command.c
sys/dev/misc/dcons/dcons_os.c
sys/dev/misc/syscons/syscons.c
sys/dev/serial/sio/sio.c
sys/kern/kern_ktr.c
sys/kern/kern_shutdown.c
sys/kern/tty_cons.c
sys/kern/vfs_conf.c
sys/platform/pc64/isa/clock.c
sys/platform/pc64/x86_64/dump_machdep.c
sys/platform/pc64/x86_64/minidump_machdep.c
sys/platform/pc64/x86_64/mp_machdep.c
sys/platform/vkernel64/platform/console.c
sys/sys/cons.h

index 70c39ff..82b16d7 100644 (file)
@@ -451,6 +451,7 @@ db_command_loop(void)
        /*
         * Initialize 'prev' and 'next' to dot.
         */
+       cnpoll(TRUE);
        db_prev = db_dot;
        db_next = db_dot;
 
@@ -467,6 +468,7 @@ db_command_loop(void)
            db_command(&db_last_command, db_command_table,
                    SET_BEGIN(db_cmd_set), SET_LIMIT(db_cmd_set));
        }
+       cnpoll(FALSE);
 }
 
 void
index 95c7362..2065a0e 100644 (file)
@@ -147,7 +147,7 @@ static cn_checkc_t  dcons_cncheckc;
 static cn_putc_t       dcons_cnputc;
 
 CONS_DRIVER(dcons, dcons_cnprobe, dcons_cninit, dcons_cninit_fini,
-           NULL, dcons_cngetc, dcons_cncheckc, dcons_cnputc, NULL);
+           NULL, dcons_cngetc, dcons_cncheckc, dcons_cnputc, NULL, NULL);
 
 #if __FreeBSD_version >= 502122
 static gdb_probe_f dcons_dbg_probe;
index a95b3a1..4184372 100644 (file)
@@ -255,9 +255,10 @@ static cn_checkc_t sccncheckc;
 static cn_putc_t       sccnputc;
 static cn_dbctl_t      sccndbctl;
 static cn_term_t       sccnterm;
+static cn_poll_t       sccnpoll;
 
 CONS_DRIVER(sc, sccnprobe, sccninit, sccninit_fini, sccnterm,
-           sccngetc, sccncheckc, sccnputc, sccndbctl);
+           sccngetc, sccncheckc, sccnputc, sccndbctl, sccnpoll);
 
 static d_open_t        scopen;
 static d_close_t       scclose;
@@ -709,6 +710,10 @@ sckbdevent(keyboard_t *thiskbd, int event, void *arg)
 
     switch (event) {
     case KBDIO_KEYINPUT:
+       if (sc->kbd && sc->kbd->kb_flags & KB_POLLED) {
+               lwkt_reltoken(&tty_token);
+               return 0;
+       }
        break;
     case KBDIO_UNLOADING:
        syscons_lock();
@@ -1743,6 +1748,23 @@ sccncheckc(void *private)
     return sccngetch(SCGETC_NONBLOCK);
 }
 
+/*
+ * Set polling mode (also disables the tty feed), use when
+ * polling for console key input.
+ */
+static void
+sccnpoll(void *private, int on)
+{
+    scr_stat *scp;
+
+    syscons_lock();
+    sc_touch_scrn_saver();
+    scp = sc_console->sc->cur_scp;     /* XXX */
+    syscons_unlock();
+    if (scp->sc->kbd)
+           kbd_poll(scp->sc->kbd, on);
+}
+
 static void
 sccndbctl(void *private, int on)
 {
@@ -1815,9 +1837,7 @@ sccngetch(int flags)
     scp->kbd_mode = K_XLATE;
     kbd_ioctl(scp->sc->kbd, KDSKBMODE, (caddr_t)&scp->kbd_mode);
 
-    kbd_poll(scp->sc->kbd, TRUE);
     c = scgetc(scp->sc, SCGETC_CN | flags);
-    kbd_poll(scp->sc->kbd, FALSE);
 
     scp->kbd_mode = cur_mode;
     kbd_ioctl(scp->sc->kbd, KDSKBMODE, (caddr_t)&scp->kbd_mode);
@@ -1837,6 +1857,9 @@ sccngetch(int flags)
        }
        return c;       /* XXX */
     case NOKEY:
+       if (flags & SCGETC_NONBLOCK)
+               return c;
+       /* fall through */
     case ERRKEY:
     default:
        return -1;
index d61aa0a..90a282c 100644 (file)
@@ -2856,7 +2856,7 @@ static cn_putc_t siocnputc;
 
 #if defined(__i386__) || defined(__x86_64__)
 CONS_DRIVER(sio, siocnprobe, siocninit, siocninit_fini,
-           NULL, siocngetc, siocncheckc, siocnputc, NULL);
+           NULL, siocngetc, siocncheckc, siocnputc, NULL, NULL);
 #endif
 
 /* To get the GDB related variables */
index 9048f18..795d32d 100644 (file)
 #include <sys/time.h>
 #include <sys/malloc.h>
 #include <sys/spinlock.h>
+#include <sys/kbio.h>
+#include <sys/ctype.h>
+
 #include <sys/thread2.h>
 #include <sys/spinlock2.h>
-#include <sys/ctype.h>
 
 #include <machine/cpu.h>
 #include <machine/cpufunc.h>
@@ -528,17 +530,21 @@ DB_SHOW_COMMAND(ktr, db_ktr_all)
                return;
        }
        /*
-        * Lopp throug all the buffers and print the content of them, sorted
+        * Loop throug all the buffers and print the content of them, sorted
         * by the timestamp.
         */
        while (1) {
                int counter;
                u_int64_t highest_ts;
-               int highest_cpu;
                struct ktr_entry *kp;
+               int highest_cpu;
+               int c;
 
-               if (a_flag == 1 && cncheckc() != -1)
-                       return;
+               if (a_flag == 1) {
+                       c = cncheckc();
+                       if (c != -1 && c != NOKEY)
+                               return;
+               }
                highest_ts = 0;
                highest_cpu = -1;
                /*
index af6644d..26a9a60 100644 (file)
@@ -65,6 +65,7 @@
 #include <sys/sysproto.h>
 #include <sys/device.h>
 #include <sys/cons.h>
+#include <sys/kbio.h>
 #include <sys/shm.h>
 #include <sys/kern_syscall.h>
 #include <vm/vm_map.h>
@@ -484,6 +485,7 @@ static void
 shutdown_panic(void *junk, int howto)
 {
        int loop;
+       int c;
 
        if (howto & RB_DUMP) {
                if (PANIC_REBOOT_WAIT_TIME != 0) {
@@ -495,7 +497,8 @@ shutdown_panic(void *junk, int howto)
                                     loop > 0; --loop) {
                                        DELAY(1000 * 100); /* 1/10th second */
                                        /* Did user type a key? */
-                                       if (cncheckc() != -1)
+                                       c = cncheckc();
+                                       if (c != -1 && c != NOKEY)
                                                break;
                                }
                                if (!loop)
index 779399a..f2bb87d 100644 (file)
@@ -133,7 +133,7 @@ static uint32_t console_rdev;
 SYSCTL_INT(_kern, OID_AUTO, console_rdev, CTLFLAG_RW,
        &console_rdev, 0, "");
 
-CONS_DRIVER(cons, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL);
+CONS_DRIVER(cons, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL);
 SET_DECLARE(cons_set, struct consdev);
 
 void
@@ -523,6 +523,15 @@ cncheckc(void)
        return ((*cn_tab->cn_checkc)(cn_tab->cn_private));
 }
 
+void
+cnpoll(int on)
+{
+       if ((cn_tab == NULL) || cn_mute)
+               return;
+       if (cn_tab->cn_poll)
+               ((*cn_tab->cn_poll)(cn_tab->cn_private, on));
+}
+
 void
 cnputc(int c)
 {
index bfa4e7a..5ea37af 100644 (file)
@@ -55,6 +55,7 @@
 #include <sys/diskslice.h>
 #include <sys/conf.h>
 #include <sys/cons.h>
+#include <sys/kbio.h>
 #include <sys/device.h>
 #include <sys/disk.h>
 #include <sys/namecache.h>
@@ -543,20 +544,26 @@ static int
 getline(char *cp, int limit)
 {
        char *lp;
+       int dummy;
        int c;
 
        lp = cp;
+       cnpoll(TRUE);
        for (;;) {
-               c = cngetc();
+               c = cncheckc();
 
                switch (c) {
+               case NOKEY:
+                       tsleep(&dummy, 0, "cnpoll", hz / 25);
+                       break;
                case -1:
-                       return(-1);
+                       goto done;
                case '\n':
                case '\r':
                        kprintf("\n");
                        *lp++ = '\0';
-                       return(0);
+                       c = 0;
+                       goto done;
                case '\b':
                case '\177':
                        if (lp > cp) {
@@ -587,6 +594,9 @@ getline(char *cp, int limit)
                        continue;
                }
        }
+done:
+       cnpoll(FALSE);
+       return c;
 }
 
 /*
index adb5c64..ccdb132 100644 (file)
 #include <sys/bus.h>
 #include <sys/sysctl.h>
 #include <sys/cons.h>
+#include <sys/kbio.h>
 #include <sys/systimer.h>
 #include <sys/globaldata.h>
-#include <sys/thread2.h>
 #include <sys/machintr.h>
 #include <sys/interrupt.h>
 
+#include <sys/thread2.h>
+
 #include <machine/clock.h>
 #include <machine/cputypes.h>
 #include <machine/frame.h>
@@ -798,10 +800,14 @@ startrtclock(void)
        freq = calibrate_clocks();
 #ifdef CLK_CALIBRATION_LOOP
        if (bootverbose) {
-               kprintf(
-               "Press a key on the console to abort clock calibration\n");
-               while (cncheckc() == -1)
+               int c;
+
+               cnpoll(TRUE);
+               kprintf("Press a key on the console to "
+                       "abort clock calibration\n");
+               while ((c = cncheckc()) == -1 || c == NOKEY)
                        calibrate_clocks();
+               cnpoll(FALSE);
        }
 #endif
 
index 3a123e2..41b01eb 100644 (file)
@@ -34,6 +34,7 @@
 #include <sys/device.h>
 #include <sys/kernel.h>
 #include <sys/kerneldump.h>
+#include <sys/kbio.h>
 #include <vm/vm.h>
 #include <vm/pmap.h>
 #include <machine/elf.h>
@@ -169,6 +170,7 @@ cb_dumpdata(struct md_pa *mdp, int seqnr, void *arg)
 
        kprintf("  chunk %d: %ldMB (%ld pages)", seqnr, PG2MB(pgs), pgs);
 
+       cnpoll(TRUE);
        while (pgs) {
                chunk = pgs;
                if (chunk > (max_iosize/PAGE_SIZE))
@@ -193,12 +195,16 @@ cb_dumpdata(struct md_pa *mdp, int seqnr, void *arg)
 
                /* Check for user abort. */
                c = cncheckc();
-               if (c == 0x03)
-                       return (ECANCELED);
-               if (c != -1)
+               if (c == 0x03) {
+                       error = ECANCELED;
+                       goto done;
+               }
+               if (c != -1 && c != NOKEY)
                        kprintf(" (CTRL-C to abort) ");
        }
        kprintf(" ... %s\n", (error) ? "fail" : "ok");
+done:
+       cnpoll(FALSE);
        return (error);
 }
 
index 6c86893..33164b0 100644 (file)
@@ -35,6 +35,7 @@
 #include <sys/kernel.h>
 #include <sys/kerneldump.h>
 #include <sys/msgbuf.h>
+#include <sys/kbio.h>
 #include <vm/vm.h>
 #include <vm/vm_kern.h>
 #include <vm/pmap.h>
@@ -169,7 +170,7 @@ blk_write(struct dumperinfo *di, char *ptr, vm_paddr_t pa, size_t sz)
        c = cncheckc();
        if (c == 0x03)
                return (ECANCELED);
-       if (c != -1)
+       if (c != -1 && c != NOKEY)
                kprintf(" (CTRL-C to abort) ");
 
        return (0);
@@ -192,6 +193,7 @@ minidumpsys(struct dumperinfo *di)
        struct minidumphdr mdhdr;
        struct mdglobaldata *md;
 
+       cnpoll(TRUE);
        counter = 0;
        /*
         * Walk page table pages, set bits in vm_page_dump.
@@ -412,9 +414,11 @@ minidumpsys(struct dumperinfo *di)
        /* Signal completion, signoff and exit stage left. */
        dev_ddump(di->priv, NULL, 0, 0, 0);
        kprintf("\nDump complete\n");
+       cnpoll(FALSE);
        return;
 
  fail:
+       cnpoll(FALSE);
        if (error < 0)
                error = -error;
 
index b62cbef..2461763 100644 (file)
@@ -474,8 +474,10 @@ start_all_aps(u_int boot_addr)
                        CHECK_PRINT("trace");   /* show checkpoints */
                        /* better panic as the AP may be running loose */
                        kprintf("panic y/n? [y] ");
+                       cnpoll(TRUE);
                        if (cngetc() != 'n')
                                panic("bye-bye");
+                       cnpoll(FALSE);
                }
                CHECK_PRINT("trace");           /* show checkpoints */
        }
index 93f2c7a..66ece78 100644 (file)
@@ -242,7 +242,7 @@ static cn_checkc_t  vconscheckc;
 static cn_putc_t       vconsputc;
 
 CONS_DRIVER(vcons, vconsprobe, vconsinit, vconsinit_fini, vconsterm, vconsgetc,
-               vconscheckc, vconsputc, NULL);
+               vconscheckc, vconsputc, NULL, NULL);
 
 static struct termios init_tio;
 static struct consdev *vconsole;
index 77feabd..8da59e8 100644 (file)
@@ -57,6 +57,7 @@ typedef       int     cn_getc_t (void *);
 typedef        int     cn_checkc_t (void *);
 typedef        void    cn_putc_t (void *, int);
 typedef        void    cn_dbctl_t (void *, int);
+typedef void   cn_poll_t (void *, int);
 
 struct consdev {
        cn_probe_t      *cn_probe;      /* probe hardware */
@@ -67,6 +68,7 @@ struct consdev {
        cn_checkc_t     *cn_checkc;     /* kernel test char ready */
        cn_putc_t       *cn_putc;       /* kernel putchar interface */
        cn_dbctl_t      *cn_dbctl;      /* debugger control interface */
+       cn_poll_t       *cn_poll;       /* polling mode control */
        struct  tty *cn_tp;     /* tty structure for console device */
        cdev_t  cn_dev;         /* device after cn_init_fini tie-in */
        short   cn_pri;         /* pecking order; the higher the better */
@@ -96,9 +98,10 @@ extern       int sysbeep_enable;             /* enable audio system beep */
 extern struct consdev *cn_tab; /* console device */
 extern  struct consdev *gdb_tab;/* gdb debugger device */
 
-#define CONS_DRIVER(name, probe, init, initfini, term, getc, checkc, putc, dbctl)      \
+#define CONS_DRIVER(name, probe, init, initfini, term, getc, checkc, putc, dbctl, poller)      \
        static struct consdev name##_consdev = {                        \
-               probe, init, initfini, term, getc, checkc, putc, dbctl  \
+               probe, init, initfini, term, getc, checkc, putc, dbctl, \
+               poller                                                  \
        };                                                              \
        DATA_SET(cons_set, name##_consdev)
 
@@ -109,6 +112,7 @@ void        cninit (void);
 void   cninit_finish (void);
 void   cndbctl (int);
 void   cnputc (int);
+void   cnpoll (int);
 
 #endif /* _KERNEL */