summaryrefslogtreecommitdiff
path: root/util/ec3po/interpreter.py
diff options
context:
space:
mode:
Diffstat (limited to 'util/ec3po/interpreter.py')
-rw-r--r--util/ec3po/interpreter.py830
1 files changed, 422 insertions, 408 deletions
diff --git a/util/ec3po/interpreter.py b/util/ec3po/interpreter.py
index 4e151083bd..8d21af247a 100644
--- a/util/ec3po/interpreter.py
+++ b/util/ec3po/interpreter.py
@@ -1,4 +1,4 @@
-# Copyright 2015 The Chromium OS Authors. All rights reserved.
+# Copyright 2015 The ChromiumOS Authors
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
@@ -25,443 +25,457 @@ import traceback
import six
-
COMMAND_RETRIES = 3 # Number of attempts to retry a command.
EC_MAX_READ = 1024 # Max bytes to read at a time from the EC.
-EC_SYN = b'\xec' # Byte indicating EC interrogation.
-EC_ACK = b'\xc0' # Byte representing correct EC response to interrogation.
+EC_SYN = b"\xec" # Byte indicating EC interrogation.
+EC_ACK = b"\xc0" # Byte representing correct EC response to interrogation.
class LoggerAdapter(logging.LoggerAdapter):
- """Class which provides a small adapter for the logger."""
+ """Class which provides a small adapter for the logger."""
- def process(self, msg, kwargs):
- """Prepends the served PTY to the beginning of the log message."""
- return '%s - %s' % (self.extra['pty'], msg), kwargs
+ def process(self, msg, kwargs):
+ """Prepends the served PTY to the beginning of the log message."""
+ return "%s - %s" % (self.extra["pty"], msg), kwargs
class Interpreter(object):
- """Class which provides the interpretation layer between the EC and user.
-
- This class essentially performs all of the intepretation for the EC and the
- user. It handles all of the automatic command retrying as well as the
- formation of commands for EC images which support that.
-
- Attributes:
- logger: A logger for this module.
- ec_uart_pty: An opened file object to the raw EC UART PTY.
- ec_uart_pty_name: A string containing the name of the raw EC UART PTY.
- cmd_pipe: A socket.socket or multiprocessing.Connection object which
- represents the Interpreter side of the command pipe. This must be a
- bidirectional pipe. Commands and responses will utilize this pipe.
- dbg_pipe: A socket.socket or multiprocessing.Connection object which
- represents the Interpreter side of the debug pipe. This must be a
- unidirectional pipe with write capabilities. EC debug output will utilize
- this pipe.
- cmd_retries: An integer representing the number of attempts the console
- should retry commands if it receives an error.
- log_level: An integer representing the numeric value of the log level.
- inputs: A list of objects that the intpreter selects for reading.
- Initially, these are the EC UART and the command pipe.
- outputs: A list of objects that the interpreter selects for writing.
- ec_cmd_queue: A FIFO queue used for sending commands down to the EC UART.
- last_cmd: A string that represents the last command sent to the EC. If an
- error is encountered, the interpreter will attempt to retry this command
- up to COMMAND_RETRIES.
- enhanced_ec: A boolean indicating if the EC image that we are currently
- communicating with is enhanced or not. Enhanced EC images will support
- packed commands and host commands over the UART. This defaults to False
- and is changed depending on the result of an interrogation.
- interrogating: A boolean indicating if we are in the middle of interrogating
- the EC.
- connected: A boolean indicating if the interpreter is actually connected to
- the UART and listening.
- """
- def __init__(self, ec_uart_pty, cmd_pipe, dbg_pipe, log_level=logging.INFO,
- name=None):
- """Intializes an Interpreter object with the provided args.
+ """Class which provides the interpretation layer between the EC and user.
- Args:
- ec_uart_pty: A string representing the EC UART to connect to.
+ This class essentially performs all of the intepretation for the EC and the
+ user. It handles all of the automatic command retrying as well as the
+ formation of commands for EC images which support that.
+
+ Attributes:
+ logger: A logger for this module.
+ ec_uart_pty: An opened file object to the raw EC UART PTY.
+ ec_uart_pty_name: A string containing the name of the raw EC UART PTY.
cmd_pipe: A socket.socket or multiprocessing.Connection object which
represents the Interpreter side of the command pipe. This must be a
bidirectional pipe. Commands and responses will utilize this pipe.
dbg_pipe: A socket.socket or multiprocessing.Connection object which
represents the Interpreter side of the debug pipe. This must be a
- unidirectional pipe with write capabilities. EC debug output will
- utilize this pipe.
+ unidirectional pipe with write capabilities. EC debug output will utilize
+ this pipe.
cmd_retries: An integer representing the number of attempts the console
should retry commands if it receives an error.
- log_level: An optional integer representing the numeric value of the log
- level. By default, the log level will be logging.INFO (20).
- name: the console source name
- """
- # Create a unique logger based on the interpreter name
- interpreter_prefix = ('%s - ' % name) if name else ''
- logger = logging.getLogger('%sEC3PO.Interpreter' % interpreter_prefix)
- self.logger = LoggerAdapter(logger, {'pty': ec_uart_pty})
- # TODO(https://crbug.com/1162189): revist the 2 TODOs below
- # TODO(https://bugs.python.org/issue27805, python3.7+): revert to ab+
- # TODO(https://bugs.python.org/issue20074): removing buffering=0 if/when
- # that gets fixed, or keep two pty: one for reading and one for writing
- self.ec_uart_pty = open(ec_uart_pty, 'r+b', buffering=0)
- self.ec_uart_pty_name = ec_uart_pty
- self.cmd_pipe = cmd_pipe
- self.dbg_pipe = dbg_pipe
- self.cmd_retries = COMMAND_RETRIES
- self.log_level = log_level
- self.inputs = [self.ec_uart_pty, self.cmd_pipe]
- self.outputs = []
- self.ec_cmd_queue = six.moves.queue.Queue()
- self.last_cmd = b''
- self.enhanced_ec = False
- self.interrogating = False
- self.connected = True
-
- def __str__(self):
- """Show internal state of the Interpreter object.
-
- Returns:
- A string that shows the values of the attributes.
- """
- string = []
- string.append('%r' % self)
- string.append('ec_uart_pty: %s' % self.ec_uart_pty)
- string.append('cmd_pipe: %r' % self.cmd_pipe)
- string.append('dbg_pipe: %r' % self.dbg_pipe)
- string.append('cmd_retries: %d' % self.cmd_retries)
- string.append('log_level: %d' % self.log_level)
- string.append('inputs: %r' % self.inputs)
- string.append('outputs: %r' % self.outputs)
- string.append('ec_cmd_queue: %r' % self.ec_cmd_queue)
- string.append('last_cmd: \'%s\'' % self.last_cmd)
- string.append('enhanced_ec: %r' % self.enhanced_ec)
- string.append('interrogating: %r' % self.interrogating)
- return '\n'.join(string)
-
- def EnqueueCmd(self, command):
- """Enqueue a command to be sent to the EC UART.
-
- Args:
- command: A string which contains the command to be sent.
+ log_level: An integer representing the numeric value of the log level.
+ inputs: A list of objects that the intpreter selects for reading.
+ Initially, these are the EC UART and the command pipe.
+ outputs: A list of objects that the interpreter selects for writing.
+ ec_cmd_queue: A FIFO queue used for sending commands down to the EC UART.
+ last_cmd: A string that represents the last command sent to the EC. If an
+ error is encountered, the interpreter will attempt to retry this command
+ up to COMMAND_RETRIES.
+ enhanced_ec: A boolean indicating if the EC image that we are currently
+ communicating with is enhanced or not. Enhanced EC images will support
+ packed commands and host commands over the UART. This defaults to False
+ and is changed depending on the result of an interrogation.
+ interrogating: A boolean indicating if we are in the middle of interrogating
+ the EC.
+ connected: A boolean indicating if the interpreter is actually connected to
+ the UART and listening.
"""
- self.ec_cmd_queue.put(command)
- self.logger.log(1, 'Commands now in queue: %d', self.ec_cmd_queue.qsize())
- # Add the EC UART as an output to be serviced.
- if self.connected and self.ec_uart_pty not in self.outputs:
- self.outputs.append(self.ec_uart_pty)
-
- def PackCommand(self, raw_cmd):
- r"""Packs a command for use with error checking.
-
- For error checking, we pack console commands in a particular format. The
- format is as follows:
-
- &&[x][x][x][x]&{cmd}\n\n
- ^ ^ ^^ ^^ ^ ^-- 2 newlines.
- | | || || |-- the raw console command.
- | | || ||-- 1 ampersand.
- | | ||____|--- 2 hex digits representing the CRC8 of cmd.
- | |____|-- 2 hex digits reprsenting the length of cmd.
- |-- 2 ampersands
-
- Args:
- raw_cmd: A pre-packed string which contains the raw command.
-
- Returns:
- A string which contains the packed command.
- """
- # Don't pack a single carriage return.
- if raw_cmd != b'\r':
- # The command format is as follows.
- # &&[x][x][x][x]&{cmd}\n\n
- packed_cmd = []
- packed_cmd.append(b'&&')
- # The first pair of hex digits are the length of the command.
- packed_cmd.append(b'%02x' % len(raw_cmd))
- # Then the CRC8 of cmd.
- packed_cmd.append(b'%02x' % Crc8(raw_cmd))
- packed_cmd.append(b'&')
- # Now, the raw command followed by 2 newlines.
- packed_cmd.append(raw_cmd)
- packed_cmd.append(b'\n\n')
- return b''.join(packed_cmd)
- else:
- return raw_cmd
-
- def ProcessCommand(self, command):
- """Captures the input determines what actions to take.
-
- Args:
- command: A string representing the command sent by the user.
- """
- if command == b'disconnect':
- if self.connected:
- self.logger.debug('UART disconnect request.')
- # Drop all pending commands if any.
- while not self.ec_cmd_queue.empty():
- c = self.ec_cmd_queue.get()
- self.logger.debug('dropped: \'%s\'', c)
- if self.enhanced_ec:
- # Reset retry state.
- self.cmd_retries = COMMAND_RETRIES
- self.last_cmd = b''
- # Get the UART that the interpreter is attached to.
- fileobj = self.ec_uart_pty
- self.logger.debug('fileobj: %r', fileobj)
- # Remove the descriptor from the inputs and outputs.
- self.inputs.remove(fileobj)
- if fileobj in self.outputs:
- self.outputs.remove(fileobj)
- self.logger.debug('Removed fileobj. Remaining inputs: %r', self.inputs)
- # Close the file.
- fileobj.close()
- # Mark the interpreter as disconnected now.
- self.connected = False
- self.logger.debug('Disconnected from %s.', self.ec_uart_pty_name)
- return
-
- elif command == b'reconnect':
- if not self.connected:
- self.logger.debug('UART reconnect request.')
- # Reopen the PTY.
+ def __init__(
+ self, ec_uart_pty, cmd_pipe, dbg_pipe, log_level=logging.INFO, name=None
+ ):
+ """Intializes an Interpreter object with the provided args.
+
+ Args:
+ ec_uart_pty: A string representing the EC UART to connect to.
+ cmd_pipe: A socket.socket or multiprocessing.Connection object which
+ represents the Interpreter side of the command pipe. This must be a
+ bidirectional pipe. Commands and responses will utilize this pipe.
+ dbg_pipe: A socket.socket or multiprocessing.Connection object which
+ represents the Interpreter side of the debug pipe. This must be a
+ unidirectional pipe with write capabilities. EC debug output will
+ utilize this pipe.
+ cmd_retries: An integer representing the number of attempts the console
+ should retry commands if it receives an error.
+ log_level: An optional integer representing the numeric value of the log
+ level. By default, the log level will be logging.INFO (20).
+ name: the console source name
+ """
+ # Create a unique logger based on the interpreter name
+ interpreter_prefix = ("%s - " % name) if name else ""
+ logger = logging.getLogger("%sEC3PO.Interpreter" % interpreter_prefix)
+ self.logger = LoggerAdapter(logger, {"pty": ec_uart_pty})
+ # TODO(https://crbug.com/1162189): revist the 2 TODOs below
# TODO(https://bugs.python.org/issue27805, python3.7+): revert to ab+
# TODO(https://bugs.python.org/issue20074): removing buffering=0 if/when
# that gets fixed, or keep two pty: one for reading and one for writing
- fileobj = open(self.ec_uart_pty_name, 'r+b', buffering=0)
- self.logger.debug('fileobj: %r', fileobj)
- self.ec_uart_pty = fileobj
- # Add the descriptor to the inputs.
- self.inputs.append(fileobj)
- self.logger.debug('fileobj added. curr inputs: %r', self.inputs)
- # Mark the interpreter as connected now.
- self.connected = True
- self.logger.debug('Connected to %s.', self.ec_uart_pty_name)
- return
-
- elif command.startswith(b'enhanced'):
- self.enhanced_ec = command.split(b' ')[1] == b'True'
- return
-
- # Ignore any other commands while in the disconnected state.
- self.logger.log(1, 'command: \'%s\'', command)
- if not self.connected:
- self.logger.debug('Ignoring command because currently disconnected.')
- return
-
- # Remove leading and trailing spaces only if this is an enhanced EC image.
- # For non-enhanced EC images, commands will be single characters at a time
- # and can be spaces.
- if self.enhanced_ec:
- command = command.strip(b' ')
-
- # There's nothing to do if the command is empty.
- if len(command) == 0:
- return
-
- # Handle log level change requests.
- if command.startswith(b'loglevel'):
- self.logger.debug('Log level change request.')
- new_log_level = int(command.split(b' ')[1])
- self.logger.logger.setLevel(new_log_level)
- self.logger.info('Log level changed to %d.', new_log_level)
- return
-
- # Check for interrogation command.
- if command == EC_SYN:
- # User is requesting interrogation. Send SYN as is.
- self.logger.debug('User requesting interrogation.')
- self.interrogating = True
- # Assume the EC isn't enhanced until we get a response.
- self.enhanced_ec = False
- elif self.enhanced_ec:
- # Enhanced EC images require the plaintext commands to be packed.
- command = self.PackCommand(command)
- # TODO(aaboagye): Make a dict of commands and keys and eventually,
- # handle partial matching based on unique prefixes.
-
- self.EnqueueCmd(command)
-
- def HandleCmdRetries(self):
- """Attempts to retry commands if possible."""
- if self.cmd_retries > 0:
- # The EC encountered an error. We'll have to retry again.
- self.logger.warning('Retrying command...')
- self.cmd_retries -= 1
- self.logger.warning('Retries remaining: %d', self.cmd_retries)
- # Retry the command and add the EC UART to the writers again.
- self.EnqueueCmd(self.last_cmd)
- self.outputs.append(self.ec_uart_pty)
- else:
- # We're out of retries, so just give up.
- self.logger.error('Command failed. No retries left.')
- # Clear the command in progress.
- self.last_cmd = b''
- # Reset the retry count.
- self.cmd_retries = COMMAND_RETRIES
-
- def SendCmdToEC(self):
- """Sends a command to the EC."""
- # If we're retrying a command, just try to send it again.
- if self.cmd_retries < COMMAND_RETRIES:
- cmd = self.last_cmd
- else:
- # If we're not retrying, we should not be writing to the EC if we have no
- # items in our command queue.
- assert not self.ec_cmd_queue.empty()
- # Get the command to send.
- cmd = self.ec_cmd_queue.get()
-
- # Send the command.
- self.ec_uart_pty.write(cmd)
- self.ec_uart_pty.flush()
- self.logger.log(1, 'Sent command to EC.')
-
- if self.enhanced_ec and cmd != EC_SYN:
- # Now, that we've sent the command, store the current command as the last
- # command sent. If we encounter an error string, we will attempt to retry
- # this command.
- if cmd != self.last_cmd:
- self.last_cmd = cmd
- # Reset the retry count.
+ self.ec_uart_pty = open(ec_uart_pty, "r+b", buffering=0)
+ self.ec_uart_pty_name = ec_uart_pty
+ self.cmd_pipe = cmd_pipe
+ self.dbg_pipe = dbg_pipe
self.cmd_retries = COMMAND_RETRIES
+ self.log_level = log_level
+ self.inputs = [self.ec_uart_pty, self.cmd_pipe]
+ self.outputs = []
+ self.ec_cmd_queue = six.moves.queue.Queue()
+ self.last_cmd = b""
+ self.enhanced_ec = False
+ self.interrogating = False
+ self.connected = True
- # If no command is pending to be sent, then we can remove the EC UART from
- # writers. Might need better checking for command retry logic in here.
- if self.ec_cmd_queue.empty():
- # Remove the EC UART from the writers while we wait for a response.
- self.logger.debug('Removing EC UART from writers.')
- self.outputs.remove(self.ec_uart_pty)
-
- def HandleECData(self):
- """Handle any debug prints from the EC."""
- self.logger.log(1, 'EC has data')
- # Read what the EC sent us.
- data = os.read(self.ec_uart_pty.fileno(), EC_MAX_READ)
- self.logger.log(1, 'got: \'%s\'', binascii.hexlify(data))
- if b'&E' in data and self.enhanced_ec:
- # We received an error, so we should retry it if possible.
- self.logger.warning('Error string found in data.')
- self.HandleCmdRetries()
- return
-
- # If we were interrogating, check the response and update our knowledge
- # of the current EC image.
- if self.interrogating:
- self.enhanced_ec = data == EC_ACK
- if self.enhanced_ec:
- self.logger.debug('The current EC image seems enhanced.')
- else:
- self.logger.debug('The current EC image does NOT seem enhanced.')
- # Done interrogating.
- self.interrogating = False
- # For now, just forward everything the EC sends us.
- self.logger.log(1, 'Forwarding to user...')
- self.dbg_pipe.send(data)
-
- def HandleUserData(self):
- """Handle any incoming commands from the user.
-
- Raises:
- EOFError: Allowed to propagate through from self.cmd_pipe.recv().
- """
- self.logger.log(1, 'Command data available. Begin processing.')
- data = self.cmd_pipe.recv()
- # Process the command.
- self.ProcessCommand(data)
+ def __str__(self):
+ """Show internal state of the Interpreter object.
+
+ Returns:
+ A string that shows the values of the attributes.
+ """
+ string = []
+ string.append("%r" % self)
+ string.append("ec_uart_pty: %s" % self.ec_uart_pty)
+ string.append("cmd_pipe: %r" % self.cmd_pipe)
+ string.append("dbg_pipe: %r" % self.dbg_pipe)
+ string.append("cmd_retries: %d" % self.cmd_retries)
+ string.append("log_level: %d" % self.log_level)
+ string.append("inputs: %r" % self.inputs)
+ string.append("outputs: %r" % self.outputs)
+ string.append("ec_cmd_queue: %r" % self.ec_cmd_queue)
+ string.append("last_cmd: '%s'" % self.last_cmd)
+ string.append("enhanced_ec: %r" % self.enhanced_ec)
+ string.append("interrogating: %r" % self.interrogating)
+ return "\n".join(string)
+
+ def EnqueueCmd(self, command):
+ """Enqueue a command to be sent to the EC UART.
+
+ Args:
+ command: A string which contains the command to be sent.
+ """
+ self.ec_cmd_queue.put(command)
+ self.logger.log(
+ 1, "Commands now in queue: %d", self.ec_cmd_queue.qsize()
+ )
+
+ # Add the EC UART as an output to be serviced.
+ if self.connected and self.ec_uart_pty not in self.outputs:
+ self.outputs.append(self.ec_uart_pty)
+
+ def PackCommand(self, raw_cmd):
+ r"""Packs a command for use with error checking.
+
+ For error checking, we pack console commands in a particular format. The
+ format is as follows:
+
+ &&[x][x][x][x]&{cmd}\n\n
+ ^ ^ ^^ ^^ ^ ^-- 2 newlines.
+ | | || || |-- the raw console command.
+ | | || ||-- 1 ampersand.
+ | | ||____|--- 2 hex digits representing the CRC8 of cmd.
+ | |____|-- 2 hex digits reprsenting the length of cmd.
+ |-- 2 ampersands
+
+ Args:
+ raw_cmd: A pre-packed string which contains the raw command.
+
+ Returns:
+ A string which contains the packed command.
+ """
+ # Don't pack a single carriage return.
+ if raw_cmd != b"\r":
+ # The command format is as follows.
+ # &&[x][x][x][x]&{cmd}\n\n
+ packed_cmd = []
+ packed_cmd.append(b"&&")
+ # The first pair of hex digits are the length of the command.
+ packed_cmd.append(b"%02x" % len(raw_cmd))
+ # Then the CRC8 of cmd.
+ packed_cmd.append(b"%02x" % Crc8(raw_cmd))
+ packed_cmd.append(b"&")
+ # Now, the raw command followed by 2 newlines.
+ packed_cmd.append(raw_cmd)
+ packed_cmd.append(b"\n\n")
+ return b"".join(packed_cmd)
+ else:
+ return raw_cmd
+
+ def ProcessCommand(self, command):
+ """Captures the input determines what actions to take.
+
+ Args:
+ command: A string representing the command sent by the user.
+ """
+ if command == b"disconnect":
+ if self.connected:
+ self.logger.debug("UART disconnect request.")
+ # Drop all pending commands if any.
+ while not self.ec_cmd_queue.empty():
+ c = self.ec_cmd_queue.get()
+ self.logger.debug("dropped: '%s'", c)
+ if self.enhanced_ec:
+ # Reset retry state.
+ self.cmd_retries = COMMAND_RETRIES
+ self.last_cmd = b""
+ # Get the UART that the interpreter is attached to.
+ fileobj = self.ec_uart_pty
+ self.logger.debug("fileobj: %r", fileobj)
+ # Remove the descriptor from the inputs and outputs.
+ self.inputs.remove(fileobj)
+ if fileobj in self.outputs:
+ self.outputs.remove(fileobj)
+ self.logger.debug(
+ "Removed fileobj. Remaining inputs: %r", self.inputs
+ )
+ # Close the file.
+ fileobj.close()
+ # Mark the interpreter as disconnected now.
+ self.connected = False
+ self.logger.debug(
+ "Disconnected from %s.", self.ec_uart_pty_name
+ )
+ return
+
+ elif command == b"reconnect":
+ if not self.connected:
+ self.logger.debug("UART reconnect request.")
+ # Reopen the PTY.
+ # TODO(https://bugs.python.org/issue27805, python3.7+): revert to ab+
+ # TODO(https://bugs.python.org/issue20074): removing buffering=0 if/when
+ # that gets fixed, or keep two pty: one for reading and one for writing
+ fileobj = open(self.ec_uart_pty_name, "r+b", buffering=0)
+ self.logger.debug("fileobj: %r", fileobj)
+ self.ec_uart_pty = fileobj
+ # Add the descriptor to the inputs.
+ self.inputs.append(fileobj)
+ self.logger.debug("fileobj added. curr inputs: %r", self.inputs)
+ # Mark the interpreter as connected now.
+ self.connected = True
+ self.logger.debug("Connected to %s.", self.ec_uart_pty_name)
+ return
+
+ elif command.startswith(b"enhanced"):
+ self.enhanced_ec = command.split(b" ")[1] == b"True"
+ return
+
+ # Ignore any other commands while in the disconnected state.
+ self.logger.log(1, "command: '%s'", command)
+ if not self.connected:
+ self.logger.debug(
+ "Ignoring command because currently disconnected."
+ )
+ return
+
+ # Remove leading and trailing spaces only if this is an enhanced EC image.
+ # For non-enhanced EC images, commands will be single characters at a time
+ # and can be spaces.
+ if self.enhanced_ec:
+ command = command.strip(b" ")
+
+ # There's nothing to do if the command is empty.
+ if len(command) == 0:
+ return
+
+ # Handle log level change requests.
+ if command.startswith(b"loglevel"):
+ self.logger.debug("Log level change request.")
+ new_log_level = int(command.split(b" ")[1])
+ self.logger.logger.setLevel(new_log_level)
+ self.logger.info("Log level changed to %d.", new_log_level)
+ return
+
+ # Check for interrogation command.
+ if command == EC_SYN:
+ # User is requesting interrogation. Send SYN as is.
+ self.logger.debug("User requesting interrogation.")
+ self.interrogating = True
+ # Assume the EC isn't enhanced until we get a response.
+ self.enhanced_ec = False
+ elif self.enhanced_ec:
+ # Enhanced EC images require the plaintext commands to be packed.
+ command = self.PackCommand(command)
+ # TODO(aaboagye): Make a dict of commands and keys and eventually,
+ # handle partial matching based on unique prefixes.
+
+ self.EnqueueCmd(command)
+
+ def HandleCmdRetries(self):
+ """Attempts to retry commands if possible."""
+ if self.cmd_retries > 0:
+ # The EC encountered an error. We'll have to retry again.
+ self.logger.warning("Retrying command...")
+ self.cmd_retries -= 1
+ self.logger.warning("Retries remaining: %d", self.cmd_retries)
+ # Retry the command and add the EC UART to the writers again.
+ self.EnqueueCmd(self.last_cmd)
+ self.outputs.append(self.ec_uart_pty)
+ else:
+ # We're out of retries, so just give up.
+ self.logger.error("Command failed. No retries left.")
+ # Clear the command in progress.
+ self.last_cmd = b""
+ # Reset the retry count.
+ self.cmd_retries = COMMAND_RETRIES
+
+ def SendCmdToEC(self):
+ """Sends a command to the EC."""
+ # If we're retrying a command, just try to send it again.
+ if self.cmd_retries < COMMAND_RETRIES:
+ cmd = self.last_cmd
+ else:
+ # If we're not retrying, we should not be writing to the EC if we have no
+ # items in our command queue.
+ assert not self.ec_cmd_queue.empty()
+ # Get the command to send.
+ cmd = self.ec_cmd_queue.get()
+
+ # Send the command.
+ self.ec_uart_pty.write(cmd)
+ self.ec_uart_pty.flush()
+ self.logger.log(1, "Sent command to EC.")
+
+ if self.enhanced_ec and cmd != EC_SYN:
+ # Now, that we've sent the command, store the current command as the last
+ # command sent. If we encounter an error string, we will attempt to retry
+ # this command.
+ if cmd != self.last_cmd:
+ self.last_cmd = cmd
+ # Reset the retry count.
+ self.cmd_retries = COMMAND_RETRIES
+
+ # If no command is pending to be sent, then we can remove the EC UART from
+ # writers. Might need better checking for command retry logic in here.
+ if self.ec_cmd_queue.empty():
+ # Remove the EC UART from the writers while we wait for a response.
+ self.logger.debug("Removing EC UART from writers.")
+ self.outputs.remove(self.ec_uart_pty)
+
+ def HandleECData(self):
+ """Handle any debug prints from the EC."""
+ self.logger.log(1, "EC has data")
+ # Read what the EC sent us.
+ data = os.read(self.ec_uart_pty.fileno(), EC_MAX_READ)
+ self.logger.log(1, "got: '%s'", binascii.hexlify(data))
+ if b"&E" in data and self.enhanced_ec:
+ # We received an error, so we should retry it if possible.
+ self.logger.warning("Error string found in data.")
+ self.HandleCmdRetries()
+ return
+
+ # If we were interrogating, check the response and update our knowledge
+ # of the current EC image.
+ if self.interrogating:
+ self.enhanced_ec = data == EC_ACK
+ if self.enhanced_ec:
+ self.logger.debug("The current EC image seems enhanced.")
+ else:
+ self.logger.debug(
+ "The current EC image does NOT seem enhanced."
+ )
+ # Done interrogating.
+ self.interrogating = False
+ # For now, just forward everything the EC sends us.
+ self.logger.log(1, "Forwarding to user...")
+ self.dbg_pipe.send(data)
+
+ def HandleUserData(self):
+ """Handle any incoming commands from the user.
+
+ Raises:
+ EOFError: Allowed to propagate through from self.cmd_pipe.recv().
+ """
+ self.logger.log(1, "Command data available. Begin processing.")
+ data = self.cmd_pipe.recv()
+ # Process the command.
+ self.ProcessCommand(data)
def Crc8(data):
- """Calculates the CRC8 of data.
+ """Calculates the CRC8 of data.
- The generator polynomial used is: x^8 + x^2 + x + 1.
- This is the same implementation that is used in the EC.
+ The generator polynomial used is: x^8 + x^2 + x + 1.
+ This is the same implementation that is used in the EC.
- Args:
- data: A string of data that we wish to calculate the CRC8 on.
+ Args:
+ data: A string of data that we wish to calculate the CRC8 on.
- Returns:
- crc >> 8: An integer representing the CRC8 value.
- """
- crc = 0
- for byte in six.iterbytes(data):
- crc ^= (byte << 8)
- for _ in range(8):
- if crc & 0x8000:
- crc ^= (0x1070 << 3)
- crc <<= 1
- return crc >> 8
+ Returns:
+ crc >> 8: An integer representing the CRC8 value.
+ """
+ crc = 0
+ for byte in six.iterbytes(data):
+ crc ^= byte << 8
+ for _ in range(8):
+ if crc & 0x8000:
+ crc ^= 0x1070 << 3
+ crc <<= 1
+ return crc >> 8
def StartLoop(interp, shutdown_pipe=None):
- """Starts an infinite loop of servicing the user and the EC.
-
- StartLoop checks to see if there are any commands to process, processing them
- if any, and forwards EC output to the user.
-
- When sending a command to the EC, we send the command once and check the
- response to see if the EC encountered an error when receiving the command. An
- error condition is reported to the interpreter by a string with at least one
- '&' and 'E'. The full string is actually '&&EE', however it's possible that
- the leading ampersand or trailing 'E' could be dropped. If an error is
- encountered, the interpreter will retry up to the amount configured.
-
- Args:
- interp: An Interpreter object that has been properly initialised.
- shutdown_pipe: A file object for a pipe or equivalent that becomes readable
- (not blocked) to indicate that the loop should exit. Can be None to never
- exit the loop.
- """
- try:
- # This is used instead of "break" to avoid exiting the loop in the middle of
- # an iteration.
- continue_looping = True
-
- while continue_looping:
- # The inputs list is created anew in each loop iteration because the
- # Interpreter class sometimes modifies the interp.inputs list.
- if shutdown_pipe is None:
- inputs = interp.inputs
- else:
- inputs = list(interp.inputs)
- inputs.append(shutdown_pipe)
-
- readable, writeable, _ = select.select(inputs, interp.outputs, [])
-
- for obj in readable:
- # Handle any debug prints from the EC.
- if obj is interp.ec_uart_pty:
- interp.HandleECData()
-
- # Handle any commands from the user.
- elif obj is interp.cmd_pipe:
- try:
- interp.HandleUserData()
- except EOFError:
- interp.logger.debug(
- 'ec3po interpreter received EOF from cmd_pipe in '
- 'HandleUserData()')
- continue_looping = False
-
- elif obj is shutdown_pipe:
- interp.logger.debug(
- 'ec3po interpreter received shutdown pipe unblocked notification')
- continue_looping = False
-
- for obj in writeable:
- # Send a command to the EC.
- if obj is interp.ec_uart_pty:
- interp.SendCmdToEC()
-
- except KeyboardInterrupt:
- pass
-
- finally:
- interp.cmd_pipe.close()
- interp.dbg_pipe.close()
- interp.ec_uart_pty.close()
- if shutdown_pipe is not None:
- shutdown_pipe.close()
- interp.logger.debug('Exit ec3po interpreter loop for %s',
- interp.ec_uart_pty_name)
+ """Starts an infinite loop of servicing the user and the EC.
+
+ StartLoop checks to see if there are any commands to process, processing them
+ if any, and forwards EC output to the user.
+
+ When sending a command to the EC, we send the command once and check the
+ response to see if the EC encountered an error when receiving the command. An
+ error condition is reported to the interpreter by a string with at least one
+ '&' and 'E'. The full string is actually '&&EE', however it's possible that
+ the leading ampersand or trailing 'E' could be dropped. If an error is
+ encountered, the interpreter will retry up to the amount configured.
+
+ Args:
+ interp: An Interpreter object that has been properly initialised.
+ shutdown_pipe: A file object for a pipe or equivalent that becomes readable
+ (not blocked) to indicate that the loop should exit. Can be None to never
+ exit the loop.
+ """
+ try:
+ # This is used instead of "break" to avoid exiting the loop in the middle of
+ # an iteration.
+ continue_looping = True
+
+ while continue_looping:
+ # The inputs list is created anew in each loop iteration because the
+ # Interpreter class sometimes modifies the interp.inputs list.
+ if shutdown_pipe is None:
+ inputs = interp.inputs
+ else:
+ inputs = list(interp.inputs)
+ inputs.append(shutdown_pipe)
+
+ readable, writeable, _ = select.select(inputs, interp.outputs, [])
+
+ for obj in readable:
+ # Handle any debug prints from the EC.
+ if obj is interp.ec_uart_pty:
+ interp.HandleECData()
+
+ # Handle any commands from the user.
+ elif obj is interp.cmd_pipe:
+ try:
+ interp.HandleUserData()
+ except EOFError:
+ interp.logger.debug(
+ "ec3po interpreter received EOF from cmd_pipe in "
+ "HandleUserData()"
+ )
+ continue_looping = False
+
+ elif obj is shutdown_pipe:
+ interp.logger.debug(
+ "ec3po interpreter received shutdown pipe unblocked notification"
+ )
+ continue_looping = False
+
+ for obj in writeable:
+ # Send a command to the EC.
+ if obj is interp.ec_uart_pty:
+ interp.SendCmdToEC()
+
+ except KeyboardInterrupt:
+ pass
+
+ finally:
+ interp.cmd_pipe.close()
+ interp.dbg_pipe.close()
+ interp.ec_uart_pty.close()
+ if shutdown_pipe is not None:
+ shutdown_pipe.close()
+ interp.logger.debug(
+ "Exit ec3po interpreter loop for %s", interp.ec_uart_pty_name
+ )