TL;DR

  • AT-x510-28GTX の stack port (port1.0.27/28) には qualified module check があって、汎用 DAC を挿すと Only ATI qualified modules are supported in this port エラーで弾かれます。
  • ファームウェア 5.4.9 以降のチェックは、SFP EEPROM (A0h page) が以下を満たせば通過します:
    • Transceiver Type が 1xCOPPER PAS
  • 1xCOPPER PAS はSFF-8472のA0hのbyte 3のbit 0(Infiniband: 1X Copper Passive) が立っているかどうかで決まる。10GTekのSFP-H10GB-CU1Mはここが立っていなくて、Typeが Unknown になるため弾かれる。
  • 変更点は2バイトだけでよかった: byte 0x03 = 0x01 と、追従する checksum byte 0x3F の更新のみすればOKです。
  • EEPROMの書き換えには Intel 82599ES のI2CCTLレジスタをBAR0 mmap経由でbit-bangする方法を使いました。(ixgbe ドライバは触らない)。これは今後詳しく解説します。
  • show system pluggable でTypeが 1xCOPPER PAS になって、認識させられました。

注意

この記事では、SFP内部のROMの書き換えや、NICのPCIレジスタへの直接アクセスといった通常の運用では行わない操作を行っています。以下のリスクを十分に理解した上で実施してください。また、執筆にあたりclaudeのskillsを使った校正をかけています。

  • SFPモジュールを物理的に破壊する可能性があります。 I2Cバスへの不正なアクセスや配線ミスにより、モジュールが二度と使えなくなる場合があります。
  • EEPROMの内容を誤って書き換えた場合、モジュールが認識されなくなる可能性があります。 書き換え前に必ず dump でバックアップを取ってください。CC_BASE (byte 0x3F)の再計算を忘れずに。
  • 本番環境・稼働中のホストでの作業は推奨しません。 筆者は本番Proxmoxホスト上で実施していますが、ixgbeドライバとのバス衝突リスクがあります。可能であれば作業対象のインターフェースは事前に ip link set dev <iface> down しておいてください。
  • この記事の内容を実施したことによる機器の破損・データの損失等について、筆者は一切の責任を負いません。 自己責任でどうぞ。

背景

秋葉原やヤフオクで、リース落ちと思しき Allied Telesis CentreCOM x510-28GTXが3000円前後で売られているのは界隈では有名な話だった(過去形)。 我が家にも 2 台あって、せっかくなので VCStack 構成にしたかった。

x510のstack portは他メーカーだと良く見る背面ではなく前面のSFP+ スロットport1.0.27/28を使う。純正運用はAT-StackXS/1.0 + ケーブルを挿す形です。

が、手元には10GTek SFP-H10GB-CU1M(汎用DAC)しかなかった。これをstack portに挿すと以下のエラーになって認識されない:

Pluggable SFP-H10GB-CU1M inserted into port1.0.27
Pluggable in port1.0.27 is of UNKNOWN type! Limited support available
ERROR: port1.0.27 - Only ATI qualified modules are supported in this port. Please remove.

検証機のファームウェアは x510-5.5.1-2.11.rel


先行情報

Nishi1234 氏のQiita記事

Nishi1234 氏の Qiita 記事 に nvsofts 氏の調査結果が引用されている:

x510のファーム5.4.9での条件は

  1. ベンダ名が「AT」から始まる
  2. トランシーバの種別が「1xCOPPER PAS」である のどちらかの満たすこと になるのか?

ちなみに 10Gtek のアレは Unknown 扱いになってました

ここまでは分かってた。ただ記事はここで止まっていて、「ここを書き換える方法がない限り、ファームウェアをアップデートしてもスタックを構成することは出来ない」と結んでいます。

TLC 氏のはてなブログ

TLC 氏のはてなブログ では、10Gtek に発注時にVendor名・PNをカスタマイズしてもらう方法で 光トランシーバでstackを張ることに成功しているっぽい。

あと、qualified checkの実装場所についての指摘がありました:

エラーを出しているのは全ファームウェア共通でnetworkパッケージ内の/usr/bin/hslとのこと。 この記事の執筆時点で最新のファームウェアであるx510-5.4.9-2.1.relの場合、上記hslに加え、networkパッケージの/usr/lib/libpluggable.so.0.0.0と、/usr/bin/plugmanも関係しているようだ。

対象が光モジュールでDACの話ではないのと、EEPROMを自力で書き換える方法は扱っていない。

Davis McCoy 氏「Claude Banged my Module」

Davis McCoy 氏の blog 記事。Allied Telesisの話ではないが、Intel 82599ESのBAR0 + I2CCTLレジスタ経由でSFPのEEPROMを書き換えたみたいな記事。

何言ってんのかわからんって人向け:単純にI2Cをproxmoxに刺さってるNICで上書きしたいって話です。

これについての詳細な実装と解説もそのうちアップします。


EEPROMを書き換えよう

失敗した方法たち

(1) Proxmox ホストで i2c-tools

Intel 82599ESを挿したProxmox ホスト上で i2cdetect -l を叩いても、SFPのI2C busは /dev/i2c-* に出てこない。ixgbe ドライバは SFP I2Cをuserlandに公開しない設計っぽい。

(2) QNAP NAS (al_eth ドライバ) で i2c-tools

おうちにQNAPのNASがあって、それにSFPポートがついてるのでいけるかな〜と思った、

しかし同様にNGだった。ドライバがSFPのI2Cを /dev/i2c-* に出してくれない。

(3) Nucleo-G474RE に SFP を直付けして MCU で書く

SFP のゴールドフィンガーに直接配線してSTM32でI2Cを叩こうとしたが、配線中にピンを誤って短絡させて危うく壊しかけた。 ハードウェアを破壊する前に撤退しました。

(4) 市販の SFP programmer(ReveLPROG 等)

買えば確実だが、Windows専用 + 価格 + 発注リードタイムとかいう三重苦。そこまでやる前にあるもので何とかしたい。 というか市販のそういうのに頼ったら負けな気がした。

採用した方法:82599ESのBAR0経由でbit-bang

Davis McCoy 氏の手法を採用。

Intel 82599ESのBAR0オフセットの0x028に I2CCTL という32bitレジスタがあり、以下のような4bitを持っているっぽい:

bit名前役割
0CLK_INSCL の現在値 (read)
1CLK_OUTSCL drive (0=drive low, 1=release → pull-up で high)
2DATA_INSDA の現在値 (read)
3DATA_OUTSDA drive (0=drive low, 1=release)

I2C controllerのhard logicはなく、ソフトウェア側でSTART/STOP/ACK/NACKを作る必要がある。BAR0 は /sys/bus/pci/devices/<addr>/resource0 でmmapできて、ユーザー空間から全部叩ける。ixgbe は走らせたまま(SFP cage への電源供給を任せたいので)。

いいところ:

  • カーネルモジュールが不要
  • /dev/i2c-* が無くても動く
  • CONFIG_STRICT_DEVMEM=y でも影響なし(resource0 経由だから)
  • root権限だけあればいい

注意点:

  • ixgbeがSFPの状態をポーリングするので書き込み中にバス衝突する可能性がある(今回は問題なかったが、厳密にやるなら ip link set dev <iface> down しておくのが無難だと思います)
  • I2C timingは手作り

詳細はまた今度別の記事で書きます。


検証環境

Proxmox ホスト pve1 の PCI 構成:

01:00.0 82599ES (enp1s0f0) - Supermicro AOC-STGN-i2S
  BAR0   : 0xbf800000, size=512KB
  Driver : ixgbe
  状態   : UP, bond0 SLAVE,

01:00.1 82599ES (enp1s0f1) - 同一 NIC の Port 2
  BAR0   : 0xbf880000, size=512KB
  Driver : ixgbe
  状態   : DOWN,

そも本番環境のproxmoxが動いてるホスト筐体でやることじゃないですね。

事前確認:

# Kernel lockdown 無効
cat /sys/kernel/security/lockdown
# => [none]

あとcでコード書いてたのでgccが必要です


ツールを書く

Davis McCoy 氏のコンセプトを参考に、Cのツール sfp_eeprom_82599 を作った。そのうち別の記事で解説するのと、気が向いたらGitHubで公開します。

./sfp_eeprom_82599 0000:01:00.1 scan                   # I2C scan
./sfp_eeprom_82599 0000:01:00.1 read <offset>          # 1 byte 読み
./sfp_eeprom_82599 0000:01:00.1 read-range <off> <len> # 範囲読み
./sfp_eeprom_82599 0000:01:00.1 write <offset> <value> # 1 byte 書き (verify 付き)
./sfp_eeprom_82599 0000:01:00.1 dump                   # A0h 256 bytes 全 dump

実装内容:

  1. /sys/bus/pci/devices/0000:01:00.1/resource0O_RDWR で開いて mmap
  2. +0x028 が I2CCTL
  3. DATA_OUT / CLK_OUT / DATA_IN / CLK_IN 4 bit を直接叩いて I2C transaction を構成
  4. SFP アドレスは 0xA0 (A0h EEPROM, 書き) / 0xA1 (読み)

動作確認

I2C scan

$ ./sfp_eeprom_82599 0000:01:00.1 scan
[info] BAR0 mapped at 0x7f... , size=0x80000
Found device at 0x50
Found device at 0x51
Found device at 0x52
Found device at 0x53

SFP の 7bit アドレスは 0x50 (A0h) と 0x51 (A2h)。0x52/0x53 は alias が出てきてるんじゃないかな?という考察。

dump と ethtool -m の照合

$ ./sfp_eeprom_82599 0000:01:00.1 dump > dump.txt
$ ethtool -m enp1s0f1 raw on length 256 | xxd > dump_ethtool.txt
$ diff <(sed 's/^.*: //' dump.txt | tr -d '|') \
       <(sed 's/^.*: //' dump_ethtool.txt | tr -d '|')

あってそう

書き込みと verify

試しに空き offset 0x5E0x01 を書いてみる:

$ ./sfp_eeprom_82599 0000:01:00.1 write 0x5e 0x01
OK: write verified

書き込みもいけそう。


3回失敗した記録

書けることは分かったのでEEPROMを書き換えていく。が、ここで3回詰まりました。

やったことその1: Vendor=“ALLIEDTELESIS” / PN=“AT-SP10TW1”

ベンダ名をAT純正品と同じにすれば通るだろうと思って書き換えてみた:

offset変更前変更後内容
0x14-0x23OEM ALLIEDTELESIS Vendor Name
0x25-0x2700 40 2000 00 F4Vendor OUI
0x28-0x37SFP-H10GB-CU1M AT-SP10TW1 Vendor PN
0x3F0xF30xC1CC_BASEの再計算

書いてて気がついたけど、Allied Telesisのベンダ名ってATIじゃね?

x510 に挿入:

Port     Vendor         Device           Serial Number     Datecode  Type
1.0.27   ALLIEDTELESIS  AT-SP10TW1       CSC251006400040   251015    Unknown
                                                                     ^^^^^^^
ERROR: port1.0.27 - Only ATI qualified modules are supported in this port.

Typeが Unknown のまま。 だめやんけ

やったことその2: Vendor=“TE Connectivity” / PN=“AT-StackXS/1.0”

ATの公式マニュアルの show system pluggable 実行例を見ると、純正AT-StackXSのVendorは TE Connectivity、PNは AT-StackXS/1.0 になっている。 参考リンク

完全に一致させたらいけんじゃね?と思ってやってみる:

offset変更後
0x14-0x23TE Connectivity
0x25-0x2700 00 00
0x28-0x37AT-StackXS/1.0
0x3F0x80 (CC_BASEの再計算)
Port     Vendor            Device            ...    Type
1.0.27   TE Connectivity   AT-StackXS/1.0    ...    Unknown   ← 変化なし

純正と同じ文字列にしても Type=Unknown のまま。あほくさ

やったことその3: Vendor Specific 領域(0x60-0x7F)のゼロ埋め

10GTek が書き込んでいる謎のデータが 0x60-0x7F にある:

0x60: 80 00 11 21 a3 f5 86 b3 e1 75 62 41 0d 76 91 9c
0x70: ce 42 6d 00 00 00 00 00 00 00 00 00 22 6b 1a 40

SFF-8472 上は Vendor Specific 領域なので firmware が参照する筋合いはないが、念のためゼロクリアしてみる。

結果:Type=Unknown のまま。まぁそうだよね〜

ここまでやってわかったこと

やったこと1~3 で分かったこと:

  • x510 の qualified check は Vendor 名・PN の文字列を見ていない(か、それだけでは足りない)
  • 0x60-0x7F は判定に関係ない
  • Type=Unknown を解消しない限り出口がない

「Type が 1xCOPPER PAS になるためにはEEPROMの何を変えればいいのか」が一番の問題だったことにここで気がつきました。


そもそも 1xCOPPER PAS って何ぞや

PASって書いてあるから内部でなんかチェックしてるのかと思ったけどどうやら違うらしい。 allied tlesisのコマンドリファレンスにこんな記述があった。

Type:モジュールの種別。10GBASE-SR(AT-SP10SRのとき)、10GBASE-LR(AT-SP10LRのとき)、1xCOPPER PAS(AT-SP10TW1/3/7、および、AT-QSFP-4SFP10G-3CU/5CUの10G側のとき)、40GBASE-SR4(AT-QSFPSRのとき)、4xCOPPER PAS(AT-QSFP1CU/3CU、および、AT-QSFP-4SFP10G-3CU/5CUの40G側のとき)のいずれか

はえ〜

じゃあこれの指定してるROMの値書き込んだら通るんじゃね?と思ってSFPのROMの仕様を調べ直した。

SFF-8472のTransceiver Codes

SFP EEPROM (A0h page) のフォーマットは SFF-8472 で規定されていて、bytes 3〜10 の8バイトが “Transceiver Codes”(どの規格に対応しているかのbitフラグ)になっているらしい。

byte 3のbitアサイン(Linux ethtool の sfpid.c から):

bit意味
710G Ethernet: 10GBASE-ER
610G Ethernet: 10GBASE-LRM
510G Ethernet: 10GBASE-LR
410G Ethernet: 10GBASE-SR
3Infiniband: 1X SX
2Infiniband: 1X LX
1Infiniband: 1X Copper Active
0Infiniband: 1X Copper Passive

byte 3のbit 0が立っていると “Infiniband: 1X Copper Passive” と表示されるのでは?。

Arista DAC の EEPROM で裏取りをする

fedora-python/python-ethtool issue #36 にある Arista CAB-SFP-SFP-2MのEEPROM:

Transceiver codes  : 0x01 0x00 0x00 0x00 0x00 0x04 0x80 0x00
                     ^^^^
Transceiver type   : Infiniband: 1X Copper Passive     ← byte 3のbit 0

AT firmware が 1xCOPPER PAS を出すのはここを見ているはず。

10GTekのbyte 3を見てみる

0x00: 03 04 21 00 00 00 00 00 04 00 00 00 67 00 00 00
                 ~~
                 byte 3 = 0x00  ← 1X Copper Passive bit が立っていない

byte 8 = 0x04(Passive Cable)は立っているので「パッシブケーブル」だとは書いてある。が、byte 3のbit 0は立っていない。これで Unknown になってるのでは?


本命: typeの2バイトを書き換える

必要な変更はここだけ:

offset変更前変更後理由
0x030x000x011X Copper Passive bit を立てる
0x3F0x800x81CC_BASE (bytes 0x00-0x3E の sum mod 256) を追従

CC_BASE がずれると firmware によっては弾かれることがあるのでちゃんと再計算する。

書き込み

# before
$ ./sfp_eeprom_82599 0000:01:00.1 read 0x03
Read 0x00 from offset 0x03
$ ./sfp_eeprom_82599 0000:01:00.1 read 0x3f
Read 0x80 from offset 0x3f

# 書き換え
$ ./sfp_eeprom_82599 0000:01:00.1 write 0x03 0x01
OK: write verified
$ ./sfp_eeprom_82599 0000:01:00.1 write 0x3f 0x81
OK: write verified

ethtool -m で確認

$ sudo ethtool -m enp1s0f1 | grep -E 'Transceiver type|Vendor name'
Identifier                                : 0x03 (SFP)
Connector                                 : 0x21 (Copper pigtail)
Transceiver type                          : Infiniband: 1X Copper Passive   ← ここ
Transceiver type                          : Passive Cable
Vendor name                               : TE Connectivity

やったぜ

x510に刺してみた結果

DACをport1.0.27 に挿入:

Port     Vendor            Device            Serial Number     Datecode  Type
1.0.27   TE Connectivity   AT-StackXS/1.0    CSC251006400040   251015    1xCOPPER PAS

10GtekのDACでも認識させられた。


振り返り

やったこと1~3で詰まっていた原因

「Vendor 名を正しく書けば通るだろう」と思い込んで、Type=Unknown を無視し続けたのが敗因だった。

Nishi1234 氏の記事には「Vendor名がAT始まるか、Typeが 1xCOPPER PASか」とあるが、純正AT-StackXSのVendorは実は TE Connectivity(OEM製造元のTE社)で ATI ではない(実物がないので検証してないけど)。

つまり純正品は条件1ではなく条件2で通っている。10GTekのVendorを ALLIEDTELESIS にしても ATI とは違う文字列なので条件1にも引っかからない。Type=Unknown を解消しない限り出口がなかった。

まぁただここら辺に関してはFWのバージョンとかによって変わるかもしれないので要検証な気はする。

AlliedのFWの実装について

TLC氏の記事によるとcheck実装は /usr/bin/hsl/usr/lib/libpluggable.so.0.0.0 にある。今回はbinaryを読まずにSFF-8472仕様 + 先行情報 + 試行錯誤で当てに行ったが、次に再燃したらbinwalkで展開してGhidraで読む予定。

推定ロジックはこんな感じ?:

bool is_qualified_for_stack(sfp_eeprom_t *e) {
    if (strncmp(e->vendor_name, "AT", 2) == 0) return true;
    if (e->byte[3] & 0x01) return true;
    return false;
}

まぁ現状動いてるし、ひさしぶりに治安悪いことできたのでよしとする。

ixgbe と競合するか

ixgbe が走ったまま bit-bang したが、今回の作業(dump 数回 + write 2 回)では衝突しなかった。書き込み量が多い場合は事前に ip link set dev enp1s0f1 down しておくのが無難。

この書き換えに問題はないのか

やっていることの本質を整理すると、「byte 3のbit 0を立てて、モジュールが Infiniband 1X Copper Passive だと自己申告するように嘘をつかせている」。なので以下のリスクは残ると思っている。

スタックの安定性は保証されない

純正AT-StackXS/1.0との電気特性の差(インピーダンス・減衰特性)がVCStackプロトコルに影響する可能性はゼロではない。1m以下のDACであれば実用上は問題ないケースがほとんどだと思うが、長距離ケーブルや大量トラフィック環境では未検証の領域になる。

ファームウェアアップデートで再び弾かれる可能性がある

今回通過できたのは「byte 3 bit 0が立っているか」というチェックだけ。ATが将来のファームでVendor OUIや Passive Cu compliance (byte 0x3C) まで検証するようになったらまた通らなくなる。

書き換えたEEPROM情報は永続する

SFPのEEPROMは不揮発なので、別ホストに挿したときにも改変後の情報が残り続ける。ethtool -m で見るとTransceiver typeが Infiniband: 1X Copper Passive と表示されるため、他の環境で使い回す場合は注意。ロールバックは後述のスクリプトで可能。

結論として

リンクが上がってVCStackが安定動作しているなら実用上は問題ない。ただし「完全に正規品と同等の動作保証」はないので、ミッションクリティカルな用途や長期無人運用での利用は自己責任で。


EEPROM hex dump 差分

Before (やったこと2の完了後)

0x00: 03 04 21 00 00 00 00 00 04 00 00 00 67 00 00 00
      ~~~~~~~~~~~
0x10: 00 00 01 00 54 45 20 43 6f 6e 6e 65 63 74 69 76
0x20: 69 74 79 20 00 00 00 00 41 54 2d 53 74 61 63 6b
0x30: 58 53 2f 31 2e 30 20 20 52 20 20 20 01 00 00 80
                                                   ~~

After (書き換え後)

0x00: 03 04 21 01 00 00 00 00 04 00 00 00 67 00 00 00
               ~~                                       
0x10: 00 00 01 00 54 45 20 43 6f 6e 6e 65 63 74 69 76
0x20: 69 74 79 20 00 00 00 00 41 54 2d 53 74 61 63 6b
0x30: 58 53 2f 31 2e 30 20 20 52 20 20 20 01 00 00 81
                                                   ~~

書き換えスクリプト

#!/usr/bin/env bash
set -euo pipefail
TOOL=/root/sfp_eeprom_82599
PCI=$1   # e.g. 0000:01:00.1

$TOOL $PCI dump > before.txt

$TOOL $PCI write 0x03 0x01
$TOOL $PCI write 0x3f 0x81

$TOOL $PCI dump > after.txt

ethtool -m $(ls /sys/bus/pci/devices/$PCI/net/) \
  | grep -E 'Transceiver type|Vendor name'

ロールバックは逆に:

$TOOL $PCI write 0x03 0x00
$TOOL $PCI write 0x3f 0x80

参考