123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614 |
- // SPDX-License-Identifier: MIT
- /*
- * Copyright 2020 Noralf Trønnes
- */
- #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.h>
- #include <drm/drm_gem_framebuffer_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"
- /*
- * Some userspace rendering loops runs all displays in the same loop.
- * This means that a fast display will have to wait for a slow one.
- * For this reason gud does flushing asynchronous by default.
- * The down side is that in e.g. a single display setup userspace thinks
- * the display is insanely fast since the driver reports back immediately
- * that the flush/pageflip is done. This wastes CPU and power.
- * Such users might want to set this module parameter to false.
- */
- static bool gud_async_flush = true;
- module_param_named(async_flush, gud_async_flush, bool, 0644);
- MODULE_PARM_DESC(async_flush, "Enable asynchronous flushing [default=true]");
- /*
- * FIXME: The driver is probably broken on Big Endian machines.
- * See discussion:
- * https://lore.kernel.org/dri-devel/CAKb7UvihLX0hgBOP3VBG7O+atwZcUVCPVuBdfmDMpg0NjXe-cQ@mail.gmail.com/
- */
- 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 pix, *pix8, *block = dst; /* Assign to silence compiler warning */
- struct iosys_map dst_map, vmap;
- 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;
- iosys_map_set_vaddr(&dst_map, buf);
- iosys_map_set_vaddr(&vmap, src);
- drm_fb_xrgb8888_to_gray8(&dst_map, NULL, &vmap, fb, rect);
- pix8 = buf;
- for (y = 0; y < height; y++) {
- for (x = 0; x < width; x++) {
- unsigned int pixpos = x % block_width; /* within byte from the left */
- unsigned int pixshift = (block_width - pixpos - 1) * bits_per_pixel;
- if (!pixpos) {
- block = dst++;
- *block = 0;
- }
- pix = (*pix8++) >> (8 - bits_per_pixel);
- *block |= pix << pixshift;
- }
- }
- kfree(buf);
- return len;
- }
- static size_t gud_xrgb8888_to_color(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;
- u8 r, g, b, pix, *block = dst; /* Assign to silence compiler warning */
- unsigned int x, y, width;
- __le32 *sbuf32;
- u32 pix32;
- size_t len;
- /* Start on a byte boundary */
- rect->x1 = ALIGN_DOWN(rect->x1, block_width);
- width = drm_rect_width(rect);
- len = drm_format_info_min_pitch(format, 0, width) * drm_rect_height(rect);
- for (y = rect->y1; y < rect->y2; y++) {
- sbuf32 = src + (y * fb->pitches[0]);
- sbuf32 += rect->x1;
- for (x = 0; x < width; x++) {
- unsigned int pixpos = x % block_width; /* within byte from the left */
- unsigned int pixshift = (block_width - pixpos - 1) * bits_per_pixel;
- if (!pixpos) {
- block = dst++;
- *block = 0;
- }
- pix32 = le32_to_cpu(*sbuf32++);
- r = pix32 >> 16;
- g = pix32 >> 8;
- b = pix32;
- switch (format->format) {
- case GUD_DRM_FORMAT_XRGB1111:
- pix = ((r >> 7) << 2) | ((g >> 7) << 1) | (b >> 7);
- break;
- default:
- WARN_ON_ONCE(1);
- return len;
- }
- *block |= pix << pixshift;
- }
- }
- 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 iosys_map map[DRM_FORMAT_MAX_PLANES] = { };
- struct iosys_map map_data[DRM_FORMAT_MAX_PLANES] = { };
- struct iosys_map dst;
- 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_fb_vmap(fb, map, map_data);
- if (ret)
- return ret;
- vaddr = map_data[0].vaddr;
- ret = drm_gem_fb_begin_cpu_access(fb, DMA_FROM_DEVICE);
- if (ret)
- goto vunmap;
- retry:
- if (compression)
- buf = gdrm->compress_buf;
- else
- buf = gdrm->bulk_buf;
- iosys_map_set_vaddr(&dst, 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_R8) {
- drm_fb_xrgb8888_to_gray8(&dst, NULL, map_data, fb, rect);
- } else if (format->format == DRM_FORMAT_RGB332) {
- drm_fb_xrgb8888_to_rgb332(&dst, NULL, map_data, fb, rect);
- } else if (format->format == DRM_FORMAT_RGB565) {
- drm_fb_xrgb8888_to_rgb565(&dst, NULL, map_data, fb, rect,
- gud_is_big_endian());
- } else if (format->format == DRM_FORMAT_RGB888) {
- drm_fb_xrgb8888_to_rgb888(&dst, NULL, map_data, fb, rect);
- } else {
- len = gud_xrgb8888_to_color(buf, format, vaddr, fb, rect);
- }
- } else if (gud_is_big_endian() && format->cpp[0] > 1) {
- drm_fb_swab(&dst, NULL, map_data, 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(&dst, NULL, map_data, 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:
- drm_gem_fb_end_cpu_access(fb, DMA_FROM_DEVICE);
- vunmap:
- drm_gem_fb_vunmap(fb, map);
- return ret;
- }
- struct gud_usb_bulk_context {
- struct timer_list timer;
- struct usb_sg_request sgr;
- };
- static void gud_usb_bulk_timeout(struct timer_list *t)
- {
- struct gud_usb_bulk_context *ctx = from_timer(ctx, t, timer);
- usb_sg_cancel(&ctx->sgr);
- }
- static int gud_usb_bulk(struct gud_device *gdrm, size_t len)
- {
- struct gud_usb_bulk_context ctx;
- int ret;
- ret = usb_sg_init(&ctx.sgr, gud_to_usb_device(gdrm), gdrm->bulk_pipe, 0,
- gdrm->bulk_sgt.sgl, gdrm->bulk_sgt.nents, len, GFP_KERNEL);
- if (ret)
- return ret;
- timer_setup_on_stack(&ctx.timer, gud_usb_bulk_timeout, 0);
- mod_timer(&ctx.timer, jiffies + msecs_to_jiffies(3000));
- usb_sg_wait(&ctx.sgr);
- if (!del_timer_sync(&ctx.timer))
- ret = -ETIMEDOUT;
- else if (ctx.sgr.status < 0)
- ret = ctx.sgr.status;
- else if (ctx.sgr.bytes != len)
- ret = -EIO;
- destroy_timer_on_stack(&ctx.timer);
- 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 gud_set_buffer_req req;
- size_t len, trlen;
- int ret;
- 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 = gud_usb_bulk(gdrm, trlen);
- 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 (!gud_async_flush)
- flush_work(&gdrm->work);
- }
- if (!crtc->state->enable)
- gud_usb_set_u8(gdrm, GUD_REQ_SET_CONTROLLER_ENABLE, 0);
- drm_dev_exit(idx);
- }
|