Merge branch 'net-sfp-quirk-support-for-xgs-pon-ont-sticks-with-unclean-eeproms'

Martino Dell says:

====================
net: sfp: quirk support for XGS-PON ONT sticks with unclean EEPROMs

Some clone XGS-PON ONT sticks return EEPROM reads where the vendor PN
field contains non-printable garbage past the legitimate string instead
of the SFF-8472 mandated space padding. sfp_strlen() then can't trim
the field, the exact-length check in sfp_match() rejects the quirk
entry before the string comparison runs, and the quirk silently never
applies - so the kernel honors the module's spurious TX_FAULT and
eventually disables it.

Patch 1 adds an opt-in part-prefix-matching flag to the quirk table so
such modules can still be matched; the vendor name is always matched
exactly and existing entries behave as before. Patch 2 adds two ONT
stick entries wired to the existing potron fixup: the "OEM"
XGSPONST2001, which needs the prefix matching (it returns trailing
garbage in the PN field on cold power-up reads), and the Fiberstore
XGS-SFP-ONT-MACI, whose PN field is fully occupied by the truncated
product name and matches exactly.

Both quirks are in production use on a Bananapi BPI-R4 (MT7988A)
router on an XGS-PON uplink, backported onto 6.12.
====================

Link: https://patch.msgid.link/20260812154708.2201266-1-tillo@tillo.ch
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
This commit is contained in:
Jakub Kicinski 2026-08-17 13:28:43 -07:00
commit c69bd5b16b
2 changed files with 34 additions and 5 deletions

View File

@ -516,6 +516,15 @@ static void sfp_quirk_ubnt_uf_instant(const struct sfp_eeprom_id *id,
{ .vendor = _v, .part = _p, .support = _s, .fixup = _f, }
#define SFP_QUIRK_S(_v, _p, _s) SFP_QUIRK(_v, _p, _s, NULL)
#define SFP_QUIRK_F(_v, _p, _f) SFP_QUIRK(_v, _p, NULL, _f)
/* Like SFP_QUIRK_F, but matches the part as a prefix; the vendor name
* is still matched exactly. Use for modules whose EEPROM vendor PN
* field reads back with garbage past the legitimate characters instead
* of the SFF-8472-mandated space padding, so sfp_strlen can't trim the
* field down to the legitimate length.
*/
#define SFP_QUIRK_F_PREFIX(_v, _p, _f) \
{ .vendor = _v, .part = _p, .support = NULL, .fixup = _f, \
.part_prefix_match = true }
static const struct sfp_quirk sfp_quirks[] = {
// Alcatel Lucent G-010S-P can operate at 2500base-X, but incorrectly
@ -548,6 +557,13 @@ static const struct sfp_quirk sfp_quirks[] = {
SFP_QUIRK("FS", "GPON-ONU-34-20BI", sfp_quirk_2500basex,
sfp_fixup_ignore_tx_fault),
// Fiberstore XGS-SFP-ONT-MACI is a MAC-mode XGS-PON ONT stick with
// ONT-class serial-passthrough TX_FAULT/LOS wiring and slow startup;
// mask both signals and extend T_START_UP via the potron fixup. The
// PN is the product name (XGS-SFP-ONT-MAC-I) truncated at the 16-byte
// field width, so the field is fully occupied and matches exactly.
SFP_QUIRK_F("FS", "XGS-SFP-ONT-MACI", sfp_fixup_potron),
SFP_QUIRK_F("HALNy", "HL-GSFP", sfp_fixup_halny_gsfp),
SFP_QUIRK_F("H-COM", "SPP425H-GAB4", sfp_fixup_potron),
@ -608,6 +624,14 @@ static const struct sfp_quirk sfp_quirks[] = {
SFP_QUIRK_S("OEM", "SFP-2.5G-LH20-A", sfp_quirk_2500basex),
SFP_QUIRK_F("OEM", "RTSFP-10", sfp_fixup_rollball_cc),
SFP_QUIRK_F("OEM", "RTSFP-10G", sfp_fixup_rollball_cc),
// OEM XGSPONST2001 is an XGS-PON ONT stick with broken TX_FAULT and
// LOS indicators and slow startup, just like potron. On cold
// power-up the EEPROM vendor PN field reads back with non-printable
// garbage past the legitimate string instead of space padding, so
// match the part as a prefix.
SFP_QUIRK_F_PREFIX("OEM", "XGSPONST2001", sfp_fixup_potron),
SFP_QUIRK_F("Turris", "RTSFP-2.5G", sfp_fixup_rollball),
SFP_QUIRK_F("Turris", "RTSFP-10", sfp_fixup_rollball),
SFP_QUIRK_F("Turris", "RTSFP-10G", sfp_fixup_rollball),
@ -629,13 +653,16 @@ static size_t sfp_strlen(const char *str, size_t maxlen)
return size;
}
static bool sfp_match(const char *qs, const char *str, size_t len)
static bool sfp_match(const char *qs, const char *str, size_t len, bool prefix)
{
size_t qs_len;
if (!qs)
return true;
if (strlen(qs) != len)
qs_len = strlen(qs);
if (prefix ? qs_len > len : qs_len != len)
return false;
return !strncmp(qs, str, len);
return !strncmp(qs, str, qs_len);
}
static const struct sfp_quirk *sfp_lookup_quirk(const struct sfp_eeprom_id *id)
@ -648,8 +675,9 @@ static const struct sfp_quirk *sfp_lookup_quirk(const struct sfp_eeprom_id *id)
ps = sfp_strlen(id->base.vendor_pn, ARRAY_SIZE(id->base.vendor_pn));
for (i = 0, q = sfp_quirks; i < ARRAY_SIZE(sfp_quirks); i++, q++)
if (sfp_match(q->vendor, id->base.vendor_name, vs) &&
sfp_match(q->part, id->base.vendor_pn, ps))
if (sfp_match(q->vendor, id->base.vendor_name, vs, false) &&
sfp_match(q->part, id->base.vendor_pn, ps,
q->part_prefix_match))
return q;
return NULL;

View File

@ -12,6 +12,7 @@ struct sfp_quirk {
void (*support)(const struct sfp_eeprom_id *id,
struct sfp_module_caps *caps);
void (*fixup)(struct sfp *sfp);
bool part_prefix_match;
};
struct sfp_socket_ops {