All of lore.kernel.org
 help / color / mirror / Atom feed
* [RFC PATCH 0/3] drm: Add DRM driver for GlandaGPU (VHDL soft-IP GPU)
@ 2026-07-14 10:11 Leander Kieweg
  2026-07-14 10:11 ` [RFC PATCH 1/3] dt-bindings: display: Add GlandaGPU binding Leander Kieweg
                   ` (2 more replies)
  0 siblings, 3 replies; 8+ messages in thread
From: Leander Kieweg @ 2026-07-14 10:11 UTC (permalink / raw)
  To: dri-devel, devicetree
  Cc: airlied, simona, maarten.lankhorst, mripard, tzimmermann, robh,
	krzk+dt, conor+dt, Leander Kieweg

Hi everyone,

This series adds a DRM driver for GlandaGPU, a custom open-source 2D
graphics core I designed in VHDL. It currently runs on a Cyclone V
SoC (Terasic DE10-Standard), with VGA output at 640x480@60Hz. I also
built a QEMU fork that emulates the same MMIO/VRAM interface as a
digital twin, so I can develop and test without needing the FPGA
board.

  Hardware/VHDL:    https://github.com/stiangglanda/GlandaGPU
  QEMU fork:        https://github.com/stiangglanda/qemu-glandagpu
  Userspace tests:  https://github.com/stiangglanda/GlandaGPU-userspace-tests

I'm sending this as an RFC because I'd like feedback on the
following points before cleaning up the series further:

1) Mainline viability of custom FPGA hardware

   GlandaGPU isn't a commercial chip, it's an open-source soft GPU
   core. The DE10-Standard itself is a regular purchasable dev
   board, and the RTL is public, so anyone can reproduce the exact
   hardware. Testing doesn't require the FPGA either, since the
   QEMU twin models the same interface. I know this is unusual
   compared to typical mainline DRM hardware. I'd like to know
   whether this is workable, or whether it's a dealbreaker for
   mainline.

2) UAPI / ioctls / userspace

   The driver currently exposes three fixed ioctls (CLEAR,
   DRAW_RECT, DRAW_LINE), mapping directly onto the current
   hardware command set. I have two related questions here:

   a) I plan to keep developing the hardware further, which will
      likely mean more ioctls over time (for example, polygon/3D
      rendering is one direction I'm considering). Is it acceptable
      to keep adding a new, separate ioctl for each drawing
      primitive like this, or should I move to a generic
      command-buffer submission model instead, similar to
      DRM_IOCTL_VIRTGPU_EXECBUFFER in virtio_gpu, before this is
      treated as stable?

   b) If I do end up with an ioctl-based acceleration UAPI, is
      writing a Mesa/Gallium3D driver the expected way to make it
      usable from userspace, or is there a lighter-weight option
      that makes more sense for a project this size?

3) x86 QEMU platform test device

   To let reviewers try the driver against the QEMU twin without
   cross-compiling an ARM kernel and rootfs, I registered a
   platform device at a fixed address on x86. I'm aware this
   doesn't belong in the driver itself, so I isolated it into patch
   3/3 and marked it "NOT FOR MERGE". Let me know if you'd rather
   see it dropped entirely, relying only on documentation of the
   QEMU-on-ARM testing path instead.

4) Pixel format conversion

   glanda_pipe_update() currently does a per-pixel software
   conversion from XRGB8888 into the hardware's native packed
   format on every flip. I'm aware this is a known bottleneck. I'm
   planning to extend the VHDL to accept XRGB8888 natively so I can
   drop this conversion entirely. I'm flagging it here as a known
   limitation rather than blocking on it, since it's a hardware-side
   change and doesn't affect the UAPI.

5) drm_simple_display_pipe vs. manual plane/CRTC/encoder

   Since the driver only has a single plane, CRTC, and encoder, I
   tried converting it to use drm_simple_display_pipe instead of the
   manual setup. It compiled cleanly, but my userspace tests didn't
   behave the way I expected. I haven't figured out why yet, so I
   kept the manual setup for this RFC. I'm open to revisiting this
   if that's the preferred direction.

Testing Status:

The driver has been tested and verified on both the QEMU fork (x86)
and physical FPGA hardware (ARM) using:
  - A custom static userspace test: https://github.com/stiangglanda/GlandaGPU-userspace-tests
  - `modetest -M glandagpu -s 36:640x480 -v` (which successfully
    displays the test pattern)

Thanks for any feedback,
Leander Kieweg

Leander Kieweg (3):
  dt-bindings: display: Add GlandaGPU binding
  drm/glanda: Add initial DRM driver for GlandaGPU
  NOT FOR MERGE: drm/glanda: Add x86 platform test device

 .../bindings/display/glanda,gpu.yaml          |  49 ++
 .../devicetree/bindings/vendor-prefixes.yaml  |   2 +
 MAINTAINERS                                   |   7 +
 drivers/gpu/drm/tiny/Kconfig                  |  25 +
 drivers/gpu/drm/tiny/Makefile                 |   1 +
 drivers/gpu/drm/tiny/glandagpu.c              | 808 ++++++++++++++++++
 include/uapi/drm/glanda_drm.h                 |  40 +
 7 files changed, 932 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/display/glanda,gpu.yaml
 create mode 100644 drivers/gpu/drm/tiny/glandagpu.c
 create mode 100644 include/uapi/drm/glanda_drm.h

-- 
2.43.0


^ permalink raw reply	[flat|nested] 8+ messages in thread

* [RFC PATCH 1/3] dt-bindings: display: Add GlandaGPU binding
  2026-07-14 10:11 [RFC PATCH 0/3] drm: Add DRM driver for GlandaGPU (VHDL soft-IP GPU) Leander Kieweg
@ 2026-07-14 10:11 ` Leander Kieweg
  2026-07-14 10:23   ` sashiko-bot
  2026-07-14 10:11 ` [RFC PATCH 2/3] drm/glanda: Add initial DRM driver for GlandaGPU Leander Kieweg
  2026-07-14 10:11 ` [RFC PATCH 3/3] NOT FOR MERGE: drm/glanda: Add x86 platform test device Leander Kieweg
  2 siblings, 1 reply; 8+ messages in thread
From: Leander Kieweg @ 2026-07-14 10:11 UTC (permalink / raw)
  To: dri-devel, devicetree
  Cc: airlied, simona, maarten.lankhorst, mripard, tzimmermann, robh,
	krzk+dt, conor+dt, Leander Kieweg

Add Device Tree binding documentation for GlandaGPU, a custom
FPGA-based 2D display controller.

Signed-off-by: Leander Kieweg <kieweg.leander@gmail.com>
---
 .../bindings/display/glanda,gpu.yaml          | 49 +++++++++++++++++++
 .../devicetree/bindings/vendor-prefixes.yaml  |  2 +
 2 files changed, 51 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/display/glanda,gpu.yaml

diff --git a/Documentation/devicetree/bindings/display/glanda,gpu.yaml b/Documentation/devicetree/bindings/display/glanda,gpu.yaml
new file mode 100644
index 000000000..40304e773
--- /dev/null
+++ b/Documentation/devicetree/bindings/display/glanda,gpu.yaml
@@ -0,0 +1,49 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/display/glanda,gpu.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: GlandaGPU 2D Hardware Accelerated Display Controller
+
+maintainers:
+  - Leander Kieweg <kieweg.leander@gmail.com>
+
+description: |
+  GlandaGPU is a custom FPGA soft-IP core providing a simple
+  2D hardware-accelerated drawing engine (clear/rect/line) with a
+  VGA-compatible display output. The register window covers a
+  combined VRAM + MMIO region, with MMIO registers at a fixed
+  offset within it.
+
+properties:
+  compatible:
+    const: glanda,gpu-1.0
+
+  reg:
+    maxItems: 1
+    description:
+      Combined VRAM + MMIO register window (VRAM at offset 0,
+      MMIO registers at offset 0x00200000 within this range).
+
+  interrupts:
+    maxItems: 1
+
+  clocks:
+    maxItems: 1
+    description: Bus and pixel clock provided to the FPGA IP.
+
+required:
+  - compatible
+  - reg
+
+additionalProperties: false
+
+examples:
+  - |
+    gpu@c0000000 {
+        compatible = "glanda,gpu-1.0";
+        reg = <0xc0000000 0x1000000>;
+        interrupts = <0 43 4>;
+        clocks = <&osc1>;
+    };
\ No newline at end of file
diff --git a/Documentation/devicetree/bindings/vendor-prefixes.yaml b/Documentation/devicetree/bindings/vendor-prefixes.yaml
index b1af9deac..b8b7a5401 100644
--- a/Documentation/devicetree/bindings/vendor-prefixes.yaml
+++ b/Documentation/devicetree/bindings/vendor-prefixes.yaml
@@ -666,6 +666,8 @@ patternProperties:
     description: Giantplus Technology Co., Ltd.
   "^gira,.*":
     description: Gira Giersiepen GmbH & Co. KG
+  "^glanda,.*":
+    description: GlandaGPU
   "^glinet,.*":
     description: GL Intelligence, Inc.
   "^globalscale,.*":
-- 
2.43.0


^ permalink raw reply related	[flat|nested] 8+ messages in thread

* [RFC PATCH 2/3] drm/glanda: Add initial DRM driver for GlandaGPU
  2026-07-14 10:11 [RFC PATCH 0/3] drm: Add DRM driver for GlandaGPU (VHDL soft-IP GPU) Leander Kieweg
  2026-07-14 10:11 ` [RFC PATCH 1/3] dt-bindings: display: Add GlandaGPU binding Leander Kieweg
@ 2026-07-14 10:11 ` Leander Kieweg
  2026-07-14 10:33   ` sashiko-bot
  2026-07-14 12:14   ` Uwe Kleine-König
  2026-07-14 10:11 ` [RFC PATCH 3/3] NOT FOR MERGE: drm/glanda: Add x86 platform test device Leander Kieweg
  2 siblings, 2 replies; 8+ messages in thread
From: Leander Kieweg @ 2026-07-14 10:11 UTC (permalink / raw)
  To: dri-devel, devicetree
  Cc: airlied, simona, maarten.lankhorst, mripard, tzimmermann, robh,
	krzk+dt, conor+dt, Leander Kieweg

Introduce the core DRM/KMS driver for GlandaGPU. This driver
supports basic modesetting, atomic updates, and custom 2D hardware
acceleration IOCTLs.

Signed-off-by: Leander Kieweg <kieweg.leander@gmail.com>
---
 MAINTAINERS                      |   7 +
 drivers/gpu/drm/tiny/Kconfig     |  11 +
 drivers/gpu/drm/tiny/Makefile    |   1 +
 drivers/gpu/drm/tiny/glandagpu.c | 769 +++++++++++++++++++++++++++++++
 include/uapi/drm/glanda_drm.h    |  40 ++
 5 files changed, 828 insertions(+)
 create mode 100644 drivers/gpu/drm/tiny/glandagpu.c
 create mode 100644 include/uapi/drm/glanda_drm.h

diff --git a/MAINTAINERS b/MAINTAINERS
index 6dea93a41..49bbb5c43 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -8076,6 +8076,13 @@ T:	git https://gitlab.freedesktop.org/drm/misc/kernel.git
 F:	drivers/gpu/drm/gud/
 F:	include/drm/gud.h
 
+DRM DRIVER FOR GLANDAGPU
+M:	Leander Kieweg <kieweg.leander@gmail.com>
+S:	Maintained
+F:	Documentation/devicetree/bindings/display/glanda,gpu.yaml
+F:	drivers/gpu/drm/tiny/glandagpu.c
+F:	include/uapi/drm/glanda_drm.h
+
 DRM DRIVER FOR GRAIN MEDIA GM12U320 PROJECTORS
 M:	Hans de Goede <hansg@kernel.org>
 S:	Maintained
diff --git a/drivers/gpu/drm/tiny/Kconfig b/drivers/gpu/drm/tiny/Kconfig
index f0e72d4b6..7a15bf95a 100644
--- a/drivers/gpu/drm/tiny/Kconfig
+++ b/drivers/gpu/drm/tiny/Kconfig
@@ -56,6 +56,17 @@ config DRM_CIRRUS_QEMU
 	   - qxl (DRM_QXL, qemu -vga qxl, works best with spice)
 	   - virtio (DRM_VIRTIO_GPU), qemu -vga virtio)
 
+config DRM_GLANDA
+    tristate "GlandaGPU DRM driver"
+    depends on DRM
+    select DRM_KMS_HELPER
+    select DRM_GEM_SHMEM_HELPER
+	help
+	  DRM/KMS driver for the GlandaGPU hardware-accelerated 2D
+	  display controller (FPGA soft IP). This driver supports
+	  basic modesetting, dumb buffers, and simple 2D drawing
+	  acceleration via custom hardware IOCTLs.
+
 config DRM_GM12U320
 	tristate "GM12U320 driver for USB projectors"
 	depends on DRM && USB && MMU
diff --git a/drivers/gpu/drm/tiny/Makefile b/drivers/gpu/drm/tiny/Makefile
index 48d30bf61..b4fa1554a 100644
--- a/drivers/gpu/drm/tiny/Makefile
+++ b/drivers/gpu/drm/tiny/Makefile
@@ -4,6 +4,7 @@ obj-$(CONFIG_DRM_APPLETBDRM)		+= appletbdrm.o
 obj-$(CONFIG_DRM_ARCPGU)		+= arcpgu.o
 obj-$(CONFIG_DRM_BOCHS)			+= bochs.o
 obj-$(CONFIG_DRM_CIRRUS_QEMU)		+= cirrus-qemu.o
+obj-$(CONFIG_DRM_GLANDA) 			+= glandagpu.o
 obj-$(CONFIG_DRM_GM12U320)		+= gm12u320.o
 obj-$(CONFIG_DRM_PANEL_MIPI_DBI)	+= panel-mipi-dbi.o
 obj-$(CONFIG_DRM_PIXPAPER)              += pixpaper.o
diff --git a/drivers/gpu/drm/tiny/glandagpu.c b/drivers/gpu/drm/tiny/glandagpu.c
new file mode 100644
index 000000000..8f87ae096
--- /dev/null
+++ b/drivers/gpu/drm/tiny/glandagpu.c
@@ -0,0 +1,769 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <linux/platform_device.h>
+#include <linux/io.h>
+#include <linux/delay.h>	/* udelay (polling) */
+#include <linux/mod_devicetable.h>	/* Device Tree parsing */
+#include <linux/of.h>
+#include <linux/slab.h>		/* GFP_KERNEL */
+#include <linux/fs.h>
+#include <linux/cdev.h>
+#include <linux/uaccess.h>
+#include <linux/interrupt.h>
+#include <linux/wait.h>
+#include <linux/mm.h>
+#include <linux/mutex.h>
+#include <linux/iosys-map.h>
+
+#include <drm/drm_drv.h>
+#include <drm/drm_device.h>
+#include <drm/drm_file.h>
+#include <drm/drm_gem.h>
+#include <drm/drm_ioctl.h>
+#include <drm/drm_gem_shmem_helper.h>
+#include <drm/drm_framebuffer.h>
+#include <drm/drm_vblank.h>
+
+#include <drm/drm_connector.h>
+#include <drm/drm_encoder.h>
+#include <drm/drm_modeset_helper.h>
+#include <drm/drm_probe_helper.h>
+#include <drm/drm_simple_kms_helper.h>
+#include <drm/drm_gem_framebuffer_helper.h>
+#include <drm/drm_crtc.h>
+#include <drm/drm_crtc_helper.h>
+#include <drm/drm_modeset_helper_vtables.h>
+#include <drm/drm_plane.h>
+#include <drm/drm_fourcc.h>
+#include <drm/drm_atomic.h>
+#include <drm/drm_atomic_helper.h>
+#include <drm/drm_damage_helper.h>
+#include <drm/drm_print.h>
+
+#include <uapi/drm/glanda_drm.h>
+
+/* Hardware Constants */
+#define GLANDA_WIDTH      640
+#define GLANDA_HEIGHT     480
+#define GLANDA_VRAM_SIZE  (GLANDA_WIDTH * GLANDA_HEIGHT * 4)
+#define GLANDA_MMIO_SIZE  32
+#define GLANDA_MMIO_OFFSET 0x00200000
+
+/* Base addresses used by the x86 test device. */
+#define BRIDGE_BASE       0xC0000000
+#define GLANDA_VRAM_BASE  (BRIDGE_BASE + 0x00000000)
+#define GLANDA_MMIO_BASE  (BRIDGE_BASE + GLANDA_MMIO_OFFSET)
+#define GLANDA_BASE_SIZE  (BRIDGE_BASE + 0x01000000 - 1)
+
+/* Register Offsets */
+#define REG_STATUS  0x00
+#define REG_CTRL    0x04
+#define REG_COORD0  0x08
+#define REG_COORD1  0x0C
+#define REG_COLOR   0x10
+#define REG_ISR     0x14
+#define REG_IER     0x18
+
+/* Bit Masks */
+#define INT_DONE    BIT(0)
+#define INT_VSYNC   BIT(1)
+
+#define STATUS_BUSY BIT(0)
+#define CMD_CLEAR   (0x1)
+#define CMD_RECT    (0x2)
+#define CMD_LINE    (0x3)
+#define CTRL_START  BIT(4)
+
+struct glanda_device {
+	struct drm_device drm;
+
+	/* hw */
+	void __iomem *mmio_base;
+	void __iomem *vram_base;
+	struct device *dev;
+	phys_addr_t vram_phys;
+
+	int irq;
+	wait_queue_head_t cmd_wq;
+	bool cmd_done;
+
+	struct mutex lock; /* for every ineration with the hardware */
+
+	/* drm */
+	struct drm_plane primary_plane;
+	struct drm_crtc crtc;
+	struct drm_encoder encoder;
+	struct drm_connector connector;
+};
+
+#define to_glanda(dev) container_of(dev, struct glanda_device, drm)
+
+static const u32 glanda_plane_formats[] = {
+	DRM_FORMAT_XRGB8888,
+};
+
+static int glanda_wait_idle(struct glanda_device *gdev)
+{
+	int ret;
+	unsigned int status;
+
+	status = readl(gdev->mmio_base + REG_STATUS);
+	if (!(status & STATUS_BUSY))
+		return 0;
+
+	if (gdev->irq < 0) {
+		int timeout = 10000;
+
+		do {
+			status = readl(gdev->mmio_base + REG_STATUS);
+			if (!(status & STATUS_BUSY))
+				return 0;
+			udelay(1);
+		} while (--timeout > 0);
+
+		dev_err(gdev->dev, "GlandaGPU: polling wait_idle timeout\n");
+		return -ETIMEDOUT;
+	}
+
+	gdev->cmd_done = false;
+
+	ret = wait_event_interruptible_timeout(gdev->cmd_wq, gdev->cmd_done ||
+					       !(readl(gdev->mmio_base + REG_STATUS) & STATUS_BUSY),
+					       msecs_to_jiffies(500));
+
+	if (ret == 0) {
+		dev_err(gdev->dev, "GlandaGPU: IRQ wait_idle timeout\n");
+		return -ETIMEDOUT;
+	} else if (ret < 0) {
+		return ret;
+	}
+
+	return 0;
+}
+
+static int glanda_hw_clear(struct glanda_device *gdev, int color)
+{
+	u32 ctrl;
+	int ret;
+
+	if (mutex_lock_interruptible(&gdev->lock))
+		return -ERESTARTSYS;
+
+	ret = glanda_wait_idle(gdev);
+	if (ret) {
+		mutex_unlock(&gdev->lock);
+		return ret;
+	}
+
+	writel(color, gdev->mmio_base + REG_COLOR);
+	ctrl = CTRL_START | CMD_CLEAR;
+	writel(ctrl, gdev->mmio_base + REG_CTRL);
+
+	mutex_unlock(&gdev->lock);
+	return 0;
+}
+
+static int glanda_hw_draw_rect(struct glanda_device *gdev,
+			       int x, int y, int w, int h, int color)
+{
+	u32 coord0, coord1, ctrl;
+	int ret;
+
+	if (mutex_lock_interruptible(&gdev->lock))
+		return -ERESTARTSYS;
+
+	ret = glanda_wait_idle(gdev);
+	if (ret) {
+		mutex_unlock(&gdev->lock);
+		return ret;
+	}
+
+	coord0 = (y << 16) | (x & 0x3FF);
+	coord1 = (h << 16) | (w & 0x3FF);
+
+	writel(coord0, gdev->mmio_base + REG_COORD0);
+	writel(coord1, gdev->mmio_base + REG_COORD1);
+	writel(color, gdev->mmio_base + REG_COLOR);
+
+	ctrl = CTRL_START | CMD_RECT;
+	writel(ctrl, gdev->mmio_base + REG_CTRL);
+
+	mutex_unlock(&gdev->lock);
+	return 0;
+}
+
+static int glanda_hw_draw_line(struct glanda_device *gdev,
+			       int x1, int y1, int x2, int y2, int color)
+{
+	u32 coord0, coord1, ctrl;
+	int ret;
+
+	if (mutex_lock_interruptible(&gdev->lock))
+		return -ERESTARTSYS;
+
+	ret = glanda_wait_idle(gdev);
+	if (ret) {
+		mutex_unlock(&gdev->lock);
+		return ret;
+	}
+
+	coord0 = (y1 << 16) | (x1 & 0x3FF);
+	coord1 = (y2 << 16) | (x2 & 0x3FF);
+
+	writel(coord0, gdev->mmio_base + REG_COORD0);
+	writel(coord1, gdev->mmio_base + REG_COORD1);
+	writel(color, gdev->mmio_base + REG_COLOR);
+
+	ctrl = CTRL_START | CMD_LINE;
+	writel(ctrl, gdev->mmio_base + REG_CTRL);
+
+	mutex_unlock(&gdev->lock);
+	return 0;
+}
+
+static int glanda_drm_ioctl_clear(struct drm_device *dev, void *data,
+				  struct drm_file *file_priv)
+{
+	struct glanda_device *gdev = to_glanda(dev);
+	struct glanda_clear_cmd *cmd = data;
+
+	return glanda_hw_clear(gdev, cmd->color);
+}
+
+static bool glanda_rect_cmd_is_valid(const struct glanda_draw_rect_cmd *cmd)
+{
+	if (cmd->x >= GLANDA_WIDTH || cmd->y >= GLANDA_HEIGHT)
+		return false;
+	if (cmd->w > GLANDA_WIDTH || cmd->h > GLANDA_HEIGHT)
+		return false;
+	if (cmd->x + cmd->w > GLANDA_WIDTH)
+		return false;
+	if (cmd->y + cmd->h > GLANDA_HEIGHT)
+		return false;
+
+	return true;
+}
+
+static int glanda_drm_ioctl_draw_rect(struct drm_device *dev, void *data,
+				      struct drm_file *file_priv)
+{
+	struct glanda_device *gdev = to_glanda(dev);
+	struct glanda_draw_rect_cmd *cmd = data;
+
+	if (!glanda_rect_cmd_is_valid(cmd))
+		return -EINVAL;
+
+	return glanda_hw_draw_rect(gdev, cmd->x, cmd->y, cmd->w, cmd->h,
+				   cmd->color);
+}
+
+static bool glanda_line_cmd_is_valid(const struct glanda_draw_line_cmd *cmd)
+{
+	if (cmd->x0 >= GLANDA_WIDTH || cmd->y0 >= GLANDA_HEIGHT)
+		return false;
+	if (cmd->x1 >= GLANDA_WIDTH || cmd->y1 >= GLANDA_HEIGHT)
+		return false;
+
+	return true;
+}
+
+static int glanda_drm_ioctl_draw_line(struct drm_device *dev, void *data,
+				      struct drm_file *file_priv)
+{
+	struct glanda_device *gdev = to_glanda(dev);
+	struct glanda_draw_line_cmd *cmd = data;
+
+	if (!glanda_line_cmd_is_valid(cmd))
+		return -EINVAL;
+
+	return glanda_hw_draw_line(gdev, cmd->x0, cmd->y0, cmd->x1, cmd->y1,
+				   cmd->color);
+}
+
+static void glanda_plane_atomic_update(struct drm_plane *plane,
+				       struct drm_atomic_commit *state)
+{
+	struct drm_plane_state *new_state = drm_atomic_get_new_plane_state(state, plane);
+	struct drm_framebuffer *fb = new_state->fb;
+	struct glanda_device *gdev = to_glanda(plane->dev);
+	struct drm_gem_shmem_object *shmem;
+	struct iosys_map map;
+	u32 src_pitch;
+	u32 width;
+	u32 height;
+	int ret;
+
+	if (!fb)
+		return;
+
+	shmem = to_drm_gem_shmem_obj(fb->obj[0]);
+	if (!shmem)  {
+		drm_err(&gdev->drm, "GlandaGPU: framebuffer is not a shmem GEM object\n");
+		return;
+	}
+
+	dma_resv_lock(shmem->base.resv, NULL);
+	ret = drm_gem_shmem_vmap_locked(shmem, &map);
+	if (ret) {
+		dma_resv_unlock(shmem->base.resv);
+		drm_err(&gdev->drm,
+			"GlandaGPU: failed to vmap GEM shmem object\n");
+		return;
+	}
+
+	mutex_lock(&gdev->lock);
+
+	ret = glanda_wait_idle(gdev);
+	if (ret) {
+		drm_err(&gdev->drm, "GlandaGPU: timed out waiting for idle\n");
+		mutex_unlock(&gdev->lock);
+		drm_gem_shmem_vunmap_locked(shmem, &map);
+	dma_resv_unlock(shmem->base.resv);
+		return;
+	}
+
+	src_pitch = fb->pitches[0];
+	width = min_t(u32, fb->width, GLANDA_WIDTH);
+	height = min_t(u32, fb->height, GLANDA_HEIGHT);
+
+	u8 __iomem *dst_base = gdev->vram_base;
+	u8 *src_base = map.vaddr;
+	u32 y;
+
+	for (y = 0; y < height; y++) {
+		u32 *src = (u32 *)(src_base + y * src_pitch);
+		u32 __iomem *dst = (u32 __iomem *)(dst_base + y * GLANDA_WIDTH * sizeof(u32));
+		u32 x;
+
+		for (x = 0; x < width; x++) {
+			u32 pixel = src[x];
+			u32 packed = ((pixel >> 12) & 0x0F00) |
+				((pixel >> 8) & 0x00F0) |
+				((pixel >> 4) & 0x000F);
+
+			writel_relaxed(packed, &dst[x]);
+		}
+	}
+
+	mutex_unlock(&gdev->lock);
+	drm_gem_shmem_vunmap_locked(shmem, &map);
+	dma_resv_unlock(shmem->base.resv);
+}
+
+static int glanda_plane_atomic_check(struct drm_plane *plane,
+				     struct drm_atomic_commit *state)
+{
+	struct drm_plane_state *new_plane_state = drm_atomic_get_new_plane_state(state, plane);
+	struct drm_crtc_state *crtc_state;
+
+	if (!new_plane_state->crtc)
+		return 0;
+
+	crtc_state = drm_atomic_get_new_crtc_state(state, new_plane_state->crtc);
+
+	return drm_atomic_helper_check_plane_state(new_plane_state, crtc_state,
+		DRM_PLANE_NO_SCALING, DRM_PLANE_NO_SCALING,
+		false,	/* can_position */
+		false	/* can_update_disabled */);
+}
+
+static const struct drm_plane_helper_funcs glanda_plane_helper_funcs = {
+	.atomic_update = glanda_plane_atomic_update,
+	.atomic_check = glanda_plane_atomic_check,
+};
+
+static const struct drm_plane_funcs glanda_plane_funcs = {
+	.update_plane = drm_atomic_helper_update_plane,
+	.disable_plane = drm_atomic_helper_disable_plane,
+	.destroy = drm_plane_cleanup,
+	.reset = drm_atomic_helper_plane_reset,
+	.atomic_duplicate_state = drm_atomic_helper_plane_duplicate_state,
+	.atomic_destroy_state = drm_atomic_helper_plane_destroy_state,
+};
+
+static int glanda_connector_get_modes(struct drm_connector *connector)
+{
+	struct drm_display_mode *mode;
+
+	mode = drm_mode_create(connector->dev);
+	if (!mode) {
+		dev_err(connector->dev->dev, "GlandaGPU: failed to create display mode\n");
+		return 0;
+	}
+
+	/* Standard VGA timing: 640x480 @ 60 Hz. */
+	mode->hdisplay = 640;
+	mode->hsync_start = 656;
+	mode->hsync_end = 752;
+	mode->htotal = 800;
+
+	mode->vdisplay = 480;
+	mode->vsync_start = 490;
+	mode->vsync_end = 492;
+	mode->vtotal = 525;
+
+	mode->clock = 25175;	/* 25.175 MHz pixel clock */
+
+	mode->flags = DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC;
+	mode->type = DRM_MODE_TYPE_DRIVER | DRM_MODE_TYPE_PREFERRED;
+
+	drm_mode_set_name(mode);
+	drm_mode_probed_add(connector, mode);
+
+	return 1;
+}
+
+static enum drm_connector_status glanda_connector_detect(struct drm_connector
+							 *connector, bool force)
+{
+	return connector_status_connected;
+}
+
+static int glanda_crtc_enable_vblank(struct drm_crtc *crtc)
+{
+	struct glanda_device *gdev = to_glanda(crtc->dev);
+	u32 ier;
+
+	ier = readl(gdev->mmio_base + REG_IER);
+	writel(ier | INT_VSYNC, gdev->mmio_base + REG_IER);
+
+	return 0;
+}
+
+static void glanda_crtc_disable_vblank(struct drm_crtc *crtc)
+{
+	struct glanda_device *gdev = to_glanda(crtc->dev);
+	u32 ier = readl(gdev->mmio_base + REG_IER);
+
+	writel(ier & ~INT_VSYNC, gdev->mmio_base + REG_IER);
+}
+
+static void glanda_crtc_atomic_enable(struct drm_crtc *crtc,
+				      struct drm_atomic_commit *state)
+{
+	drm_crtc_vblank_on(crtc);
+}
+
+static void glanda_crtc_atomic_disable(struct drm_crtc *crtc,
+				       struct drm_atomic_commit *state)
+{
+	drm_crtc_vblank_off(crtc);
+}
+
+static void glanda_crtc_atomic_flush(struct drm_crtc *crtc,
+				     struct drm_atomic_commit *state)
+{
+	struct drm_crtc_state *new_state = drm_atomic_get_new_crtc_state(state, crtc);
+	struct drm_pending_vblank_event *event;
+
+	if (new_state && new_state->event) {
+		event = new_state->event;
+
+		new_state->event = NULL;
+
+		spin_lock_irq(&crtc->dev->event_lock);
+
+		if (drm_crtc_vblank_get(crtc) == 0)
+			drm_crtc_arm_vblank_event(crtc, event);
+		else
+			drm_crtc_send_vblank_event(crtc, event);
+
+		spin_unlock_irq(&crtc->dev->event_lock);
+	}
+}
+
+static const struct drm_crtc_funcs glanda_crtc_funcs = {
+	.destroy = drm_crtc_cleanup,
+	.set_config = drm_atomic_helper_set_config,
+	.page_flip = drm_atomic_helper_page_flip,
+	.reset = drm_atomic_helper_crtc_reset,
+	.atomic_duplicate_state = drm_atomic_helper_crtc_duplicate_state,
+	.atomic_destroy_state = drm_atomic_helper_crtc_destroy_state,
+	.enable_vblank = glanda_crtc_enable_vblank,
+	.disable_vblank = glanda_crtc_disable_vblank,
+};
+
+static const struct drm_crtc_helper_funcs glanda_crtc_helper_funcs = {
+	.atomic_enable = glanda_crtc_atomic_enable,
+	.atomic_disable = glanda_crtc_atomic_disable,
+	.atomic_flush = glanda_crtc_atomic_flush,
+};
+
+static const struct drm_connector_helper_funcs glanda_connector_helper_funcs = {
+	.get_modes = glanda_connector_get_modes,
+};
+
+static const struct drm_connector_funcs glanda_connector_funcs = {
+	.fill_modes = drm_helper_probe_single_connector_modes,
+	.destroy = drm_connector_cleanup,
+	.detect = glanda_connector_detect,
+	.reset = drm_atomic_helper_connector_reset,
+	.atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state,
+	.atomic_destroy_state = drm_atomic_helper_connector_destroy_state,
+};
+
+static const struct drm_framebuffer_funcs glanda_fb_funcs = {
+	.destroy = drm_gem_fb_destroy,
+	.create_handle = drm_gem_fb_create_handle,
+	.dirty = drm_atomic_helper_dirtyfb,
+};
+
+static struct drm_framebuffer *glanda_fb_create(struct drm_device *dev,
+						struct drm_file *file,
+						const struct drm_format_info *info,
+						const struct drm_mode_fb_cmd2 *mode_cmd)
+{
+	return drm_gem_fb_create_with_funcs(dev, file, info, mode_cmd, &glanda_fb_funcs);
+}
+
+static const struct drm_mode_config_funcs glanda_mode_config_funcs = {
+	.fb_create = glanda_fb_create,
+	.atomic_check = drm_atomic_helper_check,
+	.atomic_commit = drm_atomic_helper_commit,
+};
+
+/*
+ * RFC NOTE: These three fixed-function ioctls (clear/rect/line) are a
+ * minimal placeholder UAPI to demonstrate the hardware's 2D drawing
+ * capability end-to-end. Given plans to add polygon/3D rendering support
+ * in the future, feedback is explicitly requested on whether a generic
+ * command-buffer submission ioctl (similar to virtio_gpu) would
+ * be a better long-term UAPI direction before this is treated as stable.
+ */
+static const struct drm_ioctl_desc glanda_ioctls[] = {
+	DRM_IOCTL_DEF_DRV(GLANDA_CLEAR, glanda_drm_ioctl_clear,
+			  DRM_AUTH | DRM_RENDER_ALLOW),
+	DRM_IOCTL_DEF_DRV(GLANDA_DRAW_RECT, glanda_drm_ioctl_draw_rect,
+			  DRM_AUTH | DRM_RENDER_ALLOW),
+	DRM_IOCTL_DEF_DRV(GLANDA_DRAW_LINE, glanda_drm_ioctl_draw_line,
+			  DRM_AUTH | DRM_RENDER_ALLOW),
+};
+
+DEFINE_DRM_GEM_FOPS(glanda_drm_fops);
+
+static const struct drm_driver glanda_drm_driver = {
+	.driver_features =
+	    DRIVER_GEM | DRIVER_MODESET | DRIVER_ATOMIC | DRIVER_RENDER,
+	.name = "glandagpu",
+	.desc = "GlandaGPU Hardware Accelerated DRM Driver",
+	.major = 1,
+	.minor = 0,
+	.fops = &glanda_drm_fops,
+	.dumb_create = drm_gem_shmem_dumb_create,
+	.ioctls = glanda_ioctls,
+	.num_ioctls = ARRAY_SIZE(glanda_ioctls),
+};
+
+static irqreturn_t glanda_irq_handler(int irq, void *dev_id)
+{
+	struct glanda_device *gdev = dev_id;
+
+	if (!gdev || !gdev->mmio_base)
+		return IRQ_NONE;
+
+	u32 isr = readl(gdev->mmio_base + REG_ISR);
+
+	if (!isr)
+		return IRQ_NONE;
+
+	if (isr & INT_DONE) {
+		gdev->cmd_done = true;
+		wake_up_interruptible(&gdev->cmd_wq);
+	}
+
+	if (isr & INT_VSYNC)
+		drm_crtc_handle_vblank(&gdev->crtc);
+
+	/* Clear interrupt(W1C) */
+	writel(isr, gdev->mmio_base + REG_ISR);
+	return IRQ_HANDLED;
+}
+
+static int glandagpu_probe(struct platform_device *pdev)
+{
+	struct resource *res;
+	struct glanda_device *gdev;
+	int ret;
+
+	dev_info(&pdev->dev, "GlandaGPU Probe started\n");
+
+	gdev = devm_drm_dev_alloc(&pdev->dev, &glanda_drm_driver, struct glanda_device, drm);
+	if (IS_ERR(gdev))
+		return PTR_ERR(gdev);
+
+	gdev->dev = &pdev->dev;
+	platform_set_drvdata(pdev, gdev);
+
+	mutex_init(&gdev->lock);
+	/* Interrupt setup */
+	init_waitqueue_head(&gdev->cmd_wq);
+	gdev->irq = -1;
+	/* Map VRAM */
+	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+	if (!res)
+		return -ENODEV;
+
+	gdev->vram_phys = res->start;
+	gdev->vram_base = devm_ioremap(&pdev->dev, res->start, GLANDA_VRAM_SIZE);
+	gdev->mmio_base = devm_ioremap(&pdev->dev, res->start + GLANDA_MMIO_OFFSET,
+				       GLANDA_MMIO_SIZE);
+
+	if (!gdev->vram_base || !gdev->mmio_base) {
+		drm_err(&gdev->drm, "failed to ioremap\n");
+		return -ENOMEM;
+	}
+
+	writel(0, gdev->mmio_base + REG_IER);
+	writel(0xFFFFFFFF, gdev->mmio_base + REG_ISR);	/* clear flags */
+
+	ret = platform_get_irq(pdev, 0);
+	if (ret > 0) {
+		gdev->irq = ret;
+		ret = devm_request_irq(&pdev->dev, gdev->irq, glanda_irq_handler,
+				       IRQF_SHARED, "glandagpu", gdev);
+		if (ret) {
+			drm_err(&gdev->drm, "Failed to request IRQ %d\n",
+				gdev->irq);
+			return ret;
+		}
+
+		writel(INT_DONE, gdev->mmio_base + REG_IER);
+		drm_info(&gdev->drm, "IRQ %d requested and enabled\n", gdev->irq);
+	} else {
+		drm_warn(&gdev->drm, "No IRQ found, falling back to polling\n");
+	}
+
+	/* DRM mode config */
+	drm_mode_config_init(&gdev->drm);
+	gdev->drm.mode_config.min_width = 640;
+	gdev->drm.mode_config.min_height = 480;
+	gdev->drm.mode_config.max_width = 640;
+	gdev->drm.mode_config.max_height = 480;
+	gdev->drm.mode_config.funcs = &glanda_mode_config_funcs;
+
+	ret = drm_universal_plane_init(&gdev->drm, &gdev->primary_plane, 1 << 0,
+				       &glanda_plane_funcs,
+				       glanda_plane_formats,
+				       ARRAY_SIZE(glanda_plane_formats), NULL,
+				       DRM_PLANE_TYPE_PRIMARY, NULL);
+	if (ret) {
+		drm_err(&gdev->drm, "Failed to initialize primary plane\n");
+		goto err_mode_cleanup;
+	}
+	drm_plane_helper_add(&gdev->primary_plane, &glanda_plane_helper_funcs);
+
+	/* VBlank init */
+	ret = drm_vblank_init(&gdev->drm, 1);
+	if (ret) {
+		drm_err(&gdev->drm, "Failed to initialize vblank\n");
+		goto err_mode_cleanup;
+	}
+
+	/* CRTC init */
+	ret = drm_crtc_init_with_planes(&gdev->drm, &gdev->crtc,
+					&gdev->primary_plane, NULL,
+					&glanda_crtc_funcs, NULL);
+	if (ret) {
+		drm_err(&gdev->drm, "Failed to initialize CRTC with planes\n");
+		goto err_mode_cleanup;
+	}
+	drm_crtc_helper_add(&gdev->crtc, &glanda_crtc_helper_funcs);
+
+	ret = drm_simple_encoder_init(&gdev->drm, &gdev->encoder, DRM_MODE_ENCODER_DAC);
+	if (ret) {
+		drm_err(&gdev->drm, "Failed to initialize encoder\n");
+		goto err_mode_cleanup;
+	}
+	gdev->encoder.possible_crtcs = 1;
+
+	ret = drm_connector_init(&gdev->drm, &gdev->connector,
+				 &glanda_connector_funcs, DRM_MODE_CONNECTOR_VGA);
+	if (ret) {
+		drm_err(&gdev->drm, "Failed to initialize connector\n");
+		goto err_mode_cleanup;
+	}
+	drm_connector_helper_add(&gdev->connector, &glanda_connector_helper_funcs);
+
+	drm_connector_attach_encoder(&gdev->connector, &gdev->encoder);
+
+	/* Populate connector state early so userspace can enumerate modes. */
+	mutex_lock(&gdev->drm.mode_config.mutex);
+	drm_helper_probe_single_connector_modes(&gdev->connector, 1024, 768);
+	mutex_unlock(&gdev->drm.mode_config.mutex);
+
+	drm_mode_config_reset(&gdev->drm);
+
+	ret = drm_dev_register(&gdev->drm, 0);
+	if (ret)
+		goto err_mode_cleanup;
+
+	drm_info(&gdev->drm, "GlandaGPU DRM Initialized (/dev/dri/cardX created)\n");
+	return 0;
+
+err_mode_cleanup:
+	drm_mode_config_cleanup(&gdev->drm);
+	return ret;
+}
+
+static void glandagpu_remove(struct platform_device *pdev)
+{
+	struct glanda_device *gdev = platform_get_drvdata(pdev);
+
+	/* Disable interrupts first so no new IRQ work can race the teardown
+	 * below, and wake up anyone still blocked in glanda_wait_idle().
+	 */
+	writel(0, gdev->mmio_base + REG_IER);
+	gdev->cmd_done = true;
+	wake_up_interruptible(&gdev->cmd_wq);
+
+	drm_info(&gdev->drm, "GlandaGPU DRM Driver removed\n");
+	drm_dev_unregister(&gdev->drm);
+	drm_mode_config_cleanup(&gdev->drm);
+}
+
+/* Device Tree match table. */
+static const struct of_device_id glanda_of_match[] = {
+	{.compatible = "glanda,gpu-1.0", },
+	{ /* end of table */  }
+};
+
+MODULE_DEVICE_TABLE(of, glanda_of_match);
+
+static struct platform_driver glandagpu_driver = {
+	.driver = {
+		.name = "glandagpu",
+	   .of_match_table = glanda_of_match,
+	},
+	.probe = glandagpu_probe,
+	.remove = glandagpu_remove,
+};
+
+static int __init glandagpu_init(void)
+{
+	int ret;
+
+	ret = platform_driver_register(&glandagpu_driver);
+	if (ret) {
+		pr_err("GlandaGPU: Failed to register platform driver\n");
+		return ret;
+	}
+
+	pr_info("GlandaGPU: Module loaded successfully\n");
+	return 0;
+}
+
+static void __exit glandagpu_exit(void)
+{
+	platform_driver_unregister(&glandagpu_driver);
+	pr_info("GlandaGPU: Module unloaded\n");
+}
+
+module_init(glandagpu_init);
+module_exit(glandagpu_exit);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Leander Kieweg <kieweg.leander@gmail.com>");
+MODULE_DESCRIPTION("DRM driver for GlandaGPU, an FPGA-based 2D GPU with VGA output");
diff --git a/include/uapi/drm/glanda_drm.h b/include/uapi/drm/glanda_drm.h
new file mode 100644
index 000000000..35d25ba83
--- /dev/null
+++ b/include/uapi/drm/glanda_drm.h
@@ -0,0 +1,40 @@
+/* SPDX-License-Identifier: MIT */
+
+#ifndef _GLANDA_DRM_H_
+#define _GLANDA_DRM_H_
+
+#include <linux/types.h>
+#include <drm/drm.h>
+
+struct glanda_clear_cmd {
+	__u32 color;
+};
+
+struct glanda_draw_rect_cmd {
+	__u16 x;
+	__u16 y;
+	__u16 w;
+	__u16 h;
+	__u32 color;
+};
+
+struct glanda_draw_line_cmd {
+	__u16 x0;
+	__u16 y0;
+	__u16 x1;
+	__u16 y1;
+	__u32 color;
+};
+
+#define DRM_GLANDA_CLEAR     0x00
+#define DRM_GLANDA_DRAW_RECT 0x01
+#define DRM_GLANDA_DRAW_LINE 0x02
+
+#define DRM_IOCTL_GLANDA_CLEAR \
+	DRM_IOW(DRM_COMMAND_BASE + DRM_GLANDA_CLEAR, struct glanda_clear_cmd)
+#define DRM_IOCTL_GLANDA_DRAW_RECT \
+	DRM_IOW(DRM_COMMAND_BASE + DRM_GLANDA_DRAW_RECT, struct glanda_draw_rect_cmd)
+#define DRM_IOCTL_GLANDA_DRAW_LINE \
+	DRM_IOW(DRM_COMMAND_BASE + DRM_GLANDA_DRAW_LINE, struct glanda_draw_line_cmd)
+
+#endif
-- 
2.43.0


^ permalink raw reply related	[flat|nested] 8+ messages in thread

* [RFC PATCH 3/3] NOT FOR MERGE: drm/glanda: Add x86 platform test device
  2026-07-14 10:11 [RFC PATCH 0/3] drm: Add DRM driver for GlandaGPU (VHDL soft-IP GPU) Leander Kieweg
  2026-07-14 10:11 ` [RFC PATCH 1/3] dt-bindings: display: Add GlandaGPU binding Leander Kieweg
  2026-07-14 10:11 ` [RFC PATCH 2/3] drm/glanda: Add initial DRM driver for GlandaGPU Leander Kieweg
@ 2026-07-14 10:11 ` Leander Kieweg
  2026-07-14 10:47   ` sashiko-bot
  2 siblings, 1 reply; 8+ messages in thread
From: Leander Kieweg @ 2026-07-14 10:11 UTC (permalink / raw)
  To: dri-devel, devicetree
  Cc: airlied, simona, maarten.lankhorst, mripard, tzimmermann, robh,
	krzk+dt, conor+dt, Leander Kieweg

Register a mock platform device on x86 architectures to allow
testing the GlandaGPU driver inside QEMU without cross-compiling.

Signed-off-by: Leander Kieweg <kieweg.leander@gmail.com>
---
 drivers/gpu/drm/tiny/Kconfig     | 14 ++++++++++++
 drivers/gpu/drm/tiny/glandagpu.c | 39 ++++++++++++++++++++++++++++++++
 2 files changed, 53 insertions(+)

diff --git a/drivers/gpu/drm/tiny/Kconfig b/drivers/gpu/drm/tiny/Kconfig
index 7a15bf95a..156713746 100644
--- a/drivers/gpu/drm/tiny/Kconfig
+++ b/drivers/gpu/drm/tiny/Kconfig
@@ -67,6 +67,20 @@ config DRM_GLANDA
 	  basic modesetting, dumb buffers, and simple 2D drawing
 	  acceleration via custom hardware IOCTLs.
 
+config DRM_GLANDA_X86_TEST
+    bool "Register a fixed-address test device on x86 (QEMU only)"
+    depends on DRM_GLANDA && X86
+    default n
+    help
+      Registers a platform device at a hardcoded physical address
+      (0xC0000000) for testing GlandaGPU against a QEMU-based digital
+      twin without needing a devicetree.
+
+      WARNING: this blindly ioremaps a fixed physical address range.
+      Do NOT enable this on real x86 hardware. This exists solely to
+      let reviewers test the driver in QEMU without cross-compiling
+      an ARM kernel/rootfs. Leave disabled otherwise.
+
 config DRM_GM12U320
 	tristate "GM12U320 driver for USB projectors"
 	depends on DRM && USB && MMU
diff --git a/drivers/gpu/drm/tiny/glandagpu.c b/drivers/gpu/drm/tiny/glandagpu.c
index 8f87ae096..c1e9370f2 100644
--- a/drivers/gpu/drm/tiny/glandagpu.c
+++ b/drivers/gpu/drm/tiny/glandagpu.c
@@ -741,6 +741,34 @@ static struct platform_driver glandagpu_driver = {
 	.remove = glandagpu_remove,
 };
 
+#ifdef CONFIG_DRM_GLANDA_X86_TEST
+static struct platform_device *pdev_x86;
+
+static struct resource glandagpu_resources[] = {
+	[0] = {			/* Single resource covering VRAM and MMIO. */
+	       .start = BRIDGE_BASE,
+	       .end = GLANDA_BASE_SIZE,
+	       .flags = IORESOURCE_MEM,},
+	[1] = {			/* IRQ */
+	       .start = 11,
+	       .end = 11,
+	       .flags = IORESOURCE_IRQ,},
+};
+
+static int glandagpu_register_x86_test_device(void)
+{
+	pdev_x86 = platform_device_register_simple("glandagpu", -1,
+						   glandagpu_resources,
+						   ARRAY_SIZE(glandagpu_resources));
+	if (IS_ERR(pdev_x86)) {
+		pr_err("GlandaGPU: Failed to register platform device\n");
+		return PTR_ERR(pdev_x86);
+	}
+
+	return 0;
+}
+#endif
+
 static int __init glandagpu_init(void)
 {
 	int ret;
@@ -750,6 +778,13 @@ static int __init glandagpu_init(void)
 		pr_err("GlandaGPU: Failed to register platform driver\n");
 		return ret;
 	}
+#ifdef CONFIG_DRM_GLANDA_X86_TEST
+	ret = glandagpu_register_x86_test_device();
+	if (ret) {
+		platform_driver_unregister(&glandagpu_driver);
+		return ret;
+	}
+#endif
 
 	pr_info("GlandaGPU: Module loaded successfully\n");
 	return 0;
@@ -757,6 +792,10 @@ static int __init glandagpu_init(void)
 
 static void __exit glandagpu_exit(void)
 {
+#ifdef CONFIG_DRM_GLANDA_X86_TEST
+	if (pdev_x86)
+		platform_device_unregister(pdev_x86);
+#endif
 	platform_driver_unregister(&glandagpu_driver);
 	pr_info("GlandaGPU: Module unloaded\n");
 }
-- 
2.43.0


^ permalink raw reply related	[flat|nested] 8+ messages in thread

* Re: [RFC PATCH 1/3] dt-bindings: display: Add GlandaGPU binding
  2026-07-14 10:11 ` [RFC PATCH 1/3] dt-bindings: display: Add GlandaGPU binding Leander Kieweg
@ 2026-07-14 10:23   ` sashiko-bot
  0 siblings, 0 replies; 8+ messages in thread
From: sashiko-bot @ 2026-07-14 10:23 UTC (permalink / raw)
  To: Leander Kieweg; +Cc: conor+dt, dri-devel, devicetree, robh

Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [Low] Missing newline at the end of the new YAML binding file.
--

--- Patch [1]: [RFC PATCH 1/3] dt-bindings: display: Add GlandaGPU binding ---
commit 13be4be8578f4fe27e2424184082468464c6c35a
Author: Leander Kieweg <kieweg.leander@gmail.com>

dt-bindings: display: Add GlandaGPU binding

This commit adds Device Tree binding documentation for GlandaGPU, a custom
FPGA-based 2D display controller.

> diff --git a/Documentation/devicetree/bindings/display/glanda,gpu.yaml b/Documentation/devicetree/bindings/display/glanda,gpu.yaml
> new file mode 100644
> index 0000000000000..40304e77382a4
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/display/glanda,gpu.yaml

[ ... ]

> +examples:
> +  - |
> +    gpu@c0000000 {
> +        compatible = "glanda,gpu-1.0";
> +        reg = <0xc0000000 0x1000000>;
> +        interrupts = <0 43 4>;
> +        clocks = <&osc1>;
> +    };
> \ No newline at end of file

[Severity: Low]
This isn't a bug, but should this new YAML file end with a newline? 

Missing newlines can trigger styling warnings during yamllint checks when
running make dt_binding_check.

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260714101146.200416-1-kieweg.leander@gmail.com?part=1

^ permalink raw reply	[flat|nested] 8+ messages in thread

* Re: [RFC PATCH 2/3] drm/glanda: Add initial DRM driver for GlandaGPU
  2026-07-14 10:11 ` [RFC PATCH 2/3] drm/glanda: Add initial DRM driver for GlandaGPU Leander Kieweg
@ 2026-07-14 10:33   ` sashiko-bot
  2026-07-14 12:14   ` Uwe Kleine-König
  1 sibling, 0 replies; 8+ messages in thread
From: sashiko-bot @ 2026-07-14 10:33 UTC (permalink / raw)
  To: Leander Kieweg; +Cc: dri-devel, conor+dt, devicetree, robh

Thank you for your contribution! Sashiko AI review found 7 potential issue(s) to consider:
- [High] Plane atomic update callback calls sleeping functions, violating DRM atomic context rules.
- [High] Manual call to drm_mode_config_cleanup causes a double-free.
- [High] Missing drm_atomic_helper_shutdown() during driver removal.
- [High] Teardown logic creates a race condition causing ioctl threads to write to unmapped MMIO memory.
- [High] IRQ handler is registered before the CRTC is initialized, creating a window for a NULL pointer dereference.
- [Medium] Direct pointer dereference of `iosys_map` memory bypasses abstraction and breaks on I/O memory.
- [Medium] Mishandling of platform_get_irq return value breaks probe deferral.
--

--- Patch [2]: [RFC PATCH 2/3] drm/glanda: Add initial DRM driver for GlandaGPU ---
commit ee638d3912e8ed264bf2c9ee4b6fd9350bc3726b
Author: Leander Kieweg <kieweg.leander@gmail.com>
Subject: drm/glanda: Add initial DRM driver for GlandaGPU

This commit introduces the core DRM/KMS driver for GlandaGPU. This driver
supports basic modesetting, atomic updates, and custom 2D hardware
acceleration IOCTLs.

> diff --git a/drivers/gpu/drm/tiny/glandagpu.c b/drivers/gpu/drm/tiny/glandagpu.c
> new file mode 100644
> index 0000000000000..8f87ae096a2e3
> --- /dev/null
> +++ b/drivers/gpu/drm/tiny/glandagpu.c
> @@ -0,0 +1,769 @@
[ ... ]
> +static void glanda_plane_atomic_update(struct drm_plane *plane,
> +				       struct drm_atomic_commit *state)
> +{
[ ... ]
> +	dma_resv_lock(shmem->base.resv, NULL);
> +	ret = drm_gem_shmem_vmap_locked(shmem, &map);
> +	if (ret) {
> +		dma_resv_unlock(shmem->base.resv);
> +		drm_err(&gdev->drm,
> +			"GlandaGPU: failed to vmap GEM shmem object\n");
> +		return;
> +	}
> +
> +	mutex_lock(&gdev->lock);

[Severity: High]
DRM atomic commit callbacks execute in an atomic context and cannot sleep. 
Is it safe to call dma_resv_lock, drm_gem_shmem_vmap_locked, and mutex_lock 
here? These functions can sleep and might trigger a schedule while atomic
oops or deadlocks when this path is reached via non-blocking atomic commits.

> +
> +	ret = glanda_wait_idle(gdev);
> +	if (ret) {
> +		drm_err(&gdev->drm, "GlandaGPU: timed out waiting for idle\n");
> +		mutex_unlock(&gdev->lock);
> +		drm_gem_shmem_vunmap_locked(shmem, &map);
> +	dma_resv_unlock(shmem->base.resv);
> +		return;
> +	}
> +
> +	src_pitch = fb->pitches[0];
> +	width = min_t(u32, fb->width, GLANDA_WIDTH);
> +	height = min_t(u32, fb->height, GLANDA_HEIGHT);
> +
> +	u8 __iomem *dst_base = gdev->vram_base;
> +	u8 *src_base = map.vaddr;
> +	u32 y;
> +
> +	for (y = 0; y < height; y++) {
> +		u32 *src = (u32 *)(src_base + y * src_pitch);

[Severity: Medium]
Does directly dereferencing map.vaddr break abstraction on architectures 
that do not support direct memory dereferencing for I/O memory? If the 
mapped memory is in I/O space (like an imported dma-buf), this might cause 
a panic. Should this use iosys_map helpers like iosys_map_rd instead?

[ ... ]
> +static int glandagpu_probe(struct platform_device *pdev)
> +{
[ ... ]
> +	writel(0, gdev->mmio_base + REG_IER);
> +	writel(0xFFFFFFFF, gdev->mmio_base + REG_ISR);	/* clear flags */
> +
> +	ret = platform_get_irq(pdev, 0);
> +	if (ret > 0) {
> +		gdev->irq = ret;
> +		ret = devm_request_irq(&pdev->dev, gdev->irq, glanda_irq_handler,
> +				       IRQF_SHARED, "glandagpu", gdev);

[Severity: Medium]
What happens if platform_get_irq returns -EPROBE_DEFER or another negative 
error code? The check for ret > 0 seems to treat all negative error codes 
as a missing IRQ, falling back to polling permanently rather than returning 
the error to the driver core to retry.

[Severity: High]
Since devm_request_irq is called with IRQF_SHARED here, could the IRQ 
handler fire immediately before the CRTC is initialized later in probe? 
If the shared interrupt fires early and the INT_VSYNC bit is set, 
glanda_irq_handler would call drm_crtc_handle_vblank on an uninitialized 
crtc object, which could lead to a NULL pointer dereference.

[ ... ]
> +err_mode_cleanup:
> +	drm_mode_config_cleanup(&gdev->drm);
> +	return ret;
> +}

[Severity: High]
The mode config is initialized using drm_mode_config_init, which wraps 
drmm_mode_config_init and automatically registers a devres action for 
cleanup. Does calling drm_mode_config_cleanup manually here cause a 
double-free on probe failure?

> +
> +static void glandagpu_remove(struct platform_device *pdev)
> +{
> +	struct glanda_device *gdev = platform_get_drvdata(pdev);
> +
> +	/* Disable interrupts first so no new IRQ work can race the teardown
> +	 * below, and wake up anyone still blocked in glanda_wait_idle().
> +	 */
> +	writel(0, gdev->mmio_base + REG_IER);
> +	gdev->cmd_done = true;
> +	wake_up_interruptible(&gdev->cmd_wq);

[Severity: High]
By faking a successful completion instead of signaling an abort condition, 
any ioctl threads sleeping in glanda_wait_idle will wake up and proceed 
to write to mmio_base. Without proper synchronization or a call to 
drm_dev_enter/exit to ensure threads finish, can these awakened threads 
end up writing to unmapped MMIO memory as the devres teardown proceeds?

> +
> +	drm_info(&gdev->drm, "GlandaGPU DRM Driver removed\n");
> +	drm_dev_unregister(&gdev->drm);

[Severity: High]
Should this removal sequence include a call to drm_atomic_helper_shutdown 
to quiesce the display hardware? Without it, hardware pipelines might remain 
active after the module is unloaded, which could result in memory corruption 
or resource leaks.

> +	drm_mode_config_cleanup(&gdev->drm);
> +}

[Severity: High]
Similar to the probe error path, does this manual call to 
drm_mode_config_cleanup cause a double-free on module unload, given that 
drm_mode_config_init already registered a devres cleanup handler?

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260714101146.200416-1-kieweg.leander@gmail.com?part=2

^ permalink raw reply	[flat|nested] 8+ messages in thread

* Re: [RFC PATCH 3/3] NOT FOR MERGE: drm/glanda: Add x86 platform test device
  2026-07-14 10:11 ` [RFC PATCH 3/3] NOT FOR MERGE: drm/glanda: Add x86 platform test device Leander Kieweg
@ 2026-07-14 10:47   ` sashiko-bot
  0 siblings, 0 replies; 8+ messages in thread
From: sashiko-bot @ 2026-07-14 10:47 UTC (permalink / raw)
  To: Leander Kieweg; +Cc: robh, dri-devel, conor+dt, devicetree

Thank you for your contribution! Sashiko AI review found 3 potential issue(s) to consider:

Pre-existing issues:
- [High] Missing DRM unplug protection in IOCTL handlers allows Use-After-Unmap of MMIO resources.
- [High] The plane atomic update callback calls multiple sleeping functions while executing in atomic context.
- [High] Premature shared IRQ registration causes a NULL pointer dereference if the interrupt fires before CRTC initialization.
--

--- Patch [3]: [RFC PATCH 3/3] NOT FOR MERGE: drm/glanda: Add x86 platform test device ---
commit 6108d4cd0cea176f9e750373bc9ba705d3d2a6c7
Author: Leander Kieweg <kieweg.leander@gmail.com>

NOT FOR MERGE: drm/glanda: Add x86 platform test device

Register a mock platform device on x86 architectures to allow
testing the GlandaGPU driver inside QEMU without cross-compiling.

> diff --git a/drivers/gpu/drm/tiny/glandagpu.c b/drivers/gpu/drm/tiny/glandagpu.c
> index 8f87ae096a2e3..c1e9370f2a4b1 100644
> --- a/drivers/gpu/drm/tiny/glandagpu.c
> +++ b/drivers/gpu/drm/tiny/glandagpu.c

[Severity: High]
This is a pre-existing issue, but is it possible for a concurrent ioctl to
access unmapped MMIO memory during device removal?

Looking at glandagpu_remove(), it wakes up blocked ioctls:

drivers/gpu/drm/tiny/glandagpu.c:glandagpu_remove() {
    ...
	gdev->cmd_done = true;
	wake_up_interruptible(&gdev->cmd_wq);
    ...
}

However, it lacks drm_dev_unplug() and drm_dev_enter()/drm_dev_exit()
synchronization. When glandagpu_remove() returns, the devres framework
automatically unmaps gdev->mmio_base. The forcibly awakened ioctl could
then resume and write directly to the unmapped MMIO memory:

drivers/gpu/drm/tiny/glandagpu.c:glanda_hw_clear() {
    ...
	writel(color, gdev->mmio_base + REG_COLOR);
    ...
}

Could this cause a kernel panic due to a use-after-unmap page fault?

[Severity: High]
This isn't a bug introduced by this patch, but does the plane atomic update
callback sleep while in atomic context?

Userspace can initiate a non-blocking DRM atomic commit, which executes
glanda_plane_atomic_update() in an atomic context. However, it appears to
call multiple sleeping functions:

drivers/gpu/drm/tiny/glandagpu.c:glanda_plane_atomic_update() {
    ...
	dma_resv_lock(shmem->base.resv, NULL);
	ret = drm_gem_shmem_vmap_locked(shmem, &map);
    ...
	mutex_lock(&gdev->lock);

	ret = glanda_wait_idle(gdev);
    ...
}

Since glanda_wait_idle() internally uses wait_event_interruptible_timeout(),
could these calls cause system instability, scheduling-while-atomic warnings,
or deadlocks in the DRM commit worker?

[Severity: High]
This is a pre-existing issue, but could premature shared IRQ registration
cause a NULL pointer dereference during probe?

In glandagpu_probe(), devm_request_irq() is called with IRQF_SHARED before
the DRM CRTC is initialized by drm_crtc_init_with_planes():

drivers/gpu/drm/tiny/glandagpu.c:glandagpu_probe() {
    ...
		ret = devm_request_irq(&pdev->dev, gdev->irq, glanda_irq_handler,
				       IRQF_SHARED, "glandagpu", gdev);
    ...
	ret = drm_crtc_init_with_planes(&gdev->drm, &gdev->crtc,
    ...
}

If another device on the same shared line triggers an interrupt immediately,
the handler reads the raw REG_ISR without masking it against the interrupt
enable register (REG_IER). If the INT_VSYNC bit is set, it proceeds to
handle the vblank:

drivers/gpu/drm/tiny/glandagpu.c:glanda_irq_handler() {
    ...
	if (isr & INT_VSYNC)
		drm_crtc_handle_vblank(&gdev->crtc);
    ...
}

Since gdev->crtc is still zero-initialized at this point, could this
dereference a NULL crtc->dev pointer and lead to a kernel panic?

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260714101146.200416-1-kieweg.leander@gmail.com?part=3

^ permalink raw reply	[flat|nested] 8+ messages in thread

* Re: [RFC PATCH 2/3] drm/glanda: Add initial DRM driver for GlandaGPU
  2026-07-14 10:11 ` [RFC PATCH 2/3] drm/glanda: Add initial DRM driver for GlandaGPU Leander Kieweg
  2026-07-14 10:33   ` sashiko-bot
@ 2026-07-14 12:14   ` Uwe Kleine-König
  1 sibling, 0 replies; 8+ messages in thread
From: Uwe Kleine-König @ 2026-07-14 12:14 UTC (permalink / raw)
  To: Leander Kieweg
  Cc: dri-devel, devicetree, airlied, simona, maarten.lankhorst,
	mripard, tzimmermann, robh, krzk+dt, conor+dt

[-- Attachment #1: Type: text/plain, Size: 550 bytes --]

Hello,

On Tue, Jul 14, 2026 at 12:11:44PM +0200, Leander Kieweg wrote:
> +#include <linux/mod_devicetable.h>	/* Device Tree parsing */

Don't include this header, I plan to get rid of it soon. of_device_id is
provided by <linux/of.h>, so you can just drop this #include.

> +/* Device Tree match table. */
> +static const struct of_device_id glanda_of_match[] = {
> +	{.compatible = "glanda,gpu-1.0", },

Make this:

	{ .compatible = "glanda,gpu-1.0" },

please. (That is, a space after the opening { and no comma before the closing }).

Thanks
Uwe

[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 488 bytes --]

^ permalink raw reply	[flat|nested] 8+ messages in thread

end of thread, other threads:[~2026-07-14 12:14 UTC | newest]

Thread overview: 8+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-07-14 10:11 [RFC PATCH 0/3] drm: Add DRM driver for GlandaGPU (VHDL soft-IP GPU) Leander Kieweg
2026-07-14 10:11 ` [RFC PATCH 1/3] dt-bindings: display: Add GlandaGPU binding Leander Kieweg
2026-07-14 10:23   ` sashiko-bot
2026-07-14 10:11 ` [RFC PATCH 2/3] drm/glanda: Add initial DRM driver for GlandaGPU Leander Kieweg
2026-07-14 10:33   ` sashiko-bot
2026-07-14 12:14   ` Uwe Kleine-König
2026-07-14 10:11 ` [RFC PATCH 3/3] NOT FOR MERGE: drm/glanda: Add x86 platform test device Leander Kieweg
2026-07-14 10:47   ` sashiko-bot

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.