[PATCH 09/26] video: Add support for copying to a hardware framebuffer

Simon Glass
2020-05-19T23:10:41Z

Some architectures use a cached framebuffer and flush the cache as needed
so that changes are visible. This is supported by U-Boot.

However x86 uses an uncached framebuffer with a 'write-combining' feature
to speed up writes. Reads are permitted but they are extremely expensive.

Unfortunately, reading from the frame buffer is quite common, e.g. to
scroll it. This makes scrolling very slow.

Add a new feature which supports copying modified parts of the frame
buffer to the uncached hardware buffer. This speeds up scrolling by at
least 10x on x86 so the extra complexity cost seems worth it.

As a starting point, add the Kconfig, update the video structures to keep
track of the buffer and add a function to do the copy.

Signed-off-by: Simon Glass <sjg@chromium.org>
---

drivers/video/Kconfig | 12 ++++++++
drivers/video/video-uclass.c | 54 ++++++++++++++++++++++++++++++++++++
include/video.h | 29 +++++++++++++++++++
3 files changed, 95 insertions(+)

diff --git a/drivers/video/Kconfig b/drivers/video/Kconfig
index 38123543a5..1cf63efd48 100644
--- a/drivers/video/Kconfig
+++ b/drivers/video/Kconfig
@@ -14,6 +14,18 @@ config DM_VIDEO
option compiles in the video uclass and routes all LCD/video access
through this.

+config VIDEO_COPY
+ bool "Enable copying the frame buffer to a hardware copy"
+ depends on DM_VIDEO
+ help
+ On some machines (e.g. x86), reading from the frame buffer is very
+ slow because it is uncached. To improve performance, this feature
+ allows the frame buffer to be kept in cached memory (allocated by
+ U-Boot) and then copied to the hardware frame-buffer as needed.
+
+ To use this, your video driver must set @copy_base in
+ struct video_uc_platdata.
+
config BACKLIGHT_PWM
bool "Generic PWM based Backlight Driver"
depends on DM_VIDEO && DM_PWM
diff --git a/drivers/video/video-uclass.c b/drivers/video/video-uclass.c
index bf396d1091..9fbaa8db10 100644
--- a/drivers/video/video-uclass.c
+++ b/drivers/video/video-uclass.c
@@ -4,6 +4,7 @@
*/

#include <common.h>
+#include <console.h>
#include <cpu_func.h>
#include <dm.h>
#include <log.h>
@@ -200,6 +201,59 @@ int video_get_ysize(struct udevice *dev)
return priv->ysize;
}

+#ifdef CONFIG_VIDEO_COPY
+int video_sync_copy(struct udevice *dev, void *from, void *to)
+{
+ struct video_priv *priv = dev_get_uclass_priv(dev);
+
+ if (priv->copy_fb) {
+ long offset, size;
+
+ /* Find the offset of the first byte to copy */
+ if ((ulong)to > (ulong)from) {
+ size = to - from;
+ offset = from - priv->fb;
+ } else {
+ size = from - to;
+ offset = to - priv->fb;
+ }
+
+ /*
+ * Allow a bit of leeway for valid requests somewhere near the
+ * frame buffer
+ */
+ if (offset < -priv->fb_size || offset > 2 * priv->fb_size) {
+#ifdef DEBUG
+ char str[80];
+
+ snprintf(str, sizeof(str),
+ "[sync_copy fb=%p, from=%p, to=%p, offset=%lx]",
+ priv->fb, from, to, offset);
+ console_puts_select_stderr(true, str);
+#endif
+ return -EFAULT;
+ }
+
+ /*
+ * Silently crop the memcpy. This allows callers to avoid doing
+ * this themselves. It is common for the end pointer to go a
+ * few lines after the end of the frame buffer, since most of
+ * the update algorithms terminate a line after their last write
+ */
+ if (offset + size > priv->fb_size) {
+ size = priv->fb_size - offset;
+ } else if (offset < 0) {
+ size += offset;
+ offset = 0;
+ }
+
+ memcpy(priv->copy_fb + offset, priv->fb + offset, size);
+ }
+
+ return 0;
+}
+#endif
+
/* Set up the colour map */
static int video_pre_probe(struct udevice *dev)
{
diff --git a/include/video.h b/include/video.h
index 813b5653b0..1a0ffd8037 100644
--- a/include/video.h
+++ b/include/video.h
@@ -30,11 +30,14 @@ struct udevice;
* buffer should start on. If 0, 1MB is assumed
* @size: Frame-buffer size, in bytes
* @base: Base address of frame buffer, 0 if not yet known
+ * @copy_base: Base address of a hardware copy of the frame buffer. See
+ * CONFIG_VIDEO_COPY.
*/
struct video_uc_platdata {
uint align;
uint size;
ulong base;
+ ulong copy_base;
};

enum video_polarity {
@@ -75,6 +78,8 @@ enum video_log2_bpp {
* @font_size: Font size in pixels (0 to use a default value)
* @fb: Frame buffer
* @fb_size: Frame buffer size
+ * @copy_fb: Copy of the frame buffer to keep up to date; see struct
+ * video_uc_platdata
* @line_length: Length of each frame buffer line, in bytes. This can be
* set by the driver, but if not, the uclass will set it after
* probing
@@ -101,6 +106,7 @@ struct video_priv {
*/
void *fb;
int fb_size;
+ void *copy_fb;
int line_length;
u32 colour_fg;
u32 colour_bg;
@@ -214,6 +220,29 @@ void video_set_flush_dcache(struct udevice *dev, bool flush);
*/
void video_set_default_colors(struct udevice *dev, bool invert);

+#ifdef CONFIG_VIDEO_COPY
+/**
+ * vidconsole_sync_copy() - Sync back to the copy framebuffer
+ *
+ * This ensures that the copy framebuffer has the same data as the framebuffer
+ * for a particular region. It should be called after the framebuffer is updated
+ *
+ * @from and @to can be in either order. The region between them is synced.
+ *
+ * @dev: Vidconsole device being updated
+ * @from: Start/end address within the framebuffer (->fb)
+ * @to: Other address within the frame buffer
+ * @return 0 if OK, -EFAULT if the start address is before the start of the
+ * frame buffer start
+ */
+int video_sync_copy(struct udevice *dev, void *from, void *to);
+#else
+static inline int video_sync_copy(struct udevice *dev, void *from, void *to)
+{
+ return 0;
+}
+#endif
+
#endif /* CONFIG_DM_VIDEO */

#ifndef CONFIG_DM_VIDEO
--
2.26.2.761.g0e0b3e54be-goog