diff --git a/adi/ad9084.py b/adi/ad9084.py index cf2e74198..629d80cbf 100644 --- a/adi/ad9084.py +++ b/adi/ad9084.py @@ -2,6 +2,7 @@ # # SPDX short identifier: ADIBSD +import os from typing import Dict, List from adi.adrv9002 import rx1, rx2, tx1, tx2 @@ -10,6 +11,13 @@ from adi.rx_tx import rx_tx from adi.sync_start import sync_start, sync_start_b +from .ad9088_cal_dump import validate_data + +try: + from .sshfs import sshfs +except ImportError: + sshfs = None + def _map_to_dict(paths, ch): if ch.attrs["label"].value == "buffer_only": @@ -82,6 +90,8 @@ def __init__( rx2_device_name="axi-ad9084b-rx-b", tx1_device_name="axi-ad9084-tx-hpc", tx2_device_name="axi-ad9084-tx-b", + ssh_username="root", + ssh_password="analog", ): """Create a new instance of the AD9084 MxFE @@ -104,6 +114,9 @@ def __init__( self._tx_fine_duc_channel_names = [] self._dds_channel_names = [] + self._ssh_username = ssh_username + self._ssh_password = ssh_password + context_manager.__init__(self, uri, self._device_name) # Default device for attribute writes self._ctrl = self._ctx.find_device(rx1_device_name) @@ -583,3 +596,77 @@ def chip_version(self): def api_version(self): """api_version: API version""" return self._get_iio_debug_attr_str("api_version", self._rxadc) + + def save_calibrations(self, filename="calibration_data.bin"): + """save_calibrations: Save ADC and DAC JESD calibrations to flash + + Args: + filename: Name of file to save calibration data to + + Raises: + RuntimeError: Calibration data not found on device + RuntimeError: Calibration data CRC check failed + """ + # IIO Method (does not work) + # buf = iio.create_string_buffer(2**25) + # _name_ascii = "calibration_data".encode("ascii") + # data_length = iio._d_read_attr(self._rxadc._device, _name_ascii, buf, 2**25) + # data = buf.raw[0:data_length] + # with open(filename, "wb") as f: + # f.write(data) + + # SSH Method + if "ip:" not in self.uri: + raise RuntimeError("Calibration save/load only supported over IP") + if sshfs is None: + raise RuntimeError("sshfs module not available") + ssh = sshfs(self.uri.split("ip:")[1], self._ssh_username, self._ssh_password) + path = f"/sys/bus/iio/devices/{self._rxadc.id}/calibration_data" + if not ssh.isfile(path): + raise RuntimeError("Calibration data not found on device") + cal_data = ssh.ssh.exec_command( + f"cat /sys/bus/iio/devices/{self._rxadc.id}/calibration_data" + )[1].read() + + ret = validate_data(cal_data) + if ret != 0: + raise RuntimeError("Calibration data CRC check failed") + + with open(filename, "wb") as f: + f.write(cal_data) + + def load_calibrations( + self, + filename="calibration_data.bin", + target_path="/lib/firmware/", + load_now=False, + ): + """load_calibrations: Load ADC and DAC JESD calibrations from flash + + Args: + filename: Name of file to load calibration data from + target_path: Target path on device to copy calibration file to + load_now: If true instruct driver to load calibration immediately + + Raises: + RuntimeError: Calibration file not found + RuntimeError: Calibration save/load only supported over IP + """ + # Check file exists + if not os.path.isfile(filename): + raise RuntimeError("Calibration file not found") + # Copy file to device + if "ip:" not in self.uri: + raise RuntimeError("Calibration save/load only supported over IP") + if sshfs is None: + raise RuntimeError("sshfs module not available") + ssh = sshfs(self.uri.split("ip:")[1], self._ssh_username, self._ssh_password) + remote_path = target_path + os.path.basename(filename) + ftp_client = ssh.ssh.open_sftp() + ftp_client.put(filename, remote_path) + ftp_client.close() + # Instruct driver to load calibration + if load_now: + ssh.ssh.exec_command( + f'echo "{remote_path}" > /sys/bus/iio/devices/{self._rxadc.id}/calibration_file' + ) diff --git a/adi/ad9088_cal_dump.py b/adi/ad9088_cal_dump.py new file mode 100755 index 000000000..0cede25be --- /dev/null +++ b/adi/ad9088_cal_dump.py @@ -0,0 +1,536 @@ +#!/usr/bin/env python3 +# Copyright (C) 2025 Analog Devices, Inc. +# +# SPDX short identifier: ADIBSD +# SPDX-License-Identifier: GPL-2.0 +""" +AD9088 Calibration Data Dump Tool + +Copyright 2025 Analog Devices Inc. + +Usage: python3 ad9088_cal_dump.py [SSH OPTIONS] + +This tool reads an AD9088 calibration data file and displays: +- Header information (magic, version, chip ID, configuration) +- Section offsets and sizes +- CRC validation +- Calibration data summary + +SSH OPTIONS (optional): + --ssh-pull [local_path] + Download calibration file from remote target via SSH + --ssh-push + Upload calibration file to remote target via SSH +""" + +import argparse +import os +import struct +import sys +import zlib +from pathlib import Path + +try: + import adi.sshfs as sshfs +except ImportError: + sshfs = None + +# Magic number for AD9088 calibration files +AD9088_CAL_MAGIC = 0x41443930 # "AD90" +AD9088_CAL_VERSION = 1 + +# Chip IDs +CHIPID_AD9084 = 0x9084 +CHIPID_AD9088 = 0x9088 + +# Number of devices +ADI_APOLLO_NUM_ADC_CAL_MODES = 2 +ADI_APOLLO_NUM_JRX_SERDES_12PACKS = 4 +ADI_APOLLO_NUM_JTX_SERDES_12PACKS = 4 + + +# Calibration file header structure +class AD9088CalHeader: + """Calibration file header structure""" + + def __init__(self, data): + """Parse header from binary data""" + # Parse in sections to handle padding correctly + offset = 0 + + # First 12 bytes: magic, version, chip_id + self.magic, self.version, self.chip_id = struct.unpack( + " 0 and hdr.adc_cal_size > 0: + per_mode = hdr.adc_cal_size // ADI_APOLLO_NUM_ADC_CAL_MODES + per_adc = per_mode // hdr.num_adcs + print(f" Per Mode: {per_mode} bytes") + print(f" Per ADC: {per_adc} bytes") + + print("\nDAC Calibration:") + print(f" Offset: 0x{hdr.dac_cal_offset:08X} ({hdr.dac_cal_offset} bytes)") + print(f" Size: 0x{hdr.dac_cal_size:08X} ({hdr.dac_cal_size} bytes)") + if hdr.num_dacs > 0 and hdr.dac_cal_size > 0: + per_dac = hdr.dac_cal_size // hdr.num_dacs + print(f" Per DAC: {per_dac} bytes") + + print("\nSERDES RX Calibration:") + print( + f" Offset: 0x{hdr.serdes_rx_cal_offset:08X} ({hdr.serdes_rx_cal_offset} bytes)" + ) + print(f" Size: 0x{hdr.serdes_rx_cal_size:08X} ({hdr.serdes_rx_cal_size} bytes)") + if hdr.num_serdes_rx > 0 and hdr.serdes_rx_cal_size > 0: + per_serdes = hdr.serdes_rx_cal_size // hdr.num_serdes_rx + print(f" Per Pack: {per_serdes} bytes") + + print("\nSERDES TX Calibration:") + print( + f" Offset: 0x{hdr.serdes_tx_cal_offset:08X} ({hdr.serdes_tx_cal_offset} bytes)" + ) + print(f" Size: 0x{hdr.serdes_tx_cal_size:08X} ({hdr.serdes_tx_cal_size} bytes)") + if hdr.num_serdes_tx > 0 and hdr.serdes_tx_cal_size > 0: + per_serdes = hdr.serdes_tx_cal_size // hdr.num_serdes_tx + print(f" Per Pack: {per_serdes} bytes") + + print(f"\nTotal Size: 0x{hdr.total_size:08X} ({hdr.total_size} bytes)") + + +def print_section_summary(section_name, data, offset, size, num_items, item_name): + """Print calibration section summary""" + if size == 0 or num_items == 0: + print(f"\n=== {section_name}: Empty ===") + return + + per_item = size // num_items + + print(f"\n=== {section_name} ===") + print(f"Total size: {size} bytes ({num_items} items × {per_item} bytes)\n") + + # Check for all zeros or all 0xFF (likely uninitialized) + all_zero = all(b == 0x00 for b in data[:size]) + all_ff = all(b == 0xFF for b in data[:size]) + + if all_zero: + print("WARNING: All data is zero (possibly uninitialized)") + elif all_ff: + print("WARNING: All data is 0xFF (possibly uninitialized)") + + # Print first 16 bytes of each item + for i in range(num_items): + item_offset = i * per_item + print(f"{item_name} {i} (offset 0x{offset + item_offset:08X}):") + + bytes_to_show = min(per_item, 16) + hex_bytes = data[item_offset : item_offset + bytes_to_show] + hex_str = " ".join(f"{b:02X}" for b in hex_bytes) + print(f" {hex_str} ") + + if bytes_to_show < per_item: + remaining = per_item - bytes_to_show + print(f" ... ({remaining} more bytes)") + + +def validate_and_dump(filename): + """Validate and dump calibration file""" + try: + # Open and read file + with open(filename, "rb") as fp: + data = fp.read() + + file_size = len(data) + # Use just the filename, not the full path + display_name = Path(filename).name + print(f"File: {display_name}") + print(f"Size: {file_size} bytes\n") + + # Check minimum size (header is at least 52 bytes) + if file_size < 52 + 4: + print(f"Error: File too small ({file_size} bytes)", file=sys.stderr) + return 1 + + validate_data(data, print_data=True) + + except FileNotFoundError: + print(f"Error: Cannot open file '{filename}'", file=sys.stderr) + return 1 + + +def validate_data(data, print_data=False): + + try: + + file_size = len(data) + + # Parse header + hdr = AD9088CalHeader(data) + + # Validate magic number + if hdr.magic != AD9088_CAL_MAGIC: + print( + f"Error: Invalid magic number 0x{hdr.magic:08X} (expected 0x{AD9088_CAL_MAGIC:08X})", + file=sys.stderr, + ) + if print_data: + print_header(hdr) + return 1 + + # Validate version + if hdr.version != AD9088_CAL_VERSION: + print( + f"Warning: Unsupported version {hdr.version} (expected {AD9088_CAL_VERSION})", + file=sys.stderr, + ) + + # Validate total size + if hdr.total_size != file_size: + print( + f"Warning: Size mismatch - header says {hdr.total_size}, file is {file_size}", + file=sys.stderr, + ) + + # Extract and verify CRC + crc_stored = struct.unpack(" 0 and hdr.adc_cal_offset < file_size: + num_items = hdr.num_adcs * ADI_APOLLO_NUM_ADC_CAL_MODES + if print_data: + print_section_summary( + "ADC Calibration Data", + data[hdr.adc_cal_offset :], + hdr.adc_cal_offset, + hdr.adc_cal_size, + num_items, + "ADC Chan/Mode", + ) + + # DAC calibration + if hdr.dac_cal_size > 0 and hdr.dac_cal_offset < file_size: + if print_data: + print_section_summary( + "DAC Calibration Data", + data[hdr.dac_cal_offset :], + hdr.dac_cal_offset, + hdr.dac_cal_size, + hdr.num_dacs, + "DAC", + ) + + # SERDES RX calibration + if hdr.serdes_rx_cal_size > 0 and hdr.serdes_rx_cal_offset < file_size: + if print_data: + print_section_summary( + "SERDES RX Calibration Data", + data[hdr.serdes_rx_cal_offset :], + hdr.serdes_rx_cal_offset, + hdr.serdes_rx_cal_size, + hdr.num_serdes_rx, + "SERDES RX Pack", + ) + + # SERDES TX calibration + if hdr.serdes_tx_cal_size > 0 and hdr.serdes_tx_cal_offset < file_size: + if print_data: + print_section_summary( + "SERDES TX Calibration Data", + data[hdr.serdes_tx_cal_offset :], + hdr.serdes_tx_cal_offset, + hdr.serdes_tx_cal_size, + hdr.num_serdes_tx, + "SERDES TX Pack", + ) + + return ret + + except Exception as e: + print(f"Error: {e}", file=sys.stderr) + return 1 + + +def ssh_pull(ip, username, password, remote_path, local_path=None): + """Download calibration file from remote target via SSH""" + if sshfs is None: + print("Error: paramiko module is required for SSH support", file=sys.stderr) + return 1 + + if local_path is None: + local_path = Path(remote_path).name + + try: + print(f"Connecting to {ip}...") + ssh = sshfs.sshfs(ip, username, password) + + if not remote_path: + # Find calibration_data file in /sys/bus/iio/devices/ + cal_file = None + for dev in ssh.listdir("/sys/bus/iio/devices/"): + attr_path = f"/sys/bus/iio/devices/{dev}/calibration_data" + if ssh.isfile(attr_path): + cal_file = attr_path + break + if cal_file is None: + print( + "Error: calibration_data file not found in /sys/bus/iio/devices/", + file=sys.stderr, + ) + return 1 + remote_path = cal_file + print(f"Found calibration file: {remote_path}") + + print(f"Pulling {remote_path} to {local_path}...") + + # Read file from remote via SSH + _, stdout, stderr = ssh.ssh.exec_command(f"cat {remote_path}") + data = stdout.read() + + if not data: + stderr_msg = stderr.read().decode().strip() + print(f"Error: Failed to read remote file: {stderr_msg}", file=sys.stderr) + return 1 + + # Write to local file + with open(local_path, "wb") as fp: + fp.write(data) + + print(f"Successfully downloaded {len(data)} bytes to {local_path}") + + # Validate the downloaded file + print("\nValidating downloaded file...") + return validate_and_dump(local_path) + + except Exception as e: + print(f"Error: SSH pull failed - {e}", file=sys.stderr) + return 1 + + +def ssh_push(ip, username, password, local_path, remote_path): + """Upload calibration file to remote target via SSH""" + if sshfs is None: + print("Error: paramiko module is required for SSH support", file=sys.stderr) + return 1 + + try: + # Read local file + if not os.path.isfile(local_path): + print(f"Error: Local file '{local_path}' not found", file=sys.stderr) + return 1 + + with open(local_path, "rb") as fp: + data = fp.read() + + # Validate before uploading + print(f"Validating local file {local_path}...") + ret = validate_data(data, print_data=False) + if ret != 0: + print("Error: Local file validation failed", file=sys.stderr) + return 1 + + print(f"Connecting to {ip}...") + ssh = sshfs.sshfs(ip, username, password) + + print(f"Pushing {local_path} ({len(data)} bytes) to {remote_path}...") + + # Upload file via SFTP + sftp = ssh.ssh.open_sftp() + with sftp.file(remote_path, "wb") as fp: + fp.write(data) + sftp.close() + + print(f"Successfully uploaded {len(data)} bytes to {remote_path}") + return 0 + + except Exception as e: + print(f"Error: SSH push failed - {e}", file=sys.stderr) + return 1 + + +def main(): + """Main entry point""" + parser = argparse.ArgumentParser( + description="AD9088 Calibration Data Dump Tool", + formatter_class=argparse.RawDescriptionHelpFormatter, + epilog=""" +Examples: + # Dump local calibration file + %(prog)s /lib/firmware/ad9088_cal.bin + + # Download and dump from remote target and find calibration_data attribute + %(prog)s --ssh-pull 192.168.1.100 root password + + # Download and dump from remote target with custom local filename + %(prog)s --ssh-pull 192.168.1.100 root password my_cal.bin + + # Download and dump from remote target with specified remote path + %(prog)s --ssh-pull 192.168.1.100 root password my_cal.bin /lib/firmware/ad9088_cal.bin + + # Upload local file to remote target + %(prog)s --ssh-push 192.168.1.100 root password my_cal.bin /lib/firmware/ad9088_cal.bin + """, + ) + + # SSH pull option + parser.add_argument( + "--ssh-pull", + nargs="+", + help="Download calibration file from remote target via SSH: ip username password [local_path] [remote_path]", + ) + + # SSH push option + parser.add_argument( + "--ssh-push", + nargs="+", + help="Upload calibration file to remote target via SSH: ip username password local_path remote_path", + ) + + # Default command - dump local file + parser.add_argument( + "file", nargs="?", default=None, help="Local calibration file to dump", + ) + + # Parse arguments + args = parser.parse_args() + + # Handle --ssh-pull + if args.ssh_pull: + if len(args.ssh_pull) < 4: + print( + "Error: --ssh-pull requires: ip username password remote_path [local_path]", + file=sys.stderr, + ) + return 1 + ip = args.ssh_pull[0] + username = args.ssh_pull[1] + password = args.ssh_pull[2] + local_path = ( + args.ssh_pull[3] if len(args.ssh_pull) > 3 else "calibration_data.bin" + ) + remote_path = args.ssh_pull[4] if len(args.ssh_pull) > 4 else None + return ssh_pull(ip, username, password, remote_path, local_path) + + # Handle --ssh-push + if args.ssh_push: + if len(args.ssh_push) != 5: + print( + "Error: --ssh-push requires: ip username password local_path remote_path", + file=sys.stderr, + ) + return 1 + ip = args.ssh_push[0] + username = args.ssh_push[1] + password = args.ssh_push[2] + local_path = args.ssh_push[3] + remote_path = args.ssh_push[4] + return ssh_push(ip, username, password, local_path, remote_path) + + # Handle default command - dump local file + if args.file: + return validate_and_dump(args.file) + else: + parser.print_help() + return 1 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/doc/source/guides/ad9088_cal_dump.rst b/doc/source/guides/ad9088_cal_dump.rst new file mode 100644 index 000000000..9c1b76221 --- /dev/null +++ b/doc/source/guides/ad9088_cal_dump.rst @@ -0,0 +1,351 @@ +AD9084/AD9088 Calibration Data Dump Tool +================================== + +The ``ad9088-cal-dump`` tool is a command-line utility for reading, validating, and analyzing AD9088 (and AD9084) calibration data files. It provides comprehensive information about calibration header structure, data sections, and CRC validation. Additionally, it supports remote calibration file transfer via SSH for seamless integration with networked test equipment. + +Installation +------------ + +The tool is included with pyadi-iio and can be installed as a console script entry point: + +.. code-block:: bash + + pip install pyadi-iio[jesd] + +The ``[jesd]`` optional dependency includes ``paramiko``, which is required for SSH functionality. + +Basic Usage +----------- + +Local File Analysis +~~~~~~~~~~~~~~~~~~~ + +To analyze a calibration file on your local system: + +.. code-block:: bash + + ad9088-cal-dump /path/to/calibration_file.bin + +This will display: + +- **Header Information**: Magic number, version, chip ID, and configuration +- **CRC Validation**: Calculated vs. stored CRC with status +- **Calibration Sections**: ADC, DAC, SERDES RX, and SERDES TX data summaries +- **Data Analysis**: Per-item breakdown and initialization status checks + +Example output: + +.. code-block:: text + + File: calibration_data.bin + Size: 61908 bytes + + === CRC Validation === + + Stored CRC: 0x001B3734 + Calculated CRC: 0x721B3734 + Status: [FAILED] + + === AD9088 Calibration Data Header === + + Magic Number: 0x41443930 ('09DA') [OK] + Version: 1 [OK] + Chip ID: 0x9088 (AD9088) + Configuration: 4T4R (4 TX, 4 RX) + Number of ADCs: 4 + Number of DACs: 4 + Number of SERDES RX: 2 + Number of SERDES TX: 2 + + === Calibration Sections === + + ADC Calibration: + Offset: 0x00000040 (64 bytes) + Size: 0x0000E500 (58624 bytes) + Per Mode: 29312 bytes + Per ADC: 7328 bytes + +Remote Operations +----------------- + +The tool supports SSH/SFTP operations for transferring calibration data to and from remote targets. + +Downloading from a Remote Target +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The ``--ssh-pull`` command downloads a calibration file from a remote target via SSH. The tool supports flexible argument ordering with auto-discovery capabilities. + +**Basic Syntax:** + +.. code-block:: bash + + ad9088-cal-dump --ssh-pull [local_path] [remote_path] + +**Parameters:** + +- ````: IP address or hostname of the remote target (required) +- ````: SSH username (required) +- ````: SSH password in plaintext (required) +- ``[local_path]``: (Optional) Local file path where calibration data will be saved. Defaults to ``calibration_data.bin`` if not specified. +- ``[remote_path]``: (Optional) Full path to calibration file on remote device. If omitted, the tool auto-discovers the file in ``/sys/bus/iio/devices/``. + +**Usage Examples:** + +Auto-discover calibration file on remote device: + +.. code-block:: bash + + ad9088-cal-dump --ssh-pull 192.168.1.100 root mypassword + +Download to default local filename (``calibration_data.bin``): + +.. code-block:: bash + + ad9088-cal-dump --ssh-pull 192.168.1.100 root mypassword + +Download and save to custom local filename: + +.. code-block:: bash + + ad9088-cal-dump --ssh-pull 192.168.1.100 root mypassword my_backup.bin + +Download from specific remote path to custom local filename: + +.. code-block:: bash + + ad9088-cal-dump --ssh-pull 192.168.1.100 root mypassword my_backup.bin /lib/firmware/ad9088_cal.bin + +**Auto-Discovery Behavior:** + +When no ``remote_path`` is provided, the tool automatically searches for the calibration data file in: + +.. code-block:: text + + /sys/bus/iio/devices/iio:device*/calibration_data + +This is useful when the exact calibration file location is unknown or standard. + +Uploading to a Remote Target +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The ``--ssh-push`` command uploads a calibration file to a remote target via SFTP. + +**Syntax:** + +.. code-block:: bash + + ad9088-cal-dump --ssh-push + +**Parameters:** + +- ````: IP address or hostname of the remote target (required) +- ````: SSH username (required) +- ````: SSH password in plaintext (required) +- ````: Full path to local calibration file to upload (required) +- ````: Destination path on remote device where file will be saved (required) + +**Example:** + +.. code-block:: bash + + ad9088-cal-dump --ssh-push 192.168.1.100 root mypassword ./local_cal.bin /lib/firmware/ad9088_cal.bin + +**Pre-Upload Validation:** + +The tool automatically validates the local file before uploading: + +- Verifies file exists and is readable +- Checks CRC32 checksum against stored value +- Validates magic number and version +- Validates file structure + +If validation fails, the upload is aborted to prevent corrupting the target device. + +File Format Details +------------------- + +Calibration Header Structure +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The calibration file begins with a 56-byte header containing: + +- **Magic Number** (4 bytes): 0x41443930 (``"AD90"``) +- **Version** (4 bytes): File format version (currently 1) +- **Chip ID** (4 bytes): 0x9084 for AD9084, 0x9088 for AD9088 +- **Configuration** (5 bytes): Chip configuration flags and device counts + - ``is_8t8r``: 8T8R (1) or 4T4R (0) + - ``num_adcs``: Number of ADC channels + - ``num_dacs``: Number of DAC channels + - ``num_serdes_rx``: Number of SERDES RX packs + - ``num_serdes_tx``: Number of SERDES TX packs +- **Offsets** (16 bytes): Starting byte offset for each calibration section +- **Sizes** (16 bytes): Byte length for each calibration section +- **Total Size** (4 bytes): Total file size including CRC +- **Reserved** (3 bytes): Padding for alignment + +Data Sections +~~~~~~~~~~~~~ + +The calibration data is organized into four main sections: + +**ADC Calibration** + Contains calibration data for analog-to-digital converters. Data is organized by calibration mode and ADC channel. + +**DAC Calibration** + Contains calibration data for digital-to-analog converters. Data is organized by DAC channel. + +**SERDES RX Calibration** + Contains receiver-side high-speed serializer/deserializer calibration data organized in 12-lane packs. + +**SERDES TX Calibration** + Contains transmitter-side high-speed serializer/deserializer calibration data organized in 12-lane packs. + +CRC Validation +~~~~~~~~~~~~~~ + +The last 4 bytes of the file contain a CRC32 checksum (little-endian) that covers all preceding data. The tool automatically: + +- Calculates CRC32 of all data except the last 4 bytes +- Compares with the stored value +- Reports validation status + +All data is assumed to be in little-endian format (Intel byte order). + +Troubleshooting +--------------- + +CRC Validation Failure +~~~~~~~~~~~~~~~~~~~~~~ + +**Issue**: The tool reports a CRC mismatch. + +**Causes**: +- File is corrupted or incomplete +- File was transferred without binary mode (text mode corrupted it) +- File is from a different version or format + +**Solution**: Verify the file integrity and re-download if necessary. + +SSH Connection Failures +~~~~~~~~~~~~~~~~~~~~~~~ + +**Issue**: Cannot connect to the remote target. + +**Causes**: +- Incorrect IP address or hostname +- Invalid username or password +- SSH port is blocked or not accessible +- Target device does not support SSH + +**Solution**: +- Verify IP and credentials +- Test SSH connectivity manually: ``ssh username@ip`` +- Check target device documentation for SSH availability + +File Not Found (Remote) +~~~~~~~~~~~~~~~~~~~~~~ + +**Issue**: Remote calibration file not found when using ``--ssh-pull``. + +**Causes**: +- Incorrect remote path +- File doesn't exist on the target device +- Insufficient permissions to read the file + +**Solution**: +- Verify the full path on the target device +- Check file permissions: ``ssh username@ip ls -la /path/to/file`` +- Use the auto-discovery feature if supported by your device + +Validation Failed Before Upload +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +**Issue**: Local file validation fails during ``--ssh-push``. + +**Causes**: +- Corrupted local file +- Invalid magic number or version +- CRC mismatch in local file + +**Solution**: +- Verify the local file with: ``ad9088-cal-dump /path/to/file`` +- Ensure the file was obtained from a trusted source +- Re-download or regenerate the calibration file + +Examples +-------- + +Complete Workflow +~~~~~~~~~~~~~~~~~ + +A typical workflow for backing up and analyzing calibration data: + +.. code-block:: bash + + # 1. Download calibration from production device to custom filename + ad9088-cal-dump --ssh-pull 192.168.1.100 root mypass prod_backup.bin /lib/firmware/ad9088_cal.bin + + # 2. Analyze the backup locally + ad9088-cal-dump prod_backup.bin + + # 3. Make a timestamped backup copy + cp prod_backup.bin "prod_cal_$(date +%Y%m%d_%H%M%S).bin" + + # 4. Later, restore to device + ad9088-cal-dump --ssh-push 192.168.1.100 root mypass prod_backup.bin /lib/firmware/ad9088_cal.bin + +Alternatively, use auto-discovery to download without specifying the remote path: + +.. code-block:: bash + + # 1. Auto-discover and download calibration data + ad9088-cal-dump --ssh-pull 192.168.1.100 root mypass prod_backup.bin + + # 2. Analyze the backup + ad9088-cal-dump prod_backup.bin + +Batch Operations +~~~~~~~~~~~~~~~~ + +For testing multiple devices, use shell scripting. Here are two examples: + +**Batch Download with Custom Filenames:** + +.. code-block:: bash + + #!/bin/bash + + DEVICES=("192.168.1.100" "192.168.1.101" "192.168.1.102") + USERNAME="root" + PASSWORD="mypass" + REMOTE_PATH="/lib/firmware/ad9088_cal.bin" + + for device in "${DEVICES[@]}"; do + local_file="cal_${device//./}.bin" + echo "Processing $device -> $local_file..." + ad9088-cal-dump --ssh-pull "$device" "$USERNAME" "$PASSWORD" "$local_file" "$REMOTE_PATH" + done + +**Batch Download with Auto-Discovery:** + +.. code-block:: bash + + #!/bin/bash + + DEVICES=("192.168.1.100" "192.168.1.101" "192.168.1.102") + USERNAME="root" + PASSWORD="mypass" + + for device in "${DEVICES[@]}"; do + local_file="cal_${device//./}.bin" + echo "Processing $device -> $local_file..." + # Auto-discover calibration file location + ad9088-cal-dump --ssh-pull "$device" "$USERNAME" "$PASSWORD" "$local_file" + done + +See Also +-------- + +- `Analog Devices Documentation `_ +- `pyadi-iio GitHub Repository `_ +- `AD9088 Device Documentation `_ diff --git a/doc/source/index.rst b/doc/source/index.rst index 6fcea1dcc..4ce319751 100644 --- a/doc/source/index.rst +++ b/doc/source/index.rst @@ -80,6 +80,7 @@ Sections attr/index guides/examples guides/connectivity + guides/ad9088_cal_dump buffers/index fpga/index libiio diff --git a/pyproject.toml b/pyproject.toml index 09c65d63c..cf0b83354 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -41,6 +41,9 @@ include = ["adi"] exclude = ["test*"] namespaces = true +[project.scripts] +ad9088-cal-dump = "adi.ad9088_cal_dump:main" + [project.optional-dependencies] jesd = [ "paramiko" diff --git a/test/test_ad9084.py b/test/test_ad9084.py index 65550eee0..c766cfcc9 100644 --- a/test/test_ad9084.py +++ b/test/test_ad9084.py @@ -330,3 +330,34 @@ def test_ad9084_tx_data_split_buffers( test_dma_tx, iio_uri, classname, channel, use_tx2 ): test_dma_tx(iio_uri, classname, channel, use_tx2) + + +@pytest.mark.iio_hardware("adsy1100", True) +def test_ad9084_adc_jesd_calibrations(iio_uri): + import adi + + dev = adi.ad9084(uri=iio_uri) + here = dirname(realpath(__file__)) + filename = "calibration_data.bin" + filepath = join(here, filename) + dev.save_calibrations(filepath) + # Check that calibration files are created + files = listdir(here) + assert filename in files + # Check size + import os + + size = os.path.getsize(filepath) + assert size > 0 + print(f"Calibration file size: {size} bytes") + + from adi.ad9088_cal_dump import validate_and_dump + + # 0x41443930 ('09DA') [OK] + validate_and_dump(filepath) + + # Load calibrations + dev.load_calibrations(filename=filepath, load_now=True) + # # Clean up calibration file + # import os + # os.remove(filepath)