From a7d3315ea36e088d0b1cb50e59afc879c2a7e034 Mon Sep 17 00:00:00 2001 From: Daniel Isakov Date: Tue, 11 Aug 2026 09:42:32 -0700 Subject: [PATCH 1/2] Integrate HW-MGMT 7.0070.1013 patches Update the SONiC kernel patch stack on the 202605 branch from HW-MGMT 7.0060.1002 to 7.0070.1013. This branch was several versions behind, so the series covers 7.0060.1047, 7.0070.1004 and 7.0070.1013. Added patches: * 0060-leds-mlxreg-Provide-conversion-for-hardware-LED-colo.patch: report the correct LED colour in sysfs while the LED is still driven by hardware. * 0060-platform-mellanox-nvsw-bmc-Downstream-Add-protection.patch: only allow the current owner (CPU or BMC) to set the I2C mux, so I2C traffic is not cut off. * 0061-platform-mellanox-mlxreg-io-Increase-max-supported-a.patch: raise the maximum number of mlxreg-io attributes from 128 to 250 for newer platforms. * 0062-Add-support-for-NXP-s-PCF85053A-RTC-chip.patch: add a driver for the NXP PCF85053A I2C real-time clock. * 0063-reset-phy-using-polling-function-not-register-write.patch: reset the Realtek PHY by polling until the reset completes instead of writing the reset bit and continuing, which caused intermittent initialisation failures. * 0064-hwmon-pmbus-isl68137-Add-support-for-Renesas-RAA2289.patch: add the I2C device IDs for the Renesas RAA228942 and RAA228943 regulators. Upstream: 7c760db74c9f ("hwmon: (pmbus/isl68137) Add support for Renesas RAA228942 and RAA228943") * 8014-mlxsw-i2c-Downstream-Increase-I2C-timeout-for-HW-sem.patch: raise the I2C timeout from 5000 to 10000 ms. A firmware diagnostic operation can pause the device for around five seconds, which previously produced a spurious "HW semaphore is not released" error even though the device recovers normally. Added BMC patches (sonic-aspeed): * 0001-mctp-driver-net-Extend-MCTP-support.patch: add further MCTP transports together with tracepoints, transport statistics and error-injection test helpers. * 0002-mctp-aspeed-IRoT-Add-initial-support.patch: add an MCTP transport over the hardware mailbox on Aspeed AST27xx. * 0003-mctp-driver-net-Add-global-APIs-for-MCTP.patch: export the MCTP tracepoints so transport drivers can be built as loadable modules. * 0004-mctp-driver-net-Fix-MCTP-over-VDM-PCIe.patch: add the missing module metadata and use IS_ENABLED() so the VDM/PCIe transport works when built as a module. * 0005-mctp-driver-net-Fix-MCTP-over-USB.patch: account TX errors that cannot be attributed to a single destination EID separately, and add the f_mctp USB gadget function. * 0006-jtag-Add-JTAG-core-headers-GPIO-mux-and-locking-support.patch: add the JTAG core headers, GPIO mux control, single-open locking and the Aspeed internal JTAG driver. * 0007-JTAG-Aspeed-Fix-kernel-configuration.patch: source drivers/jtag/Kconfig from drivers/Kconfig and build drivers/jtag/ on CONFIG_JTAG, so the JTAG options are selectable and actually compiled. * 0008-arm64-dts-aspeed-spc6-bmc-ext-phram-env-tpm-i3c-pull.patch: in the Aspeed AST2700 BMC device tree, reserve a small phram region so the U-Boot environment survives warm boots, and fix TPM GPIO init, GPIO expander reset and I3C pull-ups. * 0009-arm64-dts-aspeed-split-SPC6-BMC-DTS-for-SONiC-and-Op.patch: build separate SONiC and OpenBMC device trees from the shared BMC DTS. OpenBMC boots its rootfs from SPI via phram; SONiC boots from eMMC and does not. Keeping the OpenBMC ramrofs reservation in the SONiC DTB overlaps the kernel load window and breaks early boot. * 0010-net-ftgmac100-hard-reset-mac-for-sgmii.patch: hard-reset the MAC on SGMII links to clear an RX stall. * 0011-arm64-dts-aspeed-spc6-bmc-fix-sgmii-phy-names.patch: fix the mac2 SGMII phy-names property. * 0012-usb-ehci-Add-Aspeed-AST2700-support.patch: select a 64-bit DMA mask for the AST2700 EHCI controller, which unlike earlier Aspeed SoCs is not limited to 32-bit DMA addressing. Upstream: 274f2232a94f ("usb: ehci: Add Aspeed AST2700 support") Updated patches: * 0046-platform-mellanox-nvsw-bmc-Add-system-control-and-mo.patch: update the BMC system control and monitoring driver for 7.0070.1013. Removed patches: * 0001-mlxbf-bootctl-Constify-struct-bin_attribute.patch: no longer part of the HW-MGMT patch set. The change is upstream as 8ba0e61861ab ("mlxbf-bootctl: Constify 'struct bin_attribute'"). Signed-off-by: Daniel Isakov --- ...-mctp-driver-net-Extend-MCTP-support.patch | 14812 ++++++++++++++++ ...ootctl-Constify-struct-bin_attribute.patch | 48 - ...mctp-aspeed-IRoT-Add-initial-support.patch | 1183 ++ ...-driver-net-Add-global-APIs-for-MCTP.patch | 45 + ...tp-driver-net-Fix-MCTP-over-VDM-PCIe.patch | 62 + ...05-mctp-driver-net-Fix-MCTP-over-USB.patch | 882 + ...headers-GPIO-mux-and-locking-support.patch | 1657 ++ ...JTAG-Aspeed-Fix-kernel-configuration.patch | 46 + ...-spc6-bmc-ext-phram-env-tpm-i3c-pull.patch | 160 + ...-split-SPC6-BMC-DTS-for-SONiC-and-Op.patch | 620 + ...t-ftgmac100-hard-reset-mac-for-sgmii.patch | 48 + ...-aspeed-spc6-bmc-fix-sgmii-phy-names.patch | 60 + ...-usb-ehci-Add-Aspeed-AST2700-support.patch | 79 + ...x-nvsw-bmc-Add-system-control-and-mo.patch | 5266 +++++- ...ide-conversion-for-hardware-LED-colo.patch | 84 + ...x-nvsw-bmc-Downstream-Add-protection.patch | 186 + ...x-mlxreg-io-Increase-max-supported-a.patch | 35 + ...support-for-NXP-s-PCF85053A-RTC-chip.patch | 752 + ...-polling-function-not-register-write.patch | 187 + ...8137-Add-support-for-Renesas-RAA2289.patch | 66 + ...ream-Increase-I2C-timeout-for-HW-sem.patch | 36 + patches-sonic/series | 20 +- 22 files changed, 25655 insertions(+), 679 deletions(-) create mode 100644 patches-sonic/0001-mctp-driver-net-Extend-MCTP-support.patch delete mode 100644 patches-sonic/0001-mlxbf-bootctl-Constify-struct-bin_attribute.patch create mode 100644 patches-sonic/0002-mctp-aspeed-IRoT-Add-initial-support.patch create mode 100644 patches-sonic/0003-mctp-driver-net-Add-global-APIs-for-MCTP.patch create mode 100644 patches-sonic/0004-mctp-driver-net-Fix-MCTP-over-VDM-PCIe.patch create mode 100644 patches-sonic/0005-mctp-driver-net-Fix-MCTP-over-USB.patch create mode 100644 patches-sonic/0006-jtag-Add-JTAG-core-headers-GPIO-mux-and-locking-support.patch create mode 100644 patches-sonic/0007-JTAG-Aspeed-Fix-kernel-configuration.patch create mode 100644 patches-sonic/0008-arm64-dts-aspeed-spc6-bmc-ext-phram-env-tpm-i3c-pull.patch create mode 100644 patches-sonic/0009-arm64-dts-aspeed-split-SPC6-BMC-DTS-for-SONiC-and-Op.patch create mode 100644 patches-sonic/0010-net-ftgmac100-hard-reset-mac-for-sgmii.patch create mode 100644 patches-sonic/0011-arm64-dts-aspeed-spc6-bmc-fix-sgmii-phy-names.patch create mode 100644 patches-sonic/0012-usb-ehci-Add-Aspeed-AST2700-support.patch create mode 100644 patches-sonic/0060-leds-mlxreg-Provide-conversion-for-hardware-LED-colo.patch create mode 100644 patches-sonic/0060-platform-mellanox-nvsw-bmc-Downstream-Add-protection.patch create mode 100644 patches-sonic/0061-platform-mellanox-mlxreg-io-Increase-max-supported-a.patch create mode 100644 patches-sonic/0062-Add-support-for-NXP-s-PCF85053A-RTC-chip.patch create mode 100644 patches-sonic/0063-reset-phy-using-polling-function-not-register-write.patch create mode 100644 patches-sonic/0064-hwmon-pmbus-isl68137-Add-support-for-Renesas-RAA2289.patch create mode 100644 patches-sonic/8014-mlxsw-i2c-Downstream-Increase-I2C-timeout-for-HW-sem.patch diff --git a/patches-sonic/0001-mctp-driver-net-Extend-MCTP-support.patch b/patches-sonic/0001-mctp-driver-net-Extend-MCTP-support.patch new file mode 100644 index 000000000..4481a28ce --- /dev/null +++ b/patches-sonic/0001-mctp-driver-net-Extend-MCTP-support.patch @@ -0,0 +1,14812 @@ +From 640f8eae0603b564883861f895b0155f16795a72 Mon Sep 17 00:00:00 2001 +From: Vadim Pasternak +Date: Sun, 10 May 2026 18:26:40 +0300 +Subject: [PATCH 6.12 1/5] mctp: driver: net: Extend MCTP support. + +Extend the in-kernel MCTP stack with additional transports and the +supporting infrastructure used by NVIDIA BMC platforms. + +This adds new MCTP transport backends, including MCTP over SPI and the +NVIDIA OP-TEE VRoT transport, together with their headers and the +associated Kconfig and Makefile entries. It also adds infrastructure +shared by the transports: MCTP tracepoints, transport statistics, and +error-injection helpers (I2C/SPI/USB/socket) used for testing, and +extends the core MCTP routing, device and socket code accordingly. + +Signed-off-by: Vadim Pasternak +--- + drivers/net/mctp/Kconfig | 34 + + drivers/net/mctp/Makefile | 7 + + drivers/net/mctp/glacier-spb-ap.h | 677 ++++++ + drivers/net/mctp/mctp-i2c-error-inject.c | 848 +++++++ + drivers/net/mctp/mctp-i2c-error-inject.h | 91 + + drivers/net/mctp/mctp-i2c-internal.h | 117 + + drivers/net/mctp/mctp-i2c.c | 671 +++++- + drivers/net/mctp/mctp-i3c.c | 298 ++- + drivers/net/mctp/mctp-serial.c | 5 +- + drivers/net/mctp/mctp-spi-error-inject.c | 596 +++++ + drivers/net/mctp/mctp-spi-error-inject.h | 86 + + drivers/net/mctp/mctp-spi-internal.h | 97 + + drivers/net/mctp/mctp-spi.c | 772 +++++++ + drivers/net/mctp/mctp-stats.h | 217 ++ + drivers/net/mctp/mctp-usb-error-inject.c | 1055 +++++++++ + drivers/net/mctp/mctp-usb-error-inject.h | 89 + + drivers/net/mctp/mctp-usb-internal.h | 98 + + drivers/net/mctp/mctp-usb.c | 1307 +++++++++++ + drivers/net/mctp/nvidia-optee-vrot.c | 1264 +++++++++++ + drivers/net/mctp/nvidia_irot_ast27xx_msg_ns.h | 151 ++ + drivers/soc/aspeed/aspeed-mctp.c | 27 +- + include/linux/i3c/mctp/i3c-mctp.h | 50 + + include/linux/usb/mctp-usb.h | 27 + + include/net/mctp.h | 120 +- + include/net/mctpdevice.h | 39 +- + include/trace/events/mctp.h | 455 ++++ + include/uapi/linux/if_link.h | 1 + + include/uapi/linux/mctp.h | 75 + + net/mctp/Makefile | 2 +- + net/mctp/af_mctp.c | 339 ++- + net/mctp/device.c | 73 +- + net/mctp/mctp-socket-error-inject.c | 221 ++ + net/mctp/mctp-socket-error-inject.h | 41 + + net/mctp/neigh.c | 16 + + net/mctp/route.c | 1982 +++++++++++++++-- + net/mctp/test/route-test.c | 612 +++-- + 36 files changed, 11971 insertions(+), 589 deletions(-) + create mode 100644 drivers/net/mctp/glacier-spb-ap.h + create mode 100644 drivers/net/mctp/mctp-i2c-error-inject.c + create mode 100644 drivers/net/mctp/mctp-i2c-error-inject.h + create mode 100644 drivers/net/mctp/mctp-i2c-internal.h + create mode 100644 drivers/net/mctp/mctp-spi-error-inject.c + create mode 100644 drivers/net/mctp/mctp-spi-error-inject.h + create mode 100644 drivers/net/mctp/mctp-spi-internal.h + create mode 100644 drivers/net/mctp/mctp-spi.c + create mode 100644 drivers/net/mctp/mctp-stats.h + create mode 100644 drivers/net/mctp/mctp-usb-error-inject.c + create mode 100644 drivers/net/mctp/mctp-usb-error-inject.h + create mode 100644 drivers/net/mctp/mctp-usb-internal.h + create mode 100644 drivers/net/mctp/mctp-usb.c + create mode 100644 drivers/net/mctp/nvidia-optee-vrot.c + create mode 100644 drivers/net/mctp/nvidia_irot_ast27xx_msg_ns.h + create mode 100644 include/linux/i3c/mctp/i3c-mctp.h + create mode 100644 include/linux/usb/mctp-usb.h + create mode 100644 net/mctp/mctp-socket-error-inject.c + create mode 100644 net/mctp/mctp-socket-error-inject.h + +diff --git a/drivers/net/mctp/Kconfig b/drivers/net/mctp/Kconfig +index 15860d6ac..fea55902e 100644 +--- a/drivers/net/mctp/Kconfig ++++ b/drivers/net/mctp/Kconfig +@@ -47,6 +47,40 @@ config MCTP_TRANSPORT_I3C + A MCTP protocol network device is created for each I3C bus + having a "mctp-controller" devicetree property. + ++config MCTP_TRANSPORT_SPI ++ tristate "MCTP Over SPI Transprt" ++ help ++ This is in Kernel mode Mctp Over Spi Driver. ++ ++config MCTP_TRANSPORT_USB ++ tristate "MCTP USB transport" ++ depends on USB ++ help ++ Provides a driver to access MCTP devices over USB transport, ++ defined by DMTF specification DSP0283. ++ ++ MCTP-over-USB interfaces are peer-to-peer, so each interface ++ represents a physical connection to one remote MCTP endpoint. ++ ++ The driver supports TX packet batching, which can be controlled ++ at runtime via sysfs (/sys/class/net/mctpusb*/tx_batching). ++ Batching reduces USB overhead by packing multiple packets into ++ a single USB bulk transfer (up to 512 bytes). ++ ++config MCTP_TRANSPORT_PCIE_VDM ++ tristate "MCTP PCIe VDM transport" ++ depends on PCI ++ select MCTP_FLOWS ++ help ++ Provides a driver to access MCTP devices over PCIe VDM transport, ++ from DMTF specification DSP0238. ++config NVIDIA_OPTEE_VROT ++ tristate "NVIDIA OPTEE VROT support" ++ depends on OPTEE ++ help ++ Enable support for NVIDIA OPTEE VRoT. Select this option if you want ++ to use the NVIDIA VRoT functionality running on OPTEE. ++ + endmenu + + endif +diff --git a/drivers/net/mctp/Makefile b/drivers/net/mctp/Makefile +index e1cb99ced..cfb127d78 100644 +--- a/drivers/net/mctp/Makefile ++++ b/drivers/net/mctp/Makefile +@@ -1,3 +1,10 @@ + obj-$(CONFIG_MCTP_SERIAL) += mctp-serial.o + obj-$(CONFIG_MCTP_TRANSPORT_I2C) += mctp-i2c.o ++obj-$(CONFIG_MCTP_TRANSPORT_I2C) += mctp-i2c-error-inject.o + obj-$(CONFIG_MCTP_TRANSPORT_I3C) += mctp-i3c.o ++obj-$(CONFIG_MCTP_TRANSPORT_SPI) += mctp-spi.o ++obj-$(CONFIG_MCTP_TRANSPORT_SPI) += mctp-spi-error-inject.o ++obj-$(CONFIG_MCTP_TRANSPORT_USB) += mctp-usb.o ++obj-$(CONFIG_MCTP_TRANSPORT_USB) += mctp-usb-error-inject.o ++obj-$(CONFIG_MCTP_TRANSPORT_PCIE_VDM) += mctp-pcie-vdm.o ++obj-$(CONFIG_NVIDIA_OPTEE_VROT) += nvidia-optee-vrot.o +diff --git a/drivers/net/mctp/glacier-spb-ap.h b/drivers/net/mctp/glacier-spb-ap.h +new file mode 100644 +index 000000000..5b74c7292 +--- /dev/null ++++ b/drivers/net/mctp/glacier-spb-ap.h +@@ -0,0 +1,677 @@ ++/* ++ * SPDX-FileCopyrightText: Copyright (c) NVIDIA CORPORATION & AFFILIATES. All rights reserved. ++ * SPDX-License-Identifier: Apache-2.0 ++ * ++ * Licensed under the Apache License, Version 2.0 (the "License"); ++ * you may not use this file except in compliance with the License. ++ * You may obtain a copy of the License at ++ * ++ * http://www.apache.org/licenses/LICENSE-2.0 ++ * ++ * Unless required by applicable law or agreed to in writing, software ++ * distributed under the License is distributed on an "AS IS" BASIS, ++ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. ++ * See the License for the specific language governing permissions and ++ * limitations under the License. ++ */ ++ ++#ifndef __GLACIER_SPB_AP_H__ ++#define __GLACIER_SPB_AP_H__ ++ ++#include ++#include ++#include ++#include ++ ++typedef enum { ++ CMD_SREG_W8 = 0x9, ++ CMD_SREG_W16, ++ CMD_SREG_W32, ++ ++ CMD_SREG_R8 = 0xD, ++ CMD_SREG_R16, ++ CMD_SREG_R32, ++ ++ CMD_MEM_W8 = 0x21, ++ CMD_MEM_W16, ++ CMD_MEM_W32, ++ ++ CMD_MEM_R8 = 0x25, ++ CMD_MEM_R16, ++ CMD_MEM_R32, ++ ++ CMD_RD_SNGL_FIFO8 = 0x28, ++ CMD_RD_SNGL_FIFO16 = 0x29, ++ CMD_RD_SNGL_FIFO32 = 0x2b, ++ ++ CMD_POLL_LOW = 0x2C, ++ CMD_POLL_HIGH = 0x2D, ++ CMD_POLL_ALL = 0x2F, ++ ++ CMD_MEM_BLK_W1 = 0x80, ++ ++ CMD_MEM_BLK_R1 = 0xA0, ++ CMD_RD_BLK_FIFO1 = 0xC0, ++ ++ CMD_RD_SNGL_FIFO8_FSR = 0x68, ++ CMD_RD_SNGL_FIFO16_FSR = 0x69, ++ CMD_RD_SNGL_FIFO32_FSR = 0x6B, ++ CMD_BLK_RD_FIFO_FSR = 0xE0, ++} spb_spi_cmds_t; ++ ++typedef enum { ++ SPI_CFG = 0x00, ++ SPI_STS = 0x04, ++ SPI_EC_STS = 0x08, ++ SPI_IEN = 0x0C, ++ // ... ++ SPI_SPIM2EC_MBX = 0x44, ++ SPI_EC2SPIM_MBX = 0x48, ++} spb_spi_regs_t; ++ ++typedef enum { ++ EC_ACK = 0x01000000, ++ AP_REQUEST_WRITE = 0x02000000, ++ AP_READY_TO_READ = 0x03000000, ++ AP_FINISHED_READ = 0x04000000, ++ AP_REQUEST_RESET = 0x05000000, ++ EC_MSG_AVAILABLE = 0x10000000, ++} spb_spi_mailbox_cmds_t; ++ ++#define mctp_prerr(fmt, ...) \ ++ printk(KERN_ERR pr_fmt(fmt), ##__VA_ARGS__) ++#define MCTP_ASSERT_RET(cond, ret, fmt, ...) \ ++ do { \ ++ if (!(cond)) { \ ++ mctp_prerr("at %s:%d " fmt, __func__, __LINE__, \ ++ ##__VA_ARGS__); \ ++ return (ret); \ ++ } \ ++ } while (0) ++ ++typedef enum { ++ SPB_AP_OK, ++ SPB_AP_MESSAGE_AVAILABLE, ++ SPB_AP_ERROR_INVALID_ARGUMENT, ++ SPB_AP_ERROR_TIMEOUT, ++ SPB_AP_ERROR_UNKNOWN, ++} SpbApStatus; ++ ++/// Callback required for libspb ++typedef struct { ++ int msgs_available; ++ uint32_t ec2spimb; ++ struct spi_device *spi; ++ wait_queue_head_t *gpio_intr_wq; ++ bool *gpio_intr_cond; ++ spinlock_t *gpio_intr_cond_lock; ++ int (*gpio_read_interrupt_pin)(void); ++ int (*on_mode_change)(bool quad, uint8_t waitCycles); ++ ++ // required for both interrupt and no interrupt ++ ssize_t (*spi_xfer)(struct spi_device *spi, const char *txbuf, size_t txlen, ++ const char *rxbuf, size_t rxlen, bool cs_change); ++} SpbAp; ++ ++#define POLL_SREG_TIMEOUT_MSECS 10000ULL ++#define POLL_INT_TIMEOUT_MSECS 1000ULL ++#define MAX_BYTES_PER_TRANSACTION 32 ++#define WAIT_CYCLES 0 ++#define TAR_CYCLES 1 // 1 in single, 4 in quad ++#define TAR_WAIT_CYCLES (WAIT_CYCLES + TAR_CYCLES) ++ ++#ifdef pr_fmt ++#undef pr_fmt ++#define pr_fmt(x) "spb-ap: " #x ++#endif ++ ++static inline uint64_t clock_msecs(void) ++{ ++ struct timespec64 ts; ++ ktime_get_ts64(&ts); ++ return (ts.tv_sec * 1000ULL + (ts.tv_nsec >> 20)); ++} ++ ++static const char *mailbox_str(uint32_t mb) ++{ ++ static char str[128]; ++ ++ memset(str, '\0', sizeof(str)); ++ switch (mb & 0x0F000000) { ++ case EC_ACK: ++ strcat(str, "EC_ACK"); ++ break; ++ case AP_REQUEST_WRITE: ++ strcat(str, "AP_REQUEST_WRITE"); ++ break; ++ case AP_READY_TO_READ: ++ strcat(str, "AP_READY_TO_READ"); ++ break; ++ case AP_FINISHED_READ: ++ strcat(str, "AP_FINISHED_READ"); ++ break; ++ case AP_REQUEST_RESET: ++ strcat(str, "AP_REQUEST_RESET"); ++ break; ++ default: ++ break; ++ } ++ ++ if (mb & 0xFF) { ++ sprintf(str + strlen(str), "LEN:%02x", mb & 0xFF); ++ } ++ ++ if (mb & EC_MSG_AVAILABLE) { ++ sprintf(str + strlen(str), " %x", mb); ++ ++ strcat(str, (strlen(str) > 0 ? "|EC_MSG_AVAILABLE" : ++ "EC_MSG_AVAILABLE")); ++ } ++ ++ if (strlen(str) == 0) { ++ sprintf(str, "UNKNOWN MAILBOX: %08x", mb); ++ } ++ return str; ++} ++ ++static inline uint32_t buf2dw(uint8_t *x) ++{ ++ return ntohl(*(uint32_t*)x); ++} ++ ++static inline uint16_t buf2w(uint8_t *x) ++{ ++ return ntohs(*(uint16_t*)x); ++} ++ ++static inline void dw2buf(uint32_t dw, uint8_t *buf) ++{ ++ *(uint32_t*)buf = htonl(dw); ++} ++ ++static inline void w2buf(uint16_t w, uint8_t *buf) ++{ ++ *(uint16_t*)buf = htons(w); ++} ++ ++static int wait_for_gpio_interrupt(wait_queue_head_t *gpio_intr_wq, bool *gpio_intr_cond, ++ spinlock_t *gpio_intr_cond_lock, long timeout) ++{ ++ if (wait_event_timeout(*gpio_intr_wq, *gpio_intr_cond, msecs_to_jiffies(timeout)) > 0) ++ { ++ unsigned long flags; ++ spin_lock_irqsave(gpio_intr_cond_lock, flags); ++ *gpio_intr_cond = false; ++ spin_unlock_irqrestore(gpio_intr_cond_lock, flags); ++ return 0; ++ } ++ else ++ { ++ return -1; ++ } ++} ++ ++static inline void spi_xfer(SpbAp *ap, int sendLen, uint8_t *xbuf, int recvLen, ++ uint8_t *rbuf, bool deassert) ++{ ++ ap->spi_xfer(ap->spi, xbuf, sendLen, rbuf, recvLen, deassert); ++} ++ ++static inline uint16_t sreg_write_8(SpbAp *ap, uint16_t addr, uint8_t value) ++{ ++ uint8_t buf[1 + 2 + 1 + TAR_WAIT_CYCLES + 2] = { 0 }; ++ ++ buf[0] = CMD_SREG_W8; ++ w2buf(addr, buf + 1); ++ buf[3] = value; ++ spi_xfer(ap, 1 + 2 + 1, buf, TAR_WAIT_CYCLES + 2, buf, true); ++ // return lower 16 bits of status ++ return (buf2w(buf + TAR_WAIT_CYCLES)); ++} ++ ++static inline uint16_t sreg_write_32(SpbAp *ap, uint16_t addr, uint32_t value) ++{ ++ uint8_t buf[1 + 2 + 4 + TAR_WAIT_CYCLES + 2] = { 0 }; ++ ++ buf[0] = CMD_SREG_W32; ++ w2buf(addr, buf + 1); ++ dw2buf(value, buf + 3); ++ spi_xfer(ap, 7, buf, TAR_WAIT_CYCLES + 2, buf, true); ++ // return lower 16 bits of status ++ return (buf2w(buf + TAR_WAIT_CYCLES)); ++} ++ ++static inline uint16_t sreg_read_8(SpbAp *ap, uint16_t addr, uint8_t *val) ++{ ++ uint8_t buf[1 + 2 + TAR_WAIT_CYCLES + 2 + 1] = { 0 }; ++ ++ buf[0] = CMD_SREG_R8; ++ w2buf(addr, buf + 1); ++ spi_xfer(ap, 3, buf, TAR_WAIT_CYCLES + 2 + 1, buf, true); ++ *val = buf[TAR_WAIT_CYCLES + 2]; ++ // return lower 16 bits of status ++ return (buf2w(buf + TAR_WAIT_CYCLES)); ++} ++ ++static inline uint16_t sreg_read_32(SpbAp *ap, uint16_t addr, uint32_t *val) ++{ ++ uint8_t buf[1 + 2 + TAR_WAIT_CYCLES + 2 + 4] = { 0 }; ++ ++ buf[0] = CMD_SREG_R32; ++ w2buf(addr, buf + 1); ++ spi_xfer(ap, 3, buf, TAR_WAIT_CYCLES + 2 + 4, buf, true); ++ *val = buf2dw(buf + TAR_WAIT_CYCLES + 2); ++ // return lower 16 bits of status ++ return (buf2w(buf + TAR_WAIT_CYCLES)); ++} ++ ++static inline uint32_t cmd_poll_all(SpbAp *ap) ++{ ++ uint8_t buf[1 + TAR_CYCLES + 4] = { 0 }; ++ ++ buf[0] = CMD_POLL_ALL; ++ spi_xfer(ap, 1, buf, TAR_CYCLES + 4, buf, true); ++ ++ return (buf2dw(buf + TAR_CYCLES)); ++} ++ ++// Read RX_FIFO_EMPTY ++static SpbApStatus wait_for_tx_fifo_not_empty(SpbAp *ap) ++{ ++ uint64_t start = clock_msecs(); ++ ++ while ((cmd_poll_all(ap) & 0x400) != 0) { ++ if (clock_msecs() - start > POLL_SREG_TIMEOUT_MSECS) { ++ return (SPB_AP_ERROR_TIMEOUT); ++ } ++ } ++ return (SPB_AP_OK); ++} ++ ++static inline uint16_t mailbox_write(SpbAp *ap, uint32_t v) ++{ ++ uint16_t status = sreg_write_32(ap, SPI_SPIM2EC_MBX, v); ++ ++ return (status); ++} ++ ++static inline uint32_t clear_memory_write_done(SpbAp *ap) ++{ ++ // MemoryWriteDone bit 0, write to clear ++ return (sreg_write_8(ap, SPI_STS, 0x01)); ++} ++ ++static inline uint32_t clear_memory_read_done(SpbAp *ap) ++{ ++ // MemoryReadDone bit 1, write to clear ++ return (sreg_write_8(ap, SPI_STS, 0x02)); ++} ++ ++// Polling procedures with timeouts ++static inline SpbApStatus wait_for_memory_write_busy_and_rx_fifo_empty(SpbAp *ap) ++{ ++ uint64_t start = clock_msecs(); ++ ++ // RxFifoEmpty Bit 8, MemoryWriteBusy Bit 3 ++ while ((cmd_poll_all(ap) & 0x108) == 0) { ++ if (clock_msecs() - start > POLL_SREG_TIMEOUT_MSECS) ++ return (SPB_AP_ERROR_TIMEOUT); ++ } ++ ++ return (SPB_AP_OK); ++} ++ ++static inline SpbApStatus spb_ap_check_ack(SpbAp *ap) ++{ ++ uint32_t sts = 0; ++ uint32_t mb = 1; ++ SpbApStatus ret = SPB_AP_OK; ++ ++ // check mailbox and reset interrupt ++ sts = sreg_read_32(ap, SPI_EC2SPIM_MBX, &mb); ++ ++ /* ++ * expect ACK but only MSG_AVAILABLE is set. The workaround was made to ++ * mark ACK is set. ++ */ ++ ++ if (mb & EC_MSG_AVAILABLE) { ++ ap->msgs_available++; ++ } ++ ++ ap->ec2spimb = mb & ~EC_MSG_AVAILABLE; ++ return (ret); ++} ++ ++static inline SpbApStatus spb_ap_wait_for_intr(SpbAp *ap, int timeout_ms, bool polling) ++{ ++ int status = 0; ++ ++ status = wait_for_gpio_interrupt(ap->gpio_intr_wq, ap->gpio_intr_cond, ap->gpio_intr_cond_lock, timeout_ms); ++ if (status < 0) ++ return (SPB_AP_ERROR_TIMEOUT); ++ ++ return (spb_ap_check_ack(ap)); ++} ++ ++static inline SpbApStatus wait_for_ack(SpbAp *ap) ++{ ++ SpbApStatus status = SPB_AP_OK; ++ uint64_t start = clock_msecs(); ++ ++ while (ap->ec2spimb != EC_ACK) { ++ /* Check for interrupts */ ++ if (clock_msecs() - start > POLL_INT_TIMEOUT_MSECS) { ++ if (ap->msgs_available > 0) ++ break; ++ else ++ return (SPB_AP_ERROR_TIMEOUT); ++ } ++ ++ spb_ap_wait_for_intr(ap, POLL_INT_TIMEOUT_MSECS, true); ++ } ++ ++ ap->ec2spimb = 0; ++ return (status); ++} ++ ++static inline SpbApStatus wait_for_length(SpbAp *ap, uint32_t *bytes) ++{ ++ SpbApStatus status = SPB_AP_OK; ++ uint64_t start = clock_msecs(); ++ ++ while ((ap->ec2spimb & 0xFF) == 0) { ++ if (clock_msecs() - start > POLL_INT_TIMEOUT_MSECS) ++ return (SPB_AP_ERROR_TIMEOUT); ++ ++ /* Check for interrupts */ ++ spb_ap_wait_for_intr(ap, POLL_INT_TIMEOUT_MSECS, true); ++ } ++ ++ *bytes = ap->ec2spimb & 0xFF; ++ ap->ec2spimb = 0; ++ return (status); ++} ++ ++// Write payload with maxiumum of 32 bytes per transfer ++static inline SpbApStatus posted_write(SpbAp *ap, uint16_t offset, int len, uint8_t *payload) ++{ ++ uint8_t buf[128] = { 0 }; ++ int off = 0; ++ SpbApStatus status; ++ ++ while (len > 0) { ++ int bytes = len > MAX_BYTES_PER_TRANSACTION ? ++ MAX_BYTES_PER_TRANSACTION : ++ len; ++ if (bytes >= 4) { ++ bytes &= 0xFFFFFFFC; ++ buf[0] = CMD_MEM_BLK_W1 + bytes / 4 - 1; ++ w2buf(offset + off, buf + 1); ++ // byteswap for DWORD ++ for (int ii = 0; ii < bytes; ii += 4) ++ *(uint32_t*)(buf + ii + 3) = __builtin_bswap32(*(uint32_t*)(payload + off + ii)); ++ ++ status = wait_for_memory_write_busy_and_rx_fifo_empty( ++ ap); ++ MCTP_ASSERT_RET( ++ status == SPB_AP_OK, status, ++ "wait_for_memory_write_busy_and_rx_fifo_empty failed"); ++ ++ spi_xfer(ap, 3 + bytes, buf, 0, buf, true); ++ clear_memory_write_done(ap); ++ } else { ++ for (int ii = 0; ii < bytes; ii++) { ++ buf[0] = CMD_MEM_W8; ++ w2buf(offset + off + ii, buf + 1); ++ buf[3] = payload[off + ii]; ++ ++ status = ++ wait_for_memory_write_busy_and_rx_fifo_empty( ++ ap); ++ MCTP_ASSERT_RET( ++ status == SPB_AP_OK, status, ++ "wait_for_memory_write_busy_and_rx_fifo_empty failed"); ++ ++ spi_xfer(ap, 3 + 1, buf, 0, buf, true); ++ clear_memory_write_done(ap); ++ } ++ } ++ off += bytes; ++ len -= bytes; ++ } ++ ++ return SPB_AP_OK; ++} ++ ++static SpbApStatus posted_read_helper(SpbAp *ap, uint8_t cmd, uint8_t cmd2, ++ uint16_t addr, int bytes, uint8_t *buf) ++{ ++ SpbApStatus status; ++ // Send the post read command ++ buf[0] = cmd; ++ w2buf(addr, buf + 1); ++ spi_xfer(ap, 3, buf, 0, buf, true); ++ ++ // check ok to read ++ status = wait_for_tx_fifo_not_empty(ap); ++ MCTP_ASSERT_RET(status == SPB_AP_OK, status, ++ "wait_for_tx_fifo_not_empty failed: %d", status); ++ ++ // Initiate FIFO READ ++ buf[0] = cmd2; ++ spi_xfer(ap, 1, buf, TAR_CYCLES + 2, buf, false); ++ ++ // Check MemoryReadDone ++ if ((buf[TAR_CYCLES + 1] & 0x02) == 0) { ++ // Poll status ++ // TODO: this section is not tested ++ uint64_t start = clock_msecs(); ++ do { ++ // read status until MemoryReadDone is set ++ buf[0] = 0; ++ buf[1] = 0; ++ spi_xfer(ap, 0, buf, 2, buf, false); ++ if (clock_msecs() - start > POLL_INT_TIMEOUT_MSECS) { ++ return SPB_AP_ERROR_TIMEOUT; ++ } ++ } while ((buf[1] & 0x02) == 0); ++ } ++ ++ // Read actual data ++ spi_xfer(ap, 0, buf, bytes, buf, true); ++ clear_memory_read_done(ap); ++ ++ return (SPB_AP_OK); ++} ++ ++static inline SpbApStatus posted_read(SpbAp *ap, int offset, int len, uint8_t *payload) ++{ ++ uint8_t buf[128] = { 0 }; ++ int off = 0; ++ SpbApStatus status = 0; ++ int reg_offset = 0; ++ ++ while (len > 0) { ++ int bytes = len > MAX_BYTES_PER_TRANSACTION ? ++ MAX_BYTES_PER_TRANSACTION : ++ len; ++ if (bytes >= 4) { ++ bytes &= 0xFFFFFFFC; ++ reg_offset = (bytes / 4) - 1; ++ ++ status = posted_read_helper( ++ ap, CMD_MEM_BLK_R1 + reg_offset, ++ CMD_BLK_RD_FIFO_FSR + reg_offset, offset + off, ++ bytes, buf); ++ MCTP_ASSERT_RET(status == SPB_AP_OK, status, ++ "posted_read_helper failed"); ++ ++ for (int ii = 0; ii < bytes; ii += 4) ++ *(uint32_t*)(payload + off + ii) = __builtin_bswap32(*(uint32_t*)(buf + ii)); ++ } else { ++ for (int ii = 0; ii < bytes; ii++) { ++ status = posted_read_helper( ++ ap, CMD_MEM_R8, CMD_RD_SNGL_FIFO8_FSR, ++ offset + off + ii, 4, ++ buf); // TODO: why is this 4 ? ++ MCTP_ASSERT_RET(status == SPB_AP_OK, status, ++ "posted_read_helper failed"); ++ // TODO: offset 3 because we have to read 4 bytes ++ payload[off + ii] = buf[3]; ++ } ++ } ++ off += bytes; ++ len -= bytes; ++ } ++ ++ return (SPB_AP_OK); ++} ++ ++// --- public ++ ++static inline SpbApStatus spb_ap_reset(SpbAp *ap) ++{ ++ // reset globals ++ SpbApStatus status; ++ ++ ap->msgs_available = 0; ++ ap->ec2spimb = 0; ++ ++ // initiate reset ++ mailbox_write(ap, AP_REQUEST_RESET); ++ // wait for ack ++ status = wait_for_ack(ap); ++ MCTP_ASSERT_RET(status == SPB_AP_OK, status, "wait_for_ack failed."); ++ return (SPB_AP_OK); ++} ++ ++static inline SpbApStatus spb_ap_initialize(SpbAp *ap) ++{ ++ MCTP_ASSERT_RET(ap != NULL, SPB_AP_ERROR_INVALID_ARGUMENT, ++ "ap is NULL."); ++ MCTP_ASSERT_RET(ap->spi_xfer != NULL, SPB_AP_ERROR_INVALID_ARGUMENT, ++ "spi_xfer is NULL"); ++ ++ ap->msgs_available = 0; ++ ap->ec2spimb = 0x00000000U; ++ ++ return (spb_ap_reset(ap)); ++} ++ ++static inline SpbApStatus spb_ap_set_cfg(SpbAp *ap, bool quad, uint8_t waitCycles) ++{ ++ SpbApStatus status = SPB_AP_OK; ++ uint8_t cmd[] = { 0x03, 0x00, waitCycles, ++ (uint8_t)(quad ? 0x01 : 0x00) }; ++ ++ // send set cfg ++ mailbox_write(ap, AP_REQUEST_WRITE); ++ ++ status = wait_for_ack(ap); ++ MCTP_ASSERT_RET(status == SPB_AP_OK, status, "wait_for_ack failed."); ++ ++ status = posted_write(ap, 0, sizeof(cmd), cmd); ++ MCTP_ASSERT_RET(status == SPB_AP_OK, status, "posted_write failed."); ++ ++ mailbox_write(ap, sizeof(cmd)); ++ ++ // mode has changed ++ if (ap->on_mode_change(quad, waitCycles)) { ++ status = wait_for_ack(ap); ++ MCTP_ASSERT_RET(status == SPB_AP_OK, status, ++ "wait_for_ack failed"); ++ return (SPB_AP_OK); ++ } ++ return (SPB_AP_ERROR_UNKNOWN); ++} ++ ++static inline SpbApStatus spb_ap_shutdown(SpbAp *ap) ++{ ++ // leave Glacier in single spi mode ++ return (spb_ap_set_cfg(ap, false, 0)); ++} ++ ++static inline SpbApStatus spb_ap_on_interrupt(SpbAp *ap) ++{ ++ uint32_t sts = 0; ++ uint32_t mb = 1; ++ SpbApStatus ret = SPB_AP_OK; ++ ++ // check mailbox and reset interrupt ++ sts = sreg_read_32(ap, SPI_EC2SPIM_MBX, &mb); ++ ++ if (mb & EC_MSG_AVAILABLE) { ++ ret = SPB_AP_MESSAGE_AVAILABLE; ++ ap->msgs_available++; ++ } ++ ++ ap->ec2spimb = mb & ~EC_MSG_AVAILABLE; ++ return (ret); ++} ++ ++static inline SpbApStatus spb_ap_send(SpbAp *ap, int len, void *buf) ++{ ++ SpbApStatus status; ++ ++ mailbox_write(ap, AP_REQUEST_WRITE); ++ status = wait_for_ack(ap); ++ MCTP_ASSERT_RET(status == SPB_AP_OK, status, "wait_for_ack failed."); ++ ++ status = posted_write(ap, 0, len, (uint8_t *)(buf)); ++ MCTP_ASSERT_RET(status == SPB_AP_OK, status, "posted_write failed."); ++ mailbox_write(ap, len); ++ ++ status = wait_for_ack(ap); ++ MCTP_ASSERT_RET(status == SPB_AP_OK, status, "wait_for_ack failed."); ++ ++ return (SPB_AP_OK); ++} ++ ++static inline SpbApStatus spb_ap_recv(SpbAp *ap, int len, void *buf) ++{ ++ uint32_t bytes; ++ SpbApStatus status; ++ ++ mailbox_write(ap, AP_READY_TO_READ); ++ ++ ap->msgs_available--; ++ status = wait_for_length(ap, &bytes); ++ MCTP_ASSERT_RET(status == SPB_AP_OK, status, "wait_for_length failed."); ++ MCTP_ASSERT_RET(bytes <= (uint32_t)len, status, ++ "Data cannot fit into the buffer."); ++ ++ status = posted_read(ap, 0x8000, bytes, (uint8_t *)(buf)); ++ MCTP_ASSERT_RET(status == SPB_AP_OK, status, "posted_read failed."); ++ mailbox_write(ap, AP_FINISHED_READ); ++ ++ status = wait_for_ack(ap); ++ MCTP_ASSERT_RET(status == SPB_AP_OK, status, "wait_for_ack failed."); ++ ++ return (SPB_AP_OK); ++} ++ ++static inline int spb_ap_msgs_available(SpbAp *ap) ++{ ++ return (ap->msgs_available); ++} ++ ++static inline const char *spb_ap_strstatus(SpbApStatus status) ++{ ++ switch (status) { ++ case SPB_AP_OK: ++ return "OK"; ++ case SPB_AP_MESSAGE_AVAILABLE: ++ return "Message available"; ++ case SPB_AP_ERROR_INVALID_ARGUMENT: ++ return "Invalid argument"; ++ case SPB_AP_ERROR_TIMEOUT: ++ return "Timeout"; ++ case SPB_AP_ERROR_UNKNOWN: ++ return "Unknown"; ++ } ++ ++ return ("Invalid status code."); ++} ++ ++#endif // __GLACIER_SPB_AP_H__ +\ No newline at end of file +diff --git a/drivers/net/mctp/mctp-i2c-error-inject.c b/drivers/net/mctp/mctp-i2c-error-inject.c +new file mode 100644 +index 000000000..c10040f87 +--- /dev/null ++++ b/drivers/net/mctp/mctp-i2c-error-inject.c +@@ -0,0 +1,848 @@ ++// SPDX-License-Identifier: GPL-2.0 ++/* ++ * MCTP I2C Error Injection Implementation ++ * ++ * Copyright (c) 2024 NVIDIA CORPORATION. All rights reserved. ++ * ++ * Provides debugfs-based error injection for testing MCTP error queue ++ * functionality over I2C binding. ++ * ++ * Interface unified with USB error injection where applicable. ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include "mctp-i2c-internal.h" ++ ++/* Global debugfs root for all MCTP I2C error injection */ ++static struct dentry *mctp_i2c_error_inject_root; ++ ++/* Check if error should be injected based on mode and rate */ ++static bool mctp_should_inject_error(struct mctp_i2c_error_inject *ei, u32 rate, u32 *count) ++{ ++ bool inject = false; ++ ++ spin_lock_bh(&ei->lock); ++ ++ switch (ei->mode) { ++ case MCTP_ERR_MODE_ALWAYS: ++ inject = true; ++ break; ++ ++ case MCTP_ERR_MODE_RANDOM: ++ /* Random injection based on rate percentage */ ++ inject = (prandom_u32_state(&ei->rng) % 100) < rate; ++ break; ++ ++ case MCTP_ERR_MODE_COUNT: ++ /* Inject for count packets, then stop */ ++ if (*count > 0) { ++ (*count)--; ++ inject = true; ++ } ++ break; ++ } ++ ++ spin_unlock_bh(&ei->lock); ++ ++ return inject; ++} ++ ++/* Check if packet matches EID filter */ ++static bool mctp_error_inject_match_filter(struct mctp_i2c_error_inject *ei, ++ struct sk_buff *skb) ++{ ++ struct mctp_hdr *mh; ++ ++ if (!ei->eid_filter.enabled) ++ return true; ++ ++ mh = mctp_hdr(skb); ++ if (!mh) ++ return false; ++ ++ /* Check source EID */ ++ if (ei->eid_filter.src_eid != 0 && ++ ei->eid_filter.src_eid != mh->src) ++ return false; ++ ++ /* Check dest EID */ ++ if (ei->eid_filter.dest_eid != 0 && ++ ei->eid_filter.dest_eid != mh->dest) ++ return false; ++ ++ return true; ++} ++ ++/** ++ * mctp_i2c_error_inject_tx - Inject TX error ++ * @midev: MCTP I2C device ++ * @skb: Packet being transmitted ++ * ++ * Returns: 0 for normal operation, negative error code to simulate failure ++ * ++ * Called before __i2c_transfer() to potentially inject a TX error. ++ * I2C transfer is synchronous, so there's only one injection point. ++ */ ++int mctp_i2c_error_inject_tx(struct mctp_i2c_dev *midev, struct sk_buff *skb) ++{ ++ struct mctp_i2c_error_inject *ei = &midev->error_inject; ++ struct mctp_hdr *mh; ++ ++ /* Early exit if TX error injection is disabled or code not set */ ++ if (!ei->enable_tx || ei->i2c_tx_error_code == 0) ++ return 0; ++ ++ if (!mctp_error_inject_match_filter(ei, skb)) ++ return 0; ++ ++ if (!mctp_should_inject_error(ei, ei->i2c_tx_error_rate, ++ &ei->i2c_tx_inject_count)) ++ return 0; ++ ++ /* Inject delay if configured */ ++ if (ei->delay_ms > 0) ++ msleep(ei->delay_ms); ++ ++ /* Update statistics */ ++ spin_lock_bh(&ei->lock); ++ ei->i2c_tx_errors_injected++; ++ ei->total_errors_injected++; ++ ei->total_packets_processed++; ++ spin_unlock_bh(&ei->lock); ++ ++ mh = mctp_hdr(skb); ++ ++ netdev_dbg(midev->ndev, ++ "ERROR INJECTION: TX - error=%d, src_eid=%u, dest_eid=%u, " ++ "total_injected=%u, mode=%s\n", ++ ei->i2c_tx_error_code, mh->src, mh->dest, ++ ei->i2c_tx_errors_injected, ++ ei->mode == MCTP_ERR_MODE_ALWAYS ? "always" : ++ ei->mode == MCTP_ERR_MODE_RANDOM ? "random" : "count"); ++ ++ return -ei->i2c_tx_error_code; ++} ++EXPORT_SYMBOL_GPL(mctp_i2c_error_inject_tx); ++ ++/** ++ * mctp_i2c_error_inject_fragment - Inject fragment errors ++ * @midev: MCTP I2C device ++ * @skb: Packet being processed ++ * ++ * Returns: 0 to pass packet through, 1 to drop packet ++ * ++ * This function can: ++ * 1. Drop fragments (simulates fragment loss) ++ * 2. Corrupt sequence numbers (simulates protocol errors) ++ * 3. Clear SOM flag (simulates missing start-of-message) ++ */ ++int mctp_i2c_error_inject_fragment(struct mctp_i2c_dev *midev, struct sk_buff *skb) ++{ ++ struct mctp_i2c_error_inject *ei = &midev->error_inject; ++ struct mctp_hdr *mh; ++ u8 flags; ++ u8 seq; ++ ++ /* Early exit if RX error injection disabled or no fragment injection enabled */ ++ if (!ei->enable_rx || (!ei->enable_fragment_drop && !ei->enable_seq_corrupt && !ei->enable_som_clear)) ++ return 0; ++ ++ mh = mctp_hdr(skb); ++ if (!mh) ++ return 0; ++ ++ flags = mh->flags_seq_tag & (MCTP_HDR_FLAG_SOM | MCTP_HDR_FLAG_EOM); ++ seq = (mh->flags_seq_tag >> MCTP_HDR_SEQ_SHIFT) & MCTP_HDR_SEQ_MASK; ++ ++ /* Single-packet message (SOM+EOM) - pass through (not a fragment) */ ++ if (flags == (MCTP_HDR_FLAG_SOM | MCTP_HDR_FLAG_EOM)) ++ return 0; ++ ++ if (!mctp_error_inject_match_filter(ei, skb)) ++ return 0; ++ ++ /* ===== Injection Type 1: Clear SOM bit (first fragment) ===== */ ++ if (ei->enable_som_clear && (flags & MCTP_HDR_FLAG_SOM)) { ++ mh->flags_seq_tag &= ~MCTP_HDR_FLAG_SOM; ++ ++ spin_lock_bh(&ei->lock); ++ ei->som_clears++; ++ ei->total_errors_injected++; ++ spin_unlock_bh(&ei->lock); ++ ++ netdev_dbg(midev->ndev, ++ "ERROR INJECTION: SOM bit CLEARED (first fragment, src=%u, dest=%u, seq=%u)\n", ++ mh->src, mh->dest, seq); ++ ++ return 0; /* Pass through with corrupted SOM */ ++ } ++ ++ /* ===== Injection Type 2: Corrupt sequence number (middle/end fragments) ===== */ ++ if (ei->enable_seq_corrupt && !(flags & MCTP_HDR_FLAG_SOM)) { ++ u8 old_seq = seq; ++ u8 corrupted_seq = (seq + 1) & MCTP_HDR_SEQ_MASK; ++ ++ mh->flags_seq_tag &= ~(MCTP_HDR_SEQ_MASK << MCTP_HDR_SEQ_SHIFT); ++ mh->flags_seq_tag |= (corrupted_seq << MCTP_HDR_SEQ_SHIFT); ++ ++ spin_lock_bh(&ei->lock); ++ ei->seq_corruptions++; ++ ei->total_errors_injected++; ++ spin_unlock_bh(&ei->lock); ++ ++ netdev_dbg(midev->ndev, ++ "ERROR INJECTION: Sequence CORRUPTED (2nd+ fragment, src=%u, dest=%u, %u -> %u)\n", ++ mh->src, mh->dest, old_seq, corrupted_seq); ++ ++ return 0; /* Pass through with corrupted sequence */ ++ } ++ ++ /* ===== Injection Type 3: Drop fragment (2nd+ fragments) ===== */ ++ if (ei->enable_fragment_drop && !(flags & MCTP_HDR_FLAG_SOM)) { ++ spin_lock_bh(&ei->lock); ++ ei->fragments_dropped++; ++ ei->total_errors_injected++; ++ spin_unlock_bh(&ei->lock); ++ ++ netdev_dbg(midev->ndev, ++ "ERROR INJECTION: Fragment DROPPED (2nd+ fragment, src=%u, dest=%u, seq=%u)\n", ++ mh->src, mh->dest, seq); ++ ++ return 1; /* Drop this fragment */ ++ } ++ ++ return 0; /* Pass through normally */ ++} ++EXPORT_SYMBOL_GPL(mctp_i2c_error_inject_fragment); ++ ++/* ===== Debugfs Interface - Unified with USB ===== */ ++ ++static struct mctp_i2c_dev *mctp_i2c_from_file(struct file *file) ++{ ++ return file->f_inode->i_private; ++} ++ ++/* enable_tx attribute */ ++static ssize_t mctp_debugfs_enable_tx_read(struct file *file, char __user *userbuf, ++ size_t count, loff_t *ppos) ++{ ++ struct mctp_i2c_dev *midev = mctp_i2c_from_file(file); ++ char buf[8]; ++ int len; ++ ++ len = snprintf(buf, sizeof(buf), "%d\n", midev->error_inject.enable_tx); ++ return simple_read_from_buffer(userbuf, count, ppos, buf, len); ++} ++ ++static ssize_t mctp_debugfs_enable_tx_write(struct file *file, const char __user *userbuf, ++ size_t count, loff_t *ppos) ++{ ++ struct mctp_i2c_dev *midev = mctp_i2c_from_file(file); ++ char buf[8]; ++ bool enable; ++ int rc; ++ ++ if (count >= sizeof(buf)) ++ return -EINVAL; ++ ++ if (copy_from_user(buf, userbuf, count)) ++ return -EFAULT; ++ ++ buf[count] = '\0'; ++ rc = kstrtobool(buf, &enable); ++ if (rc) ++ return rc; ++ ++ spin_lock_bh(&midev->error_inject.lock); ++ midev->error_inject.enable_tx = enable; ++ spin_unlock_bh(&midev->error_inject.lock); ++ ++ netdev_dbg(midev->ndev, "TX error injection %s\n", ++ enable ? "enabled" : "disabled"); ++ ++ return count; ++} ++ ++static const struct file_operations mctp_debugfs_enable_tx_fops = { ++ .owner = THIS_MODULE, ++ .read = mctp_debugfs_enable_tx_read, ++ .write = mctp_debugfs_enable_tx_write, ++ .open = simple_open, ++ .llseek = default_llseek, ++}; ++ ++/* enable_rx attribute */ ++static ssize_t mctp_debugfs_enable_rx_read(struct file *file, char __user *userbuf, ++ size_t count, loff_t *ppos) ++{ ++ struct mctp_i2c_dev *midev = mctp_i2c_from_file(file); ++ char buf[8]; ++ int len; ++ ++ len = snprintf(buf, sizeof(buf), "%d\n", midev->error_inject.enable_rx); ++ return simple_read_from_buffer(userbuf, count, ppos, buf, len); ++} ++ ++static ssize_t mctp_debugfs_enable_rx_write(struct file *file, const char __user *userbuf, ++ size_t count, loff_t *ppos) ++{ ++ struct mctp_i2c_dev *midev = mctp_i2c_from_file(file); ++ char buf[8]; ++ bool enable; ++ int rc; ++ ++ if (count >= sizeof(buf)) ++ return -EINVAL; ++ ++ if (copy_from_user(buf, userbuf, count)) ++ return -EFAULT; ++ ++ buf[count] = '\0'; ++ rc = kstrtobool(buf, &enable); ++ if (rc) ++ return rc; ++ ++ spin_lock_bh(&midev->error_inject.lock); ++ midev->error_inject.enable_rx = enable; ++ spin_unlock_bh(&midev->error_inject.lock); ++ ++ netdev_dbg(midev->ndev, "RX error injection %s\n", ++ enable ? "enabled" : "disabled"); ++ ++ return count; ++} ++ ++static const struct file_operations mctp_debugfs_enable_rx_fops = { ++ .owner = THIS_MODULE, ++ .read = mctp_debugfs_enable_rx_read, ++ .write = mctp_debugfs_enable_rx_write, ++ .open = simple_open, ++ .llseek = default_llseek, ++}; ++ ++/* mode attribute */ ++static ssize_t mctp_debugfs_mode_read(struct file *file, char __user *userbuf, ++ size_t count, loff_t *ppos) ++{ ++ struct mctp_i2c_dev *midev = mctp_i2c_from_file(file); ++ const char *mode_str; ++ char buf[16]; ++ int len; ++ ++ switch (midev->error_inject.mode) { ++ case MCTP_ERR_MODE_ALWAYS: ++ mode_str = "always\n"; ++ break; ++ case MCTP_ERR_MODE_RANDOM: ++ mode_str = "random\n"; ++ break; ++ case MCTP_ERR_MODE_COUNT: ++ mode_str = "count\n"; ++ break; ++ default: ++ mode_str = "unknown\n"; ++ break; ++ } ++ ++ len = snprintf(buf, sizeof(buf), "%s", mode_str); ++ return simple_read_from_buffer(userbuf, count, ppos, buf, len); ++} ++ ++static ssize_t mctp_debugfs_mode_write(struct file *file, const char __user *userbuf, ++ size_t count, loff_t *ppos) ++{ ++ struct mctp_i2c_dev *midev = mctp_i2c_from_file(file); ++ char buf[16]; ++ ++ if (count >= sizeof(buf)) ++ return -EINVAL; ++ ++ if (copy_from_user(buf, userbuf, count)) ++ return -EFAULT; ++ ++ buf[count] = '\0'; ++ ++ spin_lock_bh(&midev->error_inject.lock); ++ ++ if (strncmp(buf, "always", 6) == 0) ++ midev->error_inject.mode = MCTP_ERR_MODE_ALWAYS; ++ else if (strncmp(buf, "random", 6) == 0) ++ midev->error_inject.mode = MCTP_ERR_MODE_RANDOM; ++ else if (strncmp(buf, "count", 5) == 0) ++ midev->error_inject.mode = MCTP_ERR_MODE_COUNT; ++ else { ++ spin_unlock_bh(&midev->error_inject.lock); ++ return -EINVAL; ++ } ++ ++ spin_unlock_bh(&midev->error_inject.lock); ++ ++ return count; ++} ++ ++static const struct file_operations mctp_debugfs_mode_fops = { ++ .owner = THIS_MODULE, ++ .read = mctp_debugfs_mode_read, ++ .write = mctp_debugfs_mode_write, ++ .open = simple_open, ++ .llseek = default_llseek, ++}; ++ ++/* Generic u32 read/write helpers */ ++#define MCTP_DEBUGFS_U32_DEFINE(name, field) \ ++static ssize_t mctp_debugfs_##name##_read(struct file *file, char __user *userbuf, \ ++ size_t count, loff_t *ppos) \ ++{ \ ++ struct mctp_i2c_dev *midev = mctp_i2c_from_file(file); \ ++ char buf[32]; \ ++ int len; \ ++ \ ++ len = snprintf(buf, sizeof(buf), "%u\n", midev->error_inject.field); \ ++ return simple_read_from_buffer(userbuf, count, ppos, buf, len); \ ++} \ ++\ ++static ssize_t mctp_debugfs_##name##_write(struct file *file, const char __user *userbuf, \ ++ size_t count, loff_t *ppos) \ ++{ \ ++ struct mctp_i2c_dev *midev = mctp_i2c_from_file(file); \ ++ char buf[32]; \ ++ u32 val; \ ++ int rc; \ ++ \ ++ if (count >= sizeof(buf)) \ ++ return -EINVAL; \ ++ \ ++ if (copy_from_user(buf, userbuf, count)) \ ++ return -EFAULT; \ ++ \ ++ buf[count] = '\0'; \ ++ rc = kstrtou32(buf, 0, &val); \ ++ if (rc) \ ++ return rc; \ ++ \ ++ spin_lock_bh(&midev->error_inject.lock); \ ++ midev->error_inject.field = val; \ ++ spin_unlock_bh(&midev->error_inject.lock); \ ++ \ ++ return count; \ ++} \ ++\ ++static const struct file_operations mctp_debugfs_##name##_fops = { \ ++ .owner = THIS_MODULE, \ ++ .read = mctp_debugfs_##name##_read, \ ++ .write = mctp_debugfs_##name##_write, \ ++ .open = simple_open, \ ++ .llseek = default_llseek, \ ++}; ++ ++/* Generic int read/write helpers (for error codes) */ ++#define MCTP_DEBUGFS_INT_DEFINE(name, field) \ ++static ssize_t mctp_debugfs_##name##_read(struct file *file, char __user *userbuf, \ ++ size_t count, loff_t *ppos) \ ++{ \ ++ struct mctp_i2c_dev *midev = mctp_i2c_from_file(file); \ ++ char buf[32]; \ ++ int len; \ ++ \ ++ len = snprintf(buf, sizeof(buf), "%d\n", midev->error_inject.field); \ ++ return simple_read_from_buffer(userbuf, count, ppos, buf, len); \ ++} \ ++\ ++static ssize_t mctp_debugfs_##name##_write(struct file *file, const char __user *userbuf, \ ++ size_t count, loff_t *ppos) \ ++{ \ ++ struct mctp_i2c_dev *midev = mctp_i2c_from_file(file); \ ++ char buf[32]; \ ++ int val; \ ++ int rc; \ ++ \ ++ if (count >= sizeof(buf)) \ ++ return -EINVAL; \ ++ \ ++ if (copy_from_user(buf, userbuf, count)) \ ++ return -EFAULT; \ ++ \ ++ buf[count] = '\0'; \ ++ rc = kstrtoint(buf, 0, &val); \ ++ if (rc) \ ++ return rc; \ ++ \ ++ spin_lock_bh(&midev->error_inject.lock); \ ++ midev->error_inject.field = val; \ ++ spin_unlock_bh(&midev->error_inject.lock); \ ++ \ ++ return count; \ ++} \ ++\ ++static const struct file_operations mctp_debugfs_##name##_fops = { \ ++ .owner = THIS_MODULE, \ ++ .read = mctp_debugfs_##name##_read, \ ++ .write = mctp_debugfs_##name##_write, \ ++ .open = simple_open, \ ++ .llseek = default_llseek, \ ++}; ++ ++/* Generic bool read/write helpers */ ++#define MCTP_DEBUGFS_BOOL_DEFINE(name, field) \ ++static ssize_t mctp_debugfs_##name##_read(struct file *file, char __user *userbuf, \ ++ size_t count, loff_t *ppos) \ ++{ \ ++ struct mctp_i2c_dev *midev = mctp_i2c_from_file(file); \ ++ char buf[8]; \ ++ int len; \ ++ \ ++ len = snprintf(buf, sizeof(buf), "%d\n", midev->error_inject.field ? 1 : 0); \ ++ return simple_read_from_buffer(userbuf, count, ppos, buf, len); \ ++} \ ++\ ++static ssize_t mctp_debugfs_##name##_write(struct file *file, const char __user *userbuf, \ ++ size_t count, loff_t *ppos) \ ++{ \ ++ struct mctp_i2c_dev *midev = mctp_i2c_from_file(file); \ ++ char buf[8]; \ ++ int val; \ ++ int rc; \ ++ \ ++ if (count >= sizeof(buf)) \ ++ return -EINVAL; \ ++ \ ++ if (copy_from_user(buf, userbuf, count)) \ ++ return -EFAULT; \ ++ \ ++ buf[count] = '\0'; \ ++ rc = kstrtoint(buf, 0, &val); \ ++ if (rc) \ ++ return rc; \ ++ \ ++ spin_lock_bh(&midev->error_inject.lock); \ ++ midev->error_inject.field = (val != 0); \ ++ spin_unlock_bh(&midev->error_inject.lock); \ ++ \ ++ return count; \ ++} \ ++\ ++static const struct file_operations mctp_debugfs_##name##_fops = { \ ++ .owner = THIS_MODULE, \ ++ .read = mctp_debugfs_##name##_read, \ ++ .write = mctp_debugfs_##name##_write, \ ++ .open = simple_open, \ ++ .llseek = default_llseek, \ ++}; ++ ++/* Define all the file operations */ ++MCTP_DEBUGFS_INT_DEFINE(i2c_tx_error_code, i2c_tx_error_code) ++MCTP_DEBUGFS_U32_DEFINE(i2c_tx_error_rate, i2c_tx_error_rate) ++MCTP_DEBUGFS_BOOL_DEFINE(enable_fragment_drop, enable_fragment_drop) ++MCTP_DEBUGFS_BOOL_DEFINE(enable_seq_corrupt, enable_seq_corrupt) ++MCTP_DEBUGFS_BOOL_DEFINE(enable_som_clear, enable_som_clear) ++MCTP_DEBUGFS_U32_DEFINE(delay_ms, delay_ms) ++ ++/* EID filter attributes */ ++#define MCTP_DEBUGFS_U8_DEFINE(name, field) \ ++static ssize_t mctp_debugfs_##name##_read(struct file *file, char __user *userbuf, \ ++ size_t count, loff_t *ppos) \ ++{ \ ++ struct mctp_i2c_dev *midev = mctp_i2c_from_file(file); \ ++ char buf[8]; \ ++ int len; \ ++ \ ++ len = snprintf(buf, sizeof(buf), "%u\n", midev->error_inject.eid_filter.field); \ ++ return simple_read_from_buffer(userbuf, count, ppos, buf, len); \ ++} \ ++\ ++static ssize_t mctp_debugfs_##name##_write(struct file *file, const char __user *userbuf, \ ++ size_t count, loff_t *ppos) \ ++{ \ ++ struct mctp_i2c_dev *midev = mctp_i2c_from_file(file); \ ++ char buf[8]; \ ++ u8 val; \ ++ int rc; \ ++ \ ++ if (count >= sizeof(buf)) \ ++ return -EINVAL; \ ++ \ ++ if (copy_from_user(buf, userbuf, count)) \ ++ return -EFAULT; \ ++ \ ++ buf[count] = '\0'; \ ++ rc = kstrtou8(buf, 0, &val); \ ++ if (rc) \ ++ return rc; \ ++ \ ++ spin_lock_bh(&midev->error_inject.lock); \ ++ midev->error_inject.eid_filter.field = val; \ ++ spin_unlock_bh(&midev->error_inject.lock); \ ++ \ ++ return count; \ ++} \ ++\ ++static const struct file_operations mctp_debugfs_##name##_fops = { \ ++ .owner = THIS_MODULE, \ ++ .read = mctp_debugfs_##name##_read, \ ++ .write = mctp_debugfs_##name##_write, \ ++ .open = simple_open, \ ++ .llseek = default_llseek, \ ++}; ++ ++static ssize_t mctp_debugfs_eid_filter_enable_read(struct file *file, char __user *userbuf, ++ size_t count, loff_t *ppos) ++{ ++ struct mctp_i2c_dev *midev = mctp_i2c_from_file(file); ++ char buf[8]; ++ int len; ++ ++ len = snprintf(buf, sizeof(buf), "%d\n", midev->error_inject.eid_filter.enabled); ++ return simple_read_from_buffer(userbuf, count, ppos, buf, len); ++} ++ ++static ssize_t mctp_debugfs_eid_filter_enable_write(struct file *file, const char __user *userbuf, ++ size_t count, loff_t *ppos) ++{ ++ struct mctp_i2c_dev *midev = mctp_i2c_from_file(file); ++ char buf[8]; ++ bool enable; ++ int rc; ++ ++ if (count >= sizeof(buf)) ++ return -EINVAL; ++ ++ if (copy_from_user(buf, userbuf, count)) ++ return -EFAULT; ++ ++ buf[count] = '\0'; ++ rc = kstrtobool(buf, &enable); ++ if (rc) ++ return rc; ++ ++ spin_lock_bh(&midev->error_inject.lock); ++ midev->error_inject.eid_filter.enabled = enable; ++ spin_unlock_bh(&midev->error_inject.lock); ++ ++ return count; ++} ++ ++static const struct file_operations mctp_debugfs_eid_filter_enable_fops = { ++ .owner = THIS_MODULE, ++ .read = mctp_debugfs_eid_filter_enable_read, ++ .write = mctp_debugfs_eid_filter_enable_write, ++ .open = simple_open, ++ .llseek = default_llseek, ++}; ++ ++MCTP_DEBUGFS_U8_DEFINE(eid_filter_src_eid, src_eid) ++MCTP_DEBUGFS_U8_DEFINE(eid_filter_dest_eid, dest_eid) ++MCTP_DEBUGFS_U8_DEFINE(eid_filter_msg_type, msg_type) ++ ++/* stats attribute (read-only) - unified with USB */ ++static ssize_t mctp_debugfs_stats_read(struct file *file, char __user *userbuf, ++ size_t count, loff_t *ppos) ++{ ++ struct mctp_i2c_dev *midev = mctp_i2c_from_file(file); ++ struct mctp_i2c_error_inject *ei = &midev->error_inject; ++ char *buf; ++ int len = 0; ++ ssize_t ret; ++ ++ buf = kmalloc(PAGE_SIZE, GFP_KERNEL); ++ if (!buf) ++ return -ENOMEM; ++ ++ spin_lock_bh(&ei->lock); ++ ++ len += snprintf(buf + len, PAGE_SIZE - len, "enable_tx: %d\n", ei->enable_tx); ++ len += snprintf(buf + len, PAGE_SIZE - len, "enable_rx: %d\n", ei->enable_rx); ++ len += snprintf(buf + len, PAGE_SIZE - len, "mode: %s\n", ++ ei->mode == MCTP_ERR_MODE_ALWAYS ? "always" : ++ ei->mode == MCTP_ERR_MODE_RANDOM ? "random" : "count"); ++ len += snprintf(buf + len, PAGE_SIZE - len, "i2c_tx_errors_injected: %u\n", ++ ei->i2c_tx_errors_injected); ++ len += snprintf(buf + len, PAGE_SIZE - len, "fragments_dropped: %u\n", ++ ei->fragments_dropped); ++ len += snprintf(buf + len, PAGE_SIZE - len, "seq_corruptions: %u\n", ++ ei->seq_corruptions); ++ len += snprintf(buf + len, PAGE_SIZE - len, "som_clears: %u\n", ++ ei->som_clears); ++ len += snprintf(buf + len, PAGE_SIZE - len, "total_packets_processed: %llu\n", ++ ei->total_packets_processed); ++ len += snprintf(buf + len, PAGE_SIZE - len, "total_errors_injected: %llu\n", ++ ei->total_errors_injected); ++ ++ spin_unlock_bh(&ei->lock); ++ ++ ret = simple_read_from_buffer(userbuf, count, ppos, buf, len); ++ kfree(buf); ++ ++ return ret; ++} ++ ++static const struct file_operations mctp_debugfs_stats_fops = { ++ .owner = THIS_MODULE, ++ .read = mctp_debugfs_stats_read, ++ .open = simple_open, ++ .llseek = default_llseek, ++}; ++ ++/* reset attribute (write-only) - unified with USB */ ++static ssize_t mctp_debugfs_reset_write(struct file *file, const char __user *userbuf, ++ size_t count, loff_t *ppos) ++{ ++ struct mctp_i2c_dev *midev = mctp_i2c_from_file(file); ++ struct mctp_i2c_error_inject *ei = &midev->error_inject; ++ char buf[8]; ++ int val; ++ int rc; ++ ++ if (count >= sizeof(buf)) ++ return -EINVAL; ++ ++ if (copy_from_user(buf, userbuf, count)) ++ return -EFAULT; ++ ++ buf[count] = '\0'; ++ rc = kstrtoint(buf, 0, &val); ++ if (rc) ++ return rc; ++ ++ if (val != 1) ++ return -EINVAL; ++ ++ spin_lock_bh(&ei->lock); ++ ++ /* Reset all state */ ++ ei->enable_tx = false; ++ ei->enable_rx = false; ++ ei->mode = MCTP_ERR_MODE_ALWAYS; ++ ei->i2c_tx_error_code = 0; ++ ei->i2c_tx_error_rate = 0; ++ ei->i2c_tx_inject_count = 0; ++ ei->enable_fragment_drop = false; ++ ei->enable_seq_corrupt = false; ++ ei->enable_som_clear = false; ++ ei->delay_ms = 0; ++ ei->eid_filter.enabled = false; ++ ei->eid_filter.src_eid = 0; ++ ei->eid_filter.dest_eid = 0; ++ ei->eid_filter.msg_type = 0; ++ ++ /* Reset statistics */ ++ ei->i2c_tx_errors_injected = 0; ++ ei->fragments_dropped = 0; ++ ei->seq_corruptions = 0; ++ ei->som_clears = 0; ++ ei->total_packets_processed = 0; ++ ei->total_errors_injected = 0; ++ ++ spin_unlock_bh(&ei->lock); ++ ++ netdev_dbg(midev->ndev, "Error injection reset\n"); ++ ++ return count; ++} ++ ++static const struct file_operations mctp_debugfs_reset_fops = { ++ .owner = THIS_MODULE, ++ .write = mctp_debugfs_reset_write, ++ .open = simple_open, ++ .llseek = default_llseek, ++}; ++ ++/** ++ * mctp_i2c_error_inject_init - Initialize per-device error injection ++ * @midev: MCTP I2C device ++ * ++ * Creates debugfs directory and files for this device ++ */ ++void mctp_i2c_error_inject_init(struct mctp_i2c_dev *midev) ++{ ++ struct dentry *dir, *eid_dir; ++ struct mctp_i2c_error_inject *ei = &midev->error_inject; ++ ++ /* Initialize error injection state */ ++ spin_lock_init(&ei->lock); ++ prandom_seed_state(&ei->rng, (u64)jiffies); ++ ei->enable_tx = false; ++ ei->enable_rx = false; ++ ei->mode = MCTP_ERR_MODE_ALWAYS; ++ /* All other fields are zero-initialized */ ++ ++ if (!mctp_i2c_error_inject_root) ++ return; ++ ++ /* Create per-device debugfs directory */ ++ dir = debugfs_create_dir(netdev_name(midev->ndev), mctp_i2c_error_inject_root); ++ if (IS_ERR_OR_NULL(dir)) ++ return; ++ ++ midev->debugfs_dir = dir; ++ ++ /* Create error injection files - unified with USB */ ++ debugfs_create_file("enable_tx", 0600, dir, midev, &mctp_debugfs_enable_tx_fops); ++ debugfs_create_file("enable_rx", 0600, dir, midev, &mctp_debugfs_enable_rx_fops); ++ debugfs_create_file("mode", 0600, dir, midev, &mctp_debugfs_mode_fops); ++ debugfs_create_file("i2c_tx_error_code", 0600, dir, midev, &mctp_debugfs_i2c_tx_error_code_fops); ++ debugfs_create_file("i2c_tx_error_rate", 0600, dir, midev, &mctp_debugfs_i2c_tx_error_rate_fops); ++ debugfs_create_file("enable_fragment_drop", 0600, dir, midev, &mctp_debugfs_enable_fragment_drop_fops); ++ debugfs_create_file("enable_seq_corrupt", 0600, dir, midev, &mctp_debugfs_enable_seq_corrupt_fops); ++ debugfs_create_file("enable_som_clear", 0600, dir, midev, &mctp_debugfs_enable_som_clear_fops); ++ debugfs_create_file("delay_ms", 0600, dir, midev, &mctp_debugfs_delay_ms_fops); ++ debugfs_create_file("stats", 0400, dir, midev, &mctp_debugfs_stats_fops); ++ debugfs_create_file("reset", 0200, dir, midev, &mctp_debugfs_reset_fops); ++ ++ /* Create eid_filter subdirectory - unified with USB */ ++ eid_dir = debugfs_create_dir("eid_filter", dir); ++ if (!IS_ERR_OR_NULL(eid_dir)) { ++ debugfs_create_file("enable", 0600, eid_dir, midev, &mctp_debugfs_eid_filter_enable_fops); ++ debugfs_create_file("src_eid", 0600, eid_dir, midev, &mctp_debugfs_eid_filter_src_eid_fops); ++ debugfs_create_file("dest_eid", 0600, eid_dir, midev, &mctp_debugfs_eid_filter_dest_eid_fops); ++ debugfs_create_file("msg_type", 0600, eid_dir, midev, &mctp_debugfs_eid_filter_msg_type_fops); ++ } ++} ++EXPORT_SYMBOL_GPL(mctp_i2c_error_inject_init); ++ ++/** ++ * mctp_i2c_error_inject_cleanup - Cleanup per-device error injection ++ * @midev: MCTP I2C device ++ */ ++void mctp_i2c_error_inject_cleanup(struct mctp_i2c_dev *midev) ++{ ++ debugfs_remove_recursive(midev->debugfs_dir); ++ midev->debugfs_dir = NULL; ++} ++EXPORT_SYMBOL_GPL(mctp_i2c_error_inject_cleanup); ++ ++/** ++ * mctp_i2c_error_inject_module_init - Initialize global error injection ++ * ++ * Creates root debugfs directory for MCTP I2C error injection ++ */ ++int mctp_i2c_error_inject_module_init(void) ++{ ++ mctp_i2c_error_inject_root = debugfs_create_dir("mctp_i2c", NULL); ++ if (IS_ERR_OR_NULL(mctp_i2c_error_inject_root)) { ++ pr_warn("MCTP I2C: Failed to create debugfs root, error injection disabled\n"); ++ mctp_i2c_error_inject_root = NULL; ++ return -ENODEV; ++ } ++ ++ pr_info("MCTP I2C Error Injection enabled\n"); ++ return 0; ++} ++EXPORT_SYMBOL_GPL(mctp_i2c_error_inject_module_init); ++ ++/** ++ * mctp_i2c_error_inject_module_exit - Cleanup global error injection ++ */ ++void mctp_i2c_error_inject_module_exit(void) ++{ ++ debugfs_remove_recursive(mctp_i2c_error_inject_root); ++ mctp_i2c_error_inject_root = NULL; ++} ++EXPORT_SYMBOL_GPL(mctp_i2c_error_inject_module_exit); +diff --git a/drivers/net/mctp/mctp-i2c-error-inject.h b/drivers/net/mctp/mctp-i2c-error-inject.h +new file mode 100644 +index 000000000..5689d4b1d +--- /dev/null ++++ b/drivers/net/mctp/mctp-i2c-error-inject.h +@@ -0,0 +1,91 @@ ++/* SPDX-License-Identifier: GPL-2.0 */ ++/* ++ * MCTP I2C Error Injection Infrastructure ++ * ++ * Copyright (c) 2024 NVIDIA CORPORATION. All rights reserved. ++ * ++ * Provides debugfs-based error injection for testing MCTP error queue ++ * functionality over I2C binding. ++ */ ++ ++#ifndef __MCTP_I2C_ERROR_INJECT_H ++#define __MCTP_I2C_ERROR_INJECT_H ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++/* Forward declarations */ ++struct dentry; ++struct mctp_i2c_client; ++ ++/* Error injection modes - unified with USB */ ++enum mctp_error_inject_mode { ++ MCTP_ERR_MODE_ALWAYS, ++ MCTP_ERR_MODE_RANDOM, ++ MCTP_ERR_MODE_COUNT ++}; ++ ++/* Error injection control structure - unified with USB where possible */ ++struct mctp_i2c_error_inject { ++ bool enable_tx; /* TX error injection enable */ ++ bool enable_rx; /* RX/fragment error injection enable */ ++ enum mctp_error_inject_mode mode; ++ ++ /* TX injection - I2C only has one synchronous path */ ++ int i2c_tx_error_code; /* Error code to inject (ENXIO, EAGAIN, EBUSY, etc.) */ ++ u32 i2c_tx_error_rate; /* Percentage (0-100) or count */ ++ u32 i2c_tx_inject_count; /* Counter for count mode */ ++ u32 i2c_tx_errors_injected; ++ ++ /* Fragment injection - same as USB */ ++ bool enable_fragment_drop; /* Drop 2nd+ fragments */ ++ bool enable_seq_corrupt; /* Corrupt sequence number in middle/end fragments */ ++ bool enable_som_clear; /* Clear SOM bit in first fragment */ ++ u32 fragments_dropped; ++ u32 seq_corruptions; ++ u32 som_clears; ++ ++ /* Delay injection */ ++ u32 delay_ms; ++ ++ /* EID filtering - unified with USB */ ++ struct { ++ bool enabled; ++ u8 src_eid; /* 0 = any */ ++ u8 dest_eid; /* 0 = any */ ++ u8 msg_type; /* 0 = any */ ++ } eid_filter; ++ ++ /* Statistics */ ++ u64 total_packets_processed; ++ u64 total_errors_injected; ++ ++ /* RNG state */ ++ struct rnd_state rng; ++ spinlock_t lock; ++}; ++ ++/* Forward declaration - full definition in mctp-i2c.c */ ++struct mctp_i2c_dev; ++ ++/* Module init/exit functions */ ++int mctp_i2c_error_inject_module_init(void); ++void mctp_i2c_error_inject_module_exit(void); ++ ++/* Public API for main driver */ ++void mctp_i2c_error_inject_init(struct mctp_i2c_dev *midev); ++void mctp_i2c_error_inject_cleanup(struct mctp_i2c_dev *midev); ++ ++int mctp_i2c_error_inject_tx(struct mctp_i2c_dev *midev, struct sk_buff *skb); ++int mctp_i2c_error_inject_fragment(struct mctp_i2c_dev *midev, struct sk_buff *skb); ++ ++#endif /* __MCTP_I2C_ERROR_INJECT_H */ ++ ++ +diff --git a/drivers/net/mctp/mctp-i2c-internal.h b/drivers/net/mctp/mctp-i2c-internal.h +new file mode 100644 +index 000000000..3e8768314 +--- /dev/null ++++ b/drivers/net/mctp/mctp-i2c-internal.h +@@ -0,0 +1,117 @@ ++/* SPDX-License-Identifier: GPL-2.0 */ ++/* ++ * MCTP I2C Internal Definitions ++ * Shared between mctp-i2c.c and mctp-i2c-error-inject.c ++ */ ++ ++#ifndef __MCTP_I2C_INTERNAL_H ++#define __MCTP_I2C_INTERNAL_H ++ ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include "mctp-i2c-error-inject.h" ++ ++/* Constants from mctp-i2c.c */ ++#define MCTP_I2C_MAXBLOCK 255 ++#define MCTP_I2C_BUFSZ (3 + MCTP_I2C_MAXBLOCK + 1) ++ ++/* Forward declaration */ ++struct mctp_i2c_client; ++ ++/* The netdev structure. One of these per I2C adapter. */ ++struct mctp_i2c_dev { ++ struct net_device *ndev; ++ struct i2c_adapter *adapter; ++ struct mctp_i2c_client *client; ++ struct list_head list; /* For mctp_i2c_client.devs */ ++ ++ size_t rx_pos; ++ u8 rx_buffer[MCTP_I2C_BUFSZ]; ++ struct completion rx_done; ++ ++ struct task_struct *tx_thread; ++ wait_queue_head_t tx_wq; ++ struct sk_buff_head tx_queue; ++ u8 tx_scratch[MCTP_I2C_BUFSZ]; ++ ++ /* A fake entry in our tx queue to perform an unlock operation */ ++ struct sk_buff unlock_marker; ++ ++ /* NACK retry parameters */ ++ u32 nack_retries; ++ u32 nack_retry_delay_us; ++ u32 nack_retry_addr; ++ u32 nack_total_retries; ++ u32 nack_recovered; ++ u32 nack_failed; ++ u32 nack_max_depth; ++ ++ /* Spinlock protects i2c_lock_count, release_count, allow_rx */ ++ spinlock_t lock; ++ int i2c_lock_count; ++ int release_count; ++ /* Indicates that the netif is ready to receive incoming packets */ ++ bool allow_rx; ++ bool flows_enabled; /* Runtime flow control */ ++ ++ /* Error injection support */ ++ struct mctp_i2c_error_inject error_inject; ++ struct dentry *debugfs_dir; ++ ++ /* Per-EID statistics tracking - SINGLE source of truth ++ * ++ * All statistics are tracked per-endpoint-ID (EID). Two special EIDs: ++ * - EID 0: "null endpoint" - valid packets with EID=0 (unallocated endpoint) ++ * - EID 256 (MCTP_EID_UNKNOWN): errors where EID could not be determined ++ * (pre-parse errors, allocation failures, etc.) ++ */ ++ struct { ++ DECLARE_BITMAP(active, 257); /* Which EIDs have activity */ ++ struct mctp_i2c_eid_stats { ++ /* RX stats */ ++ u64 rx_drop_invalid_cmd; /* Invalid I2C command (tracked as UNKNOWN) */ ++ u64 rx_drop_no_memory; ++ u64 rx_drop_invalid_pec; /* PEC check failed (tracked as UNKNOWN) */ ++ u64 rx_drop_fragment_error; ++ u64 rx_drop_not_ready; ++ u64 rx_early_exit_no_rx; /* RX not allowed (tracked as UNKNOWN) */ ++ ++ /* TX stats */ ++ u64 tx_drop_no_memory; /* -ENOMEM: Memory allocation failure */ ++ u64 tx_drop_busy; /* -EBUSY: Bus busy too long */ ++ u64 tx_drop_no_device; /* -ENXIO: No device at address (no ACK) */ ++ u64 tx_drop_enodev; /* -ENODEV: Device not found */ ++ u64 tx_drop_arbitration_lost; /* -EAGAIN: Lost arbitration */ ++ u64 tx_drop_proto_error; /* -EPROTO: Protocol violation */ ++ u64 tx_drop_timeout; /* -ETIMEDOUT: Transfer timeout */ ++ u64 tx_drop_eopnotsupp; /* -EOPNOTSUPP: Operation not supported */ ++ u64 tx_drop_einval; /* -EINVAL: Invalid parameter */ ++ u64 tx_drop_eafnosupport; /* -EAFNOSUPPORT: 10-bit address not supported */ ++ u64 tx_drop_ebadmsg; /* -EBADMSG: Invalid SMBus PEC */ ++ u64 tx_drop_eshutdown; /* -ESHUTDOWN: Adapter suspended */ ++ u64 tx_drop_eio; /* -EIO: Generic I/O error */ ++ u64 tx_drop_invalid_len; /* Invalid packet length */ ++ u64 tx_drop_queue_full; /* TX queue full */ ++ u64 tx_drop_flow_invalid; /* Invalid flow state */ ++ u64 tx_early_exit_stopped; /* TX thread stopped (tracked as UNKNOWN) */ ++ ++ /* Retry/requeue */ ++ u64 tx_retries_attempted; ++ u64 tx_retry_success; ++ u64 tx_retry_exhausted; ++ u64 tx_requeued; ++ /* NACK retry stats */ ++ u64 tx_nack; ++ u64 tx_nack_retries; ++ u64 tx_nack_retry_depth; ++ } eid[257]; ++ } eid_stats; ++}; ++ ++#endif /* __MCTP_I2C_INTERNAL_H */ ++ +diff --git a/drivers/net/mctp/mctp-i2c.c b/drivers/net/mctp/mctp-i2c.c +index 503a91743..4fbab9a83 100644 +--- a/drivers/net/mctp/mctp-i2c.c ++++ b/drivers/net/mctp/mctp-i2c.c +@@ -22,16 +22,22 @@ + #include + #include + #include ++#include ++#include ++ + #include + #include + ++#include "mctp-i2c-error-inject.h" ++#include "mctp-stats.h" ++#include ++ + /* byte_count is limited to u8 */ +-#define MCTP_I2C_MAXBLOCK 255 ++#include "mctp-i2c-internal.h" ++ + /* One byte is taken by source_slave */ + #define MCTP_I2C_MAXMTU (MCTP_I2C_MAXBLOCK - 1) + #define MCTP_I2C_MINMTU (64 + 4) +-/* Allow space for dest_address, command, byte_count, data, PEC */ +-#define MCTP_I2C_BUFSZ (3 + MCTP_I2C_MAXBLOCK + 1) + #define MCTP_I2C_MINLEN 8 + #define MCTP_I2C_COMMANDCODE 0x0f + #define MCTP_I2C_TX_WORK_LEN 100 +@@ -40,6 +46,11 @@ + + #define MCTP_I2C_OF_PROP "mctp-controller" + ++#define NACK_RETRY_MAX 100 ++#define NACK_RETRY_DELAY_MAX_US 1000 ++#define NACK_RETRY_DELAY_SLACK_US 1000 ++#define NACK_RETRY_ADDR_DISABLED 0xFFFFFFFF ++ + enum { + MCTP_I2C_FLOW_STATE_NEW = 0, + MCTP_I2C_FLOW_STATE_ACTIVE, +@@ -53,36 +64,6 @@ enum { + static DEFINE_MUTEX(driver_clients_lock); + static LIST_HEAD(driver_clients); + +-struct mctp_i2c_client; +- +-/* The netdev structure. One of these per I2C adapter. */ +-struct mctp_i2c_dev { +- struct net_device *ndev; +- struct i2c_adapter *adapter; +- struct mctp_i2c_client *client; +- struct list_head list; /* For mctp_i2c_client.devs */ +- +- size_t rx_pos; +- u8 rx_buffer[MCTP_I2C_BUFSZ]; +- struct completion rx_done; +- +- struct task_struct *tx_thread; +- wait_queue_head_t tx_wq; +- struct sk_buff_head tx_queue; +- u8 tx_scratch[MCTP_I2C_BUFSZ]; +- +- /* A fake entry in our tx queue to perform an unlock operation */ +- struct sk_buff unlock_marker; +- +- /* Spinlock protects i2c_lock_count, release_count, allow_rx */ +- spinlock_t lock; +- int i2c_lock_count; +- int release_count; +- /* Indicates that the netif is ready to receive incoming packets */ +- bool allow_rx; +- +-}; +- + /* The i2c client structure. One per hardware i2c bus at the top of the + * mux tree, shared by multiple netdevs + */ +@@ -177,8 +158,7 @@ static struct mctp_i2c_client *mctp_i2c_new_client(struct i2c_client *client) + return mcli; + err: + if (mcli) { +- if (mcli->client) +- i2c_unregister_device(mcli->client); ++ i2c_unregister_device(mcli->client); + kfree(mcli); + } + return ERR_PTR(rc); +@@ -235,8 +215,12 @@ static int mctp_i2c_slave_cb(struct i2c_client *client, + + spin_lock_irqsave(&mcli->sel_lock, flags); + midev = mcli->sel; +- if (midev) ++ if (midev) { + dev_hold(midev->ndev); ++ /* Track early exit if RX not allowed */ ++ if (!midev->allow_rx) ++ MCTP_STAT_INC(midev, MCTP_EID_UNKNOWN, rx_early_exit_no_rx); ++ } + spin_unlock_irqrestore(&mcli->sel_lock, flags); + + if (!midev) +@@ -249,6 +233,7 @@ static int mctp_i2c_slave_cb(struct i2c_client *client, + midev->rx_pos++; + } else { + midev->ndev->stats.rx_over_errors++; ++ trace_mctp_transport_error("i2c", midev->ndev, "rx_over_errors", midev->rx_pos); + } + + break; +@@ -283,6 +268,7 @@ static int mctp_i2c_recv(struct mctp_i2c_dev *midev) + /* + 1 for the PEC */ + if (midev->rx_pos < MCTP_I2C_MINLEN + 1) { + ndev->stats.rx_length_errors++; ++ trace_mctp_transport_error("i2c", ndev, "rx_short_packet", midev->rx_pos); + return -EINVAL; + } + /* recvlen excludes PEC */ +@@ -291,11 +277,18 @@ static int mctp_i2c_recv(struct mctp_i2c_dev *midev) + hdr = (void *)midev->rx_buffer; + if (hdr->command != MCTP_I2C_COMMANDCODE) { + ndev->stats.rx_dropped++; ++ /* UNKNOWN: Command byte checked before MCTP header can be read */ ++ MCTP_STAT_INC(midev, MCTP_EID_UNKNOWN, rx_drop_invalid_cmd); ++ netdev_dbg(ndev, "MCTP I2C: invalid command code 0x%02x\n", hdr->command); ++ trace_mctp_transport_error("i2c", ndev, "invalid_command_code", hdr->command); + return -EINVAL; + } + + if (hdr->byte_count + offsetof(struct mctp_i2c_hdr, source_slave) != recvlen) { + ndev->stats.rx_length_errors++; ++ netdev_dbg(ndev, "MCTP I2C: length mismatch (byte_count=%u, recvlen=%zu)\n", ++ hdr->byte_count, recvlen); ++ trace_mctp_transport_error("i2c", ndev, "length_mismatch", hdr->byte_count); + return -EINVAL; + } + +@@ -303,12 +296,22 @@ static int mctp_i2c_recv(struct mctp_i2c_dev *midev) + calc_pec = i2c_smbus_pec(0, midev->rx_buffer, recvlen); + if (pec != calc_pec) { + ndev->stats.rx_crc_errors++; ++ /* PEC validation failed - cannot trust header, track as UNKNOWN */ ++ MCTP_STAT_INC(midev, MCTP_EID_UNKNOWN, rx_drop_invalid_pec); ++ netdev_dbg(ndev, "MCTP I2C: PEC error (got 0x%02x, expected 0x%02x)\n", ++ pec, calc_pec); ++ trace_mctp_transport_error("i2c", ndev, "pec_error", pec); + return -EINVAL; + } + + skb = netdev_alloc_skb(ndev, recvlen); + if (!skb) { ++ struct mctp_hdr *mh = (void *)(midev->rx_buffer + sizeof(struct mctp_i2c_hdr)); ++ u8 src_eid = mh->src; ++ + ndev->stats.rx_dropped++; ++ MCTP_STAT_INC(midev, src_eid, rx_drop_no_memory); ++ trace_mctp_transport_error("i2c", ndev, "rx_drop_no_memory", recvlen); + return -ENOMEM; + } + +@@ -322,26 +325,57 @@ static int mctp_i2c_recv(struct mctp_i2c_dev *midev) + cb->halen = 1; + cb->haddr[0] = hdr->source_slave >> 1; + +- /* We need to ensure that the netif is not used once netdev +- * unregister occurs ++ /* ERROR INJECTION POINT: Fragment drop/corruption ++ * At this point: ++ * - I2C header has been removed ++ * - skb->data points to MCTP header ++ * - Before packet sent to network stack ++ * This is the ideal location to inject fragment errors + */ +- spin_lock_irqsave(&midev->lock, flags); +- if (midev->allow_rx) { +- reinit_completion(&midev->rx_done); +- spin_unlock_irqrestore(&midev->lock, flags); ++ if (mctp_i2c_error_inject_fragment(midev, skb)) { ++ struct mctp_hdr *mh = mctp_hdr(skb); ++ u8 src_eid = mh->src; + +- status = netif_rx(skb); +- complete(&midev->rx_done); +- } else { +- status = NET_RX_DROP; +- spin_unlock_irqrestore(&midev->lock, flags); ++ /* Drop this fragment */ ++ ndev->stats.rx_dropped++; ++ MCTP_STAT_INC(midev, src_eid, rx_drop_fragment_error); ++ trace_mctp_transport_error("i2c", ndev, "rx_drop_fragment_error", 0); ++ kfree_skb(skb); ++ return 0; + } + +- if (status == NET_RX_SUCCESS) { +- ndev->stats.rx_packets++; +- ndev->stats.rx_bytes += recvlen; +- } else { +- ndev->stats.rx_dropped++; ++ /* Capture source EID before skb is consumed by netif_rx */ ++ { ++ struct mctp_hdr *mh = mctp_hdr(skb); ++ u8 src_eid = mh->src; ++ ++ /* We need to ensure that the netif is not used once netdev ++ * unregister occurs ++ */ ++ spin_lock_irqsave(&midev->lock, flags); ++ if (midev->allow_rx) { ++ reinit_completion(&midev->rx_done); ++ spin_unlock_irqrestore(&midev->lock, flags); ++ ++ status = netif_rx(skb); ++ complete(&midev->rx_done); ++ } else { ++ status = NET_RX_DROP; ++ spin_unlock_irqrestore(&midev->lock, flags); ++ } ++ ++ if (status == NET_RX_SUCCESS) { ++ ndev->stats.rx_packets++; ++ ndev->stats.rx_bytes += recvlen; ++ netdev_dbg(ndev, "MCTP I2C: RX success from 0x%02x, %zu bytes\n", ++ hdr->source_slave >> 1, recvlen); ++ trace_mctp_transport_rx("i2c", ndev, hdr->source_slave >> 1, recvlen); ++ } else { ++ ndev->stats.rx_dropped++; ++ MCTP_STAT_INC(midev, src_eid, rx_drop_not_ready); ++ netdev_dbg(ndev, "MCTP I2C: RX dropped, status=%d\n", status); ++ trace_mctp_transport_error("i2c", ndev, "rx_dropped", status); ++ } + } + return 0; + } +@@ -478,18 +512,85 @@ static void mctp_i2c_invalidate_tx_flow(struct mctp_i2c_dev *midev, + mctp_i2c_unlock_nest(midev); + } + ++/* Helper function to perform I2C transfer with retries */ ++static int mctp_i2c_transfer_with_retry(struct mctp_i2c_dev *midev, ++ struct i2c_msg *msg, u8 dest_eid) ++{ ++ int retries = 0; ++ int rc; ++ ++ do { ++ rc = __i2c_transfer(midev->adapter, msg, 1); ++ ++ /* Success */ ++ if (rc >= 0) { ++ if (retries > 0) { ++ midev->nack_recovered++; ++ if (retries > midev->eid_stats.eid[dest_eid].tx_nack_retry_depth) ++ midev->eid_stats.eid[dest_eid].tx_nack_retry_depth = retries; ++ } ++ return rc; ++ } ++ ++ /* Check for NACK (-ENXIO) and matching address */ ++ if (rc == -ENXIO && ++ msg->addr == midev->nack_retry_addr && ++ retries < midev->nack_retries) { ++ ++ if (retries == 0) { ++ MCTP_STAT_INC(midev, dest_eid, tx_nack); ++ set_bit(dest_eid, midev->eid_stats.active); ++ } ++ MCTP_STAT_INC(midev, dest_eid, tx_nack_retries); ++ ++ midev->nack_total_retries++; ++ if (retries + 1 > midev->nack_max_depth) ++ midev->nack_max_depth = retries + 1; ++ ++ if (midev->nack_retry_delay_us) ++ usleep_range(midev->nack_retry_delay_us, ++ midev->nack_retry_delay_us + NACK_RETRY_DELAY_SLACK_US); ++ ++ retries++; ++ continue; ++ } ++ ++ /* Other error or retries exhausted */ ++ if (retries > 0) { ++ midev->nack_failed++; ++ if (retries > midev->eid_stats.eid[dest_eid].tx_nack_retry_depth) ++ midev->eid_stats.eid[dest_eid].tx_nack_retry_depth = retries; ++ dev_dbg(midev->ndev->dev.parent, ++ "NACK retry exhausted after %d attempts (addr 0x%02x)\n", ++ retries, msg->addr); ++ } ++ break; ++ ++ } while (1); ++ ++ return rc; ++} ++ + static void mctp_i2c_xmit(struct mctp_i2c_dev *midev, struct sk_buff *skb) + { + struct net_device_stats *stats = &midev->ndev->stats; + enum mctp_i2c_flow_state fs; + struct mctp_i2c_hdr *hdr; ++ struct mctp_hdr *mh; + struct i2c_msg msg = {0}; ++ u8 dest_eid; + u8 *pecp; + int rc; + +- fs = mctp_i2c_get_tx_flow_state(midev, skb); ++ if (midev->flows_enabled) ++ fs = mctp_i2c_get_tx_flow_state(midev, skb); ++ else ++ fs = MCTP_I2C_TX_FLOW_NONE; + + hdr = (void *)skb_mac_header(skb); ++ mh = mctp_hdr(skb); ++ dest_eid = mh->dest; ++ + /* Sanity check that packet contents matches skb length, + * and can't exceed MCTP_I2C_BUFSZ + */ +@@ -497,6 +598,8 @@ static void mctp_i2c_xmit(struct mctp_i2c_dev *midev, struct sk_buff *skb) + dev_warn_ratelimited(&midev->adapter->dev, + "Bad tx length %d vs skb %u\n", + hdr->byte_count + 3, skb->len); ++ MCTP_STAT_INC(midev, dest_eid, tx_drop_invalid_len); ++ trace_mctp_transport_error("i2c", midev->ndev, "tx_invalid_len", skb->len); + return; + } + +@@ -521,7 +624,12 @@ static void mctp_i2c_xmit(struct mctp_i2c_dev *midev, struct sk_buff *skb) + /* no flow: full lock & unlock */ + mctp_i2c_lock_nest(midev); + mctp_i2c_device_select(midev->client, midev); +- rc = __i2c_transfer(midev->adapter, &msg, 1); ++ ++ /* ERROR INJECTION POINT: TX transfer (synchronous error) */ ++ rc = mctp_i2c_error_inject_tx(midev, skb); ++ if (rc == 0) ++ rc = mctp_i2c_transfer_with_retry(midev, &msg, dest_eid); ++ + mctp_i2c_unlock_nest(midev); + break; + +@@ -535,7 +643,11 @@ static void mctp_i2c_xmit(struct mctp_i2c_dev *midev, struct sk_buff *skb) + + case MCTP_I2C_TX_FLOW_EXISTING: + /* existing flow: we already have the lock; just tx */ +- rc = __i2c_transfer(midev->adapter, &msg, 1); ++ ++ /* ERROR INJECTION POINT: TX transfer (synchronous error) */ ++ rc = mctp_i2c_error_inject_tx(midev, skb); ++ if (rc == 0) ++ rc = mctp_i2c_transfer_with_retry(midev, &msg, dest_eid); + + /* on tx errors, the flow can no longer be considered valid */ + if (rc < 0) +@@ -548,12 +660,65 @@ static void mctp_i2c_xmit(struct mctp_i2c_dev *midev, struct sk_buff *skb) + } + + if (rc < 0) { +- dev_warn_ratelimited(&midev->adapter->dev, +- "__i2c_transfer failed %d\n", rc); +- stats->tx_errors++; ++ struct mctp_sk_key *key = NULL; ++ struct sock *sk; ++ ++ /* Report error to socket error queue ++ * Look up socket and key. Key provides orig_payload for error reporting. ++ * TX key existence proves this was our transaction. ++ */ ++ sk = mctp_lookup_sock_for_error(skb, midev->ndev, NULL, &key); ++ ++ if (sk) { ++ /* Pass key so error report uses orig_payload (before fragmentation) */ ++ mctp_queue_error(sk, skb, -rc, midev->ndev, ++ MCTP_DIR_TX, MCTP_PHYS_BINDING_SMBUS, key); ++ sock_put(sk); ++ } ++ ++ stats->tx_errors++; ++ ++ /* Track specific error types per-EID */ ++ if (rc == -ENOMEM) { ++ MCTP_STAT_INC(midev, dest_eid, tx_drop_no_memory); ++ } else if (rc == -EBUSY) { ++ MCTP_STAT_INC(midev, dest_eid, tx_drop_busy); ++ } else if (rc == -ENXIO) { ++ MCTP_STAT_INC(midev, dest_eid, tx_drop_no_device); ++ } else if (rc == -ENODEV) { ++ MCTP_STAT_INC(midev, dest_eid, tx_drop_enodev); ++ } else if (rc == -EAGAIN) { ++ MCTP_STAT_INC(midev, dest_eid, tx_drop_arbitration_lost); ++ } else if (rc == -EPROTO) { ++ MCTP_STAT_INC(midev, dest_eid, tx_drop_proto_error); ++ } else if (rc == -ETIMEDOUT) { ++ MCTP_STAT_INC(midev, dest_eid, tx_drop_timeout); ++ } else if (rc == -EOPNOTSUPP) { ++ MCTP_STAT_INC(midev, dest_eid, tx_drop_eopnotsupp); ++ } else if (rc == -EINVAL) { ++ MCTP_STAT_INC(midev, dest_eid, tx_drop_einval); ++ } else if (rc == -EAFNOSUPPORT) { ++ MCTP_STAT_INC(midev, dest_eid, tx_drop_eafnosupport); ++ } else if (rc == -EBADMSG) { ++ MCTP_STAT_INC(midev, dest_eid, tx_drop_ebadmsg); ++ } else if (rc == -ESHUTDOWN) { ++ MCTP_STAT_INC(midev, dest_eid, tx_drop_eshutdown); ++ } else if (rc == -EIO) { ++ MCTP_STAT_INC(midev, dest_eid, tx_drop_eio); ++ } else { ++ /* Unexpected error - shouldn't happen with comprehensive I2C error coverage */ ++ netdev_warn(midev->ndev, "MCTP I2C: Unhandled TX error code %d\n", rc); ++ MCTP_STAT_INC(midev, dest_eid, tx_drop_eio); ++ } ++ netdev_dbg(midev->ndev, "MCTP I2C: TX failed to 0x%02x, error=%d\n", ++ hdr->dest_slave >> 1, rc); ++ trace_mctp_transport_error("i2c", midev->ndev, "i2c_transfer_failed", rc); + } else { + stats->tx_bytes += skb->len; + stats->tx_packets++; ++ netdev_dbg(midev->ndev, "MCTP I2C: TX success to 0x%02x, %u bytes\n", ++ hdr->dest_slave >> 1, skb->len); ++ trace_mctp_transport_tx("i2c", midev->ndev, hdr->dest_slave >> 1, skb->len); + } + } + +@@ -619,8 +784,10 @@ static int mctp_i2c_tx_thread(void *data) + unsigned long flags; + + for (;;) { +- if (kthread_should_stop()) ++ if (kthread_should_stop()) { ++ MCTP_STAT_INC(midev, MCTP_EID_UNKNOWN, tx_early_exit_stopped); + break; ++ } + + spin_lock_irqsave(&midev->tx_queue.lock, flags); + skb = __skb_dequeue(&midev->tx_queue); +@@ -700,6 +867,182 @@ static void mctp_i2c_release_flow(struct mctp_dev *mdev, + } + } + ++/* Ethtool statistics support */ ++/* Per-EID stat descriptors with abbreviated names */ ++struct mctp_i2c_eid_stat_desc { ++ const char *name; ++ size_t offset; ++}; ++ ++#define MCTP_I2C_EID_STAT(abbrev, field) { \ ++ .name = abbrev, \ ++ .offset = offsetof(struct mctp_i2c_eid_stats, field) \ ++} ++ ++static const struct mctp_i2c_eid_stat_desc mctp_i2c_eid_stat_descs[] = { ++ /* RX stats */ ++ MCTP_I2C_EID_STAT("rx_drop_invalid_cmd", rx_drop_invalid_cmd), ++ MCTP_I2C_EID_STAT("rx_drop_no_memory", rx_drop_no_memory), ++ MCTP_I2C_EID_STAT("rx_drop_invalid_pec", rx_drop_invalid_pec), ++ MCTP_I2C_EID_STAT("rx_drop_fragment_error", rx_drop_fragment_error), ++ MCTP_I2C_EID_STAT("rx_drop_not_ready", rx_drop_not_ready), ++ MCTP_I2C_EID_STAT("rx_early_exit_no_rx", rx_early_exit_no_rx), ++ ++ /* TX stats - I2C-specific error codes */ ++ MCTP_I2C_EID_STAT("tx_drop_no_memory", tx_drop_no_memory), ++ MCTP_I2C_EID_STAT("tx_drop_busy", tx_drop_busy), ++ MCTP_I2C_EID_STAT("tx_drop_no_device", tx_drop_no_device), ++ MCTP_I2C_EID_STAT("tx_drop_enodev", tx_drop_enodev), ++ MCTP_I2C_EID_STAT("tx_drop_arbitration_lost", tx_drop_arbitration_lost), ++ MCTP_I2C_EID_STAT("tx_drop_proto_error", tx_drop_proto_error), ++ MCTP_I2C_EID_STAT("tx_drop_timeout", tx_drop_timeout), ++ MCTP_I2C_EID_STAT("tx_drop_eopnotsupp", tx_drop_eopnotsupp), ++ MCTP_I2C_EID_STAT("tx_drop_einval", tx_drop_einval), ++ MCTP_I2C_EID_STAT("tx_drop_eafnosupport", tx_drop_eafnosupport), ++ MCTP_I2C_EID_STAT("tx_drop_ebadmsg", tx_drop_ebadmsg), ++ MCTP_I2C_EID_STAT("tx_drop_eshutdown", tx_drop_eshutdown), ++ MCTP_I2C_EID_STAT("tx_drop_eio", tx_drop_eio), ++ ++ /* TX stats - General */ ++ MCTP_I2C_EID_STAT("tx_drop_invalid_len", tx_drop_invalid_len), ++ MCTP_I2C_EID_STAT("tx_drop_queue_full", tx_drop_queue_full), ++ MCTP_I2C_EID_STAT("tx_drop_flow_invalid", tx_drop_flow_invalid), ++ MCTP_I2C_EID_STAT("tx_early_exit_stopped", tx_early_exit_stopped), ++ ++ /* Retry/requeue stats */ ++ MCTP_I2C_EID_STAT("tx_retries_attempted", tx_retries_attempted), ++ MCTP_I2C_EID_STAT("tx_retry_success", tx_retry_success), ++ MCTP_I2C_EID_STAT("tx_retry_exhausted", tx_retry_exhausted), ++ MCTP_I2C_EID_STAT("tx_requeued", tx_requeued), ++ MCTP_I2C_EID_STAT("tx_nack", tx_nack), ++ MCTP_I2C_EID_STAT("tx_nack_retries", tx_nack_retries), ++ MCTP_I2C_EID_STAT("tx_nack_retry_depth", tx_nack_retry_depth), ++}; ++ ++#define MCTP_I2C_EID_NUM_STATS ARRAY_SIZE(mctp_i2c_eid_stat_descs) ++ ++/* Generate ethtool helper functions using shared macros */ ++MCTP_EID_STATS_HELPERS(mctp_i2c, struct mctp_i2c_dev, ++ struct mctp_i2c_eid_stats, mctp_i2c_eid_stat_descs) ++ ++static void mctp_i2c_get_strings(struct net_device *ndev, u32 stringset, ++ u8 *data) ++{ ++ struct mctp_i2c_dev *midev = netdev_priv(ndev); ++ unsigned int i; ++ int eid; ++ ++ if (stringset != ETH_SS_STATS) ++ return; ++ ++ /* Output aggregate stats (computed from per-EID) */ ++ for (i = 0; i < MCTP_I2C_EID_NUM_STATS; i++) { ++ snprintf(data, ETH_GSTRING_LEN, "%-30s", mctp_i2c_eid_stat_descs[i].name); ++ data += ETH_GSTRING_LEN; ++ } ++ ++ /* Separator line */ ++ snprintf(data, ETH_GSTRING_LEN, " "); ++ data += ETH_GSTRING_LEN; ++ ++ /* Output per-EID stats (only for active EIDs with non-zero stats) */ ++ for_each_set_bit(eid, midev->eid_stats.active, 257) { ++ struct mctp_i2c_eid_stats *es = &midev->eid_stats.eid[eid]; ++ u8 *base = (u8 *)es; ++ int nz = mctp_i2c_count_eid_nonzero(midev, eid); ++ ++ if (nz == 0) ++ continue; ++ ++ /* EID header line with explanation ++ * - UNKNOWN: Errors where EID could not be determined (pre-parse ++ * errors like invalid command codes, PEC failures, or allocation ++ * failures before packet parsing) ++ * - EID_0: Valid packets with source/dest EID=0 (null/unallocated ++ * endpoint per MCTP spec DSP0236) ++ * - EID_X: Normal endpoints (X = 1..253) ++ */ ++ if (eid == MCTP_EID_UNKNOWN) { ++ snprintf(data, ETH_GSTRING_LEN, "UNKNOWN: corrupted/invalid pkt"); ++ } else if (eid == 0) { ++ snprintf(data, ETH_GSTRING_LEN, "EID_0: null endpoint "); ++ } else { ++ snprintf(data, ETH_GSTRING_LEN, "EID_%-3u ", eid); ++ } ++ data += ETH_GSTRING_LEN; ++ ++ /* Individual non-zero stats for this EID */ ++ for (i = 0; i < MCTP_I2C_EID_NUM_STATS; i++) { ++ u64 val = *(u64 *)(base + mctp_i2c_eid_stat_descs[i].offset); ++ if (val != 0) { ++ snprintf(data, ETH_GSTRING_LEN, "%-30s", ++ mctp_i2c_eid_stat_descs[i].name); ++ data += ETH_GSTRING_LEN; ++ } ++ } ++ } ++} ++ ++static int mctp_i2c_get_sset_count(struct net_device *ndev, int sset) ++{ ++ struct mctp_i2c_dev *midev = netdev_priv(ndev); ++ ++ if (sset == ETH_SS_STATS) ++ return MCTP_I2C_EID_NUM_STATS + 1 + mctp_i2c_count_eid_stats(midev); ++ /* aggregates */ /* separator */ /* per-EID */ ++ ++ return -EOPNOTSUPP; ++} ++ ++static void mctp_i2c_get_ethtool_stats(struct net_device *ndev, ++ struct ethtool_stats *stats, ++ u64 *data) ++{ ++ struct mctp_i2c_dev *midev = netdev_priv(ndev); ++ unsigned int i, idx = 0; ++ int eid; ++ ++ /* Output aggregate stats (computed by summing across all EIDs) */ ++ for (i = 0; i < MCTP_I2C_EID_NUM_STATS; i++) { ++ u64 total = 0; ++ ++ for_each_set_bit(eid, midev->eid_stats.active, 257) { ++ u8 *base = (u8 *)&midev->eid_stats.eid[eid]; ++ total += *(u64 *)(base + mctp_i2c_eid_stat_descs[i].offset); ++ } ++ data[idx++] = total; ++ } ++ ++ /* Separator (blank line in output) */ ++ data[idx++] = 0; ++ ++ /* Output per-EID stats (only for active EIDs with non-zero stats) */ ++ for_each_set_bit(eid, midev->eid_stats.active, 257) { ++ struct mctp_i2c_eid_stats *es = &midev->eid_stats.eid[eid]; ++ u8 *base = (u8 *)es; ++ int nz = mctp_i2c_count_eid_nonzero(midev, eid); ++ ++ if (nz == 0) ++ continue; ++ ++ /* EID header: value is sum of all stats for this EID */ ++ data[idx++] = mctp_i2c_eid_stats_total(midev, eid); ++ ++ /* Individual non-zero stats for this EID */ ++ for (i = 0; i < MCTP_I2C_EID_NUM_STATS; i++) { ++ u64 val = *(u64 *)(base + mctp_i2c_eid_stat_descs[i].offset); ++ if (val != 0) ++ data[idx++] = val; ++ } ++ } ++} ++ ++static const struct ethtool_ops mctp_i2c_ethtool_ops = { ++ .get_strings = mctp_i2c_get_strings, ++ .get_sset_count = mctp_i2c_get_sset_count, ++ .get_ethtool_stats = mctp_i2c_get_ethtool_stats, ++}; ++ + static const struct net_device_ops mctp_i2c_ops = { + .ndo_start_xmit = mctp_i2c_start_xmit, + .ndo_uninit = mctp_i2c_ndo_uninit, +@@ -728,7 +1071,105 @@ static void mctp_i2c_net_setup(struct net_device *dev) + + dev->netdev_ops = &mctp_i2c_ops; + dev->header_ops = &mctp_i2c_headops; ++ dev->ethtool_ops = &mctp_i2c_ethtool_ops; ++} ++ ++/* ---- sysfs attributes for NACK retry tuning ---- */ ++ ++static ssize_t nack_retries_show(struct device *dev, ++ struct device_attribute *attr, char *buf) ++{ ++ struct net_device *ndev = to_net_dev(dev); ++ struct mctp_i2c_dev *midev = netdev_priv(ndev); ++ ++ return sysfs_emit(buf, "%u\n", midev->nack_retries); ++} ++ ++static ssize_t nack_retries_store(struct device *dev, ++ struct device_attribute *attr, ++ const char *buf, size_t count) ++{ ++ struct net_device *ndev = to_net_dev(dev); ++ struct mctp_i2c_dev *midev = netdev_priv(ndev); ++ u32 val; ++ ++ if (kstrtou32(buf, 0, &val)) ++ return -EINVAL; ++ ++ if (val > NACK_RETRY_MAX) ++ val = NACK_RETRY_MAX; ++ ++ midev->nack_retries = val; ++ return count; + } ++static DEVICE_ATTR_RW(nack_retries); ++ ++static ssize_t nack_retry_delay_us_show(struct device *dev, ++ struct device_attribute *attr, char *buf) ++{ ++ struct net_device *ndev = to_net_dev(dev); ++ struct mctp_i2c_dev *midev = netdev_priv(ndev); ++ ++ return sysfs_emit(buf, "%u\n", midev->nack_retry_delay_us); ++} ++ ++static ssize_t nack_retry_delay_us_store(struct device *dev, ++ struct device_attribute *attr, ++ const char *buf, size_t count) ++{ ++ struct net_device *ndev = to_net_dev(dev); ++ struct mctp_i2c_dev *midev = netdev_priv(ndev); ++ u32 val; ++ ++ if (kstrtou32(buf, 0, &val)) ++ return -EINVAL; ++ ++ if (val > NACK_RETRY_DELAY_MAX_US) ++ val = NACK_RETRY_DELAY_MAX_US; ++ ++ midev->nack_retry_delay_us = val; ++ return count; ++} ++static DEVICE_ATTR_RW(nack_retry_delay_us); ++ ++static ssize_t nack_retry_addr_show(struct device *dev, ++ struct device_attribute *attr, char *buf) ++{ ++ struct net_device *ndev = to_net_dev(dev); ++ struct mctp_i2c_dev *midev = netdev_priv(ndev); ++ ++ return sysfs_emit(buf, "0x%02x\n", midev->nack_retry_addr); ++} ++ ++static ssize_t nack_retry_addr_store(struct device *dev, ++ struct device_attribute *attr, ++ const char *buf, size_t count) ++{ ++ struct net_device *ndev = to_net_dev(dev); ++ struct mctp_i2c_dev *midev = netdev_priv(ndev); ++ u32 val; ++ ++ if (kstrtou32(buf, 0, &val)) ++ return -EINVAL; ++ ++ if (val > 0x7f) ++ val = NACK_RETRY_ADDR_DISABLED; ++ ++ midev->nack_retry_addr = val; ++ return count; ++} ++static DEVICE_ATTR_RW(nack_retry_addr); ++ ++static struct attribute *mctp_i2c_nack_attrs[] = { ++ &dev_attr_nack_retries.attr, ++ &dev_attr_nack_retry_delay_us.attr, ++ &dev_attr_nack_retry_addr.attr, ++ NULL, ++}; ++ ++static const struct attribute_group mctp_i2c_nack_group = { ++ .attrs = mctp_i2c_nack_attrs, ++}; + + /* Populates the mctp_i2c_dev priv struct for a netdev. + * Returns an error pointer on failure. +@@ -750,6 +1191,7 @@ static struct mctp_i2c_dev *mctp_i2c_midev_init(struct net_device *dev, + midev->adapter = adap; + get_device(&mcli->client->dev); + midev->client = mcli; ++ midev->flows_enabled = true; /* Default to enabled */ + INIT_LIST_HEAD(&midev->list); + spin_lock_init(&midev->lock); + midev->i2c_lock_count = 0; +@@ -770,6 +1212,30 @@ static struct mctp_i2c_dev *mctp_i2c_midev_init(struct net_device *dev, + /* Start the worker thread */ + wake_up_process(midev->tx_thread); + ++ /* Read NACK retry params from the MCTP I2C client's DT node */ ++ if (mcli->client->dev.of_node) { ++ if (of_property_read_u32(mcli->client->dev.of_node, "i2c-nack-retries", &midev->nack_retries)) ++ midev->nack_retries = 0; ++ else if (midev->nack_retries > NACK_RETRY_MAX) ++ midev->nack_retries = NACK_RETRY_MAX; ++ ++ if (of_property_read_u32(mcli->client->dev.of_node, "i2c-nack-retry-delay-us", &midev->nack_retry_delay_us)) ++ midev->nack_retry_delay_us = 0; ++ else if (midev->nack_retry_delay_us > NACK_RETRY_DELAY_MAX_US) ++ midev->nack_retry_delay_us = NACK_RETRY_DELAY_MAX_US; ++ ++ if (of_property_read_u32(mcli->client->dev.of_node, "i2c-nack-retry-addr", &midev->nack_retry_addr)) ++ midev->nack_retry_addr = NACK_RETRY_ADDR_DISABLED; ++ else if (midev->nack_retry_addr > 0x7f) ++ midev->nack_retry_addr = NACK_RETRY_ADDR_DISABLED; ++ } else { ++ midev->nack_retry_addr = NACK_RETRY_ADDR_DISABLED; ++ } ++ ++ netdev_info(dev, "NACK retry config: retries=%u delay=%uus addr=0x%02x\n", ++ midev->nack_retries, midev->nack_retry_delay_us, ++ midev->nack_retry_addr); ++ + return midev; + } + +@@ -787,6 +1253,8 @@ static void mctp_i2c_midev_free(struct mctp_i2c_dev *midev) + /* Unconditionally unlock on close */ + mctp_i2c_unlock_reset(midev); + ++ sysfs_remove_group(&midev->ndev->dev.kobj, &mctp_i2c_nack_group); ++ + /* Remove the netdev from the parent i2c client. */ + spin_lock_irqsave(&mcli->sel_lock, flags); + list_del(&midev->list); +@@ -808,6 +1276,9 @@ static void mctp_i2c_unregister(struct mctp_i2c_dev *midev) + { + unsigned long flags; + ++ /* Cleanup error injection */ ++ mctp_i2c_error_inject_cleanup(midev); ++ + /* Stop tx thread prior to unregister, it uses netif_() functions */ + kthread_stop(midev->tx_thread); + midev->tx_thread = NULL; +@@ -885,7 +1356,8 @@ static int mctp_i2c_add_netdev(struct mctp_i2c_client *mcli, + goto err; + } + +- rc = mctp_register_netdev(ndev, &mctp_i2c_mctp_ops); ++ rc = mctp_register_netdev(ndev, &mctp_i2c_mctp_ops, ++ MCTP_PHYS_BINDING_SMBUS); + if (rc < 0) { + dev_err(&mcli->client->dev, + "register netdev \"%s\" failed %d\n", +@@ -893,6 +1365,26 @@ static int mctp_i2c_add_netdev(struct mctp_i2c_client *mcli, + goto err; + } + ++ if (adap->dev.of_node) { ++ u32 timeout_ms; ++ ++ if (!of_property_read_u32(adap->dev.of_node, ++ "mctp-timeout-ms", &timeout_ms)) { ++ mctp_dev_set_timeout(ndev, timeout_ms); ++ } ++ ++ if (of_property_read_bool(adap->dev.of_node, "mctp-no-flows")) { ++ midev->flows_enabled = false; ++ dev_info(&mcli->client->dev, "MCTP flows disabled by DTS\n"); ++ } ++ } ++ ++ /* Setup error injection after netdev registration (debugfs needs the netdev name) */ ++ mctp_i2c_error_inject_init(midev); ++ ++ if (sysfs_create_group(&ndev->dev.kobj, &mctp_i2c_nack_group)) ++ dev_warn(&mcli->client->dev, "Failed to create NACK sysfs attrs\n"); ++ + spin_lock_irqsave(&midev->lock, flags); + midev->allow_rx = false; + spin_unlock_irqrestore(&midev->lock, flags); +@@ -1090,6 +1582,44 @@ static struct notifier_block mctp_i2c_notifier = { + .notifier_call = mctp_i2c_notifier_call, + }; + ++/* Netdevice notifier to re-attach ops after namespace change. ++ * When a netdev moves namespaces, NETDEV_UNREGISTER fires in the old ns ++ * which destroys the mctp_dev, then NETDEV_REGISTER fires in the new ns ++ * which creates a new mctp_dev but without the ops pointer. ++ * We need to re-attach the ops here. ++ */ ++static int mctp_i2c_netdev_notify(struct notifier_block *nb, ++ unsigned long event, void *ptr) ++{ ++ struct net_device *dev = netdev_notifier_info_to_dev(ptr); ++ struct mctp_dev *mdev; ++ ++ if (event != NETDEV_REGISTER) ++ return NOTIFY_DONE; ++ ++ /* Only handle our devices - check if this is an mctp-i2c netdev */ ++ if (dev->netdev_ops != &mctp_i2c_ops) ++ return NOTIFY_DONE; ++ ++ /* Get the mctp_dev and re-attach the ops */ ++ rcu_read_lock(); ++ mdev = __mctp_dev_get(dev); ++ if (mdev) { ++ if (!mdev->ops) { ++ mdev->ops = &mctp_i2c_mctp_ops; ++ netdev_info(dev, "re-attached mctp ops after namespace change\n"); ++ } ++ mctp_dev_put(mdev); ++ } ++ rcu_read_unlock(); ++ ++ return NOTIFY_DONE; ++} ++ ++static struct notifier_block mctp_i2c_netdev_nb = { ++ .notifier_call = mctp_i2c_netdev_notify, ++}; ++ + static const struct i2c_device_id mctp_i2c_id[] = { + { "mctp-i2c-interface" }, + {} +@@ -1117,12 +1647,29 @@ static __init int mctp_i2c_mod_init(void) + int rc; + + pr_info("MCTP I2C interface driver\n"); ++ ++ /* Initialize error injection infrastructure */ ++ rc = mctp_i2c_error_inject_module_init(); ++ if (rc) ++ pr_warn("MCTP I2C: Error injection initialization failed, continuing without it\n"); ++ + rc = i2c_add_driver(&mctp_i2c_driver); +- if (rc < 0) ++ if (rc < 0) { ++ mctp_i2c_error_inject_module_exit(); + return rc; ++ } + rc = bus_register_notifier(&i2c_bus_type, &mctp_i2c_notifier); + if (rc < 0) { + i2c_del_driver(&mctp_i2c_driver); ++ mctp_i2c_error_inject_module_exit(); ++ return rc; ++ } ++ /* Register netdev notifier to handle namespace changes */ ++ rc = register_netdevice_notifier(&mctp_i2c_netdev_nb); ++ if (rc < 0) { ++ bus_unregister_notifier(&i2c_bus_type, &mctp_i2c_notifier); ++ i2c_del_driver(&mctp_i2c_driver); ++ mctp_i2c_error_inject_module_exit(); + return rc; + } + return 0; +@@ -1132,10 +1679,12 @@ static __exit void mctp_i2c_mod_exit(void) + { + int rc; + ++ unregister_netdevice_notifier(&mctp_i2c_netdev_nb); + rc = bus_unregister_notifier(&i2c_bus_type, &mctp_i2c_notifier); + if (rc < 0) + pr_warn("MCTP I2C could not unregister notifier, %d\n", rc); + i2c_del_driver(&mctp_i2c_driver); ++ mctp_i2c_error_inject_module_exit(); + } + + module_init(mctp_i2c_mod_init); +diff --git a/drivers/net/mctp/mctp-i3c.c b/drivers/net/mctp/mctp-i3c.c +index 47513ebbc..00038ad69 100644 +--- a/drivers/net/mctp/mctp-i3c.c ++++ b/drivers/net/mctp/mctp-i3c.c +@@ -14,9 +14,13 @@ + #include + #include + #include ++#include + #include + #include + ++#include "mctp-stats.h" ++#include ++ + #define MCTP_I3C_MAXBUF 65536 + /* 48 bit Provisioned Id */ + #define PID_SIZE 6 +@@ -67,6 +71,45 @@ struct mctp_i3c_bus { + struct i3c_bus *bus; + /* Head of mctp_i3c_device.list. Protected by busdevs_lock */ + struct list_head devs; ++ ++ /* Per-EID statistics tracking - SINGLE source of truth ++ * ++ * All statistics are tracked per-endpoint-ID (EID). Two special EIDs: ++ * - EID 0: "null endpoint" - valid packets with EID=0 (unallocated endpoint) ++ * - EID 256 (MCTP_EID_UNKNOWN): errors where EID could not be determined ++ * (IBI events, device hotplug, allocation failures, pre-parse errors) ++ */ ++ struct { ++ DECLARE_BITMAP(active, 257); /* Which EIDs have activity */ ++ struct mctp_i3c_eid_stats { ++ /* RX stats */ ++ u64 rx_drop_no_memory; ++ u64 rx_drop_length_error; /* Tracked as UNKNOWN (before EID parsed) */ ++ u64 rx_drop_pec_error; /* Tracked as UNKNOWN */ ++ u64 rx_ibi_received; /* Total IBIs received */ ++ u64 rx_ibi_missing_mdb; /* IBI without MDB (UNKNOWN) */ ++ u64 rx_early_exit_warn_once; /* IBI warning triggered (UNKNOWN) */ ++ ++ /* TX stats */ ++ u64 tx_drop_no_device; /* Device lookup failed */ ++ u64 tx_drop_mwl_exceeded; /* Exceeds device MWL */ ++ u64 tx_drop_io_error; /* I3C transfer failed (EIO) */ ++ u64 tx_drop_enomem; ++ u64 tx_drop_eagain; ++ u64 tx_drop_einval; ++ u64 tx_drop_eio; ++ u64 tx_drop_enxio; ++ u64 tx_drop_ebusy; ++ u64 tx_drop_enotsupp; ++ u64 tx_drop_emsgsize; ++ u64 tx_early_exit_queue_stopped; /* Queue already stopped */ ++ ++ /* Device management (global, tracked under UNKNOWN) */ ++ u64 devices_active; /* Current active devices */ ++ u64 devices_added; /* Total devices added */ ++ u64 devices_removed; /* Total devices removed */ ++ } eid[257]; ++ } eid_stats; + }; + + struct mctp_i3c_device { +@@ -100,6 +143,7 @@ struct mctp_i3c_internal_hdr { + static int mctp_i3c_read(struct mctp_i3c_device *mi) + { + struct i3c_priv_xfer xfer = { .rnw = 1, .len = mi->mrl }; ++ struct net_device *ndev = mi->mbus->ndev; + struct net_device_stats *stats = &mi->mbus->ndev->stats; + struct mctp_i3c_internal_hdr *ihdr = NULL; + struct sk_buff *skb = NULL; +@@ -111,6 +155,9 @@ static int mctp_i3c_read(struct mctp_i3c_device *mi) + mi->mrl + sizeof(struct mctp_i3c_internal_hdr)); + if (!skb) { + stats->rx_dropped++; ++ /* Can't extract EID - no packet data received yet */ ++ MCTP_STAT_INC(mi->mbus, MCTP_EID_UNKNOWN, rx_drop_no_memory); ++ trace_mctp_transport_error("i3c", ndev, "rx_drop_no_memory", mi->mrl); + rc = -ENOMEM; + goto err; + } +@@ -128,16 +175,28 @@ static int mctp_i3c_read(struct mctp_i3c_device *mi) + /* Make sure netif_rx() is read in the same order as i3c. */ + mutex_lock(&mi->lock); + rc = i3c_device_do_priv_xfers(mi->i3c, &xfer, 1); +- if (rc < 0) ++ if (rc < 0) { ++ trace_mctp_transport_error("i3c", ndev, "rx_i3c_xfer_failed", rc); + goto err; ++ } + + if (WARN_ON_ONCE(xfer.len > mi->mrl)) { + /* Bad i3c bus driver */ ++ trace_mctp_transport_error("i3c", ndev, "rx_len_exceeds_mrl", xfer.len); + rc = -EIO; + goto err; + } + if (xfer.len < MCTP_I3C_MINLEN) { + stats->rx_length_errors++; ++ /* Try to extract EID if we have at least MCTP header */ ++ if (xfer.len >= sizeof(struct mctp_hdr)) { ++ struct mctp_hdr *mh = (struct mctp_hdr *)xfer.data.in; ++ MCTP_STAT_INC(mi->mbus, mh->src, rx_drop_length_error); ++ } else { ++ MCTP_STAT_INC(mi->mbus, MCTP_EID_UNKNOWN, rx_drop_length_error); ++ } ++ netdev_dbg(ndev, "MCTP I3C: RX length error %d\n", xfer.len); ++ trace_mctp_transport_error("i3c", ndev, "rx_length_error", xfer.len); + rc = -EIO; + goto err; + } +@@ -148,6 +207,15 @@ static int mctp_i3c_read(struct mctp_i3c_device *mi) + pec = i2c_smbus_pec(pec, xfer.data.in, xfer.len - 1); + if (pec != ((u8 *)xfer.data.in)[xfer.len - 1]) { + stats->rx_crc_errors++; ++ /* Extract EID for per-EID tracking (we have valid length, attempt EID extraction) */ ++ if (xfer.len >= sizeof(struct mctp_hdr) + 1) { /* +1 for PEC */ ++ struct mctp_hdr *mh = (struct mctp_hdr *)xfer.data.in; ++ MCTP_STAT_INC(mi->mbus, mh->src, rx_drop_pec_error); ++ } else { ++ MCTP_STAT_INC(mi->mbus, MCTP_EID_UNKNOWN, rx_drop_pec_error); ++ } ++ netdev_dbg(ndev, "MCTP I3C: PEC error\n"); ++ trace_mctp_transport_error("i3c", ndev, "pec_error", pec); + rc = -EINVAL; + goto err; + } +@@ -164,8 +232,12 @@ static int mctp_i3c_read(struct mctp_i3c_device *mi) + if (net_status == NET_RX_SUCCESS) { + stats->rx_packets++; + stats->rx_bytes += xfer.len - 1; ++ netdev_dbg(ndev, "MCTP I3C: RX success, %u bytes\n", xfer.len - 1); ++ trace_mctp_transport_rx("i3c", ndev, 0, xfer.len - 1); + } else { + stats->rx_dropped++; ++ netdev_dbg(ndev, "MCTP I3C: RX dropped, status=%d\n", net_status); ++ trace_mctp_transport_error("i3c", ndev, "rx_dropped", net_status); + } + + mutex_unlock(&mi->lock); +@@ -181,8 +253,15 @@ static void mctp_i3c_ibi_handler(struct i3c_device *i3c, + { + struct mctp_i3c_device *mi = i3cdev_get_drvdata(i3c); + +- if (WARN_ON_ONCE(!mi)) ++ if (WARN_ON_ONCE(!mi)) { ++ /* Track early exit due to missing device data - no EID context */ ++ if (mi && mi->mbus) ++ MCTP_STAT_INC(mi->mbus, MCTP_EID_UNKNOWN, rx_early_exit_warn_once); + return; ++ } ++ ++ /* IBI received - tracked as UNKNOWN (no EID context at IBI time) */ ++ MCTP_STAT_INC(mi->mbus, MCTP_EID_UNKNOWN, rx_ibi_received); + + if (mi->have_mdb) { + if (payload->len > 0) { +@@ -195,6 +274,7 @@ static void mctp_i3c_ibi_handler(struct i3c_device *i3c, + * device didn't send one. + */ + dev_warn_once(i3cdev_to_dev(i3c), "IBI with missing MDB"); ++ MCTP_STAT_INC(mi->mbus, MCTP_EID_UNKNOWN, rx_ibi_missing_mdb); + } + } + +@@ -268,6 +348,9 @@ __must_hold(&busdevs_lock) + mi->i3c = i3c; + mutex_init(&mi->lock); + list_add(&mi->list, &mbus->devs); ++ /* Device management stats tracked as UNKNOWN (no EID context) */ ++ MCTP_STAT_INC(mbus, MCTP_EID_UNKNOWN, devices_added); ++ MCTP_STAT_INC(mbus, MCTP_EID_UNKNOWN, devices_active); + + i3cdev_set_drvdata(i3c, mi); + rc = mctp_i3c_setup(mi); +@@ -319,6 +402,10 @@ __must_hold(&busdevs_lock) + /* Counterpart of mctp_i3c_add_device */ + i3cdev_set_drvdata(mi->i3c, NULL); + list_del(&mi->list); ++ /* Device management stats tracked as UNKNOWN (no EID context) */ ++ MCTP_STAT_INC(mi->mbus, MCTP_EID_UNKNOWN, devices_removed); ++ mi->mbus->eid_stats.eid[MCTP_EID_UNKNOWN].devices_active--; ++ set_bit(MCTP_EID_UNKNOWN, mi->mbus->eid_stats.active); + + /* Safe to unlock after removing from the list */ + mutex_unlock(&mi->lock); +@@ -359,19 +446,26 @@ mctp_i3c_lookup(struct mctp_i3c_bus *mbus, u64 pid) + + static void mctp_i3c_xmit(struct mctp_i3c_bus *mbus, struct sk_buff *skb) + { ++ struct net_device *ndev = mbus->ndev; + struct net_device_stats *stats = &mbus->ndev->stats; + struct i3c_priv_xfer xfer = { .rnw = false }; + struct mctp_i3c_internal_hdr *ihdr = NULL; + struct mctp_i3c_device *mi = NULL; ++ struct mctp_hdr *mh; + unsigned int data_len; + u8 *data = NULL; + u8 addr, pec; ++ u8 dest_eid; + int rc = 0; + u64 pid; + + skb_pull(skb, sizeof(struct mctp_i3c_internal_hdr)); + data_len = skb->len; + ++ /* Extract destination EID for per-EID tracking */ ++ mh = mctp_hdr(skb); ++ dest_eid = mh->dest; ++ + ihdr = (void *)skb_mac_header(skb); + + pid = get_unaligned_be48(ihdr->dest); +@@ -379,15 +473,21 @@ static void mctp_i3c_xmit(struct mctp_i3c_bus *mbus, struct sk_buff *skb) + if (!mi) { + /* I3C endpoint went away after the packet was enqueued? */ + stats->tx_dropped++; ++ MCTP_STAT_INC(mbus, dest_eid, tx_drop_no_device); ++ trace_mctp_transport_error("i3c", ndev, "tx_no_device", 0); + goto out; + } + +- if (WARN_ON_ONCE(data_len + 1 > MCTP_I3C_MAXBUF)) ++ if (WARN_ON_ONCE(data_len + 1 > MCTP_I3C_MAXBUF)) { ++ trace_mctp_transport_error("i3c", ndev, "tx_len_exceeds_maxbuf", data_len); + goto out; ++ } + + if (data_len + 1 > (unsigned int)mi->mwl) { + /* Route MTU was larger than supported by the endpoint */ + stats->tx_dropped++; ++ MCTP_STAT_INC(mbus, dest_eid, tx_drop_mwl_exceeded); ++ trace_mctp_transport_error("i3c", ndev, "tx_len_exceeds_mwl", data_len); + goto out; + } + +@@ -413,8 +513,31 @@ static void mctp_i3c_xmit(struct mctp_i3c_bus *mbus, struct sk_buff *skb) + if (rc == 0) { + stats->tx_bytes += data_len; + stats->tx_packets++; ++ netdev_dbg(ndev, "MCTP I3C: TX success, %u bytes\n", data_len); ++ trace_mctp_transport_tx("i3c", ndev, 0, data_len); + } else { + stats->tx_errors++; ++ if (rc == -ENXIO) { ++ MCTP_STAT_INC(mbus, dest_eid, tx_drop_enxio); ++ } else if (rc == -EAGAIN) { ++ MCTP_STAT_INC(mbus, dest_eid, tx_drop_eagain); ++ } else if (rc == -EBUSY) { ++ MCTP_STAT_INC(mbus, dest_eid, tx_drop_ebusy); ++ } else if (rc == -ENOTSUPP || rc == -EOPNOTSUPP) { ++ MCTP_STAT_INC(mbus, dest_eid, tx_drop_enotsupp); ++ } else if (rc == -EINVAL) { ++ MCTP_STAT_INC(mbus, dest_eid, tx_drop_einval); ++ } else if (rc == -ENOMEM) { ++ MCTP_STAT_INC(mbus, dest_eid, tx_drop_enomem); ++ } else if (rc == -EMSGSIZE) { ++ MCTP_STAT_INC(mbus, dest_eid, tx_drop_emsgsize); ++ } else if (rc == -EIO) { ++ MCTP_STAT_INC(mbus, dest_eid, tx_drop_eio); ++ } else { ++ MCTP_STAT_INC(mbus, dest_eid, tx_drop_io_error); ++ } ++ netdev_dbg(ndev, "MCTP I3C: TX failed, error=%d\n", rc); ++ trace_mctp_transport_error("i3c", ndev, "i3c_xfer_failed", rc); + } + + out: +@@ -461,6 +584,13 @@ static netdev_tx_t mctp_i3c_start_xmit(struct sk_buff *skb, + netif_stop_queue(ndev); + if (mbus->tx_skb) { + dev_warn_ratelimited(&ndev->dev, "TX with queue stopped"); ++ /* Extract dest EID for tracking */ ++ struct mctp_i3c_internal_hdr *ihdr = (void *)skb_mac_header(skb); ++ struct mctp_hdr *mh; ++ skb_pull(skb, sizeof(struct mctp_i3c_internal_hdr)); ++ mh = mctp_hdr(skb); ++ MCTP_STAT_INC(mbus, mh->dest, tx_early_exit_queue_stopped); ++ skb_push(skb, sizeof(struct mctp_i3c_internal_hdr)); + ret = NETDEV_TX_BUSY; + } else { + mbus->tx_skb = skb; +@@ -523,6 +653,165 @@ static int mctp_i3c_header_create(struct sk_buff *skb, struct net_device *dev, + return 0; + } + ++/* Ethtool statistics support - Per-EID stat descriptors with abbreviated names */ ++struct mctp_i3c_eid_stat_desc { ++ const char *name; ++ size_t offset; ++}; ++ ++#define MCTP_I3C_EID_STAT(abbrev, field) { \ ++ .name = abbrev, \ ++ .offset = offsetof(struct mctp_i3c_eid_stats, field) \ ++} ++ ++static const struct mctp_i3c_eid_stat_desc mctp_i3c_eid_stat_descs[] = { ++ MCTP_I3C_EID_STAT("rx_drop_no_memory", rx_drop_no_memory), ++ MCTP_I3C_EID_STAT("rx_drop_length_error", rx_drop_length_error), ++ MCTP_I3C_EID_STAT("rx_drop_pec_error", rx_drop_pec_error), ++ MCTP_I3C_EID_STAT("rx_ibi_received", rx_ibi_received), ++ MCTP_I3C_EID_STAT("rx_ibi_missing_mdb", rx_ibi_missing_mdb), ++ MCTP_I3C_EID_STAT("rx_early_exit_warn_once", rx_early_exit_warn_once), ++ MCTP_I3C_EID_STAT("tx_drop_no_device", tx_drop_no_device), ++ MCTP_I3C_EID_STAT("tx_drop_mwl_exceeded", tx_drop_mwl_exceeded), ++ MCTP_I3C_EID_STAT("tx_drop_io_error", tx_drop_io_error), ++ MCTP_I3C_EID_STAT("tx_drop_enomem", tx_drop_enomem), ++ MCTP_I3C_EID_STAT("tx_drop_eagain", tx_drop_eagain), ++ MCTP_I3C_EID_STAT("tx_drop_einval", tx_drop_einval), ++ MCTP_I3C_EID_STAT("tx_drop_eio", tx_drop_eio), ++ MCTP_I3C_EID_STAT("tx_drop_enxio", tx_drop_enxio), ++ MCTP_I3C_EID_STAT("tx_drop_ebusy", tx_drop_ebusy), ++ MCTP_I3C_EID_STAT("tx_drop_enotsupp", tx_drop_enotsupp), ++ MCTP_I3C_EID_STAT("tx_drop_emsgsize", tx_drop_emsgsize), ++ MCTP_I3C_EID_STAT("tx_early_exit_queue_stopped", tx_early_exit_queue_stopped), ++ MCTP_I3C_EID_STAT("devices_active", devices_active), ++ MCTP_I3C_EID_STAT("devices_added", devices_added), ++ MCTP_I3C_EID_STAT("devices_removed", devices_removed), ++}; ++ ++#define MCTP_I3C_EID_NUM_STATS ARRAY_SIZE(mctp_i3c_eid_stat_descs) ++ ++/* Generate ethtool helper functions using shared macros */ ++MCTP_EID_STATS_HELPERS(mctp_i3c, struct mctp_i3c_bus, ++ struct mctp_i3c_eid_stats, mctp_i3c_eid_stat_descs) ++ ++static void mctp_i3c_get_strings(struct net_device *ndev, u32 stringset, ++ u8 *data) ++{ ++ struct mctp_i3c_bus *mbus = netdev_priv(ndev); ++ unsigned int i; ++ int eid; ++ ++ if (stringset != ETH_SS_STATS) ++ return; ++ ++ /* Output aggregate stats (computed from per-EID) */ ++ for (i = 0; i < MCTP_I3C_EID_NUM_STATS; i++) { ++ snprintf(data, ETH_GSTRING_LEN, "%-30s", mctp_i3c_eid_stat_descs[i].name); ++ data += ETH_GSTRING_LEN; ++ } ++ ++ /* Separator line */ ++ snprintf(data, ETH_GSTRING_LEN, " "); ++ data += ETH_GSTRING_LEN; ++ ++ /* Output per-EID stats (only for active EIDs with non-zero stats) */ ++ for_each_set_bit(eid, mbus->eid_stats.active, 257) { ++ struct mctp_i3c_eid_stats *es = &mbus->eid_stats.eid[eid]; ++ u8 *base = (u8 *)es; ++ int nz = mctp_i3c_count_eid_nonzero(mbus, eid); ++ ++ if (nz == 0) ++ continue; ++ ++ /* EID header line with explanation ++ * - UNKNOWN: Errors where EID could not be determined (I3C IBI ++ * in-band interrupts, device hotplug events, allocation failures, ++ * or pre-parse errors like invalid packet length) ++ * - EID_0: Valid packets with source/dest EID=0 (null/unallocated ++ * endpoint per MCTP spec DSP0236) ++ * - EID_X: Normal endpoints (X = 1..253) ++ */ ++ if (eid == MCTP_EID_UNKNOWN) { ++ snprintf(data, ETH_GSTRING_LEN, "UNKNOWN: IBI/device events "); ++ } else if (eid == 0) { ++ snprintf(data, ETH_GSTRING_LEN, "EID_0: null endpoint "); ++ } else { ++ snprintf(data, ETH_GSTRING_LEN, "EID_%-3u ", eid); ++ } ++ data += ETH_GSTRING_LEN; ++ ++ /* Individual non-zero stats for this EID */ ++ for (i = 0; i < MCTP_I3C_EID_NUM_STATS; i++) { ++ u64 val = *(u64 *)(base + mctp_i3c_eid_stat_descs[i].offset); ++ if (val != 0) { ++ snprintf(data, ETH_GSTRING_LEN, "%-30s", ++ mctp_i3c_eid_stat_descs[i].name); ++ data += ETH_GSTRING_LEN; ++ } ++ } ++ } ++} ++ ++static int mctp_i3c_get_sset_count(struct net_device *ndev, int sset) ++{ ++ struct mctp_i3c_bus *mbus = netdev_priv(ndev); ++ ++ if (sset == ETH_SS_STATS) ++ return MCTP_I3C_EID_NUM_STATS + 1 + mctp_i3c_count_eid_stats(mbus); ++ /* aggregates */ /* separator */ /* per-EID */ ++ ++ return -EOPNOTSUPP; ++} ++ ++static void mctp_i3c_get_ethtool_stats(struct net_device *ndev, ++ struct ethtool_stats *stats, ++ u64 *data) ++{ ++ struct mctp_i3c_bus *mbus = netdev_priv(ndev); ++ unsigned int i, idx = 0; ++ int eid; ++ ++ /* Output aggregate stats (computed by summing across all EIDs) */ ++ for (i = 0; i < MCTP_I3C_EID_NUM_STATS; i++) { ++ u64 total = 0; ++ ++ for_each_set_bit(eid, mbus->eid_stats.active, 257) { ++ u8 *base = (u8 *)&mbus->eid_stats.eid[eid]; ++ total += *(u64 *)(base + mctp_i3c_eid_stat_descs[i].offset); ++ } ++ data[idx++] = total; ++ } ++ ++ /* Separator (blank line in output) */ ++ data[idx++] = 0; ++ ++ /* Output per-EID stats (only for active EIDs with non-zero stats) */ ++ for_each_set_bit(eid, mbus->eid_stats.active, 257) { ++ struct mctp_i3c_eid_stats *es = &mbus->eid_stats.eid[eid]; ++ u8 *base = (u8 *)es; ++ int nz = mctp_i3c_count_eid_nonzero(mbus, eid); ++ ++ if (nz == 0) ++ continue; ++ ++ /* EID header: value is sum of all stats for this EID */ ++ data[idx++] = mctp_i3c_eid_stats_total(mbus, eid); ++ ++ /* Individual non-zero stats for this EID */ ++ for (i = 0; i < MCTP_I3C_EID_NUM_STATS; i++) { ++ u64 val = *(u64 *)(base + mctp_i3c_eid_stat_descs[i].offset); ++ if (val != 0) ++ data[idx++] = val; ++ } ++ } ++} ++ ++static const struct ethtool_ops mctp_i3c_ethtool_ops = { ++ .get_strings = mctp_i3c_get_strings, ++ .get_sset_count = mctp_i3c_get_sset_count, ++ .get_ethtool_stats = mctp_i3c_get_ethtool_stats, ++}; ++ + static const struct net_device_ops mctp_i3c_ops = { + .ndo_start_xmit = mctp_i3c_start_xmit, + .ndo_uninit = mctp_i3c_ndo_uninit, +@@ -546,6 +835,7 @@ static void mctp_i3c_net_setup(struct net_device *dev) + + dev->netdev_ops = &mctp_i3c_ops; + dev->header_ops = &mctp_i3c_headops; ++ dev->ethtool_ops = &mctp_i3c_ethtool_ops; + } + + static bool mctp_i3c_is_mctp_controller(struct i3c_bus *bus) +@@ -619,7 +909,7 @@ __must_hold(&busdevs_lock) + goto err_free_uninit; + } + +- rc = mctp_register_netdev(ndev, NULL); ++ rc = mctp_register_netdev(ndev, NULL, MCTP_PHYS_BINDING_I3C); + if (rc < 0) { + dev_warn(&ndev->dev, "netdev register failed: %d\n", rc); + goto err_free_netdev; +diff --git a/drivers/net/mctp/mctp-serial.c b/drivers/net/mctp/mctp-serial.c +index e63720ec3..26c9a33fd 100644 +--- a/drivers/net/mctp/mctp-serial.c ++++ b/drivers/net/mctp/mctp-serial.c +@@ -23,6 +23,7 @@ + + #include + #include ++#include + #include + + #define MCTP_SERIAL_MTU 68 /* base mtu (64) + mctp header */ +@@ -470,7 +471,7 @@ static int mctp_serial_open(struct tty_struct *tty) + spin_lock_init(&dev->lock); + INIT_WORK(&dev->tx_work, mctp_serial_tx_work); + +- rc = register_netdev(ndev); ++ rc = mctp_register_netdev(ndev, NULL, MCTP_PHYS_BINDING_SERIAL); + if (rc) + goto free_netdev; + +@@ -492,7 +493,7 @@ static void mctp_serial_close(struct tty_struct *tty) + struct mctp_serial *dev = tty->disc_data; + int idx = dev->idx; + +- unregister_netdev(dev->netdev); ++ mctp_unregister_netdev(dev->netdev); + ida_free(&mctp_serial_ida, idx); + } + +diff --git a/drivers/net/mctp/mctp-spi-error-inject.c b/drivers/net/mctp/mctp-spi-error-inject.c +new file mode 100644 +index 000000000..b342c4744 +--- /dev/null ++++ b/drivers/net/mctp/mctp-spi-error-inject.c +@@ -0,0 +1,596 @@ ++// SPDX-License-Identifier: GPL-2.0 ++/* ++ * MCTP SPI Error Injection Implementation ++ * ++ * Copyright (c) 2024 NVIDIA CORPORATION. All rights reserved. ++ * ++ * Provides debugfs-based error injection for testing MCTP error queue ++ * functionality over SPI binding. ++ * ++ * Interface unified with I2C/USB error injection where applicable. ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include "mctp-spi-internal.h" ++ ++/* Forward declare SpbApStatus enum values - avoid including glacier-spb-ap.h ++ * to prevent multiple definition linker errors */ ++#define SPB_AP_OK 0 ++#define SPB_AP_MESSAGE_AVAILABLE 1 ++#define SPB_AP_ERROR_INVALID_ARGUMENT 2 ++#define SPB_AP_ERROR_TIMEOUT 3 ++#define SPB_AP_ERROR_UNKNOWN 4 ++ ++/* Constants from mctp-spi.c */ ++#define BUFSIZE 256 ++ ++/* Global debugfs root for all MCTP SPI error injection */ ++static struct dentry *mctp_spi_error_inject_root; ++ ++/* Check if error should be injected based on mode and rate */ ++static bool mctp_should_inject_error(struct mctp_spi_error_inject *ei, u32 rate, u32 *count) ++{ ++ bool inject = false; ++ ++ spin_lock_bh(&ei->lock); ++ ++ switch (ei->mode) { ++ case MCTP_ERR_MODE_ALWAYS: ++ inject = true; ++ break; ++ ++ case MCTP_ERR_MODE_RANDOM: ++ /* Random injection based on rate percentage */ ++ inject = (prandom_u32_state(&ei->rng) % 100) < rate; ++ break; ++ ++ case MCTP_ERR_MODE_COUNT: ++ /* Inject for count packets, then stop */ ++ if (*count > 0) { ++ (*count)--; ++ inject = true; ++ } ++ break; ++ } ++ ++ spin_unlock_bh(&ei->lock); ++ ++ return inject; ++} ++ ++/* Check if packet matches EID filter */ ++static bool mctp_error_inject_match_filter(struct mctp_spi_error_inject *ei, ++ struct sk_buff *skb) ++{ ++ struct mctp_hdr *mh; ++ struct mctp_spi_hdr { ++ u8 command_code; ++ u8 byte_count; ++ u8 resrv[2]; ++ } *spi_hdr; ++ ++ if (!ei->eid_filter.enabled) ++ return true; ++ ++ /* Skip SPI header to get to MCTP header */ ++ spi_hdr = (void *)skb->data; ++ mh = (struct mctp_hdr *)(skb->data + sizeof(*spi_hdr)); ++ ++ /* Check source EID */ ++ if (ei->eid_filter.src_eid != 0 && ++ ei->eid_filter.src_eid != mh->src) ++ return false; ++ ++ /* Check dest EID */ ++ if (ei->eid_filter.dest_eid != 0 && ++ ei->eid_filter.dest_eid != mh->dest) ++ return false; ++ ++ return true; ++} ++ ++/** ++ * mctp_spi_error_inject_tx - Inject TX error ++ * @midev: MCTP SPI device ++ * @skb: Packet being transmitted ++ * @injected_status: Output - SPB_AP status code to inject ++ * ++ * Returns: 0 for normal operation, 1 to inject error ++ * ++ * Called before spb_ap_send() to potentially inject a TX error. ++ */ ++int mctp_spi_error_inject_tx(struct mctp_spi *midev, struct sk_buff *skb, int *injected_status) ++{ ++ struct mctp_spi_error_inject *ei = &midev->error_inject; ++ struct mctp_hdr *mh; ++ struct mctp_spi_hdr { ++ u8 command_code; ++ u8 byte_count; ++ u8 resrv[2]; ++ } *spi_hdr; ++ ++ /* Early exit if TX error injection is disabled or code not set */ ++ if (!ei->enable_tx || ei->spi_tx_error_code == 0) ++ return 0; ++ ++ if (!mctp_error_inject_match_filter(ei, skb)) ++ return 0; ++ ++ if (!mctp_should_inject_error(ei, ei->spi_tx_error_rate, ++ &ei->spi_tx_inject_count)) ++ return 0; ++ ++ /* Inject delay if configured */ ++ if (ei->delay_ms > 0) ++ msleep(ei->delay_ms); ++ ++ /* Update statistics */ ++ spin_lock_bh(&ei->lock); ++ ei->spi_tx_errors_injected++; ++ ei->total_errors_injected++; ++ ei->total_packets_processed++; ++ spin_unlock_bh(&ei->lock); ++ ++ spi_hdr = (void *)skb->data; ++ mh = (struct mctp_hdr *)(skb->data + sizeof(*spi_hdr)); ++ ++ netdev_info(midev->ndev, ++ "ERROR INJECTION: SPI TX - error=%d, src_eid=%u, dest_eid=%u, " ++ "total_injected=%u, mode=%s\n", ++ ei->spi_tx_error_code, mh->src, mh->dest, ++ ei->spi_tx_errors_injected, ++ ei->mode == MCTP_ERR_MODE_ALWAYS ? "always" : ++ ei->mode == MCTP_ERR_MODE_RANDOM ? "random" : "count"); ++ ++ *injected_status = ei->spi_tx_error_code; ++ return 1; ++} ++EXPORT_SYMBOL_GPL(mctp_spi_error_inject_tx); ++ ++/* Debugfs attribute files */ ++static ssize_t enable_tx_read(struct file *file, char __user *user_buf, ++ size_t count, loff_t *ppos) ++{ ++ struct mctp_spi *midev = file->private_data; ++ char buf[4]; ++ int len; ++ ++ len = snprintf(buf, sizeof(buf), "%d\n", midev->error_inject.enable_tx); ++ return simple_read_from_buffer(user_buf, count, ppos, buf, len); ++} ++ ++static ssize_t enable_tx_write(struct file *file, const char __user *user_buf, ++ size_t count, loff_t *ppos) ++{ ++ struct mctp_spi *midev = file->private_data; ++ char buf[4]; ++ bool val; ++ int ret; ++ ++ if (count >= sizeof(buf)) ++ return -EINVAL; ++ ++ if (copy_from_user(buf, user_buf, count)) ++ return -EFAULT; ++ ++ buf[count] = '\0'; ++ ret = kstrtobool(buf, &val); ++ if (ret) ++ return ret; ++ ++ spin_lock_bh(&midev->error_inject.lock); ++ midev->error_inject.enable_tx = val; ++ spin_unlock_bh(&midev->error_inject.lock); ++ ++ return count; ++} ++ ++static const struct file_operations mctp_debugfs_enable_tx_fops = { ++ .owner = THIS_MODULE, ++ .read = enable_tx_read, ++ .write = enable_tx_write, ++ .open = simple_open, ++ .llseek = default_llseek, ++}; ++ ++static ssize_t error_code_read(struct file *file, char __user *user_buf, ++ size_t count, loff_t *ppos) ++{ ++ struct mctp_spi *midev = file->private_data; ++ char buf[16]; ++ int len; ++ ++ len = snprintf(buf, sizeof(buf), "%d\n", midev->error_inject.spi_tx_error_code); ++ return simple_read_from_buffer(user_buf, count, ppos, buf, len); ++} ++ ++static ssize_t error_code_write(struct file *file, const char __user *user_buf, ++ size_t count, loff_t *ppos) ++{ ++ struct mctp_spi *midev = file->private_data; ++ char buf[16]; ++ int val; ++ int ret; ++ ++ if (count >= sizeof(buf)) ++ return -EINVAL; ++ ++ if (copy_from_user(buf, user_buf, count)) ++ return -EFAULT; ++ ++ buf[count] = '\0'; ++ ret = kstrtoint(buf, 10, &val); ++ if (ret) ++ return ret; ++ ++ /* Valid SPB_AP error codes: 2=INVALID_ARG, 3=TIMEOUT, 4=UNKNOWN */ ++ if (val < 0 || val > 10) ++ return -EINVAL; ++ ++ spin_lock_bh(&midev->error_inject.lock); ++ midev->error_inject.spi_tx_error_code = val; ++ spin_unlock_bh(&midev->error_inject.lock); ++ ++ return count; ++} ++ ++static const struct file_operations mctp_debugfs_spi_tx_error_code_fops = { ++ .owner = THIS_MODULE, ++ .read = error_code_read, ++ .write = error_code_write, ++ .open = simple_open, ++ .llseek = default_llseek, ++}; ++ ++static ssize_t mode_read(struct file *file, char __user *user_buf, ++ size_t count, loff_t *ppos) ++{ ++ struct mctp_spi *midev = file->private_data; ++ const char *mode_str; ++ char buf[32]; ++ int len; ++ ++ switch (midev->error_inject.mode) { ++ case MCTP_ERR_MODE_ALWAYS: ++ mode_str = "always\n"; ++ break; ++ case MCTP_ERR_MODE_RANDOM: ++ mode_str = "random\n"; ++ break; ++ case MCTP_ERR_MODE_COUNT: ++ mode_str = "count\n"; ++ break; ++ default: ++ mode_str = "unknown\n"; ++ } ++ ++ len = snprintf(buf, sizeof(buf), "%s", mode_str); ++ return simple_read_from_buffer(user_buf, count, ppos, buf, len); ++} ++ ++static ssize_t mode_write(struct file *file, const char __user *user_buf, ++ size_t count, loff_t *ppos) ++{ ++ struct mctp_spi *midev = file->private_data; ++ char buf[16]; ++ ++ if (count >= sizeof(buf)) ++ return -EINVAL; ++ ++ if (copy_from_user(buf, user_buf, count)) ++ return -EFAULT; ++ ++ buf[count] = '\0'; ++ ++ spin_lock_bh(&midev->error_inject.lock); ++ ++ if (strncmp(buf, "always", 6) == 0) ++ midev->error_inject.mode = MCTP_ERR_MODE_ALWAYS; ++ else if (strncmp(buf, "random", 6) == 0) ++ midev->error_inject.mode = MCTP_ERR_MODE_RANDOM; ++ else if (strncmp(buf, "count", 5) == 0) ++ midev->error_inject.mode = MCTP_ERR_MODE_COUNT; ++ else { ++ spin_unlock_bh(&midev->error_inject.lock); ++ return -EINVAL; ++ } ++ ++ spin_unlock_bh(&midev->error_inject.lock); ++ ++ return count; ++} ++ ++static const struct file_operations mctp_debugfs_mode_fops = { ++ .owner = THIS_MODULE, ++ .read = mode_read, ++ .write = mode_write, ++ .open = simple_open, ++ .llseek = default_llseek, ++}; ++ ++static ssize_t rate_read(struct file *file, char __user *user_buf, ++ size_t count, loff_t *ppos) ++{ ++ struct mctp_spi *midev = file->private_data; ++ char buf[16]; ++ int len; ++ ++ len = snprintf(buf, sizeof(buf), "%u\n", midev->error_inject.spi_tx_error_rate); ++ return simple_read_from_buffer(user_buf, count, ppos, buf, len); ++} ++ ++static ssize_t rate_write(struct file *file, const char __user *user_buf, ++ size_t count, loff_t *ppos) ++{ ++ struct mctp_spi *midev = file->private_data; ++ char buf[16]; ++ u32 val; ++ int ret; ++ ++ if (count >= sizeof(buf)) ++ return -EINVAL; ++ ++ if (copy_from_user(buf, user_buf, count)) ++ return -EFAULT; ++ ++ buf[count] = '\0'; ++ ret = kstrtou32(buf, 10, &val); ++ if (ret) ++ return ret; ++ ++ if (val > 100) ++ return -EINVAL; ++ ++ spin_lock_bh(&midev->error_inject.lock); ++ midev->error_inject.spi_tx_error_rate = val; ++ spin_unlock_bh(&midev->error_inject.lock); ++ ++ return count; ++} ++ ++static const struct file_operations mctp_debugfs_spi_tx_error_rate_fops = { ++ .owner = THIS_MODULE, ++ .read = rate_read, ++ .write = rate_write, ++ .open = simple_open, ++ .llseek = default_llseek, ++}; ++ ++static ssize_t inject_count_read(struct file *file, char __user *user_buf, ++ size_t count, loff_t *ppos) ++{ ++ struct mctp_spi *midev = file->private_data; ++ char buf[16]; ++ int len; ++ ++ len = snprintf(buf, sizeof(buf), "%u\n", midev->error_inject.spi_tx_inject_count); ++ return simple_read_from_buffer(user_buf, count, ppos, buf, len); ++} ++ ++static ssize_t inject_count_write(struct file *file, const char __user *user_buf, ++ size_t count, loff_t *ppos) ++{ ++ struct mctp_spi *midev = file->private_data; ++ char buf[16]; ++ u32 val; ++ int ret; ++ ++ if (count >= sizeof(buf)) ++ return -EINVAL; ++ ++ if (copy_from_user(buf, user_buf, count)) ++ return -EFAULT; ++ ++ buf[count] = '\0'; ++ ret = kstrtou32(buf, 10, &val); ++ if (ret) ++ return ret; ++ ++ spin_lock_bh(&midev->error_inject.lock); ++ midev->error_inject.spi_tx_inject_count = val; ++ spin_unlock_bh(&midev->error_inject.lock); ++ ++ return count; ++} ++ ++ ++/* stats attribute (read-only) - unified with USB/I2C */ ++static ssize_t stats_read(struct file *file, char __user *user_buf, ++ size_t count, loff_t *ppos) ++{ ++ struct mctp_spi *midev = file->private_data; ++ struct mctp_spi_error_inject *ei = &midev->error_inject; ++ char *buf; ++ int len = 0; ++ ssize_t ret; ++ ++ buf = kmalloc(PAGE_SIZE, GFP_KERNEL); ++ if (!buf) ++ return -ENOMEM; ++ ++ spin_lock_bh(&ei->lock); ++ ++ len += snprintf(buf + len, PAGE_SIZE - len, "enable_tx: %d\n", ei->enable_tx); ++ len += snprintf(buf + len, PAGE_SIZE - len, "mode: %s\n", ++ ei->mode == MCTP_ERR_MODE_ALWAYS ? "always" : ++ ei->mode == MCTP_ERR_MODE_RANDOM ? "random" : "count"); ++ len += snprintf(buf + len, PAGE_SIZE - len, "spi_tx_errors_injected: %u\n", ++ ei->spi_tx_errors_injected); ++ len += snprintf(buf + len, PAGE_SIZE - len, "total_packets_processed: %llu\n", ++ ei->total_packets_processed); ++ len += snprintf(buf + len, PAGE_SIZE - len, "total_errors_injected: %llu\n", ++ ei->total_errors_injected); ++ ++ spin_unlock_bh(&ei->lock); ++ ++ ret = simple_read_from_buffer(user_buf, count, ppos, buf, len); ++ kfree(buf); ++ ++ return ret; ++} ++ ++static const struct file_operations mctp_debugfs_stats_fops = { ++ .owner = THIS_MODULE, ++ .read = stats_read, ++ .open = simple_open, ++ .llseek = default_llseek, ++}; ++ ++/* reset attribute (write-only) - unified with USB/I2C */ ++static ssize_t reset_write(struct file *file, const char __user *user_buf, ++ size_t count, loff_t *ppos) ++{ ++ struct mctp_spi *midev = file->private_data; ++ struct mctp_spi_error_inject *ei = &midev->error_inject; ++ char buf[8]; ++ int val; ++ int rc; ++ ++ if (count >= sizeof(buf)) ++ return -EINVAL; ++ ++ if (copy_from_user(buf, user_buf, count)) ++ return -EFAULT; ++ ++ buf[count] = '\0'; ++ rc = kstrtoint(buf, 0, &val); ++ if (rc) ++ return rc; ++ ++ if (val != 1) ++ return -EINVAL; ++ ++ spin_lock_bh(&ei->lock); ++ ++ /* Reset all configuration - unified with USB/I2C */ ++ ei->enable_tx = false; ++ ei->mode = MCTP_ERR_MODE_ALWAYS; ++ ei->spi_tx_error_code = 0; ++ ei->spi_tx_error_rate = 0; ++ ei->spi_tx_inject_count = 0; ++ ei->delay_ms = 0; ++ ei->eid_filter.enabled = false; ++ ei->eid_filter.src_eid = 0; ++ ei->eid_filter.dest_eid = 0; ++ ei->eid_filter.msg_type = 0; ++ ++ /* Reset all statistics */ ++ ei->spi_tx_errors_injected = 0; ++ ei->total_packets_processed = 0; ++ ei->total_errors_injected = 0; ++ ++ spin_unlock_bh(&ei->lock); ++ ++ netdev_dbg(midev->ndev, "Error injection reset\n"); ++ ++ return count; ++} ++ ++static const struct file_operations mctp_debugfs_reset_fops = { ++ .owner = THIS_MODULE, ++ .write = reset_write, ++ .open = simple_open, ++ .llseek = default_llseek, ++}; ++ ++/** ++ * mctp_spi_error_inject_init - Initialize error injection for a device ++ * @midev: MCTP SPI device ++ */ ++void mctp_spi_error_inject_init(struct mctp_spi *midev) ++{ ++ struct mctp_spi_error_inject *ei = &midev->error_inject; ++ ++ /* Initialize error injection state */ ++ memset(ei, 0, sizeof(*ei)); ++ spin_lock_init(&ei->lock); ++ prandom_seed_state(&ei->rng, get_random_u32()); ++ ++ /* Default configuration */ ++ ei->mode = MCTP_ERR_MODE_ALWAYS; ++ ei->spi_tx_error_rate = 100; ++ ++ /* Create device-specific debugfs directory */ ++ if (!mctp_spi_error_inject_root) ++ return; ++ ++ midev->debugfs_dir = debugfs_create_dir(midev->ndev->name, ++ mctp_spi_error_inject_root); ++ if (IS_ERR_OR_NULL(midev->debugfs_dir)) { ++ netdev_warn(midev->ndev, "Failed to create debugfs directory\n"); ++ midev->debugfs_dir = NULL; ++ return; ++ } ++ ++ /* Create debugfs files - unified with USB/I2C */ ++ debugfs_create_file("enable_tx", 0600, midev->debugfs_dir, midev, ++ &mctp_debugfs_enable_tx_fops); ++ debugfs_create_file("mode", 0600, midev->debugfs_dir, midev, ++ &mctp_debugfs_mode_fops); ++ debugfs_create_file("spi_tx_error_code", 0600, midev->debugfs_dir, midev, ++ &mctp_debugfs_spi_tx_error_code_fops); ++ debugfs_create_file("spi_tx_error_rate", 0600, midev->debugfs_dir, midev, ++ &mctp_debugfs_spi_tx_error_rate_fops); ++ debugfs_create_file("stats", 0400, midev->debugfs_dir, midev, ++ &mctp_debugfs_stats_fops); ++ debugfs_create_file("reset", 0200, midev->debugfs_dir, midev, ++ &mctp_debugfs_reset_fops); ++ ++ netdev_info(midev->ndev, "Error injection debugfs created at /sys/kernel/debug/mctp_spi/%s\n", ++ midev->ndev->name); ++} ++EXPORT_SYMBOL_GPL(mctp_spi_error_inject_init); ++ ++/** ++ * mctp_spi_error_inject_cleanup - Cleanup error injection for a device ++ * @midev: MCTP SPI device ++ */ ++void mctp_spi_error_inject_cleanup(struct mctp_spi *midev) ++{ ++ if (midev->debugfs_dir) { ++ debugfs_remove_recursive(midev->debugfs_dir); ++ midev->debugfs_dir = NULL; ++ } ++} ++EXPORT_SYMBOL_GPL(mctp_spi_error_inject_cleanup); ++ ++/** ++ * mctp_spi_error_inject_module_init - Initialize module-wide debugfs ++ */ ++int mctp_spi_error_inject_module_init(void) ++{ ++ mctp_spi_error_inject_root = debugfs_create_dir("mctp_spi", NULL); ++ if (IS_ERR_OR_NULL(mctp_spi_error_inject_root)) { ++ pr_warn("MCTP SPI: Failed to create debugfs root, error injection disabled\n"); ++ mctp_spi_error_inject_root = NULL; ++ return -ENODEV; ++ } ++ ++ pr_info("MCTP SPI: Error injection initialized at /sys/kernel/debug/mctp_spi/\n"); ++ return 0; ++} ++EXPORT_SYMBOL_GPL(mctp_spi_error_inject_module_init); ++ ++/** ++ * mctp_spi_error_inject_module_exit - Cleanup module-wide debugfs ++ */ ++void mctp_spi_error_inject_module_exit(void) ++{ ++ if (mctp_spi_error_inject_root) { ++ debugfs_remove_recursive(mctp_spi_error_inject_root); ++ mctp_spi_error_inject_root = NULL; ++ } ++} ++EXPORT_SYMBOL_GPL(mctp_spi_error_inject_module_exit); ++ ++MODULE_LICENSE("GPL"); ++MODULE_DESCRIPTION("MCTP SPI Error Injection"); ++ +diff --git a/drivers/net/mctp/mctp-spi-error-inject.h b/drivers/net/mctp/mctp-spi-error-inject.h +new file mode 100644 +index 000000000..bbf67e633 +--- /dev/null ++++ b/drivers/net/mctp/mctp-spi-error-inject.h +@@ -0,0 +1,86 @@ ++/* SPDX-License-Identifier: GPL-2.0 */ ++/* ++ * MCTP SPI Error Injection Infrastructure ++ * ++ * Copyright (c) 2024 NVIDIA CORPORATION. All rights reserved. ++ * ++ * Provides debugfs-based error injection for testing MCTP error queue ++ * functionality over SPI binding. ++ * Interface unified with I2C/USB error injection. ++ */ ++ ++#ifndef __MCTP_SPI_ERROR_INJECT_H ++#define __MCTP_SPI_ERROR_INJECT_H ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++/* Forward declarations */ ++struct dentry; ++ ++/* Error injection modes - unified with I2C/USB */ ++enum mctp_error_inject_mode { ++ MCTP_ERR_MODE_ALWAYS, ++ MCTP_ERR_MODE_RANDOM, ++ MCTP_ERR_MODE_COUNT ++}; ++ ++/* Error injection control structure - unified interface */ ++struct mctp_spi_error_inject { ++ bool enable_tx; /* TX error injection enable */ ++ enum mctp_error_inject_mode mode; ++ ++ /* TX injection - SPI uses SPB_AP return codes */ ++ int spi_tx_error_code; /* SPB_AP error code (SPB_AP_ERROR_TIMEOUT, etc.) */ ++ u32 spi_tx_error_rate; /* Percentage (0-100) or count */ ++ u32 spi_tx_inject_count; /* Counter for count mode */ ++ u32 spi_tx_errors_injected; ++ ++ /* Delay injection */ ++ u32 delay_ms; ++ ++ /* EID filtering - unified with I2C/USB */ ++ struct { ++ bool enabled; ++ u8 src_eid; /* 0 = any */ ++ u8 dest_eid; /* 0 = any */ ++ u8 msg_type; /* 0 = any */ ++ } eid_filter; ++ ++ /* Statistics */ ++ u64 total_packets_processed; ++ u64 total_errors_injected; ++ ++ /* RNG state */ ++ struct rnd_state rng; ++ spinlock_t lock; ++}; ++ ++/* Forward declaration - full definition in mctp-spi.c */ ++struct mctp_spi; ++ ++/* Module init/exit functions */ ++int mctp_spi_error_inject_module_init(void); ++void mctp_spi_error_inject_module_exit(void); ++ ++/* Public API for main driver */ ++void mctp_spi_error_inject_init(struct mctp_spi *midev); ++void mctp_spi_error_inject_cleanup(struct mctp_spi *midev); ++ ++int mctp_spi_error_inject_tx(struct mctp_spi *midev, struct sk_buff *skb, int *injected_status); ++ ++#endif /* __MCTP_SPI_ERROR_INJECT_H */ ++ ++ ++ ++ ++ ++ ++ ++ +diff --git a/drivers/net/mctp/mctp-spi-internal.h b/drivers/net/mctp/mctp-spi-internal.h +new file mode 100644 +index 000000000..16d87268a +--- /dev/null ++++ b/drivers/net/mctp/mctp-spi-internal.h +@@ -0,0 +1,97 @@ ++/* SPDX-License-Identifier: GPL-2.0 */ ++/* ++ * MCTP SPI Internal Definitions ++ * Shared between mctp-spi.c and mctp-spi-error-inject.c ++ */ ++ ++#ifndef __MCTP_SPI_INTERNAL_H ++#define __MCTP_SPI_INTERNAL_H ++ ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include "mctp-spi-error-inject.h" ++#include "glacier-spb-ap.h" ++ ++#define RX_BUFFER_SIZE 1024 ++ ++/* SPI device data structure */ ++struct spidev_data { ++ dev_t devt; ++ spinlock_t spi_lock; ++ struct spi_device *spi; ++ struct list_head device_entry; ++ ++ struct mutex buf_lock; ++ unsigned users; ++ u8 *tx_buffer; ++ size_t tx_len; ++ u8 rx_buffer[RX_BUFFER_SIZE]; ++ size_t rx_len; ++ u32 speed_hz; ++}; ++ ++/* Main device structure - one per SPI device */ ++struct mctp_spi { ++ struct net_device *ndev; ++ struct spidev_data *spidev; ++ ++ struct task_struct *tx_thread; ++ wait_queue_head_t main_thread_wq; ++ struct sk_buff_head tx_queue; ++ spinlock_t lock; ++ bool allow_rx; ++ struct completion rx_done; ++ ++ struct gpio_desc *rx_alert; /* Input gpio to alert about the incoming package from SPI */ ++ int rx_alert_irq; ++ ++ SpbAp *ap; ++ wait_queue_head_t gpio_intr_wq; ++ bool gpio_intr_cond; ++ spinlock_t gpio_intr_cond_lock; ++ ++ /* Per-EID statistics tracking - SINGLE source of truth ++ * ++ * All statistics are tracked per-endpoint-ID (EID). Two special EIDs: ++ * - EID 0: "null endpoint" - valid packets with EID=0 (unallocated endpoint) ++ * - EID 256 (MCTP_EID_UNKNOWN): errors where EID could not be determined ++ * (GPIO interrupts, SPI transfer errors, allocation failures) ++ */ ++ struct { ++ DECLARE_BITMAP(active, 257); /* Which EIDs have activity */ ++ struct mctp_spi_eid_stats { ++ /* RX stats */ ++ u64 rx_drop_no_memory; ++ u64 rx_drop_not_ready; /* Tracked as UNKNOWN (before EID known) */ ++ u64 rx_drop_spi_error; /* Tracked as UNKNOWN */ ++ ++ /* TX stats - SPB AP specific errors */ ++ u64 tx_drop_etimedout; /* SPB_AP_ERROR_TIMEOUT */ ++ u64 tx_drop_einval; /* SPB_AP_ERROR_INVALID_ARGUMENT */ ++ u64 tx_drop_eio; /* SPB_AP_ERROR_UNKNOWN */ ++ u64 tx_drop_spi_error; /* Catch-all for unmapped errors */ ++ ++ /* GPIO interrupt tracking (UNKNOWN - no EID context) */ ++ u64 gpio_interrupts; ++ } eid[257]; ++ } eid_stats; ++ ++ /* Error injection support */ ++ struct mctp_spi_error_inject error_inject; ++ struct dentry *debugfs_dir; ++}; ++ ++/* SPI transport header */ ++struct mctp_spi_hdr { ++ u8 command_code; ++ u8 byte_count; ++ u8 resrv[2]; ++}; ++ ++#endif /* __MCTP_SPI_INTERNAL_H */ ++ +diff --git a/drivers/net/mctp/mctp-spi.c b/drivers/net/mctp/mctp-spi.c +new file mode 100644 +index 000000000..606029dc5 +--- /dev/null ++++ b/drivers/net/mctp/mctp-spi.c +@@ -0,0 +1,772 @@ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include ++#include ++ ++#include ++#include ++ ++#include ++#include ++#include ++#include ++#include ++ ++#include ++ ++#include "glacier-spb-ap.h" ++#include "mctp-spi-internal.h" ++#include "mctp-stats.h" ++#include "mctp-spi-error-inject.h" ++ ++static DEFINE_IDA(mctp_spi_ida); ++ ++#define MCTP_SPI_MAXMTU (64 + 4) ++#define MCTP_SPI_MINMTU (64 + 4) ++#define MCTP_SPI_TX_QUEUE_LEN 1100 ++#define BUFSIZE 256 ++#define MCTP_SPI_TX_WORK_LEN 100 ++#define MCTP_COMMAND_CODE 0x02 ++ ++#define ERR_SPI_RX_NO_DATA -2 ++ ++/** ++ * spb_ap_status_to_errno - Convert SPB AP status codes to Linux error codes ++ * @status: SPB AP status code from spb_ap_send() or other SPB AP functions ++ * ++ * Translates positive SPB AP status codes to negative Linux errno values ++ * for consistent error handling throughout the driver. ++ * ++ * Note: SPB_AP_MESSAGE_AVAILABLE is a success status (not an error) that ++ * indicates a message is available to read after a successful send. ++ */ ++static inline int spb_ap_status_to_errno(SpbApStatus status) ++{ ++ switch (status) { ++ case SPB_AP_OK: ++ case SPB_AP_MESSAGE_AVAILABLE: ++ return 0; ++ case SPB_AP_ERROR_TIMEOUT: ++ return -ETIMEDOUT; ++ case SPB_AP_ERROR_INVALID_ARGUMENT: ++ return -EINVAL; ++ case SPB_AP_ERROR_UNKNOWN: ++ default: ++ return -EIO; ++ } ++} ++ ++static LIST_HEAD(device_list); ++static DEFINE_MUTEX(device_list_lock); ++ ++static unsigned bufsiz = 4096; ++module_param(bufsiz, uint, S_IRUGO); ++MODULE_PARM_DESC(bufsiz, "data bytes in biggest supported SPI message"); ++ ++static const struct spi_device_id mctp_spi_ids[] = { ++ { .name = "mctp-spi" }, ++ {}, ++}; ++MODULE_DEVICE_TABLE(spi, mctp_spi_ids); ++ ++static const struct of_device_id mctp_spi_dt_ids[] = { ++ { .compatible = "mctp-spi" }, ++ {}, ++}; ++MODULE_DEVICE_TABLE(of, mctp_spi_dt_ids); ++ ++/*-------------------------------------------------------------------------*/ ++ ++static netdev_tx_t mctp_spi_net_xmit(struct sk_buff *skb, ++ struct net_device *dev) ++{ ++ struct mctp_spi *midev = netdev_priv(dev); ++ ++ spin_lock(&midev->tx_queue.lock); ++ if (skb_queue_len(&midev->tx_queue) >= MCTP_SPI_TX_WORK_LEN) { ++ netif_stop_queue(dev); ++ spin_unlock(&midev->tx_queue.lock); ++ netdev_err(dev, "BUG! Tx Ring full when queue awake!\n"); ++ return NETDEV_TX_BUSY; ++ } ++ ++ __skb_queue_tail(&midev->tx_queue, skb); ++ if (skb_queue_len(&midev->tx_queue) == MCTP_SPI_TX_WORK_LEN) ++ netif_stop_queue(dev); ++ spin_unlock(&midev->tx_queue.lock); ++ ++ wake_up(&midev->main_thread_wq); ++ return NETDEV_TX_OK; ++} ++ ++static int mctp_spi_net_recv(struct mctp_spi *midev, uint8_t *rx_buffer) ++{ ++ struct net_device *ndev = midev->ndev; ++ struct sk_buff *skb; ++ struct mctp_skb_cb *cb; ++ unsigned long flags; ++ size_t recvlen; ++ int status; ++ ++ recvlen = midev->spidev->rx_len; ++ ++ skb = netdev_alloc_skb(ndev, recvlen); ++ if (!skb) { ++ ndev->stats.rx_dropped++; ++ /* Can't extract EID - no packet data available yet */ ++ MCTP_STAT_INC(midev, MCTP_EID_UNKNOWN, rx_drop_no_memory); ++ trace_mctp_transport_error("spi", ndev, "rx_drop_no_memory", recvlen); ++ return -ENOMEM; ++ } ++ skb->protocol = htons(ETH_P_MCTP); ++ if (!rx_buffer) ++ rx_buffer = midev->spidev->rx_buffer; ++ skb_put_data(skb, rx_buffer, recvlen); ++ skb_reset_network_header(skb); ++ cb = __mctp_cb(skb); ++ cb->halen = 0; ++ ++ /* We need to ensure that the netif is not used once netdev ++ * unregister occurs ++ */ ++ spin_lock_irqsave(&midev->lock, flags); ++ if (midev->allow_rx) { ++ reinit_completion(&midev->rx_done); ++ spin_unlock_irqrestore(&midev->lock, flags); ++ status = netif_rx(skb); ++ complete(&midev->rx_done); ++ } else { ++ status = NET_RX_DROP; ++ spin_unlock_irqrestore(&midev->lock, flags); ++ } ++ ++ if (status == NET_RX_SUCCESS) { ++ ndev->stats.rx_packets++; ++ ndev->stats.rx_bytes += recvlen; ++ netdev_dbg(ndev, "MCTP SPI: RX success, %zu bytes\n", recvlen); ++ trace_mctp_transport_rx("spi", ndev, 0, recvlen); ++ } else { ++ ndev->stats.rx_dropped++; ++ /* Extract EID for per-EID tracking (SKB has packet data) */ ++ if (skb->len >= sizeof(struct mctp_spi_hdr) + sizeof(struct mctp_hdr)) { ++ struct mctp_hdr *mh = (struct mctp_hdr *)(skb->data + sizeof(struct mctp_spi_hdr)); ++ MCTP_STAT_INC(midev, mh->src, rx_drop_not_ready); ++ } else { ++ MCTP_STAT_INC(midev, MCTP_EID_UNKNOWN, rx_drop_not_ready); ++ } ++ netdev_dbg(ndev, "MCTP SPI: RX dropped, status=%d\n", status); ++ trace_mctp_transport_error("spi", ndev, "rx_dropped", status); ++ } ++ ++ return 0; ++} ++ ++static int mctp_spi_rx(struct mctp_spi *midev) ++{ ++ ssize_t len; ++ ssize_t payload_len; ++ const size_t hdr_size = sizeof(struct mctp_spi_hdr); ++ SpbApStatus status = 0; ++ struct mctp_spi_hdr *spi_hdr_rx; ++ u8 tmp_rx_buffer[RX_BUFFER_SIZE]; ++ ++ spi_hdr_rx = (struct mctp_spi_hdr *)tmp_rx_buffer; ++ ++ status = spb_ap_recv(midev->ap, RX_BUFFER_SIZE, tmp_rx_buffer); ++ if(status != SPB_AP_OK) { ++ int err = spb_ap_status_to_errno(status); ++ ++ midev->ndev->stats.rx_dropped++; ++ /* Can't extract EID - SPI receive failed, no data */ ++ MCTP_STAT_INC(midev, MCTP_EID_UNKNOWN, rx_drop_spi_error); ++ netdev_dbg(midev->ndev, "MCTP SPI: RX error, SPB status=%d, errno=%d\n", status, err); ++ trace_mctp_transport_error("spi", midev->ndev, "rx_spi_error", err); ++ return ERR_SPI_RX_NO_DATA; ++ } ++ ++ payload_len = spi_hdr_rx->byte_count; ++ len = payload_len + hdr_size; ++ ++ midev->spidev->rx_len = payload_len; ++ // memcpy(midev->spidev->rx_buffer, tmp_rx_buffer + hdr_size, payload_len); ++ mctp_spi_net_recv(midev, tmp_rx_buffer + hdr_size); ++ ++ return 0; ++} ++ ++static void mctp_spi_ndo_uninit(struct net_device *dev) ++{ ++ ++} ++ ++static int mctp_spi_ndo_open(struct net_device *dev) ++{ ++ struct mctp_spi *midev = netdev_priv(dev); ++ unsigned long flags; ++ ++ /* spi rx thread can only pass packets once the netdev is registered */ ++ spin_lock_irqsave(&midev->lock, flags); ++ midev->allow_rx = true; ++ spin_unlock_irqrestore(&midev->lock, flags); ++ return 0; ++} ++ ++static int mctp_spi_header_create(struct sk_buff *skb, struct net_device *dev, ++ unsigned short type, const void *daddr, ++ const void *saddr, unsigned int len) ++{ ++ struct mctp_spi_hdr *hdr; ++ ++ if (len > MCTP_SPI_MAXMTU) ++ return -EMSGSIZE; ++ ++ skb_push(skb, sizeof(struct mctp_spi_hdr)); ++ skb_reset_mac_header(skb); ++ hdr = (void *)skb_mac_header(skb); ++ hdr->command_code = MCTP_COMMAND_CODE; ++ hdr->byte_count = len; ++ hdr->resrv[0] = 0; ++ hdr->resrv[1] = 0; ++ ++ return sizeof(struct mctp_spi_hdr); ++} ++ ++/* Ethtool statistics support */ ++/* Per-EID stat descriptors with abbreviated names */ ++struct mctp_spi_eid_stat_desc { ++ const char *name; ++ size_t offset; ++}; ++ ++#define MCTP_SPI_EID_STAT(abbrev, field) { \ ++ .name = abbrev, \ ++ .offset = offsetof(struct mctp_spi_eid_stats, field) \ ++} ++ ++static const struct mctp_spi_eid_stat_desc mctp_spi_eid_stat_descs[] = { ++ MCTP_SPI_EID_STAT("rx_drop_no_memory", rx_drop_no_memory), ++ MCTP_SPI_EID_STAT("rx_drop_not_ready", rx_drop_not_ready), ++ MCTP_SPI_EID_STAT("rx_drop_spi_error", rx_drop_spi_error), ++ MCTP_SPI_EID_STAT("tx_drop_etimedout", tx_drop_etimedout), ++ MCTP_SPI_EID_STAT("tx_drop_einval", tx_drop_einval), ++ MCTP_SPI_EID_STAT("tx_drop_eio", tx_drop_eio), ++ MCTP_SPI_EID_STAT("tx_drop_spi_error", tx_drop_spi_error), ++ MCTP_SPI_EID_STAT("gpio_interrupts", gpio_interrupts), ++}; ++ ++#define MCTP_SPI_EID_NUM_STATS ARRAY_SIZE(mctp_spi_eid_stat_descs) ++ ++/* Generate ethtool helper functions using shared macros */ ++MCTP_EID_STATS_HELPERS(mctp_spi, struct mctp_spi, ++ struct mctp_spi_eid_stats, mctp_spi_eid_stat_descs) ++ ++static void mctp_spi_get_strings(struct net_device *ndev, u32 stringset, ++ u8 *data) ++{ ++ struct mctp_spi *midev = netdev_priv(ndev); ++ unsigned int i; ++ int eid; ++ ++ if (stringset != ETH_SS_STATS) ++ return; ++ ++ /* Output aggregate stats (computed from per-EID) */ ++ for (i = 0; i < MCTP_SPI_EID_NUM_STATS; i++) { ++ snprintf(data, ETH_GSTRING_LEN, "%-30s", mctp_spi_eid_stat_descs[i].name); ++ data += ETH_GSTRING_LEN; ++ } ++ ++ /* Separator line */ ++ snprintf(data, ETH_GSTRING_LEN, " "); ++ data += ETH_GSTRING_LEN; ++ ++ /* Output per-EID stats (only for active EIDs with non-zero stats) */ ++ for_each_set_bit(eid, midev->eid_stats.active, 257) { ++ struct mctp_spi_eid_stats *es = &midev->eid_stats.eid[eid]; ++ u8 *base = (u8 *)es; ++ int nz = mctp_spi_count_eid_nonzero(midev, eid); ++ ++ if (nz == 0) ++ continue; ++ ++ /* EID header line with explanation ++ * - UNKNOWN: Errors where EID could not be determined (GPIO interrupt ++ * events, low-level SPI transfer failures, or allocation failures ++ * before packet parsing) ++ * - EID_0: Valid packets with source/dest EID=0 (null/unallocated ++ * endpoint per MCTP spec DSP0236) ++ * - EID_X: Normal endpoints (X = 1..253) ++ */ ++ if (eid == MCTP_EID_UNKNOWN) { ++ snprintf(data, ETH_GSTRING_LEN, "UNKNOWN: SPI/GPIO errors "); ++ } else if (eid == 0) { ++ snprintf(data, ETH_GSTRING_LEN, "EID_0: null endpoint "); ++ } else { ++ snprintf(data, ETH_GSTRING_LEN, "EID_%-3u ", eid); ++ } ++ data += ETH_GSTRING_LEN; ++ ++ /* Individual non-zero stats for this EID */ ++ for (i = 0; i < MCTP_SPI_EID_NUM_STATS; i++) { ++ u64 val = *(u64 *)(base + mctp_spi_eid_stat_descs[i].offset); ++ if (val != 0) { ++ snprintf(data, ETH_GSTRING_LEN, "%-30s", ++ mctp_spi_eid_stat_descs[i].name); ++ data += ETH_GSTRING_LEN; ++ } ++ } ++ } ++} ++ ++static int mctp_spi_get_sset_count(struct net_device *ndev, int sset) ++{ ++ struct mctp_spi *midev = netdev_priv(ndev); ++ ++ if (sset == ETH_SS_STATS) ++ return MCTP_SPI_EID_NUM_STATS + 1 + mctp_spi_count_eid_stats(midev); ++ /* aggregates */ /* separator */ /* per-EID */ ++ ++ return -EOPNOTSUPP; ++} ++ ++static void mctp_spi_get_ethtool_stats(struct net_device *ndev, ++ struct ethtool_stats *stats, ++ u64 *data) ++{ ++ struct mctp_spi *midev = netdev_priv(ndev); ++ unsigned int i, idx = 0; ++ int eid; ++ ++ /* Output aggregate stats (computed by summing across all EIDs) */ ++ for (i = 0; i < MCTP_SPI_EID_NUM_STATS; i++) { ++ u64 total = 0; ++ ++ for_each_set_bit(eid, midev->eid_stats.active, 257) { ++ u8 *base = (u8 *)&midev->eid_stats.eid[eid]; ++ total += *(u64 *)(base + mctp_spi_eid_stat_descs[i].offset); ++ } ++ data[idx++] = total; ++ } ++ ++ /* Separator (blank line in output) */ ++ data[idx++] = 0; ++ ++ /* Output per-EID stats (only for active EIDs with non-zero stats) */ ++ for_each_set_bit(eid, midev->eid_stats.active, 257) { ++ struct mctp_spi_eid_stats *es = &midev->eid_stats.eid[eid]; ++ u8 *base = (u8 *)es; ++ int nz = mctp_spi_count_eid_nonzero(midev, eid); ++ ++ if (nz == 0) ++ continue; ++ ++ /* EID header: value is sum of all stats for this EID */ ++ data[idx++] = mctp_spi_eid_stats_total(midev, eid); ++ ++ /* Individual non-zero stats for this EID */ ++ for (i = 0; i < MCTP_SPI_EID_NUM_STATS; i++) { ++ u64 val = *(u64 *)(base + mctp_spi_eid_stat_descs[i].offset); ++ if (val != 0) ++ data[idx++] = val; ++ } ++ } ++} ++ ++static const struct ethtool_ops mctp_spi_ethtool_ops = { ++ .get_strings = mctp_spi_get_strings, ++ .get_sset_count = mctp_spi_get_sset_count, ++ .get_ethtool_stats = mctp_spi_get_ethtool_stats, ++}; ++ ++static const struct net_device_ops mctp_spi_ops = { ++ .ndo_start_xmit = mctp_spi_net_xmit, ++ .ndo_uninit = mctp_spi_ndo_uninit, ++ .ndo_open = mctp_spi_ndo_open, ++}; ++ ++static const struct header_ops mctp_spi_headops = { ++ .create = mctp_spi_header_create, ++}; ++ ++static void mctp_spi_net_setup(struct net_device *dev) ++{ ++ dev->type = ARPHRD_MCTP; ++ ++ dev->mtu = MCTP_SPI_MAXMTU; ++ dev->min_mtu = MCTP_SPI_MINMTU; ++ dev->max_mtu = MCTP_SPI_MAXMTU; ++ dev->tx_queue_len = MCTP_SPI_TX_QUEUE_LEN; ++ ++ dev->hard_header_len = sizeof(struct mctp_spi_hdr); ++ dev->addr_len = 0; ++ ++ dev->netdev_ops = &mctp_spi_ops; ++ dev->header_ops = &mctp_spi_headops; ++ dev->ethtool_ops = &mctp_spi_ethtool_ops; ++} ++ ++static irqreturn_t mctp_pkg_rec_wake(int irq, void *data) ++{ ++ struct mctp_spi *midev = data; ++ spin_lock(&midev->gpio_intr_cond_lock); ++ midev->gpio_intr_cond = true; ++ /* GPIO interrupts have no EID context */ ++ MCTP_STAT_INC(midev, MCTP_EID_UNKNOWN, gpio_interrupts); ++ spin_unlock(&midev->gpio_intr_cond_lock); ++ ++ wake_up(&midev->main_thread_wq); // Wake up the consumer waiting on the condition ++ return IRQ_HANDLED; ++} ++ ++ssize_t spi_raw_transfer(struct spi_device *spi, const char *txbuf, size_t txlen, ++ const char *rxbuf, size_t rxlen, bool cs_change) ++{ ++ ssize_t status; ++ // unsigned long missing; unused variable ++ struct spi_message msg; ++ ++ struct spi_transfer xfer = { ++ .tx_buf = txbuf, ++ .rx_buf = rxbuf, ++ .len = txlen + rxlen, ++ .delay.value = rxlen, ++ .bits_per_word = 8, ++ .cs_change = cs_change, ++ .speed_hz = 1000000, ++ }; ++ ++ spi_message_init(&msg); ++ spi_message_add_tail(&xfer, &msg); ++ status = spi_sync(spi, &msg); ++ return status; ++} ++ ++static bool is_gpio_interrupt(struct mctp_spi *midev) ++{ ++ bool tmp; ++ unsigned long flags; ++ spin_lock_irqsave(&midev->gpio_intr_cond_lock, flags); ++ tmp = midev->gpio_intr_cond; ++ spin_unlock_irqrestore(&midev->gpio_intr_cond_lock, flags); ++ return tmp; ++} ++ ++static bool is_skb_queue_empty(struct mctp_spi *midev) ++{ ++ bool tmp; ++ spin_lock(&midev->tx_queue.lock); ++ tmp = skb_queue_empty(&midev->tx_queue); ++ spin_unlock(&midev->tx_queue.lock); ++ return tmp; ++} ++ ++static int mctp_spi_tx_thread(void *data) ++{ ++ struct mctp_spi *midev = data; ++ struct sk_buff *skb; ++ unsigned long flags; ++ unsigned char txbuf[BUFSIZE]; ++ bool gpio_wake = false; ++ SpbApStatus status = 0; ++ ++ spb_ap_initialize(midev->ap); ++ ++ for (;;) { ++ if (kthread_should_stop()) ++ break; ++ ++ if(gpio_wake) { ++ gpio_wake = false; ++ spb_ap_on_interrupt(midev->ap); ++ while (midev->ap->msgs_available > 0) ++ mctp_spi_rx(midev); ++ } ++ ++ if (skb) { ++ int injected_status = 0; ++ /* Extract destination EID for per-EID tracking */ ++ struct mctp_hdr *mh = (void *)(skb->data + sizeof(struct mctp_spi_hdr)); ++ u8 dest_eid = mh->dest; ++ ++ skb_copy_bits(skb, 0, txbuf, skb->len); ++ ++ /* ERROR INJECTION POINT: Check if we should inject error */ ++ if (mctp_spi_error_inject_tx(midev, skb, &injected_status)) { ++ /* Inject the configured SPB_AP error */ ++ status = injected_status; ++ } else { ++ //Send SPI package ++ status = spb_ap_send(midev->ap, skb->len, txbuf); ++ } ++ ++ if(status == SPB_AP_OK) { ++ midev->ndev->stats.tx_packets++; ++ midev->ndev->stats.tx_bytes += skb->len; ++ netdev_dbg(midev->ndev, "MCTP SPI: TX success, %u bytes\n", skb->len); ++ trace_mctp_transport_tx("spi", midev->ndev, 0, skb->len); ++ } else { ++ int err = spb_ap_status_to_errno(status); ++ ++ midev->ndev->stats.tx_dropped++; ++ /* SPB AP only returns: TIMEOUT, INVALID_ARGUMENT, or UNKNOWN */ ++ if (err == -ETIMEDOUT) { ++ MCTP_STAT_INC(midev, dest_eid, tx_drop_etimedout); ++ } else if (err == -EINVAL) { ++ MCTP_STAT_INC(midev, dest_eid, tx_drop_einval); ++ } else if (err == -EIO) { ++ MCTP_STAT_INC(midev, dest_eid, tx_drop_eio); ++ } else { ++ /* Catch-all for any unmapped errors */ ++ MCTP_STAT_INC(midev, dest_eid, tx_drop_spi_error); ++ } ++ netdev_dbg(midev->ndev, "MCTP SPI: TX failed, SPB status=%d, errno=%d\n", status, err); ++ trace_mctp_transport_error("spi", midev->ndev, "spb_ap_send_failed", err); ++ ++ /* TX ERROR: Report to application via error queue */ ++ struct sock *sk; ++ struct mctp_sk_key *key = NULL; ++ int error_code; ++ ++ /* Map SPB_AP status codes to Linux errno */ ++ switch (status) { ++ case SPB_AP_ERROR_TIMEOUT: ++ error_code = ETIMEDOUT; ++ break; ++ case SPB_AP_ERROR_INVALID_ARGUMENT: ++ error_code = EINVAL; ++ break; ++ case SPB_AP_ERROR_UNKNOWN: ++ default: ++ error_code = EIO; ++ break; ++ } ++ ++ /* Remove SPI header to expose MCTP header for socket lookup. ++ * Since we're going to free the SKB anyway, we can modify it directly. ++ * This preserves skb->sk naturally without needing to clone. ++ */ ++ if (skb->len > sizeof(struct mctp_spi_hdr)) { ++ skb_pull(skb, sizeof(struct mctp_spi_hdr)); ++ skb_reset_network_header(skb); ++ ++ /* Lookup socket and report error */ ++ sk = mctp_lookup_sock_for_error(skb, midev->ndev, NULL, &key); ++ if (sk) { ++ mctp_queue_error(sk, skb, error_code, midev->ndev, ++ MCTP_DIR_TX, MCTP_PHYS_BINDING_SERIAL, key); ++ sock_put(sk); ++ } ++ } ++ } ++ kfree_skb(skb); ++ while (midev->ap->msgs_available > 0) { ++ status = mctp_spi_rx(midev); ++ if (status == ERR_SPI_RX_NO_DATA) ++ break; ++ } ++ midev->ap->msgs_available = 0; ++ } ++ ++ wait_event_idle(midev->main_thread_wq, ++ !is_skb_queue_empty(midev) || ++ kthread_should_stop() || is_gpio_interrupt(midev)); ++ // Pop skb if any ++ spin_lock(&midev->tx_queue.lock); ++ skb = __skb_dequeue(&midev->tx_queue); ++ if (netif_queue_stopped(midev->ndev)) ++ netif_wake_queue(midev->ndev); ++ spin_unlock(&midev->tx_queue.lock); ++ // Check if this is GPIO interrupt wake ++ spin_lock_irqsave(&midev->gpio_intr_cond_lock, flags); ++ gpio_wake = midev->gpio_intr_cond; ++ midev->gpio_intr_cond = false; ++ spin_unlock_irqrestore(&midev->gpio_intr_cond_lock, flags); ++ } ++ ++ return 0; ++} ++ ++static int mctp_spi_probe(struct spi_device *spi) ++{ ++ int (*match)(struct device *dev); ++ struct spidev_data *spidev; ++ SpbAp *ap; ++ int status, idx, rc; ++ struct net_device *ndev; ++ struct mctp_spi *mctp_spi_dev; ++ char name[32]; ++ unsigned long flags; ++ ++ match = device_get_match_data(&spi->dev); ++ if (match) { ++ status = match(&spi->dev); ++ if (status) ++ return status; ++ } ++ ++ /* Allocate driver data */ ++ spidev = kzalloc(sizeof(*spidev), GFP_KERNEL); ++ if (!spidev) ++ return -ENOMEM; ++ ++ /* Initialize the driver data */ ++ spidev->spi = spi; ++ spin_lock_init(&spidev->spi_lock); ++ mutex_init(&spidev->buf_lock); ++ ++ INIT_LIST_HEAD(&spidev->device_entry); ++ ++ spidev->speed_hz = spi->max_speed_hz; ++ ++ if (status == 0) ++ spi_set_drvdata(spi, spidev); ++ else ++ kfree(spidev); ++ ++ idx = ida_alloc(&mctp_spi_ida, GFP_KERNEL); ++ if (idx < 0) ++ return idx; ++ ++ snprintf(name, sizeof(name), "mctpspi%u_%u", spi->controller->bus_num, ++ spi_get_chipselect(spi, 0)); ++ ndev = alloc_netdev(sizeof(*mctp_spi_dev), name, NET_NAME_ENUM, ++ mctp_spi_net_setup); ++ if (!ndev) { ++ rc = -ENOMEM; ++ goto free_ida; ++ } ++ mctp_spi_dev = netdev_priv(ndev); ++ ++ mctp_spi_dev->rx_alert = devm_gpiod_get(&spi->dev, "alert", GPIOD_IN); ++ ++ SET_NETDEV_DEV(ndev, &spi->dev); ++ if(IS_ERR(mctp_spi_dev->rx_alert)) { ++ goto err_netdev; ++ } ++ ++ mctp_spi_dev->rx_alert_irq = gpiod_to_irq(mctp_spi_dev->rx_alert); ++ if (mctp_spi_dev->rx_alert_irq < 0) { ++ rc = mctp_spi_dev->rx_alert_irq; ++ goto err_netdev; ++ } ++ ++ /*Init GPIO Interrupt*/ ++ spin_lock_init(&mctp_spi_dev->gpio_intr_cond_lock); ++ init_waitqueue_head(&mctp_spi_dev->gpio_intr_wq); ++ mctp_spi_dev->gpio_intr_cond = 0; ++ ++ rc = request_irq(mctp_spi_dev->rx_alert_irq, mctp_pkg_rec_wake, ++ IRQF_TRIGGER_FALLING, "mctp_spi_pkg_rec_wake", mctp_spi_dev); ++ ++ if (rc < 0) ++ goto err_netdev; ++ ++ spin_lock_init(&mctp_spi_dev->lock); ++ init_waitqueue_head(&mctp_spi_dev->main_thread_wq); ++ skb_queue_head_init(&mctp_spi_dev->tx_queue); ++ rc = mctp_register_netdev(ndev, NULL, MCTP_PHYS_BINDING_SERIAL); ++ if (rc) ++ goto err_netdev; ++ init_completion(&mctp_spi_dev->rx_done); ++ complete(&mctp_spi_dev->rx_done); ++ spin_lock_irqsave(&mctp_spi_dev->lock, flags); ++ mctp_spi_dev->allow_rx = false; ++ spin_unlock_irqrestore(&mctp_spi_dev->lock, flags); ++ mctp_spi_dev->ndev = ndev; ++ mctp_spi_dev->spidev = spidev; ++ mctp_spi_dev->tx_thread = kthread_create(mctp_spi_tx_thread, mctp_spi_dev, ++ "%s/tx", ndev->name); ++ if (IS_ERR(mctp_spi_dev->tx_thread)) ++ return PTR_ERR(mctp_spi_dev->tx_thread); ++ ++ /* Init SPB */ ++ ap = kzalloc(sizeof(*ap), GFP_KERNEL); ++ ap->spi_xfer = spi_raw_transfer; ++ ap->spi = spi; ++ ap->gpio_intr_wq = &mctp_spi_dev->main_thread_wq; ++ ap->gpio_intr_cond = &mctp_spi_dev->gpio_intr_cond; ++ ap->gpio_intr_cond_lock = &mctp_spi_dev->gpio_intr_cond_lock; ++ mctp_spi_dev->ap = ap; ++ ++ /* Setup error injection after netdev registration (debugfs needs the netdev name) */ ++ mctp_spi_error_inject_init(mctp_spi_dev); ++ ++ /* Start the mctp tx worker thread */ ++ wake_up_process(mctp_spi_dev->tx_thread); ++ return status; ++err_netdev: ++ free_netdev(ndev); ++free_ida: ++ ida_free(&mctp_spi_ida, idx); ++ return rc; ++} ++ ++static void mctp_spi_remove(struct spi_device *spi) ++{ ++ struct spidev_data *spidev = spi_get_drvdata(spi); ++ /* prevent new opens */ ++ mutex_lock(&device_list_lock); ++ /* make sure ops on existing fds can abort cleanly */ ++ spin_lock_irq(&spidev->spi_lock); ++ spidev->spi = NULL; ++ spin_unlock_irq(&spidev->spi_lock); ++ ++ list_del(&spidev->device_entry); ++ if (spidev->users == 0) ++ kfree(spidev); ++ mutex_unlock(&device_list_lock); ++} ++ ++static struct spi_driver mctp_spi_driver = { ++ .driver = { ++ .name = "mctpspi", ++ .of_match_table = mctp_spi_dt_ids, ++ }, ++ .probe = mctp_spi_probe, ++ .remove = mctp_spi_remove, ++ .id_table = mctp_spi_ids, ++}; ++ ++/*-------------------------------------------------------------------------*/ ++ ++static int __init mctp_spi_mod_init(void) ++{ ++ int rc; ++ ++ pr_info("MCTP SPI interface driver\n"); ++ ++ /* Initialize error injection infrastructure */ ++ rc = mctp_spi_error_inject_module_init(); ++ if (rc) ++ pr_warn("MCTP SPI: Error injection initialization failed, continuing without it\n"); ++ ++ rc = spi_register_driver(&mctp_spi_driver); ++ if (rc) { ++ mctp_spi_error_inject_module_exit(); ++ return rc; ++ } ++ ++ return 0; ++} ++module_init(mctp_spi_mod_init); ++ ++static void __exit mctp_spi_mod_exit(void) ++{ ++ spi_unregister_driver(&mctp_spi_driver); ++ mctp_spi_error_inject_module_exit(); ++} ++module_exit(mctp_spi_mod_exit); ++ ++MODULE_LICENSE("GPL"); +diff --git a/drivers/net/mctp/mctp-stats.h b/drivers/net/mctp/mctp-stats.h +new file mode 100644 +index 000000000..216a01201 +--- /dev/null ++++ b/drivers/net/mctp/mctp-stats.h +@@ -0,0 +1,217 @@ ++/* ++ * SPDX-FileCopyrightText: Copyright (c) NVIDIA CORPORATION & AFFILIATES. All rights reserved. ++ * SPDX-License-Identifier: Apache-2.0 ++ * ++ * Licensed under the Apache License, Version 2.0 (the "License"); ++ * you may not use this file except in compliance with the License. ++ * You may obtain a copy of the License at ++ * ++ * http://www.apache.org/licenses/LICENSE-2.0 ++ * ++ * Unless required by applicable law or agreed to in writing, software ++ * distributed under the License is distributed on an "AS IS" BASIS, ++ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. ++ * See the License for the specific language governing permissions and ++ * limitations under the License. ++ */ ++ ++/* ++ * MCTP Per-EID Statistics - Shared Infrastructure ++ * ++ * This header provides macros and helpers for tracking statistics ++ * per-endpoint-ID (EID) across all MCTP bindings. ++ */ ++ ++#ifndef __MCTP_STATS_H ++#define __MCTP_STATS_H ++ ++#include ++#include ++#include ++ ++/** ++ * MCTP_EID_UNKNOWN - Special EID value for errors where EID is not yet known ++ * ++ * Value: 256 (out-of-band value to avoid conflict with valid MCTP EIDs 0-254, ++ * and distinct from broadcast address 255) ++ * ++ * This EID is used for tracking errors that occur when the actual endpoint ID ++ * cannot be determined. This happens in several scenarios: ++ * ++ * 1. PRE-PARSE ERRORS: Packets are corrupted/invalid before we can read the ++ * MCTP header (e.g., invalid I2C command code, PEC checksum failure, ++ * packet too short) ++ * ++ * 2. ALLOCATION FAILURES: Memory allocation failed before we could parse the ++ * packet to extract the source/destination EID ++ * ++ * 3. ASYNCHRONOUS EVENTS: Hardware/transport events with no packet context ++ * (e.g., USB URB completion callbacks, I3C IBI events, GPIO interrupts, ++ * device hotplug events) ++ * ++ * 4. TRANSPORT-LEVEL ERRORS: Low-level bus failures before packet transmission ++ * (e.g., SPI transfer errors, I2C arbitration loss on unrelated traffic) ++ * ++ * IMPORTANT DISTINCTION from EID 0: ++ * - UNKNOWN (256): We don't know the EID (error occurred too early) ++ * - EID 0: We successfully read the EID from the packet, and it was 0 ++ * (EID 0 is the "null endpoint" per MCTP spec DSP0236, used for ++ * unallocated/unassigned endpoints) ++ */ ++#define MCTP_EID_UNKNOWN 256 ++ ++/** ++ * MCTP_STAT_INC - Increment per-EID statistics and mark EID as active ++ * @dev_ptr: Pointer to device structure (e.g., midev, mbus, musb) ++ * @eid_val: EID value (0-256) - use MCTP_EID_UNKNOWN if EID not yet known ++ * @stat_field: Name of the statistics field ++ * ++ * This macro is the SINGLE way to track statistics. It updates the per-EID ++ * stats counter for the specified field and marks the EID as active. ++ * ++ * Usage: ++ * // When EID is known: ++ * MCTP_STAT_INC(midev, dest_eid, tx_drop_timeout); ++ * ++ * // When EID is not yet known (before parsing): ++ * MCTP_STAT_INC(midev, MCTP_EID_UNKNOWN, rx_drop_invalid_cmd); ++ */ ++#define MCTP_STAT_INC(dev_ptr, eid_val, stat_field) do { \ ++ typeof(dev_ptr) __dev = (dev_ptr); \ ++ unsigned int __eid = (eid_val); \ ++ set_bit(__eid, __dev->eid_stats.active); \ ++ __dev->eid_stats.eid[__eid].stat_field++; \ ++} while (0) ++ ++/** ++ * MCTP_EID_STATS_HELPERS - Generate ethtool helper functions for per-EID stats ++ * @prefix: Prefix for generated function names (e.g., mctp_i2c, mctp_usb) ++ * @dev_type: Type of device structure (e.g., struct mctp_i2c_dev) ++ * @eid_stats_type: Type of per-EID stats structure ++ * @stat_descs: Array name of stat descriptors ++ * ++ * Generates three helper functions: ++ * - prefix_count_eid_nonzero(): Count non-zero stats for one EID ++ * - prefix_count_eid_stats(): Count total stats for all active EIDs ++ * - prefix_eid_stats_total(): Sum all stats for one EID ++ * ++ * These functions are used by ethtool callbacks to determine output size ++ * and generate the per-EID statistics display. ++ */ ++#define MCTP_EID_STATS_HELPERS(prefix, dev_type, eid_stats_type, stat_descs) \ ++\ ++static int prefix##_count_eid_nonzero(dev_type *dev, unsigned int eid) \ ++{ \ ++ eid_stats_type *es = &dev->eid_stats.eid[eid]; \ ++ u8 *base = (u8 *)es; \ ++ int count = 0; \ ++ unsigned int i; \ ++ \ ++ for (i = 0; i < ARRAY_SIZE(stat_descs); i++) { \ ++ if (*(u64 *)(base + stat_descs[i].offset) != 0) \ ++ count++; \ ++ } \ ++ return count; \ ++} \ ++\ ++static int prefix##_count_eid_stats(dev_type *dev) \ ++{ \ ++ int count = 0; \ ++ int eid; \ ++ \ ++ for_each_set_bit(eid, dev->eid_stats.active, 257) { \ ++ int nz = prefix##_count_eid_nonzero(dev, eid); \ ++ if (nz > 0) \ ++ count += 1 + nz; \ ++ } \ ++ return count; \ ++} \ ++\ ++static u64 prefix##_eid_stats_total(dev_type *dev, unsigned int eid) \ ++{ \ ++ eid_stats_type *es = &dev->eid_stats.eid[eid]; \ ++ u8 *base = (u8 *)es; \ ++ u64 total = 0; \ ++ unsigned int i; \ ++ \ ++ for (i = 0; i < ARRAY_SIZE(stat_descs); i++) \ ++ total += *(u64 *)(base + stat_descs[i].offset); \ ++ return total; \ ++} ++ ++/** ++ * MCTP_EID_GET_STRINGS - Generate ethtool string names for per-EID stats ++ * @dev: Device structure pointer ++ * @data_ptr: Pointer to data buffer pointer (will be advanced) ++ * @stat_descs: Array of stat descriptors ++ * ++ * Outputs per-EID stat names in ethtool format. Only outputs stats for ++ * active EIDs with non-zero values. EID 256 (MCTP_EID_UNKNOWN) is displayed ++ * as "UNKNOWN" instead of "EID_256". ++ */ ++#define MCTP_EID_GET_STRINGS(dev, data_ptr, stat_descs, count_nonzero_fn) do { \ ++ int eid; \ ++ unsigned int i; \ ++ \ ++ for_each_set_bit(eid, (dev)->eid_stats.active, 257) { \ ++ typeof(&(dev)->eid_stats.eid[0]) es = &(dev)->eid_stats.eid[eid]; \ ++ u8 *base = (u8 *)es; \ ++ int nz = count_nonzero_fn((dev), eid); \ ++ \ ++ if (nz == 0) \ ++ continue; \ ++ \ ++ if (eid == MCTP_EID_UNKNOWN) \ ++ snprintf(*(data_ptr), ETH_GSTRING_LEN, "UNKNOWN "); \ ++ else \ ++ snprintf(*(data_ptr), ETH_GSTRING_LEN, "EID_%-3u ", eid); \ ++ *(data_ptr) += ETH_GSTRING_LEN; \ ++ \ ++ for (i = 0; i < ARRAY_SIZE(stat_descs); i++) { \ ++ u64 val = *(u64 *)(base + stat_descs[i].offset); \ ++ if (val != 0) { \ ++ snprintf(*(data_ptr), ETH_GSTRING_LEN, "%-30s", \ ++ stat_descs[i].name); \ ++ *(data_ptr) += ETH_GSTRING_LEN; \ ++ } \ ++ } \ ++ } \ ++} while (0) ++ ++/** ++ * MCTP_EID_GET_STATS - Generate ethtool stat values for per-EID stats ++ * @dev: Device structure pointer ++ * @data_ptr: Pointer to data array pointer (will be advanced) ++ * @stat_descs: Array of stat descriptors ++ * @count_nonzero_fn: Function to count non-zero stats ++ * @total_fn: Function to get total for an EID ++ * ++ * Outputs per-EID stat values in ethtool format. Only outputs stats for ++ * active EIDs with non-zero values. ++ */ ++#define MCTP_EID_GET_STATS(dev, data_ptr, stat_descs, count_nonzero_fn, total_fn) do { \ ++ int eid; \ ++ unsigned int i; \ ++ \ ++ for_each_set_bit(eid, (dev)->eid_stats.active, 257) { \ ++ typeof(&(dev)->eid_stats.eid[0]) es = &(dev)->eid_stats.eid[eid]; \ ++ u8 *base = (u8 *)es; \ ++ int nz = count_nonzero_fn((dev), eid); \ ++ \ ++ if (nz == 0) \ ++ continue; \ ++ \ ++ (*(data_ptr))[0] = total_fn((dev), eid); \ ++ (*data_ptr)++; \ ++ \ ++ for (i = 0; i < ARRAY_SIZE(stat_descs); i++) { \ ++ u64 val = *(u64 *)(base + stat_descs[i].offset); \ ++ if (val != 0) { \ ++ (*(data_ptr))[0] = val; \ ++ (*data_ptr)++; \ ++ } \ ++ } \ ++ } \ ++} while (0) ++ ++#endif /* __MCTP_STATS_H */ +diff --git a/drivers/net/mctp/mctp-usb-error-inject.c b/drivers/net/mctp/mctp-usb-error-inject.c +new file mode 100644 +index 000000000..24c113ea8 +--- /dev/null ++++ b/drivers/net/mctp/mctp-usb-error-inject.c +@@ -0,0 +1,1055 @@ ++// SPDX-License-Identifier: GPL-2.0+ ++/* ++ * mctp-usb-error-inject.c - Error injection infrastructure for MCTP USB ++ * ++ * Copyright (c) 2024 NVIDIA CORPORATION. All rights reserved. ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include ++ ++#include "mctp-usb-internal.h" ++ ++/* Global debugfs root directory */ ++static struct dentry *mctp_usb_debugfs_root; ++ ++/* ===== Error Injection Helper Functions ===== */ ++ ++/* Check if error should be injected based on mode and rate ++ * Note: Caller must check ei->enable_tx or ei->enable_rx before calling this ++ */ ++static bool mctp_should_inject_error(struct mctp_error_inject *ei, u32 rate, u32 *count) ++{ ++ bool inject = false; ++ ++ spin_lock_bh(&ei->lock); ++ ++ switch (ei->mode) { ++ case MCTP_ERR_MODE_ALWAYS: ++ inject = true; ++ break; ++ ++ case MCTP_ERR_MODE_RANDOM: ++ /* Random injection based on rate percentage */ ++ inject = (prandom_u32_state(&ei->rng) % 100) < rate; ++ break; ++ ++ case MCTP_ERR_MODE_COUNT: ++ /* Inject for count packets, then stop */ ++ if (*count > 0) { ++ (*count)--; ++ inject = true; ++ } ++ break; ++ } ++ ++ spin_unlock_bh(&ei->lock); ++ ++ return inject; ++} ++ ++/* Check if packet matches EID filter */ ++static bool mctp_error_inject_match_filter(struct mctp_error_inject *ei, ++ struct sk_buff *skb, ++ struct net_device *netdev) ++{ ++ struct mctp_hdr *mh; ++ ++ if (!ei->eid_filter.enabled) ++ return true; /* No filter, match all */ ++ ++ /* Check if SKB has enough data for USB header + MCTP header */ ++ if (skb->len < (sizeof(struct mctp_usb_hdr) + sizeof(struct mctp_hdr))) ++ return false; ++ ++ /* ++ * Access MCTP header manually - SKB has USB header prepended at this point. ++ * Cannot use mctp_hdr(skb) as it has WARN_ON check that expects MCTP header ++ * at skb->data, but we have: [USB HDR][MCTP HDR][PAYLOAD] ++ */ ++ mh = (struct mctp_hdr *)(skb->data + sizeof(struct mctp_usb_hdr)); ++ ++ /* Check source EID */ ++ if (ei->eid_filter.src_eid != 0 && ++ ei->eid_filter.src_eid != mh->src) { ++ netdev_dbg(netdev, ++ "Error injection: Packet filtered out (src EID %u != filter %u)\n", ++ mh->src, ei->eid_filter.src_eid); ++ return false; ++ } ++ ++ /* Check dest EID */ ++ if (ei->eid_filter.dest_eid != 0 && ++ ei->eid_filter.dest_eid != mh->dest) { ++ netdev_dbg(netdev, ++ "Error injection: Packet filtered out (dest EID %u != filter %u)\n", ++ mh->dest, ei->eid_filter.dest_eid); ++ return false; ++ } ++ ++ /* Packet matches filter */ ++ netdev_dbg(netdev, ++ "Error injection: Packet matches filter (src=%u, dest=%u)\n", ++ mh->src, mh->dest); ++ ++ return true; ++} ++ ++/* TX synchronous error injection (before URB submission) */ ++int mctp_usb_error_inject_tx_sync(struct mctp_usb *mctp_usb, struct sk_buff *skb) ++{ ++ struct mctp_error_inject *ei = &mctp_usb->error_inject; ++ struct mctp_hdr *mh; ++ ++ /* Early exit if TX error injection is disabled or sync code not set - zero overhead */ ++ if (!ei->enable_tx || ei->urb_tx_sync_error_code == 0) ++ return 0; ++ ++ if (!mctp_error_inject_match_filter(ei, skb, mctp_usb->netdev)) ++ return 0; ++ ++ if (!mctp_should_inject_error(ei, ei->urb_tx_error_rate, ++ &ei->urb_tx_sync_inject_count)) { ++ netdev_dbg(mctp_usb->netdev, ++ "Error injection: TX URB submission (sync) - not injecting (mode/rate check)\n"); ++ return 0; ++ } ++ ++ /* Inject delay if configured */ ++ if (ei->delay_ms > 0) { ++ netdev_dbg(mctp_usb->netdev, ++ "Error injection: Injecting %u ms delay\n", ++ ei->delay_ms); ++ msleep(ei->delay_ms); ++ } ++ ++ /* Update statistics */ ++ spin_lock_bh(&ei->lock); ++ ei->urb_tx_sync_errors_injected++; ++ ei->total_errors_injected++; ++ ei->total_packets_processed++; ++ spin_unlock_bh(&ei->lock); ++ ++ /* ++ * Access MCTP header manually - SKB has USB header prepended at this point. ++ * Cannot use mctp_hdr(skb) as it has WARN_ON check that expects MCTP header ++ * at skb->data, but we have: [USB HDR][MCTP HDR][PAYLOAD] ++ */ ++ mh = (struct mctp_hdr *)(skb->data + sizeof(struct mctp_usb_hdr)); ++ ++ netdev_dbg(mctp_usb->netdev, ++ "Error injection: TX URB submission failed (sync) - error=%d, src_eid=%u, dest_eid=%u, " ++ "total_injected=%u, mode=%s\n", ++ ei->urb_tx_sync_error_code, mh->src, mh->dest, ++ ei->urb_tx_sync_errors_injected, ++ ei->mode == MCTP_ERR_MODE_ALWAYS ? "always" : ++ ei->mode == MCTP_ERR_MODE_RANDOM ? "random" : "count"); ++ ++ return ei->urb_tx_sync_error_code; ++} ++EXPORT_SYMBOL_GPL(mctp_usb_error_inject_tx_sync); ++ ++/* TX asynchronous error injection (URB completion) ++ * @mctp_usb: MCTP USB device ++ * @urb: The completed URB (used for status and, when EID filter enabled, to parse first packet) ++ * ++ * Returns: Status to use (original urb->status or injected error code). ++ * ++ * EID filtering: When eid_filter.enabled, we parse the first packet in the URB buffer ++ * and match src_eid/dest_eid (0 = any). Batched URBs contain fragments of one message ++ * (same EID pair), so checking the first packet is sufficient. ++ */ ++int mctp_usb_error_inject_tx_async(struct mctp_usb *mctp_usb, struct urb *urb) ++{ ++ struct mctp_error_inject *ei = &mctp_usb->error_inject; ++ int original_status = urb->status; ++ ++ /* Only inject if original status was success */ ++ if (original_status != 0) { ++ netdev_dbg(mctp_usb->netdev, ++ "Error injection: TX URB already has error status %d, not injecting\n", ++ original_status); ++ return original_status; ++ } ++ ++ /* Early exit if TX error injection is disabled or async code not set - zero overhead */ ++ if (!ei->enable_tx || ei->urb_tx_async_error_code == 0) ++ return original_status; ++ ++ /* EID filter: parse first packet and match src_eid/dest_eid (0 = any) */ ++ if (ei->eid_filter.enabled) { ++ if (urb->transfer_buffer && urb->transfer_buffer_length >= sizeof(struct mctp_usb_hdr) + sizeof(struct mctp_hdr)) { ++ struct mctp_usb_hdr *usb_hdr = (struct mctp_usb_hdr *)urb->transfer_buffer; ++ struct mctp_hdr *mctp_hdr = (struct mctp_hdr *)(usb_hdr + 1); ++ u8 src = mctp_hdr->src; ++ u8 dest = mctp_hdr->dest; ++ bool src_ok = (ei->eid_filter.src_eid == 0 || ei->eid_filter.src_eid == src); ++ bool dest_ok = (ei->eid_filter.dest_eid == 0 || ei->eid_filter.dest_eid == dest); ++ ++ if (!src_ok || !dest_ok) { ++ netdev_dbg(mctp_usb->netdev, ++ "Error injection: TX URB async - EID filter mismatch (src_eid=%u, dest_eid=%u, pkt src=%u dest=%u), not injecting\n", ++ ei->eid_filter.src_eid, ei->eid_filter.dest_eid, src, dest); ++ return original_status; ++ } ++ netdev_dbg(mctp_usb->netdev, ++ "Error injection: TX URB async - EID filter match (src=%u dest=%u)\n", ++ src, dest); ++ } else { ++ netdev_dbg(mctp_usb->netdev, ++ "Error injection: TX URB async - buffer too small for EID parsing, not injecting\n"); ++ return original_status; ++ } ++ } ++ ++ if (!mctp_should_inject_error(ei, ei->urb_tx_error_rate, ++ &ei->urb_tx_async_inject_count)) { ++ netdev_dbg(mctp_usb->netdev, ++ "Error injection: TX URB completion (async) - not injecting (mode/rate check)\n"); ++ return original_status; ++ } ++ ++ /* Update statistics */ ++ spin_lock_bh(&ei->lock); ++ ei->urb_tx_async_errors_injected++; ++ ei->total_errors_injected++; ++ spin_unlock_bh(&ei->lock); ++ ++ netdev_dbg(mctp_usb->netdev, ++ "Error injection: TX URB completion failed (async) - error=%d, total_injected=%u, mode=%s (URB-level, may affect multiple packets)\n", ++ ei->urb_tx_async_error_code, ++ ei->urb_tx_async_errors_injected, ++ ei->mode == MCTP_ERR_MODE_ALWAYS ? "always" : ++ ei->mode == MCTP_ERR_MODE_RANDOM ? "random" : "count"); ++ ++ return ei->urb_tx_async_error_code; ++} ++EXPORT_SYMBOL_GPL(mctp_usb_error_inject_tx_async); ++ ++/* RX error injection */ ++int mctp_usb_error_inject_rx(struct mctp_usb *mctp_usb, int original_status) ++{ ++ struct mctp_error_inject *ei = &mctp_usb->error_inject; ++ ++ if (original_status != 0) { ++ netdev_dbg(mctp_usb->netdev, ++ "Error injection: RX URB already has error status %d, not injecting\n", ++ original_status); ++ return original_status; ++ } ++ ++ /* Early exit if RX error injection is disabled - zero overhead */ ++ if (!ei->enable_rx || ei->urb_rx_error_code == 0) ++ return original_status; ++ ++ /* RX: Can't check EID filter yet as packet not parsed */ ++ if (ei->eid_filter.enabled) { ++ netdev_dbg(mctp_usb->netdev, ++ "Error injection: RX - EID filter enabled but cannot filter (packet not parsed yet)\n"); ++ } ++ ++ if (!mctp_should_inject_error(ei, ei->urb_rx_error_rate, ++ &ei->urb_rx_inject_count)) { ++ netdev_dbg(mctp_usb->netdev, ++ "Error injection: RX URB completion - not injecting (mode/rate check)\n"); ++ return original_status; ++ } ++ ++ /* Update statistics */ ++ spin_lock_bh(&ei->lock); ++ ei->urb_rx_errors_injected++; ++ ei->total_errors_injected++; ++ spin_unlock_bh(&ei->lock); ++ ++ netdev_dbg(mctp_usb->netdev, ++ "Error injection: RX URB completion failed - error=%d, total_injected=%u, mode=%s\n", ++ ei->urb_rx_error_code, ++ ei->urb_rx_errors_injected, ++ ei->mode == MCTP_ERR_MODE_ALWAYS ? "always" : ++ ei->mode == MCTP_ERR_MODE_RANDOM ? "random" : "count"); ++ ++ netdev_dbg(mctp_usb->netdev, ++ "Error injection: RX packet will be DROPPED due to injected error\n"); ++ ++ return ei->urb_rx_error_code; ++} ++EXPORT_SYMBOL_GPL(mctp_usb_error_inject_rx); ++ ++/* Fragment error injection ++ * Integrated with RX error injection infrastructure: ++ * - Requires enable_rx to be set ++ * - Supports EID filtering (src_eid, dest_eid, msg_type) ++ * - Three injection modes: ++ * 1. enable_fragment_drop: Drop 2nd+ fragments (triggers reassembly timeout) ++ * 2. enable_seq_corrupt: Corrupt sequence number in middle/end fragments (triggers sequence error) ++ * 3. enable_som_clear: Clear SOM bit in first fragment (triggers missing SOM error) ++ * ++ * Returns: ++ * 0 - Pass through (normally or with corruption) ++ * 1 - Drop this fragment ++ */ ++int mctp_usb_error_inject_fragment(struct mctp_usb *mctp_usb, struct sk_buff *skb) ++{ ++ struct mctp_error_inject *ei = &mctp_usb->error_inject; ++ struct mctp_hdr *mh; ++ u8 flags; ++ u8 msg_type; ++ u8 seq; ++ ++ /* Early exit if RX error injection disabled or no fragment injection enabled */ ++ if (!ei->enable_rx || (!ei->enable_fragment_drop && !ei->enable_seq_corrupt && !ei->enable_som_clear)) ++ return 0; ++ ++ /* Check if we have enough data for MCTP header + message type */ ++ if (skb->len < sizeof(struct mctp_hdr) + 1) ++ return 0; ++ ++ /* Parse MCTP header (skb->data points to MCTP header after USB header removed) */ ++ mh = (struct mctp_hdr *)skb->data; ++ flags = mh->flags_seq_tag & (MCTP_HDR_FLAG_SOM | MCTP_HDR_FLAG_EOM); ++ seq = (mh->flags_seq_tag >> 4) & 0x03; ++ ++ /* Single-packet message (SOM+EOM) - pass through (not a fragment) */ ++ if (flags == (MCTP_HDR_FLAG_SOM | MCTP_HDR_FLAG_EOM)) ++ return 0; ++ ++ /* Apply EID filtering if enabled */ ++ if (ei->eid_filter.enabled) { ++ /* Check source EID filter */ ++ if (ei->eid_filter.src_eid != 0 && ++ ei->eid_filter.src_eid != mh->src) { ++ netdev_dbg(mctp_usb->netdev, ++ "Fragment injection: Packet filtered out (src EID %u != filter %u)\n", ++ mh->src, ei->eid_filter.src_eid); ++ return 0; /* Does not match filter, pass through */ ++ } ++ ++ /* Check destination EID filter */ ++ if (ei->eid_filter.dest_eid != 0 && ++ ei->eid_filter.dest_eid != mh->dest) { ++ netdev_dbg(mctp_usb->netdev, ++ "Fragment injection: Packet filtered out (dest EID %u != filter %u)\n", ++ mh->dest, ei->eid_filter.dest_eid); ++ return 0; /* Does not match filter, pass through */ ++ } ++ ++ /* Check message type filter (first byte after MCTP header) */ ++ msg_type = *((u8 *)(skb->data + sizeof(struct mctp_hdr))); ++ if (ei->eid_filter.msg_type != 0 && ++ ei->eid_filter.msg_type != msg_type) { ++ netdev_dbg(mctp_usb->netdev, ++ "Fragment injection: Packet filtered out (msg_type 0x%02x != filter 0x%02x)\n", ++ msg_type, ei->eid_filter.msg_type); ++ return 0; /* Does not match filter, pass through */ ++ } ++ ++ netdev_dbg(mctp_usb->netdev, ++ "Fragment injection: Packet MATCHES filter (src=%u, dest=%u, msg_type=0x%02x)\n", ++ mh->src, mh->dest, msg_type); ++ } ++ ++ /* ===== Injection Type 1: Clear SOM bit (first fragment) ===== */ ++ if (ei->enable_som_clear && (flags & MCTP_HDR_FLAG_SOM)) { ++ /* Clear the SOM bit in first fragment */ ++ mh->flags_seq_tag &= ~MCTP_HDR_FLAG_SOM; ++ ++ spin_lock_bh(&ei->lock); ++ ei->som_clears++; ++ ei->total_errors_injected++; ++ spin_unlock_bh(&ei->lock); ++ ++ netdev_dbg(mctp_usb->netdev, ++ "Error injection: SOM bit CLEARED (first fragment, src=%u, dest=%u, seq=%u)%s\n", ++ mh->src, mh->dest, seq, ++ ei->eid_filter.enabled ? " [EID filter ACTIVE]" : ""); ++ ++ return 0; /* Pass through with corrupted SOM */ ++ } ++ ++ /* ===== Injection Type 2: Corrupt sequence number (middle/end fragments) ===== */ ++ if (ei->enable_seq_corrupt && !(flags & MCTP_HDR_FLAG_SOM)) { ++ /* This is a middle or end fragment - corrupt the sequence number */ ++ u8 corrupted_seq = (seq + 1) & 0x03; /* Increment sequence (wrap at 4) */ ++ ++ /* Clear old sequence and set corrupted one */ ++ mh->flags_seq_tag &= ~(0x03 << 4); /* Clear bits 4-5 */ ++ mh->flags_seq_tag |= (corrupted_seq << 4); /* Set corrupted sequence */ ++ ++ spin_lock_bh(&ei->lock); ++ ei->seq_corruptions++; ++ ei->total_errors_injected++; ++ spin_unlock_bh(&ei->lock); ++ ++ netdev_dbg(mctp_usb->netdev, ++ "Error injection: Sequence CORRUPTED (2nd+ fragment, src=%u, dest=%u, orig_seq=%u -> corrupted_seq=%u)%s\n", ++ mh->src, mh->dest, seq, corrupted_seq, ++ ei->eid_filter.enabled ? " [EID filter ACTIVE]" : ""); ++ ++ return 0; /* Pass through with corrupted sequence */ ++ } ++ ++ /* ===== Injection Type 3: Drop fragment (2nd+ fragments) ===== */ ++ if (ei->enable_fragment_drop && !(flags & MCTP_HDR_FLAG_SOM)) { ++ /* Drop this fragment (2nd onwards) */ ++ spin_lock_bh(&ei->lock); ++ ei->fragments_dropped++; ++ ei->total_errors_injected++; ++ spin_unlock_bh(&ei->lock); ++ ++ netdev_dbg(mctp_usb->netdev, ++ "Error injection: Fragment DROPPED (2nd+ fragment, src=%u, dest=%u, seq=%u)%s\n", ++ mh->src, mh->dest, seq, ++ ei->eid_filter.enabled ? " [EID filter ACTIVE]" : ""); ++ ++ return 1; /* Drop this fragment */ ++ } ++ ++ /* No injection applied - pass through normally */ ++ return 0; ++} ++EXPORT_SYMBOL_GPL(mctp_usb_error_inject_fragment); ++ ++/* ===== Debugfs Interface ===== */ ++ ++/* Helper to get mctp_usb from file */ ++static struct mctp_usb *mctp_usb_from_file(struct file *file) ++{ ++ return file->f_inode->i_private; ++} ++ ++/* enable_tx attribute */ ++static ssize_t mctp_debugfs_enable_tx_read(struct file *file, char __user *userbuf, ++ size_t count, loff_t *ppos) ++{ ++ struct mctp_usb *mctp_usb = mctp_usb_from_file(file); ++ char buf[8]; ++ int len; ++ ++ len = snprintf(buf, sizeof(buf), "%d\n", mctp_usb->error_inject.enable_tx); ++ return simple_read_from_buffer(userbuf, count, ppos, buf, len); ++} ++ ++static ssize_t mctp_debugfs_enable_tx_write(struct file *file, const char __user *userbuf, ++ size_t count, loff_t *ppos) ++{ ++ struct mctp_usb *mctp_usb = mctp_usb_from_file(file); ++ char buf[8]; ++ bool enable; ++ int rc; ++ ++ if (count >= sizeof(buf)) ++ return -EINVAL; ++ ++ if (copy_from_user(buf, userbuf, count)) ++ return -EFAULT; ++ ++ buf[count] = '\0'; ++ rc = kstrtobool(buf, &enable); ++ if (rc) ++ return rc; ++ ++ spin_lock_bh(&mctp_usb->error_inject.lock); ++ mctp_usb->error_inject.enable_tx = enable; ++ spin_unlock_bh(&mctp_usb->error_inject.lock); ++ ++ netdev_dbg(mctp_usb->netdev, "TX error injection %s\n", ++ enable ? "enabled" : "disabled"); ++ ++ return count; ++} ++ ++static const struct file_operations mctp_debugfs_enable_tx_fops = { ++ .owner = THIS_MODULE, ++ .read = mctp_debugfs_enable_tx_read, ++ .write = mctp_debugfs_enable_tx_write, ++ .open = simple_open, ++ .llseek = default_llseek, ++}; ++ ++/* enable_rx attribute */ ++static ssize_t mctp_debugfs_enable_rx_read(struct file *file, char __user *userbuf, ++ size_t count, loff_t *ppos) ++{ ++ struct mctp_usb *mctp_usb = mctp_usb_from_file(file); ++ char buf[8]; ++ int len; ++ ++ len = snprintf(buf, sizeof(buf), "%d\n", mctp_usb->error_inject.enable_rx); ++ return simple_read_from_buffer(userbuf, count, ppos, buf, len); ++} ++ ++static ssize_t mctp_debugfs_enable_rx_write(struct file *file, const char __user *userbuf, ++ size_t count, loff_t *ppos) ++{ ++ struct mctp_usb *mctp_usb = mctp_usb_from_file(file); ++ char buf[8]; ++ bool enable; ++ int rc; ++ ++ if (count >= sizeof(buf)) ++ return -EINVAL; ++ ++ if (copy_from_user(buf, userbuf, count)) ++ return -EFAULT; ++ ++ buf[count] = '\0'; ++ rc = kstrtobool(buf, &enable); ++ if (rc) ++ return rc; ++ ++ spin_lock_bh(&mctp_usb->error_inject.lock); ++ mctp_usb->error_inject.enable_rx = enable; ++ spin_unlock_bh(&mctp_usb->error_inject.lock); ++ ++ netdev_dbg(mctp_usb->netdev, "RX error injection %s\n", ++ enable ? "enabled" : "disabled"); ++ ++ return count; ++} ++ ++static const struct file_operations mctp_debugfs_enable_rx_fops = { ++ .owner = THIS_MODULE, ++ .read = mctp_debugfs_enable_rx_read, ++ .write = mctp_debugfs_enable_rx_write, ++ .open = simple_open, ++ .llseek = default_llseek, ++}; ++ ++/* mode attribute */ ++static ssize_t mctp_debugfs_mode_read(struct file *file, char __user *userbuf, ++ size_t count, loff_t *ppos) ++{ ++ struct mctp_usb *mctp_usb = mctp_usb_from_file(file); ++ const char *mode_str; ++ char buf[16]; ++ int len; ++ ++ switch (mctp_usb->error_inject.mode) { ++ case MCTP_ERR_MODE_ALWAYS: ++ mode_str = "always\n"; ++ break; ++ case MCTP_ERR_MODE_RANDOM: ++ mode_str = "random\n"; ++ break; ++ case MCTP_ERR_MODE_COUNT: ++ mode_str = "count\n"; ++ break; ++ default: ++ mode_str = "unknown\n"; ++ break; ++ } ++ ++ len = snprintf(buf, sizeof(buf), "%s", mode_str); ++ return simple_read_from_buffer(userbuf, count, ppos, buf, len); ++} ++ ++static ssize_t mctp_debugfs_mode_write(struct file *file, const char __user *userbuf, ++ size_t count, loff_t *ppos) ++{ ++ struct mctp_usb *mctp_usb = mctp_usb_from_file(file); ++ char buf[16]; ++ ++ if (count >= sizeof(buf)) ++ return -EINVAL; ++ ++ if (copy_from_user(buf, userbuf, count)) ++ return -EFAULT; ++ ++ buf[count] = '\0'; ++ ++ spin_lock_bh(&mctp_usb->error_inject.lock); ++ ++ if (strncmp(buf, "always", 6) == 0) ++ mctp_usb->error_inject.mode = MCTP_ERR_MODE_ALWAYS; ++ else if (strncmp(buf, "random", 6) == 0) ++ mctp_usb->error_inject.mode = MCTP_ERR_MODE_RANDOM; ++ else if (strncmp(buf, "count", 5) == 0) ++ mctp_usb->error_inject.mode = MCTP_ERR_MODE_COUNT; ++ else { ++ spin_unlock_bh(&mctp_usb->error_inject.lock); ++ return -EINVAL; ++ } ++ ++ spin_unlock_bh(&mctp_usb->error_inject.lock); ++ ++ return count; ++} ++ ++static const struct file_operations mctp_debugfs_mode_fops = { ++ .owner = THIS_MODULE, ++ .read = mctp_debugfs_mode_read, ++ .write = mctp_debugfs_mode_write, ++ .open = simple_open, ++ .llseek = default_llseek, ++}; ++ ++/* Generic u32 read/write helpers */ ++#define MCTP_DEBUGFS_U32_DEFINE(name, field) \ ++static ssize_t mctp_debugfs_##name##_read(struct file *file, char __user *userbuf, \ ++ size_t count, loff_t *ppos) \ ++{ \ ++ struct mctp_usb *mctp_usb = mctp_usb_from_file(file); \ ++ char buf[32]; \ ++ int len; \ ++ \ ++ len = snprintf(buf, sizeof(buf), "%u\n", mctp_usb->error_inject.field); \ ++ return simple_read_from_buffer(userbuf, count, ppos, buf, len); \ ++} \ ++\ ++static ssize_t mctp_debugfs_##name##_write(struct file *file, const char __user *userbuf, \ ++ size_t count, loff_t *ppos) \ ++{ \ ++ struct mctp_usb *mctp_usb = mctp_usb_from_file(file); \ ++ char buf[32]; \ ++ u32 val; \ ++ int rc; \ ++ \ ++ if (count >= sizeof(buf)) \ ++ return -EINVAL; \ ++ \ ++ if (copy_from_user(buf, userbuf, count)) \ ++ return -EFAULT; \ ++ \ ++ buf[count] = '\0'; \ ++ rc = kstrtou32(buf, 0, &val); \ ++ if (rc) \ ++ return rc; \ ++ \ ++ spin_lock_bh(&mctp_usb->error_inject.lock); \ ++ mctp_usb->error_inject.field = val; \ ++ spin_unlock_bh(&mctp_usb->error_inject.lock); \ ++ \ ++ return count; \ ++} \ ++\ ++static const struct file_operations mctp_debugfs_##name##_fops = { \ ++ .owner = THIS_MODULE, \ ++ .read = mctp_debugfs_##name##_read, \ ++ .write = mctp_debugfs_##name##_write, \ ++ .open = simple_open, \ ++ .llseek = default_llseek, \ ++}; ++ ++/* Generic int read/write helpers (for error codes) */ ++#define MCTP_DEBUGFS_INT_DEFINE(name, field) \ ++static ssize_t mctp_debugfs_##name##_read(struct file *file, char __user *userbuf, \ ++ size_t count, loff_t *ppos) \ ++{ \ ++ struct mctp_usb *mctp_usb = mctp_usb_from_file(file); \ ++ char buf[32]; \ ++ int len; \ ++ \ ++ len = snprintf(buf, sizeof(buf), "%d\n", mctp_usb->error_inject.field); \ ++ return simple_read_from_buffer(userbuf, count, ppos, buf, len); \ ++} \ ++\ ++static ssize_t mctp_debugfs_##name##_write(struct file *file, const char __user *userbuf, \ ++ size_t count, loff_t *ppos) \ ++{ \ ++ struct mctp_usb *mctp_usb = mctp_usb_from_file(file); \ ++ char buf[32]; \ ++ int val; \ ++ int rc; \ ++ \ ++ if (count >= sizeof(buf)) \ ++ return -EINVAL; \ ++ \ ++ if (copy_from_user(buf, userbuf, count)) \ ++ return -EFAULT; \ ++ \ ++ buf[count] = '\0'; \ ++ rc = kstrtoint(buf, 0, &val); \ ++ if (rc) \ ++ return rc; \ ++ \ ++ spin_lock_bh(&mctp_usb->error_inject.lock); \ ++ mctp_usb->error_inject.field = val; \ ++ spin_unlock_bh(&mctp_usb->error_inject.lock); \ ++ \ ++ return count; \ ++} \ ++\ ++static const struct file_operations mctp_debugfs_##name##_fops = { \ ++ .owner = THIS_MODULE, \ ++ .read = mctp_debugfs_##name##_read, \ ++ .write = mctp_debugfs_##name##_write, \ ++ .open = simple_open, \ ++ .llseek = default_llseek, \ ++}; ++ ++/* Generic bool read/write helpers */ ++#define MCTP_DEBUGFS_BOOL_DEFINE(name, field) \ ++static ssize_t mctp_debugfs_##name##_read(struct file *file, char __user *userbuf, \ ++ size_t count, loff_t *ppos) \ ++{ \ ++ struct mctp_usb *mctp_usb = mctp_usb_from_file(file); \ ++ char buf[8]; \ ++ int len; \ ++ \ ++ len = snprintf(buf, sizeof(buf), "%d\n", mctp_usb->error_inject.field ? 1 : 0); \ ++ return simple_read_from_buffer(userbuf, count, ppos, buf, len); \ ++} \ ++\ ++static ssize_t mctp_debugfs_##name##_write(struct file *file, const char __user *userbuf, \ ++ size_t count, loff_t *ppos) \ ++{ \ ++ struct mctp_usb *mctp_usb = mctp_usb_from_file(file); \ ++ char buf[8]; \ ++ int val; \ ++ int rc; \ ++ \ ++ if (count >= sizeof(buf)) \ ++ return -EINVAL; \ ++ \ ++ if (copy_from_user(buf, userbuf, count)) \ ++ return -EFAULT; \ ++ \ ++ buf[count] = '\0'; \ ++ rc = kstrtoint(buf, 0, &val); \ ++ if (rc) \ ++ return rc; \ ++ \ ++ spin_lock_bh(&mctp_usb->error_inject.lock); \ ++ mctp_usb->error_inject.field = (val != 0); \ ++ spin_unlock_bh(&mctp_usb->error_inject.lock); \ ++ \ ++ return count; \ ++} \ ++\ ++static const struct file_operations mctp_debugfs_##name##_fops = { \ ++ .owner = THIS_MODULE, \ ++ .read = mctp_debugfs_##name##_read, \ ++ .write = mctp_debugfs_##name##_write, \ ++ .open = simple_open, \ ++ .llseek = default_llseek, \ ++}; ++ ++/* Define all the file operations */ ++MCTP_DEBUGFS_INT_DEFINE(urb_tx_sync_error_code, urb_tx_sync_error_code) ++MCTP_DEBUGFS_INT_DEFINE(urb_tx_async_error_code, urb_tx_async_error_code) ++MCTP_DEBUGFS_U32_DEFINE(urb_tx_error_rate, urb_tx_error_rate) ++MCTP_DEBUGFS_INT_DEFINE(urb_rx_error_code, urb_rx_error_code) ++MCTP_DEBUGFS_U32_DEFINE(urb_rx_error_rate, urb_rx_error_rate) ++MCTP_DEBUGFS_BOOL_DEFINE(enable_fragment_drop, enable_fragment_drop) ++MCTP_DEBUGFS_BOOL_DEFINE(enable_seq_corrupt, enable_seq_corrupt) ++MCTP_DEBUGFS_BOOL_DEFINE(enable_som_clear, enable_som_clear) ++MCTP_DEBUGFS_U32_DEFINE(delay_ms, delay_ms) ++ ++/* EID filter attributes */ ++#define MCTP_DEBUGFS_U8_DEFINE(name, field) \ ++static ssize_t mctp_debugfs_##name##_read(struct file *file, char __user *userbuf, \ ++ size_t count, loff_t *ppos) \ ++{ \ ++ struct mctp_usb *mctp_usb = mctp_usb_from_file(file); \ ++ char buf[8]; \ ++ int len; \ ++ \ ++ len = snprintf(buf, sizeof(buf), "%u\n", mctp_usb->error_inject.eid_filter.field); \ ++ return simple_read_from_buffer(userbuf, count, ppos, buf, len); \ ++} \ ++\ ++static ssize_t mctp_debugfs_##name##_write(struct file *file, const char __user *userbuf, \ ++ size_t count, loff_t *ppos) \ ++{ \ ++ struct mctp_usb *mctp_usb = mctp_usb_from_file(file); \ ++ char buf[8]; \ ++ u8 val; \ ++ int rc; \ ++ \ ++ if (count >= sizeof(buf)) \ ++ return -EINVAL; \ ++ \ ++ if (copy_from_user(buf, userbuf, count)) \ ++ return -EFAULT; \ ++ \ ++ buf[count] = '\0'; \ ++ rc = kstrtou8(buf, 0, &val); \ ++ if (rc) \ ++ return rc; \ ++ \ ++ spin_lock_bh(&mctp_usb->error_inject.lock); \ ++ mctp_usb->error_inject.eid_filter.field = val; \ ++ spin_unlock_bh(&mctp_usb->error_inject.lock); \ ++ \ ++ return count; \ ++} \ ++\ ++static const struct file_operations mctp_debugfs_##name##_fops = { \ ++ .owner = THIS_MODULE, \ ++ .read = mctp_debugfs_##name##_read, \ ++ .write = mctp_debugfs_##name##_write, \ ++ .open = simple_open, \ ++ .llseek = default_llseek, \ ++}; ++ ++static ssize_t mctp_debugfs_eid_filter_enable_read(struct file *file, char __user *userbuf, ++ size_t count, loff_t *ppos) ++{ ++ struct mctp_usb *mctp_usb = mctp_usb_from_file(file); ++ char buf[8]; ++ int len; ++ ++ len = snprintf(buf, sizeof(buf), "%d\n", mctp_usb->error_inject.eid_filter.enabled); ++ return simple_read_from_buffer(userbuf, count, ppos, buf, len); ++} ++ ++static ssize_t mctp_debugfs_eid_filter_enable_write(struct file *file, const char __user *userbuf, ++ size_t count, loff_t *ppos) ++{ ++ struct mctp_usb *mctp_usb = mctp_usb_from_file(file); ++ char buf[8]; ++ bool enable; ++ int rc; ++ ++ if (count >= sizeof(buf)) ++ return -EINVAL; ++ ++ if (copy_from_user(buf, userbuf, count)) ++ return -EFAULT; ++ ++ buf[count] = '\0'; ++ rc = kstrtobool(buf, &enable); ++ if (rc) ++ return rc; ++ ++ spin_lock_bh(&mctp_usb->error_inject.lock); ++ mctp_usb->error_inject.eid_filter.enabled = enable; ++ spin_unlock_bh(&mctp_usb->error_inject.lock); ++ ++ return count; ++} ++ ++static const struct file_operations mctp_debugfs_eid_filter_enable_fops = { ++ .owner = THIS_MODULE, ++ .read = mctp_debugfs_eid_filter_enable_read, ++ .write = mctp_debugfs_eid_filter_enable_write, ++ .open = simple_open, ++ .llseek = default_llseek, ++}; ++ ++MCTP_DEBUGFS_U8_DEFINE(eid_filter_src_eid, src_eid) ++MCTP_DEBUGFS_U8_DEFINE(eid_filter_dest_eid, dest_eid) ++MCTP_DEBUGFS_U8_DEFINE(eid_filter_msg_type, msg_type) ++ ++/* stats attribute (read-only) */ ++static ssize_t mctp_debugfs_stats_read(struct file *file, char __user *userbuf, ++ size_t count, loff_t *ppos) ++{ ++ struct mctp_usb *mctp_usb = mctp_usb_from_file(file); ++ struct mctp_error_inject *ei = &mctp_usb->error_inject; ++ char *buf; ++ int len = 0; ++ ssize_t ret; ++ ++ buf = kmalloc(PAGE_SIZE, GFP_KERNEL); ++ if (!buf) ++ return -ENOMEM; ++ ++ spin_lock_bh(&ei->lock); ++ ++ len += snprintf(buf + len, PAGE_SIZE - len, "enable_tx: %d\n", ei->enable_tx); ++ len += snprintf(buf + len, PAGE_SIZE - len, "enable_rx: %d\n", ei->enable_rx); ++ len += snprintf(buf + len, PAGE_SIZE - len, "mode: %s\n", ++ ei->mode == MCTP_ERR_MODE_ALWAYS ? "always" : ++ ei->mode == MCTP_ERR_MODE_RANDOM ? "random" : "count"); ++ len += snprintf(buf + len, PAGE_SIZE - len, "tx_sync_errors_injected: %u\n", ++ ei->urb_tx_sync_errors_injected); ++ len += snprintf(buf + len, PAGE_SIZE - len, "tx_async_errors_injected: %u\n", ++ ei->urb_tx_async_errors_injected); ++ len += snprintf(buf + len, PAGE_SIZE - len, "rx_errors_injected: %u\n", ++ ei->urb_rx_errors_injected); ++ len += snprintf(buf + len, PAGE_SIZE - len, "fragments_dropped: %u\n", ++ ei->fragments_dropped); ++ len += snprintf(buf + len, PAGE_SIZE - len, "seq_corruptions: %u\n", ++ ei->seq_corruptions); ++ len += snprintf(buf + len, PAGE_SIZE - len, "som_clears: %u\n", ++ ei->som_clears); ++ len += snprintf(buf + len, PAGE_SIZE - len, "total_packets_processed: %llu\n", ++ ei->total_packets_processed); ++ len += snprintf(buf + len, PAGE_SIZE - len, "total_errors_injected: %llu\n", ++ ei->total_errors_injected); ++ ++ spin_unlock_bh(&ei->lock); ++ ++ ret = simple_read_from_buffer(userbuf, count, ppos, buf, len); ++ kfree(buf); ++ ++ return ret; ++} ++ ++static const struct file_operations mctp_debugfs_stats_fops = { ++ .owner = THIS_MODULE, ++ .read = mctp_debugfs_stats_read, ++ .open = simple_open, ++ .llseek = default_llseek, ++}; ++ ++/* reset attribute (write-only) */ ++static ssize_t mctp_debugfs_reset_write(struct file *file, const char __user *userbuf, ++ size_t count, loff_t *ppos) ++{ ++ struct mctp_usb *mctp_usb = mctp_usb_from_file(file); ++ struct mctp_error_inject *ei = &mctp_usb->error_inject; ++ char buf[8]; ++ int val; ++ int rc; ++ ++ if (count >= sizeof(buf)) ++ return -EINVAL; ++ ++ if (copy_from_user(buf, userbuf, count)) ++ return -EFAULT; ++ ++ buf[count] = '\0'; ++ rc = kstrtoint(buf, 0, &val); ++ if (rc) ++ return rc; ++ ++ if (val != 1) ++ return -EINVAL; ++ ++ spin_lock_bh(&ei->lock); ++ ++ /* Reset all state */ ++ ei->enable_tx = false; ++ ei->enable_rx = false; ++ ei->mode = MCTP_ERR_MODE_ALWAYS; ++ ei->urb_tx_sync_error_code = 0; ++ ei->urb_tx_async_error_code = 0; ++ ei->urb_tx_error_rate = 0; ++ ei->urb_tx_sync_inject_count = 0; ++ ei->urb_tx_async_inject_count = 0; ++ ei->urb_rx_error_code = 0; ++ ei->urb_rx_error_rate = 0; ++ ei->urb_rx_inject_count = 0; ++ ei->enable_fragment_drop = false; ++ ei->enable_seq_corrupt = false; ++ ei->enable_som_clear = false; ++ ei->delay_ms = 0; ++ ei->eid_filter.enabled = false; ++ ei->eid_filter.src_eid = 0; ++ ei->eid_filter.dest_eid = 0; ++ ei->eid_filter.msg_type = 0; ++ ++ /* Reset statistics */ ++ ei->urb_tx_sync_errors_injected = 0; ++ ei->urb_tx_async_errors_injected = 0; ++ ei->urb_rx_errors_injected = 0; ++ ei->fragments_dropped = 0; ++ ei->seq_corruptions = 0; ++ ei->som_clears = 0; ++ ei->total_packets_processed = 0; ++ ei->total_errors_injected = 0; ++ ++ spin_unlock_bh(&ei->lock); ++ ++ netdev_dbg(mctp_usb->netdev, "Error injection reset\n"); ++ ++ return count; ++} ++ ++static const struct file_operations mctp_debugfs_reset_fops = { ++ .owner = THIS_MODULE, ++ .write = mctp_debugfs_reset_write, ++ .open = simple_open, ++ .llseek = default_llseek, ++}; ++ ++/* Initialize error injection - called from main driver probe */ ++void mctp_usb_error_inject_init(struct mctp_usb *mctp_usb) ++{ ++ struct dentry *dir, *eid_dir; ++ ++ /* Initialize error injection state */ ++ spin_lock_init(&mctp_usb->error_inject.lock); ++ prandom_seed_state(&mctp_usb->error_inject.rng, (u64)jiffies); ++ mctp_usb->error_inject.enable_tx = false; ++ mctp_usb->error_inject.enable_rx = false; ++ mctp_usb->error_inject.mode = MCTP_ERR_MODE_ALWAYS; ++ /* All other fields are zero-initialized */ ++ ++ /* Setup debugfs */ ++ if (!mctp_usb_debugfs_root) ++ return; ++ ++ /* Use USB interface device name (stable, unique, persistent across reboots) ++ * instead of netdev name (which can be renamed by udev after probe). ++ * This prevents debugfs directory name collisions when devices are hot-plugged. ++ * Example: "1-1.1.1.1:1.0" instead of "mctpusb0" ++ */ ++ dir = debugfs_create_dir(dev_name(&mctp_usb->intf->dev), mctp_usb_debugfs_root); ++ if (IS_ERR_OR_NULL(dir)) { ++ netdev_warn(mctp_usb->netdev, ++ "Failed to create debugfs directory '%s', error injection disabled\n", ++ dev_name(&mctp_usb->intf->dev)); ++ return; ++ } ++ ++ mctp_usb->debugfs_dir = dir; ++ ++ /* Create error_inject files */ ++ debugfs_create_file("enable_tx", 0600, dir, mctp_usb, &mctp_debugfs_enable_tx_fops); ++ debugfs_create_file("enable_rx", 0600, dir, mctp_usb, &mctp_debugfs_enable_rx_fops); ++ debugfs_create_file("mode", 0600, dir, mctp_usb, &mctp_debugfs_mode_fops); ++ debugfs_create_file("urb_tx_sync_error_code", 0600, dir, mctp_usb, &mctp_debugfs_urb_tx_sync_error_code_fops); ++ debugfs_create_file("urb_tx_async_error_code", 0600, dir, mctp_usb, &mctp_debugfs_urb_tx_async_error_code_fops); ++ debugfs_create_file("urb_tx_error_rate", 0600, dir, mctp_usb, &mctp_debugfs_urb_tx_error_rate_fops); ++ debugfs_create_file("urb_rx_error_code", 0600, dir, mctp_usb, &mctp_debugfs_urb_rx_error_code_fops); ++ debugfs_create_file("urb_rx_error_rate", 0600, dir, mctp_usb, &mctp_debugfs_urb_rx_error_rate_fops); ++ debugfs_create_file("enable_fragment_drop", 0600, dir, mctp_usb, &mctp_debugfs_enable_fragment_drop_fops); ++ debugfs_create_file("enable_seq_corrupt", 0600, dir, mctp_usb, &mctp_debugfs_enable_seq_corrupt_fops); ++ debugfs_create_file("enable_som_clear", 0600, dir, mctp_usb, &mctp_debugfs_enable_som_clear_fops); ++ debugfs_create_file("delay_ms", 0600, dir, mctp_usb, &mctp_debugfs_delay_ms_fops); ++ debugfs_create_file("stats", 0400, dir, mctp_usb, &mctp_debugfs_stats_fops); ++ debugfs_create_file("reset", 0200, dir, mctp_usb, &mctp_debugfs_reset_fops); ++ ++ /* Create eid_filter subdirectory */ ++ eid_dir = debugfs_create_dir("eid_filter", dir); ++ if (!IS_ERR_OR_NULL(eid_dir)) { ++ debugfs_create_file("enable", 0600, eid_dir, mctp_usb, &mctp_debugfs_eid_filter_enable_fops); ++ debugfs_create_file("src_eid", 0600, eid_dir, mctp_usb, &mctp_debugfs_eid_filter_src_eid_fops); ++ debugfs_create_file("dest_eid", 0600, eid_dir, mctp_usb, &mctp_debugfs_eid_filter_dest_eid_fops); ++ debugfs_create_file("msg_type", 0600, eid_dir, mctp_usb, &mctp_debugfs_eid_filter_msg_type_fops); ++ } ++} ++EXPORT_SYMBOL_GPL(mctp_usb_error_inject_init); ++ ++/* Cleanup error injection - called from main driver disconnect */ ++void mctp_usb_error_inject_cleanup(struct mctp_usb *mctp_usb) ++{ ++ debugfs_remove_recursive(mctp_usb->debugfs_dir); ++ mctp_usb->debugfs_dir = NULL; ++} ++EXPORT_SYMBOL_GPL(mctp_usb_error_inject_cleanup); ++ ++/* Module init for debugfs root */ ++int __init mctp_usb_error_inject_module_init(void) ++{ ++ /* Create debugfs root directory */ ++ mctp_usb_debugfs_root = debugfs_create_dir("mctp_usb", NULL); ++ if (IS_ERR_OR_NULL(mctp_usb_debugfs_root)) { ++ pr_warn("MCTP USB: Failed to create debugfs root, error injection disabled\n"); ++ mctp_usb_debugfs_root = NULL; ++ return -ENODEV; ++ } ++ ++ return 0; ++} ++ ++/* Module exit for debugfs root ++ * Note: Not marked __exit because it's called from __init error path in mctp-usb.c ++ */ ++void mctp_usb_error_inject_module_exit(void) ++{ ++ debugfs_remove_recursive(mctp_usb_debugfs_root); ++ mctp_usb_debugfs_root = NULL; ++} ++ +diff --git a/drivers/net/mctp/mctp-usb-error-inject.h b/drivers/net/mctp/mctp-usb-error-inject.h +new file mode 100644 +index 000000000..bc77fb8eb +--- /dev/null ++++ b/drivers/net/mctp/mctp-usb-error-inject.h +@@ -0,0 +1,89 @@ ++/* SPDX-License-Identifier: GPL-2.0+ */ ++/* ++ * mctp-usb-error-inject.h - Error injection infrastructure for MCTP USB ++ * ++ * Copyright (c) 2024 NVIDIA CORPORATION. All rights reserved. ++ */ ++ ++#ifndef _MCTP_USB_ERROR_INJECT_H ++#define _MCTP_USB_ERROR_INJECT_H ++ ++#include ++#include ++#include ++#include ++#include ++ ++struct mctp_usb; ++ ++/* Error injection modes */ ++enum mctp_error_inject_mode { ++ MCTP_ERR_MODE_ALWAYS, ++ MCTP_ERR_MODE_RANDOM, ++ MCTP_ERR_MODE_COUNT ++}; ++ ++/* Per-interface error injection state */ ++struct mctp_error_inject { ++ bool enable_tx; /* Separate enable for TX path */ ++ bool enable_rx; /* Separate enable for RX path */ ++ enum mctp_error_inject_mode mode; ++ ++ /* TX injection - separate sync and async */ ++ int urb_tx_sync_error_code; /* Sync error (URB submission) */ ++ int urb_tx_async_error_code; /* Async error (URB completion) */ ++ u32 urb_tx_error_rate; /* Percentage (0-100) or count */ ++ u32 urb_tx_sync_inject_count; /* Counter for sync count mode */ ++ u32 urb_tx_async_inject_count;/* Counter for async count mode */ ++ u32 urb_tx_sync_errors_injected; ++ u32 urb_tx_async_errors_injected; ++ ++ /* RX injection */ ++ int urb_rx_error_code; ++ u32 urb_rx_error_rate; ++ u32 urb_rx_inject_count; ++ u32 urb_rx_errors_injected; ++ ++ /* Fragment injection - drop, corrupt sequence, clear SOM */ ++ bool enable_fragment_drop; /* Drop 2nd+ fragments */ ++ bool enable_seq_corrupt; /* Corrupt sequence number in middle/end fragments */ ++ bool enable_som_clear; /* Clear SOM bit in first fragment */ ++ u32 fragments_dropped; ++ u32 seq_corruptions; ++ u32 som_clears; ++ ++ /* Delay injection */ ++ u32 delay_ms; ++ ++ /* EID filtering */ ++ struct { ++ bool enabled; ++ u8 src_eid; /* 0 = any */ ++ u8 dest_eid; /* 0 = any */ ++ u8 msg_type; /* 0 = any */ ++ } eid_filter; ++ ++ /* Statistics */ ++ u64 total_packets_processed; ++ u64 total_errors_injected; ++ ++ /* RNG state */ ++ struct rnd_state rng; ++ spinlock_t lock; ++}; ++ ++/* Module init/exit functions */ ++int mctp_usb_error_inject_module_init(void); ++void mctp_usb_error_inject_module_exit(void); ++ ++/* Public API for main driver */ ++void mctp_usb_error_inject_init(struct mctp_usb *mctp_usb); ++void mctp_usb_error_inject_cleanup(struct mctp_usb *mctp_usb); ++ ++int mctp_usb_error_inject_tx_sync(struct mctp_usb *mctp_usb, struct sk_buff *skb); ++int mctp_usb_error_inject_tx_async(struct mctp_usb *mctp_usb, struct urb *urb); ++int mctp_usb_error_inject_rx(struct mctp_usb *mctp_usb, int original_status); ++int mctp_usb_error_inject_fragment(struct mctp_usb *mctp_usb, struct sk_buff *skb); ++ ++#endif /* _MCTP_USB_ERROR_INJECT_H */ ++ +diff --git a/drivers/net/mctp/mctp-usb-internal.h b/drivers/net/mctp/mctp-usb-internal.h +new file mode 100644 +index 000000000..7b385ffc3 +--- /dev/null ++++ b/drivers/net/mctp/mctp-usb-internal.h +@@ -0,0 +1,98 @@ ++/* SPDX-License-Identifier: GPL-2.0 */ ++/* ++ * MCTP USB Internal Definitions ++ * Shared between mctp-usb.c and mctp-usb-error-inject.c ++ */ ++ ++#ifndef __MCTP_USB_INTERNAL_H ++#define __MCTP_USB_INTERNAL_H ++ ++#include ++#include ++#include ++#include ++#include ++ ++#include "mctp-usb-error-inject.h" ++ ++/* Number of IN/OUT URBs to queue */ ++#define MCTP_USB_N_RX_QUEUE 8 ++#define MCTP_USB_N_TX_QUEUE 8 ++ ++/* Main device structure - one per USB interface */ ++struct mctp_usb { ++ struct usb_device *usbdev; ++ struct usb_interface *intf; ++ bool stopped; ++ ++ struct net_device *netdev; ++ ++ u8 ep_in; ++ u8 ep_out; ++ ++ struct usb_anchor rx_anchor; ++ struct usb_anchor tx_anchor; ++ /* number of urbs currently queued */ ++ atomic_t rx_qlen, tx_qlen; ++ ++ struct delayed_work rx_retry_work; ++ ++ /* TX batching support - controlled via sysfs */ ++ bool tx_batching_enabled; ++ ++ /* Error injection support */ ++ struct mctp_error_inject error_inject; ++ struct dentry *debugfs_dir; ++ ++ /* Per-EID statistics tracking - SINGLE source of truth ++ * ++ * All statistics are tracked per-endpoint-ID (EID). Two special EIDs: ++ * - EID 0: "null endpoint" - valid packets with EID=0 (unallocated endpoint) ++ * - EID 256 (MCTP_EID_UNKNOWN): errors where EID could not be determined ++ * (URB completion callbacks, allocation failures, pre-parse errors) ++ */ ++ struct { ++ DECLARE_BITMAP(active, 257); /* Which EIDs have activity */ ++ struct mctp_usb_eid_stats { ++ /* RX stats */ ++ u64 rx_drop_no_memory; ++ u64 rx_drop_urb_error; ++ u64 rx_drop_invalid_len; ++ u64 rx_drop_parse_error; ++ u64 rx_drop_fragment_error; ++ /* TX stats - Valid USB error codes only */ ++ u64 tx_drop_no_memory; /* -ENOMEM */ ++ u64 tx_drop_urb_error; /* Generic/unknown errors */ ++ u64 tx_drop_ebusy; /* -EBUSY: URB already active */ ++ u64 tx_drop_enodev; /* -ENODEV: Device/bus doesn't exist */ ++ u64 tx_drop_enoent; /* -ENOENT: Interface/endpoint doesn't exist */ ++ u64 tx_drop_enxio; /* -ENXIO: Host controller doesn't support URB type */ ++ u64 tx_drop_einval; /* -EINVAL: Invalid transfer parameters */ ++ u64 tx_drop_exdev; /* -EXDEV: ISO frames expired */ ++ u64 tx_drop_efbig; /* -EFBIG: Too many ISO frames */ ++ u64 tx_drop_epipe; /* -EPIPE: Pipe type mismatch */ ++ u64 tx_drop_emsgsize; /* -EMSGSIZE: Transfer length invalid */ ++ u64 tx_drop_enospc; /* -ENOSPC: Bandwidth overcommit */ ++ u64 tx_drop_eshutdown; /* -ESHUTDOWN: Device/HC disabled */ ++ u64 tx_drop_eperm; /* -EPERM: URB rejected */ ++ u64 tx_drop_ehostunreach; /* -EHOSTUNREACH: Device suspended */ ++ u64 tx_drop_enoexec; /* -ENOEXEC: Control URB missing Setup packet */ ++ u64 tx_drop_queue_full; /* Queue full condition */ ++ u64 tx_requeued; ++ u64 rx_requeued; ++ u64 rx_urb_submitted; /* RX URBs submitted */ ++ u64 tx_urb_submitted; /* TX URBs submitted */ ++ } eid[257]; ++ } eid_stats; ++}; ++ ++/* Structure to track batched packets in URB context */ ++struct mctp_usb_batch_ctx { ++ struct net_device *netdev; ++ struct sk_buff_head skbs; ++ unsigned int num_packets; ++}; ++ ++#endif /* __MCTP_USB_INTERNAL_H */ ++ ++ +diff --git a/drivers/net/mctp/mctp-usb.c b/drivers/net/mctp/mctp-usb.c +new file mode 100644 +index 000000000..b9bca7067 +--- /dev/null ++++ b/drivers/net/mctp/mctp-usb.c +@@ -0,0 +1,1307 @@ ++// SPDX-License-Identifier: GPL-2.0 ++/* ++ * mctp-usb.c - MCTP-over-USB (DMTF DSP0283) transport binding driver. ++ * ++ * DSP0283 is available at: ++ * https://www.dmtf.org/sites/default/files/standards/documents/DSP0283_1.0.1.pdf ++ * ++ * Copyright (C) 2024-2025 Code Construct Pty Ltd ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++ ++#include ++#include ++#include ++ ++#include ++ ++#include "mctp-usb-internal.h" ++#include "mctp-stats.h" ++#include ++ ++/* number of IN/OUT urbs to queue */ ++const unsigned int n_rx_queue = MCTP_USB_N_RX_QUEUE; ++const unsigned int n_tx_queue = MCTP_USB_N_TX_QUEUE; ++ ++/** ++ * mctp_usb_handle_tx_urb_status - Handle TX URB completion status ++ * @mctp_usb: MCTP USB device ++ * @netdev: Network device ++ * @status: URB completion status code ++ * @num_packets: Number of packets in the batch ++ * @actual_length: Actual transfer length (for success case tracing) ++ * ++ * Processes TX URB completion status and updates statistics accordingly. ++ * All async URB errors are tracked under MCTP_EID_UNKNOWN since batch ++ * completion cannot determine individual packet EIDs. ++ */ ++static void mctp_usb_handle_tx_urb_status(struct mctp_usb *mctp_usb, ++ struct net_device *netdev, ++ int status, ++ unsigned int num_packets, ++ unsigned int actual_length) ++{ ++ switch (status) { ++ /* Valid USB error codes only */ ++ case -EBUSY: ++ netdev->stats.tx_dropped += num_packets; ++ mctp_usb->eid_stats.eid[MCTP_EID_UNKNOWN].tx_drop_ebusy += num_packets; ++ set_bit(MCTP_EID_UNKNOWN, mctp_usb->eid_stats.active); ++ break; ++ case -ENODEV: ++ netdev->stats.tx_dropped += num_packets; ++ mctp_usb->eid_stats.eid[MCTP_EID_UNKNOWN].tx_drop_enodev += num_packets; ++ set_bit(MCTP_EID_UNKNOWN, mctp_usb->eid_stats.active); ++ break; ++ case -ENOENT: ++ netdev->stats.tx_dropped += num_packets; ++ mctp_usb->eid_stats.eid[MCTP_EID_UNKNOWN].tx_drop_enoent += num_packets; ++ set_bit(MCTP_EID_UNKNOWN, mctp_usb->eid_stats.active); ++ break; ++ case -ENXIO: ++ netdev->stats.tx_dropped += num_packets; ++ mctp_usb->eid_stats.eid[MCTP_EID_UNKNOWN].tx_drop_enxio += num_packets; ++ set_bit(MCTP_EID_UNKNOWN, mctp_usb->eid_stats.active); ++ break; ++ case -EINVAL: ++ netdev->stats.tx_dropped += num_packets; ++ mctp_usb->eid_stats.eid[MCTP_EID_UNKNOWN].tx_drop_einval += num_packets; ++ set_bit(MCTP_EID_UNKNOWN, mctp_usb->eid_stats.active); ++ break; ++ case -EXDEV: ++ netdev->stats.tx_dropped += num_packets; ++ mctp_usb->eid_stats.eid[MCTP_EID_UNKNOWN].tx_drop_exdev += num_packets; ++ set_bit(MCTP_EID_UNKNOWN, mctp_usb->eid_stats.active); ++ break; ++ case -EFBIG: ++ netdev->stats.tx_dropped += num_packets; ++ mctp_usb->eid_stats.eid[MCTP_EID_UNKNOWN].tx_drop_efbig += num_packets; ++ set_bit(MCTP_EID_UNKNOWN, mctp_usb->eid_stats.active); ++ break; ++ case -EPIPE: ++ netdev->stats.tx_dropped += num_packets; ++ mctp_usb->eid_stats.eid[MCTP_EID_UNKNOWN].tx_drop_epipe += num_packets; ++ set_bit(MCTP_EID_UNKNOWN, mctp_usb->eid_stats.active); ++ break; ++ case -EMSGSIZE: ++ netdev->stats.tx_dropped += num_packets; ++ mctp_usb->eid_stats.eid[MCTP_EID_UNKNOWN].tx_drop_emsgsize += num_packets; ++ set_bit(MCTP_EID_UNKNOWN, mctp_usb->eid_stats.active); ++ break; ++ case -ENOSPC: ++ netdev->stats.tx_dropped += num_packets; ++ mctp_usb->eid_stats.eid[MCTP_EID_UNKNOWN].tx_drop_enospc += num_packets; ++ set_bit(MCTP_EID_UNKNOWN, mctp_usb->eid_stats.active); ++ break; ++ case -ESHUTDOWN: ++ netdev->stats.tx_dropped += num_packets; ++ mctp_usb->eid_stats.eid[MCTP_EID_UNKNOWN].tx_drop_eshutdown += num_packets; ++ set_bit(MCTP_EID_UNKNOWN, mctp_usb->eid_stats.active); ++ break; ++ case -EPERM: ++ netdev->stats.tx_dropped += num_packets; ++ mctp_usb->eid_stats.eid[MCTP_EID_UNKNOWN].tx_drop_eperm += num_packets; ++ set_bit(MCTP_EID_UNKNOWN, mctp_usb->eid_stats.active); ++ break; ++ case -EHOSTUNREACH: ++ netdev->stats.tx_dropped += num_packets; ++ mctp_usb->eid_stats.eid[MCTP_EID_UNKNOWN].tx_drop_ehostunreach += num_packets; ++ set_bit(MCTP_EID_UNKNOWN, mctp_usb->eid_stats.active); ++ break; ++ case -ENOEXEC: ++ netdev->stats.tx_dropped += num_packets; ++ mctp_usb->eid_stats.eid[MCTP_EID_UNKNOWN].tx_drop_enoexec += num_packets; ++ set_bit(MCTP_EID_UNKNOWN, mctp_usb->eid_stats.active); ++ break; ++ case 0: ++ netdev->stats.tx_packets += num_packets; ++ /* tx_bytes already updated per packet during batching */ ++ trace_mctp_transport_tx("usb", netdev, 0, actual_length); ++ break; ++ default: ++ if (net_ratelimit()) { ++ netdev_warn(netdev, "unexpected tx urb status: %d\n", ++ status); ++ } ++ netdev->stats.tx_dropped += num_packets; ++ mctp_usb->eid_stats.eid[MCTP_EID_UNKNOWN].tx_drop_urb_error += num_packets; ++ set_bit(MCTP_EID_UNKNOWN, mctp_usb->eid_stats.active); ++ trace_mctp_transport_error("usb", netdev, "tx_urb_unexpected", status); ++ } ++} ++ ++/** ++ * mctp_usb_handle_tx_sync_error - Handle synchronous TX error statistics ++ * @mctp_usb: MCTP USB device ++ * @dest_eid: Destination EID from MCTP header ++ * @error_code: Error code from URB submission ++ * ++ * Maps synchronous TX errors (URB submission failures) to per-EID statistics. ++ * Unlike async URB completion errors, these can be attributed to a specific ++ * destination EID since the packet header is still available. ++ * Only handles valid USB error codes per USB subsystem specification. ++ */ ++static void mctp_usb_handle_tx_sync_error(struct mctp_usb *mctp_usb, ++ u8 dest_eid, ++ int error_code) ++{ ++ switch (error_code) { ++ /* Valid USB error codes only */ ++ case -ENOMEM: ++ MCTP_STAT_INC(mctp_usb, dest_eid, tx_drop_no_memory); ++ break; ++ case -EBUSY: ++ MCTP_STAT_INC(mctp_usb, dest_eid, tx_drop_ebusy); ++ break; ++ case -ENODEV: ++ MCTP_STAT_INC(mctp_usb, dest_eid, tx_drop_enodev); ++ break; ++ case -ENOENT: ++ MCTP_STAT_INC(mctp_usb, dest_eid, tx_drop_enoent); ++ break; ++ case -ENXIO: ++ MCTP_STAT_INC(mctp_usb, dest_eid, tx_drop_enxio); ++ break; ++ case -EINVAL: ++ MCTP_STAT_INC(mctp_usb, dest_eid, tx_drop_einval); ++ break; ++ case -EXDEV: ++ MCTP_STAT_INC(mctp_usb, dest_eid, tx_drop_exdev); ++ break; ++ case -EFBIG: ++ MCTP_STAT_INC(mctp_usb, dest_eid, tx_drop_efbig); ++ break; ++ case -EPIPE: ++ MCTP_STAT_INC(mctp_usb, dest_eid, tx_drop_epipe); ++ break; ++ case -EMSGSIZE: ++ MCTP_STAT_INC(mctp_usb, dest_eid, tx_drop_emsgsize); ++ break; ++ case -ENOSPC: ++ MCTP_STAT_INC(mctp_usb, dest_eid, tx_drop_enospc); ++ break; ++ case -ESHUTDOWN: ++ MCTP_STAT_INC(mctp_usb, dest_eid, tx_drop_eshutdown); ++ break; ++ case -EPERM: ++ MCTP_STAT_INC(mctp_usb, dest_eid, tx_drop_eperm); ++ break; ++ case -EHOSTUNREACH: ++ MCTP_STAT_INC(mctp_usb, dest_eid, tx_drop_ehostunreach); ++ break; ++ case -ENOEXEC: ++ MCTP_STAT_INC(mctp_usb, dest_eid, tx_drop_enoexec); ++ break; ++ default: ++ MCTP_STAT_INC(mctp_usb, dest_eid, tx_drop_urb_error); ++ break; ++ } ++} ++ ++static void mctp_usb_out_complete(struct urb *urb) ++{ ++ struct mctp_usb_batch_ctx *ctx = urb->context; ++ struct net_device *netdev = ctx->netdev; ++ struct mctp_usb *mctp_usb = netdev_priv(netdev); ++ struct sk_buff *skb; ++ int status; ++ ++ usb_unanchor_urb(urb); ++ if (atomic_dec_return(&mctp_usb->tx_qlen) < n_tx_queue) ++ netif_wake_queue(netdev); ++ ++ status = urb->status; ++ ++ /* ERROR INJECTION POINT: TX URB completion (asynchronous error) ++ * Note: Injection happens at URB level (may affect multiple batched packets). ++ * Pass urb so async path can apply EID filter from first packet. ++ */ ++ status = mctp_usb_error_inject_tx_async(mctp_usb, urb); ++ ++ /* Handle TX URB status and update statistics */ ++ mctp_usb_handle_tx_urb_status(mctp_usb, netdev, status, ++ ctx->num_packets, urb->actual_length); ++ ++ if (status != 0) { ++ /* Report error to socket error queue for batched URB. ++ */ ++ skb = skb_dequeue(&ctx->skbs); ++ if (skb) { ++ if (skb->len >= sizeof(struct mctp_usb_hdr)) { ++ struct mctp_usb_hdr *hdr; ++ struct sk_buff *pkt_skb; ++ struct mctp_sk_key *key = NULL; ++ struct sock *sk; ++ unsigned int pkt_total_len; ++ ++ netdev_dbg(netdev, "Async TX error: Processing batched SKB len=%u\n", skb->len); ++ ++ /* Parse ONLY the first packet for error reporting */ ++ hdr = (struct mctp_usb_hdr *)skb->data; ++ pkt_total_len = hdr->len; ++ ++ if (pkt_total_len <= skb->len) { ++ /* Create temp SKB with first packet only */ ++ pkt_skb = alloc_skb(pkt_total_len, GFP_ATOMIC); ++ if (pkt_skb) { ++ skb_put_data(pkt_skb, skb->data, pkt_total_len); ++ pkt_skb->dev = netdev; ++ ++ /* Preserve socket for error reporting (response packets) */ ++ if (skb->sk) ++ skb_set_owner_w(pkt_skb, skb->sk); ++ ++ /* Remove USB header to expose MCTP header */ ++ if (skb_pull(pkt_skb, sizeof(struct mctp_usb_hdr))) { ++ skb_reset_network_header(pkt_skb); ++ ++ /* Look up socket and report ONE error for entire batch */ ++ sk = mctp_lookup_sock_for_error(pkt_skb, netdev, NULL, &key); ++ ++ if (sk) { ++ netdev_dbg(netdev, "Async TX error: Reporting error %d for entire batch (key=%p)\n", ++ status, key); ++ mctp_queue_error(sk, pkt_skb, -status, netdev, ++ MCTP_DIR_TX, MCTP_PHYS_BINDING_USB, key); ++ sock_put(sk); ++ } else { ++ netdev_dbg(netdev, "Async TX error: Not reported (no TX key or error queue disabled)\n"); ++ } ++ } ++ kfree_skb(pkt_skb); ++ } ++ } ++ } ++ /* Always free the dequeued SKB, regardless of whether we could report error */ ++ kfree_skb(skb); ++ } ++ } ++ ++ /* Free all batched skbs */ ++ while ((skb = skb_dequeue(&ctx->skbs)) != NULL) { ++ if (status == 0) ++ consume_skb(skb); ++ else ++ kfree_skb(skb); ++ } ++ ++ kfree(ctx); ++ usb_free_urb(urb); ++} ++/* Fast path: send a single packet without batching. ++ * This avoids lock overhead when batching is disabled. ++ */ ++static netdev_tx_t mctp_usb_send_single(struct mctp_usb *mctp_usb, ++ struct sk_buff *skb) ++{ ++ struct net_device *netdev = mctp_usb->netdev; ++ struct mctp_usb_batch_ctx *ctx; ++ struct urb *urb; ++ u8 *buf; ++ unsigned int pkt_len = skb->len; ++ int rc; ++ ++ /* Queue check is done in mctp_usb_start_xmit() before modifying SKB */ ++ ++ /* Allocate minimal context for single packet */ ++ ctx = kzalloc(sizeof(*ctx), GFP_ATOMIC); ++ if (!ctx) ++ goto err_drop; ++ ++ skb_queue_head_init(&ctx->skbs); ++ ctx->netdev = netdev; ++ ctx->num_packets = 1; ++ ++ /* Allocate URB and buffer */ ++ urb = usb_alloc_urb(0, GFP_ATOMIC); ++ if (!urb) { ++ trace_mctp_transport_error("usb", netdev, "tx_urb_alloc_failed", 0); ++ goto err_free_ctx; ++ } ++ ++ buf = kmalloc(pkt_len, GFP_ATOMIC); ++ if (!buf) { ++ trace_mctp_transport_error("usb", netdev, "tx_buf_alloc_failed", 0); ++ goto err_free_urb; ++ } ++ ++ /* Copy packet data */ ++ skb_copy_bits(skb, 0, buf, pkt_len); ++ skb_queue_tail(&ctx->skbs, skb); ++ ++ netdev->stats.tx_bytes += pkt_len - sizeof(struct mctp_usb_hdr); ++ ++ /* ERROR INJECTION POINT: TX URB submission (synchronous error) */ ++ rc = mctp_usb_error_inject_tx_sync(mctp_usb, skb); ++ if (rc) { ++ /* Simulate URB submission failure */ ++ netdev_info(netdev, "TX single packet: URB submit SIMULATED failure (error injection, code %d)\n", rc); ++ goto err_free_urb; ++ } ++ ++ /* Submit URB */ ++ usb_fill_bulk_urb(urb, mctp_usb->usbdev, ++ usb_sndbulkpipe(mctp_usb->usbdev, mctp_usb->ep_out), ++ buf, pkt_len, mctp_usb_out_complete, ctx); ++ urb->transfer_flags |= URB_FREE_BUFFER; ++ ++ usb_anchor_urb(urb, &mctp_usb->tx_anchor); ++ atomic_inc(&mctp_usb->tx_qlen); ++ if (atomic_read(&mctp_usb->tx_qlen) >= n_tx_queue) ++ netif_stop_queue(netdev); ++ rc = usb_submit_urb(urb, GFP_ATOMIC); ++ if (rc) { ++ usb_unanchor_urb(urb); ++ if (atomic_dec_return(&mctp_usb->tx_qlen) < n_tx_queue) ++ netif_wake_queue(netdev); ++ trace_mctp_transport_error("usb", netdev, "tx_urb_submit_failed", rc); ++ goto err_free_urb; ++ } ++ ++ return NETDEV_TX_OK; ++ ++err_free_urb: ++ usb_free_urb(urb); ++err_free_ctx: ++ skb_dequeue(&ctx->skbs); ++ kfree(ctx); ++err_drop: ++ /* Report synchronous TX error if URB submission failed. ++ * SKB still has USB header, remove it for socket lookup. ++ */ ++ if (rc != 0 && skb->len >= sizeof(struct mctp_usb_hdr)) { ++ struct sock *sk; ++ struct mctp_sk_key *key = NULL; ++ ++ skb_pull(skb, sizeof(struct mctp_usb_hdr)); ++ skb_reset_network_header(skb); ++ ++ sk = mctp_lookup_sock_for_error(skb, netdev, NULL, &key); ++ if (sk) { ++ netdev_dbg(netdev, "TX single: Reporting sync error (code=%d)\n", -rc); ++ mctp_queue_error(sk, skb, -rc, netdev, ++ MCTP_DIR_TX, MCTP_PHYS_BINDING_USB, key); ++ sock_put(sk); ++ } ++ } ++ ++ netdev->stats.tx_dropped++; ++ ++ /* Track per-EID stats (USB header already pulled above, MCTP header accessible) */ ++ { ++ struct mctp_hdr *mh = mctp_hdr(skb); ++ u8 dest_eid = mh->dest; ++ ++ mctp_usb_handle_tx_sync_error(mctp_usb, dest_eid, rc); ++ } ++ netdev_dbg(netdev, "MCTP USB: TX single dropped, error=%d\n", rc); ++ trace_mctp_transport_error("usb", netdev, "tx_single_drop", rc); ++ kfree_skb(skb); ++ return NETDEV_TX_OK; ++} ++ ++/* Callback invoked by route.c to fill in USB transport header during batching. ++ * This is called once per packet in the batch, with the exact location and size. ++ */ ++static void mctp_usb_fill_batch_hdr(void *hdr, unsigned int pkt_len) ++{ ++ struct mctp_usb_hdr *usb_hdr = (struct mctp_usb_hdr *)hdr; ++ ++ usb_hdr->id = cpu_to_be16(MCTP_USB_DMTF_ID); ++ usb_hdr->rsvd = 0; ++ usb_hdr->len = pkt_len; ++} ++ ++static const struct mctp_netdev_ops mctp_usb_ops = { ++ .fill_batch_hdr = mctp_usb_fill_batch_hdr, ++}; ++ ++/* Send a pre-batched SKB (from route.c) with multiple MCTP packets. ++ * The SKB data has headers pre-filled by route.c via our callback. ++ */ ++static netdev_tx_t mctp_usb_send_batch(struct mctp_usb *mctp_usb, ++ struct sk_buff *skb) ++{ ++ struct net_device *netdev = mctp_usb->netdev; ++ struct mctp_usb_batch_ctx *ctx; ++ struct urb *urb; ++ int rc; ++ ++ if (atomic_read(&mctp_usb->tx_qlen) >= n_tx_queue) { ++ netif_stop_queue(netdev); ++ return NETDEV_TX_BUSY; ++ } ++ ++ /* Restore the correct protocol now that we're committed to sending */ ++ skb->protocol = htons(ETH_P_MCTP); ++ ++ /* Allocate context */ ++ ctx = kzalloc(sizeof(*ctx), GFP_ATOMIC); ++ if (!ctx) ++ goto err_drop; ++ ++ skb_queue_head_init(&ctx->skbs); ++ ctx->netdev = netdev; ++ ctx->num_packets = 1; /* We'll count on completion */ ++ ++ /* Allocate URB */ ++ urb = usb_alloc_urb(0, GFP_ATOMIC); ++ if (!urb) ++ goto err_free_ctx; ++ ++ skb_queue_tail(&ctx->skbs, skb); ++ ++ netdev_dbg(netdev, "Sending batched SKB: %u bytes\n", skb->len); ++ ++ /* ERROR INJECTION POINT: TX URB submission (synchronous error) ++ * Added support for batched packets ++ */ ++ rc = mctp_usb_error_inject_tx_sync(mctp_usb, skb); ++ if (rc) { ++ goto err_free_urb; ++ } ++ ++ /* Submit URB with pre-filled SKB data directly - headers already done by route.c! */ ++ usb_fill_bulk_urb(urb, mctp_usb->usbdev, ++ usb_sndbulkpipe(mctp_usb->usbdev, mctp_usb->ep_out), ++ skb->data, skb->len, mctp_usb_out_complete, ctx); ++ ++ usb_anchor_urb(urb, &mctp_usb->tx_anchor); ++ atomic_inc(&mctp_usb->tx_qlen); ++ if (atomic_read(&mctp_usb->tx_qlen) >= n_tx_queue) ++ netif_stop_queue(netdev); ++ rc = usb_submit_urb(urb, GFP_ATOMIC); ++ if (rc) { ++ usb_unanchor_urb(urb); ++ if (atomic_dec_return(&mctp_usb->tx_qlen) < n_tx_queue) ++ netif_wake_queue(netdev); ++ trace_mctp_transport_error("usb", netdev, "tx_urb_submit_failed", rc); ++ goto err_free_urb; ++ } ++ ++ return NETDEV_TX_OK; ++ ++err_free_urb: ++ usb_free_urb(urb); ++err_free_ctx: ++ skb_dequeue(&ctx->skbs); ++ kfree(ctx); ++err_drop: ++ /* TX Synchronous Error Reporting for batched SKB: ++ */ ++ if (rc != 0) { ++ struct mctp_usb_hdr *hdr; ++ struct sk_buff *pkt_skb; ++ struct sock *sk; ++ struct mctp_sk_key *key = NULL; ++ unsigned int pkt_total_len; ++ ++ netdev_info(netdev, "TX batch DROPPED (URB submit failed, error %d)\n", rc); ++ ++ /* Parse ONLY the first packet for error reporting */ ++ if (skb->len >= sizeof(struct mctp_usb_hdr)) { ++ hdr = (struct mctp_usb_hdr *)skb->data; ++ ++ /* Validate USB header */ ++ if (be16_to_cpu(hdr->id) == MCTP_USB_DMTF_ID) { ++ pkt_total_len = hdr->len; ++ ++ if (pkt_total_len <= skb->len) { ++ /* Create temp SKB for first packet only */ ++ pkt_skb = alloc_skb(pkt_total_len, GFP_ATOMIC); ++ if (pkt_skb) { ++ skb_put_data(pkt_skb, skb->data, pkt_total_len); ++ pkt_skb->dev = netdev; ++ ++ /* Preserve socket for error reporting (response packets) */ ++ if (skb->sk) ++ skb_set_owner_w(pkt_skb, skb->sk); ++ ++ /* Remove USB header to expose MCTP header */ ++ if (skb_pull(pkt_skb, sizeof(struct mctp_usb_hdr))) { ++ skb_reset_network_header(pkt_skb); ++ ++ /* Look up socket and report ONE error for entire batch */ ++ sk = mctp_lookup_sock_for_error(pkt_skb, netdev, NULL, &key); ++ if (sk) { ++ netdev_dbg(netdev, "TX batch sync error: Reporting for entire batch\n"); ++ mctp_queue_error(sk, pkt_skb, -rc, netdev, ++ MCTP_DIR_TX, MCTP_PHYS_BINDING_USB, key); ++ sock_put(sk); ++ } ++ } ++ kfree_skb(pkt_skb); ++ } ++ } ++ } ++ } ++ } ++ ++ /* Batch TX errors - tracked as UNKNOWN (can't determine individual EIDs in batch) */ ++ netdev->stats.tx_dropped++; ++ mctp_usb_handle_tx_sync_error(mctp_usb, MCTP_EID_UNKNOWN, rc); ++ netdev_dbg(netdev, "MCTP USB: TX batch dropped, error=%d\n", rc); ++ trace_mctp_transport_error("usb", netdev, "tx_batch_drop", rc); ++ kfree_skb(skb); ++ return NETDEV_TX_OK; ++} ++ ++static netdev_tx_t mctp_usb_start_xmit(struct sk_buff *skb, ++ struct net_device *dev) ++{ ++ struct mctp_usb *mctp_usb = netdev_priv(dev); ++ struct mctp_usb_hdr *hdr; ++ unsigned int plen, pkt_len; ++ int rc; ++ ++ /* Detect batched SKBs: route.c sets a special protocol marker ++ * (ETH_P_MCTP | 0x8000) to indicate a pre-batched multi-packet SKB. ++ * This avoids relying on skb->cb which may not be initialized. ++ */ ++ if (skb->protocol == htons(ETH_P_MCTP | 0x8000)) { ++ /* This is a batched SKB - don't clear protocol until after queue check! */ ++ netdev_dbg(dev, "Detected batched SKB: len=%u\n", skb->len); ++ return mctp_usb_send_batch(mctp_usb, skb); ++ } ++ ++ /* Single packet path */ ++ plen = skb->len; ++ pkt_len = plen + sizeof(*hdr); ++ ++ /* Single packet larger than max transfer size - can't send */ ++ if (pkt_len > MCTP_USB_XFER_SIZE) { ++ /* Extract EID for tracking before dropping */ ++ struct mctp_hdr *mh = mctp_hdr(skb); ++ MCTP_STAT_INC(mctp_usb, mh->dest, tx_drop_emsgsize); ++ trace_mctp_transport_error("usb", dev, "tx_len_exceeds_max", pkt_len); ++ goto err_drop; ++ } ++ ++ /* Check queue BEFORE modifying the SKB, so retries don't double-add headers */ ++ if (atomic_read(&mctp_usb->tx_qlen) >= n_tx_queue) { ++ netif_stop_queue(dev); ++ return NETDEV_TX_BUSY; ++ } ++ ++ rc = skb_cow_head(skb, sizeof(*hdr)); ++ if (rc) { ++ trace_mctp_transport_error("usb", dev, "tx_skb_head_error", rc); ++ goto err_drop; ++ } ++ ++ hdr = skb_push(skb, sizeof(*hdr)); ++ if (!hdr) { ++ trace_mctp_transport_error("usb", dev, "tx_skb_push_error", 0); ++ goto err_drop; ++ } ++ ++ hdr->id = cpu_to_be16(MCTP_USB_DMTF_ID); ++ hdr->rsvd = 0; ++ hdr->len = pkt_len; ++ ++ /* Send single packet */ ++ return mctp_usb_send_single(mctp_usb, skb); ++ ++err_drop: ++ dev->stats.tx_dropped++; ++ trace_mctp_transport_error("usb", dev, "tx_dropped_start_xmit", 0); ++ kfree_skb(skb); ++ return NETDEV_TX_OK; ++} ++ ++static void mctp_usb_in_complete(struct urb *urb); ++ ++/* If we fail to queue an in urb atomically (either due to skb allocation or ++ * urb submission), we will schedule a rx queue in nonatomic context ++ * after a delay, specified in jiffies ++ */ ++static const unsigned long RX_RETRY_DELAY = HZ / 4; ++ ++static int mctp_usb_rx_queue(struct mctp_usb *mctp_usb, struct urb *urb, ++ gfp_t gfp) ++{ ++ struct sk_buff *skb; ++ int rc; ++ ++ /* no point allocating if the queue is going to be rejected */ ++ if (READ_ONCE(mctp_usb->stopped)) ++ return 0; ++ ++ skb = __netdev_alloc_skb(mctp_usb->netdev, MCTP_USB_XFER_SIZE, gfp); ++ if (!skb) { ++ trace_mctp_transport_error("usb", mctp_usb->netdev, "rx_alloc_skb_failed", 0); ++ return -ENOMEM; ++ } ++ ++ usb_fill_bulk_urb(urb, mctp_usb->usbdev, ++ usb_rcvbulkpipe(mctp_usb->usbdev, mctp_usb->ep_in), ++ skb->data, MCTP_USB_XFER_SIZE, ++ mctp_usb_in_complete, skb); ++ ++ atomic_inc(&mctp_usb->rx_qlen); ++ rc = usb_submit_urb(urb, gfp); ++ if (rc) { ++ netdev_dbg(mctp_usb->netdev, "rx urb submit failure: %d\n", rc); ++ trace_mctp_transport_error("usb", mctp_usb->netdev, "rx_urb_submit_failed", rc); ++ atomic_dec(&mctp_usb->rx_qlen); ++ kfree_skb(skb); ++ return rc; ++ } ++ ++ return 0; ++} ++ ++static void mctp_usb_in_complete(struct urb *urb) ++{ ++ struct sk_buff *skb = urb->context; ++ struct net_device *netdev = skb->dev; ++ struct mctp_usb *mctp_usb = netdev_priv(netdev); ++ struct mctp_skb_cb *cb; ++ unsigned int len; ++ int status, rc; ++ ++ status = urb->status; ++ atomic_dec(&mctp_usb->rx_qlen); ++ ++ /* ERROR INJECTION POINT: RX URB completion error */ ++ status = mctp_usb_error_inject_rx(mctp_usb, status); ++ ++ switch (status) { ++ case -ENOENT: ++ case -ESHUTDOWN: ++ if (net_ratelimit()) { ++ netdev_warn(netdev, ++ "rx urb shutdown/error status: %d\n", ++ status); ++ } ++ usb_unanchor_urb(urb); ++ usb_free_urb(urb); ++ if (mctp_usb->error_inject.enable_rx) { ++ netdev_info(netdev, ++ "RX packet DROPPED (error %d) - error injection is ACTIVE\n", ++ status); ++ } else { ++ netdev_dbg(netdev, ++ "RX packet DROPPED (error %d) - expected shutdown/reset\n", ++ status); ++ } ++ netdev->stats.rx_dropped++; ++ MCTP_STAT_INC(mctp_usb, MCTP_EID_UNKNOWN, rx_drop_urb_error); ++ trace_mctp_transport_error("usb", netdev, "rx_urb_error_shutdown", status); ++ kfree_skb(skb); ++ return; ++ case 0: ++ break; ++ default: ++ if (net_ratelimit()) { ++ netdev_warn(netdev, ++ "unexpected rx urb status: %d, requeuing\n", ++ status); ++ } ++ if (mctp_usb->error_inject.enable_rx) { ++ netdev_info(netdev, ++ "RX packet DROPPED (error %d) - error injection is ACTIVE\n", ++ status); ++ } else { ++ netdev_info(netdev, ++ "RX packet DROPPED (error %d) - real error\n", ++ status); ++ } ++ netdev->stats.rx_errors++; ++ netdev->stats.rx_dropped++; ++ MCTP_STAT_INC(mctp_usb, MCTP_EID_UNKNOWN, rx_drop_urb_error); ++ trace_mctp_transport_error("usb", netdev, "rx_urb_error_unexpected", status); ++ kfree_skb(skb); ++ goto requeue; ++ } ++ ++ len = urb->actual_length; ++ __skb_put(skb, len); ++ ++ while (skb) { ++ struct sk_buff *skb2 = NULL; ++ struct mctp_usb_hdr *hdr; ++ u8 pkt_len; /* length of MCTP packet, no USB header */ ++ ++ hdr = skb_pull_data(skb, sizeof(*hdr)); ++ if (!hdr) ++ break; ++ ++ if (be16_to_cpu(hdr->id) != MCTP_USB_DMTF_ID) { ++ netdev_dbg(netdev, "rx: invalid id %04x\n", ++ be16_to_cpu(hdr->id)); ++ netdev->stats.rx_errors++; ++ netdev->stats.rx_dropped++; ++ /* Try to extract EID for per-EID tracking (USB hdr was pulled, MCTP hdr should be accessible) */ ++ if (skb->len >= sizeof(struct mctp_hdr)) { ++ struct mctp_hdr *mh = (struct mctp_hdr *)skb->data; ++ MCTP_STAT_INC(mctp_usb, mh->src, rx_drop_parse_error); ++ } else { ++ MCTP_STAT_INC(mctp_usb, MCTP_EID_UNKNOWN, rx_drop_parse_error); ++ } ++ trace_mctp_transport_error("usb", netdev, "rx_invalid_id", be16_to_cpu(hdr->id)); ++ break; ++ } ++ ++ if (hdr->len < ++ sizeof(struct mctp_hdr) + sizeof(struct mctp_usb_hdr)) { ++ netdev_dbg(netdev, "rx: short packet (hdr) %d\n", ++ hdr->len); ++ netdev->stats.rx_dropped++; ++ MCTP_STAT_INC(mctp_usb, MCTP_EID_UNKNOWN, rx_drop_invalid_len); ++ trace_mctp_transport_error("usb", netdev, "rx_short_packet_hdr", hdr->len); ++ break; ++ } ++ ++ /* we know we have at least sizeof(struct mctp_usb_hdr) here */ ++ pkt_len = hdr->len - sizeof(struct mctp_usb_hdr); ++ if (pkt_len > skb->len) { ++ netdev_dbg(netdev, ++ "rx: short packet (xfer) %d, actual %d\n", ++ hdr->len, skb->len); ++ netdev->stats.rx_dropped++; ++ MCTP_STAT_INC(mctp_usb, MCTP_EID_UNKNOWN, rx_drop_invalid_len); ++ trace_mctp_transport_error("usb", netdev, "rx_short_packet_xfer", skb->len); ++ break; ++ } ++ ++ if (pkt_len < skb->len) { ++ /* more packets may follow - clone to a new ++ * skb to use on the next iteration ++ */ ++ skb2 = skb_clone(skb, GFP_ATOMIC); ++ if (skb2) { ++ if (!skb_pull(skb2, pkt_len)) { ++ kfree_skb(skb2); ++ skb2 = NULL; ++ } ++ } ++ skb_trim(skb, pkt_len); ++ } ++ ++ netdev->stats.rx_packets++; ++ netdev->stats.rx_bytes += skb->len; ++ ++ skb->protocol = htons(ETH_P_MCTP); ++ skb_reset_network_header(skb); ++ cb = __mctp_cb(skb); ++ cb->halen = 0; ++ ++ /* ERROR INJECTION POINT: Fragment drop/corruption ++ * At this point: ++ * - USB header has been removed ++ * - skb->data points to MCTP header ++ * - Before packet sent to network stack ++ * This is the ideal location to inject fragment errors ++ */ ++ if (mctp_usb->error_inject.enable_fragment_drop || ++ mctp_usb->error_inject.enable_seq_corrupt || ++ mctp_usb->error_inject.enable_som_clear || ++ mctp_usb->error_inject.enable_rx) { ++ int inject_action = mctp_usb_error_inject_fragment(mctp_usb, skb); ++ ++ if (inject_action == 1) { ++ struct mctp_hdr *mh = mctp_hdr(skb); ++ u8 src_eid = mh->src; ++ ++ /* Drop this fragment */ ++ netdev->stats.rx_dropped++; ++ MCTP_STAT_INC(mctp_usb, src_eid, rx_drop_fragment_error); ++ trace_mctp_transport_error("usb", netdev, "rx_drop_fragment_error", 0); ++ kfree_skb(skb); ++ skb = skb2; ++ continue; ++ } ++ /* inject_action == 0: pass through (normally or with corruption) */ ++ } ++ ++ trace_mctp_transport_rx("usb", netdev, 0, skb->len); ++ netif_rx(skb); ++ ++ skb = skb2; ++ } ++ ++ if (skb) ++ kfree_skb(skb); ++ ++requeue: ++ /* URB was automatically unanchored by USB core on completion, ++ * re-anchor before resubmit so it's tracked for shutdown. ++ */ ++ usb_anchor_urb(urb, &mctp_usb->rx_anchor); ++ rc = mctp_usb_rx_queue(mctp_usb, urb, GFP_ATOMIC); ++ if (rc) { ++ usb_unanchor_urb(urb); ++ usb_free_urb(urb); ++ schedule_delayed_work(&mctp_usb->rx_retry_work, RX_RETRY_DELAY); ++ } ++} ++ ++static int mctp_usb_rx_queue_fill(struct mctp_usb *mctp_usb) ++{ ++ int i, qlen, rc = 0; ++ ++ qlen = atomic_read(&mctp_usb->rx_qlen); ++ if (qlen < 0 || qlen >= n_rx_queue) ++ return 0; ++ ++ for (i = 0; i < n_rx_queue - qlen; i++) { ++ struct urb *urb = usb_alloc_urb(0, GFP_KERNEL); ++ ++ if (!urb) { ++ rc = -ENOMEM; ++ break; ++ } ++ ++ usb_anchor_urb(urb, &mctp_usb->rx_anchor); ++ ++ rc = mctp_usb_rx_queue(mctp_usb, urb, GFP_KERNEL); ++ if (rc) { ++ usb_unanchor_urb(urb); ++ usb_free_urb(urb); ++ break; ++ } ++ } ++ ++ return rc; ++} ++ ++static void mctp_usb_rx_retry_work(struct work_struct *work) ++{ ++ struct mctp_usb *mctp_usb = container_of(work, struct mctp_usb, ++ rx_retry_work.work); ++ int rc; ++ ++ if (READ_ONCE(mctp_usb->stopped)) ++ return; ++ ++ rc = mctp_usb_rx_queue_fill(mctp_usb); ++ if (rc) ++ schedule_delayed_work(&mctp_usb->rx_retry_work, RX_RETRY_DELAY); ++} ++ ++static int mctp_usb_open(struct net_device *dev) ++{ ++ struct mctp_usb *mctp_usb = netdev_priv(dev); ++ ++ WRITE_ONCE(mctp_usb->stopped, false); ++ ++ netif_start_queue(dev); ++ ++ return mctp_usb_rx_queue_fill(mctp_usb); ++} ++ ++static int mctp_usb_stop(struct net_device *dev) ++{ ++ struct mctp_usb *mctp_usb = netdev_priv(dev); ++ ++ netif_stop_queue(dev); ++ ++ /* prevent RX submission retry */ ++ WRITE_ONCE(mctp_usb->stopped, true); ++ ++ /* Cancel retry work before unlinking so no new URBs are submitted */ ++ cancel_delayed_work_sync(&mctp_usb->rx_retry_work); ++ ++ /* ++ * Unlink URBs asynchronously. Do not use usb_kill_anchored_urbs() here ++ * as it waits indefinitely for each URB and can block under RTNL (e.g. ++ * "ip link set mctpusbx down") long enough to trigger RCU stalls if the ++ * USB host or device is slow to complete the unlink. Completions will ++ * run later with -ENOENT/-ECONNRESET and clean up. Safe even if the ++ * device is brought up again immediately. ++ */ ++ usb_unlink_anchored_urbs(&mctp_usb->rx_anchor); ++ usb_unlink_anchored_urbs(&mctp_usb->tx_anchor); ++ ++ return 0; ++} ++ ++/* sysfs attribute for runtime control of TX batching */ ++static ssize_t tx_batching_show(struct device *dev, ++ struct device_attribute *attr, char *buf) ++{ ++ struct net_device *netdev = to_net_dev(dev); ++ struct mctp_usb *mctp_usb = netdev_priv(netdev); ++ ++ return sprintf(buf, "%d\n", mctp_usb->tx_batching_enabled ? 1 : 0); ++} ++ ++static ssize_t tx_batching_store(struct device *dev, ++ struct device_attribute *attr, const char *buf, ++ size_t count) ++{ ++ struct net_device *netdev = to_net_dev(dev); ++ struct mctp_usb *mctp_usb = netdev_priv(netdev); ++ struct mctp_dev *mdev; ++ bool enabled; ++ int rc; ++ ++ rc = kstrtobool(buf, &enabled); ++ if (rc) ++ return rc; ++ ++ mctp_usb->tx_batching_enabled = enabled; ++ ++ /* Also update the mctp_dev flag for use in route.c */ ++ rcu_read_lock(); ++ mdev = __mctp_dev_get(netdev); ++ if (mdev) { ++ mdev->tx_batching_enabled = enabled; ++ netdev_info(netdev, ++ "TX batching %s (hdr_len=%u, max_xfer=%u)\n", ++ enabled ? "enabled" : "disabled", ++ mdev->tx_batch_hdr_len, mdev->tx_batch_max_xfer); ++ mctp_dev_put( ++ mdev); /* Release reference taken by __mctp_dev_get() */ ++ } else { ++ netdev_err(netdev, "Failed to get mctp_dev!\n"); ++ } ++ rcu_read_unlock(); ++ ++ return count; ++} ++ ++static DEVICE_ATTR_RW(tx_batching); ++ ++static struct attribute *mctp_usb_attrs[] = { ++ &dev_attr_tx_batching.attr, ++ NULL, ++}; ++ ++static const struct attribute_group mctp_usb_attr_group = { ++ .attrs = mctp_usb_attrs, ++}; ++ ++/* Ethtool statistics support */ ++/* Per-EID stat descriptors with abbreviated names */ ++struct mctp_usb_eid_stat_desc { ++ const char *name; ++ size_t offset; ++}; ++ ++#define MCTP_USB_EID_STAT(abbrev, field) { \ ++ .name = abbrev, \ ++ .offset = offsetof(struct mctp_usb_eid_stats, field) \ ++} ++ ++static const struct mctp_usb_eid_stat_desc mctp_usb_eid_stat_descs[] = { ++ /* RX statistics */ ++ MCTP_USB_EID_STAT("rx_drop_no_memory", rx_drop_no_memory), ++ MCTP_USB_EID_STAT("rx_drop_urb_error", rx_drop_urb_error), ++ MCTP_USB_EID_STAT("rx_drop_invalid_len", rx_drop_invalid_len), ++ MCTP_USB_EID_STAT("rx_drop_parse_error", rx_drop_parse_error), ++ MCTP_USB_EID_STAT("rx_drop_fragment_error", rx_drop_fragment_error), ++ /* TX statistics - Valid USB error codes only */ ++ MCTP_USB_EID_STAT("tx_drop_no_memory", tx_drop_no_memory), ++ MCTP_USB_EID_STAT("tx_drop_urb_error", tx_drop_urb_error), ++ MCTP_USB_EID_STAT("tx_drop_ebusy", tx_drop_ebusy), ++ MCTP_USB_EID_STAT("tx_drop_enodev", tx_drop_enodev), ++ MCTP_USB_EID_STAT("tx_drop_enoent", tx_drop_enoent), ++ MCTP_USB_EID_STAT("tx_drop_enxio", tx_drop_enxio), ++ MCTP_USB_EID_STAT("tx_drop_einval", tx_drop_einval), ++ MCTP_USB_EID_STAT("tx_drop_exdev", tx_drop_exdev), ++ MCTP_USB_EID_STAT("tx_drop_efbig", tx_drop_efbig), ++ MCTP_USB_EID_STAT("tx_drop_epipe", tx_drop_epipe), ++ MCTP_USB_EID_STAT("tx_drop_emsgsize", tx_drop_emsgsize), ++ MCTP_USB_EID_STAT("tx_drop_enospc", tx_drop_enospc), ++ MCTP_USB_EID_STAT("tx_drop_eshutdown", tx_drop_eshutdown), ++ MCTP_USB_EID_STAT("tx_drop_eperm", tx_drop_eperm), ++ MCTP_USB_EID_STAT("tx_drop_ehostunreach", tx_drop_ehostunreach), ++ MCTP_USB_EID_STAT("tx_drop_enoexec", tx_drop_enoexec), ++ MCTP_USB_EID_STAT("tx_drop_queue_full", tx_drop_queue_full), ++ /* General statistics */ ++ MCTP_USB_EID_STAT("tx_requeued", tx_requeued), ++ MCTP_USB_EID_STAT("rx_requeued", rx_requeued), ++ MCTP_USB_EID_STAT("rx_urb_submitted", rx_urb_submitted), ++ MCTP_USB_EID_STAT("tx_urb_submitted", tx_urb_submitted), ++}; ++ ++#define MCTP_USB_EID_NUM_STATS ARRAY_SIZE(mctp_usb_eid_stat_descs) ++ ++/* Generate ethtool helper functions using shared macros */ ++MCTP_EID_STATS_HELPERS(mctp_usb, struct mctp_usb, ++ struct mctp_usb_eid_stats, mctp_usb_eid_stat_descs) ++ ++static void mctp_usb_get_strings(struct net_device *ndev, u32 stringset, ++ u8 *data) ++{ ++ struct mctp_usb *musb = netdev_priv(ndev); ++ unsigned int i; ++ int eid; ++ ++ if (stringset != ETH_SS_STATS) ++ return; ++ ++ /* Output aggregate stats (computed from per-EID) */ ++ for (i = 0; i < MCTP_USB_EID_NUM_STATS; i++) { ++ snprintf(data, ETH_GSTRING_LEN, "%-30s", mctp_usb_eid_stat_descs[i].name); ++ data += ETH_GSTRING_LEN; ++ } ++ ++ /* Separator line */ ++ snprintf(data, ETH_GSTRING_LEN, " "); ++ data += ETH_GSTRING_LEN; ++ ++ /* Output per-EID stats (only for active EIDs with non-zero stats) */ ++ for_each_set_bit(eid, musb->eid_stats.active, 257) { ++ struct mctp_usb_eid_stats *es = &musb->eid_stats.eid[eid]; ++ u8 *base = (u8 *)es; ++ int nz = mctp_usb_count_eid_nonzero(musb, eid); ++ ++ if (nz == 0) ++ continue; ++ ++ /* EID header line with explanation ++ * - UNKNOWN: Errors where EID could not be determined (async URB ++ * completion callbacks, batch submission failures, or packets ++ * too short to contain valid MCTP header) ++ * - EID_0: Valid packets with source/dest EID=0 (null/unallocated ++ * endpoint per MCTP spec DSP0236) ++ * - EID_X: Normal endpoints (X = 1..253) ++ */ ++ if (eid == MCTP_EID_UNKNOWN) { ++ snprintf(data, ETH_GSTRING_LEN, "UNKNOWN: URB/pre-parse errors "); ++ } else if (eid == 0) { ++ snprintf(data, ETH_GSTRING_LEN, "EID_0: null endpoint "); ++ } else { ++ snprintf(data, ETH_GSTRING_LEN, "EID_%-3u ", eid); ++ } ++ data += ETH_GSTRING_LEN; ++ ++ /* Individual non-zero stats for this EID */ ++ for (i = 0; i < MCTP_USB_EID_NUM_STATS; i++) { ++ u64 val = *(u64 *)(base + mctp_usb_eid_stat_descs[i].offset); ++ if (val != 0) { ++ snprintf(data, ETH_GSTRING_LEN, "%-30s", ++ mctp_usb_eid_stat_descs[i].name); ++ data += ETH_GSTRING_LEN; ++ } ++ } ++ } ++} ++ ++static int mctp_usb_get_sset_count(struct net_device *ndev, int sset) ++{ ++ struct mctp_usb *musb = netdev_priv(ndev); ++ ++ if (sset == ETH_SS_STATS) ++ return MCTP_USB_EID_NUM_STATS + 1 + mctp_usb_count_eid_stats(musb); ++ /* aggregates */ /* separator */ /* per-EID */ ++ ++ return -EOPNOTSUPP; ++} ++ ++static void mctp_usb_get_ethtool_stats(struct net_device *ndev, ++ struct ethtool_stats *stats, ++ u64 *data) ++{ ++ struct mctp_usb *musb = netdev_priv(ndev); ++ unsigned int i, idx = 0; ++ int eid; ++ ++ /* Output aggregate stats (computed by summing across all EIDs) */ ++ for (i = 0; i < MCTP_USB_EID_NUM_STATS; i++) { ++ u64 total = 0; ++ ++ for_each_set_bit(eid, musb->eid_stats.active, 257) { ++ u8 *base = (u8 *)&musb->eid_stats.eid[eid]; ++ total += *(u64 *)(base + mctp_usb_eid_stat_descs[i].offset); ++ } ++ data[idx++] = total; ++ } ++ ++ /* Separator (blank line in output) */ ++ data[idx++] = 0; ++ ++ /* Output per-EID stats (only for active EIDs with non-zero stats) */ ++ for_each_set_bit(eid, musb->eid_stats.active, 257) { ++ struct mctp_usb_eid_stats *es = &musb->eid_stats.eid[eid]; ++ u8 *base = (u8 *)es; ++ int nz = mctp_usb_count_eid_nonzero(musb, eid); ++ ++ if (nz == 0) ++ continue; ++ ++ /* EID header: value is sum of all stats for this EID */ ++ data[idx++] = mctp_usb_eid_stats_total(musb, eid); ++ ++ /* Individual non-zero stats for this EID */ ++ for (i = 0; i < MCTP_USB_EID_NUM_STATS; i++) { ++ u64 val = *(u64 *)(base + mctp_usb_eid_stat_descs[i].offset); ++ if (val != 0) ++ data[idx++] = val; ++ } ++ } ++} ++ ++static const struct ethtool_ops mctp_usb_ethtool_ops = { ++ .get_strings = mctp_usb_get_strings, ++ .get_sset_count = mctp_usb_get_sset_count, ++ .get_ethtool_stats = mctp_usb_get_ethtool_stats, ++}; ++ ++static const struct net_device_ops mctp_usb_netdev_ops = { ++ .ndo_start_xmit = mctp_usb_start_xmit, ++ .ndo_open = mctp_usb_open, ++ .ndo_stop = mctp_usb_stop, ++}; ++ ++static void mctp_usb_netdev_setup(struct net_device *dev) ++{ ++ dev->type = ARPHRD_MCTP; ++ ++ dev->mtu = MCTP_USB_MTU_MIN; ++ dev->ethtool_ops = &mctp_usb_ethtool_ops; ++ dev->min_mtu = MCTP_USB_MTU_MIN; ++ dev->max_mtu = MCTP_USB_MTU_MAX; ++ ++ dev->hard_header_len = sizeof(struct mctp_usb_hdr); ++ dev->tx_queue_len = DEFAULT_TX_QUEUE_LEN; ++ dev->flags = IFF_NOARP; ++ dev->netdev_ops = &mctp_usb_netdev_ops; ++} ++ ++static int mctp_usb_probe(struct usb_interface *intf, ++ const struct usb_device_id *id) ++{ ++ struct usb_endpoint_descriptor *ep_in, *ep_out; ++ struct usb_host_interface *iface_desc; ++ struct net_device *netdev; ++ struct mctp_usb *dev; ++ int rc; ++ ++ /* only one alternate */ ++ iface_desc = intf->cur_altsetting; ++ ++ rc = usb_find_common_endpoints(iface_desc, &ep_in, &ep_out, NULL, NULL); ++ if (rc) { ++ dev_err(&intf->dev, "invalid endpoints on device?\n"); ++ return rc; ++ } ++ ++ netdev = alloc_netdev(sizeof(*dev), "mctpusb%d", NET_NAME_ENUM, ++ mctp_usb_netdev_setup); ++ if (!netdev) ++ return -ENOMEM; ++ ++ SET_NETDEV_DEV(netdev, &intf->dev); ++ dev = netdev_priv(netdev); ++ dev->netdev = netdev; ++ dev->usbdev = usb_get_dev(interface_to_usbdev(intf)); ++ dev->intf = intf; ++ usb_set_intfdata(intf, dev); ++ ++ dev->ep_in = ep_in->bEndpointAddress; ++ dev->ep_out = ep_out->bEndpointAddress; ++ ++ init_usb_anchor(&dev->rx_anchor); ++ init_usb_anchor(&dev->tx_anchor); ++ ++ INIT_DELAYED_WORK(&dev->rx_retry_work, mctp_usb_rx_retry_work); ++ ++ /* Enable TX batching by default */ ++ dev->tx_batching_enabled = true; ++ ++ rc = mctp_register_netdev(netdev, &mctp_usb_ops, MCTP_PHYS_BINDING_USB); ++ if (rc) ++ goto err_free_netdev; ++ ++ /* Set the mctp_dev batching parameters for use in route.c */ ++ { ++ struct mctp_dev *mdev; ++ ++ rcu_read_lock(); ++ mdev = __mctp_dev_get(netdev); ++ if (mdev) { ++ mdev->tx_batching_enabled = true; ++ mdev->tx_batch_hdr_len = sizeof(struct mctp_usb_hdr); ++ mdev->tx_batch_max_xfer = MCTP_USB_XFER_SIZE; ++ mctp_dev_put( ++ mdev); /* Release reference taken by __mctp_dev_get() */ ++ } ++ rcu_read_unlock(); ++ } ++ ++ /* Register sysfs attribute for runtime control */ ++ rc = sysfs_create_group(&netdev->dev.kobj, &mctp_usb_attr_group); ++ if (rc) { ++ netdev_err(netdev, "failed to create sysfs group: %d\n", rc); ++ goto err_unregister_netdev; ++ } ++ ++ /* Setup error injection after netdev registration (debugfs needs the netdev name) */ ++ mctp_usb_error_inject_init(dev); ++ ++ return 0; ++ ++err_unregister_netdev: ++ mctp_unregister_netdev(netdev); ++err_free_netdev: ++ free_netdev(netdev); ++ return rc; ++} ++ ++static void mctp_usb_disconnect(struct usb_interface *intf) ++{ ++ struct mctp_usb *dev = usb_get_intfdata(intf); ++ ++ /* Remove sysfs attribute group */ ++ sysfs_remove_group(&dev->netdev->dev.kobj, &mctp_usb_attr_group); ++ ++ /* Cleanup error injection */ ++ mctp_usb_error_inject_cleanup(dev); ++ ++ mctp_unregister_netdev(dev->netdev); ++ usb_put_dev(dev->usbdev); ++ free_netdev(dev->netdev); ++} ++ ++static const struct usb_device_id mctp_usb_devices[] = { ++ { USB_INTERFACE_INFO(USB_CLASS_MCTP, 0x0, 0x1) }, ++ { USB_INTERFACE_INFO(USB_CLASS_MCTP, 0x0, 0x2) }, ++ { 0 }, ++}; ++ ++MODULE_DEVICE_TABLE(usb, mctp_usb_devices); ++ ++static struct usb_driver mctp_usb_driver = { ++ .name = "mctp-usb", ++ .id_table = mctp_usb_devices, ++ .probe = mctp_usb_probe, ++ .disconnect = mctp_usb_disconnect, ++}; ++ ++static int __init mctp_usb_init(void) ++{ ++ int rc; ++ ++ /* Initialize error injection infrastructure */ ++ rc = mctp_usb_error_inject_module_init(); ++ if (rc) ++ pr_warn("MCTP USB: Error injection initialization failed, continuing without it\n"); ++ ++ rc = usb_register(&mctp_usb_driver); ++ if (rc) ++ mctp_usb_error_inject_module_exit(); ++ ++ return rc; ++} ++ ++static void __exit mctp_usb_exit(void) ++{ ++ usb_deregister(&mctp_usb_driver); ++ mctp_usb_error_inject_module_exit(); ++} ++ ++module_init(mctp_usb_init); ++module_exit(mctp_usb_exit); ++ ++MODULE_LICENSE("GPL"); ++MODULE_AUTHOR("Jeremy Kerr "); ++MODULE_DESCRIPTION("MCTP USB transport"); +diff --git a/drivers/net/mctp/nvidia-optee-vrot.c b/drivers/net/mctp/nvidia-optee-vrot.c +new file mode 100644 +index 000000000..e0c714024 +--- /dev/null ++++ b/drivers/net/mctp/nvidia-optee-vrot.c +@@ -0,0 +1,1264 @@ ++/// @file ++/// @brief Kernel module for MCTP communication with NVIDIA's VRoT. ++/// @details VRoTs are optee Trusted Applications that each manage a single endpoint. ++/// Enable with the following device tree to create a mctp device called mctpvrot0. ++/// @code{.dts} ++/// { ++/// mctpvrot0 { ++/// compatible = "nvidia,optee,vrot"; ++/// nvidia,ta-uuid = "14fe76f6-6114-499c-9b81-f90ebde9c50c"; ++/// nvidia,mctp-uuid = "14fe76f6-6114-499c-9b81-f90ebde9c50c"; ++/// nvidia,mctp-mtu = <5000>; ++/// nvidia,mctp-protocol-versions = <0x1 0xF1F0F000 ++/// 0x5 0x0 ++/// 0x7e 0xF1F0FF00>; ++/// status = "okay"; ++/// }; ++/// }; ++/// @endcode ++/// ++/// Device Tree Properties: ++/// - nvidia,ta-uuid: The UUID of the trusted application. ++/// - nvidia,mctp-uuid: The UUID used in the GET UUID MCTP control message. ++/// - nvidia,mctp-mtu: The max transmission unit for MCTP communication. ++/// - nvidia,mctp-protocol-versions: A mapping of supported MCTP procotols ++/// to the version that should be reported in a Get MCTP Version Support command. ++/// Use zero to not respond to version requests. ++/// Non-zero values are formatted in the standard MCTP version format. ++/// The example devicetree reports PLDM version 1.0.0 SPDM version unknown ++/// and vendor defined 1.0. ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#define HEARTBEAT_COMMAND_INTERVAL_MS 30000 ++#define TEE_IOCTL_PARAM_ATTR_TYPE_NONE 0 ++ ++#define LOG_IMPL(lvl, fmt, ...) \ ++ printk(lvl "NVIDIA-OPTEE-VROT: " fmt "\n", ##__VA_ARGS__) ++ ++/// @brief Helper logging macros. ++/// @details Has the same API as printf(). ++/// @{ ++#ifdef DEBUG ++#define LOG_INF(...) LOG_IMPL(KERN_INFO, __VA_ARGS__) ++#else ++#define LOG_INF(...) \ ++ do { \ ++ } while (0) ++#endif ++#define LOG_WRN(...) LOG_IMPL(KERN_WARNING, __VA_ARGS__) ++#define LOG_ERR(...) LOG_IMPL(KERN_ERR, __VA_ARGS__) ++/// @} ++ ++#define TA_COMMAND_ID_WRITE_PACKET 0 ++#define TA_COMMAND_ID_HANDLE_NON_ATOMIC_OPERATION 1 ++#define TA_COMMAND_ID_HEARTBEAT 3 ++ ++#define NVIDIA_VROT_QUEUE_SIZE 20 ++ ++#define NVIDIA_VROT_MAX_PROTOCOLS 10 ++ ++/// @brief Entries that are encoded in the nvidia,mctp-protocol-versions property. ++/// @details The property is an array of u32. ++/// Odd indexes represent protocol IDs and even indexes are the version of the protocol. ++/// Is needed because the VRoTs do not implement the control MCTP protocol so we ++/// fake it in the kernel module. ++/// @note Entries should not be provided for the control protocol or base MCTP version ++/// as that is controlled by this module. ++struct nvidia_ta_mctp_protocol_version { ++ /// @brief The MCTP protocol ID. ++ /// @example 1 is PLDM, 5 is SPDM. ++ u8 protocol; ++ /// @brief The version number. ++ /// @note Use 0 if you do not which to respond to a ++ /// Get MCTP Version Support command for this protocol. ++ /// @note This is a u32 in native endianness, using a u8[4] to reduce padding. ++ /// @example 0xF1F0F000 is 1.0.0. ++ u8 version[4]; ++}; ++ ++/// @brief Immutable config taken from device tree. ++struct nvidia_ta_mctp_config { ++ /// @brief nvidia,ta-uuid property. ++ /// @details The UUID of the trusted application that acts as a VRoT. ++ uuid_t ta_uuid; ++ /// @brief nvidia,mctp-uuid property. ++ /// @details The UUID to use in response to a Get Endpoint UUID MCTP Control Message. ++ uuid_t mctp_uuid; ++ /// @brief nvidia,mctp-mtu property. ++ /// @details The max allowed MTU for the VRoT trusted application. ++ /// @note Is not discovered at startup via TEE function calls because we want to delay ++ /// TA initialization to as late as possible to allow for tee-supplicant to be started ++ // which may occur after kernel module loading. ++ u32 mtu; ++ /// @brief The number of valid entries in protocols. ++ u32 protocols_size; ++ /// @brief nvidia,mctp-protocol-versions property. ++ struct nvidia_ta_mctp_protocol_version ++ protocols[NVIDIA_VROT_MAX_PROTOCOLS]; ++}; ++ ++/// @brief State used by callbacks. ++struct nvidia_ta_mctp_driver { ++ /// @brief Info from device tree. ++ struct nvidia_ta_mctp_config config; ++ ++ /// @brief Trusted execution context. ++ struct tee_context *tee_ctx; ++ /// @brief Shared Memory with the trusted environment. ++ struct tee_shm *tee_pool; ++ /// @brief Trusted execution session id. ++ u32 tee_session_id; ++ ++ /// @brief Network device used by the linux kernel for this driver. ++ struct net_device *netdev; ++ ++ /// @brief Worker task for handling MCTP packets. ++ struct task_struct *worker_task; ++ ++ /// @brief wait queue that is woken when tx_queue is pushed to ++ wait_queue_head_t wq; ++ ++ /// @brief Packet queue from the network device to the tx worker thread. ++ struct sk_buff_head tx_queue; ++ ++ /// @brief The timestamp when we should service non-atomic work. ++ /// @details Is only valid if non_atomic is true. ++ u64 non_atomic_work_jiffies_64; ++ ++ /// @brief The timestamp when we should next send a heartbeat. ++ /// @details Is only valid if tee_initialized is true. ++ u64 next_heartbeat_jiffies_64; ++ ++ /// @brief If the tee is initialized. Used to deduplicate initialization since since we initialize once on first write (to ensure TEE-Supplicant is running) ++ u32 tee_initialized : 1; ++ ++ /// @brief Non-atomic operation flag ++ u32 non_atomic : 1; ++}; ++ ++/// @brief Private state for the mctp network device. ++struct nvidia_vrot_netdev_priv { ++ struct nvidia_ta_mctp_driver *driver; ++}; ++ ++static inline void log_buffer_hex_chunks(const char *prefix, const void *buf, ++ size_t count) ++{ ++#ifndef DEBUG ++ (void)prefix; ++ (void)buf; ++ (void)count; ++#else ++#define CHUNK_SIZE 16 ++ const unsigned char *ubuf = (const unsigned char *)buf; ++ size_t i, j; ++ char line[CHUNK_SIZE * ++ 3]; // 2 chars per byte + 1 space or null terminator per byte ++ for (i = 0; i < count; i += CHUNK_SIZE) { ++ size_t chunk = (count - i > CHUNK_SIZE) ? CHUNK_SIZE : ++ (count - i); ++ size_t pos = 0; ++ for (j = 0; j < chunk; j++) { ++ pos += sprintf(&line[pos], "%02x%s", ubuf[i + j], ++ (j < chunk - 1) ? " " : ""); ++ } ++ line[pos] = '\0'; ++ LOG_INF("%s [%04zu-%04zu]: %s", prefix, i, i + chunk - 1, line); ++ } ++#undef CHUNK_SIZE ++#endif ++} ++ ++static int optee_ctx_match(struct tee_ioctl_version_data *ver, const void *data) ++{ ++ switch (ver->impl_id) { ++ case TEE_IMPL_ID_OPTEE: ++ return 1; ++ default: ++ return 0; ++ } ++} ++ ++static int open_ta_session(struct nvidia_ta_mctp_driver *driver) ++{ ++ int ret; ++ struct tee_ioctl_open_session_arg sess_arg; ++ memset(&sess_arg, 0, sizeof(sess_arg)); ++ ++ if (driver == NULL) { ++ LOG_ERR("open_ta_session: driver is NULL"); ++ return -EIO; ++ } ++ ++ export_uuid(sess_arg.uuid, &driver->config.ta_uuid); ++ sess_arg.clnt_login = TEE_IOCTL_LOGIN_PUBLIC; ++ sess_arg.num_params = 0; ++ /* Open the session */ ++ ret = tee_client_open_session(driver->tee_ctx, &sess_arg, NULL); ++ if (ret < 0) { ++ LOG_ERR("Failed to open OP-TEE session"); ++ return -EINVAL; ++ } ++ ++ if (sess_arg.ret != 0) { ++ LOG_ERR("Session open failed with TA error: 0x%x", ++ sess_arg.ret); ++ return -EINVAL; ++ } ++ ++ /* Store the session ID */ ++ driver->tee_session_id = sess_arg.session; ++ LOG_INF("Kernel: OP-TEE session opened with ID: %u", ++ driver->tee_session_id); ++ ++ return 0; ++} ++ ++static int send_heartbeat_command_to_tee(struct nvidia_ta_mctp_driver *driver) ++{ ++ driver->next_heartbeat_jiffies_64 = ++ get_jiffies_64() + ++ msecs_to_jiffies(HEARTBEAT_COMMAND_INTERVAL_MS); ++ ++ struct tee_ioctl_invoke_arg invoke_arg = { ++ .func = TA_COMMAND_ID_HEARTBEAT, ++ .session = driver->tee_session_id, ++ .num_params = 4 ++ }; ++ ++ struct tee_param params[4] = { { 0 } }; ++ void *va; ++ int ret; ++ ++ // Get virtual address of shared memory ++ va = tee_shm_get_va(driver->tee_pool, 0); ++ if (IS_ERR(va)) { ++ LOG_ERR("Failed to get virtual address: %ld", PTR_ERR(va)); ++ return -EINVAL; ++ } ++ // Setup parameter for TA ++ // only parameter is the shared memory buffer to hold the vrot state ++ params[0].attr = TEE_IOCTL_PARAM_ATTR_TYPE_MEMREF_INOUT; ++ params[0].u.memref.shm = driver->tee_pool; ++ params[0].u.memref.shm_offs = 0; ++ params[0].u.memref.size = driver->config.mtu; ++ ++ // Setup parameters 1-3: TYPE_NONE ++ params[1].attr = TEE_IOCTL_PARAM_ATTR_TYPE_NONE; ++ params[2].attr = TEE_IOCTL_PARAM_ATTR_TYPE_NONE; ++ params[3].attr = TEE_IOCTL_PARAM_ATTR_TYPE_NONE; ++ ++ LOG_INF("Sending param types: p0=0x%llx, p1=0x%llx, p2=0x%llx, p3=0x%llx", ++ params[0].attr, params[1].attr, params[2].attr, params[3].attr); ++ ++ // Invoke TA ++ ret = tee_client_invoke_func(driver->tee_ctx, &invoke_arg, params); ++ LOG_INF("TA invocation returned: ret=%d, invoke_arg.ret=0x%x", ret, ++ invoke_arg.ret); ++ if (ret < 0 || invoke_arg.ret != 0) { ++ if (ret < 0) { ++ LOG_ERR("TA invocation failed: ret=%d. Check if TA is available and initialized on the system.", ++ ret); ++ return -EIO; ++ } ++ if (invoke_arg.ret != 0) { ++ LOG_ERR("TA invocation returned error: ta_ret=0x%x", ++ invoke_arg.ret); ++ } ++ return -EIO; ++ } ++ ++ LOG_INF("TA invocation completed"); ++ size_t out_size = params[0].u.memref.size; ++ ++ // at this point, we have the data in the shared memory from the tee invocation. ++ char *vrot_state = tee_shm_get_va(driver->tee_pool, 0); ++ if (vrot_state == NULL) { ++ LOG_ERR("Failed to get virtual address of shared memory"); ++ return -EFAULT; ++ } ++ ++ log_buffer_hex_chunks("VRoT state", vrot_state, out_size); ++ return 0; ++} ++ ++static int init_tee(struct nvidia_ta_mctp_driver *driver) ++{ ++ int ret; ++ if (driver == NULL) { ++ LOG_ERR("init_tee: driver is NULL"); ++ return -EIO; ++ } ++ ++ if (driver->tee_initialized) { ++ return 0; // Already initialized ++ } ++ ++ driver->tee_ctx = ++ tee_client_open_context(NULL, optee_ctx_match, NULL, NULL); ++ if (IS_ERR_OR_NULL(driver->tee_ctx)) { ++ LOG_ERR("Failed to open TEE context: %ld", ++ PTR_ERR(driver->tee_ctx)); ++ return -ENODEV; ++ } ++ ++ ret = open_ta_session(driver); ++ if (ret < 0) { ++ tee_client_close_context(driver->tee_ctx); ++ driver->tee_ctx = NULL; ++ return ret; ++ } ++ ++ driver->tee_pool = ++ tee_shm_alloc_kernel_buf(driver->tee_ctx, driver->config.mtu); ++ if (IS_ERR_OR_NULL(driver->tee_pool)) { ++ LOG_ERR("Failed to allocate shared memory: %ld", ++ PTR_ERR(driver->tee_pool)); ++ tee_client_close_session(driver->tee_ctx, ++ driver->tee_session_id); ++ tee_client_close_context(driver->tee_ctx); ++ driver->tee_ctx = NULL; ++ driver->tee_session_id = 0; ++ return -ENOMEM; ++ } ++ ++ LOG_INF("Shared memory allocated: size=%zu", driver->tee_pool->size); ++ driver->tee_initialized = true; ++ // want an immediate heartbeat ++ driver->next_heartbeat_jiffies_64 = get_jiffies_64(); ++ return 0; ++} ++ ++static int invoke_ta_function(struct nvidia_ta_mctp_driver *driver, ++ struct sk_buff *skb, int command_id, ++ size_t *out_size) ++{ ++ if (driver == NULL) { ++ LOG_ERR("invoke_ta_function: driver is NULL"); ++ return -EIO; ++ } ++ ++ size_t count = 0; ++ if (skb) { ++ count = skb->len; ++ } ++ ++ struct tee_ioctl_invoke_arg invoke_arg = { ++ .func = command_id, ++ .session = driver->tee_session_id, ++ .num_params = 4 ++ }; ++ ++ struct tee_param params[4] = { { 0 } }; ++ void *va; ++ int ret; ++ ++ if (!driver->tee_pool || !driver->tee_ctx) { ++ LOG_ERR("Shared memory or context not initialized"); ++ return -EINVAL; ++ } ++ ++ // Get virtual address of shared memory ++ va = tee_shm_get_va(driver->tee_pool, 0); ++ if (IS_ERR(va)) { ++ LOG_ERR("Failed to get virtual address: %ld", PTR_ERR(va)); ++ return -EINVAL; ++ } ++ ++ // Copy input data to shared memory ++ if (count > driver->tee_pool->size) { ++ LOG_WRN("Input size (%zu) exceeds shared memory size (%zu)", ++ count, driver->tee_pool->size); ++ count = driver->tee_pool->size; ++ } ++ ++ // copy input data to shared memory ++ if (count != 0) { ++ skb_copy_bits(skb, 0, va, count); ++ log_buffer_hex_chunks("Tx to TA", va, count); ++ } ++ ++ // Setup parameter for TA ++ // first parameter is the shared memory buffer ++ params[0].attr = TEE_IOCTL_PARAM_ATTR_TYPE_MEMREF_INOUT; ++ params[0].u.memref.shm = driver->tee_pool; ++ params[0].u.memref.shm_offs = 0; ++ params[0].u.memref.size = count; ++ ++ // second parameter will be used to get bool indicating whether the TA is handling a non-atomic operation, and a delay for invoking the TA again ++ params[1].attr = TEE_IOCTL_PARAM_ATTR_TYPE_VALUE_OUTPUT; ++ params[1].u.value.a = 0; ++ params[1].u.value.b = 0; ++ ++ // Setup parameters 2-3: TYPE_NONE for now ++ params[2].attr = TEE_IOCTL_PARAM_ATTR_TYPE_NONE; ++ params[3].attr = TEE_IOCTL_PARAM_ATTR_TYPE_NONE; ++ ++ LOG_INF("Sending param types: p0=0x%llx, p1=0x%llx, p2=0x%llx, p3=0x%llx", ++ params[0].attr, params[1].attr, params[2].attr, params[3].attr); ++ LOG_INF("Sending size: %zu", count); ++ ++ // Invoke TA ++ ret = tee_client_invoke_func(driver->tee_ctx, &invoke_arg, params); ++ LOG_INF("TA invocation returned: ret=%d, invoke_arg.ret=0x%x", ret, ++ invoke_arg.ret); ++ if (ret < 0 || invoke_arg.ret != 0) { ++ *out_size = 0; ++ driver->non_atomic = false; ++ driver->non_atomic_work_jiffies_64 = 0; ++ if (ret < 0) { ++ LOG_ERR("TA invocation failed: ret=%d. Check if TA is available and initialized on the system.", ++ ret); ++ return -EIO; ++ } ++ if (invoke_arg.ret != 0) { ++ LOG_ERR("TA invocation returned error: ta_ret=0x%x", ++ invoke_arg.ret); ++ return -EIO; ++ } ++ } ++ ++ LOG_INF("TA invocation completed"); ++ *out_size = params[0].u.memref.size; ++ driver->non_atomic = (bool)params[1].u.value.a; ++ const u32 delay = params[1].u.value.b; ++ if (driver->non_atomic) { ++ driver->non_atomic_work_jiffies_64 = get_jiffies_64() + delay; ++ LOG_INF("TA is handling a non-atomic operation, delay: %u", ++ delay); ++ } else { ++ driver->non_atomic_work_jiffies_64 = 0; ++ } ++ return count; ++} ++ ++static int handle_data_from_ta(struct nvidia_ta_mctp_driver *driver, ++ size_t size) ++{ ++ int ret = -EINVAL; ++ ++ const char *const shared_mem_data = tee_shm_get_va(driver->tee_pool, 0); ++ if (shared_mem_data == NULL) { ++ LOG_ERR("Failed to get virtual address of shared memory"); ++ return -EFAULT; ++ } ++ ++ if (size <= 4) { ++ LOG_ERR("Response size too small for MCTP packet: %u", ++ (unsigned)size); ++ return -EINVAL; ++ } ++ ++ log_buffer_hex_chunks("Rx from TA", shared_mem_data, size); ++ ++ struct sk_buff *skb; ++ skb = netdev_alloc_skb(driver->netdev, size); ++ if (!skb) { ++ LOG_ERR("failed to allocate skb for read packet"); ++ return -ENOMEM; ++ } ++ skb->protocol = htons(ETH_P_MCTP); ++ (void)skb_put_data(skb, shared_mem_data, size); ++ skb_reset_mac_header(skb); ++ skb_reset_network_header(skb); ++ ++ struct mctp_skb_cb *const cb = __mctp_cb(skb); ++ cb->halen = 0; ++ ++ ret = netif_receive_skb(skb); ++ if (ret != NET_RX_SUCCESS) { ++ LOG_ERR("netif_rx failed with %d", (int)ret); ++ // NOTE: skb is already freed by netif_receive_skb on error ++ return -EINVAL; ++ } ++ return 0; ++} ++ ++static void nvidia_ta_write_to_tee(struct nvidia_ta_mctp_driver *driver, ++ struct sk_buff *skb, int command_id) ++{ ++ int ret = -EINVAL; ++ LOG_INF("nvidia_ta_write_to_tee: Writing to TEE"); ++ ret = init_tee(driver); // Initialize on first write ++ if (ret < 0) { ++ LOG_ERR("Kernel: TEE initialization failed: %d", ret); ++ return; ++ } ++ ++ size_t out_size = 0; ++ ret = invoke_ta_function(driver, skb, command_id, &out_size); ++ if (ret < 0) { ++ LOG_ERR("Kernel: Failed to invoke TA function: %d", ret); ++ return; ++ } ++ ++ if (out_size == 0) { ++ return; ++ } ++ ++ ret = handle_data_from_ta(driver, out_size); ++ if (ret < 0) { ++ LOG_ERR("Kernel: Failed to handle data read from TA: %d", ret); ++ return; ++ } ++} ++ ++/// @brief Performs cleanup for our custom driver bound to the given platform_device. ++/// @param pdev The device having its driver cleaned up. ++/// @note The driver can be in any stage of initialization including completely unbound. ++static void clean_driver(struct platform_device *pdev) ++{ ++ if (pdev == NULL) { ++ return; ++ } ++ ++ struct nvidia_ta_mctp_driver *const driver = ++ (struct nvidia_ta_mctp_driver *)platform_get_drvdata(pdev); ++ if (driver == NULL) { ++ return; ++ } ++ ++ // perform any needed cleanup ++ // Stop the worker thread first ++ if (driver->worker_task != NULL) { ++ kthread_stop(driver->worker_task); ++ driver->worker_task = NULL; ++ } ++ ++ // Ensure network device is down and purge any pending packets ++ if (driver->netdev != NULL) { ++ // Stop the queue to prevent new packets ++ netif_tx_disable(driver->netdev); ++ ++ // Purge any remaining packets in the queue ++ skb_queue_purge(&driver->tx_queue); ++ ++ // Explicitly bring the device down if it's still running ++ rtnl_lock(); ++ if (netif_running(driver->netdev)) { ++ dev_close(driver->netdev); ++ } ++ rtnl_unlock(); ++ ++ // Unregister the netdev - this should handle final cleanup ++ mctp_unregister_netdev(driver->netdev); ++ free_netdev(driver->netdev); ++ driver->netdev = NULL; ++ } ++ ++ if (driver->tee_initialized) { ++ tee_client_close_session(driver->tee_ctx, ++ driver->tee_session_id); ++ driver->tee_initialized = false; ++ } ++ if (driver->tee_pool != NULL) { ++ tee_shm_free(driver->tee_pool); ++ driver->tee_pool = NULL; ++ } ++ if (driver->tee_ctx != NULL) { ++ tee_client_close_context(driver->tee_ctx); ++ driver->tee_ctx = NULL; ++ } ++ ++ // the driver is always heap allocated and we have exclusive ownership of it, free it. ++ platform_set_drvdata(pdev, NULL); ++ kfree(driver); ++} ++ ++netdev_tx_t nvidia_vrot_start_xmit(struct sk_buff *skb, struct net_device *dev) ++{ ++ netdev_tx_t status = NETDEV_TX_BUSY; ++ ++ struct nvidia_vrot_netdev_priv *const priv = netdev_priv(dev); ++ struct nvidia_ta_mctp_driver *const driver = priv->driver; ++ ++ unsigned long flags; ++ ++ // locking the queue for the entire push so we can atomically inspect ++ // the length to manage the network interface queue enable state. ++ spin_lock_irqsave(&driver->tx_queue.lock, flags); ++ if (skb_queue_len(&driver->tx_queue) >= NVIDIA_VROT_QUEUE_SIZE) { ++ // error: queue already full ++ status = NETDEV_TX_BUSY; ++ LOG_WRN("mctp tx queue overflow"); ++ netif_stop_queue(dev); ++ } else { ++ // push the packet ++ status = NETDEV_TX_OK; ++ __skb_queue_tail(&driver->tx_queue, skb); ++ if (skb_queue_len(&driver->tx_queue) == ++ NVIDIA_VROT_QUEUE_SIZE) { ++ // stop the queue to prevent excessive RAM usage ++ netif_stop_queue(dev); ++ } ++ } ++ spin_unlock_irqrestore(&driver->tx_queue.lock, flags); ++ ++ if (status == NETDEV_TX_OK) { ++ wake_up(&driver->wq); ++ } ++ return status; ++} ++ ++int nvidia_vrot_open(struct net_device *dev) ++{ ++ // TODO: could start/resume worker threads ++ netif_start_queue(dev); ++ return 0; ++} ++ ++int nvidia_vrot_stop(struct net_device *dev) ++{ ++ // TODO: could stop worker threads ++ netif_stop_queue(dev); ++ return 0; ++} ++ ++static struct net_device_ops nvidia_vrot_nops = { ++ .ndo_start_xmit = nvidia_vrot_start_xmit, ++ .ndo_open = nvidia_vrot_open, ++ .ndo_stop = nvidia_vrot_stop, ++}; ++ ++// mctp header byte indexes ++static const size_t nv_mctp_ver_index = 0; ++static const size_t nv_mctp_dst_index = 1; ++static const size_t nv_mctp_src_index = 2; ++static const size_t nv_mctp_flags_index = 3; ++ ++// only support version 1 packets with no reserved bits set ++static const u8 nv_mctp_expected_version_byte = 1; ++ ++// flag bits of interest ++static const u8 nv_mctp_som_bit = 1 << 7; ++static const u8 nv_mctp_eom_bit = 1 << 6; ++static const u8 nv_mctp_to_bit = 1 << 3; ++ ++// control header bytes ++static const size_t nv_mctp_ctrl_msg_type_index = 4; ++static const size_t nv_mctp_ctrl_flags_index = 5; ++static const size_t nv_mctp_ctrl_cc_index = 6; ++static const size_t nv_mctp_ctrl_rsp_index = 7; ++ ++// control header values ++static const u8 nv_mctp_ctrl_msg_type = 0; ++ ++// ctrl flags bits of interest ++static const u8 nv_mctp_ctrl_req_bit = 1 << 7; ++static const u8 nv_mctp_ctrl_datagram_bit = 1 << 6; ++ ++// used ctrl response codes ++static const u8 nv_mctp_ctrl_rsp_ok = 0; ++static const u8 nv_mctp_ctrl_rsp_error = 1; ++static const u8 nv_mctp_ctrl_rsp_bad_data = 2; ++static const u8 nv_mctp_ctrl_rsp_bad_cmd = 5; ++ ++// control header sizes ++static const size_t nv_mctp_ctrl_cmd_data_start = 7; ++static const size_t nv_mctp_ctrl_rsp_data_start = 8; ++ ++// must have entire header ++static const size_t nv_min_ctrl_req_size = nv_mctp_ctrl_cmd_data_start; ++// must always fit in a min MTU packet ++#define NV_MAX_CTRL_MSG_SIZE 68 ++ ++static bool nvidia_ta_is_control_request(struct sk_buff *skb) ++{ ++ if (skb->len < nv_min_ctrl_req_size) { ++ return false; ++ } ++ if (skb->len > NV_MAX_CTRL_MSG_SIZE) { ++ return false; ++ } ++ ++ unsigned char data[NV_MAX_CTRL_MSG_SIZE] = { 0 }; ++ if (0 != skb_copy_bits(skb, 0, data, nv_min_ctrl_req_size)) { ++ LOG_WRN("skb_copy_bits failed when detecting packet type"); ++ return false; ++ } ++ if (data[nv_mctp_ver_index] != nv_mctp_expected_version_byte) { ++ return false; ++ } ++ ++ const u8 required_bits = nv_mctp_som_bit | nv_mctp_eom_bit | ++ nv_mctp_to_bit; ++ if ((data[nv_mctp_flags_index] & required_bits) != required_bits) { ++ return false; ++ } ++ if (data[nv_mctp_ctrl_msg_type_index] != nv_mctp_ctrl_msg_type) { ++ // different packet type ++ return false; ++ } ++ return true; ++} ++ ++static int ++nvidia_ta_handle_control_request(struct nvidia_ta_mctp_driver *driver, ++ const struct sk_buff *skb) ++{ ++ // Ensure buffer is large enough for maximum control responses ++ BUILD_BUG_ON(nv_mctp_ctrl_rsp_data_start + 16 > ++ NV_MAX_CTRL_MSG_SIZE); // Get Endpoint UUID ++ BUILD_BUG_ON(nv_mctp_ctrl_rsp_data_start + 1 + ++ NVIDIA_VROT_MAX_PROTOCOLS > ++ NV_MAX_CTRL_MSG_SIZE); // Get Message Type Support ++ ++ LOG_INF("handling control message..."); ++ ++ unsigned char cmd[NV_MAX_CTRL_MSG_SIZE] = { 0 }; ++ if (skb_copy_bits(skb, 0, cmd, skb->len) != 0) { ++ return -EFAULT; ++ } ++ log_buffer_hex_chunks("Control CMD", cmd, skb->len); ++ ++ unsigned char rsp[NV_MAX_CTRL_MSG_SIZE] = { 0 }; ++ ++ // fill MCTP header ++ rsp[nv_mctp_ver_index] = cmd[nv_mctp_ver_index]; ++ rsp[nv_mctp_dst_index] = cmd[nv_mctp_src_index]; ++ rsp[nv_mctp_src_index] = cmd[nv_mctp_dst_index]; ++ rsp[nv_mctp_flags_index] = cmd[nv_mctp_flags_index] & ~nv_mctp_to_bit; ++ ++ // fill control header ++ rsp[nv_mctp_ctrl_msg_type_index] = cmd[nv_mctp_ctrl_msg_type_index]; ++ rsp[nv_mctp_ctrl_flags_index] = cmd[nv_mctp_ctrl_flags_index] & ++ ~nv_mctp_ctrl_req_bit; ++ rsp[nv_mctp_ctrl_cc_index] = cmd[nv_mctp_ctrl_cc_index]; ++ rsp[nv_mctp_ctrl_rsp_index] = nv_mctp_ctrl_rsp_error; ++ ++ // payload-less length ++ size_t rsp_len = nv_mctp_ctrl_rsp_data_start; ++ ++ // supporting only mandatory and actually used commands ++ switch (cmd[nv_mctp_ctrl_cc_index]) { ++ case 1: ++ LOG_INF("Set Endpoint ID"); ++ rsp[nv_mctp_ctrl_rsp_index] = nv_mctp_ctrl_rsp_ok; ++ // accepted + no pool ++ rsp[nv_mctp_ctrl_rsp_data_start + 0] = 0; ++ // echo back EID, is 2nd data byte in command ++ rsp[nv_mctp_ctrl_rsp_data_start + 1] = ++ cmd[nv_mctp_ctrl_cmd_data_start + 1]; ++ // no pool ++ rsp[nv_mctp_ctrl_rsp_data_start + 2] = 0; ++ rsp_len = nv_mctp_ctrl_rsp_data_start + 3; ++ break; ++ ++ case 2: ++ LOG_INF("Get Endpoint ID"); ++ rsp[nv_mctp_ctrl_rsp_index] = nv_mctp_ctrl_rsp_ok; ++ // echo back endpoint ID ++ rsp[nv_mctp_ctrl_rsp_data_start + 0] = cmd[nv_mctp_dst_index]; ++ // simple endpoint + dynamic ID ++ rsp[nv_mctp_ctrl_rsp_data_start + 1] = 0; ++ // no transport specific info ++ rsp[nv_mctp_ctrl_rsp_data_start + 2] = 0; ++ rsp_len = nv_mctp_ctrl_rsp_data_start + 3; ++ break; ++ ++ case 3: ++ LOG_INF("Get Endpoint UUID"); ++ rsp[nv_mctp_ctrl_rsp_index] = nv_mctp_ctrl_rsp_ok; ++ memcpy(&rsp[nv_mctp_ctrl_rsp_data_start], ++ &driver->config.mctp_uuid, 16); ++ rsp_len = nv_mctp_ctrl_rsp_data_start + 16; ++ break; ++ ++ case 4: { ++ LOG_INF("Get Version Support"); ++ u32 version = 0; ++ switch (cmd[nv_mctp_ctrl_cmd_data_start]) { ++ case 0x00: ++ // control version 1.3.1 ++ version = 0xF1F3F100; ++ break; ++ ++ case 0xFF: ++ // base version 1.3.1 ++ version = 0xF1F3F100; ++ break; ++ ++ default: ++ // other protocols, use version set via device tree ++ for (u32 i = 0; i < driver->config.protocols_size; ++ ++i) { ++ if (driver->config.protocols[i].protocol == ++ cmd[nv_mctp_ctrl_cmd_data_start]) { ++ memcpy(&version, ++ driver->config.protocols[i] ++ .version, ++ 4); ++ break; ++ } ++ } ++ break; ++ } ++ ++ if (version == 0) { ++ // version unknown, error ++ rsp[nv_mctp_ctrl_rsp_index] = nv_mctp_ctrl_rsp_bad_data; ++ } else { ++ rsp[nv_mctp_ctrl_rsp_index] = nv_mctp_ctrl_rsp_ok; ++ // versions are stored locally in native endianness, but MCTP wants big endian. ++ version = cpu_to_be32(version); ++ memcpy(&rsp[nv_mctp_ctrl_rsp_data_start], &version, ++ sizeof(version)); ++ rsp_len = nv_mctp_ctrl_rsp_data_start + sizeof(version); ++ } ++ break; ++ } ++ ++ case 5: ++ LOG_INF("Get Message Type Support"); ++ rsp[nv_mctp_ctrl_rsp_index] = nv_mctp_ctrl_rsp_ok; ++ rsp[nv_mctp_ctrl_rsp_data_start + 0] = ++ driver->config.protocols_size; ++ for (u32 i = 0; i < driver->config.protocols_size; ++i) { ++ rsp[nv_mctp_ctrl_rsp_data_start + 1 + i] = ++ driver->config.protocols[i].protocol; ++ } ++ rsp_len = nv_mctp_ctrl_rsp_data_start + 1 + ++ driver->config.protocols_size; ++ break; ++ ++ default: ++ LOG_WRN("Unsupported control command: 0x%02X", ++ cmd[nv_mctp_ctrl_cc_index]); ++ rsp[nv_mctp_ctrl_rsp_index] = nv_mctp_ctrl_rsp_bad_cmd; ++ break; ++ } ++ ++ log_buffer_hex_chunks("Control RSP", rsp, rsp_len); ++ ++ struct sk_buff *rsp_skb; ++ rsp_skb = netdev_alloc_skb(driver->netdev, rsp_len); ++ if (!rsp_skb) { ++ LOG_ERR("failed to allocate skb for read packet"); ++ return -ENOMEM; ++ } ++ rsp_skb->protocol = htons(ETH_P_MCTP); ++ (void)skb_put_data(rsp_skb, rsp, rsp_len); ++ skb_reset_mac_header(rsp_skb); ++ skb_reset_network_header(rsp_skb); ++ ++ struct mctp_skb_cb *const cb = __mctp_cb(rsp_skb); ++ cb->halen = 0; ++ ++ const int ret = netif_receive_skb(rsp_skb); ++ if (ret != NET_RX_SUCCESS) { ++ LOG_ERR("netif_rx failed with %d", (int)ret); ++ // NOTE: skb is already freed by netif_receive_skb on error ++ return -EINVAL; ++ } ++ ++ return 0; ++} ++ ++enum nvidia_ta_timeout_cause { ++ /// @brief There is no reason to ever timeout. ++ NVIDIA_TA_TIMEOUT_NONE, ++ /// @brief Should timeout to process delayed work. ++ NVIDIA_TA_TIMEOUT_WORK_TO_DO, ++ /// @brief Should timeout to send a heartbeat. ++ NVIDIA_TA_TIMEOUT_HEARTBEAT, ++}; ++ ++struct nvidia_ta_timeout { ++ enum nvidia_ta_timeout_cause cause; ++ /// @note Will be MAX_SCHEDULE_TIMEOUT if cause is none. ++ u32 duration_jiffies; ++}; ++ ++static struct nvidia_ta_timeout ++nvidia_ta_get_next_timeout(struct nvidia_ta_mctp_driver *driver) ++{ ++ const u64 now = get_jiffies_64(); ++ ++ bool should_timeout = false; ++ u32 jiffies_til_work = (u32)-1; ++ if (driver->tee_initialized && driver->non_atomic) { ++ should_timeout = true; ++ if (driver->non_atomic_work_jiffies_64 < now) { ++ jiffies_til_work = 0; ++ } else { ++ u64 diff = driver->non_atomic_work_jiffies_64 - now; ++ jiffies_til_work = ++ (u32)min_t(u64, diff, MAX_SCHEDULE_TIMEOUT); ++ } ++ } ++ ++ u32 jiffies_til_heartbeat = (u32)-1; ++ if (driver->tee_initialized) { ++ should_timeout = true; ++ if (driver->next_heartbeat_jiffies_64 < now) { ++ jiffies_til_heartbeat = 0; ++ } else { ++ u64 diff = driver->next_heartbeat_jiffies_64 - now; ++ jiffies_til_heartbeat = ++ (u32)min_t(u64, diff, MAX_SCHEDULE_TIMEOUT); ++ } ++ } ++ ++ if (!should_timeout) { ++ struct nvidia_ta_timeout timeout = { ++ .cause = NVIDIA_TA_TIMEOUT_NONE, ++ .duration_jiffies = MAX_SCHEDULE_TIMEOUT, ++ }; ++ return timeout; ++ } ++ ++ if (jiffies_til_work <= jiffies_til_heartbeat) { ++ struct nvidia_ta_timeout timeout = { ++ .cause = NVIDIA_TA_TIMEOUT_WORK_TO_DO, ++ .duration_jiffies = jiffies_til_work, ++ }; ++ return timeout; ++ } ++ ++ struct nvidia_ta_timeout timeout = { ++ .cause = NVIDIA_TA_TIMEOUT_HEARTBEAT, ++ .duration_jiffies = jiffies_til_heartbeat, ++ }; ++ return timeout; ++} ++ ++static int nvidia_ta_worker(void *data) ++{ ++ struct nvidia_ta_mctp_driver *driver = data; ++ ++ struct sk_buff *skb = NULL; ++ ++ for (;;) { ++ struct nvidia_ta_timeout timeout; ++ ++ // attempt to get a packet to send until we need to handle a non-atomic operation or heartbeat ++ while (!skb) { ++ timeout = nvidia_ta_get_next_timeout(driver); ++ if (timeout.cause != NVIDIA_TA_TIMEOUT_NONE && ++ timeout.duration_jiffies == 0) { ++ // already past a deadline, handle it without blocking ++ break; ++ } ++ bool timeout_expired = false; ++ if (timeout.cause == NVIDIA_TA_TIMEOUT_NONE) { ++ // wait forever ++ wait_event_interruptible( ++ driver->wq, ++ !skb_queue_empty(&driver->tx_queue) || ++ kthread_should_stop()); ++ } else { ++ // wait with timeout ++ const int wait_status = ++ wait_event_interruptible_timeout( ++ driver->wq, ++ !skb_queue_empty( ++ &driver->tx_queue) || ++ kthread_should_stop(), ++ timeout.duration_jiffies); ++ if (wait_status == 0) { ++ timeout_expired = true; ++ } ++ } ++ if (kthread_should_stop()) { ++ return 0; ++ } ++ if (timeout_expired) { ++ // hit timeout, break to handle delayed work or heartbeat ++ break; ++ } ++ ++ // got woken up before timeout, check for a packet ++ skb = skb_dequeue(&driver->tx_queue); ++ netif_start_queue(driver->netdev); ++ if (skb) { ++ break; ++ } ++ } ++ ++ if (!skb) { ++ // handle the cause of the timeout ++ switch (timeout.cause) { ++ case NVIDIA_TA_TIMEOUT_WORK_TO_DO: ++ nvidia_ta_write_to_tee( ++ driver, NULL, ++ TA_COMMAND_ID_HANDLE_NON_ATOMIC_OPERATION); ++ break; ++ ++ case NVIDIA_TA_TIMEOUT_HEARTBEAT: ++ send_heartbeat_command_to_tee(driver); ++ break; ++ ++ default: ++ LOG_WRN("unexpected read timeout cause: %d", ++ (int)timeout.cause); ++ break; ++ } ++ } else if (nvidia_ta_is_control_request(skb)) { ++ // handle control packet locally ++ const int ret = ++ nvidia_ta_handle_control_request(driver, skb); ++ if (ret != 0) { ++ LOG_ERR("nvidia_ta_handle_control_request failed, ret: %d", ++ ret); ++ } ++ } else { ++ // forward non-control packet ++ nvidia_ta_write_to_tee(driver, skb, ++ TA_COMMAND_ID_WRITE_PACKET); ++ } ++ ++ if (skb) { ++ kfree_skb(skb); ++ skb = NULL; ++ } ++ } ++ ++ return 0; ++} ++ ++void nvidia_vrot_netdev_setup(struct net_device *dev) ++{ ++ dev->type = ARPHRD_MCTP; ++ ++ // NOTE: MTU reconfigured outside of setup ++ dev->min_mtu = 68; ++ dev->max_mtu = 68; ++ dev->mtu = 68; ++ ++ dev->hard_header_len = 0; ++ dev->tx_queue_len = NVIDIA_VROT_QUEUE_SIZE; ++ dev->netdev_ops = &nvidia_vrot_nops; ++ ++ dev->addr_len = 0; ++} ++ ++/// @brief Initializes the endpoint driver. ++/// @param [in,out] driver Endpoint driver. driver->config is already init. ++/// @param [in] name The name to use for the network device and worker thread. ++/// @returns 0 on success, -error code on failure. ++static int ++nvidia_ta_mctp_init_endpoint(struct nvidia_ta_mctp_driver *const driver, ++ const char *name) ++{ ++ int result = -1; ++ ++ init_waitqueue_head(&driver->wq); ++ skb_queue_head_init(&driver->tx_queue); ++ ++ driver->non_atomic = false; ++ driver->non_atomic_work_jiffies_64 = 0; ++ driver->next_heartbeat_jiffies_64 = 0; ++ driver->tee_initialized = false; ++ ++ // create the mctp network device ++ driver->netdev = alloc_netdev(sizeof(struct nvidia_vrot_netdev_priv), ++ name, NET_NAME_PREDICTABLE, ++ nvidia_vrot_netdev_setup); ++ if (!driver->netdev) { ++ LOG_ERR("failed to allocate netdev"); ++ return -ENOMEM; ++ } ++ driver->netdev->min_mtu = driver->config.mtu; ++ driver->netdev->mtu = driver->config.mtu; ++ driver->netdev->max_mtu = driver->config.mtu; ++ struct nvidia_vrot_netdev_priv *priv = netdev_priv(driver->netdev); ++ priv->driver = driver; ++ result = mctp_register_netdev(driver->netdev, NULL, ++ MCTP_PHYS_BINDING_VENDOR); ++ if (result) { ++ free_netdev(driver->netdev); ++ driver->netdev = NULL; ++ LOG_ERR("failed to register netdev"); ++ return result; ++ } ++ ++ driver->worker_task = ++ kthread_run(nvidia_ta_worker, driver, "%s-worker", name); ++ if (IS_ERR(driver->worker_task)) { ++ LOG_ERR("failed to start worker thread"); ++ driver->worker_task = NULL; ++ return -EINVAL; ++ } ++ ++ return 0; ++} ++ ++static int nvidia_vrot_load_devicetree(struct platform_device *pdev, ++ struct nvidia_ta_mctp_config *config) ++{ ++ if (!pdev) { ++ LOG_ERR("platform_device pointer is NULL"); ++ return -EINVAL; ++ } ++ ++ const struct device_node *np = pdev->dev.of_node; ++ int ret = 0; ++ ++ if (!np || !config) { ++ LOG_ERR("Missing device tree node or config struct"); ++ return -EINVAL; ++ } ++ ++ // Parse nvidia,ta-uuid ++ const char *ta_uuid_str; ++ ret = of_property_read_string(np, "nvidia,ta-uuid", &ta_uuid_str); ++ if (ret) { ++ LOG_ERR("Failed to read nvidia,ta-uuid from device tree"); ++ return ret; ++ } ++ ret = uuid_parse(ta_uuid_str, &config->ta_uuid); ++ if (ret) { ++ LOG_ERR("Failed to parse ta_uuid string '%s'", ta_uuid_str); ++ return -EINVAL; ++ } ++ ++ // Parse nvidia,mctp-uuid ++ const char *mctp_uuid_str; ++ ret = of_property_read_string(np, "nvidia,mctp-uuid", &mctp_uuid_str); ++ if (ret) { ++ LOG_ERR("Failed to read nvidia,mctp-uuid from device tree"); ++ return ret; ++ } ++ ret = uuid_parse(mctp_uuid_str, &config->mctp_uuid); ++ if (ret) { ++ LOG_ERR("Failed to parse mctp_uuid string '%s'", mctp_uuid_str); ++ return -EINVAL; ++ } ++ ++ // Parse nvidia,mctp-mtu ++ ret = of_property_read_u32(np, "nvidia,mctp-mtu", &config->mtu); ++ if (ret) { ++ LOG_ERR("Failed to read nvidia,mctp-mtu from device tree"); ++ return ret; ++ } ++ if (config->mtu < 68 || config->mtu > 65536) { ++ LOG_ERR("Invalid nvidia,mctp-mtu value %u (must be between 68 and 65536)", ++ config->mtu); ++ return -EINVAL; ++ } ++ ++ // Parse nvidia,mctp-protocol-versions (array of u32) ++ const __be32 *prop; ++ int len; ++ prop = of_get_property(np, "nvidia,mctp-protocol-versions", &len); ++ if (!prop || len < 0) { ++ LOG_ERR("Failed to get nvidia,mctp-protocol-versions from device tree"); ++ return -EINVAL; ++ } ++ int num_entries = len / sizeof(u32); ++ if (num_entries % 2 != 0) { ++ LOG_ERR("nvidia,mctp-protocol-versions must have even number of entries (protocol/version pairs), got %d", ++ num_entries); ++ return -EINVAL; ++ } ++ config->protocols_size = num_entries / 2; ++ if (config->protocols_size > NVIDIA_VROT_MAX_PROTOCOLS) { ++ LOG_ERR("protocols_size (%u) exceeds max supported %d", ++ config->protocols_size, NVIDIA_VROT_MAX_PROTOCOLS); ++ return -EINVAL; ++ } ++ ++ for (u32 i = 0; i < config->protocols_size; ++i) { ++ const u32 protocol = be32_to_cpu(prop[i * 2]); ++ const u32 version = be32_to_cpu(prop[i * 2 + 1]); ++ if (protocol > 0xFF) { ++ LOG_ERR("protocol value 0x%x at index %u exceeds u8 range", ++ protocol, i); ++ return -EINVAL; ++ } ++ if (protocol == 0x00) { ++ LOG_ERR("protocol value 0x%x at index %u is reserved (control protocol)", ++ protocol, i); ++ return -EINVAL; ++ } ++ if (protocol == 0xFF) { ++ LOG_ERR("protocol value 0x%x at index %u is reserved (base version)", ++ protocol, i); ++ return -EINVAL; ++ } ++ config->protocols[i].protocol = (u8)protocol; ++ memcpy(config->protocols[i].version, &version, sizeof(version)); ++ } ++ ++ return 0; ++} ++ ++static int nvidia_vrot_probe(struct platform_device *pdev) ++{ ++ int ret = -1; ++ struct device *const dev = &pdev->dev; ++ dev_info(dev, "Probe starting for device: %s\n", dev_name(dev)); ++ ++ struct nvidia_ta_mctp_driver *const driver = ++ kzalloc(sizeof(struct nvidia_ta_mctp_driver), GFP_KERNEL); ++ if (!driver) { ++ dev_err(dev, "Failed to allocate memory for driver\n"); ++ return -ENOMEM; ++ } ++ platform_set_drvdata(pdev, driver); ++ ++ ret = nvidia_vrot_load_devicetree(pdev, &driver->config); ++ if (ret != 0) { ++ dev_err(dev, ++ "Failed to load device tree properties for device '%s' (devicetree path: %s), ret: %d\n", ++ dev_name(dev), of_node_full_name(dev->of_node), ret); ++ clean_driver(pdev); ++ return ret; ++ } ++ ++ ret = nvidia_ta_mctp_init_endpoint(driver, dev_name(dev)); ++ if (ret != 0) { ++ dev_err(dev, ++ "failed to init endpoint for device '%s' (devicetree path: %s), ret: %d\n", ++ dev_name(dev), of_node_full_name(dev->of_node), ret); ++ clean_driver(pdev); ++ return ret; ++ } ++ ++ dev_info(dev, "Probe successful for device: %s\n", dev_name(dev)); ++ return 0; ++} ++ ++static void nvidia_vrot_remove(struct platform_device *pdev) ++{ ++ dev_info(&pdev->dev, "Removing device: %s\n", dev_name(&pdev->dev)); ++ clean_driver(pdev); ++ dev_info(&pdev->dev, "Device removed: %s\n", dev_name(&pdev->dev)); ++} ++ ++static const struct of_device_id nvidia_vrot_match[] = { ++ { .compatible = "nvidia,optee,vrot" }, ++ {}, ++}; ++MODULE_DEVICE_TABLE(of, nvidia_vrot_match); ++ ++static struct platform_driver nvidia_vrot_driver = { ++ .probe = nvidia_vrot_probe, ++ .remove = nvidia_vrot_remove, ++ .driver = ++ { ++ .name = "nvidia-optee-vrot", ++ .of_match_table = nvidia_vrot_match, ++ }, ++}; ++ ++module_platform_driver(nvidia_vrot_driver); ++ ++MODULE_LICENSE("GPL"); ++MODULE_DESCRIPTION("NVIDIA VRoT MCTP Trusted Application Driver"); +diff --git a/drivers/net/mctp/nvidia_irot_ast27xx_msg_ns.h b/drivers/net/mctp/nvidia_irot_ast27xx_msg_ns.h +new file mode 100644 +index 000000000..3278dbe81 +--- /dev/null ++++ b/drivers/net/mctp/nvidia_irot_ast27xx_msg_ns.h +@@ -0,0 +1,151 @@ ++/// @file ++/// @brief Message types for the nvidia IRoT <-> A35 IPC connection. ++/// @details These messages are restricted to 32 bytes but can contain pointers. ++/// @note The messages are always padded to 32 bytes. For future compatibility ++/// all unused fields must be written zero and read ignored. ++ ++#pragma once ++ ++#ifdef __KERNEL__ ++#include ++typedef u32 nvidia_irot_message_u32; ++#else ++#include ++typedef uint32_t nvidia_irot_message_u32; ++#endif ++ ++/// @brief Command codes. ++/// @details Commands are even and responses are odd. ++/// See nvidia_irot_message_data for info on the data payload for each command. ++/// @note Zero is intentially not used. ++enum nvidia_irot_command_code { ++ /// @brief A ping. ++ /// @note In the future this command may be expanded to be used in feature ++ /// detection as it contains a large number of reserved bytes. ++ /// @note If multiple pings are in flight then the responder should respond ++ /// to them in order, but if any responses are dropped then prefer to drop ++ /// older ones. ++ nvidia_irot_cc_ping = 2, ++ /// @brief Response to the ping. ++ nvidia_irot_cc_ping_rsp, ++ ++ /// @brief Request for the IRoT to send buffer addresses. ++ nvidia_irot_cc_get_mctp_buffers, ++ /// @brief The requested buffer addresses. ++ /// @note This response should never change unless the system reboots. ++ nvidia_irot_cc_mctp_buffer, ++ ++ /// @brief A new MCTP message. ++ nvidia_irot_cc_mctp, ++ /// @brief Notification that the MCTP message has been processed and its ++ /// buffer is free to be used. ++ nvidia_irot_cc_mctp_done, ++}; ++ ++/// @brief Memory address in the A35's physical address space. ++/// @note Is encoded in two u32 to avoid alignment and padding issues. ++struct nvidia_irot_message_address { ++ /// @brief The low 32 bits of the address. ++ nvidia_irot_message_u32 low; ++ /// @brief The high 32 bits of the address. ++ nvidia_irot_message_u32 high; ++}; ++ ++/// @brief Memory region in the A35's physical address space. ++struct nvidia_irot_message_span { ++ /// @brief Start address. ++ struct nvidia_irot_message_address address; ++ /// @brief Size in bytes. ++ nvidia_irot_message_u32 size; ++}; ++ ++/// @brief Empty message. ++struct nvidia_irot_message_data_empty {}; ++ ++/// @brief Information about the shared buffers between the IRoT and A35. ++struct nvidia_irot_message_data_buffers { ++ /// @brief Used to compute the maximum transmission unit. ++ /// @details The read_mtu is min(mtu_limit, a35_read.size) and ++ /// the write_mtu is min(mtu_limit, a35_write.size). ++ nvidia_irot_message_u32 mtu_limit; ++ /// @brief Span for the A35 to read from. ++ struct nvidia_irot_message_span a35_read; ++ /// @brief Span for the A35 to write into. ++ struct nvidia_irot_message_span a35_write; ++}; ++ ++/// @brief MCTP packet message data. ++struct nvidia_irot_message_data_mctp { ++ /// @brief MCTP packet counter. ++ /// @details Must be echoed back in the response to the message. ++ /// Additionally if two messages are received back to back they ++ /// must be deduplicated but the response must still be sent. ++ nvidia_irot_message_u32 counter; ++ /// @brief Span of memory that contains the MCTP packet. ++ /// @details The span must be fully contained within the expected ++ /// region from the nvidia_irot_cc_mctp_buffer message. ++ /// If the packet is out of bounds the memory must not be accessed. ++ struct nvidia_irot_message_span packet; ++}; ++ ++/// @brief Data payload for IPC messages. ++union nvidia_irot_message_data { ++ /// @brief Raw arguments. ++ /// @details Used only for initializing and debugging. ++ nvidia_irot_message_u32 args[7]; ++ ++ /// @brief Empty payload. ++ /// @details Used in the message types: ++ /// - nvidia_irot_cc_get_mctp_buffers ++ struct nvidia_irot_message_data_empty empty; ++ ++ /// @brief A single value. ++ /// @details Used in the message types: ++ /// - nvidia_irot_cc_ping: for a value that is echoed back. ++ /// - nvidia_irot_cc_ping_rsp: for the value that is echoed back. ++ /// - nvidia_irot_cc_mctp_done: for the packet counter. ++ nvidia_irot_message_u32 value; ++ ++ /// @brief Shared buffer information. ++ /// @details Used in the message types: ++ /// - nvidia_irot_cc_mctp_buffer ++ struct nvidia_irot_message_data_buffers buffers; ++ ++ /// @brief Mctp packet. ++ /// @details Used in the message types: ++ /// - nvidia_irot_cc_mctp ++ struct nvidia_irot_message_data_mctp mctp; ++}; ++ ++/// @brief 32-byte IPC message type. ++struct nvidia_irot_message { ++ /// @brief The nvidia_irot_command_code. ++ /// @note Zero is used for empty locally, but should never be sent over IPC. ++ nvidia_irot_message_u32 command; ++ /// @brief The data payload. ++ union nvidia_irot_message_data data; ++}; ++ ++/// @brief Initializer for struct nvidia_irot_message. ++/// @details Zeros out all fields. ++#define NVIDIA_IROT_MESSAGE_INIT \ ++ { \ ++ .command = 0, .data = {.args = { 0 } } \ ++ } ++ ++#if defined(__cplusplus) ++#define NVIDIA_IROT_MESSAGE_STATIC_ASSERT(expr) static_assert(expr); ++#elif __STDC_VERSION__ >= 202311L ++#define NVIDIA_IROT_MESSAGE_STATIC_ASSERT(expr) static_assert(expr); ++#elif __STDC_VERSION__ >= 201112L ++#define NVIDIA_IROT_MESSAGE_STATIC_ASSERT(expr) _Static_assert(expr); ++#else ++#define NVIDIA_IROT_MESSAGE_STATIC_ASSERT(expr) ++#endif ++ ++// Ensure that our message struct is exactly 32 bytes. ++// Some APIs unconditionally copy the max 32 bytes size so both ++// undersized type and oversized types would be a problem. ++NVIDIA_IROT_MESSAGE_STATIC_ASSERT(sizeof(struct nvidia_irot_message) == 32) ++ ++#undef NVIDIA_IROT_MESSAGE_STATIC_ASSERT +diff --git a/drivers/soc/aspeed/aspeed-mctp.c b/drivers/soc/aspeed/aspeed-mctp.c +index b549013b7..3b37d969a 100644 +--- a/drivers/soc/aspeed/aspeed-mctp.c ++++ b/drivers/soc/aspeed/aspeed-mctp.c +@@ -145,7 +145,7 @@ + + /* HW buffer sizes */ + #define TX_PACKET_COUNT 48 +-#define RX_PACKET_COUNT 96 ++#define RX_PACKET_COUNT 1024 + #if (RX_PACKET_COUNT % 4 != 0) + #error The Rx buffer size should be 4-aligned. + #error 1.Make runaway wrap boundary can be determined in Ast2600 A1/A2. +@@ -155,8 +155,8 @@ + #define RX_MAX_PACKET_COUNT (RX_BUF_RD_PTR_MASK + 1) + + /* Per client packet cache sizes */ +-#define RX_RING_COUNT 64 +-#define TX_RING_COUNT 64 ++#define RX_RING_COUNT 1024 ++#define TX_RING_COUNT 512 + + /* PCIe Host Controller registers */ + #define ASPEED_PCIE_LINK 0x0c0 +@@ -2286,7 +2286,8 @@ static int aspeed_mctp_dma_init(struct aspeed_mctp *priv) + struct mctp_channel *tx = &priv->tx; + struct mctp_channel *rx = &priv->rx; + size_t alloc_size; +- int ret = -ENOMEM; ++ int ret; ++ bool use_reserved_mem = false; + + BUILD_BUG_ON(TX_PACKET_COUNT >= TX_MAX_PACKET_COUNT); + BUILD_BUG_ON(RX_PACKET_COUNT >= RX_MAX_PACKET_COUNT); +@@ -2298,17 +2299,21 @@ static int aspeed_mctp_dma_init(struct aspeed_mctp *priv) + } + + ret = of_reserved_mem_device_init(priv->dev); +- if (ret) { +- dev_err(priv->dev, "device does not have specific DMA pool: %d\n", +- ret); ++ if (ret && ret != -ENODEV) { ++ dev_err(priv->dev, "Failed to check reserved DMA pool: %d\n", ret); + return ret; + } + +- ret = devm_add_action_or_reset(priv->dev, aspeed_release_rmem, +- priv->dev); +- if (ret) +- return ret; ++ if (!ret) { ++ /* Reserved memory found, register cleanup action */ ++ use_reserved_mem = true; ++ ret = devm_add_action_or_reset(priv->dev, aspeed_release_rmem, ++ priv->dev); ++ if (ret) ++ return ret; ++ } + ++ dev_info(priv->dev, "%s DMA pool\n", use_reserved_mem ? "Reserved" : "Dynamic"); + alloc_size = PAGE_ALIGN(priv->rx_packet_count * priv->match_data->packet_unit_size); + rx->data.vaddr = + dma_alloc_coherent(priv->dev, alloc_size, &rx->data.dma_handle, GFP_KERNEL); +diff --git a/include/linux/i3c/mctp/i3c-mctp.h b/include/linux/i3c/mctp/i3c-mctp.h +new file mode 100644 +index 000000000..dd20750d7 +--- /dev/null ++++ b/include/linux/i3c/mctp/i3c-mctp.h +@@ -0,0 +1,50 @@ ++/* SPDX-License-Identifier: GPL-2.0 */ ++/* Copyright (C) 2022 Intel Corporation.*/ ++ ++#ifndef I3C_MCTP_H ++#define I3C_MCTP_H ++ ++#define I3C_MCTP_PACKET_SIZE 68 ++#define I3C_MCTP_PAYLOAD_SIZE 64 ++#define I3C_MCTP_HDR_SIZE 4 ++ ++/* PECI MCTP Intel VDM definitions */ ++#define MCTP_MSG_TYPE_VDM_PCI 0x7E ++#define MCTP_VDM_PCI_INTEL_VENDOR_ID 0x8086 ++#define MCTP_VDM_PCI_INTEL_PECI 0x2 ++ ++/* MCTP message header offsets */ ++#define MCTP_MSG_HDR_MSG_TYPE_OFFSET 0 ++#define MCTP_MSG_HDR_VENDOR_OFFSET 1 ++#define MCTP_MSG_HDR_OPCODE_OFFSET 4 ++ ++struct i3c_mctp_client; ++ ++struct mctp_protocol_hdr { ++ u8 ver; ++ u8 dest; ++ u8 src; ++ u8 flags_seq_tag; ++} __packed; ++ ++struct i3c_mctp_packet_data { ++ u8 protocol_hdr[I3C_MCTP_HDR_SIZE]; ++ u8 payload[I3C_MCTP_PAYLOAD_SIZE]; ++}; ++ ++struct i3c_mctp_packet { ++ struct i3c_mctp_packet_data data; ++ u32 size; ++}; ++ ++void *i3c_mctp_packet_alloc(gfp_t flags); ++void i3c_mctp_packet_free(void *packet); ++ ++int i3c_mctp_get_eid(struct i3c_mctp_client *client, u8 domain_id, u8 *eid); ++int i3c_mctp_send_packet(struct i3c_device *i3c, struct i3c_mctp_packet *tx_packet); ++struct i3c_mctp_packet *i3c_mctp_receive_packet(struct i3c_mctp_client *client, ++ unsigned long timeout); ++struct i3c_mctp_client *i3c_mctp_add_peci_client(struct i3c_device *i3c); ++void i3c_mctp_remove_peci_client(struct i3c_mctp_client *client); ++ ++#endif /* I3C_MCTP_H */ +diff --git a/include/linux/usb/mctp-usb.h b/include/linux/usb/mctp-usb.h +new file mode 100644 +index 000000000..880ff54ac +--- /dev/null ++++ b/include/linux/usb/mctp-usb.h +@@ -0,0 +1,27 @@ ++/* SPDX-License-Identifier: GPL-2.0+ */ ++/* ++ * mctp-usb.h - MCTP USB transport binding: common definitions. ++ * ++ * These are shared between the host and gadget drivers. ++ * ++ * Copyright (C) 2024 Code Construct Pty Ltd ++ */ ++ ++#ifndef __LINUX_USB_MCTP_USB_H ++#define __LINUX_USB_MCTP_USB_H ++ ++#include ++ ++struct mctp_usb_hdr { ++ __be16 id; ++ __u8 rsvd; ++ __u8 len; ++} __packed; ++ ++#define MCTP_USB_XFER_SIZE 512 ++#define MCTP_USB_BTU 68 ++#define MCTP_USB_MTU_MIN MCTP_USB_BTU ++#define MCTP_USB_MTU_MAX (U8_MAX - sizeof(struct mctp_usb_hdr)) ++#define MCTP_USB_DMTF_ID 0x1ab4 ++ ++#endif /* __LINUX_USB_MCTP_USB_H */ +diff --git a/include/net/mctp.h b/include/net/mctp.h +index 28d59ae94..51f1d5a4c 100644 +--- a/include/net/mctp.h ++++ b/include/net/mctp.h +@@ -38,6 +38,8 @@ struct mctp_hdr { + + #define MCTP_INITIAL_DEFAULT_NET 1 + ++#define MCTP_DEFAULT_LIFETIME (6 * CONFIG_HZ) ++ + static inline bool mctp_address_unicast(mctp_eid_t eid) + { + return eid >= 8 && eid < 255; +@@ -64,6 +66,21 @@ static inline struct mctp_hdr *mctp_hdr(struct sk_buff *skb) + } + + /* socket implementation */ ++/* Pending error context for deferred error reporting via workqueue */ ++struct mctp_pending_error { ++ struct list_head list; ++ struct sk_buff *skb; /* First fragment SKB (for addressing) */ ++ struct sock *sk; /* Socket for error reporting (refcounted) */ ++ int error_code; ++ struct net_device *dev; ++ u8 direction; ++ u8 binding; ++ /* For RX timeout: original request payload from key */ ++ u8 orig_msg_type; ++ u16 orig_payload_len; ++ u8 orig_payload[32]; /* First 32 bytes of original request */ ++}; ++ + struct mctp_sock { + struct sock sk; + +@@ -84,6 +101,14 @@ struct mctp_sock { + * tag, and any netdev state for a request/response pairing + */ + struct timer_list key_expiry; ++ ++ /* Error queue control */ ++ bool enable_errqueue; ++ ++ /* Deferred error reporting (to avoid deadlock in timer context) */ ++ struct work_struct error_report_work; ++ struct list_head pending_errors; ++ spinlock_t error_queue_lock; + }; + + /* Key for matching incoming packets to sockets or reassembly contexts. +@@ -178,15 +203,23 @@ struct mctp_sk_key { + * is used. + */ + bool manual_alloc; ++ ++ /* Original message header for error reporting on fragmented messages. ++ * Captured from first fragment (SOM=1) to ensure errors on middle/end ++ * fragments can still report original header to application. ++ */ ++ u8 orig_msg_type; /* Message type (PLDM, SPDM, etc) */ ++ u16 orig_payload_len; /* Captured payload length */ ++ u8 orig_payload[32]; /* First 32 bytes of original message */ + }; + + struct mctp_skb_cb { + unsigned int magic; + unsigned int net; +- int ifindex; /* extended/direct addressing if set */ +- mctp_eid_t src; ++ /* fields below provide extended addressing for ingress to recvmsg() */ ++ int ifindex; + unsigned char halen; +- unsigned char haddr[MAX_ADDR_LEN]; ++ unsigned char haddr[MAX_ADDR_LEN]; + }; + + /* skb control-block accessors with a little extra debugging for initial +@@ -222,6 +255,8 @@ struct mctp_flow { + struct mctp_sk_key *key; + }; + ++struct mctp_dst; ++ + /* Route definition. + * + * These are held in the pernet->mctp.routes list, with RCU protection for +@@ -229,16 +264,25 @@ struct mctp_flow { + * dropped on NETDEV_UNREGISTER events. + * + * Updates to the route table are performed under rtnl; all reads under RCU, +- * so routes cannot be referenced over a RCU grace period. Specifically: A +- * caller cannot block between mctp_route_lookup and mctp_route_release() ++ * so routes cannot be referenced over a RCU grace period. + */ + struct mctp_route { + mctp_eid_t min, max; + + unsigned char type; ++ + unsigned int mtu; +- struct mctp_dev *dev; +- int (*output)(struct mctp_route *route, ++ ++ enum { ++ MCTP_ROUTE_DIRECT, ++ MCTP_ROUTE_GATEWAY, ++ } dst_type; ++ union { ++ struct mctp_dev *dev; ++ struct mctp_fq_addr gateway; ++ }; ++ ++ int (*output)(struct mctp_dst *dst, + struct sk_buff *skb); + + struct list_head list; +@@ -246,19 +290,43 @@ struct mctp_route { + struct rcu_head rcu; + }; + ++/* Route lookup result: dst. Represents the results of a routing decision, ++ * but is only held over the individual routing operation. ++ * ++ * Will typically be stored on the caller stack, and must be released after ++ * usage. ++ */ ++struct mctp_dst { ++ struct mctp_dev *dev; ++ unsigned int mtu; ++ mctp_eid_t nexthop; ++ ++ /* set for direct addressing */ ++ unsigned char halen; ++ unsigned char haddr[MAX_ADDR_LEN]; ++ ++ int (*output)(struct mctp_dst *dst, struct sk_buff *skb); ++}; ++ ++int mctp_dst_from_extaddr(struct mctp_dst *dst, struct net *net, int ifindex, ++ unsigned char halen, const unsigned char *haddr); ++ + /* route interfaces */ +-struct mctp_route *mctp_route_lookup(struct net *net, unsigned int dnet, +- mctp_eid_t daddr); ++int mctp_route_lookup(struct net *net, unsigned int dnet, ++ mctp_eid_t daddr, struct mctp_dst *dst); ++ ++void mctp_dst_release(struct mctp_dst *dst); + + /* always takes ownership of skb */ +-int mctp_local_output(struct sock *sk, struct mctp_route *rt, ++int mctp_local_output(struct sock *sk, struct mctp_dst *dst, + struct sk_buff *skb, mctp_eid_t daddr, u8 req_tag); + + void mctp_key_unref(struct mctp_sk_key *key); + struct mctp_sk_key *mctp_alloc_local_tag(struct mctp_sock *msk, + unsigned int netid, + mctp_eid_t local, mctp_eid_t peer, +- bool manual, u8 *tagp); ++ bool manual, u8 *tagp, ++ unsigned long lifetime); + + /* routing <--> device interface */ + unsigned int mctp_default_net(struct net *net); +@@ -298,4 +366,34 @@ void mctp_routes_exit(void); + int mctp_device_init(void); + void mctp_device_exit(void); + ++/* Error queue support */ ++u8 mctp_get_binding_type(struct net_device *dev); ++void mctp_queue_error(struct sock *sk, struct sk_buff *skb, ++ int error_code, struct net_device *dev, u8 direction, u8 binding, ++ struct mctp_sk_key *key); ++struct sock *mctp_lookup_sock_by_key(struct sk_buff *skb, struct net_device *dev, ++ struct mctp_sk_key **found_key); ++struct sock *mctp_lookup_sock_for_error(struct sk_buff *skb, ++ struct net_device *dev, ++ struct mctp_sk_key *key, ++ struct mctp_sk_key **found_key); ++ ++/* MCTP IDs and Codes from DMTF specification ++ * "DSP0239 Management Component Transport Protocol (MCTP) IDs and Codes" ++ * https://www.dmtf.org/sites/default/files/standards/documents/DSP0239_1.11.1.pdf ++ */ ++enum mctp_phys_binding { ++ MCTP_PHYS_BINDING_UNSPEC = 0x00, ++ MCTP_PHYS_BINDING_SMBUS = 0x01, ++ MCTP_PHYS_BINDING_PCIE_VDM = 0x02, ++ MCTP_PHYS_BINDING_USB = 0x03, ++ MCTP_PHYS_BINDING_KCS = 0x04, ++ MCTP_PHYS_BINDING_SERIAL = 0x05, ++ MCTP_PHYS_BINDING_I3C = 0x06, ++ MCTP_PHYS_BINDING_MMBI = 0x07, ++ MCTP_PHYS_BINDING_PCC = 0x08, ++ MCTP_PHYS_BINDING_UCIE = 0x09, ++ MCTP_PHYS_BINDING_VENDOR = 0xFF, ++}; ++ + #endif /* __NET_MCTP_H */ +diff --git a/include/net/mctpdevice.h b/include/net/mctpdevice.h +index 5c0d04b5c..a3cb57624 100644 +--- a/include/net/mctpdevice.h ++++ b/include/net/mctpdevice.h +@@ -16,12 +16,31 @@ + + struct mctp_sk_key; + ++#define MCTP_NF_TRACK_BUCKETS 64 ++#define MCTP_NF_TRACK_MAX 1024 ++ ++struct mctp_nf_track_entry { ++ struct hlist_node node; ++ mctp_eid_t src; ++ mctp_eid_t dst; ++ u8 tag; ++ u8 verdict; ++ unsigned long expires; ++}; ++ ++struct mctp_nf_track_table { ++ struct hlist_head buckets[MCTP_NF_TRACK_BUCKETS]; ++ spinlock_t lock; ++ unsigned int count; ++}; ++ + struct mctp_dev { + struct net_device *dev; + + refcount_t refs; + + unsigned int net; ++ enum mctp_phys_binding binding; + + const struct mctp_netdev_ops *ops; + +@@ -30,12 +49,27 @@ struct mctp_dev { + size_t num_addrs; + spinlock_t addrs_lock; + ++ unsigned long key_lifetime; ++ ++ /* TX batching support - set by transport drivers */ ++ bool tx_batching_enabled; ++ unsigned int tx_batch_hdr_len; /* per-packet header overhead */ ++ unsigned int tx_batch_max_xfer; /* max batch transfer size */ ++ ++ /* MCTP netdev netfilter fragment tracking (per-netdev) */ ++ struct mctp_nf_track_table nf_track; ++ + struct rcu_head rcu; + }; + + struct mctp_netdev_ops { + void (*release_flow)(struct mctp_dev *dev, + struct mctp_sk_key *key); ++ /* Called during batch packing to fill in the transport header. ++ * @hdr: pointer to reserved space where header should be written ++ * @pkt_len: total packet length (including this header) ++ */ ++ void (*fill_batch_hdr)(void *hdr, unsigned int pkt_len); + }; + + #define MCTP_INITIAL_DEFAULT_NET 1 +@@ -44,7 +78,8 @@ struct mctp_dev *mctp_dev_get_rtnl(const struct net_device *dev); + struct mctp_dev *__mctp_dev_get(const struct net_device *dev); + + int mctp_register_netdev(struct net_device *dev, +- const struct mctp_netdev_ops *ops); ++ const struct mctp_netdev_ops *ops, ++ enum mctp_phys_binding binding); + void mctp_unregister_netdev(struct net_device *dev); + + void mctp_dev_hold(struct mctp_dev *mdev); +@@ -53,4 +88,6 @@ void mctp_dev_put(struct mctp_dev *mdev); + void mctp_dev_set_key(struct mctp_dev *dev, struct mctp_sk_key *key); + void mctp_dev_release_key(struct mctp_dev *dev, struct mctp_sk_key *key); + ++void mctp_dev_set_timeout(struct net_device *dev, unsigned int timeout_ms); ++ + #endif /* __NET_MCTPDEVICE_H */ +diff --git a/include/trace/events/mctp.h b/include/trace/events/mctp.h +index 165cf25f7..1d371a76c 100644 +--- a/include/trace/events/mctp.h ++++ b/include/trace/events/mctp.h +@@ -73,6 +73,461 @@ TRACE_EVENT(mctp_key_release, + ) + ); + ++TRACE_EVENT(mctp_rx_packet, ++ TP_PROTO(struct sk_buff *skb), ++ TP_ARGS(skb), ++ TP_STRUCT__entry( ++ __field(u32, len) ++ __field(u8, src) ++ __field(u8, dest) ++ __field(u8, flags_seq_tag) ++ __array(u8, data, 32) ++ ), ++ TP_fast_assign( ++ struct mctp_hdr *mh = skb->len >= sizeof(*mh) ? ++ mctp_hdr(skb) : NULL; ++ __entry->len = skb->len; ++ __entry->src = mh ? mh->src : 0; ++ __entry->dest = mh ? mh->dest : 0; ++ __entry->flags_seq_tag = mh ? mh->flags_seq_tag : 0; ++ memcpy(__entry->data, skb->data, min_t(u32, skb->len, 32)); ++ ), ++ TP_printk("RX: src=%02x dst=%02x flags=%02x len=%u data=%s", ++ __entry->src, __entry->dest, __entry->flags_seq_tag, ++ __entry->len, ++ __print_hex(__entry->data, min_t(u32, __entry->len, 32)) ++ ) ++); ++ ++TRACE_EVENT(mctp_rx_socket, ++ TP_PROTO(struct sk_buff *skb, int rc), ++ TP_ARGS(skb, rc), ++ TP_STRUCT__entry( ++ __field(u32, len) ++ __field(int, rc) ++ __array(u8, data, 32) ++ ), ++ TP_fast_assign( ++ __entry->len = skb->len; ++ __entry->rc = rc; ++ memcpy(__entry->data, skb->data, min_t(u32, skb->len, 32)); ++ ), ++ TP_printk("RX_SOCKET: len=%u rc=%d data=%s", ++ __entry->len, __entry->rc, ++ __print_hex(__entry->data, min_t(u32, __entry->len, 32)) ++ ) ++); ++ ++TRACE_EVENT(mctp_tx_packet, ++ TP_PROTO(struct sk_buff *skb), ++ TP_ARGS(skb), ++ TP_STRUCT__entry( ++ __field(u32, len) ++ __array(u8, data, 32) ++ ), ++ TP_fast_assign( ++ __entry->len = skb->len; ++ memcpy(__entry->data, skb->data, min_t(u32, skb->len, 32)); ++ ), ++ TP_printk("TX: len=%u data=%s", ++ __entry->len, ++ __print_hex(__entry->data, min_t(u32, __entry->len, 32)) ++ ) ++); ++ ++TRACE_EVENT(mctp_drop_packet, ++ TP_PROTO(struct sk_buff *skb, const char *reason), ++ TP_ARGS(skb, reason), ++ TP_STRUCT__entry( ++ __field(u32, len) ++ __field(u8, src) ++ __field(u8, dest) ++ __string(reason, reason) ++ __array(u8, data, 16) ++ ), ++ TP_fast_assign( ++ struct mctp_hdr *mh = skb->len >= sizeof(*mh) ? mctp_hdr(skb) : NULL; ++ __entry->len = skb->len; ++ __entry->src = mh ? mh->src : 0; ++ __entry->dest = mh ? mh->dest : 0; ++ __assign_str(reason); ++ memcpy(__entry->data, skb->data, min_t(u32, skb->len, 16)); ++ ), ++ TP_printk("DROP at %s: src=%02x dst=%02x len=%u data=%s", ++ __get_str(reason), __entry->src, __entry->dest, __entry->len, ++ __print_hex(__entry->data, min_t(u32, __entry->len, 16)) ++ ) ++); ++ ++TRACE_EVENT(mctp_device_register, ++ TP_PROTO(const struct net_device *dev, unsigned int net_id), ++ TP_ARGS(dev, net_id), ++ TP_STRUCT__entry( ++ __string(name, dev->name) ++ __field(int, ifindex) ++ __field(unsigned int, net_id) ++ ), ++ TP_fast_assign( ++ __assign_str(name); ++ __entry->ifindex = dev->ifindex; ++ __entry->net_id = net_id; ++ ), ++ TP_printk("dev=%s ifindex=%d net=%u", ++ __get_str(name), __entry->ifindex, __entry->net_id ++ ) ++); ++ ++TRACE_EVENT(mctp_device_unregister, ++ TP_PROTO(const struct net_device *dev), ++ TP_ARGS(dev), ++ TP_STRUCT__entry( ++ __string(name, dev->name) ++ __field(int, ifindex) ++ ), ++ TP_fast_assign( ++ __assign_str(name); ++ __entry->ifindex = dev->ifindex; ++ ), ++ TP_printk("dev=%s ifindex=%d", ++ __get_str(name), __entry->ifindex ++ ) ++); ++ ++TRACE_EVENT(mctp_address_add, ++ TP_PROTO(const struct net_device *dev, u8 addr), ++ TP_ARGS(dev, addr), ++ TP_STRUCT__entry( ++ __string(name, dev->name) ++ __field(int, ifindex) ++ __field(u8, addr) ++ ), ++ TP_fast_assign( ++ __assign_str(name); ++ __entry->ifindex = dev->ifindex; ++ __entry->addr = addr; ++ ), ++ TP_printk("dev=%s ifindex=%d addr=%02x", ++ __get_str(name), __entry->ifindex, __entry->addr ++ ) ++); ++ ++TRACE_EVENT(mctp_address_del, ++ TP_PROTO(const struct net_device *dev, u8 addr), ++ TP_ARGS(dev, addr), ++ TP_STRUCT__entry( ++ __string(name, dev->name) ++ __field(int, ifindex) ++ __field(u8, addr) ++ ), ++ TP_fast_assign( ++ __assign_str(name); ++ __entry->ifindex = dev->ifindex; ++ __entry->addr = addr; ++ ), ++ TP_printk("dev=%s ifindex=%d addr=%02x", ++ __get_str(name), __entry->ifindex, __entry->addr ++ ) ++); ++ ++TRACE_EVENT(mctp_neighbor_add, ++ TP_PROTO(const struct net_device *dev, u8 eid, const void *lladdr, size_t len), ++ TP_ARGS(dev, eid, lladdr, len), ++ TP_STRUCT__entry( ++ __string(name, dev->name) ++ __field(int, ifindex) ++ __field(u8, eid) ++ __field(size_t, lladdr_len) ++ __array(u8, lladdr, MAX_ADDR_LEN) ++ ), ++ TP_fast_assign( ++ __assign_str(name); ++ __entry->ifindex = dev->ifindex; ++ __entry->eid = eid; ++ __entry->lladdr_len = len; ++ memcpy(__entry->lladdr, lladdr, min_t(size_t, len, MAX_ADDR_LEN)); ++ ), ++ TP_printk("dev=%s ifindex=%d eid=%02x lladdr=%s", ++ __get_str(name), __entry->ifindex, __entry->eid, ++ __print_hex(__entry->lladdr, min_t(size_t, __entry->lladdr_len, MAX_ADDR_LEN)) ++ ) ++); ++ ++TRACE_EVENT(mctp_neighbor_del, ++ TP_PROTO(const struct net_device *dev, u8 eid), ++ TP_ARGS(dev, eid), ++ TP_STRUCT__entry( ++ __string(name, dev->name) ++ __field(int, ifindex) ++ __field(u8, eid) ++ ), ++ TP_fast_assign( ++ __assign_str(name); ++ __entry->ifindex = dev->ifindex; ++ __entry->eid = eid; ++ ), ++ TP_printk("dev=%s ifindex=%d eid=%02x", ++ __get_str(name), __entry->ifindex, __entry->eid ++ ) ++); ++ ++TRACE_EVENT(mctp_neighbor_lookup, ++ TP_PROTO(const struct net_device *dev, u8 eid, int rc), ++ TP_ARGS(dev, eid, rc), ++ TP_STRUCT__entry( ++ __string(name, dev->name) ++ __field(int, ifindex) ++ __field(u8, eid) ++ __field(int, rc) ++ ), ++ TP_fast_assign( ++ __assign_str(name); ++ __entry->ifindex = dev->ifindex; ++ __entry->eid = eid; ++ __entry->rc = rc; ++ ), ++ TP_printk("dev=%s ifindex=%d eid=%02x rc=%d", ++ __get_str(name), __entry->ifindex, __entry->eid, __entry->rc ++ ) ++); ++ ++TRACE_EVENT(mctp_route_add, ++ TP_PROTO(const struct net_device *dev, u8 daddr_start, u8 daddr_extent, ++ unsigned int mtu), ++ TP_ARGS(dev, daddr_start, daddr_extent, mtu), ++ TP_STRUCT__entry( ++ __string(name, dev->name) ++ __field(int, ifindex) ++ __field(u8, daddr_start) ++ __field(u8, daddr_extent) ++ __field(unsigned int, mtu) ++ ), ++ TP_fast_assign( ++ __assign_str(name); ++ __entry->ifindex = dev->ifindex; ++ __entry->daddr_start = daddr_start; ++ __entry->daddr_extent = daddr_extent; ++ __entry->mtu = mtu; ++ ), ++ TP_printk("dev=%s ifindex=%d daddr=%02x-%02x mtu=%u", ++ __get_str(name), __entry->ifindex, __entry->daddr_start, ++ __entry->daddr_start + __entry->daddr_extent, __entry->mtu ++ ) ++); ++ ++TRACE_EVENT(mctp_route_del, ++ TP_PROTO(const struct net_device *dev, u8 daddr_start, u8 daddr_extent), ++ TP_ARGS(dev, daddr_start, daddr_extent), ++ TP_STRUCT__entry( ++ __string(name, dev->name) ++ __field(int, ifindex) ++ __field(u8, daddr_start) ++ __field(u8, daddr_extent) ++ ), ++ TP_fast_assign( ++ __assign_str(name); ++ __entry->ifindex = dev->ifindex; ++ __entry->daddr_start = daddr_start; ++ __entry->daddr_extent = daddr_extent; ++ ), ++ TP_printk("dev=%s ifindex=%d daddr=%02x-%02x", ++ __get_str(name), __entry->ifindex, __entry->daddr_start, ++ __entry->daddr_start + __entry->daddr_extent ++ ) ++); ++ ++TRACE_EVENT(mctp_route_lookup, ++ TP_PROTO(u8 daddr, const struct net_device *dev, bool found), ++ TP_ARGS(daddr, dev, found), ++ TP_STRUCT__entry( ++ __field(u8, daddr) ++ __string(name, dev ? dev->name : "none") ++ __field(int, ifindex) ++ __field(bool, found) ++ ), ++ TP_fast_assign( ++ __entry->daddr = daddr; ++ __assign_str(name); ++ __entry->ifindex = dev ? dev->ifindex : 0; ++ __entry->found = found; ++ ), ++ TP_printk("daddr=%02x dev=%s ifindex=%d found=%d", ++ __entry->daddr, __get_str(name), __entry->ifindex, __entry->found ++ ) ++); ++ ++TRACE_EVENT(mctp_route_output, ++ TP_PROTO(struct sk_buff *skb, const struct net_device *dev), ++ TP_ARGS(skb, dev), ++ TP_STRUCT__entry( ++ __field(u32, len) ++ __field(u8, src) ++ __field(u8, dest) ++ __string(name, dev->name) ++ __field(int, ifindex) ++ ), ++ TP_fast_assign( ++ struct mctp_hdr *mh = skb->len >= sizeof(*mh) ? mctp_hdr(skb) : NULL; ++ __entry->len = skb->len; ++ __entry->src = mh ? mh->src : 0; ++ __entry->dest = mh ? mh->dest : 0; ++ __assign_str(name); ++ __entry->ifindex = dev->ifindex; ++ ), ++ TP_printk("dev=%s ifindex=%d src=%02x dst=%02x len=%u", ++ __get_str(name), __entry->ifindex, __entry->src, __entry->dest, __entry->len ++ ) ++); ++ ++TRACE_EVENT(mctp_local_output, ++ TP_PROTO(u8 src, u8 dest, u8 tag, size_t len), ++ TP_ARGS(src, dest, tag, len), ++ TP_STRUCT__entry( ++ __field(u8, src) ++ __field(u8, dest) ++ __field(u8, tag) ++ __field(size_t, len) ++ ), ++ TP_fast_assign( ++ __entry->src = src; ++ __entry->dest = dest; ++ __entry->tag = tag; ++ __entry->len = len; ++ ), ++ TP_printk("src=%02x dst=%02x tag=%02x len=%zu", ++ __entry->src, __entry->dest, __entry->tag, __entry->len ++ ) ++); ++ ++TRACE_EVENT(mctp_fragment, ++ TP_PROTO(u8 src, u8 dest, u8 seq, size_t frag_len, size_t total_len), ++ TP_ARGS(src, dest, seq, frag_len, total_len), ++ TP_STRUCT__entry( ++ __field(u8, src) ++ __field(u8, dest) ++ __field(u8, seq) ++ __field(size_t, frag_len) ++ __field(size_t, total_len) ++ ), ++ TP_fast_assign( ++ __entry->src = src; ++ __entry->dest = dest; ++ __entry->seq = seq; ++ __entry->frag_len = frag_len; ++ __entry->total_len = total_len; ++ ), ++ TP_printk("src=%02x dst=%02x seq=%u frag_len=%zu total_len=%zu", ++ __entry->src, __entry->dest, __entry->seq, __entry->frag_len, __entry->total_len ++ ) ++); ++ ++TRACE_EVENT(mctp_reassemble_start, ++ TP_PROTO(u8 src, u8 dest, u8 seq), ++ TP_ARGS(src, dest, seq), ++ TP_STRUCT__entry( ++ __field(u8, src) ++ __field(u8, dest) ++ __field(u8, seq) ++ ), ++ TP_fast_assign( ++ __entry->src = src; ++ __entry->dest = dest; ++ __entry->seq = seq; ++ ), ++ TP_printk("src=%02x dst=%02x seq=%u", ++ __entry->src, __entry->dest, __entry->seq ++ ) ++); ++ ++TRACE_EVENT(mctp_reassemble_finish, ++ TP_PROTO(u8 src, u8 dest, size_t total_len), ++ TP_ARGS(src, dest, total_len), ++ TP_STRUCT__entry( ++ __field(u8, src) ++ __field(u8, dest) ++ __field(size_t, total_len) ++ ), ++ TP_fast_assign( ++ __entry->src = src; ++ __entry->dest = dest; ++ __entry->total_len = total_len; ++ ), ++ TP_printk("src=%02x dst=%02x total_len=%zu", ++ __entry->src, __entry->dest, __entry->total_len ++ ) ++); ++ ++TRACE_EVENT(mctp_transport_tx, ++ TP_PROTO(const char *transport, const struct net_device *dev, ++ u8 dest_addr, size_t len), ++ TP_ARGS(transport, dev, dest_addr, len), ++ TP_STRUCT__entry( ++ __string(transport, transport) ++ __string(name, dev->name) ++ __field(int, ifindex) ++ __field(u8, dest_addr) ++ __field(size_t, len) ++ ), ++ TP_fast_assign( ++ __assign_str(transport); ++ __assign_str(name); ++ __entry->ifindex = dev->ifindex; ++ __entry->dest_addr = dest_addr; ++ __entry->len = len; ++ ), ++ TP_printk("%s dev=%s ifindex=%d dest=%02x len=%zu", ++ __get_str(transport), __get_str(name), __entry->ifindex, ++ __entry->dest_addr, __entry->len ++ ) ++); ++ ++TRACE_EVENT(mctp_transport_rx, ++ TP_PROTO(const char *transport, const struct net_device *dev, ++ u8 src_addr, size_t len), ++ TP_ARGS(transport, dev, src_addr, len), ++ TP_STRUCT__entry( ++ __string(transport, transport) ++ __string(name, dev->name) ++ __field(int, ifindex) ++ __field(u8, src_addr) ++ __field(size_t, len) ++ ), ++ TP_fast_assign( ++ __assign_str(transport); ++ __assign_str(name); ++ __entry->ifindex = dev->ifindex; ++ __entry->src_addr = src_addr; ++ __entry->len = len; ++ ), ++ TP_printk("%s dev=%s ifindex=%d src=%02x len=%zu", ++ __get_str(transport), __get_str(name), __entry->ifindex, ++ __entry->src_addr, __entry->len ++ ) ++); ++ ++TRACE_EVENT(mctp_transport_error, ++ TP_PROTO(const char *transport, const struct net_device *dev, ++ const char *error, int rc), ++ TP_ARGS(transport, dev, error, rc), ++ TP_STRUCT__entry( ++ __string(transport, transport) ++ __string(name, dev->name) ++ __field(int, ifindex) ++ __string(error, error) ++ __field(int, rc) ++ ), ++ TP_fast_assign( ++ __assign_str(transport); ++ __assign_str(name); ++ __entry->ifindex = dev->ifindex; ++ __assign_str(error); ++ __entry->rc = rc; ++ ), ++ TP_printk("%s dev=%s ifindex=%d error=%s rc=%d", ++ __get_str(transport), __get_str(name), __entry->ifindex, ++ __get_str(error), __entry->rc ++ ) ++); ++ + #endif + + #include +diff --git a/include/uapi/linux/if_link.h b/include/uapi/linux/if_link.h +index 2acc7687e..9e76f8e59 100644 +--- a/include/uapi/linux/if_link.h ++++ b/include/uapi/linux/if_link.h +@@ -1957,6 +1957,7 @@ struct ifla_rmnet_flags { + enum { + IFLA_MCTP_UNSPEC, + IFLA_MCTP_NET, ++ IFLA_MCTP_PHYS_BINDING, + __IFLA_MCTP_MAX, + }; + +diff --git a/include/uapi/linux/mctp.h b/include/uapi/linux/mctp.h +index e1db65df9..f15646c95 100644 +--- a/include/uapi/linux/mctp.h ++++ b/include/uapi/linux/mctp.h +@@ -37,6 +37,14 @@ struct sockaddr_mctp_ext { + __u8 smctp_haddr[MAX_ADDR_LEN]; + }; + ++/* A "fully qualified" MCTP address, which includes the system-local network ID, ++ * required to uniquely resolve a routable EID. ++ */ ++struct mctp_fq_addr { ++ unsigned int net; ++ mctp_eid_t eid; ++}; ++ + #define MCTP_NET_ANY 0x0 + + #define MCTP_ADDR_NULL 0x00 +@@ -47,6 +55,7 @@ struct sockaddr_mctp_ext { + #define MCTP_TAG_PREALLOC 0x10 + + #define MCTP_OPT_ADDR_EXT 1 ++#define MCTP_OPT_ENABLE_ERRQUEUE 2 + + #define SIOCMCTPALLOCTAG (SIOCPROTOPRIVATE + 0) + #define SIOCMCTPDROPTAG (SIOCPROTOPRIVATE + 1) +@@ -97,4 +106,70 @@ struct mctp_ioc_tag_ctl2 { + + }; + ++/* ++ * MCTP Error Queue Support ++ * For receiving asynchronous errors via recvmsg(MSG_ERRQUEUE) ++ */ ++ ++#define MCTP_ERROR_PAYLOAD_SIZE 32 /* Capture first 32 bytes of payload */ ++ ++/* Control message type for reading errors */ ++#define MCTP_RECVERR 1 ++ ++/* Direction values */ ++#define MCTP_DIR_TX 0 ++#define MCTP_DIR_RX 1 ++ ++/** ++ * struct mctp_error - MCTP error information for applications ++ * ++ * This structure is returned to applications via recvmsg(MSG_ERRQUEUE). ++ * Contains everything needed to identify and handle binding layer errors. ++ * ++ * @error_code: Error number (ETIMEDOUT, EPIPE, EPROTO, etc.) ++ * @direction: 0=TX, 1=RX ++ * @binding: DMTF binding type (1=SMBus, 2=PCIe VDM, 3=USB, 4=KCS, 5=Serial, 6=I3C) ++ * @src_eid: Source EID ++ * @dest_eid: Destination EID ++ * @tag: MCTP tag value (0-7) ++ * @msg_type: MCTP message type (0x01=PLDM, 0x05=SPDM, etc.) ++ * @timestamp_ns: When error occurred (nanoseconds since boot) ++ * @payload_len: Length of captured payload ++ * @payload: First N bytes of message payload (includes protocol headers) ++ * ++ * For PLDM messages, payload contains: ++ * payload[0] = Instance ID byte (bits 4-0 = instance ID) ++ * payload[1] = PLDM Type byte (bits 5-0 = type: 2=T2, 5=T5) ++ * payload[2] = Command code ++ * ++ * For SPDM messages, payload contains: ++ * payload[0] = SPDM version ++ * payload[1] = Request/Response code ++ * payload[2+] = Parameters and session context ++ */ ++struct mctp_error { ++ /* Error Information */ ++ __u32 error_code; /* errno value */ ++ __u8 direction; /* MCTP_DIR_TX or MCTP_DIR_RX */ ++ __u8 binding; /* enum mctp_phys_binding */ ++ __u16 reserved1; ++ ++ /* MCTP Addressing */ ++ __u8 src_eid; /* Source EID */ ++ __u8 dest_eid; /* Destination EID */ ++ __u8 tag; /* MCTP tag (0-7) */ ++ __u8 msg_type; /* MCTP message type */ ++ ++ /* Timestamp */ ++ __u64 timestamp_ns; /* Error timestamp */ ++ ++ /* Payload Capture */ ++ __u16 payload_len; /* Captured payload length */ ++ __u16 reserved2; ++ __u8 payload[MCTP_ERROR_PAYLOAD_SIZE]; ++ ++ /* Reserved for future use */ ++ __u32 reserved3[2]; ++} __attribute__((packed)); ++ + #endif /* __UAPI_MCTP_H */ +diff --git a/net/mctp/Makefile b/net/mctp/Makefile +index 6cd55233e..b52b33bdb 100644 +--- a/net/mctp/Makefile ++++ b/net/mctp/Makefile +@@ -1,6 +1,6 @@ + # SPDX-License-Identifier: GPL-2.0 + obj-$(CONFIG_MCTP) += mctp.o +-mctp-objs := af_mctp.o device.o route.o neigh.o ++mctp-objs := af_mctp.o device.o route.o neigh.o mctp-socket-error-inject.o + + # tests + obj-$(CONFIG_MCTP_TEST) += test/utils.o +diff --git a/net/mctp/af_mctp.c b/net/mctp/af_mctp.c +index 57850d4da..d99c35b1c 100644 +--- a/net/mctp/af_mctp.c ++++ b/net/mctp/af_mctp.c +@@ -20,6 +20,8 @@ + #define CREATE_TRACE_POINTS + #include + ++#include "mctp-socket-error-inject.h" ++ + /* socket implementation */ + + static void mctp_sk_expire_keys(struct timer_list *timer); +@@ -83,7 +85,7 @@ static int mctp_bind(struct socket *sock, struct sockaddr *addr, int addrlen) + msk->bind_type = smctp->smctp_type & 0x7f; /* ignore the IC bit */ + + rc = sk->sk_prot->hash(sk); +- ++ + out_release: + release_sock(sk); + +@@ -97,8 +99,8 @@ static int mctp_sendmsg(struct socket *sock, struct msghdr *msg, size_t len) + struct sock *sk = sock->sk; + struct mctp_sock *msk = container_of(sk, struct mctp_sock, sk); + struct mctp_skb_cb *cb; +- struct mctp_route *rt; + struct sk_buff *skb = NULL; ++ struct mctp_dst dst; + int hlen; + + if (addr) { +@@ -126,6 +128,17 @@ static int mctp_sendmsg(struct socket *sock, struct msghdr *msg, size_t len) + if (!capable(CAP_NET_RAW)) + return -EACCES; + ++ /* Socket-level error injection - test application error handling ++ * Simulates various sendto() failures at socket layer (binding-agnostic): ++ * - EBUSY: Tag allocation failure (all 8 tags in use) ++ * - EHOSTUNREACH: No route to destination ++ * - ENOBUFS/ENOMEM: Memory allocation failure ++ * - EAGAIN: Would block (non-blocking socket) ++ */ ++ rc = mctp_socket_error_inject_sendmsg(); ++ if (rc < 0) ++ return rc; ++ + if (addr->smctp_network == MCTP_NET_ANY) + addr->smctp_network = mctp_default_net(sock_net(sk)); + +@@ -133,34 +146,43 @@ static int mctp_sendmsg(struct socket *sock, struct msghdr *msg, size_t len) + if (msk->addr_ext && addrlen >= sizeof(struct sockaddr_mctp_ext)) { + DECLARE_SOCKADDR(struct sockaddr_mctp_ext *, + extaddr, msg->msg_name); +- struct net_device *dev; +- +- rc = -EINVAL; +- rcu_read_lock(); +- dev = dev_get_by_index_rcu(sock_net(sk), extaddr->smctp_ifindex); +- /* check for correct halen */ +- if (dev && extaddr->smctp_halen == dev->addr_len) { +- hlen = LL_RESERVED_SPACE(dev) + sizeof(struct mctp_hdr); +- rc = 0; +- } +- rcu_read_unlock(); ++ if (!mctp_sockaddr_ext_is_ok(extaddr)) ++ return -EINVAL; ++ ++ rc = mctp_dst_from_extaddr(&dst, sock_net(sk), ++ extaddr->smctp_ifindex, ++ extaddr->smctp_halen, ++ extaddr->smctp_haddr); + if (rc) +- goto err_free; +- rt = NULL; ++ return rc; ++ ++ /* Check SO_BINDTODEVICE constraint */ ++ if (READ_ONCE(sk->sk_bound_dev_if) && dst.dev && ++ READ_ONCE(sk->sk_bound_dev_if) != dst.dev->dev->ifindex) { ++ mctp_dst_release(&dst); ++ return -EINVAL; ++ } ++ + } else { +- rt = mctp_route_lookup(sock_net(sk), addr->smctp_network, +- addr->smctp_addr.s_addr); +- if (!rt) { +- rc = -EHOSTUNREACH; +- goto err_free; ++ rc = mctp_route_lookup(sock_net(sk), addr->smctp_network, ++ addr->smctp_addr.s_addr, &dst); ++ if (rc) ++ return rc; ++ ++ /* Check SO_BINDTODEVICE constraint */ ++ if (READ_ONCE(sk->sk_bound_dev_if) && dst.dev && ++ READ_ONCE(sk->sk_bound_dev_if) != dst.dev->dev->ifindex) { ++ mctp_dst_release(&dst); ++ return -EINVAL; + } +- hlen = LL_RESERVED_SPACE(rt->dev->dev) + sizeof(struct mctp_hdr); + } + ++ hlen = LL_RESERVED_SPACE(dst.dev->dev) + sizeof(struct mctp_hdr); ++ + skb = sock_alloc_send_skb(sk, hlen + 1 + len, + msg->msg_flags & MSG_DONTWAIT, &rc); + if (!skb) +- return rc; ++ goto err_release_dst; + + skb_reserve(skb, hlen); + +@@ -175,35 +197,22 @@ static int mctp_sendmsg(struct socket *sock, struct msghdr *msg, size_t len) + cb = __mctp_cb(skb); + cb->net = addr->smctp_network; + +- if (!rt) { +- /* fill extended address in cb */ +- DECLARE_SOCKADDR(struct sockaddr_mctp_ext *, +- extaddr, msg->msg_name); +- +- if (!mctp_sockaddr_ext_is_ok(extaddr) || +- extaddr->smctp_halen > sizeof(cb->haddr)) { +- rc = -EINVAL; +- goto err_free; +- } +- +- cb->ifindex = extaddr->smctp_ifindex; +- /* smctp_halen is checked above */ +- cb->halen = extaddr->smctp_halen; +- memcpy(cb->haddr, extaddr->smctp_haddr, cb->halen); +- } +- +- rc = mctp_local_output(sk, rt, skb, addr->smctp_addr.s_addr, ++ trace_mctp_tx_packet(skb); ++ rc = mctp_local_output(sk, &dst, skb, addr->smctp_addr.s_addr, + addr->smctp_tag); + ++ mctp_dst_release(&dst); + return rc ? : len; + + err_free: + kfree_skb(skb); ++err_release_dst: ++ mctp_dst_release(&dst); + return rc; + } + + static int mctp_recvmsg(struct socket *sock, struct msghdr *msg, size_t len, +- int flags) ++ int flags) + { + DECLARE_SOCKADDR(struct sockaddr_mctp *, addr, msg->msg_name); + struct sock *sk = sock->sk; +@@ -213,6 +222,10 @@ static int mctp_recvmsg(struct socket *sock, struct msghdr *msg, size_t len, + u8 type; + int rc; + ++ /* Handle error queue read */ ++ if (flags & MSG_ERRQUEUE) ++ return sock_recv_errqueue(sk, msg, len, SOL_MCTP, MCTP_RECVERR); ++ + if (flags & ~(MSG_DONTWAIT | MSG_TRUNC | MSG_PEEK)) + return -EOPNOTSUPP; + +@@ -278,6 +291,103 @@ static int mctp_recvmsg(struct socket *sock, struct msghdr *msg, size_t len, + return rc; + } + ++/* Work function - called by kernel worker thread to report errors ++ * Context: Process context, NO locks held ++ * ++ * This defers error reporting from timer/softirq context to avoid deadlock. ++ * The timer path (mctp_sk_expire_keys) holds keys_lock, which would cause ++ * deadlock if we call sk_error_report() directly (wake-up callback might ++ * need keys_lock). By deferring to workqueue, we ensure error reporting ++ * happens after keys_lock is released. ++ */ ++static void mctp_error_report_work_fn(struct work_struct *work) ++{ ++ struct mctp_sock *msk = container_of(work, struct mctp_sock, ++ error_report_work); ++ struct mctp_pending_error *perr, *tmp; ++ struct list_head local_list; ++ ++ /* Move all pending errors to local list to minimize lock time */ ++ INIT_LIST_HEAD(&local_list); ++ ++ spin_lock_bh(&msk->error_queue_lock); ++ list_splice_init(&msk->pending_errors, &local_list); ++ spin_unlock_bh(&msk->error_queue_lock); ++ ++ /* Process all errors without holding locks - safe! */ ++ list_for_each_entry_safe(perr, tmp, &local_list, list) { ++ /* Report error to application - safe, no MCTP locks held. ++ * ++ * For RX reassembly timeout, we use the socket saved in perr->sk ++ * (from the RX key at timeout). This avoids redundant socket lookup ++ * and ensures we report to the correct socket even if RX key is gone. ++ * ++ * For ETIMEDOUT, we have orig_payload saved in perr, so we build ++ * the error structure directly instead of calling mctp_queue_error. ++ */ ++ if (perr->error_code == ETIMEDOUT && perr->sk && perr->orig_payload_len > 0) { ++ /* RX timeout with saved request payload - build error directly */ ++ struct mctp_error *mctp_err; ++ struct sk_buff *err_skb; ++ struct mctp_hdr *mh; ++ ++ if (!perr->skb || perr->skb->len < sizeof(struct mctp_hdr)) ++ goto cleanup; ++ ++ err_skb = alloc_skb(sizeof(*mctp_err), GFP_KERNEL); ++ if (!err_skb) ++ goto cleanup; ++ ++ mctp_err = (struct mctp_error *)skb_put(err_skb, sizeof(*mctp_err)); ++ memset(mctp_err, 0, sizeof(*mctp_err)); ++ ++ /* Fill error information */ ++ mctp_err->error_code = perr->error_code; ++ mctp_err->direction = perr->direction; ++ mctp_err->binding = perr->binding; ++ mctp_err->timestamp_ns = ktime_get_ns(); ++ ++ /* Fill addressing from first fragment SKB */ ++ mh = mctp_hdr(perr->skb); ++ mctp_err->src_eid = mh->src; ++ mctp_err->dest_eid = mh->dest; ++ mctp_err->tag = mh->flags_seq_tag & MCTP_HDR_TAG_MASK; ++ ++ /* Use saved REQUEST payload from perr */ ++ mctp_err->msg_type = perr->orig_msg_type; ++ mctp_err->payload_len = min_t(u16, perr->orig_payload_len, ++ MCTP_ERROR_PAYLOAD_SIZE); ++ memcpy(mctp_err->payload, perr->orig_payload, mctp_err->payload_len); ++ ++ /* Queue error to socket */ ++ if (sock_queue_err_skb(perr->sk, err_skb) == 0) { ++ sk_error_report(perr->sk); ++ pr_debug("MCTP timeout: Error queued (REQUEST payload, len=%u)\n", ++ mctp_err->payload_len); ++ } else { ++ kfree_skb(err_skb); ++ pr_debug("MCTP timeout: Failed to queue error to socket\n"); ++ } ++cleanup: ++ sock_put(perr->sk); ++ } else if (perr->sk) { ++ /* Other error types - use mctp_queue_error */ ++ mctp_queue_error(perr->sk, perr->skb, perr->error_code, ++ perr->dev, perr->direction, perr->binding, NULL); ++ sock_put(perr->sk); ++ } else { ++ /* Fallback: Look up socket from SKB (future error types) */ ++ mctp_queue_error(&msk->sk, perr->skb, perr->error_code, ++ perr->dev, perr->direction, perr->binding, NULL); ++ } ++ ++ /* Cleanup */ ++ list_del(&perr->list); ++ kfree_skb(perr->skb); ++ kfree(perr); ++ } ++} ++ + /* We're done with the key; invalidate, stop reassembly, and remove from lists. + */ + static void __mctp_key_remove(struct mctp_sk_key *key, struct net *net, +@@ -292,6 +402,82 @@ __must_hold(&net->mctp.keys_lock) + key->reasm_head = NULL; + key->reasm_dead = true; + key->valid = false; ++ ++ /* Reassembly timeout - queue error for deferred reporting */ ++ if (reason == MCTP_TRACE_KEY_TIMEOUT && skb && key->dev && key->dev->dev && ++ key->sk) { ++ struct mctp_sock *msk = container_of(key->sk, struct mctp_sock, sk); ++ ++ netdev_warn(key->dev->dev, ++ "MCTP RX: Reassembly timeout - partial message discarded " ++ "(src=%u, dest=%u, tag=%u, fragments incomplete)\n", ++ key->peer_addr, key->local_addr, key->tag); ++ ++ /* Queue error for deferred reporting via workqueue if enabled. ++ * Use ETIMEDOUT for reassembly timeout - semantically clearer than EPROTO ++ * (timeout is caused by missing fragments that never arrive). ++ * ++ * CRITICAL: Only report if this key originated from TX (has orig_payload). ++ * This ensures we only report errors for responses to OUR requests, ++ * not for unsolicited requests from device. ++ * ++ * For TX-originated keys: ++ * - key->sk = application that sent original request ++ * - key->reasm_head (skb) = first fragment of response (used for addressing) ++ * - key->orig_payload = original REQUEST payload (from TX phase) ++ * ++ * We pass socket + first fragment SKB (for addressing) to workqueue. ++ * Workqueue will extract REQUEST payload from key->orig_payload, ++ * allowing applications to identify which transaction timed out. ++ */ ++ if (msk->enable_errqueue && key->orig_payload_len > 0) { ++ struct mctp_pending_error *perr; ++ ++ perr = kmalloc(sizeof(*perr), GFP_ATOMIC); ++ if (perr) { ++ /* Clone first fragment SKB for later use by workqueue */ ++ perr->skb = skb_clone(skb, GFP_ATOMIC); ++ if (perr->skb) { ++ /* Save socket with refcount - ensures it stays alive ++ * until workqueue processes the error. ++ * This is the application that sent the original request. ++ */ ++ perr->sk = key->sk; ++ sock_hold(perr->sk); ++ ++ perr->error_code = ETIMEDOUT; ++ perr->dev = key->dev->dev; ++ perr->direction = MCTP_DIR_RX; ++ perr->binding = mctp_get_binding_type(key->dev ? key->dev->dev : NULL); ++ ++ /* Copy original request payload from key for error reporting */ ++ perr->orig_msg_type = key->orig_msg_type; ++ perr->orig_payload_len = key->orig_payload_len; ++ memcpy(perr->orig_payload, key->orig_payload, ++ min_t(size_t, key->orig_payload_len, sizeof(perr->orig_payload))); ++ ++ /* Add to pending list and schedule work ++ * Work will run later in process context with no locks held ++ */ ++ spin_lock_bh(&msk->error_queue_lock); ++ list_add_tail(&perr->list, &msk->pending_errors); ++ spin_unlock_bh(&msk->error_queue_lock); ++ ++ pr_debug("MCTP: RX timeout queued for deferred error reporting (key has TX origin, orig_payload_len=%u)\n", ++ key->orig_payload_len); ++ ++ /* Schedule work - returns immediately */ ++ schedule_work(&msk->error_report_work); ++ } else { ++ kfree(perr); ++ } ++ } ++ } else if (msk->enable_errqueue && key->orig_payload_len == 0) { ++ /* RX-only key (unsolicited request from device) - don't report */ ++ pr_debug("MCTP: RX timeout NOT reported - no TX origin (unsolicited request, orig_payload_len=0)\n"); ++ } ++ } ++ + mctp_dev_release_key(key->dev, key); + spin_unlock_irqrestore(&key->lock, flags); + +@@ -323,6 +509,17 @@ static int mctp_setsockopt(struct socket *sock, int level, int optname, + return 0; + } + ++ if (optname == MCTP_OPT_ENABLE_ERRQUEUE) { ++ if (optlen != sizeof(int)) ++ return -EINVAL; ++ if (copy_from_sockptr(&val, optval, sizeof(int))) ++ return -EFAULT; ++ ++ msk->enable_errqueue = !!val; ++ ++ return 0; ++ } ++ + return -ENOPROTOOPT; + } + +@@ -347,7 +544,7 @@ static int mctp_getsockopt(struct socket *sock, int level, int optname, + return 0; + } + +- return -EINVAL; ++ return -ENOPROTOOPT; + } + + /* helpers for reading/writing the tag ioc, handling compatibility across the +@@ -439,7 +636,8 @@ static int mctp_ioctl_alloctag(struct mctp_sock *msk, bool tagv2, + return -EINVAL; + + key = mctp_alloc_local_tag(msk, ctl.net, MCTP_ADDR_ANY, +- ctl.peer_addr, true, &tag); ++ ctl.peer_addr, true, &tag, ++ MCTP_DEFAULT_LIFETIME); + if (IS_ERR(key)) + return PTR_ERR(key); + +@@ -547,6 +745,22 @@ static int mctp_compat_ioctl(struct socket *sock, unsigned int cmd, + } + #endif + ++static __poll_t mctp_poll(struct file *file, struct socket *sock, ++ poll_table *wait) ++{ ++ struct sock *sk = sock->sk; ++ __poll_t mask; ++ ++ /* Use standard datagram_poll for normal data */ ++ mask = datagram_poll(file, sock, wait); ++ ++ /* Check error queue */ ++ if (!skb_queue_empty_lockless(&sk->sk_error_queue)) ++ mask |= EPOLLERR | EPOLLPRI; ++ ++ return mask; ++} ++ + static const struct proto_ops mctp_dgram_ops = { + .family = PF_MCTP, + .release = mctp_release, +@@ -555,7 +769,7 @@ static const struct proto_ops mctp_dgram_ops = { + .socketpair = sock_no_socketpair, + .accept = sock_no_accept, + .getname = sock_no_getname, +- .poll = datagram_poll, ++ .poll = mctp_poll, + .ioctl = mctp_ioctl, + .gettstamp = sock_gettstamp, + .listen = sock_no_listen, +@@ -612,12 +826,21 @@ static void mctp_sk_expire_keys(struct timer_list *timer) + mod_timer(timer, next_expiry); + } + ++/* Forward declaration for workqueue callback */ ++static void mctp_error_report_work_fn(struct work_struct *work); ++ + static int mctp_sk_init(struct sock *sk) + { + struct mctp_sock *msk = container_of(sk, struct mctp_sock, sk); + + INIT_HLIST_HEAD(&msk->keys); + timer_setup(&msk->key_expiry, mctp_sk_expire_keys, 0); ++ ++ /* Initialize deferred error reporting workqueue */ ++ INIT_WORK(&msk->error_report_work, mctp_error_report_work_fn); ++ INIT_LIST_HEAD(&msk->pending_errors); ++ spin_lock_init(&msk->error_queue_lock); ++ + return 0; + } + +@@ -633,6 +856,9 @@ static int mctp_sk_hash(struct sock *sk) + /* Bind lookup runs under RCU, remain live during that. */ + sock_set_flag(sk, SOCK_RCU_FREE); + ++ /* Bind lookup runs under RCU, remain live during that. */ ++ sock_set_flag(sk, SOCK_RCU_FREE); ++ + mutex_lock(&net->mctp.bind_lock); + sk_add_node_rcu(sk, &net->mctp.binds); + mutex_unlock(&net->mctp.bind_lock); +@@ -667,6 +893,22 @@ static void mctp_sk_unhash(struct sock *sk) + * as the sk is no longer observable + */ + del_timer_sync(&msk->key_expiry); ++ ++ /* Cancel pending error reporting work and free any pending errors */ ++ cancel_work_sync(&msk->error_report_work); ++ { ++ struct mctp_pending_error *perr, *tmp_err; ++ ++ spin_lock_bh(&msk->error_queue_lock); ++ list_for_each_entry_safe(perr, tmp_err, &msk->pending_errors, list) { ++ list_del(&perr->list); ++ if (perr->sk) ++ sock_put(perr->sk); ++ kfree_skb(perr->skb); ++ kfree(perr); ++ } ++ spin_unlock_bh(&msk->error_queue_lock); ++ } + } + + static void mctp_sk_destruct(struct sock *sk) +@@ -763,6 +1005,10 @@ static __init int mctp_init(void) + if (rc) + goto err_unreg_neigh; + ++ rc = mctp_socket_error_inject_init(); ++ if (rc) ++ pr_warn("MCTP: Socket error injection init failed, continuing without it\n"); ++ + return 0; + + err_unreg_neigh: +@@ -779,6 +1025,7 @@ static __init int mctp_init(void) + + static __exit void mctp_exit(void) + { ++ mctp_socket_error_inject_cleanup(); + mctp_device_exit(); + mctp_neigh_exit(); + mctp_routes_exit(); +@@ -793,3 +1040,7 @@ MODULE_DESCRIPTION("MCTP core"); + MODULE_AUTHOR("Jeremy Kerr "); + + MODULE_ALIAS_NETPROTO(PF_MCTP); ++ ++#if IS_ENABLED(CONFIG_MCTP_TEST) ++#include "test/sock-test.c" ++#endif +diff --git a/net/mctp/device.c b/net/mctp/device.c +index 8d1386601..79cfc6d50 100644 +--- a/net/mctp/device.c ++++ b/net/mctp/device.c +@@ -19,6 +19,8 @@ + #include + #include + ++#include ++ + struct mctp_dump_cb { + unsigned long ifindex; + size_t a_idx; +@@ -39,6 +41,7 @@ struct mctp_dev *__mctp_dev_get(const struct net_device *dev) + return NULL; + return mdev; + } ++EXPORT_SYMBOL_GPL(__mctp_dev_get); + + /* Returned mctp_dev does not have refcount incremented. The returned pointer + * remains live while rtnl_lock is held, as that prevents mctp_unregister() +@@ -219,12 +222,16 @@ static int mctp_rtm_newaddr(struct sk_buff *skb, struct nlmsghdr *nlh, + return -EINVAL; + + /* Prevent duplicates. Under RTNL so don't need to lock for reading */ +- if (memchr(mdev->addrs, addr->s_addr, mdev->num_addrs)) ++ if (memchr(mdev->addrs, addr->s_addr, mdev->num_addrs)) { ++ netdev_dbg(dev, "MCTP: address %u already exists\n", addr->s_addr); + return -EEXIST; ++ } + + tmp_addrs = kmalloc(mdev->num_addrs + 1, GFP_KERNEL); +- if (!tmp_addrs) ++ if (!tmp_addrs) { ++ netdev_dbg(dev, "MCTP: failed to allocate memory for address %u\n", addr->s_addr); + return -ENOMEM; ++ } + memcpy(tmp_addrs, mdev->addrs, mdev->num_addrs); + tmp_addrs[mdev->num_addrs] = addr->s_addr; + +@@ -236,6 +243,9 @@ static int mctp_rtm_newaddr(struct sk_buff *skb, struct nlmsghdr *nlh, + + kfree(tmp_addrs); + ++ netdev_dbg(dev, "MCTP: address %u added (total %u addresses)\n", ++ addr->s_addr, mdev->num_addrs); ++ trace_mctp_address_add(dev, addr->s_addr); + mctp_addr_notify(mdev, addr->s_addr, RTM_NEWADDR, skb, nlh); + mctp_route_add_local(mdev, addr->s_addr); + +@@ -279,8 +289,10 @@ static int mctp_rtm_deladdr(struct sk_buff *skb, struct nlmsghdr *nlh, + return -ENODEV; + + pos = memchr(mdev->addrs, addr->s_addr, mdev->num_addrs); +- if (!pos) ++ if (!pos) { ++ netdev_dbg(dev, "MCTP: address %u not found for deletion\n", addr->s_addr); + return -ENOENT; ++ } + + rc = mctp_route_remove_local(mdev, addr->s_addr); + // we can ignore -ENOENT in the case a route was already removed +@@ -292,6 +304,9 @@ static int mctp_rtm_deladdr(struct sk_buff *skb, struct nlmsghdr *nlh, + mdev->num_addrs--; + spin_unlock_irqrestore(&mdev->addrs_lock, flags); + ++ netdev_dbg(dev, "MCTP: address %u deleted (remaining %u addresses)\n", ++ addr->s_addr, mdev->num_addrs); ++ trace_mctp_address_del(dev, addr->s_addr); + mctp_addr_notify(mdev, addr->s_addr, RTM_DELADDR, skb, nlh); + + return 0; +@@ -305,11 +320,27 @@ void mctp_dev_hold(struct mctp_dev *mdev) + void mctp_dev_put(struct mctp_dev *mdev) + { + if (mdev && refcount_dec_and_test(&mdev->refs)) { ++ unsigned int i; ++ struct mctp_nf_track_entry *entry; ++ struct hlist_node *tmp; ++ ++ spin_lock_bh(&mdev->nf_track.lock); ++ for (i = 0; i < MCTP_NF_TRACK_BUCKETS; i++) { ++ hlist_for_each_entry_safe(entry, tmp, ++ &mdev->nf_track.buckets[i], ++ node) { ++ hlist_del(&entry->node); ++ kfree(entry); ++ } ++ } ++ spin_unlock_bh(&mdev->nf_track.lock); ++ + kfree(mdev->addrs); + dev_put(mdev->dev); + kfree_rcu(mdev, rcu); + } + } ++EXPORT_SYMBOL_GPL(mctp_dev_put); + + void mctp_dev_release_key(struct mctp_dev *dev, struct mctp_sk_key *key) + __must_hold(&key->lock) +@@ -332,6 +363,7 @@ void mctp_dev_set_key(struct mctp_dev *dev, struct mctp_sk_key *key) + static struct mctp_dev *mctp_add_dev(struct net_device *dev) + { + struct mctp_dev *mdev; ++ unsigned int i; + + ASSERT_RTNL(); + +@@ -340,8 +372,13 @@ static struct mctp_dev *mctp_add_dev(struct net_device *dev) + return ERR_PTR(-ENOMEM); + + spin_lock_init(&mdev->addrs_lock); ++ spin_lock_init(&mdev->nf_track.lock); ++ for (i = 0; i < MCTP_NF_TRACK_BUCKETS; i++) ++ INIT_HLIST_HEAD(&mdev->nf_track.buckets[i]); ++ mdev->nf_track.count = 0; + + mdev->net = mctp_default_net(dev_net(dev)); ++ mdev->key_lifetime = MCTP_DEFAULT_LIFETIME; + + /* associate to net_device */ + refcount_set(&mdev->refs, 1); +@@ -350,6 +387,8 @@ static struct mctp_dev *mctp_add_dev(struct net_device *dev) + dev_hold(dev); + mdev->dev = dev; + ++ netdev_dbg(dev, "MCTP: device registered (net %u)\n", mdev->net); ++ trace_mctp_device_register(dev, mdev->net); + return mdev; + } + +@@ -363,6 +402,8 @@ static int mctp_fill_link_af(struct sk_buff *skb, + return -ENODATA; + if (nla_put_u32(skb, IFLA_MCTP_NET, mdev->net)) + return -EMSGSIZE; ++ if (nla_put_u8(skb, IFLA_MCTP_PHYS_BINDING, mdev->binding)) ++ return -EMSGSIZE; + return 0; + } + +@@ -377,6 +418,7 @@ static size_t mctp_get_link_af_size(const struct net_device *dev, + if (!mdev) + return 0; + ret = nla_total_size(4); /* IFLA_MCTP_NET */ ++ ret += nla_total_size(1); /* IFLA_MCTP_PHYS_BINDING */ + mctp_dev_put(mdev); + return ret; + } +@@ -424,6 +466,8 @@ static void mctp_unregister(struct net_device *dev) + if (!mdev) + return; + ++ netdev_dbg(dev, "MCTP: device unregistering (%u addresses)\n", mdev->num_addrs); ++ trace_mctp_device_unregister(dev); + RCU_INIT_POINTER(mdev->dev->mctp_ptr, NULL); + + mctp_route_remove_dev(mdev); +@@ -472,7 +516,8 @@ static int mctp_dev_notify(struct notifier_block *this, unsigned long event, + } + + static int mctp_register_netdevice(struct net_device *dev, +- const struct mctp_netdev_ops *ops) ++ const struct mctp_netdev_ops *ops, ++ enum mctp_phys_binding binding) + { + struct mctp_dev *mdev; + +@@ -481,17 +526,19 @@ static int mctp_register_netdevice(struct net_device *dev, + return PTR_ERR(mdev); + + mdev->ops = ops; ++ mdev->binding = binding; + + return register_netdevice(dev); + } + + int mctp_register_netdev(struct net_device *dev, +- const struct mctp_netdev_ops *ops) ++ const struct mctp_netdev_ops *ops, ++ enum mctp_phys_binding binding) + { + int rc; + + rtnl_lock(); +- rc = mctp_register_netdevice(dev, ops); ++ rc = mctp_register_netdevice(dev, ops, binding); + rtnl_unlock(); + + return rc; +@@ -504,6 +551,20 @@ void mctp_unregister_netdev(struct net_device *dev) + } + EXPORT_SYMBOL_GPL(mctp_unregister_netdev); + ++void mctp_dev_set_timeout(struct net_device *dev, unsigned int timeout_ms) ++{ ++ struct mctp_dev *mdev; ++ ++ rtnl_lock(); ++ mdev = mctp_dev_get_rtnl(dev); ++ if (mdev) { ++ mdev->key_lifetime = msecs_to_jiffies(timeout_ms); ++ netdev_info(dev, "MCTP timeout set to %u ms\n", timeout_ms); ++ } ++ rtnl_unlock(); ++} ++EXPORT_SYMBOL_GPL(mctp_dev_set_timeout); ++ + static struct rtnl_af_ops mctp_af_ops = { + .family = AF_MCTP, + .fill_link_af = mctp_fill_link_af, +diff --git a/net/mctp/mctp-socket-error-inject.c b/net/mctp/mctp-socket-error-inject.c +new file mode 100644 +index 000000000..b8d03f2d0 +--- /dev/null ++++ b/net/mctp/mctp-socket-error-inject.c +@@ -0,0 +1,221 @@ ++// SPDX-License-Identifier: GPL-2.0 ++/* ++ * MCTP Socket Error Injection ++ * ++ * Binding-agnostic error injection at AF_MCTP socket layer. ++ * Allows testing application handling of sendto() API failures: ++ * - EBUSY: Tag allocation failure (all tags in use) ++ * - EHOSTUNREACH: No route to destination ++ * - ENOBUFS/ENOMEM: Memory exhaustion ++ * - EAGAIN: Would block ++ */ ++ ++#include ++#include ++#include ++#include ++#include "mctp-socket-error-inject.h" ++ ++/* Global error injection state */ ++struct mctp_socket_error_inject mctp_socket_ei; ++ ++static struct dentry *mctp_socket_debugfs_root; ++ ++/* Main injection function called from sendmsg() */ ++int mctp_socket_error_inject_sendmsg(void) ++{ ++ int ret = 0; ++ ++ spin_lock_bh(&mctp_socket_ei.lock); ++ mctp_socket_ei.sendmsg_calls++; ++ ++ if (mctp_socket_ei.enabled) { ++ mctp_socket_ei.errors_injected++; ++ ret = mctp_socket_ei.error_code; ++ ++ pr_debug("MCTP Socket Error Injection: sendmsg() returning %d, " ++ "total_injected=%llu\n", ++ ret, mctp_socket_ei.errors_injected); ++ } ++ ++ spin_unlock_bh(&mctp_socket_ei.lock); ++ ++ return ret; ++} ++EXPORT_SYMBOL_GPL(mctp_socket_error_inject_sendmsg); ++ ++/* Debugfs: enable */ ++static ssize_t enable_read(struct file *file, char __user *userbuf, ++ size_t count, loff_t *ppos) ++{ ++ char buf[8]; ++ int len; ++ ++ len = snprintf(buf, sizeof(buf), "%d\n", mctp_socket_ei.enabled ? 1 : 0); ++ return simple_read_from_buffer(userbuf, count, ppos, buf, len); ++} ++ ++static ssize_t enable_write(struct file *file, const char __user *userbuf, ++ size_t count, loff_t *ppos) ++{ ++ char buf[8]; ++ size_t len = min(count, sizeof(buf) - 1); ++ int value; ++ ++ if (copy_from_user(buf, userbuf, len)) ++ return -EFAULT; ++ buf[len] = '\0'; ++ ++ if (kstrtoint(buf, 0, &value)) ++ return -EINVAL; ++ ++ mctp_socket_ei.enabled = (value != 0); ++ ++ pr_debug("MCTP Socket Error Injection: %s\n", ++ mctp_socket_ei.enabled ? "ENABLED" : "DISABLED"); ++ return count; ++} ++ ++static const struct file_operations enable_fops = { ++ .read = enable_read, ++ .write = enable_write, ++ .open = simple_open, ++ .llseek = default_llseek, ++}; ++ ++/* Debugfs: stats */ ++static ssize_t stats_read(struct file *file, char __user *userbuf, ++ size_t count, loff_t *ppos) ++{ ++ char buf[256]; ++ int len; ++ ++ spin_lock_bh(&mctp_socket_ei.lock); ++ len = snprintf(buf, sizeof(buf), ++ "MCTP Socket Error Injection Statistics:\n" ++ " Enabled: %s\n" ++ " Error Code: %d\n" ++ " Total sendmsg() calls: %llu\n" ++ " Errors injected: %llu\n", ++ mctp_socket_ei.enabled ? "yes" : "no", ++ mctp_socket_ei.error_code, ++ mctp_socket_ei.sendmsg_calls, ++ mctp_socket_ei.errors_injected); ++ spin_unlock_bh(&mctp_socket_ei.lock); ++ ++ return simple_read_from_buffer(userbuf, count, ppos, buf, len); ++} ++ ++static const struct file_operations stats_fops = { ++ .read = stats_read, ++ .open = simple_open, ++ .llseek = default_llseek, ++}; ++ ++/* Debugfs: error_code */ ++static ssize_t error_code_read(struct file *file, char __user *userbuf, ++ size_t count, loff_t *ppos) ++{ ++ char buf[32]; ++ int len; ++ ++ len = snprintf(buf, sizeof(buf), "%d\n", mctp_socket_ei.error_code); ++ return simple_read_from_buffer(userbuf, count, ppos, buf, len); ++} ++ ++static ssize_t error_code_write(struct file *file, const char __user *userbuf, ++ size_t count, loff_t *ppos) ++{ ++ char buf[32]; ++ size_t len = min(count, sizeof(buf) - 1); ++ int value; ++ ++ if (copy_from_user(buf, userbuf, len)) ++ return -EFAULT; ++ buf[len] = '\0'; ++ ++ if (kstrtoint(buf, 0, &value)) ++ return -EINVAL; ++ ++ mctp_socket_ei.error_code = value; ++ return count; ++} ++ ++static const struct file_operations error_code_fops = { ++ .read = error_code_read, ++ .write = error_code_write, ++ .open = simple_open, ++ .llseek = default_llseek, ++}; ++ ++/* Debugfs: reset */ ++static ssize_t reset_write(struct file *file, const char __user *userbuf, ++ size_t count, loff_t *ppos) ++{ ++ char buf[8]; ++ size_t len = min(count, sizeof(buf) - 1); ++ int value; ++ ++ if (copy_from_user(buf, userbuf, len)) ++ return -EFAULT; ++ buf[len] = '\0'; ++ ++ if (kstrtoint(buf, 0, &value)) ++ return -EINVAL; ++ ++ if (value != 1) ++ return -EINVAL; ++ ++ /* Reset all state */ ++ spin_lock_bh(&mctp_socket_ei.lock); ++ mctp_socket_ei.enabled = false; ++ mctp_socket_ei.error_code = 0; ++ mctp_socket_ei.sendmsg_calls = 0; ++ mctp_socket_ei.errors_injected = 0; ++ spin_unlock_bh(&mctp_socket_ei.lock); ++ ++ pr_debug("MCTP Socket Error Injection: Reset (disabled and cleared)\n"); ++ return count; ++} ++ ++static const struct file_operations reset_fops = { ++ .write = reset_write, ++ .open = simple_open, ++ .llseek = default_llseek, ++}; ++ ++/* Initialization */ ++int mctp_socket_error_inject_init(void) ++{ ++ memset(&mctp_socket_ei, 0, sizeof(mctp_socket_ei)); ++ spin_lock_init(&mctp_socket_ei.lock); ++ ++ /* Default settings */ ++ mctp_socket_ei.enabled = false; ++ mctp_socket_ei.error_code = -EBUSY; /* Default: simulate tag exhaustion */ ++ ++ /* Create debugfs directory */ ++ mctp_socket_debugfs_root = debugfs_create_dir("mctp_socket", NULL); ++ if (IS_ERR_OR_NULL(mctp_socket_debugfs_root)) { ++ pr_warn("MCTP Socket Error Injection: Failed to create debugfs directory\n"); ++ return PTR_ERR(mctp_socket_debugfs_root); ++ } ++ ++ mctp_socket_ei.debugfs_dir = mctp_socket_debugfs_root; ++ ++ /* Create debugfs files */ ++ debugfs_create_file("enable", 0644, mctp_socket_debugfs_root, NULL, &enable_fops); ++ debugfs_create_file("error_code", 0644, mctp_socket_debugfs_root, NULL, &error_code_fops); ++ debugfs_create_file("stats", 0444, mctp_socket_debugfs_root, NULL, &stats_fops); ++ debugfs_create_file("reset", 0200, mctp_socket_debugfs_root, NULL, &reset_fops); ++ ++ pr_info("MCTP Socket Error Injection: Initialized at /sys/kernel/debug/mctp_socket/\n"); ++ return 0; ++} ++ ++void mctp_socket_error_inject_cleanup(void) ++{ ++ debugfs_remove_recursive(mctp_socket_debugfs_root); ++ pr_info("MCTP Socket Error Injection: Cleaned up\n"); ++} ++ +diff --git a/net/mctp/mctp-socket-error-inject.h b/net/mctp/mctp-socket-error-inject.h +new file mode 100644 +index 000000000..07aa263b9 +--- /dev/null ++++ b/net/mctp/mctp-socket-error-inject.h +@@ -0,0 +1,41 @@ ++/* SPDX-License-Identifier: GPL-2.0 */ ++/* ++ * MCTP Socket Error Injection - Header ++ * ++ * Binding-agnostic error injection at AF_MCTP socket layer. ++ * Tests application handling of sendto() failures. ++ */ ++ ++#ifndef __MCTP_SOCKET_ERROR_INJECT_H ++#define __MCTP_SOCKET_ERROR_INJECT_H ++ ++#include ++#include ++ ++/* Socket-level error injection state */ ++struct mctp_socket_error_inject { ++ /* Error injection control */ ++ bool enabled; /* Enable/disable error injection */ ++ int error_code; /* Error to return (e.g. -EBUSY, -EHOSTUNREACH) */ ++ ++ /* Statistics */ ++ u64 sendmsg_calls; /* Total sendmsg() calls */ ++ u64 errors_injected; /* Errors injected */ ++ ++ /* Debugfs */ ++ struct dentry *debugfs_dir; ++ spinlock_t lock; ++}; ++ ++/* Global instance */ ++extern struct mctp_socket_error_inject mctp_socket_ei; ++ ++/* Initialize/cleanup */ ++int mctp_socket_error_inject_init(void); ++void mctp_socket_error_inject_cleanup(void); ++ ++/* Check if error should be injected in sendmsg() */ ++int mctp_socket_error_inject_sendmsg(void); ++ ++#endif /* __MCTP_SOCKET_ERROR_INJECT_H */ ++ +diff --git a/net/mctp/neigh.c b/net/mctp/neigh.c +index 590f64241..66d9b3acb 100644 +--- a/net/mctp/neigh.c ++++ b/net/mctp/neigh.c +@@ -21,6 +21,8 @@ + #include + #include + ++#include ++ + static int mctp_neigh_add(struct mctp_dev *mdev, mctp_eid_t eid, + enum mctp_neigh_source source, + size_t lladdr_len, const void *lladdr) +@@ -31,17 +33,21 @@ static int mctp_neigh_add(struct mctp_dev *mdev, mctp_eid_t eid, + + mutex_lock(&net->mctp.neigh_lock); + if (mctp_neigh_lookup(mdev, eid, NULL) == 0) { ++ netdev_dbg(mdev->dev, "MCTP: neighbor eid %u already exists\n", eid); + rc = -EEXIST; + goto out; + } + + if (lladdr_len > sizeof(neigh->ha)) { ++ netdev_dbg(mdev->dev, "MCTP: neighbor lladdr too long (%zu > %zu)\n", ++ lladdr_len, sizeof(neigh->ha)); + rc = -EINVAL; + goto out; + } + + neigh = kzalloc(sizeof(*neigh), GFP_KERNEL); + if (!neigh) { ++ netdev_dbg(mdev->dev, "MCTP: failed to allocate neighbor entry for eid %u\n", eid); + rc = -ENOMEM; + goto out; + } +@@ -53,6 +59,9 @@ static int mctp_neigh_add(struct mctp_dev *mdev, mctp_eid_t eid, + memcpy(neigh->ha, lladdr, lladdr_len); + + list_add_rcu(&neigh->list, &net->mctp.neighbours); ++ netdev_dbg(mdev->dev, "MCTP: neighbor eid %u added (lladdr_len %zu)\n", ++ eid, lladdr_len); ++ trace_mctp_neighbor_add(mdev->dev, eid, lladdr, lladdr_len); + rc = 0; + out: + mutex_unlock(&net->mctp.neigh_lock); +@@ -96,6 +105,8 @@ static int mctp_neigh_remove(struct mctp_dev *mdev, mctp_eid_t eid, + list_for_each_entry_safe(neigh, tmp, &net->mctp.neighbours, list) { + if (neigh->dev == mdev && neigh->eid == eid && + neigh->source == source) { ++ netdev_dbg(mdev->dev, "MCTP: neighbor eid %u removed\n", eid); ++ trace_mctp_neighbor_del(mdev->dev, eid); + list_del_rcu(&neigh->list); + /* TODO: immediate RTM_DELNEIGH */ + call_rcu(&neigh->rcu, __mctp_neigh_free); +@@ -104,6 +115,8 @@ static int mctp_neigh_remove(struct mctp_dev *mdev, mctp_eid_t eid, + } + + mutex_unlock(&net->mctp.neigh_lock); ++ if (!dropped) ++ netdev_dbg(mdev->dev, "MCTP: neighbor eid %u not found for removal\n", eid); + return dropped ? 0 : -ENOENT; + } + +@@ -293,6 +306,9 @@ int mctp_neigh_lookup(struct mctp_dev *mdev, mctp_eid_t eid, void *ret_hwaddr) + } + } + rcu_read_unlock(); ++ if (rc) ++ netdev_dbg(mdev->dev, "MCTP: neighbor eid %u lookup failed\n", eid); ++ trace_mctp_neighbor_lookup(mdev->dev, eid, rc); + return rc; + } + +diff --git a/net/mctp/route.c b/net/mctp/route.c +index d9c8e5a5f..a62788b09 100644 +--- a/net/mctp/route.c ++++ b/net/mctp/route.c +@@ -14,8 +14,12 @@ + #include + #include + #include ++#include + #include + #include ++#include ++ ++#include + + #include + +@@ -27,12 +31,283 @@ + #include + + static const unsigned int mctp_message_maxlen = 64 * 1024; +-static const unsigned long mctp_key_lifetime = 6 * CONFIG_HZ; ++static const unsigned long mctp_nf_track_timeout = 2 * CONFIG_HZ; ++ ++/* Helper to determine binding type from network device ++ * Returns the physical binding type from mctp_dev, which is set during ++ * device registration and remains constant regardless of device renaming. ++ */ ++u8 mctp_get_binding_type(struct net_device *dev) ++{ ++ struct mctp_dev *mdev; ++ u8 binding = 0; ++ ++ if (!dev) ++ return 0; /* Unknown */ ++ ++ rcu_read_lock(); ++ mdev = __mctp_dev_get(dev); ++ if (mdev) { ++ binding = mdev->binding; ++ mctp_dev_put(mdev); ++ } ++ rcu_read_unlock(); ++ ++ return binding; ++} ++ + + static void mctp_flow_prepare_output(struct sk_buff *skb, struct mctp_dev *dev); + ++enum mctp_nf_track_verdict { ++ MCTP_NF_VERDICT_ACCEPT = 0, ++ MCTP_NF_VERDICT_DROP = 1, ++}; ++ ++static u32 mctp_nf_track_hash(mctp_eid_t src, mctp_eid_t dst, u8 tag) ++{ ++ return jhash_3words(src, dst, tag, 0) & (MCTP_NF_TRACK_BUCKETS - 1); ++} ++ ++static bool mctp_nf_track_lookup(struct mctp_dev *mdev, mctp_eid_t src, ++ mctp_eid_t dst, u8 tag, u8 *verdict) ++{ ++ struct mctp_nf_track_entry *entry; ++ struct hlist_node *tmp; ++ unsigned long now = jiffies; ++ u32 idx = mctp_nf_track_hash(src, dst, tag); ++ bool found = false; ++ ++ spin_lock_bh(&mdev->nf_track.lock); ++ hlist_for_each_entry_safe(entry, tmp, &mdev->nf_track.buckets[idx], ++ node) { ++ if (entry->src != src || entry->dst != dst || entry->tag != tag) ++ continue; ++ ++ if (time_after(now, entry->expires)) { ++ netdev_dbg(mdev->dev, ++ "MCTP nf track: expiry src=%u dst=%u tag=0x%x\n", ++ src, dst, tag); ++ hlist_del(&entry->node); ++ kfree(entry); ++ mdev->nf_track.count--; ++ break; ++ } ++ ++ *verdict = entry->verdict; ++ found = true; ++ break; ++ } ++ spin_unlock_bh(&mdev->nf_track.lock); ++ ++ if (found) ++ netdev_dbg(mdev->dev, ++ "MCTP nf track lookup: src=%u dst=%u tag=0x%x verdict=%s\n", ++ src, dst, tag, *verdict == MCTP_NF_VERDICT_ACCEPT ? "accept" : "drop"); ++ ++ return found; ++} ++ ++static void mctp_nf_track_delete(struct mctp_dev *mdev, mctp_eid_t src, ++ mctp_eid_t dst, u8 tag) ++{ ++ struct mctp_nf_track_entry *entry; ++ struct hlist_node *tmp; ++ u32 idx = mctp_nf_track_hash(src, dst, tag); ++ ++ spin_lock_bh(&mdev->nf_track.lock); ++ hlist_for_each_entry_safe(entry, tmp, &mdev->nf_track.buckets[idx], ++ node) { ++ if (entry->src != src || entry->dst != dst || entry->tag != tag) ++ continue; ++ netdev_dbg(mdev->dev, ++ "MCTP nf track delete: src=%u dst=%u tag=0x%x\n", ++ src, dst, tag); ++ hlist_del(&entry->node); ++ kfree(entry); ++ mdev->nf_track.count--; ++ break; ++ } ++ spin_unlock_bh(&mdev->nf_track.lock); ++} ++ ++static void mctp_nf_track_store(struct mctp_dev *mdev, mctp_eid_t src, ++ mctp_eid_t dst, u8 tag, u8 verdict) ++{ ++ struct mctp_nf_track_entry *entry, *new_entry; ++ u32 idx = mctp_nf_track_hash(src, dst, tag); ++ ++ new_entry = kzalloc(sizeof(*new_entry), GFP_ATOMIC); ++ if (!new_entry) ++ return; ++ ++ new_entry->src = src; ++ new_entry->dst = dst; ++ new_entry->tag = tag; ++ new_entry->verdict = verdict; ++ new_entry->expires = jiffies + mctp_nf_track_timeout; ++ ++ spin_lock_bh(&mdev->nf_track.lock); ++ hlist_for_each_entry(entry, &mdev->nf_track.buckets[idx], node) { ++ if (entry->src != src || entry->dst != dst || entry->tag != tag) ++ continue; ++ entry->verdict = verdict; ++ entry->expires = jiffies + mctp_nf_track_timeout; ++ spin_unlock_bh(&mdev->nf_track.lock); ++ netdev_dbg(mdev->dev, ++ "MCTP nf track store (update): src=%u dst=%u tag=0x%x verdict=%s\n", ++ src, dst, tag, ++ verdict == MCTP_NF_VERDICT_ACCEPT ? "accept" : "drop"); ++ kfree(new_entry); ++ return; ++ } ++ ++ if (mdev->nf_track.count < MCTP_NF_TRACK_MAX) { ++ hlist_add_head(&new_entry->node, &mdev->nf_track.buckets[idx]); ++ mdev->nf_track.count++; ++ netdev_dbg(mdev->dev, ++ "MCTP nf track store: src=%u dst=%u tag=0x%x verdict=%s count=%u\n", ++ src, dst, tag, ++ verdict == MCTP_NF_VERDICT_ACCEPT ? "accept" : "drop", ++ mdev->nf_track.count); ++ new_entry = NULL; ++ } else { ++ netdev_dbg(mdev->dev, ++ "MCTP nf track store: table full, drop entry src=%u dst=%u tag=0x%x\n", ++ src, dst, tag); ++ } ++ spin_unlock_bh(&mdev->nf_track.lock); ++ ++ kfree(new_entry); ++} ++ ++static bool mctp_nf_ingress_check(struct sk_buff *skb, struct mctp_dev *mdev, ++ const struct mctp_hdr *mh) ++{ ++ u8 flags = mh->flags_seq_tag & (MCTP_HDR_FLAG_SOM | MCTP_HDR_FLAG_EOM); ++ u8 tag = mh->flags_seq_tag & (MCTP_HDR_TAG_MASK | MCTP_HDR_FLAG_TO); ++ u8 verdict; ++ u8 msg_type = 0; ++ int ret; ++ bool som = !!(flags & MCTP_HDR_FLAG_SOM); ++ bool eom = !!(flags & MCTP_HDR_FLAG_EOM); ++ unsigned int type_off = skb_network_offset(skb) + sizeof(struct mctp_hdr); ++ ++ if (skb->len >= type_off + 1 && skb_copy_bits(skb, type_off, &msg_type, 1) == 0) ++ msg_type &= 0x7f; ++ ++ netdev_dbg(mdev->dev, ++ "MCTP nf ingress: src=%u dst=%u tag=0x%x SOM=%d EOM=%d type=%u\n", ++ mh->src, mh->dest, tag, som, eom, msg_type); ++ ++ if (!(flags & MCTP_HDR_FLAG_SOM)) { ++ if (mctp_nf_track_lookup(mdev, mh->src, mh->dest, tag, ++ &verdict)) { ++ if (flags & MCTP_HDR_FLAG_EOM) ++ mctp_nf_track_delete(mdev, mh->src, mh->dest, tag); ++ netdev_dbg(mdev->dev, ++ "MCTP nf ingress: fragment verdict=%s\n", ++ verdict == MCTP_NF_VERDICT_ACCEPT ? "accept" : "drop"); ++ return verdict == MCTP_NF_VERDICT_ACCEPT; ++ } ++ netdev_dbg(mdev->dev, ++ "MCTP nf ingress: fragment no track entry, pass to hook\n"); ++ } ++ ++ if (!nf_hook_ingress_active(skb)) { ++ netdev_dbg(mdev->dev, "MCTP nf ingress: no hooks, accept\n"); ++ return true; ++ } ++ ++ rcu_read_lock(); ++ ret = nf_hook_ingress(skb); ++ rcu_read_unlock(); ++ ++ if (ret < 0) { ++ netdev_dbg(mdev->dev, ++ "MCTP nf ingress: hook dropped (ret=%d)\n", ret); ++ if (!(flags & MCTP_HDR_FLAG_EOM)) ++ mctp_nf_track_store(mdev, mh->src, mh->dest, tag, ++ MCTP_NF_VERDICT_DROP); ++ return false; ++ } ++ ++ if (!(flags & MCTP_HDR_FLAG_EOM)) ++ mctp_nf_track_store(mdev, mh->src, mh->dest, tag, ++ MCTP_NF_VERDICT_ACCEPT); ++ ++ netdev_dbg(mdev->dev, "MCTP nf ingress: hook accept\n"); ++ return true; ++} ++ ++static int mctp_nf_egress_check(struct sk_buff **pskb, struct mctp_dev *mdev, ++ const struct mctp_hdr *mh) ++{ ++ struct sk_buff *skb = *pskb; ++ u8 flags = mh->flags_seq_tag & (MCTP_HDR_FLAG_SOM | MCTP_HDR_FLAG_EOM); ++ u8 tag = mh->flags_seq_tag & (MCTP_HDR_TAG_MASK | MCTP_HDR_FLAG_TO); ++ u8 verdict; ++ u8 msg_type = 0; ++ int rc = NET_XMIT_SUCCESS; ++ bool som = !!(flags & MCTP_HDR_FLAG_SOM); ++ bool eom = !!(flags & MCTP_HDR_FLAG_EOM); ++ unsigned int type_off = skb_network_offset(skb) + sizeof(struct mctp_hdr); ++ ++ if (skb->len >= type_off + 1 && skb_copy_bits(skb, type_off, &msg_type, 1) == 0) ++ msg_type &= 0x7f; ++ ++ netdev_dbg(mdev->dev, ++ "MCTP nf egress: src=%u dst=%u tag=0x%x SOM=%d EOM=%d type=%u\n", ++ mh->src, mh->dest, tag, som, eom, msg_type); ++ ++ if (!(flags & MCTP_HDR_FLAG_SOM)) { ++ if (mctp_nf_track_lookup(mdev, mh->src, mh->dest, tag, ++ &verdict)) { ++ if (flags & MCTP_HDR_FLAG_EOM) ++ mctp_nf_track_delete(mdev, mh->src, mh->dest, tag); ++ if (verdict == MCTP_NF_VERDICT_DROP) { ++ netdev_dbg(mdev->dev, ++ "MCTP nf egress: fragment verdict=drop\n"); ++ kfree_skb(skb); ++ *pskb = NULL; ++ return -EPERM; ++ } ++ netdev_dbg(mdev->dev, ++ "MCTP nf egress: fragment verdict=accept\n"); ++ return 0; ++ } ++ netdev_dbg(mdev->dev, ++ "MCTP nf egress: fragment no track entry, pass to hook\n"); ++ } ++ ++ if (!nf_hook_egress_active()) { ++ netdev_dbg(mdev->dev, "MCTP nf egress: no hooks, accept\n"); ++ return 0; ++ } ++ ++ rcu_read_lock_bh(); ++ skb = nf_hook_egress(skb, &rc, mdev->dev); ++ rcu_read_unlock_bh(); ++ ++ if (!skb) { ++ netdev_dbg(mdev->dev, ++ "MCTP nf egress: hook dropped (rc=%d)\n", rc); ++ *pskb = NULL; ++ return net_xmit_errno(rc); ++ } ++ ++ *pskb = skb; ++ ++ if (!(flags & MCTP_HDR_FLAG_EOM)) ++ mctp_nf_track_store(mdev, mh->src, mh->dest, tag, ++ MCTP_NF_VERDICT_ACCEPT); ++ ++ netdev_dbg(mdev->dev, "MCTP nf egress: hook accept\n"); ++ return 0; ++} ++ + /* route output callbacks */ +-static int mctp_route_discard(struct mctp_route *route, struct sk_buff *skb) ++static int mctp_dst_discard(struct mctp_dst *dst, struct sk_buff *skb) + { + kfree_skb(skb); + return 0; +@@ -57,6 +332,7 @@ static struct mctp_sock *mctp_lookup_bind(struct net *net, struct sk_buff *skb) + + sk_for_each_rcu(sk, &net->mctp.binds) { + struct mctp_sock *msk = container_of(sk, struct mctp_sock, sk); ++ int bound_dev_if; + + if (msk->bind_net != MCTP_NET_ANY && msk->bind_net != cb->net) + continue; +@@ -67,6 +343,12 @@ static struct mctp_sock *mctp_lookup_bind(struct net *net, struct sk_buff *skb) + if (!mctp_address_matches(msk->bind_addr, mh->dest)) + continue; + ++ /* Check SO_BINDTODEVICE constraint */ ++ bound_dev_if = READ_ONCE(sk->sk_bound_dev_if); ++ if (bound_dev_if && skb->dev && ++ bound_dev_if != skb->dev->ifindex) ++ continue; ++ + return msk; + } + +@@ -209,7 +491,8 @@ void mctp_key_unref(struct mctp_sk_key *key) + kfree(key); + } + +-static int mctp_key_add(struct mctp_sk_key *key, struct mctp_sock *msk) ++static int mctp_key_add(struct mctp_sk_key *key, struct mctp_sock *msk, ++ unsigned long lifetime) + { + struct net *net = sock_net(&msk->sk); + struct mctp_sk_key *tmp; +@@ -237,7 +520,7 @@ static int mctp_key_add(struct mctp_sk_key *key, struct mctp_sock *msk) + + if (!rc) { + refcount_inc(&key->refs); +- key->expiry = jiffies + mctp_key_lifetime; ++ key->expiry = jiffies + lifetime; + timer_reduce(&msk->key_expiry, key->expiry); + + hlist_add_head(&key->hlist, &net->mctp.keys); +@@ -325,17 +608,21 @@ static void mctp_skb_set_flow(struct sk_buff *skb, struct mctp_sk_key *key) {} + static void mctp_flow_prepare_output(struct sk_buff *skb, struct mctp_dev *dev) {} + #endif + ++/* takes ownership of skb, both in success and failure cases */ + static int mctp_frag_queue(struct mctp_sk_key *key, struct sk_buff *skb) + { + struct mctp_hdr *hdr = mctp_hdr(skb); + u8 exp_seq, this_seq; ++ int rc = 0; + + this_seq = (hdr->flags_seq_tag >> MCTP_HDR_SEQ_SHIFT) + & MCTP_HDR_SEQ_MASK; + + if (!key->reasm_head) { +- /* Since we're manipulating the shared frag_list, ensure it isn't +- * shared with any other SKBs. ++ /* Since we're manipulating the shared frag_list, ensure it ++ * isn't shared with any other SKBs. In the cloned case, ++ * this will free the skb; callers can no longer access it ++ * safely. + */ + key->reasm_head = skb_unshare(skb, GFP_ATOMIC); + if (!key->reasm_head) +@@ -343,16 +630,21 @@ static int mctp_frag_queue(struct mctp_sk_key *key, struct sk_buff *skb) + + key->reasm_tailp = &(skb_shinfo(key->reasm_head)->frag_list); + key->last_seq = this_seq; ++ trace_mctp_reassemble_start(hdr->src, hdr->dest, this_seq); + return 0; + } + + exp_seq = (key->last_seq + 1) & MCTP_HDR_SEQ_MASK; + +- if (this_seq != exp_seq) +- return -EINVAL; ++ if (this_seq != exp_seq) { ++ rc = -EINVAL; ++ goto err_free; ++ } + +- if (key->reasm_head->len + skb->len > mctp_message_maxlen) +- return -EINVAL; ++ if (key->reasm_head->len + skb->len > mctp_message_maxlen) { ++ rc = -EMSGSIZE; ++ goto err_free; ++ } + + skb->next = NULL; + skb->sk = NULL; +@@ -365,10 +657,134 @@ static int mctp_frag_queue(struct mctp_sk_key *key, struct sk_buff *skb) + key->reasm_head->len += skb->len; + key->reasm_head->truesize += skb->truesize; + +- return 0; ++ return rc; ++ ++err_free: ++ kfree_skb(skb); ++ return rc; ++} ++ ++/* ++ * mctp_report_rx_missing_som - Report error for middle/end fragment without SOM ++ * @key: RX MCTP key for this transaction (provides addressing for TX key lookup) ++ * @skb: The failed fragment ++ * @mh: MCTP header ++ * @tag: Tag value ++ * @flags: Lock flags (will be used to release/reacquire key->lock) ++ * ++ * This function must be called with key->lock held. It will temporarily release ++ * the lock to report the error (to avoid deadlock), then reacquire it. ++ * ++ * NEW BEHAVIOR: Looks up TX key and only reports if found. This ensures we only ++ * report errors for responses to OUR requests. ++ */ ++static void mctp_report_rx_missing_som(struct mctp_sk_key *key, ++ struct sk_buff *skb, ++ struct mctp_hdr *mh, ++ unsigned long tag, ++ unsigned long *flags) ++{ ++ struct sock *sk; ++ ++ netdev_err(skb->dev, ++ "MCTP RX: Middle/End fragment without SOM (src=%u, dest=%u, tag=%lu)\n", ++ mh->src, mh->dest, tag & MCTP_HDR_TAG_MASK); ++ ++ /* CRITICAL: Release key->lock BEFORE error reporting to avoid deadlock. ++ * mctp_queue_error() needs to acquire keys_lock for TX key lookup, but ++ * correct lock order is keys_lock → key->lock. We currently hold key->lock, ++ * so we must release it first. ++ */ ++ spin_unlock_irqrestore(&key->lock, *flags); ++ ++ /* Look up socket and report error. ++ * mctp_queue_error() will look up TX key internally and only report ++ * if TX key is found (our transaction). ++ */ ++ sk = mctp_lookup_sock_for_error(skb, skb->dev, key, NULL); ++ if (sk) { ++ /* Pass RX key - mctp_queue_error will use it to find TX key */ ++ mctp_queue_error(sk, skb, EPROTO, skb->dev, MCTP_DIR_RX, ++ mctp_get_binding_type(skb->dev), key); ++ sock_put(sk); ++ } ++ ++ /* Re-acquire key->lock for caller */ ++ spin_lock_irqsave(&key->lock, *flags); ++} ++ ++/* ++ * mctp_report_rx_sequence_error - Report RX sequence/size error ++ * @key: RX MCTP key for this transaction (provides addressing for TX key lookup) ++ * @skb: The failed fragment ++ * @mh: MCTP header ++ * @tag: Tag value ++ * @flags: Lock flags (will be used to release/reacquire key->lock) ++ * @err_code: Error code from mctp_frag_queue (-EINVAL or -EMSGSIZE) ++ * ++ * This function must be called with key->lock held. It will temporarily release ++ * the lock to report the error (to avoid deadlock), then reacquire it. ++ * ++ * NEW BEHAVIOR: Looks up TX key and only reports if found. This ensures we only ++ * report errors for responses to OUR requests. ++ */ ++static void mctp_report_rx_sequence_error(struct mctp_sk_key *key, ++ struct sk_buff *skb, ++ struct mctp_hdr *mh, ++ unsigned long tag, ++ unsigned long *flags, ++ int err_code) ++{ ++ struct sock *sk; ++ struct sk_buff *report_skb; ++ u8 exp_seq = (key->last_seq + 1) & MCTP_HDR_SEQ_MASK; ++ u8 this_seq = (mh->flags_seq_tag >> MCTP_HDR_SEQ_SHIFT) & MCTP_HDR_SEQ_MASK; ++ int report_errno; ++ ++ /* Determine error type and error code to report */ ++ if (err_code == -EMSGSIZE) { ++ netdev_err(skb->dev, ++ "MCTP RX: Message too large - exceeds 64KB limit (src=%u, dest=%u, tag=%lu)\n", ++ mh->src, mh->dest, tag & MCTP_HDR_TAG_MASK); ++ report_errno = EMSGSIZE; ++ } else { ++ /* err_code == -EINVAL - sequence error */ ++ netdev_err(skb->dev, ++ "MCTP RX: Sequence error - expected %u, got %u (src=%u, dest=%u, tag=%lu)\n", ++ exp_seq, this_seq, mh->src, mh->dest, ++ tag & MCTP_HDR_TAG_MASK); ++ report_errno = EPROTO; ++ } ++ ++ /* Use first response fragment (reasm_head) for addressing if available. ++ * Fall back to failed fragment only if reasm_head is NULL. ++ */ ++ report_skb = key->reasm_head ? key->reasm_head : skb; ++ ++ /* CRITICAL: Release key->lock BEFORE error reporting to avoid deadlock. ++ * mctp_queue_error() needs to acquire keys_lock for TX key lookup, but ++ * correct lock order is keys_lock → key->lock. We currently hold key->lock, ++ * so we must release it first. ++ */ ++ spin_unlock_irqrestore(&key->lock, *flags); ++ ++ /* Look up socket and report error. ++ * mctp_queue_error() will look up TX key internally and only report ++ * if TX key is found (our transaction). ++ */ ++ sk = mctp_lookup_sock_for_error(report_skb, skb->dev, key, NULL); ++ if (sk) { ++ /* Pass RX key - mctp_queue_error will use it to find TX key */ ++ mctp_queue_error(sk, report_skb, report_errno, skb->dev, MCTP_DIR_RX, ++ mctp_get_binding_type(skb->dev), key); ++ sock_put(sk); ++ } ++ ++ /* Re-acquire key->lock for caller */ ++ spin_lock_irqsave(&key->lock, *flags); + } + +-static int mctp_route_input(struct mctp_route *route, struct sk_buff *skb) ++static int mctp_dst_input(struct mctp_dst *dst, struct sk_buff *skb) + { + struct mctp_sk_key *key, *any_key = NULL; + struct net *net = dev_net(skb->dev); +@@ -392,9 +808,14 @@ static int mctp_route_input(struct mctp_route *route, struct sk_buff *skb) + */ + skb_orphan(skb); + ++ if (skb->pkt_type == PACKET_OUTGOING) ++ skb->pkt_type = PACKET_LOOPBACK; ++ + /* ensure we have enough data for a header and a type */ +- if (skb->len < sizeof(struct mctp_hdr) + 1) ++ if (skb->len < sizeof(struct mctp_hdr) + 1) { ++ trace_mctp_drop_packet(skb, "packet_too_short"); + goto out; ++ } + + /* grab header, advance data ptr */ + mh = mctp_hdr(skb); +@@ -439,6 +860,7 @@ static int mctp_route_input(struct mctp_route *route, struct sk_buff *skb) + msk = mctp_lookup_bind(net, skb); + + if (!msk) { ++ trace_mctp_drop_packet(skb, "no_socket_bound"); + rc = -ENOENT; + goto out_unlock; + } +@@ -447,9 +869,14 @@ static int mctp_route_input(struct mctp_route *route, struct sk_buff *skb) + * pending key. + */ + if (flags & MCTP_HDR_FLAG_EOM) { ++ trace_mctp_rx_packet(skb); + rc = sock_queue_rcv_skb(&msk->sk, skb); +- if (!rc) ++ trace_mctp_rx_socket(skb, rc); ++ if (!rc) { + skb = NULL; ++ } else { ++ trace_mctp_drop_packet(skb, "sock_queue_failed"); ++ } + if (key) { + /* we've hit a pending reassembly; not much we + * can do but drop it +@@ -465,6 +892,14 @@ static int mctp_route_input(struct mctp_route *route, struct sk_buff *skb) + * packets for this message + */ + if (!key) { ++ struct mctp_dev *mdev; ++ unsigned long lifetime; ++ ++ mdev = __mctp_dev_get(skb->dev); ++ lifetime = mdev ? mdev->key_lifetime : MCTP_DEFAULT_LIFETIME; ++ if (mdev) ++ mctp_dev_put(mdev); ++ + key = mctp_key_alloc(msk, netid, mh->dest, mh->src, + tag, GFP_ATOMIC); + if (!key) { +@@ -473,21 +908,42 @@ static int mctp_route_input(struct mctp_route *route, struct sk_buff *skb) + } + + /* we can queue without the key lock here, as the +- * key isn't observable yet +- */ ++ * key isn't observable yet ++ */ + mctp_frag_queue(key, skb); ++ ++ /* Cache message type and payload from first fragment (SOM) for error reporting. ++ * If middle/end fragments are missing (timeout), we can still report ++ * the correct message headers to the application. ++ * Use skb_network_header() not skb->data for consistency. ++ */ ++ if (skb && skb->len > sizeof(struct mctp_hdr)) { ++ u8 *msg_start = (u8 *)skb_network_header(skb); ++ size_t payload_offset, available, capture_len; ++ ++ /* Capture message type (first byte after MCTP header) */ ++ key->orig_msg_type = *(msg_start + sizeof(struct mctp_hdr)); ++ ++ /* Capture first 32 bytes of payload (after message type) for RX timeout errors */ ++ payload_offset = sizeof(struct mctp_hdr) + 1; ++ if (skb->len > payload_offset) { ++ available = skb->len - payload_offset; ++ capture_len = min_t(size_t, available, sizeof(key->orig_payload)); ++ memcpy(key->orig_payload, msg_start + payload_offset, capture_len); ++ key->orig_payload_len = capture_len; ++ } ++ } ++ ++ skb = NULL; + + /* if the key_add fails, we've raced with another + * SOM packet with the same src, dest and tag. There's + * no way to distinguish future packets, so all we +- * can do is drop; we'll free the skb on exit from +- * this function. ++ * can do is drop. + */ +- rc = mctp_key_add(key, msk); +- if (!rc) { ++ rc = mctp_key_add(key, msk, lifetime); ++ if (!rc) + trace_mctp_key_acquire(key); +- skb = NULL; +- } + + /* we don't need to release key->lock on exit, so + * clean up here and suppress the unlock via +@@ -505,8 +961,7 @@ static int mctp_route_input(struct mctp_route *route, struct sk_buff *skb) + key = NULL; + } else { + rc = mctp_frag_queue(key, skb); +- if (!rc) +- skb = NULL; ++ skb = NULL; + } + } + +@@ -516,24 +971,38 @@ static int mctp_route_input(struct mctp_route *route, struct sk_buff *skb) + */ + + /* we need to be continuing an existing reassembly... */ +- if (!key->reasm_head) ++ if (!key->reasm_head) { + rc = -EINVAL; +- else ++ mctp_report_rx_missing_som(key, skb, mh, tag, &f); ++ } else { + rc = mctp_frag_queue(key, skb); ++ ++ if (rc == -EINVAL || rc == -EMSGSIZE) { ++ /* Reassembly failure: sequence error (-EINVAL) or message too large (-EMSGSIZE) */ ++ mctp_report_rx_sequence_error(key, skb, mh, tag, &f, rc); ++ } ++ ++ skb = NULL; ++ } + + if (rc) + goto out_unlock; + +- /* we've queued; the queue owns the skb now */ +- skb = NULL; +- + /* end of message? deliver to socket, and we're done with + * the reassembly/response key + */ + if (flags & MCTP_HDR_FLAG_EOM) { ++ if (key->reasm_head) { ++ trace_mctp_reassemble_finish(mh->src, mh->dest, key->reasm_head->len); ++ trace_mctp_rx_packet(key->reasm_head); ++ } + rc = sock_queue_rcv_skb(key->sk, key->reasm_head); +- if (!rc) ++ if (key->reasm_head) { ++ trace_mctp_rx_socket(key->reasm_head, rc); ++ } ++ if (!rc) { + key->reasm_head = NULL; ++ } + __mctp_key_done_in(key, net, f, MCTP_TRACE_KEY_REPLIED); + key = NULL; + } +@@ -556,39 +1025,48 @@ static int mctp_route_input(struct mctp_route *route, struct sk_buff *skb) + return rc; + } + +-static unsigned int mctp_route_mtu(struct mctp_route *rt) +-{ +- return rt->mtu ?: READ_ONCE(rt->dev->dev->mtu); +-} +- +-static int mctp_route_output(struct mctp_route *route, struct sk_buff *skb) ++static int mctp_dst_output(struct mctp_dst *dst, struct sk_buff *skb) + { +- struct mctp_skb_cb *cb = mctp_cb(skb); + struct mctp_hdr *hdr = mctp_hdr(skb); + char daddr_buf[MAX_ADDR_LEN]; + char *daddr = NULL; +- unsigned int mtu; + int rc; + +- skb->protocol = htons(ETH_P_MCTP); ++ /* Tunnel: packet egress from one net dev to another (e.g. USB -> I2C). ++ * Must do neighbour lookup for egress device; stashed hwaddr is for ingress. ++ */ ++ bool is_tunnel = (skb->dev != dst->dev->dev); + +- mtu = READ_ONCE(skb->dev->mtu); +- if (skb->len > mtu) { +- kfree_skb(skb); +- return -EMSGSIZE; ++ /* Update skb->dev to the outgoing device (needed for forwarding). */ ++ skb->dev = dst->dev->dev; ++ skb->pkt_type = PACKET_OUTGOING; ++ ++ /* Check if this is a batched SKB (marked by protocol field with high bit ++ * set). Batched SKBs are intentionally larger than MTU as they contain ++ * multiple MCTP packets packed together. The driver will clear this marker. ++ */ ++ bool is_batched = (skb->protocol == htons(ETH_P_MCTP | 0x8000)); ++ ++ /* Batched SKBs (protocol with high bit set) skip MTU check */ ++ if (!is_batched) { ++ skb->protocol = htons(ETH_P_MCTP); ++ if (skb->len > dst->mtu) { ++ kfree_skb(skb); ++ return -EMSGSIZE; ++ } + } + +- if (cb->ifindex) { +- /* direct route; use the hwaddr we stashed in sendmsg */ +- if (cb->halen != skb->dev->addr_len) { ++ /* direct route; use the hwaddr we stashed in sendmsg (unless tunnel) */ ++ if (dst->halen && !is_tunnel) { ++ if (dst->halen != skb->dev->addr_len) { + /* sanity check, sendmsg should have already caught this */ + kfree_skb(skb); + return -EMSGSIZE; + } +- daddr = cb->haddr; ++ daddr = dst->haddr; + } else { + /* If lookup fails let the device handle daddr==NULL */ +- if (mctp_neigh_lookup(route->dev, hdr->dest, daddr_buf) == 0) ++ if (mctp_neigh_lookup(dst->dev, dst->nexthop, daddr_buf) == 0) + daddr = daddr_buf; + } + +@@ -599,8 +1077,22 @@ static int mctp_route_output(struct mctp_route *route, struct sk_buff *skb) + return -EHOSTUNREACH; + } + +- mctp_flow_prepare_output(skb, route->dev); ++ mctp_flow_prepare_output(skb, dst->dev); ++ ++ if (!is_batched) { ++ rc = mctp_nf_egress_check(&skb, dst->dev, hdr); ++ if (!skb) { ++ netdev_dbg( ++ dst->dev->dev, ++ "MCTP nf: egress filter dropped packet src=%u dst=%u rc=%d\n", ++ hdr->src, hdr->dest, rc); ++ return rc; ++ } ++ if (rc) ++ return rc; ++ } + ++ trace_mctp_route_output(skb, skb->dev); + rc = dev_queue_xmit(skb); + if (rc) + rc = net_xmit_errno(rc); +@@ -612,7 +1104,8 @@ static int mctp_route_output(struct mctp_route *route, struct sk_buff *skb) + static void mctp_route_release(struct mctp_route *rt) + { + if (refcount_dec_and_test(&rt->refs)) { +- mctp_dev_put(rt->dev); ++ if (rt->dst_type == MCTP_ROUTE_DIRECT) ++ mctp_dev_put(rt->dev); + kfree_rcu(rt, rcu); + } + } +@@ -628,7 +1121,7 @@ static struct mctp_route *mctp_route_alloc(void) + + INIT_LIST_HEAD(&rt->list); + refcount_set(&rt->refs, 1); +- rt->output = mctp_route_discard; ++ rt->output = mctp_dst_discard; + + return rt; + } +@@ -648,13 +1141,13 @@ int mctp_default_net_set(struct net *net, unsigned int index) + + /* tag management */ + static void mctp_reserve_tag(struct net *net, struct mctp_sk_key *key, +- struct mctp_sock *msk) ++ struct mctp_sock *msk, unsigned long lifetime) + { + struct netns_mctp *mns = &net->mctp; + + lockdep_assert_held(&mns->keys_lock); + +- key->expiry = jiffies + mctp_key_lifetime; ++ key->expiry = jiffies + lifetime; + timer_reduce(&msk->key_expiry, key->expiry); + + /* we hold the net->key_lock here, allowing updates to both +@@ -671,7 +1164,8 @@ static void mctp_reserve_tag(struct net *net, struct mctp_sk_key *key, + struct mctp_sk_key *mctp_alloc_local_tag(struct mctp_sock *msk, + unsigned int netid, + mctp_eid_t local, mctp_eid_t peer, +- bool manual, u8 *tagp) ++ bool manual, u8 *tagp, ++ unsigned long lifetime) + { + struct net *net = sock_net(&msk->sk); + struct netns_mctp *mns = &net->mctp; +@@ -735,7 +1229,7 @@ struct mctp_sk_key *mctp_alloc_local_tag(struct mctp_sock *msk, + + if (tagbits) { + key->tag = __ffs(tagbits); +- mctp_reserve_tag(net, key, msk); ++ mctp_reserve_tag(net, key, msk, lifetime); + trace_mctp_key_acquire(key); + + key->manual_alloc = manual; +@@ -801,10 +1295,16 @@ static struct mctp_sk_key *mctp_lookup_prealloc_tag(struct mctp_sock *msk, + } + + /* routing lookups */ ++static unsigned int mctp_route_netid(struct mctp_route *rt) ++{ ++ return rt->dst_type == MCTP_ROUTE_DIRECT ? ++ READ_ONCE(rt->dev->net) : rt->gateway.net; ++} ++ + static bool mctp_rt_match_eid(struct mctp_route *rt, + unsigned int net, mctp_eid_t eid) + { +- return READ_ONCE(rt->dev->net) == net && ++ return mctp_route_netid(rt) == net && + rt->min <= eid && rt->max >= eid; + } + +@@ -813,61 +1313,284 @@ static bool mctp_rt_compare_exact(struct mctp_route *rt1, + struct mctp_route *rt2) + { + ASSERT_RTNL(); +- return rt1->dev->net == rt2->dev->net && +- rt1->min == rt2->min && +- rt1->max == rt2->max; ++ ++ if (mctp_route_netid(rt1) != mctp_route_netid(rt2)) ++ return false; ++ ++ if (rt1->max < rt2->min || rt1->min > rt2->max) ++ return false; ++ ++ /* Allow multiple local routes for same EID on different interfaces */ ++ if (rt1->type == RTN_LOCAL && rt2->type == RTN_LOCAL && ++ rt1->dev != rt2->dev) ++ return false; ++ ++ return true; ++} ++ ++/* must only be called on a direct route, as the final output hop */ ++static void mctp_dst_from_route(struct mctp_dst *dst, mctp_eid_t eid, ++ unsigned int mtu, struct mctp_route *route) ++{ ++ mctp_dev_hold(route->dev); ++ dst->nexthop = eid; ++ dst->dev = route->dev; ++ dst->mtu = READ_ONCE(dst->dev->dev->mtu); ++ if (mtu) ++ dst->mtu = min(dst->mtu, mtu); ++ dst->halen = 0; ++ dst->output = route->output; + } + +-struct mctp_route *mctp_route_lookup(struct net *net, unsigned int dnet, +- mctp_eid_t daddr) ++int mctp_dst_from_extaddr(struct mctp_dst *dst, struct net *net, int ifindex, ++ unsigned char halen, const unsigned char *haddr) + { +- struct mctp_route *tmp, *rt = NULL; ++ struct net_device *netdev; ++ struct mctp_dev *dev; ++ int rc = -ENOENT; ++ ++ if (halen > sizeof(dst->haddr)) ++ return -EINVAL; + + rcu_read_lock(); + +- list_for_each_entry_rcu(tmp, &net->mctp.routes, list) { +- /* TODO: add metrics */ +- if (mctp_rt_match_eid(tmp, dnet, daddr)) { +- if (refcount_inc_not_zero(&tmp->refs)) { +- rt = tmp; +- break; +- } +- } ++ netdev = dev_get_by_index_rcu(net, ifindex); ++ if (!netdev) ++ goto out_unlock; ++ ++ if (netdev->addr_len != halen) { ++ rc = -EINVAL; ++ goto out_unlock; + } + ++ dev = __mctp_dev_get(netdev); ++ if (!dev) ++ goto out_unlock; ++ ++ dst->dev = dev; ++ dst->mtu = READ_ONCE(netdev->mtu); ++ dst->halen = halen; ++ dst->output = mctp_dst_output; ++ dst->nexthop = 0; ++ memcpy(dst->haddr, haddr, halen); ++ ++ rc = 0; ++ ++out_unlock: + rcu_read_unlock(); ++ return rc; ++} + +- return rt; ++void mctp_dst_release(struct mctp_dst *dst) ++{ ++ mctp_dev_put(dst->dev); + } + +-static struct mctp_route *mctp_route_lookup_null(struct net *net, +- struct net_device *dev) ++static struct mctp_route *mctp_route_lookup_single(struct net *net, ++ unsigned int dnet, ++ mctp_eid_t daddr) + { +- struct mctp_route *tmp, *rt = NULL; ++ struct mctp_route *rt; ++ ++ list_for_each_entry_rcu(rt, &net->mctp.routes, list) { ++ if (mctp_rt_match_eid(rt, dnet, daddr)) ++ return rt; ++ } ++ ++ return NULL; ++} ++ ++/* populates *dst on successful lookup, if set */ ++int mctp_route_lookup(struct net *net, unsigned int dnet, ++ mctp_eid_t daddr, struct mctp_dst *dst) ++{ ++ const unsigned int max_depth = 32; ++ unsigned int depth, mtu = 0; ++ int rc = -EHOSTUNREACH; + + rcu_read_lock(); + +- list_for_each_entry_rcu(tmp, &net->mctp.routes, list) { +- if (tmp->dev->dev == dev && tmp->type == RTN_LOCAL && +- refcount_inc_not_zero(&tmp->refs)) { +- rt = tmp; ++ for (depth = 0; depth < max_depth; depth++) { ++ struct mctp_route *rt; ++ ++ rt = mctp_route_lookup_single(net, dnet, daddr); ++ if (!rt) ++ break; ++ ++ /* clamp mtu to the smallest in the path, allowing 0 ++ * to specify no restrictions ++ */ ++ if (mtu && rt->mtu) ++ mtu = min(mtu, rt->mtu); ++ else ++ mtu = mtu ?: rt->mtu; ++ ++ if (rt->dst_type == MCTP_ROUTE_DIRECT) { ++ if (dst) ++ mctp_dst_from_route(dst, daddr, mtu, rt); ++ rc = 0; + break; ++ ++ } else if (rt->dst_type == MCTP_ROUTE_GATEWAY) { ++ daddr = rt->gateway.eid; + } + } + + rcu_read_unlock(); + +- return rt; ++ return rc; + } + +-static int mctp_do_fragment_route(struct mctp_route *rt, struct sk_buff *skb, +- unsigned int mtu, u8 tag) ++static int mctp_route_lookup_null(struct net *net, struct net_device *dev, ++ struct mctp_dst *dst) ++{ ++ int rc = -EHOSTUNREACH; ++ struct mctp_route *rt; ++ ++ rcu_read_lock(); ++ ++ list_for_each_entry_rcu(rt, &net->mctp.routes, list) { ++ if (rt->dst_type != MCTP_ROUTE_DIRECT || rt->type != RTN_LOCAL) ++ continue; ++ ++ if (rt->dev->dev != dev) ++ continue; ++ ++ mctp_dst_from_route(dst, 0, 0, rt); ++ rc = 0; ++ break; ++ } ++ ++ rcu_read_unlock(); ++ ++ return rc; ++} ++ ++/* Fragment and batch: pack multiple fragments into a single SKB with space ++ * for transport headers. The transport driver will fill in headers and send. ++ * This function may send multiple batches if the message is large. ++ */ ++static int mctp_do_fragment_route_batch(struct mctp_dst *dst, struct sk_buff *skb, ++ unsigned int mtu, u8 tag, ++ unsigned int batch_hdr_len, ++ unsigned int batch_max_xfer) + { + const unsigned int hlen = sizeof(struct mctp_hdr); + struct mctp_hdr *hdr, *hdr2; ++ struct mctp_skb_cb *cb; ++ struct sk_buff *batch_skb; + unsigned int pos, size, headroom; +- struct sk_buff *skb2; +- int rc; ++ unsigned int total_len, num_frags; ++ unsigned int skb_pos; ++ u8 *batch_data; ++ u8 seq; ++ int rc; ++ ++ hdr = mctp_hdr(skb); ++ seq = 0; ++ headroom = skb_headroom(skb); ++ ++ /* we've got the header */ ++ skb_pull(skb, hlen); ++ ++ skb_pos = 0; ++ rc = 0; ++ ++ while (skb_pos < skb->len) { ++ total_len = 0; ++ num_frags = 0; ++ for (pos = skb_pos; pos < skb->len;) { ++ size = min(mtu - hlen, skb->len - pos); ++ total_len += batch_hdr_len + hlen + size; ++ num_frags++; ++ pos += size; ++ if (total_len + batch_hdr_len + hlen + 1 > batch_max_xfer) ++ break; ++ } ++ ++ batch_skb = alloc_skb(headroom + total_len, GFP_KERNEL); ++ if (!batch_skb) { ++ kfree_skb(skb); ++ return -ENOMEM; ++ } ++ ++ batch_skb->protocol = htons(ETH_P_MCTP | 0x8000); /* Mark as batched */ ++ batch_skb->priority = skb->priority; ++ batch_skb->dev = skb->dev; ++ memcpy(batch_skb->cb, skb->cb, sizeof(batch_skb->cb)); ++ ++ if (skb->sk) ++ skb_set_owner_w(batch_skb, skb->sk); ++ ++ skb_reserve(batch_skb, headroom); ++ skb_reset_network_header(batch_skb); ++ batch_data = skb_put(batch_skb, total_len); ++ ++ cb = mctp_cb(batch_skb); ++ cb->net = mctp_cb(skb)->net; ++ ++ skb_ext_copy(batch_skb, skb); ++ ++ pos = skb_pos; ++ while (num_frags--) { ++ unsigned int pkt_len; ++ bool is_last_fragment_in_message; ++ void *transport_hdr; ++ ++ size = min(mtu - hlen, skb->len - pos); ++ is_last_fragment_in_message = (pos + size >= skb->len); ++ pkt_len = batch_hdr_len + hlen + size; ++ ++ transport_hdr = batch_data; ++ batch_data += batch_hdr_len; ++ ++ hdr2 = (struct mctp_hdr *)batch_data; ++ hdr2->ver = hdr->ver; ++ hdr2->dest = hdr->dest; ++ hdr2->src = hdr->src; ++ hdr2->flags_seq_tag = tag & ++ (MCTP_HDR_TAG_MASK | MCTP_HDR_FLAG_TO); ++ ++ if (pos == 0) ++ hdr2->flags_seq_tag |= MCTP_HDR_FLAG_SOM; ++ if (is_last_fragment_in_message) ++ hdr2->flags_seq_tag |= MCTP_HDR_FLAG_EOM; ++ ++ hdr2->flags_seq_tag |= seq << MCTP_HDR_SEQ_SHIFT; ++ ++ skb_copy_bits(skb, pos, batch_data + hlen, size); ++ ++ if (dst->dev->ops && dst->dev->ops->fill_batch_hdr) ++ dst->dev->ops->fill_batch_hdr(transport_hdr, pkt_len); ++ ++ batch_data += hlen + size; ++ seq = (seq + 1) & MCTP_HDR_SEQ_MASK; ++ pos += size; ++ } ++ ++ skb_pos = pos; ++ ++ rc = dst->output(dst, batch_skb); ++ if (rc) { ++ rc = net_xmit_errno(rc); ++ break; ++ } ++ } ++ ++ consume_skb(skb); ++ return rc; ++} ++ ++static int mctp_do_fragment_route(struct mctp_dst *dst, struct sk_buff *skb, ++ unsigned int mtu, u8 tag) ++{ ++ const unsigned int hlen = sizeof(struct mctp_hdr); ++ struct mctp_hdr *hdr, *hdr2; ++ unsigned int pos, size, headroom; ++ struct sk_buff *skb2; ++ unsigned int batch_hdr_len; ++ unsigned int batch_max_xfer; ++ int rc; + u8 seq; + + hdr = mctp_hdr(skb); +@@ -879,6 +1602,16 @@ static int mctp_do_fragment_route(struct mctp_route *rt, struct sk_buff *skb, + return -EMSGSIZE; + } + ++ /* If batching is enabled on the device, use the batch path */ ++ if (dst->dev && dst->dev->tx_batching_enabled) { ++ batch_hdr_len = dst->dev->tx_batch_hdr_len; ++ batch_max_xfer = dst->dev->tx_batch_max_xfer; ++ if (batch_hdr_len && batch_max_xfer) ++ return mctp_do_fragment_route_batch(dst, skb, mtu, tag, ++ batch_hdr_len, ++ batch_max_xfer); ++ } ++ + /* keep same headroom as the original skb */ + headroom = skb_headroom(skb); + +@@ -886,8 +1619,11 @@ static int mctp_do_fragment_route(struct mctp_route *rt, struct sk_buff *skb, + skb_pull(skb, hlen); + + for (pos = 0; pos < skb->len;) { ++ bool is_last_fragment; ++ + /* size of message payload */ + size = min(mtu - hlen, skb->len - pos); ++ is_last_fragment = (pos + size >= skb->len); + + skb2 = alloc_skb(headroom + hlen + size, GFP_KERNEL); + if (!skb2) { +@@ -921,7 +1657,7 @@ static int mctp_do_fragment_route(struct mctp_route *rt, struct sk_buff *skb, + if (pos == 0) + hdr2->flags_seq_tag |= MCTP_HDR_FLAG_SOM; + +- if (pos + size == skb->len) ++ if (is_last_fragment) + hdr2->flags_seq_tag |= MCTP_HDR_FLAG_EOM; + + hdr2->flags_seq_tag |= seq << MCTP_HDR_SEQ_SHIFT; +@@ -933,7 +1669,7 @@ static int mctp_do_fragment_route(struct mctp_route *rt, struct sk_buff *skb, + skb_ext_copy(skb2, skb); + + /* do route */ +- rc = rt->output(rt, skb2); ++ rc = dst->output(dst, skb2); + if (rc) + break; + +@@ -945,68 +1681,34 @@ static int mctp_do_fragment_route(struct mctp_route *rt, struct sk_buff *skb, + return rc; + } + +-int mctp_local_output(struct sock *sk, struct mctp_route *rt, ++int mctp_local_output(struct sock *sk, struct mctp_dst *dst, + struct sk_buff *skb, mctp_eid_t daddr, u8 req_tag) + { + struct mctp_sock *msk = container_of(sk, struct mctp_sock, sk); +- struct mctp_skb_cb *cb = mctp_cb(skb); +- struct mctp_route tmp_rt = {0}; + struct mctp_sk_key *key; + struct mctp_hdr *hdr; + unsigned long flags; + unsigned int netid; + unsigned int mtu; + mctp_eid_t saddr; +- bool ext_rt; + int rc; + u8 tag; + +- rc = -ENODEV; +- +- if (rt) { +- ext_rt = false; +- if (WARN_ON(!rt->dev)) +- goto out_release; +- +- } else if (cb->ifindex) { +- struct net_device *dev; ++ KUNIT_STATIC_STUB_REDIRECT(mctp_local_output, sk, dst, skb, daddr, ++ req_tag); + +- ext_rt = true; +- rt = &tmp_rt; +- +- rcu_read_lock(); +- dev = dev_get_by_index_rcu(sock_net(sk), cb->ifindex); +- if (!dev) { +- rcu_read_unlock(); +- goto out_free; +- } +- rt->dev = __mctp_dev_get(dev); +- rcu_read_unlock(); +- +- if (!rt->dev) +- goto out_release; +- +- /* establish temporary route - we set up enough to keep +- * mctp_route_output happy +- */ +- rt->output = mctp_route_output; +- rt->mtu = 0; +- +- } else { +- rc = -EINVAL; +- goto out_free; +- } ++ rc = -ENODEV; + +- spin_lock_irqsave(&rt->dev->addrs_lock, flags); +- if (rt->dev->num_addrs == 0) { ++ spin_lock_irqsave(&dst->dev->addrs_lock, flags); ++ if (dst->dev->num_addrs == 0) { + rc = -EHOSTUNREACH; + } else { + /* use the outbound interface's first address as our source */ +- saddr = rt->dev->addrs[0]; ++ saddr = dst->dev->addrs[0]; + rc = 0; + } +- spin_unlock_irqrestore(&rt->dev->addrs_lock, flags); +- netid = READ_ONCE(rt->dev->net); ++ spin_unlock_irqrestore(&dst->dev->addrs_lock, flags); ++ netid = READ_ONCE(dst->dev->net); + + if (rc) + goto out_release; +@@ -1015,9 +1717,15 @@ int mctp_local_output(struct sock *sk, struct mctp_route *rt, + if (req_tag & MCTP_TAG_PREALLOC) + key = mctp_lookup_prealloc_tag(msk, netid, daddr, + req_tag, &tag); +- else ++ else { ++ unsigned long lifetime = MCTP_DEFAULT_LIFETIME; ++ ++ if (dst->dev) ++ lifetime = dst->dev->key_lifetime; ++ + key = mctp_alloc_local_tag(msk, netid, saddr, daddr, +- false, &tag); ++ false, &tag, lifetime); ++ } + + if (IS_ERR(key)) { + rc = PTR_ERR(key); +@@ -1032,15 +1740,13 @@ int mctp_local_output(struct sock *sk, struct mctp_route *rt, + tag = req_tag & MCTP_TAG_MASK; + } + ++ skb->pkt_type = PACKET_OUTGOING; + skb->protocol = htons(ETH_P_MCTP); + skb->priority = 0; + skb_reset_transport_header(skb); + skb_push(skb, sizeof(struct mctp_hdr)); + skb_reset_network_header(skb); +- skb->dev = rt->dev->dev; +- +- /* cb->net will have been set on initial ingress */ +- cb->src = saddr; ++ skb->dev = dst->dev->dev; + + /* set up common header fields */ + hdr = mctp_hdr(skb); +@@ -1048,86 +1754,108 @@ int mctp_local_output(struct sock *sk, struct mctp_route *rt, + hdr->dest = daddr; + hdr->src = saddr; + +- mtu = mctp_route_mtu(rt); ++ /* Capture original message header for error reporting on fragmented messages. ++ * This ensures that if a middle or end fragment fails, we can still report ++ * the original header (PLDM Instance ID, etc.) to the application. ++ */ ++ if (key && skb->len > sizeof(struct mctp_hdr)) { ++ unsigned long flags2; ++ size_t payload_offset, available, capture_len; ++ ++ spin_lock_irqsave(&key->lock, flags2); + ++ /* Capture message type (first byte after MCTP header) */ ++ key->orig_msg_type = *((u8 *)(skb->data + sizeof(struct mctp_hdr))); ++ ++ /* Capture first 32 bytes of payload (after message type) */ ++ payload_offset = sizeof(struct mctp_hdr) + 1; ++ available = skb->len - payload_offset; ++ capture_len = min_t(size_t, available, sizeof(key->orig_payload)); ++ ++ if (capture_len > 0) { ++ memcpy(key->orig_payload, skb->data + payload_offset, capture_len); ++ key->orig_payload_len = capture_len; ++ } else { ++ key->orig_payload_len = 0; ++ } ++ ++ spin_unlock_irqrestore(&key->lock, flags2); ++ } ++ ++ mtu = dst->mtu; ++ ++ trace_mctp_local_output(saddr, daddr, tag, skb->len); + if (skb->len + sizeof(struct mctp_hdr) <= mtu) { + hdr->flags_seq_tag = MCTP_HDR_FLAG_SOM | + MCTP_HDR_FLAG_EOM | tag; +- rc = rt->output(rt, skb); ++ rc = dst->output(dst, skb); + } else { +- rc = mctp_do_fragment_route(rt, skb, mtu, tag); ++ rc = mctp_do_fragment_route(dst, skb, mtu, tag); + } + + /* route output functions consume the skb, even on error */ + skb = NULL; + + out_release: +- if (!ext_rt) +- mctp_route_release(rt); +- +- mctp_dev_put(tmp_rt.dev); +- +-out_free: + kfree_skb(skb); + return rc; + } + + /* route management */ +-static int mctp_route_add(struct mctp_dev *mdev, mctp_eid_t daddr_start, +- unsigned int daddr_extent, unsigned int mtu, +- unsigned char type) ++ ++/* mctp_route_add(): Add the provided route, previously allocated via ++ * mctp_route_alloc(). On success, takes ownership of @rt, which includes a ++ * hold on rt->dev for usage in the route table. On failure a caller will want ++ * to mctp_route_release(). ++ * ++ * We expect that the caller has set rt->type, rt->dst_type, rt->min, rt->max, ++ * rt->mtu and either rt->dev (with a reference held appropriately) or ++ * rt->gateway. Other fields will be populated. ++ */ ++static int mctp_route_add(struct net *net, struct mctp_route *rt) + { +- int (*rtfn)(struct mctp_route *rt, struct sk_buff *skb); +- struct net *net = dev_net(mdev->dev); +- struct mctp_route *rt, *ert; ++ struct mctp_route *ert; + +- if (!mctp_address_unicast(daddr_start)) ++ if (!mctp_address_unicast(rt->min) || !mctp_address_unicast(rt->max)) + return -EINVAL; + +- if (daddr_extent > 0xff || daddr_start + daddr_extent >= 255) ++ if (rt->dst_type == MCTP_ROUTE_DIRECT && !rt->dev) + return -EINVAL; + +- switch (type) { ++ if (rt->dst_type == MCTP_ROUTE_GATEWAY && !rt->gateway.eid) ++ return -EINVAL; ++ ++ switch (rt->type) { + case RTN_LOCAL: +- rtfn = mctp_route_input; ++ rt->output = mctp_dst_input; + break; + case RTN_UNICAST: +- rtfn = mctp_route_output; ++ rt->output = mctp_dst_output; + break; + default: + return -EINVAL; + } + +- rt = mctp_route_alloc(); +- if (!rt) +- return -ENOMEM; +- +- rt->min = daddr_start; +- rt->max = daddr_start + daddr_extent; +- rt->mtu = mtu; +- rt->dev = mdev; +- mctp_dev_hold(rt->dev); +- rt->type = type; +- rt->output = rtfn; +- + ASSERT_RTNL(); ++ + /* Prevent duplicate identical routes. */ + list_for_each_entry(ert, &net->mctp.routes, list) { + if (mctp_rt_compare_exact(rt, ert)) { +- mctp_route_release(rt); + return -EEXIST; + } + } + + list_add_rcu(&rt->list, &net->mctp.routes); + ++ if (rt->dev) ++ trace_mctp_route_add(rt->dev->dev, rt->min, rt->max - rt->min, rt->mtu); + return 0; + } + +-static int mctp_route_remove(struct mctp_dev *mdev, mctp_eid_t daddr_start, +- unsigned int daddr_extent, unsigned char type) ++static int mctp_route_remove(struct net *net, unsigned int netid, ++ mctp_eid_t daddr_start, unsigned int daddr_extent, ++ unsigned char type) + { +- struct net *net = dev_net(mdev->dev); + struct mctp_route *rt, *tmp; + mctp_eid_t daddr_end; + bool dropped; +@@ -1141,9 +1869,11 @@ static int mctp_route_remove(struct mctp_dev *mdev, mctp_eid_t daddr_start, + ASSERT_RTNL(); + + list_for_each_entry_safe(rt, tmp, &net->mctp.routes, list) { +- if (rt->dev == mdev && ++ if (mctp_route_netid(rt) == netid && + rt->min == daddr_start && rt->max == daddr_end && + rt->type == type) { ++ if (rt->dev) ++ trace_mctp_route_del(rt->dev->dev, rt->min, rt->max - rt->min); + list_del_rcu(&rt->list); + /* TODO: immediate RTM_DELROUTE */ + mctp_route_release(rt); +@@ -1156,12 +1886,32 @@ static int mctp_route_remove(struct mctp_dev *mdev, mctp_eid_t daddr_start, + + int mctp_route_add_local(struct mctp_dev *mdev, mctp_eid_t addr) + { +- return mctp_route_add(mdev, addr, 0, 0, RTN_LOCAL); ++ struct mctp_route *rt; ++ int rc; ++ ++ rt = mctp_route_alloc(); ++ if (!rt) ++ return -ENOMEM; ++ ++ rt->min = addr; ++ rt->max = addr; ++ rt->dst_type = MCTP_ROUTE_DIRECT; ++ rt->dev = mdev; ++ rt->type = RTN_LOCAL; ++ ++ mctp_dev_hold(rt->dev); ++ ++ rc = mctp_route_add(dev_net(mdev->dev), rt); ++ if (rc) ++ mctp_route_release(rt); ++ ++ return rc; + } + + int mctp_route_remove_local(struct mctp_dev *mdev, mctp_eid_t addr) + { +- return mctp_route_remove(mdev, addr, 0, RTN_LOCAL); ++ return mctp_route_remove(dev_net(mdev->dev), mdev->net, ++ addr, 0, RTN_LOCAL); + } + + /* removes all entries for a given device */ +@@ -1172,7 +1922,7 @@ void mctp_route_remove_dev(struct mctp_dev *mdev) + + ASSERT_RTNL(); + list_for_each_entry_safe(rt, tmp, &net->mctp.routes, list) { +- if (rt->dev == mdev) { ++ if (rt->dst_type == MCTP_ROUTE_DIRECT && rt->dev == mdev) { + list_del_rcu(&rt->list); + /* TODO: immediate RTM_DELROUTE */ + mctp_route_release(rt); +@@ -1180,6 +1930,37 @@ void mctp_route_remove_dev(struct mctp_dev *mdev) + } + } + ++/* Lookup bound socket for packet delivery when no route exists */ ++static struct mctp_route *mctp_route_lookup_bound_socket(struct net *net, struct sk_buff *skb) ++{ ++ struct mctp_hdr *mh; ++ struct mctp_sock *msk; ++ struct mctp_route *rt = NULL; ++ ++ WARN_ON(!rcu_read_lock_held()); ++ ++ mh = mctp_hdr(skb); ++ ++ if (!skb_headlen(skb)) ++ return NULL; ++ ++ /* Look for bound sockets that match this packet */ ++ msk = mctp_lookup_bind(net, skb); ++ if (msk) { ++ /* Create a temporary route for socket delivery */ ++ rt = mctp_route_alloc(); ++ if (rt) { ++ rt->min = mh->dest; ++ rt->max = mh->dest; ++ rt->type = RTN_LOCAL; ++ rt->output = mctp_dst_input; ++ rt->dev = NULL; ++ } ++ } ++ ++ return rt; ++} ++ + /* Incoming packet-handling */ + + static int mctp_pkttype_receive(struct sk_buff *skb, struct net_device *dev, +@@ -1189,8 +1970,9 @@ static int mctp_pkttype_receive(struct sk_buff *skb, struct net_device *dev, + struct net *net = dev_net(dev); + struct mctp_dev *mdev; + struct mctp_skb_cb *cb; +- struct mctp_route *rt; ++ struct mctp_dst dst; + struct mctp_hdr *mh; ++ int rc; + + rcu_read_lock(); + mdev = __mctp_dev_get(dev); +@@ -1232,17 +2014,39 @@ static int mctp_pkttype_receive(struct sk_buff *skb, struct net_device *dev, + cb->net = READ_ONCE(mdev->net); + cb->ifindex = dev->ifindex; + +- rt = mctp_route_lookup(net, cb->net, mh->dest); ++ if (!mctp_nf_ingress_check(skb, mdev, mh)) { ++ netdev_dbg(dev, "MCTP nf: ingress filter dropped packet src=%u dst=%u\n", ++ mh->src, mh->dest); ++ goto err_drop; ++ } ++ ++ rc = mctp_route_lookup(net, cb->net, mh->dest, &dst); + + /* NULL EID, but addressed to our physical address */ +- if (!rt && mh->dest == MCTP_ADDR_NULL && skb->pkt_type == PACKET_HOST) +- rt = mctp_route_lookup_null(net, dev); ++ if (rc && mh->dest == MCTP_ADDR_NULL && skb->pkt_type == PACKET_HOST) { ++ rc = mctp_route_lookup_null(net, dev, &dst); ++ if (rc) { ++ /* No RTN_LOCAL route: try bound sockets so NULL EID reaches them */ ++ struct mctp_route *rt; ++ ++ rcu_read_lock(); ++ rt = mctp_route_lookup_bound_socket(net, skb); ++ rcu_read_unlock(); ++ if (rt) { ++ rt->dev = mdev; ++ mctp_dev_hold(rt->dev); ++ mctp_dst_from_route(&dst, mh->dest, 0, rt); ++ mctp_route_release(rt); ++ rc = 0; ++ } ++ } ++ } + +- if (!rt) ++ if (rc) + goto err_drop; + +- rt->output(rt, skb); +- mctp_route_release(rt); ++ dst.output(&dst, skb); ++ mctp_dst_release(&dst); + mctp_dev_put(mdev); + + return NET_RX_SUCCESS; +@@ -1264,19 +2068,28 @@ static const struct nla_policy rta_mctp_policy[RTA_MAX + 1] = { + [RTA_DST] = { .type = NLA_U8 }, + [RTA_METRICS] = { .type = NLA_NESTED }, + [RTA_OIF] = { .type = NLA_U32 }, ++ [RTA_GATEWAY] = NLA_POLICY_EXACT_LEN(sizeof(struct mctp_fq_addr)), ++}; ++ ++static const struct nla_policy rta_metrics_policy[RTAX_MAX + 1] = { ++ [RTAX_MTU] = { .type = NLA_U32 }, + }; + +-/* Common part for RTM_NEWROUTE and RTM_DELROUTE parsing. +- * tb must hold RTA_MAX+1 elements. ++/* base parsing; common to both _lookup and _populate variants. ++ * ++ * For gateway routes (which have a RTA_GATEWAY, and no RTA_OIF), we populate ++ * *gatweayp. for direct routes (RTA_OIF, no RTA_GATEWAY), we populate *mdev. + */ +-static int mctp_route_nlparse(struct sk_buff *skb, struct nlmsghdr *nlh, +- struct netlink_ext_ack *extack, +- struct nlattr **tb, struct rtmsg **rtm, +- struct mctp_dev **mdev, mctp_eid_t *daddr_start) ++static int mctp_route_nlparse_common(struct net *net, struct nlmsghdr *nlh, ++ struct netlink_ext_ack *extack, ++ struct nlattr **tb, struct rtmsg **rtm, ++ struct mctp_dev **mdev, ++ struct mctp_fq_addr *gatewayp, ++ mctp_eid_t *daddr_start) + { +- struct net *net = sock_net(skb->sk); ++ struct mctp_fq_addr *gateway = NULL; ++ unsigned int ifindex = 0; + struct net_device *dev; +- unsigned int ifindex; + int rc; + + rc = nlmsg_parse(nlh, sizeof(struct rtmsg), tb, RTA_MAX, +@@ -1292,11 +2105,44 @@ static int mctp_route_nlparse(struct sk_buff *skb, struct nlmsghdr *nlh, + } + *daddr_start = nla_get_u8(tb[RTA_DST]); + +- if (!tb[RTA_OIF]) { +- NL_SET_ERR_MSG(extack, "ifindex missing"); ++ if (tb[RTA_OIF]) ++ ifindex = nla_get_u32(tb[RTA_OIF]); ++ ++ if (tb[RTA_GATEWAY]) ++ gateway = nla_data(tb[RTA_GATEWAY]); ++ ++ if (ifindex && gateway) { ++ NL_SET_ERR_MSG(extack, ++ "cannot specify both ifindex and gateway"); ++ return -EINVAL; ++ ++ } else if (ifindex) { ++ dev = __dev_get_by_index(net, ifindex); ++ if (!dev) { ++ NL_SET_ERR_MSG(extack, "bad ifindex"); ++ return -ENODEV; ++ } ++ *mdev = mctp_dev_get_rtnl(dev); ++ if (!*mdev) ++ return -ENODEV; ++ gatewayp->eid = 0; ++ ++ } else if (gateway) { ++ if (!mctp_address_unicast(gateway->eid)) { ++ NL_SET_ERR_MSG(extack, "bad gateway"); ++ return -EINVAL; ++ } ++ ++ gatewayp->eid = gateway->eid; ++ gatewayp->net = gateway->net != MCTP_NET_ANY ? ++ gateway->net : ++ READ_ONCE(net->mctp.default_net); ++ *mdev = NULL; ++ ++ } else { ++ NL_SET_ERR_MSG(extack, "no route output provided"); + return -EINVAL; + } +- ifindex = nla_get_u32(tb[RTA_OIF]); + + *rtm = nlmsg_data(nlh); + if ((*rtm)->rtm_family != AF_MCTP) { +@@ -1304,82 +2150,157 @@ static int mctp_route_nlparse(struct sk_buff *skb, struct nlmsghdr *nlh, + return -EINVAL; + } + +- dev = __dev_get_by_index(net, ifindex); +- if (!dev) { +- NL_SET_ERR_MSG(extack, "bad ifindex"); +- return -ENODEV; +- } +- *mdev = mctp_dev_get_rtnl(dev); +- if (!*mdev) +- return -ENODEV; +- +- if (dev->flags & IFF_LOOPBACK) { +- NL_SET_ERR_MSG(extack, "no routes to loopback"); ++ if ((*rtm)->rtm_type != RTN_UNICAST) { ++ NL_SET_ERR_MSG(extack, "rtm_type must be RTN_UNICAST"); + return -EINVAL; + } + + return 0; + } + +-static const struct nla_policy rta_metrics_policy[RTAX_MAX + 1] = { +- [RTAX_MTU] = { .type = NLA_U32 }, +-}; +- +-static int mctp_newroute(struct sk_buff *skb, struct nlmsghdr *nlh, +- struct netlink_ext_ack *extack) ++/* Route parsing for lookup operations; we only need the "route target" ++ * components (ie., network and dest-EID range). ++ */ ++static int mctp_route_nlparse_lookup(struct net *net, struct nlmsghdr *nlh, ++ struct netlink_ext_ack *extack, ++ unsigned char *type, unsigned int *netid, ++ mctp_eid_t *daddr_start, ++ unsigned int *daddr_extent) + { + struct nlattr *tb[RTA_MAX + 1]; ++ struct mctp_fq_addr gw; ++ struct mctp_dev *mdev; ++ struct rtmsg *rtm; ++ int rc; ++ ++ rc = mctp_route_nlparse_common(net, nlh, extack, tb, &rtm, ++ &mdev, &gw, daddr_start); ++ if (rc) ++ return rc; ++ ++ if (mdev) { ++ *netid = mdev->net; ++ } else if (gw.eid) { ++ *netid = gw.net; ++ } else { ++ /* bug: _nlparse_common should not allow this */ ++ return -1; ++ } ++ ++ *type = rtm->rtm_type; ++ *daddr_extent = rtm->rtm_dst_len; ++ ++ return 0; ++} ++ ++/* Full route parse for RTM_NEWROUTE: populate @rt. On success, ++ * MCTP_ROUTE_DIRECT routes (ie, those with a direct dev) will hold a reference ++ * to that dev. ++ */ ++static int mctp_route_nlparse_populate(struct net *net, struct nlmsghdr *nlh, ++ struct netlink_ext_ack *extack, ++ struct mctp_route *rt) ++{ + struct nlattr *tbx[RTAX_MAX + 1]; ++ struct nlattr *tb[RTA_MAX + 1]; ++ unsigned int daddr_extent; ++ struct mctp_fq_addr gw; + mctp_eid_t daddr_start; +- struct mctp_dev *mdev; ++ struct mctp_dev *dev; + struct rtmsg *rtm; +- unsigned int mtu; ++ u32 mtu = 0; + int rc; + +- rc = mctp_route_nlparse(skb, nlh, extack, tb, +- &rtm, &mdev, &daddr_start); +- if (rc < 0) ++ rc = mctp_route_nlparse_common(net, nlh, extack, tb, &rtm, ++ &dev, &gw, &daddr_start); ++ if (rc) + return rc; + +- if (rtm->rtm_type != RTN_UNICAST) { +- NL_SET_ERR_MSG(extack, "rtm_type must be RTN_UNICAST"); ++ daddr_extent = rtm->rtm_dst_len; ++ ++ if (daddr_extent > 0xff || daddr_extent + daddr_start >= 255) { ++ NL_SET_ERR_MSG(extack, "invalid eid range"); + return -EINVAL; + } + +- mtu = 0; + if (tb[RTA_METRICS]) { + rc = nla_parse_nested(tbx, RTAX_MAX, tb[RTA_METRICS], + rta_metrics_policy, NULL); +- if (rc < 0) ++ if (rc < 0) { ++ NL_SET_ERR_MSG(extack, "incorrect RTA_METRICS format"); + return rc; ++ } + if (tbx[RTAX_MTU]) + mtu = nla_get_u32(tbx[RTAX_MTU]); + } + +- rc = mctp_route_add(mdev, daddr_start, rtm->rtm_dst_len, mtu, +- rtm->rtm_type); ++ rt->type = rtm->rtm_type; ++ rt->min = daddr_start; ++ rt->max = daddr_start + daddr_extent; ++ rt->mtu = mtu; ++ if (gw.eid) { ++ rt->dst_type = MCTP_ROUTE_GATEWAY; ++ rt->gateway.eid = gw.eid; ++ rt->gateway.net = gw.net; ++ } else { ++ rt->dst_type = MCTP_ROUTE_DIRECT; ++ rt->dev = dev; ++ mctp_dev_hold(rt->dev); ++ } ++ ++ return 0; ++} ++ ++static int mctp_newroute(struct sk_buff *skb, struct nlmsghdr *nlh, ++ struct netlink_ext_ack *extack) ++{ ++ struct net *net = sock_net(skb->sk); ++ struct mctp_route *rt; ++ int rc; ++ ++ rt = mctp_route_alloc(); ++ if (!rt) ++ return -ENOMEM; ++ ++ rc = mctp_route_nlparse_populate(net, nlh, extack, rt); ++ if (rc < 0) ++ goto err_free; ++ ++ if (rt->dst_type == MCTP_ROUTE_DIRECT && ++ rt->dev->dev->flags & IFF_LOOPBACK) { ++ NL_SET_ERR_MSG(extack, "no routes to loopback"); ++ rc = -EINVAL; ++ goto err_free; ++ } ++ ++ rc = mctp_route_add(net, rt); ++ if (!rc) ++ return 0; ++ ++err_free: ++ mctp_route_release(rt); + return rc; + } + + static int mctp_delroute(struct sk_buff *skb, struct nlmsghdr *nlh, + struct netlink_ext_ack *extack) + { +- struct nlattr *tb[RTA_MAX + 1]; ++ struct net *net = sock_net(skb->sk); ++ unsigned int netid, daddr_extent; ++ unsigned char type = RTN_UNSPEC; + mctp_eid_t daddr_start; +- struct mctp_dev *mdev; +- struct rtmsg *rtm; + int rc; + +- rc = mctp_route_nlparse(skb, nlh, extack, tb, +- &rtm, &mdev, &daddr_start); ++ rc = mctp_route_nlparse_lookup(net, nlh, extack, &type, &netid, ++ &daddr_start, &daddr_extent); + if (rc < 0) + return rc; + + /* we only have unicast routes */ +- if (rtm->rtm_type != RTN_UNICAST) ++ if (type != RTN_UNICAST) + return -EINVAL; + +- rc = mctp_route_remove(mdev, daddr_start, rtm->rtm_dst_len, RTN_UNICAST); ++ rc = mctp_route_remove(net, netid, daddr_start, daddr_extent, type); + return rc; + } + +@@ -1405,7 +2326,6 @@ static int mctp_fill_rtinfo(struct sk_buff *skb, struct mctp_route *rt, + hdr->rtm_tos = 0; + hdr->rtm_table = RT_TABLE_DEFAULT; + hdr->rtm_protocol = RTPROT_STATIC; /* everything is user-defined */ +- hdr->rtm_scope = RT_SCOPE_LINK; /* TODO: scope in mctp_route? */ + hdr->rtm_type = rt->type; + + if (nla_put_u8(skb, RTA_DST, rt->min)) +@@ -1422,13 +2342,17 @@ static int mctp_fill_rtinfo(struct sk_buff *skb, struct mctp_route *rt, + + nla_nest_end(skb, metrics); + +- if (rt->dev) { ++ if (rt->dst_type == MCTP_ROUTE_DIRECT) { ++ hdr->rtm_scope = RT_SCOPE_LINK; + if (nla_put_u32(skb, RTA_OIF, rt->dev->dev->ifindex)) + goto cancel; ++ } else if (rt->dst_type == MCTP_ROUTE_GATEWAY) { ++ hdr->rtm_scope = RT_SCOPE_UNIVERSE; ++ if (nla_put(skb, RTA_GATEWAY, ++ sizeof(rt->gateway), &rt->gateway)) ++ goto cancel; + } + +- /* TODO: conditional neighbour physaddr? */ +- + nlmsg_end(skb, nlh); + + return 0; +@@ -1534,6 +2458,602 @@ void mctp_routes_exit(void) + dev_remove_pack(&mctp_packet_type); + } + ++/** ++ * mctp_lookup_sock_by_key - Find socket using MCTP key (tag-based lookup) ++ * @skb: The SKB to look up ++ * @dev: The network device ++ * @found_key: Output parameter to return the matched key (optional, can be NULL) ++ * ++ * Uses MCTP tag to uniquely identify which socket sent the packet. ++ * This properly handles multiple applications sending to the same remote EID. ++ * If found_key is non-NULL, returns the matched key via output parameter. ++ * The returned key is still in the hash table (caller should not free it). ++ * Returns a reference to the sock if found (caller must sock_put), NULL otherwise. ++ */ ++struct sock *mctp_lookup_sock_by_key(struct sk_buff *skb, struct net_device *dev, ++ struct mctp_sk_key **found_key) ++{ ++ struct net *net = dev_net(dev); ++ struct mctp_dev *mdev; ++ struct mctp_hdr *mh; ++ struct mctp_sk_key *key; ++ struct sock *sk = NULL; ++ unsigned long flags; ++ unsigned int netid; ++ u8 tag; ++ ++ /* Initialize output parameter */ ++ if (found_key) ++ *found_key = NULL; ++ ++ if (!skb || skb->len < sizeof(struct mctp_hdr)) { ++ pr_err("MCTP: lookup_sock_by_key: invalid skb (skb=%p len=%u)\n", ++ skb, skb ? skb->len : 0); ++ return NULL; ++ } ++ ++ mh = mctp_hdr(skb); ++ ++ /* Get network ID from device rather than SKB control block. ++ * In URB completion context, skb->cb may not have valid magic, ++ * causing WARN_ON in mctp_cb(). We can safely get netid from ++ * the MCTP device instead. ++ */ ++ rcu_read_lock(); ++ mdev = __mctp_dev_get(dev); ++ if (!mdev) { ++ rcu_read_unlock(); ++ pr_err("MCTP: lookup_sock_by_key: no mctp_dev for %s\n", dev->name); ++ return NULL; ++ } ++ netid = READ_ONCE(mdev->net); ++ mctp_dev_put(mdev); ++ rcu_read_unlock(); ++ ++ /* Extract tag from MCTP header (bits 2-0) */ ++ tag = mh->flags_seq_tag & MCTP_HDR_TAG_MASK; ++ ++ pr_debug("MCTP: lookup_sock_by_key: netid=%u src=%u dest=%u tag=%u\n", ++ netid, mh->src, mh->dest, tag); ++ ++ /* Look up key in global key table */ ++ spin_lock_irqsave(&net->mctp.keys_lock, flags); ++ ++ hlist_for_each_entry(key, &net->mctp.keys, hlist) { ++ /* Match by: network, local EID, peer EID, tag */ ++ if (key->net != netid) ++ continue; ++ ++ /* For TX errors: source = local, dest = peer ++ * Use wildcard matching to handle MCTP_ADDR_ANY in keys. ++ * This is critical for Get Endpoint ID (dest=0/NULL) which ++ * creates keys with peer_addr=MCTP_ADDR_ANY. ++ */ ++ if (!mctp_address_matches(key->local_addr, mh->src)) ++ continue; ++ if (!mctp_address_matches(key->peer_addr, mh->dest)) ++ continue; ++ ++ /* The critical differentiator: TAG */ ++ if (key->tag != tag) ++ continue; ++ ++ /* Found exact match! */ ++ sk = key->sk; ++ if (sk) { ++ sock_hold(sk); ++ /* Return the matched key via output parameter */ ++ if (found_key) ++ *found_key = key; ++ pr_debug("MCTP: lookup_sock_by_key: FOUND socket %p (key: net=%u local=%u peer=%u tag=%u)\n", ++ sk, key->net, key->local_addr, key->peer_addr, key->tag); ++ } ++ break; ++} ++ ++spin_unlock_irqrestore(&net->mctp.keys_lock, flags); ++ ++if (!sk) { ++ pr_debug("MCTP: lookup_sock_by_key: NO MATCH found for netid=%u src=%u dest=%u tag=%u\n", ++ netid, mh->src, mh->dest, tag); ++} ++ ++ return sk; ++} ++EXPORT_SYMBOL_GPL(mctp_lookup_sock_by_key); ++ ++/** ++ * mctp_lookup_sock_for_error - Find socket for error reporting ++ * @skb: Packet that failed (contains addressing info) ++ * @dev: Network device ++ * @key: Existing key (if available, may be NULL) ++ * @found_key: Output parameter to return the matched key (optional, can be NULL) ++ * ++ * Finds which socket should receive an error notification for failed ++ * operations WE initiated (our requests/responses). ++ * ++ * Uses two-tier lookup: ++ * 1. Use socket from existing key (if key provided and valid) ++ * - For RX reassembly errors where key is from reassembly context ++ * 2. Try TX key lookup by tag (for TX errors from drivers) ++ * - TX key existence indicates operation we initiated ++ * - No TX key = not our transaction = don't report ++ * - Returns found key via found_key output parameter ++ * ++ * With BMC-to-device behavior where device uses TO=0 in responses, ++ * TX keys naturally match responses (both have tag value 0-7 only). ++ * ++ * Device-initiated messages won't have TX keys, so errors on those ++ * messages are correctly not reported (not our operations). ++ * ++ * DEADLOCK PREVENTION: Method 2 is skipped if key parameter is provided, ++ * as this indicates call from __mctp_key_remove() which holds keys_lock. ++ * ++ * Returns: Socket with refcount held, or NULL if no socket found or ++ * error should not be reported. Caller must call sock_put() ++ * if non-NULL returned. ++ */ ++struct sock *mctp_lookup_sock_for_error(struct sk_buff *skb, ++ struct net_device *dev, ++ struct mctp_sk_key *key, ++ struct mctp_sk_key **found_key) ++{ ++ struct net *net; ++ struct sock *sk = NULL; ++ struct mctp_hdr *mh; ++ u8 tag; ++ ++ /* Initialize output parameter */ ++ if (found_key) ++ *found_key = NULL; ++ ++ if (!skb || !dev) ++ return NULL; ++ ++ if (skb->len < sizeof(struct mctp_hdr)) ++ return NULL; ++ ++ net = dev_net(dev); ++ mh = mctp_hdr(skb); ++ /* Extract tag value only (0-7), not TO bit. ++ * With BMC-to-device behavior where device uses TO=0 in responses, ++ * this naturally matches TX keys which are also stored without TO bit. ++ */ ++ tag = mh->flags_seq_tag & MCTP_HDR_TAG_MASK; ++ ++ /* Method 1: Use existing key socket (fastest path) ++ * If caller already has a key (e.g., from RX reassembly), use it directly. ++ */ ++ if (key && key->sk) { ++ struct mctp_sock *check_msk = container_of(key->sk, struct mctp_sock, sk); ++ ++ /* Check if socket is still valid and open */ ++ if (sock_flag(&check_msk->sk, SOCK_DEAD)) { ++ pr_debug("MCTP error: key socket is dead (closed)\n"); ++ return NULL; ++ } ++ ++ /* Skip if this socket doesn't have error queue enabled */ ++ if (!check_msk->enable_errqueue) { ++ pr_debug("MCTP error: key socket found but error queue disabled (src=%u, dest=%u, tag=%u)\n", ++ mh->src, mh->dest, tag); ++ return NULL; ++ } ++ ++ sk = key->sk; ++ sock_hold(sk); ++ /* Return the key via output parameter */ ++ if (found_key) ++ *found_key = key; ++ pr_debug("MCTP error: socket via key (src=%u, dest=%u, tag=%u)\n", ++ mh->src, mh->dest, tag); ++ return sk; ++ } ++ ++ /* Method 2: TX key lookup (best effort to get key with orig_payload) ++ * ++ * Try to find TX key first because it provides: ++ * 1. Socket for error reporting ++ * 2. orig_payload for accurate error message content ++ * ++ * TX key existence indicates operation WE initiated (request packet). ++ * This is the preferred method for TX errors because it gives complete context. ++ * ++ * DEADLOCK PREVENTION: Skip this method if key parameter is provided, ++ * as this indicates call from __mctp_key_remove() which holds keys_lock. ++ * mctp_lookup_sock_by_key() also needs keys_lock, causing deadlock. ++ */ ++ if (!key) { ++ struct mctp_sk_key *tx_key = NULL; ++ ++ /* Safe to do key lookup - not called from __mctp_key_remove() */ ++ sk = mctp_lookup_sock_by_key(skb, dev, &tx_key); ++ if (sk) { ++ struct mctp_sock *check_msk = container_of(sk, struct mctp_sock, sk); ++ ++ /* Check if socket is still valid and open */ ++ if (sock_flag(&check_msk->sk, SOCK_DEAD)) { ++ pr_debug("MCTP error: TX key socket is dead (closed)\n"); ++ sock_put(sk); ++ return NULL; ++ } ++ ++ /* Skip if this socket doesn't have error queue enabled */ ++ if (!check_msk->enable_errqueue) { ++ pr_debug("MCTP error: TX key socket found but error queue disabled (src=%u, dest=%u, tag=%u)\n", ++ mh->src, mh->dest, tag); ++ sock_put(sk); ++ return NULL; ++ } ++ ++ /* Return the found TX key via output parameter */ ++ if (found_key) ++ *found_key = tx_key; ++ ++ pr_debug("MCTP error: socket via TX key lookup (src=%u, dest=%u, tag=%u) with orig_payload\n", ++ mh->src, mh->dest, tag); ++ return sk; ++ } ++ ++ /* No TX key found - fall through to Method 3 (skb->sk fallback) */ ++ pr_debug("MCTP error: No TX key found, trying skb->sk fallback (src=%u, dest=%u, tag=%u)\n", ++ mh->src, mh->dest, tag); ++ } ++ ++ /* Method 3: Check skb->sk directly (fallback for TX errors without key) ++ * ++ * If TX key lookup failed but SKB has socket pointer, use it as fallback. ++ * This is faster than TX key lookup but provides NO key (no orig_payload). ++ * Only used when TX key doesn't exist (e.g., response packets, old code paths). ++ * ++ * DEADLOCK PREVENTION: Skip this method if key parameter is provided, ++ * as this indicates call from __mctp_key_remove() which holds keys_lock. ++ * ++ * NOTE: found_key will be NULL when returning via this path. ++ * Error reporting will work but without orig_payload context. ++ */ ++ if (!key && skb->sk && skb->sk->sk_family == AF_MCTP) { ++ struct mctp_sock *msk = container_of(skb->sk, struct mctp_sock, sk); ++ ++ /* Check if socket is still valid and open */ ++ if (sock_flag(&msk->sk, SOCK_DEAD)) { ++ pr_debug("MCTP error: skb->sk socket is dead (closed)\n"); ++ return NULL; ++ } ++ ++ /* Skip if this socket doesn't have error queue enabled */ ++ if (!msk->enable_errqueue) { ++ pr_debug("MCTP error: skb->sk socket found but error queue disabled (src=%u, dest=%u)\n", ++ mh->src, mh->dest); ++ return NULL; ++ } ++ ++ /* This is the socket that sent this packet - safe to report error. ++ * NOTE: found_key stays NULL - no orig_payload available. ++ */ ++ sock_hold(skb->sk); ++ pr_debug("MCTP error: socket via skb->sk fallback (src=%u, dest=%u) - no key, no orig_payload\n", ++ mh->src, mh->dest); ++ return skb->sk; ++ } ++ ++ /* If we reach here: ++ * - key parameter provided: called from __mctp_key_remove() ++ * Methods 2 and 3 skipped for deadlock prevention ++ * - OR Method 2 found no valid skb->sk ++ * - OR Method 3 found no TX key: not our transaction ++ * ++ * In any case, don't report error. ++ */ ++ return NULL; ++} ++EXPORT_SYMBOL_GPL(mctp_lookup_sock_for_error); ++ ++/** ++ * mctp_lookup_tx_key_for_rx_error - Look up TX key for RX error reporting ++ * @net: Network namespace ++ * @netid: MCTP network ID ++ * @local_eid: Local EID (from RX packet dest) ++ * @peer_eid: Peer EID (from RX packet src) ++ * @tag: Tag value (without TO bit) ++ * ++ * For RX errors, we need to find the original TX request to report the ++ * correct payload to the application. This function looks up the TX key ++ * by reversing the addressing from the RX packet. ++ * ++ * Returns: TX key with refcount incremented, or NULL if not found ++ */ ++static struct mctp_sk_key *mctp_lookup_tx_key_for_rx_error(struct net *net, ++ unsigned int netid, ++ mctp_eid_t local_eid, ++ mctp_eid_t peer_eid, ++ u8 tag) ++{ ++ struct mctp_sk_key *key, *ret = NULL; ++ unsigned long flags; ++ ++ /* For RX errors, the TX key was created with: ++ * - local_addr = local_eid (our address when we sent request) ++ * - peer_addr = peer_eid (who we sent to) ++ * - tag = tag value (without TO bit, as TX key stores incoming perspective) ++ */ ++ spin_lock_irqsave(&net->mctp.keys_lock, flags); ++ hlist_for_each_entry(key, &net->mctp.keys, hlist) { ++ if (!mctp_key_match(key, netid, local_eid, peer_eid, tag)) ++ continue; ++ ++ spin_lock(&key->lock); ++ if (key->valid && key->orig_payload_len > 0) { ++ refcount_inc(&key->refs); ++ ret = key; ++ spin_unlock(&key->lock); ++ break; ++ } ++ spin_unlock(&key->lock); ++ } ++ spin_unlock_irqrestore(&net->mctp.keys_lock, flags); ++ ++ pr_debug("mctp_lookup_tx_key_for_rx_error: netid=%u local=%u peer=%u tag=%u -> %s\n", ++ netid, local_eid, peer_eid, tag, ret ? "FOUND" : "NOT FOUND"); ++ ++ return ret; ++} ++ ++/** ++ * mctp_queue_error - Queue error to socket error queue ++ * @sk: Socket to report error to ++ * @skb: SKB that failed (contains addressing info) ++ * @error_code: errno value (EPROTO, ETIMEDOUT, EMSGSIZE for RX; EHOSTUNREACH, ENXIO for TX) ++ * @dev: Network device (used to extract MCTP network ID) ++ * @direction: MCTP_DIR_TX or MCTP_DIR_RX ++ * @binding: enum mctp_phys_binding value (MCTP_PHYS_BINDING_USB, MCTP_PHYS_BINDING_SMBUS, etc.) ++ * @key: Key for error context. TX key for TX errors, RX key for RX errors (may be TX key). ++ * ++ * Builds an mctp_error structure and queues it to the socket's error queue. ++ * Applications can read this via recvmsg(MSG_ERRQUEUE). ++ * ++ * Strategy: ++ * - TX errors: Use provided TX key's orig_payload (captured before fragmentation) ++ * - RX errors: Look up TX key using addressing from RX key/SKB, use TX key's orig_payload ++ * (the original request we sent). Only report if TX key found - this ensures ++ * we only report errors for responses to OUR requests, not unsolicited packets. ++ * ++ * This ensures applications always receive the REQUEST payload for both TX and RX errors, ++ * allowing them to identify which transaction failed. ++ * ++ * Common error codes: ++ * RX errors: EPROTO (sequence error), ETIMEDOUT (reassembly timeout), EMSGSIZE (too large) ++ * TX errors: EHOSTUNREACH, ENXIO, ENODEV, ESHUTDOWN, ENOMEM, EPROTO, etc. ++ */ ++void mctp_queue_error(struct sock *sk, struct sk_buff *skb, ++ int error_code, struct net_device *dev, u8 direction, u8 binding, ++ struct mctp_sk_key *key) ++{ ++ struct mctp_sock *msk = container_of(sk, struct mctp_sock, sk); ++ struct mctp_sk_key *tx_key = NULL; ++ struct mctp_error *mctp_err; ++ struct sk_buff *err_skb; ++ struct mctp_hdr *mh; ++ struct mctp_dev *mdev; ++ unsigned int netid; ++ size_t capture_len; ++ bool key_found = false; ++ ++ /* Extract network ID from device. ++ * This is safer than using skb->cb which may be corrupted by qdisc. ++ */ ++ rcu_read_lock(); ++ mdev = __mctp_dev_get(dev); ++ if (!mdev) { ++ rcu_read_unlock(); ++ pr_debug("MCTP: Failed to get mctp_dev for error reporting\n"); ++ return; ++ } ++ netid = READ_ONCE(mdev->net); ++ mctp_dev_put(mdev); ++ rcu_read_unlock(); ++ ++ if (!msk->enable_errqueue) { ++ pr_debug("MCTP: Error queue not enabled, skipping error report\n"); ++ return; ++ } ++ ++ /* Extract addressing from SKB */ ++ if (!skb || skb->len < sizeof(struct mctp_hdr)) { ++ pr_debug("MCTP: Invalid SKB for error reporting\n"); ++ return; ++ } ++ mh = mctp_hdr(skb); ++ ++ /* Handle TX vs RX errors differently. ++ * ++ * TX errors: Use TX key if available, otherwise extract from SKB ++ * RX errors: Always need TX key to identify our original transaction ++ */ ++ if (direction == MCTP_DIR_TX) { ++ /* ===== TX ERROR PATH ===== ++ * TX errors can occur on: ++ * 1. REQUEST packets (owner=1) - TX key exists with orig_payload ++ * 2. RESPONSE packets (owner=0) - No TX key, extract from SKB ++ * 3. Fragmented packets - Middle/end need key, first has SKB payload ++ */ ++ tx_key = key; /* For TX errors, key parameter holds TX key */ ++ ++ if (tx_key && tx_key->orig_payload_len > 0) { ++ /* Have TX key with payload - use it (fragmented requests) */ ++ key_found = true; ++ pr_debug("mctp_queue_error: TX error - using TX key orig_payload (len=%u)\n", ++ tx_key->orig_payload_len); ++ } else { ++ /* No key or empty key - must extract from SKB. ++ * This handles: ++ * - Unfragmented messages (complete payload in SKB) ++ * - First fragment (SOM=1, has msg_type in SKB) ++ * - Response packets (no TX key created) ++ * ++ * CRITICAL: Only works for first fragment or unfragmented. ++ * Middle/end fragments without key cannot be reported. ++ */ ++ pr_debug("mctp_queue_error: TX error - no TX key, will extract from SKB\n"); ++ key_found = false; ++ } ++ } else { ++ /* ===== RX ERROR PATH ===== ++ * RX errors occur when receiving responses to OUR requests. ++ * We MUST find the TX key to identify our original transaction. ++ * If no TX key exists, this is an unsolicited packet - don't report. ++ */ ++ if (error_code == ETIMEDOUT) { ++ /* RX reassembly timeout: fragments never completed. ++ * RX key IS the TX key (same object, reused for response tracking). ++ * It contains orig_payload from when we sent the original REQUEST. ++ */ ++ tx_key = key; /* For RX timeout, key parameter holds RX key (which is TX key) */ ++ ++ if (tx_key && tx_key->orig_payload_len > 0) { ++ key_found = true; ++ pr_debug("mctp_queue_error: RX timeout - has TX orig_payload (REQUEST, len=%u)\n", ++ tx_key->orig_payload_len); ++ } else { ++ /* No orig_payload = unsolicited request from device, not our transaction */ ++ pr_debug("mctp_queue_error: RX timeout - no TX origin (unsolicited), NOT REPORTING\n"); ++ return; ++ } ++ } else { ++ /* RX sequence/SOM error: look up TX key by reversing addressing. ++ * Response came FROM peer (mh->src) TO us (mh->dest). ++ * Our original request was FROM us (mh->dest) TO peer (mh->src). ++ */ ++ u8 tag = mh->flags_seq_tag & MCTP_HDR_TAG_MASK; ++ ++ pr_debug("mctp_queue_error: RX error - looking up TX key (local=%u, peer=%u, tag=%u)\n", ++ mh->dest, mh->src, tag); ++ ++ tx_key = mctp_lookup_tx_key_for_rx_error(dev_net(dev), netid, ++ mh->dest, mh->src, tag); ++ if (tx_key && tx_key->orig_payload_len > 0) { ++ key_found = true; ++ pr_debug("mctp_queue_error: RX error - TX key found with payload\n"); ++ } else { ++ if (tx_key) { ++ pr_debug("mctp_queue_error: RX error - TX key has no payload, NOT REPORTING\n"); ++ mctp_key_unref(tx_key); ++ } else { ++ pr_debug("mctp_queue_error: RX error - TX key NOT FOUND (unsolicited), NOT REPORTING\n"); ++ } ++ return; ++ } ++ } ++ } ++ ++ /* Allocate SKB for error */ ++ err_skb = alloc_skb(sizeof(*mctp_err), GFP_ATOMIC); ++ if (!err_skb) { ++ if (direction == MCTP_DIR_RX) ++ mctp_key_unref(tx_key); ++ return; ++ } ++ ++ /* Build error structure */ ++ mctp_err = (struct mctp_error *)skb_put(err_skb, sizeof(*mctp_err)); ++ memset(mctp_err, 0, sizeof(*mctp_err)); ++ ++ /* Fill basic error information */ ++ mctp_err->error_code = error_code; ++ mctp_err->direction = direction; ++ mctp_err->binding = binding; ++ mctp_err->timestamp_ns = ktime_get_ns(); ++ ++ /* Fill addressing from SKB */ ++ mctp_err->src_eid = mh->src; ++ mctp_err->dest_eid = mh->dest; ++ mctp_err->tag = mh->flags_seq_tag & MCTP_HDR_TAG_MASK; ++ ++ /* Extract payload for error report based on direction and availability. */ ++ if (direction == MCTP_DIR_TX) { ++ /* ===== TX ERROR: Payload Extraction ===== ++ * Try TX key first, fall back to SKB extraction ++ */ ++ if (key_found) { ++ /* Use TX key payload (captured before fragmentation) */ ++ mctp_err->msg_type = tx_key->orig_msg_type; ++ capture_len = min_t(size_t, tx_key->orig_payload_len, ++ MCTP_ERROR_PAYLOAD_SIZE); ++ memcpy(mctp_err->payload, tx_key->orig_payload, capture_len); ++ mctp_err->payload_len = capture_len; ++ pr_debug("mctp_queue_error: TX - used key payload (len=%u)\n", capture_len); ++ } else { ++ /* No key - extract from SKB (responses, unfragmented, first fragments) */ ++ u8 flags = mh->flags_seq_tag; ++ bool is_first_or_unfragmented = (flags & MCTP_HDR_FLAG_SOM); ++ ++ if (!is_first_or_unfragmented) { ++ /* Middle/end fragment without key - cannot extract payload. ++ * Don't report - first fragment error already queued. ++ */ ++ pr_debug("mctp_queue_error: TX middle/end fragment without key - NOT REPORTING\n"); ++ kfree_skb(err_skb); ++ return; ++ } ++ ++ /* First fragment or unfragmented - SKB has msg_type and payload */ ++ size_t mctp_hdr_size = sizeof(struct mctp_hdr); ++ size_t available = skb->len - mctp_hdr_size; ++ ++ if (available > 0) { ++ u8 *payload_start = skb->data + mctp_hdr_size; ++ ++ /* First byte after MCTP header is message type */ ++ mctp_err->msg_type = *payload_start; ++ ++ /* Capture up to 32 bytes of payload (after message type) */ ++ if (available > 1) { ++ capture_len = min_t(size_t, available - 1, ++ MCTP_ERROR_PAYLOAD_SIZE); ++ memcpy(mctp_err->payload, payload_start + 1, capture_len); ++ mctp_err->payload_len = capture_len; ++ } else { ++ mctp_err->payload_len = 0; ++ } ++ pr_debug("mctp_queue_error: TX - extracted from SKB (msg_type=%u, len=%u)\n", ++ mctp_err->msg_type, mctp_err->payload_len); ++ } else { ++ pr_debug("mctp_queue_error: TX - SKB too small, no payload\n"); ++ mctp_err->payload_len = 0; ++ } ++ } ++ } else { ++ /* ===== RX ERROR: Payload Extraction ===== ++ * Always use TX key payload (original REQUEST we sent) ++ * We already validated key_found above, so tx_key is valid here. ++ */ ++ mctp_err->msg_type = tx_key->orig_msg_type; ++ capture_len = min_t(size_t, tx_key->orig_payload_len, ++ MCTP_ERROR_PAYLOAD_SIZE); ++ memcpy(mctp_err->payload, tx_key->orig_payload, capture_len); ++ mctp_err->payload_len = capture_len; ++ pr_debug("mctp_queue_error: RX - used TX key payload (REQUEST, len=%u)\n", capture_len); ++ ++ /* Release TX key if we looked it up (non-timeout RX errors) */ ++ if (error_code != ETIMEDOUT) ++ mctp_key_unref(tx_key); ++ } ++ ++ /* Queue error to socket */ ++ if (sock_queue_err_skb(sk, err_skb) == 0) { ++ /* Successfully queued, trigger error report */ ++ sk_error_report(sk); ++ pr_debug("mctp_queue_error: Error queued successfully (code=%d, %s, src=%u->dest=%u)\n", ++ error_code, direction == MCTP_DIR_TX ? "TX" : "RX", ++ mctp_err->src_eid, mctp_err->dest_eid); ++ } else { ++ /* Failed to queue - free the SKB to avoid memory leak */ ++ kfree_skb(err_skb); ++ pr_debug("mctp_queue_error: Failed to queue error to socket\n"); ++ } ++} ++EXPORT_SYMBOL_GPL(mctp_queue_error); ++ + #if IS_ENABLED(CONFIG_MCTP_TEST) + #include "test/route-test.c" + #endif +diff --git a/net/mctp/test/route-test.c b/net/mctp/test/route-test.c +index 06c1897b6..7a398f41b 100644 +--- a/net/mctp/test/route-test.c ++++ b/net/mctp/test/route-test.c +@@ -2,132 +2,11 @@ + + #include + +-#include "utils.h" +- +-struct mctp_test_route { +- struct mctp_route rt; +- struct sk_buff_head pkts; +-}; +- +-static int mctp_test_route_output(struct mctp_route *rt, struct sk_buff *skb) +-{ +- struct mctp_test_route *test_rt = container_of(rt, struct mctp_test_route, rt); +- +- skb_queue_tail(&test_rt->pkts, skb); +- +- return 0; +-} +- +-/* local version of mctp_route_alloc() */ +-static struct mctp_test_route *mctp_route_test_alloc(void) +-{ +- struct mctp_test_route *rt; +- +- rt = kzalloc(sizeof(*rt), GFP_KERNEL); +- if (!rt) +- return NULL; +- +- INIT_LIST_HEAD(&rt->rt.list); +- refcount_set(&rt->rt.refs, 1); +- rt->rt.output = mctp_test_route_output; +- +- skb_queue_head_init(&rt->pkts); +- +- return rt; +-} +- +-static struct mctp_test_route *mctp_test_create_route(struct net *net, +- struct mctp_dev *dev, +- mctp_eid_t eid, +- unsigned int mtu) +-{ +- struct mctp_test_route *rt; +- +- rt = mctp_route_test_alloc(); +- if (!rt) +- return NULL; +- +- rt->rt.min = eid; +- rt->rt.max = eid; +- rt->rt.mtu = mtu; +- rt->rt.type = RTN_UNSPEC; +- if (dev) +- mctp_dev_hold(dev); +- rt->rt.dev = dev; +- +- list_add_rcu(&rt->rt.list, &net->mctp.routes); +- +- return rt; +-} +- +-static void mctp_test_route_destroy(struct kunit *test, +- struct mctp_test_route *rt) +-{ +- unsigned int refs; +- +- rtnl_lock(); +- list_del_rcu(&rt->rt.list); +- rtnl_unlock(); +- +- skb_queue_purge(&rt->pkts); +- if (rt->rt.dev) +- mctp_dev_put(rt->rt.dev); +- +- refs = refcount_read(&rt->rt.refs); +- KUNIT_ASSERT_EQ_MSG(test, refs, 1, "route ref imbalance"); +- +- kfree_rcu(&rt->rt, rcu); +-} +- +-static void mctp_test_skb_set_dev(struct sk_buff *skb, +- struct mctp_test_dev *dev) +-{ +- struct mctp_skb_cb *cb; +- +- cb = mctp_cb(skb); +- cb->net = READ_ONCE(dev->mdev->net); +- skb->dev = dev->ndev; +-} +- +-static struct sk_buff *mctp_test_create_skb(const struct mctp_hdr *hdr, +- unsigned int data_len) +-{ +- size_t hdr_len = sizeof(*hdr); +- struct sk_buff *skb; +- unsigned int i; +- u8 *buf; +- +- skb = alloc_skb(hdr_len + data_len, GFP_KERNEL); +- if (!skb) +- return NULL; +- +- __mctp_cb(skb); +- memcpy(skb_put(skb, hdr_len), hdr, hdr_len); +- +- buf = skb_put(skb, data_len); +- for (i = 0; i < data_len; i++) +- buf[i] = i & 0xff; +- +- return skb; +-} +- +-static struct sk_buff *__mctp_test_create_skb_data(const struct mctp_hdr *hdr, +- const void *data, +- size_t data_len) +-{ +- size_t hdr_len = sizeof(*hdr); +- struct sk_buff *skb; +- +- skb = alloc_skb(hdr_len + data_len, GFP_KERNEL); +- if (!skb) +- return NULL; +- +- __mctp_cb(skb); +- memcpy(skb_put(skb, hdr_len), hdr, hdr_len); +- memcpy(skb_put(skb, data_len), data, data_len); ++/* keep clangd happy when compiled outside of the route.c include */ ++#include ++#include + +- return skb; +-} ++#include "utils.h" + + #define mctp_test_create_skb_data(h, d) \ + __mctp_test_create_skb_data(h, d, sizeof(*d)) +@@ -141,8 +20,10 @@ struct mctp_frag_test { + static void mctp_test_fragment(struct kunit *test) + { + const struct mctp_frag_test *params; ++ struct mctp_test_pktqueue tpq; + int rc, i, n, mtu, msgsize; +- struct mctp_test_route *rt; ++ struct mctp_test_dev *dev; ++ struct mctp_dst dst; + struct sk_buff *skb; + struct mctp_hdr hdr; + u8 seq; +@@ -159,13 +40,15 @@ static void mctp_test_fragment(struct kunit *test) + skb = mctp_test_create_skb(&hdr, msgsize); + KUNIT_ASSERT_TRUE(test, skb); + +- rt = mctp_test_create_route(&init_net, NULL, 10, mtu); +- KUNIT_ASSERT_TRUE(test, rt); ++ dev = mctp_test_create_dev(); ++ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dev); ++ ++ mctp_test_dst_setup(test, &dst, dev, &tpq, mtu); + +- rc = mctp_do_fragment_route(&rt->rt, skb, mtu, MCTP_TAG_OWNER); ++ rc = mctp_do_fragment_route(&dst, skb, mtu, MCTP_TAG_OWNER); + KUNIT_EXPECT_FALSE(test, rc); + +- n = rt->pkts.qlen; ++ n = tpq.pkts.qlen; + + KUNIT_EXPECT_EQ(test, n, params->n_frags); + +@@ -178,7 +61,7 @@ static void mctp_test_fragment(struct kunit *test) + first = i == 0; + last = i == (n - 1); + +- skb2 = skb_dequeue(&rt->pkts); ++ skb2 = skb_dequeue(&tpq.pkts); + + if (!skb2) + break; +@@ -216,7 +99,8 @@ static void mctp_test_fragment(struct kunit *test) + kfree_skb(skb2); + } + +- mctp_test_route_destroy(test, rt); ++ mctp_test_dst_release(&dst, &tpq); ++ mctp_test_destroy_dev(dev); + } + + static const struct mctp_frag_test mctp_frag_tests[] = { +@@ -246,25 +130,30 @@ struct mctp_rx_input_test { + static void mctp_test_rx_input(struct kunit *test) + { + const struct mctp_rx_input_test *params; ++ struct mctp_test_pktqueue tpq; + struct mctp_test_route *rt; + struct mctp_test_dev *dev; + struct sk_buff *skb; + + params = test->param_value; ++ test->priv = &tpq; + + dev = mctp_test_create_dev(); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dev); + +- rt = mctp_test_create_route(&init_net, dev->mdev, 8, 68); ++ rt = mctp_test_create_route_direct(&init_net, dev->mdev, 8, 68); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, rt); + + skb = mctp_test_create_skb(¶ms->hdr, 1); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, skb); + ++ mctp_test_pktqueue_init(&tpq); ++ + mctp_pkttype_receive(skb, dev->ndev, &mctp_packet_type, NULL); + +- KUNIT_EXPECT_EQ(test, !!rt->pkts.qlen, params->input); ++ KUNIT_EXPECT_EQ(test, !!tpq.pkts.qlen, params->input); + ++ skb_queue_purge(&tpq.pkts); + mctp_test_route_destroy(test, rt); + mctp_test_destroy_dev(dev); + } +@@ -292,12 +181,12 @@ KUNIT_ARRAY_PARAM(mctp_rx_input, mctp_rx_input_tests, + /* set up a local dev, route on EID 8, and a socket listening on type 0 */ + static void __mctp_route_test_init(struct kunit *test, + struct mctp_test_dev **devp, +- struct mctp_test_route **rtp, ++ struct mctp_dst *dst, ++ struct mctp_test_pktqueue *tpq, + struct socket **sockp, + unsigned int netid) + { + struct sockaddr_mctp addr = {0}; +- struct mctp_test_route *rt; + struct mctp_test_dev *dev; + struct socket *sock; + int rc; +@@ -307,8 +196,7 @@ static void __mctp_route_test_init(struct kunit *test, + if (netid != MCTP_NET_ANY) + WRITE_ONCE(dev->mdev->net, netid); + +- rt = mctp_test_create_route(&init_net, dev->mdev, 8, 68); +- KUNIT_ASSERT_NOT_ERR_OR_NULL(test, rt); ++ mctp_test_dst_setup(test, dst, dev, tpq, 68); + + rc = sock_create_kern(&init_net, AF_MCTP, SOCK_DGRAM, 0, &sock); + KUNIT_ASSERT_EQ(test, rc, 0); +@@ -320,18 +208,18 @@ static void __mctp_route_test_init(struct kunit *test, + rc = kernel_bind(sock, (struct sockaddr *)&addr, sizeof(addr)); + KUNIT_ASSERT_EQ(test, rc, 0); + +- *rtp = rt; + *devp = dev; + *sockp = sock; + } + + static void __mctp_route_test_fini(struct kunit *test, + struct mctp_test_dev *dev, +- struct mctp_test_route *rt, ++ struct mctp_dst *dst, ++ struct mctp_test_pktqueue *tpq, + struct socket *sock) + { + sock_release(sock); +- mctp_test_route_destroy(test, rt); ++ mctp_test_dst_release(dst, tpq); + mctp_test_destroy_dev(dev); + } + +@@ -344,22 +232,24 @@ struct mctp_route_input_sk_test { + static void mctp_test_route_input_sk(struct kunit *test) + { + const struct mctp_route_input_sk_test *params; ++ struct mctp_test_pktqueue tpq; + struct sk_buff *skb, *skb2; +- struct mctp_test_route *rt; + struct mctp_test_dev *dev; ++ struct mctp_dst dst; + struct socket *sock; + int rc; + + params = test->param_value; + +- __mctp_route_test_init(test, &dev, &rt, &sock, MCTP_NET_ANY); ++ __mctp_route_test_init(test, &dev, &dst, &tpq, &sock, MCTP_NET_ANY); + + skb = mctp_test_create_skb_data(¶ms->hdr, ¶ms->type); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, skb); + + mctp_test_skb_set_dev(skb, dev); ++ mctp_test_pktqueue_init(&tpq); + +- rc = mctp_route_input(&rt->rt, skb); ++ rc = mctp_dst_input(&dst, skb); + + if (params->deliver) { + KUNIT_EXPECT_EQ(test, rc, 0); +@@ -376,7 +266,7 @@ static void mctp_test_route_input_sk(struct kunit *test) + KUNIT_EXPECT_NULL(test, skb2); + } + +- __mctp_route_test_fini(test, dev, rt, sock); ++ __mctp_route_test_fini(test, dev, &dst, &tpq, sock); + } + + #define FL_S (MCTP_HDR_FLAG_SOM) +@@ -413,16 +303,17 @@ struct mctp_route_input_sk_reasm_test { + static void mctp_test_route_input_sk_reasm(struct kunit *test) + { + const struct mctp_route_input_sk_reasm_test *params; ++ struct mctp_test_pktqueue tpq; + struct sk_buff *skb, *skb2; +- struct mctp_test_route *rt; + struct mctp_test_dev *dev; ++ struct mctp_dst dst; + struct socket *sock; + int i, rc; + u8 c; + + params = test->param_value; + +- __mctp_route_test_init(test, &dev, &rt, &sock, MCTP_NET_ANY); ++ __mctp_route_test_init(test, &dev, &dst, &tpq, &sock, MCTP_NET_ANY); + + for (i = 0; i < params->n_hdrs; i++) { + c = i; +@@ -431,7 +322,7 @@ static void mctp_test_route_input_sk_reasm(struct kunit *test) + + mctp_test_skb_set_dev(skb, dev); + +- rc = mctp_route_input(&rt->rt, skb); ++ rc = mctp_dst_input(&dst, skb); + } + + skb2 = skb_recv_datagram(sock->sk, MSG_DONTWAIT, &rc); +@@ -445,7 +336,7 @@ static void mctp_test_route_input_sk_reasm(struct kunit *test) + KUNIT_EXPECT_NULL(test, skb2); + } + +- __mctp_route_test_fini(test, dev, rt, sock); ++ __mctp_route_test_fini(test, dev, &dst, &tpq, sock); + } + + #define RX_FRAG(f, s) RX_HDR(1, 10, 8, FL_TO | (f) | ((s) << MCTP_HDR_SEQ_SHIFT)) +@@ -547,7 +438,7 @@ struct mctp_route_input_sk_keys_test { + static void mctp_test_route_input_sk_keys(struct kunit *test) + { + const struct mctp_route_input_sk_keys_test *params; +- struct mctp_test_route *rt; ++ struct mctp_test_pktqueue tpq; + struct sk_buff *skb, *skb2; + struct mctp_test_dev *dev; + struct mctp_sk_key *key; +@@ -555,6 +446,7 @@ static void mctp_test_route_input_sk_keys(struct kunit *test) + struct mctp_sock *msk; + struct socket *sock; + unsigned long flags; ++ struct mctp_dst dst; + unsigned int net; + int rc; + u8 c; +@@ -565,8 +457,7 @@ static void mctp_test_route_input_sk_keys(struct kunit *test) + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dev); + net = READ_ONCE(dev->mdev->net); + +- rt = mctp_test_create_route(&init_net, dev->mdev, 8, 68); +- KUNIT_ASSERT_NOT_ERR_OR_NULL(test, rt); ++ mctp_test_dst_setup(test, &dst, dev, &tpq, 68); + + rc = sock_create_kern(&init_net, AF_MCTP, SOCK_DGRAM, 0, &sock); + KUNIT_ASSERT_EQ(test, rc, 0); +@@ -592,7 +483,7 @@ static void mctp_test_route_input_sk_keys(struct kunit *test) + + mctp_test_skb_set_dev(skb, dev); + +- rc = mctp_route_input(&rt->rt, skb); ++ rc = mctp_dst_input(&dst, skb); + + /* (potentially) receive message */ + skb2 = skb_recv_datagram(sock->sk, MSG_DONTWAIT, &rc); +@@ -606,7 +497,7 @@ static void mctp_test_route_input_sk_keys(struct kunit *test) + skb_free_datagram(sock->sk, skb2); + + mctp_key_unref(key); +- __mctp_route_test_fini(test, dev, rt, sock); ++ __mctp_route_test_fini(test, dev, &dst, &tpq, sock); + } + + static const struct mctp_route_input_sk_keys_test mctp_route_input_sk_keys_tests[] = { +@@ -681,7 +572,8 @@ KUNIT_ARRAY_PARAM(mctp_route_input_sk_keys, mctp_route_input_sk_keys_tests, + struct test_net { + unsigned int netid; + struct mctp_test_dev *dev; +- struct mctp_test_route *rt; ++ struct mctp_test_pktqueue tpq; ++ struct mctp_dst dst; + struct socket *sock; + struct sk_buff *skb; + struct mctp_sk_key *key; +@@ -699,18 +591,20 @@ mctp_test_route_input_multiple_nets_bind_init(struct kunit *test, + + t->msg.data = t->netid; + +- __mctp_route_test_init(test, &t->dev, &t->rt, &t->sock, t->netid); ++ __mctp_route_test_init(test, &t->dev, &t->dst, &t->tpq, &t->sock, ++ t->netid); + + t->skb = mctp_test_create_skb_data(&hdr, &t->msg); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, t->skb); + mctp_test_skb_set_dev(t->skb, t->dev); ++ mctp_test_pktqueue_init(&t->tpq); + } + + static void + mctp_test_route_input_multiple_nets_bind_fini(struct kunit *test, + struct test_net *t) + { +- __mctp_route_test_fini(test, t->dev, t->rt, t->sock); ++ __mctp_route_test_fini(test, t->dev, &t->dst, &t->tpq, t->sock); + } + + /* Test that skbs from different nets (otherwise identical) get routed to their +@@ -731,9 +625,9 @@ static void mctp_test_route_input_multiple_nets_bind(struct kunit *test) + mctp_test_route_input_multiple_nets_bind_init(test, &t1); + mctp_test_route_input_multiple_nets_bind_init(test, &t2); + +- rc = mctp_route_input(&t1.rt->rt, t1.skb); ++ rc = mctp_dst_input(&t1.dst, t1.skb); + KUNIT_ASSERT_EQ(test, rc, 0); +- rc = mctp_route_input(&t2.rt->rt, t2.skb); ++ rc = mctp_dst_input(&t2.dst, t2.skb); + KUNIT_ASSERT_EQ(test, rc, 0); + + rx_skb1 = skb_recv_datagram(t1.sock->sk, MSG_DONTWAIT, &rc); +@@ -767,7 +661,8 @@ mctp_test_route_input_multiple_nets_key_init(struct kunit *test, + + t->msg.data = t->netid; + +- __mctp_route_test_init(test, &t->dev, &t->rt, &t->sock, t->netid); ++ __mctp_route_test_init(test, &t->dev, &t->dst, &t->tpq, &t->sock, ++ t->netid); + + msk = container_of(t->sock->sk, struct mctp_sock, sk); + +@@ -790,7 +685,7 @@ mctp_test_route_input_multiple_nets_key_fini(struct kunit *test, + struct test_net *t) + { + mctp_key_unref(t->key); +- __mctp_route_test_fini(test, t->dev, t->rt, t->sock); ++ __mctp_route_test_fini(test, t->dev, &t->dst, &t->tpq, t->sock); + } + + /* test that skbs from different nets (otherwise identical) get routed to their +@@ -812,9 +707,9 @@ static void mctp_test_route_input_multiple_nets_key(struct kunit *test) + mctp_test_route_input_multiple_nets_key_init(test, &t1); + mctp_test_route_input_multiple_nets_key_init(test, &t2); + +- rc = mctp_route_input(&t1.rt->rt, t1.skb); ++ rc = mctp_dst_input(&t1.dst, t1.skb); + KUNIT_ASSERT_EQ(test, rc, 0); +- rc = mctp_route_input(&t2.rt->rt, t2.skb); ++ rc = mctp_dst_input(&t2.dst, t2.skb); + KUNIT_ASSERT_EQ(test, rc, 0); + + rx_skb1 = skb_recv_datagram(t1.sock->sk, MSG_DONTWAIT, &rc); +@@ -843,13 +738,14 @@ static void mctp_test_route_input_multiple_nets_key(struct kunit *test) + static void mctp_test_route_input_sk_fail_single(struct kunit *test) + { + const struct mctp_hdr hdr = RX_HDR(1, 10, 8, FL_S | FL_E | FL_TO); +- struct mctp_test_route *rt; ++ struct mctp_test_pktqueue tpq; + struct mctp_test_dev *dev; ++ struct mctp_dst dst; + struct socket *sock; + struct sk_buff *skb; + int rc; + +- __mctp_route_test_init(test, &dev, &rt, &sock, MCTP_NET_ANY); ++ __mctp_route_test_init(test, &dev, &dst, &tpq, &sock, MCTP_NET_ANY); + + /* No rcvbuf space, so delivery should fail. __sock_set_rcvbuf will + * clamp the minimum to SOCK_MIN_RCVBUF, so we open-code this. +@@ -865,14 +761,14 @@ static void mctp_test_route_input_sk_fail_single(struct kunit *test) + mctp_test_skb_set_dev(skb, dev); + + /* do route input, which should fail */ +- rc = mctp_route_input(&rt->rt, skb); ++ rc = mctp_dst_input(&dst, skb); + KUNIT_EXPECT_NE(test, rc, 0); + + /* we should hold the only reference to skb */ + KUNIT_EXPECT_EQ(test, refcount_read(&skb->users), 1); + kfree_skb(skb); + +- __mctp_route_test_fini(test, dev, rt, sock); ++ __mctp_route_test_fini(test, dev, &dst, &tpq, sock); + } + + /* Input route to socket, using a fragmented message, where sock delivery fails. +@@ -880,14 +776,15 @@ static void mctp_test_route_input_sk_fail_single(struct kunit *test) + static void mctp_test_route_input_sk_fail_frag(struct kunit *test) + { + const struct mctp_hdr hdrs[2] = { RX_FRAG(FL_S, 0), RX_FRAG(FL_E, 1) }; +- struct mctp_test_route *rt; ++ struct mctp_test_pktqueue tpq; + struct mctp_test_dev *dev; + struct sk_buff *skbs[2]; ++ struct mctp_dst dst; + struct socket *sock; + unsigned int i; + int rc; + +- __mctp_route_test_init(test, &dev, &rt, &sock, MCTP_NET_ANY); ++ __mctp_route_test_init(test, &dev, &dst, &tpq, &sock, MCTP_NET_ANY); + + lock_sock(sock->sk); + WRITE_ONCE(sock->sk->sk_rcvbuf, 0); +@@ -904,11 +801,11 @@ static void mctp_test_route_input_sk_fail_frag(struct kunit *test) + /* first route input should succeed, we're only queueing to the + * frag list + */ +- rc = mctp_route_input(&rt->rt, skbs[0]); ++ rc = mctp_dst_input(&dst, skbs[0]); + KUNIT_EXPECT_EQ(test, rc, 0); + + /* final route input should fail to deliver to the socket */ +- rc = mctp_route_input(&rt->rt, skbs[1]); ++ rc = mctp_dst_input(&dst, skbs[1]); + KUNIT_EXPECT_NE(test, rc, 0); + + /* we should hold the only reference to both skbs */ +@@ -918,7 +815,7 @@ static void mctp_test_route_input_sk_fail_frag(struct kunit *test) + KUNIT_EXPECT_EQ(test, refcount_read(&skbs[1]->users), 1); + kfree_skb(skbs[1]); + +- __mctp_route_test_fini(test, dev, rt, sock); ++ __mctp_route_test_fini(test, dev, &dst, &tpq, sock); + } + + /* Input route to socket, using a fragmented message created from clones. +@@ -933,23 +830,22 @@ static void mctp_test_route_input_cloned_frag(struct kunit *test) + RX_FRAG(FL_S, 0), + RX_FRAG(FL_E, 1), + }; +- struct mctp_test_route *rt; ++ const size_t data_len = 3; /* arbitrary */ ++ u8 compare[3 * ARRAY_SIZE(hdrs)]; ++ u8 flat[3 * ARRAY_SIZE(hdrs)]; ++ struct mctp_test_pktqueue tpq; + struct mctp_test_dev *dev; + struct sk_buff *skb[5]; + struct sk_buff *rx_skb; ++ struct mctp_dst dst; + struct socket *sock; +- size_t data_len; +- u8 compare[100]; +- u8 flat[100]; + size_t total; + void *p; + int rc; + +- /* Arbitrary length */ +- data_len = 3; + total = data_len + sizeof(struct mctp_hdr); + +- __mctp_route_test_init(test, &dev, &rt, &sock, MCTP_NET_ANY); ++ __mctp_route_test_init(test, &dev, &dst, &tpq, &sock, MCTP_NET_ANY); + + /* Create a single skb initially with concatenated packets */ + skb[0] = mctp_test_create_skb(&hdrs[0], 5 * total); +@@ -988,7 +884,7 @@ static void mctp_test_route_input_cloned_frag(struct kunit *test) + + /* Feed the fragments into MCTP core */ + for (int i = 0; i < 5; i++) { +- rc = mctp_route_input(&rt->rt, skb[i]); ++ rc = mctp_dst_input(&dst, skb[i]); + KUNIT_EXPECT_EQ(test, rc, 0); + } + +@@ -1026,29 +922,29 @@ static void mctp_test_route_input_cloned_frag(struct kunit *test) + kfree_skb(skb[i]); + } + +- __mctp_route_test_fini(test, dev, rt, sock); ++ __mctp_route_test_fini(test, dev, &dst, &tpq, sock); + } + + #if IS_ENABLED(CONFIG_MCTP_FLOWS) + + static void mctp_test_flow_init(struct kunit *test, + struct mctp_test_dev **devp, +- struct mctp_test_route **rtp, ++ struct mctp_dst *dst, ++ struct mctp_test_pktqueue *tpq, + struct socket **sock, + struct sk_buff **skbp, + unsigned int len) + { +- struct mctp_test_route *rt; + struct mctp_test_dev *dev; + struct sk_buff *skb; + + /* we have a slightly odd routing setup here; the test route + * is for EID 8, which is our local EID. We don't do a routing + * lookup, so that's fine - all we require is a path through +- * mctp_local_output, which will call rt->output on whatever ++ * mctp_local_output, which will call dst->output on whatever + * route we provide + */ +- __mctp_route_test_init(test, &dev, &rt, sock, MCTP_NET_ANY); ++ __mctp_route_test_init(test, &dev, dst, tpq, sock, MCTP_NET_ANY); + + /* Assign a single EID. ->addrs is freed on mctp netdev release */ + dev->mdev->addrs = kmalloc(sizeof(u8), GFP_KERNEL); +@@ -1061,42 +957,41 @@ static void mctp_test_flow_init(struct kunit *test, + skb_reserve(skb, sizeof(struct mctp_hdr) + 1); + memset(skb_put(skb, len), 0, len); + +- /* take a ref for the route, we'll decrement in local output */ +- refcount_inc(&rt->rt.refs); + + *devp = dev; +- *rtp = rt; + *skbp = skb; + } + + static void mctp_test_flow_fini(struct kunit *test, + struct mctp_test_dev *dev, +- struct mctp_test_route *rt, ++ struct mctp_dst *dst, ++ struct mctp_test_pktqueue *tpq, + struct socket *sock) + { +- __mctp_route_test_fini(test, dev, rt, sock); ++ __mctp_route_test_fini(test, dev, dst, tpq, sock); + } + + /* test that an outgoing skb has the correct MCTP extension data set */ + static void mctp_test_packet_flow(struct kunit *test) + { ++ struct mctp_test_pktqueue tpq; + struct sk_buff *skb, *skb2; +- struct mctp_test_route *rt; + struct mctp_test_dev *dev; ++ struct mctp_dst dst; + struct mctp_flow *flow; + struct socket *sock; +- u8 dst = 8; ++ u8 dst_eid = 8; + int n, rc; + +- mctp_test_flow_init(test, &dev, &rt, &sock, &skb, 30); ++ mctp_test_flow_init(test, &dev, &dst, &tpq, &sock, &skb, 30); + +- rc = mctp_local_output(sock->sk, &rt->rt, skb, dst, MCTP_TAG_OWNER); ++ rc = mctp_local_output(sock->sk, &dst, skb, dst_eid, MCTP_TAG_OWNER); + KUNIT_ASSERT_EQ(test, rc, 0); + +- n = rt->pkts.qlen; ++ n = tpq.pkts.qlen; + KUNIT_ASSERT_EQ(test, n, 1); + +- skb2 = skb_dequeue(&rt->pkts); ++ skb2 = skb_dequeue(&tpq.pkts); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, skb2); + + flow = skb_ext_find(skb2, SKB_EXT_MCTP); +@@ -1105,7 +1000,7 @@ static void mctp_test_packet_flow(struct kunit *test) + KUNIT_ASSERT_PTR_EQ(test, flow->key->sk, sock->sk); + + kfree_skb(skb2); +- mctp_test_flow_fini(test, dev, rt, sock); ++ mctp_test_flow_fini(test, dev, &dst, &tpq, sock); + } + + /* test that outgoing skbs, after fragmentation, all have the correct MCTP +@@ -1113,26 +1008,27 @@ static void mctp_test_packet_flow(struct kunit *test) + */ + static void mctp_test_fragment_flow(struct kunit *test) + { ++ struct mctp_test_pktqueue tpq; + struct mctp_flow *flows[2]; + struct sk_buff *tx_skbs[2]; +- struct mctp_test_route *rt; + struct mctp_test_dev *dev; ++ struct mctp_dst dst; + struct sk_buff *skb; + struct socket *sock; +- u8 dst = 8; ++ u8 dst_eid = 8; + int n, rc; + +- mctp_test_flow_init(test, &dev, &rt, &sock, &skb, 100); ++ mctp_test_flow_init(test, &dev, &dst, &tpq, &sock, &skb, 100); + +- rc = mctp_local_output(sock->sk, &rt->rt, skb, dst, MCTP_TAG_OWNER); ++ rc = mctp_local_output(sock->sk, &dst, skb, dst_eid, MCTP_TAG_OWNER); + KUNIT_ASSERT_EQ(test, rc, 0); + +- n = rt->pkts.qlen; ++ n = tpq.pkts.qlen; + KUNIT_ASSERT_EQ(test, n, 2); + + /* both resulting packets should have the same flow data */ +- tx_skbs[0] = skb_dequeue(&rt->pkts); +- tx_skbs[1] = skb_dequeue(&rt->pkts); ++ tx_skbs[0] = skb_dequeue(&tpq.pkts); ++ tx_skbs[1] = skb_dequeue(&tpq.pkts); + + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, tx_skbs[0]); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, tx_skbs[1]); +@@ -1148,7 +1044,7 @@ static void mctp_test_fragment_flow(struct kunit *test) + + kfree_skb(tx_skbs[0]); + kfree_skb(tx_skbs[1]); +- mctp_test_flow_fini(test, dev, rt, sock); ++ mctp_test_flow_fini(test, dev, &dst, &tpq, sock); + } + + #else +@@ -1166,15 +1062,16 @@ static void mctp_test_fragment_flow(struct kunit *test) + /* Test that outgoing skbs cause a suitable tag to be created */ + static void mctp_test_route_output_key_create(struct kunit *test) + { ++ const u8 dst_eid = 26, src_eid = 15; ++ struct mctp_test_pktqueue tpq; + const unsigned int netid = 50; +- const u8 dst = 26, src = 15; +- struct mctp_test_route *rt; + struct mctp_test_dev *dev; + struct mctp_sk_key *key; + struct netns_mctp *mns; + unsigned long flags; + struct socket *sock; + struct sk_buff *skb; ++ struct mctp_dst dst; + bool empty, single; + const int len = 2; + int rc; +@@ -1183,15 +1080,14 @@ static void mctp_test_route_output_key_create(struct kunit *test) + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dev); + WRITE_ONCE(dev->mdev->net, netid); + +- rt = mctp_test_create_route(&init_net, dev->mdev, dst, 68); +- KUNIT_ASSERT_NOT_ERR_OR_NULL(test, rt); ++ mctp_test_dst_setup(test, &dst, dev, &tpq, 68); + + rc = sock_create_kern(&init_net, AF_MCTP, SOCK_DGRAM, 0, &sock); + KUNIT_ASSERT_EQ(test, rc, 0); + + dev->mdev->addrs = kmalloc(sizeof(u8), GFP_KERNEL); + dev->mdev->num_addrs = 1; +- dev->mdev->addrs[0] = src; ++ dev->mdev->addrs[0] = src_eid; + + skb = alloc_skb(sizeof(struct mctp_hdr) + 1 + len, GFP_KERNEL); + KUNIT_ASSERT_TRUE(test, skb); +@@ -1199,8 +1095,6 @@ static void mctp_test_route_output_key_create(struct kunit *test) + skb_reserve(skb, sizeof(struct mctp_hdr) + 1 + len); + memset(skb_put(skb, len), 0, len); + +- refcount_inc(&rt->rt.refs); +- + mns = &sock_net(sock->sk)->mctp; + + /* We assume we're starting from an empty keys list, which requires +@@ -1211,7 +1105,7 @@ static void mctp_test_route_output_key_create(struct kunit *test) + spin_unlock_irqrestore(&mns->keys_lock, flags); + KUNIT_ASSERT_TRUE(test, empty); + +- rc = mctp_local_output(sock->sk, &rt->rt, skb, dst, MCTP_TAG_OWNER); ++ rc = mctp_local_output(sock->sk, &dst, skb, dst_eid, MCTP_TAG_OWNER); + KUNIT_ASSERT_EQ(test, rc, 0); + + key = NULL; +@@ -1227,16 +1121,295 @@ static void mctp_test_route_output_key_create(struct kunit *test) + KUNIT_ASSERT_TRUE(test, single); + + KUNIT_EXPECT_EQ(test, key->net, netid); +- KUNIT_EXPECT_EQ(test, key->local_addr, src); +- KUNIT_EXPECT_EQ(test, key->peer_addr, dst); ++ KUNIT_EXPECT_EQ(test, key->local_addr, src_eid); ++ KUNIT_EXPECT_EQ(test, key->peer_addr, dst_eid); + /* key has incoming tag, so inverse of what we sent */ + KUNIT_EXPECT_FALSE(test, key->tag & MCTP_TAG_OWNER); + + sock_release(sock); +- mctp_test_route_destroy(test, rt); ++ mctp_test_dst_release(&dst, &tpq); + mctp_test_destroy_dev(dev); + } + ++static void mctp_test_route_extaddr_input(struct kunit *test) ++{ ++ static const unsigned char haddr[] = { 0xaa, 0x55 }; ++ struct mctp_test_pktqueue tpq; ++ struct mctp_skb_cb *cb, *cb2; ++ const unsigned int len = 40; ++ struct mctp_test_dev *dev; ++ struct sk_buff *skb, *skb2; ++ struct mctp_dst dst; ++ struct mctp_hdr hdr; ++ struct socket *sock; ++ int rc; ++ ++ hdr.ver = 1; ++ hdr.src = 10; ++ hdr.dest = 8; ++ hdr.flags_seq_tag = FL_S | FL_E | FL_TO; ++ ++ __mctp_route_test_init(test, &dev, &dst, &tpq, &sock, MCTP_NET_ANY); ++ ++ skb = mctp_test_create_skb(&hdr, len); ++ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, skb); ++ ++ /* set our hardware addressing data */ ++ cb = mctp_cb(skb); ++ memcpy(cb->haddr, haddr, sizeof(haddr)); ++ cb->halen = sizeof(haddr); ++ ++ mctp_test_skb_set_dev(skb, dev); ++ ++ rc = mctp_dst_input(&dst, skb); ++ KUNIT_ASSERT_EQ(test, rc, 0); ++ ++ mctp_test_dst_release(&dst, &tpq); ++ ++ skb2 = skb_recv_datagram(sock->sk, MSG_DONTWAIT, &rc); ++ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, skb2); ++ KUNIT_ASSERT_EQ(test, skb2->len, len); ++ ++ cb2 = mctp_cb(skb2); ++ ++ /* Received SKB should have the hardware addressing as set above. ++ * We're likely to have the same actual cb here (ie., cb == cb2), ++ * but it's the comparison that we care about ++ */ ++ KUNIT_EXPECT_EQ(test, cb2->halen, sizeof(haddr)); ++ KUNIT_EXPECT_MEMEQ(test, cb2->haddr, haddr, sizeof(haddr)); ++ ++ skb_free_datagram(sock->sk, skb2); ++ mctp_test_destroy_dev(dev); ++} ++ ++static void mctp_test_route_gw_lookup(struct kunit *test) ++{ ++ struct mctp_test_route *rt1, *rt2; ++ struct mctp_dst dst = { 0 }; ++ struct mctp_test_dev *dev; ++ int rc; ++ ++ dev = mctp_test_create_dev(); ++ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dev); ++ ++ /* 8 (local) -> 10 (gateway) via 9 (direct) */ ++ rt1 = mctp_test_create_route_direct(&init_net, dev->mdev, 9, 0); ++ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, rt1); ++ rt2 = mctp_test_create_route_gw(&init_net, dev->mdev->net, 10, 9, 0); ++ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, rt2); ++ ++ rc = mctp_route_lookup(&init_net, dev->mdev->net, 10, &dst); ++ KUNIT_EXPECT_EQ(test, rc, 0); ++ KUNIT_EXPECT_PTR_EQ(test, dst.dev, dev->mdev); ++ KUNIT_EXPECT_EQ(test, dst.mtu, dev->ndev->mtu); ++ KUNIT_EXPECT_EQ(test, dst.nexthop, 9); ++ KUNIT_EXPECT_EQ(test, dst.halen, 0); ++ ++ mctp_dst_release(&dst); ++ ++ mctp_test_route_destroy(test, rt2); ++ mctp_test_route_destroy(test, rt1); ++ mctp_test_destroy_dev(dev); ++} ++ ++static void mctp_test_route_gw_loop(struct kunit *test) ++{ ++ struct mctp_test_route *rt1, *rt2; ++ struct mctp_dst dst = { 0 }; ++ struct mctp_test_dev *dev; ++ int rc; ++ ++ dev = mctp_test_create_dev(); ++ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dev); ++ ++ /* two routes using each other as the gw */ ++ rt1 = mctp_test_create_route_gw(&init_net, dev->mdev->net, 9, 10, 0); ++ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, rt1); ++ rt2 = mctp_test_create_route_gw(&init_net, dev->mdev->net, 10, 9, 0); ++ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, rt2); ++ ++ /* this should fail, rather than infinite-loop */ ++ rc = mctp_route_lookup(&init_net, dev->mdev->net, 10, &dst); ++ KUNIT_EXPECT_NE(test, rc, 0); ++ ++ mctp_test_route_destroy(test, rt2); ++ mctp_test_route_destroy(test, rt1); ++ mctp_test_destroy_dev(dev); ++} ++ ++struct mctp_route_gw_mtu_test { ++ /* working away from the local stack */ ++ unsigned int dev, neigh, gw, dst; ++ unsigned int exp; ++}; ++ ++static void mctp_route_gw_mtu_to_desc(const struct mctp_route_gw_mtu_test *t, ++ char *desc) ++{ ++ sprintf(desc, "dev %d, neigh %d, gw %d, dst %d -> %d", ++ t->dev, t->neigh, t->gw, t->dst, t->exp); ++} ++ ++static const struct mctp_route_gw_mtu_test mctp_route_gw_mtu_tests[] = { ++ /* no route-specific MTUs */ ++ { 68, 0, 0, 0, 68 }, ++ { 100, 0, 0, 0, 100 }, ++ /* one route MTU (smaller than dev mtu), others unrestricted */ ++ { 100, 68, 0, 0, 68 }, ++ { 100, 0, 68, 0, 68 }, ++ { 100, 0, 0, 68, 68 }, ++ /* smallest applied, regardless of order */ ++ { 100, 99, 98, 68, 68 }, ++ { 99, 100, 98, 68, 68 }, ++ { 98, 99, 100, 68, 68 }, ++ { 68, 98, 99, 100, 68 }, ++}; ++ ++KUNIT_ARRAY_PARAM(mctp_route_gw_mtu, mctp_route_gw_mtu_tests, ++ mctp_route_gw_mtu_to_desc); ++ ++static void mctp_test_route_gw_mtu(struct kunit *test) ++{ ++ const struct mctp_route_gw_mtu_test *mtus = test->param_value; ++ struct mctp_test_route *rt1, *rt2, *rt3; ++ struct mctp_dst dst = { 0 }; ++ struct mctp_test_dev *dev; ++ struct mctp_dev *mdev; ++ unsigned int netid; ++ int rc; ++ ++ dev = mctp_test_create_dev(); ++ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dev); ++ dev->ndev->mtu = mtus->dev; ++ mdev = dev->mdev; ++ netid = mdev->net; ++ ++ /* 8 (local) -> 11 (dst) via 10 (gw) via 9 (neigh) */ ++ rt1 = mctp_test_create_route_direct(&init_net, mdev, 9, mtus->neigh); ++ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, rt1); ++ ++ rt2 = mctp_test_create_route_gw(&init_net, netid, 10, 9, mtus->gw); ++ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, rt2); ++ ++ rt3 = mctp_test_create_route_gw(&init_net, netid, 11, 10, mtus->dst); ++ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, rt3); ++ ++ rc = mctp_route_lookup(&init_net, dev->mdev->net, 11, &dst); ++ KUNIT_EXPECT_EQ(test, rc, 0); ++ KUNIT_EXPECT_EQ(test, dst.mtu, mtus->exp); ++ ++ mctp_dst_release(&dst); ++ ++ mctp_test_route_destroy(test, rt3); ++ mctp_test_route_destroy(test, rt2); ++ mctp_test_route_destroy(test, rt1); ++ mctp_test_destroy_dev(dev); ++} ++ ++#define MCTP_TEST_LLADDR_LEN 2 ++struct mctp_test_llhdr { ++ unsigned int magic; ++ unsigned char src[MCTP_TEST_LLADDR_LEN]; ++ unsigned char dst[MCTP_TEST_LLADDR_LEN]; ++}; ++ ++static const unsigned int mctp_test_llhdr_magic = 0x5c78339c; ++ ++static int test_dev_header_create(struct sk_buff *skb, struct net_device *dev, ++ unsigned short type, const void *daddr, ++ const void *saddr, unsigned int len) ++{ ++ struct kunit *test = current->kunit_test; ++ struct mctp_test_llhdr *hdr; ++ ++ hdr = skb_push(skb, sizeof(*hdr)); ++ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, hdr); ++ skb_reset_mac_header(skb); ++ ++ hdr->magic = mctp_test_llhdr_magic; ++ memcpy(&hdr->src, saddr, sizeof(hdr->src)); ++ memcpy(&hdr->dst, daddr, sizeof(hdr->dst)); ++ ++ return 0; ++} ++ ++/* Test the dst_output path for a gateway-routed skb: we should have it ++ * lookup the nexthop EID in the neighbour table, and call into ++ * header_ops->create to resolve that to a lladdr. Our mock header_ops->create ++ * will just set a synthetic link-layer header, which we check after transmit. ++ */ ++static void mctp_test_route_gw_output(struct kunit *test) ++{ ++ const unsigned char haddr_self[MCTP_TEST_LLADDR_LEN] = { 0xaa, 0x03 }; ++ const unsigned char haddr_peer[MCTP_TEST_LLADDR_LEN] = { 0xaa, 0x02 }; ++ const struct header_ops ops = { ++ .create = test_dev_header_create, ++ }; ++ struct mctp_neigh neigh = { 0 }; ++ struct mctp_test_llhdr *ll_hdr; ++ struct mctp_dst dst = { 0 }; ++ struct mctp_hdr hdr = { 0 }; ++ struct mctp_test_dev *dev; ++ struct sk_buff *skb; ++ unsigned char *buf; ++ int i, rc; ++ ++ dev = mctp_test_create_dev_lladdr(sizeof(haddr_self), haddr_self); ++ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dev); ++ dev->ndev->header_ops = &ops; ++ ++ dst.dev = dev->mdev; ++ __mctp_dev_get(dst.dev->dev); ++ dst.mtu = 68; ++ dst.nexthop = 9; ++ ++ /* simple mctp_neigh_add for the gateway (not dest!) endpoint */ ++ INIT_LIST_HEAD(&neigh.list); ++ neigh.dev = dev->mdev; ++ mctp_dev_hold(dev->mdev); ++ neigh.eid = 9; ++ neigh.source = MCTP_NEIGH_STATIC; ++ memcpy(neigh.ha, haddr_peer, sizeof(haddr_peer)); ++ list_add_rcu(&neigh.list, &init_net.mctp.neighbours); ++ ++ hdr.ver = 1; ++ hdr.src = 8; ++ hdr.dest = 10; ++ hdr.flags_seq_tag = FL_S | FL_E | FL_TO; ++ ++ /* construct enough for a future link-layer header, the provided ++ * mctp header, and 4 bytes of data ++ */ ++ skb = alloc_skb(sizeof(*ll_hdr) + sizeof(hdr) + 4, GFP_KERNEL); ++ skb->dev = dev->ndev; ++ __mctp_cb(skb); ++ ++ skb_reserve(skb, sizeof(*ll_hdr)); ++ ++ memcpy(skb_put(skb, sizeof(hdr)), &hdr, sizeof(hdr)); ++ buf = skb_put(skb, 4); ++ for (i = 0; i < 4; i++) ++ buf[i] = i; ++ ++ /* extra ref over the dev_xmit */ ++ skb_get(skb); ++ ++ rc = mctp_dst_output(&dst, skb); ++ KUNIT_EXPECT_EQ(test, rc, 0); ++ ++ mctp_dst_release(&dst); ++ list_del_rcu(&neigh.list); ++ mctp_dev_put(dev->mdev); ++ ++ /* check that we have our header created with the correct neighbour */ ++ ll_hdr = (void *)skb_mac_header(skb); ++ KUNIT_EXPECT_EQ(test, ll_hdr->magic, mctp_test_llhdr_magic); ++ KUNIT_EXPECT_MEMEQ(test, ll_hdr->src, haddr_self, sizeof(haddr_self)); ++ KUNIT_EXPECT_MEMEQ(test, ll_hdr->dst, haddr_peer, sizeof(haddr_peer)); ++ kfree_skb(skb); ++} ++ + static struct kunit_case mctp_test_cases[] = { + KUNIT_CASE_PARAM(mctp_test_fragment, mctp_frag_gen_params), + KUNIT_CASE_PARAM(mctp_test_rx_input, mctp_rx_input_gen_params), +@@ -1253,11 +1426,16 @@ static struct kunit_case mctp_test_cases[] = { + KUNIT_CASE(mctp_test_fragment_flow), + KUNIT_CASE(mctp_test_route_output_key_create), + KUNIT_CASE(mctp_test_route_input_cloned_frag), ++ KUNIT_CASE(mctp_test_route_extaddr_input), ++ KUNIT_CASE(mctp_test_route_gw_lookup), ++ KUNIT_CASE(mctp_test_route_gw_loop), ++ KUNIT_CASE_PARAM(mctp_test_route_gw_mtu, mctp_route_gw_mtu_gen_params), ++ KUNIT_CASE(mctp_test_route_gw_output), + {} + }; + + static struct kunit_suite mctp_test_suite = { +- .name = "mctp", ++ .name = "mctp-route", + .test_cases = mctp_test_cases, + }; + +-- +2.34.1 + diff --git a/patches-sonic/0001-mlxbf-bootctl-Constify-struct-bin_attribute.patch b/patches-sonic/0001-mlxbf-bootctl-Constify-struct-bin_attribute.patch deleted file mode 100644 index cd30de111..000000000 --- a/patches-sonic/0001-mlxbf-bootctl-Constify-struct-bin_attribute.patch +++ /dev/null @@ -1,48 +0,0 @@ -From cad481488093e440217f6a93e45fe4fa03a77000 Mon Sep 17 00:00:00 2001 -From: =?UTF-8?q?Thomas=20Wei=C3=9Fschuh?= -Date: Sun, 15 Dec 2024 14:25:40 +0100 -Subject: [PATCH 01/78] mlxbf-bootctl: Constify 'struct bin_attribute' -MIME-Version: 1.0 -Content-Type: text/plain; charset=UTF-8 -Content-Transfer-Encoding: 8bit - -The sysfs core now allows instances of 'struct bin_attribute' to be -moved into read-only memory. Make use of that to protect them against -accidental or malicious modifications. - -Signed-off-by: Thomas Weißschuh -Link: https://lore.kernel.org/r/20241215-sysfs-const-bin_attr-mellanox-v1-1-b6fe4f68e2ca@weissschuh.net -Reviewed-by: Ilpo Järvinen -Signed-off-by: Ilpo Järvinen ---- - drivers/platform/mellanox/mlxbf-bootctl.c | 6 +++--- - 1 file changed, 3 insertions(+), 3 deletions(-) - -diff --git a/drivers/platform/mellanox/mlxbf-bootctl.c b/drivers/platform/mellanox/mlxbf-bootctl.c -index dd5f370..7e246dd 100644 ---- a/drivers/platform/mellanox/mlxbf-bootctl.c -+++ b/drivers/platform/mellanox/mlxbf-bootctl.c -@@ -939,7 +939,7 @@ MODULE_DEVICE_TABLE(acpi, mlxbf_bootctl_acpi_ids); - - static ssize_t mlxbf_bootctl_bootfifo_read(struct file *filp, - struct kobject *kobj, -- struct bin_attribute *bin_attr, -+ const struct bin_attribute *bin_attr, - char *buf, loff_t pos, - size_t count) - { -@@ -971,9 +971,9 @@ static ssize_t mlxbf_bootctl_bootfifo_read(struct file *filp, - return p - buf; - } - --static struct bin_attribute mlxbf_bootctl_bootfifo_sysfs_attr = { -+static const struct bin_attribute mlxbf_bootctl_bootfifo_sysfs_attr = { - .attr = { .name = "bootfifo", .mode = 0400 }, -- .read = mlxbf_bootctl_bootfifo_read, -+ .read_new = mlxbf_bootctl_bootfifo_read, - }; - - static bool mlxbf_bootctl_guid_match(const guid_t *guid, --- -2.8.4 - diff --git a/patches-sonic/0002-mctp-aspeed-IRoT-Add-initial-support.patch b/patches-sonic/0002-mctp-aspeed-IRoT-Add-initial-support.patch new file mode 100644 index 000000000..59c5c4b7d --- /dev/null +++ b/patches-sonic/0002-mctp-aspeed-IRoT-Add-initial-support.patch @@ -0,0 +1,1183 @@ +From ecaf5d322f21f2070a94310b54bc1069bde0c471 Mon Sep 17 00:00:00 2001 +From: Vadim Pasternak +Date: Sun, 10 May 2026 11:31:35 +0300 +Subject: [PATCH 6.12 2/5] mctp: aspeed: IRoT: Add initial support + +Add initial support for NVIDIA's Internal Root of Trust (IRoT) on the +Aspeed AST27xx BMC, exposed as an MCTP transport over the hardware +mailbox. + +A new CONFIG_NVIDIA_AST27XX_IROT option (depends on ARCH_ASPEED) builds +the nvidia-ast27xx-irot driver, which creates an MCTP network device for +a BMC device-tree node compatible with "nvidia,ast27xx,irot" (for +example mctpirot0). + +Signed-off-by: Vadim Pasternak +--- + drivers/net/mctp/Kconfig | 8 + + drivers/net/mctp/Makefile | 1 + + drivers/net/mctp/nvidia-ast27xx-irot.c | 1121 ++++++++++++++++++++++++ + 3 files changed, 1130 insertions(+) + create mode 100644 drivers/net/mctp/nvidia-ast27xx-irot.c + +diff --git a/drivers/net/mctp/Kconfig b/drivers/net/mctp/Kconfig +index fea55902e..2fc8e675b 100644 +--- a/drivers/net/mctp/Kconfig ++++ b/drivers/net/mctp/Kconfig +@@ -47,6 +47,14 @@ config MCTP_TRANSPORT_I3C + A MCTP protocol network device is created for each I3C bus + having a "mctp-controller" devicetree property. + ++config NVIDIA_AST27XX_IROT ++ tristate "NVIDIA AST27XX IRoT support" ++ depends on ARCH_ASPEED ++ help ++ Enable support for NVIDIA AST27XX IRoT over mailbox MCTP. ++ Select this if the BMC device tree provides a "nvidia,ast27xx,irot" ++ node (for example mctpirot0). ++ + config MCTP_TRANSPORT_SPI + tristate "MCTP Over SPI Transprt" + help +diff --git a/drivers/net/mctp/Makefile b/drivers/net/mctp/Makefile +index cfb127d78..996808975 100644 +--- a/drivers/net/mctp/Makefile ++++ b/drivers/net/mctp/Makefile +@@ -2,6 +2,7 @@ obj-$(CONFIG_MCTP_SERIAL) += mctp-serial.o + obj-$(CONFIG_MCTP_TRANSPORT_I2C) += mctp-i2c.o + obj-$(CONFIG_MCTP_TRANSPORT_I2C) += mctp-i2c-error-inject.o + obj-$(CONFIG_MCTP_TRANSPORT_I3C) += mctp-i3c.o ++obj-$(CONFIG_NVIDIA_AST27XX_IROT) += nvidia-ast27xx-irot.o + obj-$(CONFIG_MCTP_TRANSPORT_SPI) += mctp-spi.o + obj-$(CONFIG_MCTP_TRANSPORT_SPI) += mctp-spi-error-inject.o + obj-$(CONFIG_MCTP_TRANSPORT_USB) += mctp-usb.o +diff --git a/drivers/net/mctp/nvidia-ast27xx-irot.c b/drivers/net/mctp/nvidia-ast27xx-irot.c +new file mode 100644 +index 000000000..383ad4e82 +--- /dev/null ++++ b/drivers/net/mctp/nvidia-ast27xx-irot.c +@@ -0,0 +1,1121 @@ ++/// @file ++/// @brief Kernel module for MCTP communication with NVIDIA's IRoT for ASPEED's ast27xx series of BMC. ++/// @details ++/// Enable with the following device tree to create a mctp device called mctpirot0. ++/// @code{.dts} ++/// / { ++/// mctpirot0 { ++/// compatible = "nvidia,ast27xx,irot"; ++/// mboxes = <&mbox0 0>; ++/// mbox-names = "irot"; ++/// status = "okay"; ++/// }; ++/// }; ++/// ++/// &mbox0 { ++/// status = "okay"; ++/// }; ++/// @endcode ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include "nvidia_irot_ast27xx_msg_ns.h" ++ ++#define NVIDIA_IROT_QUEUE_SIZE 20 ++ ++#define NVIDIA_IROT_MCTP_LOG_ENABLED 0 ++ ++#if NVIDIA_IROT_MCTP_LOG_ENABLED ++/// @brief The max number of bytes to log per packet. ++/// @note 10 is a decent value as gets most header and protocol specific status codes. ++#define NVIDIA_IROT_MCTP_LOG_BYTES 10 ++#endif ++ ++#define LOG_IMPL(lvl, fmt, ...) \ ++ printk(lvl "NVIDIA-AST27XX-IROT: " fmt "\n", ##__VA_ARGS__) ++ ++/// @brief Helper logging macros. ++/// @details Has the same API as printf() and a newline is automatically appended. ++/// @{ ++#define LOG_INF(...) LOG_IMPL(KERN_INFO, __VA_ARGS__) ++#define LOG_WRN(...) LOG_IMPL(KERN_WARNING, __VA_ARGS__) ++#define LOG_ERR(...) LOG_IMPL(KERN_ERR, __VA_ARGS__) ++/// @} ++ ++/// @brief State shared between the mailbox callback and worker threads. ++struct nvidia_irot_driver_shared_state { ++ /// @brief Shared memory. ++ /// @details Its sizes will be zero if not initialized. ++ /// @note Is written to only once. ++ struct nvidia_irot_message_data_buffers shmem; ++ ++ /// @brief The pending MCTP read, if one exists. ++ /// @details pending_read.header.command will be zero if no read is pending. ++ struct nvidia_irot_message pending_read; ++ ++ /// @brief The pending MCTP write, if one exists. ++ /// @details pending_write.header.command will be zero if no write is pending. ++ struct nvidia_irot_message pending_write; ++ ++ /// @brief A ping that we've recieved that needs responding to. ++ /// @details rx_ping.command will be zero if no ping needs responding to. ++ struct nvidia_irot_message rx_ping; ++ ++ /// @brief If one of our responses gets dropped we may get a duplicate command. ++ /// The mailbox callback deduplicates the command handling but we need to send back a response. ++ /// This variable holds the response. ++ /// @details duplicate_response.command will be zero if no response needs to be sent. ++ struct nvidia_irot_message duplicate_response; ++}; ++ ++/// @brief State that is private for worker threads. ++struct nvidia_irot_driver_worker_state { ++ /// @brief Uncacheable RAM address from memremap for reading MCTP packets. ++ /// @note Is written to only once. ++ const void *rx_addr; ++ ++ /// @brief Uncacheable RAM address from memremap for writing MCTP packets. ++ /// @note Is written to only once. ++ void *tx_addr; ++ ++ /// @brief The next counter to use for mailbox command output. ++ u32 next_tx_counter; ++}; ++ ++/// @brief State that is private to the mailbox callback. ++struct nvidia_irot_driver_mailbox_state { ++ /// @brief The counter of the most recent rx message. ++ u32 prev_rx_counter; ++ ++ /// @brief Set to false if there is no prev_rx_counter. ++ bool has_prev_rx_counter; ++}; ++ ++/// @brief Driver type. ++struct nvidia_irot_driver { ++ /// @brief Spin lock that protects shared_state. ++ /// @warning Must not suspend when holding this lock. ++ struct spinlock shared_spin; ++ ++ /// @brief Shared state. ++ /// @details shared_spin must be held when interacting with this state. ++ struct nvidia_irot_driver_shared_state shared_state; ++ ++ /// @brief Mutex that protects worker_state. ++ struct mutex worker_mutex; ++ ++ /// @brief State used only by worker functions. ++ /// @details worker_mutex must be held when interacting with this state. ++ struct nvidia_irot_driver_worker_state worker_state; ++ ++ /// @brief State that is private to the mailbox rx callback. ++ /// @details As it is private requires no locking. ++ struct nvidia_irot_driver_mailbox_state mailbox_state; ++ ++ /// @brief Signals to the driver probe function that shared memory is ++ /// ready to be set up. ++ struct semaphore probe_sem; ++ ++ /// @brief RX worker wait queue. ++ wait_queue_head_t rx_worker_wq; ++ ++ /// @brief Set to 1 if the rx_worker has work to do. ++ atomic_t rx_work_ready; ++ ++ /// @brief wait queue that is woken when tx_queue is pushed to. ++ wait_queue_head_t tx_queue_ready_wq; ++ ++ /// @brief wait queue that is woken when shared memory becomes available. ++ wait_queue_head_t tx_shmem_ready_wq; ++ ++ /// @brief Set to 1 when shared memory becomes ready. ++ atomic_t tx_shmem_ready; ++ ++ /// @brief Mailbox objects. ++ /// @{ ++ struct mbox_client mbox_client; ++ struct mbox_chan *mbox_chan; ++ /// @} ++ ++ /// @brief Worker threads. ++ /// @{ ++ struct task_struct *rx_worker; ++ struct task_struct *tx_worker; ++ /// @} ++ ++ /// @brief Network device used by the linux kernel for this driver. ++ struct net_device *netdev; ++ ++ /// @brief Packet queue from the network device to the tx worker thread. ++ struct sk_buff_head tx_queue; ++}; ++ ++static int copy_to_shmem(struct nvidia_irot_message_span dst_phy, ++ void *dst_virt, const struct sk_buff *src) ++{ ++ if (src == NULL) { ++ return -EINVAL; ++ } ++ if (dst_phy.size < src->len) { ++ return -EINVAL; ++ } ++ if (dst_virt == NULL) { ++ return -EFAULT; ++ } ++ const int ret = skb_copy_bits(src, 0, dst_virt, src->len); ++ dma_wmb(); ++ return ret; ++} ++ ++static int copy_from_shmem(struct sk_buff *dst, ++ struct nvidia_irot_message_span src_phy, ++ const void *src_virt) ++{ ++ if (src_virt == NULL) { ++ return -EFAULT; ++ } ++ dma_rmb(); ++ (void)skb_put_data(dst, src_virt, src_phy.size); ++ return 0; ++} ++ ++/// @brief Sends a message over mailbox. ++/// @param driver The driver managing the mailbox. ++/// @param msg The message to send. ++/// @retval 0 On success. ++/// @returns -errno on error. ++static int send_message(struct nvidia_irot_driver *driver, ++ const struct nvidia_irot_message *msg) ++{ ++ if (driver == NULL || driver->mbox_chan == NULL || msg == NULL) { ++ return -EINVAL; ++ } ++ const int ret = mbox_send_message(driver->mbox_chan, (void *)msg); ++ if (ret < 0) { ++ LOG_ERR("mbox_send_message failed, ret: %d", ret); ++ return ret; ++ } ++ return 0; ++} ++ ++/// @brief Extracts a physical address from a nvidia_irot_message_span. ++static resource_size_t addr_from_span(struct nvidia_irot_message_span shmem) ++{ ++ return (((resource_size_t)shmem.address.high) << 32U) | ++ shmem.address.low; ++} ++ ++/// @brief Gets a copy of a driver's shared_state and handles all generic data processing. ++/// @pre driver->worker_mutex is held, driver->shared_spin is not held. ++/// @post The lock state is the same as on entry. driver->worker_mutex is never dropped by this function. ++/// @param driver The driver managing the IPC connection. ++/// @param [out] shared_state_copy A copy of the shared state extracted by this function. ++static void worker_locked_handle_shared_state( ++ struct nvidia_irot_driver *driver, ++ struct nvidia_irot_driver_shared_state *shared_state_copy) ++{ ++ // copy out shared_state with the lock held ++ spin_lock_irq(&driver->shared_spin); ++ memcpy(shared_state_copy, &driver->shared_state, ++ sizeof(*shared_state_copy)); ++ spin_unlock_irq(&driver->shared_spin); ++ ++ // process the shared state ++ ++ // init shared memory regions if needed ++ // NOTE: shmem is write once so can use our local copy. ++ // TODO: should we bounds check the addresses we get from IRoT? ++ if (driver->worker_state.rx_addr == NULL && ++ shared_state_copy->shmem.a35_read.size != 0) { ++ driver->worker_state.rx_addr = memremap( ++ addr_from_span(shared_state_copy->shmem.a35_read), ++ shared_state_copy->shmem.a35_read.size, MEMREMAP_WC); ++ } ++ if (driver->worker_state.tx_addr == NULL && ++ shared_state_copy->shmem.a35_write.size != 0) { ++ driver->worker_state.tx_addr = memremap( ++ addr_from_span(shared_state_copy->shmem.a35_write), ++ shared_state_copy->shmem.a35_write.size, MEMREMAP_WC); ++ } ++ ++ // handle pings if needed ++ if (shared_state_copy->rx_ping.command == nvidia_irot_cc_ping) { ++ struct nvidia_irot_message response = NVIDIA_IROT_MESSAGE_INIT; ++ response.command = nvidia_irot_cc_ping_rsp; ++ response.data.value = shared_state_copy->rx_ping.data.value; ++ if (0 == send_message(driver, &response)) { ++ // Sending the ping response succeeded. ++ // Clear the in flight ping only if the counter still matches. ++ // If the counters do not match a new ping request came in ++ // which can be handled by a future caller of this function. ++ shared_state_copy->rx_ping.command = 0; ++ spin_lock_irq(&driver->shared_spin); ++ if (driver->shared_state.rx_ping.data.value == ++ shared_state_copy->rx_ping.data.value) { ++ driver->shared_state.rx_ping.command = 0; ++ } ++ spin_unlock_irq(&driver->shared_spin); ++ } ++ } ++ ++ // handle duplicate command responses if needed ++ // NOTE: duplicate_response is only written to by the mailbox callback ++ // if it is cleared and is only cleared by worker_mutex owner. ++ // So we have exclusive write access so can use our local copy. ++ if (shared_state_copy->duplicate_response.command != 0) { ++ if (0 == send_message(driver, ++ &shared_state_copy->duplicate_response)) { ++ // success, clear the scheduled response. ++ shared_state_copy->duplicate_response.command = 0; ++ spin_lock_irq(&driver->shared_spin); ++ driver->shared_state.duplicate_response.command = 0; ++ spin_unlock_irq(&driver->shared_spin); ++ } ++ } ++} ++ ++/// @brief Gets the start offset of a allocation from within a region. ++/// @param region The region containing the allocation. ++/// @param allocation The allocation that must be fully contained within the region. ++/// @returns The offset on success. ++/// @returns -errno value on failure. ++static ssize_t get_start_offset(struct nvidia_irot_message_span region, ++ struct nvidia_irot_message_span allocation) ++{ ++ const resource_size_t region_start = addr_from_span(region); ++ const resource_size_t region_end = region_start + region.size; ++ if (region_end < region_start) { ++ // region wrap around ++ return -EFAULT; ++ } ++ const resource_size_t allocation_start = addr_from_span(allocation); ++ const resource_size_t allocation_end = ++ allocation_start + allocation.size; ++ if (allocation_end < allocation_start) { ++ // allocation wrap around ++ return -EFAULT; ++ } ++ if (allocation_start < region_start) { ++ // allocation starts too soon ++ return -EFAULT; ++ } ++ if (allocation_end > region_end) { ++ // allocation goes for too long ++ return -EFAULT; ++ } ++ return allocation_start - region_start; ++} ++ ++/// @brief Call at the end of device_read() to finialize global state and buffer management. ++/// @details If the read succeeded the pending read the IRoT is informed and the pending read is cleared. ++/// If the read failed another reader will be awoken. ++/// @pre driver->worker_mutex is held. ++/// @post No locks are held. ++/// @param driver The driver managing the read. ++/// @param result The result of the read. ++/// @param message The message that was read. ++/// @returns result unless an error was detected. ++static ssize_t finish_read_and_unlock(struct nvidia_irot_driver *driver, ++ ssize_t result, ++ const struct nvidia_irot_message *message) ++{ ++ if (result >= 0 && result != message->data.mctp.packet.size) { ++ // success return code that does not match message size, convert to fault. ++ result = -EFAULT; ++ } ++ ++ if (result >= 0) { ++ // success ++ ++ // clear the pending read ++ spin_lock_irq(&driver->shared_spin); ++ driver->shared_state.pending_read.command = 0; ++ spin_unlock_irq(&driver->shared_spin); ++ ++ // free the shared memory via mailbox command ++ struct nvidia_irot_message response = NVIDIA_IROT_MESSAGE_INIT; ++ response.command = nvidia_irot_cc_mctp_done; ++ response.data.value = message->data.mctp.counter; ++ // dropped ACKs are handled by IRoT retransmission and command deduplication ++ (void)send_message(driver, &response); ++ ++ mutex_unlock(&driver->worker_mutex); ++ return result; ++ } ++ ++ // failure, a future reader will handle this ++ mutex_unlock(&driver->worker_mutex); ++ return result; ++} ++ ++struct nvidia_irot_netdev_priv { ++ struct nvidia_irot_driver *driver; ++}; ++ ++netdev_tx_t nvidia_irot_start_xmit(struct sk_buff *skb, struct net_device *dev) ++{ ++ netdev_tx_t status = NETDEV_TX_BUSY; ++ ++ struct nvidia_irot_netdev_priv *const private = netdev_priv(dev); ++ struct nvidia_irot_driver *const driver = private->driver; ++ ++ unsigned long flags; ++ ++#if NVIDIA_IROT_MCTP_LOG_ENABLED ++ // Log the first up to NVIDIA_IROT_MCTP_LOG_BYTES bytes of skb as hex bytes separated by spaces ++ { ++ int n = skb->len < NVIDIA_IROT_MCTP_LOG_BYTES ? ++ skb->len : ++ NVIDIA_IROT_MCTP_LOG_BYTES; ++ u8 data[NVIDIA_IROT_MCTP_LOG_BYTES] = { 0 }; ++ skb_copy_bits(skb, 0, data, n); ++ char hexbuf[3 * NVIDIA_IROT_MCTP_LOG_BYTES + 1] = { 0 }; ++ int i; ++ for (i = 0; i < n; ++i) { ++ snprintf(hexbuf + i * 3, 4, "%02x ", data[i]); ++ } ++ if (n > 0) ++ hexbuf[3 * n - 1] = '\0'; // Remove trailing space ++ LOG_INF("L->I: %s", hexbuf); ++ } ++#endif ++ ++ // locking the queue for the entire push so we can atomically inspect ++ // the length to manage the network interface queue enable state. ++ spin_lock_irqsave(&driver->tx_queue.lock, flags); ++ if (skb_queue_len(&driver->tx_queue) >= NVIDIA_IROT_QUEUE_SIZE) { ++ // error: queue already full ++ status = NETDEV_TX_BUSY; ++ LOG_WRN("mctp tx queue overflow"); ++ netif_stop_queue(dev); ++ } else { ++ // push the packet ++ status = NETDEV_TX_OK; ++ __skb_queue_tail(&driver->tx_queue, skb); ++ if (skb_queue_len(&driver->tx_queue) == ++ NVIDIA_IROT_QUEUE_SIZE) { ++ // stop the queue to prevent excessive RAM usage ++ netif_stop_queue(dev); ++ } ++ } ++ spin_unlock_irqrestore(&driver->tx_queue.lock, flags); ++ ++ if (status == NETDEV_TX_OK) { ++ wake_up(&driver->tx_queue_ready_wq); ++ } ++ return status; ++} ++ ++int nvidia_irot_open(struct net_device *dev) ++{ ++ netif_start_queue(dev); ++ return 0; ++} ++ ++int nvidia_irot_stop(struct net_device *dev) ++{ ++ netif_stop_queue(dev); ++ return 0; ++} ++ ++static struct net_device_ops nvidia_irot_nops = { ++ .ndo_start_xmit = nvidia_irot_start_xmit, ++ .ndo_open = nvidia_irot_open, ++ .ndo_stop = nvidia_irot_stop, ++}; ++ ++void nvidia_irot_netdev_setup(struct net_device *dev) ++{ ++ dev->type = ARPHRD_MCTP; ++ ++ // NOTE: will be replaced outside of setup ++ dev->min_mtu = 68; ++ dev->max_mtu = 68; ++ dev->mtu = 68; ++ ++ dev->hard_header_len = 0; ++ dev->tx_queue_len = NVIDIA_IROT_QUEUE_SIZE; ++ dev->netdev_ops = &nvidia_irot_nops; ++ ++ dev->addr_len = 0; ++} ++ ++static int nvidia_irot_create_mctp_dev(struct nvidia_irot_driver *driver, ++ const char *name) ++{ ++ driver->netdev = alloc_netdev(sizeof(struct nvidia_irot_netdev_priv), ++ name, NET_NAME_PREDICTABLE, ++ nvidia_irot_netdev_setup); ++ if (!driver->netdev) { ++ return -ENOMEM; ++ } ++ ++ spin_lock_irq(&driver->shared_spin); ++ const u32 mtu = driver->shared_state.shmem.mtu_limit; ++ spin_unlock_irq(&driver->shared_spin); ++ if (mtu >= 68) { ++ driver->netdev->min_mtu = mtu; ++ driver->netdev->max_mtu = mtu; ++ driver->netdev->mtu = mtu; ++ } ++ ++ struct nvidia_irot_netdev_priv *priv = netdev_priv(driver->netdev); ++ priv->driver = driver; ++ ++ const int ret = mctp_register_netdev(driver->netdev, NULL, MCTP_PHYS_BINDING_VENDOR); ++ if (ret) { ++ free_netdev(driver->netdev); ++ driver->netdev = NULL; ++ return ret; ++ } ++ return 0; ++} ++ ++/// @brief Gets the driver managing a mailbox client. ++/// @details There are two methods to identify the mailbox client: ++/// container_of, and dev_get_drvdata(client->dev). ++/// This function does both and ensures they match. ++/// @retval NULL On error. ++static struct nvidia_irot_driver * ++nvidia_irot_driver_of_mbox(struct mbox_client *client) ++{ ++ if (client == NULL) { ++ LOG_ERR("null client input to %s", __func__); ++ return NULL; ++ } ++ ++ void *const driver_from_container_of = ++ container_of(client, struct nvidia_irot_driver, mbox_client); ++ ++ if (client->dev == NULL) { ++ LOG_ERR("null client->dev input to %s", __func__); ++ return NULL; ++ } ++ void *const driver_from_devdata = dev_get_drvdata(client->dev); ++ ++ if (driver_from_container_of != driver_from_devdata) { ++ LOG_ERR("container_of and dev_get_drvdata(client->dev) disagree in %s", ++ __func__); ++ return NULL; ++ } ++ return (struct nvidia_irot_driver *)driver_from_devdata; ++} ++ ++static void wake_rx_worker(struct nvidia_irot_driver *driver) ++{ ++ atomic_xchg(&driver->rx_work_ready, 1); ++ wake_up(&driver->rx_worker_wq); ++} ++ ++/// @brief Handles a duplicate command by scheduling a response ++/// without scheduling command handling. ++/// @details Performs no IO and does not suspend as is ++/// called from a callback that does not allow this. ++/// @pre driver->shared_spin is locked by the caller. ++/// @param driver The driver handling the mailbox. ++/// @param command The duplicate command. ++static void ++locked_handle_duplicate_rx_command(struct nvidia_irot_driver *driver, ++ const struct nvidia_irot_message *command) ++{ ++ struct nvidia_irot_message *staged_response = ++ &driver->shared_state.duplicate_response; ++ ++ if (staged_response->command != 0) { ++ // already have a duplicate response schedule ++ return; ++ } ++ if (command->command != nvidia_irot_cc_mctp) { ++ // currently this is the only command that needs duplication logic. ++ return; ++ } ++ ++ // build the response ++ struct nvidia_irot_message response = NVIDIA_IROT_MESSAGE_INIT; ++ response.command = nvidia_irot_cc_mctp_done; ++ response.data.value = command->data.mctp.counter; ++ ++ *staged_response = response; ++ ++ // wake the worker to handle the response ++ wake_rx_worker(driver); ++} ++ ++/// @brief Handles a read message. ++/// @details Performs no IO and does not suspend as is ++/// called from a callback that does not allow this. ++/// @pre driver->shared_spin is locked by the caller. ++/// @details Called from a mailbox callback so must not block. ++/// @param driver The driver managing the mailbox. ++/// @param message The read message. ++static void locked_handle_rx_message(struct nvidia_irot_driver *driver, ++ const struct nvidia_irot_message *message) ++{ ++ switch (message->command) { ++ case nvidia_irot_cc_ping: ++ // ping: stage and wake the worker ++ // NOTE: don't need duplicate command checking as ping command handling does nothing. ++ driver->shared_state.rx_ping = *message; ++ wake_rx_worker(driver); ++ return; ++ ++ case nvidia_irot_cc_mctp_buffer: ++ // got buffer addresses, check for duplicate ++ if (driver->shared_state.shmem.a35_read.size != 0) { ++ return; ++ } ++ if (driver->shared_state.shmem.a35_write.size != 0) { ++ return; ++ } ++ // is not a duplicate, inform the startup code ++ driver->shared_state.shmem = message->data.buffers; ++ up(&driver->probe_sem); ++ return; ++ ++ case nvidia_irot_cc_mctp: ++ // new mctp packet ++ if (driver->shared_state.pending_read.command != 0) { ++ // read already pending, drop ++ return; ++ } ++ if (driver->mailbox_state.has_prev_rx_counter && ++ driver->mailbox_state.prev_rx_counter == ++ message->data.mctp.counter) { ++ // duplicate command, send response with no processing ++ locked_handle_duplicate_rx_command(driver, message); ++ return; ++ } ++ // stage the packet and wake a reader ++ driver->shared_state.pending_read = *message; ++ driver->mailbox_state.has_prev_rx_counter = true; ++ driver->mailbox_state.prev_rx_counter = ++ message->data.mctp.counter; ++ wake_rx_worker(driver); ++ return; ++ ++ case nvidia_irot_cc_mctp_done: ++ // tx buffer emptied ++ if (driver->shared_state.pending_write.command == 0) { ++ // no write was pending, ignore ++ return; ++ } ++ if (driver->shared_state.pending_write.data.mctp.counter != ++ message->data.value) { ++ // message counter missmatch, ignore ++ return; ++ } ++ // unstage the pending write and wake writers ++ driver->shared_state.pending_write.command = 0; ++ atomic_xchg(&driver->tx_shmem_ready, 1); ++ wake_up(&driver->tx_shmem_ready_wq); ++ return; ++ ++ default: ++ // unexpected message type, ignore ++ return; ++ } ++} ++ ++/// @brief Callback for when we recive a notification from the IRoT that ++/// shared memory has been updated. ++/// @param client The mailbox client handling notification receptions. ++/// @param data The 32-byte message sent over IPC. ++static void nvidia_irot_on_notification(struct mbox_client *client, void *data) ++{ ++ struct nvidia_irot_driver *const driver = ++ nvidia_irot_driver_of_mbox(client); ++ if (!driver) { ++ return; ++ } ++ struct nvidia_irot_message message; ++ memcpy(&message, data, sizeof(message)); ++ unsigned long flags; ++ spin_lock_irqsave(&driver->shared_spin, flags); ++ locked_handle_rx_message(driver, &message); ++ spin_unlock_irqrestore(&driver->shared_spin, flags); ++} ++ ++static int nvidia_irot_load_mailbox(struct device *dev, ++ struct nvidia_irot_driver *driver) ++{ ++ struct mbox_chan *chan = NULL; ++ struct mbox_client *client = &driver->mbox_client; ++ ++ // bind mailbox to this device ++ client->dev = dev; ++ ++ // bind notification RX callback ++ client->rx_callback = nvidia_irot_on_notification; ++ ++ // on tx wait for the IRoT to clear the notification ++ client->tx_done = NULL; ++ client->tx_block = true; ++ client->tx_tout = 250; ++ ++ // Request mailbox channel ++ chan = mbox_request_channel_byname(client, "irot"); ++ if (IS_ERR(chan)) { ++ dev_err(dev, "Failed to request mailbox channel\n"); ++ return PTR_ERR(chan); ++ } ++ ++ // Store mailbox info in driver ++ driver->mbox_chan = chan; ++ ++ dev_info(dev, "Mailbox channel configured successfully\n"); ++ ++ return 0; ++} ++ ++/// @brief Handles reading a message from shared memory. ++/// @returns The length of the read message on success. ++/// @returns A negative status code on failure. ++static ssize_t worker_locked_handle_read( ++ struct nvidia_irot_driver *driver, ++ struct nvidia_irot_driver_shared_state *shared_state_copy) ++{ ++ ssize_t ret = -EINVAL; ++ ++ if (shared_state_copy->pending_read.command != nvidia_irot_cc_mctp || ++ shared_state_copy->pending_read.data.mctp.packet.size == 0) { ++ return -EINVAL; ++ } ++ ++ const size_t length = ++ shared_state_copy->pending_read.data.mctp.packet.size; ++ const ssize_t offset = get_start_offset( ++ shared_state_copy->shmem.a35_read, ++ shared_state_copy->pending_read.data.mctp.packet); ++ if (offset < 0) { ++ // TODO: Should we clear the pending transaction if it is invalid? ++ // This is kind of a fatal error currently. ++ LOG_ERR("bad read from shared memory"); ++ return -EINVAL; ++ } ++ ++ struct sk_buff *skb; ++ skb = netdev_alloc_skb(driver->netdev, length); ++ if (!skb) { ++ LOG_ERR("failed to allocate skb for read packet"); ++ return -ENOMEM; ++ } ++ skb->protocol = htons(ETH_P_MCTP); ++ ret = copy_from_shmem(skb, ++ shared_state_copy->pending_read.data.mctp.packet, ++ driver->worker_state.rx_addr + offset); ++ if (ret != 0) { ++ LOG_ERR("failed to copy read packet from shared memory"); ++ kfree_skb(skb); ++ return -EINVAL; ++ } ++ skb_reset_mac_header(skb); ++ skb_reset_network_header(skb); ++ ++ struct mctp_skb_cb *const cb = __mctp_cb(skb); ++ cb->halen = 0; ++ ++#if NVIDIA_IROT_MCTP_LOG_ENABLED ++ { ++ int n = skb->len < NVIDIA_IROT_MCTP_LOG_BYTES ? ++ skb->len : ++ NVIDIA_IROT_MCTP_LOG_BYTES; ++ u8 data[NVIDIA_IROT_MCTP_LOG_BYTES] = { 0 }; ++ skb_copy_bits(skb, 0, data, n); ++ char hexbuf[3 * NVIDIA_IROT_MCTP_LOG_BYTES + 1] = { 0 }; ++ int i; ++ for (i = 0; i < n; ++i) { ++ snprintf(hexbuf + i * 3, 4, "%02x ", data[i]); ++ } ++ if (n > 0) ++ hexbuf[3 * n - 1] = '\0'; // Remove trailing space ++ LOG_INF("I->L: %s", hexbuf); ++ } ++#endif ++ ++ ret = netif_receive_skb(skb); ++ if (ret != NET_RX_SUCCESS) { ++ LOG_ERR("netif_rx failed with %d", (int)ret); ++ // NOTE: skb seems to still be freed by a failed netif_receive_skb. ++ return -EINVAL; ++ } ++ return length; ++} ++ ++static int nvidia_irot_rx_worker(void *data) ++{ ++ struct nvidia_irot_driver *const driver = data; ++ ++ for (;;) { ++ wait_event_interruptible( ++ driver->rx_worker_wq, ++ kthread_should_stop() || ++ atomic_read(&driver->rx_work_ready)); ++ if (kthread_should_stop()) { ++ return 0; ++ } ++ atomic_xchg(&driver->rx_work_ready, 0); ++ struct nvidia_irot_driver_shared_state state; ++ mutex_lock(&driver->worker_mutex); ++ worker_locked_handle_shared_state(driver, &state); ++ const int ret = worker_locked_handle_read(driver, &state); ++ (void)finish_read_and_unlock(driver, ret, &state.pending_read); ++ } ++ ++ // unreachable ++ BUG(); ++} ++ ++static ssize_t worker_locked_handle_write( ++ struct nvidia_irot_driver *driver, ++ const struct nvidia_irot_driver_shared_state *shared_state_copy, ++ struct sk_buff *skb) ++{ ++ int ret = -EINVAL; ++ const size_t count = skb->len; ++ if (count == 0) { ++ // empty packets make no sense ++ return -EINVAL; ++ } ++ ++ if (count > shared_state_copy->shmem.mtu_limit) { ++ LOG_ERR("attempted to write mctp packet of length %zu to irot with mtu limit of %u", ++ count, shared_state_copy->shmem.mtu_limit); ++ return -EINVAL; ++ } ++ ++ // write to shmem ++ ret = copy_to_shmem(shared_state_copy->shmem.a35_write, ++ driver->worker_state.tx_addr, skb); ++ if (ret < 0) { ++ LOG_ERR("copy_to_shmem failed"); ++ return ret; ++ } ++ ++ // build the IPC message ++ struct nvidia_irot_message message = NVIDIA_IROT_MESSAGE_INIT; ++ message.command = nvidia_irot_cc_mctp; ++ message.data.mctp.counter = driver->worker_state.next_tx_counter++; ++ message.data.mctp.packet.address = ++ shared_state_copy->shmem.a35_write.address; ++ message.data.mctp.packet.size = count; ++ ++ // stage the pending write ++ spin_lock_irq(&driver->shared_spin); ++ driver->shared_state.pending_write = message; ++ spin_unlock_irq(&driver->shared_spin); ++ ++ // perform the write ++ ret = send_message(driver, &message); ++ if (ret < 0) { ++ // send failed, clear the pending write ++ spin_lock_irq(&driver->shared_spin); ++ driver->shared_state.pending_write.command = 0; ++ spin_unlock_irq(&driver->shared_spin); ++ return ret; ++ } ++ ++ // successfully sent, mailbox callback will clear pending_write ++ return count; ++} ++ ++static int nvidia_irot_tx_worker(void *data) ++{ ++ struct nvidia_irot_driver *const driver = data; ++ struct sk_buff *skb = NULL; ++ ++ for (;;) { ++ // get a packet to send ++ while (!skb) { ++ wait_event_interruptible( ++ driver->tx_queue_ready_wq, ++ !skb_queue_empty(&driver->tx_queue) || ++ kthread_should_stop()); ++ if (kthread_should_stop()) { ++ return 0; ++ } ++ ++ skb = skb_dequeue(&driver->tx_queue); ++ netif_start_queue(driver->netdev); ++ if (skb) { ++ break; ++ } ++ } ++ // we now have a packet to write ++ ++ // wait for shared memory to be ready for a written packet ++ struct nvidia_irot_driver_shared_state shared_state_copy; ++ for (;;) { ++ wait_event_interruptible( ++ driver->tx_shmem_ready_wq, ++ kthread_should_stop() || ++ atomic_read(&driver->tx_shmem_ready)); ++ if (kthread_should_stop()) { ++ kfree_skb(skb); ++ return 0; ++ } ++ atomic_xchg(&driver->tx_shmem_ready, 0); ++ ++ mutex_lock(&driver->worker_mutex); ++ worker_locked_handle_shared_state(driver, ++ &shared_state_copy); ++ if (driver->worker_state.tx_addr != NULL && ++ shared_state_copy.pending_write.command == 0) { ++ // shmem is init and no write is pending, allow the write. ++ // NOTE: pending_write is only cleared by mailbox callback, ++ // so since we hold worker_mutex and know that pending_write is ++ // clear we have exclusive write access. ++ break; ++ } ++ mutex_unlock(&driver->worker_mutex); ++ ++ // loop to reload and recheck state after notification ++ } ++ // We now hold the worker_mutex and tx buffer ownership. ++ ++ // send the packet ++ const ssize_t status = worker_locked_handle_write( ++ driver, &shared_state_copy, skb); ++ mutex_unlock(&driver->worker_mutex); ++ if (status < 0) { ++ // TODO: could attempt to send again, but have never seen this error ++ LOG_ERR("failed to send MCTP packet, status: %d", ++ (int)status); ++ } ++ kfree_skb(skb); ++ skb = NULL; ++ } ++ ++ // unreachable ++ BUG(); ++} ++ ++/// @brief Inits shared memory by requesting the IRoT to send buffer ++/// information and waiting for its response. ++static int nvidia_irot_init_shmem(struct nvidia_irot_driver *driver) ++{ ++ // NOTE: worker_mutex does not need to be held by ++ // this function as no workers are started yet. ++ ++ struct nvidia_irot_driver_shared_state shared_state_copy; ++ shared_state_copy.shmem.mtu_limit = 0; ++ ++ int i; ++ for (i = 0; i < 3; ++i) { ++ // request memory buffers from the IRoT ++ struct nvidia_irot_message message = NVIDIA_IROT_MESSAGE_INIT; ++ message.command = nvidia_irot_cc_get_mctp_buffers; ++ const int ret = send_message(driver, &message); ++ if (ret < 0) { ++ LOG_ERR("failed to send startup IPC message, ret: %d", ++ ret); ++ return ret; ++ } ++ ++ // wait for the response ++ const u64 end_jiffies64 = get_jiffies_64() + (5 * HZ); ++ while (time_is_after_jiffies64(end_jiffies64)) { ++ (void)down_timeout(&driver->probe_sem, HZ); ++ worker_locked_handle_shared_state(driver, ++ &shared_state_copy); ++ if (shared_state_copy.shmem.mtu_limit != 0) { ++ // success, got shared memory info ++ break; ++ } ++ } ++ ++ if (shared_state_copy.shmem.mtu_limit == 0) { ++ LOG_WRN("timed out waiting for IRoT buffer information"); ++ } else { ++ break; ++ } ++ } ++ ++ if (shared_state_copy.shmem.mtu_limit == 0) { ++ LOG_ERR("Timeout waiting for IRoT to provide shared memory buffers"); ++ return -ETIMEDOUT; ++ } ++ return 0; ++} ++ ++/// @brief Performs cleanup for our custom driver bound to the given platform_device. ++/// @param pdev The device having its driver cleaned up. ++/// @note The driver can be in any stage of initialization including completely unbound. ++static void clean_driver(struct platform_device *pdev) ++{ ++ if (pdev == NULL) { ++ return; ++ } ++ ++ struct nvidia_irot_driver *const driver = ++ (struct nvidia_irot_driver *)platform_get_drvdata(pdev); ++ if (driver == NULL) { ++ return; ++ } ++ ++ // remove worker threads ++ if (driver->rx_worker) { ++ if (0 != kthread_stop(driver->rx_worker)) { ++ LOG_ERR("failed to stop rx worker thread"); ++ } ++ driver->rx_worker = NULL; ++ } ++ if (driver->tx_worker) { ++ if (0 != kthread_stop(driver->tx_worker)) { ++ LOG_ERR("failed to stop tx worker thread"); ++ } ++ driver->tx_worker = NULL; ++ } ++ ++ // remove network device ++ if (driver->netdev) { ++ // Stop the queue to prevent new packets ++ netif_tx_disable(driver->netdev); ++ ++ // Purge any remaining packets in the queue ++ skb_queue_purge(&driver->tx_queue); ++ ++ // Explicitly bring the device down if it's still running ++ rtnl_lock(); ++ if (netif_running(driver->netdev)) { ++ dev_close(driver->netdev); ++ } ++ rtnl_unlock(); ++ ++ // Unregister the netdev - this should handle final cleanup ++ mctp_unregister_netdev(driver->netdev); ++ free_netdev(driver->netdev); ++ driver->netdev = NULL; ++ } ++ ++ // free shared memory ++ // NOTE: workers are stopped so can use worker_state freely ++ if (driver->worker_state.rx_addr) { ++ memunmap((void *)driver->worker_state.rx_addr); ++ driver->worker_state.rx_addr = NULL; ++ } ++ if (driver->worker_state.tx_addr) { ++ memunmap(driver->worker_state.tx_addr); ++ driver->worker_state.tx_addr = NULL; ++ } ++ ++ // release mailbox ++ if (driver->mbox_chan) { ++ mbox_free_channel(driver->mbox_chan); ++ driver->mbox_chan = NULL; ++ } ++ ++ // the driver is always heap allocated and we have exclusive ownership of it, free it. ++ platform_set_drvdata(pdev, NULL); ++ kfree(driver); ++}; ++ ++static int nvidia_irot_probe(struct platform_device *pdev) ++{ ++ struct device *const dev = &pdev->dev; ++ dev_info(dev, "Probe starting for device: %s\n", dev_name(dev)); ++ ++ int ret = -EINVAL; ++ struct nvidia_irot_driver *const driver = ++ kzalloc(sizeof(struct nvidia_irot_driver), GFP_KERNEL); ++ if (!driver) { ++ dev_err(dev, "Failed to allocate memory for driver\n"); ++ return -ENOMEM; ++ } ++ ++ platform_set_drvdata(pdev, driver); ++ spin_lock_init(&driver->shared_spin); ++ mutex_init(&driver->worker_mutex); ++ sema_init(&driver->probe_sem, 0); ++ init_waitqueue_head(&driver->rx_worker_wq); ++ atomic_set(&driver->rx_work_ready, 0); ++ init_waitqueue_head(&driver->tx_queue_ready_wq); ++ init_waitqueue_head(&driver->tx_shmem_ready_wq); ++ skb_queue_head_init(&driver->tx_queue); ++ ++ ret = nvidia_irot_load_mailbox(dev, driver); ++ if (ret != 0) { ++ dev_err(dev, "Failed to load mailbox.\n"); ++ clean_driver(pdev); ++ return ret; ++ } ++ ++ ret = nvidia_irot_init_shmem(driver); ++ if (ret < 0) { ++ dev_err(dev, "failed to establish shared memory, ret: %d\n", ++ ret); ++ clean_driver(pdev); ++ return ret; ++ } ++ ++ // Signal that shared memory is ready for TX operations ++ atomic_set(&driver->tx_shmem_ready, 1); ++ wake_up(&driver->tx_shmem_ready_wq); ++ ++ ret = nvidia_irot_create_mctp_dev(driver, dev_name(dev)); ++ if (ret < 0) { ++ dev_err(dev, "failed to create mctp device, ret: %d\n", ret); ++ clean_driver(pdev); ++ return ret; ++ } ++ driver->rx_worker = kthread_run(nvidia_irot_rx_worker, driver, ++ "%s-rx-worker", dev_name(dev)); ++ if (IS_ERR(driver->rx_worker)) { ++ dev_err(dev, "Failed to create rx worker thread.\n"); ++ driver->rx_worker = NULL; ++ clean_driver(pdev); ++ return -EINVAL; ++ } ++ driver->tx_worker = kthread_run(nvidia_irot_tx_worker, driver, ++ "%s-tx-worker", dev_name(dev)); ++ if (IS_ERR(driver->tx_worker)) { ++ dev_err(dev, "Failed to create tx worker thread.\n"); ++ driver->tx_worker = NULL; ++ clean_driver(pdev); ++ return -EINVAL; ++ } ++ ++ dev_info(dev, "Probe successful for device: %s\n", dev_name(dev)); ++ return 0; ++} ++ ++static void nvidia_irot_remove(struct platform_device *pdev) ++{ ++ dev_info(&pdev->dev, "Removing device: %s\n", dev_name(&pdev->dev)); ++ clean_driver(pdev); ++ dev_info(&pdev->dev, "Device removed: %s\n", dev_name(&pdev->dev)); ++} ++ ++static const struct of_device_id nvidia_irot_match[] = { ++ { .compatible = "nvidia,ast27xx,irot" }, ++ {}, ++}; ++MODULE_DEVICE_TABLE(of, nvidia_irot_match); ++ ++static struct platform_driver nvidia_irot_driver = { ++ .probe = nvidia_irot_probe, ++ .remove = nvidia_irot_remove, ++ .driver = ++ { ++ .name = "nvidia-ast27xx-irot", ++ .of_match_table = nvidia_irot_match, ++ }, ++}; ++ ++module_platform_driver(nvidia_irot_driver); ++ ++MODULE_LICENSE("GPL"); ++MODULE_DESCRIPTION("NVIDIA IRoT MCTP Driver for ASPEED AST27xx BMC"); +-- +2.34.1 + diff --git a/patches-sonic/0003-mctp-driver-net-Add-global-APIs-for-MCTP.patch b/patches-sonic/0003-mctp-driver-net-Add-global-APIs-for-MCTP.patch new file mode 100644 index 000000000..d59cc248f --- /dev/null +++ b/patches-sonic/0003-mctp-driver-net-Add-global-APIs-for-MCTP.patch @@ -0,0 +1,45 @@ +From 053a09ab106bbeeefbd733dcf0892e4f40587e1c Mon Sep 17 00:00:00 2001 +From: Vadim Pasternak +Date: Sun, 10 May 2026 18:29:00 +0300 +Subject: [PATCH 6.12 3/5] mctp: driver: net: Add global APIs for MCTP + +Export the MCTP transport tracepoints (mctp_transport_tx, +mctp_transport_rx and mctp_transport_error) via +EXPORT_TRACEPOINT_SYMBOL_GPL() so that MCTP transport drivers built as +loadable modules can emit them, and add the missing +MODULE_LICENSE("GPL") to the MCTP I2C error-injection module. + +Signed-off-by: Vadim Pasternak +--- + drivers/net/mctp/mctp-i2c-error-inject.c | 2 ++ + net/mctp/af_mctp.c | 4 ++++ + 2 files changed, 6 insertions(+) + +diff --git a/drivers/net/mctp/mctp-i2c-error-inject.c b/drivers/net/mctp/mctp-i2c-error-inject.c +index c10040f87..d06943275 100644 +--- a/drivers/net/mctp/mctp-i2c-error-inject.c ++++ b/drivers/net/mctp/mctp-i2c-error-inject.c +@@ -846,3 +846,5 @@ void mctp_i2c_error_inject_module_exit(void) + mctp_i2c_error_inject_root = NULL; + } + EXPORT_SYMBOL_GPL(mctp_i2c_error_inject_module_exit); ++ ++MODULE_LICENSE("GPL"); +diff --git a/net/mctp/af_mctp.c b/net/mctp/af_mctp.c +index d99c35b1c..f0435f634 100644 +--- a/net/mctp/af_mctp.c ++++ b/net/mctp/af_mctp.c +@@ -20,6 +20,10 @@ + #define CREATE_TRACE_POINTS + #include + ++EXPORT_TRACEPOINT_SYMBOL_GPL(mctp_transport_tx); ++EXPORT_TRACEPOINT_SYMBOL_GPL(mctp_transport_rx); ++EXPORT_TRACEPOINT_SYMBOL_GPL(mctp_transport_error); ++ + #include "mctp-socket-error-inject.h" + + /* socket implementation */ +-- +2.34.1 + diff --git a/patches-sonic/0004-mctp-driver-net-Fix-MCTP-over-VDM-PCIe.patch b/patches-sonic/0004-mctp-driver-net-Fix-MCTP-over-VDM-PCIe.patch new file mode 100644 index 000000000..40a6a8b01 --- /dev/null +++ b/patches-sonic/0004-mctp-driver-net-Fix-MCTP-over-VDM-PCIe.patch @@ -0,0 +1,62 @@ +From ef1be57f590ab3df679fb591f22065561c796c56 Mon Sep 17 00:00:00 2001 +From: Vadim Pasternak +Date: Sun, 10 May 2026 14:37:56 +0300 +Subject: [PATCH 6.12 4/5] mctp: driver: net: Fix MCTP over VDM/PCIe + +Fix two issues in the MCTP over VDM/PCIe transport +(CONFIG_MCTP_TRANSPORT_PCIE_VDM): + +- mctp-pcie-vdm.c was missing MODULE_LICENSE() and MODULE_DESCRIPTION(), + which causes a module taint/warning when the transport is built and + loaded as a module. Add both. +- The header guarded its declarations with + "#ifdef CONFIG_MCTP_TRANSPORT_PCIE_VDM", which does not handle the + option being built as a module (=m). Include and use + IS_ENABLED() so the declarations are visible in both built-in and + module configurations. + +Signed-off-by: Vadim Pasternak +--- + drivers/net/mctp/mctp-pcie-vdm.c | 3 +++ + include/linux/mctp-pcie-vdm.h | 7 ++++--- + 2 files changed, 7 insertions(+), 3 deletions(-) + +diff --git a/drivers/net/mctp/mctp-pcie-vdm.c b/drivers/net/mctp/mctp-pcie-vdm.c +index f715fc085..43f35c46e 100644 +--- a/drivers/net/mctp/mctp-pcie-vdm.c ++++ b/drivers/net/mctp/mctp-pcie-vdm.c +@@ -359,3 +359,6 @@ void mctp_pcie_vdm_remove_dev(struct net_device *vdm_dev) + } + } + EXPORT_SYMBOL_GPL(mctp_pcie_vdm_remove_dev); ++ ++MODULE_LICENSE("GPL"); ++MODULE_DESCRIPTION("MCTP PCIe VDM transport (DSP0238) helpers"); +diff --git a/include/linux/mctp-pcie-vdm.h b/include/linux/mctp-pcie-vdm.h +index 25fac3499..1aee02126 100644 +--- a/include/linux/mctp-pcie-vdm.h ++++ b/include/linux/mctp-pcie-vdm.h +@@ -8,10 +8,11 @@ + #ifndef __LINUX_MCTP_PCIE_VDM_H + #define __LINUX_MCTP_PCIE_VDM_H + ++#include + #include + #include + +-#ifdef CONFIG_MCTP_TRANSPORT_PCIE_VDM ++#if IS_ENABLED(CONFIG_MCTP_TRANSPORT_PCIE_VDM) + + /** + * @send_packet: referenced to send packets with PCIe VDM header packed. +@@ -32,5 +33,5 @@ struct net_device *mctp_pcie_vdm_add_dev(struct device *dev, + void mctp_pcie_vdm_receive_packet(struct net_device *ndev); + void mctp_pcie_vdm_remove_dev(struct net_device *ndev); + +-#endif /* CONFIG_MCTP_TRANSPORT_PCIE_VDM */ +-#endif /* __LINUX_MCTP_PCIE_VDM_H */ ++#endif /* IS_ENABLED(CONFIG_MCTP_TRANSPORT_PCIE_VDM) */ ++#endif /* __LINUX_MCTP_PCIE_VDM_H */ +-- +2.34.1 + diff --git a/patches-sonic/0005-mctp-driver-net-Fix-MCTP-over-USB.patch b/patches-sonic/0005-mctp-driver-net-Fix-MCTP-over-USB.patch new file mode 100644 index 000000000..994bdae92 --- /dev/null +++ b/patches-sonic/0005-mctp-driver-net-Fix-MCTP-over-USB.patch @@ -0,0 +1,882 @@ +From 7d8b2029f67b9f0d082b5310f5e73e540863c1e5 Mon Sep 17 00:00:00 2001 +From: Vadim Pasternak +Date: Sun, 10 May 2026 18:58:36 +0300 +Subject: [PATCH 6.12 5/5] mctp: driver: net: Fix MCTP over USB + +Fix and extend the MCTP over USB transport (CONFIG_MCTP_TRANSPORT_USB). + +This reworks the USB TX error statistics so that errors which cannot be +attributed to a single destination EID (for example batched transfers) +are accounted under a dedicated unknown-EID bucket rather than a +specific EID, removes a stale Makefile reference to a USB +error-injection object, and adds a USB gadget function (f_mctp) so the +device side can expose an MCTP-over-USB interface. + +Signed-off-by: Vadim Pasternak +--- + drivers/net/mctp/Makefile | 1 - + drivers/net/mctp/mctp-usb.c | 43 +- + drivers/usb/gadget/function/Makefile | 2 + + drivers/usb/gadget/function/f_mctp.c | 714 +++++++++++++++++++++++++++ + include/uapi/linux/usb/ch9.h | 1 + + 5 files changed, 740 insertions(+), 21 deletions(-) + create mode 100644 drivers/usb/gadget/function/f_mctp.c + +diff --git a/drivers/net/mctp/Makefile b/drivers/net/mctp/Makefile +index 996808975..5a740413e 100644 +--- a/drivers/net/mctp/Makefile ++++ b/drivers/net/mctp/Makefile +@@ -6,6 +6,5 @@ obj-$(CONFIG_NVIDIA_AST27XX_IROT) += nvidia-ast27xx-irot.o + obj-$(CONFIG_MCTP_TRANSPORT_SPI) += mctp-spi.o + obj-$(CONFIG_MCTP_TRANSPORT_SPI) += mctp-spi-error-inject.o + obj-$(CONFIG_MCTP_TRANSPORT_USB) += mctp-usb.o +-obj-$(CONFIG_MCTP_TRANSPORT_USB) += mctp-usb-error-inject.o + obj-$(CONFIG_MCTP_TRANSPORT_PCIE_VDM) += mctp-pcie-vdm.o + obj-$(CONFIG_NVIDIA_OPTEE_VROT) += nvidia-optee-vrot.o +diff --git a/drivers/net/mctp/mctp-usb.c b/drivers/net/mctp/mctp-usb.c +index b9bca7067..1217d7e29 100644 +--- a/drivers/net/mctp/mctp-usb.c ++++ b/drivers/net/mctp/mctp-usb.c +@@ -138,67 +138,67 @@ static void mctp_usb_handle_tx_urb_status(struct mctp_usb *mctp_usb, + /** + * mctp_usb_handle_tx_sync_error - Handle synchronous TX error statistics + * @mctp_usb: MCTP USB device +- * @dest_eid: Destination EID from MCTP header ++ * @stat_eid: EID for stats (0-255) or MCTP_EID_UNKNOWN (256) for batch/unknown + * @error_code: Error code from URB submission + * + * Maps synchronous TX errors (URB submission failures) to per-EID statistics. +- * Unlike async URB completion errors, these can be attributed to a specific +- * destination EID since the packet header is still available. ++ * For single-packet TX, use the MCTP destination EID from the header. ++ * For batched TX, pass MCTP_EID_UNKNOWN when no per-packet EID applies. + * Only handles valid USB error codes per USB subsystem specification. + */ + static void mctp_usb_handle_tx_sync_error(struct mctp_usb *mctp_usb, +- u8 dest_eid, ++ unsigned int stat_eid, + int error_code) + { + switch (error_code) { + /* Valid USB error codes only */ + case -ENOMEM: +- MCTP_STAT_INC(mctp_usb, dest_eid, tx_drop_no_memory); ++ MCTP_STAT_INC(mctp_usb, stat_eid, tx_drop_no_memory); + break; + case -EBUSY: +- MCTP_STAT_INC(mctp_usb, dest_eid, tx_drop_ebusy); ++ MCTP_STAT_INC(mctp_usb, stat_eid, tx_drop_ebusy); + break; + case -ENODEV: +- MCTP_STAT_INC(mctp_usb, dest_eid, tx_drop_enodev); ++ MCTP_STAT_INC(mctp_usb, stat_eid, tx_drop_enodev); + break; + case -ENOENT: +- MCTP_STAT_INC(mctp_usb, dest_eid, tx_drop_enoent); ++ MCTP_STAT_INC(mctp_usb, stat_eid, tx_drop_enoent); + break; + case -ENXIO: +- MCTP_STAT_INC(mctp_usb, dest_eid, tx_drop_enxio); ++ MCTP_STAT_INC(mctp_usb, stat_eid, tx_drop_enxio); + break; + case -EINVAL: +- MCTP_STAT_INC(mctp_usb, dest_eid, tx_drop_einval); ++ MCTP_STAT_INC(mctp_usb, stat_eid, tx_drop_einval); + break; + case -EXDEV: +- MCTP_STAT_INC(mctp_usb, dest_eid, tx_drop_exdev); ++ MCTP_STAT_INC(mctp_usb, stat_eid, tx_drop_exdev); + break; + case -EFBIG: +- MCTP_STAT_INC(mctp_usb, dest_eid, tx_drop_efbig); ++ MCTP_STAT_INC(mctp_usb, stat_eid, tx_drop_efbig); + break; + case -EPIPE: +- MCTP_STAT_INC(mctp_usb, dest_eid, tx_drop_epipe); ++ MCTP_STAT_INC(mctp_usb, stat_eid, tx_drop_epipe); + break; + case -EMSGSIZE: +- MCTP_STAT_INC(mctp_usb, dest_eid, tx_drop_emsgsize); ++ MCTP_STAT_INC(mctp_usb, stat_eid, tx_drop_emsgsize); + break; + case -ENOSPC: +- MCTP_STAT_INC(mctp_usb, dest_eid, tx_drop_enospc); ++ MCTP_STAT_INC(mctp_usb, stat_eid, tx_drop_enospc); + break; + case -ESHUTDOWN: +- MCTP_STAT_INC(mctp_usb, dest_eid, tx_drop_eshutdown); ++ MCTP_STAT_INC(mctp_usb, stat_eid, tx_drop_eshutdown); + break; + case -EPERM: +- MCTP_STAT_INC(mctp_usb, dest_eid, tx_drop_eperm); ++ MCTP_STAT_INC(mctp_usb, stat_eid, tx_drop_eperm); + break; + case -EHOSTUNREACH: +- MCTP_STAT_INC(mctp_usb, dest_eid, tx_drop_ehostunreach); ++ MCTP_STAT_INC(mctp_usb, stat_eid, tx_drop_ehostunreach); + break; + case -ENOEXEC: +- MCTP_STAT_INC(mctp_usb, dest_eid, tx_drop_enoexec); ++ MCTP_STAT_INC(mctp_usb, stat_eid, tx_drop_enoexec); + break; + default: +- MCTP_STAT_INC(mctp_usb, dest_eid, tx_drop_urb_error); ++ MCTP_STAT_INC(mctp_usb, stat_eid, tx_drop_urb_error); + break; + } + } +@@ -1277,6 +1277,9 @@ static struct usb_driver mctp_usb_driver = { + .disconnect = mctp_usb_disconnect, + }; + ++/* One .ko when CONFIG_MCTP_TRANSPORT_USB=m: error-inject is not a separate module. */ ++#include "mctp-usb-error-inject.c" ++ + static int __init mctp_usb_init(void) + { + int rc; +diff --git a/drivers/usb/gadget/function/Makefile b/drivers/usb/gadget/function/Makefile +index 87917a7d4..036a94e0a 100644 +--- a/drivers/usb/gadget/function/Makefile ++++ b/drivers/usb/gadget/function/Makefile +@@ -52,3 +52,5 @@ usb_f_printer-y := f_printer.o + obj-$(CONFIG_USB_F_PRINTER) += usb_f_printer.o + usb_f_tcm-y := f_tcm.o + obj-$(CONFIG_USB_F_TCM) += usb_f_tcm.o ++usb_f_mctp-y := f_mctp.o ++obj-$(CONFIG_USB_F_MCTP) += usb_f_mctp.o +diff --git a/drivers/usb/gadget/function/f_mctp.c b/drivers/usb/gadget/function/f_mctp.c +new file mode 100644 +index 000000000..065f00b87 +--- /dev/null ++++ b/drivers/usb/gadget/function/f_mctp.c +@@ -0,0 +1,714 @@ ++// SPDX-License-Identifier: GPL-2.0+ ++/* ++ * f_mctp.c - USB peripheral MCTP driver ++ * ++ * Copyright (C) 2024 Code Construct Pty Ltd ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include ++#include ++ ++#include ++#include ++ ++#include ++ ++#define MCTP_USB_PREALLOC 4 ++ ++struct f_mctp { ++ struct usb_function function; ++ ++ struct usb_ep *in_ep; ++ struct usb_ep *out_ep; ++ ++ struct net_device *dev; ++ ++ /* Updates to skb_free_list and the req lists are performed under ++ * ->lock ++ */ ++ spinlock_t lock; ++ struct sk_buff_head skb_free_list; ++ struct list_head rx_reqs; ++ struct list_head tx_reqs; ++ ++ struct work_struct prealloc_work; ++}; ++ ++struct f_mctp_opts { ++ struct usb_function_instance function_instance; ++}; ++ ++static inline struct f_mctp *func_to_mctp(struct usb_function *f) ++{ ++ return container_of(f, struct f_mctp, function); ++} ++ ++static struct usb_interface_descriptor mctp_usbg_intf = { ++ .bLength = sizeof(mctp_usbg_intf), ++ .bDescriptorType = USB_DT_INTERFACE, ++ ++ .bNumEndpoints = 2, ++ .bInterfaceClass = USB_CLASS_MCTP, ++ .bInterfaceSubClass = 0x0, /* todo: allow host-interface mode? */ ++ .bInterfaceProtocol = 0x1, /* MCTP version 1 */ ++ /* .iInterface = DYNAMIC */ ++}; ++ ++/* descriptors, full speed only */ ++ ++static struct usb_endpoint_descriptor hs_mctp_source_desc = { ++ .bLength = USB_DT_ENDPOINT_SIZE, ++ .bDescriptorType = USB_DT_ENDPOINT, ++ .wMaxPacketSize = cpu_to_le16(MCTP_USB_XFER_SIZE), ++ ++ .bEndpointAddress = USB_DIR_IN, ++ .bmAttributes = USB_ENDPOINT_XFER_BULK, ++}; ++ ++static struct usb_endpoint_descriptor hs_mctp_sink_desc = { ++ .bLength = USB_DT_ENDPOINT_SIZE, ++ .bDescriptorType = USB_DT_ENDPOINT, ++ .wMaxPacketSize = cpu_to_le16(MCTP_USB_XFER_SIZE), ++ ++ .bEndpointAddress = USB_DIR_OUT, ++ .bmAttributes = USB_ENDPOINT_XFER_BULK, ++}; ++ ++static struct usb_descriptor_header *hs_mctp_descs[] = { ++ (struct usb_descriptor_header *) &mctp_usbg_intf, ++ (struct usb_descriptor_header *) &hs_mctp_sink_desc, ++ (struct usb_descriptor_header *) &hs_mctp_source_desc, ++ NULL, ++}; ++ ++/* strings */ ++static struct usb_string mctp_usbg_strings[] = { ++ { .s = "MCTP over USB" }, ++ { 0 } ++}; ++ ++static struct usb_gadget_strings mctp_usbg_stringtab = { ++ .language = 0x0409, /* en-us */ ++ .strings = mctp_usbg_strings, ++}; ++ ++static struct usb_gadget_strings *mctp_usbg_gadget_strings[] = { ++ &mctp_usbg_stringtab, ++ NULL, ++}; ++ ++static int mctp_usbg_bind(struct usb_configuration *c, struct usb_function *f) ++{ ++ struct usb_composite_dev *cdev = c->cdev; ++ struct f_mctp *mctp = func_to_mctp(f); ++ int id, rc; ++ ++ id = usb_interface_id(c, f); ++ if (id < 0) ++ return id; ++ mctp_usbg_intf.bInterfaceNumber = id; ++ ++ id = usb_string_id(cdev); ++ if (id < 0) ++ return id; ++ ++ mctp_usbg_strings[0].id = id; ++ ++ mctp->in_ep = usb_ep_autoconfig(cdev->gadget, &hs_mctp_source_desc); ++ if (!mctp->in_ep) { ++ ERROR(cdev, "%s in_ep autoconfig failed\n", f->name); ++ return -ENODEV; ++ } ++ hs_mctp_source_desc.wMaxPacketSize = cpu_to_le16(MCTP_USB_XFER_SIZE); ++ ++ mctp->out_ep = usb_ep_autoconfig(cdev->gadget, &hs_mctp_sink_desc); ++ if (!mctp->out_ep) { ++ ERROR(cdev, "%s out_ep autoconfig failed\n", f->name); ++ return -ENODEV; ++ } ++ hs_mctp_sink_desc.wMaxPacketSize = cpu_to_le16(MCTP_USB_XFER_SIZE); ++ ++ rc = usb_assign_descriptors(f, NULL, hs_mctp_descs, NULL, NULL); ++ if (rc) { ++ ERROR(cdev, "assign_descriptors failed %d\n", rc); ++ return rc; ++ } ++ ++ DBG(cdev, "%s: in %s, out %s\n", f->name, mctp->in_ep->name, ++ mctp->out_ep->name); ++ ++ return 0; ++} ++ ++static void mctp_usbg_prealloc(struct f_mctp *mctp) ++{ ++ struct sk_buff_head skbs; ++ struct usb_request *req; ++ struct list_head reqs; ++ struct sk_buff *skb; ++ unsigned long flags; ++ unsigned int n_skb; ++ ++ /* allocate SKBs for requests that have been consumed and don't ++ * have an associated skb, then requeue. ++ */ ++ spin_lock_irqsave(&mctp->lock, flags); ++ list_replace_init(&mctp->rx_reqs, &reqs); ++ spin_unlock_irqrestore(&mctp->lock, flags); ++ ++ while (!list_empty(&reqs)) { ++ req = list_first_entry(&reqs, struct usb_request, list); ++ list_del(&req->list); ++ ++ skb = __netdev_alloc_skb(mctp->dev, MCTP_USB_XFER_SIZE, ++ GFP_KERNEL); ++ if (!skb) { ++ /* Put it back for next time */ ++ spin_lock_irqsave(&mctp->lock, flags); ++ list_add(&req->list, &mctp->rx_reqs); ++ spin_unlock_irqrestore(&mctp->lock, flags); ++ break; ++ } ++ ++ req->buf = skb->data; ++ req->context = skb; ++ usb_ep_queue(mctp->out_ep, req, GFP_KERNEL); ++ } ++ ++ /* next, allocate our pool of spare skbs */ ++ n_skb = skb_queue_len_lockless(&mctp->skb_free_list); ++ __skb_queue_head_init(&skbs); ++ ++ for (; n_skb < MCTP_USB_PREALLOC; n_skb++) { ++ skb = __netdev_alloc_skb(mctp->dev, MCTP_USB_XFER_SIZE, ++ GFP_KERNEL); ++ if (!skb) ++ break; ++ ++ __skb_queue_tail(&skbs, skb); ++ } ++ ++ spin_lock_irqsave(&mctp->lock, flags); ++ skb_queue_splice_tail(&skbs, &mctp->skb_free_list); ++ spin_unlock_irqrestore(&mctp->lock, flags); ++} ++ ++static void mctp_usbg_prealloc_work(struct work_struct *work) ++{ ++ struct f_mctp *mctp = container_of(work, struct f_mctp, prealloc_work); ++ ++ mctp_usbg_prealloc(mctp); ++} ++ ++static int mctp_usbg_requeue(struct f_mctp *mctp, struct usb_ep *ep, ++ struct usb_request *req) ++{ ++ unsigned long flags; ++ struct sk_buff *skb; ++ int rc = 0; ++ ++ req->buf = NULL; ++ ++ spin_lock_irqsave(&mctp->lock, flags); ++ ++ /* Do we have a preallocated skb available? if so, we can requeue ++ * immediately; otherwise wait for the workqueue to populate. ++ */ ++ skb = __skb_dequeue(&mctp->skb_free_list); ++ if (skb) { ++ req->buf = skb->data; ++ req->context = skb; ++ rc = usb_ep_queue(ep, req, GFP_ATOMIC); ++ } else { ++ /* keep for later allocation */ ++ list_add_tail(&req->list, &mctp->rx_reqs); ++ } ++ ++ spin_unlock_irqrestore(&mctp->lock, flags); ++ ++ schedule_work(&mctp->prealloc_work); ++ ++ return rc; ++} ++ ++static void mctp_usbg_handle_rx_urb(struct f_mctp *mctp, ++ struct usb_request *req) ++{ ++ struct device *dev = &mctp->function.config->cdev->gadget->dev; ++ struct net_device *netdev = mctp->dev; ++ struct sk_buff *skb = req->context; ++ struct mctp_usb_hdr *hdr; ++ struct mctp_skb_cb *cb; ++ unsigned int len; ++ u16 id; ++ ++ len = req->actual; ++ __skb_put(skb, len); ++ ++ hdr = skb_pull_data(skb, sizeof(*hdr)); ++ if (!hdr) ++ goto err; ++ ++ id = be16_to_cpu(hdr->id); ++ if (id != MCTP_USB_DMTF_ID) { ++ dev_dbg(dev, "%s: invalid id %04x\n", __func__, id); ++ goto err; ++ } ++ ++ if (hdr->len < sizeof(struct mctp_hdr) + sizeof(struct mctp_usb_hdr)) { ++ dev_dbg(dev, "%s: short packet (hdr) %d\n", ++ __func__, hdr->len); ++ goto err; ++ } ++ ++ /* todo: multi-packet transfers */ ++ if (hdr->len - sizeof(struct mctp_usb_hdr) < skb->len) { ++ dev_dbg(dev, "%s: short packet (xfer) %d, actual %d\n", ++ __func__, hdr->len, skb->len); ++ goto err; ++ } ++ ++ skb->protocol = htons(ETH_P_MCTP); ++ skb_reset_network_header(skb); ++ cb = __mctp_cb(skb); ++ cb->halen = 0; ++ netdev->stats.rx_packets++; ++ netdev->stats.rx_bytes += skb->len; ++ netif_rx(skb); ++ ++ return; ++ ++err: ++ /* todo: return to free list */ ++ kfree_skb(skb); ++} ++static void mctp_usbg_out_ep_complete(struct usb_ep *ep, ++ struct usb_request *req) ++{ ++ struct f_mctp *mctp = ep->driver_data; ++ struct usb_composite_dev *cdev = mctp->function.config->cdev; ++ int rc; ++ ++ switch (req->status) { ++ case 0: ++ mctp_usbg_handle_rx_urb(mctp, req); ++ ++ /* re-queue out request */ ++ rc = mctp_usbg_requeue(mctp, ep, req); ++ if (rc) { ++ WARNING(cdev, "%s: unable to re-queue out req\n", ++ __func__); ++ usb_ep_free_request(ep, req); ++ } ++ ++ break; ++ ++ case -ECONNABORTED: ++ case -ECONNRESET: ++ case -ESHUTDOWN: ++ kfree_skb(req->context); ++ usb_ep_free_request(ep, req); ++ break; ++ ++ default: ++ WARNING(cdev, "%s: invalid status %d?", __func__, req->status); ++ usb_ep_free_request(ep, req); ++ } ++} ++ ++static void mctp_usbg_in_ep_complete(struct usb_ep *ep, ++ struct usb_request *req) ++{ ++ struct f_mctp *mctp = ep->driver_data; ++ struct usb_composite_dev *cdev = mctp->function.config->cdev; ++ struct net_device *netdev = mctp->dev; ++ struct sk_buff *skb = req->context; ++ unsigned long flags; ++ ++ kfree_skb(skb); ++ req->context = NULL; ++ req->buf = NULL; ++ ++ /* todo: tx stats */ ++ ++ switch (req->status) { ++ case 0: ++ netdev->stats.tx_bytes += skb->len; ++ netdev->stats.tx_packets++; ++ spin_lock_irqsave(&mctp->lock, flags); ++ if (list_empty(&mctp->tx_reqs)) ++ netif_wake_queue(mctp->dev); ++ list_add(&req->list, &mctp->tx_reqs); ++ spin_unlock_irqrestore(&mctp->lock, flags); ++ break; ++ default: ++ WARNING(cdev, "%s: invalid status %d?", __func__, req->status); ++ fallthrough; ++ case -ECONNABORTED: ++ case -ECONNRESET: ++ case -ESHUTDOWN: ++ netdev->stats.tx_dropped++; ++ usb_ep_free_request(ep, req); ++ break; ++ } ++} ++ ++static int mctp_usbg_enable_ep(struct usb_gadget *gadget, struct f_mctp *mctp, ++ struct usb_ep *ep) ++{ ++ int rc; ++ ++ rc = config_ep_by_speed(gadget, &mctp->function, ep); ++ if (rc) ++ return rc; ++ ++ rc = usb_ep_enable(ep); ++ if (rc) ++ return rc; ++ ++ ep->driver_data = mctp; ++ ++ return 0; ++} ++ ++static int mctp_usbg_enable(struct usb_composite_dev *cdev, struct f_mctp *mctp) ++{ ++ struct usb_request *out_req, *in_req; ++ unsigned long flags; ++ struct sk_buff *skb; ++ int rc; ++ ++ rc = mctp_usbg_enable_ep(cdev->gadget, mctp, mctp->out_ep); ++ if (rc) { ++ ERROR(cdev, "%s: out ep enable failed %d\n", __func__, rc); ++ return rc; ++ } ++ ++ rc = mctp_usbg_enable_ep(cdev->gadget, mctp, mctp->in_ep); ++ if (rc) { ++ ERROR(cdev, "%s: in ep enable failed %d\n", __func__, rc); ++ goto err_disable_out; ++ } ++ ++ /* todo: just one out queued req for now */ ++ out_req = alloc_ep_req(mctp->out_ep, MCTP_USB_XFER_SIZE); ++ if (!out_req) { ++ ERROR(cdev, "%s: out req alloc failed\n", __func__); ++ goto err_disable_in; ++ } ++ ++ spin_lock_irqsave(&mctp->lock, flags); ++ skb = __skb_dequeue(&mctp->skb_free_list); ++ spin_unlock_irqrestore(&mctp->lock, flags); ++ ++ if (!skb) ++ skb = netdev_alloc_skb(mctp->dev, MCTP_USB_XFER_SIZE); ++ ++ if (!skb) ++ goto err_free_req; ++ ++ out_req->context = skb; ++ out_req->buf = skb->data; ++ out_req->complete = mctp_usbg_out_ep_complete; ++ ++ rc = usb_ep_queue(mctp->out_ep, out_req, GFP_ATOMIC); ++ if (rc) { ++ ERROR(cdev, "%s: out req queue failed %d\b", __func__, rc); ++ goto err_free_skb; ++ } ++ ++ /* todo: and just one in the in queue too */ ++ in_req = usb_ep_alloc_request(mctp->in_ep, GFP_ATOMIC); ++ if (!in_req) { ++ ERROR(cdev, "%s: out req alloc failed\n", __func__); ++ goto err_disable_in; ++ } ++ in_req->complete = mctp_usbg_in_ep_complete; ++ ++ spin_lock_irqsave(&mctp->lock, flags); ++ list_add(&in_req->list, &mctp->tx_reqs); ++ spin_unlock_irqrestore(&mctp->lock, flags); ++ ++ netif_carrier_on(mctp->dev); ++ netif_wake_queue(mctp->dev); ++ dev_info(&cdev->gadget->dev, "mctp-usb: enabled\n"); ++ ++ return 0; ++ ++ ++err_free_skb: ++ kfree_skb(skb); ++err_free_req: ++ free_ep_req(mctp->out_ep, out_req); ++err_disable_in: ++ usb_ep_disable(mctp->in_ep); ++err_disable_out: ++ usb_ep_disable(mctp->out_ep); ++ ++ return rc; ++} ++ ++static netdev_tx_t mctp_usbg_start_xmit(struct sk_buff *skb, ++ struct net_device *dev) ++{ ++ struct f_mctp *mctp = netdev_priv(dev); ++ struct net_device *netdev = mctp->dev; ++ struct mctp_usb_hdr *hdr; ++ struct usb_request *req; ++ unsigned long flags; ++ unsigned int plen; ++ int rc; ++ ++ if (skb->len + sizeof(*hdr) > MCTP_USB_XFER_SIZE) ++ goto drop; ++ ++ spin_lock_irqsave(&mctp->lock, flags); ++ req = list_first_entry_or_null(&mctp->tx_reqs, struct usb_request, list); ++ ++ if (req) ++ list_del(&req->list); ++ if (list_empty(&mctp->tx_reqs)) ++ netif_stop_queue(dev); ++ ++ spin_unlock_irqrestore(&mctp->lock, flags); ++ ++ if (!req) { ++ netdev_warn(dev, "no tx reqs available\n"); ++ goto drop; ++ } ++ ++ plen = skb->len; ++ hdr = skb_push(skb, sizeof(*hdr)); ++ hdr->id = cpu_to_be16(MCTP_USB_DMTF_ID); ++ hdr->rsvd = 0; ++ hdr->len = plen + sizeof(*hdr); ++ ++ /* todo: just one skb per transfer.. */ ++ req->context = skb; ++ req->buf = skb->data; ++ req->length = skb->len; ++ ++ rc = usb_ep_queue(mctp->in_ep, req, GFP_ATOMIC); ++ if (rc) { ++ netdev_warn(dev, "tx queue failed: %d\n", rc); ++ req->context = NULL; ++ req->buf = NULL; ++ spin_lock_irqsave(&mctp->lock, flags); ++ list_add(&req->list, &mctp->tx_reqs); ++ netif_wake_queue(dev); ++ spin_unlock_irqrestore(&mctp->lock, flags); ++ goto drop; ++ } ++ ++ netdev->stats.tx_bytes += skb->len; ++ netdev->stats.tx_packets++; ++ ++ return NETDEV_TX_OK; ++ ++drop: ++ kfree_skb(skb); ++ netdev->stats.tx_dropped++; ++ return NETDEV_TX_OK; ++} ++ ++static int mctp_usbg_open(struct net_device *net) ++{ ++ return 0; ++} ++ ++static int mctp_usbg_stop(struct net_device *net) ++{ ++ return 0; ++} ++ ++static const struct net_device_ops mctp_usbg_netdev_ops = { ++ .ndo_open = mctp_usbg_open, ++ .ndo_stop = mctp_usbg_stop, ++ .ndo_start_xmit = mctp_usbg_start_xmit, ++}; ++ ++static void __mctp_usbg_disable(struct f_mctp *mctp) ++{ ++ usb_ep_disable(mctp->in_ep); ++ usb_ep_disable(mctp->out_ep); ++} ++ ++static void mctp_usbg_purge_tx_reqs(struct f_mctp *mctp) ++{ ++ LIST_HEAD(reqs); ++ struct usb_request *req, *tmp; ++ unsigned long flags; ++ ++ spin_lock_irqsave(&mctp->lock, flags); ++ list_splice_init(&mctp->tx_reqs, &reqs); ++ spin_unlock_irqrestore(&mctp->lock, flags); ++ ++ list_for_each_entry_safe(req, tmp, &reqs, list) { ++ list_del(&req->list); ++ usb_ep_free_request(mctp->in_ep, req); ++ } ++} ++ ++static void mctp_usbg_disable(struct usb_function *f) ++{ ++ struct f_mctp *mctp = func_to_mctp(f); ++ struct usb_composite_dev *cdev = mctp->function.config->cdev; ++ ++ __mctp_usbg_disable(mctp); ++ ++ cancel_work_sync(&mctp->prealloc_work); ++ mctp_usbg_purge_tx_reqs(mctp); ++ ++ netif_stop_queue(mctp->dev); ++ netif_carrier_off(mctp->dev); ++ dev_info(&cdev->gadget->dev, "mctp-usb: disabled\n"); ++} ++ ++static int mctp_usbg_set_alt(struct usb_function *f, ++ unsigned intf, unsigned alt) ++{ ++ struct usb_composite_dev *cdev = f->config->cdev; ++ struct f_mctp *mctp = func_to_mctp(f); ++ ++ __mctp_usbg_disable(mctp); ++ return mctp_usbg_enable(cdev, mctp); ++} ++ ++static void mctp_usbg_free_func(struct usb_function *f) ++{ ++ struct f_mctp *mctp = func_to_mctp(f); ++ ++ /* Cancel pending work before cleanup */ ++ cancel_work_sync(&mctp->prealloc_work); ++ ++ /* Free any pending SKBs in the queues */ ++ __skb_queue_purge(&mctp->skb_free_list); ++ ++ /* The netdev (and f_mctp) will be freed automatically ++ * by the network core since needs_free_netdev = true. ++ */ ++ unregister_netdev(mctp->dev); ++} ++ ++static void mctp_usbg_netdev_setup(struct net_device *dev) ++{ ++ dev->type = ARPHRD_MCTP; ++ ++ dev->mtu = MCTP_USB_MTU_MIN; ++ dev->min_mtu = MCTP_USB_MTU_MIN; ++ dev->max_mtu = MCTP_USB_MTU_MAX; ++ ++ dev->hard_header_len = 0; ++ dev->addr_len = 0; ++ dev->tx_queue_len = DEFAULT_TX_QUEUE_LEN; ++ dev->flags = IFF_NOARP; ++ dev->netdev_ops = &mctp_usbg_netdev_ops; ++ dev->needs_free_netdev = true; ++} ++ ++static struct usb_function ++*mctp_usbg_alloc_func(struct usb_function_instance *fi) ++{ ++ struct f_mctp_opts *opts; ++ struct net_device *dev; ++ struct f_mctp *mctp; ++ int rc; ++ ++ opts = container_of(fi, struct f_mctp_opts, function_instance); ++ ++ dev = alloc_netdev(sizeof(*mctp), "mctpusbg%d", NET_NAME_ENUM, ++ mctp_usbg_netdev_setup); ++ if (!dev) ++ return ERR_PTR(-ENOMEM); ++ ++ mctp = netdev_priv(dev); ++ mctp->dev = dev; ++ ++ spin_lock_init(&mctp->lock); ++ INIT_LIST_HEAD(&mctp->rx_reqs); ++ INIT_LIST_HEAD(&mctp->tx_reqs); ++ __skb_queue_head_init(&mctp->skb_free_list); ++ INIT_WORK(&mctp->prealloc_work, mctp_usbg_prealloc_work); ++ ++ mctp->function.name = "mctp"; ++ mctp->function.bind = mctp_usbg_bind; ++ mctp->function.set_alt = mctp_usbg_set_alt; ++ mctp->function.disable = mctp_usbg_disable; ++ mctp->function.strings = mctp_usbg_gadget_strings; ++ mctp->function.free_func = mctp_usbg_free_func; ++ ++ /* this will allocate our first pool of out (rx) skbs */ ++ mctp_usbg_prealloc(mctp); ++ ++ rc = register_netdev(dev); ++ if (rc) { ++ free_netdev(dev); ++ return ERR_PTR(rc); ++ } ++ ++ return &mctp->function; ++} ++ ++static struct f_mctp_opts *to_f_mctp_opts(struct config_item *item) ++{ ++ return container_of(to_config_group(item), struct f_mctp_opts, ++ function_instance.group); ++} ++ ++static void mctp_usbg_attr_release(struct config_item *item) ++{ ++ struct f_mctp_opts *opts = to_f_mctp_opts(item); ++ ++ usb_put_function_instance(&opts->function_instance); ++} ++ ++static struct configfs_item_operations mctp_usbg_item_ops = { ++ .release = mctp_usbg_attr_release, ++}; ++ ++static struct configfs_attribute *mctp_usbg_attrs[] = { ++ NULL, ++}; ++ ++static const struct config_item_type mctp_usbg_func_type = { ++ .ct_item_ops = &mctp_usbg_item_ops, ++ .ct_attrs = mctp_usbg_attrs, ++ .ct_owner = THIS_MODULE, ++}; ++ ++static void mctp_usbg_free_instance(struct usb_function_instance *fi) ++{ ++ struct f_mctp_opts *opts; ++ ++ opts = container_of(fi, struct f_mctp_opts, function_instance); ++ kfree(opts); ++} ++ ++static struct usb_function_instance *mctp_usbg_alloc_instance(void) ++{ ++ struct f_mctp_opts *opts; ++ ++ opts = kzalloc(sizeof(*opts), GFP_KERNEL); ++ if (!opts) ++ return ERR_PTR(-ENOMEM); ++ ++ opts->function_instance.free_func_inst = mctp_usbg_free_instance; ++ ++ config_group_init_type_name(&opts->function_instance.group, "", ++ &mctp_usbg_func_type); ++ ++ return &opts->function_instance; ++} ++ ++DECLARE_USB_FUNCTION_INIT(mctp, mctp_usbg_alloc_instance, mctp_usbg_alloc_func); ++MODULE_LICENSE("GPL"); +diff --git a/include/uapi/linux/usb/ch9.h b/include/uapi/linux/usb/ch9.h +index 91f0f7e21..052290652 100644 +--- a/include/uapi/linux/usb/ch9.h ++++ b/include/uapi/linux/usb/ch9.h +@@ -330,6 +330,7 @@ struct usb_device_descriptor { + #define USB_CLASS_AUDIO_VIDEO 0x10 + #define USB_CLASS_BILLBOARD 0x11 + #define USB_CLASS_USB_TYPE_C_BRIDGE 0x12 ++#define USB_CLASS_MCTP 0x14 + #define USB_CLASS_MISC 0xef + #define USB_CLASS_APP_SPEC 0xfe + #define USB_SUBCLASS_DFU 0x01 +-- +2.34.1 + diff --git a/patches-sonic/0006-jtag-Add-JTAG-core-headers-GPIO-mux-and-locking-support.patch b/patches-sonic/0006-jtag-Add-JTAG-core-headers-GPIO-mux-and-locking-support.patch new file mode 100644 index 000000000..776c080a9 --- /dev/null +++ b/patches-sonic/0006-jtag-Add-JTAG-core-headers-GPIO-mux-and-locking-support.patch @@ -0,0 +1,1657 @@ +From: SONiC JTAG patch +Subject: [PATCH] jtag: Add JTAG core headers, GPIO mux, and locking support + +Add the JTAG core framework headers (include/linux/jtag.h and +include/uapi/linux/jtag.h) that define the kernel-internal and +userspace-visible JTAG interfaces. + +Add GPIO mux control to the Aspeed JTAG driver so a board-level +GPIO can gate JTAG bus ownership on enable/disable. + +Add single-open locking to the core JTAG character device so only one +userspace client can hold the device at a time. + +Add the jtag-aspeed-internal driver for Aspeed SoC internal JTAG +controller support. + +Signed-off-by: SONiC JTAG patch +--- +diff --git a/include/linux/jtag.h b/include/linux/jtag.h +new file mode 100644 +--- /dev/null ++++ b/include/linux/jtag.h +@@ -0,0 +1,49 @@ ++/* SPDX-License-Identifier: GPL-2.0 */ ++/* Copyright (c) 2018 Mellanox Technologies. All rights reserved. */ ++/* Copyright (c) 2018 Oleksandr Shamray */ ++/* Copyright (c) 2019 Intel Corporation */ ++ ++#ifndef __LINUX_JTAG_H ++#define __LINUX_JTAG_H ++ ++#include ++#include ++ ++#define JTAG_MAX_XFER_DATA_LEN (0xFFFFFFFF) ++ ++struct jtag; ++/** ++ * struct jtag_ops - callbacks for JTAG control functions: ++ * ++ * @freq_get: get frequency function. Filled by dev driver ++ * @freq_set: set frequency function. Filled by dev driver ++ * @status_get: get JTAG TAPC state function. Mandatory, Filled by dev driver ++ * @status_set: set JTAG TAPC state function. Mandatory, Filled by dev driver ++ * @xfer: send JTAG xfer function. Mandatory func. Filled by dev driver ++ * @mode_set: set specific work mode for JTAG. Filled by dev driver ++ * @trst_set: set TRST pin active(pull low) for JTAG. Filled by dev driver ++ * @bitbang: set low level bitbang operations. Filled by dev driver ++ * @enable: enables JTAG interface in controller mode. Filled by dev driver ++ * @disable: disables JTAG interface controller mode. Filled by dev driver ++ */ ++struct jtag_ops { ++ int (*freq_get)(struct jtag *jtag, u32 *freq); ++ int (*freq_set)(struct jtag *jtag, u32 freq); ++ int (*status_get)(struct jtag *jtag, u32 *state); ++ int (*status_set)(struct jtag *jtag, struct jtag_tap_state *endst); ++ int (*xfer)(struct jtag *jtag, struct jtag_xfer *xfer, u8 *xfer_data); ++ int (*mode_set)(struct jtag *jtag, struct jtag_mode *jtag_mode); ++ int (*trst_set)(struct jtag *jtag, u32 active); ++ int (*bitbang)(struct jtag *jtag, struct bitbang_packet *bitbang, ++ struct tck_bitbang *bitbang_data); ++ int (*enable)(struct jtag *jtag); ++ int (*disable)(struct jtag *jtag); ++}; ++ ++void *jtag_priv(struct jtag *jtag); ++int devm_jtag_register(struct device *dev, struct jtag *jtag); ++struct jtag *jtag_alloc(struct device *host, size_t priv_size, ++ const struct jtag_ops *ops); ++void jtag_free(struct jtag *jtag); ++ ++#endif /* __LINUX_JTAG_H */ + +diff --git a/include/uapi/linux/jtag.h b/include/uapi/linux/jtag.h +new file mode 100644 +--- /dev/null ++++ b/include/uapi/linux/jtag.h +@@ -0,0 +1,378 @@ ++/* SPDX-License-Identifier: GPL-2.0 */ ++/* Copyright (c) 2018 Mellanox Technologies. All rights reserved. */ ++/* Copyright (c) 2018 Oleksandr Shamray */ ++/* Copyright (c) 2019 Intel Corporation */ ++ ++#ifndef __UAPI_LINUX_JTAG_H ++#define __UAPI_LINUX_JTAG_H ++ ++#include ++#include ++ ++/* ++ * JTAG_XFER_MODE: JTAG transfer mode. Used to set JTAG controller transfer mode ++ * This is bitmask for feature param in jtag_mode for ioctl JTAG_SIOCMODE ++ */ ++#define JTAG_XFER_MODE 0 ++/* ++ * JTAG_CONTROL_MODE: JTAG controller mode. Used to set JTAG controller mode ++ * This is bitmask for feature param in jtag_mode for ioctl JTAG_SIOCMODE ++ */ ++#define JTAG_CONTROL_MODE 1 ++/* ++ * JTAG_TCK_CYCLE_DELAY_COUNT: JTAG delay counter for aspeed_jtag_tck_cycle. Used ++ * set the number of jtag_tck_cycle delays repetitions. ++ * This is bitmask for feature param in jtag_mode for ioctl JTAG_SIOCMODE ++ */ ++#define JTAG_TCK_CYCLE_DELAY_COUNT 2 ++/* ++ * JTAG_CONTROLLER_OUTPUT_DISABLE: JTAG controller mode output disable, it is ++ * used to enable other devices to own the JTAG bus. ++ * This is bitmask for mode param in jtag_mode for ioctl JTAG_SIOCMODE ++ */ ++#define JTAG_CONTROLLER_OUTPUT_DISABLE 0 ++/* ++ * JTAG_CONTROLLER_MODE: JTAG controller mode. Used to set JTAG controller in ++ * host mode. ++ * This is bitmask for mode param in jtag_mode for ioctl JTAG_SIOCMODE ++ */ ++#define JTAG_CONTROLLER_MODE 1 ++/* ++ * JTAG_XFER_HW_MODE: JTAG hardware mode. Used to set HW drived or bitbang ++ * mode. This is bitmask for mode param in jtag_mode for ioctl JTAG_SIOCMODE ++ */ ++#define JTAG_XFER_HW_MODE 1 ++/* ++ * JTAG_XFER_SW_MODE: JTAG software mode. Used to set SW drived or bitbang ++ * mode. This is bitmask for mode param in jtag_mode for ioctl JTAG_SIOCMODE ++ */ ++#define JTAG_XFER_SW_MODE 0 ++ ++/** ++ * enum jtag_tapstate: ++ * ++ * @JTAG_STATE_TLRESET: JTAG state machine Test Logic Reset state ++ * @JTAG_STATE_IDLE: JTAG state machine IDLE state ++ * @JTAG_STATE_SELECTDR: JTAG state machine SELECT_DR state ++ * @JTAG_STATE_CAPTUREDR: JTAG state machine CAPTURE_DR state ++ * @JTAG_STATE_SHIFTDR: JTAG state machine SHIFT_DR state ++ * @JTAG_STATE_EXIT1DR: JTAG state machine EXIT-1 DR state ++ * @JTAG_STATE_PAUSEDR: JTAG state machine PAUSE_DR state ++ * @JTAG_STATE_EXIT2DR: JTAG state machine EXIT-2 DR state ++ * @JTAG_STATE_UPDATEDR: JTAG state machine UPDATE DR state ++ * @JTAG_STATE_SELECTIR: JTAG state machine SELECT_IR state ++ * @JTAG_STATE_CAPTUREIR: JTAG state machine CAPTURE_IR state ++ * @JTAG_STATE_SHIFTIR: JTAG state machine SHIFT_IR state ++ * @JTAG_STATE_EXIT1IR: JTAG state machine EXIT-1 IR state ++ * @JTAG_STATE_PAUSEIR: JTAG state machine PAUSE_IR state ++ * @JTAG_STATE_EXIT2IR: JTAG state machine EXIT-2 IR state ++ * @JTAG_STATE_UPDATEIR: JTAG state machine UPDATE IR state ++ * @JTAG_STATE_CURRENT: JTAG current state, saved by driver ++ */ ++enum jtag_tapstate { ++ JTAG_STATE_TLRESET, ++ JTAG_STATE_IDLE, ++ JTAG_STATE_SELECTDR, ++ JTAG_STATE_CAPTUREDR, ++ JTAG_STATE_SHIFTDR, ++ JTAG_STATE_EXIT1DR, ++ JTAG_STATE_PAUSEDR, ++ JTAG_STATE_EXIT2DR, ++ JTAG_STATE_UPDATEDR, ++ JTAG_STATE_SELECTIR, ++ JTAG_STATE_CAPTUREIR, ++ JTAG_STATE_SHIFTIR, ++ JTAG_STATE_EXIT1IR, ++ JTAG_STATE_PAUSEIR, ++ JTAG_STATE_EXIT2IR, ++ JTAG_STATE_UPDATEIR, ++ JTAG_STATE_CURRENT ++}; ++ ++/** ++ * enum jtag_reset: ++ * ++ * @JTAG_NO_RESET: JTAG run TAP from current state ++ * @JTAG_FORCE_RESET: JTAG force TAP to reset state ++ */ ++enum jtag_reset { ++ JTAG_NO_RESET = 0, ++ JTAG_FORCE_RESET = 1, ++}; ++ ++/** ++ * enum jtag_xfer_type: ++ * ++ * @JTAG_SIR_XFER: SIR transfer ++ * @JTAG_SDR_XFER: SDR transfer ++ */ ++enum jtag_xfer_type { ++ JTAG_SIR_XFER = 0, ++ JTAG_SDR_XFER = 1, ++}; ++ ++/** ++ * enum jtag_xfer_direction: ++ * ++ * @JTAG_READ_XFER: read transfer ++ * @JTAG_WRITE_XFER: write transfer ++ * @JTAG_READ_WRITE_XFER: read & write transfer ++ */ ++enum jtag_xfer_direction { ++ JTAG_READ_XFER = 1, ++ JTAG_WRITE_XFER = 2, ++ JTAG_READ_WRITE_XFER = 3, ++}; ++ ++/** ++ * struct jtag_tap_state - forces JTAG state machine to go into a TAPC ++ * state ++ * ++ * @reset: 0 - run IDLE/PAUSE from current state ++ * 1 - go through TEST_LOGIC/RESET state before IDLE/PAUSE ++ * @from: initital jtag state ++ * @endstate: jtag end state ++ * @tck: clock counter ++ * ++ * Structure provide interface to JTAG device for JTAG set state execution. ++ */ ++struct jtag_tap_state { ++ __u8 reset; ++ __u8 from; ++ __u8 endstate; ++ __u32 tck; ++}; ++ ++/** ++ * union pad_config - Padding Configuration: ++ * ++ * @type: transfer type ++ * @pre_pad_number: Number of prepadding bits bit[11:0] ++ * @post_pad_number: Number of prepadding bits bit[23:12] ++ * @pad_data : Bit value to be used by pre and post padding bit[24] ++ * @int_value: unsigned int packed padding configuration value bit[32:0] ++ * ++ * Structure provide pre and post padding configuration in a single __u32 ++ */ ++union pad_config { ++ struct { ++ __u32 pre_pad_number : 12; ++ __u32 post_pad_number : 12; ++ __u32 pad_data : 1; ++ __u32 rsvd : 7; ++ }; ++ __u32 int_value; ++}; ++ ++/** ++ * struct jtag_xfer - jtag xfer: ++ * ++ * @type: transfer type ++ * @direction: xfer direction ++ * @from: xfer current state ++ * @endstate: xfer end state ++ * @padding: xfer padding ++ * @length: xfer bits length ++ * @tdio : xfer data array ++ * ++ * Structure provide interface to JTAG device for JTAG SDR/SIR xfer execution. ++ */ ++struct jtag_xfer { ++ __u8 type; ++ __u8 direction; ++ __u8 from; ++ __u8 endstate; ++ __u32 padding; ++ __u32 length; ++ __u64 tdio; ++}; ++ ++/** ++ * struct bitbang_packet - jtag bitbang array packet: ++ * ++ * @data: JTAG Bitbang struct array pointer(input/output) ++ * @length: array size (input) ++ * ++ * Structure provide interface to JTAG device for JTAG bitbang bundle execution ++ */ ++struct bitbang_packet { ++ struct tck_bitbang *data; ++ __u32 length; ++} __attribute__((__packed__)); ++ ++/** ++ * struct jtag_bitbang - jtag bitbang: ++ * ++ * @tms: JTAG TMS ++ * @tdi: JTAG TDI (input) ++ * @tdo: JTAG TDO (output) ++ * ++ * Structure provide interface to JTAG device for JTAG bitbang execution. ++ */ ++struct tck_bitbang { ++ __u8 tms; ++ __u8 tdi; ++ __u8 tdo; ++} __attribute__((__packed__)); ++ ++/** ++ * struct jtag_mode - jtag mode: ++ * ++ * @feature: 0 - JTAG feature setting selector for JTAG controller HW/SW ++ * 1 - JTAG feature setting selector for controller bus mode ++ * output (enable / disable). ++ * @mode: (0 - SW / 1 - HW) for JTAG_XFER_MODE feature(0) ++ * (0 - output disable / 1 - output enable) for JTAG_CONTROL_MODE ++ * feature(1) ++ * ++ * Structure provide configuration modes to JTAG device. ++ */ ++struct jtag_mode { ++ __u32 feature; ++ __u32 mode; ++}; ++ ++/* ioctl interface */ ++#define __JTAG_IOCTL_MAGIC 0xb2 ++ ++#define JTAG_SIOCSTATE _IOW(__JTAG_IOCTL_MAGIC, 0, struct jtag_tap_state) ++#define JTAG_SIOCFREQ _IOW(__JTAG_IOCTL_MAGIC, 1, unsigned int) ++#define JTAG_GIOCFREQ _IOR(__JTAG_IOCTL_MAGIC, 2, unsigned int) ++#define JTAG_IOCXFER _IOWR(__JTAG_IOCTL_MAGIC, 3, struct jtag_xfer) ++#define JTAG_GIOCSTATUS _IOWR(__JTAG_IOCTL_MAGIC, 4, enum jtag_tapstate) ++#define JTAG_SIOCMODE _IOW(__JTAG_IOCTL_MAGIC, 5, unsigned int) ++#define JTAG_IOCBITBANG _IOW(__JTAG_IOCTL_MAGIC, 6, unsigned int) ++#define JTAG_SIOCTRST _IOW(__JTAG_IOCTL_MAGIC, 7, unsigned int) ++ ++/** ++ * struct tms_cycle - This structure represents a tms cycle state. ++ * ++ * @tmsbits: is the bitwise representation of the needed tms transitions to ++ * move from one state to another. ++ * @count: number of jumps needed to move to the needed state. ++ * ++ */ ++struct tms_cycle { ++ unsigned char tmsbits; ++ unsigned char count; ++}; ++ ++/* ++ * This is the complete set TMS cycles for going from any TAP state to any ++ * other TAP state, following a "shortest path" rule. ++ */ ++static const struct tms_cycle _tms_cycle_lookup[][16] = { ++/* TLR RTI SelDR CapDR SDR Ex1DR*/ ++/* TLR */{{0x00, 0}, {0x00, 1}, {0x02, 2}, {0x02, 3}, {0x02, 4}, {0x0a, 4}, ++/* PDR Ex2DR UpdDR SelIR CapIR SIR*/ ++ {0x0a, 5}, {0x2a, 6}, {0x1a, 5}, {0x06, 3}, {0x06, 4}, {0x06, 5}, ++/* Ex1IR PIR Ex2IR UpdIR*/ ++ {0x16, 5}, {0x16, 6}, {0x56, 7}, {0x36, 6} }, ++ ++/* TLR RTI SelDR CapDR SDR Ex1DR*/ ++/* RTI */{{0x07, 3}, {0x00, 0}, {0x01, 1}, {0x01, 2}, {0x01, 3}, {0x05, 3}, ++/* PDR Ex2DR UpdDR SelIR CapIR SIR*/ ++ {0x05, 4}, {0x15, 5}, {0x0d, 4}, {0x03, 2}, {0x03, 3}, {0x03, 4}, ++/* Ex1IR PIR Ex2IR UpdIR*/ ++ {0x0b, 4}, {0x0b, 5}, {0x2b, 6}, {0x1b, 5} }, ++ ++/* TLR RTI SelDR CapDR SDR Ex1DR*/ ++/* SelDR*/{{0x03, 2}, {0x03, 3}, {0x00, 0}, {0x00, 1}, {0x00, 2}, {0x02, 2}, ++/* PDR Ex2DR UpdDR SelIR CapIR SIR*/ ++ {0x02, 3}, {0x0a, 4}, {0x06, 3}, {0x01, 1}, {0x01, 2}, {0x01, 3}, ++/* Ex1IR PIR Ex2IR UpdIR*/ ++ {0x05, 3}, {0x05, 4}, {0x15, 5}, {0x0d, 4} }, ++ ++/* TLR RTI SelDR CapDR SDR Ex1DR*/ ++/* CapDR*/{{0x1f, 5}, {0x03, 3}, {0x07, 3}, {0x00, 0}, {0x00, 1}, {0x01, 1}, ++/* PDR Ex2DR UpdDR SelIR CapIR SIR*/ ++ {0x01, 2}, {0x05, 3}, {0x03, 2}, {0x0f, 4}, {0x0f, 5}, {0x0f, 6}, ++/* Ex1IR PIR Ex2IR UpdIR*/ ++ {0x2f, 6}, {0x2f, 7}, {0xaf, 8}, {0x6f, 7} }, ++ ++/* TLR RTI SelDR CapDR SDR Ex1DR*/ ++/* SDR */{{0x1f, 5}, {0x03, 3}, {0x07, 3}, {0x07, 4}, {0x00, 0}, {0x01, 1}, ++/* PDR Ex2DR UpdDR SelIR CapIR SIR*/ ++ {0x01, 2}, {0x05, 3}, {0x03, 2}, {0x0f, 4}, {0x0f, 5}, {0x0f, 6}, ++/* Ex1IR PIR Ex2IR UpdIR*/ ++ {0x2f, 6}, {0x2f, 7}, {0xaf, 8}, {0x6f, 7} }, ++ ++/* TLR RTI SelDR CapDR SDR Ex1DR*/ ++/* Ex1DR*/{{0x0f, 4}, {0x01, 2}, {0x03, 2}, {0x03, 3}, {0x02, 3}, {0x00, 0}, ++/* PDR Ex2DR UpdDR SelIR CapIR SIR*/ ++ {0x00, 1}, {0x02, 2}, {0x01, 1}, {0x07, 3}, {0x07, 4}, {0x07, 5}, ++/* Ex1IR PIR Ex2IR UpdIR*/ ++ {0x17, 5}, {0x17, 6}, {0x57, 7}, {0x37, 6} }, ++ ++/* TLR RTI SelDR CapDR SDR Ex1DR*/ ++/* PDR */{{0x1f, 5}, {0x03, 3}, {0x07, 3}, {0x07, 4}, {0x01, 2}, {0x05, 3}, ++/* PDR Ex2DR UpdDR SelIR CapIR SIR*/ ++ {0x00, 0}, {0x01, 1}, {0x03, 2}, {0x0f, 4}, {0x0f, 5}, {0x0f, 6}, ++/* Ex1IR PIR Ex2IR UpdIR*/ ++ {0x2f, 6}, {0x2f, 7}, {0xaf, 8}, {0x6f, 7} }, ++ ++/* TLR RTI SelDR CapDR SDR Ex1DR*/ ++/* Ex2DR*/{{0x0f, 4}, {0x01, 2}, {0x03, 2}, {0x03, 3}, {0x00, 1}, {0x02, 2}, ++/* PDR Ex2DR UpdDR SelIR CapIR SIR*/ ++ {0x02, 3}, {0x00, 0}, {0x01, 1}, {0x07, 3}, {0x07, 4}, {0x07, 5}, ++/* Ex1IR PIR Ex2IR UpdIR*/ ++ {0x17, 5}, {0x17, 6}, {0x57, 7}, {0x37, 6} }, ++ ++/* TLR RTI SelDR CapDR SDR Ex1DR*/ ++/* UpdDR*/{{0x07, 3}, {0x00, 1}, {0x01, 1}, {0x01, 2}, {0x01, 3}, {0x05, 3}, ++/* PDR Ex2DR UpdDR SelIR CapIR SIR*/ ++ {0x05, 4}, {0x15, 5}, {0x00, 0}, {0x03, 2}, {0x03, 3}, {0x03, 4}, ++/* Ex1IR PIR Ex2IR UpdIR*/ ++ {0x0b, 4}, {0x0b, 5}, {0x2b, 6}, {0x1b, 5} }, ++ ++/* TLR RTI SelDR CapDR SDR Ex1DR*/ ++/* SelIR*/{{0x01, 1}, {0x01, 2}, {0x05, 3}, {0x05, 4}, {0x05, 5}, {0x15, 5}, ++/* PDR Ex2DR UpdDR SelIR CapIR SIR*/ ++ {0x15, 6}, {0x55, 7}, {0x35, 6}, {0x00, 0}, {0x00, 1}, {0x00, 2}, ++/* Ex1IR PIR Ex2IR UpdIR*/ ++ {0x02, 2}, {0x02, 3}, {0x0a, 4}, {0x06, 3} }, ++ ++/* TLR RTI SelDR CapDR SDR Ex1DR*/ ++/* CapIR*/{{0x1f, 5}, {0x03, 3}, {0x07, 3}, {0x07, 4}, {0x07, 5}, {0x17, 5}, ++/* PDR Ex2DR UpdDR SelIR CapIR SIR*/ ++ {0x17, 6}, {0x57, 7}, {0x37, 6}, {0x0f, 4}, {0x00, 0}, {0x00, 1}, ++/* Ex1IR PIR Ex2IR UpdIR*/ ++ {0x01, 1}, {0x01, 2}, {0x05, 3}, {0x03, 2} }, ++ ++/* TLR RTI SelDR CapDR SDR Ex1DR*/ ++/* SIR */{{0x1f, 5}, {0x03, 3}, {0x07, 3}, {0x07, 4}, {0x07, 5}, {0x17, 5}, ++/* PDR Ex2DR UpdDR SelIR CapIR SIR*/ ++ {0x17, 6}, {0x57, 7}, {0x37, 6}, {0x0f, 4}, {0x0f, 5}, {0x00, 0}, ++/* Ex1IR PIR Ex2IR UpdIR*/ ++ {0x01, 1}, {0x01, 2}, {0x05, 3}, {0x03, 2} }, ++ ++/* TLR RTI SelDR CapDR SDR Ex1DR*/ ++/* Ex1IR*/{{0x0f, 4}, {0x01, 2}, {0x03, 2}, {0x03, 3}, {0x03, 4}, {0x0b, 4}, ++/* PDR Ex2DR UpdDR SelIR CapIR SIR*/ ++ {0x0b, 5}, {0x2b, 6}, {0x1b, 5}, {0x07, 3}, {0x07, 4}, {0x02, 3}, ++/* Ex1IR PIR Ex2IR UpdIR*/ ++ {0x00, 0}, {0x00, 1}, {0x02, 2}, {0x01, 1} }, ++ ++/* TLR RTI SelDR CapDR SDR Ex1DR*/ ++/* PIR */{{0x1f, 5}, {0x03, 3}, {0x07, 3}, {0x07, 4}, {0x07, 5}, {0x17, 5}, ++/* PDR Ex2DR UpdDR SelIR CapIR SIR*/ ++ {0x17, 6}, {0x57, 7}, {0x37, 6}, {0x0f, 4}, {0x0f, 5}, {0x01, 2}, ++/* Ex1IR PIR Ex2IR UpdIR*/ ++ {0x05, 3}, {0x00, 0}, {0x01, 1}, {0x03, 2} }, ++ ++/* TLR RTI SelDR CapDR SDR Ex1DR*/ ++/* Ex2IR*/{{0x0f, 4}, {0x01, 2}, {0x03, 2}, {0x03, 3}, {0x03, 4}, {0x0b, 4}, ++/* PDR Ex2DR UpdDR SelIR CapIR SIR*/ ++ {0x0b, 5}, {0x2b, 6}, {0x1b, 5}, {0x07, 3}, {0x07, 4}, {0x00, 1}, ++/* Ex1IR PIR Ex2IR UpdIR*/ ++ {0x02, 2}, {0x02, 3}, {0x00, 0}, {0x01, 1} }, ++ ++/* TLR RTI SelDR CapDR SDR Ex1DR*/ ++/* UpdIR*/{{0x07, 3}, {0x00, 1}, {0x01, 1}, {0x01, 2}, {0x01, 3}, {0x05, 3}, ++/* PDR Ex2DR UpdDR SelIR CapIR SIR*/ ++ {0x05, 4}, {0x15, 5}, {0x0d, 4}, {0x03, 2}, {0x03, 3}, {0x03, 4}, ++/* Ex1IR PIR Ex2IR UpdIR*/ ++ {0x0b, 4}, {0x0b, 5}, {0x2b, 6}, {0x00, 0} }, ++}; ++ ++#endif /* __UAPI_LINUX_JTAG_H */ + +diff --git a/drivers/jtag/jtag.c b/drivers/jtag/jtag.c +--- a/drivers/jtag/jtag.c ++++ b/drivers/jtag/jtag.c +@@ -23,6 +23,7 @@ + struct miscdevice miscdev; + const struct jtag_ops *ops; + int id; ++ bool locked; + unsigned long *priv; + }; + +@@ -247,17 +248,23 @@ + struct jtag *jtag = container_of(file->private_data, + struct jtag, + miscdev); ++ int ret = 0; + + file->private_data = jtag; +- if (jtag->ops->enable(jtag)) ++ if (jtag->ops->enable(jtag) || jtag->locked) + return -EBUSY; +- return nonseekable_open(inode, file); ++ ++ ret = nonseekable_open(inode, file); ++ if (!ret) ++ jtag->locked = true; ++ return ret; + } + + static int jtag_release(struct inode *inode, struct file *file) + { + struct jtag *jtag = file->private_data; + ++ jtag->locked = false; + if (jtag->ops->disable(jtag)) + return -EBUSY; + return 0; +@@ -333,6 +340,7 @@ + dev_err(jtag->miscdev.parent, "Unable to register device\n"); + goto err_jtag_name; + } ++ jtag->locked = false; + return 0; + + err_jtag_name: + +diff --git a/drivers/jtag/jtag-aspeed.c b/drivers/jtag/jtag-aspeed.c +--- a/drivers/jtag/jtag-aspeed.c ++++ b/drivers/jtag/jtag-aspeed.c +@@ -5,6 +5,7 @@ + + #include + #include ++#include + #include + #include + #include +@@ -199,6 +200,7 @@ + const struct jtag_low_level_functions *llops; + u32 pad_data_one[ASPEED_JTAG_MAX_PAD_SIZE / 32]; + u32 pad_data_zero[ASPEED_JTAG_MAX_PAD_SIZE / 32]; ++ struct gpio_desc *mux_gpiod; + }; + + /* +@@ -1393,6 +1395,9 @@ + { + struct aspeed_jtag *aspeed_jtag = jtag_priv(jtag); + ++ if (!IS_ERR_OR_NULL(aspeed_jtag->mux_gpiod)) ++ gpiod_set_value(aspeed_jtag->mux_gpiod, 1); ++ + aspeed_jtag->llops->master_enable(aspeed_jtag); + return 0; + } +@@ -1401,6 +1406,9 @@ + { + struct aspeed_jtag *aspeed_jtag = jtag_priv(jtag); + ++ if (!IS_ERR_OR_NULL(aspeed_jtag->mux_gpiod)) ++ gpiod_set_value(aspeed_jtag->mux_gpiod, 0); ++ + aspeed_jtag->llops->output_disable(aspeed_jtag); + return 0; + } +@@ -1612,6 +1620,13 @@ + aspeed_jtag = jtag_priv(jtag); + aspeed_jtag->dev = &pdev->dev; + ++ /* Optional GPIO mux control */ ++ aspeed_jtag->mux_gpiod = devm_gpiod_get_index_optional(&pdev->dev, ++ "mux", 0, ++ GPIOD_OUT_LOW); ++ if (IS_ERR(aspeed_jtag->mux_gpiod)) ++ return PTR_ERR(aspeed_jtag->mux_gpiod); ++ + aspeed_jtag->llops = jtag_functions->aspeed_jtag_llops; + + /* Initialize device*/ + +diff --git a/drivers/jtag/jtag-aspeed-internal.c b/drivers/jtag/jtag-aspeed-internal.c +new file mode 100644 +--- /dev/null ++++ b/drivers/jtag/jtag-aspeed-internal.c +@@ -0,0 +1,1095 @@ ++// SPDX-License-Identifier: GPL-2.0-or-later ++/* ++ * JTAG driver for the Aspeed SoC ++ * ++ * Copyright (C) 2021 ASPEED Technology Inc. ++ * Ryan Chen ++ * ++ */ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++/******************************************************************************/ ++#define ASPEED_JTAG_DATA 0x00 ++#define ASPEED_JTAG_INST 0x04 ++#define ASPEED_JTAG_CTRL 0x08 ++#define ASPEED_JTAG_ISR 0x0C ++#define ASPEED_JTAG_SW 0x10 ++#define ASPEED_JTAG_TCK 0x14 ++#define ASPEED_JTAG_IDLE 0x18 ++ ++/* ASPEED_JTAG_CTRL - 0x08 : Engine Control */ ++#define JTAG_ENG_EN BIT(31) ++#define JTAG_ENG_OUT_EN BIT(30) ++#define JTAG_FORCE_TMS BIT(29) ++ ++#define JTAG_IR_UPDATE BIT(26) //AST2500 only ++ ++#define JTAG_G6_RESET_FIFO BIT(21) //AST2600 only ++#define JTAG_G6_CTRL_MODE BIT(20) //AST2600 only ++#define JTAG_G6_XFER_LEN_MASK (0x3ff << 8) //AST2600 only ++#define JTAG_G6_SET_XFER_LEN(x) (x << 8) ++#define JTAG_G6_MSB_FIRST BIT(6) //AST2600 only ++#define JTAG_G6_TERMINATE_XFER BIT(5) //AST2600 only ++#define JTAG_G6_LAST_XFER BIT(4) //AST2600 only ++#define JTAG_G6_INST_EN BIT(1) ++ ++#define JTAG_INST_LEN_MASK (0x3f << 20) ++#define JTAG_SET_INST_LEN(x) (x << 20) ++#define JTAG_SET_INST_MSB BIT(19) ++#define JTAG_TERMINATE_INST BIT(18) ++#define JTAG_LAST_INST BIT(17) ++#define JTAG_INST_EN BIT(16) ++#define JTAG_DATA_LEN_MASK (0x3f << 4) ++ ++#define JTAG_DR_UPDATE BIT(10) //AST2500 only ++#define JTAG_DATA_LEN(x) (x << 4) ++#define JTAG_MSB_FIRST BIT(3) ++#define JTAG_TERMINATE_DATA BIT(2) ++#define JTAG_LAST_DATA BIT(1) ++#define JTAG_DATA_EN BIT(0) ++ ++/* ASPEED_JTAG_ISR - 0x0C : INterrupt status and enable */ ++#define JTAG_INST_PAUSE BIT(19) ++#define JTAG_INST_COMPLETE BIT(18) ++#define JTAG_DATA_PAUSE BIT(17) ++#define JTAG_DATA_COMPLETE BIT(16) ++ ++#define JTAG_INST_PAUSE_EN BIT(3) ++#define JTAG_INST_COMPLETE_EN BIT(2) ++#define JTAG_DATA_PAUSE_EN BIT(1) ++#define JTAG_DATA_COMPLETE_EN BIT(0) ++ ++/* ASPEED_JTAG_SW - 0x10 : Software Mode and Status */ ++#define JTAG_SW_MODE_EN BIT(19) ++#define JTAG_SW_MODE_TCK BIT(18) ++#define JTAG_SW_MODE_TMS BIT(17) ++#define JTAG_SW_MODE_TDIO BIT(16) ++// ++#define JTAG_STS_INST_PAUSE BIT(2) ++#define JTAG_STS_DATA_PAUSE BIT(1) ++#define JTAG_STS_ENG_IDLE (0x1) ++ ++/* ASPEED_JTAG_TCK - 0x14 : TCK Control */ ++#define JTAG_TCK_INVERSE BIT(31) ++#define JTAG_TCK_DIVISOR_MASK (0x7ff) ++#define JTAG_GET_TCK_DIVISOR(x) (x & 0x7ff) ++ ++/* ASPEED_JTAG_IDLE - 0x18 : Ctroller set for go to IDLE */ ++#define JTAG_CTRL_TRSTn_HIGH BIT(31) ++#define JTAG_GO_IDLE BIT(0) ++ ++#define TCK_FREQ 1000000 ++#define ASPEED_JTAG_MAX_PAD_SIZE 1024 ++/******************************************************************************/ ++#define ASPEED_JTAG_DEBUG ++ ++#ifdef ASPEED_JTAG_DEBUG ++#define JTAG_DBUG(fmt, args...) \ ++ pr_debug("%s() " fmt, __func__, ##args) ++#else ++#define JTAG_DBUG(fmt, args...) ++#endif ++ ++static char *end_status_str[] = { "tlr", "idle", "selDR", "capDR", ++ "sDR", "ex1DR", "pDR", "ex2DR", ++ "updDR", "selIR", "capIR", "sIR", ++ "ex1IR", "pIR", "ex2IR", "updIR" }; ++ ++struct aspeed_jtag_config { ++ u8 jtag_version; ++ u32 jtag_buff_len; ++}; ++ ++struct aspeed_jtag_info { ++ void __iomem *reg_base; ++ struct device *dev; ++ struct aspeed_jtag_config *config; ++ enum jtag_tapstate sts; ++ int irq; ++ struct reset_control *reset; ++ struct clk *clk; ++ u32 clkin; ++ u32 tck_period; ++ u32 sw_delay; ++ u32 flag; ++ wait_queue_head_t jtag_wq; ++ u32 mode; ++ u8 pad_data_one[ASPEED_JTAG_MAX_PAD_SIZE]; ++ u8 pad_data_zero[ASPEED_JTAG_MAX_PAD_SIZE]; ++ struct gpio_desc *mux_gpiod; ++}; ++/******************************************************************************/ ++static inline u32 ++aspeed_jtag_read(struct aspeed_jtag_info *aspeed_jtag, u32 reg) ++{ ++ int val; ++ ++ val = readl(aspeed_jtag->reg_base + reg); ++ return val; ++} ++ ++static inline void ++aspeed_jtag_write(struct aspeed_jtag_info *aspeed_jtag, u32 val, u32 reg) ++{ ++ writel(val, aspeed_jtag->reg_base + reg); ++} ++ ++/******************************************************************************/ ++static int aspeed_jtag_set_freq(struct jtag *jtag, u32 freq) ++{ ++ struct aspeed_jtag_info *aspeed_jtag = jtag_priv(jtag); ++ u32 div; ++ ++ /* SW mode frequency setting */ ++ aspeed_jtag->sw_delay = DIV_ROUND_UP(NSEC_PER_SEC, freq); ++ /* ++ * HW mode frequency setting ++ * AST2600: TCK period = Period of HCLK * (JTAG14[10:0] + 1) ++ * AST2500: TCK period = Period of PCLK * (JTAG14[10:0] + 1) * 2 ++ */ ++ if (aspeed_jtag->config->jtag_version == 6) ++ div = DIV_ROUND_UP(aspeed_jtag->clkin, freq) - 1; ++ else ++ div = DIV_ROUND_UP(aspeed_jtag->clkin, freq * 2) - 1; ++ if (div > JTAG_TCK_DIVISOR_MASK) { ++ pr_warn("The actual frequency will faster than required\n"); ++ div = JTAG_TCK_DIVISOR_MASK; ++ } ++ /* ++ * HW constraint: ++ * AST2600 minimal TCK divisor = 7 ++ * AST2500 minimal TCK divisor = 1 ++ */ ++ if (aspeed_jtag->config->jtag_version == 6) { ++ if (div < 7) ++ div = 7; ++ aspeed_jtag->tck_period = DIV_ROUND_UP_ULL( ++ (u64)NSEC_PER_SEC * (div + 1), aspeed_jtag->clkin); ++ } else if (aspeed_jtag->config->jtag_version == 0) { ++ if (div < 1) ++ div = 1; ++ aspeed_jtag->tck_period = DIV_ROUND_UP_ULL( ++ (u64)NSEC_PER_SEC * (div + 1) << 2, aspeed_jtag->clkin); ++ } ++ /* ++ * At ast2500: Change clock divider may cause hardware logic confusion. ++ * Enable software mode to assert the jtag hw logical before change ++ * clock divider. ++ */ ++ if (aspeed_jtag->config->jtag_version == 0) ++ aspeed_jtag_write(aspeed_jtag, ++ JTAG_SW_MODE_EN | ++ aspeed_jtag_read(aspeed_jtag, ++ ASPEED_JTAG_SW), ++ ASPEED_JTAG_SW); ++ aspeed_jtag_write(aspeed_jtag, ++ ((aspeed_jtag_read(aspeed_jtag, ASPEED_JTAG_TCK) & ++ ~JTAG_TCK_DIVISOR_MASK) | ++ div), ++ ASPEED_JTAG_TCK); ++ if (aspeed_jtag->config->jtag_version == 0) { ++ aspeed_jtag_write(aspeed_jtag, 0, ASPEED_JTAG_SW); ++ aspeed_jtag->sts = JTAG_STATE_IDLE; ++ } ++ JTAG_DBUG("Operation freq = %d / %d\n", aspeed_jtag->clkin, div + 1); ++ return 0; ++} ++ ++static int aspeed_jtag_get_freq(struct jtag *jtag, u32 *freq) ++{ ++ struct aspeed_jtag_info *aspeed_jtag = jtag_priv(jtag); ++ ++ if (aspeed_jtag->config->jtag_version == 6) { ++ /* TCK period = Period of HCLK * (JTAG14[10:0] + 1) */ ++ *freq = aspeed_jtag->clkin / ++ (JTAG_GET_TCK_DIVISOR(aspeed_jtag_read( ++ aspeed_jtag, ASPEED_JTAG_TCK)) + 1); ++ } else if (aspeed_jtag->config->jtag_version == 0) { ++ /* TCK period = Period of PCLK * (JTAG14[10:0] + 1) * 2 */ ++ *freq = (aspeed_jtag->clkin / ++ (JTAG_GET_TCK_DIVISOR(aspeed_jtag_read( ++ aspeed_jtag, ASPEED_JTAG_TCK)) + 1)) >> 1; ++ } else { ++ /* unknown jtag version */ ++ *freq = 0; ++ } ++ return 0; ++} ++/******************************************************************************/ ++static u8 TCK_Cycle(struct aspeed_jtag_info *aspeed_jtag, u8 TMS, u8 TDI) ++{ ++ u8 tdo; ++ ++ /* IEEE 1149.1 ++ * TMS & TDI shall be sampled by the test logic on the rising edge ++ * test logic shall change TDO on the falling edge ++ */ ++ // TCK = 0 ++ aspeed_jtag_write(aspeed_jtag, ++ JTAG_SW_MODE_EN | (TMS * JTAG_SW_MODE_TMS) | ++ (TDI * JTAG_SW_MODE_TDIO), ++ ASPEED_JTAG_SW); ++ aspeed_jtag_read(aspeed_jtag, ASPEED_JTAG_SW); ++ ++ /* Target device have their operating frequency*/ ++ ndelay(aspeed_jtag->sw_delay >> 1); ++ ++ // TCK = 1 ++ aspeed_jtag_write(aspeed_jtag, ++ JTAG_SW_MODE_EN | JTAG_SW_MODE_TCK | ++ (TMS * JTAG_SW_MODE_TMS) | ++ (TDI * JTAG_SW_MODE_TDIO), ++ ASPEED_JTAG_SW); ++ aspeed_jtag_read(aspeed_jtag, ASPEED_JTAG_SW); ++ ++ ndelay(aspeed_jtag->sw_delay >> 1); ++ /* Sampled TDI(slave, master's TDO) on the rising edge */ ++ if (aspeed_jtag_read(aspeed_jtag, ASPEED_JTAG_SW) & JTAG_SW_MODE_TDIO) ++ tdo = 1; ++ else ++ tdo = 0; ++ ++ return tdo; ++} ++ ++static int aspeed_jtag_sw_set_tap_state(struct aspeed_jtag_info *aspeed_jtag, ++ enum jtag_tapstate endstate) ++{ ++ int i = 0; ++ enum jtag_tapstate from, to; ++ ++ from = aspeed_jtag->sts; ++ to = endstate; ++ /* Send 8 TMS high to ensure jtag tap state go to TLRESET */ ++ if (endstate == JTAG_STATE_TLRESET) ++ for (i = 0; i < 8 ; i++) ++ TCK_Cycle(aspeed_jtag, ((0xff >> i) & 0x1), 0); ++ else ++ for (i = 0; i < _tms_cycle_lookup[from][to].count; i++) ++ TCK_Cycle(aspeed_jtag, ++ ((_tms_cycle_lookup[from][to].tmsbits >> i) & ++ 0x1), ++ 0); ++ aspeed_jtag->sts = endstate; ++ return 0; ++} ++ ++/******************************************************************************/ ++static void aspeed_jtag_wait_instruction_pause_complete( ++ struct aspeed_jtag_info *aspeed_jtag) ++{ ++ wait_event_interruptible(aspeed_jtag->jtag_wq, ++ (aspeed_jtag->flag & JTAG_INST_PAUSE)); ++ aspeed_jtag->flag &= ~JTAG_INST_PAUSE; ++} ++static void ++aspeed_jtag_wait_instruction_complete(struct aspeed_jtag_info *aspeed_jtag) ++{ ++ wait_event_interruptible(aspeed_jtag->jtag_wq, ++ (aspeed_jtag->flag & JTAG_INST_COMPLETE)); ++ aspeed_jtag->flag &= ~JTAG_INST_COMPLETE; ++} ++static void ++aspeed_jtag_wait_data_pause_complete(struct aspeed_jtag_info *aspeed_jtag) ++{ ++ wait_event_interruptible(aspeed_jtag->jtag_wq, ++ (aspeed_jtag->flag & JTAG_DATA_PAUSE)); ++ aspeed_jtag->flag &= ~JTAG_DATA_PAUSE; ++} ++static void aspeed_jtag_wait_data_complete(struct aspeed_jtag_info *aspeed_jtag) ++{ ++ wait_event_interruptible(aspeed_jtag->jtag_wq, ++ (aspeed_jtag->flag & JTAG_DATA_COMPLETE)); ++ aspeed_jtag->flag &= ~JTAG_DATA_COMPLETE; ++} ++static int aspeed_jtag_run_to_tlr(struct aspeed_jtag_info *aspeed_jtag) ++{ ++ if (aspeed_jtag->sts == JTAG_STATE_PAUSEIR) ++ aspeed_jtag_write(aspeed_jtag, JTAG_INST_COMPLETE_EN, ++ ASPEED_JTAG_ISR); ++ else if (aspeed_jtag->sts == JTAG_STATE_PAUSEDR) ++ aspeed_jtag_write(aspeed_jtag, JTAG_DATA_COMPLETE_EN, ++ ASPEED_JTAG_ISR); ++ aspeed_jtag_write(aspeed_jtag, ++ JTAG_ENG_EN | JTAG_ENG_OUT_EN | JTAG_FORCE_TMS, ++ ASPEED_JTAG_CTRL); // x TMS high + 1 TMS low ++ if (aspeed_jtag->sts == JTAG_STATE_PAUSEIR) ++ aspeed_jtag_wait_instruction_complete(aspeed_jtag); ++ else if (aspeed_jtag->sts == JTAG_STATE_PAUSEDR) ++ aspeed_jtag_wait_data_complete(aspeed_jtag); ++ /* After that the fsm will go to idle state: hw constraint */ ++ aspeed_jtag->sts = JTAG_STATE_IDLE; ++ return 0; ++} ++ ++static int aspeed_jtag_run_to_idle(struct aspeed_jtag_info *aspeed_jtag) ++{ ++ if (aspeed_jtag->sts == JTAG_STATE_IDLE) { ++ /* nothing to do */ ++ } else if (aspeed_jtag->sts == JTAG_STATE_PAUSEDR) { ++ aspeed_jtag_write(aspeed_jtag, JTAG_DATA_COMPLETE_EN, ++ ASPEED_JTAG_ISR); ++ if (aspeed_jtag->config->jtag_version == 6) { ++ aspeed_jtag_write(aspeed_jtag, ++ JTAG_ENG_EN | JTAG_ENG_OUT_EN | ++ JTAG_G6_TERMINATE_XFER | ++ JTAG_DATA_EN, ++ ASPEED_JTAG_CTRL); ++ } else { ++ aspeed_jtag_write(aspeed_jtag, ++ JTAG_ENG_EN | JTAG_ENG_OUT_EN | ++ JTAG_TERMINATE_DATA | ++ JTAG_DATA_EN, ++ ASPEED_JTAG_CTRL); ++ } ++ aspeed_jtag_wait_data_complete(aspeed_jtag); ++ } else if (aspeed_jtag->sts == JTAG_STATE_PAUSEIR) { ++ aspeed_jtag_write(aspeed_jtag, JTAG_INST_COMPLETE_EN, ++ ASPEED_JTAG_ISR); ++ if (aspeed_jtag->config->jtag_version == 6) { ++ aspeed_jtag_write(aspeed_jtag, ++ JTAG_ENG_EN | JTAG_ENG_OUT_EN | ++ JTAG_G6_TERMINATE_XFER | ++ JTAG_G6_INST_EN, ++ ASPEED_JTAG_CTRL); ++ } else { ++ aspeed_jtag_write(aspeed_jtag, ++ JTAG_ENG_EN | JTAG_ENG_OUT_EN | ++ JTAG_TERMINATE_INST | ++ JTAG_INST_EN, ++ ASPEED_JTAG_CTRL); ++ } ++ aspeed_jtag_wait_instruction_complete(aspeed_jtag); ++ } else { ++ pr_err("Should not get here unless aspeed_jtag->sts error!"); ++ return -EFAULT; ++ } ++ aspeed_jtag->sts = JTAG_STATE_IDLE; ++ return 0; ++} ++ ++static int aspeed_jtag_hw_set_tap_state(struct aspeed_jtag_info *aspeed_jtag, ++ enum jtag_tapstate endstate) ++{ ++ int ret; ++ ++ if (endstate == JTAG_STATE_TLRESET) { ++ ret = aspeed_jtag_run_to_tlr(aspeed_jtag); ++ } else if (endstate == JTAG_STATE_IDLE) { ++ ret = aspeed_jtag_run_to_idle(aspeed_jtag); ++ } else { ++ /* other stable state will auto handle by hardware */ ++ return 0; ++ } ++ return ret; ++} ++ ++/******************************************************************************/ ++/* JTAG_reset() is to generate at leaspeed 9 TMS high and ++ * 1 TMS low to force devices into Run-Test/Idle State ++ */ ++static int aspeed_jtag_status_set(struct jtag *jtag, ++ struct jtag_tap_state *tapstate) ++{ ++ struct aspeed_jtag_info *aspeed_jtag = jtag_priv(jtag); ++ int ret; ++ uint32_t i; ++ ++ if (tapstate->from == JTAG_STATE_CURRENT) ++ tapstate->from = aspeed_jtag->sts; ++ if (tapstate->endstate == JTAG_STATE_CURRENT) ++ tapstate->endstate = aspeed_jtag->sts; ++ JTAG_DBUG("reset:%d from:%s end:%s tck:%d", tapstate->reset, ++ end_status_str[tapstate->from], ++ end_status_str[tapstate->endstate], tapstate->tck); ++ if (aspeed_jtag->mode == JTAG_XFER_HW_MODE) { ++ if (tapstate->reset == JTAG_FORCE_RESET) ++ aspeed_jtag_hw_set_tap_state(aspeed_jtag, ++ JTAG_STATE_TLRESET); ++ ret = aspeed_jtag_hw_set_tap_state(aspeed_jtag, ++ tapstate->endstate); ++ for (i = 0; i < tapstate->tck; i++) ++ ndelay(aspeed_jtag->tck_period); ++ } else { ++ if (tapstate->reset == JTAG_FORCE_RESET) ++ aspeed_jtag_sw_set_tap_state(aspeed_jtag, ++ JTAG_STATE_TLRESET); ++ ret = aspeed_jtag_sw_set_tap_state(aspeed_jtag, ++ tapstate->endstate); ++ if (tapstate->endstate == JTAG_STATE_TLRESET || ++ tapstate->endstate == JTAG_STATE_IDLE || ++ tapstate->endstate == JTAG_STATE_PAUSEDR || ++ tapstate->endstate == JTAG_STATE_PAUSEIR) ++ for (i = 0; i < tapstate->tck; i++) ++ TCK_Cycle(aspeed_jtag, 0, 0); ++ } ++ if (ret) ++ return ret; ++ return 0; ++} ++ ++static int aspeed_jtag_status_get(struct jtag *jtag, u32 *status) ++{ ++ struct aspeed_jtag_info *aspeed_jtag = jtag_priv(jtag); ++ ++ *status = aspeed_jtag->sts; ++ return 0; ++} ++static void aspeed_sw_jtag_xfer(struct aspeed_jtag_info *aspeed_jtag, ++ struct jtag_xfer *xfer, u8 *xfer_data) ++{ ++ unsigned int index = 0; ++ u32 shift_bits = 0; ++ u8 tdi = 0, tdo = 0, tdo_buff = 0; ++ u32 remain_xfer = xfer->length; ++ ++ if (xfer->type == JTAG_SIR_XFER) ++ aspeed_jtag_sw_set_tap_state(aspeed_jtag, JTAG_STATE_SHIFTIR); ++ else ++ aspeed_jtag_sw_set_tap_state(aspeed_jtag, JTAG_STATE_SHIFTDR); ++ ++ while (remain_xfer) { ++ tdi = (xfer_data[index]) >> (shift_bits % 8) & (0x1); ++ if (remain_xfer == 1 && ++ xfer->endstate != (xfer->type == JTAG_SIR_XFER ? ++ JTAG_STATE_SHIFTIR : ++ JTAG_STATE_SHIFTDR)) { ++ tdo = TCK_Cycle(aspeed_jtag, 1, tdi); // go to Exit1-XR ++ aspeed_jtag->sts = xfer->type == JTAG_SIR_XFER ? ++ JTAG_STATE_EXIT1IR : ++ JTAG_STATE_EXIT1DR; ++ } else ++ tdo = TCK_Cycle(aspeed_jtag, 0, tdi); // go to XRShift ++ tdo_buff |= (tdo << (shift_bits % 8)); ++ shift_bits++; ++ remain_xfer--; ++ if ((shift_bits % 8) == 0) { ++ if (xfer->direction & JTAG_READ_XFER) ++ xfer_data[index] = tdo_buff; ++ tdo_buff = 0; ++ index++; ++ } ++ } ++ if (xfer->direction & JTAG_READ_XFER && (shift_bits % 8)) ++ xfer_data[index] = tdo_buff; ++ aspeed_jtag_sw_set_tap_state(aspeed_jtag, xfer->endstate); ++} ++static int aspeed_hw_ir_scan(struct aspeed_jtag_info *aspeed_jtag, ++ enum jtag_tapstate endstate, u32 shift_bits) ++{ ++ if (endstate == JTAG_STATE_PAUSEIR) { ++ aspeed_jtag_write(aspeed_jtag, JTAG_INST_PAUSE_EN, ++ ASPEED_JTAG_ISR); ++ if (aspeed_jtag->config->jtag_version == 6) { ++ aspeed_jtag_write( ++ aspeed_jtag, ++ JTAG_ENG_EN | JTAG_ENG_OUT_EN | ++ JTAG_G6_SET_XFER_LEN(shift_bits), ++ ASPEED_JTAG_CTRL); ++ aspeed_jtag_write( ++ aspeed_jtag, ++ JTAG_ENG_EN | JTAG_ENG_OUT_EN | ++ JTAG_G6_SET_XFER_LEN(shift_bits) | ++ JTAG_G6_INST_EN, ++ ASPEED_JTAG_CTRL); ++ } else { ++ if (aspeed_jtag->sts == JTAG_STATE_PAUSEDR) ++ aspeed_jtag_write(aspeed_jtag, ++ JTAG_INST_PAUSE_EN | ++ JTAG_DATA_COMPLETE_EN, ++ ASPEED_JTAG_ISR); ++ aspeed_jtag_write(aspeed_jtag, ++ JTAG_ENG_EN | JTAG_ENG_OUT_EN | ++ JTAG_SET_INST_LEN(shift_bits), ++ ASPEED_JTAG_CTRL); ++ aspeed_jtag_write( ++ aspeed_jtag, ++ JTAG_ENG_EN | JTAG_ENG_OUT_EN | ++ JTAG_SET_INST_LEN(shift_bits) | ++ JTAG_INST_EN, ++ ASPEED_JTAG_CTRL); ++ if (aspeed_jtag->sts == JTAG_STATE_PAUSEDR) ++ aspeed_jtag_wait_data_complete(aspeed_jtag); ++ } ++ aspeed_jtag_wait_instruction_pause_complete(aspeed_jtag); ++ aspeed_jtag->sts = JTAG_STATE_PAUSEIR; ++ } else if (endstate == JTAG_STATE_IDLE) { ++ aspeed_jtag_write(aspeed_jtag, JTAG_INST_COMPLETE_EN, ++ ASPEED_JTAG_ISR); ++ if (aspeed_jtag->config->jtag_version == 6) { ++ aspeed_jtag_write( ++ aspeed_jtag, ++ JTAG_ENG_EN | JTAG_ENG_OUT_EN | ++ JTAG_G6_LAST_XFER | ++ JTAG_G6_SET_XFER_LEN(shift_bits), ++ ASPEED_JTAG_CTRL); ++ aspeed_jtag_write( ++ aspeed_jtag, ++ JTAG_ENG_EN | JTAG_ENG_OUT_EN | ++ JTAG_G6_LAST_XFER | ++ JTAG_G6_SET_XFER_LEN(shift_bits) | ++ JTAG_G6_INST_EN, ++ ASPEED_JTAG_CTRL); ++ } else { ++ aspeed_jtag_write(aspeed_jtag, ++ JTAG_ENG_EN | JTAG_ENG_OUT_EN | ++ JTAG_LAST_INST | ++ JTAG_SET_INST_LEN(shift_bits), ++ ASPEED_JTAG_CTRL); ++ aspeed_jtag_write( ++ aspeed_jtag, ++ JTAG_ENG_EN | JTAG_ENG_OUT_EN | JTAG_LAST_INST | ++ JTAG_SET_INST_LEN(shift_bits) | ++ JTAG_INST_EN, ++ ASPEED_JTAG_CTRL); ++ } ++ aspeed_jtag_wait_instruction_complete(aspeed_jtag); ++ aspeed_jtag->sts = JTAG_STATE_IDLE; ++ } else { ++ pr_err("End state %d not support", endstate); ++ return -EFAULT; ++ } ++ return 0; ++} ++static int aspeed_hw_dr_scan(struct aspeed_jtag_info *aspeed_jtag, ++ enum jtag_tapstate endstate, u32 shift_bits) ++{ ++ if (endstate == JTAG_STATE_PAUSEDR) { ++ aspeed_jtag_write(aspeed_jtag, JTAG_DATA_PAUSE_EN, ++ ASPEED_JTAG_ISR); ++ if (aspeed_jtag->config->jtag_version == 6) { ++ aspeed_jtag_write( ++ aspeed_jtag, ++ JTAG_ENG_EN | JTAG_ENG_OUT_EN | ++ JTAG_G6_SET_XFER_LEN(shift_bits), ++ ASPEED_JTAG_CTRL); ++ aspeed_jtag_write( ++ aspeed_jtag, ++ JTAG_ENG_EN | JTAG_ENG_OUT_EN | ++ JTAG_G6_SET_XFER_LEN(shift_bits) | ++ JTAG_DATA_EN, ++ ASPEED_JTAG_CTRL); ++ } else { ++ if (aspeed_jtag->sts == JTAG_STATE_PAUSEIR) ++ aspeed_jtag_write(aspeed_jtag, ++ JTAG_DATA_PAUSE_EN | ++ JTAG_INST_COMPLETE_EN, ++ ASPEED_JTAG_ISR); ++ aspeed_jtag_write(aspeed_jtag, ++ JTAG_ENG_EN | JTAG_ENG_OUT_EN | ++ JTAG_DATA_LEN(shift_bits), ++ ASPEED_JTAG_CTRL); ++ aspeed_jtag_write(aspeed_jtag, ++ JTAG_ENG_EN | JTAG_ENG_OUT_EN | ++ JTAG_DATA_LEN(shift_bits) | ++ JTAG_DATA_EN, ++ ASPEED_JTAG_CTRL); ++ if (aspeed_jtag->sts == JTAG_STATE_PAUSEIR) ++ aspeed_jtag_wait_instruction_complete( ++ aspeed_jtag); ++ } ++ aspeed_jtag_wait_data_pause_complete(aspeed_jtag); ++ aspeed_jtag->sts = JTAG_STATE_PAUSEDR; ++ } else if (endstate == JTAG_STATE_IDLE) { ++ aspeed_jtag_write(aspeed_jtag, JTAG_DATA_COMPLETE_EN, ++ ASPEED_JTAG_ISR); ++ if (aspeed_jtag->config->jtag_version == 6) { ++ aspeed_jtag_write( ++ aspeed_jtag, ++ JTAG_ENG_EN | JTAG_ENG_OUT_EN | ++ JTAG_G6_LAST_XFER | ++ JTAG_G6_SET_XFER_LEN(shift_bits), ++ ASPEED_JTAG_CTRL); ++ aspeed_jtag_write( ++ aspeed_jtag, ++ JTAG_ENG_EN | JTAG_ENG_OUT_EN | ++ JTAG_G6_LAST_XFER | ++ JTAG_G6_SET_XFER_LEN(shift_bits) | ++ JTAG_DATA_EN, ++ ASPEED_JTAG_CTRL); ++ } else { ++ aspeed_jtag_write(aspeed_jtag, ++ JTAG_ENG_EN | JTAG_ENG_OUT_EN | ++ JTAG_LAST_DATA | ++ JTAG_DATA_LEN(shift_bits), ++ ASPEED_JTAG_CTRL); ++ aspeed_jtag_write(aspeed_jtag, ++ JTAG_ENG_EN | JTAG_ENG_OUT_EN | ++ JTAG_LAST_DATA | ++ JTAG_DATA_LEN(shift_bits) | ++ JTAG_DATA_EN, ++ ASPEED_JTAG_CTRL); ++ } ++ aspeed_jtag_wait_data_complete(aspeed_jtag); ++ aspeed_jtag->sts = JTAG_STATE_IDLE; ++ } else { ++ pr_err("End state %d not support", endstate); ++ return -EFAULT; ++ } ++ return 0; ++} ++static void aspeed_hw_jtag_xfer(struct aspeed_jtag_info *aspeed_jtag, ++ struct jtag_xfer *xfer, u8 *xfer_data) ++{ ++ unsigned int index = 0; ++ u32 shift_bits = 0; ++ u32 remain_xfer = xfer->length; ++ int i, tmp_idx = 0; ++ u32 fifo_reg = xfer->type ? ASPEED_JTAG_DATA : ASPEED_JTAG_INST; ++ u32 *xfer_data_32 = (u32 *)xfer_data; ++ enum jtag_tapstate endstate; ++ ++ /* Translate the end tap status to the stable tap status for hw mode */ ++ if (xfer->endstate == JTAG_STATE_PAUSEDR || ++ xfer->endstate == JTAG_STATE_SHIFTDR) ++ endstate = JTAG_STATE_PAUSEDR; ++ else if (xfer->endstate == JTAG_STATE_PAUSEIR || ++ xfer->endstate == JTAG_STATE_SHIFTIR) ++ endstate = JTAG_STATE_PAUSEIR; ++ else ++ endstate = JTAG_STATE_IDLE; ++ ++ while (remain_xfer) { ++ if (remain_xfer > aspeed_jtag->config->jtag_buff_len) { ++ shift_bits = aspeed_jtag->config->jtag_buff_len; ++ tmp_idx = shift_bits / 32; ++ for (i = 0; i < tmp_idx; i++) ++ aspeed_jtag_write(aspeed_jtag, ++ xfer_data_32[index + i], ++ fifo_reg); ++ /* ++ * Add 1 tck period delay to avoid jtag hardware ++ * transfer will get wrong fifo pointer issue. ++ */ ++ ndelay(aspeed_jtag->tck_period); ++ if (xfer->type == JTAG_SIR_XFER) ++ aspeed_hw_ir_scan(aspeed_jtag, ++ JTAG_STATE_PAUSEIR, ++ shift_bits); ++ else ++ aspeed_hw_dr_scan(aspeed_jtag, ++ JTAG_STATE_PAUSEDR, ++ shift_bits); ++ } else { ++ shift_bits = remain_xfer; ++ tmp_idx = shift_bits / 32; ++ if (shift_bits % 32) ++ tmp_idx += 1; ++ for (i = 0; i < tmp_idx; i++) ++ aspeed_jtag_write(aspeed_jtag, ++ xfer_data_32[index + i], ++ fifo_reg); ++ ndelay(aspeed_jtag->tck_period); ++ if (xfer->type == JTAG_SIR_XFER) ++ aspeed_hw_ir_scan(aspeed_jtag, endstate, ++ shift_bits); ++ else ++ aspeed_hw_dr_scan(aspeed_jtag, endstate, ++ shift_bits); ++ } ++ ++ remain_xfer = remain_xfer - shift_bits; ++ ++ //handle tdo data ++ if (xfer->direction & JTAG_READ_XFER) { ++ tmp_idx = shift_bits / 32; ++ if (shift_bits % 32) ++ tmp_idx += 1; ++ for (i = 0; i < tmp_idx; i++) { ++ if (shift_bits < 32) ++ xfer_data_32[index + i] = ++ aspeed_jtag_read(aspeed_jtag, ++ fifo_reg) >> ++ (32 - shift_bits); ++ else ++ xfer_data_32[index + i] = ++ aspeed_jtag_read(aspeed_jtag, ++ fifo_reg); ++ shift_bits -= 32; ++ } ++ } ++ index += tmp_idx; ++ } ++} ++ ++static int aspeed_jtag_xfer(struct jtag *jtag, struct jtag_xfer *xfer, ++ u8 *xfer_data) ++{ ++ struct aspeed_jtag_info *aspeed_jtag = jtag_priv(jtag); ++ union pad_config padding; ++ struct jtag_xfer pre_xfer, post_xfer; ++ struct jtag_xfer peri_xfer = { ++ .type = xfer->type, ++ .direction = xfer->direction, ++ .from = xfer->from, ++ .endstate = xfer->endstate, ++ .padding = 0, ++ .length = xfer->length, ++ }; ++ ++ padding.int_value = xfer->padding; ++ JTAG_DBUG( ++ "%s mode, type: %s direction: %d, END : %s, padding: (value: %d) pre_pad: %d post_pad: %d, len: %d\n", ++ aspeed_jtag->mode ? "HW" : "SW", xfer->type ? "DR" : "IR", ++ xfer->direction, end_status_str[xfer->endstate], ++ padding.pad_data, padding.pre_pad_number, ++ padding.post_pad_number, xfer->length); ++ if (padding.pre_pad_number) { ++ pre_xfer.type = xfer->type; ++ pre_xfer.direction = JTAG_WRITE_XFER; ++ pre_xfer.from = xfer->from; ++ pre_xfer.endstate = ++ xfer->type ? JTAG_STATE_PAUSEDR : JTAG_STATE_PAUSEIR; ++ pre_xfer.padding = xfer->padding; ++ pre_xfer.length = padding.pre_pad_number; ++ ++ peri_xfer.from = pre_xfer.endstate; ++ } ++ ++ if (padding.post_pad_number) { ++ peri_xfer.endstate = ++ xfer->type ? JTAG_STATE_PAUSEDR : JTAG_STATE_PAUSEIR; ++ ++ post_xfer.type = xfer->type; ++ post_xfer.direction = JTAG_WRITE_XFER; ++ post_xfer.from = peri_xfer.endstate; ++ post_xfer.endstate = xfer->endstate; ++ post_xfer.padding = xfer->padding; ++ post_xfer.length = padding.post_pad_number; ++ } ++ if (padding.pre_pad_number) { ++ if (aspeed_jtag->mode == JTAG_XFER_HW_MODE) ++ aspeed_hw_jtag_xfer(aspeed_jtag, &pre_xfer, ++ padding.pad_data ? ++ aspeed_jtag->pad_data_one : ++ aspeed_jtag->pad_data_zero); ++ else ++ aspeed_sw_jtag_xfer(aspeed_jtag, &pre_xfer, ++ padding.pad_data ? ++ aspeed_jtag->pad_data_one : ++ aspeed_jtag->pad_data_zero); ++ } ++ ++ if (aspeed_jtag->mode == JTAG_XFER_HW_MODE) ++ aspeed_hw_jtag_xfer(aspeed_jtag, &peri_xfer, xfer_data); ++ else ++ aspeed_sw_jtag_xfer(aspeed_jtag, &peri_xfer, xfer_data); ++ ++ if (padding.post_pad_number) { ++ if (aspeed_jtag->mode == JTAG_XFER_HW_MODE) ++ aspeed_hw_jtag_xfer(aspeed_jtag, &post_xfer, ++ padding.pad_data ? ++ aspeed_jtag->pad_data_one : ++ aspeed_jtag->pad_data_zero); ++ else ++ aspeed_sw_jtag_xfer(aspeed_jtag, &post_xfer, ++ padding.pad_data ? ++ aspeed_jtag->pad_data_one : ++ aspeed_jtag->pad_data_zero); ++ } ++ ++ return 0; ++} ++ ++static irqreturn_t aspeed_jtag_isr(int this_irq, void *dev_id) ++{ ++ u32 status; ++ struct aspeed_jtag_info *aspeed_jtag = dev_id; ++ ++ status = aspeed_jtag_read(aspeed_jtag, ASPEED_JTAG_ISR); ++ ++ if (status & JTAG_INST_PAUSE) { ++ aspeed_jtag_write(aspeed_jtag, JTAG_INST_PAUSE | (status & 0xf), ++ ASPEED_JTAG_ISR); ++ aspeed_jtag->flag |= JTAG_INST_PAUSE; ++ } ++ ++ if (status & JTAG_INST_COMPLETE) { ++ aspeed_jtag_write(aspeed_jtag, ++ JTAG_INST_COMPLETE | (status & 0xf), ++ ASPEED_JTAG_ISR); ++ aspeed_jtag->flag |= JTAG_INST_COMPLETE; ++ } ++ ++ if (status & JTAG_DATA_PAUSE) { ++ aspeed_jtag_write(aspeed_jtag, JTAG_DATA_PAUSE | (status & 0xf), ++ ASPEED_JTAG_ISR); ++ aspeed_jtag->flag |= JTAG_DATA_PAUSE; ++ } ++ ++ if (status & JTAG_DATA_COMPLETE) { ++ aspeed_jtag_write(aspeed_jtag, ++ JTAG_DATA_COMPLETE | (status & 0xf), ++ ASPEED_JTAG_ISR); ++ aspeed_jtag->flag |= JTAG_DATA_COMPLETE; ++ } ++ ++ if (aspeed_jtag->flag) { ++ wake_up_interruptible(&aspeed_jtag->jtag_wq); ++ return IRQ_HANDLED; ++ } ++ pr_err("TODO Check JTAG's interrupt %x\n", ++ aspeed_jtag_read(aspeed_jtag, ASPEED_JTAG_ISR)); ++ return IRQ_NONE; ++} ++ ++ ++static struct aspeed_jtag_config jtag_config = { ++ .jtag_version = 0, ++ .jtag_buff_len = 32, ++}; ++ ++static struct aspeed_jtag_config jtag_g6_config = { ++ .jtag_version = 6, ++ .jtag_buff_len = 32, ++}; ++ ++static const struct of_device_id aspeed_jtag_of_matches[] = { ++ { ++ .compatible = "aspeed,ast2400-jtag", ++ .data = &jtag_config, ++ }, ++ { ++ .compatible = "aspeed,ast2500-jtag", ++ .data = &jtag_config, ++ }, ++ { ++ .compatible = "aspeed,ast2600-jtag", ++ .data = &jtag_g6_config, ++ }, ++ {}, ++}; ++MODULE_DEVICE_TABLE(of, aspeed_jtag_of_matches); ++ ++static int aspeed_jtag_bitbang(struct jtag *jtag, ++ struct bitbang_packet *bitbang, ++ struct tck_bitbang *bitbang_data) ++{ ++ struct aspeed_jtag_info *aspeed_jtag = jtag_priv(jtag); ++ int i = 0; ++ ++ for (i = 0; i < bitbang->length; i++) { ++ bitbang_data[i].tdo = ++ TCK_Cycle(aspeed_jtag, bitbang_data[i].tms, ++ bitbang_data[i].tdi); ++ } ++ if (aspeed_jtag->mode == JTAG_XFER_HW_MODE) ++ aspeed_jtag_write(aspeed_jtag, 0, ASPEED_JTAG_SW); ++ ++ return 0; ++} ++ ++static inline void aspeed_jtag_xfer_mode_set(struct aspeed_jtag_info *aspeed_jtag, u32 mode) ++{ ++ if (mode == JTAG_XFER_HW_MODE) ++ aspeed_jtag_write(aspeed_jtag, 0, ASPEED_JTAG_SW); ++ aspeed_jtag->mode = mode; ++} ++ ++static int aspeed_jtag_mode_set(struct jtag *jtag, struct jtag_mode *jtag_mode) ++{ ++ struct aspeed_jtag_info *aspeed_jtag = jtag_priv(jtag); ++ ++ switch (jtag_mode->feature) { ++ case JTAG_XFER_MODE: ++ aspeed_jtag_xfer_mode_set(aspeed_jtag, jtag_mode->mode); ++ break; ++ case JTAG_CONTROL_MODE: ++ return -ENOTSUPP; ++ default: ++ return -EINVAL; ++ } ++ return 0; ++} ++ ++static int aspeed_jtag_trst_set(struct jtag *jtag, u32 active) ++{ ++ struct aspeed_jtag_info *aspeed_jtag = jtag_priv(jtag); ++ ++ aspeed_jtag_write(aspeed_jtag, active ? 0 : JTAG_CTRL_TRSTn_HIGH, ++ ASPEED_JTAG_IDLE); ++ return 0; ++} ++ ++static int aspeed_jtag_enable(struct jtag *jtag) ++{ ++ struct aspeed_jtag_info *aspeed_jtag = jtag_priv(jtag); ++ ++ if (!IS_ERR(aspeed_jtag->mux_gpiod)) ++ gpiod_set_value(aspeed_jtag->mux_gpiod, 1); ++ return 0; ++} ++ ++static int aspeed_jtag_disable(struct jtag *jtag) ++{ ++ struct aspeed_jtag_info *aspeed_jtag = jtag_priv(jtag); ++ ++ if (!IS_ERR(aspeed_jtag->mux_gpiod)) ++ gpiod_set_value(aspeed_jtag->mux_gpiod, 0); ++ ++ return 0; ++} ++ ++static const struct jtag_ops aspeed_jtag_ops = { ++ .freq_get = aspeed_jtag_get_freq, ++ .freq_set = aspeed_jtag_set_freq, ++ .status_get = aspeed_jtag_status_get, ++ .status_set = aspeed_jtag_status_set, ++ .xfer = aspeed_jtag_xfer, ++ .mode_set = aspeed_jtag_mode_set, ++ .trst_set = aspeed_jtag_trst_set, ++ .bitbang = aspeed_jtag_bitbang, ++ .enable = aspeed_jtag_enable, ++ .disable = aspeed_jtag_disable, ++}; ++ ++static int aspeed_jtag_probe(struct platform_device *pdev) ++{ ++ struct aspeed_jtag_info *aspeed_jtag; ++ struct jtag *jtag; ++ const struct of_device_id *jtag_dev_id; ++ struct resource *res; ++ int ret = 0; ++ ++ jtag = jtag_alloc(&pdev->dev, sizeof(*aspeed_jtag), ++ &aspeed_jtag_ops); ++ if (!jtag) ++ return -ENOMEM; ++ ++ platform_set_drvdata(pdev, jtag); ++ aspeed_jtag = jtag_priv(jtag); ++ aspeed_jtag->dev = &pdev->dev; ++ ++ jtag_dev_id = of_match_device(aspeed_jtag_of_matches, &pdev->dev); ++ if (!jtag_dev_id) ++ return -EINVAL; ++ ++ aspeed_jtag->mux_gpiod = devm_gpiod_get_index(&pdev->dev, "mux", 0, GPIOD_OUT_LOW); ++ ++ res = platform_get_resource(pdev, IORESOURCE_MEM, 0); ++ if (res == NULL) { ++ dev_err(&pdev->dev, "cannot get IORESOURCE_MEM\n"); ++ ret = -ENOENT; ++ goto out; ++ } ++ ++ aspeed_jtag->reg_base = devm_ioremap_resource(&pdev->dev, res); ++ if (!aspeed_jtag->reg_base) { ++ ret = -EIO; ++ goto out; ++ } ++ ++ aspeed_jtag->irq = platform_get_irq(pdev, 0); ++ if (aspeed_jtag->irq < 0) { ++ dev_err(&pdev->dev, "no irq specified\n"); ++ ret = -ENOENT; ++ goto out; ++ } ++ aspeed_jtag->reset = ++ devm_reset_control_get_exclusive(&pdev->dev, NULL); ++ if (IS_ERR(aspeed_jtag->reset)) { ++ dev_err(&pdev->dev, "can't get jtag reset\n"); ++ return PTR_ERR(aspeed_jtag->reset); ++ } ++ ++ aspeed_jtag->clk = devm_clk_get(&pdev->dev, NULL); ++ if (IS_ERR(aspeed_jtag->clk)) { ++ dev_err(&pdev->dev, "no clock defined\n"); ++ return -ENODEV; ++ } ++ ++ aspeed_jtag->clkin = clk_get_rate(aspeed_jtag->clk); ++ dev_dbg(&pdev->dev, "aspeed_jtag->clkin %d\n", aspeed_jtag->clkin); ++ ++ aspeed_jtag->config = (struct aspeed_jtag_config *)jtag_dev_id->data; ++ // SCU init ++ reset_control_assert(aspeed_jtag->reset); ++ udelay(3); ++ reset_control_deassert(aspeed_jtag->reset); ++ ++ ret = devm_request_irq(&pdev->dev, aspeed_jtag->irq, aspeed_jtag_isr, ++ 0, dev_name(&pdev->dev), aspeed_jtag); ++ if (ret) { ++ dev_dbg(&pdev->dev, "JTAG Unable to get IRQ"); ++ goto out; ++ } ++ ++ // clear interrupt ++ aspeed_jtag_write(aspeed_jtag, ++ JTAG_INST_PAUSE | JTAG_INST_COMPLETE | ++ JTAG_DATA_PAUSE | JTAG_DATA_COMPLETE, ++ ASPEED_JTAG_ISR); ++ ++ aspeed_jtag_xfer_mode_set(aspeed_jtag, JTAG_XFER_HW_MODE); ++ aspeed_jtag->flag = 0; ++ aspeed_jtag->sts = JTAG_STATE_IDLE; ++ init_waitqueue_head(&aspeed_jtag->jtag_wq); ++ ++ aspeed_jtag_set_freq(jtag, TCK_FREQ); ++ /* Enable jtag clock */ ++ aspeed_jtag_write(aspeed_jtag, JTAG_ENG_OUT_EN, ASPEED_JTAG_CTRL); ++ ++ /* Initialize JTAG core structure*/ ++ ret = devm_jtag_register(aspeed_jtag->dev, jtag); ++ if (ret) ++ goto out; ++ ++ memset(aspeed_jtag->pad_data_one, ~0, ++ sizeof(aspeed_jtag->pad_data_one)); ++ memset(aspeed_jtag->pad_data_zero, 0, ++ sizeof(aspeed_jtag->pad_data_zero)); ++ ++ dev_info(&pdev->dev, "aspeed_jtag: driver successfully loaded.\n"); ++ ++ return 0; ++ ++out: ++ reset_control_assert(aspeed_jtag->reset); ++ jtag_free(jtag); ++ dev_warn(&pdev->dev, "aspeed_jtag: driver init failed (ret=%d)!\n", ++ ret); ++ return ret; ++} ++ ++static void aspeed_jtag_remove(struct platform_device *pdev) ++{ ++ struct jtag *jtag = platform_get_drvdata(pdev); ++ struct aspeed_jtag_info *aspeed_jtag; ++ ++ aspeed_jtag = jtag_priv(jtag); ++ reset_control_assert(aspeed_jtag->reset); ++ jtag_free(jtag); ++} ++ ++static struct platform_driver aspeed_jtag_driver = { ++ .probe = aspeed_jtag_probe, ++ .remove = aspeed_jtag_remove, ++ .driver = { ++ .name = "aspeed-jtag", ++ .of_match_table = aspeed_jtag_of_matches, ++ }, ++}; ++ ++module_platform_driver(aspeed_jtag_driver); ++ ++MODULE_AUTHOR("Ryan Chen "); ++MODULE_DESCRIPTION("AST JTAG LIB Driver"); ++MODULE_LICENSE("GPL"); diff --git a/patches-sonic/0007-JTAG-Aspeed-Fix-kernel-configuration.patch b/patches-sonic/0007-JTAG-Aspeed-Fix-kernel-configuration.patch new file mode 100644 index 000000000..d0a6e6ca8 --- /dev/null +++ b/patches-sonic/0007-JTAG-Aspeed-Fix-kernel-configuration.patch @@ -0,0 +1,46 @@ +From 7e6b94349b5fb51f0792b31cb186585f6466d8c0 Mon Sep 17 00:00:00 2001 +From: Vadim Pasternak +Date: Thu, 23 Apr 2026 21:40:40 +0300 +Subject: [PATCH 6.12 1/1] JTAG: Aspeed: Fix kernel configuration + +The JTAG subsystem was not properly wired into the kernel build: +drivers/jtag/Kconfig was not sourced from the top-level drivers/Kconfig, +so its configuration options were not exposed in menuconfig, and +drivers/Makefile descended into drivers/jtag/ only when +CONFIG_JTAG_ASPEED was set instead of for the generic CONFIG_JTAG +option. + +Source drivers/jtag/Kconfig from drivers/Kconfig so the JTAG options +become selectable, and build drivers/jtag/ based on CONFIG_JTAG so the +subsystem is compiled whenever JTAG support is enabled. + +Signed-off-by: Vadim Pasternak +--- + drivers/Kconfig | 2 ++ + drivers/Makefile | 2 +- + 2 files changed, 3 insertions(+), 1 deletion(-) + +diff --git a/drivers/Kconfig b/drivers/Kconfig +index 7bdad836f..c13e0a575 100644 +--- a/drivers/Kconfig ++++ b/drivers/Kconfig +@@ -245,4 +245,6 @@ source "drivers/cdx/Kconfig" + + source "drivers/dpll/Kconfig" + ++source "drivers/jtag/Kconfig" ++ + endmenu +diff --git a/drivers/Makefile b/drivers/Makefile +index 47ae5636e..8e1dc078f 100644 +--- a/drivers/Makefile ++++ b/drivers/Makefile +@@ -195,4 +195,4 @@ obj-$(CONFIG_CDX_BUS) += cdx/ + obj-$(CONFIG_DPLL) += dpll/ + + obj-$(CONFIG_S390) += s390/ +-obj-$(CONFIG_JTAG_ASPEED) += jtag/ ++obj-$(CONFIG_JTAG) += jtag/ +-- +2.34.1 + diff --git a/patches-sonic/0008-arm64-dts-aspeed-spc6-bmc-ext-phram-env-tpm-i3c-pull.patch b/patches-sonic/0008-arm64-dts-aspeed-spc6-bmc-ext-phram-env-tpm-i3c-pull.patch new file mode 100644 index 000000000..fdcdd3534 --- /dev/null +++ b/patches-sonic/0008-arm64-dts-aspeed-spc6-bmc-ext-phram-env-tpm-i3c-pull.patch @@ -0,0 +1,160 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Dvir Zaguri +Date: Sun, 31 May 2026 10:15:00 +0300 +Subject: [PATCH 6.12 1/1] arm64: dts: aspeed: spc6-bmc: phram-env, TPM init, + GPIO expander reset, I3C pull-ups + +Extend arch/arm64/boot/dts/aspeed/aspeed-nvidia-spc6-bmc.dts +(AST2700-A2 SPC6 CPU BMC) with four downstream fixes taken from +the OpenBMC Switch-26.05-1_br branch. Only the kernel DTS hunks +from each commit are taken; the matching userspace / U-Boot / +kconfig changes live in their own layers and are out of scope. + + 51f092d656fbe81d704036246fad43a186d008fb + "L1 Switch: SPC6 AST2700 BMC: move recovery counter to phram-env" + Dmitrijs Novikovs , May 12 2026 + + * Reserve a 4 KiB phram region (phram_env @ 0x4_237ff000), one + page below bmc-dev0, inside reserved-memory. fw_env now points + at /dev/mtd/by-name/phram-env so the recovery counter and the + rest of the U-Boot env survive warm boots. + + 2c97dad11108b1ceaedadd0762f3ef342b06abe9 + "L1 Switch: SPC6 AST2700 BMC: fix TPM GPIO init" + Sergejs Hatinecs , May 28 2026 + + * Add two gpio-hogs (spi-buf-oe, spi-tpm-buf-oe) on the i2c5 + pcal6524 (exp0) to drive GP_BMC_SPI_BUF_OE_L (pin 4) and + GP_BMC_SPI_TPM_BUF_OE_L (pin 5) low at gpiochip registration, + enabling the SPI and SPI/TPM level shifters before any SPI + probe touches the bus. + * Add a private buf-en-link-gpios property on tpm@1 pointing at + the same two exp0 pins. The property is intentionally NOT + consumed by any driver -- it exists solely as an fw_devlink + ordering hint so tpm@1 cannot probe until exp0 has probed and + its hogs have run. This kills the "no TPM found" race between + the i2c5 and spi0 buses. + + d406755fdf8bd1421095b6470bc1ca47e35697b6 + "L1 Switch: SPC6 AST2700 BMC: fix GPIO expander" + Sergejs Hatinecs , May 29 2026 + + * Add reset-gpios to the i2c5 pcal6524 (exp0) pointing at + GP_BMC_GPIO_EXP_RESET_L (gpio1 bank S, line 0, active-low), + so the pcal6524 driver pulse-resets the expander at probe and + starts it in a known state. Without this the expander could + stay wedged from a previous boot, breaking the SPI/TPM buffer + hogs, the mdio2 PHY reset (driven through &exp0 10) and the + WP / MUX-select lines that ride on the same expander. + + 2e6c7a8a27033a696505425384d8f5551aa86a36 + "L1 Switch: SPC6 AST2700: Enable I3C internal pull-ups via DT" + Dmitrijs Novikovs , May 15 2026 + + * Add internal-pullup = <1> to &i3c1 so the SoC drives the I3C + bus pull-ups and the controller comes up at boot without the + previous late user-space bind workaround. + +Signed-off-by: Dvir Zaguri +--- + .../boot/dts/aspeed/aspeed-nvidia-spc6-bmc.dts | 59 ++++++++++++++++++++++ + 1 file changed, 59 insertions(+) + +diff --git a/arch/arm64/boot/dts/aspeed/aspeed-nvidia-spc6-bmc.dts b/arch/arm64/boot/dts/aspeed/aspeed-nvidia-spc6-bmc.dts +--- a/arch/arm64/boot/dts/aspeed/aspeed-nvidia-spc6-bmc.dts ++++ b/arch/arm64/boot/dts/aspeed/aspeed-nvidia-spc6-bmc.dts +@@ -29,6 +29,15 @@ + ranges; + + #include "ast2700-reserved-mem.dtsi" ++ ++ // (before bmc-dev0 @0x423800000). ++ // Persistent phram region for fw_env variables across warm boots. ++ phram_env: phram-env@4237ff000 { ++ compatible = "phram"; ++ label = "phram-env"; ++ reg = <0x00000004 0x237ff000 0x0 0x00001000>; ++ no-map; ++ }; + }; + }; + +@@ -55,6 +64,30 @@ + compatible = "st,st33htpm-spi", "infineon,slb9670", "tcg,tpm_tis-spi"; + reg = <1>; + spi-max-frequency = <10000000>; ++ /* ++ * NOTE: this property is NOT consumed by any driver. Stock ++ * tpm_tis_spi (and all other drivers matching the compatibles ++ * above) does not call gpiod_get() for this name. That is ++ * intentional -- the SPI/TPM level shifters are enabled by the ++ * gpio-hogs on exp0 (see below), not by this device. ++ * ++ * It is kept purely as an fw_devlink ordering hint: any property ++ * whose name ends in "-gpios" is parsed by the DT core to build ++ * a supplier/consumer link. The phandle to exp0 therefore makes ++ * exp0 a supplier of tpm@1, so tpm@1 is not probed until the ++ * pcal6524 has probed and its hogs have run. Without this hint ++ * there is no ordering between the i2c5 and spi0 buses, and the ++ * TPM could be accessed before the buffers are enabled (races to ++ * "no TPM found"). Do not delete it. ++ * ++ * The property name is deliberately a private one ++ * ("buf-en-link-gpios") that no upstream driver consumes. If ++ * the property were called "enable-gpios" and a future kernel ++ * added gpiod_get(dev, "enable", ...) to tpm_tis_spi, it would ++ * collide with the hogs and fail -EBUSY. ++ */ ++ buf-en-link-gpios = <&exp0 4 GPIO_ACTIVE_LOW>, ++ <&exp0 5 GPIO_ACTIVE_LOW>; + }; + }; + +@@ -243,6 +276,7 @@ + #gpio-cells = <2>; + #address-cells = <1>; + #size-cells = <0>; ++ reset-gpios = <&gpio1 ASPEED_GPIO(S, 0) GPIO_ACTIVE_LOW>; /*GPIO_BMC_GPIO_EXP_RESET_L*/ + + gpio-line-names = + "RSRVD_0", "GP_EROT_RST_IN_L", +@@ -257,6 +291,30 @@ + "RSRVD_7", "GP_PROD_CS_FLASH0_EN", + "RSRVD_8", "GP_PROD_CS_FLASH1_EN", + "RSRVD_9", "RSRVD_10"; ++ /* ++ * Enable the SPI/TPM level shifters at gpiochip registration, ++ * i.e. during this node's probe -- before tpm@1 (ordered after ++ * us via the buf-en-link-gpios phandle above) touches ++ * the bus. ++ * ++ * POLARITY: these nets are active-low (..._OE_L), so physical low ++ * enables the buffer. In a gpio-hog, output-low/output-high are ++ * LOGICAL values and are inverted by GPIO_ACTIVE_LOW. Therefore ++ * "output-high" = logical 1 = physical low = buffer ENABLED. ++ * Using "output-low" here would drive the pin high and DISABLE ++ * the buffers -- the opposite of what we want. ++ */ ++ spi-buf-oe { ++ gpio-hog; ++ gpios = <4 GPIO_ACTIVE_LOW>; /* GP_BMC_SPI_BUF_OE_L */ ++ output-high; /* -> physical low -> enabled */ ++ }; ++ ++ spi-tpm-buf-oe { ++ gpio-hog; ++ gpios = <5 GPIO_ACTIVE_LOW>; /* GP_BMC_SPI_TPM_BUF_OE_L */ ++ output-high; /* -> physical low -> enabled */ ++ }; + }; + }; + +@@ -463,5 +521,6 @@ + status = "okay"; + pinctrl-names = "default"; + i3c-scl-hz = <12500000>; ++ internal-pullup = <1>; + mctp-controller; + }; +-- +2.34.1 diff --git a/patches-sonic/0009-arm64-dts-aspeed-split-SPC6-BMC-DTS-for-SONiC-and-Op.patch b/patches-sonic/0009-arm64-dts-aspeed-split-SPC6-BMC-DTS-for-SONiC-and-Op.patch new file mode 100644 index 000000000..630769244 --- /dev/null +++ b/patches-sonic/0009-arm64-dts-aspeed-split-SPC6-BMC-DTS-for-SONiC-and-Op.patch @@ -0,0 +1,620 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Vadim Pasternak +Date: Thu, 4 Jun 2026 11:03:21 +0300 +Subject: [PATCH 6.12 1/1] arm64: dts: aspeed: split SPC6 BMC DTS for SONiC + and OpenBMC boot +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +SONiC and OpenBMC use different rootfs boot paths on the same board. + +OpenBMC loads rootfs from SPI via phram: +(phram.phram=ramrofs,0x403920000,… and root=/dev/ram). +The device tree therefore reserves ramrofs@403920000. + +SONiC boots from eMMC (root=UUID, squashfs loop) and does not use +phram. Keeping ramrofs in the SONiC DTB causes early boot failures +when the kernel cannot reserve that region (it overlaps the kernel +load window around 0x403000000). + +Add SONiC-specific irot/sunda fragments without the ramrofs +reservation and build aspeed-nvidia-spc6-bmc.dtb from them so +SONiC FIT configs stay unchanged. +Add aspeed-nvidia-spc6-bmc-openbmc.dts for OpenBMC (phram/SPI) +with the existing ramrofs node and irot.dtsi. + +Signed-off-by: Vadim Pasternak +--- + .../aspeed/aspeed-nvidia-spc6-bmc-openbmc.dts | 527 ++++++++++++++++++ + .../dts/aspeed/aspeed-nvidia-spc6-bmc.dts | 2 +- + .../dts/aspeed/nvidia-ast27xx-irot-sonic.dtsi | 14 + + .../aspeed/nvidia-ast27xx-sunda-sonic.dtsi | 8 + + 4 files changed, 549 insertions(+), 1 deletion(-) + create mode 100644 arch/arm64/boot/dts/aspeed/aspeed-nvidia-spc6-bmc-openbmc.dts + create mode 100644 arch/arm64/boot/dts/aspeed/nvidia-ast27xx-irot-sonic.dtsi + create mode 100644 arch/arm64/boot/dts/aspeed/nvidia-ast27xx-sunda-sonic.dtsi + +diff --git a/arch/arm64/boot/dts/aspeed/aspeed-nvidia-spc6-bmc-openbmc.dts b/arch/arm64/boot/dts/aspeed/aspeed-nvidia-spc6-bmc-openbmc.dts +new file mode 100644 +index 000000000..e825c106d +--- /dev/null ++++ b/arch/arm64/boot/dts/aspeed/aspeed-nvidia-spc6-bmc-openbmc.dts +@@ -0,0 +1,527 @@ ++// SPDX-License-Identifier: GPL-2.0+ ++/dts-v1/; ++ ++#include "aspeed-nvidia-spc6-bmc-core.dtsi" ++#include ++#include ++#include ++#include "nvidia-ast27xx-sunda.dtsi" ++ ++#define PCIE0_EP 1 // 1: EP, 0: RC ++ ++/ { ++ model = "AST2700-A2 SPC6 CPU BMC"; ++ compatible = "aspeed,ast2700"; ++ ++ aliases { ++ rtc0 = &ext_rtc; /* Forces PCF85053A to be rtc0 */ ++ rtc1 = &rtc; /* Moves Aspeed Internal RTC to rtc1 */ ++ }; ++ ++ chosen { ++ stdout-path = &uart12; ++ bootargs = "console=ttyS12,115200 earlyprintk"; ++ }; ++ ++ memory@400000000 { ++ device_type = "memory"; ++ reg = <0x4 0x00000000 0x0 0x40000000>; ++ }; ++ ++ reserved-memory { ++ #address-cells = <2>; ++ #size-cells = <2>; ++ ranges; ++ ++ #include "ast2700-reserved-mem.dtsi" ++ ++ // (before bmc-dev0 @0x423800000). ++ // Persistent phram region for fw_env variables across warm boots. ++ phram_env: phram-env@4237ff000 { ++ compatible = "phram"; ++ label = "phram-env"; ++ reg = <0x00000004 0x237ff000 0x0 0x00001000>; ++ no-map; ++ }; ++ }; ++}; ++ ++/* ++ * SPI0: BIOS recovery (CS0, CPLD selects which flash) and TPM (CS1, AST2700 only). ++ * AST2700 uses &spi0; AST2600 uses &spi1raw. ++ * Remove default flash@0/flash@1 from dtsi so spidev and tpm are the only children. ++ */ ++&spi0 { ++ compatible = "aspeed,ast2700-spi-txrx"; ++ status = "okay"; ++ pinctrl-names = "default"; ++ pinctrl-0 = <&pinctrl_spi0_default &pinctrl_spi0_cs1_default>; ++ /delete-node/ flash@0; ++ /delete-node/ flash@1; ++ spidev@0 { ++ /* BIOS flash: CPLD (spi_chnl_select) selects which of two flashes */ ++ compatible = "rohm,dh2228fv"; ++ reg = <0>; ++ status = "okay"; ++ }; ++ /* TPM on chip select 1; both ST33 and Infineon SLB9672 use tpm_tis_spi */ ++ tpm@1 { ++ compatible = "st,st33htpm-spi", "infineon,slb9670", "tcg,tpm_tis-spi"; ++ reg = <1>; ++ spi-max-frequency = <10000000>; ++ /* ++ * NOTE: this property is NOT consumed by any driver. Stock ++ * tpm_tis_spi (and all other drivers matching the compatibles ++ * above) does not call gpiod_get() for this name. That is ++ * intentional -- the SPI/TPM level shifters are enabled by the ++ * gpio-hogs on exp0 (see below), not by this device. ++ * ++ * It is kept purely as an fw_devlink ordering hint: any property ++ * whose name ends in "-gpios" is parsed by the DT core to build ++ * a supplier/consumer link. The phandle to exp0 therefore makes ++ * exp0 a supplier of tpm@1, so tpm@1 is not probed until the ++ * pcal6524 has probed and its hogs have run. Without this hint ++ * there is no ordering between the i2c5 and spi0 buses, and the ++ * TPM could be accessed before the buffers are enabled (races to ++ * "no TPM found"). Do not delete it. ++ * ++ * The property name is deliberately a private one ++ * ("buf-en-link-gpios") that no upstream driver consumes. If ++ * the property were called "enable-gpios" and a future kernel ++ * added gpiod_get(dev, "enable", ...) to tpm_tis_spi, it would ++ * collide with the hogs and fail -EBUSY. ++ */ ++ buf-en-link-gpios = <&exp0 4 GPIO_ACTIVE_LOW>, ++ <&exp0 5 GPIO_ACTIVE_LOW>; ++ }; ++}; ++ ++#if 0 ++&fmc { ++ status = "okay"; ++ pinctrl-0 = <&pinctrl_fwspi_quad_default>; ++ pinctrl-names = "default"; ++ ++ flash@0 { ++ status = "okay"; ++ m25p,fast-read; ++ label = "bmc"; ++ spi-max-frequency = <20000000>; ++ spi-tx-bus-width = <4>; ++ spi-rx-bus-width = <4>; ++ ++ partitions { ++ compatible = "fixed-partitions"; ++ #address-cells = <1>; ++ #size-cells = <1>; ++ ++ u-boot@0 { ++ reg = <0x0 0x300000>; // 3MB ++ label = "u-boot"; ++ }; ++ ++ /* TODO: move to eMMC */ ++ u-boot-env@300000 { ++ reg = <0x300000 0x20000>; // 128KB ++ label = "u-boot-env"; ++ }; ++ ++ kernel@320000 { ++ reg = <0x320000 0x900000>; // 9MB ++ label = "kernel"; ++ }; ++ rofs@c20000 { ++ reg = <0xc20000 0x3000000>; // 48MB ++ label = "rofs"; ++ }; ++ rwfs@3c20000 { ++ reg = <0x3C20000 0x3E0000>; // ~4MB ++ label = "rwfs"; ++ }; ++ }; ++ }; ++ ++ flash@1 { ++ status = "okay"; ++ m25p,fast-read; ++ label = "fmc0:1"; ++ spi-max-frequency = <20000000>; ++ spi-tx-bus-width = <4>; ++ spi-rx-bus-width = <4>; ++ }; ++ ++ flash@2 { ++ status = "disabled"; ++ m25p,fast-read; ++ label = "fmc0:2"; ++ spi-max-frequency = <20000000>; ++ spi-tx-bus-width = <4>; ++ spi-rx-bus-width = <4>; ++ }; ++}; ++#endif ++ ++&rtc { ++ status = "okay"; ++}; ++ ++&uart1 { ++ status = "okay"; ++}; ++ ++&uart12 { ++ // BMC Debug Console ++ status = "okay"; ++}; ++ ++&mdio0 { ++ status = "disabled"; ++}; ++ ++&mdio1 { ++ status = "disabled"; ++}; ++ ++&mac0 { ++ status = "disabled"; ++}; ++ ++&mac1 { ++ status = "disabled"; ++}; ++ ++&sgmii { ++ status = "okay"; ++ #phy-cells = <0>; ++}; ++ ++&mdio2 { ++ status = "okay"; ++ reset-gpios = <&exp0 10 GPIO_ACTIVE_LOW>; ++ reset-delay-us = <30000>; ++ reset-post-delay-us = <30000>; ++ ++ ethphy2: ethernet-phy@0 { ++ compatible = "ethernet-phy-ieee802.3-c22"; ++ reg = <0>; ++ marvell,reg-init = <0 0x10 0xffff 0x3060>, ++ <3 0x10 0xF000 0x0117>, ++ <3 0x11 0x00C0 0x4405>; ++ }; ++}; ++ ++&mac2 { ++ status = "okay"; ++ phy-mode = "sgmii"; ++ phy-handle = <ðphy2>; ++ phys = <&sgmii>; ++ phy-names = "sgmii-phy"; ++}; ++ ++/* ++ * Enable port A as device (via the virtual hub) and port B as ++ * host by default on the eval board. This can be easily changed ++ * by replacing the override below with &ehci0 { ... } to enable ++ * host on both ports. ++ */ ++&vhuba0 { ++ status = "okay"; ++}; ++ ++/* USB PORT B - MCU and Power bank */ ++/* This is an assembly option */ ++&ehci1 { ++ status = "okay"; ++}; ++ ++&i2c0 { ++ status = "okay"; ++}; ++ ++/* I2C bus connecting BMC to CPU for IPMI */ ++/* Note : On Chameleon SPC6 on 2600 it will be i2c-0, on 2700 i2c-16 */ ++&i2c1 { ++ status = "okay"; ++ bus-frequency = <100000>; /* 100 KHz standard mode */ ++ /* BMC as I2C slave for IPMB */ ++ ipmb@20 { ++ compatible = "ipmb-dev"; ++ reg = <0x20>; /* BMC slave address (7-bit) */ ++ slave-dev; /* This is a slave device */ ++ }; ++}; ++ ++ ++&i2c2 { ++ status = "disabled"; ++}; ++ ++&i2c3 { ++ status = "okay"; ++ bus-frequency = <100000>; /* 100 KHz */ ++ aspeed,hw-timeout-ms = <1000>; ++ multi-master; ++}; ++ ++&i2c4 { ++ status = "okay"; ++ ++ ext_rtc: rtc@6f { ++ compatible = "nxp,pcf85053a"; ++ reg = <0x6f>; ++ }; ++}; ++ ++&i2c5 { ++ status = "okay"; ++ exp0: pcal6524@20 { ++ status = "okay"; ++ compatible = "nxp,pcal6524"; ++ reg = <0x20>; ++ gpio-controller; ++ #gpio-cells = <2>; ++ #address-cells = <1>; ++ #size-cells = <0>; ++ reset-gpios = <&gpio1 ASPEED_GPIO(S, 0) GPIO_ACTIVE_LOW>; /*GPIO_BMC_GPIO_EXP_RESET_L*/ ++ ++ gpio-line-names = ++ "RSRVD_0", "GP_EROT_RST_IN_L", ++ "GP_3V3_BMC_PHY_PG", "GP_BMC_PWR_CYCLE_3V3_MASK_L", ++ "GP_BMC_SPI_BUF_OE_L", "GP_BMC_SPI_TPM_BUF_OE_L", ++ "SE_CPLD_EROT_BOOT_COMPLETE", "GP_BMC_WP_CTRL_L", ++ "GP_BMC_REC_SPI_MUX1_SEL", "RSRVD_1", ++ "S_BMC_GPIO_PHY_RST_L", "GP_JTAG_BMC_CPLD_BURN_EN_L", ++ "GP_PWR_BANK_PRESENT_L", "RSRVD_2", ++ "RSRVD_3", "RSRVD_4", ++ "RSRVD_5", "RSRVD_6", ++ "RSRVD_7", "GP_PROD_CS_FLASH0_EN", ++ "RSRVD_8", "GP_PROD_CS_FLASH1_EN", ++ "RSRVD_9", "RSRVD_10"; ++ /* ++ * Enable the SPI/TPM level shifters at gpiochip registration, ++ * i.e. during this node's probe -- before tpm@1 (ordered after ++ * us via the buf-en-link-gpios phandle above) touches ++ * the bus. ++ * ++ * POLARITY: these nets are active-low (..._OE_L), so physical low ++ * enables the buffer. In a gpio-hog, output-low/output-high are ++ * LOGICAL values and are inverted by GPIO_ACTIVE_LOW. Therefore ++ * "output-high" = logical 1 = physical low = buffer ENABLED. ++ * Using "output-low" here would drive the pin high and DISABLE ++ * the buffers -- the opposite of what we want. ++ */ ++ spi-buf-oe { ++ gpio-hog; ++ gpios = <4 GPIO_ACTIVE_LOW>; /* GP_BMC_SPI_BUF_OE_L */ ++ output-high; /* -> physical low -> enabled */ ++ }; ++ ++ spi-tpm-buf-oe { ++ gpio-hog; ++ gpios = <5 GPIO_ACTIVE_LOW>; /* GP_BMC_SPI_TPM_BUF_OE_L */ ++ output-high; /* -> physical low -> enabled */ ++ }; ++ }; ++}; ++ ++&i2c6 { ++ status = "okay"; ++}; ++ ++&i2c7 { ++ status = "okay"; ++ clock-frequency = <400000>; ++ i2c-tck-thddat-config = <0x0099EC00>; ++}; ++ ++&i2c8 { ++ status = "okay"; ++ clock-frequency = <400000>; ++ i2c-tck-thddat-config = <0x0099EC00>; ++}; ++ ++/* Used by I3C */ ++&i2c9 { ++ status = "disabled"; ++}; ++ ++&i2c10 { ++ status = "okay"; ++ clock-frequency = <400000>; ++ i2c-tck-thddat-config = <0x0099EC00>; ++}; ++ ++&i2c11 { ++ status = "disabled"; ++}; ++ ++&i2c12 { ++ status = "disabled"; ++}; ++ ++/* CPU master to BMC slave recovery interface */ ++/* Note: On Chameleon SPC6 multi - on 2600 it will be i2c-9, on 2700 - i2c-13 */ ++&i2c13 { ++ status = "okay"; ++ clock-frequency = <400000>; ++ i2c-tck-thddat-config = <0x0099EC00>; ++ /* ++ * BMC slave device (slave-24c02 at 0x42) is to be instantiated via ++ * bmc-i2c-slave-setup.service at boot. This approach ensures the ++ * I2C_CLIENT_SLAVE flag (0x1042) is properly set, which is required ++ * for the Aspeed I2C controller to register the hardware slave address. ++ * ++ * DTS-based instantiation doesn't set the I2C_CLIENT_SLAVE flag, causing: ++ * "i2c_slave_register: client slave flag not set. You might see address collisions" ++ * and preventing proper slave address registration in hardware. ++ * ++ * Note: "multi-master" is not required. ++ * ++ * Manual instantiation: echo slave-24c02 0x1042 > /sys/bus/i2c/devices/i2c-9/new_device ++ * Consider to provide service: /etc/systemd/system/bmc-i2c-slave-setup.service ++ */ ++}; ++ ++&i2c14 { ++ status = "okay"; ++ cpld: nvsw_bmc_hid189@31 { ++ compatible = "nvidia,hid189"; ++ reg = <0x31>; ++ interrupt-parent = <&gpio1>; ++ interrupts = ; ++ }; ++}; ++ ++&i2c15 { ++ status = "okay"; ++}; ++ ++&pcie0 { ++ status = "okay"; ++}; ++ ++&mctp0 { ++ status = "okay"; ++}; ++ ++&jtag0 { ++ status = "okay"; ++}; ++ ++&jtag1 { ++ status = "okay"; ++}; ++ ++// Enable emmc ++&emmc_controller { ++ status = "okay"; ++ mmc-hs200-1_8v; ++}; ++ ++&emmc { ++ status = "okay"; ++ non-removable; ++ bus-width = <4>; ++ max-frequency = <52000000>; ++}; ++ ++/* eSPI Controller - Communication with host CPU */ ++&espi0 { ++ status = "okay"; ++ perif-dma-mode; ++ perif-mmbi-enable; ++ perif-mmbi-src-addr = <0x0 0xa8000000>; ++ perif-mmbi-tgt-memory = <&espi0_mmbi_memory>; ++ perif-mmbi-instance-num = <0x1>; ++ perif-mcyc-enable; ++ perif-mcyc-src-addr = <0x0 0x98000000>; ++ perif-mcyc-size = <0x0 0x10000>; ++ oob-dma-mode; ++ flash-dma-mode; ++}; ++ ++// TODO: FIXME probably N/A for in 2700 ++#if 0 ++&lpc_ctrl { ++ status = "okay"; ++ memory-region = <&espi_memory>; ++}; ++ ++&lpc_reset { ++ status = "okay"; ++}; ++#endif ++ ++/* ++ * LPC SNOOP for POST code capture (simpler alternative to PCC) ++ * ++ * SNOOP driver benefits: ++ * - Mainline Linux driver (well-tested, stable) ++ * - Two independent channels for monitoring multiple ports ++ * - Simple FIFO-based interface via /dev/aspeed-lpc-snoop0/1 ++ * ++ * Usage: ++ * modprobe aspeed-lpc-snoop ++ * hexdump -C /dev/aspeed-lpc-snoop0 # Monitor port 0x80 ++ * hexdump -C /dev/aspeed-lpc-snoop1 # Monitor port 0x81 (if configured) ++ */ ++&pcie_lpc0_snoop { ++ status = "okay"; ++ snoop-ports = <0x80>; /* Capture POST codes from port 0x80 */ ++ /* ++ * Optional: Add second port for dual-channel monitoring ++ * snoop-ports = <0x80>, <0x81>; ++ */ ++}; ++ ++/* ++ * PCC disabled - cannot run simultaneously with SNOOP ++ * (both use same hardware registers: SNPWADR, HICR5, HICR6) ++ */ ++&pcie_lpc0_pcc { ++ status = "disabled"; ++}; ++ ++&pcie_lpc0_kcs0 { ++ status = "okay"; ++ kcs-io-addr = <0x3a0>; ++ kcs-channel = <1>; ++}; ++ ++&pcie_lpc0_kcs1 { ++ status = "okay"; ++ kcs-io-addr = <0x3a8>; ++ kcs-channel = <2>; ++}; ++ ++&pcie_lpc0_kcs2 { ++ status = "okay"; ++ /* Primary IPMI interface */ ++ kcs-io-addr = <0x3a2>; ++ kcs-channel = <3>; ++}; ++ ++&pcie_lpc0_kcs3 { ++ status = "disabled"; ++}; ++ ++// TODO: FIXME ++#if 0 ++&pinctrl0 { ++ pinctrl-names = "default"; ++ pinctrl-0 = <&pinctrl_espi0_default>, ++ <&pinctrl_espialt_default>; pinctrl_salt0_default ???? ++}; ++#endif ++ ++/* ++ * I3C Controller for MCTP communication ++ * Note: pinctrl is already configured in aspeed-g7.dtsi base DTS ++ */ ++&i3c1 { ++ status = "okay"; ++ pinctrl-names = "default"; ++ i3c-scl-hz = <12500000>; ++ internal-pullup = <1>; ++ mctp-controller; ++}; +diff --git a/arch/arm64/boot/dts/aspeed/aspeed-nvidia-spc6-bmc.dts b/arch/arm64/boot/dts/aspeed/aspeed-nvidia-spc6-bmc.dts +index b16c766ec..3cdda09d7 100644 +--- a/arch/arm64/boot/dts/aspeed/aspeed-nvidia-spc6-bmc.dts ++++ b/arch/arm64/boot/dts/aspeed/aspeed-nvidia-spc6-bmc.dts +@@ -5,7 +5,7 @@ + #include + #include + #include +-#include "nvidia-ast27xx-sunda.dtsi" ++#include "nvidia-ast27xx-sunda-sonic.dtsi" + + #define PCIE0_EP 1 // 1: EP, 0: RC + +diff --git a/arch/arm64/boot/dts/aspeed/nvidia-ast27xx-irot-sonic.dtsi b/arch/arm64/boot/dts/aspeed/nvidia-ast27xx-irot-sonic.dtsi +new file mode 100644 +index 000000000..650a72359 +--- /dev/null ++++ b/arch/arm64/boot/dts/aspeed/nvidia-ast27xx-irot-sonic.dtsi +@@ -0,0 +1,14 @@ ++// SPDX-License-Identifier: GPL-2.0+ ++ ++/ { ++ mctpirot0 { ++ compatible = "nvidia,ast27xx,irot"; ++ mboxes = <&mbox0 0>; ++ mbox-names = "irot"; ++ status = "okay"; ++ }; ++}; ++ ++&mbox0 { ++ status = "okay"; ++}; +diff --git a/arch/arm64/boot/dts/aspeed/nvidia-ast27xx-sunda-sonic.dtsi b/arch/arm64/boot/dts/aspeed/nvidia-ast27xx-sunda-sonic.dtsi +new file mode 100644 +index 000000000..db26b5096 +--- /dev/null ++++ b/arch/arm64/boot/dts/aspeed/nvidia-ast27xx-sunda-sonic.dtsi +@@ -0,0 +1,8 @@ ++#include "nvidia-ast27xx-irot-sonic.dtsi" ++// OP-TEE vROT not used on SONiC BMC; same as meta nvidia-ast27xx-sunda.dtsi. ++//#include "nvidia-optee-vrot.dtsi" ++ ++// Enable OTP; matches OpenBMC AST2700 sund stack (DTS node + ast2700_otp.cfg). ++&otp { ++ status = "okay"; ++}; +-- +2.34.1 + diff --git a/patches-sonic/0010-net-ftgmac100-hard-reset-mac-for-sgmii.patch b/patches-sonic/0010-net-ftgmac100-hard-reset-mac-for-sgmii.patch new file mode 100644 index 000000000..1c79fadaa --- /dev/null +++ b/patches-sonic/0010-net-ftgmac100-hard-reset-mac-for-sgmii.patch @@ -0,0 +1,48 @@ +From: Sergejs Hatinecs +Date: Thu, 12 Jun 2026 12:00:00 +0300 +Subject: [PATCH 6.12 1/1] net: ftgmac100: hard-reset MAC for SGMII to clear RX stall + +Upstream-Status: Pending + +On AST2700 + Marvell 88E151x over SGMII, an "ifconfig down/up" (or a +partner-side link flap) intermittently leaves the MAC receive path wedged: +the link comes back up and reports resolved, TX works, but RX stays frozen +(no DHCP, no ping). The MDIO/serdes registers look identical to a healthy +link in this state, so the wedge is below the register layer. + +The only reliable recovery is "modprobe -r ftgmac100 / modprobe ftgmac100", +which re-runs probe and performs a full reset_control cycle of the MAC IP +block. The ndo_open/ftgmac100_reset() path only issues the soft MACCR reset, +which does not clear the wedged RX path. + +ftgmac100_reset_and_config_mac() already does the reset_control assert/ +deassert for RMII; extend it to SGMII so every MAC reset hard-resets the IP +block and clears the RX path, matching the modprobe-reload behaviour. With +this, repeated down/up cycles and partner link flaps recover reliably. + +Signed-off-by: Sergejs Hatinecs +--- + drivers/net/ethernet/faraday/ftgmac100.c | 12 +++++++++--- + 1 file changed, 9 insertions(+), 3 deletions(-) + +diff --git a/drivers/net/ethernet/faraday/ftgmac100.c b/drivers/net/ethernet/faraday/ftgmac100.c +index 8ef2b736f23e..a3534b467aa6 100644 +--- a/drivers/net/ethernet/faraday/ftgmac100.c ++++ b/drivers/net/ethernet/faraday/ftgmac100.c +@@ -157,8 +157,14 @@ static int ftgmac100_reset_and_config_mac(struct ftgmac100 *priv) + { + u32 maccr = 0; + +- /* RMII needs SCU reset to clear status */ +- if (priv->netdev->phydev->interface == PHY_INTERFACE_MODE_RMII) { ++ /* RMII needs SCU reset to clear status. SGMII (AST2700 + 88E151x) needs ++ * it too: after ifconfig down/up the soft MACCR reset alone leaves the ++ * MAC RX path wedged (link reads resolved but RX stays frozen). Only a ++ * full reset_control cycle clears it - this is why "modprobe -r/modprobe" ++ * (which hard-resets at probe) always recovers while down/up does not. ++ */ ++ if (priv->netdev->phydev->interface == PHY_INTERFACE_MODE_RMII || ++ priv->netdev->phydev->interface == PHY_INTERFACE_MODE_SGMII) { + int err; + + err = reset_control_assert(priv->rst); diff --git a/patches-sonic/0011-arm64-dts-aspeed-spc6-bmc-fix-sgmii-phy-names.patch b/patches-sonic/0011-arm64-dts-aspeed-spc6-bmc-fix-sgmii-phy-names.patch new file mode 100644 index 000000000..09219ec0a --- /dev/null +++ b/patches-sonic/0011-arm64-dts-aspeed-spc6-bmc-fix-sgmii-phy-names.patch @@ -0,0 +1,60 @@ +From: Sergejs Hatinecs +Date: Thu, 12 Jun 2026 12:30:00 +0300 +Subject: [PATCH 6.12 1/1] arm64: dts: aspeed: spc6-bmc: fix mac2 SGMII phy-names + +Upstream-Status: Pending + +ftgmac100 probes the optional SGMII serdes PHY with devm_phy_optional_get() +using the name "sgmii". mac2 in the SPC6 BMC device trees used +phy-names = "sgmii-phy", so the serdes PHY was never bound and SGMII +management traffic did not run through the expected path. + +Use phy-names = "sgmii" on mac2 in the A1, SONiC, and OpenBMC SPC6 BMC +DTS variants so the name matches the driver. + +Signed-off-by: Sergejs Hatinecs +--- + arch/arm64/boot/dts/aspeed/aspeed-nvidia-spc6-a1-bmc.dts | 2 +- + arch/arm64/boot/dts/aspeed/aspeed-nvidia-spc6-bmc-openbmc.dts | 2 +- + arch/arm64/boot/dts/aspeed/aspeed-nvidia-spc6-bmc.dts | 2 +- + 3 files changed, 3 insertions(+), 3 deletions(-) + +diff --git a/arch/arm64/boot/dts/aspeed/aspeed-nvidia-spc6-a1-bmc.dts b/arch/arm64/boot/dts/aspeed/aspeed-nvidia-spc6-a1-bmc.dts +index 810d50fe11ac..741bd588ec2a 100644 +--- a/arch/arm64/boot/dts/aspeed/aspeed-nvidia-spc6-a1-bmc.dts ++++ b/arch/arm64/boot/dts/aspeed/aspeed-nvidia-spc6-a1-bmc.dts +@@ -170,7 +170,7 @@ + phy-mode = "sgmii"; + phy-handle = <ðphy2>; + phys = <&sgmii>; +- phy-names = "sgmii-phy"; ++ phy-names = "sgmii"; + }; + + /* +diff --git a/arch/arm64/boot/dts/aspeed/aspeed-nvidia-spc6-bmc.dts b/arch/arm64/boot/dts/aspeed/aspeed-nvidia-spc6-bmc.dts +index 0817034b8658..e16559039ed5 100644 +--- a/arch/arm64/boot/dts/aspeed/aspeed-nvidia-spc6-bmc.dts ++++ b/arch/arm64/boot/dts/aspeed/aspeed-nvidia-spc6-bmc.dts +@@ -207,7 +207,7 @@ + phy-mode = "sgmii"; + phy-handle = <ðphy2>; + phys = <&sgmii>; +- phy-names = "sgmii-phy"; ++ phy-names = "sgmii"; + }; + + /* +diff --git a/arch/arm64/boot/dts/aspeed/aspeed-nvidia-spc6-bmc-openbmc.dts b/arch/arm64/boot/dts/aspeed/aspeed-nvidia-spc6-bmc-openbmc.dts +index cfe5743b90fe..bcdb5a463b42 100644 +--- a/arch/arm64/boot/dts/aspeed/aspeed-nvidia-spc6-bmc-openbmc.dts ++++ b/arch/arm64/boot/dts/aspeed/aspeed-nvidia-spc6-bmc-openbmc.dts +@@ -215,7 +215,7 @@ + phy-mode = "sgmii"; + phy-handle = <ðphy2>; + phys = <&sgmii>; +- phy-names = "sgmii-phy"; ++ phy-names = "sgmii"; + }; + + /* diff --git a/patches-sonic/0012-usb-ehci-Add-Aspeed-AST2700-support.patch b/patches-sonic/0012-usb-ehci-Add-Aspeed-AST2700-support.patch new file mode 100644 index 000000000..432f1a5be --- /dev/null +++ b/patches-sonic/0012-usb-ehci-Add-Aspeed-AST2700-support.patch @@ -0,0 +1,79 @@ +From: Ryan Chen +Date: Sun, 28 Sep 2025 03:24:07 +0000 +Subject: [PATCH 6.12 1/1] usb: ehci: Add Aspeed AST2700 support + +Upstream-Status: Backport [274f2232a94f6ca626d60288044e13d9a58c7612] + +Unlike earlier Aspeed SoCs (AST2400/2500/2600) which are limited to +32-bit DMA addressing, the EHCI controller in AST2700 supports 64-bit +DMA. Update the EHCI platform driver to make use of this capability by +selecting a 64-bit DMA mask when the "aspeed,ast2700-ehci" compatible +is present in device tree. + +Without this, ehci-platform probe of usb@12063000 (EHCI1, USB Port B) +on SPC6 AST2700 BMC fails with -ENOMEM when DTS enables &ehci1. + +Signed-off-by: Ryan Chen +Reviewed-by: Alan Stern +--- + drivers/usb/host/ehci-platform.c | 15 +++++++++++++-- + 1 file changed, 13 insertions(+), 2 deletions(-) + +diff --git a/drivers/usb/host/ehci-platform.c b/drivers/usb/host/ehci-platform.c +index cdf41886e8ca..dd6a93edf4de 100644 +--- a/drivers/usb/host/ehci-platform.c ++++ b/drivers/usb/host/ehci-platform.c +@@ -27,6 +27,7 @@ + #include + #include + #include ++#include + #include + #include + #include +@@ -238,9 +239,11 @@ static int ehci_platform_probe(struct platform_device *dev) + struct usb_hcd *hcd; + struct resource *res_mem; + struct usb_ehci_pdata *pdata = dev_get_platdata(&dev->dev); ++ const struct of_device_id *match; + struct ehci_platform_priv *priv; + struct ehci_hcd *ehci; + int err, irq, clk = 0; ++ bool dma_mask_64; + + if (usb_disabled()) + return -ENODEV; +@@ -252,8 +255,13 @@ static int ehci_platform_probe(struct platform_device *dev) + if (!pdata) + pdata = &ehci_platform_defaults; + ++ dma_mask_64 = pdata->dma_mask_64; ++ match = of_match_device(dev->dev.driver->of_match_table, &dev->dev); ++ if (match && match->data) ++ dma_mask_64 = true; ++ + err = dma_coerce_mask_and_coherent(&dev->dev, +- pdata->dma_mask_64 ? DMA_BIT_MASK(64) : DMA_BIT_MASK(32)); ++ dma_mask_64 ? DMA_BIT_MASK(64) : DMA_BIT_MASK(32)); + if (err) { + dev_err(&dev->dev, "Error: DMA mask configuration failed\n"); + return err; +@@ -297,7 +305,9 @@ static int ehci_platform_probe(struct platform_device *dev) + if (of_device_is_compatible(dev->dev.of_node, + "aspeed,ast2500-ehci") || + of_device_is_compatible(dev->dev.of_node, +- "aspeed,ast2600-ehci")) ++ "aspeed,ast2600-ehci") || ++ of_device_is_compatible(dev->dev.of_node, ++ "aspeed,ast2700-ehci")) + ehci->is_aspeed = 1; + + if (soc_device_match(quirk_poll_match)) +@@ -484,6 +494,7 @@ static const struct of_device_id vt8500_ehci_ids[] = { + { .compatible = "wm,prizm-ehci", }, + { .compatible = "generic-ehci", }, + { .compatible = "cavium,octeon-6335-ehci", }, ++ { .compatible = "aspeed,ast2700-ehci", .data = (void *)1 }, + {} + }; + MODULE_DEVICE_TABLE(of, vt8500_ehci_ids); diff --git a/patches-sonic/0046-platform-mellanox-nvsw-bmc-Add-system-control-and-mo.patch b/patches-sonic/0046-platform-mellanox-nvsw-bmc-Add-system-control-and-mo.patch index 3f3387824..aa18500ea 100644 --- a/patches-sonic/0046-platform-mellanox-nvsw-bmc-Add-system-control-and-mo.patch +++ b/patches-sonic/0046-platform-mellanox-nvsw-bmc-Add-system-control-and-mo.patch @@ -1,4 +1,4 @@ -From 7d0f9191f8e282b28411b21fd33d166c4efafc68 Mon Sep 17 00:00:00 2001 +From 79d11ae9b4611ac726289d9a27f4ac065c6a0efc Mon Sep 17 00:00:00 2001 From: Vadim Pasternak Date: Thu, 4 Jul 2024 23:50:27 +0300 Subject: [PATCH] platform/mellanox: nvsw-bmc: Add system control and @@ -16,18 +16,20 @@ This control includes: - LED control. - FAN control. +Upstream-Status: Pending + Signed-off-by: Vadim Pasternak --- .../devicetree/bindings/trivial-devices.yaml | 5 + drivers/platform/mellanox/Kconfig | 38 + drivers/platform/mellanox/Makefile | 4 + - drivers/platform/mellanox/nvsw-bmc-hid162.c | 3310 +++++++++++++++++ - drivers/platform/mellanox/nvsw-core.c | 693 ++++ - drivers/platform/mellanox/nvsw-host-l1.c | 770 ++++ - drivers/platform/mellanox/nvsw-host-spc5.c | 943 +++++ - drivers/platform/mellanox/nvsw-host-spc6.c | 852 +++++ - drivers/platform/mellanox/nvsw.h | 297 ++ - 9 files changed, 6912 insertions(+) + drivers/platform/mellanox/nvsw-bmc-hid162.c | 7304 +++++++++++++++++ + drivers/platform/mellanox/nvsw-core.c | 717 ++ + drivers/platform/mellanox/nvsw-host-l1.c | 735 ++ + drivers/platform/mellanox/nvsw-host-spc5.c | 943 +++ + drivers/platform/mellanox/nvsw-host-spc6.c | 852 ++ + drivers/platform/mellanox/nvsw.h | 318 + + 9 files changed, 10916 insertions(+) create mode 100644 drivers/platform/mellanox/nvsw-bmc-hid162.c create mode 100644 drivers/platform/mellanox/nvsw-core.c create mode 100644 drivers/platform/mellanox/nvsw-host-l1.c @@ -36,10 +38,10 @@ Signed-off-by: Vadim Pasternak create mode 100644 drivers/platform/mellanox/nvsw.h diff --git a/Documentation/devicetree/bindings/trivial-devices.yaml b/Documentation/devicetree/bindings/trivial-devices.yaml -index a190c5676..14568bd0f 100644 +index 9bf0fb17a..b6c6576f7 100644 --- a/Documentation/devicetree/bindings/trivial-devices.yaml +++ b/Documentation/devicetree/bindings/trivial-devices.yaml -@@ -307,6 +307,11 @@ properties: +@@ -305,6 +305,11 @@ properties: - national,lm85 # I2C ±0.33°C Accurate, 12-Bit + Sign Temperature Sensor and Thermal Window Comparator - national,lm92 @@ -52,10 +54,10 @@ index a190c5676..14568bd0f 100644 - nuvoton,w83773g # OKI ML86V7667 video decoder diff --git a/drivers/platform/mellanox/Kconfig b/drivers/platform/mellanox/Kconfig -index e3afbe62c..6cbda147a 100644 +index f7dfa0e78..53e5a04aa 100644 --- a/drivers/platform/mellanox/Kconfig +++ b/drivers/platform/mellanox/Kconfig -@@ -105,6 +105,44 @@ config MLXBF_PMC +@@ -79,6 +79,44 @@ config MLXBF_PMC to performance monitoring counters within various blocks in the Mellanox BlueField SoC via a sysfs interface. @@ -101,10 +103,10 @@ index e3afbe62c..6cbda147a 100644 tristate "Nvidia SN2201 platform driver support" depends on HWMON && I2C diff --git a/drivers/platform/mellanox/Makefile b/drivers/platform/mellanox/Makefile -index e86723b44..123edaa99 100644 +index 04703c041..5ad42435e 100644 --- a/drivers/platform/mellanox/Makefile +++ b/drivers/platform/mellanox/Makefile -@@ -11,4 +11,8 @@ obj-$(CONFIG_MLXREG_DPU) += mlxreg-dpu.o +@@ -9,4 +9,8 @@ obj-$(CONFIG_MLXBF_TMFIFO) += mlxbf-tmfifo.o obj-$(CONFIG_MLXREG_HOTPLUG) += mlxreg-hotplug.o obj-$(CONFIG_MLXREG_IO) += mlxreg-io.o obj-$(CONFIG_MLXREG_LC) += mlxreg-lc.o @@ -115,15 +117,15 @@ index e86723b44..123edaa99 100644 obj-$(CONFIG_NVSW_SN2201) += nvsw-sn2201.o diff --git a/drivers/platform/mellanox/nvsw-bmc-hid162.c b/drivers/platform/mellanox/nvsw-bmc-hid162.c new file mode 100644 -index 000000000..3c70e2979 +index 000000000..9f4c94f5d --- /dev/null +++ b/drivers/platform/mellanox/nvsw-bmc-hid162.c -@@ -0,0 +1,3310 @@ +@@ -0,0 +1,7304 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Nvidia BMC platform driver + * -+ * Copyright (C) 2025 Nvidia Technologies Ltd. ++ * Copyright (C) 2025-2026 Nvidia Technologies Ltd. + */ + +#include @@ -170,10 +172,39 @@ index 000000000..3c70e2979 + 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, +}; + -+static int nvsw_bmc_hid185_chan2[] = { ++static int nvsw_bmc_hid181_chan1[] = { ++ 0x01, 0x11, 0x21, 0x31, 0x09, 0x02, 0x03, 0x04, 0x14, 0x24, 0x34, 0x05, 0x06, 0x40, 0x41, ++ 0x42, 0x43, 0x0a, 0x0b, 0x1b, ++}; ++ ++static int nvsw_bmc_hid181_chan2[] = { ++ 0x00, 0x01, 0x02, 0x03, 0x04, ++}; ++ ++static int nvsw_bmc_hid189_chan1[] = { ++ 0x01, 0x02, 0X0a, 0x07, 0x09, 0x03, 0x04, 0x08, 0x0c, 0x0d, 0x015, ++ // 0x03, 0x05, 0x0C, 0x08, 0x02, 0x07, 0x0A, 0x0D, ++}; ++ ++static int nvsw_bmc_hid189_chan2[] = { ++ 0x10, 0x11, ++}; ++ ++static int nvsw_bmc_hid191_chan1[] = { ++ 0x03, 0x05, 0x0C, 0x08, 0x02, 0x07, 0x0A, 0x0D, ++}; ++ ++static int nvsw_bmc_hid191_chan2[] = { + 0x10, 0x11, +}; + ++static int nvsw_bmc_hid193_chan1[] = { ++ 0x01, 0x02, 0X0a, 0x07, 0x09, 0x03, 0x04, 0x08, 0x0c, 0x0d, 0x015, ++}; ++ ++static int nvsw_bmc_hid193_chan2[] = { ++ 0x10, 0x11, ++}; + +/* Mux configuration. */ +static struct i2c_mux_regmap_platform_data nvsw_bmc_hid162_mux_data[] = { @@ -213,18 +244,69 @@ index 000000000..3c70e2979 + }, +}; + -+static struct i2c_mux_regmap_platform_data nvsw_bmc_hid185_mux_data[] = { ++static struct i2c_mux_regmap_platform_data nvsw_bmc_hid181_mux_data[] = { ++ { ++ .parent = 12, ++ .chan_ids = nvsw_bmc_hid181_chan2, ++ .num_adaps = ARRAY_SIZE(nvsw_bmc_hid181_chan2), ++ .sel_reg_addr = NVSW_REG_MUX2_OFFSET, ++ .reg_size = 1, ++ }, + { + .parent = 14, -+ .chan_ids = nvsw_bmc_hid180_chan1, -+ .num_adaps = ARRAY_SIZE(nvsw_bmc_hid180_chan1), ++ .chan_ids = nvsw_bmc_hid181_chan1, ++ .num_adaps = ARRAY_SIZE(nvsw_bmc_hid181_chan1), ++ .sel_reg_addr = NVSW_REG_MUX1_OFFSET, ++ .reg_size = 1, ++ }, ++}; ++ ++static struct i2c_mux_regmap_platform_data nvsw_bmc_hid189_mux_data[] = { ++ { ++ .parent = 3, ++ .chan_ids = nvsw_bmc_hid189_chan1, ++ .num_adaps = ARRAY_SIZE(nvsw_bmc_hid189_chan1), ++ .sel_reg_addr = NVSW_REG_MUX1_OFFSET, ++ .reg_size = 1, ++ }, ++ { ++ .parent = 6, ++ .chan_ids = nvsw_bmc_hid189_chan2, ++ .num_adaps = ARRAY_SIZE(nvsw_bmc_hid189_chan2), ++ .sel_reg_addr = NVSW_REG_MUX2_OFFSET, ++ .reg_size = 1, ++ }, ++}; ++ ++static struct i2c_mux_regmap_platform_data nvsw_bmc_hid191_mux_data[] = { ++ { ++ .parent = 14, ++ .chan_ids = nvsw_bmc_hid191_chan1, ++ .num_adaps = ARRAY_SIZE(nvsw_bmc_hid191_chan1), ++ .sel_reg_addr = NVSW_REG_MUX1_OFFSET, ++ .reg_size = 1, ++ }, ++ { ++ .parent = 12, ++ .chan_ids = nvsw_bmc_hid191_chan2, ++ .num_adaps = ARRAY_SIZE(nvsw_bmc_hid191_chan2), ++ .sel_reg_addr = NVSW_REG_MUX2_OFFSET, ++ .reg_size = 1, ++ }, ++}; ++ ++static struct i2c_mux_regmap_platform_data nvsw_bmc_hid193_mux_data[] = { ++ { ++ .parent = 14, ++ .chan_ids = nvsw_bmc_hid193_chan1, ++ .num_adaps = ARRAY_SIZE(nvsw_bmc_hid193_chan1), + .sel_reg_addr = NVSW_REG_MUX1_OFFSET, + .reg_size = 1, + }, + { + .parent = 12, -+ .chan_ids = nvsw_bmc_hid185_chan2, -+ .num_adaps = ARRAY_SIZE(nvsw_bmc_hid185_chan2), ++ .chan_ids = nvsw_bmc_hid193_chan2, ++ .num_adaps = ARRAY_SIZE(nvsw_bmc_hid193_chan2), + .sel_reg_addr = NVSW_REG_MUX2_OFFSET, + .reg_size = 1, + }, @@ -290,6 +372,84 @@ index 000000000..3c70e2979 + }, +}; + ++static struct mlxreg_core_data nvsw_bmc_hid191_events_items_data[] = { ++ { ++ .label = "holder6_pg", ++ .reg = NVSW_REG_PWRB_OFFSET, ++ .mask = NVSW_PWR_BUTTON_MASK, ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "holder7_pg", ++ .reg = NVSW_REG_PWRB_OFFSET, ++ .mask = NVSW_AMB_TEMP_SENSE_MASK, ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "graceful_power_off_req", ++ .reg = NVSW_REG_PWRB_OFFSET, ++ .mask = NVSW_GRACEFUL_POWER_OFF_MASK, ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "cpu_power_off_ready", ++ .reg = NVSW_REG_PWRB_OFFSET, ++ .mask = NVSW_CPU_POWER_OFF_READY_MASK, ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "cpu_reset", ++ .reg = NVSW_REG_PWRB_OFFSET, ++ .mask = NVSW_CPU_RESET_MASK, ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "apml_smb_alert", ++ .reg = NVSW_REG_PWRB_OFFSET, ++ .mask = NVSW_APML_SMB_ALERT_MASK, ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "cpu_unexp_power_off", ++ .reg = NVSW_REG_PWRB_OFFSET, ++ .mask = NVSW_CPU_UNEXP_POWER_OFF_MASK, ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++}; ++ ++static struct mlxreg_core_data nvsw_bmc_hid193_events_items_data[] = { ++ { ++ .label = "graceful_power_off_req", ++ .reg = NVSW_REG_PWRB_OFFSET, ++ .mask = NVSW_GRACEFUL_POWER_OFF_MASK, ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "cpu_power_off_ready", ++ .reg = NVSW_REG_PWRB_OFFSET, ++ .mask = NVSW_CPU_POWER_OFF_READY_MASK, ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "cpu_reset", ++ .reg = NVSW_REG_PWRB_OFFSET, ++ .mask = NVSW_CPU_RESET_MASK, ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "apml_smb_alert", ++ .reg = NVSW_REG_PWRB_OFFSET, ++ .mask = NVSW_APML_SMB_ALERT_MASK, ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "cpu_unexp_power_off", ++ .reg = NVSW_REG_PWRB_OFFSET, ++ .mask = NVSW_CPU_UNEXP_POWER_OFF_MASK, ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++}; ++ +static struct mlxreg_core_data nvsw_bmc_hid162_asic1_items_data[] = { + { + .label = "asic1_health", @@ -797,6 +957,7 @@ index 000000000..3c70e2979 + NVSW_LOW_AGGR_MASK_ASIC2 | NVSW_LOW_AGGR_MASK_ASIC1, +}; + ++ +static struct mlxreg_core_data nvsw_bmc_hid180_pg1_events_items_data[] = { + { + .label = "smbus_alt_pwrconv_1", @@ -952,7 +1113,7 @@ index 000000000..3c70e2979 + +static struct mlxreg_core_data nvsw_bmc_hid180_pg4_events_items_data[] = { + { -+ .label = "smbus_alt_pwrconv_2", ++ .label = "mbus_alt_pwrconv_2", + .reg = NVSW_REG_PG3_OFFSET, + .mask = BIT(0), + .hpdev.nr = NVSW_NR_NONE, @@ -1159,39 +1320,21 @@ index 000000000..3c70e2979 + .hpdev.nr = NVSW_NR_NONE, + }, + { -+ .label = "5v_usb", ++ .label = "51v_usb", + .reg = NVSW_REG_BRD1_OFFSET, + .mask = NVSW_5V_USB_MASK, + .hpdev.nr = NVSW_NR_NONE, + }, + { -+ .label = "holder1_alarm", -+ .reg = NVSW_REG_BRD1_OFFSET, -+ .mask = BIT(3), -+ .hpdev.nr = NVSW_NR_NONE, -+ }, -+ { -+ .label = "holder2_alarm", -+ .reg = NVSW_REG_BRD1_OFFSET, -+ .mask = BIT(4), -+ .hpdev.nr = NVSW_NR_NONE, -+ }, -+ { -+ .label = "sgmii_pg", -+ .reg = NVSW_REG_BRD1_OFFSET, -+ .mask = BIT(5), -+ .hpdev.nr = NVSW_NR_NONE, -+ }, -+ { + .label = "ssd_pg", + .reg = NVSW_REG_BRD1_OFFSET, -+ .mask = BIT(6), ++ .mask = BIT(3), + .hpdev.nr = NVSW_NR_NONE, + }, + { + .label = "leakage_aggr", + .reg = NVSW_REG_BRD1_OFFSET, -+ .mask = BIT(7), ++ .mask = BIT(4), + .hpdev.nr = NVSW_NR_NONE, + }, +}; @@ -1217,7 +1360,7 @@ index 000000000..3c70e2979 + }, + { + .label = "thermtrip_alert", -+ .reg = NVSW_REG_BRD1_OFFSET, ++ .reg = NVSW_REG_HEALTH_OFFSET, + .mask = BIT(3), + .hpdev.nr = NVSW_NR_NONE, + }, @@ -1244,6 +1387,7 @@ index 000000000..3c70e2979 + }, +}; + ++ +static struct mlxreg_core_data nvsw_bmc_hid180_vr1_pwr_alert_items_data[] = { + { + .label = "asic1_vdd_pwr_alert", @@ -1423,6 +1567,7 @@ index 000000000..3c70e2979 + .count = ARRAY_SIZE(nvsw_bmc_hid180_asic_temp_items_data), + .inversed = 0, + .health = false, ++ .non_sticky = true, + }, + { + .data = nvsw_bmc_hid180_leakage_items_data, @@ -1437,7 +1582,7 @@ index 000000000..3c70e2979 + .data = nvsw_bmc_hid180_alarms_items_data, + .aggr_mask = NVSW_AGGR_MASK, + .reg = NVSW_REG_BRD1_OFFSET, -+ .mask = GENMASK(7, 0), ++ .mask = GENMASK(4, 0), + .count = ARRAY_SIZE(nvsw_bmc_hid180_alarms_items_data), + .inversed = 0, + .health = false, @@ -1511,140 +1656,189 @@ index 000000000..3c70e2979 + }, +}; + -+static -+struct mlxreg_core_hotplug_platform_data nvsw_bmc_hid180_hotplug = { ++static struct mlxreg_core_hotplug_platform_data nvsw_bmc_hid180_hotplug = { + .items = nvsw_bmc_hid180_hotplug_items_data, + .count = ARRAY_SIZE(nvsw_bmc_hid180_hotplug_items_data), + .cell = NVSW_REG_AGGR_OFFSET, + .mask = NVSW_AGGR_MASK | NVSW_AGGR_MASK_COMEX, + .cell_low = NVSW_REG_AGGRLO_OFFSET, + .mask_low = GENMASK(6, 0), -+ .deferred_nr = 5, +}; + -+static struct mlxreg_core_data nvsw_bmc_hid185_pg1_events_items_data[] = { ++static struct mlxreg_core_data nvsw_bmc_hid181_events_items_data[] = { ++ { ++ .label = "holder6_pg", ++ .reg = NVSW_REG_PWRB_OFFSET, ++ .mask = NVSW_PWR_BUTTON_MASK, ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "holder7_pg", ++ .reg = NVSW_REG_PWRB_OFFSET, ++ .mask = NVSW_AMB_TEMP_SENSE_MASK, ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "graceful_power_off_req", ++ .reg = NVSW_REG_PWRB_OFFSET, ++ .mask = NVSW_GRACEFUL_POWER_OFF_MASK, ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "cpu_power_off_ready", ++ .reg = NVSW_REG_PWRB_OFFSET, ++ .mask = NVSW_CPU_POWER_OFF_READY_MASK, ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "cpu_reset", ++ .reg = NVSW_REG_PWRB_OFFSET, ++ .mask = NVSW_CPU_RESET_MASK, ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "apml_smb_alert", ++ .reg = NVSW_REG_PWRB_OFFSET, ++ .mask = NVSW_APML_SMB_ALERT_MASK, ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "cpu_unexp_power_off", ++ .reg = NVSW_REG_PWRB_OFFSET, ++ .mask = NVSW_CPU_UNEXP_POWER_OFF_MASK, ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++}; ++ ++static struct mlxreg_core_data nvsw_bmc_hid181_asic1_pg_events_items_data[] = { + { -+ .label = "swb1_ni_smbus_alt", ++ .label = "asic1_vddscc_pg", + .reg = NVSW_REG_PG1_OFFSET, + .mask = BIT(0), + .hpdev.nr = NVSW_NR_NONE, + }, + { -+ .label = "swb1_clk_pg", ++ .label = "asic1_oe_pwr", + .reg = NVSW_REG_PG1_OFFSET, + .mask = BIT(1), + .hpdev.nr = NVSW_NR_NONE, + }, + { -+ .label = "asic1_vdd_pg", ++ .label = "asic1_clk_pg", + .reg = NVSW_REG_PG1_OFFSET, + .mask = BIT(2), + .hpdev.nr = NVSW_NR_NONE, + }, + { -+ .label = "asic1_dvdd_pl0_pg", ++ .label = "asic1_vcore_pg", + .reg = NVSW_REG_PG1_OFFSET, + .mask = BIT(3), + .hpdev.nr = NVSW_NR_NONE, + }, + { -+ .label = "asic1_dvdd_pl1_pg", ++ .label = "asic1_dvdd_pg", + .reg = NVSW_REG_PG1_OFFSET, + .mask = BIT(4), + .hpdev.nr = NVSW_NR_NONE, + }, + { -+ .label = "asic1_hvdd_avcc_pg", ++ .label = "asic1_hvdd_pg", + .reg = NVSW_REG_PG1_OFFSET, + .mask = BIT(5), + .hpdev.nr = NVSW_NR_NONE, + }, + { -+ .label = "swb1_1v8_vddio_pg", ++ .label = "asic1_1v8_vddio_pg", + .reg = NVSW_REG_PG1_OFFSET, + .mask = BIT(6), + .hpdev.nr = NVSW_NR_NONE, + }, + { -+ .label = "swb1_vdrv_pg", ++ .label = "asic1_vdrv", + .reg = NVSW_REG_PG1_OFFSET, + .mask = BIT(7), + .hpdev.nr = NVSW_NR_NONE, + }, +}; + -+static struct mlxreg_core_data nvsw_bmc_hid185_pg3_events_items_data[] = { ++static struct mlxreg_core_data nvsw_bmc_hid181_asic2_pg_events_items_data[] = { + { -+ .label = "swb1_smbus_alt_hotswap", -+ .reg = NVSW_REG_BRD4_OFFSET, ++ .label = "asic2_vddscc_pg", ++ .reg = NVSW_REG_PG2_OFFSET, + .mask = BIT(0), + .hpdev.nr = NVSW_NR_NONE, + }, + { -+ .label = "swb1_3v3_pb_pg", -+ .reg = NVSW_REG_BRD4_OFFSET, ++ .label = "aisc2_oe_pwr", ++ .reg = NVSW_REG_PG2_OFFSET, + .mask = BIT(1), + .hpdev.nr = NVSW_NR_NONE, + }, + { -+ .label = "asic2_vdd_pg", -+ .reg = NVSW_REG_BRD4_OFFSET, ++ .label = "asic2_clk_pg", ++ .reg = NVSW_REG_PG2_OFFSET, + .mask = BIT(2), + .hpdev.nr = NVSW_NR_NONE, + }, + { -+ .label = "asic2_dvdd_pl0_pg", -+ .reg = NVSW_REG_BRD4_OFFSET, ++ .label = "asic2_vcore_pg", ++ .reg = NVSW_REG_PG2_OFFSET, + .mask = BIT(3), + .hpdev.nr = NVSW_NR_NONE, + }, + { -+ .label = "asic2_dvdd_pl1_pg", -+ .reg = NVSW_REG_BRD4_OFFSET, ++ .label = "asic2_dvdd_pg", ++ .reg = NVSW_REG_PG2_OFFSET, + .mask = BIT(4), + .hpdev.nr = NVSW_NR_NONE, + }, + { + .label = "asic2_hvdd_pg", -+ .reg = NVSW_REG_BRD4_OFFSET, ++ .reg = NVSW_REG_PG2_OFFSET, + .mask = BIT(5), + .hpdev.nr = NVSW_NR_NONE, + }, + { -+ .label = "swb1_1v8_cpld_pg", -+ .reg = NVSW_REG_BRD4_OFFSET, ++ .label = "asic2_1v8_vddio_pg", ++ .reg = NVSW_REG_PG2_OFFSET, + .mask = BIT(6), + .hpdev.nr = NVSW_NR_NONE, + }, ++ { ++ .label = "asic2_vdrv", ++ .reg = NVSW_REG_PG2_OFFSET, ++ .mask = BIT(7), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, +}; + -+static struct mlxreg_core_data nvsw_bmc_hid185_pg4_events_items_data[] = { ++static struct mlxreg_core_data nvsw_bmc_hid181_asic3_pg_events_items_data[] = { + { -+ .label = "swb2_ni_smbus_alt", ++ .label = "asic3_vddscc_pg", + .reg = NVSW_REG_PG3_OFFSET, + .mask = BIT(0), + .hpdev.nr = NVSW_NR_NONE, + }, + { -+ .label = "swb2_clk_pg", ++ .label = "aisc3_oe_pwr", + .reg = NVSW_REG_PG3_OFFSET, + .mask = BIT(1), + .hpdev.nr = NVSW_NR_NONE, + }, + { -+ .label = "asic3_vdd_pg", ++ .label = "asic3_clk_pg", + .reg = NVSW_REG_PG3_OFFSET, + .mask = BIT(2), + .hpdev.nr = NVSW_NR_NONE, + }, + { -+ .label = "asic3_dvdd_pl0_pg", ++ .label = "asic3_vcore_pg", + .reg = NVSW_REG_PG3_OFFSET, + .mask = BIT(3), + .hpdev.nr = NVSW_NR_NONE, + }, + { -+ .label = "asic3_dvdd_pl1_pg", ++ .label = "asic3_dvdd_pg", + .reg = NVSW_REG_PG3_OFFSET, + .mask = BIT(4), + .hpdev.nr = NVSW_NR_NONE, @@ -1656,46 +1850,46 @@ index 000000000..3c70e2979 + .hpdev.nr = NVSW_NR_NONE, + }, + { -+ .label = "swb2_1v8_vddio_pg", ++ .label = "asic3_1v8_vddio_pg", + .reg = NVSW_REG_PG3_OFFSET, + .mask = BIT(6), + .hpdev.nr = NVSW_NR_NONE, + }, + { -+ .label = "swb2_vdrv_pg", ++ .label = "asic3_vdrv", + .reg = NVSW_REG_PG3_OFFSET, + .mask = BIT(7), + .hpdev.nr = NVSW_NR_NONE, + }, +}; + -+static struct mlxreg_core_data nvsw_bmc_hid185_pg5_events_items_data[] = { ++static struct mlxreg_core_data nvsw_bmc_hid181_asic4_pg_events_items_data[] = { + { -+ .label = "swb2_smbus_alt_hotswap", ++ .label = "asic4_vddscc_pg", + .reg = NVSW_REG_PG4_OFFSET, + .mask = BIT(0), + .hpdev.nr = NVSW_NR_NONE, + }, + { -+ .label = "swb2_3v3_pb_pg", ++ .label = "aisc4_oe_pwr", + .reg = NVSW_REG_PG4_OFFSET, + .mask = BIT(1), + .hpdev.nr = NVSW_NR_NONE, + }, + { -+ .label = "asic4_vdd_pg", ++ .label = "asic4_clk_pg", + .reg = NVSW_REG_PG4_OFFSET, + .mask = BIT(2), + .hpdev.nr = NVSW_NR_NONE, + }, + { -+ .label = "asic4_dvdd_pl0_pg", ++ .label = "asic4_vcore_pg", + .reg = NVSW_REG_PG4_OFFSET, + .mask = BIT(3), + .hpdev.nr = NVSW_NR_NONE, + }, + { -+ .label = "asic4_dvdd_pl1_pg", ++ .label = "asic4_dvdd_pg", + .reg = NVSW_REG_PG4_OFFSET, + .mask = BIT(4), + .hpdev.nr = NVSW_NR_NONE, @@ -1707,182 +1901,3538 @@ index 000000000..3c70e2979 + .hpdev.nr = NVSW_NR_NONE, + }, + { -+ .label = "swb2_1v8_cpld_pg", ++ .label = "asic4_1v8_vddio_pg", + .reg = NVSW_REG_PG4_OFFSET, + .mask = BIT(6), + .hpdev.nr = NVSW_NR_NONE, + }, ++ { ++ .label = "asic4_vdrv", ++ .reg = NVSW_REG_PG4_OFFSET, ++ .mask = BIT(7), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, +}; + -+static struct mlxreg_core_item nvsw_bmc_hid185_hotplug_items_data[] = { ++static struct mlxreg_core_data nvsw_bmc_hid181_brd2_events_items_data[] = { + { -+ .data = nvsw_bmc_hid185_pg1_events_items_data, -+ .aggr_mask = NVSW_AGGR_MASK, -+ .reg = NVSW_REG_PG1_OFFSET, -+ .mask = GENMASK(7, 0), -+ .count = ARRAY_SIZE(nvsw_bmc_hid185_pg1_events_items_data), -+ .inversed = 1, -+ .health = false, -+ .non_sticky = true, ++ .label = "global_wp_disable", ++ .reg = NVSW_REG_BRD4_OFFSET, ++ .mask = BIT(0), ++ .hpdev.nr = NVSW_NR_NONE, + }, + { -+ .data = nvsw_bmc_hid180_pg2_events_items_data, -+ .aggr_mask = NVSW_AGGR_MASK_COMEX, -+ .reg = NVSW_REG_PG2_OFFSET, -+ .mask = GENMASK(7, 0), -+ .count = ARRAY_SIZE(nvsw_bmc_hid180_pg2_events_items_data), -+ .inversed = 1, -+ .health = false, -+ .non_sticky = true, ++ .label = "pch_hot", ++ .reg = NVSW_REG_BRD4_OFFSET, ++ .mask = BIT(1), ++ .hpdev.nr = NVSW_NR_NONE, + }, + { -+ .data = nvsw_bmc_hid185_pg3_events_items_data, -+ .aggr_mask = NVSW_AGGR_MASK, ++ .label = "holder8_pg", + .reg = NVSW_REG_BRD4_OFFSET, -+ .mask = GENMASK(7, 0), -+ .count = ARRAY_SIZE(nvsw_bmc_hid185_pg3_events_items_data), -+ .inversed = 1, -+ .health = false, -+ .non_sticky = true, ++ .mask = BIT(2), ++ .hpdev.nr = NVSW_NR_NONE, + }, + { -+ .data = nvsw_bmc_hid185_pg4_events_items_data, -+ .aggr_mask = NVSW_AGGR_MASK, -+ .reg = NVSW_REG_PG3_OFFSET, -+ .mask = NVSW_AGGR_MASK, -+ .count = ARRAY_SIZE(nvsw_bmc_hid185_pg4_events_items_data), -+ .inversed = 1, -+ .health = false, -+ .non_sticky = true, ++ .label = "dimm_ab", ++ .reg = NVSW_REG_BRD4_OFFSET, ++ .mask = BIT(3), ++ .hpdev.nr = NVSW_NR_NONE, + }, ++}; ++ ++static struct mlxreg_core_data nvsw_bmc_hid181_brd4_events_items_data[] = { + { -+ .data = nvsw_bmc_hid185_pg5_events_items_data, -+ .aggr_mask = NVSW_AGGR_MASK, -+ .reg = NVSW_REG_PG4_OFFSET, -+ .mask = GENMASK(7, 0), -+ .count = ARRAY_SIZE(nvsw_bmc_hid185_pg5_events_items_data), -+ .inversed = 1, -+ .health = false, -+ .non_sticky = true, ++ .label = "3v3s_fail", ++ .reg = NVSW_REG_BRD4_OFFSET, ++ .mask = BIT(0), ++ .hpdev.nr = NVSW_NR_NONE, + }, + { -+ .data = nvsw_bmc_hid180_asic_items_data, -+ .aggr_mask = NVSW_AGGR_MASK, ++ .label = "vccio_pg", ++ .reg = NVSW_REG_BRD4_OFFSET, ++ .mask = BIT(1), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "vccst_pg", ++ .reg = NVSW_REG_BRD4_OFFSET, ++ .mask = BIT(2), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "ddr_pg", ++ .reg = NVSW_REG_BRD4_OFFSET, ++ .mask = BIT(3), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "12v_pg", ++ .reg = NVSW_REG_BRD4_OFFSET, ++ .mask = BIT(4), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "3v3_pg", ++ .reg = NVSW_REG_BRD4_OFFSET, ++ .mask = BIT(5), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "1v05_pg", ++ .reg = NVSW_REG_BRD4_OFFSET, ++ .mask = BIT(6), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "vcc_core_pg", ++ .reg = NVSW_REG_BRD4_OFFSET, ++ .mask = BIT(7), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++}; ++ ++static struct mlxreg_core_data nvsw_bmc_hid181_asic_items_data[] = { ++ { ++ .label = "asic1_health", + .reg = NVSW_REG_ASIC1_HEALTH_OFFSET, -+ .mask = NVSW_ASICS_MASK, -+ .count = ARRAY_SIZE(nvsw_bmc_hid180_asic_items_data), -+ .inversed = 0, -+ .health = true, ++ .mask = NVSW_ASIC_MASK, ++ .hpdev.nr = NVSW_NR_NONE, + }, + { -+ .data = nvsw_bmc_hid180_asic_temp_items_data, -+ .aggr_mask = NVSW_AGGR_MASK, ++ .label = "asic2_health", ++ .reg = NVSW_REG_ASIC1_HEALTH_OFFSET, ++ .mask = NVSW_ASIC2_MASK, ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "asic3_health", ++ .reg = NVSW_REG_ASIC1_HEALTH_OFFSET, ++ .mask = NVSW_ASIC3_MASK, ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "asic4_health", ++ .reg = NVSW_REG_ASIC1_HEALTH_OFFSET, ++ .mask = NVSW_ASIC4_MASK, ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++}; ++ ++static struct mlxreg_core_data nvsw_bmc_hid181_asic_temp_items_data[] = { ++ { ++ .label = "asic1_temp_warn", + .reg = NVSW_REG_ASIC2_HEALTH_OFFSET, -+ .mask = NVSW_ASICS_MASK, -+ .count = ARRAY_SIZE(nvsw_bmc_hid180_asic_temp_items_data), -+ .inversed = 0, -+ .health = false, ++ .mask = BIT(0), ++ .hpdev.nr = NVSW_NR_NONE, + }, + { -+ .data = nvsw_bmc_hid180_leakage_items_data, -+ .aggr_mask = NVSW_AGGR_MASK, -+ .reg = NVSW_REG_LEAK_OFFSET, -+ .mask = GENMASK(1, 0), -+ .count = ARRAY_SIZE(nvsw_bmc_hid180_leakage_items_data), -+ .inversed = 0, -+ .health = false, ++ .label = "asic1_temp_shtdn", ++ .reg = NVSW_REG_ASIC2_HEALTH_OFFSET, ++ .mask = BIT(1), ++ .hpdev.nr = NVSW_NR_NONE, + }, + { -+ .data = nvsw_bmc_hid180_alarms_items_data, -+ .aggr_mask = NVSW_AGGR_MASK, -+ .reg = NVSW_REG_BRD1_OFFSET, -+ .mask = GENMASK(7, 0), -+ .count = ARRAY_SIZE(nvsw_bmc_hid180_alarms_items_data), -+ .inversed = 0, -+ .health = false, ++ .label = "asic2_temp_warn", ++ .reg = NVSW_REG_ASIC2_HEALTH_OFFSET, ++ .mask = BIT(2), ++ .hpdev.nr = NVSW_NR_NONE, + }, + { -+ .data = nvsw_bmc_hid180_alarms2_items_data, -+ .aggr_mask = NVSW_AGGR_MASK_COMEX, -+ .reg = NVSW_REG_HEALTH_OFFSET, -+ .mask = GENMASK(3, 0), -+ .count = ARRAY_SIZE(nvsw_bmc_hid180_alarms2_items_data), -+ .inversed = 0, -+ .health = false, ++ .label = "asic2_temp_shtdn", ++ .reg = NVSW_REG_ASIC2_HEALTH_OFFSET, ++ .mask = BIT(3), ++ .hpdev.nr = NVSW_NR_NONE, + }, + { -+ .data = nvsw_bmc_hid180_cpu_items_data, -+ .aggr_mask = NVSW_AGGR_MASK, -+ .reg = NVSW_REG_BRD2_OFFSET, -+ .mask = GENMASK(4, 2), -+ .count = ARRAY_SIZE(nvsw_bmc_hid180_cpu_items_data), -+ .inversed = 0, -+ .health = false, ++ .label = "asic3_temp_warn", ++ .reg = NVSW_REG_ASIC2_HEALTH_OFFSET, ++ .mask = BIT(4), ++ .hpdev.nr = NVSW_NR_NONE, + }, + { -+ .data = nvsw_bmc_hid180_vr1_pwr_alert_items_data, -+ .aggr_mask = NVSW_AGGR_MASK, -+ .reg = NVSW_REG_VR1_ALERT_OFFSET, -+ .mask = GENMASK(7, 0), -+ .count = ARRAY_SIZE(nvsw_bmc_hid180_vr1_pwr_alert_items_data), -+ .inversed = 0, -+ .health = false, ++ .label = "asic3_temp_shtdn", ++ .reg = NVSW_REG_ASIC2_HEALTH_OFFSET, ++ .mask = BIT(5), ++ .hpdev.nr = NVSW_NR_NONE, + }, + { -+ .data = nvsw_bmc_hid180_vr2_pwr_alert_items_data, -+ .aggr_mask = NVSW_AGGR_MASK, -+ .reg = NVSW_REG_VR2_ALERT_OFFSET, -+ .mask = GENMASK(7, 0), -+ .count = ARRAY_SIZE(nvsw_bmc_hid180_vr2_pwr_alert_items_data), -+ .inversed = 0, -+ .health = false, ++ .label = "asic4_temp_warn", ++ .reg = NVSW_REG_ASIC2_HEALTH_OFFSET, ++ .mask = BIT(6), ++ .hpdev.nr = NVSW_NR_NONE, + }, + { -+ .data = nvsw_bmc_hid180_erot_error_items_data, -+ .aggr_mask = NVSW_AGGR_MASK, -+ .reg = NVSW_REG_EROT_ERR_OFFSET, ++ .label = "asic4_temp_shtdn", ++ .reg = NVSW_REG_ASIC2_HEALTH_OFFSET, ++ .mask = BIT(7), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++}; ++ ++static struct mlxreg_core_data nvsw_bmc_hid181_leakage_items_data[] = { ++ { ++ .label = "leakage_csm1", ++ .reg = NVSW_REG_LEAK_OFFSET, + .mask = BIT(0), -+ .count = ARRAY_SIZE(nvsw_bmc_hid180_erot_error_items_data), -+ .inversed = 1, -+ .health = false, ++ .hpdev.nr = NVSW_NR_NONE, + }, + { -+ .data = nvsw_bmc_hid162_events_items_data, -+ .aggr_mask = NVSW_AGGR_MASK, -+ .reg = NVSW_REG_PWRB_OFFSET, -+ .mask = NVSW_PWR_BUTTON_MASK | NVSW_AMB_TEMP_SENSE_MASK | -+ NVSW_GRACEFUL_POWER_OFF_MASK | NVSW_CPU_POWER_OFF_READY_MASK | -+ NVSW_CPU_RESET_MASK | NVSW_APML_SMB_ALERT_MASK | -+ NVSW_CPU_UNEXP_POWER_OFF_MASK | NVSW_UID_PUSH_BUTTON_MASK, -+ .count = ARRAY_SIZE(nvsw_bmc_hid162_events_items_data), -+ .inversed = 1, -+ .health = false, -+ .non_sticky = true, ++ .label = "leakage_csm2", ++ .reg = NVSW_REG_LEAK_OFFSET, ++ .mask = BIT(1), ++ .hpdev.nr = NVSW_NR_NONE, + }, + { -+ .data = nvsw_bmc_hid162_cartridge_items_data, -+ .aggr_mask = NVSW_AGGR_MASK, -+ .reg = NVSW_REG_FRU1_OFFSET, -+ .mask = NVSW_REG_FRU1_MASK, -+ .count = ARRAY_SIZE(nvsw_bmc_hid162_cartridge_items_data), -+ .inversed = 0, -+ .health = false, ++ .label = "leakage_csm3", ++ .reg = NVSW_REG_LEAK_OFFSET, ++ .mask = BIT(2), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "leakage_csm4", ++ .reg = NVSW_REG_LEAK_OFFSET, ++ .mask = BIT(3), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "leakage_mgmt", ++ .reg = NVSW_REG_LEAK_OFFSET, ++ .mask = BIT(4), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++}; ++ ++static struct mlxreg_core_data nvsw_bmc_hid181_alarms_items_data[] = { ++ { ++ .label = "ssd_i2c_alert", ++ .reg = NVSW_REG_BRD1_OFFSET, ++ .mask = NVSW_SSD_I2C_ALERT_MASK, ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "wd_exp", ++ .reg = NVSW_REG_BRD1_OFFSET, ++ .mask = NVSW_WD_EXP_MASK, ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "5v_usb", ++ .reg = NVSW_REG_BRD1_OFFSET, ++ .mask = NVSW_5V_USB_MASK, ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "holder10_pg", ++ .reg = NVSW_REG_BRD1_OFFSET, ++ .mask = BIT(3), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "holder11_pg", ++ .reg = NVSW_REG_BRD1_OFFSET, ++ .mask = BIT(4), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "sgmii_phy", ++ .reg = NVSW_REG_BRD1_OFFSET, ++ .mask = BIT(5), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "ssd_pg", ++ .reg = NVSW_REG_BRD1_OFFSET, ++ .mask = BIT(6), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "leakage_aggr", ++ .reg = NVSW_REG_BRD1_OFFSET, ++ .mask = BIT(7), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++}; ++ ++static struct mlxreg_core_data nvsw_bmc_hid181_alarms2_items_data[] = { ++ { ++ .label = "comex_tps_upgrade_done", ++ .reg = NVSW_REG_HEALTH_OFFSET, ++ .mask = BIT(0), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "holder12_pg", ++ .reg = NVSW_REG_HEALTH_OFFSET, ++ .mask = BIT(1), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "holder13_pg", ++ .reg = NVSW_REG_HEALTH_OFFSET, ++ .mask = BIT(2), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "holder14_pg", ++ .reg = NVSW_REG_HEALTH_OFFSET, ++ .mask = BIT(3), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "holder15_pg", ++ .reg = NVSW_REG_HEALTH_OFFSET, ++ .mask = BIT(4), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "vr13_alert", ++ .reg = NVSW_REG_HEALTH_OFFSET, ++ .mask = BIT(5), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "therm_trip", ++ .reg = NVSW_REG_HEALTH_OFFSET, ++ .mask = BIT(6), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "soc_caterr", ++ .reg = NVSW_REG_HEALTH_OFFSET, ++ .mask = BIT(7), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++}; ++ ++static struct mlxreg_core_data nvsw_bmc_hid181_vr1_pwr_alert_items_data[] = { ++ { ++ .label = "asic1_vdd_pwr_alert", ++ .reg = NVSW_REG_VR1_ALERT_OFFSET, ++ .mask = BIT(0), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "asic1_hdvdd_vr_pwr_alert", ++ .reg = NVSW_REG_VR1_ALERT_OFFSET, ++ .mask = BIT(1), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "asic1_dvdd_vrs_pwr_alert", ++ .reg = NVSW_REG_VR1_ALERT_OFFSET, ++ .mask = BIT(2), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "asic1_vdd_vrs_pwr_alert", ++ .reg = NVSW_REG_VR1_ALERT_OFFSET, ++ .mask = BIT(3), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "asic2_vdd_pwr_alert", ++ .reg = NVSW_REG_VR1_ALERT_OFFSET, ++ .mask = BIT(4), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "asic2_hdvdd_vr_pwr_alert", ++ .reg = NVSW_REG_VR1_ALERT_OFFSET, ++ .mask = BIT(5), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "asic2_dvdd_vrs_pwr_alert", ++ .reg = NVSW_REG_VR1_ALERT_OFFSET, ++ .mask = BIT(6), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "asic2_vdd_vrs_pwr_alert", ++ .reg = NVSW_REG_VR1_ALERT_OFFSET, ++ .mask = BIT(7), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++}; ++ ++static struct mlxreg_core_data nvsw_bmc_hid181_vr2_pwr_alert_items_data[] = { ++ { ++ .label = "asic3_vdd_pwr_alert", ++ .reg = NVSW_REG_VR2_ALERT_OFFSET, ++ .mask = BIT(0), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "asic3_hdvdd_vr_pwr_alert", ++ .reg = NVSW_REG_VR2_ALERT_OFFSET, ++ .mask = BIT(1), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "asic3_dvdd_vrs_pwr_alert", ++ .reg = NVSW_REG_VR2_ALERT_OFFSET, ++ .mask = BIT(2), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "asic3_vdd_vrs_pwr_alert", ++ .reg = NVSW_REG_VR2_ALERT_OFFSET, ++ .mask = BIT(3), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "asic4_vdd_pwr_alert", ++ .reg = NVSW_REG_VR2_ALERT_OFFSET, ++ .mask = BIT(4), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "asic4_hdvdd_vr_pwr_alert", ++ .reg = NVSW_REG_VR2_ALERT_OFFSET, ++ .mask = BIT(5), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "asic4_dvdd_vrs_pwr_alert", ++ .reg = NVSW_REG_VR2_ALERT_OFFSET, ++ .mask = BIT(6), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "asic4_vdd_vrs_pwr_alert", ++ .reg = NVSW_REG_VR2_ALERT_OFFSET, ++ .mask = BIT(7), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++}; ++ ++static struct mlxreg_core_item nvsw_bmc_hid181_hotplug_items_data[] = { ++ { ++ .data = nvsw_bmc_hid181_asic1_pg_events_items_data, ++ .aggr_mask = NVSW_AGGR_MASK, ++ .reg = NVSW_REG_PG1_OFFSET, ++ .mask = GENMASK(7, 0), ++ .count = ARRAY_SIZE(nvsw_bmc_hid181_asic1_pg_events_items_data), ++ .inversed = 1, ++ .health = false, ++ .non_sticky = true, ++ }, ++ { ++ .data = nvsw_bmc_hid181_asic2_pg_events_items_data, ++ .aggr_mask = NVSW_AGGR_MASK, ++ .reg = NVSW_REG_PG2_OFFSET, ++ .mask = GENMASK(7, 0), ++ .count = ARRAY_SIZE(nvsw_bmc_hid181_asic2_pg_events_items_data), ++ .inversed = 1, ++ .health = false, ++ .non_sticky = true, ++ }, ++ { ++ .data = nvsw_bmc_hid181_asic3_pg_events_items_data, ++ .aggr_mask = NVSW_AGGR_MASK, ++ .reg = NVSW_REG_PG3_OFFSET, ++ .mask = GENMASK(7, 0), ++ .count = ARRAY_SIZE(nvsw_bmc_hid181_asic3_pg_events_items_data), ++ .inversed = 1, ++ .health = false, ++ .non_sticky = true, ++ }, ++ { ++ .data = nvsw_bmc_hid181_asic4_pg_events_items_data, ++ .aggr_mask = NVSW_AGGR_MASK, ++ .reg = NVSW_REG_PG4_OFFSET, ++ .mask = GENMASK(7, 0), ++ .count = ARRAY_SIZE(nvsw_bmc_hid181_asic4_pg_events_items_data), ++ .inversed = 1, ++ .health = false, ++ .non_sticky = true, ++ }, ++ { ++ .data = nvsw_bmc_hid181_brd2_events_items_data, ++ .aggr_mask = NVSW_AGGR_MASK, ++ .reg = NVSW_REG_BRD2_OFFSET, ++ .mask = GENMASK(3, 0), ++ .count = ARRAY_SIZE(nvsw_bmc_hid181_brd2_events_items_data), ++ .inversed = 0, ++ .health = false, ++ .non_sticky = true, ++ }, ++ { ++ .data = nvsw_bmc_hid181_brd4_events_items_data, ++ .aggr_mask = NVSW_AGGR_MASK, ++ .reg = NVSW_REG_BRD4_OFFSET, ++ .mask = GENMASK(7, 0), ++ .count = ARRAY_SIZE(nvsw_bmc_hid181_brd4_events_items_data), ++ .inversed = 1, ++ .health = false, ++ .non_sticky = true, ++ }, ++ { ++ .data = nvsw_bmc_hid181_asic_items_data, ++ .aggr_mask = NVSW_AGGR_MASK, ++ .reg = NVSW_REG_ASIC1_HEALTH_OFFSET, ++ .mask = NVSW_ASICS_MASK, ++ .count = ARRAY_SIZE(nvsw_bmc_hid181_asic_items_data), ++ .inversed = 0, ++ .health = true, ++ }, ++ { ++ .data = nvsw_bmc_hid181_asic_temp_items_data, ++ .aggr_mask = NVSW_AGGR_MASK, ++ .reg = NVSW_REG_ASIC2_HEALTH_OFFSET, ++ .mask = NVSW_ASICS_MASK, ++ .count = ARRAY_SIZE(nvsw_bmc_hid181_asic_temp_items_data), ++ .inversed = 0, ++ .health = false, ++ .non_sticky = true, ++ }, ++ { ++ .data = nvsw_bmc_hid181_leakage_items_data, ++ .aggr_mask = NVSW_AGGR_MASK, ++ .reg = NVSW_REG_LEAK_OFFSET, ++ .mask = GENMASK(4, 0), ++ .count = ARRAY_SIZE(nvsw_bmc_hid181_leakage_items_data), ++ .inversed = 0, ++ .health = false, ++ }, ++ { ++ .data = nvsw_bmc_hid181_alarms_items_data, ++ .aggr_mask = NVSW_AGGR_MASK, ++ .reg = NVSW_REG_BRD1_OFFSET, ++ .mask = GENMASK(7, 0), ++ .count = ARRAY_SIZE(nvsw_bmc_hid181_alarms_items_data), ++ .inversed = 0, ++ .health = false, ++ }, ++ { ++ .data = nvsw_bmc_hid181_alarms2_items_data, ++ .aggr_mask = NVSW_AGGR_MASK_COMEX, ++ .reg = NVSW_REG_HEALTH_OFFSET, ++ .mask = GENMASK(7, 0), ++ .count = ARRAY_SIZE(nvsw_bmc_hid181_alarms2_items_data), ++ .inversed = 0, ++ .health = false, ++ }, ++ { ++ .data = nvsw_bmc_hid181_vr1_pwr_alert_items_data, ++ .aggr_mask = NVSW_AGGR_MASK, ++ .reg = NVSW_REG_VR1_ALERT_OFFSET, ++ .mask = GENMASK(7, 0), ++ .count = ARRAY_SIZE(nvsw_bmc_hid181_vr1_pwr_alert_items_data), ++ .inversed = 0, ++ .health = false, ++ }, ++ { ++ .data = nvsw_bmc_hid181_vr2_pwr_alert_items_data, ++ .aggr_mask = NVSW_AGGR_MASK, ++ .reg = NVSW_REG_VR2_ALERT_OFFSET, ++ .mask = GENMASK(7, 0), ++ .count = ARRAY_SIZE(nvsw_bmc_hid181_vr2_pwr_alert_items_data), ++ .inversed = 0, ++ .health = false, ++ }, ++ { ++ .data = nvsw_bmc_hid181_events_items_data, ++ .aggr_mask = NVSW_AGGR_MASK, ++ .reg = NVSW_REG_PWRB_OFFSET, ++ .mask = NVSW_PWR_BUTTON_MASK | NVSW_AMB_TEMP_SENSE_MASK | ++ NVSW_GRACEFUL_POWER_OFF_MASK | NVSW_CPU_POWER_OFF_READY_MASK | ++ NVSW_CPU_RESET_MASK | NVSW_APML_SMB_ALERT_MASK | ++ NVSW_CPU_UNEXP_POWER_OFF_MASK, ++ .count = ARRAY_SIZE(nvsw_bmc_hid181_events_items_data), ++ .inversed = 1, ++ .health = false, ++ .non_sticky = true, ++ }, ++}; ++ ++static struct mlxreg_core_hotplug_platform_data nvsw_bmc_hid181_hotplug = { ++ .items = nvsw_bmc_hid181_hotplug_items_data, ++ .count = ARRAY_SIZE(nvsw_bmc_hid181_hotplug_items_data), ++ .cell = NVSW_REG_AGGR_OFFSET, ++ .mask = NVSW_AGGR_MASK | NVSW_AGGR_MASK_COMEX, ++ .cell_low = NVSW_REG_AGGRLO_OFFSET, ++ .mask_low = GENMASK(6, 0), ++}; ++ ++static struct mlxreg_core_data nvsw_bmc_hid191_pg1_events_items_data[] = { ++ { ++ .label = "asic1_hsc0_smb_alert", ++ .reg = NVSW_REG_PG1_OFFSET, ++ .mask = BIT(0), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "asic1_oes_pwr", ++ .reg = NVSW_REG_PG1_OFFSET, ++ .mask = BIT(1), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "asic1_gp_vdrv_5v_clk_pg", ++ .reg = NVSW_REG_PG1_OFFSET, ++ .mask = BIT(2), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "asic1_gp_vdd_pg", ++ .reg = NVSW_REG_PG1_OFFSET, ++ .mask = BIT(3), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "asic1_pl_dvdd_pg", ++ .reg = NVSW_REG_PG1_OFFSET, ++ .mask = BIT(4), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "asic1_pl_avdd_pg", ++ .reg = NVSW_REG_PG1_OFFSET, ++ .mask = BIT(5), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "asic1_vddhbid_pg", ++ .reg = NVSW_REG_PG1_OFFSET, ++ .mask = BIT(6), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "asic1_gp_1v8_vddio_pg", ++ .reg = NVSW_REG_PG1_OFFSET, ++ .mask = BIT(7), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++}; ++ ++static struct mlxreg_core_data nvsw_bmc_hid191_pg2_events_items_data[] = { ++ { ++ .label = "vdd_misc_pg", ++ .reg = NVSW_REG_PG2_OFFSET, ++ .mask = BIT(0), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "gp_3v3_bmc_pg", ++ .reg = NVSW_REG_PG2_OFFSET, ++ .mask = BIT(1), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "vdd_1v8_pg", ++ .reg = NVSW_REG_PG2_OFFSET, ++ .mask = BIT(2), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "ddr_pg", ++ .reg = NVSW_REG_PG2_OFFSET, ++ .mask = BIT(3), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "12v_pg", ++ .reg = NVSW_REG_PG2_OFFSET, ++ .mask = BIT(4), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "vdd_3v3_pg", ++ .reg = NVSW_REG_PG2_OFFSET, ++ .mask = BIT(5), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "vdd_alw_pwrgd", ++ .reg = NVSW_REG_PG2_OFFSET, ++ .mask = BIT(6), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "vddcr_pg", ++ .reg = NVSW_REG_PG2_OFFSET, ++ .mask = BIT(7), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++}; ++ ++static struct mlxreg_core_data nvsw_bmc_hid191_brd2_events_items_data[] = { ++ { ++ .label = "bmc_tpm_pirq", ++ .reg = NVSW_REG_BRD2_OFFSET, ++ .mask = BIT(0), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "smb_alert", ++ .reg = NVSW_REG_BRD2_OFFSET, ++ .mask = BIT(1), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "cpu_reset_deassert", ++ .reg = NVSW_REG_BRD2_OFFSET, ++ .mask = BIT(2), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "bios_started", ++ .reg = NVSW_REG_BRD2_OFFSET, ++ .mask = BIT(3), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "bios_boot_completed", ++ .reg = NVSW_REG_BRD2_OFFSET, ++ .mask = BIT(4), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++}; ++ ++static struct mlxreg_core_data nvsw_bmc_hid191_pg3_events_items_data[] = { ++ { ++ .label = "asic2_hsc0_smb_alert", ++ .reg = NVSW_REG_BRD4_OFFSET, ++ .mask = BIT(0), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "asic2_oes_pwr", ++ .reg = NVSW_REG_BRD4_OFFSET, ++ .mask = BIT(1), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "asic2_gp_vdrv_5v_clk_pg", ++ .reg = NVSW_REG_BRD4_OFFSET, ++ .mask = BIT(2), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "asic2_gp_vdd_pg", ++ .reg = NVSW_REG_BRD4_OFFSET, ++ .mask = BIT(3), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "asic2_pl_dvdd_pg", ++ .reg = NVSW_REG_BRD4_OFFSET, ++ .mask = BIT(4), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "asic2_pl_avdd_pg", ++ .reg = NVSW_REG_BRD4_OFFSET, ++ .mask = BIT(5), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "asic2_vddhbid_pg", ++ .reg = NVSW_REG_BRD4_OFFSET, ++ .mask = BIT(6), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "asic2_gp_1v8_vddio_pg", ++ .reg = NVSW_REG_BRD4_OFFSET, ++ .mask = BIT(7), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++}; ++ ++static struct mlxreg_core_data nvsw_bmc_hid191_pg4_events_items_data[] = { ++ { ++ .label = "asic3_hsc0_smb_alert", ++ .reg = NVSW_REG_PG3_OFFSET, ++ .mask = BIT(0), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "asic3_oes_pwr", ++ .reg = NVSW_REG_PG3_OFFSET, ++ .mask = BIT(1), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "asic3_gp_vdrv_5v_clk_pg", ++ .reg = NVSW_REG_PG3_OFFSET, ++ .mask = BIT(2), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "asic3_gp_vdd_pg", ++ .reg = NVSW_REG_PG3_OFFSET, ++ .mask = BIT(3), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "asic3_pl_dvdd_pg", ++ .reg = NVSW_REG_PG3_OFFSET, ++ .mask = BIT(4), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "asic3_pl_avdd_pg", ++ .reg = NVSW_REG_PG3_OFFSET, ++ .mask = BIT(5), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "asic3_vddhbid_pg", ++ .reg = NVSW_REG_PG3_OFFSET, ++ .mask = BIT(6), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "asic3_gp_1v8_vddio_pg", ++ .reg = NVSW_REG_PG3_OFFSET, ++ .mask = BIT(7), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++}; ++ ++static struct mlxreg_core_data nvsw_bmc_hid191_asic4_events_items_data[] = { ++ { ++ .label = "asic4_hsc0_smb_alert", ++ .reg = NVSW_REG_PG4_OFFSET, ++ .mask = BIT(0), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "asic4_oes_pwr", ++ .reg = NVSW_REG_PG4_OFFSET, ++ .mask = BIT(1), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "asic4_gp_vdrv_5v_clk_pg", ++ .reg = NVSW_REG_PG4_OFFSET, ++ .mask = BIT(2), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "asic4_gp_vdd_pg", ++ .reg = NVSW_REG_PG4_OFFSET, ++ .mask = BIT(3), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "asic4_pl_dvdd_pg", ++ .reg = NVSW_REG_PG4_OFFSET, ++ .mask = BIT(4), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "asic4_pl_avdd_pg", ++ .reg = NVSW_REG_PG4_OFFSET, ++ .mask = BIT(5), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "asic4_vddhbid_pg", ++ .reg = NVSW_REG_PG4_OFFSET, ++ .mask = BIT(6), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "asic4_gp_1v8_vddio_pg", ++ .reg = NVSW_REG_PG4_OFFSET, ++ .mask = BIT(7), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++}; ++ ++static struct mlxreg_core_data nvsw_bmc_hid191_asic_items_data[] = { ++ { ++ .label = "asic1_health", ++ .reg = NVSW_REG_ASIC1_HEALTH_OFFSET, ++ .mask = NVSW_ASIC_MASK, ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "asic2_health", ++ .reg = NVSW_REG_ASIC1_HEALTH_OFFSET, ++ .mask = NVSW_ASIC2_MASK, ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "asic3_health", ++ .reg = NVSW_REG_ASIC1_HEALTH_OFFSET, ++ .mask = NVSW_ASIC3_MASK, ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "asic4_health", ++ .reg = NVSW_REG_ASIC1_HEALTH_OFFSET, ++ .mask = NVSW_ASIC4_MASK, ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++}; ++ ++static struct mlxreg_core_data nvsw_bmc_hid191_asic_temp_items_data[] = { ++ { ++ .label = "asic1_temp_warn", ++ .reg = NVSW_REG_ASIC2_HEALTH_OFFSET, ++ .mask = BIT(0), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "asic1_temp_shtdn", ++ .reg = NVSW_REG_ASIC2_HEALTH_OFFSET, ++ .mask = BIT(1), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "asic2_temp_warn", ++ .reg = NVSW_REG_ASIC2_HEALTH_OFFSET, ++ .mask = BIT(2), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "asic2_temp_shtdn", ++ .reg = NVSW_REG_ASIC2_HEALTH_OFFSET, ++ .mask = BIT(3), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "asic3_temp_warn", ++ .reg = NVSW_REG_ASIC2_HEALTH_OFFSET, ++ .mask = BIT(4), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "asic3_temp_shtdn", ++ .reg = NVSW_REG_ASIC2_HEALTH_OFFSET, ++ .mask = BIT(5), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "asic4_temp_warn", ++ .reg = NVSW_REG_ASIC2_HEALTH_OFFSET, ++ .mask = BIT(6), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "asic4_temp_shtdn", ++ .reg = NVSW_REG_ASIC2_HEALTH_OFFSET, ++ .mask = BIT(7), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++}; ++ ++static struct mlxreg_core_data nvsw_bmc_hid191_vr1_pwr_alert_items_data[] = { ++ { ++ .label = "asic1_vdd_txx_alert", ++ .reg = NVSW_REG_VR1_ALERT_OFFSET, ++ .mask = BIT(0), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "asic1_dvdd_txx_alert", ++ .reg = NVSW_REG_VR1_ALERT_OFFSET, ++ .mask = BIT(1), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "asic1_avdd_txx_alert", ++ .reg = NVSW_REG_VR1_ALERT_OFFSET, ++ .mask = BIT(2), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "asic1_hvdd_vddhbid_txx_alert", ++ .reg = NVSW_REG_VR1_ALERT_OFFSET, ++ .mask = BIT(3), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "asic2_vdd_txx_alert", ++ .reg = NVSW_REG_VR1_ALERT_OFFSET, ++ .mask = BIT(4), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "asic2_dvdd_txx_alert", ++ .reg = NVSW_REG_VR1_ALERT_OFFSET, ++ .mask = BIT(5), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "asic2_avdd_txx_alert", ++ .reg = NVSW_REG_VR1_ALERT_OFFSET, ++ .mask = BIT(6), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "asic2_hvdd_vddhbid_txx_alert", ++ .reg = NVSW_REG_VR1_ALERT_OFFSET, ++ .mask = BIT(7), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++}; ++ ++static struct mlxreg_core_data nvsw_bmc_hid191_vr2_pwr_alert_items_data[] = { ++ { ++ .label = "asic3_vdd_txx_alert", ++ .reg = NVSW_REG_VR2_ALERT_OFFSET, ++ .mask = BIT(0), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "asic3_dvdd_txx_alert", ++ .reg = NVSW_REG_VR2_ALERT_OFFSET, ++ .mask = BIT(1), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "asic3_avdd_txx_alert", ++ .reg = NVSW_REG_VR2_ALERT_OFFSET, ++ .mask = BIT(2), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "asic3_hvdd_vddhbid_txx_alert", ++ .reg = NVSW_REG_VR2_ALERT_OFFSET, ++ .mask = BIT(3), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "asic4_vdd_txx_alert", ++ .reg = NVSW_REG_VR2_ALERT_OFFSET, ++ .mask = BIT(4), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "asic4_dvdd_txx_alert", ++ .reg = NVSW_REG_VR2_ALERT_OFFSET, ++ .mask = BIT(5), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "asic4_avdd_txx_alert", ++ .reg = NVSW_REG_VR2_ALERT_OFFSET, ++ .mask = BIT(6), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "asic4_hvdd_vddhbid_txx_alert", ++ .reg = NVSW_REG_VR2_ALERT_OFFSET, ++ .mask = BIT(7), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++}; ++ ++static struct mlxreg_core_data nvsw_bmc_hid191_leakage_items_data[] = { ++ { ++ .label = "leakage_mgmt", ++ .reg = NVSW_REG_LEAK_OFFSET, ++ .mask = BIT(0), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "leakage_csm1", ++ .reg = NVSW_REG_LEAK_OFFSET, ++ .mask = BIT(1), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "leakage_csm2", ++ .reg = NVSW_REG_LEAK_OFFSET, ++ .mask = BIT(2), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "leakage_csm3", ++ .reg = NVSW_REG_LEAK_OFFSET, ++ .mask = BIT(3), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "leakage_csm4", ++ .reg = NVSW_REG_LEAK_OFFSET, ++ .mask = BIT(4), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++}; ++ ++static struct mlxreg_core_data nvsw_bmc_hid191_dc_ok_events_items_data[] = { ++ { ++ .label = "pdb0_12v_pg", ++ .reg = NVSW_REG_PS_DC_OK_OFFSET, ++ .mask = BIT(0), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "pdb1_12v_pg", ++ .reg = NVSW_REG_PS_DC_OK_OFFSET, ++ .mask = BIT(1), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "pdb2_12v_pg", ++ .reg = NVSW_REG_PS_DC_OK_OFFSET, ++ .mask = BIT(2), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "pdb3_12v_pg", ++ .reg = NVSW_REG_PS_DC_OK_OFFSET, ++ .mask = BIT(3), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "pdb0_hsc_pg", ++ .reg = NVSW_REG_PS_DC_OK_OFFSET, ++ .mask = BIT(4), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "pdb1_hsc_pg", ++ .reg = NVSW_REG_PS_DC_OK_OFFSET, ++ .mask = BIT(5), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "pdb2_hsc_pg", ++ .reg = NVSW_REG_PS_DC_OK_OFFSET, ++ .mask = BIT(6), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "pdb3_hsc_pg", ++ .reg = NVSW_REG_PS_DC_OK_OFFSET, ++ .mask = BIT(7), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++}; ++ ++static struct mlxreg_core_data nvsw_bmc_hid191_alarms_items_data[] = { ++ { ++ .label = "ssd_i2c_alert", ++ .reg = NVSW_REG_BRD1_OFFSET, ++ .mask = NVSW_SSD_I2C_ALERT_MASK, ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "wd_exp", ++ .reg = NVSW_REG_BRD1_OFFSET, ++ .mask = NVSW_WD_EXP_MASK, ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "5v_usb", ++ .reg = NVSW_REG_BRD1_OFFSET, ++ .mask = NVSW_5V_USB_MASK, ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "holder9_pg", ++ .reg = NVSW_REG_BRD1_OFFSET, ++ .mask = BIT(3), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "holder11_pg", ++ .reg = NVSW_REG_BRD1_OFFSET, ++ .mask = BIT(4), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "bmc_phy_pg", ++ .reg = NVSW_REG_BRD1_OFFSET, ++ .mask = BIT(5), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "ssd_pg", ++ .reg = NVSW_REG_BRD1_OFFSET, ++ .mask = BIT(6), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "leakage_aggr", ++ .reg = NVSW_REG_BRD1_OFFSET, ++ .mask = BIT(7), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++}; ++ ++static struct mlxreg_core_data nvsw_bmc_hid191_alarms2_items_data[] = { ++ { ++ .label = "psys_alert", ++ .reg = NVSW_REG_HEALTH_OFFSET, ++ .mask = BIT(0), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "vdd_mem", ++ .reg = NVSW_REG_HEALTH_OFFSET, ++ .mask = BIT(1), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "svi3_vr_alert", ++ .reg = NVSW_REG_HEALTH_OFFSET, ++ .mask = BIT(2), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "thermtrip_alert", ++ .reg = NVSW_REG_HEALTH_OFFSET, ++ .mask = BIT(3), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "vddq_vrhot", ++ .reg = NVSW_REG_BRD1_OFFSET, ++ .mask = BIT(4), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++}; ++ ++static struct mlxreg_core_item nvsw_bmc_hid191_hotplug_items_data[] = { ++ { ++ .data = nvsw_bmc_hid191_pg1_events_items_data, ++ .aggr_mask = NVSW_AGGR_MASK, ++ .reg = NVSW_REG_PG1_OFFSET, ++ .mask = GENMASK(7, 0), ++ .count = ARRAY_SIZE(nvsw_bmc_hid191_pg1_events_items_data), ++ .inversed = 1, ++ .health = false, ++ .non_sticky = true, ++ }, ++ { ++ .data = nvsw_bmc_hid191_pg2_events_items_data, ++ .aggr_mask = NVSW_AGGR_MASK_COMEX, ++ .reg = NVSW_REG_PG2_OFFSET, ++ .mask = GENMASK(7, 0), ++ .count = ARRAY_SIZE(nvsw_bmc_hid191_pg2_events_items_data), ++ .inversed = 1, ++ .health = false, ++ .non_sticky = true, ++ }, ++ { ++ .data = nvsw_bmc_hid191_pg4_events_items_data, ++ .aggr_mask = NVSW_AGGR_MASK, ++ .reg = NVSW_REG_PG3_OFFSET, ++ .mask = GENMASK(7, 0), ++ .count = ARRAY_SIZE(nvsw_bmc_hid191_pg4_events_items_data), ++ .inversed = 1, ++ .health = false, ++ .non_sticky = true, ++ }, ++ { ++ .data = nvsw_bmc_hid191_asic4_events_items_data, ++ .aggr_mask = NVSW_AGGR_MASK, ++ .reg = NVSW_REG_PG4_OFFSET, ++ .mask = GENMASK(7, 0), ++ .count = ARRAY_SIZE(nvsw_bmc_hid191_asic4_events_items_data), ++ .inversed = 1, ++ .health = false, ++ .non_sticky = true, ++ }, ++ { ++ .data = nvsw_bmc_hid191_asic_items_data, ++ .aggr_mask = NVSW_AGGR_MASK, ++ .reg = NVSW_REG_ASIC1_HEALTH_OFFSET, ++ .mask = NVSW_ASIC_MASK, ++ .count = ARRAY_SIZE(nvsw_bmc_hid191_asic_items_data), ++ .inversed = 0, ++ .health = true, ++ }, ++ { ++ .data = nvsw_bmc_hid191_asic_temp_items_data, ++ .aggr_mask = NVSW_AGGR_MASK, ++ .reg = NVSW_REG_ASIC2_HEALTH_OFFSET, ++ .mask = GENMASK(1, 0), ++ .count = ARRAY_SIZE(nvsw_bmc_hid191_asic_temp_items_data), ++ .inversed = 0, ++ .health = false, ++ .non_sticky = true, ++ }, ++ { ++ .data = nvsw_bmc_hid191_vr1_pwr_alert_items_data, ++ .aggr_mask = NVSW_AGGR_MASK, ++ .reg = NVSW_REG_VR1_ALERT_OFFSET, ++ .mask = GENMASK(7, 0), ++ .count = ARRAY_SIZE(nvsw_bmc_hid191_vr1_pwr_alert_items_data), ++ .inversed = 0, ++ .health = false, ++ }, ++ { ++ .data = nvsw_bmc_hid191_vr2_pwr_alert_items_data, ++ .aggr_mask = NVSW_AGGR_MASK, ++ .reg = NVSW_REG_VR2_ALERT_OFFSET, ++ .mask = GENMASK(7, 0), ++ .count = ARRAY_SIZE(nvsw_bmc_hid191_vr2_pwr_alert_items_data), ++ .inversed = 0, ++ .health = false, ++ }, ++ { ++ .data = nvsw_bmc_hid191_dc_ok_events_items_data, ++ .aggr_mask = NVSW_AGGR_MASK, ++ .reg = NVSW_REG_PS_DC_OK_OFFSET, ++ .mask = GENMASK(7, 0), ++ .count = ARRAY_SIZE(nvsw_bmc_hid191_dc_ok_events_items_data), ++ .inversed = 0, ++ .health = false, ++ }, ++ { ++ .data = nvsw_bmc_hid191_leakage_items_data, ++ .aggr_mask = NVSW_AGGR_MASK, ++ .reg = NVSW_REG_LEAK_OFFSET, ++ .mask = GENMASK(4, 0), ++ .count = ARRAY_SIZE(nvsw_bmc_hid191_leakage_items_data), ++ .inversed = 0, ++ .health = false, ++ }, ++ { ++ .data = nvsw_bmc_hid191_alarms_items_data, ++ .aggr_mask = NVSW_AGGR_MASK, ++ .reg = NVSW_REG_BRD1_OFFSET, ++ .mask = GENMASK(7, 0), ++ .count = ARRAY_SIZE(nvsw_bmc_hid191_alarms_items_data), ++ .inversed = 0, ++ .health = false, ++ }, ++ { ++ .data = nvsw_bmc_hid191_brd2_events_items_data, ++ .aggr_mask = NVSW_AGGR_MASK, ++ .reg = NVSW_REG_BRD2_OFFSET, ++ .mask = GENMASK(4, 0), ++ .count = ARRAY_SIZE(nvsw_bmc_hid191_brd2_events_items_data), ++ .inversed = 0, ++ .health = false, ++ .non_sticky = true, ++ }, ++ { ++ .data = nvsw_bmc_hid191_pg3_events_items_data, ++ .aggr_mask = NVSW_AGGR_MASK, ++ .reg = NVSW_REG_BRD4_OFFSET, ++ .mask = GENMASK(4, 0), ++ .count = ARRAY_SIZE(nvsw_bmc_hid191_pg3_events_items_data), ++ .inversed = 1, ++ .health = false, ++ .non_sticky = true, ++ }, ++ { ++ .data = nvsw_bmc_hid191_alarms2_items_data, ++ .aggr_mask = NVSW_AGGR_MASK_COMEX, ++ .reg = NVSW_REG_HEALTH_OFFSET, ++ .mask = GENMASK(4, 0), ++ .count = ARRAY_SIZE(nvsw_bmc_hid191_alarms2_items_data), ++ .inversed = 0, ++ .health = false, ++ }, ++ { ++ .data = nvsw_bmc_hid191_events_items_data, ++ .aggr_mask = NVSW_AGGR_MASK, ++ .reg = NVSW_REG_PWRB_OFFSET, ++ .mask = NVSW_PWR_BUTTON_MASK | NVSW_AMB_TEMP_SENSE_MASK | ++ NVSW_GRACEFUL_POWER_OFF_MASK | NVSW_CPU_POWER_OFF_READY_MASK | ++ NVSW_CPU_RESET_MASK | NVSW_APML_SMB_ALERT_MASK | ++ NVSW_CPU_UNEXP_POWER_OFF_MASK, ++ .count = ARRAY_SIZE(nvsw_bmc_hid191_events_items_data), ++ .inversed = 1, ++ .health = false, ++ .non_sticky = true, ++ }, ++}; ++ ++static struct mlxreg_core_hotplug_platform_data nvsw_bmc_hid191_hotplug = { ++ .items = nvsw_bmc_hid191_hotplug_items_data, ++ .count = ARRAY_SIZE(nvsw_bmc_hid191_hotplug_items_data), ++ .cell = NVSW_REG_AGGR_OFFSET, ++ .mask = NVSW_AGGR_MASK | NVSW_AGGR_MASK_COMEX, ++ .cell_low = NVSW_REG_AGGRLO_OFFSET, ++ .mask_low = GENMASK(5, 0), ++ .deferred_nr = 5, ++}; ++ ++static struct mlxreg_core_data nvsw_bmc_hid193_pg1_events_items_data[] = { ++ { ++ .label = "gp_vdd_pg", ++ .reg = NVSW_REG_PG1_OFFSET, ++ .mask = BIT(0), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "gp_vdd_t_pg", ++ .reg = NVSW_REG_PG1_OFFSET, ++ .mask = BIT(1), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "gp_pl_dvdd_t_pg", ++ .reg = NVSW_REG_PG1_OFFSET, ++ .mask = BIT(2), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "gp_pl_avdd_t_pg", ++ .reg = NVSW_REG_PG1_OFFSET, ++ .mask = BIT(3), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "gp_pl_hvdd_t_pg", ++ .reg = NVSW_REG_PG1_OFFSET, ++ .mask = BIT(4), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "gp_vddhbid_t_pg", ++ .reg = NVSW_REG_PG1_OFFSET, ++ .mask = BIT(5), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "1v8_vddio_pg", ++ .reg = NVSW_REG_PG1_OFFSET, ++ .mask = BIT(6), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "gp_1v8_vddqps_pg", ++ .reg = NVSW_REG_PG1_OFFSET, ++ .mask = BIT(7), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++}; ++ ++static struct mlxreg_core_data nvsw_bmc_hid193_pg3_events_items_data[] = { ++ { ++ .label = "hsc0_smb_alert", ++ .reg = NVSW_REG_BRD4_OFFSET, ++ .mask = BIT(0), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "hsc1_smb_alert", ++ .reg = NVSW_REG_BRD4_OFFSET, ++ .mask = BIT(1), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "pdb_fsd", ++ .reg = NVSW_REG_BRD4_OFFSET, ++ .mask = BIT(2), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++}; ++ ++static struct mlxreg_core_data nvsw_bmc_hid193_pg4_events_items_data[] = { ++ { ++ .label = "gp_3v3_osc_pg", ++ .reg = NVSW_REG_PG3_OFFSET, ++ .mask = BIT(0), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "gp_vdrv_pg", ++ .reg = NVSW_REG_PG3_OFFSET, ++ .mask = BIT(1), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++}; ++ ++static struct mlxreg_core_data nvsw_bmc_hid193_asic_items_data[] = { ++ { ++ .label = "asic1_health", ++ .reg = NVSW_REG_ASIC1_HEALTH_OFFSET, ++ .mask = NVSW_ASIC_MASK, ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++}; ++ ++static struct mlxreg_core_data nvsw_bmc_hid193_asic_temp_items_data[] = { ++ { ++ .label = "asic1_temp_warn", ++ .reg = NVSW_REG_ASIC2_HEALTH_OFFSET, ++ .mask = BIT(0), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "asic1_temp_shtdn", ++ .reg = NVSW_REG_ASIC2_HEALTH_OFFSET, ++ .mask = BIT(1), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++}; ++ ++static struct mlxreg_core_data nvsw_bmc_hid193_vr1_pwr_alert_items_data[] = { ++ { ++ .label = "asic1_vdd_pwr_alert", ++ .reg = NVSW_REG_VR1_ALERT_OFFSET, ++ .mask = BIT(0), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "asic1_vdd_t0_7_pwr_alert", ++ .reg = NVSW_REG_VR1_ALERT_OFFSET, ++ .mask = BIT(1), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "asic1_dvdd_t0_7_pwr_alert", ++ .reg = NVSW_REG_VR1_ALERT_OFFSET, ++ .mask = BIT(2), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "asic1_avdd_t0_7_pwr_alert", ++ .reg = NVSW_REG_VR1_ALERT_OFFSET, ++ .mask = BIT(3), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "asic1_hvdd_pwr_alert", ++ .reg = NVSW_REG_VR1_ALERT_OFFSET, ++ .mask = BIT(4), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "asic1_vdddhbid_pwr_alert", ++ .reg = NVSW_REG_VR1_ALERT_OFFSET, ++ .mask = BIT(5), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++}; ++ ++static struct mlxreg_core_data nvsw_bmc_hid193_leakage_items_data[] = { ++ { ++ .label = "leakage1", ++ .reg = NVSW_REG_LEAK_OFFSET, ++ .mask = BIT(0), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "leakage2", ++ .reg = NVSW_REG_LEAK_OFFSET, ++ .mask = BIT(1), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++}; ++ ++static struct mlxreg_core_data nvsw_bmc_hid193_dc_ok_events_items_data[] = { ++ { ++ .label = "pdb0_12v_pg", ++ .reg = NVSW_REG_PS_DC_OK_OFFSET, ++ .mask = BIT(0), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "pdb1_12v_pg", ++ .reg = NVSW_REG_PS_DC_OK_OFFSET, ++ .mask = BIT(1), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "holder16_pg", ++ .reg = NVSW_REG_PS_DC_OK_OFFSET, ++ .mask = BIT(2), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "holder17_pg", ++ .reg = NVSW_REG_PS_DC_OK_OFFSET, ++ .mask = BIT(3), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "pdb0_hsc_pg", ++ .reg = NVSW_REG_PS_DC_OK_OFFSET, ++ .mask = BIT(4), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "pdb1_hsc_pg", ++ .reg = NVSW_REG_PS_DC_OK_OFFSET, ++ .mask = BIT(5), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++}; ++ ++ ++static struct mlxreg_core_item nvsw_bmc_hid193_hotplug_items_data[] = { ++ { ++ .data = nvsw_bmc_hid193_pg1_events_items_data, ++ .aggr_mask = NVSW_AGGR_MASK, ++ .reg = NVSW_REG_PG1_OFFSET, ++ .mask = GENMASK(7, 0), ++ .count = ARRAY_SIZE(nvsw_bmc_hid193_pg1_events_items_data), ++ .inversed = 1, ++ .health = false, ++ .non_sticky = true, ++ }, ++ { ++ .data = nvsw_bmc_hid191_pg2_events_items_data, ++ .aggr_mask = NVSW_AGGR_MASK_COMEX, ++ .reg = NVSW_REG_PG2_OFFSET, ++ .mask = GENMASK(7, 0), ++ .count = ARRAY_SIZE(nvsw_bmc_hid191_pg2_events_items_data), ++ .inversed = 1, ++ .health = false, ++ .non_sticky = true, ++ }, ++ { ++ .data = nvsw_bmc_hid193_pg4_events_items_data, ++ .aggr_mask = NVSW_AGGR_MASK, ++ .reg = NVSW_REG_PG3_OFFSET, ++ .mask = GENMASK(1, 0), ++ .count = ARRAY_SIZE(nvsw_bmc_hid193_pg4_events_items_data), ++ .inversed = 1, ++ .health = false, ++ .non_sticky = true, ++ }, ++ { ++ .data = nvsw_bmc_hid193_asic_items_data, ++ .aggr_mask = NVSW_AGGR_MASK, ++ .reg = NVSW_REG_ASIC1_HEALTH_OFFSET, ++ .mask = NVSW_ASIC_MASK, ++ .count = ARRAY_SIZE(nvsw_bmc_hid193_asic_items_data), ++ .inversed = 0, ++ .health = true, ++ }, ++ { ++ .data = nvsw_bmc_hid193_asic_temp_items_data, ++ .aggr_mask = NVSW_AGGR_MASK, ++ .reg = NVSW_REG_ASIC2_HEALTH_OFFSET, ++ .mask = GENMASK(1, 0), ++ .count = ARRAY_SIZE(nvsw_bmc_hid193_asic_temp_items_data), ++ .inversed = 0, ++ .health = false, ++ .non_sticky = true, ++ }, ++ { ++ .data = nvsw_bmc_hid193_dc_ok_events_items_data, ++ .aggr_mask = NVSW_AGGR_MASK, ++ .reg = NVSW_REG_PS_DC_OK_OFFSET, ++ .mask = GENMASK(5, 0), ++ .count = ARRAY_SIZE(nvsw_bmc_hid193_dc_ok_events_items_data), ++ .inversed = 0, ++ .health = false, ++ }, ++ { ++ .data = nvsw_bmc_hid193_leakage_items_data, ++ .aggr_mask = NVSW_AGGR_MASK, ++ .reg = NVSW_REG_LEAK_OFFSET, ++ .mask = GENMASK(1, 0), ++ .count = ARRAY_SIZE(nvsw_bmc_hid193_leakage_items_data), ++ .inversed = 0, ++ .health = false, ++ }, ++ { ++ .data = nvsw_bmc_hid191_alarms_items_data, ++ .aggr_mask = NVSW_AGGR_MASK, ++ .reg = NVSW_REG_BRD1_OFFSET, ++ .mask = GENMASK(7, 0), ++ .count = ARRAY_SIZE(nvsw_bmc_hid191_alarms_items_data), ++ .inversed = 0, ++ .health = false, ++ }, ++ { ++ .data = nvsw_bmc_hid191_brd2_events_items_data, ++ .aggr_mask = NVSW_AGGR_MASK, ++ .reg = NVSW_REG_BRD2_OFFSET, ++ .mask = GENMASK(4, 0), ++ .count = ARRAY_SIZE(nvsw_bmc_hid191_brd2_events_items_data), ++ .inversed = 0, ++ .health = false, ++ .non_sticky = true, ++ }, ++ { ++ .data = nvsw_bmc_hid193_pg3_events_items_data, ++ .aggr_mask = NVSW_AGGR_MASK, ++ .reg = NVSW_REG_BRD4_OFFSET, ++ .mask = GENMASK(2, 0), ++ .count = ARRAY_SIZE(nvsw_bmc_hid193_pg3_events_items_data), ++ .inversed = 1, ++ .health = false, ++ .non_sticky = true, ++ }, ++ { ++ .data = nvsw_bmc_hid191_alarms2_items_data, ++ .aggr_mask = NVSW_AGGR_MASK_COMEX, ++ .reg = NVSW_REG_HEALTH_OFFSET, ++ .mask = GENMASK(4, 0), ++ .count = ARRAY_SIZE(nvsw_bmc_hid191_alarms2_items_data), ++ .inversed = 0, ++ .health = false, ++ }, ++ { ++ .data = nvsw_bmc_hid193_vr1_pwr_alert_items_data, ++ .aggr_mask = NVSW_AGGR_MASK, ++ .reg = NVSW_REG_VR1_ALERT_OFFSET, ++ .mask = GENMASK(5, 0), ++ .count = ARRAY_SIZE(nvsw_bmc_hid193_vr1_pwr_alert_items_data), ++ .inversed = 0, ++ .health = false, ++ }, ++ { ++ .data = nvsw_bmc_hid191_events_items_data, ++ .aggr_mask = NVSW_AGGR_MASK, ++ .reg = NVSW_REG_PWRB_OFFSET, ++ .mask = NVSW_PWR_BUTTON_MASK | NVSW_AMB_TEMP_SENSE_MASK | ++ NVSW_GRACEFUL_POWER_OFF_MASK | NVSW_CPU_POWER_OFF_READY_MASK | ++ NVSW_CPU_RESET_MASK | NVSW_APML_SMB_ALERT_MASK | ++ NVSW_CPU_UNEXP_POWER_OFF_MASK, ++ .count = ARRAY_SIZE(nvsw_bmc_hid191_events_items_data), ++ .inversed = 1, ++ .health = false, ++ .non_sticky = true, ++ }, ++}; ++ ++static struct mlxreg_core_hotplug_platform_data nvsw_bmc_hid193_hotplug = { ++ .items = nvsw_bmc_hid193_hotplug_items_data, ++ .count = ARRAY_SIZE(nvsw_bmc_hid193_hotplug_items_data), ++ .cell = NVSW_REG_AGGR_OFFSET, ++ .mask = NVSW_AGGR_MASK | NVSW_AGGR_MASK_COMEX, ++ .cell_low = NVSW_REG_AGGRLO_OFFSET, ++ .mask_low = GENMASK(6, 0), ++ .deferred_nr = 5, ++}; ++ ++/* Platform register access data. */ ++static struct mlxreg_core_data nvsw_bmc_hid162_regio_data[] = { ++ { ++ .label = "cpld1_version", ++ .reg = NVSW_REG_CPLD1_VER_OFFSET, ++ .bit = GENMASK(7, 0), ++ .mode = 0444, ++ }, ++ { ++ .label = "cpld2_version", ++ .reg = NVSW_REG_CPLD2_VER_OFFSET, ++ .bit = GENMASK(7, 0), ++ .mode = 0444, ++ }, ++ { ++ .label = "cpld3_version", ++ .reg = NVSW_REG_CPLD3_VER_OFFSET, ++ .bit = GENMASK(7, 0), ++ .mode = 0444, ++ }, ++ { ++ .label = "cpld4_version", ++ .reg = NVSW_REG_CPLD4_VER_OFFSET, ++ .bit = GENMASK(7, 0), ++ .mode = 0444, ++ }, ++ { ++ .label = "cpld1_pn", ++ .reg = NVSW_REG_CPLD1_PN_OFFSET, ++ .bit = GENMASK(15, 0), ++ .mode = 0444, ++ .regnum = 2, ++ }, ++ { ++ .label = "cpld2_pn", ++ .reg = NVSW_REG_CPLD2_PN_OFFSET, ++ .bit = GENMASK(15, 0), ++ .mode = 0444, ++ .regnum = 2, ++ }, ++ { ++ .label = "cpld3_pn", ++ .reg = NVSW_REG_CPLD3_PN_OFFSET, ++ .bit = GENMASK(15, 0), ++ .mode = 0444, ++ .regnum = 2, ++ }, ++ { ++ .label = "cpld4_pn", ++ .reg = NVSW_REG_CPLD4_PN_OFFSET, ++ .bit = GENMASK(15, 0), ++ .mode = 0444, ++ .regnum = 2, ++ }, ++ { ++ .label = "cpld1_version_min", ++ .reg = NVSW_REG_CPLD1_MVER_OFFSET, ++ .bit = GENMASK(7, 0), ++ .mode = 0444, ++ }, ++ { ++ .label = "cpld2_version_min", ++ .reg = NVSW_REG_CPLD2_MVER_OFFSET, ++ .bit = GENMASK(7, 0), ++ .mode = 0444, ++ }, ++ { ++ .label = "cpld3_version_min", ++ .reg = NVSW_REG_CPLD3_MVER_OFFSET, ++ .bit = GENMASK(7, 0), ++ .mode = 0444, ++ }, ++ { ++ .label = "cpld4_version_min", ++ .reg = NVSW_REG_CPLD4_MVER_OFFSET, ++ .bit = GENMASK(7, 0), ++ .mode = 0444, ++ }, ++ { ++ .label = "fan_dir", ++ .reg = NVSW_REG_GP0_RO_OFFSET, ++ .mask = GENMASK(7, 0), ++ .mode = 0444, ++ }, ++ { ++ .label = "fan_present", ++ .reg = NVSW_REG_FAN_OFFSET, ++ .mask = GENMASK(7, 0), ++ .mode = 0444, ++ }, ++ { ++ .label = "cpu_mctp_ready", ++ .reg = NVSW_REG_GP0_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(1), ++ .mode = 0644, ++ }, ++ { ++ .label = "cpu_shutdown_req", ++ .reg = NVSW_REG_GP0_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(2), ++ .mode = 0644, ++ }, ++ { ++ .label = "vpd_wp", ++ .reg = NVSW_REG_GP0_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(3), ++ .mode = 0644, ++ .secured = 1, ++ }, ++ { ++ .label = "pcie_asic_reset_dis", ++ .reg = NVSW_REG_GP0_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(4), ++ .mode = 0644, ++ }, ++ { ++ .label = "shutdown_unlock", ++ .reg = NVSW_REG_GP0_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(5), ++ .mode = 0644, ++ }, ++ { ++ .label = "cpu_power_off_ready", ++ .reg = NVSW_REG_GP0_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(6), ++ .mode = 0644, ++ }, ++ { ++ .label = "ignore_next_reset", ++ .reg = NVSW_REG_GP0_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(7), ++ .mode = 0644, ++ }, ++ { ++ .label = "leakage_conn_en", ++ .reg = NVSW_REG_GP6_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(0), ++ .mode = 0644, ++ }, ++ { ++ .label = "bmc_reset_reg", ++ .reg = NVSW_REG_GP6_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(1), ++ .mode = 0644, ++ }, ++ { ++ .label = "spi_chnl_select", ++ .reg = NVSW_REG_SPI_CHNL_SELECT, ++ .mask = GENMASK(7, 0), ++ .bit = 1, ++ .mode = 0644, ++ }, ++ { ++ .label = "pwr_converter_prog_en", ++ .reg = NVSW_REG_GP7_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(0), ++ .mode = 0644, ++ .secured = 1, ++ }, ++ { ++ .label = "graceful_power_off", ++ .reg = NVSW_REG_GP7_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(3), ++ .mode = 0644, ++ }, ++ { ++ .label = "bmc_perst_en", ++ .reg = NVSW_REG_GP7_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(4), ++ .mode = 0200, ++ }, ++ { ++ .label = "bmc_shutdown_unlock", ++ .reg = NVSW_REG_GP7_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(5), ++ .mode = 0200, ++ }, ++ { ++ .label = "platform_reset", ++ .reg = NVSW_REG_RESET_GP1_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(0), ++ .mode = 0200, ++ }, ++ { ++ .label = "main_brd_reset", ++ .reg = NVSW_REG_RESET_GP1_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(2), ++ .mode = 0200, ++ }, ++ { ++ .label = "nic_reset", ++ .reg = NVSW_REG_RESET_GP1_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(5), ++ .mode = 0200, ++ }, ++ { ++ .label = "tpm_reset", ++ .reg = NVSW_REG_RESET_GP1_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(6), ++ .mode = 0200, ++ }, ++ { ++ .label = "erot_asic3_reset", ++ .reg = NVSW_REG_RESET_GP2_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(2), ++ .mode = 0200, ++ }, ++ { ++ .label = "asics_reset", ++ .reg = NVSW_REG_RESET_GP2_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(3), ++ .mode = 0200, ++ }, ++ { ++ .label = "sgmii_phy_reset", ++ .reg = NVSW_REG_RESET_GP2_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(4), ++ .mode = 0200, ++ }, ++ { ++ .label = "erot_cpu_reset", ++ .reg = NVSW_REG_RESET_GP2_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(6), ++ .mode = 0200, ++ }, ++ { ++ .label = "erot_asic1_reset", ++ .reg = NVSW_REG_RESET_GP2_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(6), ++ .mode = 0200, ++ }, ++ { ++ .label = "erot_asic2_reset", ++ .reg = NVSW_REG_RESET_GP2_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(7), ++ .mode = 0200, ++ }, ++ { ++ .label = "jtag_enable", ++ .reg = NVSW_REG_FIELD_UPGRADE, ++ .mask = GENMASK(1, 0), ++ .bit = 1, ++ .mode = 0644, ++ }, ++ { ++ .label = "non_active_bios_select", ++ .reg = NVSW_REG_SAFE_BIOS_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(4), ++ .mode = 0644, ++ }, ++ { ++ .label = "bios_upgrade_fail", ++ .reg = NVSW_REG_SAFE_BIOS_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(5), ++ .mode = 0444, ++ }, ++ { ++ .label = "bios_image_invert", ++ .reg = NVSW_REG_SAFE_BIOS_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(6), ++ .mode = 0644, ++ }, ++ { ++ .label = "erot_asic3_recovery", ++ .reg = NVSW_REG_PWM_CONTROL_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(4), ++ .mode = 0644, ++ }, ++ { ++ .label = "erot_cpu_recovery", ++ .reg = NVSW_REG_PWM_CONTROL_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(5), ++ .mode = 0644, ++ }, ++ { ++ .label = "erot_asic1_recovery", ++ .reg = NVSW_REG_PWM_CONTROL_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(6), ++ .mode = 0644, ++ }, ++ { ++ .label = "erot_asic2_recovery", ++ .reg = NVSW_REG_PWM_CONTROL_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(7), ++ .mode = 0644, ++ }, ++ { ++ .label = "pwr_button_halt", ++ .reg = NVSW_REG_GP1_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(1), ++ .mode = 0644, ++ }, ++ { ++ .label = "pwr_cycle", ++ .reg = NVSW_REG_GP1_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(2), ++ .mode = 0200, ++ }, ++ { ++ .label = "pwr_down", ++ .reg = NVSW_REG_GP1_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(3), ++ .mode = 0644, ++ }, ++ { ++ .label = "aux_pwr_cycle", ++ .reg = NVSW_REG_GP1_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(4), ++ .mode = 0200, ++ }, ++ { ++ .label = "bmc_to_cpu_ctrl", ++ .reg = NVSW_REG_GP1_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(5), ++ .mode = 0644, ++ }, ++ { ++ .label = "uart_sel", ++ .reg = NVSW_REG_GP1_OFFSET, ++ .mask = NVSW_UART_SEL_MASK, ++ .bit = 7, ++ .mode = 0644, ++ }, ++ { ++ .label = "reset_long_pb", ++ .reg = NVSW_REG_RESET_CAUSE_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(0), ++ .mode = 0444, ++ }, ++ { ++ .label = "reset_short_pb", ++ .reg = NVSW_REG_RESET_CAUSE_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(1), ++ .mode = 0444, ++ }, ++ { ++ .label = "reset_aux_pwr_or_fu", ++ .reg = NVSW_REG_RESET_CAUSE_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(2), ++ .mode = 0444, ++ }, ++ { ++ .label = "reset_swb_pwr_fail", ++ .reg = NVSW_REG_RESET_CAUSE_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(3), ++ .mode = 0444, ++ }, ++ { ++ .label = "reset_pwr_button_or_leak_con", ++ .reg = NVSW_REG_RESET_CAUSE_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(5), ++ .mode = 0444, ++ }, ++ { ++ .label = "reset_swb_wd", ++ .reg = NVSW_REG_RESET_CAUSE_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(6), ++ .mode = 0444, ++ }, ++ { ++ .label = "reset_asic_thermal", ++ .reg = NVSW_REG_RESET_CAUSE_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(7), ++ .mode = 0444, ++ }, ++ { ++ .label = "reset_from_carrier", ++ .reg = NVSW_REG_RESET_CAUSE1_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(1), ++ .mode = 0444, ++ }, ++ { ++ .label = "reset_aux_pwr_or_reload", ++ .reg = NVSW_REG_RESET_CAUSE1_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(2), ++ .mode = 0444, ++ }, ++ { ++ .label = "reset_comex_pwr_fail", ++ .reg = NVSW_REG_RESET_CAUSE1_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(3), ++ .mode = 0444, ++ }, ++ { ++ .label = "reset_platform", ++ .reg = NVSW_REG_RESET_CAUSE1_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(4), ++ .mode = 0444, ++ }, ++ { ++ .label = "reset_soc", ++ .reg = NVSW_REG_RESET_CAUSE1_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(5), ++ .mode = 0444, ++ }, ++ { ++ .label = "reset_from_erot", ++ .reg = NVSW_REG_RESET_CAUSE1_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(6), ++ .mode = 0444, ++ }, ++ { ++ .label = "reset_pwr", ++ .reg = NVSW_REG_RESET_CAUSE1_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(7), ++ .mode = 0444, ++ }, ++ { ++ .label = "reset_erot", ++ .reg = NVSW_REG_RESET_CAUSE2_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(0), ++ .mode = 0444, ++ }, ++ { ++ .label = "reset_system", ++ .reg = NVSW_REG_RESET_CAUSE2_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(1), ++ .mode = 0444, ++ }, ++ { ++ .label = "reset_sw_pwr_off", ++ .reg = NVSW_REG_RESET_CAUSE2_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(2), ++ .mode = 0444, ++ }, ++ { ++ .label = "reset_comex_thermal", ++ .reg = NVSW_REG_RESET_CAUSE2_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(3), ++ .mode = 0444, ++ }, ++ { ++ .label = "reset_comex_power", ++ .reg = NVSW_REG_RESET_CAUSE2_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(4), ++ .mode = 0444, ++ }, ++ { ++ .label = "reset_pwr_converter_fail", ++ .reg = NVSW_REG_RESET_CAUSE2_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(5), ++ .mode = 0444, ++ }, ++ { ++ .label = "reset_main_51v", ++ .reg = NVSW_REG_RESET_CAUSE2_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(6), ++ .mode = 0444, ++ }, ++ { ++ .label = "reset_mgmt_pwr", ++ .reg = NVSW_REG_RESET_CAUSE2_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(7), ++ .mode = 0444, ++ }, ++ { ++ .label = "port80", ++ .reg = NVSW_REG_GP1_RO_OFFSET, ++ .bit = GENMASK(7, 0), ++ .mode = 0444, ++ }, ++ { ++ .label = "bios_status", ++ .reg = NVSW_REG_GPCOM0_OFFSET, ++ .mask = NVSW_BIOS_STATUS_MASK, ++ .bit = 2, ++ .mode = 0444, ++ }, ++ { ++ .label = "bios_start_retry", ++ .reg = NVSW_REG_GPCOM0_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(4), ++ .mode = 0444, ++ }, ++ { ++ .label = "bios_active_image", ++ .reg = NVSW_REG_GPCOM0_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(5), ++ .mode = 0444, ++ }, ++ { ++ .label = "ufm_version", ++ .reg = NVSW_REG_UFM_VERSION_OFFSET, ++ .bit = GENMASK(7, 0), ++ .mode = 0444, ++ }, ++ { ++ .label = "clk_brd1_boot_fail", ++ .reg = NVSW_REG_GP4_RO_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(4), ++ .mode = 0444, ++ }, ++ { ++ .label = "clk_brd2_boot_fail", ++ .reg = NVSW_REG_GP4_RO_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(5), ++ .mode = 0444, ++ }, ++ { ++ .label = "clk_brd_fail", ++ .reg = NVSW_REG_GP4_RO_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(6), ++ .mode = 0444, ++ }, ++ { ++ .label = "asic_pg_fail", ++ .reg = NVSW_REG_GP4_RO_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(7), ++ .mode = 0444, ++ }, ++ { ++ .label = "geo_addr", ++ .reg = NVSW_REG_CONFIG2_OFFSET, ++ .bit = GENMASK(7, 0), ++ .mode = 0444, ++ }, ++}; ++ ++static struct mlxreg_core_platform_data nvsw_bmc_hid162_regio = { ++ .data = nvsw_bmc_hid162_regio_data, ++ .counter = ARRAY_SIZE(nvsw_bmc_hid162_regio_data), ++}; ++ ++static struct mlxreg_core_data nvsw_bmc_hid180_regio_data[] = { ++ { ++ .label = "cpld1_version", ++ .reg = NVSW_REG_CPLD1_VER_OFFSET, ++ .bit = GENMASK(7, 0), ++ .mode = 0444, ++ }, ++ { ++ .label = "cpld2_version", ++ .reg = NVSW_REG_CPLD2_VER_OFFSET, ++ .bit = GENMASK(7, 0), ++ .mode = 0444, ++ }, ++ { ++ .label = "cpld1_pn", ++ .reg = NVSW_REG_CPLD1_PN_OFFSET, ++ .bit = GENMASK(15, 0), ++ .mode = 0444, ++ .regnum = 2, ++ }, ++ { ++ .label = "cpld2_pn", ++ .reg = NVSW_REG_CPLD2_PN_OFFSET, ++ .bit = GENMASK(15, 0), ++ .mode = 0444, ++ .regnum = 2, ++ }, ++ { ++ .label = "cpld1_version_min", ++ .reg = NVSW_REG_CPLD1_MVER_OFFSET, ++ .bit = GENMASK(7, 0), ++ .mode = 0444, ++ }, ++ { ++ .label = "cpld2_version_min", ++ .reg = NVSW_REG_CPLD2_MVER_OFFSET, ++ .bit = GENMASK(7, 0), ++ .mode = 0444, ++ }, ++ { ++ .label = "bios_status", ++ .reg = NVSW_REG_GPCOM0_OFFSET, ++ .mask = GENMASK(3, 1), ++ .bit = 3, ++ .mode = 0444, ++ }, ++ { ++ .label = "bios_start_retry", ++ .reg = NVSW_REG_GPCOM0_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(4), ++ .mode = 0444, ++ }, ++ { ++ .label = "bios_active_image", ++ .reg = NVSW_REG_GPCOM0_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(5), ++ .mode = 0444, ++ }, ++ { ++ .label = "pwr_converter_prog_en", ++ .reg = NVSW_REG_GP7_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(0), ++ .mode = 0644, ++ }, ++ { ++ .label = "graceful_power_off", ++ .reg = NVSW_REG_GP7_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(3), ++ .mode = 0644, ++ }, ++ { ++ .label = "bmc_perst_en", ++ .reg = NVSW_REG_GP7_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(4), ++ .mode = 0200, ++ }, ++ { ++ .label = "bmc_shutdown_unlock", ++ .reg = NVSW_REG_GP7_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(5), ++ .mode = 0200, ++ }, ++ { ++ .label = "stby_pwr_en_unmask", ++ .reg = NVSW_REG_GP7_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(5), ++ .mode = 0200, ++ }, ++ { ++ .label = "cpu_mctp_ready", ++ .reg = NVSW_REG_GP0_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(1), ++ .mode = 0644, ++ }, ++ { ++ .label = "cpu_shutdown_req", ++ .reg = NVSW_REG_GP0_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(2), ++ .mode = 0444, ++ }, ++ { ++ .label = "vpd_wp", ++ .reg = NVSW_REG_GP0_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(3), ++ .mode = 0644, ++ .secured = 1, ++ }, ++ { ++ .label = "pcie_asic_reset_dis", ++ .reg = NVSW_REG_GP0_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(4), ++ .mode = 0644, ++ }, ++ { ++ .label = "shutdown_unlock", ++ .reg = NVSW_REG_GP0_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(5), ++ .mode = 0644, ++ }, ++ { ++ .label = "cpu_power_off_ready", ++ .reg = NVSW_REG_GP0_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(6), ++ .mode = 0644, ++ }, ++ { ++ .label = "pwr_button_halt", ++ .reg = NVSW_REG_GP1_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(1), ++ .mode = 0644, ++ }, ++ { ++ .label = "pwr_cycle", ++ .reg = NVSW_REG_GP1_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(2), ++ .mode = 0200, ++ }, ++ { ++ .label = "pwr_down", ++ .reg = NVSW_REG_GP1_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(3), ++ .mode = 0644, ++ }, ++ { ++ .label = "aux_pwr_cycle", ++ .reg = NVSW_REG_GP1_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(4), ++ .mode = 0200, ++ }, ++ { ++ .label = "bmc_to_cpu_ctrl", ++ .reg = NVSW_REG_GP1_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(5), ++ .mode = 0644, ++ }, ++ { ++ .label = "uart_sel", ++ .reg = NVSW_REG_GP1_OFFSET, ++ .mask = NVSW_UART_SEL_MASK, ++ .bit = 7, ++ .mode = 0644, ++ }, ++ { ++ .label = "asics_reset", ++ .reg = NVSW_REG_RESET_GP2_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(3), ++ .mode = 0644, ++ }, ++ { ++ .label = "sgmii_phy_reset", ++ .reg = NVSW_REG_RESET_GP2_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(4), ++ .mode = 0644, ++ }, ++ { ++ .label = "erot_cpu_reset", ++ .reg = NVSW_REG_RESET_GP2_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(5), ++ .mode = 0644, ++ }, ++ { ++ .label = "mcu1_reset", ++ .reg = NVSW_REG_RESET_GP2_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(6), ++ .mode = 0644, ++ }, ++ { ++ .label = "mcu2_reset", ++ .reg = NVSW_REG_RESET_GP2_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(7), ++ .mode = 0644, ++ }, ++ { ++ .label = "platform_reset", ++ .reg = NVSW_REG_RESET_GP1_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(2), ++ .mode = 0200, ++ }, ++ { ++ .label = "cpld_phy_reset", ++ .reg = NVSW_REG_RESET_GP1_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(5), ++ .mode = 0200, ++ }, ++ { ++ .label = "tpm_reset", ++ .reg = NVSW_REG_RESET_GP1_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(6), ++ .mode = 0200, ++ }, ++ { ++ .label = "reset_long_pb", ++ .reg = NVSW_REG_RESET_CAUSE_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(0), ++ .mode = 0444, ++ }, ++ { ++ .label = "reset_short_pb", ++ .reg = NVSW_REG_RESET_CAUSE_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(1), ++ .mode = 0444, ++ }, ++ { ++ .label = "reset_aux_pwr_or_fu", ++ .reg = NVSW_REG_RESET_CAUSE_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(2), ++ .mode = 0444, ++ }, ++ { ++ .label = "reset_swb_dc_dc_pwr_fail", ++ .reg = NVSW_REG_RESET_CAUSE_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(3), ++ .mode = 0444, ++ }, ++ { ++ .label = "reset_cpu", ++ .reg = NVSW_REG_RESET_CAUSE_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(4), ++ .mode = 0444, ++ }, ++ { ++ .label = "reset_pwr_button", ++ .reg = NVSW_REG_RESET_CAUSE_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(5), ++ .mode = 0444, ++ }, ++ { ++ .label = "reset_swb_wd", ++ .reg = NVSW_REG_RESET_CAUSE_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(6), ++ .mode = 0444, ++ }, ++ { ++ .label = "reset_asic_thermal", ++ .reg = NVSW_REG_RESET_CAUSE_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(7), ++ .mode = 0444, ++ }, ++ { ++ .label = "reset_soc", ++ .reg = NVSW_REG_RESET_CAUSE1_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(5), ++ .mode = 0444, ++ }, ++ { ++ .label = "reset_erot", ++ .reg = NVSW_REG_RESET_CAUSE1_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(6), ++ .mode = 0444, ++ }, ++ { ++ .label = "leak_con", ++ .reg = NVSW_REG_RESET_CAUSE1_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(7), ++ .mode = 0444, ++ }, ++ { ++ .label = "reset_system", ++ .reg = NVSW_REG_RESET_CAUSE2_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(1), ++ .mode = 0444, ++ }, ++ { ++ .label = "reset_sw_pwr_off", ++ .reg = NVSW_REG_RESET_CAUSE2_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(2), ++ .mode = 0444, ++ }, ++ { ++ .label = "reset_cpu_thermal", ++ .reg = NVSW_REG_RESET_CAUSE2_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(3), ++ .mode = 0444, ++ }, ++ { ++ .label = "reset_pwr_converter_fail", ++ .reg = NVSW_REG_RESET_CAUSE2_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(5), ++ .mode = 0444, ++ }, ++ { ++ .label = "reset_main_51v", ++ .reg = NVSW_REG_RESET_CAUSE2_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(6), ++ .mode = 0444, ++ }, ++ { ++ .label = "reset_mgmt_pwr", ++ .reg = NVSW_REG_RESET_CAUSE2_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(7), ++ .mode = 0444, ++ }, ++ { ++ .label = "port80", ++ .reg = NVSW_REG_GP1_RO_OFFSET, ++ .bit = GENMASK(7, 0), ++ .mode = 0444, ++ }, ++ { ++ .label = "transport_status", ++ .reg = NVSW_REG_GP4_RO_OFFSET, ++ .mask = GENMASK(1, 0), ++ .bit = 1, ++ .mode = 0444, ++ }, ++ { ++ .label = "tpm_present", ++ .reg = NVSW_REG_GP4_RO_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(3), ++ .mode = 0444, ++ }, ++ { ++ .label = "asics_pg_fail", ++ .reg = NVSW_REG_GP4_RO_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(7), ++ .mode = 0444, ++ }, ++ { ++ .label = "jtag_cap", ++ .reg = NVSW_REG_FU_CAP_OFFSET, ++ .mask = NVSW_FU_CAP_MASK, ++ .bit = 1, ++ .mode = 0444, ++ }, ++ { ++ .label = "jtag_enable", ++ .reg = NVSW_REG_FIELD_UPGRADE, ++ .mask = GENMASK(1, 0), ++ .bit = 1, ++ .mode = 0644, ++ }, ++ { ++ .label = "non_active_bios_select", ++ .reg = NVSW_REG_SAFE_BIOS_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(4), ++ .mode = 0644, ++ }, ++ { ++ .label = "bios_upgrade_fail", ++ .reg = NVSW_REG_SAFE_BIOS_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(5), ++ .mode = 0444, ++ }, ++ { ++ .label = "bios_image_invert", ++ .reg = NVSW_REG_SAFE_BIOS_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(6), ++ .mode = 0644, ++ }, ++ { ++ .label = "erot_cpu_recovery", ++ .reg = NVSW_REG_PWM_CONTROL_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(5), ++ .mode = 0644, ++ }, ++ { ++ .label = "mcu1_recovery", ++ .reg = NVSW_REG_PWM_CONTROL_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(6), ++ .mode = 0644, ++ }, ++ { ++ .label = "mcu2_recovery", ++ .reg = NVSW_REG_PWM_CONTROL_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(7), ++ .mode = 0644, ++ }, ++ { ++ .label = "cpu_int_enable", ++ .reg = NVSW_REG_GP5_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(0), ++ .mode = 0644, ++ }, ++ { ++ .label = "cpu_tps_upgrade", ++ .reg = NVSW_REG_GP5_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(1), ++ .mode = 0644, ++ }, ++ { ++ .label = "cpu_spi_ctrl", ++ .reg = NVSW_REG_GP5_OFFSET, ++ .mask = GENMASK(4, 2), ++ .bit = 4, ++ .mode = 0644, ++ }, ++ { ++ .label = "ignore_next_reset", ++ .reg = NVSW_REG_GP5_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(6), ++ .mode = 0644, ++ }, ++ { ++ .label = "leakage_conn_en", ++ .reg = NVSW_REG_GP6_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(0), ++ .mode = 0644, ++ }, ++ { ++ .label = "bmc_reset_reg", ++ .reg = NVSW_REG_GP6_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(1), ++ .mode = 0644, ++ }, ++ { ++ .label = "asic_pg_fail", ++ .reg = NVSW_REG_GP4_RO_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(7), ++ .mode = 0444, ++ }, ++ { ++ .label = "spi_chnl_select", ++ .reg = NVSW_REG_SPI_CHNL_SELECT, ++ .mask = GENMASK(7, 0), ++ .bit = 1, ++ .mode = 0644, ++ }, ++ { ++ .label = "config1", ++ .reg = NVSW_REG_CONFIG1_OFFSET, ++ .bit = GENMASK(7, 0), ++ .mode = 0444, ++ }, ++ { ++ .label = "config2", ++ .reg = NVSW_REG_CONFIG2_OFFSET, ++ .bit = GENMASK(7, 0), ++ .mode = 0444, ++ }, ++ { ++ .label = "config3", ++ .reg = NVSW_REG_CONFIG3_OFFSET, ++ .bit = GENMASK(7, 0), ++ .mode = 0444, ++ }, ++}; ++ ++static struct mlxreg_core_data nvsw_bmc_hid181_regio_data[] = { ++ { ++ .label = "cpld1_version", ++ .reg = NVSW_REG_CPLD1_VER_OFFSET, ++ .bit = GENMASK(7, 0), ++ .mode = 0444, ++ }, ++ { ++ .label = "cpld2_version", ++ .reg = NVSW_REG_CPLD2_VER_OFFSET, ++ .bit = GENMASK(7, 0), ++ .mode = 0444, ++ }, ++ { ++ .label = "cpld3_version", ++ .reg = NVSW_REG_CPLD3_VER_OFFSET, ++ .bit = GENMASK(7, 0), ++ .mode = 0444, ++ }, ++ { ++ .label = "cpld4_version", ++ .reg = NVSW_REG_CPLD4_VER_OFFSET, ++ .bit = GENMASK(7, 0), ++ .mode = 0444, ++ }, ++ { ++ .label = "cpld5_version", ++ .reg = NVSW_REG_CPLD5_VER_OFFSET, ++ .bit = GENMASK(7, 0), ++ .mode = 0444, ++ }, ++ { ++ .label = "cpld6_version", ++ .reg = NVSW_REG_CPLD6_VER_OFFSET, ++ .bit = GENMASK(7, 0), ++ .mode = 0444, ++ }, ++ { ++ .label = "cpld8_version", ++ .reg = NVSW_REG_CPLD8_VER_OFFSET, ++ .bit = GENMASK(7, 0), ++ .mode = 0444, ++ }, ++ { ++ .label = "cpld9_version", ++ .reg = NVSW_REG_CPLD9_VER_OFFSET, ++ .bit = GENMASK(7, 0), ++ .mode = 0444, ++ }, ++ { ++ .label = "cpld10_version", ++ .reg = NVSW_REG_CPLD10_VER_OFFSET, ++ .bit = GENMASK(7, 0), ++ .mode = 0444, ++ }, ++ { ++ .label = "cpld1_pn", ++ .reg = NVSW_REG_CPLD1_PN_OFFSET, ++ .bit = GENMASK(15, 0), ++ .mode = 0444, ++ .regnum = 2, ++ }, ++ { ++ .label = "cpld2_pn", ++ .reg = NVSW_REG_CPLD2_PN_OFFSET, ++ .bit = GENMASK(15, 0), ++ .mode = 0444, ++ .regnum = 2, ++ }, ++ { ++ .label = "cpld3_pn", ++ .reg = NVSW_REG_CPLD3_PN_OFFSET, ++ .bit = GENMASK(15, 0), ++ .mode = 0444, ++ .regnum = 2, ++ }, ++ { ++ .label = "cpld4_pn", ++ .reg = NVSW_REG_CPLD4_PN_OFFSET, ++ .bit = GENMASK(15, 0), ++ .mode = 0444, ++ .regnum = 2, ++ }, ++ { ++ .label = "cpld5_pn", ++ .reg = NVSW_REG_CPLD5_PN_OFFSET, ++ .bit = GENMASK(15, 0), ++ .mode = 0444, ++ .regnum = 2, ++ }, ++ { ++ .label = "cpld6_pn", ++ .reg = NVSW_REG_CPLD6_PN_OFFSET, ++ .bit = GENMASK(15, 0), ++ .mode = 0444, ++ .regnum = 2, ++ }, ++ { ++ .label = "cpld7_pn", ++ .reg = NVSW_REG_CPLD7_PN_OFFSET, ++ .bit = GENMASK(15, 0), ++ .mode = 0444, ++ .regnum = 2, ++ }, ++ { ++ .label = "cpld8_pn", ++ .reg = NVSW_REG_CPLD8_PN_OFFSET, ++ .bit = GENMASK(15, 0), ++ .mode = 0444, ++ .regnum = 2, ++ }, ++ { ++ .label = "cpld9_pn", ++ .reg = NVSW_REG_CPLD9_PN_OFFSET, ++ .bit = GENMASK(15, 0), ++ .mode = 0444, ++ .regnum = 2, ++ }, ++ { ++ .label = "cpld10_pn", ++ .reg = NVSW_REG_CPLD10_PN_OFFSET, ++ .bit = GENMASK(15, 0), ++ .mode = 0444, ++ .regnum = 2, ++ }, ++ { ++ .label = "cpld1_dbg_led", ++ .reg = NVSW_REG_CPLD1_DBG_LED_OFFSET, ++ .bit = GENMASK(7, 0), ++ .mode = 0644, ++ }, ++ { ++ .label = "cpld2_dbg_led", ++ .reg = NVSW_REG_CPLD2_DBG_LED_OFFSET, ++ .bit = GENMASK(7, 0), ++ .mode = 0644, ++ }, ++ { ++ .label = "cpld3_dbg_led", ++ .reg = NVSW_REG_CPLD3_DBG_LED_OFFSET, ++ .bit = GENMASK(7, 0), ++ .mode = 0644, ++ }, ++ { ++ .label = "cpld4_dbg_led", ++ .reg = NVSW_REG_CPLD4_DBG_LED_OFFSET, ++ .bit = GENMASK(7, 0), ++ .mode = 0644, ++ }, ++ { ++ .label = "cpld7_version", ++ .reg = NVSW_REG_CPLD7_VER_OFFSET, ++ .bit = GENMASK(7, 0), ++ .mode = 0444, ++ }, ++ { ++ .label = "cpld1_version_min", ++ .reg = NVSW_REG_CPLD1_MVER_OFFSET, ++ .bit = GENMASK(7, 0), ++ .mode = 0444, ++ }, ++ { ++ .label = "cpld2_version_min", ++ .reg = NVSW_REG_CPLD2_MVER_OFFSET, ++ .bit = GENMASK(7, 0), ++ .mode = 0444, ++ }, ++ { ++ .label = "cpld3_version_min", ++ .reg = NVSW_REG_CPLD3_MVER_OFFSET, ++ .bit = GENMASK(7, 0), ++ .mode = 0444, ++ }, ++ { ++ .label = "cpld4_version_min", ++ .reg = NVSW_REG_CPLD4_MVER_OFFSET, ++ .bit = GENMASK(7, 0), ++ .mode = 0444, ++ }, ++ { ++ .label = "cpld5_version_min", ++ .reg = NVSW_REG_CPLD5_MVER_OFFSET, ++ .bit = GENMASK(7, 0), ++ .mode = 0444, ++ }, ++ { ++ .label = "cpld6_version_min", ++ .reg = NVSW_REG_CPLD6_MVER_OFFSET, ++ .bit = GENMASK(7, 0), ++ .mode = 0444, ++ }, ++ { ++ .label = "cpld7_version_min", ++ .reg = NVSW_REG_CPLD7_MVER_OFFSET, ++ .bit = GENMASK(7, 0), ++ .mode = 0444, ++ }, ++ { ++ .label = "cpld8_version_min", ++ .reg = NVSW_REG_CPLD8_MVER_OFFSET, ++ .bit = GENMASK(7, 0), ++ .mode = 0444, ++ }, ++ { ++ .label = "cpld9_version_min", ++ .reg = NVSW_REG_CPLD9_MVER_OFFSET, ++ .bit = GENMASK(7, 0), ++ .mode = 0444, ++ }, ++ { ++ .label = "cpld10_version_min", ++ .reg = NVSW_REG_CPLD10_MVER_OFFSET, ++ .bit = GENMASK(7, 0), ++ .mode = 0444, ++ }, ++ { ++ .label = "ufm_version", ++ .reg = NVSW_REG_UFM_VERSION_OFFSET, ++ .bit = GENMASK(7, 0), ++ .mode = 0444, ++ }, ++ { ++ .label = "bios_status", ++ .reg = NVSW_REG_GPCOM0_OFFSET, ++ .mask = GENMASK(3, 1), ++ .bit = 3, ++ .mode = 0444, ++ }, ++ { ++ .label = "bios_start_retry", ++ .reg = NVSW_REG_GPCOM0_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(4), ++ .mode = 0444, ++ }, ++ { ++ .label = "bios_active_image", ++ .reg = NVSW_REG_GPCOM0_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(5), ++ .mode = 0444, ++ }, ++ { ++ .label = "pwr_converter_prog_en", ++ .reg = NVSW_REG_GP7_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(0), ++ .mode = 0644, ++ }, ++ { ++ .label = "graceful_power_off", ++ .reg = NVSW_REG_GP7_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(3), ++ .mode = 0644, ++ }, ++ { ++ .label = "bmc_perst_en", ++ .reg = NVSW_REG_GP7_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(4), ++ .mode = 0200, ++ }, ++ { ++ .label = "stby_pwr_en_unmask", ++ .reg = NVSW_REG_GP7_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(5), ++ .mode = 0200, ++ }, ++ { ++ .label = "cpu_mctp_ready", ++ .reg = NVSW_REG_GP0_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(1), ++ .mode = 0644, ++ }, ++ { ++ .label = "cpu_shutdown_req", ++ .reg = NVSW_REG_GP0_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(2), ++ .mode = 0444, ++ }, ++ { ++ .label = "vpd_wp", ++ .reg = NVSW_REG_GP0_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(3), ++ .mode = 0644, ++ .secured = 1, ++ }, ++ { ++ .label = "pcie_asic_reset_dis", ++ .reg = NVSW_REG_GP0_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(4), ++ .mode = 0644, ++ }, ++ { ++ .label = "shutdown_unlock", ++ .reg = NVSW_REG_GP0_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(5), ++ .mode = 0644, ++ }, ++ { ++ .label = "cpu_power_off_ready", ++ .reg = NVSW_REG_GP0_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(6), ++ .mode = 0644, ++ }, ++ { ++ .label = "ignore_next_reset", ++ .reg = NVSW_REG_GP0_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(7), ++ .mode = 0644, ++ }, ++ { ++ .label = "pwr_cycle", ++ .reg = NVSW_REG_GP1_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(2), ++ .mode = 0200, ++ }, ++ { ++ .label = "pwr_down", ++ .reg = NVSW_REG_GP1_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(3), ++ .mode = 0644, ++ }, ++ { ++ .label = "aux_pwr_cycle", ++ .reg = NVSW_REG_GP1_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(4), ++ .mode = 0200, ++ }, ++ { ++ .label = "bmc_to_cpu_ctrl", ++ .reg = NVSW_REG_GP1_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(5), ++ .mode = 0644, ++ }, ++ { ++ .label = "uart_sel", ++ .reg = NVSW_REG_GP1_OFFSET, ++ .mask = NVSW_UART_SEL_MASK, ++ .bit = 7, ++ .mode = 0644, ++ }, ++ { ++ .label = "global_wp_disable_req", ++ .reg = NVSW_REG_GP4_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(0), ++ .mode = 0644, ++ }, ++ { ++ .label = "bmc_monitor", ++ .reg = NVSW_REG_GP4_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(1), ++ .mode = 0644, ++ }, ++ { ++ .label = "smbus_vpd", ++ .reg = NVSW_REG_GP4_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(2), ++ .mode = 0644, ++ }, ++ { ++ .label = "serirq_mode", ++ .reg = NVSW_REG_GP4_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(3), ++ .mode = 0644, ++ }, ++ { ++ .label = "gpio_cpld_fu", ++ .reg = NVSW_REG_GP4_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(4), ++ .mode = 0644, ++ }, ++ { ++ .label = "b2b_vpd", ++ .reg = NVSW_REG_GP4_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(5), ++ .mode = 0644, ++ }, ++ { ++ .label = "comm_chnl_ready", ++ .reg = NVSW_REG_GP4_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(6), ++ .mode = 0644, ++ }, ++ { ++ .label = "smb2i2c", ++ .reg = NVSW_REG_GP4_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(7), ++ .mode = 0644, ++ }, ++ { ++ .label = "sys_reset", ++ .reg = NVSW_REG_RESET_GP2_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(0), ++ .mode = 0644, ++ }, ++ { ++ .label = "asic4_reset", ++ .reg = NVSW_REG_RESET_GP2_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(2), ++ .mode = 0644, ++ }, ++ { ++ .label = "asics_reset", ++ .reg = NVSW_REG_RESET_GP2_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(3), ++ .mode = 0644, ++ }, ++ { ++ .label = "sgmii_phy_reset", ++ .reg = NVSW_REG_RESET_GP2_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(4), ++ .mode = 0644, ++ }, ++ { ++ .label = "asic3_reset", ++ .reg = NVSW_REG_RESET_GP2_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(5), ++ .mode = 0644, ++ }, ++ { ++ .label = "asic2_reset", ++ .reg = NVSW_REG_RESET_GP2_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(6), ++ .mode = 0644, ++ }, ++ { ++ .label = "asic1_reset", ++ .reg = NVSW_REG_RESET_GP2_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(7), ++ .mode = 0644, ++ }, ++ { ++ .label = "platform_reset", ++ .reg = NVSW_REG_RESET_GP1_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(0), ++ .mode = 0644, ++ }, ++ { ++ .label = "main_reset", ++ .reg = NVSW_REG_RESET_GP1_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(2), ++ .mode = 0644, ++ }, ++ { ++ .label = "ser_irq_reset", ++ .reg = NVSW_REG_RESET_GP1_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(3), ++ .mode = 0644, ++ }, ++ { ++ .label = "i219_pe_reset", ++ .reg = NVSW_REG_RESET_GP1_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(5), ++ .mode = 0644, ++ }, ++ { ++ .label = "tpm_reset", ++ .reg = NVSW_REG_RESET_GP1_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(6), ++ .mode = 0644, ++ }, ++ { ++ .label = "reset_long_pb", ++ .reg = NVSW_REG_RESET_CAUSE_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(0), ++ .mode = 0444, ++ }, ++ { ++ .label = "reset_short_pb", ++ .reg = NVSW_REG_RESET_CAUSE_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(1), ++ .mode = 0444, ++ }, ++ { ++ .label = "reset_aux_pwr_or_fu", ++ .reg = NVSW_REG_RESET_CAUSE_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(2), ++ .mode = 0444, ++ }, ++ { ++ .label = "reset_swb_dc_dc_pwr_fail", ++ .reg = NVSW_REG_RESET_CAUSE_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(3), ++ .mode = 0444, ++ }, ++ { ++ .label = "reset_from_comex", ++ .reg = NVSW_REG_RESET_CAUSE_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(4), ++ .mode = 0444, ++ }, ++ { ++ .label = "reset_swb_wd", ++ .reg = NVSW_REG_RESET_CAUSE_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(6), ++ .mode = 0444, ++ }, ++ { ++ .label = "reset_asic_thermal", ++ .reg = NVSW_REG_RESET_CAUSE_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(7), ++ .mode = 0444, ++ }, ++ { ++ .label = "reset_from_sw_cmd", ++ .reg = NVSW_REG_RESET_CAUSE1_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(0), ++ .mode = 0444, ++ }, ++ { ++ .label = "reset_from_main_brd", ++ .reg = NVSW_REG_RESET_CAUSE1_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(1), ++ .mode = 0444, ++ }, ++ { ++ .label = "reset_from_cpld", ++ .reg = NVSW_REG_RESET_CAUSE1_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(2), ++ .mode = 0444, ++ }, ++ { ++ .label = "reset_comex_pwr_fail", ++ .reg = NVSW_REG_RESET_CAUSE1_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(3), ++ .mode = 0444, ++ }, ++ { ++ .label = "reset_platform", ++ .reg = NVSW_REG_RESET_CAUSE1_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(4), ++ .mode = 0444, ++ }, ++ { ++ .label = "reset_soc", ++ .reg = NVSW_REG_RESET_CAUSE1_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(5), ++ .mode = 0444, ++ }, ++ { ++ .label = "reset_from_carrier_brd", ++ .reg = NVSW_REG_RESET_CAUSE1_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(7), ++ .mode = 0444, ++ }, ++ { ++ .label = "reset_system", ++ .reg = NVSW_REG_RESET_CAUSE2_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(1), ++ .mode = 0444, ++ }, ++ { ++ .label = "reset_sw_pwr_off", ++ .reg = NVSW_REG_RESET_CAUSE2_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(2), ++ .mode = 0444, ++ }, ++ { ++ .label = "reset_comex_thermal", ++ .reg = NVSW_REG_RESET_CAUSE2_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(3), ++ .mode = 0444, ++ }, ++ { ++ .label = "reset_comex_power", ++ .reg = NVSW_REG_RESET_CAUSE2_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(4), ++ .mode = 0444, ++ }, ++ { ++ .label = "reset_pwr_converter_fail", ++ .reg = NVSW_REG_RESET_CAUSE2_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(5), ++ .mode = 0444, ++ }, ++ { ++ .label = "reset_main_48v", ++ .reg = NVSW_REG_RESET_CAUSE2_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(6), ++ .mode = 0444, ++ }, ++ { ++ .label = "reset_plt_pwr_fail", ++ .reg = NVSW_REG_RESET_CAUSE2_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(7), ++ .mode = 0444, ++ }, ++ { ++ .label = "comex_tps", ++ .reg = NVSW_REG_GP2_RO_OFFSET, ++ .mask = GENMASK(5, 4), ++ .bit = 5, ++ .mode = 0444, ++ }, ++ { ++ .label = "port80", ++ .reg = NVSW_REG_GP1_RO_OFFSET, ++ .bit = GENMASK(7, 0), ++ .mode = 0444, ++ }, ++ { ++ .label = "asic2_clk_brd2_boot_fail", ++ .reg = NVSW_REG_GP4_RO_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(0), ++ .mode = 0444, ++ }, ++ { ++ .label = "asic2_clk_brd1_boot_fail", ++ .reg = NVSW_REG_GP4_RO_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(1), ++ .mode = 0444, ++ }, ++ { ++ .label = "sb_asic2_clk_brd_fail", ++ .reg = NVSW_REG_GP4_RO_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(2), ++ .mode = 0444, ++ }, ++ { ++ .label = "asic2_pg", ++ .reg = NVSW_REG_GP4_RO_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(3), ++ .mode = 0444, ++ }, ++ { ++ .label = "asic1_clk_brd2_boot_fail", ++ .reg = NVSW_REG_GP4_RO_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(4), ++ .mode = 0444, ++ }, ++ { ++ .label = "asic1_clk_brd1_boot_fail", ++ .reg = NVSW_REG_GP4_RO_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(5), ++ .mode = 0444, ++ }, ++ { ++ .label = "sb_asic1_clk_brd_fail", ++ .reg = NVSW_REG_GP4_RO_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(6), ++ .mode = 0444, ++ }, ++ { ++ .label = "asic1_pg", ++ .reg = NVSW_REG_GP4_RO_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(7), ++ .mode = 0444, ++ }, ++ { ++ .label = "asic4_clk_brd2_boot_fail", ++ .reg = NVSW_REG_GP5_RO_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(0), ++ .mode = 0444, ++ }, ++ { ++ .label = "asic4_clk_brd1_boot_fail", ++ .reg = NVSW_REG_GP5_RO_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(1), ++ .mode = 0444, ++ }, ++ { ++ .label = "sb_asic4_clk_brd_fail", ++ .reg = NVSW_REG_GP5_RO_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(2), ++ .mode = 0444, ++ }, ++ { ++ .label = "asic4_pg", ++ .reg = NVSW_REG_GP5_RO_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(3), ++ .mode = 0444, ++ }, ++ { ++ .label = "asic3_clk_brd2_boot_fail", ++ .reg = NVSW_REG_GP5_RO_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(4), ++ .mode = 0444, ++ }, ++ { ++ .label = "asic3_clk_brd1_boot_fail", ++ .reg = NVSW_REG_GP5_RO_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(5), ++ .mode = 0444, ++ }, ++ { ++ .label = "sb_asic3_clk_brd_fail", ++ .reg = NVSW_REG_GP5_RO_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(6), ++ .mode = 0444, ++ }, ++ { ++ .label = "asic3_pg", ++ .reg = NVSW_REG_GP5_RO_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(7), ++ .mode = 0444, ++ }, ++ { ++ .label = "jtag_cap", ++ .reg = NVSW_REG_FU_CAP_OFFSET, ++ .mask = NVSW_FU_CAP_MASK, ++ .bit = 1, ++ .mode = 0444, ++ }, ++ { ++ .label = "jtag_enable", ++ .reg = NVSW_REG_FIELD_UPGRADE, ++ .mask = GENMASK(1, 0), ++ .bit = 1, ++ .mode = 0644, ++ }, ++ { ++ .label = "non_active_bios_select", ++ .reg = NVSW_REG_SAFE_BIOS_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(4), ++ .mode = 0644, ++ }, ++ { ++ .label = "smb_erot", ++ .reg = NVSW_REG_SAFE_BIOS_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(5), ++ .mode = 0444, ++ }, ++ { ++ .label = "bios_image_invert", ++ .reg = NVSW_REG_SAFE_BIOS_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(6), ++ .mode = 0644, ++ }, ++ { ++ .label = "me_reboot", ++ .reg = NVSW_REG_SAFE_BIOS_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(7), ++ .mode = 0644, ++ }, ++ { ++ .label = "asic4_perst", ++ .reg = NVSW_REG_PWM_CONTROL_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(0), ++ .mode = 0644, ++ }, ++ { ++ .label = "clk_brd_program", ++ .reg = NVSW_REG_PWM_CONTROL_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(1), ++ .mode = 0644, ++ }, ++ { ++ .label = "asic3_perst", ++ .reg = NVSW_REG_PWM_CONTROL_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(2), ++ .mode = 0644, ++ }, ++ { ++ .label = "asic2_perst", ++ .reg = NVSW_REG_PWM_CONTROL_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(3), ++ .mode = 0644, ++ }, ++ { ++ .label = "asic1_perst", ++ .reg = NVSW_REG_PWM_CONTROL_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(4), ++ .mode = 0644, ++ }, ++ { ++ .label = "cpu_int_enable", ++ .reg = NVSW_REG_GP5_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(0), ++ .mode = 0644, ++ }, ++ { ++ .label = "cpu_tps_upgrade", ++ .reg = NVSW_REG_GP5_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(1), ++ .mode = 0644, ++ }, ++ { ++ .label = "cpu_spi_ctrl", ++ .reg = NVSW_REG_GP5_OFFSET, ++ .mask = GENMASK(4, 2), ++ .bit = 4, ++ .mode = 0644, ++ }, ++ { ++ .label = "sml_connection", ++ .reg = NVSW_REG_GP5_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(5), ++ .mode = 0644, ++ }, ++ { ++ .label = "bmc_reset_reg", ++ .reg = NVSW_REG_GP6_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(1), ++ .mode = 0644, ++ }, ++ { ++ .label = "current_temp_ro", ++ .reg = NVSW_REG_CURRENT_TEMP_RO, ++ .bit = GENMASK(7, 0), ++ .mode = 0444, ++ }, ++ { ++ .label = "switch_ic_qty", ++ .reg = NVSW_REG_SWITCH_IC_QTY, ++ .bit = GENMASK(7, 0), ++ .mode = 0444, ++ }, ++ { ++ .label = "asic_pg_fail", ++ .reg = NVSW_REG_GP4_RO_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(7), ++ .mode = 0444, ++ }, ++ { ++ .label = "spi_chnl_select", ++ .reg = NVSW_REG_SPI_CHNL_SELECT, ++ .mask = GENMASK(7, 0), ++ .bit = 1, ++ .mode = 0644, ++ }, ++ { ++ .label = "config1", ++ .reg = NVSW_REG_CONFIG1_OFFSET, ++ .bit = GENMASK(7, 0), ++ .mode = 0444, ++ }, ++ { ++ .label = "config2", ++ .reg = NVSW_REG_CONFIG2_OFFSET, ++ .bit = GENMASK(7, 0), ++ .mode = 0444, ++ }, ++ { ++ .label = "config3", ++ .reg = NVSW_REG_CONFIG3_OFFSET, ++ .bit = GENMASK(7, 0), ++ .mode = 0444, ++ }, ++ { ++ .label = "i2c_freq", ++ .reg = NVSW_REG_SYS_CPBLTY0, ++ .mask = GENMASK(5, 4), ++ .bit = 5, ++ .mode = 0444, ++ }, ++ { ++ .label = "wd_type", ++ .reg = NVSW_REG_SYS_CPBLTY0, ++ .mask = GENMASK(7, 0) & ~BIT(6), ++ .mode = 0444, ++ }, ++ { ++ .label = "avlbl_reg_new_format", ++ .reg = NVSW_REG_SYS_CPBLTY0, ++ .mask = GENMASK(7, 0) & ~BIT(7), ++ .mode = 0444, + }, +}; + -+static -+struct mlxreg_core_hotplug_platform_data nvsw_bmc_hid185_hotplug = { -+ .items = nvsw_bmc_hid185_hotplug_items_data, -+ .count = ARRAY_SIZE(nvsw_bmc_hid185_hotplug_items_data), -+ .cell = NVSW_REG_AGGR_OFFSET, -+ .mask = NVSW_AGGR_MASK | NVSW_AGGR_MASK_COMEX, -+ .cell_low = NVSW_REG_AGGRLO_OFFSET, -+ .mask_low = GENMASK(6, 0), -+ .deferred_nr = 5, -+}; -+ -+/* Platform register access data. */ -+static struct mlxreg_core_data nvsw_bmc_hid162_regio_data[] = { ++static struct mlxreg_core_data nvsw_bmc_hid191_regio_data[] = { + { + .label = "cpld1_version", + .reg = NVSW_REG_CPLD1_VER_OFFSET, @@ -1908,6 +5458,12 @@ index 000000000..3c70e2979 + .mode = 0444, + }, + { ++ .label = "cpld5_version", ++ .reg = NVSW_REG_CPLD5_VER_OFFSET, ++ .bit = GENMASK(7, 0), ++ .mode = 0444, ++ }, ++ { + .label = "cpld1_pn", + .reg = NVSW_REG_CPLD1_PN_OFFSET, + .bit = GENMASK(15, 0), @@ -1936,6 +5492,13 @@ index 000000000..3c70e2979 + .regnum = 2, + }, + { ++ .label = "cpld5_pn", ++ .reg = NVSW_REG_CPLD5_PN_OFFSET, ++ .bit = GENMASK(15, 0), ++ .mode = 0444, ++ .regnum = 2, ++ }, ++ { + .label = "cpld1_version_min", + .reg = NVSW_REG_CPLD1_MVER_OFFSET, + .bit = GENMASK(7, 0), @@ -1960,85 +5523,256 @@ index 000000000..3c70e2979 + .mode = 0444, + }, + { -+ .label = "fan_dir", -+ .reg = NVSW_REG_GP0_RO_OFFSET, -+ .mask = GENMASK(7, 0), ++ .label = "cpld5_version_min", ++ .reg = NVSW_REG_CPLD5_MVER_OFFSET, ++ .bit = GENMASK(7, 0), + .mode = 0444, + }, + { -+ .label = "fan_present", -+ .reg = NVSW_REG_FAN_OFFSET, -+ .mask = GENMASK(7, 0), -+ .mode = 0444, ++ .label = "cpld1_dbg_led", ++ .reg = NVSW_REG_CPLD1_DBG_LED_OFFSET, ++ .bit = GENMASK(7, 0), ++ .mode = 0644, + }, + { -+ .label = "cpu_mctp_ready", -+ .reg = NVSW_REG_GP0_OFFSET, -+ .mask = GENMASK(7, 0) & ~BIT(1), ++ .label = "cpld2_dbg_led", ++ .reg = NVSW_REG_CPLD2_DBG_LED_OFFSET, ++ .bit = GENMASK(7, 0), + .mode = 0644, + }, + { -+ .label = "cpu_shutdown_req", -+ .reg = NVSW_REG_GP0_OFFSET, -+ .mask = GENMASK(7, 0) & ~BIT(2), ++ .label = "cpld3_dbg_led", ++ .reg = NVSW_REG_CPLD3_DBG_LED_OFFSET, ++ .bit = GENMASK(7, 0), + .mode = 0644, + }, + { -+ .label = "vpd_wp", -+ .reg = NVSW_REG_GP0_OFFSET, -+ .mask = GENMASK(7, 0) & ~BIT(3), ++ .label = "cpld4_dbg_led", ++ .reg = NVSW_REG_CPLD4_DBG_LED_OFFSET, ++ .bit = GENMASK(7, 0), + .mode = 0644, -+ .secured = 1, + }, + { -+ .label = "pcie_asic_reset_dis", -+ .reg = NVSW_REG_GP0_OFFSET, ++ .label = "cpld6_version", ++ .reg = NVSW_REG_CPLD6_PN_OFFSET, ++ .bit = GENMASK(7, 0), ++ .mode = 0444, ++ }, ++ { ++ .label = "cpld6_pn", ++ .reg = NVSW_REG_CPLD6_PN1_OFFSET, ++ .bit = GENMASK(15, 0), ++ .mode = 0444, ++ .regnum = 2, ++ }, ++ { ++ .label = "cpld7_version", ++ .reg = NVSW_REG_CPLD7_VER_OFFSET, ++ .bit = GENMASK(7, 0), ++ .mode = 0444, ++ }, ++ { ++ .label = "cpld7_pn", ++ .reg = NVSW_REG_CPLD7_PN_OFFSET, ++ .bit = GENMASK(15, 0), ++ .mode = 0444, ++ .regnum = 2, ++ }, ++ { ++ .label = "cpld8_version", ++ .reg = NVSW_REG_CPLD8_VER_OFFSET, ++ .bit = GENMASK(7, 0), ++ .mode = 0444, ++ }, ++ { ++ .label = "cpld8_pn", ++ .reg = NVSW_REG_CPLD8_PN_OFFSET, ++ .bit = GENMASK(15, 0), ++ .mode = 0444, ++ .regnum = 2, ++ }, ++ { ++ .label = "cpld9_version", ++ .reg = NVSW_REG_CPLD9_VER_OFFSET, ++ .bit = GENMASK(7, 0), ++ .mode = 0444, ++ }, ++ { ++ .label = "cpld9_pn", ++ .reg = NVSW_REG_CPLD9B_PN_OFFSET, ++ .bit = GENMASK(15, 0), ++ .mode = 0444, ++ .regnum = 2, ++ }, ++ { ++ .label = "cpld10_version", ++ .reg = NVSW_REG_CPLD10_VER_OFFSET, ++ .bit = GENMASK(7, 0), ++ .mode = 0444, ++ }, ++ { ++ .label = "cpld10_pn", ++ .reg = NVSW_REG_CPLD10_PN_OFFSET, ++ .bit = GENMASK(15, 0), ++ .mode = 0444, ++ .regnum = 2, ++ }, ++ { ++ .label = "cpld6_version_min", ++ .reg = NVSW_REG_CPLD6_MVER_OFFSET, ++ .bit = GENMASK(7, 0), ++ .mode = 0444, ++ }, ++ { ++ .label = "cpld7_version_min", ++ .reg = NVSW_REG_CPLD7_MVER_OFFSET, ++ .bit = GENMASK(7, 0), ++ .mode = 0444, ++ }, ++ { ++ .label = "cpld8_version_min", ++ .reg = NVSW_REG_CPLD8_MVER_OFFSET, ++ .bit = GENMASK(7, 0), ++ .mode = 0444, ++ }, ++ { ++ .label = "cpld9_version_min", ++ .reg = NVSW_REG_CPLD9_MVER_OFFSET, ++ .bit = GENMASK(7, 0), ++ .mode = 0444, ++ }, ++ { ++ .label = "cpld10_version_min", ++ .reg = NVSW_REG_CPLD10_MVER_OFFSET, ++ .bit = GENMASK(7, 0), ++ .mode = 0444, ++ }, ++ { ++ .label = "sb_asic1_clk_brd_ci_boot_fail", ++ .reg = NVSW_REG_GP2_RO_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(0), ++ .mode = 0444, ++ }, ++ { ++ .label = "sb_asic1_clk_brd_fail", ++ .reg = NVSW_REG_GP2_RO_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(1), ++ .mode = 0444, ++ }, ++ { ++ .label = "asic1_pg", ++ .reg = NVSW_REG_GP2_RO_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(2), ++ .mode = 0444, ++ }, ++ { ++ .label = "sb_asic2_clk_brd_ci_boot_fail", ++ .reg = NVSW_REG_GP2_RO_OFFSET, + .mask = GENMASK(7, 0) & ~BIT(4), -+ .mode = 0644, ++ .mode = 0444, + }, + { -+ .label = "shutdown_unlock", -+ .reg = NVSW_REG_GP0_OFFSET, ++ .label = "sb_asic2_clk_brd_fail", ++ .reg = NVSW_REG_GP2_RO_OFFSET, + .mask = GENMASK(7, 0) & ~BIT(5), -+ .mode = 0644, ++ .mode = 0444, + }, + { -+ .label = "cpu_power_off_ready", -+ .reg = NVSW_REG_GP0_OFFSET, ++ .label = "asic2_pg", ++ .reg = NVSW_REG_GP2_RO_OFFSET, + .mask = GENMASK(7, 0) & ~BIT(6), -+ .mode = 0644, ++ .mode = 0444, + }, + { -+ .label = "ignore_next_reset", -+ .reg = NVSW_REG_GP0_OFFSET, -+ .mask = GENMASK(7, 0) & ~BIT(7), ++ .label = "asic1_perst", ++ .reg = NVSW_REG_GP4_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(0), + .mode = 0644, + }, + { -+ .label = "leakage_conn_en", -+ .reg = NVSW_REG_GP6_OFFSET, -+ .mask = GENMASK(7, 0) & ~BIT(0), ++ .label = "asic2_perst", ++ .reg = NVSW_REG_GP4_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(1), + .mode = 0644, + }, + { -+ .label = "bmc_reset_reg", -+ .reg = NVSW_REG_GP6_OFFSET, -+ .mask = GENMASK(7, 0) & ~BIT(1), ++ .label = "asic3_perst", ++ .reg = NVSW_REG_GP4_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(2), + .mode = 0644, + }, + { -+ .label = "spi_chnl_select", -+ .reg = NVSW_REG_SPI_CHNL_SELECT, -+ .mask = GENMASK(7, 0), -+ .bit = 1, ++ .label = "asic4_perst", ++ .reg = NVSW_REG_GP4_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(3), + .mode = 0644, + }, + { ++ .label = "sb_asic3_clk_brd_ci_boot_fail", ++ .reg = NVSW_REG_GP5_RO_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(0), ++ .mode = 0444, ++ }, ++ { ++ .label = "sb_asic3_clk_brd_fail", ++ .reg = NVSW_REG_GP5_RO_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(1), ++ .mode = 0444, ++ }, ++ { ++ .label = "asic3_pg", ++ .reg = NVSW_REG_GP5_RO_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(2), ++ .mode = 0444, ++ }, ++ { ++ .label = "sb_asic4_clk_brd_ci_boot_fail", ++ .reg = NVSW_REG_GP5_RO_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(4), ++ .mode = 0444, ++ }, ++ { ++ .label = "sb_asic4_clk_brd_fail", ++ .reg = NVSW_REG_GP5_RO_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(5), ++ .mode = 0444, ++ }, ++ { ++ .label = "asic4_pg", ++ .reg = NVSW_REG_GP5_RO_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(6), ++ .mode = 0444, ++ }, ++ { ++ .label = "bios_status_ro", ++ .reg = NVSW_REG_GPCOM0_OFFSET, ++ .mask = GENMASK(3, 1), ++ .bit = 3, ++ .mode = 0444, ++ }, ++ { ++ .label = "bios_start_retry", ++ .reg = NVSW_REG_GPCOM0_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(4), ++ .mode = 0444, ++ }, ++ { ++ .label = "bios_active_image", ++ .reg = NVSW_REG_GPCOM0_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(5), ++ .mode = 0444, ++ }, ++ { + .label = "pwr_converter_prog_en", + .reg = NVSW_REG_GP7_OFFSET, + .mask = GENMASK(7, 0) & ~BIT(0), + .mode = 0644, -+ .secured = 1, ++ }, ++ { ++ .label = "bmc_req_conf_flash_updt", ++ .reg = NVSW_REG_GP7_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(1), ++ .mode = 0644, + }, + { + .label = "graceful_power_off", @@ -2059,150 +5793,163 @@ index 000000000..3c70e2979 + .mode = 0200, + }, + { -+ .label = "platform_reset", -+ .reg = NVSW_REG_RESET_GP1_OFFSET, -+ .mask = GENMASK(7, 0) & ~BIT(0), -+ .mode = 0200, ++ .label = "stby_pwr_en_unmask", ++ .reg = NVSW_REG_GP7_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(5), ++ .mode = 0200, ++ }, ++ { ++ .label = "uart_baud_rate", ++ .reg = NVSW_REG_UART_BAUD_OFFSET, ++ .mask = NVSW_UART_BAUD_MASK, ++ .bit = 1, ++ .mode = 0644, ++ }, ++ { ++ .label = "cpu_mctp_ready", ++ .reg = NVSW_REG_GP0_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(1), ++ .mode = 0644, ++ }, ++ { ++ .label = "cpu_shutdown_req", ++ .reg = NVSW_REG_GP0_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(2), ++ .mode = 0444, + }, + { -+ .label = "main_brd_reset", -+ .reg = NVSW_REG_RESET_GP1_OFFSET, -+ .mask = GENMASK(7, 0) & ~BIT(2), -+ .mode = 0200, ++ .label = "vpd_wp", ++ .reg = NVSW_REG_GP0_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(3), ++ .mode = 0644, ++ .secured = 1, + }, + { -+ .label = "nic_reset", -+ .reg = NVSW_REG_RESET_GP1_OFFSET, -+ .mask = GENMASK(7, 0) & ~BIT(5), -+ .mode = 0200, ++ .label = "pcie_asic_reset_dis", ++ .reg = NVSW_REG_GP0_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(4), ++ .mode = 0644, + }, + { -+ .label = "tpm_reset", -+ .reg = NVSW_REG_RESET_GP1_OFFSET, ++ .label = "cpu_power_off_ready", ++ .reg = NVSW_REG_GP0_OFFSET, + .mask = GENMASK(7, 0) & ~BIT(6), -+ .mode = 0200, ++ .mode = 0644, + }, + { -+ .label = "erot_asic3_reset", -+ .reg = NVSW_REG_RESET_GP2_OFFSET, ++ .label = "pwr_cycle", ++ .reg = NVSW_REG_GP1_OFFSET, + .mask = GENMASK(7, 0) & ~BIT(2), + .mode = 0200, + }, + { -+ .label = "asics_reset", -+ .reg = NVSW_REG_RESET_GP2_OFFSET, ++ .label = "pwr_down", ++ .reg = NVSW_REG_GP1_OFFSET, + .mask = GENMASK(7, 0) & ~BIT(3), -+ .mode = 0200, ++ .mode = 0644, + }, + { -+ .label = "sgmii_phy_reset", -+ .reg = NVSW_REG_RESET_GP2_OFFSET, ++ .label = "aux_pwr_cycle", ++ .reg = NVSW_REG_GP1_OFFSET, + .mask = GENMASK(7, 0) & ~BIT(4), + .mode = 0200, + }, + { -+ .label = "erot_cpu_reset", -+ .reg = NVSW_REG_RESET_GP2_OFFSET, -+ .mask = GENMASK(7, 0) & ~BIT(6), -+ .mode = 0200, ++ .label = "bmc_to_cpu_ctrl", ++ .reg = NVSW_REG_GP1_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(5), ++ .mode = 0644, + }, + { -+ .label = "erot_asic1_reset", -+ .reg = NVSW_REG_RESET_GP2_OFFSET, -+ .mask = GENMASK(7, 0) & ~BIT(6), -+ .mode = 0200, ++ .label = "uart_sel", ++ .reg = NVSW_REG_GP1_OFFSET, ++ .mask = NVSW_UART_SEL_MASK, ++ .bit = 7, ++ .mode = 0644, + }, + { -+ .label = "erot_asic2_reset", ++ .label = "sys_reset", + .reg = NVSW_REG_RESET_GP2_OFFSET, -+ .mask = GENMASK(7, 0) & ~BIT(7), -+ .mode = 0200, -+ }, -+ { -+ .label = "jtag_enable", -+ .reg = NVSW_REG_FIELD_UPGRADE, -+ .mask = GENMASK(1, 0), -+ .bit = 1, ++ .mask = GENMASK(7, 0) & ~BIT(0), + .mode = 0644, + }, + { -+ .label = "non_active_bios_select", -+ .reg = NVSW_REG_SAFE_BIOS_OFFSET, -+ .mask = GENMASK(7, 0) & ~BIT(4), ++ .label = "asic1_reset", ++ .reg = NVSW_REG_RESET_GP2_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(1), + .mode = 0644, + }, + { -+ .label = "bios_upgrade_fail", -+ .reg = NVSW_REG_SAFE_BIOS_OFFSET, -+ .mask = GENMASK(7, 0) & ~BIT(5), -+ .mode = 0444, ++ .label = "asic2_reset", ++ .reg = NVSW_REG_RESET_GP2_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(2), ++ .mode = 0644, + }, + { -+ .label = "bios_image_invert", -+ .reg = NVSW_REG_SAFE_BIOS_OFFSET, -+ .mask = GENMASK(7, 0) & ~BIT(6), ++ .label = "asics_reset", ++ .reg = NVSW_REG_RESET_GP2_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(3), + .mode = 0644, + }, + { -+ .label = "erot_asic3_recovery", -+ .reg = NVSW_REG_PWM_CONTROL_OFFSET, ++ .label = "asic3_reset", ++ .reg = NVSW_REG_RESET_GP2_OFFSET, + .mask = GENMASK(7, 0) & ~BIT(4), + .mode = 0644, + }, + { -+ .label = "erot_cpu_recovery", -+ .reg = NVSW_REG_PWM_CONTROL_OFFSET, ++ .label = "asic4_reset", ++ .reg = NVSW_REG_RESET_GP2_OFFSET, + .mask = GENMASK(7, 0) & ~BIT(5), + .mode = 0644, + }, + { -+ .label = "erot_asic1_recovery", -+ .reg = NVSW_REG_PWM_CONTROL_OFFSET, ++ .label = "mcu1_reset", ++ .reg = NVSW_REG_RESET_GP2_OFFSET, + .mask = GENMASK(7, 0) & ~BIT(6), + .mode = 0644, + }, + { -+ .label = "erot_asic2_recovery", -+ .reg = NVSW_REG_PWM_CONTROL_OFFSET, ++ .label = "mcu2_reset", ++ .reg = NVSW_REG_RESET_GP2_OFFSET, + .mask = GENMASK(7, 0) & ~BIT(7), + .mode = 0644, + }, + { -+ .label = "pwr_button_halt", -+ .reg = NVSW_REG_GP1_OFFSET, -+ .mask = GENMASK(7, 0) & ~BIT(1), -+ .mode = 0644, -+ }, -+ { -+ .label = "pwr_cycle", -+ .reg = NVSW_REG_GP1_OFFSET, ++ .label = "platform_reset", ++ .reg = NVSW_REG_RESET_GP1_OFFSET, + .mask = GENMASK(7, 0) & ~BIT(2), + .mode = 0200, + }, + { -+ .label = "pwr_down", -+ .reg = NVSW_REG_GP1_OFFSET, ++ .label = "gp_cpld_rtc_clr", ++ .reg = NVSW_REG_RESET_GP1_OFFSET, + .mask = GENMASK(7, 0) & ~BIT(3), -+ .mode = 0644, ++ .mode = 0200, + }, + { -+ .label = "aux_pwr_cycle", -+ .reg = NVSW_REG_GP1_OFFSET, ++ .label = "cpld_phy2_rst", ++ .reg = NVSW_REG_RESET_GP1_OFFSET, + .mask = GENMASK(7, 0) & ~BIT(4), + .mode = 0200, + }, + { -+ .label = "bmc_to_cpu_ctrl", -+ .reg = NVSW_REG_GP1_OFFSET, ++ .label = "cpld_phy1_reset", ++ .reg = NVSW_REG_RESET_GP1_OFFSET, + .mask = GENMASK(7, 0) & ~BIT(5), -+ .mode = 0644, ++ .mode = 0200, + }, + { -+ .label = "uart_sel", -+ .reg = NVSW_REG_GP1_OFFSET, -+ .mask = NVSW_UART_SEL_MASK, -+ .bit = 7, -+ .mode = 0644, ++ .label = "tpm_reset", ++ .reg = NVSW_REG_RESET_GP1_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(6), ++ .mode = 0200, ++ }, ++ { ++ .label = "bmc_tpm_reset", ++ .reg = NVSW_REG_RESET_GP1_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(7), ++ .mode = 0200, + }, + { + .label = "reset_long_pb", @@ -2223,194 +5970,272 @@ index 000000000..3c70e2979 + .mode = 0444, + }, + { -+ .label = "reset_swb_pwr_fail", ++ .label = "reset_swb_dc_dc_pwr_fail", + .reg = NVSW_REG_RESET_CAUSE_OFFSET, + .mask = GENMASK(7, 0) & ~BIT(3), + .mode = 0444, + }, + { -+ .label = "reset_cpu", ++ .label = "reset_swb_wd", + .reg = NVSW_REG_RESET_CAUSE_OFFSET, -+ .mask = GENMASK(7, 0) & ~BIT(4), ++ .mask = GENMASK(7, 0) & ~BIT(6), + .mode = 0444, + }, + { -+ .label = "reset_pwr_button", ++ .label = "reset_asic_thermal", + .reg = NVSW_REG_RESET_CAUSE_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(7), ++ .mode = 0444, ++ }, ++ { ++ .label = "reset_soc", ++ .reg = NVSW_REG_RESET_CAUSE1_OFFSET, + .mask = GENMASK(7, 0) & ~BIT(5), + .mode = 0444, + }, + { -+ .label = "reset_swb_wd", -+ .reg = NVSW_REG_RESET_CAUSE_OFFSET, ++ .label = "reset_system", ++ .reg = NVSW_REG_RESET_CAUSE2_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(1), ++ .mode = 0444, ++ }, ++ { ++ .label = "reset_sw_pwr_off", ++ .reg = NVSW_REG_RESET_CAUSE2_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(2), ++ .mode = 0444, ++ }, ++ { ++ .label = "reset_thermal", ++ .reg = NVSW_REG_RESET_CAUSE2_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(3), ++ .mode = 0444, ++ }, ++ { ++ .label = "main_12v_fail", ++ .reg = NVSW_REG_RESET_CAUSE2_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(5), ++ .mode = 0444, ++ }, ++ { ++ .label = "reset_main_51v", ++ .reg = NVSW_REG_RESET_CAUSE2_OFFSET, + .mask = GENMASK(7, 0) & ~BIT(6), + .mode = 0444, + }, + { -+ .label = "reset_asic_thermal", -+ .reg = NVSW_REG_RESET_CAUSE_OFFSET, ++ .label = "reset_mgmt_pwr", ++ .reg = NVSW_REG_RESET_CAUSE2_OFFSET, + .mask = GENMASK(7, 0) & ~BIT(7), + .mode = 0444, + }, + { -+ .label = "reset_from_carrier", -+ .reg = NVSW_REG_RESET_CAUSE1_OFFSET, ++ .label = "port80", ++ .reg = NVSW_REG_GP1_RO_OFFSET, ++ .bit = GENMASK(7, 0), ++ .mode = 0444, ++ }, ++ { ++ .label = "switch_ic_qty", ++ .reg = NVSW_REG_SWITCH_IC_QTY, ++ .bit = GENMASK(7, 0), ++ .mode = 0444, ++ }, ++ { ++ .label = "gp_swb_mgmt_present", ++ .reg = NVSW_REG_GP4_RO_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(0), ++ .mode = 0444, ++ }, ++ { ++ .label = "gp_io_brd_prsnt", ++ .reg = NVSW_REG_GP4_RO_OFFSET, + .mask = GENMASK(7, 0) & ~BIT(1), + .mode = 0444, + }, + { -+ .label = "reset_aux_pwr_or_reload", -+ .reg = NVSW_REG_RESET_CAUSE1_OFFSET, ++ .label = "gp_tpm_brd_prsnt", ++ .reg = NVSW_REG_GP4_RO_OFFSET, + .mask = GENMASK(7, 0) & ~BIT(2), + .mode = 0444, + }, + { -+ .label = "reset_comex_pwr_fail", -+ .reg = NVSW_REG_RESET_CAUSE1_OFFSET, ++ .label = "gp_bmc_presnt", ++ .reg = NVSW_REG_GP4_RO_OFFSET, + .mask = GENMASK(7, 0) & ~BIT(3), + .mode = 0444, + }, + { -+ .label = "reset_platform", -+ .reg = NVSW_REG_RESET_CAUSE1_OFFSET, -+ .mask = GENMASK(7, 0) & ~BIT(4), ++ .label = "gp_bmc_tpm_brd_prsnt", ++ .reg = NVSW_REG_GP4_RO_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(5), + .mode = 0444, + }, + { -+ .label = "reset_soc", -+ .reg = NVSW_REG_RESET_CAUSE1_OFFSET, ++ .label = "jtag_cap", ++ .reg = NVSW_REG_FU_CAP_OFFSET, ++ .mask = NVSW_FU_CAP_MASK, ++ .bit = 1, ++ .mode = 0444, ++ }, ++ { ++ .label = "jtag_enable", ++ .reg = NVSW_REG_FIELD_UPGRADE, ++ .mask = GENMASK(7, 0) & ~BIT(1), ++ .mode = 0644, ++ }, ++ { ++ .label = "bios_status", ++ .reg = NVSW_REG_SAFE_BIOS_OFFSET, ++ .mask = GENMASK(2, 0), ++ .bit = 2, ++ .mode = 0644, ++ }, ++ { ++ .label = "conf_flash_updt_done", ++ .reg = NVSW_REG_SAFE_BIOS_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(3), ++ .mode = 0644, ++ }, ++ { ++ .label = "non_active_bios_select", ++ .reg = NVSW_REG_SAFE_BIOS_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(4), ++ .mode = 0644, ++ }, ++ { ++ .label = "bios_upgrade_fail", ++ .reg = NVSW_REG_SAFE_BIOS_OFFSET, + .mask = GENMASK(7, 0) & ~BIT(5), + .mode = 0444, + }, + { -+ .label = "reset_from_erot", -+ .reg = NVSW_REG_RESET_CAUSE1_OFFSET, ++ .label = "bios_image_invert", ++ .reg = NVSW_REG_SAFE_BIOS_OFFSET, + .mask = GENMASK(7, 0) & ~BIT(6), -+ .mode = 0444, ++ .mode = 0644, + }, + { -+ .label = "reset_pwr", -+ .reg = NVSW_REG_RESET_CAUSE1_OFFSET, ++ .label = "cpu_req_conf_flash_updt", ++ .reg = NVSW_REG_SAFE_BIOS_OFFSET, + .mask = GENMASK(7, 0) & ~BIT(7), -+ .mode = 0444, ++ .mode = 0644, + }, + { -+ .label = "reset_erot", -+ .reg = NVSW_REG_RESET_CAUSE2_OFFSET, -+ .mask = GENMASK(7, 0) & ~BIT(0), -+ .mode = 0444, ++ .label = "mcu1_recovery", ++ .reg = NVSW_REG_PWM_CONTROL_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(6), ++ .mode = 0644, + }, + { -+ .label = "reset_system", -+ .reg = NVSW_REG_RESET_CAUSE2_OFFSET, -+ .mask = GENMASK(7, 0) & ~BIT(1), -+ .mode = 0444, ++ .label = "gp_vdd_spd_dis", ++ .reg = NVSW_REG_PWM_CONTROL_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(4), ++ .mode = 0644, + }, + { -+ .label = "reset_sw_pwr_off", -+ .reg = NVSW_REG_RESET_CAUSE2_OFFSET, ++ .label = "gp_spd_b_wp", ++ .reg = NVSW_REG_PWM_CONTROL_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(3), ++ .mode = 0644, ++ }, ++ { ++ .label = "gp_spd_a_wp", ++ .reg = NVSW_REG_PWM_CONTROL_OFFSET, + .mask = GENMASK(7, 0) & ~BIT(2), -+ .mode = 0444, ++ .mode = 0644, + }, + { -+ .label = "reset_comex_thermal", -+ .reg = NVSW_REG_RESET_CAUSE2_OFFSET, -+ .mask = GENMASK(7, 0) & ~BIT(3), -+ .mode = 0444, ++ .label = "sb_clk_brd_prog_en", ++ .reg = NVSW_REG_PWM_CONTROL_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(1), ++ .mode = 0644, + }, + { -+ .label = "reset_comex_power", -+ .reg = NVSW_REG_RESET_CAUSE2_OFFSET, -+ .mask = GENMASK(7, 0) & ~BIT(4), -+ .mode = 0444, ++ .label = "cpu_int_enable", ++ .reg = NVSW_REG_GP5_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(0), ++ .mode = 0644, + }, + { -+ .label = "reset_pwr_converter_fail", -+ .reg = NVSW_REG_RESET_CAUSE2_OFFSET, -+ .mask = GENMASK(7, 0) & ~BIT(5), -+ .mode = 0444, ++ .label = "cpu_spi_ctrl", ++ .reg = NVSW_REG_GP5_OFFSET, ++ .mask = GENMASK(4, 2), ++ .bit = 4, ++ .mode = 0644, + }, + { -+ .label = "reset_main_51v", -+ .reg = NVSW_REG_RESET_CAUSE2_OFFSET, ++ .label = "ignore_next_reset", ++ .reg = NVSW_REG_GP5_OFFSET, + .mask = GENMASK(7, 0) & ~BIT(6), -+ .mode = 0444, ++ .mode = 0644, + }, + { -+ .label = "reset_mgmt_pwr", -+ .reg = NVSW_REG_RESET_CAUSE2_OFFSET, -+ .mask = GENMASK(7, 0) & ~BIT(7), -+ .mode = 0444, ++ .label = "bmc_reset_reg", ++ .reg = NVSW_REG_GP6_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(1), ++ .mode = 0644, + }, + { -+ .label = "port80", -+ .reg = NVSW_REG_GP1_RO_OFFSET, -+ .bit = GENMASK(7, 0), ++ .label = "asic_pg_fail", ++ .reg = NVSW_REG_GP4_RO_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(7), + .mode = 0444, + }, + { -+ .label = "bios_status", -+ .reg = NVSW_REG_GPCOM0_OFFSET, -+ .mask = NVSW_BIOS_STATUS_MASK, -+ .bit = 2, -+ .mode = 0444, ++ .label = "spi_chnl_select", ++ .reg = NVSW_REG_SPI_CHNL_SELECT, ++ .mask = GENMASK(7, 0), ++ .bit = 1, ++ .mode = 0644, + }, + { -+ .label = "bios_start_retry", -+ .reg = NVSW_REG_GPCOM0_OFFSET, -+ .mask = GENMASK(7, 0) & ~BIT(4), ++ .label = "config1", ++ .reg = NVSW_REG_CONFIG1_OFFSET, ++ .mask = GENMASK(7, 0), + .mode = 0444, + }, + { -+ .label = "bios_active_image", -+ .reg = NVSW_REG_GPCOM0_OFFSET, -+ .mask = GENMASK(7, 0) & ~BIT(5), ++ .label = "config2", ++ .reg = NVSW_REG_CONFIG2_OFFSET, ++ .bit = GENMASK(7, 0), + .mode = 0444, + }, + { -+ .label = "ufm_version", -+ .reg = NVSW_REG_UFM_VERSION_OFFSET, ++ .label = "config3", ++ .reg = NVSW_REG_CONFIG3_OFFSET, + .bit = GENMASK(7, 0), + .mode = 0444, + }, + { -+ .label = "clk_brd1_boot_fail", -+ .reg = NVSW_REG_GP4_RO_OFFSET, -+ .mask = GENMASK(7, 0) & ~BIT(4), ++ .label = "psu_qty", ++ .reg = NVSW_REG_SYS_CPBLTY0, ++ .mask = GENMASK(3, 0), ++ .bit = 3, + .mode = 0444, + }, + { -+ .label = "clk_brd2_boot_fail", -+ .reg = NVSW_REG_GP4_RO_OFFSET, -+ .mask = GENMASK(7, 0) & ~BIT(5), ++ .label = "i2c_freq", ++ .reg = NVSW_REG_SYS_CPBLTY0, ++ .mask = GENMASK(5, 4), ++ .bit = 5, + .mode = 0444, + }, + { -+ .label = "clk_brd_fail", -+ .reg = NVSW_REG_GP4_RO_OFFSET, ++ .label = "wd_type", ++ .reg = NVSW_REG_SYS_CPBLTY0, + .mask = GENMASK(7, 0) & ~BIT(6), + .mode = 0444, + }, + { -+ .label = "asic_pg_fail", -+ .reg = NVSW_REG_GP4_RO_OFFSET, ++ .label = "avlbl_reg_new_format", ++ .reg = NVSW_REG_SYS_CPBLTY0, + .mask = GENMASK(7, 0) & ~BIT(7), + .mode = 0444, + }, -+ { -+ .label = "geo_addr", -+ .reg = NVSW_REG_CONFIG2_OFFSET, -+ .bit = GENMASK(7, 0), -+ .mode = 0444, -+ }, +}; + -+static struct mlxreg_core_platform_data nvsw_bmc_hid162_regio = { -+ .data = nvsw_bmc_hid162_regio_data, -+ .counter = ARRAY_SIZE(nvsw_bmc_hid162_regio_data), -+}; -+ -+static struct mlxreg_core_data nvsw_bmc_hid180_regio_data[] = { ++static struct mlxreg_core_data nvsw_bmc_hid193_regio_data[] = { + { + .label = "cpld1_version", + .reg = NVSW_REG_CPLD1_VER_OFFSET, @@ -2430,6 +6255,12 @@ index 000000000..3c70e2979 + .mode = 0444, + }, + { ++ .label = "cpld4_version", ++ .reg = NVSW_REG_CPLD4_VER_OFFSET, ++ .bit = GENMASK(7, 0), ++ .mode = 0444, ++ }, ++ { + .label = "cpld1_pn", + .reg = NVSW_REG_CPLD1_PN_OFFSET, + .bit = GENMASK(15, 0), @@ -2451,6 +6282,13 @@ index 000000000..3c70e2979 + .regnum = 2, + }, + { ++ .label = "cpld4_pn", ++ .reg = NVSW_REG_CPLD4_PN_OFFSET, ++ .bit = GENMASK(15, 0), ++ .mode = 0444, ++ .regnum = 2, ++ }, ++ { + .label = "cpld1_version_min", + .reg = NVSW_REG_CPLD1_MVER_OFFSET, + .bit = GENMASK(7, 0), @@ -2469,7 +6307,13 @@ index 000000000..3c70e2979 + .mode = 0444, + }, + { -+ .label = "bios_status", ++ .label = "cpld4_version_min", ++ .reg = NVSW_REG_CPLD4_MVER_OFFSET, ++ .bit = GENMASK(7, 0), ++ .mode = 0444, ++ }, ++ { ++ .label = "bios_status_ro", + .reg = NVSW_REG_GPCOM0_OFFSET, + .mask = GENMASK(3, 1), + .bit = 3, @@ -2494,6 +6338,12 @@ index 000000000..3c70e2979 + .mode = 0644, + }, + { ++ .label = "bmc_req_conf_flash_updt", ++ .reg = NVSW_REG_GP7_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(1), ++ .mode = 0644, ++ }, ++ { + .label = "graceful_power_off", + .reg = NVSW_REG_GP7_OFFSET, + .mask = GENMASK(7, 0) & ~BIT(3), @@ -2503,18 +6353,25 @@ index 000000000..3c70e2979 + .label = "bmc_perst_en", + .reg = NVSW_REG_GP7_OFFSET, + .mask = GENMASK(7, 0) & ~BIT(4), -+ .mode = 0644, ++ .mode = 0200, + }, + { -+ .label = "stby_pwr_en_unmask", ++ .label = "bmc_shutdown_unlock", + .reg = NVSW_REG_GP7_OFFSET, -+ .mask = GENMASK(7, 0) & ~BIT(6), -+ .mode = 0644, ++ .mask = GENMASK(7, 0) & ~BIT(5), ++ .mode = 0200, + }, + { -+ .label = "i3c_mux_sel", ++ .label = "stby_pwr_en_unmask", + .reg = NVSW_REG_GP7_OFFSET, -+ .mask = GENMASK(7, 0) & ~BIT(7), ++ .mask = GENMASK(7, 0) & ~BIT(5), ++ .mode = 0200, ++ }, ++ { ++ .label = "uart_baud_rate", ++ .reg = NVSW_REG_UART_BAUD_OFFSET, ++ .mask = NVSW_UART_BAUD_MASK, ++ .bit = 1, + .mode = 0644, + }, + { @@ -2543,12 +6400,6 @@ index 000000000..3c70e2979 + .mode = 0644, + }, + { -+ .label = "shutdown_unlock", -+ .reg = NVSW_REG_GP0_OFFSET, -+ .mask = GENMASK(7, 0) & ~BIT(5), -+ .mode = 0644, -+ }, -+ { + .label = "cpu_power_off_ready", + .reg = NVSW_REG_GP0_OFFSET, + .mask = GENMASK(7, 0) & ~BIT(6), @@ -2598,37 +6449,31 @@ index 000000000..3c70e2979 + .mode = 0644, + }, + { -+ .label = "sgmii_phy_reset", -+ .reg = NVSW_REG_RESET_GP2_OFFSET, -+ .mask = GENMASK(7, 0) & ~BIT(4), -+ .mode = 0644, -+ }, -+ { -+ .label = "erot_cpu_reset", -+ .reg = NVSW_REG_RESET_GP2_OFFSET, -+ .mask = GENMASK(7, 0) & ~BIT(5), -+ .mode = 0644, -+ }, -+ { + .label = "mcu1_reset", + .reg = NVSW_REG_RESET_GP2_OFFSET, + .mask = GENMASK(7, 0) & ~BIT(6), + .mode = 0644, + }, + { -+ .label = "mcu2_reset", -+ .reg = NVSW_REG_RESET_GP2_OFFSET, -+ .mask = GENMASK(7, 0) & ~BIT(7), -+ .mode = 0644, -+ }, -+ { + .label = "platform_reset", + .reg = NVSW_REG_RESET_GP1_OFFSET, + .mask = GENMASK(7, 0) & ~BIT(2), + .mode = 0200, + }, + { -+ .label = "cpld_phy_reset", ++ .label = "gp_cpld_rtc_clr", ++ .reg = NVSW_REG_RESET_GP1_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(3), ++ .mode = 0200, ++ }, ++ { ++ .label = "cpld_phy2_rst", ++ .reg = NVSW_REG_RESET_GP1_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(4), ++ .mode = 0200, ++ }, ++ { ++ .label = "cpld_phy1_reset", + .reg = NVSW_REG_RESET_GP1_OFFSET, + .mask = GENMASK(7, 0) & ~BIT(5), + .mode = 0200, @@ -2640,6 +6485,12 @@ index 000000000..3c70e2979 + .mode = 0200, + }, + { ++ .label = "bmc_tpm_reset", ++ .reg = NVSW_REG_RESET_GP1_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(7), ++ .mode = 0200, ++ }, ++ { + .label = "reset_long_pb", + .reg = NVSW_REG_RESET_CAUSE_OFFSET, + .mask = GENMASK(7, 0) & ~BIT(0), @@ -2664,12 +6515,6 @@ index 000000000..3c70e2979 + .mode = 0444, + }, + { -+ .label = "reset_pwr_button_or_leak_con", -+ .reg = NVSW_REG_RESET_CAUSE_OFFSET, -+ .mask = GENMASK(7, 0) & ~BIT(5), -+ .mode = 0444, -+ }, -+ { + .label = "reset_swb_wd", + .reg = NVSW_REG_RESET_CAUSE_OFFSET, + .mask = GENMASK(7, 0) & ~BIT(6), @@ -2688,18 +6533,6 @@ index 000000000..3c70e2979 + .mode = 0444, + }, + { -+ .label = "reset_erot", -+ .reg = NVSW_REG_RESET_CAUSE1_OFFSET, -+ .mask = GENMASK(7, 0) & ~BIT(6), -+ .mode = 0444, -+ }, -+ { -+ .label = "leak_con", -+ .reg = NVSW_REG_RESET_CAUSE1_OFFSET, -+ .mask = GENMASK(7, 0) & ~BIT(7), -+ .mode = 0444, -+ }, -+ { + .label = "reset_system", + .reg = NVSW_REG_RESET_CAUSE2_OFFSET, + .mask = GENMASK(7, 0) & ~BIT(1), @@ -2712,7 +6545,7 @@ index 000000000..3c70e2979 + .mode = 0444, + }, + { -+ .label = "reset_cpu_thermal", ++ .label = "reset_thermal", + .reg = NVSW_REG_RESET_CAUSE2_OFFSET, + .mask = GENMASK(7, 0) & ~BIT(3), + .mode = 0444, @@ -2724,38 +6557,73 @@ index 000000000..3c70e2979 + .mode = 0444, + }, + { -+ .label = "reset_main_51v", -+ .reg = NVSW_REG_RESET_CAUSE2_OFFSET, -+ .mask = GENMASK(7, 0) & ~BIT(6), ++ .label = "reset_main_51v", ++ .reg = NVSW_REG_RESET_CAUSE2_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(6), ++ .mode = 0444, ++ }, ++ { ++ .label = "reset_mgmt_pwr", ++ .reg = NVSW_REG_RESET_CAUSE2_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(7), ++ .mode = 0444, ++ }, ++ { ++ .label = "port80", ++ .reg = NVSW_REG_GP1_RO_OFFSET, ++ .bit = GENMASK(7, 0), ++ .mode = 0444, ++ }, ++ { ++ .label = "switch_ic_qty", ++ .reg = NVSW_REG_SWITCH_IC_QTY, ++ .bit = GENMASK(7, 0), ++ .mode = 0444, ++ }, ++ { ++ .label = "gp_swb_mgmt_present", ++ .reg = NVSW_REG_GP4_RO_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(0), ++ .mode = 0444, ++ }, ++ { ++ .label = "gp_io_brd_prsnt", ++ .reg = NVSW_REG_GP4_RO_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(1), ++ .mode = 0444, ++ }, ++ { ++ .label = "gp_tpm_brd_prsnt", ++ .reg = NVSW_REG_GP4_RO_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(2), + .mode = 0444, + }, + { -+ .label = "reset_mgmt_pwr", -+ .reg = NVSW_REG_RESET_CAUSE2_OFFSET, -+ .mask = GENMASK(7, 0) & ~BIT(7), ++ .label = "gp_bmc_presnt", ++ .reg = NVSW_REG_GP4_RO_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(3), + .mode = 0444, + }, + { -+ .label = "port80", -+ .reg = NVSW_REG_GP1_RO_OFFSET, -+ .bit = GENMASK(7, 0), ++ .label = "sb_clk_brd_asic_ci_boot_fail", ++ .reg = NVSW_REG_GP4_RO_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(4), + .mode = 0444, + }, + { -+ .label = "transport_status", ++ .label = "gp_bmc_tpm_brd_prsnt", + .reg = NVSW_REG_GP4_RO_OFFSET, -+ .mask = GENMASK(1, 0), -+ .bit = 1, ++ .mask = GENMASK(7, 0) & ~BIT(5), + .mode = 0444, + }, + { -+ .label = "tpm_present", ++ .label = "sb_clk_brd_asic_fail", + .reg = NVSW_REG_GP4_RO_OFFSET, -+ .mask = GENMASK(7, 0) & ~BIT(3), ++ .mask = GENMASK(7, 0) & ~BIT(6), + .mode = 0444, + }, + { -+ .label = "asics_pg_fail", ++ .label = "asics_pg", + .reg = NVSW_REG_GP4_RO_OFFSET, + .mask = GENMASK(7, 0) & ~BIT(7), + .mode = 0444, @@ -2770,8 +6638,20 @@ index 000000000..3c70e2979 + { + .label = "jtag_enable", + .reg = NVSW_REG_FIELD_UPGRADE, -+ .mask = GENMASK(1, 0), -+ .bit = 1, ++ .mask = GENMASK(7, 0) & ~BIT(1), ++ .mode = 0644, ++ }, ++ { ++ .label = "bios_status", ++ .reg = NVSW_REG_SAFE_BIOS_OFFSET, ++ .mask = GENMASK(2, 0), ++ .bit = 2, ++ .mode = 0644, ++ }, ++ { ++ .label = "conf_flash_updt_done", ++ .reg = NVSW_REG_SAFE_BIOS_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(3), + .mode = 0644, + }, + { @@ -2793,9 +6673,9 @@ index 000000000..3c70e2979 + .mode = 0644, + }, + { -+ .label = "erot_cpu_recovery", -+ .reg = NVSW_REG_PWM_CONTROL_OFFSET, -+ .mask = GENMASK(7, 0) & ~BIT(5), ++ .label = "cpu_req_conf_flash_updt", ++ .reg = NVSW_REG_SAFE_BIOS_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(7), + .mode = 0644, + }, + { @@ -2805,24 +6685,36 @@ index 000000000..3c70e2979 + .mode = 0644, + }, + { -+ .label = "mcu2_recovery", ++ .label = "gp_vdd_spd_dis", + .reg = NVSW_REG_PWM_CONTROL_OFFSET, -+ .mask = GENMASK(7, 0) & ~BIT(7), ++ .mask = GENMASK(7, 0) & ~BIT(4), + .mode = 0644, + }, + { -+ .label = "cpu_int_enable", -+ .reg = NVSW_REG_GP5_OFFSET, -+ .mask = GENMASK(7, 0) & ~BIT(0), ++ .label = "gp_spd_b_wp", ++ .reg = NVSW_REG_PWM_CONTROL_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(3), + .mode = 0644, + }, + { -+ .label = "cpu_tps_upgrade", -+ .reg = NVSW_REG_GP5_OFFSET, ++ .label = "gp_spd_a_wp", ++ .reg = NVSW_REG_PWM_CONTROL_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(2), ++ .mode = 0644, ++ }, ++ { ++ .label = "sb_clk_brd_prog_en", ++ .reg = NVSW_REG_PWM_CONTROL_OFFSET, + .mask = GENMASK(7, 0) & ~BIT(1), + .mode = 0644, + }, + { ++ .label = "cpu_int_enable", ++ .reg = NVSW_REG_GP5_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(0), ++ .mode = 0644, ++ }, ++ { + .label = "cpu_spi_ctrl", + .reg = NVSW_REG_GP5_OFFSET, + .mask = GENMASK(4, 2), @@ -2836,12 +6728,6 @@ index 000000000..3c70e2979 + .mode = 0644, + }, + { -+ .label = "leakage_conn_en", -+ .reg = NVSW_REG_GP6_OFFSET, -+ .mask = GENMASK(7, 0) & ~BIT(0), -+ .mode = 0644, -+ }, -+ { + .label = "bmc_reset_reg", + .reg = NVSW_REG_GP6_OFFSET, + .mask = GENMASK(7, 0) & ~BIT(1), @@ -2861,9 +6747,17 @@ index 000000000..3c70e2979 + .mode = 0644, + }, + { ++ .label = "asic_clk_brd_hw_id", ++ .reg = NVSW_REG_CONFIG1_OFFSET, ++ .mask = GENMASK(2, 0), ++ .bit = 2, ++ .mode = 0444, ++ }, ++ { + .label = "config1", + .reg = NVSW_REG_CONFIG1_OFFSET, -+ .bit = GENMASK(7, 0), ++ .mask = GENMASK(7, 3), ++ .bit = 7, + .mode = 0444, + }, + { @@ -2885,6 +6779,21 @@ index 000000000..3c70e2979 + .counter = ARRAY_SIZE(nvsw_bmc_hid180_regio_data), +}; + ++static struct mlxreg_core_platform_data nvsw_bmc_hid181_regio = { ++ .data = nvsw_bmc_hid181_regio_data, ++ .counter = ARRAY_SIZE(nvsw_bmc_hid181_regio_data), ++}; ++ ++static struct mlxreg_core_platform_data nvsw_bmc_hid191_regio = { ++ .data = nvsw_bmc_hid191_regio_data, ++ .counter = ARRAY_SIZE(nvsw_bmc_hid191_regio_data), ++}; ++ ++static struct mlxreg_core_platform_data nvsw_bmc_hid193_regio = { ++ .data = nvsw_bmc_hid193_regio_data, ++ .counter = ARRAY_SIZE(nvsw_bmc_hid193_regio_data), ++}; ++ +/* Platform fan data. */ +static struct mlxreg_core_data nvsw_bmc_hid162_fan_data[] = { + { @@ -3046,7 +6955,7 @@ index 000000000..3c70e2979 + .counter = ARRAY_SIZE(nvsw_bmc_hid162_led_data), +}; + -+/* Platform led data for HI1676 system type. */ ++/* Platform led data for HI176 system type. */ +static struct mlxreg_core_data nvsw_bmc_hid176_led_data[] = { + { + .label = "status:green", @@ -3090,7 +6999,7 @@ index 000000000..3c70e2979 + .counter = ARRAY_SIZE(nvsw_bmc_hid176_led_data), +}; + -+/* Platform led data for HI1676 system type. */ ++/* Platform led data for HI177 system type. */ +static struct mlxreg_core_data nvsw_bmc_hid177_led_data[] = { + { + .label = "status:green", @@ -3178,6 +7087,16 @@ index 000000000..3c70e2979 + return regmap_write(regmap, NVSW_REG_AGGRCO_MASK_OFFSET, GENMASK(5, 0)); +} + ++static int nvsw_bmc_hid191_set_reg_default(struct regmap *regmap) ++{ ++ return regmap_write(regmap, NVSW_REG_AGGRCO_MASK_OFFSET, GENMASK(6, 0)); ++} ++ ++static int nvsw_bmc_hid193_set_reg_default(struct regmap *regmap) ++{ ++ return regmap_write(regmap, NVSW_REG_AGGRCO_MASK_OFFSET, GENMASK(6, 0)); ++} ++ +/* Callback is used to indicate that all adapter devices has been created. */ +static int +nvsw_bmc_hid162_completion_notify(void *handle, struct i2c_adapter *parent, @@ -3188,19 +7107,6 @@ index 000000000..3c70e2979 + return 0; +} + -+static int nvsw_bmc_hid162_mux_access_grant(void *handle) -+{ -+ struct nvsw_core *nvsw_core = handle; -+ u32 regval; -+ int err; -+ -+ err = regmap_read(nvsw_core->regmap, NVSW_REG_GP1_OFFSET, ®val); -+ if (err) -+ return err; -+ -+ return regval & NVSW_MASTER_MASK; -+} -+ +static int nvsw_bmc_hid162_platform_data_init(struct nvsw_core *nvsw_core) +{ + int i; @@ -3212,7 +7118,6 @@ index 000000000..3c70e2979 + mux_data[i] = &nvsw_bmc_hid162_mux_data[i]; + mux_data[i]->handle = nvsw_core; + mux_data[i]->completion_notify = nvsw_bmc_hid162_completion_notify; -+ mux_data[i]->mux_access_grant = nvsw_bmc_hid162_mux_access_grant; + mux_brdinfo[i] = &nvsw_bmc_hid162_mux_brdinfo; + mux_brdinfo[i]->platform_data = mux_data[i]; + } @@ -3239,7 +7144,6 @@ index 000000000..3c70e2979 + mux_data[i] = &nvsw_bmc_hid176_mux_data[i]; + mux_data[i]->handle = nvsw_core; + mux_data[i]->completion_notify = nvsw_bmc_hid162_completion_notify; -+ mux_data[i]->mux_access_grant = nvsw_bmc_hid162_mux_access_grant; + mux_brdinfo[i] = &nvsw_bmc_hid162_mux_brdinfo; + mux_brdinfo[i]->platform_data = mux_data[i]; + } @@ -3265,7 +7169,6 @@ index 000000000..3c70e2979 + mux_data[i] = &nvsw_bmc_hid176_mux_data[i]; + mux_data[i]->handle = nvsw_core; + mux_data[i]->completion_notify = nvsw_bmc_hid162_completion_notify; -+ mux_data[i]->mux_access_grant = nvsw_bmc_hid162_mux_access_grant; + mux_brdinfo[i] = &nvsw_bmc_hid162_mux_brdinfo; + mux_brdinfo[i]->platform_data = mux_data[i]; + } @@ -3291,7 +7194,6 @@ index 000000000..3c70e2979 + mux_data[i] = &nvsw_bmc_hid180_mux_data[i]; + mux_data[i]->handle = nvsw_core; + mux_data[i]->completion_notify = nvsw_bmc_hid162_completion_notify; -+ mux_data[i]->mux_access_grant = nvsw_bmc_hid162_mux_access_grant; + mux_brdinfo[i] = &nvsw_bmc_hid162_mux_brdinfo; + mux_brdinfo[i]->platform_data = mux_data[i]; + } @@ -3306,25 +7208,49 @@ index 000000000..3c70e2979 + return 0; +} + -+static int nvsw_bmc_hid185_platform_data_init(struct nvsw_core *nvsw_core) ++static int nvsw_bmc_hid181_platform_data_init(struct nvsw_core *nvsw_core) +{ + int i; + + /* Set system configuration. */ -+ nvsw_core->hid = HID180; -+ nvsw_core->mux_num = ARRAY_SIZE(nvsw_bmc_hid185_mux_data); ++ nvsw_core->hid = HID181; ++ nvsw_core->mux_num = ARRAY_SIZE(nvsw_bmc_hid181_mux_data); + for (i = 0; i < nvsw_core->mux_num; i++) { -+ mux_data[i] = &nvsw_bmc_hid185_mux_data[i]; ++ mux_data[i] = &nvsw_bmc_hid181_mux_data[i]; + mux_data[i]->handle = nvsw_core; + mux_data[i]->completion_notify = nvsw_bmc_hid162_completion_notify; + mux_brdinfo[i] = &nvsw_bmc_hid162_mux_brdinfo; + mux_brdinfo[i]->platform_data = mux_data[i]; + } -+ mux_data[0]->mux_access_grant = nvsw_bmc_hid162_mux_access_grant; + -+ nvsw_core->regio_data = &nvsw_bmc_hid180_regio; ++ nvsw_core->regio_data = &nvsw_bmc_hid181_regio; ++ nvsw_core->led_data = &nvsw_bmc_hid177_led; ++ nvsw_core->hotplug_data = &nvsw_bmc_hid181_hotplug; ++ nvsw_core->mux_init = nvsw_bmc_hid162_mux_topology_init; ++ nvsw_core->mux_exit = nvsw_bmc_hid162_mux_topology_exit; ++ nvsw_core->set_reg_default = nvsw_bmc_hid162_set_reg_default; ++ ++ return 0; ++} ++ ++static int nvsw_bmc_hid189_platform_data_init(struct nvsw_core *nvsw_core) ++{ ++ int i; ++ ++ /* Set system configuration. */ ++ nvsw_core->hid = HID189; ++ nvsw_core->mux_num = ARRAY_SIZE(nvsw_bmc_hid189_mux_data); ++ for (i = 0; i < nvsw_core->mux_num; i++) { ++ mux_data[i] = &nvsw_bmc_hid189_mux_data[i]; ++ mux_data[i]->handle = nvsw_core; ++ mux_data[i]->completion_notify = nvsw_bmc_hid162_completion_notify; ++ mux_brdinfo[i] = &nvsw_bmc_hid162_mux_brdinfo; ++ mux_brdinfo[i]->platform_data = mux_data[i]; ++ } ++ ++ nvsw_core->regio_data = &nvsw_bmc_hid193_regio; /* hid189 uses info from 193 */ + nvsw_core->led_data = &nvsw_bmc_hid177_led; -+ nvsw_core->hotplug_data = &nvsw_bmc_hid185_hotplug; ++ nvsw_core->hotplug_data = &nvsw_bmc_hid193_hotplug; + nvsw_core->mux_init = nvsw_bmc_hid162_mux_topology_init; + nvsw_core->mux_exit = nvsw_bmc_hid162_mux_topology_exit; + nvsw_core->set_reg_default = nvsw_bmc_hid180_set_reg_default; @@ -3332,6 +7258,56 @@ index 000000000..3c70e2979 + return 0; +} + ++static int nvsw_bmc_hid191_platform_data_init(struct nvsw_core *nvsw_core) ++{ ++ int i; ++ ++ /* Set system configuration. */ ++ nvsw_core->hid = HID191; ++ nvsw_core->mux_num = ARRAY_SIZE(nvsw_bmc_hid191_mux_data); ++ for (i = 0; i < nvsw_core->mux_num; i++) { ++ mux_data[i] = &nvsw_bmc_hid191_mux_data[i]; ++ mux_data[i]->handle = nvsw_core; ++ mux_data[i]->completion_notify = nvsw_bmc_hid162_completion_notify; ++ mux_brdinfo[i] = &nvsw_bmc_hid162_mux_brdinfo; ++ mux_brdinfo[i]->platform_data = mux_data[i]; ++ } ++ ++ nvsw_core->regio_data = &nvsw_bmc_hid191_regio; ++ nvsw_core->led_data = &nvsw_bmc_hid177_led; ++ nvsw_core->hotplug_data = &nvsw_bmc_hid191_hotplug; ++ nvsw_core->mux_init = nvsw_bmc_hid162_mux_topology_init; ++ nvsw_core->mux_exit = nvsw_bmc_hid162_mux_topology_exit; ++ nvsw_core->set_reg_default = nvsw_bmc_hid191_set_reg_default; ++ ++ return 0; ++} ++ ++static int nvsw_bmc_hid193_platform_data_init(struct nvsw_core *nvsw_core) ++{ ++ int i; ++ ++ /* Set system configuration. */ ++ nvsw_core->hid = HID193; ++ nvsw_core->mux_num = ARRAY_SIZE(nvsw_bmc_hid193_mux_data); ++ for (i = 0; i < nvsw_core->mux_num; i++) { ++ mux_data[i] = &nvsw_bmc_hid193_mux_data[i]; ++ mux_data[i]->handle = nvsw_core; ++ mux_data[i]->completion_notify = nvsw_bmc_hid162_completion_notify; ++ mux_brdinfo[i] = &nvsw_bmc_hid162_mux_brdinfo; ++ mux_brdinfo[i]->platform_data = mux_data[i]; ++ } ++ ++ nvsw_core->regio_data = &nvsw_bmc_hid193_regio; ++ nvsw_core->led_data = &nvsw_bmc_hid177_led; ++ nvsw_core->hotplug_data = &nvsw_bmc_hid193_hotplug; ++ nvsw_core->mux_init = nvsw_bmc_hid162_mux_topology_init; ++ nvsw_core->mux_exit = nvsw_bmc_hid162_mux_topology_exit; ++ nvsw_core->set_reg_default = nvsw_bmc_hid193_set_reg_default; ++ ++ return 0; ++} ++ +static int nvsw_bmc_platform_data_init(struct nvsw_core *nvsw_core, enum nvsw_core_hid_type type) +{ + switch (type) { @@ -3343,8 +7319,14 @@ index 000000000..3c70e2979 + return nvsw_bmc_hid177_platform_data_init(nvsw_core); + case HID180: + return nvsw_bmc_hid180_platform_data_init(nvsw_core); -+ case HID185: -+ return nvsw_bmc_hid185_platform_data_init(nvsw_core); ++ case HID181: ++ return nvsw_bmc_hid181_platform_data_init(nvsw_core); ++ case HID189: ++ return nvsw_bmc_hid189_platform_data_init(nvsw_core); ++ case HID191: ++ return nvsw_bmc_hid191_platform_data_init(nvsw_core); ++ case HID193: ++ return nvsw_bmc_hid193_platform_data_init(nvsw_core); + default: + return -ENODEV; + } @@ -3368,8 +7350,14 @@ index 000000000..3c70e2979 + type = HID177; + else if (of_device_is_compatible(np, "nvidia,hid180")) + type = HID180; -+ else if (of_device_is_compatible(np, "nvidia,hid185")) -+ type = HID185; ++ else if (of_device_is_compatible(np, "nvidia,hid181")) ++ type = HID181; ++ else if (of_device_is_compatible(np, "nvidia,hid189")) ++ type = HID189; ++ else if (of_device_is_compatible(np, "nvidia,hid191")) ++ type = HID191; ++ else if (of_device_is_compatible(np, "nvidia,hid193")) ++ type = HID193; + else + return -ENODEV; + @@ -3401,6 +7389,10 @@ index 000000000..3c70e2979 + { "hid176", HID176 }, + { "hid177", HID177 }, + { "hid180", HID180 }, ++ { "hid181", HID181 }, ++ { "hid189", HID189 }, ++ { "hid191", HID191 }, ++ { "hid193", HID193 }, + { }, +}; +MODULE_DEVICE_TABLE(i2c, nvsw_bmc_hid162_id); @@ -3410,6 +7402,10 @@ index 000000000..3c70e2979 + { .compatible = "nvidia,hid176" }, + { .compatible = "nvidia,hid177" }, + { .compatible = "nvidia,hid180" }, ++ { .compatible = "nvidia,hid181" }, ++ { .compatible = "nvidia,hid189" }, ++ { .compatible = "nvidia,hid191" }, ++ { .compatible = "nvidia,hid193" }, + { }, +}; +MODULE_DEVICE_TABLE(of, nvsw_bmc_hid162_dt_match); @@ -3431,15 +7427,15 @@ index 000000000..3c70e2979 +MODULE_LICENSE("Dual BSD/GPL"); diff --git a/drivers/platform/mellanox/nvsw-core.c b/drivers/platform/mellanox/nvsw-core.c new file mode 100644 -index 000000000..0dae7e076 +index 000000000..1bd2dfc62 --- /dev/null +++ b/drivers/platform/mellanox/nvsw-core.c -@@ -0,0 +1,693 @@ +@@ -0,0 +1,717 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Nvidia BMC platform driver + * -+ * Copyright (C) 2025 Nvidia Technologies Ltd. ++ * Copyright (C) 2025-2026 Nvidia Technologies Ltd. + */ + +#include @@ -3456,6 +7452,10 @@ index 000000000..0dae7e076 +static bool nvsw_writeable_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { ++ case NVSW_REG_CPLD1_DBG_LED_OFFSET: ++ case NVSW_REG_CPLD2_DBG_LED_OFFSET: ++ case NVSW_REG_CPLD3_DBG_LED_OFFSET: ++ case NVSW_REG_CPLD4_DBG_LED_OFFSET: + case NVSW_REG_PG1_EVENT_OFFSET: + case NVSW_REG_PG1_MASK_OFFSET: + case NVSW_REG_PG2_EVENT_OFFSET: @@ -3469,6 +7469,7 @@ index 000000000..0dae7e076 + case NVSW_REG_GP0_OFFSET: + case NVSW_REG_GP1_OFFSET: + case NVSW_REG_GP7_OFFSET: ++ case NVSW_REG_UART_BAUD_OFFSET: + case NVSW_REG_PWM_CONTROL_OFFSET: + case NVSW_REG_RESET_GP2_OFFSET: + case NVSW_REG_GP4_OFFSET: @@ -3488,12 +7489,16 @@ index 000000000..0dae7e076 + case NVSW_REG_AGGRLO_MASK_OFFSET: + case NVSW_REG_BRD1_EVENT_OFFSET: + case NVSW_REG_BRD1_MASK_OFFSET: ++ case NVSW_REG_BRD2_EVENT_OFFSET: ++ case NVSW_REG_BRD2_MASK_OFFSET: + case NVSW_REG_ASIC1_EVENT_OFFSET: + case NVSW_REG_ASIC1_MASK_OFFSET: + case NVSW_REG_ASIC2_EVENT_OFFSET: + case NVSW_REG_ASIC2_MASK_OFFSET: + case NVSW_REG_ASIC3_EVENT_OFFSET: + case NVSW_REG_ASIC3_MASK_OFFSET: ++ case NVSW_REG_ASIC4_EVENT_OFFSET: ++ case NVSW_REG_ASIC4_MASK_OFFSET: + case NVSW_REG_VR1_ALERT_EVENT_OFFSET: + case NVSW_REG_VR1_ALERT_MASK_OFFSET: + case NVSW_REG_VR2_ALERT_EVENT_OFFSET: @@ -3543,6 +7548,10 @@ index 000000000..0dae7e076 + case NVSW_REG_CPLD4_VER_OFFSET: + case NVSW_REG_CPLD4_PN_OFFSET: + case NVSW_REG_CPLD4_PN1_OFFSET: ++ case NVSW_REG_CPLD1_DBG_LED_OFFSET: ++ case NVSW_REG_CPLD2_DBG_LED_OFFSET: ++ case NVSW_REG_CPLD3_DBG_LED_OFFSET: ++ case NVSW_REG_CPLD4_DBG_LED_OFFSET: + case NVSW_REG_CPLD7_VER_OFFSET: + case NVSW_REG_PG1_OFFSET: + case NVSW_REG_PG1_EVENT_OFFSET: @@ -3577,12 +7586,14 @@ index 000000000..0dae7e076 + case NVSW_REG_RESET_GP2_OFFSET: + case NVSW_REG_GP0_RO_OFFSET: + case NVSW_REG_GP1_RO_OFFSET: ++ case NVSW_REG_GP2_RO_OFFSET: + case NVSW_REG_GP4_RO_OFFSET: + case NVSW_REG_GP5_RO_OFFSET: + case NVSW_REG_GPCOM0_OFFSET: + case NVSW_REG_GP0_OFFSET: + case NVSW_REG_GP1_OFFSET: + case NVSW_REG_GP7_OFFSET: ++ case NVSW_REG_UART_BAUD_OFFSET: + case NVSW_REG_PWM_CONTROL_OFFSET: + case NVSW_REG_GP4_OFFSET: + case NVSW_REG_GP5_OFFSET: @@ -3603,6 +7614,9 @@ index 000000000..0dae7e076 + case NVSW_REG_BRD1_OFFSET: + case NVSW_REG_BRD1_EVENT_OFFSET: + case NVSW_REG_BRD1_MASK_OFFSET: ++ case NVSW_REG_BRD2_OFFSET: ++ case NVSW_REG_BRD2_EVENT_OFFSET: ++ case NVSW_REG_BRD2_MASK_OFFSET: + case NVSW_REG_ASIC1_HEALTH_OFFSET: + case NVSW_REG_ASIC1_EVENT_OFFSET: + case NVSW_REG_ASIC1_MASK_OFFSET: @@ -3612,6 +7626,9 @@ index 000000000..0dae7e076 + case NVSW_REG_ASIC3_HEALTH_OFFSET: + case NVSW_REG_ASIC3_EVENT_OFFSET: + case NVSW_REG_ASIC3_MASK_OFFSET: ++ case NVSW_REG_ASIC4_HEALTH_OFFSET: ++ case NVSW_REG_ASIC4_EVENT_OFFSET: ++ case NVSW_REG_ASIC4_MASK_OFFSET: + case NVSW_REG_VR1_ALERT_OFFSET: + case NVSW_REG_VR1_ALERT_EVENT_OFFSET: + case NVSW_REG_VR1_ALERT_MASK_OFFSET: @@ -3767,6 +7784,9 @@ index 000000000..0dae7e076 + case NVSW_REG_BRD1_OFFSET: + case NVSW_REG_BRD1_EVENT_OFFSET: + case NVSW_REG_BRD1_MASK_OFFSET: ++ case NVSW_REG_BRD2_OFFSET: ++ case NVSW_REG_BRD2_EVENT_OFFSET: ++ case NVSW_REG_BRD2_MASK_OFFSET: + case NVSW_REG_ASIC1_HEALTH_OFFSET: + case NVSW_REG_ASIC1_EVENT_OFFSET: + case NVSW_REG_ASIC1_MASK_OFFSET: @@ -3854,7 +7874,7 @@ index 000000000..0dae7e076 + case NVSW_REG_MUX1_OFFSET: + case NVSW_REG_MUX2_OFFSET: + case NVSW_REG_UFM_VERSION_OFFSET: -+ case NVSW_REG_PSU_I2C_CAP_OFFSET: ++ case NVSW_REG_SYS_CPBLTY0: + return true; + } + return false; @@ -4130,15 +8150,15 @@ index 000000000..0dae7e076 +MODULE_LICENSE("Dual BSD/GPL"); diff --git a/drivers/platform/mellanox/nvsw-host-l1.c b/drivers/platform/mellanox/nvsw-host-l1.c new file mode 100644 -index 000000000..83d305080 +index 000000000..6fb8cf07a --- /dev/null +++ b/drivers/platform/mellanox/nvsw-host-l1.c -@@ -0,0 +1,770 @@ +@@ -0,0 +1,735 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* -+ * Nvidia BMC platform driver ++ * Nvidia L1 host platform driver + * -+ * Copyright (C) 2025 Nvidia Technologies Ltd. ++ * Copyright (C) 2025-2026 Nvidia Technologies Ltd. + */ + +#include @@ -4181,9 +8201,9 @@ index 000000000..83d305080 +static const int nvsw_host_l1_mgmt_channels[] = { + 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, + 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, -+ 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 48, 49, -+ 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, -+ 65, 66, 67, 68, 69, 70, 71, ++ 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, ++ 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, ++ 63, 64, 65, 66, 67, +}; + +/* Platform L1 scale out mux data */ @@ -4391,12 +8411,6 @@ index 000000000..83d305080 + .mode = 0444, + }, + { -+ .label = "cartridge_status_clear", -+ .reg = NVSW_REG_FRU1_EVENT_OFFSET, -+ .bit = GENMASK(3, 0), -+ .mode = 0644, -+ }, -+ { + .label = "leakage1", + .reg = NVSW_REG_LEAK_OFFSET, + .mask = GENMASK(7, 0) & ~BIT(0), @@ -4409,12 +8423,6 @@ index 000000000..83d305080 + .mode = 0444, + }, + { -+ .label = "leakage_status_clear", -+ .reg = NVSW_REG_LEAK_EVENT_OFFSET, -+ .bit = GENMASK(1, 0), -+ .mode = 0644, -+ }, -+ { + .label = "asic_reset", + .reg = NVSW_REG_RESET_GP2_OFFSET, + .mask = GENMASK(7, 0) & ~BIT(3), @@ -4427,6 +8435,24 @@ index 000000000..83d305080 + .mode = 0644, + }, + { ++ .label = "cpu_erot_reset", ++ .reg = NVSW_REG_RESET_GP2_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(5), ++ .mode = 0644, ++ }, ++ { ++ .label = "mcu1_reset", ++ .reg = NVSW_REG_RESET_GP2_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(6), ++ .mode = 0644, ++ }, ++ { ++ .label = "mcu2_reset", ++ .reg = NVSW_REG_RESET_GP2_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(7), ++ .mode = 0644, ++ }, ++ { + .label = "reset_long_pb", + .reg = NVSW_REG_RESET_CAUSE_OFFSET, + .mask = GENMASK(7, 0) & ~BIT(0), @@ -4439,7 +8465,7 @@ index 000000000..83d305080 + .mode = 0444, + }, + { -+ .label = "reset_aux_pwr_or_fu", ++ .label = "reset_aux_pwr_or_reload", + .reg = NVSW_REG_RESET_CAUSE_OFFSET, + .mask = GENMASK(7, 0) & ~BIT(2), + .mode = 0444, @@ -4451,7 +8477,13 @@ index 000000000..83d305080 + .mode = 0444, + }, + { -+ .label = "reset_pwr_button_or_leak_con", ++ .label = "reset_platform", ++ .reg = NVSW_REG_RESET_CAUSE_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(4), ++ .mode = 0444, ++ }, ++ { ++ .label = "reset_pwr_button", + .reg = NVSW_REG_RESET_CAUSE_OFFSET, + .mask = GENMASK(7, 0) & ~BIT(5), + .mode = 0444, @@ -4469,21 +8501,21 @@ index 000000000..83d305080 + .mode = 0444, + }, + { -+ .label = "reset_comex_pwr_fail", ++ .label = "reset_soc", + .reg = NVSW_REG_RESET_CAUSE1_OFFSET, -+ .mask = GENMASK(7, 0) & ~BIT(3), ++ .mask = GENMASK(7, 0) & ~BIT(5), + .mode = 0444, + }, + { -+ .label = "reset_platform", ++ .label = "reset_cpu_erot", + .reg = NVSW_REG_RESET_CAUSE1_OFFSET, -+ .mask = GENMASK(7, 0) & ~BIT(4), ++ .mask = GENMASK(7, 0) & ~BIT(6), + .mode = 0444, + }, + { -+ .label = "reset_soc", ++ .label = "reset_leak_con", + .reg = NVSW_REG_RESET_CAUSE1_OFFSET, -+ .mask = GENMASK(7, 0) & ~BIT(5), ++ .mask = GENMASK(7, 0) & ~BIT(7), + .mode = 0444, + }, + { @@ -4499,18 +8531,12 @@ index 000000000..83d305080 + .mode = 0444, + }, + { -+ .label = "reset_comex_thermal", ++ .label = "reset_cpu_thermal", + .reg = NVSW_REG_RESET_CAUSE2_OFFSET, + .mask = GENMASK(7, 0) & ~BIT(3), + .mode = 0444, + }, + { -+ .label = "reset_comex_power", -+ .reg = NVSW_REG_RESET_CAUSE2_OFFSET, -+ .mask = GENMASK(7, 0) & ~BIT(4), -+ .mode = 0444, -+ }, -+ { + .label = "reset_pwr_converter_fail", + .reg = NVSW_REG_RESET_CAUSE2_OFFSET, + .mask = GENMASK(7, 0) & ~BIT(5), @@ -4523,7 +8549,7 @@ index 000000000..83d305080 + .mode = 0444, + }, + { -+ .label = "reset_mgmt_pwr", ++ .label = "reset_mgmt_pwr_fail", + .reg = NVSW_REG_RESET_CAUSE2_OFFSET, + .mask = GENMASK(7, 0) & ~BIT(7), + .mode = 0444, @@ -4590,53 +8616,11 @@ index 000000000..83d305080 + .mode = 0644, + }, + { -+ .label = "config1", -+ .reg = NVSW_REG_CONFIG1_OFFSET, -+ .bit = GENMASK(7, 0), -+ .mode = 0444, -+ }, -+ { -+ .label = "config2", -+ .reg = NVSW_REG_CONFIG2_OFFSET, -+ .bit = GENMASK(7, 0), -+ .mode = 0444, -+ }, -+ { -+ .label = "config3", -+ .reg = NVSW_REG_CONFIG3_OFFSET, -+ .bit = GENMASK(7, 0), -+ .mode = 0444, -+ }, -+ { -+ .label = "sgmii_phy", -+ .reg = NVSW_REG_BRD1_OFFSET, -+ .mask = GENMASK(7, 0) & ~BIT(5), -+ .mode = 0444, -+ }, -+ { -+ .label = "graseful_pwr_off", ++ .label = "graceful_pwr_off", + .reg = NVSW_REG_GP7_OFFSET, + .mask = GENMASK(7, 0) & ~BIT(3), + .mode = 0644, + }, -+ { -+ .label = "power_button_evt", -+ .reg = NVSW_REG_PWRB_EVENT_OFFSET, -+ .mask = GENMASK(7, 0) & ~NVSW_PWR_BUTTON_MASK, -+ .mode = 0644, -+ }, -+ { -+ .label = "power_button_mask", -+ .reg = NVSW_REG_PWRB_MASK_OFFSET, -+ .mask = GENMASK(7, 0) & ~NVSW_PWR_BUTTON_MASK, -+ .mode = 0644, -+ }, -+ { -+ .label = "amb_sens", -+ .reg = NVSW_REG_PWRB_OFFSET, -+ .mask = GENMASK(7, 0) & ~BIT(1), -+ .mode = 0444, -+ }, +}; + +static struct mlxreg_core_platform_data nvsw_host_l1_regs_io = { @@ -4904,6 +8888,7 @@ index 000000000..83d305080 +MODULE_AUTHOR("Vadim Pasternak "); +MODULE_DESCRIPTION("Nvidia platform driver"); +MODULE_LICENSE("Dual BSD/GPL"); ++ diff --git a/drivers/platform/mellanox/nvsw-host-spc5.c b/drivers/platform/mellanox/nvsw-host-spc5.c new file mode 100644 index 000000000..e54bd4109 @@ -6713,15 +10698,15 @@ index 000000000..449ba5ffd + diff --git a/drivers/platform/mellanox/nvsw.h b/drivers/platform/mellanox/nvsw.h new file mode 100644 -index 000000000..91bdbc2ce +index 000000000..4fa0d9d31 --- /dev/null +++ b/drivers/platform/mellanox/nvsw.h -@@ -0,0 +1,297 @@ +@@ -0,0 +1,318 @@ +/* SPDX-License-Identifier: GPL-2.0+ + * + * Nvidia BMC platform driver + * -+ * Copyright (C) 2025 Nvidia Technologies Ltd. ++ * Copyright (C) 2025-2026 Nvidia Technologies Ltd. + */ + +#ifndef __NVSW_H__ @@ -6740,6 +10725,10 @@ index 000000000..91bdbc2ce +#define NVSW_REG_CPLD3_PN1_OFFSET 0x2509 +#define NVSW_REG_CPLD4_PN_OFFSET 0x250a +#define NVSW_REG_CPLD4_PN1_OFFSET 0x250b ++#define NVSW_REG_CPLD1_DBG_LED_OFFSET 0x250c ++#define NVSW_REG_CPLD2_DBG_LED_OFFSET 0x250d ++#define NVSW_REG_CPLD3_DBG_LED_OFFSET 0x250e ++#define NVSW_REG_CPLD4_DBG_LED_OFFSET 0x250f +#define NVSW_REG_CPLD7_VER_OFFSET 0x2510 +#define NVSW_REG_PG1_OFFSET 0x2511 +#define NVSW_REG_PG1_EVENT_OFFSET 0x2512 @@ -6759,11 +10748,13 @@ index 000000000..91bdbc2ce +#define NVSW_REG_CPLD7_PN_OFFSET 0x2528 +#define NVSW_REG_CPLD7_PN1_OFFSET 0x2529 +#define NVSW_REG_GP0_RO_OFFSET 0x252a ++#define NVSW_REG_GP2_RO_OFFSET 0x252b +#define NVSW_REG_GP1_RO_OFFSET 0x252c +#define NVSW_REG_GPCOM0_OFFSET 0x252d +#define NVSW_REG_GP0_OFFSET 0x252e +#define NVSW_REG_GP7_OFFSET 0x252f +#define NVSW_REG_GP1_OFFSET 0x2530 ++#define NVSW_REG_UART_BAUD_OFFSET 0x2531 +#define NVSW_REG_GP4_OFFSET 0x2532 +#define NVSW_REG_GP5_RO_OFFSET 0x2533 +#define NVSW_REG_FIELD_UPGRADE 0x2534 @@ -6784,6 +10775,9 @@ index 000000000..91bdbc2ce +#define NVSW_REG_BRD1_OFFSET 0x2547 +#define NVSW_REG_BRD1_EVENT_OFFSET 0x2548 +#define NVSW_REG_BRD1_MASK_OFFSET 0x2549 ++#define NVSW_REG_BRD2_OFFSET 0x254a ++#define NVSW_REG_BRD2_EVENT_OFFSET 0x254b ++#define NVSW_REG_BRD2_MASK_OFFSET 0x254c +#define NVSW_REG_HEALTH_OFFSET 0x254d +#define NVSW_REG_HEALTH_EVENT_OFFSET 0x254e +#define NVSW_REG_HEALTH_MASK_OFFSET 0x254f @@ -6820,6 +10814,9 @@ index 000000000..91bdbc2ce +#define NVSW_REG_ASIC3_HEALTH_OFFSET 0x2582 +#define NVSW_REG_ASIC3_EVENT_OFFSET 0x2583 +#define NVSW_REG_ASIC3_MASK_OFFSET 0x2584 ++#define NVSW_REG_ASIC4_HEALTH_OFFSET 0x2585 ++#define NVSW_REG_ASIC4_EVENT_OFFSET 0x2586 ++#define NVSW_REG_ASIC4_MASK_OFFSET 0x2587 +#define NVSW_REG_FAN_OFFSET 0x2588 +#define NVSW_REG_FAN_EVENT_OFFSET 0x2589 +#define NVSW_REG_FAN_MASK_OFFSET 0x258a @@ -6841,12 +10838,16 @@ index 000000000..91bdbc2ce +#define NVSW_REG_LEAK_OFFSET 0x25af +#define NVSW_REG_LEAK_EVENT_OFFSET 0x25b0 +#define NVSW_REG_LEAK_MASK_OFFSET 0x25b1 ++#define NVSW_REG_CURRENT_TEMP_RO 0x25c0 ++#define NVSW_REG_SWITCH_IC_QTY 0x25c1 +#define NVSW_REG_GP4_RO_OFFSET 0x25c2 +#define NVSW_REG_SPI_CHNL_SELECT 0x25c3 +#define NVSW_REG_CPLD5_MVER_OFFSET 0x25c4 +#define NVSW_REG_CPLD8_VER_OFFSET 0x25c8 +#define NVSW_REG_CPLD9_VER_OFFSET 0x25c9 +#define NVSW_REG_CPLD10_VER_OFFSET 0x25ca ++#define NVSW_REG_CPLD9B_PN_OFFSET 0x25cb ++#define NVSW_REG_CPLD9B_PN1_OFFSET 0x25cc +#define NVSW_REG_WD2_TMR_OFFSET 0x25cd +#define NVSW_REG_WD2_TLEFT_OFFSET 0x25ce +#define NVSW_REG_WD2_ACT_OFFSET 0x25cf @@ -6886,6 +10887,7 @@ index 000000000..91bdbc2ce +#define NVSW_REG_CONFIG1_OFFSET 0x25fb +#define NVSW_REG_CONFIG2_OFFSET 0x25fc +#define NVSW_REG_CONFIG3_OFFSET 0x25fd ++#define NVSW_REG_SYS_CPBLTY0 NVSW_REG_PSU_I2C_CAP_OFFSET +#define NVSW_REG_MIN 0x2500 +#define NVSW_REG_MAX 0x26ff + @@ -6926,6 +10928,7 @@ index 000000000..91bdbc2ce +#define NVSW_LED_LO_NIBBLE_MASK GENMASK(7, 4) +#define NVSW_LED_HI_NIBBLE_MASK GENMASK(3, 0) +#define NVSW_UART_SEL_MASK GENMASK(7, 6) ++#define NVSW_UART_BAUD_MASK GENMASK(3, 0) +#define NVSW_BIOS_STATUS_MASK GENMASK(3, 1) +#define NVSW_REG_RESET_MASK BIT(1) +#define NVSW_MASTER_MASK BIT(5) @@ -6936,6 +10939,7 @@ index 000000000..91bdbc2ce +#define NVSW_WD3_DFLT_TIMEOUT 600 +#define NVSW_I2C_CAP_BIT 0x04 +#define NVSW_I2C_CAP_MASK GENMASK(5, NVSW_I2C_CAP_BIT) ++#define NVSW_I2C_TRAN_END_L BIT(5) + +#define NVSW_NR_NONE -1 +#define NVSW_MUX_MAX 2 @@ -6950,6 +10954,8 @@ index 000000000..91bdbc2ce + HID181, + HID182, + HID185, ++ HID189, ++ HID191, + HID193, +}; + diff --git a/patches-sonic/0060-leds-mlxreg-Provide-conversion-for-hardware-LED-colo.patch b/patches-sonic/0060-leds-mlxreg-Provide-conversion-for-hardware-LED-colo.patch new file mode 100644 index 000000000..2e4b4c10c --- /dev/null +++ b/patches-sonic/0060-leds-mlxreg-Provide-conversion-for-hardware-LED-colo.patch @@ -0,0 +1,84 @@ +From 4db801c656712234c840883b68429e6d45080ea3 Mon Sep 17 00:00:00 2001 +From: Vadim Pasternak +Date: Tue, 6 Jul 2021 18:38:29 +0000 +Subject: [PATCH backport v5.10.43 49/67] leds: mlxreg: Provide conversion for + hardware LED color code + +In case register is set by hardware, convert hardware color code to +expose correct color to "sysfs". +For some LED color at initial state is set by hardware. Hardware +controls LED color until the first software write access to any LED +register - the first software access cancels hardware control. +If LED is under hardware control - detect the color in brightness_get() +function. + +Signed-off-by: Vadim Pasternak +--- + drivers/leds/leds-mlxreg.c | 27 ++++++++++++++++++++++----- + 1 file changed, 22 insertions(+), 5 deletions(-) + +diff --git a/drivers/leds/leds-mlxreg.c b/drivers/leds/leds-mlxreg.c +index a6470480f..67337d9c8 100644 +--- a/drivers/leds/leds-mlxreg.c ++++ b/drivers/leds/leds-mlxreg.c +@@ -17,7 +17,9 @@ + #define MLXREG_LED_OFFSET_BLINK_3HZ 0x01 /* Offset from solid: 3Hz blink */ + #define MLXREG_LED_OFFSET_BLINK_6HZ 0x02 /* Offset from solid: 6Hz blink */ + #define MLXREG_LED_IS_OFF 0x00 /* Off */ ++#define MLXREG_LED_RED_SOLID_HW 0x01 /* Solid red or orange by hardware */ + #define MLXREG_LED_RED_SOLID 0x05 /* Solid red */ ++#define MLXREG_LED_GREEN_SOLID_HW 0x09 /* Solid green by hardware */ + #define MLXREG_LED_GREEN_SOLID 0x0D /* Solid green */ + #define MLXREG_LED_BLINK_3HZ 167 /* ~167 msec off/on - HW support */ + #define MLXREG_LED_BLINK_6HZ 83 /* ~83 msec off/on - HW support */ +@@ -29,6 +31,7 @@ + * @data: led configuration data; + * @led_cdev: led class data; + * @base_color: base led color (other colors have constant offset from base); ++ * @base_color_hw: base led color set by hardware; + * @data_parent: pointer to private device control data of parent; + * @led_cdev_name: class device name + */ +@@ -36,6 +39,7 @@ struct mlxreg_led_data { + struct mlxreg_core_data *data; + struct led_classdev led_cdev; + u8 base_color; ++ u8 base_color_hw; + void *data_parent; + char led_cdev_name[MLXREG_CORE_LABEL_MAX_SIZE]; + }; +@@ -123,8 +127,17 @@ mlxreg_led_get_hw(struct mlxreg_led_data *led_data) + regval = regval & ~data->mask; + regval = (ror32(data->mask, data->bit) == 0xf0) ? ror32(regval, + data->bit) : ror32(regval, data->bit + 4); +- if (regval >= led_data->base_color && +- regval <= (led_data->base_color + MLXREG_LED_OFFSET_BLINK_6HZ)) ++ ++ /* ++ * For some LED color at initial state is set by hardware. Hardware controls LED color ++ * until the first write access to any LED register. If LED is under hardware control - ++ * convert the value to the software mask to expose correct color. The first LED set by ++ * software cancels hardware control. ++ */ ++ if ((regval >= led_data->base_color && ++ regval <= (led_data->base_color + MLXREG_LED_OFFSET_BLINK_6HZ)) || ++ (led_data->base_color_hw && regval >= led_data->base_color_hw && ++ regval <= (led_data->base_color_hw + MLXREG_LED_OFFSET_BLINK_6HZ))) + return LED_FULL; + + return LED_OFF; +@@ -250,9 +263,11 @@ static int mlxreg_led_config(struct mlxreg_led_priv_data *priv) + strstr(data->label, "amber")) { + brightness = LED_OFF; + led_data->base_color = MLXREG_LED_RED_SOLID; ++ led_data->base_color_hw = MLXREG_LED_RED_SOLID_HW; + } else { + brightness = LED_OFF; + led_data->base_color = MLXREG_LED_GREEN_SOLID; ++ led_data->base_color_hw = MLXREG_LED_GREEN_SOLID_HW; + } + snprintf(led_data->led_cdev_name, sizeof(led_data->led_cdev_name), + "mlxreg:%s", data->label); +-- +2.20.1 + diff --git a/patches-sonic/0060-platform-mellanox-nvsw-bmc-Downstream-Add-protection.patch b/patches-sonic/0060-platform-mellanox-nvsw-bmc-Downstream-Add-protection.patch new file mode 100644 index 000000000..361b023b7 --- /dev/null +++ b/patches-sonic/0060-platform-mellanox-nvsw-bmc-Downstream-Add-protection.patch @@ -0,0 +1,186 @@ +From ac1dcaac74bbfc305add230936df03a161110334 Mon Sep 17 00:00:00 2001 +From: Vadim Pasternak +Date: Mon, 23 Mar 2026 17:31:25 +0200 +Subject: [PATCH 6.12 2/4] platform/mellanox: nvsw-bmc: Downstream: Add + protection for mux access + +Since mux ownership could be moved between CPU and BMC sides, mux setting +should be allowed only by designated owner. +Otherwise, not granted access could cause I2C transaction cutoff during +execution. + +Add callback to validate if mux setting is allowed. +If not - just skip setting operation. + +Upstream-Status: Pending + +Signed-off-by: Vadim Pasternak +--- + drivers/i2c/muxes/i2c-mux-regmap.c | 13 ++++++ + drivers/platform/mellanox/nvsw-bmc-hid162.c | 45 ++++++++++++++++++++ + include/linux/platform_data/i2c-mux-regmap.h | 2 + + 3 files changed, 60 insertions(+) + +diff --git a/drivers/i2c/muxes/i2c-mux-regmap.c b/drivers/i2c/muxes/i2c-mux-regmap.c +index fb5e89c82423..cd8b45fa4dba 100644 +--- a/drivers/i2c/muxes/i2c-mux-regmap.c ++++ b/drivers/i2c/muxes/i2c-mux-regmap.c +@@ -30,6 +30,12 @@ static int i2c_mux_regmap_select_chan(struct i2c_mux_core *muxc, u32 chan) + struct i2c_mux_regmap *mux = i2c_mux_priv(muxc); + int err = 0; + ++ if (mux->pdata.mux_access_grant) { ++ err = mux->pdata.mux_access_grant(mux->pdata.handle); ++ if (err <= 0) ++ return err; ++ } ++ + /* Only select the channel if its different from the last channel */ + if (mux->last_val != chan) { + err = regmap_write(mux->pdata.regmap, mux->pdata.sel_reg_addr, chan); +@@ -42,6 +48,13 @@ static int i2c_mux_regmap_select_chan(struct i2c_mux_core *muxc, u32 chan) + static int i2c_mux_regmap_deselect(struct i2c_mux_core *muxc, u32 chan) + { + struct i2c_mux_regmap *mux = i2c_mux_priv(muxc); ++ int err; ++ ++ if (mux->pdata.mux_access_grant) { ++ err = mux->pdata.mux_access_grant(mux->pdata.handle); ++ if (err <= 0) ++ return err; ++ } + + /* Deselect active channel */ + mux->last_val = -1; +diff --git a/drivers/platform/mellanox/nvsw-bmc-hid162.c b/drivers/platform/mellanox/nvsw-bmc-hid162.c +index 89343d7660a4..1a0bd9093dd9 100644 +--- a/drivers/platform/mellanox/nvsw-bmc-hid162.c ++++ b/drivers/platform/mellanox/nvsw-bmc-hid162.c +@@ -6965,6 +6965,19 @@ nvsw_bmc_hid162_completion_notify(void *handle, struct i2c_adapter *parent, + return 0; + } + ++static int nvsw_bmc_hid162_mux_access_grant(void *handle) ++{ ++ struct nvsw_core *nvsw_core = handle; ++ u32 regval; ++ int err; ++ ++ err = regmap_read(nvsw_core->regmap, NVSW_REG_GP1_OFFSET, ®val); ++ if (err) ++ return err; ++ ++ return regval & NVSW_MASTER_MASK; ++} ++ + static int nvsw_bmc_hid162_platform_data_init(struct nvsw_core *nvsw_core) + { + int i; +@@ -6976,6 +6989,7 @@ static int nvsw_bmc_hid162_platform_data_init(struct nvsw_core *nvsw_core) + mux_data[i] = &nvsw_bmc_hid162_mux_data[i]; + mux_data[i]->handle = nvsw_core; + mux_data[i]->completion_notify = nvsw_bmc_hid162_completion_notify; ++ mux_data[i]->mux_access_grant = nvsw_bmc_hid162_mux_access_grant; + mux_brdinfo[i] = &nvsw_bmc_hid162_mux_brdinfo; + mux_brdinfo[i]->platform_data = mux_data[i]; + } +@@ -7002,6 +7016,7 @@ static int nvsw_bmc_hid176_platform_data_init(struct nvsw_core *nvsw_core) + mux_data[i] = &nvsw_bmc_hid176_mux_data[i]; + mux_data[i]->handle = nvsw_core; + mux_data[i]->completion_notify = nvsw_bmc_hid162_completion_notify; ++ mux_data[i]->mux_access_grant = nvsw_bmc_hid162_mux_access_grant; + mux_brdinfo[i] = &nvsw_bmc_hid162_mux_brdinfo; + mux_brdinfo[i]->platform_data = mux_data[i]; + } +@@ -7027,6 +7042,7 @@ static int nvsw_bmc_hid177_platform_data_init(struct nvsw_core *nvsw_core) + mux_data[i] = &nvsw_bmc_hid176_mux_data[i]; + mux_data[i]->handle = nvsw_core; + mux_data[i]->completion_notify = nvsw_bmc_hid162_completion_notify; ++ mux_data[i]->mux_access_grant = nvsw_bmc_hid162_mux_access_grant; + mux_brdinfo[i] = &nvsw_bmc_hid162_mux_brdinfo; + mux_brdinfo[i]->platform_data = mux_data[i]; + } +@@ -7052,6 +7068,7 @@ static int nvsw_bmc_hid180_platform_data_init(struct nvsw_core *nvsw_core) + mux_data[i] = &nvsw_bmc_hid180_mux_data[i]; + mux_data[i]->handle = nvsw_core; + mux_data[i]->completion_notify = nvsw_bmc_hid162_completion_notify; ++ mux_data[i]->mux_access_grant = nvsw_bmc_hid162_mux_access_grant; + mux_brdinfo[i] = &nvsw_bmc_hid162_mux_brdinfo; + mux_brdinfo[i]->platform_data = mux_data[i]; + } +@@ -7077,10 +7094,20 @@ static int nvsw_bmc_hid181_platform_data_init(struct nvsw_core *nvsw_core) + mux_data[i] = &nvsw_bmc_hid181_mux_data[i]; + mux_data[i]->handle = nvsw_core; + mux_data[i]->completion_notify = nvsw_bmc_hid162_completion_notify; ++ mux_data[i]->mux_access_grant = nvsw_bmc_hid162_mux_access_grant; + mux_brdinfo[i] = &nvsw_bmc_hid162_mux_brdinfo; + mux_brdinfo[i]->platform_data = mux_data[i]; + } + ++ /* ++ * Mux access locking is required for NVSW_REG_MUX1. ++ * NVSW_REG_MUX2 CPLD reg, which controls the leak detector A2Ds, ++ * is accessed also when CPU controls the I2C muxing. ++ * Therefore, we only set mux_access_grant for the second cell in ++ * the i2c_mux_regmap_platform_data array. ++ */ ++ mux_data[0]->mux_access_grant = nvsw_bmc_hid162_mux_access_grant; ++ + nvsw_core->regio_data = &nvsw_bmc_hid181_regio; + nvsw_core->led_data = &nvsw_bmc_hid177_led; + nvsw_core->hotplug_data = &nvsw_bmc_hid181_hotplug; +@@ -7106,6 +7133,15 @@ static int nvsw_bmc_hid189_platform_data_init(struct nvsw_core *nvsw_core) + mux_brdinfo[i]->platform_data = mux_data[i]; + } + ++ /* ++ * Mux access locking is required for NVSW_REG_MUX1. ++ * NVSW_REG_MUX2 CPLD reg, which controls the leak detector A2Ds, ++ * is accessed also when CPU controls the I2C muxing. ++ * Therefore, we only set mux_access_grant for the second cell in ++ * the i2c_mux_regmap_platform_data array. ++ */ ++ mux_data[0]->mux_access_grant = nvsw_bmc_hid162_mux_access_grant; ++ + nvsw_core->regio_data = &nvsw_bmc_hid193_regio; /* hid189 uses info from 193 */ + nvsw_core->led_data = &nvsw_bmc_hid177_led; + nvsw_core->hotplug_data = &nvsw_bmc_hid193_hotplug; +@@ -7156,6 +7192,15 @@ static int nvsw_bmc_hid193_platform_data_init(struct nvsw_core *nvsw_core) + mux_brdinfo[i]->platform_data = mux_data[i]; + } + ++ /* ++ * Mux access locking is required for NVSW_REG_MUX1. ++ * NVSW_REG_MUX2 CPLD reg, which controls the leak detector A2Ds, ++ * is accessed also when CPU controls the I2C muxing. ++ * Therefore, we only set mux_access_grant for the second cell in ++ * the i2c_mux_regmap_platform_data array. ++ */ ++ mux_data[0]->mux_access_grant = nvsw_bmc_hid162_mux_access_grant; ++ + nvsw_core->regio_data = &nvsw_bmc_hid193_regio; + nvsw_core->led_data = &nvsw_bmc_hid177_led; + nvsw_core->hotplug_data = &nvsw_bmc_hid193_hotplug; +diff --git a/include/linux/platform_data/i2c-mux-regmap.h b/include/linux/platform_data/i2c-mux-regmap.h +index a06614e5edd2..f92db6ca7851 100644 +--- a/include/linux/platform_data/i2c-mux-regmap.h ++++ b/include/linux/platform_data/i2c-mux-regmap.h +@@ -18,6 +18,7 @@ + * @reg_size: register size in bytes + * @handle: handle to be passed by callback + * @completion_notify: callback to notify when all the adapters are created ++ * @mux_access_grant: callback to validate if mux access is granted + */ + struct i2c_mux_regmap_platform_data { + void *regmap; +@@ -29,6 +30,7 @@ struct i2c_mux_regmap_platform_data { + void *handle; + int (*completion_notify)(void *handle, struct i2c_adapter *parent, + struct i2c_adapter *adapters[]); ++ int (*mux_access_grant)(void *handle); + }; + + #endif /* __LINUX_PLATFORM_DATA_I2C_MUX_REGMAP_H */ +-- +2.34.1 + diff --git a/patches-sonic/0061-platform-mellanox-mlxreg-io-Increase-max-supported-a.patch b/patches-sonic/0061-platform-mellanox-mlxreg-io-Increase-max-supported-a.patch new file mode 100644 index 000000000..51db8f466 --- /dev/null +++ b/patches-sonic/0061-platform-mellanox-mlxreg-io-Increase-max-supported-a.patch @@ -0,0 +1,35 @@ +Upstream-Status: Pending + +From 9f11c71d770ea5a66ef3a4e6bea134aaaf62adaa Mon Sep 17 00:00:00 2001 +From: Aaron Komisar +Date: Thu, 28 Aug 2025 20:57:42 +0300 +Subject: [PATCH] platform/mellanox: mlxreg-io: Increase max supported + attributes + +Newer NVIDIA/Mellanox platforms expose more mlxreg-io register +attributes than the current limit of 128 allows, so attributes beyond +that count cannot be registered on those platforms. + +Raise MLXREG_IO_ATT_NUM from 128 to 250 to accommodate them. + +Signed-off-by: Aaron Komisar +--- + drivers/platform/mellanox/mlxreg-io.c | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/drivers/platform/mellanox/mlxreg-io.c b/drivers/platform/mellanox/mlxreg-io.c +index ee7bd623ba44..ef290ac9082a 100644 +--- a/drivers/platform/mellanox/mlxreg-io.c ++++ b/drivers/platform/mellanox/mlxreg-io.c +@@ -17,7 +17,7 @@ + + /* Attribute parameters. */ + #define MLXREG_IO_ATT_SIZE 10 +-#define MLXREG_IO_ATT_NUM 128 ++#define MLXREG_IO_ATT_NUM 250 + + /** + * struct mlxreg_io_priv_data - driver's private data: +-- +2.34.1 + diff --git a/patches-sonic/0062-Add-support-for-NXP-s-PCF85053A-RTC-chip.patch b/patches-sonic/0062-Add-support-for-NXP-s-PCF85053A-RTC-chip.patch new file mode 100644 index 000000000..afeb02b99 --- /dev/null +++ b/patches-sonic/0062-Add-support-for-NXP-s-PCF85053A-RTC-chip.patch @@ -0,0 +1,752 @@ +From cdd736d2668627456cda0ca9eaf2256fd4011f9c Mon Sep 17 00:00:00 2001 +From: Vadim Pasternak +Date: Mon, 26 Jan 2026 11:28:09 +0200 +Subject: [PATCH RTC 1/1] Add support for NXP's PCF85053A RTC chip. + +Add a driver for NXP's PCF85053A I2C real-time clock (RTC). + +Introduce the rtc-pcf85053a driver together with its +CONFIG_RTC_DRV_PCF85053A Kconfig and Makefile entries, so the chip can +be used as a standard Linux RTC device on platforms that include it. + +Upstream-Status: Pending + +Signed-off-by: Carlos Menin +Reviewed-by: Sergio Prado +--- + drivers/rtc/Kconfig | 9 + + drivers/rtc/Makefile | 1 + + drivers/rtc/rtc-pcf85053a.c | 689 ++++++++++++++++++++++++++++++++++++ + 3 files changed, 699 insertions(+) + create mode 100644 drivers/rtc/rtc-pcf85053a.c + +diff --git a/drivers/rtc/Kconfig b/drivers/rtc/Kconfig +index 66eb1122248b..7906d4e8ee86 100644 +--- a/drivers/rtc/Kconfig ++++ b/drivers/rtc/Kconfig +@@ -473,6 +473,15 @@ config RTC_DRV_PCF8523 + This driver can also be built as a module. If so, the module + will be called rtc-pcf8523. + ++config RTC_DRV_PCF85053A ++ tristate "NXP PCF85053A" ++ select REGMAP_I2C ++ help ++ If you say yes here you get support for the PCF85053A RTC chip ++ ++ This driver can also be built as a module. If so, the module ++ will be called rtc-pcf85053a. ++ + config RTC_DRV_PCF85063 + tristate "NXP PCF85063" + select REGMAP_I2C +diff --git a/drivers/rtc/Makefile b/drivers/rtc/Makefile +index f62340ecc534..979d1141e5cc 100644 +--- a/drivers/rtc/Makefile ++++ b/drivers/rtc/Makefile +@@ -125,6 +125,7 @@ obj-$(CONFIG_RTC_DRV_PCAP) += rtc-pcap.o + obj-$(CONFIG_RTC_DRV_PCF2123) += rtc-pcf2123.o + obj-$(CONFIG_RTC_DRV_PCF2127) += rtc-pcf2127.o + obj-$(CONFIG_RTC_DRV_PCF50633) += rtc-pcf50633.o ++obj-$(CONFIG_RTC_DRV_PCF85053A) += rtc-pcf85053a.o + obj-$(CONFIG_RTC_DRV_PCF85063) += rtc-pcf85063.o + obj-$(CONFIG_RTC_DRV_PCF8523) += rtc-pcf8523.o + obj-$(CONFIG_RTC_DRV_PCF85363) += rtc-pcf85363.o +diff --git a/drivers/rtc/rtc-pcf85053a.c b/drivers/rtc/rtc-pcf85053a.c +new file mode 100644 +index 000000000000..e255407ea796 +--- /dev/null ++++ b/drivers/rtc/rtc-pcf85053a.c +@@ -0,0 +1,689 @@ ++// SPDX-License-Identifier: GPL-2.0 ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#define ADDR_NVMEM 0x57 ++ ++#define REG_SECS 0x00 ++#define REG_MINUTES 0x02 ++#define REG_HOURS 0x04 ++#define REG_WEEKDAYS 0x06 ++#define REG_DAYS 0x07 ++#define REG_MONTHS 0x08 ++#define REG_YEARS 0x09 ++ ++#define REG_SECOND_ALM 0x01 ++#define REG_MINUTE_ALM 0x03 ++#define REG_HOUR_ALM 0x05 ++ ++#define REG_CTRL 0x0a ++#define REG_CTRL_ST BIT(7) ++#define REG_CTRL_DM BIT(6) ++#define REG_CTRL_HF BIT(5) ++#define REG_CTRL_DSM BIT(4) ++#define REG_CTRL_AIE BIT(3) ++#define REG_CTRL_OFIE BIT(2) ++#define REG_CTRL_CIE BIT(1) ++#define REG_CTRL_TWO BIT(0) ++ ++#define REG_STATUS 0x0b ++#define REG_STATUS_AF BIT(7) ++#define REG_STATUS_OF BIT(6) ++#define REG_STATUS_RTCF BIT(5) ++#define REG_STATUS_CIF BIT(4) ++#define REG_STATUS_BVL GENMASK(2, 0) ++ ++#define REG_CLKOUT 0x0c ++#define REG_CLKOUT_CKE BIT(7) ++#define REG_CLKOUT_CKD GENMASK(1, 0) ++ ++#define REG_CTRL2 0x0d ++#define REG_CTRL2_MWO BIT(0) ++ ++#define REG_SCRATCHPAD 0x0e ++ ++#define REG_VERSION 0x0f ++#define REG_VENDOR 0x10 ++#define REG_MODEL 0x11 ++ ++#define REG_OFFSET 0x12 ++ ++#define REG_OSCILLATOR 0x13 ++#define REG_OSC_CLKIV BIT(7) ++#define REG_OSC_OFFM BIT(6) ++#define REG_OSC_LOWJ BIT(4) ++#define REG_OSC_OSCD GENMASK(3, 2) ++#define REG_OSC_CL GENMASK(1, 0) ++ ++#define REG_ACCESS 0x14 ++#define REG_ACCESS_XCLK BIT(7) ++ ++#define REG_SEC_TS 0x15 ++#define REG_MIN_TS 0x16 ++#define REG_HOUR_TS 0x17 ++#define REG_DAYWK_TS 0x18 ++#define REG_DAYMON_TS 0x19 ++#define REG_MON_TS 0x1a ++#define REG_YEAR_TS 0x1b ++ ++#define REG_RCODE1 0x1c ++#define REG_RCODE2 0x1d ++ ++#define OFFSET_STEP0 2170 ++#define OFFSET_STEP1 2034 ++ ++struct pcf85053a { ++ struct rtc_device *rtc; ++ struct regmap *regmap; ++ struct regmap *regmap_nvmem; ++}; ++ ++struct pcf85053a_config { ++ struct regmap_config regmap; ++ struct regmap_config regmap_nvmem; ++}; ++ ++static int pcf85053a_read_offset(struct device *dev, long *offset) ++{ ++ struct pcf85053a *pcf85053a = dev_get_drvdata(dev); ++ long val; ++ u32 reg_offset, reg_oscillator; ++ int ret; ++ ++ ret = regmap_read(pcf85053a->regmap, REG_OFFSET, ®_offset); ++ if (ret) ++ return -EIO; ++ ++ ret = regmap_read(pcf85053a->regmap, REG_OSCILLATOR, ®_oscillator); ++ if (ret) ++ return -EIO; ++ ++ val = sign_extend32(reg_offset, 7); ++ ++ if (reg_oscillator & REG_OSC_OFFM) ++ *offset = val * OFFSET_STEP1; ++ else ++ *offset = val * OFFSET_STEP0; ++ ++ return 0; ++} ++ ++static int pcf85053a_set_offset(struct device *dev, long offset) ++{ ++ struct pcf85053a *pcf85053a = dev_get_drvdata(dev); ++ s8 mode0, mode1, reg_offset; ++ unsigned int ret, error0, error1; ++ ++ if (offset > OFFSET_STEP0 * 127) ++ return -ERANGE; ++ if (offset < OFFSET_STEP0 * -128) ++ return -ERANGE; ++ ++ ret = regmap_set_bits(pcf85053a->regmap, REG_ACCESS, REG_ACCESS_XCLK); ++ if (ret) ++ return -EIO; ++ ++ mode0 = DIV_ROUND_CLOSEST(offset, OFFSET_STEP0); ++ mode1 = DIV_ROUND_CLOSEST(offset, OFFSET_STEP1); ++ ++ error0 = abs(offset - (mode0 * OFFSET_STEP0)); ++ error1 = abs(offset - (mode1 * OFFSET_STEP1)); ++ if (error0 < error1) { ++ reg_offset = mode0; ++ ret = regmap_clear_bits(pcf85053a->regmap, REG_OSCILLATOR, ++ REG_OSC_OFFM); ++ } else { ++ reg_offset = mode1; ++ ret = regmap_set_bits(pcf85053a->regmap, REG_OSCILLATOR, ++ REG_OSC_OFFM); ++ } ++ if (ret) ++ return -EIO; ++ ++ ret = regmap_write(pcf85053a->regmap, REG_OFFSET, reg_offset); ++ ++ return ret; ++} ++ ++static int pcf85053a_rtc_check_reliability(struct device *dev, u8 status_reg) ++{ ++ int ret = 0; ++ ++ if (status_reg & REG_STATUS_CIF) { ++ dev_warn(dev, "tamper detected," ++ " date/time is not reliable\n"); ++ ret = -EINVAL; ++ } ++ ++ if (status_reg & REG_STATUS_OF) { ++ dev_warn(dev, "oscillator fail detected," ++ " date/time is not reliable.\n"); ++ ret = -EINVAL; ++ } ++ ++ if (status_reg & REG_STATUS_RTCF) { ++ dev_warn(dev, "power loss detected," ++ " date/time is not reliable.\n"); ++ ret = -EINVAL; ++ } ++ ++ return ret; ++} ++ ++static int pcf85053a_rtc_read_time(struct device *dev, struct rtc_time *tm) ++{ ++ struct pcf85053a *pcf85053a = dev_get_drvdata(dev); ++ u8 buf[REG_STATUS + 1]; ++ int ret, len = sizeof(buf); ++ ++ ret = regmap_bulk_read(pcf85053a->regmap, REG_SECS, buf, len); ++ if (ret) { ++ dev_err(dev, "%s: error %d\n", __func__, ret); ++ return ret; ++ } ++ ++ ret = pcf85053a_rtc_check_reliability(dev, buf[REG_STATUS]); ++ if (ret) ++ return ret; ++ ++ tm->tm_year = buf[REG_YEARS]; ++ /* adjust for 1900 base of rtc_time */ ++ tm->tm_year += 100; ++ ++ tm->tm_wday = (buf[REG_WEEKDAYS] - 1) & 7; /* 1 - 7 */ ++ tm->tm_sec = buf[REG_SECS]; ++ tm->tm_min = buf[REG_MINUTES]; ++ tm->tm_hour = buf[REG_HOURS]; ++ tm->tm_mday = buf[REG_DAYS]; ++ tm->tm_mon = buf[REG_MONTHS] - 1; /* 1 - 12 */ ++ ++ return 0; ++} ++ ++static int pcf85053a_rtc_set_time(struct device *dev, struct rtc_time *tm) ++{ ++ int ret; ++ struct pcf85053a *pcf85053a = dev_get_drvdata(dev); ++ struct reg_sequence regs[] = { ++ REG_SEQ0(REG_SECS, tm->tm_sec), ++ REG_SEQ0(REG_MINUTES, tm->tm_min), ++ REG_SEQ0(REG_HOURS, tm->tm_hour), ++ REG_SEQ0(REG_WEEKDAYS, (tm->tm_wday + 1) & 0x7), /* 1 - 7 */ ++ REG_SEQ0(REG_DAYS, tm->tm_mday), ++ REG_SEQ0(REG_MONTHS, tm->tm_mon + 1), /* 1 - 12 */ ++ REG_SEQ0(REG_YEARS, tm->tm_year % 100), ++ }; ++ ++ /* tamper event will clear this bit */ ++ ret = regmap_set_bits(pcf85053a->regmap, REG_CTRL, REG_CTRL_TWO); ++ if (ret) ++ return ret; ++ ++ ret = regmap_set_bits(pcf85053a->regmap, REG_CTRL, REG_CTRL_ST); ++ if (ret) ++ return ret; ++ ++ ret = regmap_multi_reg_write(pcf85053a->regmap, regs, ARRAY_SIZE(regs)); ++ if (ret) ++ return ret; ++ ++ ret = regmap_clear_bits(pcf85053a->regmap, REG_CTRL, REG_CTRL_ST); ++ if (ret) ++ return ret; ++ ++ ret = regmap_clear_bits(pcf85053a->regmap, REG_STATUS, REG_STATUS_OF); ++ ++ return ret; ++} ++ ++static int pcf85053a_bvl_to_mv(unsigned int bvl) ++{ ++ long mv_table[] = { ++ 1700, ++ 1900, ++ 2100, ++ 2300, ++ 2500, ++ 2700, ++ 2900, ++ 3100, ++ }; ++ return mv_table[bvl & 7]; ++} ++ ++static int pcf85053a_hwmon_read_in(struct device *dev, long *mV) ++{ ++ struct pcf85053a *pcf85053a = dev_get_drvdata(dev); ++ unsigned int status; ++ int ret; ++ ++ ret = regmap_read(pcf85053a->regmap, REG_STATUS, &status); ++ if (ret) ++ return ret; ++ ++ *mV = pcf85053a_bvl_to_mv(status & REG_STATUS_BVL); ++ return 0; ++} ++ ++static umode_t pcf85053a_hwmon_is_visible(const void *data, ++ enum hwmon_sensor_types type, ++ u32 attr, int channel) ++{ ++ if (type != hwmon_in) ++ return 0; ++ ++ switch (attr) { ++ case hwmon_in_input: ++ return 0444; ++ default: ++ return 0; ++ } ++} ++ ++static int pcf85053a_hwmon_read(struct device *dev, ++ enum hwmon_sensor_types type, ++ u32 attr, int channel, long *val) ++{ ++ int ret; ++ ++ switch (attr) { ++ case hwmon_in_input: ++ ret = pcf85053a_hwmon_read_in(dev, val); ++ break; ++ default: ++ ret = -EOPNOTSUPP; ++ break; ++ } ++ ++ return ret; ++} ++ ++static u32 pcf85053a_hwmon_in_config[] = { ++ HWMON_I_INPUT, ++ 0 ++}; ++ ++static const struct hwmon_channel_info pcf85053a_hwmon_in = { ++ .type = hwmon_in, ++ .config = pcf85053a_hwmon_in_config, ++}; ++ ++static const struct hwmon_channel_info *pcf85053a_hwmon_info[] = { ++ &pcf85053a_hwmon_in, ++ 0 ++}; ++ ++static const struct hwmon_ops pcf85053a_hwmon_ops = { ++ .is_visible = pcf85053a_hwmon_is_visible, ++ .read = pcf85053a_hwmon_read, ++}; ++ ++static const struct hwmon_chip_info pcf85053a_hwmon_chip_info = { ++ .ops = &pcf85053a_hwmon_ops, ++ .info = pcf85053a_hwmon_info, ++}; ++ ++static int pcf85053a_hwmon_register(struct device *dev, const char *name) ++{ ++ struct pcf85053a *pcf85053a = dev_get_drvdata(dev); ++ struct device *hwmon_dev; ++ ++ hwmon_dev = devm_hwmon_device_register_with_info(dev, name, pcf85053a, ++ &pcf85053a_hwmon_chip_info, ++ 0); ++ if (IS_ERR(hwmon_dev)) { ++ dev_err(dev, "unable to register hwmon device: %ld\n", ++ PTR_ERR(hwmon_dev)); ++ return PTR_ERR(hwmon_dev); ++ } ++ ++ return 0; ++} ++ ++static int pcf85053a_nvmem_read(void *priv, unsigned int offset, void *val, ++ size_t num) ++{ ++ int ret; ++ struct pcf85053a *pcf85053a = priv; ++ struct regmap *regmap_nvmem = pcf85053a->regmap_nvmem; ++ ++ ret = regmap_bulk_read(regmap_nvmem, offset, val, num); ++ if (ret) ++ pr_warn("%s: failed to read nvmem: %d\n", __func__, ret); ++ ++ return ret; ++} ++ ++static int pcf85053a_nvmem_write(void *priv, unsigned int offset, void *val, ++ size_t num) ++{ ++ int ret; ++ struct pcf85053a *pcf85053a = priv; ++ struct regmap *regmap_nvmem = pcf85053a->regmap_nvmem; ++ ++ /* tamper event will clear this bit */ ++ ret = regmap_set_bits(pcf85053a->regmap, REG_CTRL2, REG_CTRL2_MWO); ++ if (ret) { ++ pr_warn("%s: failed to enable nvmem write: %d", __func__, ret); ++ return ret; ++ } ++ ++ ret = regmap_bulk_write(regmap_nvmem, offset, val, num); ++ if (ret) ++ pr_warn("%s: failed to write nvmem: %d\n", __func__, ret); ++ ++ return ret; ++} ++ ++static ssize_t attr_flag_clear(struct device *dev, ++ struct device_attribute *attr, ++ const char *buf, size_t count, ++ u8 reg, u8 flag) ++{ ++ struct pcf85053a *pcf85053a = dev_get_drvdata(dev->parent); ++ int ret; ++ ++ (void)attr; ++ (void)buf; ++ (void)count; ++ ++ ret = regmap_clear_bits(pcf85053a->regmap, reg, flag); ++ if (ret) ++ return -EIO; ++ ++ return count; ++} ++ ++static ssize_t attr_flag_read(struct device *dev, ++ struct device_attribute *attr, ++ char *buf, ++ u8 reg, u8 flag) ++{ ++ struct pcf85053a *pcf85053a = dev_get_drvdata(dev->parent); ++ unsigned int status, val; ++ int ret; ++ ++ (void)attr; ++ ret = regmap_read(pcf85053a->regmap, reg, &status); ++ if (ret) ++ return -EIO; ++ ++ val = (status & flag) != 0; ++ ++ return sprintf(buf, "%u\n", val); ++} ++ ++/* flags that can be read or written to be cleared */ ++#define PCF85053A_ATTR_FLAG_RWC(name, reg, flag) \ ++ static ssize_t name ## _store( \ ++ struct device *dev, \ ++ struct device_attribute *attr, \ ++ const char *buf, \ ++ size_t count) \ ++ { \ ++ return attr_flag_clear(dev, attr, buf, count, \ ++ REG_ ## reg, REG_ ## reg ## _ ## flag); \ ++ } \ ++ static ssize_t name ## _show( \ ++ struct device *dev, \ ++ struct device_attribute *attr, \ ++ char *buf) \ ++ { \ ++ return attr_flag_read(dev, attr, buf, \ ++ REG_ ## reg, REG_ ## reg ## _ ## flag); \ ++ } \ ++ static DEVICE_ATTR_RW(name) ++ ++PCF85053A_ATTR_FLAG_RWC(rtc_fail, STATUS, RTCF); ++PCF85053A_ATTR_FLAG_RWC(oscillator_fail, STATUS, OF); ++PCF85053A_ATTR_FLAG_RWC(rtc_clear, STATUS, CIF); ++ ++static struct attribute *pcf85053a_attrs_flags[] = { ++ &dev_attr_rtc_fail.attr, ++ &dev_attr_oscillator_fail.attr, ++ &dev_attr_rtc_clear.attr, ++ 0, ++}; ++ ++static const struct attribute_group pcf85053a_attr_group = { ++ .name = "flags", ++ .attrs = pcf85053a_attrs_flags, ++}; ++ ++static const struct rtc_class_ops pcf85053a_rtc_ops = { ++ .read_offset = pcf85053a_read_offset, ++ .set_offset = pcf85053a_set_offset, ++ .read_time = pcf85053a_rtc_read_time, ++ .set_time = pcf85053a_rtc_set_time, ++}; ++ ++static const struct pcf85053a_config pcf85053a_config = { ++ .regmap = { ++ .reg_bits = 8, ++ .val_bits = 8, ++ .max_register = 0x1d, ++ }, ++ .regmap_nvmem = { ++ .reg_bits = 8, ++ .val_bits = 8, ++ .max_register = 0xff, ++ }, ++}; ++ ++static int pcf85053a_add_nvmem(struct i2c_client *client, ++ struct pcf85053a *pcf85053a) ++{ ++ int ret; ++ const struct pcf85053a_config *config = &pcf85053a_config; ++ struct i2c_client *client_nvmem; ++ struct nvmem_config nvmem_cfg = { ++ .name = "pcf85053a_nvmem", ++ .reg_read = pcf85053a_nvmem_read, ++ .reg_write = pcf85053a_nvmem_write, ++ .type = NVMEM_TYPE_BATTERY_BACKED, ++ .size = 128, ++ }; ++ ++ client_nvmem = devm_i2c_new_dummy_device(&client->dev, client->adapter, ++ ADDR_NVMEM); ++ if (IS_ERR(client_nvmem)) { ++ dev_warn(&client->dev, "failed to create nvmem i2c device\n"); ++ return -ENODEV; ++ } ++ ++ pcf85053a->regmap_nvmem = devm_regmap_init_i2c(client_nvmem, ++ &config->regmap_nvmem); ++ if (IS_ERR(pcf85053a->regmap_nvmem)) { ++ dev_warn(&client->dev, "failed to init nvmem regmap\n"); ++ return -EIO; ++ } ++ ++ nvmem_cfg.priv = pcf85053a; ++ ret = devm_rtc_nvmem_register(pcf85053a->rtc, &nvmem_cfg); ++ ++ return ret; ++} ++ ++static void pcf85053a_set_load_capacitance(struct device *dev, u8 *reg_ctrl) ++{ ++ int ret; ++ u32 val; ++ u8 regval; ++ ++ ret = of_property_read_u32(dev->of_node, "quartz-load-femtofarads", ++ &val); ++ if (ret) { ++ dev_warn(dev, "failed to read quartz-load-femtofarads property," ++ " assuming 12500"); ++ val = 12500; ++ } ++ ++ switch (val) { ++ case 7000: ++ regval = 0; ++ break; ++ case 6000: ++ regval = 1; ++ break; ++ default: ++ dev_warn(dev, "invalid quartz-load-femtofarads value: %u," ++ " assuming 12500", val); ++ fallthrough; ++ case 12500: ++ regval = 2; ++ break; ++ } ++ ++ *reg_ctrl |= regval; ++} ++ ++static void pcf85053a_set_drive_control(struct device *dev, u8 *reg_ctrl) ++{ ++ int ret; ++ const char *val; ++ u8 regval; ++ ++ ret = of_property_read_string(dev->of_node, "nxp,quartz-drive-control", ++ &val); ++ if (ret) { ++ dev_warn(dev, "failed to read nxp,quartz-drive-control property," ++ " assuming 'normal' drive"); ++ val = "normal"; ++ } ++ ++ if (!strcmp(val, "normal")) { ++ regval = 0; ++ } else if (!strcmp(val, "low")) { ++ regval = 1; ++ } else if (!strcmp(val, "high")) { ++ regval = 2; ++ } else { ++ dev_warn(dev, "invalid nxp,quartz-drive-control value: %s," ++ " assuming 'normal' drive", val); ++ regval = 0; ++ } ++ ++ *reg_ctrl |= (regval << 2); ++} ++ ++static void pcf85053a_set_low_jitter(struct device *dev, u8 *reg_ctrl) ++{ ++ bool val; ++ u8 regval; ++ ++ val = of_property_read_bool(dev->of_node, "nxp,low-jitter-mode"); ++ ++ regval = val ? 1 : 0; ++ *reg_ctrl |= (regval << 4); ++} ++ ++static void pcf85053a_set_clk_inverted(struct device *dev, u8 *reg_ctrl) ++{ ++ bool val; ++ u8 regval; ++ ++ val = of_property_read_bool(dev->of_node, "nxp,clk-inverted"); ++ ++ regval = val ? 1 : 0; ++ *reg_ctrl |= (regval << 7); ++} ++ ++static int pcf85053a_probe(struct i2c_client *client) ++{ ++ int ret; ++ struct pcf85053a *pcf85053a; ++ const struct pcf85053a_config *config = &pcf85053a_config; ++ u8 reg_ctrl; ++ ++ pcf85053a = devm_kzalloc(&client->dev, sizeof(*pcf85053a), GFP_KERNEL); ++ if (!pcf85053a) { ++ dev_err(&client->dev, "failed to allocate device: no memory"); ++ return -ENOMEM; ++ } ++ ++ pcf85053a->regmap = devm_regmap_init_i2c(client, &config->regmap); ++ if (IS_ERR(pcf85053a->regmap)) { ++ dev_err(&client->dev, "failed to allocate regmap: %ld\n", ++ PTR_ERR(pcf85053a->regmap)); ++ return PTR_ERR(pcf85053a->regmap); ++ } ++ ++ i2c_set_clientdata(client, pcf85053a); ++ ++ pcf85053a->rtc = devm_rtc_allocate_device(&client->dev); ++ if (IS_ERR(pcf85053a->rtc)) { ++ dev_err(&client->dev, "failed to allocate rtc: %ld\n", ++ PTR_ERR(pcf85053a->rtc)); ++ return PTR_ERR(pcf85053a->rtc); ++ } ++ ++ pcf85053a->rtc->ops = &pcf85053a_rtc_ops; ++ pcf85053a->rtc->range_min = RTC_TIMESTAMP_BEGIN_2000; ++ pcf85053a->rtc->range_max = RTC_TIMESTAMP_END_2099; ++ ++ reg_ctrl = REG_CTRL_DM | REG_CTRL_HF | REG_CTRL_CIE; ++ pcf85053a_set_load_capacitance(&client->dev, ®_ctrl); ++ pcf85053a_set_drive_control(&client->dev, ®_ctrl); ++ pcf85053a_set_low_jitter(&client->dev, ®_ctrl); ++ pcf85053a_set_clk_inverted(&client->dev, ®_ctrl); ++ ++ ret = regmap_write(pcf85053a->regmap, REG_CTRL, reg_ctrl); ++ if (ret) { ++ dev_err(&client->dev, "failed to configure rtc: %d\n", ret); ++ return ret; ++ } ++ ++ ret = rtc_add_group(pcf85053a->rtc, &pcf85053a_attr_group); ++ if (ret) { ++ dev_err(&client->dev, "failed to add sysfs entry: %d\n", ret); ++ return ret; ++ } ++ ++ ret = devm_rtc_register_device(pcf85053a->rtc); ++ if (ret) { ++ dev_err(&client->dev, "failed to register rtc: %d\n", ret); ++ return ret; ++ } ++ ++ ret = pcf85053a_add_nvmem(client, pcf85053a); ++ if (ret) { ++ dev_err(&client->dev, "failed to register nvmem: %d\n", ret); ++ return ret; ++ } ++ ++ ret = pcf85053a_hwmon_register(&client->dev, client->name); ++ if (ret) ++ dev_err(&client->dev, "failed to register hwmon: %d\n", ret); ++ ++ return ret; ++} ++ ++static const __maybe_unused struct of_device_id dev_ids[] = { ++ { .compatible = "nxp,pcf85053a", .data = &pcf85053a_config }, ++ { }, ++}; ++MODULE_DEVICE_TABLE(of, dev_ids); ++ ++static struct i2c_driver pcf85053a_driver = { ++ .driver = { ++ .name = "pcf85053a", ++ .of_match_table = of_match_ptr(dev_ids), ++ }, ++ .probe = &pcf85053a_probe, ++}; ++ ++module_i2c_driver(pcf85053a_driver); ++ ++MODULE_AUTHOR("Carlos Menin "); ++MODULE_DESCRIPTION("PCF85053A I2C RTC driver"); ++MODULE_LICENSE("GPL"); +-- +2.34.1 + diff --git a/patches-sonic/0063-reset-phy-using-polling-function-not-register-write.patch b/patches-sonic/0063-reset-phy-using-polling-function-not-register-write.patch new file mode 100644 index 000000000..716b69572 --- /dev/null +++ b/patches-sonic/0063-reset-phy-using-polling-function-not-register-write.patch @@ -0,0 +1,187 @@ +From d380e09439f8576e4a89dd1f5c32850ce1c14d62 Mon Sep 17 00:00:00 2001 +From: Vadim Pasternak +Date: Thu, 2 Apr 2026 12:24:00 +0300 +Subject: [PATCH BMC Realtek PHY 1/1] net: phy: realtek: reset PHY using polling function instead of register write + +When resetting the Realtek PHY, the driver previously triggered the +reset by writing the reset bit directly to a PHY register and continued +without confirming that the reset had actually completed. Because the +reset takes a finite, non-zero time, configuration accesses that ran +immediately afterwards could observe the PHY in an intermediate state, +leading to intermittent, hard-to-reproduce link/initialization failures. + +Replace the bare register write with a polling-based reset that triggers +the reset and then waits until the PHY reports that the reset has +completed before returning. This closes the timing window between the +reset request and the subsequent PHY access. + +Signed-off-by: Mohammad Abu Gazala +--- + drivers/net/phy/realtek.c | 115 +++++++++++++++++++++++++++++++++++++- + 1 file changed, 114 insertions(+), 1 deletion(-) + +diff --git a/drivers/net/phy/realtek.c b/drivers/net/phy/realtek.c +index 8ce5705a..b05e150d 100644 +--- a/drivers/net/phy/realtek.c ++++ b/drivers/net/phy/realtek.c +@@ -44,6 +44,12 @@ + #define RTL8211F_TX_DELAY BIT(8) + #define RTL8211F_RX_DELAY BIT(3) + ++#ifdef CONFIG_RTL_RGMII_SGMII_3809 ++#define RTL8211F_MODE_SEL (BIT(2) | BIT(1) | BIT(0)) ++#define RTL8211F_SGMII_RGMII_PM 0x4 ++#define RTL8211F_SGMII_RGMII_MP 0x5 ++#endif ++ + #define RTL8211F_ALDPS_PLL_OFF BIT(1) + #define RTL8211F_ALDPS_ENABLE BIT(2) + #define RTL8211F_ALDPS_XTAL_OFF BIT(12) +@@ -120,6 +126,26 @@ static int rtl821x_write_page(struct phy_device *phydev, int page) + return __phy_write(phydev, RTL821x_PAGE_SELECT, page); + } + ++#ifdef CONFIG_REALTEK_600MV_DRIVE_STRENGTH ++static void set_600mv_drive_strength(struct phy_device *phydev){ ++ int ret; ++ struct device *dev = &phydev->mdio.dev; ++ dev_info(dev, "Writing drive strength indirect \n"); ++ rtl821x_write_page(phydev, 0xa43); ++ ret = __phy_write(phydev, 0x1b, 0xdcd0); ++ rtl821x_write_page(phydev, 0xa43); ++ ret = __phy_write(phydev, 0x1c, 0x1096); ++ rtl821x_write_page(phydev, 0xa43); ++ ret = __phy_write(phydev, 0x1b, 0xdcd2); ++ rtl821x_write_page(phydev, 0xa43); ++ ret = __phy_write(phydev, 0x1c, 0xB490); ++ rtl821x_write_page(phydev, 0xa43); ++ ret = __phy_write(phydev, 0x1f, 0x0); ++ ++ dev_info(dev, "Done writing drive strength indirect\n"); ++} ++#endif ++ + static int rtl821x_probe(struct phy_device *phydev) + { + struct device *dev = &phydev->mdio.dev; +@@ -156,7 +182,9 @@ static int rtl821x_probe(struct phy_device *phydev) + } + + phydev->priv = priv; +- ++#ifdef CONFIG_REALTEK_600MV_DRIVE_STRENGTH ++ set_600mv_drive_strength(phydev); ++#endif + return 0; + } + +@@ -409,6 +437,49 @@ static int rtl8211f_config_init(struct phy_device *phydev) + return 0; + } + ++#ifdef CONFIG_RTL_RGMII_SGMII_3809 ++ /* RTL8211F SGMII to RGMII MODE verification - NVIDIA BMC */ ++ ret = phy_read_paged(phydev, 0xd40, 0x10); ++ if(ret < 0) { ++ dev_err(dev, "Failed to read Mode Selection\n"); ++ return ret; ++ } ++ ++ ret = ret & RTL8211F_MODE_SEL; ++ ++ if (ret == RTL8211F_SGMII_RGMII_PM || ret == RTL8211F_SGMII_RGMII_MP) { ++ ++ /* Force hardware straps 3'b100, SGMII(PHY Side) to RGMII(MAC Side) for NVIDIA BMC*/ ++ ret = phy_modify_paged_changed(phydev, 0xd40, 0x10, RTL8211F_MODE_SEL, RTL8211F_SGMII_RGMII_PM); ++ if(ret < 0) { ++ dev_err(dev, "Failed to update the Mode selection \n"); ++ return ret; ++ } ++ ++ ret = genphy_soft_reset(phydev); ++ if(ret < 0) { ++ dev_err(dev, "Failed to reset PHY \n"); ++ return ret; ++ } ++ ++ /* SGMII ANAR (SGMII Auto-Negotiation Advertising Register) */ ++ /*Link status : set to 1 , Duplex Mode : Full Duplex*/ ++ ret = phy_modify_paged_changed(phydev, 0xd08, 0x10, BIT(3) | BIT(2), BIT(3) | BIT(2)); ++ if(ret < 0) { ++ dev_err(dev, "Failed to update the Link & Duplex \n"); ++ return ret; ++ } ++ ++ /* Speed : 1000Mbps */ ++ ret = phy_modify_paged_changed(phydev, 0xd08, 0x10, BIT(1) | BIT(0), 0x2); ++ if(ret < 0){ ++ dev_err(dev, "Failed to update the Speed \n"); ++ return ret; ++ } ++ ++ } ++#endif ++ + ret = phy_modify_paged_changed(phydev, 0xd08, 0x11, RTL8211F_TX_DELAY, + val_txdly); + if (ret < 0) { +@@ -687,6 +758,44 @@ static void rtlgen_decode_speed(struct phy_device *phydev, int val) + } + } + ++#ifdef CONFIG_RTL_RGMII_SGMII_3809 ++static int rtlgen_read_status(struct phy_device *phydev); ++ ++static int rtl8211f_rtlgen_read_status(struct phy_device *phydev) ++{ ++ int ret; ++ ++ /* RTL8211F SGMII to RGMII MODE verification - NVIDIA BMC */ ++ ret = phy_read_paged(phydev, 0xd40, 0x10); ++ if(ret < 0) { ++ dev_err(&phydev->mdio.dev, "Failed to read Mode Selection\n"); ++ return ret; ++ } ++ ++ ret = ret & RTL8211F_MODE_SEL; ++ ++ if (ret == RTL8211F_SGMII_RGMII_PM || ret == RTL8211F_SGMII_RGMII_MP) { ++ ++ /* Check Link status */ ++ ret = phy_read_paged(phydev, 0xdcf, 0x15); ++ if (ret < 0) { ++ dev_err(&phydev->mdio.dev,"failed to read link status \n"); ++ return ret; ++ } ++ /* Link is Up */ ++ if (ret & BIT(4)) { ++ phydev->link = 1; ++ phydev->speed = 1000; ++ phydev->duplex=DUPLEX_FULL; ++ } ++ } else { ++ return rtlgen_read_status(phydev); ++ } ++ ++ return 0; ++} ++#endif ++ + static int rtlgen_read_status(struct phy_device *phydev) + { + int ret, val; +@@ -1338,7 +1447,11 @@ static struct phy_driver realtek_drvs[] = { + .name = "RTL8211F Gigabit Ethernet", + .probe = rtl821x_probe, + .config_init = &rtl8211f_config_init, ++#ifdef CONFIG_RTL_RGMII_SGMII_3809 ++ .read_status = rtl8211f_rtlgen_read_status, ++#else + .read_status = rtlgen_read_status, ++#endif + .config_intr = &rtl8211f_config_intr, + .handle_interrupt = rtl8211f_handle_interrupt, + .suspend = rtl821x_suspend, +-- +2.34.1 + diff --git a/patches-sonic/0064-hwmon-pmbus-isl68137-Add-support-for-Renesas-RAA2289.patch b/patches-sonic/0064-hwmon-pmbus-isl68137-Add-support-for-Renesas-RAA2289.patch new file mode 100644 index 000000000..5146195e8 --- /dev/null +++ b/patches-sonic/0064-hwmon-pmbus-isl68137-Add-support-for-Renesas-RAA2289.patch @@ -0,0 +1,66 @@ +From cc38b3dd3d977941a93831f44cd0326a8e8ab34b Mon Sep 17 00:00:00 2001 +From: Vasily Vityukov +Date: Mon, 20 Apr 2026 12:19:20 +0300 +Subject: [PATCH] hwmon: pmbus/isl68137: Add support for Renesas RAA228942 and + RAA228943 + +Add I2C device IDs for Renesas RAA228942 and RAA228943. + +At the Linux PMBus hwmon interface level currently supported by this +driver, these devices are compatible with the existing 2-rail non-TC +controllers, so devicetree will use fallback compatibles and no +dedicated OF match entries are needed. + +Signed-off-by: Dawei Liu +--- + Documentation/hwmon/isl68137.rst | 20 ++++++++++++++++++++ + drivers/hwmon/pmbus/isl68137.c | 2 ++ + 2 files changed, 22 insertions(+) + +diff --git a/Documentation/hwmon/isl68137.rst b/Documentation/hwmon/isl68137.rst +index 0e71b22047f8..1a1424dd640f 100644 +--- a/Documentation/hwmon/isl68137.rst ++++ b/Documentation/hwmon/isl68137.rst +@@ -374,6 +374,26 @@ Supported chips: + + Publicly available (after August 2020 launch) at the Renesas website + ++ * Renesas RAA228942 ++ ++ Prefix: 'raa228942' ++ ++ Addresses scanned: - ++ ++ Datasheet: ++ ++ Provided by Renesas upon request and NDA ++ ++ * Renesas RAA228943 ++ ++ Prefix: 'raa228943' ++ ++ Addresses scanned: - ++ ++ Datasheet: ++ ++ Provided by Renesas upon request and NDA ++ + * Renesas RAA229001 + + Prefix: 'raa229001' +diff --git a/drivers/hwmon/pmbus/isl68137.c b/drivers/hwmon/pmbus/isl68137.c +index 7e53fb1d5ea3..f3baaee6227a 100644 +--- a/drivers/hwmon/pmbus/isl68137.c ++++ b/drivers/hwmon/pmbus/isl68137.c +@@ -311,6 +311,8 @@ static const struct i2c_device_id raa_dmpvr_id[] = { + {"raa228004", raa_dmpvr2_hv}, + {"raa228006", raa_dmpvr2_hv}, + {"raa228228", raa_dmpvr2_2rail_nontc}, ++ {"raa228942", raa_dmpvr2_2rail_nontc}, ++ {"raa228943", raa_dmpvr2_2rail_nontc}, + {"raa229001", raa_dmpvr2_2rail}, + {"raa229004", raa_dmpvr2_2rail}, + {} +-- +2.34.1 + diff --git a/patches-sonic/8014-mlxsw-i2c-Downstream-Increase-I2C-timeout-for-HW-sem.patch b/patches-sonic/8014-mlxsw-i2c-Downstream-Increase-I2C-timeout-for-HW-sem.patch new file mode 100644 index 000000000..2411f3df5 --- /dev/null +++ b/patches-sonic/8014-mlxsw-i2c-Downstream-Increase-I2C-timeout-for-HW-sem.patch @@ -0,0 +1,36 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Nadav Hugi +Date: Mon, 25 May 2026 16:52:00 +0300 +Subject: [PATCH v6.12 1/1] mlxsw: i2c: Downstream: Increase I2C timeout for + HW semaphore stability + +When taking an IR core dump on SPC1 debug kernel, the firmware halts +its IRISCs to capture a clean snapshot. This pause takes ~5 seconds +and occasionally exceeds the driver's hardcoded 5-second I2C timeout, +causing spurious "HW semaphore is not released" errors. The system +recovers normally after the dump completes — this is not a real +hardware failure. + +Increase MLXSW_I2C_TIMEOUT_MSECS from 5000 to 10000 to give the +driver enough headroom to wait out the firmware pause. + +Signed-off-by: Nadav Hugi +--- + drivers/net/ethernet/mellanox/mlxsw/i2c.c | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/drivers/net/ethernet/mellanox/mlxsw/i2c.c b/drivers/net/ethernet/mellanox/mlxsw/i2c.c +index XXXXXXX..YYYYYYY 100644 +--- a/drivers/net/ethernet/mellanox/mlxsw/i2c.c ++++ b/drivers/net/ethernet/mellanox/mlxsw/i2c.c +@@ -50,7 +50,7 @@ + #define MLXSW_I2C_BLK_DEF 32 + #define MLXSW_I2C_BLK_MAX 100 + #define MLXSW_I2C_RETRY 5 +-#define MLXSW_I2C_TIMEOUT_MSECS 5000 ++#define MLXSW_I2C_TIMEOUT_MSECS 10000 + #define MLXSW_I2C_CMD_RETRY_FW_ERR 3 + #define MLXSW_I2C_MAX_DATA_SIZE 256 + +-- +2.20.1 diff --git a/patches-sonic/series b/patches-sonic/series index cc9ec902c..455c7a9f4 100644 --- a/patches-sonic/series +++ b/patches-sonic/series @@ -65,7 +65,6 @@ driver-i2c-smbus-add-disable_spd-module-parameter.patch ###-> mellanox_sdk-end ###-> mellanox_hw_mgmt-start -0001-mlxbf-bootctl-Constify-struct-bin_attribute.patch 0002-platform-mellanox-mlxbf-pmc-Add-support-for-monitori.patch 0003-platform-mellanox-mlxbf-pmc-Add-support-for-clock_me.patch 0004-platform-mellanox-mlxbf-bootctl-use-sysfs_emit-inste.patch @@ -119,6 +118,12 @@ driver-i2c-smbus-add-disable_spd-module-parameter.patch 0057-hwmon-pmbus-mp5926-Add-support-for-MP5926-device.patch 0058-PCI-DOE-Poll-DOE-Busy-bit-for-up-to-1-second-in-pci.patch 0059-hwmon-pmbus-xdpe1a2g7b-Add-support-for-XDPE1A2G7B-5B.patch +0060-leds-mlxreg-Provide-conversion-for-hardware-LED-colo.patch +0060-platform-mellanox-nvsw-bmc-Downstream-Add-protection.patch +0061-platform-mellanox-mlxreg-io-Increase-max-supported-a.patch +0062-Add-support-for-NXP-s-PCF85053A-RTC-chip.patch +0063-reset-phy-using-polling-function-not-register-write.patch +0064-hwmon-pmbus-isl68137-Add-support-for-Renesas-RAA2289.patch 8000-mlxsw-Use-weak-reverse-dependencies-for-firmware-fla.patch 8002-mlxsw-i2c-SONIC-ISSU-Prevent-transaction-execution-f.patch 8003-mlxsw-minimal-Downstream-Ignore-error-reading-SPAD-r.patch @@ -131,6 +136,7 @@ driver-i2c-smbus-add-disable_spd-module-parameter.patch 8010-mlxsw-minimal-Downstream-Disable-ethtool-interface.patch 8011-hwmon-pmbus-Downstream-Workaround-for-psu-attributes.patch 8013-hwmon-pmbus-mp2975-Clear-interrupts-at-probe.patch +8014-mlxsw-i2c-Downstream-Increase-I2C-timeout-for-HW-sem.patch ###-> mellanox_hw_mgmt-end # Cisco patches for 5.10 kernel @@ -253,6 +259,18 @@ arista_goldfinch-dts.patch ###-> aspeed-end ###-> nvidia_aspeed_bmc-start +0001-mctp-driver-net-Extend-MCTP-support.patch +0002-mctp-aspeed-IRoT-Add-initial-support.patch +0003-mctp-driver-net-Add-global-APIs-for-MCTP.patch +0004-mctp-driver-net-Fix-MCTP-over-VDM-PCIe.patch +0005-mctp-driver-net-Fix-MCTP-over-USB.patch +0006-jtag-Add-JTAG-core-headers-GPIO-mux-and-locking-support.patch +0007-JTAG-Aspeed-Fix-kernel-configuration.patch +0008-arm64-dts-aspeed-spc6-bmc-ext-phram-env-tpm-i3c-pull.patch +0009-arm64-dts-aspeed-split-SPC6-BMC-DTS-for-SONiC-and-Op.patch +0010-net-ftgmac100-hard-reset-mac-for-sgmii.patch +0011-arm64-dts-aspeed-spc6-bmc-fix-sgmii-phy-names.patch +0012-usb-ehci-Add-Aspeed-AST2700-support.patch ###-> nvidia_aspeed_bmc-end # QID-45097: Crackarmor fixes From 09f678c47413534424edf93ef93cbe23350d9524 Mon Sep 17 00:00:00 2001 From: Daniel Isakov Date: Tue, 11 Aug 2026 09:42:32 -0700 Subject: [PATCH 2/2] Enable BMC Kconfig for sonic-aspeed (HW-MGMT 7.0070.1013) Populate the nvidia_aspeed_bmc block in config.local/arm64/config.sonic-aspeed with the 86 options required by the BMC patches added in this series. The 202605 branch did not carry this block yet, so the change is purely additive. The options cover the MCTP stack and its I2C, I3C and IRoT transports; JTAG; the Aspeed AST2700 platform (clocks, mailbox, eSPI, LPC snoop, SGPIO, UART routing, USB and SPI); networking (FTGMAC100, Realtek PHY, fixed-PHY settings and the USB gadget ethernet functions); sensors (ADT7475, SBTSI, SBRMI, EMC1403, LM25066, MP2855, MP2888, MP2975 and the mlxreg fan driver); platform drivers (mlxreg-io, hotplug, LEDs, watchdog and the BMC HID driver); the PCF85053A RTC added by 0062; the I2C and I3C controllers and muxes; and MTD phram backing the U-Boot environment reserved by 0008. Signed-off-by: Daniel Isakov --- config.local/arm64/config.sonic-aspeed | 86 ++++++++++++++++++++++++++ 1 file changed, 86 insertions(+) diff --git a/config.local/arm64/config.sonic-aspeed b/config.local/arm64/config.sonic-aspeed index 27a0ba456..d50c8eb16 100644 --- a/config.local/arm64/config.sonic-aspeed +++ b/config.local/arm64/config.sonic-aspeed @@ -106,5 +106,91 @@ CONFIG_CRYPTO_DEV_ASPEED=y # CONFIG_CRYPTO_DEV_ASPEED_ECDSA is not set ###-> nvidia_aspeed_bmc-start +CONFIG_MODVERSIONS=y +CONFIG_MCTP=y +CONFIG_MTD_PHRAM=y +CONFIG_CHR_DEV_SG=y +CONFIG_REALTEK_PHY=m +CONFIG_USB_USBNET=y +CONFIG_USB_NET_CDCETHER=y +CONFIG_USB_NET_CDC_NCM=y +CONFIG_USB_NET_CDC_SUBSET_ENABLE=y +CONFIG_USB_NET_CDC_SUBSET=y +CONFIG_TCG_TIS_SPI=y +CONFIG_TCG_TIS_SPI_CR50=n +CONFIG_I2C_MUX_MLXCPLD=m +CONFIG_I2C_SLAVE_EEPROM=y +CONFIG_I3C=m +CONFIG_SPI_SPIDEV=y +CONFIG_SENSORS_ADT7475=y +CONFIG_SENSORS_SBTSI=y +CONFIG_SENSORS_SBRMI=y +CONFIG_SENSORS_EMC1403=m +CONFIG_WATCHDOG_HRTIMER_PRETIMEOUT=y +CONFIG_WATCHDOG_PRETIMEOUT_GOV=y +CONFIG_HIDRAW=y +CONFIG_USB_MON=y +CONFIG_USB_UHCI_HCD=y +CONFIG_USB_F_ECM=y +CONFIG_USB_F_RNDIS=y +CONFIG_USB_ETH=y +CONFIG_LEDS_MLXREG=m +CONFIG_MELLANOX_PLATFORM=y +CONFIG_MAX1363=y +CONFIG_TI_ADS1015=y +CONFIG_TI_ADS7924=y +CONFIG_MSDOS_FS=y +CONFIG_FAT_DEFAULT_UTF8=y +CONFIG_PSTORE_RAM=y +CONFIG_LSUSB=y +CONFIG_USB_ETH_RNDIS=y +CONFIG_USB_ASPEED=y +CONFIG_SPI_ASPEED_TXRX=y +CONFIG_SPI_ASPEED_SMC=y +CONFIG_SPI_FMC=m +CONFIG_FIXED_PHY_APPLY=y +CONFIG_FIXED_PHY_SPEED=100 +CONFIG_FIXED_PHY_FULL_DUPLEX=y +CONFIG_FTGMAC100_FIXED_PHY_SPEED=100 +CONFIG_FTGMAC100_FIXED_PHY_FULL_DUPLEX=1 +CONFIG_ASPEED_MCTP=y +CONFIG_JTAG=y +CONFIG_JTAG_ASPEED=y +CONFIG_COMMON_CLK_AST2700=y +CONFIG_AST2700_MBOX=y +CONFIG_RTL_RGMII_SGMII_3809=y +CONFIG_HID_CP2112=m +CONFIG_GPIO_SYSFS=y +CONFIG_GPIO_ASPEED_SGPIO=y +CONFIG_ASPEED_LPC_SNOOP=y +CONFIG_AST2700_ESPI=y +CONFIG_MISC_DEVICES=y +CONFIG_ASPEED_UART_ROUTING=y +CONFIG_FTGMAC100=m +CONFIG_SENSORS_LM25066=m +CONFIG_SENSORS_MP2855=m +CONFIG_SENSORS_MP2888=m +CONFIG_SENSORS_MP2975=m +CONFIG_MCTP_FLOWS=y +CONFIG_MCTP_TRANSPORT_I2C=y +CONFIG_I3C_MCTP=m +CONFIG_I3C_TARGET_MCTP=m +CONFIG_NVIDIA_AST27XX_IROT=m +CONFIG_PLDMFW=y +CONFIG_I2C_COMPAT=y +CONFIG_I2C_AST2600=y +CONFIG_I2C_AST2600_SLAVE_RX_WORKAROUND=y +CONFIG_DW_I3C_MASTER=m +CONFIG_MIPI_I3C_HCI=m +CONFIG_I3C_HUB=m +CONFIG_REGMAP_I3C=m +CONFIG_SENSORS_MLXREG_FAN=m +CONFIG_MLXREG_HOTPLUG=m +CONFIG_MLXREG_IO=m +CONFIG_MLX_WDT=m +CONFIG_RTC_DRV_PCF85053A=y +CONFIG_NVSW_BMC_HID162=m +CONFIG_I2C_MUX_REGMAP=y +CONFIG_PCIE_PME=n ###-> nvidia_aspeed_bmc-end