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]
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.
@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:
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.