358 lines
10 KiB
Python
Executable File
358 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
|
|
|
|
# pylint: disable=duplicate-code
|
|
|
|
# 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()
|