[PATCH 11/11] ipmi: Add NVIDIA LSTP IPMI BMC driver
From: Adrian Ambrozewicz
Date: Mon Oct 12 2026 - 13:45:43 EST
Add a BMC-side driver for the IPMI channels of NVIDIA LSTP USB adapters.
The adapter carries IPMI messages between the host and the BMC; each
channel is exposed to BMC userspace as an ipmi-lstp<n> character device
that reads requests from the host and writes the responses back.
By default each message on the node carries a one-byte message number
between the length and the payload, and a response echoes the number
of the request it answers. The number exists only on the node, not in
the LSTP protocol. With USB_LSTP_IPMI_MSG_NUM disabled the
node uses the ssif_bmc layout (struct ipmi_ssif_msg) instead, so
userspace written for ssif_bmc works unchanged.
Signed-off-by: Adrian Ambrozewicz <aambrozewicz@xxxxxxxxxx>
---
MAINTAINERS | 1 +
drivers/char/ipmi/Kconfig | 32 +++
drivers/char/ipmi/Makefile | 1 +
drivers/char/ipmi/ipmi-lstp.c | 464 ++++++++++++++++++++++++++++++++++
4 files changed, 498 insertions(+)
create mode 100644 drivers/char/ipmi/ipmi-lstp.c
diff --git a/MAINTAINERS b/MAINTAINERS
index 8e58895b7d5a..96667b3baa7a 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -19577,6 +19577,7 @@ F: Documentation/devicetree/bindings/spi/nvidia,lstp-spi.yaml
F: Documentation/devicetree/bindings/usb/nvidia,lstp.yaml
F: Documentation/driver-api/usb/lstp.rst
F: Documentation/firmware-guide/acpi/lstp.rst
+F: drivers/char/ipmi/ipmi-lstp.c
F: drivers/gpio/gpio-lstp.c
F: drivers/i2c/busses/i2c-lstp.c
F: drivers/leds/leds-lstp.c
diff --git a/drivers/char/ipmi/Kconfig b/drivers/char/ipmi/Kconfig
index 538a7d3c65bf..eb51a2d56fc9 100644
--- a/drivers/char/ipmi/Kconfig
+++ b/drivers/char/ipmi/Kconfig
@@ -119,6 +119,38 @@ config IPMI_POWEROFF
endif # IPMI_HANDLER
+config LSTP_IPMI_BMC
+ tristate "NVIDIA LSTP IPMI BMC support"
+ depends on USB_LSTP
+ default USB_LSTP
+ help
+ Say Y or M here to support the IPMI channels of NVIDIA LSTP USB
+ adapters on a BMC. Each channel is exposed as an ipmi-lstp<n>
+ character device through which BMC userspace receives requests
+ from the host and sends the responses back.
+
+ This driver can also be built as a module. If so, the module
+ will be called ipmi-lstp.
+
+config USB_LSTP_IPMI_MSG_NUM
+ bool "Message-number header on LSTP IPMI nodes"
+ depends on LSTP_IPMI_BMC
+ default y
+ help
+ Messages read from and written to an LSTP IPMI device node carry a
+ one-byte message number between the length and the payload, and a
+ response must echo the number of the request it answers. The number
+ exists only on the device node, not in the LSTP protocol, and no
+ other BMC-side IPMI driver has it.
+
+ Disable this option to drop it. The node then uses the layout of
+ struct ipmi_ssif_msg from the ssif_bmc driver, a length followed by
+ the payload, so that userspace written for ssif_bmc works unchanged.
+ A response answers the request read last, and is dropped once a newer
+ request has arrived.
+
+ Say Y if userspace parses the message number.
+
config IPMI_KCS_BMC
tristate
diff --git a/drivers/char/ipmi/Makefile b/drivers/char/ipmi/Makefile
index 4ea450a82242..2e320ce2c901 100644
--- a/drivers/char/ipmi/Makefile
+++ b/drivers/char/ipmi/Makefile
@@ -29,3 +29,4 @@ obj-$(CONFIG_ASPEED_KCS_IPMI_BMC) += kcs_bmc_aspeed.o
obj-$(CONFIG_NPCM7XX_KCS_IPMI_BMC) += kcs_bmc_npcm7xx.o
obj-$(CONFIG_IPMB_DEVICE_INTERFACE) += ipmb_dev_int.o
obj-$(CONFIG_SSIF_IPMI_BMC) += ssif_bmc.o
+obj-$(CONFIG_LSTP_IPMI_BMC) += ipmi-lstp.o
diff --git a/drivers/char/ipmi/ipmi-lstp.c b/drivers/char/ipmi/ipmi-lstp.c
new file mode 100644
index 000000000000..787d633d4ada
--- /dev/null
+++ b/drivers/char/ipmi/ipmi-lstp.c
@@ -0,0 +1,464 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * IPMI driver for LSTP USB interface.
+ *
+ * Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
+ */
+
+#include <linux/build_bug.h>
+#include <linux/err.h>
+#include <linux/idr.h>
+#include <linux/ipmi_ssif_bmc.h>
+#include <linux/kfifo.h>
+#include <linux/kref.h>
+#include <linux/miscdevice.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/poll.h>
+#include <linux/property.h>
+#include <linux/slab.h>
+
+#include <linux/usb/lstp.h>
+
+/* ~1 s of queued requests at OpenBMC rates; past that the host has retried. */
+#define BUFFER_SIZE 4096
+
+static DEFINE_IDA(lstp_ipmi_ida);
+
+#define IPMI_LSTP_PAYLOAD_MAX 254
+#define IPMI_MIN_MSG_LEN 2 /* netfn/lun + cmd */
+
+/*
+ * read(2) and write(2) layout with CONFIG_USB_LSTP_IPMI_MSG_NUM. Deployed
+ * userspace parses these bytes, so the field widths and offsets are fixed;
+ * native byte order, as both ends are the same host. @msg_num exists only on
+ * this interface, never on the wire.
+ */
+struct ipmi_lstp_msg_header {
+ __u32 len;
+ __u8 msg_num;
+} __packed;
+
+struct ipmi_lstp_msg {
+ struct ipmi_lstp_msg_header header;
+ __u8 payload[IPMI_LSTP_PAYLOAD_MAX];
+};
+
+static_assert(sizeof(struct ipmi_lstp_msg_header) == 5, "IPMI read(2) header layout is ABI");
+static_assert(offsetof(struct ipmi_lstp_msg_header, msg_num) == 4,
+ "IPMI read(2) header layout is ABI");
+
+/*
+ * The layout without CONFIG_USB_LSTP_IPMI_MSG_NUM is the ssif_bmc driver's:
+ * the same len and payload, no @msg_num.
+ */
+static_assert(offsetof(struct ipmi_ssif_msg, payload) ==
+ sizeof_field(struct ipmi_lstp_msg_header, len),
+ "Untagged layout must be the LSTP header without msg_num");
+static_assert(IPMI_SSIF_PAYLOAD_MAX == IPMI_LSTP_PAYLOAD_MAX,
+ "Untagged layout must carry the LSTP payload");
+
+#define LSTP_IPMI_MSG_NUM IS_ENABLED(CONFIG_USB_LSTP_IPMI_MSG_NUM)
+
+/* The leading bytes of struct ipmi_lstp_msg_header that userspace sees. */
+#define LSTP_IPMI_UAPI_HDR_LEN \
+ (LSTP_IPMI_MSG_NUM ? sizeof(struct ipmi_lstp_msg_header) : \
+ sizeof_field(struct ipmi_lstp_msg_header, len))
+#define LSTP_IPMI_UAPI_MSG_MAX \
+ (LSTP_IPMI_MSG_NUM ? sizeof(struct ipmi_lstp_msg) : sizeof(struct ipmi_ssif_msg))
+
+/* One per bind; the bind and each open fd hold @ref. */
+struct lstp_ipmi_ctx {
+ struct kref ref;
+ struct lstp_channel *ch; /* pinned for the life of @ctx */
+ struct miscdevice miscdev;
+ int ida_id; /* -1 until allocated */
+ bool running; /* exclusive-open flag */
+ spinlock_t lock; /* protects @running, @msg_count, @read_tag, @fifo */
+ struct mutex tx_lock; /* held across write()'s gone test and send; teardown waits on it */
+ wait_queue_head_t req_wq;
+ struct kfifo_rec_ptr_2 fifo; /* pending requests, oldest dropped to fit */
+ u8 msg_count; /* tag of the outstanding request; write() echoes it */
+ u8 read_tag; /* untagged ABI: tag of the request read() handed out last, 0 for none */
+ bool stopped; /* node torn down; see lstp_ipmi_gone() */
+};
+
+enum lstp_ipmi_cmd {
+ LSTP_IPMI_CMD_MESSAGE = 0x00,
+};
+
+/* @stopped is per bind and never cleared; see Documentation/driver-api/usb/lstp.rst. */
+static inline bool lstp_ipmi_gone(struct lstp_ipmi_ctx *ctx)
+{
+ return READ_ONCE(ctx->stopped) || lstp_channel_disconnected(ctx->ch);
+}
+
+static inline bool lstp_ipmi_readable(struct lstp_ipmi_ctx *ctx)
+{
+ return !kfifo_is_empty(&ctx->fifo) || lstp_ipmi_gone(ctx);
+}
+
+/* Partial-init safe: a failed lstp_ipmi_init() unwinds through it as well. */
+static void lstp_ipmi_ctx_release(struct kref *ref)
+{
+ struct lstp_ipmi_ctx *ctx = container_of(ref, struct lstp_ipmi_ctx, ref);
+ struct lstp_channel *ch = ctx->ch;
+
+ kfifo_free(&ctx->fifo);
+ if (ctx->ida_id >= 0)
+ ida_free(&lstp_ipmi_ida, ctx->ida_id);
+ kfree(ctx->miscdev.name);
+ mutex_destroy(&ctx->tx_lock);
+ kfree(ctx);
+ lstp_channel_put(ch);
+}
+
+/* Devres action for the bind's reference, booked ahead of the node's own. */
+static void lstp_ipmi_ctx_put(void *data)
+{
+ struct lstp_ipmi_ctx *ctx = data;
+
+ kref_put(&ctx->ref, lstp_ipmi_ctx_release);
+}
+
+static inline struct lstp_ipmi_ctx *to_ctx(struct file *file)
+{
+ return container_of(file->private_data, struct lstp_ipmi_ctx, miscdev);
+}
+
+static int lstp_ipmi_open(struct inode *inode, struct file *file)
+{
+ struct lstp_ipmi_ctx *ctx = to_ctx(file);
+ int ret = 0;
+
+ spin_lock_irq(&ctx->lock);
+ if (lstp_ipmi_gone(ctx))
+ ret = -ENODEV;
+ else if (ctx->running)
+ ret = -EBUSY;
+ else
+ ctx->running = true;
+ spin_unlock_irq(&ctx->lock);
+
+ if (ret)
+ return ret;
+
+ /*
+ * misc_mtx spans this ->open and misc_deregister(), which the bind's
+ * @ref outlasts, so @ctx is live here and this cannot race the final put.
+ */
+ kref_get(&ctx->ref);
+ return 0;
+}
+
+/* Read one queued request: the userspace header, then the payload. */
+static ssize_t lstp_ipmi_read(struct file *file, char __user *buf, size_t count, loff_t *ppos)
+{
+ struct lstp_ipmi_ctx *ctx = to_ctx(file);
+ struct ipmi_lstp_msg msg;
+ unsigned long flags;
+ unsigned int len = 0;
+ size_t ulen = 0;
+
+ /*
+ * copy_to_user() may sleep, so drain to @msg under @ctx->lock first.
+ * Loop: two readers on one fd can both wake, only one wins the record.
+ */
+ while (!len) {
+ /* Before the queue: a request queued when the bind went cannot be answered. */
+ if (lstp_ipmi_gone(ctx))
+ return -ENODEV;
+ if (kfifo_is_empty(&ctx->fifo)) {
+ if (file->f_flags & O_NONBLOCK)
+ return -EAGAIN;
+ int ret = wait_event_interruptible(ctx->req_wq, lstp_ipmi_readable(ctx));
+
+ if (ret)
+ return ret;
+ }
+
+ spin_lock_irqsave(&ctx->lock, flags);
+ /*
+ * Peek before consuming: kfifo_out() drops whatever of the
+ * record does not fit, so a short @count would retire a
+ * request the caller never saw. Leave it queued and let the
+ * caller come back with room for it.
+ */
+ len = kfifo_out_peek(&ctx->fifo, &msg, sizeof(msg));
+ ulen = len ? LSTP_IPMI_UAPI_HDR_LEN + msg.header.len : 0;
+ if (len && ulen <= count) {
+ kfifo_skip(&ctx->fifo);
+ WRITE_ONCE(ctx->read_tag, msg.header.msg_num);
+ }
+ spin_unlock_irqrestore(&ctx->lock, flags);
+
+ if (ulen > count)
+ return -EINVAL;
+ }
+
+ if (copy_to_user(buf, &msg.header, LSTP_IPMI_UAPI_HDR_LEN) ||
+ copy_to_user(buf + LSTP_IPMI_UAPI_HDR_LEN, msg.payload, msg.header.len))
+ return -EFAULT;
+ return ulen;
+}
+
+/*
+ * Forward a response. A write whose @msg_num misses the outstanding tag is
+ * dropped and reported as consumed, so userspace need not track the race
+ * against a request that arrived meanwhile. Without the tag in the ABI, a
+ * response answers the request read() handed out last.
+ */
+static ssize_t lstp_ipmi_write(struct file *file, const char __user *buf, size_t count,
+ loff_t *ppos)
+{
+ struct lstp_ipmi_ctx *ctx = to_ctx(file);
+ struct lstp_channel *ch = ctx->ch;
+ struct ipmi_lstp_msg msg;
+ ssize_t ret;
+
+ if (count < LSTP_IPMI_UAPI_HDR_LEN || count > LSTP_IPMI_UAPI_MSG_MAX)
+ return -EINVAL;
+
+ /* The untagged bound admits struct padding past the payload; drop it. */
+ if (copy_from_user(&msg.header, buf, LSTP_IPMI_UAPI_HDR_LEN) ||
+ copy_from_user(msg.payload, buf + LSTP_IPMI_UAPI_HDR_LEN,
+ min(count - LSTP_IPMI_UAPI_HDR_LEN, sizeof(msg.payload))))
+ return -EFAULT;
+
+ /* Held through the send; see lstp_ipmi_stop_and_deregister(). */
+ guard(mutex)(&ctx->tx_lock);
+ if (lstp_ipmi_gone(ctx))
+ return -ENODEV;
+
+ if (!LSTP_IPMI_MSG_NUM)
+ msg.header.msg_num = READ_ONCE(ctx->read_tag);
+
+ /*
+ * 0 tags no request; it is only @msg_count's value before the first.
+ * Stale tag read is fine; pairs with WRITE_ONCE in the IRQ producer.
+ */
+ if (!msg.header.msg_num || msg.header.msg_num != READ_ONCE(ctx->msg_count))
+ return count;
+
+ if (!msg.header.len || msg.header.len > IPMI_LSTP_PAYLOAD_MAX ||
+ count < LSTP_IPMI_UAPI_HDR_LEN + msg.header.len)
+ return -EINVAL;
+
+ ret = lstp_send(ch, LSTP_IPMI_CMD_MESSAGE, msg.payload, msg.header.len);
+ if (ret && ret != -ENODEV)
+ dev_err_ratelimited(ch->dev, "%s: ch_%d: Could not forward response (%pe)\n",
+ __func__, ch->ch_id, ERR_PTR(ret));
+
+ return (ret < 0) ? ret : count;
+}
+
+static int lstp_ipmi_release(struct inode *inode, struct file *file)
+{
+ struct lstp_ipmi_ctx *ctx = to_ctx(file);
+
+ spin_lock_irq(&ctx->lock);
+ ctx->running = false;
+ spin_unlock_irq(&ctx->lock);
+
+ kref_put(&ctx->ref, lstp_ipmi_ctx_release);
+
+ return 0;
+}
+
+static __poll_t lstp_ipmi_poll(struct file *file, poll_table *wait)
+{
+ struct lstp_ipmi_ctx *ctx = to_ctx(file);
+
+ poll_wait(file, &ctx->req_wq, wait);
+ if (lstp_ipmi_gone(ctx))
+ return EPOLLHUP;
+ if (!kfifo_is_empty(&ctx->fifo))
+ return EPOLLIN | EPOLLRDNORM;
+
+ return 0;
+}
+
+static const struct file_operations lstp_ipmi_fops = {
+ .owner = THIS_MODULE,
+ .open = lstp_ipmi_open,
+ .read = lstp_ipmi_read,
+ .write = lstp_ipmi_write,
+ .release = lstp_ipmi_release,
+ .poll = lstp_ipmi_poll,
+};
+
+/*
+ * @stopped before the wake: this runs ahead of the framework's fence action, so
+ * a reader woken without it would find nothing changed and sleep again. The
+ * empty @tx_lock section then waits out a write() that tested @stopped before
+ * it went up, so no response is sent once the unwind moves on; a send is
+ * bounded by the USB timeout.
+ */
+static void lstp_ipmi_stop_and_deregister(void *data)
+{
+ struct lstp_ipmi_ctx *ctx = data;
+
+ misc_deregister(&ctx->miscdev);
+ WRITE_ONCE(ctx->stopped, true);
+ wake_up_all(&ctx->req_wq);
+ scoped_guard(mutex, &ctx->tx_lock) {
+ }
+}
+
+/*
+ * ops->irq_packet. Requests are never NACKed, so drop-oldest is the whole
+ * flow-control policy.
+ */
+static void lstp_ipmi_irq_callback(struct lstp_channel *ch, struct lstp_packet *pkt)
+{
+ struct lstp_ipmi_ctx *ctx = ch->priv;
+ u16 rx_len = le16_to_cpu(pkt->hdr.length);
+ struct ipmi_lstp_msg msg;
+ unsigned long flags;
+ unsigned int dropped = 0;
+
+ if (rx_len < IPMI_MIN_MSG_LEN || rx_len > IPMI_LSTP_PAYLOAD_MAX) {
+ dev_warn_ratelimited(ch->dev, "%s: ch_%d: Invalid IPMI request length %u\n",
+ __func__, ch->ch_id, rx_len);
+ return;
+ }
+
+ unsigned int msg_size = sizeof(msg.header) + rx_len;
+
+ msg.header.len = rx_len;
+ memcpy(msg.payload, pkt->payload, rx_len);
+
+ spin_lock_irqsave(&ctx->lock, flags);
+ /*
+ * Skip 0 on wrap: it is the initial value, meaning nothing outstanding,
+ * so no request may carry it. Pairs with the READ_ONCE in write().
+ */
+ u8 tag = ctx->msg_count + 1;
+
+ msg.header.msg_num = tag ?: 1;
+ WRITE_ONCE(ctx->msg_count, msg.header.msg_num);
+ while (kfifo_avail(&ctx->fifo) < msg_size) {
+ kfifo_skip(&ctx->fifo);
+ dropped++;
+ }
+ kfifo_in(&ctx->fifo, &msg, msg_size);
+ spin_unlock_irqrestore(&ctx->lock, flags);
+
+ if (dropped)
+ dev_warn_ratelimited(ch->dev, "%s: ch_%d: FIFO full, dropped %u stale request(s)\n",
+ __func__, ch->ch_id, dropped);
+
+ wake_up_all(&ctx->req_wq);
+}
+
+/*
+ * Build this bind's context and its "ipmi-lstpN" node name. An fd held across
+ * the unbind keeps the context, and with it the number, until it closes; the
+ * next bind takes the lowest number free.
+ */
+static int lstp_ipmi_init(struct lstp_channel *ch, const struct lstp_channel_init_info *info)
+{
+ struct lstp_ipmi_ctx *ctx = kzalloc_obj(*ctx);
+
+ if (!ctx)
+ return -ENOMEM;
+
+ kref_init(&ctx->ref);
+ ctx->ida_id = -1;
+ ctx->ch = lstp_channel_get(ch);
+ spin_lock_init(&ctx->lock);
+ mutex_init(&ctx->tx_lock);
+ init_waitqueue_head(&ctx->req_wq);
+
+ int ret = kfifo_alloc(&ctx->fifo, BUFFER_SIZE, GFP_KERNEL);
+
+ if (ret)
+ goto err_put;
+
+ ret = ida_alloc(&lstp_ipmi_ida, GFP_KERNEL);
+ if (ret < 0)
+ goto err_put;
+ ctx->ida_id = ret;
+ ctx->miscdev.name = kasprintf(GFP_KERNEL, "ipmi-lstp%d", ctx->ida_id);
+ if (!ctx->miscdev.name) {
+ ret = -ENOMEM;
+ goto err_put;
+ }
+
+ if (fwnode_property_present(ch->fwnode, "label"))
+ dev_notice_once(ch->dev,
+ "%s: ch_%d: Firmware \"label\" no longer names the node, which is %s; use a udev symlink for a stable name\n",
+ __func__, ch->ch_id, ctx->miscdev.name);
+
+ ctx->miscdev.minor = MISC_DYNAMIC_MINOR;
+ ctx->miscdev.fops = &lstp_ipmi_fops;
+ ctx->miscdev.parent = ch->dev;
+
+ ch->priv = ctx;
+ dev_dbg(ch->dev, "%s: ch_%d: Initialized as %s\n", __func__, ch->ch_id, ch->display_name);
+ return 0;
+
+err_put:
+ kref_put(&ctx->ref, lstp_ipmi_ctx_release);
+ return ret;
+}
+
+static int lstp_ipmi_start(struct lstp_channel *ch, struct lstp_ipmi_ctx *ctx)
+{
+ int ret = misc_register(&ctx->miscdev);
+
+ if (ret) {
+ dev_err(ch->dev, "%s: ch_%d: Could not register miscdevice (%pe)\n", __func__,
+ ch->ch_id, ERR_PTR(ret));
+ return ret;
+ }
+
+ ret = devm_add_action_or_reset(ch->dev, lstp_ipmi_stop_and_deregister, ctx);
+ if (ret)
+ return ret;
+
+ dev_info(ch->dev, "%s: ch_%d: Started as %s\n", __func__, ch->ch_id, ch->display_name);
+ return 0;
+}
+
+static int lstp_ipmi_probe(struct auxiliary_device *auxdev, const struct auxiliary_device_id *id)
+{
+ struct lstp_channel *ch = lstp_channel_from_auxdev(auxdev);
+ const struct lstp_channel_init_info *info = dev_get_platdata(&auxdev->dev);
+
+ if (!info)
+ return -ENODEV;
+
+ int ret = lstp_ipmi_init(ch, info);
+
+ if (ret)
+ return ret;
+
+ /* Booked first, so it unwinds after the node's deregister action. */
+ ret = devm_add_action_or_reset(ch->dev, lstp_ipmi_ctx_put, ch->priv);
+ if (ret)
+ return ret;
+
+ return lstp_ipmi_start(ch, ch->priv);
+}
+
+static const struct lstp_ops lstp_ipmi_ops = {
+ .irq_packet = lstp_ipmi_irq_callback,
+};
+
+static const struct auxiliary_device_id lstp_ipmi_id_table[] = { { .name = "lstp.ipmi" }, {} };
+MODULE_DEVICE_TABLE(auxiliary, lstp_ipmi_id_table);
+
+static struct lstp_auxiliary_driver lstp_ipmi_drv = {
+ .adrv = {
+ .name = "ipmi",
+ .probe = lstp_ipmi_probe,
+ .id_table = lstp_ipmi_id_table,
+ },
+ .ops = &lstp_ipmi_ops,
+};
+module_lstp_auxiliary_driver(lstp_ipmi_drv);
+
+MODULE_IMPORT_NS("LSTP");
+MODULE_DESCRIPTION("LSTP BMC-side IPMI driver");
+MODULE_LICENSE("GPL");
--
2.43.0