vc4_dsi_host_transfer() fills the command FIFOs and the TXPKT registers and waits on a single completion without any locking. Two callers sending at the same time, for example a panel driver and a backlight update, can overwrite each other's packets. Nothing keeps a transfer from running while the bridge callbacks reprogram the block either, for example while pre_enable sets up the PHY timings.
Add a mutex and hold it for the whole host transfer and in the bridge pre_enable, enable, disable and post_disable callbacks. None of these callbacks calls into the DSI device, so the lock is never taken recursively. Signed-off-by: Henry Zhang <[email protected]> --- drivers/gpu/drm/vc4/vc4_dsi.c | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/drivers/gpu/drm/vc4/vc4_dsi.c b/drivers/gpu/drm/vc4/vc4_dsi.c index face44a23..ea3a6d06b 100644 --- a/drivers/gpu/drm/vc4/vc4_dsi.c +++ b/drivers/gpu/drm/vc4/vc4_dsi.c @@ -25,6 +25,7 @@ #include <linux/dma-mapping.h> #include <linux/dmaengine.h> #include <linux/io.h> +#include <linux/mutex.h> #include <linux/of.h> #include <linux/of_address.h> #include <linux/platform_device.h> @@ -595,6 +596,11 @@ struct vc4_dsi { */ struct clk *pixel_clock; + /* + * Serializes host transfers against each other and against the + * bridge enable/disable paths, which reprogram the block. + */ + struct mutex transfer_lock; struct completion xfer_completion; int xfer_result; @@ -805,6 +811,8 @@ static void vc4_dsi_bridge_disable(struct drm_bridge *bridge, struct vc4_dsi *dsi = bridge_to_vc4_dsi(bridge); u32 disp0_ctrl; + guard(mutex)(&dsi->transfer_lock); + disp0_ctrl = DSI_PORT_READ(DISP0_CTRL); disp0_ctrl &= ~DSI_DISP0_ENABLE; DSI_PORT_WRITE(DISP0_CTRL, disp0_ctrl); @@ -816,6 +824,8 @@ static void vc4_dsi_bridge_post_disable(struct drm_bridge *bridge, struct vc4_dsi *dsi = bridge_to_vc4_dsi(bridge); struct device *dev = &dsi->pdev->dev; + guard(mutex)(&dsi->transfer_lock); + clk_disable_unprepare(dsi->pll_phy_clock); clk_disable_unprepare(dsi->escape_clock); clk_disable_unprepare(dsi->pixel_clock); @@ -891,6 +901,8 @@ static void vc4_dsi_bridge_pre_enable(struct drm_bridge *bridge, unsigned long phy_clock; int ret; + guard(mutex)(&dsi->transfer_lock); + ret = pm_runtime_resume_and_get(dev); if (ret) { drm_err(bridge->dev, "Failed to runtime PM enable on DSI%d\n", dsi->variant->port); @@ -1148,6 +1160,8 @@ static void vc4_dsi_bridge_enable(struct drm_bridge *bridge, bool debug_dump_regs = false; u32 disp0_ctrl; + guard(mutex)(&dsi->transfer_lock); + disp0_ctrl = DSI_PORT_READ(DISP0_CTRL); disp0_ctrl |= DSI_DISP0_ENABLE; DSI_PORT_WRITE(DISP0_CTRL, disp0_ctrl); @@ -1181,6 +1195,8 @@ static ssize_t vc4_dsi_host_transfer(struct mipi_dsi_host *host, bool is_long = mipi_dsi_packet_format_is_long(msg->type); u32 cmd_fifo_len = 0, pix_fifo_len = 0; + guard(mutex)(&dsi->transfer_lock); + mipi_dsi_create_packet(&packet, msg); pkth |= VC4_SET_FIELD(packet.header[0], DSI_TXPKT1H_BC_DT); @@ -1807,6 +1823,7 @@ static int vc4_dsi_dev_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; struct vc4_dsi *dsi; + int ret; dsi = devm_drm_bridge_alloc(&pdev->dev, struct vc4_dsi, bridge, &vc4_dsi_bridge_funcs); if (IS_ERR(dsi)) @@ -1814,6 +1831,9 @@ static int vc4_dsi_dev_probe(struct platform_device *pdev) dev_set_drvdata(dev, dsi); dsi->pdev = pdev; + ret = devm_mutex_init(dev, &dsi->transfer_lock); + if (ret) + return ret; #ifdef CONFIG_OF dsi->bridge.of_node = dev->of_node; #endif -- 2.47.3
