Skip to content

Commit afe6d75

Browse files
committed
Add Windows support
1 parent ea5db6e commit afe6d75

File tree

9 files changed

+183
-13
lines changed

9 files changed

+183
-13
lines changed

README.md

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ As well as a higher-level abstracted interface for easy access to some of the mo
88

99
## Installation
1010

11+
### Linux
12+
1113
For a basic installation with full Linux Device support, run:
1214

1315
```bash
@@ -21,7 +23,7 @@ Some interfaces require additional dependencies, for example the LPC interface p
2123
pip install cros-ec-python[lpc]
2224
```
2325

24-
### Permissions
26+
#### Permissions
2527
Since we're playing around with actual hardware, we're going to need some pretty high permissions.
2628

2729
The recommended way is to copy [`60-cros_ec_python.rules`](60-cros_ec_python.rules) to `/etc/udev/rules.d/` and run:
@@ -33,23 +35,37 @@ sudo udevadm trigger
3335

3436
This will give the current user access to both `/dev/cros_ec` for the Linux Device interface, and the IO ports for the LPC interface.
3537

36-
#### Linux Device Interface
38+
##### Linux Device Interface
3739
This library requires write permission to `/dev/cros_ec` when using the Linux Device interface,
3840
which is usually only accessible by root. You can either run your script as root, add a udev rule,
3941
or just manually change the permissions. Read permission is not needed, only write.
4042

41-
#### LPC Bus Interface
43+
##### LPC Bus Interface
4244
This library requires access to IO ports using the `CAP_SYS_RAWIO` capability.
4345
It's easiest just to run your script as root.
4446

47+
### Windows
48+
49+
The Windows version requires WinRing0 to access IO ports. You can find signed versions online, that do not require disabling driver signature enforcement.
50+
You will need to copy `WinRing0x64.dll` and `WinRing0x64.sys` to either the same
51+
directory as `python.exe`, or to `C:\Windows\System32`.
52+
53+
Then you can install the package with:
54+
55+
```bash
56+
pip install cros-ec-python
57+
```
58+
59+
WinRing0 will likely require administrator permissions to access IO ports, so you may need to run your script as an administrator.
60+
4561
## Documentation
4662

4763
The documentation for this project can be found [here](https://steve-tech.github.io/CrOS_EC_Python).
4864

4965
There are also some examples in the [`examples`](https://github.com/Steve-Tech/CrOS_EC_Python/tree/main/examples) directory,
5066
and every function has usage in the [`tests`](https://github.com/Steve-Tech/CrOS_EC_Python/tree/main/tests) directory.
5167

52-
### Runnings Tests
68+
### Running Tests
5369

5470
This package uses the built-in `unittest` module for testing. To run the tests, simply run:
5571

@@ -58,6 +74,8 @@ cd tests
5874
python -m unittest
5975
```
6076

77+
***Note: This will test against your EC, nothing is mocked.***
78+
6179
### Generating Documentation
6280

6381
The documentation is generated using `pdoc`. To generate the documentation, run:

cros_ec_python/__init__.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,8 @@
44

55
from .cros_ec import get_cros_ec, DeviceTypes
66
from .baseclass import CrosEcClass
7-
from .devices.dev import CrosEcDev
8-
from .devices.lpc import CrosEcLpc
97
from .commands import memmap, general, features, pwm, leds, thermal, framework_laptop
108
from .exceptions import ECError
9+
from .devices.lpc import CrosEcLpc
10+
if __import__("os").name == "posix":
11+
from .devices.dev import CrosEcDev

cros_ec_python/baseclass.py

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
"""
88

99
import abc
10+
1011
from .constants.COMMON import *
1112

1213

@@ -38,8 +39,15 @@ def ec_exit(self) -> None:
3839
pass
3940

4041
@abc.abstractmethod
41-
def command(self, version: Int32, command: Int32, outsize: Int32, insize: Int32, data: bytes = None,
42-
warn: bool = True) -> bytes:
42+
def command(
43+
self,
44+
version: Int32,
45+
command: Int32,
46+
outsize: Int32,
47+
insize: Int32,
48+
data: bytes = None,
49+
warn: bool = True,
50+
) -> bytes:
4351
"""
4452
Send a command to the EC and return the response.
4553
:param version: Command version number (often 0).

cros_ec_python/commands/framework_laptop.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -130,12 +130,14 @@ class FpLedBrightnessLevel(Enum):
130130
FP_LED_BRIGHTNESS_LEVEL_LOW = 2
131131

132132

133-
def set_fp_led_level(ec: CrosEcClass, level: FpLedBrightnessLevel) -> None:
133+
def set_fp_led_level(ec: CrosEcClass, level: FpLedBrightnessLevel | int) -> None:
134134
"""
135135
Set the fingerprint LED level.
136136
:param ec: The CrOS_EC object.
137137
:param level: The level to set the fingerprint LED to.
138138
"""
139+
if isinstance(level, FpLedBrightnessLevel):
140+
level = level.value
139141
data = struct.pack("<Bx", level)
140142
ec.command(0, EC_CMD_FP_LED_LEVEL_CONTROL, 2, 0, data)
141143

cros_ec_python/cros_ec.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,13 @@
55
`cros_ec_python.devices.lpc.CrosEcLpc` and `cros_ec_python.devices.dev.CrosEcDev`.
66
"""
77

8-
from .baseclass import CrosEcClass
9-
from .devices import dev, lpc
8+
import os
9+
1010
from .constants.COMMON import *
11+
from .baseclass import CrosEcClass
12+
from .devices import lpc
13+
if os.name == "posix":
14+
from .devices import dev
1115

1216

1317
class DeviceTypes(Enum):

cros_ec_python/ioports/__init__.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,9 @@
44

55
import os
66

7-
if os.name == "posix":
7+
if os.name == "nt":
8+
from .winportio import WinPortIO as PortIO
9+
elif os.name == "posix":
810
try:
911
from .x86portio import IoPortIo as PortIO
1012
except ImportError:

cros_ec_python/ioports/devportio.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,11 @@
1010

1111

1212
class DevPortIO(PortIOClass):
13+
"""
14+
A class to interact with the `/dev/port` device file.
15+
"""
16+
17+
dev_port = None
1318

1419
def __init__(self):
1520
"""
@@ -21,7 +26,8 @@ def __del__(self):
2126
"""
2227
Close the `/dev/port` device file.
2328
"""
24-
self.dev_port.close()
29+
if self.dev_port:
30+
self.dev_port.close()
2531

2632
def out_bytes(self, data: bytes, port: int) -> None:
2733
"""
Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
"""
2+
This file provides a way to interact with the WinRing0 library for port I/O on Windows.
3+
4+
For this to work, you will need to copy `WinRing0x64.dll` and `WinRing0x64.sys` to either
5+
the same directory as `python.exe`, or to `C:\Windows\System32`.
6+
"""
7+
8+
from enum import Enum
9+
import ctypes
10+
11+
from .baseportio import PortIOClass
12+
13+
14+
class WinPortIO(PortIOClass):
15+
"""
16+
A class to interact with the WinRing0 library for port I/O on Windows.
17+
"""
18+
19+
winring0 = None
20+
21+
def __init__(self):
22+
"""
23+
Load and initialize the WinRing0 library.
24+
"""
25+
self.winring0 = ctypes.WinDLL("WinRing0x64.dll")
26+
self.winring0.InitializeOls.restype = ctypes.c_bool
27+
self.winring0.GetDllStatus.restype = ctypes.c_ulong
28+
self.winring0.DeinitializeOls.restype = None
29+
# ReadIoPort (port)
30+
self.winring0.ReadIoPortByte.restype = ctypes.c_ubyte
31+
self.winring0.ReadIoPortByte.argtypes = [ctypes.c_ushort]
32+
self.winring0.ReadIoPortWord.restype = ctypes.c_ushort
33+
self.winring0.ReadIoPortWord.argtypes = [ctypes.c_ushort]
34+
self.winring0.ReadIoPortDword.restype = ctypes.c_ulong
35+
self.winring0.ReadIoPortDword.argtypes = [ctypes.c_ushort]
36+
# WriteIoPort (port, data)
37+
self.winring0.WriteIoPortByte.argtypes = [ctypes.c_ushort, ctypes.c_ubyte]
38+
self.winring0.WriteIoPortWord.argtypes = [ctypes.c_ushort, ctypes.c_ushort]
39+
self.winring0.WriteIoPortDword.argtypes = [ctypes.c_ushort, ctypes.c_ulong]
40+
41+
self.winring0.InitializeOls()
42+
if error := self.winring0.GetDllStatus():
43+
raise OSError(f"WinRing0 Error: {self.Status(error)} ({error})")
44+
45+
def __del__(self):
46+
"""
47+
Deinitialize the WinRing0 library.
48+
"""
49+
if self.winring0:
50+
self.winring0.DeinitializeOls()
51+
52+
class Status(Enum):
53+
OLS_DLL_NO_ERROR = 0
54+
OLS_DLL_UNSUPPORTED_PLATFORM = 1
55+
OLS_DLL_DRIVER_NOT_LOADED = 2
56+
OLS_DLL_DRIVER_NOT_FOUND = 3
57+
OLS_DLL_DRIVER_UNLOADED = 4
58+
OLS_DLL_DRIVER_NOT_LOADED_ON_NETWORK = 5
59+
OLS_DLL_UNKNOWN_ERROR = 9
60+
61+
OLS_DLL_DRIVER_INVALID_PARAM = 10
62+
OLS_DLL_DRIVER_SC_MANAGER_NOT_OPENED = 11
63+
OLS_DLL_DRIVER_SC_DRIVER_NOT_INSTALLED = 12
64+
OLS_DLL_DRIVER_SC_DRIVER_NOT_STARTED = 13
65+
OLS_DLL_DRIVER_SC_DRIVER_NOT_REMOVED = 14
66+
67+
def outb(self, data: int, port: int) -> None:
68+
"""
69+
Write a byte (8 bit) to the specified port.
70+
:param data: Byte to write.
71+
:param port: Port to write to.
72+
"""
73+
self.winring0.WriteIoPortByte(port, data)
74+
75+
def outw(self, data: int, port: int) -> None:
76+
"""
77+
Write a word (16 bit) to the specified port.
78+
:param data: Word to write.
79+
:param port: Port to write to.
80+
"""
81+
self.winring0.WriteIoPortWord(port, data)
82+
83+
def outl(self, data: int, port: int) -> None:
84+
"""
85+
Write a long (32 bit) to the specified port.
86+
:param data: Long to write.
87+
:param port: Port to write to.
88+
"""
89+
self.winring0.WriteIoPortDword(port, data)
90+
91+
def inb(self, port: int) -> int:
92+
"""
93+
Read a byte (8 bit) from the specified port.
94+
:param port: Port to read from.
95+
:return: Byte read.
96+
"""
97+
return self.winring0.ReadIoPortByte(port)
98+
99+
def inw(self, port: int) -> int:
100+
"""
101+
Read a word (16 bit) from the specified port.
102+
:param port: Port to read from.
103+
:return: Word read.
104+
"""
105+
return self.winring0.ReadIoPortWord(port)
106+
107+
def inl(self, port: int) -> int:
108+
"""
109+
Read a long (32 bit) from the specified port.
110+
:param port: Port to read from.
111+
:return: Long read.
112+
"""
113+
return self.winring0.ReadIoPortDword(port)
114+
115+
def ioperm(self, port: int, num: int, turn_on: bool) -> None:
116+
"""
117+
`ioperm` stub function. It's not required for WinRing0.
118+
"""
119+
pass
120+
121+
def iopl(self, level: int) -> None:
122+
"""
123+
`iopl` stub function. It's not required for WinRing0.
124+
"""
125+
pass

cros_ec_python/ioports/x86portio.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,10 @@
1111

1212

1313
class IoPortIo(PortIOClass):
14+
"""
15+
A class to interact with the portio module for x86 port I/O.
16+
"""
17+
1418
portio = portio
1519

1620
def outb(self, data: int, port: int) -> None:

0 commit comments

Comments
 (0)