blob: 73869104316f7767c8aa53ac9ee02579f3df6470 [file]
#!/usr/bin/env python3
"""
Aardvark I2C Multi-Controller Transceiver
Sends pre-fabricated I2C payloads as controller and receives responses as target.
Operates in multi-controller 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, own_addr=0x42, bitrate_khz=100, bus_timeout_ms=150):
"""
Configure the Aardvark for I2C multi-controller operation.
Args:
handle: Aardvark device handle
own_addr: Our I2C 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 target mode with our address
# Parameters: handle, own_addr, maxTxBytes, maxRxBytes
# Use 0 for unlimited buffer sizes
result = aa_i2c_slave_enable(handle, own_addr, 0, 0)
if result < 0:
print(f" Error enabling target mode: {aa_status_string(result)}")
sys.exit(1)
print(f" Our I2C Address: 0x{own_addr:02X}")
print(" Target Mode: Enabled")
print("\nConfiguration complete. Device ready for multi-controller operation.")
print(" Controller: Can send to other devices")
print(" Target: Can receive from other controllers\n")
#==========================================================================
# I2C OPERATIONS
#==========================================================================
def send_payload(handle, remote_addr, payload: List[int], description=""):
"""
Send a payload as I2C controller.
Args:
handle: Aardvark device handle
remote_addr: Remote 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" Remote: 0x{remote_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 remote device
num_written = aa_i2c_write(handle, remote_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 target data (response from another controller).
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 target)
(num_bytes, addr, data_in) = aa_i2c_slave_read(handle, max_buffer)
if num_bytes < 0:
print(f" Error reading 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 (controller read from our target)
num_bytes = aa_i2c_slave_write_stats(handle)
print(f"<<< Controller 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 controller.
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_response(handle, response_data: List[int]):
"""
Set the data to send when a controller 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 response: {aa_status_string(result)}")
else:
print(f"Response buffer set ({len(response_data)} bytes)")
#==========================================================================
# TRANSACTION SEQUENCES
#==========================================================================
def execute_transaction(handle, remote_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
remote_addr: Remote 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, remote_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-Controller Transceiver',
formatter_class=argparse.RawDescriptionHelpFormatter,
epilog='''
This tool operates in multi-controller mode:
- Sends payloads as I2C controller to remote devices
- Receives responses as I2C target from other controllers
Default configuration:
- Our I2C address: 0x10
- Remote I2C address: 0x13
- Bus speed: 100 kHz
- Bus timeout: 150 ms
'''
)
parser.add_argument('--own-addr', type=lambda x: int(x, 0), default=0x10,
metavar='ADDR', help='Our I2C address (default: 0x10)')
parser.add_argument('--own-eid', type=lambda x: int(x, 0), default=0x08,
metavar='EID', help='Our MCTP endpoint ID (default: 0x08)')
parser.add_argument('--remote-addr', type=lambda x: int(x, 0), default=0x13,
metavar='ADDR', help='Remote device address (default: 0x13)')
parser.add_argument('--remote-eid', type=lambda x: int(x, 0), default=0x42,
metavar='EID', help='Remote MCTP endpoint ID (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('--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.own_addr, args.bitrate, args.bus_timeout)
# Display configuration
print()
print("MCTP Requester Configuration:")
print(f" Our I2C Address: 0x{args.own_addr:02X}")
print(f" Our EID: 0x{args.own_eid:02X}")
print(f" Remote I2C Address: 0x{args.remote_addr:02X}")
print(f" Remote EID: 0x{args.remote_eid:02X}")
print()
# Build MCTP SPDM GET_VERSION payload using configured addresses
# Format: [CMD][ByteCount][SrcI2C][MCTP_Ver][DestEID][SrcEID][Flags][MsgType][SPDM][PEC]
src_i2c_with_read = (args.own_addr << 1) | 1 # Source I2C with read bit
# Build the frame (without I2C destination address prefix)
frame = [
0x0F, # Command code (MCTP)
0x0A, # Byte count (10 bytes)
src_i2c_with_read, # Source I2C address with read bit
0x01, # MCTP version
args.remote_eid, # Destination EID (responder)
args.own_eid, # Source EID (us)
0xC8, # Flags (SOM=1, EOM=1, Seq=0, TO=1, Tag=0)
0x05, # Message type (SPDM)
0x10, # SPDM GET_VERSION
0x84, # SPDM request code
0x00, # Param1
0x00, # Param2
]
# Calculate PEC (includes destination I2C address)
# For PEC calculation, prepend the destination I2C write address
dest_i2c_write = args.remote_addr << 1 # Destination I2C write address
frame_with_dest = [dest_i2c_write] + frame
# Calculate CRC-8 PEC
crc = 0
for byte in frame_with_dest:
crc ^= byte
for _ in range(8):
if crc & 0x80:
crc = (crc << 1) ^ 0x07
else:
crc <<= 1
crc &= 0xFF
frame.append(crc) # Append PEC to payload
print(f"Generated GET_VERSION payload:")
print(f" Source I2C: 0x{args.own_addr:02X} (frame: 0x{src_i2c_with_read:02X})")
print(f" Source EID: 0x{args.own_eid:02X}")
print(f" Dest I2C: 0x{args.remote_addr:02X}")
print(f" Dest EID: 0x{args.remote_eid:02X}")
print(f" PEC: 0x{crc:02X}")
print()
# Define pre-fabricated payloads
payloads = [
{
'name': 'MCTP SPDM GET_VERSION',
'data': frame,
'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.remote_addr,
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 target mode and close device
aa_i2c_slave_disable(handle)
aa_close(handle)
print("Aardvark device closed")
if __name__ == "__main__":
main()