From: BALATON Zoltan <balaton@eik.bme.hu>
Date: Fri, 21 Apr 2017 17:18:09 +0200
Subject: sm501: Fix hardware cursor

Git-commit: 6a2a5aae02b9a0b53807b9ad91f15cd4988781f9
References: bsc#1172478, CVE-2020-13765

Rework HWC handling to simplify it and fix cursor not updating on
screen as needed. Previously cursor was not updated because checking
for changes in a line overrode the update flag set for the cursor but
fixing this is not enough because the cursor should also be updated if
its shape or location changes. Introduce hwc_invalidate() function to
handle that similar to other display controller models.

Signed-off-by: BALATON Zoltan <balaton@eik.bme.hu>
Reviewed-by: Peter Maydell <peter.maydell@linaro.org>
Tested-by: Aurelien Jarno <aurelien@aurel32.net>
Message-id: 6970a5e9868b7246656c1d02038dc5d5fa369507.1492787889.git.balaton@eik.bme.hu
Signed-off-by: Peter Maydell <peter.maydell@linaro.org>
Signed-off-by: Bruce Rogers <brogers@suse.com>
---
 hw/sm501.c          | 191 +++++++++++++++++++++++++-------------------
 hw/sm501_template.h |  23 +++---
 2 files changed, 117 insertions(+), 97 deletions(-)

diff --git a/hw/sm501.c b/hw/sm501.c
index b7ac7f9bffc8f0fa34ac51d15845..66e4187e0a8544391a2a3a8260ae 100644
--- a/hw/sm501.c
+++ b/hw/sm501.c
@@ -545,6 +545,24 @@ static uint32_t get_local_mem_size_index(uint32_t size)
     return index;
 }
 
+static inline int get_width(SM501State *s, int crt)
+{
+    int width = crt ? s->dc_crt_h_total : s->dc_panel_h_total;
+    return (width & 0x00000FFF) + 1;
+}
+
+static inline int get_height(SM501State *s, int crt)
+{
+    int height = crt ? s->dc_crt_v_total : s->dc_panel_v_total;
+    return (height & 0x00000FFF) + 1;
+}
+
+static inline int get_bpp(SM501State *s, int crt)
+{
+    int bpp = crt ? s->dc_crt_control : s->dc_panel_control;
+    return 1 << (bpp & 3);
+}
+
 /**
  * Check the availability of hardware cursor.
  * @param crt  0 for PANEL, 1 for CRT.
@@ -559,10 +577,10 @@ static inline int is_hwc_enabled(SM501State *state, int crt)
  * Get the address which holds cursor pattern data.
  * @param crt  0 for PANEL, 1 for CRT.
  */
-static inline uint32_t get_hwc_address(SM501State *state, int crt)
+static inline uint8_t *get_hwc_address(SM501State *state, int crt)
 {
     uint32_t addr = crt ? state->dc_crt_hwc_addr : state->dc_panel_hwc_addr;
-    return (addr & 0x03FFFFF0)/* >> 4*/;
+    return state->local_mem + (addr & 0x03FFFFF0);
 }
 
 /**
@@ -588,50 +606,48 @@ static inline uint32_t get_hwc_x(SM501State *state, int crt)
 }
 
 /**
- * Get the cursor position in x coordinate.
+ * Get the hardware cursor palette.
  * @param crt  0 for PANEL, 1 for CRT.
- * @param index  0, 1, 2 or 3 which specifies color of corsor dot.
+ * @param palette  pointer to a [3 * 3] array to store color values in
  */
-static inline uint16_t get_hwc_color(SM501State *state, int crt, int index)
+static inline void get_hwc_palette(SM501State *state, int crt, uint8_t *palette)
 {
-    uint32_t color_reg = 0;
-    uint16_t color_565 = 0;
-
-    if (index == 0) {
-        return 0;
-    }
-
-    switch (index) {
-    case 1:
-    case 2:
-        color_reg = crt ? state->dc_crt_hwc_color_1_2
-                        : state->dc_panel_hwc_color_1_2;
-        break;
-    case 3:
-        color_reg = crt ? state->dc_crt_hwc_color_3
-                        : state->dc_panel_hwc_color_3;
-        break;
-    default:
-        printf("invalid hw cursor color.\n");
-        abort();
-    }
+    int i;
+    uint32_t color_reg;
+    uint16_t rgb565;
+
+    for (i = 0; i < 3; i++) {
+        if (i + 1 == 3) {
+            color_reg = crt ? state->dc_crt_hwc_color_3
+                            : state->dc_panel_hwc_color_3;
+        } else {
+            color_reg = crt ? state->dc_crt_hwc_color_1_2
+                            : state->dc_panel_hwc_color_1_2;
+        }
 
-    switch (index) {
-    case 1:
-    case 3:
-        color_565 = (uint16_t)(color_reg & 0xFFFF);
-        break;
-    case 2:
-        color_565 = (uint16_t)((color_reg >> 16) & 0xFFFF);
-        break;
+        if (i + 1 == 2) {
+            rgb565 = (color_reg >> 16) & 0xFFFF;
+        } else {
+            rgb565 = color_reg & 0xFFFF;
+        }
+        palette[i * 3 + 0] = (rgb565 << 3) & 0xf8; /* red */
+        palette[i * 3 + 1] = (rgb565 >> 3) & 0xfc; /* green */
+        palette[i * 3 + 2] = (rgb565 >> 8) & 0xf8; /* blue */
     }
-    return color_565;
 }
 
-static int within_hwc_y_range(SM501State *state, int y, int crt)
+static inline void hwc_invalidate(SM501State *s, int crt)
 {
-    int hwc_y = get_hwc_y(state, crt);
-    return (hwc_y <= y && y < hwc_y + SM501_HWC_HEIGHT);
+    int w = get_width(s, crt);
+    int h = get_height(s, crt);
+    int bpp = get_bpp(s, crt);
+    int start = get_hwc_y(s, crt);
+    int end = MIN(h, start + SM501_HWC_HEIGHT) + 1;
+
+    start *= w * bpp;
+    end *= w * bpp;
+
+    memory_region_set_dirty(&s->local_mem_region, start, end - start);
 }
 
 static void sm501_2d_operation(SM501State * s)
@@ -1006,11 +1022,19 @@ static void sm501_disp_ctrl_write(void *opaque, hwaddr addr,
 	break;
 
     case SM501_DC_PANEL_HWC_ADDR:
-	s->dc_panel_hwc_addr = value & 0x8FFFFFF0;
-	break;
+        value &= 0x8FFFFFF0;
+        if (value != s->dc_panel_hwc_addr) {
+            hwc_invalidate(s, 0);
+            s->dc_panel_hwc_addr = value;
+        }
+        break;
     case SM501_DC_PANEL_HWC_LOC:
-	s->dc_panel_hwc_location = value & 0x0FFF0FFF;
-	break;
+        value &= 0x0FFF0FFF;
+        if (value != s->dc_panel_hwc_location) {
+            hwc_invalidate(s, 0);
+            s->dc_panel_hwc_location = value;
+        }
+        break;
     case SM501_DC_PANEL_HWC_COLOR_1_2:
 	s->dc_panel_hwc_color_1_2 = value;
 	break;
@@ -1041,11 +1065,19 @@ static void sm501_disp_ctrl_write(void *opaque, hwaddr addr,
 	break;
 
     case SM501_DC_CRT_HWC_ADDR:
-	s->dc_crt_hwc_addr = value & 0x8FFFFFF0;
-	break;
+        value &= 0x8FFFFFF0;
+        if (value != s->dc_crt_hwc_addr) {
+            hwc_invalidate(s, 1);
+            s->dc_crt_hwc_addr = value;
+        }
+        break;
     case SM501_DC_CRT_HWC_LOC:
-	s->dc_crt_hwc_location = value & 0x0FFF0FFF;
-	break;
+        value &= 0x0FFF0FFF;
+        if (value != s->dc_crt_hwc_location) {
+            hwc_invalidate(s, 1);
+            s->dc_crt_hwc_location = value;
+        }
+        break;
     case SM501_DC_CRT_HWC_COLOR_1_2:
 	s->dc_crt_hwc_color_1_2 = value;
 	break;
@@ -1167,8 +1199,9 @@ static const MemoryRegionOps sm501_2d_engine_ops = {
 typedef void draw_line_func(uint8_t *d, const uint8_t *s,
 			    int width, const uint32_t *pal);
 
-typedef void draw_hwc_line_func(SM501State * s, int crt, uint8_t * palette,
-                                int c_y, uint8_t *d, int width);
+typedef void draw_hwc_line_func(uint8_t *d, const uint8_t *s,
+                                int width, const uint8_t *palette,
+                                int c_x, int c_y);
 
 #define DEPTH 8
 #include "sm501_template.h"
@@ -1254,12 +1287,11 @@ static inline int get_depth_index(DisplayState *s)
 
 static void sm501_draw_crt(SM501State * s)
 {
-    int y;
-    int width = (s->dc_crt_h_total & 0x00000FFF) + 1;
-    int height = (s->dc_crt_v_total & 0x00000FFF) + 1;
-
-    uint8_t  * src = s->local_mem;
-    int src_bpp = 0;
+    int y, c_x = 0, c_y = 0;
+    uint8_t *hwc_src = NULL, *src = s->local_mem;
+    int width = get_width(s, 1);
+    int height = get_height(s, 1);
+    int src_bpp = get_bpp(s, 1);
     int dst_bpp = ds_get_bytes_per_pixel(s->ds) + (ds_get_bits_per_pixel(s->ds) % 8 ? 1 : 0);
     uint32_t * palette = (uint32_t *)&s->dc_palette[SM501_DC_CRT_PALETTE
 						    - SM501_DC_PANEL_PALETTE];
@@ -1274,19 +1306,16 @@ static void sm501_draw_crt(SM501State * s)
     ram_addr_t offset = 0;
 
     /* choose draw_line function */
-    switch (s->dc_crt_control & 3) {
-    case SM501_DC_CRT_CONTROL_8BPP:
-	src_bpp = 1;
-	draw_line = draw_line8_funcs[ds_depth_index];
-	break;
-    case SM501_DC_CRT_CONTROL_16BPP:
-	src_bpp = 2;
-	draw_line = draw_line16_funcs[ds_depth_index];
-	break;
-    case SM501_DC_CRT_CONTROL_32BPP:
-	src_bpp = 4;
-	draw_line = draw_line32_funcs[ds_depth_index];
-	break;
+    switch (src_bpp) {
+    case 1:
+        draw_line = draw_line8_funcs[ds_depth_index];
+        break;
+    case 2:
+        draw_line = draw_line16_funcs[ds_depth_index];
+        break;
+    case 4:
+        draw_line = draw_line32_funcs[ds_depth_index];
+        break;
     default:
 	printf("sm501 draw crt : invalid DC_CRT_CONTROL=%x.\n",
 	       s->dc_crt_control);
@@ -1296,18 +1325,12 @@ static void sm501_draw_crt(SM501State * s)
 
     /* set up to draw hardware cursor */
     if (is_hwc_enabled(s, 1)) {
-        int i;
-
-        /* get cursor palette */
-        for (i = 0; i < 3; i++) {
-            uint16_t rgb565 = get_hwc_color(s, 1, i + 1);
-            hwc_palette[i * 3 + 0] = (rgb565 & 0xf800) >> 8; /* red */
-            hwc_palette[i * 3 + 1] = (rgb565 & 0x07e0) >> 3; /* green */
-            hwc_palette[i * 3 + 2] = (rgb565 & 0x001f) << 3; /* blue */
-        }
-
         /* choose cursor draw line function */
         draw_hwc_line = draw_hwc_line_funcs[ds_depth_index];
+        hwc_src = get_hwc_address(s, 1);
+        c_x = get_hwc_x(s, 1);
+        c_y = get_hwc_y(s, 1);
+        get_hwc_palette(s, 1, hwc_palette);
     }
 
     /* adjust console size */
@@ -1320,14 +1343,16 @@ static void sm501_draw_crt(SM501State * s)
 
     /* draw each line according to conditions */
     for (y = 0; y < height; y++) {
-	int update_hwc = draw_hwc_line ? within_hwc_y_range(s, y, 1) : 0;
-	int update = full_update || update_hwc;
+        int update, update_hwc;
         ram_addr_t page0 = offset;
         ram_addr_t page1 = offset + width * src_bpp - 1;
 
-	/* check dirty flags for each line */
-        update = memory_region_get_dirty(&s->local_mem_region, page0,
-                                         page1 - page0, DIRTY_MEMORY_VGA);
+        /* check if hardware cursor is enabled and we're within its range */
+        update_hwc = draw_hwc_line && c_y <= y && y < c_y + SM501_HWC_HEIGHT;
+        update = full_update || update_hwc;
+        /* check dirty flags for each line */
+        update |= memory_region_get_dirty(&s->local_mem_region, page0,
+                                          page1 - page0, DIRTY_MEMORY_VGA);
 
 	/* draw line and change status */
 	if (update) {
@@ -1338,7 +1363,7 @@ static void sm501_draw_crt(SM501State * s)
 
             /* draw haredware cursor */
             if (update_hwc) {
-                draw_hwc_line(s, 1, hwc_palette, y - get_hwc_y(s, 1), d, width);
+                draw_hwc_line(d, hwc_src, width, hwc_palette, c_x, y - c_y);
             }
 
 	    if (y_start < 0)
diff --git a/hw/sm501_template.h b/hw/sm501_template.h
index 2d4a3d8b4864247641eb47c98134..fc555ab698abfc743f63381ffa78 100644
--- a/hw/sm501_template.h
+++ b/hw/sm501_template.h
@@ -99,29 +99,24 @@ static void glue(draw_line32_, PIXEL_NAME)(
 /**
  * Draw hardware cursor image on the given line.
  */
-static void glue(draw_hwc_line_, PIXEL_NAME)(SM501State * s, int crt,
-                         uint8_t * palette, int c_y, uint8_t *d, int width)
+static void glue(draw_hwc_line_, PIXEL_NAME)(uint8_t *d, const uint8_t *s,
+                 int width, const uint8_t *palette, int c_x, int c_y)
 {
-    int x, i;
+    int i;
     uint8_t bitset = 0;
 
-    /* get hardware cursor pattern */
-    uint32_t cursor_addr = get_hwc_address(s, crt);
-    assert(0 <= c_y && c_y < SM501_HWC_HEIGHT);
-    cursor_addr += 64 * c_y / 4;  /* 4 pixels per byte */
-    cursor_addr += s->base;
-
     /* get cursor position */
-    x = get_hwc_x(s, crt);
-    d += x * BPP;
+    assert(0 <= c_y && c_y < SM501_HWC_HEIGHT);
+    s += SM501_HWC_WIDTH * c_y / 4;  /* 4 pixels per byte */
+    d += c_x * BPP;
 
-    for (i = 0; i < SM501_HWC_WIDTH && x + i < width; i++) {
+    for (i = 0; i < SM501_HWC_WIDTH && c_x + i < width; i++) {
         uint8_t v;
 
         /* get pixel value */
         if (i % 4 == 0) {
-            bitset = ldub_phys(cursor_addr);
-            cursor_addr++;
+            bitset = ldub_p(s);
+            s++;
         }
         v = bitset & 3;
         bitset >>= 2;
