cros_ec_python.devices.dev

  1import errno
  2from fcntl import ioctl
  3import struct
  4from typing import Final, IO
  5import warnings
  6import os
  7
  8from ..baseclass import CrosEcClass
  9from ..constants.COMMON import *
 10from ..constants.MEMMAP import EC_MEMMAP_SIZE
 11from ..commands.general import EC_CMD_READ_MEMMAP
 12from ..exceptions import ECError
 13
 14__all__ = ["CrosEcDev"]
 15
 16CROS_EC_IOC_MAGIC: Final = 0xEC
 17
 18
 19def IOC(dir: int, type: int, nr: int, size: int):
 20    """
 21    Create an ioctl command number.
 22    Based on the Linux kernels include/uapi/asm-generic/ioctl.h.
 23    """
 24    nr_bits, type_bits, size_bits, dir_bits = 8, 8, 14, 2
 25    nr_shift = 0
 26    type_shift = nr_shift + nr_bits
 27    size_shift = type_shift + type_bits
 28    dir_shift = size_shift + size_bits
 29
 30    return dir << dir_shift | size << size_shift | type << type_shift | nr << nr_shift
 31
 32
 33def IORW(type: int, nr: int, size: int):
 34    """
 35    Create an ioctl command number for read/write commands.
 36    Based on the Linux kernels include/uapi/asm-generic/ioctl.h.
 37    """
 38    none = 0
 39    write = 1
 40    read = 2
 41    return IOC((read | write), type, nr, size)
 42
 43
 44class CrosEcDev(CrosEcClass):
 45    """
 46    Class to interact with the EC using the Linux cros_ec device.
 47    """
 48
 49    def __init__(self, fd: IO | None = None, memmap_ioctl: bool = True):
 50        """
 51        Initialise the EC using the Linux cros_ec device.
 52        :param fd: Use a custom file description, opens /dev/cros_ec by default.
 53        :param memmap_ioctl: Use ioctl for memmap (default), if False the READ_MEMMAP command will be used instead.
 54        """
 55        if fd is None:
 56            fd = open("/dev/cros_ec", "wb", buffering=0)
 57
 58        self.fd: IO = fd
 59        """The file descriptor for /dev/cros_ec."""
 60
 61        self.memmap_ioctl: bool = memmap_ioctl
 62        """Use ioctl for memmap, if False the READ_MEMMAP command will be used instead."""
 63
 64    def __del__(self):
 65        self.ec_exit()
 66
 67    @staticmethod
 68    def detect() -> bool:
 69        """
 70        Checks for `/dev/cros_ec` and returns True if it exists.
 71        """
 72        return os.path.exists("/dev/cros_ec")
 73
 74    def ec_init(self) -> None:
 75        pass
 76
 77    def ec_exit(self) -> None:
 78        """
 79        Close the file on exit.
 80        """
 81        if hasattr(self, "fd"):
 82            self.fd.close()
 83
 84    def command(
 85            self, version: Int32, command: Int32, outsize: Int32, insize: Int32, data: bytes = None, warn: bool = True
 86    ) -> bytes:
 87        """
 88        Send a command to the EC and return the response.
 89        :param version: Command version number (often 0).
 90        :param command: Command to send (EC_CMD_...).
 91        :param outsize: Outgoing length in bytes.
 92        :param insize: Max number of bytes to accept from the EC.
 93        :param data: Outgoing data to EC.
 94        :param warn: Whether to warn if the response size is not as expected. Default is True.
 95        :return: Incoming data from EC.
 96        """
 97        if data is None:
 98            data = bytes(outsize)
 99
100        cmd = struct.pack(f"<IIIII", version, command, outsize, insize, 0xFF)
101        buf = bytearray(cmd + bytes(max(outsize, insize)))
102        buf[len(cmd): len(cmd) + outsize] = data
103
104        CROS_EC_DEV_IOCXCMD = IORW(CROS_EC_IOC_MAGIC, 0, len(cmd))
105        result = ioctl(self.fd, CROS_EC_DEV_IOCXCMD, buf)
106
107        if result < 0:
108            raise IOError(f"ioctl failed with error {result}")
109
110        ec_result = struct.unpack("<IIIII", buf[:len(cmd)])
111
112        if ec_result[4] != 0:
113            raise ECError(ec_result[4])
114
115        if result != insize and warn:
116            warnings.warn(f"Expected {insize} bytes, got {result} back from EC", RuntimeWarning)
117
118        return bytes(buf[len(cmd): len(cmd) + insize])
119
120    def memmap(self, offset: Int32, num_bytes: Int32) -> bytes:
121        """
122        Read memory from the EC.
123        :param offset: Offset to read from.
124        :param num_bytes: Number of bytes to read.
125        :return: Bytes read from the EC.
126        """
127        if self.memmap_ioctl:
128            data = struct.pack("<II", offset, num_bytes)
129            buf = bytearray(data + bytes(num_bytes))
130            CROS_EC_DEV_IOCRDMEM = IORW(
131                CROS_EC_IOC_MAGIC, 1, len(data) + EC_MEMMAP_SIZE + 1
132            )
133            try:
134                result = ioctl(self.fd, CROS_EC_DEV_IOCRDMEM, buf)
135
136                if result < 0:
137                    raise IOError(f"ioctl failed with error {result}")
138
139                if result != num_bytes:
140                    warnings.warn(f"Expected {num_bytes} bytes, got {result} back from EC", RuntimeWarning)
141
142                return buf[len(data): len(data) + num_bytes]
143            except OSError as e:
144                if e.errno == errno.ENOTTY:
145                    warnings.warn("ioctl failed, falling back to READ_MEMMAP command", RuntimeWarning)
146                    self.memmap_ioctl = False
147                    return self.memmap(offset, num_bytes)
148                else:
149                    raise e
150        else:
151            # This is untested!
152            data = struct.pack("<BB", offset, num_bytes)
153            buf = self.command(0, EC_CMD_READ_MEMMAP, len(data), num_bytes, data)
154            return buf[len(data): len(data) + num_bytes]
class CrosEcDev(cros_ec_python.baseclass.CrosEcClass):
 45class CrosEcDev(CrosEcClass):
 46    """
 47    Class to interact with the EC using the Linux cros_ec device.
 48    """
 49
 50    def __init__(self, fd: IO | None = None, memmap_ioctl: bool = True):
 51        """
 52        Initialise the EC using the Linux cros_ec device.
 53        :param fd: Use a custom file description, opens /dev/cros_ec by default.
 54        :param memmap_ioctl: Use ioctl for memmap (default), if False the READ_MEMMAP command will be used instead.
 55        """
 56        if fd is None:
 57            fd = open("/dev/cros_ec", "wb", buffering=0)
 58
 59        self.fd: IO = fd
 60        """The file descriptor for /dev/cros_ec."""
 61
 62        self.memmap_ioctl: bool = memmap_ioctl
 63        """Use ioctl for memmap, if False the READ_MEMMAP command will be used instead."""
 64
 65    def __del__(self):
 66        self.ec_exit()
 67
 68    @staticmethod
 69    def detect() -> bool:
 70        """
 71        Checks for `/dev/cros_ec` and returns True if it exists.
 72        """
 73        return os.path.exists("/dev/cros_ec")
 74
 75    def ec_init(self) -> None:
 76        pass
 77
 78    def ec_exit(self) -> None:
 79        """
 80        Close the file on exit.
 81        """
 82        if hasattr(self, "fd"):
 83            self.fd.close()
 84
 85    def command(
 86            self, version: Int32, command: Int32, outsize: Int32, insize: Int32, data: bytes = None, warn: bool = True
 87    ) -> bytes:
 88        """
 89        Send a command to the EC and return the response.
 90        :param version: Command version number (often 0).
 91        :param command: Command to send (EC_CMD_...).
 92        :param outsize: Outgoing length in bytes.
 93        :param insize: Max number of bytes to accept from the EC.
 94        :param data: Outgoing data to EC.
 95        :param warn: Whether to warn if the response size is not as expected. Default is True.
 96        :return: Incoming data from EC.
 97        """
 98        if data is None:
 99            data = bytes(outsize)
100
101        cmd = struct.pack(f"<IIIII", version, command, outsize, insize, 0xFF)
102        buf = bytearray(cmd + bytes(max(outsize, insize)))
103        buf[len(cmd): len(cmd) + outsize] = data
104
105        CROS_EC_DEV_IOCXCMD = IORW(CROS_EC_IOC_MAGIC, 0, len(cmd))
106        result = ioctl(self.fd, CROS_EC_DEV_IOCXCMD, buf)
107
108        if result < 0:
109            raise IOError(f"ioctl failed with error {result}")
110
111        ec_result = struct.unpack("<IIIII", buf[:len(cmd)])
112
113        if ec_result[4] != 0:
114            raise ECError(ec_result[4])
115
116        if result != insize and warn:
117            warnings.warn(f"Expected {insize} bytes, got {result} back from EC", RuntimeWarning)
118
119        return bytes(buf[len(cmd): len(cmd) + insize])
120
121    def memmap(self, offset: Int32, num_bytes: Int32) -> bytes:
122        """
123        Read memory from the EC.
124        :param offset: Offset to read from.
125        :param num_bytes: Number of bytes to read.
126        :return: Bytes read from the EC.
127        """
128        if self.memmap_ioctl:
129            data = struct.pack("<II", offset, num_bytes)
130            buf = bytearray(data + bytes(num_bytes))
131            CROS_EC_DEV_IOCRDMEM = IORW(
132                CROS_EC_IOC_MAGIC, 1, len(data) + EC_MEMMAP_SIZE + 1
133            )
134            try:
135                result = ioctl(self.fd, CROS_EC_DEV_IOCRDMEM, buf)
136
137                if result < 0:
138                    raise IOError(f"ioctl failed with error {result}")
139
140                if result != num_bytes:
141                    warnings.warn(f"Expected {num_bytes} bytes, got {result} back from EC", RuntimeWarning)
142
143                return buf[len(data): len(data) + num_bytes]
144            except OSError as e:
145                if e.errno == errno.ENOTTY:
146                    warnings.warn("ioctl failed, falling back to READ_MEMMAP command", RuntimeWarning)
147                    self.memmap_ioctl = False
148                    return self.memmap(offset, num_bytes)
149                else:
150                    raise e
151        else:
152            # This is untested!
153            data = struct.pack("<BB", offset, num_bytes)
154            buf = self.command(0, EC_CMD_READ_MEMMAP, len(data), num_bytes, data)
155            return buf[len(data): len(data) + num_bytes]

Class to interact with the EC using the Linux cros_ec device.

CrosEcDev(fd: typing.IO | None = None, memmap_ioctl: bool = True)
50    def __init__(self, fd: IO | None = None, memmap_ioctl: bool = True):
51        """
52        Initialise the EC using the Linux cros_ec device.
53        :param fd: Use a custom file description, opens /dev/cros_ec by default.
54        :param memmap_ioctl: Use ioctl for memmap (default), if False the READ_MEMMAP command will be used instead.
55        """
56        if fd is None:
57            fd = open("/dev/cros_ec", "wb", buffering=0)
58
59        self.fd: IO = fd
60        """The file descriptor for /dev/cros_ec."""
61
62        self.memmap_ioctl: bool = memmap_ioctl
63        """Use ioctl for memmap, if False the READ_MEMMAP command will be used instead."""

Initialise the EC using the Linux cros_ec device.

Parameters
  • fd: Use a custom file description, opens /dev/cros_ec by default.
  • memmap_ioctl: Use ioctl for memmap (default), if False the READ_MEMMAP command will be used instead.
fd: <class 'IO'>

The file descriptor for /dev/cros_ec.

memmap_ioctl: bool

Use ioctl for memmap, if False the READ_MEMMAP command will be used instead.

@staticmethod
def detect() -> bool:
68    @staticmethod
69    def detect() -> bool:
70        """
71        Checks for `/dev/cros_ec` and returns True if it exists.
72        """
73        return os.path.exists("/dev/cros_ec")

Checks for /dev/cros_ec and returns True if it exists.

def ec_init(self) -> None:
75    def ec_init(self) -> None:
76        pass

Initialise the EC, this shouldn't need to be called unless the keyword arg init was set to false.

def ec_exit(self) -> None:
78    def ec_exit(self) -> None:
79        """
80        Close the file on exit.
81        """
82        if hasattr(self, "fd"):
83            self.fd.close()

Close the file on exit.

def command( self, version: int, command: int, outsize: int, insize: int, data: bytes = None, warn: bool = True) -> bytes:
 85    def command(
 86            self, version: Int32, command: Int32, outsize: Int32, insize: Int32, data: bytes = None, warn: bool = True
 87    ) -> bytes:
 88        """
 89        Send a command to the EC and return the response.
 90        :param version: Command version number (often 0).
 91        :param command: Command to send (EC_CMD_...).
 92        :param outsize: Outgoing length in bytes.
 93        :param insize: Max number of bytes to accept from the EC.
 94        :param data: Outgoing data to EC.
 95        :param warn: Whether to warn if the response size is not as expected. Default is True.
 96        :return: Incoming data from EC.
 97        """
 98        if data is None:
 99            data = bytes(outsize)
100
101        cmd = struct.pack(f"<IIIII", version, command, outsize, insize, 0xFF)
102        buf = bytearray(cmd + bytes(max(outsize, insize)))
103        buf[len(cmd): len(cmd) + outsize] = data
104
105        CROS_EC_DEV_IOCXCMD = IORW(CROS_EC_IOC_MAGIC, 0, len(cmd))
106        result = ioctl(self.fd, CROS_EC_DEV_IOCXCMD, buf)
107
108        if result < 0:
109            raise IOError(f"ioctl failed with error {result}")
110
111        ec_result = struct.unpack("<IIIII", buf[:len(cmd)])
112
113        if ec_result[4] != 0:
114            raise ECError(ec_result[4])
115
116        if result != insize and warn:
117            warnings.warn(f"Expected {insize} bytes, got {result} back from EC", RuntimeWarning)
118
119        return bytes(buf[len(cmd): len(cmd) + insize])

Send a command to the EC and return the response.

Parameters
  • version: Command version number (often 0).
  • command: Command to send (EC_CMD_...).
  • outsize: Outgoing length in bytes.
  • insize: Max number of bytes to accept from the EC.
  • data: Outgoing data to EC.
  • warn: Whether to warn if the response size is not as expected. Default is True.
Returns

Incoming data from EC.

def memmap(self, offset: int, num_bytes: int) -> bytes:
121    def memmap(self, offset: Int32, num_bytes: Int32) -> bytes:
122        """
123        Read memory from the EC.
124        :param offset: Offset to read from.
125        :param num_bytes: Number of bytes to read.
126        :return: Bytes read from the EC.
127        """
128        if self.memmap_ioctl:
129            data = struct.pack("<II", offset, num_bytes)
130            buf = bytearray(data + bytes(num_bytes))
131            CROS_EC_DEV_IOCRDMEM = IORW(
132                CROS_EC_IOC_MAGIC, 1, len(data) + EC_MEMMAP_SIZE + 1
133            )
134            try:
135                result = ioctl(self.fd, CROS_EC_DEV_IOCRDMEM, buf)
136
137                if result < 0:
138                    raise IOError(f"ioctl failed with error {result}")
139
140                if result != num_bytes:
141                    warnings.warn(f"Expected {num_bytes} bytes, got {result} back from EC", RuntimeWarning)
142
143                return buf[len(data): len(data) + num_bytes]
144            except OSError as e:
145                if e.errno == errno.ENOTTY:
146                    warnings.warn("ioctl failed, falling back to READ_MEMMAP command", RuntimeWarning)
147                    self.memmap_ioctl = False
148                    return self.memmap(offset, num_bytes)
149                else:
150                    raise e
151        else:
152            # This is untested!
153            data = struct.pack("<BB", offset, num_bytes)
154            buf = self.command(0, EC_CMD_READ_MEMMAP, len(data), num_bytes, data)
155            return buf[len(data): len(data) + num_bytes]

Read memory from the EC.

Parameters
  • offset: Offset to read from.
  • num_bytes: Number of bytes to read.
Returns

Bytes read from the EC.