Skip to content

Commit

Permalink
psu-update: Add support to upgrade ORv3 Delta PSU
Browse files Browse the repository at this point in the history
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
  • Loading branch information
amithash authored and facebook-github-bot committed Aug 23, 2022
1 parent 541886e commit 6e87eb7
Show file tree
Hide file tree
Showing 2 changed files with 380 additions and 0 deletions.
379 changes: 379 additions & 0 deletions common/recipes-core/psu-update/files/psu-update-delta-orv3.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,379 @@
#!/usr/bin/env python3

import argparse
import json
import os
import os.path
import struct
import sys
import time
import traceback
from binascii import hexlify
from contextlib import ExitStack

import hexfile
import pyrmd


transcript_file = None


def auto_int(x):
return int(x, 0)


def bh(bs):
"""bytes to hex *str*"""
return hexlify(bs).decode("ascii")


parser = argparse.ArgumentParser()
parser.add_argument("--addr", type=auto_int, required=True, help="PSU Modbus Address")
parser.add_argument(
"--statusfile", default=None, help="Write status to JSON file during process"
)
parser.add_argument(
"--rmfwfile", action="store_true", help="Delete FW file after update completes"
)
parser.add_argument(
"--transcript",
action="store_true",
help="Write modbus commands and replies to modbus-transcript.log",
)
parser.add_argument("file", help="firmware file")

status = {"pid": os.getpid(), "state": "started"}
last_key_exchange_time = 0.0

statuspath = None


class StatusRegister:
_fields_ = [
# Byte 0
"WAIT",
"START_PROG_ACCEPTED",
"START_PROG_DECLINED",
"KEY_ACCEPTED",
"KEY_DECLINED",
"ERASE_BUSY",
"ERASE_DONE",
"ERASE_FAIL",
# Byte 1
"ADD_ACCEPTED",
"ADD_DECLINED",
"SEND_DATA_BUSY",
"SEND_DATA_RDY",
"SEND_DATA_FAIL",
"VERIFY_CRC_BUSY",
"CRC_VERIFIED",
"CRC_WRONG",
# Byte 2
"FW_IMAGE_ACCEPTED",
"FW_IMAGE_DECLINED",
"RESERVED",
"RESERVED",
"DEV_UPD_BUSY",
"DEV_UPD_RDY",
"DEV_UPD_FAIL",
"RESERVED",
# Byte 3
"RESERVED",
"RESERVED",
"RESERVED",
"RESERVED",
"REV_FLAG",
"COMPATIBILITY_ERROR",
"SEQUENCE_ERROR",
"ERROR_DETECTED",
]

def __init__(self, val):
if isinstance(val, int):
self.val = val
elif isinstance(val, bytes) or isinstance(val, bytearray):
(self.val,) = struct.unpack(">L", val)

def __getitem__(self, name):
return (self.val & (1 << self._fields_.index(name))) != 0

def __str__(self):
return str(
[
(name, (self.val & (1 << idx)) != 0)
for idx, name in enumerate(self._fields_)
]
)


def write_status():
global status
if statuspath is None:
return
tmppath = statuspath + "~"
with open(tmppath, "w") as tfh:
tfh.write(json.dumps(status))
os.rename(tmppath, statuspath)


def status_state(state):
global status
status["state"] = state
write_status()


class BadMEIResponse(pyrmd.ModbusException):
...


def get_status_reg(addr):
req = addr + b"\x2B\x64\x22\x00\x00"
resp = pyrmd.modbuscmd_sync(req, expected=12)
exp_resp = addr + b"\x2B\x71\x62\x00\x00"
if len(resp) != 10 or resp[:6] != exp_resp:
print("Bad status response: " + bh(resp))
raise BadMEIResponse()
return StatusRegister(resp[6:])


def wait_status(addr, bit_set=None, bit_cleared=None, delay=1.0, timeout=100.0):
timeout_ms = int(timeout * 1000)
delay_ms = int(delay * 1000)
start = time.monotonic()
for _ in range(0, timeout_ms, delay_ms):
fstatus = get_status_reg(addr)
if bit_set is not None and fstatus[bit_set]:
return fstatus
if bit_cleared is not None and not fstatus[bit_cleared]:
return fstatus
time.sleep(delay)
dur = time.monotonic() - start
print(
"Waiting for set:",
bit_set,
" cleared:",
bit_cleared,
" timeout after(sec):",
timeout,
"waited (sec):",
dur,
)
raise Exception(fstatus)


def get_challenge(addr):
print("Send get seed")
req = addr + b"\x2B\x64\x27\x00\x00"
resp = pyrmd.modbuscmd_sync(req, expected=12)
exp_resp = addr + b"\x2B\x71\x67\x00\x00"
if len(resp) != 10 or resp[:6] != exp_resp:
print("Bad challenge response: " + bh(resp))
raise BadMEIResponse()
challenge = resp[6:]
print("Got seed: " + bh(challenge))
return challenge


def send_key(addr, key):
print("Send key")
req = addr + b"\x2B\x64\x27\x00\x01" + key
resp = pyrmd.modbuscmd_sync(req, expected=12)
exp_resp = addr + b"\x2b\x71\x67\x00\x01\xff\xff\xff\xff"
if resp != exp_resp:
print("Bad key response: " + bh(resp))
raise BadMEIResponse()
print("Send key successful.")


def delta_seccalckey(challenge):
(seed,) = struct.unpack(">L", challenge)
for _ in range(32):
if seed & 1 != 0:
seed = seed ^ 0xC758A5B6
seed = (seed >> 1) & 0x7FFFFFFF
seed = seed ^ 0x06854137
return struct.pack(">L", seed)


def key_handshake(addr):
global last_key_exchange_time
challenge = get_challenge(addr)
send_key(addr, delta_seccalckey(challenge))
last_key_exchange_time = time.monotonic()


def check_key_validity(addr):
global last_key_exchange_time
# Spec expects key handshake to be valid for
# 8min. Take a conservative estimate of 7min.
timeout_sec = 7 * 60
if time.monotonic() > (last_key_exchange_time + timeout_sec):
print("Key is being refreshed....")
key_handshake(addr)
return True
return False


def erase_flash(addr):
print("Erasing flash... ")
sys.stdout.flush()
req = addr + b"\x2B\x64\x31\x00\x00\xFF\xFF\xFF\xFF"
resp = pyrmd.modbuscmd_sync(req, expected=12)
exp_resp = addr + b"\x2B\x71\x71\xFF\xFF\xFF\xFF\xFF\xFF"
if resp != exp_resp:
print("Bad erase response: " + bh(resp))
raise BadMEIResponse()
time.sleep(1.5)
fstatus = get_status_reg(addr)
if fstatus["ERASE_DONE"]:
print("Erase successful")
else:
print("Erase failed")
raise Exception(str(fstatus))


def set_write_address(psu_addr, flash_addr):
# print("Set write address to " + hex(flash_addr))
req = psu_addr + b"\x2B\x64\x34\x00\x00" + struct.pack(">L", flash_addr)
exp_resp = psu_addr + b"\x2B\x71\x74\xFF\xFF\xFF\xFF\xFF\xFF"
resp = pyrmd.modbuscmd_sync(req, expected=12)
if resp != exp_resp:
print("Bad set write addr response: " + bh(resp))
raise BadMEIResponse()
wait_status(psu_addr, bit_set="ADD_ACCEPTED")


def write_data(addr, data):
assert len(data) == 128
req = addr + b"\x2B\x65\x36" + data
exp_resp = addr + b"\x2B\x73\x76\xFF\xFF\xFF\xFF\xFF\xFF"
resp = pyrmd.modbuscmd_sync(req, expected=12)
if resp != exp_resp:
print("Bad write data response: " + bh(resp))
raise BadMEIResponse()
time.sleep(0.05)
# Wait till SEND_DATA_RDY is set.
fstatus = wait_status(addr, bit_cleared="SEND_DATA_BUSY", timeout=5, delay=0.1)
if fstatus["SEND_DATA_BUSY"]:
print("Write data busy after 5s")
raise Exception(str(fstatus))
# If send data rdy is set, return immediately, else wait for it
if fstatus["SEND_DATA_RDY"]:
return
fstatus = wait_status(addr, bit_set="SEND_DATA_RDY", timeout=5, delay=0.1)
if not fstatus["SEND_DATA_RDY"]:
print("Write data failed")
raise Exception(str(fstatus))


def verify_flash(addr):
print("Verifying program...")
req = addr + b"\x2B\x64\x31\x00\x01"
exp_resp = addr + b"\x2B\x71\x71\xFF\xFF\xFF\xFF\xFF\xFF"
resp = pyrmd.modbuscmd_sync(req, expected=12)
if resp != exp_resp:
print("Bad write data response: " + bh(resp))
raise BadMEIResponse()
time.sleep(0.1)
# Wait till VERIFY_CRC_BUSY is cleared.
fstatus = wait_status(addr, bit_cleared="VERIFY_CRC_BUSY")
if not fstatus["CRC_VERIFIED"]:
raise Exception(str(fstatus))
print("Verify of flash successful!")


def activate(addr):
print("Activating Image...")
req = addr + b"\x2B\x64\x2E\x00\x00"
exp_resp = addr + b"\x2B\x71\x6E\xFF\xFF\xFF\xFF\xFF\xFF"
resp = pyrmd.modbuscmd_sync(req, expected=12)
if resp != exp_resp:
print("Bad activate response: " + bh(resp))
raise BadMEIResponse()
print("Activate successful!")


def send_image(addr, fwimg):
global statuspath
chunk_size = 128
total_chunks = sum([len(s) for s in fwimg.segments]) / chunk_size
sent_chunks = 0
for s in fwimg.segments:
segment_name = str(s)
segment_size = len(s)
if segment_size == 0:
print("Ignoring empty segment:", segment_name)
continue
print("Sending " + segment_name)
set_write_address(addr, s.start_address)
for i in range(0, len(s), chunk_size):
chunk = s.data[i : i + chunk_size]
if len(chunk) < chunk_size:
chunk = chunk + (b"\xFF" * (chunk_size - len(chunk)))
sent_chunks += 1
# dont fill the restapi log with junk
if statuspath is None:
print(
"\r[%.2f%%] Sending chunk %d of %d..."
% (sent_chunks * 100.0 / total_chunks, sent_chunks, total_chunks),
end="",
)
sys.stdout.flush()
if check_key_validity(addr):
set_write_address(addr, s.start_address + i)
write_data(addr, bytearray(chunk))
status["flash_progress_percent"] = sent_chunks * 100.0 / total_chunks
write_status()
print("")


def update_psu(addr, filename):
addr_b = addr.to_bytes(1, "big")
status_state("pausing_monitoring")
pyrmd.pause_monitoring_sync()
status_state("parsing_fw_file")
fwimg = hexfile.load(filename)
key_handshake(addr_b)
status_state("erase_flash")
erase_flash(addr_b)
status_state("flashing")
send_image(addr_b, fwimg)
status_state("verifying")
verify_flash(addr_b)
status_state("activating")
activate(addr_b)
status_state("done")


def main():
args = parser.parse_args()
with ExitStack() as stack:
global statuspath
global transcript_file
statuspath = args.statusfile
if args.transcript:
transcript_file = stack.enter_context(open("modbus-transcript.log", "w"))
print("statusfile %s" % statuspath)
try:
update_psu(args.addr, args.file)
except Exception as e:
fstatus = get_status_reg(args.addr.to_bytes(1, "big"))
print("Firmware update failed %s" % str(e))
print("Status register dump:")
print(fstatus)
traceback.print_exc()
global status
status["exception"] = traceback.format_exc()
status_state("failed")
pyrmd.resume_monitoring_sync()
if args.rmfwfile:
os.remove(args.file)
sys.exit(1)
pyrmd.resume_monitoring_sync()
if args.rmfwfile:
os.remove(args.file)
sys.exit(0)


if __name__ == "__main__":
main()
1 change: 1 addition & 0 deletions common/recipes-core/psu-update/psu-update_0.1.bb
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ RDEPENDS:${PN} = "python3-core bash rackmon"

LOCAL_URI = " \
file://psu-update-delta.py \
file://psu-update-delta-orv3.py \
file://psu-update-bel.py \
file://psu-update-artesyn.py \
file://orv3-device-update-mailbox.py \
Expand Down

0 comments on commit 6e87eb7

Please sign in to comment.