Skip to content

Commit 8862d2b

Browse files
committed
Implement LPC v3 command protocol
1 parent 70e14f8 commit 8862d2b

File tree

5 files changed

+198
-23
lines changed

5 files changed

+198
-23
lines changed

cros_ec_python/constants/COMMON.py

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -25,16 +25,16 @@ class EcStatus(Enum):
2525
EC_RES_INVALID_RESPONSE = 5
2626
EC_RES_INVALID_VERSION = 6
2727
EC_RES_INVALID_CHECKSUM = 7
28-
EC_RES_IN_PROGRESS = 8, # Accepted, command in progress
29-
EC_RES_UNAVAILABLE = 9, # No response available
30-
EC_RES_TIMEOUT = 10, # We got a timeout
31-
EC_RES_OVERFLOW = 11, # Table / data overflow
32-
EC_RES_INVALID_HEADER = 12, # Header contains invalid data
33-
EC_RES_REQUEST_TRUNCATED = 13, # Didn't get the entire request
34-
EC_RES_RESPONSE_TOO_BIG = 14, # Response was too big to handle
35-
EC_RES_BUS_ERROR = 15, # Communications bus error
36-
EC_RES_BUSY = 16, # Up but too busy. Should retry
37-
EC_RES_INVALID_HEADER_VERSION = 17, # Header version invalid
38-
EC_RES_INVALID_HEADER_CRC = 18, # Header CRC invalid
39-
EC_RES_INVALID_DATA_CRC = 19, # Data CRC invalid
40-
EC_RES_DUP_UNAVAILABLE = 20, # Can't resend response
28+
EC_RES_IN_PROGRESS = 8 # Accepted, command in progress
29+
EC_RES_UNAVAILABLE = 9 # No response available
30+
EC_RES_TIMEOUT = 10 # We got a timeout
31+
EC_RES_OVERFLOW = 11 # Table / data overflow
32+
EC_RES_INVALID_HEADER = 12 # Header contains invalid data
33+
EC_RES_REQUEST_TRUNCATED = 13 # Didn't get the entire request
34+
EC_RES_RESPONSE_TOO_BIG = 14 # Response was too big to handle
35+
EC_RES_BUS_ERROR = 15 # Communications bus error
36+
EC_RES_BUSY = 16 # Up but too busy. Should retry
37+
EC_RES_INVALID_HEADER_VERSION = 17 # Header version invalid
38+
EC_RES_INVALID_HEADER_CRC = 18 # Header CRC invalid
39+
EC_RES_INVALID_DATA_CRC = 19 # Data CRC invalid
40+
EC_RES_DUP_UNAVAILABLE = 20 # Can't resend response

cros_ec_python/constants/LPC.py

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,3 +41,38 @@
4141
EC_LPC_CMDR_SMI : Final = BIT(6) # SMI event is pending
4242

4343
EC_LPC_ADDR_MEMMAP : Final = 0x900
44+
45+
46+
# Value written to legacy command port / prefix byte to indicate protocol
47+
# 3+ structs are being used. Usage is bus-dependent.
48+
49+
EC_COMMAND_PROTOCOL_3: Final = 0xda
50+
51+
EC_HOST_REQUEST_VERSION: Final = 3
52+
53+
EC_HOST_RESPONSE_VERSION: Final = 3
54+
55+
56+
# LPC command status byte masks
57+
# EC has written a byte in the data register and host hasn't read it yet
58+
EC_LPC_STATUS_TO_HOST :Final = 0x01
59+
# Host has written a command/data byte and the EC hasn't read it yet
60+
EC_LPC_STATUS_FROM_HOST :Final = 0x02
61+
# EC is processing a command
62+
EC_LPC_STATUS_PROCESSING :Final = 0x04
63+
# Last write to EC was a command, not data
64+
EC_LPC_STATUS_LAST_CMD :Final = 0x08
65+
# EC is in burst mode
66+
EC_LPC_STATUS_BURST_MODE :Final = 0x10
67+
# SCI event is pending (requesting SCI query)
68+
EC_LPC_STATUS_SCI_PENDING :Final = 0x20
69+
# SMI event is pending (requesting SMI query)
70+
EC_LPC_STATUS_SMI_PENDING :Final = 0x40
71+
# (reserved)
72+
EC_LPC_STATUS_RESERVED :Final = 0x80
73+
74+
75+
# EC is busy. This covers both the EC processing a command, and the host has
76+
# written a new command but the EC hasn't picked it up yet.
77+
78+
EC_LPC_STATUS_BUSY_MASK :Final = EC_LPC_STATUS_FROM_HOST | EC_LPC_STATUS_PROCESSING

cros_ec_python/cros_ec.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,14 +60,16 @@ def command(self, version: Int32, command: Int32, outsize: Int32, insize: Int32,
6060
@param version: Command version number (often 0).
6161
@param command: Command to send (EC_CMD_...).
6262
@param outsize: Outgoing length in bytes.
63-
@param insize: Max number of bytes to accept from the EC. None for unlimited.
63+
@param insize: Max number of bytes to accept from the EC.
6464
@param data: Outgoing data to EC.
6565
@param warn: Whether to warn if the response size is not as expected. Default is True.
6666
@return: Response from the EC.
6767
"""
6868
match self.dev_type:
6969
case DeviceTypes.LinuxDev:
7070
return dev.ec_command_fd(self.kwargs["fd"], version, command, outsize, insize, data, warn)
71+
case DeviceTypes.LPC:
72+
return lpc.ec_command(version, command, outsize, insize, data, warn)
7173
case _:
7274
raise NotImplementedError
7375

cros_ec_python/lpc.py

Lines changed: 143 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,159 @@
1+
import struct
12
import portio
3+
import warnings
24
from .constants.COMMON import *
35
from .constants.LPC import *
46
from .constants.MEMMAP import *
7+
from .exceptions import ECError
58

69

7-
def ec_init(address: Int32 = EC_LPC_ADDR_MEMMAP):
8-
if portio.ioperm(address, EC_MEMMAP_SIZE, True):
9-
print("Permission denied. Try running as root.")
10-
exit(1)
10+
def ec_init(address: Int32 = EC_LPC_ADDR_MEMMAP) -> None:
11+
"""
12+
Initialise the EC. Checks for the EC, and configures the library to speak the same version.
13+
@param address: Address of the EC memory map.
14+
"""
15+
# Request I/O permissions
16+
if portio.ioperm(address, EC_MEMMAP_SIZE, True) or \
17+
portio.ioperm(EC_LPC_ADDR_HOST_DATA, EC_MEMMAP_SIZE, True) or \
18+
portio.ioperm(EC_LPC_ADDR_HOST_CMD, EC_MEMMAP_SIZE, True) or \
19+
portio.ioperm(EC_LPC_ADDR_HOST_PACKET, EC_LPC_HOST_PACKET_SIZE, True):
20+
raise PermissionError("Permission denied. Try running as root.")
21+
22+
status = 0xFF
23+
24+
# Read status bits, at least one should be 0
25+
status &= portio.inb(EC_LPC_ADDR_HOST_CMD)
26+
status &= portio.inb(EC_LPC_ADDR_HOST_DATA)
27+
28+
if status == 0xFF:
29+
raise OSError("No EC detected.")
30+
31+
# Check for 'EC' in memory map
32+
if portio.inb(address + EC_MEMMAP_ID) != ord("E") and portio.inb(
33+
address + EC_MEMMAP_ID + 1
34+
) != ord("C"):
35+
raise OSError("Invalid EC signature.")
36+
37+
ec_get_cmd_version(address)
38+
39+
40+
def wait_for_ec(status_addr: Int32 = EC_LPC_ADDR_HOST_CMD) -> None:
41+
"""
42+
Wait for the EC to be ready after sending a command.
43+
@param status_addr: The status register to read.
44+
"""
45+
while portio.inb(status_addr) & EC_LPC_STATUS_BUSY_MASK:
46+
pass
47+
48+
49+
def ec_command_v2():
50+
raise NotImplementedError
51+
52+
53+
def ec_command_v3(version: UInt8, command: UInt32, outsize: UInt16, insize: UInt32, data: bytes = None,
54+
warn: bool = True) -> bytes:
55+
"""
56+
Send a command to the EC and return the response. Uses the v3 command protocol over LPC.
57+
@param version: Command version number (often 0).
58+
@param command: Command to send (EC_CMD_...).
59+
@param outsize: Outgoing length in bytes.
60+
@param insize: Max number of bytes to accept from the EC.
61+
@param data: Outgoing data to EC.
62+
@param warn: Whether to warn if the response size is not as expected. Default is True.
63+
@return: Response from the EC.
64+
"""
65+
csum = 0
66+
request = bytearray(struct.pack("BBHBxH", EC_HOST_REQUEST_VERSION, csum, command, version, outsize))
67+
68+
# Fail if output size is too big
69+
if outsize + len(request) > EC_LPC_HOST_PACKET_SIZE:
70+
raise ValueError("Output size too big!")
71+
72+
# Copy data and start checksum
73+
for i in range(outsize):
74+
portio.outb(data[i], EC_LPC_ADDR_HOST_PACKET + len(request) + i)
75+
csum += data[i]
76+
77+
# Finish checksum
78+
for i in range(len(request)):
79+
csum += request[i]
80+
81+
request[1] = (-csum) & 0xff
82+
83+
# Copy header
84+
for i in range(len(request)):
85+
portio.outb(request[i], EC_LPC_ADDR_HOST_PACKET + i)
86+
87+
# Start the command
88+
portio.outb(EC_COMMAND_PROTOCOL_3, EC_LPC_ADDR_HOST_CMD)
89+
90+
wait_for_ec()
91+
92+
# Check result
93+
i = portio.inb(EC_LPC_ADDR_HOST_DATA)
94+
if i:
95+
raise ECError(i)
96+
97+
# Read back response and start checksum
98+
csum = 0
99+
data_out = bytearray(1 + 1 + 2 + 2 + 2)
100+
for i in range(len(data_out)):
101+
data_out[i] = portio.inb(EC_LPC_ADDR_HOST_PACKET + i)
102+
csum += data_out[i]
103+
104+
response = struct.unpack("BBHHH", data_out)
105+
106+
if response[0] != EC_HOST_RESPONSE_VERSION:
107+
raise IOError("Invalid response version!")
108+
109+
if response[4]:
110+
# Reserved should be 0
111+
raise IOError("Invalid response!")
112+
113+
if response[3] != insize and warn:
114+
warnings.warn(f"Expected {insize} bytes, got {response[3]} back from EC", RuntimeWarning)
115+
116+
# Read back data
117+
data = bytearray()
118+
for i in range(response[3]):
119+
data.append(portio.inb(EC_LPC_ADDR_HOST_PACKET + len(data_out) + i))
120+
csum += data[i]
121+
122+
if csum & 0xff:
123+
raise IOError("Checksum error!")
124+
125+
return bytes(data)
126+
127+
128+
def ec_command(*args):
129+
"""
130+
Stub function, will get overwritten in ec_get_cmd_version.
131+
"""
132+
raise NotImplementedError
133+
134+
135+
def ec_get_cmd_version(address: Int32 = EC_LPC_ADDR_MEMMAP) -> int:
136+
global ec_command
137+
138+
version = portio.inb(address + EC_MEMMAP_HOST_CMD_FLAGS)
139+
140+
if version & EC_HOST_CMD_FLAG_VERSION_3:
141+
ec_command = ec_command_v3
142+
return 3
143+
elif version & EC_HOST_CMD_FLAG_LPC_ARGS_SUPPORTED:
144+
ec_command = ec_command_v2
145+
return 2
146+
else:
147+
warnings.warn("EC doesn't support commands!", RuntimeWarning)
148+
return 0
11149

12150

13151
def ec_readmem(offset: Int32, num_bytes: Int32, address: Int32 = EC_LPC_ADDR_MEMMAP) -> bytes:
14152
"""
15153
Read memory from the EC.
16154
@param offset: Offset to read from.
17155
@param num_bytes: Number of bytes to read.
156+
@param address: Address of the EC memory map.
18157
@return: Bytes read from the EC.
19158
"""
20159
data = bytearray()

tests/devices.py

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,9 @@ def test2_memmap(self):
1616
self.assertEqual(resp, b'EC')
1717

1818
def test3_hello(self):
19-
data = b'ECEC'
19+
data = b'\xa0\xb0\xc0\xd0'
2020
resp = ec.command(0, general.EC_CMD_HELLO, len(data), 4, data)
21-
self.assertEqual(resp, (int.from_bytes(data, "little") + 0x01020304).to_bytes(4, "little"))
21+
self.assertEqual(resp, b'\xa4\xb3\xc2\xd1')
2222

2323

2424
class TestLPC(unittest.TestCase):
@@ -31,11 +31,10 @@ def test2_memmap(self):
3131
resp = ec.memmap(MEMMAP.EC_MEMMAP_ID, 2)
3232
self.assertEqual(resp, b'EC')
3333

34-
@unittest.skip("Not implemented")
3534
def test3_hello(self):
36-
data = b'ECEC'
35+
data = b'\xa0\xb0\xc0\xd0'
3736
resp = ec.command(0, general.EC_CMD_HELLO, len(data), 4, data)
38-
self.assertEqual(resp, (int.from_bytes(data, "little") + 0x01020304).to_bytes(4, "little"))
37+
self.assertEqual(resp, b'\xa4\xb3\xc2\xd1')
3938

4039

4140
if __name__ == '__main__':

0 commit comments

Comments
 (0)