tgafb: fix mode setting with fbset
authorMikulas Patocka <mpatocka@redhat.com>
Thu, 23 Jan 2014 19:42:43 +0000 (14:42 -0500)
committerTomi Valkeinen <tomi.valkeinen@ti.com>
Tue, 11 Feb 2014 13:01:04 +0000 (15:01 +0200)
Mode setting in the TGA driver is broken for these reasons:

- info->fix.line_length is set just once in tgafb_init_fix function. If
  we change videomode, info->fix.line_length is not recalculated - so
  the video mode is changed but the screen is corrupted because of wrong
  info->fix.line_length.

- info->fix.smem_len is set in tgafb_init_fix to the size of the default
  video mode (640x480). If we set a higher resolution,
  info->fix.smem_len is smaller than the current screen size, preventing
  the userspace program from mapping the framebuffer.

This patch fixes it:

- info->fix.line_length initialization is moved to tgafb_set_par so that
  it is recalculated with each mode change.

- info->fix.smem_len is set to a fixed value representing the real
  amount of video ram (the values are taken from xfree86 driver).

- add a check to tgafb_check_var to prevent us from setting a videomode
  that doesn't fit into videoram.

- in tgafb_register, tgafb_init_fix is moved upwards, to be called
  before fb_find_mode (because fb_find_mode already needs the videoram
  size set in tgafb_init_fix).

Signed-off-by: Mikulas Patocka <mpatocka@redhat.com>
Cc: stable@vga.kernel.org
Signed-off-by: Tomi Valkeinen <tomi.valkeinen@ti.com>
drivers/video/tgafb.c

index 07c7df9..83d4458 100644 (file)
@@ -182,6 +182,8 @@ tgafb_check_var(struct fb_var_screeninfo *var, struct fb_info *info)
 
        if (var->xres_virtual != var->xres || var->yres_virtual != var->yres)
                return -EINVAL;
+       if (var->xres * var->yres * (var->bits_per_pixel >> 3) > info->fix.smem_len)
+               return -EINVAL;
        if (var->nonstd)
                return -EINVAL;
        if (1000000000 / var->pixclock > TGA_PLL_MAX_FREQ)
@@ -262,6 +264,7 @@ tgafb_set_par(struct fb_info *info)
        par->yres = info->var.yres;
        par->pll_freq = pll_freq = 1000000000 / info->var.pixclock;
        par->bits_per_pixel = info->var.bits_per_pixel;
+       info->fix.line_length = par->xres * (par->bits_per_pixel >> 3);
 
        tga_type = par->tga_type;
 
@@ -1470,6 +1473,7 @@ tgafb_init_fix(struct fb_info *info)
        int tga_bus_tc = TGA_BUS_TC(par->dev);
        u8 tga_type = par->tga_type;
        const char *tga_type_name = NULL;
+       unsigned memory_size;
 
        switch (tga_type) {
        case TGA_TYPE_8PLANE:
@@ -1477,22 +1481,27 @@ tgafb_init_fix(struct fb_info *info)
                        tga_type_name = "Digital ZLXp-E1";
                if (tga_bus_tc)
                        tga_type_name = "Digital ZLX-E1";
+               memory_size = 2097152;
                break;
        case TGA_TYPE_24PLANE:
                if (tga_bus_pci)
                        tga_type_name = "Digital ZLXp-E2";
                if (tga_bus_tc)
                        tga_type_name = "Digital ZLX-E2";
+               memory_size = 8388608;
                break;
        case TGA_TYPE_24PLUSZ:
                if (tga_bus_pci)
                        tga_type_name = "Digital ZLXp-E3";
                if (tga_bus_tc)
                        tga_type_name = "Digital ZLX-E3";
+               memory_size = 16777216;
                break;
        }
-       if (!tga_type_name)
+       if (!tga_type_name) {
                tga_type_name = "Unknown";
+               memory_size = 16777216;
+       }
 
        strlcpy(info->fix.id, tga_type_name, sizeof(info->fix.id));
 
@@ -1502,9 +1511,8 @@ tgafb_init_fix(struct fb_info *info)
                            ? FB_VISUAL_PSEUDOCOLOR
                            : FB_VISUAL_DIRECTCOLOR);
 
-       info->fix.line_length = par->xres * (par->bits_per_pixel >> 3);
        info->fix.smem_start = (size_t) par->tga_fb_base;
-       info->fix.smem_len = info->fix.line_length * par->yres;
+       info->fix.smem_len = memory_size;
        info->fix.mmio_start = (size_t) par->tga_regs_base;
        info->fix.mmio_len = 512;
 
@@ -1628,6 +1636,9 @@ static int tgafb_register(struct device *dev)
                modedb_tga = &modedb_tc;
                modedbsize_tga = 1;
        }
+
+       tgafb_init_fix(info);
+
        ret = fb_find_mode(&info->var, info,
                           mode_option ? mode_option : mode_option_tga,
                           modedb_tga, modedbsize_tga, NULL,
@@ -1645,7 +1656,6 @@ static int tgafb_register(struct device *dev)
        }
 
        tgafb_set_par(info);
-       tgafb_init_fix(info);
 
        if (register_framebuffer(info) < 0) {
                printk(KERN_ERR "tgafb: Could not register framebuffer\n");