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
next prev parent 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