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, &notification_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, &notification_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.