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 :-)
The following notations will be used in this document.
[host]# cat /proc/cpuinfo processor : 0 vendor_id : GenuineIntel cpu family : 6 ...
[kobo]# cat /proc/cpuinfo Processor : ARMv7 Processor rev 5 (v7l) BogoMIPS : 799.53 Features : swp half thumb fastmult vfp edsp neon vfpv3 ...
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:
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 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.
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:
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:
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 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
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
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
[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
[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
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 :-)

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
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.