Hi,
A while back I had the idea to turn a Raspberry Pi Zero into a $5 USB to HDMI/SDTV/DPI display adapter.
The protocol is open so people are free to make displays implementing it and use this driver, all that's needed is to add a USB vid:pid to the driver for the display.
See the wiki[1] for more information and images for the Raspberry Pi Zero/4.
Changes in this version: - LZ4_compress_default() can return zero, check for that - Fix memory leak in gud_pipe_check() error path (Peter) - Improve debug and error messages (Peter) - Don't pass length in protocol structs (Peter) - Pass USB interface to gud_usb_control_msg() et al. (Peter) - Improve gud_connector_fill_properties() (Peter) - Add GUD_PIXEL_FORMAT_RGB111 (Peter) - Remove GUD_REQ_SET_VERSION (Peter) - Fix DRM_IOCTL_MODE_OBJ_SETPROPERTY and the rotation property - Fix dma-buf import (Thomas)
Note: There are several changes to the protocol.
I plan to apply the patches in a week or so unless something turns out to be broken.
Dependency: drm: Use USB controller's DMA mask when importing dmabufs[2] (currently in drm-misc-fixes but not in drm-misc-next yet, also present in drm-tip and linux-next)
Noralf.
[1] https://github.com/notro/gud/wiki [2] https://patchwork.freedesktop.org/patch/msgid/20210303133229.3288-1-tzimmerm...
Noralf Trønnes (3): drm/uapi: Add USB connector type drm/probe-helper: Check epoch counter in output_poll_execute() drm: Add GUD USB Display driver
MAINTAINERS | 8 + drivers/gpu/drm/Kconfig | 2 + drivers/gpu/drm/Makefile | 1 + drivers/gpu/drm/drm_connector.c | 1 + drivers/gpu/drm/drm_probe_helper.c | 7 +- drivers/gpu/drm/gud/Kconfig | 14 + drivers/gpu/drm/gud/Makefile | 4 + drivers/gpu/drm/gud/gud_connector.c | 729 ++++++++++++++++++++++++++++ drivers/gpu/drm/gud/gud_drv.c | 659 +++++++++++++++++++++++++ drivers/gpu/drm/gud/gud_internal.h | 154 ++++++ drivers/gpu/drm/gud/gud_pipe.c | 535 ++++++++++++++++++++ include/drm/gud.h | 333 +++++++++++++ include/uapi/drm/drm_mode.h | 1 + 13 files changed, 2447 insertions(+), 1 deletion(-) create mode 100644 drivers/gpu/drm/gud/Kconfig create mode 100644 drivers/gpu/drm/gud/Makefile create mode 100644 drivers/gpu/drm/gud/gud_connector.c create mode 100644 drivers/gpu/drm/gud/gud_drv.c create mode 100644 drivers/gpu/drm/gud/gud_internal.h create mode 100644 drivers/gpu/drm/gud/gud_pipe.c create mode 100644 include/drm/gud.h
Add a connector type for USB connected display panels.
Some examples of what current userspace will name the connector: - Weston: "UNNAMED-%d" - Mutter: "Unknown20-%d" - X: "Unknown20-%d"
v2: - Update drm_connector_enum_list - Add examples to commit message
Acked-by: Daniel Vetter daniel.vetter@ffwll.ch Signed-off-by: Noralf Trønnes noralf@tronnes.org --- drivers/gpu/drm/drm_connector.c | 1 + include/uapi/drm/drm_mode.h | 1 + 2 files changed, 2 insertions(+)
diff --git a/drivers/gpu/drm/drm_connector.c b/drivers/gpu/drm/drm_connector.c index 717c4e7271b0..52712daed3bc 100644 --- a/drivers/gpu/drm/drm_connector.c +++ b/drivers/gpu/drm/drm_connector.c @@ -94,6 +94,7 @@ static struct drm_conn_prop_enum_list drm_connector_enum_list[] = { { DRM_MODE_CONNECTOR_DPI, "DPI" }, { DRM_MODE_CONNECTOR_WRITEBACK, "Writeback" }, { DRM_MODE_CONNECTOR_SPI, "SPI" }, + { DRM_MODE_CONNECTOR_USB, "USB" }, };
void drm_connector_ida_init(void) diff --git a/include/uapi/drm/drm_mode.h b/include/uapi/drm/drm_mode.h index fed66a03c7ae..33024cc5d26e 100644 --- a/include/uapi/drm/drm_mode.h +++ b/include/uapi/drm/drm_mode.h @@ -367,6 +367,7 @@ enum drm_mode_subconnector { #define DRM_MODE_CONNECTOR_DPI 17 #define DRM_MODE_CONNECTOR_WRITEBACK 18 #define DRM_MODE_CONNECTOR_SPI 19 +#define DRM_MODE_CONNECTOR_USB 20
/** * struct drm_mode_get_connector - Get connector metadata.
drm_helper_hpd_irq_event() checks the epoch counter to determine connector status change. This was introduced in commit 5186421cbfe2 ("drm: Introduce epoch counter to drm_connector"). Do the same for output_poll_execute() so it can detect other changes beside connection status value changes.
v2: - Add Fixes tag (Daniel)
Fixes: 5186421cbfe2 ("drm: Introduce epoch counter to drm_connector") Reviewed-by: Daniel Vetter daniel.vetter@ffwll.ch Signed-off-by: Noralf Trønnes noralf@tronnes.org --- drivers/gpu/drm/drm_probe_helper.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-)
diff --git a/drivers/gpu/drm/drm_probe_helper.c b/drivers/gpu/drm/drm_probe_helper.c index d6017726cc2a..e5432dcf6999 100644 --- a/drivers/gpu/drm/drm_probe_helper.c +++ b/drivers/gpu/drm/drm_probe_helper.c @@ -623,6 +623,7 @@ static void output_poll_execute(struct work_struct *work) struct drm_connector_list_iter conn_iter; enum drm_connector_status old_status; bool repoll = false, changed; + u64 old_epoch_counter;
if (!dev->mode_config.poll_enabled) return; @@ -659,8 +660,9 @@ static void output_poll_execute(struct work_struct *work)
repoll = true;
+ old_epoch_counter = connector->epoch_counter; connector->status = drm_helper_probe_detect(connector, NULL, false); - if (old_status != connector->status) { + if (old_epoch_counter != connector->epoch_counter) { const char *old, *new;
/* @@ -689,6 +691,9 @@ static void output_poll_execute(struct work_struct *work) connector->base.id, connector->name, old, new); + DRM_DEBUG_KMS("[CONNECTOR:%d:%s] epoch counter %llu -> %llu\n", + connector->base.id, connector->name, + old_epoch_counter, connector->epoch_counter);
changed = true; }
This adds a USB display driver with the intention that it can be used with future USB interfaced low end displays/adapters. The Linux gadget device driver will serve as the canonical device implementation.
The following DRM properties are supported: - Plane rotation - Connector TV properties
There is also support for backlight brightness exposed as a backlight device.
Display modes can be made available to the host driver either as DRM display modes or through EDID. If both are present, EDID is just passed on to userspace.
Performance is preferred over color depth, so if the device supports RGB565, DRM_CAP_DUMB_PREFERRED_DEPTH will return 16.
If the device transfer buffer can't fit an uncompressed framebuffer update, the update is split up into parts that do fit.
Optimal user experience is achieved by providing damage reports either by setting FB_DAMAGE_CLIPS on pageflips or calling DRM_IOCTL_MODE_DIRTYFB.
LZ4 compression is used if the device supports it.
The driver supports a one bit monochrome transfer format: R1. This is not implemented in the gadget driver. It is added in preparation for future monochrome e-ink displays.
The driver is MIT licensed to smooth the path for any BSD port of the driver.
v2: - Use devm_drm_dev_alloc() and drmm_mode_config_init() - drm_fbdev_generic_setup: Use preferred_bpp=0, 16 was a copy paste error - The drm_backlight_helper is dropped, copy in the code - Support protocol version backwards compatibility for device
v3: - Use donated Openmoko USB pid - Use direct compression from framebuffer when pitch matches, not only on full frames, so split updates can benefit - Use __le16 in struct gud_drm_req_get_connector_status - Set edid property when the device only provides edid - Clear compression fields in struct gud_drm_req_set_buffer - Fix protocol version negotiation - Remove mode->vrefresh, it's calculated
v4: - Drop the status req polling which was a workaround for something that turned out to be a dwc2 udc driver problem - Add a flag for the Linux gadget to require a status request on SET operations. Other devices will only get status req on STALL errors - Use protocol specific error codes (Peter) - Add a flag for devices that want to receive the entire framebuffer on each flush (Lubomir) - Retry a failed framebuffer flush - If mode has changed wait for worker and clear pending damage before queuing up new damage, fb width/height might have changed - Increase error counter on bulk transfer failures - Use DRM_MODE_CONNECTOR_USB - Handle R1 kmalloc error (Peter) - Don't try and replicate the USB get descriptor request standard for the display descriptor (Peter) - Make max_buffer_size optional (Peter), drop the pow2 requirement since it's not necessary anymore. - Don't pre-alloc a control request buffer, it was only 4k - Let gud.h describe the whole protocol explicitly and don't let DRM leak into it (Peter) - Drop display mode .hskew and .vscan from the protocol - Shorten names: s/GUD_DRM_/GUD_/ s/gud_drm_/gud_/ (Peter) - Fix gud_pipe_check() connector picking when switching connector - Drop gud_drm_driver_gem_create_object() cached is default now - Retrieve USB device from struct drm_device.dev instead of keeping a pointer - Honour fb->offsets[0] - Fix mode fetching when connector status is forced - Check EDID length reported by the device - Use drm_do_get_edid() so userspace can overrride EDID - Set epoch counter to signal connector status change - gud_drm_driver can be const now
v5: - GUD_DRM_FORMAT_R1: Use non-human ascii values (Daniel) - Change name to: GUD USB Display (Thomas, Simon) - Change one __u32 -> __le32 in protocol header - Always log fb flush errors, unless the previous one failed - Run backlight update in a worker to avoid upsetting lockdep (Daniel) - Drop backlight_ops.get_brightness, there's no readback from the device so it doesn't really add anything. - Set dma mask, needed by dma-buf importers
v6: - Use obj-y in Makefile (Peter) - Fix missing le32_to_cpu() when using GUD_DISPLAY_MAGIC (Peter) - Set initial brightness on backlight device
v7: - LZ4_compress_default() can return zero, check for that - Fix memory leak in gud_pipe_check() error path (Peter) - Improve debug and error messages (Peter) - Don't pass length in protocol structs (Peter) - Pass USB interface to gud_usb_control_msg() et al. (Peter) - Improve gud_connector_fill_properties() (Peter) - Add GUD_PIXEL_FORMAT_RGB111 (Peter) - Remove GUD_REQ_SET_VERSION (Peter) - Fix DRM_IOCTL_MODE_OBJ_SETPROPERTY and the rotation property - Fix dma-buf import (Thomas)
Cc: Peter Stuge peter@stuge.se Cc: Lubomir Rintel lkundrak@v3.sk Acked-by: Daniel Vetter daniel.vetter@ffwll.ch Signed-off-by: Noralf Trønnes noralf@tronnes.org --- MAINTAINERS | 8 + drivers/gpu/drm/Kconfig | 2 + drivers/gpu/drm/Makefile | 1 + drivers/gpu/drm/gud/Kconfig | 14 + drivers/gpu/drm/gud/Makefile | 4 + drivers/gpu/drm/gud/gud_connector.c | 729 ++++++++++++++++++++++++++++ drivers/gpu/drm/gud/gud_drv.c | 659 +++++++++++++++++++++++++ drivers/gpu/drm/gud/gud_internal.h | 154 ++++++ drivers/gpu/drm/gud/gud_pipe.c | 535 ++++++++++++++++++++ include/drm/gud.h | 333 +++++++++++++ 10 files changed, 2439 insertions(+) create mode 100644 drivers/gpu/drm/gud/Kconfig create mode 100644 drivers/gpu/drm/gud/Makefile create mode 100644 drivers/gpu/drm/gud/gud_connector.c create mode 100644 drivers/gpu/drm/gud/gud_drv.c create mode 100644 drivers/gpu/drm/gud/gud_internal.h create mode 100644 drivers/gpu/drm/gud/gud_pipe.c create mode 100644 include/drm/gud.h
diff --git a/MAINTAINERS b/MAINTAINERS index 00112c044608..e7f71ac55f08 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -5525,6 +5525,14 @@ S: Maintained F: Documentation/devicetree/bindings/display/panel/feiyang,fy07024di26a30d.yaml F: drivers/gpu/drm/panel/panel-feiyang-fy07024di26a30d.c
+DRM DRIVER FOR GENERIC USB DISPLAY +M: Noralf Trønnes noralf@tronnes.org +S: Maintained +W: https://github.com/notro/gud/wiki +T: git git://anongit.freedesktop.org/drm/drm-misc +F: drivers/gpu/drm/gud/ +F: include/drm/gud.h + DRM DRIVER FOR GRAIN MEDIA GM12U320 PROJECTORS M: Hans de Goede hdegoede@redhat.com S: Maintained diff --git a/drivers/gpu/drm/Kconfig b/drivers/gpu/drm/Kconfig index 147d61b9674e..6d3f47933e51 100644 --- a/drivers/gpu/drm/Kconfig +++ b/drivers/gpu/drm/Kconfig @@ -388,6 +388,8 @@ source "drivers/gpu/drm/tidss/Kconfig"
source "drivers/gpu/drm/xlnx/Kconfig"
+source "drivers/gpu/drm/gud/Kconfig" + # Keep legacy drivers last
menuconfig DRM_LEGACY diff --git a/drivers/gpu/drm/Makefile b/drivers/gpu/drm/Makefile index 81569009f884..78dd8e12525d 100644 --- a/drivers/gpu/drm/Makefile +++ b/drivers/gpu/drm/Makefile @@ -124,3 +124,4 @@ obj-$(CONFIG_DRM_ASPEED_GFX) += aspeed/ obj-$(CONFIG_DRM_MCDE) += mcde/ obj-$(CONFIG_DRM_TIDSS) += tidss/ obj-y += xlnx/ +obj-y += gud/ diff --git a/drivers/gpu/drm/gud/Kconfig b/drivers/gpu/drm/gud/Kconfig new file mode 100644 index 000000000000..1c8601bf4d91 --- /dev/null +++ b/drivers/gpu/drm/gud/Kconfig @@ -0,0 +1,14 @@ +# SPDX-License-Identifier: GPL-2.0 + +config DRM_GUD + tristate "GUD USB Display" + depends on DRM && USB + select LZ4_COMPRESS + select DRM_KMS_HELPER + select DRM_GEM_SHMEM_HELPER + select BACKLIGHT_CLASS_DEVICE + help + This is a DRM display driver for GUD USB Displays or display + adapters. + + If M is selected the module will be called gud. diff --git a/drivers/gpu/drm/gud/Makefile b/drivers/gpu/drm/gud/Makefile new file mode 100644 index 000000000000..68a1c622cf33 --- /dev/null +++ b/drivers/gpu/drm/gud/Makefile @@ -0,0 +1,4 @@ +# SPDX-License-Identifier: GPL-2.0 + +gud-y := gud_drv.o gud_pipe.o gud_connector.o +obj-$(CONFIG_DRM_GUD) += gud.o diff --git a/drivers/gpu/drm/gud/gud_connector.c b/drivers/gpu/drm/gud/gud_connector.c new file mode 100644 index 000000000000..ec495dcd6122 --- /dev/null +++ b/drivers/gpu/drm/gud/gud_connector.c @@ -0,0 +1,729 @@ +// SPDX-License-Identifier: MIT +/* + * Copyright 2020 Noralf Trønnes + */ + +#include <linux/backlight.h> +#include <linux/workqueue.h> + +#include <drm/drm_atomic.h> +#include <drm/drm_atomic_state_helper.h> +#include <drm/drm_connector.h> +#include <drm/drm_drv.h> +#include <drm/drm_encoder.h> +#include <drm/drm_file.h> +#include <drm/drm_modeset_helper_vtables.h> +#include <drm/drm_print.h> +#include <drm/drm_probe_helper.h> +#include <drm/drm_simple_kms_helper.h> +#include <drm/gud.h> + +#include "gud_internal.h" + +struct gud_connector { + struct drm_connector connector; + struct drm_encoder encoder; + struct backlight_device *backlight; + struct work_struct backlight_work; + + /* Supported properties */ + u16 *properties; + unsigned int num_properties; + + /* Initial gadget tv state if applicable, applied on state reset */ + struct drm_tv_connector_state initial_tv_state; + + /* + * Initial gadget backlight brightness if applicable, applied on state reset. + * The value -ENODEV is used to signal no backlight. + */ + int initial_brightness; +}; + +static inline struct gud_connector *to_gud_connector(struct drm_connector *connector) +{ + return container_of(connector, struct gud_connector, connector); +} + +static void gud_conn_err(struct drm_connector *connector, const char *msg, int ret) +{ + dev_err(connector->dev->dev, "%s: %s (ret=%d)\n", connector->name, msg, ret); +} + +/* + * Use a worker to avoid taking kms locks inside the backlight lock. + * Other display drivers use backlight within their kms locks. + * This avoids inconsistent locking rules, which would upset lockdep. + */ +static void gud_connector_backlight_update_status_work(struct work_struct *work) +{ + struct gud_connector *gconn = container_of(work, struct gud_connector, backlight_work); + struct drm_connector *connector = &gconn->connector; + struct drm_connector_state *connector_state; + struct drm_device *drm = connector->dev; + struct drm_modeset_acquire_ctx ctx; + struct drm_atomic_state *state; + int idx, ret; + + if (!drm_dev_enter(drm, &idx)) + return; + + state = drm_atomic_state_alloc(drm); + if (!state) { + ret = -ENOMEM; + goto exit; + } + + drm_modeset_acquire_init(&ctx, 0); + state->acquire_ctx = &ctx; +retry: + connector_state = drm_atomic_get_connector_state(state, connector); + if (IS_ERR(connector_state)) { + ret = PTR_ERR(connector_state); + goto out; + } + + /* Reuse tv.brightness to avoid having to subclass */ + connector_state->tv.brightness = gconn->backlight->props.brightness; + + ret = drm_atomic_commit(state); +out: + if (ret == -EDEADLK) { + drm_atomic_state_clear(state); + drm_modeset_backoff(&ctx); + goto retry; + } + + drm_atomic_state_put(state); + + drm_modeset_drop_locks(&ctx); + drm_modeset_acquire_fini(&ctx); +exit: + drm_dev_exit(idx); + + if (ret) + dev_err(drm->dev, "Failed to update backlight, err=%d\n", ret); +} + +static int gud_connector_backlight_update_status(struct backlight_device *bd) +{ + struct drm_connector *connector = bl_get_data(bd); + struct gud_connector *gconn = to_gud_connector(connector); + + /* The USB timeout is 5 seconds so use system_long_wq for worst case scenario */ + queue_work(system_long_wq, &gconn->backlight_work); + + return 0; +} + +static const struct backlight_ops gud_connector_backlight_ops = { + .update_status = gud_connector_backlight_update_status, +}; + +static int gud_connector_backlight_register(struct gud_connector *gconn) +{ + struct drm_connector *connector = &gconn->connector; + struct backlight_device *bd; + const char *name; + const struct backlight_properties props = { + .type = BACKLIGHT_RAW, + .scale = BACKLIGHT_SCALE_NON_LINEAR, + .max_brightness = 100, + .brightness = gconn->initial_brightness, + }; + + name = kasprintf(GFP_KERNEL, "card%d-%s-backlight", + connector->dev->primary->index, connector->name); + if (!name) + return -ENOMEM; + + bd = backlight_device_register(name, connector->kdev, connector, + &gud_connector_backlight_ops, &props); + kfree(name); + if (IS_ERR(bd)) + return PTR_ERR(bd); + + gconn->backlight = bd; + + return 0; +} + +static int gud_connector_detect(struct drm_connector *connector, + struct drm_modeset_acquire_ctx *ctx, bool force) +{ + struct gud_device *gdrm = to_gud_device(connector->dev); + int idx, ret; + u8 status; + + if (!drm_dev_enter(connector->dev, &idx)) + return connector_status_disconnected; + + if (force) { + ret = gud_usb_set(gdrm, GUD_REQ_SET_CONNECTOR_FORCE_DETECT, + connector->index, NULL, 0); + if (ret) { + ret = connector_status_unknown; + goto exit; + } + } + + ret = gud_usb_get_u8(gdrm, GUD_REQ_GET_CONNECTOR_STATUS, connector->index, &status); + if (ret) { + ret = connector_status_unknown; + goto exit; + } + + switch (status & GUD_CONNECTOR_STATUS_CONNECTED_MASK) { + case GUD_CONNECTOR_STATUS_DISCONNECTED: + ret = connector_status_disconnected; + break; + case GUD_CONNECTOR_STATUS_CONNECTED: + ret = connector_status_connected; + break; + default: + ret = connector_status_unknown; + break; + }; + + if (status & GUD_CONNECTOR_STATUS_CHANGED) + connector->epoch_counter += 1; +exit: + drm_dev_exit(idx); + + return ret; +} + +struct gud_connector_get_edid_ctx { + void *buf; + size_t len; + bool edid_override; +}; + +static int gud_connector_get_edid_block(void *data, u8 *buf, unsigned int block, size_t len) +{ + struct gud_connector_get_edid_ctx *ctx = data; + size_t start = block * EDID_LENGTH; + + ctx->edid_override = false; + + if (start + len > ctx->len) + return -1; + + memcpy(buf, ctx->buf + start, len); + + return 0; +} + +static int gud_connector_get_modes(struct drm_connector *connector) +{ + struct gud_device *gdrm = to_gud_device(connector->dev); + struct gud_display_mode_req *reqmodes = NULL; + struct gud_connector_get_edid_ctx edid_ctx; + unsigned int i, num_modes = 0; + struct edid *edid = NULL; + int idx, ret; + + if (!drm_dev_enter(connector->dev, &idx)) + return 0; + + edid_ctx.edid_override = true; + edid_ctx.buf = kmalloc(GUD_CONNECTOR_MAX_EDID_LEN, GFP_KERNEL); + if (!edid_ctx.buf) + goto out; + + ret = gud_usb_get(gdrm, GUD_REQ_GET_CONNECTOR_EDID, connector->index, + edid_ctx.buf, GUD_CONNECTOR_MAX_EDID_LEN); + if (ret > 0 && ret % EDID_LENGTH) { + gud_conn_err(connector, "Invalid EDID size", ret); + } else if (ret > 0) { + edid_ctx.len = ret; + edid = drm_do_get_edid(connector, gud_connector_get_edid_block, &edid_ctx); + } + + kfree(edid_ctx.buf); + drm_connector_update_edid_property(connector, edid); + + if (edid && edid_ctx.edid_override) + goto out; + + reqmodes = kmalloc_array(GUD_CONNECTOR_MAX_NUM_MODES, sizeof(*reqmodes), GFP_KERNEL); + if (!reqmodes) + goto out; + + ret = gud_usb_get(gdrm, GUD_REQ_GET_CONNECTOR_MODES, connector->index, + reqmodes, GUD_CONNECTOR_MAX_NUM_MODES * sizeof(*reqmodes)); + if (ret <= 0) + goto out; + if (ret % sizeof(*reqmodes)) { + gud_conn_err(connector, "Invalid display mode array size", ret); + goto out; + } + + num_modes = ret / sizeof(*reqmodes); + + for (i = 0; i < num_modes; i++) { + struct drm_display_mode *mode; + + mode = drm_mode_create(connector->dev); + if (!mode) { + num_modes = i; + goto out; + } + + gud_to_display_mode(mode, &reqmodes[i]); + drm_mode_probed_add(connector, mode); + } +out: + if (!num_modes) + num_modes = drm_add_edid_modes(connector, edid); + + kfree(reqmodes); + kfree(edid); + drm_dev_exit(idx); + + return num_modes; +} + +static int gud_connector_atomic_check(struct drm_connector *connector, + struct drm_atomic_state *state) +{ + struct drm_connector_state *new_state; + struct drm_crtc_state *new_crtc_state; + struct drm_connector_state *old_state; + + new_state = drm_atomic_get_new_connector_state(state, connector); + if (!new_state->crtc) + return 0; + + old_state = drm_atomic_get_old_connector_state(state, connector); + new_crtc_state = drm_atomic_get_new_crtc_state(state, new_state->crtc); + + if (old_state->tv.margins.left != new_state->tv.margins.left || + old_state->tv.margins.right != new_state->tv.margins.right || + old_state->tv.margins.top != new_state->tv.margins.top || + old_state->tv.margins.bottom != new_state->tv.margins.bottom || + old_state->tv.mode != new_state->tv.mode || + old_state->tv.brightness != new_state->tv.brightness || + old_state->tv.contrast != new_state->tv.contrast || + old_state->tv.flicker_reduction != new_state->tv.flicker_reduction || + old_state->tv.overscan != new_state->tv.overscan || + old_state->tv.saturation != new_state->tv.saturation || + old_state->tv.hue != new_state->tv.hue) + new_crtc_state->connectors_changed = true; + + return 0; +} + +static const struct drm_connector_helper_funcs gud_connector_helper_funcs = { + .detect_ctx = gud_connector_detect, + .get_modes = gud_connector_get_modes, + .atomic_check = gud_connector_atomic_check, +}; + +static int gud_connector_late_register(struct drm_connector *connector) +{ + struct gud_connector *gconn = to_gud_connector(connector); + + if (gconn->initial_brightness < 0) + return 0; + + return gud_connector_backlight_register(gconn); +} + +static void gud_connector_early_unregister(struct drm_connector *connector) +{ + struct gud_connector *gconn = to_gud_connector(connector); + + backlight_device_unregister(gconn->backlight); + cancel_work_sync(&gconn->backlight_work); +} + +static void gud_connector_destroy(struct drm_connector *connector) +{ + struct gud_connector *gconn = to_gud_connector(connector); + + drm_connector_cleanup(connector); + kfree(gconn->properties); + kfree(gconn); +} + +static void gud_connector_reset(struct drm_connector *connector) +{ + struct gud_connector *gconn = to_gud_connector(connector); + + drm_atomic_helper_connector_reset(connector); + connector->state->tv = gconn->initial_tv_state; + /* Set margins from command line */ + drm_atomic_helper_connector_tv_reset(connector); + if (gconn->initial_brightness >= 0) + connector->state->tv.brightness = gconn->initial_brightness; +} + +static const struct drm_connector_funcs gud_connector_funcs = { + .fill_modes = drm_helper_probe_single_connector_modes, + .late_register = gud_connector_late_register, + .early_unregister = gud_connector_early_unregister, + .destroy = gud_connector_destroy, + .reset = gud_connector_reset, + .atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state, + .atomic_destroy_state = drm_atomic_helper_connector_destroy_state, +}; + +/* + * The tv.mode property is shared among the connectors and its enum names are + * driver specific. This means that if more than one connector uses tv.mode, + * the enum names has to be the same. + */ +static int gud_connector_add_tv_mode(struct gud_device *gdrm, struct drm_connector *connector) +{ + size_t buf_len = GUD_CONNECTOR_TV_MODE_MAX_NUM * GUD_CONNECTOR_TV_MODE_NAME_LEN; + const char *modes[GUD_CONNECTOR_TV_MODE_MAX_NUM]; + unsigned int i, num_modes; + char *buf; + int ret; + + buf = kmalloc(buf_len, GFP_KERNEL); + if (!buf) + return -ENOMEM; + + ret = gud_usb_get(gdrm, GUD_REQ_GET_CONNECTOR_TV_MODE_VALUES, + connector->index, buf, buf_len); + if (ret < 0) + goto free; + if (!ret || ret % GUD_CONNECTOR_TV_MODE_NAME_LEN) { + ret = -EIO; + goto free; + } + + num_modes = ret / GUD_CONNECTOR_TV_MODE_NAME_LEN; + for (i = 0; i < num_modes; i++) + modes[i] = &buf[i * GUD_CONNECTOR_TV_MODE_NAME_LEN]; + + ret = drm_mode_create_tv_properties(connector->dev, num_modes, modes); +free: + kfree(buf); + if (ret < 0) + gud_conn_err(connector, "Failed to add TV modes", ret); + + return ret; +} + +static struct drm_property * +gud_connector_property_lookup(struct drm_connector *connector, u16 prop) +{ + struct drm_mode_config *config = &connector->dev->mode_config; + + switch (prop) { + case GUD_PROPERTY_TV_LEFT_MARGIN: + return config->tv_left_margin_property; + case GUD_PROPERTY_TV_RIGHT_MARGIN: + return config->tv_right_margin_property; + case GUD_PROPERTY_TV_TOP_MARGIN: + return config->tv_top_margin_property; + case GUD_PROPERTY_TV_BOTTOM_MARGIN: + return config->tv_bottom_margin_property; + case GUD_PROPERTY_TV_MODE: + return config->tv_mode_property; + case GUD_PROPERTY_TV_BRIGHTNESS: + return config->tv_brightness_property; + case GUD_PROPERTY_TV_CONTRAST: + return config->tv_contrast_property; + case GUD_PROPERTY_TV_FLICKER_REDUCTION: + return config->tv_flicker_reduction_property; + case GUD_PROPERTY_TV_OVERSCAN: + return config->tv_overscan_property; + case GUD_PROPERTY_TV_SATURATION: + return config->tv_saturation_property; + case GUD_PROPERTY_TV_HUE: + return config->tv_hue_property; + default: + return ERR_PTR(-EINVAL); + } +} + +static unsigned int *gud_connector_tv_state_val(u16 prop, struct drm_tv_connector_state *state) +{ + switch (prop) { + case GUD_PROPERTY_TV_LEFT_MARGIN: + return &state->margins.left; + case GUD_PROPERTY_TV_RIGHT_MARGIN: + return &state->margins.right; + case GUD_PROPERTY_TV_TOP_MARGIN: + return &state->margins.top; + case GUD_PROPERTY_TV_BOTTOM_MARGIN: + return &state->margins.bottom; + case GUD_PROPERTY_TV_MODE: + return &state->mode; + case GUD_PROPERTY_TV_BRIGHTNESS: + return &state->brightness; + case GUD_PROPERTY_TV_CONTRAST: + return &state->contrast; + case GUD_PROPERTY_TV_FLICKER_REDUCTION: + return &state->flicker_reduction; + case GUD_PROPERTY_TV_OVERSCAN: + return &state->overscan; + case GUD_PROPERTY_TV_SATURATION: + return &state->saturation; + case GUD_PROPERTY_TV_HUE: + return &state->hue; + default: + return ERR_PTR(-EINVAL); + } +} + +static int gud_connector_add_properties(struct gud_device *gdrm, struct gud_connector *gconn) +{ + struct drm_connector *connector = &gconn->connector; + struct drm_device *drm = &gdrm->drm; + struct gud_property_req *properties; + unsigned int i, num_properties; + int ret; + + properties = kcalloc(GUD_CONNECTOR_PROPERTIES_MAX_NUM, sizeof(*properties), GFP_KERNEL); + if (!properties) + return -ENOMEM; + + ret = gud_usb_get(gdrm, GUD_REQ_GET_CONNECTOR_PROPERTIES, connector->index, + properties, GUD_CONNECTOR_PROPERTIES_MAX_NUM * sizeof(*properties)); + if (ret <= 0) + goto out; + if (ret % sizeof(*properties)) { + ret = -EIO; + goto out; + } + + num_properties = ret / sizeof(*properties); + ret = 0; + + gconn->properties = kcalloc(num_properties, sizeof(*gconn->properties), GFP_KERNEL); + if (!gconn->properties) { + ret = -ENOMEM; + goto out; + } + + for (i = 0; i < num_properties; i++) { + u16 prop = le16_to_cpu(properties[i].prop); + u64 val = le64_to_cpu(properties[i].val); + struct drm_property *property; + unsigned int *state_val; + + drm_dbg(drm, "property: %u = %llu(0x%llx)\n", prop, val, val); + + switch (prop) { + case GUD_PROPERTY_TV_LEFT_MARGIN: + fallthrough; + case GUD_PROPERTY_TV_RIGHT_MARGIN: + fallthrough; + case GUD_PROPERTY_TV_TOP_MARGIN: + fallthrough; + case GUD_PROPERTY_TV_BOTTOM_MARGIN: + ret = drm_mode_create_tv_margin_properties(drm); + if (ret) + goto out; + break; + case GUD_PROPERTY_TV_MODE: + ret = gud_connector_add_tv_mode(gdrm, connector); + if (ret) + goto out; + break; + case GUD_PROPERTY_TV_BRIGHTNESS: + fallthrough; + case GUD_PROPERTY_TV_CONTRAST: + fallthrough; + case GUD_PROPERTY_TV_FLICKER_REDUCTION: + fallthrough; + case GUD_PROPERTY_TV_OVERSCAN: + fallthrough; + case GUD_PROPERTY_TV_SATURATION: + fallthrough; + case GUD_PROPERTY_TV_HUE: + /* This is a no-op if already added. */ + ret = drm_mode_create_tv_properties(drm, 0, NULL); + if (ret) + goto out; + break; + case GUD_PROPERTY_BACKLIGHT_BRIGHTNESS: + if (val > 100) { + ret = -EINVAL; + goto out; + } + gconn->initial_brightness = val; + break; + default: + /* New ones might show up in future devices, skip those we don't know. */ + drm_dbg(drm, "Ignoring unknown property: %u\n", prop); + continue; + } + + gconn->properties[gconn->num_properties++] = prop; + + if (prop == GUD_PROPERTY_BACKLIGHT_BRIGHTNESS) + continue; /* not a DRM property */ + + property = gud_connector_property_lookup(connector, prop); + if (WARN_ON(IS_ERR(property))) + continue; + + state_val = gud_connector_tv_state_val(prop, &gconn->initial_tv_state); + if (WARN_ON(IS_ERR(state_val))) + continue; + + *state_val = val; + drm_object_attach_property(&connector->base, property, 0); + } +out: + kfree(properties); + + return ret; +} + +int gud_connector_fill_properties(struct drm_connector_state *connector_state, + struct gud_property_req *properties) +{ + struct gud_connector *gconn = to_gud_connector(connector_state->connector); + unsigned int i; + + for (i = 0; i < gconn->num_properties; i++) { + u16 prop = gconn->properties[i]; + u64 val; + + if (prop == GUD_PROPERTY_BACKLIGHT_BRIGHTNESS) { + val = connector_state->tv.brightness; + } else { + unsigned int *state_val; + + state_val = gud_connector_tv_state_val(prop, &connector_state->tv); + if (WARN_ON_ONCE(IS_ERR(state_val))) + return PTR_ERR(state_val); + + val = *state_val; + } + + properties[i].prop = cpu_to_le16(prop); + properties[i].val = cpu_to_le64(val); + } + + return gconn->num_properties; +} + +static int gud_connector_create(struct gud_device *gdrm, unsigned int index, + struct gud_connector_descriptor_req *desc) +{ + struct drm_device *drm = &gdrm->drm; + struct gud_connector *gconn; + struct drm_connector *connector; + struct drm_encoder *encoder; + int ret, connector_type; + u32 flags; + + gconn = kzalloc(sizeof(*gconn), GFP_KERNEL); + if (!gconn) + return -ENOMEM; + + INIT_WORK(&gconn->backlight_work, gud_connector_backlight_update_status_work); + gconn->initial_brightness = -ENODEV; + flags = le32_to_cpu(desc->flags); + connector = &gconn->connector; + + drm_dbg(drm, "Connector: index=%u type=%u flags=0x%x\n", index, desc->connector_type, flags); + + switch (desc->connector_type) { + case GUD_CONNECTOR_TYPE_PANEL: + connector_type = DRM_MODE_CONNECTOR_USB; + break; + case GUD_CONNECTOR_TYPE_VGA: + connector_type = DRM_MODE_CONNECTOR_VGA; + break; + case GUD_CONNECTOR_TYPE_DVI: + connector_type = DRM_MODE_CONNECTOR_DVID; + break; + case GUD_CONNECTOR_TYPE_COMPOSITE: + connector_type = DRM_MODE_CONNECTOR_Composite; + break; + case GUD_CONNECTOR_TYPE_SVIDEO: + connector_type = DRM_MODE_CONNECTOR_SVIDEO; + break; + case GUD_CONNECTOR_TYPE_COMPONENT: + connector_type = DRM_MODE_CONNECTOR_Component; + break; + case GUD_CONNECTOR_TYPE_DISPLAYPORT: + connector_type = DRM_MODE_CONNECTOR_DisplayPort; + break; + case GUD_CONNECTOR_TYPE_HDMI: + connector_type = DRM_MODE_CONNECTOR_HDMIA; + break; + default: /* future types */ + connector_type = DRM_MODE_CONNECTOR_USB; + break; + }; + + drm_connector_helper_add(connector, &gud_connector_helper_funcs); + ret = drm_connector_init(drm, connector, &gud_connector_funcs, connector_type); + if (ret) { + kfree(connector); + return ret; + } + + if (WARN_ON(connector->index != index)) + return -EINVAL; + + if (flags & GUD_CONNECTOR_FLAGS_POLL_STATUS) + connector->polled = (DRM_CONNECTOR_POLL_CONNECT | DRM_CONNECTOR_POLL_DISCONNECT); + if (flags & GUD_CONNECTOR_FLAGS_INTERLACE) + connector->interlace_allowed = true; + if (flags & GUD_CONNECTOR_FLAGS_DOUBLESCAN) + connector->doublescan_allowed = true; + + ret = gud_connector_add_properties(gdrm, gconn); + if (ret) { + gud_conn_err(connector, "Failed to add properties", ret); + return ret; + } + + /* The first connector is attached to the existing simple pipe encoder */ + if (!connector->index) { + encoder = &gdrm->pipe.encoder; + } else { + encoder = &gconn->encoder; + + ret = drm_simple_encoder_init(drm, encoder, DRM_MODE_ENCODER_NONE); + if (ret) + return ret; + + encoder->possible_crtcs = 1; + } + + return drm_connector_attach_encoder(connector, encoder); +} + +int gud_get_connectors(struct gud_device *gdrm) +{ + struct gud_connector_descriptor_req *descs; + unsigned int i, num_connectors; + int ret; + + descs = kmalloc_array(GUD_CONNECTORS_MAX_NUM, sizeof(*descs), GFP_KERNEL); + if (!descs) + return -ENOMEM; + + ret = gud_usb_get(gdrm, GUD_REQ_GET_CONNECTORS, 0, + descs, GUD_CONNECTORS_MAX_NUM * sizeof(descs)); + if (ret < 0) + goto free; + if (!ret || ret % sizeof(*descs)) { + ret = -EIO; + goto free; + } + + num_connectors = ret / sizeof(*descs); + + for (i = 0; i < num_connectors; i++) { + ret = gud_connector_create(gdrm, i, &descs[i]); + if (ret) + goto free; + } +free: + kfree(descs); + + return ret; +} diff --git a/drivers/gpu/drm/gud/gud_drv.c b/drivers/gpu/drm/gud/gud_drv.c new file mode 100644 index 000000000000..2e3056e4d271 --- /dev/null +++ b/drivers/gpu/drm/gud/gud_drv.c @@ -0,0 +1,659 @@ +// SPDX-License-Identifier: MIT +/* + * Copyright 2020 Noralf Trønnes + */ + +#include <linux/dma-buf.h> +#include <linux/dma-mapping.h> +#include <linux/lz4.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/string_helpers.h> +#include <linux/usb.h> +#include <linux/vmalloc.h> +#include <linux/workqueue.h> + +#include <drm/drm_atomic_helper.h> +#include <drm/drm_damage_helper.h> +#include <drm/drm_debugfs.h> +#include <drm/drm_drv.h> +#include <drm/drm_fb_helper.h> +#include <drm/drm_fourcc.h> +#include <drm/drm_gem_atomic_helper.h> +#include <drm/drm_gem_framebuffer_helper.h> +#include <drm/drm_gem_shmem_helper.h> +#include <drm/drm_managed.h> +#include <drm/drm_print.h> +#include <drm/drm_probe_helper.h> +#include <drm/drm_simple_kms_helper.h> +#include <drm/gud.h> + +#include "gud_internal.h" + +/* Only used internally */ +static const struct drm_format_info gud_drm_format_r1 = { + .format = GUD_DRM_FORMAT_R1, + .num_planes = 1, + .char_per_block = { 1, 0, 0 }, + .block_w = { 8, 0, 0 }, + .block_h = { 1, 0, 0 }, + .hsub = 1, + .vsub = 1, +}; + +static const struct drm_format_info gud_drm_format_rgb111 = { + .format = GUD_DRM_FORMAT_RGB111, + .num_planes = 1, + .char_per_block = { 1, 0, 0 }, + .block_w = { 2, 0, 0 }, + .block_h = { 1, 0, 0 }, + .hsub = 1, + .vsub = 1, +}; + +static int gud_usb_control_msg(struct usb_interface *intf, bool in, + u8 request, u16 value, void *buf, size_t len) +{ + u8 requesttype = USB_TYPE_VENDOR | USB_RECIP_INTERFACE; + u8 ifnum = intf->cur_altsetting->desc.bInterfaceNumber; + struct usb_device *usb = interface_to_usbdev(intf); + unsigned int pipe; + + if (len && !buf) + return -EINVAL; + + if (in) { + pipe = usb_rcvctrlpipe(usb, 0); + requesttype |= USB_DIR_IN; + } else { + pipe = usb_sndctrlpipe(usb, 0); + requesttype |= USB_DIR_OUT; + } + + return usb_control_msg(usb, pipe, request, requesttype, value, + ifnum, buf, len, USB_CTRL_GET_TIMEOUT); +} + +static int gud_get_display_descriptor(struct usb_interface *intf, + struct gud_display_descriptor_req *desc) +{ + void *buf; + int ret; + + buf = kmalloc(sizeof(*desc), GFP_KERNEL); + if (!buf) + return -ENOMEM; + + ret = gud_usb_control_msg(intf, true, GUD_REQ_GET_DESCRIPTOR, 0, buf, sizeof(*desc)); + memcpy(desc, buf, sizeof(*desc)); + kfree(buf); + if (ret < 0) + return ret; + if (ret != sizeof(*desc)) + return -EIO; + + if (desc->magic != le32_to_cpu(GUD_DISPLAY_MAGIC)) + return -ENODATA; + + DRM_DEV_DEBUG_DRIVER(&intf->dev, + "version=%u flags=0x%x compression=0x%x max_buffer_size=%u\n", + desc->version, le32_to_cpu(desc->flags), desc->compression, + le32_to_cpu(desc->max_buffer_size)); + + if (!desc->version || !desc->max_width || !desc->max_height || + le32_to_cpu(desc->min_width) > le32_to_cpu(desc->max_width) || + le32_to_cpu(desc->min_height) > le32_to_cpu(desc->max_height)) + return -EINVAL; + + return 0; +} + +static int gud_status_to_errno(u8 status) +{ + switch (status) { + case GUD_STATUS_OK: + return 0; + case GUD_STATUS_BUSY: + return -EBUSY; + case GUD_STATUS_REQUEST_NOT_SUPPORTED: + return -EOPNOTSUPP; + case GUD_STATUS_PROTOCOL_ERROR: + return -EPROTO; + case GUD_STATUS_INVALID_PARAMETER: + return -EINVAL; + case GUD_STATUS_ERROR: + return -EREMOTEIO; + default: + return -EREMOTEIO; + } +} + +static int gud_usb_get_status(struct usb_interface *intf) +{ + int ret, status = -EIO; + u8 *buf; + + buf = kmalloc(sizeof(*buf), GFP_KERNEL); + if (!buf) + return -ENOMEM; + + ret = gud_usb_control_msg(intf, true, GUD_REQ_GET_STATUS, 0, buf, sizeof(*buf)); + if (ret == sizeof(*buf)) + status = gud_status_to_errno(*buf); + kfree(buf); + + if (ret < 0) + return ret; + + return status; +} + +static int gud_usb_transfer(struct gud_device *gdrm, bool in, u8 request, u16 index, + void *buf, size_t len) +{ + struct usb_interface *intf = to_usb_interface(gdrm->drm.dev); + int idx, ret; + + drm_dbg(&gdrm->drm, "%s: request=0x%x index=%u len=%zu\n", + in ? "get" : "set", request, index, len); + + if (!drm_dev_enter(&gdrm->drm, &idx)) + return -ENODEV; + + mutex_lock(&gdrm->ctrl_lock); + + ret = gud_usb_control_msg(intf, in, request, index, buf, len); + if (ret == -EPIPE || ((gdrm->flags & GUD_DISPLAY_FLAG_STATUS_ON_SET) && !in && ret >= 0)) { + int status; + + status = gud_usb_get_status(intf); + if (status < 0) { + ret = status; + } else if (ret < 0) { + dev_err_once(gdrm->drm.dev, + "Unexpected status OK for failed transfer\n"); + ret = -EPIPE; + } + } + + if (ret < 0) { + drm_dbg(&gdrm->drm, "ret=%d\n", ret); + gdrm->stats_num_errors++; + } + + mutex_unlock(&gdrm->ctrl_lock); + drm_dev_exit(idx); + + return ret; +} + +/* + * @buf cannot be allocated on the stack. + * Returns number of bytes received or negative error code on failure. + */ +int gud_usb_get(struct gud_device *gdrm, u8 request, u16 index, void *buf, size_t max_len) +{ + return gud_usb_transfer(gdrm, true, request, index, buf, max_len); +} + +/* + * @buf can be allocated on the stack or NULL. + * Returns zero on success or negative error code on failure. + */ +int gud_usb_set(struct gud_device *gdrm, u8 request, u16 index, void *buf, size_t len) +{ + void *trbuf = NULL; + int ret; + + if (buf && len) { + trbuf = kmemdup(buf, len, GFP_KERNEL); + if (!trbuf) + return -ENOMEM; + } + + ret = gud_usb_transfer(gdrm, false, request, index, trbuf, len); + kfree(trbuf); + if (ret < 0) + return ret; + + return ret != len ? -EIO : 0; +} + +/* + * @val can be allocated on the stack. + * Returns zero on success or negative error code on failure. + */ +int gud_usb_get_u8(struct gud_device *gdrm, u8 request, u16 index, u8 *val) +{ + u8 *buf; + int ret; + + buf = kmalloc(sizeof(*val), GFP_KERNEL); + if (!buf) + return -ENOMEM; + + ret = gud_usb_get(gdrm, request, index, buf, sizeof(*val)); + *val = *buf; + kfree(buf); + if (ret < 0) + return ret; + + return ret != sizeof(*val) ? -EIO : 0; +} + +/* Returns zero on success or negative error code on failure. */ +int gud_usb_set_u8(struct gud_device *gdrm, u8 request, u8 val) +{ + return gud_usb_set(gdrm, request, 0, &val, sizeof(val)); +} + +static int gud_get_properties(struct gud_device *gdrm) +{ + struct gud_property_req *properties; + unsigned int i, num_properties; + int ret; + + properties = kcalloc(GUD_PROPERTIES_MAX_NUM, sizeof(*properties), GFP_KERNEL); + if (!properties) + return -ENOMEM; + + ret = gud_usb_get(gdrm, GUD_REQ_GET_PROPERTIES, 0, + properties, GUD_PROPERTIES_MAX_NUM * sizeof(*properties)); + if (ret <= 0) + goto out; + if (ret % sizeof(*properties)) { + ret = -EIO; + goto out; + } + + num_properties = ret / sizeof(*properties); + + gdrm->properties = drmm_kcalloc(&gdrm->drm, num_properties, sizeof(*gdrm->properties), + GFP_KERNEL); + if (!gdrm->properties) { + ret = -ENOMEM; + goto out; + } + + for (i = 0; i < num_properties; i++) { + u16 prop = le16_to_cpu(properties[i].prop); + u64 val = le64_to_cpu(properties[i].val); + + switch (prop) { + case GUD_PROPERTY_ROTATION: + /* + * DRM UAPI matches the protocol so use the value directly, + * but mask out any additions on future devices. + */ + val &= GUD_ROTATION_MASK; + ret = drm_plane_create_rotation_property(&gdrm->pipe.plane, + DRM_MODE_ROTATE_0, val); + break; + default: + /* New ones might show up in future devices, skip those we don't know. */ + drm_dbg(&gdrm->drm, "Ignoring unknown property: %u\n", prop); + continue; + } + + if (ret) + goto out; + + gdrm->properties[gdrm->num_properties++] = prop; + } +out: + kfree(properties); + + return ret; +} + +/* + * FIXME: Dma-buf sharing requires DMA support by the importing device. + * This function is a workaround to make USB devices work as well. + * See todo.rst for how to fix the issue in the dma-buf framework. + */ +static struct drm_gem_object *gud_gem_prime_import(struct drm_device *drm, struct dma_buf *dma_buf) +{ + struct gud_device *gdrm = to_gud_device(drm); + + if (!gdrm->dmadev) + return ERR_PTR(-ENODEV); + + return drm_gem_prime_import_dev(drm, dma_buf, gdrm->dmadev); +} + +static int gud_stats_debugfs(struct seq_file *m, void *data) +{ + struct drm_info_node *node = m->private; + struct gud_device *gdrm = to_gud_device(node->minor->dev); + char buf[10]; + + string_get_size(gdrm->bulk_len, 1, STRING_UNITS_2, buf, sizeof(buf)); + seq_printf(m, "Max buffer size: %s\n", buf); + seq_printf(m, "Number of errors: %u\n", gdrm->stats_num_errors); + + seq_puts(m, "Compression: "); + if (gdrm->compression & GUD_COMPRESSION_LZ4) + seq_puts(m, " lz4"); + if (!gdrm->compression) + seq_puts(m, " none"); + seq_puts(m, "\n"); + + if (gdrm->compression) { + u64 remainder; + u64 ratio = div64_u64_rem(gdrm->stats_length, gdrm->stats_actual_length, + &remainder); + u64 ratio_frac = div64_u64(remainder * 10, gdrm->stats_actual_length); + + seq_printf(m, "Compression ratio: %llu.%llu\n", ratio, ratio_frac); + } + + return 0; +} + +static const struct drm_info_list gud_debugfs_list[] = { + { "stats", gud_stats_debugfs, 0, NULL }, +}; + +static void gud_debugfs_init(struct drm_minor *minor) +{ + drm_debugfs_create_files(gud_debugfs_list, ARRAY_SIZE(gud_debugfs_list), + minor->debugfs_root, minor); +} + +static const struct drm_simple_display_pipe_funcs gud_pipe_funcs = { + .check = gud_pipe_check, + .update = gud_pipe_update, + .prepare_fb = drm_gem_simple_display_pipe_prepare_fb, +}; + +static const struct drm_mode_config_funcs gud_mode_config_funcs = { + .fb_create = drm_gem_fb_create_with_dirty, + .atomic_check = drm_atomic_helper_check, + .atomic_commit = drm_atomic_helper_commit, +}; + +static const u64 gud_pipe_modifiers[] = { + DRM_FORMAT_MOD_LINEAR, + DRM_FORMAT_MOD_INVALID +}; + +DEFINE_DRM_GEM_FOPS(gud_fops); + +static const struct drm_driver gud_drm_driver = { + .driver_features = DRIVER_MODESET | DRIVER_GEM | DRIVER_ATOMIC, + .fops = &gud_fops, + DRM_GEM_SHMEM_DRIVER_OPS, + .gem_prime_import = gud_gem_prime_import, + .debugfs_init = gud_debugfs_init, + + .name = "gud", + .desc = "Generic USB Display", + .date = "20200422", + .major = 1, + .minor = 0, +}; + +static void gud_free_buffers_and_mutex(struct drm_device *drm, void *unused) +{ + struct gud_device *gdrm = to_gud_device(drm); + + vfree(gdrm->compress_buf); + kfree(gdrm->bulk_buf); + mutex_destroy(&gdrm->ctrl_lock); + mutex_destroy(&gdrm->damage_lock); +} + +static int gud_probe(struct usb_interface *intf, const struct usb_device_id *id) +{ + const struct drm_format_info *xrgb8888_emulation_format = NULL; + bool rgb565_supported = false, xrgb8888_supported = false; + unsigned int num_formats_dev, num_formats = 0; + struct usb_endpoint_descriptor *bulk_out; + struct gud_display_descriptor_req desc; + struct device *dev = &intf->dev; + size_t max_buffer_size = 0; + struct gud_device *gdrm; + struct drm_device *drm; + u8 *formats_dev; + u32 *formats; + int ret, i; + + ret = usb_find_bulk_out_endpoint(intf->cur_altsetting, &bulk_out); + if (ret) + return ret; + + ret = gud_get_display_descriptor(intf, &desc); + if (ret) { + DRM_DEV_DEBUG_DRIVER(dev, "Not a display interface: ret=%d\n", ret); + return -ENODEV; + } + + if (desc.version > 1) { + dev_err(dev, "Protocol version %u is not supported\n", desc.version); + return -ENODEV; + } + + gdrm = devm_drm_dev_alloc(dev, &gud_drm_driver, struct gud_device, drm); + if (IS_ERR(gdrm)) + return PTR_ERR(gdrm); + + drm = &gdrm->drm; + drm->mode_config.funcs = &gud_mode_config_funcs; + ret = drmm_mode_config_init(drm); + if (ret) + return ret; + + gdrm->flags = le32_to_cpu(desc.flags); + gdrm->compression = desc.compression & GUD_COMPRESSION_LZ4; + + if (gdrm->flags & GUD_DISPLAY_FLAG_FULL_UPDATE && gdrm->compression) + return -EINVAL; + + mutex_init(&gdrm->ctrl_lock); + mutex_init(&gdrm->damage_lock); + INIT_WORK(&gdrm->work, gud_flush_work); + gud_clear_damage(gdrm); + + ret = drmm_add_action_or_reset(drm, gud_free_buffers_and_mutex, NULL); + if (ret) + return ret; + + drm->mode_config.min_width = le32_to_cpu(desc.min_width); + drm->mode_config.max_width = le32_to_cpu(desc.max_width); + drm->mode_config.min_height = le32_to_cpu(desc.min_height); + drm->mode_config.max_height = le32_to_cpu(desc.max_height); + + formats_dev = devm_kmalloc(dev, GUD_FORMATS_MAX_NUM, GFP_KERNEL); + /* Add room for emulated XRGB8888 */ + formats = devm_kmalloc_array(dev, GUD_FORMATS_MAX_NUM + 1, sizeof(*formats), GFP_KERNEL); + if (!formats_dev || !formats) + return -ENOMEM; + + ret = gud_usb_get(gdrm, GUD_REQ_GET_FORMATS, 0, formats_dev, GUD_FORMATS_MAX_NUM); + if (ret < 0) + return ret; + + num_formats_dev = ret; + for (i = 0; i < num_formats_dev; i++) { + const struct drm_format_info *info; + size_t fmt_buf_size; + u32 format; + + format = gud_to_fourcc(formats_dev[i]); + if (!format) { + drm_dbg(drm, "Unsupported format: 0x%02x\n", formats_dev[i]); + continue; + } + + if (format == GUD_DRM_FORMAT_R1) + info = &gud_drm_format_r1; + else if (format == GUD_DRM_FORMAT_RGB111) + info = &gud_drm_format_rgb111; + else + info = drm_format_info(format); + + switch (format) { + case GUD_DRM_FORMAT_R1: + fallthrough; + case GUD_DRM_FORMAT_RGB111: + xrgb8888_emulation_format = info; + break; + case DRM_FORMAT_RGB565: + rgb565_supported = true; + if (!xrgb8888_emulation_format) + xrgb8888_emulation_format = info; + break; + case DRM_FORMAT_XRGB8888: + xrgb8888_supported = true; + break; + }; + + fmt_buf_size = drm_format_info_min_pitch(info, 0, drm->mode_config.max_width) * + drm->mode_config.max_height; + max_buffer_size = max(max_buffer_size, fmt_buf_size); + + if (format == GUD_DRM_FORMAT_R1) + continue; /* Internal not for userspace */ + + formats[num_formats++] = format; + } + + if (!num_formats && !xrgb8888_emulation_format) { + dev_err(dev, "No supported pixel formats found\n"); + return -EINVAL; + } + + /* Prefer speed over color depth */ + if (rgb565_supported) + drm->mode_config.preferred_depth = 16; + + if (!xrgb8888_supported && xrgb8888_emulation_format) { + gdrm->xrgb8888_emulation_format = xrgb8888_emulation_format; + formats[num_formats++] = DRM_FORMAT_XRGB8888; + } + + if (desc.max_buffer_size) + max_buffer_size = le32_to_cpu(desc.max_buffer_size); +retry: + /* + * Use plain kmalloc here since devm_kmalloc() places struct devres at the beginning + * of the buffer it allocates. This wastes a lot of memory when allocating big buffers. + * Asking for 2M would actually allocate 4M. This would also prevent getting the biggest + * possible buffer potentially leading to split transfers. + */ + gdrm->bulk_buf = kmalloc(max_buffer_size, GFP_KERNEL | __GFP_NOWARN); + if (!gdrm->bulk_buf) { + max_buffer_size = roundup_pow_of_two(max_buffer_size) / 2; + if (max_buffer_size < SZ_512K) + return -ENOMEM; + goto retry; + } + + gdrm->bulk_pipe = usb_sndbulkpipe(interface_to_usbdev(intf), usb_endpoint_num(bulk_out)); + gdrm->bulk_len = max_buffer_size; + + if (gdrm->compression & GUD_COMPRESSION_LZ4) { + gdrm->lz4_comp_mem = devm_kmalloc(dev, LZ4_MEM_COMPRESS, GFP_KERNEL); + if (!gdrm->lz4_comp_mem) + return -ENOMEM; + + gdrm->compress_buf = vmalloc(gdrm->bulk_len); + if (!gdrm->compress_buf) + return -ENOMEM; + } + + ret = drm_simple_display_pipe_init(drm, &gdrm->pipe, &gud_pipe_funcs, + formats, num_formats, + gud_pipe_modifiers, NULL); + if (ret) + return ret; + + devm_kfree(dev, formats); + devm_kfree(dev, formats_dev); + + ret = gud_get_properties(gdrm); + if (ret) { + dev_err(dev, "Failed to get properties (error=%d)\n", ret); + return ret; + } + + drm_plane_enable_fb_damage_clips(&gdrm->pipe.plane); + + ret = gud_get_connectors(gdrm); + if (ret) { + dev_err(dev, "Failed to get connectors (error=%d)\n", ret); + return ret; + } + + drm_mode_config_reset(drm); + + usb_set_intfdata(intf, gdrm); + + gdrm->dmadev = usb_intf_get_dma_device(intf); + if (!gdrm->dmadev) + dev_warn(dev, "buffer sharing not supported"); + + ret = drm_dev_register(drm, 0); + if (ret) { + put_device(gdrm->dmadev); + return ret; + } + + drm_kms_helper_poll_init(drm); + + drm_fbdev_generic_setup(drm, 0); + + return 0; +} + +static void gud_disconnect(struct usb_interface *interface) +{ + struct gud_device *gdrm = usb_get_intfdata(interface); + struct drm_device *drm = &gdrm->drm; + + drm_dbg(drm, "%s:\n", __func__); + + drm_kms_helper_poll_fini(drm); + drm_dev_unplug(drm); + drm_atomic_helper_shutdown(drm); + put_device(gdrm->dmadev); + gdrm->dmadev = NULL; +} + +static int gud_suspend(struct usb_interface *intf, pm_message_t message) +{ + struct gud_device *gdrm = usb_get_intfdata(intf); + + return drm_mode_config_helper_suspend(&gdrm->drm); +} + +static int gud_resume(struct usb_interface *intf) +{ + struct gud_device *gdrm = usb_get_intfdata(intf); + + drm_mode_config_helper_resume(&gdrm->drm); + + return 0; +} + +static const struct usb_device_id gud_id_table[] = { + { USB_DEVICE_INTERFACE_CLASS(0x1d50, 0x614d, USB_CLASS_VENDOR_SPEC) }, + { } +}; + +MODULE_DEVICE_TABLE(usb, gud_id_table); + +static struct usb_driver gud_usb_driver = { + .name = "gud", + .probe = gud_probe, + .disconnect = gud_disconnect, + .id_table = gud_id_table, + .suspend = gud_suspend, + .resume = gud_resume, + .reset_resume = gud_resume, +}; + +module_usb_driver(gud_usb_driver); + +MODULE_AUTHOR("Noralf Trønnes"); +MODULE_LICENSE("Dual MIT/GPL"); diff --git a/drivers/gpu/drm/gud/gud_internal.h b/drivers/gpu/drm/gud/gud_internal.h new file mode 100644 index 000000000000..b3fd2bdd4901 --- /dev/null +++ b/drivers/gpu/drm/gud/gud_internal.h @@ -0,0 +1,154 @@ +/* SPDX-License-Identifier: MIT */ + +#ifndef __LINUX_GUD_INTERNAL_H +#define __LINUX_GUD_INTERNAL_H + +#include <linux/list.h> +#include <linux/mutex.h> +#include <linux/usb.h> +#include <linux/workqueue.h> +#include <uapi/drm/drm_fourcc.h> + +#include <drm/drm_modes.h> +#include <drm/drm_simple_kms_helper.h> + +struct gud_device { + struct drm_device drm; + struct drm_simple_display_pipe pipe; + struct device *dmadev; + struct work_struct work; + u32 flags; + const struct drm_format_info *xrgb8888_emulation_format; + + u16 *properties; + unsigned int num_properties; + + unsigned int bulk_pipe; + void *bulk_buf; + size_t bulk_len; + + u8 compression; + void *lz4_comp_mem; + void *compress_buf; + + u64 stats_length; + u64 stats_actual_length; + unsigned int stats_num_errors; + + struct mutex ctrl_lock; /* Serialize get/set and status transfers */ + + struct mutex damage_lock; /* Protects the following members: */ + struct drm_framebuffer *fb; + struct drm_rect damage; + bool prev_flush_failed; +}; + +static inline struct gud_device *to_gud_device(struct drm_device *drm) +{ + return container_of(drm, struct gud_device, drm); +} + +static inline struct usb_device *gud_to_usb_device(struct gud_device *gdrm) +{ + return interface_to_usbdev(to_usb_interface(gdrm->drm.dev)); +} + +int gud_usb_get(struct gud_device *gdrm, u8 request, u16 index, void *buf, size_t len); +int gud_usb_set(struct gud_device *gdrm, u8 request, u16 index, void *buf, size_t len); +int gud_usb_get_u8(struct gud_device *gdrm, u8 request, u16 index, u8 *val); +int gud_usb_set_u8(struct gud_device *gdrm, u8 request, u8 val); + +void gud_clear_damage(struct gud_device *gdrm); +void gud_flush_work(struct work_struct *work); +int gud_pipe_check(struct drm_simple_display_pipe *pipe, + struct drm_plane_state *new_plane_state, + struct drm_crtc_state *new_crtc_state); +void gud_pipe_update(struct drm_simple_display_pipe *pipe, + struct drm_plane_state *old_state); +int gud_connector_fill_properties(struct drm_connector_state *connector_state, + struct gud_property_req *properties); +int gud_get_connectors(struct gud_device *gdrm); + +/* Driver internal fourcc transfer formats */ +#define GUD_DRM_FORMAT_R1 0x00000122 +#define GUD_DRM_FORMAT_RGB111 0x03121722 + +static inline u8 gud_from_fourcc(u32 fourcc) +{ + switch (fourcc) { + case GUD_DRM_FORMAT_R1: + return GUD_PIXEL_FORMAT_R1; + case GUD_DRM_FORMAT_RGB111: + return GUD_PIXEL_FORMAT_RGB111; + case DRM_FORMAT_RGB565: + return GUD_PIXEL_FORMAT_RGB565; + case DRM_FORMAT_XRGB8888: + return GUD_PIXEL_FORMAT_XRGB8888; + case DRM_FORMAT_ARGB8888: + return GUD_PIXEL_FORMAT_ARGB8888; + }; + + return 0; +} + +static inline u32 gud_to_fourcc(u8 format) +{ + switch (format) { + case GUD_PIXEL_FORMAT_R1: + return GUD_DRM_FORMAT_R1; + case GUD_PIXEL_FORMAT_RGB111: + return GUD_DRM_FORMAT_RGB111; + case GUD_PIXEL_FORMAT_RGB565: + return DRM_FORMAT_RGB565; + case GUD_PIXEL_FORMAT_XRGB8888: + return DRM_FORMAT_XRGB8888; + case GUD_PIXEL_FORMAT_ARGB8888: + return DRM_FORMAT_ARGB8888; + }; + + return 0; +} + +static inline void gud_from_display_mode(struct gud_display_mode_req *dst, + const struct drm_display_mode *src) +{ + u32 flags = src->flags & GUD_DISPLAY_MODE_FLAG_USER_MASK; + + if (src->type & DRM_MODE_TYPE_PREFERRED) + flags |= GUD_DISPLAY_MODE_FLAG_PREFERRED; + + dst->clock = cpu_to_le32(src->clock); + dst->hdisplay = cpu_to_le16(src->hdisplay); + dst->hsync_start = cpu_to_le16(src->hsync_start); + dst->hsync_end = cpu_to_le16(src->hsync_end); + dst->htotal = cpu_to_le16(src->htotal); + dst->vdisplay = cpu_to_le16(src->vdisplay); + dst->vsync_start = cpu_to_le16(src->vsync_start); + dst->vsync_end = cpu_to_le16(src->vsync_end); + dst->vtotal = cpu_to_le16(src->vtotal); + dst->flags = cpu_to_le32(flags); +} + +static inline void gud_to_display_mode(struct drm_display_mode *dst, + const struct gud_display_mode_req *src) +{ + u32 flags = le32_to_cpu(src->flags); + + memset(dst, 0, sizeof(*dst)); + dst->clock = le32_to_cpu(src->clock); + dst->hdisplay = le16_to_cpu(src->hdisplay); + dst->hsync_start = le16_to_cpu(src->hsync_start); + dst->hsync_end = le16_to_cpu(src->hsync_end); + dst->htotal = le16_to_cpu(src->htotal); + dst->vdisplay = le16_to_cpu(src->vdisplay); + dst->vsync_start = le16_to_cpu(src->vsync_start); + dst->vsync_end = le16_to_cpu(src->vsync_end); + dst->vtotal = le16_to_cpu(src->vtotal); + dst->flags = flags & GUD_DISPLAY_MODE_FLAG_USER_MASK; + dst->type = DRM_MODE_TYPE_DRIVER; + if (flags & GUD_DISPLAY_MODE_FLAG_PREFERRED) + dst->type |= DRM_MODE_TYPE_PREFERRED; + drm_mode_set_name(dst); +} + +#endif diff --git a/drivers/gpu/drm/gud/gud_pipe.c b/drivers/gpu/drm/gud/gud_pipe.c new file mode 100644 index 000000000000..1b036c7d781e --- /dev/null +++ b/drivers/gpu/drm/gud/gud_pipe.c @@ -0,0 +1,535 @@ +// SPDX-License-Identifier: MIT +/* + * Copyright 2020 Noralf Trønnes + */ + +#include <linux/dma-buf.h> +#include <linux/lz4.h> +#include <linux/usb.h> +#include <linux/workqueue.h> + +#include <drm/drm_atomic.h> +#include <drm/drm_connector.h> +#include <drm/drm_damage_helper.h> +#include <drm/drm_drv.h> +#include <drm/drm_format_helper.h> +#include <drm/drm_fourcc.h> +#include <drm/drm_framebuffer.h> +#include <drm/drm_gem_shmem_helper.h> +#include <drm/drm_print.h> +#include <drm/drm_rect.h> +#include <drm/drm_simple_kms_helper.h> +#include <drm/gud.h> + +#include "gud_internal.h" + +static bool gud_is_big_endian(void) +{ +#if defined(__BIG_ENDIAN) + return true; +#else + return false; +#endif +} + +static size_t gud_xrgb8888_to_r124(u8 *dst, const struct drm_format_info *format, + void *src, struct drm_framebuffer *fb, + struct drm_rect *rect) +{ + unsigned int block_width = drm_format_info_block_width(format, 0); + unsigned int bits_per_pixel = 8 / block_width; + unsigned int x, y, width, height; + u8 *p, *block = dst; /* Assign to silence compiler warning */ + size_t len; + void *buf; + + WARN_ON_ONCE(format->char_per_block[0] != 1); + + /* Start on a byte boundary */ + rect->x1 = ALIGN_DOWN(rect->x1, block_width); + width = drm_rect_width(rect); + height = drm_rect_height(rect); + len = drm_format_info_min_pitch(format, 0, width) * height; + + buf = kmalloc(width * height, GFP_KERNEL); + if (!buf) + return 0; + + drm_fb_xrgb8888_to_gray8(buf, src, fb, rect); + + p = buf; + for (y = 0; y < drm_rect_height(rect); y++) { + for (x = 0; x < drm_rect_width(rect); x++) { + if (!(x % block_width)) { + block = dst++; + *block = 0; + } + + *block <<= bits_per_pixel; + *block |= (*p++) >> (8 - bits_per_pixel); + } + } + + kfree(buf); + + return len; +} + +static size_t gud_xrgb8888_to_color(u8 *dst, const struct drm_format_info *format, + u32 *src, struct drm_framebuffer *fb, + struct drm_rect *rect) +{ + unsigned int block_width = drm_format_info_block_width(format, 0); + unsigned int x, y, width, height; + u8 r, g, b, *block = dst; /* Assign to silence compiler warning */ + size_t len; + + /* Start on a byte boundary */ + rect->x1 = ALIGN_DOWN(rect->x1, block_width); + width = drm_rect_width(rect); + height = drm_rect_height(rect); + len = drm_format_info_min_pitch(format, 0, width) * height; + + for (y = 0; y < height; y++) { + for (x = 0; x < width; x++) { + if (!(x % block_width)) { + block = dst++; + *block = 0; + } + + r = *src >> 16; + g = *src >> 8; + b = *src++; + + switch (format->format) { + case GUD_DRM_FORMAT_RGB111: + *block <<= 4; + *block |= ((r >> 7) << 2) | ((g >> 7) << 1) | (b >> 7); + break; + default: + WARN_ON_ONCE(1); + return len; + }; + } + } + + return len; +} + +static int gud_prep_flush(struct gud_device *gdrm, struct drm_framebuffer *fb, + const struct drm_format_info *format, struct drm_rect *rect, + struct gud_set_buffer_req *req) +{ + struct dma_buf_attachment *import_attach = fb->obj[0]->import_attach; + u8 compression = gdrm->compression; + struct dma_buf_map map; + void *vaddr, *buf; + size_t pitch, len; + int ret = 0; + + pitch = drm_format_info_min_pitch(format, 0, drm_rect_width(rect)); + len = pitch * drm_rect_height(rect); + if (len > gdrm->bulk_len) + return -E2BIG; + + ret = drm_gem_shmem_vmap(fb->obj[0], &map); + if (ret) + return ret; + + vaddr = map.vaddr + fb->offsets[0]; + + if (import_attach) { + ret = dma_buf_begin_cpu_access(import_attach->dmabuf, DMA_FROM_DEVICE); + if (ret) + goto vunmap; + } +retry: + if (compression) + buf = gdrm->compress_buf; + else + buf = gdrm->bulk_buf; + + /* + * Imported buffers are assumed to be write-combined and thus uncached + * with slow reads (at least on ARM). + */ + if (format != fb->format) { + if (format->format == GUD_DRM_FORMAT_R1) { + len = gud_xrgb8888_to_r124(buf, format, vaddr, fb, rect); + if (!len) { + ret = -ENOMEM; + goto end_cpu_access; + } + } else if (format->format == DRM_FORMAT_RGB565) { + drm_fb_xrgb8888_to_rgb565(buf, vaddr, fb, rect, gud_is_big_endian()); + } else { + len = gud_xrgb8888_to_color(buf, format, vaddr, fb, rect); + } + } else if (gud_is_big_endian() && format->cpp[0] > 1) { + drm_fb_swab(buf, vaddr, fb, rect, !import_attach); + } else if (compression && !import_attach && pitch == fb->pitches[0]) { + /* can compress directly from the framebuffer */ + buf = vaddr + rect->y1 * pitch; + } else { + drm_fb_memcpy(buf, vaddr, fb, rect); + } + + memset(req, 0, sizeof(*req)); + req->x = cpu_to_le32(rect->x1); + req->y = cpu_to_le32(rect->y1); + req->width = cpu_to_le32(drm_rect_width(rect)); + req->height = cpu_to_le32(drm_rect_height(rect)); + req->length = cpu_to_le32(len); + + if (compression & GUD_COMPRESSION_LZ4) { + int complen; + + complen = LZ4_compress_default(buf, gdrm->bulk_buf, len, len, gdrm->lz4_comp_mem); + if (complen <= 0) { + compression = 0; + goto retry; + } + + req->compression = GUD_COMPRESSION_LZ4; + req->compressed_length = cpu_to_le32(complen); + } + +end_cpu_access: + if (import_attach) + dma_buf_end_cpu_access(import_attach->dmabuf, DMA_FROM_DEVICE); +vunmap: + drm_gem_shmem_vunmap(fb->obj[0], &map); + + return ret; +} + +static int gud_flush_rect(struct gud_device *gdrm, struct drm_framebuffer *fb, + const struct drm_format_info *format, struct drm_rect *rect) +{ + struct usb_device *usb = gud_to_usb_device(gdrm); + struct gud_set_buffer_req req; + int ret, actual_length; + size_t len, trlen; + + drm_dbg(&gdrm->drm, "Flushing [FB:%d] " DRM_RECT_FMT "\n", fb->base.id, DRM_RECT_ARG(rect)); + + ret = gud_prep_flush(gdrm, fb, format, rect, &req); + if (ret) + return ret; + + len = le32_to_cpu(req.length); + + if (req.compression) + trlen = le32_to_cpu(req.compressed_length); + else + trlen = len; + + gdrm->stats_length += len; + /* Did it wrap around? */ + if (gdrm->stats_length <= len && gdrm->stats_actual_length) { + gdrm->stats_length = len; + gdrm->stats_actual_length = 0; + } + gdrm->stats_actual_length += trlen; + + if (!(gdrm->flags & GUD_DISPLAY_FLAG_FULL_UPDATE) || gdrm->prev_flush_failed) { + ret = gud_usb_set(gdrm, GUD_REQ_SET_BUFFER, 0, &req, sizeof(req)); + if (ret) + return ret; + } + + ret = usb_bulk_msg(usb, gdrm->bulk_pipe, gdrm->bulk_buf, trlen, + &actual_length, msecs_to_jiffies(3000)); + if (!ret && trlen != actual_length) + ret = -EIO; + if (ret) + gdrm->stats_num_errors++; + + return ret; +} + +void gud_clear_damage(struct gud_device *gdrm) +{ + gdrm->damage.x1 = INT_MAX; + gdrm->damage.y1 = INT_MAX; + gdrm->damage.x2 = 0; + gdrm->damage.y2 = 0; +} + +static void gud_add_damage(struct gud_device *gdrm, struct drm_rect *damage) +{ + gdrm->damage.x1 = min(gdrm->damage.x1, damage->x1); + gdrm->damage.y1 = min(gdrm->damage.y1, damage->y1); + gdrm->damage.x2 = max(gdrm->damage.x2, damage->x2); + gdrm->damage.y2 = max(gdrm->damage.y2, damage->y2); +} + +static void gud_retry_failed_flush(struct gud_device *gdrm, struct drm_framebuffer *fb, + struct drm_rect *damage) +{ + /* + * pipe_update waits for the worker when the display mode is going to change. + * This ensures that the width and height is still the same making it safe to + * add back the damage. + */ + + mutex_lock(&gdrm->damage_lock); + if (!gdrm->fb) { + drm_framebuffer_get(fb); + gdrm->fb = fb; + } + gud_add_damage(gdrm, damage); + mutex_unlock(&gdrm->damage_lock); + + /* Retry only once to avoid a possible storm in case of continues errors. */ + if (!gdrm->prev_flush_failed) + queue_work(system_long_wq, &gdrm->work); + gdrm->prev_flush_failed = true; +} + +void gud_flush_work(struct work_struct *work) +{ + struct gud_device *gdrm = container_of(work, struct gud_device, work); + const struct drm_format_info *format; + struct drm_framebuffer *fb; + struct drm_rect damage; + unsigned int i, lines; + int idx, ret = 0; + size_t pitch; + + if (!drm_dev_enter(&gdrm->drm, &idx)) + return; + + mutex_lock(&gdrm->damage_lock); + fb = gdrm->fb; + gdrm->fb = NULL; + damage = gdrm->damage; + gud_clear_damage(gdrm); + mutex_unlock(&gdrm->damage_lock); + + if (!fb) + goto out; + + format = fb->format; + if (format->format == DRM_FORMAT_XRGB8888 && gdrm->xrgb8888_emulation_format) + format = gdrm->xrgb8888_emulation_format; + + /* Split update if it's too big */ + pitch = drm_format_info_min_pitch(format, 0, drm_rect_width(&damage)); + lines = drm_rect_height(&damage); + + if (gdrm->bulk_len < lines * pitch) + lines = gdrm->bulk_len / pitch; + + for (i = 0; i < DIV_ROUND_UP(drm_rect_height(&damage), lines); i++) { + struct drm_rect rect = damage; + + rect.y1 += i * lines; + rect.y2 = min_t(u32, rect.y1 + lines, damage.y2); + + ret = gud_flush_rect(gdrm, fb, format, &rect); + if (ret) { + if (ret != -ENODEV && ret != -ECONNRESET && + ret != -ESHUTDOWN && ret != -EPROTO) { + bool prev_flush_failed = gdrm->prev_flush_failed; + + gud_retry_failed_flush(gdrm, fb, &damage); + if (!prev_flush_failed) + dev_err_ratelimited(fb->dev->dev, + "Failed to flush framebuffer: error=%d\n", ret); + } + break; + } + + gdrm->prev_flush_failed = false; + } + + drm_framebuffer_put(fb); +out: + drm_dev_exit(idx); +} + +static void gud_fb_queue_damage(struct gud_device *gdrm, struct drm_framebuffer *fb, + struct drm_rect *damage) +{ + struct drm_framebuffer *old_fb = NULL; + + mutex_lock(&gdrm->damage_lock); + + if (fb != gdrm->fb) { + old_fb = gdrm->fb; + drm_framebuffer_get(fb); + gdrm->fb = fb; + } + + gud_add_damage(gdrm, damage); + + mutex_unlock(&gdrm->damage_lock); + + queue_work(system_long_wq, &gdrm->work); + + if (old_fb) + drm_framebuffer_put(old_fb); +} + +int gud_pipe_check(struct drm_simple_display_pipe *pipe, + struct drm_plane_state *new_plane_state, + struct drm_crtc_state *new_crtc_state) +{ + struct gud_device *gdrm = to_gud_device(pipe->crtc.dev); + struct drm_plane_state *old_plane_state = pipe->plane.state; + const struct drm_display_mode *mode = &new_crtc_state->mode; + struct drm_atomic_state *state = new_plane_state->state; + struct drm_framebuffer *old_fb = old_plane_state->fb; + struct drm_connector_state *connector_state = NULL; + struct drm_framebuffer *fb = new_plane_state->fb; + const struct drm_format_info *format = fb->format; + struct drm_connector *connector; + unsigned int i, num_properties; + struct gud_state_req *req; + int idx, ret; + size_t len; + + if (WARN_ON_ONCE(!fb)) + return -EINVAL; + + if (old_plane_state->rotation != new_plane_state->rotation) + new_crtc_state->mode_changed = true; + + if (old_fb && old_fb->format != format) + new_crtc_state->mode_changed = true; + + if (!new_crtc_state->mode_changed && !new_crtc_state->connectors_changed) + return 0; + + /* Only one connector is supported */ + if (hweight32(new_crtc_state->connector_mask) != 1) + return -EINVAL; + + if (format->format == DRM_FORMAT_XRGB8888 && gdrm->xrgb8888_emulation_format) + format = gdrm->xrgb8888_emulation_format; + + for_each_new_connector_in_state(state, connector, connector_state, i) { + if (connector_state->crtc) + break; + } + + /* + * DRM_IOCTL_MODE_OBJ_SETPROPERTY on the rotation property will not have + * the connector included in the state. + */ + if (!connector_state) { + struct drm_connector_list_iter conn_iter; + + drm_connector_list_iter_begin(pipe->crtc.dev, &conn_iter); + drm_for_each_connector_iter(connector, &conn_iter) { + if (connector->state->crtc) { + connector_state = connector->state; + break; + } + } + drm_connector_list_iter_end(&conn_iter); + } + + if (WARN_ON_ONCE(!connector_state)) + return -ENOENT; + + len = struct_size(req, properties, + GUD_PROPERTIES_MAX_NUM + GUD_CONNECTOR_PROPERTIES_MAX_NUM); + req = kzalloc(len, GFP_KERNEL); + if (!req) + return -ENOMEM; + + gud_from_display_mode(&req->mode, mode); + + req->format = gud_from_fourcc(format->format); + if (WARN_ON_ONCE(!req->format)) { + ret = -EINVAL; + goto out; + } + + req->connector = drm_connector_index(connector_state->connector); + + ret = gud_connector_fill_properties(connector_state, req->properties); + if (ret < 0) + goto out; + + num_properties = ret; + for (i = 0; i < gdrm->num_properties; i++) { + u16 prop = gdrm->properties[i]; + u64 val; + + switch (prop) { + case GUD_PROPERTY_ROTATION: + /* DRM UAPI matches the protocol so use value directly */ + val = new_plane_state->rotation; + break; + default: + WARN_ON_ONCE(1); + ret = -EINVAL; + goto out; + } + + req->properties[num_properties + i].prop = cpu_to_le16(prop); + req->properties[num_properties + i].val = cpu_to_le64(val); + num_properties++; + } + + if (drm_dev_enter(fb->dev, &idx)) { + len = struct_size(req, properties, num_properties); + ret = gud_usb_set(gdrm, GUD_REQ_SET_STATE_CHECK, 0, req, len); + drm_dev_exit(idx); + } else { + ret = -ENODEV; + } +out: + kfree(req); + + return ret; +} + +void gud_pipe_update(struct drm_simple_display_pipe *pipe, + struct drm_plane_state *old_state) +{ + struct drm_device *drm = pipe->crtc.dev; + struct gud_device *gdrm = to_gud_device(drm); + struct drm_plane_state *state = pipe->plane.state; + struct drm_framebuffer *fb = state->fb; + struct drm_crtc *crtc = &pipe->crtc; + struct drm_rect damage; + int idx; + + if (crtc->state->mode_changed || !crtc->state->enable) { + cancel_work_sync(&gdrm->work); + mutex_lock(&gdrm->damage_lock); + if (gdrm->fb) { + drm_framebuffer_put(gdrm->fb); + gdrm->fb = NULL; + } + gud_clear_damage(gdrm); + mutex_unlock(&gdrm->damage_lock); + } + + if (!drm_dev_enter(drm, &idx)) + return; + + if (!old_state->fb) + gud_usb_set_u8(gdrm, GUD_REQ_SET_CONTROLLER_ENABLE, 1); + + if (fb && (crtc->state->mode_changed || crtc->state->connectors_changed)) + gud_usb_set(gdrm, GUD_REQ_SET_STATE_COMMIT, 0, NULL, 0); + + if (crtc->state->active_changed) + gud_usb_set_u8(gdrm, GUD_REQ_SET_DISPLAY_ENABLE, crtc->state->active); + + if (drm_atomic_helper_damage_merged(old_state, state, &damage)) { + if (gdrm->flags & GUD_DISPLAY_FLAG_FULL_UPDATE) + drm_rect_init(&damage, 0, 0, fb->width, fb->height); + gud_fb_queue_damage(gdrm, fb, &damage); + } + + if (!crtc->state->enable) + gud_usb_set_u8(gdrm, GUD_REQ_SET_CONTROLLER_ENABLE, 0); + + drm_dev_exit(idx); +} diff --git a/include/drm/gud.h b/include/drm/gud.h new file mode 100644 index 000000000000..a078aad63a31 --- /dev/null +++ b/include/drm/gud.h @@ -0,0 +1,333 @@ +/* SPDX-License-Identifier: MIT */ +/* + * Copyright 2020 Noralf Trønnes + */ + +#ifndef __LINUX_GUD_H +#define __LINUX_GUD_H + +#include <linux/types.h> + +/* + * struct gud_display_descriptor_req - Display descriptor + * @magic: Magic value GUD_DISPLAY_MAGIC + * @version: Protocol version + * @flags: Flags + * - STATUS_ON_SET: Always do a status request after a SET request. + * This is used by the Linux gadget driver since it has + * no way to control the status stage of a control OUT + * request that has a payload. + * - FULL_UPDATE: Always send the entire framebuffer when flushing changes. + * The GUD_REQ_SET_BUFFER request will not be sent + * before each bulk transfer, it will only be sent if the + * previous bulk transfer had failed. This gives the device + * a chance to reset its state machine if needed. + * This flag can not be used in combination with compression. + * @compression: Supported compression types + * - GUD_COMPRESSION_LZ4: LZ4 lossless compression. + * @max_buffer_size: Maximum buffer size the device can handle (optional). + * This is useful for devices that don't have a big enough + * buffer to decompress the entire framebuffer in one go. + * @min_width: Minimum pixel width the controller can handle + * @max_width: Maximum width + * @min_height: Minimum height + * @max_height: Maximum height + * + * Devices that have only one display mode will have min_width == max_width + * and min_height == max_height. + */ +struct gud_display_descriptor_req { + __le32 magic; +#define GUD_DISPLAY_MAGIC 0x1d50614d + __u8 version; + __le32 flags; +#define GUD_DISPLAY_FLAG_STATUS_ON_SET BIT(0) +#define GUD_DISPLAY_FLAG_FULL_UPDATE BIT(1) + __u8 compression; +#define GUD_COMPRESSION_LZ4 BIT(0) + __le32 max_buffer_size; + __le32 min_width; + __le32 max_width; + __le32 min_height; + __le32 max_height; +} __packed; + +/* + * struct gud_property_req - Property + * @prop: Property + * @val: Value + */ +struct gud_property_req { + __le16 prop; + __le64 val; +} __packed; + +/* + * struct gud_display_mode_req - Display mode + * @clock: Pixel clock in kHz + * @hdisplay: Horizontal display size + * @hsync_start: Horizontal sync start + * @hsync_end: Horizontal sync end + * @htotal: Horizontal total size + * @vdisplay: Vertical display size + * @vsync_start: Vertical sync start + * @vsync_end: Vertical sync end + * @vtotal: Vertical total size + * @flags: Bits 0-13 are the same as in the RandR protocol and also what DRM uses. + * The deprecated bits are reused for internal protocol flags leaving us + * free to follow DRM for the other bits in the future. + * - FLAG_PREFERRED: Set on the preferred display mode. + */ +struct gud_display_mode_req { + __le32 clock; + __le16 hdisplay; + __le16 hsync_start; + __le16 hsync_end; + __le16 htotal; + __le16 vdisplay; + __le16 vsync_start; + __le16 vsync_end; + __le16 vtotal; + __le32 flags; +#define GUD_DISPLAY_MODE_FLAG_PHSYNC BIT(0) +#define GUD_DISPLAY_MODE_FLAG_NHSYNC BIT(1) +#define GUD_DISPLAY_MODE_FLAG_PVSYNC BIT(2) +#define GUD_DISPLAY_MODE_FLAG_NVSYNC BIT(3) +#define GUD_DISPLAY_MODE_FLAG_INTERLACE BIT(4) +#define GUD_DISPLAY_MODE_FLAG_DBLSCAN BIT(5) +#define GUD_DISPLAY_MODE_FLAG_CSYNC BIT(6) +#define GUD_DISPLAY_MODE_FLAG_PCSYNC BIT(7) +#define GUD_DISPLAY_MODE_FLAG_NCSYNC BIT(8) +#define GUD_DISPLAY_MODE_FLAG_HSKEW BIT(9) +/* BCast and PixelMultiplex are deprecated */ +#define GUD_DISPLAY_MODE_FLAG_DBLCLK BIT(12) +#define GUD_DISPLAY_MODE_FLAG_CLKDIV2 BIT(13) +#define GUD_DISPLAY_MODE_FLAG_USER_MASK \ + (GUD_DISPLAY_MODE_FLAG_PHSYNC | GUD_DISPLAY_MODE_FLAG_NHSYNC | \ + GUD_DISPLAY_MODE_FLAG_PVSYNC | GUD_DISPLAY_MODE_FLAG_NVSYNC | \ + GUD_DISPLAY_MODE_FLAG_INTERLACE | GUD_DISPLAY_MODE_FLAG_DBLSCAN | \ + GUD_DISPLAY_MODE_FLAG_CSYNC | GUD_DISPLAY_MODE_FLAG_PCSYNC | \ + GUD_DISPLAY_MODE_FLAG_NCSYNC | GUD_DISPLAY_MODE_FLAG_HSKEW | \ + GUD_DISPLAY_MODE_FLAG_DBLCLK | GUD_DISPLAY_MODE_FLAG_CLKDIV2) +/* Internal protocol flags */ +#define GUD_DISPLAY_MODE_FLAG_PREFERRED BIT(10) +} __packed; + +/* + * struct gud_connector_descriptor_req - Connector descriptor + * @connector_type: Connector type (GUD_CONNECTOR_TYPE_*). + * If the host doesn't support the type it should fall back to PANEL. + * @flags: Flags + * - POLL_STATUS: Connector status can change (polled every 10 seconds) + * - INTERLACE: Interlaced modes are supported + * - DOUBLESCAN: Doublescan modes are supported + */ +struct gud_connector_descriptor_req { + __u8 connector_type; +#define GUD_CONNECTOR_TYPE_PANEL 0 +#define GUD_CONNECTOR_TYPE_VGA 1 +#define GUD_CONNECTOR_TYPE_COMPOSITE 2 +#define GUD_CONNECTOR_TYPE_SVIDEO 3 +#define GUD_CONNECTOR_TYPE_COMPONENT 4 +#define GUD_CONNECTOR_TYPE_DVI 5 +#define GUD_CONNECTOR_TYPE_DISPLAYPORT 6 +#define GUD_CONNECTOR_TYPE_HDMI 7 + __le32 flags; +#define GUD_CONNECTOR_FLAGS_POLL_STATUS BIT(0) +#define GUD_CONNECTOR_FLAGS_INTERLACE BIT(1) +#define GUD_CONNECTOR_FLAGS_DOUBLESCAN BIT(2) +} __packed; + +/* + * struct gud_set_buffer_req - Set buffer transfer info + * @x: X position of rectangle + * @y: Y position + * @width: Pixel width of rectangle + * @height: Pixel height + * @length: Buffer length in bytes + * @compression: Transfer compression + * @compressed_length: Compressed buffer length + * + * This request is issued right before the bulk transfer. + * @x, @y, @width and @height specifies the rectangle where the buffer should be + * placed inside the framebuffer. + */ +struct gud_set_buffer_req { + __le32 x; + __le32 y; + __le32 width; + __le32 height; + __le32 length; + __u8 compression; + __le32 compressed_length; +} __packed; + +/* + * struct gud_state_req - Display state + * @mode: Display mode + * @format: Pixel format GUD_PIXEL_FORMAT_* + * @connector: Connector index + * @properties: Array of properties + * + * The entire state is transferred each time there's a change. + */ +struct gud_state_req { + struct gud_display_mode_req mode; + __u8 format; + __u8 connector; + struct gud_property_req properties[]; +} __packed; + +/* List of supported connector properties: */ + +/* Margins in pixels to deal with overscan, range 0-100 */ +#define GUD_PROPERTY_TV_LEFT_MARGIN 1 +#define GUD_PROPERTY_TV_RIGHT_MARGIN 2 +#define GUD_PROPERTY_TV_TOP_MARGIN 3 +#define GUD_PROPERTY_TV_BOTTOM_MARGIN 4 +#define GUD_PROPERTY_TV_MODE 5 +/* Brightness in percent, range 0-100 */ +#define GUD_PROPERTY_TV_BRIGHTNESS 6 +/* Contrast in percent, range 0-100 */ +#define GUD_PROPERTY_TV_CONTRAST 7 +/* Flicker reduction in percent, range 0-100 */ +#define GUD_PROPERTY_TV_FLICKER_REDUCTION 8 +/* Overscan in percent, range 0-100 */ +#define GUD_PROPERTY_TV_OVERSCAN 9 +/* Saturation in percent, range 0-100 */ +#define GUD_PROPERTY_TV_SATURATION 10 +/* Hue in percent, range 0-100 */ +#define GUD_PROPERTY_TV_HUE 11 + +/* + * Backlight brightness is in the range 0-100 inclusive. The value represents the human perceptual + * brightness and not a linear PWM value. 0 is minimum brightness which should not turn the + * backlight completely off. The DPMS connector property should be used to control power which will + * trigger a GUD_REQ_SET_DISPLAY_ENABLE request. + * + * This does not map to a DRM property, it is used with the backlight device. + */ +#define GUD_PROPERTY_BACKLIGHT_BRIGHTNESS 12 + +/* List of supported properties that are not connector propeties: */ + +/* + * Plane rotation. Should return the supported bitmask on + * GUD_REQ_GET_PROPERTIES. GUD_ROTATION_0 is mandatory. + * + * Note: This is not display rotation so 90/270 will need scaling to make it fit (unless squared). + */ +#define GUD_PROPERTY_ROTATION 50 + #define GUD_ROTATION_0 BIT(0) + #define GUD_ROTATION_90 BIT(1) + #define GUD_ROTATION_180 BIT(2) + #define GUD_ROTATION_270 BIT(3) + #define GUD_ROTATION_REFLECT_X BIT(4) + #define GUD_ROTATION_REFLECT_Y BIT(5) + #define GUD_ROTATION_MASK (GUD_ROTATION_0 | GUD_ROTATION_90 | \ + GUD_ROTATION_180 | GUD_ROTATION_270 | \ + GUD_ROTATION_REFLECT_X | GUD_ROTATION_REFLECT_Y) + +/* USB Control requests: */ + +/* Get status from the last GET/SET control request. Value is u8. */ +#define GUD_REQ_GET_STATUS 0x00 + /* Status values: */ + #define GUD_STATUS_OK 0x00 + #define GUD_STATUS_BUSY 0x01 + #define GUD_STATUS_REQUEST_NOT_SUPPORTED 0x02 + #define GUD_STATUS_PROTOCOL_ERROR 0x03 + #define GUD_STATUS_INVALID_PARAMETER 0x04 + #define GUD_STATUS_ERROR 0x05 + +/* Get display descriptor as a &gud_display_descriptor_req */ +#define GUD_REQ_GET_DESCRIPTOR 0x01 + +/* Get supported pixel formats as a byte array of GUD_PIXEL_FORMAT_* */ +#define GUD_REQ_GET_FORMATS 0x40 + #define GUD_FORMATS_MAX_NUM 32 + /* R1 is a 1-bit monochrome transfer format presented to userspace as XRGB8888 */ + #define GUD_PIXEL_FORMAT_R1 0x01 + #define GUD_PIXEL_FORMAT_RGB111 0x20 + #define GUD_PIXEL_FORMAT_RGB565 0x40 + #define GUD_PIXEL_FORMAT_XRGB8888 0x80 + #define GUD_PIXEL_FORMAT_ARGB8888 0x81 + +/* + * Get supported properties that are not connector propeties as a &gud_property_req array. + * gud_property_req.val often contains the initial value for the property. + */ +#define GUD_REQ_GET_PROPERTIES 0x41 + #define GUD_PROPERTIES_MAX_NUM 32 + +/* Connector requests have the connector index passed in the wValue field */ + +/* Get connector descriptors as an array of &gud_connector_descriptor_req */ +#define GUD_REQ_GET_CONNECTORS 0x50 + #define GUD_CONNECTORS_MAX_NUM 32 + +/* + * Get properties supported by the connector as a &gud_property_req array. + * gud_property_req.val often contains the initial value for the property. + */ +#define GUD_REQ_GET_CONNECTOR_PROPERTIES 0x51 + #define GUD_CONNECTOR_PROPERTIES_MAX_NUM 32 + +/* + * Issued when there's a TV_MODE property present. + * Gets an array of the supported TV_MODE names each entry of length + * GUD_CONNECTOR_TV_MODE_NAME_LEN. Names must be NUL-terminated. + */ +#define GUD_REQ_GET_CONNECTOR_TV_MODE_VALUES 0x52 + #define GUD_CONNECTOR_TV_MODE_NAME_LEN 16 + #define GUD_CONNECTOR_TV_MODE_MAX_NUM 16 + +/* When userspace checks connector status, this is issued first, not used for poll requests. */ +#define GUD_REQ_SET_CONNECTOR_FORCE_DETECT 0x53 + +/* + * Get connector status. Value is u8. + * + * Userspace will get a HOTPLUG uevent if one of the following is true: + * - Connection status has changed since last + * - CHANGED is set + */ +#define GUD_REQ_GET_CONNECTOR_STATUS 0x54 + #define GUD_CONNECTOR_STATUS_DISCONNECTED 0x00 + #define GUD_CONNECTOR_STATUS_CONNECTED 0x01 + #define GUD_CONNECTOR_STATUS_UNKNOWN 0x02 + #define GUD_CONNECTOR_STATUS_CONNECTED_MASK 0x03 + #define GUD_CONNECTOR_STATUS_CHANGED BIT(7) + +/* + * Display modes can be fetched as either EDID data or an array of &gud_display_mode_req. + * + * If GUD_REQ_GET_CONNECTOR_MODES returns zero, EDID is used to create display modes. + * If both display modes and EDID are returned, EDID is just passed on to userspace + * in the EDID connector property. + */ + +/* Get &gud_display_mode_req array of supported display modes */ +#define GUD_REQ_GET_CONNECTOR_MODES 0x55 + #define GUD_CONNECTOR_MAX_NUM_MODES 128 + +/* Get Extended Display Identification Data */ +#define GUD_REQ_GET_CONNECTOR_EDID 0x56 + #define GUD_CONNECTOR_MAX_EDID_LEN 2048 + +/* Set buffer properties before bulk transfer as &gud_set_buffer_req */ +#define GUD_REQ_SET_BUFFER 0x60 + +/* Check display configuration as &gud_state_req */ +#define GUD_REQ_SET_STATE_CHECK 0x61 + +/* Apply the previous STATE_CHECK configuration */ +#define GUD_REQ_SET_STATE_COMMIT 0x62 + +/* Enable/disable the display controller, value is u8: 0/1 */ +#define GUD_REQ_SET_CONTROLLER_ENABLE 0x63 + +/* Enable/disable display/output (DPMS), value is u8: 0/1 */ +#define GUD_REQ_SET_DISPLAY_ENABLE 0x64 + +#endif
Den 05.03.2021 17.31, skrev Noralf Trønnes:
This adds a USB display driver with the intention that it can be used with future USB interfaced low end displays/adapters. The Linux gadget device driver will serve as the canonical device implementation.
diff --git a/drivers/gpu/drm/gud/gud_drv.c b/drivers/gpu/drm/gud/gud_drv.c
+static int gud_probe(struct usb_interface *intf, const struct usb_device_id *id) +{
- num_formats_dev = ret;
- for (i = 0; i < num_formats_dev; i++) {
const struct drm_format_info *info;
size_t fmt_buf_size;
u32 format;
format = gud_to_fourcc(formats_dev[i]);
if (!format) {
drm_dbg(drm, "Unsupported format: 0x%02x\n", formats_dev[i]);
continue;
}
if (format == GUD_DRM_FORMAT_R1)
info = &gud_drm_format_r1;
else if (format == GUD_DRM_FORMAT_RGB111)
info = &gud_drm_format_rgb111;
else
info = drm_format_info(format);
switch (format) {
case GUD_DRM_FORMAT_R1:
fallthrough;
case GUD_DRM_FORMAT_RGB111:
xrgb8888_emulation_format = info;
break;
case DRM_FORMAT_RGB565:
rgb565_supported = true;
if (!xrgb8888_emulation_format)
xrgb8888_emulation_format = info;
break;
case DRM_FORMAT_XRGB8888:
xrgb8888_supported = true;
break;
};
fmt_buf_size = drm_format_info_min_pitch(info, 0, drm->mode_config.max_width) *
drm->mode_config.max_height;
max_buffer_size = max(max_buffer_size, fmt_buf_size);
if (format == GUD_DRM_FORMAT_R1)
This line should be:
if (format == GUD_DRM_FORMAT_R1 || format == GUD_DRM_FORMAT_RGB111)
There's also a bug in the format conversion functions that shows up on transfers with widths that are not pixels per byte aligned. I've fixed R1, I'll look at RGB111 tomorrow.
Noralf.
continue; /* Internal not for userspace */
formats[num_formats++] = format;
- }
Hello Noralf,
I've made some progress with my test device. I'm implementing R1 first and once that works I'll test RGB111 as well. Along the way I've found a couple of things in the code:
Noralf Trønnes wrote:
+++ b/drivers/gpu/drm/gud/gud_drv.c
..
+static int gud_probe(struct usb_interface *intf, const struct usb_device_id *id)
..
if (format == GUD_DRM_FORMAT_R1)
continue; /* Internal not for userspace */
You already found RGB111 missing here.
+static int gud_usb_control_msg(struct usb_interface *intf, bool in,
u8 request, u16 value, void *buf, size_t len)
..
+static int gud_usb_transfer(struct gud_device *gdrm, bool in, u8 request, u16 index,
..
- ret = gud_usb_control_msg(intf, in, request, index, buf, len);
The u16 index parameter to gud_usb_transfer() and at least also gud_usb_{get,set,get_u8,set_u8}() is eventually passed in u16 value in the call to gud_usb_control_msg(), which had me confused for a bit.
What do you think about renaming all of those parameters to wValue, to show that and where they are part of the control request? I think it would help make the protocol more clear.
Finally, an actual bug:
- ret = gud_get_properties(gdrm);
- if (ret) {
dev_err(dev, "Failed to get properties (error=%d)\n", ret);
return ret;
- }
If gud_get_properties() and gud_connector_add_properties() receive and process (only!) one or more unknown properties then they return the number of bytes received from the device rather than 0.
I fixed this by setting ret = 0; before the for() loop, but maybe you want to do it another way.
I found this because I can't get my device to send 0 bytes IN when the host requests more, if I provide no data the request STALLs. This is for sure a bug in my device and I'll come back to it, but for now I added a dummy 65535 property as a workaround.
What do you think about formalizing this, adding an actual dummy property?
Or maybe adding flags to the display descriptor for "I have properties", "I have connector properties" and "I have EDID" ?
+++ b/drivers/gpu/drm/gud/gud_pipe.c
..
+static int gud_prep_flush(struct gud_device *gdrm, struct drm_framebuffer *fb,
..
- /*
* Imported buffers are assumed to be write-combined and thus uncached
* with slow reads (at least on ARM).
*/
- if (format != fb->format) {
if (format->format == GUD_DRM_FORMAT_R1) {
len = gud_xrgb8888_to_r124(buf, format, vaddr, fb, rect);
if (!len) {
ret = -ENOMEM;
goto end_cpu_access;
}
} else if (format->format == DRM_FORMAT_RGB565) {
drm_fb_xrgb8888_to_rgb565(buf, vaddr, fb, rect, gud_is_big_endian());
} else {
len = gud_xrgb8888_to_color(buf, format, vaddr, fb, rect);
}
Does this section also need a RGB111 case?
+void gud_pipe_update(struct drm_simple_display_pipe *pipe,
..
- if (fb && (crtc->state->mode_changed || crtc->state->connectors_changed))
gud_usb_set(gdrm, GUD_REQ_SET_STATE_COMMIT, 0, NULL, 0);
You mentioned that commit must not fail; what happens/should happen if a request does fail in pipe_update()? Some reasons could be that the device was unplugged, a bad cable is glitchy or even that some device doesn't even implement STATE_COMMIT or does it incorrectly and will report back failure?
+++ b/include/drm/gud.h
..
- #define GUD_STATUS_REQUEST_NOT_SUPPORTED 0x02
Maybe this can be removed? SET_VERSION has been removed so it's no longer used anywhere, and in any case devices typically signal that requests are unsupported using a protocol STALL, which comes back as -EPIPE from the USB stack.
Finally, here's the drm debug output when I connect my device:
Mar 09 14:57:19 vm kernel: usb 1-1: new full-speed USB device number 24 using uhci_hcd Mar 09 14:57:19 vm kernel: gud 1-1:27.0: [drm:gud_probe] version=1 flags=0x2 compression=0x0 max_buffer_size=0 Mar 09 14:57:19 vm kernel: gud 1-1:27.0: [drm:gud_usb_transfer] get: request=0x40 index=0 len=32 Mar 09 14:57:19 vm kernel: gud 1-1:27.0: [drm:gud_usb_transfer] get: request=0x41 index=0 len=320 Mar 09 14:57:19 vm kernel: gud 1-1:27.0: [drm:gud_probe] Ignoring unknown property: 65535 Mar 09 14:57:19 vm kernel: gud 1-1:27.0: [drm:gud_usb_transfer] get: request=0x50 index=0 len=256 Mar 09 14:57:19 vm kernel: gud 1-1:27.0: [drm:gud_get_connectors] Connector: index=0 type=0 flags=0x0 Mar 09 14:57:19 vm kernel: gud 1-1:27.0: [drm:gud_usb_transfer] get: request=0x51 index=0 len=320 Mar 09 14:57:19 vm kernel: gud 1-1:27.0: [drm:gud_get_connectors] property: 65535 = 0(0x0) Mar 09 14:57:19 vm kernel: gud 1-1:27.0: [drm:gud_get_connectors] Ignoring unknown property: 65535 Mar 09 14:57:19 vm kernel: [drm:drm_minor_register] Mar 09 14:57:19 vm kernel: [drm:drm_minor_register] Mar 09 14:57:19 vm kernel: [drm:drm_minor_register] new minor registered 0 Mar 09 14:57:19 vm kernel: [drm:drm_sysfs_connector_add] adding "USB-1" to sysfs Mar 09 14:57:19 vm kernel: [drm:drm_sysfs_hotplug_event] generating hotplug event Mar 09 14:57:19 vm kernel: [drm] Initialized gud 1.0.0 20200422 for 1-1:27.0 on minor 0 Mar 09 14:57:19 vm kernel: [drm:drm_client_modeset_probe] Mar 09 14:57:19 vm kernel: [drm:drm_mode_object_get] OBJ ID: 35 (2) Mar 09 14:57:19 vm kernel: [drm:drm_helper_probe_single_connector_modes] [CONNECTOR:35:USB-1] Mar 09 14:57:19 vm kernel: gud 1-1:27.0: [drm:gud_usb_transfer] set: request=0x53 index=0 len=0 Mar 09 14:57:19 vm kernel: gud 1-1:27.0: [drm:gud_usb_transfer] get: request=0x54 index=0 len=1 Mar 09 14:57:19 vm kernel: [drm:drm_helper_probe_single_connector_modes] [CONNECTOR:35:USB-1] status updated from unknown to connected Mar 09 14:57:19 vm kernel: gud 1-1:27.0: [drm:gud_usb_transfer] get: request=0x56 index=0 len=2048 Mar 09 14:57:19 vm kernel: [drm:drm_sysfs_hotplug_event] generating hotplug event Mar 09 14:57:19 vm kernel: gud 1-1:27.0: USB-1: Invalid EDID size (ret=1) Mar 09 14:57:19 vm kernel: gud 1-1:27.0: [drm:gud_usb_transfer] get: request=0x55 index=0 len=3072 Mar 09 14:57:19 vm kernel: [drm:drm_helper_probe_single_connector_modes] [CONNECTOR:35:USB-1] probed modes : Mar 09 14:57:19 vm kernel: [drm:drm_mode_debug_printmodeline] Modeline "400x240": 104 10000 400 400 400 400 240 240 240 240 0x40 0x0 Mar 09 14:57:19 vm kernel: [drm:drm_client_modeset_probe] connector 35 enabled? yes Mar 09 14:57:19 vm kernel: [drm:drm_client_modeset_probe] Not using firmware configuration Mar 09 14:57:19 vm kernel: [drm:drm_client_modeset_probe] looking for cmdline mode on connector 35 Mar 09 14:57:19 vm kernel: [drm:drm_client_modeset_probe] looking for preferred mode on connector 35 0 Mar 09 14:57:19 vm kernel: [drm:drm_client_modeset_probe] found mode 400x240 Mar 09 14:57:19 vm kernel: [drm:drm_client_modeset_probe] picking CRTCs for 400x240 config Mar 09 14:57:19 vm kernel: [drm:drm_client_modeset_probe] desired mode 400x240 set on crtc 33 (0,0) Mar 09 14:57:19 vm kernel: [drm:drm_mode_object_get] OBJ ID: 35 (2) Mar 09 14:57:19 vm kernel: [drm:drm_mode_object_put.part.0] OBJ ID: 35 (3) Mar 09 14:57:19 vm kernel: gud 1-1:27.0: [drm:__drm_fb_helper_initial_config_and_unlock] test CRTC 0 primary plane Mar 09 14:57:19 vm kernel: gud 1-1:27.0: [drm:drm_fb_helper_generic_probe] surface width(400), height(240) and bpp(32) Mar 09 14:57:19 vm kernel: [drm:drm_mode_addfb2] [FB:36] Mar 09 14:57:19 vm kernel: [drm:drm_mode_object_put.part.0] OBJ ID: 36 (2) Mar 09 14:57:19 vm kernel: gud 1-1:27.0: [drm] fb0: guddrmfb frame buffer device
It looks good, I think? However, neither GUD_REQ_SET_CONTROLLER_ENABLE nor GUD_REQ_SET_DISPLAY_ENABLE is ever called, and there is no bulk output if I write to /dev/fb0.
Can you tell what's missing?
Many thanks and kind regards
//Peter
Den 09.03.2021 15.02, skrev Peter Stuge:
Hello Noralf,
I've made some progress with my test device. I'm implementing R1 first and once that works I'll test RGB111 as well. Along the way I've found a couple of things in the code:
Noralf Trønnes wrote:
+++ b/drivers/gpu/drm/gud/gud_drv.c
..
+static int gud_probe(struct usb_interface *intf, const struct usb_device_id *id)
..
if (format == GUD_DRM_FORMAT_R1)
continue; /* Internal not for userspace */
You already found RGB111 missing here.
+static int gud_usb_control_msg(struct usb_interface *intf, bool in,
u8 request, u16 value, void *buf, size_t len)
..
+static int gud_usb_transfer(struct gud_device *gdrm, bool in, u8 request, u16 index,
..
- ret = gud_usb_control_msg(intf, in, request, index, buf, len);
The u16 index parameter to gud_usb_transfer() and at least also gud_usb_{get,set,get_u8,set_u8}() is eventually passed in u16 value in the call to gud_usb_control_msg(), which had me confused for a bit.
What do you think about renaming all of those parameters to wValue, to show that and where they are part of the control request? I think it would help make the protocol more clear.
Only connector requests use this value and in that case it's the connector index. I need to get this driver applied now, and can't spend more time polishing it, so I'll just keep it as-is.
Finally, an actual bug:
- ret = gud_get_properties(gdrm);
- if (ret) {
dev_err(dev, "Failed to get properties (error=%d)\n", ret);
return ret;
- }
If gud_get_properties() and gud_connector_add_properties() receive and process (only!) one or more unknown properties then they return the number of bytes received from the device rather than 0.
I fixed this by setting ret = 0; before the for() loop, but maybe you want to do it another way.
I'll do that.
I found this because I can't get my device to send 0 bytes IN when the host requests more, if I provide no data the request STALLs. This is for sure a bug in my device and I'll come back to it, but for now I added a dummy 65535 property as a workaround.
What do you think about formalizing this, adding an actual dummy property?
I want to avoid that. I'd rather add a descriptor flag GUD_DISPLAY_FLAG_CANT_RETURN_ZERO or something for such devices. Also this property warning is just a debug message so not a very big problem. Worse is EDID if you don't want to support that since it prints an error in that case.
Or maybe adding flags to the display descriptor for "I have properties", "I have connector properties" and "I have EDID" ?
Then I might as well go back to the previous version and have count in the structs won't I ;)
+++ b/drivers/gpu/drm/gud/gud_pipe.c
..
+static int gud_prep_flush(struct gud_device *gdrm, struct drm_framebuffer *fb,
..
- /*
* Imported buffers are assumed to be write-combined and thus uncached
* with slow reads (at least on ARM).
*/
- if (format != fb->format) {
if (format->format == GUD_DRM_FORMAT_R1) {
len = gud_xrgb8888_to_r124(buf, format, vaddr, fb, rect);
if (!len) {
ret = -ENOMEM;
goto end_cpu_access;
}
} else if (format->format == DRM_FORMAT_RGB565) {
drm_fb_xrgb8888_to_rgb565(buf, vaddr, fb, rect, gud_is_big_endian());
} else {
len = gud_xrgb8888_to_color(buf, format, vaddr, fb, rect);
}
Does this section also need a RGB111 case?
gud_xrgb8888_to_color() handles that, although broken in this version for partial updates. Here's the fix: https://gist.github.com/notro/a94d381cf98b7e15fcddbc90e4af0a21
+void gud_pipe_update(struct drm_simple_display_pipe *pipe,
..
- if (fb && (crtc->state->mode_changed || crtc->state->connectors_changed))
gud_usb_set(gdrm, GUD_REQ_SET_STATE_COMMIT, 0, NULL, 0);
You mentioned that commit must not fail; what happens/should happen if a request does fail in pipe_update()? Some reasons could be that the device was unplugged, a bad cable is glitchy or even that some device doesn't even implement STATE_COMMIT or does it incorrectly and will report back failure?
The failure is just ignored since there's nothing to be done. The error counter for the debufgs file is increased, making it somewhat visible.
I've chosen to only print probe errors, flush errors and devices returning incorrect reply lengths. I hate drivers that fill the logs with errors, but I might have gone to far on the silent side, I don't know. But one nice thing about DRM is that debug is always builtin and can be enabled.
+++ b/include/drm/gud.h
..
- #define GUD_STATUS_REQUEST_NOT_SUPPORTED 0x02
Maybe this can be removed? SET_VERSION has been removed so it's no longer used anywhere, and in any case devices typically signal that requests are unsupported using a protocol STALL, which comes back as -EPIPE from the USB stack.
The driver issues a GUD_REQ_GET_STATUS on -EPIPE to find out what the exact problem is. GUD_STATUS_REQUEST_NOT_SUPPORTED is returned by the device on an unsupported/unrecognised request.
Finally, here's the drm debug output when I connect my device:
Mar 09 14:57:19 vm kernel: usb 1-1: new full-speed USB device number 24 using uhci_hcd Mar 09 14:57:19 vm kernel: gud 1-1:27.0: [drm:gud_probe] version=1 flags=0x2 compression=0x0 max_buffer_size=0 Mar 09 14:57:19 vm kernel: gud 1-1:27.0: [drm:gud_usb_transfer] get: request=0x40 index=0 len=32 Mar 09 14:57:19 vm kernel: gud 1-1:27.0: [drm:gud_usb_transfer] get: request=0x41 index=0 len=320 Mar 09 14:57:19 vm kernel: gud 1-1:27.0: [drm:gud_probe] Ignoring unknown property: 65535 Mar 09 14:57:19 vm kernel: gud 1-1:27.0: [drm:gud_usb_transfer] get: request=0x50 index=0 len=256 Mar 09 14:57:19 vm kernel: gud 1-1:27.0: [drm:gud_get_connectors] Connector: index=0 type=0 flags=0x0 Mar 09 14:57:19 vm kernel: gud 1-1:27.0: [drm:gud_usb_transfer] get: request=0x51 index=0 len=320 Mar 09 14:57:19 vm kernel: gud 1-1:27.0: [drm:gud_get_connectors] property: 65535 = 0(0x0) Mar 09 14:57:19 vm kernel: gud 1-1:27.0: [drm:gud_get_connectors] Ignoring unknown property: 65535 Mar 09 14:57:19 vm kernel: [drm:drm_minor_register] Mar 09 14:57:19 vm kernel: [drm:drm_minor_register] Mar 09 14:57:19 vm kernel: [drm:drm_minor_register] new minor registered 0 Mar 09 14:57:19 vm kernel: [drm:drm_sysfs_connector_add] adding "USB-1" to sysfs Mar 09 14:57:19 vm kernel: [drm:drm_sysfs_hotplug_event] generating hotplug event Mar 09 14:57:19 vm kernel: [drm] Initialized gud 1.0.0 20200422 for 1-1:27.0 on minor 0 Mar 09 14:57:19 vm kernel: [drm:drm_client_modeset_probe] Mar 09 14:57:19 vm kernel: [drm:drm_mode_object_get] OBJ ID: 35 (2) Mar 09 14:57:19 vm kernel: [drm:drm_helper_probe_single_connector_modes] [CONNECTOR:35:USB-1] Mar 09 14:57:19 vm kernel: gud 1-1:27.0: [drm:gud_usb_transfer] set: request=0x53 index=0 len=0 Mar 09 14:57:19 vm kernel: gud 1-1:27.0: [drm:gud_usb_transfer] get: request=0x54 index=0 len=1 Mar 09 14:57:19 vm kernel: [drm:drm_helper_probe_single_connector_modes] [CONNECTOR:35:USB-1] status updated from unknown to connected Mar 09 14:57:19 vm kernel: gud 1-1:27.0: [drm:gud_usb_transfer] get: request=0x56 index=0 len=2048 Mar 09 14:57:19 vm kernel: [drm:drm_sysfs_hotplug_event] generating hotplug event Mar 09 14:57:19 vm kernel: gud 1-1:27.0: USB-1: Invalid EDID size (ret=1) Mar 09 14:57:19 vm kernel: gud 1-1:27.0: [drm:gud_usb_transfer] get: request=0x55 index=0 len=3072 Mar 09 14:57:19 vm kernel: [drm:drm_helper_probe_single_connector_modes] [CONNECTOR:35:USB-1] probed modes : Mar 09 14:57:19 vm kernel: [drm:drm_mode_debug_printmodeline] Modeline "400x240": 104 10000 400 400 400 400 240 240 240 240 0x40 0x0 Mar 09 14:57:19 vm kernel: [drm:drm_client_modeset_probe] connector 35 enabled? yes Mar 09 14:57:19 vm kernel: [drm:drm_client_modeset_probe] Not using firmware configuration Mar 09 14:57:19 vm kernel: [drm:drm_client_modeset_probe] looking for cmdline mode on connector 35 Mar 09 14:57:19 vm kernel: [drm:drm_client_modeset_probe] looking for preferred mode on connector 35 0 Mar 09 14:57:19 vm kernel: [drm:drm_client_modeset_probe] found mode 400x240 Mar 09 14:57:19 vm kernel: [drm:drm_client_modeset_probe] picking CRTCs for 400x240 config Mar 09 14:57:19 vm kernel: [drm:drm_client_modeset_probe] desired mode 400x240 set on crtc 33 (0,0) Mar 09 14:57:19 vm kernel: [drm:drm_mode_object_get] OBJ ID: 35 (2) Mar 09 14:57:19 vm kernel: [drm:drm_mode_object_put.part.0] OBJ ID: 35 (3) Mar 09 14:57:19 vm kernel: gud 1-1:27.0: [drm:__drm_fb_helper_initial_config_and_unlock] test CRTC 0 primary plane Mar 09 14:57:19 vm kernel: gud 1-1:27.0: [drm:drm_fb_helper_generic_probe] surface width(400), height(240) and bpp(32) Mar 09 14:57:19 vm kernel: [drm:drm_mode_addfb2] [FB:36] Mar 09 14:57:19 vm kernel: [drm:drm_mode_object_put.part.0] OBJ ID: 36 (2) Mar 09 14:57:19 vm kernel: gud 1-1:27.0: [drm] fb0: guddrmfb frame buffer device
It looks good, I think? However, neither GUD_REQ_SET_CONTROLLER_ENABLE nor GUD_REQ_SET_DISPLAY_ENABLE is ever called, and there is no bulk output if I write to /dev/fb0.
Can you tell what's missing?
IIRC you need ioctl FBIOPUT_VSCREENINFO to enable the pipeline. You can use 'con2fbmap' to put a console on the fbdev or use 'fbi' to show an image. They will do the necessary ioctls.
I use modetest for simple testing: https://github.com/notro/tinydrm/wiki/Development#modetest
I have made tool that employs usbmon to show what happens just outside the wire (runs on the host): https://github.com/notro/gud/blob/master/tools/monitor.py
I haven't got a proper USB analyser, so this was second best. I was primarily looking for performance numbers, but it has been helpful in debugging problems as well. It's not perfect, GUD_DISPLAY_FLAG_FULL_UPDATE is not handled for instance.
Depending on how long it takes for the DMA mask dependency patch to show up in drm-misc-next, I will either publish a new version or apply the current and provide patches with the necessary fixes. It's tiresome to stay out-of-tree for a long time, DRM is constantly changing. And I've spent way to long on this project already. This is one of those things that would never have happened if I knew how long it would take :)
Thanks for helping me improve it, especially the protocol which is difficult to change after the fact.
Noralf.
Den 09.03.2021 19.07, skrev Noralf Trønnes:
Den 09.03.2021 15.02, skrev Peter Stuge:
Hello Noralf,
I've made some progress with my test device. I'm implementing R1 first and once that works I'll test RGB111 as well. Along the way I've found a couple of things in the code:
Depending on how long it takes for the DMA mask dependency patch to show up in drm-misc-next, I will either publish a new version or apply the current and provide patches with the necessary fixes.
In case I apply this version, are you happy enough with it that you want to give an ack or r-b?
Noralf.
Noralf Trønnes wrote:
The u16 index parameter to gud_usb_transfer() and at least also gud_usb_{get,set,get_u8,set_u8}() is eventually passed in u16 value in the call to gud_usb_control_msg(), which had me confused for a bit.
What do you think about renaming all of those parameters to wValue,
Only connector requests use this value and in that case it's the connector index. I need to get this driver applied now, and can't spend more time polishing it, so I'll just keep it as-is.
Okay.
I found this because I can't get my device to send 0 bytes IN when the host requests more, if I provide no data the request STALLs. This is for sure a bug in my device
..
What do you think about formalizing this, adding an actual dummy property?
I want to avoid that.
Ack. I too prefer not adding workarounds, this needs to be fixed in my device, but I wanted to bring it up.
I'd rather add a descriptor flag GUD_DISPLAY_FLAG_CANT_RETURN_ZERO or something for such devices. Also this property warning is just a debug message so not a very big problem.
I'd rather not squat a currently-unused property that may be allocated later.
Worse is EDID if you don't want to support that since it prints an error in that case.
I'll fix my device. If that proves impossible I can still add EDID bytes.
Or maybe adding flags to the display descriptor for "I have properties", "I have connector properties" and "I have EDID" ?
Then I might as well go back to the previous version and have count in the structs won't I ;)
That's true. Forget the workarounds.
gud_xrgb8888_to_color() handles that, although broken in this version for partial updates. Here's the fix: https://gist.github.com/notro/a94d381cf98b7e15fcddbc90e4af0a21
Thanks.
I've chosen to only print probe errors, flush errors and devices returning incorrect reply lengths. I hate drivers that fill the logs with errors, but I might have gone to far on the silent side, I don't know. But one nice thing about DRM is that debug is always builtin and can be enabled.
I think you've struck a good balance. The error messages I've seen were all unique and immediately led me to the right place in the driver. That's really helpful.
Finally, here's the drm debug output when I connect my device:
..
It looks good, I think? However, neither GUD_REQ_SET_CONTROLLER_ENABLE nor GUD_REQ_SET_DISPLAY_ENABLE is ever called, and there is no bulk output if I write to /dev/fb0.
Can you tell what's missing?
IIRC you need ioctl FBIOPUT_VSCREENINFO to enable the pipeline. You can use 'con2fbmap' to put a console on the fbdev or use 'fbi' to show an image. They will do the necessary ioctls.
Thanks for the hints! I've used fbi, but remembered writing directly to /dev/fb0 without ioctl:s. I think that must have been after the console had already been moved to the framebuffer and thus activated the pipe. I dug up a version of fbset which calls that ioctl.
I use modetest for simple testing: https://github.com/notro/tinydrm/wiki/Development#modetest
Ack, libdrm just has an inconveniently long dependency tail for my test environment. But fbset + dd works.
I have made tool that employs usbmon to show what happens just outside the wire (runs on the host): https://github.com/notro/gud/blob/master/tools/monitor.py I haven't got a proper USB analyser, so this was second best.
Nice idea! I've been using usbmon as well during development, it's great. I find a hardware analyzer is only really neccessary for lowlevel problems like my no-0-bytes bug.
Depending on how long it takes for the DMA mask dependency patch to show up in drm-misc-next, I will either publish a new version or apply the current and provide patches with the necessary fixes.
I look forward to it landing.
This is one of those things that would never have happened if I knew how long it would take :)
Thanks again for working on this great idea! I'm happy that I can help.
Noralf Trønnes wrote:
Depending on how long it takes for the DMA mask dependency patch to show up in drm-misc-next, I will either publish a new version or apply the current and provide patches with the necessary fixes.
In case I apply this version, are you happy enough with it that you want to give an ack or r-b?
I've now tested R1 and RGB111 and I think I've found two more things:
I didn't receive the expected bits/bytes for RGB111 on the bulk endpoint, I think because of how components were extracted in gud_xrgb8888_to_color().
Changing to the following gets me the expected (X R1 G1 B1 X R2 G2 B2) bytes:
r = (*pix32 >> 8) & 0xff; g = (*pix32 >> 16) & 0xff; b = (*pix32++ >> 24) & 0xff;
Then, gud_xrgb8888_to_color() and maybe even gud_xrgb8888_to_r124() seem to be host endian dependent, at least because of that pix32, but maybe more? I don't know whether drm guarantees "native" XRGB byte sequence in memory, then I guess the pix32 is okay? Please take a look?
Finally my very last ask: Please consider renaming GUD_PIXEL_FORMAT_RGB111 to GUD_PIXEL_FORMAT_XRGB1111?
Someone may want to add the bit fiddling to output actual RGB111 data later, then that name would be occupied, and XRGB1111 would describe the current data format more accurately.
With the two ret=0 fixes, your gist and the three things above addressed I'm happy to say:
Reviewed-By: Peter Stuge peter@stuge.se
R1 and RGB111 are: (if v8 perhaps for a separate RGB commit)
Tested-By: Peter Stuge peter@stuge.se
Later I think there's good potential for performance optimization. I noticed that the drm pipe flush submits the frame as 64 byte bulk transfers, which becomes slow. The USB stack can take at least 16kb if not far more and will split as needed. The split into single 64-byte bulk packets is even done by the host controller hardware if it is given the chance. Some numbers:
usbmon during drm flush of 400x240 RGB111 (48000 byte) shows 750ms from first submit to last complete:
c1b883c0 0.503674 S Bo:4:022:1 - 64 = 4f7f2900 1.254213 C Bo:4:022:1 0 64 >
A simple userspace program with one libusb_bulk_transfer() call needs just 300ms for the same data (libusb splits it into 3x16kb transfers):
$ time ./bo < test266.xrgb1111 will output 48000 bytes
real 0m0.298s user 0m0.002s sys 0m0.008s
$
The display is slow, but the difference is noticeable. Something for later.
Kind regards
//Peter
Den 10.03.2021 05.55, skrev Peter Stuge:
Noralf Trønnes wrote:
Depending on how long it takes for the DMA mask dependency patch to show up in drm-misc-next, I will either publish a new version or apply the current and provide patches with the necessary fixes.
In case I apply this version, are you happy enough with it that you want to give an ack or r-b?
I've now tested R1 and RGB111 and I think I've found two more things:
I didn't receive the expected bits/bytes for RGB111 on the bulk endpoint, I think because of how components were extracted in gud_xrgb8888_to_color().
Changing to the following gets me the expected (X R1 G1 B1 X R2 G2 B2) bytes:
r = (*pix32 >> 8) & 0xff; g = (*pix32 >> 16) & 0xff; b = (*pix32++ >> 24) & 0xff;
We're accessing the whole word here through pix32, no byte access, so endianess doesn't come into play. This change will flip r and b, which gives: XRGB8888 -> XBGR1111
BGR is a common thing on controllers, are you sure yours are set to RGB and not BGR?
And the 0xff masking isn't necessary since we're assigning to a byte, right?
I haven't got a native R1G1B1 display so I have emulated and I do get the expected colors. This is the conversion function I use on the device which I think is correct:
static size_t rgb111_to_rgb565(uint16_t *dst, uint8_t *src, uint16_t src_width, uint16_t src_height) { uint8_t rgb111, val = 0; size_t len = 0;
for (uint16_t y = 0; y < src_height; y++) { for (uint16_t x = 0; x < src_width; x++) { if (!(x % 2)) val = *src++; rgb111 = val >> 4; *dst++ = ((rgb111 & 0x04) << 13) | ((rgb111 & 0x02) << 9) | ((rgb111 & 0x01) << 4); len += sizeof(*dst); val <<= 4; } }
return len; }
Then, gud_xrgb8888_to_color() and maybe even gud_xrgb8888_to_r124() seem to be host endian dependent, at least because of that pix32, but maybe more? I don't know whether drm guarantees "native" XRGB byte sequence in memory, then I guess the pix32 is okay? Please take a look?
Finally my very last ask: Please consider renaming GUD_PIXEL_FORMAT_RGB111 to GUD_PIXEL_FORMAT_XRGB1111?
It has crossed my mind, I'll change it.
Noralf.
Noralf Trønnes wrote:
I didn't receive the expected bits/bytes for RGB111 on the bulk endpoint, I think because of how components were extracted in gud_xrgb8888_to_color().
Changing to the following gets me the expected (X R1 G1 B1 X R2 G2 B2) bytes:
r = (*pix32 >> 8) & 0xff; g = (*pix32 >> 16) & 0xff; b = (*pix32++ >> 24) & 0xff;
We're accessing the whole word here through pix32, no byte access, so endianess doesn't come into play.
Endianness matters because parts of pix32 are used.
Software only sees bytes (or larger) because addresses are byte granular, but must pay attention to the bit order when dealing with smaller values inside larger memory accesses.
Given 4 bytes of memory {0x11, 0x22, 0x33, 0x44} at address A, both LE and BE machines appear the same when accessing individual bytes, but with uint32_t *a32 = A then a32[0] is 0x44332211 on LE and 0x11223344 on BE.
Hence the question: What does DRM promise about the XRGB8888 mode?
Is it guaranteed that the first byte in memory is always unused, the second represents red, the third green and the fourth blue (endianess agnostic)? I'd expect this and I guess that it is the case, but I don't know DRM?
Or is it instead guaranteed that when accessed natively as one 32-bit value the blue component is always in the most significant byte (endianess abstracted, always LE in memory) or always in the least significant byte (endianess abstracted, always BE in memory)? This would be annoying for userspace, but well, it's possible.
In the abstracted (latter) case pix32 would work, but could still be questioned on style, and in fact, pix32 didn't work for me, so at a minimum the byte order would be the reverse.
In the agnostic (former) case your code was correct for BE and mine for LE, but I'd then suggest using a u8 * to both work correctly everywhere and be obvious.
This change will flip r and b, which gives: XRGB8888 -> XBGR1111
The old code was: r = *pix32 >> 16; g = *pix32 >> 8; b = *pix32++;
On my LE machine this set r to the third byte (G), g to the second (R) and b to the first (X), explaining the color confusion that I saw.
BGR is a common thing on controllers, are you sure yours are set to RGB and not BGR?
Yes; I've verified that my display takes red in MSB both in its data sheet and by writing raw bits to it on a system without the gud driver.
And the 0xff masking isn't necessary since we're assigning to a byte, right?
Not strictly neccessary but I like to do it anyway, both to be explicit and also to ensure that the compiler will never sign extend, if types are changed or if values become treated as signed and/or larger by the compiler because the code is changed.
It's frustrating to debug such unexpected changes in behavior due to a type change or calculation change, but if you find it too defensive then go ahead and remove it, if pix32 does stay.
I haven't got a native R1G1B1 display so I have emulated and I do get the expected colors. This is the conversion function I use on the device which I think is correct:
static size_t rgb111_to_rgb565(uint16_t *dst, uint8_t *src, uint16_t src_width, uint16_t src_height) { uint8_t rgb111, val = 0; size_t len = 0;
for (uint16_t y = 0; y < src_height; y++) { for (uint16_t x = 0; x < src_width; x++) { if (!(x % 2)) val = *src++; rgb111 = val >> 4; *dst++ = ((rgb111 & 0x04) << 13) | ((rgb111 & 0x02) << 9) | ((rgb111 & 0x01) << 4);
I'm afraid this isn't correct. Two wrongs end up cancelling each other out and it's not so obvious because the destination has symmetric (565) components.
If you were to convert to xrgb8888 in the same way I think you'd also see some color confusion, and in any case blue is getting lost already in gud_xrgb8888_to_color() on LE.
//Peter
Den 11.03.2021 15.48, skrev Peter Stuge:
Noralf Trønnes wrote:
I didn't receive the expected bits/bytes for RGB111 on the bulk endpoint, I think because of how components were extracted in gud_xrgb8888_to_color().
Changing to the following gets me the expected (X R1 G1 B1 X R2 G2 B2) bytes:
r = (*pix32 >> 8) & 0xff; g = (*pix32 >> 16) & 0xff; b = (*pix32++ >> 24) & 0xff;
We're accessing the whole word here through pix32, no byte access, so endianess doesn't come into play.
Endianness matters because parts of pix32 are used.
This code:
#include <stdio.h> #include <stdint.h>
void main() { volatile uint32_t endian = 0x01234567; uint32_t v = 0xaabbccdd; uint32_t *pix32 = &v; uint8_t r, g, b, *p;
r = *pix32 >> 16; g = *pix32 >> 8; b = *pix32++;
printf("xrgb8888=%08x\n", v);
printf("32-bit access:\n"); printf("r=%02x\n", r); printf("g=%02x\n", g); printf("b=%02x\n", b);
printf("Byte access on %s:\n", (*((uint8_t*)(&endian))) == 0x67 ? "LE" : "BE"); p = (uint8_t *)&v; printf("r=%02x\n", p[1]); printf("g=%02x\n", p[2]); printf("b=%02x\n", p[3]); }
prints:
xrgb8888=aabbccdd 32-bit access: r=bb g=cc b=dd Byte access on LE: r=cc g=bb b=aa
Software only sees bytes (or larger) because addresses are byte granular, but must pay attention to the bit order when dealing with smaller values inside larger memory accesses.
Given 4 bytes of memory {0x11, 0x22, 0x33, 0x44} at address A, both LE and BE machines appear the same when accessing individual bytes, but with uint32_t *a32 = A then a32[0] is 0x44332211 on LE and 0x11223344 on BE.
Hence the question: What does DRM promise about the XRGB8888 mode?
That it's a 32-bit value. From include/uapi/drm/drm_fourcc.h:
/* 32 bpp RGB */ #define DRM_FORMAT_XRGB8888 fourcc_code('X', 'R', '2', '4') /* [31:0] x:R:G:B 8:8:8:8 little endian */
If a raw buffer was passed from a BE to an LE machine, there would be problems because of how the value is stored, but here it's the same endianness in userspace and kernel space.
There is code in gud_prep_flush() that handles a BE host with a multibyte format:
} else if (gud_is_big_endian() && format->cpp[0] > 1) { drm_fb_swab(buf, vaddr, fb, rect, !import_attach);
In this case we can't just pass on the raw buffer to the device since the protocol is LE, and thus have to swap the bytes to match up how they're stored in memory on the device.
I'm not loosing any of the colors when running modetest. This is the test image that modetest uses and it comes through just like that: https://commons.wikimedia.org/wiki/File:SMPTE_Color_Bars.svg
Noralf.
Is it guaranteed that the first byte in memory is always unused, the second represents red, the third green and the fourth blue (endianess agnostic)? I'd expect this and I guess that it is the case, but I don't know DRM?
Or is it instead guaranteed that when accessed natively as one 32-bit value the blue component is always in the most significant byte (endianess abstracted, always LE in memory) or always in the least significant byte (endianess abstracted, always BE in memory)? This would be annoying for userspace, but well, it's possible.
In the abstracted (latter) case pix32 would work, but could still be questioned on style, and in fact, pix32 didn't work for me, so at a minimum the byte order would be the reverse.
In the agnostic (former) case your code was correct for BE and mine for LE, but I'd then suggest using a u8 * to both work correctly everywhere and be obvious.
This change will flip r and b, which gives: XRGB8888 -> XBGR1111
The old code was: r = *pix32 >> 16; g = *pix32 >> 8; b = *pix32++;
On my LE machine this set r to the third byte (G), g to the second (R) and b to the first (X), explaining the color confusion that I saw.
BGR is a common thing on controllers, are you sure yours are set to RGB and not BGR?
Yes; I've verified that my display takes red in MSB both in its data sheet and by writing raw bits to it on a system without the gud driver.
And the 0xff masking isn't necessary since we're assigning to a byte, right?
Not strictly neccessary but I like to do it anyway, both to be explicit and also to ensure that the compiler will never sign extend, if types are changed or if values become treated as signed and/or larger by the compiler because the code is changed.
It's frustrating to debug such unexpected changes in behavior due to a type change or calculation change, but if you find it too defensive then go ahead and remove it, if pix32 does stay.
I haven't got a native R1G1B1 display so I have emulated and I do get the expected colors. This is the conversion function I use on the device which I think is correct:
static size_t rgb111_to_rgb565(uint16_t *dst, uint8_t *src, uint16_t src_width, uint16_t src_height) { uint8_t rgb111, val = 0; size_t len = 0;
for (uint16_t y = 0; y < src_height; y++) { for (uint16_t x = 0; x < src_width; x++) { if (!(x % 2)) val = *src++; rgb111 = val >> 4; *dst++ = ((rgb111 & 0x04) << 13) | ((rgb111 & 0x02) << 9) | ((rgb111 & 0x01) << 4);
I'm afraid this isn't correct. Two wrongs end up cancelling each other out and it's not so obvious because the destination has symmetric (565) components.
If you were to convert to xrgb8888 in the same way I think you'd also see some color confusion, and in any case blue is getting lost already in gud_xrgb8888_to_color() on LE.
//Peter
Noralf Trønnes wrote:
Endianness matters because parts of pix32 are used.
This code:
..
prints:
xrgb8888=aabbccdd 32-bit access: r=bb g=cc b=dd Byte access on LE: r=cc g=bb b=aa
As expected, and:
xrgb8888=aabbccdd 32-bit access: r=bb g=cc b=dd Byte access on BE: r=bb g=cc b=dd
I've done similar tests in the past and did another before my last mail.
We agree about endian effects. Apologies if I came across as overbearing!
Hence the question: What does DRM promise about the XRGB8888 mode?
That it's a 32-bit value. From include/uapi/drm/drm_fourcc.h:
/* 32 bpp RGB */ #define DRM_FORMAT_XRGB8888 fourcc_code('X', 'R', '2', '4') /* [31:0] x:R:G:B 8:8:8:8 little endian */
Okay, "[31:0] x:R:G:B 8:8:8:8" can certainly mean [31:24]=x [23:16]=R [15:8]=G [7:0]=B, which when stored "little endian" becomes B G R X in memory, for which your pix32 code is correct.
That's the reverse *memory* layout of what the name says :) but yes, the name then matches the representation seen by software. That's the "abstracted" case that I didn't expect, because I thought the name was refering to memory layout and because I was thinking about how traditional graphics adapter video memory has the R component at the lower address, at least in early linear modes.
I also didn't pay attention to the fbset output:
rgba 8/16,8/8,8/0,0/0
With drm format describing software pixel representation and per the fbset rgba description my test file was incorrect. I've recreated it with B G R X bytes and it shows correctly with your pix32 code.
Sending data directly to the device without the gud driver uses different data, so isn't actually a fair comparison, but I didn't change the device at all now, and that still works.
If a raw buffer was passed from a BE to an LE machine, there would be problems because of how the value is stored,
And swab would be required on a LE machine with a graphics adapter in a mode with X R G B memory layout, or that system would just never present XRGB8888 for that adapter/mode but perhaps something called BGRX8888 instead? I see.
but here it's the same endianness in userspace and kernel space.
Ack.
There is code in gud_prep_flush() that handles a BE host with a multibyte format:
} else if (gud_is_big_endian() && format->cpp[0] > 1) { drm_fb_swab(buf, vaddr, fb, rect, !import_attach);
In this case we can't just pass on the raw buffer to the device since the protocol is LE, and thus have to swap the bytes to match up how they're stored in memory on the device.
Ack.
I'm not loosing any of the colors when running modetest. This is the test image that modetest uses and it comes through just like that: https://commons.wikimedia.org/wiki/File:SMPTE_Color_Bars.svg
So your destination rgb565 buffer has a [15:11]=R [10:5]=G [4:0]=B pixel format, which stores as B+G G+R in memory, as opposed to R+G G+B. All right.
Thanks a lot for clearing up my misunderstanding of drm format names and my endianess concerns!
//Peter
On Thu, Mar 11, 2021 at 3:02 PM Peter Stuge peter@stuge.se wrote:
Hence the question: What does DRM promise about the XRGB8888 mode?
That it's a 32-bit value. From include/uapi/drm/drm_fourcc.h:
/* 32 bpp RGB */ #define DRM_FORMAT_XRGB8888 fourcc_code('X', 'R', '2', '4') /* [31:0] x:R:G:B 8:8:8:8 little endian */
Okay, "[31:0] x:R:G:B 8:8:8:8" can certainly mean [31:24]=x [23:16]=R [15:8]=G [7:0]=B, which when stored "little endian" becomes B G R X in memory, for which your pix32 code is correct.
That's the reverse *memory* layout of what the name says :) but yes, the name then matches the representation seen by software. That's the "abstracted" case that I didn't expect, because I thought the name was refering to memory layout and because I was thinking about how traditional graphics adapter video memory has the R component at the lower address, at least in early linear modes.
The definition of the formats is memory layout in little endian. The definition you see is of a 32-bit packed little-endian integer, which is a fixed memory layout.
Now, if you're on an actual big-endian platform, and you want to accept big-endian-packed formats, there's a bit of unpleasantness that goes on. Basically there are two options:
1. Ignore the above definition and interpret the formats as *big-endian* layouts. This is what nouveau and radeon do. They also don't support AddFB2 (which is what allows supplying a format) -- only AddFB which just has depth (and bpp). That's fine for nouveau and radeon because the relevant userspace just uses AddFB, and knows what the drivers want, so it all works out.
2. Comply with the above definition and set dev->mode_config.quirk_addfb_prefer_host_byte_order to false. This loses you native host packing of RGB565/etc, since they're just not defined as formats. There's a DRM_FORMAT_BIG_ENDIAN bit but it's not properly supported for anything but the 8888 formats.
I'm not sure why you guys were talking about BE in the first place, but since this is a topic I've looked into (in the context of moving nouveau from 1 to 2 - but that can't happen due to the reduced format availability), figured I'd share some of the current sad state.
Cheers,
-ilia
Ilia Mirkin wrote:
#define DRM_FORMAT_XRGB8888 fourcc_code('X', 'R', '2', '4') /* [31:0] x:R:G:B 8:8:8:8 little endian */
Okay, "[31:0] x:R:G:B 8:8:8:8" can certainly mean [31:24]=x [23:16]=R [15:8]=G [7:0]=B, which when stored "little endian" becomes B G R X in memory, for which your pix32 code is correct.
That's the reverse *memory* layout of what the name says :)
The definition of the formats is memory layout in little endian.
To clarify, my new (hopefully correct?) understanding is this:
XRGB8888 does *not* mean that address 0=X, 1=R, 2=G, 3=B, but that the most significant byte in a packed XRGB8888 32-bit integer is X and the least significant byte is B, and that this is the case both on LE and BE machines.
I previously thought that XRGB8888 indicated the memory byte order of components being X R G B regardless of machine endianess, but now understand XRGB to mean the MSB..LSB order of component bytes within the 32-bit integer, as seen by software, not the order of bytes in memory.
The definition you see is of a 32-bit packed little-endian integer, which is a fixed memory layout.
In the header definition I'm not completely sure what the "little endian" means - I guess it refers to how the 32-bit integer will be stored in memory, but it could also refer to the order of component packing within.
Noralf's code and testing and also what fbset tells me seems to support this understanding, at least on LE machines.
Now, if you're on an actual big-endian platform, and you want to accept big-endian-packed formats, there's a bit of unpleasantness that goes on.
In the particular case of XRGB8888 that Noralf has implemented and I've tested every pixel is translated "manually" anyway; each component byte is downconverted to a single bit, but this use case is mostly for smaller resolutions, so no too big deal.
I'm not sure why you guys were talking about BE in the first place,
I was worried that the translation didn't consider endianess.
Noralf, looking at the 3/3 patch again now, drm_fb_swab() gets called on BE when format == fb->format, but does it also need to be called on BE they are different, or will circumstances be such that it's never neccessary then?
Thanks and sorry if I'm confusing things needlessly
//Peter
On Thu, Mar 11, 2021 at 5:58 PM Peter Stuge peter@stuge.se wrote:
Ilia Mirkin wrote:
#define DRM_FORMAT_XRGB8888 fourcc_code('X', 'R', '2', '4') /* [31:0] x:R:G:B 8:8:8:8 little endian */
Okay, "[31:0] x:R:G:B 8:8:8:8" can certainly mean [31:24]=x [23:16]=R [15:8]=G [7:0]=B, which when stored "little endian" becomes B G R X in memory, for which your pix32 code is correct.
That's the reverse *memory* layout of what the name says :)
The definition of the formats is memory layout in little endian.
To clarify, my new (hopefully correct?) understanding is this:
XRGB8888 does *not* mean that address 0=X, 1=R, 2=G, 3=B, but that the most significant byte in a packed XRGB8888 32-bit integer is X and the least significant byte is B, and that this is the case both on LE and BE machines.
Not quite.
XRGB8888 means that the memory layout should match a 32-bit integer, stored as LE, with the low bits being B, next bits being G, etc. This translates to byte 0 = B, byte 1 = G, etc. If you're on a BE system, and you're handed a XRGB8888 buffer, it still expects that byte 0 = B, etc (except as I outlined, some drivers which are from before these formats were a thing, sort of do their own thing). Thankfully this is equivalent to BGRX8888 (big-endian packing), so you can just munge the format. Not so with e.g. RGB565 though (since the components don't fall on byte boundaries).
I previously thought that XRGB8888 indicated the memory byte order of components being X R G B regardless of machine endianess, but now understand XRGB to mean the MSB..LSB order of component bytes within the 32-bit integer, as seen by software, not the order of bytes in memory.
There are about 100 conventions, and they all manage to be different from each other. Packed vs array. BE vs LE. If you're *not* confused, that should be a red flag.
[...]
I'm not sure why you guys were talking about BE in the first place,
I was worried that the translation didn't consider endianess.
The translation in gud_xrgb8888_to_color definitely seems suspect. There's also a gud_is_big_endian, but I'm guessing this applies to the downstream device rather than the host system. I didn't check if dev->mode_config.quirk_addfb_prefer_host_byte_order is set -- that setting dictates whether these formats are in host-byte-order (and AddFB2 is disabled, so buffers can only be specified with depth/bpp and ambiguous component orders) or in LE byte order (and userspace can use AddFB2 which gives allows precise formats for these buffers). Not 100% sure what something like Xorg's modesetting driver does, TBH. This is a very poorly-tested scenario.
-ilia
Ilia Mirkin wrote:
XRGB8888 means that the memory layout should match a 32-bit integer, stored as LE, with the low bits being B, next bits being G, etc. This translates to byte 0 = B, byte 1 = G, etc. If you're on a BE system, and you're handed a XRGB8888 buffer, it still expects that byte 0 = B, etc (except as I outlined, some drivers which are from before these formats were a thing, sort of do their own thing). Thankfully this is equivalent to BGRX8888 (big-endian packing), so you can just munge the format.
I understand! Thanks a lot for clarifying.
It makes much more sense to me that the format indeed describes what is in memory rather than how pixels look to software.
I'm not sure why you guys were talking about BE in the first place,
I was worried that the translation didn't consider endianess.
The translation in gud_xrgb8888_to_color definitely seems suspect.
So to me this means that the gud_pipe translations from XRGB8888 to the 1-bit formats *do* have to adjust for the reversed order on BE.
There's also a gud_is_big_endian, but I'm guessing this applies to the downstream device rather than the host system.
gud_is_big_endian() is a static bool wrapper around defined(__BIG_ENDIAN) so yes, it applies to the host.
With memory layout being constant I again think gud_xrgb8888_to_color() needs to take further steps to work correctly also on BE hosts. (Maybe that's le32_to_cpu(*pix32), maybe drm_fb_swab(), maybe something else?)
I didn't check if dev->mode_config.quirk_addfb_prefer_host_byte_order is set
I can't tell if that's helpful, probably Noralf can.
Thanks a lot
//Peter
Den 12.03.2021 05.32, skrev Peter Stuge:
Ilia Mirkin wrote:
XRGB8888 means that the memory layout should match a 32-bit integer, stored as LE, with the low bits being B, next bits being G, etc. This translates to byte 0 = B, byte 1 = G, etc. If you're on a BE system, and you're handed a XRGB8888 buffer, it still expects that byte 0 = B, etc (except as I outlined, some drivers which are from before these formats were a thing, sort of do their own thing). Thankfully this is equivalent to BGRX8888 (big-endian packing), so you can just munge the format.
I understand! Thanks a lot for clarifying.
It makes much more sense to me that the format indeed describes what is in memory rather than how pixels look to software.
I'm not sure why you guys were talking about BE in the first place,
I was worried that the translation didn't consider endianess.
The translation in gud_xrgb8888_to_color definitely seems suspect.
So to me this means that the gud_pipe translations from XRGB8888 to the 1-bit formats *do* have to adjust for the reversed order on BE.
There's also a gud_is_big_endian, but I'm guessing this applies to the downstream device rather than the host system.
gud_is_big_endian() is a static bool wrapper around defined(__BIG_ENDIAN) so yes, it applies to the host.
With memory layout being constant I again think gud_xrgb8888_to_color() needs to take further steps to work correctly also on BE hosts. (Maybe that's le32_to_cpu(*pix32), maybe drm_fb_swab(), maybe something else?)
I didn't check if dev->mode_config.quirk_addfb_prefer_host_byte_order is set
I can't tell if that's helpful, probably Noralf can.
I skimmed a discussion a while back around BE and all it's problems through the whole graphics stack and my take away was that I needed a BE machine so I could test the whole stack to make sure it works. Judging from what Ilia says I've been wrong in my assumptions so the driver is probably broken on BE (along with my SPI display drivers). I'm not going to try and fix this now, I need someone with a BE machine that can test the whole stack and make sure it actually works. Me doing more guesswork is not going to work out well I think :)
I'll add a FIXME in gud_pipe.c that BE is probably broken and link to this discussion.
It seems Gerd did some work to fix BE in the kernel: drm: byteorder fixes https://patchwork.freedesktop.org/series/49073/
But that seems to deal with the format value itself and not the buffer contents.
Noralf.
dri-devel@lists.freedesktop.org