Skip to main content

MCTP and NVMe-MI protocol library for Serial Cables hardware

Project description

serialcables-sphinx

MCTP and NVMe-MI protocol library for Serial Cables HYDRA enclosures.

Sphinx handles the protocol layer (MCTP framing, NVMe-MI encoding/decoding) while HYDRA handles the transport layer (serial communication with hardware). Together they provide a complete end-to-end solution for NVMe Management Interface communication.

Supports NVMe-MI 1.2 (PCIe Gen5) and NVMe-MI 2.x (PCIe Gen6+) specifications with automatic version detection.

Architecture

┌─────────────────────────────────────────────────────────────┐
│              Host Application                               │
│    (Prometheus UI, LabView, pytest, Jenkins, scripts)       │
├─────────────────────────────────────────────────────────────┤
│                  serialcables-sphinx                        │
│    • MCTP packet building        • NVMe-MI decoding         │
│    • NVMe-MI request encoding    • Human-readable output    │
├─────────────────────────────────────────────────────────────┤
│                  serialcables-hydra                         │
│    • Serial communication        • Slot/mux management      │
│    • Raw packet transport        • Device enumeration       │
├─────────────────────────────────────────────────────────────┤
│                  HYDRA Hardware                             │
│    • MCU + Serial Mux            • I2C/SMBus to DUTs       │
└─────────────────────────────────────────────────────────────┘

Installation

pip install serialcables-sphinx

This automatically installs serialcables-hydra as a dependency.

Quick Start

With Real HYDRA Hardware

from serialcables_hydra import JBOFController
from serialcables_sphinx import Sphinx
from serialcables_sphinx.transports.hydra import HYDRATransport

# Connect to HYDRA enclosure
jbof = JBOFController(port="/dev/ttyUSB0")  # or "COM3" on Windows

# Create transport adapter for slot 1
transport = HYDRATransport(jbof, slot=1)

# Create Sphinx protocol handler
sphinx = Sphinx(transport)

# Poll NVM Subsystem Health Status
result = sphinx.nvme_mi.health_status_poll(eid=1)

if result.success:
    print(result.pretty_print())
else:
    print(f"Error: {result.status}")

With Mock Transport (Testing)

from serialcables_sphinx import Sphinx
from serialcables_sphinx.transports.mock import MockTransport

# Create mock transport (no hardware needed)
mock = MockTransport()

# Optionally configure simulated device state
mock.set_temperature(45)  # 45°C
mock.state.available_spare = 90

# Use identically to real hardware
sphinx = Sphinx(mock)
result = sphinx.nvme_mi.health_status_poll(eid=1)
print(result.pretty_print())

Features

Firmware Shortcuts (HYDRA v0.0.6+)

For quick access to common data, use the firmware shortcuts that handle MCTP protocol internally:

from serialcables_sphinx import MCTPShortcuts, create_shortcuts

# Quick setup
shortcuts = create_shortcuts("COM13")  # or "/dev/ttyUSB0"

# Get serial number
sn = shortcuts.get_serial_number(slot=1)
print(f"Serial: {sn.serial_number}")

# Get health status with quick-access values
health = shortcuts.get_health_status(slot=1)
print(f"Temperature: {health.temperature_celsius}°C")
print(f"Available Spare: {health.available_spare}%")
print(f"Is Healthy: {health.is_healthy}")

# Full Sphinx decoding also available
if health.decoded:
    print(health.decoded.pretty_print())

# Scan all slots
for result in shortcuts.scan_all_slots():
    if result.success:
        print(f"Slot {result.slot}: {result.serial_number}")

# Health summary
shortcuts.print_health_summary()

CLI tool for shortcuts:

sphinx-shortcuts --port COM13 serial 1           # Get serial number
sphinx-shortcuts --port COM13 health 1           # Get health status
sphinx-shortcuts --port COM13 health 1 --full    # Full decoded output
sphinx-shortcuts --port COM13 scan               # Scan all slots
sphinx-shortcuts --port COM13 health-all --json  # JSON output

High-Level API

Simple methods for common NVMe-MI operations:

# Health monitoring
health = sphinx.nvme_mi.health_status_poll(eid=1)
print(f"Temperature: {health['Composite Temperature']}")
print(f"Spare: {health['Available Spare']}")

# Controller enumeration
controllers = sphinx.nvme_mi.get_controller_list(eid=1)
for ctrl_id in controllers['Controller IDs']:
    ctrl_health = sphinx.nvme_mi.controller_health_status(eid=1, controller_id=ctrl_id)
    print(ctrl_health.pretty_print())

# Subsystem information
info = sphinx.nvme_mi.get_subsystem_info(eid=1)
print(f"NVMe-MI Version: {info['NVMe-MI Version']}")

Mid-Level API

Build and decode packets explicitly (useful for debugging):

from serialcables_sphinx import NVMeMIOpcode

# Build packet
packet = sphinx.mctp.build_nvme_mi_request(
    dest_eid=1,
    opcode=NVMeMIOpcode.NVM_SUBSYSTEM_HEALTH_STATUS_POLL
)
print(f"TX: {packet.hex(' ')}")

# Send raw bytes via HYDRA
response_bytes = hydra.send_packet(packet)
print(f"RX: {response_bytes.hex(' ')}")

# Decode response
decoded = sphinx.nvme_mi.decode(
    response_bytes, 
    NVMeMIOpcode.NVM_SUBSYSTEM_HEALTH_STATUS_POLL
)
print(decoded.pretty_print())

Low-Level API

Full control over MCTP framing:

# Build custom MCTP packet
packet = sphinx.mctp.build_raw(
    dest_eid=1,
    src_eid=0,
    msg_type=0x04,  # NVMe-MI
    payload=bytes([0x01, 0x00, 0x00, 0x00]),
    som=True,
    eom=True
)

Message Fragmentation

The library handles MCTP message fragmentation for payloads exceeding packet size limits:

from serialcables_sphinx.mctp import (
    MCTPBuilder,
    FragmentationConstants,
    FragmentedMessage,
)

# Hardware constraints
print(f"Max TX packet: {FragmentationConstants.MAX_TX_PACKET_SIZE} bytes")  # 128
print(f"Max RX packet: {FragmentationConstants.MAX_RX_PACKET_SIZE} bytes")  # 256
print(f"Max TX payload: {FragmentationConstants.MAX_TX_PAYLOAD} bytes")     # 120

# Check if fragmentation needed
builder = MCTPBuilder()
large_payload = bytes(300)
print(f"Needs fragmentation: {builder.needs_fragmentation(large_payload)}")
print(f"Fragment count: {builder.calculate_fragment_count(large_payload)}")

# Build fragmented message
result = builder.build_fragmented(
    dest_eid=1,
    msg_type=0x04,
    payload=large_payload,
)

# Send with timing control
for fragment in result.fragments:
    transport.send_packet(fragment.data)
    time.sleep(0.005)  # 5ms inter-fragment delay

Fragmentation parameters:

  • TX limit: 128 bytes per packet (hardware constraint)
  • RX limit: 256 bytes per packet (MCU memory constraint)
  • Timing: Fragments must arrive within ~100ms for device reassembly
  • Sequence: 2-bit counter (0-3) wraps for messages > 4 fragments

Output Formats

# Human-readable (for debugging)
print(result.pretty_print())

# Dictionary (for JSON APIs)
data = result.to_dict()

# Direct field access
temp = result['Composite Temperature']
spare = result.get('Available Spare', 'N/A')

# One-line summary
print(result.summary())  # "[✓] NVM_SUBSYSTEM_HEALTH_STATUS_POLL: SUCCESS (0x00)"

CLI Tool

Decode packets from the command line:

# Decode a captured response
sphinx-decode --opcode 0x01 "20 f 11 3b 1 0 0 c4 84 80 0 0 45 0 0 ca 1e a0 90 a0"

# Output as JSON
sphinx-decode --opcode 0x01 --json "20 f 11 3b ..."

Supported NVMe-MI Commands

NVMe-MI 1.2 Commands

Opcode Command Decode Support
0x00 Read NVMe-MI Data Structure
0x01 NVM Subsystem Health Status Poll
0x02 Controller Health Status Poll
0x03 Configuration Set
0x04 Configuration Get
0x05 VPD Read
0x06 VPD Write
0x07 MI Reset
0x08 SES Receive Planned
0x09 SES Send Planned
0x0D MI Send
0x0E MI Receive
0xC0+ Vendor Specific Extensible

NVMe-MI 2.x Commands (PCIe Gen6+)

Opcode Command Decode Support
0x10 Get Boot Partition Configuration
0x11 Set Boot Partition Configuration
0x12 Get Security State
0x13 Set Security State
0x14 Security Send
0x15 Security Receive
0x20 MI Get Features (2.1+)
0x21 MI Set Features (2.1+)

Version Detection

Sphinx automatically detects device NVMe-MI version and uses appropriate response formats:

from serialcables_sphinx.nvme_mi import DeviceCapabilities, NVMeMIVersion

# Query device capabilities
info = sphinx.nvme_mi.get_subsystem_info(eid=1)
caps = DeviceCapabilities.from_subsystem_info(info)

print(f"NVMe-MI Version: {caps.nvme_mi_version}")  # e.g., "2.0"
print(f"Is Gen6+: {caps.is_nvme_mi_2x}")
print(f"Supports Admin Tunneling: {caps.supports_admin_tunneling}")

Admin Command Tunneling

Tunnel NVMe Admin commands through NVMe-MI:

from serialcables_sphinx.nvme_mi import CommandCapsule, MISendRequest

# NVMe-MI 2.x style (Command Capsule)
capsule = CommandCapsule.identify_controller()
capsule = CommandCapsule.get_smart_log()

# NVMe-MI 1.2 style (MI Send/Receive)
mi_send = MISendRequest(opcode=AdminOpcode.IDENTIFY, dwords=dwords)

Vendor Extensions

Register custom decoders for vendor-specific commands:

from serialcables_sphinx import DecoderRegistry, ResponseDecoder

@DecoderRegistry.register(opcode=0xC0, vendor_id=0x1234)
class MyVendorDecoder(ResponseDecoder):
    def decode(self, data: bytes, response):
        self._add_field(response, "Custom Field", data[0], data[0:1])
        return response

# Use with vendor ID
sphinx = Sphinx(hydra, vendor_id=0x1234)

Integration Examples

pytest

import pytest
from serialcables_sphinx import Sphinx
from serialcables_sphinx.transports.mock import MockTransport

# For unit tests, use mock transport
@pytest.fixture
def sphinx():
    mock = MockTransport()
    return Sphinx(mock)

def test_temperature_in_range(sphinx):
    result = sphinx.nvme_mi.health_status_poll(eid=1)
    assert result.success
    # Check decoded temperature field
    temp_str = result['Composite Temperature']
    assert "°C" in temp_str

# For hardware integration tests
@pytest.fixture
def sphinx_hardware():
    from serialcables_sphinx.transports.hydra import create_hydra_transport
    transport = create_hydra_transport("/dev/ttyUSB0", slot=1)
    return Sphinx(transport)

LabView

Use Python Node to import and call Sphinx methods, returning dict/JSON to LabView.

Jenkins Pipeline

stage('NVMe-MI Health Check') {
    steps {
        sh '''
            python -c "
from serialcables_hydra import HYDRADevice
from serialcables_sphinx import Sphinx
sphinx = Sphinx(HYDRADevice('/dev/ttyUSB0'))
result = sphinx.nvme_mi.health_status_poll(eid=1)
assert result.success, f'Health check failed: {result.status}'
print(result.pretty_print())
"
        '''
    }
}

Requirements

  • Python 3.9+
  • serialcables-hydra >= 0.1.0

License

MIT License - see LICENSE file for details.

Links

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

serialcables_sphinx-0.4.0.tar.gz (87.3 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

serialcables_sphinx-0.4.0-py3-none-any.whl (93.5 kB view details)

Uploaded Python 3

File details

Details for the file serialcables_sphinx-0.4.0.tar.gz.

File metadata

  • Download URL: serialcables_sphinx-0.4.0.tar.gz
  • Upload date:
  • Size: 87.3 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.11.3

File hashes

Hashes for serialcables_sphinx-0.4.0.tar.gz
Algorithm Hash digest
SHA256 a8ebc35ffdf508184794a272cb281c6bb04d319b13fbae7e1071e3cbca3de0cf
MD5 29548bdb2023dc2a73e6020e763d071a
BLAKE2b-256 1604ade3db996d1170a8e8fce209eadd9db18231e4a052f57803b3fae8bbd214

See more details on using hashes here.

File details

Details for the file serialcables_sphinx-0.4.0-py3-none-any.whl.

File metadata

File hashes

Hashes for serialcables_sphinx-0.4.0-py3-none-any.whl
Algorithm Hash digest
SHA256 42845190edbd4f90d5d12ab6a9e10a60d90ab280ebb27efc59a266a1d17d62b7
MD5 3662385141e9c5dd0e495f4ab8e57669
BLAKE2b-256 f7585a4560e5d003b752d90227c8808d9c271db980606366c88c88d31897c649

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page