Skip to content

Commit 596dd75

Browse files
committed
Implement v2 LPC command protocol + minor cleanup
1 parent 43464be commit 596dd75

File tree

3 files changed

+114
-12
lines changed

3 files changed

+114
-12
lines changed

cros_ec_python/constants/LPC.py

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,4 +76,25 @@
7676
# EC is busy. This covers both the EC processing a command, and the host has
7777
# written a new command but the EC hasn't picked it up yet.
7878

79-
EC_LPC_STATUS_BUSY_MASK :Final = EC_LPC_STATUS_FROM_HOST | EC_LPC_STATUS_PROCESSING
79+
EC_LPC_STATUS_BUSY_MASK :Final = EC_LPC_STATUS_FROM_HOST | EC_LPC_STATUS_PROCESSING
80+
81+
82+
# Flags for ec_lpc_host_args.flags
83+
#
84+
# Args are from host. Data area at EC_LPC_ADDR_HOST_PARAM contains command
85+
# params.
86+
#
87+
# If EC gets a command and this flag is not set, this is an old-style command.
88+
# Command version is 0 and params from host are at EC_LPC_ADDR_OLD_PARAM with
89+
# unknown length. EC must respond with an old-style response (that is,
90+
# without setting EC_HOST_ARGS_FLAG_TO_HOST).
91+
92+
EC_HOST_ARGS_FLAG_FROM_HOST: Final = 0x01
93+
94+
# Args are from EC. Data area at EC_LPC_ADDR_HOST_PARAM contains response.
95+
#
96+
# If EC responds to a command and this flag is not set, this is an old-style
97+
# response. Command version is 0 and response data from EC is at
98+
# EC_LPC_ADDR_OLD_PARAM with unknown length.
99+
100+
EC_HOST_ARGS_FLAG_TO_HOST: Final = 0x02

cros_ec_python/devices/dev.py

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import errno
12
from fcntl import ioctl
23
import struct
34
from typing import Final
@@ -41,9 +42,9 @@ def _IORW(type: int, nr: int, size: int):
4142
class CrosEcDev(CrosEcClass):
4243
def __init__(self, fd=open("/dev/cros_ec", "wb", buffering=0), memmap_ioctl: bool = True):
4344
"""
44-
45-
@param fd:
46-
@param memmap_ioctl:
45+
Initialise the EC using the Linux cros_ec device.
46+
@param fd: Use a custom file description, opens /dev/cros_ec by default.
47+
@param memmap_ioctl: Use ioctl for memmap (default), if False the READ_MEMMAP command will be used instead.
4748
"""
4849
self.fd = fd
4950
self.memmap_ioctl = memmap_ioctl
@@ -62,6 +63,9 @@ def ec_init(self) -> None:
6263
pass
6364

6465
def ec_exit(self) -> None:
66+
"""
67+
Close the file on exit.
68+
"""
6569
if self.fd:
6670
self.fd.close()
6771

@@ -125,8 +129,8 @@ def memmap(self, offset: Int32, num_bytes: Int32) -> bytes:
125129

126130
return buf[len(data): len(data) + num_bytes]
127131
except OSError as e:
128-
if e.errno == 25:
129-
print(e)
132+
if e.errno == errno.ENOTTY:
133+
warnings.warn("ioctl failed, falling back to READ_MEMMAP command", RuntimeWarning)
130134
self.memmap_ioctl = False
131135
return self.memmap(offset, num_bytes)
132136
else:

cros_ec_python/devices/lpc.py

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

10-
1110
try:
1211
import portio
1312
except ImportError as e:
@@ -17,6 +16,11 @@
1716

1817
class CrosEcLpc(CrosEcClass):
1918
def __init__(self, init: bool = True, address: Int32 = None):
19+
"""
20+
Detect and initialise the EC.
21+
@param init: Whether to initialise the EC on creation. Default is True.
22+
@param address: Specify a custom memmap address, will be detected if not specified.
23+
"""
2024
self.address = address
2125
if init:
2226
self.ec_init()
@@ -33,6 +37,11 @@ def detect() -> bool:
3337

3438
@staticmethod
3539
def find_address(*addresses) -> int | None:
40+
"""
41+
Find the EC memory map address.
42+
@param addresses: A list of addresses to check.
43+
@return: The address of the EC memory map, or None if not found.
44+
"""
3645
for a in addresses:
3746
if res := portio.ioperm(a, EC_MEMMAP_SIZE, True):
3847
if res == errno.EPERM:
@@ -48,7 +57,6 @@ def find_address(*addresses) -> int | None:
4857
portio.ioperm(a, EC_MEMMAP_SIZE, False)
4958
continue
5059

51-
5260
def ec_init(self) -> None:
5361
"""
5462
Initialise the EC. Checks for the EC, and configures the library to speak the same version.
@@ -70,7 +78,6 @@ def ec_init(self) -> None:
7078
else:
7179
raise OSError(f"ioperm returned {errno.errorcode[res]} ({res})")
7280

73-
7481
status = 0xFF
7582

7683
# Read status bits, at least one should be 0
@@ -98,8 +105,75 @@ def wait_for_ec(status_addr: Int32 = EC_LPC_ADDR_HOST_CMD) -> None:
98105
while portio.inb(status_addr) & EC_LPC_STATUS_BUSY_MASK:
99106
pass
100107

101-
def ec_command_v2(self):
102-
raise NotImplementedError
108+
def ec_command_v2(self, version: UInt8, command: UInt32, outsize: UInt16, insize: UInt32, data: bytes = None,
109+
warn: bool = True):
110+
"""
111+
Send a command to the EC and return the response. Uses the v2 command protocol over LPC. UNTESTED!
112+
@param version: Command version number (often 0).
113+
@param command: Command to send (EC_CMD_...).
114+
@param outsize: Outgoing length in bytes.
115+
@param insize: Max number of bytes to accept from the EC.
116+
@param data: Outgoing data to EC.
117+
@param warn: Whether to warn if the response size is not as expected. Default is True.
118+
@return: Response from the EC.
119+
"""
120+
warnings.warn("Support for v2 commands haven't been tested! Open an issue on github if it does "
121+
"or doesn't work: https://github.com/Steve-Tech/CrOS_EC_Python/issues", RuntimeWarning)
122+
csum = 0
123+
args = bytearray(struct.pack("BBBB", EC_HOST_ARGS_FLAG_FROM_HOST, version, outsize, csum))
124+
# (flags: UInt8, command_version: UInt8, data_size: UInt8, checksum: UInt8)
125+
126+
# Copy data and start checksum
127+
for i in range(outsize):
128+
portio.outb(data[i], EC_LPC_ADDR_HOST_PARAM + i)
129+
csum += data[i]
130+
131+
# Finish checksum
132+
for i in range(len(args)):
133+
csum += args[i]
134+
135+
args[3] = csum & 0xff
136+
137+
# Copy header
138+
for i in range(len(args)):
139+
portio.outb(args[i], EC_LPC_ADDR_HOST_ARGS + i)
140+
141+
# Start the command
142+
portio.outb(command, EC_LPC_ADDR_HOST_CMD)
143+
144+
self.wait_for_ec()
145+
146+
# Check result
147+
i = portio.inb(EC_LPC_ADDR_HOST_DATA)
148+
if i:
149+
raise ECError(i)
150+
151+
# Read back args
152+
csum = 0
153+
data_out = bytearray(len(args))
154+
for i in range(len(data_out)):
155+
data_out[i] = portio.inb(EC_LPC_ADDR_HOST_ARGS + i)
156+
csum += data_out[i]
157+
158+
response = struct.unpack("BBBB", data_out)
159+
# (flags: UInt8, command_version: UInt8, data_size: UInt8, checksum: UInt8)
160+
161+
if response[0] != EC_HOST_ARGS_FLAG_TO_HOST:
162+
raise IOError("Invalid response!")
163+
164+
if response[2] != insize and warn:
165+
warnings.warn(f"Expected {insize} bytes, got {response[2]} back from EC", RuntimeWarning)
166+
167+
# Read back data
168+
data = bytearray()
169+
for i in range(response[2]):
170+
data.append(portio.inb(EC_LPC_ADDR_HOST_PARAM + i))
171+
csum += data[i]
172+
173+
if response[3] != (csum & 0xff):
174+
raise IOError("Checksum error!")
175+
176+
return bytes(data)
103177

104178
def ec_command_v3(self, version: UInt8, command: UInt32, outsize: UInt16, insize: UInt32, data: bytes = None,
105179
warn: bool = True) -> bytes:
@@ -115,6 +189,8 @@ def ec_command_v3(self, version: UInt8, command: UInt32, outsize: UInt16, insize
115189
"""
116190
csum = 0
117191
request = bytearray(struct.pack("BBHBxH", EC_HOST_REQUEST_VERSION, csum, command, version, outsize))
192+
# (struct_version: UInt8, checksum: UInt8, command: UInt16,
193+
# command_version: UInt8, reserved: UInt8, data_len: UInt16)
118194

119195
# Fail if output size is too big
120196
if outsize + len(request) > EC_LPC_HOST_PACKET_SIZE:
@@ -153,6 +229,7 @@ def ec_command_v3(self, version: UInt8, command: UInt32, outsize: UInt16, insize
153229
csum += data_out[i]
154230

155231
response = struct.unpack("BBHHH", data_out)
232+
# (struct_version: UInt8, checksum: UInt8, result: UInt16, data_len: UInt16, reserved: UInt16)
156233

157234
if response[0] != EC_HOST_RESPONSE_VERSION:
158235
raise IOError("Invalid response version!")
@@ -179,7 +256,7 @@ def command(self, *args):
179256
"""
180257
Stub function, will get overwritten in ec_get_cmd_version.
181258
"""
182-
raise NotImplementedError
259+
raise NotImplementedError("EC doesn't support commands!")
183260

184261
def ec_get_cmd_version(self) -> int:
185262
version = portio.inb(self.address + EC_MEMMAP_HOST_CMD_FLAGS)

0 commit comments

Comments
 (0)