|  | #!/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() |