i2c polling
diff --git a/services/i2c/server/src/main.rs b/services/i2c/server/src/main.rs index 02743ac..d633140 100644 --- a/services/i2c/server/src/main.rs +++ b/services/i2c/server/src/main.rs
@@ -54,7 +54,7 @@ use userspace::syscall::{self, Signals}; use userspace::time::Instant; -use app_i2c_server::{handle, signals}; +use app_i2c_server::{handle};//, _signals}; // --------------------------------------------------------------------------- // Server loop @@ -80,18 +80,23 @@ // Register both event sources with the WaitGroup. // user_data=0 → IPC request from client (I2C channel becomes READABLE). // user_data=1 → hardware I2C2 interrupt (I2C2_IRQ fires signals::I2C2). - syscall::wait_group_add(handle::WG, handle::I2C, Signals::READABLE, 0usize)?; - syscall::wait_group_add(handle::WG, handle::I2C2_IRQ, signals::I2C2, 1usize)?; + //syscall::wait_group_add(handle::WG, handle::I2C, Signals::READABLE, 0usize)?; + //syscall::wait_group_add(handle::WG, handle::I2C2_IRQ, signals::I2C2, 1usize)?; loop { - let wait_return = syscall::object_wait(handle::WG, Signals::READABLE, Instant::MAX)?; + //let wait_return = syscall::object_wait(handle::WG, Signals::READABLE, Instant::MAX)?; + syscall::object_wait(handle::I2C, Signals::READABLE, Instant::MAX)?; + - if wait_return.user_data == 1 { - // Hardware I2C2 slave interrupt: drain data into flat buffers and - // wake the client. Re-enable the IRQ after draining. - handle_i2c_interrupt(&mut backend, ¬ification_enabled); - let _ = syscall::interrupt_ack(handle::I2C2_IRQ, signals::I2C2); - } else { +// if wait_return.user_data == 1 { +// pw_log::info!("i2c irq get"); +// // Hardware I2C2 slave interrupt: drain data into flat buffers and +// // wake the client. Re-enable the IRQ after draining. +// handle_i2c_interrupt(&mut backend, ¬ification_enabled); +// let _ = syscall::interrupt_ack(handle::I2C2_IRQ, signals::I2C2); +// } + + { // IPC request from client — channel_read returns immediately since // the channel was already READABLE when the WaitGroup fired. let len = syscall::channel_read(handle::I2C, 0, &mut request_buf)?; @@ -124,7 +129,7 @@ /// into the per-bus flat buffer for every notification-enabled bus, then raises /// `Signals::USER` on the IPC channel to wake the client registered via /// `EnableSlaveNotification`. -fn handle_i2c_interrupt(backend: &mut AspeedI2cBackend, notification_enabled: &[bool; 14]) { +fn _handle_i2c_interrupt(backend: &mut AspeedI2cBackend, notification_enabled: &[bool; 14]) { for bus in 0..14u8 { if notification_enabled[bus as usize] { let _ = backend.drain_slave_rx(bus);
diff --git a/target/ast1060-evb/harness/uart_test_exec.py b/target/ast1060-evb/harness/uart_test_exec.py index fb759e9..35abeaf 100644 --- a/target/ast1060-evb/harness/uart_test_exec.py +++ b/target/ast1060-evb/harness/uart_test_exec.py
@@ -10,7 +10,10 @@ """ import argparse +import base64 +import binascii import os +import signal import subprocess import sys import time @@ -45,6 +48,7 @@ if _PW_TOKENIZER_AVAILABLE: from pw_tokenizer import Detokenizer + from pw_tokenizer.detokenize import NestedMessageParser try: import serial @@ -67,19 +71,74 @@ self.log_file_handle = None elf = getattr(args, "elf", None) self.detokenizer = Detokenizer(elf) if (elf and _PW_TOKENIZER_AVAILABLE) else None + self._token_parser = NestedMessageParser() if _PW_TOKENIZER_AVAILABLE else None def log(self, message: str): """Print message unless in quiet mode.""" if not self.args.quiet: print(message, flush=True) - def print_uart_data(self, data: str): - """Print UART data, with detokenized output on the following line in green.""" - print(data, end="", flush=True) - if self.detokenizer: - detokenized = self.detokenizer.detokenize_text(data) - if detokenized != data: - print(f"\033[32m{detokenized}\033[0m", end="", flush=True) + def _write_log(self, text: str): + """Write text to the log file if open.""" + if self.log_file_handle: + self.log_file_handle.write(text) + self.log_file_handle.flush() + + def print_uart_data(self, raw: bytes): + """Print UART data, detokenizing base64-encoded pw_log tokens when possible. + + raw -- the undecoded bytes straight from the serial port. + + pw_tokenizer embeds $-prefixed base64 token frames in otherwise plain + text. We use NestedMessageParser.read_messages() to scan the chunk for + those frames byte-by-byte. The parser preserves state between calls so + tokens split across successive reads are handled correctly. + + Each yielded span is either a raw non-token run or a complete $<base64> + token. Tokens are base64-decoded and looked up in the detokenizer; on + a hit the decoded string is printed in green (and, unless --notok, the + raw frame is printed first). On a miss the raw frame is printed as-is. + + When no ELF / detokenizer is available we just decode and print the raw + bytes. + """ + if self.detokenizer and self._token_parser: + notok = getattr(self.args, "notok", False) + + for is_token, span in self._token_parser.read_messages(raw): + if not is_token: + text = span.decode("utf-8", errors="replace") + print(text, end="", flush=True) + self._write_log(text) + continue + + # span is b'$<base64chars>' — strip the leading '$' before decoding. + # Add standard base64 padding so b64decode accepts unpadded tokens. + raw_text = span.decode("utf-8", errors="replace") + try: + b64 = span[1:] + b64 += b"=" * (-len(b64) % 4) + encoded = base64.b64decode(b64, validate=True) + result = self.detokenizer.detokenize(encoded) + except (binascii.Error, ValueError): + result = None + + if result is not None and result.ok(): + if not notok: + print(raw_text, end="", flush=True) + self._write_log(raw_text) + decoded_str = str(result) + print(f"\033[32m{decoded_str}\033[0m", end="", flush=True) + self._write_log(decoded_str) + else: + # Token not in database or decode failed — print as plain text + print(raw_text, end="", flush=True) + self._write_log(raw_text) + return + + text = raw.decode("utf-8", errors="replace") + print(text, end="", flush=True) + self._write_log(text) def run_command(self, cmd: list, check: bool = True) -> Tuple[int, str, str]: """Run command and return (returncode, stdout, stderr).""" @@ -137,6 +196,10 @@ self.log("Entering FWSPICK mode sequence...") self.toggle_srst("dl") # SRST low time.sleep(0.1) + # Empty UART buffer after asserting SRST to ensure clean state + discarded = self.read_serial_data(timeout_seconds=0.1) + if discarded: + self.log(f"Discarded {len(discarded)} bytes from UART buffer after SRST assert") self.toggle_fwspick("dh") # FWSPICK high time.sleep(1) self.toggle_srst("dh") # SRST high @@ -150,6 +213,10 @@ time.sleep(0.1) self.toggle_srst("dl") # SRST low time.sleep(0.5) + # Empty UART buffer after asserting SRST to ensure clean state + discarded = self.read_serial_data(timeout_seconds=0.1) + if discarded: + self.log(f"Discarded {len(discarded)} bytes from UART buffer after SRST assert") self.toggle_srst("dh") # SRST high time.sleep(2) self.log("Normal boot mode sequence complete") @@ -194,33 +261,22 @@ self.log_file_handle.close() self.log_file_handle = None - def read_serial_data(self, timeout_seconds: float = 1.0) -> str: - """Read available data from serial port.""" + def read_serial_data(self, timeout_seconds: float = 1.0) -> bytes: + """Read available data from serial port, returning raw bytes.""" if not self.serial_port or self.args.skip_uart: - return "" + return b"" if self.args.dry_run: - return "" + return b"" try: self.serial_port.timeout = timeout_seconds data = self.serial_port.read(1024) - - if data: - decoded = data.decode("utf-8", errors="ignore") - - # Log to file - if self.log_file_handle: - self.log_file_handle.write(decoded) - self.log_file_handle.flush() - - return decoded - - return "" + return data if data else b"" except Exception as e: self.log(f"Serial read error: {e}") - return "" + return b"" def write_serial_data(self, data: bytes) -> bool: """Write data to serial port.""" @@ -253,19 +309,23 @@ return True start_time = time.time() - buffer = "" + buffer = b"" - while time.time() - start_time < timeout: - data = self.read_serial_data(0.1) - if data: - buffer += data - if not self.args.quiet: - self.print_uart_data(data) + try: + while time.time() - start_time < timeout: + data = self.read_serial_data(0.1) + if data: + buffer += data + if not self.args.quiet: + self.print_uart_data(data) - # Look for 'U' character - if "U" in buffer: - self.log("\nUART bootloader ready detected!") - return True + # Look for 'U' character + if b"U" in buffer: + self.log("\nUART bootloader ready detected!") + return True + except KeyboardInterrupt: + self.log("\nInterrupted while waiting for UART ready") + raise self.log("\nTimeout waiting for UART ready signal") return False @@ -296,20 +356,26 @@ chunk_size = 1024 bytes_sent = 0 - for i in range(0, len(firmware_data), chunk_size): - chunk = firmware_data[i : i + chunk_size] + try: + for i in range(0, len(firmware_data), chunk_size): + chunk = firmware_data[i : i + chunk_size] - if not self.write_serial_data(chunk): - self.log("Failed to write firmware chunk") - return False + if not self.write_serial_data(chunk): + self.log("Failed to write firmware chunk") + return False - bytes_sent += len(chunk) - time.sleep(0.01) + bytes_sent += len(chunk) + time.sleep(0.01) - # Progress indicator - if not self.args.quiet and bytes_sent % (chunk_size * 10) == 0: - progress = (bytes_sent * 100) // len(firmware_data) - print(f"\rProgress: {progress}%", end="", flush=True) + # Progress indicator + if not self.args.quiet and bytes_sent % (chunk_size * 10) == 0: + progress = (bytes_sent * 100) // len(firmware_data) + print(f"\rProgress: {progress}%", end="", flush=True) + except KeyboardInterrupt: + if not self.args.quiet: + print() + self.log("Firmware upload interrupted") + raise if not self.args.quiet: print() @@ -317,6 +383,8 @@ self.log("Firmware upload completed") return True + except KeyboardInterrupt: + raise except Exception as e: self.log(f"Failed to upload firmware: {e}") return False @@ -327,47 +395,55 @@ self.log("Skipping test monitoring") return True - actual_timeout = getattr(self.args, "test_timeout", timeout) - self.log(f"Monitoring test execution with {actual_timeout}s timeout...") + actual_timeout = getattr(self.args, "timeout", timeout) + if actual_timeout == 0: + self.log("Monitoring test execution with no timeout...") + else: + self.log(f"Monitoring test execution with {actual_timeout}s timeout...") if self.args.dry_run: self.log("DRY RUN: Would monitor test execution") return True start_time = time.time() - buffer = "" + line_buffer = "" test_results = {"passed": 0, "failed": 0, "skipped": 0} - while time.time() - start_time < actual_timeout: - data = self.read_serial_data(0.5) - if data: - buffer += data - if not self.args.quiet: - self.print_uart_data(data) + try: + while actual_timeout == 0 or time.time() - start_time < actual_timeout: + data = self.read_serial_data(0.5) + if data: + if not self.args.quiet: + self.print_uart_data(data) - lines = buffer.split("\n") - for line in lines: - if "PASS" in line: - test_results["passed"] += 1 - elif "FAIL" in line: - test_results["failed"] += 1 - elif "SKIP" in line: - test_results["skipped"] += 1 + line_buffer += data.decode("utf-8", errors="replace") + lines = line_buffer.split("\n") + # Keep the last (possibly incomplete) line in the buffer + line_buffer = lines[-1] - # Check for completion - for pattern in self.SUCCESS_PATTERNS: - if pattern in line: - self.log(f"\nTest execution completed!") - self.log(f"Results: {test_results}") - return test_results["failed"] == 0 + for line in lines[:-1]: + if "PASS" in line: + test_results["passed"] += 1 + elif "FAIL" in line: + test_results["failed"] += 1 + elif "SKIP" in line: + test_results["skipped"] += 1 - # Check for failure - for pattern in self.FAILURE_PATTERNS: - if pattern.lower() in line.lower(): - self.log(f"\nFailure detected: {pattern}") - return False + # Check for completion + for pattern in self.SUCCESS_PATTERNS: + if pattern in line: + self.log(f"\nTest execution completed!") + self.log(f"Results: {test_results}") + return test_results["failed"] == 0 - buffer = "\n".join(lines[-10:]) + # Check for failure + for pattern in self.FAILURE_PATTERNS: + if pattern.lower() in line.lower(): + self.log(f"\nFailure detected: {pattern}") + return False + except KeyboardInterrupt: + self.log(f"\nMonitoring interrupted. Results so far: {test_results}") + raise self.log(f"\nTest monitoring timeout. Results so far: {test_results}") return test_results["failed"] == 0 @@ -376,6 +452,34 @@ """Clean up resources.""" self.close_serial() + def _install_signal_handler(self): + """Install SIGINT handler so Ctrl+C always triggers clean shutdown.""" + def _handler(signum, frame): + raise KeyboardInterrupt + signal.signal(signal.SIGINT, _handler) + + def run_parse_only(self) -> int: + """Read and print UART output indefinitely. Returns 0 on KeyboardInterrupt.""" + try: + if not self.open_serial(): + return 1 + + self.log( + f"Listening on {self.args.uart_device} @ {self.args.baudrate} baud" + " (Ctrl+C to stop)..." + ) + + while True: + data = self.read_serial_data(timeout_seconds=0.1) + if data: + self.print_uart_data(data) + + except KeyboardInterrupt: + self.log("\nStopped.") + return 0 + finally: + self.cleanup() + def run_full_test_sequence(self) -> bool: """Execute the complete test sequence.""" try: @@ -427,6 +531,11 @@ ) parser.add_argument("firmware", nargs="?", help="Firmware binary file path") parser.add_argument("--elf", help="ELF file for pw_tokenizer detokenization") + parser.add_argument( + "--notok", + action="store_true", + help="Suppress raw base64 tokens; only show detokenized output. Requires --elf.", + ) # GPIO control parser.add_argument( @@ -469,6 +578,12 @@ help="Test execution monitoring timeout in seconds (default: 600)", ) parser.add_argument( + "--timeout", + type=int, + default=600, + help="Test execution timeout in seconds (default: 600, 0 for no timeout)", + ) + parser.add_argument( "--log-file", help="Log file path (auto-generated if not specified)" ) @@ -497,22 +612,55 @@ action="store_true", help="Skip GPIO operations but still monitor tests", ) + parser.add_argument( + "--parse-only", + action="store_true", + help=( + "Read and print UART output indefinitely. " + "Requires UART device. Accepts only --baudrate, --log-file, and --elf. " + "No timeout; stop with Ctrl+C." + ), + ) args = parser.parse_args() # Validate pw_tokenizer / --elf argument consistency - if os.environ.get("PW_TOK_ROOT") and not args.elf: - print( - "Error: PW_TOK_ROOT is set but --elf was not provided. " - "Detokenization requires an ELF file." - ) - sys.exit(1) if args.elf and not _PW_TOKENIZER_AVAILABLE: print( "Error: --elf was provided but pw_tokenizer could not be located. " "Set PW_TOK_ROOT to the Pigweed root or ensure Bazel has fetched it." ) sys.exit(1) + if args.elf and not Path(args.elf).exists(): + print(f"Error: ELF file not found: {args.elf}") + sys.exit(1) + if args.notok and not args.elf: + parser.error("--notok requires --elf") + + # Validate --parse-only exclusivity + if args.parse_only: + incompatible = [ + name + for flag, name in [ + (args.firmware, "firmware"), + (args.manual_srst, "--manual-srst"), + (args.manual_fwspick, "--manual-fwspick"), + (args.sequence, "--sequence"), + (args.skip_uart, "--skip-uart"), + (args.dry_run, "--dry-run"), + (args.upload_only, "--upload-only"), + (args.bazel_test, "--bazel-test"), + (args.skip_gpio, "--skip-gpio"), + (args.quiet, "--quiet"), + ] + if flag + ] + if incompatible: + parser.error( + f"--parse-only is incompatible with: {', '.join(incompatible)}" + ) + if not args.uart_device: + parser.error("--parse-only requires a UART device path") # Validate arguments if args.upload_only: @@ -532,8 +680,13 @@ parser.error(f"UART device not found: {args.uart_device}") executor = UartTestExecutor(args) + executor._install_signal_handler() try: + # Parse-only mode: read UART indefinitely + if args.parse_only: + return executor.run_parse_only() + # Handle manual GPIO operations if args.manual_srst: state = "dl" if args.manual_srst in ["low", "dl"] else "dh" @@ -586,7 +739,8 @@ return 1 except KeyboardInterrupt: - executor.log("\nInterrupted by user") + executor.log("\nInterrupted.") + executor.cleanup() return 130 except Exception as e: executor.log(f"Error: {e}")
diff --git a/target/ast1060-evb/mctp/system.json5 b/target/ast1060-evb/mctp/system.json5 index ad5675b..c0b0d51 100644 --- a/target/ast1060-evb/mctp/system.json5 +++ b/target/ast1060-evb/mctp/system.json5
@@ -43,17 +43,17 @@ name: "I2C", type: "channel_handler", // Server side of client→server channel }, - { - // AST1060 I2C2 combined master+slave interrupt (IRQ 112). - name: "I2C2_IRQ", - type: "interrupt", - irqs: [ - { - name: "i2c2", - number: 112, - }, - ], - }, + //{ + // // AST1060 I2C2 combined master+slave interrupt (IRQ 112). + // name: "I2C2_IRQ", + // type: "interrupt", + // irqs: [ + // { + // name: "i2c2", + // number: 112, + // }, + // ], + //}, { name: "WG", type: "wait_group", @@ -115,7 +115,7 @@ threads: [ { name: "mctp server thread", - stack_size_bytes: 8192, // 8KB stack (Router + fragmentation buffers) + stack_size_bytes: 32768, // 8KB stack (Router + fragmentation buffers) }, ], },
diff --git a/target/ast1060-evb/spdm-req-resp-test/BUILD.bazel b/target/ast1060-evb/spdm-req-resp-test/BUILD.bazel index 647e744..55ab786 100644 --- a/target/ast1060-evb/spdm-req-resp-test/BUILD.bazel +++ b/target/ast1060-evb/spdm-req-resp-test/BUILD.bazel
@@ -26,7 +26,7 @@ name = "spdm_req_resp_test", apps = [ ":mctp_loopback_server_app", - ":spdm_requester_app", + # ":spdm_requester_app", # DISABLED ":spdm_responder_app", ], kernel = ":target",
diff --git a/target/ast1060-evb/spdm-req-resp-test/system.json5 b/target/ast1060-evb/spdm-req-resp-test/system.json5 index 7dbc44b..2bfb854 100644 --- a/target/ast1060-evb/spdm-req-resp-test/system.json5 +++ b/target/ast1060-evb/spdm-req-resp-test/system.json5
@@ -68,32 +68,33 @@ ], }, // ──── SPDM Requester ──── + // DISABLED - Requester flow is not active in this configuration // Runs the SPDM requester flow using spdm-lib's requester API. // Connects to the MCTP loopback server to send/receive SPDM messages. - { - name: "spdm_requester", - flash_size_bytes: 98304, // 96KB for requester code - ram_size_bytes: 98304, // 96KB RAM (SPDM context + buffers) - processes: [ - { - name: "spdm requester process", - objects: [ - { - name: "MCTP", - type: "channel_initiator", - handler_process: "mctp loopback server process", - handler_object_name: "MCTP_REQ", - }, - ], - threads: [ - { - name: "spdm requester thread", - stack_size_bytes: 16384, - }, - ], - }, - ], - }, + // { + // name: "spdm_requester", + // flash_size_bytes: 98304, // 96KB for requester code + // ram_size_bytes: 98304, // 96KB RAM (SPDM context + buffers) + // processes: [ + // { + // name: "spdm requester process", + // objects: [ + // { + // name: "MCTP", + // type: "channel_initiator", + // handler_process: "mctp loopback server process", + // handler_object_name: "MCTP_REQ", + // }, + // ], + // threads: [ + // { + // name: "spdm requester thread", + // stack_size_bytes: 16384, + // }, + // ], + // }, + // ], + // }, // ──── SPDM Responder ──── // Runs the SPDM responder using spdm-lib's responder API. // Connects to the MCTP loopback server to receive/send SPDM messages.