servod: add command muxing in ec3po

This creates a second pty for pty_driver to open, to prevent the
need for freezing the user pty. This also allows the user pty to
fully log and print all output.

This also cleans up ec3po printouts and prints ptys by name.

BUG=b:74023102,b:73310923
TEST=servod is fast now, power_state:rec works now
CQ-DEPEND=CL:958128

Change-Id: I4a69456d91914393f2df26b13cc6d97fe7f21a79
Reviewed-on: https://chromium-review.googlesource.com/958127
Commit-Ready: Nick Sanders <nsanders@chromium.org>
Tested-by: Nick Sanders <nsanders@chromium.org>
Reviewed-by: Aseda Aboagye <aaboagye@chromium.org>
Reviewed-by: Nick Sanders <nsanders@chromium.org>
diff --git a/servo/drv/pty_driver.py b/servo/drv/pty_driver.py
index e6b49b1..61a03f5 100644
--- a/servo/drv/pty_driver.py
+++ b/servo/drv/pty_driver.py
@@ -33,9 +33,14 @@
     super(ptyDriver, self).__init__(interface, params)
     self._child = None
     self._fd = None
-    self._pty_path = self._interface.get_pty()
+    self._cmd_iface = False
+    try:
+      # We'll probe for a control PTY if this is an ec3po interface.
+      self._pty_path = self._interface.get_control_pty()
+      self._cmd_iface = True
+    except:
+      self._pty_path = self._interface.get_pty()
     self._dict = UART_PARAMS
-    self._interface = interface
 
   @contextlib.contextmanager
   def _open(self):
@@ -45,21 +50,38 @@
     freezing and thawing any other terminals that are using this PTY as well as
     closing the connection when finished.
     """
-    # Freeze any terminals that are using this PTY, otherwise when we check
-    # for the regex matches, it will fail with a 'resource temporarily
-    # unavailable' error.
-    with servo.terminal_freezer.TerminalFreezer(self._pty_path):
-      self._logger.debug('opening %s', self._pty_path)
-      self._fd = os.open(self._pty_path, os.O_RDWR | os.O_NONBLOCK)
+    if self._cmd_iface:
       try:
-        self._child = fdpexpect.fdspawn(self._fd)
-        # pexpect dafaults to a 100ms delay before sending characters, to
-        # work around race conditions in ssh. We don't need this feature
-        # so we'll change delaybeforesend from 0.1 to 0.001 to speed things up.
-        self._child.delaybeforesend = 0.001
-        yield
+        self._interface.get_command_lock()
+        self._fd = os.open(self._pty_path, os.O_RDWR | os.O_NONBLOCK)
+        try:
+          self._child = fdpexpect.fdspawn(self._fd)
+          # pexpect dafaults to a 100ms delay before sending characters, to
+          # work around race conditions in ssh. We don't need this feature
+          # so we'll change delaybeforesend from 0.1 to 0.001
+          # to speed things up.
+          self._child.delaybeforesend = 0.001
+          yield
+        finally:
+          self._close()
       finally:
-        self._close()
+        self._interface.release_command_lock()
+    else:
+      # Freeze any terminals that are using this PTY, otherwise when we check
+      # for the regex matches, it will fail with a 'resource temporarily
+      # unavailable' error.
+      with servo.terminal_freezer.TerminalFreezer(self._pty_path):
+        self._fd = os.open(self._pty_path, os.O_RDWR | os.O_NONBLOCK)
+        try:
+          self._child = fdpexpect.fdspawn(self._fd)
+          # pexpect dafaults to a 100ms delay before sending characters, to
+          # work around race conditions in ssh. We don't need this feature
+          # so we'll change delaybeforesend from 0.1 to 0.001
+          # to speed things up.
+          self._child.delaybeforesend = 0.001
+          yield
+        finally:
+          self._close()
 
   def _close(self):
     """Close serial device connection."""
@@ -146,6 +168,7 @@
       ptyError: If timed out waiting for a response
     """
     result_list = []
+
     with self._open():
       # Make sure uart capture does not interfere with matching the expected
       # response
diff --git a/servo/ec3po_interface.py b/servo/ec3po_interface.py
index 4c5a3aa..b94bdca 100644
--- a/servo/ec3po_interface.py
+++ b/servo/ec3po_interface.py
@@ -7,6 +7,7 @@
 from __future__ import print_function
 
 # pylint: disable=cros-logging-import
+import ctypes
 import logging
 import multiprocessing
 import os
@@ -24,17 +25,19 @@
 
   This includes both the interpreter and the console objects for one UART.
   """
-  def __init__(self, raw_ec_uart):
+  def __init__(self, raw_ec_uart, source_name):
     """Provides the interface to the EC-3PO console interpreter.
 
     Args:
       raw_ec_uart: A string representing the actual PTY of the EC UART.
+      source_name: A user friendly name documenting the source of this PTY.
     """
     # Run Fuart init.
     super(EC3PO, self).__init__()
     self._logger = logging.getLogger('EC3PO Interface')
     # Create the console and interpreter passing in the raw EC UART PTY.
     self._raw_ec_uart = raw_ec_uart
+    self._source = source_name
 
     # Create some pipes to communicate between the interpreter and the console.
     # The command pipe is bidirectional.
@@ -61,12 +64,16 @@
 
     # Open a new pseudo-terminal pair.
     (master_pty, user_pty) = pty.openpty()
+    (interface_pty, control_pty) = pty.openpty()
 
     tty.setraw(master_pty, termios.TCSADRAIN)
+    tty.setraw(interface_pty, termios.TCSADRAIN)
 
     # Set the permissions to 660.
     os.chmod(os.ttyname(user_pty), (stat.S_IRGRP | stat.S_IWGRP |
                                     stat.S_IRUSR | stat.S_IWUSR))
+    os.chmod(os.ttyname(control_pty), (stat.S_IRGRP | stat.S_IWGRP |
+                                    stat.S_IRUSR | stat.S_IWUSR))
 
     # Change the owner and group of the PTY to the user who started servod.
     try:
@@ -79,30 +86,54 @@
     except TypeError:
       gid = -1
     os.fchown(user_pty, uid, gid)
+    os.fchown(control_pty, uid, gid)
 
     # Create a console.
-    c = ec3po.console.Console(master_pty, os.ttyname(user_pty),
+    c = ec3po.console.Console(master_pty, os.ttyname(user_pty), interface_pty,
                               cmd_pipe_interactive,
                               dbg_pipe_interactive)
     self._console = c
     c._logger = logging.getLogger('Console')
     # Spawn a console process.
+    v = multiprocessing.Value(ctypes.c_bool, False)
+    self._command_active = v
     console_process = multiprocessing.Process(target=ec3po.console.StartLoop,
-                                              args=(c,))
+                                              args=(c, v))
     # Make sure to kill the console when we terminate.
     console_process.daemon = True
     # Start the console.
     console_process.start()
-    self._logger.info('%s', os.ttyname(user_pty))
     self._logger.debug('Console: %s', self._console)
+
+    self._logger.debug('User console: %s', os.ttyname(user_pty))
+    self._logger.debug('Control console: %s', os.ttyname(control_pty))
     self._pty = os.ttyname(user_pty)
+    self._control_pty = os.ttyname(control_pty)
     self._cmd_pipe_int = cmd_pipe_interactive
 
+    self._logger.info('-------------------- %s console on: %s',
+        self._source, os.ttyname(user_pty))
+
   def get_pty(self):
     """Gets the path of the served PTY."""
-    self._logger.debug('get_pty')
+    self._logger.debug('get_pty: %s', self._pty)
     return self._pty
 
+  def get_control_pty(self):
+    """Gets the path of the served control PTY."""
+    self._logger.debug('get_pty: %s', self._control_pty)
+    return self._control_pty
+
+  def get_command_lock(self):
+    self._command_active.value = True
+    self._logger.debug('acquire lock for %s: %s',
+        self._control_pty, self._command_active.value)
+
+  def release_command_lock(self):
+    self._command_active.value = False
+    self._logger.debug('release lock for %s: %s',
+        self._control_pty, self._command_active.value)
+
   def set_interp_connect(self, state):
     """Set the interpreter's connection state to the UART.
 
diff --git a/servo/servo_interfaces.py b/servo/servo_interfaces.py
index 564d7c0..31232d8 100644
--- a/servo/servo_interfaces.py
+++ b/servo/servo_interfaces.py
@@ -38,9 +38,9 @@
      'ftdi_uart',                       # 7: EC
      'ftdi_uart',                       # 8: AP
      {'name': 'ec3po_uart',             # 9: EC3PO(USBPD)
-      'raw_pty': 'raw_usbpd_uart_pty'},
+      'raw_pty': 'raw_usbpd_uart_pty', 'source': 'PD/Cr50'},
      {'name': 'ec3po_uart',             #10: EC3PO(EC)
-      'raw_pty': 'raw_ec_uart_pty'},
+      'raw_pty': 'raw_ec_uart_pty', 'source': 'EC'},
     ]
 
 # servo v3
@@ -60,7 +60,7 @@
      {'name': 'bb_uart', 'uart_num': 2}, # 8: AP
      'dummy',                            # 9
      {'name': 'ec3po_uart',              #10: EC3PO(EC)
-      'raw_pty': 'raw_ec_uart_pty'},
+      'raw_pty': 'raw_ec_uart_pty', 'source': 'EC'},
     ]
 
 INTERFACE_DEFAULTS[0x0403][0x6014] = INTERFACE_DEFAULTS[0x18d1][0x5004]
@@ -80,7 +80,7 @@
      'dummy',                                # 8
      'dummy',                                # 9
      {'name': 'ec3po_uart',                  #10: dut ec console
-      'raw_pty': 'raw_ec_uart_pty'},
+      'raw_pty': 'raw_ec_uart_pty', 'source': 'EC'},
     ]
 
 # cr50 CCD
@@ -97,9 +97,9 @@
      {'name': 'stm32_uart', 'interface': 2}, # 7: EC/PD
      {'name': 'stm32_uart', 'interface': 1}, # 8: AP
      {'name': 'ec3po_uart',                  # 9: EC3PO(Cr50)
-      'raw_pty': 'raw_cr50_uart_pty'},
+      'raw_pty': 'raw_cr50_uart_pty', 'source': 'Cr50'},
      {'name': 'ec3po_uart',                  #10: EC3PO(EC)
-      'raw_pty': 'raw_ec_uart_pty'},
+      'raw_pty': 'raw_ec_uart_pty', 'source': 'EC'},
     ]
 
 # Servo micro
@@ -113,13 +113,13 @@
      'dummy',                                # 4: dummy
      'dummy',                                # 5: dummy
      {'name': 'ec3po_uart',                  # 6: servo console
-      'raw_pty': 'raw_servo_console_pty'},
+      'raw_pty': 'raw_servo_console_pty', 'source': 'servo_micro'},
      {'name': 'stm32_uart', 'interface': 6}, # 7: uart1/EC console
      {'name': 'stm32_uart', 'interface': 5}, # 8: uart2/AP console
      {'name': 'ec3po_uart',                  # 9: EC3PO for PD/Cr50
-      'raw_pty': 'raw_usbpd_uart_pty'},
+      'raw_pty': 'raw_usbpd_uart_pty', 'source': 'PD/Cr50'},
      {'name': 'ec3po_uart',                  #10: EC3PO for EC
-      'raw_pty': 'raw_ec_uart_pty'},
+      'raw_pty': 'raw_ec_uart_pty', 'source': 'EC'},
     ]
 
 # Servo v4
@@ -149,7 +149,7 @@
      {'name': 'stm32_uart', 'interface': 3}, #24: dut sbu uart
      {'name': 'stm32_uart', 'interface': 4}, #25: atmega uart
      {'name': 'ec3po_uart',                  #26: servo v4 console
-      'raw_pty': 'raw_servo_v4_console_pty'},
+      'raw_pty': 'raw_servo_v4_console_pty', 'source': 'servo_v4'},
     ]
 
   # Buffer slots for servo v4 (interface #27-40).
@@ -165,7 +165,7 @@
     ['dummy',
      'ftdi_gpiouart', # occupies 2 slots
      'dummy',         # reserved for the above ftdi_gpiouart
-     {'name': 'ec3po_uart', 'raw_pty': 'raw_ec_uart_pty'},
+     {'name': 'ec3po_uart', 'raw_pty': 'raw_ec_uart_pty', 'source': 'EC'},
     ]
 
 SERVO_ID_DEFAULTS.extend(MINISERVO_ID_DEFAULTS)
@@ -177,7 +177,7 @@
     ['dummy',
      'ftdi_gpiouart', # occupies 2 slots
      'dummy',         # reserved for the above ftdi_gpiouart
-     {'name': 'ec3po_uart', 'raw_pty': 'raw_ec_uart_pty'},
+     {'name': 'ec3po_uart', 'raw_pty': 'raw_ec_uart_pty', 'source': 'EC'},
     ]
 
 SERVO_ID_DEFAULTS.extend(TOAD_ID_DEFAULTS)
@@ -189,7 +189,7 @@
     ['dummy',
      'ftdi_gpiouart', # occupies 2 slots
      'dummy',         # reserved for the above ftdi_gpiouart
-     {'name': 'ec3po_uart', 'raw_pty': 'raw_ec_uart_pty'},
+     {'name': 'ec3po_uart', 'raw_pty': 'raw_ec_uart_pty', 'source': 'EC'},
     ]
 
 SERVO_ID_DEFAULTS.extend(RESTON_ID_DEFAULTS)
@@ -201,7 +201,7 @@
     ['dummy',
      'ftdi_gpiouart', # occupies 2 slots
      'dummy',         # reserved for the above ftdi_gpiouart
-     {'name': 'ec3po_uart', 'raw_pty': 'raw_ec_uart_pty'},
+     {'name': 'ec3po_uart', 'raw_pty': 'raw_ec_uart_pty', 'source': 'EC'},
     ]
 
 SERVO_ID_DEFAULTS.extend(FRUITPIE_ID_DEFAULTS)
@@ -213,7 +213,7 @@
     ['dummy',
      'ftdi_gpiouart', # occupies 2 slots
      'dummy',         # reserved for the above ftdi_gpiouart
-     {'name': 'ec3po_uart', 'raw_pty': 'raw_ec_uart_pty'},
+     {'name': 'ec3po_uart', 'raw_pty': 'raw_ec_uart_pty', 'source': 'EC'},
     ]
 
 SERVO_ID_DEFAULTS.extend(PLANKTON_ID_DEFAULTS)
diff --git a/servo/servo_server.py b/servo/servo_server.py
index ac821ca..114b62b 100755
--- a/servo/servo_server.py
+++ b/servo/servo_server.py
@@ -505,9 +505,10 @@
       interface for the USB PD UART.
     """
     raw_uart_name = interface['raw_pty']
+    raw_uart_source = interface['source']
     if self._syscfg.is_control(raw_uart_name):
       raw_ec_uart = self.get(raw_uart_name)
-      return ec3po_interface.EC3PO(raw_ec_uart)
+      return ec3po_interface.EC3PO(raw_ec_uart, raw_uart_source)
     else:
       # The overlay doesn't have the raw PTY defined, therefore we can skip
       # initializing this interface since no control relies on it.