diff --git a/module/evdi_color.c b/module/evdi_color.c index c40820a..71eb991 100644 --- a/module/evdi_color.c +++ b/module/evdi_color.c @@ -8,21 +8,23 @@ */ #include +#include #include +#include #include +#include #include #include #include #include "evdi_color.h" +#include "evdi_debug.h" +#include "evdi_drm_drv.h" +#include "evdi_params.h" + +static LIST_HEAD(evdi_color_list); +static DEFINE_MUTEX(evdi_color_list_lock); -/* - * struct drm_color_ctm stores each entry as S31.32 sign-magnitude (bit 63 - * is the sign, bits 0-62 are the unsigned value) rather than the two's - * complement drm_fixed.h (drm_fixp_t) format the drm_fixp_* helpers use. - * Some newer kernels provide drm_sm2fixp() for this, but it isn't present - * across the full 4.15+ range evdi supports, so convert it ourselves. - */ static s64 evdi_sm2fixp(u64 sm) { s64 magnitude = (s64)(sm & ~BIT_ULL(63)); @@ -30,8 +32,75 @@ static s64 evdi_sm2fixp(u64 sm) return (sm & BIT_ULL(63)) ? -magnitude : magnitude; } -static void evdi_color_update_gamma(struct evdi_color_data *data, - struct drm_crtc_state *crtc_state) +static bool evdi_gamma_is_identity(const u8 g[3][EVDI_GAMMA_LUT_SIZE]) +{ + int c, i; + + for (c = 0; c < 3; ++c) + for (i = 0; i < EVDI_GAMMA_LUT_SIZE; ++i) + if (g[c][i] != (u8)i) + return false; + return true; +} + +static bool evdi_ctm_is_identity(const s64 m[3][3]) +{ + int r, c; + + for (r = 0; r < 3; ++r) { + for (c = 0; c < 3; ++c) { + s64 expect = (r == c) ? (s64)DRM_FIXED_ONE : 0; + + if (m[r][c] != expect) + return false; + } + } + return true; +} + +static bool evdi_ctm_is_diagonal(const s64 m[3][3]) +{ + int r, c; + + for (r = 0; r < 3; ++r) + for (c = 0; c < 3; ++c) + if (r != c && m[r][c] != 0) + return false; + return true; +} + +/* + * Same diagonal matrix as a 256-entry LUT — speed only, not a new model. + * DRM order is CTM then gamma: when a real LUT is already loaded, compose + * gamma_out[i] = gamma_in[clamp(scale * i)] instead of overwriting it. + */ +static void evdi_fuse_diagonal_ctm_to_gamma(struct evdi_color_data *data) +{ + u8 src_gamma[3][EVDI_GAMMA_LUT_SIZE]; + const bool compose = data->has_gamma; + int c, i; + + if (compose) + memcpy(src_gamma, data->gamma, sizeof(src_gamma)); + + for (c = 0; c < 3; ++c) { + s64 scale = data->ctm[c][c]; + + for (i = 0; i < EVDI_GAMMA_LUT_SIZE; ++i) { + s64 v = drm_fixp_mul(scale, drm_int2fixp(i)); + int mid = clamp(drm_fixp2int_round(v), 0, 255); + + data->gamma[c][i] = compose ? src_gamma[c][mid] + : (u8)mid; + } + } + data->has_gamma = true; + data->has_ctm = false; + data->fused_diagonal = true; +} + +static void evdi_color_load_gamma(struct evdi_color_data *data, + struct drm_crtc_state *crtc_state) { const struct drm_color_lut *lut; unsigned int lut_len; @@ -44,19 +113,11 @@ static void evdi_color_update_gamma(struct evdi_color_data *data, lut = (const struct drm_color_lut *)crtc_state->gamma_lut->data; lut_len = crtc_state->gamma_lut->length / sizeof(*lut); - if (lut_len == 0) { data->has_gamma = false; return; } - /* - * Build a direct 256-entry table indexed by raw 8-bit channel value, - * regardless of the length of the LUT userspace actually uploaded - * (expected to be EVDI_GAMMA_LUT_SIZE, since that's what we advertise - * via drm_mode_crtc_set_gamma_size(), but not all clients necessarily - * respect that hint). - */ for (i = 0; i < EVDI_GAMMA_LUT_SIZE; ++i) { s64 idx_fp = drm_fixp_div(drm_int2fixp(i * (lut_len - 1)), drm_int2fixp(EVDI_GAMMA_LUT_SIZE - 1)); @@ -72,16 +133,14 @@ static void evdi_color_update_gamma(struct evdi_color_data *data, s64 interp = floor_fp + drm_fixp_mul(ceil_fp - floor_fp, frac); int val16 = clamp(drm_fixp2int(interp), 0, 65535); - /* 16-bit LUT entry -> 8-bit raw pixel channel */ data->gamma[c][i] = val16 >> 8; } } - data->has_gamma = true; } -static void evdi_color_update_ctm(struct evdi_color_data *data, - struct drm_crtc_state *crtc_state) +static void evdi_color_load_ctm(struct evdi_color_data *data, + struct drm_crtc_state *crtc_state) { const struct drm_color_ctm *ctm; int r, c; @@ -99,52 +158,142 @@ static void evdi_color_update_ctm(struct evdi_color_data *data, data->has_ctm = true; } -void evdi_color_transform_init(struct evdi_color_transform *color) +static void evdi_color_finalize_locked(struct evdi_color_data *data) { + data->fused_diagonal = false; + + /* Apply DRM order without degamma: CTM then gamma. */ + if (data->has_ctm && evdi_ctm_is_identity(data->ctm)) + data->has_ctm = false; + else if (data->has_ctm && evdi_ctm_is_diagonal(data->ctm)) + evdi_fuse_diagonal_ctm_to_gamma(data); + + if (data->has_gamma && evdi_gamma_is_identity(data->gamma)) + data->has_gamma = false; + + data->active = data->has_gamma || data->has_ctm; +} + +void evdi_color_transform_init(struct evdi_color_transform *color, + struct drm_device *ddev) +{ + memset(color, 0, sizeof(*color)); mutex_init(&color->lock); - memset(&color->data, 0, sizeof(color->data)); + color->ddev = ddev; + INIT_LIST_HEAD(&color->link); + + mutex_lock(&evdi_color_list_lock); + list_add_tail(&color->link, &evdi_color_list); + mutex_unlock(&evdi_color_list_lock); +} + +void evdi_color_transform_cleanup(struct evdi_color_transform *color) +{ + mutex_lock(&evdi_color_list_lock); + list_del_init(&color->link); + mutex_unlock(&evdi_color_list_lock); + mutex_destroy(&color->lock); } bool evdi_color_transform_update(struct evdi_color_transform *color, struct drm_crtc_state *crtc_state) { + struct evdi_color_data before; + bool changed; + if (!crtc_state->color_mgmt_changed) return false; mutex_lock(&color->lock); - evdi_color_update_gamma(&color->data, crtc_state); - evdi_color_update_ctm(&color->data, crtc_state); - color->data.active = color->data.has_gamma || color->data.has_ctm; + before = color->data; + memset(&color->data, 0, sizeof(color->data)); + evdi_color_load_gamma(&color->data, crtc_state); + evdi_color_load_ctm(&color->data, crtc_state); + evdi_color_finalize_locked(&color->data); + changed = memcmp(&before, &color->data, sizeof(before)) != 0; mutex_unlock(&color->lock); - return true; + return changed; } bool evdi_color_transform_snapshot(struct evdi_color_transform *color, - struct evdi_color_data *snapshot) + struct evdi_color_data *snapshot) { mutex_lock(&color->lock); *snapshot = color->data; mutex_unlock(&color->lock); - return snapshot->active; } static s64 evdi_color_apply_ctm_channel(const struct evdi_color_data *snapshot, - int row, s64 r, s64 g, s64 b) + int row, s64 r, s64 g, s64 b) { return drm_fixp_mul(snapshot->ctm[row][0], r) + drm_fixp_mul(snapshot->ctm[row][1], g) + drm_fixp_mul(snapshot->ctm[row][2], b); } +int evdi_color_format_status(char *buf, size_t size) +{ + struct evdi_color_transform *color; + int n = 0; + + n += scnprintf(buf + n, size - n, + "color_props=%s (reload module to change)\n" + "# apply only what the compositor programmed; no gamma↔ctm synthesis\n" + "# path=lut|ctm|fused_ctm_lut|off drm=N is /dev/dri/cardN\n", + evdi_color_props ? evdi_color_props : "both"); + + mutex_lock(&evdi_color_list_lock); + list_for_each_entry(color, &evdi_color_list, link) { + struct evdi_color_data d; + int r_s, g_s, b_s; + int drm_idx = -1; + const char *path; + + mutex_lock(&color->lock); + d = color->data; + if (color->ddev && color->ddev->primary) + drm_idx = color->ddev->primary->index; + mutex_unlock(&color->lock); + + if (d.has_gamma) { + r_s = d.gamma[0][255]; + g_s = d.gamma[1][255]; + b_s = d.gamma[2][255]; + } else if (d.has_ctm) { + r_s = clamp(drm_fixp2int_round( + drm_fixp_mul(d.ctm[0][0], drm_int2fixp(255))), 0, 255); + g_s = clamp(drm_fixp2int_round( + drm_fixp_mul(d.ctm[1][1], drm_int2fixp(255))), 0, 255); + b_s = clamp(drm_fixp2int_round( + drm_fixp_mul(d.ctm[2][2], drm_int2fixp(255))), 0, 255); + } else { + r_s = g_s = b_s = 255; + } + + if (!d.active) + path = "off"; + else if (d.fused_diagonal) + path = "fused_ctm_lut"; + else if (d.has_ctm) + path = "ctm"; + else if (d.has_gamma) + path = "lut"; + else + path = "off"; + + n += scnprintf(buf + n, size > n ? size - n : 0, + "drm=%d path=%s active=%d has_gamma=%d has_ctm=%d scales_rgb≈%d/%d/%d\n", + drm_idx, path, d.active, d.has_gamma, d.has_ctm, + r_s, g_s, b_s); + } + mutex_unlock(&evdi_color_list_lock); + return n; +} + void evdi_color_transform_apply_row(const struct evdi_color_data *snapshot, - void *row, int width_px, bool swap_rb) + void *row, int width_px, bool swap_rb) { - /* - * XRGB8888/ARGB8888 ("x:R:G:B" MSB-to-LSB, little endian per - * drm_fourcc.h) store B at byte 0 and R at byte 2; XBGR8888/ - * ABGR8888 swap that. G (byte 1) and X/alpha (byte 3) never move. - */ u8 *px = row; const int r_idx = swap_rb ? 0 : 2; const int b_idx = swap_rb ? 2 : 0; diff --git a/module/evdi_color.h b/module/evdi_color.h index 3d1408b..ff6176c 100644 --- a/module/evdi_color.h +++ b/module/evdi_color.h @@ -9,19 +9,23 @@ #ifndef EVDI_COLOR_H #define EVDI_COLOR_H +#include #include #include struct drm_crtc_state; /* - * evdi has no hardware CRTC gamma/CTM block, so Night Light and other - * DRM color management clients need the transform applied in software - * to the raw framebuffer bytes before they leave the driver. + * Software colour management: apply whatever the compositor programs on the + * CRTC (GAMMA_LUT and/or CTM). No synthesis between the two. * - * Advertised via drm_mode_crtc_set_gamma_size()/drm_crtc_enable_color_mgmt() - * and consumed here as a direct 8-bit-indexed lookup table, independent of - * whatever length LUT userspace actually uploads (see evdi_color_transform_update()). + * Which properties exist is chosen at module load by color_props= + * both|gamma|ctm (see evdi_params). Reload the module to change that. + * + * Diagonal CTMs are fused into a 256-entry LUT only as a speed optimisation + * of the same matrix (Night Light style scales). If a gamma LUT is also + * present it is composed (CTM then gamma), not overwritten. Identity is + * skipped. */ #define EVDI_GAMMA_LUT_SIZE 256 @@ -29,6 +33,7 @@ struct evdi_color_data { bool active; bool has_gamma; bool has_ctm; + bool fused_diagonal; u8 gamma[3][EVDI_GAMMA_LUT_SIZE]; /* row-major 3x3, drm_fixed.h S32.32 two's-complement fixed point */ s64 ctm[3][3]; @@ -36,29 +41,25 @@ struct evdi_color_data { struct evdi_color_transform { struct mutex lock; + struct list_head link; struct evdi_color_data data; + struct drm_device *ddev; }; -void evdi_color_transform_init(struct evdi_color_transform *color); +void evdi_color_transform_init(struct evdi_color_transform *color, + struct drm_device *ddev); +void evdi_color_transform_cleanup(struct evdi_color_transform *color); -/* Recomputes the LUT/CTM from crtc_state. Returns true when colour - * properties actually changed (caller should mark the scanout dirty so - * clients re-grab). No-op when !color_mgmt_changed. - */ +/* Returns true if the effective apply payload changed (caller may full-dirty). */ bool evdi_color_transform_update(struct evdi_color_transform *color, struct drm_crtc_state *crtc_state); -/* Copies the current transform out under lock. Returns whether it's a - * no-op identity transform, letting the caller skip apply_row() entirely. - */ bool evdi_color_transform_snapshot(struct evdi_color_transform *color, - struct evdi_color_data *snapshot); + struct evdi_color_data *snapshot); -/* Applies gamma/CTM in place to one packed 32bpp scanline of width_px - * pixels. swap_rb selects XBGR/ABGR (R and B swapped vs XRGB/ARGB) byte - * order; the alpha/padding byte is always left untouched. - */ void evdi_color_transform_apply_row(const struct evdi_color_data *snapshot, - void *row, int width_px, bool swap_rb); + void *row, int width_px, bool swap_rb); + +int evdi_color_format_status(char *buf, size_t size); #endif diff --git a/module/evdi_drm_drv.c b/module/evdi_drm_drv.c index 5701164..86858c0 100644 --- a/module/evdi_drm_drv.c +++ b/module/evdi_drm_drv.c @@ -159,6 +159,7 @@ static void evdi_drm_device_release_cb(__always_unused struct drm_device *dev, { struct evdi_device *evdi = dev->dev_private; + evdi_color_transform_cleanup(&evdi->color); evdi_cursor_free(evdi->cursor); evdi_painter_cleanup(evdi->painter); kfree(evdi); @@ -188,7 +189,7 @@ static int evdi_drm_device_init(struct drm_device *dev) ret = evdi_cursor_init(&evdi->cursor); if (ret) goto err_free; - evdi_color_transform_init(&evdi->color); + evdi_color_transform_init(&evdi->color, dev); evdi_modeset_init(dev); @@ -206,6 +207,7 @@ static int evdi_drm_device_init(struct drm_device *dev) return 0; err_init: + evdi_color_transform_cleanup(&evdi->color); err_free: EVDI_ERROR("Failed to setup drm device %d\n", ret); evdi_cursor_free(evdi->cursor); diff --git a/module/evdi_modeset.c b/module/evdi_modeset.c index e561f1a..4903c74 100644 --- a/module/evdi_modeset.c +++ b/module/evdi_modeset.c @@ -90,9 +90,10 @@ static void evdi_crtc_atomic_flush( bool notify_dpms = crtc_state->active_changed || evdi_painter_needs_full_modeset(evdi->painter); /* - * Colour is applied only on GRABPIX of dirty rects. When the compositor - * changes GAMMA_LUT/CTM without repainting, force a full-frame dirty so - * DisplayLinkManager re-grabs and the new transform is visible. + * Refresh the software transform from compositor blobs. If the + * effective apply payload changed, full-dirty so a static desktop + * re-grabs (Night Light toggle / temperature). Identity is skipped + * in evdi_color so redundant updates do not spam USB. */ if (evdi_color_transform_update(&evdi->color, crtc_state)) { struct drm_clip_rect full = @@ -100,6 +101,12 @@ static void evdi_crtc_atomic_flush( if (full.x2 > full.x1 && full.y2 > full.y1) evdi_painter_mark_dirty(evdi, &full); + /* + * Cursor events path must re-hide/restore the HW cursor when + * software colour turns on/off so tint stays on the SW blend. + */ + if (evdi->cursor_events_enabled) + evdi_painter_send_cursor_set(evdi->painter, evdi->cursor); } if (notify_mode_changed) @@ -172,12 +179,19 @@ static int evdi_crtc_cursor_set(struct drm_crtc *crtc, /* * For now we don't care whether the application wanted the mouse set, - * or not. + * or not. Colour-active forces SW blend (tinted); keep DLM's HW cursor + * hidden via the set event when events are enabled. */ - if (evdi->cursor_events_enabled) - evdi_painter_send_cursor_set(evdi->painter, evdi->cursor); - else - evdi_mark_full_screen_dirty(evdi); + { + struct evdi_color_data color_snap; + const bool color_active = + evdi_color_transform_snapshot(&evdi->color, &color_snap); + + if (evdi->cursor_events_enabled) + evdi_painter_send_cursor_set(evdi->painter, evdi->cursor); + if (!evdi->cursor_events_enabled || color_active) + evdi_mark_full_screen_dirty(evdi); + } return 0; } @@ -185,11 +199,14 @@ static int evdi_crtc_cursor_move(struct drm_crtc *crtc, int x, int y) { struct drm_device *dev = crtc->dev; struct evdi_device *evdi = dev->dev_private; + struct evdi_color_data color_snap; + const bool color_active = + evdi_color_transform_snapshot(&evdi->color, &color_snap); EVDI_CHECKPT(); evdi_cursor_move(evdi->cursor, x, y); - if (evdi->cursor_events_enabled) + if (evdi->cursor_events_enabled && !color_active) evdi_painter_send_cursor_move(evdi->painter, evdi->cursor); else evdi_mark_full_screen_dirty(evdi); @@ -389,19 +406,35 @@ static void evdi_cursor_atomic_update(struct drm_plane *plane, cursor_changed = true; } - if (!evdi->cursor_events_enabled) { - if (fb != NULL) { - if (efb->obj->allow_sw_cursor_rect_updates) { - evdi_cursor_atomic_get_rect(&old_rect, old_state); - evdi_cursor_atomic_get_rect(&rect, state); - - evdi_painter_mark_dirty(evdi, &old_rect); - } else { - rect = evdi_painter_framebuffer_size(evdi->painter); + { + struct evdi_color_data color_snap; + const bool color_active = + evdi_color_transform_snapshot(&evdi->color, + &color_snap); + /* SW blend when events off, or when colour forces tint. */ + const bool sw_cursor = + !evdi->cursor_events_enabled || color_active; + + if (sw_cursor) { + if (fb != NULL) { + if (efb->obj->allow_sw_cursor_rect_updates) { + evdi_cursor_atomic_get_rect(&old_rect, + old_state); + evdi_cursor_atomic_get_rect(&rect, state); + + evdi_painter_mark_dirty(evdi, &old_rect); + } else { + rect = evdi_painter_framebuffer_size( + evdi->painter); + } + evdi_painter_mark_dirty(evdi, &rect); } - evdi_painter_mark_dirty(evdi, &rect); + /* Keep DLM HW cursor hidden while colour is active. */ + if (evdi->cursor_events_enabled && cursor_changed) + evdi_painter_send_cursor_set(evdi->painter, + evdi->cursor); + return; } - return; } if (cursor_changed) diff --git a/module/evdi_painter.c b/module/evdi_painter.c index fab4327..de9fdef 100644 --- a/module/evdi_painter.c +++ b/module/evdi_painter.c @@ -236,7 +236,9 @@ static int copy_primary_pixels(struct evdi_framebuffer *efb, struct drm_framebuffer *fb = &efb->base; struct drm_clip_rect *r; const bool swap_rb = evdi_format_swaps_rb(fb->format->format); + /* Per-grab row — never share freable storage across concurrent GRABPIX. */ char *scratch_row = NULL; + int ret = 0; EVDI_CHECKPT(); @@ -246,7 +248,9 @@ static int copy_primary_pixels(struct evdi_framebuffer *efb, #endif if (color->active) { - scratch_row = vmalloc(max_x * 4); + if (max_x <= 0) + return 0; + scratch_row = kvmalloc((size_t)max_x * 4, GFP_KERNEL); if (!scratch_row) return -ENOMEM; } @@ -264,8 +268,8 @@ static int copy_primary_pixels(struct evdi_framebuffer *efb, /* rect size may correspond to previous resolution */ if (max_x < r->x2 || max_y < r->y2) { EVDI_WARN("Rect size beyond expected dimensions\n"); - vfree(scratch_row); - return -EFAULT; + ret = -EFAULT; + goto out; } EVDI_VERBOSE("copy rect %d,%d-%d,%d\n", r->x1, r->y1, r->x2, @@ -280,11 +284,12 @@ static int copy_primary_pixels(struct evdi_framebuffer *efb, evdi_color_transform_apply_row(color, scratch_row, byte_span / 4, swap_rb); if (copy_to_user(dst, scratch_row, byte_span)) { - vfree(scratch_row); - return -EFAULT; + ret = -EFAULT; + goto out; } } else if (copy_to_user(dst, src, byte_span)) { - return -EFAULT; + ret = -EFAULT; + goto out; } src += fb->pitches[0]; @@ -292,8 +297,9 @@ static int copy_primary_pixels(struct evdi_framebuffer *efb, } } - vfree(scratch_row); - return 0; +out: + kvfree(scratch_row); + return ret; } static void copy_cursor_pixels(struct evdi_framebuffer *efb, @@ -501,6 +507,10 @@ static struct drm_pending_event *create_cursor_set_event( { struct evdi_event_cursor_set_pending *event; struct evdi_gem_object *eobj = NULL; + struct evdi_device *evdi = painter->drm_device ? + painter->drm_device->dev_private : NULL; + struct evdi_color_data color_snap; + bool color_active = false; event = kzalloc_obj(*event, GFP_KERNEL); if (!event) { @@ -511,6 +521,10 @@ static struct drm_pending_event *create_cursor_set_event( event->cursor_set.base.type = DRM_EVDI_EVENT_CURSOR_SET; event->cursor_set.base.length = sizeof(event->cursor_set); + if (evdi) + color_active = evdi_color_transform_snapshot(&evdi->color, + &color_snap); + evdi_cursor_lock(cursor); event->cursor_set.enabled = evdi_cursor_enabled(cursor); evdi_cursor_hotpoint(cursor, &event->cursor_set.hot_x, @@ -529,6 +543,16 @@ static struct drm_pending_event *create_cursor_set_event( event->cursor_set.enabled = false; event->cursor_set.buffer_length = 0; } + /* + * Hardware cursor events expose the untinted GEM. While software + * colour is active, hide the HW cursor so GRABPIX SW-blends a tinted + * one instead (see grabpix_ioctl). + */ + if (color_active) { + event->cursor_set.enabled = false; + event->cursor_set.buffer_handle = 0; + event->cursor_set.buffer_length = 0; + } evdi_cursor_unlock(cursor); event->base.event = &event->cursor_set.base; @@ -1213,7 +1237,12 @@ int evdi_painter_grabpix_ioctl(struct drm_device *drm_dev, void *data, cmd->buf_width, cmd->buf_height, &color); - if (err == 0 && !evdi->cursor_events_enabled) + /* + * Cursor events hand DLM an untinted GEM. When colour is active, force + * the software blend path (which applies the same transform) instead. + * create_cursor_set_event hides the HW cursor so DLM does not double-draw. + */ + if (err == 0 && (!evdi->cursor_events_enabled || color.active)) copy_cursor_pixels(efb, cmd->buffer, cmd->buf_byte_stride, @@ -1567,6 +1596,9 @@ int evdi_painter_enable_cursor_events_ioctl(struct drm_device *drm_dev, void *da struct drm_evdi_enable_cursor_events *cmd = data; evdi->cursor_events_enabled = cmd->enable; + /* Re-advertise cursor so colour-active force-hide / restore applies. */ + if (evdi->painter && evdi->cursor) + evdi_painter_send_cursor_set(evdi->painter, evdi->cursor); return 0; } diff --git a/module/evdi_params.c b/module/evdi_params.c index 8fa0d14..93eb341 100644 --- a/module/evdi_params.c +++ b/module/evdi_params.c @@ -7,12 +7,14 @@ * more details. */ +#include #include #include #include #include "evdi_params.h" #include "evdi_debug.h" +#include "evdi_color.h" unsigned int evdi_loglevel __read_mostly = EVDI_LOGLEVEL_INFO; unsigned short int evdi_initial_device_count __read_mostly; @@ -25,9 +27,23 @@ module_param_named(initial_device_count, evdi_initial_device_count, ushort, 0644); MODULE_PARM_DESC(initial_device_count, "Initial DRM device count (default: 0)"); -module_param_named(color_props, evdi_color_props, charp, 0644); +module_param_named(color_props, evdi_color_props, charp, 0444); MODULE_PARM_DESC(color_props, - "Colour properties to advertise: both, gamma, or ctm (default: both)"); + "Load-time DRM colour props: both, gamma, or ctm (default both). " + "Requires module reload. Mutter uses GAMMA_LUT if present, else CTM."); + +static int color_status_get(char *buffer, const struct kernel_param *kp) +{ + return evdi_color_format_status(buffer, PAGE_SIZE); +} + +static const struct kernel_param_ops color_status_ops = { + .get = color_status_get, +}; + +module_param_cb(color_status, &color_status_ops, NULL, 0444); +MODULE_PARM_DESC(color_status, + "Read-only: advertised mode and per-card effective transform"); bool evdi_color_props_has_gamma(void) { @@ -35,7 +51,6 @@ bool evdi_color_props_has_gamma(void) return true; if (!strcmp(evdi_color_props, "ctm")) return false; - /* both, gamma, or unknown → offer gamma */ return true; } @@ -45,7 +60,5 @@ bool evdi_color_props_has_ctm(void) return true; if (!strcmp(evdi_color_props, "gamma")) return false; - /* both, ctm, or unknown → offer CTM */ return true; } - diff --git a/module/evdi_params.h b/module/evdi_params.h index b6c0729..d5a746f 100644 --- a/module/evdi_params.h +++ b/module/evdi_params.h @@ -13,14 +13,17 @@ extern unsigned int evdi_loglevel; extern unsigned short int evdi_initial_device_count; /* - * Which DRM colour properties to advertise (compositors pick Night Light path - * from what is present). Set at module load, e.g.: + * color_props — load-time only (read at CRTC init). Reload the module to change. + * + * both (default) — advertise GAMMA_LUT + CTM + * gamma — GAMMA_LUT only + * ctm — CTM only + * * modprobe evdi color_props=ctm - * EVDI_COLOR_PROPS=gamma sudo ./scripts/install.sh + * EVDI_COLOR_PROPS=ctm sudo ./scripts/install.sh * - * both — GAMMA_LUT + CTM (default; GNOME prefers GAMMA_LUT) - * gamma — GAMMA_LUT only - * ctm — CTM only (useful to force Mutter CTM path) + * Mutter Night Light: uses GAMMA_LUT if present, else CTM. So color_props=ctm + * makes GNOME send a real CTM (with a Mutter that supports CTM Night Light). */ extern char *evdi_color_props;