From f7ceb4661110718b6614364361e341692f2c7279 Mon Sep 17 00:00:00 2001 From: Daniel Isakov Date: Mon, 1 Jun 2026 20:41:45 +0300 Subject: [PATCH 1/2] Integrate HW-MGMT 7.0060.1047 patches These patches are not yet upstreamed and have not been sent for review, hence marked Upstream-Status: Pending. Signed-off-by: Daniel Isakov --- ...-mctp-driver-net-Extend-MCTP-support.patch | 14804 ++++++++++++++++ ...ootctl-Constify-struct-bin_attribute.patch | 16 +- ...mctp-aspeed-IRoT-Add-initial-support.patch | 1176 ++ ...-driver-net-Add-global-APIs-for-MCTP.patch | 39 + ...tp-driver-net-Fix-MCTP-over-VDM-PCIe.patch | 52 + ...05-mctp-driver-net-Fix-MCTP-over-USB.patch | 875 + ...headers-GPIO-mux-and-locking-support.patch | 1657 ++ ...JTAG-Aspeed-Fix-kernel-configuration.patch | 37 + ...x-nvsw-bmc-Add-system-control-and-mo.patch | 5136 +++++- ...ide-conversion-for-hardware-LED-colo.patch | 84 + ...x-nvsw-bmc-Downstream-Add-protection.patch | 186 + ...x-mlxreg-io-Increase-max-supported-a.patch | 31 + ...support-for-NXP-s-PCF85053A-RTC-chip.patch | 746 + ...-polling-function-not-register-write.patch | 182 + patches-sonic/series | 12 + 15 files changed, 24462 insertions(+), 571 deletions(-) create mode 100644 patches-sonic/0001-mctp-driver-net-Extend-MCTP-support.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/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 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..1df44731f --- /dev/null +++ b/patches-sonic/0001-mctp-driver-net-Extend-MCTP-support.patch @@ -0,0 +1,14804 @@ +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. + +Add support for MCTP over SPI, VRoT. + +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 index cd30de111..130f8a428 100644 --- a/patches-sonic/0001-mlxbf-bootctl-Constify-struct-bin_attribute.patch +++ b/patches-sonic/0001-mlxbf-bootctl-Constify-struct-bin_attribute.patch @@ -15,8 +15,8 @@ Link: https://lore.kernel.org/r/20241215-sysfs-const-bin_attr-mellanox-v1-1-b6fe 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(-) + drivers/platform/mellanox/mlxbf-bootctl.c | 2 +- + 1 file changed, 1 insertions(+), 1 deletions(-) diff --git a/drivers/platform/mellanox/mlxbf-bootctl.c b/drivers/platform/mellanox/mlxbf-bootctl.c index dd5f370..7e246dd 100644 @@ -31,18 +31,6 @@ index dd5f370..7e246dd 100644 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..cb7f0ef66 --- /dev/null +++ b/patches-sonic/0002-mctp-aspeed-IRoT-Add-initial-support.patch @@ -0,0 +1,1176 @@ +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 support for MCTP over IRoT. + +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..d9e48f7ba --- /dev/null +++ b/patches-sonic/0003-mctp-driver-net-Add-global-APIs-for-MCTP.patch @@ -0,0 +1,39 @@ +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 + +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..a546c12bd --- /dev/null +++ b/patches-sonic/0004-mctp-driver-net-Fix-MCTP-over-VDM-PCIe.patch @@ -0,0 +1,52 @@ +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 + +Add fixes for MCTP over VDM/PCIe (option MCTP_TRANSPORT_PCIE_VDM). + +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..99e3d7e3a --- /dev/null +++ b/patches-sonic/0005-mctp-driver-net-Fix-MCTP-over-USB.patch @@ -0,0 +1,875 @@ +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 + +Add fixes for MCTP over USB (option MCTP_TRANSPORT_USB). + +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..b01e6b14b --- /dev/null +++ b/patches-sonic/0007-JTAG-Aspeed-Fix-kernel-configuration.patch @@ -0,0 +1,37 @@ +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 + +Change Kconfig and Makefile. + +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/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..ac814fd24 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,8 +1,8 @@ -From 7d0f9191f8e282b28411b21fd33d166c4efafc68 Mon Sep 17 00:00:00 2001 +From ac19027ee711c9ab109c8cacde22af73483179db 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 - monitoring driver for Nvidia BMC +Subject: [PATCH BMC platform-next 1/4] platform/mellanox: nvsw-bmc: Add system + control and monitoring driver for Nvidia BMC The driver allows system control and monitoring of Nvidia switches through CPLD/FPGA devices from Based Management Controller SoC. @@ -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 | 7285 +++++++++++++++++ + drivers/platform/mellanox/nvsw-core.c | 717 ++ + 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 | 318 + + 9 files changed, 10932 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 9bf0fb17a05e..b6c6576f7702 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 7204b10388ca..a8e4619f56d2 100644 --- a/drivers/platform/mellanox/Kconfig +++ b/drivers/platform/mellanox/Kconfig -@@ -105,6 +105,44 @@ config MLXBF_PMC +@@ -104,6 +104,44 @@ config MLXBF_PMC to performance monitoring counters within various blocks in the Mellanox BlueField SoC via a sysfs interface. @@ -101,7 +103,7 @@ 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 e86723b44c2e..123edaa99d77 100644 --- a/drivers/platform/mellanox/Makefile +++ b/drivers/platform/mellanox/Makefile @@ -11,4 +11,8 @@ obj-$(CONFIG_MLXREG_DPU) += mlxreg-dpu.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 000000000000..89343d7660a4 --- /dev/null +++ b/drivers/platform/mellanox/nvsw-bmc-hid162.c -@@ -0,0 +1,3310 @@ +@@ -0,0 +1,7285 @@ +// 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,72 @@ index 000000000..3c70e2979 + }, +}; + ++static struct mlxreg_core_data nvsw_bmc_hid191_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_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 +945,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 +1101,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 +1308,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, + }, +}; @@ -1244,6 +1375,7 @@ index 000000000..3c70e2979 + }, +}; + ++ +static struct mlxreg_core_data nvsw_bmc_hid180_vr1_pwr_alert_items_data[] = { + { + .label = "asic1_vdd_pwr_alert", @@ -1437,7 +1569,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 +1643,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 +1837,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 +1888,3532 @@ 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, ++ .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_hid181_leakage_items_data[] = { ++ { ++ .label = "leakage_csm1", + .reg = NVSW_REG_LEAK_OFFSET, -+ .mask = GENMASK(1, 0), -+ .count = ARRAY_SIZE(nvsw_bmc_hid180_leakage_items_data), -+ .inversed = 0, -+ .health = false, ++ .mask = BIT(0), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .label = "leakage_csm2", ++ .reg = NVSW_REG_LEAK_OFFSET, ++ .mask = BIT(1), ++ .hpdev.nr = NVSW_NR_NONE, ++ }, ++ { ++ .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, ++ }, ++ { ++ .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_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_BRD1_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, ++ }, ++ { ++ .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_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, ++ }, ++ { ++ .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_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, + }, + { -+ .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 = "current_temp_ro", ++ .reg = NVSW_REG_CURRENT_TEMP_RO, ++ .bit = GENMASK(7, 0), ++ .mode = 0444, + }, + { -+ .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 = "switch_ic_qty", ++ .reg = NVSW_REG_SWITCH_IC_QTY, ++ .bit = GENMASK(7, 0), ++ .mode = 0444, + }, + { -+ .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 = "asic_pg_fail", ++ .reg = NVSW_REG_GP4_RO_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(7), ++ .mode = 0444, + }, + { -+ .data = nvsw_bmc_hid180_vr1_pwr_alert_items_data, -+ .aggr_mask = NVSW_AGGR_MASK, -+ .reg = NVSW_REG_VR1_ALERT_OFFSET, ++ .label = "spi_chnl_select", ++ .reg = NVSW_REG_SPI_CHNL_SELECT, + .mask = GENMASK(7, 0), -+ .count = ARRAY_SIZE(nvsw_bmc_hid180_vr1_pwr_alert_items_data), -+ .inversed = 0, -+ .health = false, ++ .bit = 1, ++ .mode = 0644, + }, + { -+ .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 = "config1", ++ .reg = NVSW_REG_CONFIG1_OFFSET, ++ .bit = GENMASK(7, 0), ++ .mode = 0444, + }, + { -+ .data = nvsw_bmc_hid180_erot_error_items_data, -+ .aggr_mask = NVSW_AGGR_MASK, -+ .reg = NVSW_REG_EROT_ERR_OFFSET, -+ .mask = BIT(0), -+ .count = ARRAY_SIZE(nvsw_bmc_hid180_erot_error_items_data), -+ .inversed = 1, -+ .health = false, ++ .label = "config2", ++ .reg = NVSW_REG_CONFIG2_OFFSET, ++ .bit = GENMASK(7, 0), ++ .mode = 0444, + }, + { -+ .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 = "config3", ++ .reg = NVSW_REG_CONFIG3_OFFSET, ++ .bit = GENMASK(7, 0), ++ .mode = 0444, + }, + { -+ .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 = "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 +5439,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), @@ -1926,119 +5463,297 @@ index 000000000..3c70e2979 + .reg = NVSW_REG_CPLD3_PN_OFFSET, + .bit = GENMASK(15, 0), + .mode = 0444, -+ .regnum = 2, ++ .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 = "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 = "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 = "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 = "cpld4_pn", -+ .reg = NVSW_REG_CPLD4_PN_OFFSET, -+ .bit = GENMASK(15, 0), ++ .label = "cpld10_version_min", ++ .reg = NVSW_REG_CPLD10_MVER_OFFSET, ++ .bit = GENMASK(7, 0), + .mode = 0444, -+ .regnum = 2, + }, + { -+ .label = "cpld1_version_min", -+ .reg = NVSW_REG_CPLD1_MVER_OFFSET, -+ .bit = GENMASK(7, 0), ++ .label = "sb_asic1_clk_brd_ci_boot_fail", ++ .reg = NVSW_REG_GP2_RO_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(0), + .mode = 0444, + }, + { -+ .label = "cpld2_version_min", -+ .reg = NVSW_REG_CPLD2_MVER_OFFSET, -+ .bit = GENMASK(7, 0), ++ .label = "sb_asic1_clk_brd_fail", ++ .reg = NVSW_REG_GP2_RO_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(1), + .mode = 0444, + }, + { -+ .label = "cpld3_version_min", -+ .reg = NVSW_REG_CPLD3_MVER_OFFSET, -+ .bit = GENMASK(7, 0), ++ .label = "asic1_pg", ++ .reg = NVSW_REG_GP2_RO_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(2), + .mode = 0444, + }, + { -+ .label = "cpld4_version_min", -+ .reg = NVSW_REG_CPLD4_MVER_OFFSET, -+ .bit = GENMASK(7, 0), ++ .label = "sb_asic2_clk_brd_ci_boot_fail", ++ .reg = NVSW_REG_GP2_RO_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(4), + .mode = 0444, + }, + { -+ .label = "fan_dir", -+ .reg = NVSW_REG_GP0_RO_OFFSET, -+ .mask = GENMASK(7, 0), ++ .label = "sb_asic2_clk_brd_fail", ++ .reg = NVSW_REG_GP2_RO_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(5), + .mode = 0444, + }, + { -+ .label = "fan_present", -+ .reg = NVSW_REG_FAN_OFFSET, -+ .mask = GENMASK(7, 0), ++ .label = "asic2_pg", ++ .reg = NVSW_REG_GP2_RO_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(6), + .mode = 0444, + }, + { -+ .label = "cpu_mctp_ready", -+ .reg = NVSW_REG_GP0_OFFSET, ++ .label = "asic1_perst", ++ .reg = NVSW_REG_GP4_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(0), ++ .mode = 0644, ++ }, ++ { ++ .label = "asic2_perst", ++ .reg = NVSW_REG_GP4_OFFSET, + .mask = GENMASK(7, 0) & ~BIT(1), + .mode = 0644, + }, + { -+ .label = "cpu_shutdown_req", -+ .reg = NVSW_REG_GP0_OFFSET, ++ .label = "asic3_perst", ++ .reg = NVSW_REG_GP4_OFFSET, + .mask = GENMASK(7, 0) & ~BIT(2), + .mode = 0644, + }, + { -+ .label = "vpd_wp", -+ .reg = NVSW_REG_GP0_OFFSET, ++ .label = "asic4_perst", ++ .reg = NVSW_REG_GP4_OFFSET, + .mask = GENMASK(7, 0) & ~BIT(3), + .mode = 0644, -+ .secured = 1, + }, + { -+ .label = "pcie_asic_reset_dis", -+ .reg = NVSW_REG_GP0_OFFSET, ++ .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 = 0644, ++ .mode = 0444, + }, + { -+ .label = "shutdown_unlock", -+ .reg = NVSW_REG_GP0_OFFSET, ++ .label = "sb_asic4_clk_brd_fail", ++ .reg = NVSW_REG_GP5_RO_OFFSET, + .mask = GENMASK(7, 0) & ~BIT(5), -+ .mode = 0644, ++ .mode = 0444, + }, + { -+ .label = "cpu_power_off_ready", -+ .reg = NVSW_REG_GP0_OFFSET, ++ .label = "asic4_pg", ++ .reg = NVSW_REG_GP5_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), -+ .mode = 0644, ++ .label = "bios_status_ro", ++ .reg = NVSW_REG_GPCOM0_OFFSET, ++ .mask = GENMASK(3, 1), ++ .bit = 3, ++ .mode = 0444, + }, + { -+ .label = "leakage_conn_en", -+ .reg = NVSW_REG_GP6_OFFSET, -+ .mask = GENMASK(7, 0) & ~BIT(0), -+ .mode = 0644, ++ .label = "bios_start_retry", ++ .reg = NVSW_REG_GPCOM0_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(4), ++ .mode = 0444, + }, + { -+ .label = "bmc_reset_reg", -+ .reg = NVSW_REG_GP6_OFFSET, -+ .mask = GENMASK(7, 0) & ~BIT(1), -+ .mode = 0644, ++ .label = "bios_active_image", ++ .reg = NVSW_REG_GPCOM0_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(5), ++ .mode = 0444, + }, + { -+ .label = "spi_chnl_select", -+ .reg = NVSW_REG_SPI_CHNL_SELECT, -+ .mask = GENMASK(7, 0), -+ .bit = 1, ++ .label = "pwr_converter_prog_en", ++ .reg = NVSW_REG_GP7_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(0), + .mode = 0644, + }, + { -+ .label = "pwr_converter_prog_en", ++ .label = "bmc_req_conf_flash_updt", + .reg = NVSW_REG_GP7_OFFSET, -+ .mask = GENMASK(7, 0) & ~BIT(0), ++ .mask = GENMASK(7, 0) & ~BIT(1), + .mode = 0644, -+ .secured = 1, + }, + { + .label = "graceful_power_off", @@ -2059,150 +5774,163 @@ index 000000000..3c70e2979 + .mode = 0200, + }, + { -+ .label = "platform_reset", -+ .reg = NVSW_REG_RESET_GP1_OFFSET, -+ .mask = GENMASK(7, 0) & ~BIT(0), ++ .label = "stby_pwr_en_unmask", ++ .reg = NVSW_REG_GP7_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(5), + .mode = 0200, + }, + { -+ .label = "main_brd_reset", -+ .reg = NVSW_REG_RESET_GP1_OFFSET, -+ .mask = GENMASK(7, 0) & ~BIT(2), -+ .mode = 0200, ++ .label = "uart_baud_rate", ++ .reg = NVSW_REG_UART_BAUD_OFFSET, ++ .mask = NVSW_UART_BAUD_MASK, ++ .bit = 1, ++ .mode = 0644, + }, + { -+ .label = "nic_reset", -+ .reg = NVSW_REG_RESET_GP1_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 = "tpm_reset", -+ .reg = NVSW_REG_RESET_GP1_OFFSET, ++ .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 = "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 = "tpm_reset", ++ .reg = NVSW_REG_RESET_GP1_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 = "bmc_tpm_reset", ++ .reg = NVSW_REG_RESET_GP1_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(7), ++ .mode = 0200, + }, + { + .label = "reset_long_pb", @@ -2223,24 +5951,12 @@ 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", -+ .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), @@ -2253,143 +5969,193 @@ index 000000000..3c70e2979 + .mode = 0444, + }, + { -+ .label = "reset_from_carrier", ++ .label = "reset_soc", + .reg = NVSW_REG_RESET_CAUSE1_OFFSET, -+ .mask = GENMASK(7, 0) & ~BIT(1), ++ .mask = GENMASK(7, 0) & ~BIT(5), + .mode = 0444, + }, + { -+ .label = "reset_aux_pwr_or_reload", -+ .reg = NVSW_REG_RESET_CAUSE1_OFFSET, -+ .mask = GENMASK(7, 0) & ~BIT(2), ++ .label = "reset_system", ++ .reg = NVSW_REG_RESET_CAUSE2_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(1), + .mode = 0444, + }, + { -+ .label = "reset_comex_pwr_fail", -+ .reg = NVSW_REG_RESET_CAUSE1_OFFSET, -+ .mask = GENMASK(7, 0) & ~BIT(3), ++ .label = "reset_sw_pwr_off", ++ .reg = NVSW_REG_RESET_CAUSE2_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(2), + .mode = 0444, + }, + { -+ .label = "reset_platform", -+ .reg = NVSW_REG_RESET_CAUSE1_OFFSET, -+ .mask = GENMASK(7, 0) & ~BIT(4), ++ .label = "reset_thermal", ++ .reg = NVSW_REG_RESET_CAUSE2_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(3), + .mode = 0444, + }, + { -+ .label = "reset_soc", -+ .reg = NVSW_REG_RESET_CAUSE1_OFFSET, ++ .label = "main_12v_fail", ++ .reg = NVSW_REG_RESET_CAUSE2_OFFSET, + .mask = GENMASK(7, 0) & ~BIT(5), + .mode = 0444, + }, + { -+ .label = "reset_from_erot", -+ .reg = NVSW_REG_RESET_CAUSE1_OFFSET, ++ .label = "reset_main_51v", ++ .reg = NVSW_REG_RESET_CAUSE2_OFFSET, + .mask = GENMASK(7, 0) & ~BIT(6), + .mode = 0444, + }, + { -+ .label = "reset_pwr", -+ .reg = NVSW_REG_RESET_CAUSE1_OFFSET, ++ .label = "reset_mgmt_pwr", ++ .reg = NVSW_REG_RESET_CAUSE2_OFFSET, + .mask = GENMASK(7, 0) & ~BIT(7), + .mode = 0444, + }, + { -+ .label = "reset_erot", -+ .reg = NVSW_REG_RESET_CAUSE2_OFFSET, -+ .mask = GENMASK(7, 0) & ~BIT(0), ++ .label = "port80", ++ .reg = NVSW_REG_GP1_RO_OFFSET, ++ .bit = GENMASK(7, 0), + .mode = 0444, + }, + { -+ .label = "reset_system", -+ .reg = NVSW_REG_RESET_CAUSE2_OFFSET, -+ .mask = GENMASK(7, 0) & ~BIT(1), ++ .label = "switch_ic_qty", ++ .reg = NVSW_REG_SWITCH_IC_QTY, ++ .bit = GENMASK(7, 0), + .mode = 0444, + }, + { -+ .label = "reset_sw_pwr_off", -+ .reg = NVSW_REG_RESET_CAUSE2_OFFSET, -+ .mask = GENMASK(7, 0) & ~BIT(2), ++ .label = "gp_swb_mgmt_present", ++ .reg = NVSW_REG_GP4_RO_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(0), + .mode = 0444, + }, + { -+ .label = "reset_comex_thermal", -+ .reg = NVSW_REG_RESET_CAUSE2_OFFSET, -+ .mask = GENMASK(7, 0) & ~BIT(3), ++ .label = "gp_io_brd_prsnt", ++ .reg = NVSW_REG_GP4_RO_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(1), + .mode = 0444, + }, + { -+ .label = "reset_comex_power", -+ .reg = NVSW_REG_RESET_CAUSE2_OFFSET, -+ .mask = GENMASK(7, 0) & ~BIT(4), ++ .label = "gp_tpm_brd_prsnt", ++ .reg = NVSW_REG_GP4_RO_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(2), + .mode = 0444, + }, + { -+ .label = "reset_pwr_converter_fail", -+ .reg = NVSW_REG_RESET_CAUSE2_OFFSET, -+ .mask = GENMASK(7, 0) & ~BIT(5), ++ .label = "gp_bmc_presnt", ++ .reg = NVSW_REG_GP4_RO_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(3), + .mode = 0444, + }, + { -+ .label = "reset_main_51v", -+ .reg = NVSW_REG_RESET_CAUSE2_OFFSET, -+ .mask = GENMASK(7, 0) & ~BIT(6), ++ .label = "gp_bmc_tpm_brd_prsnt", ++ .reg = NVSW_REG_GP4_RO_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(5), + .mode = 0444, + }, + { -+ .label = "reset_mgmt_pwr", -+ .reg = NVSW_REG_RESET_CAUSE2_OFFSET, -+ .mask = GENMASK(7, 0) & ~BIT(7), ++ .label = "jtag_cap", ++ .reg = NVSW_REG_FU_CAP_OFFSET, ++ .mask = NVSW_FU_CAP_MASK, ++ .bit = 1, + .mode = 0444, + }, + { -+ .label = "port80", -+ .reg = NVSW_REG_GP1_RO_OFFSET, -+ .bit = GENMASK(7, 0), -+ .mode = 0444, ++ .label = "jtag_enable", ++ .reg = NVSW_REG_FIELD_UPGRADE, ++ .mask = GENMASK(7, 0) & ~BIT(1), ++ .mode = 0644, + }, + { + .label = "bios_status", -+ .reg = NVSW_REG_GPCOM0_OFFSET, -+ .mask = NVSW_BIOS_STATUS_MASK, ++ .reg = NVSW_REG_SAFE_BIOS_OFFSET, ++ .mask = GENMASK(2, 0), + .bit = 2, -+ .mode = 0444, ++ .mode = 0644, + }, + { -+ .label = "bios_start_retry", -+ .reg = NVSW_REG_GPCOM0_OFFSET, ++ .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 = 0444, ++ .mode = 0644, + }, + { -+ .label = "bios_active_image", -+ .reg = NVSW_REG_GPCOM0_OFFSET, ++ .label = "bios_upgrade_fail", ++ .reg = NVSW_REG_SAFE_BIOS_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 = "bios_image_invert", ++ .reg = NVSW_REG_SAFE_BIOS_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(6), ++ .mode = 0644, + }, + { -+ .label = "clk_brd1_boot_fail", -+ .reg = NVSW_REG_GP4_RO_OFFSET, ++ .label = "cpu_req_conf_flash_updt", ++ .reg = NVSW_REG_SAFE_BIOS_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(7), ++ .mode = 0644, ++ }, ++ { ++ .label = "mcu1_recovery", ++ .reg = NVSW_REG_PWM_CONTROL_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(6), ++ .mode = 0644, ++ }, ++ { ++ .label = "gp_vdd_spd_dis", ++ .reg = NVSW_REG_PWM_CONTROL_OFFSET, + .mask = GENMASK(7, 0) & ~BIT(4), -+ .mode = 0444, ++ .mode = 0644, + }, + { -+ .label = "clk_brd2_boot_fail", -+ .reg = NVSW_REG_GP4_RO_OFFSET, -+ .mask = GENMASK(7, 0) & ~BIT(5), -+ .mode = 0444, ++ .label = "gp_spd_b_wp", ++ .reg = NVSW_REG_PWM_CONTROL_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(3), ++ .mode = 0644, + }, + { -+ .label = "clk_brd_fail", -+ .reg = NVSW_REG_GP4_RO_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), ++ .bit = 4, ++ .mode = 0644, ++ }, ++ { ++ .label = "ignore_next_reset", ++ .reg = NVSW_REG_GP5_OFFSET, + .mask = GENMASK(7, 0) & ~BIT(6), -+ .mode = 0444, ++ .mode = 0644, ++ }, ++ { ++ .label = "bmc_reset_reg", ++ .reg = NVSW_REG_GP6_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(1), ++ .mode = 0644, + }, + { + .label = "asic_pg_fail", @@ -2398,19 +6164,59 @@ index 000000000..3c70e2979 + .mode = 0444, + }, + { -+ .label = "geo_addr", ++ .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, ++ .mask = 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 = "psu_qty", ++ .reg = NVSW_REG_SYS_CPBLTY0, ++ .mask = GENMASK(3, 0), ++ .bit = 3, ++ .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_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 +6236,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 +6263,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), @@ -2463,13 +6282,19 @@ index 000000000..3c70e2979 + .mode = 0444, + }, + { -+ .label = "cpld3_version_min", -+ .reg = NVSW_REG_CPLD3_MVER_OFFSET, ++ .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 = "bios_status", ++ .label = "bios_status_ro", + .reg = NVSW_REG_GPCOM0_OFFSET, + .mask = GENMASK(3, 1), + .bit = 3, @@ -2494,6 +6319,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 +6334,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 +6381,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 +6430,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 +6466,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 +6496,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 +6514,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 +6526,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, @@ -2742,20 +6556,55 @@ index 000000000..3c70e2979 + .mode = 0444, + }, + { -+ .label = "transport_status", ++ .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(1, 0), -+ .bit = 1, ++ .mask = GENMASK(7, 0) & ~BIT(0), + .mode = 0444, + }, + { -+ .label = "tpm_present", ++ .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 = "gp_bmc_presnt", + .reg = NVSW_REG_GP4_RO_OFFSET, + .mask = GENMASK(7, 0) & ~BIT(3), + .mode = 0444, + }, + { -+ .label = "asics_pg_fail", ++ .label = "sb_clk_brd_asic_ci_boot_fail", ++ .reg = NVSW_REG_GP4_RO_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(4), ++ .mode = 0444, ++ }, ++ { ++ .label = "gp_bmc_tpm_brd_prsnt", ++ .reg = NVSW_REG_GP4_RO_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(5), ++ .mode = 0444, ++ }, ++ { ++ .label = "sb_clk_brd_asic_fail", ++ .reg = NVSW_REG_GP4_RO_OFFSET, ++ .mask = GENMASK(7, 0) & ~BIT(6), ++ .mode = 0444, ++ }, ++ { ++ .label = "asics_pg", + .reg = NVSW_REG_GP4_RO_OFFSET, + .mask = GENMASK(7, 0) & ~BIT(7), + .mode = 0444, @@ -2770,8 +6619,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 +6654,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 +6666,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 +6709,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 +6728,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 +6760,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 +6936,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 +6980,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 +7068,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 +7088,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 +7099,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 +7125,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 +7150,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 +7175,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 +7189,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 +7239,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 +7300,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 +7331,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 +7370,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 +7383,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 +7408,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 000000000000..1bd2dfc622ce --- /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 +7433,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 +7450,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 +7470,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 +7529,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 +7567,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 +7595,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 +7607,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 +7765,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 +7855,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,7 +8131,7 @@ 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 000000000000..83d305080150 --- /dev/null +++ b/drivers/platform/mellanox/nvsw-host-l1.c @@ -0,0 +1,770 @@ @@ -4906,7 +8907,7 @@ index 000000000..83d305080 +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 +index 000000000000..e54bd4109b79 --- /dev/null +++ b/drivers/platform/mellanox/nvsw-host-spc5.c @@ -0,0 +1,943 @@ @@ -5855,7 +9856,7 @@ index 000000000..e54bd4109 + diff --git a/drivers/platform/mellanox/nvsw-host-spc6.c b/drivers/platform/mellanox/nvsw-host-spc6.c new file mode 100644 -index 000000000..449ba5ffd +index 000000000000..449ba5ffd589 --- /dev/null +++ b/drivers/platform/mellanox/nvsw-host-spc6.c @@ -0,0 +1,852 @@ @@ -6713,15 +10714,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 000000000000..4fa0d9d31bdd --- /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 +10741,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 +10764,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 +10791,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 +10830,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 +10854,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 +10903,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 +10944,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 +10955,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 +10970,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..2360cd2e3 --- /dev/null +++ b/patches-sonic/0061-platform-mellanox-mlxreg-io-Increase-max-supported-a.patch @@ -0,0 +1,31 @@ +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 + +Increase the number of supported mlxreg-io attributes. + +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..0a9061948 --- /dev/null +++ b/patches-sonic/0062-Add-support-for-NXP-s-PCF85053A-RTC-chip.patch @@ -0,0 +1,746 @@ +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. + +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..d79f0254d --- /dev/null +++ b/patches-sonic/0063-reset-phy-using-polling-function-not-register-write.patch @@ -0,0 +1,182 @@ +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] From + d2e5ed78b4233e73d5b392cb0ca7b61d4409e8de Mon Sep 17 00:00:00 2001 Subject: + [PATCH BMC Realtek PHY 1/1] reset phy using polling function not register + write + +replace the code to reset the phy with a call to a +function that resets the phy and waits for the reset to be done, +preventing race conditions. + +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/series b/patches-sonic/series index d8f1128b5..99dfb98d4 100644 --- a/patches-sonic/series +++ b/patches-sonic/series @@ -119,6 +119,11 @@ 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 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 @@ -252,6 +257,13 @@ 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 ###-> nvidia_aspeed_bmc-end # QID-45097: Crackarmor fixes From aa308087ef29d432a8cdef007c23521dee032b9b Mon Sep 17 00:00:00 2001 From: Daniel Isakov Date: Mon, 1 Jun 2026 20:41:45 +0300 Subject: [PATCH 2/2] Enable BMC Kconfig for sonic-aspeed (HW-MGMT 7.0060.1047) 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..de4a42486 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_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_RTC_OVER_ESPI=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