1/3 Fix serial console flow control, serial.c

1/3 Fix serial console flow control, serial.c

Post by Glen Turne » Tue, 15 Oct 2002 03:00:08



--- linux-2.4.19/drivers/char/serial.c    Fri Oct 11 15:51:36 2002
+++ linux-2.4.20-pre8/drivers/char/serial.c    Fri Oct 11 15:47:27 2002
@@ -62,10 +62,16 @@
   *        Robert Schwebel <rob...@schwebel.de>,
   *        Juergen Beisert <jbeis...@eurodsn.de>,
   *        Theodore Ts'o <ty...@mit.edu>
+ *
+ * 10/02: Fixed flow control (CTS) bugs in serial console by adding
+ *      status control (DSR, DCD) too. This required new heirarchy of
+ *      serial console output functions. Added statistics so users
+ *      can debug lack of console output. Incremented to version 5.05d.
+ *      Glen Turner <glen.turner+li...@aarnet.edu.au>
   */

-static char *serial_version = "5.05c";
-static char *serial_revdate = "2001-07-08";
+static char *serial_version = "5.05d";
+static char *serial_revdate = "2002-10-11";

  /*
   * Serial driver configuration section.  Here are the various options:
@@ -97,8 +103,10 @@
   *        ACPI 2.0.
   */

+#include <limits.h>       /* For INT_MAX */
  #include <linux/config.h>
  #include <linux/version.h>
+#include <linux/sysctl.h>

  #undef SERIAL_PARANOIA_CHECK
  #define CONFIG_SERIAL_NOPAUSE_IO
@@ -248,6 +256,10 @@
  #define _INLINE_
  #endif

+#ifdef CONFIG_SERIAL_CONSOLE
+static int __init serial_console_sysctl_init(void);
+#endif
+
  static char *serial_name = "Serial driver";

  static DECLARE_TASK_QUEUE(tq_serial);
@@ -5543,6 +5555,9 @@
  #ifdef ENABLE_SERIAL_PNP
         probe_serial_pnp();
  #endif
+#ifdef CONFIG_SERIAL_CONSOLE
+       serial_console_sysctl_init();
+#endif
      return 0;
  }

@@ -5778,80 +5793,369 @@
   * ------------------------------------------------------------
   * Serial console driver
   * ------------------------------------------------------------
+ *
+ * When flow control is enabled there are some changes between this
+ * code and v5.05c to fix the following pathological conditions:
+ *
+ * - Linux took upwards of 30 minutes to boot when CTS was never
+ * asserted, such as when a modem or terminal server was idle.    This
+ * code checks the DSR and DCD status lines, detecting most cases
+ * where CTS will never be asserted if the terminal server or modem is
+ * configured with status and flow control (AT &C1 &D2 &K3).
+ *
+ * - An attached smart modem would not answer calls during disk
+ * failures.  Hayes AT command modems hang up when a call is
+ * negotiating and a character is receieved from the local machine.
+ * So a stream of constant console mesasges, such as from a failing
+ * disk, would never allow the modem to complete negotiation on an
+ * incoming call.  This condition can be avoided if the kernel flow
+ * control code checks the DCD status line and the modem is configured
+ * to assert DCD upon the conclusion of call negotiation (AT &C1).
+ *
+ * and the following bugs:
+ *
+ * - Security consideration: kernel messages, possibly containing
+ * user-derived text, were sent to modems without active calls.     Smart
+ * modems without active calls are in command mode.  So users had a
+ * path to subvert modem configurations (eg: alter auto-answer,
+ * callback or authentication).     This bug exists without the flow
+ * control option, but can't be fixed without looking at the status
+ * lines (implying that the flow control option is a Good Idea for
+ * security-conscious sites).
+ *
+ * - When CTS timed out v5.05c send the character regardless.  If CTS
+ * is used for media access control, such as on a multi-drop line or
+ * satelite circuit, other users of the circuit would be disrupted.
+ *
+ * - When the Transmit Buffer timed out v5.05 queued the character
+ * regardless.    This could result in a number of the issues above.
+ *
+ * The behaviour *without* flow control remains identical to v5.05c,
+ * with the exception of Transmit Buffer timeouts. These now cause the
+ * message to be discarded (and counted).
+ *
+ * In short, professional RS-232 applications should configure the
+ * flow control option and supply strictly correct status (DSR, DCD)
+ * and flow control (CTS) signals. For example:
+ *
+ *   console=ttyS0,9600n8r
+ *
+ * A quick three-wire hack to grab an Oops message should not use flow
+ * control.  For example:
+ *
+ *   console=ttyS0,9600n8
   */
  #ifdef CONFIG_SERIAL_CONSOLE

-#define BOTH_EMPTY (UART_LSR_TEMT | UART_LSR_THRE)
-
  static struct async_struct async_sercons;
+/* A count of the number of messages, dropped messages and reasons for
+ * dropping messages.  There really should be one of these for each
+ * console=ttyS parameter.  The counters are not yet visible to user
+ * space.
+ */
+static struct serial_console_counters_t serial_console_counters;
+
+/* Maximum number of times to test for an empty transmit buffer.
+ * About 1S.  Can be tuned using dev.serialconsole.timeout-transmit.
+ * This number could be significantly smaller than 1M, as even at
+ * 75bps the tramsit buffer of contemporary UARTs will clear in 0.5S.
+ */
+static int serial_console_attempts_tx = 1000000;
+/* Maximum number of times to test for CTS to be asserted when using
+ * flow control.  About 1S.  Can be tuned using
+ * dev.serialconsole.timeout-cts.
+ */
+static int serial_console_attempts_cts = 1000000;

-/*
- *    Wait for transmitter & holding register to empty
+
+/* Sysctl routines to access serial_console_counters.
+ *
+ * See "sysctl interface" in linux/Docmentaion/DocBook/kernel-api.*
   */
-static inline void wait_for_xmitr(struct async_struct *info)
+static int serial_console_attempts_min = 1;
+static int serial_console_attempts_max = INT_MAX;
+static ctl_table serial_console_sysctl_files[] = {
+ { 1, "messages-total",
+   &(serial_console_counters.messages_total), sizeof(int),
+   0444, NULL,
+   &proc_dointvec },
+ { 2, "messages-dropped",
+   &(serial_console_counters.messages_dropped), sizeof(int),
+   0444, NULL,
+   &proc_dointvec },
+ { 3, "timeouts-transmit",
+   &(serial_console_counters.timeouts_tx), sizeof(int),
+   0444, NULL,
+   &proc_dointvec },
+ { 4, "timeouts-data-set-ready",
+   &(serial_console_counters.timeouts_dsr), sizeof(int),
+   0444, NULL,
+   &proc_dointvec },
+ { 5, "timeouts-data-carrier-detect",
+   &(serial_console_counters.timeouts_cts), sizeof(int),
+   0444, NULL,
+   &proc_dointvec },
+ { 6, "timeouts-clear-to-send",
+   &(serial_console_counters.timeouts_cts), sizeof(int),
+   0444, NULL,
+   &proc_dointvec },
+ { 7, "maximum-attempts-transmit",
+   &serial_console_attempts_tx, sizeof(int),
+   0664, NULL,
+   &proc_dointvec_minmax, NULL, NULL,
+   &serial_console_attempts_min,
+   &serial_console_attempts_max },
+ { 8, "maximum-attempts-clear-to-send",
+   &serial_console_attempts_cts, sizeof(int),
+   0664, NULL,
+   &proc_dointvec_minmax, NULL, NULL,
+   &serial_console_attempts_min,
+   &serial_console_attempts_max },
+ { 0 }  /* Last */
+};
+
+static ctl_table serial_console_sysctl_directory[] = {
+ { 1, "serialconsole",
+   NULL, 0,
+   0555, serial_console_sysctl_files },
+ { 0 }  /* Last */
+};
+
+static ctl_table serial_console_sysctl_root[] = {
+ { CTL_DEV, "dev",
+   NULL, 0,
+   0555, serial_console_sysctl_directory },
+ { 0 }  /* Last */
+};
+
+static struct ctl_table_header *serial_console_sysctl;
+
+
+/* Initialise statitsics for the serial console.
+ *
+ * Called from rs_init(), the serial device initialiser.  Calling this
+ * routine from serial_console_init() leads to an Oops, perhaps the
+ * necessary infrastructure is not in place at that time. Hmmm. -GDT
+ *
+ * Returns: 0, always.
+ */
+static int __init serial_console_sysctl_init(void)
+{
+ memset(&serial_console_counters,
+        0,
+        sizeof(serial_console_counters));
+ serial_console_sysctl =
+   register_sysctl_table(serial_console_sysctl_root,
+             0);  /* Insert at end */
+ return 0;
+}
+
+
+/* There is no
+ *   static void __exit serial_console_sysctl_exit(void)
+ *  as compling this code as a MODULE defeats CONFIG_SERIAL_CONSOLE.
+ * This should be added should Alan Cox succeed in making all code
+ * module-based.
+ */
+
+
+/* Wait for UART's transmitter queue to empty.
+ *
+ * Inputs: info -- RS232 port used as console.
+ * Returns: 0 -- the waiting character can be transmitted
+ *        non-0 -- discard message to be transmitted, as the
+ *             transmit queue is massively long or stuck.
+ * Globals: serial_console_counters.
+ */
+#define BOTH_EMPTY (UART_LSR_TEMT | UART_LSR_THRE)
+
+static inline int serial_console_drain(struct async_struct *info)
  {
- unsigned int status, tmout = 1000000;
+ /* Number of times to test the transmit buffer of the UART. */
+ unsigned int timeout;
+ /* Contents of a UART status register. */
+ unsigned int status;

+ timeout = serial_console_attempts_tx;
+ /* Drain transmit buffer. */
      do {
          status = serial_in(info, UART_LSR);

-     if (status & UART_LSR_BI)
+     /* If Break has been recieved then note that for the
+      * regular user of the serial port.
+      */
+     if (status & UART_LSR_BI) {
           lsr_break_flag = UART_LSR_BI;
+     }

-     if (--tmout == 0)
-         break;
- } while((status & BOTH_EMPTY) != BOTH_EMPTY);
+     if (--timeout == 0) {
+         /* Transmit buffer hasn't emptied in a
+          * reasonable time.  Discard the character.
+          */
+         serial_console_counters.timeouts_tx++;
+         return (info->flags & ASYNC_CONS_FLOW);
+     }
+ } while ((status & BOTH_EMPTY) != BOTH_EMPTY);
+ return 0;
+}
+
+/* Wait for transmitter queue to empty. If using flow control then
+ * check if RS-232 status signals are asserted and wait for flow
+ * control to be asserted.
+ *
+ *
+ * Inputs: info -- RS232 port used as console.
+ * Returns: 0 -- the waiting character can be transmitted
+ *        non-0 -- discard message to be transmitted, usually because
+ *             RS-232 status control lines indicate no reciever.
+ * Globals: lsr_break_flag -- updated if RS-232 Break condition reported.
+ *        serial_console_counters
+ */
+static inline int serial_console_flow(struct async_struct *info)
+{
+ /* Timeout loop tests Clear to Send. */
+ unsigned int timeout;
+ /* Contents of a UART status register. */
+ unsigned int status;

- /* Wait for flow control if necessary */
+ if (serial_console_drain(info))
+ {
+     return !0;
+ }
+
+ /* If flow control is configured then wait for Clear to Send
+  * to be asserted.  Abandon message if no status lines
+  * asserted.
+  */
      if (info->flags & ASYNC_CONS_FLOW) {
-     tmout = 1000000;
-     while (--tmout &&
-            ((serial_in(info, UART_MSR) & UART_MSR_CTS) == 0));
- }
-}
+     for (timeout = serial_console_attempts_cts;
+          timeout > 0;
+          timeout --) {
+         status = serial_in(info, UART_MSR);
+         /* None of the modem status lines, including
+          * Clear to Send, are valid unless Data Set
+          * Ready is asserted.  Also, we should not
+          * transmit unless Data Carrier Detect is
+          * asserted (implying that a receiver is
+          * present).  If DSR or DCD is not present we
+          * discard the message (the carrier won't be
+          * asserted in the time taken to poll the UART
+          * a few times more).  Distinct DSR and DCD
+          * counters are maintained for simple
+          * diagnosis of cable and modem faults.
+          */
+         if ((status & UART_MSR_DSR) == 0) {
+             serial_console_counters.timeouts_dsr++;
+             return !0;
+         }
+         if ((status & UART_MSR_DCD) == 0) {
+             serial_console_counters.timeouts_dcd++;
+             return !0;
+         }
+         /* At this point DSR and DCD are asserted, so
+          * valid to test flow control.
+          */
+         if (status & UART_MSR_CTS) {
+             return 0;
+         }
+     }
+     /* CTS still not asserted.  Discard the message for
+      * correct operation on multi-drop lines.
+      */
+     serial_console_counters.timeouts_cts++;
+     return !0;
+ }

+ return 0;
+}

-/*
- *    Print a string to the serial port trying not to disturb
- *    any possible real use of the port...
+/* Print 'ch' to the serial console at 'info'.
   *
- *    The console must be locked when we get here.
+ * Inputs: info -- RS232 port used as console.
+ *       ch -- character to print.
+ * Returns: 0 -- 'ch' was transmitted, caller should send me more
+ *         characters of the message.
+ *        non-0 -- 'ch' was dropped, caller should discard remainder
+ *             of message.
   */
-static void serial_console_write(struct console *co, const char *s,
-             unsigned count)
+static int serial_console_out(struct async_struct *info,
+               char ch)
+{
+ int error;
+
+ error = serial_console_flow(info);
+ if (!error) {
+     serial_out(info, UART_TX, ch);
+ }
+ return error;
+}
+
+/* Print a string 'message' of 'message_length' to the serial
+ * port. 'co' is ingnored, as the serial console is described by the
+ * global 'async_sercons'.
+ *
+ * Try not to disturb any possible real use of the port (such as by
+ * getty).  So use polling rather than interrupts.
+ *
+ * The console must be locked when we get here.
+ *
+ * Inputs: co -- console to print message upon, ignored.
+ *       message -- pointer to text to print.
+ *       message_length -- amount of text, [0, MAX_UINT].
+ * Globals: async_sercons.
+ */
+static void serial_console_write(struct console *co,
+              const char *message,
+              unsigned int message_length)
  {
      static struct async_struct *info = &async_sercons;
- int ier;
- unsigned i;
+ unsigned int ier;
+ /* We go to some trouble with unsigned ints and avoiding array
+  * references to ensure that messages can be longer than is
+  * indexable by an int/size_t.    Not sure why the ability to
+  * print 4 billion characters is valuable, unless we're trying
+  * to avoid invalid memory accesses upon corrupt
+  * message_lengths [GDT].
+  */
+ unsigned int u;
+ const char *p;

- /*
-  *    First save the IER then disable the interrupts
+ serial_console_counters.messages_total++;
+
+ /* Salt away the serial port UART's Interrupt Enable Register
+  * before disabling UART interrupts.
       */
      ier = serial_in(info, UART_IER);
      serial_out(info, UART_IER, 0x00);

- /*
-  *    Now, do each character
-  */
- for (i = 0; i < count; i++, s++) {
-     wait_for_xmitr(info);
-
-     /*
-      * Send the character out.
-      * If a LF, also do CR...
-      */
-     serial_out(info, UART_TX, *s);
-     if (*s == 10) {
-         wait_for_xmitr(info);
-         serial_out(info, UART_TX, 13);
+ /* Send each character down the port. */
+ for (u = 0, p = message; u < message_length; u++, p++)
+ {
+     if (*p == '\n') {
+         /* Expand end of line LF to CR LF, as expected
+          * by terminals and printers.  v5.05c sent LF
+          * CR, which isn't right for printers.
+          */
+         if (serial_console_out(info, '\r')) {
+             serial_console_counters.messages_dropped++;
+             break;
+         }
+     }
+     if (serial_console_out(info, *p)) {
+         serial_console_counters.messages_dropped++;
+         break;
          }
      }
+ /* At this point all characters are sent or we have abandoned
+  * the message due to soft errors like timeouts on transmit
+  * queues, timeouts on CTS or unasserted DSR or DCD.
+  */

- /*
-  *    Finally, Wait for transmitter & holding register to empty
-  *     and restore the IER
+ /* Restore the interrupt status.  v5.05c also waited up to 1S
+  * for any flow control to be reasserted, now we leave any
+  * user application to cope with that.
       */
- wait_for_xmitr(info);
+ (void)serial_console_drain(info);
      serial_out(info, UART_IER, ier);
  }

-
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
the body of a message to majord...@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Please read the FAQ at  http://www.tux.org/lkml/