blob: 560f73dbeb43fca76983d4879ea20133e0510e3a [file] [log] [blame] [edit]
# Copyright 2020 The ChromiumOS Authors
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
"""Driver to talk to the i2c channels through the DUT EC console."""
import re
from servo.drv import ec
# pylint: disable=invalid-name
# This conforms to the servod error naming pattern.
class ecI2cPinError(ec.ecError):
"""Exception class for ec i2c pin."""
# TODO(b/180671248): delete this code (better yet, revert the CL as
# soon as that change lands.
# A list of all cros ec error regex strings.
# source: src/platform/ec/common/console.c
CROS_EC_ERROR_STRING_RX = [
r"Unknown error",
r"Unimplemented",
r"Overflow",
r"Timeout",
r"Invalid argument",
r"Busy",
r"Access Denied",
r"Not Powered",
r"Not Calibrated",
r"Parameter \d+ invalid",
r"Wrong number of params",
r"Command returned error \d+",
r"Command '\w+' not found or ambiguous.",
]
CROS_EC_ERROR_RX = "(" + "|".join(CROS_EC_ERROR_STRING_RX) + ")"
# pylint: disable=invalid-name
# This conforms to the servod drv naming convention.
class ecI2cPin(ec.ec):
"""Class to handle communication with the EC console."""
# 'i2cxfer %s[w|r] %d[bus] 0x%x[addr] 0x%x'[offset]
BASE_CMD = "i2cxfer %s %d 0x%x 0x%x"
REGEX = r"(0x[0-9a-f]+) \[\d+\][\n\r]"
REQUIRED_GET_PARAMS = ["bus", "addr", "offset", "mask"]
REQUIRED_SET_PARAMS = REQUIRED_GET_PARAMS
def _drv_init(self):
"""Driver specific initializer."""
super(ecI2cPin, self)._drv_init()
# Set the valid input choices for this driver.
# _choices needs to be a compiled regex
self._choices = re.compile("^(0|1)$")
self._mask = int(self._params["mask"], 0)
self._logger.debug("Mask %d" % self._mask)
if bin(self._mask).count("1") != 1:
# We want a binary mask.
raise ecI2cPinError(
"Mask 0x%x covers does not mask only one bit" % self._mask
)
bus = int(self._params["bus"])
addr = int(self._params["addr"], 0)
offset = int(self._params["offset"], 0)
self._read = self.BASE_CMD % ("r", bus, addr, offset)
self._write_base = self.BASE_CMD % ("w", bus, addr, offset)
# TODO(b/180671248): delete this code
# expand the regexes to also look for errors.
def _get_cmd_results(self, cmd, regex):
"""Overwrite to also catch ec console errors.
Args:
cmd: command to send to the EC console
regex: regex to look for
Returns:
result of the command
Raises:
ecI2cPinError: if a known issue issue is found.
"""
# TODO(b/180671248): delete this method.
# modified regex to also catch errors.
mregex = CROS_EC_ERROR_RX + "|" + regex
r = ec.ec._issue_cmd_get_results(self, cmd, [mregex])
if r is None:
raise ecI2cPinError("Failed to read out the i2c register value")
# we know that |r| is a list of list/tuple to match regex. So we need
# to check whether it matched an error string.
if re.search(CROS_EC_ERROR_RX, r[0][0]):
raise ecI2cPinError("Ran into cros ec error: %r" % r[0][0])
# We only ever pass in one regex in this drv/so there is only ever
# one member in the highest level.
return r[0]
def _set(self, value):
"""Set the bit to tbe |value|."""
# register value
rv = self._raw_read()
if value:
rv |= self._mask
else:
rv &= ~self._mask
# actually need to write the value.
write_cmd = "%s 0x%x" % (self._write_base, rv)
# Simply wait until the writing is finished.
self._get_cmd_results(write_cmd, "(>)")
def _raw_read(self):
"""Read the raw hex value out for the whole offset."""
# TODO(coconutruben): unify this with |_Get_output| in the ec.
self._limit_channel()
result = self._get_cmd_results(self._read, self.REGEX)
self._restore_channel()
# The regex has None as its first group (because nothing in the error group
# matched). Need to get to the second group.
val_str = result[2]
val = int(val_str, 16)
return val
def _get(self):
"""Read out the value."""
# Cast through bool to make it binary (it's set or it's not), and then
# through int to return a proper int as required by the servod code.
return int(bool(self._raw_read() & self._mask))