kernel: mhi: disable doorbell burst mode for RM500Q-AE

Add a pending patch that gives the RM500Q-AE its own MHI channel table
with doorbell burst mode disabled on its two hardware data channels.

With burst mode on, the downlink stalls under sustained multi-stream
load when the wwan0 receive work runs on the same CPU as the modem's
interrupt, which is where the default packet steering puts it. Uplink
keeps working, the MBIM control channel later times out, and only a
reboot recovers it. Ringing the channel doorbell by hand restores the
link at once, which points at a lost doorbell-mode handshake.

Tested on a Xunison Exigo Hub D50 5G: without the patch a single
25-second 16-stream download stalled the downlink; with it, about 200
seconds of multi-stream downloads with CPU0 at up to 100% ran with no
stall and no MHI errors.

Link: https://patch.msgid.link/20261013-rm500q-db-burst-v1-1-bb850d54e2c4@gmail.com
Signed-off-by: Steve Meek <stevemeek82@gmail.com>
Link: https://github.com/openwrt/openwrt/pull/25890
Signed-off-by: Robert Marko <robimarko@gmail.com>
This commit is contained in:
Steve Meek authored and Robert Marko committed 2026-10-16 09:52:28 +02:00
1 parent 2961f34115
commit 73b92d95d1
1 file changed
+104
@@ -0,0 +1,104 @@
From 12cbac5f694319d48287a44354a5a090b6d85ed0 Mon Sep 17 00:00:00 2001
From: Steve Meek <stevemeek82@gmail.com>
Date: Tue, 13 Oct 2026 13:04:56 +0100
Subject: [PATCH] bus: mhi: host: pci_generic: Disable doorbell burst mode for
Quectel RM500Q-AE
With burst mode enabled on the IP_HW0_MBIM hardware channels, the
RM500Q-AE downlink stops under sustained multi-stream load. The MHI
state stays M0 and uplink keeps working, but the downlink event ring
stops interrupting, and after a while the MBIM control channel times
out as well. Only a reboot recovers it.
At the stall, the last doorbell the host rang on channel 101 covered a
single buffer. The host then refilled the ring without ringing again,
as db_mode had been cleared, and the device never signalled doorbell
mode again, so it never saw the new buffers. Writing the channel 101
doorbell by hand with the current ring write pointer restores the
downlink immediately.
It is load dependent: it reproduces within a 25 second 16-stream TCP
download when the wwan0 receive work runs on the same CPU as the
modem's MSI, and was not seen with that work moved to another CPU.
PCIe runtime PM is not involved, as it reproduces with PCIe port PM
disabled.
Give the RM500Q-AE its own channel table with burst mode disabled on
the two hardware channels, so the host rings the doorbell on every
transfer. Other modems sharing the em1xx table are left unchanged.
Tested on a Xunison Exigo Hub D50 5G (IPQ5018, RM500Q-AE firmware
RM500QAEAAR13A03M4G, 6.18 with this driver): with the receive work on
the modem's CPU at up to 100% load, about 200 seconds of multi-stream
downloads at up to 135 Mbps, no stall and no MHI errors, where the
unpatched driver stalled within the first 25 second run.
Fixes: f6985f512f82 ("bus: mhi: host: pci_generic: Add Quectel RM500Q-AE support")
Assisted-by: LLM
Signed-off-by: Steve Meek <stevemeek82@gmail.com>
Link: https://patch.msgid.link/20261013-rm500q-db-burst-v1-1-bb850d54e2c4@gmail.com
---
drivers/bus/mhi/host/pci_generic.c | 49 +++++++++++++++++++++++++++++-
1 file changed, 48 insertions(+), 1 deletion(-)
--- a/drivers/bus/mhi/host/pci_generic.c
+++ b/drivers/bus/mhi/host/pci_generic.c
@@ -470,10 +470,57 @@ static const struct mhi_pci_dev_info mhi
.sideband_wake = true,
};
+/*
+ * The RM500Q-AE stalls its downlink when the host refills the hardware
+ * channel in burst mode: the last doorbell covers one buffer, the host
+ * then queues the rest without ringing, and the device never signals
+ * doorbell mode again. Ring the doorbell on every transfer instead.
+ */
+#define MHI_CHANNEL_CONFIG_HW_NOBRST(ch_num, ch_name, el_count, ev_ring, d) \
+ { \
+ .num = ch_num, \
+ .name = ch_name, \
+ .num_elements = el_count, \
+ .event_ring = ev_ring, \
+ .dir = d, \
+ .ee_mask = BIT(MHI_EE_AMSS), \
+ .pollcfg = 0, \
+ .doorbell = MHI_DB_BRST_DISABLE, \
+ .lpm_notify = false, \
+ .offload_channel = false, \
+ .doorbell_mode_switch = false, \
+ }
+
+static const struct mhi_channel_config mhi_quectel_rm500q_channels[] = {
+ MHI_CHANNEL_CONFIG_UL(0, "NMEA", 32, 0),
+ MHI_CHANNEL_CONFIG_DL(1, "NMEA", 32, 0),
+ MHI_CHANNEL_CONFIG_UL_SBL(2, "SAHARA", 32, 0),
+ MHI_CHANNEL_CONFIG_DL_SBL(3, "SAHARA", 32, 0),
+ MHI_CHANNEL_CONFIG_UL(4, "DIAG", 32, 1),
+ MHI_CHANNEL_CONFIG_DL(5, "DIAG", 32, 1),
+ MHI_CHANNEL_CONFIG_UL(12, "MBIM", 32, 0),
+ MHI_CHANNEL_CONFIG_DL(13, "MBIM", 32, 0),
+ MHI_CHANNEL_CONFIG_UL(32, "DUN", 32, 0),
+ MHI_CHANNEL_CONFIG_DL(33, "DUN", 32, 0),
+ MHI_CHANNEL_CONFIG_UL_FP(34, "FIREHOSE", 32, 0),
+ MHI_CHANNEL_CONFIG_DL_FP(35, "FIREHOSE", 32, 0),
+ MHI_CHANNEL_CONFIG_HW_NOBRST(100, "IP_HW0_MBIM", 128, 2, DMA_TO_DEVICE),
+ MHI_CHANNEL_CONFIG_HW_NOBRST(101, "IP_HW0_MBIM", 128, 3, DMA_FROM_DEVICE),
+};
+
+static const struct mhi_controller_config modem_quectel_rm500q_config = {
+ .max_channels = 128,
+ .timeout_ms = 20000,
+ .num_channels = ARRAY_SIZE(mhi_quectel_rm500q_channels),
+ .ch_cfg = mhi_quectel_rm500q_channels,
+ .num_events = ARRAY_SIZE(mhi_quectel_em1xx_events),
+ .event_cfg = mhi_quectel_em1xx_events,
+};
+
static const struct mhi_pci_dev_info mhi_quectel_rm500q_info = {
.name = "quectel-rm500q",
.edl = "qcom/sdx55m/edl.mbn",
- .config = &modem_quectel_em1xx_config,
+ .config = &modem_quectel_rm500q_config,
.bar_num = MHI_PCI_DEFAULT_BAR_NUM,
.dma_data_width = 32,
.mru_default = 32768,