Hacking Up an RGB Framebuffer Driver for Wii-Linux – Take Two

Next, we set up the callback that handles the modified-page-list.

+static void gcnfb_render_hline(struct fb_info *info, const char *vfb_mem,
+				uint32_t byte_offset, uint32_t byte_width)
+{
+	uint32_t *addr0 = (uint32_t *)(vfb_mem + byte_offset);
+	uint32_t *addr1 = (uint32_t *)(fb_mem + byte_offset);
+	uint32_t *addr2 = addr0 + (info->fix.line_length * info->var.yres >> 2);
+
+	int j = byte_width >> 2;
+	while (j--) {
+		uint32_t k = *(addr0 + j);
+		if ( k != *(addr2 + j)) {
+			*(addr2 + j) = k;
+			*(addr1 + j) = rgbrgb16toycbycr(k);
+		}
+	}
+}
+
+static void gcnfb_deferred_io(struct fb_info *info,
+				struct list_head *pagelist)
+{
+	struct page *cur;
+	struct fb_deferred_io *fbdefio = info->fbdefio;
+
+	if (!fbdefio)
+		return;
+
+	list_for_each_entry(cur, &fbdefio->pagelist, lru) {
+		gcnfb_render_hline(info, (char *) info->fix.smem_start,
+					cur->index << PAGE_SHIFT, PAGE_SIZE);
+	}
+}

Note that in addition to partial update provided by defio mechanims, another level of partial update is performed while updating physical framebuffer. The reasoning behind this is that the ‘granularity’ of memory management is ‘PAGE_SIZE’, which is 4KB in Wii-Linux, corresponding to 2048 pixels, or more than 3 horizontal lines of the screen at 640x*** resolution. It is uneconomic to convert the whole memory page when only part, and at most times only a very small part, for instance a mouse cursor, of the page is updated.

By now, the driver is already capable of supporting framebuffer clients using mmap (path C above), such as fbdev in X. But we also have to consider those using paths A and B, otherwise we will lose framebuffer console, among other things.

+static void gcnfb_handle_damage(struct vi_ctl *ctl, int x, int y,
+				int width, int height, char *data)
+{
+	int i;
+	int aligned_x = _ALIGN_DOWN(x, 2);
+	width = _ALIGN_UP(width + (x - aligned_x), 2);
+	x = aligned_x;
+	if ((width <= 0) || +		(x + width > ctl->info->var.xres) ||
+		(y + height > ctl->info->var.yres))
+			return;
+	for (i = y; i < y + height ; i++) { +		const int line_offset = ctl->info->fix.line_length * i;
+		const int byte_offset = line_offset + (x * 2);
+		gcnfb_render_hline(ctl->info,
+					(char *) ctl->info->fix.smem_start,
+					byte_offset, width * 2);
+	}
+}
+
+static void gcnfb_fillrect(struct fb_info *info,
+				   const struct fb_fillrect *rect)
+{
+	struct vi_ctl *ctl = info->par;
+
+	sys_fillrect(info, rect);
+
+	gcnfb_handle_damage(ctl, rect->dx, rect->dy, rect->width,
+				rect->height, info->screen_base);
+}
+
+static void gcnfb_copyarea(struct fb_info *info,
+				   const struct fb_copyarea *area)
+{
+	struct vi_ctl *ctl = info->par;
+
+	sys_copyarea(info, area);
+
+	gcnfb_handle_damage(ctl, area->dx, area->dy, area->width,
+				area->height, info->screen_base);
+}
+
+static void gcnfb_imageblit(struct fb_info *info,
+				const struct fb_image *image)
+{
+	struct vi_ctl *ctl = info->par;
+
+	sys_imageblit(info, image);
+
+	gcnfb_handle_damage(ctl, image->dx, image->dy, image->width,
+				image->height, info->screen_base);
+}
+
+static ssize_t gcnfb_write(struct fb_info *info, const char __user *buf,
+				size_t count, loff_t *ppos)
+{
+	ssize_t result;
+	struct vi_ctl *ctl = info->par;
+	u32 offset = (u32) *ppos;
+
+	result = fb_sys_write(info, buf, count, ppos);
+
+	if (result > 0) {
+		int start = max((int)(offset / info->fix.line_length) - 1, 0);
+		int lines = min((u32)((result / info->fix.line_length) + 1),
+				(u32)info->var.yres);
+
+		gcnfb_handle_damage(ctl, 0, start, info->var.xres,
+					lines, info->screen_base);
+	}
+
+	return result;
+}

 struct fb_ops vifb_ops = {
 	.owner = THIS_MODULE,
 	.fb_setcolreg = vifb_setcolreg,
-	.fb_pan_display = vifb_pan_display,
 	.fb_ioctl = vifb_ioctl,
 	.fb_set_par = vifb_set_par,
 	.fb_check_var = vifb_check_var,
-	.fb_mmap = vifb_mmap,
-	.fb_fillrect = cfb_fillrect,
-	.fb_copyarea = cfb_copyarea,
-	.fb_imageblit = cfb_imageblit,
+	.fb_fillrect = gcnfb_fillrect,
+	.fb_copyarea = gcnfb_copyarea,
+	.fb_imageblit = gcnfb_imageblit,
+	.fb_write = gcnfb_write,
+};

After this and some tiding-up, the driver is fully functional and works almost flawlessly in both console and X. Almost, because sometimes a few continuous lines (remember PAGE_SIZE?) will get stuck and are no longer updated as they should be:

Dead Page in Virtual Framebuffer

Dead Page in Virtual Framebuffer

Apparently something is amiss, but what? There are probably dozens of ‘correct’ ways of debugging the problem, but being no expert at any of them, I went the easier way. Since fb_defio.c is the most likely suspect, I traced the commit logs of this file and came upon this patch. You may find the name of the patch author familiar, especially if you have ever browsed through gcnfb code. Yes, that is one of the key devs of gc-linux. Unfortunately, this patch has not been backported to 2.6.32 kernel series, so we have to do it ourselves.

--- a/drivers/video/fb_defio.c~fb_defio-redo-fix-for-non-dirty-ptes
+++ a/drivers/video/fb_defio.c
@@ -100,6 +100,16 @@ static int fb_deferred_io_mkwrite(struct
 	/* protect against the workqueue changing the page list */
 	mutex_lock(&fbdefio->lock);

+	/*
+	 * We want the page to remain locked from ->page_mkwrite until
+	 * the PTE is marked dirty to avoid page_mkclean() being called
+	 * before the PTE is updated, which would leave the page ignored
+	 * by defio.
+	 * Do this by locking the page here and informing the caller
+	 * about it with VM_FAULT_LOCKED.
+	 */
+	lock_page(page);
+
 	/* we loop through the pagelist before adding in order
 	to keep the pagelist sorted */
 	list_for_each_entry(cur, &fbdefio->pagelist, lru) {
@@ -121,7 +131,7 @@ page_already_added:

 	/* come back after delay to process the deferred IO */
 	schedule_delayed_work(&info->deferred_work, fbdefio->delay);
-	return 0;
+	return VM_FAULT_LOCKED;
 }

 static const struct vm_operations_struct fb_deferred_io_vm_ops = {

And not surprisingly, the ‘dead’ page(s) went away after this patch was applied.

There is, of course, still an important question remaining to be answered: are we doing any better with all this new stuff? Well, as far as x11perf benchmarks go, deferred IO plus partial update gives the best performance among all virtual framebuffer variations in nearly all tests. It also outperforms ‘cube’ driver in about half the tests. So the answer is definitely affirmative.

Kernel patch

This is only for those trying to compile their own kernels.

Patch

This entry was posted in Linux, Wii and tagged , , , , . Bookmark the permalink.

12 Responses to Hacking Up an RGB Framebuffer Driver for Wii-Linux – Take Two

  1. Strece says:

    Hey I tested your driver in my own kernel and it works like a charm ;)
    But I wanted to know is it possible that the wii graphic card can work in 32bit colourdepth, because some XWindow Managers works only a half in 16bit mode. Higher resolultions I think is impossible, because of the PAL or NTSC limitation, or am I wrong?
    I read your text but I’m not so good in understanding the mystical things ;)

    • farter says:

      Hi,
      In short, the hardware can’t work in 32bit. You will have to create 32bit virtual framebuffer and transcode to 16bit on the fly to hardware. It’s possible, but requires a bit more CPU and RAM.

  2. Markus says:

    Hi, is it possible that you can build a 2.6.32.59 kernel with mikep5, your patch and bfs, because I tried it but I’m not so good with this. I can follow instructions, but I can’t deal with compile errors. And I get compile errors, with this kernel and the three patches (bfs patch also throw Hunk failed errors). Or is your prebuilded kernel 2.6.32.41 are enough up to date for the wii?

  3. neutronscott says:

    Hi. I am glad that someone is doing new work on Wii Linux. I just got one (again, years later).

    Probably there is a better place to ask such a question, but what does the ARM processor do now in Linux? I assume nothing. Could we bootstrap it to perform this function? (Or is this idea silly and I have more learning to do?)

    I’m stuck at the bottom of the world for a few more months and looking for something geeky to do. gc-linux wiki seems outdated. Where should I be looking for current sources? Maybe is not too difficult to catch up to mainline again…

    Thanks.

    • farter says:

      Hi, unfortunately almost all low-level Wii-hacking, especially linux-related, has stalled, and for quite some time.

      I don’t understand much about what happens before the kernel boots, you should probably try going through wiibrew, hackmii, bootmii and probably devkitppc sites. You can also try contacting the devs through email or irc, if you need any help.

      Good luck hacking!

  4. jeremy says:

    i just wanted to say thank you again for all your hard work. i hope u keep it up. i have 2 questions though. is the debian image you provide using your driver or cube? what are the “real world” differences i will notice between the cube driver and yours? thanx again

    • farter says:

      The pre-installed images both use the defio vfb driver, because cude only works in lenny and 640×480 non-overscan-safe NTSC mode. Speed-wise, the difference between the two drivers is not very noticeable.

  5. This looks like a good result, is it going to be merged into Wii Linux?

    It might still be useful to allow access to a physical framebuffer for playing video.

    • farter says:

      Eh, it will be up to the GC-Linux devs.

      Video playback from within desktop environment is probably too demanding for Wii, so a driver supporting both vfb and direct access to physical fb may not be able to find many realistic use cases.

  6. “a device framebuffer that is in an usual format”

    s/usual/unusual/

    I think the unusual format referred to was JPEG:
    http://www.gossamer-threads.com/lists/linux/kernel/349223#349223