[PATCH 6/7] tg3: normalize inherited M3000 register byte order

From: Magnus Lindholm

Date: Fri Oct 02 2026 - 12:17:33 EST


M3000 firmware can leave BCM5718 vendor registers byte-swapped while
standard PCI fields retain normal byte order. Match the Fujitsu 10cf:165a
subsystem, IKKAKU model and swapped revision/product signature before
restoring host control; reject failed PCI accesses or register readbacks.

Keep this in probe so failures can abort initialization; SPARC firmware
enumeration skips PCI_FIXUP_EARLY. Normal rebinds and other platforms
retain their existing path.

Use tg3.h's MISC_HOST_CTRL_BYTE_SWAP and TG3PCI_GEN2_PRODID_ASICREV;
the inherited state was observed on M3000 hardware.

Signed-off-by: Magnus Lindholm <linmag7@xxxxxxxxx>
---
drivers/net/ethernet/broadcom/tg3.c | 91 ++++++++++++++++++++++++++++-
1 file changed, 89 insertions(+), 2 deletions(-)

diff --git a/drivers/net/ethernet/broadcom/tg3.c b/drivers/net/ethernet/broadcom/tg3.c
index 73a4b569b03e..9335bcc0d8d3 100644
--- a/drivers/net/ethernet/broadcom/tg3.c
+++ b/drivers/net/ethernet/broadcom/tg3.c
@@ -56,6 +56,7 @@
#include <linux/hwmon-sysfs.h>
#include <linux/crc32.h>
#include <linux/dmi.h>
+#include <linux/of.h>

#include <net/checksum.h>
#include <net/gso.h>
@@ -16248,6 +16249,83 @@ static bool tg3_10_100_only_device(struct tg3 *tp,
return false;
}

+/* M3000 firmware can leave the on-board BCM5718 registers byte-swapped. */
+static bool tg3_is_m3000(struct pci_dev *pdev)
+{
+ struct device_node *root;
+ const char *model;
+ bool match;
+
+ if (pdev->vendor != PCI_VENDOR_ID_BROADCOM ||
+ pdev->device != TG3PCI_DEVICE_TIGON3_5718 ||
+ pdev->subsystem_vendor != 0x10cf ||
+ pdev->subsystem_device != 0x165a)
+ return false;
+
+ root = of_find_node_by_path("/");
+ match = !of_property_read_string(root, "model", &model) &&
+ !strcmp(model, "IKKAKU");
+ of_node_put(root);
+ return match;
+}
+
+static int tg3_m3000_fw_byteorder(struct tg3 *tp, u32 *misc_ctrl_reg)
+{
+ struct pci_dev *pdev = tp->pdev;
+ u32 prodid, expected_prodid, normalized, readback;
+ int reg, err;
+
+ if (!tg3_is_m3000(pdev) ||
+ (swab32(*misc_ctrl_reg) >> 28) != ASIC_REV_USE_PROD_ID_REG ||
+ !(swab32(*misc_ctrl_reg) & MISC_HOST_CTRL_BYTE_SWAP))
+ return 0;
+
+ /* Standard PCI fields are normal; verify the swapped vendor registers. */
+ reg = TG3PCI_GEN2_PRODID_ASICREV;
+ err = pci_read_config_dword(pdev, reg, &prodid);
+ if (err)
+ goto config_error;
+ expected_prodid = swab32(prodid);
+ if ((expected_prodid >> 12) != ASIC_REV_5717) {
+ dev_err(&pdev->dev, "M3000: unexpected swapped product ID %08x\n",
+ prodid);
+ return -ENODEV;
+ }
+
+ normalized = (swab32(*misc_ctrl_reg) & MISC_HOST_CTRL_CHIPREV) |
+ tp->misc_host_ctrl;
+ reg = TG3PCI_MISC_HOST_CTRL;
+ err = pci_write_config_dword(pdev, reg, swab32(normalized));
+ if (err)
+ goto config_error;
+ err = pci_read_config_dword(pdev, reg, &readback);
+ if (err)
+ goto config_error;
+ if (readback != normalized) {
+ dev_err(&pdev->dev, "M3000: host-control readback mismatch %08x\n",
+ readback);
+ return -EIO;
+ }
+
+ reg = TG3PCI_GEN2_PRODID_ASICREV;
+ err = pci_read_config_dword(pdev, reg, &prodid);
+ if (err)
+ goto config_error;
+ if (prodid != expected_prodid) {
+ dev_err(&pdev->dev, "M3000: product-ID readback mismatch %08x\n",
+ prodid);
+ return -EIO;
+ }
+ *misc_ctrl_reg = readback;
+ dev_info(&pdev->dev, "M3000: normalized firmware register byte order\n");
+ return 0;
+
+config_error:
+ dev_err(&pdev->dev, "M3000: PCI config access at %#x failed (%d)\n",
+ reg, err);
+ return pcibios_err_to_errno(err);
+}
+
static int tg3_get_invariants(struct tg3 *tp, const struct pci_device_id *ent)
{
u32 misc_ctrl_reg;
@@ -16272,8 +16350,17 @@ static int tg3_get_invariants(struct tg3 *tp, const struct pci_device_id *ent)
* sure that indirect register accesses are enabled before
* the first operation.
*/
- pci_read_config_dword(tp->pdev, TG3PCI_MISC_HOST_CTRL,
- &misc_ctrl_reg);
+ err = pci_read_config_dword(tp->pdev, TG3PCI_MISC_HOST_CTRL,
+ &misc_ctrl_reg);
+ if (err && tg3_is_m3000(tp->pdev)) {
+ dev_err(&tp->pdev->dev, "PCI host-control read failed (%d)\n",
+ err);
+ return pcibios_err_to_errno(err);
+ }
+ err = tg3_m3000_fw_byteorder(tp, &misc_ctrl_reg);
+ if (err)
+ return err;
+
tp->misc_host_ctrl |= (misc_ctrl_reg &
MISC_HOST_CTRL_CHIPREV);
pci_write_config_dword(tp->pdev, TG3PCI_MISC_HOST_CTRL,
--
2.43.0