Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 6b0df682 authored by Mikulas Patocka's avatar Mikulas Patocka Committed by Tomi Valkeinen
Browse files

tgafb: fix data copying



The functions for data copying copyarea_foreward_8bpp and
copyarea_backward_8bpp are buggy, they produce screen corruption.

This patch fixes the functions and moves the logic to one function
"copyarea_8bpp". For simplicity, the function only handles copying that
is aligned on 8 pixes. If we copy an unaligned area, generic function
cfb_copyarea is used.

Signed-off-by: default avatarMikulas Patocka <mpatocka@redhat.com>
Cc: stable@vger.kernel.org
Signed-off-by: default avatarTomi Valkeinen <tomi.valkeinen@ti.com>
parent 62496658
Loading
Loading
Loading
Loading
+51 −213
Original line number Diff line number Diff line
@@ -1139,222 +1139,57 @@ copyarea_line_32bpp(struct fb_info *info, u32 dy, u32 sy,
	__raw_writel(TGA_MODE_SBM_24BPP|TGA_MODE_SIMPLE, tga_regs+TGA_MODE_REG);
}

/* The general case of forward copy in 8bpp mode.  */
static inline void
copyarea_foreward_8bpp(struct fb_info *info, u32 dx, u32 dy, u32 sx, u32 sy,
		       u32 height, u32 width, u32 line_length)
{
	struct tga_par *par = (struct tga_par *) info->par;
	unsigned long i, copied, left;
	unsigned long dpos, spos, dalign, salign, yincr;
	u32 smask_first, dmask_first, dmask_last;
	int pixel_shift, need_prime, need_second;
	unsigned long n64, n32, xincr_first;
	void __iomem *tga_regs;
	void __iomem *tga_fb;

	yincr = line_length;
	if (dy > sy) {
		dy += height - 1;
		sy += height - 1;
		yincr = -yincr;
	}

	/* Compute the offsets and alignments in the frame buffer.
	   More than anything else, these control how we do copies.  */
	dpos = dy * line_length + dx;
	spos = sy * line_length + sx;
	dalign = dpos & 7;
	salign = spos & 7;
	dpos &= -8;
	spos &= -8;

	/* Compute the value for the PIXELSHIFT register.  This controls
	   both non-co-aligned source and destination and copy direction.  */
	if (dalign >= salign)
		pixel_shift = dalign - salign;
	else
		pixel_shift = 8 - (salign - dalign);

	/* Figure out if we need an additional priming step for the
	   residue register.  */
	need_prime = (salign > dalign);
	if (need_prime)
		dpos -= 8;

	/* Begin by copying the leading unaligned destination.  Copy enough
	   to make the next destination address 32-byte aligned.  */
	copied = 32 - (dalign + (dpos & 31));
	if (copied == 32)
		copied = 0;
	xincr_first = (copied + 7) & -8;
	smask_first = dmask_first = (1ul << copied) - 1;
	smask_first <<= salign;
	dmask_first <<= dalign + need_prime*8;
	if (need_prime && copied > 24)
		copied -= 8;
	left = width - copied;

	/* Care for small copies.  */
	if (copied > width) {
		u32 t;
		t = (1ul << width) - 1;
		t <<= dalign + need_prime*8;
		dmask_first &= t;
		left = 0;
	}

	/* Attempt to use 64-byte copies.  This is only possible if the
	   source and destination are co-aligned at 64 bytes.  */
	n64 = need_second = 0;
	if ((dpos & 63) == (spos & 63)
	    && (height == 1 || line_length % 64 == 0)) {
		/* We may need a 32-byte copy to ensure 64 byte alignment.  */
		need_second = (dpos + xincr_first) & 63;
		if ((need_second & 32) != need_second)
			printk(KERN_ERR "tgafb: need_second wrong\n");
		if (left >= need_second + 64) {
			left -= need_second;
			n64 = left / 64;
			left %= 64;
		} else
			need_second = 0;
	}

	/* Copy trailing full 32-byte sections.  This will be the main
	   loop if the 64 byte loop can't be used.  */
	n32 = left / 32;
	left %= 32;

	/* Copy the trailing unaligned destination.  */
	dmask_last = (1ul << left) - 1;

	tga_regs = par->tga_regs_base;
	tga_fb = par->tga_fb_base;

	/* Set up the MODE and PIXELSHIFT registers.  */
	__raw_writel(TGA_MODE_SBM_8BPP|TGA_MODE_COPY, tga_regs+TGA_MODE_REG);
	__raw_writel(pixel_shift, tga_regs+TGA_PIXELSHIFT_REG);
	wmb();

	for (i = 0; i < height; ++i) {
		unsigned long j;
		void __iomem *sfb;
		void __iomem *dfb;

		sfb = tga_fb + spos;
		dfb = tga_fb + dpos;
		if (dmask_first) {
			__raw_writel(smask_first, sfb);
			wmb();
			__raw_writel(dmask_first, dfb);
			wmb();
			sfb += xincr_first;
			dfb += xincr_first;
		}

		if (need_second) {
			__raw_writel(0xffffffff, sfb);
			wmb();
			__raw_writel(0xffffffff, dfb);
			wmb();
			sfb += 32;
			dfb += 32;
		}

		if (n64 && (((unsigned long)sfb | (unsigned long)dfb) & 63))
			printk(KERN_ERR
			       "tgafb: misaligned copy64 (s:%p, d:%p)\n",
			       sfb, dfb);

		for (j = 0; j < n64; ++j) {
			__raw_writel(sfb - tga_fb, tga_regs+TGA_COPY64_SRC);
			wmb();
			__raw_writel(dfb - tga_fb, tga_regs+TGA_COPY64_DST);
			wmb();
			sfb += 64;
			dfb += 64;
		}

		for (j = 0; j < n32; ++j) {
			__raw_writel(0xffffffff, sfb);
			wmb();
			__raw_writel(0xffffffff, dfb);
			wmb();
			sfb += 32;
			dfb += 32;
		}

		if (dmask_last) {
			__raw_writel(0xffffffff, sfb);
			wmb();
			__raw_writel(dmask_last, dfb);
			wmb();
		}

		spos += yincr;
		dpos += yincr;
	}

	/* Reset the MODE register to normal.  */
	__raw_writel(TGA_MODE_SBM_8BPP|TGA_MODE_SIMPLE, tga_regs+TGA_MODE_REG);
}

/* The (almost) general case of backward copy in 8bpp mode.  */
static inline void
copyarea_backward_8bpp(struct fb_info *info, u32 dx, u32 dy, u32 sx, u32 sy,
copyarea_8bpp(struct fb_info *info, u32 dx, u32 dy, u32 sx, u32 sy,
	      u32 height, u32 width, u32 line_length,
	      const struct fb_copyarea *area)
{
	struct tga_par *par = (struct tga_par *) info->par;
	unsigned long i, left, yincr;
	unsigned long depos, sepos, dealign, sealign;
	u32 mask_first, mask_last;
	unsigned long n32;
	unsigned i, yincr;
	int depos, sepos, backward, last_step, step;
	u32 mask_last;
	unsigned n32;
	void __iomem *tga_regs;
	void __iomem *tga_fb;

	/* Do acceleration only if we are aligned on 8 pixels */
	if ((dx | sx | width) & 7) {
		cfb_copyarea(info, area);
		return;
	}

	yincr = line_length;
	if (dy > sy) {
		dy += height - 1;
		sy += height - 1;
		yincr = -yincr;
	}
	backward = dy == sy && dx > sx && dx < sx + width;

	/* Compute the offsets and alignments in the frame buffer.
	   More than anything else, these control how we do copies.  */
	depos = dy * line_length + dx + width;
	sepos = sy * line_length + sx + width;
	dealign = depos & 7;
	sealign = sepos & 7;

	/* ??? The documentation appears to be incorrect (or very
	   misleading) wrt how pixel shifting works in backward copy
	   mode, i.e. when PIXELSHIFT is negative.  I give up for now.
	   Do handle the common case of co-aligned backward copies,
	   but frob everything else back on generic code.  */
	if (dealign != sealign) {
		cfb_copyarea(info, area);
		return;
	}

	/* We begin the copy with the trailing pixels of the
	   unaligned destination.  */
	mask_first = (1ul << dealign) - 1;
	left = width - dealign;

	/* Care for small copies.  */
	if (dealign > width) {
		mask_first ^= (1ul << (dealign - width)) - 1;
		left = 0;
	}
	depos = dy * line_length + dx;
	sepos = sy * line_length + sx;
	if (backward)
		depos += width, sepos += width;

	/* Next copy full words at a time.  */
	n32 = left / 32;
	left %= 32;
	n32 = width / 32;
	last_step = width % 32;

	/* Finally copy the unaligned head of the span.  */
	mask_last = -1 << (32 - left);
	mask_last = (1ul << last_step) - 1;

	if (!backward) {
		step = 32;
		last_step = 32;
	} else {
		step = -32;
		last_step = -last_step;
		sepos -= 32;
		depos -= 32;
	}

	tga_regs = par->tga_regs_base;
	tga_fb = par->tga_fb_base;
@@ -1371,25 +1206,33 @@ copyarea_backward_8bpp(struct fb_info *info, u32 dx, u32 dy, u32 sx, u32 sy,

		sfb = tga_fb + sepos;
		dfb = tga_fb + depos;
		if (mask_first) {
			__raw_writel(mask_first, sfb);

		for (j = 0; j < n32; j++) {
			if (j < 2 && j + 1 < n32 && !backward &&
			    !(((unsigned long)sfb | (unsigned long)dfb) & 63)) {
				do {
					__raw_writel(sfb - tga_fb, tga_regs+TGA_COPY64_SRC);
					wmb();
			__raw_writel(mask_first, dfb);
					__raw_writel(dfb - tga_fb, tga_regs+TGA_COPY64_DST);
					wmb();
					sfb += 64;
					dfb += 64;
					j += 2;
				} while (j + 1 < n32);
				j--;
				continue;
			}

		for (j = 0; j < n32; ++j) {
			sfb -= 32;
			dfb -= 32;
			__raw_writel(0xffffffff, sfb);
			wmb();
			__raw_writel(0xffffffff, dfb);
			wmb();
			sfb += step;
			dfb += step;
		}

		if (mask_last) {
			sfb -= 32;
			dfb -= 32;
			sfb += last_step - step;
			dfb += last_step - step;
			__raw_writel(mask_last, sfb);
			wmb();
			__raw_writel(mask_last, dfb);
@@ -1450,14 +1293,9 @@ tgafb_copyarea(struct fb_info *info, const struct fb_copyarea *area)
	else if (bpp == 32)
		cfb_copyarea(info, area);

	/* Detect overlapping source and destination that requires
	   a backward copy.  */
	else if (dy == sy && dx > sx && dx < sx + width)
		copyarea_backward_8bpp(info, dx, dy, sx, sy, height,
				       width, line_length, area);
	else
		copyarea_foreward_8bpp(info, dx, dy, sx, sy, height,
				       width, line_length);
		copyarea_8bpp(info, dx, dy, sx, sy, height,
			      width, line_length, area);
}