Debian on the Cat Nova

root@mine:~# adb devices
List of devices attached
0123456789ABCDEF        device

Sure, why would you ever connect more than one android device... At least rooting is easy (or maybe someone else had already done it).

root@mine:~# adb -s 0123456789ABCDEF shell
# id
uid=0(root) gid=0(root)
# cat /proc/version
Linux version 2.6.35.7-tcc (keyu@emdoor-r710) (gcc version 4.4.3 (GCC) ) #51 Wed Oct 26 10:10:41 HKT 2011

Let's first try to just sync the debian-jessie from another tables and hope for it to work on a 2.6 kernel.

Mostly it did, except that Android's mount needed an explicit source even for remounts:

nova / % cat start.sh
setprop ctl.stop media
setprop ctl.stop zygote
sleep 1
setprop ctl.stop bootanim
sleep 1
setprop ctl.stop bootanim

mount -o remount,rw,suid,dev /dev/block/mtdblock5 /data
mount -o remount,rw /dev/block/mtdblock2 /system
mount -t proc proc /data/debian-jessie/proc
mount -t sysfs sysfs /data/debian-jessie/sys
mount -t devpts devpts /data/debian-jessie/dev/pts
mount -o bind /mnt/sdcard /data/debian-jessie/sdcard

So, will X just start?

(++) using VT number 1

(EE)
Fatal server error:
(EE) xf86OpenConsole: Cannot open virtual console 1 (No such device or address)
(EE)
(EE)
Please consult the The X.Org Foundation support
         at http://wiki.x.org
 for help.
(EE) Please also check the log file at "/var/log/Xorg.0.log" for additional information.

... of course not.

After some spelunking, it seems that I can re-use /dev/tcc-uart0 as a valid terminal for X to "run on".

However, starting Xorg would now reboot the system.

On the other hand, a simple

nova /dev % dd if=urandom of=fb0

would produce random dots quite fine (and not crash).

So probably the Xorg server is setting some not-so-good video mode.

nova / % gdb Xorg
(gdb) break ioctl
Breakpoint 1 at 0x4035610c: file ../sysdeps/unix/syscall-template.S, line 82.
(gdb) run -sharevts -noreset -retro -verbose vt1
(II) FBDEV: driver for framebuffer: fbdev
(++) using VT number 1


Breakpoint 1, ioctl () at ../sysdeps/unix/syscall-template.S:82
82      ../sysdeps/unix/syscall-template.S: No such file or directory.
(gdb) print /x $r1
$1 = 0x5603
(gdb) cont
Continuing.
[tcsetpgrp failed in terminal_inferior: Operation not permitted]
(WW) xf86OpenConsole: VT_GETSTATE failed: Invalid argument
(WW) Falling back to old probe method for fbdev
(II) Loading /usr/lib/xorg/modules/libfbdevhw.so
(II) Module fbdevhw: vendor="X.Org Foundation"
        compiled for 1.16.4, module version = 0.0.2
(II) FBDEV(0): using /dev/fb0
(WW) VGA arbiter: cannot open kernel arbiter, no multi-card support

Breakpoint 1, ioctl () at ../sysdeps/unix/syscall-template.S:82
82      in ../sysdeps/unix/syscall-template.S
(gdb) print /x $r1
$2 = 0x4602
(gdb) cont
Continuing.

Breakpoint 1, ioctl () at ../sysdeps/unix/syscall-template.S:82
82      in ../sysdeps/unix/syscall-template.S
(gdb) print /x $r1
$3 = 0x4600
(gdb) print /x $r2
$3 = 0x2a199ab0
(gdb) step
Single stepping until exit from function fbdevHWInit,
which has no line number information.
Warning:
Cannot insert breakpoint 0.
Cannot access memory at address 0x320

0x408df1d6 in ?? () from /usr/lib/xorg/modules/drivers/fbdev_drv.so
(gdb) x /20lx 0x2a199ab0
0x2a199ab0:     0x00000320      0x00000258      0x00000320      0x000004b0
0x2a199ac0:     0x00000000      0x00000000      0x00000020      0x00000000
0x2a199ad0:     0x00000010      0x00000008      0x00000000      0x00000008
0x2a199ae0:     0x00000008      0x00000000      0x00000000      0x00000008
0x2a199af0:     0x00000000      0x00000018      0x00000008      0x00000000
(gdb) 
0x2a199b00:     0x00000000      0x00000010      0x0000005f      0x0000007f
0x2a199b10:     0x00000000      0x00000000      0x00000000      0x00000000
0x2a199b20:     0x00000000      0x00000000      0x00000000      0x00000000
0x2a199b30:     0x00000000      0x00000000      0x00000000      0x00000000
0x2a199b40:     0x00000000      0x00000000      0x00000000      0x00000000

Comparing struct fb_var_creeninfo, this looks pretty sane.

(gdb) cont
Continuing.
(==) FBDEV(0): Depth 24, (==) framebuffer bpp 32
(==) FBDEV(0): RGB weight 888
(==) FBDEV(0): Default visual is TrueColor
(==) FBDEV(0): Using gamma correction (1.0, 1.0, 1.0)
(II) FBDEV(0): hardware: tccfb (video memory: 3750kB)
(II) FBDEV(0): checking modes against framebuffer device...

Breakpoint 1, ioctl () at ../sysdeps/unix/syscall-template.S:82
82      in ../sysdeps/unix/syscall-template.S
(gdb) print /x $r1
$5 = 0x4601
(gdb) set $r1 = 0x4600
(gdb) cont
Continuing.
(II) FBDEV(0):  mode "800x600" ok
(II) FBDEV(0): checking modes against monitor...
(--) FBDEV(0): Virtual size is 800x600 (pitch 800)
(**) FBDEV(0):  Built-in mode "current"
(==) FBDEV(0): DPI set to (96, 96)
(II) Loading /usr/lib/xorg/modules/libfb.so
(II) Module fb: vendor="X.Org Foundation"
        compiled for 1.16.4, module version = 1.0.0
(**) FBDEV(0): using shadow framebuffer
(II) Loading /usr/lib/xorg/modules/libshadow.so
(II) Module shadow: vendor="X.Org Foundation"
        compiled for 1.16.4, module version = 1.1.0
fbdev: PreInit done
(==) Depth 24 pixmap format is 32 bpp
fbdev: FBDevScreenInit 0

Breakpoint 1, ioctl () at ../sysdeps/unix/syscall-template.S:82
82      in ../sysdeps/unix/syscall-template.S
(gdb) print /x $r1
$6 = 0x4600
(gdb) cont
Breakpoint 1, ioctl () at ../sysdeps/unix/syscall-template.S:82
82      in ../sysdeps/unix/syscall-template.S
(gdb) print /x $r1
$7 = 0x4601
(gdb) set $r1 = 0x4600
(gdb) cont
Continuing.

Breakpoint 1, ioctl () at ../sysdeps/unix/syscall-template.S:82
82      in ../sysdeps/unix/syscall-template.S
(gdb) print /x $r1
$8 = 0x4602
(gdb) cont
Continuing.

Breakpoint 1, ioctl () at ../sysdeps/unix/syscall-template.S:82
82      in ../sysdeps/unix/syscall-template.S
(gdb) print /x $r1
$9 = 0x4600
(gdb) cont
Continuing.

Breakpoint 1, ioctl () at ../sysdeps/unix/syscall-template.S:82
82      in ../sysdeps/unix/syscall-template.S
(gdb) print /x $r1
$10 = 0x4611
(gdb) cont
Continuing.

Breakpoint 1, ioctl () at ../sysdeps/unix/syscall-template.S:82
82      in ../sysdeps/unix/syscall-template.S
(gdb) print /x $r1
$11 = 0x4606
(gdb) cont
Continuing.

... and hard reset. So either it's something else entirely or the FBIOPAN_DISPLAY ioctly was a bad idea.

Let's see.

....
Breakpoint 1, ioctl () at ../sysdeps/unix/syscall-template.S:82
82      in ../sysdeps/unix/syscall-template.S
1: /x $r1 = 0x4606
(gdb) set $r1 = 0x4600
(gdb) cont
Continuing.

... and hard reset.

Hm, maybe something is wrong with the memory layout as returned by FBIOGET_FSCREENINFO?

Commands such as

nova /dev % while sleep 0.025; do echo -ne '\xff\0\x00\xff'; done > /dev/fb0

and a keen eye (in particular on the timing of wrapping) tell us that it is 8bit blue, 8bit green, 8bit red, 8bit alpha, no extra padding per row.

Breakpoint 1, ioctl () at ../sysdeps/unix/syscall-template.S:82
82      in ../sysdeps/unix/syscall-template.S
1: /x $r1 = 0x4602
(gdb) print /x $r2
$1 = 0x2a199a6c
(gdb) step
0x4093091e in fbdevHWInit () from /usr/lib/xorg/modules/libfbdevhw.so
1: /x $r1 = 0x4602
(gdb) x /20lx 0x2a199a6c
0x2a199a6c:     0x66636374      0x00000062      0x00000000      0x00000000
0x2a199a7c:     0x58000000      0x003a9800      0x00000000      0x00000000
0x2a199a8c:     0x00000002      0x00010000      0x00000000      0x00000c80
0x2a199a9c:     0x00000000      0x00000000      0x00000000      0x00000000
0x2a199aac:     0x00000000      0x00000000      0x00000000      0x00000000

But even with careful (or not so much) manipulation of the returned fb_fix_screeninfo I could not get it to stay alive.

Maybe it's dying on some other syscall? Time for another debugging tool.

nova / % apt-get install strace
nova / % strace -ttf Xorg -sharevts -noreset -retro -verbose vt1
...
14:13:27.695206 clock_gettime(CLOCK_MONOTONIC, {241, 976042669}) = 0
14:13:27.695332 write(0, "[   241.976] ", 13) = 13
14:13:27.695471 write(0, "fbdev: FBDevScreenInit 0\n", 25) = 25
14:13:27.695681 mmap2(NULL, 3842048, PROT_READ|PROT_WRITE, MAP_SHARED, 13, 0) = 0x40980000
14:13:27.696053 ioctl(13, FBIOGET_VSCREENINFO, 0x2a199b50) = 0
14:13:27.696191 ioctl(13, FBIOPUT_VSCREENINFO, 0xbe92984c) = 0
14:13:27.698429 ioctl(13, FBIOGET_FSCREENINFO, 0x2a199a6c) = 0
14:13:27.698526 ioctl(13, FBIOGET_VSCREENINFO, 0x2a199ab0) = 0
14:13:27.698648 ioctl(13, FBIOBLANK, 0x1) = 0
14:13:27.698765 ioctl(13, FBIOPAN_DISPLAY, 0x2a199ab0) = 0
14:13:27.709122 mmap2(NULL, 15364096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x40d2a000
14:13:27.738673 write(2, "(==) FBDEV(0): Backing store ena"..., 37(==) FBDEV(0): Backing store enabled
) = 37
14:13:27.738927 clock_gettime(CLOCK_MONOTONIC, {242, 19778169}) = 0
14:13:27.739091 write(0, "[   242.019] ", 13root@mine:~#

Hm, so maybe it's actually dying on memory access to the FB ram?

Breakpoint 1, ioctl () at ../sysdeps/unix/syscall-template.S:82
82      in ../sysdeps/unix/syscall-template.S
1: /x $r1 = 0x4606
(gdb) display /i $pc
2: x/i $pc
=> 0x4035610c <ioctl+12>:       it      cc
(gdb) stepi
0x4035610e      82      in ../sysdeps/unix/syscall-template.S
2: x/i $pc
=> 0x4035610e <ioctl+14>:       bxcc    lr
1: /x $r1 = 0x4606
(gdb) undisplay 1 
(gdb) break mmap
Breakpoint 2 at 0x40358b52: file ../ports/sysdeps/unix/sysv/linux/arm/mmap.S, line 29.
(gdb) cont
Continuing.

Breakpoint 2, mmap () at ../ports/sysdeps/unix/sysv/linux/arm/mmap.S:29
29      ../ports/sysdeps/unix/sysv/linux/arm/mmap.S: No such file or directory.
2: x/i $pc
=> 0x40358b52 <mmap+2>: ldr     r5, [sp, #8]
(gdb) stepi
... (for a very long time) ...
(gdb) bt
#0  0x4095011e in fbInitVisuals () from /usr/lib/xorg/modules/libfb.so
#1  0x40955186 in fbFinishScreenInit () from /usr/lib/xorg/modules/libfb.so
#2  0x409552fc in fbScreenInit () from /usr/lib/xorg/modules/libfb.so
#3  0x408ded98 in ?? () from /usr/lib/xorg/modules/drivers/fbdev_drv.so
(gdb) stepi
...
Program received signal SIGSEGV, Segmentation fault.
0x408dedb8 in ?? () from /usr/lib/xorg/modules/drivers/fbdev_drv.so

:( By observing Xorg, we changed its behavior. Not cool.

At this point I checked if the touchscreen is worth spending more work on,

nova / % xxd /dev/input/event2

shows quite responsive events even with a stylus. So yeah, as slow as the tablet seemed when running Android, this is pretty snappy.

So let's do some experiments:

nova ~ % cat test.c
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/mman.h>

int main(void) {
        int fb = open("/dev/fb0", O_RDWR);
        unsigned int *pixels = mmap((void *)0, 800 * 600 * 4, PROT_READ|PROT_WRITE, MAP_SHARED, fb, 0);

        for(int x = 0; x < 800; ++x) {
                for(int y = 0; y < 600; ++y) {
                        pixels[y * 800 + x] = (x + y) | 0xff000000;
                }
        }

        return 0;
}

nova ~ % gcc test.c -o test
test.c: In function 'main':
test.c:10:2: error: 'for' loop initial declarations are only allowed in C99 or C11 mode
  for(int x = 0; x < 800; ++x) {
  ^
test.c:10:2: note: use option -std=c99, -std=gnu99, -std=c11 or -std=gnu11 to compile your code
test.c:11:3: error: 'for' loop initial declarations are only allowed in C99 or C11 mode
   for(int y = 0; y < 600; ++y) {
   ^

Riiiiight...

nova ~ % gcc -std=c99 test.c -o test

Works like a charm. So clearly, just mapping the fb and drawing to it works as expected. Even writing to the full advertised virtual y resolution of 1200 works. So does writing to the mapping with byte-oriented access, so it's not an obvious alignment issue either.

But even when disabling all of FBIOPUT_VSCREENINFO, FBIOPAN_DISPLAY and FBIOBLANK the system would still hard-reset on starting Xorg.

Given the aim here is not to produce a high quality system, but just a worksforme, how about making a fbdev-dummy hybrid driver instead of trying to debug whatever fbdev is actuall doing?

nova ~ % git clone  https://gitlab.freedesktop.org/xorg/driver/xf86-video-dummy
... after some failing compiles ...
nove ~ % git checkout 8706f60ab457867c120dd44e812b8fadc2be7179
nova ~/xf86-video-dummy % make
gcc -std=c99 \
        -DMULTITOUCH -DPACKAGE_VERSION_MAJOR=2 -DPACKAGE_VERSION_MINOR=9 -DPACKAGE_VERSION_PATCHLEVEL=2 \
        -I src -I include -I /usr/include/libevdev-1.0 -I /usr/include/xorg -I /usr/include/pixman-1 \
        -fPIC -shared -o yolo.so src/*.c
src/dummy_dga.c: In function 'DUMMYDGAInit':
src/dummy_dga.c:103:4: warning: implicit declaration of function 'DGAInit' [-Wimplicit-function-declaration]
    return DGAInit(pScreen, &DUMMYDGAFuncs, modes, num);
    ^
In file included from src/dummy_driver.c:28:0:
/usr/include/xorg/fb.h:94:2: error: #error "GLYPHPADBYTES must be 4"
 #error "GLYPHPADBYTES must be 4"
  ^
Makefile:2: recipe for target 'yolo.so' failed
make: *** [yolo.so] Error 1

Given that it needs to be 4, well let's make it 4. Also we apparently need XORG_VERSION_CURRENT, so

nova ~/xf86-video-dummy % git diff
diff --git a/src/dummy_driver.c b/src/dummy_driver.c
index 9d4d5bf..9593307 100644
--- a/src/dummy_driver.c
+++ b/src/dummy_driver.c
@@ -14,6 +14,8 @@
 /* All drivers initialising the SW cursor need this */
 #include "mipointer.h"

+#include "xorg/xorg-server.h"
+
 /* All drivers using the mi colormap manipulation need this */
 #include "micmap.h"
 nova ~/xf86-video-dummy % make install
gcc -std=c99 \
        -DMULTITOUCH -DPACKAGE_VERSION_MAJOR=2 -DPACKAGE_VERSION_MINOR=9 -DPACKAGE_VERSION_PATCHLEVEL=2 -DGLYPHPADBYTES=4 -DXFree86LOADER \
        -I src -I include -I /usr/include/libevdev-1.0 -I /usr/include/xorg -I /usr/include/pixman-1 \
        -fPIC -shared -o yolo.so src/*.c
src/dummy_dga.c: In function 'DUMMYDGAInit':
src/dummy_dga.c:103:4: warning: implicit declaration of function 'DGAInit' [-Wimplicit-function-declaration]
    return DGAInit(pScreen, &DUMMYDGAFuncs, modes, num);
    ^
cp yolo.so /usr/lib/xorg/modules/drivers/dummy_drv.so

Now changing our video driver to "dummy" and we can start Xorg (but don't see a thing yet). Which is expected. We still need to move our pixmap data onto the actual /dev/fb0.

After some experiments it became apparent that mmap(2)-ing /dev/fb0 would not work reliably, but normal file I/O to it would always work. So I created the least quality code which would still do the blit for me:

nova ~/xf86-video-dummy % git diff
diff --git a/src/dummy_driver.c b/src/dummy_driver.c
index 9d4d5bf..4ae0113 100644
--- a/src/dummy_driver.c
+++ b/src/dummy_driver.c
@@ -3,6 +3,14 @@
  * Copyright 2002, SuSE Linux AG, Author: Egbert Eich
  */
 
+#define _GNU_SOURCE
+#include <sched.h>
+#include <sys/mman.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <unistd.h>
+
 #ifdef HAVE_CONFIG_H
 #include "config.h"
 #endif
@@ -14,6 +22,8 @@
 /* All drivers initialising the SW cursor need this */
 #include "mipointer.h"
 
+#include "xorg/xorg-server.h"
+
 /* All drivers using the mi colormap manipulation need this */
 #include "micmap.h"
 
@@ -363,15 +373,9 @@ DUMMYPreInit(ScrnInfoPtr pScrn, int flags)
 
     xf86GetOptValBool(dPtr->Options, OPTION_SW_CURSOR,&dPtr->swCursor);
 
-    if (device->videoRam != 0) {
-	pScrn->videoRam = device->videoRam;
-	xf86DrvMsg(pScrn->scrnIndex, X_CONFIG, "VideoRAM: %d kByte\n",
-		   pScrn->videoRam);
-    } else {
-	pScrn->videoRam = 4096;
-	xf86DrvMsg(pScrn->scrnIndex, X_PROBED, "VideoRAM: %d kByte\n",
-		   pScrn->videoRam);
-    }
+    pScrn->videoRam = 800 * 600 * 4 / 1024;
+    xf86DrvMsg(pScrn->scrnIndex, X_PROBED, "VideoRAM: %d kByte\n",
+	       pScrn->videoRam);
     
     if (device->dacSpeeds[0] != 0) {
 	maxClock = device->dacSpeeds[0];
@@ -515,6 +519,26 @@ DUMMYLoadPalette(
 
 static ScrnInfoPtr DUMMYScrn; /* static-globalize it */
 
+static int blitter(void *xorgVideoVoid) {
+    unsigned int *xorgVideo = xorgVideoVoid;
+
+    unsigned int *interim = malloc(800 * 600 * 4);
+
+    while(1) {
+	usleep(20000);
+	for(int y = 0; y < 600; ++y) {
+		for(int x = 0; x < 800; ++x) {
+			interim[800 * (599 - y) + (799 - x)] = xorgVideo[800 * y + x] | 0xff000000ul;
+		}
+	}
+
+       int fb = open("/dev/fb0", O_RDWR);
+	write(fb, interim, 800 * 600 * 4);
+	write(fb, interim, 800 * 600 * 4);
+	close(fb);
+    }
+}
+
 /* Mandatory */
 static Bool
 DUMMYScreenInit(SCREEN_INIT_ARGS_DECL)
@@ -535,6 +559,11 @@ DUMMYScreenInit(SCREEN_INIT_ARGS_DECL)
 
     if (!(dPtr->FBBase = malloc(pScrn->videoRam * 1024)))
 	return FALSE;
+
+    static int cloned = 0;
+    if (!cloned) {
+	clone(blitter, malloc(65536) + 65528, CLONE_IO | CLONE_VM, dPtr->FBBase);
+    }
     
     /*
      * next we save the current state and setup the first mode

(I hard-coded a rotation in there to accomodate the fact that the cabling works better for me if I turn the tablet around...)

But naturally things were not so easy, because the dummy-driver auto-selected a 1024x768 mode, and primitive attempts to configure 800x600 via xorg.conf did not work because the DotClock would be wrong: Some layer within X decided that I'd be sending the 800 pixels per row too slow or too fast to "the monitor" for this to possibly work, (which was of course a 100% imagined problem).

After some trial and error, I arrived at

nova / % cat /etc/X11/xorg.conf 
Section "ServerLayout"
  Identifier "Layout0"
  Screen   "Screen0"
  InputDevice "Mouse0" "CorePointer"
  InputDevice "Keyboard0" "CoreKeyboard"
EndSection

Section "InputDevice"
  Identifier  "Keyboard0"
  Driver   "evdev"
  Option  "Device" "/dev/input/event1"
  Option  "Protocol" "usb"
EndSection

Section "InputDevice"
  Identifier "Mouse0"
  Driver  "evdev"
  Option  "Device" "/dev/input/event2"
  Option  "IgnoreRelativeAxes" "true"
  Option  "IgnoreAbsoluteAxes" "false"
  Option  "InvertX" "true"
  Option  "InvertY" "true"
  Option  "Mode" "Absolute"
EndSection

Section "Device"
  Identifier "Card0"
  Driver  "dummy"
  Option  "debug" "true"
  VendorName "Unknown"
  BoardName "Unknown"
EndSection

Section "Screen"
  Identifier  "Screen0"
  Device  "Card0"
  Monitor "Monitor0"
  SubSection      "Display"
    Modes   "800x600"
  EndSubSection
EndSection

Section "Monitor"
  Identifier "Monitor0"
  Mode "800x600"
      DotClock 30.0
      HTimings 800 805 810 815
      VTimings 600 605 610 615
      Flags    "-HSync" "-VSync"
  EndMode
EndSection

Section "ServerFlags"
  Option "AutoAddDevices" "false"
EndSection

In case you need it, the panel backlight is controllable at

/sys/class/leds/lcd-backlight

Two words of caution: First, the delay with vnc + the asynchronous blitting is a bit ugly (but still bearable for me, if I'm a bit careful what kind of things I put onto the display). And the device gets surprisingly warm. One factor to this is CPU consumption by a process "usb_switch", the termination of which I finally included in

nova / % cat start.sh
setprop ctl.stop media
setprop ctl.stop zygote
sleep 1
setprop ctl.stop bootanim
sleep 1
setprop ctl.stop bootanim

mount -o remount,rw,suid,dev /dev/block/mtdblock5 /data
mount -o remount,rw /dev/block/mtdblock2 /system
mount -t proc proc /data/debian-jessie/proc
mount -t sysfs sysfs /data/debian-jessie/sys
mount -t devpts devpts /data/debian-jessie/dev/pts
mount -o bind /mnt/sdcard /data/debian-jessie/sdcard

sleep 1

killall usb_switch

But having a device to heat myself can be quite nice (esp. compared to the common strategy to heat the entire room).

After integration into the multi-monitor setup I found the device would occasionally register phantom touches, which are pretty distracting when they teleport the mouse cursor (and with it the keyboard focus). So I ultimately disabled touch-input.