zephyr/soc/xtensa/intel_adsp/tools/remote-fw-service.py

356 lines
10 KiB
Python
Executable File

#!/usr/bin/env python3
# Copyright(c) 2022 Intel Corporation. All rights reserved.
# SPDX-License-Identifier: Apache-2.0
import os
import sys
import struct
import logging
import time
import subprocess
import argparse
import socketserver
import threading
import hashlib
import queue
from urllib.parse import urlparse
# Global variable use to sync between log and request services.
runner = None
# INADDR_ANY as default
HOST = ''
PORT_LOG = 9999
PORT_REQ = PORT_LOG + 1
BUF_SIZE = 4096
# Define the command and the max size
CMD_LOG_START = "start_log"
CMD_DOWNLOAD = "download"
MAX_CMD_SZ = 16
# Define the return value in handle function
ERR_FAIL = 1
# Define the header format and size for
# transmiting the firmware
PACKET_HEADER_FORMAT_FW = 'I 42s 32s'
HEADER_SZ = 78
logging.basicConfig(level=logging.INFO)
log = logging.getLogger("remote-fw")
class adsp_request_handler(socketserver.BaseRequestHandler):
"""
The request handler class for control the actions of server.
"""
def receive_fw(self):
log.info("Receiving...")
# Receive the header first
d = self.request.recv(HEADER_SZ)
# Unpacked the header data
# Include size(4), filename(42) and MD5(32)
header = d[:HEADER_SZ]
total = d[HEADER_SZ:]
s = struct.Struct(PACKET_HEADER_FORMAT_FW)
fsize, fname, md5_tx_b = s.unpack(header)
log.info(f'size:{fsize}, filename:{fname}, MD5:{md5_tx_b}')
# Receive the firmware. We only receive the specified amount of bytes.
while len(total) < fsize:
data = self.request.recv(min(BUF_SIZE, fsize - len(total)))
if not data:
raise EOFError("truncated firmware file")
total += data
log.info(f"Done Receiving {len(total)}.")
try:
with open(fname,'wb') as f:
f.write(total)
except Exception as e:
log.error(f"Get exception {e} during FW transfer.")
return None
# Check the MD5 of the firmware
md5_rx = hashlib.md5(total).hexdigest()
md5_tx = md5_tx_b.decode('utf-8')
if md5_tx != md5_rx:
log.error(f'MD5 mismatch: {md5_tx} vs. {md5_rx}')
return None
return fname
def do_download(self):
recv_file = self.receive_fw()
if recv_file:
recv_file = recv_file.decode('utf-8')
if os.path.exists(recv_file):
runner.set_fw_ready(recv_file)
return 0
log.error("Cannot find the FW file.")
return ERR_FAIL
def handle(self):
cmd = self.request.recv(MAX_CMD_SZ)
log.info(f"{self.client_address[0]} wrote: {cmd}")
action = cmd.decode("utf-8")
log.debug(f'load {action}')
ret = ERR_FAIL
if action == CMD_DOWNLOAD:
self.request.sendall(cmd)
ret = self.do_download()
else:
log.error("incorrect load communitcation!")
return
if not ret:
self.request.sendall("success".encode('utf-8'))
log.info("Firmware well received. Ready to download.")
else:
self.request.sendall("failed".encode('utf-8'))
log.error("Receive firmware failed.")
class adsp_log_handler(socketserver.BaseRequestHandler):
"""
The log handler class for grabbing output messages of server.
"""
def handle(self):
cmd = self.request.recv(MAX_CMD_SZ)
log.info(f"{self.client_address[0]} wrote: {cmd}")
action = cmd.decode("utf-8")
log.debug(f'monitor {action}')
if action == CMD_LOG_START:
self.request.sendall(cmd)
else:
log.error("incorrect monitor communitcation!")
log.info("wait for FW ready...")
while not runner.is_fw_ready():
if not self.is_connection_alive():
return
time.sleep(1)
log.info("FW is ready...")
# start_new_session=True in order to get a different Process Group
# ID. When the PGID is the same, sudo does NOT propagate signals out of
# fear of "accidentally killing itself" (man sudo).
# Compare:
#
# - Different PGID: signal is propagated and sleep is terminated
#
# sudo sleep 15 & kill $!
#
# - Same PGID, sleep is NOT terminated
#
# sudo bash -c 'sleep 15 & killall sudo'
#
# ps xfao pid,ppid,pgid,sid,comm | grep -C 5 -e PID -e sleep -e sudo
with subprocess.Popen(runner.get_script(), stdout=subprocess.PIPE,
start_new_session=True) as proc:
# Thread for monitoring the conntection
t = threading.Thread(target=self.check_connection, args=(proc,))
t.start()
while True:
try:
out = proc.stdout.readline()
self.request.sendall(out)
ret = proc.poll()
if ret:
log.info(f"retrun code: {ret}")
break
except (BrokenPipeError, ConnectionResetError):
log.info("Client is disconnect.")
break
t.join()
log.info("service complete.")
def finish(self):
runner.cleanup()
log.info("Wait for next service...")
def is_connection_alive(self):
try:
self.request.sendall(b'\x00')
except (BrokenPipeError, ConnectionResetError):
log.info("Client is disconnect.")
return False
return True
def check_connection(self, proc):
# Not to check connection alive for
# the first 10 secs.
time.sleep(10)
poll_interval = 1
log.info("Now checking client connection every %ds", poll_interval)
while True:
if not self.is_connection_alive():
# cavstool
child_desc = " ".join(runner.script) + f", PID={proc.pid}"
log.info("Terminating %s", child_desc)
try:
# sudo does _not_ propagate SIGKILL (man sudo)
proc.terminate()
try:
proc.wait(timeout=0.5)
except subprocess.TimeoutExpired:
log.error("SIGTERM failed on child %s", child_desc)
if os.geteuid() == 0: # sudo not needed and not used
log.error("Sending %d SIGKILL", proc.pid)
proc.kill()
else:
log.error("Try: sudo pkill -9 -f %s", runner.load_cmd)
except PermissionError:
log.info("cannot kill proc due to it start with sudo...")
os.system(f"sudo kill -9 {proc.pid} ")
return
time.sleep(poll_interval)
class device_runner():
def __init__(self, args):
self.fw_file = None
self.lock = threading.Lock()
self.fw_queue = queue.Queue()
# Board specific config
self.board = board_config(args)
self.load_cmd = self.board.get_cmd()
def set_fw_ready(self, fw_recv):
if fw_recv:
self.fw_queue.put(fw_recv)
def is_fw_ready(self):
self.fw_file = self.fw_queue.get()
log.info(f"Current FW is {self.fw_file}")
return bool(self.fw_file)
def cleanup(self):
self.lock.acquire()
self.script = None
if self.fw_file:
os.remove(self.fw_file)
self.fw_file = None
self.lock.release()
def get_script(self):
if os.geteuid() != 0:
self.script = [f'sudo', f'{self.load_cmd}']
else:
self.script = [f'{self.load_cmd}']
self.script.append(f'{self.fw_file}')
if self.board.params:
for param in self.board.params:
self.script.append(param)
log.info(f'run script: {self.script}')
return self.script
class board_config():
def __init__(self, args):
self.load_cmd = args.load_cmd # cmd for loading
self.params = [] # params of loading cmd
if not self.load_cmd:
self.load_cmd = "./cavstool.py"
if not self.load_cmd or not os.path.exists(self.load_cmd):
log.error(f'Cannot find load cmd {self.load_cmd}.')
sys.exit(1)
def get_cmd(self):
return self.load_cmd
def get_params(self):
return self.params
ap = argparse.ArgumentParser(description="RemoteHW service tool", allow_abbrev=False)
ap.add_argument("-q", "--quiet", action="store_true",
help="No loader output, just DSP logging")
ap.add_argument("-v", "--verbose", action="store_true",
help="More loader output, DEBUG logging level")
ap.add_argument("-s", "--server-addr",
help="Specify the only IP address the log server will LISTEN on")
ap.add_argument("-p", "--log-port",
help="Specify the PORT that the log server to active")
ap.add_argument("-r", "--req-port",
help="Specify the PORT that the request server to active")
ap.add_argument("-c", "--load-cmd",
help="Specify loading command of the board")
args = ap.parse_args()
if args.quiet:
log.setLevel(logging.WARN)
elif args.verbose:
log.setLevel(logging.DEBUG)
if args.server_addr:
url = urlparse("//" + args.server_addr)
if url.hostname:
HOST = url.hostname
if url.port:
PORT_LOG = int(url.port)
if args.log_port:
PORT_LOG = int(args.log_port)
if args.req_port:
PORT_REQ = int(args.req_port)
log.info(f"Serve on LOG PORT: {PORT_LOG} REQ PORT: {PORT_REQ}")
if __name__ == "__main__":
# Do board configuration setup
runner = device_runner(args)
# Launch the command request service
socketserver.TCPServer.allow_reuse_address = True
req_server = socketserver.TCPServer((HOST, PORT_REQ), adsp_request_handler)
req_t = threading.Thread(target=req_server.serve_forever, daemon=True)
# Activate the log service which output board's execution result
log_server = socketserver.TCPServer((HOST, PORT_LOG), adsp_log_handler)
log_t = threading.Thread(target=log_server.serve_forever, daemon=True)
try:
log.info("Req server start...")
req_t.start()
log.info("Log server start...")
log_t.start()
req_t.join()
log_t.join()
except KeyboardInterrupt:
log_server.shutdown()
req_server.shutdown()