mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
synced 2026-08-31 12:52:29 -04:00
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:
committed by
Greg Kroah-Hartman
parent
8dfea56f35
commit
bafe277d6c
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user