Hacking the Kobo Touch
for
DUMMIES

Introduction

The purpose of this page is to describe my exploration of the Kobo Touch ebook reader. A basic knowledge of Linux is required, first because this is the OS running on the Kobo and also because I am exclusively working with Linux: Don't ask me how you can do the same on Windows :-)

Document History

Useful Links

Notations

The following notations will be used in this document.

Factory Reset

Hacking a device like the Kobo Touch is always a bit dangerous. It is obviously not going to explode by there is always a risk of making it unusable.

Fortunately, the Touch provides 2 methods to restore the original 1.9.0 firmware:

  1. If the original Nickel software is still working then
    1. Press the Settings icon.
    2. Select the Device Information icon.
    3. Select the FACTORY RESET button.
  2. else simply keep the Home button pressed during the boot process

It should be noted that a factory reset REMOVES ALL EBOOKS from the device. Ebooks bought from Kobo will be automatically reinstalled when the device is re-configured but other ebooks are definitely lost.

Remark: The device can be restarted by inserting a pin in the small hole at the back but that does not seem to restore the factory settings.

The Kobo upgrade mechanism

The method use by Kobo to upgrade the Touch firmware is quite simple: During start-up, if a file .kobo/KoboRoot.tgz is found in the public partition then it is extracted at the root / of the internal system partition.

A few other files can be involved during an upgrade (e.g. .kobo/upgrade and .kobo/Kobo.tgz) but only KoboRoot.tgz is needed to patch the Linux OS.

First steps inside the Kobo

Patching files at random is obviously not a good idea. The first step consists in obtaining the KoboRoot.tgz file corresponding to the current firmware:

  1. Backup your ebooks!!!!!
  2. Perform a factory reset (see above)
  3. Plug the Touch in your PC and start the setup procedure using the Kobo desktop application.
  4. Save the downloaded file KoboRoot.tgz before ejecting the upgrade.On my system, the file is located in the directory /media/KOBOeReader/.kobo/ )

Now that we have a KoboRoot.tgz, we can extract its content:

tar xvzf KoboRoot.tgz

This is not the complete system but only the modified files since firmware 1.9.0: Here is the list of files provided for firmware 1.9.5

The interesting file is etc/init.d/rcS. This is the shell script called at boot by the init process.

An analysis of that file reveals a few interesting information:

  1. The device /dev/mmcblk0p3 provides a vfat partition mounted as /mnt/onboard. This is the partition provided to the user via usb.
  2. The script is also responsible for upgrading the system (using KoboRoot.tgz)
  3. The graphical application /usr/local/Kobo/nickel is started at the end.

Shell commands can be added to that script. In order to be flexible, I added a single line at the end to execute the shell script run.sh in the user partition:

/mnt/onboard/run.sh &

The patched script can be packaged and installed as follow:

[host]# tar czf KoboRoot.tgz etc/init.d/rcS
[host]# cp KoboRoot.tgz /media/KOBOeReader/.kobo/

A file /media/KOBOeReader/run.sh can contain arbitrary command. It does not have to be made executable (that does not make sense on a vfat partition). For example, the file

cat /proc/cpuinfo > /mnt/onboard/LOG

should produce (after a reboot) a file /media/KOBOeReader/LOG looking like that

Processor       : ARMv7 Processor rev 5 (v7l)
BogoMIPS        : 799.53
Features        : swp half thumb fastmult vfp edsp neon vfpv3 
CPU implementer : 0x41
CPU architecture: 7
CPU variant     : 0x2
CPU part        : 0xc08
CPU revision    : 5
Hardware        : Freescale MX50 ARM2 Board
Revision        : 50011
Serial          : 0000000000000000

Busybox

Busybox is a single program that implements several UNIX utilities. It is commonly used on embedded system where memory and disk space are more limited resources than on a desktop or server system.

The version of Busybox found in version firmware 1.9.5 provides a large number of tools:

[kobo]# busybox
BusyBox v1.17.1 (2011-05-30 22:37:11 EDT) multi-call binary.
Copyright (C) 1998-2009 Erik Andersen, Rob Landley, Denys Vlasenko
and others. Licensed under GPLv2.
See source distribution for full notice.

Usage: busybox [function] [arguments]...
   or: function [arguments]...

        BusyBox is a multi-call binary that combines many common Unix
        utilities into a single executable.  Most people will create a
        link to busybox for each function they wish to use and BusyBox
        will act like whatever it was invoked as.

Currently defined functions:
        [, [[, acpid, addgroup, adduser, adjtimex, arp, arping, ash, awk, basename, beep,
        blkid, bootchartd, brctl, bunzip2, bzcat, bzip2, cal, cat, catv, chat, chattr,
        chgrp, chmod, chown, chpasswd, chpst, chroot, chrt, chvt, cksum, clear, cmp,
        comm, cp, cpio, crond, crontab, cryptpw, cttyhack, cut, date, dc, dd, deallocvt,
        delgroup, deluser, depmod, devmem, df, dhcprelay, diff, dirname, dmesg, dnsd,
        dnsdomainname, dos2unix, du, dumpkmap, dumpleases, echo, ed, egrep, eject, env,
        envdir, envuidgid, ether-wake, expand, expr, fakeidentd, false, fbset, fbsplash,
        fdflush, fdformat, fdisk, fgconsole, fgrep, find, findfs, flock, fold, free,
        freeramdisk, fsck, fsck.minix, fsync, ftpd, ftpget, ftpput, fuser, getopt, getty,
        grep, gunzip, gzip, halt, hd, hdparm, head, hexdump, hostid, hostname, httpd,
        hush, hwclock, id, ifconfig, ifdown, ifenslave, ifplugd, ifup, inetd, init,
        insmod, install, ionice, ip, ipaddr, ipcalc, ipcrm, ipcs, iplink, iproute,
        iprule, iptunnel, kbd_mode, kill, killall, killall5, klogd, last, length, less,
        linux32, linux64, linuxrc, ln, loadfont, loadkmap, logger, login, logname,
        logread, losetup, lpd, lpq, lpr, ls, lsattr, lsmod, lspci, lsusb, lzcat, lzma,
        lzop, lzopcat, makedevs, makemime, man, md5sum, mdev, mesg, microcom, mkdir,
        mkdosfs, mke2fs, mkfifo, mkfs.ext2, mkfs.ext3, mkfs.minix, mkfs.vfat, mknod,
        mkpasswd, mkswap, mktemp, modinfo, modprobe, more, mount, mountpoint, mt, mv,
        nameif, nc, netstat, nice, nmeter, nohup, nslookup, ntpd, od, openvt, passwd,
        patch, pgrep, pidof, ping, ping6, pipe_progress, pivot_root, pkill, popmaildir,
        poweroff, printenv, printf, ps, pscan, pwd, raidautorun, rdate, rdev, readahead,
        readlink, readprofile, realpath, reboot, reformime, renice, reset, resize, rev,
        rm, rmdir, rmmod, route, rpm, rpm2cpio, rtcwake, run-parts, runlevel, runsv,
        runsvdir, rx, script, scriptreplay, sed, sendmail, seq, setarch, setconsole,
        setfont, setkeycodes, setlogcons, setsid, setuidgid, sh, sha1sum, sha256sum,
        sha512sum, showkey, slattach, sleep, smemcap, softlimit, sort, split,
        start-stop-daemon, stat, strings, stty, su, sulogin, sum, sv, svlogd, swapoff,
        swapon, switch_root, sync, sysctl, syslogd, tac, tail, tar, tcpsvd, tee, telnet,
        telnetd, test, tftp, tftpd, time, timeout, top, touch, tr, traceroute,
        traceroute6, true, tty, ttysize, tunctl, udhcpc, udhcpd, udpsvd, umount, uname,
        unexpand, uniq, unix2dos, unlzma, unlzop, unxz, unzip, uptime, usleep, uudecode,
        uuencode, vconfig, vi, vlock, volname, wall, watch, watchdog, wc, wget, which,
        who, whoami, xargs, xz, xzcat, yes, zcat, zcip

The standard way to execute an utility is to execute a symbolic link pointing to /bin/busybox. Most of the commands already have a symbolic link in one of the default paths (/bin, /sbin or /usr/bin)

[kobo]# ls -l /bin/d*
lrwxrwxrwx    1 root     root         7 Jul  1 19:06 /bin/date -> busybox
-rwxr-xr-x    1 root     root     20824 Jun 24  2010 /bin/dbus-cleanup-sockets
-rwxr-xr-x    1 root     root   1115445 Jun 24  2010 /bin/dbus-daemon
-rwxr-xr-x    1 root     root     34275 Jun 24  2010 /bin/dbus-launch
-rwxr-xr-x    1 root     root     27406 Jun 24  2010 /bin/dbus-monitor
-rwxr-xr-x    1 root     root     32444 Jun 24  2010 /bin/dbus-send
-rwxr-xr-x    1 root     root     13880 Jun 24  2010 /bin/dbus-uuidgen
lrwxrwxrwx    1 root     root         7 Jul  1 19:06 /bin/dd -> busybox
lrwxrwxrwx    1 root     root         7 Jul  1 19:06 /bin/df -> busybox
-rwxr-xr-x    1 root     root     31566 Jun 24  2010 /bin/djpeg
lrwxrwxrwx    1 root     root         7 Jul  1 19:06 /bin/dmesg -> busybox
-rwxr-xr-x    1 root     root     48792 Jul  7  2010 /bin/dosfsck
lrwxrwxrwx    1 root     root         7 Jul  1 19:06 /bin/dumpkmap -> busybox

[kobo]# /bin/date -u
Sat Jul  2 12:21:03 UTC 2011

Alternatively, busybox can be called directly with the desired command as first arguments:

[kobo]# busybox date -u
Sat Jul  2 12:21:17 UTC 2011

For more details about the different commands and their arguments, see the Command Help page on the busybox web site

Install telnet and ftp

The run.sh trick can be used to explore the kobo internals but the method remains slow. Fortunately, Busybox provides all tools needed to control the Touch via the WiFi connection.

The patch to enable telnet & ftp on the Kobo Touch is similar to the one used for the Kobo Wifi

The shell script /etc/init.d/rcS2 provides the pseudo-devices needed by telnet. Other commands can be added if needed.

#!/bin/sh
mkdir -p /dev/pts
mount -t devpts devpts /dev/pts

The file /etc/inetd.conf configures the services managed by inetd.

# service_name sock_type proto flags user server_path args
21 stream tcp nowait root /bin/busybox ftpd -w -S  /
23 stream tcp nowait root /bin/busybox telnetd -i

Finally, the file /etc/inittab must be EXTENDED to execute /etc/init.d/rcS2 and inetd. The original file can be obtained using run.sh

... insert here the original /etc/inittab
::sysinit:/etc/init.d/rcS2
::respawn:/usr/sbin/inetd -f /etc/inetd.conf

An upgrade file containing all those files is available here. Be aware that the file /etc/inittab is based on the version from firmware 1.9.5. Future firmware versions may require a modified file (though this is unlikely).

Copy the provided file as .kobo/KoboRoot.tgz, reboot, and you should be able to log in using telnet. Of course, you will have enable WiFi and to figure out the device IP address which is unfortunately not provided by the current GUI.

Use the root account. No password is required.

[host]# telnet 192.168.1.7 
Trying 192.168.1.7...
Connected to 192.168.1.7.
Escape character is '^]'.

(none) login: root
[kobo]# uname -a
Linux (none) 2.6.35.3-568-g4cf53cf-00017-gd578834-dirty #58 PREEMPT 
  ... Mon Jun 27 10:13:55 EDT 2011 armv7l GNU/Linux

I noticed that the Nickel application (that is the Kobo graphical application), is stopping the WiFi after a few minutes of apparent inactivity so the first thing I usually do is to kill Nickel:

[kobo]# killall nickel

The hardware

Now that we have telnet and ftp, it becomes a lot easier to explore the Touch.

[kobo]# cat /proc/cpuinfo
Processor       : ARMv7 Processor rev 5 (v7l)
BogoMIPS        : 159.90
Features        : swp half thumb fastmult vfp edsp neon vfpv3 
CPU implementer : 0x41
CPU architecture: 7
CPU variant     : 0x2
CPU part        : 0xc08
CPU revision    : 5
Hardware        : Freescale MX50 ARM2 Board
Revision        : 50011
Serial          : 0000000000000000

[kobo]# cat /sys/devices/system/cpu/cpu0/cpufreq/cpuinfo_min_freq
160000
[kobo]# cat /sys/devices/system/cpu/cpu0/cpufreq/cpuinfo_max_freq
800000

So the Touch is based on a Freescale MX50 which uses a Cortex A8 at 800 Mhz.

[kobo]# cat /proc/meminfo 
MemTotal:         256124 kB
MemFree:          163612 kB
Buffers:            2012 kB
Cached:            48484 kB
SwapCached:            0 kB
...
SwapTotal:             0 kB
SwapFree:              0 kB
...

We have 256MB of main memory but no swap. That's more than my first Linux PC in 1996.

[kobo]# df
Filesystem           1K-blocks      Used Available Use% Mounted on
/dev/root               242078    111892    130186  46% /
none                     10240       116     10124   1% /tmp
none                    128060         4    128056   0% /dev
none                        16         0        16   0% /var/log
none                       128         8       120   6% /var/run
/dev/mmcblk0p3         1434600     19840   1414760   1% /mnt/onboard

[kobo]# fdisk /dev/mmcblk0

The number of cylinders for this disk is set to 61056.
There is nothing wrong with that, but this is larger than 1024,
and could in certain setups cause problems with:
1) software that runs at boot time (e.g., old versions of LILO)
2) booting and partitioning software from other OSs
   (e.g., DOS FDISK, OS/2 FDISK)

Command (m for help): p

Disk /dev/mmcblk0: 2000 MB, 2000683008 bytes
4 heads, 16 sectors/track, 61056 cylinders
Units = cylinders of 64 * 512 = 32768 bytes

        Device Boot      Start         End      Blocks  Id System
/dev/mmcblk0p1             513        8325      250000+ 83 Linux
/dev/mmcblk0p2            8325       16138      250000+ 83 Linux
/dev/mmcblk0p3           16138       61056     1437407   c Win95 FAT32 (LBA)

Command (m for help): q

The internal flash memory contains 3 partitions:

Remark: The root partition / was mounted using /dev/root which is now hidden by udev which provides it as /dev/mmcblk0p1.

[kobo]# top 
Mem: 92624K used, 163500K free, 0K shrd, 2020K buff, 48484K cached
CPU:  0.3% usr  0.9% sys  0.0% nic 98.6% idle  0.0% io  0.0% irq  0.0% sirq
Load average: 2.24 2.16 1.94 1/47 1914
  PID  PPID USER     STAT   VSZ %MEM CPU %CPU COMMAND
 1908  1559 root     R     3240  1.2   0  0.5 top
 1495     1 root     S     8032  3.1   0  0.2 /usr/local/Kobo/fickel info linkquality
 1472     1 root     S     3964  1.5   0  0.0 wpa_supplicant -s -i wlan0 -c /etc/wpa_suppl
  944   806 root     S     3604  1.4   0  0.0 telnetd -i
  945   944 root     S     3240  1.2   0  0.0 -sh
  805     1 root     S     3240  1.2   0  0.0 /sbin/getty -L ttymxc0 115200 vt100
    1     0 root     S     3236  1.2   0  0.0 init
  806     1 root     S     3236  1.2   0  0.0 /usr/sbin/inetd -f /etc/inetd.conf
  468     1 root     S <   2312  0.9   0  0.0 /sbin/udevd -d
 1400     2 root     SW       0  0.0   0  0.0 [ksdioirqd/mmc1]
    4     2 root     SW       0  0.0   0  0.0 [events/0]
  447     2 root     SW       0  0.0   0  0.0 [mmcqd]
  159     2 root     SW       0  0.0   0  0.0 [kmmcd]
  428     2 root     SW       0  0.0   0  0.0 [esdhc_wq/0]
 1392     2 root     SW       0  0.0   0  0.0 [AR6K Async]
  440     2 root     SW       0  0.0   0  0.0 [submit/0]
  132     2 root     SW       0  0.0   0  0.0 [mxc_spi.2]
  426     2 root     SW       0  0.0   0  0.0 [esdhc_wq/0]
  808     2 root     SW       0  0.0   0  0.0 [neonode_ts]
    5     2 root     SW       0  0.0   0  0.0 [khelper]
  187     2 root     SW       0  0.0   0  0.0 [pmic-event-thre]
  453     2 root     SW       0  0.0   0  0.0 [jbd2/mmcblk0p1-]
  ...

No real surprise in the process list

Keyboard and TouchScreen

[kobo]# cat /proc/bus/input/handlers 
N: Number=0 Name=kbd
N: Number=1 Name=evdev Minor=64

[kobo]# cat /proc/bus/input/devices 
I: Bus=0019 Vendor=0000 Product=0000 Version=0000
N: Name="mxc_power_key"
P: Phys=mxcpwrkey/input0
S: Sysfs=/devices/virtual/input/input0
U: Uniq=
H: Handlers=kbd event0 
B: EV=3
B: KEY=100040 0 0 0

I: Bus=0018 Vendor=0000 Product=0000 Version=0000
N: Name="neonode_ts"
P: Phys=
S: Sysfs=/devices/virtual/input/input1
U: Uniq=
H: Handlers=event1 
B: EV=b
B: KEY=400 0 0 0 0 0 0 0 0 0 0
B: ABS=1000003

The suffix '_ts' probably means 'Touch Screen' so the input device 1 should provide the touch events using the evdev protocol. The program evtest is already installed and can be used to dump the events while I move my finger on the screen:

[kobo]# evdev /dev/input/event1 
evtest  /dev/input/event1 
Input driver version is 1.0.0
Input device ID: bus 0x18 vendor 0x0 product 0x0 version 0x0
Input device name: "neonode_ts"
Supported events:
  Event type 0 (Sync)
  Event type 1 (Key)
    Event code 330 (Touch)
  Event type 3 (Absolute)
    Event code 0 (X)
      Value    592
      Min        0
      Max        0
    Event code 1 (Y)
      Value    584
      Min        0
      Max        0
    Event code 24 (Pressure)
      Value      0
      Min        0
      Max        0
Testing ... (interrupt to exit)
Event: time 6856.978099 ------- Report Sync ------ x: 428 y: 281 p: 101 ------------
Event: time 6856.994670 ------- Report Sync ------ x: 427 y: 281 p: 100 ------------
Event: time 6857.043691 ------- Report Sync ------ x: 428 y: 280 p: 101 ------------
Event: time 6857.060004 ------- Report Sync ------ x: 428 y: 279 p: 100 ------------
Event: time 6857.071023 ------- Report Sync ------ x: 428 y: 277 p: 0 ------------
Event: time 6858.035147 ------- Report Sync ------ x: 452 y: 324 p: 100 ------------
Event: time 6858.117210 ------- Report Sync ------ x: 450 y: 325 p: 101 ------------
Event: time 6858.133273 ------- Report Sync ------ x: 449 y: 326 p: 100 -----------
...

So the touchscreen works like a mouse. The events with p=0 indicate when my finger leave the screen. Double tab or two-finger events do not seem to exist. I also noticed that the touchscreen is very reactive. The lags encountered in Nickel are probably caused by the application and not by the hardware.

The device is not using the evdev protocol and so cannot be dumped with evtest. I can however, use hexdump (also installed by default) to display the raw data. Luckily the keyboard events are exactly 16 bytes wide soo the output is very readable:

[kobo]# hexdump  /dev/input/event0
0000000 1ea5 0000 4c23 000a 0001 0066 0001 0000          <--- press   HOME
0000010 1ea5 0000 973b 000c 0001 0066 0000 0000          <--- release HOME  
0000020 1ea6 0000 1d4d 0006 0001 0066 0001 0000          <--- press   HOME
0000030 1ea6 0000 cf61 0009 0001 0066 0000 0000          <--- release HOME
0000040 1eb1 0000 5022 0008 0001 0074 0001 0000          <--- press   SLEEP
0000050 1eb1 0000 0da5 000b 0001 0074 0000 0000          <--- release SLEEP
0000060 1ebc 0000 d7b3 0008 0001 0074 0001 0000          <--- press   SLEEP
0000070 1ebc 0000 54b0 000a 0001 0074 0000 0000          <--- release SLEEP
...

So the keycodes in red are 0x66 for HOME and 0x74 for SLEEP. The blue column is the event type and the rest probably provides the timestamp and other useless information. Decoding should be easy

The Screen

[kobo]# cat /sys/class/graphics/fb0/name
mxc_epdc_fb
[kobo]# cat /sys/class/graphics/fb0/modes 
U:800x600p-0
U:800x600p-0
[kobo]# cat /sys/class/graphics/fb0/virtual_size 
800,1280
[kobo]# cat /sys/class/graphics/fb0/bits_per_pixel 
16

So we have a 800x600 screen in a 800x1280 virtual screen. Each pixel occupies 16bits (2bytes) so the whole virtual screen uses 800*1280*2 = 2048000 bytes. We can assume that the video memory is 2MB = 2097152 bytes.

At first, I assumed that that the pixel format was 16bit grayscale (65536 levels). In fact this is the classical RGB565 format: 5bits red + 6bit green + 5 bit blue. This is confirmed by the command fbset:

[kobo]# fbset 

mode "800x600-0"
        # D: 0.000 MHz, H: 0.000 kHz, V: 0.000 Hz
        geometry 800 600 800 1280 16
        timings 0 0 0 0 0 0 0
        accel false
        rgba 5/11,6/5,5/0,0/0
endmode

How to Take a Screenshot

The whole content of the graphical memory can be obtained from the device /dev/fb0.

[kobo]# cat /dev/fb0 > SCREEN
[kobo]# ls -l SCREEN
-rw-r--r--    1 root   root  2179072 Jul  2 18:00 SCREEN

The resulting file is about 2MB. Most of the time we are only interested by the first 800x600 pixels = 800*600*2 = 960000 bytes. The dd tool can be used to get only those bytes:

[kobo]# dd count=1 bs=960000 if=/dev/fb0 of=SCREEN
1+0 records in
1+0 records out
960000 bytes (937.5KB) copied, 0.185473 seconds, 4.9MB/s
[kobo]# ls -l SCREEN
-rw-r--r--    1 root   root  960000 Jul  2 18:06 SCREEN

The resulting SCREEN file must be transferred via FTP to the host PC to be transformed into a usable image. On Linux, I typically use the ImageMagick toolsuite for that kind of operations. Unfortunately, ImageMagick does not support the RGB565 pixel format so I wrote a wrote a small C program called rgb565torgb to transform RGB565 pixels into the usual RGB888 format:

#include 
#include 

int main(void)
{
  unsigned char pix565[2] ;
  while ( read(0,pix565,2)==2 ) 
    {
      unsigned short val = pix565[1]*256 + pix565[0] ; // little endian

      int r5 = (val>>11) & 0x1F ; // 5 bits
      int g6 = (val>>5 ) & 0x3F ; // 6 bits
      int b5 = (val    ) & 0x1F ; // 5 bits
 
      int r8 = (r5<<3)+(r5>>2) ; // abcde  -> abcdeabc 
      int g8 = (g6<<2)+(g6>>4) ; // abcdef -> abcdefab 
      int b8 = (b5<<3)+(b5>>2) ; // abcdef -> abcdeabc 

#if 0
      printf("%5d : %3d %3d %3d\n",val,r8,g8,b8) ;
#else
      putchar(r8) ;
      putchar(g8) ;
      putchar(b8) ;
#endif
    }
  return 0;
}

Using that program, it becomes possible to use the ImageMagic tools (convert, display, ...) to display the screenshot

[host]# ./rgb565torgb < SCREEN | display -depth 8 -size 800x600 -rotate 90 rgb:-

or to convert it to a known image format.

[host]# ./rgb565torgb < SCREEN | convert -depth 8 -size 800x600 -rotate 90 rgb:- parrot.jpg

Look! I have a Color Touch :-)

Kobo Touch Screenshot

Writing to the Screen

Writing into the graphical memory should not be a problem (via the framebuffer device /dev/fb0). However, on an E-ink device the screen must be refreshed explicitly using some special system calls (ioctl) about which I currently know nothing. I can just hope that they will be documented in the upcoming relase of the Linux kernel sources by the Kobo developers.

The start-up script /etc/init.d/rcS is using the Kobo application /usr/local/Kobo/pickel to update the screen using some RGB565 compressed images stored in /etc/images. For example, the following command displays the 'Powering On...' screen:

[kobo]# zcat /etc/images/splash.raw.gz | /usr/local/Kobo/pickel showpic 

The same method can be used to restore the screenshot file obtained previously:

[kobo]# cat ./SCREEN | /usr/local/Kobo/pickel showpic 

That's all folks!

The next step for me will be to install an ARM toolchain to compile my own programs.

If you have questions, remarks or useful information then you can send me an email using this form (please start the suject with [KOBO].) or try the Kobo forum at mobileread.com.