RBQ.AI

RBQ.AI

【光模块】SFP Wizard不能改信息?YF教你如何写光模块

12
2025-12-18

那么臭SN的模块有什么插入交换机的必要嘛

管理接口与通信规范(你最常碰到的)

统一使用 I²C / SMBus(不是 UART)

模块类型

管理协议

SFP / SFP+

SFF-8472

QSFP / QSFP+

SFF-8436

QSFP28 / QSFP-DD

CMIS (Common Management Interface Spec)

管理地址差异

类型

I²C 地址

SFP

0x50 / 0x51

QSFP

0x50

CMIS

0x50 + Page

SFP:

  • 0x50:基础信息

  • 0x51:DDM / 实时诊断

QSFP:

  • 所有内容都在 0x50

  • 通过 Page 切换访问

下面的文章内容,我们就以如何改一个SFP模块的信息为案例,方法基本一样的。

如何写

SFP(SFF-8472)

📦 地址 0x50(A0h)

Byte

内容

0–63

模块标识 / 类型

64–95

厂商名

96–119

PN

120–123

SN

124–127

日期

128–255

扩展

SFP Wizard好用吗

在做这个的时候我遇到了个OEM的模块,他并不是默认密码也不是常用密码,我并无法编辑这个模块,但是我通过写了手上的另外一个模块,然后UI的SFP Wizard却可以直接copy过去。(我有点怀疑是其他方式,有点想做个转接板抓一下是怎么操作的)

不论体积还是设计,我觉得SFP Wizard都是挺棒的(除去国内溢价的问题)

缺点可能就是只能copy profile和没锁的模块(虽然可能有点厂商方法),并不能自定义改信息。

写码板从哪来

那当然是我们天大的好心人嘉立创呀,立即打开嘉立创开始白嫖板子x

如果你想少焊点东西,其实白嫖个板子焊两个座子,然后直接引出排针外接到你的什么CH347或者其他工具都行,反正能跑个通个i2c就行,记得做下上拉

因为我没有可调电源或者其他稳定的电源,所以我自己给自己的板子就做的复杂了一点,加了PD供电,QSFP的模块最大需求电流可以去到3A但是普通的电源芯片扛不住做了PD得到12V然后DC-DC用TPS54302DDCR降回3.3V(虽然顺带画了DC但是最后其实连DC座子都没买x)

做出来后你会发现整个BOM表里最贵的,不是CH347/其他串口转换芯片,而是QSFP的那个接口。

用什么软件?

因为没有买任何商业方案,这里就不考虑用各位专用的软件,比较你只要实现和i2c,所以其实随便什么调试软件都可以,甚至您有需求直接让AI生成脚本即可。

如果你要研究模块,您可以用ch347demo ,wch官网的工具即可。

如果你要简单的更改一个模块,我建议是直接用一个python脚本

下面是GPT帮我做的一个脚本(会尝试默认密码解锁)

# finisar_9bb03dfa_writer_adaptive.py
# -*- coding: utf-8 -*-
"""
Finisar / 9B B0 3D FA 密码族专用写码工具(全字段自适应版)

特点:
  - 使用你 fuzz 出的解锁序列:
        A2: 7B-7E = 9B B0 3D FA
        A2: 78    = 01
        A0: 7B-7C = A5 5A
        A0: 7D    = 01, 02
        A0: 7F    = 00
  - 对四个字段全部做“逐字节可写性检测”:
        Vendor: A0[0x14-0x23]
        PN    : A0[0x28-0x37]
        SN    : A0[0x44-0x53]
        Date  : A0[0x54-0x5B]
    然后**仅对可写的字节进行更新**,避免出现莫名其妙的半吊子状态。
  - checksum:
        先写字段 → 重读实际内容 → 按 SFF-8472 规则重新计算 checksum
        再对 CK0(A0[0x3F]) / CK1(A0[0x5F]) 各做单字节可写性测试,能写才写。

用法:
  python finisar_9bb03dfa_writer_adaptive.py --info
  python finisar_9bb03dfa_writer_adaptive.py --sn YF1145141919
  python finisar_9bb03dfa_writer_adaptive.py --vendor YFSAMA --pn YF10GSR850NM --sn YF1145141919 --date 251210
"""

import sys
import time
import datetime
from ch347_dll_i2c import CH347_I2C, CH347Error


# ===== 基础 I2C 工具 =====

def pad_ascii(s: str, length: int) -> bytes:
    b = s.encode("ascii", errors="ignore")[:length]
    return b + b" " * (length - len(b))


def read_page(i2c: CH347_I2C, dev: int) -> bytes:
    buf = bytearray()
    for off in range(0, 256, 16):
        buf.extend(i2c.i2c_read(dev, off, 16))
    return bytes(buf)


def write_bytes(i2c: CH347_I2C, dev: int, off: int, data: bytes):
    buf = bytes([off & 0xFF]) + data
    i2c.i2c_write(dev, buf)
    time.sleep(0.003)


def write_byte(i2c: CH347_I2C, dev: int, off: int, val: int):
    i2c.i2c_write(dev, bytes([off & 0xFF, val & 0xFF]))
    time.sleep(0.002)


def decode_ascii(buf: bytes, start: int, length: int) -> str:
    raw = buf[start:start+length]
    try:
        s = raw.decode("ascii", errors="ignore")
    except Exception:
        s = "".join(chr(b) if 32 <= b <= 126 else " " for b in raw)
    return s.strip()


# ===== checksum 计算 =====

def fix_sff8472_a0_checksums(a0: bytearray) -> None:
    if len(a0) < 96:
        raise ValueError("A0 长度不足 96 字节")
    c0 = sum(a0[0:63]) & 0xFF
    a0[63] = c0
    c1 = sum(a0[64:95]) & 0xFF
    a0[95] = c1


# ===== 信息输出 =====

def print_module_info(a0: bytes):
    print("=== 当前模块基本信息 (A0) ===")
    print(f"  Vendor      : {decode_ascii(a0, 0x14, 16)}")
    print(f"  Part Number : {decode_ascii(a0, 0x28, 16)}")
    print(f"  Revision    : {decode_ascii(a0, 0x38, 4)}")
    print(f"  Serial No   : {decode_ascii(a0, 0x44, 16)}")
    print(f"  Date Code   : {decode_ascii(a0, 0x54, 8)}")

    wavelength = (a0[60] << 8) | a0[61] if len(a0) > 61 else 0
    br_nominal = a0[12] * 100 if len(a0) > 12 else 0
    if wavelength:
        print(f"  Wavelength  : {wavelength} nm")
    if br_nominal:
        print(f"  Nominal BR  : {br_nominal} Mbps")
    print()


# ===== 解锁序列(你 fuzz 出的那条) =====

def finisar_unlock(i2c: CH347_I2C):
    print(">>> 执行 Finisar 9B B0 3D FA 解锁序列 ...")

    # A2: 4B 工厂密码
    write_byte(i2c, 0x51, 0x7B, 0x9B)
    write_byte(i2c, 0x51, 0x7C, 0xB0)
    write_byte(i2c, 0x51, 0x7D, 0x3D)
    write_byte(i2c, 0x51, 0x7E, 0xFA)

    # A2: 模式位
    write_byte(i2c, 0x51, 0x78, 0x01)

    # A0: 用户密码
    write_byte(i2c, 0x50, 0x7B, 0xA5)
    write_byte(i2c, 0x50, 0x7C, 0x5A)

    # A0: 命令序列
    write_byte(i2c, 0x50, 0x7D, 0x01)
    write_byte(i2c, 0x50, 0x7D, 0x02)

    # A0: Page 0
    write_byte(i2c, 0x50, 0x7F, 0x00)

    print(">>> 解锁序列发送完成。\n")


# ===== 自适应可写性检测 =====

def test_byte_writable(i2c: CH347_I2C, dev: int, off: int, orig_val: int) -> bool:
    """
    测试单字节是否可写:
      1) 写入一个 != 原值的测试值
      2) 立即读回,看是否等于测试值
      3) 尝试写回原值
    """
    test_val = (orig_val + 1) & 0x7E
    if test_val == orig_val:
        test_val = (orig_val + 2) & 0x7E
    if test_val < 0x20:
        test_val = 0x41  # 'A'

    write_byte(i2c, dev, off, test_val)
    rb = i2c.i2c_read(dev, off, 1)[0]
    writable = (rb == test_val)

    # 恢复原值(尽量)
    write_byte(i2c, dev, off, orig_val)
    _ = i2c.i2c_read(dev, off, 1)[0]

    return writable


def detect_writable_mask(i2c: CH347_I2C, dev: int, base_off: int, length: int, label: str):
    """
    对 [base_off, base_off+length) 区域做逐字节可写性检测。
    返回 (original_bytes, mask[list[bool]])。
    """
    original = bytes(i2c.i2c_read(dev, base_off, length))
    writable = []

    print(f">>> [{label}] 对 dev=0x{dev:02X}, offset=0x{base_off:02X}-0x{base_off+length-1:02X} 做可写性测试 ...")
    for i in range(length):
        off = base_off + i
        orig_val = original[i]
        w = test_byte_writable(i2c, dev, off, orig_val)
        writable.append(w)
        print(f"  - offset 0x{off:02X}: 原值=0x{orig_val:02X}, 可写? {w}")
    print()
    return original, writable


def apply_field_with_mask(i2c: CH347_I2C, dev: int, base_off: int, new_bytes: bytes,
                          writable_mask, label: str):
    """
    按掩码写入字段:
      - 只对 mask=True 的 byte 实际写入
      - 每个字节写完立即读回验证
    """
    print(f">>> [{label}] 按可写掩码写入 ...")
    any_written = False
    for i, can_write in enumerate(writable_mask):
        off = base_off + i
        target = new_bytes[i]
        if not can_write:
            continue
        write_byte(i2c, dev, off, target)
        rb = i2c.i2c_read(dev, off, 1)[0]
        ok = (rb == target)
        any_written = any_written or ok
        print(f"  offset 0x{off:02X}: 写入 0x{target:02X},读回 0x{rb:02X} / 成功? {ok}")
    if not any_written:
        print(f"  ⚠ [{label}] 没有任何字节真正写成功(该字段可能被写保护或由 MCU 回刷)。")
    print()
    return any_written


# ===== 主流程 =====

def main():
    vendor = None
    pn = None
    sn = None
    date_code = None
    info_only = False

    args = sys.argv[1:]
    i = 0
    while i < len(args):
        a = args[i]
        if a == "--vendor" and i + 1 < len(args):
            vendor = args[i + 1]
            i += 2
        elif a == "--pn" and i + 1 < len(args):
            pn = args[i + 1]
            i += 2
        elif a == "--sn" and i + 1 < len(args):
            sn = args[i + 1]
            i += 2
        elif a == "--date" and i + 1 < len(args):
            date_code = args[i + 1]   # "YYMMDD"
            i += 2
        elif a == "--info":
            info_only = True
            i += 1
        else:
            print(f"未知参数: {a}")
            print("用法:")
            print("  python finisar_9bb03dfa_writer_adaptive.py --info")
            print("  python finisar_9bb03dfa_writer_adaptive.py [--vendor V] [--pn PN] [--sn SN] [--date YYMMDD]")
            return

    i2c = CH347_I2C()
    try:
        print(">>> 打开 CH347 ...")
        i2c.open(speed_mode=1)

        # 备份
        ts = datetime.datetime.now().strftime("%Y%m%d_%H%M%S")
        a0 = read_page(i2c, 0x50)
        a2 = read_page(i2c, 0x51)
        open(f"finisar_before_50_{ts}.bin", "wb").write(a0)
        open(f"finisar_before_51_{ts}.bin", "wb").write(a2)
        print(">>> 已备份 A0/A2 到 finisar_before_*.bin\n")

        print_module_info(a0)

        if info_only and vendor is None and pn is None and sn is None and date_code is None:
            print("(info 模式,不执行写入。)")
            return

        # 构造“目标 A0”缓冲,仅用于算目标值和 checksum
        a0_target = bytearray(a0)
        any_field_requested = False

        if vendor is not None:
            new_v = pad_ascii(vendor, 16)
            print(f"  目标 Vendor='{vendor}' → A0[0x14-0x23]")
            a0_target[0x14:0x24] = new_v
            any_field_requested = True

        if pn is not None:
            new_pn = pad_ascii(pn, 16)
            print(f"  目标 PN='{pn}' → A0[0x28-0x37]")
            a0_target[0x28:0x38] = new_pn
            any_field_requested = True

        if sn is not None:
            new_sn = pad_ascii(sn, 16)
            print(f"  目标 SN='{sn}' → A0[0x44-0x53]")
            a0_target[0x44:0x54] = new_sn
            any_field_requested = True

        # Date:如果你传了 --date 就按你传的;否则默认用当前日期
        if date_code is None:
            now = datetime.datetime.now()
            date_code = now.strftime("%y%m%d")
        new_date = pad_ascii(date_code, 8)
        print(f"  目标 Date='{date_code}' → A0[0x54-0x5B]")
        a0_target[0x54:0x5C] = new_date
        any_field_requested = True

        if not any_field_requested:
            print("★ 没有任何字段需要修改,结束。")
            return

        old_ck0, old_ck1 = a0[0x3F], a0[0x5F]
        fix_sff8472_a0_checksums(a0_target)
        new_ck0, new_ck1 = a0_target[0x3F], a0_target[0x5F]
        print(f"\n  目标 Checksum(0..62): 0x{old_ck0:02X} -> 0x{new_ck0:02X}")
        print(f"  目标 Checksum(64..94): 0x{old_ck1:02X} -> 0x{new_ck1:02X}\n")

        # 解锁
        finisar_unlock(i2c)

        # ===== 对各字段做自适应写入 =====
        wrote_any_field = False

        # Vendor
        if vendor is not None:
            print("=== Vendor 自适应写入 ===")
            orig_v, mask_v = detect_writable_mask(i2c, 0x50, 0x14, 16, "Vendor")
            print(f"  原始 Vendor 字节:{orig_v}")
            print(f"  可写掩码          :{mask_v}")
            new_v_bytes = a0_target[0x14:0x24]
            if apply_field_with_mask(i2c, 0x50, 0x14, new_v_bytes, mask_v, "Vendor"):
                wrote_any_field = True

        # PN
        if pn is not None:
            print("=== PN 自适应写入 ===")
            orig_pn, mask_pn = detect_writable_mask(i2c, 0x50, 0x28, 16, "PN")
            print(f"  原始 PN 字节:{orig_pn}")
            print(f"  可写掩码     :{mask_pn}")
            new_pn_bytes = a0_target[0x28:0x38]
            if apply_field_with_mask(i2c, 0x50, 0x28, new_pn_bytes, mask_pn, "PN"):
                wrote_any_field = True

        # SN
        if sn is not None:
            print("=== SN 自适应写入 ===")
            orig_sn, mask_sn = detect_writable_mask(i2c, 0x50, 0x44, 16, "SN")
            print(f"  原始 SN 字节:{orig_sn}")
            print(f"  可写掩码     :{mask_sn}")
            new_sn_bytes = a0_target[0x44:0x54]
            if apply_field_with_mask(i2c, 0x50, 0x44, new_sn_bytes, mask_sn, "SN"):
                wrote_any_field = True

        # Date(总是尝试,因为很多模块允许你更新生产日期)
        print("=== Date 自适应写入 ===")
        orig_d, mask_d = detect_writable_mask(i2c, 0x50, 0x54, 8, "Date")
        print(f"  原始 Date 字节:{orig_d}")
        print(f"  可写掩码       :{mask_d}")
        new_date_bytes = a0_target[0x54:0x5C]
        if apply_field_with_mask(i2c, 0x50, 0x54, new_date_bytes, mask_d, "Date"):
            wrote_any_field = True

        # ===== 校正 checksum =====
        print("=== 校正 checksum ===")
        a0_after_fields = bytearray(read_page(i2c, 0x50))
        ck0_before, ck1_before = a0_after_fields[0x3F], a0_after_fields[0x5F]
        fix_sff8472_a0_checksums(a0_after_fields)
        ck0_target, ck1_target = a0_after_fields[0x3F], a0_after_fields[0x5F]
        print(f"  当前 CK0,CK1: 0x{ck0_before:02X}, 0x{ck1_before:02X}")
        print(f"  重新计算后目标:CK0=0x{ck0_target:02X}, CK1=0x{ck1_target:02X}")

        # checksum 也按单字节做可写性检测
        _, ck0_mask = detect_writable_mask(i2c, 0x50, 0x3F, 1, "CK0")
        _, ck1_mask = detect_writable_mask(i2c, 0x50, 0x5F, 1, "CK1")

        if ck0_mask[0]:
            write_byte(i2c, 0x50, 0x3F, ck0_target)
            ck0_back = i2c.i2c_read(0x50, 0x3F, 1)[0]
            print(f"  CK0 写入后:0x{ck0_back:02X} / 成功? {ck0_back == ck0_target}")
        else:
            print("  CK0 字节似乎不可写,跳过。")

        if ck1_mask[0]:
            write_byte(i2c, 0x50, 0x5F, ck1_target)
            ck1_back = i2c.i2c_read(0x50, 0x5F, 1)[0]
            print(f"  CK1 写入后:0x{ck1_back:02X} / 成功? {ck1_back == ck1_target}")
        else:
            print("  CK1 字节似乎不可写,跳过。")

        # ===== 最终检查 =====
        a0_final = read_page(i2c, 0x50)
        print("\n=== 最终 A0 字段检查 ===")
        print_module_info(a0_final)

    except CH347Error as e:
        print("✖ CH347 错误:", e)
    except Exception as e:
        print("✖ 未预期异常:", e)
    finally:
        try:
            i2c.close()
            print(">>> CH347 已关闭。")
        except Exception:
            pass


if __name__ == "__main__":
    main()
用法:
  python finisar_9bb03dfa_writer_adaptive.py --info 
  python finisar_9bb03dfa_writer_adaptive.py --sn YF1145141919 
  python finisar_9bb03dfa_writer_adaptive.py --vendor YFSAMA --pn YF10GSR850NM --sn YF1145141919 --date 251210

常见加密方法

Vendor Lock / 写保护是怎么做的?

🔐 常见锁机制

类型

说明

Password

写入前要先 unlock

Soft Lock

EEPROM 位掩码

CMIS State

未进入 Low Power → Ready

MCU Shadow

写入 RAM,断电丢失

CRC 校验

改了不算数

为什么会出现模块不兼容

这里是GPT给出的,实际可能会根据厂商有点差距

并不是“光学不通”,而是“管理面不通过”

99% 的“不兼容”并非:

  • 光功率不够

  • 波长不对

  • 速率不支持

而是👇

I²C / CMIS 管理检查失败

交换机在插模块后的真实流程是:

模块插入
 ↓
I²C 读取 EEPROM
 ↓
校验 Vendor / PN / Compliance / CRC
 ↓
校验是否在白名单
 ↓
决定是否:
  - 启用 TX
  - 报错
  - 限速
  - 直接 shutdown

光还没亮,管理面已经判你死刑


交换机是“主动审查者”,不是被动设备

🔍 交换机通常会检查什么?

1️⃣ Vendor 信息(最基础)

字段

位置

Vendor Name

EEPROM

OUI

IEEE OUI

Part Number

PN

Revision

Rev

Compliance Code

标准声明

👉 你改 SN 没用,Vendor Name 才是第一道门


2️⃣ 厂商白名单(最关键)

几乎所有品牌交换机都有:

  • 硬编码白名单

  • 软件版本差异白名单

  • ASIC / Linecard 特定白名单

例如:

厂商

策略

Cisco

白名单 + OUI + PN

Juniper

PN + Revision

Arista

相对宽松,但有隐藏校验

H3C / Huawei

极严,常配合签名

👉 同一模块:A 交换机能用,B 不能用 = 白名单不同


CMIS / QSFP28 是“地狱模式”

你已经踩到这里了,这里是兼容性最复杂的地方

🧠 CMIS 带来的新问题

1️⃣ Page + State Machine

模块必须处于正确状态:

  • Low Power

  • Ready

  • Application Selected

否则交换机会认为:

“模块存在,但未 ready”

➡️ 你 EEPROM 写对了,但模块状态不对


2️⃣ Application Code 不匹配

QSFP28 / QSFP-DD 会声明:

  • 支持哪些速率

  • 支持哪些 Host Interface

  • 支持哪些 Media Interface

交换机做的是:

我这个端口支持 100GBASE-LR4
你模块说你支持的是 FR4 / DR4
→ 不兼容

哪怕光学上完全一样

为什么“同一个模块”在不同交换机表现不同?

这是最容易误判的点

常见情况

情况 A:A 交换机能用,B 不行

原因:

  • A 白名单宽

  • B 校验更严格

  • B 需要特定 PN / Rev

情况 B:老版本能用,新版本不行

原因:

  • OS 升级

  • 新增 Vendor Check

  • 修补“第三方模块漏洞”

情况 C:端口不同,行为不同

原因:

  • 不同 ASIC

  • 不同 Linecard

  • 不同端口速率 profile