Skip to content

Commit 6e87eb7

Browse files
amithashfacebook-github-bot
authored andcommitted
psu-update: Add support to upgrade ORv3 Delta PSU
Summary: This adds firmware upgrade script to support ORv3 Delta PSU. Test Plan: Check current firmware version ``` root@bmc-oob:~# rackmoncli data --dev-addr 224 --reg-addr 48 Device Address: 0xe0 Device Type: orv3_psu CRC Errors: 0 timeouts: 0 Misc Errors: 0 Baudrate: 19200 Mode: active PSU FW Revision<0x0030> : "31322041" ``` Perform firmware upgrade: ``` root@bmc-oob:~# psu-update-delta-orv3.py --addr 224 ./V3_DFLASH_REV_01_00_01_04.dflash statusfile None Send get seed Got seed: 0e8dbde8 Send key Send key successful. Erasing flash... Erase successful Sending <128 byte segment @ 0x00000000> [0.08%] Sending chunk 1 of 1211... Sending <52224 byte segment @ 0x00000400> [33.77%] Sending chunk 409 of 1211... Sending <256 byte segment @ 0x0000d300> [33.94%] Sending chunk 411 of 1211... Sending <102144 byte segment @ 0x000c0000> [99.83%] Sending chunk 1209 of 1211... Sending <256 byte segment @ 0x000e6f00> [100.00%] Sending chunk 1211 of 1211... Verifying program... Verify of flash successful! Activating Image... Activate successful! ``` Check firmware version is updated: ``` root@bmc-oob:~# rackmoncli data --dev-addr 224 --reg-addr 48 Device Address: 0xe0 Device Type: orv3_psu CRC Errors: 0 timeouts: 0 Misc Errors: 0 Baudrate: 19200 Mode: active PSU FW Revision<0x0030> : "31252035" ``` Reviewed By: GoldenBug fbshipit-source-id: 2defa23faffec89c4bceceb4f8ccb5dd32b5b92b
1 parent 541886e commit 6e87eb7

File tree

2 files changed

+380
-0
lines changed

2 files changed

+380
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,379 @@
1+
#!/usr/bin/env python3
2+
3+
import argparse
4+
import json
5+
import os
6+
import os.path
7+
import struct
8+
import sys
9+
import time
10+
import traceback
11+
from binascii import hexlify
12+
from contextlib import ExitStack
13+
14+
import hexfile
15+
import pyrmd
16+
17+
18+
transcript_file = None
19+
20+
21+
def auto_int(x):
22+
return int(x, 0)
23+
24+
25+
def bh(bs):
26+
"""bytes to hex *str*"""
27+
return hexlify(bs).decode("ascii")
28+
29+
30+
parser = argparse.ArgumentParser()
31+
parser.add_argument("--addr", type=auto_int, required=True, help="PSU Modbus Address")
32+
parser.add_argument(
33+
"--statusfile", default=None, help="Write status to JSON file during process"
34+
)
35+
parser.add_argument(
36+
"--rmfwfile", action="store_true", help="Delete FW file after update completes"
37+
)
38+
parser.add_argument(
39+
"--transcript",
40+
action="store_true",
41+
help="Write modbus commands and replies to modbus-transcript.log",
42+
)
43+
parser.add_argument("file", help="firmware file")
44+
45+
status = {"pid": os.getpid(), "state": "started"}
46+
last_key_exchange_time = 0.0
47+
48+
statuspath = None
49+
50+
51+
class StatusRegister:
52+
_fields_ = [
53+
# Byte 0
54+
"WAIT",
55+
"START_PROG_ACCEPTED",
56+
"START_PROG_DECLINED",
57+
"KEY_ACCEPTED",
58+
"KEY_DECLINED",
59+
"ERASE_BUSY",
60+
"ERASE_DONE",
61+
"ERASE_FAIL",
62+
# Byte 1
63+
"ADD_ACCEPTED",
64+
"ADD_DECLINED",
65+
"SEND_DATA_BUSY",
66+
"SEND_DATA_RDY",
67+
"SEND_DATA_FAIL",
68+
"VERIFY_CRC_BUSY",
69+
"CRC_VERIFIED",
70+
"CRC_WRONG",
71+
# Byte 2
72+
"FW_IMAGE_ACCEPTED",
73+
"FW_IMAGE_DECLINED",
74+
"RESERVED",
75+
"RESERVED",
76+
"DEV_UPD_BUSY",
77+
"DEV_UPD_RDY",
78+
"DEV_UPD_FAIL",
79+
"RESERVED",
80+
# Byte 3
81+
"RESERVED",
82+
"RESERVED",
83+
"RESERVED",
84+
"RESERVED",
85+
"REV_FLAG",
86+
"COMPATIBILITY_ERROR",
87+
"SEQUENCE_ERROR",
88+
"ERROR_DETECTED",
89+
]
90+
91+
def __init__(self, val):
92+
if isinstance(val, int):
93+
self.val = val
94+
elif isinstance(val, bytes) or isinstance(val, bytearray):
95+
(self.val,) = struct.unpack(">L", val)
96+
97+
def __getitem__(self, name):
98+
return (self.val & (1 << self._fields_.index(name))) != 0
99+
100+
def __str__(self):
101+
return str(
102+
[
103+
(name, (self.val & (1 << idx)) != 0)
104+
for idx, name in enumerate(self._fields_)
105+
]
106+
)
107+
108+
109+
def write_status():
110+
global status
111+
if statuspath is None:
112+
return
113+
tmppath = statuspath + "~"
114+
with open(tmppath, "w") as tfh:
115+
tfh.write(json.dumps(status))
116+
os.rename(tmppath, statuspath)
117+
118+
119+
def status_state(state):
120+
global status
121+
status["state"] = state
122+
write_status()
123+
124+
125+
class BadMEIResponse(pyrmd.ModbusException):
126+
...
127+
128+
129+
def get_status_reg(addr):
130+
req = addr + b"\x2B\x64\x22\x00\x00"
131+
resp = pyrmd.modbuscmd_sync(req, expected=12)
132+
exp_resp = addr + b"\x2B\x71\x62\x00\x00"
133+
if len(resp) != 10 or resp[:6] != exp_resp:
134+
print("Bad status response: " + bh(resp))
135+
raise BadMEIResponse()
136+
return StatusRegister(resp[6:])
137+
138+
139+
def wait_status(addr, bit_set=None, bit_cleared=None, delay=1.0, timeout=100.0):
140+
timeout_ms = int(timeout * 1000)
141+
delay_ms = int(delay * 1000)
142+
start = time.monotonic()
143+
for _ in range(0, timeout_ms, delay_ms):
144+
fstatus = get_status_reg(addr)
145+
if bit_set is not None and fstatus[bit_set]:
146+
return fstatus
147+
if bit_cleared is not None and not fstatus[bit_cleared]:
148+
return fstatus
149+
time.sleep(delay)
150+
dur = time.monotonic() - start
151+
print(
152+
"Waiting for set:",
153+
bit_set,
154+
" cleared:",
155+
bit_cleared,
156+
" timeout after(sec):",
157+
timeout,
158+
"waited (sec):",
159+
dur,
160+
)
161+
raise Exception(fstatus)
162+
163+
164+
def get_challenge(addr):
165+
print("Send get seed")
166+
req = addr + b"\x2B\x64\x27\x00\x00"
167+
resp = pyrmd.modbuscmd_sync(req, expected=12)
168+
exp_resp = addr + b"\x2B\x71\x67\x00\x00"
169+
if len(resp) != 10 or resp[:6] != exp_resp:
170+
print("Bad challenge response: " + bh(resp))
171+
raise BadMEIResponse()
172+
challenge = resp[6:]
173+
print("Got seed: " + bh(challenge))
174+
return challenge
175+
176+
177+
def send_key(addr, key):
178+
print("Send key")
179+
req = addr + b"\x2B\x64\x27\x00\x01" + key
180+
resp = pyrmd.modbuscmd_sync(req, expected=12)
181+
exp_resp = addr + b"\x2b\x71\x67\x00\x01\xff\xff\xff\xff"
182+
if resp != exp_resp:
183+
print("Bad key response: " + bh(resp))
184+
raise BadMEIResponse()
185+
print("Send key successful.")
186+
187+
188+
def delta_seccalckey(challenge):
189+
(seed,) = struct.unpack(">L", challenge)
190+
for _ in range(32):
191+
if seed & 1 != 0:
192+
seed = seed ^ 0xC758A5B6
193+
seed = (seed >> 1) & 0x7FFFFFFF
194+
seed = seed ^ 0x06854137
195+
return struct.pack(">L", seed)
196+
197+
198+
def key_handshake(addr):
199+
global last_key_exchange_time
200+
challenge = get_challenge(addr)
201+
send_key(addr, delta_seccalckey(challenge))
202+
last_key_exchange_time = time.monotonic()
203+
204+
205+
def check_key_validity(addr):
206+
global last_key_exchange_time
207+
# Spec expects key handshake to be valid for
208+
# 8min. Take a conservative estimate of 7min.
209+
timeout_sec = 7 * 60
210+
if time.monotonic() > (last_key_exchange_time + timeout_sec):
211+
print("Key is being refreshed....")
212+
key_handshake(addr)
213+
return True
214+
return False
215+
216+
217+
def erase_flash(addr):
218+
print("Erasing flash... ")
219+
sys.stdout.flush()
220+
req = addr + b"\x2B\x64\x31\x00\x00\xFF\xFF\xFF\xFF"
221+
resp = pyrmd.modbuscmd_sync(req, expected=12)
222+
exp_resp = addr + b"\x2B\x71\x71\xFF\xFF\xFF\xFF\xFF\xFF"
223+
if resp != exp_resp:
224+
print("Bad erase response: " + bh(resp))
225+
raise BadMEIResponse()
226+
time.sleep(1.5)
227+
fstatus = get_status_reg(addr)
228+
if fstatus["ERASE_DONE"]:
229+
print("Erase successful")
230+
else:
231+
print("Erase failed")
232+
raise Exception(str(fstatus))
233+
234+
235+
def set_write_address(psu_addr, flash_addr):
236+
# print("Set write address to " + hex(flash_addr))
237+
req = psu_addr + b"\x2B\x64\x34\x00\x00" + struct.pack(">L", flash_addr)
238+
exp_resp = psu_addr + b"\x2B\x71\x74\xFF\xFF\xFF\xFF\xFF\xFF"
239+
resp = pyrmd.modbuscmd_sync(req, expected=12)
240+
if resp != exp_resp:
241+
print("Bad set write addr response: " + bh(resp))
242+
raise BadMEIResponse()
243+
wait_status(psu_addr, bit_set="ADD_ACCEPTED")
244+
245+
246+
def write_data(addr, data):
247+
assert len(data) == 128
248+
req = addr + b"\x2B\x65\x36" + data
249+
exp_resp = addr + b"\x2B\x73\x76\xFF\xFF\xFF\xFF\xFF\xFF"
250+
resp = pyrmd.modbuscmd_sync(req, expected=12)
251+
if resp != exp_resp:
252+
print("Bad write data response: " + bh(resp))
253+
raise BadMEIResponse()
254+
time.sleep(0.05)
255+
# Wait till SEND_DATA_RDY is set.
256+
fstatus = wait_status(addr, bit_cleared="SEND_DATA_BUSY", timeout=5, delay=0.1)
257+
if fstatus["SEND_DATA_BUSY"]:
258+
print("Write data busy after 5s")
259+
raise Exception(str(fstatus))
260+
# If send data rdy is set, return immediately, else wait for it
261+
if fstatus["SEND_DATA_RDY"]:
262+
return
263+
fstatus = wait_status(addr, bit_set="SEND_DATA_RDY", timeout=5, delay=0.1)
264+
if not fstatus["SEND_DATA_RDY"]:
265+
print("Write data failed")
266+
raise Exception(str(fstatus))
267+
268+
269+
def verify_flash(addr):
270+
print("Verifying program...")
271+
req = addr + b"\x2B\x64\x31\x00\x01"
272+
exp_resp = addr + b"\x2B\x71\x71\xFF\xFF\xFF\xFF\xFF\xFF"
273+
resp = pyrmd.modbuscmd_sync(req, expected=12)
274+
if resp != exp_resp:
275+
print("Bad write data response: " + bh(resp))
276+
raise BadMEIResponse()
277+
time.sleep(0.1)
278+
# Wait till VERIFY_CRC_BUSY is cleared.
279+
fstatus = wait_status(addr, bit_cleared="VERIFY_CRC_BUSY")
280+
if not fstatus["CRC_VERIFIED"]:
281+
raise Exception(str(fstatus))
282+
print("Verify of flash successful!")
283+
284+
285+
def activate(addr):
286+
print("Activating Image...")
287+
req = addr + b"\x2B\x64\x2E\x00\x00"
288+
exp_resp = addr + b"\x2B\x71\x6E\xFF\xFF\xFF\xFF\xFF\xFF"
289+
resp = pyrmd.modbuscmd_sync(req, expected=12)
290+
if resp != exp_resp:
291+
print("Bad activate response: " + bh(resp))
292+
raise BadMEIResponse()
293+
print("Activate successful!")
294+
295+
296+
def send_image(addr, fwimg):
297+
global statuspath
298+
chunk_size = 128
299+
total_chunks = sum([len(s) for s in fwimg.segments]) / chunk_size
300+
sent_chunks = 0
301+
for s in fwimg.segments:
302+
segment_name = str(s)
303+
segment_size = len(s)
304+
if segment_size == 0:
305+
print("Ignoring empty segment:", segment_name)
306+
continue
307+
print("Sending " + segment_name)
308+
set_write_address(addr, s.start_address)
309+
for i in range(0, len(s), chunk_size):
310+
chunk = s.data[i : i + chunk_size]
311+
if len(chunk) < chunk_size:
312+
chunk = chunk + (b"\xFF" * (chunk_size - len(chunk)))
313+
sent_chunks += 1
314+
# dont fill the restapi log with junk
315+
if statuspath is None:
316+
print(
317+
"\r[%.2f%%] Sending chunk %d of %d..."
318+
% (sent_chunks * 100.0 / total_chunks, sent_chunks, total_chunks),
319+
end="",
320+
)
321+
sys.stdout.flush()
322+
if check_key_validity(addr):
323+
set_write_address(addr, s.start_address + i)
324+
write_data(addr, bytearray(chunk))
325+
status["flash_progress_percent"] = sent_chunks * 100.0 / total_chunks
326+
write_status()
327+
print("")
328+
329+
330+
def update_psu(addr, filename):
331+
addr_b = addr.to_bytes(1, "big")
332+
status_state("pausing_monitoring")
333+
pyrmd.pause_monitoring_sync()
334+
status_state("parsing_fw_file")
335+
fwimg = hexfile.load(filename)
336+
key_handshake(addr_b)
337+
status_state("erase_flash")
338+
erase_flash(addr_b)
339+
status_state("flashing")
340+
send_image(addr_b, fwimg)
341+
status_state("verifying")
342+
verify_flash(addr_b)
343+
status_state("activating")
344+
activate(addr_b)
345+
status_state("done")
346+
347+
348+
def main():
349+
args = parser.parse_args()
350+
with ExitStack() as stack:
351+
global statuspath
352+
global transcript_file
353+
statuspath = args.statusfile
354+
if args.transcript:
355+
transcript_file = stack.enter_context(open("modbus-transcript.log", "w"))
356+
print("statusfile %s" % statuspath)
357+
try:
358+
update_psu(args.addr, args.file)
359+
except Exception as e:
360+
fstatus = get_status_reg(args.addr.to_bytes(1, "big"))
361+
print("Firmware update failed %s" % str(e))
362+
print("Status register dump:")
363+
print(fstatus)
364+
traceback.print_exc()
365+
global status
366+
status["exception"] = traceback.format_exc()
367+
status_state("failed")
368+
pyrmd.resume_monitoring_sync()
369+
if args.rmfwfile:
370+
os.remove(args.file)
371+
sys.exit(1)
372+
pyrmd.resume_monitoring_sync()
373+
if args.rmfwfile:
374+
os.remove(args.file)
375+
sys.exit(0)
376+
377+
378+
if __name__ == "__main__":
379+
main()

common/recipes-core/psu-update/psu-update_0.1.bb

+1
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ RDEPENDS:${PN} = "python3-core bash rackmon"
2525

2626
LOCAL_URI = " \
2727
file://psu-update-delta.py \
28+
file://psu-update-delta-orv3.py \
2829
file://psu-update-bel.py \
2930
file://psu-update-artesyn.py \
3031
file://orv3-device-update-mailbox.py \

0 commit comments

Comments
 (0)