You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
357 lines
10 KiB
357 lines
10 KiB
#!/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()
|
|
|