cros_ec_python.devices.win_fw_ec

  1from typing import Final
  2import warnings
  3import os
  4import ctypes
  5from ctypes import wintypes
  6
  7# Workaround for pdoc failing on Linux
  8if os.name == 'nt':
  9    from ctypes import windll
 10
 11__all__ = ["WinFrameworkEc"]
 12
 13from ..baseclass import CrosEcClass
 14from ..constants.COMMON import *
 15from ..constants.MEMMAP import EC_MEMMAP_SIZE
 16from ..exceptions import ECError
 17
 18FILE_GENERIC_READ: Final    = 0x80000000
 19FILE_GENERIC_WRITE: Final   = 0x40000000
 20FILE_SHARE_READ: Final      = 0x00000001
 21FILE_SHARE_WRITE: Final     = 0x00000002
 22OPEN_EXISTING: Final        = 3
 23
 24FILE_READ_DATA: Final       = 0x0001
 25FILE_WRITE_DATA: Final      = 0x0002
 26FILE_READ_ACCESS: Final     = 0x0001
 27METHOD_BUFFERED: Final      = 0
 28
 29INVALID_HANDLE_VALUE: Final = -1
 30
 31
 32def CTL_CODE(device_type, function, method, access) -> int:
 33    return (device_type << 16) | (access << 14) | (function << 2) | method
 34
 35
 36CROSEC_CMD_MAX_REQUEST = 0x100
 37
 38FILE_DEVICE_CROS_EMBEDDED_CONTROLLER = 0x80EC
 39
 40IOCTL_CROSEC_XCMD = CTL_CODE(
 41    FILE_DEVICE_CROS_EMBEDDED_CONTROLLER,
 42    0x801,
 43    METHOD_BUFFERED,
 44    FILE_READ_DATA | FILE_WRITE_DATA,
 45)
 46IOCTL_CROSEC_RDMEM = CTL_CODE(
 47    FILE_DEVICE_CROS_EMBEDDED_CONTROLLER,
 48    0x802,
 49    METHOD_BUFFERED,
 50    FILE_READ_ACCESS,
 51)
 52
 53
 54def CreateFileW(
 55    filename: str, access: int, mode: int, creation: int, flags: int
 56) -> wintypes.HANDLE:
 57    knl32_CreateFileW = windll.kernel32.CreateFileW
 58    knl32_CreateFileW.argtypes = [
 59            wintypes.LPCWSTR, # lpFileName
 60            wintypes.DWORD,   # dwDesiredAccess
 61            wintypes.DWORD,   # dwShareMode
 62            wintypes.LPVOID,  # lpSecurityAttributes
 63            wintypes.DWORD,   # dwCreationDisposition
 64            wintypes.DWORD,   # dwFlagsAndAttributes
 65            wintypes.HANDLE,  # hTemplateFile
 66        ]
 67    knl32_CreateFileW.restype = wintypes.HANDLE
 68
 69    return wintypes.HANDLE(
 70        knl32_CreateFileW(filename, access, mode, 0, creation, flags, 0)
 71    )
 72
 73
 74def DeviceIoControl(
 75    handle: wintypes.HANDLE,
 76    ioctl: int,
 77    inbuf: ctypes.pointer,
 78    insize: int,
 79    outbuf: ctypes.pointer,
 80    outsize: int,
 81) -> bool:
 82    knl32_DeviceIoControl = windll.kernel32.DeviceIoControl
 83    knl32_DeviceIoControl.argtypes = [
 84        wintypes.HANDLE,  # hDevice
 85        wintypes.DWORD,   # dwIoControlCode
 86        wintypes.LPVOID,  # lpInBuffer
 87        wintypes.DWORD,   # nInBufferSize
 88        wintypes.LPVOID,  # lpOutBuffer
 89        wintypes.DWORD,   # nOutBufferSize
 90        wintypes.LPDWORD, # lpBytesReturned
 91        wintypes.LPVOID,  # lpOverlapped
 92    ]
 93    knl32_DeviceIoControl.restype = wintypes.BOOL
 94
 95    status = knl32_DeviceIoControl(
 96        handle, ioctl, inbuf, insize, outbuf, outsize, None, None
 97    )
 98
 99    return bool(status)
100
101
102class WinFrameworkEc(CrosEcClass):
103    """
104    Class to interact with the EC using the Framework EC Windows Driver.
105    """
106
107    def __init__(self, handle: wintypes.HANDLE | None = None):
108        """
109        Initialise communication with the Framework EC driver.
110        :param handle: Use a custom device handle, opens one by default.
111        """
112        if handle is None:
113            handle = CreateFileW(
114                r"\\.\GLOBALROOT\Device\CrosEC",
115                FILE_GENERIC_READ | FILE_GENERIC_WRITE,
116                FILE_SHARE_READ | FILE_SHARE_WRITE,
117                OPEN_EXISTING,
118                0,
119            )
120
121        if handle.value == wintypes.HANDLE(INVALID_HANDLE_VALUE).value:
122            errno = windll.kernel32.GetLastError()
123            raise OSError(f"{errno}: {ctypes.FormatError(errno)}")
124
125        self.handle: wintypes.HANDLE = handle
126        r"""The handle for \\.\GLOBALROOT\Device\CrosEC."""
127
128    def __del__(self):
129        self.ec_exit()
130
131    @staticmethod
132    def detect() -> bool:
133        r"""
134        Checks for `\\.\GLOBALROOT\Device\CrosEC` and returns True if it exists.
135        """
136        return os.path.exists(r"\\.\GLOBALROOT\Device\CrosEC")
137
138    def ec_init(self) -> None:
139        pass
140
141    def ec_exit(self) -> None:
142        """
143        Close the file on exit.
144        """
145        if hasattr(self, "handle"):
146            windll.kernel32.CloseHandle(self.handle)
147
148    def command(
149        self,
150        version: Int32,
151        command: Int32,
152        outsize: Int32,
153        insize: Int32,
154        data: bytes = None,
155        warn: bool = True,
156    ) -> bytes:
157        """
158        Send a command to the EC and return the response.
159        :param version: Command version number (often 0).
160        :param command: Command to send (EC_CMD_...).
161        :param outsize: Outgoing length in bytes.
162        :param insize: Max number of bytes to accept from the EC.
163        :param data: Outgoing data to EC.
164        :param warn: Whether to warn if the response size is not as expected. Default is True.
165        :return: Incoming data from EC.
166        """
167        if data is None:
168            data = bytes(outsize)
169
170        class CrosEcCommand(ctypes.Structure):
171            _fields_ = [
172                ("version", ctypes.c_uint32),
173                ("command", ctypes.c_uint32),
174                ("outsize", ctypes.c_uint32),
175                ("insize", ctypes.c_uint32),
176                ("result", ctypes.c_uint32),
177                ("buffer", ctypes.c_ubyte * (CROSEC_CMD_MAX_REQUEST - (4 * 5))),
178            ]
179
180        cmd = CrosEcCommand()
181        cmd.version = version
182        cmd.command = command
183        cmd.outsize = outsize
184        cmd.insize = insize
185        cmd.result = 0xFF
186        ctypes.memmove(ctypes.addressof(cmd.buffer), data, len(data))
187        p_cmd = ctypes.pointer(cmd)
188
189        status = DeviceIoControl(
190            self.handle,
191            IOCTL_CROSEC_XCMD,
192            p_cmd,
193            ctypes.sizeof(CrosEcCommand),
194            p_cmd,
195            ctypes.sizeof(CrosEcCommand),
196        )
197        if not status:
198            errno = windll.kernel32.GetLastError()
199            raise IOError(
200                f"ioctl failed with error {errno}: {ctypes.FormatError(errno)}"
201            )
202
203        if cmd.insize != insize:
204            warnings.warn(
205                f"Expected {insize} bytes, got {cmd.insize} back from EC",
206                RuntimeWarning,
207            )
208
209        if cmd.result != 0:
210            raise ECError(cmd.result)
211
212        return bytes(cmd.buffer[:insize])
213
214    def memmap(self, offset: Int32, num_bytes: Int32) -> bytes:
215        """
216        Read memory from the EC.
217        :param offset: Offset to read from.
218        :param num_bytes: Number of bytes to read.
219        :return: Bytes read from the EC.
220        """
221
222        class CrosEcReadMem(ctypes.Structure):
223            _fields_ = [
224                ("offset", ctypes.c_uint32),
225                ("bytes", ctypes.c_uint32),
226                ("buffer", ctypes.c_ubyte * EC_MEMMAP_SIZE),
227            ]
228
229        data = CrosEcReadMem()
230        data.offset = offset
231        data.bytes = num_bytes
232        p_data = ctypes.pointer(data)
233
234        status = DeviceIoControl(
235            self.handle,
236            IOCTL_CROSEC_RDMEM,
237            p_data,
238            ctypes.sizeof(CrosEcReadMem),
239            p_data,
240            ctypes.sizeof(CrosEcReadMem),
241        )
242
243        if not status:
244            errno = windll.kernel32.GetLastError()
245            raise IOError(
246                f"ioctl failed with error {errno}: {ctypes.FormatError(errno)}"
247            )
248
249        if data.bytes != num_bytes:
250            warnings.warn(
251                f"Expected {num_bytes} bytes, got {data.bytes} back from EC",
252                RuntimeWarning,
253            )
254
255        return bytes(data.buffer[:num_bytes])
class WinFrameworkEc(cros_ec_python.baseclass.CrosEcClass):
103class WinFrameworkEc(CrosEcClass):
104    """
105    Class to interact with the EC using the Framework EC Windows Driver.
106    """
107
108    def __init__(self, handle: wintypes.HANDLE | None = None):
109        """
110        Initialise communication with the Framework EC driver.
111        :param handle: Use a custom device handle, opens one by default.
112        """
113        if handle is None:
114            handle = CreateFileW(
115                r"\\.\GLOBALROOT\Device\CrosEC",
116                FILE_GENERIC_READ | FILE_GENERIC_WRITE,
117                FILE_SHARE_READ | FILE_SHARE_WRITE,
118                OPEN_EXISTING,
119                0,
120            )
121
122        if handle.value == wintypes.HANDLE(INVALID_HANDLE_VALUE).value:
123            errno = windll.kernel32.GetLastError()
124            raise OSError(f"{errno}: {ctypes.FormatError(errno)}")
125
126        self.handle: wintypes.HANDLE = handle
127        r"""The handle for \\.\GLOBALROOT\Device\CrosEC."""
128
129    def __del__(self):
130        self.ec_exit()
131
132    @staticmethod
133    def detect() -> bool:
134        r"""
135        Checks for `\\.\GLOBALROOT\Device\CrosEC` and returns True if it exists.
136        """
137        return os.path.exists(r"\\.\GLOBALROOT\Device\CrosEC")
138
139    def ec_init(self) -> None:
140        pass
141
142    def ec_exit(self) -> None:
143        """
144        Close the file on exit.
145        """
146        if hasattr(self, "handle"):
147            windll.kernel32.CloseHandle(self.handle)
148
149    def command(
150        self,
151        version: Int32,
152        command: Int32,
153        outsize: Int32,
154        insize: Int32,
155        data: bytes = None,
156        warn: bool = True,
157    ) -> bytes:
158        """
159        Send a command to the EC and return the response.
160        :param version: Command version number (often 0).
161        :param command: Command to send (EC_CMD_...).
162        :param outsize: Outgoing length in bytes.
163        :param insize: Max number of bytes to accept from the EC.
164        :param data: Outgoing data to EC.
165        :param warn: Whether to warn if the response size is not as expected. Default is True.
166        :return: Incoming data from EC.
167        """
168        if data is None:
169            data = bytes(outsize)
170
171        class CrosEcCommand(ctypes.Structure):
172            _fields_ = [
173                ("version", ctypes.c_uint32),
174                ("command", ctypes.c_uint32),
175                ("outsize", ctypes.c_uint32),
176                ("insize", ctypes.c_uint32),
177                ("result", ctypes.c_uint32),
178                ("buffer", ctypes.c_ubyte * (CROSEC_CMD_MAX_REQUEST - (4 * 5))),
179            ]
180
181        cmd = CrosEcCommand()
182        cmd.version = version
183        cmd.command = command
184        cmd.outsize = outsize
185        cmd.insize = insize
186        cmd.result = 0xFF
187        ctypes.memmove(ctypes.addressof(cmd.buffer), data, len(data))
188        p_cmd = ctypes.pointer(cmd)
189
190        status = DeviceIoControl(
191            self.handle,
192            IOCTL_CROSEC_XCMD,
193            p_cmd,
194            ctypes.sizeof(CrosEcCommand),
195            p_cmd,
196            ctypes.sizeof(CrosEcCommand),
197        )
198        if not status:
199            errno = windll.kernel32.GetLastError()
200            raise IOError(
201                f"ioctl failed with error {errno}: {ctypes.FormatError(errno)}"
202            )
203
204        if cmd.insize != insize:
205            warnings.warn(
206                f"Expected {insize} bytes, got {cmd.insize} back from EC",
207                RuntimeWarning,
208            )
209
210        if cmd.result != 0:
211            raise ECError(cmd.result)
212
213        return bytes(cmd.buffer[:insize])
214
215    def memmap(self, offset: Int32, num_bytes: Int32) -> bytes:
216        """
217        Read memory from the EC.
218        :param offset: Offset to read from.
219        :param num_bytes: Number of bytes to read.
220        :return: Bytes read from the EC.
221        """
222
223        class CrosEcReadMem(ctypes.Structure):
224            _fields_ = [
225                ("offset", ctypes.c_uint32),
226                ("bytes", ctypes.c_uint32),
227                ("buffer", ctypes.c_ubyte * EC_MEMMAP_SIZE),
228            ]
229
230        data = CrosEcReadMem()
231        data.offset = offset
232        data.bytes = num_bytes
233        p_data = ctypes.pointer(data)
234
235        status = DeviceIoControl(
236            self.handle,
237            IOCTL_CROSEC_RDMEM,
238            p_data,
239            ctypes.sizeof(CrosEcReadMem),
240            p_data,
241            ctypes.sizeof(CrosEcReadMem),
242        )
243
244        if not status:
245            errno = windll.kernel32.GetLastError()
246            raise IOError(
247                f"ioctl failed with error {errno}: {ctypes.FormatError(errno)}"
248            )
249
250        if data.bytes != num_bytes:
251            warnings.warn(
252                f"Expected {num_bytes} bytes, got {data.bytes} back from EC",
253                RuntimeWarning,
254            )
255
256        return bytes(data.buffer[:num_bytes])

Class to interact with the EC using the Framework EC Windows Driver.

WinFrameworkEc(handle: ctypes.c_void_p | None = None)
108    def __init__(self, handle: wintypes.HANDLE | None = None):
109        """
110        Initialise communication with the Framework EC driver.
111        :param handle: Use a custom device handle, opens one by default.
112        """
113        if handle is None:
114            handle = CreateFileW(
115                r"\\.\GLOBALROOT\Device\CrosEC",
116                FILE_GENERIC_READ | FILE_GENERIC_WRITE,
117                FILE_SHARE_READ | FILE_SHARE_WRITE,
118                OPEN_EXISTING,
119                0,
120            )
121
122        if handle.value == wintypes.HANDLE(INVALID_HANDLE_VALUE).value:
123            errno = windll.kernel32.GetLastError()
124            raise OSError(f"{errno}: {ctypes.FormatError(errno)}")
125
126        self.handle: wintypes.HANDLE = handle
127        r"""The handle for \\.\GLOBALROOT\Device\CrosEC."""

Initialise communication with the Framework EC driver.

Parameters
  • handle: Use a custom device handle, opens one by default.
handle: ctypes.c_void_p

The handle for \.\GLOBALROOT\Device\CrosEC.

@staticmethod
def detect() -> bool:
132    @staticmethod
133    def detect() -> bool:
134        r"""
135        Checks for `\\.\GLOBALROOT\Device\CrosEC` and returns True if it exists.
136        """
137        return os.path.exists(r"\\.\GLOBALROOT\Device\CrosEC")

Checks for \\.\GLOBALROOT\Device\CrosEC and returns True if it exists.

def ec_init(self) -> None:
139    def ec_init(self) -> None:
140        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:
142    def ec_exit(self) -> None:
143        """
144        Close the file on exit.
145        """
146        if hasattr(self, "handle"):
147            windll.kernel32.CloseHandle(self.handle)

Close the file on exit.

def command( self, version: int, command: int, outsize: int, insize: int, data: bytes = None, warn: bool = True) -> bytes:
149    def command(
150        self,
151        version: Int32,
152        command: Int32,
153        outsize: Int32,
154        insize: Int32,
155        data: bytes = None,
156        warn: bool = True,
157    ) -> bytes:
158        """
159        Send a command to the EC and return the response.
160        :param version: Command version number (often 0).
161        :param command: Command to send (EC_CMD_...).
162        :param outsize: Outgoing length in bytes.
163        :param insize: Max number of bytes to accept from the EC.
164        :param data: Outgoing data to EC.
165        :param warn: Whether to warn if the response size is not as expected. Default is True.
166        :return: Incoming data from EC.
167        """
168        if data is None:
169            data = bytes(outsize)
170
171        class CrosEcCommand(ctypes.Structure):
172            _fields_ = [
173                ("version", ctypes.c_uint32),
174                ("command", ctypes.c_uint32),
175                ("outsize", ctypes.c_uint32),
176                ("insize", ctypes.c_uint32),
177                ("result", ctypes.c_uint32),
178                ("buffer", ctypes.c_ubyte * (CROSEC_CMD_MAX_REQUEST - (4 * 5))),
179            ]
180
181        cmd = CrosEcCommand()
182        cmd.version = version
183        cmd.command = command
184        cmd.outsize = outsize
185        cmd.insize = insize
186        cmd.result = 0xFF
187        ctypes.memmove(ctypes.addressof(cmd.buffer), data, len(data))
188        p_cmd = ctypes.pointer(cmd)
189
190        status = DeviceIoControl(
191            self.handle,
192            IOCTL_CROSEC_XCMD,
193            p_cmd,
194            ctypes.sizeof(CrosEcCommand),
195            p_cmd,
196            ctypes.sizeof(CrosEcCommand),
197        )
198        if not status:
199            errno = windll.kernel32.GetLastError()
200            raise IOError(
201                f"ioctl failed with error {errno}: {ctypes.FormatError(errno)}"
202            )
203
204        if cmd.insize != insize:
205            warnings.warn(
206                f"Expected {insize} bytes, got {cmd.insize} back from EC",
207                RuntimeWarning,
208            )
209
210        if cmd.result != 0:
211            raise ECError(cmd.result)
212
213        return bytes(cmd.buffer[: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:
215    def memmap(self, offset: Int32, num_bytes: Int32) -> bytes:
216        """
217        Read memory from the EC.
218        :param offset: Offset to read from.
219        :param num_bytes: Number of bytes to read.
220        :return: Bytes read from the EC.
221        """
222
223        class CrosEcReadMem(ctypes.Structure):
224            _fields_ = [
225                ("offset", ctypes.c_uint32),
226                ("bytes", ctypes.c_uint32),
227                ("buffer", ctypes.c_ubyte * EC_MEMMAP_SIZE),
228            ]
229
230        data = CrosEcReadMem()
231        data.offset = offset
232        data.bytes = num_bytes
233        p_data = ctypes.pointer(data)
234
235        status = DeviceIoControl(
236            self.handle,
237            IOCTL_CROSEC_RDMEM,
238            p_data,
239            ctypes.sizeof(CrosEcReadMem),
240            p_data,
241            ctypes.sizeof(CrosEcReadMem),
242        )
243
244        if not status:
245            errno = windll.kernel32.GetLastError()
246            raise IOError(
247                f"ioctl failed with error {errno}: {ctypes.FormatError(errno)}"
248            )
249
250        if data.bytes != num_bytes:
251            warnings.warn(
252                f"Expected {num_bytes} bytes, got {data.bytes} back from EC",
253                RuntimeWarning,
254            )
255
256        return bytes(data.buffer[: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.