Skip to content

Instantly share code, notes, and snippets.

@ixs
Last active May 23, 2024 23:13
Show Gist options
  • Save ixs/dbaac42730dea9bd124f26cbd439c58e to your computer and use it in GitHub Desktop.
Save ixs/dbaac42730dea9bd124f26cbd439c58e to your computer and use it in GitHub Desktop.
Intel x520 EEPROM Patcher allows to unlock the x520 network card to work with non-intel branded SFP modules.
#!/usr/bin/env python3
#
# Simple Intel x520 EEPROM patcher
# Modifies the EEPROM to unlock the card for non-intel branded SFP modules.
#
# Copyright 2020,2021,2022 Andreas Thienemann <andreas@bawue.net>
#
# Licensed under the GPLv3
#
# Based on research described at https://forums.servethehome.com/index.php?threads/patching-intel-x520-eeprom-to-unlock-all-sfp-transceivers.24634/
#
# Quick explanation of what's going on:
# Looking at the Intel driver at e.g. https://elixir.bootlin.com/linux/v5.8/source/drivers/net/ethernet/intel/ixgbe/ixgbe_type.h#L2140 we can see
# that the bit 0x1 at Address 0x58 contains a configuration setting whether the card allows any SFP modules or if Intel specific ones are enforced
# by the driver.
#
# Addr Bitstring
# 0x58 xxxxxxx0 means Intel specific SFPs
# 0x58 xxxxxxx1 means any SFP is allowed.
#
# Using the parameter allow_unsupported_sfp for the kernel module we can tell the driver to accept any SFPs.
# But this tool will flip the right bit 1 to make that change permanent in the configuration bits in the EEPROM,
# thus making kernel module parameters unnecessary.
#
import subprocess
import sys
# Supported cards. If your card is supported but not listed here, please add a comment
# with your PCI IDs at https://gist.github.com/ixs/dbaac42730dea9bd124f26cbd439c58e
card_ids = {
"0x10fb": "82599ES 10-Gigabit SFI/SFP+ Network Connection",
"0x154d": "Ethernet 10G 2P X520 Adapter",
}
try:
intf = sys.argv[1]
except IndexError:
print("%s <interface>" % sys.argv[0])
exit(255)
print("Verifying interface %s." % intf)
try:
with open("/sys/class/net/%s/device/vendor" % intf) as f:
vdr_id = f.read().strip()
with open("/sys/class/net/%s/device/device" % intf) as f:
dev_id = f.read().strip()
except IOError:
print("Can't read interface data.")
exit(2)
if vdr_id in ("0x8086"):
print("Recognized an Intel manufactured card.")
else:
print("No Intel manufactured card found.")
exit(3)
if dev_id in card_ids:
print("Recognized the %s card." % card_ids[dev_id])
else:
print("No recognized x520-based card found.")
exit(3)
# Read eeprom at offset 0x58
output = subprocess.check_output(
["ethtool", "-e", intf, "offset", "0x58", "length", "1"]
).decode("utf-8")
# Parse ethtool output and convert the value into a binary string
val = output.strip().split("\n")[-1].split()[-1]
val_bin = int(val, 16)
print("EEPROM Value at 0x58 is 0x%s (%s)." % (val, bin(val_bin)))
if val_bin & 0b00000001 == 1:
print("Card is already unlocked for all SFP modules. Nothing to do.")
exit(1)
if val_bin & 0b00000001 == 0:
print("Card is locked to Intel only SFP modules. Patching EEPROM...")
new_val = val_bin | 0b00000001
print("New EEPROM Value at 0x58 will be %s (%s)" % (hex(new_val), bin(new_val)))
# The "magic" value we need in order to write to a intel card is "0x<device_id><vendor_id>"
magic = "%s%s" % (dev_id, vdr_id[2:])
cmd = [
"ethtool",
"-E",
intf,
"magic",
str(magic),
"offset",
"0x58",
"value",
hex(new_val),
"length",
"1",
]
print("About to run %s" % " ".join(cmd))
if (
input(
"This operation will write data to your ethernet card eeprom. Type 'yes' to confirm: "
).lower()
!= "yes"
):
print("Operation aborted.")
exit(1)
output = subprocess.check_output(cmd).decode("utf-8")
if len(output) == 0:
print("Sucess!")
print("Reboot the machine for changes to take effect...")
exit(0)
else:
print(output)
@janipewter
Copy link

janipewter commented Sep 14, 2022

Running this on Ubuntu 22.04 gives following error:

Traceback (most recent call last):
  File "/home/jani/intel_x520_patcher.py", line 52, in <module>
    val = output.strip().split('\n')[-1].split()[-1]
TypeError: a bytes-like object is required, not 'str'

Solution:
On line 52 find: val = output.strip().split('\n')[-1].split()[-1]
Change to: val = output.decode().strip().split('\n')[-1].split()[-1]

Result:

~$ sudo python3 intel_x520_patcher.py enp1s0f0
EEPROM Value at 0x58 is 0xfd (0b11111101)
Card is already unlocked for all SFP modules. Nothing to do.

@tokehgekko
Copy link

Running this on Ubuntu 22.04 gives following error:

Traceback (most recent call last):
  File "/home/jani/intel_x520_patcher.py", line 52, in <module>
    val = output.strip().split('\n')[-1].split()[-1]
TypeError: a bytes-like object is required, not 'str'

Solution: On line 52 find: val = output.strip().split('\n')[-1].split()[-1] Change to: val = output.decode().strip().split('\n')[-1].split()[-1]

Result:

~$ sudo python3 intel_x520_patcher.py enp1s0f0
EEPROM Value at 0x58 is 0xfd (0b11111101)
Card is already unlocked for all SFP modules. Nothing to do.

I tried this solution but now i get this instead:
Traceback (most recent call last):
File "/home/tokehgekko/intel_x520_patcher.py", line 66, in
print("Running %s" % " ".join(cmd))
TypeError: sequence item 10: expected str instance, int found

@habibulilalbaab
Copy link

habibulilalbaab commented Feb 7, 2023

Traceback (most recent call last):
File "/home/tokehgekko/intel_x520_patcher.py", line 66, in
print("Running %s" % " ".join(cmd))
TypeError: sequence item 10: expected str instance, int found

add:
import os

line 66:
from
print("Running %s" % " ".join(cmd))
to:
print("Running {}".format(cmd))

below line 65 (new line) add convert tuple to string:
cmd = ' '.join(map(str, cmd))

from:
subprocess.call(cmd)
to:
os.system(cmd)

@bluerockny
Copy link

Here's the script with all the modifications above. Works on Ubuntu 22.04

#!/usr/bin/env python3
#
# Simple Intel x520 EEPROM patcher
# Modifies the EEPROM to unlock the card for non-intel branded SFP modules.
#
# Copyright 2020,2021,2022 Andreas Thienemann <andreas@bawue.net>
#
# Licensed under the GPLv3
#
# Based on research described at https://forums.servethehome.com/index.php?threads/patching-intel-x520-eeprom-to-unlock-all-sfp-transceivers.24634/
#
# Quick explanation of what's going on:
# Looking at the Intel driver at e.g. https://elixir.bootlin.com/linux/v5.8/source/drivers/net/ethernet/intel/ixgbe/ixgbe_type.h#L2140 we can see
# that the bit 0x1 at Address 0x58 contains a configuration setting whether the card allows any SFP modules or if Intel specific ones are enforced
# by the driver.
#
# Addr Bitstring
# 0x58 xxxxxxx0 means Intel specific SFPs
# 0x58 xxxxxxx1 means any SFP is allowed.
#
# Using the parameter allow_unsupported_sfp for the kernel module we can tell the driver to accept any SFPs.
# But this tool will flip the right bit 1 to make that change permanent in the configuration bits in the EEPROM,
# thus making kernel module parameters unnecessary.
#
import os
import subprocess
import sys

try:
    intf = sys.argv[1]
except IndexError:
    print("%s <interface>" % sys.argv[0])
    sys.exit(255)

try:
    with open("/sys/class/net/%s/device/vendor" % intf) as f:
        vdr_id = f.read().strip()

    with open("/sys/class/net/%s/device/device" % intf) as f:
        dev_id = f.read().strip()
except IOError:
    print("Can't read interface data.")
    sys.exit(2)

if vdr_id not in ('0x8086') or dev_id not in ('0x10fb', '0x154d'):
    print("Not a recognized Intel x520 card.")
    sys.exit(3)


output = subprocess.check_output(['ethtool', '-e', intf, 'offset', '0x58', 'length', '1']).decode('utf-8')

val = output.strip().split('\n')[-1].split()[-1]
val_bin = int(val, 16)

print("EEPROM Value at 0x58 is 0x%s (%s)" % (val, bin(val_bin)))
if val_bin & 0b00000001 == 1:
    print("Card is already unlocked for all SFP modules. Nothing to do.")
    exit(1)
if val_bin & 0b00000001 == 0:
    print("Card is locked to Intel only SFP modules. Patching EEPROM...")
    new_val = val_bin | 0b00000001
    print("New EEPROM Value at 0x58 will be %s (%s)" % (hex(new_val), bin(new_val)))

magic = "%s%s" % (dev_id, vdr_id[2:])
cmd = ['ethtool', '-E', intf, 'magic', magic, 'offset', '0x58', 'value', hex(new_val), 'length', 1]
print("Running {}".format(cmd))
cmd = ' '.join(map(str, cmd))
os.system(cmd)
print("Reboot the machine for changes to take effect...")

@dburkland
Copy link

@bluerockny you are a life saver, this worked perfectly!

@AaronMcGuirk007
Copy link

@bluerockny Nice work! After an hour of trouble shooting, you were the golden winner. Kudos to you!

@larz99
Copy link

larz99 commented May 17, 2024

@bluerockny Thank you for this! After pulling my hair out for an hour, swearing, crying, and banging my head for not having purchased "approved brand" SFP+ modules, I found some references to your flippin' bit flipper script. I'll head over to STH and thank them as well.
One bit = heartburn and hair loss

@bluerockny
Copy link

@larz99 et al I appreciate the kudos but I want to make clear, this is not my script. All I did was cobble together the contributions of the folks in the posts above. Glad it helped you!

@ixs
Copy link
Author

ixs commented May 20, 2024

Thank you @janipewter for recognizing the need for .decode() and the initial fix.
Thanks @habibulilalbaab and @bluerockny for a copy-and-paste-able version that attempts to fixe the problem.

I updated the original script keeping subprocess and added a bit nicer output.
Enjoy.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment