Skip to content

Commit ea5db6e

Browse files
committed
Improve LPC/devportio performance
1 parent 060930a commit ea5db6e

File tree

7 files changed

+307
-230
lines changed

7 files changed

+307
-230
lines changed

cros_ec_python/devices/lpc.py

Lines changed: 30 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -7,30 +7,24 @@
77
from ..constants.MEMMAP import *
88
from ..exceptions import ECError
99

10-
try:
11-
import portio
12-
_import_portio_error = None
13-
except ImportError as e:
14-
_import_portio_error = e
15-
warnings.warn(f"Failed to import portio: {e}, using /dev/port instead.", ImportWarning)
16-
from ..utils import devportio as portio
10+
from ..ioports import PortIO
1711

1812

1913
class CrosEcLpc(CrosEcClass):
2014
"""
2115
Class to interact with the EC using the LPC interface.
2216
"""
2317

24-
def __init__(self, init: bool = True, address: Int32 = None, portio_warn: bool = True):
18+
def __init__(self, init: bool = True, address: Int32 = None, portio: PortIO = PortIO()):
2519
"""
2620
Detect and initialise the EC.
2721
:param init: Whether to initialise the EC on creation. Default is True.
2822
:param address: Specify a custom memmap address, will be detected if not specified.
29-
:param portio_warn: Whether to warn if portio couldn't be imported. Default is True.
23+
:param portio: PortIO object to use. Default is auto-detected.
3024
"""
31-
if portio_warn and _import_portio_error is not None:
32-
warnings.warn(f"Failed to import portio: {_import_portio_error}, using /dev/port instead.",
33-
RuntimeWarning)
25+
26+
self.portio: PortIO = portio
27+
"""PortIO object to use."""
3428

3529
self.address: Int32 = address
3630
"""The address of the EC memory map."""
@@ -49,7 +43,7 @@ def detect() -> bool:
4943
return True
5044

5145
@staticmethod
52-
def find_address(*addresses) -> int | None:
46+
def find_address(*addresses, portio: PortIO = PortIO()) -> int | None:
5347
"""
5448
Find the EC memory map address.
5549
:param addresses: A list of addresses to check.
@@ -77,15 +71,15 @@ def ec_init(self) -> None:
7771
"""
7872
# Find memmap address
7973
if self.address is None:
80-
self.address = self.find_address(EC_LPC_ADDR_MEMMAP, EC_LPC_ADDR_MEMMAP_FWAMD)
74+
self.address = self.find_address(EC_LPC_ADDR_MEMMAP, EC_LPC_ADDR_MEMMAP_FWAMD, portio=self.portio)
8175
# find_address will leave ioperm enabled for the memmap
8276
if self.address is None:
8377
raise OSError("Could not find EC!")
8478

8579
# Request I/O permissions
86-
if (res := portio.ioperm(EC_LPC_ADDR_HOST_DATA, EC_MEMMAP_SIZE, True)) or \
87-
(res := portio.ioperm(EC_LPC_ADDR_HOST_CMD, EC_MEMMAP_SIZE, True)) or \
88-
(res := portio.ioperm(EC_LPC_ADDR_HOST_PACKET, EC_LPC_HOST_PACKET_SIZE, True)):
80+
if (res := self.portio.ioperm(EC_LPC_ADDR_HOST_DATA, EC_MEMMAP_SIZE, True)) or \
81+
(res := self.portio.ioperm(EC_LPC_ADDR_HOST_CMD, EC_MEMMAP_SIZE, True)) or \
82+
(res := self.portio.ioperm(EC_LPC_ADDR_HOST_PACKET, EC_LPC_HOST_PACKET_SIZE, True)):
8983
if res == errno.EPERM:
9084
raise PermissionError("Permission denied. Try running as root.")
9185
else:
@@ -94,28 +88,27 @@ def ec_init(self) -> None:
9488
status = 0xFF
9589

9690
# Read status bits, at least one should be 0
97-
status &= portio.inb(EC_LPC_ADDR_HOST_CMD)
98-
status &= portio.inb(EC_LPC_ADDR_HOST_DATA)
91+
status &= self.portio.inb(EC_LPC_ADDR_HOST_CMD)
92+
status &= self.portio.inb(EC_LPC_ADDR_HOST_DATA)
9993

10094
if status == 0xFF:
10195
raise OSError("No EC detected. Invalid status.")
10296

10397
# Check for 'EC' in memory map
104-
if portio.inw(self.address + EC_MEMMAP_ID) != int.from_bytes(b'EC', "little"):
98+
if self.portio.inw(self.address + EC_MEMMAP_ID) != int.from_bytes(b'EC', "little"):
10599
raise OSError("Invalid EC signature.")
106100

107101
self.ec_get_cmd_version()
108102

109103
def ec_exit(self) -> None:
110104
pass
111105

112-
@staticmethod
113-
def wait_for_ec(status_addr: Int32 = EC_LPC_ADDR_HOST_CMD) -> None:
106+
def wait_for_ec(self, status_addr: Int32 = EC_LPC_ADDR_HOST_CMD) -> None:
114107
"""
115108
Wait for the EC to be ready after sending a command.
116109
:param status_addr: The status register to read.
117110
"""
118-
while portio.inb(status_addr) & EC_LPC_STATUS_BUSY_MASK:
111+
while self.portio.inb(status_addr) & EC_LPC_STATUS_BUSY_MASK:
119112
pass
120113

121114
def ec_command_v2(self, version: UInt8, command: UInt32, outsize: UInt16, insize: UInt32, data: bytes = None,
@@ -138,7 +131,7 @@ def ec_command_v2(self, version: UInt8, command: UInt32, outsize: UInt16, insize
138131

139132
# Copy data and start checksum
140133
for i in range(outsize):
141-
portio.outb(data[i], EC_LPC_ADDR_HOST_PARAM + i)
134+
self.portio.outb(data[i], EC_LPC_ADDR_HOST_PARAM + i)
142135
csum += data[i]
143136

144137
# Finish checksum
@@ -149,23 +142,23 @@ def ec_command_v2(self, version: UInt8, command: UInt32, outsize: UInt16, insize
149142

150143
# Copy header
151144
for i in range(len(args)):
152-
portio.outb(args[i], EC_LPC_ADDR_HOST_ARGS + i)
145+
self.portio.outb(args[i], EC_LPC_ADDR_HOST_ARGS + i)
153146

154147
# Start the command
155-
portio.outb(command, EC_LPC_ADDR_HOST_CMD)
148+
self.portio.outb(command, EC_LPC_ADDR_HOST_CMD)
156149

157150
self.wait_for_ec()
158151

159152
# Check result
160-
i = portio.inb(EC_LPC_ADDR_HOST_DATA)
153+
i = self.portio.inb(EC_LPC_ADDR_HOST_DATA)
161154
if i:
162155
raise ECError(i)
163156

164157
# Read back args
165158
csum = 0
166159
data_out = bytearray(len(args))
167160
for i in range(len(data_out)):
168-
data_out[i] = portio.inb(EC_LPC_ADDR_HOST_ARGS + i)
161+
data_out[i] = self.portio.inb(EC_LPC_ADDR_HOST_ARGS + i)
169162
csum += data_out[i]
170163

171164
response = struct.unpack("BBBB", data_out)
@@ -180,7 +173,7 @@ def ec_command_v2(self, version: UInt8, command: UInt32, outsize: UInt16, insize
180173
# Read back data
181174
data = bytearray()
182175
for i in range(response[2]):
183-
data.append(portio.inb(EC_LPC_ADDR_HOST_PARAM + i))
176+
data.append(self.portio.inb(EC_LPC_ADDR_HOST_PARAM + i))
184177
csum += data[i]
185178

186179
if response[3] != (csum & 0xff):
@@ -211,7 +204,7 @@ def ec_command_v3(self, version: UInt8, command: UInt32, outsize: UInt16, insize
211204

212205
# Copy data and start checksum
213206
for i in range(outsize):
214-
portio.outb(data[i], EC_LPC_ADDR_HOST_PACKET + len(request) + i)
207+
self.portio.outb(data[i], EC_LPC_ADDR_HOST_PACKET + len(request) + i)
215208
csum += data[i]
216209

217210
# Finish checksum
@@ -222,23 +215,23 @@ def ec_command_v3(self, version: UInt8, command: UInt32, outsize: UInt16, insize
222215

223216
# Copy header
224217
for i in range(len(request)):
225-
portio.outb(request[i], EC_LPC_ADDR_HOST_PACKET + i)
218+
self.portio.outb(request[i], EC_LPC_ADDR_HOST_PACKET + i)
226219

227220
# Start the command
228-
portio.outb(EC_COMMAND_PROTOCOL_3, EC_LPC_ADDR_HOST_CMD)
221+
self.portio.outb(EC_COMMAND_PROTOCOL_3, EC_LPC_ADDR_HOST_CMD)
229222

230223
self.wait_for_ec()
231224

232225
# Check result
233-
i = portio.inb(EC_LPC_ADDR_HOST_DATA)
226+
i = self.portio.inb(EC_LPC_ADDR_HOST_DATA)
234227
if i:
235228
raise ECError(i)
236229

237230
# Read back response and start checksum
238231
csum = 0
239232
data_out = bytearray(1 + 1 + 2 + 2 + 2)
240233
for i in range(len(data_out)):
241-
data_out[i] = portio.inb(EC_LPC_ADDR_HOST_PACKET + i)
234+
data_out[i] = self.portio.inb(EC_LPC_ADDR_HOST_PACKET + i)
242235
csum += data_out[i]
243236

244237
response = struct.unpack("BBHHH", data_out)
@@ -257,7 +250,7 @@ def ec_command_v3(self, version: UInt8, command: UInt32, outsize: UInt16, insize
257250
# Read back data
258251
data = bytearray()
259252
for i in range(response[3]):
260-
data.append(portio.inb(EC_LPC_ADDR_HOST_PACKET + len(data_out) + i))
253+
data.append(self.portio.inb(EC_LPC_ADDR_HOST_PACKET + len(data_out) + i))
261254
csum += data[i]
262255

263256
if csum & 0xff:
@@ -276,7 +269,7 @@ def ec_get_cmd_version(self) -> int:
276269
Find the version of the EC command protocol.
277270
:return: The version of the EC command protocol.
278271
"""
279-
version = portio.inb(self.address + EC_MEMMAP_HOST_CMD_FLAGS)
272+
version = self.portio.inb(self.address + EC_MEMMAP_HOST_CMD_FLAGS)
280273

281274
if version & EC_HOST_CMD_FLAG_VERSION_3:
282275
self.command = self.ec_command_v3
@@ -298,5 +291,5 @@ def memmap(self, offset: Int32, num_bytes: Int32) -> bytes:
298291
"""
299292
data = bytearray()
300293
for i in range(num_bytes):
301-
data.append(portio.inb(self.address + offset + i))
294+
data.append(self.portio.inb(self.address + offset + i))
302295
return bytes(data)

cros_ec_python/ioports/__init__.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
"""
2+
This module is used to import the correct portio module based on the OS.
3+
"""
4+
5+
import os
6+
7+
if os.name == "posix":
8+
try:
9+
from .x86portio import IoPortIo as PortIO
10+
except ImportError:
11+
from .devportio import DevPortIO as PortIO
12+
else:
13+
raise Exception("Unsupported OS")
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
"""
2+
This file includes the base class for Port I/O backends.
3+
4+
It doesn't do anything on its own, but it is used as a base for all port I/O backends to inherit from.
5+
6+
See `cros_ec_python.io` for a few examples of a classes that inherits from this class.
7+
"""
8+
9+
import abc
10+
11+
12+
class PortIOClass(metaclass=abc.ABCMeta):
13+
"""
14+
Base class for port I/O backends to inherit from.
15+
"""
16+
17+
@abc.abstractmethod
18+
def outb(self, data: int, port: int) -> None:
19+
"""
20+
Write a byte (8 bit) to the specified port.
21+
:param data: Byte to write.
22+
:param port: Port to write to.
23+
"""
24+
pass
25+
26+
@abc.abstractmethod
27+
def outw(self, data: int, port: int) -> None:
28+
"""
29+
Write a word (16 bit) to the specified port.
30+
:param data: Word to write.
31+
:param port: Port to write to.
32+
"""
33+
pass
34+
35+
@abc.abstractmethod
36+
def outl(self, data: int, port: int) -> None:
37+
"""
38+
Write a long (32 bit) to the specified port.
39+
:param data: Long to write.
40+
:param port: Port to write to.
41+
"""
42+
pass
43+
44+
@abc.abstractmethod
45+
def inb(self, port: int) -> int:
46+
"""
47+
Read a byte (8 bit) from the specified port.
48+
:param port: Port to read from.
49+
:return: Byte read.
50+
"""
51+
pass
52+
53+
@abc.abstractmethod
54+
def inw(self, port: int) -> int:
55+
"""
56+
Read a word (16 bit) from the specified port.
57+
:param port: Port to read from.
58+
:return: Word read.
59+
"""
60+
pass
61+
62+
@abc.abstractmethod
63+
def inl(self, port: int) -> int:
64+
"""
65+
Read a long (32 bit) from the specified port.
66+
:param port: Port to read from.
67+
:return: Long read.
68+
"""
69+
pass
70+
71+
@abc.abstractmethod
72+
def ioperm(self, port: int, num: int, turn_on: bool) -> None:
73+
"""
74+
Set I/O permissions for a range of ports.
75+
:param port: Start of port range.
76+
:param num: Number of ports to set permissions for.
77+
:param turn_on: Whether to turn on or off permissions.
78+
"""
79+
pass
80+
81+
@abc.abstractmethod
82+
def iopl(self, level: int) -> None:
83+
"""
84+
Set I/O permissions level.
85+
:param level: Permissions level.
86+
"""
87+
pass

0 commit comments

Comments
 (0)