SPDM <-> MCTP <-> I2C <-> Requester Due to issues with an internal tool, the full extent of the changes within this commit may not be necessary. Communications works through the stack, a pre-fabricated GET VERSION is sent and a response is generated in kind. Two scripts have been checked in: - i2c_monitor.py - Uses the Totalphase Beagle to monitor the bus - i2c_requester.py - Uses the Totalphase Aardvark to make requests
diff --git a/services/i2c/backend-aspeed/src/lib.rs b/services/i2c/backend-aspeed/src/lib.rs index 63f9802..01e2de3 100644 --- a/services/i2c/backend-aspeed/src/lib.rs +++ b/services/i2c/backend-aspeed/src/lib.rs
@@ -262,9 +262,43 @@ /// Returns `ResponseCode::ServerError` if the bus was not initialized /// via [`init_bus()`]. pub fn write(&mut self, bus: u8, addr: u8, data: &[u8]) -> Result<(), ResponseCode> { + pw_log::debug!("AspeedI2cBackend::write bus=0x{:02x} addr=0x{:02x} len=0x{:04x}", + bus as u32, addr as u32, data.len() as u32); if !self.is_bus_initialized(bus) { + pw_log::error!("Bus 0x{:02x} not initialized (bitmask=0x{:04x})", + bus as u32, self.initialized as u32); return Err(ResponseCode::ServerError); } + + // If slave mode is enabled, temporarily disable it for master operation + let slave_was_enabled = (self.slave_configured & (1 << bus)) != 0; + if slave_was_enabled { + pw_log::debug!("Temporarily disabling slave mode for master write"); + let (regs, buffs) = self.controller_regs(bus)?; + let ctrl = aspeed_ddk::i2c_core::I2cController { + controller: DdkController(bus), + registers: regs, + buff_registers: buffs, + }; + let mut i2c = Ast1060I2c::from_initialized(&ctrl, I2cConfig::default()); + i2c.disable_slave(); + + // Explicitly ensure master mode is enabled after disabling slave + // (slave mode might have cleared the master enable bit) + pw_log::debug!("Ensuring master mode is enabled"); + regs.i2cc00().modify(|_, w| { + w.enbl_master_fn().set_bit() + .enbl_bus_autorelease_when_scllow_sdalow_or_slave_mode_inactive_timeout() + .set_bit() + }); + + // Try to recover bus - log error but don't fail, as bus might just be idle + pw_log::debug!("Attempting bus recovery after slave disable"); + if let Err(e) = i2c.recover_bus().map_err(map_i2c_error) { + pw_log::warn!("Bus recovery returned error 0x{:02x}, continuing anyway", e as u32); + } + } + let (regs, buffs) = self.controller_regs(bus)?; let ctrl = aspeed_ddk::i2c_core::I2cController { controller: DdkController(bus), @@ -272,7 +306,33 @@ buff_registers: buffs, }; let mut i2c = Ast1060I2c::from_initialized(&ctrl, I2cConfig::default()); - i2c.write(addr, data).map_err(map_i2c_error) + + // TODO: Add bus turnaround delay for MCTP-over-I2C + // Remote device needs time to switch from master to slave mode + + pw_log::debug!("Calling Ast1060I2c::write..."); + let result = i2c.write(addr, data).map_err(map_i2c_error); + + // Re-enable slave mode if it was enabled before + if slave_was_enabled { + pw_log::debug!("Re-enabling slave mode after master write"); + let (regs, buffs) = self.controller_regs(bus)?; + let ctrl = aspeed_ddk::i2c_core::I2cController { + controller: DdkController(bus), + registers: regs, + buff_registers: buffs, + }; + let mut i2c = Ast1060I2c::from_initialized(&ctrl, I2cConfig::default()); + i2c.enable_slave(); + } + + if let Err(e) = result { + pw_log::error!("Ast1060I2c::write failed: 0x{:02x}", e as u32); + Err(e) + } else { + pw_log::debug!("Ast1060I2c::write succeeded"); + Ok(()) + } } /// Read data from an I2C device.
diff --git a/services/i2c/server/src/main.rs b/services/i2c/server/src/main.rs index d633140..1b410d6 100644 --- a/services/i2c/server/src/main.rs +++ b/services/i2c/server/src/main.rs
@@ -69,7 +69,12 @@ // Per-controller hardware init (I2CC00, timing, interrupts). // Platform init (entry.rs) already ran init_i2c_global() + pinmux. // TODO: Initialize all buses this server owns. For now, just bus 2 (I2C1 in hardware). - backend.init_bus(2).map_err(|_| pw_status::Error::Internal)?; + pw_log::info!("I2C server: initializing bus 2"); + backend.init_bus(2).map_err(|e| { + pw_log::error!("I2C server: init_bus(2) failed: 0x{:02x}", e as u32); + pw_status::Error::Internal + })?; + pw_log::info!("I2C server: bus 2 initialized successfully"); // Per-bus notification state (set/cleared via EnableSlaveNotification IPC). let mut notification_enabled = [false; 14]; @@ -176,8 +181,14 @@ return encode_error(response, ResponseCode::BufferTooSmall); } match backend.write(header.bus, header.address, &payload[..wlen]) { - Ok(()) => encode_success(response, 0), - Err(code) => encode_error(response, code), + Ok(()) => { + pw_log::debug!("I2C write success"); + encode_success(response, 0) + }, + Err(code) => { + pw_log::error!("I2C backend write failed: code=0x{:02x}", code as u32); + encode_error(response, code) + }, } } @@ -280,7 +291,7 @@ } I2cOp::SlaveReceive => { - pw_log::info!("I2C dispatch slave receive"); + //pw_log::info!("I2C dispatch slave receive"); let rlen = header.read_len as usize; let avail = response.len().saturating_sub(I2cResponseHeader::SIZE); if rlen > avail {
diff --git a/services/mctp/transport-i2c/src/sender.rs b/services/mctp/transport-i2c/src/sender.rs index 01d3815..9554cd2 100644 --- a/services/mctp/transport-i2c/src/sender.rs +++ b/services/mctp/transport-i2c/src/sender.rs
@@ -58,23 +58,65 @@ let addr = 0x42; let dest_address = I2cAddress::new_unchecked(addr); let encoder = MctpI2cEncap::new(self.own_addr); + let mtu = self.get_mtu(); + pw_log::info!("Starting fragmentation: MTU=0x{:04x}, buffer size=0x{:04x}", mtu as u32, mctp_lib::serial::MTU_MAX as u32); loop { - let mut pkt = [0u8; mctp_lib::serial::MTU_MAX]; + let mut pkt = [0u8; 254]; //mctp_lib::serial::MTU_MAX]; + pw_log::debug!("Calling fragment_vectored with buffer size 0x{:04x}", pkt.len() as u32); let r = fragmenter.fragment_vectored(payload, &mut pkt); match r { mctp_lib::fragment::SendOutput::Packet(p) => { + let addr_u8 = u8::from(dest_address); + pw_log::info!("packet sending to 0x{:02x}...", addr_u8 as u32); let mut out = [0; MCTP_I2C_MAXMTU + 8]; // max MTU + I2C header size - let _packet = encoder.encode(addr, p, &mut out, true)?; - self.i2c - .write(self.bus, dest_address, &out) - .map_err(|_| mctp::Error::TxFailure)?; + let packet = encoder.encode(addr, p, &mut out, true)?; + pw_log::debug!("Encoded packet length: 0x{:04x}", packet.len() as u32); + + let packet_len = packet.len(); + if let Err(i2c_err) = self.i2c.write(self.bus, dest_address, packet) { + use embedded_hal::i2c::Error as _; + let kind = i2c_err.kind(); + let kind_code = match kind { + embedded_hal::i2c::ErrorKind::Bus => 0u32, + embedded_hal::i2c::ErrorKind::ArbitrationLoss => 1u32, + embedded_hal::i2c::ErrorKind::NoAcknowledge(src) => match src { + embedded_hal::i2c::NoAcknowledgeSource::Address => 2u32, + embedded_hal::i2c::NoAcknowledgeSource::Data => 3u32, + embedded_hal::i2c::NoAcknowledgeSource::Unknown => 4u32, + }, + embedded_hal::i2c::ErrorKind::Overrun => 5u32, + embedded_hal::i2c::ErrorKind::Other => 6u32, + _ => 0xFFu32, + }; + pw_log::error!("I2C write failed: kind=0x{:02x}", kind_code as u32); + pw_log::error!("Bus: 0x{:02x}, Address: 0x{:02x}, Data len: 0x{:04x}", + self.bus.value() as u32, addr_u8 as u32, packet_len as u32); + return Err(mctp::Error::TxFailure); + } + pw_log::info!("packet sent"); } mctp_lib::fragment::SendOutput::Complete { tag, .. } => { + pw_log::info!("complete"); break Ok(tag); } mctp_lib::fragment::SendOutput::Error { err, .. } => { + let err_code = match err { + mctp::Error::TxFailure => 0u32, + mctp::Error::RxFailure => 1u32, + mctp::Error::TimedOut => 2u32, + mctp::Error::BadArgument => 3u32, + mctp::Error::InvalidInput => 4u32, + mctp::Error::TagUnavailable => 5u32, + mctp::Error::Unreachable => 6u32, + mctp::Error::AddrInUse => 7u32, + mctp::Error::NoSpace => 8u32, + mctp::Error::Unsupported => 9u32, + _ => 0xFFu32, + }; + pw_log::error!("fragment_vectored failed with error: 0x{:02x}", err_code as u32); + pw_log::error!("Buffer size: 0x{:04x}, MTU: 0x{:04x}", mctp_lib::serial::MTU_MAX as u32, mtu as u32); break Err(err); } }
diff --git a/target/ast1060-evb/harness/i2c_monitor.py b/target/ast1060-evb/harness/i2c_monitor.py new file mode 100755 index 0000000..c247bcb --- /dev/null +++ b/target/ast1060-evb/harness/i2c_monitor.py
@@ -0,0 +1,784 @@ +#!/usr/bin/env python3 +""" +I2C/SMBus Monitor for Beagle Bus Analyzer with MCTP/SPDM Support +Automatically connects to the first available device and listens for I2C transactions. +Supports MCTP packet assembly and SPDM message parsing. +""" + +import sys +import os +import argparse +from typing import List, Optional, Dict +from collections import defaultdict + +# Add the Beagle API to the path +# Assumes script runs at the same level as beagle-api-linux-x86_64-v6.00 directory +script_dir = os.path.dirname(os.path.abspath(__file__)) +beagle_lib_path = os.path.join(script_dir, 'beagle-api-linux-x86_64-v6.00', 'python') +sys.path.insert(0, beagle_lib_path) +from beagle_py import * + + +#========================================================================== +# MCTP/SPDM PARSING CODE (from mctp-parse) +#========================================================================== + +class SPDMParser: + """Parser for SPDM messages (DSP0274)""" + + # Request codes (DSP0274 Table 4) + REQUEST_CODES = { + 0x81: "GET_DIGESTS", + 0x82: "GET_CERTIFICATE", + 0x83: "CHALLENGE", + 0x84: "GET_VERSION", + 0x85: "CHUNK_SEND", + 0x86: "CHUNK_GET", + 0x87: "GET_ENDPOINT_INFO", + 0xE0: "GET_MEASUREMENTS", + 0xE1: "GET_CAPABILITIES", + 0xE2: "GET_SUPPORTED_EVENT_TYPES", + 0xE3: "NEGOTIATE_ALGORITHMS", + 0xE4: "KEY_EXCHANGE", + 0xE5: "FINISH", + 0xE6: "PSK_EXCHANGE", + 0xE7: "PSK_FINISH", + 0xE8: "HEARTBEAT", + 0xE9: "KEY_UPDATE", + 0xEA: "GET_ENCAPSULATED_REQUEST", + 0xEB: "DELIVER_ENCAPSULATED_RESPONSE", + 0xEC: "END_SESSION", + 0xED: "GET_CSR", + 0xEE: "SET_CERTIFICATE", + 0xEF: "GET_MEASUREMENT_EXTENSION_LOG", + 0xF0: "SUBSCRIBE_EVENT_TYPES", + 0xF1: "SEND_EVENT", + 0xFC: "GET_KEY_PAIR_INFO", + 0xFD: "SET_KEY_PAIR_INFO", + 0xFE: "VENDOR_DEFINED_REQUEST", + 0xFF: "RESPOND_IF_READY", + } + + # Response codes (DSP0274 Table 5) + RESPONSE_CODES = { + 0x01: "DIGESTS", + 0x02: "CERTIFICATE", + 0x03: "CHALLENGE_AUTH", + 0x04: "VERSION", + 0x05: "CHUNK_SEND_ACK", + 0x06: "CHUNK_RESPONSE", + 0x07: "ENDPOINT_INFO", + 0x60: "MEASUREMENTS", + 0x61: "CAPABILITIES", + 0x62: "SUPPORTED_EVENT_TYPES", + 0x63: "ALGORITHMS", + 0x64: "KEY_EXCHANGE_RSP", + 0x65: "FINISH_RSP", + 0x66: "PSK_EXCHANGE_RSP", + 0x67: "PSK_FINISH_RSP", + 0x68: "HEARTBEAT_ACK", + 0x69: "KEY_UPDATE_ACK", + 0x6A: "ENCAPSULATED_REQUEST", + 0x6B: "ENCAPSULATED_RESPONSE_ACK", + 0x6C: "END_SESSION_ACK", + 0x6D: "CSR", + 0x6E: "SET_CERTIFICATE_RSP", + 0x6F: "MEASUREMENT_EXTENSION_LOG", + 0x70: "SUBSCRIBE_EVENT_TYPES_ACK", + 0x71: "EVENT_ACK", + 0x7C: "KEY_PAIR_INFO", + 0x7D: "SET_KEY_PAIR_INFO_ACK", + 0x7E: "VENDOR_DEFINED_RESPONSE", + 0x7F: "ERROR", + } + + @staticmethod + def parse(payload: List[int]) -> Optional[Dict]: + """Parse SPDM message from payload""" + if len(payload) < 2: + return None + + result = {} + + # Byte 0: SPDM version (major [7:4], minor [3:0]) + version_byte = payload[0] + result['version_major'] = (version_byte >> 4) & 0x0F + result['version_minor'] = version_byte & 0x0F + result['version'] = f"{result['version_major']}.{result['version_minor']}" + + # Byte 1: Request/Response code + code = payload[1] + result['code'] = code + + # Determine if request or response + if code in SPDMParser.REQUEST_CODES: + result['msg_direction'] = "Request" + result['msg_name'] = SPDMParser.REQUEST_CODES[code] + elif code in SPDMParser.RESPONSE_CODES: + result['msg_direction'] = "Response" + result['msg_name'] = SPDMParser.RESPONSE_CODES[code] + else: + result['msg_direction'] = "Unknown" + result['msg_name'] = f"Unknown (0x{code:02X})" + + # Byte 2: Param1 (if present) + if len(payload) > 2: + result['param1'] = payload[2] + + # Byte 3: Param2 (if present) + if len(payload) > 3: + result['param2'] = payload[3] + + # Remaining data + if len(payload) > 4: + result['data'] = payload[4:] + result['data_len'] = len(result['data']) + else: + result['data'] = [] + result['data_len'] = 0 + + return result + + +class MCTPParser: + """Parser for MCTP packets""" + + # Message type definitions (DSP0236 Table 3) + MESSAGE_TYPES = { + 0x00: "MCTP Control Message", + 0x01: "PLDM", + 0x02: "NC-SI over MCTP", + 0x03: "Ethernet over MCTP", + 0x04: "NVMe-MI over MCTP", + 0x05: "SPDM over MCTP", + 0x06: "SECDED over MCTP", + 0x07: "CXL FM API over MCTP", + 0x08: "CXL CCI over MCTP", + } + + @staticmethod + def parse(data: List[int]) -> dict: + """Parse MCTP packet and return structured data""" + if len(data) < 4: + raise ValueError("Packet too short - minimum 4 bytes required for MCTP header") + + result = {} + + # Byte 0: Destination EID + result['dest_eid'] = data[0] + + # Byte 1: Header Version [7:4], Reserved [3:0] + byte1 = data[1] + result['header_version'] = (byte1 >> 4) & 0x0F + result['rsvd'] = byte1 & 0x0F + + # Byte 2: Source EID + result['src_eid'] = data[2] + + # Byte 3: SOM, EOM, Pkt_Seq, TO, Msg_Tag + byte3 = data[3] + result['som'] = bool(byte3 & 0x80) # Start of Message + result['eom'] = bool(byte3 & 0x40) # End of Message + result['pkt_seq'] = (byte3 >> 4) & 0x03 # Packet sequence number + result['to'] = bool(byte3 & 0x08) # Tag Owner + result['msg_tag'] = byte3 & 0x07 # Message Tag + + # Message body starts at byte 4 + if len(data) > 4: + # Byte 4: IC [7], Message Type [6:0] + byte4 = data[4] + result['ic'] = bool(byte4 & 0x80) # Integrity Check + result['msg_type'] = byte4 & 0x7F + result['msg_type_name'] = MCTPParser.MESSAGE_TYPES.get( + result['msg_type'], + f"Vendor Defined (0x{result['msg_type']:02X})" if result['msg_type'] >= 0x7E + else f"Reserved (0x{result['msg_type']:02X})" + ) + + # Payload starts at byte 5 + if len(data) > 5: + result['payload'] = data[5:] + result['payload_len'] = len(result['payload']) + + # Parse SPDM if message type is 0x05 + if result['msg_type'] == 0x05 and result['payload_len'] > 0: + result['spdm'] = SPDMParser.parse(result['payload']) + else: + result['payload'] = [] + result['payload_len'] = 0 + + return result + + +def calculate_pec(data: List[int]) -> int: + """ + Calculate SMBus PEC (Packet Error Code) using CRC-8. + + SMBus uses CRC-8 with polynomial 0x07 (x^8 + x^2 + x + 1). + Initial value is 0x00. + """ + crc = 0x00 + polynomial = 0x07 + + for byte in data: + crc ^= byte + for _ in range(8): + if crc & 0x80: + crc = (crc << 1) ^ polynomial + else: + crc = crc << 1 + crc &= 0xFF + + return crc + + +def parse_smbus_header(data: List[int], expect_pec: bool = False) -> Optional[Dict]: + """ + Parse SMBus/I2C transport binding header (DSP0237). + Returns parsed transport info or None if not valid SMBus format. + """ + if len(data) < 3: + return None + + # Check for MCTP over SMBus command code (0x0F) + if data[0] != 0x0F: + return None + + result = {} + result['cmd_code'] = data[0] + result['byte_count'] = data[1] + + expected_total = 2 + result['byte_count'] + if expect_pec: + expected_total += 1 + + if len(data) < expected_total: + return None + + if expect_pec and len(data) >= expected_total: + result['pec_received'] = data[expected_total - 1] + # Calculate expected PEC + pec_data = data[:expected_total - 1] + result['pec_calculated'] = calculate_pec(pec_data) + result['pec_valid'] = (result['pec_received'] == result['pec_calculated']) + data_end = expected_total - 1 + else: + result['pec_received'] = None + result['pec_calculated'] = None + result['pec_valid'] = None + data_end = min(len(data), expected_total) + + # SMBus-specific headers start at byte 2 + if result['byte_count'] < 5: + return None + + offset = 2 + result['source_slave_addr'] = data[offset] + offset += 1 + + # MCTP reserved + header version + byte_hdr = data[offset] + result['mctp_reserved'] = (byte_hdr >> 4) & 0x0F + result['mctp_hdr_version'] = byte_hdr & 0x0F + offset += 1 + + # Destination and Source EIDs + result['dest_eid'] = data[offset] + offset += 1 + result['src_eid'] = data[offset] + offset += 1 + + # MCTP packet starts here (SOM/EOM byte) + result['mctp_offset'] = offset + result['mctp_data'] = data[offset:data_end] + + return result + + +#========================================================================== +# MCTP MESSAGE ASSEMBLER +#========================================================================== + +class MCTPAssembler: + """Assembles fragmented MCTP messages""" + + def __init__(self): + # Key: (src_eid, dest_eid, msg_tag) + # Value: {'fragments': [], 'expected_seq': int, 'complete': bool} + self.sessions = {} + + def add_fragment(self, mctp_data: Dict) -> Optional[Dict]: + """ + Add a fragment and return complete message if EOM is reached. + Returns None if message is incomplete. + """ + key = (mctp_data['src_eid'], mctp_data['dest_eid'], mctp_data['msg_tag']) + + # SOM - start new session + if mctp_data['som']: + self.sessions[key] = { + 'fragments': [mctp_data], + 'expected_seq': (mctp_data['pkt_seq'] + 1) % 4, + 'src_eid': mctp_data['src_eid'], + 'dest_eid': mctp_data['dest_eid'], + 'msg_tag': mctp_data['msg_tag'], + 'msg_type': mctp_data.get('msg_type'), + 'msg_type_name': mctp_data.get('msg_type_name'), + } + + # Single packet message (SOM+EOM) + if mctp_data['eom']: + session = self.sessions.pop(key) + return self._assemble_session(session) + + return None + + # Middle or end fragment + if key not in self.sessions: + # Fragment without SOM - ignore or could be error + return None + + session = self.sessions[key] + + # Check sequence number + if mctp_data['pkt_seq'] != session['expected_seq']: + # Sequence error - drop session + del self.sessions[key] + return None + + session['fragments'].append(mctp_data) + session['expected_seq'] = (mctp_data['pkt_seq'] + 1) % 4 + + # EOM - assemble complete message + if mctp_data['eom']: + complete_session = self.sessions.pop(key) + return self._assemble_session(complete_session) + + return None + + def _assemble_session(self, session: Dict) -> Dict: + """Assemble fragments into complete message""" + # Combine all payloads + complete_payload = [] + for frag in session['fragments']: + if 'payload' in frag and frag['payload']: + complete_payload.extend(frag['payload']) + + result = { + 'src_eid': session['src_eid'], + 'dest_eid': session['dest_eid'], + 'msg_tag': session['msg_tag'], + 'msg_type': session['msg_type'], + 'msg_type_name': session['msg_type_name'], + 'payload': complete_payload, + 'payload_len': len(complete_payload), + 'fragment_count': len(session['fragments']), + } + + # Parse SPDM if applicable + if session['msg_type'] == 0x05 and complete_payload: + result['spdm'] = SPDMParser.parse(complete_payload) + + return result + + +#========================================================================== +# BEAGLE DEVICE FUNCTIONS +#========================================================================== + +def find_and_connect(): + """Find and connect to the first available Beagle device.""" + print("Searching for Beagle devices...") + + # Find all attached devices + (num, ports, unique_ids) = bg_find_devices_ext(16, 16) + + if num == 0: + print("Error: No Beagle devices found!") + sys.exit(1) + + print(f"Found {num} device(s)") + + # Find the first available (not in-use) device + device_port = None + for i in range(num): + port = ports[i] + unique_id = unique_ids[i] + + if not (port & BG_PORT_NOT_FREE): + device_port = port + print(f"Connecting to device on port {port} (S/N: {unique_id:04d}-{unique_id % 1000000:06d})") + break + else: + print(f"Port {port & ~BG_PORT_NOT_FREE} is in use") + + if device_port is None: + print("Error: All devices are in use!") + sys.exit(1) + + # Open the device + beagle = bg_open(device_port) + if beagle <= 0: + print(f"Error: Unable to open Beagle device on port {device_port}") + print(f"Error code = {beagle}") + sys.exit(1) + + print(f"Successfully opened Beagle device on port {device_port}") + return beagle + + +def configure_device(beagle, samplerate_khz=10000, timeout_ms=500, latency_ms=200): + """Configure the Beagle device for I2C monitoring.""" + # Set sampling rate + samplerate = bg_samplerate(beagle, samplerate_khz) + if samplerate < 0: + print(f"Error setting sample rate: {bg_status_string(samplerate)}") + sys.exit(1) + print(f"Sample rate set to {samplerate} kHz") + + # Set idle timeout + bg_timeout(beagle, timeout_ms) + print(f"Idle timeout set to {timeout_ms} ms") + + # Set latency + bg_latency(beagle, latency_ms) + print(f"Latency set to {latency_ms} ms") + + # Disable pullups and target power (passive monitoring) + bg_i2c_pullup(beagle, BG_I2C_PULLUP_OFF) + bg_target_power(beagle, BG_TARGET_POWER_OFF) + + # Get host interface speed + if bg_host_ifce_speed(beagle): + print("Host interface: high speed") + else: + print("Host interface: full speed") + + +#========================================================================== +# MONITORING FUNCTIONS +#========================================================================== + +def monitor_i2c(beagle, args, max_packet_len=256): + """Monitor and print I2C transactions with optional MCTP/SPDM parsing.""" + # Calculate timing size + timing_size = bg_bit_timing_size(BG_PROTOCOL_I2C, max_packet_len) + + # Get sample rate for timestamp conversion + samplerate_khz = bg_samplerate(beagle, 0) + + # Enable I2C capture + if bg_enable(beagle, BG_PROTOCOL_I2C) != BG_OK: + print("Error: Could not enable I2C capture!") + sys.exit(1) + + print("\n" + "="*80) + print("I2C Monitoring Started - Press Ctrl+C to stop") + print("="*80) + + if args.spdm: + print("Mode: SPDM (with MCTP and SMBus)") + if args.mctp_hide: + print(" MCTP layer hidden") + elif args.mctp: + print("Mode: MCTP (with SMBus)") + if args.mctp_no_partial: + print(" Hiding partial MCTP fragments") + elif args.smbus: + print("Mode: SMBus") + if args.with_pec: + print(" PEC validation enabled") + else: + print("Mode: Raw I2C") + + print("="*80 + "\n") + + # Allocate buffers + data_in = array_u16(max_packet_len) + timing = array_u32(timing_size) + + packet_count = 0 + mctp_assembler = MCTPAssembler() if (args.mctp or args.spdm) else None + + try: + while True: + # Read I2C transaction + (count, status, time_sop, time_duration, + time_dataoffset, data_in, timing) = \ + bg_i2c_read_bit_timing(beagle, data_in, timing) + + # Convert timestamp to nanoseconds + time_sop_ns = (time_sop * 1000) // (samplerate_khz // 1000) + + # Skip if no data + if count <= 0: + if count < 0: + print(f"Error reading I2C data: {count}") + break + continue + + packet_count += 1 + + # Extract raw data bytes (strip NACK bits) + i2c_addr = None + i2c_rw = None + offset = 0 + + # Get address if present + if not (status & BG_READ_ERR_MIDDLE_OF_PACKET) and count >= 1: + nack = data_in[0] & BG_I2C_MONITOR_NACK + if count == 1 or (data_in[0] & 0xf9) != 0xf0 or nack: + # 7-bit address + i2c_addr = (int(data_in[0] & 0xff) >> 1) + i2c_rw = "R" if (data_in[0] & 0x01) else "W" + offset = 1 + else: + # 10-bit address + i2c_addr = ((data_in[0] << 7) & 0x300) | (data_in[1] & 0xff) + i2c_rw = "R" if (data_in[0] & 0x01) else "W" + offset = 2 + + # Extract payload bytes + raw_data = [int(data_in[i] & 0xff) for i in range(offset, count)] + + # Process based on mode + if args.smbus and raw_data: + smbus_result = parse_smbus_header(raw_data, args.with_pec) + + if smbus_result and (args.mctp or args.spdm): + # Parse MCTP + try: + # Create synthetic MCTP packet + synthetic_mctp = [ + smbus_result['dest_eid'], + (smbus_result['mctp_hdr_version'] << 4) | smbus_result['mctp_reserved'], + smbus_result['src_eid'] + ] + smbus_result['mctp_data'] + + mctp_parsed = MCTPParser.parse(synthetic_mctp) + + # Handle MCTP assembly + if args.mctp_no_partial or args.spdm: + complete_msg = mctp_assembler.add_fragment(mctp_parsed) + + if complete_msg is None: + # Incomplete fragment - skip or show minimal info + if not args.mctp_no_partial: + print(f"[{time_sop_ns:12d} ns] MCTP Fragment: SOM={mctp_parsed['som']} EOM={mctp_parsed['eom']} Seq={mctp_parsed['pkt_seq']}") + continue + + # Complete message assembled + mctp_parsed = complete_msg + + # Display based on mode + if args.spdm and 'spdm' in mctp_parsed: + print_spdm_message(time_sop_ns, i2c_addr, i2c_rw, mctp_parsed, args.mctp_hide) + elif not args.spdm: + print_mctp_message(time_sop_ns, i2c_addr, i2c_rw, smbus_result, mctp_parsed) + + except Exception as e: + print(f"[{time_sop_ns:12d} ns] MCTP Parse Error: {e}") + + elif smbus_result: + # SMBus mode only + print_smbus_message(time_sop_ns, i2c_addr, i2c_rw, smbus_result, raw_data) + + else: + # Not valid SMBus MCTP - print raw + print_raw_i2c(time_sop_ns, i2c_addr, i2c_rw, raw_data, status) + + else: + # Raw I2C mode + print_raw_i2c(time_sop_ns, i2c_addr, i2c_rw, raw_data, status) + + sys.stdout.flush() + + except KeyboardInterrupt: + print("\n\nCapture stopped by user") + print(f"Total packets captured: {packet_count}") + + finally: + # Disable capture + bg_disable(beagle) + + +def print_raw_i2c(timestamp, addr, rw, data, status): + """Print raw I2C transaction""" + print(f"[{timestamp:12d} ns] ", end='') + + if addr is not None: + print(f"[S] <0x{addr:02X}:{rw}> ", end='') + + if data: + hex_str = ' '.join(f'0x{b:02X}' for b in data) + print(hex_str, end=' ') + + if not (status & BG_READ_I2C_NO_STOP): + print("[P]", end=' ') + + print() + + +def print_smbus_message(timestamp, addr, rw, smbus_info, raw_data): + """Print SMBus message with PEC info""" + print(f"[{timestamp:12d} ns] SMBus: ", end='') + print(f"Cmd=0x{smbus_info['cmd_code']:02X} Len={smbus_info['byte_count']} ", end='') + + if smbus_info['pec_valid'] is not None: + if smbus_info['pec_valid']: + print(f"PEC=✓", end=' ') + else: + print(f"PEC=✗", end=' ') + + # Show data + hex_str = ' '.join(f'{b:02X}' for b in raw_data[:16]) + if len(raw_data) > 16: + hex_str += "..." + print(f"[{hex_str}]") + + +def print_mctp_message(timestamp, addr, rw, smbus_info, mctp_parsed): + """Print MCTP message with highlighting""" + som_marker = "🟢 SOM" if mctp_parsed.get('som') else "" + eom_marker = "🔴 EOM" if mctp_parsed.get('eom') else "" + markers = f"{som_marker} {eom_marker}".strip() + + print(f"[{timestamp:12d} ns] MCTP: ", end='') + if markers: + print(f"{markers} ", end='') + + print(f"EID {mctp_parsed['src_eid']:02X}→{mctp_parsed['dest_eid']:02X} ", end='') + print(f"Seq={mctp_parsed.get('pkt_seq', '?')} Tag={mctp_parsed.get('msg_tag', '?')} ", end='') + print(f"Type=0x{mctp_parsed.get('msg_type', 0):02X} ({mctp_parsed.get('msg_type_name', 'Unknown')})", end='') + + # Show fragment count if assembled + if 'fragment_count' in mctp_parsed and mctp_parsed['fragment_count'] > 1: + print(f" [{mctp_parsed['fragment_count']} fragments]", end='') + + print() + + +def print_spdm_message(timestamp, addr, rw, mctp_parsed, hide_mctp): + """Print SPDM message with request/response highlighting""" + spdm = mctp_parsed.get('spdm') + if not spdm: + return + + # Highlight request vs response + if spdm['msg_direction'] == 'Request': + direction_marker = "📤 REQ" + elif spdm['msg_direction'] == 'Response': + direction_marker = "📥 RSP" + else: + direction_marker = "❓" + + print(f"[{timestamp:12d} ns] SPDM: {direction_marker} ", end='') + + if not hide_mctp: + print(f"EID {mctp_parsed['src_eid']:02X}→{mctp_parsed['dest_eid']:02X} ", end='') + if 'fragment_count' in mctp_parsed and mctp_parsed['fragment_count'] > 1: + print(f"[{mctp_parsed['fragment_count']} frags] ", end='') + + print(f"v{spdm['version']} {spdm['msg_name']} ", end='') + + if 'param1' in spdm: + print(f"P1=0x{spdm['param1']:02X} ", end='') + if 'param2' in spdm: + print(f"P2=0x{spdm['param2']:02X} ", end='') + + if spdm.get('data_len', 0) > 0: + print(f"+{spdm['data_len']}B", end='') + + print() + + +#========================================================================== +# MAIN +#========================================================================== + +def main(): + """Main entry point.""" + parser = argparse.ArgumentParser( + description='I2C/SMBus Monitor with MCTP/SPDM Support', + formatter_class=argparse.RawDescriptionHelpFormatter, + epilog=''' +Examples: + %(prog)s # Raw I2C monitoring + %(prog)s --smbus --with-pec # SMBus with PEC validation + %(prog)s --mctp # MCTP packet monitoring + %(prog)s --spdm # SPDM message monitoring + %(prog)s --spdm --mctp-hide # SPDM only (hide MCTP layer) + %(prog)s --samplerate 5000 --timeout 1000 # Custom sample rate and timeout + ''' + ) + + # Protocol mode arguments + parser.add_argument('--smbus', action='store_true', + help='Treat all packets as having SMBus header') + parser.add_argument('--with-pec', action='store_true', + help='Assume PEC is present (requires --smbus)') + parser.add_argument('--mctp', action='store_true', + help='MCTP mode (implies --smbus --with-pec)') + parser.add_argument('--mctp-no-partial', action='store_true', + help='Hide MCTP fragments until EOM (requires --mctp or --spdm)') + parser.add_argument('--spdm', action='store_true', + help='SPDM mode (implies --mctp)') + parser.add_argument('--mctp-hide', action='store_true', + help='Hide MCTP layer details (requires --spdm)') + + # Device configuration arguments + parser.add_argument('--samplerate', type=int, default=10000, metavar='KHZ', + help='Sample rate in kHz (default: 10000)') + parser.add_argument('--timeout', type=int, default=500, metavar='MS', + help='Idle timeout in milliseconds (default: 500)') + parser.add_argument('--latency', type=int, default=200, metavar='MS', + help='Latency in milliseconds (default: 200)') + + args = parser.parse_args() + + # Handle argument implications + if args.spdm: + args.mctp = True + args.mctp_no_partial = True # SPDM mode always waits for complete messages + + if args.mctp: + args.smbus = True + args.with_pec = True + + # Validate argument combinations + if args.with_pec and not args.smbus: + parser.error("--with-pec requires --smbus") + + if args.mctp_no_partial and not (args.mctp or args.spdm): + parser.error("--mctp-no-partial requires --mctp or --spdm") + + if args.mctp_hide and not args.spdm: + parser.error("--mctp-hide requires --spdm") + + # Validate configuration arguments + if args.samplerate <= 0: + parser.error("Sample rate must be positive") + if args.timeout < 0: + parser.error("Timeout must be non-negative") + if args.latency < 0: + parser.error("Latency must be non-negative") + + # Find and connect to device + beagle = find_and_connect() + + try: + # Configure device + print() + configure_device(beagle, args.samplerate, args.timeout, args.latency) + + # Start monitoring + monitor_i2c(beagle, args) + + finally: + # Clean up + bg_close(beagle) + print("Beagle device closed") + + +if __name__ == "__main__": + main()
diff --git a/target/ast1060-evb/harness/i2c_requester.py b/target/ast1060-evb/harness/i2c_requester.py new file mode 100755 index 0000000..868ac5e --- /dev/null +++ b/target/ast1060-evb/harness/i2c_requester.py
@@ -0,0 +1,370 @@ +#!/usr/bin/env python3 +""" +Aardvark I2C Multi-Master Transceiver +Sends pre-fabricated I2C payloads as master and receives responses as slave. +Operates in multi-master mode. +""" + +import sys +import os +import time +import argparse +from typing import List, Tuple + +# Add the Aardvark API to the path +# Assumes script runs at the same level as aardvark-api-linux-x86_64-v6.00 directory +script_dir = os.path.dirname(os.path.abspath(__file__)) +aardvark_lib_path = os.path.join(script_dir, 'aardvark-api-linux-x86_64-v6.00', 'python') +sys.path.insert(0, aardvark_lib_path) +from aardvark_py import * + + +#========================================================================== +# DEVICE MANAGEMENT +#========================================================================== + +def find_and_connect(): + """Find and connect to the first available Aardvark device.""" + print("Searching for Aardvark devices...") + + # Find all attached devices + (num, ports, unique_ids) = aa_find_devices_ext(16, 16) + + if num == 0: + print("Error: No Aardvark devices found!") + sys.exit(1) + + print(f"Found {num} device(s)") + + # Find the first available (not in-use) device + device_port = None + for i in range(num): + port = ports[i] + unique_id = unique_ids[i] + + if not (port & AA_PORT_NOT_FREE): + device_port = port + print(f"Connecting to device on port {port} (S/N: {unique_id:04d}-{unique_id % 1000000:06d})") + break + else: + print(f"Port {port & ~AA_PORT_NOT_FREE} is in use") + + if device_port is None: + print("Error: All devices are in use!") + sys.exit(1) + + # Open the device + handle = aa_open(device_port) + if handle <= 0: + print(f"Error: Unable to open Aardvark device on port {device_port}") + print(f"Error code = {handle}") + sys.exit(1) + + print(f"Successfully opened Aardvark device on port {device_port}") + return handle + + +def configure_device(handle, slave_addr=0x42, bitrate_khz=100, bus_timeout_ms=150): + """ + Configure the Aardvark for I2C multi-master operation. + + Args: + handle: Aardvark device handle + slave_addr: Our slave address (default: 0x42) + bitrate_khz: I2C bus speed in kHz (default: 100) + bus_timeout_ms: Bus lock timeout in ms (default: 150) + """ + print("\nConfiguring Aardvark I2C interface...") + + # Configure for I2C mode + aa_configure(handle, AA_CONFIG_SPI_I2C) + print(" Mode: I2C") + + # Enable I2C pullup resistors (2.2k) + aa_i2c_pullup(handle, AA_I2C_PULLUP_BOTH) + print(" Pullups: Enabled (both lines)") + + # Enable target power + aa_target_power(handle, AA_TARGET_POWER_BOTH) + print(" Target Power: Enabled") + + # Set the bitrate + actual_bitrate = aa_i2c_bitrate(handle, bitrate_khz) + print(f" Bitrate: {actual_bitrate} kHz (requested: {bitrate_khz} kHz)") + + # Set the bus lock timeout + actual_timeout = aa_i2c_bus_timeout(handle, bus_timeout_ms) + print(f" Bus Timeout: {actual_timeout} ms") + + # Enable slave mode with our address + # Parameters: handle, slave_addr, maxTxBytes, maxRxBytes + # Use 0 for unlimited buffer sizes + result = aa_i2c_slave_enable(handle, slave_addr, 0, 0) + if result < 0: + print(f" Error enabling slave mode: {aa_status_string(result)}") + sys.exit(1) + print(f" Slave Address: 0x{slave_addr:02X}") + print(" Slave Mode: Enabled") + + print("\nConfiguration complete. Device ready for multi-master operation.") + print(" Master: Can send to other devices") + print(" Slave: Can receive from other masters\n") + + +#========================================================================== +# I2C OPERATIONS +#========================================================================== + +def send_payload(handle, target_addr, payload: List[int], description=""): + """ + Send a payload as I2C master. + + Args: + handle: Aardvark device handle + target_addr: Target device address (7-bit) + payload: List of bytes to send + description: Optional description of the payload + + Returns: + Tuple of (success, num_bytes_written) + """ + if description: + print(f">>> Sending: {description}") + + print(f" Target: 0x{target_addr:02X}") + print(f" Length: {len(payload)} bytes") + print(f" Data: {' '.join(f'{b:02X}' for b in payload)}") + + # Convert to array + data_out = array('B', payload) + + # Write to target device + num_written = aa_i2c_write(handle, target_addr, AA_I2C_NO_FLAGS, data_out) + + if num_written < 0: + print(f" Status: ERROR - {aa_status_string(num_written)}") + return False, 0 + elif num_written != len(payload): + print(f" Status: PARTIAL - Wrote {num_written}/{len(payload)} bytes") + return False, num_written + else: + print(f" Status: SUCCESS - {num_written} bytes written") + return True, num_written + + +def poll_for_response(handle, timeout_ms=1000, max_buffer=256): + """ + Poll for incoming I2C slave data (response from another master). + + Args: + handle: Aardvark device handle + timeout_ms: Timeout in milliseconds + max_buffer: Maximum buffer size for receiving data + + Returns: + Tuple of (has_data, addr, data_bytes) + """ + # Poll for async events + result = aa_async_poll(handle, timeout_ms) + + if result == AA_ASYNC_NO_DATA: + return False, None, [] + + if result == AA_ASYNC_I2C_READ: + # Data was written to us (we're the slave) + (num_bytes, addr, data_in) = aa_i2c_slave_read(handle, max_buffer) + + if num_bytes < 0: + print(f" Error reading slave data: {aa_status_string(num_bytes)}") + return False, None, [] + + # Convert to list + data_list = [data_in[i] for i in range(num_bytes)] + return True, addr, data_list + + elif result == AA_ASYNC_I2C_WRITE: + # Data was read from us (master read from our slave) + num_bytes = aa_i2c_slave_write_stats(handle) + print(f"<<< Master read {num_bytes} bytes from us") + return False, None, [] + + else: + print(f" Unexpected async event: {result}") + return False, None, [] + + +def wait_for_response(handle, timeout_ms=1000, description=""): + """ + Wait for and display a response from another master. + + Args: + handle: Aardvark device handle + timeout_ms: Timeout in milliseconds + description: Optional description + + Returns: + Tuple of (success, data_bytes) + """ + if description: + print(f"<<< Waiting for response: {description}") + else: + print(f"<<< Waiting for response...") + + print(f" Timeout: {timeout_ms} ms") + + has_data, addr, data = poll_for_response(handle, timeout_ms) + + if not has_data: + print(f" Status: NO DATA (timeout)") + return False, [] + + print(f" From: 0x{addr:02X}") + print(f" Length: {len(data)} bytes") + print(f" Data: {' '.join(f'{b:02X}' for b in data)}") + print(f" Status: SUCCESS") + + return True, data + + +def set_slave_response(handle, response_data: List[int]): + """ + Set the data to send when a master reads from us. + + Args: + handle: Aardvark device handle + response_data: List of bytes to respond with + """ + data_array = array('B', response_data) + result = aa_i2c_slave_set_response(handle, data_array) + if result < 0: + print(f"Error setting slave response: {aa_status_string(result)}") + else: + print(f"Slave response buffer set ({len(response_data)} bytes)") + + +#========================================================================== +# TRANSACTION SEQUENCES +#========================================================================== + +def execute_transaction(handle, target_addr, payload, wait_response=True, + response_timeout=1000, description=""): + """ + Execute a complete transaction: send payload and optionally wait for response. + + Args: + handle: Aardvark device handle + target_addr: Target device address + payload: Payload to send + wait_response: Whether to wait for a response + response_timeout: Response timeout in ms + description: Transaction description + + Returns: + Tuple of (send_success, response_data) + """ + print("=" * 80) + if description: + print(f"Transaction: {description}") + print("=" * 80) + + # Send the payload + success, _ = send_payload(handle, target_addr, payload, "Request") + + response_data = [] + if success and wait_response: + print() + # Wait a bit for the device to process + time.sleep(0.05) + + # Wait for response + _, response_data = wait_for_response(handle, response_timeout, "Response") + + print("=" * 80) + print() + + return success, response_data + + +#========================================================================== +# MAIN +#========================================================================== + +def main(): + """Main entry point.""" + parser = argparse.ArgumentParser( + description='Aardvark I2C Multi-Master Transceiver', + formatter_class=argparse.RawDescriptionHelpFormatter, + epilog=''' +This tool operates in multi-master mode: + - Sends payloads as I2C master to target devices + - Receives responses as I2C slave from other masters + +Default configuration: + - Slave address: 0x42 + - Bus speed: 100 kHz + - Bus timeout: 150 ms + ''' + ) + + parser.add_argument('--slave-addr', type=lambda x: int(x, 0), default=0x42, + metavar='ADDR', help='Our slave address (default: 0x42)') + parser.add_argument('--bitrate', type=int, default=100, metavar='KHZ', + help='I2C bus speed in kHz (default: 100)') + parser.add_argument('--bus-timeout', type=int, default=150, metavar='MS', + help='Bus lock timeout in ms (default: 150)') + parser.add_argument('--target', type=lambda x: int(x, 0), default=0x10, + metavar='ADDR', help='Target device address (default: 0x10)') + parser.add_argument('--response-timeout', type=int, default=1000, metavar='MS', + help='Response timeout in ms (default: 1000)') + parser.add_argument('--no-wait-response', action='store_true', + help='Do not wait for response after sending') + parser.add_argument('--interactive', action='store_true', + help='Interactive mode - prompt between transactions') + + args = parser.parse_args() + + # Find and connect to device + handle = find_and_connect() + + try: + # Configure device + configure_device(handle, args.slave_addr, args.bitrate, args.bus_timeout) + + # Define pre-fabricated payloads + payloads = [ + { + 'name': 'MCTP SPDM GET_VERSION', + 'data': [0x0F, 0x0A, 0x85, 0x01, 0x08, 0x30, 0xC8, 0x05, 0x10, 0x84, 0x00, 0x00, 0x65], + 'description': 'MCTP over SMBus: SPDM GET_VERSION request' + }, + # Add more payloads here as needed + ] + + # Execute transactions + for idx, payload_info in enumerate(payloads): + if args.interactive and idx > 0: + input("\nPress Enter to send next transaction...") + + execute_transaction( + handle, + args.target, + payload_info['data'], + wait_response=not args.no_wait_response, + response_timeout=args.response_timeout, + description=payload_info['name'] + ) + + print("All transactions complete.") + + except KeyboardInterrupt: + print("\n\nInterrupted by user") + + finally: + # Disable slave mode and close device + aa_i2c_slave_disable(handle) + aa_close(handle) + print("Aardvark device closed") + + +if __name__ == "__main__": + main()