Devicetree
 help / color / mirror / Atom feed
From: sashiko-bot@kernel.org
To: "Devarsh Thakkar" <devarsht@ti.com>
Cc: dri-devel@lists.freedesktop.org, devicetree@vger.kernel.org,
	robh@kernel.org, conor+dt@kernel.org
Subject: Re: [PATCH v3 04/14] drm/solomon: Add DRM driver for Solomon SSD16xx e-paper display controllers
Date: Mon, 19 Oct 2026 22:02:28 +0000	[thread overview]
Message-ID: <sashiko-outbox-178287@kernel.org> (raw)
In-Reply-To: <20261019213726.4145532-5-devarsht@ti.com>

Thank you for your contribution! Sashiko AI review found 6 potential issue(s) to consider:
- [Critical] drm/solomon: heap buffer overflow of tx_buf in ssd16xx_fb_dirty()
- [Medium] drm/solomon: bit-reversed pixels in ssd16xx_convert_fb_to_1bpp()
- [Medium] drm/solomon: ssd16xx_convert_fb_to_3color() ignores orientation
- [Medium] drm/solomon: init_refresh_pending cleared on error in atomic_update
- [Medium] drm/solomon: ssd16xx: use-after-free of devm-allocated mode after unbind
- [Low] drm/solomon: broken portrait check in ssd16xx_connector_get_modes()
--

commit 2fb6265a2051abb85583fcfb0ff873962f7bd4d7
Author: Devarsh Thakkar <devarsht@ti.com>

drm/solomon: Add DRM driver for Solomon SSD16xx e-paper display controllers

This commit adds a DRM driver for the Solomon Systech SSD16xx family of
e-paper display controllers over SPI, starting with support for the
Gooddisplay GDEY042T81 400x300 black/white panel using the SSD1683
controller.

[1] : https://www.crystalfontz.com/controllers/SolomonSystech/SSD1683
[2] : https://files.seeedstudio.com/wiki/Other_Display/42-epaper/GDEY042T81.pdf
[3] : https://www.crystalfontz.com/controllers/SolomonSystech/SSD1673
[4] : https://www.crystalfontz.com/controllers/SolomonSystech/SSD1680
[5] : https://www.crystalfontz.com/controllers/SolomonSystech/SSD1681

> diff --git a/drivers/gpu/drm/solomon/ssd16xx.c b/drivers/gpu/drm/solomon/ssd16xx.c
> new file mode 100644
> index 0000000000000..ca6550b5dd5bd
> --- /dev/null
> +++ b/drivers/gpu/drm/solomon/ssd16xx.c
> @@ -0,0 +1,1801 @@

[ ... ]

> +static int ssd16xx_hw_init(struct ssd16xx_device *device)
> +{

[ ... ]

> +	switch (device->orientation) {
> +	case 90:
> +	case 180:
> +		data_entry_mode = SSD16XX_DATA_ENTRY_XDEC_YDEC;
> +		break;
> +	default: /* 0°/270° */
> +		data_entry_mode = SSD16XX_DATA_ENTRY_XINC_YINC;
> +		break;
> +	}

[ ... ]

> +static void ssd16xx_convert_fb_to_3color(u8 *bw_dst, u8 *red_dst,
> +					 struct iosys_map *src,
> +					 struct drm_framebuffer *fb,
> +					 struct drm_rect *rect)
> +{
> +	unsigned int x, y;
> +	u8 bw_byte = 0, red_byte = 0;
> +	unsigned int bit_pos = 0;
> +	unsigned int dst_idx = 0;
> +
> +	if (fb->format->format == DRM_FORMAT_R1) {
> +		unsigned int src_pitch = fb->pitches[0];
> +		unsigned int width_bytes = drm_rect_width(rect) / 8;
> +		unsigned int data_size = width_bytes * drm_rect_height(rect);
> +
> +		/* White background — no black pixels, only red ink shows */
> +		memset(bw_dst, 0xFF, data_size);
> +		/* RED RAM: copy R1 data directly (1=red ink, 0=no red) */
> +		for (y = rect->y1; y < rect->y2; y++) {
> +			u8 *line = src->vaddr + y * src_pitch + (rect->x1 / 8);
> +
> +			memcpy(red_dst + dst_idx, line, width_bytes);
> +			dst_idx += width_bytes;
> +		}
> +		return;
> +	}
> +
> +	/* XRGB8888 */
> +	for (y = rect->y1; y < rect->y2; y++) {
> +		for (x = rect->x1; x < rect->x2; x++) {
> +			bool is_red = ssd16xx_pixel_is_red(src, fb, x, y);
> +
> +			if (is_red)
> +				red_byte |= (1 << (7 - bit_pos));
> +			else if (ssd16xx_pixel_is_white(src, fb, x, y))
> +				bw_byte |= (1 << (7 - bit_pos));
> +			if (++bit_pos == 8) {
> +				bw_dst[dst_idx] = bw_byte;
> +				red_dst[dst_idx] = red_byte;
> +				dst_idx++;
> +				bw_byte = 0;
> +				red_byte = 0;
> +				bit_pos = 0;
> +			}
> +		}
> +		if (bit_pos > 0) {
> +			bw_dst[dst_idx] = bw_byte;
> +			red_dst[dst_idx] = red_byte;
> +			dst_idx++;
> +			bw_byte = 0;
> +			red_byte = 0;
> +			bit_pos = 0;
> +		}
> +	}
> +}

[Severity: Medium]
Should ssd16xx_convert_fb_to_3color() also handle device->orientation like
ssd16xx_convert_fb_to_1bpp() does?

When ssd16xx_fb_dirty() runs with device->color_mode ==
SSD16XX_COLOR_MODE_3COLOR and FULL or FAST refresh, it calls
ssd16xx_convert_fb_to_3color() without passing device->orientation.

Because ssd16xx_convert_fb_to_3color() always packs in 0-degree landscape
row-major order, 90, 180, and 270 degree rotations are ignored. In portrait
orientations (90 or 270 degrees) where fb->width is 300 and fb->height is
400, the XRGB8888 path packs 38 bytes per row across 400 rows (15,200 bytes
in row-major order instead of 300 column-major rows of 50 bytes, while
ssd16xx_fb_dirty() only sends data_size = 15,000 bytes), and the
DRM_FORMAT_R1 path computes width_bytes = 300 / 8 = 37 bytes per row
(14,800 bytes), truncating 4 pixels per row without transposing for
portrait orientation.

> +
> +/*
> + * Convert framebuffer to 1-bit monochrome for e-paper display.
> + *
> + * Supports XRGB8888 (thresholded via ITU-R BT.601 luma at 127) and R1
> + * (native 1bpp, zero-copy fast path for aligned landscape frames).
> + *
> + * Output layout:
> + *   0°/180°  landscape: row-major, left-to-right, top-to-bottom
> + *   90°/270° CW portrait: column-major, rightmost column first
> + */
> +static void ssd16xx_convert_fb_to_1bpp(u8 *dst, struct iosys_map *src,
> +				       struct drm_framebuffer *fb,
> +				       struct drm_rect *rect,
> +				       unsigned int orientation)
> +{

[ ... ]

> +	switch (orientation) {
> +	case 90:
> +	case 270:
> +		/*
> +		 * Portrait (90° or 270°): column-major packing.
> +		 * Each portrait source column becomes one physical RAM row.
> +		 * The data entry mode and cursor position control scan direction.
> +		 */
> +		for (x = rect->x2 - 1; x >= (int)rect->x1; x--) {
> +			for (y = rect->y1; y < rect->y2; y++) {
> +				if (ssd16xx_pixel_is_white(src, fb, x, y))
> +					byte |= (1 << (7 - bit_pos));
> +				if (++bit_pos == 8) {
> +					dst[dst_idx++] = byte;
> +					byte = 0;
> +					bit_pos = 0;
> +				}
> +			}
> +			if (bit_pos > 0) {
> +				dst[dst_idx++] = byte;
> +				byte = 0;
> +				bit_pos = 0;
> +			}
> +		}
> +		break;
> +
> +	case 0:
> +	case 180:
> +	default:
> +		/*
> +		 * Landscape (0° or 180°): row-major packing.
> +		 * Each landscape source row becomes one physical RAM row.
> +		 * The data entry mode and cursor position control scan direction.
> +		 */
> +		for (y = rect->y1; y < rect->y2; y++) {
> +			for (x = rect->x1; x < rect->x2; x++) {
> +				if (ssd16xx_pixel_is_white(src, fb, x, y))
> +					byte |= (1 << (7 - bit_pos));
> +				if (++bit_pos == 8) {
> +					dst[dst_idx++] = byte;
> +					byte = 0;
> +					bit_pos = 0;
> +				}
> +			}
> +			if (bit_pos > 0) {
> +				dst[dst_idx++] = byte;
> +				byte = 0;
> +				bit_pos = 0;
> +			}
> +		}
> +		break;
> +	}
> +}

[Severity: Medium]
Are pixels within each byte bit-reversed when orientation is 90 or 180
degrees?

When device->orientation is 90 or 180, ssd16xx_hw_init() configures Data
Entry Mode (command 0x11) to SSD16XX_DATA_ENTRY_XDEC_YDEC (0x00), and
ssd16xx_fb_dirty() sets the RAM X address counter to ram_x_end (49) so that
it decrements down to 0 across each scanline.

In SSD16xx byte-addressed RAM, each byte represents 8 horizontal pixels
along the physical X axis where bit 7 (MSB) drives source line 8 * X_addr +
0 and bit 0 (LSB) drives source line 8 * X_addr + 7. Decrementing the X
byte counter reverses the byte order across a scanline (49, 48, ..., 0),
but does not reverse the bit order within each byte.

Because ssd16xx_convert_fb_to_1bpp() uses the same MSB-first packing
expression:

    byte |= (1 << (7 - bit_pos));

for 90 and 180 degrees as it does for 270 and 0 degrees, pixel 0 is placed
at physical pixel 392 and pixel 7 at physical pixel 399 in X_addr = 49,
followed by pixel 8 at physical pixel 384 and pixel 15 at physical pixel
391 in X_addr = 48, mirroring each 8-pixel group horizontally.

> +
> +static int ssd16xx_fb_dirty(struct drm_framebuffer *fb, struct drm_rect *rect,
> +			    struct ssd16xx_device *device,
> +			    const struct iosys_map *src_map)
> +{
> +	const u8 *ctrl2_tbl = device->controller_cfg->ctrl2_refresh;
> +	struct iosys_map map = *src_map;
> +	int err = 0;
> +	unsigned int data_size = (device->width * device->height) / 8;
> +	u8 *mono_buffer = NULL;
> +	u8 *red_buffer = NULL;
> +	u16 ram_x_start, ram_x_end, ram_y_start, ram_y_end;
> +
> +	/*
> +	 * Process full display area.  The rect for convert_fb_to_1bpp uses
> +	 * the framebuffer (logical) dimensions - the pixel iteration walks
> +	 * the source fb coordinate space. RAM window registers below use
> +	 * the physical panel dimensions (device->width/height).
> +	 */
> +	rect->x1 = 0;
> +	rect->y1 = 0;
> +	rect->x2 = fb->width;
> +	rect->y2 = fb->height;

[Severity: Critical]
Can setting rect->x2 = fb->width and rect->y2 = fb->height here overflow
device->tx_buf and device->tx_red_buf?

In ssd16xx_probe(), drm->mode_config is configured with:

    drm->mode_config.min_width = min(device->width, device->height);
    drm->mode_config.max_width = max(device->width, device->height);
    drm->mode_config.min_height = min(device->width, device->height);
    drm->mode_config.max_height = max(device->width, device->height);

This allows userspace via DRM_IOCTL_MODE_ADDFB2 to create a 400x400
framebuffer and attach it to the primary plane with a 400x300 or 300x400
source rectangle matching the CRTC mode, which passes both
drm_atomic_plane_check() and ssd16xx_plane_atomic_check().

However, ssd16xx_alloc_tx_bufs() allocates device->tx_buf and
device->tx_red_buf with:

    size_t frame_size = max(DIV_ROUND_UP(device->width, 8) * device->height,
                            DIV_ROUND_UP(device->height, 8) * device->width);

which is 15,200 bytes for a 400x300 panel. Because ssd16xx_fb_dirty()
overwrites rect with fb->width and fb->height (400x400) rather than the
active mode/clipped plane dimensions, ssd16xx_convert_fb_to_1bpp() and
ssd16xx_convert_fb_to_3color() will write (400 / 8) * 400 = 20,000 bytes
into the 15,200-byte heap buffer.

In addition, ssd16xx_crtc_mode_valid() accepts both 400x300 and 300x400
modes regardless of the static device->orientation configured in
ssd16xx_probe():

    if ((mode->hdisplay == device->mode->hdisplay &&
         mode->vdisplay == device->mode->vdisplay) ||
        (mode->hdisplay == device->mode->vdisplay &&
         mode->vdisplay == device->mode->hdisplay))
        return MODE_OK;

If userspace commits a 300x400 mode when device->orientation is 0 or 180
(or 400x300 when orientation is 90 or 270), ssd16xx_convert_fb_to_1bpp()
packs 38-byte padded rows across 400 rows (15,200 bytes, or 37-byte rows
totaling 14,800 bytes on the DRM_FORMAT_R1 fast path), while
ssd16xx_fb_dirty() only zeroes and transmits data_size = 15,000 bytes to a
hardware RAM window programmed for 50-byte rows.

[ ... ]

> +static void ssd16xx_plane_atomic_update(struct drm_plane *plane,
> +					struct drm_atomic_commit *state)
> +{

[ ... ]

> +	ret = drm_gem_fb_begin_cpu_access(fb, DMA_FROM_DEVICE);
> +	if (ret) {
> +		drm_err(&device->drm, "plane_atomic_update: begin_cpu_access failed: %d\n", ret);
> +		goto out_restore;
> +	}
> +
> +	ret = ssd16xx_fb_dirty(fb, &rect, device, &shadow_state->data[0]);
> +	if (ret)
> +		drm_err(&device->drm, "plane_atomic_update: display update failed: %d\n", ret);
> +
> +	drm_gem_fb_end_cpu_access(fb, DMA_FROM_DEVICE);
> +
> +out_restore:
> +	device->refresh_mode = saved_mode;
> +	device->border_waveform_update_idx = saved_border_waveform_idx;
> +
> +	/*
> +	 * If this was the init frame (which used border_waveform_init_idx
> +	 * inside fb_dirty), arm border_waveform_pending so the normal
> +	 * (non-init) border value is sent at the start of the next update.
> +	 */
> +	if (device->init_refresh_pending) {
> +		device->init_refresh_pending = false;
> +		device->border_waveform_pending = true;
> +	}

[Severity: Medium]
If drm_gem_fb_begin_cpu_access() or ssd16xx_fb_dirty() fails during the
initial frame, should device->init_refresh_pending remain set instead of
being cleared at out_restore?

Clearing device->init_refresh_pending on the error path causes subsequent
frames to skip the initial full refresh and perform partial refreshes
without a valid RED RAM baseline.

[ ... ]

> +static int ssd16xx_connector_get_modes(struct drm_connector *connector)
> +{
> +	struct ssd16xx_device *device = to_ssd16xx_device(connector->dev);
> +	bool mode_is_portrait = (device->mode->hdisplay < device->mode->vdisplay);
> +	bool orient_is_portrait = (device->orientation == 90 || device->orientation == 270);
> +
> +	drm_dbg(&device->drm,
> +		"connector_get_modes: orientation=%u° mode=%ux%u mode_portrait=%d orient_portrait=%d\n",
> +		device->orientation, device->mode->hdisplay, device->mode->vdisplay,
> +		mode_is_portrait, orient_is_portrait);
> +
> +	/* For portrait, swap dimensions so clients see logical size. */
> +	if (mode_is_portrait != orient_is_portrait) {
> +		struct drm_display_mode *mode;
> +
> +		mode = drm_mode_duplicate(&device->drm, device->mode);
> +		if (!mode)
> +			return 0;
> +		swap(mode->hdisplay, mode->vdisplay);
> +		swap(mode->hsync_start, mode->vsync_start);
> +		swap(mode->hsync_end, mode->vsync_end);
> +		swap(mode->htotal, mode->vtotal);
> +		swap(mode->width_mm, mode->height_mm);
> +		mode->type |= DRM_MODE_TYPE_PREFERRED;
> +		drm_mode_set_name(mode);
> +		drm_mode_probed_add(connector, mode);
> +		return 1;
> +	}
> +
> +	return drm_connector_helper_get_modes_fixed(connector, device->mode);
> +}

[Severity: Low]
Since ssd16xx_probe() already swaps device->mode in-place when
device->orientation is 90 or 270, is the swap check in
ssd16xx_connector_get_modes() redundant?

For natively landscape panels like GDEY042T81, device->mode->hdisplay <
device->mode->vdisplay is already true when orient_is_portrait is true, so
mode_is_portrait != orient_is_portrait is never taken. Conversely, for any
natively portrait panel, mode_is_portrait != orient_is_portrait would be
true in all orientations and undo the swap performed in ssd16xx_probe()
(while also skipping the connector->display_info.width_mm and height_mm
assignments done by drm_connector_helper_get_modes_fixed()).

[ ... ]

> +static int ssd16xx_probe(struct spi_device *spi)
> +{

[ ... ]

> +	mode = devm_kmemdup(dev, device->device_cfg->mode,
> +			    sizeof(*device->device_cfg->mode), GFP_KERNEL);
> +	if (!mode)
> +		return -ENOMEM;

[Severity: Medium]
Can allocating device->mode with devm_kmemdup() lead to a use-after-free
after the SPI device is unbound?

While struct ssd16xx_device is allocated via devm_drm_dev_alloc() and stays
alive until the last open DRM file descriptor is closed, devm_kmemdup()
ties device->mode to the SPI struct device devres list, which is freed as
soon as ssd16xx_remove() returns.

If an in-flight KMS ioctl (such as DRM_IOCTL_MODE_GETCONNECTOR or
DRM_IOCTL_MODE_SETCRTC) passes the initial unplug check in
drm_ioctl_kernel() and waits on modeset locks while ssd16xx_remove() runs,
it can subsequently invoke ssd16xx_connector_get_modes() or
ssd16xx_crtc_mode_valid() (neither of which is guarded by drm_dev_enter())
and dereference device->mode after devres_release_all() has freed it.
Would embedding struct drm_display_mode directly in struct ssd16xx_device
(or using drmm_kmemdup()) avoid this lifetime mismatch?

> +
> +	device->refresh_mode = device->device_cfg->default_refresh_mode;

[ ... ]

> +	/* Swap mode dimensions for portrait so clients see logical size. */
> +	if (device->orientation == 90 || device->orientation == 270) {
> +		swap(mode->hdisplay, mode->vdisplay);
> +		swap(mode->hsync_start, mode->vsync_start);
> +		swap(mode->hsync_end, mode->vsync_end);
> +		swap(mode->htotal, mode->vtotal);
> +		swap(mode->width_mm, mode->height_mm);
> +		drm_dbg(drm, "Mode dimensions swapped for portrait: %ux%u\n",
> +			mode->hdisplay, mode->vdisplay);
> +	} else {
> +		drm_dbg(drm, "Mode dimensions unchanged: %ux%u\n",
> +			mode->hdisplay, mode->vdisplay);
> +	}
> +	device->mode = mode;

[ ... ]

> +static void ssd16xx_remove(struct spi_device *spi)
> +{
> +	struct ssd16xx_device *device = spi_get_drvdata(spi);
> +
> +	drm_dev_unplug(&device->drm);
> +	drm_atomic_helper_shutdown(&device->drm);
> +}

[ ... ]

> +

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20261019213726.4145532-1-devarsht@ti.com?part=4

  reply	other threads:[~2026-10-19 22:02 UTC|newest]

Thread overview: 26+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-10-19 21:37 [PATCH v3 00/14] Add DRM driver for Solomon SSD16xx e-paper display controllers Devarsh Thakkar
2026-10-19 21:37 ` [PATCH v3 01/14] dt-bindings: vendor-prefixes: Add Dalian Good Display Co., Ltd Devarsh Thakkar
2026-10-19 21:37 ` [PATCH v3 02/14] dt-bindings: display: Add Solomon SSD16xx e-paper controller Devarsh Thakkar
2026-10-20  8:34   ` Krzysztof Kozlowski
2026-10-19 21:37 ` [PATCH v3 03/14] dt-bindings: display: solomon,ssd16xx: Add Solomon SSD1677 controller Devarsh Thakkar
2026-10-19 21:44   ` sashiko-bot
2026-10-21  3:37   ` Rob Herring (Arm)
2026-10-19 21:37 ` [PATCH v3 04/14] drm/solomon: Add DRM driver for Solomon SSD16xx e-paper display controllers Devarsh Thakkar
2026-10-19 22:02   ` sashiko-bot [this message]
2026-10-19 21:37 ` [PATCH v3 05/14] drm/solomon: ssd16xx: Add clear_on_init/close/disable session management Devarsh Thakkar
2026-10-19 21:59   ` sashiko-bot
2026-10-19 21:37 ` [PATCH v3 06/14] drm/solomon: ssd16xx: Add support for Solomon SSD1677 controller Devarsh Thakkar
2026-10-19 21:37 ` [PATCH v3 07/14] drm/solomon: ssd16xx: Add power management support Devarsh Thakkar
2026-10-19 21:57   ` sashiko-bot
2026-10-19 21:37 ` [PATCH v3 08/14] drm/solomon: ssd16xx: Expose refresh mode as plane property Devarsh Thakkar
2026-10-19 22:02   ` sashiko-bot
2026-10-19 21:37 ` [PATCH v3 09/14] drm/solomon: ssd16xx: Expose color " Devarsh Thakkar
2026-10-19 21:52   ` sashiko-bot
2026-10-19 21:37 ` [PATCH v3 10/14] drm/solomon: ssd16xx: Expose session management as plane properties Devarsh Thakkar
2026-10-19 21:58   ` sashiko-bot
2026-10-20 10:32   ` Markus Elfring
2026-10-19 21:37 ` [PATCH v3 11/14] drm/solomon: ssd16xx: support panels whose RAM X order is reversed Devarsh Thakkar
2026-10-19 21:37 ` [PATCH v3 12/14] MAINTAINERS: Add entry for Solomon SSD16xx DRM driver Devarsh Thakkar
2026-10-19 21:37 ` [PATCH v3 13/14] arm64: defconfig: Enable DRM_SSD16XX for AM62L3 EVM Devarsh Thakkar
2026-10-19 21:37 ` [PATCH v3 14/14] arm64: dts: ti: [DO_NOT_MERGE] Add AM62L3 EVM overlay for GDEY042T81 e-paper display Devarsh Thakkar
2026-10-19 21:59   ` sashiko-bot

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=sashiko-outbox-178287@kernel.org \
    --to=sashiko-bot@kernel.org \
    --cc=conor+dt@kernel.org \
    --cc=devarsht@ti.com \
    --cc=devicetree@vger.kernel.org \
    --cc=dri-devel@lists.freedesktop.org \
    --cc=robh@kernel.org \
    --cc=sashiko-reviews@lists.linux.dev \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox