From a5f7a5bb3b7f28ba7e4fa246775b29a0e5537255 Mon Sep 17 00:00:00 2001 From: Jinke Han Date: Tue, 8 Sep 2026 15:37:42 +0800 Subject: x86/kprobes: Fix crash when probing CS CALL instructions When using eBPF to probe CS CALL instructions within a function, a crash can be triggered. The eBPF tool probes offset 257 of the __hrtimer_run_queues() function: <__hrtimer_run_queues+249>: nopl 0x0(%rax,%rax,1) <__hrtimer_run_queues+254>: mov %r14,%rdi <__hrtimer_run_queues+257>: cs call <__x86_indirect_thunk_r12> <__hrtimer_run_queues+263>: mov %eax,%r12d <__hrtimer_run_queues+266>: xchg %ax,%ax <__hrtimer_run_queues+268>: mov %r13,%rdi Which triggers this crash: BUG: unable to handle page fault for address: 00000000000f41c9 #PF: supervisor write access in kernel mode #PF: error_code(0x0002) - not-present page PGD 0 P4D 0 Oops: 0002 [#1] SMP NOPTI CPU: 1 PID: 0 Comm: swapper/1 Kdump: loaded Tainted: P RIP: 0010:__hrtimer_run_queues+0x106/0x230 Note that __hrtimer_run_queues+0x106 is __hrtimer_run_queues+262, which is at the 6th byte of the above CS CALL instruction. Since the CS CALL instruction occupies 6 bytes, the exception occurred in the middle of that call instruction. The root cause is that when using eBPF tools to probe in the middle of a function, a kprobe with INT3 is used as the underlying implementation. During single-step emulation of the original CALL instruction, int3_emulate_call() assumes that the probed CALL instruction is 5 bytes long. However, the actual CS-prefixed CALL instruction occupies 6 bytes, so it constructs an incorrect exception return address. When the CPU returns from the kprobe handler, the next instruction to be executed is at the address of the last byte of that CS CALL instruction. Coincidentally, starting from that address, the CPU fetches and decodes a completely different instruction, which ultimately triggers a kernel crash. Fix the issue by using the actual instruction length obtained from the instruction decoder when constructing the exception return address, rather than relying on the hardcoded CALL_INSN_SIZE macro. [ mingo: Refined the changelog ] Fixes: 6256e668b7af ("x86/kprobes: Use int3 instead of debug trap for single-step") Suggested-by: Masami Hiramatsu (Google) Signed-off-by: Jinke Han Signed-off-by: Ingo Molnar Reviewed-by: Masami Hiramatsu (Google) Acked-by: Yafang Shao Acked-by: Borislav Petkov Cc: Peter Zijlstra Link: https://patch.msgid.link/20260908073742.GA10517@didi-ThinkCentre-M920t-N000 --- drivers/gpu/drm/udl/Kconfig | 13 + drivers/gpu/drm/udl/Makefile | 10 + drivers/gpu/drm/udl/udl_drv.c | 149 ++++++++++ drivers/gpu/drm/udl/udl_drv.h | 90 ++++++ drivers/gpu/drm/udl/udl_edid.c | 81 ++++++ drivers/gpu/drm/udl/udl_edid.h | 15 + drivers/gpu/drm/udl/udl_main.c | 386 ++++++++++++++++++++++++++ drivers/gpu/drm/udl/udl_modeset.c | 547 +++++++++++++++++++++++++++++++++++++ drivers/gpu/drm/udl/udl_proto.h | 68 +++++ drivers/gpu/drm/udl/udl_transfer.c | 217 +++++++++++++++ 10 files changed, 1576 insertions(+) create mode 100644 drivers/gpu/drm/udl/Kconfig create mode 100644 drivers/gpu/drm/udl/Makefile create mode 100644 drivers/gpu/drm/udl/udl_drv.c create mode 100644 drivers/gpu/drm/udl/udl_drv.h create mode 100644 drivers/gpu/drm/udl/udl_edid.c create mode 100644 drivers/gpu/drm/udl/udl_edid.h create mode 100644 drivers/gpu/drm/udl/udl_main.c create mode 100644 drivers/gpu/drm/udl/udl_modeset.c create mode 100644 drivers/gpu/drm/udl/udl_proto.h create mode 100644 drivers/gpu/drm/udl/udl_transfer.c (limited to 'drivers/gpu/drm/udl') diff --git a/drivers/gpu/drm/udl/Kconfig b/drivers/gpu/drm/udl/Kconfig new file mode 100644 index 000000000..d7a6abef7 --- /dev/null +++ b/drivers/gpu/drm/udl/Kconfig @@ -0,0 +1,13 @@ +# SPDX-License-Identifier: GPL-2.0-only +config DRM_UDL + tristate "DisplayLink" + depends on DRM + depends on USB + depends on USB_ARCH_HAS_HCD + depends on MMU + select DRM_CLIENT_SELECTION + select DRM_GEM_SHMEM_HELPER + select DRM_KMS_HELPER + help + This is a KMS driver for the USB displaylink video adapters. + Say M/Y to add support for these devices via drm/kms interfaces. diff --git a/drivers/gpu/drm/udl/Makefile b/drivers/gpu/drm/udl/Makefile new file mode 100644 index 000000000..43d69a16a --- /dev/null +++ b/drivers/gpu/drm/udl/Makefile @@ -0,0 +1,10 @@ +# SPDX-License-Identifier: GPL-2.0-only + +udl-y := \ + udl_drv.o \ + udl_edid.o \ + udl_main.o \ + udl_modeset.o \ + udl_transfer.o + +obj-$(CONFIG_DRM_UDL) := udl.o diff --git a/drivers/gpu/drm/udl/udl_drv.c b/drivers/gpu/drm/udl/udl_drv.c new file mode 100644 index 000000000..192298862 --- /dev/null +++ b/drivers/gpu/drm/udl/udl_drv.c @@ -0,0 +1,149 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (C) 2012 Red Hat + */ + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "udl_drv.h" + +static int udl_usb_suspend(struct usb_interface *interface, + pm_message_t message) +{ + struct drm_device *dev = usb_get_intfdata(interface); + struct udl_device *udl = to_udl(dev); + int ret; + + ret = drm_mode_config_helper_suspend(dev); + if (ret) + return ret; + + udl_sync_pending_urbs(udl); + return 0; +} + +static int udl_usb_resume(struct usb_interface *interface) +{ + struct drm_device *dev = usb_get_intfdata(interface); + + return drm_mode_config_helper_resume(dev); +} + +static int udl_usb_reset_resume(struct usb_interface *interface) +{ + struct drm_device *dev = usb_get_intfdata(interface); + struct udl_device *udl = to_udl(dev); + + udl_select_std_channel(udl); + + return drm_mode_config_helper_resume(dev); +} + +DEFINE_DRM_GEM_FOPS(udl_driver_fops); + +static const struct drm_driver driver = { + .driver_features = DRIVER_ATOMIC | DRIVER_GEM | DRIVER_MODESET, + + /* GEM hooks */ + .fops = &udl_driver_fops, + DRM_GEM_SHMEM_DRIVER_OPS, + DRM_FBDEV_SHMEM_DRIVER_OPS, + + .name = DRIVER_NAME, + .desc = DRIVER_DESC, + .major = DRIVER_MAJOR, + .minor = DRIVER_MINOR, + .patchlevel = DRIVER_PATCHLEVEL, +}; + +static struct udl_device *udl_driver_create(struct usb_interface *interface) +{ + struct udl_device *udl; + int r; + + udl = devm_drm_dev_alloc(&interface->dev, &driver, + struct udl_device, drm); + if (IS_ERR(udl)) + return udl; + + r = udl_init(udl); + if (r) + return ERR_PTR(r); + + usb_set_intfdata(interface, udl); + + return udl; +} + +static int udl_usb_probe(struct usb_interface *interface, + const struct usb_device_id *id) +{ + int r; + struct udl_device *udl; + + udl = udl_driver_create(interface); + if (IS_ERR(udl)) + return PTR_ERR(udl); + + r = drm_dev_register(&udl->drm, 0); + if (r) + return r; + + DRM_INFO("Initialized udl on minor %d\n", udl->drm.primary->index); + + drm_client_setup(&udl->drm, NULL); + + return 0; +} + +static void udl_usb_disconnect(struct usb_interface *interface) +{ + struct drm_device *dev = usb_get_intfdata(interface); + struct udl_device *udl = to_udl(dev); + + drm_dev_unplug(dev); + udl_drop_usb(udl); +} + +/* + * There are many DisplayLink-based graphics products, all with unique PIDs. + * So we match on DisplayLink's VID + Vendor-Defined Interface Class (0xff) + * We also require a match on SubClass (0x00) and Protocol (0x00), + * which is compatible with all known USB 2.0 era graphics chips and firmware, + * but allows DisplayLink to increment those for any future incompatible chips + */ +static const struct usb_device_id id_table[] = { + {.idVendor = 0x17e9, .bInterfaceClass = 0xff, + .bInterfaceSubClass = 0x00, + .bInterfaceProtocol = 0x00, + .match_flags = USB_DEVICE_ID_MATCH_VENDOR | + USB_DEVICE_ID_MATCH_INT_CLASS | + USB_DEVICE_ID_MATCH_INT_SUBCLASS | + USB_DEVICE_ID_MATCH_INT_PROTOCOL,}, + {}, +}; +MODULE_DEVICE_TABLE(usb, id_table); + +static struct usb_driver udl_driver = { + .name = "udl", + .probe = udl_usb_probe, + .disconnect = udl_usb_disconnect, + .suspend = udl_usb_suspend, + .resume = udl_usb_resume, + .reset_resume = udl_usb_reset_resume, + .id_table = id_table, +}; +module_usb_driver(udl_driver); +MODULE_DESCRIPTION("KMS driver for the USB displaylink video adapters"); +MODULE_LICENSE("GPL"); diff --git a/drivers/gpu/drm/udl/udl_drv.h b/drivers/gpu/drm/udl/udl_drv.h new file mode 100644 index 000000000..145bb95cc --- /dev/null +++ b/drivers/gpu/drm/udl/udl_drv.h @@ -0,0 +1,90 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (C) 2012 Red Hat + * + * based in parts on udlfb.c: + * Copyright (C) 2009 Roberto De Ioris + * Copyright (C) 2009 Jaya Kumar + * Copyright (C) 2009 Bernie Thompson + */ + +#ifndef UDL_DRV_H +#define UDL_DRV_H + +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +struct drm_mode_create_dumb; + +#define DRIVER_NAME "udl" +#define DRIVER_DESC "DisplayLink" + +#define DRIVER_MAJOR 0 +#define DRIVER_MINOR 0 +#define DRIVER_PATCHLEVEL 1 + +struct udl_device; + +struct urb_node { + struct list_head entry; + struct udl_device *dev; + struct urb *urb; +}; + +struct urb_list { + struct list_head list; + spinlock_t lock; + wait_queue_head_t sleep; + int available; + int count; + size_t size; +}; + +struct udl_device { + struct drm_device drm; + + unsigned long sku_pixel_limit; + + struct drm_plane primary_plane; + struct drm_crtc crtc; + struct drm_encoder encoder; + struct drm_connector connector; + + struct urb_list urbs; +}; + +#define to_udl(x) container_of(x, struct udl_device, drm) + +static inline struct usb_device *udl_to_usb_device(struct udl_device *udl) +{ + return interface_to_usbdev(to_usb_interface(udl->drm.dev)); +} + +/* modeset */ +int udl_modeset_init(struct udl_device *udl); +struct drm_connector *udl_connector_init(struct drm_device *dev); + +struct urb *udl_get_urb(struct udl_device *udl); + +int udl_submit_urb(struct udl_device *udl, struct urb *urb, size_t len); +void udl_sync_pending_urbs(struct udl_device *udl); +void udl_urb_completion(struct urb *urb); + +int udl_init(struct udl_device *udl); + +int udl_render_hline(struct udl_device *udl, int log_bpp, struct urb **urb_ptr, + const char *front, char **urb_buf_ptr, + u32 byte_offset, u32 device_byte_offset, u32 byte_width); + +int udl_drop_usb(struct udl_device *udl); +int udl_select_std_channel(struct udl_device *udl); + +#endif diff --git a/drivers/gpu/drm/udl/udl_edid.c b/drivers/gpu/drm/udl/udl_edid.c new file mode 100644 index 000000000..af4cff2a7 --- /dev/null +++ b/drivers/gpu/drm/udl/udl_edid.c @@ -0,0 +1,81 @@ +// SPDX-License-Identifier: GPL-2.0-only + +#include + +#include +#include +#include + +#include "udl_drv.h" +#include "udl_edid.h" + +static int udl_read_edid_block(void *data, u8 *buf, unsigned int block, size_t len) +{ + struct udl_device *udl = data; + struct drm_device *dev = &udl->drm; + struct usb_device *udev = udl_to_usb_device(udl); + u8 *read_buff; + int idx, ret; + size_t i; + + read_buff = kmalloc(2, GFP_KERNEL); + if (!read_buff) + return -ENOMEM; + + if (!drm_dev_enter(dev, &idx)) { + ret = -ENODEV; + goto err_kfree; + } + + for (i = 0; i < len; i++) { + int bval = (i + block * EDID_LENGTH) << 8; + + ret = usb_control_msg(udev, usb_rcvctrlpipe(udev, 0), + 0x02, (0x80 | (0x02 << 5)), bval, + 0xA1, read_buff, 2, USB_CTRL_GET_TIMEOUT); + if (ret < 0) { + drm_err(dev, "Read EDID byte %zu failed err %x\n", i, ret); + goto err_drm_dev_exit; + } else if (ret < 1) { + ret = -EIO; + drm_err(dev, "Read EDID byte %zu failed\n", i); + goto err_drm_dev_exit; + } + + buf[i] = read_buff[1]; + } + + drm_dev_exit(idx); + kfree(read_buff); + + return 0; + +err_drm_dev_exit: + drm_dev_exit(idx); +err_kfree: + kfree(read_buff); + return ret; +} + +bool udl_probe_edid(struct udl_device *udl) +{ + u8 hdr[8]; + int ret; + + ret = udl_read_edid_block(udl, hdr, 0, sizeof(hdr)); + if (ret) + return false; + + /* + * The adapter sends all-zeros if no monitor has been + * connected. We consider anything else a connection. + */ + return !mem_is_zero(hdr, sizeof(hdr)); +} + +const struct drm_edid *udl_edid_read(struct drm_connector *connector) +{ + struct udl_device *udl = to_udl(connector->dev); + + return drm_edid_read_custom(connector, udl_read_edid_block, udl); +} diff --git a/drivers/gpu/drm/udl/udl_edid.h b/drivers/gpu/drm/udl/udl_edid.h new file mode 100644 index 000000000..fe15ff375 --- /dev/null +++ b/drivers/gpu/drm/udl/udl_edid.h @@ -0,0 +1,15 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ + +#ifndef UDL_EDID_H +#define UDL_EDID_H + +#include + +struct drm_connector; +struct drm_edid; +struct udl_device; + +bool udl_probe_edid(struct udl_device *udl); +const struct drm_edid *udl_edid_read(struct drm_connector *connector); + +#endif diff --git a/drivers/gpu/drm/udl/udl_main.c b/drivers/gpu/drm/udl/udl_main.c new file mode 100644 index 000000000..17950fe3a --- /dev/null +++ b/drivers/gpu/drm/udl/udl_main.c @@ -0,0 +1,386 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (C) 2012 Red Hat + * + * based in parts on udlfb.c: + * Copyright (C) 2009 Roberto De Ioris + * Copyright (C) 2009 Jaya Kumar + * Copyright (C) 2009 Bernie Thompson + */ + +#include + +#include +#include +#include + +#include "udl_drv.h" + +/* -BULK_SIZE as per usb-skeleton. Can we get full page and avoid overhead? */ +#define BULK_SIZE 512 + +#define NR_USB_REQUEST_CHANNEL 0x12 + +#define MAX_TRANSFER (PAGE_SIZE*16 - BULK_SIZE) +#define WRITES_IN_FLIGHT (20) +#define MAX_VENDOR_DESCRIPTOR_SIZE 256 + +#define UDL_SKU_PIXEL_LIMIT_DEFAULT 2080000 + +static struct urb *udl_get_urb_locked(struct udl_device *udl, long timeout); + +/* + * Try to make sense of whatever we parse. Therefore return @end on + * errors, but don't fail hard. + */ +static const u8 *udl_parse_key_value_pair(struct udl_device *udl, const u8 *pos, const u8 *end) +{ + u16 key; + u8 len; + + /* read key */ + if (pos >= end - 2) + return end; + key = get_unaligned_le16(pos); + pos += 2; + + /* read value length */ + if (pos >= end - 1) + return end; + len = *pos++; + + /* read value */ + if (pos >= end - len) + return end; + switch (key) { + case 0x0200: { /* maximum number of pixels */ + unsigned int sku_pixel_limit; + + if (len < sizeof(__le32)) + break; + sku_pixel_limit = get_unaligned_le32(pos); + if (sku_pixel_limit >= 16 * UDL_SKU_PIXEL_LIMIT_DEFAULT) + break; /* almost 100 MiB, so probably bogus */ + udl->sku_pixel_limit = sku_pixel_limit; + break; + } + default: + break; + } + pos += len; + + return pos; +} + +static int udl_parse_vendor_descriptor(struct udl_device *udl) +{ + struct drm_device *dev = &udl->drm; + struct usb_device *udev = udl_to_usb_device(udl); + bool detected = false; + void *buf; + int ret; + unsigned int len; + const u8 *desc; + const u8 *desc_end; + + buf = kzalloc(MAX_VENDOR_DESCRIPTOR_SIZE, GFP_KERNEL); + if (!buf) + return -ENOMEM; + + ret = usb_get_descriptor(udev, 0x5f, /* vendor specific */ + 0, buf, MAX_VENDOR_DESCRIPTOR_SIZE); + if (ret < 0) + goto out; + len = ret; + + if (len < 5) + goto out; + + desc = buf; + desc_end = desc + len; + + if ((desc[0] != len) || /* descriptor length */ + (desc[1] != 0x5f) || /* vendor descriptor type */ + (desc[2] != 0x01) || /* version (2 bytes) */ + (desc[3] != 0x00) || + (desc[4] != len - 2)) /* length after type */ + goto out; + desc += 5; + + detected = true; + + while (desc < desc_end) + desc = udl_parse_key_value_pair(udl, desc, desc_end); + +out: + if (!detected) + drm_warn(dev, "Unrecognized vendor firmware descriptor\n"); + kfree(buf); + + return 0; +} + +/* + * Need to ensure a channel is selected before submitting URBs + */ +int udl_select_std_channel(struct udl_device *udl) +{ + static const u8 set_def_chn[] = {0x57, 0xCD, 0xDC, 0xA7, + 0x1C, 0x88, 0x5E, 0x15, + 0x60, 0xFE, 0xC6, 0x97, + 0x16, 0x3D, 0x47, 0xF2}; + + void *sendbuf; + int ret; + struct usb_device *udev = udl_to_usb_device(udl); + + sendbuf = kmemdup(set_def_chn, sizeof(set_def_chn), GFP_KERNEL); + if (!sendbuf) + return -ENOMEM; + + ret = usb_control_msg(udev, usb_sndctrlpipe(udev, 0), + NR_USB_REQUEST_CHANNEL, + (USB_DIR_OUT | USB_TYPE_VENDOR), 0, 0, + sendbuf, sizeof(set_def_chn), + USB_CTRL_SET_TIMEOUT); + kfree(sendbuf); + return ret < 0 ? ret : 0; +} + +void udl_urb_completion(struct urb *urb) +{ + struct urb_node *unode = urb->context; + struct udl_device *udl = unode->dev; + unsigned long flags; + + /* sync/async unlink faults aren't errors */ + if (urb->status) { + if (!(urb->status == -ENOENT || + urb->status == -ECONNRESET || + urb->status == -EPROTO || + urb->status == -ESHUTDOWN)) { + DRM_ERROR("%s - nonzero write bulk status received: %d\n", + __func__, urb->status); + } + } + + urb->transfer_buffer_length = udl->urbs.size; /* reset to actual */ + + spin_lock_irqsave(&udl->urbs.lock, flags); + list_add_tail(&unode->entry, &udl->urbs.list); + udl->urbs.available++; + spin_unlock_irqrestore(&udl->urbs.lock, flags); + + wake_up(&udl->urbs.sleep); +} + +static void udl_free_urb_list(struct udl_device *udl) +{ + struct urb_node *unode; + struct urb *urb; + + DRM_DEBUG("Waiting for completes and freeing all render urbs\n"); + + /* keep waiting and freeing, until we've got 'em all */ + while (udl->urbs.count) { + spin_lock_irq(&udl->urbs.lock); + urb = udl_get_urb_locked(udl, MAX_SCHEDULE_TIMEOUT); + udl->urbs.count--; + spin_unlock_irq(&udl->urbs.lock); + if (WARN_ON(!urb)) + break; + unode = urb->context; + /* Free each separately allocated piece */ + usb_free_coherent(urb->dev, udl->urbs.size, + urb->transfer_buffer, urb->transfer_dma); + usb_free_urb(urb); + kfree(unode); + } + + wake_up_all(&udl->urbs.sleep); +} + +static int udl_alloc_urb_list(struct udl_device *udl, int count, size_t size) +{ + struct urb *urb; + struct urb_node *unode; + char *buf; + size_t wanted_size = count * size; + struct usb_device *udev = udl_to_usb_device(udl); + + spin_lock_init(&udl->urbs.lock); + INIT_LIST_HEAD(&udl->urbs.list); + init_waitqueue_head(&udl->urbs.sleep); + udl->urbs.count = 0; + udl->urbs.available = 0; + +retry: + udl->urbs.size = size; + + while (udl->urbs.count * size < wanted_size) { + unode = kzalloc_obj(struct urb_node); + if (!unode) + break; + unode->dev = udl; + + urb = usb_alloc_urb(0, GFP_KERNEL); + if (!urb) { + kfree(unode); + break; + } + unode->urb = urb; + + buf = usb_alloc_coherent(udev, size, GFP_KERNEL, + &urb->transfer_dma); + if (!buf) { + kfree(unode); + usb_free_urb(urb); + if (size > PAGE_SIZE) { + size /= 2; + udl_free_urb_list(udl); + goto retry; + } + break; + } + + /* urb->transfer_buffer_length set to actual before submit */ + usb_fill_bulk_urb(urb, udev, usb_sndbulkpipe(udev, 1), + buf, size, udl_urb_completion, unode); + urb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP; + + list_add_tail(&unode->entry, &udl->urbs.list); + + udl->urbs.count++; + udl->urbs.available++; + } + + DRM_DEBUG("allocated %d %d byte urbs\n", udl->urbs.count, (int) size); + + return udl->urbs.count; +} + +static struct urb *udl_get_urb_locked(struct udl_device *udl, long timeout) +{ + struct urb_node *unode; + + assert_spin_locked(&udl->urbs.lock); + + /* Wait for an in-flight buffer to complete and get re-queued */ + if (!wait_event_lock_irq_timeout(udl->urbs.sleep, + !udl->urbs.count || + !list_empty(&udl->urbs.list), + udl->urbs.lock, timeout)) { + DRM_INFO("wait for urb interrupted: available: %d\n", + udl->urbs.available); + return NULL; + } + + if (!udl->urbs.count) + return NULL; + + unode = list_first_entry(&udl->urbs.list, struct urb_node, entry); + list_del_init(&unode->entry); + udl->urbs.available--; + + return unode->urb; +} + +struct urb *udl_get_urb(struct udl_device *udl) +{ + struct urb *urb; + + spin_lock_irq(&udl->urbs.lock); + urb = udl_get_urb_locked(udl, HZ * 2); + spin_unlock_irq(&udl->urbs.lock); + return urb; +} + +int udl_submit_urb(struct udl_device *udl, struct urb *urb, size_t len) +{ + int ret; + + if (WARN_ON(len > udl->urbs.size)) { + ret = -EINVAL; + goto error; + } + urb->transfer_buffer_length = len; /* set to actual payload len */ + ret = usb_submit_urb(urb, GFP_ATOMIC); + error: + if (ret) { + udl_urb_completion(urb); /* because no one else will */ + DRM_ERROR("usb_submit_urb error %x\n", ret); + } + return ret; +} + +/* wait until all pending URBs have been processed */ +void udl_sync_pending_urbs(struct udl_device *udl) +{ + struct drm_device *dev = &udl->drm; + + spin_lock_irq(&udl->urbs.lock); + /* 2 seconds as a sane timeout */ + if (!wait_event_lock_irq_timeout(udl->urbs.sleep, + udl->urbs.available == udl->urbs.count, + udl->urbs.lock, + msecs_to_jiffies(2000))) + drm_err(dev, "Timeout for syncing pending URBs\n"); + spin_unlock_irq(&udl->urbs.lock); +} + +int udl_init(struct udl_device *udl) +{ + struct drm_device *dev = &udl->drm; + int ret = -ENOMEM; + struct device *dma_dev; + + DRM_DEBUG("\n"); + + dma_dev = usb_intf_get_dma_device(to_usb_interface(dev->dev)); + if (dma_dev) { + drm_dev_set_dma_dev(dev, dma_dev); + put_device(dma_dev); + } else { + drm_warn(dev, "buffer sharing not supported"); /* not an error */ + } + + /* + * Not all devices provide vendor descriptors with device + * information. Initialize to default values of real-world + * devices. It is just enough memory for FullHD. + */ + udl->sku_pixel_limit = UDL_SKU_PIXEL_LIMIT_DEFAULT; + + ret = udl_parse_vendor_descriptor(udl); + if (ret) + goto err; + + if (udl_select_std_channel(udl)) + DRM_ERROR("Selecting channel failed\n"); + + if (!udl_alloc_urb_list(udl, WRITES_IN_FLIGHT, MAX_TRANSFER)) { + DRM_ERROR("udl_alloc_urb_list failed\n"); + ret = -ENOMEM; + goto err; + } + + DRM_DEBUG("\n"); + ret = udl_modeset_init(udl); + if (ret) + goto err; + + return 0; + +err: + if (udl->urbs.count) + udl_free_urb_list(udl); + DRM_ERROR("%d\n", ret); + return ret; +} + +int udl_drop_usb(struct udl_device *udl) +{ + udl_free_urb_list(udl); + + return 0; +} diff --git a/drivers/gpu/drm/udl/udl_modeset.c b/drivers/gpu/drm/udl/udl_modeset.c new file mode 100644 index 000000000..289711035 --- /dev/null +++ b/drivers/gpu/drm/udl/udl_modeset.c @@ -0,0 +1,547 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (C) 2012 Red Hat + * + * based in parts on udlfb.c: + * Copyright (C) 2009 Roberto De Ioris + * Copyright (C) 2009 Jaya Kumar + * Copyright (C) 2009 Bernie Thompson + */ + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "udl_drv.h" +#include "udl_edid.h" +#include "udl_proto.h" + +/* + * All DisplayLink bulk operations start with 0xaf (UDL_MSG_BULK), followed by + * a specific command code. All operations are written to a command buffer, which + * the driver sends to the device. + */ +static char *udl_set_register(char *buf, u8 reg, u8 val) +{ + *buf++ = UDL_MSG_BULK; + *buf++ = UDL_CMD_WRITEREG; + *buf++ = reg; + *buf++ = val; + + return buf; +} + +static char *udl_vidreg_lock(char *buf) +{ + return udl_set_register(buf, UDL_REG_VIDREG, UDL_VIDREG_LOCK); +} + +static char *udl_vidreg_unlock(char *buf) +{ + return udl_set_register(buf, UDL_REG_VIDREG, UDL_VIDREG_UNLOCK); +} + +static char *udl_set_blank_mode(char *buf, u8 mode) +{ + return udl_set_register(buf, UDL_REG_BLANKMODE, mode); +} + +static char *udl_set_color_depth(char *buf, u8 selection) +{ + return udl_set_register(buf, UDL_REG_COLORDEPTH, selection); +} + +static char *udl_set_base16bpp(char *buf, u32 base) +{ + /* the base pointer is 24 bits wide, 0x20 is hi byte. */ + u8 reg20 = FIELD_GET(UDL_BASE_ADDR2_MASK, base); + u8 reg21 = FIELD_GET(UDL_BASE_ADDR1_MASK, base); + u8 reg22 = FIELD_GET(UDL_BASE_ADDR0_MASK, base); + + buf = udl_set_register(buf, UDL_REG_BASE16BPP_ADDR2, reg20); + buf = udl_set_register(buf, UDL_REG_BASE16BPP_ADDR1, reg21); + buf = udl_set_register(buf, UDL_REG_BASE16BPP_ADDR0, reg22); + + return buf; +} + +/* + * DisplayLink HW has separate 16bpp and 8bpp framebuffers. + * In 24bpp modes, the low 323 RGB bits go in the 8bpp framebuffer + */ +static char *udl_set_base8bpp(char *buf, u32 base) +{ + /* the base pointer is 24 bits wide, 0x26 is hi byte. */ + u8 reg26 = FIELD_GET(UDL_BASE_ADDR2_MASK, base); + u8 reg27 = FIELD_GET(UDL_BASE_ADDR1_MASK, base); + u8 reg28 = FIELD_GET(UDL_BASE_ADDR0_MASK, base); + + buf = udl_set_register(buf, UDL_REG_BASE8BPP_ADDR2, reg26); + buf = udl_set_register(buf, UDL_REG_BASE8BPP_ADDR1, reg27); + buf = udl_set_register(buf, UDL_REG_BASE8BPP_ADDR0, reg28); + + return buf; +} + +static char *udl_set_register_16(char *wrptr, u8 reg, u16 value) +{ + wrptr = udl_set_register(wrptr, reg, value >> 8); + return udl_set_register(wrptr, reg+1, value); +} + +/* + * This is kind of weird because the controller takes some + * register values in a different byte order than other registers. + */ +static char *udl_set_register_16be(char *wrptr, u8 reg, u16 value) +{ + wrptr = udl_set_register(wrptr, reg, value); + return udl_set_register(wrptr, reg+1, value >> 8); +} + +/* + * LFSR is linear feedback shift register. The reason we have this is + * because the display controller needs to minimize the clock depth of + * various counters used in the display path. So this code reverses the + * provided value into the lfsr16 value by counting backwards to get + * the value that needs to be set in the hardware comparator to get the + * same actual count. This makes sense once you read above a couple of + * times and think about it from a hardware perspective. + */ +static u16 udl_lfsr16(u16 actual_count) +{ + u32 lv = 0xFFFF; /* This is the lfsr value that the hw starts with */ + + while (actual_count--) { + lv = ((lv << 1) | + (((lv >> 15) ^ (lv >> 4) ^ (lv >> 2) ^ (lv >> 1)) & 1)) + & 0xFFFF; + } + + return (u16) lv; +} + +/* + * This does LFSR conversion on the value that is to be written. + * See LFSR explanation above for more detail. + */ +static char *udl_set_register_lfsr16(char *wrptr, u8 reg, u16 value) +{ + return udl_set_register_16(wrptr, reg, udl_lfsr16(value)); +} + +/* + * Takes a DRM display mode and converts it into the DisplayLink + * equivalent register commands. + */ +static char *udl_set_display_mode(char *buf, struct drm_display_mode *mode) +{ + u16 reg01 = mode->crtc_htotal - mode->crtc_hsync_start; + u16 reg03 = reg01 + mode->crtc_hdisplay; + u16 reg05 = mode->crtc_vtotal - mode->crtc_vsync_start; + u16 reg07 = reg05 + mode->crtc_vdisplay; + u16 reg09 = mode->crtc_htotal - 1; + u16 reg0b = 1; /* libdlo hardcodes hsync start to 1 */ + u16 reg0d = mode->crtc_hsync_end - mode->crtc_hsync_start + 1; + u16 reg0f = mode->hdisplay; + u16 reg11 = mode->crtc_vtotal; + u16 reg13 = 0; /* libdlo hardcodes vsync start to 0 */ + u16 reg15 = mode->crtc_vsync_end - mode->crtc_vsync_start; + u16 reg17 = mode->crtc_vdisplay; + u16 reg1b = mode->clock / 5; + + buf = udl_set_register_lfsr16(buf, UDL_REG_XDISPLAYSTART, reg01); + buf = udl_set_register_lfsr16(buf, UDL_REG_XDISPLAYEND, reg03); + buf = udl_set_register_lfsr16(buf, UDL_REG_YDISPLAYSTART, reg05); + buf = udl_set_register_lfsr16(buf, UDL_REG_YDISPLAYEND, reg07); + buf = udl_set_register_lfsr16(buf, UDL_REG_XENDCOUNT, reg09); + buf = udl_set_register_lfsr16(buf, UDL_REG_HSYNCSTART, reg0b); + buf = udl_set_register_lfsr16(buf, UDL_REG_HSYNCEND, reg0d); + buf = udl_set_register_16(buf, UDL_REG_HPIXELS, reg0f); + buf = udl_set_register_lfsr16(buf, UDL_REG_YENDCOUNT, reg11); + buf = udl_set_register_lfsr16(buf, UDL_REG_VSYNCSTART, reg13); + buf = udl_set_register_lfsr16(buf, UDL_REG_VSYNCEND, reg15); + buf = udl_set_register_16(buf, UDL_REG_VPIXELS, reg17); + buf = udl_set_register_16be(buf, UDL_REG_PIXELCLOCK5KHZ, reg1b); + + return buf; +} + +static char *udl_dummy_render(char *wrptr) +{ + *wrptr++ = UDL_MSG_BULK; + *wrptr++ = UDL_CMD_WRITECOPY16; + *wrptr++ = 0x00; /* from addr */ + *wrptr++ = 0x00; + *wrptr++ = 0x00; + *wrptr++ = 0x01; /* one pixel */ + *wrptr++ = 0x00; /* to address */ + *wrptr++ = 0x00; + *wrptr++ = 0x00; + return wrptr; +} + +static long udl_log_cpp(unsigned int cpp) +{ + if (WARN_ON(!is_power_of_2(cpp))) + return -EINVAL; + return __ffs(cpp); +} + +static int udl_handle_damage(struct drm_framebuffer *fb, + const struct iosys_map *map, + const struct drm_rect *clip) +{ + struct drm_device *dev = fb->dev; + struct udl_device *udl = to_udl(dev); + void *vaddr = map->vaddr; /* TODO: Use mapping abstraction properly */ + int i, ret; + char *cmd; + struct urb *urb; + int log_bpp; + + ret = udl_log_cpp(fb->format->cpp[0]); + if (ret < 0) + return ret; + log_bpp = ret; + + urb = udl_get_urb(udl); + if (!urb) + return -ENOMEM; + cmd = urb->transfer_buffer; + + for (i = clip->y1; i < clip->y2; i++) { + const int line_offset = fb->pitches[0] * i; + const int byte_offset = line_offset + (clip->x1 << log_bpp); + const int dev_byte_offset = (fb->width * i + clip->x1) << log_bpp; + const int byte_width = drm_rect_width(clip) << log_bpp; + ret = udl_render_hline(udl, log_bpp, &urb, (char *)vaddr, + &cmd, byte_offset, dev_byte_offset, + byte_width); + if (ret) + return ret; + } + + if (cmd > (char *)urb->transfer_buffer) { + /* Send partial buffer remaining before exiting */ + int len; + if (cmd < (char *)urb->transfer_buffer + urb->transfer_buffer_length) + *cmd++ = UDL_MSG_BULK; + len = cmd - (char *)urb->transfer_buffer; + ret = udl_submit_urb(udl, urb, len); + } else { + udl_urb_completion(urb); + } + + return 0; +} + +/* + * Primary plane + */ + +static const uint32_t udl_primary_plane_formats[] = { + DRM_FORMAT_RGB565, + DRM_FORMAT_XRGB8888, +}; + +static const uint64_t udl_primary_plane_fmtmods[] = { + DRM_FORMAT_MOD_LINEAR, + DRM_FORMAT_MOD_INVALID +}; + +static int udl_primary_plane_helper_atomic_check(struct drm_plane *plane, + struct drm_atomic_commit *state) +{ + struct drm_plane_state *new_plane_state = drm_atomic_get_new_plane_state(state, plane); + struct drm_crtc *new_crtc = new_plane_state->crtc; + struct drm_crtc_state *new_crtc_state = NULL; + + if (new_crtc) + new_crtc_state = drm_atomic_get_new_crtc_state(state, new_crtc); + + return drm_atomic_helper_check_plane_state(new_plane_state, new_crtc_state, + DRM_PLANE_NO_SCALING, + DRM_PLANE_NO_SCALING, + false, false); +} + +static void udl_primary_plane_helper_atomic_update(struct drm_plane *plane, + struct drm_atomic_commit *state) +{ + struct drm_device *dev = plane->dev; + struct drm_plane_state *plane_state = drm_atomic_get_new_plane_state(state, plane); + struct drm_shadow_plane_state *shadow_plane_state = to_drm_shadow_plane_state(plane_state); + struct drm_framebuffer *fb = plane_state->fb; + struct drm_plane_state *old_plane_state = drm_atomic_get_old_plane_state(state, plane); + struct drm_atomic_helper_damage_iter iter; + struct drm_rect damage; + int ret, idx; + + if (!fb) + return; /* no framebuffer; plane is disabled */ + + ret = drm_gem_fb_begin_cpu_access(fb, DMA_FROM_DEVICE); + if (ret) + return; + + if (!drm_dev_enter(dev, &idx)) + goto out_drm_gem_fb_end_cpu_access; + + drm_atomic_helper_damage_iter_init(&iter, old_plane_state, plane_state); + drm_atomic_for_each_plane_damage(&iter, &damage) { + udl_handle_damage(fb, &shadow_plane_state->data[0], &damage); + } + + drm_dev_exit(idx); + +out_drm_gem_fb_end_cpu_access: + drm_gem_fb_end_cpu_access(fb, DMA_FROM_DEVICE); +} + +static const struct drm_plane_helper_funcs udl_primary_plane_helper_funcs = { + DRM_GEM_SHADOW_PLANE_HELPER_FUNCS, + .atomic_check = udl_primary_plane_helper_atomic_check, + .atomic_update = udl_primary_plane_helper_atomic_update, +}; + +static const struct drm_plane_funcs udl_primary_plane_funcs = { + .update_plane = drm_atomic_helper_update_plane, + .disable_plane = drm_atomic_helper_disable_plane, + .destroy = drm_plane_cleanup, + DRM_GEM_SHADOW_PLANE_FUNCS, +}; + +/* + * CRTC + */ + +static void udl_crtc_helper_atomic_enable(struct drm_crtc *crtc, struct drm_atomic_commit *state) +{ + struct drm_device *dev = crtc->dev; + struct udl_device *udl = to_udl(dev); + struct drm_crtc_state *crtc_state = drm_atomic_get_new_crtc_state(state, crtc); + struct drm_display_mode *mode = &crtc_state->mode; + struct urb *urb; + char *buf; + int idx; + + if (!drm_dev_enter(dev, &idx)) + return; + + urb = udl_get_urb(udl); + if (!urb) { + drm_err_ratelimited(dev, "get urb failed when enabling crtc\n"); + goto out; + } + + buf = (char *)urb->transfer_buffer; + buf = udl_vidreg_lock(buf); + buf = udl_set_color_depth(buf, UDL_COLORDEPTH_16BPP); + /* set base for 16bpp segment to 0 */ + buf = udl_set_base16bpp(buf, 0); + /* set base for 8bpp segment to end of fb */ + buf = udl_set_base8bpp(buf, 2 * mode->vdisplay * mode->hdisplay); + buf = udl_set_display_mode(buf, mode); + buf = udl_set_blank_mode(buf, UDL_BLANKMODE_ON); + buf = udl_vidreg_unlock(buf); + buf = udl_dummy_render(buf); + + udl_submit_urb(udl, urb, buf - (char *)urb->transfer_buffer); + +out: + drm_dev_exit(idx); +} + +static void udl_crtc_helper_atomic_disable(struct drm_crtc *crtc, struct drm_atomic_commit *state) +{ + struct drm_device *dev = crtc->dev; + struct udl_device *udl = to_udl(dev); + struct urb *urb; + char *buf; + int idx; + + if (!drm_dev_enter(dev, &idx)) + return; + + urb = udl_get_urb(udl); + if (!urb) + goto out; + + buf = (char *)urb->transfer_buffer; + buf = udl_vidreg_lock(buf); + buf = udl_set_blank_mode(buf, UDL_BLANKMODE_POWERDOWN); + buf = udl_vidreg_unlock(buf); + buf = udl_dummy_render(buf); + + udl_submit_urb(udl, urb, buf - (char *)urb->transfer_buffer); + +out: + drm_dev_exit(idx); +} + +static const struct drm_crtc_helper_funcs udl_crtc_helper_funcs = { + .atomic_check = drm_crtc_helper_atomic_check, + .atomic_enable = udl_crtc_helper_atomic_enable, + .atomic_disable = udl_crtc_helper_atomic_disable, +}; + +static const struct drm_crtc_funcs udl_crtc_funcs = { + .reset = drm_atomic_helper_crtc_reset, + .destroy = drm_crtc_cleanup, + .set_config = drm_atomic_helper_set_config, + .page_flip = drm_atomic_helper_page_flip, + .atomic_duplicate_state = drm_atomic_helper_crtc_duplicate_state, + .atomic_destroy_state = drm_atomic_helper_crtc_destroy_state, +}; + +/* + * Encoder + */ + +static const struct drm_encoder_funcs udl_encoder_funcs = { + .destroy = drm_encoder_cleanup, +}; + +/* + * Connector + */ + +static int udl_connector_helper_get_modes(struct drm_connector *connector) +{ + const struct drm_edid *drm_edid; + int count; + + drm_edid = udl_edid_read(connector); + drm_edid_connector_update(connector, drm_edid); + count = drm_edid_connector_add_modes(connector); + drm_edid_free(drm_edid); + + return count; +} + +static int udl_connector_helper_detect_ctx(struct drm_connector *connector, + struct drm_modeset_acquire_ctx *ctx, + bool force) +{ + struct udl_device *udl = to_udl(connector->dev); + + if (udl_probe_edid(udl)) + return connector_status_connected; + + return connector_status_disconnected; +} + +static const struct drm_connector_helper_funcs udl_connector_helper_funcs = { + .get_modes = udl_connector_helper_get_modes, + .detect_ctx = udl_connector_helper_detect_ctx, +}; + +static const struct drm_connector_funcs udl_connector_funcs = { + .reset = drm_atomic_helper_connector_reset, + .fill_modes = drm_helper_probe_single_connector_modes, + .destroy = drm_connector_cleanup, + .atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state, + .atomic_destroy_state = drm_atomic_helper_connector_destroy_state, +}; + +/* + * Modesetting + */ + +static enum drm_mode_status udl_mode_config_mode_valid(struct drm_device *dev, + const struct drm_display_mode *mode) +{ + struct udl_device *udl = to_udl(dev); + + if (udl->sku_pixel_limit) { + if (mode->vdisplay * mode->hdisplay > udl->sku_pixel_limit) + return MODE_MEM; + } + + return MODE_OK; +} + +static const struct drm_mode_config_funcs udl_mode_config_funcs = { + .fb_create = drm_gem_fb_create_with_dirty, + .mode_valid = udl_mode_config_mode_valid, + .atomic_check = drm_atomic_helper_check, + .atomic_commit = drm_atomic_helper_commit, +}; + +int udl_modeset_init(struct udl_device *udl) +{ + struct drm_device *dev = &udl->drm; + struct drm_plane *primary_plane; + struct drm_crtc *crtc; + struct drm_encoder *encoder; + struct drm_connector *connector; + int ret; + + ret = drmm_mode_config_init(dev); + if (ret) + return ret; + + dev->mode_config.min_width = 640; + dev->mode_config.min_height = 480; + dev->mode_config.max_width = 2048; + dev->mode_config.max_height = 2048; + dev->mode_config.preferred_depth = 16; + dev->mode_config.funcs = &udl_mode_config_funcs; + + primary_plane = &udl->primary_plane; + ret = drm_universal_plane_init(dev, primary_plane, 0, + &udl_primary_plane_funcs, + udl_primary_plane_formats, + ARRAY_SIZE(udl_primary_plane_formats), + udl_primary_plane_fmtmods, + DRM_PLANE_TYPE_PRIMARY, NULL); + if (ret) + return ret; + drm_plane_helper_add(primary_plane, &udl_primary_plane_helper_funcs); + drm_plane_enable_fb_damage_clips(primary_plane); + + crtc = &udl->crtc; + ret = drm_crtc_init_with_planes(dev, crtc, primary_plane, NULL, + &udl_crtc_funcs, NULL); + if (ret) + return ret; + drm_crtc_helper_add(crtc, &udl_crtc_helper_funcs); + + encoder = &udl->encoder; + ret = drm_encoder_init(dev, encoder, &udl_encoder_funcs, DRM_MODE_ENCODER_DAC, NULL); + if (ret) + return ret; + encoder->possible_crtcs = drm_crtc_mask(crtc); + + connector = &udl->connector; + ret = drm_connector_init(dev, connector, &udl_connector_funcs, DRM_MODE_CONNECTOR_VGA); + if (ret) + return ret; + drm_connector_helper_add(connector, &udl_connector_helper_funcs); + + connector->polled = DRM_CONNECTOR_POLL_CONNECT | + DRM_CONNECTOR_POLL_DISCONNECT; + + ret = drm_connector_attach_encoder(connector, encoder); + if (ret) + return ret; + + drm_mode_config_reset(dev); + drmm_kms_helper_poll_init(dev); + + return 0; +} diff --git a/drivers/gpu/drm/udl/udl_proto.h b/drivers/gpu/drm/udl/udl_proto.h new file mode 100644 index 000000000..c92d21095 --- /dev/null +++ b/drivers/gpu/drm/udl/udl_proto.h @@ -0,0 +1,68 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ + +#ifndef UDL_PROTO_H +#define UDL_PROTO_H + +#include + +#define UDL_MSG_BULK 0xaf + +/* Register access */ +#define UDL_CMD_WRITEREG 0x20 /* See register constants below */ + +/* Framebuffer access */ +#define UDL_CMD_WRITERAW8 0x60 /* 8 bit raw write command. */ +#define UDL_CMD_WRITERL8 0x61 /* 8 bit run length command. */ +#define UDL_CMD_WRITECOPY8 0x62 /* 8 bit copy command. */ +#define UDL_CMD_WRITERLX8 0x63 /* 8 bit extended run length command. */ +#define UDL_CMD_WRITERAW16 0x68 /* 16 bit raw write command. */ +#define UDL_CMD_WRITERL16 0x69 /* 16 bit run length command. */ +#define UDL_CMD_WRITECOPY16 0x6a /* 16 bit copy command. */ +#define UDL_CMD_WRITERLX16 0x6b /* 16 bit extended run length command. */ + +/* Color depth */ +#define UDL_REG_COLORDEPTH 0x00 +#define UDL_COLORDEPTH_16BPP 0 +#define UDL_COLORDEPTH_24BPP 1 + +/* Display-mode settings */ +#define UDL_REG_XDISPLAYSTART 0x01 +#define UDL_REG_XDISPLAYEND 0x03 +#define UDL_REG_YDISPLAYSTART 0x05 +#define UDL_REG_YDISPLAYEND 0x07 +#define UDL_REG_XENDCOUNT 0x09 +#define UDL_REG_HSYNCSTART 0x0b +#define UDL_REG_HSYNCEND 0x0d +#define UDL_REG_HPIXELS 0x0f +#define UDL_REG_YENDCOUNT 0x11 +#define UDL_REG_VSYNCSTART 0x13 +#define UDL_REG_VSYNCEND 0x15 +#define UDL_REG_VPIXELS 0x17 +#define UDL_REG_PIXELCLOCK5KHZ 0x1b + +/* On/Off for driving the DisplayLink framebuffer to the display */ +#define UDL_REG_BLANKMODE 0x1f +#define UDL_BLANKMODE_ON 0x00 /* hsync and vsync on, visible */ +#define UDL_BLANKMODE_BLANKED 0x01 /* hsync and vsync on, blanked */ +#define UDL_BLANKMODE_VSYNC_OFF 0x03 /* vsync off, blanked */ +#define UDL_BLANKMODE_HSYNC_OFF 0x05 /* hsync off, blanked */ +#define UDL_BLANKMODE_POWERDOWN 0x07 /* powered off; requires modeset */ + +/* Framebuffer address */ +#define UDL_REG_BASE16BPP_ADDR2 0x20 +#define UDL_REG_BASE16BPP_ADDR1 0x21 +#define UDL_REG_BASE16BPP_ADDR0 0x22 +#define UDL_REG_BASE8BPP_ADDR2 0x26 +#define UDL_REG_BASE8BPP_ADDR1 0x27 +#define UDL_REG_BASE8BPP_ADDR0 0x28 + +#define UDL_BASE_ADDR0_MASK GENMASK(7, 0) +#define UDL_BASE_ADDR1_MASK GENMASK(15, 8) +#define UDL_BASE_ADDR2_MASK GENMASK(23, 16) + +/* Lock/unlock video registers */ +#define UDL_REG_VIDREG 0xff +#define UDL_VIDREG_LOCK 0x00 +#define UDL_VIDREG_UNLOCK 0xff + +#endif diff --git a/drivers/gpu/drm/udl/udl_transfer.c b/drivers/gpu/drm/udl/udl_transfer.c new file mode 100644 index 000000000..7d670b3a5 --- /dev/null +++ b/drivers/gpu/drm/udl/udl_transfer.c @@ -0,0 +1,217 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (C) 2012 Red Hat + * based in parts on udlfb.c: + * Copyright (C) 2009 Roberto De Ioris + * Copyright (C) 2009 Jaya Kumar + * Copyright (C) 2009 Bernie Thompson + */ + +#include + +#include "udl_drv.h" +#include "udl_proto.h" + +#define MAX_CMD_PIXELS 255 + +#define RLX_HEADER_BYTES 7 +#define MIN_RLX_PIX_BYTES 4 +#define MIN_RLX_CMD_BYTES (RLX_HEADER_BYTES + MIN_RLX_PIX_BYTES) + +#define RLE_HEADER_BYTES 6 +#define MIN_RLE_PIX_BYTES 3 +#define MIN_RLE_CMD_BYTES (RLE_HEADER_BYTES + MIN_RLE_PIX_BYTES) + +#define RAW_HEADER_BYTES 6 +#define MIN_RAW_PIX_BYTES 2 +#define MIN_RAW_CMD_BYTES (RAW_HEADER_BYTES + MIN_RAW_PIX_BYTES) + +static inline u16 pixel32_to_be16(const uint32_t pixel) +{ + return (((pixel >> 3) & 0x001f) | + ((pixel >> 5) & 0x07e0) | + ((pixel >> 8) & 0xf800)); +} + +static inline u16 get_pixel_val16(const uint8_t *pixel, int log_bpp) +{ + u16 pixel_val16; + if (log_bpp == 1) + pixel_val16 = *(const uint16_t *)pixel; + else + pixel_val16 = pixel32_to_be16(*(const uint32_t *)pixel); + return pixel_val16; +} + +/* + * Render a command stream for an encoded horizontal line segment of pixels. + * + * A command buffer holds several commands. + * It always begins with a fresh command header + * (the protocol doesn't require this, but we enforce it to allow + * multiple buffers to be potentially encoded and sent in parallel). + * A single command encodes one contiguous horizontal line of pixels + * + * The function relies on the client to do all allocation, so that + * rendering can be done directly to output buffers (e.g. USB URBs). + * The function fills the supplied command buffer, providing information + * on where it left off, so the client may call in again with additional + * buffers if the line will take several buffers to complete. + * + * A single command can transmit a maximum of 256 pixels, + * regardless of the compression ratio (protocol design limit). + * To the hardware, 0 for a size byte means 256 + * + * Rather than 256 pixel commands which are either rl or raw encoded, + * the rlx command simply assumes alternating raw and rl spans within one cmd. + * This has a slightly larger header overhead, but produces more even results. + * It also processes all data (read and write) in a single pass. + * Performance benchmarks of common cases show it having just slightly better + * compression than 256 pixel raw or rle commands, with similar CPU consumpion. + * But for very rl friendly data, will compress not quite as well. + */ +static void udl_compress_hline16( + const u8 **pixel_start_ptr, + const u8 *const pixel_end, + uint32_t *device_address_ptr, + uint8_t **command_buffer_ptr, + const uint8_t *const cmd_buffer_end, int log_bpp) +{ + const int bpp = 1 << log_bpp; + const u8 *pixel = *pixel_start_ptr; + uint32_t dev_addr = *device_address_ptr; + uint8_t *cmd = *command_buffer_ptr; + + while ((pixel_end > pixel) && + (cmd_buffer_end - MIN_RLX_CMD_BYTES > cmd)) { + uint8_t *raw_pixels_count_byte = NULL; + uint8_t *cmd_pixels_count_byte = NULL; + const u8 *raw_pixel_start = NULL; + const u8 *cmd_pixel_start, *cmd_pixel_end = NULL; + uint16_t pixel_val16; + + *cmd++ = UDL_MSG_BULK; + *cmd++ = UDL_CMD_WRITERLX16; + *cmd++ = (uint8_t) ((dev_addr >> 16) & 0xFF); + *cmd++ = (uint8_t) ((dev_addr >> 8) & 0xFF); + *cmd++ = (uint8_t) ((dev_addr) & 0xFF); + + cmd_pixels_count_byte = cmd++; /* we'll know this later */ + cmd_pixel_start = pixel; + + raw_pixels_count_byte = cmd++; /* we'll know this later */ + raw_pixel_start = pixel; + + cmd_pixel_end = pixel + (min3(MAX_CMD_PIXELS + 1UL, + (unsigned long)(pixel_end - pixel) >> log_bpp, + (unsigned long)(cmd_buffer_end - 1 - cmd) / 2) << log_bpp); + + pixel_val16 = get_pixel_val16(pixel, log_bpp); + + while (pixel < cmd_pixel_end) { + const u8 *const start = pixel; + const uint16_t repeating_pixel_val16 = pixel_val16; + + put_unaligned_be16(pixel_val16, cmd); + + cmd += 2; + pixel += bpp; + + while (pixel < cmd_pixel_end) { + pixel_val16 = get_pixel_val16(pixel, log_bpp); + if (pixel_val16 != repeating_pixel_val16) + break; + pixel += bpp; + } + + if (unlikely(pixel > start + bpp)) { + /* go back and fill in raw pixel count */ + *raw_pixels_count_byte = (((start - + raw_pixel_start) >> log_bpp) + 1) & 0xFF; + + /* immediately after raw data is repeat byte */ + *cmd++ = (((pixel - start) >> log_bpp) - 1) & 0xFF; + + /* Then start another raw pixel span */ + raw_pixel_start = pixel; + raw_pixels_count_byte = cmd++; + } + } + + if (pixel > raw_pixel_start) { + /* finalize last RAW span */ + *raw_pixels_count_byte = ((pixel - raw_pixel_start) >> log_bpp) & 0xFF; + } else { + /* undo unused byte */ + cmd--; + } + + *cmd_pixels_count_byte = ((pixel - cmd_pixel_start) >> log_bpp) & 0xFF; + dev_addr += ((pixel - cmd_pixel_start) >> log_bpp) * 2; + } + + if (cmd_buffer_end <= MIN_RLX_CMD_BYTES + cmd) { + /* Fill leftover bytes with no-ops */ + if (cmd_buffer_end > cmd) + memset(cmd, UDL_MSG_BULK, cmd_buffer_end - cmd); + cmd = (uint8_t *) cmd_buffer_end; + } + + *command_buffer_ptr = cmd; + *pixel_start_ptr = pixel; + *device_address_ptr = dev_addr; + + return; +} + +/* + * There are 3 copies of every pixel: The front buffer that the fbdev + * client renders to, the actual framebuffer across the USB bus in hardware + * (that we can only write to, slowly, and can never read), and (optionally) + * our shadow copy that tracks what's been sent to that hardware buffer. + */ +int udl_render_hline(struct udl_device *udl, int log_bpp, struct urb **urb_ptr, + const char *front, char **urb_buf_ptr, + u32 byte_offset, u32 device_byte_offset, + u32 byte_width) +{ + const u8 *line_start, *line_end, *next_pixel; + u32 base16 = 0 + (device_byte_offset >> log_bpp) * 2; + struct urb *urb = *urb_ptr; + u8 *cmd = *urb_buf_ptr; + u8 *cmd_end = (u8 *) urb->transfer_buffer + urb->transfer_buffer_length; + + if (WARN_ON(!(log_bpp == 1 || log_bpp == 2))) { + /* need to finish URB at error from this function */ + udl_urb_completion(urb); + return -EINVAL; + } + + line_start = (u8 *) (front + byte_offset); + next_pixel = line_start; + line_end = next_pixel + byte_width; + + while (next_pixel < line_end) { + + udl_compress_hline16(&next_pixel, + line_end, &base16, + (u8 **) &cmd, (u8 *) cmd_end, log_bpp); + + if (cmd >= cmd_end) { + int len = cmd - (u8 *) urb->transfer_buffer; + int ret = udl_submit_urb(udl, urb, len); + if (ret) + return ret; + urb = udl_get_urb(udl); + if (!urb) + return -EAGAIN; + *urb_ptr = urb; + cmd = urb->transfer_buffer; + cmd_end = &cmd[urb->transfer_buffer_length]; + } + } + + *urb_buf_ptr = cmd; + + return 0; +} -- cgit v1.3.1