[PATCH] serial: qcom-geni: Pre-map RX DMA buffer at probe to avoid sleep-in-atomic
From: Praveen Talari
Date: Wed Jul 01 2026 - 00:23:36 EST
geni_se_rx_dma_prep() calls dma_map_single() which can trigger IOMMU
page table allocations under GFP_KERNEL. This is unsafe when called
from qcom_geni_serial_start_rx_dma(), which runs in atomic context
producing a "sleeping function called from invalid context" splat:
__might_resched+0x15c/0x17c
__alloc_pages_noprof+0xe4/0x4c8
qcom_io_pgtable_alloc_page+0x100/0x250
__arm_lpae_map+0x2d0/0x870
geni_se_rx_dma_prep+0xd8/0x158
qcom_geni_serial_start_rx_dma+0x84/0x16c
qcom_geni_serial_startup+0x70/0x104
Fix this by mapping the RX DMA buffer once during probe, where sleeping
is allowed, and keeping it mapped for the lifetime of the device.
Replace the geni_se_rx_dma_prep() / geni_se_rx_dma_unprep() calls in
the runtime paths with dma_sync_single_for_device() before initiating a
transfer and dma_sync_single_for_cpu() on completion, using the persistent
mapping. The buffer is unmapped in probe's error path and in remove().
Signed-off-by: Praveen Talari <praveen.talari@xxxxxxxxxxxxxxxx>
---
drivers/tty/serial/qcom_geni_serial.c | 55 +++++++++++++++++++++--------------
1 file changed, 33 insertions(+), 22 deletions(-)
diff --git a/drivers/tty/serial/qcom_geni_serial.c b/drivers/tty/serial/qcom_geni_serial.c
index 7ead87b4eb65..3e460b0358eb 100644
--- a/drivers/tty/serial/qcom_geni_serial.c
+++ b/drivers/tty/serial/qcom_geni_serial.c
@@ -9,6 +9,7 @@
#include <linux/clk.h>
#include <linux/console.h>
+#include <linux/dma-mapping.h>
#include <linux/io.h>
#include <linux/iopoll.h>
#include <linux/irq.h>
@@ -863,37 +864,31 @@ static void qcom_geni_serial_stop_rx_dma(struct uart_port *uport)
uport->membase + SE_DMA_RX_IRQ_CLR);
}
- if (port->rx_dma_addr) {
- geni_se_rx_dma_unprep(&port->se, port->rx_dma_addr,
- DMA_RX_BUF_SIZE);
- port->rx_dma_addr = 0;
- }
}
static void qcom_geni_serial_start_rx_dma(struct uart_port *uport)
{
struct qcom_geni_serial_port *port = to_dev_port(uport);
- int ret;
if (qcom_geni_serial_secondary_active(uport))
qcom_geni_serial_stop_rx_dma(uport);
geni_se_setup_s_cmd(&port->se, UART_START_READ, UART_PARAM_RFR_OPEN);
- ret = geni_se_rx_dma_prep(&port->se, port->rx_buf,
- DMA_RX_BUF_SIZE,
- &port->rx_dma_addr);
- if (ret) {
- dev_err(uport->dev, "unable to start RX SE DMA: %d\n", ret);
- qcom_geni_serial_stop_rx_dma(uport);
+ if (!port->rx_dma_addr) {
+ dev_err(uport->dev, "RX DMA buffer not mapped\n");
+ return;
}
+
+ dma_sync_single_for_device(uport->dev->parent, port->rx_dma_addr,
+ DMA_RX_BUF_SIZE, DMA_FROM_DEVICE);
+ geni_se_rx_init_dma(&port->se, port->rx_dma_addr, DMA_RX_BUF_SIZE);
}
static void qcom_geni_serial_handle_rx_dma(struct uart_port *uport, bool drop)
{
struct qcom_geni_serial_port *port = to_dev_port(uport);
u32 rx_in;
- int ret;
if (!qcom_geni_serial_secondary_active(uport))
return;
@@ -901,8 +896,8 @@ static void qcom_geni_serial_handle_rx_dma(struct uart_port *uport, bool drop)
if (!port->rx_dma_addr)
return;
- geni_se_rx_dma_unprep(&port->se, port->rx_dma_addr, DMA_RX_BUF_SIZE);
- port->rx_dma_addr = 0;
+ dma_sync_single_for_cpu(uport->dev->parent, port->rx_dma_addr,
+ DMA_RX_BUF_SIZE, DMA_FROM_DEVICE);
rx_in = readl(uport->membase + SE_DMA_RX_LEN_IN);
if (!rx_in)
@@ -910,13 +905,9 @@ static void qcom_geni_serial_handle_rx_dma(struct uart_port *uport, bool drop)
else if (!drop)
handle_rx_uart(uport, rx_in);
- ret = geni_se_rx_dma_prep(&port->se, port->rx_buf,
- DMA_RX_BUF_SIZE,
- &port->rx_dma_addr);
- if (ret) {
- dev_err(uport->dev, "unable to start RX SE DMA: %d\n", ret);
- qcom_geni_serial_stop_rx_dma(uport);
- }
+ dma_sync_single_for_device(uport->dev->parent, port->rx_dma_addr,
+ DMA_RX_BUF_SIZE, DMA_FROM_DEVICE);
+ geni_se_rx_init_dma(&port->se, port->rx_dma_addr, DMA_RX_BUF_SIZE);
}
static void qcom_geni_serial_start_rx(struct uart_port *uport)
@@ -1864,6 +1855,14 @@ static int qcom_geni_serial_probe(struct platform_device *pdev)
ret = -ENOMEM;
goto error;
}
+
+ port->rx_dma_addr = dma_map_single(pdev->dev.parent, port->rx_buf,
+ DMA_RX_BUF_SIZE, DMA_FROM_DEVICE);
+ if (dma_mapping_error(pdev->dev.parent, port->rx_dma_addr)) {
+ ret = -EIO;
+ dev_err(&pdev->dev, "Failed to map RX DMA buffer: %d\n", ret);
+ goto error;
+ }
}
port->name = devm_kasprintf(uport->dev, GFP_KERNEL,
@@ -1928,6 +1927,11 @@ static int qcom_geni_serial_probe(struct platform_device *pdev)
return 0;
error:
+ if (port->rx_dma_addr) {
+ dma_unmap_single(pdev->dev.parent, port->rx_dma_addr,
+ DMA_RX_BUF_SIZE, DMA_FROM_DEVICE);
+ port->rx_dma_addr = 0;
+ }
dev_pm_domain_detach_list(port->pd_list);
return ret;
}
@@ -1942,6 +1946,13 @@ static void qcom_geni_serial_remove(struct platform_device *pdev)
device_init_wakeup(&pdev->dev, false);
ida_free(&port_ida, uport->line);
uart_remove_one_port(drv, &port->uport);
+
+ if (port->rx_dma_addr) {
+ dma_unmap_single(pdev->dev.parent, port->rx_dma_addr,
+ DMA_RX_BUF_SIZE, DMA_FROM_DEVICE);
+ port->rx_dma_addr = 0;
+ }
+
dev_pm_domain_detach_list(port->pd_list);
}
---
base-commit: 4fa3f5fabb30bf00d7475d5a33459ea83d639bf9
change-id: 20260617-fix-sleep-in-atomic-context-during-rx-dma-setup-c056bdf08adb
Best regards,
--
Praveen Talari <praveen.talari@xxxxxxxxxxxxxxxx>