serial: qcom-geni: Pre-map RX DMA buffer at probe to avoid sleep-in-atomic

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@oss.qualcomm.com>
Link: https://patch.msgid.link/20260701-fix-sleep-in-atomic-context-during-rx-dma-setup-v1-1-95c208380c65@oss.qualcomm.com
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
This commit is contained in:
Praveen Talari
2026-07-01 09:53:10 +05:30
committed by Greg Kroah-Hartman
parent 8dfea56f35
commit bafe277d6c

View File

@@ -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);
}