cros_ec_python.devices.pawnio

  1import struct
  2import warnings
  3import ctypes
  4import os
  5import sys
  6from ctypes import wintypes
  7from typing import Iterable
  8
  9# Workaround for pdoc failing on Linux
 10if os.name == 'nt':
 11    import winreg
 12
 13from ..baseclass import CrosEcClass
 14from ..constants.COMMON import *
 15from ..exceptions import ECError
 16
 17
 18class CrosEcPawnIO(CrosEcClass):
 19    """
 20    Class to interact with the EC using the Windows PawnIO driver.
 21    """
 22
 23    def __init__(self, dll: str | None = None, bin: str | None = None):
 24        """
 25        Initialise the EC using the PawnIO LpcCrOSEC driver.
 26        :param dll: Path to the DLL to use. If None, will use the default path.
 27        :param bin: Path to the binary to load. If None, will use the default path.
 28        """
 29
 30        if getattr(sys, 'frozen', False) and hasattr(sys, '_MEIPASS'):
 31            self.bin = bin or os.path.join(sys._MEIPASS, "LpcCrOSEC.bin")
 32        else:
 33            self.bin = bin or "LpcCrOSEC.bin"
 34
 35        if not dll:
 36            dll = self._get_location()
 37        self.pawniolib = ctypes.OleDLL(dll)
 38
 39        self.pawniolib.pawnio_version.argtypes = [ctypes.POINTER(wintypes.ULONG)]
 40        self.pawniolib.pawnio_open.argtypes = [ctypes.POINTER(wintypes.HANDLE)]
 41        self.pawniolib.pawnio_load.argtypes = [
 42            wintypes.HANDLE,
 43            ctypes.POINTER(ctypes.c_ubyte),
 44            ctypes.c_size_t,
 45        ]
 46        self.pawniolib.pawnio_execute.argtypes = [
 47            wintypes.HANDLE,
 48            ctypes.c_char_p,
 49            ctypes.POINTER(ctypes.c_ulonglong),
 50            ctypes.c_size_t,
 51            ctypes.POINTER(ctypes.c_ulonglong),
 52            ctypes.c_size_t,
 53            ctypes.POINTER(ctypes.c_size_t),
 54        ]
 55        self.pawniolib.pawnio_close.argtypes = [wintypes.HANDLE]
 56
 57        self.ec_init()
 58
 59    def __del__(self):
 60        self.ec_exit()
 61
 62    def _pawnio_version(self) -> str:
 63        version = wintypes.ULONG()
 64        self.pawniolib.pawnio_version(ctypes.byref(version))
 65        major, minor, patch = (
 66            version.value >> 16,
 67            (version.value >> 8) & 0xFF,
 68            version.value & 0xFF,
 69        )
 70        return f"{major}.{minor}.{patch}"
 71
 72    def _pawnio_open(self) -> None:
 73        self.handle = wintypes.HANDLE()
 74        self.pawniolib.pawnio_open(ctypes.byref(self.handle))
 75
 76    def _pawnio_load(self, filepath: str) -> None:
 77        with open(filepath, "rb") as file:
 78            blob = file.read()
 79        size = len(blob)
 80        blob_array = (ctypes.c_ubyte * size)(*blob)
 81        self.pawniolib.pawnio_load(self.handle, blob_array, size)
 82
 83    def _pawnio_execute(
 84        self,
 85        function: str,
 86        in_data: Iterable,
 87        out_size: int,
 88        in_size: int | None = None,
 89    ) -> tuple[int | ctypes.Array]:
 90        function_bytes = function.encode("utf-8")
 91        in_size = in_size if in_size is not None else len(in_data)
 92        in_array = (ctypes.c_uint64 * in_size)(*in_data)
 93        out_array = (ctypes.c_uint64 * out_size)()
 94        return_size = ctypes.c_size_t()
 95
 96        self.pawniolib.pawnio_execute(
 97            self.handle,
 98            function_bytes,
 99            in_array,
100            in_size,
101            out_array,
102            out_size,
103            ctypes.byref(return_size),
104        )
105
106        return (return_size.value, out_array)
107
108    def _pawnio_close(self):
109        self.pawniolib.pawnio_close(self.handle)
110
111    @staticmethod
112    def _get_location() -> str | None:
113        """
114        Get the location of the PawnIO driver.
115        """
116        try:
117            with winreg.OpenKey(
118                winreg.HKEY_LOCAL_MACHINE,
119                r"SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\PawnIO"
120            ) as key:
121                return winreg.QueryValueEx(key, "InstallLocation")[0] + r"\\PawnIOLib.dll"
122        except FileNotFoundError:
123            fallback = os.environ.get("ProgramFiles", r"C:\Program Files") + r"\PawnIO\PawnIOLib.dll"
124            if os.path.exists(fallback):
125                return fallback
126            return None
127
128    @staticmethod
129    def detect() -> bool:
130        """
131        Detect if the PawnIO driver is installed.
132        """
133        return CrosEcPawnIO._get_location() is not None
134
135    def ec_init(self) -> None:
136        self._pawnio_open()
137        self._pawnio_load(self.bin)
138
139    def ec_exit(self) -> None:
140        """
141        Close the file on exit.
142        """
143        if hasattr(self, "handle"):
144            self._pawnio_close()
145
146    def command(
147        self,
148        version: Int32,
149        command: Int32,
150        outsize: Int32,
151        insize: Int32,
152        data: bytes = None,
153        warn: bool = True,
154    ) -> bytes:
155        """
156        Send a command to the EC and return the response.
157        :param version: Command version number (often 0).
158        :param command: Command to send (EC_CMD_...).
159        :param outsize: Outgoing length in bytes.
160        :param insize: Max number of bytes to accept from the EC.
161        :param data: Outgoing data to EC.
162        :param warn: Whether to warn if the response size is not as expected. Default is True.
163        :return: Incoming data from EC.
164        """
165
166        pawn_in_data = [version, command, outsize, insize]
167        # Convert data to 64-bit cells
168        for i in range(0, len(data or ()), 8):
169            if i + 8 > len(data):
170                # If the data is not a multiple of 8, pad it with zeros
171                pawn_in_data.append(struct.unpack('<Q', data[i:i+8] + b'\x00' * (8 - len(data[i:i+8])))[0])
172            else:
173                pawn_in_data.append(struct.unpack('<Q', data[i:i+8])[0])
174
175        # Convert insize from bytes to 64bit cells, + 1 for the EC result
176        pawn_out_size = -(-insize//8) + 1
177
178        size, res = self._pawnio_execute(
179            "ioctl_ec_command",
180            pawn_in_data,
181            pawn_out_size
182        )
183
184        if size != pawn_out_size and warn:
185            warnings.warn(
186                f"Expected {pawn_out_size} bytes, got {size} back from PawnIO",
187                RuntimeWarning,
188            )
189
190        # If the first cell is negative, it has failed
191        # Also convert to signed
192        if res[0] & (1 << 63):
193            signed_res = res[0] - (1 << 64)
194            raise ECError(-signed_res)
195
196        # Otherwise it's the length
197        if res[0] != insize and warn:
198            warnings.warn(
199                f"Expected {pawn_out_size} bytes, got {res[0]} back from EC",
200                RuntimeWarning,
201            )
202        return bytes(res)[8:insize+8]
203
204    def memmap(self, offset: Int32, num_bytes: Int32) -> bytes:
205        """
206        Read memory from the EC.
207        :param offset: Offset to read from.
208        :param num_bytes: Number of bytes to read.
209        :return: Bytes read from the EC.
210        """
211        size, res = self._pawnio_execute(
212            "ioctl_ec_readmem", (offset, num_bytes), -(-num_bytes//8)
213        )
214        return bytes(res)[:num_bytes]
class CrosEcPawnIO(cros_ec_python.baseclass.CrosEcClass):
 19class CrosEcPawnIO(CrosEcClass):
 20    """
 21    Class to interact with the EC using the Windows PawnIO driver.
 22    """
 23
 24    def __init__(self, dll: str | None = None, bin: str | None = None):
 25        """
 26        Initialise the EC using the PawnIO LpcCrOSEC driver.
 27        :param dll: Path to the DLL to use. If None, will use the default path.
 28        :param bin: Path to the binary to load. If None, will use the default path.
 29        """
 30
 31        if getattr(sys, 'frozen', False) and hasattr(sys, '_MEIPASS'):
 32            self.bin = bin or os.path.join(sys._MEIPASS, "LpcCrOSEC.bin")
 33        else:
 34            self.bin = bin or "LpcCrOSEC.bin"
 35
 36        if not dll:
 37            dll = self._get_location()
 38        self.pawniolib = ctypes.OleDLL(dll)
 39
 40        self.pawniolib.pawnio_version.argtypes = [ctypes.POINTER(wintypes.ULONG)]
 41        self.pawniolib.pawnio_open.argtypes = [ctypes.POINTER(wintypes.HANDLE)]
 42        self.pawniolib.pawnio_load.argtypes = [
 43            wintypes.HANDLE,
 44            ctypes.POINTER(ctypes.c_ubyte),
 45            ctypes.c_size_t,
 46        ]
 47        self.pawniolib.pawnio_execute.argtypes = [
 48            wintypes.HANDLE,
 49            ctypes.c_char_p,
 50            ctypes.POINTER(ctypes.c_ulonglong),
 51            ctypes.c_size_t,
 52            ctypes.POINTER(ctypes.c_ulonglong),
 53            ctypes.c_size_t,
 54            ctypes.POINTER(ctypes.c_size_t),
 55        ]
 56        self.pawniolib.pawnio_close.argtypes = [wintypes.HANDLE]
 57
 58        self.ec_init()
 59
 60    def __del__(self):
 61        self.ec_exit()
 62
 63    def _pawnio_version(self) -> str:
 64        version = wintypes.ULONG()
 65        self.pawniolib.pawnio_version(ctypes.byref(version))
 66        major, minor, patch = (
 67            version.value >> 16,
 68            (version.value >> 8) & 0xFF,
 69            version.value & 0xFF,
 70        )
 71        return f"{major}.{minor}.{patch}"
 72
 73    def _pawnio_open(self) -> None:
 74        self.handle = wintypes.HANDLE()
 75        self.pawniolib.pawnio_open(ctypes.byref(self.handle))
 76
 77    def _pawnio_load(self, filepath: str) -> None:
 78        with open(filepath, "rb") as file:
 79            blob = file.read()
 80        size = len(blob)
 81        blob_array = (ctypes.c_ubyte * size)(*blob)
 82        self.pawniolib.pawnio_load(self.handle, blob_array, size)
 83
 84    def _pawnio_execute(
 85        self,
 86        function: str,
 87        in_data: Iterable,
 88        out_size: int,
 89        in_size: int | None = None,
 90    ) -> tuple[int | ctypes.Array]:
 91        function_bytes = function.encode("utf-8")
 92        in_size = in_size if in_size is not None else len(in_data)
 93        in_array = (ctypes.c_uint64 * in_size)(*in_data)
 94        out_array = (ctypes.c_uint64 * out_size)()
 95        return_size = ctypes.c_size_t()
 96
 97        self.pawniolib.pawnio_execute(
 98            self.handle,
 99            function_bytes,
100            in_array,
101            in_size,
102            out_array,
103            out_size,
104            ctypes.byref(return_size),
105        )
106
107        return (return_size.value, out_array)
108
109    def _pawnio_close(self):
110        self.pawniolib.pawnio_close(self.handle)
111
112    @staticmethod
113    def _get_location() -> str | None:
114        """
115        Get the location of the PawnIO driver.
116        """
117        try:
118            with winreg.OpenKey(
119                winreg.HKEY_LOCAL_MACHINE,
120                r"SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\PawnIO"
121            ) as key:
122                return winreg.QueryValueEx(key, "InstallLocation")[0] + r"\\PawnIOLib.dll"
123        except FileNotFoundError:
124            fallback = os.environ.get("ProgramFiles", r"C:\Program Files") + r"\PawnIO\PawnIOLib.dll"
125            if os.path.exists(fallback):
126                return fallback
127            return None
128
129    @staticmethod
130    def detect() -> bool:
131        """
132        Detect if the PawnIO driver is installed.
133        """
134        return CrosEcPawnIO._get_location() is not None
135
136    def ec_init(self) -> None:
137        self._pawnio_open()
138        self._pawnio_load(self.bin)
139
140    def ec_exit(self) -> None:
141        """
142        Close the file on exit.
143        """
144        if hasattr(self, "handle"):
145            self._pawnio_close()
146
147    def command(
148        self,
149        version: Int32,
150        command: Int32,
151        outsize: Int32,
152        insize: Int32,
153        data: bytes = None,
154        warn: bool = True,
155    ) -> bytes:
156        """
157        Send a command to the EC and return the response.
158        :param version: Command version number (often 0).
159        :param command: Command to send (EC_CMD_...).
160        :param outsize: Outgoing length in bytes.
161        :param insize: Max number of bytes to accept from the EC.
162        :param data: Outgoing data to EC.
163        :param warn: Whether to warn if the response size is not as expected. Default is True.
164        :return: Incoming data from EC.
165        """
166
167        pawn_in_data = [version, command, outsize, insize]
168        # Convert data to 64-bit cells
169        for i in range(0, len(data or ()), 8):
170            if i + 8 > len(data):
171                # If the data is not a multiple of 8, pad it with zeros
172                pawn_in_data.append(struct.unpack('<Q', data[i:i+8] + b'\x00' * (8 - len(data[i:i+8])))[0])
173            else:
174                pawn_in_data.append(struct.unpack('<Q', data[i:i+8])[0])
175
176        # Convert insize from bytes to 64bit cells, + 1 for the EC result
177        pawn_out_size = -(-insize//8) + 1
178
179        size, res = self._pawnio_execute(
180            "ioctl_ec_command",
181            pawn_in_data,
182            pawn_out_size
183        )
184
185        if size != pawn_out_size and warn:
186            warnings.warn(
187                f"Expected {pawn_out_size} bytes, got {size} back from PawnIO",
188                RuntimeWarning,
189            )
190
191        # If the first cell is negative, it has failed
192        # Also convert to signed
193        if res[0] & (1 << 63):
194            signed_res = res[0] - (1 << 64)
195            raise ECError(-signed_res)
196
197        # Otherwise it's the length
198        if res[0] != insize and warn:
199            warnings.warn(
200                f"Expected {pawn_out_size} bytes, got {res[0]} back from EC",
201                RuntimeWarning,
202            )
203        return bytes(res)[8:insize+8]
204
205    def memmap(self, offset: Int32, num_bytes: Int32) -> bytes:
206        """
207        Read memory from the EC.
208        :param offset: Offset to read from.
209        :param num_bytes: Number of bytes to read.
210        :return: Bytes read from the EC.
211        """
212        size, res = self._pawnio_execute(
213            "ioctl_ec_readmem", (offset, num_bytes), -(-num_bytes//8)
214        )
215        return bytes(res)[:num_bytes]

Class to interact with the EC using the Windows PawnIO driver.

CrosEcPawnIO(dll: str | None = None, bin: str | None = None)
24    def __init__(self, dll: str | None = None, bin: str | None = None):
25        """
26        Initialise the EC using the PawnIO LpcCrOSEC driver.
27        :param dll: Path to the DLL to use. If None, will use the default path.
28        :param bin: Path to the binary to load. If None, will use the default path.
29        """
30
31        if getattr(sys, 'frozen', False) and hasattr(sys, '_MEIPASS'):
32            self.bin = bin or os.path.join(sys._MEIPASS, "LpcCrOSEC.bin")
33        else:
34            self.bin = bin or "LpcCrOSEC.bin"
35
36        if not dll:
37            dll = self._get_location()
38        self.pawniolib = ctypes.OleDLL(dll)
39
40        self.pawniolib.pawnio_version.argtypes = [ctypes.POINTER(wintypes.ULONG)]
41        self.pawniolib.pawnio_open.argtypes = [ctypes.POINTER(wintypes.HANDLE)]
42        self.pawniolib.pawnio_load.argtypes = [
43            wintypes.HANDLE,
44            ctypes.POINTER(ctypes.c_ubyte),
45            ctypes.c_size_t,
46        ]
47        self.pawniolib.pawnio_execute.argtypes = [
48            wintypes.HANDLE,
49            ctypes.c_char_p,
50            ctypes.POINTER(ctypes.c_ulonglong),
51            ctypes.c_size_t,
52            ctypes.POINTER(ctypes.c_ulonglong),
53            ctypes.c_size_t,
54            ctypes.POINTER(ctypes.c_size_t),
55        ]
56        self.pawniolib.pawnio_close.argtypes = [wintypes.HANDLE]
57
58        self.ec_init()

Initialise the EC using the PawnIO LpcCrOSEC driver.

Parameters
  • dll: Path to the DLL to use. If None, will use the default path.
  • bin: Path to the binary to load. If None, will use the default path.
pawniolib
@staticmethod
def detect() -> bool:
129    @staticmethod
130    def detect() -> bool:
131        """
132        Detect if the PawnIO driver is installed.
133        """
134        return CrosEcPawnIO._get_location() is not None

Detect if the PawnIO driver is installed.

def ec_init(self) -> None:
136    def ec_init(self) -> None:
137        self._pawnio_open()
138        self._pawnio_load(self.bin)

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

def ec_exit(self) -> None:
140    def ec_exit(self) -> None:
141        """
142        Close the file on exit.
143        """
144        if hasattr(self, "handle"):
145            self._pawnio_close()

Close the file on exit.

def command( self, version: int, command: int, outsize: int, insize: int, data: bytes = None, warn: bool = True) -> bytes:
147    def command(
148        self,
149        version: Int32,
150        command: Int32,
151        outsize: Int32,
152        insize: Int32,
153        data: bytes = None,
154        warn: bool = True,
155    ) -> bytes:
156        """
157        Send a command to the EC and return the response.
158        :param version: Command version number (often 0).
159        :param command: Command to send (EC_CMD_...).
160        :param outsize: Outgoing length in bytes.
161        :param insize: Max number of bytes to accept from the EC.
162        :param data: Outgoing data to EC.
163        :param warn: Whether to warn if the response size is not as expected. Default is True.
164        :return: Incoming data from EC.
165        """
166
167        pawn_in_data = [version, command, outsize, insize]
168        # Convert data to 64-bit cells
169        for i in range(0, len(data or ()), 8):
170            if i + 8 > len(data):
171                # If the data is not a multiple of 8, pad it with zeros
172                pawn_in_data.append(struct.unpack('<Q', data[i:i+8] + b'\x00' * (8 - len(data[i:i+8])))[0])
173            else:
174                pawn_in_data.append(struct.unpack('<Q', data[i:i+8])[0])
175
176        # Convert insize from bytes to 64bit cells, + 1 for the EC result
177        pawn_out_size = -(-insize//8) + 1
178
179        size, res = self._pawnio_execute(
180            "ioctl_ec_command",
181            pawn_in_data,
182            pawn_out_size
183        )
184
185        if size != pawn_out_size and warn:
186            warnings.warn(
187                f"Expected {pawn_out_size} bytes, got {size} back from PawnIO",
188                RuntimeWarning,
189            )
190
191        # If the first cell is negative, it has failed
192        # Also convert to signed
193        if res[0] & (1 << 63):
194            signed_res = res[0] - (1 << 64)
195            raise ECError(-signed_res)
196
197        # Otherwise it's the length
198        if res[0] != insize and warn:
199            warnings.warn(
200                f"Expected {pawn_out_size} bytes, got {res[0]} back from EC",
201                RuntimeWarning,
202            )
203        return bytes(res)[8:insize+8]

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:
205    def memmap(self, offset: Int32, num_bytes: Int32) -> bytes:
206        """
207        Read memory from the EC.
208        :param offset: Offset to read from.
209        :param num_bytes: Number of bytes to read.
210        :return: Bytes read from the EC.
211        """
212        size, res = self._pawnio_execute(
213            "ioctl_ec_readmem", (offset, num_bytes), -(-num_bytes//8)
214        )
215        return bytes(res)[: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.